summaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:15:43 +0000
commitf5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch)
tree49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib
parentInitial commit. (diff)
downloadisc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz
isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/lib/Makefile.am18
-rw-r--r--src/lib/Makefile.in739
-rw-r--r--src/lib/asiodns/Makefile.am72
-rw-r--r--src/lib/asiodns/Makefile.in1022
-rw-r--r--src/lib/asiodns/README154
-rw-r--r--src/lib/asiodns/asiodns_messages.cc73
-rw-r--r--src/lib/asiodns/asiodns_messages.h40
-rw-r--r--src/lib/asiodns/asiodns_messages.mes167
-rw-r--r--src/lib/asiodns/io_fetch.cc402
-rw-r--r--src/lib/asiodns/io_fetch.h246
-rw-r--r--src/lib/asiodns/logger.cc19
-rw-r--r--src/lib/asiodns/logger.h18
-rw-r--r--src/lib/asiodns/tests/Makefile.am43
-rw-r--r--src/lib/asiodns/tests/Makefile.in896
-rw-r--r--src/lib/asiodns/tests/io_fetch_unittest.cc738
-rw-r--r--src/lib/asiodns/tests/run_unittests.cc21
-rw-r--r--src/lib/asiolink/Makefile.am100
-rw-r--r--src/lib/asiolink/Makefile.in1177
-rw-r--r--src/lib/asiolink/README28
-rw-r--r--src/lib/asiolink/addr_utilities.cc414
-rw-r--r--src/lib/asiolink/addr_utilities.h99
-rw-r--r--src/lib/asiolink/asio_wrapper.h82
-rw-r--r--src/lib/asiolink/asiolink.dox103
-rw-r--r--src/lib/asiolink/asiolink.h67
-rw-r--r--src/lib/asiolink/botan_boost_tls.cc339
-rw-r--r--src/lib/asiolink/botan_boost_tls.h207
-rw-r--r--src/lib/asiolink/botan_boost_wrapper.h32
-rw-r--r--src/lib/asiolink/botan_tls.cc60
-rw-r--r--src/lib/asiolink/botan_tls.h168
-rw-r--r--src/lib/asiolink/common_tls.cc65
-rw-r--r--src/lib/asiolink/common_tls.h183
-rw-r--r--src/lib/asiolink/crypto_tls.h27
-rw-r--r--src/lib/asiolink/dummy_io_cb.h65
-rw-r--r--src/lib/asiolink/interval_timer.cc202
-rw-r--r--src/lib/asiolink/interval_timer.h142
-rw-r--r--src/lib/asiolink/io_acceptor.h136
-rw-r--r--src/lib/asiolink/io_address.cc194
-rw-r--r--src/lib/asiolink/io_address.h329
-rw-r--r--src/lib/asiolink/io_asio_socket.h381
-rw-r--r--src/lib/asiolink/io_endpoint.cc68
-rw-r--r--src/lib/asiolink/io_endpoint.h183
-rw-r--r--src/lib/asiolink/io_error.h29
-rw-r--r--src/lib/asiolink/io_service.cc159
-rw-r--r--src/lib/asiolink/io_service.h114
-rw-r--r--src/lib/asiolink/io_service_signal.cc146
-rw-r--r--src/lib/asiolink/io_service_signal.h60
-rw-r--r--src/lib/asiolink/io_service_thread_pool.cc276
-rw-r--r--src/lib/asiolink/io_service_thread_pool.h265
-rw-r--r--src/lib/asiolink/io_socket.cc57
-rw-r--r--src/lib/asiolink/io_socket.h128
-rw-r--r--src/lib/asiolink/openssl_tls.cc143
-rw-r--r--src/lib/asiolink/openssl_tls.h242
-rw-r--r--src/lib/asiolink/process_spawn.cc448
-rw-r--r--src/lib/asiolink/process_spawn.h149
-rw-r--r--src/lib/asiolink/tcp_acceptor.h69
-rw-r--r--src/lib/asiolink/tcp_endpoint.h115
-rw-r--r--src/lib/asiolink/tcp_socket.h503
-rw-r--r--src/lib/asiolink/tests/Makefile.am75
-rw-r--r--src/lib/asiolink/tests/Makefile.in1303
-rw-r--r--src/lib/asiolink/tests/addr_utilities_unittest.cc388
-rw-r--r--src/lib/asiolink/tests/dummy_io_callback_unittest.cc27
-rw-r--r--src/lib/asiolink/tests/hash_address_unittest.cc58
-rw-r--r--src/lib/asiolink/tests/interval_timer_unittest.cc377
-rw-r--r--src/lib/asiolink/tests/io_address_unittest.cc370
-rw-r--r--src/lib/asiolink/tests/io_endpoint_unittest.cc286
-rw-r--r--src/lib/asiolink/tests/io_service_signal_unittests.cc282
-rw-r--r--src/lib/asiolink/tests/io_service_thread_pool_unittests.cc264
-rw-r--r--src/lib/asiolink/tests/io_service_unittest.cc48
-rw-r--r--src/lib/asiolink/tests/io_socket_unittest.cc25
-rw-r--r--src/lib/asiolink/tests/process_spawn_app.sh.in75
-rw-r--r--src/lib/asiolink/tests/process_spawn_unittest.cc350
-rw-r--r--src/lib/asiolink/tests/run_unittests.cc19
-rw-r--r--src/lib/asiolink/tests/tcp_acceptor_unittest.cc444
-rw-r--r--src/lib/asiolink/tests/tcp_endpoint_unittest.cc46
-rw-r--r--src/lib/asiolink/tests/tcp_socket_unittest.cc515
-rw-r--r--src/lib/asiolink/tests/tls_acceptor_unittest.cc450
-rw-r--r--src/lib/asiolink/tests/tls_socket_unittest.cc560
-rw-r--r--src/lib/asiolink/tests/tls_unittest.cc2695
-rw-r--r--src/lib/asiolink/tests/udp_endpoint_unittest.cc46
-rw-r--r--src/lib/asiolink/tests/udp_socket_unittest.cc324
-rw-r--r--src/lib/asiolink/tests/unix_domain_socket_unittest.cc310
-rw-r--r--src/lib/asiolink/testutils/Makefile.am93
-rw-r--r--src/lib/asiolink/testutils/Makefile.in1076
-rw-r--r--src/lib/asiolink/testutils/botan_boost_sample_client.cc229
-rw-r--r--src/lib/asiolink/testutils/botan_boost_sample_server.cc220
-rw-r--r--src/lib/asiolink/testutils/ca/00af7a28.019
-rw-r--r--src/lib/asiolink/testutils/ca/0c7eedb9.024
-rw-r--r--src/lib/asiolink/testutils/ca/28f5a777.023
-rw-r--r--src/lib/asiolink/testutils/ca/2eefa08b.030
-rw-r--r--src/lib/asiolink/testutils/ca/71336a4d.023
-rw-r--r--src/lib/asiolink/testutils/ca/7a5b785e.023
-rw-r--r--src/lib/asiolink/testutils/ca/ad950210.024
-rw-r--r--src/lib/asiolink/testutils/ca/doc.txt129
-rw-r--r--src/lib/asiolink/testutils/ca/ext-addr-conf.cnf1
-rw-r--r--src/lib/asiolink/testutils/ca/ext-conf.cnf1
-rw-r--r--src/lib/asiolink/testutils/ca/kea-ca.crt30
-rw-r--r--src/lib/asiolink/testutils/ca/kea-ca.key54
-rw-r--r--src/lib/asiolink/testutils/ca/kea-client.crt23
-rw-r--r--src/lib/asiolink/testutils/ca/kea-client.csr16
-rw-r--r--src/lib/asiolink/testutils/ca/kea-client.key28
-rw-r--r--src/lib/asiolink/testutils/ca/kea-client.p12bin0 -> 2597 bytes
-rw-r--r--src/lib/asiolink/testutils/ca/kea-other.crt23
-rw-r--r--src/lib/asiolink/testutils/ca/kea-other.key28
-rw-r--r--src/lib/asiolink/testutils/ca/kea-self.crt19
-rw-r--r--src/lib/asiolink/testutils/ca/kea-self.key28
-rw-r--r--src/lib/asiolink/testutils/ca/kea-server-addr.crt24
-rw-r--r--src/lib/asiolink/testutils/ca/kea-server-addr.csr17
-rw-r--r--src/lib/asiolink/testutils/ca/kea-server-raw.crt23
-rw-r--r--src/lib/asiolink/testutils/ca/kea-server-raw.csr16
-rw-r--r--src/lib/asiolink/testutils/ca/kea-server.crt24
-rw-r--r--src/lib/asiolink/testutils/ca/kea-server.csr17
-rw-r--r--src/lib/asiolink/testutils/ca/kea-server.key28
-rw-r--r--src/lib/asiolink/testutils/ca/server-addr-conf.cnf355
-rw-r--r--src/lib/asiolink/testutils/ca/server-conf.cnf354
-rw-r--r--src/lib/asiolink/testutils/openssl_sample_client.cc187
-rw-r--r--src/lib/asiolink/testutils/openssl_sample_server.cc193
-rw-r--r--src/lib/asiolink/testutils/test_server_unix_socket.cc331
-rw-r--r--src/lib/asiolink/testutils/test_server_unix_socket.h171
-rw-r--r--src/lib/asiolink/testutils/test_tls.cc74
-rw-r--r--src/lib/asiolink/testutils/test_tls.h47
-rw-r--r--src/lib/asiolink/testutils/timed_signal.cc17
-rw-r--r--src/lib/asiolink/testutils/timed_signal.h86
-rw-r--r--src/lib/asiolink/tls_acceptor.h62
-rw-r--r--src/lib/asiolink/tls_socket.h519
-rw-r--r--src/lib/asiolink/udp_endpoint.h115
-rw-r--r--src/lib/asiolink/udp_socket.h323
-rw-r--r--src/lib/asiolink/unix_domain_socket.cc371
-rw-r--r--src/lib/asiolink/unix_domain_socket.h137
-rw-r--r--src/lib/asiolink/unix_domain_socket_acceptor.h65
-rw-r--r--src/lib/asiolink/unix_domain_socket_endpoint.h50
-rw-r--r--src/lib/cc/Makefile.am45
-rw-r--r--src/lib/cc/Makefile.in994
-rw-r--r--src/lib/cc/base_stamped_element.cc28
-rw-r--r--src/lib/cc/base_stamped_element.h80
-rw-r--r--src/lib/cc/cc.dox107
-rw-r--r--src/lib/cc/cfg_to_element.h48
-rw-r--r--src/lib/cc/command_interpreter.cc318
-rw-r--r--src/lib/cc/command_interpreter.h225
-rw-r--r--src/lib/cc/data.cc1633
-rw-r--r--src/lib/cc/data.h1075
-rw-r--r--src/lib/cc/dhcp_config_error.h72
-rw-r--r--src/lib/cc/element_value.h117
-rw-r--r--src/lib/cc/json_feed.cc563
-rw-r--r--src/lib/cc/json_feed.h351
-rw-r--r--src/lib/cc/server_tag.cc53
-rw-r--r--src/lib/cc/server_tag.h82
-rw-r--r--src/lib/cc/simple_parser.cc369
-rw-r--r--src/lib/cc/simple_parser.h339
-rw-r--r--src/lib/cc/stamped_element.cc50
-rw-r--r--src/lib/cc/stamped_element.h91
-rw-r--r--src/lib/cc/stamped_value.cc193
-rw-r--r--src/lib/cc/stamped_value.h237
-rw-r--r--src/lib/cc/tests/Makefile.am40
-rw-r--r--src/lib/cc/tests/Makefile.in1071
-rw-r--r--src/lib/cc/tests/command_interpreter_unittests.cc249
-rw-r--r--src/lib/cc/tests/data_file_unittests.cc98
-rw-r--r--src/lib/cc/tests/data_unittests.cc2232
-rw-r--r--src/lib/cc/tests/element_value_unittests.cc43
-rw-r--r--src/lib/cc/tests/json_feed_unittests.cc453
-rw-r--r--src/lib/cc/tests/run_unittests.cc20
-rw-r--r--src/lib/cc/tests/server_tag_unittest.cc97
-rw-r--r--src/lib/cc/tests/simple_parser_unittest.cc364
-rw-r--r--src/lib/cc/tests/stamped_element_unittest.cc132
-rw-r--r--src/lib/cc/tests/stamped_value_unittest.cc175
-rw-r--r--src/lib/cc/tests/user_context_unittests.cc132
-rw-r--r--src/lib/cc/user_context.cc30
-rw-r--r--src/lib/cc/user_context.h59
-rw-r--r--src/lib/config/Makefile.am85
-rw-r--r--src/lib/config/Makefile.in1037
-rw-r--r--src/lib/config/base_command_mgr.cc222
-rw-r--r--src/lib/config/base_command_mgr.h218
-rw-r--r--src/lib/config/client_connection.cc285
-rw-r--r--src/lib/config/client_connection.h159
-rw-r--r--src/lib/config/cmd_http_listener.cc165
-rw-r--r--src/lib/config/cmd_http_listener.h160
-rw-r--r--src/lib/config/cmd_response_creator.cc176
-rw-r--r--src/lib/config/cmd_response_creator.h132
-rw-r--r--src/lib/config/cmd_response_creator_factory.h63
-rw-r--r--src/lib/config/cmds_impl.h78
-rw-r--r--src/lib/config/command-socket.dox188
-rw-r--r--src/lib/config/command_mgr.cc667
-rw-r--r--src/lib/config/command_mgr.h94
-rw-r--r--src/lib/config/config_log.cc22
-rw-r--r--src/lib/config/config_log.h29
-rw-r--r--src/lib/config/config_messages.cc77
-rw-r--r--src/lib/config/config_messages.h42
-rw-r--r--src/lib/config/config_messages.mes143
-rw-r--r--src/lib/config/hooked_command_mgr.cc133
-rw-r--r--src/lib/config/hooked_command_mgr.h91
-rw-r--r--src/lib/config/tests/Makefile.am50
-rw-r--r--src/lib/config/tests/Makefile.in1108
-rw-r--r--src/lib/config/tests/client_connection_unittests.cc183
-rw-r--r--src/lib/config/tests/cmd_http_listener_unittests.cc980
-rw-r--r--src/lib/config/tests/cmd_response_creator_factory_unittests.cc71
-rw-r--r--src/lib/config/tests/cmd_response_creator_unittests.cc438
-rw-r--r--src/lib/config/tests/command_mgr_unittests.cc570
-rw-r--r--src/lib/config/tests/data_def_unittests_config.h.in7
-rw-r--r--src/lib/config/tests/run_unittests.cc18
-rw-r--r--src/lib/config/tests/testdata/Makefile.am59
-rw-r--r--src/lib/config/tests/testdata/Makefile.in560
-rw-r--r--src/lib/config/tests/testdata/data22_1.data9
-rw-r--r--src/lib/config/tests/testdata/data22_10.data11
-rw-r--r--src/lib/config/tests/testdata/data22_2.data8
-rw-r--r--src/lib/config/tests/testdata/data22_3.data8
-rw-r--r--src/lib/config/tests/testdata/data22_4.data8
-rw-r--r--src/lib/config/tests/testdata/data22_5.data8
-rw-r--r--src/lib/config/tests/testdata/data22_6.data10
-rw-r--r--src/lib/config/tests/testdata/data22_7.data10
-rw-r--r--src/lib/config/tests/testdata/data22_8.data10
-rw-r--r--src/lib/config/tests/testdata/data22_9.data11
-rw-r--r--src/lib/config/tests/testdata/data32_1.data3
-rw-r--r--src/lib/config/tests/testdata/data32_2.data3
-rw-r--r--src/lib/config/tests/testdata/data32_3.data3
-rw-r--r--src/lib/config/tests/testdata/data33_1.data7
-rw-r--r--src/lib/config/tests/testdata/data33_2.data7
-rw-r--r--src/lib/config/tests/testdata/data41_1.data12
-rw-r--r--src/lib/config/tests/testdata/data41_2.data16
-rw-r--r--src/lib/config/tests/testdata/spec1.spec6
-rw-r--r--src/lib/config/tests/testdata/spec10.spec13
-rw-r--r--src/lib/config/tests/testdata/spec11.spec13
-rw-r--r--src/lib/config/tests/testdata/spec12.spec13
-rw-r--r--src/lib/config/tests/testdata/spec13.spec13
-rw-r--r--src/lib/config/tests/testdata/spec14.spec13
-rw-r--r--src/lib/config/tests/testdata/spec15.spec13
-rw-r--r--src/lib/config/tests/testdata/spec16.spec7
-rw-r--r--src/lib/config/tests/testdata/spec17.spec17
-rw-r--r--src/lib/config/tests/testdata/spec18.spec12
-rw-r--r--src/lib/config/tests/testdata/spec19.spec13
-rw-r--r--src/lib/config/tests/testdata/spec2.spec83
-rw-r--r--src/lib/config/tests/testdata/spec20.spec18
-rw-r--r--src/lib/config/tests/testdata/spec21.spec7
-rw-r--r--src/lib/config/tests/testdata/spec22.spec114
-rw-r--r--src/lib/config/tests/testdata/spec23.spec18
-rw-r--r--src/lib/config/tests/testdata/spec24.spec18
-rw-r--r--src/lib/config/tests/testdata/spec25.spec7
-rw-r--r--src/lib/config/tests/testdata/spec26.spec6
-rw-r--r--src/lib/config/tests/testdata/spec27.spec121
-rw-r--r--src/lib/config/tests/testdata/spec28.spec7
-rw-r--r--src/lib/config/tests/testdata/spec29.spec35
-rw-r--r--src/lib/config/tests/testdata/spec3.spec13
-rw-r--r--src/lib/config/tests/testdata/spec30.spec45
-rw-r--r--src/lib/config/tests/testdata/spec31.spec63
-rw-r--r--src/lib/config/tests/testdata/spec32.spec72
-rw-r--r--src/lib/config/tests/testdata/spec33.spec50
-rw-r--r--src/lib/config/tests/testdata/spec34.spec14
-rw-r--r--src/lib/config/tests/testdata/spec35.spec15
-rw-r--r--src/lib/config/tests/testdata/spec36.spec17
-rw-r--r--src/lib/config/tests/testdata/spec37.spec7
-rw-r--r--src/lib/config/tests/testdata/spec38.spec17
-rw-r--r--src/lib/config/tests/testdata/spec39.spec21
-rw-r--r--src/lib/config/tests/testdata/spec4.spec12
-rw-r--r--src/lib/config/tests/testdata/spec40.spec21
-rw-r--r--src/lib/config/tests/testdata/spec41.spec35
-rw-r--r--src/lib/config/tests/testdata/spec42.spec17
-rw-r--r--src/lib/config/tests/testdata/spec5.spec12
-rw-r--r--src/lib/config/tests/testdata/spec6.spec12
-rw-r--r--src/lib/config/tests/testdata/spec7.spec5
-rw-r--r--src/lib/config/tests/testdata/spec8.spec3
-rw-r--r--src/lib/config/tests/testdata/spec9.spec13
-rw-r--r--src/lib/config/timeouts.h44
-rw-r--r--src/lib/config_backend/Makefile.am25
-rw-r--r--src/lib/config_backend/Makefile.in804
-rw-r--r--src/lib/config_backend/base_config_backend.h87
-rw-r--r--src/lib/config_backend/base_config_backend_mgr.h220
-rw-r--r--src/lib/config_backend/base_config_backend_pool.h649
-rw-r--r--src/lib/config_backend/constants.h90
-rw-r--r--src/lib/config_backend/tests/Makefile.am37
-rw-r--r--src/lib/config_backend/tests/Makefile.in1006
-rw-r--r--src/lib/config_backend/tests/config_backend_mgr_unittest.cc557
-rw-r--r--src/lib/config_backend/tests/run_unittests.cc19
-rw-r--r--src/lib/cryptolink/Makefile.am52
-rw-r--r--src/lib/cryptolink/Makefile.in1003
-rw-r--r--src/lib/cryptolink/botan_common.h20
-rw-r--r--src/lib/cryptolink/botan_hash.cc199
-rw-r--r--src/lib/cryptolink/botan_hmac.cc245
-rw-r--r--src/lib/cryptolink/botan_link.cc84
-rw-r--r--src/lib/cryptolink/crypto_hash.cc39
-rw-r--r--src/lib/cryptolink/crypto_hash.h144
-rw-r--r--src/lib/cryptolink/crypto_hmac.cc59
-rw-r--r--src/lib/cryptolink/crypto_hmac.h213
-rw-r--r--src/lib/cryptolink/crypto_rng.cc33
-rw-r--r--src/lib/cryptolink/crypto_rng.h64
-rw-r--r--src/lib/cryptolink/cryptolink.cc40
-rw-r--r--src/lib/cryptolink/cryptolink.h248
-rw-r--r--src/lib/cryptolink/openssl_common.h109
-rw-r--r--src/lib/cryptolink/openssl_compat.h61
-rw-r--r--src/lib/cryptolink/openssl_hash.cc188
-rw-r--r--src/lib/cryptolink/openssl_hmac.cc245
-rw-r--r--src/lib/cryptolink/openssl_link.cc84
-rw-r--r--src/lib/cryptolink/tests/Makefile.am32
-rw-r--r--src/lib/cryptolink/tests/Makefile.in1039
-rw-r--r--src/lib/cryptolink/tests/crypto_unittests.cc55
-rw-r--r--src/lib/cryptolink/tests/hash_unittests.cc608
-rw-r--r--src/lib/cryptolink/tests/hmac_unittests.cc717
-rw-r--r--src/lib/cryptolink/tests/run_unittests.cc18
-rw-r--r--src/lib/d2srv/Makefile.am91
-rw-r--r--src/lib/d2srv/Makefile.in1139
-rw-r--r--src/lib/d2srv/d2_cfg_mgr.cc334
-rw-r--r--src/lib/d2srv/d2_cfg_mgr.h339
-rw-r--r--src/lib/d2srv/d2_config.cc674
-rw-r--r--src/lib/d2srv/d2_config.h926
-rw-r--r--src/lib/d2srv/d2_log.cc23
-rw-r--r--src/lib/d2srv/d2_log.h25
-rw-r--r--src/lib/d2srv/d2_messages.cc187
-rw-r--r--src/lib/d2srv/d2_messages.h97
-rw-r--r--src/lib/d2srv/d2_messages.mes441
-rw-r--r--src/lib/d2srv/d2_simple_parser.cc310
-rw-r--r--src/lib/d2srv/d2_simple_parser.h96
-rw-r--r--src/lib/d2srv/d2_stats.cc58
-rw-r--r--src/lib/d2srv/d2_stats.h53
-rw-r--r--src/lib/d2srv/d2_tsig_key.cc72
-rw-r--r--src/lib/d2srv/d2_tsig_key.h76
-rw-r--r--src/lib/d2srv/d2_update_message.cc230
-rw-r--r--src/lib/d2srv/d2_update_message.h357
-rw-r--r--src/lib/d2srv/d2_zone.cc30
-rw-r--r--src/lib/d2srv/d2_zone.h109
-rw-r--r--src/lib/d2srv/d2srv.dox21
-rw-r--r--src/lib/d2srv/dns_client.cc324
-rw-r--r--src/lib/d2srv/dns_client.h157
-rw-r--r--src/lib/d2srv/nc_trans.cc578
-rw-r--r--src/lib/d2srv/nc_trans.h602
-rw-r--r--src/lib/d2srv/tests/Makefile.am52
-rw-r--r--src/lib/d2srv/tests/Makefile.in1110
-rw-r--r--src/lib/d2srv/tests/d2_tsig_key_unittest.cc98
-rw-r--r--src/lib/d2srv/tests/d2_update_message_unittests.cc696
-rw-r--r--src/lib/d2srv/tests/d2_zone_unittests.cc83
-rw-r--r--src/lib/d2srv/tests/dns_client_unittests.cc677
-rw-r--r--src/lib/d2srv/tests/nc_trans_unittests.cc1279
-rw-r--r--src/lib/d2srv/tests/run_unittests.cc20
-rw-r--r--src/lib/d2srv/testutils/Makefile.am23
-rw-r--r--src/lib/d2srv/testutils/Makefile.in876
-rw-r--r--src/lib/d2srv/testutils/nc_test_utils.cc987
-rw-r--r--src/lib/d2srv/testutils/nc_test_utils.h497
-rw-r--r--src/lib/d2srv/testutils/stats_test_utils.cc41
-rw-r--r--src/lib/d2srv/testutils/stats_test_utils.h48
-rw-r--r--src/lib/database/Makefile.am79
-rw-r--r--src/lib/database/Makefile.in1027
-rw-r--r--src/lib/database/audit_entry.cc88
-rw-r--r--src/lib/database/audit_entry.h299
-rw-r--r--src/lib/database/backend_selector.cc165
-rw-r--r--src/lib/database/backend_selector.h215
-rw-r--r--src/lib/database/database.dox24
-rw-r--r--src/lib/database/database_connection.cc285
-rw-r--r--src/lib/database/database_connection.h280
-rw-r--r--src/lib/database/db_exceptions.h99
-rw-r--r--src/lib/database/db_log.cc137
-rw-r--r--src/lib/database/db_log.h215
-rw-r--r--src/lib/database/db_messages.cc55
-rw-r--r--src/lib/database/db_messages.h31
-rw-r--r--src/lib/database/db_messages.mes105
-rw-r--r--src/lib/database/dbaccess_parser.cc293
-rw-r--r--src/lib/database/dbaccess_parser.h83
-rw-r--r--src/lib/database/server.cc44
-rw-r--r--src/lib/database/server.h90
-rw-r--r--src/lib/database/server_collection.cc28
-rw-r--r--src/lib/database/server_collection.h53
-rw-r--r--src/lib/database/server_selector.cc51
-rw-r--r--src/lib/database/server_selector.h165
-rw-r--r--src/lib/database/tests/Makefile.am44
-rw-r--r--src/lib/database/tests/Makefile.in1130
-rw-r--r--src/lib/database/tests/audit_entry_unittest.cc362
-rw-r--r--src/lib/database/tests/backend_selector_unittest.cc197
-rw-r--r--src/lib/database/tests/database_connection_unittest.cc643
-rw-r--r--src/lib/database/tests/database_log_unittest.cc48
-rw-r--r--src/lib/database/tests/dbaccess_parser_unittest.cc1001
-rw-r--r--src/lib/database/tests/run_unittests.cc20
-rw-r--r--src/lib/database/tests/server_selector_unittest.cc77
-rw-r--r--src/lib/database/tests/server_unittest.cc85
-rw-r--r--src/lib/database/testutils/Makefile.am21
-rw-r--r--src/lib/database/testutils/Makefile.in860
-rw-r--r--src/lib/database/testutils/schema.cc153
-rw-r--r--src/lib/database/testutils/schema.h85
-rw-r--r--src/lib/dhcp/Makefile.am157
-rw-r--r--src/lib/dhcp/Makefile.in1576
-rw-r--r--src/lib/dhcp/README9
-rw-r--r--src/lib/dhcp/classify.cc76
-rw-r--r--src/lib/dhcp/classify.h208
-rw-r--r--src/lib/dhcp/dhcp4.h315
-rw-r--r--src/lib/dhcp/dhcp6.h342
-rw-r--r--src/lib/dhcp/docsis3_option_defs.h90
-rw-r--r--src/lib/dhcp/duid.cc79
-rw-r--r--src/lib/dhcp/duid.h270
-rw-r--r--src/lib/dhcp/duid_factory.cc409
-rw-r--r--src/lib/dhcp/duid_factory.h185
-rw-r--r--src/lib/dhcp/hwaddr.cc86
-rw-r--r--src/lib/dhcp/hwaddr.h159
-rw-r--r--src/lib/dhcp/iface_mgr.cc1989
-rw-r--r--src/lib/dhcp/iface_mgr.h1628
-rw-r--r--src/lib/dhcp/iface_mgr_bsd.cc200
-rw-r--r--src/lib/dhcp/iface_mgr_error_handler.h49
-rw-r--r--src/lib/dhcp/iface_mgr_linux.cc621
-rw-r--r--src/lib/dhcp/iface_mgr_sun.cc189
-rw-r--r--src/lib/dhcp/libdhcp++.cc1383
-rw-r--r--src/lib/dhcp/libdhcp++.dox280
-rw-r--r--src/lib/dhcp/libdhcp++.h459
-rw-r--r--src/lib/dhcp/opaque_data_tuple.cc148
-rw-r--r--src/lib/dhcp/opaque_data_tuple.h291
-rw-r--r--src/lib/dhcp/option.cc389
-rw-r--r--src/lib/dhcp/option.h608
-rw-r--r--src/lib/dhcp/option4_addrlst.cc128
-rw-r--r--src/lib/dhcp/option4_addrlst.h165
-rw-r--r--src/lib/dhcp/option4_client_fqdn.cc554
-rw-r--r--src/lib/dhcp/option4_client_fqdn.h377
-rw-r--r--src/lib/dhcp/option4_dnr.cc489
-rw-r--r--src/lib/dhcp/option4_dnr.h526
-rw-r--r--src/lib/dhcp/option6_addrlst.cc112
-rw-r--r--src/lib/dhcp/option6_addrlst.h98
-rw-r--r--src/lib/dhcp/option6_auth.cc132
-rw-r--r--src/lib/dhcp/option6_auth.h145
-rw-r--r--src/lib/dhcp/option6_client_fqdn.cc456
-rw-r--r--src/lib/dhcp/option6_client_fqdn.h271
-rw-r--r--src/lib/dhcp/option6_dnr.cc145
-rw-r--r--src/lib/dhcp/option6_dnr.h147
-rw-r--r--src/lib/dhcp/option6_ia.cc120
-rw-r--r--src/lib/dhcp/option6_ia.h121
-rw-r--r--src/lib/dhcp/option6_iaaddr.cc117
-rw-r--r--src/lib/dhcp/option6_iaaddr.h129
-rw-r--r--src/lib/dhcp/option6_iaprefix.cc148
-rw-r--r--src/lib/dhcp/option6_iaprefix.h148
-rw-r--r--src/lib/dhcp/option6_pdexclude.cc237
-rw-r--r--src/lib/dhcp/option6_pdexclude.h130
-rw-r--r--src/lib/dhcp/option6_status_code.cc218
-rw-r--r--src/lib/dhcp/option6_status_code.h207
-rw-r--r--src/lib/dhcp/option_custom.cc732
-rw-r--r--src/lib/dhcp/option_custom.h511
-rw-r--r--src/lib/dhcp/option_data_types.cc617
-rw-r--r--src/lib/dhcp/option_data_types.h678
-rw-r--r--src/lib/dhcp/option_definition.cc916
-rw-r--r--src/lib/dhcp/option_definition.h887
-rw-r--r--src/lib/dhcp/option_int.h250
-rw-r--r--src/lib/dhcp/option_int_array.h295
-rw-r--r--src/lib/dhcp/option_opaque_data_tuples.cc158
-rw-r--r--src/lib/dhcp/option_opaque_data_tuples.h164
-rw-r--r--src/lib/dhcp/option_space.cc65
-rw-r--r--src/lib/dhcp/option_space.h183
-rw-r--r--src/lib/dhcp/option_space_container.h184
-rw-r--r--src/lib/dhcp/option_string.cc115
-rw-r--r--src/lib/dhcp/option_string.h128
-rw-r--r--src/lib/dhcp/option_vendor.cc115
-rw-r--r--src/lib/dhcp/option_vendor.h117
-rw-r--r--src/lib/dhcp/option_vendor_class.cc202
-rw-r--r--src/lib/dhcp/option_vendor_class.h201
-rw-r--r--src/lib/dhcp/packet_queue.h141
-rw-r--r--src/lib/dhcp/packet_queue_mgr.h189
-rw-r--r--src/lib/dhcp/packet_queue_mgr4.cc36
-rw-r--r--src/lib/dhcp/packet_queue_mgr4.h42
-rw-r--r--src/lib/dhcp/packet_queue_mgr6.cc36
-rw-r--r--src/lib/dhcp/packet_queue_mgr6.h43
-rw-r--r--src/lib/dhcp/packet_queue_ring.h263
-rw-r--r--src/lib/dhcp/pkt.cc314
-rw-r--r--src/lib/dhcp/pkt.h954
-rw-r--r--src/lib/dhcp/pkt4.cc618
-rw-r--r--src/lib/dhcp/pkt4.h560
-rw-r--r--src/lib/dhcp/pkt4o6.cc60
-rw-r--r--src/lib/dhcp/pkt4o6.h88
-rw-r--r--src/lib/dhcp/pkt6.cc1045
-rw-r--r--src/lib/dhcp/pkt6.h627
-rw-r--r--src/lib/dhcp/pkt_filter.cc72
-rw-r--r--src/lib/dhcp/pkt_filter.h139
-rw-r--r--src/lib/dhcp/pkt_filter6.cc38
-rw-r--r--src/lib/dhcp/pkt_filter6.h141
-rw-r--r--src/lib/dhcp/pkt_filter_bpf.cc606
-rw-r--r--src/lib/dhcp/pkt_filter_bpf.h142
-rw-r--r--src/lib/dhcp/pkt_filter_inet.cc285
-rw-r--r--src/lib/dhcp/pkt_filter_inet.h89
-rw-r--r--src/lib/dhcp/pkt_filter_inet6.cc339
-rw-r--r--src/lib/dhcp/pkt_filter_inet6.h88
-rw-r--r--src/lib/dhcp/pkt_filter_lpf.cc340
-rw-r--r--src/lib/dhcp/pkt_filter_lpf.h75
-rw-r--r--src/lib/dhcp/pkt_template.h47
-rw-r--r--src/lib/dhcp/protocol_util.cc240
-rw-r--r--src/lib/dhcp/protocol_util.h147
-rw-r--r--src/lib/dhcp/socket_info.h68
-rw-r--r--src/lib/dhcp/std_option_defs.h765
-rw-r--r--src/lib/dhcp/tests/Makefile.am124
-rw-r--r--src/lib/dhcp/tests/Makefile.in2176
-rw-r--r--src/lib/dhcp/tests/classify_unittest.cc171
-rw-r--r--src/lib/dhcp/tests/duid_factory_unittest.cc529
-rw-r--r--src/lib/dhcp/tests/duid_unittest.cc350
-rw-r--r--src/lib/dhcp/tests/hwaddr_unittest.cc170
-rw-r--r--src/lib/dhcp/tests/iface_mgr_test_config.cc211
-rw-r--r--src/lib/dhcp/tests/iface_mgr_test_config.h273
-rw-r--r--src/lib/dhcp/tests/iface_mgr_unittest.cc3612
-rw-r--r--src/lib/dhcp/tests/libdhcp++_unittest.cc3584
-rw-r--r--src/lib/dhcp/tests/opaque_data_tuple_unittest.cc523
-rw-r--r--src/lib/dhcp/tests/option4_addrlst_unittest.cc275
-rw-r--r--src/lib/dhcp/tests/option4_client_fqdn_unittest.cc1029
-rw-r--r--src/lib/dhcp/tests/option4_dnr_unittest.cc793
-rw-r--r--src/lib/dhcp/tests/option6_addrlst_unittest.cc276
-rw-r--r--src/lib/dhcp/tests/option6_auth_unittest.cc166
-rw-r--r--src/lib/dhcp/tests/option6_client_fqdn_unittest.cc819
-rw-r--r--src/lib/dhcp/tests/option6_dnr_unittest.cc650
-rw-r--r--src/lib/dhcp/tests/option6_ia_unittest.cc360
-rw-r--r--src/lib/dhcp/tests/option6_iaaddr_unittest.cc138
-rw-r--r--src/lib/dhcp/tests/option6_iaprefix_unittest.cc271
-rw-r--r--src/lib/dhcp/tests/option6_pdexclude_unittest.cc170
-rw-r--r--src/lib/dhcp/tests/option6_status_code_unittest.cc169
-rw-r--r--src/lib/dhcp/tests/option_copy_unittest.cc790
-rw-r--r--src/lib/dhcp/tests/option_custom_unittest.cc2510
-rw-r--r--src/lib/dhcp/tests/option_data_types_unittest.cc928
-rw-r--r--src/lib/dhcp/tests/option_definition_unittest.cc2108
-rw-r--r--src/lib/dhcp/tests/option_int_array_unittest.cc486
-rw-r--r--src/lib/dhcp/tests/option_int_unittest.cc571
-rw-r--r--src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc666
-rw-r--r--src/lib/dhcp/tests/option_space_unittest.cc142
-rw-r--r--src/lib/dhcp/tests/option_string_unittest.cc241
-rw-r--r--src/lib/dhcp/tests/option_unittest.cc651
-rw-r--r--src/lib/dhcp/tests/option_vendor_class_unittest.cc611
-rw-r--r--src/lib/dhcp/tests/option_vendor_unittest.cc257
-rw-r--r--src/lib/dhcp/tests/packet_queue4_unittest.cc294
-rw-r--r--src/lib/dhcp/tests/packet_queue6_unittest.cc295
-rw-r--r--src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc144
-rw-r--r--src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc133
-rw-r--r--src/lib/dhcp/tests/packet_queue_testutils.h64
-rw-r--r--src/lib/dhcp/tests/pkt4_unittest.cc1529
-rw-r--r--src/lib/dhcp/tests/pkt4o6_unittest.cc123
-rw-r--r--src/lib/dhcp/tests/pkt6_unittest.cc2373
-rw-r--r--src/lib/dhcp/tests/pkt_captures.h103
-rw-r--r--src/lib/dhcp/tests/pkt_captures4.cc387
-rw-r--r--src/lib/dhcp/tests/pkt_captures6.cc509
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_stub.cc48
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_stub.h107
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_utils.cc206
-rw-r--r--src/lib/dhcp/tests/pkt_filter6_test_utils.h152
-rw-r--r--src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc235
-rw-r--r--src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc134
-rw-r--r--src/lib/dhcp/tests/pkt_filter_inet_unittest.cc148
-rw-r--r--src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc222
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_stub.cc57
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_stub.h116
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_utils.cc196
-rw-r--r--src/lib/dhcp/tests/pkt_filter_test_utils.h170
-rw-r--r--src/lib/dhcp/tests/pkt_filter_unittest.cc67
-rw-r--r--src/lib/dhcp/tests/protocol_util_unittest.cc677
-rw-r--r--src/lib/dhcp/tests/run_unittests.cc21
-rw-r--r--src/lib/dhcp_ddns/Makefile.am78
-rw-r--r--src/lib/dhcp_ddns/Makefile.in1057
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_log.cc21
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_log.h23
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_messages.cc51
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_messages.h29
-rw-r--r--src/lib/dhcp_ddns/dhcp_ddns_messages.mes88
-rw-r--r--src/lib/dhcp_ddns/libdhcp_ddns.dox61
-rw-r--r--src/lib/dhcp_ddns/ncr_io.cc499
-rw-r--r--src/lib/dhcp_ddns/ncr_io.h853
-rw-r--r--src/lib/dhcp_ddns/ncr_msg.cc696
-rw-r--r--src/lib/dhcp_ddns/ncr_msg.h761
-rw-r--r--src/lib/dhcp_ddns/ncr_udp.cc386
-rw-r--r--src/lib/dhcp_ddns/ncr_udp.h588
-rw-r--r--src/lib/dhcp_ddns/tests/Makefile.am48
-rw-r--r--src/lib/dhcp_ddns/tests/Makefile.in1075
-rw-r--r--src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc1428
-rw-r--r--src/lib/dhcp_ddns/tests/ncr_unittests.cc743
-rw-r--r--src/lib/dhcp_ddns/tests/run_unittests.cc21
-rw-r--r--src/lib/dhcp_ddns/tests/test_utils.cc37
-rw-r--r--src/lib/dhcp_ddns/tests/test_utils.h29
-rw-r--r--src/lib/dhcpsrv/Makefile.am418
-rw-r--r--src/lib/dhcpsrv/Makefile.in2438
-rw-r--r--src/lib/dhcpsrv/alloc_engine.cc5250
-rw-r--r--src/lib/dhcpsrv/alloc_engine.h1877
-rw-r--r--src/lib/dhcpsrv/alloc_engine_log.cc26
-rw-r--r--src/lib/dhcpsrv/alloc_engine_log.h50
-rw-r--r--src/lib/dhcpsrv/alloc_engine_messages.cc181
-rw-r--r--src/lib/dhcpsrv/alloc_engine_messages.h94
-rw-r--r--src/lib/dhcpsrv/alloc_engine_messages.mes627
-rw-r--r--src/lib/dhcpsrv/allocation_state.cc34
-rw-r--r--src/lib/dhcpsrv/allocation_state.h85
-rw-r--r--src/lib/dhcpsrv/allocator.cc83
-rw-r--r--src/lib/dhcpsrv/allocator.h244
-rw-r--r--src/lib/dhcpsrv/base_host_data_source.h580
-rw-r--r--src/lib/dhcpsrv/cache_host_data_source.h68
-rw-r--r--src/lib/dhcpsrv/callout_handle_store.h60
-rw-r--r--src/lib/dhcpsrv/cb_ctl_dhcp.h63
-rw-r--r--src/lib/dhcpsrv/cb_ctl_dhcp4.cc360
-rw-r--r--src/lib/dhcpsrv/cb_ctl_dhcp4.h50
-rw-r--r--src/lib/dhcpsrv/cb_ctl_dhcp6.cc376
-rw-r--r--src/lib/dhcpsrv/cb_ctl_dhcp6.h50
-rw-r--r--src/lib/dhcpsrv/cfg_4o6.cc51
-rw-r--r--src/lib/dhcpsrv/cfg_4o6.h106
-rw-r--r--src/lib/dhcpsrv/cfg_consistency.cc60
-rw-r--r--src/lib/dhcpsrv/cfg_consistency.h111
-rw-r--r--src/lib/dhcpsrv/cfg_db_access.cc108
-rw-r--r--src/lib/dhcpsrv/cfg_db_access.h196
-rw-r--r--src/lib/dhcpsrv/cfg_duid.cc122
-rw-r--r--src/lib/dhcpsrv/cfg_duid.h171
-rw-r--r--src/lib/dhcpsrv/cfg_expiration.cc141
-rw-r--r--src/lib/dhcpsrv/cfg_expiration.h338
-rw-r--r--src/lib/dhcpsrv/cfg_globals.cc186
-rw-r--r--src/lib/dhcpsrv/cfg_globals.h168
-rw-r--r--src/lib/dhcpsrv/cfg_host_operations.cc73
-rw-r--r--src/lib/dhcpsrv/cfg_host_operations.h97
-rw-r--r--src/lib/dhcpsrv/cfg_hosts.cc1276
-rw-r--r--src/lib/dhcpsrv/cfg_hosts.h951
-rw-r--r--src/lib/dhcpsrv/cfg_hosts_util.cc96
-rw-r--r--src/lib/dhcpsrv/cfg_hosts_util.h53
-rw-r--r--src/lib/dhcpsrv/cfg_iface.cc607
-rw-r--r--src/lib/dhcpsrv/cfg_iface.h509
-rw-r--r--src/lib/dhcpsrv/cfg_mac_source.cc89
-rw-r--r--src/lib/dhcpsrv/cfg_mac_source.h86
-rw-r--r--src/lib/dhcpsrv/cfg_multi_threading.cc52
-rw-r--r--src/lib/dhcpsrv/cfg_multi_threading.h42
-rw-r--r--src/lib/dhcpsrv/cfg_option.cc555
-rw-r--r--src/lib/dhcpsrv/cfg_option.h816
-rw-r--r--src/lib/dhcpsrv/cfg_option_def.cc261
-rw-r--r--src/lib/dhcpsrv/cfg_option_def.h196
-rw-r--r--src/lib/dhcpsrv/cfg_rsoo.cc54
-rw-r--r--src/lib/dhcpsrv/cfg_rsoo.h81
-rw-r--r--src/lib/dhcpsrv/cfg_shared_networks.cc23
-rw-r--r--src/lib/dhcpsrv/cfg_shared_networks.h243
-rw-r--r--src/lib/dhcpsrv/cfg_subnets4.cc649
-rw-r--r--src/lib/dhcpsrv/cfg_subnets4.h361
-rw-r--r--src/lib/dhcpsrv/cfg_subnets6.cc595
-rw-r--r--src/lib/dhcpsrv/cfg_subnets6.h359
-rw-r--r--src/lib/dhcpsrv/cfgmgr.cc233
-rw-r--r--src/lib/dhcpsrv/cfgmgr.h357
-rw-r--r--src/lib/dhcpsrv/client_class_def.cc649
-rw-r--r--src/lib/dhcpsrv/client_class_def.h543
-rw-r--r--src/lib/dhcpsrv/config_backend_dhcp4.h691
-rw-r--r--src/lib/dhcpsrv/config_backend_dhcp4_mgr.cc41
-rw-r--r--src/lib/dhcpsrv/config_backend_dhcp4_mgr.h71
-rw-r--r--src/lib/dhcpsrv/config_backend_dhcp6.h729
-rw-r--r--src/lib/dhcpsrv/config_backend_dhcp6_mgr.cc41
-rw-r--r--src/lib/dhcpsrv/config_backend_dhcp6_mgr.h71
-rw-r--r--src/lib/dhcpsrv/config_backend_pool_dhcp4.cc524
-rw-r--r--src/lib/dhcpsrv/config_backend_pool_dhcp4.h619
-rw-r--r--src/lib/dhcpsrv/config_backend_pool_dhcp6.cc549
-rw-r--r--src/lib/dhcpsrv/config_backend_pool_dhcp6.h653
-rw-r--r--src/lib/dhcpsrv/csv_lease_file4.cc278
-rw-r--r--src/lib/dhcpsrv/csv_lease_file4.h175
-rw-r--r--src/lib/dhcpsrv/csv_lease_file6.cc333
-rw-r--r--src/lib/dhcpsrv/csv_lease_file6.h217
-rw-r--r--src/lib/dhcpsrv/d2_client_cfg.cc222
-rw-r--r--src/lib/dhcpsrv/d2_client_cfg.h241
-rw-r--r--src/lib/dhcpsrv/d2_client_mgr.cc423
-rw-r--r--src/lib/dhcpsrv/d2_client_mgr.h530
-rw-r--r--src/lib/dhcpsrv/database_backends.dox217
-rw-r--r--src/lib/dhcpsrv/db_type.h22
-rw-r--r--src/lib/dhcpsrv/dhcp4o6_ipc.cc288
-rw-r--r--src/lib/dhcpsrv/dhcp4o6_ipc.h131
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_exceptions.h26
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_log.cc27
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_log.h61
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_messages.cc569
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_messages.h288
-rw-r--r--src/lib/dhcpsrv/dhcpsrv_messages.mes1401
-rw-r--r--src/lib/dhcpsrv/flq_allocation_state.cc87
-rw-r--r--src/lib/dhcpsrv/flq_allocation_state.h108
-rw-r--r--src/lib/dhcpsrv/flq_allocator.cc361
-rw-r--r--src/lib/dhcpsrv/flq_allocator.h202
-rw-r--r--src/lib/dhcpsrv/fuzz.cc209
-rw-r--r--src/lib/dhcpsrv/fuzz.h144
-rw-r--r--src/lib/dhcpsrv/fuzz_log.cc23
-rw-r--r--src/lib/dhcpsrv/fuzz_log.h43
-rw-r--r--src/lib/dhcpsrv/fuzz_messages.cc39
-rw-r--r--src/lib/dhcpsrv/fuzz_messages.h23
-rw-r--r--src/lib/dhcpsrv/fuzz_messages.mes49
-rw-r--r--src/lib/dhcpsrv/host.cc724
-rw-r--r--src/lib/dhcpsrv/host.h821
-rw-r--r--src/lib/dhcpsrv/host_container.h315
-rw-r--r--src/lib/dhcpsrv/host_data_source_factory.cc238
-rw-r--r--src/lib/dhcpsrv/host_data_source_factory.h149
-rw-r--r--src/lib/dhcpsrv/host_mgr.cc985
-rw-r--r--src/lib/dhcpsrv/host_mgr.h912
-rw-r--r--src/lib/dhcpsrv/hosts_log.cc26
-rw-r--r--src/lib/dhcpsrv/hosts_log.h56
-rw-r--r--src/lib/dhcpsrv/hosts_messages.cc159
-rw-r--r--src/lib/dhcpsrv/hosts_messages.h83
-rw-r--r--src/lib/dhcpsrv/hosts_messages.mes340
-rw-r--r--src/lib/dhcpsrv/images/pgsql_host_data_source.svg419
-rw-r--r--src/lib/dhcpsrv/ip_range.cc74
-rw-r--r--src/lib/dhcpsrv/ip_range.h67
-rw-r--r--src/lib/dhcpsrv/ip_range_permutation.cc114
-rw-r--r--src/lib/dhcpsrv/ip_range_permutation.h147
-rw-r--r--src/lib/dhcpsrv/iterative_allocation_state.cc56
-rw-r--r--src/lib/dhcpsrv/iterative_allocation_state.h138
-rw-r--r--src/lib/dhcpsrv/iterative_allocator.cc370
-rw-r--r--src/lib/dhcpsrv/iterative_allocator.h129
-rw-r--r--src/lib/dhcpsrv/key_from_key.h74
-rw-r--r--src/lib/dhcpsrv/lease.cc750
-rw-r--r--src/lib/dhcpsrv/lease.h713
-rw-r--r--src/lib/dhcpsrv/lease_file_loader.h246
-rw-r--r--src/lib/dhcpsrv/lease_file_stats.h94
-rw-r--r--src/lib/dhcpsrv/lease_mgr.cc1270
-rw-r--r--src/lib/dhcpsrv/lease_mgr.h1103
-rw-r--r--src/lib/dhcpsrv/lease_mgr_factory.cc138
-rw-r--r--src/lib/dhcpsrv/lease_mgr_factory.h119
-rw-r--r--src/lib/dhcpsrv/libdhcpsrv.dox528
-rw-r--r--src/lib/dhcpsrv/memfile_lease_limits.cc161
-rw-r--r--src/lib/dhcpsrv/memfile_lease_limits.h162
-rw-r--r--src/lib/dhcpsrv/memfile_lease_mgr.cc3575
-rw-r--r--src/lib/dhcpsrv/memfile_lease_mgr.h1582
-rw-r--r--src/lib/dhcpsrv/memfile_lease_storage.h513
-rw-r--r--src/lib/dhcpsrv/mysql_host_data_source.cc4141
-rw-r--r--src/lib/dhcpsrv/mysql_host_data_source.h536
-rw-r--r--src/lib/dhcpsrv/mysql_lease_mgr.cc4236
-rw-r--r--src/lib/dhcpsrv/mysql_lease_mgr.h1293
-rw-r--r--src/lib/dhcpsrv/ncr_generator.cc154
-rw-r--r--src/lib/dhcpsrv/ncr_generator.h67
-rw-r--r--src/lib/dhcpsrv/network.cc389
-rw-r--r--src/lib/dhcpsrv/network.h1559
-rw-r--r--src/lib/dhcpsrv/network_state.cc332
-rw-r--r--src/lib/dhcpsrv/network_state.h209
-rw-r--r--src/lib/dhcpsrv/parsers/base_network_parser.cc324
-rw-r--r--src/lib/dhcpsrv/parsers/base_network_parser.h151
-rw-r--r--src/lib/dhcpsrv/parsers/client_class_def_parser.cc358
-rw-r--r--src/lib/dhcpsrv/parsers/client_class_def_parser.h175
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_parsers.cc1676
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_parsers.h1079
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.cc61
-rw-r--r--src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.h58
-rw-r--r--src/lib/dhcpsrv/parsers/duid_config_parser.cc95
-rw-r--r--src/lib/dhcpsrv/parsers/duid_config_parser.h41
-rw-r--r--src/lib/dhcpsrv/parsers/expiration_config_parser.cc69
-rw-r--r--src/lib/dhcpsrv/parsers/expiration_config_parser.h59
-rw-r--r--src/lib/dhcpsrv/parsers/host_reservation_parser.cc457
-rw-r--r--src/lib/dhcpsrv/parsers/host_reservation_parser.h243
-rw-r--r--src/lib/dhcpsrv/parsers/host_reservations_list_parser.h53
-rw-r--r--src/lib/dhcpsrv/parsers/ifaces_config_parser.cc130
-rw-r--r--src/lib/dhcpsrv/parsers/ifaces_config_parser.h70
-rw-r--r--src/lib/dhcpsrv/parsers/multi_threading_config_parser.cc73
-rw-r--r--src/lib/dhcpsrv/parsers/multi_threading_config_parser.h34
-rw-r--r--src/lib/dhcpsrv/parsers/option_data_parser.cc479
-rw-r--r--src/lib/dhcpsrv/parsers/option_data_parser.h240
-rw-r--r--src/lib/dhcpsrv/parsers/sanity_checks_parser.cc80
-rw-r--r--src/lib/dhcpsrv/parsers/sanity_checks_parser.h32
-rw-r--r--src/lib/dhcpsrv/parsers/shared_network_parser.cc429
-rw-r--r--src/lib/dhcpsrv/parsers/shared_network_parser.h126
-rw-r--r--src/lib/dhcpsrv/parsers/shared_networks_list_parser.h95
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser4.cc548
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser4.h69
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser6.cc564
-rw-r--r--src/lib/dhcpsrv/parsers/simple_parser6.h70
-rw-r--r--src/lib/dhcpsrv/pgsql_host_data_source.cc3339
-rw-r--r--src/lib/dhcpsrv/pgsql_host_data_source.h588
-rw-r--r--src/lib/dhcpsrv/pgsql_lease_mgr.cc3302
-rw-r--r--src/lib/dhcpsrv/pgsql_lease_mgr.h1253
-rw-r--r--src/lib/dhcpsrv/pool.cc438
-rw-r--r--src/lib/dhcpsrv/pool.h491
-rw-r--r--src/lib/dhcpsrv/random_allocation_state.cc40
-rw-r--r--src/lib/dhcpsrv/random_allocation_state.h74
-rw-r--r--src/lib/dhcpsrv/random_allocator.cc180
-rw-r--r--src/lib/dhcpsrv/random_allocator.h108
-rw-r--r--src/lib/dhcpsrv/resource_handler.cc98
-rw-r--r--src/lib/dhcpsrv/resource_handler.h221
-rw-r--r--src/lib/dhcpsrv/sanity_checker.cc187
-rw-r--r--src/lib/dhcpsrv/sanity_checker.h86
-rw-r--r--src/lib/dhcpsrv/shared_network.cc531
-rw-r--r--src/lib/dhcpsrv/shared_network.h502
-rw-r--r--src/lib/dhcpsrv/srv_config.cc1044
-rw-r--r--src/lib/dhcpsrv/srv_config.h1243
-rw-r--r--src/lib/dhcpsrv/subnet.cc952
-rw-r--r--src/lib/dhcpsrv/subnet.h1042
-rw-r--r--src/lib/dhcpsrv/subnet_id.h48
-rw-r--r--src/lib/dhcpsrv/subnet_selector.h74
-rw-r--r--src/lib/dhcpsrv/tests/Makefile.am197
-rw-r--r--src/lib/dhcpsrv/tests/Makefile.in2795
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc5988
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc5967
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc2329
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc658
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_utils.cc697
-rw-r--r--src/lib/dhcpsrv/tests/alloc_engine_utils.h662
-rw-r--r--src/lib/dhcpsrv/tests/allocation_state_unittest.cc77
-rw-r--r--src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc74
-rw-r--r--src/lib/dhcpsrv/tests/callout_library.cc25
-rw-r--r--src/lib/dhcpsrv/tests/callout_params_library.cc25
-rw-r--r--src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc2157
-rw-r--r--src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc338
-rw-r--r--src/lib/dhcpsrv/tests/cfg_duid_unittest.cc280
-rw-r--r--src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc433
-rw-r--r--src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc113
-rw-r--r--src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc1512
-rw-r--r--src/lib/dhcpsrv/tests/cfg_iface_unittest.cc1065
-rw-r--r--src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc84
-rw-r--r--src/lib/dhcpsrv/tests/cfg_multi_threading_unittest.cc117
-rw-r--r--src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc400
-rw-r--r--src/lib/dhcpsrv/tests/cfg_option_unittest.cc1309
-rw-r--r--src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc100
-rw-r--r--src/lib/dhcpsrv/tests/cfg_shared_networks4_unittest.cc383
-rw-r--r--src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc391
-rw-r--r--src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc2481
-rw-r--r--src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc2510
-rw-r--r--src/lib/dhcpsrv/tests/cfgmgr_unittest.cc1124
-rw-r--r--src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc2091
-rw-r--r--src/lib/dhcpsrv/tests/client_class_def_unittest.cc1678
-rw-r--r--src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc649
-rw-r--r--src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc717
-rw-r--r--src/lib/dhcpsrv/tests/d2_client_unittest.cc1240
-rw-r--r--src/lib/dhcpsrv/tests/d2_udp_unittest.cc504
-rw-r--r--src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc584
-rw-r--r--src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc3557
-rw-r--r--src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc211
-rw-r--r--src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc224
-rw-r--r--src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc252
-rw-r--r--src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc278
-rw-r--r--src/lib/dhcpsrv/tests/flq_allocator_unittest.cc1016
-rw-r--r--src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc4599
-rw-r--r--src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h812
-rw-r--r--src/lib/dhcpsrv/tests/host_cache_unittest.cc1000
-rw-r--r--src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc202
-rw-r--r--src/lib/dhcpsrv/tests/host_mgr_unittest.cc209
-rw-r--r--src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc1499
-rw-r--r--src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc388
-rw-r--r--src/lib/dhcpsrv/tests/host_unittest.cc1455
-rw-r--r--src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc392
-rw-r--r--src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc290
-rw-r--r--src/lib/dhcpsrv/tests/ip_range_unittest.cc94
-rw-r--r--src/lib/dhcpsrv/tests/iterative_allocation_state_unittest.cc188
-rw-r--r--src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc822
-rw-r--r--src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc893
-rw-r--r--src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc31
-rw-r--r--src/lib/dhcpsrv/tests/lease_mgr_unittest.cc1038
-rw-r--r--src/lib/dhcpsrv/tests/lease_unittest.cc1368
-rw-r--r--src/lib/dhcpsrv/tests/memfile_lease_extended_info_unittest.cc1729
-rw-r--r--src/lib/dhcpsrv/tests/memfile_lease_limits_unittest.cc600
-rw-r--r--src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc4413
-rw-r--r--src/lib/dhcpsrv/tests/multi_threading_config_parser_unittest.cc190
-rw-r--r--src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc1753
-rw-r--r--src/lib/dhcpsrv/tests/mysql_lease_extended_info_unittest.cc1111
-rw-r--r--src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc1310
-rw-r--r--src/lib/dhcpsrv/tests/ncr_generator_unittest.cc737
-rw-r--r--src/lib/dhcpsrv/tests/network_state_unittest.cc784
-rw-r--r--src/lib/dhcpsrv/tests/network_unittest.cc837
-rw-r--r--src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc1721
-rw-r--r--src/lib/dhcpsrv/tests/pgsql_lease_extended_info_unittest.cc1111
-rw-r--r--src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc1299
-rw-r--r--src/lib/dhcpsrv/tests/pool_unittest.cc676
-rw-r--r--src/lib/dhcpsrv/tests/random_allocation_state_unittest.cc106
-rw-r--r--src/lib/dhcpsrv/tests/random_allocator_unittest.cc485
-rw-r--r--src/lib/dhcpsrv/tests/resource_handler_unittest.cc509
-rw-r--r--src/lib/dhcpsrv/tests/run_unittests.cc20
-rw-r--r--src/lib/dhcpsrv/tests/sanity_checks_unittest.cc1614
-rw-r--r--src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc1106
-rw-r--r--src/lib/dhcpsrv/tests/shared_network_unittest.cc1607
-rw-r--r--src/lib/dhcpsrv/tests/shared_networks_list_parser_unittest.cc116
-rw-r--r--src/lib/dhcpsrv/tests/srv_config_unittest.cc2333
-rw-r--r--src/lib/dhcpsrv/tests/subnet_unittest.cc2045
-rw-r--r--src/lib/dhcpsrv/tests/test_get_callout_handle.cc24
-rw-r--r--src/lib/dhcpsrv/tests/test_get_callout_handle.h38
-rw-r--r--src/lib/dhcpsrv/tests/test_libraries.h.in35
-rw-r--r--src/lib/dhcpsrv/tests/timer_mgr_unittest.cc484
-rw-r--r--src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc426
-rw-r--r--src/lib/dhcpsrv/testutils/Makefile.am64
-rw-r--r--src/lib/dhcpsrv/testutils/Makefile.in1103
-rw-r--r--src/lib/dhcpsrv/testutils/concrete_lease_mgr.cc345
-rw-r--r--src/lib/dhcpsrv/testutils/concrete_lease_mgr.h447
-rw-r--r--src/lib/dhcpsrv/testutils/config_result_check.cc89
-rw-r--r--src/lib/dhcpsrv/testutils/config_result_check.h48
-rw-r--r--src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc41
-rw-r--r--src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h90
-rw-r--r--src/lib/dhcpsrv/testutils/generic_backend_unittest.cc287
-rw-r--r--src/lib/dhcpsrv/testutils/generic_backend_unittest.h366
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc4621
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h390
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc4772
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h394
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc376
-rw-r--r--src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h164
-rw-r--r--src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc5226
-rw-r--r--src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h1051
-rw-r--r--src/lib/dhcpsrv/testutils/host_data_source_utils.cc407
-rw-r--r--src/lib/dhcpsrv/testutils/host_data_source_utils.h134
-rw-r--r--src/lib/dhcpsrv/testutils/lease_file_io.cc66
-rw-r--r--src/lib/dhcpsrv/testutils/lease_file_io.h63
-rw-r--r--src/lib/dhcpsrv/testutils/memory_host_data_source.cc424
-rw-r--r--src/lib/dhcpsrv/testutils/memory_host_data_source.h352
-rw-r--r--src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc46
-rw-r--r--src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h45
-rw-r--r--src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc51
-rw-r--r--src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h45
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend.h121
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc1452
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h550
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc1556
-rw-r--r--src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h580
-rw-r--r--src/lib/dhcpsrv/testutils/test_utils.cc155
-rw-r--r--src/lib/dhcpsrv/testutils/test_utils.h86
-rw-r--r--src/lib/dhcpsrv/timer_mgr.cc531
-rw-r--r--src/lib/dhcpsrv/timer_mgr.h163
-rw-r--r--src/lib/dhcpsrv/tracking_lease_mgr.cc162
-rw-r--r--src/lib/dhcpsrv/tracking_lease_mgr.h308
-rw-r--r--src/lib/dhcpsrv/utils.h32
-rw-r--r--src/lib/dhcpsrv/writable_host_data_source.h245
-rw-r--r--src/lib/dns/Makefile.am226
-rw-r--r--src/lib/dns/Makefile.in1521
-rw-r--r--src/lib/dns/dns_fwd.h56
-rw-r--r--src/lib/dns/edns.cc178
-rw-r--r--src/lib/dns/edns.h437
-rw-r--r--src/lib/dns/exceptions.cc26
-rw-r--r--src/lib/dns/exceptions.h76
-rw-r--r--src/lib/dns/gen-rdatacode.py.in391
-rw-r--r--src/lib/dns/labelsequence.cc472
-rw-r--r--src/lib/dns/labelsequence.h456
-rw-r--r--src/lib/dns/master_lexer.cc614
-rw-r--r--src/lib/dns/master_lexer.h678
-rw-r--r--src/lib/dns/master_lexer_inputsource.cc221
-rw-r--r--src/lib/dns/master_lexer_inputsource.h185
-rw-r--r--src/lib/dns/master_lexer_state.h138
-rw-r--r--src/lib/dns/master_loader.cc1073
-rw-r--r--src/lib/dns/master_loader.h187
-rw-r--r--src/lib/dns/master_loader_callbacks.cc28
-rw-r--r--src/lib/dns/master_loader_callbacks.h134
-rw-r--r--src/lib/dns/masterload.cc100
-rw-r--r--src/lib/dns/masterload.h175
-rw-r--r--src/lib/dns/message.cc1176
-rw-r--r--src/lib/dns/message.h691
-rw-r--r--src/lib/dns/messagerenderer.cc397
-rw-r--r--src/lib/dns/messagerenderer.h395
-rw-r--r--src/lib/dns/name.cc724
-rw-r--r--src/lib/dns/name.h766
-rw-r--r--src/lib/dns/name_internal.h35
-rw-r--r--src/lib/dns/nsec3hash.cc270
-rw-r--r--src/lib/dns/nsec3hash.h290
-rw-r--r--src/lib/dns/opcode.cc62
-rw-r--r--src/lib/dns/opcode.h282
-rw-r--r--src/lib/dns/qid_gen.cc42
-rw-r--r--src/lib/dns/qid_gen.h54
-rw-r--r--src/lib/dns/question.cc81
-rw-r--r--src/lib/dns/question.h291
-rw-r--r--src/lib/dns/rcode.cc95
-rw-r--r--src/lib/dns/rcode.h338
-rw-r--r--src/lib/dns/rdata.cc408
-rw-r--r--src/lib/dns/rdata.h582
-rw-r--r--src/lib/dns/rdata/any_255/tsig_250.cc567
-rw-r--r--src/lib/dns/rdata/any_255/tsig_250.h148
-rw-r--r--src/lib/dns/rdata/ch_3/a_1.cc64
-rw-r--r--src/lib/dns/rdata/ch_3/a_1.h32
-rw-r--r--src/lib/dns/rdata/generic/afsdb_18.cc201
-rw-r--r--src/lib/dns/rdata/generic/afsdb_18.h68
-rw-r--r--src/lib/dns/rdata/generic/caa_257.cc298
-rw-r--r--src/lib/dns/rdata/generic/caa_257.h64
-rw-r--r--src/lib/dns/rdata/generic/cname_5.cc125
-rw-r--r--src/lib/dns/rdata/generic/cname_5.h39
-rw-r--r--src/lib/dns/rdata/generic/detail/char_string.cc264
-rw-r--r--src/lib/dns/rdata/generic/detail/char_string.h140
-rw-r--r--src/lib/dns/rdata/generic/detail/ds_like.h277
-rw-r--r--src/lib/dns/rdata/generic/detail/lexer_util.h62
-rw-r--r--src/lib/dns/rdata/generic/detail/nsec3param_common.cc115
-rw-r--r--src/lib/dns/rdata/generic/detail/nsec3param_common.h123
-rw-r--r--src/lib/dns/rdata/generic/detail/nsec_bitmap.cc169
-rw-r--r--src/lib/dns/rdata/generic/detail/nsec_bitmap.h103
-rw-r--r--src/lib/dns/rdata/generic/detail/txt_like.h237
-rw-r--r--src/lib/dns/rdata/generic/dlv_32769.cc120
-rw-r--r--src/lib/dns/rdata/generic/dlv_32769.h69
-rw-r--r--src/lib/dns/rdata/generic/dname_39.cc127
-rw-r--r--src/lib/dns/rdata/generic/dname_39.h39
-rw-r--r--src/lib/dns/rdata/generic/dnskey_48.cc316
-rw-r--r--src/lib/dns/rdata/generic/dnskey_48.h60
-rw-r--r--src/lib/dns/rdata/generic/ds_43.cc90
-rw-r--r--src/lib/dns/rdata/generic/ds_43.h69
-rw-r--r--src/lib/dns/rdata/generic/hinfo_13.cc151
-rw-r--r--src/lib/dns/rdata/generic/hinfo_13.h64
-rw-r--r--src/lib/dns/rdata/generic/minfo_14.cc173
-rw-r--r--src/lib/dns/rdata/generic/minfo_14.h74
-rw-r--r--src/lib/dns/rdata/generic/mx_15.cc160
-rw-r--r--src/lib/dns/rdata/generic/mx_15.h52
-rw-r--r--src/lib/dns/rdata/generic/naptr_35.cc256
-rw-r--r--src/lib/dns/rdata/generic/naptr_35.h64
-rw-r--r--src/lib/dns/rdata/generic/ns_2.cc121
-rw-r--r--src/lib/dns/rdata/generic/ns_2.h43
-rw-r--r--src/lib/dns/rdata/generic/nsec3_50.cc343
-rw-r--r--src/lib/dns/rdata/generic/nsec3_50.h54
-rw-r--r--src/lib/dns/rdata/generic/nsec3param_51.cc232
-rw-r--r--src/lib/dns/rdata/generic/nsec3param_51.h56
-rw-r--r--src/lib/dns/rdata/generic/nsec_47.cc216
-rw-r--r--src/lib/dns/rdata/generic/nsec_47.h53
-rw-r--r--src/lib/dns/rdata/generic/opt_41.cc219
-rw-r--r--src/lib/dns/rdata/generic/opt_41.h90
-rw-r--r--src/lib/dns/rdata/generic/ptr_12.cc123
-rw-r--r--src/lib/dns/rdata/generic/ptr_12.h44
-rw-r--r--src/lib/dns/rdata/generic/rp_17.cc160
-rw-r--r--src/lib/dns/rdata/generic/rp_17.h78
-rw-r--r--src/lib/dns/rdata/generic/rrsig_46.cc334
-rw-r--r--src/lib/dns/rdata/generic/rrsig_46.h54
-rw-r--r--src/lib/dns/rdata/generic/soa_6.cc210
-rw-r--r--src/lib/dns/rdata/generic/soa_6.h51
-rw-r--r--src/lib/dns/rdata/generic/spf_99.cc139
-rw-r--r--src/lib/dns/rdata/generic/spf_99.h72
-rw-r--r--src/lib/dns/rdata/generic/sshfp_44.cc298
-rw-r--r--src/lib/dns/rdata/generic/sshfp_44.h56
-rw-r--r--src/lib/dns/rdata/generic/tkey_249.cc613
-rw-r--r--src/lib/dns/rdata/generic/tkey_249.h142
-rw-r--r--src/lib/dns/rdata/generic/tlsa_52.cc342
-rw-r--r--src/lib/dns/rdata/generic/tlsa_52.h57
-rw-r--r--src/lib/dns/rdata/generic/txt_16.cc95
-rw-r--r--src/lib/dns/rdata/generic/txt_16.h46
-rw-r--r--src/lib/dns/rdata/hs_4/a_1.cc64
-rw-r--r--src/lib/dns/rdata/hs_4/a_1.h32
-rw-r--r--src/lib/dns/rdata/in_1/a_1.cc174
-rw-r--r--src/lib/dns/rdata/in_1/a_1.h38
-rw-r--r--src/lib/dns/rdata/in_1/aaaa_28.cc153
-rw-r--r--src/lib/dns/rdata/in_1/aaaa_28.h38
-rw-r--r--src/lib/dns/rdata/in_1/dhcid_49.cc161
-rw-r--r--src/lib/dns/rdata/in_1/dhcid_49.h53
-rw-r--r--src/lib/dns/rdata/in_1/srv_33.cc298
-rw-r--r--src/lib/dns/rdata/in_1/srv_33.h85
-rw-r--r--src/lib/dns/rdata/template.cc67
-rw-r--r--src/lib/dns/rdata/template.h54
-rw-r--r--src/lib/dns/rdata_pimpl_holder.h52
-rw-r--r--src/lib/dns/rdataclass.cc7078
-rw-r--r--src/lib/dns/rdataclass.h2746
-rw-r--r--src/lib/dns/rdatafields.cc216
-rw-r--r--src/lib/dns/rdatafields.h419
-rw-r--r--src/lib/dns/rrclass-placeholder.h312
-rw-r--r--src/lib/dns/rrclass.cc74
-rw-r--r--src/lib/dns/rrclass.h354
-rw-r--r--src/lib/dns/rrcollator.cc105
-rw-r--r--src/lib/dns/rrcollator.h125
-rw-r--r--src/lib/dns/rrparamregistry-placeholder.cc556
-rw-r--r--src/lib/dns/rrparamregistry.cc660
-rw-r--r--src/lib/dns/rrparamregistry.h545
-rw-r--r--src/lib/dns/rrset.cc466
-rw-r--r--src/lib/dns/rrset.h958
-rw-r--r--src/lib/dns/rrset_collection.cc123
-rw-r--r--src/lib/dns/rrset_collection.h164
-rw-r--r--src/lib/dns/rrset_collection_base.h214
-rw-r--r--src/lib/dns/rrttl.cc214
-rw-r--r--src/lib/dns/rrttl.h306
-rw-r--r--src/lib/dns/rrtype-placeholder.h286
-rw-r--r--src/lib/dns/rrtype.cc65
-rw-r--r--src/lib/dns/rrtype.h748
-rw-r--r--src/lib/dns/serial.cc70
-rw-r--r--src/lib/dns/serial.h150
-rw-r--r--src/lib/dns/tests/Makefile.am94
-rw-r--r--src/lib/dns/tests/Makefile.in2357
-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.cc1243
-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.cc1445
-rw-r--r--src/lib/dns/tests/masterload_unittest.cc397
-rw-r--r--src/lib/dns/tests/message_unittest.cc1162
-rw-r--r--src/lib/dns/tests/messagerenderer_unittest.cc292
-rw-r--r--src/lib/dns/tests/name_unittest.cc797
-rw-r--r--src/lib/dns/tests/nsec3hash_unittest.cc269
-rw-r--r--src/lib/dns/tests/opcode_unittest.cc100
-rw-r--r--src/lib/dns/tests/qid_gen_unittest.cc39
-rw-r--r--src/lib/dns/tests/question_unittest.cc196
-rw-r--r--src/lib/dns/tests/rcode_unittest.cc126
-rw-r--r--src/lib/dns/tests/rdata_afsdb_unittest.cc235
-rw-r--r--src/lib/dns/tests/rdata_caa_unittest.cc322
-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_cname_unittest.cc135
-rw-r--r--src/lib/dns/tests/rdata_dhcid_unittest.cc165
-rw-r--r--src/lib/dns/tests/rdata_dname_unittest.cc137
-rw-r--r--src/lib/dns/tests/rdata_dnskey_unittest.cc200
-rw-r--r--src/lib/dns/tests/rdata_ds_like_unittest.cc229
-rw-r--r--src/lib/dns/tests/rdata_hinfo_unittest.cc154
-rw-r--r--src/lib/dns/tests/rdata_in_a_unittest.cc159
-rw-r--r--src/lib/dns/tests/rdata_in_aaaa_unittest.cc151
-rw-r--r--src/lib/dns/tests/rdata_minfo_unittest.cc230
-rw-r--r--src/lib/dns/tests/rdata_mx_unittest.cc147
-rw-r--r--src/lib/dns/tests/rdata_naptr_unittest.cc239
-rw-r--r--src/lib/dns/tests/rdata_ns_unittest.cc145
-rw-r--r--src/lib/dns/tests/rdata_nsec3_unittest.cc226
-rw-r--r--src/lib/dns/tests/rdata_nsec3param_like_unittest.cc272
-rw-r--r--src/lib/dns/tests/rdata_nsec3param_unittest.cc209
-rw-r--r--src/lib/dns/tests/rdata_nsec_unittest.cc138
-rw-r--r--src/lib/dns/tests/rdata_nsecbitmap_unittest.cc269
-rw-r--r--src/lib/dns/tests/rdata_opt_unittest.cc198
-rw-r--r--src/lib/dns/tests/rdata_pimpl_holder_unittest.cc56
-rw-r--r--src/lib/dns/tests/rdata_ptr_unittest.cc145
-rw-r--r--src/lib/dns/tests/rdata_rp_unittest.cc200
-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_srv_unittest.cc205
-rw-r--r--src/lib/dns/tests/rdata_sshfp_unittest.cc311
-rw-r--r--src/lib/dns/tests/rdata_tkey_unittest.cc450
-rw-r--r--src/lib/dns/tests/rdata_tlsa_unittest.cc276
-rw-r--r--src/lib/dns/tests/rdata_tsig_unittest.cc423
-rw-r--r--src/lib/dns/tests/rdata_txt_like_unittest.cc397
-rw-r--r--src/lib/dns/tests/rdata_unittest.cc467
-rw-r--r--src/lib/dns/tests/rdata_unittest.h92
-rw-r--r--src/lib/dns/tests/rdatafields_unittest.cc366
-rw-r--r--src/lib/dns/tests/rrclass_unittest.cc174
-rw-r--r--src/lib/dns/tests/rrcollator_unittest.cc208
-rw-r--r--src/lib/dns/tests/rrparamregistry_unittest.cc185
-rw-r--r--src/lib/dns/tests/rrset_collection_unittest.cc240
-rw-r--r--src/lib/dns/tests/rrset_unittest.cc442
-rw-r--r--src/lib/dns/tests/rrttl_unittest.cc279
-rw-r--r--src/lib/dns/tests/rrtype_unittest.cc195
-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.am219
-rw-r--r--src/lib/dns/tests/testdata/Makefile.in740
-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_afsdb_fromWire1.spec3
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.wire11
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.spec4
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.spec4
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.spec4
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_toWire1.spec4
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_toWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_toWire2.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_afsdb_toWire2.wire11
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire2.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire3.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire4.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire56
-rw-r--r--src/lib/dns/tests/testdata/rdata_caa_fromWire64
-rw-r--r--src/lib/dns/tests/testdata/rdata_cname_fromWire44
-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_dname_fromWire44
-rw-r--r--src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_dnskey_fromWire.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_ds_fromWire6
-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_minfo_fromWire1.spec3
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire2.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire2.wire11
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire3.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire3.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire4.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire4.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire5.spec5
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire5.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire6.spec5
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_fromWire6.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWire1.spec5
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWire2.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWire2.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_mx_fromWire15
-rw-r--r--src/lib/dns/tests/testdata/rdata_mx_toWire112
-rw-r--r--src/lib/dns/tests/testdata/rdata_mx_toWire212
-rw-r--r--src/lib/dns/tests/testdata/rdata_ns_fromWire44
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire17
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.wire12
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire36
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec13
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.wire16
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire15
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire16
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire10.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire10.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire16.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire210
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire310
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire4.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire4.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire5.spec13
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire5.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire6.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire6.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire7.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire7.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire8.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire8.wire10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire9.spec10
-rw-r--r--src/lib/dns/tests/testdata/rdata_nsec_fromWire9.wire12
-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_rp_fromWire1.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec12
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire2.wire14
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire3.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire4.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire5.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_fromWire6.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_toWire1.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_toWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_toWire2.spec14
-rw-r--r--src/lib/dns/tests/testdata/rdata_rp_toWire2.wire14
-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_srv_fromWire36
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire4
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec6
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire106
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire114
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire124
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire24
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.spec8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_sshfp_fromWire96
-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_tlsa_fromWire4
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire106
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire114
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire124
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire24
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec9
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.wire8
-rw-r--r--src/lib/dns/tests/testdata/rdata_tlsa_fromWire97
-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/rdatafields1.spec10
-rw-r--r--src/lib/dns/tests/testdata/rdatafields1.wire9
-rw-r--r--src/lib/dns/tests/testdata/rdatafields2.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdatafields2.wire9
-rw-r--r--src/lib/dns/tests/testdata/rdatafields3.spec11
-rw-r--r--src/lib/dns/tests/testdata/rdatafields3.wire12
-rw-r--r--src/lib/dns/tests/testdata/rdatafields4.spec7
-rw-r--r--src/lib/dns/tests/testdata/rdatafields4.wire12
-rw-r--r--src/lib/dns/tests/testdata/rdatafields5.spec12
-rw-r--r--src/lib/dns/tests/testdata/rdatafields5.wire18
-rw-r--r--src/lib/dns/tests/testdata/rdatafields6.spec13
-rw-r--r--src/lib/dns/tests/testdata/rdatafields6.wire18
-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/tsig_unittest.cc1185
-rw-r--r--src/lib/dns/tests/tsigerror_unittest.cc126
-rw-r--r--src/lib/dns/tests/tsigkey_unittest.cc350
-rw-r--r--src/lib/dns/tests/tsigrecord_unittest.cc158
-rw-r--r--src/lib/dns/tests/unittest_util.cc176
-rw-r--r--src/lib/dns/tests/unittest_util.h88
-rw-r--r--src/lib/dns/tests/zone_checker_unittest.cc349
-rw-r--r--src/lib/dns/tsig.cc581
-rw-r--r--src/lib/dns/tsig.h445
-rw-r--r--src/lib/dns/tsigerror.cc66
-rw-r--r--src/lib/dns/tsigerror.h374
-rw-r--r--src/lib/dns/tsigkey.cc364
-rw-r--r--src/lib/dns/tsigkey.h391
-rw-r--r--src/lib/dns/tsigrecord.cc142
-rw-r--r--src/lib/dns/tsigrecord.h300
-rw-r--r--src/lib/dns/zone_checker.cc191
-rw-r--r--src/lib/dns/zone_checker.h153
-rw-r--r--src/lib/eval/Makefile.am139
-rw-r--r--src/lib/eval/Makefile.in1134
-rw-r--r--src/lib/eval/dependency.cc37
-rw-r--r--src/lib/eval/dependency.h37
-rw-r--r--src/lib/eval/eval.dox198
-rw-r--r--src/lib/eval/eval_context.cc260
-rw-r--r--src/lib/eval/eval_context.h249
-rw-r--r--src/lib/eval/eval_context_decl.h20
-rw-r--r--src/lib/eval/eval_log.cc23
-rw-r--r--src/lib/eval/eval_log.h38
-rw-r--r--src/lib/eval/eval_messages.cc113
-rw-r--r--src/lib/eval/eval_messages.h60
-rw-r--r--src/lib/eval/eval_messages.mes285
-rw-r--r--src/lib/eval/evaluate.cc42
-rw-r--r--src/lib/eval/evaluate.h34
-rw-r--r--src/lib/eval/lexer.cc2996
-rw-r--r--src/lib/eval/lexer.ll277
-rw-r--r--src/lib/eval/location.hh306
-rw-r--r--src/lib/eval/parser.cc2227
-rw-r--r--src/lib/eval/parser.h2677
-rw-r--r--src/lib/eval/parser.yy649
-rw-r--r--src/lib/eval/tests/Makefile.am46
-rw-r--r--src/lib/eval/tests/Makefile.in1098
-rw-r--r--src/lib/eval/tests/boolean_unittest.cc64
-rw-r--r--src/lib/eval/tests/context_unittest.cc2118
-rw-r--r--src/lib/eval/tests/dependency_unittest.cc103
-rw-r--r--src/lib/eval/tests/evaluate_unittest.cc515
-rw-r--r--src/lib/eval/tests/run_unittests.cc21
-rw-r--r--src/lib/eval/tests/token_unittest.cc3487
-rw-r--r--src/lib/eval/token.cc1319
-rw-r--r--src/lib/eval/token.h1300
-rw-r--r--src/lib/exceptions/Makefile.am16
-rw-r--r--src/lib/exceptions/Makefile.in932
-rw-r--r--src/lib/exceptions/exceptions.cc55
-rw-r--r--src/lib/exceptions/exceptions.h264
-rw-r--r--src/lib/exceptions/isc_assert.h28
-rw-r--r--src/lib/exceptions/tests/Makefile.am24
-rw-r--r--src/lib/exceptions/tests/Makefile.in875
-rw-r--r--src/lib/exceptions/tests/exceptions_unittest.cc117
-rw-r--r--src/lib/exceptions/tests/run_unittests.cc18
-rw-r--r--src/lib/hooks/Makefile.am103
-rw-r--r--src/lib/hooks/Makefile.in1164
-rw-r--r--src/lib/hooks/callout_handle.cc167
-rw-r--r--src/lib/hooks/callout_handle.h512
-rw-r--r--src/lib/hooks/callout_handle_associate.cc34
-rw-r--r--src/lib/hooks/callout_handle_associate.h63
-rw-r--r--src/lib/hooks/callout_manager.cc351
-rw-r--r--src/lib/hooks/callout_manager.h438
-rw-r--r--src/lib/hooks/hooks.h71
-rw-r--r--src/lib/hooks/hooks_component_developer.dox520
-rw-r--r--src/lib/hooks/hooks_config.cc129
-rw-r--r--src/lib/hooks/hooks_config.h115
-rw-r--r--src/lib/hooks/hooks_log.cc28
-rw-r--r--src/lib/hooks/hooks_log.h49
-rw-r--r--src/lib/hooks/hooks_maintenance.dox381
-rw-r--r--src/lib/hooks/hooks_manager.cc280
-rw-r--r--src/lib/hooks/hooks_manager.h496
-rw-r--r--src/lib/hooks/hooks_messages.cc93
-rw-r--r--src/lib/hooks/hooks_messages.h50
-rw-r--r--src/lib/hooks/hooks_messages.mes201
-rw-r--r--src/lib/hooks/hooks_parser.cc110
-rw-r--r--src/lib/hooks/hooks_parser.h65
-rw-r--r--src/lib/hooks/hooks_user.dox1670
-rw-r--r--src/lib/hooks/images/DataScopeArgument.diabin0 -> 1887 bytes
-rw-r--r--src/lib/hooks/images/DataScopeArgument.pngbin0 -> 11672 bytes
-rw-r--r--src/lib/hooks/images/DataScopeContext.diabin0 -> 2161 bytes
-rw-r--r--src/lib/hooks/images/DataScopeContext.pngbin0 -> 14180 bytes
-rw-r--r--src/lib/hooks/images/HooksUml.diabin0 -> 2354 bytes
-rw-r--r--src/lib/hooks/images/HooksUml.pngbin0 -> 13841 bytes
-rw-r--r--src/lib/hooks/libinfo.cc30
-rw-r--r--src/lib/hooks/libinfo.h42
-rw-r--r--src/lib/hooks/library_handle.cc131
-rw-r--r--src/lib/hooks/library_handle.h242
-rw-r--r--src/lib/hooks/library_manager.cc435
-rw-r--r--src/lib/hooks/library_manager.h253
-rw-r--r--src/lib/hooks/library_manager_collection.cc153
-rw-r--r--src/lib/hooks/library_manager_collection.h192
-rw-r--r--src/lib/hooks/parking_lots.h426
-rw-r--r--src/lib/hooks/pointer_converter.h122
-rw-r--r--src/lib/hooks/server_hooks.cc221
-rw-r--r--src/lib/hooks/server_hooks.h246
-rw-r--r--src/lib/hooks/tests/Makefile.am152
-rw-r--r--src/lib/hooks/tests/Makefile.in1547
-rw-r--r--src/lib/hooks/tests/async_callout_library.cc153
-rw-r--r--src/lib/hooks/tests/basic_callout_library.cc145
-rw-r--r--src/lib/hooks/tests/callout_handle_associate_unittest.cc35
-rw-r--r--src/lib/hooks/tests/callout_handle_unittest.cc384
-rw-r--r--src/lib/hooks/tests/callout_manager_unittest.cc944
-rw-r--r--src/lib/hooks/tests/callout_params_library.cc129
-rw-r--r--src/lib/hooks/tests/common_test_class.h174
-rw-r--r--src/lib/hooks/tests/framework_exception_library.cc41
-rw-r--r--src/lib/hooks/tests/full_callout_library.cc177
-rw-r--r--src/lib/hooks/tests/handles_unittest.cc777
-rw-r--r--src/lib/hooks/tests/hooks_manager_unittest.cc1081
-rw-r--r--src/lib/hooks/tests/incorrect_version_library.cc26
-rw-r--r--src/lib/hooks/tests/library_manager_collection_unittest.cc314
-rw-r--r--src/lib/hooks/tests/library_manager_unittest.cc735
-rw-r--r--src/lib/hooks/tests/load_callout_library.cc152
-rw-r--r--src/lib/hooks/tests/load_error_callout_library.cc47
-rw-r--r--src/lib/hooks/tests/marker_file.h.in19
-rw-r--r--src/lib/hooks/tests/no_version_library.cc24
-rw-r--r--src/lib/hooks/tests/parking_lots_unittest.cc339
-rw-r--r--src/lib/hooks/tests/run_unittests.cc19
-rw-r--r--src/lib/hooks/tests/server_hooks_unittest.cc232
-rw-r--r--src/lib/hooks/tests/test_libraries.h.in61
-rw-r--r--src/lib/hooks/tests/unload_callout_library.cc46
-rw-r--r--src/lib/http/Makefile.am131
-rw-r--r--src/lib/http/Makefile.in1310
-rw-r--r--src/lib/http/auth_config.h102
-rw-r--r--src/lib/http/auth_log.cc20
-rw-r--r--src/lib/http/auth_log.h23
-rw-r--r--src/lib/http/auth_messages.cc31
-rw-r--r--src/lib/http/auth_messages.h19
-rw-r--r--src/lib/http/auth_messages.mes24
-rw-r--r--src/lib/http/basic_auth.cc45
-rw-r--r--src/lib/http/basic_auth.h87
-rw-r--r--src/lib/http/basic_auth_config.cc399
-rw-r--r--src/lib/http/basic_auth_config.h204
-rw-r--r--src/lib/http/client.cc2062
-rw-r--r--src/lib/http/client.h344
-rw-r--r--src/lib/http/connection.cc600
-rw-r--r--src/lib/http/connection.h432
-rw-r--r--src/lib/http/connection_pool.cc74
-rw-r--r--src/lib/http/connection_pool.h78
-rw-r--r--src/lib/http/date_time.cc158
-rw-r--r--src/lib/http/date_time.h161
-rw-r--r--src/lib/http/header_context.h49
-rw-r--r--src/lib/http/http.dox24
-rw-r--r--src/lib/http/http_acceptor.h39
-rw-r--r--src/lib/http/http_header.cc56
-rw-r--r--src/lib/http/http_header.h86
-rw-r--r--src/lib/http/http_log.cc21
-rw-r--r--src/lib/http/http_log.h23
-rw-r--r--src/lib/http/http_message.cc108
-rw-r--r--src/lib/http/http_message.h265
-rw-r--r--src/lib/http/http_message_parser_base.cc307
-rw-r--r--src/lib/http/http_message_parser_base.h316
-rw-r--r--src/lib/http/http_messages.cc77
-rw-r--r--src/lib/http/http_messages.h42
-rw-r--r--src/lib/http/http_messages.mes164
-rw-r--r--src/lib/http/http_types.h76
-rw-r--r--src/lib/http/listener.cc56
-rw-r--r--src/lib/http/listener.h147
-rw-r--r--src/lib/http/listener_impl.cc125
-rw-r--r--src/lib/http/listener_impl.h136
-rw-r--r--src/lib/http/post_request.cc33
-rw-r--r--src/lib/http/post_request.h53
-rw-r--r--src/lib/http/post_request_json.cc102
-rw-r--r--src/lib/http/post_request_json.h110
-rw-r--r--src/lib/http/request.cc285
-rw-r--r--src/lib/http/request.h312
-rw-r--r--src/lib/http/request_context.h44
-rw-r--r--src/lib/http/request_parser.cc458
-rw-r--r--src/lib/http/request_parser.h246
-rw-r--r--src/lib/http/response.cc233
-rw-r--r--src/lib/http/response.h247
-rw-r--r--src/lib/http/response_context.h44
-rw-r--r--src/lib/http/response_creator.cc33
-rw-r--r--src/lib/http/response_creator.h113
-rw-r--r--src/lib/http/response_creator_factory.h59
-rw-r--r--src/lib/http/response_json.cc122
-rw-r--r--src/lib/http/response_json.h114
-rw-r--r--src/lib/http/response_parser.cc461
-rw-r--r--src/lib/http/response_parser.h243
-rw-r--r--src/lib/http/tests/Makefile.am76
-rw-r--r--src/lib/http/tests/Makefile.in1392
-rw-r--r--src/lib/http/tests/basic_auth_config_unittests.cc540
-rw-r--r--src/lib/http/tests/basic_auth_unittests.cc65
-rw-r--r--src/lib/http/tests/client_mt_unittests.cc1042
-rw-r--r--src/lib/http/tests/connection_pool_unittests.cc270
-rw-r--r--src/lib/http/tests/date_time_unittests.cc190
-rw-r--r--src/lib/http/tests/http_header_unittests.cc54
-rw-r--r--src/lib/http/tests/post_request_json_unittests.cc197
-rw-r--r--src/lib/http/tests/post_request_unittests.cc83
-rw-r--r--src/lib/http/tests/request_parser_unittests.cc387
-rw-r--r--src/lib/http/tests/request_test.h82
-rw-r--r--src/lib/http/tests/request_unittests.cc422
-rw-r--r--src/lib/http/tests/response_creator_unittests.cc340
-rw-r--r--src/lib/http/tests/response_json_unittests.cc151
-rw-r--r--src/lib/http/tests/response_parser_unittests.cc351
-rw-r--r--src/lib/http/tests/response_test.h62
-rw-r--r--src/lib/http/tests/response_unittests.cc169
-rw-r--r--src/lib/http/tests/run_unittests.cc21
-rw-r--r--src/lib/http/tests/server_client_unittests.cc2041
-rw-r--r--src/lib/http/tests/test_http_client.h273
-rw-r--r--src/lib/http/tests/testdata/empty0
-rw-r--r--src/lib/http/tests/testdata/hiddenp1
-rw-r--r--src/lib/http/tests/testdata/hiddens1
-rw-r--r--src/lib/http/tests/testdata/hiddenu1
-rw-r--r--src/lib/http/tests/tls_client_unittests.cc1398
-rw-r--r--src/lib/http/tests/tls_server_unittests.cc1253
-rw-r--r--src/lib/http/tests/url_unittests.cc115
-rw-r--r--src/lib/http/url.cc223
-rw-r--r--src/lib/http/url.h131
-rw-r--r--src/lib/log/Makefile.am119
-rw-r--r--src/lib/log/Makefile.in1258
-rw-r--r--src/lib/log/buffer_appender_impl.cc96
-rw-r--r--src/lib/log/buffer_appender_impl.h110
-rw-r--r--src/lib/log/compiler/Makefile.am24
-rw-r--r--src/lib/log/compiler/Makefile.in878
-rw-r--r--src/lib/log/compiler/message.cc543
-rw-r--r--src/lib/log/interprocess/Makefile.am21
-rw-r--r--src/lib/log/interprocess/Makefile.in854
-rw-r--r--src/lib/log/interprocess/README13
-rw-r--r--src/lib/log/interprocess/interprocess_sync.h143
-rw-r--r--src/lib/log/interprocess/interprocess_sync_file.cc132
-rw-r--r--src/lib/log/interprocess/interprocess_sync_file.h85
-rw-r--r--src/lib/log/interprocess/interprocess_sync_null.cc38
-rw-r--r--src/lib/log/interprocess/interprocess_sync_null.h58
-rw-r--r--src/lib/log/interprocess/tests/Makefile.am36
-rw-r--r--src/lib/log/interprocess/tests/Makefile.in1024
-rw-r--r--src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc153
-rw-r--r--src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc69
-rw-r--r--src/lib/log/interprocess/tests/run_unittests.cc22
-rw-r--r--src/lib/log/log_dbglevels.cc25
-rw-r--r--src/lib/log/log_dbglevels.h83
-rw-r--r--src/lib/log/log_formatter.cc68
-rw-r--r--src/lib/log/log_formatter.h263
-rw-r--r--src/lib/log/log_messages.cc63
-rw-r--r--src/lib/log/log_messages.h35
-rw-r--r--src/lib/log/log_messages.mes138
-rw-r--r--src/lib/log/logger.cc220
-rw-r--r--src/lib/log/logger.h369
-rw-r--r--src/lib/log/logger_impl.cc231
-rw-r--r--src/lib/log/logger_impl.h208
-rw-r--r--src/lib/log/logger_level.cc42
-rw-r--r--src/lib/log/logger_level.h68
-rw-r--r--src/lib/log/logger_level_impl.cc215
-rw-r--r--src/lib/log/logger_level_impl.h126
-rw-r--r--src/lib/log/logger_manager.cc216
-rw-r--r--src/lib/log/logger_manager.h173
-rw-r--r--src/lib/log/logger_manager_impl.cc322
-rw-r--r--src/lib/log/logger_manager_impl.h197
-rw-r--r--src/lib/log/logger_name.cc58
-rw-r--r--src/lib/log/logger_name.h55
-rw-r--r--src/lib/log/logger_specification.h148
-rw-r--r--src/lib/log/logger_support.cc119
-rw-r--r--src/lib/log/logger_support.h78
-rw-r--r--src/lib/log/logger_unittest_support.cc101
-rw-r--r--src/lib/log/logger_unittest_support.h111
-rw-r--r--src/lib/log/logging.dox698
-rw-r--r--src/lib/log/logimpl_messages.cc29
-rw-r--r--src/lib/log/logimpl_messages.h18
-rw-r--r--src/lib/log/logimpl_messages.mes35
-rw-r--r--src/lib/log/macros.h43
-rw-r--r--src/lib/log/message_dictionary.cc121
-rw-r--r--src/lib/log/message_dictionary.h208
-rw-r--r--src/lib/log/message_exception.h112
-rw-r--r--src/lib/log/message_initializer.cc137
-rw-r--r--src/lib/log/message_initializer.h170
-rw-r--r--src/lib/log/message_reader.cc279
-rw-r--r--src/lib/log/message_reader.h207
-rw-r--r--src/lib/log/message_types.h29
-rw-r--r--src/lib/log/output_option.cc58
-rw-r--r--src/lib/log/output_option.h85
-rw-r--r--src/lib/log/tests/Makefile.am158
-rw-r--r--src/lib/log/tests/Makefile.in1602
-rw-r--r--src/lib/log/tests/buffer_appender_unittest.cc138
-rw-r--r--src/lib/log/tests/buffer_logger_test.cc66
-rw-r--r--src/lib/log/tests/buffer_logger_test.sh.in45
-rw-r--r--src/lib/log/tests/console_test.sh.in54
-rw-r--r--src/lib/log/tests/destination_test.sh.in85
-rw-r--r--src/lib/log/tests/init_logger_test.cc36
-rw-r--r--src/lib/log/tests/init_logger_test.sh.in96
-rw-r--r--src/lib/log/tests/local_file_test.sh.in66
-rw-r--r--src/lib/log/tests/log_formatter_unittest.cc181
-rw-r--r--src/lib/log/tests/log_test_messages.cc25
-rw-r--r--src/lib/log/tests/log_test_messages.h16
-rw-r--r--src/lib/log/tests/log_test_messages.mes18
-rw-r--r--src/lib/log/tests/logger_example.cc306
-rw-r--r--src/lib/log/tests/logger_level_impl_unittest.cc168
-rw-r--r--src/lib/log/tests/logger_level_unittest.cc78
-rw-r--r--src/lib/log/tests/logger_lock_test.cc101
-rw-r--r--src/lib/log/tests/logger_lock_test.sh.in38
-rw-r--r--src/lib/log/tests/logger_manager_unittest.cc511
-rw-r--r--src/lib/log/tests/logger_name_unittest.cc76
-rw-r--r--src/lib/log/tests/logger_specification_unittest.cc90
-rw-r--r--src/lib/log/tests/logger_support_unittest.cc77
-rw-r--r--src/lib/log/tests/logger_unittest.cc508
-rw-r--r--src/lib/log/tests/message_dictionary_unittest.cc219
-rw-r--r--src/lib/log/tests/message_initializer_1_unittest.cc251
-rw-r--r--src/lib/log/tests/message_initializer_1a_unittest.cc31
-rw-r--r--src/lib/log/tests/message_reader_unittest.cc269
-rw-r--r--src/lib/log/tests/output_option_unittest.cc60
-rw-r--r--src/lib/log/tests/run_initializer_unittests.cc19
-rw-r--r--src/lib/log/tests/run_unittests.cc20
-rw-r--r--src/lib/log/tests/severity_test.sh.in74
-rw-r--r--src/lib/log/tests/tempdir.h.in21
-rw-r--r--src/lib/mysql/Makefile.am32
-rw-r--r--src/lib/mysql/Makefile.in960
-rw-r--r--src/lib/mysql/mysql_binding.cc349
-rw-r--r--src/lib/mysql/mysql_binding.h671
-rw-r--r--src/lib/mysql/mysql_connection.cc534
-rw-r--r--src/lib/mysql/mysql_connection.h781
-rw-r--r--src/lib/mysql/mysql_constants.h64
-rw-r--r--src/lib/mysql/tests/Makefile.am39
-rw-r--r--src/lib/mysql/tests/Makefile.in1028
-rw-r--r--src/lib/mysql/tests/mysql_binding_unittest.cc249
-rw-r--r--src/lib/mysql/tests/mysql_connection_unittest.cc921
-rw-r--r--src/lib/mysql/tests/run_unittests.cc20
-rw-r--r--src/lib/mysql/testutils/Makefile.am24
-rw-r--r--src/lib/mysql/testutils/Makefile.in862
-rw-r--r--src/lib/mysql/testutils/mysql_schema.cc163
-rw-r--r--src/lib/mysql/testutils/mysql_schema.h123
-rw-r--r--src/lib/pgsql/Makefile.am31
-rw-r--r--src/lib/pgsql/Makefile.in959
-rw-r--r--src/lib/pgsql/pgsql_connection.cc560
-rw-r--r--src/lib/pgsql/pgsql_connection.h597
-rw-r--r--src/lib/pgsql/pgsql_exchange.cc767
-rw-r--r--src/lib/pgsql/pgsql_exchange.h1004
-rw-r--r--src/lib/pgsql/tests/Makefile.am40
-rw-r--r--src/lib/pgsql/tests/Makefile.in1066
-rw-r--r--src/lib/pgsql/tests/pgsql_basics.cc149
-rw-r--r--src/lib/pgsql/tests/pgsql_basics.h161
-rw-r--r--src/lib/pgsql/tests/pgsql_connection_unittest.cc651
-rw-r--r--src/lib/pgsql/tests/pgsql_exchange_unittest.cc1545
-rw-r--r--src/lib/pgsql/tests/run_unittests.cc20
-rw-r--r--src/lib/pgsql/testutils/Makefile.am24
-rw-r--r--src/lib/pgsql/testutils/Makefile.in862
-rw-r--r--src/lib/pgsql/testutils/pgsql_schema.cc103
-rw-r--r--src/lib/pgsql/testutils/pgsql_schema.h105
-rw-r--r--src/lib/process/Makefile.am99
-rw-r--r--src/lib/process/Makefile.in1146
-rw-r--r--src/lib/process/cb_ctl_base.h373
-rw-r--r--src/lib/process/cfgrpt/Makefile.am29
-rw-r--r--src/lib/process/cfgrpt/Makefile.in930
-rw-r--r--src/lib/process/cfgrpt/cfgrpt.cc34
-rw-r--r--src/lib/process/cfgrpt/config_report.h25
-rw-r--r--src/lib/process/cfgrpt/tests/Makefile.am27
-rw-r--r--src/lib/process/cfgrpt/tests/Makefile.in990
-rw-r--r--src/lib/process/cfgrpt/tests/config_report_unittests.cc27
-rw-r--r--src/lib/process/cfgrpt/tests/run_unittests.cc18
-rw-r--r--src/lib/process/config_base.cc143
-rw-r--r--src/lib/process/config_base.h181
-rw-r--r--src/lib/process/config_ctl_info.cc130
-rw-r--r--src/lib/process/config_ctl_info.h247
-rw-r--r--src/lib/process/config_ctl_parser.cc66
-rw-r--r--src/lib/process/config_ctl_parser.h35
-rw-r--r--src/lib/process/d_cfg_mgr.cc164
-rw-r--r--src/lib/process/d_cfg_mgr.h252
-rw-r--r--src/lib/process/d_controller.cc860
-rw-r--r--src/lib/process/d_controller.h663
-rw-r--r--src/lib/process/d_log.cc21
-rw-r--r--src/lib/process/d_log.h23
-rw-r--r--src/lib/process/d_process.h214
-rw-r--r--src/lib/process/daemon.cc265
-rw-r--r--src/lib/process/daemon.h296
-rw-r--r--src/lib/process/libprocess.dox195
-rw-r--r--src/lib/process/log_parser.cc158
-rw-r--r--src/lib/process/log_parser.h93
-rw-r--r--src/lib/process/logging_info.cc212
-rw-r--r--src/lib/process/logging_info.h146
-rw-r--r--src/lib/process/process_messages.cc83
-rw-r--r--src/lib/process/process_messages.h45
-rw-r--r--src/lib/process/process_messages.mes156
-rw-r--r--src/lib/process/redact_config.cc97
-rw-r--r--src/lib/process/redact_config.h37
-rw-r--r--src/lib/process/tests/Makefile.am56
-rw-r--r--src/lib/process/tests/Makefile.in1184
-rw-r--r--src/lib/process/tests/cb_ctl_base_unittests.cc718
-rw-r--r--src/lib/process/tests/config_base_unittests.cc230
-rw-r--r--src/lib/process/tests/config_ctl_info_unittests.cc180
-rw-r--r--src/lib/process/tests/config_ctl_parser_unittests.cc102
-rw-r--r--src/lib/process/tests/d_cfg_mgr_unittests.cc375
-rw-r--r--src/lib/process/tests/d_controller_unittests.cc470
-rw-r--r--src/lib/process/tests/daemon_unittest.cc324
-rw-r--r--src/lib/process/tests/log_parser_unittests.cc530
-rw-r--r--src/lib/process/tests/logging_info_unittests.cc208
-rw-r--r--src/lib/process/tests/run_unittests.cc24
-rw-r--r--src/lib/process/testutils/Makefile.am27
-rw-r--r--src/lib/process/testutils/Makefile.in870
-rw-r--r--src/lib/process/testutils/d_test_stubs.cc338
-rw-r--r--src/lib/process/testutils/d_test_stubs.h753
-rw-r--r--src/lib/stats/Makefile.am28
-rw-r--r--src/lib/stats/Makefile.in978
-rw-r--r--src/lib/stats/context.cc99
-rw-r--r--src/lib/stats/context.h90
-rw-r--r--src/lib/stats/observation.cc568
-rw-r--r--src/lib/stats/observation.h484
-rw-r--r--src/lib/stats/stats.dox16
-rw-r--r--src/lib/stats/stats_mgr.cc541
-rw-r--r--src/lib/stats/stats_mgr.h805
-rw-r--r--src/lib/stats/tests/Makefile.am37
-rw-r--r--src/lib/stats/tests/Makefile.in1042
-rw-r--r--src/lib/stats/tests/context_unittest.cc126
-rw-r--r--src/lib/stats/tests/observation_unittest.cc717
-rw-r--r--src/lib/stats/tests/run_unittests.cc18
-rw-r--r--src/lib/stats/tests/stats_mgr_unittest.cc1117
-rw-r--r--src/lib/stats/testutils/Makefile.am1
-rw-r--r--src/lib/stats/testutils/Makefile.in548
-rw-r--r--src/lib/stats/testutils/stats_test_utils.h73
-rw-r--r--src/lib/tcp/Makefile.am80
-rw-r--r--src/lib/tcp/Makefile.in1076
-rw-r--r--src/lib/tcp/README1
-rw-r--r--src/lib/tcp/libkea_tcp.dox19
-rw-r--r--src/lib/tcp/mt_tcp_listener_mgr.cc162
-rw-r--r--src/lib/tcp/mt_tcp_listener_mgr.h211
-rw-r--r--src/lib/tcp/tcp_connection.cc529
-rw-r--r--src/lib/tcp/tcp_connection.h469
-rw-r--r--src/lib/tcp/tcp_connection_acceptor.h38
-rw-r--r--src/lib/tcp/tcp_connection_pool.cc127
-rw-r--r--src/lib/tcp/tcp_connection_pool.h113
-rw-r--r--src/lib/tcp/tcp_listener.cc115
-rw-r--r--src/lib/tcp/tcp_listener.h169
-rw-r--r--src/lib/tcp/tcp_log.cc21
-rw-r--r--src/lib/tcp/tcp_log.h23
-rw-r--r--src/lib/tcp/tcp_messages.cc67
-rw-r--r--src/lib/tcp/tcp_messages.h37
-rw-r--r--src/lib/tcp/tcp_messages.mes122
-rw-r--r--src/lib/tcp/tcp_stream_msg.cc127
-rw-r--r--src/lib/tcp/tcp_stream_msg.h151
-rw-r--r--src/lib/tcp/tests/Makefile.am57
-rw-r--r--src/lib/tcp/tests/Makefile.in970
-rw-r--r--src/lib/tcp/tests/mt_tcp_listener_mgr_unittests.cc984
-rw-r--r--src/lib/tcp/tests/run_unittests.cc20
-rw-r--r--src/lib/tcp/tests/tcp_listener_unittests.cc603
-rw-r--r--src/lib/tcp/tests/tcp_test_client.h437
-rw-r--r--src/lib/tcp/tests/tcp_test_listener.h315
-rw-r--r--src/lib/tcp/tests/tls_listener_unittests.cc498
-rw-r--r--src/lib/testutils/Makefile.am30
-rw-r--r--src/lib/testutils/Makefile.in824
-rw-r--r--src/lib/testutils/README2
-rw-r--r--src/lib/testutils/dhcp_test_lib.sh.in1176
-rw-r--r--src/lib/testutils/gtest_utils.h92
-rw-r--r--src/lib/testutils/io_utils.cc117
-rw-r--r--src/lib/testutils/io_utils.h45
-rw-r--r--src/lib/testutils/lib_load_test_fixture.h148
-rw-r--r--src/lib/testutils/log_utils.cc131
-rw-r--r--src/lib/testutils/log_utils.h105
-rw-r--r--src/lib/testutils/multi_threading_utils.h38
-rw-r--r--src/lib/testutils/sandbox.h69
-rw-r--r--src/lib/testutils/test_to_element.cc34
-rw-r--r--src/lib/testutils/test_to_element.h91
-rw-r--r--src/lib/testutils/threaded_test.cc73
-rw-r--r--src/lib/testutils/threaded_test.h88
-rw-r--r--src/lib/testutils/unix_control_client.cc139
-rw-r--r--src/lib/testutils/unix_control_client.h66
-rw-r--r--src/lib/testutils/user_context_utils.cc137
-rw-r--r--src/lib/testutils/user_context_utils.h37
-rw-r--r--src/lib/testutils/xml_reporting_test_lib.sh.in353
-rw-r--r--src/lib/util/Makefile.am111
-rw-r--r--src/lib/util/Makefile.in1161
-rw-r--r--src/lib/util/bigints.h26
-rw-r--r--src/lib/util/boost_time_utils.cc50
-rw-r--r--src/lib/util/boost_time_utils.h56
-rw-r--r--src/lib/util/buffer.h611
-rw-r--r--src/lib/util/chrono_time_utils.cc102
-rw-r--r--src/lib/util/chrono_time_utils.h48
-rw-r--r--src/lib/util/csv_file.cc557
-rw-r--r--src/lib/util/csv_file.h575
-rw-r--r--src/lib/util/dhcp_space.cc25
-rw-r--r--src/lib/util/dhcp_space.h45
-rw-r--r--src/lib/util/doubles.h29
-rw-r--r--src/lib/util/encode/base16_from_binary.h103
-rw-r--r--src/lib/util/encode/base32hex.h56
-rw-r--r--src/lib/util/encode/base32hex_from_binary.h105
-rw-r--r--src/lib/util/encode/base64.h71
-rw-r--r--src/lib/util/encode/base_n.cc494
-rw-r--r--src/lib/util/encode/binary_from_base16.h112
-rw-r--r--src/lib/util/encode/binary_from_base32hex.h115
-rw-r--r--src/lib/util/encode/hex.h66
-rw-r--r--src/lib/util/encode/utf8.cc35
-rw-r--r--src/lib/util/encode/utf8.h27
-rw-r--r--src/lib/util/file_utilities.cc69
-rw-r--r--src/lib/util/file_utilities.h34
-rw-r--r--src/lib/util/filename.cc148
-rw-r--r--src/lib/util/filename.h164
-rw-r--r--src/lib/util/hash.h57
-rw-r--r--src/lib/util/io/Makefile.am14
-rw-r--r--src/lib/util/io/Makefile.in914
-rw-r--r--src/lib/util/io/fd.cc78
-rw-r--r--src/lib/util/io/fd.h53
-rw-r--r--src/lib/util/io/fd_share.cc166
-rw-r--r--src/lib/util/io/fd_share.h61
-rw-r--r--src/lib/util/io/pktinfo_utilities.h43
-rw-r--r--src/lib/util/io/sockaddr_util.h76
-rw-r--r--src/lib/util/io/socketsession.cc438
-rw-r--r--src/lib/util/io/socketsession.h495
-rw-r--r--src/lib/util/io_utilities.h185
-rw-r--r--src/lib/util/labeled_value.cc117
-rw-r--r--src/lib/util/labeled_value.h176
-rw-r--r--src/lib/util/memory_segment.h333
-rw-r--r--src/lib/util/memory_segment_local.cc71
-rw-r--r--src/lib/util/memory_segment_local.h100
-rw-r--r--src/lib/util/multi_threading_mgr.cc291
-rw-r--r--src/lib/util/multi_threading_mgr.h374
-rw-r--r--src/lib/util/optional.h201
-rw-r--r--src/lib/util/pid_file.cc93
-rw-r--r--src/lib/util/pid_file.h97
-rw-r--r--src/lib/util/pointer_util.h49
-rw-r--r--src/lib/util/python/Makefile.am3
-rw-r--r--src/lib/util/python/Makefile.in555
-rw-r--r--src/lib/util/python/const2hdr.py56
-rw-r--r--src/lib/util/python/gen_wiredata.py.in1448
-rw-r--r--src/lib/util/range_utilities.h61
-rw-r--r--src/lib/util/readwrite_mutex.h187
-rw-r--r--src/lib/util/reconnect_ctl.cc41
-rw-r--r--src/lib/util/reconnect_ctl.h143
-rw-r--r--src/lib/util/staged_value.h118
-rw-r--r--src/lib/util/state_model.cc465
-rw-r--r--src/lib/util/state_model.h850
-rw-r--r--src/lib/util/stopwatch.cc85
-rw-r--r--src/lib/util/stopwatch.h129
-rw-r--r--src/lib/util/stopwatch_impl.cc103
-rw-r--r--src/lib/util/stopwatch_impl.h122
-rw-r--r--src/lib/util/strutil.cc467
-rw-r--r--src/lib/util/strutil.h403
-rw-r--r--src/lib/util/tests/Makefile.am74
-rw-r--r--src/lib/util/tests/Makefile.in1730
-rw-r--r--src/lib/util/tests/base32hex_unittest.cc158
-rw-r--r--src/lib/util/tests/base64_unittest.cc93
-rw-r--r--src/lib/util/tests/bigint_unittest.cc94
-rw-r--r--src/lib/util/tests/boost_time_utils_unittest.cc99
-rw-r--r--src/lib/util/tests/buffer_unittest.cc341
-rw-r--r--src/lib/util/tests/chrono_time_utils_unittest.cc159
-rw-r--r--src/lib/util/tests/csv_file_unittest.cc700
-rw-r--r--src/lib/util/tests/dhcp_space_unittest.cc32
-rw-r--r--src/lib/util/tests/doubles_unittest.cc32
-rw-r--r--src/lib/util/tests/fd_share_tests.cc72
-rw-r--r--src/lib/util/tests/fd_tests.cc71
-rw-r--r--src/lib/util/tests/file_utilities_unittest.cc86
-rw-r--r--src/lib/util/tests/filename_unittest.cc225
-rw-r--r--src/lib/util/tests/hash_unittest.cc34
-rw-r--r--src/lib/util/tests/hex_unittest.cc114
-rw-r--r--src/lib/util/tests/io_utilities_unittest.cc160
-rw-r--r--src/lib/util/tests/labeled_value_unittest.cc101
-rw-r--r--src/lib/util/tests/memory_segment_common_unittest.cc100
-rw-r--r--src/lib/util/tests/memory_segment_common_unittest.h28
-rw-r--r--src/lib/util/tests/memory_segment_local_unittest.cc117
-rw-r--r--src/lib/util/tests/multi_threading_mgr_unittest.cc636
-rw-r--r--src/lib/util/tests/optional_unittest.cc162
-rw-r--r--src/lib/util/tests/pid_file_unittest.cc206
-rw-r--r--src/lib/util/tests/range_utilities_unittest.cc50
-rw-r--r--src/lib/util/tests/readwrite_mutex_unittest.cc470
-rw-r--r--src/lib/util/tests/run_unittests.cc18
-rw-r--r--src/lib/util/tests/staged_value_unittest.cc106
-rw-r--r--src/lib/util/tests/state_model_unittest.cc916
-rw-r--r--src/lib/util/tests/stopwatch_unittest.cc307
-rw-r--r--src/lib/util/tests/strutil_unittest.cc642
-rw-r--r--src/lib/util/tests/thread_pool_unittest.cc661
-rw-r--r--src/lib/util/tests/time_utilities_unittest.cc155
-rw-r--r--src/lib/util/tests/triplet_unittest.cc125
-rw-r--r--src/lib/util/tests/unlock_guard_unittests.cc236
-rw-r--r--src/lib/util/tests/utf8_unittest.cc50
-rw-r--r--src/lib/util/tests/versioned_csv_file_unittest.cc501
-rw-r--r--src/lib/util/tests/watch_socket_unittests.cc263
-rw-r--r--src/lib/util/tests/watched_thread_unittest.cc218
-rw-r--r--src/lib/util/thread_pool.h527
-rw-r--r--src/lib/util/time_utilities.cc203
-rw-r--r--src/lib/util/time_utilities.h165
-rw-r--r--src/lib/util/triplet.h127
-rw-r--r--src/lib/util/unittests/Makefile.am32
-rw-r--r--src/lib/util/unittests/Makefile.in846
-rw-r--r--src/lib/util/unittests/README5
-rw-r--r--src/lib/util/unittests/check_valgrind.cc33
-rw-r--r--src/lib/util/unittests/check_valgrind.h45
-rw-r--r--src/lib/util/unittests/fork.cc141
-rw-r--r--src/lib/util/unittests/fork.h44
-rw-r--r--src/lib/util/unittests/interprocess_util.cc42
-rw-r--r--src/lib/util/unittests/interprocess_util.h23
-rw-r--r--src/lib/util/unittests/mock_socketsession.h151
-rw-r--r--src/lib/util/unittests/newhook.cc45
-rw-r--r--src/lib/util/unittests/newhook.h74
-rw-r--r--src/lib/util/unittests/resource.cc29
-rw-r--r--src/lib/util/unittests/resource.h31
-rw-r--r--src/lib/util/unittests/run_all.cc89
-rw-r--r--src/lib/util/unittests/run_all.h44
-rw-r--r--src/lib/util/unittests/testdata.cc55
-rw-r--r--src/lib/util/unittests/testdata.h46
-rw-r--r--src/lib/util/unittests/textdata.h95
-rw-r--r--src/lib/util/unittests/wiredata.cc46
-rw-r--r--src/lib/util/unittests/wiredata.h37
-rw-r--r--src/lib/util/unlock_guard.h47
-rw-r--r--src/lib/util/util.dox93
-rw-r--r--src/lib/util/versioned_csv_file.cc247
-rw-r--r--src/lib/util/versioned_csv_file.h317
-rw-r--r--src/lib/util/watch_socket.cc165
-rw-r--r--src/lib/util/watch_socket.h143
-rw-r--r--src/lib/util/watched_thread.cc103
-rw-r--r--src/lib/util/watched_thread.h145
-rw-r--r--src/lib/yang/Makefile.am83
-rw-r--r--src/lib/yang/Makefile.in1067
-rw-r--r--src/lib/yang/adaptor.cc302
-rw-r--r--src/lib/yang/adaptor.h121
-rw-r--r--src/lib/yang/adaptor_config.cc653
-rw-r--r--src/lib/yang/adaptor_config.h286
-rw-r--r--src/lib/yang/adaptor_host.cc64
-rw-r--r--src/lib/yang/adaptor_host.h44
-rw-r--r--src/lib/yang/adaptor_option.cc123
-rw-r--r--src/lib/yang/adaptor_option.h102
-rw-r--r--src/lib/yang/adaptor_pool.cc83
-rw-r--r--src/lib/yang/adaptor_pool.h93
-rw-r--r--src/lib/yang/adaptor_subnet.cc68
-rw-r--r--src/lib/yang/adaptor_subnet.h60
-rw-r--r--src/lib/yang/netconf_error.h39
-rw-r--r--src/lib/yang/pretests/Makefile.am25
-rw-r--r--src/lib/yang/pretests/Makefile.in853
-rw-r--r--src/lib/yang/pretests/sysrepo_setup_tests.cc119
-rw-r--r--src/lib/yang/tests/Makefile.am70
-rw-r--r--src/lib/yang/tests/Makefile.in1315
-rw-r--r--src/lib/yang/tests/adaptor_config_unittests.cc154
-rw-r--r--src/lib/yang/tests/adaptor_host_unittests.cc119
-rw-r--r--src/lib/yang/tests/adaptor_option_unittests.cc245
-rw-r--r--src/lib/yang/tests/adaptor_pool_unittests.cc276
-rw-r--r--src/lib/yang/tests/adaptor_subnet_unittests.cc214
-rw-r--r--src/lib/yang/tests/adaptor_unittests.cc398
-rw-r--r--src/lib/yang/tests/config_unittests.cc392
-rw-r--r--src/lib/yang/tests/json_configs.h176
-rw-r--r--src/lib/yang/tests/run_unittests.cc21
-rw-r--r--src/lib/yang/tests/sysrepo_setup.h96
-rw-r--r--src/lib/yang/tests/translator_class_unittests.cc123
-rw-r--r--src/lib/yang/tests/translator_control_socket_unittests.cc174
-rw-r--r--src/lib/yang/tests/translator_database_unittests.cc324
-rw-r--r--src/lib/yang/tests/translator_host_unittests.cc192
-rw-r--r--src/lib/yang/tests/translator_logger_unittests.cc156
-rw-r--r--src/lib/yang/tests/translator_option_data_unittests.cc135
-rw-r--r--src/lib/yang/tests/translator_option_def_unittests.cc138
-rw-r--r--src/lib/yang/tests/translator_pd_pool_unittests.cc296
-rw-r--r--src/lib/yang/tests/translator_pool_unittests.cc252
-rw-r--r--src/lib/yang/tests/translator_shared_network_unittests.cc210
-rw-r--r--src/lib/yang/tests/translator_subnet_unittests.cc379
-rw-r--r--src/lib/yang/tests/translator_unittests.cc892
-rw-r--r--src/lib/yang/tests/translator_utils_unittests.cc288
-rw-r--r--src/lib/yang/tests/yang_configs.h655
-rw-r--r--src/lib/yang/testutils/Makefile.am30
-rw-r--r--src/lib/yang/testutils/Makefile.in867
-rw-r--r--src/lib/yang/testutils/translator_test.cc276
-rw-r--r--src/lib/yang/testutils/translator_test.h162
-rw-r--r--src/lib/yang/translator.cc382
-rw-r--r--src/lib/yang/translator.h432
-rw-r--r--src/lib/yang/translator_class.cc219
-rw-r--r--src/lib/yang/translator_class.h203
-rw-r--r--src/lib/yang/translator_config.cc759
-rw-r--r--src/lib/yang/translator_config.h556
-rw-r--r--src/lib/yang/translator_control_socket.cc97
-rw-r--r--src/lib/yang/translator_control_socket.h134
-rw-r--r--src/lib/yang/translator_database.cc213
-rw-r--r--src/lib/yang/translator_database.h243
-rw-r--r--src/lib/yang/translator_host.cc215
-rw-r--r--src/lib/yang/translator_host.h229
-rw-r--r--src/lib/yang/translator_logger.cc212
-rw-r--r--src/lib/yang/translator_logger.h233
-rw-r--r--src/lib/yang/translator_option_data.cc180
-rw-r--r--src/lib/yang/translator_option_data.h195
-rw-r--r--src/lib/yang/translator_option_def.cc182
-rw-r--r--src/lib/yang/translator_option_def.h198
-rw-r--r--src/lib/yang/translator_pd_pool.cc320
-rw-r--r--src/lib/yang/translator_pd_pool.h256
-rw-r--r--src/lib/yang/translator_pool.cc327
-rw-r--r--src/lib/yang/translator_pool.h277
-rw-r--r--src/lib/yang/translator_shared_network.cc306
-rw-r--r--src/lib/yang/translator_shared_network.h281
-rw-r--r--src/lib/yang/translator_subnet.cc430
-rw-r--r--src/lib/yang/translator_subnet.h411
-rw-r--r--src/lib/yang/yang.dox276
-rw-r--r--src/lib/yang/yang_models.h37
-rw-r--r--src/lib/yang/yang_revisions.h35
-rw-r--r--src/lib/yang/yang_revisions.h.skel27
2158 files changed, 621563 insertions, 0 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
new file mode 100644
index 0000000..360210a
--- /dev/null
+++ b/src/lib/Makefile.am
@@ -0,0 +1,18 @@
+# The following build order must be maintained.
+SUBDIRS = exceptions util log cryptolink dns asiolink cc testutils database
+
+if HAVE_MYSQL
+SUBDIRS += mysql
+endif
+
+if HAVE_PGSQL
+SUBDIRS += pgsql
+endif
+
+SUBDIRS += config_backend hooks dhcp tcp http config stats
+
+if HAVE_NETCONF
+SUBDIRS += yang
+endif
+
+SUBDIRS += asiodns dhcp_ddns eval process dhcpsrv d2srv
diff --git a/src/lib/Makefile.in b/src/lib/Makefile.in
new file mode 100644
index 0000000..1f53b76
--- /dev/null
+++ b/src/lib/Makefile.in
@@ -0,0 +1,739 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_MYSQL_TRUE@am__append_1 = mysql
+@HAVE_PGSQL_TRUE@am__append_2 = pgsql
+@HAVE_NETCONF_TRUE@am__append_3 = yang
+subdir = src/lib
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = exceptions util log cryptolink dns asiolink cc \
+ testutils database mysql pgsql config_backend hooks dhcp tcp \
+ http config stats yang asiodns dhcp_ddns eval process dhcpsrv \
+ d2srv
+am__DIST_COMMON = $(srcdir)/Makefile.in
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+
+# The following build order must be maintained.
+SUBDIRS = exceptions util log cryptolink dns asiolink cc testutils \
+ database $(am__append_1) $(am__append_2) config_backend hooks \
+ dhcp tcp http config stats $(am__append_3) asiodns dhcp_ddns \
+ eval process dhcpsrv d2srv
+all: all-recursive
+
+.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/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/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
+
+# 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: check-recursive
+all-am: Makefile
+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:
+
+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 mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am 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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean 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-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/asiodns/Makefile.am b/src/lib/asiodns/Makefile.am
new file mode 100644
index 0000000..2897989
--- /dev/null
+++ b/src/lib/asiodns/Makefile.am
@@ -0,0 +1,72 @@
+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
+
+lib_LTLIBRARIES = libkea-asiodns.la
+
+libkea_asiodns_la_LDFLAGS = -no-undefined -version-info 35:0:0
+
+libkea_asiodns_la_SOURCES = io_fetch.cc io_fetch.h
+libkea_asiodns_la_SOURCES += logger.h logger.cc
+libkea_asiodns_la_SOURCES += asiodns_messages.cc asiodns_messages.h
+
+EXTRA_DIST = asiodns_messages.mes
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+libkea_asiodns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_asiodns_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_asiodns_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_asiodns_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_asiodns_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_asiodns_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_asiodns_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_asiodns_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_asiodns_la_LIBADD += $(BOOST_LIBS)
+libkea_asiodns_la_LIBADD += $(LOG4CPLUS_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f asiodns_messages.h asiodns_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: asiodns_messages.h asiodns_messages.cc
+ @echo Message files regenerated
+
+asiodns_messages.h asiodns_messages.cc: asiodns_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/asiodns/asiodns_messages.mes
+
+else
+
+messages asiodns_messages.h asiodns_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_asiodns_includedir = $(pkgincludedir)/asiodns
+libkea_asiodns_include_HEADERS = \
+ asiodns_messages.h \
+ io_fetch.h \
+ logger.h
+
diff --git a/src/lib/asiodns/Makefile.in b/src/lib/asiodns/Makefile.in
new file mode 100644
index 0000000..dda9b26
--- /dev/null
+++ b/src/lib/asiodns/Makefile.in
@@ -0,0 +1,1022 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/asiodns
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_asiodns_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)$(libkea_asiodns_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_asiodns_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_asiodns_la_OBJECTS = libkea_asiodns_la-io_fetch.lo \
+ libkea_asiodns_la-logger.lo \
+ libkea_asiodns_la-asiodns_messages.lo
+libkea_asiodns_la_OBJECTS = $(am_libkea_asiodns_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_asiodns_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_asiodns_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_asiodns_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_asiodns_la-asiodns_messages.Plo \
+ ./$(DEPDIR)/libkea_asiodns_la-io_fetch.Plo \
+ ./$(DEPDIR)/libkea_asiodns_la-logger.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_asiodns_la_SOURCES)
+DIST_SOURCES = $(libkea_asiodns_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 = $(libkea_asiodns_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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-asiodns.la
+libkea_asiodns_la_LDFLAGS = -no-undefined -version-info 35:0:0
+libkea_asiodns_la_SOURCES = io_fetch.cc io_fetch.h logger.h logger.cc \
+ asiodns_messages.cc asiodns_messages.h
+EXTRA_DIST = asiodns_messages.mes
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+libkea_asiodns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_asiodns_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_asiodns_la_LIBADD = \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(BOOST_LIBS) $(LOG4CPLUS_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_asiodns_includedir = $(pkgincludedir)/asiodns
+libkea_asiodns_include_HEADERS = \
+ asiodns_messages.h \
+ io_fetch.h \
+ logger.h
+
+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/asiodns/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/asiodns/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-asiodns.la: $(libkea_asiodns_la_OBJECTS) $(libkea_asiodns_la_DEPENDENCIES) $(EXTRA_libkea_asiodns_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_asiodns_la_LINK) -rpath $(libdir) $(libkea_asiodns_la_OBJECTS) $(libkea_asiodns_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiodns_la-asiodns_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiodns_la-io_fetch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiodns_la-logger.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)$(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 $@ $<
+
+libkea_asiodns_la-io_fetch.lo: io_fetch.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiodns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiodns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiodns_la-io_fetch.lo -MD -MP -MF $(DEPDIR)/libkea_asiodns_la-io_fetch.Tpo -c -o libkea_asiodns_la-io_fetch.lo `test -f 'io_fetch.cc' || echo '$(srcdir)/'`io_fetch.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiodns_la-io_fetch.Tpo $(DEPDIR)/libkea_asiodns_la-io_fetch.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_fetch.cc' object='libkea_asiodns_la-io_fetch.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_asiodns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiodns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiodns_la-io_fetch.lo `test -f 'io_fetch.cc' || echo '$(srcdir)/'`io_fetch.cc
+
+libkea_asiodns_la-logger.lo: logger.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiodns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiodns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiodns_la-logger.lo -MD -MP -MF $(DEPDIR)/libkea_asiodns_la-logger.Tpo -c -o libkea_asiodns_la-logger.lo `test -f 'logger.cc' || echo '$(srcdir)/'`logger.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiodns_la-logger.Tpo $(DEPDIR)/libkea_asiodns_la-logger.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger.cc' object='libkea_asiodns_la-logger.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_asiodns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiodns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiodns_la-logger.lo `test -f 'logger.cc' || echo '$(srcdir)/'`logger.cc
+
+libkea_asiodns_la-asiodns_messages.lo: asiodns_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiodns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiodns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiodns_la-asiodns_messages.lo -MD -MP -MF $(DEPDIR)/libkea_asiodns_la-asiodns_messages.Tpo -c -o libkea_asiodns_la-asiodns_messages.lo `test -f 'asiodns_messages.cc' || echo '$(srcdir)/'`asiodns_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiodns_la-asiodns_messages.Tpo $(DEPDIR)/libkea_asiodns_la-asiodns_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='asiodns_messages.cc' object='libkea_asiodns_la-asiodns_messages.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_asiodns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiodns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiodns_la-asiodns_messages.lo `test -f 'asiodns_messages.cc' || echo '$(srcdir)/'`asiodns_messages.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_asiodns_includeHEADERS: $(libkea_asiodns_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_asiodns_include_HEADERS)'; test -n "$(libkea_asiodns_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_asiodns_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_asiodns_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)$(libkea_asiodns_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_asiodns_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_asiodns_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_asiodns_include_HEADERS)'; test -n "$(libkea_asiodns_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_asiodns_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_asiodns_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_asiodns_la-asiodns_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiodns_la-io_fetch.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiodns_la-logger.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-libkea_asiodns_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_asiodns_la-asiodns_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiodns_la-io_fetch.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiodns_la-logger.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_asiodns_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_asiodns_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_asiodns_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f asiodns_messages.h asiodns_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: asiodns_messages.h asiodns_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@asiodns_messages.h asiodns_messages.cc: asiodns_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/asiodns/asiodns_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages asiodns_messages.h asiodns_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/asiodns/README b/src/lib/asiodns/README
new file mode 100644
index 0000000..e785845
--- /dev/null
+++ b/src/lib/asiodns/README
@@ -0,0 +1,154 @@
+The asiodns library is intended to provide an abstraction layer between
+BIND10 modules and asiolink library.
+
+These DNS server and client routines are written using the "stackless
+coroutine" pattern invented by Chris Kohlhoff and described at
+http://blog.think-async.com/2010/03/potted-guide-to-stackless-coroutines.html.
+This is intended to simplify development a bit, since it allows the
+routines to be written in a straightforward step-step-step fashion rather
+than as a complex chain of separate handler functions.
+
+Coroutine objects (i.e., UDPServer, TCPServer and IOFetch) are objects
+with reentrant operator() members. When an instance of one of these
+classes is called as a function, it resumes at the position where it left
+off. Thus, a UDPServer can issue an asynchronous I/O call and specify
+itself as the handler object; when the call completes, the UDPServer
+carries on at the same position. As a result, the code can look as
+if it were using synchronous, not asynchronous, I/O, providing some of
+the benefit of threading but with minimal switching overhead.
+
+So, in simplified form, the behavior of a DNS Server is:
+
+ REENTER:
+ while true:
+ YIELD packet = read_packet
+ FORK
+ if not parent:
+ break
+
+ YIELD answer = DNSLookup(packet, this)
+ response = DNSAnswer(answer)
+ YIELD send(response)
+
+At each "YIELD" point, the coroutine initiates an asynchronous operation,
+then pauses and turns over control to some other task on the ASIO service
+queue. When the operation completes, the coroutine resumes.
+
+DNSLookup and DNSAnswer define callback methods
+used by a DNS Server to communicate with the module that called it.
+They are abstract-only classes whose concrete implementations
+are supplied by the calling module.
+
+The DNSLookup callback always runs asynchronously. Concrete
+implementations must be sure to call the server's "resume" method when
+it is finished.
+
+In an authoritative server, the DNSLookup implementation would examine
+the query, look up the answer, then call "resume". (See the diagram
+in doc/auth_process.jpg.)
+
+In a recursive server, the DNSLookup implementation would initiate a
+DNSQuery, which in turn would be responsible for calling the server's
+"resume" method. (See the diagram in doc/recursive_process.jpg.)
+
+A DNSQuery object is intended to handle resolution of a query over
+the network when the local authoritative data sources or cache are not
+sufficient. The plan is that it will make use of subsidiary DNSFetch
+calls to get data from particular authoritative servers, and when it has
+gotten a complete answer, it calls "resume".
+
+In current form, however, DNSQuery is much simpler; it forwards queries
+to a single upstream resolver and passes the answers back to the client.
+It is constructed with the address of the forward server. Queries are
+initiated with the question to ask the forward server, a buffer into
+which to write the answer, and a pointer to the coroutine to be resumed
+when the answer has arrived. In simplified form, the DNSQuery routine is:
+
+ REENTER:
+ render the question into a wire-format query packet
+ YIELD send(query)
+ YIELD response = read_packet
+ server->resume
+
+Currently, DNSQuery is only implemented for UDP queries. In future work
+it will be necessary to write code to fall back to TCP when circumstances
+require it.
+
+
+Upstream Fetches
+================
+Upstream fetches (queries by the resolver on behalf of a client) are made
+using a slightly-modified version of the pattern described above.
+
+Sockets
+-------
+First, it will be useful to understand the class hierarchy used in the
+fetch logic:
+
+ IOSocket
+ |
+ IOAsioSocket
+ |
+ +-----+-----+
+ | |
+UDPSocket TCPSocket
+
+IOSocket is a wrapper class for a socket and is used by the authoritative
+server code. It is an abstract base class, providing little more that the ability to hold the socket and to return the protocol in use.
+
+Built on this is IOAsioSocket, which adds the open, close, asyncSend and
+asyncReceive methods. This is a template class, which takes as template
+argument the class of the object that will be used as the callback when the
+asynchronous operation completes. This object can be of any type, but must
+include an operator() method with the signature:
+
+ operator()(boost::system::error_code ec, size_t length)
+
+... the two arguments being the status of the completed I/O operation and
+the number of bytes transferred. (In the case of the open method, the second
+argument will be zero.)
+
+Finally, the TCPSocket and UDPSocket classes provide the body of the
+asynchronous operations.
+
+Fetch Sequence
+--------------
+The fetch is implemented by the IOFetch class, which takes as argument the
+protocol to use. The sequence is:
+
+ REENTER:
+ render the question into a wire-format query packet
+ open() // Open socket and optionally connect
+ if (! synchronous) {
+ YIELD;
+ }
+ YIELD asyncSend(query) // Send query
+ do {
+ YIELD asyncReceive(response) // Read response
+ } while (! complete(response))
+ close() // Drop connection and close socket
+ server->resume
+
+The open() method opens a socket for use. On TCP, it also makes a
+connection to the remote end. So under UDP the operation will complete
+immediately, but under TCP it could take a long time. One solution would be
+for the open operation to post an event to the I/O queue; then both cases
+could be regarded as being equivalent, with the completion being signalled
+by the posting of the completion event. However UDP is the most common case
+and that would involve extra overhead. So the open() returns a status
+indicating whether the operation completed asynchronously. If it did, the
+code yields back to the coroutine; if not the yield is bypassed.
+
+The asynchronous send is straightforward, invoking the underlying ASIO
+function. (Note that the address/port is supplied to both the open() and
+asyncSend() methods - it is used by the TCPSocket in open() and by the
+UDPSocket in asyncSend().)
+
+The asyncReceive() method issues an asynchronous read and waits for completion.
+The fetch object keeps track of the amount of data received so far and when
+the receive completes it calls a method on the socket to determine if the
+entire message has been received. (This will always be the case for UDP. On
+TCP though, the message is preceded by a count field as several reads may be
+required to read all the data.) The fetch loops until all the data is read.
+
+Finally, the socket is closed and the server called to resume operation.
diff --git a/src/lib/asiodns/asiodns_messages.cc b/src/lib/asiodns/asiodns_messages.cc
new file mode 100644
index 0000000..7900294
--- /dev/null
+++ b/src/lib/asiodns/asiodns_messages.cc
@@ -0,0 +1,73 @@
+// File created from ../../../src/lib/asiodns/asiodns_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace asiodns {
+
+extern const isc::log::MessageID ASIODNS_FD_ADD_TCP = "ASIODNS_FD_ADD_TCP";
+extern const isc::log::MessageID ASIODNS_FD_ADD_UDP = "ASIODNS_FD_ADD_UDP";
+extern const isc::log::MessageID ASIODNS_FETCH_COMPLETED = "ASIODNS_FETCH_COMPLETED";
+extern const isc::log::MessageID ASIODNS_FETCH_STOPPED = "ASIODNS_FETCH_STOPPED";
+extern const isc::log::MessageID ASIODNS_OPEN_SOCKET = "ASIODNS_OPEN_SOCKET";
+extern const isc::log::MessageID ASIODNS_READ_DATA = "ASIODNS_READ_DATA";
+extern const isc::log::MessageID ASIODNS_READ_TIMEOUT = "ASIODNS_READ_TIMEOUT";
+extern const isc::log::MessageID ASIODNS_SEND_DATA = "ASIODNS_SEND_DATA";
+extern const isc::log::MessageID ASIODNS_SYNC_UDP_CLOSE_FAIL = "ASIODNS_SYNC_UDP_CLOSE_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_ACCEPT_FAIL = "ASIODNS_TCP_ACCEPT_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_CLEANUP_CLOSE_FAIL = "ASIODNS_TCP_CLEANUP_CLOSE_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL = "ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_CLOSE_FAIL = "ASIODNS_TCP_CLOSE_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_CLOSE_NORESP_FAIL = "ASIODNS_TCP_CLOSE_NORESP_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_GETREMOTE_FAIL = "ASIODNS_TCP_GETREMOTE_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_READDATA_FAIL = "ASIODNS_TCP_READDATA_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_READLEN_FAIL = "ASIODNS_TCP_READLEN_FAIL";
+extern const isc::log::MessageID ASIODNS_TCP_WRITE_FAIL = "ASIODNS_TCP_WRITE_FAIL";
+extern const isc::log::MessageID ASIODNS_UDP_ASYNC_SEND_FAIL = "ASIODNS_UDP_ASYNC_SEND_FAIL";
+extern const isc::log::MessageID ASIODNS_UDP_CLOSE_FAIL = "ASIODNS_UDP_CLOSE_FAIL";
+extern const isc::log::MessageID ASIODNS_UDP_RECEIVE_FAIL = "ASIODNS_UDP_RECEIVE_FAIL";
+extern const isc::log::MessageID ASIODNS_UDP_SYNC_RECEIVE_FAIL = "ASIODNS_UDP_SYNC_RECEIVE_FAIL";
+extern const isc::log::MessageID ASIODNS_UDP_SYNC_SEND_FAIL = "ASIODNS_UDP_SYNC_SEND_FAIL";
+extern const isc::log::MessageID ASIODNS_UNKNOWN_ORIGIN = "ASIODNS_UNKNOWN_ORIGIN";
+extern const isc::log::MessageID ASIODNS_UNKNOWN_RESULT = "ASIODNS_UNKNOWN_RESULT";
+
+} // namespace asiodns
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "ASIODNS_FD_ADD_TCP", "adding a new TCP server by opened fd %1",
+ "ASIODNS_FD_ADD_UDP", "adding a new UDP server by opened fd %1",
+ "ASIODNS_FETCH_COMPLETED", "upstream fetch to %1(%2) has now completed",
+ "ASIODNS_FETCH_STOPPED", "upstream fetch to %1(%2) has been stopped",
+ "ASIODNS_OPEN_SOCKET", "error %1 opening %2 socket to %3(%4)",
+ "ASIODNS_READ_DATA", "error %1 reading %2 data from %3(%4)",
+ "ASIODNS_READ_TIMEOUT", "receive timeout while waiting for data from %1(%2)",
+ "ASIODNS_SEND_DATA", "error %1 sending data using %2 to %3(%4)",
+ "ASIODNS_SYNC_UDP_CLOSE_FAIL", "failed to close a DNS/UDP socket: %1",
+ "ASIODNS_TCP_ACCEPT_FAIL", "failed to accept TCP DNS connection: %1",
+ "ASIODNS_TCP_CLEANUP_CLOSE_FAIL", "failed to close a DNS/TCP socket on port cleanup: %1",
+ "ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL", "failed to close listening TCP socket: %1",
+ "ASIODNS_TCP_CLOSE_FAIL", "failed to close DNS/TCP socket with a client: %1",
+ "ASIODNS_TCP_CLOSE_NORESP_FAIL", "failed to close DNS/TCP socket with a client: %1",
+ "ASIODNS_TCP_GETREMOTE_FAIL", "failed to get remote address of a DNS TCP connection: %1",
+ "ASIODNS_TCP_READDATA_FAIL", "failed to get DNS data on a TCP socket: %1",
+ "ASIODNS_TCP_READLEN_FAIL", "failed to get DNS data length on a TCP socket: %1",
+ "ASIODNS_TCP_WRITE_FAIL", "failed to send DNS message over a TCP socket: %1",
+ "ASIODNS_UDP_ASYNC_SEND_FAIL", "Error sending UDP packet to %1: %2",
+ "ASIODNS_UDP_CLOSE_FAIL", "failed to close a DNS/UDP socket: %1",
+ "ASIODNS_UDP_RECEIVE_FAIL", "failed to receive UDP DNS packet: %1",
+ "ASIODNS_UDP_SYNC_RECEIVE_FAIL", "failed to receive UDP DNS packet: %1",
+ "ASIODNS_UDP_SYNC_SEND_FAIL", "Error sending UDP packet to %1: %2",
+ "ASIODNS_UNKNOWN_ORIGIN", "unknown origin for ASIO error code %1 (protocol: %2, address %3)",
+ "ASIODNS_UNKNOWN_RESULT", "unknown result (%1) when IOFetch::stop() was executed for I/O to %2(%3)",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/asiodns/asiodns_messages.h b/src/lib/asiodns/asiodns_messages.h
new file mode 100644
index 0000000..b3dab48
--- /dev/null
+++ b/src/lib/asiodns/asiodns_messages.h
@@ -0,0 +1,40 @@
+// File created from ../../../src/lib/asiodns/asiodns_messages.mes
+
+#ifndef ASIODNS_MESSAGES_H
+#define ASIODNS_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace asiodns {
+
+extern const isc::log::MessageID ASIODNS_FD_ADD_TCP;
+extern const isc::log::MessageID ASIODNS_FD_ADD_UDP;
+extern const isc::log::MessageID ASIODNS_FETCH_COMPLETED;
+extern const isc::log::MessageID ASIODNS_FETCH_STOPPED;
+extern const isc::log::MessageID ASIODNS_OPEN_SOCKET;
+extern const isc::log::MessageID ASIODNS_READ_DATA;
+extern const isc::log::MessageID ASIODNS_READ_TIMEOUT;
+extern const isc::log::MessageID ASIODNS_SEND_DATA;
+extern const isc::log::MessageID ASIODNS_SYNC_UDP_CLOSE_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_ACCEPT_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_CLEANUP_CLOSE_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_CLOSE_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_CLOSE_NORESP_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_GETREMOTE_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_READDATA_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_READLEN_FAIL;
+extern const isc::log::MessageID ASIODNS_TCP_WRITE_FAIL;
+extern const isc::log::MessageID ASIODNS_UDP_ASYNC_SEND_FAIL;
+extern const isc::log::MessageID ASIODNS_UDP_CLOSE_FAIL;
+extern const isc::log::MessageID ASIODNS_UDP_RECEIVE_FAIL;
+extern const isc::log::MessageID ASIODNS_UDP_SYNC_RECEIVE_FAIL;
+extern const isc::log::MessageID ASIODNS_UDP_SYNC_SEND_FAIL;
+extern const isc::log::MessageID ASIODNS_UNKNOWN_ORIGIN;
+extern const isc::log::MessageID ASIODNS_UNKNOWN_RESULT;
+
+} // namespace asiodns
+} // namespace isc
+
+#endif // ASIODNS_MESSAGES_H
diff --git a/src/lib/asiodns/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
new file mode 100644
index 0000000..1f7f35e
--- /dev/null
+++ b/src/lib/asiodns/asiodns_messages.mes
@@ -0,0 +1,167 @@
+# 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/.
+
+$NAMESPACE isc::asiodns
+
+% ASIODNS_FD_ADD_TCP adding a new TCP server by opened fd %1
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+
+% ASIODNS_FD_ADD_UDP adding a new UDP server by opened fd %1
+A debug message informing about installing a file descriptor as a server.
+The file descriptor number is noted.
+
+% ASIODNS_FETCH_COMPLETED upstream fetch to %1(%2) has now completed
+A debug message, this records that the upstream fetch (a query made by the
+resolver on behalf of its client) to the specified address has completed.
+
+% ASIODNS_FETCH_STOPPED upstream fetch to %1(%2) has been stopped
+An external component has requested the halting of an upstream fetch. This
+is an allowed operation, and the message should only appear if debug is
+enabled.
+
+% ASIODNS_OPEN_SOCKET error %1 opening %2 socket to %3(%4)
+The asynchronous I/O code encountered an error when trying to open a socket
+of the specified protocol in order to send a message to the target address.
+The number of the system error that caused the problem is given in the
+message.
+
+% ASIODNS_READ_DATA error %1 reading %2 data from %3(%4)
+The asynchronous I/O code encountered an error when trying to read data from
+the specified address on the given protocol. The number of the system
+error that caused the problem is given in the message.
+
+% ASIODNS_READ_TIMEOUT receive timeout while waiting for data from %1(%2)
+An upstream fetch from the specified address timed out. This may happen for
+any number of reasons and is most probably a problem at the remote server
+or a problem on the network. The message will only appear if debug is
+enabled.
+
+% ASIODNS_SEND_DATA error %1 sending data using %2 to %3(%4)
+The asynchronous I/O code encountered an error when trying to send data to
+the specified address on the given protocol. The number of the system
+error that caused the problem is given in the message.
+
+% ASIODNS_SYNC_UDP_CLOSE_FAIL failed to close a DNS/UDP socket: %1
+This is the same to ASIODNS_UDP_CLOSE_FAIL but happens on the
+"synchronous UDP server", mainly used for the authoritative DNS server
+daemon.
+
+% ASIODNS_TCP_ACCEPT_FAIL failed to accept TCP DNS connection: %1
+Accepting a TCP connection from a DNS client failed due to an error
+that could happen but should be rare. The reason for the error is
+included in the log message. The server still keeps accepting new
+connections, so unless it happens often it's probably okay to ignore
+this error. If the shown error indicates something like "too many
+open files", it's probably because the run time environment is too
+restrictive on this limitation, so consider adjusting the limit using
+a tool such as ulimit. If you see other types of errors too often,
+there may be something overlooked; please file a bug report in that case.
+
+% ASIODNS_TCP_CLEANUP_CLOSE_FAIL failed to close a DNS/TCP socket on port cleanup: %1
+A TCP DNS server tried to close a TCP socket (one created on accepting
+a new connection or is already unused) as a step of cleaning up the
+corresponding listening port, but it failed to do that. This is
+generally an unexpected event and so is logged as an error.
+See also the description of ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL.
+
+% ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL failed to close listening TCP socket: %1
+A TCP DNS server tried to close a listening TCP socket (for accepting
+new connections) as a step of cleaning up the corresponding listening
+port (e.g., on server shutdown or updating port configuration), but it
+failed to do that. This is generally an unexpected event and so is
+logged as an error. See ASIODNS_TCP_CLOSE_FAIL on the implication of
+related system resources.
+
+% ASIODNS_TCP_CLOSE_FAIL failed to close DNS/TCP socket with a client: %1
+A TCP DNS server tried to close a TCP socket used to communicate with
+a client, but it failed to do that. While closing a socket should
+normally be an error-free operation, there have been known cases where
+this happened with a "connection reset by peer" error. This might be
+because of some odd client behavior, such as sending a TCP RST after
+establishing the connection and before the server closes the socket,
+but how exactly this could happen seems to be system dependent (i.e,
+it's not part of the standard socket API), so it's difficult to
+provide a general explanation. In any case, it is believed that an
+error on closing a socket doesn't mean leaking system resources (the
+kernel should clean up any internal resource related to the socket,
+just reporting an error detected in the close call), but, again, it
+seems to be system dependent. This message is logged at a debug level
+as it's known to happen and could be triggered by a remote node and it
+would be better to not be too verbose, but you might want to increase
+the log level and make sure there's no resource leak or other system
+level troubles when it's logged.
+
+% ASIODNS_TCP_CLOSE_NORESP_FAIL failed to close DNS/TCP socket with a client: %1
+A TCP DNS server tried to close a TCP socket used to communicate with
+a client without returning an answer (which normally happens for zone
+transfer requests), but it failed to do that. See ASIODNS_TCP_CLOSE_FAIL
+for more details.
+
+% ASIODNS_TCP_GETREMOTE_FAIL failed to get remote address of a DNS TCP connection: %1
+A TCP DNS server tried to get the address and port of a remote client
+on a connected socket but failed. It's expected to be rare but can
+still happen. See also ASIODNS_TCP_READLEN_FAIL.
+
+% ASIODNS_TCP_READDATA_FAIL failed to get DNS data on a TCP socket: %1
+A TCP DNS server tried to read a DNS message (that follows a 2-byte
+length field) but failed. It's expected to be rare but can still happen.
+See also ASIODNS_TCP_READLEN_FAIL.
+
+% ASIODNS_TCP_READLEN_FAIL failed to get DNS data length on a TCP socket: %1
+A TCP DNS server tried to get the length field of a DNS message (the first
+2 bytes of a new chunk of data) but failed. This is generally expected to
+be rare but can still happen, e.g, due to an unexpected reset of the
+connection. A specific reason for the failure is included in the log
+message.
+
+% ASIODNS_TCP_WRITE_FAIL failed to send DNS message over a TCP socket: %1
+A TCP DNS server tried to send a DNS message to a remote client but
+failed. It's expected to be rare but can still happen. See also
+ASIODNS_TCP_READLEN_FAIL.
+
+% ASIODNS_UDP_ASYNC_SEND_FAIL Error sending UDP packet to %1: %2
+The low-level ASIO library reported an error when trying to send a UDP
+packet in asynchronous UDP mode. This can be any error reported by
+send_to(), and can indicate problems such as too high a load on the network,
+or a problem in the underlying library or system.
+This packet is dropped and will not be sent, but service should resume
+normally.
+If you see a single occurrence of this message, it probably does not
+indicate any significant problem, but if it is logged often, it is probably
+a good idea to inspect your network traffic.
+
+% ASIODNS_UDP_CLOSE_FAIL failed to close a DNS/UDP socket: %1
+A UDP DNS server tried to close its UDP socket, but failed to do that.
+This is generally an unexpected event and so is logged as an error.
+
+% ASIODNS_UDP_RECEIVE_FAIL failed to receive UDP DNS packet: %1
+Receiving a UDP packet from a DNS client failed due to an error that
+could happen but should be very rare. The server still keeps
+receiving UDP packets on this socket. The reason for the error is
+included in the log message. This log message is basically not
+expected to appear at all in practice; if it does, there may be some
+system level failure and other system logs may have to be checked.
+
+% ASIODNS_UDP_SYNC_RECEIVE_FAIL failed to receive UDP DNS packet: %1
+This is the same to ASIODNS_UDP_RECEIVE_FAIL but happens on the
+"synchronous UDP server", mainly used for the authoritative DNS server
+daemon.
+
+% ASIODNS_UDP_SYNC_SEND_FAIL Error sending UDP packet to %1: %2
+The low-level ASIO library reported an error when trying to send a UDP
+packet in synchronous UDP mode. See ASIODNS_UDP_ASYNC_SEND_FAIL for
+more information.
+
+% ASIODNS_UNKNOWN_ORIGIN unknown origin for ASIO error code %1 (protocol: %2, address %3)
+An internal consistency check on the origin of a message from the
+asynchronous I/O module failed. This may indicate an internal error;
+please submit a bug report.
+
+% ASIODNS_UNKNOWN_RESULT unknown result (%1) when IOFetch::stop() was executed for I/O to %2(%3)
+An internal error indicating that the termination method of the resolver's
+upstream fetch class was called with an unknown result code (which is
+given in the message). Please submit a bug report.
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
new file mode 100644
index 0000000..980a600
--- /dev/null
+++ b/src/lib/asiodns/io_fetch.cc
@@ -0,0 +1,402 @@
+// Copyright (C) 2011-2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <asiodns/io_fetch.h>
+#include <asiodns/logger.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/qid_gen.h>
+#include <dns/rcode.h>
+#include <util/buffer.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <functional>
+#include <unistd.h> // for some IPC/network system calls
+#include <netinet/in.h>
+#include <stdint.h>
+#include <sys/socket.h>
+
+using namespace boost::asio;
+using namespace isc::asiolink;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::log;
+using namespace std;
+
+namespace isc {
+namespace asiodns {
+
+// Log debug verbosity
+
+const int DBG_IMPORTANT = DBGLVL_TRACE_BASIC;
+const int DBG_COMMON = DBGLVL_TRACE_DETAIL;
+const int DBG_ALL = DBGLVL_TRACE_DETAIL + 20;
+
+/// \brief IOFetch Data
+///
+/// The data for IOFetch is held in a separate struct pointed to by a shared_ptr
+/// object. This is because the IOFetch object will be copied often (it is used
+/// as a coroutine and passed as callback to many async_*() functions) and we
+/// want keep the same data). Organising the data in this way keeps copying to
+/// a minimum.
+struct IOFetchData {
+
+ // The first two members are shared pointers to a base class because what is
+ // actually instantiated depends on whether the fetch is over UDP or TCP,
+ // which is not known until construction of the IOFetch. Use of a shared
+ // pointer here is merely to ensure deletion when the data object is deleted.
+ boost::scoped_ptr<IOAsioSocket<IOFetch> > socket;
+ ///< Socket to use for I/O
+ boost::scoped_ptr<IOEndpoint> remote_snd;///< Where the fetch is sent
+ boost::scoped_ptr<IOEndpoint> remote_rcv;///< Where the response came from
+ OutputBufferPtr msgbuf; ///< Wire buffer for question
+ OutputBufferPtr received; ///< Received data put here
+ IOFetch::Callback* callback; ///< Called on I/O Completion
+ boost::asio::deadline_timer timer; ///< Timer to measure timeouts
+ IOFetch::Protocol protocol; ///< Protocol being used
+ size_t cumulative; ///< Cumulative received amount
+ size_t expected; ///< Expected amount of data
+ size_t offset; ///< Offset to receive data
+ bool stopped; ///< Have we stopped running?
+ int timeout; ///< Timeout in ms
+ bool packet; ///< true if packet was supplied
+
+ // In case we need to log an error, the origin of the last asynchronous
+ // I/O is recorded. To save time and simplify the code, this is recorded
+ // as the ID of the error message that would be generated if the I/O failed.
+ // This means that we must make sure that all possible "origins" take the
+ // same arguments in their message in the same order.
+ isc::log::MessageID origin; ///< Origin of last asynchronous I/O
+ uint8_t staging[IOFetch::STAGING_LENGTH];
+ ///< Temporary array for received data
+ isc::dns::qid_t qid; ///< The QID set in the query
+
+ /// \brief Constructor
+ ///
+ /// Just fills in the data members of the IOFetchData structure
+ ///
+ /// \param proto Either IOFetch::TCP or IOFetch::UDP.
+ /// \param service I/O Service object to handle the asynchronous
+ /// operations.
+ /// \param address IP address of upstream server
+ /// \param port Port to use for the query
+ /// \param buff Output buffer into which the response (in wire format)
+ /// is written (if a response is received).
+ /// \param cb Callback object containing the callback to be called
+ /// when we terminate. The caller is responsible for managing this
+ /// object and deleting it if necessary.
+ /// \param wait Timeout for the fetch (in ms).
+ ///
+ /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554)
+ IOFetchData(IOFetch::Protocol proto, IOService& service,
+ const IOAddress& address, uint16_t port, OutputBufferPtr& buff,
+ IOFetch::Callback* cb, int wait) :
+ socket((proto == IOFetch::UDP) ?
+ static_cast<IOAsioSocket<IOFetch>*>(
+ new UDPSocket<IOFetch>(service)) :
+ static_cast<IOAsioSocket<IOFetch>*>(
+ new TCPSocket<IOFetch>(service))
+ ),
+ remote_snd((proto == IOFetch::UDP) ?
+ static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
+ static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
+ ),
+ remote_rcv((proto == IOFetch::UDP) ?
+ static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
+ static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
+ ),
+ msgbuf(new OutputBuffer(512)),
+ received(buff),
+ callback(cb),
+ timer(service.get_io_service()),
+ protocol(proto),
+ cumulative(0),
+ expected(0),
+ offset(0),
+ stopped(false),
+ timeout(wait),
+ packet(false),
+ origin(ASIODNS_UNKNOWN_ORIGIN),
+ staging(),
+ qid(QidGenerator::getInstance().generateQid())
+ {}
+
+ // Checks if the response we received was ok;
+ // - data contains the buffer we read, as well as the address
+ // we sent to and the address we received from.
+ // length is provided by the operator() in IOFetch.
+ // Addresses must match, number of octets read must be at least
+ // 2, and the first two octets must match the qid of the message
+ // we sent.
+ bool responseOK() {
+ return (*remote_snd == *remote_rcv && cumulative >= 2 &&
+ readUint16(received->getData(), received->getLength()) == qid);
+ }
+};
+
+/// IOFetch Constructor - just initialize the private data
+
+IOFetch::IOFetch(Protocol protocol, IOService& service,
+ const isc::dns::Question& question, const IOAddress& address,
+ uint16_t port, OutputBufferPtr& buff, Callback* cb, int wait, bool edns) {
+ MessagePtr query_msg(new Message(Message::RENDER));
+ initIOFetch(query_msg, protocol, service, question, address, port, buff,
+ cb, wait, edns);
+}
+
+IOFetch::IOFetch(Protocol protocol, IOService& service,
+ OutputBufferPtr& outpkt, const IOAddress& address, uint16_t port,
+ OutputBufferPtr& buff, Callback* cb, int wait) :
+ data_(new IOFetchData(protocol, service,
+ address, port, buff, cb, wait)) {
+ data_->msgbuf = outpkt;
+ data_->packet = true;
+}
+
+IOFetch::IOFetch(Protocol protocol, IOService& service,
+ ConstMessagePtr query_message, const IOAddress& address, uint16_t port,
+ OutputBufferPtr& buff, Callback* cb, int wait) {
+ MessagePtr msg(new Message(Message::RENDER));
+
+ msg->setHeaderFlag(Message::HEADERFLAG_RD,
+ query_message->getHeaderFlag(Message::HEADERFLAG_RD));
+ msg->setHeaderFlag(Message::HEADERFLAG_CD,
+ query_message->getHeaderFlag(Message::HEADERFLAG_CD));
+
+ initIOFetch(msg, protocol, service,
+ **(query_message->beginQuestion()),
+ address, port, buff, cb, wait);
+}
+
+void
+IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol,
+ IOService& service,
+ const isc::dns::Question& question,
+ const IOAddress& address, uint16_t port,
+ OutputBufferPtr& buff, Callback* cb, int wait, bool edns) {
+ data_ = boost::shared_ptr<IOFetchData>(new IOFetchData(
+ protocol, service, address, port, buff, cb, wait));
+
+ query_msg->setQid(data_->qid);
+ query_msg->setOpcode(Opcode::QUERY());
+ query_msg->setRcode(Rcode::NOERROR());
+ query_msg->setHeaderFlag(Message::HEADERFLAG_RD);
+ query_msg->addQuestion(question);
+
+ if (edns) {
+ EDNSPtr edns_query(new EDNS());
+ edns_query->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
+ query_msg->setEDNS(edns_query);
+ }
+
+ MessageRenderer renderer;
+ renderer.setBuffer(data_->msgbuf.get());
+ query_msg->toWire(renderer);
+ renderer.setBuffer(NULL);
+}
+
+// Return protocol in use.
+
+IOFetch::Protocol
+IOFetch::getProtocol() const {
+ return (data_->protocol);
+}
+
+/// The function operator is implemented with the "stackless coroutine"
+/// pattern; see boost/asio/coroutine.hpp for details.
+
+void
+IOFetch::operator()(boost::system::error_code ec, size_t length) {
+ if (data_->stopped) {
+ return;
+
+ // On Debian it has been often observed that boost::asio async
+ // operations result in EINPROGRESS. This doesn't necessarily
+ // indicate an issue. Thus, we continue as if no error occurred.
+ } else if (ec && (ec.value() != boost::asio::error::in_progress)) {
+ logIOFailure(ec);
+ return;
+ }
+
+ BOOST_ASIO_CORO_REENTER (this) {
+
+ /// Generate the upstream query and render it to wire format
+ /// This is done in a different scope to allow inline variable
+ /// declarations.
+ {
+ if (data_->packet) {
+ // A packet was given, overwrite the QID (which is in the
+ // first two bytes of the packet).
+ data_->msgbuf->writeUint16At(data_->qid, 0);
+
+ }
+ }
+
+ // If we timeout, we stop, which cancels outstanding I/O operations and
+ // shuts down everything.
+ if (data_->timeout != -1) {
+ data_->timer.expires_from_now(boost::posix_time::milliseconds(
+ data_->timeout));
+ data_->timer.async_wait(std::bind(&IOFetch::stop, *this,
+ TIME_OUT));
+ }
+
+ // Open a connection to the target system. For speed, if the operation
+ // is synchronous (i.e. UDP operation) we bypass the yield.
+ data_->origin = ASIODNS_OPEN_SOCKET;
+ if (data_->socket->isOpenSynchronous()) {
+ data_->socket->open(data_->remote_snd.get(), *this);
+ } else {
+ BOOST_ASIO_CORO_YIELD data_->socket->open(data_->remote_snd.get(), *this);
+ }
+
+ do {
+ // Begin an asynchronous send, and then yield. When the send completes,
+ // we will resume immediately after this point.
+ data_->origin = ASIODNS_SEND_DATA;
+ BOOST_ASIO_CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
+ data_->msgbuf->getLength(), data_->remote_snd.get(), *this);
+
+ // Now receive the response. Since TCP may not receive the entire
+ // message in one operation, we need to loop until we have received
+ // it. (This can't be done within the asyncReceive() method because
+ // each I/O operation will be done asynchronously and between each one
+ // we need to yield ... and we *really* don't want to set up another
+ // coroutine within that method.) So after each receive (and yield),
+ // we check if the operation is complete and if not, loop to read again.
+ //
+ // Another concession to TCP is that the amount of is contained in the
+ // first two bytes. This leads to two problems:
+ //
+ // a) We don't want those bytes in the return buffer.
+ // b) They may not both arrive in the first I/O.
+ //
+ // So... we need to loop until we have at least two bytes, then store
+ // the expected amount of data. Then we need to loop until we have
+ // received all the data before copying it back to the user's buffer.
+ // And we want to minimize the amount of copying...
+
+ data_->origin = ASIODNS_READ_DATA;
+ data_->cumulative = 0; // No data yet received
+ data_->offset = 0; // First data into start of buffer
+ data_->received->clear(); // Clear the receive buffer
+ do {
+ BOOST_ASIO_CORO_YIELD data_->socket->asyncReceive(data_->staging,
+ static_cast<size_t>(STAGING_LENGTH),
+ data_->offset,
+ data_->remote_rcv.get(), *this);
+ } while (!data_->socket->processReceivedData(data_->staging, length,
+ data_->cumulative, data_->offset,
+ data_->expected, data_->received));
+ } while (!data_->responseOK());
+
+ // Finished with this socket, so close it. This will not generate an
+ // I/O error, but reset the origin to unknown in case we change this.
+ data_->origin = ASIODNS_UNKNOWN_ORIGIN;
+ data_->socket->close();
+
+ /// We are done
+ stop(SUCCESS);
+ }
+}
+
+// Function that stops the coroutine sequence. It is called either when the
+// query finishes or when the timer times out. Either way, it sets the
+// "stopped_" flag and cancels anything that is in progress.
+//
+// As the function may be entered multiple times as things wind down, it checks
+// if the stopped_ flag is already set. If it is, the call is a no-op.
+
+void
+IOFetch::stop(Result result) {
+ if (!data_->stopped) {
+
+ // Mark the fetch as stopped to prevent other completion callbacks
+ // (invoked because of the calls to cancel()) from executing the
+ // cancel calls again.
+ //
+ // In a single threaded environment, the callbacks won't be invoked
+ // until this one completes. In a multi-threaded environment, they may
+ // well be, in which case the testing (and setting) of the stopped_
+ // variable should be done inside a mutex (and the stopped_ variable
+ // declared as "volatile").
+ //
+ // TODO: Update testing of stopped_ if threads are used.
+ data_->stopped = true;
+ switch (result) {
+ case TIME_OUT:
+ LOG_DEBUG(logger, DBG_COMMON, ASIODNS_READ_TIMEOUT).
+ arg(data_->remote_snd->getAddress().toText()).
+ arg(data_->remote_snd->getPort());
+ break;
+
+ case SUCCESS:
+ LOG_DEBUG(logger, DBG_ALL, ASIODNS_FETCH_COMPLETED).
+ arg(data_->remote_rcv->getAddress().toText()).
+ arg(data_->remote_rcv->getPort());
+ break;
+
+ case STOPPED:
+ // Fetch has been stopped for some other reason. This is
+ // allowed but as it is unusual it is logged, but with a lower
+ // debug level than a timeout (which is totally normal).
+ LOG_DEBUG(logger, DBG_IMPORTANT, ASIODNS_FETCH_STOPPED).
+ arg(data_->remote_snd->getAddress().toText()).
+ arg(data_->remote_snd->getPort());
+ break;
+
+ default:
+ LOG_ERROR(logger, ASIODNS_UNKNOWN_RESULT).
+ arg(data_->remote_snd->getAddress().toText()).
+ arg(data_->remote_snd->getPort());
+ }
+
+ // Stop requested, cancel and I/O's on the socket and shut it down,
+ // and cancel the timer.
+ data_->socket->cancel();
+ data_->socket->close();
+
+ data_->timer.cancel();
+
+ // Execute the I/O completion callback (if present).
+ if (data_->callback) {
+ (*(data_->callback))(result);
+ }
+ }
+}
+
+// Log an error - called on I/O failure
+
+void IOFetch::logIOFailure(boost::system::error_code ec) {
+ // Should only get here with a known error code.
+ if ((data_->origin != ASIODNS_OPEN_SOCKET) &&
+ (data_->origin != ASIODNS_SEND_DATA) &&
+ (data_->origin != ASIODNS_READ_DATA) &&
+ (data_->origin != ASIODNS_UNKNOWN_ORIGIN)) {
+ isc_throw(isc::Unexpected, "impossible error code " << data_->origin);
+ }
+
+ LOG_ERROR(logger, data_->origin).arg(ec.value()).
+ arg((data_->remote_snd->getProtocol() == IPPROTO_TCP) ?
+ "TCP" : "UDP").
+ arg(data_->remote_snd->getAddress().toText()).
+ arg(data_->remote_snd->getPort());
+}
+
+} // namespace asiodns
+} // namespace isc {
diff --git a/src/lib/asiodns/io_fetch.h b/src/lib/asiodns/io_fetch.h
new file mode 100644
index 0000000..4b247ab
--- /dev/null
+++ b/src/lib/asiodns/io_fetch.h
@@ -0,0 +1,246 @@
+// Copyright (C) 2010-2021 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 IO_FETCH_H
+#define IO_FETCH_H 1
+
+#include <config.h>
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+// We want to use coroutine.hpp from the system's boost headers if possible.
+// However, very old Boost versions (provided by RHEL 7 or CentOS 7) didn't have
+// this header. So we can resort to our bundled version, but only if necessary.
+#ifdef HAVE_BOOST_ASIO_COROUTINE_HPP
+#include <boost/asio/coroutine.hpp>
+#else
+#include <ext/coroutine/coroutine.hpp>
+#endif
+
+#include <boost/system/error_code.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+
+#include <util/buffer.h>
+#include <dns/question.h>
+#include <dns/message.h>
+
+namespace isc {
+namespace asiodns {
+
+// Forward declarations
+struct IOFetchData;
+
+/// \brief Upstream Fetch Processing
+///
+/// IOFetch is the class used to send upstream fetches and to handle responses.
+///
+/// \param E Endpoint type to use.
+
+class IOFetch : public boost::asio::coroutine {
+public:
+ /// \brief Protocol to use on the fetch
+ enum Protocol {
+ UDP = 0,
+ TCP = 1
+ };
+
+ /// \brief Origin of Asynchronous I/O Call
+ ///
+ /// Indicates what initiated an asynchronous I/O call and used in deciding
+ /// what error message to output if the I/O fails.
+ enum Origin {
+ NONE = 0, ///< No asynchronous call outstanding
+ OPEN = 1,
+ SEND = 2,
+ RECEIVE = 3,
+ CLOSE = 4
+ };
+
+ /// \brief Result of Upstream Fetch
+ ///
+ /// Note that this applies to the status of I/Os in the fetch - a fetch
+ /// that resulted in a packet being received from the server is a SUCCESS,
+ /// even if the contents of the packet indicate that some error occurred.
+ enum Result {
+ SUCCESS = 0, ///< Success, fetch completed
+ TIME_OUT = 1, ///< Failure, fetch timed out
+ STOPPED = 2, ///< Control code, fetch has been stopped
+ NOTSET = 3 ///< For testing, indicates value not set
+ };
+
+ // The next enum is a "trick" to allow constants to be defined in a class
+ // declaration.
+
+ /// \brief Integer Constants
+ enum {
+ STAGING_LENGTH = 8192 ///< Size of staging buffer
+ };
+
+ /// \brief I/O Fetch Callback
+ ///
+ /// Class of callback object for when the fetch itself has completed - an
+ /// object of this class is passed to the IOFetch constructor and its
+ /// operator() method called when the fetch completes.
+ ///
+ /// Note the difference between the two operator() methods:
+ /// - IOFetch::operator() callback is called when an asynchronous I/O has
+ /// completed.
+ /// - IOFetch::Callback::operator() is called when an upstream fetch - which
+ /// may have involved several asynchronous I/O operations - has completed.
+ ///
+ /// This is an abstract class.
+ class Callback {
+ public:
+ /// \brief Default Constructor
+ Callback()
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~Callback()
+ {}
+
+ /// \brief Callback method
+ ///
+ /// This is the method called when the fetch completes.
+ ///
+ /// \param result Result of the fetch
+ virtual void operator()(Result result) = 0;
+ };
+
+ /// \brief Constructor.
+ ///
+ /// Creates the object that will handle the upstream fetch.
+ ///
+ /// \param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP
+ /// \param service I/O Service object to handle the asynchronous
+ /// operations.
+ /// \param question DNS question to send to the upstream server.
+ /// \param address IP address of upstream server
+ /// \param port Port to which to connect on the upstream server
+ /// \param buff Output buffer into which the response (in wire format)
+ /// is written (if a response is received).
+ /// \param cb Callback object containing the callback to be called when we
+ /// terminate. The caller is responsible for managing this object
+ /// and deleting it if necessary.
+ /// \param wait Timeout for the fetch (in ms). The default value of
+ /// -1 indicates no timeout.
+ /// \param edns true if the request should be EDNS. The default value is
+ /// true.
+ IOFetch(Protocol protocol, isc::asiolink::IOService& service,
+ const isc::dns::Question& question,
+ const isc::asiolink::IOAddress& address,
+ uint16_t port, isc::util::OutputBufferPtr& buff, Callback* cb,
+ int wait = -1,
+ bool edns = true);
+
+ /// \brief Constructor
+ /// This constructor has one parameter "query_message", which
+ /// is the shared_ptr to a full query message. It's different
+ /// with above constructor which has only question section. All
+ /// other parameters are same.
+ ///
+ /// \param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP
+ /// \param service I/O Service object to handle the asynchronous
+ /// operations.
+ /// \param query_message the shared_ptr to a full query message
+ /// got from a query client.
+ /// \param address IP address of upstream server
+ /// \param port Port to which to connect on the upstream server
+ /// \param buff Output buffer into which the response (in wire format)
+ /// is written (if a response is received).
+ /// \param cb Callback object containing the callback to be called when we
+ /// terminate. The caller is responsible for managing this object
+ /// and deleting it if necessary.
+ /// \param wait Timeout for the fetch (in ms). The default value of
+ /// -1 indicates no timeout.
+ IOFetch(Protocol protocol, isc::asiolink::IOService& service,
+ isc::dns::ConstMessagePtr query_message,
+ const isc::asiolink::IOAddress& address,
+ uint16_t port, isc::util::OutputBufferPtr& buff, Callback* cb,
+ int wait = -1);
+
+ /// \brief Constructor.
+ ///
+ /// Creates the object that will handle the upstream fetch.
+ ///
+ /// \param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP
+ /// \param service I/O Service object to handle the asynchronous
+ /// operations.
+ /// \param outpkt Packet to send to upstream server. Note that the
+ /// QID (first two bytes of the packet) may be altered in the sending.
+ /// \param buff Output buffer into which the response (in wire format)
+ /// is written (if a response is received).
+ /// \param cb Callback object containing the callback to be called
+ /// when we terminate. The caller is responsible for managing this
+ /// object and deleting it if necessary.
+ /// \param address IP address of upstream server
+ /// \param port Port to which to connect on the upstream server
+ /// (default = 53)
+ /// \param wait Timeout for the fetch (in ms). The default value of
+ /// -1 indicates no timeout.
+ IOFetch(Protocol protocol, isc::asiolink::IOService& service,
+ isc::util::OutputBufferPtr& outpkt,
+ const isc::asiolink::IOAddress& address,
+ uint16_t port, isc::util::OutputBufferPtr& buff, Callback* cb,
+ int wait = -1);
+
+ /// \brief Return Current Protocol
+ ///
+ /// \return Protocol associated with this IOFetch object.
+ Protocol getProtocol() const;
+
+ /// \brief Coroutine entry point
+ ///
+ /// The operator() method is the method in which the coroutine code enters
+ /// this object when an operation has been completed.
+ ///
+ /// \param ec Error code, the result of the last asynchronous I/O operation.
+ /// \param length Amount of data received on the last asynchronous read
+ void operator()(boost::system::error_code ec = boost::system::error_code(), size_t length = 0);
+
+ /// \brief Terminate query
+ ///
+ /// This method can be called at any point. It terminates the current
+ /// query with the specified reason.
+ ///
+ /// \param reason Reason for terminating the query
+ void stop(Result reason = STOPPED);
+
+private:
+ /// \brief IOFetch Initialization Function.
+ /// All the parameters are same with the constructor, except
+ /// parameter "query_message"
+ /// \param query_message the message to be sent out.
+ void initIOFetch(isc::dns::MessagePtr& query_message, Protocol protocol,
+ isc::asiolink::IOService& service,
+ const isc::dns::Question& question,
+ const isc::asiolink::IOAddress& address, uint16_t port,
+ isc::util::OutputBufferPtr& buff, Callback* cb, int wait,
+ bool edns = true);
+
+ /// \brief Log I/O Failure
+ ///
+ /// Records an I/O failure to the log file
+ ///
+ /// \param ec ASIO error code
+ void logIOFailure(boost::system::error_code ec);
+
+ // Member variables. All data is in a structure pointed to by a shared
+ // pointer. The IOFetch object is copied a number of times during its
+ // life, and only requiring a pointer to be copied reduces overhead.
+ boost::shared_ptr<IOFetchData> data_; ///< Private data
+};
+
+/// \brief Defines a pointer to an IOFetch.
+typedef boost::shared_ptr<IOFetch> IOFetchPtr;
+
+} // namespace asiodns
+} // namespace isc
+
+#endif // IO_FETCH_H
diff --git a/src/lib/asiodns/logger.cc b/src/lib/asiodns/logger.cc
new file mode 100644
index 0000000..b14701c
--- /dev/null
+++ b/src/lib/asiodns/logger.cc
@@ -0,0 +1,19 @@
+// Copyright (C) 2012-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 <asiodns/logger.h>
+
+namespace isc {
+namespace asiodns {
+
+/// Use the ASIO logger
+
+isc::log::Logger logger("asiodns");
+
+}
+}
diff --git a/src/lib/asiodns/logger.h b/src/lib/asiodns/logger.h
new file mode 100644
index 0000000..e2d88e2
--- /dev/null
+++ b/src/lib/asiodns/logger.h
@@ -0,0 +1,18 @@
+// Copyright (C) 2012-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 <log/logger.h>
+#include <log/macros.h>
+#include <log/log_dbglevels.h>
+#include <asiodns/asiodns_messages.h>
+
+namespace isc {
+namespace asiodns {
+
+extern isc::log::Logger logger;
+
+}
+}
diff --git a/src/lib/asiodns/tests/Makefile.am b/src/lib/asiodns/tests/Makefile.am
new file mode 100644
index 0000000..74517c1
--- /dev/null
+++ b/src/lib/asiodns/tests/Makefile.am
@@ -0,0 +1,43 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+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 = run_unittests.cc
+run_unittests_SOURCES += io_fetch_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+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/log/libkea-log.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 += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+run_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiodns/tests/Makefile.in b/src/lib/asiodns/tests/Makefile.in
new file mode 100644
index 0000000..56728c7
--- /dev/null
+++ b/src/lib/asiodns/tests/Makefile.in
@@ -0,0 +1,896 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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
+@HAVE_GTEST_TRUE@@USE_GXX_TRUE@am__append_2 = -Wno-unused-parameter
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/asiodns/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = run_unittests.cc io_fetch_unittest.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_fetch_unittest.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) \
+@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) \
+ $(run_unittests_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-io_fetch_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 =
+SOURCES = $(run_unittests_SOURCES)
+DIST_SOURCES = $(am__run_unittests_SOURCES_DIST)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+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; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+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 = run_unittests.cc \
+@HAVE_GTEST_TRUE@ io_fetch_unittest.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(GTEST_LDADD)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_CXXFLAGS = $(AM_CXXFLAGS) \
+@HAVE_GTEST_TRUE@ $(am__append_2)
+all: all-am
+
+.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/asiodns/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/asiodns/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-io_fetch_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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`
+
+run_unittests-io_fetch_unittest.o: io_fetch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_fetch_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-io_fetch_unittest.Tpo -c -o run_unittests-io_fetch_unittest.o `test -f 'io_fetch_unittest.cc' || echo '$(srcdir)/'`io_fetch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_fetch_unittest.Tpo $(DEPDIR)/run_unittests-io_fetch_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_fetch_unittest.cc' object='run_unittests-io_fetch_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_fetch_unittest.o `test -f 'io_fetch_unittest.cc' || echo '$(srcdir)/'`io_fetch_unittest.cc
+
+run_unittests-io_fetch_unittest.obj: io_fetch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_fetch_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_fetch_unittest.Tpo -c -o run_unittests-io_fetch_unittest.obj `if test -f 'io_fetch_unittest.cc'; then $(CYGPATH_W) 'io_fetch_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_fetch_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_fetch_unittest.Tpo $(DEPDIR)/run_unittests-io_fetch_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_fetch_unittest.cc' object='run_unittests-io_fetch_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_fetch_unittest.obj `if test -f 'io_fetch_unittest.cc'; then $(CYGPATH_W) 'io_fetch_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_fetch_unittest.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS)
+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 clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/run_unittests-io_fetch_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/run_unittests-io_fetch_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: 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 \
+ 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/asiodns/tests/io_fetch_unittest.cc b/src/lib/asiodns/tests/io_fetch_unittest.cc
new file mode 100644
index 0000000..628e8fd
--- /dev/null
+++ b/src/lib/asiodns/tests/io_fetch_unittest.cc
@@ -0,0 +1,738 @@
+// Copyright (C) 2011-2020 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiodns/io_fetch.h>
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <gtest/gtest.h>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <algorithm>
+#include <cstdlib>
+#include <functional>
+#include <string>
+#include <iostream>
+#include <iomanip>
+#include <iterator>
+#include <vector>
+
+using namespace boost::asio::ip;
+using namespace boost::asio;
+using namespace isc::asiolink;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiodns {
+
+const boost::asio::ip::address TEST_HOST(boost::asio::ip::address::from_string("127.0.0.1"));
+const uint16_t TEST_PORT(5301);
+const int SEND_INTERVAL = 250; // Interval in ms between TCP sends
+const size_t MAX_SIZE = 64 * 1024; // Should be able to take 64kB
+
+// The tests are complex, so debug output has been left in (although disabled).
+// Set this to true to enable it.
+const bool DEBUG = false;
+
+/// \brief Test fixture for the asiolink::IOFetch.
+class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback
+{
+public:
+ IOService service_; ///< Service to run the query
+ IOFetch::Result expected_; ///< Expected result of the callback
+ bool run_; ///< Did the callback run already?
+ Question question_; ///< What to ask
+ OutputBufferPtr result_buff_; ///< Buffer to hold result of fetch
+ OutputBufferPtr msgbuf_; ///< Buffer corresponding to known question
+ IOFetch udp_fetch_; ///< For UDP query test
+ IOFetch tcp_fetch_; ///< For TCP query test
+ IOFetch::Protocol protocol_; ///< Protocol being tested
+ size_t cumulative_; ///< Cumulative data received by "server".
+ deadline_timer timer_; ///< Timer to measure timeouts
+
+ // The next member is the buffer in which the "server" (implemented by the
+ // response handler methods in this class) receives the question sent by the
+ // fetch object.
+ uint8_t receive_buffer_[MAX_SIZE]; ///< Server receive buffer
+ OutputBufferPtr expected_buffer_; ///< Data we expect to receive
+ vector<uint8_t> send_buffer_; ///< Server send buffer
+ uint16_t send_cumulative_; ///< Data sent so far
+
+ // Other data.
+ string return_data_; ///< Data returned by server
+ string test_data_; ///< Large string - here for convenience
+ bool debug_; ///< true to enable debug output
+ size_t tcp_send_size_; ///< Max size of TCP send
+ uint8_t qid_0; ///< First octet of qid
+ uint8_t qid_1; ///< Second octet of qid
+
+ bool tcp_short_send_; ///< If set to true, we do not send
+ /// all data in the tcp response
+
+ /// \brief Constructor
+ IOFetchTest() :
+ service_(),
+ expected_(IOFetch::NOTSET),
+ run_(false),
+ question_(Name("example.net"), RRClass::IN(), RRType::A()),
+ result_buff_(new OutputBuffer(512)),
+ msgbuf_(new OutputBuffer(512)),
+ udp_fetch_(IOFetch::UDP, service_, question_, IOAddress(TEST_HOST),
+ TEST_PORT, result_buff_, this, 100),
+ tcp_fetch_(IOFetch::TCP, service_, question_, IOAddress(TEST_HOST),
+ TEST_PORT, result_buff_, this, (24 * SEND_INTERVAL)),
+ // Timeout interval chosen to ensure no timeout
+ protocol_(IOFetch::TCP), // for initialization - will be changed
+ cumulative_(0),
+ timer_(service_.get_io_service()),
+ receive_buffer_(),
+ expected_buffer_(new OutputBuffer(512)),
+ send_buffer_(),
+ send_cumulative_(0),
+ return_data_(""),
+ test_data_(""),
+ debug_(DEBUG),
+ tcp_send_size_(0),
+ qid_0(0),
+ qid_1(0),
+ tcp_short_send_(false)
+ {
+ // Construct the data buffer for question we expect to receive.
+ Message msg(Message::RENDER);
+ msg.setQid(0);
+ msg.setOpcode(Opcode::QUERY());
+ msg.setRcode(Rcode::NOERROR());
+ msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ msg.addQuestion(question_);
+ EDNSPtr msg_edns(new EDNS());
+ msg_edns->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
+ msg.setEDNS(msg_edns);
+
+ MessageRenderer renderer;
+ renderer.setBuffer(msgbuf_.get());
+ msg.toWire(renderer);
+ renderer.setBuffer(NULL);
+
+ renderer.setBuffer(expected_buffer_.get());
+ msg.toWire(renderer);
+ renderer.setBuffer(NULL);
+
+ // Initialize the test data to be returned: tests will return a
+ // substring of this data. (It's convenient to have this as a member of
+ // the class.)
+ //
+ // We could initialize the data with a single character, but as an added
+ // check we'll make ssre that it has some structure.
+
+ test_data_.clear();
+ test_data_.reserve(MAX_SIZE);
+ while (test_data_.size() < MAX_SIZE) {
+ test_data_ += "A message to be returned to the client that has "
+ "some sort of structure.";
+ }
+ }
+
+ /// \brief UDP Response handler (the "remote UDP DNS server")
+ ///
+ /// When IOFetch is sending data, this response handler emulates the remote
+ /// DNS server. It checks that the data sent by the IOFetch object is what
+ /// was expected to have been sent, then sends back a known buffer of data.
+ ///
+ /// \param remote Endpoint to which to send the answer
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param bad_qid If set to true, the QID in the response will be mangled
+ /// \param second_send If set to true, (and bad_qid is too), after the
+ /// mangled qid response has been sent, a second packet will be
+ /// sent with the correct QID.
+ /// \param length Amount of data received.
+ void udpReceiveHandler(udp::endpoint* remote, udp::socket* socket,
+ boost::system::error_code ec = boost::system::error_code(),
+ size_t length = 0, bool bad_qid = false,
+ bool second_send = false)
+ {
+ if (debug_) {
+ cout << "udpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // The QID in the incoming data is random so set it to 0 for the
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ qid_0 = receive_buffer_[0];
+ qid_1 = receive_buffer_[1];
+ receive_buffer_[0] = receive_buffer_[1] = 0;
+
+ // Check that length of the received data and the expected data are
+ // identical, then check that the data is identical as well.
+ EXPECT_EQ(msgbuf_->getLength(), length);
+ EXPECT_TRUE(equal(receive_buffer_, (receive_buffer_ + length - 1),
+ static_cast<const uint8_t*>(msgbuf_->getData())));
+
+ // Return a message back to the IOFetch object.
+ if (!bad_qid) {
+ expected_buffer_->writeUint8At(qid_0, 0);
+ expected_buffer_->writeUint8At(qid_1, 1);
+ } else {
+ expected_buffer_->writeUint8At(qid_0 + 1, 0);
+ expected_buffer_->writeUint8At(qid_1 + 1, 1);
+ }
+ socket->send_to(boost::asio::buffer(expected_buffer_->getData(), length), *remote);
+
+ if (bad_qid && second_send) {
+ expected_buffer_->writeUint8At(qid_0, 0);
+ expected_buffer_->writeUint8At(qid_1, 1);
+ socket->send_to(boost::asio::buffer(expected_buffer_->getData(),
+ expected_buffer_->getLength()), *remote);
+ }
+ if (debug_) {
+ cout << "udpReceiveHandler(): returned " << expected_buffer_->getLength() <<
+ " bytes to the client" << endl;
+ }
+ }
+
+ /// \brief Completion Handler for accepting TCP data
+ ///
+ /// Called when the remote system connects to the "server". It issues
+ /// an asynchronous read on the socket to read data.
+ ///
+ /// \param socket Socket on which data will be received
+ /// \param ec Boost error code, value should be zero.
+ void tcpAcceptHandler(tcp::socket* socket,
+ boost::system::error_code ec = boost::system::error_code())
+ {
+ if (debug_) {
+ cout << "tcpAcceptHandler(): error = " << ec.value() << endl;
+ }
+
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Work out the maximum size of data we can send over it when we
+ // respond, then subtract 1kB or so for safety.
+ tcp::socket::send_buffer_size send_size;
+ socket->get_option(send_size);
+ if (send_size.value() < (2 * 1024)) {
+ FAIL() << "TCP send size is less than 2kB";
+ } else {
+ tcp_send_size_ = send_size.value() - 1024;
+ if (debug_) {
+ cout << "tcpacceptHandler(): will use send size = " << tcp_send_size_ << endl;
+ }
+ }
+
+ // Initiate a read on the socket.
+ cumulative_ = 0;
+ socket->async_receive(boost::asio::buffer(receive_buffer_, sizeof(receive_buffer_)),
+ std::bind(&IOFetchTest::tcpReceiveHandler, this, socket, ph::_1, ph::_2));
+ }
+
+ /// \brief Completion handler for receiving TCP data
+ ///
+ /// When IOFetch is sending data, this response handler emulates the remote
+ /// DNS server. It that all the data sent by the IOFetch object has been
+ /// received, issuing another read if not. If the data is complete, it is
+ /// compared to what is expected and a reply sent back to the IOFetch.
+ ///
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void tcpReceiveHandler(tcp::socket* socket,
+ boost::system::error_code ec = boost::system::error_code(),
+ size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // If we haven't received all the data, issue another read.
+ cumulative_ += length;
+ bool complete = false;
+ if (cumulative_ > 2) {
+ uint16_t dns_length = readUint16(receive_buffer_,
+ sizeof(receive_buffer_));
+ complete = ((dns_length + 2) == cumulative_);
+ }
+
+ if (!complete) {
+ socket->async_receive(boost::asio::buffer((receive_buffer_ + cumulative_),
+ (sizeof(receive_buffer_) - cumulative_)),
+ std::bind(&IOFetchTest::tcpReceiveHandler, this, socket, ph::_1, ph::_2));
+ return;
+ }
+
+ // Check that length of the DNS message received is that expected, then
+ // compare buffers, zeroing the QID in the received buffer to match
+ // that set in our expected question. Note that due to the length
+ // field the QID in the received buffer is in the third and fourth
+ // bytes.
+ EXPECT_EQ(msgbuf_->getLength() + 2, cumulative_);
+ qid_0 = receive_buffer_[2];
+ qid_1 = receive_buffer_[3];
+
+ receive_buffer_[2] = receive_buffer_[3] = 0;
+ EXPECT_TRUE(equal((receive_buffer_ + 2), (receive_buffer_ + cumulative_ - 2),
+ static_cast<const uint8_t*>(msgbuf_->getData())));
+
+ // ... and return a message back. This has to be preceded by a two-byte
+ // count field.
+
+ send_buffer_.clear();
+ send_buffer_.push_back(0);
+ send_buffer_.push_back(0);
+ // send_buffer_.capacity() seems more logical below, but the
+ // code above fills in the first two bytes and size() becomes 2
+ // (sizeof uint16_t).
+ writeUint16(return_data_.size(), &send_buffer_[0], send_buffer_.size());
+ copy(return_data_.begin(), return_data_.end(), back_inserter(send_buffer_));
+ if (return_data_.size() >= 2) {
+ send_buffer_[2] = qid_0;
+ send_buffer_[3] = qid_1;
+ }
+ // Send the data. This is done in multiple writes with a delay between
+ // each to check that the reassembly of TCP packets from fragments works.
+ send_cumulative_ = 0;
+ tcpSendData(socket);
+ }
+
+ /// \brief Sent Data Over TCP
+ ///
+ /// Send the TCP data back to the IOFetch object. The data is sent in
+ /// three chunks - two of 16 bytes and the remainder, with a 250ms gap
+ /// between each. (Amounts of data smaller than one 32 bytes are sent in
+ /// one or two packets.)
+ ///
+ /// \param socket Socket over which send should take place
+ void tcpSendData(tcp::socket* socket) {
+ if (debug_) {
+ cout << "tcpSendData()" << endl;
+ }
+
+ // Decide what to send based on the cumulative count. At most we'll do
+ // two chunks of 16 bytes (with a 250ms gap between) and then the
+ // remainder.
+ uint8_t* send_ptr = &send_buffer_[send_cumulative_];
+ // Pointer to data to send
+ size_t amount = 16; // Amount of data to send
+ if (send_cumulative_ < (2 * amount)) {
+
+ // First or second time through, send at most 16 bytes
+ amount = min(amount, (send_buffer_.size() - send_cumulative_));
+
+ } else {
+
+ // For all subsequent times, send the remainder, maximised to
+ // whatever we have chosen for the maximum send size.
+ amount = min(tcp_send_size_,
+ (send_buffer_.size() - send_cumulative_));
+ }
+
+ // This is for the short send test; reduce the actual amount of
+ // data we send
+ if (tcp_short_send_) {
+ if (debug_) {
+ cout << "tcpSendData(): sending incomplete data (" <<
+ (amount - 1) << " of " << amount << " bytes)" <<
+ endl;
+ }
+ --amount;
+ } else {
+ if (debug_) {
+ cout << "tcpSendData(): sending " << amount << " bytes" << endl;
+ }
+ }
+
+ // ... and send it. The amount sent is also passed as the first
+ // argument of the send callback, as a check.
+ socket->async_send(boost::asio::buffer(send_ptr, amount),
+ std::bind(&IOFetchTest::tcpSendHandler, this,
+ amount, socket, ph::_1, ph::_2));
+ }
+
+ /// \brief Completion Handler for Sending TCP data
+ ///
+ /// Called when the asynchronous send of data back to the IOFetch object
+ /// by the TCP "server" in this class has completed. (This send has to
+ /// be asynchronous because control needs to return to the caller in order
+ /// for the IOService "run()" method to be called to run the handlers.)
+ ///
+ /// If not all the data has been sent, a short delay is instigated (during
+ /// which control returns to the IOService). This should force the queued
+ /// data to actually be sent and the IOFetch receive handler to be triggered.
+ /// In this way, the ability of IOFetch to handle fragmented TCP packets
+ /// should be checked.
+ ///
+ /// \param expected Number of bytes that were expected to have been sent.
+ /// \param socket Socket over which the send took place. Only used to
+ /// pass back to the send method.
+ /// \param ec Boost error code, value should be zero.
+ /// \param length Number of bytes sent.
+ void tcpSendHandler(size_t expected, tcp::socket* socket,
+ boost::system::error_code ec = boost::system::error_code(),
+ size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpSendHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ EXPECT_EQ(0, ec.value()); // Expect no error
+ EXPECT_EQ(expected, length); // And that amount sent is as expected
+
+ // Do we need to send more?
+ send_cumulative_ += length;
+ if (send_cumulative_ < send_buffer_.size()) {
+
+ // Yes - set up a timer: the callback handler for the timer is
+ // tcpSendData, which will then send the next chunk. We pass the
+ // socket over which data should be sent as an argument to that
+ // function.
+ timer_.expires_from_now(boost::posix_time::milliseconds(SEND_INTERVAL));
+ timer_.async_wait(std::bind(&IOFetchTest::tcpSendData, this,
+ socket));
+ }
+ }
+
+ /// \brief Fetch completion callback
+ ///
+ /// This is the callback's operator() method which is called when the fetch
+ /// is complete. It checks that the data received is the wire format of the
+ /// data sent back by the server.
+ ///
+ /// \param result Result indicated by the callback
+ void operator()(IOFetch::Result result) {
+ if (debug_) {
+ cout << "operator()(): result = " << result << endl;
+ }
+
+ EXPECT_EQ(expected_, result); // Check correct result returned
+ EXPECT_FALSE(run_); // Check it is run only once
+ run_ = true; // Note success
+
+ // If the expected result for SUCCESS, then this should have been called
+ // when one of the "servers" in this class has sent back return_data_.
+ // Check the data is as expected/
+ if (expected_ == IOFetch::SUCCESS) {
+ // In the case of UDP, we actually send back a real looking packet
+ // in the case of TCP, we send back a 'random' string
+ if (protocol_ == IOFetch::UDP) {
+ EXPECT_EQ(expected_buffer_->getLength(), result_buff_->getLength());
+ EXPECT_EQ(0, memcmp(expected_buffer_->getData(), result_buff_->getData(),
+ expected_buffer_->getLength()));
+ } else {
+ EXPECT_EQ(return_data_.size(), result_buff_->getLength());
+ // Overwrite the random qid with our own data for the
+ // comparison to succeed
+ if (result_buff_->getLength() >= 2) {
+ result_buff_->writeUint8At(return_data_[0], 0);
+ result_buff_->writeUint8At(return_data_[1], 1);
+ }
+ const uint8_t* start = static_cast<const uint8_t*>(result_buff_->getData());
+ EXPECT_TRUE(equal(return_data_.begin(), return_data_.end(), start));
+ }
+ }
+
+ // ... and cause the run loop to exit.
+ service_.stop();
+ }
+
+ // The next set of methods are the tests themselves. A number of the TCP
+ // and UDP tests are very similar.
+
+ /// \brief Check for stop()
+ ///
+ /// Test that when we run the query and stop it after it was run, it returns
+ /// "stopped" correctly. (That is why stop() is posted to the service_ as
+ /// well instead of calling it.)
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void stopTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::STOPPED;
+
+ // Post the query
+ service_.get_io_service().post(fetch);
+
+ // Post query_.stop() (yes, the std::bind thing is just
+ // query_.stop()).
+ service_.get_io_service().post(
+ std::bind(&IOFetch::stop, fetch, IOFetch::STOPPED));
+
+ // Run both of them. run() returns when everything in the I/O service
+ // queue has completed.
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
+
+ /// \brief Premature stop test
+ ///
+ /// Test that when we queue the query to service_ and call stop() before it
+ /// gets executed, it acts sanely as well (eg. has the same result as
+ /// running stop() after - calls the callback).
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void prematureStopTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::STOPPED;
+
+ // Stop before it is started
+ fetch.stop();
+ service_.get_io_service().post(fetch);
+
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
+
+ /// \brief Timeout test
+ ///
+ /// Test that fetch times out when no answer arrives.
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void timeoutTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::TIME_OUT;
+
+ service_.get_io_service().post(fetch);
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
+
+ /// \brief Send/Receive Test
+ ///
+ /// Send a query to the server then receives a response.
+ ///
+ /// \param Test data to return to client
+ /// \param short_send If true, do not send all data
+ /// (should result in timeout)
+ void tcpSendReturnTest(const std::string& return_data, bool short_send = false) {
+ if (debug_) {
+ cout << "tcpSendReturnTest(): data size = " << return_data.size() << endl;
+ }
+ return_data_ = return_data;
+ protocol_ = IOFetch::TCP;
+ if (short_send) {
+ tcp_short_send_ = true;
+ expected_ = IOFetch::TIME_OUT;
+ } else {
+ expected_ = IOFetch::SUCCESS;
+ }
+
+ // Socket into which the connection will be accepted.
+ tcp::socket socket(service_.get_io_service());
+
+ // Acceptor object - called when the connection is made, the handler
+ // will initiate a read on the socket.
+ tcp::acceptor acceptor(service_.get_io_service(),
+ tcp::endpoint(tcp::v4(), TEST_PORT));
+ acceptor.async_accept(socket,
+ std::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, ph::_1));
+
+ // Post the TCP fetch object to send the query and receive the response.
+ service_.get_io_service().post(tcp_fetch_);
+
+ // ... and execute all the callbacks. This exits when the fetch
+ // completes.
+ service_.run();
+ EXPECT_TRUE(run_); // Make sure the callback did execute
+
+ // Tidy up
+ socket.close();
+ }
+
+ /// Perform a send/receive test over UDP
+ ///
+ /// \param bad_qid If true, do the test where the QID is mangled
+ /// in the response
+ /// \param second_send If true, do the test where the QID is
+ /// mangled in the response, but a second
+ /// (correct) packet is used
+ void udpSendReturnTest(bool bad_qid, bool second_send) {
+ protocol_ = IOFetch::UDP;
+
+ // Set up the server.
+ udp::socket socket(service_.get_io_service(), udp::v4());
+ socket.set_option(socket_base::reuse_address(true));
+ socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+ return_data_ = "Message returned to the client";
+
+ udp::endpoint remote;
+ socket.async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ remote,
+ std::bind(&IOFetchTest::udpReceiveHandler,
+ this, &remote, &socket,
+ ph::_1, ph::_2, bad_qid, second_send));
+ service_.get_io_service().post(udp_fetch_);
+ if (debug_) {
+ cout << "udpSendReceive: async_receive_from posted,"
+ "waiting for callback" << endl;
+ }
+ service_.run();
+
+ socket.close();
+
+ EXPECT_TRUE(run_);
+ }
+};
+
+// Check the protocol
+TEST_F(IOFetchTest, Protocol) {
+ EXPECT_EQ(IOFetch::UDP, udp_fetch_.getProtocol());
+ EXPECT_EQ(IOFetch::TCP, tcp_fetch_.getProtocol());
+}
+
+// UDP Stop test - see IOFetchTest::stopTest() header.
+TEST_F(IOFetchTest, UdpStop) {
+ stopTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP premature stop test - see IOFetchTest::prematureStopTest() header.
+TEST_F(IOFetchTest, UdpPrematureStop) {
+ prematureStopTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP premature stop test - see IOFetchTest::timeoutTest() header.
+TEST_F(IOFetchTest, UdpTimeout) {
+ timeoutTest(IOFetch::UDP, udp_fetch_);
+}
+
+// UDP SendReceive test. Set up a UDP server then ports a UDP fetch object.
+// This will send question_ to the server and receive the answer back from it.
+TEST_F(IOFetchTest, UdpSendReceive) {
+ expected_ = IOFetch::SUCCESS;
+
+ udpSendReturnTest(false, false);
+
+ EXPECT_TRUE(run_);;
+}
+
+TEST_F(IOFetchTest, UdpSendReceiveBadQid) {
+ expected_ = IOFetch::TIME_OUT;
+
+ udpSendReturnTest(true, false);
+
+ EXPECT_TRUE(run_);;
+}
+
+TEST_F(IOFetchTest, UdpSendReceiveBadQidResend) {
+ expected_ = IOFetch::SUCCESS;
+
+ udpSendReturnTest(true, true);
+
+ EXPECT_TRUE(run_);;
+}
+
+// Do the same tests for TCP transport
+
+TEST_F(IOFetchTest, TcpStop) {
+ stopTest(IOFetch::TCP, tcp_fetch_);
+}
+
+TEST_F(IOFetchTest, TcpPrematureStop) {
+ prematureStopTest(IOFetch::TCP, tcp_fetch_);
+}
+
+TEST_F(IOFetchTest, TcpTimeout) {
+ timeoutTest(IOFetch::TCP, tcp_fetch_);
+}
+
+// Test with values at or near 2, then at or near the chunk size (16 and 32
+// bytes, the sizes of the first two packets) then up to 65535. These are done
+// in separate tests because in practice a new IOFetch is created for each
+// query/response exchange and we don't want to confuse matters in the test
+// by running the test with an IOFetch that has already done one exchange.
+//
+// Don't do 0 or 1; the server would not accept the packet
+// (since the length is too short to check the qid)
+TEST_F(IOFetchTest, TcpSendReceive2) {
+ tcpSendReturnTest(test_data_.substr(0, 2));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive3) {
+ tcpSendReturnTest(test_data_.substr(0, 3));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive15) {
+ tcpSendReturnTest(test_data_.substr(0, 15));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive16) {
+ tcpSendReturnTest(test_data_.substr(0, 16));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive17) {
+ tcpSendReturnTest(test_data_.substr(0, 17));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive31) {
+ tcpSendReturnTest(test_data_.substr(0, 31));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive32) {
+ tcpSendReturnTest(test_data_.substr(0, 32));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive33) {
+ tcpSendReturnTest(test_data_.substr(0, 33));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive4096) {
+ tcpSendReturnTest(test_data_.substr(0, 4096));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive8192) {
+ tcpSendReturnTest(test_data_.substr(0, 8192));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive16384) {
+ tcpSendReturnTest(test_data_.substr(0, 16384));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive32768) {
+ tcpSendReturnTest(test_data_.substr(0, 32768));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive65535) {
+ tcpSendReturnTest(test_data_.substr(0, 65535));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive2ShortSend) {
+ tcpSendReturnTest(test_data_.substr(0, 2), true);
+}
+
+TEST_F(IOFetchTest, TcpSendReceive15ShortSend) {
+ tcpSendReturnTest(test_data_.substr(0, 15), true);
+}
+
+TEST_F(IOFetchTest, TcpSendReceive8192ShortSend) {
+ tcpSendReturnTest(test_data_.substr(0, 8192), true);
+}
+
+
+} // namespace asiodns
+} // namespace isc
diff --git a/src/lib/asiodns/tests/run_unittests.cc b/src/lib/asiodns/tests/run_unittests.cc
new file mode 100644
index 0000000..5d71cc9
--- /dev/null
+++ b/src/lib/asiodns/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// 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 <log/logger_support.h>
+
+int
+main(int argc, char* argv[])
+{
+ ::testing::InitGoogleTest(&argc, argv); // Initialize Google test
+ isc::log::initLogger(); // Initialize logging
+
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
new file mode 100644
index 0000000..5d13833
--- /dev/null
+++ b/src/lib/asiolink/Makefile.am
@@ -0,0 +1,100 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS) -Wno-non-virtual-dtor
+
+EXTRA_DIST = asiolink.dox
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-asiolink.la
+
+libkea_asiolink_la_LDFLAGS = -no-undefined -version-info 56:0:0
+libkea_asiolink_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+
+libkea_asiolink_la_SOURCES = asiolink.h
+libkea_asiolink_la_SOURCES += asio_wrapper.h
+libkea_asiolink_la_SOURCES += addr_utilities.cc addr_utilities.h
+libkea_asiolink_la_SOURCES += botan_boost_tls.h botan_boost_wrapper.h
+libkea_asiolink_la_SOURCES += botan_tls.h
+libkea_asiolink_la_SOURCES += common_tls.cc common_tls.h
+libkea_asiolink_la_SOURCES += crypto_tls.h
+libkea_asiolink_la_SOURCES += dummy_io_cb.h
+libkea_asiolink_la_SOURCES += interval_timer.cc interval_timer.h
+libkea_asiolink_la_SOURCES += io_acceptor.h
+libkea_asiolink_la_SOURCES += io_address.cc io_address.h
+libkea_asiolink_la_SOURCES += io_asio_socket.h
+libkea_asiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
+libkea_asiolink_la_SOURCES += io_error.h
+libkea_asiolink_la_SOURCES += io_service.h io_service.cc
+libkea_asiolink_la_SOURCES += io_service_signal.cc io_service_signal.h
+libkea_asiolink_la_SOURCES += io_service_thread_pool.cc io_service_thread_pool.h
+libkea_asiolink_la_SOURCES += io_socket.h io_socket.cc
+libkea_asiolink_la_SOURCES += openssl_tls.h
+libkea_asiolink_la_SOURCES += process_spawn.h process_spawn.cc
+libkea_asiolink_la_SOURCES += tcp_acceptor.h
+libkea_asiolink_la_SOURCES += tcp_endpoint.h
+libkea_asiolink_la_SOURCES += tcp_socket.h
+libkea_asiolink_la_SOURCES += tls_acceptor.h
+libkea_asiolink_la_SOURCES += tls_socket.h
+libkea_asiolink_la_SOURCES += udp_endpoint.h
+libkea_asiolink_la_SOURCES += udp_socket.h
+libkea_asiolink_la_SOURCES += unix_domain_socket.cc unix_domain_socket.h
+libkea_asiolink_la_SOURCES += unix_domain_socket_acceptor.h
+libkea_asiolink_la_SOURCES += unix_domain_socket_endpoint.h
+
+if HAVE_BOTAN
+if HAVE_BOTAN_BOOST
+libkea_asiolink_la_SOURCES += botan_boost_tls.cc
+else
+libkea_asiolink_la_SOURCES += botan_tls.cc
+endif
+endif
+if HAVE_OPENSSL
+libkea_asiolink_la_SOURCES += openssl_tls.cc
+endif
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+libkea_asiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_asiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_asiolink_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la
+libkea_asiolink_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_asiolink_la_LIBADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_asiolink_includedir = $(pkgincludedir)/asiolink
+libkea_asiolink_include_HEADERS = \
+ addr_utilities.h \
+ asio_wrapper.h \
+ asiolink.h \
+ botan_boost_tls.h \
+ botan_boost_wrapper.h \
+ botan_tls.h \
+ common_tls.h \
+ crypto_tls.h \
+ dummy_io_cb.h \
+ interval_timer.h \
+ io_acceptor.h \
+ io_address.h \
+ io_asio_socket.h \
+ io_endpoint.h \
+ io_error.h \
+ io_service.h \
+ io_service_signal.h \
+ io_service_thread_pool.h \
+ io_socket.h \
+ openssl_tls.h \
+ process_spawn.h \
+ tcp_acceptor.h \
+ tcp_endpoint.h \
+ tcp_socket.h \
+ tls_acceptor.h \
+ tls_socket.h \
+ udp_endpoint.h \
+ udp_socket.h \
+ unix_domain_socket.h \
+ unix_domain_socket_acceptor.h \
+ unix_domain_socket_endpoint.h
diff --git a/src/lib/asiolink/Makefile.in b/src/lib/asiolink/Makefile.in
new file mode 100644
index 0000000..6115b78
--- /dev/null
+++ b/src/lib/asiolink/Makefile.in
@@ -0,0 +1,1177 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_BOTAN_TRUE@am__append_1 = botan_boost_tls.cc
+@HAVE_BOTAN_BOOST_FALSE@@HAVE_BOTAN_TRUE@am__append_2 = botan_tls.cc
+@HAVE_OPENSSL_TRUE@am__append_3 = openssl_tls.cc
+subdir = src/lib/asiolink
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_asiolink_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)$(libkea_asiolink_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_asiolink_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__libkea_asiolink_la_SOURCES_DIST = asiolink.h asio_wrapper.h \
+ addr_utilities.cc addr_utilities.h botan_boost_tls.h \
+ botan_boost_wrapper.h botan_tls.h common_tls.cc common_tls.h \
+ crypto_tls.h dummy_io_cb.h interval_timer.cc interval_timer.h \
+ io_acceptor.h io_address.cc io_address.h io_asio_socket.h \
+ io_endpoint.cc io_endpoint.h io_error.h io_service.h \
+ io_service.cc io_service_signal.cc io_service_signal.h \
+ io_service_thread_pool.cc io_service_thread_pool.h io_socket.h \
+ io_socket.cc openssl_tls.h process_spawn.h process_spawn.cc \
+ tcp_acceptor.h tcp_endpoint.h tcp_socket.h tls_acceptor.h \
+ tls_socket.h udp_endpoint.h udp_socket.h unix_domain_socket.cc \
+ unix_domain_socket.h unix_domain_socket_acceptor.h \
+ unix_domain_socket_endpoint.h botan_boost_tls.cc botan_tls.cc \
+ openssl_tls.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_BOTAN_TRUE@am__objects_1 = libkea_asiolink_la-botan_boost_tls.lo
+@HAVE_BOTAN_BOOST_FALSE@@HAVE_BOTAN_TRUE@am__objects_2 = libkea_asiolink_la-botan_tls.lo
+@HAVE_OPENSSL_TRUE@am__objects_3 = libkea_asiolink_la-openssl_tls.lo
+am_libkea_asiolink_la_OBJECTS = libkea_asiolink_la-addr_utilities.lo \
+ libkea_asiolink_la-common_tls.lo \
+ libkea_asiolink_la-interval_timer.lo \
+ libkea_asiolink_la-io_address.lo \
+ libkea_asiolink_la-io_endpoint.lo \
+ libkea_asiolink_la-io_service.lo \
+ libkea_asiolink_la-io_service_signal.lo \
+ libkea_asiolink_la-io_service_thread_pool.lo \
+ libkea_asiolink_la-io_socket.lo \
+ libkea_asiolink_la-process_spawn.lo \
+ libkea_asiolink_la-unix_domain_socket.lo $(am__objects_1) \
+ $(am__objects_2) $(am__objects_3)
+libkea_asiolink_la_OBJECTS = $(am_libkea_asiolink_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_asiolink_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_asiolink_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_asiolink_la-addr_utilities.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-botan_boost_tls.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-botan_tls.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-common_tls.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-interval_timer.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-io_address.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-io_endpoint.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-io_service.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-io_service_signal.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-io_service_thread_pool.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-io_socket.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-openssl_tls.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-process_spawn.Plo \
+ ./$(DEPDIR)/libkea_asiolink_la-unix_domain_socket.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_asiolink_la_SOURCES)
+DIST_SOURCES = $(am__libkea_asiolink_la_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
+HEADERS = $(libkea_asiolink_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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . testutils tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS) -Wno-non-virtual-dtor
+EXTRA_DIST = asiolink.dox
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-asiolink.la
+libkea_asiolink_la_LDFLAGS = -no-undefined -version-info 56:0:0 \
+ $(CRYPTO_LDFLAGS)
+libkea_asiolink_la_SOURCES = asiolink.h asio_wrapper.h \
+ addr_utilities.cc addr_utilities.h botan_boost_tls.h \
+ botan_boost_wrapper.h botan_tls.h common_tls.cc common_tls.h \
+ crypto_tls.h dummy_io_cb.h interval_timer.cc interval_timer.h \
+ io_acceptor.h io_address.cc io_address.h io_asio_socket.h \
+ io_endpoint.cc io_endpoint.h io_error.h io_service.h \
+ io_service.cc io_service_signal.cc io_service_signal.h \
+ io_service_thread_pool.cc io_service_thread_pool.h io_socket.h \
+ io_socket.cc openssl_tls.h process_spawn.h process_spawn.cc \
+ tcp_acceptor.h tcp_endpoint.h tcp_socket.h tls_acceptor.h \
+ tls_socket.h udp_endpoint.h udp_socket.h unix_domain_socket.cc \
+ unix_domain_socket.h unix_domain_socket_acceptor.h \
+ unix_domain_socket_endpoint.h $(am__append_1) $(am__append_2) \
+ $(am__append_3)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+libkea_asiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_asiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_asiolink_la_LIBADD = \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_asiolink_includedir = $(pkgincludedir)/asiolink
+libkea_asiolink_include_HEADERS = \
+ addr_utilities.h \
+ asio_wrapper.h \
+ asiolink.h \
+ botan_boost_tls.h \
+ botan_boost_wrapper.h \
+ botan_tls.h \
+ common_tls.h \
+ crypto_tls.h \
+ dummy_io_cb.h \
+ interval_timer.h \
+ io_acceptor.h \
+ io_address.h \
+ io_asio_socket.h \
+ io_endpoint.h \
+ io_error.h \
+ io_service.h \
+ io_service_signal.h \
+ io_service_thread_pool.h \
+ io_socket.h \
+ openssl_tls.h \
+ process_spawn.h \
+ tcp_acceptor.h \
+ tcp_endpoint.h \
+ tcp_socket.h \
+ tls_acceptor.h \
+ tls_socket.h \
+ udp_endpoint.h \
+ udp_socket.h \
+ unix_domain_socket.h \
+ unix_domain_socket_acceptor.h \
+ unix_domain_socket_endpoint.h
+
+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/asiolink/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/asiolink/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-asiolink.la: $(libkea_asiolink_la_OBJECTS) $(libkea_asiolink_la_DEPENDENCIES) $(EXTRA_libkea_asiolink_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_asiolink_la_LINK) -rpath $(libdir) $(libkea_asiolink_la_OBJECTS) $(libkea_asiolink_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-addr_utilities.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-botan_boost_tls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-botan_tls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-common_tls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-interval_timer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-io_address.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-io_endpoint.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-io_service.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-io_service_signal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-io_service_thread_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-io_socket.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-openssl_tls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-process_spawn.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_asiolink_la-unix_domain_socket.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)$(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 $@ $<
+
+libkea_asiolink_la-addr_utilities.lo: addr_utilities.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-addr_utilities.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-addr_utilities.Tpo -c -o libkea_asiolink_la-addr_utilities.lo `test -f 'addr_utilities.cc' || echo '$(srcdir)/'`addr_utilities.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-addr_utilities.Tpo $(DEPDIR)/libkea_asiolink_la-addr_utilities.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='addr_utilities.cc' object='libkea_asiolink_la-addr_utilities.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-addr_utilities.lo `test -f 'addr_utilities.cc' || echo '$(srcdir)/'`addr_utilities.cc
+
+libkea_asiolink_la-common_tls.lo: common_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-common_tls.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-common_tls.Tpo -c -o libkea_asiolink_la-common_tls.lo `test -f 'common_tls.cc' || echo '$(srcdir)/'`common_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-common_tls.Tpo $(DEPDIR)/libkea_asiolink_la-common_tls.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='common_tls.cc' object='libkea_asiolink_la-common_tls.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-common_tls.lo `test -f 'common_tls.cc' || echo '$(srcdir)/'`common_tls.cc
+
+libkea_asiolink_la-interval_timer.lo: interval_timer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-interval_timer.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-interval_timer.Tpo -c -o libkea_asiolink_la-interval_timer.lo `test -f 'interval_timer.cc' || echo '$(srcdir)/'`interval_timer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-interval_timer.Tpo $(DEPDIR)/libkea_asiolink_la-interval_timer.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interval_timer.cc' object='libkea_asiolink_la-interval_timer.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-interval_timer.lo `test -f 'interval_timer.cc' || echo '$(srcdir)/'`interval_timer.cc
+
+libkea_asiolink_la-io_address.lo: io_address.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-io_address.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-io_address.Tpo -c -o libkea_asiolink_la-io_address.lo `test -f 'io_address.cc' || echo '$(srcdir)/'`io_address.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-io_address.Tpo $(DEPDIR)/libkea_asiolink_la-io_address.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_address.cc' object='libkea_asiolink_la-io_address.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-io_address.lo `test -f 'io_address.cc' || echo '$(srcdir)/'`io_address.cc
+
+libkea_asiolink_la-io_endpoint.lo: io_endpoint.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-io_endpoint.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-io_endpoint.Tpo -c -o libkea_asiolink_la-io_endpoint.lo `test -f 'io_endpoint.cc' || echo '$(srcdir)/'`io_endpoint.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-io_endpoint.Tpo $(DEPDIR)/libkea_asiolink_la-io_endpoint.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_endpoint.cc' object='libkea_asiolink_la-io_endpoint.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-io_endpoint.lo `test -f 'io_endpoint.cc' || echo '$(srcdir)/'`io_endpoint.cc
+
+libkea_asiolink_la-io_service.lo: io_service.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-io_service.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-io_service.Tpo -c -o libkea_asiolink_la-io_service.lo `test -f 'io_service.cc' || echo '$(srcdir)/'`io_service.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-io_service.Tpo $(DEPDIR)/libkea_asiolink_la-io_service.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service.cc' object='libkea_asiolink_la-io_service.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-io_service.lo `test -f 'io_service.cc' || echo '$(srcdir)/'`io_service.cc
+
+libkea_asiolink_la-io_service_signal.lo: io_service_signal.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-io_service_signal.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-io_service_signal.Tpo -c -o libkea_asiolink_la-io_service_signal.lo `test -f 'io_service_signal.cc' || echo '$(srcdir)/'`io_service_signal.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-io_service_signal.Tpo $(DEPDIR)/libkea_asiolink_la-io_service_signal.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_signal.cc' object='libkea_asiolink_la-io_service_signal.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-io_service_signal.lo `test -f 'io_service_signal.cc' || echo '$(srcdir)/'`io_service_signal.cc
+
+libkea_asiolink_la-io_service_thread_pool.lo: io_service_thread_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-io_service_thread_pool.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-io_service_thread_pool.Tpo -c -o libkea_asiolink_la-io_service_thread_pool.lo `test -f 'io_service_thread_pool.cc' || echo '$(srcdir)/'`io_service_thread_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-io_service_thread_pool.Tpo $(DEPDIR)/libkea_asiolink_la-io_service_thread_pool.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_thread_pool.cc' object='libkea_asiolink_la-io_service_thread_pool.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-io_service_thread_pool.lo `test -f 'io_service_thread_pool.cc' || echo '$(srcdir)/'`io_service_thread_pool.cc
+
+libkea_asiolink_la-io_socket.lo: io_socket.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-io_socket.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-io_socket.Tpo -c -o libkea_asiolink_la-io_socket.lo `test -f 'io_socket.cc' || echo '$(srcdir)/'`io_socket.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-io_socket.Tpo $(DEPDIR)/libkea_asiolink_la-io_socket.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_socket.cc' object='libkea_asiolink_la-io_socket.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-io_socket.lo `test -f 'io_socket.cc' || echo '$(srcdir)/'`io_socket.cc
+
+libkea_asiolink_la-process_spawn.lo: process_spawn.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-process_spawn.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-process_spawn.Tpo -c -o libkea_asiolink_la-process_spawn.lo `test -f 'process_spawn.cc' || echo '$(srcdir)/'`process_spawn.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-process_spawn.Tpo $(DEPDIR)/libkea_asiolink_la-process_spawn.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='process_spawn.cc' object='libkea_asiolink_la-process_spawn.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-process_spawn.lo `test -f 'process_spawn.cc' || echo '$(srcdir)/'`process_spawn.cc
+
+libkea_asiolink_la-unix_domain_socket.lo: unix_domain_socket.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-unix_domain_socket.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-unix_domain_socket.Tpo -c -o libkea_asiolink_la-unix_domain_socket.lo `test -f 'unix_domain_socket.cc' || echo '$(srcdir)/'`unix_domain_socket.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-unix_domain_socket.Tpo $(DEPDIR)/libkea_asiolink_la-unix_domain_socket.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unix_domain_socket.cc' object='libkea_asiolink_la-unix_domain_socket.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-unix_domain_socket.lo `test -f 'unix_domain_socket.cc' || echo '$(srcdir)/'`unix_domain_socket.cc
+
+libkea_asiolink_la-botan_boost_tls.lo: botan_boost_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-botan_boost_tls.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-botan_boost_tls.Tpo -c -o libkea_asiolink_la-botan_boost_tls.lo `test -f 'botan_boost_tls.cc' || echo '$(srcdir)/'`botan_boost_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-botan_boost_tls.Tpo $(DEPDIR)/libkea_asiolink_la-botan_boost_tls.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='botan_boost_tls.cc' object='libkea_asiolink_la-botan_boost_tls.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-botan_boost_tls.lo `test -f 'botan_boost_tls.cc' || echo '$(srcdir)/'`botan_boost_tls.cc
+
+libkea_asiolink_la-botan_tls.lo: botan_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-botan_tls.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-botan_tls.Tpo -c -o libkea_asiolink_la-botan_tls.lo `test -f 'botan_tls.cc' || echo '$(srcdir)/'`botan_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-botan_tls.Tpo $(DEPDIR)/libkea_asiolink_la-botan_tls.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='botan_tls.cc' object='libkea_asiolink_la-botan_tls.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-botan_tls.lo `test -f 'botan_tls.cc' || echo '$(srcdir)/'`botan_tls.cc
+
+libkea_asiolink_la-openssl_tls.lo: openssl_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_asiolink_la-openssl_tls.lo -MD -MP -MF $(DEPDIR)/libkea_asiolink_la-openssl_tls.Tpo -c -o libkea_asiolink_la-openssl_tls.lo `test -f 'openssl_tls.cc' || echo '$(srcdir)/'`openssl_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_asiolink_la-openssl_tls.Tpo $(DEPDIR)/libkea_asiolink_la-openssl_tls.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='openssl_tls.cc' object='libkea_asiolink_la-openssl_tls.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_asiolink_la_CPPFLAGS) $(CPPFLAGS) $(libkea_asiolink_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_asiolink_la-openssl_tls.lo `test -f 'openssl_tls.cc' || echo '$(srcdir)/'`openssl_tls.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_asiolink_includeHEADERS: $(libkea_asiolink_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_asiolink_include_HEADERS)'; test -n "$(libkea_asiolink_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_asiolink_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_asiolink_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)$(libkea_asiolink_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_asiolink_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_asiolink_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_asiolink_include_HEADERS)'; test -n "$(libkea_asiolink_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_asiolink_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_asiolink_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-addr_utilities.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-botan_boost_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-botan_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-common_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-interval_timer.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_address.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_endpoint.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_service.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_service_signal.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_service_thread_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_socket.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-openssl_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-process_spawn.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-unix_domain_socket.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-libkea_asiolink_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_asiolink_la-addr_utilities.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-botan_boost_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-botan_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-common_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-interval_timer.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_address.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_endpoint.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_service.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_service_signal.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_service_thread_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-io_socket.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-openssl_tls.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-process_spawn.Plo
+ -rm -f ./$(DEPDIR)/libkea_asiolink_la-unix_domain_socket.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-libkea_asiolink_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_asiolink_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-libkea_asiolink_includeHEADERS
+
+.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/asiolink/README b/src/lib/asiolink/README
new file mode 100644
index 0000000..83f66b8
--- /dev/null
+++ b/src/lib/asiolink/README
@@ -0,0 +1,28 @@
+The asiolink library is intended to provide an abstraction layer between
+Kea modules and the socket I/O subsystem we are using (currently, the
+headers-only version of ASIO included in Boost). This has several benefits,
+including:
+
+ - Simple interface
+
+ - Back-end flexibility: It would be relatively easy to switch to any
+ other asynchronous I/O system.
+
+ - Cleaner compilation: The ASIO headers include code which can
+ generate warnings in some compilers due to unused parameters and
+ such. Including ASIO header files throughout the Kea tree would
+ require us to relax the strictness of our error checking. Including
+ them in only one place allows us to relax strictness here, while
+ leaving it in place elsewhere.
+
+Some of the classes defined here--for example, IOSocket, IOEndpoint,
+and IOAddress--are to be used by Kea modules as wrappers around
+ASIO-specific classes.
+
+
+Logging
+-------
+
+At this point, nothing is logged by this low-level library. We may
+revisit that in the future, if we find suitable messages to log, but
+right now there are also no loggers initialized or called.
diff --git a/src/lib/asiolink/addr_utilities.cc b/src/lib/asiolink/addr_utilities.cc
new file mode 100644
index 0000000..9dfbf0b
--- /dev/null
+++ b/src/lib/asiolink/addr_utilities.cc
@@ -0,0 +1,414 @@
+// Copyright (C) 2012-2023 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 <asiolink/addr_utilities.h>
+#include <exceptions/exceptions.h>
+
+#include <vector>
+#include <limits>
+#include <string.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// @brief mask used for first/last address calculation in a IPv4 prefix
+///
+/// Using a static mask is faster than calculating it dynamically every time.
+const uint32_t bitMask4[] = { 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
+ 0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
+ 0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
+ 0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
+ 0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
+ 0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
+ 0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
+ 0x0000000f, 0x00000007, 0x00000003, 0x00000001,
+ 0x00000000 };
+
+/// @brief mask used for first/last address calculation in a IPv6 prefix
+const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+
+/// @brief mask used for IPv6 prefix calculation
+const uint8_t revMask6[]= { 0xff, 0x7f, 0x3f, 0x1f, 0xf, 0x7, 0x3, 0x1 };
+
+/// @brief calculates the first IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix
+/// @param len prefix length
+IOAddress firstAddrInPrefix6(const IOAddress& prefix, uint8_t len) {
+ if (len > 128) {
+ isc_throw(isc::BadValue,
+ "Too large netmask. 0..128 is allowed in IPv6");
+ }
+
+ // First we copy the whole address as 16 bytes.
+ // We don't check that it is a valid IPv6 address and thus has
+ // the required length because it is already checked by
+ // the calling function.
+ uint8_t packed[V6ADDRESS_LEN];
+ memcpy(packed, &prefix.toBytes()[0], V6ADDRESS_LEN);
+
+ // If the length is divisible by 8, it is simple. We just zero out the host
+ // part. Otherwise we need to handle the byte that has to be partially
+ // zeroed.
+ if (len % 8 != 0) {
+
+ // Get the appropriate mask. It has relevant bits (those that should
+ // stay) set and irrelevant (those that should be wiped) cleared.
+ uint8_t mask = bitMask6[len % 8];
+
+ // Let's leave only whatever the mask says should not be cleared.
+ packed[len / 8] = packed[len / 8] & mask;
+
+ // Since we have just dealt with this byte, let's move the prefix length
+ // to the beginning of the next byte (len is expressed in bits).
+ len = (len / 8 + 1) * 8;
+ }
+
+ // Clear out the remaining bits.
+ for (int i = len / 8; i < sizeof(packed); ++i) {
+ packed[i] = 0x0;
+ }
+
+ // Finally, let's wrap this into nice and easy IOAddress object.
+ return (IOAddress::fromBytes(AF_INET6, packed));
+}
+
+/// @brief calculates the first IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix
+/// @param len netmask length (0-32)
+IOAddress firstAddrInPrefix4(const IOAddress& prefix, uint8_t len) {
+ if (len > 32) {
+ isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+ }
+
+ // We don't check that it is a valid IPv4 address and thus has
+ // a required length of 4 bytes because it has been already
+ // checked by the calling function.
+ uint32_t addr = prefix.toUint32();
+ return (IOAddress(addr & (~bitMask4[len])));
+}
+
+/// @brief calculates the last IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix that we calculate first address for
+/// @param len netmask length (0-32)
+IOAddress lastAddrInPrefix4(const IOAddress& prefix, uint8_t len) {
+ if (len > 32) {
+ isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+ }
+
+ uint32_t addr = prefix.toUint32();
+ return (IOAddress(addr | bitMask4[len]));
+}
+
+/// @brief calculates the last IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use lastAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix that we calculate first address for
+/// @param len netmask length (0-128)
+IOAddress lastAddrInPrefix6(const IOAddress& prefix, uint8_t len) {
+ if (len > 128) {
+ isc_throw(isc::BadValue,
+ "Too large netmask. 0..128 is allowed in IPv6");
+ }
+
+ // First we copy the whole address as 16 bytes.
+ uint8_t packed[V6ADDRESS_LEN];
+ memcpy(packed, &prefix.toBytes()[0], 16);
+
+ // if the length is divisible by 8, it is simple. We just fill the host part
+ // with ones. Otherwise we need to handle the byte that has to be partially
+ // zeroed.
+ if (len % 8 != 0) {
+ // Get the appropriate mask. It has relevant bits (those that should
+ // stay) set and irrelevant (those that should be set to 1) cleared.
+ uint8_t mask = bitMask6[len % 8];
+
+ // Let's set those irrelevant bits with 1. It would be perhaps
+ // easier to not use negation here and invert bitMask6 content. However,
+ // with this approach, we can use the same mask in first and last
+ // address calculations.
+ packed[len / 8] = packed[len / 8] | ~mask;
+
+ // Since we have just dealt with this byte, let's move the prefix length
+ // to the beginning of the next byte (len is expressed in bits).
+ len = (len / 8 + 1) * 8;
+ }
+
+ // Finally set remaining bits to 1.
+ for (int i = len / 8; i < sizeof(packed); ++i) {
+ packed[i] = 0xff;
+ }
+
+ // Finally, let's wrap this into nice and easy IOAddress object.
+ return (IOAddress::fromBytes(AF_INET6, packed));
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace asiolink {
+
+IOAddress firstAddrInPrefix(const IOAddress& prefix, uint8_t len) {
+ if (prefix.isV4()) {
+ return (firstAddrInPrefix4(prefix, len));
+
+ } else {
+ return (firstAddrInPrefix6(prefix, len));
+
+ }
+}
+
+IOAddress lastAddrInPrefix(const IOAddress& prefix, uint8_t len) {
+ if (prefix.isV4()) {
+ return (lastAddrInPrefix4(prefix, len));
+
+ } else {
+ return (lastAddrInPrefix6(prefix, len));
+
+ }
+}
+
+IOAddress getNetmask4(uint8_t len) {
+ if (len > 32) {
+ isc_throw(BadValue, "Invalid netmask size "
+ << static_cast<unsigned>(len) << ", allowed range is 0..32");
+ }
+ uint32_t x = ~bitMask4[len];
+
+ return (IOAddress(x));
+}
+
+uint128_t
+addrsInRange(const IOAddress& min, const IOAddress& max) {
+ if (min.getFamily() != max.getFamily()) {
+ isc_throw(BadValue, "Both addresses have to be the same family");
+ }
+
+ if (max < min) {
+ isc_throw(BadValue, min.toText() << " must not be greater than "
+ << max.toText());
+ }
+
+ if (min.isV4()) {
+ // Let's explicitly cast last_ and first_ (IOAddress). This conversion is
+ // automatic, but let's explicitly cast it show that we moved to integer
+ // domain and addresses are now substractable.
+ uint64_t max_numeric = static_cast<uint64_t>(max.toUint32());
+ uint64_t min_numeric = static_cast<uint64_t>(min.toUint32());
+
+ // We can simply subtract the values. We need to increase the result
+ // by one, as both min and max are included in the range. So even if
+ // min == max, there's one address.
+ return (max_numeric - min_numeric + 1);
+ } else {
+
+ // Calculating the difference in v6 is more involved. Let's subtract
+ // one from the other. By subtracting min from max, we move the
+ // [a, b] range to the [0, (b-a)] range. We don't care about the beginning
+ // of the new range (it's always zero). The upper bound now specifies
+ // the number of addresses minus one.
+ IOAddress count = IOAddress::subtract(max, min);
+
+ // There's one very special case. Someone is trying to check how many
+ // IPv6 addresses are in IPv6 address space. He called this method
+ // with ::, ffff:ffff:ffff:fffff:ffff:ffff:ffff:ffff. The diff is also
+ // all 1s. Had we increased it by one, the address would flip to all 0s.
+ // This will not happen in a real world. Apparently, unit-tests are
+ // sometimes nastier then a real world.
+ static IOAddress max6("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+ if (count == max6) {
+ return (std::numeric_limits<uint64_t>::max());
+ }
+
+ // Increase it by one (a..a range still contains one address, even though
+ // a subtracted from a is zero).
+ count = IOAddress::increase(count);
+
+ // We don't have uint128, so for anything greater than 2^64, we'll just
+ // assume numeric_limits<uint64_t>::max. Let's do it the manual way.
+ const std::vector<uint8_t>& bin(count.toBytes());
+
+ // If any of the most significant 64 bits is set, we have more than
+ // 2^64 addresses and can't represent it even on uint64_t.
+ for (int i = 0 ; i < 8; i++) {
+ if (bin[i]) {
+ return (std::numeric_limits<uint64_t>::max());
+ }
+ }
+
+ // Ok, we're good. The pool is sanely sized. It may be huge, but at least
+ // that's something we can represent on uint64_t.
+ uint64_t numeric = 0;
+ for (int i = 8; i < 16; i++) {
+ numeric <<= 8;
+ numeric += bin[i];
+ }
+
+ return (numeric);
+ }
+}
+
+int
+prefixLengthFromRange(const IOAddress& min, const IOAddress& max) {
+ if (min.getFamily() != max.getFamily()) {
+ isc_throw(BadValue, "Both addresses have to be the same family");
+ }
+
+ if (max < min) {
+ isc_throw(BadValue, min.toText() << " must not be greater than "
+ << max.toText());
+ }
+
+ if (min.isV4()) {
+ // Get addresses as integers
+ uint32_t max_numeric = max.toUint32();
+ uint32_t min_numeric = min.toUint32();
+
+ // Get the exclusive or which must be one of the bit masks
+ // and the min must be at the beginning of the prefix
+ // so it does not contribute to trailing ones.
+ uint32_t xor_numeric = max_numeric ^ min_numeric;
+ if ((min_numeric & ~xor_numeric) != min_numeric) {
+ return (-1);
+ }
+ for (uint8_t prefix_len = 0; prefix_len <= 32; ++prefix_len) {
+ if (xor_numeric == bitMask4[prefix_len]) {
+ // Got it: the wanted value is also the index
+ return (static_cast<int>(prefix_len));
+ }
+ }
+
+ // If it was not found the range is not from a prefix / prefix_len
+ return (-1);
+ } else {
+ // Get addresses as 16 bytes
+ uint8_t min_packed[V6ADDRESS_LEN];
+ memcpy(min_packed, &min.toBytes()[0], 16);
+ uint8_t max_packed[V6ADDRESS_LEN];
+ memcpy(max_packed, &max.toBytes()[0], 16);
+
+ // Scan the exclusive or of addresses to find a difference
+ int candidate = 128;
+ bool zeroes = true;
+ for (uint8_t i = 0; i < 16; ++i) {
+ uint8_t xor_byte = min_packed[i] ^ max_packed[i];
+ // The min must be at the beginning of the prefix
+ // so it does not contribute to trailing ones.
+ if ((min_packed[i] & ~xor_byte) != min_packed[i]) {
+ return (-1);
+ }
+ if (zeroes) {
+ // Skipping zero bits searching for one bits
+ if (xor_byte == 0) {
+ continue;
+ }
+ // Found a one bit: note the fact
+ zeroes = false;
+ // Compare the exclusive or to masks
+ for (uint8_t j = 0; j < 8; ++j) {
+ if (xor_byte == revMask6[j]) {
+ // Got it the prefix length: note it
+ candidate = static_cast<int>((i * 8) + j);
+ }
+ }
+ if (candidate == 128) {
+ // Not found? The range is not from a prefix / prefix_len
+ return (-1);
+ }
+ } else {
+ // Checking that trailing bits are on bits
+ if (xor_byte == 0xff) {
+ continue;
+ }
+ // Not all ones is bad
+ return (-1);
+ }
+ }
+ return (candidate);
+ }
+}
+
+uint128_t prefixesInRange(const uint8_t pool_len, const uint8_t delegated_len) {
+ if (delegated_len < pool_len) {
+ return (0);
+ }
+
+ uint8_t const count(delegated_len - pool_len);
+
+ if (count == 128) {
+ // One off is the best we can do, unless we promote to uint256_t.
+ return uint128_t(-1);
+ }
+
+ return (uint128_t(1) << count);
+}
+
+IOAddress offsetAddress(const IOAddress& addr, uint128_t offset) {
+ // There is nothing to do if the offset is 0.
+ if (offset == 0) {
+ return (addr);
+ }
+
+ // If this is an IPv4 address, then we utilize the conversion to uint32_t.
+ if (addr.isV4()) {
+ auto addr_uint32 = static_cast<uint64_t>(addr.toUint32());
+ // If the result would exceed the maximum possible IPv4 address, let's return
+ // the maximum IPv4 address.
+ if (static_cast<uint64_t>(std::numeric_limits<uint32_t>::max() - addr_uint32) < offset) {
+ return (IOAddress(std::numeric_limits<uint32_t>::max()));
+ }
+ return (IOAddress(static_cast<uint32_t>(addr_uint32 + offset)));
+ }
+
+ // This is IPv6 address. Let's first convert the offset value to network
+ // byte order and store within the vector.
+ std::vector<uint8_t> offset_bytes(16);
+ for (int offset_idx = offset_bytes.size() - 1; offset_idx >= 0; --offset_idx) {
+ offset_bytes[offset_idx] = static_cast<uint8_t>(offset & 0xff);
+ offset = offset >> 8;
+ }
+
+ // Convert the IPv6 address to vector.
+ auto addr_bytes = addr.toBytes();
+
+ // Sum up the bytes.
+ uint16_t carry = 0;
+ for (int i = offset_bytes.size() - 1; (i >= 0); --i) {
+ // Sum the bytes of the address, offset and the carry.
+ uint16_t sum = static_cast<uint16_t>(addr_bytes[i]) + carry;
+ sum += static_cast<uint16_t>(offset_bytes[i]);
+
+ // Update the address byte.
+ addr_bytes[i] = sum % 256;
+
+ // Calculate the carry value.
+ carry = sum / 256;
+ }
+
+ // Reconstruct IPv6 address from the vector.
+ return (IOAddress::fromBytes(AF_INET6, &addr_bytes[0]));
+}
+
+}
+}
diff --git a/src/lib/asiolink/addr_utilities.h b/src/lib/asiolink/addr_utilities.h
new file mode 100644
index 0000000..87f356c
--- /dev/null
+++ b/src/lib/asiolink/addr_utilities.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2012-2023 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 ADDR_UTILITIES_H
+#define ADDR_UTILITIES_H
+
+#include <asiolink/io_address.h>
+#include <util/bigints.h>
+
+namespace isc {
+namespace asiolink {
+
+/// This code is based on similar code from the Dibbler project. I, Tomasz Mrugalski,
+/// as a sole creator of that code hereby release it under BSD license for the benefit
+/// of the Kea project.
+
+/// @brief returns a first address in a given prefix
+///
+/// Example: For 2001:db8:1\::deaf:beef and length /120 the function will return
+/// 2001:db8:1\::dead:be00. See also @ref lastAddrInPrefix.
+///
+/// @param prefix and address that belongs to a prefix
+/// @param len prefix length
+///
+/// @return first address from a prefix
+IOAddress firstAddrInPrefix(const IOAddress& prefix, uint8_t len);
+
+/// @brief returns a last address in a given prefix
+///
+/// Example: For 2001:db8:1\::deaf:beef and length /112 the function will return
+/// 2001:db8:1\::dead:ffff. See also @ref firstAddrInPrefix.
+///
+/// @param prefix and address that belongs to a prefix
+/// @param len prefix length
+///
+/// @return first address from a prefix
+IOAddress lastAddrInPrefix(const IOAddress& prefix, uint8_t len);
+
+/// @brief Generates an IPv4 netmask of specified length
+/// @throw BadValue if len is greater than 32
+/// @return netmask
+IOAddress getNetmask4(uint8_t len);
+
+
+/// @brief Returns number of available addresses in the specified range (min - max).
+///
+/// Note that for min equal max case, the number of addresses is one.
+///
+/// @throw BadValue if min and max are not of the same family (both must be
+/// either IPv4 or IPv6) or if max < min.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return number of addresses in range
+isc::util::uint128_t addrsInRange(const IOAddress& min, const IOAddress& max);
+
+/// @brief Returns prefix length from the specified range (min - max).
+///
+/// This can be considered as log2(addrsInRange)
+///
+/// @throw BadValue if min and max do not define a prefix.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return the prefix length or -1 if the range is not from a prefix
+int prefixLengthFromRange(const IOAddress& min, const IOAddress& max);
+
+/// @brief Returns number of available IPv6 prefixes in the specified prefix.
+///
+/// Note that if the answer is bigger than uint64_t can hold, it will return
+/// the max value of uint64_t type.
+///
+/// Example: prefixesInRange(48, 64) returns 65536, as there are /48 pool
+/// can be split into 65536 prefixes, each /64 large.
+///
+/// @param pool_len length of the pool in bits
+/// @param delegated_len length of the prefixes to be delegated from the pool
+/// @return number of prefixes in range
+isc::util::uint128_t prefixesInRange(const uint8_t pool_len, const uint8_t delegated_len);
+
+/// @brief Finds the address increased by offset.
+///
+/// Adds offset to the IPv4 or iPv6 address and finds the resulting address.
+/// Note that the current limitation is the maximum value of the offset,
+/// i.e. max uint64_t. If the sum of the IPv4 address and the offset exceeds
+/// the maximum value of uint32_t type, the 255.255.255.255 is returned.
+///
+/// @param addr input address
+/// @param offset distance of the returned address from the input address.
+/// @return address being offset greater than the input address
+IOAddress offsetAddress(const IOAddress& addr, isc::util::uint128_t offset);
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // ADDR_UTILITIES_H
diff --git a/src/lib/asiolink/asio_wrapper.h b/src/lib/asiolink/asio_wrapper.h
new file mode 100644
index 0000000..a33c56f
--- /dev/null
+++ b/src/lib/asiolink/asio_wrapper.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2016-2022 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 ASIO_WRAPPER_H
+#define ASIO_WRAPPER_H 1
+
+/// !!! IMPORTANT THIS IS A HACK FOR BOOST HEADERS ONLY BUILDING !!!!
+///
+/// As of #5215 (Kea 1.3) The default build configuration is to link with
+/// Boost's system library (boost_system) rather than build with Boost's
+/// headers only. Linking with the boost_system eliminates the issue as
+/// detailed below. This file exists solely for the purpose of allowing
+/// people to attempt to build headers only. ISC DOES NOT RECOMMEND
+/// building Kea with Boost headers only.
+///
+/// This file must be included anywhere one would normally have included
+/// boost/asio.hpp. Until the issue described below is resolved in some
+/// other fashion, (or we abandon support for headers only building)
+/// asio.hpp MUST NOT be included other than through this file.
+///
+/// The optimizer as of gcc 5.2.0, may not reliably ensure a single value
+/// returned by boost::system::system_category() within a translation unit
+/// when building the header only version of the boost error handling.
+/// See Trac #4243 for more details. For now we turn off optimization for
+/// header only builds the under the suspect GCC versions.
+///
+/// The issue arises from in-lining the above function, which returns a
+/// reference to a local static variable, system_category_const. This leads
+/// to situations where a construct such as the following:
+///
+/// {{{
+/// if (ec == boost::asio::error::would_block
+/// || ec == boost::asio::error::try_again)
+/// return false;
+/// }}}
+///
+/// which involve implicit conversion of enumerates to error_code instances
+/// to not evaluate correctly. During the implicit conversion the error_code
+/// instances may be assigned differing values error_code:m_cat. This
+/// causes two instances of error_code which should have been equal to
+/// to not be equal.
+///
+/// The problem disappears if either error handling code is not built header
+/// only as this results in a single definition of system_category() supplied
+/// by libboost_system; or the error handling code is not optimized.
+///
+/// We're doing the test here, rather than in configure to guard against the
+/// user supplying the header only flag via environment variables.
+///
+/// We opened bugs with GNU and BOOST:
+///
+/// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69789
+/// https://svn.boost.org/trac/boost/ticket/11989
+///
+/// @todo Version 6.0 will need to be tested.
+///
+/// As of 2016-08-19, the version 5.4.0 from Ubuntu 16.04 is affected. Updated
+/// the check to cover everything that is not 6.0, hoping that 6.0 solves the
+/// problem.
+
+// Some boost headers need the <utility> header to be included for some Boost versions under C++20.
+// Include it in all situations for simplicity.
+#include <utility>
+
+#define GNU_CC_VERSION (__GNUC__ * 10000 \
+ + __GNUC_MINOR__ * 100 \
+ + __GNUC_PATCHLEVEL__)
+
+#if (defined(__GNUC__) && \
+ ((GNU_CC_VERSION >= 50200) && (GNU_CC_VERSION < 60000)) \
+ && defined(BOOST_ERROR_CODE_HEADER_ONLY))
+#pragma GCC push_options
+#pragma GCC optimize ("O0")
+#include <boost/asio.hpp>
+#pragma GCC pop_options
+#else
+#include <boost/asio.hpp>
+#endif
+
+#endif // ASIO_WRAPPER_H
diff --git a/src/lib/asiolink/asiolink.dox b/src/lib/asiolink/asiolink.dox
new file mode 100644
index 0000000..8a74b8b
--- /dev/null
+++ b/src/lib/asiolink/asiolink.dox
@@ -0,0 +1,103 @@
+// Copyright (C) 2020-2021 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/.
+
+/**
+ @page libasiolink libkea-asiolink - Kea Boost ASIO Library
+
+@section asiolinkUtilities Boost ASIO Utilities
+
+The asiolink library (libkea-asiolink) encapsulates Boost ASIO tools:
+
+ - addr utilities: prefix (IOAddress and length pair) tools.
+
+ - dummy I/O callback.
+
+ - interval timer.
+
+ - I/O acceptor: asynchronous server ASIO socket (base class).
+
+ - I/O address: ASIO IP address.
+
+ - I/O ASIO socket (derived from I/O socket).
+
+ - I/O endpoint: ASIO IP endpoint (abstraction of a socket address).
+
+ - I/O error: @c isc::asiolink::IOError exception declaration.
+
+ - I/O service: ASIO I/O service (named I/O context in recent versions).
+
+ - I/O socket: ASIO I/O socket base class.
+
+ - TCP acceptor: TCP derivation of I/O acceptor.
+
+ - TCP endpoint: TCP derivation of I/O endpoint.
+
+ - TCP socket: TCP derivation of I/O socket.
+
+ - TLS acceptor: TLS derivation of TCP acceptor.
+
+ - TLS socket: TLS derivation of I/O socket embedding a TCP socket.
+
+ - UDP endpoint: UDP derivation of I/O endpoint.
+
+ - UDP socket: UDP derivation of I/O socket.
+
+ - Unix domain socket: Unix socket (AF_LOCAL) derivation of I/O socket.
+
+ - Unix domain acceptor: Unix socket (AF_LOCAL) derivation of I/O acceptor.
+
+ - Unix domain endpoint: Unix socket (AF_LOCAL) derivation of I/O endpoint.
+
+These tools in general use the error code (vs throwing exception) overload
+and for asynchronous methods / functions a callback taking the error code
+as its first argument.
+
+@section asiolinkTLSSupport TLS ASIO support
+
+The TLS support is an extension of the TCP one:
+
+ - the TLS acceptor is derived from the TCP acceptor.
+
+ - the TLS socket embeds a TCP socket.
+
+Basic socket operations as accept, connect, close, etc, are performed on
+the parent or embedded TCP socket.
+
+TLS adds a TLS context (TLS context objects encapsulate the TLS setup e.g.
+certificates) and two new operations:
+
+ - TLS handshake which must be called after accept or connect before
+ any read or write.
+
+ - TLS shutdown which is an optional orderly close prepare. It must not
+ be called on any kind of errors and after a TLS shutdown the TLS stream
+ can not be used: the only valid operation on it is to close it.
+
+@note TLS shows a stream of sequenced records interface but there is
+no direct mapping between high level TLS operations and TCP I/O,
+e.g. a TLS read can involve a TCP write and the opposite.
+
+@note TLS introduces a new error code "stream_truncated" which is
+the TLS short read.
+
+To debug or extend the TLS support two tools are available:
+
+ - client and server samples for both OpenSSL and Botan.
+
+ - TLS unit tests (tls_unittest.cc file).
+
+@section asiolinkMTConsiderations Multi-Threading Consideration for Boost ASIO Utilities
+
+By default Boost ASIO utilities are not thread safe even if Boost ASIO tools
+themselves are. When there is no state and the encapsulation is direct
+the thread safety property is preserved. Exceptions to the by default
+no thread safe are:
+
+ - I/O address (direct encapsulation) is thread safe.
+
+ - interval timer setup and cancel methods are thread safe.
+
+*/
diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h
new file mode 100644
index 0000000..dfe31a2
--- /dev/null
+++ b/src/lib/asiolink/asiolink.h
@@ -0,0 +1,67 @@
+// 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/.
+
+#ifndef ASIOLINK_H
+#define ASIOLINK_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_error.h>
+
+/// \namespace asiolink
+/// \brief A wrapper interface for the ASIO library.
+///
+/// The \c asiolink namespace is used to define a set of wrapper interfaces
+/// for the ASIO library.
+///
+/// Kea uses the Boost version of ASIO in its header-only mode,
+/// i.e., does not require a separate library object to be linked, and thus
+/// lowers the bar for introduction.
+///
+/// But the advantage comes with its own costs: since the header-only version
+/// includes more definitions in public header files, it tends to trigger
+/// more compiler warnings for our own sources, and, depending on the
+/// compiler options, may make the build fail.
+///
+/// We also found it may be tricky to use ASIO and standard C++ libraries
+/// in a single translation unit, i.e., a .cc file: depending on the order
+/// of including header files, ASIO may or may not work on some platforms.
+///
+/// This wrapper interface is intended to centralize these
+/// problematic issues in a single sub module. Other Kea modules should
+/// simply include \c asiolink.h and use the wrapper API instead of
+/// including ASIO header files and using ASIO-specific classes directly.
+///
+/// This wrapper may be used for other IO libraries if and when we want to
+/// switch, but generality for that purpose is not the primary goal of
+/// this module. The resulting interfaces are thus straightforward mapping
+/// to the ASIO counterparts.
+///
+/// One obvious drawback of this approach is performance overhead
+/// due to the additional layer. We should eventually evaluate the cost
+/// of the wrapper abstraction in benchmark tests. Another drawback is
+/// that the wrapper interfaces don't provide all features of ASIO
+/// (at least for the moment). We should also re-evaluate the
+/// maintenance overhead of providing necessary wrappers as we develop
+/// more.
+///
+/// On the other hand, we may be able to exploit the wrapper approach to
+/// simplify the interfaces (by limiting the usage) and unify performance
+/// optimization points.
+///
+/// As for optimization, we may want to provide a custom allocator for
+/// the placeholder of callback handlers:
+/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
+
+#endif // ASIOLINK_H
diff --git a/src/lib/asiolink/botan_boost_tls.cc b/src/lib/asiolink/botan_boost_tls.cc
new file mode 100644
index 0000000..12db353
--- /dev/null
+++ b/src/lib/asiolink/botan_boost_tls.cc
@@ -0,0 +1,339 @@
+// Copyright (C) 2021 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>
+
+/// @file botan_boost_tls.cc Botan boost ASIO implementation of the TLS API.
+
+#if defined(WITH_BOTAN) && defined(WITH_BOTAN_BOOST)
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+
+#include <botan/auto_rng.h>
+#include <botan/certstor_flatfile.h>
+#include <botan/data_src.h>
+#include <botan/pem.h>
+#include <botan/pkcs8.h>
+
+using namespace isc::cryptolink;
+
+namespace isc {
+namespace asiolink {
+
+// Classes of Kea certificate stores.
+using KeaCertificateStorePath = Botan::Certificate_Store_In_Memory;
+using KeaCertificateStoreFile = Botan::Flatfile_Certificate_Store;
+
+// Class of Kea credential managers.
+class KeaCredentialsManager : public Botan::Credentials_Manager {
+public:
+ // Constructor.
+ KeaCredentialsManager() : store_(), use_stores_(true), certs_(), key_() {
+ }
+
+ // Destructor.
+ virtual ~KeaCredentialsManager() {
+ }
+
+ // CA certificate stores.
+ // nullptr means do not require or check peer certificate.
+ std::vector<Botan::Certificate_Store*>
+ trusted_certificate_authorities(const std::string&,
+ const std::string&) override {
+ std::vector<Botan::Certificate_Store*> result;
+ if (use_stores_ && store_) {
+ result.push_back(store_.get());
+ }
+ return (result);
+ }
+
+ // Certificate chain.
+ std::vector<Botan::X509_Certificate>
+ cert_chain(const std::vector<std::string>&,
+ const std::string&,
+ const std::string&) override {
+ return (certs_);
+ }
+
+ // Private key.
+ Botan::Private_Key*
+ private_key_for(const Botan::X509_Certificate&,
+ const std::string&,
+ const std::string&) override {
+ return (key_.get());
+ }
+
+ // Set the store from a path.
+ void setStorePath(const std::string& path) {
+ store_.reset(new KeaCertificateStorePath(path));
+ }
+
+ // Set the store from a file.
+ void setStoreFile(const std::string& file) {
+ store_.reset(new KeaCertificateStoreFile(file));
+ }
+
+ // Get the use of CA certificate stores flag.
+ bool getUseStores() const {
+ return (use_stores_);
+ }
+
+ // Set the use of CA certificate stores flag.
+ void setUseStores(bool use_stores) {
+ use_stores_ = use_stores;
+ }
+
+ // Set the certificate chain.
+ void setCertChain(const std::string& file) {
+ Botan::DataSource_Stream source(file);
+ certs_.clear();
+ while (!source.end_of_data()) {
+ std::string label;
+ std::vector<uint8_t> cert;
+ try {
+ cert = unlock(Botan::PEM_Code::decode(source, label));
+ if ((label != "CERTIFICATE") &&
+ (label != "X509 CERTIFICATE") &&
+ (label != "TRUSTED CERTIFICATE")) {
+ isc_throw(LibraryError, "Expected a certificate, got '"
+ << label << "'");
+ }
+ certs_.push_back(Botan::X509_Certificate(cert));
+ } catch (const std::exception& ex) {
+ if (certs_.empty()) {
+ throw;
+ }
+ // Got one certificate so skipping garbage.
+ continue;
+ }
+ }
+ if (certs_.empty()) {
+ isc_throw(LibraryError, "Found no certificate?");
+ }
+ }
+
+ // Set the private key.
+ void setPrivateKey(const std::string& file,
+ Botan::RandomNumberGenerator& rng,
+ bool& is_rsa) {
+ key_.reset(Botan::PKCS8::load_key(file, rng));
+ if (!key_) {
+ isc_throw(Unexpected,
+ "Botan::PKCS8::load_key failed but not threw?");
+ }
+ is_rsa = (key_->algo_name() == "RSA");
+ }
+
+ // Pointer to the CA certificate store.
+ std::unique_ptr<Botan::Certificate_Store> store_;
+
+ // Use the CA certificate store flag.
+ bool use_stores_;
+
+ // The certificate chain.
+ std::vector<Botan::X509_Certificate> certs_;
+
+ // Pointer to the private key.
+ std::unique_ptr<Botan::Private_Key> key_;
+};
+
+// Class of Kea policy.
+// Use Strict_Policy?
+class KeaPolicy : public Botan::TLS::Default_Policy {
+public:
+ // Constructor.
+ KeaPolicy() : prefer_rsa_(true) {
+ }
+
+ // Destructor.
+ virtual ~KeaPolicy() {
+ }
+
+ // Allowed signature methods in preference order.
+ std::vector<std::string> allowed_signature_methods() const override {
+ if (prefer_rsa_) {
+ return (AllowedSignatureMethodsRSA);
+ } else {
+ return (AllowedSignatureMethodsECDSA);
+ }
+ }
+
+ // Disable OSCP.
+ bool require_cert_revocation_info() const override {
+ return false;
+ }
+
+ // Set the RSA preferred flag.
+ void setPrefRSA(bool prefer_rsa) {
+ prefer_rsa_ = prefer_rsa;
+ }
+
+ // Prefer RSA preferred flag.
+ bool prefer_rsa_;
+
+ // Allowed signature methods which prefers RSA.
+ static const std::vector<std::string> AllowedSignatureMethodsRSA;
+
+ // Allowed signature methods which prefers ECDSA.
+ static const std::vector<std::string> AllowedSignatureMethodsECDSA;
+};
+
+
+// Kea session manager.
+using KeaSessionManager = Botan::TLS::Session_Manager_Noop;
+
+// Allowed signature methods which prefers RSA.
+const std::vector<std::string>
+KeaPolicy::AllowedSignatureMethodsRSA = { "RSA", "DSA", "ECDSA" };
+
+// Allowed signature methods which prefers ECDSA.
+const std::vector<std::string>
+KeaPolicy::AllowedSignatureMethodsECDSA = { "ECDSA", "RSA", "DSA" };
+
+// Class of Botan TLS context implementations.
+class TlsContextImpl {
+public:
+ // Constructor.
+ TlsContextImpl() : cred_mgr_(), rng_(), sess_mgr_(), policy_() {
+ }
+
+ // Destructor.
+ virtual ~TlsContextImpl() {
+ }
+
+ // Get the peer certificate requirement mode.
+ virtual bool getCertRequired() const {
+ return (cred_mgr_.getUseStores());
+ }
+
+ // Set the peer certificate requirement mode.
+ //
+ // With Botan this means to provide or not the CA certificate stores.
+ virtual void setCertRequired(bool cert_required) {
+ cred_mgr_.setUseStores(cert_required);
+ }
+
+ // Load the trust anchor aka certificate authority (path).
+ virtual void loadCaPath(const std::string& ca_path) {
+ try {
+ cred_mgr_.setStorePath(ca_path);
+ } catch (const std::exception& ex) {
+ isc_throw(LibraryError, ex.what());
+ }
+ }
+
+ // Load the trust anchor aka certificate authority (file).
+ virtual void loadCaFile(const std::string& ca_file) {
+ try {
+ cred_mgr_.setStoreFile(ca_file);
+ } catch (const std::exception& ex) {
+ isc_throw(LibraryError, ex.what());
+ }
+ }
+
+ /// @brief Load the certificate file.
+ virtual void loadCertFile(const std::string& cert_file) {
+ try {
+ cred_mgr_.setCertChain(cert_file);
+ } catch (const std::exception& ex) {
+ isc_throw(LibraryError, ex.what());
+ }
+ }
+
+ /// @brief Load the private key file.
+ ///
+ /// As a side effect set the preference for RSA in the policy.
+ virtual void loadKeyFile(const std::string& key_file) {
+ try {
+ bool is_rsa = true;
+ cred_mgr_.setPrivateKey(key_file, rng_, is_rsa);
+ policy_.setPrefRSA(is_rsa);
+ } catch (const std::exception& ex) {
+ isc_throw(LibraryError, ex.what());
+ }
+ }
+
+ // Build the context if not yet done.
+ virtual void build() {
+ if (context_) {
+ return;
+ }
+ context_.reset(new Botan::TLS::Context(cred_mgr_,
+ rng_,
+ sess_mgr_,
+ policy_));
+ }
+
+ virtual Botan::TLS::Context& get() {
+ return (*context_);
+ }
+
+ // Credentials Manager.
+ KeaCredentialsManager cred_mgr_;
+
+ // Random Number Generator.
+ Botan::AutoSeeded_RNG rng_;
+
+ // Session Manager.
+ KeaSessionManager sess_mgr_;
+
+ KeaPolicy policy_;
+
+ std::unique_ptr<Botan::TLS::Context> context_;
+};
+
+TlsContext::~TlsContext() {
+}
+
+TlsContext::TlsContext(TlsRole role)
+ : TlsContextBase(role), impl_(new TlsContextImpl()) {
+}
+
+Botan::TLS::Context&
+TlsContext::getContext() {
+ impl_->build();
+ return (impl_->get());
+}
+
+void
+TlsContext::setCertRequired(bool cert_required) {
+ if (!cert_required && (getRole() == TlsRole::CLIENT)) {
+ isc_throw(BadValue,
+ "'cert-required' parameter must be true for a TLS client");
+ }
+ impl_->setCertRequired(cert_required);
+}
+
+bool
+TlsContext::getCertRequired() const {
+ return (impl_->getCertRequired());
+}
+
+void
+TlsContext::loadCaFile(const std::string& ca_file) {
+ impl_->loadCaFile(ca_file);
+}
+
+void
+TlsContext::loadCaPath(const std::string& ca_path) {
+ impl_->loadCaPath(ca_path);
+}
+
+void
+TlsContext::loadCertFile(const std::string& cert_file) {
+ impl_->loadCertFile(cert_file);
+}
+
+void
+TlsContext::loadKeyFile(const std::string& key_file) {
+ impl_->loadKeyFile(key_file);
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_BOTAN && WITH_BOTAN_BOOST
diff --git a/src/lib/asiolink/botan_boost_tls.h b/src/lib/asiolink/botan_boost_tls.h
new file mode 100644
index 0000000..9037ebc
--- /dev/null
+++ b/src/lib/asiolink/botan_boost_tls.h
@@ -0,0 +1,207 @@
+// Copyright (C) 2021 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/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef BOTAN_BOOST_TLS_H
+#define BOTAN_BOOST_TLS_H
+
+/// @file botan_boost_tls.h Botan boost ASIO implementation of the TLS API.
+
+#if defined(WITH_BOTAN) && defined(WITH_BOTAN_BOOST)
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/common_tls.h>
+#include <exceptions/exceptions.h>
+
+#include <asiolink/botan_boost_wrapper.h>
+#include <botan/asio_stream.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Translate TLS role into implementation.
+inline Botan::TLS::Connection_Side roleToImpl(TlsRole role) {
+ if (role == TlsRole::SERVER) {
+ return (Botan::TLS::Connection_Side::SERVER);
+ } else {
+ return (Botan::TLS::Connection_Side::CLIENT);
+ }
+}
+
+/// @brief Forward declaration of Botan TLS context.
+class TlsContextImpl;
+
+/// @brief Botan boost ASIO TLS context.
+class TlsContext : public TlsContextBase {
+public:
+
+ /// @brief Destructor.
+ ///
+ /// @note The destructor can't be defined here because a unique
+ /// pointer to an incomplete type is used.
+ virtual ~TlsContext();
+
+ /// @brief Create a fresh context.
+ ///
+ /// @param role The TLS role client or server.
+ explicit TlsContext(TlsRole role);
+
+ /// @brief Return the underlying context.
+ Botan::TLS::Context& getContext();
+
+ /// @brief Get the peer certificate requirement mode.
+ ///
+ /// @return True if peer certificates are required, false if they
+ /// are optional.
+ virtual bool getCertRequired() const;
+
+protected:
+ /// @brief Set the peer certificate requirement mode.
+ ///
+ /// @param cert_required True if peer certificates are required,
+ /// false if they are optional.
+ virtual void setCertRequired(bool cert_required);
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_file The certificate file name.
+ virtual void loadCaFile(const std::string& ca_file);
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_path The certificate directory name.
+ virtual void loadCaPath(const std::string& ca_path);
+
+ /// @brief Load the certificate file.
+ ///
+ /// @param cert_file The certificate file name.
+ virtual void loadCertFile(const std::string& cert_file);
+
+ /// @brief Load the private key from a file.
+ ///
+ /// @param key_file The private key file name.
+ virtual void loadKeyFile(const std::string& key_file);
+
+ /// @brief Botan TLS context.
+ std::unique_ptr<TlsContextImpl> impl_;
+
+ /// @brief Allow access to protected methods by the base class.
+ friend class TlsContextBase;
+};
+
+/// @brief The type of underlying TLS streams.
+typedef Botan::TLS::Stream<boost::asio::ip::tcp::socket> TlsStreamImpl;
+
+/// @brief TlsStreamBase constructor.
+///
+/// @tparam Callback The type of callbacks.
+/// @tparam TlsStreamImpl The type of underlying TLS streams.
+/// @param service I/O Service object used to manage the stream.
+/// @param context Pointer to the TLS context.
+/// @note The caller must not provide a null pointer to the TLS context.
+template <typename Callback, typename TlsStreamImpl>
+TlsStreamBase<Callback, TlsStreamImpl>::
+TlsStreamBase(IOService& service, TlsContextPtr context)
+ : TlsStreamImpl(service.get_io_service(), context->getContext()),
+ role_(context->getRole()) {
+}
+
+/// @brief Botan boost ASIO TLS stream.
+///
+/// @tparam callback The callback.
+template <typename Callback>
+class TlsStream : public TlsStreamBase<Callback, TlsStreamImpl>
+{
+public:
+
+ /// @brief Type of the base.
+ typedef TlsStreamBase<Callback, TlsStreamImpl> Base;
+
+ /// @brief Constructor.
+ ///
+ /// @param service I/O Service object used to manage the stream.
+ /// @param context Pointer to the TLS context.
+ /// @note The caller must not provide a null pointer to the TLS context.
+ TlsStream(IOService& service, TlsContextPtr context)
+ : Base(service, context) {
+ }
+
+ /// @brief Destructor.
+ virtual ~TlsStream() { }
+
+ /// @brief TLS Handshake.
+ ///
+ /// @param callback Callback object.
+ virtual void handshake(Callback& callback) {
+ Base::async_handshake(roleToImpl(Base::getRole()), callback);
+ }
+
+ /// @brief TLS shutdown.
+ ///
+ /// @param callback Callback object.
+ virtual void shutdown(Callback& callback) {
+ Base::async_shutdown(callback);
+ }
+
+ /// @brief Clear the TLS object.
+ ///
+ /// @note The idea to reuse a TCP connection for a fresh TLS is at
+ /// least arguable. Currently it does nothing so the socket is
+ /// **not** reusable.
+ virtual void clear() {
+ }
+
+ /// @brief Return the commonName part of the subjectName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// RFC 3280 provides as a commonName example "Susan Housley",
+ /// to idea to give access to this come from the Role Based
+ /// Access Control experiment.
+ ///
+ /// @return The commonName part of the subjectName or the empty string.
+ virtual std::string getSubject() {
+ const std::vector<Botan::X509_Certificate>& cert_chain =
+ Base::native_handle()->peer_cert_chain();
+ if (cert_chain.empty()) {
+ return ("");
+ }
+ const Botan::X509_DN& subject = cert_chain[0].subject_dn();
+ return (subject.get_first_attribute("CommonName"));
+ }
+
+ /// @brief Return the commonName part of the issuerName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// The issuerName is the subjectName of the signing certificate
+ /// (the issue in PKIX terms). The idea is to encode a group as
+ /// members of an intermediate certification authority.
+ ///
+ /// @return The commonName part of the issuerName or the empty string.
+ virtual std::string getIssuer() {
+ const std::vector<Botan::X509_Certificate>& cert_chain =
+ Base::native_handle()->peer_cert_chain();
+ if (cert_chain.empty()) {
+ return ("");
+ }
+ const Botan::X509_DN& issuer = cert_chain[0].issuer_dn();
+ return (issuer.get_first_attribute("CommonName"));
+ }
+};
+
+// Stream truncated error code.
+const int STREAM_TRUNCATED = Botan::TLS::StreamError::StreamTruncated;
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_BOTAN && WITH_BOTAN_BOOST
+
+#endif // BOTAN_BOOST_TLS_H
diff --git a/src/lib/asiolink/botan_boost_wrapper.h b/src/lib/asiolink/botan_boost_wrapper.h
new file mode 100644
index 0000000..e244bbb
--- /dev/null
+++ b/src/lib/asiolink/botan_boost_wrapper.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 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/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef BOTAN_BOOST_WRAPPER_H
+#define BOTAN_BOOST_WRAPPER_H
+
+/// @file botan_boost_wrapper.h Botan boost ASIO wrapper.
+
+#if defined(WITH_BOTAN) && defined(WITH_BOTAN_BOOST)
+
+/// The error classes do not define virtual destructors.
+/// This workaround is taken from the boost header.
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+#include <botan/asio_error.h>
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif // WITH_BOTAN && WITH_BOTAN_BOOST
+
+#endif // BOTAN_BOOST_WRAPPER_H
diff --git a/src/lib/asiolink/botan_tls.cc b/src/lib/asiolink/botan_tls.cc
new file mode 100644
index 0000000..3cd1818
--- /dev/null
+++ b/src/lib/asiolink/botan_tls.cc
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 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/.
+
+/// @file botan_tls.cc Botan fake implementation of the TLS API.
+
+#include <config.h>
+
+#if defined(WITH_BOTAN) && !defined(WITH_BOTAN_BOOST)
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+
+namespace isc {
+namespace asiolink {
+
+TlsContext::TlsContext(TlsRole role)
+ : TlsContextBase(role), cert_required_(true) {
+}
+
+void
+TlsContext::setCertRequired(bool cert_required) {
+ if (!cert_required && (getRole() == TlsRole::CLIENT)) {
+ isc_throw(BadValue,
+ "'cert-required' parameter must be true for a TLS client");
+ }
+ cert_required_ = cert_required;
+}
+
+bool
+TlsContext::getCertRequired() const {
+ return (cert_required_);
+}
+
+void
+TlsContext::loadCaFile(const std::string&) {
+ isc_throw(NotImplemented, "Botan TLS is not yet supported");
+}
+
+void
+TlsContext::loadCaPath(const std::string&) {
+ isc_throw(NotImplemented, "loadCaPath is not implemented by Botan");
+}
+
+void
+TlsContext::loadCertFile(const std::string&) {
+ isc_throw(NotImplemented, "Botan TLS is not yet supported");
+}
+
+void
+TlsContext::loadKeyFile(const std::string&) {
+ isc_throw(NotImplemented, "Botan TLS is not yet supported");
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_BOTAN && !WITH_BOTAN_BOOST
diff --git a/src/lib/asiolink/botan_tls.h b/src/lib/asiolink/botan_tls.h
new file mode 100644
index 0000000..1735cc7
--- /dev/null
+++ b/src/lib/asiolink/botan_tls.h
@@ -0,0 +1,168 @@
+// Copyright (C) 2021 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/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef BOTAN_TLS_H
+#define BOTAN_TLS_H
+
+/// @file botan_tls.h Botan fake implementation of the TLS API.
+
+#if defined(WITH_BOTAN) && !defined(WITH_BOTAN_BOOST)
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/common_tls.h>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Botan TLS context.
+class TlsContext : public TlsContextBase {
+public:
+
+ /// @brief Destructor.
+ virtual ~TlsContext() { }
+
+ /// @brief Create a fresh context.
+ ///
+ /// @param role The TLS role client or server.
+ explicit TlsContext(TlsRole role);
+
+ /// @brief Get the peer certificate requirement mode.
+ ///
+ /// @return True if peer certificates are required, false if they
+ /// are optional.
+ virtual bool getCertRequired() const;
+
+protected:
+ /// @brief Set the peer certificate requirement mode.
+ ///
+ /// @param cert_required True if peer certificates are required,
+ /// false if they are optional.
+ virtual void setCertRequired(bool cert_required);
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_file The certificate file name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadCaFile(const std::string& ca_file);
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_path The certificate directory name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadCaPath(const std::string& ca_path);
+
+ /// @brief Load the certificate file.
+ ///
+ /// @param cert_file The certificate file name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadCertFile(const std::string& cert_file);
+
+ /// @brief Load the private key from a file.
+ ///
+ /// @param key_file The private key file name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadKeyFile(const std::string& key_file);
+
+ /// @brief Cached cert_required value.
+ bool cert_required_;
+
+ /// @brief Allow access to protected methods by the base class.
+ friend class TlsContextBase;
+};
+
+/// @brief The type of Botan TLS streams (in fact pure TCP streams).
+typedef boost::asio::ip::tcp::socket TlsStreamImpl;
+
+/// @brief TlsStreamBase constructor.
+///
+/// @tparam Callback The type of callbacks.
+/// @tparam TlsStreamImpl The type of underlying TLS streams.
+/// @param service I/O Service object used to manage the stream.
+/// @param context Pointer to the TLS context.
+/// @note The caller must not provide a null pointer to the TLS context.
+template <typename Callback, typename TlsStreamImpl>
+TlsStreamBase<Callback, TlsStreamImpl>::
+TlsStreamBase(IOService& service, TlsContextPtr context)
+ : TlsStreamImpl(service.get_io_service()), role_(context->getRole()) {
+}
+
+/// @brief Botan fake TLS stream.
+///
+/// @tparam callback The callback.
+template <typename Callback>
+class TlsStream : public TlsStreamBase<Callback, TlsStreamImpl> {
+public:
+
+ /// @brief Type of the base.
+ typedef TlsStreamBase<Callback, TlsStreamImpl> Base;
+
+ /// @brief Constructor.
+ ///
+ /// @param service I/O Service object used to manage the stream.
+ /// @param context Pointer to the TLS context.
+ /// @note The caller must not provide a null pointer to the TLS context.
+ TlsStream(IOService& service, TlsContextPtr context)
+ : Base(service, context) {
+ }
+
+ /// @brief Destructor.
+ virtual ~TlsStream() { }
+
+ /// @brief TLS Handshake.
+ virtual void handshake(Callback&) {
+ isc_throw(NotImplemented, "Botan TLS is not yet supported");
+ }
+
+ /// @brief TLS shutdown.
+ virtual void shutdown(Callback&) {
+ isc_throw(NotImplemented, "Botan TLS is not yet supported");
+ }
+
+ /// @brief Return the commonName part of the subjectName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// RFC 3280 provides as a commonName example "Susan Housley",
+ /// to idea to give access to this come from the Role Based
+ /// Access Control experiment.
+ ///
+ ///
+ /// @return The commonName part of the subjectName or the empty string.
+ std::string getSubject() {
+ return ("");
+ }
+
+ /// @brief Return the commonName part of the issuerName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// The issuerName is the subjectName of the signing certificate
+ /// (the issue in PKIX terms). The idea is to encode a group as
+ /// members of an intermediate certification authority.
+ ///
+ ///
+ /// @return The commonName part of the issuerName or the empty string.
+ std::string getIssuer() {
+ return ("");
+ }
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_BOTAN && !WITH_BOTAN_BOOST
+
+#endif // BOTAN_TLS_H
diff --git a/src/lib/asiolink/common_tls.cc b/src/lib/asiolink/common_tls.cc
new file mode 100644
index 0000000..35ca637
--- /dev/null
+++ b/src/lib/asiolink/common_tls.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2021-2022 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/.
+
+/// @file common_tls.cc Common part of implementations of the TLS API.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+#include <util/file_utilities.h>
+
+using namespace isc::cryptolink;
+using namespace isc::util;
+
+namespace isc {
+namespace asiolink {
+
+void
+TlsContextBase::configure(TlsContextPtr& context,
+ TlsRole role,
+ const std::string& ca_file,
+ const std::string& cert_file,
+ const std::string& key_file,
+ bool cert_required) {
+ try {
+ context.reset(new TlsContext(role));
+ context->setCertRequired(cert_required);
+ if (file::isDir(ca_file)) {
+ try {
+ context->loadCaPath(ca_file);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "load of CA directory '"
+ << ca_file << "' failed: " << ex.what());
+ }
+ } else {
+ try {
+ context->loadCaFile(ca_file);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "load of CA file '"
+ << ca_file << "' failed: " << ex.what());
+ }
+ }
+ try {
+ context->loadCertFile(cert_file);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "load of cert file '"
+ << cert_file << "' failed: " << ex.what());
+ }
+ try {
+ context->loadKeyFile(key_file);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "load of private key file '"
+ << key_file << "' failed: " << ex.what());
+ }
+ } catch (...) {
+ context.reset();
+ throw;
+ }
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/common_tls.h b/src/lib/asiolink/common_tls.h
new file mode 100644
index 0000000..b119760
--- /dev/null
+++ b/src/lib/asiolink/common_tls.h
@@ -0,0 +1,183 @@
+// Copyright (C) 2021 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/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef COMMON_TLS_H
+#define COMMON_TLS_H
+
+/// @file common_tls.h Common TLS API.
+
+// Verify that this file was not directly included.
+#ifndef CRYPTO_TLS_H
+#error crypto_tls.h must be included in place of common_tls.h
+#endif
+
+#include <cryptolink/cryptolink.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Client and server roles.
+enum TlsRole { CLIENT, SERVER };
+
+/// @brief Forward declaration of backend TLS context.
+class TlsContext;
+
+/// @brief The type of shared pointers to TlsContext objects.
+typedef boost::shared_ptr<TlsContext> TlsContextPtr;
+
+/// @brief TLS context base class.
+class TlsContextBase : private boost::noncopyable {
+public:
+ /// @brief Destructor.
+ virtual ~TlsContextBase() { }
+
+ /// @brief Create a fresh context.
+ ///
+ /// @param role The TLS role client or server.
+ explicit TlsContextBase(TlsRole role) : role_(role) { }
+
+ /// @brief Returns the role.
+ TlsRole getRole() const {
+ return (role_);
+ }
+
+ /// @note No need for a role set method.
+
+ /// @brief Configure.
+ ///
+ /// @param context The TLS context to configure.
+ /// @param role The TLS role client or server.
+ /// @param ca_file The certificate file or directory name.
+ /// @param cert_file The certificate file name.
+ /// @param key_file The private key file name.
+ /// @param cert_required True if peer certificates are required,
+ /// false if they are optional. This is a server specific parameter.
+ /// @throw isc::BadValue on error.
+ static void configure(TlsContextPtr& context,
+ TlsRole role,
+ const std::string& ca_file,
+ const std::string& cert_file,
+ const std::string& key_file,
+ bool cert_required = true);
+
+ /// @brief Get the peer certificate requirement mode.
+ ///
+ /// @return True if peer certificates are required, false if they
+ /// are optional.
+ virtual bool getCertRequired() const = 0;
+
+protected:
+ /// @brief Set the peer certificate requirement mode.
+ ///
+ /// @param cert_required True if peer certificates are required,
+ /// false if they are optional.
+ /// @throw isc::BadValue when cert_required is set to false for a client.
+ virtual void setCertRequired(bool cert_required) = 0;
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_file The certificate file name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadCaFile(const std::string& ca_file) = 0;
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_path The certificate directory name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadCaPath(const std::string& ca_path) = 0;
+
+ /// @brief Load the certificate file.
+ ///
+ /// @param cert_file The certificate file name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadCertFile(const std::string& cert_file) = 0;
+
+ /// @brief Load the private key from a file.
+ ///
+ /// @param key_file The private key file name.
+ /// @throw isc::cryptolink::LibraryError on various errors as
+ /// file not found, bad format, etc.
+ virtual void loadKeyFile(const std::string& key_file) = 0;
+
+public:
+ /// @brief The role i.e. client or server.
+ TlsRole role_;
+};
+
+/// @brief TLS stream base class.
+///
+/// @tparam Callback The type of callbacks.
+/// @tparam TlsStreamImpl The type of underlying TLS streams.
+template <typename Callback, typename TlsStreamImpl>
+class TlsStreamBase : public TlsStreamImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param service I/O Service object used to manage the stream.
+ /// @param context Pointer to the TLS context.
+ /// @note The caller must not provide a null pointer to the TLS context.
+ TlsStreamBase(IOService& service, TlsContextPtr context);
+
+ /// @brief Destructor.
+ virtual ~TlsStreamBase() { }
+
+ /// @brief Returns the role.
+ TlsRole getRole() const {
+ return (role_);
+ }
+
+ /// @brief TLS Handshake.
+ ///
+ /// @param callback Callback object.
+ virtual void handshake(Callback& callback) = 0;
+
+ /// @brief TLS shutdown.
+ ///
+ /// @param callback Callback object.
+ virtual void shutdown(Callback& callback) = 0;
+
+ /// @brief Return the commonName part of the subjectName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// RFC 3280 provides as a commonName example "Susan Housley",
+ /// to idea to give access to this come from the Role Based
+ /// Access Control experiment.
+ ///
+ /// @return The commonName part of the subjectName or the empty string.
+ virtual std::string getSubject() = 0;
+
+ /// @brief Return the commonName part of the issuerName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// The issuerName is the subjectName of the signing certificate
+ /// (the issue in PKIX terms). The idea is to encode a group as
+ /// members of an intermediate certification authority.
+ ///
+ /// @return The commonName part of the issuerName or the empty string.
+ virtual std::string getIssuer() = 0;
+
+ /// @brief The role i.e. client or server.
+ TlsRole role_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // COMMON_TLS_H
diff --git a/src/lib/asiolink/crypto_tls.h b/src/lib/asiolink/crypto_tls.h
new file mode 100644
index 0000000..c3e899f
--- /dev/null
+++ b/src/lib/asiolink/crypto_tls.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2021 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 CRYPTO_TLS_H
+#define CRYPTO_TLS_H
+
+/// @file crypto_tls.h TLS API.
+
+// Verify that config.h was included.
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before crypto_tls.h
+#endif
+
+// Include different versions.
+#include <asiolink/botan_boost_tls.h>
+#include <asiolink/botan_tls.h>
+#include <asiolink/openssl_tls.h>
+
+// Verify that one version matched.
+#ifndef COMMON_TLS_H
+#error no TLS backend was found
+#endif
+
+#endif // CRYPTO_TLS_H
diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h
new file mode 100644
index 0000000..795fad9
--- /dev/null
+++ b/src/lib/asiolink/dummy_io_cb.h
@@ -0,0 +1,65 @@
+// 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/.
+
+#ifndef DUMMY_IO_CB_H
+#define DUMMY_IO_CB_H
+
+#include <iostream>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/asio/error.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief Asynchronous I/O Completion Callback
+///
+/// The two socket classes (UDPSocket and TCPSocket) require that the I/O
+/// completion callback function have an operator() method with the appropriate
+/// signature. The classes are templates, any class with that method and
+/// signature can be passed as the callback object - there is no need for a
+/// base class defining the interface. However, some users of the socket
+/// classes do not use the asynchronous I/O operations, yet have to supply a
+/// template parameter. This is the reason for this class - it is the dummy
+/// template parameter.
+
+class DummyIOCallback {
+public:
+
+ /// \brief Asynchronous I/O callback method
+ ///
+ /// Should never be called, as this class is a convenience class provided
+ /// for instances where a socket is required but it is known that no
+ /// asynchronous operations will be carried out.
+ void operator()(boost::system::error_code) {
+ // If the function is called, there is a serious logic error in
+ // the program (this class should not be used as the callback
+ // class). As the asiolink module is too low-level for logging
+ // errors, throw an exception.
+ isc_throw(isc::Unexpected,
+ "DummyIOCallback::operator() must not be called");
+ }
+
+ /// \brief Asynchronous I/O callback method
+ ///
+ /// Should never be called, as this class is a convenience class provided
+ /// for instances where a socket is required but it is known that no
+ /// asynchronous operations will be carried out.
+ void operator()(boost::system::error_code, size_t) {
+ // If the function is called, there is a serious logic error in
+ // the program (this class should not be used as the callback
+ // class). As the asiolink module is too low-level for logging
+ // errors, throw an exception.
+ isc_throw(isc::Unexpected,
+ "DummyIOCallback::operator() must not be called");
+ }
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // DUMMY_IO_CB_H
diff --git a/src/lib/asiolink/interval_timer.cc b/src/lib/asiolink/interval_timer.cc
new file mode 100644
index 0000000..18088cb
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.cc
@@ -0,0 +1,202 @@
+// Copyright (C) 2011-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <atomic>
+#include <functional>
+#include <mutex>
+
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// This class holds a call back function of asynchronous operations.
+/// To ensure the object is alive while an asynchronous operation refers
+/// to it, we use shared_ptr and enable_shared_from_this.
+/// The object will be destructed in case IntervalTimer has been destructed
+/// and no asynchronous operation refers to it.
+/// Please follow the link to get an example:
+/// http://think-async.com/asio/asio-1.4.8/doc/asio/tutorial/tutdaytime3.html#asio.tutorial.tutdaytime3.the_tcp_connection_class
+class IntervalTimerImpl :
+ public boost::enable_shared_from_this<IntervalTimerImpl>,
+ public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service The IO service used to handle events.
+ IntervalTimerImpl(IOService& io_service);
+
+ /// @brief Destructor.
+ ~IntervalTimerImpl();
+
+ /// @brief Setup function to register callback and start timer.
+ ///
+ /// @param cbfunc The callback function registered on timer.
+ /// @param interval The interval used to start the timer.
+ /// @param interval_mode The interval mode used by the timer.
+ void setup(const IntervalTimer::Callback& cbfunc, const long interval,
+ const IntervalTimer::Mode& interval_mode
+ = IntervalTimer::REPEATING);
+
+ /// @brief Callback function which calls the registerd callback.
+ ///
+ /// @param error The error code retrieved from the timer.
+ void callback(const boost::system::error_code& error);
+
+ /// @brief Cancel timer.
+ void cancel() {
+ lock_guard<mutex> lk (mutex_);
+ timer_.cancel();
+ interval_ = 0;
+ cbfunc_ = std::function<void()>();
+ }
+
+ /// @brief Get the timer interval.
+ ///
+ /// @return The timer interval.
+ long getInterval() const { return (interval_); }
+
+private:
+
+ /// @brief Update function to update timer_ when it expires.
+ ///
+ /// Should be called in a thread safe context.
+ void update();
+
+ /// @brief The callback function to call when timer_ expires.
+ IntervalTimer::Callback cbfunc_;
+
+ /// @brief The interval in milliseconds.
+ std::atomic<long> interval_;
+
+ /// @brief The asio timer.
+ boost::asio::deadline_timer timer_;
+
+ /// @brief Controls how the timer behaves after expiration.
+ IntervalTimer::Mode mode_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+
+ /// @brief Invalid interval value.
+ ///
+ /// @ref interval_ will be set to this value in destructor in order to
+ /// detect use-after-free type of bugs.
+ static const long INVALIDATED_INTERVAL = -1;
+};
+
+IntervalTimerImpl::IntervalTimerImpl(IOService& io_service) :
+ interval_(0), timer_(io_service.get_io_service()),
+ mode_(IntervalTimer::REPEATING) {
+}
+
+IntervalTimerImpl::~IntervalTimerImpl() {
+ interval_ = INVALIDATED_INTERVAL;
+}
+
+void
+IntervalTimerImpl::setup(const IntervalTimer::Callback& cbfunc,
+ const long interval,
+ const IntervalTimer::Mode& mode) {
+ // Interval should not be less than 0.
+ if (interval < 0) {
+ isc_throw(isc::BadValue, "Interval should not be less than or "
+ "equal to 0");
+ }
+ // Call back function should not be empty.
+ if (!cbfunc) {
+ isc_throw(isc::InvalidParameter, "Callback function is empty");
+ }
+
+ lock_guard<mutex> lk(mutex_);
+ cbfunc_ = cbfunc;
+ interval_ = interval;
+ mode_ = mode;
+
+ // Set initial expire time.
+ // At this point the timer is not running yet and will not expire.
+ // After calling IOService::run(), the timer will expire.
+ update();
+}
+
+void
+IntervalTimerImpl::update() {
+ try {
+ // Update expire time to (current time + interval_).
+ timer_.expires_from_now(boost::posix_time::millisec(long(interval_)));
+ // Reset timer.
+ // Pass a function bound with a shared_ptr to this.
+ timer_.async_wait(std::bind(&IntervalTimerImpl::callback,
+ shared_from_this(),
+ ph::_1)); //error
+ } catch (const boost::system::system_error& e) {
+ isc_throw(isc::Unexpected, "Failed to update timer: " << e.what());
+ } catch (const boost::bad_weak_ptr&) {
+ // Can't happen. It means a severe internal bug.
+ }
+}
+
+void
+IntervalTimerImpl::callback(const boost::system::error_code& ec) {
+ if (interval_ == INVALIDATED_INTERVAL) {
+ isc_throw(isc::BadValue, "Interval internal state");
+ }
+ if (interval_ == 0 || ec) {
+ // timer has been canceled. Do nothing.
+ } else {
+ {
+ lock_guard<mutex> lk(mutex_);
+ // If we should repeat, set next expire time.
+ if (mode_ == IntervalTimer::REPEATING) {
+ update();
+ }
+ }
+
+ // Invoke the call back function.
+ cbfunc_();
+ }
+}
+
+IntervalTimer::IntervalTimer(IOService& io_service) :
+ impl_(new IntervalTimerImpl(io_service)) {
+}
+
+IntervalTimer::~IntervalTimer() {
+ // Cancel the timer to make sure cbfunc_() will not be called any more.
+ cancel();
+}
+
+void
+IntervalTimer::setup(const Callback& cbfunc, const long interval,
+ const IntervalTimer::Mode& mode) {
+ return (impl_->setup(cbfunc, interval, mode));
+}
+
+void
+IntervalTimer::cancel() {
+ impl_->cancel();
+}
+
+long
+IntervalTimer::getInterval() const {
+ return (impl_->getInterval());
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/interval_timer.h b/src/lib/asiolink/interval_timer.h
new file mode 100644
index 0000000..5dc8b71
--- /dev/null
+++ b/src/lib/asiolink/interval_timer.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2011-2020 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 ASIOLINK_INTERVAL_TIMER_H
+#define ASIOLINK_INTERVAL_TIMER_H 1
+
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+#include <asiolink/io_service.h>
+
+namespace isc {
+namespace asiolink {
+
+class IntervalTimerImpl;
+
+/// \brief The \c IntervalTimer class is a wrapper for the ASIO
+/// \c boost::asio::deadline_timer class.
+///
+/// This class is implemented to use \c boost::asio::deadline_timer as interval
+/// timer.
+///
+/// \c setup() sets a timer to expire on (now + interval), a call back
+/// function, and an interval mode.
+///
+/// \c IntervalTimerImpl::callback() is called by the timer when it expires.
+///
+/// The function calls the call back function set by \c setup() and if the
+/// the interval mode indicates a repeating interval, will reschedule the
+/// timer to expire in (now + interval) milliseconds.
+///
+/// The type of call back function is \c void(void).
+///
+/// The call back function will not be called if the instance of this class is
+/// destroyed before the timer is expired.
+///
+/// Sample code:
+/// \code
+/// void function_to_call_back() {
+/// // this function will be called periodically
+/// }
+/// int interval_in_milliseconds = 1000;
+/// IOService io_service;
+///
+/// IntervalTimer intervalTimer(io_service);
+/// intervalTimer.setup(function_to_call_back, interval_in_milliseconds);
+/// io_service.run();
+/// \endcode
+class IntervalTimer {
+public:
+ /// \name The type of timer callback function
+ typedef std::function<void()> Callback;
+
+ /// \brief Defines possible timer modes used to setup a timer.
+ /// - REPEATING - Timer will reschedule itself after each expiration
+ /// - ONE_SHOT - Timer will expire after one interval and not reschedule.
+ enum Mode
+ {
+ REPEATING,
+ ONE_SHOT
+ };
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IntervalTimer(const IntervalTimer& source);
+ IntervalTimer& operator=(const IntervalTimer& source);
+public:
+ /// \brief The constructor with \c IOService.
+ ///
+ /// This constructor may throw a standard exception if
+ /// memory allocation fails inside the method.
+ /// This constructor may also throw \c boost::system::system_error.
+ ///
+ /// \param io_service A reference to an instance of IOService
+ IntervalTimer(IOService& io_service);
+
+ /// \brief The destructor.
+ ///
+ /// This destructor never throws an exception.
+ ///
+ /// On the destruction of this class the timer will be canceled
+ /// inside \c boost::asio::deadline_timer.
+ ~IntervalTimer();
+ //@}
+
+ /// \brief Register timer callback function and interval.
+ ///
+ /// This function sets callback function and interval in milliseconds.
+ /// Timer will actually start after calling \c IOService::run().
+ ///
+ /// \param cbfunc A reference to a function \c void(void) to call back
+ /// when the timer is expired (should not be an empty functor)
+ /// \param interval Interval in milliseconds (greater than 0)
+ /// \param mode Determines if the timer will automatically reschedule after
+ /// each expiration (the default) or behave as a one-shot which will run
+ /// for a single interval and not reschedule.
+ ///
+ /// Note: IntervalTimer will not pass \c boost::system::error_code to
+ /// call back function. In case the timer is canceled, the function
+ /// will not be called.
+ ///
+ /// \throw isc::InvalidParameter cbfunc is empty
+ /// \throw isc::BadValue interval is less than or equal to 0
+ /// \throw isc::Unexpected internal runtime error
+ void setup(const Callback& cbfunc, const long interval,
+ const Mode& mode = REPEATING);
+
+ /// Cancel the timer.
+ ///
+ /// If the timer has been set up, this method cancels any asynchronous
+ /// events waiting on the timer and stops the timer itself.
+ /// If the timer has already been canceled, this method effectively does
+ /// nothing.
+ ///
+ /// This method never throws an exception.
+ void cancel();
+
+ /// Return the timer interval.
+ ///
+ /// This method returns the timer interval in milliseconds if it's running;
+ /// if the timer has been canceled it returns 0.
+ ///
+ /// This method never throws an exception.
+ long getInterval() const;
+
+private:
+ boost::shared_ptr<IntervalTimerImpl> impl_;
+};
+
+typedef boost::shared_ptr<isc::asiolink::IntervalTimer> IntervalTimerPtr;
+
+} // namespace asiolink
+} // namespace isc
+#endif // ASIOLINK_INTERVAL_TIMER_H
diff --git a/src/lib/asiolink/io_acceptor.h b/src/lib/asiolink/io_acceptor.h
new file mode 100644
index 0000000..eab0d83
--- /dev/null
+++ b/src/lib/asiolink/io_acceptor.h
@@ -0,0 +1,136 @@
+// Copyright (C) 2017-2018 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 IO_ACCEPTOR_H
+#define IO_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Base class for acceptor services in Kea.
+///
+/// This is a wrapper class for ASIO acceptor service. Classes implementing
+/// services for specific protocol types should derive from this class.
+///
+/// Acceptor is an IO object which accepts incoming connections into a socket
+/// object. This socket is then used for data transmission from the client
+/// to server and back. The acceptor is continued to be used to accept new
+/// connections while the accepted connection is active.
+///
+/// @tparam ProtocolType ASIO protocol type, e.g. stream_protocol
+/// @tparam CallbackType Callback function type which should have the following
+/// signature: @c void(const boost::system::error_code&).
+template<typename ProtocolType, typename CallbackType>
+class IOAcceptor : public IOSocket {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit IOAcceptor(IOService& io_service)
+ : IOSocket(),
+ acceptor_(new typename ProtocolType::acceptor(io_service.get_io_service())) {
+ }
+
+ /// @brief Destructor.
+ virtual ~IOAcceptor() { }
+
+ /// @brief Returns file descriptor of the underlying socket.
+ virtual int getNative() const {
+#if BOOST_VERSION < 106600
+ return (acceptor_->native());
+#else
+ return (acceptor_->native_handle());
+#endif
+ }
+
+ /// @brief Opens acceptor socket given the endpoint.
+ ///
+ /// @param endpoint Reference to the endpoint object defining local
+ /// acceptor endpoint.
+ ///
+ /// @tparam EndpointType Endpoint type.
+ template<typename EndpointType>
+ void open(const EndpointType& endpoint) {
+ acceptor_->open(endpoint.getASIOEndpoint().protocol());
+ }
+
+ /// @brief Binds socket to an endpoint.
+ ///
+ /// @param endpoint Reference to the endpoint object defining local
+ /// acceptor endpoint.
+ ///
+ /// @tparam EndpointType Endpoint type.
+ template<typename EndpointType>
+ void bind(const EndpointType& endpoint) {
+ acceptor_->bind(endpoint.getASIOEndpoint());
+ }
+
+ /// @brief Sets socket option.
+ ///
+ /// @param socket_option Reference to the object encapsulating an option to
+ /// be set for the socket.
+ /// @tparam SettableSocketOption Type of the object encapsulating socket option
+ /// being set.
+ template<typename SettableSocketOption>
+ void setOption(const SettableSocketOption& socket_option) {
+ acceptor_->set_option(socket_option);
+ }
+
+ /// @brief Starts listening new connections.
+ void listen() {
+ acceptor_->listen();
+ }
+
+ /// @brief Checks if the acceptor is open.
+ ///
+ /// @return true if acceptor is open.
+ bool isOpen() const {
+ return (acceptor_->is_open());
+ }
+
+ /// @brief Closes the acceptor.
+ void close() const {
+ acceptor_->close();
+ }
+
+protected:
+
+ /// @brief Asynchronously accept new connection.
+ ///
+ /// This method accepts new connection into the specified socket. When the
+ /// new connection arrives or an error occurs the specified callback
+ /// function is invoked.
+ ///
+ /// @param socket Socket into which connection should be accepted.
+ /// @param callback Callback function to be invoked when the new connection
+ /// arrives.
+ /// @tparam SocketType Socket type, e.g. @ref UnixDomainSocket. It must
+ /// implement @c getASIOSocket method.
+ template<typename SocketType>
+ void asyncAcceptInternal(const SocketType& socket,
+ const CallbackType& callback) {
+ acceptor_->async_accept(socket.getASIOSocket(), callback);
+ }
+
+
+ /// @brief Underlying ASIO acceptor implementation.
+ boost::shared_ptr<typename ProtocolType::acceptor> acceptor_;
+
+};
+
+
+} // end of namespace asiolink
+} // end of isc
+
+#endif // IO_ACCEPTOR_H
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
new file mode 100644
index 0000000..b17ec63
--- /dev/null
+++ b/src/lib/asiolink/io_address.cc
@@ -0,0 +1,194 @@
+// Copyright (C) 2010-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/static_assert.hpp>
+// moved to container_hash on recent boost versions (backward compatible)
+#include <boost/functional/hash.hpp>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <stdint.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+using namespace boost::asio;
+using boost::asio::ip::udp;
+using boost::asio::ip::tcp;
+
+using namespace std;
+
+namespace isc {
+namespace asiolink {
+
+size_t
+IOAddress::Hash::operator()(const IOAddress &io_address) const {
+ return (hash_value(io_address));
+}
+
+// XXX: we cannot simply construct the address in the initialization list,
+// because we'd like to throw our own exception on failure.
+IOAddress::IOAddress(const std::string& address_str) {
+ boost::system::error_code err;
+ asio_address_ = ip::address::from_string(address_str, err);
+ if (err) {
+ isc_throw(IOError, "Failed to convert string to address '"
+ << address_str << "': " << err.message());
+ }
+}
+
+IOAddress::IOAddress(const boost::asio::ip::address& asio_address) :
+ asio_address_(asio_address)
+{}
+
+IOAddress::IOAddress(uint32_t v4address):
+ asio_address_(boost::asio::ip::address_v4(v4address)) {
+
+}
+
+string
+IOAddress::toText() const {
+ return (asio_address_.to_string());
+}
+
+IOAddress
+IOAddress::fromBytes(short family, const uint8_t* data) {
+ if (data == NULL) {
+ isc_throw(BadValue, "NULL pointer received.");
+ } else
+ if ( (family != AF_INET) && (family != AF_INET6) ) {
+ isc_throw(BadValue, "Invalid family type. Only AF_INET and AF_INET6"
+ << "are supported");
+ }
+
+ BOOST_STATIC_ASSERT(INET6_ADDRSTRLEN >= INET_ADDRSTRLEN);
+ char addr_str[INET6_ADDRSTRLEN];
+ inet_ntop(family, data, addr_str, INET6_ADDRSTRLEN);
+ return IOAddress(string(addr_str));
+}
+
+std::vector<uint8_t>
+IOAddress::toBytes() const {
+ if (asio_address_.is_v4()) {
+ const boost::asio::ip::address_v4::bytes_type bytes4 =
+ asio_address_.to_v4().to_bytes();
+ return (std::vector<uint8_t>(bytes4.begin(), bytes4.end()));
+ }
+
+ // Not V4 address, so must be a V6 address (else we could never construct
+ // this object).
+ const boost::asio::ip::address_v6::bytes_type bytes6 =
+ asio_address_.to_v6().to_bytes();
+ return (std::vector<uint8_t>(bytes6.begin(), bytes6.end()));
+}
+
+short
+IOAddress::getFamily() const {
+ if (asio_address_.is_v4()) {
+ return (AF_INET);
+ } else {
+ return (AF_INET6);
+ }
+}
+
+bool
+IOAddress::isV6LinkLocal() const {
+ if (!asio_address_.is_v6()) {
+ return (false);
+ }
+ return (asio_address_.to_v6().is_link_local());
+}
+
+bool
+IOAddress::isV6Multicast() const {
+ if (!asio_address_.is_v6()) {
+ return (false);
+ }
+ return (asio_address_.to_v6().is_multicast());
+}
+
+uint32_t
+IOAddress::toUint32() const {
+ if (asio_address_.is_v4()) {
+ return (asio_address_.to_v4().to_ulong());
+ } else {
+ isc_throw(BadValue, "Can't convert " << toText()
+ << " address to IPv4.");
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const IOAddress& address) {
+ os << address.toText();
+ return (os);
+}
+
+IOAddress
+IOAddress::subtract(const IOAddress& a, const IOAddress& b) {
+ if (a.getFamily() != b.getFamily()) {
+ isc_throw(BadValue, "Both addresses have to be the same family");
+ }
+ if (a.isV4()) {
+ // Subtracting v4 is easy. We have a conversion function to uint32_t.
+ return (IOAddress(a.toUint32() - b.toUint32()));
+ } else {
+ // v6 is more involved.
+
+ // Let's extract the raw data first.
+ vector<uint8_t> a_vec = a.toBytes();
+ vector<uint8_t> b_vec = b.toBytes();
+
+ // ... and prepare the result
+ vector<uint8_t> result(V6ADDRESS_LEN,0);
+
+ // Carry is a boolean, but to avoid its frequent casting, let's
+ // use uint8_t. Also, some would prefer to call it borrow, but I prefer
+ // carry. Somewhat relevant discussion here:
+ // http://en.wikipedia.org/wiki/Carry_flag#Carry_flag_vs._Borrow_flag
+ uint8_t carry = 0;
+
+ // Now perform subtraction with borrow.
+ for (int i = a_vec.size() - 1; i >= 0; --i) {
+ result[i] = a_vec[i] - b_vec[i] - carry;
+ carry = (a_vec[i] < b_vec[i] + carry);
+ }
+
+ return (fromBytes(AF_INET6, &result[0]));
+ }
+}
+
+IOAddress
+IOAddress::increase(const IOAddress& addr) {
+ std::vector<uint8_t> packed(addr.toBytes());
+
+ // Start increasing the least significant byte
+ for (int i = packed.size() - 1; i >= 0; --i) {
+ // if we haven't overflowed (0xff -> 0x0), than we are done
+ if (++packed[i] != 0) {
+ break;
+ }
+ }
+
+ return (IOAddress::fromBytes(addr.getFamily(), &packed[0]));
+}
+
+size_t
+hash_value(const IOAddress& address) {
+ if (address.isV4()) {
+ boost::hash<uint32_t> hasher;
+ return (hasher(address.toUint32()));
+ } else {
+ boost::hash<std::vector<uint8_t> > hasher;
+ return (hasher(address.toBytes()));
+ }
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
new file mode 100644
index 0000000..d08753b
--- /dev/null
+++ b/src/lib/asiolink/io_address.h
@@ -0,0 +1,329 @@
+// Copyright (C) 2010-2023 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 IO_ADDRESS_H
+#define IO_ADDRESS_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+#include <stdint.h> // for uint32_t
+#include <boost/asio/ip/address.hpp>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+ /// Defines length of IPv6 address (in binary format).
+ static constexpr size_t V6ADDRESS_LEN = 16;
+
+ /// Defines length of IPv4 address (in binary format).
+ static constexpr size_t V4ADDRESS_LEN = 4;
+
+ /// @brief Maximum size of an IPv4 address represented as a text string. 12
+ /// digits plus 3 full stops (dots).
+ static constexpr size_t V4ADDRESS_TEXT_MAX_LEN = 15u;
+
+ /// @brief Maximum size of an IPv6 address represented as a text string. 32
+ /// hexadecimal characters written in 8 groups of four, plus 7 colon
+ /// separators.
+ static constexpr size_t V6ADDRESS_TEXT_MAX_LEN = 39u;
+
+/// \brief The \c IOAddress class represents an IP addresses (version
+/// agnostic)
+///
+/// This class is a wrapper for the ASIO \c ip::address class.
+class IOAddress {
+public:
+
+ /// \brief An \c IOAddress hash enabling the use in the unordered
+ /// STL containers.
+ struct Hash {
+ /// \brief A hashing operator.
+ ///
+ /// \param io_address an address to be hashed.
+ /// \return a hashing result.
+ size_t operator()(const IOAddress &io_address) const;
+ };
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// This class is copyable. We use default versions of copy constructor
+ /// and the assignment operator.
+ /// We use the default destructor.
+ //@{
+ /// \brief Constructor from string.
+ ///
+ /// This constructor converts a textual representation of IPv4 and IPv6
+ /// addresses into an IOAddress object.
+ /// If \c address_str is not a valid representation of any type of
+ /// address, an exception of class \c IOError will be thrown.
+ /// This constructor allocates memory for the object, and if that fails
+ /// a corresponding standard exception will be thrown.
+ ///
+ /// \param address_str Textual representation of address.
+ IOAddress(const std::string& address_str);
+
+ /// \brief Constructor from an ASIO \c ip::address object.
+ ///
+ /// This constructor is intended to be used within the wrapper
+ /// implementation; user applications of the wrapper API won't use it.
+ ///
+ /// This constructor never throws an exception.
+ ///
+ /// \param asio_address The ASIO \c ip::address to be converted.
+ IOAddress(const boost::asio::ip::address& asio_address);
+ //@}
+
+ /// @brief Constructor for ip::address_v4 object.
+ ///
+ /// This constructor is intended to be used when constructing
+ /// IPv4 address out of uint32_t type. Passed value must be in
+ /// network byte order
+ ///
+ /// @param v4address IPv4 address represented by uint32_t
+ IOAddress(uint32_t v4address);
+
+ /// \brief Convert the address to a string.
+ ///
+ /// This method is basically expected to be exception free, but
+ /// generating the string will involve resource allocation,
+ /// and if it fails the corresponding standard exception will be thrown.
+ ///
+ /// \return A string representation of the address.
+ std::string toText() const;
+
+ /// \brief Returns the address family
+ ///
+ /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
+ short getFamily() const;
+
+ /// \brief Convenience function to check for an IPv4 address
+ ///
+ /// \return true if the address is a V4 address
+ bool isV4() const {
+ return (asio_address_.is_v4());
+ }
+
+ /// \brief Convenience function to check if it is an IPv4 zero address.
+ ///
+ /// \return true if the address is the zero IPv4 address.
+ bool isV4Zero() const {
+ return (equals(IPV4_ZERO_ADDRESS()));
+ }
+
+ /// \brief Convenience function to check if it is an IPv4 broadcast
+ /// address.
+ ///
+ /// \return true if the address is the broadcast IPv4 address.
+ bool isV4Bcast() const {
+ return (equals(IPV4_BCAST_ADDRESS()));
+ }
+
+ /// \brief Convenience function to check for an IPv6 address
+ ///
+ /// \return true if the address is a V6 address
+ bool isV6() const {
+ return (asio_address_.is_v6());
+ }
+
+ /// \brief Convenience function to check if it is an IPv4 zero address.
+ ///
+ /// \return true if the address is the zero IPv4 address.
+ bool isV6Zero() const {
+ return (equals(IPV6_ZERO_ADDRESS()));
+ }
+
+ /// \brief checks whether and address is IPv6 and is link-local
+ ///
+ /// \return true if the address is IPv6 link-local, false otherwise
+ bool isV6LinkLocal() const;
+
+ /// \brief checks whether and address is IPv6 and is multicast
+ ///
+ /// \return true if the address is IPv6 multicast, false otherwise
+ bool isV6Multicast() const;
+
+ /// \brief Creates an address from over wire data.
+ ///
+ /// \param family AF_INET for IPv4 or AF_INET6 for IPv6.
+ /// \param data pointer to first char of data
+ ///
+ /// \return Created IOAddress object
+ static IOAddress fromBytes(short family, const uint8_t* data);
+
+ /// \brief Return address as set of bytes
+ ///
+ /// \return Contents of the address as a set of bytes in network-byte
+ /// order.
+ std::vector<uint8_t> toBytes() const;
+
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool equals(const IOAddress& other) const {
+ return (asio_address_ == other.asio_address_);
+ }
+
+ /// \brief Compare addresses for equality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return true if addresses are equal, false if not.
+ bool operator==(const IOAddress& other) const {
+ return equals(other);
+ }
+
+ /// \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool nequals(const IOAddress& other) const {
+ return (!equals(other));
+ }
+
+ /// \brief Checks if one address is smaller than the other
+ ///
+ /// \param other Address to compare against.
+ bool operator<(const IOAddress& other) const {
+ return (asio_address_ < other.asio_address_);
+ }
+
+ /// \brief Checks if one address is smaller or equal than the other
+ ///
+ /// \param other Address to compare against.
+ bool operator<=(const IOAddress& other) const {
+ return (asio_address_ <= other.asio_address_);
+ }
+
+ /// \brief Compare addresses for inequality
+ ///
+ /// \param other Address to compare against.
+ ///
+ /// \return false if addresses are equal, true if not.
+ bool operator!=(const IOAddress& other) const {
+ return (nequals(other));
+ }
+
+ /// @brief Subtracts one address from another (a - b)
+ ///
+ /// Treats addresses as integers and subtracts them. For example:
+ /// @code
+ /// 192.0.2.5 - 192.0.2.0 = 0.0.0.5
+ /// fe80::abcd - fe80:: = ::abcd
+ /// @endcode
+ ///
+ /// It is possible to subtract greater from lesser address, e.g.
+ /// 192.168.56.10 - 192.168.67.20, but please do understand that
+ /// the address space is a finite field in mathematical sense, so
+ /// you may end up with a result that is greater then any of the
+ /// addresses you specified. Also, subtraction is not commutative,
+ /// so a - b != b - a.
+ ///
+ /// This operation is essential for calculating the number of
+ /// leases in a pool, where we need to calculate (max - min).
+ /// @throw BadValue if addresses are of different family
+ /// @param a address to be subtracted from
+ /// @param b address to be subtracted
+ /// @return IOAddress object that represents the difference
+ static IOAddress subtract(const IOAddress& a, const IOAddress& b);
+
+ /// @brief Returns an address increased by one
+ ///
+ /// This method works for both IPv4 and IPv6 addresses. For example,
+ /// increase 192.0.2.255 will become 192.0.3.0.
+ ///
+ /// Address space is a finite field in the mathematical sense, so keep
+ /// in mind that the address space "loops". 255.255.255.255 increased
+ /// by one gives 0.0.0.0. The same is true for maximum value of IPv6
+ /// (all 1's) looping to ::.
+ ///
+ /// @todo Determine if we have a use-case for increasing the address
+ /// by more than one. Increase by one is used in AllocEngine. This method
+ /// could take extra parameter that specifies the value by which the
+ /// address should be increased.
+ ///
+ /// @param addr address to be increased
+ /// @return address increased by one
+ static IOAddress
+ increase(const IOAddress& addr);
+
+ /// \brief Converts IPv4 address to uint32_t
+ ///
+ /// Will throw BadValue exception if that is not IPv4
+ /// address.
+ ///
+ /// \return uint32_t that represents IPv4 address in
+ /// network byte order
+ uint32_t toUint32() const;
+
+ /// @name Methods returning @c IOAddress objects encapsulating typical addresses.
+ ///
+ //@{
+ /// @brief Returns an address set to all zeros.
+ static const IOAddress& IPV4_ZERO_ADDRESS() {
+ static IOAddress address(0);
+ return (address);
+ }
+
+ /// @brief Returns a "255.255.255.255" broadcast address.
+ static const IOAddress& IPV4_BCAST_ADDRESS() {
+ static IOAddress address(0xFFFFFFFF);
+ return (address);
+ }
+
+ /// @brief Returns an IPv6 zero address.
+ static const IOAddress& IPV6_ZERO_ADDRESS() {
+ static IOAddress address("::");
+ return (address);
+ }
+
+ //@}
+
+private:
+ boost::asio::ip::address asio_address_;
+};
+
+/// \brief Insert the IOAddress as a string into stream.
+///
+/// This method converts the \c address 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 IOAddress objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param address The \c IOAddress 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 IOAddress& address);
+
+/// \brief Hash the IOAddress.
+///
+/// This method allows boost multi-index hashed indexes on IOAddresses.
+/// It follows the requirement with equality: if two addresses are equal
+/// their hashes are equal, if two addresses are not equal their hashes
+/// are almost surely not equal.
+///
+/// \param address A \c IOAddress to hash.
+/// \return The hash of the IOAddress.
+size_t hash_value(const IOAddress& address);
+
+} // namespace asiolink
+} // namespace isc
+#endif // IO_ADDRESS_H
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
new file mode 100644
index 0000000..29ca97f
--- /dev/null
+++ b/src/lib/asiolink/io_asio_socket.h
@@ -0,0 +1,381 @@
+// Copyright (C) 2010-2018 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 IO_ASIO_SOCKET_H
+#define IO_ASIO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <config.h>
+
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <asiolink/io_error.h>
+#include <asiolink/io_socket.h>
+
+// We want to use coroutine.hpp from the system's boost headers if possible.
+// However, very old Boost versions (provided by RHEL 7 or CentOS 7) didn't have
+// this header. So we can resort to our bundled version, but only if necessary.
+#ifndef HAVE_BOOST_ASIO_COROUTINE_HPP
+#include <ext/coroutine/coroutine.hpp>
+#else
+#include <boost/asio/coroutine.hpp>
+#endif
+
+namespace isc {
+namespace asiolink {
+
+/// \brief Socket not open
+///
+/// Thrown on an attempt to do read/write to a socket that is not open.
+class SocketNotOpen : public IOError {
+public:
+ SocketNotOpen(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// \brief Error setting socket options
+///
+/// Thrown if attempt to change socket options fails.
+class SocketSetError : public IOError {
+public:
+ SocketSetError(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// \brief Buffer overflow
+///
+/// Thrown if an attempt is made to receive into an area beyond the end of
+/// the receive data buffer.
+class BufferOverflow : public IOError {
+public:
+ BufferOverflow(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// Forward declaration of an IOEndpoint
+class IOEndpoint;
+
+
+/// \brief I/O Socket with asynchronous operations
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// This is the basic IOSocket with additional operations - open, send, receive
+/// and close. Depending on how the asiolink code develops, it may be a
+/// temporary class: its main use is to add the template parameter needed for
+/// the derived classes UDPSocket and TCPSocket but without changing the
+/// signature of the more basic IOSocket class.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it. Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class IOAsioSocket : public IOSocket {
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOAsioSocket(const IOAsioSocket<C>& source);
+ IOAsioSocket& operator=(const IOAsioSocket<C>& 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).
+ IOAsioSocket() {}
+public:
+ /// The destructor.
+ virtual ~IOAsioSocket() {}
+ //@}
+
+ /// \brief Return the "native" representation of the socket.
+ ///
+ /// In practice, this is the file descriptor of the socket for UNIX-like
+ /// systems so the current implementation simply uses \c int as the type of
+ /// the return value. We may have to need revisit this decision later.
+ ///
+ /// In general, the application should avoid using this method; it
+ /// essentially discloses an implementation specific "handle" that can
+ /// change the internal state of the socket (consider what would happen if
+ /// the application closes it, for example). But we sometimes need to
+ /// perform very low-level operations that requires the native
+ /// representation. Passing the file descriptor to a different process is
+ /// one example. This method is provided as a necessary evil for such
+ /// limited purposes.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The native representation of the socket. This is the socket
+ /// file descriptor for UNIX-like systems.
+ virtual int getNative() const = 0;
+
+ /// \brief Return the transport protocol of the socket.
+ ///
+ /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+ /// \c IPPROTO_TCP for TCP sockets.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return \c IPPROTO_UDP for UDP sockets, \c IPPROTO_TCP for TCP sockets
+ virtual int getProtocol() const = 0;
+
+ /// \brief Is Open() synchronous?
+ ///
+ /// On a TCP socket, an "open" operation is a call to the socket's "open()"
+ /// method followed by a connection to the remote system: it is an
+ /// asynchronous operation. On a UDP socket, it is just a call to "open()"
+ /// and completes synchronously.
+ ///
+ /// For TCP, signalling of the completion of the operation is done by
+ /// by calling the callback function in the normal way. This could be done
+ /// for UDP (by posting en event on the event queue); however, that will
+ /// incur additional overhead in the most common case. So we give the
+ /// caller the choice for calling this open() method synchronously or
+ /// asynchronously.
+ ///
+ /// Owing to the way that the stackless coroutines are implemented, we need
+ /// to know _before_ executing the "open" function whether or not it is
+ /// asynchronous. So this method is called to provide that information.
+ ///
+ /// (The reason there is a need to know is because the call to open() passes
+ /// in the state of the coroutine at the time the call is made. On an
+ /// asynchronous I/O, we need to set the state to point to the statement
+ /// after the call to open() _before_ we pass the coroutine to the open()
+ /// call. Unfortunately, the macros that set the state of the coroutine
+ /// also yield control - which we don't want to do if the open is
+ /// synchronous. Hence we need to know before we make the call to open()
+ /// whether that call will complete asynchronously.)
+ virtual bool isOpenSynchronous() const = 0;
+
+ /// \brief Open AsioSocket
+ ///
+ /// Opens the socket for asynchronous I/O. The open will complete
+ /// synchronously on UCP or asynchronously on TCP (in which case a callback
+ /// will be queued).
+ ///
+ /// \param endpoint Pointer to the endpoint object. This is ignored for
+ /// a UDP socket (the target is specified in the send call), but
+ /// should be of type TCPEndpoint for a TCP connection.
+ /// \param callback I/O Completion callback, called when the operation has
+ /// completed, but only if the operation was asynchronous. (It is
+ /// ignored on a UDP socket.)
+ virtual void open(const IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Send Asynchronously
+ ///
+ /// This corresponds to async_send_to() for UDP sockets and async_send()
+ /// for TCP. In both cases an endpoint argument is supplied indicating the
+ /// target of the send - this is ignored for TCP.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param endpoint Target of the send
+ /// \param callback Callback object.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Receive Asynchronously
+ ///
+ /// This corresponds to async_receive_from() for UDP sockets and
+ /// async_receive() for TCP. In both cases, an endpoint argument is
+ /// supplied to receive the source of the communication. For TCP it will
+ /// be filled in with details of the connection.
+ ///
+ /// \param data Buffer to receive incoming message
+ /// \param length Length of the data buffer
+ /// \param offset Offset into buffer where data is to be put. Although the
+ /// offset could be implied by adjusting "data" and "length"
+ /// appropriately, using this argument allows data to be specified as
+ /// "const void*" - the overhead of converting it to a pointer to a
+ /// set of bytes is hidden away here.
+ /// \param endpoint Source of the communication
+ /// \param callback Callback object
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Processes received data
+ ///
+ /// In the IOFetch code, data is received into a staging buffer before being
+ /// copied into the target buffer. (This is because (a) we don't know how
+ /// much data we will be receiving, so don't know how to size the output
+ /// buffer and (b) TCP data is preceded by a two-byte count field that needs
+ /// to be discarded before being returned to the user.)
+ ///
+ /// An additional consideration is that TCP data is not received in one
+ /// I/O - it may take a number of I/Os - each receiving any non-zero number
+ /// of bytes - to read the entire message.
+ ///
+ /// So the IOFetch code has to loop until it determines that all the data
+ /// has been read. This is where this method comes in. It has several
+ /// functions:
+ ///
+ /// - It checks if the received data is complete.
+ /// - If data is not complete, decides if the next set of data is to go into
+ /// the start of the staging buffer or at some offset into it. (This
+ /// simplifies the case we could have in a TCP receive where the two-byte
+ /// count field is received in one-byte chunks: we put off interpreting
+ /// the count until we have all of it. The alternative - copying the
+ /// data to the output buffer and interpreting the count from there -
+ /// would require moving the data in the output buffer by two bytes before
+ /// returning it to the caller.)
+ /// - Copies data from the staging buffer into the output buffer.
+ ///
+ /// This functionality mainly applies to TCP receives. For UDP, all the
+ /// data is received in one I/O, so this just copies the data into the
+ /// output buffer.
+ ///
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed (this includes the TCP count field if appropriate).
+ /// The value should be set to zero before the receive loop is
+ /// entered, and it will be updated by this method as required.
+ /// \param offset Offset into the staging buffer where the next read should
+ /// put the received data. It should be set to zero before the first
+ /// call and may be updated by this method.
+ /// \param expected Expected amount of data to be received. This is
+ /// really the TCP count field and is set to that value when enough
+ /// of a TCP message is received. It should be initialized to -1
+ /// before the first read is executed.
+ /// \param outbuff Output buffer. Data in the staging buffer may be copied
+ /// to this output buffer in the call.
+ ///
+ /// \return true if the receive is complete, false if another receive is
+ /// needed. This is always true for UDP, but for TCP involves
+ /// checking the amount of data received so far against the amount
+ /// expected (as indicated by the two-byte count field). If this
+ /// method returns false, another read should be queued and data
+ /// should be read into the staging buffer at offset given by the
+ /// "offset" parameter.
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::util::OutputBufferPtr& outbuff) = 0;
+
+ /// \brief Cancel I/O On AsioSocket
+ virtual void cancel() = 0;
+
+ /// \brief Close socket
+ virtual void close() = 0;
+};
+
+
+/// \brief The \c DummyAsioSocket class is a concrete derived class of
+/// \c IOAsioSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOAsioSocket object without involving system resource
+/// allocation such as real network sockets.
+///
+/// \param C Template parameter identifying type of the callback object.
+
+template <typename C>
+class DummyAsioSocket : public IOAsioSocket<C> {
+private:
+ DummyAsioSocket(const DummyAsioSocket<C>& source);
+ DummyAsioSocket& operator=(const DummyAsioSocket<C>& source);
+public:
+ /// \brief Constructor from the protocol number.
+ ///
+ /// The protocol must validly identify a standard network protocol.
+ /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+ ///
+ /// \param protocol The network protocol number for the socket.
+ DummyAsioSocket(const int protocol) : protocol_(protocol) {}
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getNative().
+ ///
+ /// \return Always returns -1 as the object is not associated with a real
+ /// (native) socket.
+ virtual int getNative() const { return (-1); }
+
+ /// \brief A dummy derived method of \c IOAsioSocket::getProtocol().
+ ///
+ /// \return Protocol socket was created with
+ virtual int getProtocol() const { return (protocol_); }
+
+
+ /// \brief Is socket opening synchronous?
+ ///
+ /// \return true - it is for this class.
+ bool isOpenSynchronous() const {
+ return true;
+ }
+
+ /// \brief Open AsioSocket
+ ///
+ /// A call that is a no-op on UDP sockets, this opens a connection to the
+ /// system identified by the given endpoint.
+ /// The endpoint and callback are unused.
+ ///
+ /// \return false indicating that the operation completed synchronously.
+ virtual bool open(const IOEndpoint*, C&) {
+ return (false);
+ }
+
+ /// \brief Send Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ /// This is unused.
+ virtual void asyncSend(const void*, size_t, const IOEndpoint*, C&) {
+ }
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ /// The parameters are unused.
+ virtual void asyncReceive(void*, size_t, size_t, IOEndpoint*, C&) {
+ }
+
+ /// \brief Checks if the data received is complete.
+ ///
+ /// The parameters are unused.
+ /// \return Always true
+ virtual bool receiveComplete(const void*, size_t, size_t&, size_t&,
+ size_t&, isc::util::OutputBufferPtr&)
+ {
+ return (true);
+ }
+
+
+ /// \brief Cancel I/O On AsioSocket
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ virtual void cancel() {
+ }
+
+ /// \brief Close socket
+ ///
+ /// Must be supplied as it is abstract in the base class.
+ virtual void close() {
+ }
+
+private:
+ const int protocol_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_ASIO_SOCKET_H
diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc
new file mode 100644
index 0000000..d236aa8
--- /dev/null
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 2011-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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/udp_endpoint.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <unistd.h> // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+using namespace std;
+
+namespace isc {
+namespace asiolink {
+
+const IOEndpoint*
+IOEndpoint::create(const int protocol, const IOAddress& address,
+ const unsigned short port)
+{
+ if (protocol == IPPROTO_UDP) {
+ return (new UDPEndpoint(address, port));
+ } else if (protocol == IPPROTO_TCP) {
+ return (new TCPEndpoint(address, port));
+ }
+ isc_throw(IOError,
+ "IOEndpoint creation attempt for unsupported protocol: " <<
+ protocol);
+}
+
+bool
+IOEndpoint::operator==(const IOEndpoint& other) const {
+ return (getProtocol() == other.getProtocol() &&
+ getPort() == other.getPort() &&
+ getFamily() == other.getFamily() &&
+ getAddress() == other.getAddress());
+}
+
+bool
+IOEndpoint::operator!=(const IOEndpoint& other) const {
+ return (!operator==(other));
+}
+
+ostream&
+operator<<(ostream& os, const IOEndpoint& endpoint) {
+ if (endpoint.getFamily() == AF_INET6) {
+ os << "[" << endpoint.getAddress() << "]";
+ } else {
+ // In practice this should be AF_INET, but it's not guaranteed by
+ // the interface. We'll use the result of textual address
+ // representation opaquely.
+ os << endpoint.getAddress();
+ }
+ os << ":" << boost::lexical_cast<string>(endpoint.getPort());
+ return (os);
+}
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h
new file mode 100644
index 0000000..8421a30
--- /dev/null
+++ b/src/lib/asiolink/io_endpoint.h
@@ -0,0 +1,183 @@
+// 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/.
+
+#ifndef IO_ENDPOINT_H
+#define IO_ENDPOINT_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+
+# include <ostream>
+
+#include <unistd.h> // for some network system calls
+
+#include <sys/socket.h> // for sockaddr
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c IOEndpoint class is an abstract base class to represent
+/// a communication endpoint.
+///
+/// This class is a wrapper for the ASIO endpoint classes such as
+/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation. User applications only get access to concrete
+/// \c IOEndpoint objects via the abstract interfaces.
+class IOEndpoint {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOEndpoint(const IOEndpoint& source);
+ IOEndpoint& operator=(const IOEndpoint& 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).
+ IOEndpoint() {}
+public:
+ /// The destructor.
+ virtual ~IOEndpoint() {}
+ //@}
+
+ /// \brief Returns the address of the endpoint.
+ ///
+ /// This method returns an IOAddress object corresponding to \c this
+ /// endpoint.
+ ///
+ /// Note that the return value is a real object, not a reference or
+ /// a pointer.
+ ///
+ /// This is aligned with the interface of the ASIO counterpart:
+ /// the \c address() method of \c ip::xxx::endpoint classes returns
+ /// an \c ip::address object.
+ ///
+ /// This also means handling the address of an endpoint using this method
+ /// can be expensive. If the address information is necessary in a
+ /// performance sensitive context and there's a more efficient interface
+ /// for that purpose, it's probably better to avoid using this method.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A copy of \c IOAddress object corresponding to the endpoint.
+ virtual IOAddress getAddress() const = 0;
+
+ /// \brief Returns the port of the endpoint.
+ virtual uint16_t getPort() const = 0;
+
+ /// \brief Returns the protocol number of the endpoint (TCP, UDP...)
+ virtual short getProtocol() const = 0;
+
+ /// \brief Returns the address family of the endpoint.
+ virtual short getFamily() const = 0;
+
+ /// \brief Returns the address of the endpoint in the form of sockaddr
+ /// structure.
+ ///
+ /// The actual instance referenced by the returned value of this method
+ /// is of per address family structure: For IPv4 (AF_INET), it's
+ /// \c sockaddr_in; for IPv6 (AF_INET6), it's \c sockaddr_in6.
+ /// The corresponding port and address members of the underlying structure
+ /// will be set in the network byte order.
+ ///
+ /// This method is "redundant" in that all information to construct the
+ /// \c sockaddr is available via the other "get" methods.
+ /// It is still defined for performance sensitive applications that need
+ /// to get the address information, such as for address based access
+ /// control at a high throughput. Internally it is implemented with
+ /// minimum overhead such as data copy (this is another reason why this
+ /// method returns a reference).
+ ///
+ /// As a tradeoff, this method is more fragile; it assumes that the
+ /// underlying ASIO implementation stores the address information in
+ /// the form of \c sockaddr and it can be accessed in an efficient way.
+ /// This is the case as of this writing, but if the underlying
+ /// implementation changes this method may become much slower or its
+ /// interface may have to be changed, too.
+ ///
+ /// It is therefore discouraged for normal applications to use this
+ /// method. Unless the application is very performance sensitive, it
+ /// should use the other "get" method to retrieve specific information
+ /// of the endpoint.
+ ///
+ /// The returned reference is only valid while the corresponding
+ /// \c IOEndpoint is valid. Once it's destructed the reference will
+ /// become invalid.
+ ///
+ /// \exception None
+ /// \return Reference to a \c sockaddr structure corresponding to the
+ /// endpoint.
+ virtual const struct sockaddr& getSockAddr() const = 0;
+
+ bool operator==(const IOEndpoint& other) const;
+ bool operator!=(const IOEndpoint& other) const;
+
+ /// \brief A polymorphic factory of endpoint from address and port.
+ ///
+ /// This method creates a new instance of (a derived class of)
+ /// \c IOEndpoint object that identifies the pair of given address
+ /// and port.
+ /// The appropriate derived class is chosen based on the specified
+ /// transport protocol. If the \c protocol doesn't specify a protocol
+ /// supported in this implementation, an exception of class \c IOError
+ /// will be thrown.
+ ///
+ /// Memory for the created object will be dynamically allocated. It's
+ /// the caller's responsibility to \c delete it later.
+ /// If resource allocation for the new object fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \param protocol The transport protocol used for the endpoint.
+ /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
+ /// \param address The (IP) address of the endpoint.
+ /// \param port The transport port number of the endpoint
+ /// \return A pointer to a newly created \c IOEndpoint object.
+ static const IOEndpoint* create(const int protocol,
+ const IOAddress& address,
+ const unsigned short port);
+};
+
+/// \brief Insert the \c IOEndpoint as a string into stream.
+///
+/// This method converts \c endpoint into a string and inserts it into the
+/// output stream \c os.
+///
+/// This method converts the address and port of the endpoint in the textual
+/// format that other Kea modules would use in logging, i.e.,
+/// - For IPv6 address: [&lt;address&gt;]:port (e.g., [2001:db8::5300]:53)
+/// - For IPv4 address: &lt;address&gt;:port (e.g., 192.0.2.53:5300)
+///
+/// If it's neither IPv6 nor IPv4, it converts the endpoint into text in the
+/// same format as that for IPv4, although in practice such a case is not
+/// really expected.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param endpoint A reference to an \c IOEndpoint 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 IOEndpoint& endpoint);
+} // namespace asiolink
+} // namespace isc
+#endif // IO_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/io_error.h b/src/lib/asiolink/io_error.h
new file mode 100644
index 0000000..692070c
--- /dev/null
+++ b/src/lib/asiolink/io_error.h
@@ -0,0 +1,29 @@
+// 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/.
+
+
+#ifndef IO_ERROR_H
+#define IO_ERROR_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief An exception that is thrown if an error occurs within the IO
+/// module. This is mainly intended to be a wrapper exception class for
+/// ASIO specific exceptions.
+class IOError : public isc::Exception {
+public:
+ IOError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_ERROR_H
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
new file mode 100644
index 0000000..03cc2e2
--- /dev/null
+++ b/src/lib/asiolink/io_service.cc
@@ -0,0 +1,159 @@
+// Copyright (C) 2011-2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+
+#include <unistd.h> // for some IPC/network system calls
+#include <netinet/in.h>
+#include <boost/shared_ptr.hpp>
+#include <sys/socket.h>
+
+namespace isc {
+namespace asiolink {
+
+class IOServiceImpl {
+private:
+ IOServiceImpl(const IOService& source);
+ IOServiceImpl& operator=(const IOService& source);
+public:
+ /// \brief The constructor
+ IOServiceImpl() :
+ io_service_(),
+ work_(new boost::asio::io_service::work(io_service_)) {
+ };
+
+ /// \brief The destructor.
+ ~IOServiceImpl() {};
+ //@}
+
+ /// \brief Start the underlying event loop.
+ ///
+ /// This method does not return control to the caller until
+ /// the \c stop() method is called via some handler.
+ void run() {
+ io_service_.run();
+ };
+
+ /// \brief Run the underlying event loop for a single event.
+ ///
+ /// This method return control to the caller as soon as the
+ /// first handler has completed. (If no handlers are ready when
+ /// it is run, it will block until one is.)
+ void run_one() {
+ io_service_.run_one();
+ };
+
+ /// \brief Run the underlying event loop for a ready events.
+ ///
+ /// This method executes handlers for all ready events and returns.
+ /// It will return immediately if there are no ready events.
+ void poll() {
+ io_service_.poll();
+ };
+
+ /// \brief Stop the underlying event loop.
+ ///
+ /// This will return the control to the caller of the \c run() method.
+ void stop() {
+ io_service_.stop();
+ }
+
+ /// \brief Indicates if the IOService has been stopped.
+ ///
+ /// \return true if the IOService has been stopped, false otherwise.
+ bool stopped() const {
+ return (io_service_.stopped());
+ }
+
+ /// \brief Restarts the IOService in preparation for a subsequent \c run() invocation.
+ void restart() {
+ io_service_.reset();
+ }
+
+ /// \brief Removes IO service work object to let it finish running
+ /// when all handlers have been invoked.
+ void stopWork() {
+ work_.reset();
+ }
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other Kea modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ boost::asio::io_service& get_io_service() {
+ return (io_service_);
+ }
+
+ /// \brief Post a callback on the IO service
+ ///
+ /// \param callback The callback to be run on the IO service.
+ void post(const std::function<void ()>& callback) {
+ io_service_.post(callback);
+ }
+
+private:
+ boost::asio::io_service io_service_;
+ boost::shared_ptr<boost::asio::io_service::work> work_;
+};
+
+IOService::IOService() : io_impl_(new IOServiceImpl()) {
+}
+
+IOService::~IOService() {
+}
+
+void
+IOService::run() {
+ io_impl_->run();
+}
+
+void
+IOService::run_one() {
+ io_impl_->run_one();
+}
+
+void
+IOService::poll() {
+ io_impl_->poll();
+}
+
+void
+IOService::stop() {
+ io_impl_->stop();
+}
+
+bool
+IOService::stopped() const {
+ return (io_impl_->stopped());
+}
+
+void
+IOService::restart() {
+ io_impl_->restart();
+}
+
+void
+IOService::stopWork() {
+ io_impl_->stopWork();
+}
+
+boost::asio::io_service&
+IOService::get_io_service() {
+ return (io_impl_->get_io_service());
+}
+
+void
+IOService::post(const std::function<void ()>& callback) {
+ return (io_impl_->post(callback));
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h
new file mode 100644
index 0000000..4bcedf1
--- /dev/null
+++ b/src/lib/asiolink/io_service.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2011-2021 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 ASIOLINK_IO_SERVICE_H
+#define ASIOLINK_IO_SERVICE_H
+
+#include <boost/version.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+namespace boost {
+namespace asio {
+#if BOOST_VERSION < 106600
+ class io_service;
+#else
+ class io_context;
+ typedef io_context io_service;
+#endif
+}
+}
+
+namespace isc {
+namespace asiolink {
+
+class IOServiceImpl;
+
+/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
+/// class.
+///
+class IOService {
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOService(const IOService& source);
+ IOService& operator=(const IOService& source);
+public:
+ /// \brief The constructor
+ IOService();
+ /// \brief The destructor.
+ ~IOService();
+ //@}
+
+ /// \brief Start the underlying event loop.
+ ///
+ /// This method does not return control to the caller until
+ /// the \c stop() method is called via some handler.
+ void run();
+
+ /// \brief Run the underlying event loop for a single event.
+ ///
+ /// This method return control to the caller as soon as the
+ /// first handler has completed. (If no handlers are ready when
+ /// it is run, it will block until one is.)
+ void run_one();
+
+ /// \brief Run the underlying event loop for a ready events.
+ ///
+ /// This method executes handlers for all ready events and returns.
+ /// It will return immediately if there are no ready events.
+ void poll();
+
+ /// \brief Stop the underlying event loop.
+ ///
+ /// This will return the control to the caller of the \c run() method.
+ void stop();
+
+ /// \brief Indicates if the IOService has been stopped.
+ ///
+ /// \return true if the IOService has been stopped, false otherwise.
+ bool stopped() const;
+
+ /// \brief Restarts the IOService in preparation for a subsequent \c run() invocation.
+ void restart();
+
+ /// \brief Removes IO service work object to let it finish running
+ /// when all handlers have been invoked.
+ void stopWork();
+
+ /// \brief Return the native \c io_service object used in this wrapper.
+ ///
+ /// This is a short term work around to support other Kea modules
+ /// that share the same \c io_service with the authoritative server.
+ /// It will eventually be removed once the wrapper interface is
+ /// generalized.
+ boost::asio::io_service& get_io_service();
+
+ /// \brief Post a callback to the end of the queue.
+ ///
+ /// Requests the callback be called sometime later. It is not guaranteed
+ /// by the underlying asio, but it can reasonably be expected the callback
+ /// is put to the end of the callback queue. It is not called from within
+ /// this function.
+ ///
+ /// It may be used to implement "background" work, for example (doing stuff
+ /// by small bits that are called from time to time).
+ void post(const std::function<void ()>& callback);
+
+private:
+ boost::shared_ptr<IOServiceImpl> io_impl_;
+};
+
+/// @brief Defines a smart pointer to an IOService instance.
+typedef boost::shared_ptr<IOService> IOServicePtr;
+
+} // namespace asiolink
+} // namespace isc
+#endif // ASIOLINK_IO_SERVICE_H
diff --git a/src/lib/asiolink/io_service_signal.cc b/src/lib/asiolink/io_service_signal.cc
new file mode 100644
index 0000000..26529f0
--- /dev/null
+++ b/src/lib/asiolink/io_service_signal.cc
@@ -0,0 +1,146 @@
+// Copyright (C) 2020-2023 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 <asiolink/io_service_signal.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/asio/signal_set.hpp>
+#include <functional>
+
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implementation class of IOSignalSet.
+class IOSignalSetImpl : public boost::enable_shared_from_this<IOSignalSetImpl>,
+ public boost::noncopyable {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param io_service the process IO service.
+ /// @param handler the signal handler.
+ IOSignalSetImpl(IOServicePtr io_service, IOSignalHandler handler);
+
+ /// @brief Destructor.
+ ~IOSignalSetImpl();
+
+ /// @brief Install the callback on the IO service queue.
+ void install();
+
+ /// @brief Add a signal to the ASIO signal set.
+ ///
+ /// @param signum the signal number.
+ void add(int signum);
+
+ /// @brief Remove a signal from the ASIO signal set.
+ ///
+ /// @param signum the signal number.
+ void remove(int signum);
+
+ /// @brief Cancel the remaining installed signal handler callbacks.
+ void cancel();
+
+private:
+ /// @brief Extends the lifetime of IOService to avoid heap-use-after-free.
+ IOServicePtr io_service_;
+
+ /// @brief the ASIO signal set.
+ boost::asio::signal_set signal_set_;
+
+ /// @brief the signal handler.
+ IOSignalHandler handler_;
+
+ /// @brief the callback (called on cancel or received signal).
+ ///
+ /// The callback is installed on the IO service queue and calls
+ /// the handler if the operation was not aborted.
+ void callback(const boost::system::error_code& ec, int signum);
+};
+
+IOSignalSetImpl::IOSignalSetImpl(IOServicePtr io_service,
+ IOSignalHandler handler)
+ : io_service_(io_service),
+ signal_set_(io_service_->get_io_service()),
+ handler_(handler) {
+}
+
+IOSignalSetImpl::~IOSignalSetImpl() {
+ handler_ = IOSignalHandler();
+}
+
+void
+IOSignalSetImpl::cancel() {
+ signal_set_.cancel();
+}
+
+void
+IOSignalSetImpl::callback(const boost::system::error_code& ec, int signum) {
+ if (ec && ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ install();
+ if (!ec && (signum > 0)) {
+ try {
+ handler_(signum);
+ } catch (const std::exception& ex) {
+ }
+ }
+}
+
+void
+IOSignalSetImpl::install() {
+ signal_set_.async_wait(std::bind(&IOSignalSetImpl::callback,
+ shared_from_this(),
+ ph::_1, ph::_2));
+}
+
+void
+IOSignalSetImpl::add(int signum) {
+ try {
+ signal_set_.add(signum);
+ } catch (const boost::system::system_error& ex) {
+ isc_throw(isc::Unexpected,
+ "Failed to add signal " << signum << ": " << ex.what());
+ }
+}
+
+void
+IOSignalSetImpl::remove(int signum) {
+ try {
+ signal_set_.remove(signum);
+ } catch (const boost::system::system_error& ex) {
+ isc_throw(isc::Unexpected,
+ "Failed to remove signal " << signum << ": " << ex.what());
+ }
+}
+
+IOSignalSet::IOSignalSet(IOServicePtr io_service, IOSignalHandler handler) :
+ impl_(new IOSignalSetImpl(io_service, handler)) {
+ // It can throw but the error is fatal...
+ impl_->install();
+}
+
+IOSignalSet::~IOSignalSet() {
+ impl_->cancel();
+}
+
+void
+IOSignalSet::add(int signum) {
+ impl_->add(signum);
+}
+
+void
+IOSignalSet::remove(int signum) {
+ impl_->remove(signum);
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/io_service_signal.h b/src/lib/asiolink/io_service_signal.h
new file mode 100644
index 0000000..8fbcbe1
--- /dev/null
+++ b/src/lib/asiolink/io_service_signal.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2020-2023 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 IO_SERVICE_SIGNAL_H
+#define IO_SERVICE_SIGNAL_H
+
+#include <asiolink/io_service.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Defines a handler function for an IOSignal.
+typedef std::function<void(int signum)> IOSignalHandler;
+
+class IOSignalSetImpl;
+
+/// @brief Implements an asynchronous "signal" for IOService driven processing
+///
+/// This class allows a OS signal such as SIGHUP to propagated to an IOService
+/// as a ready event with a callback using boost ASIO.
+class IOSignalSet {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param io_service IOService to which to send the signal.
+ /// @param handler Handler to call when a signal is received.
+ IOSignalSet(asiolink::IOServicePtr io_service, IOSignalHandler handler);
+
+ /// @brief Destructor.
+ ~IOSignalSet();
+
+ /// @brief Add a signal to the list of signals to handle.
+ ///
+ /// @param signum Signal number.
+ /// @throw Unexpected on error.
+ void add(int signum);
+
+ /// @brief Remove a signal from the list of signals to handle.
+ ///
+ /// @param signum Signal number.
+ /// @throw Unexpected on error.
+ void remove(int signum);
+
+private:
+ /// @brief Pointer to the implementation.
+ boost::shared_ptr<IOSignalSetImpl> impl_;
+};
+
+/// @brief Defines a pointer to an IOSignalSet.
+typedef boost::shared_ptr<IOSignalSet> IOSignalSetPtr;
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_SERVICE_SIGNAL_H
diff --git a/src/lib/asiolink/io_service_thread_pool.cc b/src/lib/asiolink/io_service_thread_pool.cc
new file mode 100644
index 0000000..8baf720
--- /dev/null
+++ b/src/lib/asiolink/io_service_thread_pool.cc
@@ -0,0 +1,276 @@
+// Copyright (C) 2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_service_thread_pool.h>
+#include <exceptions/exceptions.h>
+#include <util/multi_threading_mgr.h>
+#include <util/unlock_guard.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <atomic>
+#include <functional>
+#include <iostream>
+#include <list>
+#include <mutex>
+#include <thread>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+IoServiceThreadPool::IoServiceThreadPool(IOServicePtr io_service, size_t pool_size,
+ bool defer_start /* = false */)
+ : pool_size_(pool_size), io_service_(io_service),
+ run_state_(State::STOPPED), mutex_(), thread_cv_(),
+ main_cv_(), paused_(0), running_(0), exited_(0) {
+ if (!pool_size) {
+ isc_throw(BadValue, "pool_size must be non 0");
+ }
+
+ // If we weren't given an IOService, create our own.
+ if (!io_service_) {
+ io_service_.reset(new IOService());
+ }
+
+ // If we're not deferring the start, do it now.
+ if (!defer_start) {
+ run();
+ }
+}
+
+IoServiceThreadPool::~IoServiceThreadPool() {
+ stop();
+}
+
+void
+IoServiceThreadPool::run() {
+ setState(State::RUNNING);
+}
+
+void
+IoServiceThreadPool::pause() {
+ setState(State::PAUSED);
+}
+
+void
+IoServiceThreadPool::stop() {
+ setState(State::STOPPED);
+}
+
+IoServiceThreadPool::State
+IoServiceThreadPool::getState() {
+ std::lock_guard<std::mutex> lck(mutex_);
+ return (run_state_);
+}
+
+bool
+IoServiceThreadPool::validateStateChange(State state) const {
+ switch (run_state_) {
+ case State::STOPPED:
+ return (state == State::RUNNING);
+ case State::RUNNING:
+ return (state != State::RUNNING);
+ case State::PAUSED:
+ return (state != State::PAUSED);
+ }
+ return (false);
+}
+
+std::string
+IoServiceThreadPool::stateToText(State state) {
+ switch (state) {
+ case State::STOPPED:
+ return (std::string("stopped"));
+ case State::RUNNING:
+ return (std::string("running"));
+ case State::PAUSED:
+ return (std::string("paused"));
+ }
+ return (std::string("unknown-state"));
+}
+
+void
+IoServiceThreadPool::checkPausePermissions() {
+ checkPermissions(State::PAUSED);
+}
+
+void
+IoServiceThreadPool::checkPermissions(State state) {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "invalid thread pool state change to "
+ << IoServiceThreadPool::stateToText(state) << " performed by worker thread");
+ }
+}
+
+bool
+IoServiceThreadPool::checkThreadId(std::thread::id id) {
+ for (auto thread : threads_) {
+ if (id == thread->get_id()) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+void
+IoServiceThreadPool::setState(State state) {
+ checkPermissions(state);
+
+ std::unique_lock<std::mutex> main_lck(mutex_);
+
+ // Bail if the transition is invalid.
+ if (!validateStateChange(state)) {
+ return;
+ }
+
+ run_state_ = state;
+ // Notify threads of state change.
+ thread_cv_.notify_all();
+
+ switch (state) {
+ case State::RUNNING: {
+ // Restart the IOService.
+ io_service_->restart();
+
+ // While we have fewer threads than we should, make more.
+ while (threads_.size() < pool_size_) {
+ boost::shared_ptr<std::thread> thread(new std::thread(
+ std::bind(&IoServiceThreadPool::threadWork, this)));
+
+ // Add thread to the pool.
+ threads_.push_back(thread);
+ }
+
+ // Main thread waits here until all threads are running.
+ main_cv_.wait(main_lck,
+ [&]() {
+ return (running_ == threads_.size());
+ });
+
+ exited_ = 0;
+ break;
+ }
+
+ case State::PAUSED: {
+ // Stop IOService.
+ if (!io_service_->stopped()) {
+ io_service_->poll();
+ io_service_->stop();
+ }
+
+ // Main thread waits here until all threads are paused.
+ main_cv_.wait(main_lck,
+ [&]() {
+ return (paused_ == threads_.size());
+ });
+
+ break;
+ }
+
+ case State::STOPPED: {
+ // Stop IOService.
+ if (!io_service_->stopped()) {
+ io_service_->poll();
+ io_service_->stop();
+ }
+
+ // Main thread waits here until all threads have exited.
+ main_cv_.wait(main_lck,
+ [&]() {
+ return (exited_ == threads_.size());
+ });
+
+ for (auto const& thread : threads_) {
+ thread->join();
+ }
+
+ threads_.clear();
+ break;
+ }}
+}
+
+void
+IoServiceThreadPool::threadWork() {
+ bool done = false;
+ while (!done) {
+ switch (getState()) {
+ case State::RUNNING: {
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ running_++;
+
+ // If We're all running notify main thread.
+ if (running_ == pool_size_) {
+ main_cv_.notify_all();
+ }
+ }
+
+ // Run the IOService.
+ io_service_->run();
+
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ running_--;
+ }
+
+ break;
+ }
+
+ case State::PAUSED: {
+ std::unique_lock<std::mutex> lck(mutex_);
+ paused_++;
+
+ // If we're all paused notify main.
+ if (paused_ == threads_.size()) {
+ main_cv_.notify_all();
+ }
+
+ // Wait here till I'm released.
+ thread_cv_.wait(lck,
+ [&]() {
+ return (run_state_ != State::PAUSED);
+ });
+
+ paused_--;
+ break;
+ }
+
+ case State::STOPPED: {
+ done = true;
+ break;
+ }}
+ }
+
+ std::unique_lock<std::mutex> lck(mutex_);
+ exited_++;
+
+ // If we've all exited, notify main.
+ if (exited_ == threads_.size()) {
+ main_cv_.notify_all();
+ }
+}
+
+IOServicePtr
+IoServiceThreadPool::getIOService() const {
+ return (io_service_);
+}
+
+uint16_t
+IoServiceThreadPool::getPoolSize() const {
+ return (pool_size_);
+}
+
+uint16_t
+IoServiceThreadPool::getThreadCount() const {
+ return (threads_.size());
+}
diff --git a/src/lib/asiolink/io_service_thread_pool.h b/src/lib/asiolink/io_service_thread_pool.h
new file mode 100644
index 0000000..68c4e21
--- /dev/null
+++ b/src/lib/asiolink/io_service_thread_pool.h
@@ -0,0 +1,265 @@
+// Copyright (C) 2022 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 IOSERVICE_THREAD_POOL_H
+#define IOSERVICE_THREAD_POOL_H
+
+#include <asiolink/io_service.h>
+#include <util/unlock_guard.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <condition_variable>
+#include <list>
+#include <mutex>
+#include <thread>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implements a pausable pool of IOService driven threads.
+class IoServiceThreadPool {
+public:
+ /// @brief Describes the possible operational state of the thread pool.
+ enum class State {
+ STOPPED, /// Pool is not operational.
+ RUNNING, /// Pool is populated with running threads.
+ PAUSED, /// Pool is populated with threads that are paused.
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param io_service IOService that will drive the pool's IO. If empty, it
+ /// create its own instance.
+ /// @param pool_size Maximum number of threads in the pool. Currently the
+ /// number of threads is fixed at this value.
+ /// @param defer_start If true, creation of the threads is deferred until
+ /// a subsequent call to @ref run(). In this case the pool's operational
+ /// state post construction is STOPPED. If false, the constructor will
+ /// invoke run() to transition the pool into the RUNNING state.
+ IoServiceThreadPool(asiolink::IOServicePtr io_service, size_t pool_size,
+ bool defer_start = false);
+
+ /// @brief Destructor
+ ///
+ /// Ensures the thread pool is stopped prior to destruction.
+ ~IoServiceThreadPool();
+
+ /// @brief Transitions the pool from STOPPED or PAUSED to RUNNING.
+ ///
+ /// When called from the STOPPED state, the pool threads are created and
+ /// begin processing events.
+ /// When called from the PAUSED state, the pool threads are released
+ /// from PAUSED and resume processing events.
+ /// Has no effect if the pool is already in the RUNNING state.
+ void run();
+
+ /// @brief Transitions the pool from RUNNING to PAUSED.
+ ///
+ /// Pool threads suspend event processing and pause until they
+ /// are released to either resume running or stop.
+ /// Has no effect if the pool is already in the PAUSED or STOPPED
+ /// state.
+ void pause();
+
+ /// @brief Transitions the pool from RUNNING or PAUSED to STOPPED.
+ ///
+ /// Stops thread event processing and then destroys the pool's threads
+ /// Has no effect if the pool is already in the STOPPED state.
+ void stop();
+
+ /// @brief Check if the thread pool is running.
+ ///
+ /// @return True if the thread pool is running, false otherwise.
+ bool isRunning() {
+ return (getState() == State::RUNNING);
+ }
+
+ /// @brief Check if the thread pool is paused.
+ ///
+ /// @return True if the thread pool is paused, false otherwise.
+ bool isPaused() {
+ return (getState() == State::PAUSED);
+ }
+
+ /// @brief Check if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool is stopped, false otherwise.
+ bool isStopped() {
+ return (getState() == State::STOPPED);
+ }
+
+ /// @brief Check current thread permissions to transition to the new PAUSED
+ /// state.
+ ///
+ /// This function throws @ref MultiThreadingInvalidOperation if the calling
+ /// thread is one of the worker threads. This would prevent a dead-lock if
+ /// the calling thread would try to perform a thread pool state transition
+ /// to PAUSED state.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPausePermissions();
+
+private:
+ /// @brief Check current thread permissions to transition to the new state.
+ ///
+ /// This function throws @ref MultiThreadingInvalidOperation if the calling
+ /// thread is one of the worker threads. This would prevent a dead-lock if
+ /// the calling thread would try to perform a thread pool state transition.
+ ///
+ /// @param state The new transition state for the pool.
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions(State state);
+
+ /// @brief Check specified thread id against own threads.
+ ///
+ /// @return true if thread is owned, false otherwise.
+ bool checkThreadId(std::thread::id id);
+
+ /// @brief Thread-safe change of the pool's run state.
+ ///
+ /// Transitions a pool from one run state to another:
+ ///
+ /// When moving from STOPPED or PAUSED to RUNNING:
+ /// -# Sets state to RUNNING.
+ /// -# Notifies threads of state change.
+ /// -# Restarts the IOService.
+ /// -# Creates the threads if they do not yet exist (true only
+ /// when transitioning from STOPPED).
+ /// -# Waits until all threads are running.
+ /// -# Sets the count of exited threads to 0.
+ /// -# Returns to caller.
+ ///
+ /// When moving from RUNNING or PAUSED to STOPPED:
+ /// -# Sets state to STOPPED
+ /// -# Notifies threads of state change.
+ /// -# Polls the IOService to flush handlers.
+ /// -# Stops the IOService.
+ /// -# Waits until all threads have exited the work function.
+ /// -# Joins and destroys the threads.
+ /// -# Returns to caller.
+ ///
+ /// When moving from RUNNING to PAUSED:
+ /// -# Sets state to PAUSED
+ /// -# Notifies threads of state change.
+ /// -# Polls the IOService to flush handlers.
+ /// -# Stops the IOService.
+ /// -# Waits until all threads have paused.
+ /// -# Returns to caller.
+ ///
+ /// @param state The new transition state for the pool.
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void setState(State state);
+
+ /// @brief Thread-safe fetch of the pool's operational state.
+ ///
+ /// @return Thread pool state.
+ State getState();
+
+ /// @brief Validates whether the pool can change to a given state.
+ ///
+ /// @param state new state for the pool.
+ /// @return true if the change is valid, false otherwise.
+ /// @note Must be called from a thread-safe context.
+ bool validateStateChange(State state) const;
+
+ /// @brief Text representation of a given state.
+ ///
+ /// @param state The state for the pool.
+ /// @return The text representation of a given state.
+ static std::string stateToText(State state);
+
+ /// @brief Work function executed by each thread in the pool.
+ ///
+ /// Implements the run state responsibilities for a given thread.
+ /// It executes a run loop until the pool is stopped. At the top
+ /// of each iteration of the loop the pool's run state is checked
+ /// and when it is:
+ ///
+ /// RUNNING:
+ /// -# The count of threads running is incremented.
+ /// -# If the count has reached the number of threads in pool the
+ /// main thread is notified.
+ /// -# IOService::run() is invoked.
+ /// -# When IOService::run() returns, the count of threads running
+ /// is decremented.
+ ///
+ /// PAUSED:
+ /// -# The count of threads paused is incremented.
+ /// -# If the count has reached the number of threads in pool the
+ /// main thread is notified.
+ /// -# Thread blocks until notified the pool's run state is no
+ /// longer PAUSED.
+ /// -# The count of threads paused is decremented.
+ ///
+ /// STOPPED:
+ /// -# The run loop is exited.
+ /// -# The count of threads exited is incremented.
+ /// -# If the count has reached the number of threads in pool the
+ /// main thread is notified.
+ /// -# The function exits.
+ void threadWork();
+
+public:
+ /// @brief Fetches the IOService that drives the pool.
+ ///
+ /// @return the pointer to the IOService.
+ asiolink::IOServicePtr getIOService() const;
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return the maximum size of the thread pool.
+ uint16_t getPoolSize() const;
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return the number of running threads.
+ uint16_t getThreadCount() const;
+
+private:
+ /// @brief Maximum number of threads in the thread pool.
+ size_t pool_size_;
+
+ /// @brief Pointer to private IOService used in multi-threaded mode.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Tracks the operational state of the pool.
+ State run_state_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+
+ /// @brief Condition variable used by threads for synchronization.
+ std::condition_variable thread_cv_;
+
+ /// @brief Condition variable used by main thread to wait on threads
+ /// state transitions.
+ std::condition_variable main_cv_;
+
+ /// @brief Number of threads currently paused.
+ size_t paused_;
+
+ /// @brief Number of threads currently running.
+ size_t running_;
+
+ /// @brief Number of threads that have exited the work function.
+ size_t exited_;
+
+ /// @brief Pool of threads used to service connections in multi-threaded
+ /// mode.
+ std::list<boost::shared_ptr<std::thread> > threads_;
+};
+
+/// @brief Defines a pointer to a thread pool.
+typedef boost::shared_ptr<IoServiceThreadPool> IoServiceThreadPoolPtr;
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/asiolink/io_socket.cc b/src/lib/asiolink/io_socket.cc
new file mode 100644
index 0000000..f7792a9
--- /dev/null
+++ b/src/lib/asiolink/io_socket.cc
@@ -0,0 +1,57 @@
+// 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_socket.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c DummySocket class is a concrete derived class of
+/// \c IOSocket that is not associated with any real socket.
+///
+/// This main purpose of this class is tests, where it may be desirable to
+/// instantiate an \c IOSocket object without involving system resource
+/// allocation such as real network sockets.
+class DummySocket : public IOSocket {
+private:
+ DummySocket(const DummySocket& source);
+ DummySocket& operator=(const DummySocket& source);
+public:
+ /// \brief Constructor from the protocol number.
+ ///
+ /// The protocol must validly identify a standard network protocol.
+ /// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
+ ///
+ /// \param protocol The network protocol number for the socket.
+ DummySocket(const int protocol) : protocol_(protocol) {}
+
+ /// \brief A dummy derived method of \c IOSocket::getNative().
+ ///
+ /// This version of method always returns -1 as the object is not
+ /// associated with a real (native) socket.
+ virtual int getNative() const { return (-1); }
+
+ virtual int getProtocol() const { return (protocol_); }
+private:
+ const int protocol_;
+};
+
+IOSocket&
+IOSocket::getDummyUDPSocket() {
+ static DummySocket socket(IPPROTO_UDP);
+ return (socket);
+}
+
+IOSocket&
+IOSocket::getDummyTCPSocket() {
+ static DummySocket socket(IPPROTO_TCP);
+ return (socket);
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/io_socket.h b/src/lib/asiolink/io_socket.h
new file mode 100644
index 0000000..9c9cee1
--- /dev/null
+++ b/src/lib/asiolink/io_socket.h
@@ -0,0 +1,128 @@
+// Copyright (C) 2010-2017 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 IO_SOCKET_H
+#define IO_SOCKET_H 1
+
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file. In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h> // for some network system calls
+
+#include <functional>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c IOSocket class is an abstract base class to represent
+/// various types of network sockets.
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation. User applications only get access to concrete
+/// \c IOSocket objects via the abstract interfaces.
+///
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it. Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+class IOSocket {
+public:
+
+ /// @name Types of objects encapsulating socket options.
+ //@{
+
+ /// @brief Represents SO_REUSEADDR socket option.
+ typedef boost::asio::socket_base::reuse_address ReuseAddress;
+
+ //@}
+
+ ///
+ /// \name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private, making this class non-copyable.
+ //@{
+private:
+ IOSocket(const IOSocket& source);
+ IOSocket& operator=(const IOSocket& 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).
+ IOSocket() {}
+public:
+ /// The destructor.
+ virtual ~IOSocket() {}
+ //@}
+
+ /// \brief Return the "native" representation of the socket.
+ ///
+ /// In practice, this is the file descriptor of the socket for
+ /// UNIX-like systems so the current implementation simply uses
+ /// \c int as the type of the return value.
+ /// We may have to need revisit this decision later.
+ ///
+ /// In general, the application should avoid using this method;
+ /// it essentially discloses an implementation specific "handle" that
+ /// can change the internal state of the socket (consider the
+ /// application closes it, for example).
+ /// But we sometimes need to perform very low-level operations that
+ /// requires the native representation. Passing the file descriptor
+ /// to a different process is one example.
+ /// This method is provided as a necessary evil for such limited purposes.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return The native representation of the socket. This is the socket
+ /// file descriptor for UNIX-like systems.
+ virtual int getNative() const = 0;
+
+ /// \brief Return the transport protocol of the socket.
+ ///
+ /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+ /// \c IPPROTO_TCP for TCP sockets.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return IPPROTO_UDP for UDP sockets
+ /// \return IPPROTO_TCP for TCP sockets
+ virtual int getProtocol() const = 0;
+
+ /// \brief Return a non-usable "dummy" UDP socket for testing.
+ ///
+ /// This is a class method that returns a "mock" of UDP socket.
+ /// This is not associated with any actual socket, and its only
+ /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
+ /// The only feasible usage of this socket is for testing so that
+ /// the test code can prepare some "UDP data" even without opening any
+ /// actual socket.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \return A reference to an \c IOSocket object whose \c getProtocol()
+ /// returns \c IPPROTO_UDP.
+ static IOSocket& getDummyUDPSocket();
+
+ /// \brief Return a non-usable "dummy" TCP socket for testing.
+ ///
+ /// See \c getDummyUDPSocket(). This method is its TCP version.
+ ///
+ /// \return A reference to an \c IOSocket object whose \c getProtocol()
+ /// returns \c IPPROTO_TCP.
+ static IOSocket& getDummyTCPSocket();
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // IO_SOCKET_H
diff --git a/src/lib/asiolink/openssl_tls.cc b/src/lib/asiolink/openssl_tls.cc
new file mode 100644
index 0000000..270d96a
--- /dev/null
+++ b/src/lib/asiolink/openssl_tls.cc
@@ -0,0 +1,143 @@
+// Copyright (C) 2021-2022 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/.
+
+/// @file openssl_tls.cc OpenSSL implementation of the TLS API.
+
+#include <config.h>
+
+#ifdef WITH_OPENSSL
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/crypto_tls.h>
+
+#include <sys/stat.h>
+
+#include <openssl/opensslv.h>
+
+using namespace boost::asio;
+using namespace boost::asio::ssl;
+using namespace boost::system;
+using namespace isc::cryptolink;
+
+namespace isc {
+namespace asiolink {
+
+// Enforce TLS 1.2 when the generic TLS method is not available (i.e.
+// the boost version is older than 1.64.0).
+TlsContext::TlsContext(TlsRole role)
+ : TlsContextBase(role), cert_required_(true),
+#ifdef HAVE_GENERIC_TLS_METHOD
+ context_(context::method::tls)
+#else
+#ifdef HAVE_TLS_1_2_METHOD
+ context_(context::method::tlsv12)
+#else
+ context_(context::method::tlsv1)
+#endif
+#endif
+{
+ // Not leave the verify mode to OpenSSL default.
+ setCertRequired(true);
+}
+
+boost::asio::ssl::context&
+TlsContext::getContext() {
+ return (context_);
+}
+
+::SSL_CTX*
+TlsContext::getNativeContext() {
+ return (context_.native_handle());
+}
+
+void
+TlsContext::setCertRequired(bool cert_required) {
+ if (!cert_required && (getRole() == TlsRole::CLIENT)) {
+ isc_throw(BadValue,
+ "'cert-required' parameter must be true for a TLS client");
+ }
+ cert_required_ = cert_required;
+ error_code ec;
+ int mode = verify_peer | verify_fail_if_no_peer_cert;
+ if (!cert_required_) {
+ mode = verify_none;
+ }
+ context_.set_verify_mode(mode, ec);
+ if (ec) {
+ isc_throw(LibraryError, getErrMsg(ec));
+ }
+}
+
+bool
+TlsContext::getCertRequired() const {
+ return (cert_required_);
+}
+
+void
+TlsContext::loadCaFile(const std::string& ca_file) {
+ error_code ec;
+ context_.load_verify_file(ca_file, ec);
+ if (ec) {
+ isc_throw(LibraryError, getErrMsg(ec));
+ }
+}
+
+void
+TlsContext::loadCaPath(const std::string& ca_path) {
+ error_code ec;
+ context_.add_verify_path(ca_path, ec);
+ if (ec) {
+ isc_throw(LibraryError, getErrMsg(ec));
+ }
+}
+
+void
+TlsContext::loadCertFile(const std::string& cert_file) {
+ error_code ec;
+ context_.use_certificate_chain_file(cert_file, ec);
+ if (ec) {
+ isc_throw(LibraryError, getErrMsg(ec));
+ }
+}
+
+void
+TlsContext::loadKeyFile(const std::string& key_file) {
+ error_code ec;
+ context_.use_private_key_file(key_file, context::file_format::pem, ec);
+ if (ec) {
+ isc_throw(LibraryError, getErrMsg(ec));
+ }
+}
+
+std::string
+TlsContext::getErrMsg(error_code ec) {
+ std::string msg = ec.message();
+#ifdef ERR_SYSTEM_ERROR
+ // The SSL category message() method uses ERR_reason_error_string()
+ // which since OpenSSL 3.0 returns NULL on system errors in order
+ // to avoid a memory leak with the strerror_r() buffer.
+ // This code recovers the user-friendly message from the error code
+ // value i.e. the OpenSSL error. Layout of OpenSSL errors is detailed
+ // in the OpenSSL err.h header.
+ unsigned long err = static_cast<unsigned long>(ec.value());
+ if ((msg == "asio.ssl error") && (ERR_SYSTEM_ERROR(err))) {
+ char buf[1024];
+#ifndef __USE_GNU
+ if (strerror_r(err & ERR_SYSTEM_MASK, &buf[0], sizeof(buf)) == 0) {
+ msg = buf;
+ }
+#else
+ msg = strerror_r(err & ERR_SYSTEM_MASK, &buf[0], sizeof(buf));
+#endif
+ }
+#endif
+ return (msg);
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_OPENSSL
diff --git a/src/lib/asiolink/openssl_tls.h b/src/lib/asiolink/openssl_tls.h
new file mode 100644
index 0000000..e5a5a21
--- /dev/null
+++ b/src/lib/asiolink/openssl_tls.h
@@ -0,0 +1,242 @@
+// Copyright (C) 2021-2022 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/.
+
+// Do not include this header directly: use crypto_tls.h instead.
+
+#ifndef OPENSSL_TLS_H
+#define OPENSSL_TLS_H
+
+/// @file openssl_tls.h OpenSSL implementation of the TLS API.
+
+#ifdef WITH_OPENSSL
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/common_tls.h>
+
+#include <boost/asio/ssl.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Translate TLS role into implementation.
+inline boost::asio::ssl::stream_base::handshake_type roleToImpl(TlsRole role) {
+ if (role == TlsRole::SERVER) {
+ return (boost::asio::ssl::stream_base::server);
+ } else {
+ return (boost::asio::ssl::stream_base::client);
+ }
+}
+
+/// @brief OpenSSL TLS context.
+class TlsContext : public TlsContextBase {
+public:
+
+ /// @brief Destructor.
+ virtual ~TlsContext() { }
+
+ /// @brief Create a fresh context.
+ ///
+ /// @param role The TLS role client or server.
+ explicit TlsContext(TlsRole role);
+
+ /// @brief Return a reference to the underlying context.
+ boost::asio::ssl::context& getContext();
+
+ /// @brief Return the pointer to the SSL_CTX object.
+ ///
+ /// Currently used only for tests. Please note that since OpenSSL 1.1
+ /// The SSL_CTX type is not fully publicly defined.
+ ::SSL_CTX* getNativeContext();
+
+ /// @brief Get the peer certificate requirement mode.
+ ///
+ /// @return True if peer certificates are required, false if they
+ /// are optional.
+ virtual bool getCertRequired() const;
+
+ /// @brief Get the error message.
+ ///
+ /// @note Wrapper against OpenSSL 3.x not returning error messages
+ /// from system errors.
+ ///
+ /// @param ec The Boost error code.
+ /// @return The error message.
+ static std::string getErrMsg(boost::system::error_code ec);
+
+protected:
+ /// @brief Set the peer certificate requirement mode.
+ ///
+ /// @param cert_required True if peer certificates are required,
+ /// false if they are optional.
+ virtual void setCertRequired(bool cert_required);
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_file The certificate file name.
+ virtual void loadCaFile(const std::string& ca_file);
+
+ /// @brief Load the trust anchor aka certification authority.
+ ///
+ /// @param ca_path The certificate directory name.
+ virtual void loadCaPath(const std::string& ca_path);
+
+ /// @brief Load the certificate file.
+ ///
+ /// @param cert_file The certificate file name.
+ virtual void loadCertFile(const std::string& cert_file);
+
+ /// @brief Load the private key from a file.
+ ///
+ /// @param key_file The private key file name.
+ virtual void loadKeyFile(const std::string& key_file);
+
+ /// @brief Cached cert_required value.
+ bool cert_required_;
+
+ /// @brief Boost ASIO SSL object.
+ boost::asio::ssl::context context_;
+
+ /// @brief Allow access to protected methods by the base class.
+ friend class TlsContextBase;
+};
+
+/// @brief The type of underlying TLS streams.
+typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> TlsStreamImpl;
+
+/// @brief TlsStreamBase constructor.
+///
+/// @tparam Callback The type of callbacks.
+/// @tparam TlsStreamImpl The type of underlying TLS streams.
+/// @param service I/O Service object used to manage the stream.
+/// @param context Pointer to the TLS context.
+/// @note The caller must not provide a null pointer to the TLS context.
+template <typename Callback, typename TlsStreamImpl>
+TlsStreamBase<Callback, TlsStreamImpl>::
+TlsStreamBase(IOService& service, TlsContextPtr context)
+ : TlsStreamImpl(service.get_io_service(), context->getContext()),
+ role_(context->getRole()) {
+}
+
+/// @brief OpenSSL TLS stream.
+///
+/// @tparam callback The callback.
+template <typename Callback>
+class TlsStream : public TlsStreamBase<Callback, TlsStreamImpl> {
+public:
+
+ /// @brief Type of the base.
+ typedef TlsStreamBase<Callback, TlsStreamImpl> Base;
+
+ /// @brief Constructor.
+ ///
+ /// @param service I/O Service object used to manage the stream.
+ /// @param context Pointer to the TLS context.
+ /// @note The caller must not provide a null pointer to the TLS context.
+ TlsStream(IOService& service, TlsContextPtr context)
+ : Base(service, context) {
+ }
+
+ /// @brief Destructor.
+ virtual ~TlsStream() { }
+
+ /// @brief TLS Handshake.
+ ///
+ /// @param callback Callback object.
+ virtual void handshake(Callback& callback) {
+ Base::async_handshake(roleToImpl(Base::getRole()), callback);
+ }
+
+ /// @brief TLS shutdown.
+ ///
+ /// @param callback Callback object.
+ virtual void shutdown(Callback& callback) {
+ Base::async_shutdown(callback);
+ }
+
+ /// @brief Return the commonName part of the subjectName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// RFC 3280 provides as a commonName example "Susan Housley",
+ /// to idea to give access to this come from the Role Based
+ /// Access Control experiment.
+ ///
+ ///
+ /// @return The commonName part of the subjectName or the empty string.
+ virtual std::string getSubject() {
+ ::X509* cert = ::SSL_get_peer_certificate(this->native_handle());
+ if (!cert) {
+ return ("");
+ }
+ ::X509_NAME *name = ::X509_get_subject_name(cert);
+ int loc = ::X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+ ::X509_NAME_ENTRY* ne = ::X509_NAME_get_entry(name, loc);
+ if (!ne) {
+ ::X509_free(cert);
+ return ("");
+ }
+ unsigned char* buf = 0;
+ int len = ::ASN1_STRING_to_UTF8(&buf, ::X509_NAME_ENTRY_get_data(ne));
+ if (len < 0) {
+ ::X509_free(cert);
+ return ("");
+ }
+ std::string ret(reinterpret_cast<char*>(buf), static_cast<size_t>(len));
+ ::OPENSSL_free(buf);
+ ::X509_free(cert);
+ return (ret);
+ }
+
+ /// @brief Return the commonName part of the issuerName of
+ /// the peer certificate.
+ ///
+ /// First commonName when there are more than one, in UTF-8.
+ /// The issuerName is the subjectName of the signing certificate
+ /// (the issue in PKIX terms). The idea is to encode a group as
+ /// members of an intermediate certification authority.
+ ///
+ ///
+ /// @return The commonName part of the issuerName or the empty string.
+ virtual std::string getIssuer() {
+ ::X509* cert = ::SSL_get_peer_certificate(this->native_handle());
+ if (!cert) {
+ return ("");
+ }
+ ::X509_NAME *name = ::X509_get_issuer_name(cert);
+ int loc = ::X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+ ::X509_NAME_ENTRY* ne = ::X509_NAME_get_entry(name, loc);
+ if (!ne) {
+ ::X509_free(cert);
+ return ("");
+ }
+ unsigned char* buf = 0;
+ int len = ::ASN1_STRING_to_UTF8(&buf, ::X509_NAME_ENTRY_get_data(ne));
+ if (len < 0) {
+ ::X509_free(cert);
+ return ("");
+ }
+ std::string ret(reinterpret_cast<char*>(buf), static_cast<size_t>(len));
+ ::OPENSSL_free(buf);
+ ::X509_free(cert);
+ return (ret);
+ }
+};
+
+// Stream truncated error code.
+#ifdef HAVE_STREAM_TRUNCATED_ERROR
+const int STREAM_TRUNCATED = boost::asio::ssl::error::stream_truncated;
+#else
+const int STREAM_TRUNCATED = ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ);
+#endif
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // WITH_OPENSSL
+
+#endif // OPENSSL_TLS_H
diff --git a/src/lib/asiolink/process_spawn.cc b/src/lib/asiolink/process_spawn.cc
new file mode 100644
index 0000000..a245065
--- /dev/null
+++ b/src/lib/asiolink/process_spawn.cc
@@ -0,0 +1,448 @@
+// Copyright (C) 2015-2023 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 <asiolink/io_service_signal.h>
+#include <asiolink/process_spawn.h>
+#include <exceptions/exceptions.h>
+#include <cstring>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <signal.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <boost/make_shared.hpp>
+
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Type for process state
+struct ProcessState {
+
+ /// @brief Constructor
+ ProcessState() : running_(true), status_(0) {
+ }
+
+ /// @brief true until the exit status is collected
+ bool running_;
+
+ /// @brief 0 or the exit status
+ int status_;
+};
+
+/// @brief Defines a pointer to a ProcessState.
+typedef boost::shared_ptr<ProcessState> ProcessStatePtr;
+
+/// @brief ProcessStates container which stores a ProcessState for each process
+/// identified by PID.
+typedef std::map<pid_t, ProcessStatePtr> ProcessStates;
+
+class ProcessSpawnImpl;
+
+/// @brief ProcessCollection container which stores all ProcessStates for each
+/// instance of @ref ProcessSpawnImpl.
+typedef std::map<const ProcessSpawnImpl*, ProcessStates> ProcessCollection;
+
+/// @brief Implementation of the @c ProcessSpawn class.
+///
+/// This pimpl idiom is used by the @c ProcessSpawn in this case to
+/// avoid exposing the internals of the implementation, such as
+/// custom handling of a SIGCHLD signal, and the conversion of the
+/// arguments of the executable from the STL container to the array.
+///
+/// This class is made noncopyable so that we don't have attempts
+/// to make multiple copies of an object. This avoid problems
+/// with multiple copies of objects for a single global resource
+/// such as the SIGCHLD signal handler. In addition making it
+/// noncopyable keeps the static check code from flagging the
+/// lack of a copy constructor as an issue.
+class ProcessSpawnImpl : boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service The IOService which handles signal handlers.
+ /// @param executable A full path to the program to be executed.
+ /// @param args Arguments for the program to be executed.
+ /// @param vars Environment variables for the program to be executed.
+ ProcessSpawnImpl(IOServicePtr io_service,
+ const std::string& executable,
+ const ProcessArgs& args,
+ const ProcessEnvVars& vars);
+
+ /// @brief Destructor.
+ ~ProcessSpawnImpl();
+
+ /// @brief Returns full command line, including arguments, for the process.
+ std::string getCommandLine() const;
+
+ /// @brief Spawn the new process.
+ ///
+ /// This method forks the current process and executes the specified
+ /// binary with arguments within the child process.
+ ///
+ /// The child process will return EXIT_FAILURE if the method was unable
+ /// to start the executable, e.g. as a result of insufficient permissions
+ /// or when the executable does not exist. If the process ends successfully
+ /// the EXIT_SUCCESS is returned.
+ ///
+ /// @param dismiss The flag which indicated if the process status can be
+ /// disregarded.
+ /// @return PID of the spawned process.
+ /// @throw ProcessSpawnError if forking a current process failed.
+ pid_t spawn(bool dismiss);
+
+ /// @brief Checks if the process is still running.
+ ///
+ /// @param pid ID of the child processes for which state should be checked.
+ /// @return true if the child process is running, false otherwise.
+ bool isRunning(const pid_t pid) const;
+
+ /// @brief Checks if any of the spawned processes is still running.
+ ///
+ /// @return true if at least one child process is still running.
+ bool isAnyRunning() const;
+
+ /// @brief Returns exit status of the process.
+ ///
+ /// If the process is still running, the previous status is returned
+ /// or 0, if the process is being ran for the first time.
+ ///
+ /// @param pid ID of the child process for which exit status should be
+ /// returned.
+ /// @return Exit code of the process.
+ int getExitStatus(const pid_t pid) const;
+
+ /// @brief Removes the status of the process with a specified PID.
+ ///
+ /// This method removes the status of the process with a specified PID.
+ /// If the process is still running, the status is not removed and the
+ /// exception is thrown.
+ ///
+ /// @param pid A process pid.
+ void clearState(const pid_t pid);
+
+private:
+
+ /// @brief Initializer class for the SIGCHLD signal handler.
+ ///
+ /// This is a singleton class used to initialize the SIGCHLD signal handler
+ /// only on the first call of @ref initIOSignalSet which happens on each
+ /// call of @ref ProcessSpawn::spawn.
+ class IOSignalSetInitializer {
+ private:
+
+ /// @brief Constructor
+ ///
+ /// @param io_service The IOService which handles signal handlers.
+ IOSignalSetInitializer(IOServicePtr io_service) {
+ if (!io_service) {
+ isc_throw(ProcessSpawnError, "NULL IOService instance");
+ }
+ io_signal_set_ = boost::make_shared<IOSignalSet>(io_service,
+ std::bind(&ProcessSpawnImpl::waitForProcess, ph::_1));
+ io_signal_set_->add(SIGCHLD);
+ }
+
+ /// @brief Destructor
+ ~IOSignalSetInitializer() {
+ io_signal_set_->remove(SIGCHLD);
+ }
+
+ public:
+
+ /// @brief Initialize the SIGCHLD signal handler.
+ ///
+ /// It creates the single instance of @ref IOSignalSetInitializer.
+ ///
+ /// @param io_service The IOService which handles signal handlers.
+ static void initIOSignalSet(IOServicePtr io_service);
+
+ private:
+
+ /// @brief ASIO signal set.
+ IOSignalSetPtr io_signal_set_;
+ };
+
+ /// @brief Copies the argument specified as a C++ string to the new
+ /// C string.
+ ///
+ /// This method is used to convert arguments specified as an STL container
+ /// holding @c std::string objects to an array of C strings, used by the
+ /// @c execve function in the @c ProcessSpawnImpl::spawn. It allocates a
+ /// new C string and copies the contents of the @c src to it.
+ /// The data is stored in an internal container so that the caller of the
+ /// function can be exception safe.
+ ///
+ /// @param src A source string.
+ ///
+ /// @return Allocated C string holding the data from @c src.
+ char* allocateInternal(const std::string& src);
+
+ /// @brief Signal handler for SIGCHLD.
+ ///
+ /// This handler waits for the child process to finish and retrieves
+ /// its exit code into the @c status_ member.
+ ///
+ /// @return true if the processed signal was SIGCHLD or false if it
+ /// was a different signal.
+ static bool waitForProcess(int signum);
+
+ /// @brief A map holding the status codes of executed processes.
+ static ProcessCollection process_collection_;
+
+ /// @brief Path to an executable.
+ std::string executable_;
+
+ /// @brief An array holding arguments for the executable.
+ boost::shared_ptr<char*[]> args_;
+
+ /// @brief An array holding environment variables for the executable.
+ boost::shared_ptr<char*[]> vars_;
+
+ /// @brief Typedef for CString pointer.
+ typedef boost::shared_ptr<char[]> CStringPtr;
+
+ /// @brief An storage container for all allocated C strings.
+ std::vector<CStringPtr> storage_;
+
+ /// @brief Flag to indicate if process status must be stored.
+ bool store_;
+
+ /// @brief Mutex to protect internal state.
+ static std::mutex mutex_;
+
+ /// @brief The IOService which handles IO operations.
+ IOServicePtr io_service_;
+};
+
+ProcessCollection ProcessSpawnImpl::process_collection_;
+std::mutex ProcessSpawnImpl::mutex_;
+
+void ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(IOServicePtr io_service) {
+ static IOSignalSetInitializer init(io_service);
+}
+
+ProcessSpawnImpl::ProcessSpawnImpl(IOServicePtr io_service,
+ const std::string& executable,
+ const ProcessArgs& args,
+ const ProcessEnvVars& vars)
+ : executable_(executable), args_(new char*[args.size() + 2]),
+ vars_(new char*[vars.size() + 1]), store_(false), io_service_(io_service) {
+
+ struct stat st;
+
+ if (stat(executable_.c_str(), &st)) {
+ isc_throw(ProcessSpawnError, "File not found: " << executable_);
+ }
+
+ if (!(st.st_mode & S_IEXEC)) {
+ isc_throw(ProcessSpawnError, "File not executable: " << executable_);
+ }
+
+ // Conversion of the arguments to the C-style array we start by setting
+ // all pointers within an array to NULL to indicate that they haven't
+ // been allocated yet.
+ memset(args_.get(), 0, (args.size() + 2) * sizeof(char*));
+ memset(vars_.get(), 0, (vars.size() + 1) * sizeof(char*));
+ // By convention, the first argument points to an executable name.
+ args_[0] = allocateInternal(executable_);
+ // Copy arguments to the array.
+ for (int i = 1; i <= args.size(); ++i) {
+ args_[i] = allocateInternal(args[i - 1]);
+ }
+ // Copy environment variables to the array.
+ for (int i = 0; i < vars.size(); ++i) {
+ vars_[i] = allocateInternal(vars[i]);
+ }
+}
+
+ProcessSpawnImpl::~ProcessSpawnImpl() {
+ if (store_) {
+ lock_guard<std::mutex> lk(mutex_);
+ process_collection_.erase(this);
+ }
+}
+
+std::string
+ProcessSpawnImpl::getCommandLine() const {
+ std::ostringstream s;
+ s << executable_;
+ // Start with index 1, because the first argument duplicates the
+ // path to the executable. Note, that even if there are no parameters
+ // the minimum size of the table is 2.
+ int i = 1;
+ while (args_[i] != NULL) {
+ s << " " << args_[i];
+ ++i;
+ }
+ return (s.str());
+}
+
+pid_t
+ProcessSpawnImpl::spawn(bool dismiss) {
+ lock_guard<std::mutex> lk(mutex_);
+ ProcessSpawnImpl::IOSignalSetInitializer::initIOSignalSet(io_service_);
+ // Create the child
+ pid_t pid = fork();
+ if (pid < 0) {
+ isc_throw(ProcessSpawnError, "unable to fork current process");
+
+ } else if (pid == 0) {
+ // Reset masked signals for the child process.
+ sigset_t sset;
+ sigemptyset(&sset);
+ pthread_sigmask(SIG_SETMASK, &sset, 0);
+ // Run the executable.
+ execve(executable_.c_str(), args_.get(), vars_.get());
+ // We may end up here if the execve failed, e.g. as a result
+ // of issue with permissions or invalid executable name.
+ _exit(EXIT_FAILURE);
+ }
+
+ // We're in the parent process.
+ if (!dismiss) {
+ store_ = true;
+ process_collection_[this].insert(std::pair<pid_t, ProcessStatePtr>(pid, ProcessStatePtr(new ProcessState())));
+ }
+ return (pid);
+}
+
+bool
+ProcessSpawnImpl::isRunning(const pid_t pid) const {
+ lock_guard<std::mutex> lk(mutex_);
+ ProcessStates::const_iterator proc;
+ if (process_collection_.find(this) == process_collection_.end() ||
+ (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
+ isc_throw(BadValue, "the process with the pid '" << pid
+ << "' hasn't been spawned and it status cannot be"
+ " returned");
+ }
+ return (proc->second->running_);
+}
+
+bool
+ProcessSpawnImpl::isAnyRunning() const {
+ lock_guard<std::mutex> lk(mutex_);
+ if (process_collection_.find(this) != process_collection_.end()) {
+ for (auto const& proc : process_collection_[this]) {
+ if (proc.second->running_) {
+ return (true);
+ }
+ }
+ }
+ return (false);
+}
+
+int
+ProcessSpawnImpl::getExitStatus(const pid_t pid) const {
+ lock_guard<std::mutex> lk(mutex_);
+ ProcessStates::const_iterator proc;
+ if (process_collection_.find(this) == process_collection_.end() ||
+ (proc = process_collection_[this].find(pid)) == process_collection_[this].end()) {
+ isc_throw(InvalidOperation, "the process with the pid '" << pid
+ << "' hasn't been spawned and it status cannot be"
+ " returned");
+ }
+ return (WEXITSTATUS(proc->second->status_));
+}
+
+char*
+ProcessSpawnImpl::allocateInternal(const std::string& src) {
+ const size_t src_len = src.length();
+ storage_.push_back(CStringPtr(new char[src_len + 1]));
+ // Allocate the C-string with one byte more for the null termination.
+ char* dest = storage_[storage_.size() - 1].get();
+ // copy doesn't append the null at the end.
+ src.copy(dest, src_len);
+ // Append null on our own.
+ dest[src_len] = '\0';
+ return (dest);
+}
+
+bool
+ProcessSpawnImpl::waitForProcess(int) {
+ lock_guard<std::mutex> lk(mutex_);
+ for (;;) {
+ int status = 0;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+ if (pid <= 0) {
+ break;
+ }
+ for (auto const& instance : process_collection_) {
+ auto const& proc = instance.second.find(pid);
+ /// Check that the terminating process was started
+ /// by our instance of ProcessSpawn
+ if (proc != instance.second.end()) {
+ // In this order please
+ proc->second->status_ = status;
+ proc->second->running_ = false;
+ }
+ }
+ }
+ return (true);
+}
+
+void
+ProcessSpawnImpl::clearState(const pid_t pid) {
+ if (isRunning(pid)) {
+ isc_throw(InvalidOperation, "unable to remove the status for the "
+ "process (pid: " << pid << ") which is still running");
+ }
+ lock_guard<std::mutex> lk(mutex_);
+ if (process_collection_.find(this) != process_collection_.end()) {
+ process_collection_[this].erase(pid);
+ }
+}
+
+ProcessSpawn::ProcessSpawn(IOServicePtr io_service,
+ const std::string& executable,
+ const ProcessArgs& args,
+ const ProcessEnvVars& vars)
+ : impl_(new ProcessSpawnImpl(io_service, executable, args, vars)) {
+}
+
+std::string
+ProcessSpawn::getCommandLine() const {
+ return (impl_->getCommandLine());
+}
+
+pid_t
+ProcessSpawn::spawn(bool dismiss) {
+ return (impl_->spawn(dismiss));
+}
+
+bool
+ProcessSpawn::isRunning(const pid_t pid) const {
+ return (impl_->isRunning(pid));
+}
+
+bool
+ProcessSpawn::isAnyRunning() const {
+ return (impl_->isAnyRunning());
+}
+
+int
+ProcessSpawn::getExitStatus(const pid_t pid) const {
+ return (impl_->getExitStatus(pid));
+}
+
+void
+ProcessSpawn::clearState(const pid_t pid) {
+ return (impl_->clearState(pid));
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/process_spawn.h b/src/lib/asiolink/process_spawn.h
new file mode 100644
index 0000000..ca72ce8
--- /dev/null
+++ b/src/lib/asiolink/process_spawn.h
@@ -0,0 +1,149 @@
+// Copyright (C) 2015-2021 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 PROCESS_SPAWN_H
+#define PROCESS_SPAWN_H
+
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <sys/types.h>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Exception thrown when error occurs during spawning a process.
+class ProcessSpawnError : public Exception {
+public:
+ ProcessSpawnError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the implementation of the @c ProcessSpawn
+/// class.
+class ProcessSpawnImpl;
+
+/// @brief Pointer to a ProcessSpawnImpl class.
+typedef boost::shared_ptr<ProcessSpawnImpl> ProcessSpawnImplPtr;
+
+/// @brief Type of the container holding arguments of the executable
+/// being run as a background process.
+typedef std::vector<std::string> ProcessArgs;
+
+/// @brief Type of the container holding environment variables of the executable
+/// being run as a background process.
+typedef std::vector<std::string> ProcessEnvVars;
+
+/// @brief Utility class for spawning new processes.
+///
+/// This class is used to spawn new process by Kea. It forks the current
+/// process and then uses the @c execve function to execute the specified
+/// binary with parameters. The @c ProcessSpawn installs the handler for
+/// the SIGCHLD signal, which is executed when the child process ends.
+/// The handler checks the exit code returned by the process and records
+/// it. The exit code can be retrieved by the caller using the
+/// @c ProcessSpawn::getExitStatus method.
+///
+/// This class is made noncopyable so that we don't have attempts
+/// to make multiple copies of an object. This avoid problems
+/// with multiple copies of objects for a single global resource
+/// such as the SIGCHLD signal handler. In addition making it
+/// noncopyable keeps the static check code from flagging the
+/// lack of a copy constructor as an issue.
+///
+/// @note The ProcessSpawn uses full path for the program to execute.
+class ProcessSpawn : boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service The IOService which handles signal handlers.
+ /// @param executable A full path to the program to be executed.
+ /// @param args Arguments for the program to be executed.
+ /// @param vars Environment variables for the program to be executed.
+ ProcessSpawn(isc::asiolink::IOServicePtr io_service,
+ const std::string& executable,
+ const ProcessArgs& args = ProcessArgs(),
+ const ProcessEnvVars& vars = ProcessEnvVars());
+
+ /// @brief Destructor.
+ ~ProcessSpawn() = default;
+
+ /// @brief Returns full command line, including arguments, for the process.
+ std::string getCommandLine() const;
+
+ /// @brief Spawn the new process.
+ ///
+ /// This method forks the current process and executes the specified
+ /// binary with arguments within the child process.
+ ///
+ /// The child process will return EXIT_FAILURE if the method was unable
+ /// to start the executable, e.g. as a result of insufficient permissions
+ /// or when the executable does not exist. If the process ends successfully
+ /// the EXIT_SUCCESS is returned.
+ ///
+ /// @param dismiss The flag which indicated if the process status can be
+ /// disregarded.
+ /// @throw ProcessSpawnError if forking a current process failed.
+ pid_t spawn(bool dismiss = false);
+
+ /// @brief Checks if the process is still running.
+ ///
+ /// Note that only a negative (false) result is reliable as the child
+ /// process can exit between the time its state is checked and this
+ /// function returns.
+ ///
+ /// @param pid ID of the child processes for which state should be checked.
+ ///
+ /// @return true if the child process is running, false otherwise.
+ bool isRunning(const pid_t pid) const;
+
+ /// @brief Checks if any of the spawned processes is still running.
+ ///
+ /// @return true if at least one child process is still running.
+ bool isAnyRunning() const;
+
+ /// @brief Returns exit status of the process.
+ ///
+ /// If the process is still running, the previous status is returned
+ /// or 0, if the process is being ran for the first time.
+ ///
+ /// @note @c ProcessSpawn::isRunning should be called and have returned
+ /// false before using @c ProcessSpawn::getExitStatus.
+ ///
+ /// @param pid ID of the child process for which exit status should be
+ /// returned.
+ ///
+ /// @return Exit code of the process.
+ int getExitStatus(const pid_t pid) const;
+
+ /// @brief Removes the status of the process with a specified PID.
+ ///
+ /// This method removes the status of the process with a specified PID.
+ /// If the process is still running, the status is not removed and the
+ /// exception is thrown.
+ ///
+ /// Note @c ProcessSpawn::isRunning must be called and have returned
+ /// false before using clearState(). And of course
+ /// @c ProcessSpawn::getExitStatus should be called first, if there is
+ /// some interest in the status.
+ ///
+ /// @param pid A process pid.
+ void clearState(const pid_t pid);
+
+private:
+
+ /// @brief A smart pointer to the implementation of this class.
+ ProcessSpawnImplPtr impl_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // PROCESS_SPAWN_H
diff --git a/src/lib/asiolink/tcp_acceptor.h b/src/lib/asiolink/tcp_acceptor.h
new file mode 100644
index 0000000..4fab1dd
--- /dev/null
+++ b/src/lib/asiolink/tcp_acceptor.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2016-2017 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 TCP_ACCEPTOR_H
+#define TCP_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <netinet/in.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Provides a service for accepting new TCP connections.
+///
+/// Internally it uses @c boost::asio::ip::tcp::acceptor class to implement
+/// the acceptor service.
+///
+/// @tparam C Acceptor callback type.
+template<typename C>
+class TCPAcceptor : public IOAcceptor<boost::asio::ip::tcp, C> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ explicit TCPAcceptor(IOService& io_service)
+ : IOAcceptor<boost::asio::ip::tcp, C>(io_service) {
+ }
+
+ /// @brief Returns protocol of the socket.
+ ///
+ /// @return IPPROTO_TCP.
+ virtual int getProtocol() const final {
+ return (IPPROTO_TCP);
+ }
+
+ /// @brief Asynchronously accept new connection.
+ ///
+ /// This method accepts new connection into the specified socket. When the
+ /// new connection arrives or an error occurs the specified callback function
+ /// is invoked.
+ ///
+ /// @param socket Socket into which connection should be accepted.
+ /// @param callback Callback function to be invoked when the new connection
+ /// arrives.
+ /// @tparam SocketCallback Type of the callback for the @ref TCPSocket.
+ template<typename SocketCallback>
+ void asyncAccept(const TCPSocket<SocketCallback>& socket, C& callback) {
+ IOAcceptor<boost::asio::ip::tcp, C>::asyncAcceptInternal(socket, callback);
+ }
+};
+
+
+} // namespace asiolink
+} // namespace isc
+
+#endif
diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h
new file mode 100644
index 0000000..7cbb73b
--- /dev/null
+++ b/src/lib/asiolink/tcp_endpoint.h
@@ -0,0 +1,115 @@
+// 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/.
+
+#ifndef TCP_ENDPOINT_H
+#define TCP_ENDPOINT_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c TCPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a TCP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class TCPEndpoint : public IOEndpoint {
+public:
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ //@{
+
+ /// \brief Default Constructor
+ ///
+ /// Creates an internal endpoint. This is expected to be set by some
+ /// external call.
+ TCPEndpoint() :
+ asio_endpoint_placeholder_(new boost::asio::ip::tcp::endpoint()),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from a pair of address and port.
+ ///
+ /// \param address The IP address of the endpoint.
+ /// \param port The TCP port number of the endpoint.
+ TCPEndpoint(const IOAddress& address, const unsigned short port) :
+ asio_endpoint_placeholder_(
+ new boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address.toText()),
+ port)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from an ASIO TCP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c tcp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ TCPEndpoint(boost::asio::ip::tcp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+ {}
+
+ /// \brief Constructor from an ASIO TCP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c tcp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ TCPEndpoint(const boost::asio::ip::tcp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(new boost::asio::ip::tcp::endpoint(asio_endpoint)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief The destructor.
+ virtual ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+ //@}
+
+ virtual IOAddress getAddress() const {
+ return (asio_endpoint_.address());
+ }
+
+ virtual const struct sockaddr& getSockAddr() const {
+ return (*asio_endpoint_.data());
+ }
+
+ virtual uint16_t getPort() const {
+ return (asio_endpoint_.port());
+ }
+
+ virtual short getProtocol() const {
+ return (asio_endpoint_.protocol().protocol());
+ }
+
+ virtual short getFamily() const {
+ return (asio_endpoint_.protocol().family());
+ }
+
+ // This is not part of the exposed IOEndpoint API but allows
+ // direct access to the ASIO implementation of the endpoint
+ inline const boost::asio::ip::tcp::endpoint& getASIOEndpoint() const {
+ return (asio_endpoint_);
+ }
+ inline boost::asio::ip::tcp::endpoint& getASIOEndpoint() {
+ return (asio_endpoint_);
+ }
+
+private:
+ boost::asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
+ boost::asio::ip::tcp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+} // namespace isc
+#endif // TCP_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
new file mode 100644
index 0000000..c8ea454
--- /dev/null
+++ b/src/lib/asiolink/tcp_socket.h
@@ -0,0 +1,503 @@
+// Copyright (C) 2011-2022 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 TCP_SOCKET_H
+#define TCP_SOCKET_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <algorithm>
+#include <cstddef>
+
+#include <boost/numeric/conversion/cast.hpp>
+
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+
+#include <exceptions/isc_assert.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief Buffer Too Large
+///
+/// Thrown on an attempt to send a buffer > 64k
+class BufferTooLarge : public IOError {
+public:
+ BufferTooLarge(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
+/// \brief The \c TCPSocket class is a concrete derived class of \c IOAsioSocket
+/// that represents a TCP socket.
+///
+/// \param C Callback type
+template <typename C>
+class TCPSocket : public IOAsioSocket<C> {
+private:
+ /// \brief Class is non-copyable
+ TCPSocket(const TCPSocket&);
+ TCPSocket& operator=(const TCPSocket&);
+
+public:
+
+ /// \brief Constructor from an ASIO TCP socket.
+ ///
+ /// \param socket The ASIO representation of the TCP socket. It is assumed
+ /// that the caller will open and close the socket, so these
+ /// operations are a no-op for that socket.
+ TCPSocket(boost::asio::ip::tcp::socket& socket);
+
+ /// \brief Constructor
+ ///
+ /// Used when the TCPSocket is being asked to manage its own internal
+ /// socket. In this case, the open() and close() methods are used.
+ ///
+ /// \param service I/O Service object used to manage the socket.
+ TCPSocket(IOService& service);
+
+ /// \brief Destructor
+ virtual ~TCPSocket();
+
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+#if BOOST_VERSION < 106600
+ return (socket_.native());
+#else
+ return (socket_.native_handle());
+#endif
+ }
+
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_TCP);
+ }
+
+ /// \brief Is "open()" synchronous?
+ ///
+ /// Indicates that the opening of a TCP socket is asynchronous.
+ virtual bool isOpenSynchronous() const {
+ return (false);
+ }
+
+ /// \brief Checks if the connection is usable.
+ ///
+ /// The connection is usable if the socket is open and the peer has not
+ /// closed its connection.
+ ///
+ /// \return true if the connection is usable.
+ bool isUsable() const {
+ // If the socket is open it doesn't mean that it is still usable. The connection
+ // could have been closed on the other end. We have to check if we can still
+ // use this socket.
+ if (socket_.is_open()) {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = socket_.non_blocking();
+ // Set the socket to non blocking mode. We're going to test if the socket
+ // returns would_block status on the attempt to read from it.
+ socket_.non_blocking(true);
+
+ boost::system::error_code ec;
+ char data[2];
+
+ // Use receive with message peek flag to avoid removing the data awaiting
+ // to be read.
+ socket_.receive(boost::asio::buffer(data, sizeof(data)),
+ boost::asio::socket_base::message_peek,
+ ec);
+
+ // Revert the original non_blocking flag on the socket.
+ socket_.non_blocking(non_blocking_orig);
+
+ // If the connection is alive we'd typically get would_block status code.
+ // If there are any data that haven't been read we may also get success
+ // status. We're guessing that try_again may also be returned by some
+ // implementations in some situations. Any other error code indicates a
+ // problem with the connection so we assume that the connection has been
+ // closed.
+ return (!ec || (ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block));
+ }
+
+ return (false);
+ }
+
+ /// \brief Open Socket
+ ///
+ /// Opens the TCP socket. This is an asynchronous operation, completion of
+ /// which will be signalled via a call to the callback function.
+ ///
+ /// \param endpoint Endpoint to which the socket will connect.
+ /// \param callback Callback object.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Send Asynchronously
+ ///
+ /// Calls the underlying socket's async_send() method to send a packet of
+ /// data asynchronously to the remote endpoint. The callback will be called
+ /// on completion.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param endpoint Target of the send. (Unused for a TCP socket because
+ /// that was determined when the connection was opened.)
+ /// \param callback Callback object.
+ /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Send Asynchronously without count.
+ ///
+ /// This variant of the method sends data over the TCP socket without
+ /// preceding the data with a data count. Eventually, we should migrate
+ /// the virtual method to not insert the count but there are existing
+ /// classes using the count. Once this migration is done, the existing
+ /// virtual method should be replaced by this method.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param callback Callback object.
+ /// \throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+ void asyncSend(const void* data, size_t length, C& callback);
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Calls the underlying socket's async_receive() method to read a packet
+ /// of data from a remote endpoint. Arrival of the data is signalled via a
+ /// call to the callback function.
+ ///
+ /// \param data Buffer to receive incoming message
+ /// \param length Length of the data buffer
+ /// \param offset Offset into buffer where data is to be put
+ /// \param endpoint Source of the communication
+ /// \param callback Callback object
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback);
+
+ /// \brief Process received data packet
+ ///
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
+ ///
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed.
+ /// \param offset Unused.
+ /// \param expected unused.
+ /// \param outbuff Output buffer. Data in the staging buffer is be copied
+ /// to this output buffer in the call.
+ ///
+ /// \return Always true
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::util::OutputBufferPtr& outbuff);
+
+ /// \brief Cancel I/O On Socket
+ virtual void cancel();
+
+ /// \brief Close socket
+ virtual void close();
+
+ /// \brief Returns reference to the underlying ASIO socket.
+ ///
+ /// \return Reference to underlying ASIO socket.
+ virtual boost::asio::ip::tcp::socket& getASIOSocket() const {
+ return (socket_);
+ }
+
+private:
+ /// Two variables to hold the socket - a socket and a pointer to it. This
+ /// handles the case where a socket is passed to the TCPSocket on
+ /// construction, or where it is asked to manage its own socket.
+
+ /// Pointer to own socket
+ std::unique_ptr<boost::asio::ip::tcp::socket> socket_ptr_;
+
+ /// Socket
+ boost::asio::ip::tcp::socket& socket_;
+
+ /// @todo Remove temporary buffer
+ /// The current implementation copies the buffer passed to asyncSend() into
+ /// a temporary buffer and precedes it with a two-byte count field. As
+ /// ASIO should really be just about sending and receiving data, the TCP
+ /// code should not do this. If the protocol using this requires a two-byte
+ /// count, it should add it before calling this code. (This may be best
+ /// achieved by altering isc::dns::buffer to have pairs of methods:
+ /// getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
+ /// methods taking into account a two-byte count field.)
+ ///
+ /// The option of sending the data in two operations, the count followed by
+ /// the data was discounted as that would lead to two callbacks which would
+ /// cause problems with the stackless coroutine code.
+
+ /// Send buffer
+ isc::util::OutputBufferPtr send_buffer_;
+};
+
+// Constructor - caller manages socket
+
+template <typename C>
+TCPSocket<C>::TCPSocket(boost::asio::ip::tcp::socket& socket) :
+ socket_ptr_(), socket_(socket), send_buffer_()
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+TCPSocket<C>::TCPSocket(IOService& service) :
+ socket_ptr_(new boost::asio::ip::tcp::socket(service.get_io_service())),
+ socket_(*socket_ptr_)
+{
+}
+
+// Destructor.
+
+template <typename C>
+TCPSocket<C>::~TCPSocket()
+{
+}
+
+// Open the socket.
+
+template <typename C> void
+TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
+ // If socket is open on this end but has been closed by the peer,
+ // we need to reconnect.
+ if (socket_.is_open() && !isUsable()) {
+ close();
+ }
+ // Ignore opens on already-open socket. Don't throw a failure because
+ // of uncertainties as to what precedes when using asynchronous I/O.
+ // Also allows us a treat a passed-in socket as a self-managed socket.
+ if (!socket_.is_open()) {
+ if (endpoint->getFamily() == AF_INET) {
+ socket_.open(boost::asio::ip::tcp::v4());
+ } else {
+ socket_.open(boost::asio::ip::tcp::v6());
+ }
+
+ // Set options on the socket:
+
+ // Reuse address - allow the socket to bind to a port even if the port
+ // is in the TIMED_WAIT state.
+ socket_.set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
+ // contain a method for getting at the underlying endpoint type - that is in
+ /// the derived class and the two classes differ on return type.
+ isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+ const TCPEndpoint* tcp_endpoint =
+ static_cast<const TCPEndpoint*>(endpoint);
+
+ // Connect to the remote endpoint. On success, the handler will be
+ // called (with one argument - the length argument will default to
+ // zero).
+ socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
+}
+
+// Send a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length, C& callback)
+{
+ if (socket_.is_open()) {
+
+ try {
+ send_buffer_.reset(new isc::util::OutputBuffer(length));
+ send_buffer_->writeData(data, length);
+
+ // Send the data.
+ socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
+ send_buffer_->getLength()),
+ callback);
+ } catch (const boost::numeric::bad_numeric_cast&) {
+ isc_throw(BufferTooLarge,
+ "attempt to send buffer larger than 64kB");
+ }
+
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a TCP socket that is not open");
+ }
+}
+
+template <typename C> void
+TCPSocket<C>::asyncSend(const void* data, size_t length,
+ const IOEndpoint*, C& callback)
+{
+ if (socket_.is_open()) {
+
+ /// Need to copy the data into a temporary buffer and precede it with
+ /// a two-byte count field.
+ /// @todo arrange for the buffer passed to be preceded by the count
+ try {
+ /// Ensure it fits into 16 bits
+ uint16_t count = boost::numeric_cast<uint16_t>(length);
+
+ /// Copy data into a buffer preceded by the count field.
+ send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
+ send_buffer_->writeUint16(count);
+ send_buffer_->writeData(data, length);
+
+ /// ... and send it
+ socket_.async_send(boost::asio::buffer(send_buffer_->getData(),
+ send_buffer_->getLength()), callback);
+ } catch (const boost::numeric::bad_numeric_cast&) {
+ isc_throw(BufferTooLarge,
+ "attempt to send buffer larger than 64kB");
+ }
+
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a TCP socket that is not open");
+ }
+}
+
+// Receive a message. Note that the "offset" argument is used as an index
+// into the buffer in order to decide where to put the data. It is up to the
+// caller to initialize the data to zero
+template <typename C> void
+TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback)
+{
+ if (socket_.is_open()) {
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+ // does not contain a method for getting at the underlying endpoint
+ // type - that is in the derived class and the two classes differ on
+ // return type.
+ isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+ TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
+
+ // Write the endpoint details from the communications link. Ideally
+ // we should make IOEndpoint assignable, but this runs in to all sorts
+ // of problems concerning the management of the underlying Boost
+ // endpoint (e.g. if it is not self-managed, is the copied one
+ // self-managed?) The most pragmatic solution is to let Boost take care
+ // of everything and copy details of the underlying endpoint.
+ tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
+
+ // Ensure we can write into the buffer and if so, set the pointer to
+ // where the data will be written.
+ if (offset >= length) {
+ isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+ "TCP receive buffer");
+ }
+ void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+ // ... and kick off the read.
+ socket_.async_receive(boost::asio::buffer(buffer_start, length - offset), callback);
+
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to receive from a TCP socket that is not open");
+ }
+}
+
+// Is the receive complete?
+
+template <typename C> bool
+TCPSocket<C>::processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::util::OutputBufferPtr& outbuff)
+{
+ // Point to the data in the staging buffer and note how much there is.
+ const uint8_t* data = static_cast<const uint8_t*>(staging);
+ size_t data_length = length;
+
+ // Is the number is "expected" valid? It won't be unless we have received
+ // at least two bytes of data in total for this set of receives.
+ if (cumulative < 2) {
+
+ // "expected" is not valid. Did this read give us enough data to
+ // work it out?
+ cumulative += length;
+ if (cumulative < 2) {
+
+ // Nope, still not valid. This must have been the first packet and
+ // was only one byte long. Tell the fetch code to read the next
+ // packet into the staging buffer beyond the data that is already
+ // there so that the next time we are called we have a complete
+ // TCP count.
+ offset = cumulative;
+ return (false);
+ }
+
+ // Have enough data to interpret the packet count, so do so now.
+ expected = isc::util::readUint16(data, cumulative);
+
+ // We have two bytes less of data to process. Point to the start of the
+ // data and adjust the packet size. Note that at this point,
+ // "cumulative" is the true amount of data in the staging buffer, not
+ // "length".
+ data += 2;
+ data_length = cumulative - 2;
+ } else {
+
+ // Update total amount of data received.
+ cumulative += length;
+ }
+
+ // Regardless of anything else, the next read goes into the start of the
+ // staging buffer.
+ offset = 0;
+
+ // Work out how much data we still have to put in the output buffer. (This
+ // could be zero if we have just interpreted the TCP count and that was
+ // set to zero.)
+ if (expected >= outbuff->getLength()) {
+
+ // Still need data in the output packet. Copy what we can from the
+ // staging buffer to the output buffer.
+ size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
+ outbuff->writeData(data, copy_amount);
+ }
+
+ // We can now say if we have all the data.
+ return (expected == outbuff->getLength());
+}
+
+// Cancel I/O on the socket. No-op if the socket is not open.
+
+template <typename C> void
+TCPSocket<C>::cancel() {
+ if (socket_.is_open()) {
+ socket_.cancel();
+ }
+}
+
+// Close the socket down. Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TCPSocket<C>::close() {
+ if (socket_.is_open() && socket_ptr_) {
+ socket_.close();
+ }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // TCP_SOCKET_H
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
new file mode 100644
index 0000000..cad0d6d
--- /dev/null
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -0,0 +1,75 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CPPFLAGS += -DTEST_SCRIPT_SH=\"$(abs_top_builddir)/src/lib/asiolink/tests/process_spawn_app.sh\"
+AM_CPPFLAGS += -DINVALID_TEST_SCRIPT_SH=\"$(abs_top_srcdir)/README\"
+TEST_CA_DIR = $(abs_srcdir)/../testutils/ca
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda test-socket
+
+DISTCLEANFILES = process_spawn_app.sh
+
+noinst_SCRIPTS = process_spawn_app.sh
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += addr_utilities_unittest.cc
+run_unittests_SOURCES += io_address_unittest.cc
+run_unittests_SOURCES += hash_address_unittest.cc
+run_unittests_SOURCES += io_endpoint_unittest.cc
+run_unittests_SOURCES += io_socket_unittest.cc
+run_unittests_SOURCES += interval_timer_unittest.cc
+run_unittests_SOURCES += tcp_endpoint_unittest.cc
+run_unittests_SOURCES += tcp_socket_unittest.cc
+run_unittests_SOURCES += udp_endpoint_unittest.cc
+run_unittests_SOURCES += udp_socket_unittest.cc
+run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += io_service_signal_unittests.cc
+run_unittests_SOURCES += io_service_thread_pool_unittests.cc
+run_unittests_SOURCES += dummy_io_callback_unittest.cc
+run_unittests_SOURCES += tcp_acceptor_unittest.cc
+run_unittests_SOURCES += unix_domain_socket_unittest.cc
+run_unittests_SOURCES += process_spawn_unittest.cc
+if HAVE_OPENSSL
+run_unittests_SOURCES += tls_unittest.cc
+run_unittests_SOURCES += tls_acceptor_unittest.cc
+run_unittests_SOURCES += tls_socket_unittest.cc
+endif
+if HAVE_BOTAN_BOOST
+run_unittests_SOURCES += tls_unittest.cc
+run_unittests_SOURCES += tls_acceptor_unittest.cc
+run_unittests_SOURCES += tls_socket_unittest.cc
+endif
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.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 += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+run_unittests_LDADD += $(GTEST_LDADD)
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter -Wno-unused-private-field
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/asiolink/tests/Makefile.in b/src/lib/asiolink/tests/Makefile.in
new file mode 100644
index 0000000..b76d283
--- /dev/null
+++ b/src/lib/asiolink/tests/Makefile.in
@@ -0,0 +1,1303 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__append_2 = tls_unittest.cc \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ tls_acceptor_unittest.cc \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ tls_socket_unittest.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__append_3 = \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ tls_unittest.cc \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ tls_acceptor_unittest.cc \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ tls_socket_unittest.cc
+@HAVE_GTEST_TRUE@@USE_GXX_TRUE@am__append_4 = -Wno-unused-parameter -Wno-unused-private-field
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/asiolink/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = process_spawn_app.sh
+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 = run_unittests.cc \
+ addr_utilities_unittest.cc io_address_unittest.cc \
+ hash_address_unittest.cc io_endpoint_unittest.cc \
+ io_socket_unittest.cc interval_timer_unittest.cc \
+ tcp_endpoint_unittest.cc tcp_socket_unittest.cc \
+ udp_endpoint_unittest.cc udp_socket_unittest.cc \
+ io_service_unittest.cc io_service_signal_unittests.cc \
+ io_service_thread_pool_unittests.cc \
+ dummy_io_callback_unittest.cc tcp_acceptor_unittest.cc \
+ unix_domain_socket_unittest.cc process_spawn_unittest.cc \
+ tls_unittest.cc tls_acceptor_unittest.cc \
+ tls_socket_unittest.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__objects_1 = run_unittests-tls_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ run_unittests-tls_acceptor_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ run_unittests-tls_socket_unittest.$(OBJEXT)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__objects_2 = run_unittests-tls_unittest.$(OBJEXT) \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ run_unittests-tls_acceptor_unittest.$(OBJEXT) \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ run_unittests-tls_socket_unittest.$(OBJEXT)
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-addr_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_address_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hash_address_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_endpoint_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_socket_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-interval_timer_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tcp_endpoint_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tcp_socket_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-udp_endpoint_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-udp_socket_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_service_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_service_signal_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_service_thread_pool_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-dummy_io_callback_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tcp_acceptor_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-unix_domain_socket_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-process_spawn_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@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) \
+ $(run_unittests_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SCRIPTS = $(noinst_SCRIPTS)
+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-addr_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-dummy_io_callback_unittest.Po \
+ ./$(DEPDIR)/run_unittests-hash_address_unittest.Po \
+ ./$(DEPDIR)/run_unittests-interval_timer_unittest.Po \
+ ./$(DEPDIR)/run_unittests-io_address_unittest.Po \
+ ./$(DEPDIR)/run_unittests-io_endpoint_unittest.Po \
+ ./$(DEPDIR)/run_unittests-io_service_signal_unittests.Po \
+ ./$(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Po \
+ ./$(DEPDIR)/run_unittests-io_service_unittest.Po \
+ ./$(DEPDIR)/run_unittests-io_socket_unittest.Po \
+ ./$(DEPDIR)/run_unittests-process_spawn_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-tcp_acceptor_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tcp_endpoint_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tcp_socket_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tls_acceptor_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tls_socket_unittest.Po \
+ ./$(DEPDIR)/run_unittests-tls_unittest.Po \
+ ./$(DEPDIR)/run_unittests-udp_endpoint_unittest.Po \
+ ./$(DEPDIR)/run_unittests-udp_socket_unittest.Po \
+ ./$(DEPDIR)/run_unittests-unix_domain_socket_unittest.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 =
+SOURCES = $(run_unittests_SOURCES)
+DIST_SOURCES = $(am__run_unittests_SOURCES_DIST)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+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; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/process_spawn_app.sh.in $(top_srcdir)/depcomp
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ -DTEST_SCRIPT_SH=\"$(abs_top_builddir)/src/lib/asiolink/tests/process_spawn_app.sh\" \
+ -DINVALID_TEST_SCRIPT_SH=\"$(abs_top_srcdir)/README\" \
+ -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+TEST_CA_DIR = $(abs_srcdir)/../testutils/ca
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda test-socket
+DISTCLEANFILES = process_spawn_app.sh
+noinst_SCRIPTS = process_spawn_app.sh
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ addr_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ io_address_unittest.cc \
+@HAVE_GTEST_TRUE@ hash_address_unittest.cc \
+@HAVE_GTEST_TRUE@ io_endpoint_unittest.cc io_socket_unittest.cc \
+@HAVE_GTEST_TRUE@ interval_timer_unittest.cc \
+@HAVE_GTEST_TRUE@ tcp_endpoint_unittest.cc \
+@HAVE_GTEST_TRUE@ tcp_socket_unittest.cc \
+@HAVE_GTEST_TRUE@ udp_endpoint_unittest.cc \
+@HAVE_GTEST_TRUE@ udp_socket_unittest.cc io_service_unittest.cc \
+@HAVE_GTEST_TRUE@ io_service_signal_unittests.cc \
+@HAVE_GTEST_TRUE@ io_service_thread_pool_unittests.cc \
+@HAVE_GTEST_TRUE@ dummy_io_callback_unittest.cc \
+@HAVE_GTEST_TRUE@ tcp_acceptor_unittest.cc \
+@HAVE_GTEST_TRUE@ unix_domain_socket_unittest.cc \
+@HAVE_GTEST_TRUE@ process_spawn_unittest.cc $(am__append_2) \
+@HAVE_GTEST_TRUE@ $(am__append_3)
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LIBS) $(GTEST_LDADD)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_CXXFLAGS = $(AM_CXXFLAGS) \
+@HAVE_GTEST_TRUE@ $(am__append_4)
+all: all-am
+
+.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/asiolink/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/asiolink/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):
+process_spawn_app.sh: $(top_builddir)/config.status $(srcdir)/process_spawn_app.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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-addr_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-dummy_io_callback_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hash_address_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-interval_timer_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_address_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_endpoint_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_service_signal_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_service_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_socket_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-process_spawn_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-tcp_acceptor_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tcp_endpoint_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tcp_socket_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tls_acceptor_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tls_socket_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tls_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-udp_endpoint_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-udp_socket_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-unix_domain_socket_unittest.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-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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`
+
+run_unittests-addr_utilities_unittest.o: addr_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-addr_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-addr_utilities_unittest.Tpo -c -o run_unittests-addr_utilities_unittest.o `test -f 'addr_utilities_unittest.cc' || echo '$(srcdir)/'`addr_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-addr_utilities_unittest.Tpo $(DEPDIR)/run_unittests-addr_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='addr_utilities_unittest.cc' object='run_unittests-addr_utilities_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-addr_utilities_unittest.o `test -f 'addr_utilities_unittest.cc' || echo '$(srcdir)/'`addr_utilities_unittest.cc
+
+run_unittests-addr_utilities_unittest.obj: addr_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-addr_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-addr_utilities_unittest.Tpo -c -o run_unittests-addr_utilities_unittest.obj `if test -f 'addr_utilities_unittest.cc'; then $(CYGPATH_W) 'addr_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/addr_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-addr_utilities_unittest.Tpo $(DEPDIR)/run_unittests-addr_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='addr_utilities_unittest.cc' object='run_unittests-addr_utilities_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-addr_utilities_unittest.obj `if test -f 'addr_utilities_unittest.cc'; then $(CYGPATH_W) 'addr_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/addr_utilities_unittest.cc'; fi`
+
+run_unittests-io_address_unittest.o: io_address_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_address_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-io_address_unittest.Tpo -c -o run_unittests-io_address_unittest.o `test -f 'io_address_unittest.cc' || echo '$(srcdir)/'`io_address_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_address_unittest.Tpo $(DEPDIR)/run_unittests-io_address_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_address_unittest.cc' object='run_unittests-io_address_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_address_unittest.o `test -f 'io_address_unittest.cc' || echo '$(srcdir)/'`io_address_unittest.cc
+
+run_unittests-io_address_unittest.obj: io_address_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_address_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_address_unittest.Tpo -c -o run_unittests-io_address_unittest.obj `if test -f 'io_address_unittest.cc'; then $(CYGPATH_W) 'io_address_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_address_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_address_unittest.Tpo $(DEPDIR)/run_unittests-io_address_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_address_unittest.cc' object='run_unittests-io_address_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_address_unittest.obj `if test -f 'io_address_unittest.cc'; then $(CYGPATH_W) 'io_address_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_address_unittest.cc'; fi`
+
+run_unittests-hash_address_unittest.o: hash_address_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_address_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-hash_address_unittest.Tpo -c -o run_unittests-hash_address_unittest.o `test -f 'hash_address_unittest.cc' || echo '$(srcdir)/'`hash_address_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_address_unittest.Tpo $(DEPDIR)/run_unittests-hash_address_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_address_unittest.cc' object='run_unittests-hash_address_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hash_address_unittest.o `test -f 'hash_address_unittest.cc' || echo '$(srcdir)/'`hash_address_unittest.cc
+
+run_unittests-hash_address_unittest.obj: hash_address_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_address_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-hash_address_unittest.Tpo -c -o run_unittests-hash_address_unittest.obj `if test -f 'hash_address_unittest.cc'; then $(CYGPATH_W) 'hash_address_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hash_address_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_address_unittest.Tpo $(DEPDIR)/run_unittests-hash_address_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_address_unittest.cc' object='run_unittests-hash_address_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hash_address_unittest.obj `if test -f 'hash_address_unittest.cc'; then $(CYGPATH_W) 'hash_address_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hash_address_unittest.cc'; fi`
+
+run_unittests-io_endpoint_unittest.o: io_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_endpoint_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-io_endpoint_unittest.Tpo -c -o run_unittests-io_endpoint_unittest.o `test -f 'io_endpoint_unittest.cc' || echo '$(srcdir)/'`io_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_endpoint_unittest.Tpo $(DEPDIR)/run_unittests-io_endpoint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_endpoint_unittest.cc' object='run_unittests-io_endpoint_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_endpoint_unittest.o `test -f 'io_endpoint_unittest.cc' || echo '$(srcdir)/'`io_endpoint_unittest.cc
+
+run_unittests-io_endpoint_unittest.obj: io_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_endpoint_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_endpoint_unittest.Tpo -c -o run_unittests-io_endpoint_unittest.obj `if test -f 'io_endpoint_unittest.cc'; then $(CYGPATH_W) 'io_endpoint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_endpoint_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_endpoint_unittest.Tpo $(DEPDIR)/run_unittests-io_endpoint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_endpoint_unittest.cc' object='run_unittests-io_endpoint_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_endpoint_unittest.obj `if test -f 'io_endpoint_unittest.cc'; then $(CYGPATH_W) 'io_endpoint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_endpoint_unittest.cc'; fi`
+
+run_unittests-io_socket_unittest.o: io_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_socket_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-io_socket_unittest.Tpo -c -o run_unittests-io_socket_unittest.o `test -f 'io_socket_unittest.cc' || echo '$(srcdir)/'`io_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_socket_unittest.Tpo $(DEPDIR)/run_unittests-io_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_socket_unittest.cc' object='run_unittests-io_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_socket_unittest.o `test -f 'io_socket_unittest.cc' || echo '$(srcdir)/'`io_socket_unittest.cc
+
+run_unittests-io_socket_unittest.obj: io_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_socket_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_socket_unittest.Tpo -c -o run_unittests-io_socket_unittest.obj `if test -f 'io_socket_unittest.cc'; then $(CYGPATH_W) 'io_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_socket_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_socket_unittest.Tpo $(DEPDIR)/run_unittests-io_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_socket_unittest.cc' object='run_unittests-io_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_socket_unittest.obj `if test -f 'io_socket_unittest.cc'; then $(CYGPATH_W) 'io_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_socket_unittest.cc'; fi`
+
+run_unittests-interval_timer_unittest.o: interval_timer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-interval_timer_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-interval_timer_unittest.Tpo -c -o run_unittests-interval_timer_unittest.o `test -f 'interval_timer_unittest.cc' || echo '$(srcdir)/'`interval_timer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-interval_timer_unittest.Tpo $(DEPDIR)/run_unittests-interval_timer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interval_timer_unittest.cc' object='run_unittests-interval_timer_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-interval_timer_unittest.o `test -f 'interval_timer_unittest.cc' || echo '$(srcdir)/'`interval_timer_unittest.cc
+
+run_unittests-interval_timer_unittest.obj: interval_timer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-interval_timer_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-interval_timer_unittest.Tpo -c -o run_unittests-interval_timer_unittest.obj `if test -f 'interval_timer_unittest.cc'; then $(CYGPATH_W) 'interval_timer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/interval_timer_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-interval_timer_unittest.Tpo $(DEPDIR)/run_unittests-interval_timer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interval_timer_unittest.cc' object='run_unittests-interval_timer_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-interval_timer_unittest.obj `if test -f 'interval_timer_unittest.cc'; then $(CYGPATH_W) 'interval_timer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/interval_timer_unittest.cc'; fi`
+
+run_unittests-tcp_endpoint_unittest.o: tcp_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_endpoint_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tcp_endpoint_unittest.Tpo -c -o run_unittests-tcp_endpoint_unittest.o `test -f 'tcp_endpoint_unittest.cc' || echo '$(srcdir)/'`tcp_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_endpoint_unittest.Tpo $(DEPDIR)/run_unittests-tcp_endpoint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_endpoint_unittest.cc' object='run_unittests-tcp_endpoint_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_endpoint_unittest.o `test -f 'tcp_endpoint_unittest.cc' || echo '$(srcdir)/'`tcp_endpoint_unittest.cc
+
+run_unittests-tcp_endpoint_unittest.obj: tcp_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_endpoint_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tcp_endpoint_unittest.Tpo -c -o run_unittests-tcp_endpoint_unittest.obj `if test -f 'tcp_endpoint_unittest.cc'; then $(CYGPATH_W) 'tcp_endpoint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_endpoint_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_endpoint_unittest.Tpo $(DEPDIR)/run_unittests-tcp_endpoint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_endpoint_unittest.cc' object='run_unittests-tcp_endpoint_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_endpoint_unittest.obj `if test -f 'tcp_endpoint_unittest.cc'; then $(CYGPATH_W) 'tcp_endpoint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_endpoint_unittest.cc'; fi`
+
+run_unittests-tcp_socket_unittest.o: tcp_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_socket_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tcp_socket_unittest.Tpo -c -o run_unittests-tcp_socket_unittest.o `test -f 'tcp_socket_unittest.cc' || echo '$(srcdir)/'`tcp_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_socket_unittest.Tpo $(DEPDIR)/run_unittests-tcp_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_socket_unittest.cc' object='run_unittests-tcp_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_socket_unittest.o `test -f 'tcp_socket_unittest.cc' || echo '$(srcdir)/'`tcp_socket_unittest.cc
+
+run_unittests-tcp_socket_unittest.obj: tcp_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_socket_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tcp_socket_unittest.Tpo -c -o run_unittests-tcp_socket_unittest.obj `if test -f 'tcp_socket_unittest.cc'; then $(CYGPATH_W) 'tcp_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_socket_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_socket_unittest.Tpo $(DEPDIR)/run_unittests-tcp_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_socket_unittest.cc' object='run_unittests-tcp_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_socket_unittest.obj `if test -f 'tcp_socket_unittest.cc'; then $(CYGPATH_W) 'tcp_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_socket_unittest.cc'; fi`
+
+run_unittests-udp_endpoint_unittest.o: udp_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-udp_endpoint_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-udp_endpoint_unittest.Tpo -c -o run_unittests-udp_endpoint_unittest.o `test -f 'udp_endpoint_unittest.cc' || echo '$(srcdir)/'`udp_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-udp_endpoint_unittest.Tpo $(DEPDIR)/run_unittests-udp_endpoint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='udp_endpoint_unittest.cc' object='run_unittests-udp_endpoint_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-udp_endpoint_unittest.o `test -f 'udp_endpoint_unittest.cc' || echo '$(srcdir)/'`udp_endpoint_unittest.cc
+
+run_unittests-udp_endpoint_unittest.obj: udp_endpoint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-udp_endpoint_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-udp_endpoint_unittest.Tpo -c -o run_unittests-udp_endpoint_unittest.obj `if test -f 'udp_endpoint_unittest.cc'; then $(CYGPATH_W) 'udp_endpoint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/udp_endpoint_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-udp_endpoint_unittest.Tpo $(DEPDIR)/run_unittests-udp_endpoint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='udp_endpoint_unittest.cc' object='run_unittests-udp_endpoint_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-udp_endpoint_unittest.obj `if test -f 'udp_endpoint_unittest.cc'; then $(CYGPATH_W) 'udp_endpoint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/udp_endpoint_unittest.cc'; fi`
+
+run_unittests-udp_socket_unittest.o: udp_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-udp_socket_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-udp_socket_unittest.Tpo -c -o run_unittests-udp_socket_unittest.o `test -f 'udp_socket_unittest.cc' || echo '$(srcdir)/'`udp_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-udp_socket_unittest.Tpo $(DEPDIR)/run_unittests-udp_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='udp_socket_unittest.cc' object='run_unittests-udp_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-udp_socket_unittest.o `test -f 'udp_socket_unittest.cc' || echo '$(srcdir)/'`udp_socket_unittest.cc
+
+run_unittests-udp_socket_unittest.obj: udp_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-udp_socket_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-udp_socket_unittest.Tpo -c -o run_unittests-udp_socket_unittest.obj `if test -f 'udp_socket_unittest.cc'; then $(CYGPATH_W) 'udp_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/udp_socket_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-udp_socket_unittest.Tpo $(DEPDIR)/run_unittests-udp_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='udp_socket_unittest.cc' object='run_unittests-udp_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-udp_socket_unittest.obj `if test -f 'udp_socket_unittest.cc'; then $(CYGPATH_W) 'udp_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/udp_socket_unittest.cc'; fi`
+
+run_unittests-io_service_unittest.o: io_service_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_service_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-io_service_unittest.Tpo -c -o run_unittests-io_service_unittest.o `test -f 'io_service_unittest.cc' || echo '$(srcdir)/'`io_service_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_service_unittest.Tpo $(DEPDIR)/run_unittests-io_service_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_unittest.cc' object='run_unittests-io_service_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_service_unittest.o `test -f 'io_service_unittest.cc' || echo '$(srcdir)/'`io_service_unittest.cc
+
+run_unittests-io_service_unittest.obj: io_service_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_service_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_service_unittest.Tpo -c -o run_unittests-io_service_unittest.obj `if test -f 'io_service_unittest.cc'; then $(CYGPATH_W) 'io_service_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_service_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_service_unittest.Tpo $(DEPDIR)/run_unittests-io_service_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_unittest.cc' object='run_unittests-io_service_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_service_unittest.obj `if test -f 'io_service_unittest.cc'; then $(CYGPATH_W) 'io_service_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_service_unittest.cc'; fi`
+
+run_unittests-io_service_signal_unittests.o: io_service_signal_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_service_signal_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-io_service_signal_unittests.Tpo -c -o run_unittests-io_service_signal_unittests.o `test -f 'io_service_signal_unittests.cc' || echo '$(srcdir)/'`io_service_signal_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_service_signal_unittests.Tpo $(DEPDIR)/run_unittests-io_service_signal_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_signal_unittests.cc' object='run_unittests-io_service_signal_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_service_signal_unittests.o `test -f 'io_service_signal_unittests.cc' || echo '$(srcdir)/'`io_service_signal_unittests.cc
+
+run_unittests-io_service_signal_unittests.obj: io_service_signal_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_service_signal_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_service_signal_unittests.Tpo -c -o run_unittests-io_service_signal_unittests.obj `if test -f 'io_service_signal_unittests.cc'; then $(CYGPATH_W) 'io_service_signal_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/io_service_signal_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_service_signal_unittests.Tpo $(DEPDIR)/run_unittests-io_service_signal_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_signal_unittests.cc' object='run_unittests-io_service_signal_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_service_signal_unittests.obj `if test -f 'io_service_signal_unittests.cc'; then $(CYGPATH_W) 'io_service_signal_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/io_service_signal_unittests.cc'; fi`
+
+run_unittests-io_service_thread_pool_unittests.o: io_service_thread_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_service_thread_pool_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Tpo -c -o run_unittests-io_service_thread_pool_unittests.o `test -f 'io_service_thread_pool_unittests.cc' || echo '$(srcdir)/'`io_service_thread_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Tpo $(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_thread_pool_unittests.cc' object='run_unittests-io_service_thread_pool_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_service_thread_pool_unittests.o `test -f 'io_service_thread_pool_unittests.cc' || echo '$(srcdir)/'`io_service_thread_pool_unittests.cc
+
+run_unittests-io_service_thread_pool_unittests.obj: io_service_thread_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_service_thread_pool_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Tpo -c -o run_unittests-io_service_thread_pool_unittests.obj `if test -f 'io_service_thread_pool_unittests.cc'; then $(CYGPATH_W) 'io_service_thread_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/io_service_thread_pool_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Tpo $(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_service_thread_pool_unittests.cc' object='run_unittests-io_service_thread_pool_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-io_service_thread_pool_unittests.obj `if test -f 'io_service_thread_pool_unittests.cc'; then $(CYGPATH_W) 'io_service_thread_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/io_service_thread_pool_unittests.cc'; fi`
+
+run_unittests-dummy_io_callback_unittest.o: dummy_io_callback_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dummy_io_callback_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-dummy_io_callback_unittest.Tpo -c -o run_unittests-dummy_io_callback_unittest.o `test -f 'dummy_io_callback_unittest.cc' || echo '$(srcdir)/'`dummy_io_callback_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dummy_io_callback_unittest.Tpo $(DEPDIR)/run_unittests-dummy_io_callback_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dummy_io_callback_unittest.cc' object='run_unittests-dummy_io_callback_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dummy_io_callback_unittest.o `test -f 'dummy_io_callback_unittest.cc' || echo '$(srcdir)/'`dummy_io_callback_unittest.cc
+
+run_unittests-dummy_io_callback_unittest.obj: dummy_io_callback_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dummy_io_callback_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-dummy_io_callback_unittest.Tpo -c -o run_unittests-dummy_io_callback_unittest.obj `if test -f 'dummy_io_callback_unittest.cc'; then $(CYGPATH_W) 'dummy_io_callback_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dummy_io_callback_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dummy_io_callback_unittest.Tpo $(DEPDIR)/run_unittests-dummy_io_callback_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dummy_io_callback_unittest.cc' object='run_unittests-dummy_io_callback_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dummy_io_callback_unittest.obj `if test -f 'dummy_io_callback_unittest.cc'; then $(CYGPATH_W) 'dummy_io_callback_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dummy_io_callback_unittest.cc'; fi`
+
+run_unittests-tcp_acceptor_unittest.o: tcp_acceptor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_acceptor_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tcp_acceptor_unittest.Tpo -c -o run_unittests-tcp_acceptor_unittest.o `test -f 'tcp_acceptor_unittest.cc' || echo '$(srcdir)/'`tcp_acceptor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_acceptor_unittest.Tpo $(DEPDIR)/run_unittests-tcp_acceptor_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_acceptor_unittest.cc' object='run_unittests-tcp_acceptor_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_acceptor_unittest.o `test -f 'tcp_acceptor_unittest.cc' || echo '$(srcdir)/'`tcp_acceptor_unittest.cc
+
+run_unittests-tcp_acceptor_unittest.obj: tcp_acceptor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_acceptor_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tcp_acceptor_unittest.Tpo -c -o run_unittests-tcp_acceptor_unittest.obj `if test -f 'tcp_acceptor_unittest.cc'; then $(CYGPATH_W) 'tcp_acceptor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_acceptor_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_acceptor_unittest.Tpo $(DEPDIR)/run_unittests-tcp_acceptor_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_acceptor_unittest.cc' object='run_unittests-tcp_acceptor_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_acceptor_unittest.obj `if test -f 'tcp_acceptor_unittest.cc'; then $(CYGPATH_W) 'tcp_acceptor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_acceptor_unittest.cc'; fi`
+
+run_unittests-unix_domain_socket_unittest.o: unix_domain_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unix_domain_socket_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-unix_domain_socket_unittest.Tpo -c -o run_unittests-unix_domain_socket_unittest.o `test -f 'unix_domain_socket_unittest.cc' || echo '$(srcdir)/'`unix_domain_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unix_domain_socket_unittest.Tpo $(DEPDIR)/run_unittests-unix_domain_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unix_domain_socket_unittest.cc' object='run_unittests-unix_domain_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unix_domain_socket_unittest.o `test -f 'unix_domain_socket_unittest.cc' || echo '$(srcdir)/'`unix_domain_socket_unittest.cc
+
+run_unittests-unix_domain_socket_unittest.obj: unix_domain_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unix_domain_socket_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-unix_domain_socket_unittest.Tpo -c -o run_unittests-unix_domain_socket_unittest.obj `if test -f 'unix_domain_socket_unittest.cc'; then $(CYGPATH_W) 'unix_domain_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/unix_domain_socket_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unix_domain_socket_unittest.Tpo $(DEPDIR)/run_unittests-unix_domain_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unix_domain_socket_unittest.cc' object='run_unittests-unix_domain_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unix_domain_socket_unittest.obj `if test -f 'unix_domain_socket_unittest.cc'; then $(CYGPATH_W) 'unix_domain_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/unix_domain_socket_unittest.cc'; fi`
+
+run_unittests-process_spawn_unittest.o: process_spawn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-process_spawn_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-process_spawn_unittest.Tpo -c -o run_unittests-process_spawn_unittest.o `test -f 'process_spawn_unittest.cc' || echo '$(srcdir)/'`process_spawn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-process_spawn_unittest.Tpo $(DEPDIR)/run_unittests-process_spawn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='process_spawn_unittest.cc' object='run_unittests-process_spawn_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-process_spawn_unittest.o `test -f 'process_spawn_unittest.cc' || echo '$(srcdir)/'`process_spawn_unittest.cc
+
+run_unittests-process_spawn_unittest.obj: process_spawn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-process_spawn_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-process_spawn_unittest.Tpo -c -o run_unittests-process_spawn_unittest.obj `if test -f 'process_spawn_unittest.cc'; then $(CYGPATH_W) 'process_spawn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/process_spawn_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-process_spawn_unittest.Tpo $(DEPDIR)/run_unittests-process_spawn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='process_spawn_unittest.cc' object='run_unittests-process_spawn_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-process_spawn_unittest.obj `if test -f 'process_spawn_unittest.cc'; then $(CYGPATH_W) 'process_spawn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/process_spawn_unittest.cc'; fi`
+
+run_unittests-tls_unittest.o: tls_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tls_unittest.Tpo -c -o run_unittests-tls_unittest.o `test -f 'tls_unittest.cc' || echo '$(srcdir)/'`tls_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_unittest.Tpo $(DEPDIR)/run_unittests-tls_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_unittest.cc' object='run_unittests-tls_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_unittest.o `test -f 'tls_unittest.cc' || echo '$(srcdir)/'`tls_unittest.cc
+
+run_unittests-tls_unittest.obj: tls_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tls_unittest.Tpo -c -o run_unittests-tls_unittest.obj `if test -f 'tls_unittest.cc'; then $(CYGPATH_W) 'tls_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tls_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_unittest.Tpo $(DEPDIR)/run_unittests-tls_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_unittest.cc' object='run_unittests-tls_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_unittest.obj `if test -f 'tls_unittest.cc'; then $(CYGPATH_W) 'tls_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tls_unittest.cc'; fi`
+
+run_unittests-tls_acceptor_unittest.o: tls_acceptor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_acceptor_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tls_acceptor_unittest.Tpo -c -o run_unittests-tls_acceptor_unittest.o `test -f 'tls_acceptor_unittest.cc' || echo '$(srcdir)/'`tls_acceptor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_acceptor_unittest.Tpo $(DEPDIR)/run_unittests-tls_acceptor_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_acceptor_unittest.cc' object='run_unittests-tls_acceptor_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_acceptor_unittest.o `test -f 'tls_acceptor_unittest.cc' || echo '$(srcdir)/'`tls_acceptor_unittest.cc
+
+run_unittests-tls_acceptor_unittest.obj: tls_acceptor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_acceptor_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tls_acceptor_unittest.Tpo -c -o run_unittests-tls_acceptor_unittest.obj `if test -f 'tls_acceptor_unittest.cc'; then $(CYGPATH_W) 'tls_acceptor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tls_acceptor_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_acceptor_unittest.Tpo $(DEPDIR)/run_unittests-tls_acceptor_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_acceptor_unittest.cc' object='run_unittests-tls_acceptor_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_acceptor_unittest.obj `if test -f 'tls_acceptor_unittest.cc'; then $(CYGPATH_W) 'tls_acceptor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tls_acceptor_unittest.cc'; fi`
+
+run_unittests-tls_socket_unittest.o: tls_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_socket_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tls_socket_unittest.Tpo -c -o run_unittests-tls_socket_unittest.o `test -f 'tls_socket_unittest.cc' || echo '$(srcdir)/'`tls_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_socket_unittest.Tpo $(DEPDIR)/run_unittests-tls_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_socket_unittest.cc' object='run_unittests-tls_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_socket_unittest.o `test -f 'tls_socket_unittest.cc' || echo '$(srcdir)/'`tls_socket_unittest.cc
+
+run_unittests-tls_socket_unittest.obj: tls_socket_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_socket_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tls_socket_unittest.Tpo -c -o run_unittests-tls_socket_unittest.obj `if test -f 'tls_socket_unittest.cc'; then $(CYGPATH_W) 'tls_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tls_socket_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_socket_unittest.Tpo $(DEPDIR)/run_unittests-tls_socket_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_socket_unittest.cc' object='run_unittests-tls_socket_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_socket_unittest.obj `if test -f 'tls_socket_unittest.cc'; then $(CYGPATH_W) 'tls_socket_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tls_socket_unittest.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS) $(SCRIPTS)
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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 clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/run_unittests-addr_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-dummy_io_callback_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_address_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-interval_timer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_address_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_endpoint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_service_signal_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_service_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-process_spawn_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_acceptor_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_endpoint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_acceptor_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-udp_endpoint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-udp_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unix_domain_socket_unittest.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/run_unittests-addr_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-dummy_io_callback_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_address_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-interval_timer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_address_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_endpoint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_service_signal_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_service_thread_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_service_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-process_spawn_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_acceptor_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_endpoint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_acceptor_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-udp_endpoint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-udp_socket_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unix_domain_socket_unittest.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: 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 \
+ 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/asiolink/tests/addr_utilities_unittest.cc b/src/lib/asiolink/tests/addr_utilities_unittest.cc
new file mode 100644
index 0000000..30a9817
--- /dev/null
+++ b/src/lib/asiolink/tests/addr_utilities_unittest.cc
@@ -0,0 +1,388 @@
+// Copyright (C) 2012-2023 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 <asiolink/addr_utilities.h>
+#include <exceptions/exceptions.h>
+#include <util/bigints.h>
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
+ IOAddress addr1("192.0.2.1");
+
+ // Prefixes rounded to addresses are easy...
+ EXPECT_EQ("192.255.255.255", lastAddrInPrefix(addr1, 8).toText());
+ EXPECT_EQ("192.0.255.255", lastAddrInPrefix(addr1, 16).toText());
+ EXPECT_EQ("192.0.2.255", lastAddrInPrefix(addr1, 24).toText());
+
+ // these are trickier
+ EXPECT_EQ("192.0.2.127", lastAddrInPrefix(addr1, 25).toText());
+ EXPECT_EQ("192.0.2.63", lastAddrInPrefix(addr1, 26).toText());
+ EXPECT_EQ("192.0.2.31", lastAddrInPrefix(addr1, 27).toText());
+ EXPECT_EQ("192.0.2.15", lastAddrInPrefix(addr1, 28).toText());
+ EXPECT_EQ("192.0.2.7", lastAddrInPrefix(addr1, 29).toText());
+ EXPECT_EQ("192.0.2.3", lastAddrInPrefix(addr1, 30).toText());
+
+ // that doesn't make much sense as /31 subnet consists of network address
+ // and a broadcast address, with 0 usable addresses.
+ EXPECT_EQ("192.0.2.1", lastAddrInPrefix(addr1, 31).toText());
+ EXPECT_EQ("192.0.2.1", lastAddrInPrefix(addr1, 32).toText());
+
+ // Let's check extreme cases
+ IOAddress anyAddr("0.0.0.0");
+ EXPECT_EQ("127.255.255.255", lastAddrInPrefix(anyAddr, 1).toText());
+ EXPECT_EQ("255.255.255.255", lastAddrInPrefix(anyAddr, 0).toText());
+ EXPECT_EQ("0.0.0.0", lastAddrInPrefix(anyAddr, 32).toText());
+}
+
+// This test checks if firstAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, firstAddrInPrefix4) {
+ IOAddress addr1("192.223.2.255");
+
+ // Prefixes rounded to addresses are easy...
+ EXPECT_EQ("192.0.0.0", firstAddrInPrefix(addr1, 8).toText());
+ EXPECT_EQ("192.223.0.0", firstAddrInPrefix(addr1, 16).toText());
+ EXPECT_EQ("192.223.2.0", firstAddrInPrefix(addr1, 24).toText());
+
+ // these are trickier
+ EXPECT_EQ("192.223.2.128", firstAddrInPrefix(addr1, 25).toText());
+ EXPECT_EQ("192.223.2.192", firstAddrInPrefix(addr1, 26).toText());
+ EXPECT_EQ("192.223.2.224", firstAddrInPrefix(addr1, 27).toText());
+ EXPECT_EQ("192.223.2.240", firstAddrInPrefix(addr1, 28).toText());
+ EXPECT_EQ("192.223.2.248", firstAddrInPrefix(addr1, 29).toText());
+ EXPECT_EQ("192.223.2.252", firstAddrInPrefix(addr1, 30).toText());
+
+ // that doesn't make much sense as /31 subnet consists of network address
+ // and a broadcast address, with 0 usable addresses.
+ EXPECT_EQ("192.223.2.254", firstAddrInPrefix(addr1, 31).toText());
+ EXPECT_EQ("192.223.2.255", firstAddrInPrefix(addr1, 32).toText());
+
+ // Let's check extreme cases.
+ IOAddress bcast("255.255.255.255");
+ EXPECT_EQ("128.0.0.0", firstAddrInPrefix(bcast, 1).toText());
+ EXPECT_EQ("0.0.0.0", firstAddrInPrefix(bcast, 0).toText());
+ EXPECT_EQ("255.255.255.255", firstAddrInPrefix(bcast, 32).toText());
+
+}
+
+/// This test checks if lastAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, lastAddrInPrefix6) {
+ IOAddress addr1("2001:db8:1:1234:5678:abcd:1234:beef");
+
+ // Prefixes rounded to nibbles are easy...
+ EXPECT_EQ("2001:db8:1:1234:5678:abcd:1234:ffff",
+ lastAddrInPrefix(addr1, 112).toText());
+ EXPECT_EQ("2001:db8:1:1234:5678:abcd:123f:ffff",
+ lastAddrInPrefix(addr1, 108).toText());
+ EXPECT_EQ("2001:db8:1:1234:5678:abcd:12ff:ffff",
+ lastAddrInPrefix(addr1, 104).toText());
+ EXPECT_EQ("2001:db8:1:1234:ffff:ffff:ffff:ffff",
+ lastAddrInPrefix(addr1, 64).toText());
+
+ IOAddress addr2("2001::");
+
+ // These are trickier, though, as they are done in 1 bit increments
+
+ // the last address in 2001::/127 pool should be 2001::1
+ EXPECT_EQ("2001::1", lastAddrInPrefix(addr2, 127).toText());
+
+ EXPECT_EQ("2001::3", lastAddrInPrefix(addr2, 126).toText());
+ EXPECT_EQ("2001::7", lastAddrInPrefix(addr2, 125).toText());
+ EXPECT_EQ("2001::f", lastAddrInPrefix(addr2, 124).toText());
+ EXPECT_EQ("2001::1f", lastAddrInPrefix(addr2, 123).toText());
+ EXPECT_EQ("2001::3f", lastAddrInPrefix(addr2, 122).toText());
+ EXPECT_EQ("2001::7f", lastAddrInPrefix(addr2, 121).toText());
+ EXPECT_EQ("2001::ff", lastAddrInPrefix(addr2, 120).toText());
+
+ // Let's check extreme cases
+ IOAddress anyAddr("::");
+ EXPECT_EQ("7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ lastAddrInPrefix(anyAddr, 1).toText());
+ EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ lastAddrInPrefix(anyAddr, 0).toText());
+ EXPECT_EQ("::", lastAddrInPrefix(anyAddr, 128).toText());
+}
+
+/// This test checks if firstAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
+ IOAddress addr1("2001:db8:1:1234:5678:1234:abcd:beef");
+
+ // Prefixes rounded to nibbles are easy...
+ EXPECT_EQ("2001:db8:1:1234:5678:1234::",
+ firstAddrInPrefix(addr1, 96).toText());
+ EXPECT_EQ("2001:db8:1:1234:5678:1230::",
+ firstAddrInPrefix(addr1, 92).toText());
+ EXPECT_EQ("2001:db8:1:1234:5678:1200::",
+ firstAddrInPrefix(addr1, 88).toText());
+ EXPECT_EQ("2001:db8:1:1234::",
+ firstAddrInPrefix(addr1, 64).toText());
+
+ IOAddress addr2("2001::ffff");
+
+ // These are trickier, though, as they are done in 1 bit increments
+
+ // the first address in 2001::/127 pool should be 2001::1
+ EXPECT_EQ("2001::fffe", firstAddrInPrefix(addr2, 127).toText());
+
+ EXPECT_EQ("2001::fffc", firstAddrInPrefix(addr2, 126).toText());
+ EXPECT_EQ("2001::fff8", firstAddrInPrefix(addr2, 125).toText());
+ EXPECT_EQ("2001::fff0", firstAddrInPrefix(addr2, 124).toText());
+ EXPECT_EQ("2001::ffe0", firstAddrInPrefix(addr2, 123).toText());
+ EXPECT_EQ("2001::ffc0", firstAddrInPrefix(addr2, 122).toText());
+ EXPECT_EQ("2001::ff80", firstAddrInPrefix(addr2, 121).toText());
+ EXPECT_EQ("2001::ff00", firstAddrInPrefix(addr2, 120).toText());
+}
+
+// Checks if IPv4 netmask is generated properly
+TEST(AddrUtilitiesTest, getNetmask4) {
+ EXPECT_EQ("0.0.0.0", getNetmask4(0).toText());
+ EXPECT_EQ("128.0.0.0", getNetmask4(1).toText());
+ EXPECT_EQ("192.0.0.0", getNetmask4(2).toText());
+ EXPECT_EQ("224.0.0.0", getNetmask4(3).toText());
+ EXPECT_EQ("240.0.0.0", getNetmask4(4).toText());
+ EXPECT_EQ("248.0.0.0", getNetmask4(5).toText());
+ EXPECT_EQ("252.0.0.0", getNetmask4(6).toText());
+ EXPECT_EQ("254.0.0.0", getNetmask4(7).toText());
+ EXPECT_EQ("255.0.0.0", getNetmask4(8).toText());
+
+ EXPECT_EQ("255.128.0.0", getNetmask4(9).toText());
+ EXPECT_EQ("255.192.0.0", getNetmask4(10).toText());
+ EXPECT_EQ("255.224.0.0", getNetmask4(11).toText());
+ EXPECT_EQ("255.240.0.0", getNetmask4(12).toText());
+ EXPECT_EQ("255.248.0.0", getNetmask4(13).toText());
+ EXPECT_EQ("255.252.0.0", getNetmask4(14).toText());
+ EXPECT_EQ("255.254.0.0", getNetmask4(15).toText());
+ EXPECT_EQ("255.255.0.0", getNetmask4(16).toText());
+
+ EXPECT_EQ("255.255.128.0", getNetmask4(17).toText());
+ EXPECT_EQ("255.255.192.0", getNetmask4(18).toText());
+ EXPECT_EQ("255.255.224.0", getNetmask4(19).toText());
+ EXPECT_EQ("255.255.240.0", getNetmask4(20).toText());
+ EXPECT_EQ("255.255.248.0", getNetmask4(21).toText());
+ EXPECT_EQ("255.255.252.0", getNetmask4(22).toText());
+ EXPECT_EQ("255.255.254.0", getNetmask4(23).toText());
+ EXPECT_EQ("255.255.255.0", getNetmask4(24).toText());
+
+ EXPECT_EQ("255.255.255.128", getNetmask4(25).toText());
+ EXPECT_EQ("255.255.255.192", getNetmask4(26).toText());
+ EXPECT_EQ("255.255.255.224", getNetmask4(27).toText());
+ EXPECT_EQ("255.255.255.240", getNetmask4(28).toText());
+ EXPECT_EQ("255.255.255.248", getNetmask4(29).toText());
+ EXPECT_EQ("255.255.255.252", getNetmask4(30).toText());
+ EXPECT_EQ("255.255.255.254", getNetmask4(31).toText());
+ EXPECT_EQ("255.255.255.255", getNetmask4(32).toText());
+
+ EXPECT_THROW(getNetmask4(33), isc::BadValue);
+}
+
+// Checks if the calculation for IPv4 addresses in range are correct.
+TEST(AddrUtilitiesTest, addrsInRange4) {
+
+ // Let's start with something simple
+ EXPECT_EQ(1, addrsInRange(IOAddress("192.0.2.0"), IOAddress("192.0.2.0")));
+ EXPECT_EQ(10, addrsInRange(IOAddress("192.0.2.10"), IOAddress("192.0.2.19")));
+ EXPECT_EQ(256, addrsInRange(IOAddress("192.0.2.0"), IOAddress("192.0.2.255")));
+ EXPECT_EQ(65536, addrsInRange(IOAddress("192.0.0.0"), IOAddress("192.0.255.255")));
+ EXPECT_EQ(16777216, addrsInRange(IOAddress("10.0.0.0"), IOAddress("10.255.255.255")));
+
+ // Let's check if the network boundaries are crossed correctly.
+ EXPECT_EQ(3, addrsInRange(IOAddress("10.0.0.255"), IOAddress("10.0.1.1")));
+
+ // Let's go a bit overboard with this! How many addresses are there in
+ // IPv4 address space? That's a slightly tricky question, as the answer
+ // cannot be stored in uint32_t.
+ EXPECT_EQ(uint64_t(std::numeric_limits<uint32_t>::max()) + 1,
+ addrsInRange(IOAddress("0.0.0.0"), IOAddress("255.255.255.255")));
+
+ // The upper bound cannot be smaller than the lower bound.
+ EXPECT_THROW(addrsInRange(IOAddress("192.0.2.5"), IOAddress("192.0.2.4")),
+ isc::BadValue);
+}
+
+// Checks if the calculation for IPv6 addresses in range are correct.
+TEST(AddrUtilitiesTest, addrsInRange6) {
+
+ // Let's start with something simple
+ EXPECT_EQ(1, addrsInRange(IOAddress("::"), IOAddress("::")));
+ EXPECT_EQ(16, addrsInRange(IOAddress("fe80::1"), IOAddress("fe80::10")));
+ EXPECT_EQ(65536, addrsInRange(IOAddress("fe80::"), IOAddress("fe80::ffff")));
+ EXPECT_EQ(uint64_t(std::numeric_limits<uint32_t>::max()) + 1,
+ addrsInRange(IOAddress("fe80::"), IOAddress("fe80::ffff:ffff")));
+
+ // There's 2^80 addresses between those. Due to uint64_t limits, this method is
+ // capped at 2^64 -1.
+ EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+ addrsInRange(IOAddress("2001:db8:1::"), IOAddress("2001:db8:2::")));
+
+ // Let's check if the network boundaries are crossed correctly.
+ EXPECT_EQ(3, addrsInRange(IOAddress("2001:db8::ffff"), IOAddress("2001:db8::1:1")));
+
+ // Let's go a bit overboard with this! How many addresses are there in
+ // IPv6 address space? That's a really tricky question, as the answer
+ // wouldn't fit even in uint128_t (if we had it). This method is capped
+ // at max value of uint64_t.
+ EXPECT_EQ(std::numeric_limits<uint64_t>::max(), addrsInRange(IOAddress("::"),
+ IOAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+ EXPECT_THROW(addrsInRange(IOAddress("fe80::5"), IOAddress("fe80::4")),
+ isc::BadValue);
+}
+
+// Checks if IPv4 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange4) {
+ // Use a shorter name
+ const auto& plfr = prefixLengthFromRange;
+
+ // Let's start with something simple
+ EXPECT_EQ(32, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.0")));
+ EXPECT_EQ(31, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.1")));
+ EXPECT_EQ(30, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.3")));
+ EXPECT_EQ(29, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.7")));
+ EXPECT_EQ(28, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.15")));
+ EXPECT_EQ(27, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.31")));
+ EXPECT_EQ(26, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.63")));
+ EXPECT_EQ(25, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.127")));
+ EXPECT_EQ(24, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.255")));
+ EXPECT_EQ(23, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.3.255")));
+ EXPECT_EQ(16, plfr(IOAddress("10.0.0.0"), IOAddress("10.0.255.255")));
+ EXPECT_EQ(8, plfr(IOAddress("10.0.0.0"), IOAddress("10.255.255.255")));
+ EXPECT_EQ(0, plfr(IOAddress("0.0.0.0"), IOAddress("255.255.255.255")));
+
+ // Fail if a network boundary is crossed
+ EXPECT_EQ(-1, plfr(IOAddress("10.0.0.255"), IOAddress("10.0.1.1")));
+
+ // Fail if first is not at the begin
+ EXPECT_EQ(-1, plfr(IOAddress("10.0.0.2"), IOAddress("10.0.0.5")));
+
+ // The upper bound cannot be smaller than the lower bound
+ EXPECT_THROW(plfr(IOAddress("192.0.2.5"), IOAddress("192.0.2.4")),
+ isc::BadValue);
+}
+
+// Checks if IPv6 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange6) {
+ // Use a shorter name
+ const auto& plfr = prefixLengthFromRange;
+
+ // Let's start with something simple
+ EXPECT_EQ(128, plfr(IOAddress("::"), IOAddress("::")));
+ EXPECT_EQ(112, plfr(IOAddress("fe80::"), IOAddress("fe80::ffff")));
+ EXPECT_EQ(96, plfr(IOAddress("fe80::"), IOAddress("fe80::ffff:ffff")));
+ EXPECT_EQ(80, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ffff:ffff:ffff")));
+ EXPECT_EQ(64, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(63, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(62, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(61, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(60, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(59, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(58, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(57, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7f:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(56, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(55, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(54, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(53, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7ff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(52, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(51, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::1fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(50, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::3fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(49, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::7fff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(48, plfr(IOAddress("fe80::"),
+ IOAddress("fe80::ffff:ffff:ffff:ffff:ffff")));
+ EXPECT_EQ(0, plfr(IOAddress("::"),
+ IOAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+ // Fail if a network boundary is crossed
+ EXPECT_EQ(-1, plfr(IOAddress("2001:db8::ffff"),
+ IOAddress("2001:db8::1:1")));
+
+ // Fail if first is not at the begin
+ EXPECT_EQ(-1, plfr(IOAddress("2001:db8::2"), IOAddress("2001:db8::5")));
+ EXPECT_EQ(-1, plfr(IOAddress("2001:db8::2:0"),
+ IOAddress("2001:db8::5:ffff")));
+ EXPECT_EQ(-1, plfr(IOAddress("2001:db8::2:ff00:0"),
+ IOAddress("2001:db8::3:00ff:ffff")));
+
+ // The upper bound cannot be smaller than the lower bound
+ EXPECT_THROW(plfr(IOAddress("fe80::5"), IOAddress("fe80::4")),
+ isc::BadValue);
+
+ // Address family must match
+ EXPECT_THROW(plfr(IOAddress("192.0.2.0"), IOAddress("fe80::1")),
+ isc::BadValue);
+}
+
+// Checks if prefixInRange returns valid number of prefixes in specified range.
+TEST(AddrUtilitiesTest, prefixesInRange) {
+
+ // How many /64 prefixes are in /64 pool?
+ EXPECT_EQ(1, prefixesInRange(64, 64));
+
+ // How many /63 prefixes are in /64 pool?
+ EXPECT_EQ(2, prefixesInRange(63, 64));
+
+ // How many /64 prefixes are in /48 pool?
+ EXPECT_EQ(65536, prefixesInRange(48, 64));
+
+ // How many /127 prefixes are in /64 pool?
+ EXPECT_EQ(uint64_t(9223372036854775808ull), prefixesInRange(64, 127));
+
+ // How many /128 prefixes are in /64 pool?
+ EXPECT_EQ(uint128_t(1) << 64, prefixesInRange(64, 128));
+
+ // Let's go overboard again. How many IPv6 addresses are there?
+ EXPECT_EQ(uint128_t(1) << 127, prefixesInRange(1, 128));
+
+ // Let's go overboard again. How many IPv6 addresses are there?
+ EXPECT_EQ(uint128_t(-1), prefixesInRange(0, 128));
+}
+
+// Checks the function which finds an IPv4 address from input address and offset.
+TEST(AddrUtilitiesTest, offsetIPv4Address) {
+ EXPECT_EQ("10.1.2.46", offsetAddress(IOAddress("10.1.1.45"), 257).toText());
+ EXPECT_EQ("10.1.7.9", offsetAddress(IOAddress("10.1.1.45"), 1500).toText());
+ // Using very large offset. The maximum IPv4 address should be returned.
+ EXPECT_EQ("255.255.255.255", offsetAddress(IOAddress("255.255.254.254"), 0xFFFFFFFFFFFFFFFA).toText());
+}
+
+// Checks the function which finds an IPv6 address from input address and offset.
+TEST(AddrUtilitiesTest, offsetIPv6Address) {
+ EXPECT_EQ("2001:db8:1::4", offsetAddress(IOAddress("2001:db8:1::4"), 0).toText());
+ EXPECT_EQ("2001:db8:1::10:3", offsetAddress(IOAddress("2001:db8:1::4"), 0xFFFFF).toText());
+ EXPECT_EQ("2001:db8:2::", offsetAddress(IOAddress("2001:db8:1:FFFF::1"), 0xFFFFFFFFFFFFFFFF).toText());
+ EXPECT_EQ("3000::1c", offsetAddress(IOAddress("3000::15"), 7).toText());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/asiolink/tests/dummy_io_callback_unittest.cc b/src/lib/asiolink/tests/dummy_io_callback_unittest.cc
new file mode 100644
index 0000000..b836893
--- /dev/null
+++ b/src/lib/asiolink/tests/dummy_io_callback_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2014-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 <asiolink/asio_wrapper.h>
+#include <asiolink/dummy_io_cb.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace boost::asio;
+
+namespace { // begin unnamed namespace
+
+TEST(DummyIOCallbackTest, throws) {
+ DummyIOCallback cb;
+ boost::system::error_code error_code;
+
+ // All methods should throw isc::Unexpected.
+ EXPECT_THROW(cb(error_code), isc::Unexpected);
+ EXPECT_THROW(cb(error_code, 42), isc::Unexpected);
+}
+
+} // end of unnamed namespace
diff --git a/src/lib/asiolink/tests/hash_address_unittest.cc b/src/lib/asiolink/tests/hash_address_unittest.cc
new file mode 100644
index 0000000..50fa43f
--- /dev/null
+++ b/src/lib/asiolink/tests/hash_address_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright (C) 2020 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 <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <boost/functional/hash.hpp>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+// In fact the goal i.e. is to check if the file compiles.
+
+using namespace isc::asiolink;
+
+typedef boost::hash<IOAddress> hash_address;
+
+TEST(HashAddressTest, unorderedSet) {
+ std::unordered_set<IOAddress, hash_address> set;
+ EXPECT_TRUE(set.empty());
+ EXPECT_EQ(0, set.size());
+
+ IOAddress addr("192.168.2.1");
+ EXPECT_EQ(set.end(), set.find(addr));
+ EXPECT_EQ(0, set.count(addr));
+
+ EXPECT_NO_THROW(set.insert(addr));
+ EXPECT_FALSE(set.empty());
+ EXPECT_EQ(1, set.size());
+ EXPECT_NE(set.end(), set.find(addr));
+ EXPECT_EQ(1, set.count(addr));
+
+ EXPECT_NO_THROW(set.clear());
+ EXPECT_TRUE(set.empty());
+}
+
+TEST(HashAddressTest, unorderedMap) {
+ std::unordered_map<IOAddress, std::string, hash_address> map;
+ EXPECT_TRUE(map.empty());
+ EXPECT_EQ(0, map.size());
+
+ IOAddress addr("192.168.2.1");
+ EXPECT_EQ(map.end(), map.find(addr));
+ EXPECT_EQ(0, map.count(addr));
+
+ std::string str("my-address");
+ EXPECT_NO_THROW(map[addr] = str);
+ EXPECT_FALSE(map.empty());
+ EXPECT_EQ(1, map.size());
+ EXPECT_NE(map.end(), map.find(addr));
+ EXPECT_EQ(1, map.count(addr));
+
+ EXPECT_NO_THROW(map.clear());
+ EXPECT_TRUE(map.empty());
+}
diff --git a/src/lib/asiolink/tests/interval_timer_unittest.cc b/src/lib/asiolink/tests/interval_timer_unittest.cc
new file mode 100644
index 0000000..757bba4
--- /dev/null
+++ b/src/lib/asiolink/tests/interval_timer_unittest.cc
@@ -0,0 +1,377 @@
+// Copyright (C) 2011-2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/asiolink.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+// TODO: Consider this margin
+const boost::posix_time::time_duration TIMER_MARGIN_MSEC =
+ boost::posix_time::milliseconds(50);
+}
+
+using namespace isc::asiolink;
+
+// This fixture is for testing IntervalTimer. Some callback functors are
+// registered as callback function of the timer to test if they are called
+// or not.
+class IntervalTimerTest : public ::testing::Test {
+protected:
+ IntervalTimerTest() :
+ io_service_(), timer_called_(false), timer_cancel_success_(false)
+ {}
+ ~IntervalTimerTest() {}
+ class TimerCallBack {
+ public:
+ TimerCallBack(IntervalTimerTest* test_obj) : test_obj_(test_obj) {}
+ void operator()() const {
+ test_obj_->timer_called_ = true;
+ test_obj_->io_service_.stop();
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCounter {
+ public:
+ TimerCallBackCounter(IntervalTimerTest* test_obj) :
+ test_obj_(test_obj)
+ {
+ counter_ = 0;
+ }
+ void operator()() {
+ ++counter_;
+ return;
+ }
+ int counter_;
+ private:
+ IntervalTimerTest* test_obj_;
+ };
+ class TimerCallBackCancelDeleter {
+ public:
+ TimerCallBackCancelDeleter(IntervalTimerTest* test_obj,
+ IntervalTimer* timer,
+ TimerCallBackCounter& counter)
+ : test_obj_(test_obj), timer_(timer), counter_(counter), count_(0),
+ prev_counter_(-1)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Store the value of counter_.counter_.
+ prev_counter_ = counter_.counter_;
+ delete timer_;
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // Stop io_service to stop all timers.
+ test_obj_->io_service_.stop();
+ // Compare the value of counter_.counter_ with stored one.
+ // If TimerCallBackCounter was not called (expected behavior),
+ // they are same.
+ if (counter_.counter_ == prev_counter_) {
+ test_obj_->timer_cancel_success_ = true;
+ }
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer* timer_;
+ TimerCallBackCounter& counter_;
+ int count_;
+ int prev_counter_;
+ };
+ class TimerCallBackCanceller {
+ public:
+ TimerCallBackCanceller(unsigned int& counter, IntervalTimer& itimer) :
+ counter_(counter), itimer_(itimer)
+ {}
+ void operator()() {
+ ++counter_;
+ itimer_.cancel();
+ }
+ private:
+ unsigned int& counter_;
+ IntervalTimer& itimer_;
+ };
+ class TimerCallBackOverwriter {
+ public:
+ TimerCallBackOverwriter(IntervalTimerTest* test_obj,
+ IntervalTimer& timer)
+ : test_obj_(test_obj), timer_(timer), count_(0)
+ {}
+ void operator()() {
+ ++count_;
+ if (count_ == 1) {
+ // First time of call back.
+ // Call setup() to update callback function to TimerCallBack.
+ test_obj_->timer_called_ = false;
+ timer_.setup(TimerCallBack(test_obj_), 100);
+ } else if (count_ == 2) {
+ // Second time of call back.
+ // If it reaches here, re-setup() is failed (unexpected).
+ // We should stop here.
+ test_obj_->io_service_.stop();
+ }
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ IntervalTimer& timer_;
+ int count_;
+ };
+ class TimerCallBackAccumulator {
+ public:
+ TimerCallBackAccumulator(IntervalTimerTest* test_obj, int &counter) :
+ test_obj_(test_obj), counter_(counter) {
+ }
+ void operator()() {
+ ++counter_;
+ return;
+ }
+ private:
+ IntervalTimerTest* test_obj_;
+ // Reference to integer accumulator
+ int& counter_;
+ };
+protected:
+ IOService io_service_;
+ bool timer_called_;
+ bool timer_cancel_success_;
+};
+
+TEST_F(IntervalTimerTest, invalidArgumentToIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ IntervalTimer itimer(io_service_);
+ // expect throw if call back function is empty
+ EXPECT_THROW(itimer.setup(IntervalTimer::Callback(), 1),
+ isc::InvalidParameter);
+ // expect throw if interval is negative.
+ EXPECT_THROW(itimer.setup(TimerCallBack(this), -1), isc::BadValue);
+}
+
+TEST_F(IntervalTimerTest, startIntervalTimer) {
+ // Create asio_link::IntervalTimer and setup.
+ // Then run IOService and test if the callback function is called.
+ IntervalTimer itimer(io_service_);
+ timer_called_ = false;
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ // setup timer
+ itimer.setup(TimerCallBack(this), 100);
+ EXPECT_EQ(100, itimer.getInterval());
+ io_service_.run();
+ // Control reaches here after io_service_ was stopped by TimerCallBack.
+
+ // delta: difference between elapsed time and 100 milliseconds.
+ boost::posix_time::time_duration test_runtime =
+ boost::posix_time::microsec_clock::universal_time() - start;
+ EXPECT_FALSE(test_runtime.is_negative()) <<
+ "test duration " << test_runtime <<
+ " negative - clock skew?";
+ // Expect TimerCallBack is called; timer_called_ is true
+ EXPECT_TRUE(timer_called_);
+ // Expect test_runtime is 100 milliseconds or longer.
+ // Allow 1% of clock skew
+ EXPECT_TRUE(test_runtime >= boost::posix_time::milliseconds(99)) <<
+ "test runtime " << test_runtime.total_milliseconds() <<
+ "msec >= 100";
+}
+
+TEST_F(IntervalTimerTest, destructIntervalTimer) {
+ // This code isn't exception safe, but we'd rather keep the code
+ // simpler and more readable as this is only for tests and if it throws
+ // the program would immediately terminate anyway.
+
+ // The call back function will not be called after the timer is
+ // destroyed.
+ //
+ // There are two timers:
+ // itimer_counter (A)
+ // (Calls TimerCallBackCounter)
+ // - increments internal counter in callback function
+ // itimer_canceller (B)
+ // (Calls TimerCallBackCancelDeleter)
+ // - first time of callback, it stores the counter value of
+ // callback_canceller and destroys itimer_counter
+ // - second time of callback, it compares the counter value of
+ // callback_canceller with stored value
+ // if they are same the timer was not called; expected result
+ // if they are different the timer was called after destroyed
+ //
+ // 0 100 200 300 400 500 600 (ms)
+ // (A) i--------+----x
+ // ^
+ // |destroy itimer_counter
+ // (B) i-------------+--------------s
+ // ^stop io_service
+ // and check if itimer_counter have been
+ // stopped
+
+ // itimer_counter will be deleted in TimerCallBackCancelDeleter
+ IntervalTimer* itimer_counter = new IntervalTimer(io_service_);
+ IntervalTimer itimer_canceller(io_service_);
+ timer_cancel_success_ = false;
+ TimerCallBackCounter callback_canceller(this);
+ itimer_counter->setup(callback_canceller, 200);
+ itimer_canceller.setup(
+ TimerCallBackCancelDeleter(this, itimer_counter, callback_canceller),
+ 300);
+ io_service_.run();
+ EXPECT_TRUE(timer_cancel_success_);
+}
+
+TEST_F(IntervalTimerTest, cancel) {
+ // Similar to destructIntervalTimer test, but the first timer explicitly
+ // cancels itself on first callback.
+ IntervalTimer itimer_counter(io_service_);
+ IntervalTimer itimer_watcher(io_service_);
+ unsigned int counter = 0;
+ itimer_counter.setup(TimerCallBackCanceller(counter, itimer_counter), 100);
+ itimer_watcher.setup(TimerCallBack(this), 200);
+ io_service_.run();
+ EXPECT_EQ(1, counter);
+ EXPECT_EQ(0, itimer_counter.getInterval());
+
+ // canceling an already canceled timer shouldn't cause any surprise.
+ EXPECT_NO_THROW(itimer_counter.cancel());
+}
+
+TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
+ // Call setup() multiple times to update call back function and interval.
+ //
+ // There are two timers:
+ // itimer (A)
+ // (Calls TimerCallBackCounter / TimerCallBack)
+ // - increments internal counter in callback function
+ // (TimerCallBackCounter)
+ // interval: 300 milliseconds
+ // - io_service_.stop() (TimerCallBack)
+ // interval: 100 milliseconds
+ // itimer_overwriter (B)
+ // (Calls TimerCallBackOverwriter)
+ // - first time of callback, it calls setup() to change call back
+ // function to TimerCallBack and interval of itimer to 100
+ // milliseconds
+ // after 300 + 100 milliseconds from the beginning of this test,
+ // TimerCallBack() will be called and io_service_ stops.
+ // - second time of callback, it means the test fails.
+ //
+ // 0 100 200 300 400 500 600 700 800 (ms)
+ // (A) i-------------+----C----s
+ // ^ ^stop io_service
+ // |change call back function and interval
+ // (B) i------------------+-------------------S
+ // ^(stop io_service on fail)
+ //
+
+ IntervalTimer itimer(io_service_);
+ IntervalTimer itimer_overwriter(io_service_);
+ // store start time
+ boost::posix_time::ptime start;
+ start = boost::posix_time::microsec_clock::universal_time();
+ itimer.setup(TimerCallBackCounter(this), 300);
+ itimer_overwriter.setup(TimerCallBackOverwriter(this, itimer), 400);
+ io_service_.run();
+ // Control reaches here after io_service_ was stopped by
+ // TimerCallBackCounter or TimerCallBackOverwriter.
+
+ // Expect callback function is updated: TimerCallBack is called
+ EXPECT_TRUE(timer_called_);
+ // Expect interval is updated: return value of getInterval() is updated
+ EXPECT_EQ(itimer.getInterval(), 100);
+}
+
+// This test verifies that timers operate correctly based on their mode.
+TEST_F(IntervalTimerTest, intervalModeTest) {
+ // Create a timer to control the duration of the test.
+ IntervalTimer test_timer(io_service_);
+ test_timer.setup(TimerCallBack(this), 2000);
+
+ // Create an timer which automatically reschedules itself. Use the
+ // accumulator callback to increment local counter for it.
+ int repeater_count = 0;
+ IntervalTimer repeater(io_service_);
+ repeater.setup(TimerCallBackAccumulator(this, repeater_count), 10);
+
+ // Create a one-shot timer. Use the accumulator callback to increment
+ // local counter variable for it.
+ int one_shot_count = 0;
+ IntervalTimer one_shot(io_service_);
+ one_shot.setup(TimerCallBackAccumulator(this, one_shot_count), 10,
+ IntervalTimer::ONE_SHOT);
+
+ // As long as service runs at least one event handler, loop until
+ // we've hit our goals. It won't return zero unless is out of
+ // work or the service has been stopped by the test timer.
+ int cnt = 0;
+ while (((cnt = io_service_.get_io_service().run_one()) > 0)
+ && (repeater_count < 5)) {
+ // deliberately empty
+ };
+
+ // If cnt is zero, then something went wrong.
+ EXPECT_TRUE(cnt > 0);
+
+ // The loop stopped make sure it was for the right reason.
+ EXPECT_EQ(repeater_count, 5);
+ EXPECT_EQ(one_shot_count, 1);
+}
+
+// This test verifies that the same timer can be reused in either mode.
+TEST_F(IntervalTimerTest, timerReuseTest) {
+ // Create a timer to control the duration of the test.
+ IntervalTimer test_timer(io_service_);
+ test_timer.setup(TimerCallBack(this), 2000);
+
+ // Create a one-shot timer. Use the accumulator callback to increment
+ // local counter variable for it.
+ int one_shot_count = 0;
+ IntervalTimer one_shot(io_service_);
+ TimerCallBackAccumulator callback(this, one_shot_count);
+ one_shot.setup(callback, 10, IntervalTimer::ONE_SHOT);
+
+ // Run until a single event handler executes. This should be our
+ // one-shot expiring.
+ io_service_.run_one();
+
+ // Verify the timer expired once.
+ ASSERT_EQ(one_shot_count, 1);
+
+ // Setup the one-shot to go again.
+ one_shot.setup(callback, 10, IntervalTimer::ONE_SHOT);
+
+ // Run until a single event handler executes. This should be our
+ // one-shot expiring.
+ io_service_.run_one();
+
+ // Verify the timer expired once.
+ ASSERT_EQ(one_shot_count, 2);
+
+ // Setup the timer to be repeating.
+ one_shot.setup(callback, 10, IntervalTimer::REPEATING);
+
+ // As long as service runs at least one event handler, loop until
+ // we've hit our goals. It won't return zero unless is out of
+ // work or the service has been stopped by the test timer.
+ int cnt = 0;
+ while ((cnt = io_service_.get_io_service().run_one())
+ && (one_shot_count < 4)) {
+ // deliberately empty
+ };
+
+ // If cnt is zero, then something went wrong.
+ EXPECT_TRUE(cnt > 0);
+
+ // Verify the timer repeated.
+ EXPECT_GE(one_shot_count, 4);
+}
diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc
new file mode 100644
index 0000000..b113940
--- /dev/null
+++ b/src/lib/asiolink/tests/io_address_unittest.cc
@@ -0,0 +1,370 @@
+// Copyright (C) 2011-2023 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 <asiolink/io_error.h>
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+
+#include <algorithm>
+#include <cstring>
+#include <vector>
+#include <sstream>
+#include <unordered_set>
+
+using namespace isc::asiolink;
+
+TEST(IOAddressHashTest, hashIPv4) {
+ IOAddress::Hash hash;
+ std::unordered_set<size_t> results;
+ for (uint32_t i = 0; i < 10; i++) {
+ IOAddress address(i);
+ auto result = hash(address);
+ results.insert(result);
+ }
+ // Make sure that the hashing function generated a unique hash for
+ // each address.
+ EXPECT_EQ(10, results.size());
+}
+
+TEST(IOAddressHashTest, hashIPv6) {
+ IOAddress::Hash hash;
+ std::unordered_set<size_t> results;
+ for (auto i = 0; i < 10; i++) {
+ std::ostringstream s;
+ s << "2001:db8:" << i << "::ffff";
+ IOAddress address(s.str());
+ auto result = hash(address);
+ results.insert(result);
+ }
+ // Make sure that the hashing function generated a unique hash for
+ // each address.
+ EXPECT_EQ(10, results.size());
+}
+
+TEST(IOAddressTest, fromText) {
+ IOAddress io_address_v4("192.0.2.1");
+ EXPECT_EQ("192.0.2.1", io_address_v4.toText());
+
+ IOAddress io_address_v6("2001:db8::1234");
+ EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
+
+ // bogus IPv4 address-like input
+ EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
+
+ // bogus IPv4 address-like input: out-of-range octet
+ EXPECT_THROW(IOAddress("192.0.2.300"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
+
+ // bogus IPv6 address-like input
+ EXPECT_THROW(IOAddress("2001:db8::efgh"), IOError);
+}
+
+TEST(IOAddressTest, Equality) {
+ EXPECT_TRUE(IOAddress("192.0.2.1") == IOAddress("192.0.2.1"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") != IOAddress("192.0.2.1"));
+
+ EXPECT_TRUE(IOAddress("192.0.2.1") != IOAddress("192.0.2.2"));
+ EXPECT_FALSE(IOAddress("192.0.2.1") == IOAddress("192.0.2.2"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::12") == IOAddress("2001:0DB8:0:0::0012"));
+ EXPECT_FALSE(IOAddress("2001:db8::12") != IOAddress("2001:0DB8:0:0::0012"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("2001:db8::1235"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("2001:db8::1235"));
+
+ EXPECT_TRUE(IOAddress("2001:db8::1234") != IOAddress("192.0.2.3"));
+ EXPECT_FALSE(IOAddress("2001:db8::1234") == IOAddress("192.0.2.3"));
+}
+
+TEST(IOAddressTest, Family) {
+ EXPECT_EQ(AF_INET, IOAddress("192.0.2.1").getFamily());
+ EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily());
+}
+
+TEST(IOAddressTest, fromBytes) {
+ // 2001:db8:1::dead:beef
+ uint8_t v6[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
+ 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef };
+
+ uint8_t v4[] = { 192, 0 , 2, 3 };
+
+ IOAddress addr("::");
+ EXPECT_NO_THROW({
+ addr = IOAddress::fromBytes(AF_INET6, v6);
+ });
+ EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
+
+ EXPECT_NO_THROW({
+ addr = IOAddress::fromBytes(AF_INET, v4);
+ });
+ EXPECT_EQ(addr, IOAddress("192.0.2.3"));
+}
+
+TEST(IOAddressTest, toBytesV4) {
+ // Address and network byte-order representation of the address.
+ const char* V4STRING = "192.0.2.1";
+ uint8_t V4[] = {0xc0, 0x00, 0x02, 0x01};
+
+ std::vector<uint8_t> actual = IOAddress(V4STRING).toBytes();
+ ASSERT_EQ(sizeof(V4), actual.size());
+ EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V4));
+}
+
+TEST(IOAddressTest, toBytesV6) {
+ // Address and network byte-order representation of the address.
+ const char* V6STRING = "2001:db8:1::dead:beef";
+ uint8_t V6[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef
+ };
+
+ std::vector<uint8_t> actual = IOAddress(V6STRING).toBytes();
+ ASSERT_EQ(sizeof(V6), actual.size());
+ EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V6));
+}
+
+TEST(IOAddressTest, isV4) {
+ const IOAddress address4("192.0.2.1");
+ const IOAddress address6("2001:db8:1::dead:beef");
+
+ EXPECT_TRUE(address4.isV4());
+ EXPECT_FALSE(address6.isV4());
+}
+
+TEST(IOAddressTest, isV4Zero) {
+ // 0.0.0.0
+ const IOAddress address_zero("0.0.0.0");
+ EXPECT_TRUE(address_zero.isV4Zero());
+ // :: (v6 zero address)
+ const IOAddress address_zero_v6("::");
+ EXPECT_FALSE(address_zero_v6.isV4Zero());
+ // 192.0.2.3
+ const IOAddress address_non_zero("192.0.2.3");
+ EXPECT_FALSE(address_non_zero.isV4Zero());
+ // 0.0.0.100
+ const IOAddress address_non_zero1("0.0.0.100");
+ EXPECT_FALSE(address_non_zero1.isV4Zero());
+ // 64.0.0.0
+ const IOAddress address_non_zero2("64.0.0.0");
+ EXPECT_FALSE(address_non_zero2.isV4Zero());
+}
+
+TEST(IOAddressTest, isV4Bcast) {
+ // 255.255.255.255
+ const IOAddress address_bcast("255.255.255.255");
+ EXPECT_TRUE(address_bcast.isV4Bcast());
+ // 10.2.3.4
+ const IOAddress address_non_bcast("10.2.3.4");
+ EXPECT_FALSE(address_non_bcast.isV4Bcast());
+ // 255.255.255.23
+ const IOAddress address_non_bcast1("255.255.255.23");
+ EXPECT_FALSE(address_non_bcast1.isV4Bcast());
+ // 123.255.255.255
+ const IOAddress address_non_bcast2("123.255.255.255");
+ EXPECT_FALSE(address_non_bcast2.isV4Bcast());
+
+}
+
+TEST(IOAddressTest, isV6) {
+ const IOAddress address4("192.0.2.1");
+ const IOAddress address6("2001:db8:1::dead:beef");
+
+ EXPECT_FALSE(address4.isV6());
+ EXPECT_TRUE(address6.isV6());
+}
+
+TEST(IOAddressTest, isV6Zero) {
+ // ::
+ const IOAddress address_zero("::");
+ EXPECT_TRUE(address_zero.isV6Zero());
+ // 0.0.0.0
+ const IOAddress address_non_zero("0.0.0.0");
+ EXPECT_FALSE(address_non_zero.isV6Zero());
+ // ::ff
+ const IOAddress address_non_zero1("::ff");
+ EXPECT_FALSE(address_non_zero1.isV6Zero());
+ // ff::
+ const IOAddress address_non_zero2("ff::");
+ EXPECT_FALSE(address_non_zero2.isV6Zero());
+}
+
+TEST(IOAddressTest, uint32) {
+ IOAddress addr1("192.0.2.5");
+
+ // operator uint_32() is used here
+ uint32_t tmp = addr1.toUint32();
+
+ uint32_t expected = (192U << 24) + (0U << 16) + (2U << 8) + 5U;
+
+ EXPECT_EQ(expected, tmp);
+
+ // now let's try opposite conversion
+ IOAddress addr3 = IOAddress(expected);
+
+ EXPECT_EQ(addr3.toText(), "192.0.2.5");
+}
+
+TEST(IOAddressTest, lessThanEqual) {
+ IOAddress addr1("192.0.2.5");
+ IOAddress addr2("192.0.2.6");
+ IOAddress addr3("0.0.0.0");
+
+ IOAddress addr4("::");
+ IOAddress addr5("2001:db8::1");
+ IOAddress addr6("2001:db8::1:0");
+ IOAddress addr7("2001:db8::1:0"); // the same as 6
+
+ // v4 comparisons
+ EXPECT_TRUE(addr1 < addr2);
+ EXPECT_FALSE(addr2 < addr1);
+ EXPECT_FALSE(addr2 <= addr1);
+ EXPECT_TRUE(addr3 < addr1);
+ EXPECT_TRUE(addr3 < addr2);
+ EXPECT_TRUE(addr3 <= addr2);
+
+ // v6 comparisons
+ EXPECT_TRUE(addr4 < addr5);
+ EXPECT_TRUE(addr5 < addr6);
+ EXPECT_FALSE(addr6 < addr5);
+ EXPECT_FALSE(addr6 <= addr5);
+
+ // v4 to v6 - v4 should always be smaller
+ EXPECT_TRUE(addr1 < addr4);
+ EXPECT_TRUE(addr3 < addr4);
+ EXPECT_TRUE(addr2 < addr5);
+
+ EXPECT_TRUE(addr6 <= addr7);
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(IOAddressTest, LeftShiftOperator) {
+ const IOAddress addr("192.0.2.5");
+
+ std::ostringstream oss;
+ oss << addr;
+ EXPECT_EQ(addr.toText(), oss.str());
+}
+
+// Tests address classification methods (which were previously used by accessing
+// underlying asio objects directly)
+TEST(IOAddressTest, accessClassificationMethods) {
+ IOAddress addr1("192.0.2.5"); // IPv4
+ IOAddress addr2("::"); // IPv6
+ IOAddress addr3("2001:db8::1"); // global IPv6
+ IOAddress addr4("fe80::1234"); // link-local
+ IOAddress addr5("ff02::1:2"); // multicast
+
+ EXPECT_TRUE (addr1.isV4());
+ EXPECT_FALSE(addr1.isV6());
+ EXPECT_FALSE(addr1.isV6LinkLocal());
+ EXPECT_FALSE(addr1.isV6Multicast());
+
+ EXPECT_FALSE(addr2.isV4());
+ EXPECT_TRUE (addr2.isV6());
+ EXPECT_FALSE(addr2.isV6LinkLocal());
+ EXPECT_FALSE(addr2.isV6Multicast());
+
+ EXPECT_FALSE(addr3.isV4());
+ EXPECT_TRUE (addr3.isV6());
+ EXPECT_FALSE(addr3.isV6LinkLocal());
+ EXPECT_FALSE(addr3.isV6Multicast());
+
+ EXPECT_FALSE(addr4.isV4());
+ EXPECT_TRUE (addr4.isV6());
+ EXPECT_TRUE (addr4.isV6LinkLocal());
+ EXPECT_FALSE(addr4.isV6Multicast());
+
+ EXPECT_FALSE(addr5.isV4());
+ EXPECT_TRUE (addr5.isV6());
+ EXPECT_FALSE(addr5.isV6LinkLocal());
+ EXPECT_TRUE (addr5.isV6Multicast());
+}
+
+TEST(IOAddressTest, staticAddresses) {
+ EXPECT_EQ(IOAddress("0.0.0.0"), IOAddress::IPV4_ZERO_ADDRESS());
+ EXPECT_EQ(IOAddress("255.255.255.255"), IOAddress::IPV4_BCAST_ADDRESS());
+ EXPECT_EQ(IOAddress("::"), IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+// Tests whether address subtraction works correctly.
+TEST(IOAddressTest, subtract) {
+ IOAddress addr1("192.0.2.12");
+ IOAddress addr2("192.0.2.5");
+ IOAddress addr3("192.0.2.0");
+ IOAddress addr4("0.0.2.1");
+ IOAddress any4("0.0.0.0");
+ IOAddress bcast("255.255.255.255");
+
+ EXPECT_EQ("0.0.0.7", IOAddress::subtract(addr1, addr2).toText());
+ EXPECT_EQ("0.0.0.12", IOAddress::subtract(addr1, addr3).toText());
+
+ // Subtracting 0.0.0.0 is like subtracting 0.
+ EXPECT_EQ("192.0.2.12", IOAddress::subtract(addr1, any4).toText());
+ EXPECT_EQ("192.0.2.13", IOAddress::subtract(addr1, bcast).toText());
+ EXPECT_EQ("191.255.255.255", IOAddress::subtract(addr3, addr4).toText());
+
+ // Let's check if we can subtract greater address from smaller.
+ // This will check if we can "loop"
+ EXPECT_EQ("255.255.255.251", IOAddress::subtract(addr3, addr2).toText());
+
+ IOAddress addr6("fe80::abcd");
+ IOAddress addr7("fe80::");
+ IOAddress addr8("fe80::1234");
+ IOAddress addr9("2001:db8::face");
+ IOAddress addr10("2001:db8::ffff:ffff:ffff:ffff");
+ IOAddress addr11("::1");
+ IOAddress any6("::");
+
+ EXPECT_EQ(IOAddress("::abcd"), IOAddress::subtract(addr6, addr7));
+ EXPECT_EQ(IOAddress("::9999"), IOAddress::subtract(addr6, addr8));
+ EXPECT_EQ("::ffff:ffff:ffff:531", IOAddress::subtract(addr10, addr9).toText());
+
+ // Subtract with borrow, extreme edition. Need to borrow one bit
+ // 112 times.
+ EXPECT_EQ("fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ IOAddress::subtract(addr7, addr11).toText());
+
+ // Now check if we can loop beyond :: (:: - ::1 is a lot of F's)
+ EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ IOAddress::subtract(any6, addr11).toText());
+
+ // Subtracting :: is like subtracting 0.
+ EXPECT_EQ("2001:db8::face", IOAddress::subtract(addr9, any6).toText());
+
+ // Let's check if we can subtract greater address from smaller.
+ // This will check if we can "loop"
+ EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:edcc",
+ IOAddress::subtract(addr7, addr8).toText());
+
+ // Inter-family relations are not allowed.
+ EXPECT_THROW(IOAddress::subtract(addr1, addr6), isc::BadValue);
+ EXPECT_THROW(IOAddress::subtract(addr6, addr1), isc::BadValue);
+}
+
+// Test checks whether an address can be increased.
+TEST(IOAddressTest, increaseAddr) {
+ IOAddress addr1("192.0.2.12");
+ IOAddress any4("0.0.0.0");
+ IOAddress bcast("255.255.255.255");
+ IOAddress addr6("2001:db8::ffff:ffff:ffff:ffff");
+ IOAddress addr11("::1");
+ IOAddress any6("::");
+ IOAddress the_last_one("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+
+ EXPECT_EQ("192.0.2.13", IOAddress::increase(addr1).toText());
+ EXPECT_EQ("0.0.0.1", IOAddress::increase(any4).toText());
+ EXPECT_EQ("0.0.0.0", IOAddress::increase(bcast).toText());
+ EXPECT_EQ("2001:db8:0:1::", IOAddress::increase(addr6).toText());
+ EXPECT_EQ(IOAddress("::2"), IOAddress::increase(addr11));
+ EXPECT_EQ(IOAddress("::1"), IOAddress::increase(any6));
+ EXPECT_EQ(IOAddress("::"), IOAddress::increase(the_last_one));
+}
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
new file mode 100644
index 0000000..314a6a0
--- /dev/null
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.cc
@@ -0,0 +1,286 @@
+// 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 <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <sstream>
+#include <string>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <string.h>
+
+using namespace isc::asiolink;
+
+namespace {
+typedef boost::shared_ptr<const IOEndpoint> ConstIOEndpointPtr;
+
+TEST(IOEndpointTest, createUDPv4) {
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 53210));
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(53210, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(static_cast<short>(IPPROTO_UDP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv4) {
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("192.0.2.1"), 5301));
+ EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+ EXPECT_EQ(5301, ep->getPort());
+ EXPECT_EQ(AF_INET, ep->getFamily());
+ EXPECT_EQ(AF_INET, ep->getAddress().getFamily());
+ EXPECT_EQ(static_cast<short>(IPPROTO_TCP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createUDPv6) {
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1234"),
+ 5302));
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5302, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ EXPECT_EQ(static_cast<short>(IPPROTO_UDP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, createTCPv6) {
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1234"),
+ 5303));
+ EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+ EXPECT_EQ(5303, ep->getPort());
+ EXPECT_EQ(AF_INET6, ep->getFamily());
+ EXPECT_EQ(AF_INET6, ep->getAddress().getFamily());
+ EXPECT_EQ(static_cast<short>(IPPROTO_TCP), ep->getProtocol());
+}
+
+TEST(IOEndpointTest, equality) {
+ std::vector<ConstIOEndpointPtr> epv;
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1234"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1234"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1234"), 5304)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1234"), 5304)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1235"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1235"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1235"), 5304)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1235"), 5304)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("192.0.2.1"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("192.0.2.1"), 5304)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 5304)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("192.0.2.2"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.2"), 5303)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("192.0.2.2"), 5304)));
+ epv.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.2"), 5304)));
+
+ for (size_t i = 0; i < epv.size(); ++i) {
+ for (size_t j = 0; j < epv.size(); ++j) {
+ if (i != j) {
+ // We use EXPECT_TRUE/FALSE instead of _EQ here, since
+ // _EQ requires there is an operator<< as well
+ EXPECT_FALSE(*epv[i] == *epv[j]);
+ EXPECT_TRUE(*epv[i] != *epv[j]);
+ }
+ }
+ }
+
+ // Create a second array with exactly the same values. We use create()
+ // again to make sure we get different endpoints
+ std::vector<ConstIOEndpointPtr> epv2;
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1234"), 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1234"), 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1234"), 5304)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1234"), 5304)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1235"), 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1235"), 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("2001:db8::1235"), 5304)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("2001:db8::1235"), 5304)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP,
+ IOAddress("192.0.2.1"), 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"),
+ 5304)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"),
+ 5304)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"),
+ 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"),
+ 5303)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"),
+ 5304)));
+ epv2.push_back(ConstIOEndpointPtr(
+ IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"),
+ 5304)));
+
+ for (size_t i = 0; i < epv.size(); ++i) {
+ EXPECT_TRUE(*epv[i] == *epv2[i]);
+ EXPECT_FALSE(*epv[i] != *epv2[i]);
+ }
+}
+
+TEST(IOEndpointTest, createIPProto) {
+ EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
+ 53210)->getAddress().toText(),
+ IOError);
+}
+
+void
+sockAddrMatch(const struct sockaddr& actual_sa,
+ const char* const expected_addr_text,
+ const char* const expected_port_text)
+{
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; // this shouldn't matter
+ hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+ struct addrinfo* res;
+ ASSERT_EQ(0, getaddrinfo(expected_addr_text, expected_port_text, &hints,
+ &res));
+ EXPECT_EQ(res->ai_family, actual_sa.sa_family);
+#ifdef HAVE_SA_LEN
+ // ASIO doesn't seem to set sa_len, so we set it to the expected value
+ res->ai_addr->sa_len = actual_sa.sa_len;
+#endif
+ EXPECT_EQ(0, memcmp(res->ai_addr, &actual_sa, res->ai_addrlen));
+ freeaddrinfo(res);
+}
+
+TEST(IOEndpointTest, getSockAddr) {
+ // UDP/IPv4
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 53210));
+ sockAddrMatch(ep->getSockAddr(), "192.0.2.1", "53210");
+
+ // UDP/IPv6
+ ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+ sockAddrMatch(ep->getSockAddr(), "2001:db8::53", "53");
+
+ // TCP/IPv4
+ ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 53211));
+ sockAddrMatch(ep->getSockAddr(), "192.0.2.2", "53211");
+
+ // TCP/IPv6
+ ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::5300"), 35));
+ sockAddrMatch(ep->getSockAddr(), "2001:db8::5300", "35");
+}
+
+// A faked IOEndpoint for an uncommon address family. It wouldn't be possible
+// to create via the normal factory, so we define a special derived class
+// for it.
+class TestIOEndpoint : public IOEndpoint {
+ virtual IOAddress getAddress() const {
+ return IOAddress("2001:db8::bad:add");
+ }
+ virtual uint16_t getPort() const { return (42); }
+ virtual short getProtocol() const { return (IPPROTO_UDP); }
+ virtual short getFamily() const { return (AF_UNSPEC); }
+ virtual const struct sockaddr& getSockAddr() const {
+ static struct sockaddr sa_placeholder;
+ return (sa_placeholder);
+ }
+};
+
+void
+checkEndpointText(const std::string& expected, const IOEndpoint& ep) {
+ std::ostringstream oss;
+ oss << ep;
+ EXPECT_EQ(expected, oss.str());
+}
+
+// test operator<<. We simply confirm it appends the result of toText().
+TEST(IOEndpointTest, LeftShiftOperator) {
+ // UDP/IPv4
+ ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress("192.0.2.1"), 53210));
+ checkEndpointText("192.0.2.1:53210", *ep);
+
+ // UDP/IPv6
+ ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+ checkEndpointText("[2001:db8::53]:53", *ep);
+
+ // Same for TCP: shouldn't be different
+ ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 53210));
+ checkEndpointText("192.0.2.1:53210", *ep);
+ ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::53"), 53));
+ checkEndpointText("[2001:db8::53]:53", *ep);
+
+ // Uncommon address family. The actual behavior doesn't matter much
+ // in practice, but we check such input doesn't make it crash.
+ // We explicitly instantiate the test EP because otherwise some compilers
+ // would be confused and complain.
+ TestIOEndpoint test_ep;
+ checkEndpointText("2001:db8::bad:add:42", test_ep);
+}
+}
diff --git a/src/lib/asiolink/tests/io_service_signal_unittests.cc b/src/lib/asiolink/tests/io_service_signal_unittests.cc
new file mode 100644
index 0000000..1dd41a6
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_signal_unittests.cc
@@ -0,0 +1,282 @@
+// Copyright (C) 2014-2023 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 <asiolink/interval_timer.h>
+#include <asiolink/io_service_signal.h>
+#include <asiolink/testutils/timed_signal.h>
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <queue>
+
+using namespace isc::asiolink::test;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Test fixture for testing the use of IO service signals.
+///
+/// This fixture is exercises IO service signaling.
+class IOSignalTest : public ::testing::Test {
+public:
+ /// @brief IOService instance to process IO.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Failsafe timer to ensure test(s) do not hang.
+ isc::asiolink::IntervalTimer test_timer_;
+
+ /// @brief Maximum time should be allowed to run.
+ int test_time_ms_;
+
+ /// @brief IOSignalSet object.
+ IOSignalSetPtr io_signal_set_;
+
+ /// @brief Vector to record the signal values received.
+ std::vector<int> processed_signals_;
+
+ /// @brief The number of signals that must be received to stop the test.
+ int stop_at_count_;
+
+ /// @brief Boolean which causes IOSignalHandler to throw if true.
+ bool handler_throw_error_;
+
+ /// @brief Constructor.
+ IOSignalTest() :
+ io_service_(new asiolink::IOService()), test_timer_(*io_service_),
+ test_time_ms_(0), io_signal_set_(),
+ processed_signals_(), stop_at_count_(0), handler_throw_error_(false) {
+
+ io_signal_set_.reset(new IOSignalSet(io_service_,
+ std::bind(&IOSignalTest::processSignal,
+ this, ph::_1)));
+ }
+
+ /// @brief Destructor.
+ ~IOSignalTest() {
+ io_signal_set_.reset();
+ // Make sure the cancel handler for the IOSignalSet is called.
+ io_service_->poll();
+ }
+
+ /// @brief Method used as the IOSignalSet handler.
+ ///
+ /// Records the value of the given signal and checks if the desired
+ /// number of signals have been received. If so, the IOService is
+ /// stopped which will cause IOService::run() to exit, returning control
+ /// to the test.
+ ///
+ /// @param signum signal number.
+ void processSignal(int signum) {
+ // Remember the signal we got.
+ processed_signals_.push_back(signum);
+
+ // If the flag is on, force a throw to test error handling.
+ if (handler_throw_error_) {
+ handler_throw_error_ = false;
+ isc_throw(BadValue, "processSignal throwing simulated error");
+ }
+
+ // If we've hit the number we want stop the IOService. This will cause
+ // run to exit.
+ if (processed_signals_.size() >= stop_at_count_) {
+ io_service_->stop();
+ }
+ }
+
+ /// @brief Sets the failsafe timer for the test to the given time.
+ ///
+ /// @param test_time_ms maximum time in milliseconds the test should
+ /// be allowed to run.
+ void setTestTime(int test_time_ms) {
+ // Fail safe shutdown
+ test_time_ms_ = test_time_ms;
+ test_timer_.setup(std::bind(&IOSignalTest::testTimerHandler, this),
+ test_time_ms_, asiolink::IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Failsafe timer expiration handler.
+ void testTimerHandler() {
+ io_service_->stop();
+ FAIL() << "Test Time: " << test_time_ms_ << " expired";
+ }
+};
+
+// Test the basic mechanics of IOSignal by handling one signal occurrence.
+TEST_F(IOSignalTest, singleSignalTest) {
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // Register to receive SIGINT.
+ ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+
+ // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
+ TimedSignal sig_int(*io_service_, SIGINT, 100);
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ // Verify that we processed the signal.
+ ASSERT_EQ(1, processed_signals_.size());
+
+ // Now check that signal value is correct.
+ EXPECT_EQ(SIGINT, processed_signals_[0]);
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // Unregister the receive of SIGINT.
+ ASSERT_NO_THROW(io_signal_set_->remove(SIGINT));
+
+ // Use TimedSignal to generate SIGINT 100 ms after we start IOService::run.
+ TimedSignal sig_int_too_late(*io_service_, SIGINT, 100);
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ // Verify that we did not process the signal.
+ ASSERT_EQ(1, processed_signals_.size());
+}
+
+// Test verifies that signals can be delivered rapid-fire without falling over.
+TEST_F(IOSignalTest, hammer) {
+ // Set test fail safe. We want to allow at least 100 ms per signal,
+ // plus a bit more so 6 seconds ought to be enough.
+ setTestTime(6000);
+
+ // Register to receive SIGINT.
+ ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+
+ // Stop the test after 50 signals. This allows 100ms+ per signal
+ // so even sluggish VMs should handle it.
+ stop_at_count_ = 50;
+
+ // User a repeating TimedSignal so we should generate a signal every 1 ms
+ // until we hit our stop count.
+ TimedSignal sig_int(*io_service_, SIGINT, 1,
+ asiolink::IntervalTimer::REPEATING);
+
+ // Start processing IO. This should continue until we stop either by
+ // hitting the stop count or if things go wrong, max test time.
+ io_service_->run();
+
+ // Verify we received the expected number of signals.
+ EXPECT_EQ(stop_at_count_, processed_signals_.size());
+
+ // Now check that each signal value is correct. This is sort of a silly
+ // check but it does ensure things didn't go off the rails somewhere.
+ for (int i = 0; i < processed_signals_.size(); ++i) {
+ EXPECT_EQ(SIGINT, processed_signals_[i]);
+ }
+}
+
+// Verifies that handler exceptions are caught.
+TEST_F(IOSignalTest, handlerThrow) {
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // Register to receive SIGINT.
+ ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+
+ // Set the stop after we've done at least 1 all the way through.
+ stop_at_count_ = 1;
+
+ // Use TimedSignal to generate SIGINT after we start IOService::run.
+ TimedSignal sig_int(*io_service_, SIGINT, 100,
+ asiolink::IntervalTimer::REPEATING);
+
+ // Set the test flag to cause the handler to throw an exception.
+ handler_throw_error_ = true;
+
+ // Start processing IO. This should fail with the handler exception.
+ ASSERT_NO_THROW(io_service_->run());
+
+ // Verify that the we hit the throw block. The flag will be false
+ // we will have skipped the stop count check so number signals processed
+ // is stop_at_count_ + 1.
+ EXPECT_FALSE(handler_throw_error_);
+ EXPECT_EQ(stop_at_count_ + 1, processed_signals_.size());
+}
+
+// Verifies that we can handle a mixed set of signals.
+TEST_F(IOSignalTest, mixedSignals) {
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // Register to receive SIGINT, SIGUSR1, and SIGUSR2.
+ ASSERT_NO_THROW(io_signal_set_->add(SIGINT));
+ ASSERT_NO_THROW(io_signal_set_->add(SIGUSR1));
+ ASSERT_NO_THROW(io_signal_set_->add(SIGUSR2));
+
+ stop_at_count_ = 8;
+
+ // User a repeating TimedSignal so we should generate a signal every 3, 5
+ // and 7 ms until we hit our stop count.
+ TimedSignal sig_1(*io_service_, SIGINT, 3,
+ asiolink::IntervalTimer::REPEATING);
+ TimedSignal sig_2(*io_service_, SIGUSR1, 5,
+ asiolink::IntervalTimer::REPEATING);
+ TimedSignal sig_3(*io_service_, SIGUSR2, 7,
+ asiolink::IntervalTimer::REPEATING);
+
+ // Start processing IO. This should continue until we stop either by
+ // hitting the stop count or if things go wrong, max test time.
+ io_service_->run();
+
+ // Verify we received the expected number of signals.
+ ASSERT_EQ(stop_at_count_, processed_signals_.size());
+
+ // There is no guarantee that the signals will always be delivered in the
+ // order they are raised, but all of them should get delivered. Loop
+ // through and tally them up.
+ int sigint_cnt = 0;
+ int sigusr1_cnt = 0;
+ int sigusr2_cnt = 0;
+ for (int i = 0; i < stop_at_count_; ++i) {
+ switch (processed_signals_[i]) {
+ case SIGINT:
+ ++sigint_cnt;
+ break;
+ case SIGUSR1:
+ ++sigusr1_cnt;
+ break;
+ case SIGUSR2:
+ ++sigusr2_cnt;
+ break;
+ default:
+ FAIL() << "Invalid signal value: "
+ << processed_signals_[i]
+ << " at i:" << i;
+ break;
+ }
+ }
+
+ // See if our counts are correct.
+ EXPECT_EQ(sigint_cnt, 4);
+ EXPECT_EQ(sigusr1_cnt, 2);
+ EXPECT_EQ(sigusr2_cnt, 2);
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/tests/io_service_thread_pool_unittests.cc b/src/lib/asiolink/tests/io_service_thread_pool_unittests.cc
new file mode 100644
index 0000000..d867011
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_thread_pool_unittests.cc
@@ -0,0 +1,264 @@
+// Copyright (C) 2021-2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_service_thread_pool.h>
+#include <exceptions/exceptions.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple test fixture for testing IoServiceThreadPool.
+class IoServiceThreadPoolTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ IoServiceThreadPoolTest()
+ : io_service_(new IOService()) {
+ }
+
+ /// @brief Destructor.
+ virtual ~IoServiceThreadPoolTest() {
+ io_service_->stop();
+ }
+
+ /// @brief IOService instance used by thread pools.
+ IOServicePtr io_service_;
+};
+
+// URL contains scheme and hostname.
+TEST_F(IoServiceThreadPoolTest, invalidConstruction) {
+ IoServiceThreadPoolPtr pool;
+
+ // Constructing with pool size of 0 should fail.
+ ASSERT_THROW_MSG(pool.reset(new IoServiceThreadPool(io_service_, 0)),
+ BadValue, "pool_size must be non 0");
+}
+
+// Verifies that a pool can be created without starting it.
+TEST_F(IoServiceThreadPoolTest, deferredStartConstruction) {
+ IoServiceThreadPoolPtr pool;
+
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3, true)));
+
+ // State should be stopped.
+ // Pool size should be 3.
+ // IOService should be there.
+ // IOService is new, so it should not be stopped.
+ // No threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_EQ(pool->getPoolSize(), 3);
+ ASSERT_TRUE(pool->getIOService());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destructor should not throw.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that a pool can be started within the constructor.
+TEST_F(IoServiceThreadPoolTest, startDuringConstruction) {
+ IoServiceThreadPoolPtr pool;
+
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3)));
+
+ // State should be running.
+ // Pool size should be 3.
+ // IOService should be there.
+ // IOService is new, so it should not be stopped.
+ // Should have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_EQ(pool->getPoolSize(), 3);
+ ASSERT_TRUE(pool->getIOService());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destructor should not throw.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from STOPPED to RUNNING.
+TEST_F(IoServiceThreadPoolTest, stoppedToRunning) {
+ IoServiceThreadPoolPtr pool;
+
+ // Create a stopped pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3, true)));
+ ASSERT_TRUE(pool->isStopped());
+
+ // Call run from STOPPED.
+ ASSERT_NO_THROW_LOG(pool->run());
+
+ // State should be RUNNING, IOService should not be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Calling run again should be harmless.
+ ASSERT_NO_THROW_LOG(pool->run());
+
+ // State should be RUNNING, IOService should not be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from RUNNING to STOPPED.
+TEST_F(IoServiceThreadPoolTest, runningToStopped) {
+ IoServiceThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call stop.
+ ASSERT_NO_THROW_LOG(pool->stop());
+
+ // State should be STOPPED, IOService should be stopped, we should
+ // have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Calling stop again should be harmless.
+ ASSERT_NO_THROW_LOG(pool->stop());
+
+ // State should be STOPPED, IOService should be stopped, we should
+ // have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from RUNNING to PAUSED.
+TEST_F(IoServiceThreadPoolTest, runningToPaused) {
+ IoServiceThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call pause from RUNNING.
+ ASSERT_NO_THROW_LOG(pool->pause());
+
+ // State should be PAUSED, IOService should be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isPaused());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Calling pause again should be harmless.
+ ASSERT_NO_THROW_LOG(pool->pause());
+
+ // State should be PAUSED, IOService should be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isPaused());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from PAUSED to RUNNING.
+TEST_F(IoServiceThreadPoolTest, pausedToRunning) {
+ IoServiceThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call pause from RUNNING.
+ ASSERT_NO_THROW_LOG(pool->pause());
+ ASSERT_TRUE(pool->isPaused());
+
+ // Call run.
+ ASSERT_NO_THROW_LOG(pool->run());
+
+ // State should be RUNNING, IOService should not be stopped, we should
+ // have 3 threads in the pool.
+ ASSERT_TRUE(pool->isRunning());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 3);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that pool can move from PAUSED to STOPPED.
+TEST_F(IoServiceThreadPoolTest, pausedToStopped) {
+ IoServiceThreadPoolPtr pool;
+
+ // Create a running pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3, false)));
+ ASSERT_TRUE(pool->isRunning());
+
+ // Call pause from RUNNING.
+ ASSERT_NO_THROW_LOG(pool->pause());
+ ASSERT_TRUE(pool->isPaused());
+
+ // Call stop.
+ ASSERT_NO_THROW_LOG(pool->stop());
+
+ // State should be STOPPED, IOService should be stopped, we should
+ // have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_TRUE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+// Verifies that attempting to pause a STOPPED pool has no effect.
+TEST_F(IoServiceThreadPoolTest, stoppedToPaused) {
+ IoServiceThreadPoolPtr pool;
+
+ // Create a stopped pool.
+ ASSERT_NO_THROW_LOG(pool.reset(new IoServiceThreadPool(io_service_, 3, true)));
+ ASSERT_TRUE(pool->isStopped());
+
+ // State should be STOPPED, IOService won't be stopped because it was
+ // never started. We should have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Call pause from STOPPED.
+ ASSERT_NO_THROW_LOG(pool->pause());
+
+ // Should have no effect.
+ ASSERT_TRUE(pool->isStopped());
+
+ // State should be STOPPED, IOService won't be stopped because it was
+ // never started. We should have 0 threads in the pool.
+ ASSERT_TRUE(pool->isStopped());
+ EXPECT_FALSE(pool->getIOService()->stopped());
+ EXPECT_EQ(pool->getThreadCount(), 0);
+
+ // Destroying the pool should be fine.
+ ASSERT_NO_THROW_LOG(pool.reset());
+}
+
+}
diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc
new file mode 100644
index 0000000..05fc5ac
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (C) 2013-2020 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 <asiolink/io_service.h>
+
+#include <gtest/gtest.h>
+#include <functional>
+#include <vector>
+
+using namespace isc::asiolink;
+
+namespace {
+
+void
+postedEvent(std::vector<int>* destination, int value) {
+ destination->push_back(value);
+}
+
+// Check the posted events are called, in the same order they are posted.
+TEST(IOService, post) {
+ std::vector<int> called;
+ IOService service;
+ // Post two events
+ service.post(std::bind(&postedEvent, &called, 1));
+ service.post(std::bind(&postedEvent, &called, 2));
+ service.post(std::bind(&postedEvent, &called, 3));
+ // They have not yet been called
+ EXPECT_TRUE(called.empty());
+ // Process two events
+ service.run_one();
+ service.run_one();
+ // Both events were called in the right order
+ ASSERT_EQ(2, called.size());
+ EXPECT_EQ(1, called[0]);
+ EXPECT_EQ(2, called[1]);
+
+ // Test that poll() executes the last handler.
+ service.poll();
+ ASSERT_EQ(3, called.size());
+ EXPECT_EQ(3, called[2]);
+}
+
+}
diff --git a/src/lib/asiolink/tests/io_socket_unittest.cc b/src/lib/asiolink/tests/io_socket_unittest.cc
new file mode 100644
index 0000000..659a6a6
--- /dev/null
+++ b/src/lib/asiolink/tests/io_socket_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2011-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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_socket.h>
+
+#include <gtest/gtest.h>
+#include <netinet/in.h>
+
+using namespace isc::asiolink;
+
+TEST(IOSocketTest, dummySockets) {
+ EXPECT_EQ(static_cast<short>(IPPROTO_UDP),
+ IOSocket::getDummyUDPSocket().getProtocol());
+ EXPECT_EQ(static_cast<short>(IPPROTO_TCP),
+ IOSocket::getDummyTCPSocket().getProtocol());
+ EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
+ EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
+}
+
+
diff --git a/src/lib/asiolink/tests/process_spawn_app.sh.in b/src/lib/asiolink/tests/process_spawn_app.sh.in
new file mode 100644
index 0000000..eab7310
--- /dev/null
+++ b/src/lib/asiolink/tests/process_spawn_app.sh.in
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+# Copyright (C) 2015-2021 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 script is used for testing the ProcessSpawn utility class. This
+# class is used to fork and execute a new process. It also allows for
+# checking the exit code returned when the process terminates.
+# The unit tests execute this script via ProcessSpawn class with
+# different command line parameters to test the class functionality.
+#
+# In particular, they check if the class correctly records the exit code
+# returned. The exit code returned is controlled by the caller. It is
+# possible to explicitly specify the exit code to be returned using
+# the command line options. It is also possible to specify that the
+# exit code is "unique" for the process, so as the test can check
+# that two distinct processes spawned by the same ProcessSpawn
+# object may return different status code. The command line of this
+# script also allows for forcing the process to sleep so as the
+# test has much enough time to verify that the convenience methods
+# checking the state of the process, i.e. process running or not.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+exit_code=
+
+while test "${#}" -gt 0
+do
+ option=${1}
+ case ${option} in
+ -p)
+ exit_code=${$}
+ ;;
+ -e)
+ shift
+ exit_code=${1-}
+ ;;
+ -s)
+ shift
+ sleep "${1}"
+ ;;
+ -v)
+ shift
+ VAR_NAME=${1}
+ shift
+ VAR_VALUE=${1}
+ EXPECTED=$(env | grep -E "^${VAR_NAME}=")
+ if ! test "${VAR_NAME}=${VAR_VALUE}" = "${EXPECTED}"; then
+ exit 123
+ fi
+ ;;
+ *)
+ exit 123
+ ;;
+ esac
+ # We've shifted in the loop so we may have run out of parameters in the
+ # meantime. Check again.
+ if test "${#}" -gt 0; then
+ shift
+ fi
+done
+
+# The exit code of 32 is returned when no args specified or
+# when only the -s arg has been specified.
+if [ -z "${exit_code}" ]; then
+ exit 32
+fi
+
+exit "${exit_code}"
diff --git a/src/lib/asiolink/tests/process_spawn_unittest.cc b/src/lib/asiolink/tests/process_spawn_unittest.cc
new file mode 100644
index 0000000..0fda5a1
--- /dev/null
+++ b/src/lib/asiolink/tests/process_spawn_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright (C) 2015-2023 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 <asiolink/io_service_signal.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/process_spawn.h>
+#include <gtest/gtest.h>
+#include <signal.h>
+#include <stdint.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <testutils/gtest_utils.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace std;
+namespace ph = std::placeholders;
+
+/// @brief Test fixture for testing the use of ProcessSpawn with IO service
+/// signals.
+///
+/// This fixture is exercises ProcessSpawn using IO service signaling.
+class ProcessSpawnTest : public ::testing::Test {
+public:
+ /// @brief IOService instance to process IO.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Single instance of IOService.
+ static asiolink::IOServicePtr getIOService() {
+ static asiolink::IOServicePtr io_service(new asiolink::IOService());
+ return (io_service);
+ }
+
+ /// @brief Failsafe timer to ensure test(s) do not hang.
+ isc::asiolink::IntervalTimer test_timer_;
+
+ /// @brief Maximum time should be allowed to run.
+ int test_time_ms_;
+
+ /// @brief IOSignalSet object.
+ IOSignalSetPtr io_signal_set_;
+
+ /// @brief Vector to record the signal values received.
+ std::vector<int> processed_signals_;
+
+ /// @brief Constructor.
+ ProcessSpawnTest() :
+ io_service_(getIOService()), test_timer_(*io_service_),
+ test_time_ms_(0), io_signal_set_(), processed_signals_() {
+
+ io_signal_set_.reset(new IOSignalSet(io_service_,
+ std::bind(&ProcessSpawnTest::processSignal,
+ this, ph::_1)));
+ io_signal_set_->add(SIGCHLD);
+ }
+
+ /// @brief Destructor.
+ ~ProcessSpawnTest() {
+ io_signal_set_->remove(SIGCHLD);
+ io_signal_set_.reset();
+ // Make sure the cancel handler for the IOSignalSet is called.
+ io_service_->poll();
+ }
+
+ /// @brief Method used as the IOSignalSet handler.
+ ///
+ /// Records the value of the given signal and checks if the desired
+ /// number of signals have been received. If so, the IOService is
+ /// stopped which will cause IOService::run() to exit, returning control
+ /// to the test.
+ ///
+ /// @param signum signal number.
+ void processSignal(int signum) {
+ // Remember the signal we got.
+ processed_signals_.push_back(signum);
+ }
+
+ /// @brief Sets the failsafe timer for the test to the given time.
+ ///
+ /// @param test_time_ms maximum time in milliseconds the test should
+ /// be allowed to run.
+ void setTestTime(int test_time_ms) {
+ // Fail safe shutdown
+ test_time_ms_ = test_time_ms;
+ test_timer_.setup(std::bind(&ProcessSpawnTest::testTimerHandler, this),
+ test_time_ms_, asiolink::IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Failsafe timer expiration handler.
+ void testTimerHandler() {
+ io_service_->stop();
+ }
+};
+
+// This test verifies that the external application can be ran with
+// arguments and that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnWithArgs) {
+ vector<string> args;
+ args.push_back("-e");
+ args.push_back("64");
+
+ ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ ASSERT_EQ(1, processed_signals_.size());
+ ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+
+ EXPECT_EQ(64, process.getExitStatus(pid));
+}
+
+// This test verifies that the external application can be ran with
+// arguments and environment variables that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnWithArgsAndEnvVars) {
+ vector<string> args;
+ vector<string> vars;
+ args.push_back("-v");
+ args.push_back("TEST_VARIABLE_NAME");
+ args.push_back("TEST_VARIABLE_VALUE");
+ vars.push_back("TEST_VARIABLE_NAME=TEST_VARIABLE_VALUE");
+
+ ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args, vars);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ ASSERT_EQ(1, processed_signals_.size());
+ ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+
+ EXPECT_EQ(32, process.getExitStatus(pid));
+}
+
+// This test verifies that the single ProcessSpawn object can be used
+// to start two processes and that their status codes can be gathered.
+// It also checks that it is possible to clear the status of the
+// process.
+TEST_F(ProcessSpawnTest, spawnTwoProcesses) {
+ vector<string> args;
+ args.push_back("-p");
+
+ ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args);
+ pid_t pid1 = 0;
+ ASSERT_NO_THROW(pid1 = process.spawn());
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ pid_t pid2 = 0;
+ ASSERT_NO_THROW(pid2 = process.spawn());
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ ASSERT_EQ(2, processed_signals_.size());
+ ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+ ASSERT_EQ(SIGCHLD, processed_signals_[1]);
+
+ EXPECT_NE(process.getExitStatus(pid1), process.getExitStatus(pid2));
+
+ // Clear the status of the first process. An attempt to get the status
+ // for the cleared process should result in exception. But, there should
+ // be no exception for the second process.
+ process.clearState(pid1);
+ EXPECT_THROW(process.getExitStatus(pid1), InvalidOperation);
+ EXPECT_NO_THROW(process.getExitStatus(pid2));
+
+ process.clearState(pid2);
+ EXPECT_THROW(process.getExitStatus(pid2), InvalidOperation);
+}
+
+// This test verifies that the external application can be ran without
+// arguments and that the exit code is gathered.
+TEST_F(ProcessSpawnTest, spawnNoArgs) {
+ ProcessSpawn process(io_service_, TEST_SCRIPT_SH);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ ASSERT_EQ(1, processed_signals_.size());
+ ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+
+ EXPECT_EQ(32, process.getExitStatus(pid));
+
+ ASSERT_NO_THROW(pid = process.spawn(true));
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ ASSERT_EQ(2, processed_signals_.size());
+ ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+ ASSERT_EQ(SIGCHLD, processed_signals_[1]);
+
+ EXPECT_THROW(process.getExitStatus(pid), InvalidOperation);
+}
+
+// This test verifies that the EXIT_FAILURE code is returned when
+// application can't be executed.
+TEST_F(ProcessSpawnTest, invalidExecutable) {
+ std::string expected = "File not found: foo";
+ ASSERT_THROW_MSG(ProcessSpawn process(io_service_, "foo"),
+ ProcessSpawnError, expected);
+
+ std::string name = INVALID_TEST_SCRIPT_SH;
+
+ expected = "File not executable: ";
+ expected += name;
+ ASSERT_THROW_MSG(ProcessSpawn process(io_service_, name),
+ ProcessSpawnError, expected);
+}
+
+// This test verifies that the full command line for the process is
+// returned.
+TEST_F(ProcessSpawnTest, getCommandLine) {
+ // Note that cases below are enclosed in separate scopes to make
+ // sure that the ProcessSpawn object is destroyed before a new
+ // object is created. Current implementation doesn't allow for
+ // having two ProcessSpawn objects simultaneously as they will
+ // both try to allocate a signal handler for SIGCHLD.
+ {
+ // Case 1: arguments present.
+ ProcessArgs args;
+ args.push_back("-x");
+ args.push_back("-y");
+ args.push_back("foo");
+ args.push_back("bar");
+ ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args);
+ std::string expected = TEST_SCRIPT_SH;
+ expected += " -x -y foo bar";
+ EXPECT_EQ(expected, process.getCommandLine());
+ }
+
+ {
+ // Case 2: no arguments.
+ ProcessSpawn process(io_service_, TEST_SCRIPT_SH);
+ EXPECT_EQ(TEST_SCRIPT_SH, process.getCommandLine());
+ }
+}
+
+// This test verifies that it is possible to check if the process is
+// running.
+TEST_F(ProcessSpawnTest, isRunning) {
+ // Run the process which sleeps for 10 seconds, so as we have
+ // enough time to check if it is running.
+ vector<string> args;
+ args.push_back("-s");
+ args.push_back("10");
+
+ ProcessSpawn process(io_service_, TEST_SCRIPT_SH, args);
+ pid_t pid = 0;
+ ASSERT_NO_THROW(pid = process.spawn());
+
+ EXPECT_TRUE(process.isRunning(pid));
+
+ // Kill the process.
+ ASSERT_EQ(0, kill(pid, SIGKILL));
+
+ // Set test fail safe.
+ setTestTime(1000);
+
+ // The next handler executed is IOSignal's handler.
+ io_service_->run_one();
+
+ // The first handler executed is the IOSignal's internal timer expire
+ // callback.
+ io_service_->run_one();
+
+ // Polling once to be sure.
+ io_service_->poll();
+
+ ASSERT_EQ(1, processed_signals_.size());
+ ASSERT_EQ(SIGCHLD, processed_signals_[0]);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/asiolink/tests/run_unittests.cc b/src/lib/asiolink/tests/run_unittests.cc
new file mode 100644
index 0000000..566c7c9
--- /dev/null
+++ b/src/lib/asiolink/tests/run_unittests.cc
@@ -0,0 +1,19 @@
+// Copyright (C) 2009-2018 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 <log/logger_manager.h>
+
+int
+main(int argc, char* argv[])
+{
+ ::testing::InitGoogleTest(&argc, argv); // Initialize Google test
+ isc::log::LoggerManager::init("unittest"); // Set a root logger name
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/asiolink/tests/tcp_acceptor_unittest.cc b/src/lib/asiolink/tests/tcp_acceptor_unittest.cc
new file mode 100644
index 0000000..5a925cc
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_acceptor_unittest.cc
@@ -0,0 +1,444 @@
+// Copyright (C) 2016-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tcp_endpoint.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+#include <list>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple class representing TCP socket callback.
+class SocketCallback {
+public:
+
+ /// @brief Implements callback for the asynchronous operation on the socket.
+ ///
+ /// This callback merely checks if error has occurred and reports this
+ /// error. It does nothing in case of success.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of received data.
+ void operator()(boost::system::error_code ec, size_t length = 0) {
+ if (ec) {
+ ADD_FAILURE() << "error occurred for a socket with data of length "
+ << length << ": " << ec.message();
+ }
+ }
+
+};
+
+/// @brief Entity which can connect to the TCP server endpoint and close the
+/// connection.
+class TCPClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error.
+ explicit TCPClient(IOService& io_service)
+ : io_service_(io_service.get_io_service()), socket_(io_service_) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TCPClient() {
+ close();
+ }
+
+ /// @brief Connect to the test server address and port.
+ ///
+ /// This method asynchronously connects to the server endpoint and uses the
+ /// connectHandler as a callback function.
+ void connect() {
+ boost::asio::ip::tcp::endpoint
+ endpoint(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
+ socket_.async_connect(endpoint,
+ std::bind(&TCPClient::connectHandler, this,
+ ph::_1));
+ }
+
+ /// @brief Callback function for connect().
+ ///
+ /// This function stops the IO service upon error.
+ ///
+ /// @param ec Error code.
+ void connectHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ // One would expect that async_connect wouldn't return EINPROGRESS
+ // error code, but simply wait for the connection to get
+ // established before the handler is invoked. It turns out, however,
+ // that on some OSes the connect handler may receive this error code
+ // which doesn't necessarily indicate a problem. Making an attempt
+ // to write and read from this socket will typically succeed. So,
+ // we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ }
+ }
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ boost::asio::ip::tcp::socket socket_;
+
+};
+
+/// @brief Pointer to the TCPClient.
+typedef boost::shared_ptr<TCPClient> TCPClientPtr;
+
+/// @brief A signature of the function implementing callback for the
+/// TCPAcceptor.
+typedef std::function<void(const boost::system::error_code&)> TCPAcceptorCallback;
+
+/// @brief TCPAcceptor using TCPAcceptorCallback.
+typedef TCPAcceptor<TCPAcceptorCallback> TestTCPAcceptor;
+
+/// @brief Implements asynchronous TCP acceptor service.
+///
+/// It creates a new socket into which connection is accepted. The socket
+/// is retained until class instance exists.
+class Acceptor {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ /// @param acceptor Reference to the TCP acceptor on which asyncAccept
+ /// will be called.
+ /// @param callback Callback function for the asyncAccept.
+ explicit Acceptor(IOService& io_service, TestTCPAcceptor& acceptor,
+ const TCPAcceptorCallback& callback)
+ : socket_(io_service), acceptor_(acceptor), callback_(callback) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes socket.
+ ~Acceptor() {
+ socket_.close();
+ }
+
+ /// @brief Asynchronous accept new connection.
+ void accept() {
+ acceptor_.asyncAccept(socket_, callback_);
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Socket into which connection is accepted.
+ TCPSocket<SocketCallback> socket_;
+
+ /// @brief Reference to the TCPAcceptor on which asyncAccept is called.
+ TestTCPAcceptor& acceptor_;
+
+ /// @brief Instance of the callback used for asyncAccept.
+ TCPAcceptorCallback callback_;
+
+};
+
+/// @brief Pointer to the Acceptor object.
+typedef boost::shared_ptr<Acceptor> AcceptorPtr;
+
+/// @brief Test fixture class for TCPAcceptor.
+///
+/// This class provides means for creating new TCP connections, i.e. simulates
+/// clients connecting to the servers via TCPAcceptor. It is possible to create
+/// multiple simultaneous connections, which are retained by the test fixture
+/// class and closed cleanly when the test fixture is destroyed.
+class TCPAcceptorTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Besides initializing class members it also sets the test timer to guard
+ /// against endlessly running IO service when TCP connections are
+ /// unsuccessful.
+ TCPAcceptorTest()
+ : io_service_(), acceptor_(io_service_),
+ asio_endpoint_(boost::asio::ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT),
+ endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
+ clients_(), connections_num_(0), aborted_connections_num_(0),
+ max_connections_(1) {
+ test_timer_.setup(std::bind(&TCPAcceptorTest::timeoutHandler, this),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ virtual ~TCPAcceptorTest() {
+ }
+
+ /// @brief Specifies how many new connections are expected before the IO
+ /// service is stopped.
+ ///
+ /// @param max_connections Connections limit.
+ void setMaxConnections(const unsigned int max_connections) {
+ max_connections_ = max_connections;
+ }
+
+ /// @brief Create ASIO endpoint from the provided endpoint by retaining the
+ /// IP address and modifying the port.
+ ///
+ /// This convenience method is useful to create new endpoint from the
+ /// existing endpoint to test reusing IP address for multiple acceptors.
+ /// The returned endpoint has the same IP address but different port.
+ ///
+ /// @param endpoint Source endpoint.
+ ///
+ /// @return New endpoint with the port number increased by 1.
+ boost::asio::ip::tcp::endpoint
+ createSiblingEndpoint(const boost::asio::ip::tcp::endpoint& endpoint) const {
+ boost::asio::ip::tcp::endpoint endpoint_copy(endpoint);
+ endpoint_copy.port(endpoint.port() + 1);
+ return (endpoint_copy);
+ }
+
+ /// @brief Opens TCP acceptor and sets 'reuse address' option.
+ void acceptorOpen() {
+ acceptor_.open(endpoint_);
+ acceptor_.setOption(TestTCPAcceptor::ReuseAddress(true));
+ }
+
+ /// @brief Starts accepting TCP connections.
+ ///
+ /// This method creates new Acceptor instance and calls accept() to start
+ /// accepting new connections. The instance of the Acceptor object is
+ /// retained in the connections_ list.
+ void accept() {
+ TCPAcceptorCallback cb = std::bind(&TCPAcceptorTest::acceptHandler,
+ this, ph::_1);
+ AcceptorPtr conn(new Acceptor(io_service_, acceptor_, cb));
+ connections_.push_back(conn);
+ connections_.back()->accept();
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TCPClient instance and retains it in the clients_
+ /// list.
+ void connect() {
+ TCPClientPtr client(new TCPClient(io_service_));
+ clients_.push_back(client);
+ clients_.back()->connect();
+ }
+
+ /// @brief Callback function for asynchronous accept calls.
+ ///
+ /// It stops the IO service upon error or when the number of accepted
+ /// connections reaches the max_connections_ value. Otherwise it calls
+ /// accept() to start accepting next connections.
+ ///
+ /// @param ec Error code.
+ void acceptHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while accepting connection: "
+ << ec.message();
+ } else {
+ ++aborted_connections_num_;
+ }
+ io_service_.stop();
+ }
+
+ // We have reached the maximum number of connections - end the test.
+ if (++connections_num_ >= max_connections_) {
+ io_service_.stop();
+ return;
+ }
+
+ accept();
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ void timeoutHandler() {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ io_service_.stop();
+ }
+
+ /// @brief IO service.
+ IOService io_service_;
+
+ /// @brief TCPAcceptor under test.
+ TestTCPAcceptor acceptor_;
+
+ /// @brief Server endpoint.
+ boost::asio::ip::tcp::endpoint asio_endpoint_;
+
+ /// @brief asiolink server endpoint (uses asio_endpoint_).
+ TCPEndpoint endpoint_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief List of connections on the server side.
+ std::list<AcceptorPtr> connections_;
+
+ /// @brief List of client connections.
+ std::list<TCPClientPtr> clients_;
+
+ /// @brief Current number of established connections.
+ unsigned int connections_num_;
+
+ /// @brief Current number of aborted connections.
+ unsigned int aborted_connections_num_;
+
+ /// @brief Connections limit.
+ unsigned int max_connections_;
+};
+
+// Test TCPAcceptor::asyncAccept.
+TEST_F(TCPAcceptorTest, asyncAccept) {
+ // Establish up to 10 connections.
+ setMaxConnections(10);
+
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TCP connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Run the IO service until we have accepted 10 connections, an error
+ // or test timeout occurred.
+ io_service_.run();
+
+ // Make sure that all accepted connections have been recorded.
+ EXPECT_EQ(10, connections_num_);
+ EXPECT_EQ(10, connections_.size());
+}
+
+// Check that it is possible to set SO_REUSEADDR flag for the TCPAcceptor.
+TEST_F(TCPAcceptorTest, reuseAddress) {
+ // We need at least two acceptors using common address. Let's create the
+ // second endpoint which has the same address but different port.
+ boost::asio::ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
+ TCPEndpoint endpoint2(asio_endpoint2);
+
+ // Create and open two acceptors.
+ TestTCPAcceptor acceptor1(io_service_);
+ TestTCPAcceptor acceptor2(io_service_);
+ ASSERT_NO_THROW(acceptor1.open(endpoint_));
+ ASSERT_NO_THROW(acceptor2.open(endpoint2));
+
+ // Set SO_REUSEADDR socket option so as acceptors can bind to the
+ /// same address.
+ ASSERT_NO_THROW(
+ acceptor1.setOption(TestTCPAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(
+ acceptor2.setOption(TestTCPAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(acceptor1.bind(endpoint_));
+ ASSERT_NO_THROW(acceptor2.bind(endpoint2));
+
+ // Create third acceptor, but don't set the SO_REUSEADDR. It should
+ // refuse to bind.
+ TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
+ TestTCPAcceptor acceptor3(io_service_);
+ ASSERT_NO_THROW(acceptor3.open(endpoint3));
+ EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
+}
+
+// Test that TCPAcceptor::getProtocol returns IPPROTO_TCP.
+TEST_F(TCPAcceptorTest, getProtocol) {
+ EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
+}
+
+// Test that TCPAcceptor::getNative returns valid socket descriptor.
+TEST_F(TCPAcceptorTest, getNative) {
+ // Initially the descriptor should be invalid (negative).
+ ASSERT_LT(acceptor_.getNative(), 0);
+ // Now open the socket and make sure the returned descriptor is now valid.
+ ASSERT_NO_THROW(acceptorOpen());
+ EXPECT_GE(acceptor_.getNative(), 0);
+}
+
+// macOS 10.12.3 has a bug which causes the connections to not enter
+// the TIME-WAIT state and they never get closed.
+#if !defined (OS_OSX)
+
+// Test that TCPAcceptor::close works properly.
+TEST_F(TCPAcceptorTest, close) {
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TCP connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Close the acceptor before connections are accepted.
+ acceptor_.close();
+
+ // Run the IO service.
+ io_service_.run();
+
+ // The connections should have been aborted.
+ EXPECT_EQ(1, connections_num_);
+ EXPECT_EQ(1, aborted_connections_num_);
+ EXPECT_EQ(1, connections_.size());
+}
+
+#endif
+
+}
diff --git a/src/lib/asiolink/tests/tcp_endpoint_unittest.cc b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc
new file mode 100644
index 0000000..0d838d4
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2011-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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc::asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// boost::asio::ip::tcp::endpoint object.
+
+TEST(TCPEndpointTest, v4Address) {
+ const string test_address("192.0.2.1");
+ const unsigned short test_port = 5301;
+
+ IOAddress address(test_address);
+ TCPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(static_cast<short>(IPPROTO_TCP), endpoint.getProtocol());
+ EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(TCPEndpointTest, v6Address) {
+ const string test_address("2001:db8::1235");
+ const unsigned short test_port = 5302;
+
+ IOAddress address(test_address);
+ TCPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(static_cast<short>(IPPROTO_TCP), endpoint.getProtocol());
+ EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
new file mode 100644
index 0000000..7531af6
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -0,0 +1,515 @@
+// Copyright (C) 2011-2021 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/.
+
+/// @brief Test of TCPSocket
+///
+/// Tests the functionality of a TCPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cstddef>
+#include <cstdlib>
+#include <errno.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string>
+#include <vector>
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5303;
+
+/// @todo Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O completes.
+/// The arguments to the completion callback are stored for later retrieval.
+class TCPCallback {
+public:
+ /// @brief Operations the server is doing
+ enum Operation {
+ ACCEPT = 0, ///< accept() was issued
+ OPEN = 1, /// Client connected to server
+ READ = 2, ///< Asynchronous read completed
+ WRITE = 3, ///< Asynchronous write completed
+ NONE = 4 ///< "Not set" state
+ };
+
+ /// @brief Minimum size of buffers
+ enum {
+ MIN_SIZE = (64 * 1024 + 2) ///< 64kB + two bytes for a count
+ };
+
+ struct PrivateData {
+ PrivateData() :
+ error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0),
+ name_(""), queued_(NONE), called_(NONE), data_(MIN_SIZE, 0)
+ {}
+
+ boost::system::error_code error_code_; ///< Completion error code
+ size_t length_; ///< Bytes transferred in this I/O
+ size_t cumulative_; ///< Cumulative bytes transferred
+ size_t expected_; ///< Expected amount of data
+ size_t offset_; ///< Where to put data in buffer
+ std::string name_; ///< Which of the objects this is
+ Operation queued_; ///< Queued operation
+ Operation called_; ///< Which callback called
+ std::vector<uint8_t> data_; ///< Receive buffer
+ };
+
+ /// @brief Constructor
+ ///
+ /// Constructs the object. It also creates the data member pointed to by
+ /// a shared pointer. When used as a callback object, this is copied as it
+ /// is passed into the asynchronous function. This means that there are two
+ /// objects and inspecting the one we passed in does not tell us anything.
+ ///
+ /// Therefore we use a boost::shared_ptr. When the object is copied, the
+ /// shared pointer is copied, which leaves both objects pointing to the same
+ /// data.
+ ///
+ /// @param which Which of the two callback objects this is
+ TCPCallback(std::string which) : ptr_(new PrivateData())
+ {
+ ptr_->name_ = which;
+ }
+
+ /// @brief Destructor
+ ///
+ /// No code needed, destroying the shared pointer destroys the private data.
+ virtual ~TCPCallback()
+ {}
+
+ /// @brief Client Callback Function
+ ///
+ /// Called when an asynchronous operation is completed by the client, this
+ /// stores the origin of the operation in the client_called_ data member.
+ ///
+ /// @param ec I/O completion error code passed to callback function.
+ /// @param length Number of bytes transferred
+ void operator()(boost::system::error_code ec = boost::system::error_code(),
+ size_t length = 0)
+ {
+ setCode(ec.value());
+ ptr_->called_ = ptr_->queued_;
+ ptr_->length_ = length;
+ }
+
+ /// @brief Get I/O completion error code
+ int getCode() {
+ return (ptr_->error_code_.value());
+ }
+
+ /// @brief Set I/O completion code
+ ///
+ /// @param code New value of completion code
+ void setCode(int code) {
+ ptr_->error_code_ = boost::system::error_code(code, boost::system::error_code().category());
+ }
+
+ /// @brief Get number of bytes transferred in I/O
+ size_t& length() {
+ return (ptr_->length_);
+ }
+
+ /// @brief Get cumulative number of bytes transferred in I/O
+ size_t& cumulative() {
+ return (ptr_->cumulative_);
+ }
+
+ /// @brief Get expected amount of data
+ size_t& expected() {
+ return (ptr_->expected_);
+ }
+
+ /// @brief Get offset into data
+ size_t& offset() {
+ return (ptr_->offset_);
+ }
+
+ /// @brief Get data member
+ uint8_t* data() {
+ return (&ptr_->data_[0]);
+ }
+
+ /// @brief Get flag to say what was queued
+ Operation& queued() {
+ return (ptr_->queued_);
+ }
+
+ /// @brief Get flag to say when callback was called
+ Operation& called() {
+ return (ptr_->called_);
+ }
+
+ /// @brief Return instance of callback name
+ std::string& name() {
+ return (ptr_->name_);
+ }
+
+private:
+ boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
+};
+
+
+// Read Server Data
+//
+// Called in the part of the test that has the client send a message to the
+// server, this loops until all the data has been read (synchronously) by the
+// server.
+//
+// "All the data read" means that the server has received a message that is
+// preceded by a two-byte count field and that the total amount of data received
+// from the remote end is equal to the value in the count field plus two bytes
+// for the count field itself.
+//
+// @param socket Socket on which the server is reading data
+// @param server_cb Structure in which server data is held.
+void
+serverRead(tcp::socket& socket, TCPCallback& server_cb) {
+
+ // As we may need to read multiple times, keep a count of the cumulative
+ // amount of data read and do successive reads into the appropriate part
+ // of the buffer.
+ //
+ // Note that there are no checks for buffer overflow - this is a test
+ // program and we have sized the buffer to be large enough for the test.
+ server_cb.cumulative() = 0;
+
+ bool complete = false;
+ while (!complete) {
+
+ // Read block of data and update cumulative amount of data received.
+ server_cb.length() = socket.receive(
+ boost::asio::buffer(server_cb.data() + server_cb.cumulative(),
+ TCPCallback::MIN_SIZE - server_cb.cumulative()));
+ server_cb.cumulative() += server_cb.length();
+
+ // If we have read at least two bytes, we can work out how much we
+ // should be reading.
+ if (server_cb.cumulative() >= 2) {
+ server_cb.expected() = readUint16(server_cb.data(), server_cb.length());
+ if ((server_cb.expected() + 2) == server_cb.cumulative()) {
+
+ // Amount of data read from socket equals the size of the
+ // message (as indicated in the first two bytes of the message)
+ // plus the size of the count field. Therefore we have received
+ // all the data.
+ complete = true;
+ }
+ }
+ }
+}
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TCPSocket, processReceivedData) {
+ const uint16_t PACKET_SIZE = 16382; // Amount of "real" data in the buffer
+
+ IOService service; // Used to instantiate socket
+ TCPSocket<TCPCallback> test(service); // Socket under test
+ uint8_t inbuff[PACKET_SIZE + 2]; // Buffer to check
+ OutputBufferPtr outbuff(new OutputBuffer(16));
+ // Where data is put
+ size_t expected; // Expected amount of data
+ size_t offset; // Where to put next data
+ size_t cumulative; // Cumulative data received
+
+ // Set some dummy values in the buffer to check
+ for (size_t i = 0; i < sizeof(inbuff); ++i) {
+ inbuff[i] = i % 256;
+ }
+
+ // Check that the method will handle various receive sizes.
+ writeUint16(PACKET_SIZE, inbuff, sizeof(inbuff));
+
+ cumulative = 0;
+ offset = 0;
+ expected = 0;
+ outbuff->clear();
+ bool complete = test.processReceivedData(inbuff, 1, cumulative, offset,
+ expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(1, cumulative);
+ EXPECT_EQ(1, offset);
+ EXPECT_EQ(0, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Now pretend that we've received one more byte.
+ complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected,
+ outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Add another two bytes. However, this time note that we have to offset
+ // in the input buffer because it is expected that the next chunk of data
+ // from the connection will be read into the start of the buffer.
+ complete = test.processReceivedData(inbuff + cumulative, 2, cumulative,
+ offset, expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(4, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(2, outbuff->getLength());
+
+ const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+
+ // And add the remaining data. Remember that "inbuff" is "PACKET_SIZE + 2"
+ // long.
+ complete = test.processReceivedData(inbuff + cumulative,
+ PACKET_SIZE + 2 - cumulative,
+ cumulative, offset, expected, outbuff);
+ EXPECT_TRUE(complete);
+ EXPECT_EQ(PACKET_SIZE + 2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(PACKET_SIZE, outbuff->getLength());
+ dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+}
+
+/// @todo Need to add a test to check the cancel() method
+
+// Tests the operation of a TCPSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(TCPSocket, sequenceTest) {
+
+ // Common objects.
+ IOService service; // Service object for async control
+
+ // The client - the TCPSocket being tested
+ TCPSocket<TCPCallback> client(service);// Socket under test
+ TCPCallback client_cb("Client"); // Async I/O callback function
+ TCPEndpoint client_remote_endpoint; // Where client receives message from
+ OutputBufferPtr client_buffer(new OutputBuffer(128));
+ // Received data is put here
+
+ // The server - with which the client communicates.
+ IOAddress server_address(SERVER_ADDRESS);
+ // Address of target server
+ TCPCallback server_cb("Server"); // Server callback
+ TCPEndpoint server_endpoint(server_address, SERVER_PORT);
+ // Endpoint describing server
+ TCPEndpoint server_remote_endpoint; // Address where server received message from
+ tcp::socket server_socket(service.get_io_service());
+ // Socket used for server
+
+ // Step 1. Create the connection between the client and the server. Set
+ // up the server to accept incoming connections and have the client open
+ // a channel to it.
+
+ // Set up server - open socket and queue an accept.
+ server_cb.queued() = TCPCallback::ACCEPT;
+ server_cb.called() = TCPCallback::NONE;
+ server_cb.setCode(42); // Some error
+ tcp::acceptor acceptor(service.get_io_service(),
+ tcp::endpoint(tcp::v4(), SERVER_PORT));
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ acceptor.async_accept(server_socket, server_cb);
+
+ // Set up client - connect to the server.
+ client_cb.queued() = TCPCallback::OPEN;
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.setCode(43); // Some error
+ EXPECT_FALSE(client.isOpenSynchronous());
+ client.open(&server_endpoint, client_cb);
+
+ // Run the open and the accept callback and check that they ran.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_EQ(TCPCallback::ACCEPT, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+
+ EXPECT_EQ(TCPCallback::OPEN, client_cb.called());
+
+ // On some operating system the async_connect may return EINPROGRESS.
+ // This doesn't necessarily indicate an error. In most cases trying
+ // to asynchronously write and read from the socket would work just
+ // fine.
+ if ((client_cb.getCode()) != 0 && (client_cb.getCode() != EINPROGRESS)) {
+ ADD_FAILURE() << "expected error code of 0 or " << EINPROGRESS
+ << " as a result of async_connect, got " << client_cb.getCode();
+ }
+
+ // Step 2. Get the client to write to the server asynchronously. The
+ // server will loop reading the data synchronously.
+
+ // Write asynchronously to the server.
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::WRITE;
+ client_cb.setCode(143); // Arbitrary number
+ client_cb.length() = 0;
+ client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+
+ // Wait for the client callback to complete. (Must do this first on
+ // Solaris: if we do the synchronous read first, the test hangs.)
+ service.run_one();
+
+ // Synchronously read the data from the server.;
+ serverRead(server_socket, server_cb);
+
+ // Check the client state
+ EXPECT_EQ(TCPCallback::WRITE, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length());
+
+ // ... and check what the server received.
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative());
+ EXPECT_TRUE(equal(OUTBOUND_DATA,
+ (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)),
+ (server_cb.data() + 2)));
+
+ // Step 3. Get the server to write all the data asynchronously and have the
+ // client loop (asynchronously) reading the data. Note that we copy the
+ // data into the server's internal buffer in order to precede it with a two-
+ // byte count field.
+
+ // Have the server write asynchronously to the client.
+ server_cb.called() = TCPCallback::NONE;
+ server_cb.queued() = TCPCallback::WRITE;
+ server_cb.length() = 0;
+ server_cb.cumulative() = 0;
+
+ writeUint16(sizeof(INBOUND_DATA), server_cb.data(), TCPCallback::MIN_SIZE);
+ copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
+ (server_cb.data() + 2));
+ server_socket.async_send(boost::asio::buffer(server_cb.data(),
+ (sizeof(INBOUND_DATA) + 2)),
+ server_cb);
+
+ // Have the client read asynchronously.
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::READ;
+ client_cb.length() = 0;
+ client_cb.cumulative() = 0;
+ client_cb.expected() = 0;
+ client_cb.offset() = 0;
+
+ client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+
+ // Run the callbacks. Several options are possible depending on how ASIO
+ // is implemented and whether the message gets fragmented:
+ //
+ // 1) The send handler may complete immediately, regardless of whether the
+ // data has been read by the client. (This is the most likely.)
+ // 2) The send handler may only run after all the data has been read by
+ // the client. (This could happen if the client's TCP buffers were too
+ // small so the data was not transferred to the "remote" system until the
+ // remote buffer has been emptied one or more times.)
+ // 3) The client handler may be run a number of times to handle the message
+ // fragments and the server handler may run between calls of the client
+ // handler.
+ //
+ // So loop, running one handler at a time until we are certain that all the
+ // handlers have run.
+
+ bool server_complete = false;
+ bool client_complete = false;
+ while (!server_complete || !client_complete) {
+ service.run_one();
+
+ // Has the server run?
+ if (!server_complete) {
+ if (server_cb.called() == server_cb.queued()) {
+
+ // Yes. Check that the send completed successfully and that
+ // all the data that was expected to have been sent was in fact
+ // sent.
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length());
+ server_complete = true;
+ }
+ }
+
+ // Has the client run?
+ if (!client_complete) {
+
+ if (client_cb.called() != client_cb.queued()) {
+ // No. Run the service another time.
+ continue;
+ }
+
+ // Client callback must have run. Check that it ran OK.
+ EXPECT_EQ(TCPCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+
+ // Check if we need to queue another read, copying the data into
+ // the output buffer as we do so.
+ client_complete = client.processReceivedData(client_cb.data(),
+ client_cb.length(),
+ client_cb.cumulative(),
+ client_cb.offset(),
+ client_cb.expected(),
+ client_buffer);
+
+ // If the data is not complete, queue another read.
+ if (! client_complete) {
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::READ;
+ client_cb.length() = 0;
+ client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE ,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+ }
+ }
+ }
+
+ // Both the send and the receive have completed. Check that the received
+ // is what was sent.
+
+ // Check the client state
+ EXPECT_EQ(TCPCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative());
+ EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength());
+
+ // ... and check what the server sent.
+ EXPECT_EQ(TCPCallback::WRITE, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length());
+
+ // ... and that what was sent is what was received.
+ const uint8_t* received = static_cast<const uint8_t*>(client_buffer->getData());
+ EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)),
+ received));
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.close());
+ EXPECT_NO_THROW(server_socket.close());
+}
diff --git a/src/lib/asiolink/tests/tls_acceptor_unittest.cc b/src/lib/asiolink/tests/tls_acceptor_unittest.cc
new file mode 100644
index 0000000..d9d9ccd
--- /dev/null
+++ b/src/lib/asiolink/tests/tls_acceptor_unittest.cc
@@ -0,0 +1,450 @@
+// Copyright (C) 2016-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tls_acceptor.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+#include <list>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace boost::asio;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Simple class representing TCP socket callback.
+class SocketCallback {
+public:
+
+ /// @brief Implements callback for the asynchronous operation on the socket.
+ ///
+ /// This callback merely checks if error has occurred and reports this
+ /// error. It does nothing in case of success.
+ ///
+ /// @param ec Error code.
+ /// @param length Length of received data.
+ void operator()(boost::system::error_code ec, size_t length = 0) {
+ if (ec) {
+ ADD_FAILURE() << "error occurred for a socket with data of length "
+ << length << ": " << ec.message();
+ }
+ }
+
+};
+
+/// @brief Entity which can connect to the TLS server endpoint and close the
+/// connection.
+class TLSClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error.
+ explicit TLSClient(IOService& io_service)
+ : io_service_(io_service.get_io_service()), socket_(io_service_) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TLSClient() {
+ close();
+ }
+
+ /// @brief Connect to the test server address and port.
+ ///
+ /// This method asynchronously connects to the server endpoint and uses the
+ /// connectHandler as a callback function.
+ void connect() {
+ ip::tcp::endpoint
+ endpoint(ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
+ socket_.async_connect(endpoint,
+ std::bind(&TLSClient::connectHandler, this,
+ ph::_1));
+ }
+
+ /// @brief Callback function for connect().
+ ///
+ /// This function stops the IO service upon error.
+ ///
+ /// @param ec Error code.
+ void connectHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ // One would expect that async_connect wouldn't return EINPROGRESS
+ // error code, but simply wait for the connection to get
+ // established before the handler is invoked. It turns out, however,
+ // that on some OSes the connect handler may receive this error code
+ // which doesn't necessarily indicate a problem. Making an attempt
+ // to write and read from this socket will typically succeed. So,
+ // we ignore this error.
+ if (ec.value() != error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ }
+ }
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ ip::tcp::socket socket_;
+
+};
+
+/// @brief Pointer to the TLSClient.
+typedef boost::shared_ptr<TLSClient> TLSClientPtr;
+
+/// @brief A signature of the function implementing callback for the
+/// TLSAcceptor.
+typedef std::function<void(const boost::system::error_code&)> TLSAcceptorCallback;
+
+/// @brief TLSAcceptor using TLSAcceptorCallback.
+typedef TLSAcceptor<TLSAcceptorCallback> TestTLSAcceptor;
+
+/// @brief Implements asynchronous TLS acceptor service.
+///
+/// It creates a new socket into which connection is accepted. The socket
+/// is retained until class instance exists.
+class Acceptor {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ /// @param context Pointer to TLS context.
+ /// @param acceptor Reference to the TLS acceptor on which asyncAccept
+ /// will be called.
+ /// @param callback Callback function for the asyncAccept.
+ explicit Acceptor(IOService& io_service,
+ TlsContextPtr context,
+ TestTLSAcceptor& acceptor,
+ const TLSAcceptorCallback& callback)
+ : socket_(io_service, context), acceptor_(acceptor),
+ callback_(callback) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes socket.
+ ~Acceptor() {
+ socket_.close();
+ }
+
+ /// @brief Asynchronous accept new connection.
+ void accept() {
+ acceptor_.asyncAccept(socket_, callback_);
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+private:
+
+ /// @brief Socket into which connection is accepted.
+ TLSSocket<SocketCallback> socket_;
+
+ /// @brief Reference to the TLSAcceptor on which asyncAccept is called.
+ TestTLSAcceptor& acceptor_;
+
+ /// @brief Instance of the callback used for asyncAccept.
+ TLSAcceptorCallback callback_;
+
+};
+
+/// @brief Pointer to the Acceptor object.
+typedef boost::shared_ptr<Acceptor> AcceptorPtr;
+
+/// @brief Test fixture class for TLSAcceptor.
+///
+/// This class provides means for creating new TLS connections, i.e. simulates
+/// clients connecting to the servers via TLSAcceptor. It is possible to create
+/// multiple simultaneous connections, which are retained by the test fixture
+/// class and closed cleanly when the test fixture is destroyed.
+class TLSAcceptorTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Besides initializing class members it also sets the test timer to guard
+ /// against endlessly running IO service when TLS connections are
+ /// unsuccessful.
+ TLSAcceptorTest()
+ : io_service_(), acceptor_(io_service_),
+ asio_endpoint_(ip::address::from_string(SERVER_ADDRESS),
+ SERVER_PORT),
+ endpoint_(asio_endpoint_), test_timer_(io_service_), connections_(),
+ clients_(), connections_num_(0), aborted_connections_num_(0),
+ max_connections_(1) {
+ test_timer_.setup(std::bind(&TLSAcceptorTest::timeoutHandler, this),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ virtual ~TLSAcceptorTest() {
+ }
+
+ /// @brief Specifies how many new connections are expected before the IO
+ /// service is stopped.
+ ///
+ /// @param max_connections Connections limit.
+ void setMaxConnections(const unsigned int max_connections) {
+ max_connections_ = max_connections;
+ }
+
+ /// @brief Create ASIO endpoint from the provided endpoint by retaining the
+ /// IP address and modifying the port.
+ ///
+ /// This convenience method is useful to create new endpoint from the
+ /// existing endpoint to test reusing IP address for multiple acceptors.
+ /// The returned endpoint has the same IP address but different port.
+ ///
+ /// @param endpoint Source endpoint.
+ ///
+ /// @return New endpoint with the port number increased by 1.
+ ip::tcp::endpoint
+ createSiblingEndpoint(const ip::tcp::endpoint& endpoint) const {
+ ip::tcp::endpoint endpoint_copy(endpoint);
+ endpoint_copy.port(endpoint.port() + 1);
+ return (endpoint_copy);
+ }
+
+ /// @brief Opens TLS acceptor and sets 'reuse address' option.
+ void acceptorOpen() {
+ acceptor_.open(endpoint_);
+ acceptor_.setOption(TestTLSAcceptor::ReuseAddress(true));
+ }
+
+ /// @brief Starts accepting TLS connections.
+ ///
+ /// This method creates new Acceptor instance and calls accept() to start
+ /// accepting new connections. The instance of the Acceptor object is
+ /// retained in the connections_ list.
+ void accept() {
+ TLSAcceptorCallback cb = std::bind(&TLSAcceptorTest::acceptHandler,
+ this, ph::_1);
+ TlsContextPtr ctx(new TlsContext(SERVER));
+ AcceptorPtr conn(new Acceptor(io_service_, ctx, acceptor_, cb));
+ connections_.push_back(conn);
+ connections_.back()->accept();
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TLSClient instance and retains it in the clients_
+ /// list.
+ void connect() {
+ TLSClientPtr client(new TLSClient(io_service_));
+ clients_.push_back(client);
+ clients_.back()->connect();
+ }
+
+ /// @brief Callback function for asynchronous accept calls.
+ ///
+ /// It stops the IO service upon error or when the number of accepted
+ /// connections reaches the max_connections_ value. Otherwise it calls
+ /// accept() to start accepting next connections.
+ ///
+ /// @param ec Error code.
+ void acceptHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ if (ec.value() != error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while accepting connection: "
+ << ec.message();
+ } else {
+ ++aborted_connections_num_;
+ }
+ io_service_.stop();
+ }
+
+ // We have reached the maximum number of connections - end the test.
+ if (++connections_num_ >= max_connections_) {
+ io_service_.stop();
+ return;
+ }
+
+ accept();
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ void timeoutHandler() {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ io_service_.stop();
+ }
+
+ /// @brief IO service.
+ IOService io_service_;
+
+ /// @brief TLSAcceptor under test.
+ TestTLSAcceptor acceptor_;
+
+ /// @brief Server endpoint.
+ ip::tcp::endpoint asio_endpoint_;
+
+ /// @brief asiolink server endpoint (uses asio_endpoint_).
+ TCPEndpoint endpoint_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief List of connections on the server side.
+ std::list<AcceptorPtr> connections_;
+
+ /// @brief List of client connections.
+ std::list<TLSClientPtr> clients_;
+
+ /// @brief Current number of established connections.
+ unsigned int connections_num_;
+
+ /// @brief Current number of aborted connections.
+ unsigned int aborted_connections_num_;
+
+ /// @brief Connections limit.
+ unsigned int max_connections_;
+};
+
+// Test TLSAcceptor::asyncAccept.
+TEST_F(TLSAcceptorTest, asyncAccept) {
+ // Establish up to 10 connections.
+ setMaxConnections(10);
+
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TLS connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Run the IO service until we have accepted 10 connections, an error
+ // or test timeout occurred.
+ io_service_.run();
+
+ // Make sure that all accepted connections have been recorded.
+ EXPECT_EQ(10, connections_num_);
+ EXPECT_EQ(10, connections_.size());
+}
+
+// Check that it is possible to set SO_REUSEADDR flag for the TLSAcceptor.
+TEST_F(TLSAcceptorTest, reuseAddress) {
+ // We need at least two acceptors using common address. Let's create the
+ // second endpoint which has the same address but different port.
+ ip::tcp::endpoint asio_endpoint2(createSiblingEndpoint(asio_endpoint_));
+ TCPEndpoint endpoint2(asio_endpoint2);
+
+ // Create and open two acceptors.
+ TestTLSAcceptor acceptor1(io_service_);
+ TestTLSAcceptor acceptor2(io_service_);
+ ASSERT_NO_THROW(acceptor1.open(endpoint_));
+ ASSERT_NO_THROW(acceptor2.open(endpoint2));
+
+ // Set SO_REUSEADDR socket option so as acceptors can bind to the
+ /// same address.
+ ASSERT_NO_THROW(
+ acceptor1.setOption(TestTLSAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(
+ acceptor2.setOption(TestTLSAcceptor::ReuseAddress(true))
+ );
+ ASSERT_NO_THROW(acceptor1.bind(endpoint_));
+ ASSERT_NO_THROW(acceptor2.bind(endpoint2));
+
+ // Create third acceptor, but don't set the SO_REUSEADDR. It should
+ // refuse to bind.
+ TCPEndpoint endpoint3(createSiblingEndpoint(asio_endpoint2));
+ TestTLSAcceptor acceptor3(io_service_);
+ ASSERT_NO_THROW(acceptor3.open(endpoint3));
+ EXPECT_THROW(acceptor3.bind(endpoint_), boost::system::system_error);
+}
+
+// Test that TLSAcceptor::getProtocol returns IPPROTO_TCP.
+TEST_F(TLSAcceptorTest, getProtocol) {
+ EXPECT_EQ(IPPROTO_TCP, acceptor_.getProtocol());
+}
+
+// Test that TLSAcceptor::getNative returns valid socket descriptor.
+TEST_F(TLSAcceptorTest, getNative) {
+ // Initially the descriptor should be invalid (negative).
+ ASSERT_LT(acceptor_.getNative(), 0);
+ // Now open the socket and make sure the returned descriptor is now valid.
+ ASSERT_NO_THROW(acceptorOpen());
+ EXPECT_GE(acceptor_.getNative(), 0);
+}
+
+// macOS 10.12.3 has a bug which causes the connections to not enter
+// the TIME-WAIT state and they never get closed.
+#if !defined (OS_OSX)
+
+// Test that TLSAcceptor::close works properly.
+TEST_F(TLSAcceptorTest, close) {
+ // Initialize acceptor.
+ acceptorOpen();
+ acceptor_.bind(endpoint_);
+ acceptor_.listen();
+
+ // Start accepting new connections.
+ accept();
+
+ // Create 10 new TLS connections (client side).
+ for (unsigned int i = 0; i < 10; ++i) {
+ connect();
+ }
+
+ // Close the acceptor before connections are accepted.
+ acceptor_.close();
+
+ // Run the IO service.
+ io_service_.run();
+
+ // The connections should have been aborted.
+ EXPECT_EQ(1, connections_num_);
+ EXPECT_EQ(1, aborted_connections_num_);
+ EXPECT_EQ(1, connections_.size());
+}
+
+#endif
+
+}
diff --git a/src/lib/asiolink/tests/tls_socket_unittest.cc b/src/lib/asiolink/tests/tls_socket_unittest.cc
new file mode 100644
index 0000000..c2572c0
--- /dev/null
+++ b/src/lib/asiolink/tests/tls_socket_unittest.cc
@@ -0,0 +1,560 @@
+// Copyright (C) 2011-2021 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/.
+
+/// @brief Test of TLSSocket
+///
+/// Tests the functionality of a TLSSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tls_socket.h>
+#include <asiolink/testutils/test_tls.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cstddef>
+#include <cstdlib>
+#include <errno.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string>
+#include <vector>
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5303;
+
+/// @todo Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O completes.
+/// The arguments to the completion callback are stored for later retrieval.
+class TLSCallback {
+public:
+ /// @brief Operations the server is doing
+ enum Operation {
+ ACCEPT = 0, ///< accept() was issued
+ OPEN = 1, ///< Client connected to server
+ HANDSHAKE = 2, ///< TLS handshake completed
+ READ = 3, ///< Asynchronous read completed
+ WRITE = 4, ///< Asynchronous write completed
+ NONE = 5 ///< "Not set" state
+ };
+
+ /// @brief Minimum size of buffers
+ enum {
+ MIN_SIZE = (64 * 1024 + 2) ///< 64kB + two bytes for a count
+ };
+
+ struct PrivateData {
+ PrivateData() :
+ error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0),
+ name_(""), queued_(NONE), called_(NONE), data_(MIN_SIZE, 0)
+ {}
+
+ boost::system::error_code error_code_; ///< Completion error code
+ size_t length_; ///< Bytes transferred in this I/O
+ size_t cumulative_; ///< Cumulative bytes transferred
+ size_t expected_; ///< Expected amount of data
+ size_t offset_; ///< Where to put data in buffer
+ std::string name_; ///< Which of the objects this is
+ Operation queued_; ///< Queued operation
+ Operation called_; ///< Which callback called
+ std::vector<uint8_t> data_; ///< Receive buffer
+ };
+
+ /// @brief Constructor
+ ///
+ /// Constructs the object. It also creates the data member pointed to by
+ /// a shared pointer. When used as a callback object, this is copied as it
+ /// is passed into the asynchronous function. This means that there are two
+ /// objects and inspecting the one we passed in does not tell us anything.
+ ///
+ /// Therefore we use a boost::shared_ptr. When the object is copied, the
+ /// shared pointer is copied, which leaves both objects pointing to the same
+ /// data.
+ ///
+ /// @param which Which of the two callback objects this is
+ TLSCallback(std::string which) : ptr_(new PrivateData())
+ {
+ ptr_->name_ = which;
+ }
+
+ /// @brief Destructor
+ ///
+ /// No code needed, destroying the shared pointer destroys the private data.
+ virtual ~TLSCallback()
+ {}
+
+ /// @brief Client Callback Function
+ ///
+ /// Called when an asynchronous operation is completed by the client, this
+ /// stores the origin of the operation in the client_called_ data member.
+ ///
+ /// @param ec I/O completion error code passed to callback function.
+ /// @param length Number of bytes transferred
+ void operator()(boost::system::error_code ec = boost::system::error_code(),
+ size_t length = 0)
+ {
+ setCode(ec.value());
+ ptr_->called_ = ptr_->queued_;
+ ptr_->length_ = length;
+ }
+
+ /// @brief Get I/O completion error code
+ int getCode() {
+ return (ptr_->error_code_.value());
+ }
+
+ /// @brief Set I/O completion code
+ ///
+ /// @param code New value of completion code
+ void setCode(int code) {
+ ptr_->error_code_ = boost::system::error_code(code, boost::system::error_code().category());
+ }
+
+ /// @brief Get number of bytes transferred in I/O
+ size_t& length() {
+ return (ptr_->length_);
+ }
+
+ /// @brief Get cumulative number of bytes transferred in I/O
+ size_t& cumulative() {
+ return (ptr_->cumulative_);
+ }
+
+ /// @brief Get expected amount of data
+ size_t& expected() {
+ return (ptr_->expected_);
+ }
+
+ /// @brief Get offset into data
+ size_t& offset() {
+ return (ptr_->offset_);
+ }
+
+ /// @brief Get data member
+ uint8_t* data() {
+ return (&ptr_->data_[0]);
+ }
+
+ /// @brief Get flag to say what was queued
+ Operation& queued() {
+ return (ptr_->queued_);
+ }
+
+ /// @brief Get flag to say when callback was called
+ Operation& called() {
+ return (ptr_->called_);
+ }
+
+ /// @brief Return instance of callback name
+ std::string& name() {
+ return (ptr_->name_);
+ }
+
+private:
+ boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
+};
+
+
+// Read Server Data
+//
+// Called in the part of the test that has the client send a message to the
+// server, this loops until all the data has been read (synchronously) by the
+// server.
+//
+// "All the data read" means that the server has received a message that is
+// preceded by a two-byte count field and that the total amount of data received
+// from the remote end is equal to the value in the count field plus two bytes
+// for the count field itself.
+//
+// @param stream Stream on which the server is reading data
+// @param server_cb Structure in which server data is held.
+void
+serverRead(TlsStreamImpl& stream, TLSCallback& server_cb) {
+
+ // As we may need to read multiple times, keep a count of the cumulative
+ // amount of data read and do successive reads into the appropriate part
+ // of the buffer.
+ //
+ // Note that there are no checks for buffer overflow - this is a test
+ // program and we have sized the buffer to be large enough for the test.
+ server_cb.cumulative() = 0;
+
+ bool complete = false;
+ while (!complete) {
+
+ // Read block of data and update cumulative amount of data received.
+ server_cb.length() = stream.read_some(
+ boost::asio::buffer(server_cb.data() + server_cb.cumulative(),
+ TLSCallback::MIN_SIZE - server_cb.cumulative()));
+ server_cb.cumulative() += server_cb.length();
+
+ // If we have read at least two bytes, we can work out how much we
+ // should be reading.
+ if (server_cb.cumulative() >= 2) {
+ server_cb.expected() = readUint16(server_cb.data(), server_cb.length());
+ if ((server_cb.expected() + 2) == server_cb.cumulative()) {
+
+ // Amount of data read from stream equals the size of the
+ // message (as indicated in the first two bytes of the message)
+ // plus the size of the count field. Therefore we have received
+ // all the data.
+ complete = true;
+ }
+ }
+ }
+}
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TLSSocket, processReceivedData) {
+ // Amount of "real" data in the buffer
+ const uint16_t PACKET_SIZE = 16382;
+
+ // Used to instantiate socket
+ IOService service;
+ TlsContextPtr context(new TlsContext(CLIENT));
+ // Socket under test
+ TLSSocket<TLSCallback> test(service, context);
+ // Buffer to check
+ uint8_t inbuff[PACKET_SIZE + 2];
+ // Where data is put
+ OutputBufferPtr outbuff(new OutputBuffer(16));
+ // Expected amount of data
+ size_t expected;
+ // Where to put next data
+ size_t offset;
+ // Cumulative data received
+ size_t cumulative;
+
+ // Set some dummy values in the buffer to check
+ for (size_t i = 0; i < sizeof(inbuff); ++i) {
+ inbuff[i] = i % 256;
+ }
+
+ // Check that the method will handle various receive sizes.
+ writeUint16(PACKET_SIZE, inbuff, sizeof(inbuff));
+
+ cumulative = 0;
+ offset = 0;
+ expected = 0;
+ outbuff->clear();
+ bool complete = test.processReceivedData(inbuff, 1, cumulative, offset,
+ expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(1, cumulative);
+ EXPECT_EQ(1, offset);
+ EXPECT_EQ(0, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Now pretend that we've received one more byte.
+ complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected,
+ outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Add another two bytes. However, this time note that we have to offset
+ // in the input buffer because it is expected that the next chunk of data
+ // from the connection will be read into the start of the buffer.
+ complete = test.processReceivedData(inbuff + cumulative, 2, cumulative,
+ offset, expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(4, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(2, outbuff->getLength());
+
+ const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+
+ // And add the remaining data. Remember that "inbuff" is "PACKET_SIZE + 2"
+ // long.
+ complete = test.processReceivedData(inbuff + cumulative,
+ PACKET_SIZE + 2 - cumulative,
+ cumulative, offset, expected, outbuff);
+ EXPECT_TRUE(complete);
+ EXPECT_EQ(PACKET_SIZE + 2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(PACKET_SIZE, outbuff->getLength());
+ dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+}
+
+/// @todo Need to add a test to check the cancel() method
+
+// Tests the operation of a TLSSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(TLSSocket, sequenceTest) {
+
+ // Common objects.
+ // Service object for async control
+ IOService service;
+
+ // The client - the TLSSocket being tested
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ // Socket under test
+ TLSSocket<TLSCallback> client(service, client_ctx);
+ // Async I/O callback function
+ TLSCallback client_cb("Client");
+ // Where client receives message from
+ TCPEndpoint client_remote_endpoint;
+ // Received data is put here
+ OutputBufferPtr client_buffer(new OutputBuffer(128));
+ // The server - with which the client communicates.
+ // Address of target server
+ IOAddress server_address(SERVER_ADDRESS);
+ // Server callback
+ TLSCallback server_cb("Server");
+ // Endpoint describing server
+ TCPEndpoint server_endpoint(server_address, SERVER_PORT);
+ // Address where server received message from
+ TCPEndpoint server_remote_endpoint;
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ // Stream used for server.
+ TlsStreamImpl server(service.get_io_service(), server_ctx->getContext());
+
+ // Step 1. Create the connection between the client and the server. Set
+ // up the server to accept incoming connections and have the client open
+ // a channel to it.
+
+ // Set up server - open socket and queue an accept.
+ server_cb.queued() = TLSCallback::ACCEPT;
+ server_cb.called() = TLSCallback::NONE;
+ server_cb.setCode(42); // Some error
+ tcp::acceptor acceptor(service.get_io_service(),
+ tcp::endpoint(tcp::v4(), SERVER_PORT));
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ acceptor.async_accept(server.lowest_layer(), server_cb);
+
+ // Set up client - connect to the server.
+ client_cb.queued() = TLSCallback::OPEN;
+ client_cb.called() = TLSCallback::NONE;
+ client_cb.setCode(43); // Some error
+ EXPECT_FALSE(client.isOpenSynchronous());
+ client.open(&server_endpoint, client_cb);
+
+ // Run the open and the accept callback and check that they ran.
+ while ((server_cb.called() == TLSCallback::NONE) ||
+ (client_cb.called() == TLSCallback::NONE)) {
+ service.run_one();
+ }
+ EXPECT_EQ(TLSCallback::ACCEPT, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+
+ EXPECT_EQ(TLSCallback::OPEN, client_cb.called());
+
+ // On some operating system the async_connect may return EINPROGRESS.
+ // This doesn't necessarily indicate an error. In most cases trying
+ // to asynchronously write and read from the socket would work just
+ // fine.
+ if ((client_cb.getCode()) != 0 && (client_cb.getCode() != EINPROGRESS)) {
+ ADD_FAILURE() << "expected error code of 0 or " << EINPROGRESS
+ << " as a result of async_connect, got " << client_cb.getCode();
+ }
+
+ // Perform handshake.
+ client_cb.queued() = TLSCallback::HANDSHAKE;
+ client_cb.called() = TLSCallback::NONE;
+ client_cb.setCode(43); // Some error
+ client.handshake(client_cb);
+
+ server_cb.queued() = TLSCallback::HANDSHAKE;
+ server_cb.called() = TLSCallback::NONE;
+ server_cb.setCode(42); // Some error
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+
+ while ((server_cb.called() == TLSCallback::NONE) ||
+ (client_cb.called() == TLSCallback::NONE)) {
+ service.run_one();
+ }
+ EXPECT_EQ(TLSCallback::HANDSHAKE, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+
+ EXPECT_EQ(TLSCallback::HANDSHAKE, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+
+ // Step 2. Get the client to write to the server asynchronously. The
+ // server will loop reading the data synchronously.
+
+ // Write asynchronously to the server.
+ client_cb.called() = TLSCallback::NONE;
+ client_cb.queued() = TLSCallback::WRITE;
+ client_cb.setCode(143); // Arbitrary number
+ client_cb.length() = 0;
+ client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+
+ // Wait for the client callback to complete. (Must do this first on
+ // Solaris: if we do the synchronous read first, the test hangs.)
+ while (client_cb.called() == TLSCallback::NONE) {
+ service.run_one();
+ }
+
+ // Synchronously read the data from the server.;
+ serverRead(server, server_cb);
+
+ // Check the client state
+ EXPECT_EQ(TLSCallback::WRITE, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length());
+
+ // ... and check what the server received.
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative());
+ EXPECT_TRUE(equal(OUTBOUND_DATA,
+ (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)),
+ (server_cb.data() + 2)));
+
+ // Step 3. Get the server to write all the data asynchronously and have the
+ // client loop (asynchronously) reading the data. Note that we copy the
+ // data into the server's internal buffer in order to precede it with a two-
+ // byte count field.
+
+ // Have the server write asynchronously to the client.
+ server_cb.called() = TLSCallback::NONE;
+ server_cb.queued() = TLSCallback::WRITE;
+ server_cb.length() = 0;
+ server_cb.cumulative() = 0;
+
+ writeUint16(sizeof(INBOUND_DATA), server_cb.data(), TLSCallback::MIN_SIZE);
+ copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
+ (server_cb.data() + 2));
+ boost::asio::async_write(server,
+ boost::asio::buffer(server_cb.data(),
+ (sizeof(INBOUND_DATA) + 2)),
+ server_cb);
+
+ // Have the client read asynchronously.
+ client_cb.called() = TLSCallback::NONE;
+ client_cb.queued() = TLSCallback::READ;
+ client_cb.length() = 0;
+ client_cb.cumulative() = 0;
+ client_cb.expected() = 0;
+ client_cb.offset() = 0;
+
+ client.asyncReceive(client_cb.data(), TLSCallback::MIN_SIZE,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+
+ // Run the callbacks. Several options are possible depending on how ASIO
+ // is implemented and whether the message gets fragmented:
+ //
+ // 1) The send handler may complete immediately, regardless of whether the
+ // data has been read by the client. (This is the most likely.)
+ // 2) The send handler may only run after all the data has been read by
+ // the client. (This could happen if the client's TCP buffers were too
+ // small so the data was not transferred to the "remote" system until the
+ // remote buffer has been emptied one or more times.)
+ // 3) The client handler may be run a number of times to handle the message
+ // fragments and the server handler may run between calls of the client
+ // handler.
+ //
+ // So loop, running one handler at a time until we are certain that all the
+ // handlers have run.
+
+ bool server_complete = false;
+ bool client_complete = false;
+ while (!server_complete || !client_complete) {
+ service.run_one();
+
+ // Has the server run?
+ if (!server_complete) {
+ if (server_cb.called() != TLSCallback::NONE) {
+
+ // Yes. Check that the send completed successfully and that
+ // all the data that was expected to have been sent was in fact
+ // sent.
+ EXPECT_EQ(TLSCallback::WRITE, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length());
+ server_complete = true;
+ }
+ }
+
+ // Has the client run?
+ if (!client_complete) {
+
+ if (client_cb.called() == TLSCallback::NONE) {
+ // No. Run the service another time.
+ continue;
+ }
+
+ // Client callback must have run. Check that it ran OK.
+ EXPECT_EQ(TLSCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+
+ // Check if we need to queue another read, copying the data into
+ // the output buffer as we do so.
+ client_complete = client.processReceivedData(client_cb.data(),
+ client_cb.length(),
+ client_cb.cumulative(),
+ client_cb.offset(),
+ client_cb.expected(),
+ client_buffer);
+
+ // If the data is not complete, queue another read.
+ if (!client_complete) {
+ client_cb.called() = TLSCallback::NONE;
+ client_cb.queued() = TLSCallback::READ;
+ client_cb.length() = 0;
+ client.asyncReceive(client_cb.data(), TLSCallback::MIN_SIZE ,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+ }
+ }
+ }
+
+ // Both the send and the receive have completed. Check that the received
+ // is what was sent.
+
+ // Check the client state
+ EXPECT_EQ(TLSCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative());
+ EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength());
+
+ // ... and check what the server sent.
+ EXPECT_EQ(TLSCallback::WRITE, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length());
+
+ // ... and that what was sent is what was received.
+ const uint8_t* received = static_cast<const uint8_t*>(client_buffer->getData());
+ EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)),
+ received));
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
diff --git a/src/lib/asiolink/tests/tls_unittest.cc b/src/lib/asiolink/tests/tls_unittest.cc
new file mode 100644
index 0000000..35fc8c4
--- /dev/null
+++ b/src/lib/asiolink/tests/tls_unittest.cc
@@ -0,0 +1,2695 @@
+// Copyright (C) 2021-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/crypto_tls.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/testutils/test_tls.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <list>
+#include <string>
+#include <vector>
+
+#ifdef WITH_OPENSSL
+#include <openssl/opensslv.h>
+#endif
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::cryptolink;
+using namespace std;
+
+namespace { // anonymous namespace.
+
+/// @brief Local server address used for testing.
+const char SERVER_ADDRESS[] = "127.0.0.1";
+
+/// @brief Local server port used for testing.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Name of the environment variable controlling the display
+/// (default off) of TLS error messages.
+const char KEA_TLS_CHECK_VERBOSE[] = "KEA_TLS_CHECK_VERBOSE";
+
+/// @brief Test TLS context class exposing protected methods.
+class TestTlsContext : public TlsContext {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param role The TLS role client or server.
+ explicit TestTlsContext(TlsRole role) : TlsContext(role) { }
+
+ /// @brief Destructor.
+ virtual ~TestTlsContext() { }
+
+ /// @brief Make protected methods visible in tests.
+ using TlsContext::setCertRequired;
+ using TlsContext::loadCaFile;
+ using TlsContext::loadCaPath;
+ using TlsContext::loadCertFile;
+ using TlsContext::loadKeyFile;
+};
+
+/// @brief Class of test callbacks.
+class TestCallback {
+public:
+ /// @brief State part.
+ class State {
+ public:
+ /// @brief Constructor.
+ State() : called_(false), error_code_() {
+ }
+
+ /// @brief Destructor.
+ virtual ~State() {
+ }
+
+ /// @brief Called flag.
+ ///
+ /// Initialized to false, set to true when the callback is called.
+ bool called_;
+
+ /// @brief Last error code.
+ boost::system::error_code error_code_;
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Used to shared pointer to state to allow the callback object to
+ /// be copied keeping the state member values.
+ TestCallback()
+ : state_(new State()), tcpp_(0) {
+ }
+
+ /// @brief Close on error constructor.
+ ///
+ /// An overload which takes the stream to close on error.
+ ///
+ /// @param tcpp Pointer to the stream to close on error.
+ TestCallback(TlsStream<TestCallback>::lowest_layer_type* tcpp)
+ : state_(new State()), tcpp_(tcpp) {
+ }
+
+ /// @brief Destructor.
+ virtual ~TestCallback() {
+ }
+
+ /// @brief Callback function (one argument).
+ ///
+ /// @parame ec Boost completion code.
+ void operator()(const boost::system::error_code& ec) {
+ state_->called_ = true;
+ state_->error_code_ = ec;
+ if (ec && tcpp_) {
+ tcpp_->close();
+ }
+ }
+
+ /// @brief Callback function (two arguments).
+ ///
+ /// @parame ec Boost completion code.
+ void operator()(const boost::system::error_code& ec, size_t) {
+ state_->called_ = true;
+ state_->error_code_ = ec;
+ if (ec && tcpp_) {
+ tcpp_->close();
+ }
+ }
+
+ /// @brief Get called value.
+ inline bool getCalled() const {
+ return (state_->called_);
+ }
+
+ /// @brief Get error code.
+ inline const boost::system::error_code& getCode() const {
+ return (state_->error_code_);
+ }
+
+protected:
+ /// @brief Pointer to state.
+ boost::shared_ptr<State> state_;
+
+ /// @brief Pointer to the stream to close on error.
+ TlsStream<TestCallback>::lowest_layer_type* tcpp_;
+};
+
+/// @brief The type of a test to be run.
+typedef function<void()> Test;
+
+/// @brief Class of an expected behavior.
+///
+/// Some TLS tests can not use the standard GTEST macros because they
+/// show different behaviors depending on the crypto backend and the
+/// boost library versions. Worse in some cases the behavior can not
+/// be deduced from them so #ifdef's do not work...
+///
+/// Until this is adopted / widespread the policy is to use these flexible
+/// expected behavior tests ONLY when needed.
+class Expected {
+private:
+ /// Constructor.
+ ///
+ /// @param throwing True when an exception should be thrown.
+ /// @param timeout True when a timeout should occur.
+ /// @param no_error True when no error should be returned.
+ /// @param message Expected message.
+ Expected(bool throwing, bool timeout, bool no_error,
+ const string& message)
+ : throwing_(throwing), timeout_(timeout), no_error_(no_error),
+ message_(message) {
+ }
+
+ /// @brief The throwing flag.
+ bool throwing_;
+
+ /// @brief The timeout flag.
+ bool timeout_;
+
+ /// @brief The no error flag.
+ bool no_error_;
+
+ /// @brief The expected error message.
+ string message_;
+
+public:
+ /// @brief Create an expected throwing exception behavior.
+ ///
+ /// @param message Expected message.
+ static Expected createThrow(const string& message) {
+ return (Expected(true, false, false, message));
+ }
+
+ /// @brief Create an expected timeout behavior.
+ static Expected createTimeout() {
+ return (Expected(false, true, false, ""));
+ }
+
+ /// @brief Create an expected no error behavior.
+ static Expected createNoError() {
+ return (Expected(false, false, true, ""));
+ }
+
+ /// @brief Create an expected error message behavior.
+ ///
+ /// @param message Expected message.
+ static Expected createError(const string& message) {
+ return (Expected(false, false, false, message));
+ }
+
+ /// @brief Get the throwing flag.
+ ///
+ /// @return The throwing flag.
+ bool getThrow() const {
+ return (throwing_);
+ }
+
+ /// @brief Get the timeout flag.
+ ///
+ /// @return The timeout flag.
+ bool getTimeout() const {
+ return (timeout_);
+ }
+
+ /// @brief Get the no error flag.
+ ///
+ /// @return The no error flag.
+ bool getNoError() const {
+ return (no_error_);
+ }
+
+ /// @brief Get the expected error message.
+ ///
+ /// @return The expected error message.
+ const string& getMessage() const {
+ return (message_);
+ }
+};
+
+/// @brief Class of expected behaviors.
+class Expecteds {
+private:
+ /// @brief List of expected behaviors.
+ list<Expected> list_;
+
+ /// @brief The error message for the verbose mode.
+ string errmsg_;
+
+public:
+ /// Constructor.
+ ///
+ /// @return An empty expected behavior list.
+ Expecteds() : list_(), errmsg_("") {
+ }
+
+ /// @brief Clear the list.
+ void clear() {
+ list_.clear();
+ errmsg_.clear();
+ }
+
+ /// @brief Add an expected throwing exception behavior.
+ ///
+ /// @param message Expected message.
+ void addThrow(const string& message) {
+ list_.push_back(Expected::createThrow(message));
+ }
+
+ /// @brief Add an expected timeout behavior.
+ void addTimeout() {
+ list_.push_back(Expected::createTimeout());
+ }
+
+ /// @brief Add an expected no error behavior.
+ void addNoError() {
+ list_.push_back(Expected::createNoError());
+ }
+
+ /// @brief Add an expected error message behavior.
+ ///
+ /// @param message Expected message.
+ void addError(const string& message) {
+ list_.push_back(Expected::createError(message));
+ }
+
+ /// @brief Display error messages.
+ ///
+ /// @return True if error messages are displayed.
+ static bool displayErrMsg() {
+ return (getenv(KEA_TLS_CHECK_VERBOSE));
+ }
+
+ /// @brief Has an error message.
+ ///
+ /// @return True when there is a cached error message.
+ bool hasErrMsg() const {
+ return (!errmsg_.empty());
+ }
+
+ /// @brief Get error message.
+ ///
+ /// @return The cached error message.
+ const string& getErrMsg() const {
+ return (errmsg_);
+ }
+
+ /// @brief Run a test which can throw.
+ ///
+ /// @param test The test to run.
+ void runCanThrow(const Test& test) {
+ // Check consistency.
+ for (auto const& exp : list_) {
+ if (!exp.getThrow() && !exp.getNoError()) {
+ ADD_FAILURE() << "inconsistent runCanThrow settings";
+ }
+ }
+
+ // Collect the test behavior.
+ bool thrown = false;
+ try {
+ test();
+ } catch (const LibraryError& ex) {
+ thrown = true;
+ errmsg_ = ex.what();
+ } catch (const isc::BadValue& ex) {
+ thrown = true;
+ errmsg_ = ex.what();
+ } catch (const exception& ex) {
+ thrown = true;
+ errmsg_ = ex.what();
+ ADD_FAILURE() << "expect only LibraryError or BadValue exception";
+ }
+
+ // Check the no error case.
+ if (!thrown) {
+ for (auto const& exp : list_) {
+ if (exp.getNoError()) {
+ // No error was expected: good.
+ return;
+ }
+ }
+ // No error was not expected: bad.
+ ADD_FAILURE() << "no exception?";
+ return;
+ }
+
+ // Check the thrown message.
+ for (auto const& exp : list_) {
+ if (!exp.getThrow()) {
+ continue;
+ }
+ if (errmsg_ == exp.getMessage()) {
+ // Got an expected message: good.
+ return;
+ }
+ }
+ // The message was not expected: bad.
+ ADD_FAILURE() << "exception with unknown '" << errmsg_ << "'";
+ }
+
+ /// @brief Check the result of an asynchronous operation.
+ ///
+ /// @param party The name of the party.
+ /// @param callback The test callback of the an asynchronous.
+ void checkAsync(const string& party, const TestCallback& callback) {
+ // Check timeout i.e. the callback was not called.
+ if (!callback.getCalled()) {
+ bool expected = false;
+ for (auto const& exp : list_) {
+ if (exp.getTimeout()) {
+ expected = true;
+ break;
+ }
+ }
+ if (!expected) {
+ ADD_FAILURE() << "unexpected timeout";
+ }
+ }
+
+ // Check the no error case.
+ const boost::system::error_code& ec = callback.getCode();
+ if (!ec) {
+ for (auto const& exp : list_) {
+ if (exp.getTimeout() || exp.getNoError()) {
+ // Expected timeout or no error: good.
+ return;
+ }
+ }
+ // Should have failed but did not: bad.
+ ADD_FAILURE() << party << " did not failed as expected";
+ return;
+ }
+
+ // Got an error but was this one expected?
+ errmsg_ = ec.message();
+ for (auto const& exp : list_) {
+ if (exp.getTimeout() || exp.getNoError()) {
+ continue;
+ }
+ if (errmsg_ == exp.getMessage()) {
+ // This error message was expected: good.
+ return;
+ }
+ }
+ ADD_FAILURE() << party << " got unexpected error '" << errmsg_ << "'";
+ }
+
+};
+
+////////////////////////////////////////////////////////////////////////
+// TlsContext tests //
+////////////////////////////////////////////////////////////////////////
+
+// Test if we can get a client context.
+TEST(TLSTest, clientContext) {
+ TlsContextPtr ctx;
+ EXPECT_NO_THROW(ctx.reset(new TlsContext(TlsRole::CLIENT)));
+}
+
+// Test if we can get a server context.
+TEST(TLSTest, serverContext) {
+ TlsContextPtr ctx;
+ EXPECT_NO_THROW(ctx.reset(new TlsContext(TlsRole::SERVER)));
+}
+
+// Test if the cert required flag is handled as expected.
+TEST(TLSTest, certRequired) {
+ auto check = [] (TlsContext& ctx) -> bool {
+#ifdef WITH_BOTAN
+ return (ctx.getCertRequired());
+#else // WITH_OPENSSL
+ ::SSL_CTX* ssl_ctx = ctx.getNativeContext();
+ if (!ssl_ctx) {
+ ADD_FAILURE() << "null SSL_CTX";
+ return (false);
+ }
+ int mode = SSL_CTX_get_verify_mode(ssl_ctx);
+ switch (mode) {
+ case SSL_VERIFY_NONE:
+ return (false);
+ case (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT):
+ return (true);
+ default:
+ ADD_FAILURE() << "unknown ssl_verify_mode: " << mode;
+ return (false);
+ }
+#endif
+ };
+
+ TestTlsContext ctx(TlsRole::SERVER);
+ EXPECT_TRUE(ctx.getCertRequired());
+ EXPECT_TRUE(check(ctx));
+ ASSERT_NO_THROW(ctx.setCertRequired(false));
+ EXPECT_FALSE(ctx.getCertRequired());
+ EXPECT_FALSE(check(ctx));
+ ASSERT_NO_THROW(ctx.setCertRequired(true));
+ EXPECT_TRUE(ctx.getCertRequired());
+ EXPECT_TRUE(check(ctx));
+
+ // Client role is different so test it too.
+ TestTlsContext cctx(TlsRole::CLIENT);
+ EXPECT_TRUE(cctx.getCertRequired());
+ EXPECT_TRUE(check(cctx));
+ ASSERT_NO_THROW(cctx.setCertRequired(true));
+ EXPECT_TRUE(cctx.getCertRequired());
+ EXPECT_TRUE(check(cctx));
+ EXPECT_THROW(cctx.setCertRequired(false), isc::BadValue);
+}
+
+// Test if the certificate authority can be loaded.
+TEST(TLSTest, loadCAFile) {
+ string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ EXPECT_NO_THROW(ctx.loadCaFile(ca));
+}
+
+// Test that no certificate authority gives an error.
+TEST(TLSTest, loadNoCAFile) {
+ Expecteds exps;
+ // Botan error.
+ exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file");
+ // OpenSSL errors.
+ exps.addThrow("No such file or directory");
+ exps.addThrow("No such file or directory (system library)");
+ exps.addThrow("No such file or directory (system library, fopen)");
+ exps.runCanThrow([] {
+ string ca("/no-such-file");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ ctx.loadCaFile(ca);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+#ifdef WITH_BOTAN
+// Test that Botan requires a real CA certificate so fails with
+// trusted self-signed client.
+/// @note: convert to GTEST when gtest_utils.h will be moved.
+TEST(TLSTest, loadTrustedSelfCAFile) {
+ Expecteds exps;
+ // Botan error.
+ string botan_error = "Flatfile_Certificate_Store received non CA cert ";
+ botan_error += "C=\"US\",X520.State=\"Some-State\",O=\"ISC Inc.\",";
+ botan_error += "CN=\"kea-self\"";
+ exps.addThrow(botan_error);
+ exps.runCanThrow([] {
+ string ca(string(TEST_CA_DIR) + "/kea-self.crt");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ ctx.loadCaFile(ca);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+#endif // WITH_BOTAN
+
+// Test that a directory can be loaded.
+TEST(TLSTest, loadCAPath) {
+ string ca(TEST_CA_DIR);
+ TestTlsContext ctx(TlsRole::CLIENT);
+ EXPECT_NO_THROW(ctx.loadCaPath(ca));
+}
+
+// Test that a certificate is wanted.
+TEST(TLSTest, loadKeyCA) {
+ Expecteds exps;
+ // Botan error.
+ exps.addThrow("Flatfile_Certificate_Store::Flatfile_Certificate_Store cert file is empty");
+ // LibreSSL or old OpenSSL does not check.
+ exps.addNoError();
+ // Recent OpenSSL errors.
+ exps.addThrow("no certificate or crl found");
+ exps.addThrow("no certificate or crl found (x509 certificate routines)");
+ exps.addThrow("no certificate or crl found (x509 certificate routines, CRYPTO_internal)");
+ exps.addThrow("no certificate or crl found (x509 certificate routines, X509_load_cert_crl_file)");
+ exps.runCanThrow([] {
+ string ca(string(TEST_CA_DIR) + "/kea-ca.key");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ ctx.loadCaFile(ca);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+// Test if the end entity certificate can be loaded.
+TEST(TLSTest, loadCertFile) {
+ string cert(string(TEST_CA_DIR) + "/kea-client.crt");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ EXPECT_NO_THROW(ctx.loadCertFile(cert));
+}
+
+// Test that no end entity certificate gives an error.
+TEST(TLSTest, loadNoCertFile) {
+ Expecteds exps;
+ // Botan error.
+ exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file");
+ // OpenSSL errors.
+ exps.addThrow("No such file or directory");
+ exps.addThrow("No such file or directory (system library)");
+ exps.addThrow("No such file or directory (system library, fopen)");
+ exps.runCanThrow([] {
+ string cert("/no-such-file");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ ctx.loadCertFile(cert);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+// Test that a certificate is wanted.
+TEST(TLSTest, loadCsrCertFile) {
+ Expecteds exps;
+ // Botan error.
+ exps.addThrow("Expected a certificate, got 'CERTIFICATE REQUEST'");
+ // OpenSSL errors.
+ exps.addThrow("no start line");
+ exps.addThrow("no start line (PEM routines)");
+ exps.addThrow("no start line (PEM routines, get_name)");
+ exps.addThrow("no start line (PEM routines, CRYPTO_internal)");
+ exps.runCanThrow([] {
+ string cert(string(TEST_CA_DIR) + "/kea-client.csr");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ ctx.loadCertFile(cert);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+// Test if the private key can be loaded.
+TEST(TLSTest, loadKeyFile) {
+ string key(string(TEST_CA_DIR) + "/kea-client.key");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ EXPECT_NO_THROW(ctx.loadKeyFile(key));
+}
+
+// Test that no private key gives an error.
+TEST(TLSTest, loadNoKeyFile) {
+ Expecteds exps;
+ // Botan error.
+ exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file");
+ // OpenSSL errors.
+ exps.addThrow("No such file or directory");
+ exps.addThrow("No such file or directory (system library)");
+ exps.addThrow("No such file or directory (system library, fopen)");
+ // Another possible error.
+ exps.addThrow("PEM lib");
+ exps.runCanThrow([] {
+ string key("/no-such-file");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ ctx.loadKeyFile(key);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+// Test that a private key is wanted.
+TEST(TLSTest, loadCertKeyFile) {
+ Expecteds exps;
+ // Botan error.
+ string botan_error = "PKCS #8 private key decoding failed with PKCS #8: ";
+ botan_error += "Unknown PEM label CERTIFICATE";
+ exps.addThrow(botan_error);
+ // OpenSSL errors.
+ exps.addThrow("no start line");
+ exps.addThrow("no start line (PEM routines)");
+ exps.addThrow("no start line (PEM routines, get_name)");
+ exps.addThrow("no start line (PEM routines, CRYPTO_internal)");
+ exps.addThrow("PEM lib");
+ exps.addThrow("PEM lib (SSL routines)");
+ exps.addThrow("unsupported");
+ exps.addThrow("unsupported (DECODER routines)");
+ // Another possible error.
+ exps.addThrow("No such file or directory");
+ exps.runCanThrow([] {
+ string key(string(TEST_CA_DIR) + "/kea-client.crt");
+ TestTlsContext ctx(TlsRole::CLIENT);
+ ctx.loadKeyFile(key);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+// Test that the certificate and private key must match.
+TEST(TLSTest, loadMismatch) {
+ Expecteds exps;
+ exps.addNoError();
+ exps.runCanThrow([] {
+ string cert(string(TEST_CA_DIR) + "/kea-server.crt");
+ TestTlsContext ctx(TlsRole::SERVER);
+ ctx.loadCertFile(cert);
+ });
+ // Keep no error for at least Botan.
+ // OpenSSL error.
+ exps.addThrow("key values mismatch");
+ exps.runCanThrow([] {
+ string key(string(TEST_CA_DIR) + "/kea-client.key");
+ TestTlsContext ctx(TlsRole::SERVER);
+ // In fact OpenSSL checks only RSA key values...
+ // The explicit check function is SSL_CTX_check_private_key.
+ ctx.loadKeyFile(key);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+// Test the configure class method.
+TEST(TLSTest, configure) {
+ TlsContextPtr ctx;
+ string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+ string cert(string(TEST_CA_DIR) + "/kea-client.crt");
+ string key(string(TEST_CA_DIR) + "/kea-client.key");
+ EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::CLIENT,
+ ca, cert, key, true));
+ ASSERT_TRUE(ctx);
+ EXPECT_EQ(TlsRole::CLIENT, ctx->getRole());
+ EXPECT_TRUE(ctx->getCertRequired());
+
+ // Retry using the directory and the server.
+ ctx.reset();
+ ca = TEST_CA_DIR;
+ cert = string(TEST_CA_DIR) + "/kea-server.crt";
+ key = string(TEST_CA_DIR) + "/kea-server.key";
+ EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::SERVER,
+ ca, cert, key, false));
+ ASSERT_TRUE(ctx);
+ EXPECT_EQ(TlsRole::SERVER, ctx->getRole());
+ EXPECT_FALSE(ctx->getCertRequired());
+}
+
+// Test the configure class method error case.
+TEST(TLSTest, configureError) {
+ // The error case.
+ Expecteds exps;
+ // Common part of the error message.
+ string common_error = "load of cert file '/no-such-file' failed: ";
+ // Botan error.
+ string botan_error = "I/O error: DataSource: Failure opening file /no-such-file";
+ exps.addThrow(common_error + botan_error);
+ // OpenSSL errors.
+ string openssl_error = "No such file or directory";
+ exps.addThrow(common_error + openssl_error);
+ exps.addThrow(common_error + "No such file or directory (system library)");
+ exps.addThrow(common_error + "No such file or directory (system library, fopen)");
+ exps.runCanThrow([] {
+ TlsContextPtr ctx1;
+ string ca(string(TEST_CA_DIR) + "/kea-ca.crt");
+ string cert = "/no-such-file";
+ string key = string(TEST_CA_DIR) + "/kea-client.key";
+ TlsContext::configure(ctx1, TlsRole::CLIENT,
+ ca, cert, key, true);
+ // The context is reset on errors.
+ EXPECT_FALSE(ctx1);
+ });
+ if (Expecteds::displayErrMsg()) {
+ std::cout << exps.getErrMsg() << "\n";
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Basic handshake failures //
+////////////////////////////////////////////////////////////////////////
+
+// Test if we can get a stream.
+TEST(TLSTest, stream) {
+ IOService service;
+ TlsContextPtr ctx(new TlsContext(TlsRole::CLIENT));
+ boost::scoped_ptr<TlsStream<TestCallback> > st;
+ EXPECT_NO_THROW(st.reset(new TlsStream<TestCallback>(service, ctx)));
+}
+
+// Test what happens when handshake is forgotten.
+TEST(TLSTest, noHandshake) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer1(service);
+ bool timeout = false;
+ timer1.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Send on the client.
+ char send_buf[] = "some text...";
+ TestCallback send_cb;
+ async_write(client, boost::asio::buffer(send_buf), send_cb);
+ while (!timeout && !send_cb.getCalled()) {
+ service.run_one();
+ }
+ timer1.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("InvalidObjectState");
+ // OpenSSL errors.
+ exps.addError("uninitialized");
+ exps.addError("uninitialized (SSL routines)");
+ exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)");
+ exps.addError("uninitialized (SSL routines, ssl_write_internal)");
+ exps.checkAsync("send", send_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "send: " << exps.getErrMsg() << "\n";
+ }
+
+ // Setup a second timeout.
+ IntervalTimer timer2(service);
+ timeout = false;
+ timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Receive on the server.
+ vector<char> receive_buf(64);
+ TestCallback receive_cb;
+ server.async_read_some(boost::asio::buffer(receive_buf), receive_cb);
+ while (!timeout && !receive_cb.getCalled()) {
+ service.run_one();
+ }
+ timer2.cancel();
+
+ exps.clear();
+ // On Botan and some OpenSSL the receive party hangs.
+ exps.addTimeout();
+ // OpenSSL errors.
+ exps.addError("uninitialized");
+ exps.addError("uninitialized (SSL routines)");
+ exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)");
+ exps.addError("uninitialized (SSL routines, ssl_read_internal)");
+ exps.checkAsync("receive", receive_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "receive timeout\n";
+ } else {
+ std::cout << "receive: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the server was not configured.
+TEST(TLSTest, serverNotConfigured) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+ // Skip config.
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.handshake(server_cb);
+ TestCallback client_cb;
+ client.handshake(client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("handshake_failure");
+ // Old LibreSSL error.
+ exps.addError("no shared cipher");
+ // OpenSSL errors.
+ exps.addError("sslv3 alert handshake failure");
+ exps.addError("no shared cipher (SSL routines)");
+ exps.addError("no shared cipher (SSL routines, ACCEPT_SR_CLNT_HELLO_C)");
+ exps.addError("no shared cipher (SSL routines, tls_post_process_client_hello)");
+ // Recent LibreSSL error.
+ exps.addError("missing rsa certificate (SSL routines, (UNKNOWN)SSL_internal)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ exps.clear();
+ // On Botan and some OpenSSL the client hangs.
+ exps.addTimeout();
+ // OpenSSL errors.
+ exps.addError("sslv3 alert handshake failure");
+ exps.addError("sslv3 alert handshake failure (SSL routines)");
+ exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_SRVR_HELLO)");
+ exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_CERT)");
+ exps.addError("sslv3 alert handshake failure (SSL routines, ssl3_read_bytes)");
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "client timeout\n";
+ } else {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client was not configured.
+TEST(TLSTest, clientNotConfigured) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx(new TlsContext(TlsRole::CLIENT));
+ // Skip config.
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // On Botan and some OpenSSL the server hangs.
+ exps.addTimeout();
+ // OpenSSL errors.
+ exps.addError("tlsv1 alert unknown ca");
+ exps.addError("tlsv1 alert unknown ca (SSL routines)");
+ exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT)");
+ exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT_VRFY)");
+ exps.addError("tlsv1 alert unknown ca (SSL routines, ssl3_read_bytes)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "server timeout\n";
+ } else {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ exps.clear();
+ // Botan error (unfortunately a bit generic).
+ exps.addError("bad_certificate");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // OpenSSL errors.
+ exps.addError("certificate verify failed");
+ exps.addError("certificate verify failed (SSL routines)");
+ exps.addError("certificate verify failed (SSL routines, CONNECT_CR_CERT)");
+ exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)");
+ exps.addError("certificate verify failed (SSL routines, tls_process_server_certificate)");
+ // The client should not hang.
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client is HTTP (vs HTTPS).
+TEST(TLSTest, clientHTTPnoS) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ tcp::socket client(service.get_io_service());
+
+ // Connect to.
+ client.open(tcp::v4());
+ TestCallback connect_cb;
+ client.async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform server TLS handshake.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+
+ // Client sending a HTTP GET.
+ char send_buf[] = "GET / HTTP/1.1\r\n";
+ TestCallback client_cb;
+ client.async_send(boost::asio::buffer(send_buf), client_cb);
+
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan server hangs before 2.18.
+ exps.addTimeout();
+ // Botan error.
+ exps.addError("protocol_version");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert protocol version");
+ // OpenSSL errors (OpenSSL recognizes HTTP).
+ exps.addError("http request");
+ exps.addError("http request (SSL routines)");
+ exps.addError("http request (SSL routines, ACCEPT_SR_CLNT_HELLO)");
+ exps.addError("http request (SSL routines, ssl3_get_record)");
+ // Another OpenSSL error (not all OpenSSL recognizes HTTP).
+ exps.addError("wrong version number");
+ // Recent LibreSSL error.
+ exps.addError("tlsv1 alert protocol version (SSL routines, ACCEPT_SR_CLNT_HELLO)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "server timeout\n";
+ } else {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // No error at the client.
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client does not use HTTP nor HTTP.
+TEST(TLSTest, unknownClient) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ tcp::socket client(service.get_io_service());
+
+ // Connect to.
+ client.open(tcp::v4());
+ TestCallback connect_cb;
+ client.async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform server TLS handshake.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+
+ // Client sending something which is not a TLS ClientHello.
+ char send_buf[] = "hello my server...";
+ TestCallback client_cb;
+ client.async_send(boost::asio::buffer(send_buf), client_cb);
+
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan errors.
+ exps.addError("record_overflow");
+ exps.addError("protocol_version");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert protocol version");
+ // Old OpenSSL error.
+ exps.addError("unknown protocol");
+ // Recent OpenSSL errors.
+ exps.addError("wrong version number");
+ exps.addError("wrong version number (SSL routines)");
+ exps.addError("wrong version number (SSL routines, ssl3_get_record)");
+ // Recent LibreSSL error.
+ exps.addError("unknown protocol (SSL routines, ACCEPT_SR_CLNT_HELLO)");
+ exps.addError("tlsv1 alert protocol version (SSL routines, ACCEPT_SR_CLNT_HELLO)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ // No error on the client side.
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a certificate from another CA.
+TEST(TLSTest, anotherClient) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part using a certificate signed by another CA.
+ TlsContextPtr client_ctx;
+ test::configOther(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("bad_certificate");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // OpenSSL errors.
+ // Full error is:
+ // error 20 at 0 depth lookup:unable to get local issuer certificate
+ exps.addError("certificate verify failed");
+ exps.addError("certificate verify failed (SSL routines)");
+ exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)");
+ // Recent LibreSSL errors.
+ exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)");
+ exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ exps.clear();
+ // Botan client hangs.
+ exps.addTimeout();
+ // Old LibreSSL and recent OpenSSL do not fail.
+ exps.addNoError();
+ // Old OpenSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // Recent LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)");
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "client timeout\n";
+ } else if (exps.hasErrMsg()) {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a self-signed certificate.
+TEST(TLSTest, selfSigned) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part using a self-signed certificate.
+ TlsContextPtr client_ctx;
+ test::configSelf(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("bad_certificate");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // OpenSSL errors.
+ // Full error is:
+ // error 18 at 0 depth lookup:self signed certificate
+ exps.addError("certificate verify failed");
+ exps.addError("certificate verify failed (SSL routines)");
+ exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)");
+ // Recent LibreSSL errors.
+ exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)");
+ exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ exps.clear();
+ // Botan client hangs.
+ exps.addTimeout();
+ // Old LibreSSL and recent OpenSSL do not fail.
+ exps.addNoError();
+ // Old OpenSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // Recent LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)");
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "client timeout\n";
+ } else if (exps.hasErrMsg()) {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+////////////////////////////////////////////////////////////////////////
+// Close on error handshake failures //
+////////////////////////////////////////////////////////////////////////
+
+// Investigate what happens when a peer closes its streams when the
+// handshake callback returns an error. In particular does still
+// the other peer timeout?
+
+// Test what happens when handshake is forgotten.
+TEST(TLSTest, noHandshakeCloseonError) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer1(service);
+ bool timeout = false;
+ timer1.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Send on the client.
+ char send_buf[] = "some text...";
+ TestCallback send_cb(&client.lowest_layer());
+ async_write(client, boost::asio::buffer(send_buf), send_cb);
+ while (!timeout && !send_cb.getCalled()) {
+ service.run_one();
+ }
+ timer1.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("InvalidObjectState");
+ // OpenSSL errors.
+ exps.addError("uninitialized");
+ exps.addError("uninitialized (SSL routines)");
+ exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)");
+ exps.addError("uninitialized (SSL routines, ssl_write_internal)");
+ exps.checkAsync("send", send_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "send: " << exps.getErrMsg() << "\n";
+ }
+
+ // Setup a second timeout.
+ IntervalTimer timer2(service);
+ timeout = false;
+ timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Receive on the server.
+ vector<char> receive_buf(64);
+ TestCallback receive_cb;
+ server.async_read_some(boost::asio::buffer(receive_buf), receive_cb);
+ while (!timeout && !receive_cb.getCalled()) {
+ service.run_one();
+ }
+ timer2.cancel();
+
+ exps.clear();
+ // Botan and some OpenSSL.
+ exps.addError("stream truncated");
+ // OpenSSL errors.
+ exps.addError("uninitialized");
+ exps.addError("uninitialized (SSL routines)");
+ exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)");
+ exps.addError("uninitialized (SSL routines, ssl_read_internal)");
+ exps.checkAsync("receive", receive_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "receive: " << exps.getErrMsg() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the server was not configured.
+TEST(TLSTest, serverNotConfiguredCloseonError) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+ // Skip config.
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb(&server.lowest_layer());
+ server.handshake(server_cb);
+ TestCallback client_cb;
+ client.handshake(client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("handshake_failure");
+ // Old LibreSSL error.
+ exps.addError("no shared cipher");
+ // OpenSSL errors.
+ exps.addError("sslv3 alert handshake failure");
+ exps.addError("no shared cipher (SSL routines)");
+ exps.addError("no shared cipher (SSL routines, ACCEPT_SR_CLNT_HELLO_C)");
+ exps.addError("no shared cipher (SSL routines, tls_post_process_client_hello)");
+ // Recent LibreSSL error.
+ exps.addError("missing rsa certificate (SSL routines, (UNKNOWN)SSL_internal)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ exps.clear();
+ // Botan and some OpenSSL.
+ exps.addError("stream truncated");
+ // Alias on old OpenSSL.
+ exps.addError("short read");
+ // OpenSSL errors.
+ exps.addError("sslv3 alert handshake failure");
+ exps.addError("sslv3 alert handshake failure (SSL routines)");
+ exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_SRVR_HELLO)");
+ exps.addError("sslv3 alert handshake failure (SSL routines, ssl3_read_bytes)");
+ // Recent LibreSSL error.
+ exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_CERT)");
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client was not configured.
+TEST(TLSTest, clientNotConfiguredCloseonError) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx(new TlsContext(TlsRole::CLIENT));
+ // Skip config.
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb(&client.lowest_layer());
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan and some OpenSSL.
+ exps.addError("stream truncated");
+ // Alias on old OpenSSL.
+ exps.addError("short read");
+ // OpenSSL errors.
+ exps.addError("tlsv1 alert unknown ca");
+ exps.addError("tlsv1 alert unknown ca (SSL routines)");
+ exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT)");
+ exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT_VRFY)");
+ exps.addError("tlsv1 alert unknown ca (SSL routines, ssl3_read_bytes)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ exps.clear();
+ // Botan error (unfortunately a bit generic).
+ exps.addError("bad_certificate");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // OpenSSL errors.
+ exps.addError("certificate verify failed");
+ exps.addError("certificate verify failed (SSL routines)");
+ exps.addError("certificate verify failed (SSL routines, CONNECT_CR_CERT)");
+ exps.addError("certificate verify failed (SSL routines, tls_process_server_certificate)");
+ // Recent LibreSSL error.
+ exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)");
+ // The client should not hang.
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client is HTTP (vs HTTPS).
+TEST(TLSTest, clientHTTPnoSCloseonError) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ tcp::socket client(service.get_io_service());
+
+ // Connect to.
+ client.open(tcp::v4());
+ TestCallback connect_cb;
+ client.async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform server TLS handshake.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+
+ // Client sending a HTTP GET.
+ char send_buf[] = "GET / HTTP/1.1\r\n";
+ TestCallback client_cb;
+ client.async_send(boost::asio::buffer(send_buf), client_cb);
+
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan server hangs before 2.18.
+ exps.addTimeout();
+ // Botan behavior was reported and fixed.
+ exps.addError("protocol_version");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert protocol version");
+ // OpenSSL errors when OpenSSL recognizes HTTP.
+ exps.addError("http request");
+ exps.addError("http request (SSL routines)");
+ exps.addError("http request (SSL routines, ACCEPT_SR_CLNT_HELLO)");
+ exps.addError("http request (SSL routines, ssl3_get_record)");
+ // Another OpenSSL error (not all OpenSSL recognizes HTTP).
+ exps.addError("wrong version number");
+ // Recent LibreSSL error.
+ exps.addError("tlsv1 alert protocol version (SSL routines, ACCEPT_SR_CLNT_HELLO)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "server timeout\n";
+ } else {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // No error at the client.
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a certificate from another CA.
+TEST(TLSTest, anotherClientCloseonError) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part using a certificate signed by another CA.
+ TlsContextPtr client_ctx;
+ test::configOther(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb(&server.lowest_layer());
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("bad_certificate");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // OpenSSL errors.
+ // Full error is:
+ // error 20 at 0 depth lookup:unable to get local issuer certificate
+ exps.addError("certificate verify failed");
+ exps.addError("certificate verify failed (SSL routines)");
+ exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)");
+ // Recent LibreSSL errors.
+ exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)");
+ exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ exps.clear();
+ // Botan and some OpenSSL.
+ exps.addError("stream truncated");
+ // Alias on old OpenSSL.
+ exps.addError("short read");
+ // Old LibreSSL and recent OpenSSL do not fail.
+ exps.addNoError();
+ // Old OpenSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // Recent LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)");
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg() && exps.hasErrMsg()) {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the client uses a self-signed certificate.
+TEST(TLSTest, selfSignedCloseonError) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part using a self-signed certificate.
+ TlsContextPtr client_ctx;
+ test::configSelf(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb(&server.lowest_layer());
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ Expecteds exps;
+ // Botan error.
+ exps.addError("bad_certificate");
+ // Old LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // OpenSSL errors.
+ // Full error is:
+ // error 18 at 0 depth lookup:self signed certificate
+ exps.addError("certificate verify failed");
+ exps.addError("certificate verify failed (SSL routines)");
+ exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)");
+ // Recent LibreSSL errors.
+ exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)");
+ exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)");
+ exps.checkAsync("server", server_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "server: " << exps.getErrMsg() << "\n";
+ }
+
+ exps.clear();
+ // Botan and some OpenSSL.
+ exps.addError("stream truncated");
+ // Alias on old OpenSSL.
+ exps.addError("short read");
+ // Old LibreSSL and recent OpenSSL do not fail.
+ exps.addNoError();
+ // Old OpenSSL error.
+ exps.addError("tlsv1 alert unknown ca");
+ // Recent LibreSSL error.
+ exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)");
+ exps.checkAsync("client", client_cb);
+ if (Expecteds::displayErrMsg() && exps.hasErrMsg()) {
+ std::cout << "client: " << exps.getErrMsg() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+////////////////////////////////////////////////////////////////////////
+// TLS handshake corner case //
+////////////////////////////////////////////////////////////////////////
+
+// Some special cases.
+
+// Test what happens when the client uses a certificate from another CA
+// but the client certificate request and validation are disabled.
+TEST(TLSTest, anotherClientNoReq) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServerNoReq(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part using a certificate signed by another CA.
+ TlsContextPtr client_ctx;
+ test::configOther(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ // Should not fail.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_FALSE(server_cb.getCode());
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the server uses a certificate without subject
+// alternative name (but still a version 3 certificate).
+TEST(TLSTest, serverRaw) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configServerRaw(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ // Should not fail.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_FALSE(server_cb.getCode());
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+#ifdef WITH_OPENSSL
+// Test what happens when the client uses a trusted self-signed certificate.
+// Not really a failure case as it works...
+TEST(TLSTest, trustedSelfSigned) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx;
+ test::configTrustedSelf(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part using a self-signed certificate.
+ TlsContextPtr client_ctx;
+ test::configSelf(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb;
+ server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb);
+ TestCallback client_cb;
+ client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ // It should work for all OpenSSL.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_FALSE(server_cb.getCode());
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+#endif // WITH_OPENSSL
+
+////////////////////////////////////////////////////////////////////////
+// TLS shutdown //
+////////////////////////////////////////////////////////////////////////
+
+// Investigate the TLS shutdown processing.
+
+// Test what happens when the shutdown receiver is inactive.
+TEST(TLSTest, shutdownInactive) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb(&server.lowest_layer());
+ server.handshake(server_cb);
+ TestCallback client_cb;
+ client.handshake(client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ // No problem is expected.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_FALSE(server_cb.getCode());
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Setup a timeout for the shutdown.
+ IntervalTimer timer2(service);
+ timeout = false;
+ timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Shutdown on the client leaving the server inactive.
+ TestCallback shutdown_cb;
+ client.shutdown(shutdown_cb);
+ while (!timeout && !shutdown_cb.getCalled()) {
+ service.run_one();
+ }
+ timer2.cancel();
+
+ Expecteds exps;
+ // Botan gets no error.
+ exps.addNoError();
+ // OpenSSL hangs.
+ exps.addTimeout();
+ exps.checkAsync("shutdown", shutdown_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "shutdown timeout\n";
+ } else if (exps.hasErrMsg()) {
+ std::cout << "shutdown: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the shutdown receiver is active.
+TEST(TLSTest, shutdownActive) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb(&server.lowest_layer());
+ server.handshake(server_cb);
+ TestCallback client_cb;
+ client.handshake(client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ // No problem is expected.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_FALSE(server_cb.getCode());
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Setup a timeout for the shutdown and receive.
+ IntervalTimer timer2(service);
+ timeout = false;
+ timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Receive on the server.
+ vector<char> receive_buf(64);
+ TestCallback receive_cb;
+ server.async_read_some(boost::asio::buffer(receive_buf), receive_cb);
+
+ // Shutdown on the client.
+ TestCallback shutdown_cb;
+ client.shutdown(shutdown_cb);
+ while (!timeout && (!shutdown_cb.getCalled() || !receive_cb.getCalled())) {
+ service.run_one();
+ }
+ timer2.cancel();
+
+ Expecteds exps;
+ // Botan gets no error.
+ exps.addNoError();
+ // OpenSSL hangs.
+ exps.addTimeout();
+ exps.checkAsync("shutdown", shutdown_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "shutdown timeout\n";
+ } else if (exps.hasErrMsg()) {
+ std::cout << "shutdown: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ exps.clear();
+ // End of file on the receive side.
+ exps.addError("End of file");
+ exps.checkAsync("receive", receive_cb);
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "receive: " << exps.getErrMsg() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the shutdown receiver is inactive on shutdown
+// and immediate close.
+TEST(TLSTest, shutdownCloseInactive) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb(&server.lowest_layer());
+ server.handshake(server_cb);
+ TestCallback client_cb;
+ client.handshake(client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ // No problem is expected.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_FALSE(server_cb.getCode());
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Setup a timeout for the shutdown.
+ IntervalTimer timer2(service);
+ timeout = false;
+ timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Shutdown on the client leaving the server inactive.
+ TestCallback shutdown_cb;
+ client.shutdown(shutdown_cb);
+
+ // Post a close which should be called after the shutdown.
+ service.post([&client] { client.lowest_layer().close(); });
+ while (!timeout && !shutdown_cb.getCalled()) {
+ service.run_one();
+ }
+ timer2.cancel();
+
+ Expecteds exps;
+ // Botan gets no error.
+ exps.addNoError();
+ // LibreSSL and some old OpenSSL gets Operation canceled.
+ exps.addError("Operation canceled");
+ // OpenSSL gets Bad file descriptor.
+ exps.addError("Bad file descriptor");
+ exps.checkAsync("shutdown", shutdown_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "shutdown timeout\n";
+ } else if (exps.hasErrMsg()) {
+ std::cout << "shutdown: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Test what happens when the shutdown receiver is active with an
+// immediate close.
+TEST(TLSTest, shutdownCloseActive) {
+ IOService service;
+
+ // Server part.
+ TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER));
+ test::configServer(server_ctx);
+ TlsStream<TestCallback> server(service, server_ctx);
+
+ // Accept a client.
+ tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT));
+ tcp::acceptor acceptor(service.get_io_service(), server_ep);
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ TestCallback accept_cb;
+ acceptor.async_accept(server.lowest_layer(), accept_cb);
+
+ // Client part.
+ TlsContextPtr client_ctx;
+ test::configClient(client_ctx);
+ TlsStream<TestCallback> client(service, client_ctx);
+
+ // Connect to.
+ client.lowest_layer().open(tcp::v4());
+ TestCallback connect_cb;
+ client.lowest_layer().async_connect(server_ep, connect_cb);
+
+ // Run accept and connect.
+ while (!accept_cb.getCalled() || !connect_cb.getCalled()) {
+ service.run_one();
+ }
+
+ // Verify the error codes.
+ if (accept_cb.getCode()) {
+ FAIL() << "accept error " << accept_cb.getCode().value()
+ << " '" << accept_cb.getCode().message() << "'";
+ }
+ // Possible EINPROGRESS for the client.
+ if (connect_cb.getCode() &&
+ (connect_cb.getCode().value() != EINPROGRESS)) {
+ FAIL() << "connect error " << connect_cb.getCode().value()
+ << " '" << connect_cb.getCode().message() << "'";
+ }
+
+ // Setup a timeout.
+ IntervalTimer timer(service);
+ bool timeout = false;
+ timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Perform TLS handshakes.
+ TestCallback server_cb(&server.lowest_layer());
+ server.handshake(server_cb);
+ TestCallback client_cb;
+ client.handshake(client_cb);
+ while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) {
+ service.run_one();
+ }
+ timer.cancel();
+
+ // No problem is expected.
+ EXPECT_FALSE(timeout);
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_FALSE(server_cb.getCode());
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_FALSE(client_cb.getCode());
+
+ // Setup a timeout for the shutdown and receive.
+ IntervalTimer timer2(service);
+ timeout = false;
+ timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT);
+
+ // Receive on the server.
+ vector<char> receive_buf(64);
+ TestCallback receive_cb;
+ server.async_read_some(boost::asio::buffer(receive_buf), receive_cb);
+
+ // Shutdown on the client.
+ TestCallback shutdown_cb;
+ client.shutdown(shutdown_cb);
+
+ // Post a close which should be called after the shutdown.
+ service.post([&client] { client.lowest_layer().close(); });
+ while (!timeout && (!shutdown_cb.getCalled() || !receive_cb.getCalled())) {
+ service.run_one();
+ }
+ timer2.cancel();
+
+ Expecteds exps;
+ // Botan gets no error.
+ exps.addNoError();
+ // LibreSSL and some old OpenSSL gets Operation canceled.
+ exps.addError("Operation canceled");
+ // OpenSSL gets Bad file descriptor.
+ exps.addError("Bad file descriptor");
+ exps.checkAsync("shutdown", shutdown_cb);
+ if (Expecteds::displayErrMsg()) {
+ if (timeout) {
+ std::cout << "shutdown timeout\n";
+ } else if (exps.hasErrMsg()) {
+ std::cout << "shutdown: " << exps.getErrMsg() << "\n";
+ }
+ }
+
+ // End of file on the receive side.
+ EXPECT_TRUE(receive_cb.getCalled());
+ EXPECT_TRUE(receive_cb.getCode());
+ EXPECT_EQ("End of file", receive_cb.getCode().message());
+ if (Expecteds::displayErrMsg()) {
+ std::cout << "receive: " << receive_cb.getCode().message() << "\n";
+ }
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.lowest_layer().close());
+ EXPECT_NO_THROW(server.lowest_layer().close());
+}
+
+// Conclusion about the shutdown: do the close on completion (e.g. in the
+// handler) or on timeout (i.e. simulate an asynchronous shutdown with
+// timeout).
+
+} // end of anonymous namespace.
diff --git a/src/lib/asiolink/tests/udp_endpoint_unittest.cc b/src/lib/asiolink/tests/udp_endpoint_unittest.cc
new file mode 100644
index 0000000..cf932f1
--- /dev/null
+++ b/src/lib/asiolink/tests/udp_endpoint_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2011-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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/udp_endpoint.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc::asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// boost::asio::ip::udp::endpoint object.
+
+TEST(UDPEndpointTest, v4Address) {
+ const string test_address("192.0.2.1");
+ const unsigned short test_port = 5301;
+
+ IOAddress address(test_address);
+ UDPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(static_cast<short>(IPPROTO_UDP), endpoint.getProtocol());
+ EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(UDPEndpointTest, v6Address) {
+ const string test_address("2001:db8::1235");
+ const unsigned short test_port = 5302;
+
+ IOAddress address(test_address);
+ UDPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(static_cast<short>(IPPROTO_UDP), endpoint.getProtocol());
+ EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc
new file mode 100644
index 0000000..1fb9616
--- /dev/null
+++ b/src/lib/asiolink/tests/udp_socket_unittest.cc
@@ -0,0 +1,324 @@
+// Copyright (C) 2011-2020 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/.
+
+/// \brief Test of UDPSocket
+///
+/// Tests the functionality of a UDPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <string>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <algorithm>
+#include <cstdlib>
+#include <cstddef>
+#include <vector>
+
+
+using namespace boost::asio;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5301;
+
+// TODO: Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+///
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O
+/// completes. The arguments to the completion callback are stored for later
+/// retrieval.
+class UDPCallback {
+public:
+
+ struct PrivateData {
+ PrivateData() :
+ error_code_(), length_(0), called_(false), name_("")
+ {}
+
+ boost::system::error_code error_code_; ///< Completion error code
+ size_t length_; ///< Number of bytes transferred
+ bool called_; ///< Set true when callback called
+ std::string name_; ///< Which of the objects this is
+ };
+
+ /// \brief Constructor
+ ///
+ /// Constructs the object. It also creates the data member pointed to by
+ /// a shared pointer. When used as a callback object, this is copied as it
+ /// is passed into the asynchronous function. This means that there are two
+ /// objects and inspecting the one we passed in does not tell us anything.
+ ///
+ /// Therefore we use a boost::shared_ptr. When the object is copied, the
+ /// shared pointer is copied, which leaves both objects pointing to the same
+ /// data.
+ ///
+ /// \param which Which of the two callback objects this is
+ UDPCallback(const std::string& which) : ptr_(new PrivateData())
+ {
+ setName(which);
+ }
+
+ /// \brief Destructor
+ ///
+ /// No code needed, destroying the shared pointer destroys the private data.
+ virtual ~UDPCallback()
+ {}
+
+ /// \brief Callback Function
+ ///
+ /// Called when an asynchronous I/O completes, this stores the
+ /// completion error code and the number of bytes transferred.
+ ///
+ /// \param ec I/O completion error code passed to callback function.
+ /// \param length Number of bytes transferred
+ virtual void operator()(boost::system::error_code ec, size_t length = 0) {
+ ptr_->error_code_ = ec;
+ setLength(length);
+ setCalled(true);
+ }
+
+ /// \brief Get I/O completion error code
+ int getCode() {
+ return (ptr_->error_code_.value());
+ }
+
+ /// \brief Set I/O completion code
+ ///
+ /// \param code New value of completion code
+ void setCode(int code) {
+ ptr_->error_code_ = boost::system::error_code(code, boost::system::error_code().category());
+ }
+
+ /// \brief Get number of bytes transferred in I/O
+ size_t getLength() const {
+ return (ptr_->length_);
+ }
+
+ /// \brief Set number of bytes transferred in I/O
+ ///
+ /// \param length New value of length parameter
+ void setLength(size_t length) {
+ ptr_->length_ = length;
+ }
+
+ /// \brief Get flag to say when callback was called
+ bool getCalled() const {
+ return (ptr_->called_);
+ }
+
+ /// \brief Set flag to say when callback was called
+ ///
+ /// \param called New value of called parameter
+ void setCalled(bool called) {
+ ptr_->called_ = called;
+ }
+
+ /// \brief Return instance of callback name
+ std::string getName() const {
+ return (ptr_->name_);
+ }
+
+ /// \brief Set callback name
+ ///
+ /// \param name New value of the callback name
+ void setName(const std::string& name) {
+ ptr_->name_ = name;
+ }
+
+private:
+ boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
+};
+
+// Receive complete method should return true regardless of what is in the first
+// two bytes of a buffer.
+
+TEST(UDPSocket, processReceivedData) {
+ IOService service; // Used to instantiate socket
+ UDPSocket<UDPCallback> test(service); // Socket under test
+ uint8_t inbuff[32]; // Buffer to check
+ OutputBufferPtr outbuff(new OutputBuffer(16));
+ // Where data is put
+ // cppcheck-suppress variableScope
+ size_t expected; // Expected amount of data
+ // cppcheck-suppress variableScope
+ size_t offset; // Where to put next data
+ // cppcheck-suppress variableScope
+ size_t cumulative; // Cumulative data received
+
+ // Set some dummy values in the buffer to check
+ for (uint8_t i = 0; i < sizeof(inbuff); ++i) {
+ inbuff[i] = i;
+ }
+
+ // Expect that the value is true whatever number is written in the first
+ // two bytes of the buffer.
+ uint16_t count = 0;
+ for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+ writeUint16(count, inbuff, sizeof(inbuff));
+
+ // Set some random values
+ cumulative = 5;
+ offset = 10;
+ expected = 15;
+ outbuff->clear();
+
+ bool completed = test.processReceivedData(inbuff, sizeof(inbuff),
+ cumulative, offset, expected,
+ outbuff);
+ EXPECT_TRUE(completed);
+ EXPECT_EQ(sizeof(inbuff), cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(sizeof(inbuff), expected);
+
+ const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff, inbuff + sizeof(inbuff) - 1, dataptr));
+ }
+}
+
+// TODO: Need to add a test to check the cancel() method
+
+// Tests the operation of a UDPSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(UDPSocket, SequenceTest) {
+
+ // Common objects.
+ IOService service; // Service object for async control
+
+ // Server
+ IOAddress server_address(SERVER_ADDRESS); // Address of target server
+ UDPCallback server_cb("Server"); // Server callback
+ UDPEndpoint server_endpoint( // Endpoint describing server
+ server_address, SERVER_PORT);
+ UDPEndpoint server_remote_endpoint; // Address where server received message from
+
+ // The client - the UDPSocket being tested
+ UDPSocket<UDPCallback> client(service);// Socket under test
+ UDPCallback client_cb("Client"); // Async I/O callback function
+ UDPEndpoint client_remote_endpoint; // Where client receives message from
+ size_t client_cumulative = 0; // Cumulative data received
+ size_t client_offset = 0; // Offset into buffer where data is put
+ size_t client_expected = 0; // Expected amount of data
+ OutputBufferPtr client_buffer(new OutputBuffer(16));
+ // Where data is put
+
+ // The server - with which the client communicates. For convenience, we
+ // use the same io_service, and use the endpoint object created for
+ // the client to send to as the endpoint object in the constructor.
+ boost::asio::ip::udp::socket server(service.get_io_service(),
+ server_endpoint.getASIOEndpoint());
+ server.set_option(socket_base::reuse_address(true));
+
+ // Assertion to ensure that the server buffer is large enough
+ char data[UDPSocket<UDPCallback>::MIN_SIZE];
+ ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA));
+
+ // Open the client socket - the operation should be synchronous
+ EXPECT_TRUE(client.isOpenSynchronous());
+ client.open(&server_endpoint, client_cb);
+
+ // Issue read on the server. Completion callback should not have run.
+ server_cb.setCalled(false);
+ server_cb.setCode(42); // Answer to Life, the Universe and Everything!
+ server.async_receive_from(buffer(data, sizeof(data)),
+ server_remote_endpoint.getASIOEndpoint(), server_cb);
+ EXPECT_FALSE(server_cb.getCalled());
+
+ // Write something to the server using the client - the callback should not
+ // be called until we call the io_service.run() method.
+ client_cb.setCalled(false);
+ client_cb.setCode(7); // Arbitrary number
+ client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+ EXPECT_FALSE(client_cb.getCalled());
+
+ // Execute the two callbacks.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA), client_cb.getLength());
+
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA), server_cb.getLength());
+
+ EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], OUTBOUND_DATA));
+
+ // Now return data from the server to the client. Issue the read on the
+ // client.
+ client_cb.setLength(12345); // Arbitrary number
+ client_cb.setCalled(false);
+ client_cb.setCode(32); // Arbitrary number
+ client.asyncReceive(data, sizeof(data), client_cumulative,
+ &client_remote_endpoint, client_cb);
+
+ // Issue the write on the server side to the source of the data it received.
+ server_cb.setLength(22345); // Arbitrary number
+ server_cb.setCalled(false);
+ server_cb.setCode(232); // Arbitrary number
+ server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)),
+ server_remote_endpoint.getASIOEndpoint(), server_cb);
+
+ // Expect two callbacks to run.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_TRUE(client_cb.getCalled());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA), client_cb.getLength());
+
+ EXPECT_TRUE(server_cb.getCalled());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA), server_cb.getLength());
+
+ EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], INBOUND_DATA));
+
+ // Check that the address/port received by the client corresponds to the
+ // address and port the server is listening on.
+ EXPECT_TRUE(server_address == client_remote_endpoint.getAddress());
+ EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort());
+
+ // Check that the receive received a complete buffer's worth of data.
+ EXPECT_TRUE(client.processReceivedData(&data[0], client_cb.getLength(),
+ client_cumulative, client_offset,
+ client_expected, client_buffer));
+
+ EXPECT_EQ(client_cb.getLength(), client_cumulative);
+ EXPECT_EQ(0, client_offset);
+ EXPECT_EQ(client_cb.getLength(), client_expected);
+ EXPECT_EQ(client_cb.getLength(), client_buffer->getLength());
+
+ // ...and check that the data was copied to the output client buffer.
+ const char* client_char_data = static_cast<const char*>(client_buffer->getData());
+ EXPECT_TRUE(equal(&data[0], &data[client_cb.getLength() - 1], client_char_data));
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.close());
+ EXPECT_NO_THROW(server.close());
+}
diff --git a/src/lib/asiolink/tests/unix_domain_socket_unittest.cc b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc
new file mode 100644
index 0000000..31c573d
--- /dev/null
+++ b/src/lib/asiolink/tests/unix_domain_socket_unittest.cc
@@ -0,0 +1,310 @@
+// Copyright (C) 2017-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <gtest/gtest.h>
+#include <testutils/sandbox.h>
+#include <array>
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref UnixDomainSocket class.
+class UnixDomainSocketTest : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Constructor.
+ ///
+ /// Removes unix socket descriptor before the test.
+ UnixDomainSocketTest() :
+ io_service_(),
+ test_socket_(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath())),
+ response_(),
+ read_buf_() {
+ test_socket_->startTimer(TEST_TIMEOUT);
+ removeUnixSocketFile();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes unix socket descriptor after the test.
+ virtual ~UnixDomainSocketTest() {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Returns socket file path.
+ ///
+ /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
+ /// socket file is created in the location pointed to by this variable.
+ /// Otherwise, it is created in the build directory.
+ std::string unixSocketFilePath() {
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = sandbox.join("test-socket");
+ }
+ return (socket_path);
+ }
+
+ /// @brief Removes unix socket descriptor.
+ void removeUnixSocketFile() {
+ static_cast<void>(remove(unixSocketFilePath().c_str()));
+ }
+
+ /// @brief Performs asynchronous receive on unix domain socket.
+ ///
+ /// This function performs partial read from the unix domain socket.
+ /// It uses @c read_buf_ or small size to ensure that the buffer fills
+ /// in before all that have been read. The partial responses are
+ /// appended to the @c response_ class member.
+ ///
+ /// If the response received so far is shorter than the expected
+ /// response, another partial read is scheduled.
+ ///
+ /// @param socket Reference to the unix domain socket.
+ /// @param expected_response Expected response.
+ void doReceive(UnixDomainSocket& socket,
+ const std::string& expected_response) {
+ socket.asyncReceive(&read_buf_[0], read_buf_.size(),
+ [this, &socket, expected_response]
+ (const boost::system::error_code& ec, size_t length) {
+ if (!ec) {
+ // Append partial response received and see if the
+ // size of the response received so far is still
+ // smaller than expected. If it is, schedule another
+ // partial read.
+ response_.append(&read_buf_[0], length);
+ if (expected_response.size() > response_.size()) {
+ doReceive(socket, expected_response);
+ }
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while asynchronously receiving"
+ " data via unix domain socket: " << ec.message();
+ }
+ });
+ }
+
+ /// @brief IO service used by the tests.
+ IOService io_service_;
+
+ /// @brief Server side unix socket used in these tests.
+ test::TestServerUnixSocketPtr test_socket_;
+
+ /// @brief String containing a response received with @c doReceive.
+ std::string response_;
+
+ /// @brief Read buffer used by @c doReceive.
+ std::array<char, 2> read_buf_;
+};
+
+// This test verifies that the client can send data over the unix
+// domain socket and receive a response.
+TEST_F(UnixDomainSocketTest, sendReceive) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+ ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+
+ // Send "foo".
+ const std::string outbound_data = "foo";
+ size_t sent_size = 0;
+ ASSERT_NO_THROW(sent_size = socket.write(outbound_data.c_str(),
+ outbound_data.size()));
+ // Make sure all data have been sent.
+ ASSERT_EQ(outbound_data.size(), sent_size);
+
+ // Run IO service to generate server's response.
+ while ((test_socket_->getResponseNum() < 1) &&
+ (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // Receive response from the socket.
+ std::array<char, 1024> read_buf;
+ size_t bytes_read = 0;
+ ASSERT_NO_THROW(bytes_read = socket.receive(&read_buf[0], read_buf.size()));
+ std::string response(&read_buf[0], bytes_read);
+
+ // The server should prepend "received" to the data we had sent.
+ EXPECT_EQ("received foo", response);
+}
+
+// This test verifies that the client can send the data over the unix
+// domain socket and receive a response asynchronously.
+TEST_F(UnixDomainSocketTest, asyncSendReceive) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+
+ // We're going to asynchronously connect to the server. The boolean value
+ // below will be modified by the connect handler function (lambda) invoked
+ // when the connection is established or if an error occurs.
+ bool connect_handler_invoked = false;
+ ASSERT_NO_THROW(socket.asyncConnect(unixSocketFilePath(),
+ [&connect_handler_invoked](const boost::system::error_code& ec) {
+ // Indicate that the handler has been called so as the loop below gets
+ // interrupted.
+ connect_handler_invoked = true;
+ // Operation aborted indicates that IO service has been stopped. This
+ // shouldn't happen here.
+ if (ec && (ec.value() != boost::asio::error::operation_aborted)) {
+ ADD_FAILURE() << "error occurred while asynchronously connecting"
+ " via unix domain socket: " << ec.message();
+ }
+ }
+ ));
+ // Run IO service until connect handler is invoked.
+ while (!connect_handler_invoked && (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // We are going to asynchronously send the 'foo' over the unix socket.
+ const std::string outbound_data = "foo";
+ size_t sent_size = 0;
+ ASSERT_NO_THROW(socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+ [&sent_size](const boost::system::error_code& ec, size_t length) {
+ // If we have been successful sending the data, record the number of
+ // bytes we have sent.
+ if (!ec) {
+ sent_size = length;
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ ADD_FAILURE() << "error occurred while asynchronously sending the"
+ " data over unix domain socket: " << ec.message();
+ }
+ }
+ ));
+
+ // Run IO service to generate server's response.
+ while ((test_socket_->getResponseNum() < 1) &&
+ (!test_socket_->isStopped())) {
+ io_service_.run_one();
+ }
+
+ // There is no guarantee that all data have been sent so we only check that
+ // some data have been sent.
+ ASSERT_GT(sent_size, 0);
+
+ std::string expected_response = "received foo";
+ doReceive(socket, expected_response);
+
+ // Run IO service until we get the full response from the server.
+ while ((response_.size() < expected_response.size()) &&
+ !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Check that the entire response has been received and is correct.
+ EXPECT_EQ(expected_response, response_);
+}
+
+// This test verifies that UnixDomainSocketError exception is thrown
+// on attempt to connect, write or receive when the server socket
+// is not available.
+TEST_F(UnixDomainSocketTest, clientErrors) {
+ UnixDomainSocket socket(io_service_);
+ ASSERT_THROW(socket.connect(unixSocketFilePath()), UnixDomainSocketError);
+ const std::string outbound_data = "foo";
+ ASSERT_THROW(socket.write(outbound_data.c_str(), outbound_data.size()),
+ UnixDomainSocketError);
+ std::array<char, 1024> read_buf;
+ ASSERT_THROW(socket.receive(&read_buf[0], read_buf.size()),
+ UnixDomainSocketError);
+}
+
+// This test verifies that an error is returned on attempt to asynchronously
+// connect, write or receive when the server socket is not available.
+TEST_F(UnixDomainSocketTest, asyncClientErrors) {
+ UnixDomainSocket socket(io_service_);
+
+ // Asynchronous operations signal errors through boost::system::error_code
+ // object passed to the handler function. This object casts to boolean.
+ // In case of success the object casts to false. In case of an error it
+ // casts to true. The actual error codes can be retrieved by comparing the
+ // ec objects to predefined error objects. We don't check for the actual
+ // errors here, because it is not certain that the same error codes would
+ // be returned on various operating systems.
+
+ // In the following tests we use C++11 lambdas as callbacks.
+
+ // Connect
+ bool connect_handler_invoked = false;
+ socket.asyncConnect(unixSocketFilePath(),
+ [&connect_handler_invoked](const boost::system::error_code& ec) {
+ connect_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!connect_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Send
+ const std::string outbound_data = "foo";
+ bool send_handler_invoked = false;
+ socket.asyncSend(outbound_data.c_str(), outbound_data.size(),
+ [&send_handler_invoked]
+ (const boost::system::error_code& ec, size_t /* length */) {
+ send_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!send_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+
+ // Receive
+ bool receive_handler_invoked = false;
+ std::array<char, 1024> read_buf;
+ socket.asyncReceive(&read_buf[0], read_buf.size(),
+ [&receive_handler_invoked]
+ (const boost::system::error_code& ec, size_t /* length */) {
+ receive_handler_invoked = true;
+ EXPECT_TRUE(ec);
+ });
+ while (!receive_handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// Check that native socket descriptor is returned correctly when
+// the socket is connected.
+TEST_F(UnixDomainSocketTest, getNative) {
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Setup client side.
+ UnixDomainSocket socket(io_service_);
+ ASSERT_NO_THROW(socket.connect(unixSocketFilePath()));
+ ASSERT_GE(socket.getNative(), 0);
+}
+
+// Check that protocol returned is 0.
+TEST_F(UnixDomainSocketTest, getProtocol) {
+ UnixDomainSocket socket(io_service_);
+ EXPECT_EQ(0, socket.getProtocol());
+}
+
+}
diff --git a/src/lib/asiolink/testutils/Makefile.am b/src/lib/asiolink/testutils/Makefile.am
new file mode 100644
index 0000000..cb06448
--- /dev/null
+++ b/src/lib/asiolink/testutils/Makefile.am
@@ -0,0 +1,93 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+TEST_CA_DIR = $(abs_srcdir)/ca
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST =
+EXTRA_DIST += ca/00af7a28.0
+EXTRA_DIST += ca/0c7eedb9.0
+EXTRA_DIST += ca/28f5a777.0
+EXTRA_DIST += ca/2eefa08b.0
+EXTRA_DIST += ca/71336a4d.0
+EXTRA_DIST += ca/7a5b785e.0
+EXTRA_DIST += ca/ad950210.0
+EXTRA_DIST += ca/doc.txt
+EXTRA_DIST += ca/ext-addr-conf.cnf
+EXTRA_DIST += ca/ext-conf.cnf
+EXTRA_DIST += ca/kea-ca.crt
+EXTRA_DIST += ca/kea-ca.key
+EXTRA_DIST += ca/kea-client.crt
+EXTRA_DIST += ca/kea-client.csr
+EXTRA_DIST += ca/kea-client.key
+EXTRA_DIST += ca/kea-client.p12
+EXTRA_DIST += ca/kea-other.crt
+EXTRA_DIST += ca/kea-other.key
+EXTRA_DIST += ca/kea-self.crt
+EXTRA_DIST += ca/kea-self.key
+EXTRA_DIST += ca/kea-server-addr.crt
+EXTRA_DIST += ca/kea-server-addr.csr
+EXTRA_DIST += ca/kea-server-raw.crt
+EXTRA_DIST += ca/kea-server-raw.csr
+EXTRA_DIST += ca/kea-server.crt
+EXTRA_DIST += ca/kea-server.csr
+EXTRA_DIST += ca/kea-server.key
+EXTRA_DIST += ca/server-addr-conf.cnf
+EXTRA_DIST += ca/server-conf.cnf
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libasiolinktest.la
+
+libasiolinktest_la_SOURCES = test_server_unix_socket.cc test_server_unix_socket.h
+libasiolinktest_la_SOURCES += timed_signal.cc timed_signal.h
+libasiolinktest_la_SOURCES += test_tls.cc test_tls.h
+
+libasiolinktest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libasiolinktest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libasiolinktest_la_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+libasiolinktest_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libasiolinktest_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libasiolinktest_la_LIBADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+if HAVE_OPENSSL
+# Boost ASIO SSL sample server and client for C++11.
+# https://www.boost.org/doc/libs/1_75_0/doc/html/boost_asio/example/cpp11/ssl/
+# openssl_sample_server <port>
+# openssl_sample_server <address> <port>
+
+noinst_PROGRAMS = openssl_sample_client openssl_sample_server
+
+openssl_sample_client_SOURCES = openssl_sample_client.cc
+openssl_sample_client_CPPFLAGS = $(AM_CPPFLAGS)
+openssl_sample_client_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+openssl_sample_client_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+openssl_sample_server_SOURCES = openssl_sample_server.cc
+openssl_sample_server_CPPFLAGS = $(AM_CPPFLAGS)
+openssl_sample_server_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+openssl_sample_server_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+endif
+
+if HAVE_BOTAN_BOOST
+# Same samples ported to Botan boost ASIO.
+
+noinst_PROGRAMS = botan_boost_sample_client botan_boost_sample_server
+
+botan_boost_sample_client_SOURCES = botan_boost_sample_client.cc
+botan_boost_sample_client_CPPFLAGS = $(AM_CPPFLAGS)
+botan_boost_sample_client_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+botan_boost_sample_client_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+botan_boost_sample_server_SOURCES = botan_boost_sample_server.cc
+botan_boost_sample_server_CPPFLAGS = $(AM_CPPFLAGS)
+botan_boost_sample_server_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+botan_boost_sample_server_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+endif
+endif
diff --git a/src/lib/asiolink/testutils/Makefile.in b/src/lib/asiolink/testutils/Makefile.in
new file mode 100644
index 0000000..b77ebc9
--- /dev/null
+++ b/src/lib/asiolink/testutils/Makefile.in
@@ -0,0 +1,1076 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_BOTAN_BOOST_FALSE@@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@noinst_PROGRAMS = openssl_sample_client$(EXEEXT) \
+@HAVE_BOTAN_BOOST_FALSE@@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ openssl_sample_server$(EXEEXT)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@noinst_PROGRAMS = botan_boost_sample_client$(EXEEXT) \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ botan_boost_sample_server$(EXEEXT)
+subdir = src/lib/asiolink/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libasiolinktest_la_DEPENDENCIES = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__libasiolinktest_la_SOURCES_DIST = test_server_unix_socket.cc \
+ test_server_unix_socket.h timed_signal.cc timed_signal.h \
+ test_tls.cc test_tls.h
+@HAVE_GTEST_TRUE@am_libasiolinktest_la_OBJECTS = libasiolinktest_la-test_server_unix_socket.lo \
+@HAVE_GTEST_TRUE@ libasiolinktest_la-timed_signal.lo \
+@HAVE_GTEST_TRUE@ libasiolinktest_la-test_tls.lo
+libasiolinktest_la_OBJECTS = $(am_libasiolinktest_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 =
+libasiolinktest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libasiolinktest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libasiolinktest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libasiolinktest_la_rpath =
+am__botan_boost_sample_client_SOURCES_DIST = \
+ botan_boost_sample_client.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am_botan_boost_sample_client_OBJECTS = botan_boost_sample_client-botan_boost_sample_client.$(OBJEXT)
+botan_boost_sample_client_OBJECTS = \
+ $(am_botan_boost_sample_client_OBJECTS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_client_DEPENDENCIES = \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1)
+botan_boost_sample_client_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(botan_boost_sample_client_LDFLAGS) $(LDFLAGS) -o $@
+am__botan_boost_sample_server_SOURCES_DIST = \
+ botan_boost_sample_server.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am_botan_boost_sample_server_OBJECTS = botan_boost_sample_server-botan_boost_sample_server.$(OBJEXT)
+botan_boost_sample_server_OBJECTS = \
+ $(am_botan_boost_sample_server_OBJECTS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_server_DEPENDENCIES = \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1)
+botan_boost_sample_server_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) \
+ $(botan_boost_sample_server_LDFLAGS) $(LDFLAGS) -o $@
+am__openssl_sample_client_SOURCES_DIST = openssl_sample_client.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am_openssl_sample_client_OBJECTS = openssl_sample_client-openssl_sample_client.$(OBJEXT)
+openssl_sample_client_OBJECTS = $(am_openssl_sample_client_OBJECTS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_client_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ $(am__DEPENDENCIES_1)
+openssl_sample_client_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(openssl_sample_client_LDFLAGS) \
+ $(LDFLAGS) -o $@
+am__openssl_sample_server_SOURCES_DIST = openssl_sample_server.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am_openssl_sample_server_OBJECTS = openssl_sample_server-openssl_sample_server.$(OBJEXT)
+openssl_sample_server_OBJECTS = $(am_openssl_sample_server_OBJECTS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_server_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ $(am__DEPENDENCIES_1)
+openssl_sample_server_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(openssl_sample_server_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)/botan_boost_sample_client-botan_boost_sample_client.Po \
+ ./$(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Po \
+ ./$(DEPDIR)/libasiolinktest_la-test_server_unix_socket.Plo \
+ ./$(DEPDIR)/libasiolinktest_la-test_tls.Plo \
+ ./$(DEPDIR)/libasiolinktest_la-timed_signal.Plo \
+ ./$(DEPDIR)/openssl_sample_client-openssl_sample_client.Po \
+ ./$(DEPDIR)/openssl_sample_server-openssl_sample_server.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 = $(libasiolinktest_la_SOURCES) \
+ $(botan_boost_sample_client_SOURCES) \
+ $(botan_boost_sample_server_SOURCES) \
+ $(openssl_sample_client_SOURCES) \
+ $(openssl_sample_server_SOURCES)
+DIST_SOURCES = $(am__libasiolinktest_la_SOURCES_DIST) \
+ $(am__botan_boost_sample_client_SOURCES_DIST) \
+ $(am__botan_boost_sample_server_SOURCES_DIST) \
+ $(am__openssl_sample_client_SOURCES_DIST) \
+ $(am__openssl_sample_server_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+TEST_CA_DIR = $(abs_srcdir)/ca
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+EXTRA_DIST = ca/00af7a28.0 ca/0c7eedb9.0 ca/28f5a777.0 ca/2eefa08b.0 \
+ ca/71336a4d.0 ca/7a5b785e.0 ca/ad950210.0 ca/doc.txt \
+ ca/ext-addr-conf.cnf ca/ext-conf.cnf ca/kea-ca.crt \
+ ca/kea-ca.key ca/kea-client.crt ca/kea-client.csr \
+ ca/kea-client.key ca/kea-client.p12 ca/kea-other.crt \
+ ca/kea-other.key ca/kea-self.crt ca/kea-self.key \
+ ca/kea-server-addr.crt ca/kea-server-addr.csr \
+ ca/kea-server-raw.crt ca/kea-server-raw.csr ca/kea-server.crt \
+ ca/kea-server.csr ca/kea-server.key ca/server-addr-conf.cnf \
+ ca/server-conf.cnf
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libasiolinktest.la
+@HAVE_GTEST_TRUE@libasiolinktest_la_SOURCES = \
+@HAVE_GTEST_TRUE@ test_server_unix_socket.cc \
+@HAVE_GTEST_TRUE@ test_server_unix_socket.h timed_signal.cc \
+@HAVE_GTEST_TRUE@ timed_signal.h test_tls.cc test_tls.h
+@HAVE_GTEST_TRUE@libasiolinktest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libasiolinktest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libasiolinktest_la_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+@HAVE_GTEST_TRUE@libasiolinktest_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(CRYPTO_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_client_SOURCES = openssl_sample_client.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_client_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_client_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_client_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_server_SOURCES = openssl_sample_server.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_server_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_server_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@openssl_sample_server_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_client_SOURCES = botan_boost_sample_client.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_client_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_client_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_client_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_server_SOURCES = botan_boost_sample_server.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_server_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_server_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@botan_boost_sample_server_LDADD = $(BOOST_LIBS) $(CRYPTO_LIBS)
+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/asiolink/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/asiolink/testutils/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
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libasiolinktest.la: $(libasiolinktest_la_OBJECTS) $(libasiolinktest_la_DEPENDENCIES) $(EXTRA_libasiolinktest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libasiolinktest_la_LINK) $(am_libasiolinktest_la_rpath) $(libasiolinktest_la_OBJECTS) $(libasiolinktest_la_LIBADD) $(LIBS)
+
+botan_boost_sample_client$(EXEEXT): $(botan_boost_sample_client_OBJECTS) $(botan_boost_sample_client_DEPENDENCIES) $(EXTRA_botan_boost_sample_client_DEPENDENCIES)
+ @rm -f botan_boost_sample_client$(EXEEXT)
+ $(AM_V_CXXLD)$(botan_boost_sample_client_LINK) $(botan_boost_sample_client_OBJECTS) $(botan_boost_sample_client_LDADD) $(LIBS)
+
+botan_boost_sample_server$(EXEEXT): $(botan_boost_sample_server_OBJECTS) $(botan_boost_sample_server_DEPENDENCIES) $(EXTRA_botan_boost_sample_server_DEPENDENCIES)
+ @rm -f botan_boost_sample_server$(EXEEXT)
+ $(AM_V_CXXLD)$(botan_boost_sample_server_LINK) $(botan_boost_sample_server_OBJECTS) $(botan_boost_sample_server_LDADD) $(LIBS)
+
+openssl_sample_client$(EXEEXT): $(openssl_sample_client_OBJECTS) $(openssl_sample_client_DEPENDENCIES) $(EXTRA_openssl_sample_client_DEPENDENCIES)
+ @rm -f openssl_sample_client$(EXEEXT)
+ $(AM_V_CXXLD)$(openssl_sample_client_LINK) $(openssl_sample_client_OBJECTS) $(openssl_sample_client_LDADD) $(LIBS)
+
+openssl_sample_server$(EXEEXT): $(openssl_sample_server_OBJECTS) $(openssl_sample_server_DEPENDENCIES) $(EXTRA_openssl_sample_server_DEPENDENCIES)
+ @rm -f openssl_sample_server$(EXEEXT)
+ $(AM_V_CXXLD)$(openssl_sample_server_LINK) $(openssl_sample_server_OBJECTS) $(openssl_sample_server_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libasiolinktest_la-test_server_unix_socket.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libasiolinktest_la-test_tls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libasiolinktest_la-timed_signal.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openssl_sample_client-openssl_sample_client.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openssl_sample_server-openssl_sample_server.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 $@ $<
+
+libasiolinktest_la-test_server_unix_socket.lo: test_server_unix_socket.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libasiolinktest_la_CPPFLAGS) $(CPPFLAGS) $(libasiolinktest_la_CXXFLAGS) $(CXXFLAGS) -MT libasiolinktest_la-test_server_unix_socket.lo -MD -MP -MF $(DEPDIR)/libasiolinktest_la-test_server_unix_socket.Tpo -c -o libasiolinktest_la-test_server_unix_socket.lo `test -f 'test_server_unix_socket.cc' || echo '$(srcdir)/'`test_server_unix_socket.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libasiolinktest_la-test_server_unix_socket.Tpo $(DEPDIR)/libasiolinktest_la-test_server_unix_socket.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_server_unix_socket.cc' object='libasiolinktest_la-test_server_unix_socket.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) $(libasiolinktest_la_CPPFLAGS) $(CPPFLAGS) $(libasiolinktest_la_CXXFLAGS) $(CXXFLAGS) -c -o libasiolinktest_la-test_server_unix_socket.lo `test -f 'test_server_unix_socket.cc' || echo '$(srcdir)/'`test_server_unix_socket.cc
+
+libasiolinktest_la-timed_signal.lo: timed_signal.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libasiolinktest_la_CPPFLAGS) $(CPPFLAGS) $(libasiolinktest_la_CXXFLAGS) $(CXXFLAGS) -MT libasiolinktest_la-timed_signal.lo -MD -MP -MF $(DEPDIR)/libasiolinktest_la-timed_signal.Tpo -c -o libasiolinktest_la-timed_signal.lo `test -f 'timed_signal.cc' || echo '$(srcdir)/'`timed_signal.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libasiolinktest_la-timed_signal.Tpo $(DEPDIR)/libasiolinktest_la-timed_signal.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='timed_signal.cc' object='libasiolinktest_la-timed_signal.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) $(libasiolinktest_la_CPPFLAGS) $(CPPFLAGS) $(libasiolinktest_la_CXXFLAGS) $(CXXFLAGS) -c -o libasiolinktest_la-timed_signal.lo `test -f 'timed_signal.cc' || echo '$(srcdir)/'`timed_signal.cc
+
+libasiolinktest_la-test_tls.lo: test_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libasiolinktest_la_CPPFLAGS) $(CPPFLAGS) $(libasiolinktest_la_CXXFLAGS) $(CXXFLAGS) -MT libasiolinktest_la-test_tls.lo -MD -MP -MF $(DEPDIR)/libasiolinktest_la-test_tls.Tpo -c -o libasiolinktest_la-test_tls.lo `test -f 'test_tls.cc' || echo '$(srcdir)/'`test_tls.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libasiolinktest_la-test_tls.Tpo $(DEPDIR)/libasiolinktest_la-test_tls.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_tls.cc' object='libasiolinktest_la-test_tls.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) $(libasiolinktest_la_CPPFLAGS) $(CPPFLAGS) $(libasiolinktest_la_CXXFLAGS) $(CXXFLAGS) -c -o libasiolinktest_la-test_tls.lo `test -f 'test_tls.cc' || echo '$(srcdir)/'`test_tls.cc
+
+botan_boost_sample_client-botan_boost_sample_client.o: botan_boost_sample_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(botan_boost_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT botan_boost_sample_client-botan_boost_sample_client.o -MD -MP -MF $(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Tpo -c -o botan_boost_sample_client-botan_boost_sample_client.o `test -f 'botan_boost_sample_client.cc' || echo '$(srcdir)/'`botan_boost_sample_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Tpo $(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='botan_boost_sample_client.cc' object='botan_boost_sample_client-botan_boost_sample_client.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) $(botan_boost_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o botan_boost_sample_client-botan_boost_sample_client.o `test -f 'botan_boost_sample_client.cc' || echo '$(srcdir)/'`botan_boost_sample_client.cc
+
+botan_boost_sample_client-botan_boost_sample_client.obj: botan_boost_sample_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(botan_boost_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT botan_boost_sample_client-botan_boost_sample_client.obj -MD -MP -MF $(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Tpo -c -o botan_boost_sample_client-botan_boost_sample_client.obj `if test -f 'botan_boost_sample_client.cc'; then $(CYGPATH_W) 'botan_boost_sample_client.cc'; else $(CYGPATH_W) '$(srcdir)/botan_boost_sample_client.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Tpo $(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='botan_boost_sample_client.cc' object='botan_boost_sample_client-botan_boost_sample_client.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) $(botan_boost_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o botan_boost_sample_client-botan_boost_sample_client.obj `if test -f 'botan_boost_sample_client.cc'; then $(CYGPATH_W) 'botan_boost_sample_client.cc'; else $(CYGPATH_W) '$(srcdir)/botan_boost_sample_client.cc'; fi`
+
+botan_boost_sample_server-botan_boost_sample_server.o: botan_boost_sample_server.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(botan_boost_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT botan_boost_sample_server-botan_boost_sample_server.o -MD -MP -MF $(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Tpo -c -o botan_boost_sample_server-botan_boost_sample_server.o `test -f 'botan_boost_sample_server.cc' || echo '$(srcdir)/'`botan_boost_sample_server.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Tpo $(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='botan_boost_sample_server.cc' object='botan_boost_sample_server-botan_boost_sample_server.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) $(botan_boost_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o botan_boost_sample_server-botan_boost_sample_server.o `test -f 'botan_boost_sample_server.cc' || echo '$(srcdir)/'`botan_boost_sample_server.cc
+
+botan_boost_sample_server-botan_boost_sample_server.obj: botan_boost_sample_server.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(botan_boost_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT botan_boost_sample_server-botan_boost_sample_server.obj -MD -MP -MF $(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Tpo -c -o botan_boost_sample_server-botan_boost_sample_server.obj `if test -f 'botan_boost_sample_server.cc'; then $(CYGPATH_W) 'botan_boost_sample_server.cc'; else $(CYGPATH_W) '$(srcdir)/botan_boost_sample_server.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Tpo $(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='botan_boost_sample_server.cc' object='botan_boost_sample_server-botan_boost_sample_server.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) $(botan_boost_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o botan_boost_sample_server-botan_boost_sample_server.obj `if test -f 'botan_boost_sample_server.cc'; then $(CYGPATH_W) 'botan_boost_sample_server.cc'; else $(CYGPATH_W) '$(srcdir)/botan_boost_sample_server.cc'; fi`
+
+openssl_sample_client-openssl_sample_client.o: openssl_sample_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(openssl_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT openssl_sample_client-openssl_sample_client.o -MD -MP -MF $(DEPDIR)/openssl_sample_client-openssl_sample_client.Tpo -c -o openssl_sample_client-openssl_sample_client.o `test -f 'openssl_sample_client.cc' || echo '$(srcdir)/'`openssl_sample_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openssl_sample_client-openssl_sample_client.Tpo $(DEPDIR)/openssl_sample_client-openssl_sample_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='openssl_sample_client.cc' object='openssl_sample_client-openssl_sample_client.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) $(openssl_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o openssl_sample_client-openssl_sample_client.o `test -f 'openssl_sample_client.cc' || echo '$(srcdir)/'`openssl_sample_client.cc
+
+openssl_sample_client-openssl_sample_client.obj: openssl_sample_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(openssl_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT openssl_sample_client-openssl_sample_client.obj -MD -MP -MF $(DEPDIR)/openssl_sample_client-openssl_sample_client.Tpo -c -o openssl_sample_client-openssl_sample_client.obj `if test -f 'openssl_sample_client.cc'; then $(CYGPATH_W) 'openssl_sample_client.cc'; else $(CYGPATH_W) '$(srcdir)/openssl_sample_client.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openssl_sample_client-openssl_sample_client.Tpo $(DEPDIR)/openssl_sample_client-openssl_sample_client.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='openssl_sample_client.cc' object='openssl_sample_client-openssl_sample_client.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) $(openssl_sample_client_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o openssl_sample_client-openssl_sample_client.obj `if test -f 'openssl_sample_client.cc'; then $(CYGPATH_W) 'openssl_sample_client.cc'; else $(CYGPATH_W) '$(srcdir)/openssl_sample_client.cc'; fi`
+
+openssl_sample_server-openssl_sample_server.o: openssl_sample_server.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(openssl_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT openssl_sample_server-openssl_sample_server.o -MD -MP -MF $(DEPDIR)/openssl_sample_server-openssl_sample_server.Tpo -c -o openssl_sample_server-openssl_sample_server.o `test -f 'openssl_sample_server.cc' || echo '$(srcdir)/'`openssl_sample_server.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openssl_sample_server-openssl_sample_server.Tpo $(DEPDIR)/openssl_sample_server-openssl_sample_server.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='openssl_sample_server.cc' object='openssl_sample_server-openssl_sample_server.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) $(openssl_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o openssl_sample_server-openssl_sample_server.o `test -f 'openssl_sample_server.cc' || echo '$(srcdir)/'`openssl_sample_server.cc
+
+openssl_sample_server-openssl_sample_server.obj: openssl_sample_server.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(openssl_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT openssl_sample_server-openssl_sample_server.obj -MD -MP -MF $(DEPDIR)/openssl_sample_server-openssl_sample_server.Tpo -c -o openssl_sample_server-openssl_sample_server.obj `if test -f 'openssl_sample_server.cc'; then $(CYGPATH_W) 'openssl_sample_server.cc'; else $(CYGPATH_W) '$(srcdir)/openssl_sample_server.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/openssl_sample_server-openssl_sample_server.Tpo $(DEPDIR)/openssl_sample_server-openssl_sample_server.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='openssl_sample_server.cc' object='openssl_sample_server-openssl_sample_server.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) $(openssl_sample_server_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o openssl_sample_server-openssl_sample_server.obj `if test -f 'openssl_sample_server.cc'; then $(CYGPATH_W) 'openssl_sample_server.cc'; else $(CYGPATH_W) '$(srcdir)/openssl_sample_server.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
+
+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: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/botan_boost_sample_client-botan_boost_sample_client.Po
+ -rm -f ./$(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Po
+ -rm -f ./$(DEPDIR)/libasiolinktest_la-test_server_unix_socket.Plo
+ -rm -f ./$(DEPDIR)/libasiolinktest_la-test_tls.Plo
+ -rm -f ./$(DEPDIR)/libasiolinktest_la-timed_signal.Plo
+ -rm -f ./$(DEPDIR)/openssl_sample_client-openssl_sample_client.Po
+ -rm -f ./$(DEPDIR)/openssl_sample_server-openssl_sample_server.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)/botan_boost_sample_client-botan_boost_sample_client.Po
+ -rm -f ./$(DEPDIR)/botan_boost_sample_server-botan_boost_sample_server.Po
+ -rm -f ./$(DEPDIR)/libasiolinktest_la-test_server_unix_socket.Plo
+ -rm -f ./$(DEPDIR)/libasiolinktest_la-test_tls.Plo
+ -rm -f ./$(DEPDIR)/libasiolinktest_la-timed_signal.Plo
+ -rm -f ./$(DEPDIR)/openssl_sample_client-openssl_sample_client.Po
+ -rm -f ./$(DEPDIR)/openssl_sample_server-openssl_sample_server.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) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/asiolink/testutils/botan_boost_sample_client.cc b/src/lib/asiolink/testutils/botan_boost_sample_client.cc
new file mode 100644
index 0000000..16a83e8
--- /dev/null
+++ b/src/lib/asiolink/testutils/botan_boost_sample_client.cc
@@ -0,0 +1,229 @@
+//
+// client.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <config.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <iostream>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/botan_boost_wrapper.h>
+#include <botan/asio_stream.h>
+#include <botan/certstor_flatfile.h>
+#include <botan/pkcs8.h>
+#include <botan/auto_rng.h>
+
+inline std::string CA_(const std::string& filename) {
+ return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+using boost::asio::ip::tcp;
+
+enum { max_length = 1024 };
+
+using Client_Certificate_Store = Botan::Flatfile_Certificate_Store;
+
+class Client_Credentials_Manager : public Botan::Credentials_Manager
+{
+public:
+ explicit Client_Credentials_Manager(Botan::RandomNumberGenerator& rng)
+ : stores_(), certs_(),
+ store_(new Client_Certificate_Store(CA_("kea-ca.crt"))),
+ cert_(Botan::X509_Certificate(CA_("kea-client.crt"))),
+ key_(Botan::PKCS8::load_key(CA_("kea-client.key"), rng))
+ {
+ stores_.push_back(store_.get());
+ certs_.push_back(cert_);
+ }
+
+ virtual ~Client_Credentials_Manager()
+ {
+ }
+
+ std::vector<Botan::Certificate_Store*>
+ trusted_certificate_authorities(const std::string&,
+ const std::string&) override
+ {
+ return stores_;
+ }
+
+ std::vector<Botan::X509_Certificate>
+ cert_chain(const std::vector<std::string>&,
+ const std::string&,
+ const std::string&) override
+ {
+ return certs_;
+ }
+
+ Botan::Private_Key*
+ private_key_for(const Botan::X509_Certificate&,
+ const std::string&,
+ const std::string&) override
+ {
+ return key_.get();
+ }
+
+ std::vector<Botan::Certificate_Store*> stores_;
+ std::vector<Botan::X509_Certificate> certs_;
+ std::shared_ptr<Botan::Certificate_Store> store_;
+ Botan::X509_Certificate cert_;
+ std::unique_ptr<Botan::Private_Key> key_;
+};
+
+using Client_Session_Manager = Botan::TLS::Session_Manager_Noop;
+
+class Client_Policy : public Botan::TLS::Default_Policy {
+public:
+ virtual ~Client_Policy()
+ {
+ }
+
+ std::vector<std::string> allowed_signature_methods() const override
+ {
+ return { "RSA", "ECDSA", "IMPLICIT" };
+ }
+
+ bool require_cert_revocation_info() const override
+ {
+ return false;
+ }
+};
+
+class client
+{
+public:
+ client(boost::asio::io_service& io_context,
+ Botan::TLS::Context& context,
+ const tcp::endpoint& endpoint)
+ : socket_(io_context, context)
+ {
+ connect(endpoint);
+ }
+
+private:
+ void connect(const tcp::endpoint& endpoint)
+ {
+ socket_.lowest_layer().async_connect(endpoint,
+ [this](const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ handshake();
+ }
+ else
+ {
+ std::cout << "Connect failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void handshake()
+ {
+ socket_.async_handshake(Botan::TLS::Connection_Side::CLIENT,
+ [this](const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ // Print the certificate's subject name.
+ const std::vector<Botan::X509_Certificate>& cert_chain =
+ socket_.native_handle()->peer_cert_chain();
+ for (auto const& cert : cert_chain) {
+ const Botan::X509_DN& subject = cert.subject_dn();
+ std::cout << "Verified " << subject.to_string() << "\n";
+ }
+
+ send_request();
+ }
+ else
+ {
+ std::cout << "Handshake failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void send_request()
+ {
+ std::cout << "Enter message: ";
+ std::cin.getline(request_, max_length);
+ size_t request_length = std::strlen(request_);
+
+ boost::asio::async_write(socket_,
+ boost::asio::buffer(request_, request_length),
+ [this](const boost::system::error_code& error, std::size_t length)
+ {
+ if (!error)
+ {
+ receive_response(length);
+ }
+ else
+ {
+ std::cout << "Write failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void receive_response(std::size_t length)
+ {
+ boost::asio::async_read(socket_,
+ boost::asio::buffer(reply_, length),
+ [this](const boost::system::error_code& error, std::size_t length)
+ {
+ if (!error)
+ {
+ std::cout << "Reply: ";
+ std::cout.write(reply_, length);
+ std::cout << "\n";
+ }
+ else
+ {
+ std::cout << "Read failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ Botan::TLS::Stream<tcp::socket> socket_;
+ char request_[max_length];
+ char reply_[max_length];
+};
+
+int main(int argc, char* argv[])
+{
+ try
+ {
+ if (argc != 3)
+ {
+ std::cerr << "Usage: client <addr> <port>\n";
+ return 1;
+ }
+
+ boost::asio::io_service io_context;
+
+ using namespace std; // For atoi.
+ tcp::endpoint endpoint(
+ boost::asio::ip::address::from_string(argv[1]), atoi(argv[2]));
+ Botan::AutoSeeded_RNG rng;
+ Client_Credentials_Manager creds_mgr(rng);
+ Client_Session_Manager sess_mgr;
+ Client_Policy policy;
+ Botan::TLS::Context ctx(creds_mgr, rng, sess_mgr, policy);
+
+ client c(io_context, ctx, endpoint);
+
+ io_context.run();
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Exception: " << e.what() << "\n";
+ }
+
+ return 0;
+}
diff --git a/src/lib/asiolink/testutils/botan_boost_sample_server.cc b/src/lib/asiolink/testutils/botan_boost_sample_server.cc
new file mode 100644
index 0000000..9d7e8c5
--- /dev/null
+++ b/src/lib/asiolink/testutils/botan_boost_sample_server.cc
@@ -0,0 +1,220 @@
+//
+// server.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <config.h>
+
+#include <cstdlib>
+#include <functional>
+#include <iostream>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/botan_boost_wrapper.h>
+#include <botan/asio_stream.h>
+#include <botan/certstor_flatfile.h>
+#include <botan/pkcs8.h>
+#include <botan/auto_rng.h>
+
+inline std::string CA_(const std::string& filename) {
+ return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+using boost::asio::ip::tcp;
+
+using Server_Certificate_Store = Botan::Flatfile_Certificate_Store;
+
+class Server_Credentials_Manager : public Botan::Credentials_Manager
+{
+public:
+ explicit Server_Credentials_Manager(Botan::RandomNumberGenerator& rng)
+ : stores_(), certs_(),
+ store_(new Server_Certificate_Store(CA_("kea-ca.crt"))),
+ cert_(Botan::X509_Certificate(CA_("kea-server.crt"))),
+ key_(Botan::PKCS8::load_key(CA_("kea-server.key"), rng))
+ {
+ stores_.push_back(store_.get());
+ certs_.push_back(cert_);
+ }
+
+ virtual ~Server_Credentials_Manager()
+ {
+ }
+
+ std::vector<Botan::Certificate_Store*>
+ trusted_certificate_authorities(const std::string&,
+ const std::string&) override
+ {
+ return stores_;
+ }
+
+ std::vector<Botan::X509_Certificate>
+ cert_chain(const std::vector<std::string>&,
+ const std::string&,
+ const std::string&) override
+ {
+ return certs_;
+ }
+
+ Botan::Private_Key*
+ private_key_for(const Botan::X509_Certificate&,
+ const std::string&,
+ const std::string&) override
+ {
+ return key_.get();
+ }
+
+ std::vector<Botan::Certificate_Store*> stores_;
+ std::vector<Botan::X509_Certificate> certs_;
+ std::shared_ptr<Botan::Certificate_Store> store_;
+ Botan::X509_Certificate cert_;
+ std::unique_ptr<Botan::Private_Key> key_;
+};
+
+using Server_Session_Manager = Botan::TLS::Session_Manager_Noop;
+
+class Server_Policy : public Botan::TLS::Default_Policy {
+public:
+ virtual ~Server_Policy()
+ {
+ }
+
+ std::vector<std::string> allowed_signature_methods() const override
+ {
+ return { "RSA", "ECDSA", "IMPLICIT" };
+ }
+
+ bool require_cert_revocation_info() const override
+ {
+ return false;
+ }
+};
+
+class session : public std::enable_shared_from_this<session>
+{
+public:
+ session(tcp::socket socket, Botan::TLS::Context& ctx)
+ : socket_(std::move(socket), ctx)
+ {
+ }
+
+ void start()
+ {
+ do_handshake();
+ }
+
+private:
+ void do_handshake()
+ {
+ auto self(shared_from_this());
+ socket_.async_handshake(Botan::TLS::Connection_Side::SERVER,
+ [this, self](const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ do_read();
+ }
+ else
+ {
+ std::cerr << "handshake failed with " << error.message() << "\n";
+ }
+ });
+ }
+
+ void do_read()
+ {
+ auto self(shared_from_this());
+ socket_.async_read_some(boost::asio::buffer(data_),
+ [this, self](const boost::system::error_code& ec, std::size_t length)
+ {
+ if (!ec)
+ {
+ do_write(length);
+ }
+ });
+ }
+
+ void do_write(std::size_t length)
+ {
+ auto self(shared_from_this());
+ boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
+ [this, self](const boost::system::error_code& ec,
+ std::size_t /*length*/)
+ {
+ if (!ec)
+ {
+ do_read();
+ }
+ });
+ }
+
+ Botan::TLS::Stream<tcp::socket> socket_;
+ char data_[1024];
+};
+
+class server
+{
+public:
+ server(boost::asio::io_service& io_context,
+ unsigned short port,
+ Botan::Credentials_Manager& creds_mgr,
+ Botan::RandomNumberGenerator& rng,
+ Botan::TLS::Session_Manager& sess_mgr,
+ Botan::TLS::Policy& policy)
+ : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
+ context_(creds_mgr, rng, sess_mgr, policy)
+ {
+ do_accept();
+ }
+
+private:
+ void do_accept()
+ {
+ acceptor_.async_accept(
+ [this](const boost::system::error_code& error, tcp::socket socket)
+ {
+ if (!error)
+ {
+ std::make_shared<session>(std::move(socket), context_)->start();
+ }
+
+ do_accept();
+ });
+ }
+
+ tcp::acceptor acceptor_;
+ Botan::TLS::Context context_;
+};
+
+int main(int argc, char* argv[])
+{
+ try
+ {
+ if (argc != 2)
+ {
+ std::cerr << "Usage: server <port>\n";
+ return 1;
+ }
+
+ boost::asio::io_service io_context;
+
+ Botan::AutoSeeded_RNG rng;
+ Server_Credentials_Manager creds_mgr(rng);
+ Server_Session_Manager sess_mgr;
+ Server_Policy policy;
+ server s(io_context, std::atoi(argv[1]), creds_mgr, rng, sess_mgr, policy);
+
+ io_context.run();
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Exception: " << e.what() << "\n";
+ }
+
+ return 0;
+}
diff --git a/src/lib/asiolink/testutils/ca/00af7a28.0 b/src/lib/asiolink/testutils/ca/00af7a28.0
new file mode 100644
index 0000000..5d7534d
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/00af7a28.0
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHDCCAgSgAwIBAgIUe1AyLcAeSfKwCZNZLFTRkWMyOJQwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoM
+CElTQyBJbmMuMREwDwYDVQQDDAhrZWEtc2VsZjAeFw0yMTAzMDIxNDQ3MDdaFw0z
+MTAyMjgxNDQ3MDdaMEgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRl
+MREwDwYDVQQKDAhJU0MgSW5jLjERMA8GA1UEAwwIa2VhLXNlbGYwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+
+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3u
+tPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iP
+j7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZz
+SnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4
+ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBAHWFX55xUt1Opqtji+I2XvBrcexleSAME+irKwExe+tY
+laFEWb1eWyzFHiuOSuNLjcXt1PkUYZ0lYUg17cDj5urpAy+F07uCRQWTXBY8W53H
+IppYl4KjN3w4e5DSyDfiTv99MT8xVKJk+rVu75lQ0kgg68fZR6yK82SLjBQmjV2A
+OcSqHNHtnBU5RcdlZ+E05M1Vo1jHzxHpybkgNxjvmUgBRc9ieLbgSFRZji0nNmhA
+TSZ0DjRce6eyDI+OoEFJL0wXMl0ZOijeuCJr4C45h3TyreU2COC1GaoIeNwmGSIb
+mw0j+XR4rKHcgkUQ7L2DfwOjGFG7IeT+k0QdyeM2NU4=
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/0c7eedb9.0 b/src/lib/asiolink/testutils/ca/0c7eedb9.0
new file mode 100644
index 0000000..3476032
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/0c7eedb9.0
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+DCCAeCgAwIBAgIBFDANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDEy
+N1oXDTMxMDIyODE1MDEyN1owNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRMwEQYDVQQDDAprZWEtc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMNzlab
+xu20h82Y/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oyCiWF
+pJLJ8WOF4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5GJtQ
+iJGapgWRbeyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKCj+IL
+MS46wYjjHTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9dNZB5
+2U9QkyPFHKrnNn400B/xBGNKoyTSYbLQEwIDAQABoxgwFjAUBgNVHREEDTALggls
+b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAKYtC4/KKZnTktvWankLnlVact5K
+L0bJT4qCDg/0gj0pj3rofqyOEoGIjZssQtAG/wmJNF6gNisX/1F23BdEdPAsOJQv
+KuRwr4zL3uj2Mkz585Or/iz633LnD8Ibv8KQsKLnJ/UnJikeH5UgxqcU9kA7ymAE
+pzilP23p3bINvyBMwWZUzT3CsYB7PrcRzx3ScZhbhYaN0f8lq83nspXr8U3FyH5U
+NkrgpuqIE9dFPiaY4CsjNIISpYANcVeWwyPKMk/uty3KbzbmDr7ssm1u1MyJjeVP
+jE/Dhq+WTbDGMfqR3gyXBWq7b1ROA7tk9kAMQg91PLAELSB6lRmzfxzrH/wYk6E/
+0gHgpznpDcA68uW/54eX8phJQQp7Ak7csElXjqXDJ1AWA8VVjRXHerOkq0cUWply
+YsJQCkx3jKdLDFfjtKZWVOjc9rGCnph4BfUej/Lt7z7tTr/Yh+oAR+UyowRzdZM/
+RSsui8vVbvKU+bRlyB5qmNR8cSI5oEA+kAs5DXK2bh5v1SGSxVjwKuwwLeu8eCr3
+HUYQMxKi7Y15+BqjbrOZCEfHE4WORkKze1dh9U/UU9h+LVd+TB7jprZc3ZOvuqYP
+Bb+ponHJJaRvHUKD/jL8kHQ7KX79wXNVkrevGcPe8qE1X/xu4ChK5PuDzq2HQPLs
+USYWw/aARNwslhV6
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/28f5a777.0 b/src/lib/asiolink/testutils/ca/28f5a777.0
new file mode 100644
index 0000000..bdcc9bd
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/28f5a777.0
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID4DCCAcigAwIBAgIBFDANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJVUzER
+MA8GA1UECgwISVNDIEluYy4xETAPBgNVBAMMCG90aGVyLWNhMB4XDTIxMDMwMjE0
+NTI0OFoXDTMxMDIyODE0NTI0OFowNDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElT
+QyBJbmMuMRIwEAYDVQQDDAlrZWEtb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xm
+leGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG
++R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k
+2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbB
+CAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcU
+CUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJKoZIhvcNAQELBQAD
+ggIBAMYcxVfoCIn+NPlsoRB2m5vAOuJTuBNigf8Fm0HYougE2W+p50+5USx2BCM8
+M1Cet+8X0dktHbRdDL5aZrRbYnz/OENBD4tKuWMQoP/qzafRiKSkDckxYM6AR4T+
+fzPgLjUde2NE1cDeRlJUmereRXiD2qefEFH55StLl8YnnciAMGTRjwBuLiReF+qE
+noaD8ZIKZ5pBMzoxyOe+39tLJkzhESdZ8gJZRXGm+ickAlP96w8z8TlQiWHG3Caw
+kM7SZSyVYdyfiF32J6A7hwlG3qud83GcunfrjOurWBe1lv51pb/OFGe6wlRD/pcS
+UcKZ07KXXYMXV40O6A5Dv0yJB8ocKhOkfU5MvotAAm2GL2ZXizfmEAz23X9I8830
+B5ggVxgp/bO/exC1sBJjUgF4qVPByE1MdDDWYvPKT8cYg5j8pD9rDn7WGVAmgCk9
+59lEI0HBP33ulBRoxrOQ7kV3pUlV8oP3wG/joz8PwSNAbbtQuUnAmjElONPyTrMN
+2Yqah89SqH9ygzz/UomdrKYuoTu/QEfLLtBcyBLKHrRT8ODvsp2kY9RpveCctsAR
+2gmnYixj7GDdp5c6zTich1+QkVvFtrl3Zu+AWRekFAn92bwwOli14S3LgW2t4iXL
+InVUqNg6l6K9d+FdHogvITQLKKMpfIfsCKPqvacpqryyaith
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/2eefa08b.0 b/src/lib/asiolink/testutils/ca/2eefa08b.0
new file mode 100644
index 0000000..e5762cd
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/2eefa08b.0
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFMzCCAxugAwIBAgIJAJHdRK24tsELMA0GCSqGSIb3DQEBCwUAMDAxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQKDAdJU0MgSW5jMQ8wDQYDVQQDDAZrZWEtY2EwHhcNMjEw
+MzAyMTQ1OTM3WhcNMzEwMjI4MTQ1OTM3WjAwMQswCQYDVQQGEwJVUzEQMA4GA1UE
+CgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMIICIjANBgkqhkiG9w0BAQEFAAOC
+Ag8AMIICCgKCAgEAvKQ/vJpJnXjZ+/LxZNfPc/QYSChSEQ8qoxh8prBYvPXyDu9O
+RHOaDtd5AWusQLCI3iNYMDaJwrazj0g91jPKcxfvFZbnzFHTAZrDnmJwcTw96Ufr
+P4b7PyXpUSF1/YfDf+/M3C7Wm9IJ/e704XHln/vFCw2dR/N5VOrXXJRcCd5NOES/
+ICXexe62Mv7OjUQS8u6ovejtaaMkvoV2hGSG2LXdgVOCv0U8ybRs03Xl8BVM4lFY
+VO9HjnQ7O9AeGMqebvuyNAyGK9Dv+ERu65M9hB+pW//d+tVv3Dkfou+d5cOXPFXj
+f6vIK+2ClxkBH4A5dhsRJ7vPI41mwXA+H0g+MzxJ8Lg0pzJuLher03RZq3pBHvEc
+/jekP4u6mPrc+5J84jQ0hFwH4XIpxaKJsUiE/r1nFDiWRV27PgXMQgEbjdotxFX4
+IDBNKPtQNrybxiQHsYoZPdKcEfh8XyVT4NHrcbqN1SNf2ZIfDkm09aeDYXDdINAD
++0yZE+3YMeH4oWPpOIfW4OVzEDyfBGHyo2klTZfI5zdd54Kp4dKkzSlmIPC7Oubd
+ZZGoSlZfUlWVcRkqMbUAsZ8H2sdz0l+4k8+VmyiA4EWAiO6SV5xmYSncPQIN5dE2
+PbIxjKosl9JGhajs2gxCqlK+ZA3zgoFHhG1mKGWW7ucMic8Jy4oEq1XsoI0CAwEA
+AaNQME4wHQYDVR0OBBYEFA2rYljxKlzKLA/dsiAmRtO876ifMB8GA1UdIwQYMBaA
+FA2rYljxKlzKLA/dsiAmRtO876ifMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAGqY1cv913Hj1+FDmD5fhzW6D/SeyL/vh3bCJ6ZJmnHFXxHZbK4lufdi
+v3HRJ4iSPnU40ZWVukWE+vKrZOJeBM2ip/cqv8iAiZg2NaQ56AcDgrpOfJcXOJzD
+83kZI8W3dF/zk1flJM3rsi5QlwkCaxBvwA+QInejN+oncA90CphumNqblPQp1Ifm
+dt+b1BIk6QJFYT0oEXnNj+5EmSu+zJ+fR5bJoZX0YTcP6YAHjdZo2qAHTeM6yX8s
+bLnX97IopyPZ/xgG2kdlp2TZZdeysaICOZ16LldE7fp2OD2ifjrAqF9eezwa2ybi
+wNhduRUn0Nmuw/Vy3X5l3gUekc3mS9br8ooHy6N+8pnq04gGWK3AAZLY5v7uvzmD
+BC6eA0IJAvLyeiuTpBlkHZTFxk7ENaStEMFjvPiLrgquHLmJQzsgKoUtR7TGdEJ+
+DOeLAhuXjpaZ/kefSODmm09BP0d/q3iFU3gp1xGu2svUK0/BC6NQNuTIIap+L/I+
+tKq+SpPpp7laJ7M04TqAlI+EMQ4KFRDbmlWAy5uq/ynEpEJ1FFuyg6Zo+fxracTR
+ytP3p/LUEYl1VQbtn9IEcrkzZNEshBglRSJ09u1nLccy3WoX03P0iQiF4oNCEPMg
+PdPlvvf1t3FbcEn5AFOsMRW4U7MBPD/gvy0EVuEJ/boydq8qMzyi
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/71336a4d.0 b/src/lib/asiolink/testutils/ca/71336a4d.0
new file mode 100644
index 0000000..34f3392
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/71336a4d.0
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID4jCCAcqgAwIBAgIBHjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMyMjEyNTcw
+MFoXDTMxMDMyMDEyNTcwMFowOTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRcwFQYDVQQDDA5rZWEtc2VydmVyLXJhdzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAL3kUYCDdEvKHgV1WiBJ7BtQyrg8yiqi6C3QUDX6GayswyyD
+Dc5Wm8bttIfNmPzlNNhHRM2q/fDrLc4JSFopihVSNDpotALop1JL/euNLY6DtINK
+MgolhaSSyfFjheI+75mVFrkkt1bYZQfjC1pTmecwAsj8lj6OGdp6B2mI34kMQR/K
++RibUIiRmqYFkW3siPpvN+244pqc+ldQBnmx73/s3dS4/swznM6d/D6iBlG5gL3y
+go/iCzEuOsGI4x07whuVkghv0pJHq9hwCTNnDXBD5kKgDqQJJhUNhuOm1weKwV4f
+XTWQedlPUJMjxRyq5zZ+NNAf8QRjSqMk0mGy0BMCAwEAATANBgkqhkiG9w0BAQsF
+AAOCAgEArTCCoN7IKQ1g9PqrCeZe0sFOPmL8tEfg83bdTnOUF1rcaK5b3E/ktuT2
+b4axEOTLo8OdwBFFdGHn7XcXAWEx9mVeEw3J1X4143FfhzwnU5ZfLvgKx3yY22ZO
+9WUf0sT35aEH8jS9OzqeaGqkgNufCrmNG5TBXnTG8iFVVKqxdaI9EpoiXjLJwOi1
+5ZO3iB04saPPekVA+u0nngG+sx30hjpNu8EDl9u5f04B0cE3iZSvc4/DN4GDBjIn
+eHzAwlP++mDTQ6d9K8h9BRNnqXBwdN+6CbTTB3Mw5DlvHxBSXRf9xIuhWEdiT7kQ
+Ac7tTs9qsC+g56j3N526hVegbnhB9SSlO1gNWhKdWoag51TJQP38d7lrD6YhJIVi
+57idCeEfvGNcrIMr7hbn6nm8q1nd8waE2dX0FMm3WCf3Nj8Zpsj8JxnQj3jQ/Q38
+bHoHVtAvc7W7tAzMHl5R7UufEqP/42lnes4DECQ5WvN+t9l5gErO4svHfeXNFGbM
+nbjVxGeJeiRPGriej8dlD5Ea0WVHOETh77+5p7DdDBir/xLHSbS/QypKnTGixhwB
+Zg5z8CHeepVf5Y+xhteOZwJCjxCTwW43aOEHQ0U7gHke2hNtCagwlbmLBITzJMJL
+HIFvpHfNTLX1ZRU/z/3OJVEfuMRjah5BJZPGuhuJxR47hP0tLJY=
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/7a5b785e.0 b/src/lib/asiolink/testutils/ca/7a5b785e.0
new file mode 100644
index 0000000..29ff5e5
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/7a5b785e.0
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID3TCCAcWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDAz
+M1oXDTMxMDIyODE1MDAzM1owNDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0lTQyBJ
+bmMxEzARBgNVBAMMCmtlYS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDKbsDkElojvFhVt234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfz
+G1k16qMB5jXF7dRhzq4FiPbZMs5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PE
+l47e4Ae3Cl96dWUfQKQmGIzzHfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyG
+JKXSuqCsaXBRK7Amn8/jxMgdhds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJS
+RKaG9hVRrAe4GPisOjUzerADkPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zq
+qTgTp1MvttSv8JEN6OMhM+bpCbaiWp4DAgMBAAEwDQYJKoZIhvcNAQELBQADggIB
+AENl7hCBjAft1uC/XAO/yBkkDrTk6R21+mdJMghJ9ojFP33QvYYv0pDNeCZ/IJEK
+G2ML8gFzd2YulF1qzBMuFvESRQyqJMnIWJS8FSEIKEyqj5RMTnVWjFM6V2yGhBA5
+XXAL4CVVNz/NqWV/Ebd1XB1OB/y5uz+ZowpWktHtqCKYhDzDtK600GswMOJ5UsZF
+X6JtkvG86nVfuyOIK3NtMXQE/ptAgwa87hVecu7yY/u6PmRwS7YbVBsh9VplnAsQ
+bLARtTGCWHL3otZaDi81dghHkHYmv1NmaubgKnFffKxJGLCtyHF0pqS7C0v7lLOo
+qOhSd3qaFEU1yWpXCFlyglDnadFQs8pdWIPBngwQC2luF1N7Kppz5zzGF5MHNt+E
+LuPlRAwgs8aRRPsySGYKvtCeNYAgjsbec9f0P7lMEGr+AqbZF9qNbbQQkq0dHrMH
+goazCek3XtlMAYYUdmkqQ5a44XRQUu4FuTVqzCH8nqhkeHcWTwO9BHayUebxiBk8
+njDwLtHiQ8u9TjVf/35UOdqFSxra+wZJPKYbH++82KG6rbEotGp3jv0uxasgiHVL
+qrD3dkQAU8zF7cllsUkRE3Gw4tDaZXkZCawiMfLiGK1FVApXkUnKilASDsaH6i3x
+Ui8LM1F9vbtJnzftTa3yi0FR6Gmi5Mc+R42gpE8xCa4y
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/ad950210.0 b/src/lib/asiolink/testutils/ca/ad950210.0
new file mode 100644
index 0000000..2332046
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/ad950210.0
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECjCCAfKgAwIBAgIBHjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDE0
+OVoXDTMxMDIyODE1MDE0OVowOjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRgwFgYDVQQDDA9rZWEtc2VydmVyLWFkZHIwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQC95FGAg3RLyh4FdVogSewbUMq4PMoqougt0FA1+hmsrMMs
+gw3OVpvG7bSHzZj85TTYR0TNqv3w6y3OCUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SD
+SjIKJYWkksnxY4XiPu+ZlRa5JLdW2GUH4wtaU5nnMALI/JY+jhnaegdpiN+JDEEf
+yvkYm1CIkZqmBZFt7Ij6bzftuOKanPpXUAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC9
+8oKP4gsxLjrBiOMdO8IblZIIb9KSR6vYcAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFe
+H101kHnZT1CTI8Ucquc2fjTQH/EEY0qjJNJhstATAgMBAAGjJTAjMCEGA1UdEQQa
+MBiHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggIBAAaf
+GIHwgnSo4zo6cIfpzirVpSqjzOrsAqzSswigZdj7dwx959sgSJzZssDf/TA98iXM
+YQEkBao6jPuo8fTlCF0XGCUGAfq/f6Yn1Nhkk0qUdxLrNsEjKPXjISZPaVZllZBR
++mRMKObn0l86vJ/0zGzPRxH2P5CKg9g3sT8zkg1fGIE/SNr8abZV5Cf3spYQ9PF9
+zQ2TdpgaEGGufKR6VAIJH4CVShMfvBF0qFbzMC7R/CTdSvEBXagWclBT7PqcVGlV
+rK/NB6rt8W8hLQQE6bRunJmkLrmLKLVjFtPZPq5hm3jE8fnGxfzvThiZHTj+oFGw
+KXcbuSvwgYuLKym648V+VDGiDWdpS2dIwQi2JeHTt7Y4P+8dqPfHY7oDy2+67J6o
+ElTXvloGVNCedQtpp9gNrtil5avXrU9HCfD9avYlsn89kqYZ3Ht1GBYPyqeSZDCo
+a+sffazhYPfqFdH0U7wpq6Gf8/JMSAuQmAR2UAwhjoQatqDqEJ3pAFsI3YcQOZqm
+kj3/T0iYkU8YdJkxI2YgVCRRIzTKHkGMVc/iz+C0OJwFeJDuj+dj+EXXtyi3sjhL
+oTQT2y01nW2TPrHqlG3/fQyPx1gKXrij+1uOZJpZcgKE7/YBGByRiUdOyRJ0E6h6
+oimhTLT6mC9wteMiRmj68z5tTC1P0H4nuOU7OqwL
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/doc.txt b/src/lib/asiolink/testutils/ca/doc.txt
new file mode 100644
index 0000000..fc88a6f
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/doc.txt
@@ -0,0 +1,129 @@
+Similar to doc/examples/https/nginx/kea-nginx.conf
+ password is keatest
+ Country Name is US
+ Organization Name is ISC Inc.
+ Common Name is the key name.
+
+Some critical details:
+ - recent versions of OpenSSL requires at least 2038 bit RSA
+ - certificate version should be 3 (enforced by Botan for leaves),
+ if openssl creates a version 1 add an extension
+ - RSA allows a simpler format than PKCS#8 for RSA private keys
+ but Botan and other algorithms require PKCS#8
+ - some tools check the alternate subject name of the server so put
+ a correct value in it
+
+Files:
+ - doc.txt this file
+ - ext-addr-conf.cnf extension definition file to add an IP address subject
+ alternative name to the server certificate (IP 127.0.0.1)
+ - ext-conf.cnf extension definition file to add a subject alternative
+ name to the server certificate (DNS localhost)
+ - kea-ca.crt Certification Authority (CA) certificate
+ - kea-ca.key Certification Authority (CA) private key (password keatest)
+ - kea-client.crt client certificate
+ - kea-client.csr client PKCS#10 certificate request
+ - kea-client.key client private key (not encrypted)
+ - kea-client.p12 client PKCS#12 archive with the certificate and the private
+ key (required by curl on macOS or iOS when built with Secure Transport)
+ - kea-other.crt test client certificate (signed by another CA)
+ - kea-other.key test client private key (signed by another CA, not encrypted)
+ - kea-self.crt test client certificate (self-signed)
+ - kea-self.key test client private key (self-signed, not encrypted)
+ - kea-server-addr.crt server certificate using the 127.0.0.1 IP address
+ - kea-server-addr.csr server PKCS#10 certificate request using the
+ 127.0.0.1 IP address
+ - kea-server-raw.crt server certificate with no subject alternative name
+ - kea-server-raw.csr server PKCS#10 certificate request using no
+ subject alternative name
+ - kea-server.crt server certificate using the localhost DNS name
+ - kea-server.csr server PKCS#10 certificate request using the localhost
+ DNS name
+ - kea-server.key server private key (all certificates, not encrypted)
+ - server-addr-conf.cnf OpenSSL configuration file to add an IP address
+ subject alternative name (127.0.0.1 and ::1)
+ - server-conf.cnf OpenSSL configuration file to add a DNS subject
+ alternative name (localhost)
+
+NOTE: On some systems, the openssl pkcs8 commands require -topk8 parameter.
+
+Procedure to build CA, server and client files:
+
+1 - create a CA self signed certificate (password is keatest)
+ openssl genrsa -aes128 -out kea-ca.key 4096
+ openssl req -new -x509 -days 3650 -key kea-ca.key -out kea-ca.crt \
+ -extensions v3_ca -config server-conf.cnf
+
+2 - create a key for the client and convert to PKCS#8
+ openssl genrsa -aes128 -out kea-client-aes.key 2048
+ openssl pkcs8 -in kea-client-aes.key -out kea-client.key -nocrypt
+ rm kea-client-aes.key
+
+3 - create a certificate for the client
+ openssl req -new -key kea-client.key -out kea-client.csr
+ openssl x509 -req -days 3650 -in kea-client.csr -CA kea-ca.crt \
+ -CAkey kea-ca.key -set_serial 10 -out kea-client.crt \
+ -extfile /dev/null -sha256
+
+4 - create a PKCS#12 bundle on macOS (password is keatest)
+ openssl pkcs12 -in kea-client.crt -inkey kea-client.key -export \
+ -out kea-client.p12
+
+5 - create a key for the server and convert to PKCS#8 (same than 2)
+ openssl genrsa -aes128 -out kea-server-aes.key 2048
+ openssl pkcs8 -in kea-server-aes.key -out kea-server.key -nocrypt
+ rm kea-server-aes.key
+
+6 - create a certificate with a subject alternate name set to localhost
+ for the server
+ openssl req -new -key kea-server.key -out kea-server.csr \
+ -config server-conf.cnf
+ openssl x509 -req -days 3650 -in kea-server.csr -CA kea-ca.crt \
+ -CAkey kea-ca.key -set_serial 20 -out kea-server.crt \
+ -extfile ext-conf.cnf -sha256
+
+7 - create a certificate with a subject alternate name set to 127.0.0.1
+ and ::1 for the server
+ openssl req -new -key kea-server.key -out kea-server-addr.csr \
+ -config server-addr-conf.cnf
+ openssl x509 -req -days 3650 -in kea-server-addr.csr -CA kea-ca.crt \
+ -CAkey kea-ca.key -set_serial 30 -out kea-server-addr.crt \
+ -extfile ext-addr-conf.cnf -sha256
+
+8 - use c_rehash or openssl rehash to create hashes
+ openssl rehash .
+
+Setup the control agent: kea-ctrl-agent.json sample.
+
+Using curl.
+Note the localhost is important: using 127.0.0.1 instead can make the
+subjectAltName check to fail. curl is also picky about http vs https.
+
+to send a command (e.g. list-commands) directly to the control agent
+listening at port 8000:
+
+curl -D - -X POST -H Content-Type:application/json \
+ -d '{ "command": "list-commands" }' http://localhost:8000
+
+With the CA only (so authenticating the server only):
+curl -D - -X POST -H Content-Type:application/json --cacert kea-ca.crt \
+ -d '{ "command": "list-commands" }' https://localhost:8443
+
+With mutual authentication using OpenSSL:
+curl -D - -X POST -H Content-Type:application/json \
+ --cacert kea-ca.crt --cert kea-client.crt --key kea-client.key \
+
+With the mutual authentication on macOS (when the OpenSSL one fails):
+curl -D - -X POST -H Content-Type:application/json \
+ --cacert kea-ca.crt --cert kea-client.p12:keatest --cert-type P12 \
+ -d '{ "command": "list-commands" }' https://localhost:8443
+
+To the control agent:
+echo | kea-shell
+
+With server authentication only:
+echo | kea-shell --ca kea-ca.crt --port 8443 --host localhost
+
+With the mutual authentication:
+echo | kea-shell --ca kea-ca.crt --port 8443 --host localhost \
+ --cert kea-client.crt --key kea-client.key
diff --git a/src/lib/asiolink/testutils/ca/ext-addr-conf.cnf b/src/lib/asiolink/testutils/ca/ext-addr-conf.cnf
new file mode 100644
index 0000000..a6b78c1
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/ext-addr-conf.cnf
@@ -0,0 +1 @@
+subjectAltName=IP:127.0.0.1,IP:::1
diff --git a/src/lib/asiolink/testutils/ca/ext-conf.cnf b/src/lib/asiolink/testutils/ca/ext-conf.cnf
new file mode 100644
index 0000000..aafe5bd
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/ext-conf.cnf
@@ -0,0 +1 @@
+subjectAltName=DNS:localhost
diff --git a/src/lib/asiolink/testutils/ca/kea-ca.crt b/src/lib/asiolink/testutils/ca/kea-ca.crt
new file mode 100644
index 0000000..e5762cd
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-ca.crt
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFMzCCAxugAwIBAgIJAJHdRK24tsELMA0GCSqGSIb3DQEBCwUAMDAxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQKDAdJU0MgSW5jMQ8wDQYDVQQDDAZrZWEtY2EwHhcNMjEw
+MzAyMTQ1OTM3WhcNMzEwMjI4MTQ1OTM3WjAwMQswCQYDVQQGEwJVUzEQMA4GA1UE
+CgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMIICIjANBgkqhkiG9w0BAQEFAAOC
+Ag8AMIICCgKCAgEAvKQ/vJpJnXjZ+/LxZNfPc/QYSChSEQ8qoxh8prBYvPXyDu9O
+RHOaDtd5AWusQLCI3iNYMDaJwrazj0g91jPKcxfvFZbnzFHTAZrDnmJwcTw96Ufr
+P4b7PyXpUSF1/YfDf+/M3C7Wm9IJ/e704XHln/vFCw2dR/N5VOrXXJRcCd5NOES/
+ICXexe62Mv7OjUQS8u6ovejtaaMkvoV2hGSG2LXdgVOCv0U8ybRs03Xl8BVM4lFY
+VO9HjnQ7O9AeGMqebvuyNAyGK9Dv+ERu65M9hB+pW//d+tVv3Dkfou+d5cOXPFXj
+f6vIK+2ClxkBH4A5dhsRJ7vPI41mwXA+H0g+MzxJ8Lg0pzJuLher03RZq3pBHvEc
+/jekP4u6mPrc+5J84jQ0hFwH4XIpxaKJsUiE/r1nFDiWRV27PgXMQgEbjdotxFX4
+IDBNKPtQNrybxiQHsYoZPdKcEfh8XyVT4NHrcbqN1SNf2ZIfDkm09aeDYXDdINAD
++0yZE+3YMeH4oWPpOIfW4OVzEDyfBGHyo2klTZfI5zdd54Kp4dKkzSlmIPC7Oubd
+ZZGoSlZfUlWVcRkqMbUAsZ8H2sdz0l+4k8+VmyiA4EWAiO6SV5xmYSncPQIN5dE2
+PbIxjKosl9JGhajs2gxCqlK+ZA3zgoFHhG1mKGWW7ucMic8Jy4oEq1XsoI0CAwEA
+AaNQME4wHQYDVR0OBBYEFA2rYljxKlzKLA/dsiAmRtO876ifMB8GA1UdIwQYMBaA
+FA2rYljxKlzKLA/dsiAmRtO876ifMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggIBAGqY1cv913Hj1+FDmD5fhzW6D/SeyL/vh3bCJ6ZJmnHFXxHZbK4lufdi
+v3HRJ4iSPnU40ZWVukWE+vKrZOJeBM2ip/cqv8iAiZg2NaQ56AcDgrpOfJcXOJzD
+83kZI8W3dF/zk1flJM3rsi5QlwkCaxBvwA+QInejN+oncA90CphumNqblPQp1Ifm
+dt+b1BIk6QJFYT0oEXnNj+5EmSu+zJ+fR5bJoZX0YTcP6YAHjdZo2qAHTeM6yX8s
+bLnX97IopyPZ/xgG2kdlp2TZZdeysaICOZ16LldE7fp2OD2ifjrAqF9eezwa2ybi
+wNhduRUn0Nmuw/Vy3X5l3gUekc3mS9br8ooHy6N+8pnq04gGWK3AAZLY5v7uvzmD
+BC6eA0IJAvLyeiuTpBlkHZTFxk7ENaStEMFjvPiLrgquHLmJQzsgKoUtR7TGdEJ+
+DOeLAhuXjpaZ/kefSODmm09BP0d/q3iFU3gp1xGu2svUK0/BC6NQNuTIIap+L/I+
+tKq+SpPpp7laJ7M04TqAlI+EMQ4KFRDbmlWAy5uq/ynEpEJ1FFuyg6Zo+fxracTR
+ytP3p/LUEYl1VQbtn9IEcrkzZNEshBglRSJ09u1nLccy3WoX03P0iQiF4oNCEPMg
+PdPlvvf1t3FbcEn5AFOsMRW4U7MBPD/gvy0EVuEJ/boydq8qMzyi
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-ca.key b/src/lib/asiolink/testutils/ca/kea-ca.key
new file mode 100644
index 0000000..4ac82d3
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-ca.key
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,1E4500430B45CC59A1AFA62E20D0632E
+
+L1O4pVdZnk9nHSyH4fkoEehRNfhE4xbYt28YMtVctjeOQQWCf6m89k/rtOlSb9c6
+82WMHWiACuWNGxcd3RLZl0dWTPZYE4xk6T3TzTk/GwkDbQRf/6hfzGcRnObVRGYq
+kzBq6zXtoqFbq2jAACqCSoRlZgpLOv8hUdUcnto707iT0ebmwbNgPsxCBXjvxOYO
+Pvkihpfd7QY5GD8fn14y/y/im/9sqZgpNfhEVeO//Dpo1Nvo6DasU1gTnEoOkRRK
+/IBl12N4FxdiAjg16SfDw/M3/uka6ftekdr4PwD616qiUsBdKsuslp9aN82k+5RK
+X3iuODmMc/42SUoSskbL5mkuroOZxihwbiKsejcmGOfVygYXuZ9a9tLHLsdKLoWO
+1mmTMU4fzNpwXPor4h0yEDaortX2KwBVPnSWOMCJtwreukgt0GHfePfbd08Ojf6M
+pyZZ7gVv/q573RSgQL6nipU+4Il6T+cK4Iwdui9WSFahiOKgALuhTX0eY7CmlfcR
+hgNqmJhXEuXbEiQONcDA7iEAggdha4W3bm8blCj7QEBpr45fAyDSZxP/dNrIoZWC
+BxbrTq+YqzLyhUOOE7THdR5qpCha5Tsoyv8n7K91v77wZjmL1poyqHbXqvWDIJni
++LAPJDd6/Z0lqXLyTV3U9FcE6cAz6kkl5J1aeWFzfWSPtdiSzMPFkaz1MUPPllHF
+nyoA1R8PAD1yPj2accSIi8nBMYpOUrwMZcS+MbSW4GsbPEOqkluLgLLas/H9eohp
+SdyPsSnNBmWaCAwNHGWRAyRRefeMsrjtlF2AfVMsrCIzUNiSiw0MHsZQV6zlI23i
+/xyYxMn3fDmMxqJCJ8FkEHxVx5SeyzbysYmCfBsquKnfzE8JAyjmRQzdqfXHt5H9
+MEctsLiTQ+WPwWMN/6zHjuJMpJFZTfK/y0RUgTUyf02t0C4Bobx30DOx0SM4B7Rx
+QQ7uwMlarE8Pg7tCDA0kC2aGCSaHo2u0qssmLVGhNKNkBVKkr7SpS4CM7dcIh+Yk
+30Q4UQfCzRbS17RD1LfdUg+SPCeDFoKdh4f4FVoHXrbeEOhPJVeCjPli78nnPuZ0
+kGvndf/v+4DH40Wvt5aZj90mes6q+2Hy4GlgciELEWhMcj2QSiRISNi5UFNYRsSL
+RsEhuksONQVrFnRS3n3WvQrZ8X4OLAfatlFewpR9UVvgfWXLuWLy6etDWa056wDa
+4OW715YaEedSsF8WrfhRXmU/IDJ19oiQzsQiyeiKoFW3OVRyf2ngb8psUOwLbgA6
+kjcrzt77RsYKlP7TYC2hvycqnvvDhKCe6yQmd6vS1lOdBm8VZWzJCGFfoeucx4i2
+DS5ryWhU9d4VoCxFYEEsNhC8GKkrcATikhLnB8riJgt5PrJenYMBd9EsuwAo3Xaa
++95SeiAdka2XIN2dBDOJ4qAJYKhHyZF/fJpJP/1s3zGsdBN3mkY3C1C3/dYR1fan
+7fK9Qx2fcZjeMTkdm91Ito7ui2LQDVjJoTEaZ0LyMh3Gz7hALuDfPeS3Eft3QXMB
+Do3Tki68lvtc9DadlDQfTm84WvS4BVyOhQVQqhS2Ttq+ICGrNekPg1zyMUI2N0bo
+8ulenrCKStFBqgyWq1aczcLNEDth0GWOFjLdgWUwI2pcN3tuouLHXpfKKARxxdis
+Un3Dj5nhg6G2vGhTTTRdxMQeiT0Dr6Q2tD9VUNojVZwJ1c50dgZ6hlhzU5pv+1vU
+krBjlx9szF2ikx2pUp8RHDAziKkv17zXDjvEJpE/pvYWHBfBPoQr5NPaPGYnbFIX
+qaLYtWOAFlL3BI1XSO/32nYee0+WjnKMr4IOvXJfnaa94S+wU6pJEbTGHP+1aGNS
+wsslmcfRDmmeblGd40Bo4ENCc93KxBf3V7g7/JnSUZO39TyfvMnyy1E3JC6fu/A4
+VvnlnFM+6ZjdhkiZ4RJqd2rc2AhA6HhOslJSa0kPRc6UQQqAci+7YHZBc/PELhpD
+LpFbBXbqyi1jNQNodhhJtkD8VkvYHOisqzHFTITZp5epK8mjLkBhIW2VUVZ+dDK+
+3kFrKB+CaEvE1OBAlDYeVxMAvT1rmyjT04mqPRnp0G57+5VQQFYrKfVevDddLIt2
+tQphIcgZYAHTU+2otlPAOXqgPJWRoKNTw6Rtc6dELrAOE/kDFqZ4VKRnXRNFmxj3
+NSC8zapuNmkGQTo8CHzJuRI8sfNHjcDrMELHV1Fe8XSoqdovV2X+Xa/fesCaYfrp
+6506uFGZSR7SrMdT5MoXGri1IEvGXkGI30UDq5QTEzHiyyYgC7kZFn3E/zREbA0y
+/WahS8zICLsEK2ZknSv3q6e9aONokNbYu7PqvQtW5IPGrjdZxuQDtRXEYafiDLKT
+c3h9eE8OKk5Si49TRjsYbuR4+BBw9N0R0RIfs5TIDkkGeCu0M4yFPKQVhCN98OAk
+h0L+ZhQJZfbDE7QNBuvmRBNcpJYe7JTXl2/p6JjoxeyZTgShk81BiOmMCaWavKB+
+gIqy4X39y+J+AiYMiKy/+B5gtNaZaE9hka7RH2tV5nkiTBilZ6v5N1A4V4Q0PRFT
+HZAXgnUwI0HcIRfkqxlF3gXMzhG1+K2wxS9uVn5K0E27xNeswr+ksfLJsyWz+gdT
+/ZFgGyErUY6CLmYzmW+WfQox+qd9pd1TMISNuBWXrdoKkX8iFjj8SWyPcZvqMUkx
+lo8RVzb/6ugSTcbCQGpf+6H8ZuOe9hZwD9tKBh6XZbC5KtBQ8TtSnrmsk9ufIzn8
+ACrJFTVOG4u/g/xn1j3MY4NIaLA77YSCed+TzOXBPmG+LrJM67n1tMtGWEPoOnGi
+6pzJpF5cxsF4i0QoqdYFThqMb6mHtaVPsjjIpdzEXmYyQENLQECERE6lYlz9ZVkS
+NsOR3KMOxXZQ+iWmqCptazz0hVVmEBFisg6K6WuQR3BpXcf8N9UP7xUnStlUUaQ7
+G5nf6BZl3AIxZPay/NoM87n4I4lplPaQwyK/ReMztu78OQFyx9mC1BGOHxVtF6hO
+W+POZqc7ugCXiY8A08vSv5yt8paWDnU+hHXnEo04Hw0ex2KNOOZeL0Eg+idJTZe0
+/0yl0olct0HUgSyhU3wm0uWiHwulreoa3tNL+a4Xt7k5L2e5XcvAh3T2mgxzDq5q
+-----END RSA PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.crt b/src/lib/asiolink/testutils/ca/kea-client.crt
new file mode 100644
index 0000000..29ff5e5
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-client.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID3TCCAcWgAwIBAgIBCjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDAz
+M1oXDTMxMDIyODE1MDAzM1owNDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0lTQyBJ
+bmMxEzARBgNVBAMMCmtlYS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDKbsDkElojvFhVt234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfz
+G1k16qMB5jXF7dRhzq4FiPbZMs5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PE
+l47e4Ae3Cl96dWUfQKQmGIzzHfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyG
+JKXSuqCsaXBRK7Amn8/jxMgdhds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJS
+RKaG9hVRrAe4GPisOjUzerADkPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zq
+qTgTp1MvttSv8JEN6OMhM+bpCbaiWp4DAgMBAAEwDQYJKoZIhvcNAQELBQADggIB
+AENl7hCBjAft1uC/XAO/yBkkDrTk6R21+mdJMghJ9ojFP33QvYYv0pDNeCZ/IJEK
+G2ML8gFzd2YulF1qzBMuFvESRQyqJMnIWJS8FSEIKEyqj5RMTnVWjFM6V2yGhBA5
+XXAL4CVVNz/NqWV/Ebd1XB1OB/y5uz+ZowpWktHtqCKYhDzDtK600GswMOJ5UsZF
+X6JtkvG86nVfuyOIK3NtMXQE/ptAgwa87hVecu7yY/u6PmRwS7YbVBsh9VplnAsQ
+bLARtTGCWHL3otZaDi81dghHkHYmv1NmaubgKnFffKxJGLCtyHF0pqS7C0v7lLOo
+qOhSd3qaFEU1yWpXCFlyglDnadFQs8pdWIPBngwQC2luF1N7Kppz5zzGF5MHNt+E
+LuPlRAwgs8aRRPsySGYKvtCeNYAgjsbec9f0P7lMEGr+AqbZF9qNbbQQkq0dHrMH
+goazCek3XtlMAYYUdmkqQ5a44XRQUu4FuTVqzCH8nqhkeHcWTwO9BHayUebxiBk8
+njDwLtHiQ8u9TjVf/35UOdqFSxra+wZJPKYbH++82KG6rbEotGp3jv0uxasgiHVL
+qrD3dkQAU8zF7cllsUkRE3Gw4tDaZXkZCawiMfLiGK1FVApXkUnKilASDsaH6i3x
+Ui8LM1F9vbtJnzftTa3yi0FR6Gmi5Mc+R42gpE8xCa4y
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.csr b/src/lib/asiolink/testutils/ca/kea-client.csr
new file mode 100644
index 0000000..e607360
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-client.csr
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICeTCCAWECAQAwNDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0lTQyBJbmMxEzAR
+BgNVBAMMCmtlYS1jbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDKbsDkElojvFhVt234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfzG1k16qMB
+5jXF7dRhzq4FiPbZMs5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PEl47e4Ae3
+Cl96dWUfQKQmGIzzHfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyGJKXSuqCs
+aXBRK7Amn8/jxMgdhds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJSRKaG9hVR
+rAe4GPisOjUzerADkPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zqqTgTp1Mv
+ttSv8JEN6OMhM+bpCbaiWp4DAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAs7Ed
+zY2f2BN33Jd2/XAe3Vl/Jm7JgLN7GnvwzdoM/KewsTsSo0wrgqBU2r36F+W2+/T6
+rN8C0SseFfaURd3CQc66UcGzp4+FKxWIS9loO4P43t6MjBUQ/RiW3IQUAbkMIL52
+CG1HiyyOp7GNtXb861CCu25t82oXeW7WWvWJxaKeAk/hkr7lrVxCcU7XkVY6sDU0
+t4fP3W31p5ZkLUK4qELiZ3iJZLnf/5xaXgJpVlS3E4DUe8tyl3TjayYxroyRj+TT
+D0LWwE65QGygJM2cZrraIvue5kVan4C8XZvO/VvZoakWH/ZkGN8Pis33r8oEfrQL
+SyGt7oTSRYob5MTWmA==
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.key b/src/lib/asiolink/testutils/ca/kea-client.key
new file mode 100644
index 0000000..a8768b3
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-client.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKbsDkElojvFhV
+t234GQOEVVudEp4s8KYnDQTZpsdeidrP3yY+qWfzG1k16qMB5jXF7dRhzq4FiPbZ
+Ms5cz3BfwZDlxjWMxgixPaCrVphYLGhI8AOne8PEl47e4Ae3Cl96dWUfQKQmGIzz
+HfTcJvCxUOCob5zYOCDvtjk48IxdvHi18Ab/hXyGJKXSuqCsaXBRK7Amn8/jxMgd
+hds92tNxm0BiAJtsmkQm9QW8ztcoiEEgO4ViDRJSRKaG9hVRrAe4GPisOjUzerAD
+kPX/pchHIqmrTJ9YKhngOfDdiAZY1lkZc1cbM6zqqTgTp1MvttSv8JEN6OMhM+bp
+CbaiWp4DAgMBAAECggEBAKJP05ILtQLaTenMvfwj8lH1LxPuja1y94ZwRedOdqUy
+26O5RS0RICwpTYqRrEolkBA39gbGdXoyq9rTheuc2Hmu9sOF/gH195pF08IOGPD6
+ClQRPpzX+8xxyTijYQw+4PeLkZ1Rc0yoeruk1WSARJWoR7pGY/hqaN5Lue4R0jqF
+KoVBrsR6ULsLscgjHP+OmIdFiTamnmjfDHcQTLKFH4inf5T0Q6UZnKq9qxoZ/o9i
+AELalOOK1U/v4CvGiQFPHX9V82ZWA5P4D7aSSqDgR7eyCYEaPLRpRq2v9A5IQQuc
+nD1wdkshqmSUOj5xL/KlJaIYBIZS8LKOYGZUOD8MMMECgYEA8VcBHU3CZU2zmZTk
+sD2SCzw/P93gsA5rcKtQQWMFIyt8CQHcl7zTHReaG3ZAhFU9DY0WSUzTtqpOhYy3
+E9KgugaWi+BMQC1zItadGsA2WF3RXAbSbrZiVGQitFxLmbXJL4QoRMIRaewagKt1
+hsF2kolWg13inEAddiCOXMos8fkCgYEA1rq3PRGI/oYdTvI41HTJcRiYuGeJacT0
+D7lQQqMS3hk6jisPGhwFlIQ0/Ax/vnvpW/eRO4vhpKQE8DkPEGyM6r8JPejcWeDZ
+pEs0vfuCVitUsCw0g+z9hVa4slnZ9clkxsY9tHZJWFdTxi9Bpjezo6XN9DTtl3hP
+lNSegykMDtsCgYAnnrfxHp3mUZ5FfVsZz9HVBFwB2SQU4xkiUw2G3oGuZ2oidGrJ
+gldKNGC5V216DCBMxDe/atxq5YSkihhYKcD3KTO33OfHtW5sbr018g457ZT8PaZ4
+RHraDeJgp7JFlsFjipetygpf0EH9k6hkqggUQHWydUxJiIENroSQmSRNyQKBgF5T
+dS0BZ/GPDo7gfrBtgRQKXwQaj1WELEY//I7ZPe+Mm5laNu8cQiNElFXoU7Fkk1VQ
+Al9rCjsdxgGUvxZS6PAx7ShiA3IEAPdYBhoywsWBkVk2gfc2AwQw3T+TktiSmI9t
+BCwjDgMdkXJszeTrcSFBM6DEI163fhX99IffXymjAoGAf9B0v+NIxRXgMac+1rLN
+MSzOOA2yq3tI+Ra8q0D4r4ShfauWll/rlEgx6L0FrAdTYfit8I3dBOqKYe3b/E0r
+IKjAX5rh9Es/PxsOo6qJYw9l4P4+xxZKsqqvdMNQ1+21ZC90TnHWdy3bRPh1D0Vj
+XDwyByyi4FuaEWhZgNA5+44=
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-client.p12 b/src/lib/asiolink/testutils/ca/kea-client.p12
new file mode 100644
index 0000000..baf4420
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-client.p12
Binary files differ
diff --git a/src/lib/asiolink/testutils/ca/kea-other.crt b/src/lib/asiolink/testutils/ca/kea-other.crt
new file mode 100644
index 0000000..bdcc9bd
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-other.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID4DCCAcigAwIBAgIBFDANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJVUzER
+MA8GA1UECgwISVNDIEluYy4xETAPBgNVBAMMCG90aGVyLWNhMB4XDTIxMDMwMjE0
+NTI0OFoXDTMxMDIyODE0NTI0OFowNDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElT
+QyBJbmMuMRIwEAYDVQQDDAlrZWEtb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xm
+leGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG
++R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k
+2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbB
+CAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcU
+CUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJKoZIhvcNAQELBQAD
+ggIBAMYcxVfoCIn+NPlsoRB2m5vAOuJTuBNigf8Fm0HYougE2W+p50+5USx2BCM8
+M1Cet+8X0dktHbRdDL5aZrRbYnz/OENBD4tKuWMQoP/qzafRiKSkDckxYM6AR4T+
+fzPgLjUde2NE1cDeRlJUmereRXiD2qefEFH55StLl8YnnciAMGTRjwBuLiReF+qE
+noaD8ZIKZ5pBMzoxyOe+39tLJkzhESdZ8gJZRXGm+ickAlP96w8z8TlQiWHG3Caw
+kM7SZSyVYdyfiF32J6A7hwlG3qud83GcunfrjOurWBe1lv51pb/OFGe6wlRD/pcS
+UcKZ07KXXYMXV40O6A5Dv0yJB8ocKhOkfU5MvotAAm2GL2ZXizfmEAz23X9I8830
+B5ggVxgp/bO/exC1sBJjUgF4qVPByE1MdDDWYvPKT8cYg5j8pD9rDn7WGVAmgCk9
+59lEI0HBP33ulBRoxrOQ7kV3pUlV8oP3wG/joz8PwSNAbbtQuUnAmjElONPyTrMN
+2Yqah89SqH9ygzz/UomdrKYuoTu/QEfLLtBcyBLKHrRT8ODvsp2kY9RpveCctsAR
+2gmnYixj7GDdp5c6zTich1+QkVvFtrl3Zu+AWRekFAn92bwwOli14S3LgW2t4iXL
+InVUqNg6l6K9d+FdHogvITQLKKMpfIfsCKPqvacpqryyaith
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-other.key b/src/lib/asiolink/testutils/ca/kea-other.key
new file mode 100644
index 0000000..212dbe6
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-other.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAoEENWQ6tl6aa
+RMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2
+aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXd
+cD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyv
+qVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11
+QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ
+/3udIh1vAgMBAAECggEAYUpPsPszM7Bt4GswDvUu/loTXcsq1vglirGAsmr+aEf7
+bqF473NyRONFD5bpgWUSFg2aDxMtn884VN3ir0rPIHjIxhnnhY2FF1TnH/B3OUxv
+bWfTYQK/ppv7THHkctqucFCh3POhcrOSqOaB1SB1EFmntJbDpG0EhPYXbC1nALy/
+1NstWOBhqcr6xTU2VUnzqSDxa49pJPuSZpEj4G0VdqmrlI/8wjl0mSLdY2VqXMlQ
+hSJG2aqRKiKe0kgQvUnHVEaic9YNNC2arxX/zp5c4USioxekrmCkoAKXpqbFswGS
+zcFWJQer+nvCkIX9zhr+3bFn4/dkDK4GD49Atw0kKQKBgQDew/iEDKgQ2obQFVW2
+2WheTSuE3sDsBdnod3YRZgIn7Vf7QyaNqzoOec8QgE3oMu2EdKKyLDyN8/3IBCVq
+zUIeWJLN3CCt6DNK348hEJc3Fc2hibv/Ea4TQ4rZyNxiYJfV86ndYvKMQSMmHz9l
+DKzrjB/x1LaBBuO4qLUzgK7vwwKBgQDdXSwJo9MzCeDvqQtUxNbMXKJOfEqFusaW
+/NidbS9MnphImcsQobMfcN/h74r2aH+xGHBk5pPecfU/qK48dzEWncrWppRTfJ9V
+eU5VYlnkvI/mHDKJqoEZEycRqlUCRvlu8DGqEOHelW7ZCjcGD4DckRDmZCaeD78O
+xKkiyu2M5QKBgBi2GI1dcg9cjnPqyfVcrK05VkiJBVGpXIDjL5/Cdx7Cv23KBy7T
+/b65WHT2Jq5JZ/u3jIzDR3xfwpk7jIMKffkrzi0z7BQenAIERrZeRsf/jS4MP2SO
+K4dLiM2b8IahPHapbwB2B33zg9iowrmM7Gm8w5ZqCEzL3NsRK/ion79NAoGAYEtw
+pbzjWfd5Jyg1Kqn5+qptXJEK5gOq8fGJ1WmywrTW7/Ye9NwyjIHQknteyvQIYCSO
+eAYp2wFdu1SIfvsmmn0HyLpsGalDsq3zWodPLYatXl9zyJkoUZ0YSMH8+uGfDhhk
+smNnrij5MGcWKofB+bENVfvJJMcayLTaEq2OCtUCgYEAoXRn4p1P3kdtLXYnTAsn
+gsDIVEzFAnb4DXuvK3ozA3mUgIwGHQiiwcN+mCHml3hkMnmkDSx4bakbAUuQ+EIz
+kS66aKRTvXdIZujGSNLyjN9SQkaJfKDYLW32WmxTowoLh5MyDpaVOMFegVzex5c/
+zeY0qPLtGf+qogMcoftZeR4=
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-self.crt b/src/lib/asiolink/testutils/ca/kea-self.crt
new file mode 100644
index 0000000..5d7534d
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-self.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHDCCAgSgAwIBAgIUe1AyLcAeSfKwCZNZLFTRkWMyOJQwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAoM
+CElTQyBJbmMuMREwDwYDVQQDDAhrZWEtc2VsZjAeFw0yMTAzMDIxNDQ3MDdaFw0z
+MTAyMjgxNDQ3MDdaMEgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRl
+MREwDwYDVQQKDAhJU0MgSW5jLjERMA8GA1UEAwwIa2VhLXNlbGYwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAoEENWQ6tl6aaRMn+yaNUKTBIIWpVoy5+
+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2aWD0H+Hk4l2WTFigWO3u
+tPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXdcD9yNXVOb2JWp4Cxk1iP
+j7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyvqVnVb4rNh+PjGL8l5SZz
+SnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11QvQzB7f/U5dQoQPzq3j4
+ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ/3udIh1vAgMBAAEwDQYJ
+KoZIhvcNAQELBQADggEBAHWFX55xUt1Opqtji+I2XvBrcexleSAME+irKwExe+tY
+laFEWb1eWyzFHiuOSuNLjcXt1PkUYZ0lYUg17cDj5urpAy+F07uCRQWTXBY8W53H
+IppYl4KjN3w4e5DSyDfiTv99MT8xVKJk+rVu75lQ0kgg68fZR6yK82SLjBQmjV2A
+OcSqHNHtnBU5RcdlZ+E05M1Vo1jHzxHpybkgNxjvmUgBRc9ieLbgSFRZji0nNmhA
+TSZ0DjRce6eyDI+OoEFJL0wXMl0ZOijeuCJr4C45h3TyreU2COC1GaoIeNwmGSIb
+mw0j+XR4rKHcgkUQ7L2DfwOjGFG7IeT+k0QdyeM2NU4=
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-self.key b/src/lib/asiolink/testutils/ca/kea-self.key
new file mode 100644
index 0000000..212dbe6
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-self.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAoEENWQ6tl6aa
+RMn+yaNUKTBIIWpVoy5+uGsBdZW++fEvw4xmleGD+bwyHZFEsHPos/v7zWUNFaX2
+aWD0H+Hk4l2WTFigWO3utPoXDzDOjfQmglKG+R08p3giURrJzUKWwe/RRJBs7qXd
+cD9yNXVOb2JWp4Cxk1iPj7zTS/LGsFr7F4/k2nlH3EuqvB3GBEXHa/sA55xigMyv
+qVnVb4rNh+PjGL8l5SZzSnrbdoIEtKw/LVbBCAVrQsgcADNqjR7ILbqeIqg1Td11
+QvQzB7f/U5dQoQPzq3j4ow1zOiaSokZE7UcUCUNfjRv5E2lW+mmyM7nkgyE9LqUJ
+/3udIh1vAgMBAAECggEAYUpPsPszM7Bt4GswDvUu/loTXcsq1vglirGAsmr+aEf7
+bqF473NyRONFD5bpgWUSFg2aDxMtn884VN3ir0rPIHjIxhnnhY2FF1TnH/B3OUxv
+bWfTYQK/ppv7THHkctqucFCh3POhcrOSqOaB1SB1EFmntJbDpG0EhPYXbC1nALy/
+1NstWOBhqcr6xTU2VUnzqSDxa49pJPuSZpEj4G0VdqmrlI/8wjl0mSLdY2VqXMlQ
+hSJG2aqRKiKe0kgQvUnHVEaic9YNNC2arxX/zp5c4USioxekrmCkoAKXpqbFswGS
+zcFWJQer+nvCkIX9zhr+3bFn4/dkDK4GD49Atw0kKQKBgQDew/iEDKgQ2obQFVW2
+2WheTSuE3sDsBdnod3YRZgIn7Vf7QyaNqzoOec8QgE3oMu2EdKKyLDyN8/3IBCVq
+zUIeWJLN3CCt6DNK348hEJc3Fc2hibv/Ea4TQ4rZyNxiYJfV86ndYvKMQSMmHz9l
+DKzrjB/x1LaBBuO4qLUzgK7vwwKBgQDdXSwJo9MzCeDvqQtUxNbMXKJOfEqFusaW
+/NidbS9MnphImcsQobMfcN/h74r2aH+xGHBk5pPecfU/qK48dzEWncrWppRTfJ9V
+eU5VYlnkvI/mHDKJqoEZEycRqlUCRvlu8DGqEOHelW7ZCjcGD4DckRDmZCaeD78O
+xKkiyu2M5QKBgBi2GI1dcg9cjnPqyfVcrK05VkiJBVGpXIDjL5/Cdx7Cv23KBy7T
+/b65WHT2Jq5JZ/u3jIzDR3xfwpk7jIMKffkrzi0z7BQenAIERrZeRsf/jS4MP2SO
+K4dLiM2b8IahPHapbwB2B33zg9iowrmM7Gm8w5ZqCEzL3NsRK/ion79NAoGAYEtw
+pbzjWfd5Jyg1Kqn5+qptXJEK5gOq8fGJ1WmywrTW7/Ye9NwyjIHQknteyvQIYCSO
+eAYp2wFdu1SIfvsmmn0HyLpsGalDsq3zWodPLYatXl9zyJkoUZ0YSMH8+uGfDhhk
+smNnrij5MGcWKofB+bENVfvJJMcayLTaEq2OCtUCgYEAoXRn4p1P3kdtLXYnTAsn
+gsDIVEzFAnb4DXuvK3ozA3mUgIwGHQiiwcN+mCHml3hkMnmkDSx4bakbAUuQ+EIz
+kS66aKRTvXdIZujGSNLyjN9SQkaJfKDYLW32WmxTowoLh5MyDpaVOMFegVzex5c/
+zeY0qPLtGf+qogMcoftZeR4=
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server-addr.crt b/src/lib/asiolink/testutils/ca/kea-server-addr.crt
new file mode 100644
index 0000000..2332046
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-server-addr.crt
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECjCCAfKgAwIBAgIBHjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDE0
+OVoXDTMxMDIyODE1MDE0OVowOjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRgwFgYDVQQDDA9rZWEtc2VydmVyLWFkZHIwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQC95FGAg3RLyh4FdVogSewbUMq4PMoqougt0FA1+hmsrMMs
+gw3OVpvG7bSHzZj85TTYR0TNqv3w6y3OCUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SD
+SjIKJYWkksnxY4XiPu+ZlRa5JLdW2GUH4wtaU5nnMALI/JY+jhnaegdpiN+JDEEf
+yvkYm1CIkZqmBZFt7Ij6bzftuOKanPpXUAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC9
+8oKP4gsxLjrBiOMdO8IblZIIb9KSR6vYcAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFe
+H101kHnZT1CTI8Ucquc2fjTQH/EEY0qjJNJhstATAgMBAAGjJTAjMCEGA1UdEQQa
+MBiHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggIBAAaf
+GIHwgnSo4zo6cIfpzirVpSqjzOrsAqzSswigZdj7dwx959sgSJzZssDf/TA98iXM
+YQEkBao6jPuo8fTlCF0XGCUGAfq/f6Yn1Nhkk0qUdxLrNsEjKPXjISZPaVZllZBR
++mRMKObn0l86vJ/0zGzPRxH2P5CKg9g3sT8zkg1fGIE/SNr8abZV5Cf3spYQ9PF9
+zQ2TdpgaEGGufKR6VAIJH4CVShMfvBF0qFbzMC7R/CTdSvEBXagWclBT7PqcVGlV
+rK/NB6rt8W8hLQQE6bRunJmkLrmLKLVjFtPZPq5hm3jE8fnGxfzvThiZHTj+oFGw
+KXcbuSvwgYuLKym648V+VDGiDWdpS2dIwQi2JeHTt7Y4P+8dqPfHY7oDy2+67J6o
+ElTXvloGVNCedQtpp9gNrtil5avXrU9HCfD9avYlsn89kqYZ3Ht1GBYPyqeSZDCo
+a+sffazhYPfqFdH0U7wpq6Gf8/JMSAuQmAR2UAwhjoQatqDqEJ3pAFsI3YcQOZqm
+kj3/T0iYkU8YdJkxI2YgVCRRIzTKHkGMVc/iz+C0OJwFeJDuj+dj+EXXtyi3sjhL
+oTQT2y01nW2TPrHqlG3/fQyPx1gKXrij+1uOZJpZcgKE7/YBGByRiUdOyRJ0E6h6
+oimhTLT6mC9wteMiRmj68z5tTC1P0H4nuOU7OqwL
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server-addr.csr b/src/lib/asiolink/testutils/ca/kea-server-addr.csr
new file mode 100644
index 0000000..d6ba063
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-server-addr.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICyzCCAbMCAQAwOjELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRgw
+FgYDVQQDDA9rZWEtc2VydmVyLWFkZHIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC95FGAg3RLyh4FdVogSewbUMq4PMoqougt0FA1+hmsrMMsgw3OVpvG
+7bSHzZj85TTYR0TNqv3w6y3OCUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SDSjIKJYWk
+ksnxY4XiPu+ZlRa5JLdW2GUH4wtaU5nnMALI/JY+jhnaegdpiN+JDEEfyvkYm1CI
+kZqmBZFt7Ij6bzftuOKanPpXUAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC98oKP4gsx
+LjrBiOMdO8IblZIIb9KSR6vYcAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFeH101kHnZ
+T1CTI8Ucquc2fjTQH/EEY0qjJNJhstATAgMBAAGgTDBKBgkqhkiG9w0BCQ4xPTA7
+MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMCEGA1UdEQQaMBiHBH8AAAGHEAAAAAAA
+AAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBADlAkM7Vt3acIbgx9uz/nzEU
+biTUETzQnCU/mJZU+F8nuZtIlH9TAej4oT0J1uBuneGdkgGSm3lONUNxYJ7Uz8dm
+wyudv4cpvtacAzPqZNb0aapX3qD9/lUbXfReoOUmt+asdmF2ncmn3l465ercxtUg
+zhbU5uQUEk7C7f4OZQ3b08yG+tblFhpO7Xm4JD6nJk9iQ6gB4WBUDSr7mdm7PMmV
+T8xesD7lDZVjSdXql9p/6YxJJR3360jycLXeTQbom6gfvsfQcs91yfGHRel2yoDx
+ZBcmjfkYK7mwagpB/QCsZDuC4cxZyFM7lV/ukIysviW7WzrtT9mvfTEcTqmPsPU=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server-raw.crt b/src/lib/asiolink/testutils/ca/kea-server-raw.crt
new file mode 100644
index 0000000..34f3392
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-server-raw.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID4jCCAcqgAwIBAgIBHjANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMyMjEyNTcw
+MFoXDTMxMDMyMDEyNTcwMFowOTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRcwFQYDVQQDDA5rZWEtc2VydmVyLXJhdzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAL3kUYCDdEvKHgV1WiBJ7BtQyrg8yiqi6C3QUDX6GayswyyD
+Dc5Wm8bttIfNmPzlNNhHRM2q/fDrLc4JSFopihVSNDpotALop1JL/euNLY6DtINK
+MgolhaSSyfFjheI+75mVFrkkt1bYZQfjC1pTmecwAsj8lj6OGdp6B2mI34kMQR/K
++RibUIiRmqYFkW3siPpvN+244pqc+ldQBnmx73/s3dS4/swznM6d/D6iBlG5gL3y
+go/iCzEuOsGI4x07whuVkghv0pJHq9hwCTNnDXBD5kKgDqQJJhUNhuOm1weKwV4f
+XTWQedlPUJMjxRyq5zZ+NNAf8QRjSqMk0mGy0BMCAwEAATANBgkqhkiG9w0BAQsF
+AAOCAgEArTCCoN7IKQ1g9PqrCeZe0sFOPmL8tEfg83bdTnOUF1rcaK5b3E/ktuT2
+b4axEOTLo8OdwBFFdGHn7XcXAWEx9mVeEw3J1X4143FfhzwnU5ZfLvgKx3yY22ZO
+9WUf0sT35aEH8jS9OzqeaGqkgNufCrmNG5TBXnTG8iFVVKqxdaI9EpoiXjLJwOi1
+5ZO3iB04saPPekVA+u0nngG+sx30hjpNu8EDl9u5f04B0cE3iZSvc4/DN4GDBjIn
+eHzAwlP++mDTQ6d9K8h9BRNnqXBwdN+6CbTTB3Mw5DlvHxBSXRf9xIuhWEdiT7kQ
+Ac7tTs9qsC+g56j3N526hVegbnhB9SSlO1gNWhKdWoag51TJQP38d7lrD6YhJIVi
+57idCeEfvGNcrIMr7hbn6nm8q1nd8waE2dX0FMm3WCf3Nj8Zpsj8JxnQj3jQ/Q38
+bHoHVtAvc7W7tAzMHl5R7UufEqP/42lnes4DECQ5WvN+t9l5gErO4svHfeXNFGbM
+nbjVxGeJeiRPGriej8dlD5Ea0WVHOETh77+5p7DdDBir/xLHSbS/QypKnTGixhwB
+Zg5z8CHeepVf5Y+xhteOZwJCjxCTwW43aOEHQ0U7gHke2hNtCagwlbmLBITzJMJL
+HIFvpHfNTLX1ZRU/z/3OJVEfuMRjah5BJZPGuhuJxR47hP0tLJY=
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server-raw.csr b/src/lib/asiolink/testutils/ca/kea-server-raw.csr
new file mode 100644
index 0000000..b0ab32d
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-server-raw.csr
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICfjCCAWYCAQAwOTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRcw
+FQYDVQQDDA5rZWEtc2VydmVyLXJhdzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAL3kUYCDdEvKHgV1WiBJ7BtQyrg8yiqi6C3QUDX6GayswyyDDc5Wm8bt
+tIfNmPzlNNhHRM2q/fDrLc4JSFopihVSNDpotALop1JL/euNLY6DtINKMgolhaSS
+yfFjheI+75mVFrkkt1bYZQfjC1pTmecwAsj8lj6OGdp6B2mI34kMQR/K+RibUIiR
+mqYFkW3siPpvN+244pqc+ldQBnmx73/s3dS4/swznM6d/D6iBlG5gL3ygo/iCzEu
+OsGI4x07whuVkghv0pJHq9hwCTNnDXBD5kKgDqQJJhUNhuOm1weKwV4fXTWQedlP
+UJMjxRyq5zZ+NNAf8QRjSqMk0mGy0BMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IB
+AQBsIgOnhSxHeOmtPDNyDqPfr6JHyrPko4+gdK+iazsxWbJmqK/BSGaMIDEq48qI
+3mt52d4VQvw19Vsf51WhYZRz4igXGmHaxKf1RMIjnGoDjqzHzsdj2BfIqcToGCep
+QT5rdBsECcHBq4zs/wa7F01B5gqAfk7Ytt7Qeu5o4aAzCGvJsZsWlr+7tEcMbMNf
+IzMp2FCdV6HJiB157GuVZotRq6bjLrc/YT1G+XG5COUNmWmgXip3deVRhPOxshxn
+ofJTZ5ryhImG2nJNRjobaiTeWpWPaXoXLZ0qiRl9ZfydPLktky2k48AvF11Jp8Y1
+auOe48l7d/LyWEmCNACeoyvJ
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server.crt b/src/lib/asiolink/testutils/ca/kea-server.crt
new file mode 100644
index 0000000..3476032
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-server.crt
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID+DCCAeCgAwIBAgIBFDANBgkqhkiG9w0BAQsFADAwMQswCQYDVQQGEwJVUzEQ
+MA4GA1UECgwHSVNDIEluYzEPMA0GA1UEAwwGa2VhLWNhMB4XDTIxMDMwMjE1MDEy
+N1oXDTMxMDIyODE1MDEyN1owNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJ
+bmMuMRMwEQYDVQQDDAprZWEtc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMNzlab
+xu20h82Y/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oyCiWF
+pJLJ8WOF4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5GJtQ
+iJGapgWRbeyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKCj+IL
+MS46wYjjHTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9dNZB5
+2U9QkyPFHKrnNn400B/xBGNKoyTSYbLQEwIDAQABoxgwFjAUBgNVHREEDTALggls
+b2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAKYtC4/KKZnTktvWankLnlVact5K
+L0bJT4qCDg/0gj0pj3rofqyOEoGIjZssQtAG/wmJNF6gNisX/1F23BdEdPAsOJQv
+KuRwr4zL3uj2Mkz585Or/iz633LnD8Ibv8KQsKLnJ/UnJikeH5UgxqcU9kA7ymAE
+pzilP23p3bINvyBMwWZUzT3CsYB7PrcRzx3ScZhbhYaN0f8lq83nspXr8U3FyH5U
+NkrgpuqIE9dFPiaY4CsjNIISpYANcVeWwyPKMk/uty3KbzbmDr7ssm1u1MyJjeVP
+jE/Dhq+WTbDGMfqR3gyXBWq7b1ROA7tk9kAMQg91PLAELSB6lRmzfxzrH/wYk6E/
+0gHgpznpDcA68uW/54eX8phJQQp7Ak7csElXjqXDJ1AWA8VVjRXHerOkq0cUWply
+YsJQCkx3jKdLDFfjtKZWVOjc9rGCnph4BfUej/Lt7z7tTr/Yh+oAR+UyowRzdZM/
+RSsui8vVbvKU+bRlyB5qmNR8cSI5oEA+kAs5DXK2bh5v1SGSxVjwKuwwLeu8eCr3
+HUYQMxKi7Y15+BqjbrOZCEfHE4WORkKze1dh9U/UU9h+LVd+TB7jprZc3ZOvuqYP
+Bb+ponHJJaRvHUKD/jL8kHQ7KX79wXNVkrevGcPe8qE1X/xu4ChK5PuDzq2HQPLs
+USYWw/aARNwslhV6
+-----END CERTIFICATE-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server.csr b/src/lib/asiolink/testutils/ca/kea-server.csr
new file mode 100644
index 0000000..458b369
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-server.csr
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICuTCCAaECAQAwNTELMAkGA1UEBhMCVVMxETAPBgNVBAoMCElTQyBJbmMuMRMw
+EQYDVQQDDAprZWEtc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAveRRgIN0S8oeBXVaIEnsG1DKuDzKKqLoLdBQNfoZrKzDLIMNzlabxu20h82Y
+/OU02EdEzar98OstzglIWimKFVI0Omi0AuinUkv9640tjoO0g0oyCiWFpJLJ8WOF
+4j7vmZUWuSS3VthlB+MLWlOZ5zACyPyWPo4Z2noHaYjfiQxBH8r5GJtQiJGapgWR
+beyI+m837bjimpz6V1AGebHvf+zd1Lj+zDOczp38PqIGUbmAvfKCj+ILMS46wYjj
+HTvCG5WSCG/Skker2HAJM2cNcEPmQqAOpAkmFQ2G46bXB4rBXh9dNZB52U9QkyPF
+HKrnNn400B/xBGNKoyTSYbLQEwIDAQABoD8wPQYJKoZIhvcNAQkOMTAwLjAJBgNV
+HRMEAjAAMAsGA1UdDwQEAwIF4DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZI
+hvcNAQELBQADggEBAECqICoEZb0XeGwoBedtG2Exb4RUeoTAfL24q5a8cOtv0+Mw
+i7y9LNihtRqP2kzhoZ7IhzSUZGVuh4BIUywpJHuWfM9b+fe+hxSGdqCeULKS3InK
+4RWRh9jr12L7hEKfAG7VtL03/+Lm5DHLr47X6RkeZ5GwP29qqLwJcrK9qeFi26Bs
+TrEafPInhF7PgyFjH2YVZVotNaOFMRvwEQwAMtuF7SAqRHr+8VHXP3yi9UjHvxRs
+BpbVD6fEWNkLLJhoSqERgjWnsFlU3O+kj9R+iKA+6arxr4d+HS+dyYitFtVJaR6C
+0+De9msTbJmn+2mu4zQ09Sdf0pN5lb/I3pgcbLU=
+-----END CERTIFICATE REQUEST-----
diff --git a/src/lib/asiolink/testutils/ca/kea-server.key b/src/lib/asiolink/testutils/ca/kea-server.key
new file mode 100644
index 0000000..7709e16
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/kea-server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC95FGAg3RLyh4F
+dVogSewbUMq4PMoqougt0FA1+hmsrMMsgw3OVpvG7bSHzZj85TTYR0TNqv3w6y3O
+CUhaKYoVUjQ6aLQC6KdSS/3rjS2Og7SDSjIKJYWkksnxY4XiPu+ZlRa5JLdW2GUH
+4wtaU5nnMALI/JY+jhnaegdpiN+JDEEfyvkYm1CIkZqmBZFt7Ij6bzftuOKanPpX
+UAZ5se9/7N3UuP7MM5zOnfw+ogZRuYC98oKP4gsxLjrBiOMdO8IblZIIb9KSR6vY
+cAkzZw1wQ+ZCoA6kCSYVDYbjptcHisFeH101kHnZT1CTI8Ucquc2fjTQH/EEY0qj
+JNJhstATAgMBAAECggEAdhnidsNLOTfjpBFwlFRlfDerXRqxwgK/1H6S5H9AKJzq
+Zmy70XEcQYTlmvDMDb2gOENbD28hsQ0T1+j+DtV3A/u0b/9etdBtAEozCqUriE9x
+nZYvuQ/NJqYE4xS62BO8gRCwqUWkoWbErzsOfIcyWQ8LLGWsLAvFGJR8t65hGKJq
+7/eYAdSd4mJhMv6mpBWR7OdKRarVxin0DucbLj7eXHPKPeu7//9LbxXhRKGHnc+A
+8PmCFYo+oslhtLplzVPxzSa2D9xvBkFTK5dbwCRP+J9i4kQGfehBaW4jZpzBXtjL
+6idGAU6x9nBSJfWhDuR42HsPn7lcnoKlnErG0inDQQKBgQDzBbo7TsBB8DR003Ix
+seKN9Vpul4bUUp+pl4iECdtgvBTUMmKKkeV/I0OjvAzQAzbum6iEwCOjXpl9pxSx
+6u4iJRK+/xtxZAT2Ddc3tEaql8kcc+VEjZVZPSIcSSOQw6Sri+K59ZzEbP+fDgFk
+REGdiYA4A2ZvBdftmdVsKeuMRQKBgQDICEM881RNAZNwwT3yVU+iEe+16S5qoDjX
+mi0K0cDffQjiYbwjdcZhtjKZdieZsjPLQof5mMxl1NeJaao9giHQ/tk55cpc17xl
+N50LB9f7XAamOTTUoIjOl2hLxflZ43bf/W2GgX4dMp2FEFC17rxYUPfoO1vSvUfB
+74goLyHsdwKBgHSLSZ1Jje/RRwbDpF7qpPBZOo4Qwsst+H23OvO/WmKQsBh3NUSo
+5PtMqRJri2VyNTTGl1FaZ3zgUBGvP8B3Hs5nIw9PfhSp16s8RfrjzIPhGMQ5XDi9
+AWNzatlPxeuVt3HBOvDdNdoJP6lCaS5xgVoQZ9n033ncvommnXAqxlhVAoGATBqI
+qlnRivK8i7uZu+clQv4b+1PaKwsGVVD9Lg6bmOvTQ333vG4EqgxNuAEyE9Guzvhj
+D11I9r1Bu7AN6xTllMRBFTwN/8C8lq3P+/BiBen/RaKiLPte0WrdbWbG9aILCjE7
+SF9gAe/N6mBItM89rUQw7ZQX3VfSQ0DExrUX7QUCgYAAhwKg3c9rMVZKw0w7gR9l
+/hVSCdOD0OHeYwfwzeEQbJshJJUrPk0gsEI4pEo0s8u6PhPBNy6t6U2Mw5S+R3/7
+JCC7UH0iY24d1K8mNL1PNYKBnbpDXrCgzO/Ip6TLnXiyy3/Uu1a7CKK2YZLSukp/
+e8iWSpxQT1Zwt3cfL8/EqA==
+-----END PRIVATE KEY-----
diff --git a/src/lib/asiolink/testutils/ca/server-addr-conf.cnf b/src/lib/asiolink/testutils/ca/server-addr-conf.cnf
new file mode 100644
index 0000000..12a34f4
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/server-addr-conf.cnf
@@ -0,0 +1,355 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME = .
+RANDFILE = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file = $ENV::HOME/.oid
+oid_section = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions =
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca = CA_default # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir = ./demoCA # Where everything is kept
+certs = $dir/certs # Where the issued certs are kept
+crl_dir = $dir/crl # Where the issued crl are kept
+database = $dir/index.txt # database index file.
+#unique_subject = no # Set to 'no' to allow creation of
+ # several ctificates with same subject.
+new_certs_dir = $dir/newcerts # default place for new certs.
+
+certificate = $dir/cacert.pem # The CA certificate
+serial = $dir/serial # The current serial number
+crlnumber = $dir/crlnumber # the current crl number
+ # must be commented out to leave a V1 CRL
+crl = $dir/crl.pem # The current CRL
+private_key = $dir/private/cakey.pem# The private key
+RANDFILE = $dir/private/.rand # private random number file
+
+x509_extensions = usr_cert # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt = ca_default # Subject Name options
+cert_opt = ca_default # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions = crl_ext
+
+default_days = 365 # how long to certify for
+default_crl_days= 30 # how long before next CRL
+default_md = sha256 # use SHA-256 by default
+preserve = no # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName = match
+stateOrProvinceName = match
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+####################################################################
+[ req ]
+default_bits = 1024
+default_keyfile = privkey.pem
+distinguished_name = req_distinguished_name
+attributes = req_attributes
+x509_extensions = v3_ca # The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options.
+# default: PrintableString, T61String, BMPString.
+# pkix : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = AU
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+#stateOrProvinceName_default = Some-State
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName = Second Organization Name (eg, company)
+#1.organizationName_default = World Wide Web Pty Ltd
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_max = 64
+
+emailAddress = Email Address
+emailAddress_max = 64
+
+# SET-ex3 = SET extension number 3
+
+[ req_attributes ]
+challengePassword = A challenge password
+challengePassword_min = 4
+challengePassword_max = 20
+
+unstructuredName = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_name
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1 # the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir = ./demoCA # TSA root directory
+serial = $dir/tsaserial # The current serial number (mandatory)
+crypto_device = builtin # OpenSSL engine to use for signing
+signer_cert = $dir/tsacert.pem # The TSA signing certificate
+ # (optional)
+certs = $dir/cacert.pem # Certificate chain to include in reply
+ # (optional)
+signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy = tsa_policy1 # Policy if request did not specify it
+ # (optional)
+other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
+digests = md5, sha1 # Acceptable message digests (mandatory)
+accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
+clock_precision_digits = 0 # number of digits after dot. (optional)
+ordering = yes # Is ordering defined for timestamps?
+ # (optional, default: no)
+tsa_name = yes # Must the TSA name be included in the reply?
+ # (optional, default: no)
+ess_cert_id_chain = no # Must the ESS cert id chain be included?
+ # (optional, default: no)
+
+[ alt_name ]
+IP.1 = 127.0.0.1
+IP.2 = ::1
diff --git a/src/lib/asiolink/testutils/ca/server-conf.cnf b/src/lib/asiolink/testutils/ca/server-conf.cnf
new file mode 100644
index 0000000..843b641
--- /dev/null
+++ b/src/lib/asiolink/testutils/ca/server-conf.cnf
@@ -0,0 +1,354 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME = .
+RANDFILE = $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file = $ENV::HOME/.oid
+oid_section = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions =
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca = CA_default # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir = ./demoCA # Where everything is kept
+certs = $dir/certs # Where the issued certs are kept
+crl_dir = $dir/crl # Where the issued crl are kept
+database = $dir/index.txt # database index file.
+#unique_subject = no # Set to 'no' to allow creation of
+ # several ctificates with same subject.
+new_certs_dir = $dir/newcerts # default place for new certs.
+
+certificate = $dir/cacert.pem # The CA certificate
+serial = $dir/serial # The current serial number
+crlnumber = $dir/crlnumber # the current crl number
+ # must be commented out to leave a V1 CRL
+crl = $dir/crl.pem # The current CRL
+private_key = $dir/private/cakey.pem# The private key
+RANDFILE = $dir/private/.rand # private random number file
+
+x509_extensions = usr_cert # The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt = ca_default # Subject Name options
+cert_opt = ca_default # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions = crl_ext
+
+default_days = 365 # how long to certify for
+default_crl_days= 30 # how long before next CRL
+default_md = sha256 # use SHA-256 by default
+preserve = no # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName = match
+stateOrProvinceName = match
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+####################################################################
+[ req ]
+default_bits = 1024
+default_keyfile = privkey.pem
+distinguished_name = req_distinguished_name
+attributes = req_attributes
+x509_extensions = v3_ca # The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options.
+# default: PrintableString, T61String, BMPString.
+# pkix : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = AU
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+#stateOrProvinceName_default = Some-State
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName = Second Organization Name (eg, company)
+#1.organizationName_default = World Wide Web Pty Ltd
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_max = 64
+
+emailAddress = Email Address
+emailAddress_max = 64
+
+# SET-ex3 = SET extension number 3
+
+[ req_attributes ]
+challengePassword = A challenge password
+challengePassword_min = 4
+challengePassword_max = 20
+
+unstructuredName = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_name
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType = server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment = "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1 # the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir = ./demoCA # TSA root directory
+serial = $dir/tsaserial # The current serial number (mandatory)
+crypto_device = builtin # OpenSSL engine to use for signing
+signer_cert = $dir/tsacert.pem # The TSA signing certificate
+ # (optional)
+certs = $dir/cacert.pem # Certificate chain to include in reply
+ # (optional)
+signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy = tsa_policy1 # Policy if request did not specify it
+ # (optional)
+other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
+digests = md5, sha1 # Acceptable message digests (mandatory)
+accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
+clock_precision_digits = 0 # number of digits after dot. (optional)
+ordering = yes # Is ordering defined for timestamps?
+ # (optional, default: no)
+tsa_name = yes # Must the TSA name be included in the reply?
+ # (optional, default: no)
+ess_cert_id_chain = no # Must the ESS cert id chain be included?
+ # (optional, default: no)
+
+[ alt_name ]
+DNS.1 = localhost
diff --git a/src/lib/asiolink/testutils/openssl_sample_client.cc b/src/lib/asiolink/testutils/openssl_sample_client.cc
new file mode 100644
index 0000000..d71de4e
--- /dev/null
+++ b/src/lib/asiolink/testutils/openssl_sample_client.cc
@@ -0,0 +1,187 @@
+//
+// client.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <config.h>
+
+#include <iostream>
+
+#ifdef HAVE_GENERIC_TLS_METHOD
+
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+
+#include <asiolink/asio_wrapper.h>
+#include <boost/asio/ssl.hpp>
+
+using boost::asio::ip::tcp;
+using std::placeholders::_1;
+using std::placeholders::_2;
+
+inline std::string CA_(const std::string& filename) {
+ return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+enum { max_length = 1024 };
+
+class client
+{
+public:
+ client(boost::asio::io_service& io_context,
+ boost::asio::ssl::context& context,
+ const tcp::endpoint& endpoint)
+ : socket_(io_context, context)
+ {
+ socket_.set_verify_mode(boost::asio::ssl::verify_peer |
+ boost::asio::ssl::verify_fail_if_no_peer_cert);
+ socket_.set_verify_callback(
+ std::bind(&client::verify_certificate, this, _1, _2));
+
+ connect(endpoint);
+ }
+
+private:
+ bool verify_certificate(bool preverified,
+ boost::asio::ssl::verify_context& ctx)
+ {
+ // The verify callback can be used to check whether the certificate that is
+ // being presented is valid for the peer. For example, RFC 2818 describes
+ // the steps involved in doing this for HTTPS. Consult the OpenSSL
+ // documentation for more details. Note that the callback is called once
+ // for each certificate in the certificate chain, starting from the root
+ // certificate authority.
+
+ // In this example we will simply print the certificate's subject name.
+ char subject_name[256];
+ X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
+ X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
+ std::cout << "Verifying " << subject_name << "\n";
+
+ return preverified;
+ }
+
+ void connect(const tcp::endpoint& endpoint)
+ {
+ socket_.lowest_layer().async_connect(endpoint,
+ [this](const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ handshake();
+ }
+ else
+ {
+ std::cout << "Connect failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void handshake()
+ {
+ socket_.async_handshake(boost::asio::ssl::stream_base::client,
+ [this](const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ send_request();
+ }
+ else
+ {
+ std::cout << "Handshake failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void send_request()
+ {
+ std::cout << "Enter message: ";
+ std::cin.getline(request_, max_length);
+ size_t request_length = std::strlen(request_);
+
+ boost::asio::async_write(socket_,
+ boost::asio::buffer(request_, request_length),
+ [this](const boost::system::error_code& error, std::size_t length)
+ {
+ if (!error)
+ {
+ receive_response(length);
+ }
+ else
+ {
+ std::cout << "Write failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void receive_response(std::size_t length)
+ {
+ boost::asio::async_read(socket_,
+ boost::asio::buffer(reply_, length),
+ [this](const boost::system::error_code& error, std::size_t length)
+ {
+ if (!error)
+ {
+ std::cout << "Reply: ";
+ std::cout.write(reply_, length);
+ std::cout << "\n";
+ }
+ else
+ {
+ std::cout << "Read failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ boost::asio::ssl::stream<tcp::socket> socket_;
+ char request_[max_length];
+ char reply_[max_length];
+};
+
+int main(int argc, char* argv[])
+{
+ try
+ {
+ if (argc != 3)
+ {
+ std::cerr << "Usage: client <addr> <port>\n";
+ return 1;
+ }
+
+ boost::asio::io_service io_context;
+
+ using namespace std; // For atoi.
+ tcp::endpoint endpoint(
+ boost::asio::ip::address::from_string(argv[1]), atoi(argv[2]));
+
+ boost::asio::ssl::context ctx(boost::asio::ssl::context::method::tls);
+ ctx.load_verify_file(CA_("kea-ca.crt"));
+ ctx.use_certificate_chain_file(CA_("kea-client.crt"));
+ ctx.use_private_key_file(CA_("kea-client.key"),
+ boost::asio::ssl::context::pem);
+
+ client c(io_context, ctx, endpoint);
+
+ io_context.run();
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Exception: " << e.what() << "\n";
+ }
+
+ return 0;
+}
+#else // !HAVE_GENERIC_TLS_METHOD
+
+int main()
+{
+ std::cerr << "this tool requires recent boost version (>= 1.64)\n";
+ return 0;
+}
+#endif
diff --git a/src/lib/asiolink/testutils/openssl_sample_server.cc b/src/lib/asiolink/testutils/openssl_sample_server.cc
new file mode 100644
index 0000000..b92e253
--- /dev/null
+++ b/src/lib/asiolink/testutils/openssl_sample_server.cc
@@ -0,0 +1,193 @@
+//
+// server.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+// Use the cpp03 version because the cpp11 version does not compile with
+// some g++ e.g. on Fedora 33.
+
+#include <config.h>
+
+#include <iostream>
+
+#ifdef HAVE_GENERIC_TLS_METHOD
+
+#include <cstdlib>
+#include <boost/bind/bind.hpp>
+
+#include <asiolink/asio_wrapper.h>
+#include <boost/asio/ssl.hpp>
+
+inline std::string CA_(const std::string& filename) {
+ return (std::string(TEST_CA_DIR) + "/" + filename);
+}
+
+typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
+
+class session
+{
+public:
+ session(boost::asio::io_service& io_context,
+ boost::asio::ssl::context& context)
+ : socket_(io_context, context)
+ {
+ }
+
+ ssl_socket::lowest_layer_type& socket()
+ {
+ return socket_.lowest_layer();
+ }
+
+ void start()
+ {
+ socket_.async_handshake(boost::asio::ssl::stream_base::server,
+ boost::bind(&session::handle_handshake, this,
+ boost::asio::placeholders::error));
+ }
+
+ void handle_handshake(const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ socket_.async_read_some(boost::asio::buffer(data_, max_length),
+ boost::bind(&session::handle_read, this,
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ }
+ else
+ {
+ std::cerr << "handshake error '" << error.message() << "'\n";
+ delete this;
+ }
+ }
+
+ void handle_read(const boost::system::error_code& error,
+ size_t bytes_transferred)
+ {
+ if (!error)
+ {
+ boost::asio::async_write(socket_,
+ boost::asio::buffer(data_, bytes_transferred),
+ boost::bind(&session::handle_write, this,
+ boost::asio::placeholders::error));
+ }
+ else
+ {
+ delete this;
+ }
+ }
+
+ void handle_write(const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ socket_.async_read_some(boost::asio::buffer(data_, max_length),
+ boost::bind(&session::handle_read, this,
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
+ }
+ else
+ {
+ delete this;
+ }
+ }
+
+private:
+ ssl_socket socket_;
+ enum { max_length = 1024 };
+ char data_[max_length];
+};
+
+class server
+{
+public:
+ server(boost::asio::io_service& io_context, unsigned short port)
+ : io_context_(io_context),
+ acceptor_(io_context,
+ boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
+ context_(boost::asio::ssl::context::method::tls)
+ {
+ //context_.set_options(
+ // boost::asio::ssl::context::default_workarounds
+ // | boost::asio::ssl::context::no_sslv2
+ // | boost::asio::ssl::context::single_dh_use);
+ //context_.set_password_callback(boost::bind(&server::get_password, this));
+ context_.set_verify_mode(boost::asio::ssl::verify_peer |
+ boost::asio::ssl::verify_fail_if_no_peer_cert);
+ context_.load_verify_file(CA_("kea-ca.crt"));
+ context_.use_certificate_chain_file(CA_("kea-server.crt"));
+ context_.use_private_key_file(CA_("kea-server.key"),
+ boost::asio::ssl::context::pem);
+ //context_.use_tmp_dh_file("dh2048.pem");
+
+ start_accept();
+ }
+
+ void start_accept()
+ {
+ session* new_session = new session(io_context_, context_);
+ acceptor_.async_accept(new_session->socket(),
+ boost::bind(&server::handle_accept, this, new_session,
+ boost::asio::placeholders::error));
+ }
+
+ void handle_accept(session* new_session,
+ const boost::system::error_code& error)
+ {
+ if (!error)
+ {
+ new_session->start();
+ }
+ else
+ {
+ delete new_session;
+ }
+
+ start_accept();
+ }
+
+private:
+ boost::asio::io_service& io_context_;
+ boost::asio::ip::tcp::acceptor acceptor_;
+ boost::asio::ssl::context context_;
+};
+
+int main(int argc, char* argv[])
+{
+ try
+ {
+ if (argc != 2)
+ {
+ std::cerr << "Usage: server <port>\n";
+ return 1;
+ }
+
+ boost::asio::io_service io_context;
+
+ using namespace std; // For atoi.
+ server s(io_context, atoi(argv[1]));
+
+ io_context.run();
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Exception: " << e.what() << "\n";
+ }
+
+ return 0;
+}
+
+#else // !HAVE_GENERIC_TLS_METHOD
+
+int main()
+{
+ std::cerr << "this tool requires recent boost version (>= 1.64)\n";
+ return 0;
+}
+#endif
+
diff --git a/src/lib/asiolink/testutils/test_server_unix_socket.cc b/src/lib/asiolink/testutils/test_server_unix_socket.cc
new file mode 100644
index 0000000..7f7007d
--- /dev/null
+++ b/src/lib/asiolink/testutils/test_server_unix_socket.cc
@@ -0,0 +1,331 @@
+// Copyright (C) 2017-2020 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 <asiolink/asio_wrapper.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <set>
+#include <sstream>
+
+using namespace boost::asio::local;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief ASIO unix domain socket.
+typedef stream_protocol::socket UnixSocket;
+
+/// @brief Pointer to the ASIO unix domain socket.
+typedef boost::shared_ptr<UnixSocket> UnixSocketPtr;
+
+/// @brief Callback function invoked when response is sent from the server.
+typedef std::function<void()> SentResponseCallback;
+
+/// @brief Connection to the server over unix domain socket.
+///
+/// It reads the data over the socket, sends responses and closes a socket.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// It starts asynchronous read operation.
+ ///
+ /// @param unix_socket Pointer to the unix domain socket into which
+ /// connection has been accepted.
+ /// @param custom_response Custom response that the server should send.
+ /// @param sent_response_callback Callback function to be invoked when
+ /// server sends a response.
+ Connection(const UnixSocketPtr& unix_socket,
+ const std::string custom_response,
+ SentResponseCallback sent_response_callback)
+ : socket_(unix_socket), custom_response_(custom_response),
+ sent_response_callback_(sent_response_callback) {
+ }
+
+ /// @brief Starts asynchronous read from the socket.
+ void start() {
+ socket_->async_read_some(boost::asio::buffer(&raw_buf_[0], raw_buf_.size()),
+ std::bind(&Connection::readHandler, shared_from_this(),
+ ph::_1, // error
+ ph::_2)); // bytes_transferred
+ }
+
+ /// @brief Closes the socket.
+ void stop() {
+ try {
+ socket_->close();
+
+ } catch (...) {
+ // ignore errors when closing the socket.
+ }
+ }
+
+ /// @brief Handler invoked when data have been received over the socket.
+ ///
+ /// This is the handler invoked when the data have been received over the
+ /// socket. If custom response has been specified, this response is sent
+ /// back to the client. Otherwise, the handler echoes back the request
+ /// and prepends the word "received ". Finally, it calls a custom
+ /// callback function (specified in the constructor) to notify that the
+ /// response has been sent over the socket.
+ ///
+ /// @param bytes_transferred Number of bytes received.
+ void
+ readHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ // This is most likely due to the abort.
+ if (ec) {
+ // An error occurred so let's close the socket.
+ stop();
+ return;
+ }
+
+ if (!custom_response_.empty()) {
+ boost::asio::write(*socket_,
+ boost::asio::buffer(custom_response_.c_str(), custom_response_.size()));
+
+ } else {
+ std::string received(&raw_buf_[0], bytes_transferred);
+ std::string response("received " + received);
+ boost::asio::write(*socket_,
+ boost::asio::buffer(response.c_str(), response.size()));
+ }
+
+ /// @todo We're taking simplistic approach and send a response right away
+ /// after receiving data over the socket. Therefore, after responding we
+ /// do not schedule another read. We could extend this logic slightly to
+ /// parse the received data and see when we've got enough data before we
+ /// send a response. However, the current unit tests don't really require
+ /// that.
+
+ // Invoke callback function to notify that the response has been sent.
+ sent_response_callback_();
+ }
+
+private:
+
+ /// @brief Pointer to the unix domain socket.
+ UnixSocketPtr socket_;
+
+ /// @brief Custom response to be sent to the client.
+ std::string custom_response_;
+
+ /// @brief Receive buffer.
+ std::array<char, 1024> raw_buf_;
+
+ /// @brief Pointer to the callback function to be invoked when response
+ /// has been sent.
+ SentResponseCallback sent_response_callback_;
+
+};
+
+/// @brief Pointer to a Connection object.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Connection pool.
+///
+/// Holds all connections established with the server and gracefully
+/// terminates these connections.
+class ConnectionPool {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ ConnectionPool(IOService& io_service)
+ : io_service_(io_service), connections_(), next_socket_(),
+ response_num_(0) {
+ }
+
+ /// @brief Destructor.
+ ~ConnectionPool() {
+ stopAll();
+ }
+
+ /// @brief Creates new unix domain socket and returns it.
+ ///
+ /// This convenience method creates a socket which can be used to accept
+ /// new connections. If such socket already exists, it is returned.
+ ///
+ /// @return Pointer to the socket.
+ UnixSocketPtr getSocket() {
+ if (!next_socket_) {
+ next_socket_.reset(new UnixSocket(io_service_.get_io_service()));
+ }
+ return (next_socket_);
+ }
+
+ /// @brief Starts new connection.
+ ///
+ /// The socket returned by the @ref ConnectionPool::getSocket is used to
+ /// create new connection. Then, the @ref next_socket_ is reset, to force
+ /// the @ref ConnectionPool::getSocket to generate a new socket for a
+ /// next connection.
+ ///
+ /// @param custom_response Custom response to be sent to the client.
+ void start(const std::string& custom_response) {
+ ConnectionPtr conn(new Connection(next_socket_, custom_response, [this] {
+ ++response_num_;
+ }));
+ conn->start();
+
+ connections_.insert(conn);
+ next_socket_.reset();
+ }
+
+ /// @brief Stops the given connection.
+ ///
+ /// @param conn Pointer to the connection to be stopped.
+ void stop(const ConnectionPtr& conn) {
+ conn->stop();
+ connections_.erase(conn);
+ }
+
+ /// @brief Stops all connections.
+ void stopAll() {
+ for (auto conn = connections_.begin(); conn != connections_.end();
+ ++conn) {
+ (*conn)->stop();
+ }
+ connections_.clear();
+ }
+
+ /// @brief Returns number of responses sent so far.
+ size_t getResponseNum() const {
+ return (response_num_);
+ }
+
+private:
+
+ /// @brief Reference to the IO service.
+ IOService& io_service_;
+
+ /// @brief Container holding established connections.
+ std::set<ConnectionPtr> connections_;
+
+ /// @brief Holds pointer to the generated socket.
+ ///
+ /// This socket will be used by the next connection.
+ UnixSocketPtr next_socket_;
+
+ /// @brief Holds the number of sent responses.
+ size_t response_num_;
+};
+
+
+TestServerUnixSocket::TestServerUnixSocket(IOService& io_service,
+ const std::string& socket_file_path,
+ const std::string& custom_response)
+ : io_service_(io_service),
+ server_endpoint_(socket_file_path),
+ server_acceptor_(io_service_.get_io_service()),
+ test_timer_(io_service_),
+ custom_response_(custom_response),
+ connection_pool_(new ConnectionPool(io_service)),
+ stopped_(false),
+ running_(false) {
+}
+
+TestServerUnixSocket::~TestServerUnixSocket() {
+ server_acceptor_.close();
+}
+
+void
+TestServerUnixSocket::generateCustomResponse(const uint64_t response_size) {
+ std::ostringstream s;
+ s << "{";
+ while (s.tellp() < response_size) {
+ s << "\"param\": \"value\",";
+ }
+ s << "}";
+ custom_response_ = s.str();
+}
+
+void
+TestServerUnixSocket::startTimer(const long test_timeout) {
+ test_timer_.setup(std::bind(&TestServerUnixSocket::timeoutHandler, this),
+ test_timeout, IntervalTimer::ONE_SHOT);
+}
+
+void
+TestServerUnixSocket::stopServer() {
+ test_timer_.cancel();
+ server_acceptor_.cancel();
+ connection_pool_->stopAll();
+}
+
+void
+TestServerUnixSocket::bindServerSocket(const bool use_thread) {
+ server_acceptor_.open();
+ server_acceptor_.bind(server_endpoint_);
+ server_acceptor_.listen();
+ accept();
+
+ // When threads are in use, we need to post a handler which will be invoked
+ // when the thread has already started and the IO service is running. The
+ // main thread can move forward when it receives this signal from the handler.
+ if (use_thread) {
+ io_service_.post(std::bind(&TestServerUnixSocket::signalRunning,
+ this));
+ }
+}
+
+void
+TestServerUnixSocket::acceptHandler(const boost::system::error_code& ec) {
+ if (ec) {
+ return;
+ }
+
+ connection_pool_->start(custom_response_);
+ accept();
+}
+
+void
+TestServerUnixSocket::accept() {
+ server_acceptor_.async_accept(*(connection_pool_->getSocket()),
+ std::bind(&TestServerUnixSocket::acceptHandler, this,
+ ph::_1)); // error
+}
+
+void
+TestServerUnixSocket::signalRunning() {
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ running_ = true;
+ }
+ condvar_.notify_one();
+}
+
+void
+TestServerUnixSocket::waitForRunning() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ while (!running_) {
+ condvar_.wait(lock);
+ }
+}
+
+void
+TestServerUnixSocket::timeoutHandler() {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ io_service_.stop();
+ stopped_ = true;
+}
+
+size_t
+TestServerUnixSocket::getResponseNum() const {
+ return (connection_pool_->getResponseNum());
+}
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/testutils/test_server_unix_socket.h b/src/lib/asiolink/testutils/test_server_unix_socket.h
new file mode 100644
index 0000000..c272ee7
--- /dev/null
+++ b/src/lib/asiolink/testutils/test_server_unix_socket.h
@@ -0,0 +1,171 @@
+// Copyright (C) 2017-2020 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 TEST_SERVER_UNIX_SOCKET_H
+#define TEST_SERVER_UNIX_SOCKET_H
+
+#include <config.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <stdint.h>
+#include <string>
+#include <mutex>
+#include <condition_variable>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+class ConnectionPool;
+
+/// @brief Provides unix domain socket functionality for unit tests.
+///
+/// This class represents a server side socket. It can be used to
+/// test client's transmission over the unix domain socket. By default,
+/// the server side socket echoes the client's message so the client's
+/// message (prefixed with the word "received").
+///
+/// It is also possible to specify a custom response from the server
+/// instead of echoing back the request.
+///
+/// It is possible to make multiple connections to the server side
+/// socket simultaneously.
+///
+/// The test should perform IOService::run_one until it finds that
+/// the number of responses sent by the server is greater than
+/// expected. The number of responses sent so far can be retrieved
+/// using @ref TestServerUnixSocket::getResponseNum.
+///
+/// This class uses @c shared_from_this() to pass its instance to the
+/// @c std::bind function, thus the caller must store shared pointer
+/// to this object.
+class TestServerUnixSocket {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ /// @param socket_file_path Socket file path.
+ /// @param custom_response Custom response to be sent to the client.
+ TestServerUnixSocket(IOService& io_service,
+ const std::string& socket_file_path,
+ const std::string& custom_response = "");
+
+ /// @brief Destructor.
+ ///
+ /// Closes active connections.
+ ~TestServerUnixSocket();
+
+ /// @brief Starts timer for detecting test timeout.
+ ///
+ /// @param test_timeout Test timeout in milliseconds.
+ void startTimer(const long test_timeout);
+
+ /// @brief Cancels all asynchronous operations.
+ void stopServer();
+
+ /// @brief Generates response of a given length.
+ ///
+ /// Note: The response may be a few bytes larger than requested.
+ ///
+ /// @param response_size Desired response size.
+ void generateCustomResponse(const uint64_t response_size);
+
+ /// @brief Creates and binds server socket.
+ ///
+ /// @param use_thread Boolean value indicating if the IO service
+ /// is running in thread.
+ void bindServerSocket(const bool use_thread = false);
+
+ /// @brief Server acceptor handler.
+ ///
+ /// @param ec Error code.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ void timeoutHandler();
+
+ /// @brief Return number of responses sent so far to the clients.
+ size_t getResponseNum() const;
+
+ /// @brief Indicates if the server has been stopped.
+ bool isStopped() {
+ return (stopped_);
+ }
+
+ /// @brief Waits for the server signal that it is running.
+ ///
+ /// When the caller starts the service he indicates whether
+ /// IO service will be running in thread or not. If threads
+ /// are used the caller has to wait for the IO service to
+ /// actually run. In such case this function should be invoked
+ /// which waits for a posted callback to be executed. When this
+ /// happens it means that IO service is running and the main
+ /// thread can move forward.
+ void waitForRunning();
+
+private:
+
+ /// @brief Asynchronously accept new connections.
+ void accept();
+
+ /// @brief Handler invoked to signal that server is running.
+ ///
+ /// This is used only when thread is used to run IO service.
+ void signalRunning();
+
+ /// @brief IO service used by the tests.
+ IOService& io_service_;
+
+ /// @brief Server endpoint.
+ boost::asio::local::stream_protocol::endpoint server_endpoint_;
+ /// @brief Server acceptor.
+ boost::asio::local::stream_protocol::acceptor server_acceptor_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Holds custom response to be sent to the client.
+ std::string custom_response_;
+
+ /// @brief Pool of connections.
+ boost::shared_ptr<ConnectionPool> connection_pool_;
+
+ /// @brief Indicates if IO service has been stopped as a result of
+ /// a timeout.
+ bool stopped_;
+
+ /// @brief Indicates if the server in a thread is running.
+ bool running_;
+
+ /// @brief Mutex used by the server.
+ ///
+ /// Mutex is used in situations when server's IO service is being run in a
+ /// thread to synchronize this thread with a main thread using
+ /// @ref signalRunning and @ref waitForRunning.
+ std::mutex mutex_;
+
+ /// @brief Conditional variable used by the server.
+ ///
+ /// Conditional variable is used in situations when server's IO service is
+ /// being run in a thread to synchronize this thread with a main thread
+ /// using @ref signalRunning and @ref waitForRunning.
+ std::condition_variable condvar_;
+};
+
+/// @brief Pointer to the @ref TestServerUnixSocket.
+typedef boost::shared_ptr<TestServerUnixSocket> TestServerUnixSocketPtr;
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TEST_SERVER_UNIX_SOCKET_H
diff --git a/src/lib/asiolink/testutils/test_tls.cc b/src/lib/asiolink/testutils/test_tls.cc
new file mode 100644
index 0000000..de3a7b8
--- /dev/null
+++ b/src/lib/asiolink/testutils/test_tls.cc
@@ -0,0 +1,74 @@
+// Copyright (C) 2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/testutils/test_tls.h>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Configure the TLS server.
+void configServer(TlsContextPtr& ctx) {
+ std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+ std::string cert(std::string(TEST_CA_DIR) + "/kea-server.crt");
+ std::string key(std::string(TEST_CA_DIR) + "/kea-server.key");
+ TlsContext::configure(ctx, TlsRole::SERVER, ca, cert, key, true);
+}
+
+/// @brief Configure the TLS server trusting the self-signed client.
+void configTrustedSelf(TlsContextPtr& ctx) {
+ std::string ca(std::string(TEST_CA_DIR) + "/kea-self.crt");
+ std::string cert(std::string(TEST_CA_DIR) + "/kea-server.crt");
+ std::string key(std::string(TEST_CA_DIR) + "/kea-server.key");
+ TlsContext::configure(ctx, TlsRole::SERVER, ca, cert, key, true);
+}
+
+/// @brief Configure the TLS server with no client certificate request.
+void configServerNoReq(TlsContextPtr& ctx) {
+ std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+ std::string cert(std::string(TEST_CA_DIR) + "/kea-server.crt");
+ std::string key(std::string(TEST_CA_DIR) + "/kea-server.key");
+ TlsContext::configure(ctx, TlsRole::SERVER, ca, cert, key, false);
+}
+
+/// @brief Configure the TLS server with no subject alternative name.
+void configServerRaw(TlsContextPtr& ctx) {
+ std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+ std::string cert(std::string(TEST_CA_DIR) + "/kea-server-raw.crt");
+ std::string key(std::string(TEST_CA_DIR) + "/kea-server.key");
+ TlsContext::configure(ctx, TlsRole::SERVER, ca, cert, key, true);
+}
+
+/// @brief Configure the TLS client.
+void configClient(TlsContextPtr& ctx) {
+ std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+ std::string cert(std::string(TEST_CA_DIR) + "/kea-client.crt");
+ std::string key(std::string(TEST_CA_DIR) + "/kea-client.key");
+ TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+/// @brief Configure another TLS client.
+void configOther(TlsContextPtr& ctx) {
+ std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+ std::string cert(std::string(TEST_CA_DIR) + "/kea-other.crt");
+ std::string key(std::string(TEST_CA_DIR) + "/kea-other.key");
+ TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+/// @brief Configure self-signed TLS client.
+void configSelf(TlsContextPtr& ctx) {
+ std::string ca(std::string(TEST_CA_DIR) + "/kea-ca.crt");
+ std::string cert(std::string(TEST_CA_DIR) + "/kea-self.crt");
+ std::string key(std::string(TEST_CA_DIR) + "/kea-self.key");
+ TlsContext::configure(ctx, TlsRole::CLIENT, ca, cert, key, true);
+}
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/testutils/test_tls.h b/src/lib/asiolink/testutils/test_tls.h
new file mode 100644
index 0000000..ee27015
--- /dev/null
+++ b/src/lib/asiolink/testutils/test_tls.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 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 TEST_TLS_H
+#define TEST_TLS_H
+
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before test_tls.h
+#endif
+
+#include <asiolink/crypto_tls.h>
+
+#include <string>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Configure the TLS server.
+void configServer(TlsContextPtr& ctx);
+
+/// @brief Configure trusted self-signed TLS server.
+void configTrustedSelf(TlsContextPtr& ctx);
+
+/// @brief Configure the TLS server with no client certificate request.
+void configServerNoReq(TlsContextPtr& ctx);
+
+/// @brief Configure the TLS server with no subject alternative name.
+void configServerRaw(TlsContextPtr& ctx);
+
+/// @brief Configure the TLS client.
+void configClient(TlsContextPtr& ctx);
+
+/// @brief Configure another TLS client.
+void configOther(TlsContextPtr& ctx);
+
+/// @brief Configure self-signed TLS client.
+void configSelf(TlsContextPtr& ctx);
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TEST_TLS_H
diff --git a/src/lib/asiolink/testutils/timed_signal.cc b/src/lib/asiolink/testutils/timed_signal.cc
new file mode 100644
index 0000000..28d4b1b
--- /dev/null
+++ b/src/lib/asiolink/testutils/timed_signal.cc
@@ -0,0 +1,17 @@
+// Copyright (C) 2021 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 <asiolink/testutils/timed_signal.h>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/testutils/timed_signal.h b/src/lib/asiolink/testutils/timed_signal.h
new file mode 100644
index 0000000..f1cd0c9
--- /dev/null
+++ b/src/lib/asiolink/testutils/timed_signal.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2021-2022 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 TIMED_SIGNAL_H
+#define TIMED_SIGNAL_H
+
+#include <config.h>
+
+#include <asiolink/interval_timer.h>
+#include <signal.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace asiolink {
+namespace test {
+
+/// @brief Implements a time-delayed signal
+///
+/// Given an IOService, a signal number, and a time period, this class will
+/// send (raise) the signal to the current process.
+class TimedSignal {
+public:
+ /// @brief Constructor
+ ///
+ /// @param io_service IOService to run the timer
+ /// @param signum OS signal value (e.g. SIGHUP, SIGUSR1 ...)
+ /// @param milliseconds time in milliseconds to wait until the signal is
+ /// raised.
+ /// @param mode selects between a one-shot signal or a signal which repeats
+ /// at "milliseconds" interval.
+ TimedSignal(asiolink::IOService& io_service, int signum, int milliseconds,
+ const asiolink::IntervalTimer::Mode& mode =
+ asiolink::IntervalTimer::ONE_SHOT)
+ : timer_(new asiolink::IntervalTimer(io_service)) {
+ timer_->setup(SendSignalCallback(signum), milliseconds, mode);
+ }
+
+ /// @brief Cancels the given timer.
+ void cancel() {
+ if (timer_) {
+ timer_->cancel();
+ }
+ }
+
+ /// @brief Destructor.
+ ~TimedSignal() {
+ cancel();
+ }
+
+ /// @brief Callback for the TimeSignal's internal timer.
+ class SendSignalCallback {
+ public:
+
+ /// @brief Constructor
+ ///
+ /// @param signum OS signal value of the signal to send
+ SendSignalCallback(int signum) : signum_(signum) {
+ }
+
+ /// @brief Callback method invoked when the timer expires
+ ///
+ /// Calls raise with the given signal which should generate that
+ /// signal to the given process.
+ void operator()() {
+ ASSERT_EQ(0, raise(signum_));
+ return;
+ }
+
+ private:
+ /// @brief Stores the OS signal value to send.
+ int signum_;
+ };
+
+private:
+ /// @brief Timer which controls when the signal is sent.
+ asiolink::IntervalTimerPtr timer_;
+};
+
+} // end of namespace isc::asiolink::test
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TIMED_SIGNAL_H
diff --git a/src/lib/asiolink/tls_acceptor.h b/src/lib/asiolink/tls_acceptor.h
new file mode 100644
index 0000000..c575559
--- /dev/null
+++ b/src/lib/asiolink/tls_acceptor.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2016-2021 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 TLS_ACCEPTOR_H
+#define TLS_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/tls_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <netinet/in.h>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Provides a service for accepting new TLS connections.
+///
+/// @tparam C Acceptor callback type.
+template<typename C>
+class TLSAcceptor : public TCPAcceptor<C> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service.
+ explicit TLSAcceptor(IOService& io_service) : TCPAcceptor<C>(io_service) {
+ }
+
+ /// @brief Destructor.
+ virtual ~TLSAcceptor() { }
+
+ /// @brief Asynchronously accept new connection.
+ ///
+ /// This method accepts new connection into the specified socket. When the
+ /// new connection arrives or an error occurs the specified callback function
+ /// is invoked.
+ ///
+ /// @param socket Socket into which connection should be accepted.
+ /// @param callback Callback function to be invoked when the new connection
+ /// arrives.
+ /// @tparam SocketCallback Type of the callback for the @ref TLSSocket.
+ template<typename SocketCallback>
+ void asyncAccept(const TLSSocket<SocketCallback>& socket, C& callback) {
+ TCPAcceptor<C>::acceptor_->async_accept(socket.getASIOSocket(), callback);
+ }
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif
diff --git a/src/lib/asiolink/tls_socket.h b/src/lib/asiolink/tls_socket.h
new file mode 100644
index 0000000..cdd2f78
--- /dev/null
+++ b/src/lib/asiolink/tls_socket.h
@@ -0,0 +1,519 @@
+// Copyright (C) 2021 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 TLS_SOCKET_H
+#define TLS_SOCKET_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/crypto_tls.h>
+#include <asiolink/tcp_socket.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief The @c TLSSocket class is a concrete derived class of @c IOAsioSocket
+/// that represents a TLS socket.
+///
+/// @tparam C Callback type.
+template <typename C>
+class TLSSocket : public IOAsioSocket<C>, private boost::noncopyable {
+public:
+
+ /// @brief Constructor from a TLS stream.
+ ///
+ /// It is assumed that the caller will open and close the stream,
+ /// so these operations are a no-op for that stream.
+ ///
+ /// @param stream The TLS stream.
+ TLSSocket(TlsStream<C>& stream);
+
+ /// @brief Constructor.
+ ///
+ /// Used when the TLSSocket is being asked to manage its own internal
+ /// socket. In this case, the open() and close() methods are used.
+ ///
+ /// @param service I/O Service object used to manage the socket.
+ /// @param context Pointer to TLS context.
+ TLSSocket(IOService& service, TlsContextPtr context);
+
+ /// @brief Destructor.
+ virtual ~TLSSocket() { }
+
+ /// @brief Return file descriptor of underlying socket.
+ virtual int getNative() const {
+#if BOOST_VERSION < 106600
+ return (socket_.native());
+#else
+ return (socket_.native_handle());
+#endif
+ }
+
+ /// @brief Return protocol of socket.
+ virtual int getProtocol() const {
+ return (IPPROTO_TCP);
+ }
+
+ /// @brief Is "open()" synchronous predicate.
+ ///
+ /// Indicates that the opening of a TLS socket is asynchronous.
+ virtual bool isOpenSynchronous() const {
+ return (false);
+ }
+
+ /// @brief Checks if the connection is usable.
+ ///
+ /// The connection is usable if the socket is open and the peer has not
+ /// closed its connection.
+ ///
+ /// @return true if the connection is usable.
+ bool isUsable() const {
+ // If the socket is open it doesn't mean that it is still
+ // usable. The connection could have been closed on the other
+ // end. We have to check if we can still use this socket.
+ if (socket_.is_open()) {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = socket_.non_blocking();
+
+ // Set the socket to non blocking mode. We're going to
+ // test if the socket returns would_block status on the
+ // attempt to read from it.
+ socket_.non_blocking(true);
+
+ // Use receive with message peek flag to avoid removing
+ // the data awaiting to be read.
+ char data[2];
+ int err = 0;
+ int cc = recv(getNative(), data, sizeof(data), MSG_PEEK);
+ if (cc < 0) {
+ // Error case.
+ err = errno;
+ } else if (cc == 0) {
+ // End of file.
+ err = -1;
+ }
+
+ // Revert the original non_blocking flag on the socket.
+ socket_.non_blocking(non_blocking_orig);
+
+ // If the connection is alive we'd typically get
+ // would_block status code. If there are any data that
+ // haven't been read we may also get success status. We're
+ // guessing that try_again may also be returned by some
+ // implementations in some situations. Any other error
+ // code indicates a problem with the connection so we
+ // assume that the connection has been closed.
+ return ((err == 0) || (err == EAGAIN) || (err == EWOULDBLOCK));
+ }
+
+ return (false);
+ }
+
+ /// @brief Open Socket.
+ ///
+ /// Opens the TLS socket. This is an asynchronous operation, completion of
+ /// which will be signalled via a call to the callback function.
+ ///
+ /// @param endpoint Endpoint to which the socket will connect.
+ /// @param callback Callback object.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
+
+ /// @brief Perform Handshake.
+ ///
+ /// Perform the TLS handshake. This is an asynchronous operation,
+ /// completion of which will be signalled via a call to the callback
+ /// function.
+ ///
+ /// @param callback Callback object.
+ virtual void handshake(C& callback);
+
+ /// @brief Send Asynchronously.
+ ///
+ /// Calls the underlying socket's async_send() method to send a
+ /// packet of data asynchronously to the remote endpoint. The
+ /// callback will be called on completion.
+ ///
+ /// @param data Data to send.
+ /// @param length Length of data to send.
+ /// @param endpoint Target of the send. (Unused for a TLS socket because
+ /// that was determined when the connection was opened.)
+ /// @param callback Callback object.
+ /// @throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback);
+
+ /// @brief Send Asynchronously without count.
+ ///
+ /// This variant of the method sends data over the TLS socket without
+ /// preceding the data with a data count. Eventually, we should migrate
+ /// the virtual method to not insert the count but there are existing
+ /// classes using the count. Once this migration is done, the existing
+ /// virtual method should be replaced by this method.
+ ///
+ /// @param data Data to send.
+ /// @param length Length of data to send.
+ /// @param callback Callback object.
+ /// @throw BufferTooLarge on attempt to send a buffer larger than 64kB.
+ void asyncSend(const void* data, size_t length, C& callback);
+
+ /// @brief Receive Asynchronously.
+ ///
+ /// Calls the underlying socket's async_receive() method to read a packet
+ /// of data from a remote endpoint. Arrival of the data is signalled via a
+ /// call to the callback function.
+ ///
+ /// @param data Buffer to receive incoming message.
+ /// @param length Length of the data buffer.
+ /// @param offset Offset into buffer where data is to be put.
+ /// @param endpoint Source of the communication.
+ /// @param callback Callback object.
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback);
+
+ /// @brief Process received data packet.
+ ///
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
+ ///
+ /// @param staging Pointer to the start of the staging buffer.
+ /// @param length Amount of data in the staging buffer.
+ /// @param cumulative Amount of data received before the staging buffer is
+ /// processed.
+ /// @param offset Unused.
+ /// @param expected unused.
+ /// @param outbuff Output buffer. Data in the staging buffer is be copied
+ /// to this output buffer in the call.
+ ///
+ /// @return Always true.
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::util::OutputBufferPtr& outbuff);
+
+ /// @brief Cancel I/O On Socket.
+ virtual void cancel();
+
+ /// @brief Close socket.
+ virtual void close();
+
+ /// @brief TLS shutdown.
+ ///
+ /// The callback is called on completion i.e. when the peer performs
+ /// a shutdown or a close.
+ virtual void shutdown(C& callback);
+
+ /// @brief Returns reference to the underlying ASIO socket.
+ ///
+ /// @return Reference to underlying ASIO socket.
+ virtual typename TlsStream<C>::lowest_layer_type& getASIOSocket() const {
+ return (socket_);
+ }
+
+ /// @brief Returns reference to the underlying TLS stream.
+ ///
+ /// @return Reference to underlying TLS stream.
+ virtual TlsStream<C>& getTlsStream() const {
+ return (stream_);
+ }
+
+private:
+ /// Two variables to hold the stream - a stream and a pointer to it. This
+ /// handles the case where a stream is passed to the TLSSocket on
+ /// construction, or where it is asked to manage its own stream.
+
+ /// @brief Pointer to own stream.
+ std::unique_ptr<TlsStream<C>> stream_ptr_;
+
+ /// @brief TLS stream.
+ TlsStream<C>& stream_;
+
+ /// @brief Underlying TCP socket.
+ typename TlsStream<C>::lowest_layer_type& socket_;
+
+ /// @todo Remove temporary buffer
+ /// The current implementation copies the buffer passed to asyncSend() into
+ /// a temporary buffer and precedes it with a two-byte count field. As
+ /// ASIO should really be just about sending and receiving data, the TCP
+ /// code should not do this. If the protocol using this requires a two-byte
+ /// count, it should add it before calling this code. (This may be best
+ /// achieved by altering isc::dns::buffer to have pairs of methods:
+ /// getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
+ /// methods taking into account a two-byte count field.)
+ ///
+ /// The option of sending the data in two operations, the count followed by
+ /// the data was discounted as that would lead to two callbacks which would
+ /// cause problems with the stackless coroutine code.
+
+ /// @brief Send buffer.
+ isc::util::OutputBufferPtr send_buffer_;
+};
+
+// Constructor - caller manages socket.
+
+template <typename C>
+TLSSocket<C>::TLSSocket(TlsStream<C>& stream) :
+ stream_ptr_(), stream_(stream),
+ socket_(stream_.lowest_layer()), send_buffer_() {
+}
+
+// Constructor - create socket on the fly.
+
+template <typename C>
+TLSSocket<C>::TLSSocket(IOService& service, TlsContextPtr context) :
+ stream_ptr_(new TlsStream<C>(service, context)),
+ stream_(*stream_ptr_), socket_(stream_.lowest_layer()), send_buffer_()
+{
+}
+
+// Open the socket.
+
+template <typename C> void
+TLSSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
+ // Ignore opens on already-open socket. Don't throw a failure because
+ // of uncertainties as to what precedes when using asynchronous I/O.
+ // Also allows us a treat a passed-in socket as a self-managed socket.
+ if (!socket_.is_open()) {
+ if (endpoint->getFamily() == AF_INET) {
+ socket_.open(boost::asio::ip::tcp::v4());
+ } else {
+ socket_.open(boost::asio::ip::tcp::v6());
+ }
+
+ // Set options on the socket:
+
+ // Reuse address - allow the socket to bind to a port even if the port
+ // is in the TIMED_WAIT state.
+ socket_.set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
+ // contain a method for getting at the underlying endpoint type - that is in
+ /// the derived class and the two classes differ on return type.
+ isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+ const TCPEndpoint* tcp_endpoint =
+ static_cast<const TCPEndpoint*>(endpoint);
+
+ // Connect to the remote endpoint. On success, the handler will be
+ // called (with one argument - the length argument will default to
+ // zero).
+ socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
+}
+
+// Perform the handshake.
+
+template <typename C> void
+TLSSocket<C>::handshake(C& callback) {
+ if (!socket_.is_open()) {
+ isc_throw(SocketNotOpen, "attempt to perform handshake on "
+ "a TLS socket that is not open");
+ }
+ stream_.handshake(callback);
+}
+
+// Send a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+TLSSocket<C>::asyncSend(const void* data, size_t length, C& callback)
+{
+ if (!socket_.is_open()) {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a TLS socket that is not open");
+ }
+
+ try {
+ send_buffer_.reset(new isc::util::OutputBuffer(length));
+ send_buffer_->writeData(data, length);
+
+ // Send the data.
+ boost::asio::async_write(stream_,
+ boost::asio::buffer(send_buffer_->getData(),
+ send_buffer_->getLength()),
+ callback);
+ } catch (const boost::numeric::bad_numeric_cast&) {
+ isc_throw(BufferTooLarge,
+ "attempt to send buffer larger than 64kB");
+ }
+}
+
+template <typename C> void
+TLSSocket<C>::asyncSend(const void* data, size_t length,
+ const IOEndpoint*, C& callback)
+{
+ if (!socket_.is_open()) {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a TLS socket that is not open");
+ }
+
+ /// Need to copy the data into a temporary buffer and precede it with
+ /// a two-byte count field.
+ /// @todo arrange for the buffer passed to be preceded by the count
+ try {
+ // Ensure it fits into 16 bits
+ uint16_t count = boost::numeric_cast<uint16_t>(length);
+
+ // Copy data into a buffer preceded by the count field.
+ send_buffer_.reset(new isc::util::OutputBuffer(length + 2));
+ send_buffer_->writeUint16(count);
+ send_buffer_->writeData(data, length);
+
+ // ... and send it
+ boost::asio::async_write(stream_,
+ boost::asio::buffer(send_buffer_->getData(),
+ send_buffer_->getLength()),
+ callback);
+ } catch (const boost::numeric::bad_numeric_cast&) {
+ isc_throw(BufferTooLarge,
+ "attempt to send buffer larger than 64kB");
+ }
+}
+
+// Receive a message. Note that the "offset" argument is used as an index
+// into the buffer in order to decide where to put the data. It is up to the
+// caller to initialize the data to zero
+template <typename C> void
+TLSSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback)
+{
+ if (!socket_.is_open()) {
+ isc_throw(SocketNotOpen,
+ "attempt to receive from a TLS socket that is not open");
+ }
+
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+ // does not contain a method for getting at the underlying endpoint
+ // type - that is in the derived class and the two classes differ on
+ // return type.
+ isc_throw_assert(endpoint->getProtocol() == IPPROTO_TCP);
+ TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
+
+ // Write the endpoint details from the communications link. Ideally
+ // we should make IOEndpoint assignable, but this runs in to all sorts
+ // of problems concerning the management of the underlying Boost
+ // endpoint (e.g. if it is not self-managed, is the copied one
+ // self-managed?) The most pragmatic solution is to let Boost take care
+ // of everything and copy details of the underlying endpoint.
+ tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
+
+ // Ensure we can write into the buffer and if so, set the pointer to
+ // where the data will be written.
+ if (offset >= length) {
+ isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+ "TCP receive buffer");
+ }
+ void* buffer_start =
+ static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+ // ... and kick off the read.
+ stream_.async_read_some(boost::asio::buffer(buffer_start, length - offset),
+ callback);
+}
+
+// Is the receive complete?
+
+template <typename C> bool
+TLSSocket<C>::processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::util::OutputBufferPtr& outbuff)
+{
+ // Point to the data in the staging buffer and note how much there is.
+ const uint8_t* data = static_cast<const uint8_t*>(staging);
+ size_t data_length = length;
+
+ // Is the number is "expected" valid? It won't be unless we have received
+ // at least two bytes of data in total for this set of receives.
+ if (cumulative < 2) {
+
+ // "expected" is not valid. Did this read give us enough data to
+ // work it out?
+ cumulative += length;
+ if (cumulative < 2) {
+
+ // Nope, still not valid. This must have been the first packet and
+ // was only one byte long. Tell the fetch code to read the next
+ // packet into the staging buffer beyond the data that is already
+ // there so that the next time we are called we have a complete
+ // TCP count.
+ offset = cumulative;
+ return (false);
+ }
+
+ // Have enough data to interpret the packet count, so do so now.
+ expected = isc::util::readUint16(data, cumulative);
+
+ // We have two bytes less of data to process. Point to the start of the
+ // data and adjust the packet size. Note that at this point,
+ // "cumulative" is the true amount of data in the staging buffer, not
+ // "length".
+ data += 2;
+ data_length = cumulative - 2;
+ } else {
+
+ // Update total amount of data received.
+ cumulative += length;
+ }
+
+ // Regardless of anything else, the next read goes into the start of the
+ // staging buffer.
+ offset = 0;
+
+ // Work out how much data we still have to put in the output buffer. (This
+ // could be zero if we have just interpreted the TCP count and that was
+ // set to zero.)
+ if (expected >= outbuff->getLength()) {
+
+ // Still need data in the output packet. Copy what we can from the
+ // staging buffer to the output buffer.
+ size_t copy_amount = std::min(expected - outbuff->getLength(),
+ data_length);
+ outbuff->writeData(data, copy_amount);
+ }
+
+ // We can now say if we have all the data.
+ return (expected == outbuff->getLength());
+}
+
+// Cancel I/O on the socket. No-op if the socket is not open.
+
+template <typename C> void
+TLSSocket<C>::cancel() {
+ if (socket_.is_open()) {
+ socket_.cancel();
+ }
+}
+
+// TLS shutdown. Can be used for orderly close.
+
+template <typename C> void
+TLSSocket<C>::shutdown(C& callback) {
+ if (!socket_.is_open()) {
+ isc_throw(SocketNotOpen, "attempt to perform shutdown on "
+ "a TLS socket that is not open");
+ }
+ stream_.shutdown(callback);
+}
+
+// Close the socket down. Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+TLSSocket<C>::close() {
+ if (socket_.is_open() && stream_ptr_) {
+ socket_.close();
+ }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // TLS_SOCKET_H
diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h
new file mode 100644
index 0000000..894032b
--- /dev/null
+++ b/src/lib/asiolink/udp_endpoint.h
@@ -0,0 +1,115 @@
+// 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/.
+
+#ifndef UDP_ENDPOINT_H
+#define UDP_ENDPOINT_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_endpoint.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c UDPEndpoint class is a concrete derived class of
+/// \c IOEndpoint that represents an endpoint of a UDP packet.
+///
+/// Other notes about \c TCPEndpoint applies to this class, too.
+class UDPEndpoint : public IOEndpoint {
+public:
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ //@{
+
+ /// \brief Default Constructor
+ ///
+ /// Creates an internal endpoint. This is expected to be set by some
+ /// external call.
+ UDPEndpoint() :
+ asio_endpoint_placeholder_(new boost::asio::ip::udp::endpoint()),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from a pair of address and port.
+ ///
+ /// \param address The IP address of the endpoint.
+ /// \param port The UDP port number of the endpoint.
+ UDPEndpoint(const IOAddress& address, const unsigned short port) :
+ asio_endpoint_placeholder_(
+ new boost::asio::ip::udp::endpoint(boost::asio::ip::address::from_string(address.toText()),
+ port)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief Constructor from an ASIO UDP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c udp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the UDP endpoint.
+ UDPEndpoint(boost::asio::ip::udp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+ {}
+
+ /// \brief Constructor from an ASIO UDP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c udp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ UDPEndpoint(const boost::asio::ip::udp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(new boost::asio::ip::udp::endpoint(asio_endpoint)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
+ /// \brief The destructor.
+ virtual ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
+ //@}
+
+ virtual IOAddress getAddress() const {
+ return (asio_endpoint_.address());
+ }
+
+ virtual const struct sockaddr& getSockAddr() const {
+ return (*asio_endpoint_.data());
+ }
+
+ virtual uint16_t getPort() const {
+ return (asio_endpoint_.port());
+ }
+
+ virtual short getProtocol() const {
+ return (asio_endpoint_.protocol().protocol());
+ }
+
+ virtual short getFamily() const {
+ return (asio_endpoint_.protocol().family());
+ }
+
+ // This is not part of the exposed IOEndpoint API but allows
+ // direct access to the ASIO implementation of the endpoint
+ inline const boost::asio::ip::udp::endpoint& getASIOEndpoint() const {
+ return (asio_endpoint_);
+ }
+ inline boost::asio::ip::udp::endpoint& getASIOEndpoint() {
+ return (asio_endpoint_);
+ }
+
+private:
+ boost::asio::ip::udp::endpoint* asio_endpoint_placeholder_;
+ boost::asio::ip::udp::endpoint& asio_endpoint_;
+};
+
+} // namespace asiolink
+} // namespace isc
+#endif // UDP_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h
new file mode 100644
index 0000000..fea8def
--- /dev/null
+++ b/src/lib/asiolink/udp_socket.h
@@ -0,0 +1,323 @@
+// Copyright (C) 2011-2022 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 UDP_SOCKET_H
+#define UDP_SOCKET_H 1
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <cstddef>
+
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+
+#include <exceptions/isc_assert.h>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief The \c UDPSocket class is a concrete derived class of \c IOAsioSocket
+/// that represents a UDP socket.
+///
+/// \param C Callback type
+template <typename C>
+class UDPSocket : public IOAsioSocket<C> {
+private:
+ /// \brief Class is non-copyable
+ UDPSocket(const UDPSocket&);
+ UDPSocket& operator=(const UDPSocket&);
+
+public:
+ enum {
+ MIN_SIZE = 4096 // Minimum send and receive size
+ };
+
+ /// \brief Constructor from an ASIO UDP socket.
+ ///
+ /// \param socket The ASIO representation of the UDP socket. It is assumed
+ /// that the caller will open and close the socket, so these
+ /// operations are a no-op for that socket.
+ UDPSocket(boost::asio::ip::udp::socket& socket);
+
+ /// \brief Constructor
+ ///
+ /// Used when the UDPSocket is being asked to manage its own internal
+ /// socket. In this case, the open() and close() methods are used.
+ ///
+ /// \param service I/O Service object used to manage the socket.
+ UDPSocket(IOService& service);
+
+ /// \brief Destructor
+ virtual ~UDPSocket();
+
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+#if BOOST_VERSION < 106600
+ return (socket_.native());
+#else
+ return (socket_.native_handle());
+#endif
+ }
+
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_UDP);
+ }
+
+ /// \brief Is "open()" synchronous?
+ ///
+ /// Indicates that the opening of a UDP socket is synchronous.
+ virtual bool isOpenSynchronous() const {
+ return true;
+ }
+
+ /// \brief Open Socket
+ ///
+ /// Opens the UDP socket. This is a synchronous operation.
+ ///
+ /// \param endpoint Endpoint to which the socket will send data. This is
+ /// used to determine the address family that should be used for the
+ /// underlying socket.
+ /// \param callback Unused as the operation is synchronous.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Send Asynchronously
+ ///
+ /// Calls the underlying socket's async_send_to() method to send a packet of
+ /// data asynchronously to the remote endpoint. The callback will be called
+ /// on completion.
+ ///
+ /// \param data Data to send
+ /// \param length Length of data to send
+ /// \param endpoint Target of the send
+ /// \param callback Callback object.
+ virtual void asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback);
+
+ /// \brief Receive Asynchronously
+ ///
+ /// Calls the underlying socket's async_receive_from() method to read a
+ /// packet of data from a remote endpoint. Arrival of the data is signalled
+ /// via a call to the callback function.
+ ///
+ /// \param data Buffer to receive incoming message
+ /// \param length Length of the data buffer
+ /// \param offset Offset into buffer where data is to be put
+ /// \param endpoint Source of the communication
+ /// \param callback Callback object
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback);
+
+ /// \brief Process received data
+ ///
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
+ ///
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed.
+ /// \param offset Unused.
+ /// \param expected unused.
+ /// \param outbuff Output buffer. Data in the staging buffer is be copied
+ /// to this output buffer in the call.
+ ///
+ /// \return Always true
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::util::OutputBufferPtr& outbuff);
+
+ /// \brief Cancel I/O On Socket
+ virtual void cancel();
+
+ /// \brief Close socket
+ virtual void close();
+
+
+private:
+ // Two variables to hold the socket - a socket and a pointer to it. This
+ // handles the case where a socket is passed to the UDPSocket on
+ // construction, or where it is asked to manage its own socket.
+
+ /// Pointer to own socket
+ std::unique_ptr<boost::asio::ip::udp::socket> socket_ptr_;
+
+ // Socket
+ boost::asio::ip::udp::socket& socket_;
+
+ // True when socket is open
+ bool isopen_;
+};
+
+// Constructor - caller manages socket
+
+template <typename C>
+UDPSocket<C>::UDPSocket(boost::asio::ip::udp::socket& socket) :
+ socket_ptr_(), socket_(socket), isopen_(true)
+{
+}
+
+// Constructor - create socket on the fly
+
+template <typename C>
+UDPSocket<C>::UDPSocket(IOService& service) :
+ socket_ptr_(new boost::asio::ip::udp::socket(service.get_io_service())),
+ socket_(*socket_ptr_), isopen_(false)
+{
+}
+
+// Destructor.
+
+template <typename C>
+UDPSocket<C>::~UDPSocket()
+{
+}
+
+// Open the socket.
+
+template <typename C> void
+UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
+
+ // Ignore opens on already-open socket. (Don't throw a failure because
+ // of uncertainties as to what precedes when using asynchronous I/O.)
+ // It also allows us a treat a passed-in socket in exactly the same way as
+ // a self-managed socket (in that we can call the open() and close() methods
+ // of this class).
+ if (!isopen_) {
+ if (endpoint->getFamily() == AF_INET) {
+ socket_.open(boost::asio::ip::udp::v4());
+ } else {
+ socket_.open(boost::asio::ip::udp::v6());
+ }
+ isopen_ = true;
+
+ // Ensure it can send and receive at least 4K buffers.
+ boost::asio::ip::udp::socket::send_buffer_size snd_size;
+ socket_.get_option(snd_size);
+ if (snd_size.value() < MIN_SIZE) {
+ snd_size = MIN_SIZE;
+ socket_.set_option(snd_size);
+ }
+
+ boost::asio::ip::udp::socket::receive_buffer_size rcv_size;
+ socket_.get_option(rcv_size);
+ if (rcv_size.value() < MIN_SIZE) {
+ rcv_size = MIN_SIZE;
+ socket_.set_option(rcv_size);
+ }
+ }
+}
+
+// Send a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+UDPSocket<C>::asyncSend(const void* data, size_t length,
+ const IOEndpoint* endpoint, C& callback)
+{
+ if (isopen_) {
+
+ // Upconvert to a UDPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+ // does not contain a method for getting at the underlying endpoint
+ // type - that is in the derived class and the two classes differ on
+ // return type.
+ isc_throw_assert(endpoint->getProtocol() == IPPROTO_UDP);
+ const UDPEndpoint* udp_endpoint =
+ static_cast<const UDPEndpoint*>(endpoint);
+
+ // ... and send the message.
+ socket_.async_send_to(boost::asio::buffer(data, length),
+ udp_endpoint->getASIOEndpoint(), callback);
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to send on a UDP socket that is not open");
+ }
+}
+
+// Receive a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
+
+template <typename C> void
+UDPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback)
+{
+ if (isopen_) {
+
+ // Upconvert the endpoint again.
+ isc_throw_assert(endpoint->getProtocol() == IPPROTO_UDP);
+ UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
+
+ // Ensure we can write into the buffer
+ if (offset >= length) {
+ isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+ "UDP receive buffer");
+ }
+ void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+ // Issue the read
+ socket_.async_receive_from(boost::asio::buffer(buffer_start, length - offset),
+ udp_endpoint->getASIOEndpoint(), callback);
+ } else {
+ isc_throw(SocketNotOpen,
+ "attempt to receive from a UDP socket that is not open");
+ }
+}
+
+// Receive complete. Just copy the data across to the output buffer and
+// update arguments as appropriate.
+
+template <typename C> bool
+UDPSocket<C>::processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::util::OutputBufferPtr& outbuff)
+{
+ // Set return values to what we should expect.
+ cumulative = length;
+ expected = length;
+ offset = 0;
+
+ // Copy data across
+ outbuff->writeData(staging, length);
+
+ // ... and mark that we have everything.
+ return (true);
+}
+
+// Cancel I/O on the socket. No-op if the socket is not open.
+
+template <typename C> void
+UDPSocket<C>::cancel() {
+ if (isopen_) {
+ socket_.cancel();
+ }
+}
+
+// Close the socket down. Can only do this if the socket is open and we are
+// managing it ourself.
+
+template <typename C> void
+UDPSocket<C>::close() {
+ if (isopen_ && socket_ptr_) {
+ socket_.close();
+ isopen_ = false;
+ }
+}
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // UDP_SOCKET_H
diff --git a/src/lib/asiolink/unix_domain_socket.cc b/src/lib/asiolink/unix_domain_socket.cc
new file mode 100644
index 0000000..ca9e15c
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket.cc
@@ -0,0 +1,371 @@
+// Copyright (C) 2017-2020 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 <asiolink/asio_wrapper.h>
+#include <asiolink/unix_domain_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <functional>
+#include <iostream>
+
+using namespace boost::asio::local;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implementation of the unix domain socket.
+class UnixDomainSocketImpl : public boost::enable_shared_from_this<UnixDomainSocketImpl> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the socket class.
+ UnixDomainSocketImpl(IOService& io_service)
+ : socket_(io_service.get_io_service()) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the socket.
+ ~UnixDomainSocketImpl() {
+ close();
+ }
+
+ /// @brief Asynchronously connects to an endpoint.
+ ///
+ /// This method schedules asynchronous connect and installs the
+ /// @ref UnixDomainSocketImpl::connectHandler as a callback.
+ ///
+ /// @param endpoint Reference to an endpoint to connect to.
+ /// @param handler User supplied handler to be invoked when the connection
+ /// is established or when error is signalled.
+ void asyncConnect(const stream_protocol::endpoint& endpoint,
+ const UnixDomainSocket::ConnectHandler& handler);
+
+ /// @brief Local handler invoked as a result of asynchronous connection.
+ ///
+ /// This is a wrapper around the user supplied callback. It ignores
+ /// EINPROGRESS errors which are observed on some operating systems as
+ /// a result of trying to connect asynchronously. This error code doesn't
+ /// necessarily indicate a problem and the subsequent attempts to read
+ /// and write to the socket will succeed. Therefore, the handler simply
+ /// overrides this error code with success status. The user supplied
+ /// handler doesn't need to deal with the EINPROGRESS error codes.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param ec Error code returned as a result of connection.
+ void connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+ const boost::system::error_code& ec);
+
+ /// @brief Asynchronously sends data over the socket.
+ ///
+ /// This method schedules an asynchronous send and installs the
+ /// @ref UnixDomainSocketImpl::sendHandler as a callback.
+ ///
+ /// @param data Pointer to data to be sent.
+ /// @param length Number of bytes to be sent.
+ /// @param handler Callback to be invoked when data have been sent or an
+ /// sending error is signalled.
+ void asyncSend(const void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Asynchronously sends the data over the socket.
+ ///
+ /// This method is called by the @ref asyncSend and the @ref sendHandler
+ /// if the asynchronous send has to be repeated as a result of receiving
+ /// EAGAIN or EWOULDBLOCK.
+ ///
+ /// @param buffer Buffers holding the data to be sent.
+ /// @param handler User supplied callback to be invoked when data have
+ /// been sent or sending error is signalled.
+ void doSend(const boost::asio::const_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler);
+
+
+ /// @brief Local handler invoked as a result of asynchronous send.
+ ///
+ /// This handler is invoked as a result of asynchronous send. It is a
+ /// wrapper callback around the user supplied callback. It handles
+ /// EWOULDBLOCK and EAGAIN errors by retrying an asynchronous send.
+ /// These errors are often returned on some operating systems, even
+ /// though one would expect that asynchronous operation would not
+ /// return such errors. Because these errors are handled by the
+ /// wrapper callback, the user supplied callback never receives
+ /// these errors.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param buffer Buffers holding the data to be sent.
+ /// @param ec Error code returned as a result of sending the data.
+ /// @param length Length of the data sent.
+ void sendHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::const_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Asynchronously receive data over the socket.
+ ///
+ /// This method schedules asynchronous receive and installs the
+ /// @ref UnixDomainSocketImpl::receiveHandler is a callback.
+ ///
+ /// @param data Pointer to a buffer into which the data should be read.
+ /// @param length Length of the buffer.
+ /// @param handler User supplied callback invoked when data have been
+ /// received or an error is signalled.
+ void asyncReceive(void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Asynchronously receives the data over the socket.
+ ///
+ /// This method is called @ref asyncReceive and @ref receiveHandler when
+ /// EWOULDBLOCK or EAGAIN is returned.
+ ///
+ /// @param buffer A buffer into which the data should be received.
+ /// @param handler User supplied callback invoked when data have been
+ /// received on an error is signalled.
+ void doReceive(const boost::asio::mutable_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler);
+
+ /// @brief Local handler invoked as a result of asynchronous receive.
+ ///
+ /// This handler is invoked as a result of asynchronous receive. It is a
+ /// wrapper callback around the user supplied callback. It handles
+ /// EWOULDBLOCK and EAGAIN by retrying to asynchronously receive the
+ /// data. These errors are often returned on some operating systems, even
+ /// though one would expect that asynchronous operation would not
+ /// return such errors. Because these errors are handled by the
+ /// wrapper callback, the user supplied callback never receives
+ /// these errors.
+ ///
+ /// @param remote_handler User supplied callback.
+ /// @param buffer Buffer into which the data are received.
+ /// @param ec Error code returned as a result of asynchronous receive.
+ /// @param length Size of the received data.
+ void receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::mutable_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Disables read and write operations on the socket.
+ void shutdown();
+
+ /// @brief Cancels asynchronous operations on the socket.
+ void cancel();
+
+ /// @brief Closes the socket.
+ void close();
+
+ /// @brief Instance of the boost asio unix domain socket.
+ stream_protocol::socket socket_;
+};
+
+void
+UnixDomainSocketImpl::asyncConnect(const stream_protocol::endpoint& endpoint,
+ const UnixDomainSocket::ConnectHandler& handler) {
+ auto local_handler = std::bind(&UnixDomainSocketImpl::connectHandler,
+ shared_from_this(),
+ handler, ph::_1);
+ socket_.async_connect(endpoint, local_handler);
+}
+
+void
+UnixDomainSocketImpl::connectHandler(const UnixDomainSocket::ConnectHandler& remote_handler,
+ const boost::system::error_code& ec) {
+ // It was observed on Debian and Fedora that asynchronous connect may result
+ // in EINPROGRESS error. This doesn't really indicate a problem with a
+ // connection. If we continue transmitting data over the socket it will
+ // succeed. So we suppress this error and return 'success' to the user's
+ // handler.
+ if (ec.value() == boost::asio::error::in_progress) {
+ remote_handler(boost::system::error_code());
+ } else {
+ remote_handler(ec);
+ }
+}
+
+void
+UnixDomainSocketImpl::asyncSend(const void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler) {
+ doSend(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doSend(const boost::asio::const_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler) {
+ auto local_handler = std::bind(&UnixDomainSocketImpl::sendHandler,
+ shared_from_this(),
+ handler, buffer, ph::_1, ph::_2);
+ socket_.async_send(buffer, local_handler);
+}
+
+void
+UnixDomainSocketImpl::sendHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::const_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length) {
+ // The asynchronous send may return EWOULDBLOCK or EAGAIN on some
+ // operating systems. In this case, we simply retry hoping that it
+ // will succeed next time. The user's callback never sees these
+ // errors.
+ if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ doSend(buffer, remote_handler);
+
+ } else {
+ remote_handler(ec, length);
+ }
+}
+
+void
+UnixDomainSocketImpl::asyncReceive(void* data, const size_t length,
+ const UnixDomainSocket::Handler& handler) {
+ doReceive(boost::asio::buffer(data, length), handler);
+}
+
+void
+UnixDomainSocketImpl::doReceive(const boost::asio::mutable_buffers_1& buffer,
+ const UnixDomainSocket::Handler& handler) {
+ auto local_handler = std::bind(&UnixDomainSocketImpl::receiveHandler,
+ shared_from_this(),
+ handler, buffer, ph::_1, ph::_2);
+ socket_.async_receive(buffer, 0, local_handler);
+}
+
+void
+UnixDomainSocketImpl::receiveHandler(const UnixDomainSocket::Handler& remote_handler,
+ const boost::asio::mutable_buffers_1& buffer,
+ const boost::system::error_code& ec,
+ size_t length) {
+ // The asynchronous receive may return EWOULDBLOCK or EAGAIN on some
+ // operating systems. In this case, we simply retry hoping that it
+ // will succeed next time. The user's callback never sees these
+ // errors.
+ if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ doReceive(buffer, remote_handler);
+
+ } else {
+ remote_handler(ec, length);
+ }
+}
+
+void
+UnixDomainSocketImpl::shutdown() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.shutdown(stream_protocol::socket::shutdown_both, ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocketImpl::cancel() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.cancel(ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocketImpl::close() {
+ boost::system::error_code ec;
+ static_cast<void>(socket_.close(ec));
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+UnixDomainSocket::UnixDomainSocket(IOService& io_service)
+ : impl_(new UnixDomainSocketImpl(io_service)) {
+}
+
+int
+UnixDomainSocket::getNative() const {
+#if BOOST_VERSION < 106600
+ return (impl_->socket_.native());
+#else
+ return (impl_->socket_.native_handle());
+#endif
+}
+
+int
+UnixDomainSocket::getProtocol() const {
+ return (0);
+}
+
+void
+UnixDomainSocket::connect(const std::string& path) {
+ boost::system::error_code ec;
+ impl_->socket_.connect(stream_protocol::endpoint(path.c_str()), ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+}
+
+void
+UnixDomainSocket::asyncConnect(const std::string& path, const ConnectHandler& handler) {
+ impl_->asyncConnect(stream_protocol::endpoint(path.c_str()), handler);
+}
+
+size_t
+UnixDomainSocket::write(const void* data, size_t length) {
+ boost::system::error_code ec;
+ size_t res = boost::asio::write(impl_->socket_,
+ boost::asio::buffer(data, length),
+ boost::asio::transfer_all(),
+ ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+ return (res);
+}
+
+void
+UnixDomainSocket::asyncSend(const void* data, const size_t length,
+ const Handler& handler) {
+ impl_->asyncSend(data, length, handler);
+}
+
+size_t
+UnixDomainSocket::receive(void* data, size_t length) {
+ boost::system::error_code ec;
+ size_t res = impl_->socket_.receive(boost::asio::buffer(data, length), 0, ec);
+ if (ec) {
+ isc_throw(UnixDomainSocketError, ec.message());
+ }
+ return (res);
+}
+
+void
+UnixDomainSocket::asyncReceive(void* data, const size_t length,
+ const Handler& handler) {
+ impl_->asyncReceive(data, length, handler);
+}
+
+void
+UnixDomainSocket::shutdown() {
+ impl_->shutdown();
+}
+
+void
+UnixDomainSocket::cancel() {
+ impl_->cancel();
+}
+
+void
+UnixDomainSocket::close() {
+ impl_->close();
+}
+
+boost::asio::local::stream_protocol::socket&
+UnixDomainSocket::getASIOSocket() const {
+ return (impl_->socket_);
+}
+
+} // end of namespace asiolink
+} // end of namespace isc
diff --git a/src/lib/asiolink/unix_domain_socket.h b/src/lib/asiolink/unix_domain_socket.h
new file mode 100644
index 0000000..cd02f41
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket.h
@@ -0,0 +1,137 @@
+// Copyright (C) 2017 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 UNIX_DOMAIN_SOCKET_H
+#define UNIX_DOMAIN_SOCKET_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_socket.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Exception thrown upon socket error.
+class UnixDomainSocketError : public Exception {
+public:
+ UnixDomainSocketError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class UnixDomainSocketImpl;
+
+/// @brief Represents unix domain socket implemented in terms
+/// of boost asio.
+class UnixDomainSocket : public IOSocket {
+public:
+
+ /// @brief Callback type used in call to @ref UnixDomainSocket::asyncConnect.
+ typedef std::function<void(const boost::system::error_code&)> ConnectHandler;
+
+ /// @brief Callback type used in calls to @ref UnixDomainSocket::asyncSend
+ /// and @ref UnixDomainSocket::asyncReceive.
+ typedef std::function<void(const boost::system::error_code&, size_t)> Handler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to IOService to be used by this
+ /// class.
+ explicit UnixDomainSocket(IOService& io_service);
+
+ /// @brief Returns native socket representation.
+ virtual int getNative() const;
+
+ /// @brief Always returns 0.
+ virtual int getProtocol() const;
+
+ /// @brief Connects the socket to the specified endpoint.
+ ///
+ /// @param path Path to the unix socket to which we should connect.
+ ///
+ /// @throw UnixDomainSocketError if error occurs.
+ void connect(const std::string& path);
+
+ /// @brief Asynchronously connects the socket to the specified endpoint.
+ ///
+ /// Always returns immediately.
+ ///
+ /// @param path Path to the unix socket to which we should connect.
+ /// @param handler Callback to be invoked when connection is established or
+ /// a connection error is signalled.
+ void asyncConnect(const std::string& path, const ConnectHandler& handler);
+
+ /// @brief Writes specified amount of data to a socket.
+ ///
+ /// @param data Pointer to data to be written.
+ /// @param length Number of bytes to be written.
+ ///
+ /// @return Number of bytes written.
+ /// @throw UnixDomainSocketError if error occurs.
+ size_t write(const void* data, size_t length);
+
+ /// @brief Asynchronously sends data over the socket.
+ ///
+ /// Always returns immediately.
+ ///
+ /// @param data Pointer to data to be sent.
+ /// @param length Number of bytes to be sent.
+ /// @param handler Callback to be invoked when data have been sent or
+ /// sending error is signalled.
+ void asyncSend(const void* data, const size_t length, const Handler& handler);
+
+ /// @brief Receives data from a socket.
+ ///
+ /// @param [out] data Pointer to a location into which the read data should
+ /// be stored.
+ /// @param length Length of the buffer.
+ ///
+ /// @return Number of bytes read.
+ /// @throw UnixDomainSocketError if error occurs.
+ size_t receive(void* data, size_t length);
+
+ /// @brief Asynchronously receives data over the socket.
+ ///
+ /// Always returns immediately.
+ /// @param [out] data Pointer to a location into which the read data should
+ /// be stored.
+ /// @param length Length of the buffer.
+ /// @param handler Callback to be invoked when data have been received or an
+ /// error is signalled.
+ void asyncReceive(void* data, const size_t length, const Handler& handler);
+
+ /// @brief Disables read and write operations on the socket.
+ ///
+ /// @throw UnixDomainSocketError if an error occurs during shutdown.
+ void shutdown();
+
+ /// @brief Cancels scheduled asynchronous operations on the socket.
+ ///
+ /// @throw UnixDomainSocketError if an error occurs during cancel operation.
+ void cancel();
+
+ /// @brief Closes the socket.
+ ///
+ /// @throw UnixDomainSocketError if an error occurs during closure.
+ void close();
+
+ /// @brief Returns reference to the underlying ASIO socket.
+ ///
+ /// @return Reference to underlying ASIO socket.
+ virtual boost::asio::local::stream_protocol::socket& getASIOSocket() const;
+
+private:
+
+ /// @brief Pointer to the implementation of this class.
+ boost::shared_ptr<UnixDomainSocketImpl> impl_;
+
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_H
diff --git a/src/lib/asiolink/unix_domain_socket_acceptor.h b/src/lib/asiolink/unix_domain_socket_acceptor.h
new file mode 100644
index 0000000..8aa11ca
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket_acceptor.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 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 UNIX_DOMAIN_SOCKET_ACCEPTOR_H
+#define UNIX_DOMAIN_SOCKET_ACCEPTOR_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <asiolink/io_acceptor.h>
+#include <asiolink/unix_domain_socket.h>
+#include <functional>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Implements acceptor service for @ref UnixDomainSocket.
+///
+/// This class is used to accept new incoming connections over unix domain
+/// sockets.
+class UnixDomainSocketAcceptor : public IOAcceptor<boost::asio::local::stream_protocol,
+ std::function<void(const boost::system::error_code&)> > {
+public:
+
+ /// @brief Callback type used in call to @ref UnixDomainSocketAcceptor::asyncAccept.
+ typedef std::function<void(const boost::system::error_code&)> AcceptHandler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit UnixDomainSocketAcceptor(IOService& io_service)
+ : IOAcceptor<boost::asio::local::stream_protocol,
+ std::function<void(const boost::system::error_code&)> >(io_service) {
+ }
+
+ /// @brief Returns the transport protocol of the socket.
+ ///
+ /// @return AF_LOCAL.
+ virtual int getProtocol() const final {
+ return (AF_LOCAL);
+ }
+
+ /// @brief Asynchronously accept new connection.
+ ///
+ /// This method accepts new connection into the specified socket. When the
+ /// new connection arrives or an error occurs the specified callback function
+ /// is invoked.
+ ///
+ /// @param socket Socket into which connection should be accepted.
+ /// @param callback Callback function to be invoked when the new connection
+ /// arrives.
+ /// @tparam SocketType
+ void asyncAccept(const UnixDomainSocket& socket, const AcceptHandler& callback) {
+ asyncAcceptInternal(socket, callback);
+ }
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_ACCEPTOR_H
diff --git a/src/lib/asiolink/unix_domain_socket_endpoint.h b/src/lib/asiolink/unix_domain_socket_endpoint.h
new file mode 100644
index 0000000..9378a83
--- /dev/null
+++ b/src/lib/asiolink/unix_domain_socket_endpoint.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2017 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 UNIX_DOMAIN_SOCKET_ENDPOINT_H
+#define UNIX_DOMAIN_SOCKET_ENDPOINT_H
+
+#ifndef BOOST_ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include <string>
+
+namespace isc {
+namespace asiolink {
+
+/// @brief Endpoint for @ref UnixDomainSocket.
+///
+/// This is a simple class encapsulating ASIO unix domain socket endpoint.
+/// It is used to represent endpoints taking part in communication via
+/// unix domain sockets.
+class UnixDomainSocketEndpoint {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param endpoint_path Path to the socket descriptor.
+ explicit UnixDomainSocketEndpoint(const std::string& endpoint_path)
+ : endpoint_(endpoint_path) {
+ }
+
+ /// @brief Returns underlying ASIO endpoint.
+ const boost::asio::local::stream_protocol::endpoint&
+ getASIOEndpoint() const {
+ return (endpoint_);
+ }
+
+private:
+
+ /// @brief Underlying ASIO endpoint.
+ boost::asio::local::stream_protocol::endpoint endpoint_;
+
+};
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // UNIX_DOMAIN_SOCKET_ENDPOINT_H
diff --git a/src/lib/cc/Makefile.am b/src/lib/cc/Makefile.am
new file mode 100644
index 0000000..a24af62
--- /dev/null
+++ b/src/lib/cc/Makefile.am
@@ -0,0 +1,45 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-cc.la
+libkea_cc_la_SOURCES = base_stamped_element.cc base_stamped_element.h
+libkea_cc_la_SOURCES += data.cc data.h
+libkea_cc_la_SOURCES += element_value.h
+libkea_cc_la_SOURCES += cfg_to_element.h dhcp_config_error.h
+libkea_cc_la_SOURCES += command_interpreter.cc command_interpreter.h
+libkea_cc_la_SOURCES += json_feed.cc json_feed.h
+libkea_cc_la_SOURCES += server_tag.cc server_tag.h
+libkea_cc_la_SOURCES += simple_parser.cc simple_parser.h
+libkea_cc_la_SOURCES += stamped_element.cc stamped_element.h
+libkea_cc_la_SOURCES += stamped_value.cc stamped_value.h
+libkea_cc_la_SOURCES += user_context.cc user_context.h
+
+libkea_cc_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_cc_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_cc_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_cc_la_LIBADD += $(BOOST_LIBS)
+
+libkea_cc_la_LDFLAGS = -no-undefined -version-info 54:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cc_includedir = $(pkgincludedir)/cc
+libkea_cc_include_HEADERS = \
+ base_stamped_element.h \
+ cfg_to_element.h \
+ command_interpreter.h \
+ data.h \
+ dhcp_config_error.h \
+ element_value.h \
+ json_feed.h \
+ server_tag.h \
+ simple_parser.h \
+ stamped_element.h \
+ stamped_value.h \
+ user_context.h
+
+EXTRA_DIST = cc.dox
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/cc/Makefile.in b/src/lib/cc/Makefile.in
new file mode 100644
index 0000000..dcd6501
--- /dev/null
+++ b/src/lib/cc/Makefile.in
@@ -0,0 +1,994 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/cc
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_cc_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)$(libkea_cc_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_cc_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1)
+am_libkea_cc_la_OBJECTS = base_stamped_element.lo data.lo \
+ command_interpreter.lo json_feed.lo server_tag.lo \
+ simple_parser.lo stamped_element.lo stamped_value.lo \
+ user_context.lo
+libkea_cc_la_OBJECTS = $(am_libkea_cc_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_cc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(libkea_cc_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)/base_stamped_element.Plo \
+ ./$(DEPDIR)/command_interpreter.Plo ./$(DEPDIR)/data.Plo \
+ ./$(DEPDIR)/json_feed.Plo ./$(DEPDIR)/server_tag.Plo \
+ ./$(DEPDIR)/simple_parser.Plo ./$(DEPDIR)/stamped_element.Plo \
+ ./$(DEPDIR)/stamped_value.Plo ./$(DEPDIR)/user_context.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_cc_la_SOURCES)
+DIST_SOURCES = $(libkea_cc_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 = $(libkea_cc_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-cc.la
+libkea_cc_la_SOURCES = base_stamped_element.cc base_stamped_element.h \
+ data.cc data.h element_value.h cfg_to_element.h \
+ dhcp_config_error.h command_interpreter.cc \
+ command_interpreter.h json_feed.cc json_feed.h server_tag.cc \
+ server_tag.h simple_parser.cc simple_parser.h \
+ stamped_element.cc stamped_element.h stamped_value.cc \
+ stamped_value.h user_context.cc user_context.h
+libkea_cc_la_LIBADD = \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(BOOST_LIBS)
+libkea_cc_la_LDFLAGS = -no-undefined -version-info 54:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cc_includedir = $(pkgincludedir)/cc
+libkea_cc_include_HEADERS = \
+ base_stamped_element.h \
+ cfg_to_element.h \
+ command_interpreter.h \
+ data.h \
+ dhcp_config_error.h \
+ element_value.h \
+ json_feed.h \
+ server_tag.h \
+ simple_parser.h \
+ stamped_element.h \
+ stamped_value.h \
+ user_context.h
+
+EXTRA_DIST = cc.dox
+CLEANFILES = *.gcno *.gcda
+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/cc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/cc/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-cc.la: $(libkea_cc_la_OBJECTS) $(libkea_cc_la_DEPENDENCIES) $(EXTRA_libkea_cc_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_cc_la_LINK) -rpath $(libdir) $(libkea_cc_la_OBJECTS) $(libkea_cc_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base_stamped_element.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command_interpreter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json_feed.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server_tag.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stamped_element.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stamped_value.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/user_context.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_cc_includeHEADERS: $(libkea_cc_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_cc_include_HEADERS)'; test -n "$(libkea_cc_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_cc_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_cc_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)$(libkea_cc_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_cc_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_cc_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_cc_include_HEADERS)'; test -n "$(libkea_cc_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_cc_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_cc_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/base_stamped_element.Plo
+ -rm -f ./$(DEPDIR)/command_interpreter.Plo
+ -rm -f ./$(DEPDIR)/data.Plo
+ -rm -f ./$(DEPDIR)/json_feed.Plo
+ -rm -f ./$(DEPDIR)/server_tag.Plo
+ -rm -f ./$(DEPDIR)/simple_parser.Plo
+ -rm -f ./$(DEPDIR)/stamped_element.Plo
+ -rm -f ./$(DEPDIR)/stamped_value.Plo
+ -rm -f ./$(DEPDIR)/user_context.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-libkea_cc_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)/base_stamped_element.Plo
+ -rm -f ./$(DEPDIR)/command_interpreter.Plo
+ -rm -f ./$(DEPDIR)/data.Plo
+ -rm -f ./$(DEPDIR)/json_feed.Plo
+ -rm -f ./$(DEPDIR)/server_tag.Plo
+ -rm -f ./$(DEPDIR)/simple_parser.Plo
+ -rm -f ./$(DEPDIR)/stamped_element.Plo
+ -rm -f ./$(DEPDIR)/stamped_value.Plo
+ -rm -f ./$(DEPDIR)/user_context.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-libkea_cc_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_cc_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-libkea_cc_includeHEADERS
+
+.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/cc/base_stamped_element.cc b/src/lib/cc/base_stamped_element.cc
new file mode 100644
index 0000000..c3b6f38
--- /dev/null
+++ b/src/lib/cc/base_stamped_element.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2019-2020 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 <cc/base_stamped_element.h>
+
+namespace isc {
+namespace data {
+
+BaseStampedElement::BaseStampedElement()
+ /// @todo Change it to microsec_clock once we transition to subsecond
+ /// precision.
+ : id_(0), timestamp_(boost::posix_time::second_clock::local_time()) {
+}
+
+void
+BaseStampedElement::updateModificationTime() {
+ /// @todo Change it to microsec_clock once we transition to subsecond
+ /// precision.
+ setModificationTime(boost::posix_time::second_clock::local_time());
+}
+
+} // end of namespace isc::data
+} // end of namespace isc
diff --git a/src/lib/cc/base_stamped_element.h b/src/lib/cc/base_stamped_element.h
new file mode 100644
index 0000000..beb3090
--- /dev/null
+++ b/src/lib/cc/base_stamped_element.h
@@ -0,0 +1,80 @@
+// Copyright (C) 2019,2021 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 BASE_STAMPED_ELEMENT_H
+#define BASE_STAMPED_ELEMENT_H
+
+#include <cc/data.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace data {
+
+/// @brief This class represents configuration element which is
+/// associated with database identifier and the modification
+/// timestamp.
+///
+/// The @c StampedElement class derives from this class to extend
+/// it with the capability to associate the configuration elements
+/// with server tags. The @c db::Server class derives from it to
+/// store a single server tag identifying a server it describes.
+///
+/// @note This class is not derived from @c Element and should not
+/// be confused with the classes being derived from @c Element class.
+/// Those classes are used to represent JSON structures, whereas this
+/// class represents data fetched from the database.
+class BaseStampedElement {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets timestamp to the current time.
+ BaseStampedElement();
+
+ /// @brief Sets element's database identifier.
+ ///
+ /// @param id New id.
+ void setId(const uint64_t id) {
+ id_ = id;
+ }
+
+ /// @brief Returns element's database identifier.
+ uint64_t getId() const {
+ return (id_);
+ }
+
+ /// @brief Sets timestamp to the explicitly provided value.
+ ///
+ /// @param timestamp New timestamp value.
+ void setModificationTime(const boost::posix_time::ptime& timestamp) {
+ timestamp_ = timestamp;
+ }
+
+ /// @brief Sets timestamp to the current time.
+ void updateModificationTime();
+
+ /// @brief Returns timestamp.
+ boost::posix_time::ptime getModificationTime() const {
+ return (timestamp_);
+ }
+
+protected:
+
+ /// @brief Database identifier of the configuration element.
+ ///
+ /// The default value of 0 indicates that the identifier is
+ /// not set.
+ uint64_t id_;
+
+ /// @brief Holds timestamp value.
+ boost::posix_time::ptime timestamp_;
+};
+
+} // end of namespace isc::data
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/cc/cc.dox b/src/lib/cc/cc.dox
new file mode 100644
index 0000000..da8d79b
--- /dev/null
+++ b/src/lib/cc/cc.dox
@@ -0,0 +1,107 @@
+// Copyright (C) 2016-2020 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/.
+
+/**
+ @page libcc libkea-cc - Kea Configuration Utilities Library
+
+@section ccSimpleParser Simple JSON Parser
+
+Since the early beginnings, our configuration parsing code was a mess. It
+started back in 2011 when Tomek joined ISC recently and was told to implement
+Kea configuration handling in similar way as DNS Auth module. The code grew
+over time (DHCP configuration is significantly more complex than DNS, with
+more interdependent values) and as of Kea 1.1 release it became very difficult
+to manage. The decision has been made to significantly refactor or even
+partially rewrite the parser code. The design for this effort is documented
+here: https://gitlab.isc.org/isc-projects/kea/wikis/designs/simple-parser-design. It discusses the original issues
+and the proposed architecture.
+
+There are several aspects of this new approach. The base class for all
+parsers is @ref isc::data::SimpleParser. It simplifies the parsers
+based on DhcpConfigParser by rejecting the concept of build/commit
+phases. Instead, there should be a single method called parse that
+takes ConstElementPtr as a single parameter (that's the JSON
+structures to be parsed) and returns the config structure to be used
+in CfgMgr. An example of such a method can be the following:
+
+@code
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::parse(isc::data::ConstElementPtr single_option)
+@endcode
+
+Since each derived class will have the same parameter, but a different return
+type, it's not possible to use virtual methods mechanism. That's perfectly
+ok, though, as there is only a single instance of the class needed to parse
+arbitrary number of parameters of the same type. There is no need to
+keep pointers to the parser object. As such there are fewer incentives to have
+one generic way to handle all parsers.
+
+@subsection ccSimpleParserDefaults Default values in Simple Parser
+
+Another simplification comes from the fact that almost all parameters
+are mandatory in SimpleParser. One source of complexities in the old
+parser was the necessity to deal with optional parameters. Simple
+parser deals with that by explicitly requiring the input structure to
+have all parameters filled. Obviously, it's not feasible to expect
+everyone to always specify all parameters, therefore there's an easy
+way to fill missing parameters with their default values. There are
+several methods to do this, but the most generic one is:
+
+@code
+static size_t
+isc::data::SimpleParser::setDefaults(isc::data::ElementPtr scope,
+ const SimpleDefaults& default_values);
+@endcode
+
+It takes a pointer to element to be filled with default values and
+vector of default values. Having those values specified in a single
+place in a way that can easily be read even by non-programmers is a
+big advantage of this approach. Here's an example from simple_parser.cc file:
+
+@code
+/// This table defines default values for option definitions in DHCPv6
+const SimpleDefaults OPTION6_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp6"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+@endcode
+
+This array (which technically is implemented as a vector and
+initialized the C++11 way) can be passed to the aforementioned
+setDefaults. That code will iterate over all default values and see if
+there are explicit values provided. If not, the gaps will be filled
+with default values. There are also convenience methods specified for
+filling in option data defaults, option definition defaults and
+setAllDefaults that sets all defaults (starts with global, but then
+walks down the Element tree and fills defaults in subsequent scopes).
+
+@subsection ccSimpleParserInherits Inheriting parameters between scopes
+
+SimpleParser provides a mechanism to inherit parameters between scopes,
+e.g. to inherit global parameters in the subnet scope if more specific
+values are not defined in the subnet scope. This is achieved by calling
+@code
+static size_t SimpleParser::deriveParams(isc::data::ConstElementPtr parent,
+ isc::data::ElementPtr child,
+ const ParamsList& params);
+
+@endcode
+
+ParamsList is a simple vector<string>. There will be more specific
+methods implemented in the future, but for the time being only
+@ref isc::data::SimpleParser::deriveParams is implemented.
+
+@subsection ccMTConsiderations Multi-Threading Consideration for Configuration Utilities
+
+No configuration utility is thread safe. For instance stamped values are
+not thread safe so any read access must be done in a context where write
+access at the same time is not possible. Note that configuration is
+performed by the main thread with service threads stopped so this constraint
+is fulfilled.
+
+*/
diff --git a/src/lib/cc/cfg_to_element.h b/src/lib/cc/cfg_to_element.h
new file mode 100644
index 0000000..480dd1f
--- /dev/null
+++ b/src/lib/cc/cfg_to_element.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2017 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 CFG_TO_ELEMENT_H
+#define CFG_TO_ELEMENT_H
+
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
+namespace isc {
+
+/// @brief Cannot unparse error
+///
+/// This exception is expected to be thrown when toElement fails
+/// and to skip flawed elements is not wanted.
+class ToElementError : public isc::Exception {
+public:
+ ToElementError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+namespace data {
+
+/// @brief Abstract class for configuration Cfg_* classes
+///
+struct CfgToElement {
+ /// Destructor
+ virtual ~CfgToElement() { }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// Returns an element which must parse into the same object, i.e.
+ /// @code
+ /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+ /// @endcode
+ ///
+ /// @return a pointer to a configuration which can be parsed into
+ /// the initial configuration object
+ virtual isc::data::ElementPtr toElement() const = 0;
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // CFG_TO_ELEMENT_H
diff --git a/src/lib/cc/command_interpreter.cc b/src/lib/cc/command_interpreter.cc
new file mode 100644
index 0000000..0ea03bc
--- /dev/null
+++ b/src/lib/cc/command_interpreter.cc
@@ -0,0 +1,318 @@
+// Copyright (C) 2009-2023 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 <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <string>
+#include <set>
+
+using namespace std;
+
+using isc::data::Element;
+using isc::data::ConstElementPtr;
+using isc::data::ElementPtr;
+using isc::data::JSONError;
+
+namespace isc {
+namespace config {
+
+const char *CONTROL_COMMAND = "command";
+const char *CONTROL_RESULT = "result";
+const char *CONTROL_TEXT = "text";
+const char *CONTROL_ARGUMENTS = "arguments";
+const char *CONTROL_SERVICE = "service";
+const char *CONTROL_REMOTE_ADDRESS = "remote-address";
+
+// Full version, with status, text and arguments
+ConstElementPtr
+createAnswer(const int status_code, const std::string& text,
+ const ConstElementPtr& arg) {
+ if (status_code != 0 && text.empty()) {
+ isc_throw(CtrlChannelError, "Text has to be provided for status_code != 0");
+ }
+
+ ElementPtr answer = Element::createMap();
+ ElementPtr result = Element::create(status_code);
+ answer->set(CONTROL_RESULT, result);
+
+ if (!text.empty()) {
+ answer->set(CONTROL_TEXT, Element::create(text));
+ }
+ if (arg) {
+ answer->set(CONTROL_ARGUMENTS, arg);
+ }
+ return (answer);
+}
+
+ConstElementPtr
+createAnswer() {
+ return (createAnswer(CONTROL_RESULT_SUCCESS, string(""), ConstElementPtr()));
+}
+
+ConstElementPtr
+createAnswer(const int status_code, const std::string& text) {
+ return (createAnswer(status_code, text, ElementPtr()));
+}
+
+ConstElementPtr
+createAnswer(const int status_code, const ConstElementPtr& arg) {
+ return (createAnswer(status_code, "", arg));
+}
+
+ConstElementPtr
+parseAnswerText(int &rcode, const ConstElementPtr& msg) {
+ if (!msg) {
+ isc_throw(CtrlChannelError, "invalid answer: no answer specified");
+ }
+ if (msg->getType() != Element::map) {
+ isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got "
+ << Element::typeToName(msg->getType()) << " instead");
+ }
+ if (!msg->contains(CONTROL_RESULT)) {
+ isc_throw(CtrlChannelError,
+ "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'");
+ }
+
+ ConstElementPtr result = msg->get(CONTROL_RESULT);
+ if (result->getType() != Element::integer) {
+ isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT
+ << "' to be an integer, got "
+ << Element::typeToName(result->getType()) << " instead");
+ }
+
+ rcode = result->intValue();
+
+ // If there are arguments, return them.
+ return (msg->get(CONTROL_TEXT));
+}
+
+ConstElementPtr
+parseAnswer(int &rcode, const ConstElementPtr& msg) {
+ if (!msg) {
+ isc_throw(CtrlChannelError, "invalid answer: no answer specified");
+ }
+ if (msg->getType() != Element::map) {
+ isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got "
+ << Element::typeToName(msg->getType()) << " instead");
+ }
+ if (!msg->contains(CONTROL_RESULT)) {
+ isc_throw(CtrlChannelError,
+ "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'");
+ }
+
+ ConstElementPtr result = msg->get(CONTROL_RESULT);
+ if (result->getType() != Element::integer) {
+ isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT
+ << "' to be an integer, got "
+ << Element::typeToName(result->getType()) << " instead");
+ }
+
+ rcode = result->intValue();
+
+ // If there are arguments, return them.
+ ConstElementPtr args = msg->get(CONTROL_ARGUMENTS);
+ if (args) {
+ return (args);
+ }
+
+ // There are no arguments, let's try to return just the text status
+ return (msg->get(CONTROL_TEXT));
+}
+
+
+std::string
+answerToText(const ConstElementPtr& msg) {
+ if (!msg) {
+ isc_throw(CtrlChannelError, "invalid answer: no answer specified");
+ }
+ if (msg->getType() != Element::map) {
+ isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got "
+ << Element::typeToName(msg->getType()) << " instead");
+ }
+ if (!msg->contains(CONTROL_RESULT)) {
+ isc_throw(CtrlChannelError,
+ "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'");
+ }
+
+ ConstElementPtr result = msg->get(CONTROL_RESULT);
+ if (result->getType() != Element::integer) {
+ isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT
+ << "' to be an integer, got " << Element::typeToName(result->getType())
+ << " instead");
+ }
+
+ stringstream txt;
+ int rcode = result->intValue();
+ if (rcode == 0) {
+ txt << "success(0)";
+ } else {
+ txt << "failure(" << rcode << ")";
+ }
+
+ // Was any text provided? If yes, include it.
+ ConstElementPtr txt_elem = msg->get(CONTROL_TEXT);
+ if (txt_elem) {
+ txt << ", text=" << txt_elem->stringValue();
+ }
+
+ return (txt.str());
+}
+
+ConstElementPtr
+createCommand(const std::string& command) {
+ return (createCommand(command, ElementPtr(), ""));
+}
+
+ConstElementPtr
+createCommand(const std::string& command, ConstElementPtr arg) {
+ return (createCommand(command, arg, ""));
+}
+
+ConstElementPtr
+createCommand(const std::string& command, const std::string& service) {
+ return (createCommand(command, ElementPtr(), service));
+}
+
+ConstElementPtr
+createCommand(const std::string& command,
+ ConstElementPtr arg,
+ const std::string& service) {
+ ElementPtr query = Element::createMap();
+ ElementPtr cmd = Element::create(command);
+ query->set(CONTROL_COMMAND, cmd);
+ if (arg) {
+ query->set(CONTROL_ARGUMENTS, arg);
+ }
+ if (!service.empty()) {
+ ElementPtr services = Element::createList();
+ services->add(Element::create(service));
+ query->set(CONTROL_SERVICE, services);
+ }
+ return (query);
+}
+
+std::string
+parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
+ if (!command) {
+ isc_throw(CtrlChannelError, "invalid command: no command specified");
+ }
+ if (command->getType() != Element::map) {
+ isc_throw(CtrlChannelError, "invalid command: expected toplevel entry to be a map, got "
+ << Element::typeToName(command->getType()) << " instead");
+ }
+ if (!command->contains(CONTROL_COMMAND)) {
+ isc_throw(CtrlChannelError,
+ "invalid command: does not contain mandatory '" << CONTROL_COMMAND << "'");
+ }
+
+ // Make sure that all specified parameters are supported.
+ auto command_params = command->mapValue();
+ for (auto param : command_params) {
+ if ((param.first != CONTROL_COMMAND) &&
+ (param.first != CONTROL_ARGUMENTS) &&
+ (param.first != CONTROL_SERVICE) &&
+ (param.first != CONTROL_REMOTE_ADDRESS)) {
+ isc_throw(CtrlChannelError,
+ "invalid command: unsupported parameter '" << param.first << "'");
+ }
+ }
+
+ ConstElementPtr cmd = command->get(CONTROL_COMMAND);
+ if (cmd->getType() != Element::string) {
+ isc_throw(CtrlChannelError, "invalid command: expected '"
+ << CONTROL_COMMAND << "' to be a string, got "
+ << Element::typeToName(command->getType()) << " instead");
+ }
+
+ arg = command->get(CONTROL_ARGUMENTS);
+
+ return (cmd->stringValue());
+}
+
+std::string
+parseCommandWithArgs(ConstElementPtr& arg, ConstElementPtr command) {
+ std::string command_name = parseCommand(arg, command);
+
+ // This function requires arguments within the command.
+ if (!arg) {
+ isc_throw(CtrlChannelError,
+ "invalid command '" << command_name << "': no arguments specified");
+ }
+
+ // Arguments must be a map.
+ if (arg->getType() != Element::map) {
+ isc_throw(CtrlChannelError,
+ "invalid command '" << command_name << "': expected '"
+ << CONTROL_ARGUMENTS << "' to be a map, got "
+ << Element::typeToName(arg->getType()) << " instead");
+ }
+
+ // At least one argument is required.
+ if (arg->size() == 0) {
+ isc_throw(CtrlChannelError,
+ "invalid command '" << command_name << "': '"
+ << CONTROL_ARGUMENTS << "' is empty");
+ }
+
+ return (command_name);
+}
+
+ConstElementPtr
+combineCommandsLists(const ConstElementPtr& response1,
+ const ConstElementPtr& response2) {
+ // Usually when this method is called there should be two non-null
+ // responses. If there is just a single response, return this
+ // response.
+ if (!response1 && response2) {
+ return (response2);
+
+ } else if (response1 && !response2) {
+ return (response1);
+
+ } else if (!response1 && !response2) {
+ return (ConstElementPtr());
+
+ } else {
+ // Both responses are non-null so we need to combine the lists
+ // of supported commands if the status codes are 0.
+ int status_code;
+ ConstElementPtr args1 = parseAnswer(status_code, response1);
+ if (status_code != 0) {
+ return (response1);
+ }
+
+ ConstElementPtr args2 = parseAnswer(status_code, response2);
+ if (status_code != 0) {
+ return (response2);
+ }
+
+ const std::vector<ElementPtr> vec1 = args1->listValue();
+ const std::vector<ElementPtr> vec2 = args2->listValue();
+
+ // Storing command names in a set guarantees that the non-unique
+ // command names are aggregated.
+ std::set<std::string> combined_set;
+ for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) {
+ combined_set.insert((*v)->stringValue());
+ }
+ for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) {
+ combined_set.insert((*v)->stringValue());
+ }
+
+ // Create a combined list of commands.
+ ElementPtr combined_list = Element::createList();
+ for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) {
+ combined_list->add(Element::create(*s));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list));
+ }
+}
+
+} // namespace config
+} // namespace isc
diff --git a/src/lib/cc/command_interpreter.h b/src/lib/cc/command_interpreter.h
new file mode 100644
index 0000000..f834f34
--- /dev/null
+++ b/src/lib/cc/command_interpreter.h
@@ -0,0 +1,225 @@
+// Copyright (C) 2009-2023 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 COMMAND_INTERPRETER_H
+#define COMMAND_INTERPRETER_H
+
+#include <cc/data.h>
+#include <string>
+
+/// @file command_interpreter.h
+///
+/// This file contains several functions and constants that are used for
+/// handling commands and responses sent over control channel. The design
+/// is described here: https://gitlab.isc.org/isc-projects/kea/wikis/Stats-design, but also
+/// in @ref ctrlSocket section in the Developer's Guide.
+
+namespace isc {
+namespace config {
+
+/// @brief String used for commands ("command")
+extern const char *CONTROL_COMMAND;
+
+/// @brief String used for result, i.e. integer status ("result")
+extern const char *CONTROL_RESULT;
+
+/// @brief String used for storing textual description ("text")
+extern const char *CONTROL_TEXT;
+
+/// @brief String used for arguments map ("arguments")
+extern const char *CONTROL_ARGUMENTS;
+
+/// @brief String used for service list ("service")
+extern const char *CONTROL_SERVICE;
+
+/// @brief String used for remote address ("remote-address")
+extern const char *CONTROL_REMOTE_ADDRESS;
+
+/// @brief Status code indicating a successful operation
+const int CONTROL_RESULT_SUCCESS = 0;
+
+/// @brief Status code indicating a general failure
+const int CONTROL_RESULT_ERROR = 1;
+
+/// @brief Status code indicating that the specified command is not supported.
+const int CONTROL_RESULT_COMMAND_UNSUPPORTED = 2;
+
+/// @brief Status code indicating that the specified command was completed
+/// correctly, but failed to produce any results. For example, get
+/// completed the search, but couldn't find the object it was looking for.
+const int CONTROL_RESULT_EMPTY = 3;
+
+/// @brief Status code indicating that the command was unsuccessful due to a
+/// conflict between the command arguments and the server state. For example,
+/// a lease4-add fails when the subnet identifier in the command does not
+/// match the subnet identifier in the server configuration.
+const int CONTROL_RESULT_CONFLICT = 4;
+
+/// @brief A standard control channel exception that is thrown if a function
+/// is there is a problem with one of the messages
+class CtrlChannelError : public isc::Exception {
+public:
+ CtrlChannelError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Creates a standard config/command level success answer message
+/// (i.e. of the form { "result": 0 }
+/// @return Standard command/config success answer message
+isc::data::ConstElementPtr createAnswer();
+
+/// @brief Creates a standard config/command level answer message
+/// (i.e. of the form { "result": 1, "text": "Invalid command received" }
+///
+/// @param status_code The return code (0 for success)
+/// @param status_text A string to put into the "text" argument
+/// @return Standard command/config answer message
+isc::data::ConstElementPtr createAnswer(const int status_code,
+ const std::string& status_text);
+
+/// @brief Creates a standard config/command level answer message
+/// (i.e. of the form { "result": status_code, "arguments": arg }
+///
+/// @param status_code The return code (0 for success)
+/// @param arg The optional argument for the answer. This can be of
+/// any Element type. May be NULL.
+/// @return Standard command/config answer message
+isc::data::ConstElementPtr createAnswer(const int status_code,
+ const isc::data::ConstElementPtr& arg);
+
+/// @brief Creates a standard config/command level answer message
+///
+/// @param status_code The return code (0 for success)
+/// @param status textual representation of the status (used mostly for errors)
+/// @param arg The optional argument for the answer. This can be of
+/// any Element type. May be NULL.
+/// @return Standard command/config answer message
+isc::data::ConstElementPtr createAnswer(const int status_code,
+ const std::string& status,
+ const isc::data::ConstElementPtr& arg);
+
+/// @brief Parses a standard config/command level answer and returns arguments
+/// or text status code.
+///
+/// If you need to get the text status, please use @ref parseAnswerText.
+///
+/// @param status_code This value will be set to the return code contained in
+/// the message
+/// @param msg The message to parse
+/// @return The optional argument in the message (or null)
+isc::data::ConstElementPtr
+parseAnswer(int &status_code, const isc::data::ConstElementPtr& msg);
+
+/// @brief Parses a standard config/command level answer and returns text status.
+///
+/// This method returns the text status. If you need to get the arguments provided,
+/// please use @ref parseAnswer.
+///
+/// @param status_code This value will be set to the return code contained in
+/// the message
+/// @param msg The message to parse
+/// @return The optional argument in the message (or null)
+isc::data::ConstElementPtr
+parseAnswerText(int &rcode, const isc::data::ConstElementPtr& msg);
+
+/// @brief Converts answer to printable text
+///
+/// @param msg answer to be parsed
+/// @return printable string
+std::string answerToText(const isc::data::ConstElementPtr& msg);
+
+/// @brief Creates a standard command message with no
+/// argument (of the form { "command": "my_command" })
+///
+/// @param command The command string
+/// @return The created message
+isc::data::ConstElementPtr createCommand(const std::string& command);
+
+/// @brief Creates a standard command message with the
+/// given argument (of the form { "command": "my_command", "arguments": arg }
+///
+/// @param command The command string
+/// @param arg The optional argument for the command. This can be of
+/// any Element type. May be NULL.
+/// @return The created message
+isc::data::ConstElementPtr createCommand(const std::string& command,
+ isc::data::ConstElementPtr arg);
+
+/// @brief Creates a standard config/command command message with no
+/// argument and with the given service (of the form
+/// { "command": "my_command", "service": [ service ] })
+///
+/// @param command The command string
+/// @param service The target service. May be empty.
+/// @return The created message
+ isc::data::ConstElementPtr createCommand(const std::string& command,
+ const std::string& service);
+
+/// @brief Creates a standard config/command command message with the
+/// given argument and given service (of the form
+/// { "command": "my_command", "arguments": arg, "service": [ service ] }
+///
+/// @param command The command string
+/// @param arg The optional argument for the command. This can be of
+/// any Element type. May be NULL.
+/// @param service The target service. May be empty.
+/// @return The created message
+isc::data::ConstElementPtr createCommand(const std::string& command,
+ isc::data::ConstElementPtr arg,
+ const std::string& service);
+
+/// @brief Parses the given command into a string containing the actual
+/// command and an ElementPtr containing the optional argument.
+///
+/// @throw CtrlChannelError if this is not a well-formed command
+///
+/// @param arg This value will be set to the ElementPtr pointing to
+/// the argument, or to an empty Map (ElementPtr) if there was none.
+/// @param command The command message containing the command (as made
+/// by createCommand()
+/// @return The command name.
+std::string parseCommand(isc::data::ConstElementPtr& arg,
+ isc::data::ConstElementPtr command);
+
+
+/// @brief Parses the given command into a string containing the command
+/// name and an ElementPtr containing the mandatory argument.
+///
+/// This function expects that command arguments are specified and are
+/// a map.
+///
+/// @throw CtrlChannelError if this is not a well-formed command,
+/// arguments are not specified or are not a map.
+///
+/// @param arg Reference to the data element to which command arguments
+/// will be assigned.
+/// @param command The command message containing the command and
+/// the arguments.
+/// @return Command name.
+std::string parseCommandWithArgs(isc::data::ConstElementPtr& arg,
+ isc::data::ConstElementPtr command);
+
+/// @brief Combines lists of commands carried in two responses.
+///
+/// This method is used to combine list of commands returned by the
+/// two command managers.
+///
+/// If the same command appears in two responses only a single
+/// instance is returned in the combined response.
+///
+/// @param response1 First command response.
+/// @param response2 Second command response.
+///
+/// @return Pointer to the 'list-commands' response holding combined
+/// list of commands.
+isc::data::ConstElementPtr
+combineCommandsLists(const isc::data::ConstElementPtr& response1,
+ const isc::data::ConstElementPtr& response2);
+
+}; // end of namespace isc::config
+}; // end of namespace isc
+
+#endif // COMMAND_INTERPRETER_H
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
new file mode 100644
index 0000000..2f69f27
--- /dev/null
+++ b/src/lib/cc/data.cc
@@ -0,0 +1,1633 @@
+// Copyright (C) 2010-2023 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 <cc/data.h>
+
+#include <cstring>
+#include <cassert>
+#include <climits>
+#include <list>
+#include <map>
+#include <cstdio>
+#include <iostream>
+#include <iomanip>
+#include <string>
+#include <sstream>
+#include <fstream>
+#include <cerrno>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cmath>
+
+using namespace std;
+
+namespace {
+const char* const WHITESPACE = " \b\f\n\r\t";
+} // end anonymous namespace
+
+namespace isc {
+namespace data {
+
+std::string
+Element::Position::str() const {
+ std::ostringstream ss;
+ ss << file_ << ":" << line_ << ":" << pos_;
+ return (ss.str());
+}
+
+std::ostream&
+operator<<(std::ostream& out, const Element::Position& pos) {
+ out << pos.str();
+ return (out);
+}
+
+std::string
+Element::str() const {
+ std::stringstream ss;
+ toJSON(ss);
+ return (ss.str());
+}
+
+std::string
+Element::toWire() const {
+ std::stringstream ss;
+ toJSON(ss);
+ return (ss.str());
+}
+
+void
+Element::toWire(std::ostream& ss) const {
+ toJSON(ss);
+}
+
+bool
+Element::getValue(int64_t&) const {
+ return (false);
+}
+
+bool
+Element::getValue(double&) const {
+ return (false);
+}
+
+bool
+Element::getValue(bool&) const {
+ return (false);
+}
+
+bool
+Element::getValue(std::string&) const {
+ return (false);
+}
+
+bool
+Element::getValue(std::vector<ElementPtr>&) const {
+ return (false);
+}
+
+bool
+Element::getValue(std::map<std::string, ConstElementPtr>&) const {
+ return (false);
+}
+
+bool
+Element::setValue(const long long int) {
+ return (false);
+}
+
+bool
+Element::setValue(isc::util::int128_t const&) {
+ return (false);
+}
+
+bool
+Element::setValue(const double) {
+ return (false);
+}
+
+bool
+Element::setValue(const bool) {
+ return (false);
+}
+
+bool
+Element::setValue(const std::string&) {
+ return (false);
+}
+
+bool
+Element::setValue(const std::vector<ElementPtr>&) {
+ return (false);
+}
+
+bool
+Element::setValue(const std::map<std::string, ConstElementPtr>&) {
+ return (false);
+}
+
+ConstElementPtr
+Element::get(const int) const {
+ throwTypeError("get(int) called on a non-container Element");
+}
+
+ElementPtr
+Element::getNonConst(const int) const {
+ throwTypeError("get(int) called on a non-container Element");
+}
+
+void
+Element::set(const size_t, ElementPtr) {
+ throwTypeError("set(int, element) called on a non-list Element");
+}
+
+void
+Element::add(ElementPtr) {
+ throwTypeError("add() called on a non-list Element");
+}
+
+void
+Element::remove(const int) {
+ throwTypeError("remove(int) called on a non-container Element");
+}
+
+size_t
+Element::size() const {
+ throwTypeError("size() called on a non-list Element");
+}
+
+bool
+Element::empty() const {
+ throwTypeError("empty() called on a non-container Element");
+}
+
+ConstElementPtr
+Element::get(const std::string&) const {
+ throwTypeError("get(string) called on a non-map Element");
+}
+
+void
+Element::set(const std::string&, ConstElementPtr) {
+ throwTypeError("set(name, element) called on a non-map Element");
+}
+
+void
+Element::remove(const std::string&) {
+ throwTypeError("remove(string) called on a non-map Element");
+}
+
+bool
+Element::contains(const std::string&) const {
+ throwTypeError("contains(string) called on a non-map Element");
+}
+
+ConstElementPtr
+Element::find(const std::string&) const {
+ throwTypeError("find(string) called on a non-map Element");
+}
+
+bool
+Element::find(const std::string&, ConstElementPtr&) const {
+ return (false);
+}
+
+namespace {
+inline void
+throwJSONError(const std::string& error, const std::string& file, int line,
+ int pos) {
+ std::stringstream ss;
+ ss << error << " in " + file + ":" << line << ":" << pos;
+ isc_throw(JSONError, ss.str());
+}
+} // end anonymous namespace
+
+std::ostream&
+operator<<(std::ostream& out, const Element& e) {
+ return (out << e.str());
+}
+
+bool
+operator==(const Element& a, const Element& b) {
+ return (a.equals(b));
+}
+
+bool operator!=(const Element& a, const Element& b) {
+ return (!a.equals(b));
+}
+
+bool
+operator<(Element const& a, Element const& b) {
+ if (a.getType() != b.getType()) {
+ isc_throw(BadValue, "cannot compare Elements of different types");
+ }
+ switch (a.getType()) {
+ case Element::integer:
+ return a.intValue() < b.intValue();
+ case Element::real:
+ return a.doubleValue() < b.doubleValue();
+ case Element::boolean:
+ return b.boolValue() || !a.boolValue();
+ case Element::string:
+ return std::strcmp(a.stringValue().c_str(), b.stringValue().c_str()) < 0;
+ default:
+ isc_throw(BadValue, "cannot compare Elements of type " << to_string(a.getType()));
+ }
+}
+
+//
+// factory functions
+//
+ElementPtr
+Element::create(const Position& pos) {
+ return (ElementPtr(new NullElement(pos)));
+}
+
+ElementPtr
+Element::create(const long long int i, const Position& pos) {
+ return (ElementPtr(new IntElement(static_cast<int64_t>(i), pos)));
+}
+
+ElementPtr
+Element::create(const isc::util::int128_t& i, const Position& pos) {
+ return (ElementPtr(new BigIntElement(i, pos)));
+}
+
+ElementPtr
+Element::create(const int i, const Position& pos) {
+ return (create(static_cast<long long int>(i), pos));
+}
+
+ElementPtr
+Element::create(const long int i, const Position& pos) {
+ return (create(static_cast<long long int>(i), pos));
+}
+
+ElementPtr
+Element::create(const uint32_t i, const Position& pos) {
+ return (create(static_cast<long long int>(i), pos));
+}
+
+ElementPtr
+Element::create(const double d, const Position& pos) {
+ return (ElementPtr(new DoubleElement(d, pos)));
+}
+
+ElementPtr
+Element::create(const bool b, const Position& pos) {
+ return (ElementPtr(new BoolElement(b, pos)));
+}
+
+ElementPtr
+Element::create(const std::string& s, const Position& pos) {
+ return (ElementPtr(new StringElement(s, pos)));
+}
+
+ElementPtr
+Element::create(const char *s, const Position& pos) {
+ return (create(std::string(s), pos));
+}
+
+ElementPtr
+Element::createList(const Position& pos) {
+ return (ElementPtr(new ListElement(pos)));
+}
+
+ElementPtr
+Element::createMap(const Position& pos) {
+ return (ElementPtr(new MapElement(pos)));
+}
+
+
+//
+// helper functions for fromJSON factory
+//
+namespace {
+bool
+charIn(const int c, const char* chars) {
+ const size_t chars_len = std::strlen(chars);
+ for (size_t i = 0; i < chars_len; ++i) {
+ if (chars[i] == c) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+void
+skipChars(std::istream& in, const char* chars, int& line, int& pos) {
+ int c = in.peek();
+ while (charIn(c, chars) && c != EOF) {
+ if (c == '\n') {
+ ++line;
+ pos = 1;
+ } else {
+ ++pos;
+ }
+ in.ignore();
+ c = in.peek();
+ }
+}
+
+// skip on the input stream to one of the characters in chars
+// if another character is found this function throws JSONError
+// unless that character is specified in the optional may_skip
+//
+// It returns the found character (as an int value).
+int
+skipTo(std::istream& in, const std::string& file, int& line, int& pos,
+ const char* chars, const char* may_skip="") {
+ int c = in.get();
+ ++pos;
+ while (c != EOF) {
+ if (c == '\n') {
+ pos = 1;
+ ++line;
+ }
+ if (charIn(c, may_skip)) {
+ c = in.get();
+ ++pos;
+ } else if (charIn(c, chars)) {
+ while (charIn(in.peek(), may_skip)) {
+ if (in.peek() == '\n') {
+ pos = 1;
+ ++line;
+ } else {
+ ++pos;
+ }
+ in.ignore();
+ }
+ return (c);
+ } else {
+ throwJSONError(std::string("'") + std::string(1, c) + "' read, one of \"" + chars + "\" expected", file, line, pos);
+ }
+ }
+ throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos);
+ return (c); // shouldn't reach here, but some compilers require it
+}
+
+// TODO: Should we check for all other official escapes here (and
+// error on the rest)?
+std::string
+strFromStringstream(std::istream& in, const std::string& file,
+ const int line, int& pos) {
+ std::stringstream ss;
+ int c = in.get();
+ ++pos;
+ if (c == '"') {
+ c = in.get();
+ ++pos;
+ } else {
+ throwJSONError("String expected", file, line, pos);
+ }
+
+ while (c != EOF && c != '"') {
+ if (c == '\\') {
+ // see the spec for allowed escape characters
+ int d;
+ switch (in.peek()) {
+ case '"':
+ c = '"';
+ break;
+ case '/':
+ c = '/';
+ break;
+ case '\\':
+ c = '\\';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'u':
+ // skip first 0
+ in.ignore();
+ ++pos;
+ c = in.peek();
+ if (c != '0') {
+ throwJSONError("Unsupported unicode escape", file, line, pos);
+ }
+ // skip second 0
+ in.ignore();
+ ++pos;
+ c = in.peek();
+ if (c != '0') {
+ throwJSONError("Unsupported unicode escape", file, line, pos - 2);
+ }
+ // get first digit
+ in.ignore();
+ ++pos;
+ d = in.peek();
+ if ((d >= '0') && (d <= '9')) {
+ c = (d - '0') << 4;
+ } else if ((d >= 'A') && (d <= 'F')) {
+ c = (d - 'A' + 10) << 4;
+ } else if ((d >= 'a') && (d <= 'f')) {
+ c = (d - 'a' + 10) << 4;
+ } else {
+ throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 3);
+ }
+ // get second digit
+ in.ignore();
+ ++pos;
+ d = in.peek();
+ if ((d >= '0') && (d <= '9')) {
+ c |= d - '0';
+ } else if ((d >= 'A') && (d <= 'F')) {
+ c |= d - 'A' + 10;
+ } else if ((d >= 'a') && (d <= 'f')) {
+ c |= d - 'a' + 10;
+ } else {
+ throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 4);
+ }
+ break;
+ default:
+ throwJSONError("Bad escape", file, line, pos);
+ }
+ // drop the escaped char
+ in.ignore();
+ ++pos;
+ }
+ ss.put(c);
+ c = in.get();
+ ++pos;
+ }
+ if (c == EOF) {
+ throwJSONError("Unterminated string", file, line, pos);
+ }
+ return (ss.str());
+}
+
+std::string
+wordFromStringstream(std::istream& in, int& pos) {
+ std::stringstream ss;
+ while (isalpha(in.peek())) {
+ ss << (char) in.get();
+ }
+ pos += ss.str().size();
+ return (ss.str());
+}
+
+std::string
+numberFromStringstream(std::istream& in, int& pos) {
+ std::stringstream ss;
+ while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' ||
+ in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') {
+ ss << (char) in.get();
+ }
+ pos += ss.str().size();
+ return (ss.str());
+}
+
+// Should we change from IntElement and DoubleElement to NumberElement
+// that can also hold an e value? (and have specific getters if the
+// value is larger than an int can handle)
+//
+ElementPtr
+fromStringstreamNumber(std::istream& in, const std::string& file,
+ const int line, int& pos) {
+ // Remember position where the value starts. It will be set in the
+ // Position structure of the Element to be created.
+ const uint32_t start_pos = pos;
+ // This will move the pos to the end of the value.
+ const std::string number = numberFromStringstream(in, pos);
+
+ if (number.find_first_of(".eE") < number.size()) {
+ try {
+ return (Element::create(boost::lexical_cast<double>(number),
+ Element::Position(file, line, start_pos)));
+ } catch (const boost::bad_lexical_cast&) {
+ throwJSONError(std::string("Number overflow: ") + number,
+ file, line, start_pos);
+ }
+ } else {
+ try {
+ return (Element::create(boost::lexical_cast<int64_t>(number),
+ Element::Position(file, line, start_pos)));
+ } catch (const boost::bad_lexical_cast&) {
+ throwJSONError(std::string("Number overflow: ") + number, file,
+ line, start_pos);
+ }
+ }
+ return (ElementPtr());
+}
+
+ElementPtr
+fromStringstreamBool(std::istream& in, const std::string& file,
+ const int line, int& pos) {
+ // Remember position where the value starts. It will be set in the
+ // Position structure of the Element to be created.
+ const uint32_t start_pos = pos;
+ // This will move the pos to the end of the value.
+ const std::string word = wordFromStringstream(in, pos);
+
+ if (word == "true") {
+ return (Element::create(true, Element::Position(file, line,
+ start_pos)));
+ } else if (word == "false") {
+ return (Element::create(false, Element::Position(file, line,
+ start_pos)));
+ } else {
+ throwJSONError(std::string("Bad boolean value: ") + word, file,
+ line, start_pos);
+ }
+ return (ElementPtr());
+}
+
+ElementPtr
+fromStringstreamNull(std::istream& in, const std::string& file,
+ const int line, int& pos) {
+ // Remember position where the value starts. It will be set in the
+ // Position structure of the Element to be created.
+ const uint32_t start_pos = pos;
+ // This will move the pos to the end of the value.
+ const std::string word = wordFromStringstream(in, pos);
+ if (word == "null") {
+ return (Element::create(Element::Position(file, line, start_pos)));
+ } else {
+ throwJSONError(std::string("Bad null value: ") + word, file,
+ line, start_pos);
+ return (ElementPtr());
+ }
+}
+
+ElementPtr
+fromStringstreamString(std::istream& in, const std::string& file, int& line,
+ int& pos) {
+ // Remember position where the value starts. It will be set in the
+ // Position structure of the Element to be created.
+ const uint32_t start_pos = pos;
+ // This will move the pos to the end of the value.
+ const std::string string_value = strFromStringstream(in, file, line, pos);
+ return (Element::create(string_value, Element::Position(file, line,
+ start_pos)));
+}
+
+ElementPtr
+fromStringstreamList(std::istream& in, const std::string& file, int& line,
+ int& pos) {
+ int c = 0;
+ ElementPtr list = Element::createList(Element::Position(file, line, pos));
+ ElementPtr cur_list_element;
+
+ skipChars(in, WHITESPACE, line, pos);
+ while (c != EOF && c != ']') {
+ if (in.peek() != ']') {
+ cur_list_element = Element::fromJSON(in, file, line, pos);
+ list->add(cur_list_element);
+ c = skipTo(in, file, line, pos, ",]", WHITESPACE);
+ } else {
+ c = in.get();
+ ++pos;
+ }
+ }
+ return (list);
+}
+
+ElementPtr
+fromStringstreamMap(std::istream& in, const std::string& file, int& line,
+ int& pos) {
+ ElementPtr map = Element::createMap(Element::Position(file, line, pos));
+ skipChars(in, WHITESPACE, line, pos);
+ int c = in.peek();
+ if (c == EOF) {
+ throwJSONError(std::string("Unterminated map, <string> or } expected"), file, line, pos);
+ } else if (c == '}') {
+ // empty map, skip closing curly
+ in.ignore();
+ } else {
+ while (c != EOF && c != '}') {
+ std::string key = strFromStringstream(in, file, line, pos);
+
+ skipTo(in, file, line, pos, ":", WHITESPACE);
+ // skip the :
+
+ ConstElementPtr value = Element::fromJSON(in, file, line, pos);
+ map->set(key, value);
+
+ c = skipTo(in, file, line, pos, ",}", WHITESPACE);
+ }
+ }
+ return (map);
+}
+} // end anonymous namespace
+
+std::string
+Element::typeToName(Element::types type) {
+ switch (type) {
+ case Element::integer:
+ return (std::string("integer"));
+ case Element::bigint:
+ return (std::string("bigint"));
+ case Element::real:
+ return (std::string("real"));
+ case Element::boolean:
+ return (std::string("boolean"));
+ case Element::string:
+ return (std::string("string"));
+ case Element::list:
+ return (std::string("list"));
+ case Element::map:
+ return (std::string("map"));
+ case Element::null:
+ return (std::string("null"));
+ case Element::any:
+ return (std::string("any"));
+ default:
+ return (std::string("unknown"));
+ }
+}
+
+Element::types
+Element::nameToType(const std::string& type_name) {
+ if (type_name == "integer") {
+ return (Element::integer);
+ } else if (type_name == "bigint") {
+ return (Element::bigint);
+ } else if (type_name == "real") {
+ return (Element::real);
+ } else if (type_name == "boolean") {
+ return (Element::boolean);
+ } else if (type_name == "string") {
+ return (Element::string);
+ } else if (type_name == "list") {
+ return (Element::list);
+ } else if (type_name == "map") {
+ return (Element::map);
+ } else if (type_name == "named_set") {
+ return (Element::map);
+ } else if (type_name == "null") {
+ return (Element::null);
+ } else if (type_name == "any") {
+ return (Element::any);
+ } else {
+ isc_throw(TypeError, type_name + " is not a valid type name");
+ }
+}
+
+ElementPtr
+Element::fromJSON(std::istream& in, bool preproc) {
+
+ int line = 1, pos = 1;
+ stringstream filtered;
+ if (preproc) {
+ preprocess(in, filtered);
+ }
+
+ ElementPtr value = fromJSON(preproc ? filtered : in, "<istream>", line, pos);
+
+ return (value);
+}
+
+ElementPtr
+Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) {
+ int line = 1, pos = 1;
+ stringstream filtered;
+ if (preproc) {
+ preprocess(in, filtered);
+ }
+ return (fromJSON(preproc ? filtered : in, file_name, line, pos));
+}
+
+ElementPtr
+Element::fromJSON(std::istream& in, const std::string& file, int& line,
+ int& pos) {
+ int c = 0;
+ ElementPtr element;
+ bool el_read = false;
+ skipChars(in, WHITESPACE, line, pos);
+ while (c != EOF && !el_read) {
+ c = in.get();
+ pos++;
+ switch(c) {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '0':
+ case '-':
+ case '+':
+ case '.':
+ in.putback(c);
+ --pos;
+ element = fromStringstreamNumber(in, file, line, pos);
+ el_read = true;
+ break;
+ case 't':
+ case 'f':
+ in.putback(c);
+ --pos;
+ element = fromStringstreamBool(in, file, line, pos);
+ el_read = true;
+ break;
+ case 'n':
+ in.putback(c);
+ --pos;
+ element = fromStringstreamNull(in, file, line, pos);
+ el_read = true;
+ break;
+ case '"':
+ in.putback('"');
+ --pos;
+ element = fromStringstreamString(in, file, line, pos);
+ el_read = true;
+ break;
+ case '[':
+ element = fromStringstreamList(in, file, line, pos);
+ el_read = true;
+ break;
+ case '{':
+ element = fromStringstreamMap(in, file, line, pos);
+ el_read = true;
+ break;
+ case EOF:
+ break;
+ default:
+ throwJSONError(std::string("error: unexpected character ") + std::string(1, c), file, line, pos);
+ break;
+ }
+ }
+ if (el_read) {
+ return (element);
+ } else {
+ isc_throw(JSONError, "nothing read");
+ }
+}
+
+ElementPtr
+Element::fromJSON(const std::string& in, bool preproc) {
+ std::stringstream ss;
+ ss << in;
+
+ int line = 1, pos = 1;
+ stringstream filtered;
+ if (preproc) {
+ preprocess(ss, filtered);
+ }
+ ElementPtr result(fromJSON(preproc ? filtered : ss, "<string>", line, pos));
+ skipChars(ss, WHITESPACE, line, pos);
+ // ss must now be at end
+ if (ss.peek() != EOF) {
+ throwJSONError("Extra data", "<string>", line, pos);
+ }
+ return result;
+}
+
+ElementPtr
+Element::fromJSONFile(const std::string& file_name, bool preproc) {
+ // zero out the errno to be safe
+ errno = 0;
+
+ std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary);
+ if (!infile.is_open()) {
+ const char* error = strerror(errno);
+ isc_throw(InvalidOperation, "failed to read file '" << file_name
+ << "': " << error);
+ }
+
+ return (fromJSON(infile, file_name, preproc));
+}
+
+// to JSON format
+
+void
+IntElement::toJSON(std::ostream& ss) const {
+ ss << intValue();
+}
+
+void
+BigIntElement::toJSON(std::ostream& ss) const {
+ ss << bigIntValue();
+}
+
+void
+DoubleElement::toJSON(std::ostream& ss) const {
+ // The default output for doubles nicely drops off trailing
+ // zeros, however this produces strings without decimal points
+ // for whole number values. When reparsed this will create
+ // IntElements not DoubleElements. Rather than used a fixed
+ // precision, we'll just tack on an ".0" when the decimal point
+ // is missing.
+ ostringstream val_ss;
+ val_ss << doubleValue();
+ ss << val_ss.str();
+ if (val_ss.str().find_first_of('.') == string::npos) {
+ ss << ".0";
+ }
+}
+
+void
+BoolElement::toJSON(std::ostream& ss) const {
+ if (boolValue()) {
+ ss << "true";
+ } else {
+ ss << "false";
+ }
+}
+
+void
+NullElement::toJSON(std::ostream& ss) const {
+ ss << "null";
+}
+
+void
+StringElement::toJSON(std::ostream& ss) const {
+ ss << "\"";
+ const std::string& str = stringValue();
+ for (size_t i = 0; i < str.size(); ++i) {
+ const char c = str[i];
+ // Escape characters as defined in JSON spec
+ // Note that we do not escape forward slash; this
+ // is allowed, but not mandatory.
+ switch (c) {
+ case '"':
+ ss << '\\' << c;
+ break;
+ case '\\':
+ ss << '\\' << c;
+ break;
+ case '\b':
+ ss << '\\' << 'b';
+ break;
+ case '\f':
+ ss << '\\' << 'f';
+ break;
+ case '\n':
+ ss << '\\' << 'n';
+ break;
+ case '\r':
+ ss << '\\' << 'r';
+ break;
+ case '\t':
+ ss << '\\' << 't';
+ break;
+ default:
+ if (((c >= 0) && (c < 0x20)) || (c < 0) || (c >= 0x7f)) {
+ std::ostringstream esc;
+ esc << "\\u"
+ << hex
+ << setw(4)
+ << setfill('0')
+ << (static_cast<unsigned>(c) & 0xff);
+ ss << esc.str();
+ } else {
+ ss << c;
+ }
+ }
+ }
+ ss << "\"";
+}
+
+void
+ListElement::toJSON(std::ostream& ss) const {
+ ss << "[ ";
+
+ const std::vector<ElementPtr>& v = listValue();
+ for (auto it = v.begin(); it != v.end(); ++it) {
+ if (it != v.begin()) {
+ ss << ", ";
+ }
+ (*it)->toJSON(ss);
+ }
+ ss << " ]";
+}
+
+void
+MapElement::toJSON(std::ostream& ss) const {
+ ss << "{ ";
+
+ const std::map<std::string, ConstElementPtr>& m = mapValue();
+ for (auto it = m.begin(); it != m.end(); ++it) {
+ if (it != m.begin()) {
+ ss << ", ";
+ }
+ ss << "\"" << (*it).first << "\": ";
+ if ((*it).second) {
+ (*it).second->toJSON(ss);
+ } else {
+ ss << "None";
+ }
+ }
+ ss << " }";
+}
+
+// throws when one of the types in the path (except the one
+// we're looking for) is not a MapElement
+// returns 0 if it could simply not be found
+// should that also be an exception?
+ConstElementPtr
+MapElement::find(const std::string& id) const {
+ const size_t sep = id.find('/');
+ if (sep == std::string::npos) {
+ return (get(id));
+ } else {
+ ConstElementPtr ce = get(id.substr(0, sep));
+ if (ce) {
+ // ignore trailing slash
+ if (sep + 1 != id.size()) {
+ return (ce->find(id.substr(sep + 1)));
+ } else {
+ return (ce);
+ }
+ } else {
+ return (ElementPtr());
+ }
+ }
+}
+
+ElementPtr
+Element::fromWire(const std::string& s) {
+ std::stringstream ss;
+ ss << s;
+ int line = 0, pos = 0;
+ return (fromJSON(ss, "<wire>", line, pos));
+}
+
+ElementPtr
+Element::fromWire(std::stringstream& in, int) {
+ //
+ // Check protocol version
+ //
+ //for (int i = 0 ; i < 4 ; ++i) {
+ // const unsigned char version_byte = get_byte(in);
+ // if (PROTOCOL_VERSION[i] != version_byte) {
+ // throw DecodeError("Protocol version incorrect");
+ // }
+ //}
+ //length -= 4;
+ int line = 0, pos = 0;
+ return (fromJSON(in, "<wire>", line, pos));
+}
+
+void
+MapElement::set(const std::string& key, ConstElementPtr value) {
+ m[key] = value;
+}
+
+bool
+MapElement::find(const std::string& id, ConstElementPtr& t) const {
+ try {
+ ConstElementPtr p = find(id);
+ if (p) {
+ t = p;
+ return (true);
+ }
+ } catch (const TypeError&) {
+ // ignore
+ }
+ return (false);
+}
+
+bool
+IntElement::equals(const Element& other) const {
+ // Let's not be very picky with constraining the integer types to be the
+ // same. Equality is sometimes checked from high-up in the Element hierarcy.
+ // That is a context which, most of the time, does not have information on
+ // the type of integers stored on Elements lower in the hierarchy. So it
+ // would be difficult to differentiate between the integer types.
+ return (other.getType() == Element::integer && i == other.intValue()) ||
+ (other.getType() == Element::bigint && i == other.bigIntValue());
+}
+
+bool
+BigIntElement::equals(const Element& other) const {
+ // Let's not be very picky with constraining the integer types to be the
+ // same. Equality is sometimes checked from high-up in the Element hierarcy.
+ // That is a context which, most of the time, does not have information on
+ // the type of integers stored on Elements lower in the hierarchy. So it
+ // would be difficult to differentiate between the integer types.
+ return (other.getType() == Element::bigint && i_ == other.bigIntValue()) ||
+ (other.getType() == Element::integer && i_ == other.intValue());
+}
+
+bool
+DoubleElement::equals(const Element& other) const {
+ return (other.getType() == Element::real) &&
+ (fabs(d - other.doubleValue()) < 1e-14);
+}
+
+bool
+BoolElement::equals(const Element& other) const {
+ return (other.getType() == Element::boolean) &&
+ (b == other.boolValue());
+}
+
+bool
+NullElement::equals(const Element& other) const {
+ return (other.getType() == Element::null);
+}
+
+bool
+StringElement::equals(const Element& other) const {
+ return (other.getType() == Element::string) &&
+ (s == other.stringValue());
+}
+
+bool
+ListElement::equals(const Element& other) const {
+ if (other.getType() == Element::list) {
+ const size_t s = size();
+ if (s != other.size()) {
+ return (false);
+ }
+ for (size_t i = 0; i < s; ++i) {
+ if (!get(i)->equals(*other.get(i))) {
+ return (false);
+ }
+ }
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+void
+ListElement::sort(std::string const& index /* = std::string() */) {
+ if (l.empty()) {
+ return;
+ }
+
+ int const t(l.at(0)->getType());
+ std::function<bool(ElementPtr, ElementPtr)> comparator;
+ if (t == map) {
+ if (index.empty()) {
+ isc_throw(BadValue, "index required when sorting maps");
+ }
+ comparator = [&](ElementPtr const& a, ElementPtr const& b) {
+ ConstElementPtr const& ai(a->get(index));
+ ConstElementPtr const& bi(b->get(index));
+ if (ai && bi) {
+ return *ai < *bi;
+ }
+ return true;
+ };
+ } else if (t == list) {
+ // Nested lists. Not supported.
+ return;
+ } else {
+ // Assume scalars.
+ if (!index.empty()) {
+ isc_throw(BadValue, "index given when sorting scalars?");
+ }
+ comparator = [&](ElementPtr const& a, ElementPtr const& b) {
+ return *a < *b;
+ };
+ }
+
+ std::sort(l.begin(), l.end(), comparator);
+}
+
+bool
+MapElement::equals(const Element& other) const {
+ if (other.getType() == Element::map) {
+ if (size() != other.size()) {
+ return (false);
+ }
+ for (auto kv : mapValue()) {
+ auto key = kv.first;
+ if (other.contains(key)) {
+ if (!get(key)->equals(*other.get(key))) {
+ return (false);
+ }
+ } else {
+ return (false);
+ }
+ }
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+bool
+isNull(ConstElementPtr p) {
+ return (!p);
+}
+
+void
+removeIdentical(ElementPtr a, ConstElementPtr b) {
+ if (!b) {
+ return;
+ }
+ if (a->getType() != Element::map || b->getType() != Element::map) {
+ isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
+ }
+
+ // As maps do not allow entries with multiple keys, we can either iterate
+ // over a checking for identical entries in b or vice-versa. As elements
+ // are removed from a if a match is found, we choose to iterate over b to
+ // avoid problems with element removal affecting the iterator.
+ for (auto kv : b->mapValue()) {
+ auto key = kv.first;
+ if (a->contains(key)) {
+ if (a->get(key)->equals(*b->get(key))) {
+ a->remove(key);
+ }
+ }
+ }
+}
+
+ConstElementPtr
+removeIdentical(ConstElementPtr a, ConstElementPtr b) {
+ ElementPtr result = Element::createMap();
+
+ if (!b) {
+ return (result);
+ }
+
+ if (a->getType() != Element::map || b->getType() != Element::map) {
+ isc_throw(TypeError, "Non-map Elements passed to removeIdentical");
+ }
+
+ for (auto kv : a->mapValue()) {
+ auto key = kv.first;
+ if (!b->contains(key) ||
+ !a->get(key)->equals(*b->get(key))) {
+ result->set(key, kv.second);
+ }
+ }
+
+ return (result);
+}
+
+void
+merge(ElementPtr element, ConstElementPtr other) {
+ if (element->getType() != Element::map ||
+ other->getType() != Element::map) {
+ isc_throw(TypeError, "merge arguments not MapElements");
+ }
+
+ for (auto kv : other->mapValue()) {
+ auto key = kv.first;
+ auto value = kv.second;
+ if (value && value->getType() != Element::null) {
+ element->set(key, value);
+ } else if (element->contains(key)) {
+ element->remove(key);
+ }
+ }
+}
+
+void
+mergeDiffAdd(ElementPtr& element, ElementPtr& other,
+ HierarchyDescriptor& hierarchy, std::string key, size_t idx) {
+ if (element->getType() != other->getType()) {
+ isc_throw(TypeError, "mergeDiffAdd arguments not same type");
+ }
+
+ if (element->getType() == Element::list) {
+ // Store new elements in a separate container so we don't overwrite
+ // options as we add them (if there are duplicates).
+ ElementPtr new_elements = Element::createList();
+ for (auto& right : other->listValue()) {
+ // Check if we have any description of the key in the configuration
+ // hierarchy.
+ auto f = hierarchy[idx].find(key);
+ if (f != hierarchy[idx].end()) {
+ bool found = false;
+ ElementPtr mutable_right = boost::const_pointer_cast<Element>(right);
+ for (auto& left : element->listValue()) {
+ ElementPtr mutable_left = boost::const_pointer_cast<Element>(left);
+ // Check if the elements refer to the same configuration
+ // entity.
+ if (f->second.match_(mutable_left, mutable_right)) {
+ found = true;
+ mergeDiffAdd(mutable_left, mutable_right, hierarchy, key, idx);
+ }
+ }
+ if (!found) {
+ new_elements->add(right);
+ }
+ } else {
+ new_elements->add(right);
+ }
+ }
+ // Finally add the new elements.
+ for (auto& right : new_elements->listValue()) {
+ element->add(right);
+ }
+ return;
+ }
+
+ if (element->getType() == Element::map) {
+ for (auto kv : other->mapValue()) {
+ auto current_key = kv.first;
+ auto value = boost::const_pointer_cast<Element>(kv.second);
+ if (value && value->getType() != Element::null) {
+ if (element->contains(current_key) &&
+ (value->getType() == Element::map ||
+ value->getType() == Element::list)) {
+ ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
+ mergeDiffAdd(mutable_element, value, hierarchy, current_key, idx + 1);
+ } else {
+ element->set(current_key, value);
+ }
+ }
+ }
+ return;
+ }
+ element = other;
+}
+
+void
+mergeDiffDel(ElementPtr& element, ElementPtr& other,
+ HierarchyDescriptor& hierarchy, std::string key, size_t idx) {
+ if (element->getType() != other->getType()) {
+ isc_throw(TypeError, "mergeDiffDel arguments not same type");
+ }
+
+ if (element->getType() == Element::list) {
+ for (auto const& value : other->listValue()) {
+ ElementPtr mutable_right = boost::const_pointer_cast<Element>(value);
+ for (uint32_t iter = 0; iter < element->listValue().size();) {
+ bool removed = false;
+ // Check if we have any description of the key in the
+ // configuration hierarchy.
+ auto f = hierarchy[idx].find(key);
+ if (f != hierarchy[idx].end()) {
+ ElementPtr mutable_left = boost::const_pointer_cast<Element>(element->listValue().at(iter));
+ // Check if the elements refer to the same configuration
+ // entity.
+ if (f->second.match_(mutable_left, mutable_right)) {
+ // Check if the user supplied data only contains
+ // identification information, so the intent is to
+ // delete the element, not just element data.
+ if (f->second.no_data_(mutable_right)) {
+ element->remove(iter);
+ removed = true;
+ } else {
+ mergeDiffDel(mutable_left, mutable_right, hierarchy, key, idx);
+ if (mutable_left->empty()) {
+ element->remove(iter);
+ removed = true;
+ }
+ }
+ }
+ } else if (element->listValue().at(iter)->equals(*value)) {
+ element->remove(iter);
+ removed = true;
+ }
+ if (!removed) {
+ ++iter;
+ }
+ }
+ }
+ return;
+ }
+
+ if (element->getType() == Element::map) {
+ // If the resulting element still contains data, we need to restore the
+ // key parameters, so we store them here.
+ ElementPtr new_elements = Element::createMap();
+ for (auto kv : other->mapValue()) {
+ auto current_key = kv.first;
+ auto value = boost::const_pointer_cast<Element>(kv.second);
+ if (value && value->getType() != Element::null) {
+ if (element->contains(current_key)) {
+ ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
+ if (mutable_element->getType() == Element::map ||
+ mutable_element->getType() == Element::list) {
+ mergeDiffDel(mutable_element, value, hierarchy, current_key, idx + 1);
+ if (mutable_element->empty()) {
+ element->remove(current_key);
+ }
+ } else {
+ // Check if we have any description of the key in the
+ // configuration hierarchy.
+ auto f = hierarchy[idx].find(key);
+ if (f != hierarchy[idx].end()) {
+ // Check if the key is used for element
+ // identification.
+ if (f->second.is_key_(current_key)) {
+ // Store the key parameter.
+ new_elements->set(current_key, mutable_element);
+ }
+ }
+ element->remove(current_key);
+ }
+ }
+ }
+ }
+ // If the element still contains data, restore the key elements.
+ if (element->size()) {
+ for (auto kv : new_elements->mapValue()) {
+ element->set(kv.first, kv.second);
+ }
+ }
+ return;
+ }
+ element = ElementPtr(new NullElement);
+}
+
+void
+extend(const std::string& container, const std::string& extension,
+ ElementPtr& element, ElementPtr& other, HierarchyDescriptor& hierarchy,
+ std::string key, size_t idx, bool alter) {
+ if (element->getType() != other->getType()) {
+ isc_throw(TypeError, "extend arguments not same type");
+ }
+
+ if (element->getType() == Element::list) {
+ for (auto& right : other->listValue()) {
+ // Check if we have any description of the key in the configuration
+ // hierarchy.
+ auto f = hierarchy[idx].find(key);
+ if (f != hierarchy[idx].end()) {
+ ElementPtr mutable_right = boost::const_pointer_cast<Element>(right);
+ for (auto& left : element->listValue()) {
+ ElementPtr mutable_left = boost::const_pointer_cast<Element>(left);
+ if (container == key) {
+ alter = true;
+ }
+ if (f->second.match_(mutable_left, mutable_right)) {
+ extend(container, extension, mutable_left, mutable_right,
+ hierarchy, key, idx, alter);
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ if (element->getType() == Element::map) {
+ for (auto kv : other->mapValue()) {
+ auto current_key = kv.first;
+ auto value = boost::const_pointer_cast<Element>(kv.second);
+ if (value && value->getType() != Element::null) {
+ if (element->contains(current_key) &&
+ (value->getType() == Element::map ||
+ value->getType() == Element::list)) {
+ ElementPtr mutable_element = boost::const_pointer_cast<Element>(element->get(current_key));
+ if (container == key) {
+ alter = true;
+ }
+ extend(container, extension, mutable_element, value, hierarchy, current_key, idx + 1, alter);
+ } else if (alter && current_key == extension) {
+ element->set(current_key, value);
+ }
+ }
+ }
+ return;
+ }
+}
+
+ElementPtr
+copy(ConstElementPtr from, int level) {
+ if (!from) {
+ isc_throw(BadValue, "copy got a null pointer");
+ }
+ int from_type = from->getType();
+ if (from_type == Element::integer) {
+ return (ElementPtr(new IntElement(from->intValue())));
+ } else if (from_type == Element::real) {
+ return (ElementPtr(new DoubleElement(from->doubleValue())));
+ } else if (from_type == Element::boolean) {
+ return (ElementPtr(new BoolElement(from->boolValue())));
+ } else if (from_type == Element::null) {
+ return (ElementPtr(new NullElement()));
+ } else if (from_type == Element::string) {
+ return (ElementPtr(new StringElement(from->stringValue())));
+ } else if (from_type == Element::list) {
+ ElementPtr result = ElementPtr(new ListElement());
+ for (auto elem : from->listValue()) {
+ if (level == 0) {
+ result->add(elem);
+ } else {
+ result->add(copy(elem, level - 1));
+ }
+ }
+ return (result);
+ } else if (from_type == Element::map) {
+ ElementPtr result = ElementPtr(new MapElement());
+ for (auto kv : from->mapValue()) {
+ auto key = kv.first;
+ auto value = kv.second;
+ if (level == 0) {
+ result->set(key, value);
+ } else {
+ result->set(key, copy(value, level - 1));
+ }
+ }
+ return (result);
+ } else {
+ isc_throw(BadValue, "copy got an element of type: " << from_type);
+ }
+}
+
+namespace {
+
+// Helper function which blocks infinite recursion
+bool
+isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level) {
+ // check looping forever on cycles
+ if (!level) {
+ isc_throw(BadValue, "isEquivalent got infinite recursion: "
+ "arguments include cycles");
+ }
+ if (!a || !b) {
+ isc_throw(BadValue, "isEquivalent got a null pointer");
+ }
+ // check types
+ if (a->getType() != b->getType()) {
+ return (false);
+ }
+ if (a->getType() == Element::list) {
+ // check empty
+ if (a->empty()) {
+ return (b->empty());
+ }
+ // check size
+ if (a->size() != b->size()) {
+ return (false);
+ }
+
+ // copy b into a list
+ const size_t s = a->size();
+ std::list<ConstElementPtr> l;
+ for (size_t i = 0; i < s; ++i) {
+ l.push_back(b->get(i));
+ }
+
+ // iterate on a
+ for (size_t i = 0; i < s; ++i) {
+ ConstElementPtr item = a->get(i);
+ // lookup this item in the list
+ bool found = false;
+ for (auto it = l.begin(); it != l.end(); ++it) {
+ // if found in the list remove it
+ if (isEquivalent0(item, *it, level - 1)) {
+ found = true;
+ l.erase(it);
+ break;
+ }
+ }
+ // if not found argument differs
+ if (!found) {
+ return (false);
+ }
+ }
+
+ // sanity check: the list must be empty
+ if (!l.empty()) {
+ isc_throw(Unexpected, "isEquivalent internal error");
+ }
+ return (true);
+ } else if (a->getType() == Element::map) {
+ // check sizes
+ if (a->size() != b->size()) {
+ return (false);
+ }
+ // iterate on the first map
+ for (auto kv : a->mapValue()) {
+ // get the b value for the given keyword and recurse
+ ConstElementPtr item = b->get(kv.first);
+ if (!item || !isEquivalent0(kv.second, item, level - 1)) {
+ return (false);
+ }
+ }
+ return (true);
+ } else {
+ return (a->equals(*b));
+ }
+}
+
+} // end anonymous namespace
+
+bool
+isEquivalent(ConstElementPtr a, ConstElementPtr b) {
+ return (isEquivalent0(a, b, 100));
+}
+
+void
+prettyPrint(ConstElementPtr element, std::ostream& out,
+ unsigned indent, unsigned step) {
+ if (!element) {
+ isc_throw(BadValue, "prettyPrint got a null pointer");
+ }
+ if (element->getType() == Element::list) {
+ // empty list case
+ if (element->empty()) {
+ out << "[ ]";
+ return;
+ }
+
+ // complex ? multiline : oneline
+ if (!element->get(0)) {
+ isc_throw(BadValue, "prettyPrint got a null pointer");
+ }
+ int first_type = element->get(0)->getType();
+ bool complex = false;
+ if ((first_type == Element::list) || (first_type == Element::map)) {
+ complex = true;
+ }
+ std::string separator = complex ? ",\n" : ", ";
+
+ // open the list
+ out << "[" << (complex ? "\n" : " ");
+
+ // iterate on items
+ const auto& l = element->listValue();
+ for (auto it = l.begin(); it != l.end(); ++it) {
+ // add the separator if not the first item
+ if (it != l.begin()) {
+ out << separator;
+ }
+ // add indentation
+ if (complex) {
+ out << std::string(indent + step, ' ');
+ }
+ // recursive call
+ prettyPrint(*it, out, indent + step, step);
+ }
+
+ // close the list
+ if (complex) {
+ out << "\n" << std::string(indent, ' ');
+ } else {
+ out << " ";
+ }
+ out << "]";
+ } else if (element->getType() == Element::map) {
+ // empty map case
+ if (element->size() == 0) {
+ out << "{ }";
+ return;
+ }
+
+ // open the map
+ out << "{\n";
+
+ // iterate on keyword: value
+ const auto& m = element->mapValue();
+ bool first = true;
+ for (auto it = m.begin(); it != m.end(); ++it) {
+ // add the separator if not the first item
+ if (first) {
+ first = false;
+ } else {
+ out << ",\n";
+ }
+ // add indentation
+ out << std::string(indent + step, ' ');
+ // add keyword:
+ out << "\"" << it->first << "\": ";
+ // recursive call
+ prettyPrint(it->second, out, indent + step, step);
+ }
+
+ // close the map
+ out << "\n" << std::string(indent, ' ') << "}";
+ } else {
+ // not a list or a map
+ element->toJSON(out);
+ }
+}
+
+std::string
+prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) {
+ std::stringstream ss;
+ prettyPrint(element, ss, indent, step);
+ return (ss.str());
+}
+
+void Element::preprocess(std::istream& in, std::stringstream& out) {
+
+ std::string line;
+
+ while (std::getline(in, line)) {
+ // If this is a comments line, replace it with empty line
+ // (so the line numbers will still match
+ if (!line.empty() && line[0] == '#') {
+ line = "";
+ }
+
+ // getline() removes end line characters. Unfortunately, we need
+ // it for getting the line numbers right (in case we report an
+ // error.
+ out << line;
+ out << "\n";
+ }
+}
+
+} // end of isc::data namespace
+} // end of isc namespace
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
new file mode 100644
index 0000000..e954ea9
--- /dev/null
+++ b/src/lib/cc/data.h
@@ -0,0 +1,1075 @@
+// Copyright (C) 2010-2023 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 ISC_DATA_H
+#define ISC_DATA_H 1
+
+#include <util/bigints.h>
+
+#include <iostream>
+#include <map>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+
+#include <exceptions/exceptions.h>
+
+namespace isc { namespace data {
+
+class Element;
+// todo: describe the rationale behind ElementPtr?
+typedef boost::shared_ptr<Element> ElementPtr;
+typedef boost::shared_ptr<const Element> ConstElementPtr;
+
+///
+/// @brief A standard Data module exception that is thrown if a function
+/// is called for an Element that has a wrong type (e.g. int_value on a
+/// ListElement)
+///
+class TypeError : public isc::Exception {
+public:
+ TypeError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
+/// @brief A standard Data module exception that is thrown if a parse
+/// error is encountered when constructing an Element from a string
+///
+// i'd like to use Exception here but we need one that is derived from
+// runtime_error (as this one is directly based on external data, and
+// i want to add some values to any static data string that is provided)
+class JSONError : public isc::Exception {
+public:
+ JSONError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
+/// @brief The @c Element class represents a piece of data, used by
+/// the command channel and configuration parts.
+///
+/// An @c Element can contain simple types (int, real, string, bool and
+/// None), and composite types (list and string->element maps)
+///
+/// Elements should in calling functions usually be referenced through
+/// an @c ElementPtr, which can be created using the factory functions
+/// @c Element::create() and @c Element::fromJSON()
+///
+/// Notes to developers: Element is a base class, implemented by a
+/// specific subclass for each type (IntElement, BoolElement, etc).
+/// Element does define all functions for all types, and defaults to
+/// raising a @c TypeError for functions that are not supported for
+/// the type in question.
+///
+class Element {
+
+public:
+ /// @brief Represents the position of the data element within a
+ /// configuration string.
+ ///
+ /// Position comprises a file name, line number and an offset within this
+ /// line where the element value starts. For example, if the JSON string is
+ ///
+ /// @code
+ /// { "foo": "some string",
+ /// "bar": 123 }
+ /// \endcode
+ ///
+ /// the position of the element "bar" is: line_ = 2; pos_ = 9, because
+ /// beginning of the value "123" is at offset 9 from the beginning of
+ /// the second line, including whitespaces.
+ ///
+ /// Note that the @c Position structure is used as an argument to @c Element
+ /// constructors and factory functions to avoid ambiguity and so that the
+ /// uint32_t arguments holding line number and position within the line are
+ /// not confused with the @c Element values passed to these functions.
+ struct Position {
+ std::string file_; ///< File name.
+ uint32_t line_; ///< Line number.
+ uint32_t pos_; ///< Position within the line.
+
+ /// @brief Default constructor.
+ Position() : file_(""), line_(0), pos_(0) {
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param file File name.
+ /// @param line Line number.
+ /// @param pos Position within the line.
+ Position(const std::string& file, const uint32_t line,
+ const uint32_t pos)
+ : file_(file), line_(line), pos_(pos) {
+ }
+
+ /// @brief Returns the position in the textual format.
+ ///
+ /// The returned position has the following format: file:line:pos.
+ std::string str() const;
+ };
+
+ /// @brief Returns @c Position object with line_ and pos_ set to 0, and
+ /// with an empty file name.
+ ///
+ /// The object containing two zeros is a default for most of the
+ /// methods creating @c Element objects. The returned value is static
+ /// so as it is not created everytime the function with the default
+ /// position argument is called.
+ static const Position& ZERO_POSITION() {
+ static Position position("", 0, 0);
+ return (position);
+ }
+
+ /// @brief The types that an Element can hold
+ ///
+ /// Some of these types need to match their associated integer from the
+ /// parameter_data_type database table, so let the enums be explicitly
+ /// mapped to integers, to reduce the chance of messing up.
+ ///
+ /// any is a special type used in list specifications, specifying that the
+ /// elements can be of any type.
+ enum types {
+ integer = 0,
+ real = 1,
+ boolean = 2,
+ null = 3,
+ string = 4,
+ bigint = 5,
+ list = 6,
+ map = 7,
+ any = 8,
+ };
+
+private:
+ // technically the type could be omitted; is it useful?
+ // should we remove it or replace it with a pure virtual
+ // function getType?
+ types type_;
+
+ /// @brief Position of the element in the configuration string.
+ Position position_;
+
+protected:
+
+ /// @brief Constructor.
+ ///
+ /// @param t Element type.
+ /// @param pos Structure holding position of the value of the data element.
+ /// It comprises the line number and the position within this line. The values
+ /// held in this structure are used for error logging purposes.
+ Element(types t, const Position& pos = ZERO_POSITION())
+ : type_(t), position_(pos) {
+ }
+
+
+public:
+ // base class; make dtor virtual
+ virtual ~Element() {};
+
+ /// @return the type of this element
+ types getType() const { return (type_); }
+
+ /// @brief Returns position where the data element's value starts in a
+ /// configuration string.
+ ///
+ /// @warning The returned reference is valid as long as the object which
+ /// created it lives.
+ const Position& getPosition() const { return (position_); }
+
+ /// Returns a string representing the Element and all its
+ /// child elements; note that this is different from stringValue(),
+ /// which only returns the single value of a StringElement
+ ///
+ /// The resulting string will contain the Element in JSON format.
+ ///
+ /// @return std::string containing the string representation
+ std::string str() const;
+
+ /// Returns the wireformat for the Element and all its child
+ /// elements.
+ ///
+ /// @return std::string containing the element in wire format
+ std::string toWire() const;
+ void toWire(std::ostream& out) const;
+
+ /// @brief Add the position to a TypeError message
+ /// should be used in place of isc_throw(TypeError, error)
+#define throwTypeError(error) \
+ { \
+ std::string msg_ = error; \
+ if ((position_.file_ != "") || \
+ (position_.line_ != 0) || \
+ (position_.pos_ != 0)) { \
+ msg_ += " in (" + position_.str() + ")"; \
+ } \
+ isc_throw(TypeError, msg_); \
+ }
+
+ /// @name pure virtuals, every derived class must implement these
+
+ /// @return true if the other ElementPtr has the same value and the same
+ /// type (or a different and compatible type), false otherwise.
+ virtual bool equals(const Element& other) const = 0;
+
+ /// Converts the Element to JSON format and appends it to
+ /// the given stringstream.
+ virtual void toJSON(std::ostream& ss) const = 0;
+
+ /// @name Type-specific getters
+ ///
+ /// @brief These functions only
+ /// work on their corresponding Element type. For all other
+ /// types, a TypeError is thrown.
+ /// If you want an exception-safe getter method, use
+ /// getValue() below
+ //@{
+ virtual int64_t intValue() const
+ { throwTypeError("intValue() called on non-integer Element"); };
+ virtual isc::util::int128_t bigIntValue() const {
+ throwTypeError("bigIntValue() called on non-big-integer Element");
+ }
+ virtual double doubleValue() const
+ { throwTypeError("doubleValue() called on non-double Element"); };
+ virtual bool boolValue() const
+ { throwTypeError("boolValue() called on non-Bool Element"); };
+ virtual std::string stringValue() const
+ { throwTypeError("stringValue() called on non-string Element"); };
+ virtual const std::vector<ElementPtr>& listValue() const {
+ // replace with real exception or empty vector?
+ throwTypeError("listValue() called on non-list Element");
+ };
+ virtual const std::map<std::string, ConstElementPtr>& mapValue() const {
+ // replace with real exception or empty map?
+ throwTypeError("mapValue() called on non-map Element");
+ };
+ //@}
+
+ /// @name Exception-safe getters
+ ///
+ /// @brief The getValue() functions return false if the given reference
+ /// is of another type than the element contains
+ /// By default it always returns false; the derived classes
+ /// override the function for their type, copying their
+ /// data to the given reference and returning true
+ ///
+ //@{
+ virtual bool getValue(int64_t& t) const;
+ virtual bool getValue(double& t) const;
+ virtual bool getValue(bool& t) const;
+ virtual bool getValue(std::string& t) const;
+ virtual bool getValue(std::vector<ElementPtr>& t) const;
+ virtual bool getValue(std::map<std::string, ConstElementPtr>& t) const;
+ //@}
+
+ ///
+ /// @name Exception-safe setters.
+ ///
+ /// @brief Return false if the Element is not
+ /// the right type. Set the value and return true if the Elements
+ /// is of the correct type
+ ///
+ /// Notes: Read notes of IntElement definition about the use of
+ /// long long int, long int and int.
+ //@{
+ virtual bool setValue(const long long int v);
+ virtual bool setValue(const isc::util::int128_t& v);
+ bool setValue(const long int i) { return (setValue(static_cast<long long int>(i))); };
+ bool setValue(const int i) { return (setValue(static_cast<long long int>(i))); };
+ virtual bool setValue(const double v);
+ virtual bool setValue(const bool t);
+ virtual bool setValue(const std::string& v);
+ virtual bool setValue(const std::vector<ElementPtr>& v);
+ virtual bool setValue(const std::map<std::string, ConstElementPtr>& v);
+ //@}
+
+ // Other functions for specific subtypes
+
+ /// @name ListElement functions
+ ///
+ /// @brief If the Element on which these functions are called are not
+ /// an instance of ListElement, a TypeError exception is thrown.
+ //@{
+ /// Returns the ElementPtr at the given index. If the index is out
+ /// of bounds, this function throws an std::out_of_range exception.
+ /// @param i The position of the ElementPtr to return
+ virtual ConstElementPtr get(const int i) const;
+
+ /// @brief returns element as non-const pointer
+ ///
+ /// @param i The position of the ElementPtr to retrieve
+ /// @return specified element pointer
+ virtual ElementPtr getNonConst(const int i) const;
+
+ /// Sets the ElementPtr at the given index. If the index is out
+ /// of bounds, this function throws an std::out_of_range exception.
+ /// @param i The position of the ElementPtr to set
+ /// @param element The ElementPtr to set at the position
+ virtual void set(const size_t i, ElementPtr element);
+
+ /// Adds an ElementPtr to the list
+ /// @param element The ElementPtr to add
+ virtual void add(ElementPtr element);
+
+ /// Removes the element at the given position. If the index is out
+ /// of nothing happens.
+ /// @param i The index of the element to remove.
+ virtual void remove(const int i);
+
+ /// Returns the number of elements in the list.
+ virtual size_t size() const;
+
+ /// Return true if there are no elements in the list.
+ virtual bool empty() const;
+ //@}
+
+
+ /// @name MapElement functions
+ ///
+ /// @brief If the Element on which these functions are called are not
+ /// an instance of MapElement, a TypeError exception is thrown.
+ //@{
+ /// Returns the ElementPtr at the given key
+ /// @param name The key of the Element to return
+ /// @return The ElementPtr at the given key, or null if not present
+ virtual ConstElementPtr get(const std::string& name) const;
+
+ /// Sets the ElementPtr at the given key
+ /// @param name The key of the Element to set
+ /// @param element The ElementPtr to set at the given key.
+ virtual void set(const std::string& name, ConstElementPtr element);
+
+ /// Remove the ElementPtr at the given key
+ /// @param name The key of the Element to remove
+ virtual void remove(const std::string& name);
+
+ /// Checks if there is data at the given key
+ /// @param name The key of the Element checked for existence
+ /// @return true if there is data at the key, false if not.
+ virtual bool contains(const std::string& name) const;
+
+ /// Recursively finds any data at the given identifier. The
+ /// identifier is a /-separated list of names of nested maps, with
+ /// the last name being the leaf that is returned.
+ ///
+ /// For instance, if you have a MapElement that contains another
+ /// MapElement at the key "foo", and that second MapElement contains
+ /// Another Element at key "bar", the identifier for that last
+ /// element from the first is "foo/bar".
+ ///
+ /// @param identifier The identifier of the element to find
+ /// @return The ElementPtr at the given identifier. Returns a
+ /// null ElementPtr if it is not found, which can be checked with
+ /// Element::is_null(ElementPtr e).
+ virtual ConstElementPtr find(const std::string& identifier) const;
+
+ /// See @c Element::find()
+ /// @param identifier The identifier of the element to find
+ /// @param t Reference to store the resulting ElementPtr, if found.
+ /// @return true if the element was found, false if not.
+ virtual bool find(const std::string& identifier, ConstElementPtr& t) const;
+ //@}
+
+ /// @name Factory functions
+
+ // TODO: should we move all factory functions to a different class
+ // so as not to burden the Element base with too many functions?
+ // and/or perhaps even to a separate header?
+
+ /// @name Direct factory functions
+ /// @brief These functions simply wrap the given data directly
+ /// in an Element object, and return a reference to it, in the form
+ /// of an @c ElementPtr.
+ /// These factory functions are exception-free (unless there is
+ /// no memory available, in which case bad_alloc is raised by the
+ /// underlying system).
+ /// (Note that that is different from an NullElement, which
+ /// represents an empty value, and is created with Element::create())
+ ///
+ /// Notes: Read notes of IntElement definition about the use of
+ /// long long int, long int and int.
+ //@{
+ static ElementPtr create(const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const long long int i,
+ const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const isc::util::int128_t& i,
+ const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const int i,
+ const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const long int i,
+ const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const uint32_t i,
+ const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const double d,
+ const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const bool b,
+ const Position& pos = ZERO_POSITION());
+ static ElementPtr create(const std::string& s,
+ const Position& pos = ZERO_POSITION());
+ // need both std:string and char *, since c++ will match
+ // bool before std::string when you pass it a char *
+ static ElementPtr create(const char *s,
+ const Position& pos = ZERO_POSITION());
+
+ /// @brief Creates an empty ListElement type ElementPtr.
+ ///
+ /// @param pos A structure holding position of the data element value
+ /// in the configuration string. It is used for error logging purposes.
+ static ElementPtr createList(const Position& pos = ZERO_POSITION());
+
+ /// @brief Creates an empty MapElement type ElementPtr.
+ ///
+ /// @param pos A structure holding position of the data element value
+ /// in the configuration string. It is used for error logging purposes.
+ static ElementPtr createMap(const Position& pos = ZERO_POSITION());
+ //@}
+
+ /// @name Compound factory functions
+
+ /// @brief These functions will parse the given string (JSON)
+ /// representation of a compound element. If there is a parse
+ /// error, an exception of the type isc::data::JSONError is thrown.
+
+ //@{
+ /// Creates an Element from the given JSON string
+ /// @param in The string to parse the element from
+ /// @param preproc specified whether preprocessing (e.g. comment removal)
+ /// should be performed
+ /// @return An ElementPtr that contains the element(s) specified
+ /// in the given string.
+ static ElementPtr fromJSON(const std::string& in, bool preproc = false);
+
+ /// Creates an Element from the given input stream containing JSON
+ /// formatted data.
+ ///
+ /// @param in The string to parse the element from
+ /// @param preproc specified whether preprocessing (e.g. comment removal)
+ /// should be performed
+ /// @throw JSONError
+ /// @return An ElementPtr that contains the element(s) specified
+ /// in the given input stream.
+ static ElementPtr fromJSON(std::istream& in, bool preproc = false);
+
+ /// Creates an Element from the given input stream containing JSON
+ /// formatted data.
+ ///
+ /// @param in The string to parse the element from
+ /// @param file_name specified input file name (used in error reporting)
+ /// @param preproc specified whether preprocessing (e.g. comment removal)
+ /// should be performed
+ /// @throw JSONError
+ /// @return An ElementPtr that contains the element(s) specified
+ /// in the given input stream.
+ /// @throw JSONError
+ static ElementPtr fromJSON(std::istream& in, const std::string& file_name,
+ bool preproc = false);
+
+ /// Creates an Element from the given input stream, where we keep
+ /// track of the location in the stream for error reporting.
+ ///
+ /// @param in The string to parse the element from.
+ /// @param file The input file name.
+ /// @param line A reference to the int where the function keeps
+ /// track of the current line.
+ /// @param pos A reference to the int where the function keeps
+ /// track of the current position within the current line.
+ /// @throw JSONError
+ /// @return An ElementPtr that contains the element(s) specified
+ /// in the given input stream.
+ // make this one private?
+ /// @throw JSONError
+ static ElementPtr fromJSON(std::istream& in, const std::string& file,
+ int& line, int &pos);
+
+ /// Reads contents of specified file and interprets it as JSON.
+ ///
+ /// @param file_name name of the file to read
+ /// @param preproc specified whether preprocessing (e.g. comment removal)
+ /// should be performed
+ /// @return An ElementPtr that contains the element(s) specified
+ /// if the given file.
+ static ElementPtr fromJSONFile(const std::string& file_name,
+ bool preproc = false);
+ //@}
+
+ /// @name Type name conversion functions
+
+ /// Returns the name of the given type as a string
+ ///
+ /// @param type The type to return the name of
+ /// @return The name of the type, or "unknown" if the type
+ /// is not known.
+ static std::string typeToName(Element::types type);
+
+ /// Converts the string to the corresponding type
+ /// Throws a TypeError if the name is unknown.
+ ///
+ /// @param type_name The name to get the type of
+ /// @return the corresponding type value
+ static Element::types nameToType(const std::string& type_name);
+
+ /// @brief input text preprocessor
+ ///
+ /// This method performs preprocessing of the input stream (which is
+ /// expected to contain a text version of to be parsed JSON). For now the
+ /// sole supported operation is bash-style (line starting with #) comment
+ /// removal, but it will be extended later to cover more cases (C, C++ style
+ /// comments, file inclusions, maybe macro replacements?).
+ ///
+ /// This method processes the whole input stream. It reads all contents of
+ /// the input stream, filters the content and returns the result in a
+ /// different stream.
+ ///
+ /// @param in input stream to be preprocessed
+ /// @param out output stream (filtered content will be written here)
+ static void preprocess(std::istream& in, std::stringstream& out);
+
+ /// @name Wire format factory functions
+
+ /// These function pparse the wireformat at the given stringstream
+ /// (of the given length). If there is a parse error an exception
+ /// of the type isc::cc::DecodeError is raised.
+
+ //@{
+ /// Creates an Element from the wire format in the given
+ /// stringstream of the given length.
+ /// Since the wire format is JSON, this is the same as
+ /// fromJSON, and could be removed.
+ ///
+ /// @param in The input stringstream.
+ /// @param length The length of the wireformat data in the stream
+ /// @return ElementPtr with the data that is parsed.
+ static ElementPtr fromWire(std::stringstream& in, int length);
+
+ /// Creates an Element from the wire format in the given string
+ /// Since the wire format is JSON, this is the same as
+ /// fromJSON, and could be removed.
+ ///
+ /// @param s The input string
+ /// @return ElementPtr with the data that is parsed.
+ static ElementPtr fromWire(const std::string& s);
+ //@}
+
+ /// @brief Remove all empty maps and lists from this Element and its
+ /// descendants.
+ void removeEmptyContainersRecursively() {
+ if (type_ == list || type_ == map) {
+ size_t s(size());
+ for (size_t i = 0; i < s; ++i) {
+ // Get child.
+ ElementPtr child;
+ if (type_ == list) {
+ child = getNonConst(i);
+ } else if (type_ == map) {
+ std::string const key(get(i)->stringValue());
+ // The ElementPtr - ConstElementPtr disparity between
+ // ListElement and MapElement is forcing a const cast here.
+ // It's undefined behavior to modify it after const casting.
+ // The options are limited. I've tried templating, moving
+ // this function from a member function to free-standing and
+ // taking the Element template as argument. I've tried
+ // making it a virtual function with overridden
+ // implementations in ListElement and MapElement. Nothing
+ // works.
+ child = boost::const_pointer_cast<Element>(get(key));
+ }
+
+ // Makes no sense to continue for non-container children.
+ if (child->getType() != list && child->getType() != map) {
+ continue;
+ }
+
+ // Recurse if not empty.
+ if (!child->empty()){
+ child->removeEmptyContainersRecursively();
+ }
+
+ // When returning from recursion, remove if empty.
+ if (child->empty()) {
+ remove(i);
+ --i;
+ --s;
+ }
+ }
+ }
+ }
+};
+
+/// Notes: IntElement type is changed to int64_t.
+/// Due to C++ problems on overloading and automatic type conversion,
+/// (C++ tries to convert integer type values and reference/pointer
+/// if value types do not match exactly)
+/// We decided the storage as int64_t,
+/// three (long long, long, int) override function definitions
+/// and cast int/long/long long to int64_t via long long.
+/// Therefore, call by value methods (create, setValue) have three
+/// (int,long,long long) definitions. Others use int64_t.
+///
+class IntElement : public Element {
+ int64_t i;
+public:
+ IntElement(int64_t v, const Position& pos = ZERO_POSITION())
+ : Element(integer, pos), i(v) { }
+ int64_t intValue() const { return (i); }
+ using Element::getValue;
+ bool getValue(int64_t& t) const { t = i; return (true); }
+ using Element::setValue;
+ bool setValue(long long int v) { i = v; return (true); }
+ void toJSON(std::ostream& ss) const;
+ bool equals(const Element& other) const;
+};
+
+/// @brief Wrapper over int128_t
+class BigIntElement : public Element {
+ using int128_t = isc::util::int128_t;
+ using Element::getValue;
+ using Element::setValue;
+
+public:
+ /// @brief Constructor
+ BigIntElement(const int128_t& v, const Position& pos = ZERO_POSITION())
+ : Element(bigint, pos), i_(v) {
+ }
+
+ /// @brief Retrieve the underlying big integer value.
+ ///
+ /// @return the underlying value
+ int128_t bigIntValue() const override {
+ return (i_);
+ }
+
+ /// @brief Sets the underlying big integer value.
+ ///
+ /// @return true for no reason
+ bool setValue(const int128_t& v) override {
+ i_ = v;
+ return (true);
+ }
+
+ /// @brief Converts the Element to JSON format and appends it to the given
+ /// stringstream.
+ void toJSON(std::ostream& ss) const override;
+
+ /// @brief Checks whether the other Element is equal.
+ ///
+ /// @return true if the other ElementPtr has the same value and the same
+ /// type (or a different and compatible type), false otherwise.
+ bool equals(const Element& other) const override;
+
+private:
+ /// @brief the underlying stored value
+ int128_t i_;
+};
+
+class DoubleElement : public Element {
+ double d;
+
+public:
+ DoubleElement(double v, const Position& pos = ZERO_POSITION())
+ : Element(real, pos), d(v) {};
+ double doubleValue() const { return (d); }
+ using Element::getValue;
+ bool getValue(double& t) const { t = d; return (true); }
+ using Element::setValue;
+ bool setValue(const double v) { d = v; return (true); }
+ void toJSON(std::ostream& ss) const;
+ bool equals(const Element& other) const;
+};
+
+class BoolElement : public Element {
+ bool b;
+
+public:
+ BoolElement(const bool v, const Position& pos = ZERO_POSITION())
+ : Element(boolean, pos), b(v) {};
+ bool boolValue() const { return (b); }
+ using Element::getValue;
+ bool getValue(bool& t) const { t = b; return (true); }
+ using Element::setValue;
+ bool setValue(const bool v) { b = v; return (true); }
+ void toJSON(std::ostream& ss) const;
+ bool equals(const Element& other) const;
+};
+
+class NullElement : public Element {
+public:
+ NullElement(const Position& pos = ZERO_POSITION())
+ : Element(null, pos) {};
+ void toJSON(std::ostream& ss) const;
+ bool equals(const Element& other) const;
+};
+
+class StringElement : public Element {
+ std::string s;
+
+public:
+ StringElement(std::string v, const Position& pos = ZERO_POSITION())
+ : Element(string, pos), s(v) {};
+ std::string stringValue() const { return (s); }
+ using Element::getValue;
+ bool getValue(std::string& t) const { t = s; return (true); }
+ using Element::setValue;
+ bool setValue(const std::string& v) { s = v; return (true); }
+ void toJSON(std::ostream& ss) const;
+ bool equals(const Element& other) const;
+};
+
+class ListElement : public Element {
+ std::vector<ElementPtr> l;
+
+public:
+ ListElement(const Position& pos = ZERO_POSITION())
+ : Element(list, pos) {}
+ const std::vector<ElementPtr>& listValue() const { return (l); }
+ using Element::getValue;
+ bool getValue(std::vector<ElementPtr>& t) const {
+ t = l;
+ return (true);
+ }
+ using Element::setValue;
+ bool setValue(const std::vector<ElementPtr>& v) {
+ l = v;
+ return (true);
+ }
+ using Element::get;
+ ConstElementPtr get(int i) const { return (l.at(i)); }
+ ElementPtr getNonConst(int i) const { return (l.at(i)); }
+ using Element::set;
+ void set(size_t i, ElementPtr e) {
+ l.at(i) = e;
+ }
+ void add(ElementPtr e) { l.push_back(e); };
+ using Element::remove;
+ void remove(int i) { l.erase(l.begin() + i); };
+ void toJSON(std::ostream& ss) const;
+ size_t size() const { return (l.size()); }
+ bool empty() const { return (l.empty()); }
+ bool equals(const Element& other) const;
+
+ /// @brief Sorts the elements inside the list.
+ ///
+ /// The list must contain elements of the same type.
+ /// Call with the key by which you want to sort when the list contains maps.
+ /// Nested lists are not supported.
+ /// Call without a parameter when sorting any other type.
+ ///
+ /// @param index the key by which you want to sort when the list contains
+ /// maps
+ void sort(std::string const& index = std::string());
+};
+
+class MapElement : public Element {
+ std::map<std::string, ConstElementPtr> m;
+
+public:
+ MapElement(const Position& pos = ZERO_POSITION()) : Element(map, pos) {}
+ // @todo should we have direct iterators instead of exposing the std::map
+ // here?
+ const std::map<std::string, ConstElementPtr>& mapValue() const override {
+ return (m);
+ }
+ using Element::getValue;
+ bool getValue(std::map<std::string, ConstElementPtr>& t) const override {
+ t = m;
+ return (true);
+ }
+ using Element::setValue;
+ bool setValue(const std::map<std::string, ConstElementPtr>& v) override {
+ m = v;
+ return (true);
+ }
+ using Element::get;
+ ConstElementPtr get(const std::string& s) const override {
+ auto found = m.find(s);
+ return (found != m.end() ? found->second : ConstElementPtr());
+ }
+
+ /// @brief Get the i-th element in the map.
+ ///
+ /// Useful when required to iterate with an index.
+ ///
+ /// @param i the position of the element you want to return
+ /// @return the element at position i
+ ConstElementPtr get(int const i) const override {
+ auto it(m.begin());
+ std::advance(it, i);
+ return create(it->first);
+ }
+
+ using Element::set;
+ void set(const std::string& key, ConstElementPtr value) override;
+ using Element::remove;
+ void remove(const std::string& s) override { m.erase(s); }
+
+ /// @brief Remove the i-th element from the map.
+ ///
+ /// @param i the position of the element you want to remove
+ void remove(int const i) override {
+ auto it(m.begin());
+ std::advance(it, i);
+ m.erase(it);
+ }
+
+ bool contains(const std::string& s) const override {
+ return (m.find(s) != m.end());
+ }
+ void toJSON(std::ostream& ss) const override;
+
+ // we should name the two finds better...
+ // find the element at id; raises TypeError if one of the
+ // elements at path except the one we're looking for is not a
+ // mapelement.
+ // returns an empty element if the item could not be found
+ ConstElementPtr find(const std::string& id) const override;
+
+ // find the Element at 'id', and store the element pointer in t
+ // returns true if found, or false if not found (either because
+ // it doesn't exist or one of the elements in the path is not
+ // a MapElement)
+ bool find(const std::string& id, ConstElementPtr& t) const override;
+
+ /// @brief Returns number of stored elements
+ ///
+ /// @return number of elements.
+ size_t size() const override {
+ return (m.size());
+ }
+
+ bool equals(const Element& other) const override;
+
+ bool empty() const override { return (m.empty()); }
+};
+
+/// Checks whether the given ElementPtr is a NULL pointer
+/// @param p The ElementPtr to check
+/// @return true if it is NULL, false if not.
+bool isNull(ConstElementPtr p);
+
+///
+/// @brief Remove all values from the first ElementPtr that are
+/// equal in the second. Both ElementPtrs MUST be MapElements
+/// The use for this function is to end up with a MapElement that
+/// only contains new and changed values (for ModuleCCSession and
+/// configuration update handlers)
+/// Raises a TypeError if a or b are not MapElements
+void removeIdentical(ElementPtr a, ConstElementPtr b);
+
+/// @brief Create a new ElementPtr from the first ElementPtr, removing all
+/// values that are equal in the second. Both ElementPtrs MUST be MapElements.
+/// The returned ElementPtr will be a MapElement that only contains new and
+/// changed values (for ModuleCCSession and configuration update handlers).
+/// Raises a TypeError if a or b are not MapElements
+ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b);
+
+/// @brief Merges the data from other into element. (on the first level). Both
+/// elements must be MapElements. Every string, value pair in other is copied
+/// into element (the ElementPtr of value is copied, this is not a new object)
+/// Unless the value is a NullElement, in which case the key is removed from
+/// element, rather than setting the value to the given NullElement.
+/// This way, we can remove values from for instance maps with configuration
+/// data (which would then result in reverting back to the default).
+/// Raises a TypeError if either ElementPtr is not a MapElement
+void merge(ElementPtr element, ConstElementPtr other);
+
+/// @brief Function used to check if two MapElements refer to the same
+/// configuration data. It can check if the two MapElements have the same or
+/// have equivalent value for some members.
+/// e.g.
+/// (
+/// left->get("prefix")->stringValue() == right->get("prefix")->stringValue() &&
+/// left->get("prefix-len")->intValue() == right->get("prefix-len")->intValue() &&
+/// left->get("delegated-len")->intValue() == right->get("delegated-len")->intValue()
+/// )
+typedef std::function<bool (ElementPtr&, ElementPtr&)> MatchTestFunc;
+
+/// @brief Function used to check if the data provided for the element contains
+/// only information used for identification, or it contains extra useful data.
+typedef std::function<bool (ElementPtr&)> NoDataTestFunc;
+
+/// @brief Function used to check if the key is used for identification
+typedef std::function<bool (const std::string&)> IsKeyTestFunc;
+
+/// @brief Structure holding the test functions used to traverse the element
+/// hierarchy.
+struct HierarchyTraversalTest {
+ MatchTestFunc match_;
+ NoDataTestFunc no_data_;
+ IsKeyTestFunc is_key_;
+};
+
+/// @brief Mapping between a container name and functions used to match elements
+/// inside the container.
+typedef std::map<std::string, HierarchyTraversalTest> FunctionMap;
+
+/// @brief Hierarchy descriptor of the containers in a specific Element
+/// hierarchy tree. The position inside the vector indicates the level at which
+/// the respective containers are located.
+///
+/// e.g.
+/// {
+/// { { "pools", { ... , ... } }, { "pd-pools", { ... , ... } }, { "option-data", { ... , ... } } },
+/// { { "option-data", { ... , ... } } }
+/// }
+/// At first subnet level the 'pools', 'pd-pools' and 'option-data' containers
+/// can be found.
+/// At second subnet level the 'option-data' container can be found
+/// (obviously only inside 'pools' and 'pd-pools' containers).
+typedef std::vector<FunctionMap> HierarchyDescriptor;
+
+/// @brief Merges the diff data by adding the missing elements from 'other'
+/// to 'element' (recursively). Both elements must be the same Element type.
+/// Raises a TypeError if elements are not the same Element type.
+/// @note
+/// for non map and list elements the values are updated with the new values
+/// for maps:
+/// - non map and list elements are added/updated with the new values
+/// - list and map elements are processed recursively
+/// for lists:
+/// - regardless of the element type, all elements from 'other' are added to
+/// 'element'
+///
+/// @param element The element to which new data is added.
+/// @param other The element containing the data which needs to be added.
+/// @param hierarchy The hierarchy describing the elements relations and
+/// identification keys.
+/// @param key The container holding the current element.
+/// @param idx The level inside the hierarchy the current element is located.
+void mergeDiffAdd(ElementPtr& element, ElementPtr& other,
+ HierarchyDescriptor& hierarchy, std::string key,
+ size_t idx = 0);
+
+/// @brief Merges the diff data by removing the data present in 'other' from
+/// 'element' (recursively). Both elements must be the same Element type.
+/// Raises a TypeError if elements are not the same Element type.
+/// for non map and list elements the values are set to NullElement
+/// for maps:
+/// - non map and list elements are removed from the map
+/// - list and map elements are processed recursively
+/// for lists:
+/// - regardless of the element type, all elements from 'other' matching
+/// elements in 'element' are removed
+///
+/// @param element The element from which new data is removed.
+/// @param other The element containing the data which needs to be removed.
+/// @param hierarchy The hierarchy describing the elements relations and
+/// identification keys.
+/// @param key The container holding the current element.
+/// @param idx The level inside the hierarchy the current element is located.
+void mergeDiffDel(ElementPtr& element, ElementPtr& other,
+ HierarchyDescriptor& hierarchy, std::string key,
+ size_t idx = 0);
+
+/// @brief Extends data by adding the specified 'extension' elements from
+/// 'other' inside the 'container' element (recursively). Both elements must be
+/// the same Element type.
+/// Raises a TypeError if elements are not the same Element type.
+///
+/// @param container The container holding the data that must be extended.
+/// @param extension The name of the element that contains the data that must be
+/// added (if not already present) in order to extend the initial data.
+/// @param element The element from which new data is added.
+/// @param other The element containing the data which needs to be added.
+/// @param hierarchy The hierarchy describing the elements relations and
+/// identification keys.
+/// @param key The container holding the current element.
+/// @param idx The level inside the hierarchy the current element is located.
+/// @param alter The flag which indicates if the current element should be
+/// updated.
+void extend(const std::string& container, const std::string& extension,
+ ElementPtr& element, ElementPtr& other,
+ HierarchyDescriptor& hierarchy, std::string key, size_t idx = 0,
+ bool alter = false);
+
+/// @brief Copy the data up to a nesting level.
+///
+/// The copy is a deep copy so nothing is shared if it is not
+/// under the given nesting level.
+///
+/// @param from the pointer to the element to copy
+/// @param level nesting level (default is 100, 0 means shallow copy,
+/// negative means outbound and perhaps looping forever).
+/// @return a pointer to a fresh copy
+/// @throw raises a BadValue is a null pointer occurs.
+ElementPtr copy(ConstElementPtr from, int level = 100);
+
+/// @brief Compares the data with other using unordered lists
+///
+/// This comparison function handles lists (JSON arrays) as
+/// unordered multi sets (multi means an item can occurs more
+/// than once as soon as it occurs the same number of times).
+bool isEquivalent(ConstElementPtr a, ConstElementPtr b);
+
+/// @brief Pretty prints the data into stream.
+///
+/// This operator converts the @c ConstElementPtr into a string and
+/// inserts it into the output stream @c out with an initial
+/// indentation @c indent and add at each level @c step spaces.
+/// For maps if there is a comment property it is printed first.
+///
+/// @param element A @c ConstElementPtr to pretty print
+/// @param out A @c std::ostream on which the print operation is performed
+/// @param indent An initial number of spaces to add each new line
+/// @param step A number of spaces to add to indentation at a new level
+void prettyPrint(ConstElementPtr element, std::ostream& out,
+ unsigned indent = 0, unsigned step = 2);
+
+/// @brief Pretty prints the data into string
+///
+/// This operator converts the @c ConstElementPtr into a string with
+/// an initial indentation @c indent and add at each level @c step spaces.
+/// For maps if there is a comment property it is printed first.
+///
+/// @param element A @c ConstElementPtr to pretty print
+/// @param indent An initial number of spaces to add each new line
+/// @param step A number of spaces to add to indentation at a new level
+/// @return a string where element was pretty printed
+std::string prettyPrint(ConstElementPtr element,
+ unsigned indent = 0, unsigned step = 2);
+
+/// @brief Insert Element::Position as a string into stream.
+///
+/// This operator converts the @c Element::Position into a string and
+/// inserts it into the output stream @c out.
+///
+/// @param out A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param pos The @c Element::Position structure to insert.
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c out after the insertion operation.
+std::ostream& operator<<(std::ostream& out, const Element::Position& pos);
+
+/// @brief Insert the Element as a string into stream.
+///
+/// This method converts the @c ElementPtr into a string with
+/// @c Element::str() and inserts it into the
+/// output stream @c out.
+///
+/// This function overloads the global operator<< to behave as described in
+/// ostream::operator<< but applied to @c ElementPtr objects.
+///
+/// @param out A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param e The @c ElementPtr object to insert.
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c out after the insertion operation.
+std::ostream& operator<<(std::ostream& out, const Element& e);
+
+bool operator==(const Element& a, const Element& b);
+bool operator!=(const Element& a, const Element& b);
+bool operator<(const Element& a, const Element& b);
+
+} // namespace data
+} // namespace isc
+
+#endif // ISC_DATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/cc/dhcp_config_error.h b/src/lib/cc/dhcp_config_error.h
new file mode 100644
index 0000000..1337dd8
--- /dev/null
+++ b/src/lib/cc/dhcp_config_error.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2017 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 DHCP_CONFIG_ERROR_H
+#define DHCP_CONFIG_ERROR_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+
+/// @brief Evaluation error exception raised when trying to parse.
+///
+/// This exception is expected to be thrown when parsing of the input
+/// configuration has failed. This exception is used by parsers.
+class ParseError : public isc::Exception {
+ public:
+ ParseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// An exception that is thrown if an error occurs while configuring
+/// any server.
+/// By convention when this exception is thrown there is a position
+/// between parentheses so the code style should be like this:
+///
+/// try {
+/// ...
+/// } catch (const ConfigError&) {
+/// throw;
+/// } catch (const std::exception& ex) {
+/// isc_throw(ConfigError, "message" << ex.what()
+/// << " (" << getPosition(what) << ")");
+/// }
+
+/// @todo: move this header into simple_parser.h
+/// @todo: create an isc_throw like macro to add the
+/// position more easily.
+/// @todo: replace all references to DhcpConfigError with ConfigError,
+/// then remove DhcpConfigError.
+class ConfigError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ ConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+namespace dhcp {
+
+/// @brief To be removed. Please use ConfigError instead.
+class DhcpConfigError : public isc::Exception {
+public:
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ DhcpConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP_CONFIG_ERROR_H
diff --git a/src/lib/cc/element_value.h b/src/lib/cc/element_value.h
new file mode 100644
index 0000000..ff48561
--- /dev/null
+++ b/src/lib/cc/element_value.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2019 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 ELEMENT_VALUE_H
+#define ELEMENT_VALUE_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <string>
+
+namespace isc {
+namespace data {
+
+/// @brief Template class for converting a value encapsulated in the
+/// @c Element object into a simple type.
+///
+/// The @c Element object provides a set of accessors to retrieve
+/// values of different types it encapsulates. These methods
+/// however can't be always used in template methods and classes.
+///
+/// Consider a template function which returns a value of a type
+/// specified as template argument. In order to convert a value
+/// held in the @c Element object it would have to conditionally
+/// call this object's accessors to return the value of the
+/// appropriate type. This would however fail to compile because
+/// the compiler would check for all possible value types returned
+/// by the @c Element accessors and report an error for those that
+/// don't cast to the returned type.
+///
+/// This class provides a mechanism to extract the value of the
+/// appropriate type from the @c Element object within the
+/// template function. It comes with a number of class specializations
+/// for various data types to be returned. The default implementation
+/// calls @c Element::intValue and casts it to the returned type.
+/// There are class specializations for @c double, @c bool and
+/// @c string.
+///
+/// @tparam T Type of the value to be extracted.
+template<typename T>
+class ElementValue {
+public:
+
+ /// @brief Function operator extracting an @c Element value as
+ /// integer.
+ ///
+ /// @param el Element holding a value to be extracted.
+ T operator()(ConstElementPtr el) const {
+ return (static_cast<T>(el->intValue()));
+ }
+};
+
+/// @brief The @c ElementValue specialization for double.
+template<>
+class ElementValue<double> {
+public:
+
+ /// @brief Function operator extracting an @c Element value as
+ /// double.
+ ///
+ /// @param el Element holding a value to be extracted.
+ double operator()(ConstElementPtr el) const {
+ return (el->doubleValue());
+ }
+};
+
+/// @brief The @c ElementValue specialization for boolean.
+template<>
+class ElementValue<bool> {
+public:
+
+ /// @brief Function operator extracting an @c Element value as
+ /// boolean.
+ ///
+ /// @param el Element holding a value to be extracted.
+ bool operator()(ConstElementPtr el) const {
+ return (el->boolValue());
+ }
+
+};
+
+/// @brief The @c ElementValue specialization for string.
+template<>
+class ElementValue<std::string> {
+public:
+
+ /// @brief Function operator extracting an @c Element value as
+ /// string.
+ ///
+ /// @param el Element holding a value to be extracted.
+ std::string operator()(ConstElementPtr el) const {
+ return (el->stringValue());
+ }
+};
+
+/// @brief The @c ElementValue specialization for IOAddress.
+template<>
+class ElementValue<asiolink::IOAddress> {
+public:
+
+ /// @brief Function operator extracting an @c Element value as
+ /// IOAddress.
+ ///
+ /// @note This does NOT support empty string value.
+ ///
+ /// @param el Element holding a value to be extracted.
+ asiolink::IOAddress operator()(ConstElementPtr el) const {
+ return (asiolink::IOAddress(el->stringValue()));
+ }
+};
+
+} // end of namespace isc::data
+} // end of namespace isc
+
+#endif // ELEMENT_VALUE_H
diff --git a/src/lib/cc/json_feed.cc b/src/lib/cc/json_feed.cc
new file mode 100644
index 0000000..7cd1e31
--- /dev/null
+++ b/src/lib/cc/json_feed.cc
@@ -0,0 +1,563 @@
+// Copyright (C) 2017-2021 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 <cc/data.h>
+#include <cc/json_feed.h>
+#include <functional>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace config {
+
+const int JSONFeed::RECEIVE_START_ST;
+const int JSONFeed::WHITESPACE_BEFORE_JSON_ST;
+const int JSONFeed::EOL_COMMENT_BEFORE_JSON_ST;
+const int JSONFeed::START_COMMENT_BEFORE_JSON_ST;
+const int JSONFeed::C_COMMENT_BEFORE_JSON_ST;
+const int JSONFeed::STOP_COMMENT_BEFORE_JSON_ST;
+const int JSONFeed::JSON_START_ST;
+const int JSONFeed::INNER_JSON_ST;
+const int JSONFeed::STRING_JSON_ST;
+const int JSONFeed::ESCAPE_JSON_ST;
+const int JSONFeed::EOL_COMMENT_ST;
+const int JSONFeed::START_COMMENT_ST;
+const int JSONFeed::C_COMMENT_ST;
+const int JSONFeed::STOP_COMMENT_ST;
+const int JSONFeed::JSON_END_ST;
+const int JSONFeed::FEED_OK_ST;
+const int JSONFeed::FEED_FAILED_ST;
+
+const int JSONFeed::DATA_READ_OK_EVT;
+const int JSONFeed::NEED_MORE_DATA_EVT;
+const int JSONFeed::MORE_DATA_PROVIDED_EVT;
+const int JSONFeed::FEED_OK_EVT;
+const int JSONFeed::FEED_FAILED_EVT;
+
+JSONFeed::JSONFeed()
+ : StateModel(), buffer_(), data_ptr_(0), error_message_(), open_scopes_(0),
+ output_() {
+}
+
+void
+JSONFeed::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+JSONFeed::poll() {
+ try {
+ // Process the input data until no more data is available or until
+ // JSON feed ends with matching closing brace.
+ do {
+ getState(getCurrState())->run();
+
+ } while (!isModelDone() && (getNextEvent() != NOP_EVT) &&
+ (getNextEvent() != NEED_MORE_DATA_EVT));
+ } catch (const std::exception& ex) {
+ abortModel(ex.what());
+ }
+}
+
+bool
+JSONFeed::needData() const {
+ return ((getNextEvent() == NEED_MORE_DATA_EVT) ||
+ (getNextEvent() == START_EVT));
+}
+
+bool
+JSONFeed::feedOk() const {
+ return ((getNextEvent() == END_EVT) &&
+ (getLastEvent() == FEED_OK_EVT));
+}
+
+ElementPtr
+JSONFeed::toElement() const {
+ if (needData()) {
+ isc_throw(JSONFeedError, "unable to retrieve the data form the"
+ " JSON feed while parsing hasn't finished");
+ }
+ try {
+ return (Element::fromWire(output_));
+
+ } catch (const std::exception& ex) {
+ isc_throw(JSONFeedError, ex.what());
+ }
+}
+
+void
+JSONFeed::postBuffer(const void* buf, const size_t buf_size) {
+ if (buf_size > 0) {
+ // The next event is NEED_MORE_DATA_EVT when the parser wants to
+ // signal that more data is needed. This method is called to supply
+ // more data and thus it should change the next event to
+ // MORE_DATA_PROVIDED_EVT.
+ if (getNextEvent() == NEED_MORE_DATA_EVT) {
+ transition(getCurrState(), MORE_DATA_PROVIDED_EVT);
+ }
+ buffer_.assign(static_cast<const char*>(buf),
+ static_cast<const char*>(buf) + buf_size);
+ data_ptr_ = 0;
+ }
+}
+
+void
+JSONFeed::defineEvents() {
+ StateModel::defineEvents();
+
+ // Define JSONFeed specific events.
+ defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT");
+ defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT");
+ defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT");
+ defineEvent(FEED_OK_EVT, "FEED_OK_EVT");
+ defineEvent(FEED_FAILED_EVT, "FEED_FAILED_EVT");
+}
+
+void
+JSONFeed::verifyEvents() {
+ StateModel::verifyEvents();
+
+ getEvent(DATA_READ_OK_EVT);
+ getEvent(NEED_MORE_DATA_EVT);
+ getEvent(MORE_DATA_PROVIDED_EVT);
+ getEvent(FEED_OK_EVT);
+ getEvent(FEED_FAILED_EVT);
+}
+
+void
+JSONFeed::defineStates() {
+ // Call parent class implementation first.
+ StateModel::defineStates();
+
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ std::bind(&JSONFeed::receiveStartHandler, this));
+ defineState(WHITESPACE_BEFORE_JSON_ST, "WHITESPACE_BEFORE_JSON_ST",
+ std::bind(&JSONFeed::whiteSpaceBeforeJSONHandler, this));
+ defineState(EOL_COMMENT_BEFORE_JSON_ST, "EOL_COMMENT_BEFORE_JSON_ST",
+ std::bind(&JSONFeed::eolCommentBeforeJSONHandler, this));
+ defineState(START_COMMENT_BEFORE_JSON_ST, "START_COMMENT_BEFORE_JSON_ST",
+ std::bind(&JSONFeed::startCommentBeforeJSONHandler, this));
+ defineState(C_COMMENT_BEFORE_JSON_ST, "C_COMMENT_BEFORE_JSON_ST",
+ std::bind(&JSONFeed::cCommentBeforeJSONHandler, this));
+ defineState(STOP_COMMENT_BEFORE_JSON_ST, "STOP_COMMENT_BEFORE_JSON_ST",
+ std::bind(&JSONFeed::stopCommentBeforeJSONHandler, this));
+ defineState(INNER_JSON_ST, "INNER_JSON_ST",
+ std::bind(&JSONFeed::innerJSONHandler, this));
+ defineState(STRING_JSON_ST, "STRING_JSON_ST",
+ std::bind(&JSONFeed::stringJSONHandler, this));
+ defineState(ESCAPE_JSON_ST, "ESCAPE_JSON_ST",
+ std::bind(&JSONFeed::escapeJSONHandler, this));
+ defineState(EOL_COMMENT_ST, "EOL_COMMENT_ST",
+ std::bind(&JSONFeed::eolCommentHandler, this));
+ defineState(START_COMMENT_ST, "START_COMMENT_ST",
+ std::bind(&JSONFeed::startCommentHandler, this));
+ defineState(C_COMMENT_ST, "C_COMMENT_ST",
+ std::bind(&JSONFeed::cCommentHandler, this));
+ defineState(STOP_COMMENT_ST, "STOP_COMMENT_ST",
+ std::bind(&JSONFeed::stopCommentHandler, this));
+ defineState(JSON_END_ST, "JSON_END_ST",
+ std::bind(&JSONFeed::endJSONHandler, this));
+}
+
+void
+JSONFeed::feedFailure(const std::string& error_msg) {
+ error_message_ = error_msg;
+ transition(FEED_FAILED_ST, FEED_FAILED_EVT);
+}
+
+void
+JSONFeed::onModelFailure(const std::string& explanation) {
+ if (error_message_.empty()) {
+ error_message_ = explanation;
+ }
+}
+
+bool
+JSONFeed::popNextFromBuffer(char& next) {
+ // If there are any characters in the buffer, pop next.
+ if (!buffer_.empty() && (data_ptr_ < buffer_.size())) {
+ next = buffer_[data_ptr_++];
+ return (true);
+ }
+ return (false);
+}
+
+char
+JSONFeed::getNextFromBuffer() {
+ unsigned int ev = getNextEvent();
+ char c = '\0';
+ // The caller should always provide additional data when the
+ // NEED_MORE_DATA_EVT occurs. If the next event is still
+ // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided
+ // the data.
+ if (ev == NEED_MORE_DATA_EVT) {
+ isc_throw(JSONFeedError,
+ "JSONFeed requires new data to progress, but no data"
+ " have been provided. The transaction is aborted to avoid"
+ " a deadlock.");
+
+ } else {
+ // Try to pop next character from the buffer.
+ const bool data_exist = popNextFromBuffer(c);
+ if (!data_exist) {
+ // There is no more data so it is really not possible that we're
+ // at MORE_DATA_PROVIDED_EVT.
+ if (ev == MORE_DATA_PROVIDED_EVT) {
+ isc_throw(JSONFeedError,
+ "JSONFeed state indicates that new data have been"
+ " provided to be parsed, but the transaction buffer"
+ " contains no new data.");
+
+ } else {
+ // If there is no more data we should set NEED_MORE_DATA_EVT
+ // event to indicate that new data should be provided.
+ transition(getCurrState(), NEED_MORE_DATA_EVT);
+ }
+ }
+ }
+ return (c);
+}
+
+void
+JSONFeed::invalidEventError(const std::string& handler_name,
+ const unsigned int event) {
+ isc_throw(JSONFeedError, handler_name << ": invalid event "
+ << getEventLabel(static_cast<int>(event)));
+}
+
+void
+JSONFeed::receiveStartHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (getNextEvent()) {
+ case START_EVT:
+ switch (c) {
+ case '\t':
+ case '\n':
+ case '\r':
+ case ' ':
+ transition(WHITESPACE_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ case '#':
+ transition(EOL_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ case '/':
+ transition(START_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ case '{':
+ case '[':
+ output_.push_back(c);
+ ++open_scopes_;
+ transition(INNER_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ // Cannot start by a string
+ case '"':
+ default:
+ feedFailure("invalid first character " + std::string(1, c));
+ break;
+ }
+ break;
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+JSONFeed::whiteSpaceBeforeJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '\t':
+ case '\n':
+ case '\r':
+ case ' ':
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ break;
+
+ case '#':
+ transition(EOL_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ case '/':
+ transition(START_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ return;
+
+ case '{':
+ case '[':
+ output_.push_back(c);
+ ++open_scopes_;
+ transition(INNER_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ // Cannot start by a string
+ case '"':
+ default:
+ feedFailure("invalid character " + std::string(1, c));
+ }
+ }
+}
+
+void
+JSONFeed::eolCommentBeforeJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '\n':
+ transition(WHITESPACE_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ postNextEvent(DATA_READ_OK_EVT);
+ break;
+ }
+ }
+}
+
+void
+JSONFeed::startCommentBeforeJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '/':
+ transition(EOL_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '*':
+ transition(C_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ feedFailure("invalid characters /" + std::string(1, c));
+ }
+ }
+}
+
+void
+JSONFeed::cCommentBeforeJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '*':
+ transition(STOP_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ postNextEvent(DATA_READ_OK_EVT);
+ break;
+ }
+ }
+}
+
+void
+JSONFeed::stopCommentBeforeJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '/':
+ transition(WHITESPACE_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '*':
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ break;
+
+ default:
+ transition(C_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT);
+ break;
+ }
+ }
+}
+
+void
+JSONFeed::innerJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(c) {
+ case '{':
+ case '[':
+ output_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ ++open_scopes_;
+ break;
+
+ case '}':
+ case ']':
+ output_.push_back(c);
+ if (--open_scopes_ == 0) {
+ transition(JSON_END_ST, FEED_OK_EVT);
+
+ } else {
+ postNextEvent(DATA_READ_OK_EVT);
+ }
+ break;
+
+ case '"':
+ output_.push_back(c);
+ transition(STRING_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '#':
+ transition(EOL_COMMENT_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '/':
+ transition(START_COMMENT_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ output_.push_back(c);
+ postNextEvent(DATA_READ_OK_EVT);
+ }
+ }
+}
+
+void
+JSONFeed::stringJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ output_.push_back(c);
+
+ switch(c) {
+ case '"':
+ transition(INNER_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '\\':
+ transition(ESCAPE_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ }
+}
+
+void
+JSONFeed::escapeJSONHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ output_.push_back(c);
+
+ transition(STRING_JSON_ST, DATA_READ_OK_EVT);
+ }
+}
+
+void
+JSONFeed::eolCommentHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '\n':
+ output_.push_back(c);
+ transition(INNER_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ postNextEvent(DATA_READ_OK_EVT);
+ break;
+ }
+ }
+}
+
+void
+JSONFeed::startCommentHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '/':
+ transition(EOL_COMMENT_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '*':
+ transition(C_COMMENT_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ feedFailure("invalid characters /" + std::string(1, c));
+ }
+ }
+}
+
+void
+JSONFeed::cCommentHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '*':
+ transition(STOP_COMMENT_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '\n':
+ output_.push_back(c);
+ postNextEvent(DATA_READ_OK_EVT);
+ break;
+
+ default:
+ postNextEvent(DATA_READ_OK_EVT);
+ break;
+ }
+ }
+}
+
+void
+JSONFeed::stopCommentHandler() {
+ char c = getNextFromBuffer();
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch (c) {
+ case '/':
+ transition(INNER_JSON_ST, DATA_READ_OK_EVT);
+ break;
+
+ case '*':
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ break;
+
+ case '\n':
+ output_.push_back(c);
+ transition(C_COMMENT_ST, DATA_READ_OK_EVT);
+ break;
+
+ default:
+ transition(C_COMMENT_ST, DATA_READ_OK_EVT);
+ break;
+ }
+ }
+}
+
+void
+JSONFeed::endJSONHandler() {
+ switch (getNextEvent()) {
+ case FEED_OK_EVT:
+ transition(END_ST, END_EVT);
+ break;
+
+ case FEED_FAILED_EVT:
+ abortModel("reading into JSON feed failed");
+ break;
+
+ default:
+ invalidEventError("endJSONHandler", getNextEvent());
+ }
+}
+
+} // end of namespace config
+} // end of namespace isc
diff --git a/src/lib/cc/json_feed.h b/src/lib/cc/json_feed.h
new file mode 100644
index 0000000..abef6b0
--- /dev/null
+++ b/src/lib/cc/json_feed.h
@@ -0,0 +1,351 @@
+// Copyright (C) 2017-2021 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 JSON_FEED_H
+#define JSON_FEED_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <util/state_model.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace config {
+
+class JSONFeed;
+
+/// @brief Pointer to the @ref JSONFeed.
+typedef boost::shared_ptr<JSONFeed> JSONFeedPtr;
+
+/// @brief Pointer to the const @ref JSONFeed.
+typedef boost::shared_ptr<const JSONFeed> ConstJSONFeedPtr;
+
+/// @brief A generic exception thrown upon an error in the @ref JSONFeed.
+class JSONFeedError : public Exception {
+public:
+ JSONFeedError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief State model for asynchronous read of data in JSON format.
+///
+/// Kea control channel uses stream sockets for forwarding commands received
+/// by the Kea Control Agent to respective Kea services. The responses may
+/// contain large amounts of data (e.g. lease queries may return thousands
+/// of leases). Such responses rarely fit into a single data buffer and
+/// require multiple calls to receive/read or asynchronous receive/read.
+///
+/// A receiver performing multiple reads from a socket must be able to
+/// locate the boundaries of the command within the data stream. The
+/// @ref JSONFeed state model solves this problem.
+///
+/// When the partial data is read from the stream socket it should be provided
+/// to the @ref JSONFeed using @ref JSONFeed::postBuffer and then the
+/// @ref JSONFeed::poll should be called to start processing the received
+/// data. The actual JSON structure can be preceded by whitespaces. When first
+/// occurrence of one of the '{' or '[' characters is found in the stream it is
+/// considered a beginning of the JSON structure. The model includes an internal
+/// counter of new '{' and '[' occurrences. The counter increases one of these
+/// characters is found. When any of the '}' or ']' is found, the counter
+/// is decreased. When the counter is decreased to 0 it indicates that the
+/// entire JSON structure has been received and processed.
+///
+/// As '{', '}', '[' and ']' can be embedded in JSON strings two states
+/// for strings and escape in strings are required. Note the processing
+/// of escapes is greatly simplified compared to ECMA 404 figure 5.
+///
+/// Added support for '#' to end of line (bash), '//' to end of line (C++)
+/// and '/*' to '*/' (C) comments both before JSON and inside JSON.
+
+/// Note that this mechanism doesn't check if the JSON structure is well
+/// formed. It merely detects the end of the JSON structure if this structure
+/// is well formed. The structure is validated when @ref JSONFeed::toElement
+/// is called to retrieve the data structures encapsulated with
+/// @ref isc::data::Element objects.
+class JSONFeed : public util::StateModel {
+public:
+
+ /// @name States supported by the @ref JSONFeed
+ ///
+ //@{
+
+ /// @brief State indicating a beginning of a feed.
+ static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1;
+
+ /// @brief Skipping whitespaces before actual JSON.
+ static const int WHITESPACE_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 2;
+
+ /// @brief Skipping an end-of-line comment before actual JSON.
+ static const int EOL_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 3;
+
+ /// @brief Starting one of the comments beginning with a slash before actual JSON.
+ static const int START_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 4;
+
+ /// @brief Skipping a C style comment before actual JSON.
+ static const int C_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 5;
+
+ /// @brief Stopping a C style comment before actual JSON.
+ static const int STOP_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 6;
+
+ /// @brief Found first opening brace or square bracket.
+ static const int JSON_START_ST = SM_DERIVED_STATE_MIN + 7;
+
+ /// @brief Parsing JSON.
+ static const int INNER_JSON_ST = SM_DERIVED_STATE_MIN + 8;
+
+ /// @brief Parsing JSON string.
+ static const int STRING_JSON_ST = SM_DERIVED_STATE_MIN + 9;
+
+ /// @brief JSON escape next character.
+ static const int ESCAPE_JSON_ST = SM_DERIVED_STATE_MIN + 10;
+
+ /// @brief Skipping an end-of-line comment.
+ static const int EOL_COMMENT_ST = SM_DERIVED_STATE_MIN + 11;
+
+ /// @brief Starting one of the comments beginning with a slash.
+ static const int START_COMMENT_ST = SM_DERIVED_STATE_MIN + 12;
+
+ /// @brief Skipping a C style comment.
+ static const int C_COMMENT_ST = SM_DERIVED_STATE_MIN + 13;
+
+ /// @brief Stopping a C style comment.
+ static const int STOP_COMMENT_ST = SM_DERIVED_STATE_MIN + 14;
+
+ /// @brief Found last closing brace or square bracket.
+ static const int JSON_END_ST = SM_DERIVED_STATE_MIN + 15;
+
+ /// @brief Found opening and closing brace or square bracket.
+ ///
+ /// This doesn't however indicate that the JSON is well formed. It
+ /// only means that matching closing brace or square bracket was
+ /// found.
+ static const int FEED_OK_ST = SM_DERIVED_STATE_MIN + 100;
+
+ /// @brief Invalid syntax detected.
+ ///
+ /// For example, non matching braces or invalid characters found.
+ static const int FEED_FAILED_ST = SM_DERIVED_STATE_MIN + 101;
+
+ //@}
+
+
+ /// @name Events used during data processing.
+ ///
+ //@{
+
+ /// @brief Chunk of data successfully read and parsed.
+ static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Unable to proceed with parsing until new data is provided.
+ static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ /// @brief New data provided and parsing should continue.
+ static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ /// @brief Found opening brace and the matching closing brace.
+ static const int FEED_OK_EVT = SM_DERIVED_EVENT_MIN + 100;
+
+ /// @brief Invalid syntax detected.
+ static const int FEED_FAILED_EVT = SM_DERIVED_EVENT_MIN + 101;
+
+ //@}
+
+ /// @brief Constructor.
+ JSONFeed();
+
+ /// @brief Initializes state model.
+ ///
+ /// Initializes events and states. It sets the model to @c RECEIVE_START_ST
+ /// and the next event to @c START_EVT.
+ void initModel();
+
+ /// @brief Runs the model as long as data is available.
+ ///
+ /// It processes the input data character by character until it reaches the
+ /// end of the input buffer, in which case it returns. The next event is set
+ /// to @c NEED_MORE_DATA_EVT to indicate the need for providing additional
+ /// data using @ref JSONFeed::postBuffer. This function also returns when
+ /// the end of the JSON structure has been detected or when an error has
+ /// occurred.
+ void poll();
+
+ /// @brief Checks if the model needs additional data to continue.
+ ///
+ /// The caller can use this method to check if the model expects additional
+ /// data to be provided to finish processing input data.
+ ///
+ /// @return true if more data is needed, false otherwise.
+ bool needData() const;
+
+ /// @brief Checks if the data have been successfully processed.
+ bool feedOk() const;
+
+ /// @brief Returns error string when data processing has failed.
+ std::string getErrorMessage() const {
+ return (error_message_);
+ }
+
+ /// @brief Returns the text parsed into the buffer.
+ std::string getProcessedText() const {
+ return (output_);
+ }
+
+ /// @brief Returns processed data as a structure of @ref isc::data::Element
+ /// objects.
+ ///
+ /// @throw JSONFeedError if the received JSON is not well formed.
+ data::ElementPtr toElement() const;
+
+ /// @brief Receives additional data read from a data stream.
+ ///
+ /// A caller invokes this method to pass additional chunk of data received
+ /// from the stream.
+ ///
+ /// @param buf Pointer to a buffer holding additional input data.
+ /// @param buf_size Size of the data in the input buffer.
+ void postBuffer(const void* buf, const size_t buf_size);
+
+
+private:
+
+ /// @brief Make @ref runModel private to make sure that the caller uses
+ /// @ref poll method instead.
+ using StateModel::runModel;
+
+ /// @brief Define events used by the feed.
+ virtual void defineEvents();
+
+ /// @brief Verifies events used by the feed.
+ virtual void verifyEvents();
+
+ /// @brief Defines states of the feed.
+ virtual void defineStates();
+
+ /// @brief Transition to failure state.
+ ///
+ /// This method transitions the model to @ref FEED_FAILED_ST and
+ /// sets next event to FEED_FAILED_EVT.
+ ///
+ /// @param error_msg Error message explaining the failure.
+ void feedFailure(const std::string& error_msg);
+
+ /// @brief A method called when state model fails.
+ ///
+ /// @param explanation Error message explaining the reason for failure.
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Retrieves next byte of data from the buffer.
+ ///
+ /// During normal operation, when there is no more data in the buffer,
+ /// the NEED_MORE_DATA_EVT is set as next event to signal the need for
+ /// calling @ref JSONFeed::postBuffer.
+ ///
+ /// @throw JSONFeedError If current event is already set to
+ /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it
+ /// indicates that the caller failed to provide new data using
+ /// @ref JSONFeed::postBuffer. The latter case is highly unlikely
+ /// as it indicates that no new data were provided but the state of the
+ /// parser was changed from NEED_MORE_DATA_EVT or the data were provided
+ /// but the data buffer is empty. In both cases, it is a programming
+ /// error.
+ char getNextFromBuffer();
+
+ /// @brief This method is called when invalid event occurred in a particular
+ /// state.
+ ///
+ /// This method simply throws @ref JSONFeedError informing about invalid
+ /// event occurring for the particular state. The error message includes
+ /// the name of the handler in which the exception has been thrown.
+ /// It also includes the event which caused the exception.
+ ///
+ /// @param handler_name Name of the handler in which the exception is
+ /// thrown.
+ /// @param event An event which caused the exception.
+ ///
+ /// @throw JSONFeedError.
+ void invalidEventError(const std::string& handler_name,
+ const unsigned int event);
+
+ /// @brief Tries to read next byte from buffer.
+ ///
+ /// @param [out] next A reference to the variable where read data should be
+ /// stored.
+ ///
+ /// @return true if character was successfully read, false otherwise.
+ bool popNextFromBuffer(char& next);
+
+ /// @name State handlers.
+ ///
+ //@{
+
+ /// @brief Handler for RECEIVE_START_ST.
+ void receiveStartHandler();
+
+ /// @brief Handler for WHITESPACE_BEFORE_JSON_ST.
+ void whiteSpaceBeforeJSONHandler();
+
+ /// @brief Handler for EOL_COMMENT_BEFORE_JSON_ST.
+ void eolCommentBeforeJSONHandler();
+
+ /// @brief Handler for START_COMMENT_BEFORE_JSON_ST.
+ void startCommentBeforeJSONHandler();
+
+ /// @brief Handler for C_COMMENT_BEFORE_JSON_ST.
+ void cCommentBeforeJSONHandler();
+
+ /// @brief Handler for STOP_COMMENT_BEFORE_JSON_ST.
+ void stopCommentBeforeJSONHandler();
+
+ /// @brief Handler for the FIRST_BRACE_ST.
+ void innerJSONHandler();
+
+ /// @brief Handler for the STRING_JSON_ST.
+ void stringJSONHandler();
+
+ /// @brief Handler for the ESCAPE_JSON_ST;
+ void escapeJSONHandler();
+
+ /// @brief Handler for EOL_COMMENT_ST.
+ void eolCommentHandler();
+
+ /// @brief Handler for START_COMMENT_ST.
+ void startCommentHandler();
+
+ /// @brief Handler for C_COMMENT_ST.
+ void cCommentHandler();
+
+ /// @brief Handler for STOP_COMMENT_ST.
+ void stopCommentHandler();
+
+ /// @brief Handler for the JSON_END_ST.
+ void endJSONHandler();
+
+ //@}
+
+ /// @brief Internal buffer from which the feed reads data.
+ std::vector<char> buffer_;
+
+ /// @brief Holds pointer to the next byte in the buffer to be read.
+ size_t data_ptr_;
+
+ /// @brief Error message set by @ref onModelFailure.
+ std::string error_message_;
+
+ /// @brief A counter increased when '{' or '[' is found and decreased when
+ /// '}' or ']' is found in the stream.
+ uint64_t open_scopes_;
+
+ /// @brief Holds processed data.
+ std::string output_;
+};
+
+} // end of namespace config
+} // end of namespace isc
+
+#endif // JSON_FEED_H
diff --git a/src/lib/cc/server_tag.cc b/src/lib/cc/server_tag.cc
new file mode 100644
index 0000000..80e5184
--- /dev/null
+++ b/src/lib/cc/server_tag.cc
@@ -0,0 +1,53 @@
+// Copyright (C) 2019-2020 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 <cc/server_tag.h>
+#include <exceptions/exceptions.h>
+#include <util/strutil.h>
+#include <boost/algorithm/string.hpp>
+
+namespace isc {
+namespace data {
+
+std::string ServerTag::ALL = "all";
+
+ServerTag::ServerTag()
+ : tag_(ALL) {
+}
+
+ServerTag::ServerTag(const std::string& tag)
+ : tag_(util::str::trim(tag)) {
+ if (tag_.empty()) {
+ isc_throw(BadValue, "server-tag must not be empty");
+
+ } else if (tag_.length() > 256) {
+ isc_throw(BadValue, "server-tag must not be longer than 256 characters");
+ }
+
+ boost::algorithm::to_lower(tag_);
+
+ // ANY has a defined meaning for server selector and must not be used as
+ // a server tag.
+ if (tag_ == "any") {
+ isc_throw(BadValue, "'any' is reserved and must not be used as a server-tag");
+ }
+}
+
+bool
+ServerTag::amAll() const {
+ return (tag_ == ALL);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const ServerTag& server_tag) {
+ os << server_tag.get();
+ return (os);
+}
+
+} // end of namespace isc::data
+} // end of namespace isc
diff --git a/src/lib/cc/server_tag.h b/src/lib/cc/server_tag.h
new file mode 100644
index 0000000..6f80015
--- /dev/null
+++ b/src/lib/cc/server_tag.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2019 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 SERVER_TAG_H
+#define SERVER_TAG_H
+
+#include <string>
+
+namespace isc {
+namespace data {
+
+/// @brief Represents a server tag.
+///
+/// The server tag is a label identifying a server or all servers which
+/// configuration is stored in the database. The label "all" is reserved
+/// and it means "all servers". A configuration object in the database
+/// associated with this server tag belongs to all servers. The server
+/// tag must not be empty, must not be longer than 256 characters
+/// (excluding leading and terminating whitespaces, which are trimmed)
+/// and must not be set to "any" which has a special meaning for the
+/// server selector.
+class ServerTag {
+public:
+
+ /// @brief Server tag for all servers as text.
+ static std::string ALL;
+
+ /// @brief Default constructor.
+ ///
+ /// Creates server tag for all servers.
+ ServerTag();
+
+ /// @brief Constructor.
+ ///
+ /// @param tag server tag provided as string. The tag is converted to
+ /// lower case.
+ explicit ServerTag(const std::string& tag);
+
+ /// @brief Checks if the server tag is set to "all servers".
+ ///
+ /// @return true if the server tag is set to all servers, false
+ /// otherwise.
+ bool amAll() const;
+
+ /// @brief Returns server tag as string.
+ ///
+ /// @return lower case server tag.
+ std::string get() const {
+ return (tag_);
+ }
+
+ /// @brief Overload of the less operator for using @c ServerTag in sets.
+ ///
+ /// @param other other server tag to compare to.
+ /// @return true if this server tag is less than the other server tag.
+ bool operator<(const ServerTag& other) const {
+ return (tag_ < other.tag_);
+ }
+
+private:
+
+ /// @brief Holds server tag as string.
+ std::string tag_;
+};
+
+/// @brief Insert the @c ServerTag as a string into stream.
+///
+/// @param os stream to insert server tag into.
+/// @param server_tag server tag to be converted to text and
+/// inserted into a stream.
+/// @return Reference to the stream object with inserted server
+/// tag.
+std::ostream&
+operator<<(std::ostream& os, const ServerTag& server_tag);
+
+} // end of namespace isc::data
+} // end of namespace isc
+
+#endif // SERVER_TAG_H
diff --git a/src/lib/cc/simple_parser.cc b/src/lib/cc/simple_parser.cc
new file mode 100644
index 0000000..8140d13
--- /dev/null
+++ b/src/lib/cc/simple_parser.cc
@@ -0,0 +1,369 @@
+// Copyright (C) 2016-2022 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 <cc/simple_parser.h>
+#include <asiolink/io_address.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <cc/data.h>
+#include <string>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using isc::dhcp::DhcpConfigError;
+
+namespace isc {
+namespace data {
+
+void
+SimpleParser::checkRequired(const SimpleRequiredKeywords& required,
+ ConstElementPtr scope) {
+ for (auto name : required) {
+ if (scope->contains(name)) {
+ continue;
+ }
+ isc_throw(DhcpConfigError, "missing '" << name << "' parameter");
+ }
+}
+
+void
+SimpleParser::checkKeywords(const SimpleKeywords& keywords,
+ ConstElementPtr scope) {
+ string spurious;
+ for (auto entry : scope->mapValue()) {
+ if (keywords.count(entry.first) == 0) {
+ if (spurious.empty()) {
+ spurious = entry.first;
+ }
+ continue;
+ }
+ Element::types expected = keywords.at(entry.first);
+ if ((expected == Element::any) ||
+ (entry.second->getType() == expected)) {
+ continue;
+ }
+ isc_throw(DhcpConfigError, "'" << entry.first << "' parameter is not "
+ << (expected == Element::integer ? "an " : "a ")
+ << Element::typeToName(expected));
+ }
+ if (!spurious.empty()) {
+ isc_throw(DhcpConfigError, "spurious '" << spurious << "' parameter");
+ }
+}
+
+std::string
+SimpleParser::getString(ConstElementPtr scope, const std::string& name) {
+ ConstElementPtr x = scope->get(name);
+ if (!x) {
+ isc_throw(DhcpConfigError,
+ "missing parameter '" << name << "' ("
+ << scope->getPosition() << ")");
+ }
+ if (x->getType() != Element::string) {
+ isc_throw(DhcpConfigError,
+ "invalid type specified for parameter '" << name
+ << "' (" << x->getPosition() << ")");
+ }
+
+ return (x->stringValue());
+}
+
+int64_t
+SimpleParser::getInteger(ConstElementPtr scope, const std::string& name) {
+ ConstElementPtr x = scope->get(name);
+ if (!x) {
+ isc_throw(DhcpConfigError,
+ "missing parameter '" << name << "' ("
+ << scope->getPosition() << ")");
+ }
+ if (x->getType() != Element::integer) {
+ isc_throw(DhcpConfigError,
+ "invalid type specified for parameter '" << name
+ << "' (" << x->getPosition() << ")");
+ }
+
+ return (x->intValue());
+}
+
+int64_t
+SimpleParser::getInteger(isc::data::ConstElementPtr scope, const std::string& name,
+ int64_t min, int64_t max) {
+ int64_t tmp = getInteger(scope, name);
+ if (tmp < min || tmp > max) {
+ isc_throw(OutOfRange,
+ "The '" << name << "' value (" << tmp
+ << ") is not within expected range: (" << min << " - " << max
+ << ")");
+ }
+
+ return (tmp);
+}
+
+bool
+SimpleParser::getBoolean(ConstElementPtr scope, const std::string& name) {
+ ConstElementPtr x = scope->get(name);
+ if (!x) {
+ isc_throw(DhcpConfigError,
+ "missing parameter '" << name << "' ("
+ << scope->getPosition() << ")");
+ }
+ if (x->getType() != Element::boolean) {
+ isc_throw(DhcpConfigError,
+ "invalid type specified for parameter '" << name
+ << "' (" << x->getPosition() << ")");
+ }
+
+ return (x->boolValue());
+}
+
+IOAddress
+SimpleParser::getAddress(const ConstElementPtr& scope,
+ const std::string& name) {
+ std::string str = getString(scope, name);
+ try {
+ return (IOAddress(str));
+ } catch (const std::exception& e) {
+ isc_throw(DhcpConfigError, "Failed to convert '" << str
+ << "' to address: " << e.what() << "("
+ << getPosition(name, scope) << ")");
+ }
+}
+
+double
+SimpleParser::getDouble(const ConstElementPtr& scope,
+ const std::string& name) {
+ ConstElementPtr x = scope->get(name);
+ if (!x) {
+ isc_throw(DhcpConfigError,
+ "missing parameter '" << name << "' ("
+ << scope->getPosition() << ")");
+ }
+
+ if (x->getType() != Element::real) {
+ isc_throw(DhcpConfigError,
+ "invalid type specified for parameter '" << name
+ << "' (" << x->getPosition() << ")");
+ }
+
+ return (x->doubleValue());
+}
+
+
+const data::Element::Position&
+SimpleParser::getPosition(const std::string& name, const data::ConstElementPtr parent) {
+ if (!parent) {
+ return (data::Element::ZERO_POSITION());
+ }
+ ConstElementPtr elem = parent->get(name);
+ if (!elem) {
+ return (parent->getPosition());
+ }
+
+ return (elem->getPosition());
+}
+
+size_t SimpleParser::setDefaults(ElementPtr scope,
+ const SimpleDefaults& default_values) {
+ size_t cnt = 0;
+
+ // This is the position representing a default value. As the values
+ // we're inserting here are not present in whatever the config file
+ // came from, we need to make sure it's clearly labeled as default.
+ const Element::Position pos("<default-value>", 0, 0);
+
+ // Let's go over all parameters we have defaults for.
+ BOOST_FOREACH(SimpleDefault def_value, default_values) {
+
+ // Try if such a parameter is there. If it is, let's
+ // skip it, because user knows best *cough*.
+ ConstElementPtr x = scope->get(string(def_value.name_));
+ if (x) {
+ // There is such a value already, skip it.
+ continue;
+ }
+
+ // There isn't such a value defined, let's create the default
+ // value...
+ switch (def_value.type_) {
+ case Element::string: {
+ x.reset(new StringElement(def_value.value_, pos));
+ break;
+ }
+ case Element::integer: {
+ try {
+ int int_value = boost::lexical_cast<int>(def_value.value_);
+ x.reset(new IntElement(int_value, pos));
+ }
+ catch (const std::exception& ex) {
+ isc_throw(BadValue, "Internal error. Integer value expected for: "
+ << def_value.name_ << ", value is: "
+ << def_value.value_ );
+ }
+
+ break;
+ }
+ case Element::boolean: {
+ bool bool_value;
+ if (def_value.value_ == string("true")) {
+ bool_value = true;
+ } else if (def_value.value_ == string("false")) {
+ bool_value = false;
+ } else {
+ isc_throw(DhcpConfigError,
+ "Internal error. Boolean value specified as "
+ << def_value.value_ << ", expected true or false");
+ }
+ x.reset(new BoolElement(bool_value, pos));
+ break;
+ }
+ case Element::real: {
+ double dbl_value = boost::lexical_cast<double>(def_value.value_);
+ x.reset(new DoubleElement(dbl_value, pos));
+ break;
+ }
+ default:
+ // No default values for null, list or map
+ isc_throw(DhcpConfigError,
+ "Internal error. Incorrect default value type.");
+ }
+
+ // ... and insert it into the provided Element tree.
+ scope->set(def_value.name_, x);
+ ++cnt;
+ }
+
+ return (cnt);
+}
+
+size_t
+SimpleParser::setListDefaults(ConstElementPtr list,
+ const SimpleDefaults& default_values) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ElementPtr entry, list->listValue()) {
+ cnt += setDefaults(entry, default_values);
+ }
+ return (cnt);
+}
+
+size_t
+SimpleParser::deriveParams(ConstElementPtr parent,
+ ElementPtr child,
+ const ParamsList& params) {
+ if ( (parent->getType() != Element::map) ||
+ (child->getType() != Element::map)) {
+ return (0);
+ }
+
+ size_t cnt = 0;
+ BOOST_FOREACH(string param, params) {
+ ConstElementPtr x = parent->get(param);
+ if (!x) {
+ // Parent doesn't define this parameter, so there's
+ // nothing to derive from
+ continue;
+ }
+
+ if (child->get(param)) {
+ // Child defines this parameter already. There's
+ // nothing to do here.
+ continue;
+ }
+
+ // Copy the parameters to the child scope.
+ child->set(param, x);
+ cnt++;
+ }
+
+ return (cnt);
+}
+
+const util::Triplet<uint32_t>
+SimpleParser::parseIntTriplet(const ConstElementPtr& scope,
+ const std::string& name) {
+ // Initialize as some compilers complain otherwise.
+ uint32_t value = 0;
+ bool has_value = false;
+ uint32_t min_value = 0;
+ bool has_min = false;
+ uint32_t max_value = 0;
+ bool has_max = false;
+ if (scope->contains(name)) {
+ value = getInteger(scope, name);
+ has_value = true;
+ }
+ if (scope->contains("min-" + name)) {
+ min_value = getInteger(scope, "min-" + name);
+ has_min = true;
+ }
+ if (scope->contains("max-" + name)) {
+ max_value = getInteger(scope, "max-" + name);
+ has_max = true;
+ }
+ if (!has_value && !has_min && !has_max) {
+ return (util::Triplet<uint32_t>());
+ }
+ if (has_value) {
+ if (!has_min && !has_max) {
+ // default only.
+ min_value = value;
+ max_value = value;
+ } else if (!has_min) {
+ // default and max.
+ min_value = value;
+ } else if (!has_max) {
+ // default and min.
+ max_value = value;
+ }
+ } else if (has_min) {
+ // min only.
+ if (!has_max) {
+ value = min_value;
+ max_value = min_value;
+ } else {
+ // min and max.
+ isc_throw(DhcpConfigError, "have min-" << name << " and max-"
+ << name << " but no " << name << " (default) in "
+ << scope->getPosition());
+ }
+ } else {
+ // max only.
+ min_value = max_value;
+ value = max_value;
+ }
+ // Check that min <= max.
+ if (min_value > max_value) {
+ if (has_min && has_max) {
+ isc_throw(DhcpConfigError, "the value of min-" << name << " ("
+ << min_value << ") is not less than max-" << name << " ("
+ << max_value << ")");
+ } else if (has_min) {
+ // Only min and default so min > default.
+ isc_throw(DhcpConfigError, "the value of min-" << name << " ("
+ << min_value << ") is not less than (default) " << name
+ << " (" << value << ")");
+ } else {
+ // Only default and max so default > max.
+ isc_throw(DhcpConfigError, "the value of (default) " << name
+ << " (" << value << ") is not less than max-" << name
+ << " (" << max_value << ")");
+ }
+ }
+ // Check that value is between min and max.
+ if ((value < min_value) || (value > max_value)) {
+ isc_throw(DhcpConfigError, "the value of (default) " << name << " ("
+ << value << ") is not between min-" << name << " ("
+ << min_value << ") and max-" << name << " ("
+ << max_value << ")");
+ }
+
+ return (util::Triplet<uint32_t>(min_value, value, max_value));
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/cc/simple_parser.h b/src/lib/cc/simple_parser.h
new file mode 100644
index 0000000..cb78802
--- /dev/null
+++ b/src/lib/cc/simple_parser.h
@@ -0,0 +1,339 @@
+// Copyright (C) 2016-2022 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 SIMPLE_PARSER_H
+#define SIMPLE_PARSER_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <util/triplet.h>
+#include <map>
+#include <vector>
+#include <string>
+#include <stdint.h>
+#include <limits>
+
+namespace isc {
+namespace data {
+
+/// This array defines a single entry of default values.
+struct SimpleDefault {
+ SimpleDefault(const char* name, isc::data::Element::types type, const char* value)
+ :name_(name), type_(type), value_(value) {}
+ std::string name_;
+ const isc::data::Element::types type_;
+ const char* value_;
+};
+
+/// This specifies all required keywords.
+typedef std::vector<std::string> SimpleRequiredKeywords;
+
+/// This specifies all accepted keywords with their types.
+typedef std::map<std::string, isc::data::Element::types> SimpleKeywords;
+
+/// This specifies all default values in a given scope (e.g. a subnet).
+typedef std::vector<SimpleDefault> SimpleDefaults;
+
+/// This defines a list of all parameters that are derived (or inherited) between
+/// contexts.
+typedef std::vector<std::string> ParamsList;
+
+
+/// @brief A simple parser
+///
+/// This class is intended to be a simpler replacement for DhcpConfigParser.
+/// This class has been initially created to facilitate DHCPv4 and
+/// DHCPv6 servers' configuration parsing. Thus examples provided
+/// herein are related to DHCP configuration. Nevertheless, this is a
+/// generic class to be used in other modules too.
+///
+/// The simplification comes from several factors:
+/// - no build/commit nonsense. There's a single step:
+/// CfgStorage parse(ConstElementPtr json)
+/// that converts JSON configuration into an object and returns it.
+/// - no state kept. This greatly simplifies the parsers (no contexts, no child
+/// parsers list, no separate storage for uint32, strings etc. In fact,
+/// this base class is purely static. However, some derived classes may store
+/// some state. Implementors are advised to store as little state as possible.
+/// - no optional parameters (all are mandatory). This simplifies the parser,
+/// but introduces a new step before parsing where we insert the default
+/// values into client configuration before parsing. This is actually a good
+/// thing, because we now have a clear picture of the default parameters as
+/// they're defined in a single place (the DhcpConfigParser had the defaults
+/// spread out in multiple files in multiple directories).
+class SimpleParser {
+public:
+
+ /// @brief Checks that all required keywords are present.
+ ///
+ /// This method throws an exception when a required
+ /// entry is not present in the given scope.
+ ///
+ /// @param required Required keywords.
+ /// @param scope Specified parameters which are checked.
+ /// @throw DhcpConfigError if a required parameter is not present.
+ static void checkRequired(const SimpleRequiredKeywords& required,
+ isc::data::ConstElementPtr scope);
+
+ /// @brief Checks acceptable keywords with their expected type.
+ ///
+ /// This methods throws an exception when a not acceptable keyword
+ /// is found or when an acceptable entry does not have the expected type.
+ ///
+ /// @param keywords The @c SimpleKeywords keywords and types map.
+ /// @param scope Specified parameters which are checked.
+ /// @throw DhcpConfigError if a not acceptable keyword is found.
+ /// @throw DhcpConfigError if an acceptable entry does not have
+ /// the expected type.
+ static void checkKeywords(const SimpleKeywords& keywords,
+ isc::data::ConstElementPtr scope);
+
+ /// @brief Derives (inherits) parameters from parent scope to a child
+ ///
+ /// This method derives parameters from the parent scope to the child,
+ /// if there are no values specified in the child scope. For example,
+ /// this method can be used to derive timers from global scope (e.g. for
+ /// the whole DHCPv6 server) to a subnet scope. This method checks
+ /// if the child scope doesn't have more specific values defined. If
+ /// it doesn't, then the value from parent scope is copied over.
+ ///
+ /// @param parent scope to copy from (e.g. global)
+ /// @param child scope to copy from (e.g. subnet)
+ /// @param params names of the parameters to copy
+ /// @return number of parameters copied
+ static size_t deriveParams(isc::data::ConstElementPtr parent,
+ isc::data::ElementPtr child,
+ const ParamsList& params);
+
+ /// @brief Sets the default values
+ ///
+ /// This method sets the default values for parameters that are not
+ /// defined. The list of default values is specified by default_values.
+ /// If not present, those will be inserted into the scope. If
+ /// a parameter is already present, the default value will not
+ /// be inserted.
+ ///
+ /// @param scope default values will be inserted here
+ /// @param default_values list of default values
+ /// @return number of parameters inserted
+ static size_t setDefaults(isc::data::ElementPtr scope,
+ const SimpleDefaults& default_values);
+
+ /// @brief Sets the default values for all entries in a list
+ ///
+ /// This is a simple utility method that iterates over all
+ /// parameters in a list and calls setDefaults for each
+ /// entry.
+ ///
+ /// @param list list to be iterated over
+ /// @param default_values list of default values
+ /// @return number of parameters inserted
+ static size_t setListDefaults(isc::data::ConstElementPtr list,
+ const SimpleDefaults& default_values);
+
+ /// @brief Utility method that returns position of an element
+ ///
+ /// It's mostly useful for logging. If the element is missing
+ /// the parent position is returned or ZERO_POSITION if parent
+ /// is null.
+ ///
+ /// @param name position of that element will be returned
+ /// @param parent parent element (optional)
+ /// @return position of the element specified.
+ static const data::Element::Position&
+ getPosition(const std::string& name, const data::ConstElementPtr parent);
+
+ /// @brief Returns a string parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a string value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type
+ static std::string getString(isc::data::ConstElementPtr scope,
+ const std::string& name);
+
+ /// @brief Returns an integer parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an integer value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type
+ static int64_t getInteger(isc::data::ConstElementPtr scope,
+ const std::string& name);
+
+ /// @brief Returns an integer parameter from a scope and checks its range
+ ///
+ /// Unconditionally returns a parameter. Checks that the value specified
+ /// is in min =< X =< max range.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @param min minimum allowed value
+ /// @param max maximum allowed value
+ /// @return an integer value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type.
+ /// @throw OutOfRange if the parameter is out of range
+ static int64_t getInteger(isc::data::ConstElementPtr scope,
+ const std::string& name,
+ int64_t min, int64_t max);
+
+ /// @brief Returns a boolean parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a boolean value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type
+ static bool getBoolean(isc::data::ConstElementPtr scope,
+ const std::string& name);
+
+
+ /// @brief Returns a IOAddress parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an IOAddress representing the value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not of
+ /// appropriate type (or its conversion to IOAddress fails due to not
+ /// being a proper address).
+ static isc::asiolink::IOAddress
+ getAddress(const ConstElementPtr& scope, const std::string& name);
+
+ /// @brief Returns a floating point parameter from a scope
+ ///
+ /// Unconditionally returns a parameter.
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a double value of the parameter
+ /// @throw DhcpConfigError if the parameter is not there or is not
+ /// an Element::real
+ static double getDouble(const ConstElementPtr& scope,
+ const std::string& name);
+
+protected:
+
+ /// @brief Returns an integer value with range checking from a scope
+ ///
+ /// This template should be instantiated in parsers when useful
+ ///
+ /// @tparam int_type the integer type e.g. uint32_t
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter for error report
+ /// @return a value of int_type
+ /// @throw DhcpConfigError if the parameter is not there, is not of
+ /// appropriate type or is out of type value range
+ template <typename int_type> int_type
+ getIntType(isc::data::ConstElementPtr scope,
+ const std::string& name) {
+ int64_t val_int = getInteger(scope, name);
+ if ((val_int < std::numeric_limits<int_type>::min()) ||
+ (val_int > std::numeric_limits<int_type>::max())) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "out of range value (" << val_int
+ << ") specified for parameter '" << name
+ << "' (" << getPosition(name, scope) << ")");
+ }
+ return (static_cast<int_type>(val_int));
+ }
+
+ /// @brief Returns a converted value from a scope
+ ///
+ /// This template should be instantiated in parsers when useful
+ ///
+ /// @tparam target_type the type of the result
+ /// @tparam convert the conversion function std::string -> target_type
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter for error report
+ /// @param type_name name of target_type for error report
+ /// @return a converted value of target_type
+ /// @throw DhcpConfigError if the parameter is not there, is not of
+ /// appropriate type or can not be converted
+ template <typename target_type,
+ target_type convert(const std::string&)> target_type
+ getAndConvert(isc::data::ConstElementPtr scope,
+ const std::string& name,
+ const std::string& type_name) {
+ std::string str = getString(scope, name);
+ try {
+ return (convert(str));
+ } catch (const std::exception&) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "invalid " << type_name << " (" << str
+ << ") specified for parameter '" << name
+ << "' (" << getPosition(name, scope) << ")");
+ }
+ }
+
+public:
+ /// @brief Returns a value converted to uint32_t
+ ///
+ /// Instantiation of getIntType() to uint32_t
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an uint32_t value
+ /// @throw isc::dhcp::DhcpConfigError when it is not an uint32_t
+ uint32_t getUint32(isc::data::ConstElementPtr scope,
+ const std::string& name) {
+ return (getIntType<uint32_t>(scope, name));
+ }
+
+ /// @brief Returns a value converted to uint16_t
+ ///
+ /// Instantiation of getIntType() to uint16_t
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return an uint16_t value
+ /// @throw isc::dhcp::DhcpConfigError when it is not an uint16_t
+ uint16_t getUint16(isc::data::ConstElementPtr scope,
+ const std::string& name) {
+ return (getIntType<uint16_t>(scope, name));
+ }
+
+ /// @brief Get an uint8_t value
+ ///
+ /// Instantiation of getIntType() to uint8_t
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return uint8_t value
+ /// @throw isc::dhcp::DhcpConfigError when it is not an uint8_t
+ uint8_t getUint8(ConstElementPtr scope, const std::string& name) {
+ return (getIntType<uint8_t>(scope, name));
+ }
+
+ /// @brief Parses an integer triplet
+ ///
+ /// Parses an integer triplet parameter of the form:
+ ///
+ /// min-{name}, {name}, max-{name}
+ ///
+ /// @param scope Data element holding e.g. shared network configuration
+ /// to be parsed.
+ /// @param name Base name of the parameter.
+ /// @return A triplet with the parsed value.
+ const isc::util::Triplet<uint32_t> parseIntTriplet(const data::ConstElementPtr& scope,
+ const std::string& name);
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/cc/stamped_element.cc b/src/lib/cc/stamped_element.cc
new file mode 100644
index 0000000..3c53b38
--- /dev/null
+++ b/src/lib/cc/stamped_element.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2018-2020 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 <cc/stamped_element.h>
+
+namespace isc {
+namespace data {
+
+StampedElement::StampedElement()
+ : BaseStampedElement(), server_tags_() {
+}
+
+bool
+StampedElement::hasServerTag(const ServerTag& server_tag) const {
+ return (server_tags_.count(server_tag) > 0);
+}
+
+void
+StampedElement::delServerTag(const std::string& server_tag) {
+ if (!server_tags_.erase(ServerTag(server_tag))) {
+ isc_throw(NotFound, "can't find server tag '" << server_tag << "' to delete");
+ }
+}
+
+bool
+StampedElement::hasAllServerTag() const {
+ return (hasServerTag(ServerTag(ServerTag::ALL)));
+}
+
+
+ElementPtr
+StampedElement::getMetadata() const {
+ ElementPtr metadata = Element::createMap();
+ ElementPtr tags = Element::createList();
+
+ for (auto server_tag : server_tags_) {
+ tags->add(Element::create(server_tag.get()));
+ }
+
+ metadata->set("server-tags", tags);
+ return (metadata);
+}
+
+} // end of namespace isc::data
+} // end of namespace isc
diff --git a/src/lib/cc/stamped_element.h b/src/lib/cc/stamped_element.h
new file mode 100644
index 0000000..c676826
--- /dev/null
+++ b/src/lib/cc/stamped_element.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2018-2019 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 STAMPED_ELEMENT_H
+#define STAMPED_ELEMENT_H
+
+#include <cc/base_stamped_element.h>
+#include <cc/server_tag.h>
+#include <set>
+
+namespace isc {
+namespace data {
+
+/// @brief This class represents configuration element which is
+/// associated with database identifier, modification timestamp
+/// and servers.
+///
+/// Classes storing Kea configuration should derive from this object
+/// to track ids and modification times of the configuration objects.
+/// This is specifically required by the Kea Configuration Backend
+/// feature which stores and fetches configuration from the database.
+/// The configuration elements must be accessible by their database
+/// identifiers and modification times.
+///
+/// @note This class is not derived from @c Element and should not
+/// be confused with the classes being derived from @c Element class.
+/// Those classes are used to represent JSON structures, whereas this
+/// class represents data fetched from the database.
+///
+/// @todo Find a better name for @c StampedElement.
+class StampedElement : public BaseStampedElement {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets timestamp to the current time.
+ StampedElement();
+
+ /// @brief Adds new server tag.
+ ///
+ /// @param server_tag new server tag.
+ /// @throw BadValue if the server tag length exceeds 256 characters.
+ void setServerTag(const std::string& server_tag) {
+ server_tags_.insert(ServerTag(server_tag));
+ }
+
+ /// @brief Deletes server tag.
+ ///
+ /// Remove the first occurrence of the given server tag.
+ ///
+ /// @param server_tag server tag to delete.
+ /// @throw NotFound if the server tag cannot be found.
+ void delServerTag(const std::string& server_tag);
+
+ /// @brief Returns server tags.
+ ///
+ /// @return Server tags.
+ std::set<ServerTag> getServerTags() const {
+ return (server_tags_);
+ }
+
+ /// @brief Checks if the element has the given server tag.
+ ///
+ /// @param server_tag Server tag to be found.
+ /// @return true if the server tag was found, false otherwise.
+ bool hasServerTag(const ServerTag& server_tag) const;
+
+ /// @brief Checks if the element has 'all' server tag.
+ ///
+ /// @return true if the server tag was found, false otherwise.
+ bool hasAllServerTag() const;
+
+ /// @brief Returns an object representing metadata to be returned
+ /// with objects from the configuration backend.
+ ///
+ /// @return Pointer to the metadata element.
+ isc::data::ElementPtr getMetadata() const;
+
+private:
+
+ /// @brief Holds server tags.
+ std::set<ServerTag> server_tags_;
+};
+
+} // end of namespace isc::data
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/cc/stamped_value.cc b/src/lib/cc/stamped_value.cc
new file mode 100644
index 0000000..077c688
--- /dev/null
+++ b/src/lib/cc/stamped_value.cc
@@ -0,0 +1,193 @@
+// Copyright (C) 2018-2023 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 <cc/stamped_value.h>
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace data {
+
+StampedValue::StampedValue(const std::string& name)
+ : StampedElement(), name_(name), value_() {
+}
+
+StampedValue::StampedValue(const std::string& name, const ElementPtr& value)
+ : StampedElement(), name_(name), value_(value) {
+ validateConstruct();
+}
+
+StampedValue::StampedValue(const std::string& name, const std::string& value)
+ : StampedElement(), name_(name), value_(Element::create(value)) {
+ validateConstruct();
+}
+
+StampedValuePtr
+StampedValue::create(const std::string& name) {
+ return (StampedValuePtr(new StampedValue(name)));
+}
+
+StampedValuePtr
+StampedValue::create(const std::string& name, const ElementPtr& value) {
+ return (StampedValuePtr(new StampedValue(name, value)));
+}
+
+StampedValuePtr
+StampedValue::create(const std::string& name, const std::string& value) {
+ return (StampedValuePtr(new StampedValue(name, value)));
+}
+
+StampedValuePtr
+StampedValue::create(const std::string& name, const std::string& value,
+ Element::types parameter_type) {
+ StampedValuePtr stamped_value;
+
+ try {
+ switch (parameter_type) {
+ case Element::string:
+ stamped_value = StampedValue::create(name, value);
+ break;
+
+ case Element::integer:
+ stamped_value = StampedValue::create(name,
+ Element::create(boost::lexical_cast<int64_t>(value)));
+ break;
+
+ case Element::boolean:
+ // We only allow "1" and "0" as input to this function.
+ if ((value != "0") && (value != "1")) {
+ isc_throw(BadValue, "StampedValue: invalid value " << value
+ << " specified as boolean. Expected \"0\" or \"1\"");
+ }
+ stamped_value = StampedValue::create(name,
+ Element::create((value == "0") ? false : true));
+ break;
+
+ case Element::real:
+ stamped_value = StampedValue::create(name,
+ Element::create(boost::lexical_cast<double>(value)));
+ break;
+
+ default:
+ // Invalid data type provided as argument.
+ isc_throw(TypeError, "StampedValue: unsupported type '"
+ << Element::typeToName(parameter_type)
+ << " of the parameter '" << name);
+ }
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ // Failed to cast the value to a given type.
+ isc_throw(BadValue, "StampedValue: the value of the parameter '"
+ << Element::typeToName(parameter_type)
+ << "' can't be converted to "
+ << Element::typeToName(parameter_type)
+ << " type");
+ }
+
+ return (stamped_value);
+}
+
+int
+StampedValue::getType() const {
+ if (!value_) {
+ isc_throw(InvalidOperation, "StampedValue: attempt to retrieve the "
+ "type of the null value for the '" << name_
+ << "' parameter");
+ }
+
+ return (value_->getType());
+}
+
+std::string
+StampedValue::getValue() const {
+ validateAccess(Element::string);
+
+ try {
+ switch (static_cast<Element::types>(value_->getType())) {
+ case Element::string:
+ return (value_->stringValue());
+ case Element::integer:
+ return (boost::lexical_cast<std::string>(value_->intValue()));
+ case Element::boolean:
+ return (value_->boolValue() ? "1" : "0");
+ case Element::real:
+ {
+ std::string repr =
+ boost::lexical_cast<std::string>(value_->doubleValue());
+ if (repr.find_first_of('.') == std::string::npos) {
+ repr += ".0";
+ }
+ return (repr);
+ }
+ default:
+ // Impossible condition.
+ isc_throw(TypeError, "StampedValue: invalid type of the '"
+ << name_ << "' parameter");
+ }
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(BadValue, "StampedValue: unable to convert the value of "
+ "the parameter '" << name_ << "' to string");
+ }
+ // unreachable
+ return (value_->stringValue());
+}
+
+int64_t
+StampedValue::getIntegerValue() const {
+ validateAccess(Element::integer);
+ return (value_->intValue());
+}
+
+bool
+StampedValue::getBoolValue() const {
+ validateAccess(Element::boolean);
+ return (value_->boolValue());
+}
+
+double
+StampedValue::getDoubleValue() const {
+ validateAccess(Element::real);
+ return (value_->doubleValue());
+}
+
+void
+StampedValue::validateConstruct() const {
+ if (!value_) {
+ isc_throw(BadValue, "StampedValue: provided value of the '"
+ << name_ << "' parameter is NULL");
+ }
+
+ if ((value_->getType() != Element::string) &&
+ (value_->getType() != Element::integer) &&
+ (value_->getType() != Element::boolean) &&
+ (value_->getType() != Element::real)) {
+ isc_throw(TypeError, "StampedValue: provided value of the '"
+ << name_ << "' parameter has invalid type: "
+ << Element::typeToName(value_->getType()));
+ }
+}
+
+void
+StampedValue::validateAccess(Element::types type) const {
+ if (!value_) {
+ isc_throw(InvalidOperation, "StampedValue: attempt to get null value "
+ "of the '" << name_ << "' parameter");
+ }
+
+ if ((type != Element::string) && (type != value_->getType())) {
+ isc_throw(TypeError, "StampedValue: attempt to access a '"
+ << name_ << "' parameter as " << Element::typeToName(type)
+ << ", but this parameter has "
+ << Element::typeToName(value_->getType())
+ << " type");
+ }
+}
+
+} // end of namespace isc::data
+} // end of namespace isc
diff --git a/src/lib/cc/stamped_value.h b/src/lib/cc/stamped_value.h
new file mode 100644
index 0000000..2120593
--- /dev/null
+++ b/src/lib/cc/stamped_value.h
@@ -0,0 +1,237 @@
+// Copyright (C) 2018-2019 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 STAMPED_VALUE_H
+#define STAMPED_VALUE_H
+
+#include <cc/data.h>
+#include <cc/stamped_element.h>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace data {
+
+class StampedValue;
+
+/// @brief Pointer to the stamped value.
+typedef boost::shared_ptr<StampedValue> StampedValuePtr;
+
+/// @brief This class represents a named configuration parameter,
+/// e.g. global parameter of the DHCP server.
+///
+/// Global configuration elements having simple types, e.g. DHCP
+/// timers, need to be associatied with modification timestamps.
+/// This association is made by deriving from @c StampedElement.
+/// The values can be strings, integers, booleans or real numbers.
+///
+/// Because the strings are more flexible, configuration elements
+/// are always held as strings in the configuration backends. This
+/// class reflects a single value held in the database. The value
+/// can be return in its orginal type or can be returned as a
+/// string. Also the null values are allowed.
+class StampedValue : public StampedElement {
+public:
+
+ /// @brief Constructor creating a null value.
+ ///
+ /// @param name Name of the value.
+ StampedValue(const std::string& name);
+
+ /// @brief Constructor creating a value from the @c Element.
+ ///
+ /// @param name Name of the value.
+ /// @param value Value encapsulated in the @c Element object.
+ ///
+ /// @throw BadValue if the value is null.
+ /// @throw TypeError if the value is neither a string, integer,
+ /// bool nor real.
+ StampedValue(const std::string& name, const ElementPtr& value);
+
+ /// @brief Constructor creating a string value.
+ ///
+ /// Creates stamped value from a string.
+ ///
+ /// @param name Name of the value.
+ /// @param value Value to be set.
+ StampedValue(const std::string& name, const std::string& value);
+
+ /// @brief Factory function creating a null value.
+ ///
+ /// @param name Name of the value.
+ static StampedValuePtr create(const std::string& name);
+
+ /// @brief Factory function creating a value from the @c Element.
+ ///
+ /// @param name Name of the value.
+ /// @param value Value encapsulated in the @c Element object.
+ ///
+ /// @throw BadValue if the value is null.
+ /// @throw TypeError if the value is neither a string, integer,
+ /// bool nor real.
+ static StampedValuePtr create(const std::string& name,
+ const ElementPtr& value);
+
+ /// @brief Factory function creating a string value.
+ ///
+ /// Creates stamped value from a string.
+ ///
+ /// @param name Name of the value.
+ /// @param value Value to be set.
+ static StampedValuePtr create(const std::string& name,
+ const std::string& value);
+
+ /// @brief Factory function which attempts to convert provided
+ /// string value to a given type.
+ ///
+ /// This factory function is useful in cases when the value is
+ /// read as a string from a database. The string value has to
+ /// be converted to the appropriate data type. The type is also
+ /// known from the database.
+ ///
+ /// @param name Name of the value.
+ /// @param value Value given as string to be converted.
+ /// @param type Type of the value to convert to.
+ static StampedValuePtr create(const std::string& name,
+ const std::string& value,
+ Element::types type);
+
+ /// @brief Returns a type of the value.
+ ///
+ /// @return Type of the value as integer. It can be compared
+ /// with the @c Element::getType() output.
+ /// @throw InvalidOperation if the value is null.
+ int getType() const;
+
+ /// @brief Returns value name.
+ ///
+ /// @return Value name.
+ std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Returns value as string.
+ ///
+ /// It is allowed to call this function for all supported data
+ /// types. They are converted to a string. For example, a real
+ /// number of 1.4 will be returned as "1.4". The boolean true
+ /// value will be returned as "1" etc.
+ ///
+ /// @return Stored value as string.
+ /// @throw InvalidOperation if the value is null.
+ std::string getValue() const;
+
+ /// @brief Checks if the value is null.
+ ///
+ /// @return true if the value is null, false otherwise.
+ bool amNull() const {
+ return (!value_);
+ }
+
+ /// @brief Returns value as signed integer.
+ ///
+ /// @return Stored value as a signed integer.
+ /// @throw TypeError if the value is not of @c Element::integer
+ /// type.
+ int64_t getIntegerValue() const;
+
+ /// @brief Returns value as a boolean.
+ ///
+ /// @return Stored value as a boolean.
+ /// @throw TypeError if the value is not of @c Element::boolean
+ /// type.
+ bool getBoolValue() const;
+
+ /// @brief Returns value as a real number.
+ ///
+ /// @return Stored value as a real number.
+ /// @throw TypeError if the value is not of @c Element::real
+ /// type.
+ double getDoubleValue() const;
+
+ /// @brief Returns the value as @c Element.
+ ConstElementPtr getElementValue() const {
+ return (value_);
+ }
+
+private:
+
+ /// @brief Checks if the values passed to the constructors
+ /// were correct.
+ ///
+ /// This is called from the constructors.
+ ///
+ /// @throw BadValue if the value is null.
+ /// @throw TypeError if the value type is neither a string,
+ /// integer, boolean nor real.
+ void validateConstruct() const;
+
+ /// @brief Checks if the value is accessed correctly.
+ ///
+ /// This is called from the accessors of this class.
+ ///
+ /// @param type Type of the value expected by the accessor
+ /// function.
+ ///
+ /// @throw InvalidOperation if the accessed value is null.
+ /// @throw TypeError if the expected type is not a string
+ /// and it doesn't match the value type.
+ void validateAccess(Element::types type) const;
+
+ /// @brief Name of the value.
+ std::string name_;
+
+ /// @brief Stored value.
+ ElementPtr value_;
+};
+
+/// @name Definition of the multi index container for @c StampedValue.
+///
+//@{
+
+/// @brief Tag for the index for access by value name.
+struct StampedValueNameIndexTag { };
+
+/// @brief Tag for the index for access by modification time.
+struct StampedValueModificationTimeIndexTag { };
+
+/// @brief Multi index container for @c StampedValue.
+typedef boost::multi_index_container<
+ StampedValuePtr,
+ boost::multi_index::indexed_by<
+ // Index used to access value by name.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<StampedValueNameIndexTag>,
+ boost::multi_index::const_mem_fun<
+ StampedValue,
+ std::string,
+ &StampedValue::getName
+ >
+ >,
+
+ // Index used to access value by modification time.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<StampedValueModificationTimeIndexTag>,
+ boost::multi_index::const_mem_fun<
+ BaseStampedElement,
+ boost::posix_time::ptime,
+ &BaseStampedElement::getModificationTime
+ >
+ >
+ >
+> StampedValueCollection;
+
+//@}
+
+} // end of namespace isc::data
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am
new file mode 100644
index 0000000..38f6e7a
--- /dev/null
+++ b/src/lib/cc/tests/Makefile.am
@@ -0,0 +1,40 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+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 = command_interpreter_unittests.cc
+run_unittests_SOURCES += data_unittests.cc
+run_unittests_SOURCES += data_file_unittests.cc
+run_unittests_SOURCES += element_value_unittests.cc
+run_unittests_SOURCES += json_feed_unittests.cc
+run_unittests_SOURCES += server_tag_unittest.cc
+run_unittests_SOURCES += simple_parser_unittest.cc
+run_unittests_SOURCES += stamped_element_unittest.cc
+run_unittests_SOURCES += stamped_value_unittest.cc
+run_unittests_SOURCES += user_context_unittests.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/cc/libkea-cc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.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 += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/cc/tests/Makefile.in b/src/lib/cc/tests/Makefile.in
new file mode 100644
index 0000000..5fb8c66
--- /dev/null
+++ b/src/lib/cc/tests/Makefile.in
@@ -0,0 +1,1071 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/cc/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = command_interpreter_unittests.cc \
+ data_unittests.cc data_file_unittests.cc \
+ element_value_unittests.cc json_feed_unittests.cc \
+ server_tag_unittest.cc simple_parser_unittest.cc \
+ stamped_element_unittest.cc stamped_value_unittest.cc \
+ user_context_unittests.cc run_unittests.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = run_unittests-command_interpreter_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-data_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-data_file_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-element_value_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-json_feed_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-server_tag_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-simple_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-stamped_element_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-stamped_value_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-user_context_unittests.$(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/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(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-command_interpreter_unittests.Po \
+ ./$(DEPDIR)/run_unittests-data_file_unittests.Po \
+ ./$(DEPDIR)/run_unittests-data_unittests.Po \
+ ./$(DEPDIR)/run_unittests-element_value_unittests.Po \
+ ./$(DEPDIR)/run_unittests-json_feed_unittests.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-server_tag_unittest.Po \
+ ./$(DEPDIR)/run_unittests-simple_parser_unittest.Po \
+ ./$(DEPDIR)/run_unittests-stamped_element_unittest.Po \
+ ./$(DEPDIR)/run_unittests-stamped_value_unittest.Po \
+ ./$(DEPDIR)/run_unittests-user_context_unittests.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 =
+SOURCES = $(run_unittests_SOURCES)
+DIST_SOURCES = $(am__run_unittests_SOURCES_DIST)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+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; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+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 = \
+@HAVE_GTEST_TRUE@ command_interpreter_unittests.cc \
+@HAVE_GTEST_TRUE@ data_unittests.cc data_file_unittests.cc \
+@HAVE_GTEST_TRUE@ element_value_unittests.cc \
+@HAVE_GTEST_TRUE@ json_feed_unittests.cc server_tag_unittest.cc \
+@HAVE_GTEST_TRUE@ simple_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ stamped_element_unittest.cc \
+@HAVE_GTEST_TRUE@ stamped_value_unittest.cc \
+@HAVE_GTEST_TRUE@ user_context_unittests.cc run_unittests.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(GTEST_LDADD)
+all: all-am
+
+.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/cc/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/cc/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-command_interpreter_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-data_file_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-data_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-element_value_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-json_feed_unittests.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-server_tag_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-simple_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-stamped_element_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-stamped_value_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-user_context_unittests.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-command_interpreter_unittests.o: command_interpreter_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_interpreter_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo -c -o run_unittests-command_interpreter_unittests.o `test -f 'command_interpreter_unittests.cc' || echo '$(srcdir)/'`command_interpreter_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo $(DEPDIR)/run_unittests-command_interpreter_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_interpreter_unittests.cc' object='run_unittests-command_interpreter_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-command_interpreter_unittests.o `test -f 'command_interpreter_unittests.cc' || echo '$(srcdir)/'`command_interpreter_unittests.cc
+
+run_unittests-command_interpreter_unittests.obj: command_interpreter_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_interpreter_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo -c -o run_unittests-command_interpreter_unittests.obj `if test -f 'command_interpreter_unittests.cc'; then $(CYGPATH_W) 'command_interpreter_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_interpreter_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo $(DEPDIR)/run_unittests-command_interpreter_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_interpreter_unittests.cc' object='run_unittests-command_interpreter_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-command_interpreter_unittests.obj `if test -f 'command_interpreter_unittests.cc'; then $(CYGPATH_W) 'command_interpreter_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_interpreter_unittests.cc'; fi`
+
+run_unittests-data_unittests.o: data_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-data_unittests.Tpo -c -o run_unittests-data_unittests.o `test -f 'data_unittests.cc' || echo '$(srcdir)/'`data_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_unittests.Tpo $(DEPDIR)/run_unittests-data_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_unittests.cc' object='run_unittests-data_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-data_unittests.o `test -f 'data_unittests.cc' || echo '$(srcdir)/'`data_unittests.cc
+
+run_unittests-data_unittests.obj: data_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-data_unittests.Tpo -c -o run_unittests-data_unittests.obj `if test -f 'data_unittests.cc'; then $(CYGPATH_W) 'data_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_unittests.Tpo $(DEPDIR)/run_unittests-data_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_unittests.cc' object='run_unittests-data_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-data_unittests.obj `if test -f 'data_unittests.cc'; then $(CYGPATH_W) 'data_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_unittests.cc'; fi`
+
+run_unittests-data_file_unittests.o: data_file_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_file_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-data_file_unittests.Tpo -c -o run_unittests-data_file_unittests.o `test -f 'data_file_unittests.cc' || echo '$(srcdir)/'`data_file_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_file_unittests.Tpo $(DEPDIR)/run_unittests-data_file_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_file_unittests.cc' object='run_unittests-data_file_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-data_file_unittests.o `test -f 'data_file_unittests.cc' || echo '$(srcdir)/'`data_file_unittests.cc
+
+run_unittests-data_file_unittests.obj: data_file_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_file_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-data_file_unittests.Tpo -c -o run_unittests-data_file_unittests.obj `if test -f 'data_file_unittests.cc'; then $(CYGPATH_W) 'data_file_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_file_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_file_unittests.Tpo $(DEPDIR)/run_unittests-data_file_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_file_unittests.cc' object='run_unittests-data_file_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-data_file_unittests.obj `if test -f 'data_file_unittests.cc'; then $(CYGPATH_W) 'data_file_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_file_unittests.cc'; fi`
+
+run_unittests-element_value_unittests.o: element_value_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-element_value_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-element_value_unittests.Tpo -c -o run_unittests-element_value_unittests.o `test -f 'element_value_unittests.cc' || echo '$(srcdir)/'`element_value_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-element_value_unittests.Tpo $(DEPDIR)/run_unittests-element_value_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='element_value_unittests.cc' object='run_unittests-element_value_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-element_value_unittests.o `test -f 'element_value_unittests.cc' || echo '$(srcdir)/'`element_value_unittests.cc
+
+run_unittests-element_value_unittests.obj: element_value_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-element_value_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-element_value_unittests.Tpo -c -o run_unittests-element_value_unittests.obj `if test -f 'element_value_unittests.cc'; then $(CYGPATH_W) 'element_value_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/element_value_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-element_value_unittests.Tpo $(DEPDIR)/run_unittests-element_value_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='element_value_unittests.cc' object='run_unittests-element_value_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-element_value_unittests.obj `if test -f 'element_value_unittests.cc'; then $(CYGPATH_W) 'element_value_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/element_value_unittests.cc'; fi`
+
+run_unittests-json_feed_unittests.o: json_feed_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-json_feed_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-json_feed_unittests.Tpo -c -o run_unittests-json_feed_unittests.o `test -f 'json_feed_unittests.cc' || echo '$(srcdir)/'`json_feed_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-json_feed_unittests.Tpo $(DEPDIR)/run_unittests-json_feed_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='json_feed_unittests.cc' object='run_unittests-json_feed_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-json_feed_unittests.o `test -f 'json_feed_unittests.cc' || echo '$(srcdir)/'`json_feed_unittests.cc
+
+run_unittests-json_feed_unittests.obj: json_feed_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-json_feed_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-json_feed_unittests.Tpo -c -o run_unittests-json_feed_unittests.obj `if test -f 'json_feed_unittests.cc'; then $(CYGPATH_W) 'json_feed_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/json_feed_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-json_feed_unittests.Tpo $(DEPDIR)/run_unittests-json_feed_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='json_feed_unittests.cc' object='run_unittests-json_feed_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-json_feed_unittests.obj `if test -f 'json_feed_unittests.cc'; then $(CYGPATH_W) 'json_feed_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/json_feed_unittests.cc'; fi`
+
+run_unittests-server_tag_unittest.o: server_tag_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_tag_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-server_tag_unittest.Tpo -c -o run_unittests-server_tag_unittest.o `test -f 'server_tag_unittest.cc' || echo '$(srcdir)/'`server_tag_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_tag_unittest.Tpo $(DEPDIR)/run_unittests-server_tag_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_tag_unittest.cc' object='run_unittests-server_tag_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-server_tag_unittest.o `test -f 'server_tag_unittest.cc' || echo '$(srcdir)/'`server_tag_unittest.cc
+
+run_unittests-server_tag_unittest.obj: server_tag_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_tag_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-server_tag_unittest.Tpo -c -o run_unittests-server_tag_unittest.obj `if test -f 'server_tag_unittest.cc'; then $(CYGPATH_W) 'server_tag_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_tag_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_tag_unittest.Tpo $(DEPDIR)/run_unittests-server_tag_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_tag_unittest.cc' object='run_unittests-server_tag_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-server_tag_unittest.obj `if test -f 'server_tag_unittest.cc'; then $(CYGPATH_W) 'server_tag_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_tag_unittest.cc'; fi`
+
+run_unittests-simple_parser_unittest.o: simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-simple_parser_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo -c -o run_unittests-simple_parser_unittest.o `test -f 'simple_parser_unittest.cc' || echo '$(srcdir)/'`simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo $(DEPDIR)/run_unittests-simple_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser_unittest.cc' object='run_unittests-simple_parser_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-simple_parser_unittest.o `test -f 'simple_parser_unittest.cc' || echo '$(srcdir)/'`simple_parser_unittest.cc
+
+run_unittests-simple_parser_unittest.obj: simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-simple_parser_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo -c -o run_unittests-simple_parser_unittest.obj `if test -f 'simple_parser_unittest.cc'; then $(CYGPATH_W) 'simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo $(DEPDIR)/run_unittests-simple_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser_unittest.cc' object='run_unittests-simple_parser_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-simple_parser_unittest.obj `if test -f 'simple_parser_unittest.cc'; then $(CYGPATH_W) 'simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser_unittest.cc'; fi`
+
+run_unittests-stamped_element_unittest.o: stamped_element_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_element_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo -c -o run_unittests-stamped_element_unittest.o `test -f 'stamped_element_unittest.cc' || echo '$(srcdir)/'`stamped_element_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo $(DEPDIR)/run_unittests-stamped_element_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_element_unittest.cc' object='run_unittests-stamped_element_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-stamped_element_unittest.o `test -f 'stamped_element_unittest.cc' || echo '$(srcdir)/'`stamped_element_unittest.cc
+
+run_unittests-stamped_element_unittest.obj: stamped_element_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_element_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo -c -o run_unittests-stamped_element_unittest.obj `if test -f 'stamped_element_unittest.cc'; then $(CYGPATH_W) 'stamped_element_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_element_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo $(DEPDIR)/run_unittests-stamped_element_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_element_unittest.cc' object='run_unittests-stamped_element_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-stamped_element_unittest.obj `if test -f 'stamped_element_unittest.cc'; then $(CYGPATH_W) 'stamped_element_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_element_unittest.cc'; fi`
+
+run_unittests-stamped_value_unittest.o: stamped_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_value_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo -c -o run_unittests-stamped_value_unittest.o `test -f 'stamped_value_unittest.cc' || echo '$(srcdir)/'`stamped_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo $(DEPDIR)/run_unittests-stamped_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_value_unittest.cc' object='run_unittests-stamped_value_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-stamped_value_unittest.o `test -f 'stamped_value_unittest.cc' || echo '$(srcdir)/'`stamped_value_unittest.cc
+
+run_unittests-stamped_value_unittest.obj: stamped_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_value_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo -c -o run_unittests-stamped_value_unittest.obj `if test -f 'stamped_value_unittest.cc'; then $(CYGPATH_W) 'stamped_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_value_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo $(DEPDIR)/run_unittests-stamped_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_value_unittest.cc' object='run_unittests-stamped_value_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-stamped_value_unittest.obj `if test -f 'stamped_value_unittest.cc'; then $(CYGPATH_W) 'stamped_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_value_unittest.cc'; fi`
+
+run_unittests-user_context_unittests.o: user_context_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-user_context_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-user_context_unittests.Tpo -c -o run_unittests-user_context_unittests.o `test -f 'user_context_unittests.cc' || echo '$(srcdir)/'`user_context_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-user_context_unittests.Tpo $(DEPDIR)/run_unittests-user_context_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='user_context_unittests.cc' object='run_unittests-user_context_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-user_context_unittests.o `test -f 'user_context_unittests.cc' || echo '$(srcdir)/'`user_context_unittests.cc
+
+run_unittests-user_context_unittests.obj: user_context_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-user_context_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-user_context_unittests.Tpo -c -o run_unittests-user_context_unittests.obj `if test -f 'user_context_unittests.cc'; then $(CYGPATH_W) 'user_context_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/user_context_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-user_context_unittests.Tpo $(DEPDIR)/run_unittests-user_context_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='user_context_unittests.cc' object='run_unittests-user_context_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-user_context_unittests.obj `if test -f 'user_context_unittests.cc'; then $(CYGPATH_W) 'user_context_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/user_context_unittests.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
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS)
+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 clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/run_unittests-command_interpreter_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-data_file_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-data_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-element_value_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-json_feed_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-server_tag_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-simple_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stamped_element_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stamped_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-user_context_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/run_unittests-command_interpreter_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-data_file_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-data_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-element_value_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-json_feed_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-server_tag_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-simple_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stamped_element_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stamped_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-user_context_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: 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 \
+ 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/cc/tests/command_interpreter_unittests.cc b/src/lib/cc/tests/command_interpreter_unittests.cc
new file mode 100644
index 0000000..8738b92
--- /dev/null
+++ b/src/lib/cc/tests/command_interpreter_unittests.cc
@@ -0,0 +1,249 @@
+// Copyright (C) 2009-2023 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 <cc/command_interpreter.h>
+#include <config/tests/data_def_unittests_config.h>
+#include <log/logger_name.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <fstream>
+
+using namespace isc::data;
+using namespace isc::config;
+using namespace std;
+
+namespace {
+
+
+/// @brief Convenience method for creating elements from JSON string
+///
+/// @param str string to be converted
+/// @return Element structure
+ElementPtr
+el(const std::string& str) {
+ return (Element::fromJSON(str));
+}
+
+// This test verifies that that createAnswer method is able to generate
+// various answers.
+TEST(CommandInterpreterTest, createAnswer) {
+ ConstElementPtr answer;
+
+ // By default the answer is a successful one.
+ answer = createAnswer();
+ EXPECT_EQ("{ \"result\": 0 }", answer->str());
+
+ // Let's check if we can generate an error.
+ answer = createAnswer(CONTROL_RESULT_ERROR, "error");
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"error\" }", answer->str());
+
+ // This is expected to throw. When status code is non-zero (indicating error),
+ // textual explanation is mandatory.
+ EXPECT_THROW(createAnswer(CONTROL_RESULT_ERROR, ElementPtr()), CtrlChannelError);
+ EXPECT_THROW(createAnswer(CONTROL_RESULT_ERROR, Element::create(1)), CtrlChannelError);
+
+ // Let's check if answer can be generate with some data in it.
+ ConstElementPtr arg = el("[ \"just\", \"some\", \"data\" ]");
+ answer = createAnswer(CONTROL_RESULT_SUCCESS, arg);
+ EXPECT_EQ("{ \"arguments\": [ \"just\", \"some\", \"data\" ], \"result\": 0 }",
+ answer->str());
+}
+
+// This test checks whether parseAnswer is able to handle good and malformed
+// answers.
+TEST(CommandInterpreterTest, parseAnswer) {
+ ConstElementPtr answer;
+ ConstElementPtr arg;
+ int rcode;
+
+ EXPECT_THROW(parseAnswer(rcode, ElementPtr()), CtrlChannelError);
+ EXPECT_THROW(parseAnswer(rcode, el("1")), CtrlChannelError);
+ EXPECT_THROW(parseAnswer(rcode, el("[]")), CtrlChannelError);
+ EXPECT_THROW(parseAnswer(rcode, el("{ }")), CtrlChannelError);
+ EXPECT_THROW(parseAnswer(rcode, el("{ \"something\": 1 }")), CtrlChannelError);
+ EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 0 ] }")), CtrlChannelError);
+ EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1 ] }")), CtrlChannelError);
+ EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1, 1 ] }")), CtrlChannelError);
+
+ answer = el("{ \"result\": 0 }");
+ arg = parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+ EXPECT_TRUE(isNull(arg));
+
+ answer = el("{ \"result\": 3, \"text\": \"error\", \"arguments\": [ \"some\", \"data\" ] }");
+ arg = parseAnswer(rcode, answer);
+ ASSERT_TRUE(arg);
+ EXPECT_EQ(3, rcode);
+ EXPECT_EQ("[ \"some\", \"data\" ]", arg->str());
+}
+
+// Checks if parseAnswerText can return the text
+TEST(CommandInterpreterTest, parseAnswerText) {
+ ConstElementPtr answer;
+ ConstElementPtr arg;
+ int rcode;
+
+ answer = el("{ \"result\": 5, \"text\": \"error\", \"arguments\": [ \"some\", \"data\" ] }");
+ arg = parseAnswerText(rcode, answer);
+ ASSERT_TRUE(arg);
+ EXPECT_EQ(5, rcode);
+ EXPECT_EQ("error", arg->stringValue());
+}
+
+// This checks whether we can convert an answer to easily printable form.
+TEST(CommandInterpreterTest, answerToText) {
+ ConstElementPtr answer;
+
+ // Doing jolly good here.
+ answer = el("{ \"result\": 0 }");
+ EXPECT_EQ("success(0)", answerToText(answer));
+
+ // Sometimes things don't go according to plan.
+ answer = el("{ \"result\": 1, \"text\": \"ho lee fuk sum ting wong\" }");
+ EXPECT_EQ("failure(1), text=ho lee fuk sum ting wong", answerToText(answer));
+}
+
+// This test checks whether createCommand function is able to create commands
+// with and without parameters.
+TEST(CommandInterpreterTest, createCommand) {
+ ConstElementPtr command;
+ ConstElementPtr arg;
+ string service;
+
+ command = createCommand("my_command");
+ ASSERT_EQ("{ \"command\": \"my_command\" }", command->str());
+
+ arg = el("1");
+ command = createCommand("my_command", arg);
+ ASSERT_EQ("{ \"arguments\": 1, \"command\": \"my_command\" }",
+ command->str());
+
+ arg = el("[ \"a\", \"b\" ]");
+ command = createCommand("my_cmd", arg);
+ ASSERT_EQ("{ \"arguments\": [ \"a\", \"b\" ], \"command\": \"my_cmd\" }",
+ command->str());
+
+ arg = el("{ \"a\": \"map\" }");
+ command = createCommand("foo", arg);
+ ASSERT_EQ("{ \"arguments\": { \"a\": \"map\" }, \"command\": \"foo\" }",
+ command->str());
+
+ command = createCommand("my_command", "my_service");
+ ASSERT_EQ("{ \"command\": \"my_command\", "
+ "\"service\": [ \"my_service\" ] }",
+ command->str());
+
+ arg = el("1");
+ command = createCommand("my_command", arg, "my_service");
+ ASSERT_EQ("{ \"arguments\": 1, \"command\": \"my_command\", "
+ "\"service\": [ \"my_service\" ] }",
+ command->str());
+
+ arg = el("[ \"a\", \"b\" ]");
+ command = createCommand("my_cmd", arg, "my_server");
+ ASSERT_EQ("{ \"arguments\": [ \"a\", \"b\" ], "
+ "\"command\": \"my_cmd\", "
+ "\"service\": [ \"my_server\" ] }",
+ command->str());
+
+ arg = el("{ \"a\": \"map\" }");
+ command = createCommand("foo", arg, "bar");
+ ASSERT_EQ("{ \"arguments\": { \"a\": \"map\" }, "
+ "\"command\": \"foo\", "
+ "\"service\": [ \"bar\" ] }",
+ command->str());
+}
+
+// This test checks whether parseCommand function is able to parse various valid
+// and malformed commands.
+TEST(CommandInterpreterTest, parseCommand) {
+ ConstElementPtr arg;
+ std::string cmd;
+
+ // should throw
+ EXPECT_THROW(parseCommand(arg, ElementPtr()), CtrlChannelError);
+ EXPECT_THROW(parseCommand(arg, el("1")), CtrlChannelError);
+ EXPECT_THROW(parseCommand(arg, el("{ }")), CtrlChannelError);
+ EXPECT_THROW(parseCommand(arg, el("{ \"not a command\": 1 }")), CtrlChannelError);
+ EXPECT_THROW(parseCommand(arg, el("{ \"command\": 1 }")), CtrlChannelError);
+ EXPECT_THROW(parseCommand(arg, el("{ \"command\": [] }")), CtrlChannelError);
+ EXPECT_THROW(parseCommand(arg, el("{ \"command\": [ 1 ] }")), CtrlChannelError);
+ EXPECT_THROW(parseCommand(arg, el("{ \"command\": \"my_command\", "
+ "\"unknown\": \"xyz\" }")), CtrlChannelError);
+
+ cmd = parseCommand(arg, el("{ \"command\": \"my_command\" }"));
+ EXPECT_EQ("my_command", cmd);
+ EXPECT_FALSE(arg);
+
+ // Include "service" to verify that it is not rejected.
+ cmd = parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": 1, "
+ " \"service\": [ \"dhcp4\" ] }"));
+ ASSERT_TRUE(arg);
+ EXPECT_EQ("my_command", cmd);
+ EXPECT_EQ("1", arg->str());
+
+ parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": "
+ "[ \"some\", \"argument\", \"list\" ] }"));
+ EXPECT_EQ("my_command", cmd);
+ ASSERT_TRUE(arg);
+ EXPECT_EQ("[ \"some\", \"argument\", \"list\" ]", arg->str());
+
+}
+
+// This test checks whether parseCommandWithArgs function is able to parse
+// various valid and malformed commands.
+TEST(CommandInterpreterTest, parseCommandWithArgs) {
+ ConstElementPtr arg;
+ std::string cmd;
+
+ // Arguments are required.
+ EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\" }")),
+ CtrlChannelError);
+
+ // Arguments must be a map.
+ EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", "
+ "\"arguments\": [ 1, 2, 3 ] }")),
+ CtrlChannelError);
+
+ // Arguments must not be empty.
+ EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", "
+ "\"arguments\": { } }")),
+ CtrlChannelError);
+
+ // Command with unsupported parameter is rejected.
+ EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", "
+ " \"arguments\": { \"arg1\": \"value1\" }, "
+ " \"unsupported\": 1 }")),
+ CtrlChannelError);
+
+
+ // Specifying arguments in non empty map should be successful.
+ EXPECT_NO_THROW(
+ cmd = parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", "
+ " \"arguments\": { \"arg1\": \"value1\" } }"))
+ );
+ ASSERT_TRUE(arg);
+ ASSERT_EQ(Element::map, arg->getType());
+ auto arg1 = arg->get("arg1");
+ ASSERT_TRUE(arg1);
+ ASSERT_EQ(Element::string, arg1->getType());
+ EXPECT_EQ("value1", arg1->stringValue());
+ EXPECT_EQ("my_command", cmd);
+
+ // The "service" parameter should be allowed.
+ EXPECT_NO_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", "
+ " \"service\": [ \"dhcp4\" ], "
+ " \"arguments\": { \"arg1\": \"value1\" } }"))
+ );
+
+}
+
+}
diff --git a/src/lib/cc/tests/data_file_unittests.cc b/src/lib/cc/tests/data_file_unittests.cc
new file mode 100644
index 0000000..a7f1b8d
--- /dev/null
+++ b/src/lib/cc/tests/data_file_unittests.cc
@@ -0,0 +1,98 @@
+// Copyright (C) 2014-2018 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 <gtest/gtest.h>
+#include <cc/data.h>
+#include <fstream>
+
+using namespace isc;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Test class for testing Daemon class
+class DataFileTest : public ::testing::Test {
+public:
+
+ /// @brief writes specified text to a file
+ ///
+ /// That is an auxiliary function used in fileRead() tests.
+ ///
+ /// @param content text to be written to disk
+ void writeFile(const std::string& content) {
+ // Write sample content to disk
+ static_cast<void>(remove(TEMP_FILE));
+ std::ofstream write_me(TEMP_FILE);
+ EXPECT_TRUE(write_me.is_open());
+ write_me << content;
+ write_me.close();
+ }
+
+ /// destructor
+ ~DataFileTest() {
+ static_cast<void>(remove(TEMP_FILE));
+ }
+
+ /// Name of the temporary file
+ static const char* TEMP_FILE;
+};
+
+/// Temporary file name used in some tests
+const char* DataFileTest::TEMP_FILE="temp-file.json";
+
+// Test checks whether a text file can be read from disk.
+TEST_F(DataFileTest, readFileMultiline) {
+
+ const char* no_endline = "{ \"abc\": 123 }";
+ const char* with_endline = "{\n \"abc\":\n 123\n }\n";
+
+ // That's what we expect
+ ElementPtr exp = Element::fromJSON(no_endline);
+
+ // Write sample content to disk
+ writeFile(no_endline);
+
+ // Check that the read content is correct
+ EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE)));
+
+ // Write sample content to disk
+ writeFile(with_endline);
+
+ // Check that the read content is correct
+ EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE)));
+}
+
+// Test checks whether comments in file are ignored as expected.
+TEST_F(DataFileTest, readFileComments) {
+ const char* commented_content = "# This is a comment\n"
+ "{ \"abc\":\n"
+ "# a comment comment\n"
+ "1 }\n";
+
+ // That's what we expect
+ ElementPtr exp = Element::fromJSON("{ \"abc\": 1 }");
+
+ // Write sample content to disk
+ writeFile(commented_content);
+
+ // Check that the read will fail (without comment elimination)
+ EXPECT_THROW(Element::fromJSONFile(TEMP_FILE), JSONError);
+
+ // Check that the read content is correct (with comment elimination)
+ EXPECT_NO_THROW(Element::fromJSONFile(TEMP_FILE, true));
+ EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE, true)));
+}
+
+// This test checks that missing file will generate an exception.
+TEST_F(DataFileTest, readFileError) {
+
+ // Check that the read content is correct
+ EXPECT_THROW(Element::fromJSONFile("no-such-file.txt"), isc::InvalidOperation);
+}
+
+};
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
new file mode 100644
index 0000000..ed23c45
--- /dev/null
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -0,0 +1,2232 @@
+// Copyright (C) 2009-2023 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 <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
+#include <boost/assign/std/vector.hpp>
+#include <climits>
+
+#include <cc/data.h>
+#include <util/unittests/check_valgrind.h>
+
+using namespace isc::data;
+
+#include <sstream>
+#include <iostream>
+using std::oct;
+#include <iomanip>
+using std::setfill;
+using std::setw;
+using std::string;
+
+namespace {
+
+TEST(Position, str) {
+ Element::Position position("kea.conf", 30, 20);
+ EXPECT_EQ("kea.conf:30:20", position.str());
+
+ Element::Position position2("another.conf", 123, 24);
+ EXPECT_EQ("another.conf:123:24", position2.str());
+}
+
+TEST(Element, type) {
+ // this tests checks whether the getType() function returns the
+ // correct type
+ IntElement int_el = IntElement(1);
+ EXPECT_EQ(int_el.getType(), Element::integer);
+ DoubleElement double_el = DoubleElement(1.0);
+ EXPECT_EQ(double_el.getType(), Element::real);
+ BoolElement bool_el = BoolElement(true);
+ EXPECT_EQ(bool_el.getType(), Element::boolean);
+ StringElement str_el = StringElement("foo");
+ EXPECT_EQ(str_el.getType(), Element::string);
+ ListElement list_el = ListElement();
+ EXPECT_EQ(list_el.getType(), Element::list);
+ MapElement map_el = MapElement();
+ EXPECT_EQ(map_el.getType(), Element::map);
+
+}
+
+TEST(Element, TypeNameConversion) {
+ EXPECT_EQ(Element::integer, Element::nameToType("integer"));
+ EXPECT_EQ(Element::bigint, Element::nameToType("bigint"));
+ EXPECT_EQ(Element::real, Element::nameToType("real"));
+ EXPECT_EQ(Element::boolean, Element::nameToType("boolean"));
+ EXPECT_EQ(Element::string, Element::nameToType("string"));
+ EXPECT_EQ(Element::list, Element::nameToType("list"));
+ EXPECT_EQ(Element::map, Element::nameToType("map"));
+ EXPECT_EQ(Element::null, Element::nameToType("null"));
+ EXPECT_EQ(Element::any, Element::nameToType("any"));
+ EXPECT_THROW(Element::nameToType("somethingunknown"), TypeError);
+
+ EXPECT_EQ("integer", Element::typeToName(Element::integer));
+ EXPECT_EQ("bigint", Element::typeToName(Element::bigint));
+ EXPECT_EQ("real", Element::typeToName(Element::real));
+ EXPECT_EQ("boolean", Element::typeToName(Element::boolean));
+ EXPECT_EQ("string", Element::typeToName(Element::string));
+ EXPECT_EQ("list", Element::typeToName(Element::list));
+ EXPECT_EQ("map", Element::typeToName(Element::map));
+ EXPECT_EQ("null", Element::typeToName(Element::null));
+ EXPECT_EQ("any", Element::typeToName(Element::any));
+ EXPECT_EQ("unknown", Element::typeToName(static_cast<Element::types>(123)));
+}
+
+TEST(Element, from_and_to_json) {
+ // a set of inputs that are the same when converted to json and
+ // back to a string (tests for inputs that have equivalent, but
+ // different string representations when converted back are below)
+ ConstElementPtr el;
+ std::vector<std::string> sv;
+
+ sv.push_back("12");
+ sv.push_back("1.1");
+ sv.push_back("true");
+ sv.push_back("false");
+ sv.push_back("\"asdf\"");
+ sv.push_back("null");
+ sv.push_back("[ 1, 2, 3, 4 ]");
+ sv.push_back("{ \"name\": \"foo\", \"value\": 56176 }");
+ sv.push_back("[ { \"a\": 1, \"b\": \"c\" }, { \"a\": 2, \"b\": \"d\" } ]");
+ sv.push_back("8.23");
+ sv.push_back("123.456");
+ sv.push_back("null");
+ sv.push_back("-1");
+ sv.push_back("-1.234");
+ sv.push_back("-123.456");
+ // We should confirm that our string handling is 8-bit clean.
+ // At one point we were using char-length data and comparing to EOF,
+ // which means that character '\xFF' would not parse properly.
+ sv.push_back("\"\\u00ff\"");
+
+ BOOST_FOREACH(const std::string& s, sv) {
+ // Test two types of fromJSON(): with string and istream.
+ for (unsigned i = 0; i < 2; ++i) {
+ // test << operator, which uses Element::str()
+ if (i == 0) {
+ el = Element::fromJSON(s);
+ } else {
+ std::istringstream iss(s);
+ el = Element::fromJSON(iss);
+ }
+ std::ostringstream stream;
+ stream << *el;
+ EXPECT_EQ(s, stream.str());
+
+ // test toWire(ostream), which should also be the same now
+ std::ostringstream wire_stream;
+ el->toWire(wire_stream);
+ EXPECT_EQ(s, wire_stream.str());
+ }
+ }
+
+ // some parse errors
+ try {
+ Element::fromJSON("{1}");
+ } catch (const isc::data::JSONError& pe) {
+ std::string s = std::string(pe.what());
+ EXPECT_EQ("String expected in <string>:1:3", s);
+ }
+
+ sv.clear();
+ sv.push_back("{1}");
+ //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
+ //std::cout << ep << std::endl;
+ sv.push_back("\n\nTrue");
+ sv.push_back("\n\ntru");
+ sv.push_back("{ \n \"aaa\nbbb\"err:");
+ sv.push_back("{ \t\n \"aaa\nbbb\"\t\n\n:\n true, \"\\\"");
+ sv.push_back("{ \"a\": None}");
+ sv.push_back("");
+ sv.push_back("NULL");
+ sv.push_back("nul");
+ sv.push_back("hello\"foobar\"");
+ sv.push_back("\"foobar\"hello");
+ sv.push_back("[]hello");
+ sv.push_back("{}hello");
+ // String not delimited correctly
+ sv.push_back("\"hello");
+ sv.push_back("hello\"");
+ // Bad unicode
+ sv.push_back("\"\\u123\"");
+ sv.push_back("\"\\u1234\"");
+ sv.push_back("\"\\u0123\"");
+ sv.push_back("\"\\u00ag\"");
+ sv.push_back("\"\\u00BH\"");
+
+ BOOST_FOREACH(std::string s, sv) {
+ EXPECT_THROW(el = Element::fromJSON(s), isc::data::JSONError);
+ }
+
+ // some json specific format tests, here the str() output is
+ // different from the string input
+ // +100 is incorrect according to the ECMA 404 JSON standard.
+ // Keeping it as it will be reversed.
+ // EXPECT_EQ("100", Element::fromJSON("+100")->str());
+ EXPECT_EQ("100.0", Element::fromJSON("1e2")->str());
+ EXPECT_EQ("100.0", Element::fromJSON("+1e2")->str());
+ EXPECT_EQ("-100.0", Element::fromJSON("-1e2")->str());
+
+ EXPECT_NO_THROW({
+ EXPECT_EQ("9223372036854775807", Element::fromJSON("9223372036854775807")->str());
+ });
+ EXPECT_NO_THROW({
+ EXPECT_EQ("-9223372036854775808", Element::fromJSON("-9223372036854775808")->str());
+ });
+ EXPECT_THROW({
+ EXPECT_NE("9223372036854775808", Element::fromJSON("9223372036854775808")->str());
+ }, JSONError);
+
+ EXPECT_EQ("0.01", Element::fromJSON("1e-2")->str());
+ EXPECT_EQ("0.01", Element::fromJSON(".01")->str());
+ EXPECT_EQ("-0.01", Element::fromJSON("-1e-2")->str());
+ EXPECT_EQ("1.2", Element::fromJSON("1.2")->str());
+ EXPECT_EQ("1.0", Element::fromJSON("1.0")->str());
+ EXPECT_EQ("120.0", Element::fromJSON("1.2e2")->str());
+ EXPECT_EQ("100.0", Element::fromJSON("1.0e2")->str());
+ EXPECT_EQ("100.0", Element::fromJSON("1.0E2")->str());
+ EXPECT_EQ("0.01", Element::fromJSON("1.0e-2")->str());
+ EXPECT_EQ("0.012", Element::fromJSON("1.2e-2")->str());
+ EXPECT_EQ("0.012", Element::fromJSON("1.2E-2")->str());
+ EXPECT_EQ("\"\"", Element::fromJSON(" \n \t \r \f \b \"\" \n \f \t \r \b")->str());
+ EXPECT_EQ("{ }", Element::fromJSON("{ \n \r \t \b \f }")->str());
+ EXPECT_EQ("[ ]", Element::fromJSON("[ \n \r \f \t \b ]")->str());
+
+ // number overflows
+ EXPECT_THROW(Element::fromJSON("12345678901234567890")->str(), JSONError);
+ EXPECT_THROW(Element::fromJSON("1.1e12345678901234567890")->str(), JSONError);
+ EXPECT_THROW(Element::fromJSON("-1.1e12345678901234567890")->str(), JSONError);
+ EXPECT_THROW(Element::fromJSON("1e12345678901234567890")->str(), JSONError);
+ EXPECT_THROW(Element::fromJSON("1e50000")->str(), JSONError);
+ // number underflow
+ // EXPECT_THROW(Element::fromJSON("1.1e-12345678901234567890")->str(), JSONError);
+
+}
+
+template <typename T>
+void
+testGetValueInt() {
+ T el;
+ int64_t i;
+ int32_t i32;
+ uint32_t ui32;
+ long l;
+ long long ll;
+ double d;
+ bool b;
+ std::string s;
+ std::vector<ElementPtr> v;
+ std::map<std::string, ConstElementPtr> m;
+
+ el = Element::create(1);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(1, el->intValue());
+ });
+ EXPECT_THROW(el->doubleValue(), TypeError);
+ EXPECT_THROW(el->boolValue(), TypeError);
+ EXPECT_THROW(el->stringValue(), TypeError);
+ EXPECT_THROW(el->listValue(), TypeError);
+ EXPECT_THROW(el->mapValue(), TypeError);
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_FALSE(el->getValue(d));
+ EXPECT_FALSE(el->getValue(b));
+ EXPECT_FALSE(el->getValue(s));
+ EXPECT_FALSE(el->getValue(v));
+ EXPECT_FALSE(el->getValue(m));
+ EXPECT_EQ(1, i);
+
+ el = Element::create(9223372036854775807LL);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(9223372036854775807LL, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(9223372036854775807LL, i);
+
+ ll = 9223372036854775807LL;
+ el = Element::create(ll);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(ll, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(ll, i);
+
+ i32 = 2147483647L;
+ el = Element::create(i32);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(i32, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(i32, i);
+
+ ui32 = 4294967295L;
+ el = Element::create(ui32);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(ui32, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(ui32, i);
+
+ l = 2147483647L;
+ el = Element::create(l);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(l, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(l, i);
+}
+
+template <typename T>
+void
+testGetValueDouble() {
+ T el;
+ int64_t i;
+ double d;
+ bool b;
+ std::string s;
+ std::vector<ElementPtr> v;
+ std::map<std::string, ConstElementPtr> m;
+
+ el = Element::create(1.1);
+ EXPECT_THROW(el->intValue(), TypeError);
+ EXPECT_NO_THROW(el->doubleValue());
+ EXPECT_THROW(el->boolValue(), TypeError);
+ EXPECT_THROW(el->stringValue(), TypeError);
+ EXPECT_THROW(el->listValue(), TypeError);
+ EXPECT_THROW(el->mapValue(), TypeError);
+ EXPECT_FALSE(el->getValue(i));
+ EXPECT_TRUE(el->getValue(d));
+ EXPECT_FALSE(el->getValue(b));
+ EXPECT_FALSE(el->getValue(s));
+ EXPECT_FALSE(el->getValue(v));
+ EXPECT_FALSE(el->getValue(m));
+ EXPECT_EQ(1.1, d);
+}
+
+template <typename T>
+void
+testGetValueBool() {
+ T el;
+ int64_t i;
+ double d;
+ bool b;
+ std::string s;
+ std::vector<ElementPtr> v;
+ std::map<std::string, ConstElementPtr> m;
+
+ el = Element::create(true);
+ EXPECT_THROW(el->intValue(), TypeError);
+ EXPECT_THROW(el->doubleValue(), TypeError);
+ EXPECT_NO_THROW(el->boolValue());
+ EXPECT_THROW(el->stringValue(), TypeError);
+ EXPECT_THROW(el->listValue(), TypeError);
+ EXPECT_THROW(el->mapValue(), TypeError);
+ EXPECT_FALSE(el->getValue(i));
+ EXPECT_FALSE(el->getValue(d));
+ EXPECT_TRUE(el->getValue(b));
+ EXPECT_FALSE(el->getValue(s));
+ EXPECT_FALSE(el->getValue(v));
+ EXPECT_FALSE(el->getValue(m));
+ EXPECT_EQ(true, b);
+}
+
+template <typename T>
+void
+testGetValueString() {
+ T el;
+ int64_t i;
+ double d;
+ bool b;
+ std::string s;
+ std::vector<ElementPtr> v;
+ std::map<std::string, ConstElementPtr> m;
+
+ el = Element::create("foo");
+ EXPECT_THROW(el->intValue(), TypeError);
+ EXPECT_THROW(el->doubleValue(), TypeError);
+ EXPECT_THROW(el->boolValue(), TypeError);
+ EXPECT_NO_THROW(el->stringValue());
+ EXPECT_THROW(el->listValue(), TypeError);
+ EXPECT_THROW(el->mapValue(), TypeError);
+ EXPECT_FALSE(el->getValue(i));
+ EXPECT_FALSE(el->getValue(d));
+ EXPECT_FALSE(el->getValue(b));
+ EXPECT_TRUE(el->getValue(s));
+ EXPECT_FALSE(el->getValue(v));
+ EXPECT_FALSE(el->getValue(m));
+ EXPECT_EQ("foo", s);
+}
+
+template <typename T>
+void
+testGetValueList() {
+ T el;
+ int64_t i;
+ double d;
+ bool b;
+ std::string s;
+ std::vector<ElementPtr> v;
+ std::map<std::string, ConstElementPtr> m;
+
+ el = Element::createList();
+ EXPECT_THROW(el->intValue(), TypeError);
+ EXPECT_THROW(el->doubleValue(), TypeError);
+ EXPECT_THROW(el->boolValue(), TypeError);
+ EXPECT_THROW(el->stringValue(), TypeError);
+ EXPECT_NO_THROW(el->listValue());
+ EXPECT_THROW(el->mapValue(), TypeError);
+ EXPECT_FALSE(el->getValue(i));
+ EXPECT_FALSE(el->getValue(d));
+ EXPECT_FALSE(el->getValue(b));
+ EXPECT_FALSE(el->getValue(s));
+ EXPECT_TRUE(el->getValue(v));
+ EXPECT_FALSE(el->getValue(m));
+ EXPECT_EQ("[ ]", el->str());
+}
+
+template <typename T>
+void
+testGetValueMap() {
+ T el;
+ int64_t i;
+ double d;
+ bool b;
+ std::string s;
+ std::vector<ElementPtr> v;
+ std::map<std::string, ConstElementPtr> m;
+
+ el = Element::createMap();
+ EXPECT_THROW(el->intValue(), TypeError);
+ EXPECT_THROW(el->doubleValue(), TypeError);
+ EXPECT_THROW(el->boolValue(), TypeError);
+ EXPECT_THROW(el->stringValue(), TypeError);
+ EXPECT_THROW(el->listValue(), TypeError);
+ EXPECT_NO_THROW(el->mapValue());
+ EXPECT_FALSE(el->getValue(i));
+ EXPECT_FALSE(el->getValue(d));
+ EXPECT_FALSE(el->getValue(b));
+ EXPECT_FALSE(el->getValue(s));
+ EXPECT_FALSE(el->getValue(v));
+ EXPECT_TRUE(el->getValue(m));
+ EXPECT_EQ("{ }", el->str());
+}
+
+TEST(Element, create_and_value_throws) {
+ // this test checks whether elements throw exceptions if the
+ // incorrect type is requested
+ ElementPtr el;
+ ConstElementPtr cel;
+ int64_t i = 0;
+ double d = 0.0;
+ bool b = false;
+ std::string s("asdf");
+ std::vector<ElementPtr> v;
+ std::map<std::string, ConstElementPtr> m;
+ ConstElementPtr tmp;
+
+ testGetValueInt<ElementPtr>();
+ testGetValueInt<ConstElementPtr>();
+
+ el = Element::create(1);
+ i = 2;
+ EXPECT_TRUE(el->setValue(i));
+ EXPECT_EQ(2, el->intValue());
+ EXPECT_FALSE(el->setValue(d));
+ EXPECT_FALSE(el->setValue(b));
+ EXPECT_FALSE(el->setValue(s));
+ EXPECT_FALSE(el->setValue(v));
+ EXPECT_FALSE(el->setValue(m));
+ EXPECT_THROW(el->get(1), TypeError);
+ EXPECT_THROW(el->set(1, el), TypeError);
+ EXPECT_THROW(el->add(el), TypeError);
+ EXPECT_THROW(el->remove(1), TypeError);
+ EXPECT_THROW(el->size(), TypeError);
+ EXPECT_THROW(el->empty(), TypeError);
+ EXPECT_THROW(el->get("foo"), TypeError);
+ EXPECT_THROW(el->set("foo", el), TypeError);
+ EXPECT_THROW(el->remove("foo"), TypeError);
+ EXPECT_THROW(el->contains("foo"), TypeError);
+ EXPECT_FALSE(el->find("foo", tmp));
+
+ testGetValueDouble<ElementPtr>();
+ testGetValueDouble<ConstElementPtr>();
+
+ el = Element::create(1.1);
+ d = 2.2;
+ EXPECT_TRUE(el->setValue(d));
+ EXPECT_EQ(2.2, el->doubleValue());
+ EXPECT_FALSE(el->setValue(i));
+ EXPECT_FALSE(el->setValue(b));
+ EXPECT_FALSE(el->setValue(s));
+ EXPECT_FALSE(el->setValue(v));
+ EXPECT_FALSE(el->setValue(m));
+ EXPECT_THROW(el->get(1), TypeError);
+ EXPECT_THROW(el->set(1, el), TypeError);
+ EXPECT_THROW(el->add(el), TypeError);
+ EXPECT_THROW(el->remove(1), TypeError);
+ EXPECT_THROW(el->size(), TypeError);
+ EXPECT_THROW(el->empty(), TypeError);
+ EXPECT_THROW(el->get("foo"), TypeError);
+ EXPECT_THROW(el->set("foo", el), TypeError);
+ EXPECT_THROW(el->remove("foo"), TypeError);
+ EXPECT_THROW(el->contains("foo"), TypeError);
+ EXPECT_FALSE(el->find("foo", tmp));
+
+ testGetValueBool<ElementPtr>();
+ testGetValueBool<ConstElementPtr>();
+
+ el = Element::create(true);
+ b = false;
+ EXPECT_TRUE(el->setValue(b));
+ EXPECT_FALSE(el->boolValue());
+ EXPECT_FALSE(el->setValue(i));
+ EXPECT_FALSE(el->setValue(d));
+ EXPECT_FALSE(el->setValue(s));
+ EXPECT_FALSE(el->setValue(v));
+ EXPECT_FALSE(el->setValue(m));
+ EXPECT_THROW(el->get(1), TypeError);
+ EXPECT_THROW(el->set(1, el), TypeError);
+ EXPECT_THROW(el->add(el), TypeError);
+ EXPECT_THROW(el->remove(1), TypeError);
+ EXPECT_THROW(el->size(), TypeError);
+ EXPECT_THROW(el->empty(), TypeError);
+ EXPECT_THROW(el->get("foo"), TypeError);
+ EXPECT_THROW(el->set("foo", el), TypeError);
+ EXPECT_THROW(el->remove("foo"), TypeError);
+ EXPECT_THROW(el->contains("foo"), TypeError);
+ EXPECT_FALSE(el->find("foo", tmp));
+
+ testGetValueString<ElementPtr>();
+ testGetValueString<ConstElementPtr>();
+
+ el = Element::create("foo");
+ s = "bar";
+ EXPECT_TRUE(el->setValue(s));
+ EXPECT_EQ("bar", el->stringValue());
+ EXPECT_FALSE(el->setValue(i));
+ EXPECT_FALSE(el->setValue(b));
+ EXPECT_FALSE(el->setValue(d));
+ EXPECT_FALSE(el->setValue(v));
+ EXPECT_FALSE(el->setValue(m));
+ EXPECT_THROW(el->get(1), TypeError);
+ EXPECT_THROW(el->set(1, el), TypeError);
+ EXPECT_THROW(el->add(el), TypeError);
+ EXPECT_THROW(el->remove(1), TypeError);
+ EXPECT_THROW(el->size(), TypeError);
+ EXPECT_THROW(el->empty(), TypeError);
+ EXPECT_THROW(el->get("foo"), TypeError);
+ EXPECT_THROW(el->set("foo", el), TypeError);
+ EXPECT_THROW(el->remove("foo"), TypeError);
+ EXPECT_THROW(el->contains("foo"), TypeError);
+ EXPECT_FALSE(el->find("foo", tmp));
+
+ testGetValueList<ElementPtr>();
+ testGetValueList<ConstElementPtr>();
+
+ el = Element::createList();
+ EXPECT_TRUE(el->empty());
+ v.push_back(Element::create(1));
+ EXPECT_TRUE(el->setValue(v));
+ EXPECT_FALSE(el->empty());
+ EXPECT_EQ("[ 1 ]", el->str());
+
+ testGetValueMap<ElementPtr>();
+ testGetValueMap<ConstElementPtr>();
+
+ el = Element::createMap();
+ EXPECT_NO_THROW(el->set("foo", Element::create("bar")));
+ EXPECT_EQ("{ \"foo\": \"bar\" }", el->str());
+}
+
+// Helper for escape check; it puts the given string in a StringElement,
+// then checks for the following conditions:
+// stringValue() must be same as input
+// toJSON() output must be escaped
+// fromJSON() on the previous output must result in original input
+void
+escapeHelper(const std::string& input, const std::string& expected) {
+ StringElement str_element = StringElement(input);
+ EXPECT_EQ(input, str_element.stringValue());
+ std::stringstream os;
+ str_element.toJSON(os);
+ EXPECT_EQ(expected, os.str());
+ ElementPtr str_element2 = Element::fromJSON(os.str());
+ EXPECT_EQ(str_element.stringValue(), str_element2->stringValue());
+}
+
+TEST(Element, escape) {
+ // Test whether quotes are escaped correctly when creating direct
+ // String elements.
+ escapeHelper("foo\"bar", "\"foo\\\"bar\"");
+ escapeHelper("foo\\bar", "\"foo\\\\bar\"");
+ escapeHelper("foo\bbar", "\"foo\\bbar\"");
+ escapeHelper("foo\fbar", "\"foo\\fbar\"");
+ escapeHelper("foo\nbar", "\"foo\\nbar\"");
+ escapeHelper("foo\rbar", "\"foo\\rbar\"");
+ escapeHelper("foo\tbar", "\"foo\\tbar\"");
+ escapeHelper("foo\u001fbar", "\"foo\\u001fbar\"");
+ // Bad escapes
+ EXPECT_THROW(Element::fromJSON("\\a"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\"), JSONError);
+ // Can't have escaped quotes outside strings
+ EXPECT_THROW(Element::fromJSON("\\\"\\\""), JSONError);
+ // Unicode use lower u and 4 hexa, only 00 prefix is supported
+ EXPECT_THROW(Element::fromJSON("\\U0020"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\u002"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\u0123"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\u1023"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\u00ag"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\u00ga"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\u00BH"), JSONError);
+ EXPECT_THROW(Element::fromJSON("\\u00HB"), JSONError);
+ // Inside strings is OK
+ EXPECT_NO_THROW(Element::fromJSON("\"\\\"\\\"\""));
+ // A whitespace test
+ EXPECT_NO_THROW(Element::fromJSON("\" \n \r \t \f \n \n \t\""));
+ // Escape for forward slash is optional
+ ASSERT_NO_THROW(Element::fromJSON("\"foo\\/bar\""));
+ EXPECT_EQ("foo/bar", Element::fromJSON("\"foo\\/bar\"")->stringValue());
+ // Control characters
+ StringElement bell("foo\abar");
+ EXPECT_EQ("\"foo\\u0007bar\"", bell.str());
+ // 8 bit escape
+ StringElement ab("foo\253bar");
+ EXPECT_EQ("\"foo\\u00abbar\"", ab.str());
+ ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00abbar\""));
+ EXPECT_TRUE(ab.equals(*Element::fromJSON("\"foo\\u00abbar\"")));
+ ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00ABbar\""));
+ EXPECT_TRUE(ab.equals(*Element::fromJSON("\"foo\\u00ABbar\"")));
+ StringElement f1("foo\361bar");
+ EXPECT_EQ("\"foo\\u00f1bar\"", f1.str());
+ ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00f1bar\""));
+ EXPECT_TRUE(f1.equals(*Element::fromJSON("\"foo\\u00f1bar\"")));
+ ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00F1bar\""));
+ EXPECT_TRUE(f1.equals(*Element::fromJSON("\"foo\\u00F1bar\"")));
+}
+
+// This test verifies that strings are copied.
+TEST(Element, stringCopy) {
+ // StringElement constructor copies its string argument.
+ std::string foo = "foo";
+ ElementPtr elem = ElementPtr(new StringElement(foo));
+ EXPECT_EQ(foo, elem->stringValue());
+ foo[1] = 'O';
+ EXPECT_EQ("fOo", foo);
+ EXPECT_NE(foo, elem->stringValue());
+
+ // Map keys are copied too.
+ ElementPtr map = ElementPtr(new MapElement());
+ std::string bar = "bar";
+ map->set(bar, ElementPtr(new IntElement(1)));
+ ConstElementPtr item = map->get("bar");
+ ASSERT_TRUE(item);
+ EXPECT_EQ(1, item->intValue());
+ bar[0] = 'B';
+ EXPECT_EQ("Bar", bar);
+ EXPECT_TRUE(map->get("bar"));
+ EXPECT_FALSE(map->get(bar));
+}
+
+// This test verifies that a backslash can be used in element content
+// when the element is created using constructor.
+TEST(Element, backslash1) {
+ string input = "SMSBoot\\x64";// One slash passed to elem constructor...
+ string exp = "SMSBoot\\x64"; // ... should result in one slash in the actual option.
+
+ StringElement elem(input);
+ EXPECT_EQ(exp, elem.stringValue());
+}
+
+// This test verifies that a backslash can be used in element content
+// when the element is created using fromJSON.
+TEST(Element, backslash2) {
+ string input = "\"SMSBoot\\\\x64\""; // Two slashes put in the config file...
+ string exp = "SMSBoot\\x64"; // ... should result in one slash in the actual option.
+
+ ElementPtr elem = Element::fromJSON(input);
+ EXPECT_EQ(exp, elem->stringValue());
+}
+
+TEST(Element, ListElement) {
+ // this function checks the specific functions for ListElements
+ ElementPtr el = Element::fromJSON("[ 1, \"bar\", 3 ]");
+ EXPECT_EQ(el->get(0)->intValue(), 1);
+ EXPECT_EQ(el->get(1)->stringValue(), "bar");
+ EXPECT_EQ(el->get(2)->intValue(), 3);
+
+ el->set(0, Element::fromJSON("\"foo\""));
+ EXPECT_EQ(el->get(0)->stringValue(), "foo");
+
+ el->add(Element::create(56176));
+ EXPECT_EQ(el->get(3)->intValue(), 56176);
+
+ el->remove(1);
+ el->remove(1);
+ EXPECT_EQ(el->str(), "[ \"foo\", 56176 ]");
+
+ // hmm, it errors on EXPECT_THROW(el->get(3), std::out_of_range)
+ EXPECT_ANY_THROW(el->get(3));
+
+ el->add(Element::create(32));
+ EXPECT_EQ(32, el->get(2)->intValue());
+
+ // boundary condition tests for set()
+ el->set(2, Element::create(0)); // update the last entry of the list
+ EXPECT_EQ(0, el->get(2)->intValue());
+ // attempt of set beyond the range of list should trigger an exception.
+ EXPECT_ANY_THROW(el->set(3, Element::create(0)));
+}
+
+TEST(Element, MapElement) {
+ // this function checks the specific functions for ListElements
+ ElementPtr el = Element::fromJSON("{ \"name\": \"foo\", \"value1\": \"bar\", \"value2\": { \"number\": 42 } }");
+ ConstElementPtr el2;
+
+ EXPECT_EQ(el->get("name")->stringValue(), "foo");
+ EXPECT_EQ(el->get("value2")->getType(), Element::map);
+
+ EXPECT_TRUE(isNull(el->get("value3")));
+
+ EXPECT_FALSE(el->empty());
+
+ el->set("value3", Element::create(56176));
+ EXPECT_EQ(el->get("value3")->intValue(), 56176);
+
+ el->remove("value3");
+ EXPECT_TRUE(isNull(el->get("value3")));
+
+ EXPECT_EQ(el->find("value2/number")->intValue(), 42);
+ EXPECT_TRUE(isNull(el->find("value2/nothing/")));
+
+ EXPECT_EQ(el->find("value1")->stringValue(), "bar");
+ EXPECT_EQ(el->find("value1/")->stringValue(), "bar");
+
+ EXPECT_TRUE(el->find("value1", el2));
+ EXPECT_EQ("bar", el2->stringValue());
+ EXPECT_FALSE(el->find("name/error", el2));
+
+ // A map element whose (only) element has the maximum length of tag.
+ string long_maptag("0123456789abcdef1123456789abcdef2123456789abcdef"
+ "3123456789abcdef4123456789abcdef5123456789abcdef"
+ "6123456789abcdef7123456789abcdef8123456789abcdef"
+ "9123456789abcdefa123456789abcdefb123456789abcdef"
+ "c123456789abcdefd123456789abcdefe123456789abcdef"
+ "f123456789abcde");
+
+ EXPECT_EQ(255, long_maptag.length()); // check prerequisite
+ el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}");
+ EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
+
+ el = Element::createMap();
+ el->set(long_maptag, Element::create("bar"));
+ EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
+
+ // A one-byte longer tag should still be allowed
+ long_maptag.push_back('f');
+ el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}");
+ el->set(long_maptag, Element::create("bar"));
+ EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
+
+ // Null pointer value
+ el.reset(new MapElement());
+ ConstElementPtr null_ptr;
+ el->set("value", null_ptr);
+ EXPECT_FALSE(el->get("value"));
+ EXPECT_EQ("{ \"value\": None }", el->str());
+}
+
+TEST(Element, to_and_from_wire) {
+ // Wire format is now plain JSON.
+ EXPECT_EQ("1", Element::create(1)->toWire());
+ EXPECT_EQ("1.1", Element::create(1.1)->toWire());
+ EXPECT_EQ("true", Element::create(true)->toWire());
+ EXPECT_EQ("false", Element::create(false)->toWire());
+ EXPECT_EQ("null", Element::create()->toWire());
+ EXPECT_EQ("\"a string\"", Element::create("a string")->toWire());
+ EXPECT_EQ("[ \"a\", \"list\" ]", Element::fromJSON("[ \"a\", \"list\" ]")->toWire());
+ EXPECT_EQ("{ \"a\": \"map\" }", Element::fromJSON("{ \"a\": \"map\" }")->toWire());
+
+ EXPECT_EQ("1", Element::fromWire("1")->str());
+
+ std::stringstream ss;
+ ss << "1";
+ EXPECT_EQ("1", Element::fromWire(ss, 1)->str());
+
+ // Some malformed JSON input
+ EXPECT_THROW(Element::fromJSON("{ "), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\" "), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\": "), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\": \"b\""), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\": {"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\": {}"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\": []"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\": [ }"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{\":"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("]"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("[ 1, 2, }"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("[ 1, 2, {}"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("[ 1, 2, { ]"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("[ "), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{{}}"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{[]}"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("{ \"a\", \"b\" }"), isc::data::JSONError);
+ EXPECT_THROW(Element::fromJSON("[ \"a\": \"b\" ]"), isc::data::JSONError);
+}
+
+ConstElementPtr
+efs(const std::string& str) {
+ return (Element::fromJSON(str));
+}
+
+TEST(Element, equals) {
+ EXPECT_EQ(*efs("1"), *efs("1"));
+ EXPECT_NE(*efs("1"), *efs("2"));
+ EXPECT_NE(*efs("1"), *efs("\"1\""));
+ EXPECT_NE(*efs("1"), *efs("[]"));
+ EXPECT_NE(*efs("1"), *efs("true"));
+ EXPECT_NE(*efs("1"), *efs("{}"));
+ EXPECT_EQ(*efs("1.1"), *efs("1.1"));
+ EXPECT_NE(*efs("1.0"), *efs("1"));
+ EXPECT_NE(*efs("1.1"), *efs("\"1\""));
+ EXPECT_NE(*efs("1.1"), *efs("[]"));
+ EXPECT_NE(*efs("1.1"), *efs("true"));
+ EXPECT_NE(*efs("1.1"), *efs("{}"));
+
+ EXPECT_EQ(*efs("true"), *efs("true"));
+ EXPECT_NE(*efs("true"), *efs("false"));
+ EXPECT_NE(*efs("true"), *efs("1"));
+ EXPECT_NE(*efs("true"), *efs("\"1\""));
+ EXPECT_NE(*efs("true"), *efs("[]"));
+ EXPECT_NE(*efs("true"), *efs("{}"));
+
+ EXPECT_EQ(*efs("\"foo\""), *efs("\"foo\""));
+ EXPECT_NE(*efs("\"foo\""), *efs("\"bar\""));
+ EXPECT_NE(*efs("\"foo\""), *efs("1"));
+ EXPECT_NE(*efs("\"foo\""), *efs("\"1\""));
+ EXPECT_NE(*efs("\"foo\""), *efs("true"));
+ EXPECT_NE(*efs("\"foo\""), *efs("[]"));
+ EXPECT_NE(*efs("\"foo\""), *efs("{}"));
+
+ EXPECT_EQ(*efs("[]"), *efs("[]"));
+ EXPECT_EQ(*efs("[ 1, 2, 3 ]"), *efs("[ 1, 2, 3 ]"));
+ EXPECT_EQ(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 1], 2.2 ]"));
+ EXPECT_NE(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 2], 2.2 ]"));
+ EXPECT_NE(*efs("[]"), *efs("[1]"));
+ EXPECT_NE(*efs("[]"), *efs("1"));
+ EXPECT_NE(*efs("[]"), *efs("\"1\""));
+ EXPECT_NE(*efs("[]"), *efs("{}"));
+
+ EXPECT_EQ(*efs("{}"), *efs("{}"));
+ EXPECT_EQ(*efs("{ \"foo\": \"bar\" }"), *efs("{ \"foo\": \"bar\" }"));
+ EXPECT_EQ(*efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }"), *efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }"));
+ EXPECT_NE(*efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }"), *efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar2\" } }"));
+ EXPECT_NE(*efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }"), *efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\", 1 ], \"item3\": { \"foo\": \"bar\" } }"));
+ EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("1"));
+ EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("\"1\""));
+ EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("[]"));
+ EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("{}"));
+ EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("{ \"something\": \"different\" }"));
+
+ EXPECT_EQ(*efs("null"), *Element::create());
+}
+
+TEST(Element, removeIdentical) {
+ ElementPtr a = Element::createMap();
+ ConstElementPtr b = Element::createMap();
+ ConstElementPtr c = Element::createMap();
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1 }");
+ b = Element::fromJSON("{ \"a\": 1 }");
+ c = Element::createMap();
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ b = Element::createMap();
+ c = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ c = Element::createMap();
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 3 ] }");
+ c = Element::fromJSON("{ \"b\": [ 1, 2 ] }");
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::createMap();
+ c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ c = Element::createMap();
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
+ c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
+ b = Element::fromJSON("{ \"c\": 3, \"b\": 2 }");
+ c = Element::fromJSON("{ \"a\": 1 }");
+ removeIdentical(a, b);
+ EXPECT_EQ(*a, *c);
+
+ EXPECT_THROW(removeIdentical(Element::create(1), Element::create(2)), TypeError);
+}
+
+TEST(Element, constRemoveIdentical) {
+ ConstElementPtr a = Element::createMap();
+ ConstElementPtr b = Element::createMap();
+ ConstElementPtr c = Element::createMap();
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": 1 }");
+ b = Element::fromJSON("{ \"a\": 1 }");
+ c = Element::createMap();
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ b = Element::createMap();
+ c = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ c = Element::createMap();
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }");
+ b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 3 ] }");
+ c = Element::fromJSON("{ \"b\": [ 1, 2 ] }");
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::createMap();
+ c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ c = Element::createMap();
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
+ c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
+ b = Element::fromJSON("{ \"c\": 3, \"b\": 2 }");
+ c = Element::fromJSON("{ \"a\": 1 }");
+ EXPECT_EQ(*removeIdentical(a, b), *c);
+
+ // removeIdentical() is overloaded so force the first argument to const
+ ConstElementPtr bad = Element::create(1);
+ EXPECT_THROW(removeIdentical(bad, Element::create(2)), TypeError);
+}
+
+TEST(Element, merge) {
+ ElementPtr a = Element::createMap();
+ ElementPtr b = Element::createMap();
+ ConstElementPtr c = Element::createMap();
+ merge(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("1");
+ b = Element::createMap();
+ EXPECT_THROW(merge(a, b), TypeError);
+
+ a = Element::createMap();
+ b = Element::fromJSON("{ \"a\": 1 }");
+ c = Element::fromJSON("{ \"a\": 1 }");
+ merge(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::createMap();
+ b = Element::fromJSON("{ \"a\": 1 }");
+ c = Element::fromJSON("{ \"a\": 1 }");
+ merge(b, a);
+ EXPECT_EQ(*b, *c);
+
+ a = Element::fromJSON("{ \"a\": 1 }");
+ b = Element::fromJSON("{ \"a\": 2 }");
+ c = Element::fromJSON("{ \"a\": 2 }");
+ merge(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1 }");
+ b = Element::fromJSON("{ \"a\": 2 }");
+ c = Element::fromJSON("{ \"a\": 1 }");
+ merge(b, a);
+ EXPECT_EQ(*b, *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
+ c = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
+ merge(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }");
+ c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ merge(b, a);
+ EXPECT_EQ(*b, *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": null }");
+ c = Element::fromJSON("{ }");
+ merge(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ b = Element::fromJSON("{ \"a\": null }");
+ c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
+ merge(b, a);
+ EXPECT_EQ(*b, *c);
+
+ // And some tests with multiple values
+ a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
+ b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");
+ c = Element::fromJSON("{ \"a\": 1, \"c\": \"a string\" }");
+ merge(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
+ b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");
+ c = Element::fromJSON("{ \"a\": 1, \"b\": true }");
+ merge(b, a);
+ EXPECT_EQ(*b, *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
+ b = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }");
+ c = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }");
+ merge(a, b);
+ EXPECT_EQ(*a, *c);
+
+ a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
+ b = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }");
+ c = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }");
+ merge(b, a);
+ EXPECT_EQ(*b, *c);
+
+}
+
+// This test checks copy.
+TEST(Element, copy) {
+ // Null pointer
+ ElementPtr elem;
+ EXPECT_THROW(copy(elem, 0), isc::BadValue);
+ EXPECT_THROW(copy(elem), isc::BadValue);
+ EXPECT_THROW(copy(elem, -1), isc::BadValue);
+
+ // Basic types
+ elem.reset(new IntElement(1));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("1")));
+ EXPECT_EQ("1", elem->str());
+ ElementPtr copied;
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new DoubleElement(1.0));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("1.0")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new BoolElement(true));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("true")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new NullElement());
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("null")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+
+ elem.reset(new StringElement("foo"));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("\"foo\"")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+ ASSERT_NO_THROW(elem->setValue(std::string("bar")));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("\"bar\"")));
+ EXPECT_FALSE(elem->equals(*copied));
+
+ elem.reset(new ListElement());
+ ElementPtr item = ElementPtr(new IntElement(1));
+ elem->add(item);
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 1 ]")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+ ElementPtr deep;
+ ASSERT_NO_THROW(deep = copy(elem));
+ EXPECT_TRUE(elem->equals(*deep));
+ ASSERT_NO_THROW(item = elem->getNonConst(0));
+ ASSERT_NO_THROW(item->setValue(2));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 2 ]")));
+ EXPECT_TRUE(elem->equals(*copied));
+ EXPECT_FALSE(elem->equals(*deep));
+
+ elem.reset(new MapElement());
+ item.reset(new StringElement("bar"));
+ elem->set("foo", item);
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"bar\" }")));
+ ASSERT_NO_THROW(copied = copy(elem, 0));
+ EXPECT_TRUE(elem->equals(*copied));
+ ASSERT_NO_THROW(deep = copy(elem));
+ EXPECT_TRUE(elem->equals(*deep));
+ ASSERT_NO_THROW(item->setValue(std::string("Bar")));
+ EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"Bar\" }")));
+ EXPECT_TRUE(elem->equals(*copied));
+ EXPECT_FALSE(elem->equals(*deep));
+
+ // Complex example
+ std::string input = "{ \n"
+ "\"integer\": 1,\n"
+ "\"double\": 1.0,\n"
+ "\"boolean\": true,\n"
+ "\"null\": null,\n"
+ "\"string\": \"foobar\",\n"
+ "\"list\": [ 1, 2 ],\n"
+ "\"map\": { \"foo\": \"bar\" } }\n";
+ ConstElementPtr complex;
+ ASSERT_NO_THROW(complex = Element::fromJSON(input));
+ ASSERT_NO_THROW(copied = copy(complex, 0));
+ EXPECT_TRUE(copied->equals(*complex));
+ ASSERT_NO_THROW(deep = copy(complex));
+ EXPECT_TRUE(deep->equals(*complex));
+ ElementPtr shallow;
+ ASSERT_NO_THROW(shallow = copy(complex, 1));
+ EXPECT_TRUE(shallow->equals(*complex));
+ // Try to modify copies
+ ASSERT_NO_THROW(item = deep->get("list")->getNonConst(1));
+ ASSERT_NO_THROW(item->setValue(3));
+ EXPECT_FALSE(deep->equals(*complex));
+ EXPECT_TRUE(shallow->equals(*complex));
+ ASSERT_NO_THROW(item = boost::const_pointer_cast<Element>(shallow->get("string")));
+ ASSERT_NO_THROW(item->setValue(std::string("FooBar")));
+ EXPECT_FALSE(shallow->equals(*complex));
+ EXPECT_TRUE(copied->equals(*complex));
+}
+
+// This test checks the isEquivalent function.
+TEST(Element, isEquivalent) {
+ // All are different but a is equivalent to b
+ string texta = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 1, 2 ] }";
+ string textb = "{ \"b\": [ ], \"a\": 1, \"c\": [ 1, 2, 1 ] }";
+ string textc = "{ \"a\": 2, \"b\": [ ], \"c\": [ 1, 1, 2 ] }";
+ string textd = "{ \"a\": 1, \"c\": [ ], \"b\": [ 1, 1, 2 ] }";
+ string texte = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 2, 2 ] }";
+
+ ElementPtr a = Element::fromJSON(texta);
+ ElementPtr b = Element::fromJSON(textb);
+ ElementPtr c = Element::fromJSON(textc);
+ ElementPtr d = Element::fromJSON(textd);
+ ElementPtr e = Element::fromJSON(texte);
+
+ EXPECT_TRUE(isEquivalent(a, b));
+ EXPECT_NE(a, b);
+ EXPECT_FALSE(isEquivalent(a, c));
+ EXPECT_FALSE(isEquivalent(a, d));
+ EXPECT_FALSE(isEquivalent(a, e));
+
+ // Verifies isEquivalent handles cycles
+ if (isc::util::unittests::runningOnValgrind()) {
+ ElementPtr l = Element::createList();
+ l->add(l);
+ EXPECT_THROW(isEquivalent(l, l), isc::BadValue);
+ }
+}
+
+// This test checks the pretty print function.
+TEST(Element, prettyPrint) {
+
+ // default step is 2, order is alphabetic, no \n at the end
+ string text = "{\n"
+ " \"boolean\": true,\n"
+ " \"comment\": \"this is an exception\",\n"
+ " \"empty-list\": [ ],\n"
+ " \"empty-map\": { },\n"
+ " \"integer\": 1,\n"
+ " \"list\": [ 1, 2, 3 ],\n"
+ " \"map\": {\n"
+ " \"item\": null\n"
+ " },\n"
+ " \"string\": \"foobar\"\n"
+ "}";
+ ElementPtr json = Element::fromJSON(text);
+ string pprinted = prettyPrint(json);
+ EXPECT_EQ(text, pprinted);
+}
+
+// This test checks whether it is possible to ignore comments. It also checks
+// that the comments are ignored only when told to.
+TEST(Element, preprocessor) {
+
+ string no_comment = "{ \"a\": 1,\n"
+ " \"b\": 2}";
+
+ string head_comment = "# this is a comment, ignore me\n"
+ "{ \"a\": 1,\n"
+ " \"b\": 2}";
+
+ string mid_comment = "{ \"a\": 1,\n"
+ "# this is a comment, ignore me\n"
+ " \"b\": 2}";
+
+ string tail_comment = "{ \"a\": 1,\n"
+ " \"b\": 2}"
+ "# this is a comment, ignore me\n";
+
+ string dbl_head_comment = "# this is a comment, ignore me\n"
+ "# second line, still ignored\n"
+ "{ \"a\": 1,\n"
+ " \"b\": 2}";
+
+ string dbl_mid_comment = "{ \"a\": 1,\n"
+ "# this is a comment, ignore me\n"
+ "# second line, still ignored\n"
+ " \"b\": 2}";
+
+ string dbl_tail_comment = "{ \"a\": 1,\n"
+ " \"b\": 2}"
+ "# this is a comment, ignore me\n"
+ "# second line, still ignored\n";
+
+ // This is what we expect in all cases.
+ ElementPtr exp = Element::fromJSON(no_comment);
+
+ // Let's convert them all and see that the result it the same every time
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(head_comment, true)));
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(mid_comment, true)));
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(tail_comment, true)));
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_head_comment, true)));
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_mid_comment, true)));
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_tail_comment, true)));
+
+ // With preprocessing disabled, it should fail all around
+ EXPECT_THROW(Element::fromJSON(head_comment), JSONError);
+ EXPECT_THROW(Element::fromJSON(mid_comment), JSONError);
+ EXPECT_THROW(Element::fromJSON(tail_comment), JSONError);
+ EXPECT_THROW(Element::fromJSON(dbl_head_comment), JSONError);
+ EXPECT_THROW(Element::fromJSON(dbl_mid_comment), JSONError);
+ EXPECT_THROW(Element::fromJSON(dbl_tail_comment), JSONError);
+
+ // For coverage
+ std::istringstream iss(no_comment);
+ EXPECT_TRUE(exp->equals(*Element::fromJSON(iss, true)));
+}
+
+TEST(Element, getPosition) {
+ std::istringstream ss("{\n"
+ " \"a\": 2,\n"
+ " \"b\":true,\n"
+ " \"cy\": \"a string\",\n"
+ " \"dyz\": {\n"
+ "\n"
+ " \"e\": 3,\n"
+ " \"f\": null\n"
+ "\n"
+ " },\n"
+ " \"g\": [ 5, 6,\n"
+ " 7 ]\n"
+ "}\n");
+
+ // Create a JSON string holding different type of values. Some of the
+ // values in the config string are not aligned, so as we can check that
+ // the position is set correctly for the elements.
+ ElementPtr top = Element::fromJSON(ss, string("kea.conf"));
+ ASSERT_TRUE(top);
+
+ // Element "a"
+ ConstElementPtr level1_el = top->get("a");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(2, level1_el->getPosition().line_);
+ EXPECT_EQ(11, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // Element "b"
+ level1_el = top->get("b");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(3, level1_el->getPosition().line_);
+ EXPECT_EQ(9, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // Element "cy"
+ level1_el = top->get("cy");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(4, level1_el->getPosition().line_);
+ EXPECT_EQ(11, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // Element "dyz"
+ level1_el = top->get("dyz");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(5, level1_el->getPosition().line_);
+ EXPECT_EQ(13, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // Element "e" is a sub element of "dyz".
+ ConstElementPtr level2_el = level1_el->get("e");
+ ASSERT_TRUE(level2_el);
+ EXPECT_EQ(7, level2_el->getPosition().line_);
+ EXPECT_EQ(12, level2_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+
+ // Element "f" is also a sub element of "dyz"
+ level2_el = level1_el->get("f");
+ ASSERT_TRUE(level2_el);
+ EXPECT_EQ(8, level2_el->getPosition().line_);
+ EXPECT_EQ(14, level2_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+
+ // Element "g" is a list.
+ level1_el = top->get("g");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(11, level1_el->getPosition().line_);
+ // Position indicates where the values start (excluding the "[" character)"
+ EXPECT_EQ(11, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // First element from the list.
+ level2_el = level1_el->get(0);
+ ASSERT_TRUE(level2_el);
+ EXPECT_EQ(11, level2_el->getPosition().line_);
+ EXPECT_EQ(12, level2_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+
+ // Second element from the list.
+ level2_el = level1_el->get(1);
+ ASSERT_TRUE(level2_el);
+ EXPECT_EQ(11, level2_el->getPosition().line_);
+ EXPECT_EQ(15, level2_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+
+ // Third element from the list.
+ level2_el = level1_el->get(2);
+ ASSERT_TRUE(level2_el);
+ EXPECT_EQ(12, level2_el->getPosition().line_);
+ EXPECT_EQ(14, level2_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+
+}
+
+// Tests whether position is returned properly for a commented input JSON text.
+TEST(Element, getPositionCommented) {
+ std::istringstream ss("{\n"
+ " \"a\": 2,\n"
+ "# comment\n"
+ " \"cy\": \"a string\",\n"
+ " \"dyz\": {\n"
+ "# another comment\n"
+ " \"e\": 3,\n"
+ " \"f\": null\n"
+ "\n"
+ " } }\n");
+
+ // Create a JSON string holding different type of values. Some of the
+ // values in the config string are not aligned, so as we can check that
+ // the position is set correctly for the elements.
+ ElementPtr top = Element::fromJSON(ss, string("kea.conf"), true);
+ ASSERT_TRUE(top);
+
+ // Element "a"
+ ConstElementPtr level1_el = top->get("a");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(2, level1_el->getPosition().line_);
+ EXPECT_EQ(11, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // Element "cy"
+ level1_el = top->get("cy");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(4, level1_el->getPosition().line_);
+ EXPECT_EQ(11, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // Element "dyz"
+ level1_el = top->get("dyz");
+ ASSERT_TRUE(level1_el);
+ EXPECT_EQ(5, level1_el->getPosition().line_);
+ EXPECT_EQ(13, level1_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
+
+ // Element "e" is a sub element of "dyz".
+ ConstElementPtr level2_el = level1_el->get("e");
+ ASSERT_TRUE(level2_el);
+ EXPECT_EQ(7, level2_el->getPosition().line_);
+ EXPECT_EQ(12, level2_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+
+ // Element "f" is also a sub element of "dyz"
+ level2_el = level1_el->get("f");
+ ASSERT_TRUE(level2_el);
+ EXPECT_EQ(8, level2_el->getPosition().line_);
+ EXPECT_EQ(14, level2_el->getPosition().pos_);
+ EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
+}
+
+TEST(Element, empty) {
+
+ // Let's try Map first
+ ElementPtr m = Element::createMap();
+ EXPECT_TRUE(m->empty());
+ m->set("something", Element::create(123));
+ EXPECT_FALSE(m->empty());
+ m->remove("something");
+ EXPECT_TRUE(m->empty());
+
+ // Now do the same with list
+ ElementPtr l = Element::createList();
+ EXPECT_TRUE(l->empty());
+ l->add(Element::create(123));
+ EXPECT_FALSE(l->empty());
+ l->remove(0);
+ EXPECT_TRUE(l->empty());
+}
+
+TEST(Element, sortIntegers) {
+ ElementPtr l(Element::fromJSON("[5, 7, 4, 2, 8, 6, 1, 9, 0, 3]"));
+ ElementPtr expected(Element::fromJSON("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"));
+ boost::dynamic_pointer_cast<ListElement>(l)->sort();
+ EXPECT_EQ(*l, *expected);
+}
+
+TEST(Element, sortFloatingPoint) {
+ ElementPtr l(Element::fromJSON("[2.1, 3.2, 2.1, 2.2, 4.1, 3.2, 1.1, 4.2, 0.1, 1.2]"));
+ ElementPtr expected(Element::fromJSON("[0.1, 1.1, 1.2, 2.1, 2.1, 2.2, 3.2, 3.2, 4.1, 4.2]"));
+ boost::dynamic_pointer_cast<ListElement>(l)->sort();
+ EXPECT_EQ(*l, *expected);
+}
+
+TEST(Element, sortBooleans) {
+ ElementPtr l(Element::fromJSON("[false, true, false, true]"));
+ ElementPtr expected(Element::fromJSON("[false, false, true, true]"));
+ boost::dynamic_pointer_cast<ListElement>(l)->sort();
+ EXPECT_EQ(*l, *expected);
+}
+
+TEST(Element, sortStrings) {
+ ElementPtr l(Element::fromJSON(R"(["hello", "world", "lorem", "ipsum", "dolor", "sit", "amet"])"));
+ ElementPtr expected(Element::fromJSON(R"(["amet", "dolor", "hello", "ipsum", "lorem", "sit", "world"])"));
+ boost::dynamic_pointer_cast<ListElement>(l)->sort();
+ EXPECT_EQ(*l, *expected);
+}
+
+TEST(Element, sortMaps) {
+ ElementPtr e1(Element::fromJSON(R"({"id": 1, "subnet": "10.0.2.0/24"})"));
+ ElementPtr e2(Element::fromJSON(R"({"id": 2, "subnet": "10.0.1.0/24"})"));
+ ElementPtr l;
+
+ // Test sorting by "id". Order shouldn't change.
+ l = Element::createList();
+ l->add(e1);
+ l->add(e2);
+ boost::dynamic_pointer_cast<ListElement>(l)->sort("id");
+ ASSERT_EQ(l->size(), 2);
+ EXPECT_EQ(*l->get(0), *e1);
+ EXPECT_EQ(*l->get(1), *e2);
+
+ // Test sorting by "id". Order should change.
+ l = Element::createList();
+ l->add(e2);
+ l->add(e1);
+ boost::dynamic_pointer_cast<ListElement>(l)->sort("id");
+ ASSERT_EQ(l->size(), 2);
+ EXPECT_EQ(*l->get(0), *e1);
+ EXPECT_EQ(*l->get(1), *e2);
+
+ // Test sorting by "subnet". Order should change.
+ l = Element::createList();
+ l->add(e1);
+ l->add(e2);
+ boost::dynamic_pointer_cast<ListElement>(l)->sort("subnet");
+ ASSERT_EQ(l->size(), 2);
+ EXPECT_EQ(*l->get(0), *e2);
+ EXPECT_EQ(*l->get(1), *e1);
+
+ // Test sorting by "subnet". Order shouldn't change.
+ l = Element::createList();
+ l->add(e2);
+ l->add(e1);
+ boost::dynamic_pointer_cast<ListElement>(l)->sort("subnet");
+ ASSERT_EQ(l->size(), 2);
+ EXPECT_EQ(*l->get(0), *e2);
+ EXPECT_EQ(*l->get(1), *e1);
+}
+
+TEST(Element, removeEmptyContainersRecursively) {
+ ElementPtr e(Element::fromJSON(R"(
+{
+ "list": [
+ {
+ "nested-list": [
+ {
+ "nestedx2-list": [
+ {}
+ ]
+ }
+ ]
+ }
+ ],
+ "map": {
+ "nested-map": {
+ "nestedx2-map": {}
+ }
+ },
+ "simple-list": {},
+ "simple-map": {}
+}
+)"));
+ e->removeEmptyContainersRecursively();
+ EXPECT_EQ(*e, *Element::fromJSON("{}"));
+
+ e = Element::fromJSON(R"(
+{
+ "list": [
+ {
+ "value": "not empty anymore",
+ "nested-list": [
+ {
+ "nestedx2-list": [
+ {}
+ ]
+ }
+ ]
+ }
+ ],
+ "map": {
+ "value": "not empty anymore",
+ "nested-map": {
+ "nestedx2-map": {}
+ }
+ },
+ "simple-list": {},
+ "simple-map": {}
+}
+)");
+ e->removeEmptyContainersRecursively();
+ EXPECT_EQ(*e, *Element::fromJSON(R"(
+{
+ "list": [
+ {
+ "value": "not empty anymore"
+ }
+ ],
+ "map": {
+ "value": "not empty anymore"
+ }
+}
+)"));
+}
+
+/// @brief Function which creates an imaginary configuration hierarchy used to
+/// test mergeDiffAdd, mergeDiffDel and extend.
+///
+/// @param any Flag which indicates if traversing the hierarchy should use exact
+/// element match or not.
+isc::data::HierarchyDescriptor createHierarchy(bool any = false) {
+ auto const& element_empty = [](ElementPtr& element) {
+ for (auto const& kv : element->mapValue()) {
+ auto const& key = kv.first;
+ if (key != "id") {
+ return (false);
+ }
+ }
+ return (true);
+ };
+ auto const& element_match = [](ElementPtr& left, ElementPtr& right) -> bool {
+ return (left->get("id")->intValue() == right->get("id")->intValue());
+ };
+ auto const& element_any_match = [](ElementPtr&, ElementPtr&) -> bool {
+ return (true);
+ };
+ auto const& element_is_key = [](const std::string& key) -> bool {
+ return (key == "id");
+ };
+ isc::data::HierarchyDescriptor hierarchy = {
+ { { "root", { element_match, element_empty, element_is_key } } },
+ { { "elements", { element_match, element_empty, element_is_key } },
+ { "elements-other", { element_match, element_empty, element_is_key } } }
+ };
+ if (any) {
+ hierarchy = {
+ { { "root", { element_any_match, element_empty, element_is_key } } },
+ { { "elements", { element_any_match, element_empty, element_is_key } },
+ { "elements-other", { element_any_match, element_empty, element_is_key } } }
+ };
+ }
+ return (hierarchy);
+}
+
+/// @brief Test which checks that mergeDiffAdd throws if called with wrong
+/// element types.
+TEST(Element, mergeDiffAddBadParams) {
+ {
+ SCOPED_TRACE("root bad scalars");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(true);
+ ElementPtr right = Element::create("false");
+ ASSERT_THROW(mergeDiffAdd(left, right, hierarchy, ""), TypeError);
+ }
+ {
+ SCOPED_TRACE("map bad elements");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ left->set("elements", Element::createList());
+ right->set("elements", Element::createMap());
+ ASSERT_THROW(mergeDiffAdd(left, right, hierarchy, "root"), TypeError);
+ }
+ {
+ SCOPED_TRACE("list bad elements");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::createMap());
+ right_right->set("id", Element::create(0));
+ right_right->set("elements", Element::createList());
+ left->add(left_left);
+ right->add(right_right);
+ ASSERT_THROW(mergeDiffAdd(left, right, hierarchy, "root"), TypeError);
+ }
+}
+
+/// @brief Test which checks that mergeDiffAdd works as expected.
+TEST(Element, mergeDiffAdd) {
+ {
+ SCOPED_TRACE("scalar bool");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(true);
+ ElementPtr right = Element::create(false);
+ EXPECT_NE(left->boolValue(), right->boolValue());
+ mergeDiffAdd(left, right, hierarchy, "");
+ EXPECT_EQ(left->boolValue(), right->boolValue());
+ std::string expected_str("false");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar int");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(1);
+ ElementPtr right = Element::create(2);
+ EXPECT_NE(left->intValue(), right->intValue());
+ mergeDiffAdd(left, right, hierarchy, "");
+ EXPECT_EQ(left->intValue(), right->intValue());
+ std::string expected_str("2");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar double");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(0.1);
+ ElementPtr right = Element::create(0.2);
+ EXPECT_NE(left->doubleValue(), right->doubleValue());
+ mergeDiffAdd(left, right, hierarchy, "");
+ EXPECT_EQ(left->doubleValue(), right->doubleValue());
+ std::string expected_str("0.2");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar string");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create("left");
+ ElementPtr right = Element::create("right");
+ EXPECT_NE(left->stringValue(), right->stringValue());
+ mergeDiffAdd(left, right, hierarchy, "");
+ EXPECT_EQ(left->stringValue(), right->stringValue());
+ std::string expected_str("\"right\"");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar in map");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ left->set("elements", Element::create("left"));
+ left->set("other-elements", Element::create("other"));
+ // scalar element which is updated
+ right->set("elements", Element::create("right"));
+ // scalar element which is added
+ right->set("new-elements", Element::create("new"));
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffAdd(left, right, hierarchy, "");
+ std::string expected_str("{ \"elements\": \"right\", \"new-elements\": \"new\", \"other-elements\": \"other\" }");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar in list");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ left->add(Element::create("left"));
+ left->add(Element::create("other"));
+ left->add(Element::create("test"));
+ // scalar element which is added
+ right->add(Element::create("right"));
+ // scalar element which is added
+ right->add(Element::create("new"));
+ // scalar element which already exists but is still added
+ right->add(Element::create("test"));
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffAdd(left, right, hierarchy, "");
+ std::string expected_str("[ \"left\", \"other\", \"test\", \"right\", \"new\", \"test\" ]");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar and list and map in map");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::create("left"));
+ left_left->set("other-elements", Element::create("other"));
+ // scalar element used as key
+ right_right->set("id", Element::create(0));
+ // scalar element which is updated
+ right_right->set("elements", Element::create("right"));
+ // scalar element which is added
+ right_right->set("new-elements", Element::create("new"));
+ ElementPtr left_other_left = Element::createMap();
+ ElementPtr right_other_right = Element::createMap();
+ left_other_left->set("id", Element::create(1));
+ left_other_left->set("elements", Element::create("other-left"));
+ // scalar element used as key
+ right_other_right->set("id", Element::create(2));
+ // scalar element which is added
+ right_other_right->set("elements", Element::create("other-right"));
+ left->set("elements", left_left);
+ left->set("left-other-elements", left_other_left);
+ // map element which is added
+ right->set("right-other-elements", right_other_right);
+ // map element which is updated
+ right->set("elements", right_right);
+ left_other_left = Element::createList();
+ right_other_right = Element::createList();
+ left_other_left->add(Element::create("left-other-left"));
+ left_other_left->add(Element::create("left-other-left-other"));
+ left_other_left->add(Element::create("other-other"));
+ // scalar element which is added
+ right_other_right->add(Element::create("right-other-right"));
+ // scalar element which is added
+ right_other_right->add(Element::create("right-other-right-other"));
+ // scalar element which already exists but is still added
+ right_other_right->add(Element::create("other-other"));
+ left->set("other", left_other_left);
+ // list element which is updated
+ right->set("other", right_other_right);
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffAdd(left, right, hierarchy, "root");
+ std::string expected_str("{ \"elements\": { \"elements\": \"right\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" }, "
+ "\"left-other-elements\": { \"elements\": \"other-left\", \"id\": 1 }, "
+ "\"other\": [ \"left-other-left\", \"left-other-left-other\", \"other-other\", \"right-other-right\", \"right-other-right-other\", \"other-other\" ], "
+ "\"right-other-elements\": { \"elements\": \"other-right\", \"id\": 2 } }");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar and list and map in list");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::create("left"));
+ left_left->set("other-elements", Element::create("other"));
+ // scalar element used as key
+ right_right->set("id", Element::create(0));
+ // scalar element which is updated
+ right_right->set("elements", Element::create("right"));
+ // scalar element which is added
+ right_right->set("new-elements", Element::create("new"));
+ ElementPtr left_other_left = Element::createMap();
+ ElementPtr right_other_right = Element::createMap();
+ left_other_left->set("id", Element::create(1));
+ left_other_left->set("elements", Element::create("other-left"));
+ // scalar element used as key
+ right_other_right->set("id", Element::create(2));
+ // scalar element which is added
+ right_other_right->set("elements", Element::create("other-right"));
+ left->add(left_left);
+ left->add(left_other_left);
+ // map element which is added
+ right->add(right_other_right);
+ // map element which is updated
+ right->add(right_right);
+ left_other_left = Element::createList();
+ right_other_right = Element::createList();
+ left_other_left->add(Element::create("left-other-left"));
+ left_other_left->add(Element::create("left-other-left-other"));
+ left_other_left->add(Element::create("other-other"));
+ // scalar element which is added
+ right_other_right->add(Element::create("right-other-right"));
+ // scalar element which is added
+ right_other_right->add(Element::create("right-other-right-other"));
+ // scalar element which already exists but is still added
+ right_other_right->add(Element::create("other-other"));
+ left_left->set("other", left_other_left);
+ // list element which is updated
+ right_right->set("other", right_other_right);
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffAdd(left, right, hierarchy, "root");
+ std::string expected_str("[ { \"elements\": \"right\", \"id\": 0, \"new-elements\": \"new\", "
+ "\"other\": [ \"left-other-left\", \"left-other-left-other\", \"other-other\", \"right-other-right\", \"right-other-right-other\", \"other-other\" ], "
+ "\"other-elements\": \"other\" }, "
+ "{ \"elements\": \"other-left\", \"id\": 1 }, "
+ "{ \"elements\": \"other-right\", \"id\": 2 } ]");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+}
+
+/// @brief Test which checks that mergeDiffDel throws if called with wrong
+/// element types.
+TEST(Element, mergeDiffDelBadParams) {
+ {
+ SCOPED_TRACE("root bad scalars");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(true);
+ ElementPtr right = Element::create("false");
+ ASSERT_THROW(mergeDiffDel(left, right, hierarchy, ""), TypeError);
+ }
+ {
+ SCOPED_TRACE("map bad elements");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ left->set("elements", Element::createList());
+ right->set("elements", Element::createMap());
+ ASSERT_THROW(mergeDiffDel(left, right, hierarchy, "root"), TypeError);
+ }
+ {
+ SCOPED_TRACE("list bad elements");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::createMap());
+ right_right->set("id", Element::create(0));
+ right_right->set("elements", Element::createList());
+ left->add(left_left);
+ right->add(right_right);
+ ASSERT_THROW(mergeDiffDel(left, right, hierarchy, "root"), TypeError);
+ }
+}
+
+/// @brief Test which checks that mergeDiffDel works as expected.
+TEST(Element, mergeDiffDel) {
+ {
+ SCOPED_TRACE("scalar bool");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(true);
+ ElementPtr right = Element::create(false);
+ EXPECT_NE(left->boolValue(), right->boolValue());
+ mergeDiffDel(left, right, hierarchy, "");
+ EXPECT_EQ(left->getType(), Element::null);
+ }
+ {
+ SCOPED_TRACE("scalar int");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(1);
+ ElementPtr right = Element::create(2);
+ EXPECT_NE(left->intValue(), right->intValue());
+ mergeDiffDel(left, right, hierarchy, "");
+ EXPECT_EQ(left->getType(), Element::null);
+ }
+ {
+ SCOPED_TRACE("scalar double");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(0.1);
+ ElementPtr right = Element::create(0.2);
+ EXPECT_NE(left->doubleValue(), right->doubleValue());
+ mergeDiffDel(left, right, hierarchy, "");
+ EXPECT_EQ(left->getType(), Element::null);
+ }
+ {
+ SCOPED_TRACE("scalar string");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create("left");
+ ElementPtr right = Element::create("right");
+ EXPECT_NE(left->stringValue(), right->stringValue());
+ mergeDiffDel(left, right, hierarchy, "");
+ EXPECT_EQ(left->getType(), Element::null);
+ }
+ {
+ SCOPED_TRACE("scalar in map");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ left->set("elements", Element::create("left"));
+ left->set("other-elements", Element::create("other"));
+ // scalar element which is removed
+ right->set("elements", Element::create("right"));
+ // scalar element which does not exist and does nothing
+ right->set("new-elements", Element::create("new"));
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffDel(left, right, hierarchy, "root");
+ std::string expected_str("{ \"other-elements\": \"other\" }");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar in list");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ left->add(Element::create("left"));
+ left->add(Element::create("other"));
+ left->add(Element::create("other-left"));
+ left->add(Element::create("new"));
+ // scalar element which does not exist and does nothing
+ right->add(Element::create("right"));
+ // scalar element which is removed
+ right->add(Element::create("other"));
+ // scalar element which does not exist and does nothing
+ right->add(Element::create("other-right"));
+ // scalar element which is removed
+ right->add(Element::create("new"));
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffDel(left, right, hierarchy, "");
+ std::string expected_str("[ \"left\", \"other-left\" ]");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar and list and map in map");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::create("left"));
+ left_left->set("other-elements", Element::create("other"));
+ // scalar element used as key
+ right_right->set("id", Element::create(0));
+ // scalar element which is removed
+ right_right->set("elements", Element::create("right"));
+ // scalar element which does not exist and does nothing
+ right_right->set("new-elements", Element::create("new"));
+ ElementPtr left_other_left = Element::createMap();
+ ElementPtr right_other_right = Element::createMap();
+ left_other_left->set("id", Element::create(1));
+ left_other_left->set("elements", Element::create("other-left"));
+ // scalar element used as key
+ right_other_right->set("id", Element::create(2));
+ // scalar element which does not exist and does nothing
+ right_other_right->set("elements", Element::create("other-right"));
+ left->set("elements", left_left);
+ left->set("left-other-elements", left_other_left);
+ // map element which does not exist and does nothing
+ right->set("right-other-elements", right_other_right);
+ // map element which is updated
+ right->set("elements", right_right);
+ left_other_left = Element::createList();
+ right_other_right = Element::createList();
+ left_other_left->add(Element::create("left-other-left"));
+ left_other_left->add(Element::create("other"));
+ left_other_left->add(Element::create("left-other-left-other"));
+ left_other_left->add(Element::create("new"));
+ // scalar element which does not exist and does nothing
+ right_other_right->add(Element::create("right-other-right"));
+ // scalar element which is removed
+ right_other_right->add(Element::create("other"));
+ // scalar element which does not exist and does nothing
+ right_other_right->add(Element::create("right-other-right-other"));
+ // scalar element which is removed
+ right_other_right->add(Element::create("new"));
+ left->set("other", left_other_left);
+ // list element which is updated
+ right->set("other", right_other_right);
+ left_left = Element::createMap();
+ right_right = Element::createMap();
+ left_left->set("id", Element::create(3));
+ left_left->set("elements", Element::create("new-left"));
+ left_left->set("other-elements", Element::create("new-other"));
+ left->set("elements-other", left_left);
+ // scalar element used as key
+ right_right->set("id", Element::create(3));
+ // map element which is not removed because it is contained in a map and
+ // the key can not be removed
+ right->set("elements-other", right_right);
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffDel(left, right, hierarchy, "root");
+ std::string expected_str("{ \"elements\": { \"id\": 0, \"other-elements\": \"other\" }, "
+ "\"elements-other\": { \"elements\": \"new-left\", \"id\": 3, \"other-elements\": \"new-other\" }, "
+ "\"left-other-elements\": { \"elements\": \"other-left\", \"id\": 1 }, "
+ "\"other\": [ \"left-other-left\", \"left-other-left-other\" ] }");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar and list and map in list");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::create("left"));
+ left_left->set("other-elements", Element::create("other"));
+ // scalar element used as key
+ right_right->set("id", Element::create(0));
+ // scalar element which is removed
+ right_right->set("elements", Element::create("right"));
+ // scalar element which does not exist and does nothing
+ right_right->set("new-elements", Element::create("new"));
+ ElementPtr left_other_left = Element::createMap();
+ ElementPtr right_other_right = Element::createMap();
+ left_other_left->set("id", Element::create(1));
+ left_other_left->set("elements", Element::create("other-left"));
+ // scalar element used as key
+ right_other_right->set("id", Element::create(2));
+ // scalar element which does not exist and does nothing
+ right_other_right->set("elements", Element::create("other-right"));
+ left->add(left_left);
+ left->add(left_other_left);
+ // map element which does not exist and does nothing
+ right->add(right_other_right);
+ // map element which is updated
+ right->add(right_right);
+ left_other_left = Element::createList();
+ right_other_right = Element::createList();
+ left_other_left->add(Element::create("left-other-left"));
+ left_other_left->add(Element::create("other"));
+ left_other_left->add(Element::create("left-other-left-other"));
+ left_other_left->add(Element::create("new"));
+ // scalar element which does not exist and does nothing
+ right_other_right->add(Element::create("right-other-right"));
+ // scalar element which is removed
+ right_other_right->add(Element::create("other"));
+ // scalar element which does not exist and does nothing
+ right_other_right->add(Element::create("right-other-right-other"));
+ // scalar element which is removed
+ right_other_right->add(Element::create("new"));
+ left_left->set("other", left_other_left);
+ // list element which is updated
+ right_right->set("other", right_other_right);
+ left_left = Element::createMap();
+ right_right = Element::createMap();
+ left_left->set("id", Element::create(3));
+ left_left->set("elements", Element::create("new-left"));
+ left_left->set("other-elements", Element::create("new-other"));
+ left->add(left_left);
+ // scalar element used as key
+ right_right->set("id", Element::create(3));
+ // map element which is removed by key
+ // the key can not be removed
+ right->add(right_right);
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ mergeDiffDel(left, right, hierarchy, "root");
+ std::string expected_str("[ { \"id\": 0, \"other\": [ \"left-other-left\", \"left-other-left-other\" ], \"other-elements\": \"other\" }, "
+ "{ \"elements\": \"other-left\", \"id\": 1 } ]");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+}
+
+/// @brief Test which checks that extend throws if called with wrong element
+/// types.
+TEST(Element, extendBadParam) {
+ {
+ SCOPED_TRACE("root bad scalars");
+ isc::data::HierarchyDescriptor hierarchy;
+ ElementPtr left = Element::create(true);
+ ElementPtr right = Element::create("false");
+ ASSERT_THROW(extend("elements", "", left, right, hierarchy, ""), TypeError);
+ }
+ {
+ SCOPED_TRACE("map bad elements");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ left->set("elements", Element::createList());
+ right->set("elements", Element::createMap());
+ ASSERT_THROW(extend("elements", "", left, right, hierarchy, "root"), TypeError);
+ }
+ {
+ SCOPED_TRACE("list bad elements");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy();
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::createMap());
+ right_right->set("id", Element::create(0));
+ right_right->set("elements", Element::createList());
+ left->add(left_left);
+ right->add(right_right);
+ ASSERT_THROW(extend("elements", "", left, right, hierarchy, "root"), TypeError);
+ }
+}
+
+/// @brief Test which checks that extend works as expected.
+TEST(Element, extend) {
+ {
+ SCOPED_TRACE("scalar in map but alter flag is not set");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy(true);
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ left->set("elements", Element::create("left"));
+ left->set("other-elements", Element::create("other"));
+ // scalar element which is not updated
+ right->set("elements", Element::create("right"));
+ // scalar element which is extended
+ right->set("new-elements", Element::create("new"));
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ extend("root", "new-elements", left, right, hierarchy, "root", 0, false);
+ std::string expected_str("{ \"elements\": \"left\", \"other-elements\": \"other\" }");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar in map");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy(true);
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ left->set("elements", Element::create("left"));
+ left->set("other-elements", Element::create("other"));
+ // scalar element which is not updated
+ right->set("elements", Element::create("right"));
+ // scalar element which is extended
+ right->set("new-elements", Element::create("new"));
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ extend("root", "new-elements", left, right, hierarchy, "root", 0, true);
+ std::string expected_str("{ \"elements\": \"left\", \"new-elements\": \"new\", \"other-elements\": \"other\" }");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar in map in map");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy(true);
+ ElementPtr left = Element::createMap();
+ ElementPtr right = Element::createMap();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::create("left"));
+ left_left->set("other-elements", Element::create("other"));
+ // scalar element used as key
+ right_right->set("id", Element::create(1));
+ // scalar element which is not updated
+ right_right->set("elements", Element::create("right"));
+ // scalar element which is extended
+ right_right->set("new-elements", Element::create("new"));
+ left->set("elements", left_left);
+ // map element which is used for extension
+ right->set("elements", right_right);
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ extend("root", "new-elements", left, right, hierarchy, "root");
+ std::string expected_str("{ \"elements\": { \"elements\": \"left\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" } }");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+ {
+ SCOPED_TRACE("scalar in map in list");
+ isc::data::HierarchyDescriptor hierarchy;
+ hierarchy = createHierarchy(true);
+ ElementPtr left = Element::createList();
+ ElementPtr right = Element::createList();
+ ElementPtr left_left = Element::createMap();
+ ElementPtr right_right = Element::createMap();
+ left_left->set("id", Element::create(0));
+ left_left->set("elements", Element::create("left"));
+ left_left->set("other-elements", Element::create("other"));
+ // scalar element used as key
+ right_right->set("id", Element::create(1));
+ // scalar element which is not updated
+ right_right->set("elements", Element::create("right"));
+ // scalar element which is extended
+ right_right->set("new-elements", Element::create("new"));
+ left->add(left_left);
+ // map element which is used for extension
+ right->add(right_right);
+ ASSERT_FALSE(isc::data::isEquivalent(left, right));
+ extend("root", "new-elements", left, right, hierarchy, "root");
+ std::string expected_str("[ { \"elements\": \"left\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" } ]");
+ ElementPtr expected = Element::fromJSON(expected_str);
+ EXPECT_TRUE(isc::data::isEquivalent(left, expected))
+ << "Actual: " << left->str()
+ << "\nExpected: " << expected->str();
+ }
+}
+
+} // namespace
diff --git a/src/lib/cc/tests/element_value_unittests.cc b/src/lib/cc/tests/element_value_unittests.cc
new file mode 100644
index 0000000..e284440
--- /dev/null
+++ b/src/lib/cc/tests/element_value_unittests.cc
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 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 <cc/element_value.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+
+namespace {
+
+// This test verifies that integer value can be extracted.
+TEST(ElementValue, intValue) {
+ EXPECT_EQ(5, ElementValue<int>()(Element::create(5)));
+ EXPECT_THROW(ElementValue<int>()(Element::create("hola!")),
+ TypeError);
+}
+
+// This test verifies that double value can be extracted.
+TEST(ElementValue, doubleValue) {
+ EXPECT_EQ(1.4, ElementValue<double>()(Element::create(1.4)));
+ EXPECT_THROW(ElementValue<double>()(Element::create("hola!")),
+ TypeError);
+}
+
+// This test verifies that boolean value can be extracted.
+TEST(ElementValue, boolValue) {
+ EXPECT_TRUE(ElementValue<bool>()(Element::create(true)));
+ EXPECT_THROW(ElementValue<bool>()(Element::create("hola!")),
+ TypeError);
+}
+
+// This test verifies that string value can be extracted.
+TEST(ElementValue, stringValue) {
+ EXPECT_EQ("hola!", ElementValue<std::string>()(Element::create("hola!")));
+ EXPECT_THROW(ElementValue<std::string>()(Element::create(false)),
+ TypeError);
+}
+
+}
diff --git a/src/lib/cc/tests/json_feed_unittests.cc b/src/lib/cc/tests/json_feed_unittests.cc
new file mode 100644
index 0000000..747d142
--- /dev/null
+++ b/src/lib/cc/tests/json_feed_unittests.cc
@@ -0,0 +1,453 @@
+// Copyright (C) 2017-2023 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 <cc/data.h>
+#include <cc/json_feed.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace isc::config;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Test fixture class for @ref JSONFeed class.
+class JSONFeedTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes @ref json_map_ and @ref json_list_ which hold reference
+ /// JSON structures.
+ JSONFeedTest()
+ : json_map_(), json_list_() {
+ ElementPtr m = Element::fromJSON(createJSON());
+ ElementPtr l = Element::createList();
+ l->add(m);
+ json_map_ = m;
+ json_list_ = l;
+ }
+
+ /// @brief Creates a JSON map holding 20 elements.
+ ///
+ /// Each map value is a list of 20 elements.
+ std::string createJSON() const {
+ // Create a list of 20 elements.
+ ElementPtr list_element = Element::createList();
+ for (unsigned i = 0; i < 20; ++i) {
+ std::ostringstream s;
+ s << "list_element" << i;
+ list_element->add(Element::create(s.str()));
+ }
+
+ // Create a map of 20 elements. Each map element holds a list
+ // of 20 elements.
+ ElementPtr map_element = Element::createMap();
+ for (unsigned i = 0; i < 20; ++i) {
+ std::ostringstream s;
+ s << "map_element" << i;
+ map_element->set(s.str(), list_element);
+ }
+
+ return (prettyPrint(map_element));
+ }
+
+ /// @brief Test that the JSONFeed correctly recognizes the beginning
+ /// and the end of the JSON structure.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ /// @param expected_output A structure holding expected output from the
+ /// @ref JSONFeed::toElement.
+ void testRead(const std::string& input_json,
+ const ConstElementPtr& expected_output) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ // Post the data into the feed in 10 bytes long chunks.
+ size_t chunk = 10;
+
+ for (size_t i = 0; i < input_json.size(); i += chunk) {
+ bool done = false;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk >= input_json.size()) {
+ chunk = input_json.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ feed.postBuffer(&input_json[i], chunk);
+ feed.poll();
+ if (!done) {
+ ASSERT_TRUE(feed.needData());
+ }
+ }
+
+ // Convert parsed/collected data in the feed into the structure of
+ // elements.
+ ConstElementPtr element_from_feed = feed.toElement();
+ EXPECT_TRUE(element_from_feed->equals(*expected_output));
+ }
+
+ /// @brief Test that the JSONFeed correctly recognizes the beginning
+ /// and the end of the JSON structure.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ /// @param expected_output A string holding expected output from the
+ /// @ref JSONFeed::getProcessedText.
+ void testRead(const std::string& input_json,
+ const std::string& expected_output) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ // Post the data into the feed in 10 bytes long chunks.
+ size_t chunk = 10;
+
+ for (size_t i = 0; i < input_json.size(); i += chunk) {
+ bool done = false;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk >= input_json.size()) {
+ chunk = input_json.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ feed.postBuffer(&input_json[i], chunk);
+ feed.poll();
+ if (!done) {
+ ASSERT_TRUE(feed.needData());
+ }
+ }
+
+ EXPECT_EQ(expected_output, feed.getProcessedText());
+ }
+
+ /// @brief Test that the @ref JSONFeed signals an error when the input
+ /// string holds invalid data.
+ ///
+ /// @param input_json A string holding an input JSON structure.
+ /// @param err_msg A string holding an expected error message.
+ void testInvalidRead(const std::string& input_json,
+ const std::string& err_msg) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+
+ ASSERT_NO_THROW(feed.postBuffer(&input_json[0], input_json.size()));
+ ASSERT_NO_THROW(feed.poll());
+
+ EXPECT_FALSE(feed.needData());
+ EXPECT_FALSE(feed.feedOk());
+
+ EXPECT_EQ(err_msg, feed.getErrorMessage());
+ }
+
+ /// @brief JSON map holding a number of lists.
+ ConstElementPtr json_map_;
+
+ /// @brief JSON list holding a map of lists.
+ ConstElementPtr json_list_;
+
+};
+
+// This test verifies that toElement should not be called before
+// the feed detects the end of the data stream.
+TEST_F(JSONFeedTest, toElementTooSoon) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{\n";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_TRUE(feed.needData());
+ EXPECT_THROW(feed.toElement(), JSONFeedError);
+}
+
+// This test verifies that toElement checks JSON syntax as a side effect.
+TEST_F(JSONFeedTest, badJSON) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{\n]\n";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_THROW(feed.toElement(), JSONFeedError);
+}
+
+// This test verifies that a JSON structure starting with '{' is accepted
+// and parsed.
+TEST_F(JSONFeedTest, startWithBrace) {
+ std::string json = createJSON();
+ testRead(json, json_map_);
+}
+
+// This test verifies that a JSON structure starting with '[' is accepted
+// and parsed.
+TEST_F(JSONFeedTest, startWithSquareBracket) {
+ std::string json = createJSON();
+ json = std::string("[") + json + std::string("]");
+ testRead(json, json_list_);
+}
+
+// This test verifies that input JSON can be preceded with whitespaces.
+TEST_F(JSONFeedTest, startWithWhitespace) {
+ std::string json = createJSON();
+ json = std::string(" \r\r\t ") + json;
+ testRead(json, json_map_);
+}
+
+// This test verifies that an empty map is accepted and parsed.
+TEST_F(JSONFeedTest, emptyMap) {
+ std::string json = "{}";
+ testRead(json, Element::createMap());
+}
+
+// This test verifies that an empty list is accepted and parsed.
+TEST_F(JSONFeedTest, emptyList) {
+ std::string json = "[ ]";
+ testRead(json, Element::createList());
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// is preceded by invalid character.
+TEST_F(JSONFeedTest, unexpectedFirstCharacter) {
+ std::string json = "a {}";
+ std::string err_msg = "invalid first character a";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// is preceded by white spaces and an invalid character.
+TEST_F(JSONFeedTest, unexpectedCharacter) {
+ std::string json = " a {}";
+ std::string err_msg = "invalid character a";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when the JSON structure
+// begins by a string.
+TEST_F(JSONFeedTest, stringFirst) {
+ std::string json = "\"foo\"";
+ std::string err_msg = "invalid first character \"";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when the JSON structure
+// begins by white spaces followed by a string.
+TEST_F(JSONFeedTest, stringBefore) {
+ std::string json = " \"foo\"";
+ std::string err_msg = "invalid character \"";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// lacks an opening brace character.
+TEST_F(JSONFeedTest, noOpeningBrace) {
+ std::string json = "\"x\": \"y\" }";
+ std::string err_msg = "invalid first character \"";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that an error is signalled when a JSON structure
+// lacks an opening square bracket.
+TEST_F(JSONFeedTest, noOpeningSquareBracket) {
+ std::string json = "1, 2 ]";
+ std::string err_msg = "invalid first character 1";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that a string is correctly handled
+TEST_F(JSONFeedTest, string) {
+ std::string json = "{ \"braces\": \"}}}}\" }";
+ ElementPtr expected = Element::createMap();
+ expected->set("braces", Element::create("}}}}"));
+ testRead(json, expected);
+}
+
+// This test verifies that a string with escapes is correctly handled
+TEST_F(JSONFeedTest, escape) {
+ std::string json = "{ \"escapes\": \"\\n\\t\\\"\\\\\" }";
+ ElementPtr expected = Element::createMap();
+ expected->set("escapes", Element::create("\n\t\"\\"));
+ testRead(json, expected);
+}
+
+// This test verifies that white spaces before JSON are ignored.
+TEST_F(JSONFeedTest, whiteSpaceBefore) {
+ std::string json = " \n [ ]\n";
+ std::string expected = "[ ]";
+ testRead(json, expected);
+}
+
+// This test verifies that bash style comments before JSON are ignored.
+TEST_F(JSONFeedTest, bashCommentBefore) {
+ std::string json = "# ahah\n # foo\"bar\n{ }\n";
+ std::string expected = "{ }";
+ testRead(json, expected);
+}
+
+// This test verifies that C++ style comments before JSON are ignored.
+TEST_F(JSONFeedTest, cppCommentBefore) {
+ std::string json = "// ahah\n // foo\"bar\n[ 12 ]\n";
+ std::string expected = "[ 12 ]";
+ testRead(json, expected);
+}
+
+// This test verifies that multi-line comments before JSON are ignored.
+TEST_F(JSONFeedTest, multiLineCommentBefore) {
+ std::string json = "/* ahah\n \"// foo*bar**/\n { \"foo\": \"bar\" }\n";
+ std::string expected = "{ \"foo\": \"bar\" }";
+ testRead(json, expected);
+}
+
+// This test verifies that an error is signalled when a slash does not
+// begin a C++ or C style comment before JSON.
+TEST_F(JSONFeedTest, badCommentBefore) {
+ std::string json = "/# foo\n [ ]\n";
+ std::string err_msg = "invalid characters /#";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that bash style comments are ignored.
+TEST_F(JSONFeedTest, bashComments) {
+ std::string json = "{ # ahah\n \"foo\": # value?\n \"bar\" }";
+ std::string expected = "{ \n \"foo\": \n \"bar\" }";
+ testRead(json, expected);
+}
+
+// This test verifies that C++ style comments are ignored.
+TEST_F(JSONFeedTest, cppComments) {
+ std::string json = "[ // ahah\n \"foo\", /// value?\n \"bar\" ]";
+ std::string expected = "[ \n \"foo\", \n \"bar\" ]";
+ testRead(json, expected);
+}
+
+// This test verifies that multi-line comments are ignored.
+TEST_F(JSONFeedTest, multiLineComments) {
+ std::string json = "{ /* ahah\n \"// foo*bar**/\n \"foo\": \"bar\" }\n";
+ std::string expected = "{ \n\n \"foo\": \"bar\" }";
+ testRead(json, expected);
+}
+
+// This test verifies that an error is signalled a slash does not begin
+// a C++ or C style comment.
+TEST_F(JSONFeedTest, badComment) {
+ std::string json = "[ /# foo\n ]\n";
+ std::string err_msg = "invalid characters /#";
+ testInvalidRead(json, err_msg);
+}
+
+// This test verifies that trailing garbage is ignored.
+TEST_F(JSONFeedTest, trailing) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "[ 1, 2] 3, 4]";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ std::string expected = "[ 1, 2]";
+ EXPECT_EQ(expected, feed.getProcessedText());
+}
+
+// Example from DHCPv4 unit tests.
+TEST_F(JSONFeedTest, bashComment4) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "# this is a comment\n"
+ "\"rebind-timer\": 2000, \n"
+ "# lots of comments here\n"
+ "# and here\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1, "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+// Example from DHCPv4 unit tests.
+TEST_F(JSONFeedTest, bashCommentsInline4) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp4\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"rebind-timer\": 2000, # everything after # is ignored\n"
+ "\"renew-timer\": 1000, # this will be ignored, too\n"
+ "\"subnet4\": [ { "
+ " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"id\": 1, "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+// Example from DHCPv6 unit tests.
+TEST_F(JSONFeedTest, cppComments6) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000, // this is a comment \n"
+ "\"rebind-timer\": 2000, // everything after // is ignored\n"
+ "\"renew-timer\": 1000, // this will be ignored, too\n"
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"id\": 1, "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+// Example from DHCPv6 unit tests.
+TEST_F(JSONFeedTest, multilineComments6) {
+ JSONFeed feed;
+ ASSERT_NO_THROW(feed.initModel());
+ std::string json = "{ \"Dhcp6\": { \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},\n"
+ "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
+ "that\n can \n span \n multiple \n lines */ \n"
+ "\"rebind-timer\": 2000,\n"
+ "\"renew-timer\": 1000, \n"
+ "\"subnet6\": [ { "
+ " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"id\": 1, "
+ " \"interface\": \"eth0\""
+ " } ],"
+ "\"valid-lifetime\": 4000 } }";
+ feed.postBuffer(&json[0], json.size());
+ feed.poll();
+ EXPECT_FALSE(feed.needData());
+ EXPECT_TRUE(feed.feedOk());
+ EXPECT_NO_THROW(feed.toElement());
+}
+
+} // end of anonymous namespace.
diff --git a/src/lib/cc/tests/run_unittests.cc b/src/lib/cc/tests/run_unittests.cc
new file mode 100644
index 0000000..40c051f
--- /dev/null
+++ b/src/lib/cc/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// 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 <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ isc::log::initLogger();
+
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/cc/tests/server_tag_unittest.cc b/src/lib/cc/tests/server_tag_unittest.cc
new file mode 100644
index 0000000..523be74
--- /dev/null
+++ b/src/lib/cc/tests/server_tag_unittest.cc
@@ -0,0 +1,97 @@
+// Copyright (C) 2019 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 <cc/server_tag.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+
+namespace {
+
+// This test verifies that the constructors of the ServerTag class
+// work properly.
+TEST(ServerTagTest, constructors) {
+ boost::scoped_ptr<ServerTag> tag;
+
+ {
+ SCOPED_TRACE("default constructor for all servers");
+ ASSERT_NO_THROW(tag.reset(new ServerTag()));
+ EXPECT_EQ(ServerTag::ALL, tag->get());
+ EXPECT_TRUE(tag->amAll());
+ }
+
+ {
+ SCOPED_TRACE("all servers");
+ ASSERT_NO_THROW(tag.reset(new ServerTag(ServerTag::ALL)));
+ EXPECT_EQ(ServerTag::ALL, tag->get());
+ EXPECT_TRUE(tag->amAll());
+ }
+
+ {
+ SCOPED_TRACE("no whitespace");
+ ASSERT_NO_THROW(tag.reset(new ServerTag("xyz")));
+ EXPECT_EQ("xyz", tag->get());
+ EXPECT_FALSE(tag->amAll());
+ }
+
+ {
+ SCOPED_TRACE("leading whitespace");
+ ASSERT_NO_THROW(tag.reset(new ServerTag(" left")));
+ EXPECT_EQ("left", tag->get());
+ EXPECT_FALSE(tag->amAll());
+ }
+
+ {
+ SCOPED_TRACE("terminating whitespace");
+ ASSERT_NO_THROW(tag.reset(new ServerTag("right ")));
+ EXPECT_EQ("right", tag->get());
+ EXPECT_FALSE(tag->amAll());
+ }
+
+ {
+ SCOPED_TRACE("leading and terminating whitespace");
+ ASSERT_NO_THROW(tag.reset(new ServerTag(" both left-right ")));
+ EXPECT_EQ("both left-right", tag->get());
+ EXPECT_FALSE(tag->amAll());
+ }
+
+ {
+ SCOPED_TRACE("upper to lower case");
+ ASSERT_NO_THROW(tag.reset(new ServerTag("UPPER CASE TAG")));
+ EXPECT_EQ("upper case tag", tag->get());
+ EXPECT_FALSE(tag->amAll());
+ }
+}
+
+// This test verifies that malformed server tags are rejected.
+TEST(ServerTagTest, malformed) {
+ {
+ SCOPED_TRACE("empty tag");
+ EXPECT_THROW(ServerTag(""), BadValue);
+ }
+
+ {
+ SCOPED_TRACE("only whitespaces");
+ EXPECT_THROW(ServerTag(" "), BadValue);
+ }
+
+ {
+ SCOPED_TRACE("too long tag, max is 256");
+ EXPECT_THROW(ServerTag(std::string(257, 'c')), BadValue);
+ }
+
+ {
+ SCOPED_TRACE("use reserved keyword any as a tag");
+ EXPECT_THROW(ServerTag("any"), BadValue);
+ }
+}
+
+}
diff --git a/src/lib/cc/tests/simple_parser_unittest.cc b/src/lib/cc/tests/simple_parser_unittest.cc
new file mode 100644
index 0000000..76e5ffc
--- /dev/null
+++ b/src/lib/cc/tests/simple_parser_unittest.cc
@@ -0,0 +1,364 @@
+// Copyright (C) 2016-2023 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 <stdint.h>
+#include <cc/simple_parser.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::asiolink;
+using isc::dhcp::DhcpConfigError;
+
+/// This list defines required keywords.
+const SimpleRequiredKeywords REQUIRED_KEYWORDS = { "foobar" };
+
+/// This table defines keywords and types.
+const SimpleKeywords KEYWORDS = {
+ { "id", Element::integer },
+ { "prefix", Element::string },
+ { "map", Element::map },
+ { "any", Element::any }
+};
+
+/// This table defines sample default values. Although these are DHCPv6
+/// specific, the mechanism is generic and can be used by any other component.
+const SimpleDefaults SAMPLE_DEFAULTS = {
+ { "renew-timer", Element::integer, "900" },
+ { "rebind-timer", Element::integer, "1800" },
+ { "preferred-lifetime", Element::integer, "3600" },
+ { "valid-lifetime", Element::integer, "7200" }
+};
+
+/// This list defines parameters that can be inherited from one scope
+/// to another. Although these are DHCPv6 specific, the mechanism is generic and
+/// can be used by any other component.
+const ParamsList SAMPLE_INHERITS = {
+ "renew-timer",
+ "rebind-timer",
+ "preferred-lifetime",
+ "valid-lifetime"
+};
+
+/// @brief Simple Parser test fixture class
+class SimpleParserTest : public ::testing::Test {
+public:
+ /// @brief Checks if specified map has an integer parameter with expected value
+ ///
+ /// @param map map to be checked
+ /// @param param_name name of the parameter to be checked
+ /// @param exp_value expected value of the parameter.
+ void checkIntegerValue(const ConstElementPtr& map, const std::string& param_name,
+ int64_t exp_value) {
+
+ // First check if the passed element is a map.
+ ASSERT_EQ(Element::map, map->getType());
+
+ // Now try to get the element being checked
+ ConstElementPtr elem = map->get(param_name);
+ ASSERT_TRUE(elem);
+
+ // Now check if it's indeed integer
+ ASSERT_EQ(Element::integer, elem->getType());
+
+ // Finally, check if its value meets expectation.
+ EXPECT_EQ(exp_value, elem->intValue());
+ }
+};
+
+class SimpleParserClassTest : public SimpleParser {
+public:
+ /// @brief Instantiation of getAndConvert
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter for error report
+ /// @return a bool value
+ bool getAsBool(ConstElementPtr scope, const std::string& name) {
+ return (getAndConvert<bool, toBool>(scope, name, "boolean"));
+ }
+
+ /// @brief Convert to boolean
+ ///
+ /// @param str the string "false" or "true"
+ /// @return false for "false" and true for "true"
+ /// @thrown isc::OutOfRange if not "false" or "true'
+ static bool toBool(const std::string& str) {
+ if (str == "false") {
+ return (false);
+ } else if (str == "true") {
+ return (true);
+ } else {
+ isc_throw(TypeError, "not a boolean: " << str);
+ }
+ }
+};
+
+// This test checks if the checkRequired method works as expected.
+TEST_F(SimpleParserTest, checkRequired) {
+ ConstElementPtr empty = Element::fromJSON("{ }");
+ EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, empty),
+ DhcpConfigError);
+ ConstElementPtr other = Element::fromJSON("{ \"foo\": 1, \"bar\": 2 }");
+ EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, other),
+ DhcpConfigError);
+ ConstElementPtr good = Element::fromJSON("{ \"foobar\": 2 }");
+ EXPECT_NO_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, good));
+}
+
+// This test checks if the checkKeywords method works as expected.
+TEST_F(SimpleParserTest, checkKeywords) {
+ ConstElementPtr empty = Element::fromJSON("{ }");
+ EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, empty));
+ ConstElementPtr id = Element::fromJSON("{ \"id\": 1 }");
+ EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, id));
+ ConstElementPtr any = Element::fromJSON("{ \"any\": 1 }");
+ EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, any));
+ ConstElementPtr bad_id = Element::fromJSON("{ \"id\": true }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_id),
+ DhcpConfigError);
+ ConstElementPtr bad_prefix = Element::fromJSON("{ \"prefix\": 12 }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_prefix),
+ DhcpConfigError);
+ ConstElementPtr bad_map = Element::fromJSON("{ \"map\": [ ] }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_map),
+ DhcpConfigError);
+ ConstElementPtr spurious = Element::fromJSON("{ \"spurious\": 1 }");
+ EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, spurious),
+ DhcpConfigError);
+
+ // Bad type has precedence.
+ ConstElementPtr bad = Element::fromJSON("{ \"spurious\": 1, \"id\": true }");
+ try {
+ SimpleParser::checkKeywords(KEYWORDS, bad);
+ ADD_FAILURE() << "expect exception";
+ } catch (const DhcpConfigError& ex) {
+ EXPECT_EQ("'id' parameter is not an integer", std::string(ex.what()));
+ } catch (...) {
+ ADD_FAILURE() << "expect DhcpConfigError";
+ }
+}
+
+// This test checks if the parameters can be inherited from the global
+// scope to the subnet scope.
+TEST_F(SimpleParserTest, deriveParams) {
+ ElementPtr global = Element::fromJSON("{ \"renew-timer\": 1,"
+ " \"rebind-timer\": 2,"
+ " \"preferred-lifetime\": 3,"
+ " \"valid-lifetime\": 4"
+ "}");
+ ElementPtr subnet = Element::fromJSON("{ \"renew-timer\": 100 }");
+
+ // we should inherit 3 parameters. Renew-timer should remain intact,
+ // as it was already defined in the subnet scope.
+ size_t num;
+ EXPECT_NO_THROW(num = SimpleParser::deriveParams(global, subnet,
+ SAMPLE_INHERITS));
+ EXPECT_EQ(3, num);
+
+ // Check the values. 3 of them are inherited, while the fourth one
+ // was already defined in the subnet, so should not be inherited.
+ checkIntegerValue(subnet, "renew-timer", 100);
+ checkIntegerValue(subnet, "rebind-timer", 2);
+ checkIntegerValue(subnet, "preferred-lifetime", 3);
+ checkIntegerValue(subnet, "valid-lifetime", 4);
+}
+
+// This test checks if global defaults are properly set for DHCPv6.
+TEST_F(SimpleParserTest, setDefaults) {
+
+ ElementPtr empty = Element::fromJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = SimpleParser::setDefaults(empty, SAMPLE_DEFAULTS));
+
+ // We expect at least 4 parameters to be inserted.
+ EXPECT_GE(num, 3);
+
+ checkIntegerValue(empty, "valid-lifetime", 7200);
+ checkIntegerValue(empty, "preferred-lifetime", 3600);
+ checkIntegerValue(empty, "rebind-timer", 1800);
+ checkIntegerValue(empty, "renew-timer", 900);
+}
+
+// This test checks if global defaults are properly set for DHCPv6.
+TEST_F(SimpleParserTest, setListDefaults) {
+
+ ElementPtr empty = Element::fromJSON("[{}, {}, {}]");
+ size_t num;
+
+ EXPECT_NO_THROW(num = SimpleParser::setListDefaults(empty, SAMPLE_DEFAULTS));
+
+ // We expect at least 12 parameters to be inserted (3 entries, with
+ // 4 parameters inserted in each)
+ EXPECT_EQ(12, num);
+
+ ASSERT_EQ(Element::list, empty->getType());
+ ASSERT_EQ(3, empty->size());
+
+ ConstElementPtr first = empty->get(0);
+ ConstElementPtr second = empty->get(1);
+ ConstElementPtr third = empty->get(2);
+
+ checkIntegerValue(first, "valid-lifetime", 7200);
+ checkIntegerValue(first, "preferred-lifetime", 3600);
+ checkIntegerValue(first, "rebind-timer", 1800);
+ checkIntegerValue(first, "renew-timer", 900);
+
+ checkIntegerValue(second, "valid-lifetime", 7200);
+ checkIntegerValue(second, "preferred-lifetime", 3600);
+ checkIntegerValue(second, "rebind-timer", 1800);
+ checkIntegerValue(second, "renew-timer", 900);
+
+ checkIntegerValue(third, "valid-lifetime", 7200);
+ checkIntegerValue(third, "preferred-lifetime", 3600);
+ checkIntegerValue(third, "rebind-timer", 1800);
+ checkIntegerValue(third, "renew-timer", 900);
+}
+
+// This test exercises the getIntType template
+TEST_F(SimpleParserTest, getIntType) {
+
+ SimpleParserClassTest parser;
+
+ // getIntType checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }");
+ EXPECT_THROW(parser.getUint8(not_found, "foo"), DhcpConfigError);
+
+ // getIntType checks if it is an integer
+ ElementPtr not_int = Element::fromJSON("{ \"foo\": \"xyz\" }");
+ EXPECT_THROW(parser.getUint8(not_int, "foo"), DhcpConfigError);
+
+ // getIntType checks bounds
+ ElementPtr negative = Element::fromJSON("{ \"foo\": -1 }");
+ EXPECT_THROW(parser.getUint8(negative, "foo"), DhcpConfigError);
+ ElementPtr too_large = Element::fromJSON("{ \"foo\": 1024 }");
+ EXPECT_THROW(parser.getUint8(too_large, "foo"), DhcpConfigError);
+
+ // checks if getIntType can return the expected value
+ ElementPtr hundred = Element::fromJSON("{ \"foo\": 100 }");
+ uint8_t val = 0;
+ EXPECT_NO_THROW(val = parser.getUint8(hundred, "foo"));
+ EXPECT_EQ(100, val);
+}
+
+// This test exercises the getInteger with range checking
+TEST_F(SimpleParserTest, getInteger) {
+
+ // The value specified is 100.
+ ElementPtr json = Element::fromJSON("{ \"bar\": 100 }");
+ int64_t x = -1;
+
+ // Positive case: we expect value in range 0..200. All ok.
+ EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 0, 200));
+ EXPECT_EQ(100, x);
+
+ // Border checks: 100 for 100..200 range is still ok.
+ EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 100, 200));
+ // Border checks: 100 for 1..100 range is still ok.
+ EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 1, 100));
+
+ // Out of expected range. Should throw.
+ EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 101, 200), OutOfRange);
+ EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 1, 99), OutOfRange);
+}
+
+// This test exercises the getAndConvert template
+TEST_F(SimpleParserTest, getAndConvert) {
+
+ SimpleParserClassTest parser;
+
+ // getAndConvert checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": \"true\" }");
+ EXPECT_THROW(parser.getAsBool(not_found, "foo"), DhcpConfigError);
+
+ // getAndConvert checks if it is a string
+ ElementPtr not_bool = Element::fromJSON("{ \"foo\": 1 }");
+ EXPECT_THROW(parser.getAsBool(not_bool, "foo"), DhcpConfigError);
+
+ // checks if getAndConvert can return the expected value
+ ElementPtr a_bool = Element::fromJSON("{ \"foo\": \"false\" }");
+ bool val = true;
+ EXPECT_NO_THROW(val = parser.getAsBool(a_bool, "foo"));
+ EXPECT_FALSE(val);
+
+ // getAndConvert checks conversion
+ ElementPtr bad_bool = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_THROW(parser.getAsBool(bad_bool, "bar"), DhcpConfigError);
+}
+
+// This test exercises the getIOAddress
+TEST_F(SimpleParserTest, getIOAddress) {
+
+ SimpleParserClassTest parser;
+
+ // getAddress checks it can be found
+ ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }");
+ EXPECT_THROW(parser.getAddress(not_found, "foo"), DhcpConfigError);
+
+ // getAddress checks if it is a string
+ ElementPtr not_addr = Element::fromJSON("{ \"foo\": 1234 }");
+ EXPECT_THROW(parser.getAddress(not_addr, "foo"), DhcpConfigError);
+
+ // checks if getAddress can return the expected value of v4 address
+ ElementPtr v4 = Element::fromJSON("{ \"foo\": \"192.0.2.1\" }");
+ IOAddress val("::");
+ EXPECT_NO_THROW(val = parser.getAddress(v4, "foo"));
+ EXPECT_EQ("192.0.2.1" , val.toText());
+
+ // checks if getAddress can return the expected value of v4 address
+ ElementPtr v6 = Element::fromJSON("{ \"foo\": \"2001:db8::1\" }");
+ EXPECT_NO_THROW(val = parser.getAddress(v6, "foo"));
+ EXPECT_EQ("2001:db8::1" , val.toText());
+}
+
+// This test exercises getDouble()
+TEST_F(SimpleParserTest, getDouble) {
+
+ SimpleParserClassTest parser;
+ std::string json =
+ "{\n"
+ " \"string\" : \"12.3\",\n"
+ " \"bool\" : true, \n"
+ " \"int\" : 777, \n"
+ " \"map\" : {}, \n"
+ " \"list\" : [], \n"
+ " \"zero\" : 0.0, \n"
+ " \"fraction\" : .75, \n"
+ " \"negative\" : -1.45, \n"
+ " \"positive\" : 346.7 \n"
+ "}\n";
+
+ // Create our test set of parameters.
+ ElementPtr elems;
+ ASSERT_NO_THROW(elems = Element::fromJSON(json)) << " invalid JSON, test is broken";
+
+ // Verify that a non-existant element is caught.
+ EXPECT_THROW(parser.getDouble(elems, "not-there"), DhcpConfigError);
+
+ // Verify that wrong element types are caught.
+ EXPECT_THROW(parser.getDouble(elems, "string"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "int"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "bool"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "map"), DhcpConfigError);
+ EXPECT_THROW(parser.getDouble(elems, "list"), DhcpConfigError);
+
+ // Verify valid values are correct.
+ double value;
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "zero"));
+ EXPECT_EQ(0.0, value);
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "fraction"));
+ EXPECT_EQ(.75, value);
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "negative"));
+ EXPECT_EQ(-1.45, value);
+
+ EXPECT_NO_THROW(value = parser.getDouble(elems, "positive"));
+ EXPECT_EQ(346.7, value);
+}
diff --git a/src/lib/cc/tests/stamped_element_unittest.cc b/src/lib/cc/tests/stamped_element_unittest.cc
new file mode 100644
index 0000000..1ffcd84
--- /dev/null
+++ b/src/lib/cc/tests/stamped_element_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2018-2019 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 <cc/stamped_element.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+
+namespace {
+
+// Tests that the modification timestamp is by default set to current
+// time and the identifier is set to 0.
+TEST(StampedElementTest, create) {
+ StampedElement element;
+
+ // Default identifier is 0.
+ EXPECT_EQ(0, element.getId());
+
+ // By default there is no server tag.
+ EXPECT_TRUE(element.getServerTags().empty());
+
+ // Checking that the delta between now and the timestamp is within
+ // 5s range should be sufficient.
+ boost::posix_time::time_duration delta =
+ boost::posix_time::second_clock::local_time() -
+ element.getModificationTime();
+ EXPECT_LT(delta.seconds(), 5);
+}
+
+// Tests that default id can be overridden by a new value.
+TEST(StampedElementTest, setId) {
+ StampedElement element;
+ element.setId(123);
+ EXPECT_EQ(123, element.getId());
+}
+
+// Tests that the modification timestamp can be set to an arbitrary
+// value.
+TEST(StampedElementTest, setModificationTime) {
+ boost::posix_time::ptime
+ modification_time(boost::gregorian::date(2002, boost::date_time::Jan, 10),
+ boost::posix_time::time_duration(1,2,3));
+ StampedElement element;
+ element.setModificationTime(modification_time);
+ EXPECT_TRUE(element.getModificationTime() == modification_time);
+}
+
+// Tests that updating modification timestamp sets it to the current
+// time.
+TEST(StampedElementTest, update) {
+ boost::posix_time::ptime
+ modification_time(boost::gregorian::date(2002, boost::date_time::Jan, 10),
+ boost::posix_time::time_duration(1,2,3));
+ StampedElement element;
+ element.setModificationTime(modification_time);
+ element.updateModificationTime();
+
+ // Checking that the delta between now and the timestamp is within
+ // 5s range should be sufficient.
+ boost::posix_time::time_duration delta =
+ boost::posix_time::second_clock::local_time() -
+ element.getModificationTime();
+ EXPECT_LT(delta.seconds(), 5);
+}
+
+// Tests that one or more server tag can be specified.
+TEST(StampedElementTest, setServerTag) {
+ StampedElement element;
+ element.setServerTag("foo");
+ EXPECT_EQ(1, element.getServerTags().size());
+ EXPECT_EQ("foo", element.getServerTags().begin()->get());
+
+ element.setServerTag("bar");
+ EXPECT_EQ(2, element.getServerTags().size());
+
+ EXPECT_TRUE(element.hasServerTag(ServerTag("foo")));
+ EXPECT_TRUE(element.hasServerTag(ServerTag("bar")));
+ EXPECT_FALSE(element.hasServerTag(ServerTag("xyz")));
+ EXPECT_FALSE(element.hasAllServerTag());
+
+ element.setServerTag(ServerTag::ALL);
+ EXPECT_TRUE(element.hasAllServerTag());
+}
+
+// Tests that a server tag can be deleted.
+TEST(StampedElementTest, delServerTag) {
+ StampedElement element;
+ EXPECT_THROW(element.delServerTag("foo"), isc::NotFound);
+ element.setServerTag("foo");
+ element.setServerTag("bar");
+
+ ASSERT_EQ(2, element.getServerTags().size());
+ EXPECT_TRUE(element.hasServerTag(ServerTag("foo")));
+ EXPECT_TRUE(element.hasServerTag(ServerTag("bar")));
+
+ EXPECT_NO_THROW(element.delServerTag("foo"));
+ ASSERT_EQ(1, element.getServerTags().size());
+ EXPECT_TRUE(element.hasServerTag(ServerTag("bar")));
+
+ EXPECT_NO_THROW(element.delServerTag("bar"));
+ EXPECT_EQ(0, element.getServerTags().size());
+ EXPECT_THROW(element.delServerTag("bar"), isc::NotFound);
+}
+
+
+// Test that metadata can be created from the StampedElement.
+TEST(StampedElementTest, getMetadata) {
+ StampedElement element;
+ element.setServerTag("world");
+ auto metadata = element.getMetadata();
+ ASSERT_TRUE(metadata);
+ ASSERT_EQ(Element::map, metadata->getType());
+
+ auto server_tags_element = metadata->get("server-tags");
+ ASSERT_TRUE(server_tags_element);
+ EXPECT_EQ(Element::list, server_tags_element->getType());
+ EXPECT_EQ(1, server_tags_element->size());
+
+ auto server_tag_element = server_tags_element->get(0);
+ ASSERT_TRUE(server_tag_element);
+ EXPECT_EQ(Element::string, server_tag_element->getType());
+ EXPECT_EQ("world", server_tag_element->stringValue());
+}
+
+}
diff --git a/src/lib/cc/tests/stamped_value_unittest.cc b/src/lib/cc/tests/stamped_value_unittest.cc
new file mode 100644
index 0000000..d305a34
--- /dev/null
+++ b/src/lib/cc/tests/stamped_value_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright (C) 2018-2023 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 <cc/stamped_value.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::data;
+
+namespace {
+
+// Tests that the stamped value can be created with a NULL value.
+TEST(StampedValueTest, createNull) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar"));
+
+ EXPECT_TRUE(value->amNull());
+
+ EXPECT_THROW(value->getType(), InvalidOperation);
+ EXPECT_THROW(value->getValue(), InvalidOperation);
+ EXPECT_THROW(value->getIntegerValue(), InvalidOperation);
+ EXPECT_THROW(value->getBoolValue(), InvalidOperation);
+ EXPECT_THROW(value->getDoubleValue(), InvalidOperation);
+}
+
+// Tests that stamped value from string can be created.
+TEST(StampedValueTest, createFromString) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create("foo")));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::string, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ("foo", value->getValue());
+
+ EXPECT_THROW(value->getIntegerValue(), TypeError);
+ EXPECT_THROW(value->getBoolValue(), TypeError);
+ EXPECT_THROW(value->getDoubleValue(), TypeError);
+}
+
+// Tests that the stamped value can be created from string using the
+// factory function variant that takes parameter type as an argument.
+TEST(StampedValueTest, convertStringToString) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", "foo", Element::string));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::string, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ("foo", value->getValue());
+}
+
+// Tests that stamped value from integer can be created.
+TEST(StampedValueTest, createFromInteger) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast<int64_t>(5))));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::integer, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ("5", value->getValue());
+ int64_t signed_integer = 0;
+ ASSERT_NO_THROW(signed_integer = value->getIntegerValue());
+ EXPECT_EQ(5, signed_integer);
+
+ EXPECT_THROW(value->getBoolValue(), TypeError);
+ EXPECT_THROW(value->getDoubleValue(), TypeError);
+}
+
+// Tests that stamped value can be converted from string to integer.
+TEST(StampedValueTest, convertStringToInteger) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", "123", Element::integer));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::integer, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ(123, value->getIntegerValue());
+
+ EXPECT_THROW(StampedValue::create("bar", "hoho", Element::integer), BadValue);
+}
+
+// Tests that stamped value from bool can be created.
+TEST(StampedValueTest, createFromBool) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast<bool>(true))));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::boolean, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ("1", value->getValue());
+ bool bool_value = false;
+ ASSERT_NO_THROW(bool_value = value->getBoolValue());
+ EXPECT_TRUE(bool_value);
+
+ EXPECT_THROW(value->getIntegerValue(), TypeError);
+ EXPECT_THROW(value->getDoubleValue(), TypeError);
+}
+
+// Tests that stamped value can be converted from string to boolean.
+TEST(StampedValueTest, convertStringToBoolean) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", "1", Element::boolean));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::boolean, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_TRUE(value->getBoolValue());
+
+ ASSERT_NO_THROW(value = StampedValue::create("foo", "0", Element::boolean));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::boolean, value->getType());
+ EXPECT_EQ("foo", value->getName());
+ EXPECT_FALSE(value->getBoolValue());
+
+ EXPECT_THROW(StampedValue::create("bar", "888", Element::boolean), BadValue);
+}
+
+// Tests that stamped value from real can be created.
+TEST(StampedValueTest, createFromDouble) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast<double>(1.45))));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::real, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ("1.45", value->getValue());
+ double double_value = 0;
+ ASSERT_NO_THROW(double_value = value->getDoubleValue());
+ EXPECT_EQ(1.45, double_value);
+
+ EXPECT_THROW(value->getIntegerValue(), TypeError);
+ EXPECT_THROW(value->getBoolValue(), TypeError);
+}
+
+// Tests that stamped value from real can handle a round value.
+TEST(StampedValueTest, createFromDoubleRound) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast<double>(7.0))));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::real, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ("7.0", value->getValue());
+ double double_value = 0;
+ ASSERT_NO_THROW(double_value = value->getDoubleValue());
+ EXPECT_EQ(7.0, double_value);
+
+ EXPECT_THROW(value->getIntegerValue(), TypeError);
+ EXPECT_THROW(value->getBoolValue(), TypeError);
+}
+
+// Tests that stamped value can be converted from string to real.
+TEST(StampedValueTest, convertStringToDouble) {
+ StampedValuePtr value;
+ ASSERT_NO_THROW(value = StampedValue::create("bar", "1.67", Element::real));
+ EXPECT_FALSE(value->amNull());
+ EXPECT_EQ(Element::real, value->getType());
+ EXPECT_EQ("bar", value->getName());
+ EXPECT_EQ(1.67, value->getDoubleValue());
+
+ EXPECT_THROW(StampedValue::create("bar", "hoho", Element::real), BadValue);
+}
+
+// Tests that the value must have an allowed type.
+TEST(StampedValueTest, createFailures) {
+ EXPECT_THROW(StampedValue::create("bar", ElementPtr()), BadValue);
+ EXPECT_THROW(StampedValue::create("bar", Element::createMap()), TypeError);
+ EXPECT_THROW(StampedValue::create("bar", Element::createList()), TypeError);
+
+ EXPECT_THROW(StampedValue::create("bar", "1", Element::map), TypeError);
+ EXPECT_THROW(StampedValue::create("bar", "1", Element::list), TypeError);
+ EXPECT_THROW(StampedValue::create("bar", "1", Element::null), TypeError);
+}
+
+}
diff --git a/src/lib/cc/tests/user_context_unittests.cc b/src/lib/cc/tests/user_context_unittests.cc
new file mode 100644
index 0000000..e88b421
--- /dev/null
+++ b/src/lib/cc/tests/user_context_unittests.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2018-2020 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 <cc/user_context.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+
+namespace {
+
+ElementPtr gen() {
+ std::string content = "{ \"foo\": 1, \"bar\": \"xyz\" }";
+ return (Element::fromJSON(content));
+}
+
+TEST(UserContext, setget) {
+ UserContext parent;
+ EXPECT_FALSE(parent.getContext());
+ ConstElementPtr map = gen();
+ parent.setContext(map);
+ ConstElementPtr ctx = parent.getContext();
+ EXPECT_EQ(*ctx, *map);
+}
+
+TEST(UserContext, null) {
+ UserContext parent;
+ ElementPtr map = gen();
+ parent.contextToElement(map);
+ ElementPtr expected = gen();
+ EXPECT_EQ(*expected, *map);
+}
+
+TEST(UserContext, notMap) {
+ UserContext parent;
+ ConstElementPtr ctx = Element::create("foo");
+ parent.setContext(ctx);
+ ElementPtr map = gen();
+ parent.contextToElement(map);
+ ElementPtr expected = gen();
+ expected->set("user-context", ctx);
+ EXPECT_EQ(*expected, *map);
+}
+
+TEST(UserContext, empty) {
+ UserContext parent;
+ ConstElementPtr ctx = Element::createMap();
+ parent.setContext(ctx);
+ ElementPtr map = gen();
+ parent.contextToElement(map);
+ ElementPtr expected = gen();
+ expected->set("user-context", ctx);
+ EXPECT_EQ(*expected, *map);
+}
+
+TEST(UserContext, noComment) {
+ UserContext parent;
+ ConstElementPtr ctx = Element::fromJSON("{ \"version\": 1 }");
+ parent.setContext(ctx);
+ ElementPtr map = gen();
+ parent.contextToElement(map);
+ ElementPtr expected = gen();
+ expected->set("user-context", ctx);
+ EXPECT_EQ(*expected, *map);
+};
+
+TEST(UserContext, onlyComment) {
+ UserContext parent;
+ ConstElementPtr ctx = Element::fromJSON("{ \"comment\": \"foobar\" }");
+ parent.setContext(ctx);
+ ElementPtr map = gen();
+ parent.contextToElement(map);
+ ElementPtr expected = gen();
+ expected->set("user-context", ctx);
+ EXPECT_EQ(*expected, *map);
+}
+
+TEST(UserContext, both) {
+ UserContext parent;
+ ConstElementPtr ctx =
+ Element::fromJSON("{ \"comment\": \"foobar\", \"version\": 1 }");
+ parent.setContext(ctx);
+ ElementPtr map = gen();
+ parent.contextToElement(map);
+ ElementPtr expected = gen();
+ expected->set("user-context", ctx);
+ EXPECT_EQ(*expected, *map);
+}
+
+TEST(toElement, notMap) {
+ ConstElementPtr arg = Element::create("foo");
+ ConstElementPtr result = UserContext::toElement(arg);
+ EXPECT_EQ(*result, *arg);
+}
+
+TEST(toElement, empty) {
+ ElementPtr map = gen();
+ ConstElementPtr ctx = Element::createMap();
+ map->set("user-context", ctx);
+ ConstElementPtr result = UserContext::toElement(map);
+ EXPECT_EQ(*result, *map);
+}
+
+TEST(toElement, noComment) {
+ ElementPtr map = gen();
+ ConstElementPtr ctx = Element::fromJSON("{ \"version\": 1 }");
+ map->set("user-context", ctx);
+ ConstElementPtr result = UserContext::toElement(map);
+ EXPECT_EQ(*result, *map);
+}
+
+TEST(toElement, onlyComment) {
+ ElementPtr map = gen();
+ ConstElementPtr ctx = Element::fromJSON("{ \"comment\": \"foobar\" }");
+ map->set("user-context", ctx);
+ ConstElementPtr result = UserContext::toElement(map);
+ EXPECT_EQ(*result, *map);
+}
+
+TEST(toElement, both) {
+ ElementPtr map = gen();
+ ConstElementPtr ctx =
+ Element::fromJSON("{ \"comment\": \"foobar\", \"version\": 1 }");
+ map->set("user-context", ctx);
+ ConstElementPtr result = UserContext::toElement(map);
+ EXPECT_EQ(*result, *map);
+}
+
+}
diff --git a/src/lib/cc/user_context.cc b/src/lib/cc/user_context.cc
new file mode 100644
index 0000000..2ff1984
--- /dev/null
+++ b/src/lib/cc/user_context.cc
@@ -0,0 +1,30 @@
+// Copyright (C) 2017-2020 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 <cc/user_context.h>
+
+
+namespace isc {
+namespace data {
+
+void
+UserContext::contextToElement(ElementPtr map) const {
+ // Set user-context extracting comment
+ ConstElementPtr context = getContext();
+ if (context) {
+ map->set("user-context", context);
+ }
+}
+
+ElementPtr
+UserContext::toElement(ConstElementPtr map) {
+ ElementPtr result = isc::data::copy(map);
+ return (result);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/cc/user_context.h b/src/lib/cc/user_context.h
new file mode 100644
index 0000000..2804c7e
--- /dev/null
+++ b/src/lib/cc/user_context.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2017-2020 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 USER_CONTEXT_H
+#define USER_CONTEXT_H
+
+#include <cc/data.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace data {
+
+/// @brief Base class for user context
+///
+/// Many configuration structures allow attaching and storing arbitrary
+/// user data that we call user context. Each of those configuration
+/// structures need to derive from this class or include this class
+/// to handle user context.
+struct UserContext {
+ /// @brief Returns const pointer to the user context.
+ data::ConstElementPtr getContext() const {
+ return (user_context_);
+ }
+
+ /// @brief Sets user context.
+ /// @param ctx user context to be stored.
+ void setContext(const data::ConstElementPtr& ctx) {
+ user_context_ = ctx;
+ }
+
+ /// @brief Merge unparse a user_context object.
+ ///
+ /// Add user-context to map, but only if defined. Omit if it was not.
+ ///
+ /// @param map A pointer to map where the user context will be unparsed.
+ void contextToElement(data::ElementPtr map) const;
+
+ /// @brief Copy an Element map
+ ///
+ /// A previous version of this extracted comments.
+ ///
+ /// @param map A pointer to map.
+ /// @return a copy of map
+ static data::ElementPtr toElement(data::ConstElementPtr map);
+
+protected:
+
+ /// @brief Pointer to the user context (may be NULL)
+ data::ConstElementPtr user_context_;
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+
+#endif // USER_CONTEXT_H
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
new file mode 100644
index 0000000..a38f1bd
--- /dev/null
+++ b/src/lib/config/Makefile.am
@@ -0,0 +1,85 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-cfgclient.la
+libkea_cfgclient_la_SOURCES = cmds_impl.h
+libkea_cfgclient_la_SOURCES += base_command_mgr.cc base_command_mgr.h
+libkea_cfgclient_la_SOURCES += client_connection.cc client_connection.h
+libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.h
+libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
+libkea_cfgclient_la_SOURCES += config_messages.h config_messages.cc
+libkea_cfgclient_la_SOURCES += hooked_command_mgr.cc hooked_command_mgr.h
+libkea_cfgclient_la_SOURCES += timeouts.h
+libkea_cfgclient_la_SOURCES += cmd_http_listener.cc cmd_http_listener.h
+libkea_cfgclient_la_SOURCES += cmd_response_creator.cc cmd_response_creator.h
+libkea_cfgclient_la_SOURCES += cmd_response_creator_factory.h
+
+libkea_cfgclient_la_LIBADD = $(top_builddir)/src/lib/http/libkea-http.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_cfgclient_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+libkea_cfgclient_la_LDFLAGS = -no-undefined -version-info 51:0:0
+libkea_cfgclient_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+
+# The message file should be in the distribution.
+EXTRA_DIST = config_messages.mes command-socket.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f config_messages.h config_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: config_messages.h config_messages.cc
+ @echo Message files regenerated
+
+config_messages.h config_messages.cc: config_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/config/config_messages.mes
+
+else
+
+messages config_messages.h config_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cfgclient_includedir = $(pkgincludedir)/config
+libkea_cfgclient_include_HEADERS = \
+ base_command_mgr.h \
+ client_connection.h \
+ cmds_impl.h \
+ command_mgr.h \
+ config_log.h \
+ config_messages.h \
+ hooked_command_mgr.h \
+ timeouts.h
+
diff --git a/src/lib/config/Makefile.in b/src/lib/config/Makefile.in
new file mode 100644
index 0000000..bbd2e3b
--- /dev/null
+++ b/src/lib/config/Makefile.in
@@ -0,0 +1,1037 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/config
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 \
+ $(libkea_cfgclient_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)$(libkea_cfgclient_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_cfgclient_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_cfgclient_la_OBJECTS = base_command_mgr.lo \
+ client_connection.lo command_mgr.lo config_log.lo \
+ config_messages.lo hooked_command_mgr.lo cmd_http_listener.lo \
+ cmd_response_creator.lo
+libkea_cfgclient_la_OBJECTS = $(am_libkea_cfgclient_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_cfgclient_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_cfgclient_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)/base_command_mgr.Plo \
+ ./$(DEPDIR)/client_connection.Plo \
+ ./$(DEPDIR)/cmd_http_listener.Plo \
+ ./$(DEPDIR)/cmd_response_creator.Plo \
+ ./$(DEPDIR)/command_mgr.Plo ./$(DEPDIR)/config_log.Plo \
+ ./$(DEPDIR)/config_messages.Plo \
+ ./$(DEPDIR)/hooked_command_mgr.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_cfgclient_la_SOURCES)
+DIST_SOURCES = $(libkea_cfgclient_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 = $(libkea_cfgclient_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-cfgclient.la
+libkea_cfgclient_la_SOURCES = cmds_impl.h base_command_mgr.cc \
+ base_command_mgr.h client_connection.cc client_connection.h \
+ command_mgr.cc command_mgr.h config_log.h config_log.cc \
+ config_messages.h config_messages.cc hooked_command_mgr.cc \
+ hooked_command_mgr.h timeouts.h cmd_http_listener.cc \
+ cmd_http_listener.h cmd_response_creator.cc \
+ cmd_response_creator.h cmd_response_creator_factory.h
+libkea_cfgclient_la_LIBADD = \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+libkea_cfgclient_la_LDFLAGS = -no-undefined -version-info 51:0:0 \
+ $(CRYPTO_LDFLAGS)
+
+# The message file should be in the distribution.
+EXTRA_DIST = config_messages.mes command-socket.dox
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cfgclient_includedir = $(pkgincludedir)/config
+libkea_cfgclient_include_HEADERS = \
+ base_command_mgr.h \
+ client_connection.h \
+ cmds_impl.h \
+ command_mgr.h \
+ config_log.h \
+ config_messages.h \
+ hooked_command_mgr.h \
+ timeouts.h
+
+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/config/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config/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-cfgclient.la: $(libkea_cfgclient_la_OBJECTS) $(libkea_cfgclient_la_DEPENDENCIES) $(EXTRA_libkea_cfgclient_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_cfgclient_la_LINK) -rpath $(libdir) $(libkea_cfgclient_la_OBJECTS) $(libkea_cfgclient_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base_command_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/client_connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd_http_listener.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd_response_creator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hooked_command_mgr.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_cfgclient_includeHEADERS: $(libkea_cfgclient_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_cfgclient_include_HEADERS)'; test -n "$(libkea_cfgclient_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_cfgclient_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_cfgclient_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)$(libkea_cfgclient_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_cfgclient_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_cfgclient_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_cfgclient_include_HEADERS)'; test -n "$(libkea_cfgclient_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_cfgclient_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_cfgclient_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/base_command_mgr.Plo
+ -rm -f ./$(DEPDIR)/client_connection.Plo
+ -rm -f ./$(DEPDIR)/cmd_http_listener.Plo
+ -rm -f ./$(DEPDIR)/cmd_response_creator.Plo
+ -rm -f ./$(DEPDIR)/command_mgr.Plo
+ -rm -f ./$(DEPDIR)/config_log.Plo
+ -rm -f ./$(DEPDIR)/config_messages.Plo
+ -rm -f ./$(DEPDIR)/hooked_command_mgr.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-libkea_cfgclient_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)/base_command_mgr.Plo
+ -rm -f ./$(DEPDIR)/client_connection.Plo
+ -rm -f ./$(DEPDIR)/cmd_http_listener.Plo
+ -rm -f ./$(DEPDIR)/cmd_response_creator.Plo
+ -rm -f ./$(DEPDIR)/command_mgr.Plo
+ -rm -f ./$(DEPDIR)/config_log.Plo
+ -rm -f ./$(DEPDIR)/config_messages.Plo
+ -rm -f ./$(DEPDIR)/hooked_command_mgr.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_cfgclient_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_cfgclient_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_cfgclient_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f config_messages.h config_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: config_messages.h config_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@config_messages.h config_messages.cc: config_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/config/config_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages config_messages.h config_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/config/base_command_mgr.cc b/src/lib/config/base_command_mgr.cc
new file mode 100644
index 0000000..ff1d2d8
--- /dev/null
+++ b/src/lib/config/base_command_mgr.cc
@@ -0,0 +1,222 @@
+// Copyright (C) 2017-2023 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 <cc/command_interpreter.h>
+#include <config/base_command_mgr.h>
+#include <config/config_log.h>
+#include <cryptolink/crypto_hash.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <util/encode/hex.h>
+#include <util/buffer.h>
+#include <functional>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::hooks;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// Structure that holds registered hook indexes
+struct BaseCommandMgrHooks {
+ int hook_index_command_processed_; ///< index for "command_processe" hook point
+
+ /// Constructor that registers hook points for AllocationEngine
+ BaseCommandMgrHooks() {
+ hook_index_command_processed_ = HooksManager::registerHook("command_processed");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+BaseCommandMgrHooks Hooks;
+
+}; // anonymous namespace
+
+namespace isc {
+namespace config {
+
+BaseCommandMgr::BaseCommandMgr() {
+ registerCommand("list-commands", std::bind(&BaseCommandMgr::listCommandsHandler,
+ this, ph::_1, ph::_2));
+}
+
+void
+BaseCommandMgr::registerCommand(const std::string& cmd, CommandHandler handler) {
+ if (!handler) {
+ isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
+ }
+
+ HandlerContainer::const_iterator it = handlers_.find(cmd);
+ if (it != handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' is already installed.");
+ }
+
+ HandlersPair handlers;
+ handlers.handler = handler;
+ handlers_.insert(make_pair(cmd, handlers));
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::registerExtendedCommand(const std::string& cmd,
+ ExtendedCommandHandler handler) {
+ if (!handler) {
+ isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
+ }
+
+ HandlerContainer::const_iterator it = handlers_.find(cmd);
+ if (it != handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' is already installed.");
+ }
+
+ HandlersPair handlers;
+ handlers.extended_handler = handler;
+ handlers_.insert(make_pair(cmd, handlers));
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_EXTENDED_REGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::deregisterCommand(const std::string& cmd) {
+ if (cmd == "list-commands") {
+ isc_throw(InvalidCommandName,
+ "Can't uninstall internal command 'list-commands'");
+ }
+
+ HandlerContainer::iterator it = handlers_.find(cmd);
+ if (it == handlers_.end()) {
+ isc_throw(InvalidCommandName, "Handler for command '" << cmd
+ << "' not found.");
+ }
+ handlers_.erase(it);
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_DEREGISTERED).arg(cmd);
+}
+
+void
+BaseCommandMgr::deregisterAll() {
+
+ // No need to log anything here. deregisterAll is not used in production
+ // code, just in tests.
+ handlers_.clear();
+ registerCommand("list-commands",
+ std::bind(&BaseCommandMgr::listCommandsHandler, this, ph::_1, ph::_2));
+}
+
+isc::data::ConstElementPtr
+BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
+ if (!cmd) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Command processing failed: NULL command parameter"));
+ }
+
+ try {
+ ConstElementPtr arg;
+ std::string name = parseCommand(arg, cmd);
+
+ LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
+
+ ConstElementPtr response = handleCommand(name, arg, cmd);
+
+ // If there any callouts for command-processed hook point call them
+ if (HooksManager::calloutsPresent(Hooks.hook_index_command_processed_)) {
+ // Commands are not associated with anything so there's no pre-existing
+ // callout.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Add the command name, arguments, and response to the callout context
+ callout_handle->setArgument("name", name);
+ callout_handle->setArgument("arguments", arg);
+ callout_handle->setArgument("response", response);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_command_processed_,
+ *callout_handle);
+
+ // Refresh the response from the callout context in case it was modified.
+ // @todo Should we allow this?
+ callout_handle->getArgument("response", response);
+ }
+
+ return (response);
+
+ } catch (const Exception& e) {
+ LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ std::string("Error during command processing: ")
+ + e.what()));
+ }
+}
+
+ConstElementPtr
+BaseCommandMgr::handleCommand(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd) {
+ auto it = handlers_.find(cmd_name);
+ if (it == handlers_.end()) {
+ // Ok, there's no such command.
+ return (createAnswer(CONTROL_RESULT_COMMAND_UNSUPPORTED,
+ "'" + cmd_name + "' command not supported."));
+ }
+
+ // Call the actual handler and return whatever it returned
+ if (it->second.handler) {
+ return (it->second.handler(cmd_name, params));
+ }
+ return (it->second.extended_handler(cmd_name, params, original_cmd));
+}
+
+isc::data::ConstElementPtr
+BaseCommandMgr::listCommandsHandler(const std::string& /* name */,
+ const isc::data::ConstElementPtr& ) {
+ using namespace isc::data;
+ ElementPtr commands = Element::createList();
+ for (HandlerContainer::const_iterator it = handlers_.begin();
+ it != handlers_.end(); ++it) {
+ commands->add(Element::create(it->first));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS, commands));
+}
+
+std::string
+BaseCommandMgr::getHash(const isc::data::ConstElementPtr& config) {
+
+ // Sanity.
+ if (!config) {
+ isc_throw(Unexpected, "BaseCommandMgr::getHash called with null");
+ }
+
+ // First, get the string representation.
+ std::string config_txt = config->str();
+ isc::util::OutputBuffer hash_data(0);
+ isc::cryptolink::digest(config_txt.c_str(),
+ config_txt.size(),
+ isc::cryptolink::HashAlgorithm::SHA256,
+ hash_data);
+
+ // Now we need to convert this to output buffer to vector, which can be accepted
+ // by encodeHex().
+ std::vector<uint8_t> hash;
+ hash.resize(hash_data.getLength());
+ if (hash.size() > 0) {
+ memmove(&hash[0], hash_data.getData(), hash.size());
+ }
+
+ // Now encode the value as base64 and we're done here.
+ return (isc::util::encode::encodeHex(hash));
+}
+
+} // namespace isc::config
+} // namespace isc
diff --git a/src/lib/config/base_command_mgr.h b/src/lib/config/base_command_mgr.h
new file mode 100644
index 0000000..916eebc
--- /dev/null
+++ b/src/lib/config/base_command_mgr.h
@@ -0,0 +1,218 @@
+// Copyright (C) 2017-2023 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 BASE_COMMAND_MGR_H
+#define BASE_COMMAND_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <functional>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace config {
+
+/// @brief Exception indicating that the handler specified is not valid
+class InvalidCommandHandler : public Exception {
+public:
+ InvalidCommandHandler(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception indicating that the command name is not valid
+class InvalidCommandName : public Exception {
+public:
+ InvalidCommandName(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Commands Manager, responsible for processing external commands.
+///
+/// Commands Manager is a generic interface for handling external commands.
+/// Commands are received over control sockets. Derivations of this class
+/// provide implementations of the control socket layers, e.g. unix domain
+/// sockets, TCP sockets etc. This base class merely provides methods to manage
+/// command handling functions, i.e. register commands, deregister commands.
+/// It also includes a @ref BaseCommandMgr::processCommand method which
+/// uses the command as an input and invokes appropriate handlers.
+///
+/// The commands and responses are formatted using JSON.
+/// See https://gitlab.isc.org/isc-projects/kea/wikis/designs/Stats-design
+/// for details.
+///
+/// Below is an example of the command using JSON format:
+/// @code
+/// {
+/// "command": "statistic-get",
+/// "arguments": {
+/// "name": "received-packets"
+/// }
+/// }
+/// @endcode
+///
+/// And the response is:
+///
+/// @code
+/// {
+/// "result": 0,
+/// "arguments": {
+/// "received-packets": [ [ 1234, "2015-04-15 12:34:45.123" ] ]
+/// }
+/// }
+/// @endcode
+///
+/// BaseCommandsMgr does not implement the commands (except one,
+/// "list-commands") itself, but rather provides an interface
+/// (see @ref registerCommand, @ref deregisterCommand, @ref processCommand)
+/// for other components to use it.
+class BaseCommandMgr {
+public:
+
+ /// @brief Defines command handler type
+ ///
+ /// Command handlers are expected to use this format.
+ ///
+ /// @param name name of the commands
+ /// @param params parameters specific to the command
+ /// @return response (created with createAnswer())
+ typedef std::function<isc::data::ConstElementPtr (const std::string& name,
+ const isc::data::ConstElementPtr& params)> CommandHandler;
+
+ /// @brief Defines extended command handler type.
+ ///
+ /// This command handler includes third parameter which holds the
+ /// entire command control message. The handler can retrieve
+ /// additional information from this parameter, e.g. 'service'.
+ ///
+ /// @param name name of the commands
+ /// @param params parameters specific to the command
+ /// @param original original control command.
+ /// @return response (created with createAnswer())
+ typedef std::function<isc::data::ConstElementPtr (const std::string& name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original)> ExtendedCommandHandler;
+
+ /// @brief Constructor.
+ ///
+ /// Registers hookpoint "command-processed"
+ /// Registers "list-commands" command.
+ BaseCommandMgr();
+
+ /// @brief Destructor.
+ virtual ~BaseCommandMgr() { };
+
+ /// @brief Triggers command processing.
+ ///
+ /// This method processes specified command. The command is specified using
+ /// a single Element. See @ref BaseCommandMgr for description of its syntax.
+ /// After the command has been handled, callouts for the hook point,
+ /// "command-processed" will be invoked.
+ ///
+ /// This method is virtual so it can be overridden in derived classes to
+ /// pre-process command and post-process response if necessary.
+ ///
+ /// This method is an entry point for dealing with a command. Internally
+ /// it calls @c BaseCommandMgr::handleCommand.
+ ///
+ /// @param cmd Pointer to the data element representing command in JSON
+ /// format.
+ virtual isc::data::ConstElementPtr
+ processCommand(const isc::data::ConstElementPtr& cmd);
+
+ /// @brief Registers specified command handler for a given command
+ ///
+ /// @param cmd Name of the command to be handled.
+ /// @param handler Pointer to the method that will handle the command.
+ void registerCommand(const std::string& cmd, CommandHandler handler);
+
+ /// @brief Registers specified command handler for a given command.
+ ///
+ /// This variant of the method uses extended command handler which, besides
+ /// command name and arguments, also has a third parameter 'original_cmd'
+ /// in its signature. Such handlers can retrieve additional parameters from
+ /// the command, e.g. 'service' indicating where the command should be
+ /// routed.
+ ///
+ /// @param cmd Name of the command to be handled.
+ /// @param handler Pointer to the method that will handle the command.
+ void registerExtendedCommand(const std::string& cmd,
+ ExtendedCommandHandler handler);
+
+ /// @brief Deregisters specified command handler.
+ ///
+ /// @param cmd Name of the command that's no longer handled.
+ void deregisterCommand(const std::string& cmd);
+
+ /// @brief Auxiliary method that removes all installed commands.
+ ///
+ /// The only unwipeable method is list-commands, which is internally
+ /// handled at all times.
+ void deregisterAll();
+
+ /// @brief returns a hash of a given Element structure
+ ///
+ /// The hash is currently implemented as SHA256 on the string
+ // representation of the structure.
+ ///
+ /// @param config typically full config, but hash can be calculated on any structure
+ /// @return hash of string representation
+ static std::string getHash(const isc::data::ConstElementPtr& config);
+
+protected:
+
+ /// @brief Handles the command having a given name and arguments.
+ ///
+ /// This method can be overridden in the derived classes to provide
+ /// custom logic for processing commands. For example, the
+ /// @ref HookedCommandMgr extends this method to delegate commands
+ /// processing to a hook library.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Pointer to the entire command received. It may
+ /// be sometimes useful to retrieve additional parameters from this
+ /// command.
+ ///
+ /// @return Pointer to the const data element representing response
+ /// to a command.
+ virtual isc::data::ConstElementPtr
+ handleCommand(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd);
+
+ struct HandlersPair {
+ CommandHandler handler;
+ ExtendedCommandHandler extended_handler;
+ };
+
+ /// @brief Type of the container for command handlers.
+ typedef std::map<std::string, HandlersPair> HandlerContainer;
+
+ /// @brief Container for command handlers.
+ HandlerContainer handlers_;
+
+private:
+
+ /// @brief 'list-commands' command handler.
+ ///
+ /// This method implements command 'list-commands'. It returns a list of all
+ /// currently supported commands.
+ ///
+ /// @param name Name of the command (should always be 'list-commands').
+ /// @param params Additional parameters (ignored).
+ ///
+ /// @return Pointer to the structure that includes all currently supported
+ /// commands.
+ isc::data::ConstElementPtr
+ listCommandsHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/client_connection.cc b/src/lib/config/client_connection.cc
new file mode 100644
index 0000000..6217c1a
--- /dev/null
+++ b/src/lib/config/client_connection.cc
@@ -0,0 +1,285 @@
+// Copyright (C) 2017-2020 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/unix_domain_socket.h>
+#include <cc/json_feed.h>
+#include <config/client_connection.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <array>
+#include <functional>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace config {
+
+/// @brief Implementation of the @ref ClientConnection.
+class ClientConnectionImpl : public boost::enable_shared_from_this<ClientConnectionImpl> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit ClientConnectionImpl(IOService& io_service);
+
+ /// @brief This method schedules timer or reschedules existing timer.
+ ///
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ void scheduleTimer(ClientConnection::Handler handler);
+
+ /// @brief Starts asynchronous transaction with a remote endpoint.
+ ///
+ /// See @ref ClientConnection::start documentation for the details.
+ ///
+ /// @param socket_path Path to the socket description that the server
+ /// is bound to.
+ /// @param command Control command to be sent to the server.
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ /// @param timeout Connection timeout in milliseconds.
+ void start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout);
+
+ /// @brief Closes the socket.
+ void stop();
+
+ /// @brief Starts asynchronous send.
+ ///
+ /// This method may be called multiple times internally when the command
+ /// is large and can't be sent all at once.
+ ///
+ /// @param buffer Pointer to the buffer holding input data.
+ /// @param length Length of the data in the input buffer.
+ /// @param handler User supplied callback invoked after the chunk of data
+ /// has been sent.
+ void doSend(const void* buffer, const size_t length,
+ ClientConnection::Handler handler);
+
+ /// @brief Starts asynchronous receive from the server.
+ ///
+ /// This method may be called multiple times internally if the response
+ /// is large. The @ref JSONFeed instance is used to detect the boundaries
+ /// of the command within the stream. Once the entire command has been
+ /// received the user callback is invoked and the instance of the
+ /// @ref JSONFeed is returned.
+ ///
+ /// @param handler User supplied callback.
+ void doReceive(ClientConnection::Handler handler);
+
+ /// @brief Terminates the connection and invokes a user callback indicating
+ /// an error.
+ ///
+ /// @param ec Error code.
+ /// @param handler User callback.
+ void terminate(const boost::system::error_code& ec,
+ ClientConnection::Handler handler);
+
+ /// @brief Callback invoked when the timeout occurs.
+ ///
+ /// It calls @ref terminate with the @c boost::asio::error::timed_out.
+ void timeoutCallback(ClientConnection::Handler handler);
+
+private:
+
+ /// @brief Unix domain socket used for communication with a server.
+ UnixDomainSocket socket_;
+
+ /// @brief Pointer to the @ref JSONFeed holding a response.
+ ///
+ ///It may be a null pointer until some part of a response has been received.
+ JSONFeedPtr feed_;
+
+ /// @brief Holds the entire command being transmitted over the unix
+ /// socket.
+ std::string current_command_;
+
+ /// @brief Buffer into which chunks of the response are received.
+ std::array<char, 32768> read_buf_;
+
+ /// @brief Instance of the interval timer protecting against timeouts.
+ IntervalTimer timer_;
+
+ /// @brief Timeout value used for the timer.
+ long timeout_;
+};
+
+ClientConnectionImpl::ClientConnectionImpl(IOService& io_service)
+ : socket_(io_service), feed_(), current_command_(), timer_(io_service),
+ timeout_(0) {
+}
+
+void
+ClientConnectionImpl::scheduleTimer(ClientConnection::Handler handler) {
+ if (timeout_ > 0) {
+ timer_.setup(std::bind(&ClientConnectionImpl::timeoutCallback,
+ this, handler),
+ timeout_, IntervalTimer::ONE_SHOT);
+ }
+}
+
+void
+ClientConnectionImpl::start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout) {
+ // Start the timer protecting against timeouts.
+ timeout_ = timeout.timeout_;
+ scheduleTimer(handler);
+
+ // Store the command in the class member to make sure it is valid
+ // the entire time.
+ current_command_.assign(command.control_command_);
+
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async connect.
+ auto self(shared_from_this());
+ // Start asynchronous connect. This will return immediately.
+ socket_.asyncConnect(socket_path.socket_path_,
+ [this, self, command, handler](const boost::system::error_code& ec) {
+ // We failed to connect so we can't proceed. Simply clean up
+ // and invoke the user callback to signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // Connection successful. Transmit the command to the remote
+ // endpoint asynchronously.
+ doSend(current_command_.c_str(), current_command_.length(),
+ handler);
+ }
+ });
+}
+
+void
+ClientConnectionImpl::doSend(const void* buffer, const size_t length,
+ ClientConnection::Handler handler) {
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async send.
+ auto self(shared_from_this());
+ // Start asynchronous transmission of the command. This will return
+ // immediately.
+ socket_.asyncSend(buffer, length,
+ [this, self, buffer, length, handler]
+ (const boost::system::error_code& ec, size_t bytes_transferred) {
+ // An error has occurred while sending. Close the connection and
+ // signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // Sending is in progress, so push back the timeout.
+ scheduleTimer(handler);
+
+ // If the number of bytes we have managed to send so far is
+ // lower than the amount of data we're trying to send, we
+ // have to schedule another send to deliver the rest of
+ // the data.
+ if (bytes_transferred < length) {
+ doSend(static_cast<const char*>(buffer) + bytes_transferred,
+ length - bytes_transferred, handler);
+
+ } else {
+ // We have sent all the data. Start receiving a response.
+ doReceive(handler);
+ }
+ }
+ });
+}
+
+void
+ClientConnectionImpl::doReceive(ClientConnection::Handler handler) {
+ // Pass self to lambda to make sure that the instance of this class
+ // lives as long as the lambda is held for async receive.
+ auto self(shared_from_this());
+ socket_.asyncReceive(&read_buf_[0], read_buf_.size(),
+ [this, self, handler]
+ (const boost::system::error_code& ec, size_t length) {
+ // An error has occurred while receiving the data. Close the connection
+ // and signal an error.
+ if (ec) {
+ // This doesn't throw.
+ terminate(ec, handler);
+
+ } else {
+ // Receiving is in progress, so push back the timeout.
+ scheduleTimer(handler);
+
+ std::string x(&read_buf_[0], length);
+ // Lazy initialization of the JSONFeed. The feed will be "parsing"
+ // received JSON stream and will detect when the whole response
+ // has been received.
+ if (!feed_) {
+ feed_.reset(new JSONFeed());
+ feed_->initModel();
+ }
+ // Put everything we have received so far into the feed and process
+ // the data.
+ feed_->postBuffer(&read_buf_[0], length);
+ feed_->poll();
+ // If the feed indicates that only a part of the response has been
+ // received, schedule another receive to get more data.
+ if (feed_->needData()) {
+ doReceive(handler);
+
+ } else {
+ // We have received the entire response, let's call the handler
+ // and indicate success.
+ terminate(ec, handler);
+ }
+ }
+ });
+}
+
+void
+ClientConnectionImpl::terminate(const boost::system::error_code& ec,
+ ClientConnection::Handler handler) {
+ try {
+ timer_.cancel();
+ socket_.close();
+ current_command_.clear();
+ handler(ec, feed_);
+
+ } catch (...) {
+ // None of these operations should throw. In particular, the handler
+ // should not throw but if it has been misimplemented, we want to make
+ // sure we don't emit any exceptions from here.
+ }
+}
+
+void
+ClientConnectionImpl::timeoutCallback(ClientConnection::Handler handler) {
+ // Timeout has occurred. The remote server didn't provide the entire
+ // response within the given time frame. Let's close the connection
+ // and signal the timeout.
+ terminate(boost::asio::error::timed_out, handler);
+}
+
+ClientConnection::ClientConnection(asiolink::IOService& io_service)
+ : impl_(new ClientConnectionImpl(io_service)) {
+}
+
+void
+ClientConnection::start(const ClientConnection::SocketPath& socket_path,
+ const ClientConnection::ControlCommand& command,
+ ClientConnection::Handler handler,
+ const ClientConnection::Timeout& timeout) {
+ impl_->start(socket_path, command, handler, timeout);
+}
+
+
+} // end of namespace config
+} // end of namespace isc
diff --git a/src/lib/config/client_connection.h b/src/lib/config/client_connection.h
new file mode 100644
index 0000000..6321f34
--- /dev/null
+++ b/src/lib/config/client_connection.h
@@ -0,0 +1,159 @@
+// Copyright (C) 2017-2018 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 CLIENT_CONNECTION_H
+#define CLIENT_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <cc/json_feed.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+
+namespace isc {
+namespace config {
+
+class ClientConnectionImpl;
+
+/// @brief Represents client side connection over the unix domain socket.
+///
+/// This class represents a client side connection between the controlling
+/// client and the server exposing control API over a unix domain socket.
+/// In particular, this class is used by the Kea Control Agent to establish
+/// connections with respective Kea services to forward received commands.
+/// As of Kea 1.2 the servers can handle a single connection at the time.
+/// In the future, we're planning to support multiple simulatenous connections.
+/// In this case, each connection will be handled by a unique instance of the
+/// @ref ClientConnection class.
+///
+/// The @ref ClientConnection supports asynchronous connections. A caller
+/// creates an instance of the @ref ClientConnection and calls
+/// @ref ClientConnection::start to start asynchronous communication with
+/// a remote server. The caller provides a pointer to the callback function
+/// (handler) which will be called when the communication with the server
+/// completes, i.e. the command is sent to the server and the response
+/// from the server is received. If an error occurs, the callback is
+/// invoked with an error code indicating a reason for the failure.
+///
+/// The documentation of the @ref ClientConnection::start explains the
+/// sequence of operations performed by this class.
+///
+/// Even though the @ref ClientConnection is asynchronous in nature, it
+/// can also be used in cases requiring synchronous communication. As it
+/// has been already mentioned, the servers in Kea 1.2 do not support
+/// multiple concurrent connections. The following pseudo code demonstrates
+/// how to perform synchronous transaction using this class.
+///
+/// @code
+/// IOService io_service;
+/// ClientConnection conn(io_service);
+/// bool cb_invoked = false;
+/// conn.start(ClientConnection::SocketPath("/tmp/kea.sock"),
+/// ClientConnection::ControlCommand(command),
+/// [this, &cb_invoked](const boost::system::error_code& ec,
+/// const ConstJSONFeedPtr& feed) {
+/// cb_invoked = true;
+/// if (ec) {
+/// ... handle error here ...
+/// } else {
+/// ... use feed to retrieve the response ...
+/// }
+/// }
+/// );
+/// while (!cb_invoked) {
+/// io_service.run_one();
+/// }
+/// @endcode
+///
+class ClientConnection {
+public:
+
+ /// @name Structures used for strong typing.
+ ///
+ //@{
+
+ /// @brief Encapsulates socket path.
+ struct SocketPath {
+ explicit SocketPath(const std::string& socket_path)
+ : socket_path_(socket_path) { }
+
+ std::string socket_path_;
+ };
+
+ /// @brief Encapsulates control command.
+ struct ControlCommand {
+ explicit ControlCommand(const std::string control_command)
+ : control_command_(control_command) { }
+
+ std::string control_command_;
+ };
+
+ /// @brief Encapsulates timeout value.
+ struct Timeout {
+ explicit Timeout(const long timeout)
+ : timeout_(timeout) { }
+
+ long timeout_;
+ };
+
+ //@}
+
+ /// @brief Type of the callback invoked when the communication with
+ /// the server is complete or an error has occurred.
+ typedef std::function<void(const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& feed)> Handler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service.
+ explicit ClientConnection(asiolink::IOService& io_service);
+
+ /// @brief Starts asynchronous transaction with a remote endpoint.
+ ///
+ /// Starts asynchronous connection with the remote endpoint. If the
+ /// connection is successful, the control command is asynchronously
+ /// sent to the remote endpoint. When the entire command has been sent,
+ /// the response is read asynchronously, possibly in multiple chunks.
+ ///
+ /// The timeout is specified in milliseconds. The corresponding timer
+ /// measures the connection idle time. If the transaction is progressing,
+ /// the timer is updated accordingly. If the connection idle time is
+ /// longer than the timeout value the connection is closed and the
+ /// callback is called with the error code of
+ /// @c boost::asio::error::timed_out.
+ ///
+ /// In other cases, the callback is called with the error code returned
+ /// by the boost asynchronous operations. If the transaction is successful
+ /// the 'success' status is indicated with the error code. In addition
+ /// the instance of the @ref JSONFeed is returned to the caller. It can
+ /// be used to retrieve parsed response from the server. Note that the
+ /// response may still be malformed, even if no error is signalled in
+ /// the handler. The @ref JSONFeed::toElement will return a parsing
+ /// error if the JSON appears to be malformed.
+ ///
+ /// @param socket_path Path to the socket description that the server
+ /// is bound to.
+ /// @param command Control command to be sent to the server.
+ /// @param handler Pointer to the user supplied callback function which
+ /// should be invoked when transaction completes or when an error has
+ /// occurred during the transaction.
+ /// @param timeout Connection timeout in milliseconds.
+ void start(const SocketPath& socket_path, const ControlCommand& command,
+ Handler handler, const Timeout& timeout = Timeout(5000));
+
+private:
+
+ /// @brief Pointer to the implementation.
+ boost::shared_ptr<ClientConnectionImpl> impl_;
+
+};
+
+/// @brief Type of the pointer to the @ref ClientConnection object.
+typedef boost::shared_ptr<ClientConnection> ClientConnectionPtr;
+
+} // end of namespace config
+} // end of namespace isc
+
+#endif // CLIENT_CONNECTION_H
diff --git a/src/lib/config/cmd_http_listener.cc b/src/lib/config/cmd_http_listener.cc
new file mode 100644
index 0000000..aa917c7
--- /dev/null
+++ b/src/lib/config/cmd_http_listener.cc
@@ -0,0 +1,165 @@
+// Copyright (C) 2021-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <asiolink/io_service.h>
+#include <cmd_http_listener.h>
+#include <cmd_response_creator_factory.h>
+#include <config_log.h>
+#include <config/timeouts.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::util;
+
+namespace isc {
+namespace config {
+
+CmdHttpListener::CmdHttpListener(const IOAddress& address, const uint16_t port,
+ const uint16_t thread_pool_size /* = 1 */,
+ TlsContextPtr context /* = () */)
+ : address_(address), port_(port), thread_io_service_(), http_listener_(),
+ thread_pool_size_(thread_pool_size), thread_pool_(),
+ tls_context_(context) {
+}
+
+CmdHttpListener::~CmdHttpListener() {
+ stop();
+}
+
+void
+CmdHttpListener::start() {
+ // We must be in multi-threading mode.
+ if (!MultiThreadingMgr::instance().getMode()) {
+ isc_throw(InvalidOperation, "CmdHttpListener cannot be started"
+ " when multi-threading is disabled");
+ }
+
+ // Punt if we're already started.
+ if (!isStopped()) {
+ isc_throw(InvalidOperation, "CmdHttpListener already started!");
+ }
+
+ try {
+ // Create a new IOService.
+ thread_io_service_.reset(new IOService());
+
+ // Create the response creator factory first. It will be used to
+ // generate response creators. Each response creator will be
+ // used to generate the answer to specific request.
+ HttpResponseCreatorFactoryPtr rcf(new CmdResponseCreatorFactory());
+
+ // Create the HTTP listener. It will open up a TCP socket and be
+ // prepared to accept incoming connections.
+ http_listener_.reset(new HttpListener(*thread_io_service_, address_,
+ port_, tls_context_, rcf,
+ HttpListener::RequestTimeout(TIMEOUT_AGENT_RECEIVE_COMMAND),
+ HttpListener::IdleTimeout(TIMEOUT_AGENT_IDLE_CONNECTION_TIMEOUT)));
+
+ // Instruct the HTTP listener to actually open socket, install
+ // callback and start listening.
+ http_listener_->start();
+
+ // Create the thread pool with immediate start.
+ thread_pool_.reset(new IoServiceThreadPool(thread_io_service_, thread_pool_size_));
+
+ // OK, seems like we're good to go.
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_STARTED)
+ .arg(thread_pool_size_)
+ .arg(address_)
+ .arg(port_)
+ .arg(tls_context_ ? "true" : "false");
+ } catch (const std::exception& ex) {
+ thread_io_service_.reset();
+ http_listener_.reset();
+ thread_pool_.reset();
+ isc_throw(Unexpected, "CmdHttpListener::run failed: " << ex.what());
+ }
+}
+
+void
+CmdHttpListener::checkPermissions() {
+ if (thread_pool_) {
+ thread_pool_->checkPausePermissions();
+ }
+}
+
+void
+CmdHttpListener::pause() {
+ if (thread_pool_) {
+ thread_pool_->pause();
+ }
+}
+
+void
+CmdHttpListener::resume() {
+ if (thread_pool_) {
+ thread_pool_->run();
+ }
+}
+
+void
+CmdHttpListener::stop() {
+ // Nothing to do.
+ if (!thread_io_service_) {
+ return;
+ }
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_STOPPING)
+ .arg(address_)
+ .arg(port_);
+
+ // Stop the thread pool.
+ thread_pool_->stop();
+
+ // Get rid of the listener.
+ http_listener_.reset();
+
+ // Ditch the IOService.
+ thread_io_service_.reset();
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_HTTP_LISTENER_STOPPED)
+ .arg(address_)
+ .arg(port_);
+}
+
+bool
+CmdHttpListener::isRunning() {
+ if (thread_pool_) {
+ return (thread_pool_->isRunning());
+ }
+
+ return (false);
+}
+
+bool
+CmdHttpListener::isStopped() {
+ if (thread_pool_) {
+ return (thread_pool_->isStopped());
+ }
+
+ return (true);
+}
+
+bool
+CmdHttpListener::isPaused() {
+ if (thread_pool_) {
+ return (thread_pool_->isPaused());
+ }
+
+ return (false);
+}
+
+} // namespace config
+} // namespace isc
diff --git a/src/lib/config/cmd_http_listener.h b/src/lib/config/cmd_http_listener.h
new file mode 100644
index 0000000..4a87210
--- /dev/null
+++ b/src/lib/config/cmd_http_listener.h
@@ -0,0 +1,160 @@
+// Copyright (C) 2021-2022 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 CMD_HTTP_LISTENER_H
+#define CMD_HTTP_LISTENER_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_service_thread_pool.h>
+#include <http/listener.h>
+#include <thread>
+#include <vector>
+
+namespace isc {
+namespace config {
+
+/// @brief A multi-threaded HTTP listener that can process API commands
+/// requests.
+///
+/// This class will listen for Command API client requests on a given
+/// IP address and port. It uses its own IOService instance to drive
+/// a thread-pool which can service multiple connections concurrently.
+/// The number of concurrent connections is currently limited to the
+/// configured thread pool size.
+///
+/// @note This class is NOT compatible with Kea core single-threading.
+/// It is incumbent upon the owner to ensure the Kea core multi-threading
+/// is (or will be) enabled when creating instances of this class.
+class CmdHttpListener {
+public:
+ /// @brief Constructor
+ CmdHttpListener(const asiolink::IOAddress& address, const uint16_t port,
+ const uint16_t thread_pool_size = 1,
+ asiolink::TlsContextPtr context = asiolink::TlsContextPtr());
+
+ /// @brief Destructor
+ virtual ~CmdHttpListener();
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions();
+
+ /// @brief Starts running the listener's thread pool.
+ void start();
+
+ /// @brief Pauses the listener's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ void pause();
+
+ /// @brief Resumes running the listener's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ void resume();
+
+ /// @brief Stops the listener's thread pool.
+ void stop();
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning();
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool does not exist or it is in the STOPPED
+ /// state, false otherwise.
+ bool isStopped();
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused();
+
+ /// @brief Fetches the IP address on which to listen.
+ ///
+ /// @return IOAddress containing the address on which to listen.
+ isc::asiolink::IOAddress getAddress() const {
+ return (address_);
+ }
+
+ /// @brief Fetches the port number on which to listen.
+ ///
+ /// @return uint16_t containing the port number on which to listen.
+ uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return uint16_t containing the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() const {
+ return (thread_pool_size_);
+ }
+
+ /// @brief Fetches the TLS context.
+ ///
+ /// @return TLS context.
+ asiolink::TlsContextPtr getTlsContext() const {
+ return (tls_context_);
+ }
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return uint16_t containing the number of running threads.
+ uint16_t getThreadCount() const {
+ if (!thread_pool_) {
+ return (0);
+ }
+
+ return (thread_pool_->getThreadCount());
+ }
+
+ /// @brief Fetches a pointer to the internal IOService used to
+ /// drive the thread-pool in multi-threaded mode.
+ ///
+ /// @return pointer to the IOService instance, or an empty pointer
+ /// in single-threaded mode.
+ asiolink::IOServicePtr getThreadIOService() const {
+ return (thread_io_service_);
+ }
+
+private:
+ /// @brief IP address on which to listen.
+ isc::asiolink::IOAddress address_;
+
+ /// @brief Port on which to listen.
+ uint16_t port_;
+
+ /// @brief IOService instance that drives our IO.
+ isc::asiolink::IOServicePtr thread_io_service_;
+
+ /// @brief The HttpListener instance
+ http::HttpListenerPtr http_listener_;
+
+ /// @brief The number of threads that will call IOService_context::run().
+ std::size_t thread_pool_size_;
+
+ /// @brief The pool of threads that do IO work.
+ asiolink::IoServiceThreadPoolPtr thread_pool_;
+
+ /// @brief The TLS context.
+ asiolink::TlsContextPtr tls_context_;
+};
+
+/// @brief Defines a shared pointer to CmdHttpListener.
+typedef boost::shared_ptr<CmdHttpListener> CmdHttpListenerPtr;
+
+} // namespace isc::config
+} // namespace isc
+
+#endif // CMD_HTTP_LISTENER_H
diff --git a/src/lib/config/cmd_response_creator.cc b/src/lib/config/cmd_response_creator.cc
new file mode 100644
index 0000000..c586d98
--- /dev/null
+++ b/src/lib/config/cmd_response_creator.cc
@@ -0,0 +1,176 @@
+// Copyright (C) 2021-2022 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 <config/cmd_response_creator.h>
+#include <config/command_mgr.h>
+#include <config/config_log.h>
+#include <cc/command_interpreter.h>
+#include <http/post_request_json.h>
+#include <http/response_json.h>
+#include <boost/pointer_cast.hpp>
+#include <iostream>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace std;
+
+namespace isc {
+namespace config {
+
+HttpAuthConfigPtr CmdResponseCreator::http_auth_config_;
+
+unordered_set<string> CmdResponseCreator::command_accept_list_;
+
+HttpRequestPtr
+CmdResponseCreator::createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+}
+
+HttpResponsePtr
+CmdResponseCreator::
+createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
+ response->finalize();
+ return (response);
+}
+
+HttpResponsePtr
+CmdResponseCreator::
+createStockHttpResponseInternal(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed OK).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // We only accept HTTP version 1.0 or 1.1. If other version number is found
+ // we fall back to HTTP/1.0.
+ if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) {
+ http_version.major_ = 1;
+ http_version.minor_ = 0;
+ }
+ // This will generate the response holding JSON content.
+ HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
+ return (response);
+}
+
+HttpResponsePtr
+CmdResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) {
+ HttpResponseJsonPtr http_response;
+
+ // Check the basic HTTP authentication.
+ if (http_auth_config_) {
+ http_response = http_auth_config_->checkAuth(*this, request);
+ if (http_response) {
+ return (http_response);
+ }
+ }
+
+ // The request is always non-null, because this is verified by the
+ // createHttpResponse method. Let's try to convert it to the
+ // PostHttpRequestJson type as this is the type generated by the
+ // createNewHttpRequest. If the conversion result is null it means that
+ // the caller did not use createNewHttpRequest method to create this
+ // instance. This is considered an error in the server logic.
+ PostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast<
+ PostHttpRequestJson>(request);
+ if (!request_json) {
+ // Notify the client that we have a problem with our server.
+ return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
+ }
+
+ // We have already checked that the request is finalized so the call
+ // to getBodyAsJson must not trigger an exception.
+ ConstElementPtr command = request_json->getBodyAsJson();
+
+ // Filter the command.
+ http_response = filterCommand(request, command, command_accept_list_);
+ if (http_response) {
+ return (http_response);
+ }
+
+ // Process command doesn't generate exceptions but can possibly return
+ // null response, if the handler is not implemented properly. This is
+ // again an internal server issue.
+ ConstElementPtr response = config::CommandMgr::instance().processCommand(command);
+
+ if (!response) {
+ // Notify the client that we have a problem with our server.
+ return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
+ }
+
+ // Normal Responses coming from the Kea Control Agent must always be wrapped in
+ // a list as they may contain responses from multiple daemons.
+ // If we're emulating that for backward compatibility, then we need to wrap
+ // the answer in a list if it isn't in one already.
+ if (emulateAgentResponse() && (response->getType() != Element::list)) {
+ ElementPtr response_list = Element::createList();
+ response_list->add(boost::const_pointer_cast<Element>(response));
+ response = response_list;
+ }
+
+ // The response is OK, so let's create new HTTP response with the status OK.
+ http_response = boost::dynamic_pointer_cast<
+ HttpResponseJson>(createStockHttpResponseInternal(request, HttpStatusCode::OK));
+ http_response->setBodyAsJson(response);
+ http_response->finalize();
+
+ return (http_response);
+}
+
+HttpResponseJsonPtr
+CmdResponseCreator::filterCommand(const HttpRequestPtr& request,
+ const ConstElementPtr& body,
+ const unordered_set<string>& accept) {
+ HttpResponseJsonPtr response;
+ if (!body || accept.empty()) {
+ return (response);
+ }
+ if (body->getType() != Element::map) {
+ return (response);
+ }
+ ConstElementPtr elem = body->get(CONTROL_COMMAND);
+ if (!elem || (elem->getType() != Element::string)) {
+ return (response);
+ }
+ string command = elem->stringValue();
+ if (command.empty() || accept.count(command)) {
+ return (response);
+ }
+
+ // Reject the command.
+ LOG_DEBUG(command_logger, DBG_COMMAND,
+ COMMAND_HTTP_LISTENER_COMMAND_REJECTED)
+ .arg(command)
+ .arg(request->getRemote());
+ // From CtrlAgentResponseCreator::createStockHttpResponseInternal.
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ if ((http_version < HttpVersion(1, 0)) ||
+ (HttpVersion(1, 1) < http_version)) {
+ http_version.major_ = 1;
+ http_version.minor_ = 0;
+ }
+ HttpStatusCode status_code = HttpStatusCode::FORBIDDEN;
+ response.reset(new HttpResponseJson(http_version, status_code));
+ ElementPtr response_body = Element::createMap();
+ uint16_t result = HttpResponse::statusCodeToNumber(status_code);
+ response_body->set(CONTROL_RESULT,
+ Element::create(static_cast<long long>(result)));
+ const string& text = HttpResponse::statusCodeToString(status_code);
+ response_body->set(CONTROL_TEXT, Element::create(text));
+ response->setBodyAsJson(response_body);
+ response->finalize();
+ return (response);
+}
+
+} // end of namespace isc::config
+} // end of namespace isc
diff --git a/src/lib/config/cmd_response_creator.h b/src/lib/config/cmd_response_creator.h
new file mode 100644
index 0000000..2d76749
--- /dev/null
+++ b/src/lib/config/cmd_response_creator.h
@@ -0,0 +1,132 @@
+// Copyright (C) 2021-2022 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 CMD__RESPONSE_CREATOR_H
+#define CMD__RESPONSE_CREATOR_H
+
+#include <http/response_creator.h>
+#include <http/basic_auth_config.h>
+#include <boost/shared_ptr.hpp>
+#include <unordered_set>
+
+namespace isc {
+namespace config {
+
+/// @brief Concrete implementation of the HTTP response creator used
+/// for processing API commands
+///
+/// See the documentation of the @ref isc::http::HttpResponseCreator for
+/// the basic information how HTTP response creators are utilized by
+/// the libkea-http library to generate HTTP responses.
+///
+/// This creator expects that received requests are encapsulated in the
+/// @ref isc::http::PostHttpRequestJson objects. The generated responses
+/// are encapsulated in the HttpResponseJson objects.
+///
+/// This class uses @ref CommandMgr singleton to process commands
+/// conveyed in the HTTP body. The JSON responses returned by the manager
+/// are placed in the body of the generated HTTP responses.
+class CmdResponseCreator : public http::HttpResponseCreator {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param emulate_agent_response if true, responses for normal
+ /// command outcomes are guaranteed to be wrapped in an Element::list.
+ /// This emulates how kea-ctrl-agent forms responses. Defaults to true.
+ CmdResponseCreator(bool emulate_agent_response = true)
+ : emulate_agent_response_(emulate_agent_response) {};
+
+ /// @brief Create a new request.
+ ///
+ /// This method creates a bare instance of the @ref
+ /// isc::http::PostHttpRequestJson.
+ ///
+ /// @return Pointer to the new instance of the @ref
+ /// isc::http::PostHttpRequestJson.
+ virtual http::HttpRequestPtr createNewHttpRequest() const;
+
+ /// @brief Creates stock HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an @ref isc::http::HttpResponseJson object
+ /// representing stock HTTP response.
+ virtual http::HttpResponsePtr
+ createStockHttpResponse(const http::HttpRequestPtr& request,
+ const http::HttpStatusCode& status_code) const;
+
+ /// @brief Indicates whether or not agent response emulation is enabled.
+ ///
+ /// @return true if emulation is enabled.
+ bool emulateAgentResponse() {
+ return (emulate_agent_response_);
+ }
+
+ /// @brief Filter commands.
+ ///
+ /// From RBAC code: if the access list is empty or the command
+ /// cannot be found just return.
+ ///
+ /// @param request The HTTP request (for the HTTP version).
+ /// @param body The request body.
+ /// @param accept The accept access list.
+ http::HttpResponseJsonPtr
+ filterCommand(const http::HttpRequestPtr& request,
+ const data::ConstElementPtr& body,
+ const std::unordered_set<std::string>& accept);
+
+ /// @brief The server current authentication configuration.
+ ///
+ /// Default to the empty HttpAuthConfigPtr.
+ ///
+ /// @note: This is currently not used, except in unit-tests. For the time being,
+ /// we postponed writing the corresponding code in the HA, so http_auth_config_
+ /// is left to its empty default value.
+ static http::HttpAuthConfigPtr http_auth_config_;
+
+ /// @brief The server command accept list.
+ ///
+ /// Default to the empty list which means to accept everything.
+ static std::unordered_set<std::string> command_accept_list_;
+
+private:
+
+ /// @brief Creates un-finalized stock HTTP response.
+ ///
+ /// The un-finalized response is the response that can't be sent over the
+ /// wire until @c finalize() is called, which commits the contents of the
+ /// message body.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an @ref isc::http::HttpResponseJson object
+ /// representing stock HTTP response.
+ http::HttpResponsePtr
+ createStockHttpResponseInternal(const http::HttpRequestPtr& request,
+ const http::HttpStatusCode& status_code) const;
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual http::HttpResponsePtr
+ createDynamicHttpResponse(http::HttpRequestPtr request);
+
+ /// @brief Determines whether or not responses are enclosed in an Element::list.
+ /// Currently kea-ctrl-agent wraps all responses in a list, as it may have
+ /// response from more than one server. If this is true, we'll ensure
+ /// responses (other than error responses) are in a list.
+ bool emulate_agent_response_;
+};
+
+/// @brief Pointer to the @ref CmdResponseCreator.
+typedef boost::shared_ptr<CmdResponseCreator> CmdResponseCreatorPtr;
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/cmd_response_creator_factory.h b/src/lib/config/cmd_response_creator_factory.h
new file mode 100644
index 0000000..f9c4126
--- /dev/null
+++ b/src/lib/config/cmd_response_creator_factory.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2021 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 CMD_RESPONSE_CREATOR_FACTORY_H
+#define CMD_RESPONSE_CREATOR_FACTORY_H
+
+#include <config/cmd_response_creator.h>
+#include <http/response_creator_factory.h>
+
+namespace isc {
+namespace config {
+
+/// @brief HTTP response creator factory for an API listener
+///
+/// @param emulate_agent_response if true results for normal command
+/// outcomes are wrapped in Element::list. This emulates responses
+/// generated by kea-ctrl-agent. The value is passed into the
+/// CmdResponseCreator when created. Defaults to true.
+///
+/// See the documentation of the @ref isc::http::HttpResponseCreatorFactory
+/// for the details how the response factory object is used by the
+/// @ref isc::http::HttpListener.
+///
+/// This class always returns the same instance of the
+/// @ref CmdResponseCreator which @ref isc::http::HttpListener and
+/// @ref isc::http::HttpConnection classes use to generate HTTP response
+/// messages which comply with the formats required by the Control Agent.
+class CmdResponseCreatorFactory : public http::HttpResponseCreatorFactory {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates sole instance of the @ref CmdResponseCreator object
+ /// returned by the @ref CmdResponseCreatorFactory::create.
+ ///
+ /// @param emulate_agent_response if true, responses for normal
+ /// command outcomes are guaranteed to be wrapped in an Element::list.
+ /// This emulates how kea-ctrl-agent forms responses. Defaults to true.
+ CmdResponseCreatorFactory(bool emulate_agent_response = true)
+ : sole_creator_(new CmdResponseCreator(emulate_agent_response)) {
+ }
+
+ /// @brief Returns an instance of the @ref CmdResponseCreator which
+ /// is used by HTTP server to generate responses to commands.
+ ///
+ /// @return Pointer to the @ref CmdResponseCreator object.
+ virtual http::HttpResponseCreatorPtr create() const {
+ return (sole_creator_);
+ }
+
+private:
+
+ /// @brief Instance of the @ref CmdResponseCreator returned.
+ http::HttpResponseCreatorPtr sole_creator_;
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/cmds_impl.h b/src/lib/config/cmds_impl.h
new file mode 100644
index 0000000..f90cee4
--- /dev/null
+++ b/src/lib/config/cmds_impl.h
@@ -0,0 +1,78 @@
+// Copyright (C) 2017-2021 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 CMDS_IMPL_H
+#define CMDS_IMPL_H
+
+#include <config.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <hooks/hooks.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+namespace isc {
+namespace config {
+
+/// @brief Base class that command handler implementers may use for common tasks.
+class CmdsImpl {
+protected:
+ /// @brief Extracts the command name and arguments from a Callout handle
+ ///
+ /// @param handle Callout context handle expected to contain the JSON command
+ /// text
+ ///
+ /// @throw isc::BadValue if the text does not contain a properly formed command
+ void extractCommand(hooks::CalloutHandle& handle) {
+ try {
+ data::ConstElementPtr command;
+ handle.getArgument("command", command);
+ cmd_name_ = parseCommand(cmd_args_, command);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "JSON command text is invalid: " << ex.what());
+ }
+ }
+
+ /// @brief Set the callout argument "response" to indicate success
+ ///
+ /// @param handle Callout context handle in which to set the "response" argument
+ /// @param text string text to be used as the response description
+ void setSuccessResponse(hooks::CalloutHandle& handle, const std::string& text) {
+ data::ConstElementPtr response = createAnswer(CONTROL_RESULT_SUCCESS, text);
+ setResponse (handle, response);
+ }
+
+ /// @brief Set the callout argument "response" to indicate an error
+ ///
+ /// @param handle Callout context handle in which to set the "response" argument
+ /// @param text string text to be used as the response description
+ /// @param status numeric value to use as the response result, defaults to
+ /// CONTROL_RESULT_ERROR
+ void setErrorResponse(hooks::CalloutHandle& handle, const std::string& text,
+ int status=CONTROL_RESULT_ERROR) {
+ data::ConstElementPtr response = createAnswer(status, text);
+ setResponse (handle, response);
+ }
+
+ /// @brief Set the callout argument "response" to the given response
+ ///
+ /// @param handle Callout context handle in which to set the "response" argument
+ /// @param response ElementPtr to the result to use as the response
+ void setResponse(hooks::CalloutHandle& handle, data::ConstElementPtr& response) {
+ handle.setArgument ("response", response);
+ }
+
+ /// @brief Stores the command name extracted by a call to extractCommand
+ std::string cmd_name_;
+
+ /// @brief Stores the command arguments extracted by a call to extractCommand
+ data::ConstElementPtr cmd_args_;
+};
+
+}
+}
+
+#endif // CMDS_IMPL_H
diff --git a/src/lib/config/command-socket.dox b/src/lib/config/command-socket.dox
new file mode 100644
index 0000000..3b5150a
--- /dev/null
+++ b/src/lib/config/command-socket.dox
@@ -0,0 +1,188 @@
+// Copyright (C) 2015-2020 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/.
+
+/**
+ @page ctrlSocket Control Channel
+
+@section ctrlSocketOverview Control Channel Overview
+
+In many cases it is useful to manage certain aspects of the DHCP servers
+while they are running. In Kea, this may be done via the Control Channel.
+Control Channel allows an external entity (e.g. a tool run by a sysadmin
+or a script) to issue commands to the server which can influence its
+behavior or retrieve information from it. Several notable examples
+envisioned are: reconfiguration, statistics retrieval and manipulation,
+and shutdown.
+
+Communication over Control Channel is conducted using JSON structures.
+As of Kea 0.9.2, the only supported communication channel is UNIX stream
+socket, but additional types may be added in the future.
+
+If configured, Kea will open a socket and will listen for any incoming
+connections. A process connecting to this socket is expected to send JSON
+commands structured as follows:
+
+@code
+{
+ "command": "foo",
+ "arguments": {
+ "param_foo": "value1",
+ "param_bar": "value2",
+ ...
+ }
+}
+@endcode
+
+- command - is the name of command to execute and is mandatory.
+- arguments - contain a single parameter or a map or parameters
+required to carry out the given command. The exact content and format is command specific.
+
+The server will process the incoming command and then send a response of the form:
+
+@code
+{
+ "result": 0|1,
+ "text": "textual description",
+ "arguments": {
+ "argument1": "value1",
+ "argument2": "value2",
+ ...
+ }
+}
+@endcode
+
+- result - indicates the outcome of the command. A value of 0 means a success while
+any non-zero value designates an error. Currently 1 is used as a generic error, but additional
+error codes may be added in the future.
+- text field - typically appears when result is non-zero and contains description of the error
+encountered.
+- arguments - is a map of additional data values returned by the server, specific to the
+command issue. The map is always present, even if it contains no data values.
+
+@section ctrlSocketClient Using Control Channel
+
+Here are two examples of how to access the Control Channel:
+
+1. Use socat tool, which is available in many Linux and BSD distributions.
+See http://www.dest-unreach.org/socat/ for details. To use it:
+@code
+socat UNIX:/var/run/kea/kea4.sock -
+@endcode
+You then can type JSON commands and get responses (also in JSON format).
+
+2. Here's an example C code that connects and gets a list of supported commands:
+@code
+// Copyright (C) 2015-2020 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 <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, const char* argv[]) {
+
+ if (argc != 2) {
+ printf("Usage: %s socket_path\n", argv[0]);
+ return (1);
+ }
+
+ // Create UNIX stream socket.
+ int socket_fd;
+ if ((socket_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ {
+ perror("Failed to create UNIX stream");
+ return (1);
+ }
+
+ // Specify the address to connect to (unix path)
+ struct sockaddr_un srv_addr;
+ memset(&srv_addr, 0, sizeof(struct sockaddr_un));
+ srv_addr.sun_family = AF_UNIX;
+ strcpy(srv_addr.sun_path, argv[1]);
+ socklen_t len = sizeof(srv_addr);
+
+ // Try to connect.
+ if (connect(socket_fd, (struct sockaddr*) &srv_addr, len) == -1) {
+ perror("Failed to connect");
+ return (1);
+ }
+
+ // Send a command to list all available commands.
+ char buf[1024];
+ sprintf(buf, "{ \"command\": \"list-commands\" }");
+ int bytes_sent = send(socket_fd, buf, strlen(buf), 0);
+ printf("%d bytes sent\n", bytes_sent);
+
+ // Receive a response (should be JSON formatted list of commands)
+ int bytes_rcvd = recv(socket_fd, buf, sizeof(buf), 0);
+ printf("%d bytes received: [%s]\n", bytes_rcvd, buf);
+
+ // Close the socket
+ close(socket_fd);
+
+ return 0;
+}
+@endcode
+
+@section ctrlSocketImpl Control Channel Implementation
+
+Control Channel is implemented in @ref isc::config::CommandMgr. It is a singleton
+class that allows registration of callbacks that handle specific commands.
+It internally supports a single command: @c list-commands that returns a list
+of supported commands. This component is expected to be shared among all daemons.
+
+There are 3 main methods that are expected to be used by developers:
+- @ref isc::config::CommandMgr::registerCommand, which allows registration of
+ additional commands.
+- @ref isc::config::CommandMgr::deregisterCommand, which allows removing previously
+ registered command.
+- @ref isc::config::CommandMgr::processCommand, which allows handling specified
+ command.
+
+There are also two methods for managing control sockets. They are not expected
+to be used directly, unless someone implements a new Control Channel (e.g. TCP
+or HTTPS connection):
+
+- @ref isc::config::CommandMgr::openCommandSocket that passes structure defined
+ in the configuration file. Currently only two parameters are supported: socket-type
+ (which must contain value 'unix') and socket-name (which contains unix path for
+ the named socket to be created).
+- @ref isc::config::CommandMgr::closeCommandSocket() - it is used to close the
+ socket.
+
+Kea servers use @c CommandMgr to register handlers for various commands they
+support natively. However, it is possible extend a set of supported commands
+using hooks framework. See @ref hooksdgCommandHandlers how to implement support
+for your own control commands in Kea.
+
+@section ctrlSocketConnections Accepting connections
+
+The @ref isc::config::CommandMgr is implemented using boost ASIO and uses
+asynchronous calls to accept new connections and receive commands from the
+controlling clients. ASIO uses IO service object to run asynchronous calls.
+Thus, before the server can use the @ref isc::config::CommandMgr it must
+provide it with a common instance of the @ref isc::asiolink::IOService
+object using @ref isc::config::CommandMgr::setIOService. The server's
+main loop must contain calls to @ref isc::asiolink::IOService::run or
+@ref isc::asiolink::IOService::poll or their variants to invoke Command
+Manager's handlers as required for processing control requests.
+
+@section ctrlSocketMTConsiderations Multi-Threading Consideration for Control Channel
+
+The control channel utilities are not thread safe but they are used only
+by the main thread so in most cases it does not matter. For instance
+the assumption that only at most one command can be executed at a given
+time can be done. Of course this has its limit: when the command changes
+the configuration or is incompatible with a simultaneous packet
+processing the multi-threading mode must be checked and service threads
+stopped.
+
+*/
diff --git a/src/lib/config/command_mgr.cc b/src/lib/config/command_mgr.cc
new file mode 100644
index 0000000..831d56e
--- /dev/null
+++ b/src/lib/config/command_mgr.cc
@@ -0,0 +1,667 @@
+// Copyright (C) 2015-2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/unix_domain_socket_acceptor.h>
+#include <asiolink/unix_domain_socket_endpoint.h>
+#include <config/command_mgr.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <cc/json_feed.h>
+#include <dhcp/iface_mgr.h>
+#include <config/config_log.h>
+#include <config/timeouts.h>
+#include <util/watch_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <array>
+#include <functional>
+#include <unistd.h>
+#include <sys/file.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the data chunk sent/received over the socket.
+const size_t BUF_SIZE = 32768;
+
+class ConnectionPool;
+
+/// @brief Represents a single connection over control socket.
+///
+/// An instance of this object is created when the @c CommandMgr acceptor
+/// receives new connection from a controlling client.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor registers a socket of this connection in the Interface
+ /// Manager to cause the blocking call to @c select() to return as soon as
+ /// a transmission over the control socket is received.
+ ///
+ /// It installs two external sockets on the @IfaceMgr to break synchronous
+ /// calls to @select(). The @c WatchSocket is used for send operations
+ /// over the connection. The native socket is used for signaling reads
+ /// over the connection.
+ ///
+ /// @param io_service IOService object used to handle the asio operations
+ /// @param socket Pointer to the object representing a socket which is used
+ /// for data transmission.
+ /// @param connection_pool Reference to the connection pool to which this
+ /// connection belongs.
+ /// @param timeout Connection timeout (in seconds).
+ Connection(const IOServicePtr& io_service,
+ const boost::shared_ptr<UnixDomainSocket>& socket,
+ ConnectionPool& connection_pool,
+ const long timeout)
+ : socket_(socket), timeout_timer_(*io_service), timeout_(timeout),
+ buf_(), response_(), connection_pool_(connection_pool), feed_(),
+ response_in_progress_(false), watch_socket_(new util::WatchSocket()) {
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_OPENED)
+ .arg(socket_->getNative());
+
+ // Callback value of 0 is used to indicate that callback function is
+ // not installed.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(watch_socket_->getSelectFd(), 0);
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
+
+ // Initialize state model for receiving and preparsing commands.
+ feed_.initModel();
+
+ // Start timer for detecting timeouts.
+ scheduleTimer();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cancels timeout timer if one is scheduled.
+ ~Connection() {
+ timeout_timer_.cancel();
+ }
+
+ /// @brief This method schedules timer or reschedules existing timer.
+ void scheduleTimer() {
+ timeout_timer_.setup(std::bind(&Connection::timeoutHandler, this),
+ timeout_, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Close current connection.
+ ///
+ /// Connection is not closed if the invocation of this method is a result of
+ /// server reconfiguration. The connection will be closed once a response is
+ /// sent to the client. Closing a socket during processing a request would
+ /// cause the server to not send a response to the client.
+ void stop() {
+ if (!response_in_progress_) {
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_CLOSED)
+ .arg(socket_->getNative());
+
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(watch_socket_->getSelectFd());
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
+
+ // Close watch socket and log errors if occur.
+ std::string watch_error;
+ if (!watch_socket_->closeSocket(watch_error)) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLOSE_ERROR)
+ .arg(watch_error);
+ }
+
+ socket_->close();
+ timeout_timer_.cancel();
+ }
+ }
+
+ /// @brief Gracefully terminates current connection.
+ ///
+ /// This method should be called prior to closing the socket to initiate
+ /// graceful shutdown.
+ void terminate();
+
+ /// @brief Start asynchronous read over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the transmission is received over the
+ /// socket, the @c Connection::receiveHandler callback is invoked to
+ /// process received data.
+ void doReceive() {
+ socket_->asyncReceive(&buf_[0], sizeof(buf_),
+ std::bind(&Connection::receiveHandler,
+ shared_from_this(), ph::_1, ph::_2));
+ }
+
+ /// @brief Starts asynchronous send over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the send operation (that covers the whole
+ /// data if it's small or first BUF_SIZE bytes if its large) is completed, the
+ /// @c Connection::sendHandler callback is invoked. That handler will either
+ /// close the connection gracefully if all data has been sent, or will
+ /// call @ref doSend() again to send the next chunk of data.
+ void doSend() {
+ size_t chunk_size = (response_.size() < BUF_SIZE) ? response_.size() : BUF_SIZE;
+ socket_->asyncSend(&response_[0], chunk_size,
+ std::bind(&Connection::sendHandler, shared_from_this(), ph::_1, ph::_2));
+
+ // Asynchronous send has been scheduled and we need to indicate this
+ // to break the synchronous select(). The handler should clear this
+ // status when invoked.
+ try {
+ watch_socket_->markReady();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_MARK_READY_ERROR)
+ .arg(ex.what());
+ }
+ }
+
+ /// @brief Handler invoked when the data is received over the control
+ /// socket.
+ ///
+ /// It collects received data into the @c isc::config::JSONFeed object and
+ /// schedules additional asynchronous read of data if this object signals
+ /// that command is incomplete. When the entire command is received, the
+ /// handler processes this command and asynchronously responds to the
+ /// controlling client.
+ //
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes received.
+ void receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+
+ /// @brief Handler invoked when the data is sent over the control socket.
+ ///
+ /// If there are still data to be sent, another asynchronous send is
+ /// scheduled. When the entire command is sent, the connection is shutdown
+ /// and closed.
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes sent.
+ void sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+ /// @brief Handler invoked when timeout has occurred.
+ ///
+ /// Asynchronously sends a response to the client indicating that the
+ /// timeout has occurred.
+ void timeoutHandler();
+
+private:
+
+ /// @brief Pointer to the socket used for transmission.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Interval timer used to detect connection timeouts.
+ IntervalTimer timeout_timer_;
+
+ /// @brief Connection timeout (in milliseconds)
+ long timeout_;
+
+ /// @brief Buffer used for received data.
+ std::array<char, BUF_SIZE> buf_;
+
+ /// @brief Response created by the server.
+ std::string response_;
+
+ /// @brief Reference to the pool of connections.
+ ConnectionPool& connection_pool_;
+
+ /// @brief State model used to receive data over the connection and detect
+ /// when the command ends.
+ JSONFeed feed_;
+
+ /// @brief Boolean flag indicating if the request to stop connection is a
+ /// result of server reconfiguration.
+ bool response_in_progress_;
+
+ /// @brief Pointer to watch socket instance used to signal that the socket
+ /// is ready for read or write.
+ util::WatchSocketPtr watch_socket_;
+};
+
+/// @brief Pointer to the @c Connection.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Holds all open connections.
+class ConnectionPool {
+public:
+
+ /// @brief Starts new connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void start(const ConnectionPtr& connection) {
+ connection->doReceive();
+ connections_.insert(connection);
+ }
+
+ /// @brief Stops running connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void stop(const ConnectionPtr& connection) {
+ try {
+ connection->stop();
+ connections_.erase(connection);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CLOSE_FAIL)
+ .arg(ex.what());
+ }
+ }
+
+ /// @brief Stops all connections which are allowed to stop.
+ void stopAll() {
+ for (auto conn = connections_.begin(); conn != connections_.end();
+ ++conn) {
+ (*conn)->stop();
+ }
+ connections_.clear();
+ }
+
+private:
+
+ /// @brief Pool of connections.
+ std::set<ConnectionPtr> connections_;
+
+};
+
+void
+Connection::terminate() {
+ try {
+ socket_->shutdown();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL)
+ .arg(ex.what());
+ }
+}
+
+void
+Connection::receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ if (ec) {
+ if (ec.value() == boost::asio::error::eof) {
+ std::stringstream os;
+ if (feed_.getProcessedText().empty()) {
+ os << "no input data to discard";
+ } else {
+ os << "discarding partial command of "
+ << feed_.getProcessedText().size() << " bytes";
+ }
+
+ // Foreign host has closed the connection. We should remove it from the
+ // connection pool.
+ LOG_INFO(command_logger, COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST)
+ .arg(socket_->getNative()).arg(os.str());
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_READ_FAIL)
+ .arg(ec.value()).arg(socket_->getNative());
+ }
+
+ connection_pool_.stop(shared_from_this());
+ return;
+
+ } else if (bytes_transferred == 0) {
+ // Nothing received. Close the connection.
+ connection_pool_.stop(shared_from_this());
+ return;
+ }
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ)
+ .arg(bytes_transferred).arg(socket_->getNative());
+
+ // Reschedule the timer because the transaction is ongoing.
+ scheduleTimer();
+
+ ConstElementPtr cmd;
+ ConstElementPtr rsp;
+
+ try {
+ // Received some data over the socket. Append them to the JSON feed
+ // to see if we have reached the end of command.
+ feed_.postBuffer(&buf_[0], bytes_transferred);
+ feed_.poll();
+ // If we haven't yet received the full command, continue receiving.
+ if (feed_.needData()) {
+ doReceive();
+ return;
+ }
+
+ // Received entire command. Parse the command into JSON.
+ if (feed_.feedOk()) {
+ cmd = feed_.toElement();
+ response_in_progress_ = true;
+
+ // Cancel the timer to make sure that long lasting command
+ // processing doesn't cause the timeout.
+ timeout_timer_.cancel();
+
+ // If successful, then process it as a command.
+ rsp = CommandMgr::instance().processCommand(cmd);
+
+ response_in_progress_ = false;
+
+ } else {
+ // Failed to parse command as JSON or process the received command.
+ // This exception will be caught below and the error response will
+ // be sent.
+ isc_throw(BadValue, feed_.getErrorMessage());
+ }
+
+ } catch (const Exception& ex) {
+ LOG_WARN(command_logger, COMMAND_PROCESS_ERROR1).arg(ex.what());
+ rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
+ }
+
+ // No response generated. Connection will be closed.
+ if (!rsp) {
+ LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR)
+ .arg(cmd ? cmd->str() : "unknown");
+ rsp = createAnswer(CONTROL_RESULT_ERROR,
+ "internal server error: no response generated");
+
+ } else {
+
+ // Reschedule the timer as it may be either canceled or need to be
+ // updated to not timeout before we manage to the send the reply.
+ scheduleTimer();
+
+ // Let's convert JSON response to text. Note that at this stage
+ // the rsp pointer is always set.
+ response_ = rsp->str();
+
+ doSend();
+ return;
+ }
+
+ // Close the connection if we have sent the entire response.
+ connection_pool_.stop(shared_from_this());
+}
+
+void
+Connection::sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ // Clear the watch socket so as the future send operation can mark it
+ // again to interrupt the synchronous select() call.
+ try {
+ watch_socket_->clearReady();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLEAR_ERROR)
+ .arg(ex.what());
+ }
+
+ if (ec) {
+ // If an error occurred, log this error and stop the connection.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
+ .arg(socket_->getNative()).arg(ec.message());
+ }
+
+ } else {
+
+ // Reschedule the timer because the transaction is ongoing.
+ scheduleTimer();
+
+ // No error. We are in a process of sending a response. Need to
+ // remove the chunk that we have managed to sent with the previous
+ // attempt.
+ response_.erase(0, bytes_transferred);
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE)
+ .arg(bytes_transferred).arg(response_.size())
+ .arg(socket_->getNative());
+
+ // Check if there is any data left to be sent and sent it.
+ if (!response_.empty()) {
+ doSend();
+ return;
+ }
+
+ // Gracefully shutdown the connection and close the socket if
+ // we have sent the whole response.
+ terminate();
+ }
+
+ // All data sent or an error has occurred. Close the connection.
+ connection_pool_.stop(shared_from_this());
+}
+
+void
+Connection::timeoutHandler() {
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_TIMEOUT)
+ .arg(socket_->getNative());
+
+ try {
+ socket_->cancel();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CANCEL_FAIL)
+ .arg(socket_->getNative())
+ .arg(ex.what());
+ }
+
+ std::stringstream os;
+ os << "Connection over control channel timed out";
+ if (!feed_.getProcessedText().empty()) {
+ os << ", discarded partial command of "
+ << feed_.getProcessedText().size() << " bytes";
+ }
+
+ ConstElementPtr rsp = createAnswer(CONTROL_RESULT_ERROR, os.str());
+ response_ = rsp->str();
+ doSend();
+}
+
+
+}
+
+namespace isc {
+namespace config {
+
+/// @brief Implementation of the @c CommandMgr.
+class CommandMgrImpl {
+public:
+
+ /// @brief Constructor.
+ CommandMgrImpl()
+ : io_service_(), acceptor_(), socket_(), socket_name_(),
+ connection_pool_(), timeout_(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND) {
+ }
+
+ /// @brief Opens acceptor service allowing the control clients to connect.
+ ///
+ /// @param socket_info Configuration information for the control socket.
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
+ void openCommandSocket(const isc::data::ConstElementPtr& socket_info);
+
+ /// @brief Asynchronously accepts next connection.
+ void doAccept();
+
+ /// @brief Returns the lock file name
+ std::string getLockName() {
+ return (std::string(socket_name_ + ".lock"));
+ }
+
+ /// @brief Pointer to the IO service used by the server process for running
+ /// asynchronous tasks.
+ IOServicePtr io_service_;
+
+ /// @brief Pointer to the acceptor service.
+ boost::shared_ptr<UnixDomainSocketAcceptor> acceptor_;
+
+ /// @brief Pointer to the socket into which the new connection is accepted.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Path to the unix domain socket descriptor.
+ ///
+ /// This is used to remove the socket file once the connection terminates.
+ std::string socket_name_;
+
+ /// @brief Pool of connections.
+ ConnectionPool connection_pool_;
+
+ /// @brief Connection timeout
+ long timeout_;
+};
+
+void
+CommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ socket_name_.clear();
+
+ if(!socket_info) {
+ isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
+ }
+
+ ConstElementPtr type = socket_info->get("socket-type");
+ if (!type) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
+ }
+
+ // Only supporting unix sockets right now.
+ if (type->stringValue() != "unix") {
+ isc_throw(BadSocketInfo, "Invalid 'socket-type' parameter value "
+ << type->stringValue());
+ }
+
+ // UNIX socket is requested. It takes one parameter: socket-name that
+ // specifies UNIX path of the socket.
+ ConstElementPtr name = socket_info->get("socket-name");
+ if (!name) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
+ }
+
+ if (name->getType() != Element::string) {
+ isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
+ }
+
+ socket_name_ = name->stringValue();
+
+ // First let's open lock file.
+ std::string lock_name = getLockName();
+ int lock_fd = open(lock_name.c_str(), O_RDONLY | O_CREAT, 0600);
+ if (lock_fd == -1) {
+ std::string errmsg = strerror(errno);
+ isc_throw(SocketError, "cannot create socket lockfile, "
+ << lock_name << ", : " << errmsg);
+ }
+
+ // Try to acquire lock. If we can't somebody else is actively
+ // using it.
+ int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
+ if (ret != 0) {
+ std::string errmsg = strerror(errno);
+ isc_throw(SocketError, "cannot lock socket lockfile, "
+ << lock_name << ", : " << errmsg);
+ }
+
+ // We have the lock, so let's remove the pre-existing socket
+ // file if it exists.
+ static_cast<void>(::remove(socket_name_.c_str()));
+
+ LOG_INFO(command_logger, COMMAND_ACCEPTOR_START)
+ .arg(socket_name_);
+
+ try {
+ // Start asynchronous acceptor service.
+ acceptor_.reset(new UnixDomainSocketAcceptor(*io_service_));
+ UnixDomainSocketEndpoint endpoint(socket_name_);
+ acceptor_->open(endpoint);
+ acceptor_->bind(endpoint);
+ acceptor_->listen();
+ // Install this socket in Interface Manager.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(acceptor_->getNative(), 0);
+
+ doAccept();
+
+ } catch (const std::exception& ex) {
+ isc_throw(SocketError, ex.what());
+ }
+}
+
+void
+CommandMgrImpl::doAccept() {
+ // Create a socket into which the acceptor will accept new connection.
+ socket_.reset(new UnixDomainSocket(*io_service_));
+ acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
+ if (!ec) {
+ // New connection is arriving. Start asynchronous transmission.
+ ConnectionPtr connection(new Connection(io_service_, socket_,
+ connection_pool_,
+ timeout_));
+ connection_pool_.start(connection);
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
+ .arg(acceptor_->getNative()).arg(ec.message());
+ }
+
+ // Unless we're stopping the service, start accepting connections again.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ doAccept();
+ }
+ });
+}
+
+CommandMgr::CommandMgr()
+ : HookedCommandMgr(), impl_(new CommandMgrImpl()) {
+}
+
+void
+CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ impl_->openCommandSocket(socket_info);
+}
+
+void CommandMgr::closeCommandSocket() {
+ // Close acceptor if the acceptor is open.
+ if (impl_->acceptor_ && impl_->acceptor_->isOpen()) {
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(impl_->acceptor_->getNative());
+ impl_->acceptor_->close();
+ static_cast<void>(::remove(impl_->socket_name_.c_str()));
+ static_cast<void>(::remove(impl_->getLockName().c_str()));
+ }
+
+ // Stop all connections which can be closed. The only connection that won't
+ // be closed is the one over which we have received a request to reconfigure
+ // the server. This connection will be held until the CommandMgr responds to
+ // such request.
+ impl_->connection_pool_.stopAll();
+}
+
+int
+CommandMgr::getControlSocketFD() {
+ return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
+}
+
+
+CommandMgr&
+CommandMgr::instance() {
+ static CommandMgr cmd_mgr;
+ return (cmd_mgr);
+}
+
+void
+CommandMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->io_service_ = io_service;
+}
+
+void
+CommandMgr::setConnectionTimeout(const long timeout) {
+ impl_->timeout_ = timeout;
+}
+
+
+}; // end of isc::config
+}; // end of isc
diff --git a/src/lib/config/command_mgr.h b/src/lib/config/command_mgr.h
new file mode 100644
index 0000000..f4ba6c1
--- /dev/null
+++ b/src/lib/config/command_mgr.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2015-2018 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 COMMAND_MGR_H
+#define COMMAND_MGR_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <config/hooked_command_mgr.h>
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace config {
+
+/// @brief An exception indicating that specified socket parameters are invalid
+class BadSocketInfo : public Exception {
+public:
+ BadSocketInfo(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief An exception indicating a problem with socket operation
+class SocketError : public Exception {
+public:
+ SocketError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+class CommandMgrImpl;
+
+/// @brief Commands Manager implementation for the Kea servers.
+///
+/// This class extends @ref BaseCommandMgr with the ability to receive and
+/// respond to commands over unix domain sockets.
+class CommandMgr : public HookedCommandMgr, public boost::noncopyable {
+public:
+
+ /// @brief CommandMgr is a singleton class. This method returns reference
+ /// to its sole instance.
+ ///
+ /// @return the only existing instance of the manager
+ static CommandMgr& instance();
+
+ /// @brief Sets IO service to be used by the command manager.
+ ///
+ /// The server should use this method to provide the Command Manager with the
+ /// common IO service used by the server.
+ /// @param io_service Pointer to the IO service.
+ void setIOService(const asiolink::IOServicePtr& io_service);
+
+ /// @brief Override default connection timeout.
+ ///
+ /// @param timeout New connection timeout in milliseconds.
+ void setConnectionTimeout(const long timeout);
+
+ /// @brief Opens control socket with parameters specified in socket_info
+ ///
+ /// Currently supported types are:
+ /// - unix (required parameters: socket-type: unix, socket-name:/unix/path)
+ ///
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
+ ///
+ /// @param socket_info Configuration information for the control socket.
+ void
+ openCommandSocket(const isc::data::ConstElementPtr& socket_info);
+
+ /// @brief Shuts down any open control sockets
+ void closeCommandSocket();
+
+ /// @brief Returns control socket descriptor
+ ///
+ /// This method should be used only in tests.
+ int getControlSocketFD();
+
+private:
+
+ /// @brief Private constructor
+ CommandMgr();
+
+ /// @brief Pointer to the implementation of the @ref CommandMgr.
+ boost::shared_ptr<CommandMgrImpl> impl_;
+};
+
+}; // end of isc::config namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/config/config_log.cc b/src/lib/config/config_log.cc
new file mode 100644
index 0000000..67f896f
--- /dev/null
+++ b/src/lib/config/config_log.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2011-2018 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/.
+
+/// Defines the logger used by the config lib
+
+#include <config.h>
+
+#include "config/config_log.h"
+
+namespace isc {
+namespace config {
+
+isc::log::Logger command_logger("commands");
+
+extern const int DBG_COMMAND = isc::log::DBGLVL_COMMAND;
+
+} // namespace nsas
+} // namespace isc
+
diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h
new file mode 100644
index 0000000..08b4ddb
--- /dev/null
+++ b/src/lib/config/config_log.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2011-2018 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 CONFIG_LOG_H
+#define CONFIG_LOG_H
+
+#include <log/macros.h>
+#include "config_messages.h"
+
+namespace isc {
+namespace config {
+
+/// @brief Command processing Logger
+///
+/// Define the logger used to log messages related to command processing.
+/// We could define it in multiple modules, but defining in a single
+/// module and linking to it saves time and space.
+extern isc::log::Logger command_logger;
+
+// Enumerate configuration elements as they are processed.
+extern const int DBG_COMMAND;
+
+} // namespace config
+} // namespace isc
+
+#endif // CONFIG_LOG_H
diff --git a/src/lib/config/config_messages.cc b/src/lib/config/config_messages.cc
new file mode 100644
index 0000000..f2754a1
--- /dev/null
+++ b/src/lib/config/config_messages.cc
@@ -0,0 +1,77 @@
+// File created from ../../../src/lib/config/config_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace config {
+
+extern const isc::log::MessageID COMMAND_ACCEPTOR_START = "COMMAND_ACCEPTOR_START";
+extern const isc::log::MessageID COMMAND_DEREGISTERED = "COMMAND_DEREGISTERED";
+extern const isc::log::MessageID COMMAND_EXTENDED_REGISTERED = "COMMAND_EXTENDED_REGISTERED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_COMMAND_REJECTED = "COMMAND_HTTP_LISTENER_COMMAND_REJECTED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STARTED = "COMMAND_HTTP_LISTENER_STARTED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPED = "COMMAND_HTTP_LISTENER_STOPPED";
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPING = "COMMAND_HTTP_LISTENER_STOPPING";
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR1 = "COMMAND_PROCESS_ERROR1";
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR2 = "COMMAND_PROCESS_ERROR2";
+extern const isc::log::MessageID COMMAND_RECEIVED = "COMMAND_RECEIVED";
+extern const isc::log::MessageID COMMAND_REGISTERED = "COMMAND_REGISTERED";
+extern const isc::log::MessageID COMMAND_RESPONSE_ERROR = "COMMAND_RESPONSE_ERROR";
+extern const isc::log::MessageID COMMAND_SOCKET_ACCEPT_FAIL = "COMMAND_SOCKET_ACCEPT_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST = "COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CANCEL_FAIL = "COMMAND_SOCKET_CONNECTION_CANCEL_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSED = "COMMAND_SOCKET_CONNECTION_CLOSED";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSE_FAIL = "COMMAND_SOCKET_CONNECTION_CLOSE_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_OPENED = "COMMAND_SOCKET_CONNECTION_OPENED";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL = "COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_TIMEOUT = "COMMAND_SOCKET_CONNECTION_TIMEOUT";
+extern const isc::log::MessageID COMMAND_SOCKET_READ = "COMMAND_SOCKET_READ";
+extern const isc::log::MessageID COMMAND_SOCKET_READ_FAIL = "COMMAND_SOCKET_READ_FAIL";
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE = "COMMAND_SOCKET_WRITE";
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL = "COMMAND_SOCKET_WRITE_FAIL";
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR = "COMMAND_WATCH_SOCKET_CLEAR_ERROR";
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR = "COMMAND_WATCH_SOCKET_CLOSE_ERROR";
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR = "COMMAND_WATCH_SOCKET_MARK_READY_ERROR";
+
+} // namespace config
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "COMMAND_ACCEPTOR_START", "Starting to accept connections via unix domain socket bound to %1",
+ "COMMAND_DEREGISTERED", "Command %1 deregistered",
+ "COMMAND_EXTENDED_REGISTERED", "Command %1 registered",
+ "COMMAND_HTTP_LISTENER_COMMAND_REJECTED", "Command HTTP listener rejected command '%1' from '%2'",
+ "COMMAND_HTTP_LISTENER_STARTED", "Command HTTP listener started with %1 threads, listening on %2:%3, use TLS: %4",
+ "COMMAND_HTTP_LISTENER_STOPPED", "Command HTTP listener for %1:%2 stopped.",
+ "COMMAND_HTTP_LISTENER_STOPPING", "Stopping Command HTTP listener for %1:%2",
+ "COMMAND_PROCESS_ERROR1", "Error while processing command: %1",
+ "COMMAND_PROCESS_ERROR2", "Error while processing command: %1",
+ "COMMAND_RECEIVED", "Received command '%1'",
+ "COMMAND_REGISTERED", "Command %1 registered",
+ "COMMAND_RESPONSE_ERROR", "Server failed to generate response for command: %1",
+ "COMMAND_SOCKET_ACCEPT_FAIL", "Failed to accept incoming connection on command socket %1: %2",
+ "COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST", "Closed command socket %1 by foreign host, %2",
+ "COMMAND_SOCKET_CONNECTION_CANCEL_FAIL", "Failed to cancel read operation on socket %1: %2",
+ "COMMAND_SOCKET_CONNECTION_CLOSED", "Closed socket %1 for existing command connection",
+ "COMMAND_SOCKET_CONNECTION_CLOSE_FAIL", "Failed to close command connection: %1",
+ "COMMAND_SOCKET_CONNECTION_OPENED", "Opened socket %1 for incoming command connection",
+ "COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL", "Encountered error %1 while trying to gracefully shutdown socket",
+ "COMMAND_SOCKET_CONNECTION_TIMEOUT", "Timeout occurred for connection over socket %1",
+ "COMMAND_SOCKET_READ", "Received %1 bytes over command socket %2",
+ "COMMAND_SOCKET_READ_FAIL", "Encountered error %1 while reading from command socket %2",
+ "COMMAND_SOCKET_WRITE", "Sent response of %1 bytes (%2 bytes left to send) over command socket %3",
+ "COMMAND_SOCKET_WRITE_FAIL", "Error while writing to command socket %1 : %2",
+ "COMMAND_WATCH_SOCKET_CLEAR_ERROR", "watch socket failed to clear: %1",
+ "COMMAND_WATCH_SOCKET_CLOSE_ERROR", "watch socket failed to close: %1",
+ "COMMAND_WATCH_SOCKET_MARK_READY_ERROR", "watch socket failed to mark ready: %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/config/config_messages.h b/src/lib/config/config_messages.h
new file mode 100644
index 0000000..3aac871
--- /dev/null
+++ b/src/lib/config/config_messages.h
@@ -0,0 +1,42 @@
+// File created from ../../../src/lib/config/config_messages.mes
+
+#ifndef CONFIG_MESSAGES_H
+#define CONFIG_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace config {
+
+extern const isc::log::MessageID COMMAND_ACCEPTOR_START;
+extern const isc::log::MessageID COMMAND_DEREGISTERED;
+extern const isc::log::MessageID COMMAND_EXTENDED_REGISTERED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_COMMAND_REJECTED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STARTED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPED;
+extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPING;
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR1;
+extern const isc::log::MessageID COMMAND_PROCESS_ERROR2;
+extern const isc::log::MessageID COMMAND_RECEIVED;
+extern const isc::log::MessageID COMMAND_REGISTERED;
+extern const isc::log::MessageID COMMAND_RESPONSE_ERROR;
+extern const isc::log::MessageID COMMAND_SOCKET_ACCEPT_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CANCEL_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSED;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSE_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_OPENED;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_CONNECTION_TIMEOUT;
+extern const isc::log::MessageID COMMAND_SOCKET_READ;
+extern const isc::log::MessageID COMMAND_SOCKET_READ_FAIL;
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE;
+extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL;
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR;
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR;
+extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR;
+
+} // namespace config
+} // namespace isc
+
+#endif // CONFIG_MESSAGES_H
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
new file mode 100644
index 0000000..18f764f
--- /dev/null
+++ b/src/lib/config/config_messages.mes
@@ -0,0 +1,143 @@
+# Copyright (C) 2011-2023 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/.
+
+$NAMESPACE isc::config
+
+% COMMAND_ACCEPTOR_START Starting to accept connections via unix domain socket bound to %1
+This informational message is issued when the Kea server starts an acceptor
+via which it is going to accept new control connections. The acceptor is
+bound to the endpoint associated with the filename provided as an argument.
+If starting the acceptor fails, subsequent error messages will provide a
+reason for failure.
+
+% COMMAND_DEREGISTERED Command %1 deregistered
+This debug message indicates that the daemon stopped supporting specified
+command. This command can no longer be issued. If the command socket is
+open and this command is issued, the daemon will not be able to process it.
+
+% COMMAND_EXTENDED_REGISTERED Command %1 registered
+This debug message indicates that the daemon started supporting specified
+command. The handler for the registered command includes a parameter holding
+entire command to be processed.
+
+% COMMAND_HTTP_LISTENER_COMMAND_REJECTED Command HTTP listener rejected command '%1' from '%2'
+This debug messages is issued when a command is rejected. Arguments detail
+the command and the address the request was received from.
+
+% COMMAND_HTTP_LISTENER_STARTED Command HTTP listener started with %1 threads, listening on %2:%3, use TLS: %4
+This debug messages is issued when an HTTP listener has been started to
+accept connections from Command API clients through which commands can be
+received and responses sent. Arguments detail the number of threads
+that the listener is using, the address and port at which it is listening,
+and if HTTPS/TLS is used or not.
+
+% COMMAND_HTTP_LISTENER_STOPPED Command HTTP listener for %1:%2 stopped.
+This debug messages is issued when the Command HTTP listener, listening
+at the given address and port, has completed shutdown.
+
+% COMMAND_HTTP_LISTENER_STOPPING Stopping Command HTTP listener for %1:%2
+This debug messages is issued when the Command HTTP listener, listening
+at the given address and port, has begun to shutdown.
+
+% COMMAND_PROCESS_ERROR1 Error while processing command: %1
+This warning message indicates that the server encountered an error while
+processing received command. Additional information will be provided, if
+available. Additional log messages may provide more details.
+
+% COMMAND_PROCESS_ERROR2 Error while processing command: %1
+This warning message indicates that the server encountered an error while
+processing received command. The difference, compared to COMMAND_PROCESS_ERROR1
+is that the initial command was well formed and the error occurred during
+logic processing, not the command parsing. Additional information will be
+provided, if available. Additional log messages may provide more details.
+
+% COMMAND_RECEIVED Received command '%1'
+This informational message indicates that a command was received over command
+socket. The nature of this command and its possible results will be logged
+with separate messages.
+
+% COMMAND_REGISTERED Command %1 registered
+This debug message indicates that the daemon started supporting specified
+command. If the command socket is open, this command can now be issued.
+
+% COMMAND_RESPONSE_ERROR Server failed to generate response for command: %1
+This error message indicates that the server failed to generate response for
+specified command. This likely indicates a server logic error, as the server
+is expected to generate valid responses for all commands, even malformed
+ones.
+
+% COMMAND_SOCKET_ACCEPT_FAIL Failed to accept incoming connection on command socket %1: %2
+This error indicates that the server detected incoming connection and executed
+accept system call on said socket, but this call returned an error. Additional
+information may be provided by the system as second parameter.
+
+% COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST Closed command socket %1 by foreign host, %2
+This is an information message indicating that the command connection has been
+closed by a command control client, and whether any partially read data
+was discarded.
+
+% COMMAND_SOCKET_CONNECTION_CANCEL_FAIL Failed to cancel read operation on socket %1: %2
+This error message is issued to indicate an error to cancel asynchronous read
+of the control command over the control socket. The cancel operation is performed
+when the timeout occurs during communication with a client. The error message
+includes details about the reason for failure.
+
+% COMMAND_SOCKET_CONNECTION_CLOSED Closed socket %1 for existing command connection
+This is a debug message indicating that the socket created for handling
+client's connection is closed. This usually means that the client disconnected,
+but may also mean a timeout.
+
+% COMMAND_SOCKET_CONNECTION_CLOSE_FAIL Failed to close command connection: %1
+This error message is issued when an error occurred when closing a
+command connection and/or removing it from the connections pool. The
+detailed error is provided as an argument.
+
+% COMMAND_SOCKET_CONNECTION_OPENED Opened socket %1 for incoming command connection
+This is a debug message indicating that a new incoming command connection was
+detected and a dedicated socket was opened for that connection.
+
+% COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL Encountered error %1 while trying to gracefully shutdown socket
+This message indicates an error while trying to gracefully shutdown command
+connection. The type of the error is included in the message.
+
+% COMMAND_SOCKET_CONNECTION_TIMEOUT Timeout occurred for connection over socket %1
+This is an informational message that indicates that the timeout has
+occurred for one of the command channel connections. The response
+sent by the server indicates a timeout and is then closed.
+
+% COMMAND_SOCKET_READ Received %1 bytes over command socket %2
+This debug message indicates that specified number of bytes was received
+over command socket identified by specified file descriptor.
+
+% COMMAND_SOCKET_READ_FAIL Encountered error %1 while reading from command socket %2
+This error message indicates that an error was encountered while
+reading from command socket.
+
+% COMMAND_SOCKET_WRITE Sent response of %1 bytes (%2 bytes left to send) over command socket %3
+This debug message indicates that the specified number of bytes was sent
+over command socket identifier by the specified file descriptor.
+
+% COMMAND_SOCKET_WRITE_FAIL Error while writing to command socket %1 : %2
+This error message indicates that an error was encountered while
+attempting to send a response to the command socket.
+
+% COMMAND_WATCH_SOCKET_CLEAR_ERROR watch socket failed to clear: %1
+This error message is issued when the command manager was unable to reset
+the ready status after completing a send. This is a programmatic error
+that should be reported. The command manager may or may not continue
+to operate correctly.
+
+% COMMAND_WATCH_SOCKET_CLOSE_ERROR watch socket failed to close: %1
+This error message is issued when command manager attempted to close the
+socket used for indicating the ready status for send operations. This
+should not have any negative impact on the operation of the command
+manager as it happens when the connection is being terminated.
+
+% COMMAND_WATCH_SOCKET_MARK_READY_ERROR watch socket failed to mark ready: %1
+This error message is issued when the command manager was unable to set
+ready status after scheduling asynchronous send. This is programmatic error
+that should be reported. The command manager may or may not continue
+to operate correctly.
diff --git a/src/lib/config/hooked_command_mgr.cc b/src/lib/config/hooked_command_mgr.cc
new file mode 100644
index 0000000..4674080
--- /dev/null
+++ b/src/lib/config/hooked_command_mgr.cc
@@ -0,0 +1,133 @@
+// Copyright (C) 2017-2023 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 <cc/command_interpreter.h>
+#include <config/hooked_command_mgr.h>
+#include <config/config_log.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+#include <boost/pointer_cast.hpp>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::hooks;
+
+namespace isc {
+namespace config {
+
+HookedCommandMgr::HookedCommandMgr()
+ : BaseCommandMgr() {
+}
+
+bool
+HookedCommandMgr::delegateCommandToHookLibrary(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd,
+ ElementPtr& answer) {
+
+ ConstElementPtr hook_response;
+ if (HooksManager::commandHandlersPresent(cmd_name)) {
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Set status to normal.
+ callout_handle->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+
+ // Delete previously set arguments.
+ callout_handle->deleteAllArguments();
+
+ ConstElementPtr command = original_cmd ? original_cmd :
+ createCommand(cmd_name, params);
+
+ // And pass it to the hook library.
+ callout_handle->setArgument("command", command);
+ callout_handle->setArgument("response", hook_response);
+
+ HooksManager::callCommandHandlers(cmd_name, *callout_handle);
+
+ // The callouts should set the response.
+ callout_handle->getArgument("response", hook_response);
+
+ answer = boost::const_pointer_cast<Element>(hook_response);
+
+ return (true);
+ }
+
+ return (false);
+}
+
+ConstElementPtr
+HookedCommandMgr::handleCommand(const std::string& cmd_name,
+ const ConstElementPtr& params,
+ const ConstElementPtr& original_cmd) {
+
+ // The 'list-commands' is a special case. Hook libraries do not implement
+ // this command. We determine what commands are supported by the hook
+ // libraries by checking what hook points are present that have callouts
+ // registered.
+ if ((cmd_name != "list-commands")) {
+ ElementPtr hook_response;
+ // Check if there are any hooks libraries to process this command.
+ if (delegateCommandToHookLibrary(cmd_name, params, original_cmd,
+ hook_response)) {
+ // Hooks libraries processed this command so simply return a
+ // result.
+ return (hook_response);
+ }
+
+ }
+
+ // If we're here it means that the callouts weren't called. We need
+ // to handle the command using local Command Manager.
+ ConstElementPtr response = BaseCommandMgr::handleCommand(cmd_name,
+ params,
+ original_cmd);
+
+ // If we're processing 'list-commands' command we may need to include
+ // commands supported by hooks libraries in the response.
+ if (cmd_name == "list-commands") {
+ // Hooks names can be used to decode what commands are supported.
+ const std::vector<std::string>& hooks =
+ ServerHooks::getServerHooksPtr()->getHookNames();
+
+ // Only update the response if there are any hooks present.
+ if (!hooks.empty()) {
+ ElementPtr hooks_commands = Element::createList();
+ for (auto h = hooks.cbegin(); h != hooks.end(); ++h) {
+ // Try to convert hook name to command name. If non-empty
+ // string is returned it means that the hook point may have
+ // command handlers associated with it. Otherwise, it means that
+ // existing hook points are not for command handlers but for
+ // regular callouts.
+ std::string command_name = ServerHooks::hookToCommandName(*h);
+ if (!command_name.empty()) {
+ // Final check: are command handlers registered for this
+ // hook point? If there are no command handlers associated,
+ // it means that the hook library was already unloaded.
+ if (HooksManager::commandHandlersPresent(command_name)) {
+ hooks_commands->add(Element::create(command_name));
+ }
+ }
+ }
+
+ // If there is at least one hook point with command handlers
+ // registered
+ // for it, combine the lists of commands.
+ if (!hooks_commands->empty()) {
+ response = combineCommandsLists(response, createAnswer(CONTROL_RESULT_SUCCESS, hooks_commands));
+ }
+ }
+ }
+
+ return (response);
+}
+
+
+} // end of namespace isc::config
+} // end of namespace isc
diff --git a/src/lib/config/hooked_command_mgr.h b/src/lib/config/hooked_command_mgr.h
new file mode 100644
index 0000000..fc5c258
--- /dev/null
+++ b/src/lib/config/hooked_command_mgr.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2017 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 HOOKED_COMMAND_MGR_H
+#define HOOKED_COMMAND_MGR_H
+
+#include <cc/data.h>
+#include <config/base_command_mgr.h>
+
+namespace isc {
+namespace config {
+
+/// @brief Command Manager which can delegate commands to a hook library.
+///
+/// This class extends @ref BaseCommandMgr with the logic to delegate the
+/// commands to a hook library if the hook library is installed and provides
+/// command handlers for the control API.
+///
+/// The command handlers are registered by a hook library by calling
+/// @ref isc::hooks::LibraryHandle::registerCommandCallout. This call
+/// creates a hook point for this command (if one doesn't exist) and then
+/// registers the specified handler(s). When the @ref HookedCommandMgr
+/// receives a command for processing it calls the
+/// @ref isc::hooks::HooksManager::commandHandlersPresent to check if there
+/// are handlers present for this command. If so, the @ref HookedCommandMgr
+/// calls @ref isc::hooks::HooksManager::callCommandHandlers to process
+/// the command in the hooks libraries. If command handlers are not installed
+/// for this command, the @ref HookedCommandMgr will try to process the
+/// command on its own.
+///
+/// The @ref isc::hooks::CalloutHandle::CalloutNextStep flag setting by the
+/// command handlers does NOT have any influence on the operation of the
+/// @ref HookedCommandMgr, i.e. it will always skip processing command on
+/// its own if the command handlers are present for the given command, even
+/// if the handlers return an error code.
+class HookedCommandMgr : public BaseCommandMgr {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes callout handle used by the Command Manager.
+ HookedCommandMgr();
+
+protected:
+
+ /// @brief Handles the command within the hooks libraries.
+ ///
+ /// This method checks if the hooks libraries are installed which implement
+ /// command handlers for the specified command to be processed. If the
+ /// command handlers are present, this method calls them to create a response
+ /// and then passes the response back within the @c answer argument.
+ ///
+ /// Values of all arguments can be modified by the hook library.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Original command received.
+ /// @param [out] answer Command processing result returned by the hook.
+ ///
+ /// @return Boolean value indicating if any callouts have been executed.
+ bool
+ delegateCommandToHookLibrary(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd,
+ isc::data::ElementPtr& answer);
+
+ /// @brief Handles the command having a given name and arguments.
+ ///
+ /// This method calls @ref HookedCommandMgr::delegateCommandToHookLibrary to
+ /// try to process the command with the hook libraries, if they are installed.
+ ///
+ /// @param cmd_name Command name.
+ /// @param params Command arguments.
+ /// @param original_cmd Original command received.
+ ///
+ /// @return Pointer to the const data element representing response
+ /// to a command.
+ virtual isc::data::ConstElementPtr
+ handleCommand(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd);
+
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
new file mode 100644
index 0000000..546c357
--- /dev/null
+++ b/src/lib/config/tests/Makefile.am
@@ -0,0 +1,50 @@
+SUBDIRS = testdata .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\"
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(srcdir)/../../asiolink/testutils/ca\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+DISTCLEANFILES = data_def_unittests_config.h
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = client_connection_unittests.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_SOURCES += command_mgr_unittests.cc
+run_unittests_SOURCES += cmd_http_listener_unittests.cc
+run_unittests_SOURCES += cmd_response_creator_unittests.cc
+run_unittests_SOURCES += cmd_response_creator_factory_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/config/libkea-cfgclient.la
+run_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+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/log/libkea-log.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 += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+run_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/config/tests/Makefile.in b/src/lib/config/tests/Makefile.in
new file mode 100644
index 0000000..8fd3708
--- /dev/null
+++ b/src/lib/config/tests/Makefile.in
@@ -0,0 +1,1108 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/config/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = data_def_unittests_config.h
+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 = client_connection_unittests.cc \
+ run_unittests.cc command_mgr_unittests.cc \
+ cmd_http_listener_unittests.cc \
+ cmd_response_creator_unittests.cc \
+ cmd_response_creator_factory_unittests.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = run_unittests-client_connection_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-command_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-cmd_http_listener_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-cmd_response_creator_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-cmd_response_creator_factory_unittests.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) \
+@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-client_connection_unittests.Po \
+ ./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po \
+ ./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po \
+ ./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po \
+ ./$(DEPDIR)/run_unittests-command_mgr_unittests.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 =
+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)`
+ETAGS = etags
+CTAGS = ctags
+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 \
+ $(srcdir)/data_def_unittests_config.h.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\" \
+ -DTEST_CA_DIR=\"$(srcdir)/../../asiolink/testutils/ca\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+DISTCLEANFILES = data_def_unittests_config.h
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ client_connection_unittests.cc \
+@HAVE_GTEST_TRUE@ run_unittests.cc command_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ cmd_http_listener_unittests.cc \
+@HAVE_GTEST_TRUE@ cmd_response_creator_unittests.cc \
+@HAVE_GTEST_TRUE@ cmd_response_creator_factory_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 = $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_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/config/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config/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):
+data_def_unittests_config.h: $(top_builddir)/config.status $(srcdir)/data_def_unittests_config.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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-client_connection_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-command_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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-client_connection_unittests.o: client_connection_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-client_connection_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-client_connection_unittests.Tpo -c -o run_unittests-client_connection_unittests.o `test -f 'client_connection_unittests.cc' || echo '$(srcdir)/'`client_connection_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-client_connection_unittests.Tpo $(DEPDIR)/run_unittests-client_connection_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_connection_unittests.cc' object='run_unittests-client_connection_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-client_connection_unittests.o `test -f 'client_connection_unittests.cc' || echo '$(srcdir)/'`client_connection_unittests.cc
+
+run_unittests-client_connection_unittests.obj: client_connection_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-client_connection_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-client_connection_unittests.Tpo -c -o run_unittests-client_connection_unittests.obj `if test -f 'client_connection_unittests.cc'; then $(CYGPATH_W) 'client_connection_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_connection_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-client_connection_unittests.Tpo $(DEPDIR)/run_unittests-client_connection_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_connection_unittests.cc' object='run_unittests-client_connection_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-client_connection_unittests.obj `if test -f 'client_connection_unittests.cc'; then $(CYGPATH_W) 'client_connection_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_connection_unittests.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`
+
+run_unittests-command_mgr_unittests.o: command_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_mgr_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo -c -o run_unittests-command_mgr_unittests.o `test -f 'command_mgr_unittests.cc' || echo '$(srcdir)/'`command_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo $(DEPDIR)/run_unittests-command_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_mgr_unittests.cc' object='run_unittests-command_mgr_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-command_mgr_unittests.o `test -f 'command_mgr_unittests.cc' || echo '$(srcdir)/'`command_mgr_unittests.cc
+
+run_unittests-command_mgr_unittests.obj: command_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo -c -o run_unittests-command_mgr_unittests.obj `if test -f 'command_mgr_unittests.cc'; then $(CYGPATH_W) 'command_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_mgr_unittests.Tpo $(DEPDIR)/run_unittests-command_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_mgr_unittests.cc' object='run_unittests-command_mgr_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-command_mgr_unittests.obj `if test -f 'command_mgr_unittests.cc'; then $(CYGPATH_W) 'command_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_mgr_unittests.cc'; fi`
+
+run_unittests-cmd_http_listener_unittests.o: cmd_http_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_http_listener_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo -c -o run_unittests-cmd_http_listener_unittests.o `test -f 'cmd_http_listener_unittests.cc' || echo '$(srcdir)/'`cmd_http_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_http_listener_unittests.cc' object='run_unittests-cmd_http_listener_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-cmd_http_listener_unittests.o `test -f 'cmd_http_listener_unittests.cc' || echo '$(srcdir)/'`cmd_http_listener_unittests.cc
+
+run_unittests-cmd_http_listener_unittests.obj: cmd_http_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_http_listener_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo -c -o run_unittests-cmd_http_listener_unittests.obj `if test -f 'cmd_http_listener_unittests.cc'; then $(CYGPATH_W) 'cmd_http_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_http_listener_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Tpo $(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_http_listener_unittests.cc' object='run_unittests-cmd_http_listener_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-cmd_http_listener_unittests.obj `if test -f 'cmd_http_listener_unittests.cc'; then $(CYGPATH_W) 'cmd_http_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_http_listener_unittests.cc'; fi`
+
+run_unittests-cmd_response_creator_unittests.o: cmd_response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo -c -o run_unittests-cmd_response_creator_unittests.o `test -f 'cmd_response_creator_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_unittests.cc' object='run_unittests-cmd_response_creator_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-cmd_response_creator_unittests.o `test -f 'cmd_response_creator_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_unittests.cc
+
+run_unittests-cmd_response_creator_unittests.obj: cmd_response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo -c -o run_unittests-cmd_response_creator_unittests.obj `if test -f 'cmd_response_creator_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_unittests.cc' object='run_unittests-cmd_response_creator_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-cmd_response_creator_unittests.obj `if test -f 'cmd_response_creator_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_unittests.cc'; fi`
+
+run_unittests-cmd_response_creator_factory_unittests.o: cmd_response_creator_factory_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_factory_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo -c -o run_unittests-cmd_response_creator_factory_unittests.o `test -f 'cmd_response_creator_factory_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_factory_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_factory_unittests.cc' object='run_unittests-cmd_response_creator_factory_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-cmd_response_creator_factory_unittests.o `test -f 'cmd_response_creator_factory_unittests.cc' || echo '$(srcdir)/'`cmd_response_creator_factory_unittests.cc
+
+run_unittests-cmd_response_creator_factory_unittests.obj: cmd_response_creator_factory_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-cmd_response_creator_factory_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo -c -o run_unittests-cmd_response_creator_factory_unittests.obj `if test -f 'cmd_response_creator_factory_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_factory_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_factory_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Tpo $(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cmd_response_creator_factory_unittests.cc' object='run_unittests-cmd_response_creator_factory_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-cmd_response_creator_factory_unittests.obj `if test -f 'cmd_response_creator_factory_unittests.cc'; then $(CYGPATH_W) 'cmd_response_creator_factory_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cmd_response_creator_factory_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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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-client_connection_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-command_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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-client_connection_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_http_listener_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_factory_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-cmd_response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-command_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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/config/tests/client_connection_unittests.cc b/src/lib/config/tests/client_connection_unittests.cc
new file mode 100644
index 0000000..3ecca82
--- /dev/null
+++ b/src/lib/config/tests/client_connection_unittests.cc
@@ -0,0 +1,183 @@
+// Copyright (C) 2017-2019 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 <testutils/sandbox.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/testutils/test_server_unix_socket.h>
+#include <cc/json_feed.h>
+#include <config/client_connection.h>
+#include <gtest/gtest.h>
+#include <cstdlib>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+
+namespace {
+
+/// @brief Test timeout in ms.
+const long TEST_TIMEOUT = 10000;
+
+/// Test fixture class for @ref ClientConnection.
+class ClientConnectionTest : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Constructor.
+ ///
+ /// Removes unix socket descriptor before the test.
+ ClientConnectionTest() :
+ io_service_(),
+ test_socket_(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath())) {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes unix socket descriptor after the test.
+ virtual ~ClientConnectionTest() {
+ removeUnixSocketFile();
+ }
+
+ /// @brief Returns socket file path.
+ ///
+ /// If the KEA_SOCKET_TEST_DIR environment variable is specified, the
+ /// socket file is created in the location pointed to by this variable.
+ /// Otherwise, it is created in the build directory.
+ ///
+ /// The KEA_SOCKET_TEST_DIR is typically used to overcome the problem of
+ /// a system limit on the unix socket file path (usually 102 or 103 characters).
+ /// When Kea build is located in the nested directories with absolute path
+ /// exceeding this limit, the test system should be configured to set
+ /// the KEA_SOCKET_TEST_DIR environmental variable to point to an alternative
+ /// location, e.g. /tmp, with an absolute path length being within the
+ /// allowed range.
+ std::string unixSocketFilePath() {
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = sandbox.join("test-socket");
+ }
+ return (socket_path);
+ }
+
+ /// @brief Removes unix socket descriptor.
+ void removeUnixSocketFile() {
+ static_cast<void>(remove(unixSocketFilePath().c_str()));
+ }
+
+ /// @brief IO service used by the tests.
+ IOService io_service_;
+
+ /// @brief Server side unix socket used in these tests.
+ test::TestServerUnixSocketPtr test_socket_;
+};
+
+// Tests successful transaction: connect, send command and receive a
+// response.
+TEST_F(ClientConnectionTest, success) {
+ // Start timer protecting against test timeouts.
+ test_socket_->startTimer(TEST_TIMEOUT);
+
+ // Start the server.
+ test_socket_->bindServerSocket();
+ test_socket_->generateCustomResponse(2048);
+
+ // Create some valid command.
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ ClientConnection conn(io_service_);
+
+ // This boolean value will indicate when the callback function is invoked
+ // at the end of the transaction (whether it is successful or unsuccessful).
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [&handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& feed) {
+ // Indicate that the handler has been called to break from the
+ // while loop below.
+ handler_invoked = true;
+ // The ec should contain no error.
+ ASSERT_FALSE(ec);
+ // The JSONFeed should be present and it should contain a valid
+ // response.
+ ASSERT_TRUE(feed);
+ EXPECT_TRUE(feed->feedOk()) << feed->getErrorMessage();
+ });
+ // Run the connection.
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// This test checks that a timeout is signalled when the communication
+// takes too long.
+TEST_F(ClientConnectionTest, timeout) {
+ // The server will return only partial JSON response (lacking closing
+ // brace). The client will wait for closing brace and eventually the
+ // connection should time out.
+ test_socket_.reset(new test::TestServerUnixSocket(io_service_,
+ unixSocketFilePath(),
+ "{ \"command\": \"foo\""));
+ test_socket_->startTimer(TEST_TIMEOUT);
+
+ // Start the server.
+ test_socket_->bindServerSocket();
+
+ // Command to be sent to the server.
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ ClientConnection conn(io_service_);
+
+ // This boolean value will be set to true when the callback is invoked.
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [&handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& /*feed*/) {
+ // Indicate that the callback has been invoked to break the loop
+ // below.
+ handler_invoked = true;
+ ASSERT_TRUE(ec);
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ }, ClientConnection::Timeout(1000));
+
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+// This test checks that an error is returned when the client is unable
+// to connect to the server.
+TEST_F(ClientConnectionTest, connectionError) {
+ // Create the new connection but do not bind the server socket.
+ // The connection should be refused and an error returned.
+ ClientConnection conn(io_service_);
+
+ std::string command = "{ \"command\": \"list-commands\" }";
+
+ bool handler_invoked = false;
+ conn.start(ClientConnection::SocketPath(unixSocketFilePath()),
+ ClientConnection::ControlCommand(command),
+ [&handler_invoked](const boost::system::error_code& ec,
+ const ConstJSONFeedPtr& /*feed*/) {
+ handler_invoked = true;
+ ASSERT_TRUE(ec);
+ });
+
+ while (!handler_invoked && !test_socket_->isStopped()) {
+ io_service_.run_one();
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/config/tests/cmd_http_listener_unittests.cc b/src/lib/config/tests/cmd_http_listener_unittests.cc
new file mode 100644
index 0000000..0093980
--- /dev/null
+++ b/src/lib/config/tests/cmd_http_listener_unittests.cc
@@ -0,0 +1,980 @@
+// Copyright (C) 2021-2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/testutils/test_tls.h>
+#include <cc/command_interpreter.h>
+#include <config/cmd_http_listener.h>
+#include <config/command_mgr.h>
+#include <http/response.h>
+#include <http/response_parser.h>
+#include <http/tests/test_http_client.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <thread>
+#include <list>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::config;
+using namespace isc::data;
+using namespace boost::asio::ip;
+using namespace isc::http;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref CmdHttpListener.
+class CmdHttpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts, deregisters all commands
+ /// from CommandMgr, and enables multi-threading mode.
+ CmdHttpListenerTest()
+ : listener_(), io_service_(), test_timer_(io_service_),
+ run_io_service_timer_(io_service_), clients_(), num_threads_(),
+ num_clients_(), num_in_progress_(0), num_finished_(0), chunk_size_(0),
+ pause_cnt_(0) {
+ test_timer_.setup(std::bind(&CmdHttpListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+
+ // Deregisters commands.
+ CommandMgr::instance().deregisterAll();
+
+ // Enable multi-threading.
+ MultiThreadingMgr::instance().setMode(true);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes HTTP clients, unregisters commands, disables MT.
+ virtual ~CmdHttpListenerTest() {
+ // Wipe out the listener.
+ listener_.reset();
+
+ // Destroy all remaining clients.
+ for (auto const& client : clients_) {
+ client->close();
+ }
+
+ // Deregisters commands.
+ config::CommandMgr::instance().deregisterAll();
+
+ // Disable multi-threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Constructs a complete HTTP POST given a request body.
+ ///
+ /// @param request_body string containing the desired request body.
+ ///
+ /// @return string containing the constructed POST.
+ std::string buildPostStr(const std::string& request_body) {
+ // Create the command string.
+ std::stringstream ss;
+ ss << "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: "
+ << request_body.size() << "\r\n\r\n"
+ << request_body;
+ return (ss.str());
+ }
+
+ /// @brief Initiates a command via a new HTTP client.
+ ///
+ /// This method creates a TestHttpClient instance, adds the
+ /// client to the list of clients, and starts a request based
+ /// on the given command. The client will run on the main
+ /// thread and be driven by the test's IOService instance.
+ ///
+ /// @param request_body JSON String containing the API command
+ /// to be sent.
+ void startRequest(const std::string& request_body = "{ }") {
+ std::string request_str = buildPostStr(request_body);
+
+ // Instantiate the client.
+ TestHttpClientPtr client(new TestHttpClient(io_service_, SERVER_ADDRESS,
+ SERVER_PORT));
+ // Add it to the list of clients.
+ clients_.push_back(client);
+
+ // Start the request. Note, nothing happens until the IOService runs.
+ client->startRequest(request_str);
+ }
+
+ /// @brief Initiates a "thread" command via a new HTTP client.
+ ///
+ /// This method creates a TestHttpClient instance, adds the
+ /// client to the list of clients, and starts a request based
+ /// on the given command. The client will run on the main
+ /// thread and be driven by the test's IOService instance.
+ ///
+ /// The command has a single argument, "client-ptr". The function creates
+ /// the value for this argument from the pointer address of client instance
+ /// it creates. This argument should be echoed back in the response, along
+ /// with the thread-id of the CmdHttpListener thread which handled the
+ /// command. The response body should look this:
+ ///
+ /// ```
+ /// [ { "arguments": { "client-ptr": "xxxxx", "thread-id": "zzzzz" }, "result": 0} ]
+ /// ```
+ void startThreadCommand() {
+ // Create a new client.
+ TestHttpClientPtr client(new TestHttpClient(io_service_, SERVER_ADDRESS,
+ SERVER_PORT));
+
+ // Construct the "thread" command post including the argument,
+ // "client-ptr", whose value is the stringified pointer to the
+ // newly created client.
+ std::stringstream request_body;
+ request_body << "{\"command\": \"thread\", \"arguments\": { \"client-ptr\": \""
+ << client << "\" } }";
+
+ std::string command = buildPostStr(request_body.str());
+
+ // Add it to the list of clients.
+ clients_.push_back(client);
+
+ // Start the request. Note, nothing happens until the IOService runs.
+ ASSERT_NO_THROW_LOG(client->startRequest(command));
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// We iterate over calls to asio::io_service.run(), until
+ /// all the clients have completed their requests. We do it this way
+ /// because the test clients stop the io_service when they're
+ /// through with a request.
+ ///
+ /// @param request_limit Desired number of requests the function should wait
+ /// to be processed before returning.
+ void runIOService(size_t request_limit = 0) {
+ if (!request_limit) {
+ request_limit = clients_.size();
+ }
+
+ // Loop until the clients are done, an error occurs, or the time runs out.
+ size_t num_done = 0;
+ while (num_done != request_limit) {
+ // Always call restart() before we call run();
+ io_service_.restart();
+
+ // Run until a client stops the service.
+ io_service_.run();
+
+ // If all the clients are done receiving, the test is done.
+ num_done = 0;
+ for (auto const& client : clients_) {
+ if (client->receiveDone()) {
+ ++num_done;
+ }
+ }
+ }
+ }
+
+ /// @brief Create an HttpResponse from a response string.
+ ///
+ /// @param response_str a string containing the whole HTTP
+ /// response received.
+ ///
+ /// @return An HttpResponse constructed from by parsing the
+ /// response string.
+ HttpResponsePtr parseResponse(const std::string response_str) {
+ HttpResponsePtr hr(new HttpResponse());
+ HttpResponseParser parser(*hr);
+ parser.initModel();
+ parser.postBuffer(&response_str[0], response_str.size());
+ parser.poll();
+ if (!parser.httpParseOk()) {
+ isc_throw(Unexpected, "response_str: '" << response_str
+ << "' failed to parse: " << parser.getErrorMessage());
+ }
+
+ return (hr);
+ }
+
+ /// @brief Handler for the 'foo' command.
+ ///
+ /// The command needs no arguments and returns a response
+ /// with a body containing:
+ ///
+ /// "[ { \"arguments\": [ \"bar\" ], \"result\": 0 } ]")
+ ///
+ /// @param command_name Command name, i.e. 'foo'.
+ /// @param command_arguments Command arguments (empty).
+ ///
+ /// @return Returns response with a single string "bar".
+ ConstElementPtr fooCommandHandler(const std::string& /*command_name*/,
+ const ConstElementPtr& /*command_arguments*/) {
+ ElementPtr arguments = Element::createList();
+ arguments->add(Element::create("bar"));
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Handler for the 'thread' command.
+ ///
+ /// @param command_name Command name, i.e. 'thread'.
+ /// @param command_arguments Command arguments should contain
+ /// one string element, "client-ptr", whose value is the stringified
+ /// pointer to the client that issued the command.
+ ///
+ /// @return Returns response with map of arguments containing
+ /// a string value 'thread-id': <thread id>
+ ConstElementPtr synchronizedCommandHandler(const std::string& /*command_name*/,
+ const ConstElementPtr& command_arguments) {
+ // If the number of in progress commands is less than the number
+ // of threads, then wait here until we're notified. Otherwise,
+ // notify everyone and finish. The idea is to force each thread
+ // to handle the same number of requests over the course of the
+ // test, making verification reliable.
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ ++num_in_progress_;
+ if (num_in_progress_ == chunk_size_) {
+ num_finished_ = 0;
+ cv_.notify_all();
+ } else {
+ bool ret = cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_in_progress_ == chunk_size_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to start work";
+ }
+ }
+ }
+
+ // Create the map of response arguments.
+ ElementPtr arguments = Element::createMap();
+ // First we echo the client-ptr command argument.
+ ConstElementPtr client_ptr = command_arguments->get("client-ptr");
+ if (!client_ptr) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "missing client-ptr"));
+ }
+
+ arguments->set("client-ptr", client_ptr);
+
+ // Now we add the thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ arguments->set("thread-id", Element::create(ss.str()));
+
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ num_finished_++;
+ if (num_finished_ == chunk_size_) {
+ // We're all done, notify the others and finish.
+ num_in_progress_ = 0;
+ cv_.notify_all();
+ } else {
+ // I'm done but others aren't wait here.
+ bool ret = cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_finished_ == chunk_size_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to finish work";
+ }
+ }
+ }
+
+ EXPECT_THROW(listener_->start(), InvalidOperation);
+ EXPECT_THROW(listener_->pause(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(listener_->resume(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(listener_->stop(), MultiThreadingInvalidOperation);
+
+ // We're done, ship it!
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Handler for the 'thread' command.
+ ///
+ /// @param command_name Command name, i.e. 'thread'.
+ /// @param command_arguments Command arguments should contain
+ /// one string element, "client-ptr", whose value is the stringified
+ /// pointer to the client that issued the command.
+ ///
+ /// @return Returns response with map of arguments containing
+ /// a string value 'thread-id': <thread id>
+ ConstElementPtr simpleCommandHandler(const std::string& /*command_name*/,
+ const ConstElementPtr& command_arguments) {
+ // Create the map of response arguments.
+ ElementPtr arguments = Element::createMap();
+ // First we echo the client-ptr command argument.
+ ConstElementPtr client_ptr = command_arguments->get("client-ptr");
+ if (!client_ptr) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "missing client-ptr"));
+ }
+
+ arguments->set("client-ptr", client_ptr);
+
+ // Now we add the thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ arguments->set("thread-id", Element::create(ss.str()));
+
+ // We're done, ship it!
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Submits one or more thread commands to a CmdHttpListener.
+ ///
+ /// This function command will create a CmdHttpListener
+ /// with the given number of threads, initiates the given
+ /// number of clients, each requesting the "thread" command,
+ /// and then iteratively runs the test's IOService until all
+ /// the clients have received their responses or an error occurs.
+ ///
+ /// It requires that the number of clients, when greater than the
+ /// number of threads, be a multiple of the number of threads. The
+ /// thread command handler is structured in such a way as to ensure
+ /// (we hope) that each thread handles the same number of commands.
+ ///
+ /// @param num_threads - the number of threads the CmdHttpListener
+ /// should use. Must be greater than 0.
+ /// @param num_clients - the number of clients that should issue the
+ /// thread command. Each client is used to carry out a single thread
+ /// command request. Must be greater than 0 and a multiple of num_threads
+ /// if it is greater than num_threads.
+ void threadListenAndRespond(size_t num_threads, size_t num_clients) {
+ // First we makes sure the parameter rules apply.
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_clients);
+ ASSERT_TRUE((num_clients < num_threads) || (num_clients % num_threads == 0));
+
+ num_threads_ = num_threads;
+ num_clients_ = num_clients;
+ chunk_size_ = num_threads_;
+ if (num_clients_ < chunk_size_) {
+ chunk_size_ = num_clients_;
+ }
+
+ // Register the thread command handler.
+ CommandMgr::instance().registerCommand("thread",
+ std::bind(&CmdHttpListenerTest
+ ::synchronizedCommandHandler,
+ this, ph::_1, ph::_2));
+
+ // Create a listener with prescribed number of threads.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, num_threads)));
+ ASSERT_TRUE(listener_);
+
+ // Start it and verify it is running.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), num_threads);
+
+ // Maps the number of clients served by a given thread-id.
+ std::map<std::string, int> clients_per_thread;
+
+ // Initiate the prescribed number of command requests.
+ num_in_progress_ = 0;
+ while (clients_.size() < num_clients) {
+ ASSERT_NO_THROW_LOG(startThreadCommand());
+ }
+
+ // Now we run the client-side IOService until all requests are done,
+ // errors occur or the test times out.
+ ASSERT_NO_FATAL_FAILURE(runIOService());
+
+ // Stop the listener and then verify it has stopped.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Iterate over the clients, checking their outcomes.
+ size_t total_responses = 0;
+ for (auto const& client : clients_) {
+ // Client should have completed its receive successfully.
+ ASSERT_TRUE(client->receiveDone());
+
+ // Client response should not be empty.
+ HttpResponsePtr hr;
+ std::string response_str = client->getResponse();
+ ASSERT_FALSE(response_str.empty());
+
+ // Parse the response into an HttpResponse.
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // Now we walk the element tree to get the response data. It should look
+ // this:
+ //
+ // [ {
+ // "arguments": { "client-ptr": "xxxxx",
+ // "thread-id": "zzzzz" },
+ // "result": 0
+ // } ]
+ //
+ // First we turn it into an Element tree.
+ std::string body_str = hr->getBody();
+ ConstElementPtr body;
+ ASSERT_NO_THROW_LOG(body = Element::fromJSON(hr->getBody()));
+
+ // Outermost is a list, since we're emulating agent responses.
+ ASSERT_EQ(body->getType(), Element::list);
+ ASSERT_EQ(body->size(), 1);
+
+ // Answer should be a map containing "arguments" and "results".
+ ConstElementPtr answer = body->get(0);
+ ASSERT_EQ(answer->getType(), Element::map);
+
+ // "result" should be 0.
+ ConstElementPtr result = answer->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result->getType(), Element::integer);
+ ASSERT_EQ(result->intValue(), 0);
+
+ // "arguments" is a map containing "client-ptr" and "thread-id".
+ ConstElementPtr arguments = answer->get("arguments");
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(arguments->getType(), Element::map);
+
+ // "client-ptr" is a string.
+ ConstElementPtr client_ptr = arguments->get("client-ptr");
+ ASSERT_TRUE(client_ptr);
+ ASSERT_EQ(client_ptr->getType(), Element::string);
+
+ // "thread-id" is a string.
+ ConstElementPtr thread_id = arguments->get("thread-id");
+ ASSERT_TRUE(thread_id);
+ ASSERT_EQ(thread_id->getType(), Element::string);
+ std::string thread_id_str = thread_id->stringValue();
+
+ // Make sure the response received was for this client.
+ std::stringstream ss;
+ ss << client;
+ ASSERT_EQ(client_ptr->stringValue(), ss.str());
+
+ // Bump the client count for the given thread-id.
+ auto it = clients_per_thread.find(thread_id_str);
+ if (it != clients_per_thread.end()) {
+ clients_per_thread[thread_id_str] = it->second + 1;
+ } else {
+ clients_per_thread[thread_id_str] = 1;
+ }
+
+ ++total_responses;
+ }
+
+ // We should have responses for all our clients.
+ EXPECT_EQ(total_responses, num_clients);
+
+ // Verify we have the expected number of entries in our map.
+ size_t expected_thread_count = (num_clients < num_threads ?
+ num_clients : num_threads);
+
+ ASSERT_EQ(clients_per_thread.size(), expected_thread_count);
+
+ // Each thread-id ought to have handled the same number of clients.
+ for (auto const& it : clients_per_thread) {
+ EXPECT_EQ(it.second, num_clients / clients_per_thread.size())
+ << "thread-id: " << it.first
+ << ", clients: " << it.second << std::endl;
+ }
+ }
+
+ /// @brief Pauses and resumes a CmdHttpListener while it processes command
+ /// requests.
+ ///
+ /// This function command will create a CmdHttpListener
+ /// with the given number of threads, initiates the given
+ /// number of clients, each requesting the "thread" command,
+ /// and then iteratively runs the test's IOService until all
+ /// the clients have received their responses or an error occurs.
+ /// It will pause and resume the listener at intervals governed
+ /// by the given number of pauses.
+ ///
+ /// @param num_threads - the number of threads the CmdHttpListener
+ /// should use. Must be greater than 0.
+ /// @param num_clients - the number of clients that should issue the
+ /// thread command. Each client is used to carry out a single thread
+ /// command request. Must be greater than 0.
+ /// @param num_pauses Desired number of times the listener should be
+ /// paused during the test. Must be greater than 0.
+ void workPauseAndResume(size_t num_threads, size_t num_clients,
+ size_t num_pauses) {
+ // First we makes sure the parameter rules apply.
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_clients);
+ ASSERT_TRUE(num_pauses);
+ num_threads_ = num_threads;
+ num_clients_ = num_clients;
+
+ // Register the thread command handler.
+ CommandMgr::instance().registerCommand("thread",
+ std::bind(&CmdHttpListenerTest
+ ::simpleCommandHandler,
+ this, ph::_1, ph::_2));
+
+ // Create a listener with prescribed number of threads.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, num_threads)));
+ ASSERT_TRUE(listener_);
+
+ // Start it and verify it is running.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), num_threads);
+
+ // Initiate the prescribed number of command requests.
+ num_in_progress_ = 0;
+ while (clients_.size() < num_clients) {
+ ASSERT_NO_THROW_LOG(startThreadCommand());
+ }
+
+ // Now we run the client-side IOService until all requests are done,
+ // errors occur or the test times out. We'll pause and resume the
+ // number of times given by num_pauses.
+ size_t num_done = 0;
+ size_t total_requests = clients_.size();
+ while (num_done < total_requests) {
+ // Calculate how many more requests to process before we pause again.
+ // We divide the number of outstanding requests by the number of pauses
+ // and stop after we've done at least that many more requests.
+ size_t request_limit = (pause_cnt_ < num_pauses ?
+ (num_done + ((total_requests - num_done) / num_pauses))
+ : total_requests);
+
+ // Run test IOService until we hit the limit.
+ runIOService(request_limit);
+
+ // If we've done all our pauses we should be through.
+ if (pause_cnt_ == num_pauses) {
+ break;
+ }
+
+ // Pause the client.
+ ASSERT_NO_THROW(listener_->pause());
+ ASSERT_TRUE(listener_->isPaused());
+ ++pause_cnt_;
+
+ // Check our progress.
+ num_done = 0;
+ for (auto const& client : clients_) {
+ if (client->receiveDone()) {
+ ++num_done;
+ }
+ }
+
+ // We should completed at least as many as our
+ // target limit.
+ ASSERT_GE(num_done, request_limit);
+
+ // Resume the listener.
+ ASSERT_NO_THROW(listener_->resume());
+ ASSERT_TRUE(listener_->isRunning());
+ }
+
+ // Stop the listener and then verify it has stopped.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Iterate over the clients, checking their outcomes.
+ size_t total_responses = 0;
+ for (auto const& client : clients_) {
+ // Client should have completed its receive successfully.
+ ASSERT_TRUE(client->receiveDone());
+
+ // Client response should not be empty.
+ HttpResponsePtr hr;
+ std::string response_str = client->getResponse();
+ ASSERT_FALSE(response_str.empty());
+
+ // Parse the response into an HttpResponse.
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // Now we walk the element tree to get the response data. It should look
+ // this:
+ //
+ // [ {
+ // "arguments": { "client-ptr": "xxxxx",
+ // "thread-id": "zzzzz" },
+ // "result": 0
+ // } ]
+ //
+ // First we turn it into an Element tree.
+ std::string body_str = hr->getBody();
+ ConstElementPtr body;
+ ASSERT_NO_THROW_LOG(body = Element::fromJSON(hr->getBody()));
+
+ // Outermost is a list, since we're emulating agent responses.
+ ASSERT_EQ(body->getType(), Element::list);
+ ASSERT_EQ(body->size(), 1);
+
+ // Answer should be a map containing "arguments" and "results".
+ ConstElementPtr answer = body->get(0);
+ ASSERT_EQ(answer->getType(), Element::map);
+
+ // "result" should be 0.
+ ConstElementPtr result = answer->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result->getType(), Element::integer);
+ ASSERT_EQ(result->intValue(), 0);
+
+ // "arguments" is a map containing "client-ptr" and "thread-id".
+ ConstElementPtr arguments = answer->get("arguments");
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(arguments->getType(), Element::map);
+
+ // "client-ptr" is a string.
+ ConstElementPtr client_ptr = arguments->get("client-ptr");
+ ASSERT_TRUE(client_ptr);
+ ASSERT_EQ(client_ptr->getType(), Element::string);
+
+ // "thread-id" is a string.
+ ConstElementPtr thread_id = arguments->get("thread-id");
+ ASSERT_TRUE(thread_id);
+ ASSERT_EQ(thread_id->getType(), Element::string);
+ std::string thread_id_str = thread_id->stringValue();
+
+ // Make sure the response received was for this client.
+ std::stringstream ss;
+ ss << client;
+ ASSERT_EQ(client_ptr->stringValue(), ss.str());
+
+ ++total_responses;
+ }
+
+ // We should have responses for all our clients.
+ EXPECT_EQ(total_responses, num_clients);
+
+ // We should have had the expected number of pauses.
+ if (!num_pauses) {
+ ASSERT_EQ(pause_cnt_, 0);
+ } else {
+ // We allow a range on pauses of +-1.
+ ASSERT_TRUE((num_pauses - 1) <= pause_cnt_ &&
+ (pause_cnt_ <= (num_pauses + 1)))
+ << " num_pauses: " << num_pauses
+ << ", pause_cnt_" << pause_cnt_;
+ }
+ }
+
+ /// @brief CmdHttpListener instance under test.
+ CmdHttpListenerPtr listener_;
+
+ /// @brief IO service used in drive the test and test clients.
+ IOService io_service_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TestHttpClientPtr> clients_;
+
+ /// @brief Number of threads the listener should use for the test.
+ size_t num_threads_;
+
+ /// @brief Number of client requests to make during the test.
+ size_t num_clients_;
+
+ /// @brief Number of requests currently in progress.
+ size_t num_in_progress_;
+
+ /// @brief Number of requests that have finished.
+ size_t num_finished_;
+
+ /// @brief Chunk size of requests that need to be processed in parallel.
+ ///
+ /// This can either be the number of threads (if the number of requests is
+ /// greater than the number of threads) or the number of requests (if the
+ /// number of threads is greater than the number of requests).
+ size_t chunk_size_;
+
+ /// @brief Mutex used to lock during thread coordination.
+ std::mutex mutex_;
+
+ /// @brief Condition variable used to coordinate threads.
+ std::condition_variable cv_;
+
+ /// @brief Number of times client has been paused during the test.
+ size_t pause_cnt_;
+};
+
+/// Verifies the construction, starting, stopping, pausing, resuming,
+/// and destruction of CmdHttpListener.
+TEST_F(CmdHttpListenerTest, basics) {
+ // Make sure multi-threading is off.
+ MultiThreadingMgr::instance().setMode(false);
+ IOAddress address(SERVER_ADDRESS);
+ uint16_t port = SERVER_PORT;
+
+ // Make sure we can create one.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(address, port)));
+ ASSERT_TRUE(listener_);
+
+ // Verify the getters do what we expect.
+ EXPECT_EQ(listener_->getAddress(), address);
+ EXPECT_EQ(listener_->getPort(), port);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 1);
+ EXPECT_FALSE(listener_->getTlsContext());
+
+ // It should not have an IOService, should not be listening and
+ // should have no threads.
+ ASSERT_FALSE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Verify that we cannot start it when multi-threading is disabled.
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ ASSERT_THROW_MSG(listener_->start(), InvalidOperation,
+ "CmdHttpListener cannot be started"
+ " when multi-threading is disabled");
+
+ // It should still not be listening and have no threads.
+ EXPECT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Enable multi-threading.
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Make sure we can start it and it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Trying to start it again should fail.
+ ASSERT_THROW_MSG(listener_->start(), InvalidOperation,
+ "CmdHttpListener already started!");
+
+ // Stop it and verify we're no longer listening.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+ ASSERT_FALSE(listener_->getThreadIOService());
+
+ // Make sure we can call stop again without problems.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+
+ // We should be able to restart it.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Destroying it should also stop it.
+ // If the test timeouts we know it didn't!
+ ASSERT_NO_THROW_LOG(listener_.reset());
+
+ // Verify we can construct with more than one thread.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(address, port, 4)));
+ ASSERT_NO_THROW_LOG(listener_->start());
+ EXPECT_EQ(listener_->getAddress(), address);
+ EXPECT_EQ(listener_->getPort(), port);
+ EXPECT_EQ(listener_->getThreadCount(), 4);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(listener_->isRunning());
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Verify we can pause it. We should still be listening, threads intact,
+ // IOService stopped, state set to PAUSED.
+ ASSERT_NO_THROW_LOG(listener_->pause());
+ ASSERT_TRUE(listener_->isPaused());
+ EXPECT_EQ(listener_->getThreadCount(), 4);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->getThreadIOService()->stopped());
+
+ // Verify we can resume it.
+ ASSERT_NO_THROW_LOG(listener_->resume());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 4);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Stop it and verify we're no longer listening.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 4);
+ ASSERT_FALSE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->isStopped());
+}
+
+// This test verifies that an HTTP connection can be established and used to
+// transmit an HTTP request and receive the response.
+TEST_F(CmdHttpListenerTest, basicListenAndRespond) {
+
+ // Create a listener with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(IOAddress(SERVER_ADDRESS),
+ SERVER_PORT)));
+ ASSERT_TRUE(listener_);
+
+ // Start the listener and verify it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+
+ // Now let's send a "foo" command. This should create a client, connect
+ // to our listener, post our request and retrieve our reply.
+ ASSERT_NO_THROW(startRequest("{\"command\": \"foo\"}"));
+ ++num_clients_;
+ ASSERT_EQ(num_clients_, clients_.size());
+ ASSERT_NO_THROW(runIOService());
+ TestHttpClientPtr client = clients_.front();
+ ASSERT_TRUE(client);
+
+ // Parse the response into an HttpResponse.
+ HttpResponsePtr hr;
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // Without a command handler loaded, we should get an unsupported command response.
+ EXPECT_EQ(hr->getBody(), "[ { \"result\": 2, \"text\": \"'foo' command not supported.\" } ]");
+
+ // Now let's register the foo command handler.
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CmdHttpListenerTest::fooCommandHandler,
+ this, ph::_1, ph::_2));
+ // Try posting the foo command again.
+ ASSERT_NO_THROW(startRequest("{\"command\": \"foo\"}"));
+ ++num_clients_;
+ ASSERT_EQ(num_clients_, clients_.size());
+ ASSERT_NO_THROW(runIOService());
+ client = clients_.back();
+ ASSERT_TRUE(client);
+
+ // Parse the response.
+ ASSERT_NO_THROW_LOG(hr = parseResponse(client->getResponse()));
+
+ // We should have a response from our command handler.
+ EXPECT_EQ(hr->getBody(), "[ { \"arguments\": [ \"bar\" ], \"result\": 0 } ]");
+
+ // Make sure the listener is still listening.
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+
+ // Stop the listener then verify it has stopped.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+}
+
+// Now we'll run some permutations of the number of listener threads
+// and the number of client requests.
+
+// One thread, one client.
+TEST_F(CmdHttpListenerTest, oneByOne) {
+ size_t num_threads = 1;
+ size_t num_clients = 1;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// One thread, four clients.
+TEST_F(CmdHttpListenerTest, oneByFour) {
+ size_t num_threads = 1;
+ size_t num_clients = 4;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, one clients.
+TEST_F(CmdHttpListenerTest, fourByOne) {
+ size_t num_threads = 4;
+ size_t num_clients = 1;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, four clients.
+TEST_F(CmdHttpListenerTest, fourByFour) {
+ size_t num_threads = 4;
+ size_t num_clients = 4;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, eight clients.
+TEST_F(CmdHttpListenerTest, fourByEight) {
+ size_t num_threads = 4;
+ size_t num_clients = 8;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Six threads, eighteen clients.
+TEST_F(CmdHttpListenerTest, sixByEighteen) {
+ size_t num_threads = 6;
+ size_t num_clients = 18;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Pauses and resumes the listener while it is processing
+// requests.
+TEST_F(CmdHttpListenerTest, pauseAndResume) {
+ size_t num_threads = 6;
+ size_t num_clients = 18;
+ size_t num_pauses = 3;
+ workPauseAndResume(num_threads, num_clients, num_pauses);
+}
+
+// Check if a TLS listener can be created.
+TEST_F(CmdHttpListenerTest, tls) {
+ IOAddress address(SERVER_ADDRESS);
+ uint16_t port = SERVER_PORT;
+ TlsContextPtr context;
+ configServer(context);
+
+ // Make sure we can create the listener.
+ ASSERT_NO_THROW_LOG(listener_.reset(new CmdHttpListener(address, port, 1, context)));
+ EXPECT_EQ(listener_->getAddress(), address);
+ EXPECT_EQ(listener_->getPort(), port);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 1);
+ EXPECT_EQ(listener_->getTlsContext(), context);
+ EXPECT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+
+ // Make sure we can start it and it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(listener_->start());
+ ASSERT_TRUE(listener_->isRunning());
+ EXPECT_EQ(listener_->getThreadCount(), 1);
+ ASSERT_TRUE(listener_->getThreadIOService());
+ EXPECT_FALSE(listener_->getThreadIOService()->stopped());
+
+ // Stop it.
+ ASSERT_NO_THROW_LOG(listener_->stop());
+ ASSERT_TRUE(listener_->isStopped());
+ EXPECT_EQ(listener_->getThreadCount(), 0);
+ EXPECT_EQ(listener_->getThreadPoolSize(), 1);
+ ASSERT_FALSE(listener_->getThreadIOService());
+ EXPECT_TRUE(listener_->isStopped());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/config/tests/cmd_response_creator_factory_unittests.cc b/src/lib/config/tests/cmd_response_creator_factory_unittests.cc
new file mode 100644
index 0000000..50f2dd8
--- /dev/null
+++ b/src/lib/config/tests/cmd_response_creator_factory_unittests.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2021-2022 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 <config/cmd_response_creator_factory.h>
+#include <boost/pointer_cast.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::config;
+
+namespace {
+
+// This test verifies the default factory constructor and
+// the create() method.
+TEST(CmdResponseCreatorFactory, createDefault) {
+ // Create the factory.
+ CmdResponseCreatorFactory factory;
+
+ // Create a response creator.
+ CmdResponseCreatorPtr response1;
+ ASSERT_NO_THROW(response1 = boost::dynamic_pointer_cast<
+ CmdResponseCreator>(factory.create()));
+ ASSERT_TRUE(response1);
+
+ // Agent response emulation should be enabled by default.
+ EXPECT_TRUE(response1->emulateAgentResponse());
+
+ // Authorization configuration should be an empty pointer.
+ EXPECT_FALSE(CmdResponseCreator::http_auth_config_);
+
+ // By default all commands are accepted.
+ EXPECT_TRUE(CmdResponseCreator::command_accept_list_.empty());
+
+ // Invoke create() again.
+ CmdResponseCreatorPtr response2;
+ ASSERT_NO_THROW(response2 = boost::dynamic_pointer_cast<
+ CmdResponseCreator>(factory.create()));
+ ASSERT_TRUE(response2);
+
+ // And it must always return the same object.
+ EXPECT_TRUE(response1 == response2);
+}
+
+// This test verifies that agent response emulation can
+// be turned off.
+TEST(CmdResponseCreatorFactory, createAgentEmulationDisabled) {
+ // Instantiate the factory with agent emulation disabled.
+ CmdResponseCreatorFactory factory(false);
+
+ // Create the response creator.
+ CmdResponseCreatorPtr response;
+ ASSERT_NO_THROW(response = boost::dynamic_pointer_cast<
+ CmdResponseCreator>(factory.create()));
+ ASSERT_TRUE(response);
+
+ // Agent response emulation should be disabled.
+ EXPECT_FALSE(response->emulateAgentResponse());
+
+ // Authorization configuration should be an empty pointer.
+ EXPECT_FALSE(CmdResponseCreator::http_auth_config_);
+
+ // By default all commands are accepted.
+ EXPECT_TRUE(CmdResponseCreator::command_accept_list_.empty());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/config/tests/cmd_response_creator_unittests.cc b/src/lib/config/tests/cmd_response_creator_unittests.cc
new file mode 100644
index 0000000..7ab27c0
--- /dev/null
+++ b/src/lib/config/tests/cmd_response_creator_unittests.cc
@@ -0,0 +1,438 @@
+// Copyright (C) 2021-2022 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 <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <config/cmd_response_creator.h>
+#include <http/post_request.h>
+#include <http/post_request_json.h>
+#include <http/response_json.h>
+
+#include <gtest/gtest.h>
+#include <boost/pointer_cast.hpp>
+#include <functional>
+
+using namespace isc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::http;
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Test fixture class for @ref CmdResponseCreator.
+class CmdResponseCreatorTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates instance of the response creator and uses this instance to
+ /// create "empty" request. It also removes registered commands from the
+ /// command manager.
+ CmdResponseCreatorTest() {
+ // Deregisters commands.
+ config::CommandMgr::instance().deregisterAll();
+ // Register our "foo" command.
+ config::CommandMgr::instance().
+ registerCommand("foo", std::bind(&CmdResponseCreatorTest::fooCommandHandler,
+ this, ph::_1, ph::_2));
+ // Clear class variables.
+ CmdResponseCreator::http_auth_config_.reset();
+ CmdResponseCreator::command_accept_list_.clear();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes registered commands from the command manager.
+ virtual ~CmdResponseCreatorTest() {
+ config::CommandMgr::instance().deregisterAll();
+ CmdResponseCreator::http_auth_config_.reset();
+ CmdResponseCreator::command_accept_list_.clear();
+ }
+
+ /// @brief SetUp function that wraps call to initCreator.
+ ///
+ /// Creates a default CmdResponseCreator and new HttpRequest.
+ virtual void SetUp() {
+ initCreator();
+ }
+
+ /// @brief Creates a new CmdResponseCreator and new HttpRequest.
+ ///
+ /// @param emulate_agent_flag enables/disables agent response emulation
+ /// in the CmdResponsCreator.
+ void initCreator(bool emulate_agent_flag = true) {
+ response_creator_.reset(new CmdResponseCreator(emulate_agent_flag));
+ request_ = response_creator_->createNewHttpRequest();
+ ASSERT_TRUE(request_) << "initCreator failed to create request";
+ }
+
+ /// @brief Fills request context with required data to create new request.
+ ///
+ /// @param request Request which context should be configured.
+ void setBasicContext(const HttpRequestPtr& request) {
+ request->context()->method_ = "POST";
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 1;
+ request->context()->uri_ = "/foo";
+
+ // Content-Type
+ HttpHeaderContext content_type;
+ content_type.name_ = "Content-Type";
+ content_type.value_ = "application/json";
+ request->context()->headers_.push_back(content_type);
+
+ // Content-Length
+ HttpHeaderContext content_length;
+ content_length.name_ = "Content-Length";
+ content_length.value_ = "0";
+ request->context()->headers_.push_back(content_length);
+ }
+
+ /// @brief Test creation of stock response.
+ ///
+ /// @param status_code Status code to be included in the response.
+ /// @param must_contain Text that must be present in the textual
+ /// representation of the generated response.
+ void testStockResponse(const HttpStatusCode& status_code,
+ const string& must_contain) {
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(
+ response = response_creator_->createStockHttpResponse(request_,
+ status_code)
+ );
+ ASSERT_TRUE(response);
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+ // Make sure the response contains the string specified as argument.
+ EXPECT_TRUE(response_json->toString().find(must_contain) != string::npos);
+
+ }
+
+ /// @brief Handler for the 'foo' test command.
+ ///
+ /// @param command_name Command name, i.e. 'foo'.
+ /// @param command_arguments Command arguments (empty).
+ ///
+ /// @return Returns response with a single string "bar".
+ ConstElementPtr fooCommandHandler(const string& /*command_name*/,
+ const ConstElementPtr& /*command_arguments*/) {
+ ElementPtr arguments = Element::createList();
+ arguments->add(Element::create("bar"));
+ return (createAnswer(CONTROL_RESULT_SUCCESS, arguments));
+ }
+
+ /// @brief Instance of the response creator.
+ CmdResponseCreatorPtr response_creator_;
+
+ /// @brief Instance of the "empty" request.
+ ///
+ /// The context belonging to this request may be modified by the unit
+ /// tests to verify various scenarios of response creation.
+ HttpRequestPtr request_;
+};
+
+// This test verifies that the created "empty" request has valid type.
+TEST_F(CmdResponseCreatorTest, createNewHttpRequest) {
+ // The request must be of PostHttpRequestJson type.
+ PostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast<
+ PostHttpRequestJson>(request_);
+ ASSERT_TRUE(request_json);
+}
+
+// Test that HTTP version of stock response is set to 1.0 if the request
+// context doesn't specify any version.
+TEST_F(CmdResponseCreatorTest, createStockHttpResponseNoVersion) {
+ testStockResponse(HttpStatusCode::BAD_REQUEST, "HTTP/1.0 400 Bad Request");
+}
+
+// Test that HTTP version of stock response is set to 1.0 if the request
+// version is higher than 1.1.
+TEST_F(CmdResponseCreatorTest, createStockHttpResponseHighVersion) {
+ request_->context()->http_version_major_ = 2;
+ testStockResponse(HttpStatusCode::REQUEST_TIMEOUT,
+ "HTTP/1.0 408 Request Timeout");
+}
+
+// Test that the server responds with version 1.1 if request version is 1.1.
+TEST_F(CmdResponseCreatorTest, createStockHttpResponseCorrectVersion) {
+ request_->context()->http_version_major_ = 1;
+ request_->context()->http_version_minor_ = 1;
+ testStockResponse(HttpStatusCode::NO_CONTENT, "HTTP/1.1 204 No Content");
+}
+
+// Test successful server response when the client specifies valid command.
+TEST_F(CmdResponseCreatorTest, createDynamicHttpResponse) {
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response should be in a list by default.
+ ASSERT_TRUE(response_creator_->emulateAgentResponse());
+ ASSERT_TRUE(response_json->getBodyAsJson()->getType() == Element::list)
+ << "response is not a list: " << response_json->toString();
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+// Test successful server response without emulating agent response.
+TEST_F(CmdResponseCreatorTest, createDynamicHttpResponseNoEmulation) {
+ // Recreate the response creator setting emulate_agent_response to false;
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response should be a map that is not enclosed in a list.
+ ASSERT_FALSE(response_creator_->emulateAgentResponse());
+ ASSERT_TRUE(response_json->getBodyAsJson()->getType() == Element::map)
+ << "response is not a map: " << response_json->toString();
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+// This test verifies that Internal Server Error is returned when invalid C++
+// request type is used. This is considered an error in the server logic.
+TEST_F(CmdResponseCreatorTest, createDynamicHttpResponseInvalidType) {
+ PostHttpRequestPtr request(new PostHttpRequest());
+ setBasicContext(request);
+
+ // Body: "list-commands" is natively supported by the command manager.
+ request->context()->body_ = "{ \"command\": \"list-commands\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request->finalize());
+
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must contain Internal Server Error status code.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 500 Internal Server Error") !=
+ string::npos);
+}
+
+// This test verifies command filtering.
+TEST_F(CmdResponseCreatorTest, filterCommand) {
+ initCreator(false);
+ setBasicContext(request_);
+ // For the log message...
+ request_->setRemote("127.0.0.1");
+
+ HttpResponseJsonPtr response;
+ ConstElementPtr body;
+ unordered_set<string> accept;
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ accept.insert("foo");
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = Element::createList();
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = Element::createMap();
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = createCommand("foo", ConstElementPtr());
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+
+ body = createCommand("bar", ConstElementPtr());
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_TRUE(response);
+ EXPECT_EQ("HTTP/1.1 403 Forbidden", response->toBriefString());
+
+ accept.clear();
+ ASSERT_NO_THROW(response = response_creator_->filterCommand(request_, body, accept));
+ EXPECT_FALSE(response);
+}
+
+// This test verifies basic HTTP authentication - reject case.
+// Empty case was handled in createDynamicHttpResponseNoEmulation.
+TEST_F(CmdResponseCreatorTest, basicAuthReject) {
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // Add no basic HTTP authentication.
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create basic HTTP authentication configuration.
+ CmdResponseCreator::http_auth_config_.reset(new BasicHttpAuthConfig());
+ BasicHttpAuthConfigPtr basic =
+ boost::dynamic_pointer_cast<BasicHttpAuthConfig>(
+ CmdResponseCreator::http_auth_config_);
+ ASSERT_TRUE(basic);
+ EXPECT_NO_THROW(basic->add("test", "", "123\xa3", ""));
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must not be successful.
+ EXPECT_EQ("HTTP/1.1 401 Unauthorized", response->toBriefString());
+}
+
+// This test verifies basic HTTP authentication - accept case.
+// Empty case was handled in createDynamicHttpResponseNoEmulation.
+TEST_F(CmdResponseCreatorTest, basicAuthAccept) {
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // Add basic HTTP authentication.
+ HttpHeaderContext auth("Authorization", "Basic dGVzdDoxMjPCow==");
+ request_->context()->headers_.push_back(auth);
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create basic HTTP authentication configuration.
+ CmdResponseCreator::http_auth_config_.reset(new BasicHttpAuthConfig());
+ BasicHttpAuthConfigPtr basic =
+ boost::dynamic_pointer_cast<BasicHttpAuthConfig>(
+ CmdResponseCreator::http_auth_config_);
+ ASSERT_TRUE(basic);
+ EXPECT_NO_THROW(basic->add("test", "", "123\xa3", ""));
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+// This test verifies command filtering at the HTTP level - reject case.
+TEST_F(CmdResponseCreatorTest, filterCommandReject) {
+ initCreator(false);
+ setBasicContext(request_);
+ // For the log message...
+ request_->setRemote("127.0.0.1");
+
+ // Body: "bar" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"bar\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Add foo in the access list.
+ CmdResponseCreator::command_accept_list_.insert("foo");
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must not be successful.
+ EXPECT_EQ("HTTP/1.1 403 Forbidden", response->toBriefString());
+}
+
+// This test verifies command filtering at the HTTP level - accept case.
+TEST_F(CmdResponseCreatorTest, filterCommandAccept) {
+ initCreator(false);
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Add foo in the access list.
+ CmdResponseCreator::command_accept_list_.insert("foo");
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_->createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Response must be convertible to HttpResponseJsonPtr.
+ HttpResponseJsonPtr response_json = boost::dynamic_pointer_cast<
+ HttpResponseJson>(response);
+ ASSERT_TRUE(response_json);
+
+ // Response must be successful.
+ EXPECT_TRUE(response_json->toString().find("HTTP/1.1 200 OK") !=
+ string::npos);
+
+ // Response must contain JSON body with "result" of 0.
+ EXPECT_TRUE(response_json->toString().find("\"result\": 0") !=
+ string::npos);
+}
+
+}
diff --git a/src/lib/config/tests/command_mgr_unittests.cc b/src/lib/config/tests/command_mgr_unittests.cc
new file mode 100644
index 0000000..b607d3e
--- /dev/null
+++ b/src/lib/config/tests/command_mgr_unittests.cc
@@ -0,0 +1,570 @@
+// Copyright (C) 2015-2020 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 <testutils/sandbox.h>
+#include <asiolink/io_service.h>
+#include <config/base_command_mgr.h>
+#include <config/command_mgr.h>
+#include <config/hooked_command_mgr.h>
+#include <cc/command_interpreter.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+#include <string>
+#include <vector>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace std;
+
+// Test class for Command Manager
+class CommandMgrTest : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// Default constructor
+ CommandMgrTest()
+ : io_service_(new IOService()) {
+
+ CommandMgr::instance().setIOService(io_service_);
+
+ handler_name_ = "";
+ handler_params_ = ElementPtr();
+ handler_called_ = false;
+ callout_name_ = "";
+ callout_argument_names_.clear();
+ std::string processed_log_ = "";
+
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().closeCommandSocket();
+
+ resetCalloutIndicators();
+ }
+
+ /// Default destructor
+ virtual ~CommandMgrTest() {
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().closeCommandSocket();
+ resetCalloutIndicators();
+ }
+
+ /// @brief Returns socket path (using either hardcoded path or env variable)
+ /// @return path to the unix socket
+ std::string getSocketPath() {
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = sandbox.join("test-socket");
+ }
+ return (socket_path);
+ }
+
+ /// @brief Resets indicators related to callout invocation.
+ ///
+ /// It also removes any registered callouts.
+ static void resetCalloutIndicators() {
+ callout_name_ = "";
+ callout_argument_names_.clear();
+
+ // Iterate over existing hook points and for each of them remove
+ // callouts registered.
+ std::vector<std::string> hooks = ServerHooks::getServerHooksPtr()->getHookNames();
+ for (auto h = hooks.cbegin(); h != hooks.cend(); ++h) {
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(*h);
+ }
+ }
+
+ /// @brief A simple command handler that always returns an eror
+ static ConstElementPtr my_handler(const std::string& name,
+ const ConstElementPtr& params) {
+
+ handler_name_ = name;
+ handler_params_ = params;
+ handler_called_ = true;
+
+ return (createAnswer(123, "test error message"));
+ }
+
+ /// @brief Test callback which stores callout name and passed arguments and
+ /// which handles the command.
+ ///
+ /// @param callout_handle Handle passed by the hooks framework.
+ /// @return Always 0.
+ static int
+ hook_lib_callout(CalloutHandle& callout_handle) {
+ callout_name_ = "hook_lib_callout";
+
+ ConstElementPtr command;
+ callout_handle.getArgument("command", command);
+
+ ConstElementPtr arg;
+ std::string command_name = parseCommand(arg, command);
+
+ callout_handle.setArgument("response",
+ createAnswer(234, "text generated by hook handler"));
+
+ callout_argument_names_ = callout_handle.getArgumentNames();
+ // Sort arguments alphabetically, so as we can access them on
+ // expected positions and verify.
+ std::sort(callout_argument_names_.begin(), callout_argument_names_.end());
+ return (0);
+ }
+
+ /// @brief Test callback which stores callout name and passed arguments and
+ /// which handles the command.
+ ///
+ /// @param callout_handle Handle passed by the hooks framework.
+ /// @return Always 0.
+ static int
+ command_processed_callout(CalloutHandle& callout_handle) {
+ callout_name_ = "command_processed_handler";
+
+ std::string name;
+ callout_handle.getArgument("name", name);
+
+ ConstElementPtr arguments;
+ callout_handle.getArgument("arguments", arguments);
+
+ ConstElementPtr response;
+ callout_handle.getArgument("response", response);
+ std::ostringstream os;
+ os << name << ":" << arguments->str() << ":" << response->str();
+ processed_log_ = os.str();
+
+ if (name == "change-response") {
+ callout_handle.setArgument("response",
+ createAnswer(777, "replaced response text"));
+ }
+
+ return (0);
+ }
+
+ /// @brief IO service used by these tests.
+ IOServicePtr io_service_;
+
+ /// @brief Name of the command (used in my_handler)
+ static std::string handler_name_;
+
+ /// @brief Parameters passed to the handler (used in my_handler)
+ static ConstElementPtr handler_params_;
+
+ /// @brief Indicates whether my_handler was called
+ static bool handler_called_;
+
+ /// @brief Holds invoked callout name.
+ static std::string callout_name_;
+
+ /// @brief Holds a list of arguments passed to the callout.
+ static std::vector<std::string> callout_argument_names_;
+
+ /// @brief Holds the generated command process log
+ static std::string processed_log_;
+};
+
+/// Name passed to the handler (used in my_handler)
+std::string CommandMgrTest::handler_name_("");
+
+/// Parameters passed to the handler (used in my_handler)
+ConstElementPtr CommandMgrTest::handler_params_;
+
+/// Indicates whether my_handler was called
+bool CommandMgrTest::handler_called_(false);
+
+/// Holds invoked callout name.
+std::string CommandMgrTest::callout_name_("");
+
+/// @brief Holds the generated command process log
+std::string CommandMgrTest::processed_log_;
+
+/// Holds a list of arguments passed to the callout.
+std::vector<std::string> CommandMgrTest::callout_argument_names_;
+
+// Test checks whether the internal command 'list-commands'
+// is working properly.
+TEST_F(CommandMgrTest, listCommandsEmpty) {
+
+ ConstElementPtr command = createCommand("list-commands");
+
+ ConstElementPtr answer;
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ ASSERT_TRUE(answer);
+
+ EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
+ answer->str());
+}
+
+// Test checks whether calling a bogus command is handled properly.
+TEST_F(CommandMgrTest, bogusCommand) {
+
+ ConstElementPtr command = createCommand("no-such-command");
+
+ ConstElementPtr answer;
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // Make sure the status code is non-zero
+ ASSERT_TRUE(answer);
+ int status_code;
+ parseAnswer(status_code, answer);
+ EXPECT_EQ(CONTROL_RESULT_COMMAND_UNSUPPORTED, status_code);
+}
+
+// Test checks whether handlers installation is sanitized. In particular,
+// whether NULL handler and attempt to install handlers for the same
+// command twice are rejected.
+TEST_F(CommandMgrTest, handlerInstall) {
+
+ // Check that it's not allowed to install NULL pointer instead of a real
+ // command.
+ EXPECT_THROW(CommandMgr::instance().registerCommand("my-command", 0),
+ InvalidCommandHandler);
+
+ // This registration should succeed.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Check that it's not possible to install handlers for the same
+ // command twice.
+ EXPECT_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler), InvalidCommandName);
+}
+
+// Test checks whether the internal list-commands command is working
+// correctly. Also, checks installation and deinstallation of other
+// command handlers.
+TEST_F(CommandMgrTest, listCommands) {
+
+ // Let's install two custom commands.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("make-a-coffee",
+ my_handler));
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("do-the-dishes",
+ my_handler));
+
+ // And then run 'list-commands'
+ ConstElementPtr list_all = createCommand("list-commands");
+ ConstElementPtr answer;
+
+ // Now check that the command is returned by list-commands
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"arguments\": [ \"do-the-dishes\", \"list-commands\", "
+ "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
+
+ // Now unregister one command
+ EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("do-the-dishes"));
+
+ // Now check that the command is returned by list-commands
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_all));
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"arguments\": [ \"list-commands\", "
+ "\"make-a-coffee\" ], \"result\": 0 }", answer->str());
+
+ // Now test deregistration. It should work the first time.
+ EXPECT_NO_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"));
+
+ // Second time should throw an exception as the handler is no longer there.
+ EXPECT_THROW(CommandMgr::instance().deregisterCommand("make-a-coffee"),
+ InvalidCommandName);
+
+ // You can't uninstall list-commands as it's the internal handler.
+ // It always must be there.
+ EXPECT_THROW(CommandMgr::instance().deregisterCommand("list-commands"),
+ InvalidCommandName);
+
+ // Attempt to register a handler for existing command should fail.
+ EXPECT_THROW(CommandMgr::instance().registerCommand("list-commands",
+ my_handler), InvalidCommandName);
+}
+
+// Test checks whether deregisterAll method uninstalls all handlers,
+// except list-commands.
+TEST_F(CommandMgrTest, deregisterAll) {
+
+ // Install a couple handlers.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command1",
+ my_handler));
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command2",
+ my_handler));
+
+ EXPECT_NO_THROW(CommandMgr::instance().deregisterAll());
+
+ ConstElementPtr answer;
+ EXPECT_NO_THROW(answer = CommandMgr::instance()
+ .processCommand(createCommand("list-commands")));
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"arguments\": [ \"list-commands\" ], \"result\": 0 }",
+ answer->str());
+}
+
+// Test checks whether a command handler can be installed and then
+// runs through processCommand to check that it's indeed called.
+TEST_F(CommandMgrTest, processCommand) {
+ // Install my handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("my-command", my_params);
+ ConstElementPtr answer;
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // my_handler remembers all passed parameters and returns status code of 123.
+ ConstElementPtr answer_arg;
+ int status_code;
+ // Check that the returned status code is correct.
+ EXPECT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(123, status_code);
+
+ // Check that the parameters passed are correct.
+ EXPECT_EQ(true, handler_called_);
+ EXPECT_EQ("my-command", handler_name_);
+ ASSERT_TRUE(handler_params_);
+ EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", handler_params_->str());
+
+ // Command handlers not installed so expecting that callouts weren't
+ // called.
+ EXPECT_TRUE(callout_name_.empty());
+}
+
+// Verify that processing a command can be delegated to a hook library.
+TEST_F(CommandMgrTest, delegateProcessCommand) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
+ "my-command", hook_lib_callout);
+
+ // Install local handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("my-command", my_params);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // Local handler shouldn't be called because the command is handled by the
+ // hook library.
+ ASSERT_FALSE(handler_called_);
+
+ // Returned status should be unique for the hook library.
+ ConstElementPtr answer_arg;
+ int status_code;
+ ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(234, status_code);
+
+ EXPECT_EQ("hook_lib_callout", callout_name_);
+
+ // Check that the appropriate arguments have been set. Include the
+ // 'response' which should have been set by the callout.
+ ASSERT_EQ(2, callout_argument_names_.size());
+ EXPECT_EQ("command", callout_argument_names_[0]);
+ EXPECT_EQ("response", callout_argument_names_[1]);
+}
+
+// Verify that 'list-command' command returns combined list of supported
+// commands from hook library and from the Kea Command Manager.
+TEST_F(CommandMgrTest, delegateListCommands) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCommandCallout(
+ "my-command", hook_lib_callout);
+
+ // Create my-command-bis which is unique for the local Command Manager,
+ // i.e. not supported by the hook library. This command should also
+ // be returned as a result of processing 'list-commands'.
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command-bis",
+ my_handler));
+
+ // Process command. The command should be routed to the hook library
+ // and the hook library should return the commands it supports.
+ ConstElementPtr command = createCommand("list-commands");
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ ConstElementPtr answer_arg;
+ int status_code;
+ ASSERT_NO_THROW(answer_arg = parseAnswer(status_code, answer));
+ EXPECT_EQ(0, status_code);
+
+ // The hook library supports: my-command and list-commands commands. The
+ // local Command Manager supports list-commands and my-command-bis. The
+ // combined list should include 3 unique commands.
+ const std::vector<ElementPtr>& commands_list = answer_arg->listValue();
+ ASSERT_EQ(3, commands_list.size());
+ std::vector<std::string> command_names_list;
+ for (auto cmd = commands_list.cbegin(); cmd != commands_list.cend();
+ ++cmd) {
+ command_names_list.push_back((*cmd)->stringValue());
+ }
+ std::sort(command_names_list.begin(), command_names_list.end());
+ EXPECT_EQ("list-commands", command_names_list[0]);
+ EXPECT_EQ("my-command", command_names_list[1]);
+ EXPECT_EQ("my-command-bis", command_names_list[2]);
+}
+
+// This test verifies that a Unix socket can be opened properly and that input
+// parameters (socket-type and socket-name) are verified.
+TEST_F(CommandMgrTest, unixCreate) {
+ // Null pointer is obviously a bad idea.
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(ConstElementPtr()),
+ isc::config::BadSocketInfo);
+
+ // So is passing no parameters.
+ ElementPtr socket_info = Element::createMap();
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ // We don't support ipx sockets
+ socket_info->set("socket-type", Element::create("ipx"));
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-type", Element::create("unix"));
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-name", Element::create(getSocketPath()));
+ EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
+
+ // It should be possible to close the socket.
+ EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
+}
+
+// This test checks that when unix path is too long, the socket cannot be opened.
+TEST_F(CommandMgrTest, unixCreateTooLong) {
+ ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
+ "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
+ "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
+ "\" }");
+
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ SocketError);
+}
+
+// This test verifies that a registered callout for the command_processed
+// hookpoint is invoked and passed the correct information.
+TEST_F(CommandMgrTest, commandProcessedHook) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "command_processed", command_processed_callout);
+
+ // Install local handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("my-command", my_params);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // Local handler should be called
+ ASSERT_TRUE(handler_called_);
+
+ // Verify that the response came through intact
+ EXPECT_EQ("{ \"result\": 123, \"text\": \"test error message\" }",
+ answer->str());
+
+ // Make sure invoked the command_processed callout
+ EXPECT_EQ("command_processed_handler", callout_name_);
+
+ // Verify the callout could extract all the context arguments
+ EXPECT_EQ("my-command:[ \"just\", \"some\", \"data\" ]:"
+ "{ \"result\": 123, \"text\": \"test error message\" }",
+ processed_log_);
+}
+
+// This test verifies that a registered callout for the command_processed
+// hookpoint is invoked and can replace the command response content.
+TEST_F(CommandMgrTest, commandProcessedHookReplaceResponse) {
+ // Register callout so as we can check that it is called before
+ // processing the command by the manager.
+ HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "command_processed", command_processed_callout);
+
+ // Install local handler
+ EXPECT_NO_THROW(CommandMgr::instance().registerCommand("my-command",
+ my_handler));
+
+ // Now tell CommandMgr to process a command 'my-command' with the
+ // specified parameter.
+ ElementPtr my_params = Element::fromJSON("[ \"just\", \"some\", \"data\" ]");
+ ConstElementPtr command = createCommand("change-response", my_params);
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = CommandMgr::instance().processCommand(command));
+
+ // There should be an answer.
+ ASSERT_TRUE(answer);
+
+ // Local handler should not have been called, command isn't recognized
+ ASSERT_FALSE(handler_called_);
+
+ // Verify that we overrode response came
+ EXPECT_EQ("{ \"result\": 777, \"text\": \"replaced response text\" }",
+ answer->str());
+
+ // Make sure invoked the command_processed callout
+ EXPECT_EQ("command_processed_handler", callout_name_);
+
+ // Verify the callout could extract all the context arguments
+ EXPECT_EQ("change-response:[ \"just\", \"some\", \"data\" ]:"
+ "{ \"result\": 2, \"text\": \"'change-response' command not supported.\" }",
+ processed_log_);
+}
+
+// Verifies that a socket cannot be concurrently opened more than once.
+TEST_F(CommandMgrTest, exclusiveOpen) {
+ // Pass in valid parameters.
+ ElementPtr socket_info = Element::createMap();
+ socket_info->set("socket-type", Element::create("unix"));
+ socket_info->set("socket-name", Element::create(getSocketPath()));
+
+ EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
+
+ // Should not be able to open it twice.
+ EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::SocketError);
+
+ // Now let's close it.
+ EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
+
+ // Should be able to re-open it now.
+ EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
+
+ // Now let's close it.
+ EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
+}
diff --git a/src/lib/config/tests/data_def_unittests_config.h.in b/src/lib/config/tests/data_def_unittests_config.h.in
new file mode 100644
index 0000000..0837f84
--- /dev/null
+++ b/src/lib/config/tests/data_def_unittests_config.h.in
@@ -0,0 +1,7 @@
+// 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/.
+
+#define TEST_DATA_PATH "@abs_srcdir@/testdata"
diff --git a/src/lib/config/tests/run_unittests.cc b/src/lib/config/tests/run_unittests.cc
new file mode 100644
index 0000000..fc385f9
--- /dev/null
+++ b/src/lib/config/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// 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 <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/config/tests/testdata/Makefile.am b/src/lib/config/tests/testdata/Makefile.am
new file mode 100644
index 0000000..641a142
--- /dev/null
+++ b/src/lib/config/tests/testdata/Makefile.am
@@ -0,0 +1,59 @@
+EXTRA_DIST = data22_1.data
+EXTRA_DIST += data22_2.data
+EXTRA_DIST += data22_3.data
+EXTRA_DIST += data22_4.data
+EXTRA_DIST += data22_5.data
+EXTRA_DIST += data22_6.data
+EXTRA_DIST += data22_7.data
+EXTRA_DIST += data22_8.data
+EXTRA_DIST += data22_9.data
+EXTRA_DIST += data22_10.data
+EXTRA_DIST += data32_1.data
+EXTRA_DIST += data32_2.data
+EXTRA_DIST += data32_3.data
+EXTRA_DIST += data33_1.data
+EXTRA_DIST += data33_2.data
+EXTRA_DIST += data41_1.data
+EXTRA_DIST += data41_2.data
+EXTRA_DIST += spec1.spec
+EXTRA_DIST += spec2.spec
+EXTRA_DIST += spec3.spec
+EXTRA_DIST += spec4.spec
+EXTRA_DIST += spec5.spec
+EXTRA_DIST += spec6.spec
+EXTRA_DIST += spec7.spec
+EXTRA_DIST += spec8.spec
+EXTRA_DIST += spec9.spec
+EXTRA_DIST += spec10.spec
+EXTRA_DIST += spec11.spec
+EXTRA_DIST += spec12.spec
+EXTRA_DIST += spec13.spec
+EXTRA_DIST += spec14.spec
+EXTRA_DIST += spec15.spec
+EXTRA_DIST += spec16.spec
+EXTRA_DIST += spec17.spec
+EXTRA_DIST += spec18.spec
+EXTRA_DIST += spec19.spec
+EXTRA_DIST += spec20.spec
+EXTRA_DIST += spec21.spec
+EXTRA_DIST += spec22.spec
+EXTRA_DIST += spec23.spec
+EXTRA_DIST += spec24.spec
+EXTRA_DIST += spec25.spec
+EXTRA_DIST += spec26.spec
+EXTRA_DIST += spec27.spec
+EXTRA_DIST += spec28.spec
+EXTRA_DIST += spec29.spec
+EXTRA_DIST += spec30.spec
+EXTRA_DIST += spec31.spec
+EXTRA_DIST += spec32.spec
+EXTRA_DIST += spec33.spec
+EXTRA_DIST += spec34.spec
+EXTRA_DIST += spec35.spec
+EXTRA_DIST += spec36.spec
+EXTRA_DIST += spec37.spec
+EXTRA_DIST += spec38.spec
+EXTRA_DIST += spec39.spec
+EXTRA_DIST += spec40.spec
+EXTRA_DIST += spec41.spec
+EXTRA_DIST += spec42.spec
diff --git a/src/lib/config/tests/testdata/Makefile.in b/src/lib/config/tests/testdata/Makefile.in
new file mode 100644
index 0000000..9bbe3e7
--- /dev/null
+++ b/src/lib/config/tests/testdata/Makefile.in
@@ -0,0 +1,560 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/config/tests/testdata
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+EXTRA_DIST = data22_1.data data22_2.data data22_3.data data22_4.data \
+ data22_5.data data22_6.data data22_7.data data22_8.data \
+ data22_9.data data22_10.data data32_1.data data32_2.data \
+ data32_3.data data33_1.data data33_2.data data41_1.data \
+ data41_2.data spec1.spec spec2.spec spec3.spec spec4.spec \
+ spec5.spec spec6.spec spec7.spec spec8.spec spec9.spec \
+ spec10.spec spec11.spec spec12.spec spec13.spec spec14.spec \
+ spec15.spec spec16.spec spec17.spec spec18.spec spec19.spec \
+ spec20.spec spec21.spec spec22.spec spec23.spec spec24.spec \
+ spec25.spec spec26.spec spec27.spec spec28.spec spec29.spec \
+ spec30.spec spec31.spec spec32.spec spec33.spec spec34.spec \
+ spec35.spec spec36.spec spec37.spec spec38.spec spec39.spec \
+ spec40.spec spec41.spec spec42.spec
+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/config/tests/testdata/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config/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:
+
+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
+
+
+# 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/config/tests/testdata/data22_1.data b/src/lib/config/tests/testdata/data22_1.data
new file mode 100644
index 0000000..18732c7
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_1.data
@@ -0,0 +1,9 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_10.data b/src/lib/config/tests/testdata/data22_10.data
new file mode 100644
index 0000000..fed4001
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_10.data
@@ -0,0 +1,11 @@
+{
+ "version": 123,
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": "e" } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_2.data b/src/lib/config/tests/testdata/data22_2.data
new file mode 100644
index 0000000..c820706
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_2.data
@@ -0,0 +1,8 @@
+{
+ "value1": "asdf",
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true }
+}
diff --git a/src/lib/config/tests/testdata/data22_3.data b/src/lib/config/tests/testdata/data22_3.data
new file mode 100644
index 0000000..7d57dfe
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_3.data
@@ -0,0 +1,8 @@
+{
+ "value1": 1,
+ "value2": false,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true }
+}
diff --git a/src/lib/config/tests/testdata/data22_4.data b/src/lib/config/tests/testdata/data22_4.data
new file mode 100644
index 0000000..52862d5
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_4.data
@@ -0,0 +1,8 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, "a" ],
+ "value6": { "v61": "bar", "v62": true }
+}
diff --git a/src/lib/config/tests/testdata/data22_5.data b/src/lib/config/tests/testdata/data22_5.data
new file mode 100644
index 0000000..43937d6
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_5.data
@@ -0,0 +1,8 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": "Break" }
+}
diff --git a/src/lib/config/tests/testdata/data22_6.data b/src/lib/config/tests/testdata/data22_6.data
new file mode 100644
index 0000000..7b53490
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_6.data
@@ -0,0 +1,10 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value7": [ 1, 2.2, "str", true ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_7.data b/src/lib/config/tests/testdata/data22_7.data
new file mode 100644
index 0000000..98f537b
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_7.data
@@ -0,0 +1,10 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": "e" } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_8.data b/src/lib/config/tests/testdata/data22_8.data
new file mode 100644
index 0000000..8bc0aa9
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_8.data
@@ -0,0 +1,10 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": 1 } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/config/tests/testdata/data22_9.data b/src/lib/config/tests/testdata/data22_9.data
new file mode 100644
index 0000000..f115194
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_9.data
@@ -0,0 +1,11 @@
+{
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": "e" } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } },
+ "value_does_not_exist": 1
+}
diff --git a/src/lib/config/tests/testdata/data32_1.data b/src/lib/config/tests/testdata/data32_1.data
new file mode 100644
index 0000000..5695b52
--- /dev/null
+++ b/src/lib/config/tests/testdata/data32_1.data
@@ -0,0 +1,3 @@
+{
+ "named_set_item": { "foo": 1, "bar": 2 }
+}
diff --git a/src/lib/config/tests/testdata/data32_2.data b/src/lib/config/tests/testdata/data32_2.data
new file mode 100644
index 0000000..d5b9765
--- /dev/null
+++ b/src/lib/config/tests/testdata/data32_2.data
@@ -0,0 +1,3 @@
+{
+ "named_set_item": { "foo": "wrongtype", "bar": 2 }
+}
diff --git a/src/lib/config/tests/testdata/data32_3.data b/src/lib/config/tests/testdata/data32_3.data
new file mode 100644
index 0000000..85f32fe
--- /dev/null
+++ b/src/lib/config/tests/testdata/data32_3.data
@@ -0,0 +1,3 @@
+{
+ "named_set_item": []
+}
diff --git a/src/lib/config/tests/testdata/data33_1.data b/src/lib/config/tests/testdata/data33_1.data
new file mode 100644
index 0000000..429852c
--- /dev/null
+++ b/src/lib/config/tests/testdata/data33_1.data
@@ -0,0 +1,7 @@
+{
+ "dummy_str": "Dummy String",
+ "dummy_int": 118,
+ "dummy_datetime": "2011-05-27T19:42:57Z",
+ "dummy_date": "2011-05-27",
+ "dummy_time": "19:42:57"
+}
diff --git a/src/lib/config/tests/testdata/data33_2.data b/src/lib/config/tests/testdata/data33_2.data
new file mode 100644
index 0000000..eb0615c
--- /dev/null
+++ b/src/lib/config/tests/testdata/data33_2.data
@@ -0,0 +1,7 @@
+{
+ "dummy_str": "Dummy String",
+ "dummy_int": 118,
+ "dummy_datetime": "xxxx",
+ "dummy_date": "xxxx",
+ "dummy_time": "xxxx"
+}
diff --git a/src/lib/config/tests/testdata/data41_1.data b/src/lib/config/tests/testdata/data41_1.data
new file mode 100644
index 0000000..d309854
--- /dev/null
+++ b/src/lib/config/tests/testdata/data41_1.data
@@ -0,0 +1,12 @@
+{
+ "zones": {
+ "example.org": {
+ "queries.tcp": 100,
+ "queries.udp": 200
+ },
+ "example.net": {
+ "queries.tcp": 300,
+ "queries.udp": 400
+ }
+ }
+}
diff --git a/src/lib/config/tests/testdata/data41_2.data b/src/lib/config/tests/testdata/data41_2.data
new file mode 100644
index 0000000..c64e931
--- /dev/null
+++ b/src/lib/config/tests/testdata/data41_2.data
@@ -0,0 +1,16 @@
+{
+ "zones": [
+ {
+ "example.org": {
+ "queries.tcp": 100,
+ "queries.udp": 200
+ }
+ },
+ {
+ "example.net": {
+ "queries.tcp": 300,
+ "queries.udp": 400
+ }
+ }
+ ]
+}
diff --git a/src/lib/config/tests/testdata/spec1.spec b/src/lib/config/tests/testdata/spec1.spec
new file mode 100644
index 0000000..05a3794
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec1.spec
@@ -0,0 +1,6 @@
+{
+ "module_spec": {
+ "module_name": "Spec1"
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec10.spec b/src/lib/config/tests/testdata/spec10.spec
new file mode 100644
index 0000000..d2bac77
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec10.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec11.spec b/src/lib/config/tests/testdata/spec11.spec
new file mode 100644
index 0000000..61cb2c3
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec11.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec12.spec b/src/lib/config/tests/testdata/spec12.spec
new file mode 100644
index 0000000..9083a66
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec12.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec13.spec b/src/lib/config/tests/testdata/spec13.spec
new file mode 100644
index 0000000..c13be46
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec13.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec14.spec b/src/lib/config/tests/testdata/spec14.spec
new file mode 100644
index 0000000..3ce1852
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec14.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec15.spec b/src/lib/config/tests/testdata/spec15.spec
new file mode 100644
index 0000000..a9ef085
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec15.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "badname",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec16.spec b/src/lib/config/tests/testdata/spec16.spec
new file mode 100644
index 0000000..e78bc2e
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec16.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": 1
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec17.spec b/src/lib/config/tests/testdata/spec17.spec
new file mode 100644
index 0000000..74a1c25
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec17.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec18.spec b/src/lib/config/tests/testdata/spec18.spec
new file mode 100644
index 0000000..e3854aa
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec18.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec19.spec b/src/lib/config/tests/testdata/spec19.spec
new file mode 100644
index 0000000..1b3c703
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec19.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec2.spec b/src/lib/config/tests/testdata/spec2.spec
new file mode 100644
index 0000000..482c206
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec2.spec
@@ -0,0 +1,83 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ },
+ { "item_name": "item2",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 1.1
+ },
+ { "item_name": "item3",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": true
+ },
+ { "item_name": "item4",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "test"
+ },
+ { "item_name": "item5",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [ "a", "b" ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ },
+ { "item_name": "item6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "value1",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "default"
+ },
+ { "item_name": "value2",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ },
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down Kea",
+ "command_args": []
+ }
+ ],
+ "statistics": [
+ {
+ "item_name": "dummy_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "Dummy Time",
+ "item_description": "A dummy date time",
+ "item_format": "date-time"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec20.spec b/src/lib/config/tests/testdata/spec20.spec
new file mode 100644
index 0000000..c9d32a7
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec20.spec
@@ -0,0 +1,18 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "somethingbad",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec21.spec b/src/lib/config/tests/testdata/spec21.spec
new file mode 100644
index 0000000..0af4302
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec21.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": 1
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec22.spec b/src/lib/config/tests/testdata/spec22.spec
new file mode 100644
index 0000000..687db08
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec22.spec
@@ -0,0 +1,114 @@
+{
+ "module_spec": {
+ "module_name": "Spec22",
+ "config_data": [
+ { "item_name": "value1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 9
+ },
+ { "item_name": "value2",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 9.9
+ },
+ { "item_name": "value3",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ },
+ { "item_name": "value4",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "default_string"
+ },
+ { "item_name": "value5",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [ "a", "b" ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 8
+ }
+ },
+ { "item_name": "value6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v61",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v62",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ ]
+ },
+ { "item_name": "value7",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "any",
+ "item_optional": true
+ }
+ },
+ { "item_name": "value8",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": { "a": "b" },
+ "map_item_spec": [
+ { "item_name": "a",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "empty"
+ }
+ ]
+ }
+ },
+ { "item_name": "value9",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": { "v91": "def", "v92": {} },
+ "map_item_spec": [
+ { "item_name": "v91",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v92",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v92a",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Hello"
+ } ,
+ {
+ "item_name": "v92b",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 56176
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec23.spec b/src/lib/config/tests/testdata/spec23.spec
new file mode 100644
index 0000000..6791b6c
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec23.spec
@@ -0,0 +1,18 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "commands": [
+ {
+ "command_name": "print_message",
+ "command_description": "Print the given message to stdout",
+ "command_args": [ {
+ "item_name": "message",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec24.spec b/src/lib/config/tests/testdata/spec24.spec
new file mode 100644
index 0000000..bcb50ba
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec24.spec
@@ -0,0 +1,18 @@
+{
+ "module_spec": {
+ "module_name": "Spec24",
+ "config_data": [
+ { "item_name": "item",
+ "item_type": "list",
+ "item_optional": true,
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec25.spec b/src/lib/config/tests/testdata/spec25.spec
new file mode 100644
index 0000000..6a174d5
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec25.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec25",
+ "module_description": "Just an empty module"
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec26.spec b/src/lib/config/tests/testdata/spec26.spec
new file mode 100644
index 0000000..27f3c5b
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec26.spec
@@ -0,0 +1,6 @@
+{
+ "module_spec": {
+ "module_name": "Spec26",
+ "module_description": 1
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec27.spec b/src/lib/config/tests/testdata/spec27.spec
new file mode 100644
index 0000000..2c73021
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec27.spec
@@ -0,0 +1,121 @@
+{
+ "module_spec": {
+ "module_name": "Spec27",
+ "commands": [
+ {
+ "command_name": "cmd1",
+ "command_description": "command_for_unittest",
+ "command_args": [
+ {
+ "item_name": "value1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 9
+ },
+ { "item_name": "value2",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 9.9
+ },
+ { "item_name": "value3",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ },
+ { "item_name": "value4",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "default_string"
+ },
+ { "item_name": "value5",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [ "a", "b" ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 8
+ }
+ },
+ { "item_name": "value6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v61",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v62",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ ]
+ },
+ { "item_name": "value7",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "any",
+ "item_optional": true
+ }
+ },
+ { "item_name": "value8",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [ ],
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": { "a": "b" },
+ "map_item_spec": [
+ { "item_name": "a",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "empty"
+ }
+ ]
+ }
+ },
+ { "item_name": "value9",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v91",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "def"
+ },
+ { "item_name": "v92",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "v92a",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Hello"
+ } ,
+ {
+ "item_name": "v92b",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 56176
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec28.spec b/src/lib/config/tests/testdata/spec28.spec
new file mode 100644
index 0000000..cc83acb
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec28.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec28",
+ 'commands': [ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec29.spec b/src/lib/config/tests/testdata/spec29.spec
new file mode 100644
index 0000000..c7ae5f8
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec29.spec
@@ -0,0 +1,35 @@
+{
+ "module_spec": {
+ "module_name": "Spec29",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "good_command",
+ "command_description": "A good command",
+ "command_args": []
+ },
+ {
+ "command_name": "bad_command",
+ "command_description": "A bad command",
+ "command_args": []
+ },
+ {
+ "command_name": "command_with_arg",
+ "command_description": "A command with an (integer) argument",
+ "command_args": [ {
+ "item_name": "number",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 1
+ } ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec3.spec b/src/lib/config/tests/testdata/spec3.spec
new file mode 100644
index 0000000..b59d949
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec3.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec3",
+ "config_data": [
+ {
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec30.spec b/src/lib/config/tests/testdata/spec30.spec
new file mode 100644
index 0000000..a9e00ad
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec30.spec
@@ -0,0 +1,45 @@
+{
+ "module_spec": {
+ "module_name": "lists",
+ "module_description": "Logging options",
+ "config_data": [
+ {
+ "item_name": "first_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "first_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "foo",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "foo"
+ },
+ { "item_name": "second_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "second_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "final_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "hello"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec31.spec b/src/lib/config/tests/testdata/spec31.spec
new file mode 100644
index 0000000..9eebfd1
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec31.spec
@@ -0,0 +1,63 @@
+{
+ "module_spec": {
+ "module_name": "lists",
+ "module_description": "Logging options",
+ "config_data": [
+ {
+ "item_name": "first_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "first_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "foo",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "foo"
+ },
+ { "item_name": "second_list_items",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "second_list_item",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "map_element",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "list1",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list2",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "number",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1
+ }
+ }
+ }]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec32.spec b/src/lib/config/tests/testdata/spec32.spec
new file mode 100644
index 0000000..2baf1c1
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec32.spec
@@ -0,0 +1,72 @@
+{
+ "module_spec": {
+ "module_name": "Spec32",
+ "config_data": [
+ { "item_name": "named_set_item",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": { "a": 1, "b": 2 },
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 3
+ }
+ },
+ { "item_name": "named_set_item2",
+ "item_type": "named_set",
+ "item_optional": true,
+ "item_default": { },
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ { "item_name": "first",
+ "item_type": "integer",
+ "item_optional": true
+ },
+ { "item_name": "second",
+ "item_type": "string",
+ "item_optional": true
+ }
+ ]
+ }
+ },
+ { "item_name": "named_set_item3",
+ "item_type": "named_set",
+ "item_optional": true,
+ "item_default": { "values": [ 1, 2, 3 ] },
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ { "item_name": "list_value",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ }
+ },
+ { "item_name": "named_set_item4",
+ "item_type": "named_set",
+ "item_optional": true,
+ "item_default": {},
+ "named_set_item_spec": {
+ "item_name": "named_set_element",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": { "a": 1, "b": 2 },
+ "named_set_item_spec":
+ { "item_name": "named_set_element",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ }
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec33.spec b/src/lib/config/tests/testdata/spec33.spec
new file mode 100644
index 0000000..3002488
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec33.spec
@@ -0,0 +1,50 @@
+{
+ "module_spec": {
+ "module_name": "Spec33",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_title": "Dummy String",
+ "item_description": "A dummy string"
+ },
+ {
+ "item_name": "dummy_int",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Dummy Integer",
+ "item_description": "A dummy integer"
+ },
+ {
+ "item_name": "dummy_datetime",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01T00:00:00Z",
+ "item_title": "Dummy DateTime",
+ "item_description": "A dummy datetime",
+ "item_format": "date-time"
+ },
+ {
+ "item_name": "dummy_date",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "1970-01-01",
+ "item_title": "Dummy Date",
+ "item_description": "A dummy date",
+ "item_format": "date"
+ },
+ {
+ "item_name": "dummy_time",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "00:00:00",
+ "item_title": "Dummy Time",
+ "item_description": "A dummy time",
+ "item_format": "time"
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec34.spec b/src/lib/config/tests/testdata/spec34.spec
new file mode 100644
index 0000000..dd1f3ca
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec34.spec
@@ -0,0 +1,14 @@
+{
+ "module_spec": {
+ "module_name": "Spec34",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_description": "A dummy string"
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec35.spec b/src/lib/config/tests/testdata/spec35.spec
new file mode 100644
index 0000000..86aaf14
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec35.spec
@@ -0,0 +1,15 @@
+{
+ "module_spec": {
+ "module_name": "Spec35",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_title": "Dummy String"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec36.spec b/src/lib/config/tests/testdata/spec36.spec
new file mode 100644
index 0000000..fb9ce26
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec36.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec36",
+ "statistics": [
+ {
+ "item_name": "dummy_str",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "Dummy",
+ "item_title": "Dummy String",
+ "item_description": "A dummy string",
+ "item_format": "dummy"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec37.spec b/src/lib/config/tests/testdata/spec37.spec
new file mode 100644
index 0000000..bc444d1
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec37.spec
@@ -0,0 +1,7 @@
+{
+ "module_spec": {
+ "module_name": "Spec37",
+ "statistics": 8
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec38.spec b/src/lib/config/tests/testdata/spec38.spec
new file mode 100644
index 0000000..1892e88
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec38.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec38",
+ "statistics": [
+ {
+ "item_name": "dummy_datetime",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "11",
+ "item_title": "Dummy DateTime",
+ "item_description": "A dummy datetime",
+ "item_format": "date-time"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec39.spec b/src/lib/config/tests/testdata/spec39.spec
new file mode 100644
index 0000000..1f72319
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec39.spec
@@ -0,0 +1,21 @@
+{
+ "module_spec": {
+ "module_name": "Spec39",
+ "config_data": [
+ { "item_name": "list",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "list_item",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": false
+ }
+ }
+ ],
+ "commands": [],
+ "statistics": []
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec4.spec b/src/lib/config/tests/testdata/spec4.spec
new file mode 100644
index 0000000..80d9b22
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec4.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_optional": false,
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec40.spec b/src/lib/config/tests/testdata/spec40.spec
new file mode 100644
index 0000000..6fbec10
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec40.spec
@@ -0,0 +1,21 @@
+{
+ "module_spec": {
+ "module_name": "Spec40",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "any",
+ "item_optional": false,
+ "item_default": "asdf"
+ },
+ { "item_name": "item2",
+ "item_type": "any",
+ "item_optional": true
+ },
+ { "item_name": "item3",
+ "item_type": "any",
+ "item_optional": true,
+ "item_default": null
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec41.spec b/src/lib/config/tests/testdata/spec41.spec
new file mode 100644
index 0000000..1c57cfe
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec41.spec
@@ -0,0 +1,35 @@
+{
+ "module_spec": {
+ "module_name": "Spec40",
+ "statistics": [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": { },
+ "item_title": "Dummy name set",
+ "item_description": "A dummy name set",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": { },
+ "map_item_spec": [
+ {
+ "item_name": "queries.tcp",
+ "item_optional": false,
+ "item_type": "integer",
+ "item_default": 0
+ },
+ {
+ "item_name": "queries.udp",
+ "item_optional": false,
+ "item_type": "integer",
+ "item_default": 0
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec42.spec b/src/lib/config/tests/testdata/spec42.spec
new file mode 100644
index 0000000..d822465
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec42.spec
@@ -0,0 +1,17 @@
+{
+ "module_spec": {
+ "module_name": "Spec42",
+ "config_data": [
+ { "item_name": "list_item",
+ "item_type": "list",
+ "item_optional": true,
+ "list_item_spec": {
+ "item_name": "list_element",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/config/tests/testdata/spec5.spec b/src/lib/config/tests/testdata/spec5.spec
new file mode 100644
index 0000000..515424a
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec5.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_default": 1
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec6.spec b/src/lib/config/tests/testdata/spec6.spec
new file mode 100644
index 0000000..631d882
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec6.spec
@@ -0,0 +1,12 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec7.spec b/src/lib/config/tests/testdata/spec7.spec
new file mode 100644
index 0000000..42f8b7a
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec7.spec
@@ -0,0 +1,5 @@
+{
+ "module_spec": {
+ }
+}
+
diff --git a/src/lib/config/tests/testdata/spec8.spec b/src/lib/config/tests/testdata/spec8.spec
new file mode 100644
index 0000000..bfd870e
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec8.spec
@@ -0,0 +1,3 @@
+{
+}
+
diff --git a/src/lib/config/tests/testdata/spec9.spec b/src/lib/config/tests/testdata/spec9.spec
new file mode 100644
index 0000000..21018b8
--- /dev/null
+++ b/src/lib/config/tests/testdata/spec9.spec
@@ -0,0 +1,13 @@
+{
+ "module_spec": {
+ "module_name": "Spec2",
+ "config_data": [
+ { "item_name": "item1",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": "asdf"
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/config/timeouts.h b/src/lib/config/timeouts.h
new file mode 100644
index 0000000..c7f889e
--- /dev/null
+++ b/src/lib/config/timeouts.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2018-2019,2021 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 CONFIG_TIMEOUTS_H
+#define CONFIG_TIMEOUTS_H
+
+namespace isc {
+namespace config {
+
+// All timeouts provided below are in milliseconds.
+
+/// @brief Timeout for the DHCP server to receive command over the
+/// unix domain socket.
+constexpr long TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND = 10000;
+
+/// @brief Timeout for the Control Agent to receive command over the
+/// RESTful interface.
+constexpr long TIMEOUT_AGENT_RECEIVE_COMMAND = 10000;
+
+/// @brief Timeout for the idle connection to be closed.
+constexpr long TIMEOUT_AGENT_IDLE_CONNECTION_TIMEOUT = 30000;
+
+/// @brief Timeout for the Control Agent to forward command to a
+/// Kea server, e.g. DHCP server.
+///
+/// This value is high to ensure that the server have enough time
+/// to generate large responses, e.g. dump whole lease database.
+constexpr long TIMEOUT_AGENT_FORWARD_COMMAND = 60000;
+
+/// @brief Timeout for the HTTP clients awaiting a response to a request.
+///
+/// This value is high to ensure that the client waits long enough
+/// for the fulfilling server to generate a response. Specified
+/// milliseconds.
+constexpr long TIMEOUT_DEFAULT_HTTP_CLIENT_REQUEST = 10000;
+
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif // CONFIG_TIMEOUTS_H
diff --git a/src/lib/config_backend/Makefile.am b/src/lib/config_backend/Makefile.am
new file mode 100644
index 0000000..2b51675
--- /dev/null
+++ b/src/lib/config_backend/Makefile.am
@@ -0,0 +1,25 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST = base_config_backend.h
+EXTRA_DIST += base_config_backend_mgr.h
+EXTRA_DIST += base_config_backend_pool.h
+EXTRA_DIST += constants.h
+
+# The message file should be in the distribution.
+#EXTRA_DIST += config_backend.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cb_includedir = $(pkgincludedir)/config_backend
+libkea_cb_include_HEADERS = \
+ base_config_backend.h \
+ base_config_backend_mgr.h \
+ base_config_backend_pool.h \
+ constants.h
+
diff --git a/src/lib/config_backend/Makefile.in b/src/lib/config_backend/Makefile.in
new file mode 100644
index 0000000..19670b8
--- /dev/null
+++ b/src/lib/config_backend/Makefile.in
@@ -0,0 +1,804 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/config_backend
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_cb_include_HEADERS) \
+ $(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 =
+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
+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)$(libkea_cb_includedir)"
+HEADERS = $(libkea_cb_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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+EXTRA_DIST = base_config_backend.h base_config_backend_mgr.h \
+ base_config_backend_pool.h constants.h
+
+# The message file should be in the distribution.
+#EXTRA_DIST += config_backend.dox
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cb_includedir = $(pkgincludedir)/config_backend
+libkea_cb_include_HEADERS = \
+ base_config_backend.h \
+ base_config_backend_mgr.h \
+ base_config_backend_pool.h \
+ constants.h
+
+all: all-recursive
+
+.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/config_backend/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config_backend/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
+install-libkea_cb_includeHEADERS: $(libkea_cb_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_cb_include_HEADERS)'; test -n "$(libkea_cb_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_cb_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_cb_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)$(libkea_cb_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_cb_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_cb_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_cb_include_HEADERS)'; test -n "$(libkea_cb_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_cb_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: check-recursive
+all-am: Makefile $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libkea_cb_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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 mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am: install-libkea_cb_includeHEADERS
+
+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 Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-libkea_cb_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean 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-libkea_cb_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-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-libkea_cb_includeHEADERS
+
+.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/config_backend/base_config_backend.h b/src/lib/config_backend/base_config_backend.h
new file mode 100644
index 0000000..9e38919
--- /dev/null
+++ b/src/lib/config_backend/base_config_backend.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2018-2022 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 BASE_CONFIG_BACKEND_H
+#define BASE_CONFIG_BACKEND_H
+
+#include <database/database_connection.h>
+
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+#include <set>
+#include <string>
+
+namespace isc {
+namespace cb {
+
+/// @brief Interface for Kea server specific configuration backend
+/// implementations.
+///
+/// Each Kea server (e.g. DHCPv4 server) needs to implement its own
+/// interface to store and fetch its configuration from the databases.
+/// This is because each Kea server uses a different set of
+/// configuration information. This is a base interface which should
+/// be implemented (and extended) by respective Kea servers to provide
+/// API to store and fetch configuration information from a database.
+/// Such implementation is called configuration backend. Each
+/// configuration backend facilitates a single database type, e.g. MySQL
+/// database. In order to support multiple database types, i.e. MySQL,
+/// PostgreSQL, each Kea server will have to implement 2 separate configuration
+/// backends, one for each database type.
+class BaseConfigBackend {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~BaseConfigBackend() { }
+
+ /// @brief Returns backend type in the textual format.
+ ///
+ /// @return Name of the storage for configurations, e.g. "mysql",
+ /// "postgresql" and so forth.
+ virtual std::string getType() const = 0;
+
+ /// @brief Returns backend host.
+ ///
+ /// This is used by the @c BaseConfigBackendPool to select backend
+ /// when @c BackendSelector is specified.
+ ///
+ /// @return host on which the database is located.
+ virtual std::string getHost() const = 0;
+
+ /// @brief Returns backend port number.
+ ///
+ /// This is used by the @c BaseConfigBackendPool to select backend
+ /// when @c BackendSelector is specified.
+ ///
+ /// @return Port number on which database service is available.
+ virtual uint16_t getPort() const = 0;
+
+ /// @brief Flag which indicates if the config backend has an unusable
+ /// connection.
+ ///
+ /// @return true if there is at least one unusable connection, false
+ /// otherwise.
+ virtual bool isUnusable() {
+ return (false);
+ }
+
+ /// @brief Return backend parameters.
+ ///
+ /// Returns the backend parameters.
+ ///
+ /// @return Parameters of the backend.
+ virtual isc::db::DatabaseConnection::ParameterMap getParameters() const {
+ return (isc::db::DatabaseConnection::ParameterMap());
+ }
+};
+
+/// @brief Shared pointer to the @c BaseConfigBackend.
+typedef boost::shared_ptr<BaseConfigBackend> BaseConfigBackendPtr;
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // BASE_CONFIG_BACKEND_H
diff --git a/src/lib/config_backend/base_config_backend_mgr.h b/src/lib/config_backend/base_config_backend_mgr.h
new file mode 100644
index 0000000..92d6858
--- /dev/null
+++ b/src/lib/config_backend/base_config_backend_mgr.h
@@ -0,0 +1,220 @@
+// Copyright (C) 2018-2021 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 BASE_CONFIG_BACKEND_MGR_H
+#define BASE_CONFIG_BACKEND_MGR_H
+
+#include <config_backend/base_config_backend.h>
+#include <database/database_connection.h>
+#include <database/backend_selector.h>
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace cb {
+
+/// @brief Base class for Configuration Backend Managers (CBM).
+///
+/// Each Kea server supporting Configuration Backend feature implements
+/// a "manager" class which holds information about supported and
+/// configured backends and provides access to the backends. This is
+/// similar to @c HostMgr and @c LeaseMgr singletons being used by the
+/// DHCP servers.
+///
+/// The Config Backend Managers are typically implemented as singletons
+/// which can be accessed from any place within the server code. This
+/// includes server configuration, data fetching during normal server
+/// operation and data management, including processing of control
+/// commands implemented within hooks libraries.
+///
+/// The @c BaseConfigBackendMgr is a base class for all CBMs implemented
+/// for respective Kea servers. It includes mechanisms to register config
+/// backend factory functions and to create instances of the backends using
+/// those factory functions as a result of server configuration. The mechanism
+/// of factory functions registration is useful in cases when the config
+/// backend is implemented within the hook library. Such hook library
+/// registers factory function in its @c load function and the server
+/// simply calls this function to create the instance of this backend when
+/// instructed to do so via configuration. Similar mechanism exists in
+/// DHCP @c HostMgr.
+///
+/// Unlike @c HostMgr, the CBMs do not directly expose API to fetch and
+/// manipulate the data in the database. This is done via, so called,
+/// Configuration Backends Pools. See @c BaseConfigBackendPool for
+/// details. The @c BaseConfigBackendMgr is provided with the pool type
+/// via class template parameter. Respective CBM implementations
+/// use their own pools, which provide APIs appropriate for those
+/// implementations.
+///
+/// @tparam ConfgBackendPoolType Type of the configuration backend pool
+/// to be used by the manager. It must derive from @c BaseConfigBackendPool
+/// template class.
+template<typename ConfigBackendPoolType>
+class BaseConfigBackendMgr {
+public:
+
+ /// @brief Pointer to the configuration backend pool.
+ typedef boost::shared_ptr<ConfigBackendPoolType> ConfigBackendPoolPtr;
+
+ /// @brief Type of the backend factory function.
+ ///
+ /// Factory function returns a pointer to the instance of the configuration
+ /// backend created.
+ typedef std::function<typename ConfigBackendPoolType::ConfigBackendTypePtr
+ (const db::DatabaseConnection::ParameterMap&)> Factory;
+
+ /// @brief Constructor.
+ BaseConfigBackendMgr()
+ : factories_(), pool_(new ConfigBackendPoolType()) {
+ }
+
+ /// @brief Registers new backend factory function for a given backend type.
+ ///
+ /// The typical usage of this function is to make the CBM aware of a
+ /// configuration backend implementation. This implementation may exist
+ /// in a hooks library. In such case, this function should be called from
+ /// the @c load function in this library. When the backend is registered,
+ /// the server will use it when required by the configuration, i.e. a
+ /// user includes configuration backend of that type in the
+ /// "config-databases" list.
+ ///
+ /// If the backend of the given type has already been registered, perhaps
+ /// by another hooks library, the CBM will refuse to register another
+ /// backend of the same type.
+ ///
+ /// @param db_type Backend type, e.g. "mysql".
+ /// @param factory Pointer to the backend factory function.
+ ///
+ /// @return true if the backend has been successfully registered, false
+ /// if another backend of this type already exists.
+ bool registerBackendFactory(const std::string& db_type,
+ const Factory& factory) {
+ // Check if this backend has been already registered.
+ if (factories_.count(db_type)) {
+ return (false);
+ }
+
+ // Register the new backend.
+ factories_.insert(std::make_pair(db_type, factory));
+ return (true);
+ }
+
+ /// @brief Unregisters the backend factory function for a given backend type.
+ ///
+ /// This function is used to remove the factory function and all backend instances
+ /// for a given backend type. Typically, it would be called when unloading the
+ /// a config backend hook library, and thus called by the library's @c unload
+ /// function.
+ ///
+ /// @param db_type Backend type, e.g. "mysql".
+ ///
+ /// @return false if no factory for the given type was unregistered, true
+ /// if the factory was removed.
+ bool unregisterBackendFactory(const std::string& db_type) {
+ // Look for it.
+ auto index = factories_.find(db_type);
+
+ // If it's there remove it
+ if (index != factories_.end()) {
+ factories_.erase(index);
+ pool_->delAllBackends(db_type);
+ return (true);
+
+ }
+
+ return (false);
+ }
+
+ /// @brief Create an instance of a configuration backend.
+ ///
+ /// This method uses provided @c dbaccess string representing database
+ /// connection information to create an instance of the database
+ /// backend. If the specified backend type is not supported, i.e. there
+ /// is no relevant factory function registered, an exception is thrown.
+ ///
+ /// @param dbaccess Database access string being a collection of
+ /// key=value pairs.
+ ///
+ /// @throw InvalidParameter if access string lacks database type value.
+ /// @throw db::InvalidType if the type of the database backend is not
+ /// supported.
+ /// @throw Unexpected if the backend factory function returned NULL.
+ void addBackend(const std::string& dbaccess) {
+ // Parse the access string into a map of parameters.
+ db::DatabaseConnection::ParameterMap parameters =
+ db::DatabaseConnection::parse(dbaccess);
+
+ // Get the database type to locate a factory function.
+ db::DatabaseConnection::ParameterMap::iterator it = parameters.find("type");
+ if (it == parameters.end()) {
+ isc_throw(InvalidParameter, "Config backend specification lacks the "
+ "'type' keyword");
+ }
+
+ std::string db_type = it->second;
+ auto index = factories_.find(db_type);
+
+ // No match?
+ if (index == factories_.end()) {
+ isc_throw(db::InvalidType, "The type of the configuration backend: '" <<
+ db_type << "' is not supported");
+ }
+
+ // Call the factory and push the pointer on sources.
+ auto backend = index->second(parameters);
+ if (!backend) {
+ isc_throw(Unexpected, "Config database " << db_type <<
+ " factory returned NULL");
+ }
+
+ // Backend instance created successfully.
+ pool_->addBackend(backend);
+ }
+
+ /// @brief Removes all backends from the pool.
+ void delAllBackends() {
+ pool_->delAllBackends();
+ }
+
+ /// @brief Delete a config backend manager.
+ ///
+ /// Delete the first instance of a config database manager which matches
+ /// specific parameters.
+ /// This should have the effect of closing the database connection.
+ ///
+ /// @param db_type Backend to remove.
+ /// @param dbaccess Database access string being a collection of
+ /// key=value pairs.
+ /// @param if_unusable Flag which indicates if the config backend should be
+ /// deleted only if it is unusable.
+ /// @return false when not removed because it is not found or because it is
+ /// still usable (if_unusable is true), true otherwise.
+ bool delBackend(const std::string& db_type, const std::string& dbaccess,
+ bool if_unusable) {
+ return (pool_->del(db_type, dbaccess, if_unusable));
+ }
+
+ /// @brief Returns underlying config backend pool.
+ ConfigBackendPoolPtr getPool() const {
+ return (pool_);
+ }
+
+protected:
+
+ /// @brief A map holding registered backend factory functions.
+ std::map<std::string, Factory> factories_;
+
+ /// @brief Pointer to the configuration backends pool.
+ ConfigBackendPoolPtr pool_;
+};
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // BASE_CONFIG_BACKEND_MGR_H
diff --git a/src/lib/config_backend/base_config_backend_pool.h b/src/lib/config_backend/base_config_backend_pool.h
new file mode 100644
index 0000000..1c83677
--- /dev/null
+++ b/src/lib/config_backend/base_config_backend_pool.h
@@ -0,0 +1,649 @@
+// Copyright (C) 2018-2021 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 BASE_CONFIG_BACKEND_POOL_H
+#define BASE_CONFIG_BACKEND_POOL_H
+
+#include <cc/data.h>
+#include <config_backend/base_config_backend.h>
+#include <database/backend_selector.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/server_selector.h>
+#include <functional>
+#include <list>
+#include <string>
+
+namespace isc {
+namespace cb {
+
+
+/// @brief Base class for configuration backend pools.
+///
+/// Each Kea server supporting databases as a configuration repository can
+/// use multiple database instances simultaneously. A pool is a collection
+/// of database backends used by a particular server. Different Kea servers
+/// use different pools because they store and fetch different configuration
+/// information. For example: DHCPv4 server stores and fetches IPv4 subnets,
+/// and DHCPv6 server stores and fetches IPv6 subnets. Therefore, each pool
+/// type will expose a different API calls.
+///
+/// This template class is a base class for all pools used by various servers.
+/// It implements mechanisms for managing multiple backends and for forwarding
+/// API calls to one or many database backends depending on the selections
+/// made via @c BackendSelector class.
+///
+/// @tparam ConfigBackendType Type of the configuration backend. This must
+/// be a class deriving from @c BaseConfigBackend class. It is a class
+/// dedicated to a particular server type, e.g. DHCPv4 server, and from
+/// which database specific backends derive.
+template<typename ConfigBackendType>
+class BaseConfigBackendPool {
+public:
+
+ /// @brief Shared pointer to the Configuration Backend used.
+ typedef boost::shared_ptr<ConfigBackendType> ConfigBackendTypePtr;
+
+ /// @brief Virtual destructor.
+ virtual ~BaseConfigBackendPool() { }
+
+ /// @brief Adds a backend to the pool.
+ ///
+ /// @param backend Pointer to a backend to be added.
+ void addBackend(ConfigBackendTypePtr backend) {
+ backends_.push_back(backend);
+ }
+
+ /// @brief Deletes all backends from the pool.
+ void delAllBackends() {
+ backends_.clear();
+ }
+
+ /// @brief Deletes all backends of the given type from the pool.
+ ///
+ /// @param db_type Backend to remove.
+ void delAllBackends(const std::string& db_type) {
+ typename std::list<ConfigBackendTypePtr>::iterator backend = backends_.begin();
+
+ while (backend != backends_.end()) {
+ if ((*backend)->getType() == db_type) {
+ backend = backends_.erase(backend);
+ } else {
+ ++backend;
+ }
+ }
+ }
+
+ /// @brief Deletes all backends of the given type from the pool.
+ ///
+ /// @param db_type Backend to remove.
+ /// @param dbaccess Database access string being a collection of
+ /// key=value pairs.
+ /// @param if_unusable Flag which indicates if the config backend should be
+ /// deleted only if it is unusable.
+ /// @return false when not removed because it is not found or because it is
+ /// still usable (if_unusable is true), true otherwise.
+ bool del(const std::string& db_type, const std::string& dbaccess,
+ bool if_unusable) {
+ isc::db::DatabaseConnection::ParameterMap parameters =
+ isc::db::DatabaseConnection::parse(dbaccess);
+ bool deleted = false;
+ if (if_unusable) {
+ deleted = true;
+ }
+
+ typename std::list<ConfigBackendTypePtr>::iterator backend = backends_.begin();
+
+ while (backend != backends_.end()) {
+ if ((*backend)->getType() != db_type || (*backend)->getParameters() != parameters) {
+ ++backend;
+ } else if (if_unusable && (!(*backend)->isUnusable())) {
+ deleted = false;
+ ++backend;
+ } else {
+ backends_.erase(backend);
+ return (true);
+ }
+ }
+ return (deleted);
+ }
+
+protected:
+
+ /// @brief Retrieve a single configuration property from the pool.
+ ///
+ /// This is common method for retrieving a single configuration property
+ /// from the databases. The server specific backends call this method to
+ /// retrieve a single object. For example, the DHCPv4 configuration backend
+ /// pool may use this function to implement a @c getSubnet4 method:
+ ///
+ /// @code
+ /// Subnet4Ptr getSubnet4(const SubnetID& subnet_id,
+ /// const BackendSelector& backend_selector,
+ /// const ServerSelector& server_selector) const {
+ /// Subnet4Ptr subnet;
+ /// getPropertyPtrConst<Subnet4Ptr, const SubnetID&>
+ /// (&ConfigBackendDHCPv4::getSubnet4, backend_selector,
+ /// server_selector, subnet, subnet_id);
+ /// return (subnet);
+ /// }
+ /// @endcode
+ ///
+ /// where @c ConfigBackendDHCPv4::getSubnet4 has the following signature:
+ ///
+ /// @code
+ /// Subnet4Ptr getSubnet4(const ServerSelector&, const SubnetID&) const;
+ /// @endcode
+ ///
+ /// If the backend selector is set to "unspecified", this method will iterate
+ /// over the existing backends and call the @c MethodPointer method on each
+ /// backend. It will return the first non-null (or non-zero) value returned
+ /// by this call. For example: if the first backend returns non-null value,
+ /// this value is returned via @c property argument and the calls for the
+ /// rest of the backends are skipped.
+ ///
+ /// @tparam PropertyType Type of the object returned by the backend call.
+ /// @tparam FnPtrArgs Parameter pack holding argument types of the backend
+ /// method to be invoked.
+ /// @tparam Args Parameter pack holding types of the arguments provided
+ /// in the call to this method.
+ ///
+ /// @param MethodPointer Pointer to the backend method to be called.
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param [out] property Reference to the shared pointer where retrieved
+ /// property should be assigned.
+ /// @param input Values to be used as input to the backend call.
+ ///
+ /// @throw db::NoSuchDatabase if no database matching the given selector
+ /// was found.
+ template<typename PropertyType, typename... FnPtrArgs, typename... Args>
+ void getPropertyPtrConst(PropertyType (ConfigBackendType::*MethodPointer)
+ (const db::ServerSelector&, FnPtrArgs...) const,
+ const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ PropertyType& property,
+ Args... input) const {
+
+ // If no particular backend is selected, call each backend and return
+ // the first non-null (non zero) value.
+ if (backend_selector.amUnspecified()) {
+ for (auto backend : backends_) {
+ property = ((*backend).*MethodPointer)(server_selector, input...);
+ if (property) {
+ break;
+ }
+ }
+
+ } else {
+ // Backend selected, find the one that matches selection.
+ auto backends = selectBackends(backend_selector);
+ if (!backends.empty()) {
+ for (auto backend : backends) {
+ property = ((*backend).*MethodPointer)(server_selector, input...);
+ if (property) {
+ break;
+ }
+ }
+
+ } else {
+ isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+ << backend_selector.toText());
+ }
+ }
+ }
+
+ /// @brief Retrieve a single configuration property from the backend.
+ ///
+ /// This is common method for retrieving a single configuration property
+ /// from the selected database without specifying the server selector. The
+ /// server specific backends call this method to retrieve a single object.
+ /// For example, the DHCPv4 configuration backend pool may use this function
+ /// to implement a @c getServer4 method:
+ ///
+ /// @code
+ /// ServerPtr getServer4(const BackendSelector& backend_selector,
+ /// const std::string& server_tag) const {
+ /// ServerPtr server;
+ /// getPropertyPtrConst<ServerPtr, const std::string&>
+ /// (&ConfigBackendDHCPv4::getServer4, backend_selector,
+ /// server_tag);
+ /// return (server);
+ /// }
+ /// @endcode
+ ///
+ /// where @c ConfigBackendDHCPv4::getServer4 has the following signature:
+ ///
+ /// @code
+ /// ServerPtr getServer4(const std::string&) const;
+ /// @endcode
+ ///
+ /// This method is used in cases when server selector is not applicable.
+ ///
+ /// @tparam PropertyType Type of the object returned by the backend call.
+ /// @tparam FnPtrArgs Parameter pack holding argument types of the backend
+ /// method to be invoked.
+ /// @tparam Args Parameter pack holding types of the arguments provided
+ /// in the call to this method.
+ ///
+ /// @param MethodPointer Pointer to the backend method to be called.
+ /// @param backend_selector Backend selector.
+ /// @param [out] property Reference to the shared pointer where retrieved
+ /// property should be assigned.
+ /// @param input Values to be used as input to the backend call.
+ ///
+ /// @throw db::NoSuchDatabase if no database matching the given selector
+ /// was found.
+ /// @throw db::AmbiguousDatabase if multiple databases matching the selector
+ /// were found.
+ template<typename PropertyType, typename... FnPtrArgs, typename... Args>
+ void getBackendPropertyPtrConst(PropertyType (ConfigBackendType::*MethodPointer)
+ (FnPtrArgs...) const,
+ const db::BackendSelector& backend_selector,
+ PropertyType& property,
+ Args... input) const {
+ auto backends = selectBackends(backend_selector);
+ if (backends.empty()) {
+ isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+ << backend_selector.toText());
+
+ } else if (backends.size() > 1) {
+ isc_throw(db::AmbiguousDatabase, "more than one database found for "
+ "selector: " << backend_selector.toText());
+ }
+
+ property = ((*(*(backends.begin())).*MethodPointer)(input...));
+ }
+
+ /// @brief Retrieve multiple configuration properties from the pool.
+ ///
+ /// This is a common method for retrieving multiple configuration properties
+ /// from the databases. The server specific backends call this method to
+ /// retrieve multiple objects of the same type. For example, the DHCPv6
+ /// configuration backend pool may use this function to implement a
+ /// @c getSubnets6 method:
+ ///
+ /// @code
+ /// Subnet6Collection getModifiedSubnets6(const BackendSelector& backend_selector,
+ /// const ServerSelector& server_selector,
+ /// const ptime& modification_time) const {
+ /// Subnet6Collection subnets;
+ /// getMultiplePropertiesConst<Subnet6Collection, const ptime&>
+ /// (&ConfigBackendDHCPv6::getSubnets6, backend_selector, server_selector,
+ /// subnets, modification_time);
+ /// return (subnets);
+ /// }
+ /// @endcode
+ ///
+ /// where @c ConfigBackendDHCPv6::getSubnets6 has the following signature:
+ ///
+ /// @code
+ /// Subnet6Collection getSubnets6(const ServerSelector&, const ptime&) const;
+ /// @endcode
+ ///
+ /// If the backend selector is set to "unspecified", this method will iterate
+ /// over existing backends and call the @c MethodPointer method on each
+ /// backend. It will return the first non-empty list returned by one of the
+ /// backends.
+ ///
+ /// @tparam PropertyCollectionType Type of the container into which the
+ /// properties are stored.
+ /// @tparam FnPtrArgs Parameter pack holding argument types of the backend
+ /// method to be invoked.
+ /// @tparam Args Parameter pack holding types of the arguments provided
+ /// in the call to this method.
+ ///
+ /// @param MethodPointer Pointer to the backend method to be called.
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param [out] properties Reference to the collection of retrieved properties.
+ /// @param input Values to be used as input to the backend call.
+ ///
+ /// @throw db::NoSuchDatabase if no database matching the given selector
+ /// was found.
+ template<typename PropertyCollectionType, typename... FnPtrArgs, typename... Args>
+ void getMultiplePropertiesConst(PropertyCollectionType (ConfigBackendType::*MethodPointer)
+ (const db::ServerSelector&, FnPtrArgs...) const,
+ const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ PropertyCollectionType& properties,
+ Args... input) const {
+ if (backend_selector.amUnspecified()) {
+ for (auto backend : backends_) {
+ properties = ((*backend).*MethodPointer)(server_selector, input...);
+ if (!properties.empty()) {
+ break;
+ }
+ }
+
+ } else {
+ auto backends = selectBackends(backend_selector);
+ if (!backends.empty()) {
+ for (auto backend : backends) {
+ properties = ((*backend).*MethodPointer)(server_selector, input...);
+ if (!properties.empty()) {
+ break;
+ }
+ }
+
+ } else {
+ isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+ << backend_selector.toText());
+ }
+ }
+ }
+
+ /// @brief Retrieve all configuration properties from the pool.
+ ///
+ /// This is a common method for retrieving all configuration properties
+ /// from the databases. The server specific backends call this method
+ /// to retrieve all objects of the same type. For example, the DHCPv4
+ /// configuration backend pool may use this function to implement a
+ /// @c getAllSubnets4 method:
+ ///
+ /// @code
+ /// Subnet4Collection getAllSubnets4(const BackendSelector&, const ServerSelector&) const {
+ /// Subnet4Collection subnets;
+ /// getAllPropertiesConst<Subnet6Collection>
+ /// (&ConfigBackendDHCPv4::getAllSubnets4, subnets, backend_selector,
+ /// server_selector);
+ /// return (subnets);
+ /// }
+ /// @endcode
+ ///
+ /// where @c ConfigBackendDHCPv4::getAllSubnets4 has the following signature:
+ ///
+ /// @code
+ /// Subnet4Collection getAllSubnets4(const ServerSelector&) const;
+ /// @endcode
+ ///
+ /// If the backend selector is set to "unspecified", this method will iterate
+ /// over existing backends and call the @c MethodPointer method on each
+ /// backend. It will return the first non-empty list returned by one of the
+ /// backends.
+ ///
+ /// @tparam PropertyCollectionType Type of the container into which the
+ /// properties are stored.
+ ///
+ /// @param MethodPointer Pointer to the backend method to be called.
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param [out] properties Reference to the collection of retrieved properties.
+ ///
+ /// @throw db::NoSuchDatabase if no database matching the given selector
+ /// was found.
+ template<typename PropertyCollectionType>
+ void getAllPropertiesConst(PropertyCollectionType (ConfigBackendType::*MethodPointer)
+ (const db::ServerSelector&) const,
+ const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ PropertyCollectionType& properties) const {
+ if (backend_selector.amUnspecified()) {
+ for (auto backend : backends_) {
+ properties = ((*backend).*MethodPointer)(server_selector);
+ if (!properties.empty()) {
+ break;
+ }
+ }
+
+ } else {
+ auto backends = selectBackends(backend_selector);
+ if (!backends.empty()) {
+ for (auto backend : backends) {
+ properties = ((*backend).*MethodPointer)(server_selector);
+ if (!properties.empty()) {
+ break;
+ }
+ }
+
+ } else {
+ isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+ << backend_selector.toText());
+ }
+ }
+ }
+
+ /// @brief Get all configuration properties from the backend.
+ ///
+ /// This is a common method for retrieving all configuration properties
+ /// from the selected database without specifying the server selector. The
+ /// server specific backends call this method to retrieve all objects of
+ /// the same type. For example, the DHCPv4 configuration backend pool may
+ /// use this function to implement a @c getAllServers4 method:
+ ///
+ /// @code
+ /// ServerCollection getAllServers4(const BackendSelector&) const {
+ /// ServerCollection servers;
+ /// getAllBackendPropertiesConst<ServerCollection>
+ /// (&ConfigBackendDHCPv4::getAllServers4, backend_selector, servers);
+ /// return (servers);
+ /// }
+ /// @endcode
+ ///
+ /// where @c ConfigBackendDHCPv4::getAllServers4 has the following signature:
+ ///
+ /// @code
+ /// ServerCollection getAllServers4() const;
+ /// @endcode
+ ///
+ /// This method is used in cases when server selector is not applicable.
+ ///
+ /// @tparam PropertyCollectionType Type of the container into which the
+ /// properties are stored.
+ ///
+ /// @param MethodPointer Pointer to the backend method to be called.
+ /// @param backend_selector Backend selector.
+ /// @param [out] properties Reference to the collection of retrieved properties.
+ ///
+ /// @throw db::NoSuchDatabase if no database matching the given selector
+ /// was found.
+ /// @throw db::AmbiguousDatabase if multiple databases matching the selector
+ /// were found.
+ template<typename PropertyCollectionType>
+ void getAllBackendPropertiesConst(PropertyCollectionType (ConfigBackendType::*MethodPointer)() const,
+ const db::BackendSelector& backend_selector,
+ PropertyCollectionType& properties) const {
+ auto backends = selectBackends(backend_selector);
+ if (backends.empty()) {
+ isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+ << backend_selector.toText());
+
+ } else if (backends.size() > 1) {
+ isc_throw(db::AmbiguousDatabase, "more than one database found for "
+ "selector: " << backend_selector.toText());
+ }
+
+ properties = (*(*(backends.begin())).*MethodPointer)();
+ }
+
+ /// @brief Add, update or delete property from the backend.
+ ///
+ /// This is a common method for storing a single configuration property in
+ /// a database, updating an existing property or deleting the property.
+ /// The server specific backends call this method. For example,
+ /// the DHCPv6 configuration backend pool may use this function to implement
+ /// a @c createUpdateSubnet6 method:
+ ///
+ /// @code
+ /// void createUpdateSubnet6(const Subnet6Ptr& subnet,
+ /// const BackendSelector& backend_selector,
+ /// const ServerSelector& server_selector) {
+ /// createUpdateDeleteProperty<void, const Subnet6Ptr&>
+ /// (&ConfigBackendDHCPv6::createUpdateSubnet6, backend_selector,
+ /// server_selector, subnet, selector);
+ /// }
+ /// @endcode
+ ///
+ /// where @c ConfigBackendDHCPv6::createUpdateSubnet6 has the following
+ /// signature:
+ ///
+ /// @code
+ /// void createUpdateSubnet6(const ServerSelector&, const Subnet6Ptr&);
+ /// @endcode
+ ///
+ /// The backend selector must point to exactly one backend. If more than one
+ /// backend is selected, an exception is thrown. If no backend is selected
+ /// an exception is thrown either.
+ ///
+ /// @tparam ReturnValue Returned value, typically void or uint64_t.
+ /// @tparam FnPtrArgs Parameter pack holding argument types of the backend
+ /// method to be invoked.
+ /// @tparam Args Parameter pack holding types of the arguments provided
+ /// in the call to this method.
+ ///
+ /// @param MethodPointer Pointer to the backend method to be called.
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param input Objects used as arguments to the backend method to be
+ /// called.
+ ///
+ /// @throw db::NoSuchDatabase if no database matching the given selector
+ /// was found.
+ /// @throw db::AmbiguousDatabase if multiple databases matching the selector
+ /// were found.
+ /// @return Number of affected properties, if the value is non void.
+ template<typename ReturnValue, typename... FnPtrArgs, typename... Args>
+ ReturnValue createUpdateDeleteProperty(ReturnValue (ConfigBackendType::*MethodPointer)
+ (const db::ServerSelector&, FnPtrArgs...),
+ const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ Args... input) {
+ auto backends = selectBackends(backend_selector);
+ if (backends.empty()) {
+ isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+ << backend_selector.toText());
+
+ } else if (backends.size() > 1) {
+ isc_throw(db::AmbiguousDatabase, "more than one database found for "
+ "selector: " << backend_selector.toText());
+ }
+
+ return ((*(*(backends.begin())).*MethodPointer)(server_selector, input...));
+ }
+
+ /// @brief Add, update or delete property from the backend.
+ ///
+ /// This is a common method for storing a single configuration property in
+ /// a database, updating an existing property or deleting the property
+ /// without specifying the server selector. The server specific backends
+ /// call this method. For example, the DHCPv4 configuration backend pool
+ /// may use this function to implement the @c createUpdateServer4 method:
+ ///
+ /// @code
+ /// void createUpdateServer4(const BackendSelector& backend_selector,
+ /// const ServerSelector& server_selector,
+ /// const ServerPtr& server) {
+ /// createUpdateDeleteBackendProperty<void, const ServerPtr&>
+ /// (&ConfigBackendDHCPv4::createUpdateServer4, backend_selector,
+ /// server);
+ /// }
+ /// @endcode
+ ///
+ /// where @c ConfigBackendDHCPv4::createUpdateServer4 has the following
+ /// signature:
+ ///
+ /// @code
+ /// void createUpdateServer4(const ServerPtr&);
+ /// @endcode
+ ///
+ /// This method is used in cases when server selector is not applicable.
+ ///
+ /// @tparam ReturnValue Returned value, typically void or uint64_t.
+ /// @tparam FnPtrArgs Parameter pack holding argument types of the backend
+ /// method to be invoked.
+ /// @tparam Args Parameter pack holding types of the arguments provided
+ /// in the call to this method.
+ ///
+ /// @param MethodPointer Pointer to the backend method to be called.
+ /// @param backend_selector Backend selector.
+ /// @param input Objects used as arguments to the backend method to be
+ /// called.
+ ///
+ /// @throw db::NoSuchDatabase if no database matching the given selector
+ /// was found.
+ /// @throw db::AmbiguousDatabase if multiple databases matching the selector
+ /// were found.
+ /// @return Number of affected properties, if the value is non void.
+ template<typename ReturnValue, typename... FnPtrArgs, typename... Args>
+ ReturnValue createUpdateDeleteBackendProperty(ReturnValue (ConfigBackendType::*MethodPointer)
+ (FnPtrArgs...),
+ const db::BackendSelector& backend_selector,
+ Args... input) {
+ auto backends = selectBackends(backend_selector);
+ if (backends.empty()) {
+ isc_throw(db::NoSuchDatabase, "no such database found for selector: "
+ << backend_selector.toText());
+
+ } else if (backends.size() > 1) {
+ isc_throw(db::AmbiguousDatabase, "more than one database found for "
+ "selector: " << backend_selector.toText());
+ }
+
+ return ((*(*(backends.begin())).*MethodPointer)(input...));
+ }
+
+ /// @brief Selects existing backends matching the selector.
+ ///
+ /// This method selects backends matching the selector. If the selector is
+ /// "unspecified" or there is no backend in the pool, an empty list is returned.
+ ///
+ /// @param backend_selector Selector for which matching backends should be selected.
+ std::list<ConfigBackendTypePtr>
+ selectBackends(const db::BackendSelector& backend_selector) const {
+
+ std::list<ConfigBackendTypePtr> selected;
+
+ // In case there is only one backend and the caller hasn't specified
+ // any particular backend, simply return it.
+ if ((backends_.size() == 1) && backend_selector.amUnspecified()) {
+ selected.push_back(*backends_.begin());
+ return (selected);
+ }
+
+ // For other cases we return empty list.
+ if (backends_.empty() || backend_selector.amUnspecified()) {
+ return (selected);
+ }
+
+ // Go over all backends.
+ for (auto backend : backends_) {
+ // If backend type is specified and it is not matching,
+ // do not select this backend.
+ if ((backend_selector.getBackendType() != db::BackendSelector::Type::UNSPEC) &&
+ (backend_selector.getBackendType() !=
+ db::BackendSelector::stringToBackendType(backend->getType()))) {
+ continue;
+ }
+
+ // If the host has been specified by the backend's host is not
+ // matching, do not select this backend.
+ if ((!backend_selector.getBackendHost().empty()) &&
+ (backend_selector.getBackendHost() != backend->getHost())) {
+ continue;
+ }
+
+ // If the port has been specified by the backend's port is not
+ // matching, do not select this backend.
+ if ((backend_selector.getBackendPort() != 0) &&
+ (backend_selector.getBackendPort() != backend->getPort())) {
+ continue;
+ }
+
+ // Passed all checks, so the backend is matching. Add it to the list.
+ selected.push_back(backend);
+ }
+
+ return (selected);
+ }
+
+ /// @brief Holds configuration backends belonging to the pool.
+ std::list<ConfigBackendTypePtr> backends_;
+};
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif // BASE_CONFIG_BACKEND_POOL_H
diff --git a/src/lib/config_backend/constants.h b/src/lib/config_backend/constants.h
new file mode 100644
index 0000000..b869a03
--- /dev/null
+++ b/src/lib/config_backend/constants.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2018-2023 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 CONFIG_BACKEND_CONSTANTS_H
+#define CONFIG_BACKEND_CONSTANTS_H
+
+namespace isc {
+namespace cb {
+
+/// @name Constants specifying output buffer lengths.
+///
+/// Config backends create buffers of these sizes to store
+/// fetched variable length data in them.
+///
+//@{
+constexpr unsigned long SUBNET4_PREFIX_BUF_LENGTH = 32;
+
+constexpr unsigned long SUBNET6_PREFIX_BUF_LENGTH = 64;
+
+constexpr unsigned long POOL_ADDRESS6_BUF_LENGTH = 45;
+
+constexpr unsigned long DHCP4O6_INTERFACE_BUF_LENGTH = 128;
+
+constexpr unsigned long DHCP4O6_INTERFACE_ID_BUF_LENGTH = 128;
+
+constexpr unsigned long DHCP4O6_SUBNET_BUF_LENGTH = 64;
+
+constexpr unsigned long BOOT_FILE_NAME_BUF_LENGTH = 512;
+
+constexpr unsigned long CLIENT_CLASS_BUF_LENGTH = 128;
+
+constexpr unsigned long INTERFACE_BUF_LENGTH = 128;
+
+constexpr unsigned long INTERFACE_ID_BUF_LENGTH = 128;
+
+constexpr unsigned long RELAY_BUF_LENGTH = 65536;
+
+constexpr unsigned long REQUIRE_CLIENT_CLASSES_BUF_LENGTH = 65536;
+
+constexpr unsigned long SERVER_HOSTNAME_BUF_LENGTH = 512;
+
+constexpr unsigned long SHARED_NETWORK_NAME_BUF_LENGTH = 128;
+
+constexpr unsigned long USER_CONTEXT_BUF_LENGTH = 65536;
+
+constexpr unsigned long OPTION_VALUE_BUF_LENGTH = 65536;
+
+constexpr unsigned long FORMATTED_OPTION_VALUE_BUF_LENGTH = 8192;
+
+constexpr unsigned long OPTION_SPACE_BUF_LENGTH = 128;
+
+constexpr unsigned long OPTION_NAME_BUF_LENGTH = 128;
+
+constexpr unsigned long OPTION_ENCAPSULATE_BUF_LENGTH = 128;
+
+constexpr unsigned long OPTION_RECORD_TYPES_BUF_LENGTH = 512;
+
+constexpr unsigned long GLOBAL_PARAMETER_NAME_BUF_LENGTH = 128;
+
+constexpr unsigned long GLOBAL_PARAMETER_VALUE_BUF_LENGTH = 65536;
+
+constexpr unsigned long CLIENT_CLASS_NAME_BUF_LENGTH = 128;
+
+constexpr unsigned long CLIENT_CLASS_TEST_BUF_LENGTH = 2048;
+
+constexpr unsigned long CLIENT_CLASS_SNAME_BUF_LENGTH = 128;
+
+constexpr unsigned long CLIENT_CLASS_FILENAME_BUF_LENGTH = 512;
+
+constexpr unsigned long AUDIT_ENTRY_OBJECT_TYPE_BUF_LENGTH = 256;
+
+constexpr unsigned long AUDIT_ENTRY_LOG_MESSAGE_BUF_LENGTH = 65536;
+
+constexpr unsigned long SERVER_TAG_BUF_LENGTH = 64;
+
+constexpr unsigned long SERVER_DESCRIPTION_BUF_LENGTH = 65536;
+
+constexpr unsigned long DNS_NAME_BUF_LENGTH = 255;
+
+constexpr unsigned long ALLOCATOR_TYPE_BUF_LENGTH = 64;
+
+//*}
+
+} // end of namespace isc::cb
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config_backend/tests/Makefile.am b/src/lib/config_backend/tests/Makefile.am
new file mode 100644
index 0000000..a58d143
--- /dev/null
+++ b/src/lib/config_backend/tests/Makefile.am
@@ -0,0 +1,37 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\"
+
+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 += libcb_unittests
+
+libcb_unittests_SOURCES = config_backend_mgr_unittest.cc
+libcb_unittests_SOURCES += run_unittests.cc
+
+libcb_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libcb_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libcb_unittests_LDADD = $(top_builddir)/src/lib/database/libkea-database.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libcb_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libcb_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/config_backend/tests/Makefile.in b/src/lib/config_backend/tests/Makefile.in
new file mode 100644
index 0000000..95f2bac
--- /dev/null
+++ b/src/lib/config_backend/tests/Makefile.in
@@ -0,0 +1,1006 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libcb_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/config_backend/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libcb_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libcb_unittests_SOURCES_DIST = config_backend_mgr_unittest.cc \
+ run_unittests.cc
+@HAVE_GTEST_TRUE@am_libcb_unittests_OBJECTS = libcb_unittests-config_backend_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libcb_unittests-run_unittests.$(OBJEXT)
+libcb_unittests_OBJECTS = $(am_libcb_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libcb_unittests_DEPENDENCIES = $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(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 =
+libcb_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libcb_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)/libcb_unittests-config_backend_mgr_unittest.Po \
+ ./$(DEPDIR)/libcb_unittests-run_unittests.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 =
+SOURCES = $(libcb_unittests_SOURCES)
+DIST_SOURCES = $(am__libcb_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libcb_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ config_backend_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ run_unittests.cc
+@HAVE_GTEST_TRUE@libcb_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libcb_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libcb_unittests_LDADD = $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(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/config_backend/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/config_backend/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
+
+libcb_unittests$(EXEEXT): $(libcb_unittests_OBJECTS) $(libcb_unittests_DEPENDENCIES) $(EXTRA_libcb_unittests_DEPENDENCIES)
+ @rm -f libcb_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libcb_unittests_LINK) $(libcb_unittests_OBJECTS) $(libcb_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcb_unittests-config_backend_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcb_unittests-run_unittests.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 $@ $<
+
+libcb_unittests-config_backend_mgr_unittest.o: config_backend_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libcb_unittests-config_backend_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libcb_unittests-config_backend_mgr_unittest.Tpo -c -o libcb_unittests-config_backend_mgr_unittest.o `test -f 'config_backend_mgr_unittest.cc' || echo '$(srcdir)/'`config_backend_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcb_unittests-config_backend_mgr_unittest.Tpo $(DEPDIR)/libcb_unittests-config_backend_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_mgr_unittest.cc' object='libcb_unittests-config_backend_mgr_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) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libcb_unittests-config_backend_mgr_unittest.o `test -f 'config_backend_mgr_unittest.cc' || echo '$(srcdir)/'`config_backend_mgr_unittest.cc
+
+libcb_unittests-config_backend_mgr_unittest.obj: config_backend_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libcb_unittests-config_backend_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libcb_unittests-config_backend_mgr_unittest.Tpo -c -o libcb_unittests-config_backend_mgr_unittest.obj `if test -f 'config_backend_mgr_unittest.cc'; then $(CYGPATH_W) 'config_backend_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/config_backend_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcb_unittests-config_backend_mgr_unittest.Tpo $(DEPDIR)/libcb_unittests-config_backend_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_mgr_unittest.cc' object='libcb_unittests-config_backend_mgr_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) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libcb_unittests-config_backend_mgr_unittest.obj `if test -f 'config_backend_mgr_unittest.cc'; then $(CYGPATH_W) 'config_backend_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/config_backend_mgr_unittest.cc'; fi`
+
+libcb_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libcb_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libcb_unittests-run_unittests.Tpo -c -o libcb_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcb_unittests-run_unittests.Tpo $(DEPDIR)/libcb_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libcb_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) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libcb_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libcb_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libcb_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libcb_unittests-run_unittests.Tpo -c -o libcb_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)/libcb_unittests-run_unittests.Tpo $(DEPDIR)/libcb_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libcb_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) $(libcb_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libcb_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)/libcb_unittests-config_backend_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libcb_unittests-run_unittests.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)/libcb_unittests-config_backend_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libcb_unittests-run_unittests.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/config_backend/tests/config_backend_mgr_unittest.cc b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc
new file mode 100644
index 0000000..09645e3
--- /dev/null
+++ b/src/lib/config_backend/tests/config_backend_mgr_unittest.cc
@@ -0,0 +1,557 @@
+// Copyright (C) 2018-2023 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 <config_backend/base_config_backend_mgr.h>
+#include <config_backend/base_config_backend_pool.h>
+#include <config_backend/base_config_backend.h>
+#include <database/database_connection.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <list>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::cb;
+using namespace isc::db;
+
+namespace {
+
+/// @brief Defines list of properties retrieved by @c TestConfigBackend.
+///
+/// A single property is a name/value pair, where value is an integer.
+typedef std::list<std::pair<std::string, int> > PropertiesList;
+
+/// @brief Implements configuration backend used in tests.
+///
+/// @c BaseConfigBackend is an abstract class that must be implemented
+/// in order to allow us to test mechanisms implemented in
+/// @c BaseConfigBackendMgr and @c BaseConfigBackendPool.
+///
+/// Normally, a class derived directly from the @c BaseConfigBackend
+/// will merely provide an interface for server specific operations,
+/// e.g. DHCPv4 specific operations, and the database specific classes
+/// will implement this interface. However, for the test purposes it
+/// is convenient to implement them here and let the derivations
+/// only provide the implementations of the @c getType, @c getHost
+/// and @c getPort functions. That way, the logic for adding and
+/// retrieving the data from the backend is implemented only once and
+/// is common accross all test backends.
+///
+/// This class provides a logic for managing test data being a collection
+/// of name/value pairs, i.e. "properties". It contains a list of
+/// properties and the functions for adding the properties, retrieving
+/// a single property and retrieving a collection of properties.
+class TestConfigBackend : public BaseConfigBackend {
+public:
+
+ /// @brief Retrieves first property having a given name.
+ ///
+ /// @param property_name Name of the property to be retrieved.
+ /// @return Value of the property or 0 if property doesn't exist.
+ virtual int getProperty(const ServerSelector&,
+ const std::string& property_name) const {
+ for (auto property : properties_) {
+ if (property.first == property_name) {
+ return (property.second);
+ }
+ }
+ return (0);
+ }
+
+ /// @brief Retrieves first property matching the name and value.
+ ///
+ /// @param property_name Name of the property to be retrieved.
+ /// @param property_value Value of the property to be retrieved.
+ /// @return Value of the property or 0 if the property doesn't exist.
+ virtual int getProperty(const ServerSelector&,
+ const std::string& property_name,
+ const int property_value) const {
+ for (auto property : properties_) {
+ if ((property.first == property_name) &&
+ (property.second == property_value)) {
+ return (property.second);
+ }
+ }
+ return (0);
+ }
+
+ /// @brief Retrieves all properties having a given name.
+ ///
+ /// @param property_name Name of the properties to be retrieved.
+ /// @return List of the properties having a given name. This list is
+ /// empty if no property was found.
+ virtual PropertiesList getProperties(const ServerSelector&,
+ const std::string& property_name) const {
+ PropertiesList properties;
+ for (auto property : properties_) {
+ if (property.first == property_name) {
+ properties.push_back(property);
+ }
+ }
+ return (properties);
+ }
+
+ /// @brief Retrieves all properties.
+ ///
+ /// @return List of all properties held in the backend.
+ virtual PropertiesList getAllProperties(const ServerSelector&) const {
+ return (properties_);
+ }
+
+ /// @brief Creates new property.
+ ///
+ /// @param new_property Property to be added to the backend.
+ virtual void createProperty(const ServerSelector&,
+ const std::pair<std::string, int>& new_property) {
+ properties_.push_back(new_property);
+ }
+
+protected:
+
+ /// @brief Holds list of properties (simulates database).
+ PropertiesList properties_;
+
+};
+
+/// @brief Shared pointer to the @c TestConfigBackend.
+typedef boost::shared_ptr<TestConfigBackend> TestConfigBackendPtr;
+
+/// @brief First implementation of the test config backend.
+///
+/// It simulates being a MySQL backend installed on the
+/// "mysql-host" host and running on port 2345.
+class TestConfigBackendImpl1 : public TestConfigBackend {
+public:
+
+ /// @brief Returns backend type.
+ ///
+ /// @return "mysql".
+ virtual std::string getType() const {
+ return (std::string("mysql"));
+ }
+
+ /// @brief Returns backend host.
+ ///
+ /// @return "mysql-host".
+ virtual std::string getHost() const {
+ return (std::string("mysql-host"));
+ }
+
+ /// @brief Returns backend port.
+ ///
+ /// @return Port number 2345.
+ virtual uint16_t getPort() const {
+ return (2345);
+ }
+
+};
+
+/// @brief Shared pointer to the @c TestConfigBackendImpl1.
+typedef boost::shared_ptr<TestConfigBackendImpl1> TestConfigBackendImpl1Ptr;
+
+/// @brief Second implementation of the test config backend.
+///
+/// It simulates being a Postgres backend installed on the
+/// "postgresql-host" host and running on port 1234.
+class TestConfigBackendImpl2 : public TestConfigBackend {
+public:
+
+ /// @brief Returns backend type.
+ ///
+ /// @return "postgresql".
+ virtual std::string getType() const {
+ return (std::string("postgresql"));
+ }
+
+ /// @brief Returns backend host.
+ ///
+ /// @return "postgresql-host".
+ virtual std::string getHost() const {
+ return (std::string("postgresql-host"));
+ }
+
+ /// @brief Returns backend port.
+ ///
+ /// @return Port number 1234.
+ virtual uint16_t getPort() const {
+ return (1234);
+ }
+};
+
+/// @brief Shared pointer to the @c TestConfigBackendImpl2.
+typedef boost::shared_ptr<TestConfigBackendImpl2> TestConfigBackendImpl2Ptr;
+
+/// @brief Implements test pool of configuration backends.
+///
+/// @c BaseConfigBackendPool template provides mechanics for managing the data
+/// stored in multiple backends. Server specific pools must extend this class
+/// with methods for managing the data appropriate for the server types.
+/// This class provides an example pool implementation for managing the
+/// "properties" being name/value pairs. It extends the base class with
+/// new methods to retrieve a single property and multiple properties. It
+/// also adds a method to create new property. Those methods correspond to
+/// the ones implemented in the @c TestConfigBackend, but also each of
+/// them includes a "database selector" used to indicate the backend to
+/// be used.
+class TestConfigBackendPool : public BaseConfigBackendPool<TestConfigBackend> {
+public:
+
+ /// @brief Retrieves a value of the property.
+ ///
+ /// @param property_name Name of the property which value should be returned.
+ /// @param backend_selector Backend selector. The default value of the selector
+ /// is @c UNSPEC which means that the property will be searched in all backends
+ /// and the first value found will be returned.
+ /// @param server_selector Server selector. The default value is set to @c ALL,
+ /// which means that the property for all servers will be returned.
+ virtual int getProperty(const std::string& property_name,
+ const BackendSelector& backend_selector =
+ BackendSelector::UNSPEC(),
+ const ServerSelector& server_selector =
+ ServerSelector::ALL()) const {
+ int property = 0;
+
+ // If the selector is specified, this method will pick the appropriate
+ // backend and will call getProperty method on this backend. If the
+ // selector is not specified, this method will iterate over existing
+ // backends and call getProperty on them. It will return after finding
+ // the first non-zero value of the property. For example, if the first
+ // backend contains a non-zero value this value will be returned and
+ // the value held in the second backend (if any) won't be fetched.
+ // The template arguments specify the returned value type and the
+ // argument of the getProperty method.
+ getPropertyPtrConst<int, const std::string&>
+ (&TestConfigBackend::getProperty, backend_selector, server_selector,
+ property, property_name);
+ return (property);
+ }
+
+ /// @brief Retrieves value of the property.
+ ///
+ /// @param property_name Name of the property which value should be returned.
+ /// @param property_value Value of the property to be retrieved.
+ /// @param backend_selector Backend selector. The default value of the selector
+ /// is @c UNSPEC which means that the property will be searched in all backends
+ /// and the first value found will be returned.
+ /// @param server_selector Server selector. The default value is set to @c ALL,
+ /// which means that the property for all servers will be returned.
+ virtual int getProperty(const std::string& property_name,
+ const int property_value,
+ const BackendSelector& backend_selector =
+ BackendSelector::UNSPEC(),
+ const ServerSelector& server_selector =
+ ServerSelector::ALL()) const {
+ int property = 0;
+
+ getPropertyPtrConst<int, const std::string&, int>
+ (&TestConfigBackend::getProperty, backend_selector, server_selector,
+ property, property_name, property_value);
+ return (property);
+ }
+
+
+ /// @brief Retrieves multiple properties.
+ ///
+ /// @param property_name Name of the properties which should be retrieved.
+ /// @param backend_selector Backend selector. The default value of the selector
+ /// is @c UNSPEC which means that the properties will be searched in all
+ /// backends and the first non-empty list will be returned.
+ /// @param server_selector Server selector. The default value is set to @c ALL,
+ /// which means that the properties for all servers will be returned.
+ virtual PropertiesList getProperties(const std::string& property_name,
+ const BackendSelector& backend_selector =
+ BackendSelector::UNSPEC(),
+ const ServerSelector& server_selector =
+ ServerSelector::ALL()) const {
+ PropertiesList properties;
+
+ // If the selector is specified, this method will pick the appropriate
+ // backend and will call getProperties method on this backend. If the
+ // selector is not specified, this method will iterate over existing
+ // backends and call getProperties on them. It will return after finding
+ // the first non-empty list of properties in one of the backends.
+ // The template arguments specify the type of the list of properties
+ // and the argument of the getProperties method.
+ getMultiplePropertiesConst<PropertiesList, const std::string&>
+ (&TestConfigBackend::getProperties, backend_selector, server_selector,
+ properties, property_name);
+ return (properties);
+ }
+
+ /// @brief Retrieves all properties.
+ ///
+ /// @param backend_selector Backend selector. The default value of the selector
+ /// is @c UNSPEC which means that the properties will be searched in all
+ /// backends and the first non-empty list will be returned.
+ /// @param server_selector Server selector. The default value is set to @c ALL,
+ /// which means that the properties for all servers will be returned.
+ virtual PropertiesList getAllProperties(const BackendSelector& backend_selector =
+ BackendSelector::UNSPEC(),
+ const ServerSelector& server_selector =
+ ServerSelector::ALL()) const {
+ PropertiesList properties;
+
+ // This method is similar to getMultiplePropertiesConst but it lacks
+ // an argument and it simply returns all properties.
+ getAllPropertiesConst<PropertiesList>
+ (&TestConfigBackend::getAllProperties, backend_selector, server_selector,
+ properties);
+ return (properties);
+ }
+
+ /// @brief Creates new property in a selected backend.
+ ///
+ /// @param new_property New property to be added to a backend.
+ /// @param backend_selector Backend selector. It has no default value.
+ /// @param server_selector The default value is @c ALL which means that
+ /// new property is going to be shared by all servers.
+ virtual void createProperty(const std::pair<std::string, int>& new_property,
+ const BackendSelector& backend_selector,
+ const ServerSelector& server_selector =
+ ServerSelector::ALL()) {
+ createUpdateDeleteProperty<void, const std::pair<std::string, int>&>
+ (&TestConfigBackend::createProperty, backend_selector, server_selector,
+ new_property);
+ }
+};
+
+using TestConfigBackendMgr = BaseConfigBackendMgr<TestConfigBackendPool>;
+
+/// @brief Test fixture class for testing @c ConfigBackendMgr.
+class ConfigBackendMgrTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ConfigBackendMgrTest()
+ : config_mgr_() {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes backend instances.
+ ~ConfigBackendMgrTest() {
+ config_mgr_.delAllBackends();
+ }
+
+ /// @brief Creates example database backend called "mysql".
+ ///
+ /// It uses @c TestConfigBackendImpl1.
+ void addTestMySQLBackend() {
+ config_mgr_.registerBackendFactory("mysql", [](const DatabaseConnection::ParameterMap&)
+ -> TestConfigBackendPtr {
+ return (TestConfigBackendImpl1Ptr(new TestConfigBackendImpl1()));
+ });
+
+ config_mgr_.addBackend("type=mysql");
+ }
+
+ /// @brief Creates example database backend called "postgresql".
+ ///
+ /// It uses @c TestConfigBackendImpl2.
+ void addTestPgSQLBackend() {
+ config_mgr_.registerBackendFactory("postgresql", [](const DatabaseConnection::ParameterMap&)
+ -> TestConfigBackendPtr {
+ return (TestConfigBackendImpl2Ptr(new TestConfigBackendImpl2()));
+ });
+
+ // Actually create the backends.
+ config_mgr_.addBackend("type=postgresql");
+ }
+
+ /// @brief Creates two example database backends called "mysql" and "postgresql".
+ ///
+ /// It uses @c TestConfigBackendImpl1 and @c TestConfigBackendImpl2.
+ void addTestBackends() {
+ addTestMySQLBackend();
+ addTestPgSQLBackend();
+ }
+
+ /// @brief Adds test data into the backends.
+ void addTestData() {
+ // Add two properties with different names into the first backend.
+ config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+ BackendSelector(BackendSelector::Type::MYSQL));
+ config_mgr_.getPool()->createProperty(std::make_pair("wolves", 3),
+ BackendSelector(BackendSelector::Type::MYSQL));
+
+ // Add two properties into the second backend. Both properties share the
+ // name so as we can test retrieving multiple records from the same backend.
+ config_mgr_.getPool()->createProperty(std::make_pair("cats", 2),
+ BackendSelector(BackendSelector::Type::POSTGRESQL));
+ config_mgr_.getPool()->createProperty(std::make_pair("cats", 4),
+ BackendSelector(BackendSelector::Type::POSTGRESQL));
+ }
+
+ /// Instance of the test configuration manager.
+ TestConfigBackendMgr config_mgr_;
+};
+
+// Test that selector can be left "unspecified" if there is only one backend,
+// when manipulating the data.
+TEST_F(ConfigBackendMgrTest, createPropertySingleBackendUnspec) {
+ addTestMySQLBackend();
+
+ ASSERT_NO_THROW(
+ config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+ BackendSelector(BackendSelector::Type::UNSPEC))
+ );
+
+ // We should be able to retrieve stored value without having to specify
+ // the backend.
+ EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs"));
+}
+
+// Test that selector must be specified if there is more than one backend,
+// when manipulating the data.
+TEST_F(ConfigBackendMgrTest, createPropertyTwoBackendsUnspec) {
+ addTestBackends();
+
+ // Backend must be selected if there is more than one backend present.
+ EXPECT_THROW(
+ config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+ BackendSelector(BackendSelector::Type::UNSPEC)),
+ NoSuchDatabase
+ );
+}
+
+// Test that exception is thrown when multiple backends are selected.
+TEST_F(ConfigBackendMgrTest, createPropertyAmbiguousSelection) {
+ addTestBackends();
+
+ // Add another MySQL backend to cause the selection to give ambiguous
+ // result.
+ config_mgr_.addBackend("type=mysql");
+
+ EXPECT_THROW(
+ config_mgr_.getPool()->createProperty(std::make_pair("dogs", 1),
+ BackendSelector(BackendSelector::Type::MYSQL)),
+ AmbiguousDatabase
+ );
+}
+
+// Test that a single property can be retrieved from a selected backend.
+TEST_F(ConfigBackendMgrTest, getSingleProperty) {
+
+ addTestBackends();
+ addTestData();
+
+ // Backend is not specified, so it should find the dogs in first one and
+ // cats in the second one.
+ EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs"));
+ EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats"));
+
+ // No dogs in the postgresql backend and no cats in mysql backend.
+ EXPECT_EQ(0, config_mgr_.getPool()->getProperty("dogs",
+ BackendSelector(BackendSelector::Type::POSTGRESQL)));
+ EXPECT_EQ(0, config_mgr_.getPool()->getProperty("cats",
+ BackendSelector(BackendSelector::Type::MYSQL)));
+
+ // If the selectors are pointing to the right databases, the dogs and cats
+ // should be returned properly.
+ EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs",
+ BackendSelector(BackendSelector::Type::MYSQL)));
+ EXPECT_EQ(2, config_mgr_.getPool()->getProperty("cats",
+ BackendSelector(BackendSelector::Type::POSTGRESQL)));
+
+ // Also make sure that the variant of getProperty function taking two arguments
+ // would return the value.
+ EXPECT_EQ(1, config_mgr_.getPool()->getProperty("dogs", 1,
+ BackendSelector(BackendSelector::Type::MYSQL)));
+
+ // If the value is not matching it should return 0.
+ EXPECT_EQ(0, config_mgr_.getPool()->getProperty("dogs", 2,
+ BackendSelector(BackendSelector::Type::MYSQL)));
+
+ // Try to use the backend that is not present.
+ EXPECT_THROW(config_mgr_.getPool()->getProperty("cats",
+ BackendSelector(static_cast<BackendSelector::Type>(-1))),
+ NoSuchDatabase);
+}
+
+// Test that multiple properties can be retrieved with filtering.
+TEST_F(ConfigBackendMgrTest, getMultipleProperties) {
+
+ addTestBackends();
+ addTestData();
+
+ // There is one dogs entry in mysql.
+ PropertiesList mysql_list =
+ config_mgr_.getPool()->getProperties("dogs",
+ BackendSelector(BackendSelector::Type::MYSQL));
+ ASSERT_EQ(1, mysql_list.size());
+
+ // There is also one wolves entry in mysql.
+ mysql_list = config_mgr_.getPool()->getProperties("wolves",
+ BackendSelector(BackendSelector::Type::MYSQL));
+ ASSERT_EQ(1, mysql_list.size());
+
+ // There are two cats entries in postgresql.
+ PropertiesList postgresql_list =
+ config_mgr_.getPool()->getProperties("cats",
+ BackendSelector(BackendSelector::Type::POSTGRESQL));
+ ASSERT_EQ(2, postgresql_list.size());
+
+ // Try to use the backend that is not present.
+ EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
+ BackendSelector(static_cast<BackendSelector::Type>(-1))),
+ NoSuchDatabase);
+
+}
+
+// Test that all properties can be retrieved from each backend.
+TEST_F(ConfigBackendMgrTest, getAllProperties) {
+
+ addTestBackends();
+ addTestData();
+
+ // The mysql backend holds two entries.
+ PropertiesList mysql_list =
+ config_mgr_.getPool()->getAllProperties(BackendSelector(BackendSelector::Type::MYSQL));
+ ASSERT_EQ(2, mysql_list.size());
+
+ // The postgresql backends also holds two entries.
+ PropertiesList postgresql_list =
+ config_mgr_.getPool()->getAllProperties(BackendSelector(BackendSelector::Type::POSTGRESQL));
+ ASSERT_EQ(2, postgresql_list.size());
+
+ // Try to use the backend that is not present.
+ EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
+ BackendSelector(static_cast<BackendSelector::Type>(-1))),
+ NoSuchDatabase);
+}
+
+// Verify that unregistering a factory works.
+TEST_F(ConfigBackendMgrTest, unregister) {
+
+ // Verify we can't remove what is not there.
+ ASSERT_FALSE(config_mgr_.unregisterBackendFactory("mysql"));
+
+ // Add both MySQL and Postgresql backends
+ addTestBackends();
+
+ // Backend should be present.
+ EXPECT_NO_THROW(config_mgr_.getPool()->getProperties("cats",
+ BackendSelector(BackendSelector::Type::MYSQL)));
+
+ // Verify that unregistering MySQL factory returns true.
+ ASSERT_TRUE(config_mgr_.unregisterBackendFactory("mysql"));
+
+ // Verify that the factory is actually gone.
+ ASSERT_THROW(config_mgr_.addBackend("type=mysql"), db::InvalidType);
+
+ // Verify we can't remove what is not there.
+ ASSERT_FALSE(config_mgr_.unregisterBackendFactory("mysql"));
+
+ // Try to use the backend that is not present.
+ EXPECT_THROW(config_mgr_.getPool()->getProperties("cats",
+ BackendSelector(BackendSelector::Type::MYSQL)),
+ NoSuchDatabase);
+}
+
+}
diff --git a/src/lib/config_backend/tests/run_unittests.cc b/src/lib/config_backend/tests/run_unittests.cc
new file mode 100644
index 0000000..6754d66
--- /dev/null
+++ b/src/lib/config_backend/tests/run_unittests.cc
@@ -0,0 +1,19 @@
+// Copyright (C) 2018 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/cryptolink/Makefile.am b/src/lib/cryptolink/Makefile.am
new file mode 100644
index 0000000..8a7e72e
--- /dev/null
+++ b/src/lib/cryptolink/Makefile.am
@@ -0,0 +1,52 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-cryptolink.la
+
+libkea_cryptolink_la_SOURCES = cryptolink.h cryptolink.cc
+libkea_cryptolink_la_SOURCES += crypto_hash.h crypto_hash.cc
+libkea_cryptolink_la_SOURCES += crypto_hmac.h crypto_hmac.cc
+libkea_cryptolink_la_SOURCES += crypto_rng.h crypto_rng.cc
+if HAVE_BOTAN
+libkea_cryptolink_la_SOURCES += botan_link.cc
+libkea_cryptolink_la_SOURCES += botan_common.h
+libkea_cryptolink_la_SOURCES += botan_hash.cc
+libkea_cryptolink_la_SOURCES += botan_hmac.cc
+endif
+if HAVE_OPENSSL
+libkea_cryptolink_la_SOURCES += openssl_link.cc
+libkea_cryptolink_la_SOURCES += openssl_common.h
+libkea_cryptolink_la_SOURCES += openssl_hash.cc
+libkea_cryptolink_la_SOURCES += openssl_compat.h
+libkea_cryptolink_la_SOURCES += openssl_hmac.cc
+endif
+
+libkea_cryptolink_la_LDFLAGS = $(CRYPTO_LDFLAGS)
+libkea_cryptolink_la_LDFLAGS += -no-undefined -version-info 38:0:0
+libkea_cryptolink_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_cryptolink_la_LIBADD += $(CRYPTO_LIBS)
+
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cryptolink_includedir = $(pkgincludedir)/cryptolink
+libkea_cryptolink_include_HEADERS = \
+ crypto_hash.h \
+ crypto_hmac.h \
+ crypto_rng.h \
+ cryptolink.h
+
+if HAVE_BOTAN
+libkea_cryptolink_include_HEADERS += \
+ botan_common.h
+endif
+
+if HAVE_OPENSSL
+libkea_cryptolink_include_HEADERS += \
+ openssl_common.h \
+ openssl_compat.h
+endif
diff --git a/src/lib/cryptolink/Makefile.in b/src/lib/cryptolink/Makefile.in
new file mode 100644
index 0000000..0757ffd
--- /dev/null
+++ b/src/lib/cryptolink/Makefile.in
@@ -0,0 +1,1003 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_BOTAN_TRUE@am__append_1 = botan_link.cc botan_common.h \
+@HAVE_BOTAN_TRUE@ botan_hash.cc botan_hmac.cc
+@HAVE_OPENSSL_TRUE@am__append_2 = openssl_link.cc openssl_common.h \
+@HAVE_OPENSSL_TRUE@ openssl_hash.cc openssl_compat.h \
+@HAVE_OPENSSL_TRUE@ openssl_hmac.cc
+@HAVE_BOTAN_TRUE@am__append_3 = \
+@HAVE_BOTAN_TRUE@ botan_common.h
+
+@HAVE_OPENSSL_TRUE@am__append_4 = \
+@HAVE_OPENSSL_TRUE@ openssl_common.h \
+@HAVE_OPENSSL_TRUE@ openssl_compat.h
+
+subdir = src/lib/cryptolink
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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__libkea_cryptolink_include_HEADERS_DIST) \
+ $(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)$(libkea_cryptolink_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_cryptolink_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1)
+am__libkea_cryptolink_la_SOURCES_DIST = cryptolink.h cryptolink.cc \
+ crypto_hash.h crypto_hash.cc crypto_hmac.h crypto_hmac.cc \
+ crypto_rng.h crypto_rng.cc botan_link.cc botan_common.h \
+ botan_hash.cc botan_hmac.cc openssl_link.cc openssl_common.h \
+ openssl_hash.cc openssl_compat.h openssl_hmac.cc
+@HAVE_BOTAN_TRUE@am__objects_1 = botan_link.lo botan_hash.lo \
+@HAVE_BOTAN_TRUE@ botan_hmac.lo
+@HAVE_OPENSSL_TRUE@am__objects_2 = openssl_link.lo openssl_hash.lo \
+@HAVE_OPENSSL_TRUE@ openssl_hmac.lo
+am_libkea_cryptolink_la_OBJECTS = cryptolink.lo crypto_hash.lo \
+ crypto_hmac.lo crypto_rng.lo $(am__objects_1) $(am__objects_2)
+libkea_cryptolink_la_OBJECTS = $(am_libkea_cryptolink_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_cryptolink_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_cryptolink_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)/botan_hash.Plo \
+ ./$(DEPDIR)/botan_hmac.Plo ./$(DEPDIR)/botan_link.Plo \
+ ./$(DEPDIR)/crypto_hash.Plo ./$(DEPDIR)/crypto_hmac.Plo \
+ ./$(DEPDIR)/crypto_rng.Plo ./$(DEPDIR)/cryptolink.Plo \
+ ./$(DEPDIR)/openssl_hash.Plo ./$(DEPDIR)/openssl_hmac.Plo \
+ ./$(DEPDIR)/openssl_link.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_cryptolink_la_SOURCES)
+DIST_SOURCES = $(am__libkea_cryptolink_la_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
+am__libkea_cryptolink_include_HEADERS_DIST = crypto_hash.h \
+ crypto_hmac.h crypto_rng.h cryptolink.h botan_common.h \
+ openssl_common.h openssl_compat.h
+HEADERS = $(libkea_cryptolink_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-cryptolink.la
+libkea_cryptolink_la_SOURCES = cryptolink.h cryptolink.cc \
+ crypto_hash.h crypto_hash.cc crypto_hmac.h crypto_hmac.cc \
+ crypto_rng.h crypto_rng.cc $(am__append_1) $(am__append_2)
+libkea_cryptolink_la_LDFLAGS = $(CRYPTO_LDFLAGS) -no-undefined \
+ -version-info 38:0:0
+libkea_cryptolink_la_LIBADD = \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(CRYPTO_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_cryptolink_includedir = $(pkgincludedir)/cryptolink
+libkea_cryptolink_include_HEADERS = crypto_hash.h crypto_hmac.h \
+ crypto_rng.h cryptolink.h $(am__append_3) $(am__append_4)
+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/cryptolink/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/cryptolink/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-cryptolink.la: $(libkea_cryptolink_la_OBJECTS) $(libkea_cryptolink_la_DEPENDENCIES) $(EXTRA_libkea_cryptolink_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_cryptolink_la_LINK) -rpath $(libdir) $(libkea_cryptolink_la_OBJECTS) $(libkea_cryptolink_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/botan_hash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/botan_hmac.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/botan_link.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crypto_hash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crypto_hmac.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crypto_rng.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cryptolink.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openssl_hash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openssl_hmac.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/openssl_link.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_cryptolink_includeHEADERS: $(libkea_cryptolink_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_cryptolink_include_HEADERS)'; test -n "$(libkea_cryptolink_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_cryptolink_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_cryptolink_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)$(libkea_cryptolink_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_cryptolink_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_cryptolink_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_cryptolink_include_HEADERS)'; test -n "$(libkea_cryptolink_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_cryptolink_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_cryptolink_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/botan_hash.Plo
+ -rm -f ./$(DEPDIR)/botan_hmac.Plo
+ -rm -f ./$(DEPDIR)/botan_link.Plo
+ -rm -f ./$(DEPDIR)/crypto_hash.Plo
+ -rm -f ./$(DEPDIR)/crypto_hmac.Plo
+ -rm -f ./$(DEPDIR)/crypto_rng.Plo
+ -rm -f ./$(DEPDIR)/cryptolink.Plo
+ -rm -f ./$(DEPDIR)/openssl_hash.Plo
+ -rm -f ./$(DEPDIR)/openssl_hmac.Plo
+ -rm -f ./$(DEPDIR)/openssl_link.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-libkea_cryptolink_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)/botan_hash.Plo
+ -rm -f ./$(DEPDIR)/botan_hmac.Plo
+ -rm -f ./$(DEPDIR)/botan_link.Plo
+ -rm -f ./$(DEPDIR)/crypto_hash.Plo
+ -rm -f ./$(DEPDIR)/crypto_hmac.Plo
+ -rm -f ./$(DEPDIR)/crypto_rng.Plo
+ -rm -f ./$(DEPDIR)/cryptolink.Plo
+ -rm -f ./$(DEPDIR)/openssl_hash.Plo
+ -rm -f ./$(DEPDIR)/openssl_hmac.Plo
+ -rm -f ./$(DEPDIR)/openssl_link.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-libkea_cryptolink_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_cryptolink_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-libkea_cryptolink_includeHEADERS
+
+.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/cryptolink/botan_common.h b/src/lib/cryptolink/botan_common.h
new file mode 100644
index 0000000..05cae30
--- /dev/null
+++ b/src/lib/cryptolink/botan_common.h
@@ -0,0 +1,20 @@
+// Copyright (C) 2014-2017 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/.
+
+namespace isc {
+namespace cryptolink {
+namespace btn {
+
+/// @brief Decode the HashAlgorithm enum into a name usable by Botan
+///
+/// @param algorithm algorithm to be converted
+/// @return static text representation of the algorithm name
+const std::string
+getHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm);
+
+} // namespace btn
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/botan_hash.cc b/src/lib/cryptolink/botan_hash.cc
new file mode 100644
index 0000000..06dca6d
--- /dev/null
+++ b/src/lib/cryptolink/botan_hash.cc
@@ -0,0 +1,199 @@
+// Copyright (C) 2014-2020 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 <cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <botan/lookup.h>
+
+#include <cryptolink/botan_common.h>
+
+namespace isc {
+namespace cryptolink {
+
+const std::string
+btn::getHashAlgorithmName(HashAlgorithm algorithm) {
+ switch (algorithm) {
+ case isc::cryptolink::MD5:
+ return ("MD5");
+ case isc::cryptolink::SHA1:
+ return ("SHA-1");
+ case isc::cryptolink::SHA256:
+ return ("SHA-256");
+ case isc::cryptolink::SHA224:
+ return ("SHA-224");
+ case isc::cryptolink::SHA384:
+ return ("SHA-384");
+ case isc::cryptolink::SHA512:
+ return ("SHA-512");
+ case isc::cryptolink::UNKNOWN_HASH:
+ return ("Unknown");
+ }
+ // compiler should have prevented us to reach this, since we have
+ // no default. But we need a return value anyway
+ return ("Unknown");
+}
+
+/// @brief Botan implementation of Hash. Each method is the counterpart
+/// of the Hash corresponding method.
+class HashImpl {
+public:
+
+ /// @brief Constructor for specific hash algorithm
+ ///
+ /// @param hash_algorithm The hash algorithm
+ explicit HashImpl(const HashAlgorithm hash_algorithm)
+ : hash_algorithm_(hash_algorithm), hash_() {
+ Botan::HashFunction* hash;
+ try {
+ const std::string& name =
+ btn::getHashAlgorithmName(hash_algorithm);
+ hash = Botan::HashFunction::create(name).release();
+ } catch (const Botan::Algorithm_Not_Found&) {
+ isc_throw(isc::cryptolink::UnsupportedAlgorithm,
+ "Unknown hash algorithm: " <<
+ static_cast<int>(hash_algorithm));
+ } catch (const Botan::Exception& exc) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
+ }
+
+ hash_.reset(hash);
+ }
+
+ /// @brief Destructor
+ ~HashImpl() { }
+
+ /// @brief Returns the HashAlgorithm of the object
+ HashAlgorithm getHashAlgorithm() const {
+ return (hash_algorithm_);
+ }
+
+ /// @brief Returns the output size of the digest
+ ///
+ /// @return output size of the digest
+ size_t getOutputLength() const {
+ return (hash_->output_length());
+ }
+
+ /// @brief Adds data to the digest
+ ///
+ /// See @ref isc::cryptolink::Hash::update() for details.
+ void update(const void* data, const size_t len) {
+ try {
+ hash_->update(static_cast<const Botan::byte*>(data), len);
+ } catch (const Botan::Exception& exc) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
+ }
+ }
+
+ /// @brief Calculate the final digest
+ ///
+ /// See @ref isc::cryptolink::Hash::final() for details.
+ void final(isc::util::OutputBuffer& result, size_t len) {
+ try {
+ Botan::secure_vector<Botan::byte> b_result(hash_->final());
+
+ if (len > b_result.size()) {
+ len = b_result.size();
+ }
+ result.writeData(&b_result[0], len);
+ } catch (const Botan::Exception& exc) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
+ }
+ }
+
+ /// @brief Calculate the final digest
+ ///
+ /// See @ref isc::cryptolink::Hash::final() for details.
+ void final(void* result, size_t len) {
+ try {
+ Botan::secure_vector<Botan::byte> b_result(hash_->final());
+ size_t output_size = getOutputLength();
+ if (output_size > len) {
+ output_size = len;
+ }
+ std::memcpy(result, &b_result[0], output_size);
+ } catch (const Botan::Exception& exc) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
+ }
+ }
+
+ /// @brief Calculate the final digest
+ ///
+ /// See @ref isc::cryptolink::Hash::final() for details.
+ std::vector<uint8_t> final(size_t len) {
+ try {
+ Botan::secure_vector<Botan::byte> b_result(hash_->final());
+ if (len > b_result.size()) {
+ len = b_result.size();
+ }
+ // Return vector with content. Construct &b_result[len] attempts
+ // to get an address of one element beyond the b_result. Replaced
+ // with the address of first element + len
+ return (std::vector<uint8_t>(&b_result[0], &b_result[0]+len));
+ } catch (const Botan::Exception& exc) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << exc.what());
+ }
+ }
+
+private:
+ /// @brief The hash algorithm
+ HashAlgorithm hash_algorithm_;
+
+ /// @brief The protected pointer to the Botan HashFunction object
+ boost::scoped_ptr<Botan::HashFunction> hash_;
+};
+
+Hash::Hash(const HashAlgorithm hash_algorithm)
+{
+ impl_ = new HashImpl(hash_algorithm);
+}
+
+Hash::~Hash() {
+ delete impl_;
+}
+
+HashAlgorithm
+Hash::getHashAlgorithm() const {
+ return (impl_->getHashAlgorithm());
+}
+
+size_t
+Hash::getOutputLength() const {
+ return (impl_->getOutputLength());
+}
+
+void
+Hash::update(const void* data, const size_t len) {
+ impl_->update(data, len);
+}
+
+void
+Hash::final(isc::util::OutputBuffer& result, size_t len) {
+ impl_->final(result, len);
+}
+
+void
+Hash::final(void* result, size_t len) {
+ impl_->final(result, len);
+}
+
+std::vector<uint8_t>
+Hash::final(size_t len) {
+ return impl_->final(len);
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/botan_hmac.cc b/src/lib/cryptolink/botan_hmac.cc
new file mode 100644
index 0000000..88efb2e
--- /dev/null
+++ b/src/lib/cryptolink/botan_hmac.cc
@@ -0,0 +1,245 @@
+// Copyright (C) 2011-2019 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 <cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <botan/hmac.h>
+#include <botan/lookup.h>
+
+#include <cryptolink/botan_common.h>
+
+namespace isc {
+namespace cryptolink {
+
+/// @brief Botan implementation of HMAC. Each method is the counterpart
+/// of the HMAC corresponding method.
+class HMACImpl {
+public:
+ /// @brief Constructor from a secret and a hash algorithm
+ ///
+ /// See constructor of the @ref isc::cryptolink::HMAC class for details.
+ ///
+ /// @param secret The secret to sign with
+ /// @param secret_len The length of the secret
+ /// @param hash_algorithm The hash algorithm
+ explicit HMACImpl(const void* secret, size_t secret_len,
+ const HashAlgorithm hash_algorithm)
+ : hash_algorithm_(hash_algorithm), hmac_() {
+ Botan::HashFunction* hash;
+ try {
+ const std::string& name =
+ btn::getHashAlgorithmName(hash_algorithm);
+ std::unique_ptr<Botan::HashFunction> hash_ptr =
+ Botan::HashFunction::create(name);
+ if (hash_ptr) {
+ hash = hash_ptr.release();
+ } else {
+ throw Botan::Algorithm_Not_Found(name);
+ }
+ } catch (const Botan::Algorithm_Not_Found&) {
+ isc_throw(UnsupportedAlgorithm,
+ "Unknown hash algorithm: " <<
+ static_cast<int>(hash_algorithm));
+ } catch (const Botan::Exception& exc) {
+ isc_throw(LibraryError, "Botan error: " << exc.what());
+ }
+
+ hmac_.reset(new Botan::HMAC(hash));
+
+ // If the key length is larger than the block size, we hash the
+ // key itself first.
+ try {
+ // use a temp var so we don't have blocks spanning
+ // preprocessor directives
+ size_t block_length = hash->hash_block_size();
+ if (secret_len > block_length) {
+ Botan::secure_vector<Botan::byte> hashed_key =
+ hash->process(static_cast<const Botan::byte*>(secret),
+ secret_len);
+ hmac_->set_key(&hashed_key[0], hashed_key.size());
+ } else {
+ // Botan 1.8 considers len 0 a bad key. 1.9 does not,
+ // but we won't accept it anyway, and fail early
+ if (secret_len == 0) {
+ isc_throw(BadKey, "Bad HMAC secret length: 0");
+ }
+ hmac_->set_key(static_cast<const Botan::byte*>(secret),
+ secret_len);
+ }
+ } catch (const Botan::Invalid_Key_Length& ikl) {
+ isc_throw(BadKey, ikl.what());
+ } catch (const Botan::Exception& exc) {
+ isc_throw(LibraryError, "Botan error: " << exc.what());
+ }
+ }
+
+ /// @brief Destructor
+ ~HMACImpl() {
+ }
+
+ /// @brief Returns the HashAlgorithm of the object
+ HashAlgorithm getHashAlgorithm() const {
+ return (hash_algorithm_);
+ }
+
+ /// @brief Returns the output size of the digest
+ ///
+ /// @return output size of the digest
+ size_t getOutputLength() const {
+ return (hmac_->output_length());
+ }
+
+ /// @brief Add data to digest
+ ///
+ /// See @ref isc::cryptolink::HMAC::update() for details.
+ void update(const void* data, const size_t len) {
+ try {
+ hmac_->update(static_cast<const Botan::byte*>(data), len);
+ } catch (const Botan::Exception& exc) {
+ isc_throw(LibraryError, "Botan error: " << exc.what());
+ }
+ }
+
+ /// @brief Calculate the final signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::sign() for details.
+ void sign(isc::util::OutputBuffer& result, size_t len) {
+ try {
+ Botan::secure_vector<Botan::byte> b_result(hmac_->final());
+
+ if (len > b_result.size()) {
+ len = b_result.size();
+ }
+ result.writeData(&b_result[0], len);
+ } catch (const Botan::Exception& exc) {
+ isc_throw(LibraryError, "Botan error: " << exc.what());
+ }
+ }
+
+ /// @brief Calculate the final signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::sign() for details.
+ void sign(void* result, size_t len) {
+ try {
+ Botan::secure_vector<Botan::byte> b_result(hmac_->final());
+ size_t output_size = getOutputLength();
+ if (output_size > len) {
+ output_size = len;
+ }
+ std::memcpy(result, &b_result[0], output_size);
+ } catch (const Botan::Exception& exc) {
+ isc_throw(LibraryError, "Botan error: " << exc.what());
+ }
+ }
+
+ /// @brief Calculate the final signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::sign() for details.
+ std::vector<uint8_t> sign(size_t len) {
+ try {
+ Botan::secure_vector<Botan::byte> b_result(hmac_->final());
+ if (len > b_result.size()) {
+ len = b_result.size();
+ }
+ // Return vector with content. Construct &b_result[len] attempts
+ // to get an address of one element beyond the b_result. Replaced
+ // with the address of first element + len
+ return (std::vector<uint8_t>(&b_result[0], &b_result[0]+len));
+ } catch (const Botan::Exception& exc) {
+ isc_throw(LibraryError, "Botan error: " << exc.what());
+ }
+ }
+
+
+ /// @brief Verify an existing signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::verify() for details.
+ bool verify(const void* sig, size_t len) {
+ // Botan's verify_mac checks if len matches the output_length,
+ // which causes it to fail for truncated signatures, so we do
+ // the check ourselves
+ try {
+ size_t size = getOutputLength();
+ if (len < 10 || len < size / 2) {
+ return (false);
+ }
+ if (len > size) {
+ len = size;
+ }
+ if (digest_.size() == 0) {
+ digest_ = hmac_->final();
+ }
+ return (Botan::same_mem(&digest_[0],
+ static_cast<const unsigned char*>(sig),
+ len));
+ } catch (const Botan::Exception& exc) {
+ isc_throw(LibraryError, "Botan error: " << exc.what());
+ }
+ }
+
+private:
+ /// @brief The hash algorithm
+ HashAlgorithm hash_algorithm_;
+
+ /// @brief The protected pointer to the Botan HMAC object
+ boost::scoped_ptr<Botan::HMAC> hmac_;
+
+ /// @brief The digest cache for multiple verify
+ Botan::secure_vector<Botan::byte> digest_;
+};
+
+HMAC::HMAC(const void* secret, size_t secret_length,
+ const HashAlgorithm hash_algorithm)
+{
+ impl_ = new HMACImpl(secret, secret_length, hash_algorithm);
+}
+
+HMAC::~HMAC() {
+ delete impl_;
+}
+
+HashAlgorithm
+HMAC::getHashAlgorithm() const {
+ return (impl_->getHashAlgorithm());
+}
+
+size_t
+HMAC::getOutputLength() const {
+ return (impl_->getOutputLength());
+}
+
+void
+HMAC::update(const void* data, const size_t len) {
+ impl_->update(data, len);
+}
+
+void
+HMAC::sign(isc::util::OutputBuffer& result, size_t len) {
+ impl_->sign(result, len);
+}
+
+void
+HMAC::sign(void* result, size_t len) {
+ impl_->sign(result, len);
+}
+
+std::vector<uint8_t>
+HMAC::sign(size_t len) {
+ return impl_->sign(len);
+}
+
+bool
+HMAC::verify(const void* sig, const size_t len) {
+ return (impl_->verify(sig, len));
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/botan_link.cc b/src/lib/cryptolink/botan_link.cc
new file mode 100644
index 0000000..5eb0e34
--- /dev/null
+++ b/src/lib/cryptolink/botan_link.cc
@@ -0,0 +1,84 @@
+// Copyright (C) 2011-2022 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 <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+#include <cryptolink/crypto_hmac.h>
+#include <cryptolink/crypto_rng.h>
+
+#include <botan/exceptn.h>
+#include <botan/version.h>
+#include <botan/auto_rng.h>
+
+namespace isc {
+namespace cryptolink {
+
+// For Botan, we use the CryptoLink class object in RAII style
+class CryptoLinkImpl {
+ // empty class
+};
+
+CryptoLink::~CryptoLink() {
+}
+
+/// \brief Botan implementation of RNG.
+class RNGImpl : public RNG {
+public:
+ RNGImpl() {
+ rng.reset(new Botan::AutoSeeded_RNG());
+ }
+
+ ~RNGImpl() {
+ }
+
+private:
+ std::vector<uint8_t> random(size_t len) {
+ std::vector<uint8_t> data;
+ if (len > 0) {
+ data.resize(len);
+ try {
+ rng->randomize(&data[0], len);
+ } catch (const Botan::Exception& ex) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "Botan error: " << ex.what());
+ }
+ }
+ return (data);
+ }
+
+ boost::shared_ptr<Botan::RandomNumberGenerator> rng;
+};
+
+void
+CryptoLink::initialize(CryptoLink& c) {
+ if (!c.impl_) {
+ try {
+ c.impl_.reset(new CryptoLinkImpl());
+ } catch (const Botan::Exception& ex) {
+ isc_throw(InitializationError, "Botan error: " << ex.what());
+ }
+ }
+ if (!c.rng_) {
+ try {
+ c.rng_.reset(new RNGImpl());
+ } catch (const Botan::Exception& ex) {
+ isc_throw(InitializationError, "Botan error: " << ex.what());
+ }
+ }
+ // A not yet fixed bug makes RNG to be destroyed after memory pool...
+ atexit([]{ getCryptoLink().getRNG().reset(); });
+}
+
+std::string
+CryptoLink::getVersion() {
+ return (Botan::version_string());
+}
+
+} // namespace cryptolink
+} // namespace isc
+
diff --git a/src/lib/cryptolink/crypto_hash.cc b/src/lib/cryptolink/crypto_hash.cc
new file mode 100644
index 0000000..524ee22
--- /dev/null
+++ b/src/lib/cryptolink/crypto_hash.cc
@@ -0,0 +1,39 @@
+// Copyright (C) 2014-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 <cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cstring>
+
+namespace isc {
+namespace cryptolink {
+
+void
+digest(const void* data, const size_t data_len,
+ const HashAlgorithm hash_algorithm,
+ isc::util::OutputBuffer& result, size_t len)
+{
+ boost::scoped_ptr<Hash> hash(
+ CryptoLink::getCryptoLink().createHash(hash_algorithm));
+ hash->update(data, data_len);
+ if (len == 0) {
+ len = hash->getOutputLength();
+ }
+ hash->final(result, len);
+}
+
+void
+deleteHash(Hash* hash) {
+ delete hash;
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/crypto_hash.h b/src/lib/cryptolink/crypto_hash.h
new file mode 100644
index 0000000..f5ea3fa
--- /dev/null
+++ b/src/lib/cryptolink/crypto_hash.h
@@ -0,0 +1,144 @@
+// Copyright (C) 2014-2017 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 <util/buffer.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <cryptolink/cryptolink.h>
+
+#ifndef ISC_CRYPTO_HASH_H
+#define ISC_CRYPTO_HASH_H
+
+namespace isc {
+namespace cryptolink {
+
+/// Forward declaration, pimpl style
+class HashImpl;
+
+/// \brief Hash support
+///
+/// This class is used to create Hash digests. Instances
+/// can be created with CryptoLink::createHash()
+///
+class Hash : private boost::noncopyable {
+private:
+ /// \brief Constructor from a hash algorithm
+ ///
+ /// \exception UnsupportedAlgorithmException if the given algorithm
+ /// is unknown or not supported by the underlying library
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param hash_algorithm The hash algorithm
+ Hash(const HashAlgorithm hash_algorithm);
+
+ friend Hash* CryptoLink::createHash(const HashAlgorithm);
+
+public:
+ /// \brief Destructor
+ ~Hash();
+
+ /// \brief Returns the HashAlgorithm of the object
+ ///
+ /// \return hash algorithm
+ HashAlgorithm getHashAlgorithm() const;
+
+ /// \brief Returns the output size of the digest
+ ///
+ /// \return output size of the digest
+ size_t getOutputLength() const;
+
+ /// \brief Add data to digest
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param data The data to add
+ /// \param len The size of the data
+ void update(const void* data, const size_t len);
+
+ /// \brief Calculate the final digest
+ ///
+ /// The result will be appended to the given outputbuffer
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param result The OutputBuffer to append the result to
+ /// \param len The number of bytes from the result to copy. If this
+ /// value is smaller than the algorithms output size, the
+ /// result will be truncated. If this value is larger,
+ /// only output size bytes will be copied
+ void final(isc::util::OutputBuffer& result, size_t len);
+
+ /// \brief Calculate the final digest
+ ///
+ /// len bytes of data from the result will be copied to *result
+ /// If len is larger than the output size, only output_size bytes
+ /// will be copied. If it is smaller, the output will be truncated
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// At least len bytes of data must be available for writing at
+ /// result.
+ ///
+ /// \param result The memory location the digest will be written to
+ /// \param len Specifies the size of the result location available
+ void final(void* result, size_t len);
+
+ /// \brief Calculate the final digest
+ ///
+ /// The result will be returned as a std::vector<uint8_t>
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param len The number of bytes from the result to copy. If this
+ /// value is smaller than the algorithms output size, the
+ /// result will be truncated. If this value is larger,
+ /// only output size bytes will be copied
+ /// \return a vector containing the signature
+ std::vector<uint8_t> final(size_t len);
+
+private:
+ HashImpl* impl_;
+};
+
+/// \brief Create an Hash digest for the given data
+///
+/// This is a convenience function that calculates the hash digest,
+/// given a fixed amount of data. Internally it does the same as
+/// creating an Hash object, feeding it the data, and calculating the
+/// resulting digest.
+///
+/// \exception UnsupportedAlgorithm if the given algorithm is unknown
+/// or not supported by the underlying library
+/// \exception LibraryError if there was any unexpected exception
+/// in the underlying library
+///
+/// \param data The data to digest
+/// \param data_len The length of the data
+/// \param hash_algorithm The hash algorithm
+/// \param result The digest will be appended to this buffer
+/// \param len If this is non-zero and less than the output size, the result
+/// will be truncated to len bytes. If greater than output size
+/// (or equal to zero) only output size bytes are written
+void digest(const void* data,
+ const size_t data_len,
+ const HashAlgorithm hash_algorithm,
+ isc::util::OutputBuffer& result,
+ size_t len = 0);
+
+/// \brief Delete an Hash object
+void deleteHash(Hash* hash);
+
+} // namespace cryptolink
+} // namespace isc
+
+#endif // ISC_CRYPTO_HASH_H
+
diff --git a/src/lib/cryptolink/crypto_hmac.cc b/src/lib/cryptolink/crypto_hmac.cc
new file mode 100644
index 0000000..23ce242
--- /dev/null
+++ b/src/lib/cryptolink/crypto_hmac.cc
@@ -0,0 +1,59 @@
+// 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 <cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cstring>
+
+namespace isc {
+namespace cryptolink {
+
+void
+signHMAC(const void* data, const size_t data_len, const void* secret,
+ size_t secret_len, const HashAlgorithm hash_algorithm,
+ isc::util::OutputBuffer& result, size_t len)
+{
+ boost::scoped_ptr<HMAC> hmac(
+ CryptoLink::getCryptoLink().createHMAC(secret,
+ secret_len,
+ hash_algorithm));
+ hmac->update(data, data_len);
+ if (len == 0) {
+ len = hmac->getOutputLength();
+ }
+ hmac->sign(result, len);
+}
+
+
+bool
+verifyHMAC(const void* data, const size_t data_len, const void* secret,
+ size_t secret_len, const HashAlgorithm hash_algorithm,
+ const void* sig, const size_t sig_len)
+{
+ boost::scoped_ptr<HMAC> hmac(
+ CryptoLink::getCryptoLink().createHMAC(secret,
+ secret_len,
+ hash_algorithm));
+ hmac->update(data, data_len);
+ size_t len = sig_len;
+ if (len == 0) {
+ len = hmac->getOutputLength();
+ }
+ return (hmac->verify(sig, len));
+}
+
+void
+deleteHMAC(HMAC* hmac) {
+ delete hmac;
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/crypto_hmac.h b/src/lib/cryptolink/crypto_hmac.h
new file mode 100644
index 0000000..5c7bffe
--- /dev/null
+++ b/src/lib/cryptolink/crypto_hmac.h
@@ -0,0 +1,213 @@
+// Copyright (C) 2011-2017 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 <util/buffer.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <cryptolink/cryptolink.h>
+
+#ifndef ISC_CRYPTO_HMAC_H
+#define ISC_CRYPTO_HMAC_H
+
+namespace isc {
+namespace cryptolink {
+
+/// Forward declaration, pimpl style
+class HMACImpl;
+
+/// \brief HMAC support
+///
+/// This class is used to create and verify HMAC signatures. Instances
+/// can be created with CryptoLink::createHMAC()
+///
+class HMAC : private boost::noncopyable {
+private:
+ /// \brief Constructor from a secret and a hash algorithm
+ ///
+ /// \exception UnsupportedAlgorithmException if the given algorithm
+ /// is unknown or not supported by the underlying library
+ /// \exception InvalidKeyLength if the given key secret_len is bad
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// Notes: if the secret is longer than the block size of its
+ /// algorithm, the constructor will run it through the hash
+ /// algorithm, and use the digest as the secret for this HMAC
+ /// operation
+ ///
+ /// \param secret The secret to sign with
+ /// \param len The length of the secret
+ /// \param hash_algorithm The hash algorithm
+ HMAC(const void* secret, size_t secret_len,
+ const HashAlgorithm hash_algorithm);
+
+ friend HMAC* CryptoLink::createHMAC(const void*, size_t,
+ const HashAlgorithm);
+
+public:
+ /// \brief Destructor
+ ~HMAC();
+
+ /// \brief Returns the HashAlgorithm of the object
+ ///
+ /// \return hash algorithm
+ HashAlgorithm getHashAlgorithm() const;
+
+ /// \brief Returns the output size of the digest
+ ///
+ /// \return output size of the digest
+ size_t getOutputLength() const;
+
+ /// \brief Add data to digest
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param data The data to add
+ /// \param len The size of the data
+ void update(const void* data, const size_t len);
+
+ /// \brief Calculate the final signature
+ ///
+ /// The result will be appended to the given outputbuffer
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param result The OutputBuffer to append the result to
+ /// \param len The number of bytes from the result to copy. If this
+ /// value is smaller than the algorithms output size, the
+ /// result will be truncated. If this value is larger,
+ /// only output size bytes will be copied
+ void sign(isc::util::OutputBuffer& result, size_t len);
+
+ /// \brief Calculate the final signature
+ ///
+ /// len bytes of data from the result will be copied to *result
+ /// If len is larger than the output size, only output_size bytes
+ /// will be copied. If it is smaller, the output will be truncated
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// At least len bytes of data must be available for writing at
+ /// result
+ void sign(void* result, size_t len);
+
+ /// \brief Calculate the final signature
+ ///
+ /// The result will be returned as a std::vector<uint8_t>
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param len The number of bytes from the result to copy. If this
+ /// value is smaller than the algorithms output size, the
+ /// result will be truncated. If this value is larger,
+ /// only output size bytes will be copied
+ /// \return a vector containing the signature
+ std::vector<uint8_t> sign(size_t len);
+
+ /// \brief Verify an existing signature
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param sig The signature to verify
+ /// \param len The length of the signature. If this is smaller
+ /// than the output length of the algorithm,
+ /// only len bytes will be checked. If this is
+ /// larger than the output length of the algorithm,
+ /// only output size bytes will be checked
+ /// \return true if the signature is correct, false otherwise
+ ///
+ /// \note verify() does not destroy its context so it can be
+ /// called multiple times with different signatures.
+ bool verify(const void* sig, size_t len);
+
+private:
+ HMACImpl* impl_;
+};
+
+/// \brief Create an HMAC signature for the given data
+///
+/// This is a convenience function that calculates the HMAC signature,
+/// given a fixed amount of data. Internally it does the same as
+/// creating an HMAC object, feeding it the data, and calculating the
+/// resulting signature.
+///
+/// \exception UnsupportedAlgorithm if the given algorithm is unknown
+/// or not supported by the underlying library
+/// \exception BadKey if the given key secret_len is bad
+/// \exception LibraryError if there was any unexpected exception
+/// in the underlying library
+///
+/// Notes: if the secret is longer than the block size of its
+/// algorithm, the constructor will run it through the hash
+/// algorithm, and use the digest as the secret for this HMAC
+/// operation
+///
+/// \param data The data to sign
+/// \param data_len The length of the data
+/// \param secret The secret to sign with
+/// \param secret_len The length of the secret
+/// \param hash_algorithm The hash algorithm
+/// \param result The signature will be appended to this buffer
+/// \param len If this is non-zero and less than the output size, the result
+/// will be truncated to len bytes. If greater than output size
+/// (or equal to zero) only output size bytes are written
+void signHMAC(const void* data,
+ const size_t data_len,
+ const void* secret,
+ size_t secret_len,
+ const HashAlgorithm hash_algorithm,
+ isc::util::OutputBuffer& result,
+ size_t len = 0);
+
+/// \brief Verify an HMAC signature for the given data
+///
+/// This is a convenience function that verifies an hmac signature,
+/// given a fixed amount of data. Internally it does the same as
+/// creating an HMAC object, feeding it the data, and checking the
+/// resulting signature at the exception a zero sig_len is
+/// internally replaced by the output size.
+///
+/// \exception UnsupportedAlgorithm if the given algorithm is unknown
+/// or not supported by the underlying library
+/// \exception BadKey if the given key secret_len is bad
+/// \exception LibraryError if there was any unexpected exception
+/// in the underlying library
+///
+/// Notes: if the secret is longer than the block size of its
+/// algorithm, the constructor will run it through the hash
+/// algorithm, and use the digest as the secret for this HMAC
+/// operation
+///
+/// \param data The data to verify
+/// \param data_len The length of the data
+/// \param secret The secret to sign with
+/// \param secret_len The length of the secret
+/// \param hash_algorithm The hash algorithm
+/// \param sig The signature to verify
+/// \param sig_len The length of the signature
+/// \return True if the signature verifies, false if not
+bool verifyHMAC(const void* data,
+ const size_t data_len,
+ const void* secret,
+ size_t secret_len,
+ const HashAlgorithm hash_algorithm,
+ const void* sig,
+ const size_t sig_len);
+
+/// \brief Delete an HMAC object
+void deleteHMAC(HMAC* hmac);
+
+} // namespace cryptolink
+} // namespace isc
+
+#endif // ISC_CRYPTO_HMAC_H
+
diff --git a/src/lib/cryptolink/crypto_rng.cc b/src/lib/cryptolink/crypto_rng.cc
new file mode 100644
index 0000000..54dacce
--- /dev/null
+++ b/src/lib/cryptolink/crypto_rng.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2018 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 <cryptolink.h>
+#include <cryptolink/crypto_rng.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cstring>
+
+namespace isc {
+namespace cryptolink {
+
+RNG::RNG() {
+}
+
+RNG::~RNG() {
+}
+
+std::vector<uint8_t>
+random(size_t len)
+{
+ RNGPtr rng(CryptoLink::getCryptoLink().getRNG());
+ return (rng->random(len));
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/crypto_rng.h b/src/lib/cryptolink/crypto_rng.h
new file mode 100644
index 0000000..916321e
--- /dev/null
+++ b/src/lib/cryptolink/crypto_rng.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2018 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 <boost/noncopyable.hpp>
+
+#include <cryptolink/cryptolink.h>
+
+#ifndef ISC_CRYPTO_RNG_H
+#define ISC_CRYPTO_RNG_H
+
+namespace isc {
+namespace cryptolink {
+
+/// \brief RNG support
+///
+/// This class is used to get the RNG.
+/// The global instance can be get with CryptoLink::getRNG()
+///
+class RNG : private boost::noncopyable {
+public:
+ /// \brief Constructor from a Random Number Generator
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ RNG();
+
+ /// \brief Destructor
+ virtual ~RNG();
+
+ /// \brief Generate random value.
+ ///
+ /// The result will be returned as a std::vector<uint8_t>
+ ///
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param len The number of bytes from the result to generate.
+ /// \return a vector containing random value.
+ virtual std::vector<uint8_t> random(size_t len) = 0;
+
+private:
+ friend RNGPtr& CryptoLink::getRNG();
+};
+
+/// \brief Generate random value.
+///
+/// This is a convenience function that generate random data
+/// given a fixed amount of data. Internally it does the same as
+/// creating an RNG object and generating the resulting value.
+///
+/// \exception LibraryError if there was any unexpected exception
+/// in the underlying library
+///
+/// \param len The length of the data
+std::vector<uint8_t> random(size_t len);
+
+} // namespace cryptolink
+} // namespace isc
+
+#endif // ISC_CRYPTO_RNG_H
+
diff --git a/src/lib/cryptolink/cryptolink.cc b/src/lib/cryptolink/cryptolink.cc
new file mode 100644
index 0000000..3e238b2
--- /dev/null
+++ b/src/lib/cryptolink/cryptolink.cc
@@ -0,0 +1,40 @@
+// Copyright (C) 2011-2022 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 <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+#include <cryptolink/crypto_hmac.h>
+
+namespace isc {
+namespace cryptolink {
+
+CryptoLink&
+CryptoLink::getCryptoLink() {
+ static CryptoLink instance;
+ return (instance);
+}
+
+Hash*
+CryptoLink::createHash(const HashAlgorithm hash_algorithm) {
+ return (new Hash(hash_algorithm));
+}
+
+HMAC*
+CryptoLink::createHMAC(const void* secret, size_t secret_len,
+ const HashAlgorithm hash_algorithm) {
+ return (new HMAC(secret, secret_len, hash_algorithm));
+}
+
+RNGPtr&
+CryptoLink::getRNG() {
+ return (rng_);
+}
+
+} // namespace cryptolink
+} // namespace isc
+
diff --git a/src/lib/cryptolink/cryptolink.h b/src/lib/cryptolink/cryptolink.h
new file mode 100644
index 0000000..366fa5b
--- /dev/null
+++ b/src/lib/cryptolink/cryptolink.h
@@ -0,0 +1,248 @@
+// Copyright (C) 2011-2022 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 ISC_CRYPTO_H
+#define ISC_CRYPTO_H
+
+#include <string>
+#include <util/buffer.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <memory>
+
+namespace isc {
+namespace cryptolink {
+
+/// \brief Hash algorithm identifiers
+enum HashAlgorithm {
+ UNKNOWN_HASH = 0, ///< This value can be used in conversion
+ /// functions, to be returned when the
+ /// input is unknown (but a value MUST be
+ /// returned), for instance when the input
+ /// is a Name or a string, and the return
+ /// value is a HashAlgorithm.
+ MD5 = 1, ///< MD5
+ SHA1 = 2, ///< SHA-1
+ SHA256 = 3, ///< SHA-256
+ SHA224 = 4, ///< SHA-224
+ SHA384 = 5, ///< SHA-384
+ SHA512 = 6 ///< SHA-512
+
+};
+
+/// \brief Forward declaration for createHash()
+class Hash;
+
+/// \brief Forward declaration for createHMAC()
+class HMAC;
+
+/// \brief Forward declaration for getRNG()
+class RNG;
+
+/// \brief Type representing the pointer to the RNG.
+typedef boost::shared_ptr<RNG> RNGPtr;
+
+/// General exception class that is the base for all crypto-related
+/// exceptions
+class CryptoLinkError : public Exception {
+public:
+ CryptoLinkError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// This exception is thrown if there was a problem initializing the
+/// crypto library
+class InitializationError : public CryptoLinkError {
+public:
+ InitializationError(const char* file, size_t line, const char* what) :
+ CryptoLinkError(file, line, what) {}
+};
+
+/// This exception is thrown when a cryptographic action is requested
+/// for an algorithm that is not supported by the underlying library.
+class UnsupportedAlgorithm : public CryptoLinkError {
+public:
+ UnsupportedAlgorithm(const char* file, size_t line, const char* what) :
+ CryptoLinkError(file, line, what) {}
+};
+
+/// This exception is thrown when the underlying library could not
+/// handle the key data.
+class BadKey : public CryptoLinkError {
+public:
+ BadKey(const char* file, size_t line, const char* what) :
+ CryptoLinkError(file, line, what) {}
+};
+
+/// This exception is raised when a general error that was not
+/// specifically caught is thrown by the underlying library. It
+/// is replaced by this one so as not have 'external' exceptions
+/// bubbling up
+class LibraryError : public CryptoLinkError {
+public:
+ LibraryError(const char* file, size_t line, const char* what) :
+ CryptoLinkError(file, line, what) {}
+};
+
+/// \brief Forward declarations for CryptoLink pimpl.
+class CryptoLinkImpl;
+
+/// \brief Type representing the pointer to the CryptoLinkImpl.
+typedef boost::shared_ptr<CryptoLinkImpl> CryptoLinkImplPtr;
+
+/// \brief Forward declarations for RNG pimpl.
+class RNGImpl;
+
+/// \brief Singleton entry point and factory class
+///
+/// This is a singleton class that serves as the entry point to
+/// the underlying cryptography library, and as a factory for objects
+/// within the cryptolink library.
+///
+/// There is only one way to access it, through getCryptoLink(), which
+/// returns a reference to the initialized library. On the first call,
+/// it will be initialized automatically.
+///
+/// In order for the CryptoLink library to be sure that the underlying
+/// library has been initialized, and because we do not want to add
+/// such a check to every class and function within it, we have made
+/// the constructors of all classes within cryptolink private. This way
+/// a caller cannot instantiate an object before the library is
+/// initialized, but must use CryptoLink's create method (e.g.
+/// createHMAC()), which enforces (automatic) initialization.
+///
+/// In order for the CryptoLink class to be able to create objects that
+/// have private constructors, it is declared a friend class of these
+/// classes.
+///
+/// Since these factory functions return bare pointers, we also provide
+/// deleter functions for them (e.g. deleteHMAC()), so that a caller
+/// can use that to make sure it uses the correct delete operator (the
+/// one defined at compilation time of this library). A way to make
+/// sure you do not forget this, is to place the result of the create
+/// functions in a shared_ptr with the corresponding deleter function.
+///
+/// \note All other classes within cryptolink should have private
+/// constructors as well, and should have a factory function from
+/// CryptoLink, and a deleter function.
+///
+// Internal note: we can use this class later to initialize and manage
+// dynamic (PKCS#11) libs
+class CryptoLink : private boost::noncopyable {
+public:
+ /// \brief Returns a reference to the singleton instance
+ ///
+ /// If the library has not been initialized yet, it will be
+ /// initialized with some default values.
+ ///
+ /// Since this class is noncopyable, you must use the return
+ /// value directly, or store it in a reference variable.
+ ///
+ /// \exception InitializationError if initialization fails
+ ///
+ /// \return Reference to the singleton instance
+ static CryptoLink& getCryptoLink();
+
+ /// \brief Get version string
+ static std::string getVersion();
+
+ /// \brief Factory function for Hash objects
+ ///
+ /// CryptoLink objects cannot be constructed directly. This
+ /// function creates a new Hash object usable for signing or
+ /// verification.
+ ///
+ /// The caller is responsible for deleting the object, and it is
+ /// therefore highly recommended to place the return value of this
+ /// function in a scoped_ptr or shared_ptr.
+ ///
+ /// If you want to safely delete objects created with this method,
+ /// you can use the function deleteHash() as defined in
+ /// crypto_hash.h
+ ///
+ /// \exception UnsupportedAlgorithmException if the given algorithm
+ /// is unknown or not supported by the underlying library
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param hash_algorithm The hash algorithm
+ Hash* createHash(const HashAlgorithm hash_algorithm);
+
+ /// \brief Factory function for HMAC objects
+ ///
+ /// CryptoLink objects cannot be constructed directly. This
+ /// function creates a new HMAC object usable for signing or
+ /// verification.
+ ///
+ /// The caller is responsible for deleting the object, and it is
+ /// therefore highly recommended to place the return value of this
+ /// function in a scoped_ptr or shared_ptr.
+ ///
+ /// Notes: if the secret is longer than the block size of its
+ /// algorithm, the constructor will run it through the hash
+ /// algorithm, and use the digest as the secret for this HMAC
+ /// operation
+ ///
+ /// If you want to safely delete objects created with this method,
+ /// you can use the function deleteHMAC() as defined in
+ /// crypto_hmac.h
+ ///
+ /// \exception UnsupportedAlgorithmException if the given algorithm
+ /// is unknown or not supported by the underlying library
+ /// \exception InvalidKeyLength if the given key secret_len is bad
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ ///
+ /// \param secret The secret to sign with
+ /// \param secret_len The length of the secret
+ /// \param hash_algorithm The hash algorithm
+ HMAC* createHMAC(const void* secret, size_t secret_len,
+ const HashAlgorithm hash_algorithm);
+
+ /// \brief Get the global RNG
+ ///
+ /// \exception NotImplemented if the method was not implemented
+ /// in a derived class
+ /// \exception LibraryError if there was any unexpected exception
+ /// in the underlying library
+ virtual RNGPtr& getRNG();
+
+private:
+ /// \brief Initialize the library
+ ///
+ /// If the library has already been initialized (either by a call
+ /// to initialize() or automatically in getCryptoLink()), this
+ /// function does nothing.
+ ///
+ /// \note A call to initialize() is not strictly necessary with
+ /// the current implementation.
+ ///
+ /// \exception InitializationError if initialization fails
+ ///
+ /// \param c the CryptoLink singleton instance which is being initialized.
+ void initialize(CryptoLink& c);
+
+ // To prevent people constructing their own, we make the constructor
+ // private too.
+ CryptoLink() {
+ initialize(*this);
+ }
+ ~CryptoLink();
+
+ /// \brief Smart pointer holding the implementation.
+ CryptoLinkImplPtr impl_;
+
+ /// \brief Smart pointer holding the RNG.
+ RNGPtr rng_;
+};
+
+} // namespace cryptolink
+} // namespace isc
+
+#endif // ISC_CRYPTO_H
diff --git a/src/lib/cryptolink/openssl_common.h b/src/lib/cryptolink/openssl_common.h
new file mode 100644
index 0000000..8a6381c
--- /dev/null
+++ b/src/lib/cryptolink/openssl_common.h
@@ -0,0 +1,109 @@
+// Copyright (C) 2014-2022 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/.
+
+namespace isc {
+namespace cryptolink {
+namespace ossl {
+
+/// @brief Decode the HashAlgorithm enum into an EVP_MD pointer (or 0)
+///
+/// EVP_MD pointer is a OpenSSL's way of identifying hash algorithms
+/// @param algorithm algorithm to be converted
+/// @return pointer to a static EVP_MD which identifies the algorithm
+const EVP_MD*
+getHashAlgorithm(isc::cryptolink::HashAlgorithm algorithm);
+
+/// Secure Buffers which are wiped out when released.
+/// Subset of the std::vector interface but not derived from
+/// to avoid unwanted inheritance.
+template<typename T>
+class SecBuf {
+public:
+ typedef typename std::vector<T>::iterator iterator;
+
+ typedef typename std::vector<T>::const_iterator const_iterator;
+
+ explicit SecBuf() : vec_() {}
+
+ explicit SecBuf(size_t n, const T& value = T()) : vec_(n, value) {}
+
+ SecBuf(iterator first, iterator last) : vec_(first, last) {}
+
+ SecBuf(const_iterator first, const_iterator last) : vec_(first, last) {}
+
+ SecBuf(const std::vector<T>& x) : vec_(x) {}
+
+ ~SecBuf() {
+ // Resize to its largest capacity and fill the whole memory with zeros.
+ vec_.resize(vec_.capacity());
+ std::fill(vec_.begin(), vec_.end(), 0);
+ };
+
+ iterator begin() {
+ return (vec_.begin());
+ };
+
+ const_iterator begin() const {
+ return (vec_.begin());
+ };
+
+ iterator end() {
+ return (vec_.end());
+ };
+
+ const_iterator end() const {
+ return (vec_.end());
+ };
+
+ size_t size() const {
+ return (vec_.size());
+ };
+
+ void resize(size_t sz) {
+ vec_.resize(sz);
+ };
+
+ void clear() {
+ // Resize to its largest capacity and fill the whole memory with zeros.
+ vec_.resize(vec_.capacity());
+ std::fill(vec_.begin(), vec_.end(), 0);
+
+ // Remove all elements.
+ vec_.clear();
+ }
+
+ SecBuf& operator=(const SecBuf& x) {
+ if (&x != *this) {
+ vec_ = x.vec_;
+ }
+ return (*this);
+ };
+
+ T& operator[](size_t n) {
+ return (vec_[n]);
+ };
+
+ const T& operator[](size_t n) const {
+ return (vec_[n]);
+ };
+
+ // constant time comparison against timing attacks
+ // (same type than XXX::verify() so const void* (vs. const T*) x)
+ bool same(const void* x, size_t len) const {
+ bool ret = true;
+ const T* p = static_cast<const T*>(x);
+ for (size_t i = 0; i < len; ++i)
+ ret = ret && (vec_[i] == p[i]);
+ return ret;
+ };
+
+private:
+ std::vector<T> vec_;
+};
+
+} // namespace ossl
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/openssl_compat.h b/src/lib/cryptolink/openssl_compat.h
new file mode 100644
index 0000000..33b85e1
--- /dev/null
+++ b/src/lib/cryptolink/openssl_compat.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2016-2022 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 file is included by hash and hmac codes so KEA_H* macros
+// avoid to define unused inlines.
+
+#ifdef KEA_HASH
+
+#ifndef HAVE_EVP_MD_CTX_NEW
+#ifdef HAVE_EVP_MD_CTX_CREATE
+
+// EVP_MD_CTX_new() is EVP_MD_CTX_create() in old OpenSSL
+
+inline EVP_MD_CTX* EVP_MD_CTX_new() {
+ return (EVP_MD_CTX_create());
+}
+
+#else
+#error have no EVP_MD_CTX_new() nor EVP_MD_CTX_create()
+#endif
+#endif
+
+#ifndef HAVE_EVP_MD_CTX_FREE
+#ifdef HAVE_EVP_MD_CTX_DESTROY
+
+// EVP_MD_CTX_free(ctx) is EVP_MD_CTX_destroy(ctx) in old OpenSSL
+
+inline void EVP_MD_CTX_free(EVP_MD_CTX* ctx) {
+ EVP_MD_CTX_destroy(ctx);
+}
+
+#else
+#error have no EVP_MD_CTX_free() nor EVP_MD_CTX_destroy()
+#endif
+#endif
+
+#endif
+
+#ifdef KEA_HMAC
+
+#ifndef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
+#ifdef HAVE_EVP_PKEY_NEW_MAC_KEY
+
+// EVP_PKEY_new_raw_private_key(type, e, key, keylen) is
+// EVP_PKEY_new_mac_key(type, e, key, (int)keylen) in old OpenSSL
+
+inline EVP_PKEY* EVP_PKEY_new_raw_private_key(int type, ENGINE* e,
+ const unsigned char *key,
+ size_t keylen) {
+ return (EVP_PKEY_new_mac_key(type, e, key, static_cast<int>(keylen)));
+}
+
+#else
+#error have no EVP_PKEY_new_raw_private_key() nor EVP_PKEY_new_mac_key()
+#endif
+#endif
+
+#endif
diff --git a/src/lib/cryptolink/openssl_hash.cc b/src/lib/cryptolink/openssl_hash.cc
new file mode 100644
index 0000000..68f2285
--- /dev/null
+++ b/src/lib/cryptolink/openssl_hash.cc
@@ -0,0 +1,188 @@
+// Copyright (C) 2014-2020 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 <cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <openssl/evp.h>
+
+#include <cryptolink/openssl_common.h>
+#define KEA_HASH
+#include <cryptolink/openssl_compat.h>
+
+#include <cstring>
+
+namespace isc {
+namespace cryptolink {
+
+const EVP_MD*
+ossl::getHashAlgorithm(HashAlgorithm algorithm) {
+ switch (algorithm) {
+ case isc::cryptolink::MD5:
+ return (EVP_md5());
+ case isc::cryptolink::SHA1:
+ return (EVP_sha1());
+ case isc::cryptolink::SHA256:
+ return (EVP_sha256());
+ case isc::cryptolink::SHA224:
+ return (EVP_sha224());
+ case isc::cryptolink::SHA384:
+ return (EVP_sha384());
+ case isc::cryptolink::SHA512:
+ return (EVP_sha512());
+ case isc::cryptolink::UNKNOWN_HASH:
+ return (0);
+ }
+ // compiler should have prevented us to reach this, since we have
+ // no default. But we need a return value anyway
+ return (0);
+}
+
+/// \brief OpenSSL implementation of Hash. Each method is the counterpart
+/// of the Hash corresponding method.
+class HashImpl {
+public:
+
+ /// @brief Constructor for specific hash algorithm
+ ///
+ /// @param hash_algorithm The hash algorithm
+ explicit HashImpl(const HashAlgorithm hash_algorithm)
+ : hash_algorithm_(hash_algorithm), md_(0) {
+ const EVP_MD* algo = ossl::getHashAlgorithm(hash_algorithm);
+ if (algo == 0) {
+ isc_throw(isc::cryptolink::UnsupportedAlgorithm,
+ "Unknown hash algorithm: " <<
+ static_cast<int>(hash_algorithm));
+ }
+
+ md_ = EVP_MD_CTX_new();
+ if (md_ == 0) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "OpenSSL EVP_MD_CTX_new() failed");
+ }
+
+ EVP_DigestInit_ex(md_, algo, NULL);
+ }
+
+ /// @brief Destructor
+ ~HashImpl() {
+ if (md_) {
+ EVP_MD_CTX_free(md_);
+ }
+ md_ = 0;
+ }
+
+ /// @brief Returns the HashAlgorithm of the object
+ HashAlgorithm getHashAlgorithm() const {
+ return (hash_algorithm_);
+ }
+
+ /// @brief Returns the output size of the digest
+ ///
+ /// @return output size of the digest
+ size_t getOutputLength() const {
+ return (EVP_MD_CTX_size(md_));
+ }
+
+ /// @brief Adds data to the digest
+ ///
+ /// See @ref isc::cryptolink::Hash::update() for details.
+ void update(const void* data, const size_t len) {
+ EVP_DigestUpdate(md_, data, len);
+ }
+
+ /// @brief Calculate the final digest
+ ///
+ /// See @ref isc::cryptolink::Hash::final() for details.
+ void final(isc::util::OutputBuffer& result, size_t len) {
+ size_t size = getOutputLength();
+ std::vector<unsigned char> digest(size);
+ EVP_DigestFinal_ex(md_, &digest[0], NULL);
+ if (len > size) {
+ len = size;
+ }
+ result.writeData(&digest[0], len);
+ }
+
+ /// @brief Calculate the final digest
+ ///
+ /// See @ref isc::cryptolink::Hash::final() for details.
+ void final(void* result, size_t len) {
+ size_t size = getOutputLength();
+ std::vector<unsigned char> digest(size);
+ EVP_DigestFinal_ex(md_, &digest[0], NULL);
+ if (len > size) {
+ len = size;
+ }
+ std::memcpy(result, &digest[0], len);
+ }
+
+ /// @brief Calculate the final digest
+ ///
+ /// See @ref isc::cryptolink::Hash::final() for details.
+ std::vector<uint8_t> final(size_t len) {
+ size_t size = getOutputLength();
+ std::vector<unsigned char> digest(size);
+ EVP_DigestFinal_ex(md_, &digest[0], NULL);
+ if (len < size) {
+ digest.resize(len);
+ }
+ return (std::vector<uint8_t>(digest.begin(), digest.end()));
+ }
+
+private:
+ /// @brief The hash algorithm
+ HashAlgorithm hash_algorithm_;
+
+ /// @brief The pointer to the OpenSSL EVP_MD_CTX structure
+ EVP_MD_CTX* md_;
+};
+
+Hash::Hash(const HashAlgorithm hash_algorithm)
+{
+ impl_ = new HashImpl(hash_algorithm);
+}
+
+Hash::~Hash() {
+ delete impl_;
+}
+
+HashAlgorithm
+Hash::getHashAlgorithm() const {
+ return (impl_->getHashAlgorithm());
+}
+
+size_t
+Hash::getOutputLength() const {
+ return (impl_->getOutputLength());
+}
+
+void
+Hash::update(const void* data, const size_t len) {
+ impl_->update(data, len);
+}
+
+void
+Hash::final(isc::util::OutputBuffer& result, size_t len) {
+ impl_->final(result, len);
+}
+
+void
+Hash::final(void* result, size_t len) {
+ impl_->final(result, len);
+}
+
+std::vector<uint8_t>
+Hash::final(size_t len) {
+ return impl_->final(len);
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/openssl_hmac.cc b/src/lib/cryptolink/openssl_hmac.cc
new file mode 100644
index 0000000..de609fe
--- /dev/null
+++ b/src/lib/cryptolink/openssl_hmac.cc
@@ -0,0 +1,245 @@
+// Copyright (C) 2014-2022 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 <cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <openssl/evp.h>
+
+#include <cryptolink/openssl_common.h>
+#define KEA_HASH
+#define KEA_HMAC
+#include <cryptolink/openssl_compat.h>
+
+#include <cstring>
+
+namespace isc {
+namespace cryptolink {
+
+/// @brief OpenSSL implementation of HMAC. Each method is the counterpart
+/// of the HMAC corresponding method.
+class HMACImpl {
+public:
+ /// @brief Constructor from a secret and a hash algorithm
+ ///
+ /// See constructor of the @ref isc::cryptolink::HMAC class for details.
+ ///
+ /// @param secret The secret to sign with
+ /// @param secret_len The length of the secret
+ /// @param hash_algorithm The hash algorithm
+ explicit HMACImpl(const void* secret, size_t secret_len,
+ const HashAlgorithm hash_algorithm)
+ : hash_algorithm_(hash_algorithm), md_(), digest_() {
+ const EVP_MD* algo = ossl::getHashAlgorithm(hash_algorithm);
+ if (algo == 0) {
+ isc_throw(UnsupportedAlgorithm,
+ "Unknown hash algorithm: " <<
+ static_cast<int>(hash_algorithm));
+ }
+ if (secret_len == 0) {
+ isc_throw(BadKey, "Bad HMAC secret length: 0");
+ }
+
+ md_ = EVP_MD_CTX_new();
+ if (md_ == 0) {
+ isc_throw(LibraryError, "OpenSSL EVP_MD_CTX_new() failed");
+ }
+
+ EVP_PKEY* pkey =
+ EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL,
+ reinterpret_cast<const unsigned char*>(secret),
+ secret_len);
+
+ if (pkey == 0) {
+ isc_throw(LibraryError,
+ "OpenSSL EVP_PKEY_new_raw_private_key() failed");
+ }
+
+ if (!EVP_DigestSignInit(md_, NULL, algo, NULL, pkey)) {
+ EVP_PKEY_free(pkey);
+ isc_throw(LibraryError, "OpenSSL EVP_DigestSignInit() failed");
+ }
+
+ EVP_PKEY_free(pkey);
+ }
+
+ /// @brief Destructor
+ ~HMACImpl() {
+ if (md_) {
+ EVP_MD_CTX_free(md_);
+ }
+ md_ = 0;
+ }
+
+ /// @brief Returns the HashAlgorithm of the object
+ HashAlgorithm getHashAlgorithm() const {
+ return (hash_algorithm_);
+ }
+
+ /// @brief Returns the output size of the digest
+ ///
+ /// @return output size of the digest
+ size_t getOutputLength() const {
+ return (EVP_MD_CTX_size(md_));
+ }
+
+ /// @brief Add data to digest
+ ///
+ /// See @ref isc::cryptolink::HMAC::update() for details.
+ void update(const void* data, const size_t len) {
+ if (len == 0) {
+ return;
+ }
+
+ if (!EVP_DigestSignUpdate(md_, data, len)) {
+ isc_throw(LibraryError, "OpenSSL EVP_DigestSignUpdate() failed");
+ }
+ }
+
+ /// @brief Calculate the final signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::sign() for details.
+ void sign(isc::util::OutputBuffer& result, size_t len) {
+ size_t size = getOutputLength();
+ ossl::SecBuf<unsigned char> digest(size);
+ size_t digest_len = size;
+ if (!EVP_DigestSignFinal(md_, &digest[0], &digest_len)) {
+ isc_throw(LibraryError, "OpenSSL EVP_DigestSignFinal() failed");
+ }
+ if (digest_len != size) {
+ isc_throw(LibraryError, "OpenSSL partial EVP_DigestSignFinal()");
+ }
+ if (len > size) {
+ len = size;
+ }
+ result.writeData(&digest[0], len);
+ }
+
+ /// @brief Calculate the final signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::sign() for details.
+ void sign(void* result, size_t len) {
+ size_t size = getOutputLength();
+ ossl::SecBuf<unsigned char> digest(size);
+ size_t digest_len = size;
+ if (!EVP_DigestSignFinal(md_, &digest[0], &digest_len)) {
+ isc_throw(LibraryError, "OpenSSL EVP_DigestSignFinal() failed");
+ }
+ if (digest_len != size) {
+ isc_throw(LibraryError, "OpenSSL partial EVP_DigestSignFinal()");
+ }
+ if (len > size) {
+ len = size;
+ }
+ std::memcpy(result, &digest[0], len);
+ }
+
+ /// @brief Calculate the final signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::sign() for details.
+ std::vector<uint8_t> sign(size_t len) {
+ size_t size = getOutputLength();
+ ossl::SecBuf<unsigned char> digest(size);
+ size_t digest_len = size;
+ if (!EVP_DigestSignFinal(md_, &digest[0], &digest_len)) {
+ isc_throw(LibraryError, "OpenSSL EVP_DigestSignFinal() failed");
+ }
+ if (digest_len != size) {
+ isc_throw(LibraryError, "OpenSSL partial EVP_DigestSignFinal()");
+ }
+ if (len < size) {
+ digest.resize(len);
+ }
+ return (std::vector<uint8_t>(digest.begin(), digest.end()));
+ }
+
+ /// @brief Verify an existing signature
+ ///
+ /// See @ref isc::cryptolink::HMAC::verify() for details.
+ bool verify(const void* sig, size_t len) {
+ // Check the length
+ size_t size = getOutputLength();
+ if (len < 10 || len < size / 2) {
+ return (false);
+ }
+ if (digest_.size() == 0) {
+ digest_.resize(size);
+ size_t digest_len = size;
+ if (!EVP_DigestSignFinal(md_, &digest_[0], &digest_len)) {
+ isc_throw(LibraryError, "OpenSSL EVP_DigestSignFinal() failed");
+ }
+ if (digest_len != size) {
+ isc_throw(LibraryError, "OpenSSL partial EVP_DigestSignFinal()");
+ }
+ }
+ if (len > size) {
+ len = size;
+ }
+ return (digest_.same(sig, len));
+ }
+
+private:
+ /// @brief The hash algorithm
+ HashAlgorithm hash_algorithm_;
+
+ /// @brief The protected pointer to the OpenSSL EVP_MD_CTX structure
+ EVP_MD_CTX* md_;
+
+ /// @brief The digest cache for multiple verify
+ ossl::SecBuf<unsigned char> digest_;
+};
+
+HMAC::HMAC(const void* secret, size_t secret_length,
+ const HashAlgorithm hash_algorithm)
+{
+ impl_ = new HMACImpl(secret, secret_length, hash_algorithm);
+}
+
+HMAC::~HMAC() {
+ delete impl_;
+}
+
+HashAlgorithm
+HMAC::getHashAlgorithm() const {
+ return (impl_->getHashAlgorithm());
+}
+
+size_t
+HMAC::getOutputLength() const {
+ return (impl_->getOutputLength());
+}
+
+void
+HMAC::update(const void* data, const size_t len) {
+ impl_->update(data, len);
+}
+
+void
+HMAC::sign(isc::util::OutputBuffer& result, size_t len) {
+ impl_->sign(result, len);
+}
+
+void
+HMAC::sign(void* result, size_t len) {
+ impl_->sign(result, len);
+}
+
+std::vector<uint8_t>
+HMAC::sign(size_t len) {
+ return impl_->sign(len);
+}
+
+bool
+HMAC::verify(const void* sig, const size_t len) {
+ return (impl_->verify(sig, len));
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/openssl_link.cc b/src/lib/cryptolink/openssl_link.cc
new file mode 100644
index 0000000..4bceb8f
--- /dev/null
+++ b/src/lib/cryptolink/openssl_link.cc
@@ -0,0 +1,84 @@
+// Copyright (C) 2014-2022 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 <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_rng.h>
+#include <cryptolink/crypto_hash.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <openssl/crypto.h>
+#include <openssl/rand.h>
+
+namespace isc {
+namespace cryptolink {
+
+// For OpenSSL, we use the CryptoLink class object in RAII style
+class CryptoLinkImpl {
+};
+
+CryptoLink::~CryptoLink() {
+}
+
+/// \brief OpenSSL implementation of RNG.
+class RNGImpl : public RNG {
+public:
+ RNGImpl() { }
+
+ ~RNGImpl() { }
+
+private:
+ std::vector<uint8_t> random(size_t len) {
+ std::vector<uint8_t> data;
+ if (len > 0) {
+ data.resize(len);
+ if (RAND_bytes(&data[0], len) != 1) {
+ isc_throw(isc::cryptolink::LibraryError,
+ "OpenSSL RAND_bytes() failed");
+ }
+ }
+ return (data);
+ }
+};
+
+void
+CryptoLink::initialize(CryptoLink& c) {
+ if (!c.impl_) {
+ try {
+ c.impl_.reset(new CryptoLinkImpl());
+ } catch (const std::exception &ex) {
+ // Should never happen
+ isc_throw(InitializationError,
+ "Error during OpenSSL initialization:" << ex.what());
+ } catch (...) {
+ // Should never happen
+ isc_throw(InitializationError,
+ "Error during OpenSSL initialization");
+ }
+ }
+ if (!c.rng_) {
+ try {
+ c.rng_.reset(new RNGImpl());
+ } catch (const std::exception &ex) {
+ // Should never happen
+ isc_throw(InitializationError,
+ "Error during OpenSSL RNG initialization:" << ex.what());
+ } catch (...) {
+ // Should never happen
+ isc_throw(InitializationError,
+ "Error during OpenSSL RNG initialization");
+ }
+ }
+}
+
+std::string
+CryptoLink::getVersion() {
+ return (SSLeay_version(SSLEAY_VERSION));
+}
+
+} // namespace cryptolink
+} // namespace isc
diff --git a/src/lib/cryptolink/tests/Makefile.am b/src/lib/cryptolink/tests/Makefile.am
new file mode 100644
index 0000000..a8bbb47
--- /dev/null
+++ b/src/lib/cryptolink/tests/Makefile.am
@@ -0,0 +1,32 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+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 = run_unittests.cc
+run_unittests_SOURCES += crypto_unittests.cc
+run_unittests_SOURCES += hash_unittests.cc
+run_unittests_SOURCES += hmac_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/cryptolink/libkea-cryptolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.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/cryptolink/tests/Makefile.in b/src/lib/cryptolink/tests/Makefile.in
new file mode 100644
index 0000000..1f90600
--- /dev/null
+++ b/src/lib/cryptolink/tests/Makefile.in
@@ -0,0 +1,1039 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/cryptolink/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = run_unittests.cc crypto_unittests.cc \
+ hash_unittests.cc hmac_unittests.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-crypto_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hash_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hmac_unittests.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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-crypto_unittests.Po \
+ ./$(DEPDIR)/run_unittests-hash_unittests.Po \
+ ./$(DEPDIR)/run_unittests-hmac_unittests.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 =
+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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+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 = run_unittests.cc \
+@HAVE_GTEST_TRUE@ crypto_unittests.cc hash_unittests.cc \
+@HAVE_GTEST_TRUE@ hmac_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 = $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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/cryptolink/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/cryptolink/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-crypto_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hash_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hmac_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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-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`
+
+run_unittests-crypto_unittests.o: crypto_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-crypto_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-crypto_unittests.Tpo -c -o run_unittests-crypto_unittests.o `test -f 'crypto_unittests.cc' || echo '$(srcdir)/'`crypto_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-crypto_unittests.Tpo $(DEPDIR)/run_unittests-crypto_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='crypto_unittests.cc' object='run_unittests-crypto_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-crypto_unittests.o `test -f 'crypto_unittests.cc' || echo '$(srcdir)/'`crypto_unittests.cc
+
+run_unittests-crypto_unittests.obj: crypto_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-crypto_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-crypto_unittests.Tpo -c -o run_unittests-crypto_unittests.obj `if test -f 'crypto_unittests.cc'; then $(CYGPATH_W) 'crypto_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/crypto_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-crypto_unittests.Tpo $(DEPDIR)/run_unittests-crypto_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='crypto_unittests.cc' object='run_unittests-crypto_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-crypto_unittests.obj `if test -f 'crypto_unittests.cc'; then $(CYGPATH_W) 'crypto_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/crypto_unittests.cc'; fi`
+
+run_unittests-hash_unittests.o: hash_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-hash_unittests.Tpo -c -o run_unittests-hash_unittests.o `test -f 'hash_unittests.cc' || echo '$(srcdir)/'`hash_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_unittests.Tpo $(DEPDIR)/run_unittests-hash_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_unittests.cc' object='run_unittests-hash_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-hash_unittests.o `test -f 'hash_unittests.cc' || echo '$(srcdir)/'`hash_unittests.cc
+
+run_unittests-hash_unittests.obj: hash_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-hash_unittests.Tpo -c -o run_unittests-hash_unittests.obj `if test -f 'hash_unittests.cc'; then $(CYGPATH_W) 'hash_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/hash_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_unittests.Tpo $(DEPDIR)/run_unittests-hash_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_unittests.cc' object='run_unittests-hash_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-hash_unittests.obj `if test -f 'hash_unittests.cc'; then $(CYGPATH_W) 'hash_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/hash_unittests.cc'; fi`
+
+run_unittests-hmac_unittests.o: hmac_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hmac_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-hmac_unittests.Tpo -c -o run_unittests-hmac_unittests.o `test -f 'hmac_unittests.cc' || echo '$(srcdir)/'`hmac_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hmac_unittests.Tpo $(DEPDIR)/run_unittests-hmac_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hmac_unittests.cc' object='run_unittests-hmac_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-hmac_unittests.o `test -f 'hmac_unittests.cc' || echo '$(srcdir)/'`hmac_unittests.cc
+
+run_unittests-hmac_unittests.obj: hmac_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hmac_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-hmac_unittests.Tpo -c -o run_unittests-hmac_unittests.obj `if test -f 'hmac_unittests.cc'; then $(CYGPATH_W) 'hmac_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/hmac_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hmac_unittests.Tpo $(DEPDIR)/run_unittests-hmac_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hmac_unittests.cc' object='run_unittests-hmac_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-hmac_unittests.obj `if test -f 'hmac_unittests.cc'; then $(CYGPATH_W) 'hmac_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/hmac_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-crypto_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hmac_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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-crypto_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hmac_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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/cryptolink/tests/crypto_unittests.cc b/src/lib/cryptolink/tests/crypto_unittests.cc
new file mode 100644
index 0000000..9edb52d
--- /dev/null
+++ b/src/lib/cryptolink/tests/crypto_unittests.cc
@@ -0,0 +1,55 @@
+// Copyright (C) 2011-2018 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/encode/hex.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_rng.h>
+
+using namespace std;
+using namespace isc::cryptolink;
+
+// Test get version
+TEST(CryptoLinkTest, Version) {
+ EXPECT_NO_THROW(CryptoLink::getVersion());
+}
+
+// Tests whether getCryptoLink() returns a singleton instance
+TEST(CryptoLinkTest, Singleton) {
+ const CryptoLink& c1 = CryptoLink::getCryptoLink();
+ const CryptoLink& c2 = CryptoLink::getCryptoLink();
+ ASSERT_EQ(&c1, &c2);
+}
+
+// Tests whether getRNG() returns a global value
+TEST(CryptoLinkTest, GlobalRNG) {
+ CryptoLink& c = CryptoLink::getCryptoLink();
+ RNGPtr rng1 = c.getRNG();
+ RNGPtr rng2 = c.getRNG();
+ ASSERT_EQ(rng1, rng2);
+}
+
+// Tests whether RNG works
+TEST(CryptoLinkTest, RNG) {
+ RNGPtr rng = CryptoLink::getCryptoLink().getRNG();
+ vector<uint8_t> data;
+ ASSERT_NO_THROW(data = rng->random(16));
+ ASSERT_EQ(16, data.size());
+ vector<uint8_t> zero;
+ zero.resize(16);
+ EXPECT_NE(0, memcmp(&zero[0], &data[0], zero.size()));
+
+ // Retry with the function (vs method)
+ vector<uint8_t> dataf;
+ ASSERT_NO_THROW(dataf = random(16));
+ ASSERT_EQ(16, dataf.size());
+ EXPECT_NE(0, memcmp(&zero[0], &dataf[0], zero.size()));
+ EXPECT_NE(0, memcmp(&data[0], &dataf[0], zero.size()));
+}
diff --git a/src/lib/cryptolink/tests/hash_unittests.cc b/src/lib/cryptolink/tests/hash_unittests.cc
new file mode 100644
index 0000000..d2e9396
--- /dev/null
+++ b/src/lib/cryptolink/tests/hash_unittests.cc
@@ -0,0 +1,608 @@
+// Copyright (C) 2014-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 <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <util/encode/hex.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <util/buffer.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::cryptolink;
+
+namespace {
+ /// @brief Compare data with expected value
+ /// @param data Value to compare
+ /// @param expected Expected value
+ /// @param len Length of the expected value
+ void checkData(const uint8_t* data, const uint8_t* expected,
+ size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ ASSERT_EQ(expected[i], data[i]);
+ }
+ }
+
+ /// @brief Compare OutputBuffer with expected value
+ /// encapsulated checkData()
+ /// @param buf buffer to compare
+ /// @param expected Expected value
+ /// @param len Length of the expected value
+ void checkBuffer(const OutputBuffer& buf, const uint8_t* expected,
+ size_t len)
+ {
+ ASSERT_EQ(len, buf.getLength());
+ checkData(static_cast<const uint8_t*>(buf.getData()), expected,
+ len);
+ }
+
+ /// @brief Hash with the convenience functions
+ /// See @ref doHashTest for parameters
+ void doHashTestConv(const std::string& data,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hash,
+ size_t hash_len) {
+ OutputBuffer data_buf(data.size());
+ data_buf.writeData(data.c_str(), data.size());
+ OutputBuffer hash_digest(0);
+
+ // Sign it
+ digest(data_buf.getData(), data_buf.getLength(),
+ hash_algorithm, hash_digest, hash_len);
+
+ // Check if the signature is what we expect
+ checkBuffer(hash_digest, expected_hash, hash_len);
+ }
+
+ /// @brief Hash with an instantiation of a Hash object
+ /// See @ref doHashTest for parameters
+ void doHashTestDirect(const std::string& data,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hash,
+ size_t hash_len) {
+ OutputBuffer data_buf(data.size());
+ data_buf.writeData(data.c_str(), data.size());
+ OutputBuffer result(1);
+ CryptoLink& crypto = CryptoLink::getCryptoLink();
+
+ // Do it
+ boost::shared_ptr<Hash> hash_digest(crypto.createHash(hash_algorithm),
+ deleteHash);
+ hash_digest->update(data_buf.getData(), data_buf.getLength());
+ hash_digest->final(result, hash_len);
+
+ // Check if the digest is what we expect
+ checkBuffer(result, expected_hash, hash_len);
+ }
+
+ /// @brief Hash with a vector representation
+ /// See @ref doHashTest for parameters
+ void doHashTestVector(const std::string& data,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hash,
+ size_t hash_len) {
+ CryptoLink& crypto = CryptoLink::getCryptoLink();
+ boost::shared_ptr<Hash> hash_digest(crypto.createHash(hash_algorithm),
+ deleteHash);
+ hash_digest->update(data.c_str(), data.size());
+ std::vector<uint8_t> result = hash_digest->final(hash_len);
+ ASSERT_EQ(hash_len, result.size());
+ checkData(&result[0], expected_hash, hash_len);
+ }
+
+ /// @brief Hash with an array representation
+ /// See @ref doHashTest for parameters
+ void doHashTestArray(const std::string& data,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hash,
+ size_t hash_len) {
+ CryptoLink& crypto = CryptoLink::getCryptoLink();
+ boost::shared_ptr<Hash> hash_digest(crypto.createHash(hash_algorithm),
+ deleteHash);
+ hash_digest->update(data.c_str(), data.size());
+
+ // note: this is not exception-safe, and can leak, but
+ // if there is an unexpected exception in the code below we
+ // have more important things to fix.
+ boost::scoped_array<uint8_t> result(new uint8_t[hash_len]);
+
+ hash_digest->final(result.get(), hash_len);
+ checkData(result.get(), expected_hash, hash_len);
+ }
+
+ /// @brief Hash using all variants
+ /// @param data Input value
+ /// @param hash_algorithm Hash algorithm enum
+ /// @param expected_hash Expected value
+ /// @param hash_len Expected value length
+ void doHashTest(const std::string& data,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hash,
+ size_t hash_len) {
+ doHashTestConv(data, hash_algorithm, expected_hash, hash_len);
+ doHashTestDirect(data, hash_algorithm, expected_hash, hash_len);
+ doHashTestVector(data, hash_algorithm, expected_hash, hash_len);
+ doHashTestArray(data, hash_algorithm, expected_hash, hash_len);
+ }
+}
+
+//
+// Test values taken from RFC 1321
+//
+TEST(HashTest, MD5_RFC1321) {
+ const uint8_t hash_expected[] = { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f,
+ 0x00, 0xb2, 0x04, 0xe9, 0x80,
+ 0x09, 0x98, 0xec, 0xf8, 0x42,
+ 0x7e };
+ doHashTest("", MD5, hash_expected, 16);
+
+ const uint8_t hash_expected2[] = { 0x0c, 0xc1, 0x75, 0xb9, 0xc0,
+ 0xf1, 0xb6, 0xa8, 0x31, 0xc3,
+ 0x99, 0xe2, 0x69, 0x77, 0x26,
+ 0x61 };
+ doHashTest("a", MD5, hash_expected2, 16);
+
+ const uint8_t hash_expected3[] = { 0x90, 0x01, 0x50, 0x98, 0x3c,
+ 0xd2, 0x4f, 0xb0, 0xd6, 0x96,
+ 0x3f, 0x7d, 0x28, 0xe1, 0x7f,
+ 0x72 };
+ doHashTest("abc", MD5, hash_expected3, 16);
+
+ const uint8_t hash_expected4[] = { 0xf9, 0x6b, 0x69, 0x7d, 0x7c,
+ 0xb7, 0x93, 0x8d, 0x52, 0x5a,
+ 0x2f, 0x31, 0xaa, 0xf1, 0x61,
+ 0xd0 };
+ doHashTest("message digest", MD5, hash_expected4, 16);
+
+ const uint8_t hash_expected6[] = { 0xc3, 0xfc, 0xd3, 0xd7, 0x61,
+ 0x92, 0xe4, 0x00, 0x7d, 0xfb,
+ 0x49, 0x6c, 0xca, 0x67, 0xe1,
+ 0x3b };
+ doHashTest("abcdefghijklmnopqrstuvwxyz", MD5, hash_expected6, 16);
+
+ const uint8_t hash_expected7[] = { 0xd1, 0x74, 0xab, 0x98, 0xd2,
+ 0x77, 0xd9, 0xf5, 0xa5, 0x61,
+ 0x1c, 0x2c, 0x9f, 0x41, 0x9d,
+ 0x9f };
+ doHashTest("ABCDEFGHIJKLMNOPQRSTUVWXYZabcd"
+ "efghijklmnopqrstuvwxyz0123456789",
+ MD5, hash_expected7, 16);
+
+ const uint8_t hash_expected8[] = { 0x57, 0xed, 0xf4, 0xa2, 0x2b,
+ 0xe3, 0xc9, 0x55, 0xac, 0x49,
+ 0xda, 0x2e, 0x21, 0x07, 0xb6,
+ 0x7a };
+ doHashTest("1234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890",
+ MD5, hash_expected8, 16);
+}
+
+//
+// Test values taken from RFC 3174
+//
+TEST(HashTest, SHA1_RFC3174) {
+ const uint8_t hash_expected[] = { 0xa9, 0x99, 0x3e, 0x36, 0x47,
+ 0x06, 0x81, 0x6a, 0xba, 0x3e,
+ 0x25, 0x71, 0x78, 0x50, 0xc2,
+ 0x6c, 0x9c, 0xd0, 0xd8, 0x9d };
+ doHashTest("abc", SHA1, hash_expected, 20);
+
+ const uint8_t hash_expected2[] = { 0x84, 0x98, 0x3e, 0x44, 0x1c,
+ 0x3b, 0xd2, 0x6e, 0xba, 0xae,
+ 0x4a, 0xa1, 0xf9, 0x51, 0x29,
+ 0xe5, 0xe5, 0x46, 0x70, 0xf1 };
+ doHashTest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ SHA1, hash_expected2, 20);
+
+ const uint8_t hash_expected3[] = { 0x34, 0xaa, 0x97, 0x3c, 0xd4,
+ 0xc4, 0xda, 0xa4, 0xf6, 0x1e,
+ 0xeb, 0x2b, 0xdb, 0xad, 0x27,
+ 0x31, 0x65, 0x34, 0x01, 0x6f };
+ doHashTest(std::string(1000000, 0x61), SHA1, hash_expected3, 20);
+
+ const uint8_t hash_expected4[] = { 0xde, 0xa3, 0x56, 0xa2, 0xcd,
+ 0xdd, 0x90, 0xc7, 0xa7, 0xec,
+ 0xed, 0xc5, 0xeb, 0xb5, 0x63,
+ 0x93, 0x4f, 0x46, 0x04, 0x52 };
+ doHashTest("01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567",
+ SHA1, hash_expected4, 20);
+}
+
+//
+// Test values taken from RFC 6234
+//
+TEST(HashTest, SHA224_RFC6234) {
+ const uint8_t hash_expected[] = { 0x23, 0x09, 0x7d, 0x22, 0x34,
+ 0x05, 0xd8, 0x22, 0x86, 0x42,
+ 0xa4, 0x77, 0xbd, 0xa2, 0x55,
+ 0xb3, 0x2a, 0xad, 0xbc, 0xe4,
+ 0xbd, 0xa0, 0xb3, 0xf7, 0xe3,
+ 0x6c, 0x9d, 0xa7 };
+ doHashTest("abc", SHA224, hash_expected, 28);
+
+ const uint8_t hash_expected2[] = { 0x75, 0x38, 0x8b, 0x16, 0x51,
+ 0x27, 0x76, 0xcc, 0x5d, 0xba,
+ 0x5d, 0xa1, 0xfd, 0x89, 0x01,
+ 0x50, 0xb0, 0xc6, 0x45, 0x5c,
+ 0xb4, 0xf5, 0x8b, 0x19, 0x52,
+ 0x52, 0x25, 0x25 };
+ doHashTest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ SHA224, hash_expected2, 28);
+
+ const uint8_t hash_expected3[] = { 0x20, 0x79, 0x46, 0x55, 0x98,
+ 0x0c, 0x91, 0xd8, 0xbb, 0xb4,
+ 0xc1, 0xea, 0x97, 0x61, 0x8a,
+ 0x4b, 0xf0, 0x3f, 0x42, 0x58,
+ 0x19, 0x48, 0xb2, 0xee, 0x4e,
+ 0xe7, 0xad, 0x67 };
+ doHashTest(std::string(1000000, 0x61), SHA224, hash_expected3, 28);
+
+ const uint8_t hash_expected4[] = { 0x56, 0x7f, 0x69, 0xf1, 0x68,
+ 0xcd, 0x78, 0x44, 0xe6, 0x52,
+ 0x59, 0xce, 0x65, 0x8f, 0xe7,
+ 0xaa, 0xdf, 0xa2, 0x52, 0x16,
+ 0xe6, 0x8e, 0xca, 0x0e, 0xb7,
+ 0xab, 0x82, 0x62 };
+ doHashTest("01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567",
+ SHA224, hash_expected4, 28);
+}
+
+TEST(HashTest, SHA256_RFC6234) {
+ const uint8_t hash_expected[] = { 0xba, 0x78, 0x16, 0xbf, 0x8f,
+ 0x01, 0xcf, 0xea, 0x41, 0x41,
+ 0x40, 0xde, 0x5d, 0xae, 0x22,
+ 0x23, 0xb0, 0x03, 0x61, 0xa3,
+ 0x96, 0x17, 0x7a, 0x9c, 0xb4,
+ 0x10, 0xff, 0x61, 0xf2, 0x00,
+ 0x15, 0xad };
+ doHashTest("abc", SHA256, hash_expected, 32);
+
+ const uint8_t hash_expected2[] = { 0x24, 0x8d, 0x6a, 0x61, 0xd2,
+ 0x06, 0x38, 0xb8, 0xe5, 0xc0,
+ 0x26, 0x93, 0x0c, 0x3e, 0x60,
+ 0x39, 0xa3, 0x3c, 0xe4, 0x59,
+ 0x64, 0xff, 0x21, 0x67, 0xf6,
+ 0xec, 0xed, 0xd4, 0x19, 0xdb,
+ 0x06, 0xc1 };
+ doHashTest("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ SHA256, hash_expected2, 32);
+
+ const uint8_t hash_expected3[] = { 0xcd, 0xc7, 0x6e, 0x5c, 0x99,
+ 0x14, 0xfb, 0x92, 0x81, 0xa1,
+ 0xc7, 0xe2, 0x84, 0xd7, 0x3e,
+ 0x67, 0xf1, 0x80, 0x9a, 0x48,
+ 0xa4, 0x97, 0x20, 0x0e, 0x04,
+ 0x6d, 0x39, 0xcc, 0xc7, 0x11,
+ 0x2c, 0xd0 };
+ doHashTest(std::string(1000000, 0x61), SHA256, hash_expected3, 32);
+
+ const uint8_t hash_expected4[] = { 0x59, 0x48, 0x47, 0x32, 0x84,
+ 0x51, 0xbd, 0xfa, 0x85, 0x05,
+ 0x62, 0x25, 0x46, 0x2c, 0xc1,
+ 0xd8, 0x67, 0xd8, 0x77, 0xfb,
+ 0x38, 0x8d, 0xf0, 0xce, 0x35,
+ 0xf2, 0x5a, 0xb5, 0x56, 0x2b,
+ 0xfb, 0xb5 };
+ doHashTest("01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567",
+ SHA256, hash_expected4, 32);
+}
+
+TEST(HashTest, SHA384_RFC6234) {
+ const uint8_t hash_expected[] = { 0xcb, 0x00, 0x75, 0x3f, 0x45,
+ 0xa3, 0x5e, 0x8b, 0xb5, 0xa0,
+ 0x3d, 0x69, 0x9a, 0xc6, 0x50,
+ 0x07, 0x27, 0x2c, 0x32, 0xab,
+ 0x0e, 0xde, 0xd1, 0x63, 0x1a,
+ 0x8b, 0x60, 0x5a, 0x43, 0xff,
+ 0x5b, 0xed, 0x80, 0x86, 0x07,
+ 0x2b, 0xa1, 0xe7, 0xcc, 0x23,
+ 0x58, 0xba, 0xec, 0xa1, 0x34,
+ 0xc8, 0x25, 0xa7 };
+ doHashTest("abc", SHA384, hash_expected, 48);
+
+ const uint8_t hash_expected2[] = { 0x09, 0x33, 0x0c, 0x33, 0xf7,
+ 0x11, 0x47, 0xe8, 0x3d, 0x19,
+ 0x2f, 0xc7, 0x82, 0xcd, 0x1b,
+ 0x47, 0x53, 0x11, 0x1b, 0x17,
+ 0x3b, 0x3b, 0x05, 0xd2, 0x2f,
+ 0xa0, 0x80, 0x86, 0xe3, 0xb0,
+ 0xf7, 0x12, 0xfc, 0xc7, 0xc7,
+ 0x1a, 0x55, 0x7e, 0x2d, 0xb9,
+ 0x66, 0xc3, 0xe9, 0xfa, 0x91,
+ 0x74, 0x60, 0x39 };
+ doHashTest("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+ "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ SHA384, hash_expected2, 48);
+
+ const uint8_t hash_expected3[] = { 0x9d, 0x0e, 0x18, 0x09, 0x71,
+ 0x64, 0x74, 0xcb, 0x08, 0x6e,
+ 0x83, 0x4e, 0x31, 0x0a, 0x4a,
+ 0x1c, 0xed, 0x14, 0x9e, 0x9c,
+ 0x00, 0xf2, 0x48, 0x52, 0x79,
+ 0x72, 0xce, 0xc5, 0x70, 0x4c,
+ 0x2a, 0x5b, 0x07, 0xb8, 0xb3,
+ 0xdc, 0x38, 0xec, 0xc4, 0xeb,
+ 0xae, 0x97, 0xdd, 0xd8, 0x7f,
+ 0x3d, 0x89, 0x85 };
+ doHashTest(std::string(1000000, 0x61), SHA384, hash_expected3, 48);
+
+ const uint8_t hash_expected4[] = { 0x2f, 0xc6, 0x4a, 0x4f, 0x50,
+ 0x0d, 0xdb, 0x68, 0x28, 0xf6,
+ 0xa3, 0x43, 0x0b, 0x8d, 0xd7,
+ 0x2a, 0x36, 0x8e, 0xb7, 0xf3,
+ 0xa8, 0x32, 0x2a, 0x70, 0xbc,
+ 0x84, 0x27, 0x5b, 0x9c, 0x0b,
+ 0x3a, 0xb0, 0x0d, 0x27, 0xa5,
+ 0xcc, 0x3c, 0x2d, 0x22, 0x4a,
+ 0xa6, 0xb6, 0x1a, 0x0d, 0x79,
+ 0xfb, 0x45, 0x96 };
+ doHashTest("01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567",
+ SHA384, hash_expected4, 48);
+}
+
+TEST(HashTest, SHA512_RFC6234) {
+ const uint8_t hash_expected[] = { 0xdd, 0xaf, 0x35, 0xa1, 0x93,
+ 0x61, 0x7a, 0xba, 0xcc, 0x41,
+ 0x73, 0x49, 0xae, 0x20, 0x41,
+ 0x31, 0x12, 0xe6, 0xfa, 0x4e,
+ 0x89, 0xa9, 0x7e, 0xa2, 0x0a,
+ 0x9e, 0xee, 0xe6, 0x4b, 0x55,
+ 0xd3, 0x9a, 0x21, 0x92, 0x99,
+ 0x2a, 0x27, 0x4f, 0xc1, 0xa8,
+ 0x36, 0xba, 0x3c, 0x23, 0xa3,
+ 0xfe, 0xeb, 0xbd, 0x45, 0x4d,
+ 0x44, 0x23, 0x64, 0x3c, 0xe8,
+ 0x0e, 0x2a, 0x9a, 0xc9, 0x4f,
+ 0xa5, 0x4c, 0xa4, 0x9f };
+ doHashTest("abc", SHA512, hash_expected, 64);
+
+ const uint8_t hash_expected2[] = { 0x8e, 0x95, 0x9b, 0x75, 0xda,
+ 0xe3, 0x13, 0xda, 0x8c, 0xf4,
+ 0xf7, 0x28, 0x14, 0xfc, 0x14,
+ 0x3f, 0x8f, 0x77, 0x79, 0xc6,
+ 0xeb, 0x9f, 0x7f, 0xa1, 0x72,
+ 0x99, 0xae, 0xad, 0xb6, 0x88,
+ 0x90, 0x18, 0x50, 0x1d, 0x28,
+ 0x9e, 0x49, 0x00, 0xf7, 0xe4,
+ 0x33, 0x1b, 0x99, 0xde, 0xc4,
+ 0xb5, 0x43, 0x3a, 0xc7, 0xd3,
+ 0x29, 0xee, 0xb6, 0xdd, 0x26,
+ 0x54, 0x5e, 0x96, 0xe5, 0x5b,
+ 0x87, 0x4b, 0xe9, 0x09 };
+ doHashTest("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn"
+ "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ SHA512, hash_expected2, 64);
+
+ const uint8_t hash_expected3[] = { 0xe7, 0x18, 0x48, 0x3d, 0x0c,
+ 0xe7, 0x69, 0x64, 0x4e, 0x2e,
+ 0x42, 0xc7, 0xbc, 0x15, 0xb4,
+ 0x63, 0x8e, 0x1f, 0x98, 0xb1,
+ 0x3b, 0x20, 0x44, 0x28, 0x56,
+ 0x32, 0xa8, 0x03, 0xaf, 0xa9,
+ 0x73, 0xeb, 0xde, 0x0f, 0xf2,
+ 0x44, 0x87, 0x7e, 0xa6, 0x0a,
+ 0x4c, 0xb0, 0x43, 0x2c, 0xe5,
+ 0x77, 0xc3, 0x1b, 0xeb, 0x00,
+ 0x9c, 0x5c, 0x2c, 0x49, 0xaa,
+ 0x2e, 0x4e, 0xad, 0xb2, 0x17,
+ 0xad, 0x8c, 0xc0, 0x9b };
+ doHashTest(std::string(1000000, 0x61), SHA512, hash_expected3, 64);
+
+ const uint8_t hash_expected4[] = { 0x89, 0xd0, 0x5b, 0xa6, 0x32,
+ 0xc6, 0x99, 0xc3, 0x12, 0x31,
+ 0xde, 0xd4, 0xff, 0xc1, 0x27,
+ 0xd5, 0xa8, 0x94, 0xda, 0xd4,
+ 0x12, 0xc0, 0xe0, 0x24, 0xdb,
+ 0x87, 0x2d, 0x1a, 0xbd, 0x2b,
+ 0xa8, 0x14, 0x1a, 0x0f, 0x85,
+ 0x07, 0x2a, 0x9b, 0xe1, 0xe2,
+ 0xaa, 0x04, 0xcf, 0x33, 0xc7,
+ 0x65, 0xcb, 0x51, 0x08, 0x13,
+ 0xa3, 0x9c, 0xd5, 0xa8, 0x4c,
+ 0x4a, 0xca, 0xa6, 0x4d, 0x3f,
+ 0x3f, 0xb7, 0xba, 0xe9 };
+ doHashTest("01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567"
+ "01234567012345670123456701234567",
+ SHA512, hash_expected4, 64);
+}
+
+namespace {
+ /// @brief Get the hash algorithm
+ /// @param alg Hash algorithm enum
+ /// @return Hash algorithm enum
+ HashAlgorithm
+ digestHashAlgorithm(HashAlgorithm alg) {
+ boost::shared_ptr<Hash> hash_digest(
+ CryptoLink::getCryptoLink().createHash(alg),
+ deleteHash);
+ return (hash_digest->getHashAlgorithm());
+ }
+}
+
+TEST(HashTest, HashAlgorithm) {
+ EXPECT_EQ(MD5, digestHashAlgorithm(MD5));
+ EXPECT_EQ(SHA1, digestHashAlgorithm(SHA1));
+ EXPECT_EQ(SHA256, digestHashAlgorithm(SHA256));
+ EXPECT_EQ(SHA224, digestHashAlgorithm(SHA224));
+ EXPECT_EQ(SHA384, digestHashAlgorithm(SHA384));
+ EXPECT_EQ(SHA512, digestHashAlgorithm(SHA512));
+}
+
+namespace {
+ /// @brief Compute the vector digest length
+ /// @param alg Hash algorithm enum
+ /// @param len Wanted length
+ /// @return Effective length
+ size_t
+ digestVectorLength(HashAlgorithm alg, size_t len) {
+ boost::shared_ptr<Hash> hash_digest(
+ CryptoLink::getCryptoLink().createHash(alg),
+ deleteHash);
+ hash_digest->update("asdf", 4);
+ const std::vector<uint8_t> result = hash_digest->final(len);
+ return (result.size());
+ }
+
+ /// @brief Compute the buffer digest length
+ /// @param alg Hash algorithm enum
+ /// @param len Wanted length
+ /// @return Effective length
+ size_t
+ digestBufferLength(HashAlgorithm alg, size_t len) {
+ boost::shared_ptr<Hash> hash_digest(
+ CryptoLink::getCryptoLink().createHash(alg),
+ deleteHash);
+ hash_digest->update("asdf", 4);
+ OutputBuffer result(0);
+ hash_digest->final(result, len);
+ return (result.getLength());
+ }
+ // There is no equivalent for array digest because it is copied
+ // in place
+}
+
+TEST(HashTest, HashLength) {
+ EXPECT_EQ(8, digestVectorLength(MD5, 8));
+ EXPECT_EQ(16, digestVectorLength(MD5, 16));
+ EXPECT_EQ(16, digestVectorLength(MD5, 40));
+ EXPECT_EQ(16, digestVectorLength(MD5, 2000));
+
+ EXPECT_EQ(8, digestBufferLength(SHA1, 8));
+ EXPECT_EQ(20, digestBufferLength(SHA1, 20));
+ EXPECT_EQ(20, digestBufferLength(SHA1, 40));
+ EXPECT_EQ(20, digestBufferLength(SHA1, 2000));
+
+ EXPECT_EQ(8, digestBufferLength(SHA256, 8));
+ EXPECT_EQ(32, digestBufferLength(SHA256, 32));
+ EXPECT_EQ(32, digestBufferLength(SHA256, 40));
+ EXPECT_EQ(32, digestBufferLength(SHA256, 3200));
+
+ EXPECT_EQ(8, digestBufferLength(MD5, 8));
+ EXPECT_EQ(16, digestBufferLength(MD5, 16));
+ EXPECT_EQ(16, digestBufferLength(MD5, 40));
+ EXPECT_EQ(16, digestBufferLength(MD5, 2000));
+
+ EXPECT_EQ(8, digestBufferLength(SHA1, 8));
+ EXPECT_EQ(20, digestBufferLength(SHA1, 20));
+ EXPECT_EQ(20, digestBufferLength(SHA1, 40));
+ EXPECT_EQ(20, digestBufferLength(SHA1, 2000));
+
+ EXPECT_EQ(8, digestBufferLength(SHA256, 8));
+ EXPECT_EQ(32, digestBufferLength(SHA256, 32));
+ EXPECT_EQ(32, digestBufferLength(SHA256, 40));
+ EXPECT_EQ(32, digestBufferLength(SHA256, 3200));
+}
+
+// @todo Error cases?
diff --git a/src/lib/cryptolink/tests/hmac_unittests.cc b/src/lib/cryptolink/tests/hmac_unittests.cc
new file mode 100644
index 0000000..d0c5cd9
--- /dev/null
+++ b/src/lib/cryptolink/tests/hmac_unittests.cc
@@ -0,0 +1,717 @@
+// Copyright (C) 2011-2022 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 <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <util/encode/hex.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+#include <util/buffer.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::cryptolink;
+
+namespace {
+ /// @brief Fill a string with copies of an out of char range value
+ /// @param data String to fill
+ /// @param len Number of copies
+ /// @param val Value
+ void fillString(std::string& data, size_t len, int val) {
+ data.resize(len);
+ if (len != 0) {
+ std::memset(&data[0], val, len);
+ }
+ }
+
+ /// @brief Compare data with expected value
+ /// @param data Value to compare
+ /// @param expected Expected value
+ /// @param len Length of the expected value
+ void checkData(const uint8_t* data, const uint8_t* expected,
+ size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ ASSERT_EQ(expected[i], data[i]);
+ }
+ }
+
+ /// @brief Compare OutputBuffer with expected value
+ /// encapsulated checkData()
+ /// @param buf buffer to compare
+ /// @param expected Expected value
+ /// @param len Length of the expected value
+ void checkBuffer(const OutputBuffer& buf, const uint8_t* expected,
+ size_t len)
+ {
+ ASSERT_EQ(len, buf.getLength());
+ checkData(static_cast<const uint8_t*>(buf.getData()), expected,
+ len);
+ }
+
+ /// @brief Sign and verify with the convenience functions
+ /// See @ref doHMACTest for parameters
+ void doHMACTestConv(const std::string& data,
+ const void* secret,
+ size_t secret_len,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hmac,
+ size_t hmac_len) {
+ OutputBuffer data_buf(data.size());
+ data_buf.writeData(data.c_str(), data.size());
+ OutputBuffer hmac_sig(0);
+
+ // Sign it
+ signHMAC(data_buf.getData(), data_buf.getLength(),
+ secret, secret_len, hash_algorithm, hmac_sig, hmac_len);
+
+ // Check if the signature is what we expect
+ checkBuffer(hmac_sig, expected_hmac, hmac_len);
+
+ // Check whether we can verify it ourselves
+ EXPECT_TRUE(verifyHMAC(data_buf.getData(), data_buf.getLength(),
+ secret, secret_len, hash_algorithm,
+ hmac_sig.getData(),
+ hmac_sig.getLength()));
+
+ // Change the sig by flipping the first octet, and check
+ // whether verification fails then
+ hmac_sig.writeUint8At(~hmac_sig[0], 0);
+ EXPECT_FALSE(verifyHMAC(data_buf.getData(), data_buf.getLength(),
+ secret, secret_len, hash_algorithm,
+ hmac_sig.getData(),
+ hmac_sig.getLength()));
+ }
+
+ /// @brief Sign and verify with an instantiation of an HMAC object
+ /// See @ref doHMACTest for parameters
+ void doHMACTestDirect(const std::string& data,
+ const void* secret,
+ size_t secret_len,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hmac,
+ size_t hmac_len) {
+ OutputBuffer data_buf(data.size());
+ data_buf.writeData(data.c_str(), data.size());
+ OutputBuffer hmac_sig(1);
+ CryptoLink& crypto = CryptoLink::getCryptoLink();
+
+ // Sign it
+ boost::shared_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
+ secret_len,
+ hash_algorithm),
+ deleteHMAC);
+ hmac_sign->update(data_buf.getData(), data_buf.getLength());
+ hmac_sign->sign(hmac_sig, hmac_len);
+
+ // Check if the signature is what we expect
+ checkBuffer(hmac_sig, expected_hmac, hmac_len);
+
+ // Check whether we can verify it ourselves
+ boost::shared_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
+ secret_len,
+ hash_algorithm),
+ deleteHMAC);
+ hmac_verify->update(data_buf.getData(), data_buf.getLength());
+ EXPECT_TRUE(hmac_verify->verify(hmac_sig.getData(),
+ hmac_sig.getLength()));
+
+ // Change the sig by flipping the first octet, and check
+ // whether verification fails then
+ hmac_sig.writeUint8At(~hmac_sig[0], 0);
+ EXPECT_FALSE(hmac_verify->verify(hmac_sig.getData(),
+ hmac_sig.getLength()));
+
+ // Restore the sig by flipping the first octet, and check
+ // whether verification succeeds then
+ hmac_sig.writeUint8At(~hmac_sig[0], 0);
+ EXPECT_TRUE(hmac_verify->verify(hmac_sig.getData(),
+ hmac_sig.getLength()));
+ }
+
+ /// @brief Sign and verify with vector representation of signature
+ /// See @ref doHMACTest for parameters
+ void doHMACTestVector(const std::string& data,
+ const void* secret,
+ size_t secret_len,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hmac,
+ size_t hmac_len) {
+ CryptoLink& crypto = CryptoLink::getCryptoLink();
+ boost::shared_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
+ secret_len,
+ hash_algorithm),
+ deleteHMAC);
+ hmac_sign->update(data.c_str(), data.size());
+ std::vector<uint8_t> sig = hmac_sign->sign(hmac_len);
+ ASSERT_EQ(hmac_len, sig.size());
+ checkData(&sig[0], expected_hmac, hmac_len);
+
+ boost::shared_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
+ secret_len,
+ hash_algorithm),
+ deleteHMAC);
+ hmac_verify->update(data.c_str(), data.size());
+ EXPECT_TRUE(hmac_verify->verify(&sig[0], sig.size()));
+
+ sig[0] = ~sig[0];
+ EXPECT_FALSE(hmac_verify->verify(&sig[0], sig.size()));
+ }
+
+ /// @brief Sign and verify with array representation of signature
+ /// See @ref doHMACTest for parameters
+ void doHMACTestArray(const std::string& data,
+ const void* secret,
+ size_t secret_len,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hmac,
+ size_t hmac_len) {
+ CryptoLink& crypto = CryptoLink::getCryptoLink();
+ boost::shared_ptr<HMAC> hmac_sign(crypto.createHMAC(secret,
+ secret_len,
+ hash_algorithm),
+ deleteHMAC);
+ hmac_sign->update(data.c_str(), data.size());
+
+ // note: this is not exception-safe, and can leak, but
+ // if there is an unexpected exception in the code below we
+ // have more important things to fix.
+ uint8_t* sig = new uint8_t[hmac_len];
+
+ hmac_sign->sign(sig, hmac_len);
+ checkData(sig, expected_hmac, hmac_len);
+
+ boost::shared_ptr<HMAC> hmac_verify(crypto.createHMAC(secret,
+ secret_len,
+ hash_algorithm),
+ deleteHMAC);
+ hmac_verify->update(data.c_str(), data.size());
+ EXPECT_TRUE(hmac_verify->verify(sig, hmac_len));
+
+ sig[0] = ~sig[0];
+ EXPECT_FALSE(hmac_verify->verify(sig, hmac_len));
+
+ sig[0] = ~sig[0];
+ EXPECT_TRUE(hmac_verify->verify(sig, hmac_len));
+
+ delete[] sig;
+ }
+
+ /// @brief Sign and verify using all variants
+ /// @param data Input value
+ /// @param secret Secret value
+ /// @param secret_len Secret value length
+ /// @param hash_algorithm Hash algorithm enum
+ /// @param expected_hmac Expected value
+ /// @param hmac_len Expected value length
+ void doHMACTest(const std::string& data,
+ const void* secret,
+ size_t secret_len,
+ const HashAlgorithm hash_algorithm,
+ const uint8_t* expected_hmac,
+ size_t hmac_len) {
+ doHMACTestConv(data, secret, secret_len, hash_algorithm,
+ expected_hmac, hmac_len);
+ doHMACTestDirect(data, secret, secret_len, hash_algorithm,
+ expected_hmac, hmac_len);
+ doHMACTestVector(data, secret, secret_len, hash_algorithm,
+ expected_hmac, hmac_len);
+ doHMACTestArray(data, secret, secret_len, hash_algorithm,
+ expected_hmac, hmac_len);
+ }
+}
+
+//
+// Test values taken from RFC 2202
+//
+TEST(HMACTest, HMAC_MD5_RFC2202_SIGN) {
+ const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b };
+ const uint8_t hmac_expected[] = { 0x92, 0x94, 0x72, 0x7a, 0x36,
+ 0x38, 0xbb, 0x1c, 0x13, 0xf4,
+ 0x8e, 0xf8, 0x15, 0x8b, 0xfc,
+ 0x9d };
+ doHMACTest("Hi There", secret, 16, MD5, hmac_expected, 16);
+
+ const uint8_t hmac_expected2[] = { 0x75, 0x0c, 0x78, 0x3e, 0x6a,
+ 0xb0, 0xb5, 0x03, 0xea, 0xa8,
+ 0x6e, 0x31, 0x0a, 0x5d, 0xb7,
+ 0x38 };
+ doHMACTest("what do ya want for nothing?", "Jefe", 4, MD5,
+ hmac_expected2, 16);
+
+ std::string data3;
+ fillString(data3, 50, 0xdd);
+ const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa };
+ const uint8_t hmac_expected3[] = { 0x56, 0xbe, 0x34, 0x52, 0x1d,
+ 0x14, 0x4c, 0x88, 0xdb, 0xb8,
+ 0xc7, 0x33, 0xf0, 0xe8, 0xb3,
+ 0xf6};
+ doHMACTest(data3, secret3, 16, MD5, hmac_expected3, 16);
+
+ std::string data4;
+ fillString(data4, 50, 0xcd);
+ const uint8_t secret4[] = { 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 };
+ const uint8_t hmac_expected4[] = { 0x69, 0x7e, 0xaf, 0x0a, 0xca,
+ 0x3a, 0x3a, 0xea, 0x3a, 0x75,
+ 0x16, 0x47, 0x46, 0xff, 0xaa,
+ 0x79 };
+ doHMACTest(data4, secret4, 25, MD5, hmac_expected4, 16);
+
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34,
+ 0x2e, 0xdc, 0x00, 0xf9, 0xba,
+ 0xb9, 0x95, 0x69, 0x0e, 0xfd,
+ 0x4c };
+ doHMACTest("Test With Truncation", secret5, 16, MD5,
+ hmac_expected5, 16);
+ doHMACTest("Test With Truncation", secret5, 16, MD5,
+ hmac_expected5, 12);
+
+ std::string secret6;
+ fillString(secret6, 80, 0xaa);
+ const uint8_t hmac_expected6[] = { 0x6b, 0x1a, 0xb7, 0xfe, 0x4b,
+ 0xd7, 0xbf, 0x8f, 0x0b, 0x62,
+ 0xe6, 0xce, 0x61, 0xb9, 0xd0,
+ 0xcd };
+ doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First",
+ secret6.c_str(), 80, MD5, hmac_expected6, 16);
+
+ std::string secret7;
+ fillString(secret7, 80, 0xaa);
+ const uint8_t hmac_expected7[] = { 0x6f, 0x63, 0x0f, 0xad, 0x67,
+ 0xcd, 0xa0, 0xee, 0x1f, 0xb1,
+ 0xf5, 0x62, 0xdb, 0x3a, 0xa5,
+ 0x3e };
+ doHMACTest("Test Using Larger Than Block-Size Key and Larger Than "
+ "One Block-Size Data",
+ secret7.c_str(), 80, MD5, hmac_expected7, 16);
+}
+
+// Temporarily disabled
+TEST(HMACTest, HMAC_MD5_RFC2202_SIGN_TRUNCATED) {
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0x56, 0x46, 0x1e, 0xf2, 0x34,
+ 0x2e, 0xdc, 0x00, 0xf9, 0xba,
+ 0xb9, 0x95, 0x69, 0x0e, 0xfd,
+ 0x4c };
+ doHMACTest("Test With Truncation", secret5, 16, MD5,
+ hmac_expected5, 16);
+ doHMACTest("Test With Truncation", secret5, 16, MD5,
+ hmac_expected5, 12);
+}
+
+//
+// Test values taken from RFC 2202
+//
+TEST(HMACTest, HMAC_SHA1_RFC2202_SIGN) {
+ const uint8_t secret[] = { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b };
+ const uint8_t hmac_expected[] = { 0xb6, 0x17, 0x31, 0x86, 0x55,
+ 0x05, 0x72, 0x64, 0xe2, 0x8b,
+ 0xc0, 0xb6, 0xfb, 0x37, 0x8c,
+ 0x8e, 0xf1, 0x46, 0xbe, 0x00 };
+ doHMACTest("Hi There", secret, 20, SHA1, hmac_expected, 20);
+
+ const uint8_t hmac_expected2[] = { 0xef, 0xfc, 0xdf, 0x6a, 0xe5,
+ 0xeb, 0x2f, 0xa2, 0xd2, 0x74,
+ 0x16, 0xd5, 0xf1, 0x84, 0xdf,
+ 0x9c, 0x25, 0x9a, 0x7c, 0x79 };
+ doHMACTest("what do ya want for nothing?", "Jefe", 4, SHA1,
+ hmac_expected2, 20);
+
+ std::string data3;
+ fillString(data3, 50, 0xdd);
+ const uint8_t secret3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa };
+ const uint8_t hmac_expected3[] = { 0x12, 0x5d, 0x73, 0x42, 0xb9,
+ 0xac, 0x11, 0xcd, 0x91, 0xa3,
+ 0x9a, 0xf4, 0x8a, 0xa1, 0x7b,
+ 0x4f, 0x63, 0xf1, 0x75, 0xd3 };
+ doHMACTest(data3, secret3, 20, SHA1, hmac_expected3, 20);
+
+ std::string data4;
+ fillString(data4, 50, 0xcd);
+ const uint8_t secret4[] = { 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 };
+ const uint8_t hmac_expected4[] = { 0x4c, 0x90, 0x07, 0xf4, 0x02,
+ 0x62, 0x50, 0xc6, 0xbc, 0x84,
+ 0x14, 0xf9, 0xbf, 0x50, 0xc8,
+ 0x6c, 0x2d, 0x72, 0x35, 0xda };
+ doHMACTest(data4, secret4, 25, SHA1, hmac_expected4, 20);
+
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b,
+ 0x55, 0xe0, 0x7f, 0xe7, 0xf2,
+ 0x7b, 0xe1, 0xd5, 0x8b, 0xb9,
+ 0x32, 0x4a, 0x9a, 0x5a, 0x04 };
+ doHMACTest("Test With Truncation", secret5, 20, SHA1,
+ hmac_expected5, 20);
+ doHMACTest("Test With Truncation", secret5, 20, SHA1,
+ hmac_expected5, 12);
+
+ std::string secret6;
+ fillString(secret6, 80, 0xaa);
+ const uint8_t hmac_expected6[] = { 0xaa, 0x4a, 0xe5, 0xe1, 0x52,
+ 0x72, 0xd0, 0x0e, 0x95, 0x70,
+ 0x56, 0x37, 0xce, 0x8a, 0x3b,
+ 0x55, 0xed, 0x40, 0x21, 0x12 };
+ doHMACTest("Test Using Larger Than Block-Size Key - Hash Key First",
+ secret6.c_str(), 80, SHA1, hmac_expected6, 20);
+
+ std::string secret7;
+ fillString(secret7, 80, 0xaa);
+ const uint8_t hmac_expected7[] = { 0xe8, 0xe9, 0x9d, 0x0f, 0x45,
+ 0x23, 0x7d, 0x78, 0x6d, 0x6b,
+ 0xba, 0xa7, 0x96, 0x5c, 0x78,
+ 0x08, 0xbb, 0xff, 0x1a, 0x91 };
+ doHMACTest("Test Using Larger Than Block-Size Key and Larger Than "
+ "One Block-Size Data",
+ secret7.c_str(), 80, SHA1, hmac_expected7, 20);
+}
+
+// Temporarily disabled
+TEST(HMACTest, HMAC_SHA1_RFC2202_SIGN_TRUNCATED) {
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0x4c, 0x1a, 0x03, 0x42, 0x4b,
+ 0x55, 0xe0, 0x7f, 0xe7, 0xf2,
+ 0x7b, 0xe1, 0xd5, 0x8b, 0xb9,
+ 0x32, 0x4a, 0x9a, 0x5a, 0x04 };
+ doHMACTest("Test With Truncation", secret5, 20, SHA1,
+ hmac_expected5, 20);
+ doHMACTest("Test With Truncation", secret5, 20, SHA1,
+ hmac_expected5, 12);
+}
+
+//
+// Test values taken from RFC 4231
+//
+// Test data from RFC4231, including secret key
+// and source data, they are common for sha224/256/384/512
+// so put them together within the separate function.
+void
+doRFC4231Tests(HashAlgorithm hash_algorithm,
+ const std::vector<std::vector<uint8_t> >& hmac_list)
+{
+ std::vector<std::string> data_list;
+ std::vector<std::string> secret_list;
+
+ data_list.push_back("Hi There");
+ data_list.push_back("what do ya want for nothing?");
+ std::string fiftydd;
+ fillString(fiftydd, 50, 0xdd);
+ data_list.push_back(fiftydd);
+ std::string fiftycd;
+ fillString(fiftycd, 50, 0xcd);
+ data_list.push_back(fiftycd);
+ data_list.push_back("Test With Truncation");
+ data_list.push_back("Test Using Larger Than Block-Size Key - "
+ "Hash Key First");
+ data_list.push_back("This is a test using a larger than block-size "
+ "key and a larger than block-size data. The key "
+ "needs to be hashed before being used by the HMAC "
+ "algorithm.");
+
+ std::string twenty0b;
+ fillString(twenty0b, 20, 0x0b);
+ secret_list.push_back(twenty0b);
+ secret_list.push_back("Jefe");
+ std::string twentyaa;
+ fillString(twentyaa, 20, 0xaa);
+ secret_list.push_back(twentyaa);
+ const uint8_t secret_array[] = {
+ 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
+ };
+ secret_list.push_back(std::string(secret_array,
+ secret_array + sizeof(secret_array)));
+ std::string twenty0c;
+ fillString(twenty0c, 20, 0x0c);
+ secret_list.push_back(twenty0c);
+ std::string alotofaa;
+ fillString(alotofaa, 131, 0xaa);
+ secret_list.push_back(alotofaa);
+ secret_list.push_back(alotofaa);
+
+ // Make sure we provide a consistent size of test data
+ ASSERT_EQ(secret_list.size(), data_list.size());
+ ASSERT_EQ(secret_list.size(), hmac_list.size());
+
+ for (std::vector<std::string>::size_type i = 0;
+ i < data_list.size(); ++i) {
+ SCOPED_TRACE("RFC4231 HMAC test for algorithm ID: " +
+ lexical_cast<std::string>(hash_algorithm) +
+ ", data ID: " + lexical_cast<std::string>(i));
+ // Until #920 is resolved we have to skip truncation cases.
+ if (data_list[i] == "Test With Truncation") {
+ continue;
+ }
+ doHMACTest(data_list[i], secret_list[i].c_str(), secret_list[i].size(),
+ hash_algorithm, &hmac_list[i][0], hmac_list[i].size());
+ }
+}
+
+TEST(HMACTest, HMAC_SHA256_RFC4231_SIGN) {
+ std::vector<std::vector<uint8_t> > hmac_expected_list(7);
+
+ int i = 0;
+ decodeHex(
+ "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
+ hmac_expected_list[i++]);
+ decodeHex(
+ "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
+ hmac_expected_list[i++]);
+ decodeHex(
+ "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
+ hmac_expected_list[i++]);
+ decodeHex(
+ "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
+ hmac_expected_list[i++]);
+ decodeHex("a3b6167473100ee06e0c796c2955552b", hmac_expected_list[i++]);
+ decodeHex(
+ "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
+ hmac_expected_list[i++]);
+ decodeHex(
+ "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
+ hmac_expected_list[i++]);
+
+ doRFC4231Tests(SHA256, hmac_expected_list);
+}
+
+//
+// Test values taken from RFC 4231, test optional algorithm 224,384,512
+//
+TEST(HMACTest, HMAC_SHA224_RFC4231_SIGN) {
+ std::vector<std::vector<uint8_t> > hmac_expected_list(7);
+
+ int i = 0;
+ decodeHex("896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22",
+ hmac_expected_list[i++]);
+ decodeHex("a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44",
+ hmac_expected_list[i++]);
+ decodeHex("7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea",
+ hmac_expected_list[i++]);
+ decodeHex("6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a",
+ hmac_expected_list[i++]);
+ decodeHex("0e2aea68a90c8d37c988bcdb9fca6fa8", hmac_expected_list[i++]);
+ decodeHex("95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e",
+ hmac_expected_list[i++]);
+ decodeHex("3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1",
+ hmac_expected_list[i++]);
+
+ doRFC4231Tests(SHA224, hmac_expected_list);
+}
+
+TEST(HMACTest, HMAC_SHA384_RFC4231_SIGN) {
+ std::vector<std::vector<uint8_t> > hmac_expected_list(7);
+
+ int i = 0;
+ decodeHex("afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc5"
+ "9cfaea9ea9076ede7f4af152e8b2fa9cb6", hmac_expected_list[i++]);
+ decodeHex("af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373632244"
+ "5e8e2240ca5e69e2c78b3239ecfab21649", hmac_expected_list[i++]);
+ decodeHex("88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5596614"
+ "4b2a5ab39dc13814b94e3ab6e101a34f27", hmac_expected_list[i++]);
+ decodeHex("3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b"
+ "4e6801dd23c4a7d679ccf8a386c674cffb", hmac_expected_list[i++]);
+ decodeHex("3abf34c3503b2a23a46efc619baef897", hmac_expected_list[i++]);
+ decodeHex("4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4"
+ "c60c2ef6ab4030fe8296248df163f44952", hmac_expected_list[i++]);
+ decodeHex("6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99"
+ "c5a678cc31e799176d3860e6110c46523e", hmac_expected_list[i++]);
+
+ doRFC4231Tests(SHA384, hmac_expected_list);
+}
+
+TEST(HMACTest, HMAC_SHA512_RFC4231_SIGN) {
+ std::vector<std::vector<uint8_t> > hmac_expected_list(7);
+
+ int i = 0;
+ decodeHex("87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17c"
+ "dedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a12"
+ "6854", hmac_expected_list[i++]);
+ decodeHex("164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505"
+ "549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bc"
+ "e737", hmac_expected_list[i++]);
+ decodeHex("fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d"
+ "39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e132"
+ "92fb", hmac_expected_list[i++]);
+ decodeHex("b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3"
+ "dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a2"
+ "98dd", hmac_expected_list[i++]);
+ decodeHex("415fad6271580a531d4179bc891d87a6", hmac_expected_list[i++]);
+ decodeHex("80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3"
+ "526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d78"
+ "6598", hmac_expected_list[i++]);
+ decodeHex("e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc9"
+ "44b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c"
+ "6a58", hmac_expected_list[i++]);
+
+ doRFC4231Tests(SHA512, hmac_expected_list);
+}
+
+TEST(HMACTest, HMAC_SHA256_RFC2202_SIGN_TRUNCATED) {
+ const uint8_t secret5[] = { 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+ 0x0c, 0x0c };
+ const uint8_t hmac_expected5[] = { 0xa3, 0xb6, 0x16, 0x74, 0x73,
+ 0x10, 0x0e, 0xe0, 0x6e, 0x0c,
+ 0x79, 0x6c, 0x29, 0x55, 0x55,
+ 0x2b };
+ doHMACTest("Test With Truncation", secret5, 20, SHA256,
+ hmac_expected5, 16);
+}
+
+namespace {
+ /// @brief Get the hash algorithm
+ /// @param alg Hash algorithm enum
+ /// @return Hash algorithm enum
+ HashAlgorithm
+ signHashAlgorithm(HashAlgorithm alg) {
+ boost::shared_ptr<HMAC> hmac_sign(
+ CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg),
+ deleteHMAC);
+ return (hmac_sign->getHashAlgorithm());
+ }
+}
+
+TEST(HMACTest, HashAlgorithm) {
+ EXPECT_EQ(MD5, signHashAlgorithm(MD5));
+ EXPECT_EQ(SHA1, signHashAlgorithm(SHA1));
+ EXPECT_EQ(SHA256, signHashAlgorithm(SHA256));
+ EXPECT_EQ(SHA224, signHashAlgorithm(SHA224));
+ EXPECT_EQ(SHA384, signHashAlgorithm(SHA384));
+ EXPECT_EQ(SHA512, signHashAlgorithm(SHA512));
+}
+
+namespace {
+ /// @brief Compute the vector signature length
+ /// @param alg Hash algorithm enum
+ /// @param len Wanted length
+ /// @return Effective length
+ size_t
+ sigVectorLength(HashAlgorithm alg, size_t len) {
+ boost::shared_ptr<HMAC> hmac_sign(
+ CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg),
+ deleteHMAC);
+ hmac_sign->update("asdf", 4);
+ const std::vector<uint8_t> sig = hmac_sign->sign(len);
+ return (sig.size());
+ }
+
+ /// @brief Compute the buffer signature length
+ /// @param alg Hash algorithm enum
+ /// @param len Wanted length
+ /// @return Effective length
+ size_t
+ sigBufferLength(HashAlgorithm alg, size_t len) {
+ boost::shared_ptr<HMAC> hmac_sign(
+ CryptoLink::getCryptoLink().createHMAC("asdf", 4, alg),
+ deleteHMAC);
+ hmac_sign->update("asdf", 4);
+ OutputBuffer sig(0);
+ hmac_sign->sign(sig, len);
+ return (sig.getLength());
+ }
+
+ // There is no equivalent for array signature because it is copied
+ // in place
+}
+
+TEST(HMACTest, HMACSigLengthArgument) {
+ EXPECT_EQ(8, sigVectorLength(MD5, 8));
+ EXPECT_EQ(16, sigVectorLength(MD5, 16));
+ EXPECT_EQ(16, sigVectorLength(MD5, 40));
+ EXPECT_EQ(16, sigVectorLength(MD5, 2000));
+
+ EXPECT_EQ(8, sigBufferLength(SHA1, 8));
+ EXPECT_EQ(20, sigBufferLength(SHA1, 20));
+ EXPECT_EQ(20, sigBufferLength(SHA1, 40));
+ EXPECT_EQ(20, sigBufferLength(SHA1, 2000));
+
+ EXPECT_EQ(8, sigBufferLength(SHA256, 8));
+ EXPECT_EQ(32, sigBufferLength(SHA256, 32));
+ EXPECT_EQ(32, sigBufferLength(SHA256, 40));
+ EXPECT_EQ(32, sigBufferLength(SHA256, 3200));
+
+ EXPECT_EQ(8, sigBufferLength(MD5, 8));
+ EXPECT_EQ(16, sigBufferLength(MD5, 16));
+ EXPECT_EQ(16, sigBufferLength(MD5, 40));
+ EXPECT_EQ(16, sigBufferLength(MD5, 2000));
+
+ EXPECT_EQ(8, sigBufferLength(SHA1, 8));
+ EXPECT_EQ(20, sigBufferLength(SHA1, 20));
+ EXPECT_EQ(20, sigBufferLength(SHA1, 40));
+ EXPECT_EQ(20, sigBufferLength(SHA1, 2000));
+
+ EXPECT_EQ(8, sigBufferLength(SHA256, 8));
+ EXPECT_EQ(32, sigBufferLength(SHA256, 32));
+ EXPECT_EQ(32, sigBufferLength(SHA256, 40));
+ EXPECT_EQ(32, sigBufferLength(SHA256, 3200));
+}
+
+// Error cases (not only BadKey)
+TEST(HMACTest, BadKey) {
+ OutputBuffer data_buf(0);
+ OutputBuffer hmac_sig(0);
+ CryptoLink& crypto = CryptoLink::getCryptoLink();
+
+ EXPECT_THROW(crypto.createHMAC(NULL, 0, MD5), BadKey);
+ EXPECT_THROW(crypto.createHMAC(NULL, 0, UNKNOWN_HASH), UnsupportedAlgorithm);
+
+ EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(),
+ NULL, 0, MD5, hmac_sig), BadKey);
+ EXPECT_THROW(signHMAC(data_buf.getData(), data_buf.getLength(),
+ NULL, 0, UNKNOWN_HASH, hmac_sig),
+ UnsupportedAlgorithm);
+
+ EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(),
+ NULL, 0, MD5, hmac_sig.getData(),
+ hmac_sig.getLength()), BadKey);
+ EXPECT_THROW(verifyHMAC(data_buf.getData(), data_buf.getLength(),
+ NULL, 0, UNKNOWN_HASH, hmac_sig.getData(),
+ hmac_sig.getLength()),
+ UnsupportedAlgorithm);
+}
diff --git a/src/lib/cryptolink/tests/run_unittests.cc b/src/lib/cryptolink/tests/run_unittests.cc
new file mode 100644
index 0000000..b2e8e4f
--- /dev/null
+++ b/src/lib/cryptolink/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// Copyright (C) 2011-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 <gtest/gtest.h>
+
+#include <log/logger_support.h>
+#include <util/unittests/run_all.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/d2srv/Makefile.am b/src/lib/d2srv/Makefile.am
new file mode 100644
index 0000000..5141ac4
--- /dev/null
+++ b/src/lib/d2srv/Makefile.am
@@ -0,0 +1,91 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST = d2srv.dox
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-d2srv.la
+libkea_d2srv_la_SOURCES =
+libkea_d2srv_la_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+libkea_d2srv_la_SOURCES += d2_config.cc d2_config.h
+libkea_d2srv_la_SOURCES += d2_log.cc d2_log.h
+libkea_d2srv_la_SOURCES += d2_messages.cc d2_messages.h
+libkea_d2srv_la_SOURCES += d2_update_message.cc d2_update_message.h
+libkea_d2srv_la_SOURCES += d2_simple_parser.cc d2_simple_parser.h
+libkea_d2srv_la_SOURCES += d2_stats.cc d2_stats.h
+libkea_d2srv_la_SOURCES += d2_tsig_key.cc d2_tsig_key.h
+libkea_d2srv_la_SOURCES += d2_zone.cc d2_zone.h
+libkea_d2srv_la_SOURCES += dns_client.cc dns_client.h
+libkea_d2srv_la_SOURCES += nc_trans.cc nc_trans.h
+EXTRA_DIST += d2_messages.mes
+
+libkea_d2srv_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_d2srv_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+libkea_d2srv_la_LIBADD = $(top_builddir)/src/lib/process/libkea-process.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/http/libkea-http.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_d2srv_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_d2srv_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+libkea_d2srv_la_LDFLAGS = -no-undefined -version-info 30:0:0
+libkea_d2srv_la_LDFLAGS += $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f d2_messages.h d2_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: d2_messages.h d2_messages.cc
+ @echo Message files regenerated
+
+d2_messages.h d2_messages.cc: d2_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/d2srv/d2_messages.mes
+
+else
+
+messages d2_messages.h d2_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_d2srv_includedir = $(pkgincludedir)/d2srv
+libkea_d2srv_include_HEADERS = \
+ d2_cfg_mgr.h \
+ d2_config.h \
+ d2_log.h \
+ d2_messages.h \
+ d2_simple_parser.h
diff --git a/src/lib/d2srv/Makefile.in b/src/lib/d2srv/Makefile.in
new file mode 100644
index 0000000..4abc134
--- /dev/null
+++ b/src/lib/d2srv/Makefile.in
@@ -0,0 +1,1139 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/d2srv
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_d2srv_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)$(libkea_d2srv_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_d2srv_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_d2srv_la_OBJECTS = libkea_d2srv_la-d2_cfg_mgr.lo \
+ libkea_d2srv_la-d2_config.lo libkea_d2srv_la-d2_log.lo \
+ libkea_d2srv_la-d2_messages.lo \
+ libkea_d2srv_la-d2_update_message.lo \
+ libkea_d2srv_la-d2_simple_parser.lo \
+ libkea_d2srv_la-d2_stats.lo libkea_d2srv_la-d2_tsig_key.lo \
+ libkea_d2srv_la-d2_zone.lo libkea_d2srv_la-dns_client.lo \
+ libkea_d2srv_la-nc_trans.lo
+libkea_d2srv_la_OBJECTS = $(am_libkea_d2srv_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_d2srv_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_d2srv_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_d2srv_la-d2_cfg_mgr.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo \
+ ./$(DEPDIR)/libkea_d2srv_la-nc_trans.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_d2srv_la_SOURCES)
+DIST_SOURCES = $(libkea_d2srv_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 = $(libkea_d2srv_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . testutils tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+EXTRA_DIST = d2srv.dox d2_messages.mes
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-d2srv.la
+libkea_d2srv_la_SOURCES = d2_cfg_mgr.cc d2_cfg_mgr.h d2_config.cc \
+ d2_config.h d2_log.cc d2_log.h d2_messages.cc d2_messages.h \
+ d2_update_message.cc d2_update_message.h d2_simple_parser.cc \
+ d2_simple_parser.h d2_stats.cc d2_stats.h d2_tsig_key.cc \
+ d2_tsig_key.h d2_zone.cc d2_zone.h dns_client.cc dns_client.h \
+ nc_trans.cc nc_trans.h
+libkea_d2srv_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_d2srv_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_d2srv_la_LIBADD = \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+libkea_d2srv_la_LDFLAGS = -no-undefined -version-info 30:0:0 \
+ $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_d2srv_includedir = $(pkgincludedir)/d2srv
+libkea_d2srv_include_HEADERS = \
+ d2_cfg_mgr.h \
+ d2_config.h \
+ d2_log.h \
+ d2_messages.h \
+ d2_simple_parser.h
+
+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/d2srv/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/d2srv/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-d2srv.la: $(libkea_d2srv_la_OBJECTS) $(libkea_d2srv_la_DEPENDENCIES) $(EXTRA_libkea_d2srv_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_d2srv_la_LINK) -rpath $(libdir) $(libkea_d2srv_la_OBJECTS) $(libkea_d2srv_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_d2srv_la-nc_trans.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)$(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 $@ $<
+
+libkea_d2srv_la-d2_cfg_mgr.lo: d2_cfg_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_cfg_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Tpo -c -o libkea_d2srv_la-d2_cfg_mgr.lo `test -f 'd2_cfg_mgr.cc' || echo '$(srcdir)/'`d2_cfg_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Tpo $(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_cfg_mgr.cc' object='libkea_d2srv_la-d2_cfg_mgr.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_cfg_mgr.lo `test -f 'd2_cfg_mgr.cc' || echo '$(srcdir)/'`d2_cfg_mgr.cc
+
+libkea_d2srv_la-d2_config.lo: d2_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_config.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_config.Tpo -c -o libkea_d2srv_la-d2_config.lo `test -f 'd2_config.cc' || echo '$(srcdir)/'`d2_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_config.Tpo $(DEPDIR)/libkea_d2srv_la-d2_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_config.cc' object='libkea_d2srv_la-d2_config.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_config.lo `test -f 'd2_config.cc' || echo '$(srcdir)/'`d2_config.cc
+
+libkea_d2srv_la-d2_log.lo: d2_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_log.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_log.Tpo -c -o libkea_d2srv_la-d2_log.lo `test -f 'd2_log.cc' || echo '$(srcdir)/'`d2_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_log.Tpo $(DEPDIR)/libkea_d2srv_la-d2_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_log.cc' object='libkea_d2srv_la-d2_log.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_log.lo `test -f 'd2_log.cc' || echo '$(srcdir)/'`d2_log.cc
+
+libkea_d2srv_la-d2_messages.lo: d2_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_messages.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_messages.Tpo -c -o libkea_d2srv_la-d2_messages.lo `test -f 'd2_messages.cc' || echo '$(srcdir)/'`d2_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_messages.Tpo $(DEPDIR)/libkea_d2srv_la-d2_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_messages.cc' object='libkea_d2srv_la-d2_messages.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_messages.lo `test -f 'd2_messages.cc' || echo '$(srcdir)/'`d2_messages.cc
+
+libkea_d2srv_la-d2_update_message.lo: d2_update_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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_update_message.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_update_message.Tpo -c -o libkea_d2srv_la-d2_update_message.lo `test -f 'd2_update_message.cc' || echo '$(srcdir)/'`d2_update_message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_update_message.Tpo $(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_message.cc' object='libkea_d2srv_la-d2_update_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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_update_message.lo `test -f 'd2_update_message.cc' || echo '$(srcdir)/'`d2_update_message.cc
+
+libkea_d2srv_la-d2_simple_parser.lo: d2_simple_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_simple_parser.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Tpo -c -o libkea_d2srv_la-d2_simple_parser.lo `test -f 'd2_simple_parser.cc' || echo '$(srcdir)/'`d2_simple_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Tpo $(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_simple_parser.cc' object='libkea_d2srv_la-d2_simple_parser.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_simple_parser.lo `test -f 'd2_simple_parser.cc' || echo '$(srcdir)/'`d2_simple_parser.cc
+
+libkea_d2srv_la-d2_stats.lo: d2_stats.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_stats.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_stats.Tpo -c -o libkea_d2srv_la-d2_stats.lo `test -f 'd2_stats.cc' || echo '$(srcdir)/'`d2_stats.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_stats.Tpo $(DEPDIR)/libkea_d2srv_la-d2_stats.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_stats.cc' object='libkea_d2srv_la-d2_stats.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_stats.lo `test -f 'd2_stats.cc' || echo '$(srcdir)/'`d2_stats.cc
+
+libkea_d2srv_la-d2_tsig_key.lo: d2_tsig_key.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_tsig_key.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Tpo -c -o libkea_d2srv_la-d2_tsig_key.lo `test -f 'd2_tsig_key.cc' || echo '$(srcdir)/'`d2_tsig_key.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Tpo $(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_tsig_key.cc' object='libkea_d2srv_la-d2_tsig_key.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_tsig_key.lo `test -f 'd2_tsig_key.cc' || echo '$(srcdir)/'`d2_tsig_key.cc
+
+libkea_d2srv_la-d2_zone.lo: d2_zone.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-d2_zone.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-d2_zone.Tpo -c -o libkea_d2srv_la-d2_zone.lo `test -f 'd2_zone.cc' || echo '$(srcdir)/'`d2_zone.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-d2_zone.Tpo $(DEPDIR)/libkea_d2srv_la-d2_zone.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_zone.cc' object='libkea_d2srv_la-d2_zone.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-d2_zone.lo `test -f 'd2_zone.cc' || echo '$(srcdir)/'`d2_zone.cc
+
+libkea_d2srv_la-dns_client.lo: dns_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-dns_client.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-dns_client.Tpo -c -o libkea_d2srv_la-dns_client.lo `test -f 'dns_client.cc' || echo '$(srcdir)/'`dns_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-dns_client.Tpo $(DEPDIR)/libkea_d2srv_la-dns_client.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_client.cc' object='libkea_d2srv_la-dns_client.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-dns_client.lo `test -f 'dns_client.cc' || echo '$(srcdir)/'`dns_client.cc
+
+libkea_d2srv_la-nc_trans.lo: nc_trans.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_d2srv_la-nc_trans.lo -MD -MP -MF $(DEPDIR)/libkea_d2srv_la-nc_trans.Tpo -c -o libkea_d2srv_la-nc_trans.lo `test -f 'nc_trans.cc' || echo '$(srcdir)/'`nc_trans.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_d2srv_la-nc_trans.Tpo $(DEPDIR)/libkea_d2srv_la-nc_trans.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_trans.cc' object='libkea_d2srv_la-nc_trans.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_d2srv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_d2srv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_d2srv_la-nc_trans.lo `test -f 'nc_trans.cc' || echo '$(srcdir)/'`nc_trans.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_d2srv_includeHEADERS: $(libkea_d2srv_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_d2srv_include_HEADERS)'; test -n "$(libkea_d2srv_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_d2srv_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_d2srv_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)$(libkea_d2srv_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_d2srv_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_d2srv_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_d2srv_include_HEADERS)'; test -n "$(libkea_d2srv_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_d2srv_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_d2srv_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_cfg_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-nc_trans.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-libkea_d2srv_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_d2srv_la-d2_cfg_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_simple_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_stats.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_tsig_key.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_update_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-d2_zone.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-dns_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_d2srv_la-nc_trans.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_d2srv_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_d2srv_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_d2srv_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f d2_messages.h d2_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: d2_messages.h d2_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@d2_messages.h d2_messages.cc: d2_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/d2srv/d2_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages d2_messages.h d2_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/d2srv/d2_cfg_mgr.cc b/src/lib/d2srv/d2_cfg_mgr.cc
new file mode 100644
index 0000000..17ac59b
--- /dev/null
+++ b/src/lib/d2srv/d2_cfg_mgr.cc
@@ -0,0 +1,334 @@
+// Copyright (C) 2014-2023 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 <d2srv/d2_log.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_simple_parser.h>
+#include <cc/command_interpreter.h>
+#include <config/base_command_mgr.h>
+#include <util/encode/hex.h>
+
+#include <boost/foreach.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::process;
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+typedef std::vector<uint8_t> ByteAddress;
+
+} // end of unnamed namespace
+
+// *********************** D2CfgContext *************************
+
+D2CfgContext::D2CfgContext()
+ : d2_params_(new D2Params()),
+ forward_mgr_(new DdnsDomainListMgr("forward-ddns")),
+ reverse_mgr_(new DdnsDomainListMgr("reverse-ddns")),
+ keys_(new TSIGKeyInfoMap()),
+ control_socket_(ConstElementPtr()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : ConfigBase(rhs) {
+ d2_params_ = rhs.d2_params_;
+ if (rhs.forward_mgr_) {
+ forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+ forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+ }
+
+ if (rhs.reverse_mgr_) {
+ reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+ reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+ }
+
+ keys_ = rhs.keys_;
+
+ control_socket_ = rhs.control_socket_;
+
+ hooks_config_ = rhs.hooks_config_;
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+ElementPtr
+D2CfgContext::toElement() const {
+ ElementPtr d2 = ConfigBase::toElement();
+ // Set user-context
+ contextToElement(d2);
+ // Set ip-address
+ const IOAddress& ip_address = d2_params_->getIpAddress();
+ d2->set("ip-address", Element::create(ip_address.toText()));
+ // Set port
+ size_t port = d2_params_->getPort();
+ d2->set("port", Element::create(static_cast<int64_t>(port)));
+ // Set dns-server-timeout
+ size_t dns_server_timeout = d2_params_->getDnsServerTimeout();
+ d2->set("dns-server-timeout",
+ Element::create(static_cast<int64_t>(dns_server_timeout)));
+ // Set ncr-protocol
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol =
+ d2_params_->getNcrProtocol();
+ d2->set("ncr-protocol",
+ Element::create(dhcp_ddns::ncrProtocolToString(ncr_protocol)));
+ // Set ncr-format
+ const dhcp_ddns::NameChangeFormat& ncr_format = d2_params_->getNcrFormat();
+ d2->set("ncr-format",
+ Element::create(dhcp_ddns::ncrFormatToString(ncr_format)));
+ // Set forward-ddns
+ ElementPtr forward_ddns = Element::createMap();
+ forward_ddns->set("ddns-domains", forward_mgr_->toElement());
+ d2->set("forward-ddns", forward_ddns);
+ // Set reverse-ddns
+ ElementPtr reverse_ddns = Element::createMap();
+ reverse_ddns->set("ddns-domains", reverse_mgr_->toElement());
+ d2->set("reverse-ddns", reverse_ddns);
+ // Set tsig-keys
+ ElementPtr tsig_keys = Element::createList();
+ for (TSIGKeyInfoMap::const_iterator key = keys_->begin();
+ key != keys_->end(); ++key) {
+ tsig_keys->add(key->second->toElement());
+ }
+ d2->set("tsig-keys", tsig_keys);
+ // Set control-socket (skip if null as empty is not legal)
+ if (!isNull(control_socket_)) {
+ d2->set("control-socket", UserContext::toElement(control_socket_));
+ }
+ // Set hooks-libraries
+ d2->set("hooks-libraries", hooks_config_.toElement());
+ // Set DhcpDdns
+ ElementPtr result = Element::createMap();
+ result->set("DhcpDdns", d2);
+
+ return (result);
+}
+
+// *********************** D2CfgMgr *************************
+
+const char* D2CfgMgr::IPV4_REV_ZONE_SUFFIX = "in-addr.arpa.";
+
+const char* D2CfgMgr::IPV6_REV_ZONE_SUFFIX = "ip6.arpa.";
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(ConfigPtr(new D2CfgContext())) {
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+ConfigPtr
+D2CfgMgr::createNewContext() {
+ return (ConfigPtr(new D2CfgContext()));
+}
+
+bool
+D2CfgMgr::forwardUpdatesEnabled() {
+ // Forward updates are not enabled if no forward servers are defined.
+ return (getD2CfgContext()->getForwardMgr()->size() > 0);
+}
+
+bool
+D2CfgMgr::reverseUpdatesEnabled() {
+ // Reverse updates are not enabled if no reverse servers are defined.
+ return (getD2CfgContext()->getReverseMgr()->size() > 0);
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+ }
+
+ // Fetch the forward manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& ip_address, DdnsDomainPtr& domain) {
+ // Note, reverseIpAddress will throw if the ip_address is invalid.
+ std::string reverse_address = reverseIpAddress(ip_address);
+
+ // Fetch the reverse manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+ return (mgr->matchDomain(reverse_address, domain));
+}
+
+std::string
+D2CfgMgr::reverseIpAddress(const std::string& address) {
+ try {
+ // Convert string address into an IOAddress and invoke the
+ // appropriate reverse method.
+ isc::asiolink::IOAddress ioaddr(address);
+ if (ioaddr.isV4()) {
+ return (reverseV4Address(ioaddr));
+ }
+
+ return (reverseV6Address(ioaddr));
+
+ } catch (const isc::Exception& ex) {
+ isc_throw(D2CfgError, "D2CfgMgr cannot reverse address: "
+ << address << " : " << ex.what());
+ }
+}
+
+std::string
+D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
+ if (!ioaddr.isV4()) {
+ isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
+ << ioaddr);
+ }
+
+ // Get the address in byte vector form.
+ const ByteAddress bytes = ioaddr.toBytes();
+
+ // Walk backwards through vector outputting each octet and a dot.
+ std::ostringstream stream;
+
+ // We have to set the following variable to get
+ // const_reverse_iterator type of rend(), otherwise Solaris GCC
+ // complains on operator!= by trying to use the non-const variant.
+ const ByteAddress::const_reverse_iterator end = bytes.rend();
+
+ for (ByteAddress::const_reverse_iterator rit = bytes.rbegin();
+ rit != end;
+ ++rit)
+ {
+ stream << static_cast<unsigned int>(*rit) << ".";
+ }
+
+ // Tack on the suffix and we're done.
+ stream << IPV4_REV_ZONE_SUFFIX;
+ return(stream.str());
+}
+
+std::string
+D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
+ if (!ioaddr.isV6()) {
+ isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: " << ioaddr);
+ }
+
+ // Turn the address into a string of digits.
+ const ByteAddress bytes = ioaddr.toBytes();
+ const std::string digits = isc::util::encode::encodeHex(bytes);
+
+ // Walk backwards through string outputting each digits and a dot.
+ std::ostringstream stream;
+
+ // We have to set the following variable to get
+ // const_reverse_iterator type of rend(), otherwise Solaris GCC
+ // complains on operator!= by trying to use the non-const variant.
+ const std::string::const_reverse_iterator end = digits.rend();
+
+ for (std::string::const_reverse_iterator rit = digits.rbegin();
+ rit != end;
+ ++rit)
+ {
+ stream << static_cast<char>(*rit) << ".";
+ }
+
+ // Tack on the suffix and we're done.
+ stream << IPV6_REV_ZONE_SUFFIX;
+ return(stream.str());
+}
+
+const D2ParamsPtr&
+D2CfgMgr::getD2Params() {
+ return (getD2CfgContext()->getD2Params());
+}
+
+const isc::data::ConstElementPtr
+D2CfgMgr::getControlSocketInfo() {
+ return (getD2CfgContext()->getControlSocketInfo());
+}
+
+std::string
+D2CfgMgr::getConfigSummary(const uint32_t) {
+ return (getD2Params()->getConfigSummary());
+}
+
+void
+D2CfgMgr::setCfgDefaults(ElementPtr mutable_config) {
+ D2SimpleParser::setAllDefaults(mutable_config);
+}
+
+isc::data::ConstElementPtr
+D2CfgMgr::parse(isc::data::ConstElementPtr config_set, bool check_only) {
+ // Do a sanity check first.
+ if (!config_set) {
+ isc_throw(D2CfgError, "Mandatory config parameter not provided");
+ }
+
+ D2CfgContextPtr ctx = getD2CfgContext();
+
+ // Set the defaults
+ ElementPtr cfg = boost::const_pointer_cast<Element>(config_set);
+ D2SimpleParser::setAllDefaults(cfg);
+
+ // And parse the configuration.
+ ConstElementPtr answer;
+ std::string excuse;
+ try {
+ // Do the actual parsing
+ D2SimpleParser parser;
+ parser.parse(ctx, cfg, check_only);
+ } catch (const isc::Exception& ex) {
+ excuse = ex.what();
+ answer = createAnswer(CONTROL_RESULT_ERROR, excuse);
+ } catch (...) {
+ excuse = "undefined configuration parsing error";
+ answer = createAnswer(CONTROL_RESULT_ERROR, excuse);
+ }
+
+ // At this stage the answer was created only in case of exception.
+ if (answer) {
+ if (check_only) {
+ LOG_ERROR(d2_logger, DHCP_DDNS_CONFIG_CHECK_FAIL).arg(excuse);
+ } else {
+ LOG_ERROR(d2_logger, DHCP_DDNS_CONFIG_FAIL).arg(excuse);
+ }
+ return (answer);
+ }
+
+ if (check_only) {
+ answer = createAnswer(CONTROL_RESULT_SUCCESS,
+ "Configuration check successful");
+ } else {
+
+ // Calculate hash of the configuration that was just set.
+ ConstElementPtr config = getContext()->toElement();
+ std::string hash = BaseCommandMgr::getHash(config);
+ ElementPtr params = Element::createMap();
+ params->set("hash", Element::create(hash));
+
+ answer = createAnswer(CONTROL_RESULT_SUCCESS,
+ "Configuration applied successfully.", params);
+ }
+
+ return (answer);
+}
+
+std::list<std::list<std::string>>
+D2CfgMgr::jsonPathsToRedact() const {
+ static std::list<std::list<std::string>> const list({
+ {"tsig-keys", "[]"},
+ {"hooks-libraries", "[]", "parameters", "*"},
+ });
+ return list;
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_cfg_mgr.h b/src/lib/d2srv/d2_cfg_mgr.h
new file mode 100644
index 0000000..eade86c
--- /dev/null
+++ b/src/lib/d2srv/d2_cfg_mgr.h
@@ -0,0 +1,339 @@
+// Copyright (C) 2014-2021 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 D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2srv/d2_config.h>
+#include <hooks/hooks_config.h>
+#include <process/d_cfg_mgr.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, ConfigBase.
+class D2CfgContext : public process::ConfigBase {
+public:
+ /// @brief Constructor
+ D2CfgContext();
+
+ /// @brief Destructor
+ virtual ~D2CfgContext();
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual process::ConfigPtr clone() {
+ return (process::ConfigPtr(new D2CfgContext(*this)));
+ }
+
+ /// @brief Fetches a reference to the D2Params
+ D2ParamsPtr& getD2Params() {
+ return (d2_params_);
+ }
+
+ /// @brief Fetches the forward DNS domain list manager.
+ ///
+ /// @return returns a pointer to the forward manager.
+ DdnsDomainListMgrPtr getForwardMgr() {
+ return (forward_mgr_);
+ }
+
+ /// @brief Sets the forward domain list manager
+ /// @param forward_mgr pointer to the new forward manager
+ void setForwardMgr(DdnsDomainListMgrPtr forward_mgr) {
+ forward_mgr_ = forward_mgr;
+ }
+
+ /// @brief Fetches the reverse DNS domain list manager.
+ ///
+ /// @return returns a pointer to the reverse manager.
+ DdnsDomainListMgrPtr getReverseMgr() {
+ return (reverse_mgr_);
+ }
+
+ /// @brief Sets the reverse domain list manager
+ /// @param reverse_mgr pointer to the new reverse manager
+ void setReverseMgr(DdnsDomainListMgrPtr reverse_mgr) {
+ reverse_mgr_ = reverse_mgr;
+ }
+
+ /// @brief Fetches the map of TSIG keys.
+ ///
+ /// @return returns a pointer to the key map.
+ TSIGKeyInfoMapPtr getKeys() {
+ return (keys_);
+ }
+
+ /// @brief Sets the map of TSIG keys
+ ///
+ /// @param keys pointer to the new TSIG key map
+ void setKeys(const TSIGKeyInfoMapPtr& keys) {
+ keys_ = keys;
+ }
+
+ /// @brief Returns information about control socket
+ /// @return pointer to the Element that holds control-socket map
+ const isc::data::ConstElementPtr getControlSocketInfo() const {
+ return (control_socket_);
+ }
+
+ /// @brief Sets information about the control socket
+ /// @param control_socket Element that holds control-socket map
+ void setControlSocketInfo(const isc::data::ConstElementPtr& control_socket) {
+ control_socket_ = control_socket;
+ }
+
+ /// @brief Returns non-const reference to configured hooks libraries.
+ ///
+ /// @return non-const reference to configured hooks libraries.
+ isc::hooks::HooksConfig& getHooksConfig() {
+ return (hooks_config_);
+ }
+
+ /// @brief Returns const reference to configured hooks libraries.
+ ///
+ /// @return const reference to configured hooks libraries.
+ const isc::hooks::HooksConfig& getHooksConfig() const {
+ return (hooks_config_);
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ D2CfgContext(const D2CfgContext& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ D2CfgContext& operator=(const D2CfgContext& rhs);
+
+ /// @brief Global level parameter storage
+ D2ParamsPtr d2_params_;
+
+ /// @brief Forward domain list manager.
+ DdnsDomainListMgrPtr forward_mgr_;
+
+ /// @brief Reverse domain list manager.
+ DdnsDomainListMgrPtr reverse_mgr_;
+
+ /// @brief Storage for the map of TSIGKeyInfos.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the control-socket information.
+ isc::data::ConstElementPtr control_socket_;
+
+ /// @brief Configured hooks libraries.
+ isc::hooks::HooksConfig hooks_config_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration. This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public process::DCfgMgrBase {
+public:
+ /// @brief Reverse zone suffix added to IPv4 addresses for reverse lookups
+ /// @todo This should be configurable.
+ static const char* IPV4_REV_ZONE_SUFFIX;
+
+ /// @brief Reverse zone suffix added to IPv6 addresses for reverse lookups
+ /// @todo This should be configurable.
+ static const char* IPV6_REV_ZONE_SUFFIX;
+
+ /// @brief Constructor
+ D2CfgMgr();
+
+ /// @brief Destructor
+ virtual ~D2CfgMgr();
+
+ /// @brief Convenience method that returns the D2 configuration context.
+ ///
+ /// @return returns a pointer to the configuration context.
+ D2CfgContextPtr getD2CfgContext() {
+ return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+ }
+
+ /// @brief Returns whether or not forward updates are enabled.
+ ///
+ /// This method currently uses the presence or absence of Forward DDNS
+ /// Domains to determine if forward updates are enabled or disabled.
+ /// @todo This could be expanded to include the check of a configurable
+ /// boolean value.
+ ///
+ /// @return true if forward updates are enabled, false otherwise.
+ bool forwardUpdatesEnabled();
+
+ /// @brief Returns whether or not reverse updates are enabled.
+ ///
+ /// This method currently uses the presence or absence of Reverse DDNS
+ /// Domains to determine if reverse updates are enabled or disabled.
+ /// @todo This could be expanded to include the check of a configurable
+ /// boolean value.
+ ///
+ /// @return true if reverse updates are enabled, false otherwise.
+ bool reverseUpdatesEnabled();
+
+ /// @brief Matches a given FQDN to a forward domain.
+ ///
+ /// This calls the matchDomain method of the forward domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchForward(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Matches a given IP address to a reverse domain.
+ ///
+ /// This calls the matchDomain method of the reverse domain manager to
+ /// match the given IPv4 or IPv6 address to a reverse domain.
+ ///
+ /// @param ip_address is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchReverse(const std::string& ip_address, DdnsDomainPtr& domain);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IP address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// @param address string containing a valid IPv4 or IPv6 address.
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if given an invalid address.
+ static std::string reverseIpAddress(const std::string& address);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IP address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// Example:
+ /// input: 192.168.1.15
+ /// output: 15.1.168.192.in-addr.arpa.
+ ///
+ /// @param ioaddr is the IPv4 IOaddress to convert
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if not given an IPv4 address.
+ static std::string reverseV4Address(const isc::asiolink::IOAddress& ioaddr);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IPv6 address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// IPv6 example:
+ /// input: 2001:db8:302:99::
+ /// output:
+ ///0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.
+ ///
+ /// @param ioaddr string containing a valid IPv6 address.
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if not given an IPv6 address.
+ static std::string reverseV6Address(const isc::asiolink::IOAddress& ioaddr);
+
+ /// @brief Convenience method fetches the D2Params from context
+ /// @return reference to const D2ParamsPtr
+ const D2ParamsPtr& getD2Params();
+
+ /// @brief Convenience method fetches information about control socket
+ /// from context
+ /// @return pointer to the Element that holds control-socket map
+ const isc::data::ConstElementPtr getControlSocketInfo();
+
+ /// @brief Returns configuration summary in the textual format.
+ ///
+ /// @param selection Bitfield which describes the parts of the configuration
+ /// to be returned. This parameter is ignored for the D2.
+ ///
+ /// @return Summary of the configuration in the textual format.
+ virtual std::string getConfigSummary(const uint32_t selection) override;
+
+ std::list<std::list<std::string>> jsonPathsToRedact() const final override;
+
+protected:
+ /// @brief Parses configuration of the D2.
+ ///
+ /// @param config Pointer to a configuration specified for D2.
+ /// @param check_only Boolean flag indicating if this method should
+ /// only verify correctness of the provided configuration.
+ /// @return Pointer to a result of configuration parsing.
+ virtual isc::data::ConstElementPtr
+ parse(isc::data::ConstElementPtr config, bool check_only) override;
+
+ /// @brief Adds default values to the given config
+ ///
+ /// Adds the D2 default values to the configuration Element map. This
+ /// method is invoked by @c DCfgMgrBase::parseConfig().
+ ///
+ /// @param mutable_config - configuration to which defaults should be added
+ virtual void setCfgDefaults(isc::data::ElementPtr mutable_config) override;
+
+ /// @brief Creates an new, blank D2CfgContext context
+ ///
+ /// This method is used at the beginning of configuration process to
+ /// create a fresh, empty copy of a D2CfgContext. This new context will
+ /// be populated during the configuration process and will replace the
+ /// existing context provided the configuration process completes without
+ /// error.
+ ///
+ /// @return Returns a ConfigPtr to the new context instance.
+ virtual process::ConfigPtr createNewContext() override;
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+} // end of isc::d2 namespace
+} // end of isc namespace
+
+#endif // D2_CFG_MGR_H
diff --git a/src/lib/d2srv/d2_config.cc b/src/lib/d2srv/d2_config.cc
new file mode 100644
index 0000000..a5633f3
--- /dev/null
+++ b/src/lib/d2srv/d2_config.cc
@@ -0,0 +1,674 @@
+// Copyright (C) 2013-2023 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 <d2srv/d2_log.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <sstream>
+#include <string>
+
+using namespace isc::process;
+using namespace isc::data;
+
+namespace isc {
+namespace d2 {
+
+// *********************** D2Params *************************
+
+D2Params::D2Params(const isc::asiolink::IOAddress& ip_address,
+ const size_t port,
+ const size_t dns_server_timeout,
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::NameChangeFormat& ncr_format)
+ : ip_address_(ip_address),
+ port_(port),
+ dns_server_timeout_(dns_server_timeout),
+ ncr_protocol_(ncr_protocol),
+ ncr_format_(ncr_format) {
+ validateContents();
+}
+
+D2Params::D2Params()
+ : ip_address_(isc::asiolink::IOAddress("127.0.0.1")),
+ port_(53001), dns_server_timeout_(500),
+ ncr_protocol_(dhcp_ddns::NCR_UDP),
+ ncr_format_(dhcp_ddns::FMT_JSON) {
+ validateContents();
+}
+
+D2Params::~D2Params(){};
+
+void
+D2Params::validateContents() {
+ if ((ip_address_.toText() == "0.0.0.0") || (ip_address_.toText() == "::")) {
+ isc_throw(D2CfgError,
+ "D2Params: IP address cannot be \"" << ip_address_ << "\"");
+ }
+
+ if (port_ == 0) {
+ isc_throw(D2CfgError, "D2Params: port cannot be 0");
+ }
+
+ if (dns_server_timeout_ < 1) {
+ isc_throw(D2CfgError,
+ "D2Params: DNS server timeout must be larger than 0");
+ }
+
+ if (ncr_format_ != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2CfgError, "D2Params: NCR Format:"
+ << dhcp_ddns::ncrFormatToString(ncr_format_)
+ << " is not yet supported");
+ }
+
+ if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2CfgError, "D2Params: NCR Protocol:"
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+ << " is not yet supported");
+ }
+}
+
+std::string
+D2Params::getConfigSummary() const {
+ std::ostringstream s;
+ s << "listening on " << getIpAddress() << ", port " << getPort()
+ << ", using " << ncrProtocolToString(ncr_protocol_);
+ return (s.str());
+}
+
+bool
+D2Params::operator == (const D2Params& other) const {
+ return ((ip_address_ == other.ip_address_) &&
+ (port_ == other.port_) &&
+ (dns_server_timeout_ == other.dns_server_timeout_) &&
+ (ncr_protocol_ == other.ncr_protocol_) &&
+ (ncr_format_ == other.ncr_format_));
+}
+
+bool
+D2Params::operator != (const D2Params& other) const {
+ return (!(*this == other));
+}
+
+std::string
+D2Params::toText() const {
+ std::ostringstream stream;
+
+ stream << ", ip-address: " << ip_address_.toText()
+ << ", port: " << port_
+ << ", dns-server-timeout_: " << dns_server_timeout_
+ << ", ncr-protocol: "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+ << ", ncr-format: " << ncr_format_
+ << dhcp_ddns::ncrFormatToString(ncr_format_);
+
+ return (stream.str());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Params& config) {
+ os << config.toText();
+ return (os);
+}
+
+// *********************** TSIGKeyInfo *************************
+// Note these values match corresponding values for Bind9's
+// dnssec-keygen
+const char* TSIGKeyInfo::HMAC_MD5_STR = "HMAC-MD5";
+const char* TSIGKeyInfo::HMAC_SHA1_STR = "HMAC-SHA1";
+const char* TSIGKeyInfo::HMAC_SHA224_STR = "HMAC-SHA224";
+const char* TSIGKeyInfo::HMAC_SHA256_STR = "HMAC-SHA256";
+const char* TSIGKeyInfo::HMAC_SHA384_STR = "HMAC-SHA384";
+const char* TSIGKeyInfo::HMAC_SHA512_STR = "HMAC-SHA512";
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret, uint32_t digestbits)
+ :name_(name), algorithm_(algorithm), secret_(secret),
+ digestbits_(digestbits), tsig_key_() {
+ remakeKey();
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+const dns::Name&
+TSIGKeyInfo::stringToAlgorithmName(const std::string& algorithm_id) {
+ if (boost::iequals(algorithm_id, HMAC_MD5_STR)) {
+ return (dns::TSIGKey::HMACMD5_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA1_STR)) {
+ return (dns::TSIGKey::HMACSHA1_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA224_STR)) {
+ return (dns::TSIGKey::HMACSHA224_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA256_STR)) {
+ return (dns::TSIGKey::HMACSHA256_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA384_STR)) {
+ return (dns::TSIGKey::HMACSHA384_NAME());
+ } else if (boost::iequals(algorithm_id, HMAC_SHA512_STR)) {
+ return (dns::TSIGKey::HMACSHA512_NAME());
+ }
+
+ isc_throw(BadValue, "Unknown TSIG Key algorithm: " << algorithm_id);
+}
+
+void
+TSIGKeyInfo::remakeKey() {
+ try {
+ // Since our secret value is base64 encoded already, we need to
+ // build the input string for the appropriate D2TsigKey constructor.
+ // If secret isn't a valid base64 value, the constructor will throw.
+ std::ostringstream stream;
+ stream << dns::Name(name_).toText() << ":"
+ << secret_ << ":"
+ << stringToAlgorithmName(algorithm_);
+ if (digestbits_ > 0) {
+ stream << ":" << digestbits_;
+ }
+
+ tsig_key_.reset(new D2TsigKey(stream.str()));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, "Cannot make D2TsigKey: " << ex.what());
+ }
+}
+
+ElementPtr
+TSIGKeyInfo::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user-context
+ contextToElement(result);
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set algorithm
+ result->set("algorithm", Element::create(algorithm_));
+ // Set secret
+ result->set("secret", Element::create(secret_));
+ // Set digest-bits
+ result->set("digest-bits",
+ Element::create(static_cast<int64_t>(digestbits_)));
+
+ return (result);
+}
+
+// *********************** DnsServerInfo *************************
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port,
+ bool enabled,
+ const TSIGKeyInfoPtr& tsig_key_info,
+ bool inherited_key)
+ : hostname_(hostname), ip_address_(ip_address), port_(port),
+ enabled_(enabled), tsig_key_info_(tsig_key_info),
+ inherited_key_(inherited_key) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+const std::string
+DnsServerInfo::getKeyName() const {
+ if (tsig_key_info_) {
+ return (tsig_key_info_->getName());
+ }
+
+ return ("");
+}
+
+std::string
+DnsServerInfo::toText() const {
+ std::ostringstream stream;
+ stream << (getIpAddress().toText()) << " port:" << getPort();
+ return (stream.str());
+}
+
+ElementPtr
+DnsServerInfo::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user-context
+ contextToElement(result);
+ // Set hostname
+ result->set("hostname", Element::create(hostname_));
+ // Set ip-address
+ result->set("ip-address", Element::create(ip_address_.toText()));
+ // Set port
+ result->set("port", Element::create(static_cast<int64_t>(port_)));
+ // Set key-name
+ if (tsig_key_info_ && !inherited_key_) {
+ result->set("key-name", Element::create(tsig_key_info_->getName()));
+ }
+
+ return (result);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const DnsServerInfo& server) {
+ os << server.toText();
+ return (os);
+}
+
+// *********************** DdnsDomain *************************
+
+DdnsDomain::DdnsDomain(const std::string& name,
+ DnsServerInfoStoragePtr servers,
+ const std::string& key_name)
+ : name_(name), servers_(servers), key_name_(key_name) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+ElementPtr
+DdnsDomain::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user-context
+ contextToElement(result);
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set servers
+ ElementPtr servers = Element::createList();
+ for (DnsServerInfoStorage::const_iterator server = servers_->begin();
+ server != servers_->end(); ++server) {
+ ElementPtr dns_server = (*server)->toElement();
+ servers->add(dns_server);
+ }
+ // the dns server list may not be empty
+ if (!servers->empty()) {
+ result->set("dns-servers", servers);
+ }
+ // Set key-name
+ if (!key_name_.empty()) {
+ result->set("key-name", Element::create(key_name_));
+ }
+
+ return (result);
+}
+
+// *********************** DdnsDomainLstMgr *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+ domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+ if (!domains) {
+ isc_throw(D2CfgError,
+ "DdnsDomainListMgr::setDomains: Domain list may not be null");
+ }
+
+ domains_ = domains;
+
+ // Look for the wild card domain. If present, set the member variable
+ // to remember it. This saves us from having to look for it every time
+ // we attempt a match.
+ DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+ if (gotit != domains_->end()) {
+ wildcard_domain_ = gotit->second;
+ }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+ // First check the case of one domain to rule them all.
+ if ((size() == 1) && (wildcard_domain_)) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ // Iterate over the domain map looking for the domain which matches
+ // the longest portion of the given fqdn.
+
+ size_t req_len = fqdn.size();
+ size_t match_len = 0;
+ DdnsDomainMapPair map_pair;
+ DdnsDomainPtr best_match;
+ BOOST_FOREACH (map_pair, *domains_) {
+ std::string domain_name = map_pair.first;
+ size_t dom_len = domain_name.size();
+
+ // If the domain name is longer than the fqdn, then it cant be match.
+ if (req_len < dom_len) {
+ continue;
+ }
+
+ // If the lengths are identical and the names match we're done.
+ if (req_len == dom_len) {
+ if (boost::iequals(fqdn, domain_name)) {
+ // exact match, done
+ domain = map_pair.second;
+ return (true);
+ }
+ } else {
+ // The fqdn is longer than the domain name. Adjust the start
+ // point of comparison by the excess in length. Only do the
+ // comparison if the adjustment lands on a boundary. This
+ // prevents "onetwo.net" from matching "two.net".
+ size_t offset = req_len - dom_len;
+ if ((fqdn[offset - 1] == '.') &&
+ (boost::iequals(fqdn.substr(offset), domain_name))) {
+ // Fqdn contains domain name, keep it if its better than
+ // any we have matched so far.
+ if (dom_len > match_len) {
+ match_len = dom_len;
+ best_match = map_pair.second;
+ }
+ }
+ }
+ }
+
+ if (!best_match) {
+ // There's no match. If they specified a wild card domain use it
+ // otherwise there's no domain for this entry.
+ if (wildcard_domain_) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ LOG_WARN(dhcp_to_d2_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+ return (false);
+ }
+
+ domain = best_match;
+ return (true);
+}
+
+ElementPtr
+DdnsDomainListMgr::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate on ddns domains
+ for (DdnsDomainMap::const_iterator domain = domains_->begin();
+ domain != domains_->end(); ++domain) {
+ ElementPtr ddns_domain = domain->second->toElement();
+ result->add(ddns_domain);
+ }
+
+ return (result);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser *************************
+
+TSIGKeyInfoPtr
+TSIGKeyInfoParser::parse(ConstElementPtr key_config) {
+ std::string name = getString(key_config, "name");
+ std::string algorithm = getString(key_config, "algorithm");
+ uint32_t digestbits = getInteger(key_config, "digest-bits");
+ std::string secret = getString(key_config, "secret");
+ ConstElementPtr user_context = key_config->get("user-context");
+
+ // Algorithm must be valid.
+ try {
+ TSIGKeyInfo::stringToAlgorithmName(algorithm);
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, "tsig-key : " << ex.what()
+ << " (" << getPosition("algorithm", key_config) << ")");
+ }
+
+ // Non-zero digest-bits must be an integral number of octets, greater
+ // than 80 and at least half of the algorithm key length. It defaults
+ // to zero and JSON parsing ensures it's a multiple of 8.
+ if ((digestbits > 0) &&
+ ((digestbits < 80) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA224_STR)
+ && (digestbits < 112)) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA256_STR)
+ && (digestbits < 128)) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA384_STR)
+ && (digestbits < 192)) ||
+ (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA512_STR)
+ && (digestbits < 256)))) {
+ isc_throw(D2CfgError, "tsig-key: digest-bits too small : ("
+ << getPosition("digest-bits", key_config)
+ << ")");
+ }
+
+ // Everything should be valid, so create the key instance.
+ // It is possible for the D2TsigKey constructor to fail such as
+ // with an invalid secret content.
+ TSIGKeyInfoPtr key_info;
+ try {
+ key_info.reset(new TSIGKeyInfo(name, algorithm, secret, digestbits));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, ex.what() << " ("
+ << key_config->getPosition() << ")");
+ }
+
+ // Add user-context
+ if (user_context) {
+ key_info->setContext(user_context);
+ }
+
+ return (key_info);
+}
+
+// *********************** TSIGKeyInfoListParser *************************
+
+TSIGKeyInfoMapPtr
+TSIGKeyInfoListParser::parse(ConstElementPtr key_list) {
+ TSIGKeyInfoMapPtr keys(new TSIGKeyInfoMap());
+ ConstElementPtr key_config;
+ TSIGKeyInfoParser key_parser;
+ BOOST_FOREACH(key_config, key_list->listValue()) {
+ TSIGKeyInfoPtr key = key_parser.parse(key_config);
+
+ // Duplicates are not allowed and should be flagged as an error.
+ if (keys->find(key->getName()) != keys->end()) {
+ isc_throw(D2CfgError, "Duplicate TSIG key name specified : "
+ << key->getName()
+ << " (" << getPosition("name", key_config) << ")");
+ }
+
+ (*keys)[key->getName()] = key;
+ }
+
+ return (keys);
+}
+
+// *********************** DnsServerInfoParser *************************
+
+DnsServerInfoPtr
+DnsServerInfoParser::parse(ConstElementPtr server_config,
+ ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys) {
+ std::string hostname = getString(server_config, "hostname");
+ std::string ip_address = getString(server_config, "ip-address");
+ uint32_t port = getInteger(server_config, "port");
+ std::string key_name = getString(server_config, "key-name");
+ ConstElementPtr user_context = server_config->get("user-context");
+
+ // Key name is optional. If it is not blank, then find the key in the
+ // list of defined keys.
+ TSIGKeyInfoPtr tsig_key_info;
+ bool inherited_key = true;
+ if (key_name.empty()) {
+ std::string domain_key_name = getString(domain_config, "key-name");
+ if (!domain_key_name.empty()) {
+ key_name = domain_key_name;
+ }
+ } else {
+ inherited_key = false;
+ }
+ if (!key_name.empty()) {
+ if (keys) {
+ TSIGKeyInfoMap::iterator kit = keys->find(key_name);
+ if (kit != keys->end()) {
+ tsig_key_info = kit->second;
+ }
+ }
+
+ if (!tsig_key_info) {
+ if (inherited_key) {
+ isc_throw(D2CfgError, "DdnsDomain : specifies an "
+ << "undefined key: " << key_name << " ("
+ << getPosition("key-name", domain_config) << ")");
+ } else {
+ isc_throw(D2CfgError, "Dns Server : specifies an "
+ << "undefined key: " << key_name << " ("
+ << getPosition("key-name", server_config) << ")");
+ }
+ }
+ }
+
+ // The configuration must specify one or the other.
+ if (hostname.empty() == ip_address.empty()) {
+ isc_throw(D2CfgError, "Dns Server must specify one or the other"
+ " of hostname or IP address"
+ << " (" << server_config->getPosition() << ")");
+ }
+
+ DnsServerInfoPtr server_info;
+ if (!hostname.empty()) {
+ /// @todo when resolvable hostname is supported we create the entry
+ /// as follows:
+ ///
+ /// @code
+ /// // When hostname is specified, create a valid, blank IOAddress
+ /// // and then create the DnsServerInfo.
+ /// serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ ///
+ /// @endcode
+ ///
+ /// Resolution will be done prior to connection during transaction
+ /// processing.
+ /// Until then we'll throw unsupported.
+ isc_throw(D2CfgError, "Dns Server : hostname is not yet supported"
+ << " (" << getPosition("hostname", server_config) << ")");
+ } else {
+ try {
+ // Create an IOAddress from the IP address string given and then
+ // create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(ip_address);
+ server_info.reset(new DnsServerInfo(hostname, io_addr, port,
+ true, tsig_key_info,
+ inherited_key));
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(D2CfgError, "Dns Server : invalid IP address : "
+ << ip_address
+ << " (" << getPosition("ip-address", server_config) << ")");
+ }
+ }
+
+ // Add user-context
+ if (user_context) {
+ server_info->setContext(user_context);
+ }
+
+ return (server_info);
+}
+
+// *********************** DnsServerInfoListParser *************************
+
+DnsServerInfoStoragePtr
+DnsServerInfoListParser::parse(ConstElementPtr server_list,
+ ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys) {
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ ConstElementPtr server_config;
+ DnsServerInfoParser parser;
+ BOOST_FOREACH(server_config, server_list->listValue()) {
+ DnsServerInfoPtr server =
+ parser.parse(server_config, domain_config, keys);
+ servers->push_back(server);
+ }
+
+ return (servers);
+}
+
+// *********************** DdnsDomainParser *************************
+
+DdnsDomainPtr DdnsDomainParser::parse(ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys) {
+ std::string name = getString(domain_config, "name");
+ std::string key_name = getString(domain_config, "key-name");
+ ConstElementPtr user_context = domain_config->get("user-context");
+
+ // Parse the list of DNS servers
+ ConstElementPtr servers_config;
+ try {
+ servers_config = domain_config->get("dns-servers");
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError, "DdnsDomain : missing dns-server list"
+ << " (" << servers_config->getPosition() << ")");
+ }
+
+ DnsServerInfoListParser server_parser;
+ DnsServerInfoStoragePtr servers =
+ server_parser.parse(servers_config, domain_config, keys);
+ if (servers->size() == 0) {
+ isc_throw(D2CfgError, "DNS server list cannot be empty"
+ << servers_config->getPosition());
+ }
+
+ // Instantiate the new domain and add it to domain storage.
+ DdnsDomainPtr domain(new DdnsDomain(name, servers, key_name));
+
+ // Add user-context
+ if (user_context) {
+ domain->setContext(user_context);
+ }
+
+ return (domain);
+}
+
+// *********************** DdnsDomainListParser *************************
+
+DdnsDomainMapPtr DdnsDomainListParser::parse(ConstElementPtr domain_list,
+ const TSIGKeyInfoMapPtr keys) {
+ DdnsDomainMapPtr domains(new DdnsDomainMap());
+ DdnsDomainParser parser;
+ ConstElementPtr domain_config;
+ BOOST_FOREACH(domain_config, domain_list->listValue()) {
+ DdnsDomainPtr domain = parser.parse(domain_config, keys);
+
+ // Duplicates are not allowed
+ if (domains->find(domain->getName()) != domains->end()) {
+ isc_throw(D2CfgError, "Duplicate domain specified:"
+ << domain->getName()
+ << " (" << getPosition("name", domain_config) << ")");
+ }
+
+ (*domains)[domain->getName()] = domain;
+ }
+
+ return (domains);
+}
+
+// *********************** DdnsDomainListMgrParser *************************
+
+DdnsDomainListMgrPtr
+DdnsDomainListMgrParser::parse(ConstElementPtr mgr_config,
+ const std::string& mgr_name,
+ const TSIGKeyInfoMapPtr keys) {
+ DdnsDomainListMgrPtr mgr(new DdnsDomainListMgr(mgr_name));
+
+ // Parse the list of domains
+ ConstElementPtr domains_config = mgr_config->get("ddns-domains");
+ if (domains_config) {
+ DdnsDomainListParser domain_parser;
+ DdnsDomainMapPtr domains = domain_parser.parse(domains_config, keys);
+
+ // Add the new domain to the domain storage.
+ mgr->setDomains(domains);
+ }
+
+ return(mgr);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/d2srv/d2_config.h b/src/lib/d2srv/d2_config.h
new file mode 100644
index 0000000..cb141ad
--- /dev/null
+++ b/src/lib/d2srv/d2_config.h
@@ -0,0 +1,926 @@
+// Copyright (C) 2013-2022 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 D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <d2srv/d2_tsig_key.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <process/d_cfg_mgr.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, optionally the minimum truncated
+/// length, and its secret key component.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain. This value should
+/// map to one of the TSIG Keys in the key list. Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The parsing class hierarchy reflects this same scheme. Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainListParser creates and invokes a DdnsDomainParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+/// "interface" : "eth1" ,
+/// "ip-address" : "192.168.1.33" ,
+/// "port" : 88 ,
+/// "control-socket":
+/// {
+/// "socket-type": "unix" ,
+/// "socket-name": "/tmp/kea-ddns-ctrl-socket"
+//// },
+/// "tsig-keys":
+//// [
+/// {
+/// "name": "d2_key.tmark.org" ,
+/// "algorithm": "md5" ,
+/// "secret": "0123456989"
+/// }
+/// ],
+/// "forward-ddns" :
+/// {
+/// "ddns-domains":
+/// [
+/// {
+/// "name": "tmark.org." ,
+/// "key-name": "d2_key.tmark.org" ,
+/// "dns-servers" :
+/// [
+/// { "hostname": "fserver.tmark.org" },
+/// { "hostname": "f2server.tmark.org" }
+/// ]
+/// },
+/// {
+/// "name": "pub.tmark.org." ,
+/// "key-name": "d2_key.tmark.org" ,
+/// "dns-servers" :
+/// [
+/// { "hostname": "f3server.tmark.org" }
+/// ]
+/// }
+/// ]
+/// },
+/// "reverse-ddns" :
+/// {
+/// "ddns-domains":
+/// [
+/// {
+/// "name": " 0.168.192.in.addr.arpa." ,
+/// "key-name": "d2_key.tmark.org" ,
+/// "dns-servers" :
+/// [
+/// { "ip-address": "127.0.0.101" , "port": 100 ,
+/// "key-name": "d2_key.tmark.org" }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+ D2CfgError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Acts as a storage vault for D2 global scalar parameters
+class D2Params {
+public:
+ /// @brief Constructor
+ ///
+ /// @param ip_address IP address at which D2 should listen for NCRs
+ /// @param port port on which D2 should listen NCRs
+ /// @param dns_server_timeout maximum amount of time in milliseconds to
+ /// wait for a response to a single DNS update request.
+ /// @param ncr_protocol socket protocol D2 should use to receive NCRS
+ /// @param ncr_format packet format of the inbound NCRs
+ ///
+ /// @throw D2CfgError if:
+ /// -# ip_address is 0.0.0.0 or ::
+ /// -# port is 0
+ /// -# dns_server_timeout is < 1
+ /// -# ncr_protocol is invalid, currently only NCR_UDP is supported
+ /// -# ncr_format is invalid, currently only FMT_JSON is supported
+ D2Params(const isc::asiolink::IOAddress& ip_address,
+ const size_t port,
+ const size_t dns_server_timeout,
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::NameChangeFormat& ncr_format);
+
+ /// @brief Default constructor
+ /// The default constructor creates an instance that has updates disabled.
+ D2Params();
+
+ /// @brief Destructor
+ virtual ~D2Params();
+
+ /// @brief Return the IP address D2 listens on.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return(ip_address_);
+ }
+
+ /// @brief Return the TCP/UPD port D2 listens on.
+ size_t getPort() const {
+ return(port_);
+ }
+
+ /// @brief Return the DNS server timeout value.
+ size_t getDnsServerTimeout() const {
+ return(dns_server_timeout_);
+ }
+
+ /// @brief Return the socket protocol in use.
+ const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
+ return(ncr_protocol_);
+ }
+
+ /// @brief Return the expected format of inbound requests (NCRs).
+ const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
+ return(ncr_format_);
+ }
+
+ /// @brief Return summary of the configuration used by D2.
+ ///
+ /// The returned summary of the configuration is meant to be appended to
+ /// the log message informing about the successful completion of the
+ /// D2 configuration.
+ ///
+ /// @return Configuration summary in the textual format.
+ std::string getConfigSummary() const;
+
+ /// @brief Compares two D2Params's for equality
+ bool operator == (const D2Params& other) const;
+
+ /// @brief Compares two D2Params's for inequality
+ bool operator != (const D2Params& other) const;
+
+ /// @brief Generates a string representation of the class contents.
+ std::string toText() const;
+
+protected:
+ /// @brief Validates member values.
+ ///
+ /// Method is used by the constructor to validate member contents.
+ /// Currently checks:
+ /// -# ip_address is not 0.0.0.0 or ::
+ /// -# port is not 0
+ /// -# dns_server_timeout is 0
+ /// -# ncr_protocol is UDP
+ /// -# ncr_format is JSON
+ ///
+ /// @throw D2CfgError if contents are invalid
+ virtual void validateContents();
+
+private:
+ /// @brief IP address D2 listens on.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief IP port D2 listens on.
+ size_t port_;
+
+ /// @brief Timeout for a single DNS packet exchange in milliseconds.
+ size_t dns_server_timeout_;
+
+ /// @brief The socket protocol to use.
+ /// Currently only UDP is supported.
+ dhcp_ddns::NameChangeProtocol ncr_protocol_;
+
+ /// @brief Format of the inbound requests (NCRs).
+ /// Currently only JSON format is supported.
+ dhcp_ddns::NameChangeFormat ncr_format_;
+};
+
+/// @brief Dumps the contents of a D2Params as text to an output stream
+///
+/// @param os output stream to which text should be sent
+/// @param config D2Param instance to dump
+std::ostream&
+operator<<(std::ostream& os, const D2Params& config);
+
+/// @brief Defines a pointer for D2Params instances.
+typedef boost::shared_ptr<D2Params> D2ParamsPtr;
+
+/// @brief Represents a TSIG Key.
+///
+/// Acts as both a storage class containing the basic attributes which
+/// describe a TSIG Key, as well as owning and providing access to an
+/// instance of the actual key (@ref isc::dns::TSIGKey) that can be used
+/// by the IO layer for signing and verifying messages.
+///
+class TSIGKeyInfo : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @brief Defines string values for the supported TSIG algorithms
+ /// @{
+ static const char* HMAC_MD5_STR;
+ static const char* HMAC_SHA1_STR;
+ static const char* HMAC_SHA256_STR;
+ static const char* HMAC_SHA224_STR;
+ static const char* HMAC_SHA384_STR;
+ static const char* HMAC_SHA512_STR;
+ /// @}
+
+ /// @brief Constructor
+ ///
+ /// @param name the unique label used to identify this key
+ /// @param algorithm the id of the encryption algorithm this key uses.
+ /// Currently supported values are (case insensitive):
+ /// -# "HMAC-MD5"
+ /// -# "HMAC-SHA1"
+ /// -# "HMAC-SHA224"
+ /// -# "HMAC-SHA256"
+ /// -# "HMAC-SHA384"
+ /// -# "HMAC-SHA512"
+ ///
+ /// @param secret The base-64 encoded secret component for this key.
+ /// (A suitable string for use here could be obtained by running the
+ /// BIND 9 dnssec-keygen program; the contents of resulting key file
+ /// will look similar to:
+ /// @code
+ /// Private-key-format: v1.3
+ /// Algorithm: 157 (HMAC_MD5)
+ /// Key: LSWXnfkKZjdPJI5QxlpnfQ==
+ /// Bits: AAA=
+ /// Created: 20140515143700
+ /// Publish: 20140515143700
+ /// Activate: 20140515143700
+ /// @endcode
+ /// where the value the "Key:" entry is the secret component of the key.)
+ /// @param digestbits the minimum truncated length in bits
+ ///
+ /// @throw D2CfgError if values supplied are invalid:
+ /// name cannot be blank, algorithm must be a supported value,
+ /// secret must be a non-blank, base64 encoded string.
+ TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret, uint32_t digestbits = 0);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfo();
+
+ /// @brief Getter which returns the key's name.
+ ///
+ /// @return returns the name as a std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the key's algorithm string ID
+ ///
+ /// @return returns the algorithm as a std::string.
+ const std::string getAlgorithm() const {
+ return (algorithm_);
+ }
+
+ /// @brief Getter which returns the key's minimum truncated length
+ ///
+ /// @return returns the minimum truncated length or 0 as an uint32_t
+ uint32_t getDigestbits() const {
+ return (digestbits_);
+ }
+
+ /// @brief Getter which returns the key's secret.
+ ///
+ /// @return returns the secret as a std::string.
+ const std::string getSecret() const {
+ return (secret_);
+ }
+
+ /// @brief Getter which returns the TSIG key used to sign and verify
+ /// messages
+ ///
+ /// @return const pointer reference to @c D2TsigKeyPtr
+ const D2TsigKeyPtr& getTSIGKey() const {
+ return (tsig_key_);
+ }
+
+ /// @brief Converts algorithm id to dns::TSIGKey algorithm dns::Name
+ ///
+ /// @param algorithm_id string value to translate into an algorithm name.
+ /// Currently supported values are (case insensitive):
+ /// -# "HMAC-MD5"
+ /// -# "HMAC-SHA1"
+ /// -# "HMAC-SHA224"
+ /// -# "HMAC-SHA256"
+ /// -# "HMAC-SHA384"
+ /// -# "HMAC-SHA512"
+ ///
+ /// @return const reference to a dns::Name containing the algorithm name
+ /// @throw BadValue if ID isn't recognized.
+ static const dns::Name& stringToAlgorithmName(const std::string&
+ algorithm_id);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief Creates the actual TSIG key instance member
+ ///
+ /// Replaces this tsig_key member with a key newly created using the key
+ /// name, algorithm id, digest bits, and secret.
+ /// This method is currently only called by the constructor, however it
+ /// could be called post-construction should keys ever support expiration.
+ ///
+ /// @throw D2CfgError with an explanation if the key could not be created.
+ void remakeKey();
+
+ /// @brief The name of the key.
+ ///
+ /// This value is the unique identifier that domains use to
+ /// to specify which TSIG key they need.
+ std::string name_;
+
+ /// @brief The string ID of the algorithm that should be used for this key.
+ std::string algorithm_;
+
+ /// @brief The base64 encoded string secret value component of this key.
+ std::string secret_;
+
+ /// @brief The minimum truncated length in bits
+ /// (0 means no truncation is allowed and is the default)
+ uint32_t digestbits_;
+
+ /// @brief The actual TSIG key.
+ D2TsigKeyPtr tsig_key_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @brief defines DNS standard port value
+ static const uint32_t STANDARD_DNS_PORT = 53;
+
+ /// @brief Constructor
+ ///
+ /// @param hostname is the resolvable name of the server. If not blank,
+ /// then the server address should be resolved at runtime.
+ /// @param ip_address is the static IP address of the server. If hostname
+ /// is blank, then this address should be used to connect to the server.
+ /// @param port is the port number on which the server listens.
+ /// primarily meant for testing purposes. Normally, DNS traffic is on
+ /// is port 53. (NOTE the constructing code is responsible for setting
+ /// the default.)
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo for the server's key
+ /// It defaults to an empty pointer, signifying the server has no key.
+ /// @param inherited_key is a flag that indicates whether the key was
+ /// inherited from the domain or not. It defaults to true i.e. inherited.
+ DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port = STANDARD_DNS_PORT,
+ bool enabled = true,
+ const TSIGKeyInfoPtr& tsig_key_info = TSIGKeyInfoPtr(),
+ bool inherited_key = true);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfo();
+
+ /// @brief Getter which returns the server's hostname.
+ ///
+ /// @return the hostname as a std::string.
+ const std::string getHostname() const {
+ return (hostname_);
+ }
+
+ /// @brief Getter which returns the server's port number.
+ ///
+ /// @return the port number as a unsigned integer.
+ uint32_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Getter which returns the server's ip_address.
+ ///
+ /// @return the address as an IOAddress reference.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Convenience method which returns whether or not the
+ /// server is enabled.
+ ///
+ /// @return true if the server is enabled, false otherwise.
+ bool isEnabled() const {
+ return (enabled_);
+ }
+
+ /// @brief Sets the server's enabled flag to true.
+ void enable() {
+ enabled_ = true;
+ }
+
+ /// @brief Sets the server's enabled flag to false.
+ void disable() {
+ enabled_ = false;
+ }
+
+ /// @brief Convenience method which returns the server's TSIG key name.
+ ///
+ /// @return the key name in a std::string. If server has no TSIG key,
+ /// the string will be empty.
+ const std::string getKeyName() const;
+
+ /// @brief Getter which returns the server's TSIGKey info.
+ ///
+ /// @return the pointer to the server storage. If the server
+ /// is not configured to use TSIG the pointer will be empty.
+ const TSIGKeyInfoPtr& getTSIGKeyInfo() {
+ return (tsig_key_info_);
+ }
+
+ /// @brief Returns a text representation for the server.
+ std::string toText() const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief The resolvable name of the server. If not blank, then the
+ /// server's IP address should be dynamically resolved at runtime.
+ std::string hostname_;
+
+ /// @brief The static IP address of the server. When hostname is blank,
+ /// then this address should be used to connect to the server.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief The port number on which the server listens for DNS traffic.
+ uint32_t port_;
+
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ bool enabled_;
+
+ /// @brief Pointer to the server TSIGKeyInfo.
+ /// Value is empty if the server is not configured for TSIG.
+ TSIGKeyInfoPtr tsig_key_info_;
+
+ /// @brief Inherited key. When true the key was inherited from the domain,
+ /// false otherwise.
+ bool inherited_key_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const DnsServerInfo& server);
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it. Its primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just a std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
+class DdnsDomain : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name is the domain name of the domain.
+ /// @param servers is the list of server(s) supporting this domain.
+ /// @param key_name is the TSIG key name of the domain.
+ DdnsDomain(const std::string& name, DnsServerInfoStoragePtr servers,
+ const std::string& key_name = "");
+
+ /// @brief Destructor
+ virtual ~DdnsDomain();
+
+ /// @brief Getter which returns the domain's name.
+ ///
+ /// @return returns the name in a std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the domain's TSIG key name.
+ ///
+ /// @note: TSIG key infos are in servers.
+ ///
+ /// @return returns the key name in a std::string. If domain has no
+ /// TSIG key, the string will be empty.
+ const std::string getKeyName() const {
+ return (key_name_);
+ }
+
+ /// @brief Getter which returns the domain's list of servers.
+ ///
+ /// @return returns the pointer to the server storage.
+ const DnsServerInfoStoragePtr& getServers() {
+ return (servers_);
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief The domain name of the domain.
+ std::string name_;
+
+ /// @brief The list of server(s) supporting this domain.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief The TSIG key name (empty when there is no key for the domain).
+ std::string key_name_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services. These services are used to match a FQDN to a domain. Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified. The wild card domain is
+/// specified as a domain whose name is "*". The wild card domain will match
+/// any entry and is provided for flexibility in FQDNs If for instance, all
+/// forward requests are handled by the same servers, the configuration could
+/// specify the wild card domain as the only forward domain. All forward DNS
+/// updates would be sent to that one list of servers, regardless of the FQDN.
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr : public isc::data::CfgToElement {
+public:
+ /// @brief defines the domain name for denoting the wildcard domain.
+ static const char* wildcard_domain_name_;
+
+ /// @brief Constructor
+ ///
+ /// @param name is an arbitrary label assigned to this manager.
+ DdnsDomainListMgr(const std::string& name);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgr ();
+
+ /// @brief Matches a given name to a domain based on a longest match
+ /// scheme.
+ ///
+ /// Given a FQDN, search the list of domains, successively removing a
+ /// sub-domain from the FQDN until a match is found. If no match is found
+ /// and the wild card domain is present in the list, then return it as the
+ /// match. If the wild card domain is the only domain in the list, then
+ /// it will be returned immediately for any FQDN.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. If no match is found its
+ /// contents will be unchanged.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @todo This is a very basic match method, which expects valid FQDNs
+ /// both as input and for the DdnsDomain::getName(). Currently both are
+ /// simple strings and there is no normalization (i.e. added trailing dots
+ /// if missing).
+ virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Fetches the manager's name.
+ ///
+ /// @return returns a std::string containing the name of the manager.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Returns the number of domains in the domain list.
+ ///
+ /// @brief returns an unsigned int containing the domain count.
+ uint32_t size() const {
+ return (domains_->size());
+ }
+
+ /// @brief Fetches the wild card domain.
+ ///
+ /// @return returns a pointer reference to the domain. The pointer will
+ /// empty if the wild card domain is not present.
+ const DdnsDomainPtr& getWildcardDomain() {
+ return (wildcard_domain_);
+ }
+
+ /// @brief Fetches the domain list.
+ ///
+ /// @return returns a pointer reference to the list of domains.
+ const DdnsDomainMapPtr &getDomains() {
+ return (domains_);
+ }
+
+ /// @brief Sets the manger's domain list to the given list of domains.
+ /// This method will scan the inbound list for the wild card domain and
+ /// set the internal wild card domain pointer accordingly.
+ void setDomains(DdnsDomainMapPtr domains);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief An arbitrary label assigned to this manager.
+ std::string name_;
+
+ /// @brief Map of the domains, keyed by name.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the wild card domain.
+ DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map"). It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public process::ConfigBase {
+public:
+
+ /// @brief Constructor
+ DScalarContext() {
+ };
+
+ /// @brief Destructor
+ virtual ~DScalarContext() {
+ }
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual process::ConfigPtr clone() {
+ return (process::ConfigPtr(new DScalarContext(*this)));
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const {
+ isc_throw(isc::NotImplemented, "DScalarContext::ElementPtr");
+ }
+
+protected:
+ /// @brief Copy constructor
+ DScalarContext(const DScalarContext& rhs) : ConfigBase(rhs) {
+ }
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig-key"
+/// and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given "tsig-key" element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// TSIGKeyInfo, validates those entries, creates a TSIGKeyInfo instance
+ ///
+ /// @param key_config is the "tsig-key" configuration to parse
+ ///
+ /// @return pointer to the new TSIGKeyInfo instance
+ TSIGKeyInfoPtr parse(data::ConstElementPtr key_config);
+
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig-key" configuration elements.
+/// The TSIGKeyInfo instances are added to the given storage upon commit.
+class TSIGKeyInfoListParser : public data::SimpleParser {
+public:
+ /// @brief Performs the parsing of the given list "tsig-key" elements.
+ ///
+ /// Creates an empty TSIGKeyInfoMap
+ ///
+ /// Instantiates a TSIGKeyInfoParser
+ /// It iterates over each key entry in the list:
+ /// 2. Pass the element configuration to the parser's parse method
+ /// 3. Add the new TSIGKeyInfo instance to the key map
+ ///
+ /// @param key_list_config is the list of "tsig_key" elements to parse.
+ ///
+ /// @return a map containing the TSIGKeyInfo instances
+ TSIGKeyInfoMapPtr parse(data::ConstElementPtr key_list_config);
+};
+
+/// @brief Parser for DnsServerInfo
+///
+/// This class parses the configuration element "dns-server"
+/// and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given "dns-server" element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// DnsServerInfo, validates those entries, creates a DnsServerInfo instance
+ /// and returns it.
+ ///
+ /// @param server_config is the "dns-server" configuration to parse
+ /// @param domain_config the parent domain's configuration
+ /// @param keys map of defined TSIG keys
+ ///
+ /// @return a pointer to the newly created server instance
+ ///
+ /// @throw D2CfgError if:
+ /// -# hostname is not blank, hostname is not yet supported
+ /// -# ip_address is invalid
+ /// -# port is 0
+ DnsServerInfoPtr parse(data::ConstElementPtr server_config,
+ data::ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns-server" configuration elements.
+/// The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given list "dns-server"
+ /// elements.
+ ///
+ /// Creates an empty server list
+ /// It iterates over each server entry in the list:
+ /// 1. Creates a server instance by passing the entry to @c
+ /// DnsSeverInfoParser::parse()
+ /// 2. Adds the server to the server list
+ ///
+ /// @param server_list_config is the list of "dns-server" elements to parse.
+ /// @param domain_config the parent domain's configuration
+ /// @param keys map of defined TSIG keys
+ /// @return A pointer to the new, populated server list
+ DnsServerInfoStoragePtr parse(data::ConstElementPtr server_list_config,
+ data::ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for DdnsDomain
+///
+/// This class parses the configuration element "ddns-domain"
+/// and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given "ddns-domain" element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// DdnsDomain, validates those entries, and creates a DdnsDomain instance.
+ ///
+ /// @param domain_config is the "ddns-domain" configuration to parse
+ /// @param keys map of defined TSIG keys
+ ///
+ /// @return a pointer to the new domain instance
+ DdnsDomainPtr parse(data::ConstElementPtr domain_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns-domain" configuration elements
+/// into a map of DdnsDomains.
+class DdnsDomainListParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given list "ddns-domain"
+ /// elements.
+ /// Creates a new DdnsDomain map
+ /// It iterates over each domain entry in the list:
+ /// 1. Creates a DdnsDomain instance by passing the entry into @c
+ /// DdnsDomainParser::parser()
+ /// 2. Adds the DdnsDomain instance to the domain map
+ ///
+ /// @param domain_list_config is the list of "ddns-domain" elements to
+ /// parse.
+ /// @param keys map of defined TSIG keys
+ /// @return a pointer to the newly populated domain map
+ DdnsDomainMapPtr parse(data::ConstElementPtr domain_list_config,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward-ddns" and
+/// "reverse-ddns". It populates the given DdnsDomainListMgr with parsed
+/// information.
+class DdnsDomainListMgrParser : public data::SimpleParser {
+public:
+ /// @brief Performs the actual parsing of the given manager element.
+ ///
+ /// Parses a configuration for the elements needed to instantiate a
+ /// DdnsDomainListMgr, validates those entries, then creates a
+ /// DdnsDomainListMgr.
+ ///
+ /// @param mgr_config manager configuration to parse
+ /// @param mgr_name convenience label for the manager instance
+ /// @param keys map of defined TSIG keys
+ ///
+ /// @return a pointer to the new manager instance
+ DdnsDomainListMgrPtr parse(data::ConstElementPtr mgr_config,
+ const std::string& mgr_name,
+ const TSIGKeyInfoMapPtr keys);
+};
+
+} // end of isc::d2 namespace
+} // end of isc namespace
+
+#endif // D2_CONFIG_H
diff --git a/src/lib/d2srv/d2_log.cc b/src/lib/d2srv/d2_log.cc
new file mode 100644
index 0000000..a56640b
--- /dev/null
+++ b/src/lib/d2srv/d2_log.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2013-2021 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/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the logger used within D2.
+isc::log::Logger d2_logger("dhcpddns");
+isc::log::Logger dhcp_to_d2_logger("dhcp-to-d2");
+isc::log::Logger d2_to_dns_logger("d2-to-dns");
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/lib/d2srv/d2_log.h b/src/lib/d2srv/d2_log.h
new file mode 100644
index 0000000..2b408e0
--- /dev/null
+++ b/src/lib/d2srv/d2_log.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2013-2021 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 D2_LOG_H
+#define D2_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <d2srv/d2_messages.h>
+
+namespace isc {
+namespace d2 {
+
+/// Define the loggers for the "d2" logging.
+extern isc::log::Logger d2_logger;
+extern isc::log::Logger dhcp_to_d2_logger;
+extern isc::log::Logger d2_to_dns_logger;
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_LOG_H
diff --git a/src/lib/d2srv/d2_messages.cc b/src/lib/d2srv/d2_messages.cc
new file mode 100644
index 0000000..a35c63c
--- /dev/null
+++ b/src/lib/d2srv/d2_messages.cc
@@ -0,0 +1,187 @@
+// File created from ../../../src/lib/d2srv/d2_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace d2 {
+
+extern const isc::log::MessageID DHCP_DDNS_ADD_FAILED = "DHCP_DDNS_ADD_FAILED";
+extern const isc::log::MessageID DHCP_DDNS_ADD_SUCCEEDED = "DHCP_DDNS_ADD_SUCCEEDED";
+extern const isc::log::MessageID DHCP_DDNS_ALREADY_RUNNING = "DHCP_DDNS_ALREADY_RUNNING";
+extern const isc::log::MessageID DHCP_DDNS_AT_MAX_TRANSACTIONS = "DHCP_DDNS_AT_MAX_TRANSACTIONS";
+extern const isc::log::MessageID DHCP_DDNS_CLEARED_FOR_SHUTDOWN = "DHCP_DDNS_CLEARED_FOR_SHUTDOWN";
+extern const isc::log::MessageID DHCP_DDNS_COMMAND = "DHCP_DDNS_COMMAND";
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURE = "DHCP_DDNS_CONFIGURE";
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURED_CALLOUT_DROP = "DHCP_DDNS_CONFIGURED_CALLOUT_DROP";
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_CHECK_FAIL = "DHCP_DDNS_CONFIG_CHECK_FAIL";
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_FAIL = "DHCP_DDNS_CONFIG_FAIL";
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_SYNTAX_WARNING = "DHCP_DDNS_CONFIG_SYNTAX_WARNING";
+extern const isc::log::MessageID DHCP_DDNS_FAILED = "DHCP_DDNS_FAILED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE = "DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_IO_ERROR = "DHCP_DDNS_FORWARD_ADD_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_REJECTED = "DHCP_DDNS_FORWARD_ADD_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT = "DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_TIMEOUT = "DHCP_DDNS_FORWARD_ADD_TIMEOUT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_TIMEOUT = "DHCP_DDNS_FORWARD_REMOVE_ADDRS_TIMEOUT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE = "DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR = "DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED = "DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT = "DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_TIMEOUT = "DHCP_DDNS_FORWARD_REMOVE_RRS_TIMEOUT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE = "DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_IO_ERROR = "DHCP_DDNS_FORWARD_REPLACE_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_REJECTED = "DHCP_DDNS_FORWARD_REPLACE_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT = "DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_TIMEOUT = "DHCP_DDNS_FORWARD_REPLACE_TIMEOUT";
+extern const isc::log::MessageID DHCP_DDNS_FWD_REQUEST_IGNORED = "DHCP_DDNS_FWD_REQUEST_IGNORED";
+extern const isc::log::MessageID DHCP_DDNS_INVALID_RESPONSE = "DHCP_DDNS_INVALID_RESPONSE";
+extern const isc::log::MessageID DHCP_DDNS_NOT_ON_LOOPBACK = "DHCP_DDNS_NOT_ON_LOOPBACK";
+extern const isc::log::MessageID DHCP_DDNS_NO_ELIGIBLE_JOBS = "DHCP_DDNS_NO_ELIGIBLE_JOBS";
+extern const isc::log::MessageID DHCP_DDNS_NO_FWD_MATCH_ERROR = "DHCP_DDNS_NO_FWD_MATCH_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NO_MATCH = "DHCP_DDNS_NO_MATCH";
+extern const isc::log::MessageID DHCP_DDNS_NO_REV_MATCH_ERROR = "DHCP_DDNS_NO_REV_MATCH_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_PROCESS_INIT = "DHCP_DDNS_PROCESS_INIT";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_FULL = "DHCP_DDNS_QUEUE_MGR_QUEUE_FULL";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE = "DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECONFIGURING = "DHCP_DDNS_QUEUE_MGR_RECONFIGURING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECOVERING = "DHCP_DDNS_QUEUE_MGR_RECOVERING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECV_ERROR = "DHCP_DDNS_QUEUE_MGR_RECV_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUME_ERROR = "DHCP_DDNS_QUEUE_MGR_RESUME_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUMING = "DHCP_DDNS_QUEUE_MGR_RESUMING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STARTED = "DHCP_DDNS_QUEUE_MGR_STARTED";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_START_ERROR = "DHCP_DDNS_QUEUE_MGR_START_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPED = "DHCP_DDNS_QUEUE_MGR_STOPPED";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPING = "DHCP_DDNS_QUEUE_MGR_STOPPING";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOP_ERROR = "DHCP_DDNS_QUEUE_MGR_STOP_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR = "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP = "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP";
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_FAILED = "DHCP_DDNS_REMOVE_FAILED";
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_SUCCEEDED = "DHCP_DDNS_REMOVE_SUCCEEDED";
+extern const isc::log::MessageID DHCP_DDNS_REQUEST_DROPPED = "DHCP_DDNS_REQUEST_DROPPED";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE = "DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_IO_ERROR = "DHCP_DDNS_REVERSE_REMOVE_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_REJECTED = "DHCP_DDNS_REVERSE_REMOVE_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT = "DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_TIMEOUT = "DHCP_DDNS_REVERSE_REMOVE_TIMEOUT";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS = "DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE = "DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_IO_ERROR = "DHCP_DDNS_REVERSE_REPLACE_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_REJECTED = "DHCP_DDNS_REVERSE_REPLACE_REJECTED";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT = "DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT";
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_TIMEOUT = "DHCP_DDNS_REVERSE_REPLACE_TIMEOUT";
+extern const isc::log::MessageID DHCP_DDNS_REV_REQUEST_IGNORED = "DHCP_DDNS_REV_REQUEST_IGNORED";
+extern const isc::log::MessageID DHCP_DDNS_RUN_EXIT = "DHCP_DDNS_RUN_EXIT";
+extern const isc::log::MessageID DHCP_DDNS_SHUTDOWN_COMMAND = "DHCP_DDNS_SHUTDOWN_COMMAND";
+extern const isc::log::MessageID DHCP_DDNS_STARTED = "DHCP_DDNS_STARTED";
+extern const isc::log::MessageID DHCP_DDNS_STARTING_TRANSACTION = "DHCP_DDNS_STARTING_TRANSACTION";
+extern const isc::log::MessageID DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR = "DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_TRANS_SEND_ERROR = "DHCP_DDNS_TRANS_SEND_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_REQUEST_SENT = "DHCP_DDNS_UPDATE_REQUEST_SENT";
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_RESPONSE_RECEIVED = "DHCP_DDNS_UPDATE_RESPONSE_RECEIVED";
+
+} // namespace d2
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DHCP_DDNS_ADD_FAILED", "DHCP_DDNS Request ID %1: Transaction outcome %2",
+ "DHCP_DDNS_ADD_SUCCEEDED", "DHCP_DDNS Request ID %1: successfully added the DNS mapping addition for this request: %2",
+ "DHCP_DDNS_ALREADY_RUNNING", "%1 already running? %2",
+ "DHCP_DDNS_AT_MAX_TRANSACTIONS", "application has %1 queued requests but has reached maximum number of %2 concurrent transactions",
+ "DHCP_DDNS_CLEARED_FOR_SHUTDOWN", "application has met shutdown criteria for shutdown type: %1",
+ "DHCP_DDNS_COMMAND", "command directive received, command: %1 - args: %2",
+ "DHCP_DDNS_CONFIGURE", "configuration %1 received: %2",
+ "DHCP_DDNS_CONFIGURED_CALLOUT_DROP", "configuration was rejected because a callout set the next step to 'drop': %1",
+ "DHCP_DDNS_CONFIG_CHECK_FAIL", "DHCP-DDNS server configuration check failed: %1",
+ "DHCP_DDNS_CONFIG_FAIL", "DHCP-DDNS server configuration failed: %1",
+ "DHCP_DDNS_CONFIG_SYNTAX_WARNING", "DHCP-DDNS server configuration syntax warning: %1",
+ "DHCP_DDNS_FAILED", "application experienced a fatal error: %1",
+ "DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while adding a forward address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE", "DNS Request ID %1: update message to add a forward DNS entry could not be constructed for this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_ADD_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping add for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_ADD_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to add the address mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while adding forward address mapping for FQDN, %3",
+ "DHCP_DDNS_FORWARD_ADD_TIMEOUT", "DHCP_DDNS Request ID %1: timed out waiting for a response to forward mapping add for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing a forward address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE", "DNS Request ID %1: update message to remove a forward DNS Address entry could not be constructed for this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping address removal for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to remove the forward address mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward address mapping for FQDN, %3",
+ "DHCP_DDNS_FORWARD_REMOVE_ADDRS_TIMEOUT", "DHCP_DDNS Request ID %1: timed out waiting for a response to forward mapping address removal for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing forward RRs for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE", "DNS Request ID %1: update message to remove forward DNS RR entries could not be constructed for this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward RR removal for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to remove forward RR entries for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward RRs for FQDN, %3",
+ "DHCP_DDNS_FORWARD_REMOVE_RRS_TIMEOUT", "DHCP_DDNS Request ID %1: timed out waiting for response to forward RR removal for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing forward address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE", "DNS Request ID %1: update message to replace a forward DNS entry could not be constructed from this request: %2, reason: %3",
+ "DHCP_DDNS_FORWARD_REPLACE_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping replace for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FORWARD_REPLACE_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to replace the address mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing forward address mapping for FQDN, %3",
+ "DHCP_DDNS_FORWARD_REPLACE_TIMEOUT", "DHCP_DDNS Request ID %1: timed out waiting for a response to forward mapping replace for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_FWD_REQUEST_IGNORED", "Request ID %1: Forward updates are disabled, the forward portion of request will be ignored: %2",
+ "DHCP_DDNS_INVALID_RESPONSE", "received response to DNS Update message is malformed: %1",
+ "DHCP_DDNS_NOT_ON_LOOPBACK", "the DHCP-DDNS server has been configured to listen on %1 which is not the local loopback. This is an insecure configuration supported for testing purposes only",
+ "DHCP_DDNS_NO_ELIGIBLE_JOBS", "although there are queued requests, there are pending transactions for each, Queue count: %1 Transaction count: %2",
+ "DHCP_DDNS_NO_FWD_MATCH_ERROR", "Request ID %1: the configured list of forward DDNS domains does not contain a match for: %2 The request has been discarded.",
+ "DHCP_DDNS_NO_MATCH", "No DNS servers match FQDN %1",
+ "DHCP_DDNS_NO_REV_MATCH_ERROR", "Request ID %1: the configured list of reverse DDNS domains does not contain a match for: %2 The request has been discarded.",
+ "DHCP_DDNS_PROCESS_INIT", "application init invoked",
+ "DHCP_DDNS_QUEUE_MGR_QUEUE_FULL", "application request queue has reached maximum number of entries %1",
+ "DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE", "Request ID %1: received and queued a request.",
+ "DHCP_DDNS_QUEUE_MGR_RECONFIGURING", "application is reconfiguring the queue manager",
+ "DHCP_DDNS_QUEUE_MGR_RECOVERING", "application is attempting to recover from a queue manager IO error",
+ "DHCP_DDNS_QUEUE_MGR_RECV_ERROR", "application's queue manager was notified of a request receive error by its listener.",
+ "DHCP_DDNS_QUEUE_MGR_RESUME_ERROR", "application could not restart the queue manager, reason: %1",
+ "DHCP_DDNS_QUEUE_MGR_RESUMING", "application is resuming listening for requests now that the request queue size has reached %1 of a maximum %2 allowed",
+ "DHCP_DDNS_QUEUE_MGR_STARTED", "application's queue manager has begun listening for requests.",
+ "DHCP_DDNS_QUEUE_MGR_START_ERROR", "application could not start the queue manager, reason: %1",
+ "DHCP_DDNS_QUEUE_MGR_STOPPED", "application's queue manager has stopped listening for requests.",
+ "DHCP_DDNS_QUEUE_MGR_STOPPING", "application is stopping the queue manager for %1",
+ "DHCP_DDNS_QUEUE_MGR_STOP_ERROR", "application encountered an error stopping the queue manager: %1",
+ "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR", "application's queue manager request receive handler experienced an unexpected exception %1:",
+ "DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP", "application's queue manager receive was",
+ "DHCP_DDNS_REMOVE_FAILED", "DHCP_DDNS Request ID %1: Transaction outcome: %2",
+ "DHCP_DDNS_REMOVE_SUCCEEDED", "DHCP_DDNS Request ID %1: successfully removed the DNS mapping addition for this request: %2",
+ "DHCP_DDNS_REQUEST_DROPPED", "Request ID %1: Request contains no enabled update requests and will be dropped: %2",
+ "DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing reverse address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE", "DNS Request ID %1: update message to remove a reverse DNS entry could not be constructed from this request: %2, reason: %3",
+ "DHCP_DDNS_REVERSE_REMOVE_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping remove for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_REVERSE_REMOVE_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to remove the reverse mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing reverse address mapping for FQDN, %3",
+ "DHCP_DDNS_REVERSE_REMOVE_TIMEOUT", "DHCP_DDNS Request ID %1: timed out waiting for a response to reverse mapping remove for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS", "DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing reverse address mapping for FQDN %3 to DNS server %4",
+ "DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE", "DNS Request ID %1: update message to replace a reverse DNS entry could not be constructed from this request: %2, reason: %3",
+ "DHCP_DDNS_REVERSE_REPLACE_IO_ERROR", "DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping replacement for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_REVERSE_REPLACE_REJECTED", "DNS Request ID %1: Server, %2, rejected a DNS update request to replace the reverse mapping for FQDN, %3, with an RCODE: %4",
+ "DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT", "DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing reverse address mapping for FQDN, %3",
+ "DHCP_DDNS_REVERSE_REPLACE_TIMEOUT", "DHCP_DDNS Request ID %1: timed out waiting for a response to reverse mapping replacement for FQDN %2 to DNS server %3",
+ "DHCP_DDNS_REV_REQUEST_IGNORED", "Request ID %1: Reverse updates are disabled, the reverse portion of request will be ignored: %2",
+ "DHCP_DDNS_RUN_EXIT", "application is exiting the event loop",
+ "DHCP_DDNS_SHUTDOWN_COMMAND", "application received shutdown command with args: %1",
+ "DHCP_DDNS_STARTED", "Kea DHCP-DDNS server version %1 started",
+ "DHCP_DDNS_STARTING_TRANSACTION", "Request ID %1:",
+ "DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR", "Request ID %1: application encountered an unexpected error while carrying out a NameChangeRequest: %2",
+ "DHCP_DDNS_TRANS_SEND_ERROR", "Request ID %1: application encountered an unexpected error while attempting to send a DNS update: %2",
+ "DHCP_DDNS_UPDATE_REQUEST_SENT", "Request ID %1: %2 to server: %3",
+ "DHCP_DDNS_UPDATE_RESPONSE_RECEIVED", "Request ID %1: to server: %2 status: %3",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/d2srv/d2_messages.h b/src/lib/d2srv/d2_messages.h
new file mode 100644
index 0000000..2fcf224
--- /dev/null
+++ b/src/lib/d2srv/d2_messages.h
@@ -0,0 +1,97 @@
+// File created from ../../../src/lib/d2srv/d2_messages.mes
+
+#ifndef D2_MESSAGES_H
+#define D2_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace d2 {
+
+extern const isc::log::MessageID DHCP_DDNS_ADD_FAILED;
+extern const isc::log::MessageID DHCP_DDNS_ADD_SUCCEEDED;
+extern const isc::log::MessageID DHCP_DDNS_ALREADY_RUNNING;
+extern const isc::log::MessageID DHCP_DDNS_AT_MAX_TRANSACTIONS;
+extern const isc::log::MessageID DHCP_DDNS_CLEARED_FOR_SHUTDOWN;
+extern const isc::log::MessageID DHCP_DDNS_COMMAND;
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURE;
+extern const isc::log::MessageID DHCP_DDNS_CONFIGURED_CALLOUT_DROP;
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_CHECK_FAIL;
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_FAIL;
+extern const isc::log::MessageID DHCP_DDNS_CONFIG_SYNTAX_WARNING;
+extern const isc::log::MessageID DHCP_DDNS_FAILED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_ADD_TIMEOUT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_ADDRS_TIMEOUT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REMOVE_RRS_TIMEOUT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_FORWARD_REPLACE_TIMEOUT;
+extern const isc::log::MessageID DHCP_DDNS_FWD_REQUEST_IGNORED;
+extern const isc::log::MessageID DHCP_DDNS_INVALID_RESPONSE;
+extern const isc::log::MessageID DHCP_DDNS_NOT_ON_LOOPBACK;
+extern const isc::log::MessageID DHCP_DDNS_NO_ELIGIBLE_JOBS;
+extern const isc::log::MessageID DHCP_DDNS_NO_FWD_MATCH_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NO_MATCH;
+extern const isc::log::MessageID DHCP_DDNS_NO_REV_MATCH_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_PROCESS_INIT;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_FULL;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECONFIGURING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECOVERING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RECV_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUME_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_RESUMING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STARTED;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_START_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPED;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOPPING;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_STOP_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP;
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_FAILED;
+extern const isc::log::MessageID DHCP_DDNS_REMOVE_SUCCEEDED;
+extern const isc::log::MessageID DHCP_DDNS_REQUEST_DROPPED;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REMOVE_TIMEOUT;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_REJECTED;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT;
+extern const isc::log::MessageID DHCP_DDNS_REVERSE_REPLACE_TIMEOUT;
+extern const isc::log::MessageID DHCP_DDNS_REV_REQUEST_IGNORED;
+extern const isc::log::MessageID DHCP_DDNS_RUN_EXIT;
+extern const isc::log::MessageID DHCP_DDNS_SHUTDOWN_COMMAND;
+extern const isc::log::MessageID DHCP_DDNS_STARTED;
+extern const isc::log::MessageID DHCP_DDNS_STARTING_TRANSACTION;
+extern const isc::log::MessageID DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_TRANS_SEND_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_REQUEST_SENT;
+extern const isc::log::MessageID DHCP_DDNS_UPDATE_RESPONSE_RECEIVED;
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_MESSAGES_H
diff --git a/src/lib/d2srv/d2_messages.mes b/src/lib/d2srv/d2_messages.mes
new file mode 100644
index 0000000..53a7a32
--- /dev/null
+++ b/src/lib/d2srv/d2_messages.mes
@@ -0,0 +1,441 @@
+# Copyright (C) 2013-2023 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/.
+
+$NAMESPACE isc::d2
+
+% DHCP_DDNS_ADD_FAILED DHCP_DDNS Request ID %1: Transaction outcome %2
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry additions have failed. The precise reason for the failure should be
+documented in preceding log entries.
+
+% DHCP_DDNS_ADD_SUCCEEDED DHCP_DDNS Request ID %1: successfully added the DNS mapping addition for this request: %2
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping additions which were received and accepted by an appropriate DNS server.
+
+% DHCP_DDNS_ALREADY_RUNNING %1 already running? %2
+This is an error message that occurs when DHCP_DDNS encounters a pre-existing
+PID file which contains the PID of a running process. This most likely
+indicates an attempt to start a second instance of DHCP_DDNS using the
+same configuration file. It is possible, though unlikely, that the PID file
+is a remnant left behind by a server crash or power failure and the PID
+it contains refers to a process other than DHCP_DDNS. In such an event,
+it would be necessary to manually remove the PID file. The first argument is
+the DHCP_DDNS process name, the second contains the PID and PID file.
+
+% DHCP_DDNS_AT_MAX_TRANSACTIONS application has %1 queued requests but has reached maximum number of %2 concurrent transactions
+This is a debug message that indicates that the application has DHCP_DDNS
+requests in the queue but is working as many concurrent requests as allowed.
+
+% DHCP_DDNS_CLEARED_FOR_SHUTDOWN application has met shutdown criteria for shutdown type: %1
+This is a debug message issued when the application has been instructed
+to shutdown and has met the required criteria to exit.
+
+% DHCP_DDNS_COMMAND command directive received, command: %1 - args: %2
+This is a debug message issued when the DHCP-DDNS application command method
+has been invoked.
+
+% DHCP_DDNS_CONFIGURE configuration %1 received: %2
+This is a debug message issued when the DHCP-DDNS application configure method
+has been invoked.
+
+% DHCP_DDNS_CONFIGURED_CALLOUT_DROP configuration was rejected because a callout set the next step to 'drop': %1
+This error message indicates that the DHCP-DDNS had failed configuration
+attempt because the next step of the configured callout was set to 'drop'
+by a hook library. The error message provided by the hook library is displayed.
+
+% DHCP_DDNS_CONFIG_CHECK_FAIL DHCP-DDNS server configuration check failed: %1
+This error message indicates that the DHCP-DDNS had failed configuration
+check. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.
+
+% DHCP_DDNS_CONFIG_FAIL DHCP-DDNS server configuration failed: %1
+This error message indicates that the DHCP-DDNS had failed configuration
+attempt. Details are provided. Additional details may be available
+in earlier log entries, possibly on lower levels.
+
+% DHCP_DDNS_CONFIG_SYNTAX_WARNING DHCP-DDNS server configuration syntax warning: %1
+This warning message indicates that the DHCP-DDNS configuration had a minor
+syntax error. The error was displayed and the configuration parsing resumed.
+
+% DHCP_DDNS_FAILED application experienced a fatal error: %1
+This is a debug message issued when the DHCP-DDNS application encounters an
+unrecoverable error from within the event loop.
+
+% DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while adding a forward address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was adding a forward address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE DNS Request ID %1: update message to add a forward DNS entry could not be constructed for this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address addition. This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.
+This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_ADD_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping add for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address add. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to add the address mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while adding forward address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to add a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_ADD_TIMEOUT DHCP_DDNS Request ID %1: timed out waiting for a response to forward mapping add for FQDN %2 to DNS server %3
+This is an error message issued when no response is received from the DNS
+server before exceeding dns-server-timeout while DHCP_DDNS is carrying out
+a forward address add. The application will retry against the same
+server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing a forward address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a forward address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE DNS Request ID %1: update message to remove a forward DNS Address entry could not be constructed for this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address (A or AAAA) removal. This
+is due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+/*sar*/
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping address removal for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address remove. The application will retry
+against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to remove the forward address mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_TIMEOUT DHCP_DDNS Request ID %1: timed out waiting for a response to forward mapping address removal for FQDN %2 to DNS server %3
+This is an error message issued when no response is received from the DNS
+server before exceeding dns-server-timeout while DHCP_DDNS is carrying out
+a forward mapping address removal. The application will retry against the same
+server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing forward RRs for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing forward RRs. The request will be aborted. This is
+most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE DNS Request ID %1: update message to remove forward DNS RR entries could not be constructed for this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting forward RR (DHCID RR) removal. This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.
+This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward RR removal for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward RR remove. The application will retry
+against the same server.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to remove forward RR entries for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing forward RRs for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove forward RRs mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+/*sar*/
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_TIMEOUT DHCP_DDNS Request ID %1: timed out waiting for response to forward RR removal for FQDN %2 to DNS server %3
+This is an error message issued when no response is received from the DNS
+server before exceeding dns-server-timeout while DHCP_DDNS is carrying out
+a forward RR removal. The application will retry against the same
+server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing forward address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a forward address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE DNS Request ID %1: update message to replace a forward DNS entry could not be constructed from this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address replacement. This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REPLACE_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a forward mapping replace for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward mapping replace. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to replace the address mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing forward address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REPLACE_TIMEOUT DHCP_DDNS Request ID %1: timed out waiting for a response to forward mapping replace for FQDN %2 to DNS server %3
+This is an error message issued when no response is received from the DNS
+server before exceeding dns-server-timeout while DHCP_DDNS is carrying out
+a forward mapping replace. The application will retry against the same
+server or others as appropriate.
+
+% DHCP_DDNS_FWD_REQUEST_IGNORED Request ID %1: Forward updates are disabled, the forward portion of request will be ignored: %2
+This is a debug message issued when forward DNS updates are disabled and
+DHCP_DDNS receives an update request containing a forward DNS update. The
+forward update will not performed.
+
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an
+error while decoding a response to DNS Update message. Typically, this error
+will be encountered when a response message is malformed.
+
+% DHCP_DDNS_NOT_ON_LOOPBACK the DHCP-DDNS server has been configured to listen on %1 which is not the local loopback. This is an insecure configuration supported for testing purposes only
+This is a warning message issued when the DHCP-DDNS server is configured to
+listen at an address other than the loopback address (127.0.0.1 or ::1). It is
+possible for a malicious attacker to send bogus NameChangeRequests to it and
+change entries in the DNS. For this reason, addresses other than the IPv4 or
+IPv6 loopback addresses should only be used for testing purposes. A future
+version of Kea will implement authentication to guard against such attacks.
+
+% DHCP_DDNS_NO_ELIGIBLE_JOBS although there are queued requests, there are pending transactions for each, Queue count: %1 Transaction count: %2
+This is a debug message issued when all of the queued requests represent clients
+for which there is an update already in progress. This may occur under
+normal operations but should be temporary situation.
+
+% DHCP_DDNS_NO_FWD_MATCH_ERROR Request ID %1: the configured list of forward DDNS domains does not contain a match for: %2 The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update the forward DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration. Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN). The DNS Update
+request for the FQDN cannot be processed.
+
+% DHCP_DDNS_NO_REV_MATCH_ERROR Request ID %1: the configured list of reverse DDNS domains does not contain a match for: %2 The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update the reverse DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration. Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_PROCESS_INIT application init invoked
+This is a debug message issued when the DHCP-DDNS application enters
+its initialization method.
+
+% DHCP_DDNS_QUEUE_MGR_QUEUE_FULL application request queue has reached maximum number of entries %1
+This an error message indicating that DHCP-DDNS is receiving DNS update
+requests faster than they can be processed. This may mean the maximum queue
+needs to be increased, the DHCP-DDNS clients are simply generating too many
+requests too quickly, or perhaps upstream DNS servers are experiencing
+load issues.
+
+% DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE Request ID %1: received and queued a request.
+This is an informational message indicating that the NameChangeRequest listener used
+by DHCP-DDNS to receive a request has received a request and queued it for further
+processing.
+
+% DHCP_DDNS_QUEUE_MGR_RECONFIGURING application is reconfiguring the queue manager
+This is an informational message indicating that DHCP_DDNS is reconfiguring the queue manager as part of normal startup or in response to a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RECOVERING application is attempting to recover from a queue manager IO error
+This is an informational message indicating that DHCP_DDNS is attempting to
+restart the queue manager after it suffered an IO error while receiving
+requests.
+
+% DHCP_DDNS_QUEUE_MGR_RECV_ERROR application's queue manager was notified of a request receive error by its listener.
+This is an error message indicating that the NameChangeRequest listener used by
+DHCP-DDNS to receive requests encountered an IO error. There should be
+corresponding log messages from the listener layer with more details. This may
+indicate a network connectivity or system resource issue.
+
+% DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be restarted after stopping due to a full receive queue. This means that
+the application cannot receive requests. This is most likely due to DHCP_DDNS
+configuration parameters referring to resources such as an IP address or port,
+that is no longer unavailable. DHCP_DDNS will attempt to restart the queue
+manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RESUMING application is resuming listening for requests now that the request queue size has reached %1 of a maximum %2 allowed
+This is an informational message indicating that DHCP_DDNS, which had stopped
+accepting new requests, has processed enough entries from the receive queue to
+resume accepting requests.
+
+% DHCP_DDNS_QUEUE_MGR_STARTED application's queue manager has begun listening for requests.
+This is a debug message indicating that DHCP_DDNS's Queue Manager has
+successfully started and is now listening for NameChangeRequests.
+
+% DHCP_DDNS_QUEUE_MGR_START_ERROR application could not start the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be started. This means that the application cannot receive requests. This is
+most likely due to DHCP_DDNS configuration parameters referring to resources
+such as an IP address or port, that are unavailable. DHCP_DDNS will attempt to
+restart the queue manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPED application's queue manager has stopped listening for requests.
+This is a debug message indicating that DHCP_DDNS's Queue Manager has
+stopped listening for NameChangeRequests. This may be because of normal event
+such as reconfiguration or as a result of an error. There should be log
+messages preceding this one to indicate why it has stopped.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPING application is stopping the queue manager for %1
+This is an informational message indicating that DHCP_DDNS is stopping the
+queue manager either to reconfigure it or as part of application shutdown.
+
+% DHCP_DDNS_QUEUE_MGR_STOP_ERROR application encountered an error stopping the queue manager: %1
+This is an error message indicating that DHCP_DDNS encountered an error while
+trying to stop the queue manager. This error is unlikely to occur or to
+impair the application's ability to function but it should be reported for
+analysis.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR application's queue manager request receive handler experienced an unexpected exception %1:
+This is an error message indicating that an unexpected error occurred within the
+DHCP_DDNS's Queue Manager request receive completion handler. This is most
+likely a programmatic issue that should be reported. The application may
+recover on its own.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP application's queue manager receive was
+aborted unexpectedly while queue manager state is: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager request
+receive was unexpected interrupted. Normally, the read is receive is only
+interrupted as a normal part of stopping the queue manager. This is most
+likely a programmatic issue that should be reported.
+
+% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS Request ID %1: Transaction outcome: %2
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry removals have failed. The precise reason for the failure should be
+documented in preceding log entries.
+
+% DHCP_DDNS_REMOVE_SUCCEEDED DHCP_DDNS Request ID %1: successfully removed the DNS mapping addition for this request: %2
+This is an informational message issued after DHCP_DDNS has submitted DNS
+mapping removals which were received and accepted by an appropriate DNS server.
+
+% DHCP_DDNS_REQUEST_DROPPED Request ID %1: Request contains no enabled update requests and will be dropped: %2
+This is a debug message issued when DHCP_DDNS receives a request which does not
+contain updates in a direction that is enabled. In other words, if only forward
+updates are enabled and request is received that asks only for reverse updates
+then the request is dropped.
+
+% DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while removing reverse address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a reverse address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE DNS Request ID %1: update message to remove a reverse DNS entry could not be constructed from this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR removal. This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+
+% DHCP_DDNS_REVERSE_REMOVE_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping remove for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse mapping remove. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REMOVE_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to remove the reverse mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while removing reverse address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a reverse address, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REMOVE_TIMEOUT DHCP_DDNS Request ID %1: timed out waiting for a response to reverse mapping remove for FQDN %2 to DNS server %3
+This is an error message issued when no response is received from the DNS
+server before exceeding dns-server-timeout while DHCP_DDNS is carrying out
+a reverse mapping remove. The application will retry against the same
+server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS DHCP_DDNS Request ID %1: received an unknown DNSClient status: %2, while replacing reverse address mapping for FQDN %3 to DNS server %4
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was replacing a reverse address mapping. The request will be
+aborted. This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE DNS Request ID %1: update message to replace a reverse DNS entry could not be constructed from this request: %2, reason: %3
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR replacement. This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted. This is most likely a configuration issue.
+
+% DHCP_DDNS_REVERSE_REPLACE_IO_ERROR DHCP_DDNS Request ID %1: encountered an IO error sending a reverse mapping replacement for FQDN %2 to DNS server %3
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse mapping replacement. The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_REJECTED DNS Request ID %1: Server, %2, rejected a DNS update request to replace the reverse mapping for FQDN, %3, with an RCODE: %4
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT DHCP_DDNS Request ID %1: received a corrupt response from the DNS server, %2, while replacing reverse address mapping for FQDN, %3
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to replace a reverse address, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REPLACE_TIMEOUT DHCP_DDNS Request ID %1: timed out waiting for a response to reverse mapping replacement for FQDN %2 to DNS server %3
+This is an error message issued when no response is received from the DNS
+server before exceeding dns-server-timeout while DHCP_DDNS is carrying out
+a reverse mapping replacement. The application will retry against the same
+server or others as appropriate.
+
+% DHCP_DDNS_REV_REQUEST_IGNORED Request ID %1: Reverse updates are disabled, the reverse portion of request will be ignored: %2
+This is a debug message issued when reverse DNS updates are disabled and
+DHCP_DDNS receives an update request containing a reverse DNS update. The
+reverse update will not performed.
+
+% DHCP_DDNS_RUN_EXIT application is exiting the event loop
+This is a debug message issued when the DHCP-DDNS server exits its
+event lo
+
+% DHCP_DDNS_SHUTDOWN_COMMAND application received shutdown command with args: %1
+This is a debug message issued when the application has been instructed
+to shut down by the controller.
+
+% DHCP_DDNS_STARTED Kea DHCP-DDNS server version %1 started
+This informational message indicates that the DHCP-DDNS server has
+processed all configuration information and is ready to begin processing.
+The version is also printed.
+
+% DHCP_DDNS_STARTING_TRANSACTION Request ID %1:
+This is a debug message issued when DHCP-DDNS has begun a transaction for
+a given request.
+
+% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR Request ID %1: application encountered an unexpected error while carrying out a NameChangeRequest: %2
+This is error message issued when the application fails to process a
+NameChangeRequest correctly. Some or all of the DNS updates requested as part
+of this update did not succeed. This is a programmatic error and should be
+reported.
+
+% DHCP_DDNS_TRANS_SEND_ERROR Request ID %1: application encountered an unexpected error while attempting to send a DNS update: %2
+This is error message issued when the application is able to construct an update
+message but the attempt to send it suffered an unexpected error. This is most
+likely a programmatic error, rather than a communications issue. Some or all
+of the DNS updates requested as part of this request did not succeed.
+
+% DHCP_DDNS_UPDATE_REQUEST_SENT Request ID %1: %2 to server: %3
+This is a debug message issued when DHCP_DDNS sends a DNS request to a DNS
+server.
+
+% DHCP_DDNS_UPDATE_RESPONSE_RECEIVED Request ID %1: to server: %2 status: %3
+This is a debug message issued when DHCP_DDNS receives sends a DNS update
+response from a DNS server.
diff --git a/src/lib/d2srv/d2_simple_parser.cc b/src/lib/d2srv/d2_simple_parser.cc
new file mode 100644
index 0000000..c24dfdc
--- /dev/null
+++ b/src/lib/d2srv/d2_simple_parser.cc
@@ -0,0 +1,310 @@
+// Copyright (C) 2017-2023 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 <d2srv/d2_config.h>
+#include <d2srv/d2_simple_parser.h>
+#include <cc/data.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/hooks_parser.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+using namespace isc::d2;
+using namespace isc;
+
+namespace {
+
+dhcp_ddns::NameChangeProtocol
+getProtocol(ConstElementPtr map, const std::string& name) {
+ ConstElementPtr value = map->get(name);
+ if (!value) {
+ isc_throw(D2CfgError, "Mandatory parameter " << name
+ << " not found (" << map->getPosition() << ")");
+ }
+ std::string str = value->stringValue();
+ try {
+ return (dhcp_ddns::stringToNcrProtocol(str));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError,
+ "invalid NameChangeRequest protocol (" << str
+ << ") specified for parameter '" << name
+ << "' (" << value->getPosition() << ")");
+ }
+}
+
+dhcp_ddns::NameChangeFormat
+getFormat(ConstElementPtr map, const std::string& name) {
+ ConstElementPtr value = map->get(name);
+ if (!value) {
+ isc_throw(D2CfgError, "Mandatory parameter " << name
+ << " not found (" << map->getPosition() << ")");
+ }
+ std::string str = value->stringValue();
+ try {
+ return (dhcp_ddns::stringToNcrFormat(str));
+ } catch (const std::exception& ex) {
+ isc_throw(D2CfgError,
+ "invalid NameChangeRequest format (" << str
+ << ") specified for parameter '" << name
+ << "' (" << value->getPosition() << ")");
+ }
+}
+
+} // anon
+
+namespace isc {
+namespace d2 {
+/// @brief This sets of arrays define the default values and
+/// values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file d2_simple_parser.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default global values for D2
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in DhcpDdns) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults D2SimpleParser::D2_GLOBAL_DEFAULTS = {
+ { "ip-address", Element::string, "127.0.0.1" },
+ { "port", Element::integer, "53001" },
+ { "dns-server-timeout", Element::integer, "500" }, // in milliseconds
+ { "ncr-protocol", Element::string, "UDP" },
+ { "ncr-format", Element::string, "JSON" }
+};
+
+/// Supplies defaults for ddns-domains list elements (i.e. DdnsDomains)
+const SimpleDefaults D2SimpleParser::TSIG_KEY_DEFAULTS = {
+ { "digest-bits", Element::integer, "0" }
+};
+
+/// Supplies defaults for optional values in DDNS domain managers
+/// (e.g. "forward-ddns" and "reverse-ddns").
+/// @note While there are none yet defined, it is highly likely
+/// there will be domain manager defaults added in the future.
+/// This code to set defaults already uses this list, so supporting
+/// values will simply require adding them to this list.
+const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_MGR_DEFAULTS = {
+};
+
+/// Supplies defaults for ddns-domains list elements (i.e. DdnsDomains)
+const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_DEFAULTS = {
+ { "key-name", Element::string, "" }
+};
+
+/// Supplies defaults for optional values DdnsDomain entries.
+const SimpleDefaults D2SimpleParser::DNS_SERVER_DEFAULTS = {
+ { "hostname", Element::string, "" },
+ { "port", Element::integer, "53" },
+ { "key-name", Element::string, "" }
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t
+D2SimpleParser::setAllDefaults(isc::data::ElementPtr global) {
+ size_t cnt = 0;
+ // Set global defaults first.
+ cnt = setDefaults(global, D2_GLOBAL_DEFAULTS);
+
+ // If the key list is present, set its members' defaults
+ if (global->find("tsig-keys")) {
+ ConstElementPtr keys = global->get("tsig-keys");
+ cnt += setListDefaults(keys, TSIG_KEY_DEFAULTS);
+ } else {
+ // Not present, so add an empty list.
+ ConstElementPtr list(new ListElement());
+ global->set("tsig-keys", list);
+ cnt++;
+ }
+
+ // Set the forward domain manager defaults.
+ cnt += setManagerDefaults(global, "forward-ddns", DDNS_DOMAIN_MGR_DEFAULTS);
+
+ // Set the reverse domain manager defaults.
+ cnt += setManagerDefaults(global, "reverse-ddns", DDNS_DOMAIN_MGR_DEFAULTS);
+ return (cnt);
+}
+
+size_t
+D2SimpleParser::setDdnsDomainDefaults(ElementPtr domain,
+ const SimpleDefaults& domain_defaults) {
+ size_t cnt = 0;
+
+ // Set the domain's scalar defaults
+ cnt += setDefaults(domain, domain_defaults);
+ if (domain->find("dns-servers")) {
+ // Now add the defaults to its server list.
+ ConstElementPtr servers = domain->get("dns-servers");
+ cnt += setListDefaults(servers, DNS_SERVER_DEFAULTS);
+ }
+
+ return (cnt);
+}
+
+
+size_t
+D2SimpleParser::setManagerDefaults(ElementPtr global,
+ const std::string& mgr_name,
+ const SimpleDefaults& mgr_defaults) {
+ size_t cnt = 0;
+
+ if (!global->find(mgr_name)) {
+ // If it's not present, then default is an empty map
+ ConstElementPtr map(new MapElement());
+ global->set(mgr_name, map);
+ ++cnt;
+ } else {
+ // Get a writable copy of the manager element map
+ ElementPtr mgr =
+ boost::const_pointer_cast<Element>(global->get(mgr_name));
+
+ // Set the manager's scalar defaults first
+ cnt += setDefaults(mgr, mgr_defaults);
+
+ // Get the domain list and set defaults for them.
+ // The domain list may not be present ddns for this
+ // manager is disabled.
+ if (mgr->find("ddns-domains")) {
+ ConstElementPtr domains = mgr->get("ddns-domains");
+ BOOST_FOREACH(ElementPtr domain, domains->listValue()) {
+ // Set the domain's defaults. We can't use setListDefaults()
+ // as this does not handle sub-lists or maps, like server list.
+ cnt += setDdnsDomainDefaults(domain, DDNS_DOMAIN_DEFAULTS);
+ }
+ }
+
+ }
+
+ return (cnt);
+}
+
+void D2SimpleParser::parse(const D2CfgContextPtr& ctx,
+ const isc::data::ConstElementPtr& config,
+ bool check_only) {
+ // TSIG keys need to parse before the Domains, so we can catch Domains
+ // that specify undefined keys. Create the necessary parsing order now.
+ // addToParseOrder("tsig-keys");
+ // addToParseOrder("forward-ddns");
+ // addToParseOrder("reverse-ddns");
+
+ ConstElementPtr keys = config->get("tsig-keys");
+ if (keys) {
+ TSIGKeyInfoListParser parser;
+ ctx->setKeys(parser.parse(keys));
+ }
+
+ ConstElementPtr fwd = config->get("forward-ddns");
+ if (fwd) {
+ DdnsDomainListMgrParser parser;
+ DdnsDomainListMgrPtr mgr = parser.parse(fwd, "forward-ddns",
+ ctx->getKeys());
+ ctx->setForwardMgr(mgr);
+ }
+
+ ConstElementPtr rev = config->get("reverse-ddns");
+ if (rev) {
+ DdnsDomainListMgrParser parser;
+ DdnsDomainListMgrPtr mgr = parser.parse(rev, "reverse-ddns",
+ ctx->getKeys());
+ ctx->setReverseMgr(mgr);
+ }
+
+ // Fetch the parameters in the config, performing any logical
+ // validation required.
+ asiolink::IOAddress ip_address(0);
+ uint32_t port = 0;
+ uint32_t dns_server_timeout = 0;
+ dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
+ dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
+
+ ip_address = SimpleParser::getAddress(config, "ip-address");
+
+ if ((ip_address.toText() == "0.0.0.0") ||
+ (ip_address.toText() == "::")) {
+ isc_throw(D2CfgError, "IP address cannot be \""
+ << ip_address << "\""
+ << " (" << config->get("ip-address")->getPosition() << ")");
+ }
+
+ port = SimpleParser::getUint32(config, "port");
+
+ dns_server_timeout = SimpleParser::getUint32(config, "dns-server-timeout");
+
+ ncr_protocol = getProtocol(config, "ncr-protocol");
+ if (ncr_protocol != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2CfgError, "ncr-protocol : "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol)
+ << " is not yet supported ("
+ << config->get("ncr-protocol")->getPosition() << ")");
+ }
+
+ ncr_format = getFormat(config, "ncr-format");
+ if (ncr_format != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2CfgError, "NCR Format:"
+ << dhcp_ddns::ncrFormatToString(ncr_format)
+ << " is not yet supported"
+ << " (" << config->get("ncr-format")->getPosition() << ")");
+ }
+
+ ConstElementPtr user = config->get("user-context");
+ if (user) {
+ ctx->setContext(user);
+ }
+
+ ConstElementPtr socket = config->get("control-socket");
+ if (socket) {
+ if (socket->getType() != Element::map) {
+ isc_throw(D2CfgError, "Specified control-socket is expected to be a map"
+ ", i.e. a structure defined within { }");
+ }
+ ctx->setControlSocketInfo(socket);
+ }
+
+ // Finally, let's get the hook libs!
+ using namespace isc::hooks;
+ HooksConfig& libraries = ctx->getHooksConfig();
+ ConstElementPtr hooks = config->get("hooks-libraries");
+ if (hooks) {
+ HooksLibrariesParser hooks_parser;
+ hooks_parser.parse(libraries, hooks);
+ libraries.verifyLibraries(hooks->getPosition(), false);
+ }
+
+ // Attempt to create the new client config. This ought to fly as
+ // we already validated everything.
+ D2ParamsPtr params(new D2Params(ip_address, port, dns_server_timeout,
+ ncr_protocol, ncr_format));
+
+ ctx->getD2Params() = params;
+
+ if (!check_only) {
+ // This occurs last as if it succeeds, there is no easy way
+ // revert it. As a result, the failure to commit a subsequent
+ // change causes problems when trying to roll back.
+ HooksManager::prepareUnloadLibraries();
+ static_cast<void>(HooksManager::unloadLibraries());
+ libraries.loadLibraries(false);
+ }
+}
+
+}
+}
diff --git a/src/lib/d2srv/d2_simple_parser.h b/src/lib/d2srv/d2_simple_parser.h
new file mode 100644
index 0000000..49f0d8a
--- /dev/null
+++ b/src/lib/d2srv/d2_simple_parser.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2017-2021 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 D2_SIMPLE_PARSER_H
+#define D2_SIMPLE_PARSER_H
+
+#include <cc/simple_parser.h>
+#include <d2srv/d2_cfg_mgr.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief SimpleParser specialized for D2
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to D2.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file d2_simple_parser.cc
+class D2SimpleParser : public data::SimpleParser {
+public:
+
+ /// @brief Sets all defaults for D2 configuration
+ ///
+ /// This method sets global and element defaults.
+ ///
+ /// @param global scope to be filled in with defaults.
+ /// @return number of default values added
+ static size_t setAllDefaults(data::ElementPtr global);
+
+ // see d2_simple_parser.cc for comments for those parameters
+ static const data::SimpleDefaults D2_GLOBAL_DEFAULTS;
+
+ // Defaults for tsig-keys list elements, TSIGKeyInfos
+ static const data::SimpleDefaults TSIG_KEY_DEFAULTS;
+
+ // Defaults for <forward|reverse>-ddns elements, DdnsDomainListMgrs
+ static const data::SimpleDefaults DDNS_DOMAIN_MGR_DEFAULTS;
+
+ // Defaults for ddns-domains list elements, DdnsDomains
+ static const data::SimpleDefaults DDNS_DOMAIN_DEFAULTS;
+
+ // Defaults for dns-servers list elements, DnsServerInfos
+ static const data::SimpleDefaults DNS_SERVER_DEFAULTS;
+
+ /// @brief Adds default values to a DDNS Domain element
+ ///
+ /// Adds the scalar default values to the given DDNS domain
+ /// element, and then adds the DNS Server defaults to the domain's
+ /// server list, "dns-servers".
+ ///
+ /// @param domain DDNS domain element to which defaults should be added
+ /// @param domain_defaults list of default values from which to add
+ /// @return returns the number of default values added
+ static size_t setDdnsDomainDefaults(data::ElementPtr domain,
+ const data::SimpleDefaults&
+ domain_defaults);
+
+ /// @brief Adds default values to a DDNS Domain List Manager
+ ///
+ /// This function looks for the named DDNS domain manager element within
+ /// the given element tree. If it is found, it adds the scalar default
+ /// values to the manager element and then adds the DDNS Domain defaults
+ /// to its domain list, "ddns-domains". If the manager element is not
+ /// found, then an empty map entry is added for it, thus defaulting the
+ /// manager to "disabled".
+ ///
+ /// @param global element tree containing the DDNS domain manager element
+ /// to which defaults should be
+ /// added
+ /// @param mgr_name name of the manager element within the element tree
+ /// (e.g. "forward-ddns", "reverse-ddns")
+ /// @param mgr_defaults list of default values from which to add
+ /// @return returns the number of default values added
+ static size_t setManagerDefaults(data::ElementPtr global,
+ const std::string& mgr_name,
+ const data::SimpleDefaults& mgr_defaults);
+
+ /// @brief Parses the whole D2 configuration
+ ///
+ /// @param ctx - parsed information will be stored here
+ /// @param config - Element tree structure that holds configuration
+ /// @param check_only - if true the configuration is verified only, not applied
+ ///
+ /// @throw ConfigError if any issues are encountered.
+ void parse(const D2CfgContextPtr& ctx,
+ const isc::data::ConstElementPtr& config,
+ bool check_only);
+};
+
+};
+};
+
+#endif
diff --git a/src/lib/d2srv/d2_stats.cc b/src/lib/d2srv/d2_stats.cc
new file mode 100644
index 0000000..8534f13
--- /dev/null
+++ b/src/lib/d2srv/d2_stats.cc
@@ -0,0 +1,58 @@
+// Copyright (C) 2021 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/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <config.h>
+
+#include <d2srv/d2_stats.h>
+#include <stats/stats_mgr.h>
+
+using namespace std;
+using namespace isc::stats;
+
+namespace isc {
+namespace d2 {
+
+const list<string>
+D2Stats::ncr = {
+ "ncr-received",
+ "ncr-invalid",
+ "ncr-error"
+};
+
+const list<string>
+D2Stats::update = {
+ "update-sent",
+ "update-signed",
+ "update-unsigned",
+ "update-success",
+ "update-timeout",
+ "update-error"
+};
+
+const list<string>
+D2Stats::key = {
+ "update-sent",
+ "update-success",
+ "update-timeout",
+ "update-error"
+};
+
+void
+D2Stats::init() {
+ StatsMgr& stats_mgr = isc::stats::StatsMgr::instance();
+ stats_mgr.setMaxSampleCountDefault(0);
+ for (const auto& name : D2Stats::ncr) {
+ stats_mgr.setValue(name, static_cast<int64_t>(0));
+ }
+ for (const auto& name : D2Stats::update) {
+ stats_mgr.setValue(name, static_cast<int64_t>(0));
+ }
+};
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_stats.h b/src/lib/d2srv/d2_stats.h
new file mode 100644
index 0000000..b4122b4
--- /dev/null
+++ b/src/lib/d2srv/d2_stats.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2021 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 D2_STATS_H
+#define D2_STATS_H
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Statistics Names.
+class D2Stats {
+public:
+ /// @brief Global NCR statistics names.
+ ///
+ /// - ncr-received
+ /// - ncr-invalid
+ /// - ncr-error
+ static const std::list<std::string> ncr;
+
+ /// @brief Global DNS update statistics names.
+ ///
+ /// - update-sent
+ /// - update-signed
+ /// - update-unsigned
+ /// - update-success
+ /// - update-timeout
+ /// - update-error
+ static const std::list<std::string> update;
+
+ /// @brief Key DNS update statistics names.
+ ///
+ /// - update-sent
+ /// - update-success
+ /// - update-timeout
+ /// - update-error
+ static const std::list<std::string> key;
+
+ /// @brief Initialize D2 statistics.
+ ///
+ /// @note: Add default samples if needed.
+ static void init();
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_STATS_H
diff --git a/src/lib/d2srv/d2_tsig_key.cc b/src/lib/d2srv/d2_tsig_key.cc
new file mode 100644
index 0000000..75dc527
--- /dev/null
+++ b/src/lib/d2srv/d2_tsig_key.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 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/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <config.h>
+
+#include <d2srv/d2_stats.h>
+#include <d2srv/d2_tsig_key.h>
+#include <stats/stats_mgr.h>
+
+using namespace isc::dns;
+using namespace isc::stats;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+
+D2TsigKey::D2TsigKey(const std::string& key_spec) : TSIGKey(key_spec) {
+ initStats();
+}
+
+D2TsigKey::D2TsigKey(const Name& key_name, const Name& algorithm_name,
+ const void* secret, size_t secret_len, size_t digestbits)
+ : TSIGKey(key_name, algorithm_name, secret, secret_len, digestbits) {
+ initStats();
+}
+
+D2TsigKey::~D2TsigKey() {
+ removeStats();
+}
+
+void
+D2TsigKey::initStats() {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ const string& kname = getKeyName().toText();
+ for (const auto& name : D2Stats::key) {
+ const string& sname = StatsMgr::generateName("key", kname, name);
+ stats_mgr.setValue(sname, static_cast<int64_t>(0));
+ }
+}
+
+void
+D2TsigKey::removeStats() {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ const string& kname = getKeyName().toText();
+ for (const auto& name : D2Stats::key) {
+ string sname = StatsMgr::generateName("key", kname, name);
+ stats_mgr.del(sname);
+ }
+}
+
+void
+D2TsigKey::resetStats() {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ const string& kname = getKeyName().toText();
+ for (const auto& name : D2Stats::key) {
+ string sname = StatsMgr::generateName("key", kname, name);
+ stats_mgr.reset(sname);
+ }
+}
+
+TSIGContextPtr
+D2TsigKey::createContext() {
+ return (TSIGContextPtr(new TSIGContext(*this)));
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_tsig_key.h b/src/lib/d2srv/d2_tsig_key.h
new file mode 100644
index 0000000..4cdb036
--- /dev/null
+++ b/src/lib/d2srv/d2_tsig_key.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2021 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 D2_TSIG_KEY_H
+#define D2_TSIG_KEY_H
+
+#include <dns/name.h>
+#include <dns/tsig.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Statistics keeping extension of the DNS TSIGKey class.
+///
+/// Implements a TSIGKey derived class which can be used as the value
+/// of TSIGKeyPtr so with minimal or no update to the DNS++ library.
+class D2TsigKey : public dns::TSIGKey {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initialize the key statistics.
+ ///
+ /// @param key_spec Specification of the key
+ /// (name:secret[:algorithm][:digestbits])
+ explicit D2TsigKey(const std::string& key_spec);
+
+ /// @brief Constructor.
+ ///
+ /// Initialize the key statistics.
+ ///
+ /// @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.
+ /// @param secret Point to a binary sequence of the shared secret to be
+ /// used for this key.
+ /// @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)
+ D2TsigKey(const dns::Name& key_name, const dns::Name& algorithm_name,
+ const void* secret, size_t secret_len, size_t digestbits = 0);
+
+ /// @brief Destructor.
+ ///
+ /// Remove the key statistics.
+ virtual ~D2TsigKey();
+
+ /// @brief Reset statistics.
+ ///
+ virtual void resetStats();
+
+ /// @brief Create TSIG context.
+ ///
+ /// @note Derived classes can implement their own specific context.
+ ///
+ /// @return The specific @ref dns::TSIGContext of the @ref dns::TSIGKey.
+ virtual dns::TSIGContextPtr createContext();
+
+private:
+ /// @brief Initialize key statistics.
+ void initStats();
+
+ /// @brief Remove key statistics.
+ void removeStats();
+};
+
+/// @brief Type of pointer to a D2 TSIG key.
+typedef boost::shared_ptr<D2TsigKey> D2TsigKeyPtr;
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_TSIG_KEY_H
diff --git a/src/lib/d2srv/d2_update_message.cc b/src/lib/d2srv/d2_update_message.cc
new file mode 100644
index 0000000..1917d6e
--- /dev/null
+++ b/src/lib/d2srv/d2_update_message.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2013-2021 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 <d2srv/d2_update_message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+
+namespace isc {
+namespace d2 {
+
+using namespace isc::dns;
+
+D2UpdateMessage::D2UpdateMessage(const Direction direction)
+ : message_(direction == INBOUND ?
+ dns::Message::PARSE : dns::Message::RENDER) {
+ // If this object is to create an outgoing message, we have to
+ // set the proper Opcode field and QR flag here.
+ if (direction == OUTBOUND) {
+ message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
+ message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
+ message_.setRcode(Rcode::NOERROR());
+ }
+}
+
+D2UpdateMessage::QRFlag
+D2UpdateMessage::getQRFlag() const {
+ return (message_.getHeaderFlag(dns::Message::HEADERFLAG_QR) ?
+ RESPONSE : REQUEST);
+}
+
+uint16_t
+D2UpdateMessage::getId() const {
+ return (message_.getQid());
+}
+
+void
+D2UpdateMessage::setId(const uint16_t id) {
+ message_.setQid(id);
+}
+
+
+const dns::Rcode&
+D2UpdateMessage::getRcode() const {
+ return (message_.getRcode());
+}
+
+void
+D2UpdateMessage::setRcode(const dns::Rcode& rcode) {
+ message_.setRcode(rcode);
+}
+
+unsigned int
+D2UpdateMessage::getRRCount(const UpdateMsgSection section) const {
+ return (message_.getRRCount(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::beginSection(const UpdateMsgSection section) const {
+ return (message_.beginSection(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::endSection(const UpdateMsgSection section) const {
+ return (message_.endSection(ddnsToDnsSection(section)));
+}
+
+void
+D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
+ // The Zone data is kept in the underlying Question class. If there
+ // is a record stored there already, we need to remove it, because
+ // we may have at most one Zone record in the DNS Update message.
+ if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
+ message_.clearSection(dns::Message::SECTION_QUESTION);
+ }
+ // Add the new record...
+ Question question(zone, rrclass, RRType::SOA());
+ message_.addQuestion(question);
+ // ... and update the local class member holding the D2Zone object.
+ zone_.reset(new D2Zone(question.getName(), question.getClass()));
+}
+
+D2ZonePtr
+D2UpdateMessage::getZone() const {
+ return (zone_);
+}
+
+void
+D2UpdateMessage::addRRset(const UpdateMsgSection section,
+ const dns::RRsetPtr& rrset) {
+ if (section == SECTION_ZONE) {
+ isc_throw(isc::BadValue, "unable to add RRset to the Zone section"
+ " of the DNS Update message, use setZone instead");
+ }
+ message_.addRRset(ddnsToDnsSection(section), rrset);
+}
+
+void
+D2UpdateMessage::toWire(AbstractMessageRenderer& renderer,
+ TSIGContext* const tsig_context) {
+ // We are preparing the wire format of the message, meaning
+ // that this message will be sent as a request to the DNS.
+ // Therefore, we expect that this message is a REQUEST.
+ if (getQRFlag() != REQUEST) {
+ isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
+ " DNS Update message");
+ }
+ // According to RFC2136, the ZONE section may contain exactly one
+ // record.
+ if (getRRCount(SECTION_ZONE) != 1) {
+ isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
+ " must comprise exactly one record (RFC2136, section 2.3)");
+ }
+ message_.toWire(renderer, tsig_context);
+}
+
+void
+D2UpdateMessage::fromWire(const void* received_data, size_t bytes_received,
+ dns::TSIGContext* const tsig_context) {
+ // First, use the underlying dns::Message implementation to get the
+ // contents of the DNS response message. Note that it may or may
+ // not be the message that we are interested in, but needs to be
+ // parsed so as we can check its ID, Opcode etc.
+ isc::util::InputBuffer received_data_buffer(received_data, bytes_received);
+ message_.fromWire(received_data_buffer);
+
+ // If tsig_context is not NULL, then we need to verify the message.
+ if (tsig_context) {
+ TSIGError error = tsig_context->verify(message_.getTSIGRecord(),
+ received_data, bytes_received);
+ if (error != TSIGError::NOERROR()) {
+ isc_throw(TSIGVerifyError, "TSIG verification failed: "
+ << error.toText());
+ }
+ }
+
+ // This class exposes the getZone() function. This function will return
+ // pointer to the D2Zone object if non-empty Zone section exists in the
+ // received message. It will return NULL pointer if it doesn't exist.
+ // The pointer is held in the D2UpdateMessage class member. We need to
+ // update this pointer every time we parse the message.
+ if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
+ // There is a Zone section in the received message. Replace
+ // Zone pointer with the new value.
+ QuestionPtr question = *message_.beginQuestion();
+ // If the Zone counter is greater than 0 (which we have checked)
+ // there must be a valid Question pointer stored in the message_
+ // object. If there isn't, it is a programming error.
+ if (!question) {
+ isc_throw(isc::Unexpected, "question is null?!");
+ }
+ zone_.reset(new D2Zone(question->getName(), question->getClass()));
+
+ } else {
+ // Zone section doesn't hold any pointers, so set the pointer to NULL.
+ zone_.reset();
+
+ }
+ // Check that the content of the received message is sane.
+ // One of the basic checks to do is to verify that we have
+ // received the DNS update message. If not, it can be dropped
+ // or an error message can be printed. Other than that, we
+ // will check that there is at most one Zone record and QR flag
+ // is set.
+ validateResponse();
+}
+
+dns::Message::Section
+D2UpdateMessage::ddnsToDnsSection(const UpdateMsgSection section) {
+ /// The following switch maps the enumerator values from the
+ /// DNS Update message to the corresponding enumerator values
+ /// representing fields of the DNS message.
+ switch(section) {
+ case SECTION_ZONE :
+ return (dns::Message::SECTION_QUESTION);
+
+ case SECTION_PREREQUISITE:
+ return (dns::Message::SECTION_ANSWER);
+
+ case SECTION_UPDATE:
+ return (dns::Message::SECTION_AUTHORITY);
+
+ case SECTION_ADDITIONAL:
+ return (dns::Message::SECTION_ADDITIONAL);
+
+ default:
+ ;
+ }
+ isc_throw(dns::InvalidMessageSection,
+ "unknown message section " << section);
+}
+
+void
+D2UpdateMessage::validateResponse() const {
+ // Verify that we are dealing with the DNS Update message. According to
+ // RFC 2136, section 3.8 server will copy the Opcode from the query.
+ // If we are dealing with a different type of message, we may simply
+ // stop further processing, because it is likely that the message was
+ // directed to someone else.
+ if (message_.getOpcode() != Opcode::UPDATE()) {
+ isc_throw(NotUpdateMessage, "received message is not a DDNS update,"
+ << " received message code is "
+ << message_.getOpcode().getCode());
+ }
+ // Received message should have QR flag set, which indicates that it is
+ // a RESPONSE.
+ if (getQRFlag() == REQUEST) {
+ isc_throw(InvalidQRFlag, "received message should have QR flag set,"
+ " to indicate that it is a RESPONSE message; the QR"
+ << " flag in received message is unset");
+ }
+ // DNS server may copy a Zone record from the query message. Since query
+ // must comprise exactly one Zone record (RFC 2136, section 2.3), the
+ // response message may contain 1 record at most. It may also contain no
+ // records if a server chooses not to copy Zone section.
+ if (getRRCount(SECTION_ZONE) > 1) {
+ isc_throw(InvalidZoneSection, "received message contains "
+ << getRRCount(SECTION_ZONE) << " Zone records,"
+ << " it should contain at most 1 record");
+ }
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/d2_update_message.h b/src/lib/d2srv/d2_update_message.h
new file mode 100644
index 0000000..8365128
--- /dev/null
+++ b/src/lib/d2srv/d2_update_message.h
@@ -0,0 +1,357 @@
+// Copyright (C) 2013-2021 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 D2_UPDATE_MESSAGE_H
+#define D2_UPDATE_MESSAGE_H
+
+#include <d2srv/d2_zone.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/tsig.h>
+
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception indicating that Zone section contains invalid content.
+///
+/// This exception is thrown when ZONE section of the DNS Update message
+/// is invalid. According to RFC2136, section 2.3, the zone section is
+/// allowed to contain exactly one record. When Request message contains
+/// more records or is empty, this exception is thrown.
+class InvalidZoneSection : public Exception {
+public:
+ InvalidZoneSection(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that QR flag has invalid value.
+///
+/// This exception is thrown when QR flag has invalid value for
+/// the operation performed on the particular message. For instance,
+/// the QR flag must be set to indicate that the given message is
+/// a RESPONSE when @c D2UpdateMessage::fromWire is performed.
+/// The QR flag must be cleared when @c D2UpdateMessage::toWire
+/// is executed.
+class InvalidQRFlag : public Exception {
+public:
+ InvalidQRFlag(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that the parsed message is not DNS Update.
+///
+/// This exception is thrown when decoding the DNS message which is not
+/// a DNS Update.
+class NotUpdateMessage : public Exception {
+public:
+ NotUpdateMessage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that a signed, inbound message failed to verify
+///
+/// This exception is thrown when TSIG verification of a DNS server's response
+/// fails.
+class TSIGVerifyError : public Exception {
+public:
+ TSIGVerifyError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+class D2UpdateMessage;
+
+/// @brief Pointer to the DNS Update Message.
+typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr;
+
+/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
+///
+/// This class represents the DNS Update message. Functions exposed by this
+/// class allow to specify the data sections carried by the message and create
+/// an on-wire format of this message. This class is also used to decode
+/// messages received from the DNS server in the on-wire format.
+///
+/// <b>Design choice:</b> A dedicated class has been created to encapsulate
+/// DNS Update message because existing @c isc::dns::Message is designed to
+/// support regular DNS messages (described in RFC 1035) only. Although DNS
+/// Update has the same format, particular sections serve different purposes.
+/// In order to avoid rewrite of significant portions of @c isc::dns::Message
+/// class, this class is implemented in-terms-of @c isc::dns::Message class
+/// to reuse its functionality where possible.
+class D2UpdateMessage {
+public:
+
+ /// @brief Indicates if the @c D2UpdateMessage object encapsulates Inbound
+ /// or Outbound message.
+ enum Direction {
+ INBOUND,
+ OUTBOUND
+ };
+
+ /// @brief Indicates whether DNS Update message is a REQUEST or RESPONSE.
+ enum QRFlag {
+ REQUEST,
+ RESPONSE
+ };
+
+ /// @brief Identifies sections in the DNS Update Message.
+ ///
+ /// Each message comprises message Header and may contain the following
+ /// sections:
+ /// - ZONE
+ /// - PREREQUISITE
+ /// - UPDATE
+ /// - ADDITIONAL
+ ///
+ /// The enum elements are used by functions such as @c getRRCount (to get
+ /// the number of records in a corresponding section) and @c beginSection
+ /// and @c endSection (to access data in the corresponding section).
+ enum UpdateMsgSection {
+ SECTION_ZONE,
+ SECTION_PREREQUISITE,
+ SECTION_UPDATE,
+ SECTION_ADDITIONAL
+ };
+
+public:
+ /// @brief Constructor used to create an instance of the DNS Update Message
+ /// (either outgoing or incoming).
+ ///
+ /// This constructor is used to create an instance of either incoming or
+ /// outgoing DNS Update message. The boolean argument indicates whether it
+ /// is incoming (true) or outgoing (false) message. For incoming messages
+ /// the @c D2UpdateMessage::fromWire function is used to parse on-wire data.
+ /// For outgoing messages, modifier functions should be used to set the
+ /// message contents and @c D2UpdateMessage::toWire function to create
+ /// on-wire data.
+ ///
+ /// @param direction indicates if this is an inbound or outbound message.
+ D2UpdateMessage(const Direction direction = OUTBOUND);
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because we assume
+ /// there will be no need to copy messages on the client side.
+ //@{
+private:
+ D2UpdateMessage(const D2UpdateMessage& source);
+ D2UpdateMessage& operator=(const D2UpdateMessage& source);
+ //@}
+
+public:
+
+ /// @brief Returns enum value indicating if the message is a
+ /// REQUEST or RESPONSE
+ ///
+ /// The returned value is REQUEST if the message is created as an outgoing
+ /// message. In such case the QR flag bit in the message header is cleared.
+ /// The returned value is RESPONSE if the message is created as an incoming
+ /// message and the QR flag bit was set in the received message header.
+ ///
+ /// @return An enum value indicating whether the message is a
+ /// REQUEST or RESPONSE.
+ QRFlag getQRFlag() const;
+
+ /// @brief Returns message ID.
+ ///
+ /// @return message ID.
+ uint16_t getId() const;
+
+ /// @brief Sets message ID.
+ ///
+ /// @param id 16-bit value of the message id.
+ void setId(const uint16_t id);
+
+ /// @brief Returns an object representing message RCode.
+ ///
+ /// @return An object representing message RCode.
+ const dns::Rcode& getRcode() const;
+
+ /// @brief Sets message RCode.
+ ///
+ /// @param rcode An object representing message RCode.
+ void setRcode(const dns::Rcode& rcode);
+
+ /// @brief Returns number of RRsets in the specified message section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the number of RRsets is to be returned.
+ ///
+ /// @return A number of RRsets in the specified message section.
+ unsigned int getRRCount(const UpdateMsgSection section) const;
+
+ /// @name Functions returning iterators to RRsets in message sections.
+ ///
+ //@{
+ /// @brief Return iterators pointing to the beginning of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the beginning of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator beginSection(const UpdateMsgSection section) const;
+
+ /// @brief Return iterators pointing to the end of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the end of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator endSection(const UpdateMsgSection section) const;
+ //@}
+
+ /// @brief Sets the Zone record.
+ ///
+ /// This function creates the @c D2Zone object, representing a Zone record
+ /// for the outgoing message. If the Zone record is already set, it is
+ /// replaced by the new record being set by this function. The RRType for
+ /// the record is always SOA.
+ ///
+ /// @param zone A name of the zone being updated.
+ /// @param rrclass A class of the zone record.
+ void setZone(const dns::Name& zone, const dns::RRClass& rrclass);
+
+ /// @brief Returns a pointer to the object representing Zone record.
+ ///
+ /// @return A pointer to the object representing Zone record.
+ D2ZonePtr getZone() const;
+
+ /// @brief Adds an RRset to the specified section.
+ ///
+ /// This function may throw exception if the specified section is
+ /// out of bounds or Zone section update is attempted. For Zone
+ /// section @c D2UpdateMessage::setZone function should be used instead.
+ /// Also, this function expects that @c rrset argument is non-NULL.
+ ///
+ /// @param section A message section where the RRset should be added.
+ /// @param rrset A reference to a RRset which should be added.
+ void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset);
+
+ /// @name Functions to handle message encoding and decoding.
+ ///
+ //@{
+ /// @brief Encode outgoing message into wire format.
+ ///
+ /// This function encodes the DNS Update into the wire format. The format of
+ /// such a message is described in the RFC2136, section 2. Some of the
+ /// sections which belong to encoded message may be empty. If a particular
+ /// message section is empty (does not comprise any RRs), the corresponding
+ /// counter in the message header is set to 0. These counters are: PRCOUNT,
+ /// UPCOUNT, ADCOUNT for the Prerequisites, Update RRs and Additional Data
+ /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
+ /// requires that the message comprises exactly one Zone record.
+ ///
+ /// If given a TSIG context, this method will pass the context down into
+ /// dns::Message.toWire() method which signs the message using the context.
+ ///
+ /// This function does not guarantee exception safety. However, exceptions
+ /// should be rare because @c D2UpdateMessage class API prevents invalid
+ /// use of the class. The typical case, when this function may throw an
+ /// exception is when this it is called on the object representing
+ /// incoming (instead of outgoing) message. In such case, the QR field
+ /// will be set to RESPONSE, which is invalid setting when calling this
+ /// function.
+ ///
+ /// @param renderer A renderer object used to generate the message wire
+ /// format.
+ /// @param tsig_ctx A TSIG context that is to be used for signing the
+ /// message. If NULL the message will not be signed.
+ void toWire(dns::AbstractMessageRenderer& renderer,
+ dns::TSIGContext* const tsig_ctx = NULL);
+
+ /// @brief Decode incoming message from the wire format.
+ ///
+ /// This function decodes the DNS Update message stored in the buffer
+ /// specified by the function argument. If given a TSIG context, then
+ /// the function will first attempt to use that context to verify the
+ /// message signature. If verification fails a TSIGVerifyError exception
+ /// will be thrown. The function then parses message header and extracts
+ /// the section counters: ZOCOUNT, PRCOUNT, UPCOUNT and ADCOUNT. Using
+ /// these counters, function identifies message sections, which follow
+ /// message header. These sections can be later accessed using:
+ /// @c D2UpdateMessage::getZone, @c D2UpdateMessage::beginSection and
+ /// @c D2UpdateMessage::endSection functions.
+ ///
+ /// This function is NOT exception safe. It signals message decoding errors
+ /// through exceptions. Message decoding error may occur if the received
+ /// message does not conform to the general DNS Message format, specified in
+ /// RFC 1035. Errors which are specific to DNS Update messages include:
+ /// - Invalid Opcode - not an UPDATE.
+ /// - Invalid QR flag - the QR bit should be set to indicate that the
+ /// message is the server response.
+ /// - The number of records in the Zone section is greater than 1.
+ ///
+ /// @param received_data buffer holding DNS Update message to be parsed.
+ /// @param bytes_received the number of bytes in received_data
+ /// @param tsig_context A TSIG context that is to be used to verify the
+ /// message. If NULL TSIG verification will not be attempted.
+ void fromWire(const void* received_data, size_t bytes_received,
+ dns::TSIGContext* const tsig_context = NULL);
+ //@}
+
+private:
+ /// Maps the values of the @c UpdateMessageSection field to the
+ /// corresponding values in the @c isc::dns::Message class. This
+ /// mapping is required here because this class uses @c isc::dns::Message
+ /// class to do the actual processing of the DNS Update message.
+ ///
+ /// @param section An enum indicating the section for which the
+ /// corresponding enum value from @c isc::dns::Message will be returned.
+ ///
+ /// @return The enum value indicating the section in the DNS message
+ /// represented by the @c isc::dns::Message class.
+ static
+ dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section);
+
+ /// @brief Checks received response message for correctness.
+ ///
+ /// This function verifies that the received response from a server is
+ /// correct. Currently this function checks the following:
+ /// - Opcode is 'DNS Update',
+ /// - QR flag is RESPONSE (flag bit is set),
+ /// - Zone section comprises at most one record.
+ ///
+ /// The function will throw exception if any of the conditions above are
+ /// not met.
+ ///
+ /// @throw isc::d2::NotUpdateMessage if invalid Opcode.
+ /// @throw isc::d2::InvalidQRFlag if QR flag is not set to RESPONSE
+ /// @throw isc::d2::InvalidZone section, if Zone section comprises more
+ /// than one record.
+ void validateResponse() const;
+
+ /// @brief An object representing DNS Message which is used by the
+ /// implementation of @c D2UpdateMessage to perform low level.
+ ///
+ /// Declaration of this object pollutes the header with the details
+ /// of @c D2UpdateMessage implementation. It might be cleaner to use
+ /// Pimpl idiom to hide this object in an D2UpdateMessageImpl. However,
+ /// it would bring additional complications to the implementation
+ /// while the benefit would low - this header is not a part of any
+ /// common library. Therefore, if implementation is changed, modification of
+ /// private members of this class in the header has low impact.
+ dns::Message message_;
+
+ /// @brief Holds a pointer to the object, representing Zone in the DNS
+ /// Update.
+ D2ZonePtr zone_;
+
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_UPDATE_MESSAGE_H
diff --git a/src/lib/d2srv/d2_zone.cc b/src/lib/d2srv/d2_zone.cc
new file mode 100644
index 0000000..c48b2c5
--- /dev/null
+++ b/src/lib/d2srv/d2_zone.cc
@@ -0,0 +1,30 @@
+// Copyright (C) 2013-2021 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 <d2srv/d2_zone.h>
+
+namespace isc {
+namespace d2 {
+
+D2Zone::D2Zone(const dns::Name& name, const dns::RRClass& rrclass)
+ : name_(name), rrclass_(rrclass) {
+}
+
+std::string D2Zone::toText() const {
+ return (name_.toText() + " " + rrclass_.toText() + " SOA\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Zone& zone) {
+ os << zone.toText();
+ return (os);
+}
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/lib/d2srv/d2_zone.h b/src/lib/d2srv/d2_zone.h
new file mode 100644
index 0000000..077ad16
--- /dev/null
+++ b/src/lib/d2srv/d2_zone.h
@@ -0,0 +1,109 @@
+// Copyright (C) 2013-2021 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 D2_ZONE_H
+#define D2_ZONE_H
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief The @c D2Zone encapsulates the Zone section in DNS Update message.
+///
+/// This class is used by the @c D2UpdateMessage to encapsulate the Zone section
+/// of the DNS Update message. Class members hold corresponding values of
+/// section's fields: NAME, CLASS. This class does not hold the RTYPE field
+/// value because RTYPE is always equal to SOA for DNS Update message (see
+/// RFC 2136, section 2.3).
+///
+/// Note, that this @c D2Zone class neither exposes functions to decode messages
+/// from wire format nor to encode to wire format. This is not needed, because
+/// @c isc::d2::D2UpdateMessage class uses @c D2Zone only to return the parsed
+/// Zone information to the caller. Internally, D2UpdateMessage parses and
+/// stores Zone section using @c isc::dns::Question class, and the @c toWire
+/// and @c fromWire functions of the @c isc::dns::Question class are used.
+class D2Zone {
+public:
+ /// @brief Constructor from Name and RRClass.
+ ///
+ /// @param name The name of the Zone.
+ /// @param rrclass The RR class of the Zone.
+ D2Zone(const dns::Name& name, const dns::RRClass& rrclass);
+
+ ///
+ /// @name Getters
+ ///
+ //@{
+ /// @brief Returns the Zone name.
+ ///
+ /// @return A reference to the Zone name.
+ const dns::Name& getName() const { return (name_); }
+
+ /// @brief Returns the Zone class.
+ ///
+ /// @return A reference to the Zone class.
+ const dns::RRClass& getClass() const { return (rrclass_); }
+ //@}
+
+ /// @brief Returns text representation of the Zone.
+ ///
+ /// This function concatenates the name of the Zone, Class and Type.
+ /// The type is always SOA.
+ ///
+ /// @return A text representation of the Zone.
+ std::string toText() const;
+
+ ///
+ /// @name Comparison Operators
+ ///
+ //@{
+ /// @brief Equality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if name and class are equal, false otherwise.
+ bool operator==(const D2Zone& rhs) const {
+ return ((rrclass_ == rhs.rrclass_) && (name_ == rhs.name_));
+ }
+
+ /// @brief Inequality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if any of name or class are unequal, false otherwise.
+ bool operator!=(const D2Zone& rhs) const {
+ return (!operator==(rhs));
+ }
+ //@}
+
+private:
+ dns::Name name_; ///< Holds the Zone name.
+ dns::RRClass rrclass_; ///< Holds the Zone class.
+};
+
+typedef boost::shared_ptr<D2Zone> D2ZonePtr;
+
+/// @brief Insert the @c D2Zone as a string into stream.
+///
+/// @param os A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param zone A reference to the @c D2Zone 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 D2Zone& zone);
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_ZONE_H
diff --git a/src/lib/d2srv/d2srv.dox b/src/lib/d2srv/d2srv.dox
new file mode 100644
index 0000000..71d88d5
--- /dev/null
+++ b/src/lib/d2srv/d2srv.dox
@@ -0,0 +1,21 @@
+// Copyright (C) 2020-2022 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/.
+
+/**
+ @page libd2srv libkea-d2srv - Kea D2 Server Library
+
+This library contains most of the DNS classes used by the Kea D2 server
+(kea-dhcp-ddns) but is organized as an independent library so that it
+can be reused as needed by other components.
+
+For a more detailed documentation about the design and class separation
+see @ref src/bin/d2/d2.dox.
+
+@section d2srvMTConsiderations Multi-Threading Consideration for D2 Server Library
+
+By default this library is not thread safe.
+
+*/
diff --git a/src/lib/d2srv/dns_client.cc b/src/lib/d2srv/dns_client.cc
new file mode 100644
index 0000000..5798207
--- /dev/null
+++ b/src/lib/d2srv/dns_client.cc
@@ -0,0 +1,324 @@
+// Copyright (C) 2013-2021 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 <d2srv/d2_log.h>
+#include <d2srv/dns_client.h>
+#include <dns/messagerenderer.h>
+#include <stats/stats_mgr.h>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+// OutputBuffer objects are pre-allocated before data is written to them.
+// This is a default number of bytes for the buffers we create within
+// DNSClient class.
+const size_t DEFAULT_BUFFER_SIZE = 128;
+
+}
+
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::dns;
+using namespace isc::stats;
+
+// This class provides the implementation for the DNSClient. This allows for
+// the separation of the DNSClient interface from the implementation details.
+// Currently, implementation uses IOFetch object to handle asynchronous
+// communication with the DNS. This design may be revisited in the future. If
+// implementation is changed, the DNSClient API will remain unchanged thanks
+// to this separation.
+class DNSClientImpl : public asiodns::IOFetch::Callback {
+public:
+ /// @brief A buffer holding response from a DNS.
+ util::OutputBufferPtr in_buf_;
+
+ /// A caller-supplied object which will hold the parsed response from DNS.
+ /// The response object is (or descends from) isc::dns::Message and is
+ /// populated using Message::fromWire(). This method may only be called
+ /// once in the lifetime of a Message instance. Therefore, response_ is a
+ /// pointer reference thus allowing this class to replace the object
+ /// pointed to with a new Message instance each time a message is
+ /// received. This allows a single DNSClientImpl instance to be used for
+ /// multiple, sequential IOFetch calls. (@todo Trac# 3286 has been opened
+ /// against dns::Message::fromWire. Should the behavior of fromWire change
+ /// the behavior here with could be reexamined).
+ D2UpdateMessagePtr& response_;
+
+ /// @brief A caller-supplied external callback which is invoked when DNS
+ /// message exchange is complete or interrupted.
+ DNSClient::Callback* callback_;
+
+ /// @brief A Transport Layer protocol used to communicate with a DNS.
+ DNSClient::Protocol proto_;
+
+ /// @brief TSIG context used to sign outbound and verify inbound messages.
+ dns::TSIGContextPtr tsig_context_;
+
+ /// @brief TSIG key name for stats.
+ std::string tsig_key_name_;
+
+ /// @brief Constructor.
+ ///
+ /// @param response_placeholder Message object pointer which will be updated
+ /// with dynamically allocated object holding the DNS server's response.
+ /// @param callback Pointer to an object implementing @c DNSClient::Callback
+ /// class. This object will be called when DNS message exchange completes or
+ /// if an error occurs. NULL value disables callback invocation.
+ /// @param proto caller's preference regarding Transport layer protocol to
+ /// be used by DNS Client to communicate with a server.
+ DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto);
+
+ /// @brief Destructor.
+ virtual ~DNSClientImpl();
+
+ /// @brief This internal callback is called when the DNS update message
+ /// exchange is complete. It further invokes the external callback provided
+ /// by a caller. Before external callback is invoked, an object of the
+ /// D2UpdateMessage type, representing a response from the server is set.
+ virtual void operator()(asiodns::IOFetch::Result result);
+
+ /// @brief Starts asynchronous DNS Update using TSIG.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in milliseconds) for the response. If a response
+ /// is not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ /// @param tsig_key A pointer to an @c D2TsigKeyPtr object that will
+ /// (if not null) be used to sign the DNS Update message and verify the
+ /// response.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key);
+
+ /// @brief This function maps the IO error to the DNSClient error.
+ ///
+ /// @param result The IOFetch result to be converted to DNSClient status.
+ /// @return The DNSClient status corresponding to the IOFetch result.
+ DNSClient::Status getStatus(const asiodns::IOFetch::Result result);
+
+ /// @brief This function updates statistics.
+ ///
+ /// @param stat The statistic name to be incremented.
+ /// @param update_key The flag indicating if the key statistics should also
+ /// be updated.
+ void incrStats(const std::string& stat, bool update_key = true);
+};
+
+DNSClientImpl::DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto)
+ : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
+ response_(response_placeholder), callback_(callback), proto_(proto) {
+
+ // Response should be an empty pointer. It gets populated by the
+ // operator() method.
+ if (response_) {
+ isc_throw(isc::BadValue, "Response buffer pointer should be null");
+ }
+
+ // @todo Currently we only support UDP. The support for TCP is planned for
+ // the future release.
+ if (proto_ == DNSClient::TCP) {
+ isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
+ << " Transport protocol for DNS Updates; please use UDP");
+ }
+
+ // Given that we already eliminated the possibility that TCP is used, it
+ // would be sufficient to check that (proto != DNSClient::UDP). But, once
+ // support TCP is added the check above will disappear and the extra check
+ // will be needed here anyway.
+ // Note that cascaded check is used here instead of:
+ // if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
+ // because some versions of GCC compiler complain that check above would
+ // be always 'false' due to limited range of enumeration. In fact, it is
+ // possible to pass out of range integral value through enum and it should
+ // be caught here.
+ if (proto_ != DNSClient::TCP) {
+ if (proto_ != DNSClient::UDP) {
+ isc_throw(isc::NotImplemented, "invalid transport protocol type '"
+ << proto_ << "' specified for DNS Updates");
+ }
+ }
+}
+
+DNSClientImpl::~DNSClientImpl() {
+}
+
+void
+DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
+ // Get the status from IO. If no success, we just call user's callback
+ // and pass the status code.
+ DNSClient::Status status = getStatus(result);
+ if (status == DNSClient::SUCCESS) {
+ // Allocate a new response message. (Note that Message::fromWire
+ // may only be run once per message, so we need to start fresh
+ // each time.)
+ response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+
+ // Server's response may be corrupted. In such case, fromWire will
+ // throw an exception. We want to catch this exception to return
+ // appropriate status code to the caller and log this event.
+ try {
+ response_->fromWire(in_buf_->getData(), in_buf_->getLength(),
+ tsig_context_.get());
+ incrStats("update-success");
+ } catch (const isc::Exception& ex) {
+ status = DNSClient::INVALID_RESPONSE;
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
+ incrStats("update-error");
+ }
+
+ if (tsig_context_) {
+ // Context is a one-shot deal, get rid of it.
+ tsig_context_.reset();
+ }
+ } else if (status == DNSClient::TIMEOUT) {
+ incrStats("update-timeout");
+ } else {
+ incrStats("update-error");
+ }
+
+ // Once we are done with internal business, let's call a callback supplied
+ // by a caller.
+ if (callback_ != NULL) {
+ (*callback_)(status);
+ }
+}
+
+DNSClient::Status
+DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
+ switch (result) {
+ case IOFetch::SUCCESS:
+ return (DNSClient::SUCCESS);
+
+ case IOFetch::TIME_OUT:
+ return (DNSClient::TIMEOUT);
+
+ case IOFetch::STOPPED:
+ return (DNSClient::IO_STOPPED);
+
+ default:
+ ;
+ }
+ return (DNSClient::OTHER);
+}
+
+void
+DNSClientImpl::doUpdate(asiolink::IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key) {
+ // The underlying implementation which we use to send DNS Updates uses
+ // signed integers for timeout. If we want to avoid overflows we need to
+ // respect this limitation here.
+ if (wait > DNSClient::getMaxTimeout()) {
+ isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
+ " not exceed " << DNSClient::getMaxTimeout()
+ << ". Provided timeout value is '" << wait << "'");
+ }
+
+ // Create a TSIG context if we have a key, otherwise clear the context
+ // pointer. Message marshalling uses non-null context is the indicator
+ // that TSIG should be used.
+ if (tsig_key) {
+ tsig_context_ = tsig_key->createContext();
+ tsig_key_name_ = tsig_key->getKeyName().toText();
+ } else {
+ tsig_context_.reset();
+ tsig_key_name_.clear();
+ }
+
+ // A renderer is used by the toWire function which creates the on-wire data
+ // from the DNS Update message. A renderer has its internal buffer where it
+ // renders data by default. However, this buffer can't be directly accessed.
+ // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
+ // create our own buffer and pass it to the renderer so as the message is
+ // rendered to this buffer. Finally, we pass this buffer to IOFetch.
+ dns::MessageRenderer renderer;
+ OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
+ renderer.setBuffer(msg_buf.get());
+
+ // Render DNS Update message. This may throw a bunch of exceptions if
+ // invalid message object is given.
+ update.toWire(renderer, tsig_context_.get());
+
+ // IOFetch has all the mechanisms that we need to perform asynchronous
+ // communication with the DNS server. The last but one argument points to
+ // this object as a completion callback for the message exchange. As a
+ // result operator()(Status) will be called.
+
+ // Timeout value is explicitly cast to the int type to avoid warnings about
+ // overflows when doing implicit cast. It should have been checked by the
+ // caller that the unsigned timeout value will fit into int.
+ IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
+ in_buf_, this, static_cast<int>(wait));
+
+ // Post the task to the task queue in the IO service. Caller will actually
+ // run these tasks by executing IOService::run.
+ io_service.post(io_fetch);
+
+ // Update sent statistics.
+ incrStats("update-sent");
+ if (tsig_key) {
+ incrStats("update-signed", false);
+ } else {
+ incrStats("update-unsigned", false);
+ }
+}
+
+void
+DNSClientImpl::incrStats(const std::string& stat, bool update_key) {
+ StatsMgr& mgr = StatsMgr::instance();
+ mgr.addValue(stat, static_cast<int64_t>(1));
+ if (update_key && !tsig_key_name_.empty()) {
+ mgr.addValue(StatsMgr::generateName("key", tsig_key_name_, stat),
+ static_cast<int64_t>(1));
+ }
+}
+
+DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
+ Callback* callback, const DNSClient::Protocol proto)
+ : impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
+}
+
+DNSClient::~DNSClient() {
+}
+
+unsigned int
+DNSClient::getMaxTimeout() {
+ static const unsigned int max_timeout = std::numeric_limits<int>::max();
+ return (max_timeout);
+}
+
+void
+DNSClient::doUpdate(asiolink::IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key) {
+ impl_->doUpdate(io_service, ns_addr, ns_port, update, wait, tsig_key);
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/lib/d2srv/dns_client.h b/src/lib/d2srv/dns_client.h
new file mode 100644
index 0000000..c63a7a0
--- /dev/null
+++ b/src/lib/d2srv/dns_client.h
@@ -0,0 +1,157 @@
+// Copyright (C) 2013-2021 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_CLIENT_H
+#define DNS_CLIENT_H
+
+#include <asiolink/io_service.h>
+#include <asiodns/io_fetch.h>
+#include <d2srv/d2_tsig_key.h>
+#include <d2srv/d2_update_message.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace d2 {
+
+class DNSClient;
+typedef boost::shared_ptr<DNSClient> DNSClientPtr;
+
+/// DNSClient class implementation.
+class DNSClientImpl;
+
+/// @brief The @c DNSClient class handles communication with the DNS server.
+///
+/// Communication with the DNS server is asynchronous. Caller must provide a
+/// callback, which will be invoked when the response from the DNS server is
+/// received, a timeout has occurred or IO service has been stopped for any
+/// reason. The caller-supplied callback is called by the internal callback
+/// operator implemented by @c DNSClient. This callback is responsible for
+/// initializing the @c D2UpdateMessage instance which encapsulates the response
+/// from the DNS. This initialization does not take place if the response from
+/// DNS is not received.
+///
+/// Caller must supply a pointer to the @c D2UpdateMessage object, which will
+/// encapsulate DNS response, through class constructor. An exception will be
+/// thrown if the pointer is not initialized by the caller.
+///
+/// @todo Ultimately, this class will support both TCP and UDP Transport.
+/// Currently only UDP is supported and can be specified as a preferred
+/// protocol. @c DNSClient constructor will throw an exception if TCP is
+/// specified. Once both protocols are supported, the @c DNSClient logic will
+/// try to obey caller's preference. However, it may use the other protocol if
+/// on its own discretion, when there is a legitimate reason to do so. For
+/// example, if communication with the server using preferred protocol fails.
+class DNSClient {
+public:
+
+ /// @brief Transport layer protocol used by a DNS Client to communicate
+ /// with a server.
+ enum Protocol {
+ UDP,
+ TCP
+ };
+
+ /// @brief A status code of the DNSClient.
+ enum Status {
+ SUCCESS, ///< Response received and is ok.
+ TIMEOUT, ///< No response, timeout.
+ IO_STOPPED, ///< IO was stopped.
+ INVALID_RESPONSE, ///< Response received but invalid.
+ OTHER ///< Other, unclassified error.
+ };
+
+ /// @brief Callback for the @c DNSClient class.
+ ///
+ /// This is an abstract class which represents the external callback for the
+ /// @c DNSClient. Caller must implement this class and supply its instance
+ /// in the @c DNSClient constructor to get callbacks when the DNS Update
+ /// exchange is complete (@see @c DNSClient).
+ class Callback {
+ public:
+ /// @brief Virtual destructor.
+ virtual ~Callback() { }
+
+ /// @brief Function operator implementing a callback.
+ ///
+ /// @param status a @c DNSClient::Status enum representing status code
+ /// of DNSClient operation.
+ virtual void operator()(DNSClient::Status status) = 0;
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param response_placeholder Message object pointer which will be updated
+ /// with dynamically allocated object holding the DNS server's response.
+ /// @param callback Pointer to an object implementing @c DNSClient::Callback
+ /// class. This object will be called when DNS message exchange completes or
+ /// if an error occurs. NULL value disables callback invocation.
+ /// @param proto caller's preference regarding Transport layer protocol to
+ /// be used by DNS Client to communicate with a server.
+ DNSClient(D2UpdateMessagePtr& response_placeholder, Callback* callback,
+ const Protocol proto = UDP);
+
+ /// @brief Virtual destructor, does nothing.
+ ~DNSClient();
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because there are
+ /// no use cases when a DNSClient instance will need to be copied. Also, it
+ /// is desired to avoid copying a DNSClient::impl_ pointer and external
+ /// callbacks.
+ ///
+ //@{
+private:
+ DNSClient(const DNSClient& source);
+ DNSClient& operator=(const DNSClient& source);
+ //@}
+
+public:
+
+ /// @brief Returns maximal allowed timeout value accepted by
+ /// @c DNSClient::doUpdate.
+ ///
+ /// @return maximal allowed timeout value accepted by @c DNSClient::doUpdate
+ static unsigned int getMaxTimeout();
+
+ /// @brief Start asynchronous DNS Update with TSIG.
+ ///
+ /// This function starts asynchronous DNS Update and returns. The DNS Update
+ /// will be executed by the specified IO service. Once the message exchange
+ /// with a DNS server is complete, timeout occurs or IO operation is
+ /// interrupted, the caller-supplied callback function will be invoked.
+ ///
+ /// An address and port of the DNS server is specified through the function
+ /// arguments so as the same instance of the @c DNSClient can be used to
+ /// initiate multiple message exchanges.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in milliseconds) for the response. If a response
+ /// is not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ /// @param tsig_key A pointer to an @c D2TsigKeyPtr object that will
+ /// (if not null) be used to sign the DNS Update message and verify the
+ /// response.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const D2TsigKeyPtr& tsig_key = D2TsigKeyPtr());
+
+private:
+ /// @brief Pointer to DNSClient implementation.
+ std::unique_ptr<DNSClientImpl> impl_;
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // DNS_CLIENT_H
diff --git a/src/lib/d2srv/nc_trans.cc b/src/lib/d2srv/nc_trans.cc
new file mode 100644
index 0000000..e9534c2
--- /dev/null
+++ b/src/lib/d2srv/nc_trans.cc
@@ -0,0 +1,578 @@
+// Copyright (C) 2013-2021 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 <d2srv/d2_log.h>
+#include <d2srv/nc_trans.h>
+#include <dns/qid_gen.h>
+#include <dns/rdata.h>
+#include <hooks/hooks.h>
+#include <hooks/hooks_manager.h>
+
+#include <sstream>
+
+using namespace isc::hooks;
+using namespace isc::util;
+
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct NcTransHooks {
+ int hooks_index_select_key_;
+
+ /// Constructor that registers hook points for the D2 server.
+ NcTransHooks() {
+ hooks_index_select_key_ = HooksManager::registerHook("select_key");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+
+NcTransHooks Hooks;
+
+}
+
+namespace isc {
+namespace d2 {
+
+// Common transaction states
+const int NameChangeTransaction::READY_ST;
+const int NameChangeTransaction::SELECTING_FWD_SERVER_ST;
+const int NameChangeTransaction::SELECTING_REV_SERVER_ST;
+const int NameChangeTransaction::PROCESS_TRANS_OK_ST;
+const int NameChangeTransaction::PROCESS_TRANS_FAILED_ST;
+
+const int NameChangeTransaction::NCT_DERIVED_STATE_MIN;
+
+// Common transaction events
+const int NameChangeTransaction::SELECT_SERVER_EVT;
+const int NameChangeTransaction::SERVER_SELECTED_EVT;
+const int NameChangeTransaction::SERVER_IO_ERROR_EVT;
+const int NameChangeTransaction::NO_MORE_SERVERS_EVT;
+const int NameChangeTransaction::IO_COMPLETED_EVT;
+const int NameChangeTransaction::UPDATE_OK_EVT;
+const int NameChangeTransaction::UPDATE_FAILED_EVT;
+
+const int NameChangeTransaction::NCT_DERIVED_EVENT_MIN;
+
+const unsigned int NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+
+NameChangeTransaction::
+NameChangeTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
+ reverse_domain_(reverse_domain), dns_client_(), dns_update_request_(),
+ dns_update_status_(DNSClient::OTHER), dns_update_response_(),
+ forward_change_completed_(false), reverse_change_completed_(false),
+ current_server_list_(), current_server_(), next_server_pos_(0),
+ update_attempts_(0), cfg_mgr_(cfg_mgr), tsig_key_() {
+ /// @todo if io_service is NULL we are multi-threading and should
+ /// instantiate our own
+ if (!io_service_) {
+ isc_throw(NameChangeTransactionError, "IOServicePtr cannot be null");
+ }
+
+ if (!ncr_) {
+ isc_throw(NameChangeTransactionError,
+ "NameChangeRequest cannot be null");
+ }
+
+ if (ncr_->isForwardChange() && !(forward_domain_)) {
+ isc_throw(NameChangeTransactionError,
+ "Forward change must have a forward domain");
+ }
+
+ if (ncr_->isReverseChange() && !(reverse_domain_)) {
+ isc_throw(NameChangeTransactionError,
+ "Reverse change must have a reverse domain");
+ }
+
+ if (!cfg_mgr_) {
+ isc_throw(NameChangeTransactionError,
+ "Configuration manager cannot be null");
+ }
+}
+
+NameChangeTransaction::~NameChangeTransaction(){
+}
+
+void
+NameChangeTransaction::startTransaction() {
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_STARTING_TRANSACTION)
+ .arg(getRequestId());
+
+ setNcrStatus(dhcp_ddns::ST_PENDING);
+ startModel(READY_ST);
+}
+
+void
+NameChangeTransaction::operator()(DNSClient::Status status) {
+ // Stow the completion status and re-enter the run loop with the event
+ // set to indicate IO completed.
+ // runModel is exception safe so we are good to call it here.
+ // It won't exit until we hit the next IO wait or the state model ends.
+ setDnsUpdateStatus(status);
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_UPDATE_RESPONSE_RECEIVED)
+ .arg(getRequestId())
+ .arg(current_server_->toText())
+ .arg(responseString());
+
+ runModel(IO_COMPLETED_EVT);
+}
+
+std::string
+NameChangeTransaction::responseString() const {
+ std::ostringstream stream;
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS:
+ stream << "SUCCESS, rcode: ";
+ if (getDnsUpdateResponse()) {
+ stream << getDnsUpdateResponse()->getRcode().toText();
+ } else {
+ stream << " update response is NULL";
+ }
+ break;
+ case DNSClient::TIMEOUT:
+ stream << "TIMEOUT";
+ break;
+ case DNSClient::IO_STOPPED:
+ stream << "IO_STOPPED";
+ break;
+ case DNSClient::INVALID_RESPONSE:
+ stream << "INVALID_RESPONSE";
+ break;
+ case DNSClient::OTHER:
+ stream << "OTHER";
+ break;
+ default:
+ stream << "UNKNOWN("
+ << static_cast<int>(getDnsUpdateStatus()) << ")";
+ break;
+
+ }
+
+ return (stream.str());
+}
+
+std::string
+NameChangeTransaction::transactionOutcomeString() const {
+ std::ostringstream stream;
+ stream << "Status: " << (getNcrStatus() == dhcp_ddns::ST_COMPLETED
+ ? "Completed, " : "Failed, ")
+ << "Event: " << getEventLabel(getNextEvent()) << ", ";
+
+ if (ncr_->isForwardChange()) {
+ stream << " Forward change:" << (getForwardChangeCompleted()
+ ? " completed, " : " failed, ");
+ }
+
+ if (ncr_->isReverseChange()) {
+ stream << " Reverse change:" << (getReverseChangeCompleted()
+ ? " completed, " : " failed, ");
+ }
+
+ stream << " request: " << ncr_->toText();
+ return (stream.str());
+}
+
+
+void
+NameChangeTransaction::sendUpdate(const std::string& comment) {
+ try {
+ ++update_attempts_;
+ // @todo add logic to add/replace TSIG key info in request if
+ // use_tsig_ is true. We should be able to navigate to the TSIG key
+ // for the current server. If not we would need to add that.
+
+ D2ParamsPtr d2_params = cfg_mgr_->getD2Params();
+ dns_client_->doUpdate(*io_service_, current_server_->getIpAddress(),
+ current_server_->getPort(), *dns_update_request_,
+ d2_params->getDnsServerTimeout(), tsig_key_);
+ // Message is on its way, so the next event should be NOP_EVT.
+ postNextEvent(NOP_EVT);
+ LOG_DEBUG(d2_to_dns_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_UPDATE_REQUEST_SENT)
+ .arg(getRequestId())
+ .arg(comment)
+ .arg(current_server_->toText());
+ } catch (const std::exception& ex) {
+ // We were unable to initiate the send.
+ // It is presumed that any throw from doUpdate is due to a programmatic
+ // error, such as an unforeseen permutation of data, rather than an IO
+ // failure. IO errors should be caught by the underlying asiolink
+ // mechanisms and manifested as an unsuccessful IO status in the
+ // DNSClient callback. Any problem here most likely means the request
+ // is corrupt in some way and cannot be completed, therefore we will
+ // log it and transition it to failure.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_TRANS_SEND_ERROR)
+ .arg(getRequestId())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+}
+
+void
+NameChangeTransaction::defineEvents() {
+ // Call superclass impl first.
+ StateModel::defineEvents();
+
+ // Define NCT events.
+ defineEvent(SELECT_SERVER_EVT, "SELECT_SERVER_EVT");
+ defineEvent(SERVER_SELECTED_EVT, "SERVER_SELECTED_EVT");
+ defineEvent(SERVER_IO_ERROR_EVT, "SERVER_IO_ERROR_EVT");
+ defineEvent(NO_MORE_SERVERS_EVT, "NO_MORE_SERVERS_EVT");
+ defineEvent(IO_COMPLETED_EVT, "IO_COMPLETED_EVT");
+ defineEvent(UPDATE_OK_EVT, "UPDATE_OK_EVT");
+ defineEvent(UPDATE_FAILED_EVT, "UPDATE_FAILED_EVT");
+}
+
+void
+NameChangeTransaction::verifyEvents() {
+ // Call superclass impl first.
+ StateModel::verifyEvents();
+
+ // Verify NCT events.
+ getEvent(SELECT_SERVER_EVT);
+ getEvent(SERVER_SELECTED_EVT);
+ getEvent(SERVER_IO_ERROR_EVT);
+ getEvent(NO_MORE_SERVERS_EVT);
+ getEvent(IO_COMPLETED_EVT);
+ getEvent(UPDATE_OK_EVT);
+ getEvent(UPDATE_FAILED_EVT);
+}
+
+void
+NameChangeTransaction::defineStates() {
+ // Call superclass impl first.
+ StateModel::defineStates();
+ // This class is "abstract" in that it does not supply handlers for its
+ // states, derivations must do that therefore they must define them.
+}
+
+void
+NameChangeTransaction::verifyStates() {
+ // Call superclass impl first.
+ StateModel::verifyStates();
+
+ // Verify NCT states. This ensures that derivations provide the handlers.
+ getStateInternal(READY_ST);
+ getStateInternal(SELECTING_FWD_SERVER_ST);
+ getStateInternal(SELECTING_REV_SERVER_ST);
+ getStateInternal(PROCESS_TRANS_OK_ST);
+ getStateInternal(PROCESS_TRANS_FAILED_ST);
+}
+
+void
+NameChangeTransaction::onModelFailure(const std::string& explanation) {
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR)
+ .arg(getRequestId())
+ .arg(explanation);
+}
+
+void
+NameChangeTransaction::retryTransition(const int fail_to_state) {
+ if (update_attempts_ < MAX_UPDATE_TRIES_PER_SERVER) {
+ // Re-enter the current state with same server selected.
+ transition(getCurrState(), SERVER_SELECTED_EVT);
+ } else {
+ // Transition to given fail_to_state state if we are out
+ // of retries.
+ transition(fail_to_state, SERVER_IO_ERROR_EVT);
+ }
+}
+
+void
+NameChangeTransaction::setDnsUpdateRequest(D2UpdateMessagePtr& request) {
+ dns_update_request_ = request;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateRequest() {
+ dns_update_request_.reset();
+}
+
+void
+NameChangeTransaction::clearUpdateAttempts() {
+ update_attempts_ = 0;
+}
+
+void
+NameChangeTransaction::setDnsUpdateStatus(const DNSClient::Status& status) {
+ dns_update_status_ = status;
+}
+
+void
+NameChangeTransaction::setDnsUpdateResponse(D2UpdateMessagePtr& response) {
+ dns_update_response_ = response;
+}
+
+void
+NameChangeTransaction::clearDnsUpdateResponse() {
+ dns_update_response_.reset();
+}
+
+void
+NameChangeTransaction::setForwardChangeCompleted(const bool value) {
+ forward_change_completed_ = value;
+}
+
+void
+NameChangeTransaction::setReverseChangeCompleted(const bool value) {
+ reverse_change_completed_ = value;
+}
+
+void
+NameChangeTransaction::setUpdateAttempts(const size_t value) {
+ update_attempts_ = value;
+}
+
+D2UpdateMessagePtr
+NameChangeTransaction::prepNewRequest(DdnsDomainPtr domain) {
+ if (!domain) {
+ isc_throw(NameChangeTransactionError,
+ "prepNewRequest - domain cannot be null");
+ }
+
+ try {
+ // Create a "blank" update request.
+ D2UpdateMessagePtr request(new D2UpdateMessage(D2UpdateMessage::
+ OUTBOUND));
+ // Set the query id
+ request->setId(dns::QidGenerator::getInstance().generateQid());
+ // Construct the Zone Section.
+ dns::Name zone_name(domain->getName());
+ request->setZone(zone_name, dns::RRClass::IN());
+ return (request);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot create new request :"
+ << ex.what());
+ }
+}
+
+void
+NameChangeTransaction::addLeaseAddressRdata(dns::RRsetPtr& rrset) {
+ if (!rrset) {
+ isc_throw(NameChangeTransactionError,
+ "addLeaseAddressRdata - RRset cannot cannot be null");
+ }
+
+ try {
+ // Manufacture an RData from the lease address then add it to the RR.
+ dns::rdata::ConstRdataPtr rdata;
+ if (ncr_->isV4()) {
+ rdata.reset(new dns::rdata::in::A(ncr_->getIpAddress()));
+ } else {
+ rdata.reset(new dns::rdata::in::AAAA(ncr_->getIpAddress()));
+ }
+ rrset->addRdata(rdata);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot add address rdata: "
+ << ex.what());
+ }
+}
+
+void
+NameChangeTransaction::addDhcidRdata(dns::RRsetPtr& rrset) {
+ if (!rrset) {
+ isc_throw(NameChangeTransactionError,
+ "addDhcidRdata - RRset cannot cannot be null");
+ }
+
+ try {
+ const std::vector<uint8_t>& ncr_dhcid = ncr_->getDhcid().getBytes();
+ util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size());
+ dns::rdata::ConstRdataPtr rdata (new dns::rdata::in::
+ DHCID(buffer, ncr_dhcid.size()));
+ rrset->addRdata(rdata);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot add DCHID rdata: "
+ << ex.what());
+ }
+
+}
+
+void
+NameChangeTransaction::addPtrRdata(dns::RRsetPtr& rrset) {
+ if (!rrset) {
+ isc_throw(NameChangeTransactionError,
+ "addPtrRdata - RRset cannot cannot be null");
+ }
+
+ try {
+ dns::rdata::ConstRdataPtr rdata(new dns::rdata::generic::
+ PTR(getNcr()->getFqdn()));
+ rrset->addRdata(rdata);
+ } catch (const std::exception& ex) {
+ isc_throw(NameChangeTransactionError, "Cannot add PTR rdata: "
+ << ex.what());
+ }
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+NameChangeTransaction::getNcr() const {
+ return (ncr_);
+}
+
+const TransactionKey&
+NameChangeTransaction::getTransactionKey() const {
+ return (ncr_->getDhcid());
+}
+
+std::string
+NameChangeTransaction::getRequestId() const {
+ return (ncr_->getRequestId());
+}
+
+dhcp_ddns::NameChangeStatus
+NameChangeTransaction::getNcrStatus() const {
+ return (ncr_->getStatus());
+}
+
+DdnsDomainPtr&
+NameChangeTransaction::getForwardDomain() {
+ return (forward_domain_);
+}
+
+DdnsDomainPtr&
+NameChangeTransaction::getReverseDomain() {
+ return (reverse_domain_);
+}
+
+void
+NameChangeTransaction::initServerSelection(const DdnsDomainPtr& domain) {
+ if (!domain) {
+ isc_throw(NameChangeTransactionError,
+ "initServerSelection called with an empty domain");
+ }
+
+ current_server_list_ = domain->getServers();
+ next_server_pos_ = 0;
+ current_server_.reset();
+}
+
+bool
+NameChangeTransaction::selectNextServer() {
+ for (;;) {
+ if ((current_server_list_) &&
+ (next_server_pos_ < current_server_list_->size())) {
+ current_server_ = (*current_server_list_)[next_server_pos_];
+ // Toss out any previous response.
+ dns_update_response_.reset();
+
+ // Set the tsig_key to that of the current server.
+ if (!selectTSIGKey()) {
+ ++next_server_pos_;
+ continue;
+ }
+
+ // @todo Protocol is set on DNSClient constructor. We need
+ // to propagate a configuration value downward, probably starting
+ // at global, then domain, finishing by server.
+ // Once that is supported we need to add it here.
+ dns_client_.reset(new DNSClient(dns_update_response_, this,
+ DNSClient::UDP));
+ ++next_server_pos_;
+ return (true);
+ }
+
+ return (false);
+ }
+}
+
+bool
+NameChangeTransaction::selectTSIGKey() {
+ TSIGKeyInfoPtr tsig_key_info = current_server_->getTSIGKeyInfo();
+ if (tsig_key_info) {
+ tsig_key_ = tsig_key_info->getTSIGKey();
+ } else {
+ tsig_key_.reset();
+ }
+
+ // This hook point allows hooks libraries to overwrite the TSIG key.
+ if (HooksManager::calloutsPresent(Hooks.hooks_index_select_key_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ callout_handle->setArgument("current_server", current_server_);
+ callout_handle->setArgument("tsig_key", tsig_key_);
+
+ HooksManager::callCallouts(Hooks.hooks_index_select_key_,
+ *callout_handle);
+
+ // This server is skipped because the status is not NEXT_STEP_CONTINUE.
+ if (callout_handle->getStatus() != CalloutHandle::NEXT_STEP_CONTINUE) {
+ return (false);
+ }
+
+ // Reread the TSIG key.
+ callout_handle->getArgument("tsig_key", tsig_key_);
+ }
+
+ return (true);
+}
+
+
+const DNSClientPtr&
+NameChangeTransaction::getDNSClient() const {
+ return (dns_client_);
+}
+
+const DnsServerInfoPtr&
+NameChangeTransaction::getCurrentServer() const {
+ return (current_server_);
+}
+
+void
+NameChangeTransaction::setNcrStatus(const dhcp_ddns::NameChangeStatus& status) {
+ return (ncr_->setStatus(status));
+}
+
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateRequest() const {
+ return (dns_update_request_);
+}
+
+DNSClient::Status
+NameChangeTransaction::getDnsUpdateStatus() const {
+ return (dns_update_status_);
+}
+
+const D2UpdateMessagePtr&
+NameChangeTransaction::getDnsUpdateResponse() const {
+ return (dns_update_response_);
+}
+
+bool
+NameChangeTransaction::getForwardChangeCompleted() const {
+ return (forward_change_completed_);
+}
+
+bool
+NameChangeTransaction::getReverseChangeCompleted() const {
+ return (reverse_change_completed_);
+}
+
+size_t
+NameChangeTransaction::getUpdateAttempts() const {
+ return (update_attempts_);
+}
+
+const dns::RRType&
+NameChangeTransaction::getAddressRRType() const {
+ return (ncr_->isV4() ? dns::RRType::A() : dns::RRType::AAAA());
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/lib/d2srv/nc_trans.h b/src/lib/d2srv/nc_trans.h
new file mode 100644
index 0000000..2aee7ec
--- /dev/null
+++ b/src/lib/d2srv/nc_trans.h
@@ -0,0 +1,602 @@
+// Copyright (C) 2013-2021 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 NC_TRANS_H
+#define NC_TRANS_H
+
+/// @file nc_trans.h This file defines the class NameChangeTransaction.
+
+#include <asiolink/io_service.h>
+#include <d2srv/dns_client.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_tsig_key.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <exceptions/exceptions.h>
+#include <util/state_model.h>
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the transaction encounters a general error.
+class NameChangeTransactionError : public isc::Exception {
+public:
+ NameChangeTransactionError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines the type used as the unique key for transactions.
+typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS update.
+///
+/// NameChangeTransaction is the base class that provides the common state
+/// model mechanics and services performing the DNS updates needed to carry out
+/// a DHCP_DDNS request as described by a NameChangeRequest. It is derived
+/// from StateModel which supplies a simple, general purpose FSM implementation.
+///
+/// Upon construction, each transaction has all of the information and
+/// resources required to carry out its assigned request, including the list(s)
+/// of DNS server(s) needed. It is responsible for knowing what conversations
+/// it must have with which servers and in the order necessary to fulfill the
+/// request. Upon fulfillment of the request, the transaction's work is complete
+/// and it is destroyed.
+///
+/// Fulfillment of the request is carried out through the performance of the
+/// transaction's state model. Using a state driven implementation accounts
+/// for the conditional processing flow necessary to meet the DDNS RFCs as well
+/// as the asynchronous nature of IO with DNS servers.
+///
+/// Derivations of the class are responsible for defining the state model and
+/// conversations necessary to carry out the specific of request.
+///
+/// Conversations with DNS servers are done through the use of the DNSClient
+/// class. The DNSClient provides a IOService-based means a service which
+/// performs a single, packet exchange with a given DNS server. It sends a
+/// single update to the server and returns the response, asynchronously,
+/// through a callback. At each point in a transaction's state model, where
+/// an update is to be sent, the model "suspends" until notified by the
+/// DNSClient via the callback. Suspension is done by posting a
+/// StateModel::NOP_EVT as the next event, stopping the state model execution.
+///
+/// Resuming state model execution when a DNS update completes is done by a
+/// call to StateModel::runStateModel() from within the DNSClient callback,
+/// with an event value of IO_COMPLETED_EVT (described below).
+///
+/// This class defines a set of events and states that are a common to all
+/// transactions. Each derivation may add define additional states and events
+/// as needed, but it must support the common set. NameChangeTransaction
+/// does not supply any state handlers. These are the sole responsibility of
+/// derivations.
+class NameChangeTransaction : public DNSClient::Callback, public util::StateModel {
+public:
+
+ //@{ States common to all transactions.
+
+ /// @brief State from which a transaction is started.
+ static const int READY_ST = SM_DERIVED_STATE_MIN + 1;
+
+ /// @brief State in which forward DNS server selection is done.
+ ///
+ /// Within this state, the actual selection of the next forward server
+ /// to use is conducted. Upon conclusion of this state the next server
+ /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
+ /// event.
+ static const int SELECTING_FWD_SERVER_ST = SM_DERIVED_STATE_MIN + 2;
+
+ /// @brief State in which reverse DNS server selection is done.
+ ///
+ /// Within this state, the actual selection of the next reverse server
+ /// to use is conducted. Upon conclusion of this state the next server
+ /// is either selected or it should transition out with NO_MORE_SERVERS_EVT
+ /// event.
+ static const int SELECTING_REV_SERVER_ST = SM_DERIVED_STATE_MIN + 3;
+
+ /// @brief State which processes successful transaction conclusion.
+ static const int PROCESS_TRANS_OK_ST = SM_DERIVED_STATE_MIN + 4;
+
+ /// @brief State which processes an unsuccessful transaction conclusion.
+ static const int PROCESS_TRANS_FAILED_ST = SM_DERIVED_STATE_MIN + 5;
+
+ /// @brief Value at which custom states in a derived class should begin.
+ static const int NCT_DERIVED_STATE_MIN = SM_DERIVED_STATE_MIN + 101;
+ //@}
+
+ //@{ Events common to all transactions.
+ /// @brief Issued when a server needs to be selected.
+ static const int SELECT_SERVER_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Issued when a server has been selected.
+ static const int SERVER_SELECTED_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ /// @brief Issued when an update fails due to an IO error.
+ static const int SERVER_IO_ERROR_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ /// @brief Issued when there are no more servers from which to select.
+ /// This occurs when none of the servers in the list can be reached to
+ /// perform the update.
+
+ static const int NO_MORE_SERVERS_EVT =SM_DERIVED_EVENT_MIN + 4;
+ /// @brief Issued when a DNS update packet exchange has completed.
+ /// This occurs whenever the DNSClient callback is invoked whether the
+ /// exchange was successful or not.
+
+ static const int IO_COMPLETED_EVT = SM_DERIVED_EVENT_MIN + 5;
+ /// @brief Issued when the attempted update successfully completed.
+ /// This occurs when an DNS update packet was successfully processed
+ /// by the server.
+
+ static const int UPDATE_OK_EVT = SM_DERIVED_EVENT_MIN + 6;
+
+ /// @brief Issued when the attempted update fails to complete.
+ /// This occurs when an DNS update packet fails to process. The nature of
+ /// the failure is given by the DNSClient return status and the response
+ /// packet (if one was received).
+ static const int UPDATE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 7;
+
+ /// @brief Value at which custom events in a derived class should begin.
+ static const int NCT_DERIVED_EVENT_MIN = SM_DERIVED_EVENT_MIN + 101;
+ //@}
+
+ /// @brief Default time to assign to a single DNS update.
+ /// @todo This value will be made configurable in the very near future
+ /// under trac3268. For now we will define it to 100 milliseconds
+ /// so unit tests will run within a reasonable amount of time.
+ static const unsigned int DNS_UPDATE_DEFAULT_TIMEOUT = 100;
+
+ /// @brief Maximum times to attempt a single update on a given server.
+ static const unsigned int MAX_UPDATE_TRIES_PER_SERVER = 3;
+
+ /// @brief Constructor
+ ///
+ /// Instantiates a transaction that is ready to be started.
+ ///
+ /// @param io_service IO service to be used for IO processing
+ /// @param ncr is the NameChangeRequest to fulfill
+ /// @param forward_domain is the domain to use for forward DNS updates
+ /// @param reverse_domain is the domain to use for reverse DNS updates
+ /// @param cfg_mgr reference to the current configuration manager
+ ///
+ /// @throw NameChangeTransactionError if given an null request,
+ /// if forward change is enabled but forward domain is null, if
+ /// reverse change is enabled but reverse domain is null.
+ NameChangeTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr);
+
+ /// @brief Destructor
+ virtual ~NameChangeTransaction();
+
+ /// @brief Begins execution of the transaction.
+ ///
+ /// This method invokes StateModel::startModel() with a value of READY_ST.
+ /// This causes transaction's state model to attempt to begin execution
+ /// with the state handler for READY_ST.
+ void startTransaction();
+
+ /// @brief Serves as the DNSClient IO completion event handler.
+ ///
+ /// This is the implementation of the method inherited by our derivation
+ /// from DNSClient::Callback. When the DNSClient completes an update it
+ /// invokes this method as the completion handler. This method stores
+ /// the given status and invokes runStateModel() with an event value of
+ /// IO_COMPLETED_EVT.
+ ///
+ /// @param status is the outcome of the DNS update packet exchange.
+ /// This method is exception safe.
+ virtual void operator()(DNSClient::Status status);
+
+protected:
+ /// @brief Send the update request to the current server.
+ ///
+ /// This method increments the update attempt count and then passes the
+ /// current update request to the DNSClient instance to be sent to the
+ /// currently selected server. Since the send is asynchronous, the method
+ /// posts NOP_EVT as the next event and then returns.
+ ///
+ /// If tsig_key_ is not NULL, then the update will be conducted using
+ /// the key to sign the request and verify the response, otherwise it
+ /// will be conducted without TSIG.
+ ///
+ /// @param comment text to include in log detail
+ ///
+ /// If an exception occurs it will be logged and and the transaction will
+ /// be failed.
+ virtual void sendUpdate(const std::string& comment = "");
+
+ /// @brief Adds events defined by NameChangeTransaction to the event set.
+ ///
+ /// This method adds the events common to NCR transaction processing to
+ /// the set of define events. It invokes the superclass's implementation
+ /// first to maintain the hierarchical chain of event definition.
+ /// Derivations of NameChangeTransaction must invoke its implementation
+ /// in like fashion.
+ ///
+ /// @throw StateModelError if an event definition is invalid or a duplicate.
+ virtual void defineEvents();
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// This method verifies that the events defined by both the superclass and
+ /// this class are defined. As with defineEvents, this method calls the
+ /// superclass's implementation first, to verify events defined by it and
+ /// then this implementation to verify events defined by
+ /// NameChangeTransaction.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyEvents();
+
+ /// @brief Adds states defined by NameChangeTransaction to the state set.
+ ///
+ /// This method adds the states common to NCR transaction processing to
+ /// the dictionary of states. It invokes the superclass's implementation
+ /// first to maintain the hierarchical chain of state definition.
+ /// Derivations of NameChangeTransaction must invoke its implementation
+ /// in like fashion.
+ ///
+ /// @throw StateModelError if an state definition is invalid or a duplicate.
+ virtual void defineStates();
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// This method verifies that the states defined by both the superclass and
+ /// this class are defined. As with defineStates, this method calls the
+ /// superclass's implementation first, to verify states defined by it and
+ /// then this implementation to verify states defined by
+ /// NameChangeTransaction.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyStates();
+
+ /// @brief Handler for fatal model execution errors.
+ ///
+ /// This handler is called by the StateModel implementation when the model
+ /// execution encounters a model violation: attempt to call an unmapped
+ /// state, an event not valid for the current state, or an uncaught
+ /// exception thrown during a state handler invocation. When such an
+ /// error occurs the transaction is deemed inoperable, and further model
+ /// execution cannot be performed. It marks the transaction as failed by
+ /// setting the NCR status to dhcp_ddns::ST_FAILED
+ ///
+ /// @param explanation is text detailing the error
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Determines the state and next event based on update attempts.
+ ///
+ /// This method will post a next event of SERVER_SELECTED_EVT to the
+ /// current state if the number of update attempts has not reached the
+ /// maximum allowed.
+ ///
+ /// If the maximum number of attempts has been reached, it will transition
+ /// to the given state with a next event of SERVER_IO_ERROR_EVT.
+ ///
+ /// @param fail_to_state State to transition to if maximum attempts
+ /// have been tried.
+ ///
+ void retryTransition(const int fail_to_state);
+
+ /// @brief Sets the update request packet to the given packet.
+ ///
+ /// @param request is the new request packet to assign.
+ void setDnsUpdateRequest(D2UpdateMessagePtr& request);
+
+ /// @brief Destroys the current update request packet.
+ void clearDnsUpdateRequest();
+
+ /// @brief Resets the update attempts count.
+ void clearUpdateAttempts();
+
+ /// @brief Sets the update status to the given status value.
+ ///
+ /// @param status is the new value for the update status.
+ void setDnsUpdateStatus(const DNSClient::Status& status);
+
+ /// @brief Sets the update response packet to the given packet.
+ ///
+ /// @param response is the new response packet to assign.
+ void setDnsUpdateResponse(D2UpdateMessagePtr& response);
+
+ /// @brief Destroys the current update response packet.
+ void clearDnsUpdateResponse();
+
+ /// @brief Sets the forward change completion flag to the given value.
+ ///
+ /// @param value is the new value to assign to the flag.
+ void setForwardChangeCompleted(const bool value);
+
+ /// @brief Sets the reverse change completion flag to the given value.
+ ///
+ /// @param value is the new value to assign to the flag.
+ void setReverseChangeCompleted(const bool value);
+
+ /// @brief Sets the status of the transaction's NameChangeRequest
+ ///
+ /// @param status is the new value to assign to the NCR status.
+ void setNcrStatus(const dhcp_ddns::NameChangeStatus& status);
+
+ /// @brief Initializes server selection from the given DDNS domain.
+ ///
+ /// Method prepares internal data to conduct server selection from the
+ /// list of servers supplied by the given domain. This method should be
+ /// called when a transaction is ready to begin selecting servers from
+ /// a new list. Typically this will be prior to starting the updates for
+ /// a given DNS direction.
+ ///
+ /// @param domain is the domain from which server selection is to be
+ /// conducted.
+ void initServerSelection(const DdnsDomainPtr& domain);
+
+ /// @brief Selects the next server in the current server list.
+ ///
+ /// This method is used to iterate over the list of servers. If there are
+ /// no more servers in the list, it returns false. Otherwise it sets the
+ /// current server to the next server and creates a new DNSClient
+ /// instance.
+ ///
+ /// @return True if a server has been selected, false if there are no more
+ /// servers from which to select.
+ bool selectNextServer();
+
+ /// @brief Selects the TSIG key.
+ ///
+ /// This method uses the current server and the select_key callout.
+ /// When no TSIG key is selected the tsig_key_ pointer is null.
+ ///
+ /// @return False when the current server should be skipped.
+ bool selectTSIGKey();
+
+ /// @brief Sets the update attempt count to the given value.
+ ///
+ /// @param value is the new value to assign.
+ void setUpdateAttempts(const size_t value);
+
+ /// @brief Fetches the IOService the transaction uses for IO processing.
+ ///
+ /// @return returns a const pointer to the IOService.
+ const asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Creates a new DNS update request based on the given domain.
+ ///
+ /// Constructs a new "empty", OUTBOUND, request with the message id set
+ /// and zone section populated based on the given domain.
+ /// It is declared virtual for test purposes.
+ ///
+ /// @return A D2UpdateMessagePtr to the new request.
+ ///
+ /// @throw NameChangeTransactionError if request cannot be constructed.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain);
+
+ /// @brief Adds an RData for the lease address to the given RRset.
+ ///
+ /// Creates an in::A() or in:AAAA() RData instance from the NCR
+ /// lease address and adds it to the given RRset.
+ ///
+ /// @param rrset RRset to which to add the RData
+ ///
+ /// @throw NameChangeTransactionError if RData cannot be constructed or
+ /// the RData cannot be added to the given RRset.
+ void addLeaseAddressRdata(dns::RRsetPtr& rrset);
+
+ /// @brief Adds an RData for the lease client's DHCID to the given RRset.
+ ///
+ /// Creates an in::DHCID() RData instance from the NCR DHCID and adds
+ /// it to the given RRset.
+ ///
+ /// @param rrset RRset to which to add the RData
+ ///
+ /// @throw NameChangeTransactionError if RData cannot be constructed or
+ /// the RData cannot be added to the given RRset.
+ void addDhcidRdata(dns::RRsetPtr& rrset);
+
+ /// @brief Adds an RData for the lease FQDN to the given RRset.
+ ///
+ /// Creates an in::PTR() RData instance from the NCR FQDN and adds
+ /// it to the given RRset.
+ ///
+ /// @param rrset RRset to which to add the RData
+ ///
+ /// @throw NameChangeTransactionError if RData cannot be constructed or
+ /// the RData cannot be added to the given RRset.
+ void addPtrRdata(dns::RRsetPtr& rrset);
+
+ /// @brief Returns a string version of the current response status and rcode
+ ///
+ /// Renders a string containing the text label of current DNS update status
+ /// and RCODE (if status is DNSClient::SUCCESS)
+ ///
+ /// @return std::string containing constructed text
+ std::string responseString() const;
+
+ /// @brief Returns a string version of transaction outcome.
+ ///
+ /// Renders a string containing summarizes the outcome of the
+ /// transaction. The information includes the overall status,
+ /// the last event, whether not forward and reverse changes were
+ /// done, as well as the NCR serviced.
+ ///
+ /// @return std::string containing constructed text
+ std::string transactionOutcomeString() const;
+
+public:
+ /// @brief Fetches the NameChangeRequest for this transaction.
+ ///
+ /// @return A const pointer reference to the NameChangeRequest.
+ const dhcp_ddns::NameChangeRequestPtr& getNcr() const;
+
+ /// @brief Fetches the unique key that identifies this transaction.
+ ///
+ /// Transactions are uniquely identified by a TransactionKey. Currently
+ /// this is wrapper around a D2Dhcid.
+ ///
+ /// @return A const reference to the TransactionKey.
+ const TransactionKey& getTransactionKey() const;
+
+ /// @brief Fetches the request id that identifies this transaction.
+ ///
+ /// This is a wrapper around getRequestId from the NCR which currently
+ /// returns DHCID. In the future we may include a distinct request id.
+ /// The primary purpose of this function is to provide a consistent way
+ /// to identify requests for logging purposes.
+ ///
+ /// @return a string with the request's request ID (currently DHCID)
+ std::string getRequestId() const;
+
+ /// @brief Fetches the NameChangeRequest status of the transaction.
+ ///
+ /// This is the current status of the NameChangeRequest, not to
+ /// be confused with the state of the transaction. Once the transaction
+ /// is reached its conclusion, the request will end up with a final
+ /// status.
+ ///
+ /// @return A dhcp_ddns::NameChangeStatus representing the current
+ /// status of the transaction.
+ dhcp_ddns::NameChangeStatus getNcrStatus() const;
+
+ /// @brief Fetches the forward DdnsDomain.
+ ///
+ /// @return A pointer reference to the forward DdnsDomain. If
+ /// the request does not include a forward change, the pointer will empty.
+ DdnsDomainPtr& getForwardDomain();
+
+ /// @brief Fetches the reverse DdnsDomain.
+ ///
+ /// @return A pointer reference to the reverse DdnsDomain. If
+ /// the request does not include a reverse change, the pointer will empty.
+ DdnsDomainPtr& getReverseDomain();
+
+ /// @brief Fetches the currently selected server.
+ ///
+ /// @return A const pointer reference to the DnsServerInfo of the current
+ /// server.
+ const DnsServerInfoPtr& getCurrentServer() const;
+
+ /// @brief Fetches the DNSClient instance
+ ///
+ /// @return A const pointer reference to the DNSClient
+ const DNSClientPtr& getDNSClient() const;
+
+ /// @brief Fetches the current DNS update request packet.
+ ///
+ /// @return A const pointer reference to the current D2UpdateMessage
+ /// request.
+ const D2UpdateMessagePtr& getDnsUpdateRequest() const;
+
+ /// @brief Fetches the most recent DNS update status.
+ ///
+ /// @return A DNSClient::Status indicating the result of the most recent
+ /// DNS update to complete.
+ DNSClient::Status getDnsUpdateStatus() const;
+
+ /// @brief Fetches the most recent DNS update response packet.
+ ///
+ /// @return A const pointer reference to the D2UpdateMessage most recently
+ /// received.
+ const D2UpdateMessagePtr& getDnsUpdateResponse() const;
+
+ /// @brief Returns whether the forward change has completed or not.
+ ///
+ /// The value returned is only meaningful if the NameChangeRequest calls
+ /// for a forward change to be done. The value returned indicates if
+ /// forward change has been completed successfully.
+ ///
+ /// @return True if the forward change has been completed, false otherwise.
+ bool getForwardChangeCompleted() const;
+
+ /// @brief Returns whether the reverse change has completed or not.
+ ///
+ /// The value returned is only meaningful if the NameChangeRequest calls
+ /// for a reverse change to be done. The value returned indicates if
+ /// reverse change has been completed successfully.
+ ///
+ /// @return True if the reverse change has been completed, false otherwise.
+ bool getReverseChangeCompleted() const;
+
+ /// @brief Fetches the update attempt count for the current update.
+ ///
+ /// @return size_t which is the number of times the current request has
+ /// been attempted against the current server.
+ size_t getUpdateAttempts() const;
+
+ /// @brief Returns the DHCP data type for the lease address
+ ///
+ /// @return constant reference to dns::RRType::A() if the lease address
+ /// is IPv4 or dns::RRType::AAAA() if the lease address is IPv6.
+ const dns::RRType& getAddressRRType() const;
+
+private:
+ /// @brief The IOService which should be used to for IO processing.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief The NameChangeRequest that the transaction is to fulfill.
+ dhcp_ddns::NameChangeRequestPtr ncr_;
+
+ /// @brief The forward domain that matches the request.
+ ///
+ /// The forward "domain" is DdnsDomain which contains all of the information
+ /// necessary, including the list of DNS servers to be used for a forward
+ /// change.
+ DdnsDomainPtr forward_domain_;
+
+ /// @brief The reverse domain that matches the request.
+ ///
+ /// The reverse "domain" is DdnsDomain which contains all of the information
+ /// necessary, including the list of DNS servers to be used for a reverse
+ /// change.
+ DdnsDomainPtr reverse_domain_;
+
+ /// @brief The DNSClient instance that will carry out DNS packet exchanges.
+ DNSClientPtr dns_client_;
+
+ /// @brief The DNS current update request packet.
+ D2UpdateMessagePtr dns_update_request_;
+
+ /// @brief The outcome of the most recently completed DNS packet exchange.
+ DNSClient::Status dns_update_status_;
+
+ /// @brief The DNS update response packet most recently received.
+ D2UpdateMessagePtr dns_update_response_;
+
+ /// @brief Indicator for whether or not the forward change completed ok.
+ bool forward_change_completed_;
+
+ /// @brief Indicator for whether or not the reverse change completed ok.
+ bool reverse_change_completed_;
+
+ /// @brief Pointer to the current server selection list.
+ DnsServerInfoStoragePtr current_server_list_;
+
+ /// @brief Pointer to the currently selected server.
+ DnsServerInfoPtr current_server_;
+
+ /// @brief Next server position in the list.
+ ///
+ /// This value is always the position of the next selection in the server
+ /// list, which may be beyond the end of the list.
+ size_t next_server_pos_;
+
+ /// @brief Number of transmit attempts for the current request.
+ size_t update_attempts_;
+
+ /// @brief Pointer to the configuration manager.
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief Pointer to the TSIG key which should be used (if any).
+ D2TsigKeyPtr tsig_key_;
+};
+
+/// @brief Defines a pointer to a NameChangeTransaction.
+typedef boost::shared_ptr<NameChangeTransaction> NameChangeTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/lib/d2srv/tests/Makefile.am b/src/lib/d2srv/tests/Makefile.am
new file mode 100644
index 0000000..2311b8f
--- /dev/null
+++ b/src/lib/d2srv/tests/Makefile.am
@@ -0,0 +1,52 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+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 += libd2srv_unittests
+
+libd2srv_unittests_SOURCES = run_unittests.cc
+libd2srv_unittests_SOURCES += d2_tsig_key_unittest.cc
+libd2srv_unittests_SOURCES += d2_update_message_unittests.cc
+libd2srv_unittests_SOURCES += d2_zone_unittests.cc
+libd2srv_unittests_SOURCES += dns_client_unittests.cc
+libd2srv_unittests_SOURCES += nc_trans_unittests.cc
+
+libd2srv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libd2srv_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libd2srv_unittests_LDADD = $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/d2srv/libkea-d2srv.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libd2srv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libd2srv_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/d2srv/tests/Makefile.in b/src/lib/d2srv/tests/Makefile.in
new file mode 100644
index 0000000..768d3ac
--- /dev/null
+++ b/src/lib/d2srv/tests/Makefile.in
@@ -0,0 +1,1110 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libd2srv_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/d2srv/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libd2srv_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libd2srv_unittests_SOURCES_DIST = run_unittests.cc \
+ d2_tsig_key_unittest.cc d2_update_message_unittests.cc \
+ d2_zone_unittests.cc dns_client_unittests.cc \
+ nc_trans_unittests.cc
+@HAVE_GTEST_TRUE@am_libd2srv_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-d2_tsig_key_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-d2_update_message_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-d2_zone_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-dns_client_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libd2srv_unittests-nc_trans_unittests.$(OBJEXT)
+libd2srv_unittests_OBJECTS = $(am_libd2srv_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libd2srv_unittests_DEPENDENCIES = $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(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 =
+libd2srv_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libd2srv_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)/libd2srv_unittests-d2_tsig_key_unittest.Po \
+ ./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po \
+ ./$(DEPDIR)/libd2srv_unittests-run_unittests.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 =
+SOURCES = $(libd2srv_unittests_SOURCES)
+DIST_SOURCES = $(am__libd2srv_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libd2srv_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_tsig_key_unittest.cc \
+@HAVE_GTEST_TRUE@ d2_update_message_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_zone_unittests.cc dns_client_unittests.cc \
+@HAVE_GTEST_TRUE@ nc_trans_unittests.cc
+@HAVE_GTEST_TRUE@libd2srv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libd2srv_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libd2srv_unittests_LDADD = $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(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/d2srv/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/d2srv/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
+
+libd2srv_unittests$(EXEEXT): $(libd2srv_unittests_OBJECTS) $(libd2srv_unittests_DEPENDENCIES) $(EXTRA_libd2srv_unittests_DEPENDENCIES)
+ @rm -f libd2srv_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libd2srv_unittests_LINK) $(libd2srv_unittests_OBJECTS) $(libd2srv_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srv_unittests-run_unittests.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 $@ $<
+
+libd2srv_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-run_unittests.Tpo -c -o libd2srv_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-run_unittests.Tpo $(DEPDIR)/libd2srv_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libd2srv_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libd2srv_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-run_unittests.Tpo -c -o libd2srv_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)/libd2srv_unittests-run_unittests.Tpo $(DEPDIR)/libd2srv_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libd2srv_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libd2srv_unittests-d2_tsig_key_unittest.o: d2_tsig_key_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_tsig_key_unittest.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo -c -o libd2srv_unittests-d2_tsig_key_unittest.o `test -f 'd2_tsig_key_unittest.cc' || echo '$(srcdir)/'`d2_tsig_key_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_tsig_key_unittest.cc' object='libd2srv_unittests-d2_tsig_key_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_tsig_key_unittest.o `test -f 'd2_tsig_key_unittest.cc' || echo '$(srcdir)/'`d2_tsig_key_unittest.cc
+
+libd2srv_unittests-d2_tsig_key_unittest.obj: d2_tsig_key_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_tsig_key_unittest.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo -c -o libd2srv_unittests-d2_tsig_key_unittest.obj `if test -f 'd2_tsig_key_unittest.cc'; then $(CYGPATH_W) 'd2_tsig_key_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_tsig_key_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Tpo $(DEPDIR)/libd2srv_unittests-d2_tsig_key_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_tsig_key_unittest.cc' object='libd2srv_unittests-d2_tsig_key_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_tsig_key_unittest.obj `if test -f 'd2_tsig_key_unittest.cc'; then $(CYGPATH_W) 'd2_tsig_key_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_tsig_key_unittest.cc'; fi`
+
+libd2srv_unittests-d2_update_message_unittests.o: d2_update_message_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_update_message_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo -c -o libd2srv_unittests-d2_update_message_unittests.o `test -f 'd2_update_message_unittests.cc' || echo '$(srcdir)/'`d2_update_message_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_message_unittests.cc' object='libd2srv_unittests-d2_update_message_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_update_message_unittests.o `test -f 'd2_update_message_unittests.cc' || echo '$(srcdir)/'`d2_update_message_unittests.cc
+
+libd2srv_unittests-d2_update_message_unittests.obj: d2_update_message_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_update_message_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo -c -o libd2srv_unittests-d2_update_message_unittests.obj `if test -f 'd2_update_message_unittests.cc'; then $(CYGPATH_W) 'd2_update_message_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_message_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_message_unittests.cc' object='libd2srv_unittests-d2_update_message_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_update_message_unittests.obj `if test -f 'd2_update_message_unittests.cc'; then $(CYGPATH_W) 'd2_update_message_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_message_unittests.cc'; fi`
+
+libd2srv_unittests-d2_zone_unittests.o: d2_zone_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_zone_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo -c -o libd2srv_unittests-d2_zone_unittests.o `test -f 'd2_zone_unittests.cc' || echo '$(srcdir)/'`d2_zone_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_zone_unittests.cc' object='libd2srv_unittests-d2_zone_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_zone_unittests.o `test -f 'd2_zone_unittests.cc' || echo '$(srcdir)/'`d2_zone_unittests.cc
+
+libd2srv_unittests-d2_zone_unittests.obj: d2_zone_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-d2_zone_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo -c -o libd2srv_unittests-d2_zone_unittests.obj `if test -f 'd2_zone_unittests.cc'; then $(CYGPATH_W) 'd2_zone_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_zone_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Tpo $(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_zone_unittests.cc' object='libd2srv_unittests-d2_zone_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-d2_zone_unittests.obj `if test -f 'd2_zone_unittests.cc'; then $(CYGPATH_W) 'd2_zone_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_zone_unittests.cc'; fi`
+
+libd2srv_unittests-dns_client_unittests.o: dns_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-dns_client_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo -c -o libd2srv_unittests-dns_client_unittests.o `test -f 'dns_client_unittests.cc' || echo '$(srcdir)/'`dns_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_client_unittests.cc' object='libd2srv_unittests-dns_client_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-dns_client_unittests.o `test -f 'dns_client_unittests.cc' || echo '$(srcdir)/'`dns_client_unittests.cc
+
+libd2srv_unittests-dns_client_unittests.obj: dns_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-dns_client_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo -c -o libd2srv_unittests-dns_client_unittests.obj `if test -f 'dns_client_unittests.cc'; then $(CYGPATH_W) 'dns_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dns_client_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Tpo $(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_client_unittests.cc' object='libd2srv_unittests-dns_client_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-dns_client_unittests.obj `if test -f 'dns_client_unittests.cc'; then $(CYGPATH_W) 'dns_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/dns_client_unittests.cc'; fi`
+
+libd2srv_unittests-nc_trans_unittests.o: nc_trans_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-nc_trans_unittests.o -MD -MP -MF $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo -c -o libd2srv_unittests-nc_trans_unittests.o `test -f 'nc_trans_unittests.cc' || echo '$(srcdir)/'`nc_trans_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_trans_unittests.cc' object='libd2srv_unittests-nc_trans_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-nc_trans_unittests.o `test -f 'nc_trans_unittests.cc' || echo '$(srcdir)/'`nc_trans_unittests.cc
+
+libd2srv_unittests-nc_trans_unittests.obj: nc_trans_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libd2srv_unittests-nc_trans_unittests.obj -MD -MP -MF $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo -c -o libd2srv_unittests-nc_trans_unittests.obj `if test -f 'nc_trans_unittests.cc'; then $(CYGPATH_W) 'nc_trans_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_trans_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Tpo $(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_trans_unittests.cc' object='libd2srv_unittests-nc_trans_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) $(libd2srv_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libd2srv_unittests-nc_trans_unittests.obj `if test -f 'nc_trans_unittests.cc'; then $(CYGPATH_W) 'nc_trans_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_trans_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)/libd2srv_unittests-d2_tsig_key_unittest.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-run_unittests.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)/libd2srv_unittests-d2_tsig_key_unittest.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_update_message_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-d2_zone_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-dns_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-nc_trans_unittests.Po
+ -rm -f ./$(DEPDIR)/libd2srv_unittests-run_unittests.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/d2srv/tests/d2_tsig_key_unittest.cc b/src/lib/d2srv/tests/d2_tsig_key_unittest.cc
new file mode 100644
index 0000000..2bc6171
--- /dev/null
+++ b/src/lib/d2srv/tests/d2_tsig_key_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright (C) 2021 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 <cc/data.h>
+#include <d2srv/d2_stats.h>
+#include <d2srv/d2_tsig_key.h>
+#include <stats/stats_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::stats;
+using namespace std;
+
+namespace {
+
+/// @brief Check statistics names.
+TEST(D2StatsTest, names) {
+ ASSERT_EQ(3, D2Stats::ncr.size());
+ ASSERT_EQ(6, D2Stats::update.size());
+ ASSERT_EQ(4, D2Stats::key.size());
+}
+
+/// @brief Fixture class for TSIG key / DNS update statistics.
+class D2TsigKeyTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ D2TsigKeyTest() {
+ StatsMgr::instance();
+ StatsMgr::instance().removeAll();
+ StatsMgr::instance().setMaxSampleCountDefault(0);
+ }
+
+ /// @brief Destructor.
+ ~D2TsigKeyTest() {
+ StatsMgr::instance().removeAll();
+ StatsMgr::instance().setMaxSampleCountDefault(0);
+ }
+};
+
+/// @brief Check TSIG key life.
+TEST_F(D2TsigKeyTest, key) {
+ // Get the statistics manager.
+ StatsMgr& stat_mgr = StatsMgr::instance();
+ ASSERT_EQ(0, stat_mgr.count());
+
+ // Create a key.
+ const string& key_spec = "foo.bar.::test";
+ D2TsigKeyPtr key(new D2TsigKey(key_spec));
+ EXPECT_EQ(4, stat_mgr.count());
+
+ // Create a context.
+ TSIGContextPtr ctx;
+ ASSERT_NO_THROW(ctx = key->createContext());
+ ASSERT_TRUE(ctx);
+ EXPECT_EQ(TSIGContext::INIT, ctx->getState());
+
+ // Get the 'sent' statistics.
+ const string& stat_name = "key[foo.bar.].update-sent";
+ EXPECT_EQ(1, stat_mgr.getSize(stat_name));
+ ObservationPtr stat = stat_mgr.getObservation(stat_name);
+ ASSERT_TRUE(stat);
+ IntegerSample sample;
+ ASSERT_NO_THROW(sample = stat->getInteger());
+ EXPECT_EQ(0, sample.first);
+
+ // Increment the 'sent' statistics.
+ stat_mgr.addValue(stat_name, static_cast<int64_t>(1));
+ stat = stat_mgr.getObservation(stat_name);
+ ASSERT_TRUE(stat);
+ ASSERT_NO_THROW(sample = stat->getInteger());
+ EXPECT_EQ(1, sample.first);
+
+ // Reset the key statistics.
+ ASSERT_NO_THROW(key->resetStats());
+ stat = stat_mgr.getObservation(stat_name);
+ ASSERT_TRUE(stat);
+ ASSERT_NO_THROW(sample = stat->getInteger());
+ EXPECT_EQ(0, sample.first);
+
+ // Destroy the key: its stats are removed.
+ key.reset();
+ EXPECT_EQ(0, stat_mgr.count());
+ stat = stat_mgr.getObservation(stat_name);
+ EXPECT_FALSE(stat);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/d2srv/tests/d2_update_message_unittests.cc b/src/lib/d2srv/tests/d2_update_message_unittests.cc
new file mode 100644
index 0000000..6f0cfca
--- /dev/null
+++ b/src/lib/d2srv/tests/d2_update_message_unittests.cc
@@ -0,0 +1,696 @@
+// Copyright (C) 2013-2021 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 <d2srv/d2_config.h>
+#include <d2srv/d2_update_message.h>
+#include <d2srv/d2_zone.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrttl.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+namespace {
+ /// @brief Test fixture class for testing D2UpdateMessage object
+class D2UpdateMessageTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ //
+ // Does nothing.
+ D2UpdateMessageTest() { }
+
+ /// @brief Destructor
+ //
+ // Does nothing.
+ ~D2UpdateMessageTest() { };
+
+ /// @brief Returns string representation of the name encoded in wire format.
+ //
+ // This function reads the number of bytes specified in the second
+ // argument from the buffer. It doesn't check if buffer has sufficient
+ // length for reading given number of bytes. Caller should verify it
+ // prior to calling this function.
+ //
+ // @param buf input buffer, its internal pointer will be moved to
+ // the position after a name being read from it.
+ // @param name_length length of the name stored in the buffer
+ // @param no_zero_byte if true it indicates that the given buffer does not
+ // comprise the zero byte, which signals end of the name. This is
+ // the case, when dealing with compressed messages which don't have
+ // this byte.
+ //
+ // @return string representation of the name.
+ std::string readNameFromWire(InputBuffer& buf, size_t name_length,
+ const bool no_zero_byte = false) {
+ std::vector<uint8_t> name_data;
+ // Create another InputBuffer which holds only the name in the wire
+ // format.
+ buf.readVector(name_data, name_length);
+ if (no_zero_byte) {
+ ++name_length;
+ name_data.push_back(0);
+ }
+ InputBuffer name_buf(&name_data[0], name_length);
+ // Parse the name and return its textual representation.
+ Name name(name_buf);
+ return (name.toText());
+ }
+};
+
+// This test verifies that DNS Update message ID can be set using
+// setId function.
+TEST_F(D2UpdateMessageTest, setId) {
+ // Message ID is initialized to 0.
+ D2UpdateMessage msg;
+ EXPECT_EQ(0, msg.getId());
+ // Override the default value and verify that it has been set.
+ msg.setId(0x1234);
+ EXPECT_EQ(0x1234, msg.getId());
+}
+
+// This test verifies that the DNS Update message RCODE can be set
+// using setRcode function.
+TEST_F(D2UpdateMessageTest, setRcode) {
+ D2UpdateMessage msg;
+ // Rcode must be explicitly set before it is accessed.
+ msg.setRcode(Rcode::NOERROR());
+ EXPECT_EQ(Rcode::NOERROR().getCode(), msg.getRcode().getCode());
+ // Let's override current value to make sure that getter does
+ // not return fixed value.
+ msg.setRcode(Rcode::NOTIMP());
+ EXPECT_EQ(Rcode::NOTIMP().getCode(), msg.getRcode().getCode());
+}
+
+// This test verifies that the Zone section in the DNS Update message
+// can be set.
+TEST_F(D2UpdateMessageTest, setZone) {
+ D2UpdateMessage msg;
+ // The zone pointer is initialized to NULL.
+ D2ZonePtr zone = msg.getZone();
+ EXPECT_FALSE(zone);
+ // Let's create a new Zone and check that it is returned
+ // via getter.
+ msg.setZone(Name("example.com"), RRClass::ANY());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone->getClass().getCode());
+
+ // Now, let's check that the existing Zone object can be
+ // overridden with a new one.
+ msg.setZone(Name("foo.example.com"), RRClass::NONE());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("foo.example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::NONE().getCode(), zone->getClass().getCode());
+}
+
+// This test verifies that the DNS message is properly decoded from the
+// wire format.
+TEST_F(D2UpdateMessageTest, fromWire) {
+ // The following table holds the DNS response in on-wire format.
+ // This message comprises the following sections:
+ // - HEADER
+ // - PREREQUISITE section with one RR
+ // - UPDATE section with 1 RR.
+ // Such a response may be generated by the DNS server as a result
+ // of copying the contents of the REQUEST message sent by DDNS client.
+ const uint8_t bin_msg[] = {
+ // HEADER section starts here (see RFC 2136, section 2).
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x1, // ZOCOUNT=1
+ 0x0, 0x2, // PRCOUNT=2
+ 0x0, 0x1, // UPCOUNT=1
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Zone section starts here. The The first field comprises
+ // the Zone name encoded as a set of labels, each preceded
+ // by a length of the following label. The whole Zone name is
+ // terminated with a NULL char.
+ // For Zone section format see (RFC 2136, section 2.3).
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Prerequisite section starts here. This section comprises two
+ // prerequisites:
+ // - 'Name is not in use'
+ // - 'Name is in use'
+ // See RFC 2136, section 2.4 for the format of Prerequisite section.
+ // Each prerequisite RR starts with its name. It is expressed in the
+ // compressed format as described in RFC 1035, section 4.1.4. The first
+ // label is expressed as in case of non-compressed name. It is preceded
+ // by the length value. The following two bytes are the pointer to the
+ // offset in the message where 'example.com' was used. That is, in the
+ // Zone name at offset 12. Pointer starts with two bits set - they
+ // mark start of the pointer.
+
+ // First prerequisite. NONE class indicates that the update requires
+ // that the name 'foo.example.com' is not use/
+ 0x03, 0x66, 0x6F, 0x6F, // foo.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFE, // CLASS=NONE
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Second prerequisite. ANY class indicates tha the update requires
+ // that the name 'bar.example.com' exists.
+ 0x03, 0x62, 0x61, 0x72, // bar.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFF, // CLASS=ANY
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Update section starts here. The format of this section conforms to
+ // RFC 2136, section 2.5. The name of the RR is again expressed in
+ // compressed format. The two pointer bytes point to the offset in the
+ // message where 'foo.example.com' was used already - 29.
+ 0xC0, 0x1D, // pointer to foo.example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0x1, // CLASS=IN
+ 0xAA, 0xBB, 0xCC, 0xDD, // TTL=0xAABBCCDD
+ 0x0, 0x10, // RDLENGTH=16
+ // The following 16 bytes of RDATA hold IPv6 address: 2001:db8:1::1.
+ 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ };
+
+ // Create an object to be used to decode the message from the wire format.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+
+ // Decode the message.
+ ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
+
+ // Check that the message header is valid.
+ EXPECT_EQ(0x05AF, msg.getId());
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+ EXPECT_EQ(Rcode::YXDOMAIN_CODE, msg.getRcode().getCode());
+
+ // The ZOCOUNT must contain exactly one zone. If it does, we should get
+ // the name, class and type of the zone and verify they are valid.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = msg.getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ // Check the Prerequisite section. It should contain two records.
+ ASSERT_EQ(2, msg.getRRCount(D2UpdateMessage::SECTION_PREREQUISITE));
+
+ // Proceed to the first prerequisite.
+ RRsetIterator rrset_it =
+ msg.beginSection(D2UpdateMessage::SECTION_PREREQUISITE);
+ RRsetPtr prereq1 = *rrset_it;
+ ASSERT_TRUE(prereq1);
+ // Check record fields.
+ EXPECT_EQ("foo.example.com.", prereq1->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq1->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::NONE().getCode(),
+ prereq1->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq1->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq1->getRdataCount()); // RDLENGTH
+
+ // Move to next prerequisite section.
+ ++rrset_it;
+ RRsetPtr prereq2 = *rrset_it;
+ ASSERT_TRUE(prereq2);
+ // Check record fields.
+ EXPECT_EQ("bar.example.com.", prereq2->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq2->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::ANY().getCode(), prereq2->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq2->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq2->getRdataCount()); // RDLENGTH
+
+ // Check the Update section. There is only one record, so beginSection()
+ // should return the pointer to this sole record.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_UPDATE));
+ rrset_it = msg.beginSection(D2UpdateMessage::SECTION_UPDATE);
+ RRsetPtr update = *rrset_it;
+ ASSERT_TRUE(update);
+ // Check the record fields.
+ EXPECT_EQ("foo.example.com.", update->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), update->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::IN().getCode(), update->getClass().getCode()); // CLASS
+ EXPECT_EQ(0xAABBCCDD, update->getTTL().getValue()); // TTL
+ // There should be exactly one record holding the IPv6 address.
+ // This record can be accessed using RdataIterator. This record
+ // can be compared with the reference record, holding expected IPv6
+ // address using compare function.
+ ASSERT_EQ(1, update->getRdataCount());
+ RdataIteratorPtr rdata_it = update->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+ in::AAAA rdata_ref("2001:db8:1::1");
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+
+ // @todo: at this point we don't test Additional Data records. We may
+ // consider implementing tests for it in the future.
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid Opcode (is not a DNS Update).
+TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid Opcode=3, expected value is 6
+ // (Update).
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x98, 0x6, // QR=1, Opcode=3, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid Opcode, the fromWire function should
+ // throw NotUpdateMessage exception.
+ EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+ isc::d2::NotUpdateMessage);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid QR flag. The QR bit is
+// expected to be set to indicate that the message is a RESPONSE.
+TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid QR flag = 0.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x28, 0x6, // QR=0, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid QR flag, the fromWire function should
+ // throw InvalidQRFlag exception.
+ EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+ isc::d2::InvalidQRFlag);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises more than one (two in this case)
+// Zone records.
+TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
+ // This is a binary representation of the DNS message. This message
+ // comprises two Zone records.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x2, // ZOCOUNT=2
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Start first Zone record.
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Start second Zone record. Presence of this record should result
+ // in error when parsing this message.
+ 0x3, 0x63, 0x6F, 0x6D, // com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1 // ZCLASS='IN'
+ };
+
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When parsing a message with more than one Zone record,
+ // exception should be thrown.
+ EXPECT_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)),
+ isc::d2::InvalidZoneSection);
+}
+
+// This test verifies that the wire format of the message is produced
+// in the render mode.
+TEST_F(D2UpdateMessageTest, toWire) {
+ D2UpdateMessage msg;
+ // Set message ID.
+ msg.setId(0x1234);
+ // Rcode to NOERROR.
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+
+ // Set Zone section. This section must comprise exactly
+ // one Zone. toWire function would fail if Zone is not set.
+ msg.setZone(Name("example.com"), RRClass::IN());
+
+ // Set prerequisites.
+
+ // 'Name Is Not In Use' prerequisite (RFC 2136, section 2.4.5)
+ RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+
+ // 'Name is In Use' prerequisite (RFC 2136, section 2.4.4)
+ RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+
+ // Set Update Section.
+
+ // Create RR holding a name being added. This RR is constructed
+ // in conformance to RFC 2136, section 2.5.1.
+ RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(10)));
+ // RR record is of the type A, thus RDATA holds 4 octet Internet
+ // address. This address is 10.10.1.1.
+ char rdata1[] = {
+ 0xA, 0xA , 0x1, 0x1
+ };
+ InputBuffer buf_rdata1(rdata1, 4);
+ updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+ buf_rdata1.getLength()));
+ // Add the RR to the message.
+ msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+ // Render message into the wire format.
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer));
+
+ // Make sure that created packet is not truncated.
+ ASSERT_EQ(77, renderer.getLength());
+
+ // Create input buffer from the rendered data. InputBuffer
+ // is handy to validate the byte contents of the rendered
+ // message.
+ InputBuffer buf(renderer.getData(), renderer.getLength());
+
+ // Start validating the message header.
+
+ // Verify message ID.
+ EXPECT_EQ(0x1234, buf.readUint16());
+ // The 2-bytes following message ID comprise the following fields:
+ // - QR - 1 bit indicating that it is REQUEST. Should be 0.
+ // - Opcode - 4 bits which should hold value of 5 indicating this is
+ // an Update message. Binary form is "0101".
+ // - Z - These bits are unused for Update Message and should be 0.
+ // - RCODE - Response code, set to NOERROR for REQUEST. It is 0.
+ //8706391835
+ // The binary value is:
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | QR| Opcode | Z | RCODE |
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | 0 | 0 1 0 1 | 0 0 0 0 0 0 0 | 0 0 0 0 |
+ // +---+---+---+-------+---+---+---+---+---+---+---+---+---+---+---+
+ // and the hexadecimal representation is 0x2800.
+ EXPECT_EQ(0x2800, buf.readUint16());
+
+ // ZOCOUNT - holds the number of zones for the update. For Request
+ // message it must be exactly one record (RFC2136, section 2.3).
+ EXPECT_EQ(1, buf.readUint16());
+
+ // PRCOUNT - holds the number of prerequisites. Earlier we have added
+ // two prerequisites. Thus, expect that this counter is 2.
+ EXPECT_EQ(2, buf.readUint16());
+
+ // UPCOUNT - holds the number of RRs in the Update Section. We have
+ // added 1 RR, which adds the name foo.example.com to the Zone.
+ EXPECT_EQ(1, buf.readUint16());
+
+ // ADCOUNT - holds the number of RRs in the Additional Data Section.
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start validating the Zone section. This section comprises the
+ // following data:
+ // - ZNAME
+ // - ZTYPE
+ // - ZCLASS
+
+ // ZNAME holds 'example.com.' encoded as set of labels. Each label
+ // is preceded by its length. The name is ended with the byte holding
+ // zero value. This yields the total size of the name in wire format
+ // of 13 bytes.
+
+ // The simplest way to convert the name from wire format to a string
+ // is to use dns::Name class. It should be ok to rely on the Name class
+ // to decode the name, because it is unit tested elsewhere.
+ std::string zone_name = readNameFromWire(buf, 13);
+ EXPECT_EQ("example.com.", zone_name);
+
+ // ZTYPE of the Zone section must be SOA according to RFC 2136,
+ // section 2.3.
+ EXPECT_EQ(RRType::SOA().getCode(), buf.readUint16());
+
+ // ZCLASS of the Zone section is IN.
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+
+ // Start checks on Prerequisite section. Each prerequisite comprises
+ // the following fields:
+ // - NAME - name of the RR in wire format
+ // - TYPE - two octets with one of the RR TYPE codes
+ // - CLASS - two octets with one of the RR CLASS codes
+ // - TTL - a 32-bit signed integer specifying Time-To-Live
+ // - RDLENGTH - length of the RDATA field
+ // - RDATA - a variable length string of octets containing
+ // resource data.
+ // In case of this message, we expect to have two prerequisite RRs.
+ // Their structure is checked below.
+
+ // First prerequisite should comprise the 'Name is not in use prerequisite'
+ // for 'foo.example.com'.
+
+ // Check the name first. Message renderer is using compression for domain
+ // names as described in RFC 1035, section 4.1.4. The name in this RR is
+ // foo.example.com. The name of the zone is example.com and it has occurred
+ // in this message already at offset 12 (the size of the header is 12).
+ // Therefore, name of this RR is encoded as 'foo', followed by a pointer
+ // to offset in this message where the remainder of this name was used.
+ // This pointer has the following format:
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| OFFSET |
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| 0 0 0 0 0 0 0 0 0 0 1 1 0 0|
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // which has a following hexadecimal representation: 0xC00C
+
+ // Let's read the non-compressed part first - 'foo.'
+ std::string name_prereq1 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("foo.", name_prereq1);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is NONE
+ EXPECT_EQ(RRClass::NONE().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking second prerequisite.
+
+ std::string name_prereq2 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("bar.", name_prereq2);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is ANY
+ EXPECT_EQ(RRClass::ANY().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking Update section. This section contains RRset with
+ // one A RR.
+
+ // The name of the RR is 'foo.example.com'. It is encoded in the
+ // compressed format - as a pointer to the name of prerequisite 1.
+ // This name is in offset 0x1D in this message.
+ EXPECT_EQ(0xC01D, buf.readUint16());
+ // TYPE is A
+ EXPECT_EQ(RRType::A().getCode(), buf.readUint16());
+ // CLASS is IN (same as zone class)
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+ // TTL is a 32-but value, set here to 10.
+ EXPECT_EQ(10, buf.readUint32());
+ // For A records, the RDATA comprises the 4-byte Internet address.
+ // So, RDLENGTH is 4.
+ EXPECT_EQ(4, buf.readUint16());
+ // We have stored the following address in RDATA field: 10.10.1.1
+ // (which is 0A 0A 01 01) in hexadecimal format.
+ EXPECT_EQ(0x0A0A0101, buf.readUint32());
+
+ // @todo: consider extending this test to verify Additional Data
+ // section.
+}
+
+// This test verifies that an attempt to call toWire function on the
+// received message will result in an exception.
+TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // This message is valid and should be parsed with no
+ // error.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary message data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ ASSERT_NO_THROW(msg.fromWire(bin_msg, sizeof(bin_msg)));
+
+ // The message is parsed. The QR Flag should now indicate that
+ // it is a Response message.
+ ASSERT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+
+ // An attempt to call toWire on the Response message should
+ // result in the InvalidQRFlag exception.
+ MessageRenderer renderer;
+ EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag);
+}
+
+// TSIG test
+TEST_F(D2UpdateMessageTest, validTSIG) {
+ // Create a TSIG Key and context
+ std::string secret("this key will match");
+ D2TsigKeyPtr right_key;
+ ASSERT_NO_THROW(right_key.reset(new
+ D2TsigKey(Name("right.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+
+ D2TsigKeyPtr wrong_key;
+ secret = "this key will not match";
+ ASSERT_NO_THROW(wrong_key.reset(new
+ D2TsigKey(Name("wrong.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+
+
+ // Build a request message
+ D2UpdateMessage msg;
+ msg.setId(0x1234);
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+ msg.setZone(Name("example.com"), RRClass::IN());
+ RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+ RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+ RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(10)));
+ char rdata1[] = {
+ 0xA, 0xA , 0x1, 0x1
+ };
+ InputBuffer buf_rdata1(rdata1, 4);
+ updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+ buf_rdata1.getLength()));
+ msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+ // Make a context to send the message with and use it to render
+ // the message into the wire format.
+ TSIGContextPtr context;
+ ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer, context.get()));
+
+ // Grab the wire data from the signed message.
+ const void* wire_data = renderer.getData();
+ const size_t wire_size = renderer.getLength();
+
+ // Make a context with the wrong key and use it to convert the wired data.
+ // Verification should fail.
+ D2UpdateMessage msg2(D2UpdateMessage::INBOUND);
+ ASSERT_NO_THROW(context.reset(new TSIGContext(*wrong_key)));
+ ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+ TSIGVerifyError);
+
+ // Now make a context with the correct key and try again.
+ // If the message passes TSIG verification, then the QR Flag test in
+ // the subsequent call to D2UpdateMessage::validateResponse should
+ // fail because this isn't really received message.
+ ASSERT_NO_THROW(context.reset(new TSIGContext(*right_key)));
+ ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+ InvalidQRFlag);
+}
+
+// Tests message signing and verification for all supported algorithms.
+TEST_F(D2UpdateMessageTest, allValidTSIG) {
+ std::vector<std::string>algorithms;
+ algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR);
+
+ dns::Name key_name("test_key");
+ std::string secret("random text for secret");
+ for (int i = 0; i < algorithms.size(); ++i) {
+ D2TsigKey key(key_name,
+ TSIGKeyInfo::stringToAlgorithmName(algorithms[i]),
+ secret.c_str(), secret.size());
+
+ // Build a request message
+ D2UpdateMessage msg;
+ msg.setId(0x1234);
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+ msg.setZone(Name("example.com"), RRClass::IN());
+
+ // Make a context to send the message with and use it to render
+ // the message into the wire format.
+ TSIGContextPtr context;
+ ASSERT_NO_THROW(context.reset(new TSIGContext(key)));
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer, context.get()));
+
+ // Grab the wire data from the signed message.
+ const void* wire_data = renderer.getData();
+ const size_t wire_size = renderer.getLength();
+
+ // Create a fresh context to "receive" the message. (We can't use the
+ // one we signed it with, as its expecting a signed response to its
+ // request. Here we are acting like the server).
+ // If the message passes TSIG verification, then the QR Flag test in
+ // the subsequent call to D2UpdateMessage::validateResponse should
+ // fail because this isn't really received message.
+ ASSERT_NO_THROW(context.reset(new TSIGContext(key)));
+ D2UpdateMessage msg2(D2UpdateMessage::INBOUND);
+ ASSERT_THROW(msg2.fromWire(wire_data, wire_size, context.get()),
+ InvalidQRFlag);
+ }
+}
+
+
+} // End of anonymous namespace
diff --git a/src/lib/d2srv/tests/d2_zone_unittests.cc b/src/lib/d2srv/tests/d2_zone_unittests.cc
new file mode 100644
index 0000000..f252d07
--- /dev/null
+++ b/src/lib/d2srv/tests/d2_zone_unittests.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2013-2021 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 <d2srv/d2_zone.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+
+namespace {
+
+// This test verifies that Zone object is created and its constructor sets
+// appropriate values for its members.
+TEST(D2ZoneTest, constructor) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com.", zone1.getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone1.getClass().getCode());
+ // Create another object to make sure that constructor doesn't assign
+ // fixed values, but they change when constructor's parameters change.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com.", zone2.getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone2.getClass().getCode());
+}
+
+// This test verifies that toText() function returns text representation of
+// of the zone in expected format.
+TEST(D2ZoneTest, toText) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com. ANY SOA\n", zone1.toText());
+ // Create another object with different parameters to make sure that the
+ // function's output changes accordingly.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com. IN SOA\n", zone2.toText());
+}
+
+ // Same than for toText() but using the << operator.
+TEST(D2ZoneTest, output) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ ostringstream ss;
+ ss << zone1;
+ EXPECT_EQ("example.com. ANY SOA\n", ss.str());
+ // Create another object with different parameters to make sure that the
+ // function's output changes accordingly.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ ostringstream ss2;
+ ss2 << zone2;
+ EXPECT_EQ("foo.example.com. IN SOA\n", ss2.str());
+}
+
+// This test verifies that the equality and inequality operators behave as
+// expected.
+TEST(D2ZoneTest, compare) {
+ const Name a("a"), b("b");
+ const RRClass in(RRClass::IN()), any(RRClass::ANY());
+
+ // Equality check
+ EXPECT_TRUE(D2Zone(a, any) == D2Zone(a, any));
+ EXPECT_FALSE(D2Zone(a, any) != D2Zone(a, any));
+
+ // Inequality check, objects differ by class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(a, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(a, in));
+
+ // Inequality check, objects differ by name.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, any));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, any));
+
+ // Inequality check, objects differ by name and class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, in));
+}
+
+} // End of anonymous namespace
diff --git a/src/lib/d2srv/tests/dns_client_unittests.cc b/src/lib/d2srv/tests/dns_client_unittests.cc
new file mode 100644
index 0000000..34115fa
--- /dev/null
+++ b/src/lib/d2srv/tests/dns_client_unittests.cc
@@ -0,0 +1,677 @@
+// Copyright (C) 2013-2023 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 <d2srv/dns_client.h>
+#include <dns/opcode.h>
+#include <asiodns/io_fetch.h>
+#include <asiodns/logger.h>
+#include <asiolink/interval_timer.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2srv/testutils/stats_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <boost/asio/ip/udp.hpp>
+#include <boost/asio/socket_base.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <functional>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::d2;
+using namespace isc::d2::test;
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::stats;
+using namespace isc::util;
+using namespace boost::asio;
+using namespace boost::asio::ip;
+namespace ph = std::placeholders;
+
+namespace {
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint16_t TEST_PORT = 5381;
+const size_t MAX_SIZE = 1024;
+const long TEST_TIMEOUT = 5 * 1000;
+/// @brief Test Fixture class
+//
+// This test fixture class implements DNSClient::Callback so as it can be
+// installed as a completion callback for tests it implements. This callback
+// is called when a DDNS transaction (send and receive) completes. This allows
+// for the callback function to directly access class members. In particular,
+// the callback function can access IOService on which run() was called and
+// call stop() on it.
+//
+// Many of the tests defined here schedule execution of certain tasks and block
+// until tasks are completed or a timeout is hit. However, if timeout is not
+// properly handled a task may be hanging for a long time. In order to prevent
+// it, the asiolink::IntervalTimer is used to break a running test if test
+// timeout is hit. This will result in test failure.
+class DNSClientTest : public ::testing::Test, DNSClient::Callback,
+ public D2StatTest {
+public:
+ /// @brief The IOService which handles IO operations.
+ IOService service_;
+
+ /// @brief The UDP socket.
+ std::unique_ptr<udp::socket> socket_;
+
+ /// @brief The UDP socket endpoint.
+ std::unique_ptr<udp::endpoint> endpoint_;
+
+ /// @brief DNS client response.
+ D2UpdateMessagePtr response_;
+
+ /// @brief The status of the DNS client update callback.
+ DNSClient::Status status_;
+
+ /// @brief The receive buffer.
+ uint8_t receive_buffer_[MAX_SIZE];
+
+ /// @brief The DNS client performing DNS update.
+ DNSClientPtr dns_client_;
+
+ /// @brief The flag which specifies if the response should be corrupted.
+ bool corrupt_response_;
+
+ /// @brief The flag which specifies if a response is expected.
+ bool expect_response_;
+
+ /// @brief The timeout timer.
+ asiolink::IntervalTimer test_timer_;
+
+ /// @brief The number of received DNS updates.
+ int received_;
+
+ /// @brief The number of expected DNS updates.
+ int expected_;
+
+ /// @brief The flag which specifies if the server should continue with
+ /// receiving DNS updates.
+ bool go_on_;
+
+ /// @brief Constructor
+ ///
+ /// This constructor overrides the default logging level of asiodns logger to
+ /// prevent it from emitting debug messages from IOFetch class. Such an error
+ /// message can be emitted if timeout occurs when DNSClient class is
+ /// waiting for a response. Some of the tests are checking DNSClient behavior
+ /// in case when response from the server is not received. Tests output would
+ /// become messy if such errors were logged.
+ DNSClientTest() : service_(), socket_(), endpoint_(),
+ status_(DNSClient::SUCCESS), corrupt_response_(false),
+ expect_response_(true), test_timer_(service_),
+ received_(0), expected_(0), go_on_(false) {
+ asiodns::logger.setSeverity(isc::log::INFO);
+ response_.reset();
+ dns_client_.reset(new DNSClient(response_, this));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&DNSClientTest::testTimeoutHandler, this),
+ TEST_TIMEOUT);
+ }
+
+ /// @brief Destructor
+ ///
+ /// Sets the asiodns logging level back to DEBUG.
+ virtual ~DNSClientTest() {
+ asiodns::logger.setSeverity(isc::log::DEBUG);
+ };
+
+ /// @brief Exchange completion callback
+ ///
+ /// This callback is called when the exchange with the DNS server is
+ /// complete or an error occurred. This includes the occurrence of a timeout.
+ ///
+ /// @param status A status code returned by DNSClient.
+ virtual void operator()(DNSClient::Status status) {
+ status_ = status;
+ if (!expected_ || (expected_ == ++received_)) {
+ service_.stop();
+ }
+
+ if (expect_response_) {
+ if (!corrupt_response_) {
+ // We should have received a response.
+ EXPECT_EQ(DNSClient::SUCCESS, status_);
+
+ ASSERT_TRUE(response_);
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
+ ASSERT_EQ(1,
+ response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = response_->getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ } else {
+ EXPECT_EQ(DNSClient::INVALID_RESPONSE, status_);
+
+ }
+ // If we don't expect a response, the status should indicate a timeout.
+ } else {
+ EXPECT_EQ(DNSClient::TIMEOUT, status_);
+
+ }
+ }
+
+ /// @brief Handler invoked when test timeout is hit
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+
+ /// @brief Handler invoked when test request is received
+ ///
+ /// This callback handler is installed when performing async read on a
+ /// socket to emulate reception of the DNS Update request by a server.
+ /// As a result, this handler will send an appropriate DNS Update response
+ /// message back to the address from which the request has come.
+ ///
+ /// @param socket A pointer to a socket used to receive a query and send a
+ /// response.
+ /// @param remote A pointer to an object which specifies the host (address
+ /// and port) from which a request has come.
+ /// @param receive_length A length (in bytes) of the received data.
+ /// @param corrupt_response A bool value which specifies if the server's
+ /// response should be invalid (true) or valid (false).
+ void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+ size_t receive_length, const bool corrupt_response) {
+ // The easiest way to create a response message is to copy the entire
+ // request.
+ OutputBuffer response_buf(receive_length);
+ response_buf.writeData(receive_buffer_, receive_length);
+ // If a response is to be valid, we have to modify it slightly. If not,
+ // we leave it as is.
+ if (!corrupt_response) {
+ // For a valid response the QR bit must be set. This bit
+ // differentiates both types of messages. Note that the 3rd byte of
+ // the message header comprises this bit in the front followed by
+ // the message code and reserved zeros. Therefore, this byte
+ // has the following value:
+ // 10101000,
+ // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
+ // Write it at message offset 2.
+ response_buf.writeUint8At(0xA8, 2);
+ }
+ // A response message is now ready to send. Send it!
+ socket->send_to(boost::asio::buffer(response_buf.getData(),
+ response_buf.getLength()),
+ *remote);
+
+ if (go_on_) {
+ socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ *endpoint_,
+ std::bind(&DNSClientTest::udpReceiveHandler,
+ this, socket_.get(),
+ endpoint_.get(), ph::_2,
+ corrupt_response));
+ }
+ }
+
+ /// @brief Request handler for testing clients using TSIG
+ ///
+ /// This callback handler is installed when performing async read on a
+ /// socket to emulate reception of the DNS Update request with TSIG by a
+ /// server. As a result, this handler will send an appropriate DNS Update
+ /// response message back to the address from which the request has come.
+ ///
+ /// @param socket A pointer to a socket used to receive a query and send a
+ /// response.
+ /// @param remote A pointer to an object which specifies the host (address
+ /// and port) from which a request has come.
+ /// @param receive_length A length (in bytes) of the received data.
+ /// @param client_key TSIG key the server should use to verify the inbound
+ /// request. If the pointer is NULL, the server will not attempt to
+ /// verify the request.
+ /// @param server_key TSIG key the server should use to sign the outbound
+ /// request. If the pointer is NULL, the server will not sign the outbound
+ /// response. If the pointer is not NULL and not the same value as the
+ /// client_key, the server will use a new context to sign the response then
+ /// the one used to verify it. This allows us to simulate the server
+ /// signing with the wrong key.
+ void TSIGReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+ size_t receive_length,
+ D2TsigKeyPtr client_key,
+ D2TsigKeyPtr server_key) {
+
+ TSIGContextPtr context;
+ if (client_key) {
+ context = client_key->createContext();
+ }
+
+ isc::util::InputBuffer received_data_buffer(receive_buffer_,
+ receive_length);
+
+ dns::Message request(Message::PARSE);
+ request.fromWire(received_data_buffer);
+
+ // If context is not NULL, then we need to verify the message.
+ if (context) {
+ TSIGError error = context->verify(request.getTSIGRecord(),
+ receive_buffer_, receive_length);
+ if (error != TSIGError::NOERROR()) {
+ isc_throw(TSIGVerifyError, "TSIG verification failed: "
+ << error.toText());
+ }
+ }
+
+ dns::Message response(Message::RENDER);
+ response.setOpcode(Opcode(Opcode::UPDATE_CODE));
+ response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
+ response.setQid(request.getQid());
+ response.setRcode(Rcode::NOERROR());
+ dns::Question question(Name("example.com."),
+ RRClass::IN(), RRType::SOA());
+ response.addQuestion(question);
+
+ MessageRenderer renderer;
+
+ if (!server_key) {
+ // don't sign the response.
+ context.reset();
+ } else if (server_key != client_key) {
+ // use a different key to sign the response.
+ context.reset(new TSIGContext(*server_key));
+ } // otherwise use the context based on client_key.
+
+ response.toWire(renderer, context.get());
+ // A response message is now ready to send. Send it!
+ socket->send_to(boost::asio::buffer(renderer.getData(),
+ renderer.getLength()),
+ *remote);
+ }
+
+ /// @brief This test verifies that when invalid response placeholder object
+ /// is passed to a constructor which throws the appropriate exception.
+ /// It also verifies that the constructor will not throw if the supplied
+ /// callback object is NULL.
+ void runConstructorTest() {
+ EXPECT_NO_THROW(DNSClient(response_, NULL, DNSClient::UDP));
+
+ // The TCP Transport is not supported right now. So, we return exception
+ // if caller specified TCP as a preferred protocol. This test will be
+ // removed once TCP is supported.
+ EXPECT_THROW(DNSClient(response_, NULL, DNSClient::TCP),
+ isc::NotImplemented);
+ }
+
+ /// @brief This test verifies that it accepted timeout values belong to the
+ /// range of <0, DNSClient::getMaxTimeout()>.
+ void runInvalidTimeoutTest() {
+
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Start with a valid timeout equal to maximal allowed. This way we will
+ // ensure that doUpdate doesn't throw an exception for valid timeouts.
+ unsigned int timeout = DNSClient::getMaxTimeout();
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // Cross the limit and expect that exception is thrown this time.
+ timeout = DNSClient::getMaxTimeout() + 1;
+ EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout),
+ isc::BadValue);
+ }
+
+ /// @brief This test verifies the DNSClient behavior when a server does not
+ /// respond do the DNS Update message. In such case, the callback function
+ /// is expected to be called and the TIME_OUT error code should be returned.
+ void runSendNoReceiveTest() {
+ // We expect no response from a server.
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ /// @todo The timeout value could be set to 0 to trigger timeout
+ /// instantly. However, it may lead to situations that the message sent
+ /// in one test will not be dropped by the kernel by the time, the next
+ /// test starts. This will lead to intermittent unit test errors.
+ /// Increasing the timeout to a non-zero value mitigates this problem.
+ /// The proper way to solve this problem is to receive the packet
+ /// on our own and drop it. Such a fix will need to be applied not only
+ /// to this test but also for other tests that rely on arbitrary timeout
+ /// values.
+ const int timeout = 500;
+ // The doUpdate() function starts asynchronous message exchange with DNS
+ // server. When message exchange is done or timeout occurs, the
+ // completion callback will be triggered. The doUpdate function returns
+ // immediately.
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // This starts the execution of tasks posted to IOService. run() blocks
+ // until stop() is called in the completion callback function.
+ service_.run();
+
+ }
+
+ /// @brief This test verifies that DNSClient can send DNS Update and receive
+ /// a corresponding response from a server.
+ void runSendReceiveTest(const bool corrupt_response,
+ const bool two_sends) {
+ go_on_ = two_sends;
+ corrupt_response_ = corrupt_response;
+
+ // Create a request DNS Update message.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // In order to perform the full test, when the client sends the request
+ // and receives a response from the server, we have to emulate the
+ // server's response in the test. A request will be sent via loopback
+ // interface to 127.0.0.1 and known test port. Response must be sent
+ // to 127.0.0.1 and the source port which has been used to send the
+ // request. A new socket is created, specifically to handle sending
+ // responses. The reuse address option is set so as both sockets can
+ // use the same address. This new socket is bound to the test address
+ // and port, where requests will be sent.
+ socket_.reset(new udp::socket(service_.get_io_service(),
+ boost::asio::ip::udp::v4()));
+ socket_->set_option(socket_base::reuse_address(true));
+ socket_->bind(udp::endpoint(address::from_string(TEST_ADDRESS),
+ TEST_PORT));
+ // Once socket is created, we can post an IO request to receive some
+ // packet from this socket. This is asynchronous operation and
+ // nothing is received until another IO request to send a query is
+ // posted and the run() is invoked on this IO. A callback function is
+ // attached to this asynchronous read. This callback function requires
+ // that a socket object used to receive the request is passed to it,
+ // because the same socket will be used by the callback function to send
+ // a response. Also, the remote object is passed to the callback,
+ // because it holds a source address and port where request originated.
+ // Callback function will send a response to this address and port.
+ // The last parameter holds a length of the received request. It is
+ // required to construct a response.
+ endpoint_.reset(new udp::endpoint());
+ socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ *endpoint_,
+ std::bind(&DNSClientTest::udpReceiveHandler,
+ this, socket_.get(),
+ endpoint_.get(), ph::_2,
+ corrupt_response));
+
+ // The socket is now ready to receive the data. Let's post some request
+ // message then. Set timeout to some reasonable value to make sure that
+ // there is sufficient amount of time for the test to generate a
+ // response.
+ const int timeout = 500;
+ expected_++;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ // It is possible to request that two packets are sent concurrently.
+ if (two_sends) {
+ expected_++;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ }
+
+ // Kick of the message exchange by actually running the scheduled
+ // "send" and "receive" operations.
+ service_.run();
+
+ socket_->close();
+
+ // Since the callback, operator(), calls stop() on the io_service,
+ // we must reset it in order for subsequent calls to run() or
+ // run_one() to work.
+ service_.get_io_service().reset();
+ }
+
+ /// @brief Performs a single request-response exchange with or without TSIG.
+ ///
+ /// @param client_key TSIG passed to dns_client and also used by the
+ /// "server" to verify the request.
+ /// @param server_key TSIG key the "server" should use to sign the response.
+ /// If this is NULL, then client_key is used.
+ /// @param should_pass indicates if the test should pass.
+ void runTSIGTest(D2TsigKeyPtr client_key, D2TsigKeyPtr server_key,
+ bool should_pass = true) {
+ // Tell operator() method if we expect an invalid response.
+ corrupt_response_ = !should_pass;
+
+ // Create a request DNS Update message.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Setup our "loopback" server.
+ udp::socket udp_socket(service_.get_io_service(), boost::asio::ip::udp::v4());
+ udp_socket.set_option(socket_base::reuse_address(true));
+ udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
+ TEST_PORT));
+ udp::endpoint remote;
+ udp_socket.async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ remote,
+ std::bind(&DNSClientTest::
+ TSIGReceiveHandler, this,
+ &udp_socket, &remote, ph::_2,
+ client_key, server_key));
+
+ // The socket is now ready to receive the data. Let's post some request
+ // message then. Set timeout to some reasonable value to make sure that
+ // there is sufficient amount of time for the test to generate a
+ // response.
+ const int timeout = 500;
+ expected_++;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout, client_key);
+
+ // Kick of the message exchange by actually running the scheduled
+ // "send" and "receive" operations.
+ service_.run();
+
+ udp_socket.close();
+
+ // Since the callback, operator(), calls stop() on the io_service,
+ // we must reset it in order for subsequent calls to run() or
+ // run_one() to work.
+ service_.get_io_service().reset();
+ }
+};
+
+// Verify that the DNSClient object can be created if provided parameters are
+// valid. Constructor should throw exceptions when parameters are invalid.
+TEST_F(DNSClientTest, constructor) {
+ runConstructorTest();
+ StatMap stats_upd = {
+ { "update-sent", 0},
+ { "update-signed", 0},
+ { "update-unsigned", 0},
+ { "update-success", 0},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// This test verifies that the maximal allowed timeout value is maximal int
+// value.
+TEST_F(DNSClientTest, getMaxTimeout) {
+ EXPECT_EQ(std::numeric_limits<int>::max(), DNSClient::getMaxTimeout());
+}
+
+// Verify that timeout is reported when no response is received from DNS.
+TEST_F(DNSClientTest, timeout) {
+ runSendNoReceiveTest();
+ StatMap stats_upd = {
+ { "update-sent", 1},
+ { "update-signed", 0},
+ { "update-unsigned", 1},
+ { "update-success", 0},
+ { "update-timeout", 1},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that exception is thrown when invalid (too high) timeout value is
+// specified for asynchronous DNS Update.
+TEST_F(DNSClientTest, invalidTimeout) {
+ runInvalidTimeoutTest();
+}
+
+// Verifies that TSIG can be used to sign requests and verify responses.
+TEST_F(DNSClientTest, runTSIGTest) {
+ std::string secret ("key number one");
+ D2TsigKeyPtr key_one;
+ ASSERT_NO_THROW(key_one.reset(new
+ D2TsigKey(Name("one.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+ StatMap stats_key = {
+ { "update-sent", 0},
+ { "update-success", 0},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats("one.com.", stats_key);
+ secret = "key number two";
+ D2TsigKeyPtr key_two;
+ ASSERT_NO_THROW(key_two.reset(new
+ D2TsigKey(Name("two.com"),
+ TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+ checkStats("two.com.", stats_key);
+ D2TsigKeyPtr nokey;
+
+ // Should be able to send and receive with no keys.
+ // Neither client nor server will attempt to sign or verify.
+ runTSIGTest(nokey, nokey);
+
+ // Client signs the request, server verifies but doesn't sign.
+ runTSIGTest(key_one, nokey, false);
+
+ // Client and server use the same key to sign and verify.
+ runTSIGTest(key_one, key_one);
+
+ // Server uses different key to sign the response.
+ runTSIGTest(key_one, key_two, false);
+
+ // Client neither signs nor verifies, server responds with a signed answer
+ // Since we are "liberal" in what we accept this should be ok.
+ runTSIGTest(nokey, key_two);
+
+ // Check statistics.
+ StatMap stats_one = {
+ { "update-sent", 3},
+ { "update-success", 1},
+ { "update-timeout", 0},
+ { "update-error", 2}
+ };
+ checkStats("one.com.", stats_one);
+ checkStats("two.com.", stats_key);
+ StatMap stats_upd = {
+ { "update-sent", 5},
+ { "update-signed", 3},
+ { "update-unsigned", 2},
+ { "update-success", 3},
+ { "update-timeout", 0},
+ { "update-error", 2}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that the DNSClient receives the response from DNS and the received
+// buffer can be decoded as DNS Update Response.
+TEST_F(DNSClientTest, sendReceive) {
+ // false means that server response is not corrupted.
+ runSendReceiveTest(false, false);
+ StatMap stats_upd = {
+ { "update-sent", 1},
+ { "update-signed", 0},
+ { "update-unsigned", 1},
+ { "update-success", 1},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that the DNSClient reports an error when the response is received from
+// a DNS and this response is corrupted.
+TEST_F(DNSClientTest, sendReceiveCorrupted) {
+ // true means that server's response is corrupted.
+ runSendReceiveTest(true, false);
+ StatMap stats_upd = {
+ { "update-sent", 1},
+ { "update-signed", 0},
+ { "update-unsigned", 1},
+ { "update-success", 0},
+ { "update-timeout", 0},
+ { "update-error", 1}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that it is possible to use the same DNSClient instance to
+// perform the following sequence of message exchanges:
+// 1. send
+// 2. receive
+// 3. send
+// 4. receive
+TEST_F(DNSClientTest, sendReceiveTwice) {
+ runSendReceiveTest(false, false);
+ runSendReceiveTest(false, false);
+ EXPECT_EQ(2, received_);
+ StatMap stats_upd = {
+ { "update-sent", 2},
+ { "update-signed", 0},
+ { "update-unsigned", 2},
+ { "update-success", 2},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+// Verify that it is possible to use the DNSClient instance to perform the
+// following sequence of message exchanges:
+// 1. send
+// 2. send
+// 3. receive
+// 4. receive
+TEST_F(DNSClientTest, concurrentSendReceive) {
+ runSendReceiveTest(false, true);
+ StatMap stats_upd = {
+ { "update-sent", 2},
+ { "update-signed", 0},
+ { "update-unsigned", 2},
+ { "update-success", 2},
+ { "update-timeout", 0},
+ { "update-error", 0}
+ };
+ checkStats(stats_upd);
+}
+
+} // End of anonymous namespace
diff --git a/src/lib/d2srv/tests/nc_trans_unittests.cc b/src/lib/d2srv/tests/nc_trans_unittests.cc
new file mode 100644
index 0000000..89a23b7
--- /dev/null
+++ b/src/lib/d2srv/tests/nc_trans_unittests.cc
@@ -0,0 +1,1279 @@
+// Copyright (C) 2013-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <d2srv/nc_trans.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/opcode.h>
+#include <dns/messagerenderer.h>
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <util/buffer.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test derivation of NameChangeTransaction for exercising state
+/// model mechanics.
+///
+/// This class facilitates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines. It implements a very
+/// rudimentary state model, sufficient to test the state model mechanics
+/// supplied by the base class.
+class NameChangeStub : public NameChangeTransaction {
+public:
+
+ // NameChangeStub states
+ static const int DOING_UPDATE_ST = NCT_DERIVED_STATE_MIN + 1;
+
+ // NameChangeStub events
+ static const int SEND_UPDATE_EVT = NCT_DERIVED_EVENT_MIN + 2;
+
+ /// @brief Flag which specifies if the NameChangeStub's callback should be
+ /// used instead of the NameChangeTransaction's callback.
+ bool use_stub_callback_;
+
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by NameChangeTransaction.
+ NameChangeStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameChangeTransaction(io_service, ncr, forward_domain,
+ reverse_domain, cfg_mgr),
+ use_stub_callback_(false) {
+ }
+
+ /// @brief Destructor
+ virtual ~NameChangeStub() {
+ }
+
+ /// @brief DNSClient callback
+ /// Overrides the callback in NameChangeTransaction to allow testing
+ /// sendUpdate without incorporating execution of the state model
+ /// into the test.
+ /// It sets the DNS status update and posts IO_COMPLETED_EVT as does
+ /// the normal callback.
+ virtual void operator()(DNSClient::Status status) {
+ if (use_stub_callback_) {
+ setDnsUpdateStatus(status);
+ postNextEvent(IO_COMPLETED_EVT);
+ } else {
+ // For tests which need to use the real callback.
+ NameChangeTransaction::operator()(status);
+ }
+ }
+
+ /// @brief Some tests require a server to be selected. This method
+ /// selects the first server in the forward domain without involving
+ /// state model execution to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Empty handler used to satisfy map verification.
+ void dummyHandler() {
+ isc_throw(NameChangeTransactionError,
+ "dummyHandler - invalid event: " << getContextStr());
+ }
+
+ /// @brief State handler for the READY_ST.
+ ///
+ /// Serves as the starting state handler, it consumes the
+ /// START_EVT "transitioning" to the state, DOING_UPDATE_ST and
+ /// sets the next event to SEND_UPDATE_EVT.
+ void readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ transition(DOING_UPDATE_ST, SEND_UPDATE_EVT);
+ break;
+ default:
+ // its bogus
+ isc_throw(NameChangeTransactionError,
+ "readyHandler - invalid event: " << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DOING_UPDATE_ST.
+ ///
+ /// Simulates a state that starts some form of asynchronous work.
+ /// When next event is SEND_UPDATE_EVT it sets the status to pending
+ /// and signals the state model must "wait" for an event by setting
+ /// next event to NOP_EVT.
+ ///
+ /// When next event is IO_COMPLETED_EVT, it transitions to the state,
+ /// PROCESS_TRANS_OK_ST, and sets the next event to UPDATE_OK_EVT.
+ void doingUpdateHandler() {
+ switch(getNextEvent()) {
+ case SEND_UPDATE_EVT:
+ setNcrStatus(dhcp_ddns::ST_PENDING);
+ postNextEvent(NOP_EVT);
+ break;
+ case IO_COMPLETED_EVT:
+ if (getDnsUpdateStatus() == DNSClient::SUCCESS) {
+ setForwardChangeCompleted(true);
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ } else {
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+ break;
+ default:
+ // its bogus
+ isc_throw(NameChangeTransactionError,
+ "doingUpdateHandler - invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the PROCESS_TRANS_OK_ST.
+ ///
+ /// This is the last state in the model. Note that it sets the
+ /// status to completed and next event to NOP_EVT.
+ void processTransDoneHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ endModel();
+ break;
+ case UPDATE_FAILED_EVT:
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ endModel();
+ break;
+ default:
+ // its bogus
+ isc_throw(NameChangeTransactionError,
+ "processTransDoneHandler - invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief Construct the event dictionary.
+ virtual void defineEvents() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::defineEvents();
+
+ // Define our events.
+ defineEvent(SEND_UPDATE_EVT, "SEND_UPDATE_EVT");
+ }
+
+ /// @brief Verify the event dictionary.
+ virtual void verifyEvents() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::verifyEvents();
+
+ // Define our events.
+ getEvent(SEND_UPDATE_EVT);
+ }
+
+ /// @brief Construct the state dictionary.
+ virtual void defineStates() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::defineStates();
+
+ // Define our states.
+ defineState(READY_ST, "READY_ST",
+ std::bind(&NameChangeStub::readyHandler, this));
+
+ defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+ std::bind(&NameChangeStub::dummyHandler, this));
+
+ defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+ std::bind(&NameChangeStub::dummyHandler, this));
+
+ defineState(DOING_UPDATE_ST, "DOING_UPDATE_ST",
+ std::bind(&NameChangeStub::doingUpdateHandler,
+ this));
+
+ defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+ std::bind(&NameChangeStub::
+ processTransDoneHandler, this));
+
+ defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+ std::bind(&NameChangeStub::
+ processTransDoneHandler, this));
+ }
+
+ /// @brief Verify the event dictionary.
+ virtual void verifyStates() {
+ // Invoke the base call implementation first.
+ NameChangeTransaction::verifyStates();
+
+ // Check our states.
+ getStateInternal(DOING_UPDATE_ST);
+ }
+
+ // Expose the protected methods to be tested.
+ using StateModel::runModel;
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using NameChangeTransaction::initServerSelection;
+ using NameChangeTransaction::selectNextServer;
+ using NameChangeTransaction::getCurrentServer;
+ using NameChangeTransaction::getDNSClient;
+ using NameChangeTransaction::setNcrStatus;
+ using NameChangeTransaction::setDnsUpdateRequest;
+ using NameChangeTransaction::clearDnsUpdateRequest;
+ using NameChangeTransaction::clearUpdateAttempts;
+ using NameChangeTransaction::setDnsUpdateStatus;
+ using NameChangeTransaction::getDnsUpdateResponse;
+ using NameChangeTransaction::setDnsUpdateResponse;
+ using NameChangeTransaction::clearDnsUpdateResponse;
+ using NameChangeTransaction::getForwardChangeCompleted;
+ using NameChangeTransaction::getReverseChangeCompleted;
+ using NameChangeTransaction::setForwardChangeCompleted;
+ using NameChangeTransaction::setReverseChangeCompleted;
+ using NameChangeTransaction::setUpdateAttempts;
+ using NameChangeTransaction::transition;
+ using NameChangeTransaction::retryTransition;
+ using NameChangeTransaction::sendUpdate;
+ using NameChangeTransaction::prepNewRequest;
+ using NameChangeTransaction::addLeaseAddressRdata;
+ using NameChangeTransaction::addDhcidRdata;
+ using NameChangeTransaction::addPtrRdata;
+ using NameChangeTransaction::responseString;
+ using NameChangeTransaction::transactionOutcomeString;
+};
+
+// Declare them so Gtest can see them.
+const int NameChangeStub::DOING_UPDATE_ST;
+const int NameChangeStub::SEND_UPDATE_EVT;
+
+/// @brief Defines a pointer to a NameChangeStubPtr instance.
+typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
+
+/// @brief Test fixture for testing NameChangeTransaction
+///
+/// Note this class uses NameChangeStub class to exercise non-public
+/// aspects of NameChangeTransaction.
+class NameChangeTransactionTest : public TransactionTest {
+public:
+ NameChangeTransactionTest() {
+ }
+
+ virtual ~NameChangeTransactionTest() {
+ }
+
+
+ /// @brief Instantiates a NameChangeStub test transaction
+ /// The transaction is constructed around a predefined (i.e "canned")
+ /// NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested, and both forward and reverse domains are populated.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo to use, defaults to
+ /// an empty pointer, in which case TSIG will not be used.
+ NameChangeStubPtr makeCannedTransaction(const TSIGKeyInfoPtr&
+ tsig_key_info = TSIGKeyInfoPtr()) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG,
+ tsig_key_info);
+
+ // Now create the test transaction as would occur in update manager.
+ // Instantiate the transaction as would be done by update manager.
+ return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_,
+ forward_domain_, reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Instantiates a NameChangeStub test transaction
+ /// The transaction is constructed around a predefined (i.e "canned")
+ /// NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested, and both forward and reverse domains are populated.
+ /// @param key_name value to use to create TSIG key, if blank TSIG will not
+ /// be used.
+ NameChangeStubPtr makeCannedTransaction(const std::string& key_name) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG, key_name);
+
+ // Now create the test transaction as would occur in update manager.
+ // Instantiate the transaction as would be done by update manager.
+ return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_,
+ forward_domain_, reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Builds and then sends an update request
+ ///
+ /// This method is used to build and send and update request. It is used
+ /// in conjunction with FauxServer to test various message response
+ /// scenarios.
+ /// @param name_change Transaction under test
+ /// @param run_time Maximum time to permit IO processing to run before
+ /// timing out (in milliseconds)
+ void doOneExchange(NameChangeStubPtr name_change,
+ unsigned int run_time = 500) {
+ // Create a valid request for the transaction.
+ D2UpdateMessagePtr req;
+ ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::
+ OUTBOUND)));
+ ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+ req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
+ req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
+
+ // Set the flag to use the NameChangeStub's DNSClient callback.
+ name_change->use_stub_callback_ = true;
+
+ // Invoke sendUpdate.
+ ASSERT_NO_THROW(name_change->sendUpdate());
+
+ // Update attempt count should be 1, next event should be NOP_EVT.
+ ASSERT_EQ(1, name_change->getUpdateAttempts());
+ ASSERT_EQ(NameChangeTransaction::NOP_EVT,
+ name_change->getNextEvent());
+
+ while (name_change->getNextEvent() == NameChangeTransaction::NOP_EVT) {
+ int cnt = 0;
+ ASSERT_NO_THROW(cnt = runTimedIO(run_time));
+ if (cnt == 0) {
+ FAIL() << "IO Service stopped unexpectedly";
+ }
+ }
+ }
+};
+
+/// @brief Tests NameChangeTransaction construction.
+/// This test verifies that:
+/// 1. Construction with null NameChangeRequest
+/// 2. Construction with null forward domain is not allowed when the request
+/// requires forward change.
+/// 3. Construction with null reverse domain is not allowed when the request
+/// requires reverse change.
+/// 4. Valid construction functions properly
+TEST(NameChangeTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+
+ dhcp_ddns::NameChangeRequestPtr empty_ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with a null IOServicePtr fails.
+ // @todo Subject to change if multi-threading is implemented.
+ asiolink::IOServicePtr empty;
+ EXPECT_THROW(NameChangeTransaction(empty, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that construction with an empty NameChangeRequest throws.
+ EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that construction with an empty D2CfgMgr throws.
+ D2CfgMgrPtr empty_cfg;
+ EXPECT_THROW(NameChangeTransaction(io_service, empty_ncr,
+ forward_domain, reverse_domain,
+ empty_cfg),
+ NameChangeTransactionError);
+
+
+ // Verify that construction with an empty forward domain when the
+ // NameChangeRequest calls for a forward change throws.
+ EXPECT_THROW(NameChangeTransaction(io_service, ncr,
+ empty_domain, reverse_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that construction with an empty reverse domain when the
+ // NameChangeRequest calls for a reverse change throws.
+ EXPECT_THROW(NameChangeTransaction(io_service, ncr,
+ forward_domain, empty_domain, cfg_mgr),
+ NameChangeTransactionError);
+
+ // Verify that a valid construction attempt works.
+ EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+
+ // Verify that an empty forward domain is allowed when the requests does
+ // not include a forward change.
+ ncr->setForwardChange(false);
+ ncr->setReverseChange(true);
+ EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+ empty_domain, reverse_domain,
+ cfg_mgr));
+
+ // Verify that an empty reverse domain is allowed when the requests does
+ // not include a reverse change.
+ ncr->setForwardChange(true);
+ ncr->setReverseChange(false);
+ EXPECT_NO_THROW(NameChangeTransaction(io_service, ncr,
+ forward_domain, empty_domain,
+ cfg_mgr));
+}
+
+/// @brief General testing of member accessors.
+/// Most if not all of these are also tested as a byproduct of larger tests.
+TEST_F(NameChangeTransactionTest, accessors) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Verify that fetching the NameChangeRequest works.
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+ ASSERT_TRUE(ncr);
+
+ // Verify that getTransactionKey works.
+ EXPECT_EQ(ncr->getDhcid(), name_change->getTransactionKey());
+
+ // Verify that getRequestId works.
+ EXPECT_EQ(ncr->getRequestId(), name_change->getRequestId());
+
+ // Verify that NcrStatus can be set and retrieved.
+ EXPECT_NO_THROW(name_change->setNcrStatus(dhcp_ddns::ST_FAILED));
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, ncr->getStatus());
+
+ // Verify that the forward domain can be retrieved.
+ ASSERT_TRUE(name_change->getForwardDomain());
+ EXPECT_EQ(forward_domain_, name_change->getForwardDomain());
+
+ // Verify that the reverse domain can be retrieved.
+ ASSERT_TRUE(name_change->getReverseDomain());
+ EXPECT_EQ(reverse_domain_, name_change->getReverseDomain());
+
+ // Neither of these have direct setters, but are tested under server
+ // selection.
+ EXPECT_FALSE(name_change->getDNSClient());
+ EXPECT_FALSE(name_change->getCurrentServer());
+
+ // Verify that DNS update status can be set and retrieved.
+ EXPECT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
+ EXPECT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
+
+ // Verify that the forward change complete flag can be set and fetched.
+ EXPECT_NO_THROW(name_change->setForwardChangeCompleted(true));
+ EXPECT_TRUE(name_change->getForwardChangeCompleted());
+
+ // Verify that the reverse change complete flag can be set and fetched.
+ EXPECT_NO_THROW(name_change->setReverseChangeCompleted(true));
+ EXPECT_TRUE(name_change->getReverseChangeCompleted());
+}
+
+/// @brief Tests DNS update request accessor methods.
+TEST_F(NameChangeTransactionTest, dnsUpdateRequestAccessors) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Post transaction construction, there should not be an update request.
+ EXPECT_FALSE(name_change->getDnsUpdateRequest());
+
+ // Create a request.
+ D2UpdateMessagePtr req;
+ ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+
+ // Use the setter and then verify we can fetch the request.
+ ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+
+ // Post set, we should be able to fetch it.
+ ASSERT_TRUE(name_change->getDnsUpdateRequest());
+
+ // Should be able to clear it.
+ ASSERT_NO_THROW(name_change->clearDnsUpdateRequest());
+
+ // Should be empty again.
+ EXPECT_FALSE(name_change->getDnsUpdateRequest());
+}
+
+/// @brief Tests DNS update request accessor methods.
+TEST_F(NameChangeTransactionTest, dnsUpdateResponseAccessors) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Post transaction construction, there should not be an update response.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Create a response.
+ D2UpdateMessagePtr resp;
+ ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND)));
+
+ // Use the setter and then verify we can fetch the response.
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+ // Post set, we should be able to fetch it.
+ EXPECT_TRUE(name_change->getDnsUpdateResponse());
+
+ // Should be able to clear it.
+ ASSERT_NO_THROW(name_change->clearDnsUpdateResponse());
+
+ // Should be empty again.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+}
+
+/// @brief Tests responseString method.
+TEST_F(NameChangeTransactionTest, responseString) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Make sure it is safe to call when status says success but there
+ // is no update response.
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::SUCCESS));
+ EXPECT_EQ("SUCCESS, rcode: update response is NULL",
+ name_change->responseString());
+
+ // Create a response. (We use an OUTBOUND message so we can set RCODE)
+ D2UpdateMessagePtr resp;
+ ASSERT_NO_THROW(resp.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(resp));
+
+ // Make sure we decode Rcode when status is successful.
+ ASSERT_NO_THROW(resp->setRcode(dns::Rcode::NXDOMAIN()));
+ EXPECT_EQ("SUCCESS, rcode: NXDOMAIN", name_change->responseString());
+
+ // Test all of the non-success values for status.
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::TIMEOUT));
+ EXPECT_EQ("TIMEOUT", name_change->responseString());
+
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::IO_STOPPED));
+ EXPECT_EQ("IO_STOPPED", name_change->responseString());
+
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::
+ INVALID_RESPONSE));
+ EXPECT_EQ("INVALID_RESPONSE", name_change->responseString());
+
+ ASSERT_NO_THROW(name_change->setDnsUpdateStatus(DNSClient::OTHER));
+ EXPECT_EQ("OTHER", name_change->responseString());
+}
+
+/// @brief Tests transactionOutcomeString method.
+TEST_F(NameChangeTransactionTest, transactionOutcomeString) {
+ // Create a transaction.
+ NameChangeStubPtr name_change;
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ncr = name_change->getNcr();
+
+ // Check case of failed transaction in both directions
+ std::string exp_str("Status: Failed, Event: UNDEFINED, Forward change:"
+ " failed, Reverse change: failed, request: ");
+ exp_str += ncr->toText();
+
+ std::string tstring = name_change->transactionOutcomeString();
+ std::cout << "tstring is: [" << tstring << "]" << std::endl;
+
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+ // Check case of success all around
+ name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ name_change->setForwardChangeCompleted(true);
+ name_change->setReverseChangeCompleted(true);
+
+ exp_str = "Status: Completed, Event: UNDEFINED, Forward change: completed,"
+ " Reverse change: completed, request: " + ncr->toText();
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+ // Check case of success, with no forward change
+ name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ ncr->setForwardChange(false);
+ exp_str = "Status: Completed, Event: UNDEFINED, "
+ " Reverse change: completed, request: " + ncr->toText();
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+
+ // Check case of success, with no reverse change
+ name_change->setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ ncr->setForwardChange(true);
+ ncr->setReverseChange(false);
+ exp_str = "Status: Completed, Event: UNDEFINED, "
+ " Forward change: completed, request: " + ncr->toText();
+ EXPECT_EQ(exp_str, name_change->transactionOutcomeString());
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameChangeTransactionTest, dictionaryCheck) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_change->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_change->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_change->defineEvents());
+ ASSERT_NO_THROW(name_change->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_change->verifyEvents());
+ ASSERT_NO_THROW(name_change->verifyStates());
+}
+
+/// @brief Tests server selection methods.
+/// Each transaction has a list of one or more servers for each DNS direction
+/// it is required to update. The transaction must be able to start at the
+/// beginning of a server list and cycle through them one at time, as needed,
+/// when a DNS exchange fails due to an IO error. This test verifies the
+/// ability to iteratively select a server from the list as the current server.
+TEST_F(NameChangeTransactionTest, serverSelectionTest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Verify that the forward domain and its list of servers can be retrieved.
+ DdnsDomainPtr& domain = name_change->getForwardDomain();
+ ASSERT_TRUE(domain);
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ ASSERT_TRUE(servers);
+
+ // Get the number of entries in the server list.
+ int num_servers = servers->size();
+ ASSERT_TRUE(num_servers > 0);
+
+ // Verify that we can initialize server selection. This "resets" the
+ // selection process to start over using the list of servers in the
+ // given domain.
+ ASSERT_NO_THROW(name_change->initServerSelection(domain));
+
+ // The server selection process determines the current server,
+ // instantiates a new DNSClient, and a DNS response message buffer.
+ // We need to save the values before each selection, so we can verify
+ // they are correct after each selection.
+ DnsServerInfoPtr prev_server = name_change->getCurrentServer();
+ DNSClientPtr prev_client = name_change->getDNSClient();
+
+ // Verify response pointer is empty.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Create dummy response so we can verify it is cleared at each
+ // new server select.
+ D2UpdateMessagePtr dummyResp;
+ dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp));
+ ASSERT_TRUE(name_change->getDnsUpdateResponse());
+
+ // Iteratively select through the list of servers.
+ int passes = 0;
+ while (name_change->selectNextServer()) {
+ // Get the new values after the selection has been made.
+ DnsServerInfoPtr server = name_change->getCurrentServer();
+ DNSClientPtr client = name_change->getDNSClient();
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+
+ // Verify that the new values are not empty.
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(client);
+
+ // Verify response pointer is now empty.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Verify that the new values are indeed new.
+ EXPECT_NE(server, prev_server);
+ EXPECT_NE(client, prev_client);
+
+ // Remember the selected values for the next pass.
+ prev_server = server;
+ prev_client = client;
+
+ // Create new dummy response.
+ dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp));
+ ASSERT_TRUE(name_change->getDnsUpdateResponse());
+
+ ++passes;
+ }
+
+ // Verify that the number of passes made equal the number of servers.
+ EXPECT_EQ (passes, num_servers);
+
+ // Repeat the same test using the reverse domain.
+ // Verify that the reverse domain and its list of servers can be retrieved.
+ domain = name_change->getReverseDomain();
+ ASSERT_TRUE(domain);
+ servers = domain->getServers();
+ ASSERT_TRUE(servers);
+
+ // Get the number of entries in the server list.
+ num_servers = servers->size();
+ ASSERT_TRUE(num_servers > 0);
+
+ // Verify that we can initialize server selection. This "resets" the
+ // selection process to start over using the list of servers in the
+ // given domain.
+ ASSERT_NO_THROW(name_change->initServerSelection(domain));
+
+ // The server selection process determines the current server,
+ // instantiates a new DNSClient, and resets the DNS response message buffer.
+ // We need to save the values before each selection, so we can verify
+ // they are correct after each selection.
+ prev_server = name_change->getCurrentServer();
+ prev_client = name_change->getDNSClient();
+
+ // Iteratively select through the list of servers.
+ passes = 0;
+ while (name_change->selectNextServer()) {
+ // Get the new values after the selection has been made.
+ DnsServerInfoPtr server = name_change->getCurrentServer();
+ DNSClientPtr client = name_change->getDNSClient();
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+
+ // Verify that the new values are not empty.
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(client);
+
+ // Verify response pointer is now empty.
+ EXPECT_FALSE(name_change->getDnsUpdateResponse());
+
+ // Verify that the new values are indeed new.
+ EXPECT_NE(server, prev_server);
+ EXPECT_NE(client, prev_client);
+
+ // Remember the selected values for the next pass.
+ prev_server = server;
+ prev_client = client;
+
+ // Create new dummy response.
+ dummyResp.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ ASSERT_NO_THROW(name_change->setDnsUpdateResponse(dummyResp));
+ ASSERT_TRUE(name_change->getDnsUpdateResponse());
+
+ ++passes;
+ }
+
+ // Verify that the number of passes made equal the number of servers.
+ EXPECT_EQ (passes, num_servers);
+}
+
+/// @brief Tests that the transaction will be "failed" upon model errors.
+TEST_F(NameChangeTransactionTest, modelFailure) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Now call runModel() with an undefined event which should not throw,
+ // but should result in a failed model and failed transaction.
+ EXPECT_NO_THROW(name_change->runModel(9999));
+
+ // Verify that the model reports are done but failed.
+ EXPECT_TRUE(name_change->isModelDone());
+ EXPECT_TRUE(name_change->didModelFail());
+
+ // Verify that the transaction has failed.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
+}
+
+/// @brief Tests the ability to use startTransaction to initiate the state
+/// model execution, and DNSClient callback, operator(), to resume the
+/// model with a update successful outcome.
+TEST_F(NameChangeTransactionTest, successfulUpdateTest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ EXPECT_TRUE(name_change->isModelNew());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+ // Launch the transaction by calling startTransaction. The state model
+ // should run up until the "IO" operation is initiated in DOING_UPDATE_ST.
+ ASSERT_NO_THROW(name_change->startTransaction());
+
+ // Verify that the model is running but waiting, and that forward change
+ // completion is still false.
+ EXPECT_TRUE(name_change->isModelRunning());
+ EXPECT_TRUE(name_change->isModelWaiting());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+ // Simulate completion of DNSClient exchange by invoking the callback, as
+ // DNSClient would. This should cause the state model to progress through
+ // completion.
+ EXPECT_NO_THROW((*name_change)(DNSClient::SUCCESS));
+
+ // The model should have worked through to completion.
+ // Verify that the model is done and not failed.
+ EXPECT_TRUE(name_change->isModelDone());
+ EXPECT_FALSE(name_change->didModelFail());
+
+ // Verify that NCR status is completed, and that the forward change
+ // was completed.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_change->getNcrStatus());
+ EXPECT_TRUE(name_change->getForwardChangeCompleted());
+}
+
+/// @brief Tests the ability to use startTransaction to initiate the state
+/// model execution, and DNSClient callback, operator(), to resume the
+/// model with a update failure outcome.
+TEST_F(NameChangeTransactionTest, failedUpdateTest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Launch the transaction by calling startTransaction. The state model
+ // should run up until the "IO" operation is initiated in DOING_UPDATE_ST.
+ ASSERT_NO_THROW(name_change->startTransaction());
+
+ // Verify that the model is running but waiting, and that the forward
+ // change has not been completed.
+ EXPECT_TRUE(name_change->isModelRunning());
+ EXPECT_TRUE(name_change->isModelWaiting());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+
+ // Simulate completion of DNSClient exchange by invoking the callback, as
+ // DNSClient would. This should cause the state model to progress through
+ // to completion.
+ EXPECT_NO_THROW((*name_change)(DNSClient::TIMEOUT));
+
+ // The model should have worked through to completion.
+ // Verify that the model is done and not failed.
+ EXPECT_TRUE(name_change->isModelDone());
+ EXPECT_FALSE(name_change->didModelFail());
+
+ // Verify that the NCR status is failed and that the forward change
+ // was not completed.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_change->getNcrStatus());
+ EXPECT_FALSE(name_change->getForwardChangeCompleted());
+}
+
+/// @brief Tests update attempt accessors.
+TEST_F(NameChangeTransactionTest, updateAttempts) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Post transaction construction, update attempts should be 0.
+ EXPECT_EQ(0, name_change->getUpdateAttempts());
+
+ // Set it to a known value.
+ name_change->setUpdateAttempts(5);
+
+ // Verify that the value is as expected.
+ EXPECT_EQ(5, name_change->getUpdateAttempts());
+
+ // Clear it.
+ name_change->clearUpdateAttempts();
+
+ // Verify that it was cleared as expected.
+ EXPECT_EQ(0, name_change->getUpdateAttempts());
+}
+
+/// @brief Tests retryTransition method
+///
+/// Verifies that while the maximum number of update attempts has not
+/// been exceeded, the method will leave the state unchanged but post a
+/// SERVER_SELECTED_EVT. Once the maximum is exceeded, the method should
+/// transition to the state given with a next event of SERVER_IO_ERROR_EVT.
+TEST_F(NameChangeTransactionTest, retryTransition) {
+ // Create the transaction.
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+
+ // Define dictionaries.
+ ASSERT_NO_THROW(name_change->initDictionaries());
+
+ // Transition to a known spot.
+ ASSERT_NO_THROW(name_change->transition(
+ NameChangeStub::DOING_UPDATE_ST,
+ NameChangeStub::SEND_UPDATE_EVT));
+
+ // Verify we are at the known spot.
+ ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeStub::SEND_UPDATE_EVT,
+ name_change->getNextEvent());
+
+ // Verify that we have not exceeded maximum number of attempts.
+ ASSERT_LT(name_change->getUpdateAttempts(),
+ NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER);
+
+ // Call retryTransition.
+ ASSERT_NO_THROW(name_change->retryTransition(
+ NameChangeTransaction::PROCESS_TRANS_FAILED_ST));
+
+ // Since the number of update attempts is less than the maximum allowed
+ // we should remain in our current state but with next event of
+ // SERVER_SELECTED_EVT posted.
+ ASSERT_EQ(NameChangeStub::DOING_UPDATE_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_change->getNextEvent());
+
+ // Now set the number of attempts to the maximum.
+ name_change->setUpdateAttempts(NameChangeTransaction::
+ MAX_UPDATE_TRIES_PER_SERVER);
+ // Call retryTransition.
+ ASSERT_NO_THROW(name_change->retryTransition(
+ NameChangeTransaction::PROCESS_TRANS_FAILED_ST));
+
+ // Since we have exceeded maximum attempts, we should transition to
+ // PROCESS_UPDATE_FAILED_ST with a next event of SERVER_IO_ERROR_EVT.
+ ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_change->getNextEvent());
+}
+
+/// @brief Tests sendUpdate method when underlying doUpdate throws.
+///
+/// DNSClient::doUpdate can throw for a variety of reasons. This tests
+/// sendUpdate handling of such a throw by passing doUpdate a request
+/// that will not render.
+TEST_F(NameChangeTransactionTest, sendUpdateDoUpdateFailure) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Set the transaction's request to an empty DNS update.
+ D2UpdateMessagePtr req;
+ ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
+ ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+
+ // Verify that sendUpdate does not throw, but it should fail because
+ // the request won't render.
+ ASSERT_NO_THROW(name_change->sendUpdate());
+
+ // Verify that we transition to failed state and event.
+ ASSERT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_change->getCurrState());
+ ASSERT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_change->getNextEvent());
+}
+
+/// @brief Tests sendUpdate method when underlying doUpdate times out.
+TEST_F(NameChangeTransactionTest, sendUpdateTimeout) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Build a valid request, call sendUpdate and process the response.
+ // Note we have to wait for DNSClient timeout plus a bit more to allow
+ // DNSClient to timeout.
+ // The method, doOneExchange, can suffer fatal assertions which invalidate
+ // not only it but the invoking test as well. In other words, if the
+ // doOneExchange blows up the rest of test is pointless. I use
+ // ASSERT_NO_FATAL_FAILURE to abort the test immediately.
+
+ D2ParamsPtr d2_params = cfg_mgr_->getD2Params();
+ size_t timeout = d2_params->getDnsServerTimeout() + 100;
+
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change, timeout));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is TIMEOUT.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+ ASSERT_EQ(DNSClient::TIMEOUT, name_change->getDnsUpdateStatus());
+}
+
+/// @brief Tests sendUpdate method when it receives a corrupt response from
+/// the server.
+TEST_F(NameChangeTransactionTest, sendUpdateCorruptResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive(FauxServer::CORRUPT_RESP);
+
+ // Build a valid request, call sendUpdate and process the response.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is INVALID.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+ ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
+}
+
+/// @brief Tests sendUpdate method when the exchange succeeds.
+TEST_F(NameChangeTransactionTest, sendUpdate) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Build a valid request, call sendUpdate and process the response.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+ ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
+
+ // Verify that we have a response and it's Rcode is NOERROR,
+ // and the zone is as expected.
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ D2ZonePtr zone = response->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("response.example.com.", zone->getName().toText());
+}
+
+/// @brief Tests that an unsigned response to a signed request is an error
+TEST_F(NameChangeTransactionTest, tsigUnsignedResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one"));
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Do the update.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is
+ // INVALID_RESPONSE.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
+
+ // When TSIG errors occur, only the message header (including Rcode) is
+ // unpacked. In this case, it should be NOERROR but have no other
+ // information.
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ EXPECT_FALSE(response->getZone());
+}
+
+/// @brief Tests that a response signed with the wrong key is an error
+TEST_F(NameChangeTransactionTest, tsigInvalidResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction("key_one"));
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server, tell it to sign responses with a "random" key,
+ // then start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR());
+
+ // Do the update.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is
+ // INVALID_RESPONSE.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::INVALID_RESPONSE, name_change->getDnsUpdateStatus());
+
+ // When TSIG errors occur, only the message header (including Rcode) is
+ // unpacked. In this case, it should be NOERROR but have no other
+ // information.
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ EXPECT_FALSE(response->getZone());
+}
+
+/// @brief Tests that a signed response to an unsigned request is ok.
+/// Currently our policy is to accept a signed response to an unsigned request
+/// even though the spec says a server MUST not do that.
+TEST_F(NameChangeTransactionTest, tsigUnexpectedSignedResponse) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server, tell it to sign responses with a "random" key,
+ // then start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ server.receive (FauxServer::INVALID_TSIG, dns::Rcode::NOERROR());
+
+ // Perform an update without TSIG.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
+
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(), response->getRcode().getCode());
+ D2ZonePtr zone = response->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("response.example.com.", zone->getName().toText());
+}
+
+/// @brief Tests that a TSIG update succeeds when client and server both use
+/// the right key. Runs the test for all supported algorithms.
+TEST_F(NameChangeTransactionTest, tsigAllValid) {
+ std::vector<std::string>algorithms;
+ algorithms.push_back(TSIGKeyInfo::HMAC_MD5_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA1_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA224_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA256_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA384_STR);
+ algorithms.push_back(TSIGKeyInfo::HMAC_SHA512_STR);
+
+ for (int i = 0; i < algorithms.size(); ++i) {
+ SCOPED_TRACE (algorithms[i]);
+ TSIGKeyInfoPtr key;
+ ASSERT_NO_THROW(key.reset(new TSIGKeyInfo("test_key",
+ algorithms[i],
+ "GWG/Xfbju4O2iXGqkSu4PQ==")));
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction(key));
+ ASSERT_NO_THROW(name_change->initDictionaries());
+ ASSERT_TRUE(name_change->selectFwdServer());
+
+ // Create a server, set its TSIG key, and then start it listening.
+ FauxServer server(*io_service_, *(name_change->getCurrentServer()));
+ // Since we create a new server instance each time we need to tell
+ // it not reschedule receives automatically.
+ server.perpetual_receive_ = false;
+ server.setTSIGKey(key->getTSIGKey());
+ server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Do the update.
+ ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
+
+ // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
+ ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
+ name_change->getNextEvent());
+
+ ASSERT_EQ(DNSClient::SUCCESS, name_change->getDnsUpdateStatus());
+
+ D2UpdateMessagePtr response = name_change->getDnsUpdateResponse();
+ ASSERT_TRUE(response);
+ ASSERT_EQ(dns::Rcode::NOERROR().getCode(),
+ response->getRcode().getCode());
+ D2ZonePtr zone = response->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("response.example.com.", zone->getName().toText());
+ }
+}
+
+
+/// @brief Tests the prepNewRequest method
+TEST_F(NameChangeTransactionTest, prepNewRequest) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ D2UpdateMessagePtr request;
+
+ // prepNewRequest should fail on empty domain.
+ ASSERT_THROW(request = name_change->prepNewRequest(DdnsDomainPtr()),
+ NameChangeTransactionError);
+
+ // Verify that prepNewRequest fails on invalid zone name.
+ // @todo This test becomes obsolete if/when DdnsDomain enforces valid
+ // names as is done in dns::Name.
+ DdnsDomainPtr bsDomain = makeDomain(".badname","");
+ ASSERT_THROW(request = name_change->prepNewRequest(bsDomain),
+ NameChangeTransactionError);
+
+ // Verify that prepNewRequest properly constructs a message given
+ // valid input.
+ ASSERT_NO_THROW(request = name_change->prepNewRequest(forward_domain_));
+ checkZone(request, forward_domain_->getName());
+
+ // The query id is random so 0 is not impossible
+ for (unsigned i = 0; i < 10; ++i) {
+ if (request->getId() == 0) {
+ request = name_change->prepNewRequest(forward_domain_);
+ }
+ }
+
+ EXPECT_NE(0, request->getId());
+}
+
+/// @brief Tests the addLeaseAddressRData method
+TEST_F(NameChangeTransactionTest, addLeaseAddressRData) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+
+ // Verify we can add a lease RData to an valid RRset.
+ dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+ name_change->getAddressRRType(),
+ dns::RRTTL(0)));
+ ASSERT_NO_THROW(name_change->addLeaseAddressRdata(rrset));
+
+ // Verify the Rdata was added and the value is correct.
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+ EXPECT_EQ(ncr->getIpAddress(), rdata_it->getCurrent().toText());
+
+}
+
+/// @brief Tests the addDhcidRData method
+TEST_F(NameChangeTransactionTest, addDhcidRdata) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+
+ // Verify we can add a lease RData to an valid RRset.
+ dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ ASSERT_NO_THROW(name_change->addDhcidRdata(rrset));
+
+ // Verify the Rdata was added and the value is correct.
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+
+ const std::vector<uint8_t>& ncr_dhcid = ncr->getDhcid().getBytes();
+ util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size());
+ dns::rdata::in::DHCID rdata_ref(buffer, ncr_dhcid.size());
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+}
+
+/// @brief Tests the addPtrData method
+TEST_F(NameChangeTransactionTest, addPtrRdata) {
+ NameChangeStubPtr name_change;
+ ASSERT_NO_THROW(name_change = makeCannedTransaction());
+ dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
+
+ // Verify we can add a PTR RData to an valid RRset.
+ dns::RRsetPtr rrset (new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+ dns::RRType::PTR(), dns::RRTTL(0)));
+ ASSERT_NO_THROW(name_change->addPtrRdata(rrset));
+
+ // Verify the Rdata was added and the value is correct.
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+
+ EXPECT_EQ(ncr->getFqdn(), rdata_it->getCurrent().toText());
+}
+
+}; // anonymous namespace
diff --git a/src/lib/d2srv/tests/run_unittests.cc b/src/lib/d2srv/tests/run_unittests.cc
new file mode 100644
index 0000000..768319e
--- /dev/null
+++ b/src/lib/d2srv/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2012-2021 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 <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/d2srv/testutils/Makefile.am b/src/lib/d2srv/testutils/Makefile.am
new file mode 100644
index 0000000..c7ae2ad
--- /dev/null
+++ b/src/lib/d2srv/testutils/Makefile.am
@@ -0,0 +1,23 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libd2srvtest.la
+
+libd2srvtest_la_SOURCES = nc_test_utils.cc nc_test_utils.h
+libd2srvtest_la_SOURCES += stats_test_utils.cc stats_test_utils.h
+
+libd2srvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libd2srvtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libd2srvtest_la_LIBADD = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la
+libd2srvtest_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+
+endif
diff --git a/src/lib/d2srv/testutils/Makefile.in b/src/lib/d2srv/testutils/Makefile.in
new file mode 100644
index 0000000..c19494f
--- /dev/null
+++ b/src/lib/d2srv/testutils/Makefile.in
@@ -0,0 +1,876 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/d2srv/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libd2srvtest_la_DEPENDENCIES = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+am__libd2srvtest_la_SOURCES_DIST = nc_test_utils.cc nc_test_utils.h \
+ stats_test_utils.cc stats_test_utils.h
+@HAVE_GTEST_TRUE@am_libd2srvtest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libd2srvtest_la-nc_test_utils.lo \
+@HAVE_GTEST_TRUE@ libd2srvtest_la-stats_test_utils.lo
+libd2srvtest_la_OBJECTS = $(am_libd2srvtest_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 =
+libd2srvtest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \
+ $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libd2srvtest_la_rpath =
+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)/libd2srvtest_la-nc_test_utils.Plo \
+ ./$(DEPDIR)/libd2srvtest_la-stats_test_utils.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 = $(libd2srvtest_la_SOURCES)
+DIST_SOURCES = $(am__libd2srvtest_la_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libd2srvtest.la
+@HAVE_GTEST_TRUE@libd2srvtest_la_SOURCES = nc_test_utils.cc \
+@HAVE_GTEST_TRUE@ nc_test_utils.h stats_test_utils.cc \
+@HAVE_GTEST_TRUE@ stats_test_utils.h
+@HAVE_GTEST_TRUE@libd2srvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libd2srvtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libd2srvtest_la_LIBADD = $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+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/d2srv/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/d2srv/testutils/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libd2srvtest.la: $(libd2srvtest_la_OBJECTS) $(libd2srvtest_la_DEPENDENCIES) $(EXTRA_libd2srvtest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libd2srvtest_la_LINK) $(am_libd2srvtest_la_rpath) $(libd2srvtest_la_OBJECTS) $(libd2srvtest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libd2srvtest_la-stats_test_utils.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)$(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 $@ $<
+
+libd2srvtest_la-nc_test_utils.lo: nc_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libd2srvtest_la-nc_test_utils.lo -MD -MP -MF $(DEPDIR)/libd2srvtest_la-nc_test_utils.Tpo -c -o libd2srvtest_la-nc_test_utils.lo `test -f 'nc_test_utils.cc' || echo '$(srcdir)/'`nc_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srvtest_la-nc_test_utils.Tpo $(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_test_utils.cc' object='libd2srvtest_la-nc_test_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) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libd2srvtest_la-nc_test_utils.lo `test -f 'nc_test_utils.cc' || echo '$(srcdir)/'`nc_test_utils.cc
+
+libd2srvtest_la-stats_test_utils.lo: stats_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libd2srvtest_la-stats_test_utils.lo -MD -MP -MF $(DEPDIR)/libd2srvtest_la-stats_test_utils.Tpo -c -o libd2srvtest_la-stats_test_utils.lo `test -f 'stats_test_utils.cc' || echo '$(srcdir)/'`stats_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libd2srvtest_la-stats_test_utils.Tpo $(DEPDIR)/libd2srvtest_la-stats_test_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stats_test_utils.cc' object='libd2srvtest_la-stats_test_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) $(libd2srvtest_la_CPPFLAGS) $(CPPFLAGS) $(libd2srvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libd2srvtest_la-stats_test_utils.lo `test -f 'stats_test_utils.cc' || echo '$(srcdir)/'`stats_test_utils.cc
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libd2srvtest_la-nc_test_utils.Plo
+ -rm -f ./$(DEPDIR)/libd2srvtest_la-stats_test_utils.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-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)/libd2srvtest_la-nc_test_utils.Plo
+ -rm -f ./$(DEPDIR)/libd2srvtest_la-stats_test_utils.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/d2srv/testutils/nc_test_utils.cc b/src/lib/d2srv/testutils/nc_test_utils.cc
new file mode 100644
index 0000000..3903535
--- /dev/null
+++ b/src/lib/d2srv/testutils/nc_test_utils.cc
@@ -0,0 +1,987 @@
+// Copyright (C) 2013-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/udp_endpoint.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <util/encode/base64.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace d2 {
+
+const char* valid_d2_config = "{ "
+ "\"ip-address\" : \"127.0.0.1\" , "
+ "\"port\" : 5031, "
+ "\"tsig-keys\": ["
+ "{ \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "} ],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" } "
+ "] } ] }, "
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
+
+const char* TEST_DNS_SERVER_IP = "127.0.0.1";
+size_t TEST_DNS_SERVER_PORT = 5301;
+
+//const bool HAS_RDATA = true;
+const bool NO_RDATA = false;
+
+//*************************** FauxServer class ***********************
+
+FauxServer::FauxServer(asiolink::IOService& io_service,
+ asiolink::IOAddress& address, size_t port)
+ :io_service_(io_service), address_(address), port_(port),
+ server_socket_(), receive_pending_(false), perpetual_receive_(true),
+ tsig_key_() {
+
+ server_socket_.reset(new boost::asio::ip::udp::socket(io_service_.get_io_service(),
+ boost::asio::ip::udp::v4()));
+ server_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+
+ isc::asiolink::UDPEndpoint endpoint(address_, port_);
+ server_socket_->bind(endpoint.getASIOEndpoint());
+}
+
+FauxServer::FauxServer(asiolink::IOService& io_service,
+ DnsServerInfo& server)
+ :io_service_(io_service), address_(server.getIpAddress()),
+ port_(server.getPort()), server_socket_(), receive_pending_(false),
+ perpetual_receive_(true), tsig_key_() {
+ server_socket_.reset(new boost::asio::ip::udp::socket(io_service_.get_io_service(),
+ boost::asio::ip::udp::v4()));
+ server_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ isc::asiolink::UDPEndpoint endpoint(address_, port_);
+ server_socket_->bind(endpoint.getASIOEndpoint());
+}
+
+
+FauxServer::~FauxServer() {
+}
+
+void
+FauxServer::receive (const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode) {
+ if (receive_pending_) {
+ return;
+ }
+
+ receive_pending_ = true;
+ server_socket_->async_receive_from(boost::asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ remote_,
+ std::bind(&FauxServer::requestHandler,
+ this, ph::_1, ph::_2,
+ response_mode,
+ response_rcode));
+}
+
+void
+FauxServer::requestHandler(const boost::system::error_code& error,
+ std::size_t bytes_recvd,
+ const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode) {
+ receive_pending_ = false;
+ // If we encountered an error or received no data then fail.
+ // We expect the client to send good requests.
+ if (error.value() != 0 || bytes_recvd < 1) {
+ ADD_FAILURE() << "FauxServer receive failed: " << error.message();
+ return;
+ }
+
+ // If TSIG key isn't NULL, create a context and use to verify the
+ // request and sign the response.
+ dns::TSIGContextPtr context;
+ if (tsig_key_) {
+ context.reset(new dns::TSIGContext(*tsig_key_));
+ }
+
+ // We have a successfully received data. We need to turn it into
+ // a request in order to build a proper response.
+ // Note D2UpdateMessage is geared towards making requests and
+ // reading responses. This is the opposite perspective so we have
+ // to a bit of roll-your-own here.
+ dns::Message request(dns::Message::PARSE);
+ util::InputBuffer request_buf(receive_buffer_, bytes_recvd);
+ try {
+ request.fromWire(request_buf);
+
+ // If context is not NULL, then we need to verify the message.
+ if (context) {
+ dns::TSIGError error = context->verify(request.getTSIGRecord(),
+ receive_buffer_,
+ bytes_recvd);
+ if (error != dns::TSIGError::NOERROR()) {
+ isc_throw(TSIGVerifyError, "TSIG verification failed: "
+ << error.toText());
+ }
+ }
+ } catch (const std::exception& ex) {
+ // If the request cannot be parsed, then fail the test.
+ // We expect the client to send good requests.
+ ADD_FAILURE() << "FauxServer request is corrupt:" << ex.what();
+ return;
+ }
+
+ // The request parsed OK, so let's build a response.
+ // We must use the QID we received in the response or IOFetch will
+ // toss the response out, resulting in eventual timeout.
+ // We fill in the zone with data we know is from the "server".
+ dns::Message response(dns::Message::RENDER);
+ response.setQid(request.getQid());
+ dns::Question question(dns::Name("response.example.com"),
+ dns::RRClass::ANY(), dns::RRType::SOA());
+ response.addQuestion(question);
+ response.setOpcode(dns::Opcode(dns::Opcode::UPDATE_CODE));
+ response.setHeaderFlag(dns::Message::HEADERFLAG_QR, true);
+
+ // Set the response Rcode to value passed in, default is NOERROR.
+ response.setRcode(response_rcode);
+
+ // Render the response to a buffer.
+ dns::MessageRenderer renderer;
+ util::OutputBuffer response_buf(TEST_MSG_MAX);
+ renderer.setBuffer(&response_buf);
+
+ if (response_mode == INVALID_TSIG) {
+ // Create a different key to sign the response.
+ std::string secret ("key that doesn't match");
+ D2TsigKeyPtr key;
+ ASSERT_NO_THROW(key.reset(new
+ D2TsigKey(dns::Name("badkey"),
+ dns::TSIGKey::HMACMD5_NAME(),
+ secret.c_str(), secret.size())));
+ context.reset(new dns::TSIGContext(*key));
+ }
+
+ response.toWire(renderer, context.get());
+
+ // If mode is to ship garbage, then stomp on part of the rendered
+ // message.
+ if (response_mode == CORRUPT_RESP) {
+ response_buf.writeUint16At(0xFFFF, 2);
+ }
+
+ // Ship the response via synchronous send.
+ try {
+ int cnt = server_socket_->send_to(boost::asio::
+ buffer(response_buf.getData(),
+ response_buf.getLength()),
+ remote_);
+ // Make sure we sent what we expect to send.
+ if (cnt != response_buf.getLength()) {
+ ADD_FAILURE() << "FauxServer sent: " << cnt << " expected: "
+ << response_buf.getLength();
+ }
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "FauxServer send failed: " << ex.what();
+ }
+
+ if (perpetual_receive_) {
+ // Schedule the next receive
+ receive (response_mode, response_rcode);
+ }
+}
+
+
+
+//********************** TimedIO class ***********************
+
+TimedIO::TimedIO()
+ : io_service_(new isc::asiolink::IOService()),
+ timer_(*io_service_), run_time_(0) {
+}
+
+TimedIO::~TimedIO() {
+}
+
+int
+TimedIO::runTimedIO(int run_time) {
+ run_time_ = run_time;
+ int cnt = io_service_->get_io_service().poll();
+ if (cnt == 0) {
+ timer_.setup(std::bind(&TimedIO::timesUp, this), run_time_);
+ cnt = io_service_->get_io_service().run_one();
+ timer_.cancel();
+ }
+
+ return (cnt);
+}
+
+void
+TimedIO::timesUp() {
+ io_service_->stop();
+ FAIL() << "Test Time: " << run_time_ << " expired";
+}
+
+//********************** TransactionTest class ***********************
+
+const unsigned int TransactionTest::FORWARD_CHG = 0x01;
+const unsigned int TransactionTest::REVERSE_CHG = 0x02;
+const unsigned int TransactionTest::FWD_AND_REV_CHG = REVERSE_CHG | FORWARD_CHG;
+
+TransactionTest::TransactionTest() : ncr_(), cfg_mgr_(new D2CfgMgr()) {
+}
+
+TransactionTest::~TransactionTest() {
+}
+
+void
+TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info) {
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"my.forward.example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Create NameChangeRequest from JSON string.
+ ncr_ = dhcp_ddns::NameChangeRequest::fromJSON(msg_str);
+
+ // Set the change type.
+ ncr_->setChangeType(chg_type);
+
+ // If the change mask does not include a forward change clear the
+ // forward domain; otherwise create the domain and its servers.
+ if (!(change_mask & FORWARD_CHG)) {
+ ncr_->setForwardChange(false);
+ forward_domain_.reset();
+ } else {
+ // Create the forward domain and then its servers.
+ forward_domain_ = makeDomain("example.com.", tsig_key_info);
+ addDomainServer(forward_domain_, "forward.example.com",
+ "127.0.0.1", 5301, tsig_key_info);
+ addDomainServer(forward_domain_, "forward2.example.com",
+ "127.0.0.1", 5302, tsig_key_info);
+ }
+
+ // If the change mask does not include a reverse change clear the
+ // reverse domain; otherwise create the domain and its servers.
+ if (!(change_mask & REVERSE_CHG)) {
+ ncr_->setReverseChange(false);
+ reverse_domain_.reset();
+ } else {
+ // Create the reverse domain and its server.
+ reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.", tsig_key_info);
+ addDomainServer(reverse_domain_, "reverse.example.com",
+ "127.0.0.1", 5301, tsig_key_info);
+ addDomainServer(reverse_domain_, "reverse2.example.com",
+ "127.0.0.1", 5302, tsig_key_info);
+ }
+}
+
+void
+TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const std::string& key_name) {
+ setupForIPv4Transaction(chg_type, change_mask, makeTSIGKeyInfo(key_name));
+}
+
+void
+TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info) {
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"my6.forward.example.com.\" , "
+ " \"ip-address\" : \"2001:1::100\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Create NameChangeRequest from JSON string.
+ ncr_ = makeNcrFromString(msg_str);
+
+ // Set the change type.
+ ncr_->setChangeType(chg_type);
+
+ // If the change mask does not include a forward change clear the
+ // forward domain; otherwise create the domain and its servers.
+ if (!(change_mask & FORWARD_CHG)) {
+ ncr_->setForwardChange(false);
+ forward_domain_.reset();
+ } else {
+ // Create the forward domain and then its servers.
+ forward_domain_ = makeDomain("example.com.", tsig_key_info);
+ addDomainServer(forward_domain_, "fwd6-server.example.com",
+ "::1", 5301, tsig_key_info);
+ addDomainServer(forward_domain_, "fwd6-server2.example.com",
+ "::1", 5302, tsig_key_info);
+ }
+
+ // If the change mask does not include a reverse change clear the
+ // reverse domain; otherwise create the domain and its servers.
+ if (!(change_mask & REVERSE_CHG)) {
+ ncr_->setReverseChange(false);
+ reverse_domain_.reset();
+ } else {
+ // Create the reverse domain and its server.
+ reverse_domain_ = makeDomain("1.2001.ip6.arpa.", tsig_key_info);
+ addDomainServer(reverse_domain_, "rev6-server.example.com",
+ "::1", 5301, tsig_key_info);
+ addDomainServer(reverse_domain_, "rev6-server2.example.com",
+ "::1", 5302, tsig_key_info);
+ }
+}
+
+void
+TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
+ int change_mask,
+ const std::string& key_name) {
+ setupForIPv6Transaction(chg_type, change_mask, makeTSIGKeyInfo(key_name));
+}
+
+
+//********************** Functions ****************************
+
+void
+checkRRCount(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section, int count) {
+ dns::RRsetIterator rrset_it = request->beginSection(section);
+ dns::RRsetIterator rrset_end = request->endSection(section);
+
+ ASSERT_EQ(count, std::distance(rrset_it, rrset_end));
+}
+
+void
+checkZone(const D2UpdateMessagePtr& request, const std::string& exp_zone_name) {
+ // Verify the zone section info.
+ D2ZonePtr zone = request->getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ(exp_zone_name, zone->getName().toText());
+ EXPECT_EQ(dns::RRClass::IN().getCode(), zone->getClass().getCode());
+}
+
+void
+checkRR(dns::RRsetPtr rrset, const std::string& exp_name,
+ const dns::RRClass& exp_class, const dns::RRType& exp_type,
+ unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr,
+ bool has_rdata) {
+ // Verify the FQDN/DHCID RR fields.
+ EXPECT_EQ(exp_name, rrset->getName().toText());
+ EXPECT_EQ(exp_class.getCode(), rrset->getClass().getCode());
+ EXPECT_EQ(exp_type.getCode(), rrset->getType().getCode());
+ EXPECT_EQ(exp_ttl, rrset->getTTL().getValue());
+ if ((!has_rdata) ||
+ (exp_type == dns::RRType::ANY() || exp_class == dns::RRClass::ANY())) {
+ // ANY types do not have RData
+ ASSERT_EQ(0, rrset->getRdataCount());
+ return;
+ }
+
+ ASSERT_EQ(1, rrset->getRdataCount());
+ dns::RdataIteratorPtr rdata_it = rrset->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+
+ if ((exp_type == dns::RRType::A()) ||
+ (exp_type == dns::RRType::AAAA())) {
+ // should have lease rdata
+ EXPECT_EQ(ncr->getIpAddress(), rdata_it->getCurrent().toText());
+ } else if (exp_type == dns::RRType::PTR()) {
+ // should have PTR rdata
+ EXPECT_EQ(ncr->getFqdn(), rdata_it->getCurrent().toText());
+ } else if (exp_type == dns::RRType::DHCID()) {
+ // should have DHCID rdata
+ const std::vector<uint8_t>& ncr_dhcid = ncr->getDhcid().getBytes();
+ util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size());
+ dns::rdata::in::DHCID rdata_ref(buffer, ncr_dhcid.size());
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+ } else {
+ // we got a problem
+ FAIL();
+ }
+}
+
+dns::RRsetPtr
+getRRFromSection(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section, int index) {
+ dns::RRsetIterator rrset_it = request->beginSection(section);
+ dns::RRsetIterator rrset_end = request->endSection(section);
+
+ if (std::distance(rrset_it, rrset_end) <= index) {
+ // Return an empty pointer if index is out of range.
+ return (dns::RRsetPtr());
+ }
+
+ std::advance(rrset_it, index);
+ return (*rrset_it);
+}
+
+dhcp_ddns::NameChangeRequestPtr
+makeNcrFromString(const std::string& ncr_str) {
+ return (dhcp_ddns::NameChangeRequest::fromJSON(ncr_str));
+}
+
+DdnsDomainPtr
+makeDomain(const std::string& zone_name, const std::string& key_name) {
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ DdnsDomainPtr domain(new DdnsDomain(zone_name, servers, key_name));
+ return (domain);
+}
+
+DdnsDomainPtr
+makeDomain(const std::string& zone_name, const TSIGKeyInfoPtr &tsig_key_info) {
+ DdnsDomainPtr domain;
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ std::string key_name;
+ if (tsig_key_info) {
+ key_name = tsig_key_info->getName();
+ }
+ domain.reset(new DdnsDomain(zone_name, servers, key_name));
+ return (domain);
+}
+
+TSIGKeyInfoPtr
+makeTSIGKeyInfo(const std::string& key_name, const std::string& secret,
+ const std::string& algorithm) {
+ TSIGKeyInfoPtr key_info;
+ if (!key_name.empty()) {
+ if (!secret.empty()) {
+ key_info.reset(new TSIGKeyInfo(key_name, algorithm, secret));
+ } else {
+ // Since secret was left blank, we'll convert key_name into a
+ // base64 encoded string and use that.
+ const uint8_t* bytes = reinterpret_cast<const uint8_t*>
+ (key_name.c_str());
+ size_t len = key_name.size();
+ const vector<uint8_t> key_name_v(bytes, bytes + len);
+ std::string key_name64
+ = isc::util::encode::encodeBase64(key_name_v);
+
+ // Now, make the TSIGKeyInfo with a real base64 secret.
+ key_info.reset(new TSIGKeyInfo(key_name, algorithm, key_name64));
+ }
+ }
+
+ return (key_info);
+}
+
+void
+addDomainServer(DdnsDomainPtr& domain, const std::string& name,
+ const std::string& ip, const size_t port,
+ const TSIGKeyInfoPtr &tsig_key_info) {
+ DnsServerInfoPtr server(new DnsServerInfo(name, asiolink::IOAddress(ip),
+ port, true, tsig_key_info));
+ domain->getServers()->push_back(server);
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for adding a forward DNS entry
+void
+checkAddFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify the PREREQUISITE SECTION
+ // Should be 1 which tests for FQDN does not exist.
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::ANY(), 0, ncr);
+
+ // Verify the UPDATE SECTION
+ // Should be 2 RRs: 1 to add the FQDN/IP and one to add the DHCID RR
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // First, Verify the FQDN/IP add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
+
+ // Now, verify the DHCID add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for replacing a forward DNS entry
+void
+checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify the PREREQUISITE SECTION
+ // Should be 2, 1 which tests for FQDN does exist and the other
+ // checks for a matching DHCID.
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 2);
+
+ // Verify the FQDN test RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::ANY(), 0, ncr);
+
+ // Verify the DHCID test RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), 0, ncr);
+
+ // Verify the UPDATE SECTION
+ // Should be 2, 1 which deletes the existing FQDN/IP and one that
+ // adds the new one.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // Verify the FQDN delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+ // Verify the FQDN/IP add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for replacing a reverse DNS entry
+void
+checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getReverseDomain()->getName();
+ std::string exp_rev_addr = D2CfgMgr::reverseIpAddress(ncr->getIpAddress());
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there are no RRs in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // Verify the UPDATE Section.
+ // It should contain 4 RRs:
+ // 1. A delete all PTR RRs for the given IP
+ // 2. A delete of all DHCID RRs for the given IP
+ // 3. An add of the new PTR RR
+ // 4. An add of the new DHCID RR
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 4);
+
+ // Verify the PTR delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(),
+ 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify the PTR add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 2));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
+ ttl, ncr);
+
+ // Verify the DHCID add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 3));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::DHCID(),
+ ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkRemoveFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there is 1 RR in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+
+ // Verify the DHCID matching assertion RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify there is 1 RR in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+ // Verify the FQDN/IP delete RR.
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), exp_ip_rr_type,
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkRemoveFwdRRsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there is 1 RR in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 3);
+
+ // Verify the DHCID matches assertion.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify the NO A RRs assertion.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::A(),
+ 0, ncr, NO_RDATA);
+
+ // Verify the NO AAAA RRs assertion.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 2));
+ checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::AAAA(),
+ 0, ncr, NO_RDATA);
+
+ // Verify there is 1 RR in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+ // Verify the delete all for the FQDN RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::ANY(),
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkRemoveRevPtrsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getReverseDomain()->getName();
+ std::string exp_rev_addr = D2CfgMgr::reverseIpAddress(ncr->getIpAddress());
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there is 1 RR in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+
+ // Verify the FQDN-PTRNAME assertion RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_PREREQUISITE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
+ 0, ncr);
+
+ // Verify there is 1 RR in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+ // Verify the delete all for the FQDN RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::ANY(),
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+std::string
+toHexText(const uint8_t* data, size_t len) {
+ std::ostringstream stream;
+ stream << "Data length is: " << len << std::endl;
+ for (int i = 0; i < len; ++i) {
+ if (i > 0 && ((i % 16) == 0)) {
+ stream << std::endl;
+ }
+
+ stream << setfill('0') << setw(2) << setbase(16)
+ << static_cast<unsigned int>(data[i]) << " ";
+ }
+
+ return (stream.str());
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for replacing a forward DNS entry when not using conflict
+// resolution.
+void
+checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify the PREREQUISITE SECTION
+ // There should be no prerequisites.
+ dns::RRsetPtr rrset;
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Verify the UPDATE SECTION
+ // Should be 4
+ // 1. delete of the FQDN/IP RR
+ // 2. delete of the DHCID RR
+ // 3. add of the FQDN/IP RR
+ // 4. add of the DHCID RR
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 4);
+
+ // Fetch ttl.
+ uint32_t ttl = ncr->getLeaseLength();
+
+ // Verify the FQDN delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify the FQDN/IP add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 2));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
+
+ // Now, verify the DHCID add RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 3));
+ checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+ ttl, ncr);
+
+ // Verify there are no RRs in the ADDITIONAL Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void
+checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getForwardDomain()->getName();
+ std::string exp_fqdn = ncr->getFqdn();
+ const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there no prerequisites.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Verify there are 2 RRs in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Verify the FQDN delete RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), exp_ip_rr_type, 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::DHCID(),
+ 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies that the contents of the given transaction's DNS update request
+// is correct for removing a reverse DNS entry when not using conflict
+// resolution.
+void
+checkSimpleRemoveRevPtrsRequest(NameChangeTransaction& tran) {
+ const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+ ASSERT_TRUE(request);
+
+ // Safety check.
+ dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+ ASSERT_TRUE(ncr);
+
+ std::string exp_zone_name = tran.getReverseDomain()->getName();
+ std::string exp_rev_addr = D2CfgMgr::reverseIpAddress(ncr->getIpAddress());
+
+ // Verify the zone section.
+ checkZone(request, exp_zone_name);
+
+ // Verify there are no RRs in the PREREQUISITE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
+
+ // Verify there is 2 RRs in the UPDATE Section.
+ checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
+
+ // Verify the FQDN delete RR.
+ dns::RRsetPtr rrset;
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 0));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::PTR(), 0, ncr);
+
+ // Verify the DHCID delete RR.
+ ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+ SECTION_UPDATE, 1));
+ checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::DHCID(), 0, ncr);
+
+ // Verify that it will render toWire without throwing.
+ dns::MessageRenderer renderer;
+ ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+// Verifies the current state and next event in a transaction
+void
+checkContext(NameChangeTransactionPtr trans, const int exp_state,
+ const int exp_evt, const std::string& file, int line) {
+ ASSERT_TRUE(trans);
+ ASSERT_TRUE(exp_state == trans->getCurrState() && exp_evt == trans->getNextEvent())
+ << "expected state: " << trans->getStateLabel(exp_state)
+ << " event: " << trans->getEventLabel(exp_evt)
+ << ", actual context: " << trans->getContextStr()
+ << " at " << file << ":" << line;
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/lib/d2srv/testutils/nc_test_utils.h b/src/lib/d2srv/testutils/nc_test_utils.h
new file mode 100644
index 0000000..73b6287
--- /dev/null
+++ b/src/lib/d2srv/testutils/nc_test_utils.h
@@ -0,0 +1,497 @@
+// Copyright (C) 2013-2021 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 NC_TEST_UTILS_H
+#define NC_TEST_UTILS_H
+
+/// @file nc_test_utils.h prototypes for functions related transaction testing.
+
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <d2srv/d2_update_message.h>
+#include <d2srv/nc_trans.h>
+
+#include <boost/asio/ip/udp.hpp>
+#include <boost/asio/socket_base.hpp>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace d2 {
+
+extern const char* valid_d2_config;
+extern const char* TEST_DNS_SERVER_IP;
+extern size_t TEST_DNS_SERVER_PORT;
+
+// Not extern'ed to allow use as array size
+const int TEST_MSG_MAX = 1024;
+
+typedef boost::shared_ptr<boost::asio::ip::udp::socket> SocketPtr;
+
+/// @brief This class simulates a DNS server. It is capable of performing
+/// an asynchronous read, governed by an IOService, and responding to received
+/// requests in a given manner.
+class FauxServer {
+public:
+ /// @brief The types of response generated by the server.
+ enum ResponseMode {
+ USE_RCODE, // Generate a response with a given RCODE
+ CORRUPT_RESP, // Generate a corrupt response
+ INVALID_TSIG // Generate a response with the wrong TSIG key
+ };
+
+ /// @brief Reference to IOService to use for IO processing.
+ asiolink::IOService& io_service_;
+
+ /// @brief IP address at which to listen for requests.
+ const asiolink::IOAddress& address_;
+
+ /// @brief Port on which to listen for requests.
+ size_t port_;
+
+ /// @brief Socket on which listening is done.
+ SocketPtr server_socket_;
+
+ /// @brief Stores the end point of requesting client.
+ boost::asio::ip::udp::endpoint remote_;
+
+ /// @brief Buffer in which received packets are stuffed.
+ uint8_t receive_buffer_[TEST_MSG_MAX];
+
+ /// @brief Flag which indicates if a receive has been initiated but not yet
+ /// completed.
+ bool receive_pending_;
+ /// @brief Flag which indicates if server is in perpetual receive mode.
+ /// If true once a receive has been completed, a new one will be
+ /// automatically initiated.
+ bool perpetual_receive_;
+
+ /// @brief TSIG Key to use to verify requests and sign responses. If it is
+ /// NULL TSIG is not used.
+ D2TsigKeyPtr tsig_key_;
+
+ /// @brief Constructor
+ ///
+ /// @param io_service IOService to be used for socket IO.
+ /// @param address IP address at which the server should listen.
+ /// @param port Port number at which the server should listen.
+ FauxServer(asiolink::IOService& io_service, asiolink::IOAddress& address,
+ size_t port);
+
+ /// @brief Constructor
+ ///
+ /// @param io_service IOService to be used for socket IO.
+ /// @param server DnsServerInfo of server the DNS server. This supplies the
+ /// server's ip address and port.
+ FauxServer(asiolink::IOService& io_service, DnsServerInfo& server);
+
+ /// @brief Destructor
+ virtual ~FauxServer();
+
+ /// @brief Initiates an asynchronous receive
+ ///
+ /// Starts the server listening for requests. Upon completion of the
+ /// listen, the callback method, requestHandler, is invoked.
+ ///
+ /// @param response_mode Selects how the server responds to a request
+ /// @param response_rcode The Rcode value set in the response. Not used
+ /// for all modes.
+ void receive (const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode=dns::Rcode::NOERROR());
+
+ /// @brief Socket IO Completion callback
+ ///
+ /// This method servers as the Server's UDP socket receive callback handler.
+ /// When the receive completes the handler is invoked with the parameters
+ /// listed.
+ ///
+ /// @param error result code of the receive (determined by asio layer)
+ /// @param bytes_recvd number of bytes received, if any
+ /// @param response_mode type of response the handler should produce
+ /// @param response_rcode value of Rcode in the response constructed by
+ /// handler
+ void requestHandler(const boost::system::error_code& error,
+ std::size_t bytes_recvd,
+ const ResponseMode& response_mode,
+ const dns::Rcode& response_rcode);
+
+ /// @brief Returns true if a receive has been started but not completed.
+ bool isReceivePending() {
+ return receive_pending_;
+ }
+
+ /// @brief Sets the TSIG key to the given value.
+ ///
+ /// @param tsig_key Pointer to the TSIG key to use. If the pointer is
+ /// empty, TSIG will not be used.
+ void setTSIGKey(const D2TsigKeyPtr& tsig_key) {
+ tsig_key_ = tsig_key;
+ }
+};
+
+/// @brief Provides a means to process IOService IO for a finite amount of time.
+///
+/// This class instantiates an IOService provides a single method, runTimedIO
+/// which will run the IOService for no more than a finite amount of time,
+/// at least one event is executed or the IOService is stopped.
+/// It provides an virtual handler for timer expiration event. It is
+/// intended to be used as a base class for test fixtures that need to process
+/// IO by providing them a consistent way to do so while retaining a safety
+/// valve so tests do not hang.
+class TimedIO {
+public:
+ asiolink::IOServicePtr io_service_;
+ asiolink::IntervalTimer timer_;
+ int run_time_;
+
+ /// @brief Constructor
+ TimedIO();
+
+ /// @brief Destructor
+ virtual ~TimedIO();
+
+ /// @brief IO Timer expiration handler
+ ///
+ /// Stops the IOService and fails the current test.
+ virtual void timesUp();
+
+ /// @brief Processes IO till time expires or at least one handler executes.
+ ///
+ /// This method first polls IOService to run any ready handlers. If no
+ /// handlers are ready, it starts the internal time to run for the given
+ /// amount of time and invokes service's run_one method. This method
+ /// blocks until at least one handler executes or the IO Service is stopped.
+ /// Upon completion of this method the timer is cancelled. Should the
+ /// timer expires prior to run_one returning, the timesUp handler will be
+ /// invoked which stops the IO service and fails the test.
+ ///
+ /// Note that this method closely mimics the runIO method in D2Process.
+ ///
+ /// @param run_time maximum length of time to run in milliseconds before
+ /// timing out.
+ ///
+ /// @return Returns the number of handlers executed or zero. A return of
+ /// zero indicates that the IOService has been stopped.
+ int runTimedIO(int run_time);
+
+};
+
+/// @brief Base class Test fixture for testing transactions.
+class TransactionTest : public TimedIO, public ::testing::Test {
+public:
+ dhcp_ddns::NameChangeRequestPtr ncr_;
+ DdnsDomainPtr forward_domain_;
+ DdnsDomainPtr reverse_domain_;
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief constants used to specify change directions for a transaction.
+ static const unsigned int FORWARD_CHG; // Only forward change.
+ static const unsigned int REVERSE_CHG; // Only reverse change.
+ static const unsigned int FWD_AND_REV_CHG; // Both forward and reverse.
+
+ TransactionTest();
+ virtual ~TransactionTest();
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both
+ /// domains in the transaction. This will cause the transaction to
+ /// use TSIG. If the pointer is empty, TSIG will not be used.
+ void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info =
+ TSIGKeyInfoPtr());
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// Convenience wrapper around the above method which accepts a string
+ /// key_name from which the TSIGKeyInfo is constructed. Note the string
+ /// may not be blank.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param key_name value to use to create TSIG key. The value may not
+ /// be blank.
+ void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask, const std::string& key_name);
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param tsig_key_info pointer to the TSIGKeyInfo to assign to both
+ /// domains in the transaction. This will cause the transaction to
+ /// use TSIG. If the pointer is empty, TSIG will not be used.
+ void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask,
+ const TSIGKeyInfoPtr& tsig_key_info =
+ TSIGKeyInfoPtr());
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// Convenience wrapper around the above method which accepts a string
+ /// key_name from which the TSIGKeyInfo is constructed. Note the string
+ /// may not be blank.
+ ///
+ /// @param change_type selects the type of change requested, CHG_ADD or
+ /// CHG_REMOVE.
+ /// @param change_mask determines which change directions are requested
+ /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+ /// @param key_name value to use to create TSIG key, if blank TSIG will not
+ /// be used.
+ void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type,
+ int change_mask, const std::string& key_name);
+};
+
+
+/// @brief Tests the number of RRs in a request section against a given count.
+///
+/// This function actually returns the number of RRsetPtrs in a section. Since
+/// D2 only uses RRsets with a single RData in each (i.e. 1 RR), it is used
+/// as the number of RRs. The dns::Message::getRRCount() cannot be used for
+/// this as it returns the number of RDatas in an RRSet which does NOT equate
+/// to the number of RRs. RRs with no RData, those with class or type of ANY,
+/// are not counted.
+///
+/// @param request DNS update request to test
+/// @param section enum value of the section to count
+/// @param count the expected number of RRs
+extern void checkRRCount(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section, int count);
+
+/// @brief Tests the zone content of a given request.
+///
+/// @param request DNS update request to validate
+/// @param exp_zone_name expected value of the zone name in the zone section
+extern void checkZone(const D2UpdateMessagePtr& request,
+ const std::string& exp_zone_name);
+
+/// @brief Tests the contents of an RRset
+///
+/// @param rrset Pointer the RRset to test
+/// @param exp_name expected value of RRset name (FQDN or reverse ip)
+/// @param exp_class expected RRClass value of RRset
+/// @param exp_typ expected RRType value of RRset
+/// @param exp_ttl expected TTL value of RRset
+/// @param ncr NameChangeRequest on which the RRset is based
+/// @param has_rdata if true, RRset's rdata will be checked based on it's
+/// RRType. Set this to false if the RRset's type supports Rdata but it does
+/// not contain it. For instance, prerequisites of type NONE have no Rdata
+/// where updates of type NONE may.
+extern void checkRR(dns::RRsetPtr rrset, const std::string& exp_name,
+ const dns::RRClass& exp_class, const dns::RRType& exp_type,
+ unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr,
+ bool has_rdata=true);
+
+/// @brief Fetches an RR(set) from a given section of a request
+///
+/// @param request DNS update request from which the RR should come
+/// @param section enum value of the section from which the RR should come
+/// @param index zero-based index of the RR of interest.
+///
+/// @return Pointer to the RR of interest, empty pointer if the index is out
+/// of range.
+extern dns::RRsetPtr getRRFromSection(const D2UpdateMessagePtr& request,
+ D2UpdateMessage::UpdateMsgSection section,
+ int index);
+/// @brief Creates a NameChangeRequest from a JSON string
+///
+/// @param ncr_str JSON string form of a NameChangeRequest. Example:
+/// @code
+/// const char* msg_str =
+/// "{"
+/// " \"change-type\" : 0 , "
+/// " \"forward-change\" : true , "
+/// " \"reverse-change\" : true , "
+/// " \"fqdn\" : \"my.example.com.\" , "
+/// " \"ip-address\" : \"192.168.2.1\" , "
+/// " \"dhcid\" : \"0102030405060708\" , "
+/// " \"lease-expires-on\" : \"20130121132405\" , "
+/// " \"lease-length\" : 1300 "
+/// "}";
+///
+/// @endcode
+
+/// @brief Verifies a forward mapping addition DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// adding a forward DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkAddFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward mapping replacement DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// replacing a forward DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkReplaceFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a reverse mapping replacement DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// replacing a reverse DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkReplaceRevPtrsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward address removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing the forward address DNS entry.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing forward RR DNS entries.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveFwdRRsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a reverse mapping removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing a reverse DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveRevPtrsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a simple forward mapping replacement DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// replacing a forward DNS mapping when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleReplaceFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a simple forward RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing forward RR DNS entries when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleRemoveFwdRRsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a simple reverse RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing reverse RR DNS entries when not using conflict resolution.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkSimpleRemoveRevPtrsRequest(NameChangeTransaction& tran);
+
+/// @brief Creates a NameChangeRequest from JSON string.
+///
+/// @param ncr_str string of JSON text from which to make the request.
+///
+/// @return Pointer to newly created request.
+///
+/// @throw Underlying methods may throw.
+extern
+dhcp_ddns::NameChangeRequestPtr makeNcrFromString(const std::string& ncr_str);
+
+/// @brief Creates a DdnsDomain with the one server.
+///
+/// @param zone_name zone name of the domain
+/// @param key_name TSIG key name of the TSIG key for this domain. It will
+/// create a TSIGKeyInfo based on the key_name and assign it to the domain.
+///
+/// @throw Underlying methods may throw.
+extern DdnsDomainPtr makeDomain(const std::string& zone_name,
+ const std::string& key_name);
+
+/// @brief Creates a DdnsDomain with the one server.
+///
+/// @param zone_name zone name of the domain
+/// @param tsig_key_info pointer to the TSIGInfog key for this domain.
+/// Defaults to an empty pointer, meaning this domain has no key.
+///
+/// @throw Underlying methods may throw.
+extern DdnsDomainPtr makeDomain(const std::string& zone_name,
+ const TSIGKeyInfoPtr&
+ tsig_key_info = TSIGKeyInfoPtr());
+
+/// @brief Creates a TSIGKeyInfo
+///
+/// @param key_name name of the key
+/// @param secret key secret data as a base64 encoded string. If blank,
+/// then the secret value will be generated from key_name.
+/// @param algorithm algorithm to use. Defaults to MD5.
+/// @return a TSIGKeyInfoPtr for the newly created key. If key_name is blank
+/// the pointer will be empty.
+/// @throw Underlying methods may throw.
+extern
+TSIGKeyInfoPtr makeTSIGKeyInfo(const std::string& key_name,
+ const std::string& secret = "",
+ const std::string& algorithm
+ = TSIGKeyInfo::HMAC_MD5_STR);
+
+/// @brief Creates a DnsServerInfo and adds it to the given DdnsDomain.
+///
+/// The server is created and added to the domain, without duplicate entry
+/// checking.
+///
+/// @param domain DdnsDomain to which to add the server
+/// @param name new server's host name of the server
+/// @param ip new server's ip address
+/// @param port new server's port
+/// @param tsig_key_info pointer to the TSIGInfog key for this server.
+/// Defaults to an empty pointer, meaning this server has no key.
+///
+/// @throw Underlying methods may throw.
+extern void addDomainServer(DdnsDomainPtr& domain, const std::string& name,
+ const std::string& ip = TEST_DNS_SERVER_IP,
+ const size_t port = TEST_DNS_SERVER_PORT,
+ const TSIGKeyInfoPtr&
+ tsig_key_info = TSIGKeyInfoPtr());
+
+/// @brief Creates a hex text dump of the given data buffer.
+///
+/// This method is not used for testing but is handy for debugging. It creates
+/// a pleasantly formatted string of 2-digits per byte separated by spaces with
+/// 16 bytes per line.
+///
+/// @param data pointer to the data to dump
+/// @param len size (in bytes) of data
+extern std::string toHexText(const uint8_t* data, size_t len);
+
+/// @brief Verifies the current state and next event in a transaction
+/// @param trans NameChangeTransaction to check
+/// @param exp_state expected current state of the transaction
+/// @param exp_event expected next event of the transaction
+/// @param file source file name
+/// @param line source line number
+extern void checkContext(NameChangeTransactionPtr trans, const int exp_state,
+ const int exp_evt, const std::string& file, int line);
+
+/// @brief Macro for calling checkContext() that supplies invocation location
+#define CHECK_CONTEXT(a,b,c) checkContext(a,b,c,__FILE__,__LINE__)
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif
diff --git a/src/lib/d2srv/testutils/stats_test_utils.cc b/src/lib/d2srv/testutils/stats_test_utils.cc
new file mode 100644
index 0000000..262879b
--- /dev/null
+++ b/src/lib/d2srv/testutils/stats_test_utils.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2021 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 <d2srv/d2_stats.h>
+#include <d2srv/testutils/stats_test_utils.h>
+
+using namespace isc::data;
+using namespace isc::stats;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+D2StatTest::D2StatTest() {
+ StatsMgr::instance().removeAll();
+ D2Stats::init();
+}
+
+D2StatTest::~D2StatTest() {
+ StatsMgr::instance().removeAll();
+}
+
+void
+checkStats(const string& key_name, const StatMap& expected_stats) {
+ StatMap key_stats;
+ for (const auto& it : expected_stats) {
+ const string& stat_name =
+ StatsMgr::generateName("key", key_name, it.first);
+ key_stats[stat_name] = it.second;
+ }
+ checkStats(key_stats);
+}
+
+}
+}
+}
diff --git a/src/lib/d2srv/testutils/stats_test_utils.h b/src/lib/d2srv/testutils/stats_test_utils.h
new file mode 100644
index 0000000..ccdad02
--- /dev/null
+++ b/src/lib/d2srv/testutils/stats_test_utils.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2020-2021 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 D2_STATS_TEST_UTILS_H
+#define D2_STATS_TEST_UTILS_H
+
+#include <cc/data.h>
+#include <d2srv/d2_stats.h>
+#include <d2srv/d2_tsig_key.h>
+#include <stats/testutils/stats_test_utils.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief Import statistic test utils.
+using isc::stats::test::StatMap;
+using isc::stats::test::checkStat;
+using isc::stats::test::checkStats;
+
+/// @brief Test class with utility functions to test statistics.
+class D2StatTest {
+public:
+ /// @brief Constructor.
+ D2StatTest();
+
+ /// @brief Destructor.
+ virtual ~D2StatTest();
+};
+
+/// @brief Compares StatsMgr key statistics against expected values.
+///
+/// Prepend key part of names before calling checkStats simpler variant.
+///
+/// @param key_name Name of the key.
+/// @param expected_stats Map of expected static names and values.
+void checkStats(const std::string& key_name, const StatMap& expected_stats);
+
+}
+}
+}
+
+#endif // D2_STATS_TEST_UTILS_H
diff --git a/src/lib/database/Makefile.am b/src/lib/database/Makefile.am
new file mode 100644
index 0000000..5127b52
--- /dev/null
+++ b/src/lib/database/Makefile.am
@@ -0,0 +1,79 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = database.dox db_messages.mes
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-database.la
+libkea_database_la_SOURCES = audit_entry.cc audit_entry.h
+libkea_database_la_SOURCES += backend_selector.cc backend_selector.h
+libkea_database_la_SOURCES += database_connection.cc database_connection.h
+libkea_database_la_SOURCES += dbaccess_parser.h dbaccess_parser.cc
+libkea_database_la_SOURCES += db_exceptions.h
+libkea_database_la_SOURCES += db_log.cc db_log.h
+libkea_database_la_SOURCES += db_messages.cc db_messages.h
+libkea_database_la_SOURCES += server.cc server.h
+libkea_database_la_SOURCES += server_collection.cc server_collection.h
+libkea_database_la_SOURCES += server_selector.cc server_selector.h
+
+libkea_database_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_database_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_database_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_database_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_database_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_database_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+libkea_database_la_LDFLAGS = -no-undefined -version-info 48:0:0
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f db_messages.h db_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: db_messages.h db_messages.cc
+ @echo Message files regenerated
+
+db_messages.h db_messages.cc: db_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/database/db_messages.mes
+
+else
+
+messages db_messages.h db_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_database_includedir = $(pkgincludedir)/database
+libkea_database_include_HEADERS = \
+ audit_entry.h \
+ backend_selector.h \
+ database_connection.h \
+ dbaccess_parser.h \
+ db_exceptions.h \
+ db_log.h \
+ db_messages.h \
+ server.h \
+ server_collection.h \
+ server_selector.h
diff --git a/src/lib/database/Makefile.in b/src/lib/database/Makefile.in
new file mode 100644
index 0000000..ae4d5ca
--- /dev/null
+++ b/src/lib/database/Makefile.in
@@ -0,0 +1,1027 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/database
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_database_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)$(libkea_database_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_database_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_database_la_OBJECTS = audit_entry.lo backend_selector.lo \
+ database_connection.lo dbaccess_parser.lo db_log.lo \
+ db_messages.lo server.lo server_collection.lo \
+ server_selector.lo
+libkea_database_la_OBJECTS = $(am_libkea_database_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_database_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_database_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)/audit_entry.Plo \
+ ./$(DEPDIR)/backend_selector.Plo \
+ ./$(DEPDIR)/database_connection.Plo ./$(DEPDIR)/db_log.Plo \
+ ./$(DEPDIR)/db_messages.Plo ./$(DEPDIR)/dbaccess_parser.Plo \
+ ./$(DEPDIR)/server.Plo ./$(DEPDIR)/server_collection.Plo \
+ ./$(DEPDIR)/server_selector.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_database_la_SOURCES)
+DIST_SOURCES = $(libkea_database_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 = $(libkea_database_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . testutils tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = database.dox db_messages.mes
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-database.la
+libkea_database_la_SOURCES = audit_entry.cc audit_entry.h \
+ backend_selector.cc backend_selector.h database_connection.cc \
+ database_connection.h dbaccess_parser.h dbaccess_parser.cc \
+ db_exceptions.h db_log.cc db_log.h db_messages.cc \
+ db_messages.h server.cc server.h server_collection.cc \
+ server_collection.h server_selector.cc server_selector.h
+libkea_database_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libkea_database_la_LDFLAGS = -no-undefined -version-info 48:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_database_includedir = $(pkgincludedir)/database
+libkea_database_include_HEADERS = \
+ audit_entry.h \
+ backend_selector.h \
+ database_connection.h \
+ dbaccess_parser.h \
+ db_exceptions.h \
+ db_log.h \
+ db_messages.h \
+ server.h \
+ server_collection.h \
+ server_selector.h
+
+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/database/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/database/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-database.la: $(libkea_database_la_OBJECTS) $(libkea_database_la_DEPENDENCIES) $(EXTRA_libkea_database_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_database_la_LINK) -rpath $(libdir) $(libkea_database_la_OBJECTS) $(libkea_database_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/audit_entry.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/backend_selector.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/database_connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/db_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dbaccess_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server_collection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server_selector.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_database_includeHEADERS: $(libkea_database_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_database_include_HEADERS)'; test -n "$(libkea_database_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_database_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_database_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)$(libkea_database_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_database_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_database_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_database_include_HEADERS)'; test -n "$(libkea_database_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_database_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_database_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/audit_entry.Plo
+ -rm -f ./$(DEPDIR)/backend_selector.Plo
+ -rm -f ./$(DEPDIR)/database_connection.Plo
+ -rm -f ./$(DEPDIR)/db_log.Plo
+ -rm -f ./$(DEPDIR)/db_messages.Plo
+ -rm -f ./$(DEPDIR)/dbaccess_parser.Plo
+ -rm -f ./$(DEPDIR)/server.Plo
+ -rm -f ./$(DEPDIR)/server_collection.Plo
+ -rm -f ./$(DEPDIR)/server_selector.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-libkea_database_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)/audit_entry.Plo
+ -rm -f ./$(DEPDIR)/backend_selector.Plo
+ -rm -f ./$(DEPDIR)/database_connection.Plo
+ -rm -f ./$(DEPDIR)/db_log.Plo
+ -rm -f ./$(DEPDIR)/db_messages.Plo
+ -rm -f ./$(DEPDIR)/dbaccess_parser.Plo
+ -rm -f ./$(DEPDIR)/server.Plo
+ -rm -f ./$(DEPDIR)/server_collection.Plo
+ -rm -f ./$(DEPDIR)/server_selector.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_database_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_database_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_database_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f db_messages.h db_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: db_messages.h db_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@db_messages.h db_messages.cc: db_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/database/db_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages db_messages.h db_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/database/audit_entry.cc b/src/lib/database/audit_entry.cc
new file mode 100644
index 0000000..92cb887
--- /dev/null
+++ b/src/lib/database/audit_entry.cc
@@ -0,0 +1,88 @@
+// Copyright (C) 2019-2020 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 <database/audit_entry.h>
+#include <exceptions/exceptions.h>
+#include <boost/make_shared.hpp>
+
+namespace isc {
+namespace db {
+
+AuditEntry::AuditEntry(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t revision_id,
+ const std::string& log_message)
+ : object_type_(object_type),
+ object_id_(object_id),
+ modification_type_(modification_type),
+ modification_time_(modification_time),
+ revision_id_(revision_id),
+ log_message_(log_message) {
+ // Check if the provided values are sane.
+ validate();
+}
+
+AuditEntry::AuditEntry(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const uint64_t revision_id,
+ const std::string& log_message)
+ : object_type_(object_type),
+ object_id_(object_id),
+ modification_type_(modification_type),
+ modification_time_(boost::posix_time::microsec_clock::local_time()),
+ revision_id_(revision_id),
+ log_message_(log_message) {
+ // Check if the provided values are sane.
+ validate();
+}
+
+AuditEntryPtr
+AuditEntry::create(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t revision_id,
+ const std::string& log_message) {
+ return (boost::make_shared<AuditEntry>(object_type, object_id,
+ modification_type,
+ modification_time,
+ revision_id,
+ log_message));
+}
+
+AuditEntryPtr
+AuditEntry::create(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const uint64_t revision_id,
+ const std::string& log_message) {
+ return (boost::make_shared<AuditEntry>(object_type, object_id,
+ modification_type,
+ revision_id,
+ log_message));
+}
+
+void
+AuditEntry::validate() const {
+ // object type can't be empty
+ if (object_type_.empty()) {
+ isc_throw(BadValue, "object type can't be empty in the database "
+ "audit entry");
+
+ // modification time must be a valid date time value
+ } else if (modification_time_.is_not_a_date_time()) {
+ isc_throw(BadValue, "modification time value is not a valid time "
+ "object in the database audit entry");
+ }
+}
+
+} // end of namespace isc::db
+} // end of namespace isc
diff --git a/src/lib/database/audit_entry.h b/src/lib/database/audit_entry.h
new file mode 100644
index 0000000..6c46175
--- /dev/null
+++ b/src/lib/database/audit_entry.h
@@ -0,0 +1,299 @@
+// Copyright (C) 2019-2020 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 AUDIT_ENTRY_H
+#define AUDIT_ENTRY_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace db {
+
+class AuditEntry;
+
+/// @brief Pointer to the @c AuditEntry object.
+typedef boost::shared_ptr<AuditEntry> AuditEntryPtr;
+
+/// @brief Represents a single entry in the audit table.
+///
+/// The audit tables are used in the databases to track incremental
+/// changes, e.g. configuration changes applied via the configuration
+/// backend. The backend can query for the entries in the audit table
+/// to identify the SQL table in which the records were modified, the
+/// identifiers of the modified objects, type of the modifications,
+/// time of the modifications and optional log messages associated
+/// with the modifications.
+///
+/// The server should remember the most recent modification time out of
+/// all audit entries it has fetched. During the next attempt to fetch
+/// the most recent modifications in the database it will query for all
+/// entries with later modification time than stored. That way the
+/// server queries only for the audit entries it hasn't fetched yet.
+/// In the case two (or more) successive audit entries have the same
+/// modification time the strictly increasing modification id is used.
+///
+/// When the modification type of the entry is set to
+/// @c AuditEntry::ModificationType::DELETE, the corresponding
+/// configuration entry is already gone from the database. For example:
+/// when a subnet with ID of 123 is deleted from the dhcp4_subnet
+/// table, the audit entry similar to this will be stored in the audit
+/// table:
+///
+/// - object_type: "dhcp4_subnet"
+/// - object_id: 123
+/// - modification_type: 3 (DELETE)
+/// - modification_time: "2019-01-15 15:45:23"
+/// - revision id: 234
+/// - log_message: "DHCPv4 subnet 123 deleted"
+///
+/// The subnet is instantly removed from the dhcp4_subnet table. When
+/// the server finds such entry in the audit table, it removes the
+/// subnet 123 from its (in-memory) configuration. There is no need
+/// make additional queries to fetch updated data from the dhcp4_subnet
+/// table, unless there are also audit entries indicating that some
+/// new subnets have been added or some subnets have been updated.
+class AuditEntry {
+public:
+
+ /// @brief Types of the modifications.
+ ///
+ /// The numbers representing those modification types correspond
+ /// to the values representing them in the database.
+ enum class ModificationType : uint8_t {
+ CREATE = 0,
+ UPDATE = 1,
+ DELETE = 2
+ };
+
+ /// @brief Constructor using explicit modification time and id.
+ ///
+ /// @param object_type name of the table where data was modified.
+ /// @param object_id identifier of the modified record in this table.
+ /// @param modification_type type of the modification, e.g. DELETE.
+ /// @param modification_time time of modification for that record.
+ /// @param revision_id identifier of the revision.
+ /// @param log_message optional log message associated with the
+ /// modification.
+ AuditEntry(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t revision_id,
+ const std::string& log_message);
+
+ /// @brief Constructor using default modification time.
+ ///
+ /// @param object_type name of the table where data was modified.
+ /// @param object_id identifier of the modified record in this table.
+ /// @param modification_type type of the modification, e.g. DELETE.
+ /// @param revision_id identifier of the revision.
+ /// @param log_message optional log message associated with the
+ /// modification.
+ AuditEntry(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const uint64_t revision_id,
+ const std::string& log_message);
+
+ /// @brief Factory function creating an instance of @c AuditEntry.
+ ///
+ /// This function should be used to create an instance of the audit
+ /// entry within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param object_type name of the table where data was modified.
+ /// @param object_id identifier of the modified record in this table.
+ /// @param modification_type type of the modification, e.g. DELETE.
+ /// @param modification_time time of modification for that record.
+ /// @param revision_id identifier of the revision.
+ /// @param log_message optional log message associated with the
+ /// modification.
+ ///
+ /// @return Pointer to the @c AuditEntry instance.
+ static AuditEntryPtr create(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t revision_id,
+ const std::string& log_message);
+
+ /// @brief Factory function creating an instance of @c AuditEntry.
+ ///
+ /// This function should be used to create an instance of the audit
+ /// entry within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param object_type name of the table where data was modified.
+ /// @param object_id identifier of the modified record in this table.
+ /// @param modification_type type of the modification, e.g. DELETE.
+ /// @param revision_id identifier of the revision.
+ /// @param log_message optional log message associated with the
+ /// modification.
+ ///
+ /// @return Pointer to the @c AuditEntry instance.
+ static AuditEntryPtr create(const std::string& object_type,
+ const uint64_t object_id,
+ const ModificationType& modification_type,
+ const uint64_t revision_id,
+ const std::string& log_message);
+
+ /// @brief Returns object type.
+ ///
+ /// @return Name of the table in which the modification is present.
+ std::string getObjectType() const {
+ return (object_type_);
+ }
+
+ /// @brief Returns object id.
+ ///
+ /// @return Identifier of the added, updated or deleted object.
+ uint64_t getObjectId() const {
+ return (object_id_);
+ }
+
+ /// @brief Returns modification type.
+ ///
+ /// @return Type of the modification, e.g. DELETE.
+ ModificationType getModificationType() const {
+ return (modification_type_);
+ }
+
+ /// @brief Returns modification time.
+ ///
+ /// @return Modification time of the corresponding record.
+ boost::posix_time::ptime getModificationTime() const {
+ return (modification_time_);
+ }
+
+ /// @brief Returns revision id.
+ ///
+ /// The revision id is used when two audit entries have the same
+ /// modification time.
+ ///
+ /// @return Identifier of the revision.
+ uint64_t getRevisionId() const {
+ return (revision_id_);
+ }
+
+ /// @brief Returns log message.
+ ///
+ /// @return Optional log message corresponding to the changes.
+ std::string getLogMessage() const {
+ return (log_message_);
+ }
+
+private:
+
+ /// @brief Validates the values specified for the audit entry.
+ ///
+ /// @throw BadValue if the object type is empty or if the
+ /// modification time is not a date time value.
+ void validate() const;
+
+ /// @brief Object type.
+ std::string object_type_;
+
+ /// @brief Object id.
+ uint64_t object_id_;
+
+ /// @brief Modification type.
+ ModificationType modification_type_;
+
+ /// @brief Modification time.
+ boost::posix_time::ptime modification_time_;
+
+ /// @brief Revision id.
+ ///
+ /// The revision id is used when two audit entries have the same
+ /// modification time.
+ uint64_t revision_id_;
+
+ /// @brief Log message.
+ std::string log_message_;
+};
+
+/// @brief Tag used to access index by object type.
+struct AuditEntryObjectTypeTag { };
+
+/// @brief Tag used to access index by modification time.
+struct AuditEntryModificationTimeIdTag { };
+
+/// @brief Tag used to access index by object id.
+struct AuditEntryObjectIdTag { };
+
+/// @brief Multi index container holding @c AuditEntry instances.
+///
+/// This container provides indexes to access the audit entries
+/// by object type and modification time.
+typedef boost::multi_index_container<
+ // The container holds pointers to @c AuditEntry objects.
+ AuditEntryPtr,
+ // First index allows for accessing by the object type.
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<AuditEntryObjectTypeTag>,
+ boost::multi_index::composite_key<
+ AuditEntry,
+ boost::multi_index::const_mem_fun<
+ AuditEntry,
+ std::string,
+ &AuditEntry::getObjectType
+ >,
+ boost::multi_index::const_mem_fun<
+ AuditEntry,
+ AuditEntry::ModificationType,
+ &AuditEntry::getModificationType
+ >
+ >
+ >,
+
+ // Second index allows for accessing by the modification time and id.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<AuditEntryModificationTimeIdTag>,
+ boost::multi_index::composite_key<
+ AuditEntry,
+ boost::multi_index::const_mem_fun<
+ AuditEntry,
+ boost::posix_time::ptime,
+ &AuditEntry::getModificationTime
+ >,
+ boost::multi_index::const_mem_fun<
+ AuditEntry,
+ uint64_t,
+ &AuditEntry::getRevisionId
+ >
+ >
+ >,
+
+ // Third index allows for accessing by the object id.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<AuditEntryObjectIdTag>,
+ boost::multi_index::const_mem_fun<
+ AuditEntry,
+ uint64_t,
+ &AuditEntry::getObjectId
+ >
+ >
+ >
+> AuditEntryCollection;
+
+//// @brief Pointer to the @c AuditEntryCollection object.
+typedef boost::shared_ptr<AuditEntryCollection> AuditEntryCollectionPtr;
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/database/backend_selector.cc b/src/lib/database/backend_selector.cc
new file mode 100644
index 0000000..611017c
--- /dev/null
+++ b/src/lib/database/backend_selector.cc
@@ -0,0 +1,165 @@
+// Copyright (C) 2018-2022 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 <database/backend_selector.h>
+#include <exceptions/exceptions.h>
+#include <limits>
+#include <sstream>
+
+using namespace isc::data;
+
+namespace isc {
+namespace db {
+
+BackendSelector::BackendSelector()
+ : backend_type_(BackendSelector::Type::UNSPEC),
+ host_(), port_(0) {
+}
+
+BackendSelector::BackendSelector(const Type& backend_type)
+ : backend_type_(backend_type),
+ host_(), port_(0) {
+}
+
+BackendSelector::BackendSelector(const std::string& host,
+ const uint16_t port)
+ : backend_type_(BackendSelector::Type::UNSPEC),
+ host_(host), port_(port) {
+ validate();
+}
+
+BackendSelector::BackendSelector(const data::ConstElementPtr& access_map)
+ : backend_type_(BackendSelector::Type::UNSPEC),
+ host_(), port_(0) {
+ if (access_map->getType() != Element::map) {
+ isc_throw(BadValue, "database access information must be a map");
+ }
+
+ ConstElementPtr t = access_map->get("type");
+ if (t) {
+ if (t->getType() != Element::string) {
+ isc_throw(BadValue, "'type' parameter must be a string");
+ }
+ backend_type_ = stringToBackendType(t->stringValue());
+ }
+
+ ConstElementPtr h = access_map->get("host");
+ if (h) {
+ if (h->getType() != Element::string) {
+ isc_throw(BadValue, "'host' parameter must be a string");
+ }
+ host_ = h->stringValue();
+ }
+
+ ConstElementPtr p = access_map->get("port");
+ if (p) {
+ if ((p->getType() != Element::integer) ||
+ (p->intValue() < 0) ||
+ (p->intValue() > std::numeric_limits<uint16_t>::max())) {
+ isc_throw(BadValue, "'port' parameter must be a number in range from 0 "
+ "to " << std::numeric_limits<uint16_t>::max());
+ }
+ port_ = static_cast<uint16_t>(p->intValue());
+ }
+
+ validate();
+}
+
+const BackendSelector&
+BackendSelector::UNSPEC() {
+ static BackendSelector selector;
+ return (selector);
+}
+
+bool
+BackendSelector::amUnspecified() const {
+ return ((backend_type_ == BackendSelector::Type::UNSPEC) &&
+ (host_.empty()) &&
+ (port_ == 0));
+}
+
+std::string
+BackendSelector::toText() const {
+ std::ostringstream s;
+ if (amUnspecified()) {
+ s << "unspecified";
+
+ } else {
+ if (backend_type_ != BackendSelector::Type::UNSPEC) {
+ s << "type=" << backendTypeToString(backend_type_) << ",";
+ }
+
+ if (!host_.empty()) {
+ s << "host=" << host_ << ",";
+
+ if (port_ > 0) {
+ s << "port=" << port_ << ",";
+ }
+ }
+ }
+
+ std::string text = s.str();
+ if ((!text.empty() && (text.back() == ','))) {
+ text.pop_back();
+ }
+
+ return (text);
+}
+
+ElementPtr
+BackendSelector::toElement() const {
+ if (backend_type_ == BackendSelector::Type::UNSPEC) {
+ isc_throw(BadValue, "toElement: backend selector type is unspecified");
+ }
+ ElementPtr result = Element::createMap();
+ result->set("type", Element::create(backendTypeToString(backend_type_)));
+ if (!host_.empty()) {
+ result->set("host", Element::create(host_));
+ if (port_ > 0) {
+ result->set("port", Element::create(static_cast<long int>(port_)));
+ }
+ }
+ return (result);
+}
+
+BackendSelector::Type
+BackendSelector::stringToBackendType(const std::string& type) {
+ if (type == "mysql") {
+ return (BackendSelector::Type::MYSQL);
+
+ } else if (type == "postgresql") {
+ return (BackendSelector::Type::POSTGRESQL);
+
+ } else {
+ isc_throw(BadValue, "unsupported configuration backend type '" << type << "'");
+ }
+}
+
+std::string
+BackendSelector::backendTypeToString(const BackendSelector::Type& type) {
+ switch (type) {
+ case BackendSelector::Type::MYSQL:
+ return ("mysql");
+ case BackendSelector::Type::POSTGRESQL:
+ return ("postgresql");
+ default:
+ ;
+ }
+
+ return (std::string());
+}
+
+void
+BackendSelector::validate() const {
+ if ((port_ != 0) && (host_.empty())) {
+ isc_throw(BadValue, "'host' must be specified along with 'port' parameter");
+ }
+}
+
+} // end of namespace isc::db
+} // end of namespace isc
diff --git a/src/lib/database/backend_selector.h b/src/lib/database/backend_selector.h
new file mode 100644
index 0000000..db207df
--- /dev/null
+++ b/src/lib/database/backend_selector.h
@@ -0,0 +1,215 @@
+// Copyright (C) 2018-2022 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 BACKEND_SELECTOR_H
+#define BACKEND_SELECTOR_H
+
+#include <cc/data.h>
+#include <cc/cfg_to_element.h>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace db {
+
+/// @brief Config Backend selector.
+///
+/// Each Kea server using database as a configuration respository
+/// may use multiple configuration backends simultaneously. The most
+/// typical case is to use a single configuration backend per server,
+/// but there are use cases when configuration information is distributed
+/// accross multiple database instances. In the future, there may be
+/// also caching mechanisms implemented, which will allow for storing
+/// results of certain database queries in memory.
+///
+/// From the server perspective, the most common use of the configuration
+/// backend is to fetch entire configuration information from the
+/// databases (upon startup) or fetch the latest updates to the
+/// configuration, e.g. new subnet added, DHCP option modified etc.
+/// In those cases, it is not so important from the server which backend
+/// this data come from. Therefore, the server would fetch this information
+/// from all available backends.
+///
+/// When the server administrator wants to insert some new data into
+/// the database, modify existing data or simply wants to check the
+/// contents of one of the database instance, he would specify which
+/// database backend he wants to direct queries to.
+///
+/// The @c BackendSelector class provides means to specify whether
+/// the queries should be directed to any backend (see server case
+/// above) or to a specific backend (data insertion case above).
+/// In addition, the @c BackendSelector allows for using various
+/// criteria for selecting a backend to use. Currently those criteria
+/// are: database type (e.g. mysql), database host and database port.
+/// In order to use a specific port, the database host must also be
+/// specified. Note that in a general case multiple backends of the
+/// same type can be used simultaneously, e.g. multiple MySQL backends.
+/// In that case, it may be necessary to specify host (and port) to
+/// issue a query to the right one.
+///
+/// The @c BackendSelector class may be extended in the future to provide
+/// additional backend selection criteria.
+class BackendSelector : public data::CfgToElement {
+public:
+
+ /// @brief Supported database types.
+ ///
+ /// The @c UNSPEC indicates that the database type is not specified
+ /// as selection criteria.
+ enum class Type {
+ MYSQL,
+ POSTGRESQL,
+ UNSPEC
+ };
+
+ /// @brief Default constructor.
+ ///
+ /// It sets the selector to "unspecified". When this selector is used
+ /// the backend pool will use "any" backend. This has a different meaning
+ /// for each type of query. See the @c BaseConfigBackendPool for details.
+ explicit BackendSelector();
+
+ /// @brief Constructor specifying backend type as a selection criteria.
+ ///
+ /// @param backend_type Type of the backend to be selected.
+ explicit BackendSelector(const Type& backend_type);
+
+ /// @brief Constructor for specifying host and optionally port as a
+ /// selection criteria.
+ ///
+ /// @param host Hostname to be used for selecting a backend.
+ /// @param port Port number to be used for selecting a backend. This value
+ /// is optional and is ignored when set to 0. It must be used in conjunction
+ /// with hostname.
+ explicit BackendSelector(const std::string& host, const uint16_t port = 0);
+
+ /// @brief Constructor for selecting a backend using JSON access map.
+ ///
+ /// The provided access map must have the same structure as an element
+ /// of the "config-databases" configuration parameter. However, it merely
+ /// takes into account: "type", "host" and "port" parameters. In addition,
+ /// these parameters are optional. The following are valid combinations:
+ ///
+ /// @code
+ /// {
+ /// "type": "mysql"
+ /// }
+ /// @endcode
+ ///
+ /// @code
+ /// {
+ /// "host": "somehost.example.org"
+ /// }
+ /// @endcode
+ ///
+ /// @code
+ /// {
+ /// "host": "somehost.example.org",
+ /// "port": 1234
+ /// }
+ /// @endcode
+ ///
+ /// @code
+ /// {
+ /// "type": "mysql"
+ /// "host": "somehost.example.org",
+ /// }
+ /// @endcode
+ ///
+ /// @code
+ /// {
+ /// "type": "mysql"
+ /// "host": "somehost.example.org",
+ /// "port": 1234
+ /// }
+ /// @endcode
+ ///
+ /// where "type" can be any of the supported backend types.
+ ///
+ /// This constructor is useful for creating backend selectors from the
+ /// received control commands.
+ ///
+ /// @param access_map Access map as provided above.
+ explicit BackendSelector(const data::ConstElementPtr& access_map);
+
+ /// @brief Returns instance of the "unspecified" backend selector.
+ static const BackendSelector& UNSPEC();
+
+ /// @brief Checks if selector is "unspecified".
+ ///
+ /// @return true if backend type is @c UNSPEC, hostname is empty and
+ /// port number 0, false otherwise.
+ bool amUnspecified() const;
+
+ /// @brief Returns backend type selected.
+ Type getBackendType() const {
+ return (backend_type_);
+ }
+
+ /// @brief Returns host selected.
+ ///
+ /// @return host if specified or empty string if host is not
+ /// specified.
+ std::string getBackendHost() const {
+ return (host_);
+ }
+
+ /// @brief Returns port selected.
+ ///
+ /// @return port number of the selected backend or 0 if port number
+ /// is not specified.
+ uint16_t getBackendPort() const {
+ return (port_);
+ }
+
+ /// @brief Returns selections as text.
+ ///
+ /// @return Collection of comma separated selections, e.g.
+ /// "type=mysql,host=somehost.example.org,port=1234".
+ std::string toText() const;
+
+ /// @brief Unparse a backend selector object.
+ ///
+ /// The caller must ensure that the selector type is specified.
+ ///
+ /// @return A pointer to unparsed backend selector configuration.
+ /// @throw BadValue If the backend selector type is unspecified.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief Converts string to backend type.
+ ///
+ /// @param type Backend type as string.
+ static Type stringToBackendType(const std::string& type);
+
+ /// @brief Converts backend type to string.
+ ///
+ /// @param type Backend type to be converted.
+ static std::string backendTypeToString(const Type& type);
+
+private:
+
+ /// @brief Checks if the specified selector is valid.
+ ///
+ /// It checks if the port number is specified in conjunction with
+ /// host.
+ /// @throw BadValue if selector validation fails.
+ void validate() const;
+
+ /// @brief Backend type selected.
+ Type backend_type_;
+
+ /// @brief Host selected.
+ std::string host_;
+
+ /// @brief Port number selected.
+ uint16_t port_;
+};
+
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/database/database.dox b/src/lib/database/database.dox
new file mode 100644
index 0000000..6612d6d
--- /dev/null
+++ b/src/lib/database/database.dox
@@ -0,0 +1,24 @@
+// Copyright (C) 2020-2022 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/.
+
+/**
+ @page libdatabase libkea-database - Kea Database Library
+
+@section databaseMTConsiderations Multi-Threading Consideration for Database
+
+MySQL and PostgreSQL provide connection pools which are used to make
+lease, host and legal log backends thread safe.
+
+MySQL and PostgreSQL are inter-process safe only when transactions are used
+(including the MySQL auto-transaction mode which includes queries into
+a transaction). For MySQL this means that transactions must be supported
+by the database engine (the engine selection is done in the schema).
+
+Note the InnoDB engine used by Kea for MySQL databases cancels a transaction
+when a deadlock is detected (rare but possible event) and leaves the
+responsibility to retry the transaction to the caller.
+
+*/
diff --git a/src/lib/database/database_connection.cc b/src/lib/database/database_connection.cc
new file mode 100644
index 0000000..50175c6
--- /dev/null
+++ b/src/lib/database/database_connection.cc
@@ -0,0 +1,285 @@
+// Copyright (C) 2015-2023 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 <cc/cfg_to_element.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/db_log.h>
+#include <database/db_messages.h>
+#include <exceptions/exceptions.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <vector>
+
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace db {
+
+const time_t DatabaseConnection::MAX_DB_TIME = 2147483647;
+
+std::string
+DatabaseConnection::getParameter(const std::string& name) const {
+ ParameterMap::const_iterator param = parameters_.find(name);
+ if (param == parameters_.end()) {
+ isc_throw(BadValue, "Parameter " << name << " not found");
+ }
+ return (param->second);
+}
+
+DatabaseConnection::ParameterMap
+DatabaseConnection::parse(const std::string& dbaccess) {
+ DatabaseConnection::ParameterMap mapped_tokens;
+ std::string dba = dbaccess;
+
+ if (!dba.empty()) {
+ try {
+ vector<string> tokens;
+
+ // Handle the special case of a password which is enclosed in apostrophes.
+ // Such password may include whitespace.
+ std::string password_prefix = "password='";
+ auto password_pos = dba.find(password_prefix);
+ if (password_pos != string::npos) {
+ // Password starts with apostrophe, so let's find ending apostrophe.
+ auto password_end_pos = dba.find('\'', password_pos + password_prefix.length());
+ if (password_end_pos == string::npos) {
+ // No ending apostrophe. This is wrong.
+ isc_throw(InvalidParameter, "Apostrophe (') expected at the end of password");
+ }
+ // Extract the password value. It starts after the password=' prefix and ends
+ // at the position of ending apostrophe.
+ auto password = dba.substr(password_pos + password_prefix.length(),
+ password_end_pos - password_pos - password_prefix.length());
+ mapped_tokens.insert(make_pair("password", password));
+
+ // We need to erase the password from the access string because the generic
+ // algorithm parsing other parameters requires that there are no whitespaces
+ // within the parameter values.
+ dba.erase(password_pos, password_prefix.length() + password.length() + 2);
+ // Leading or trailing whitespace may remain after the password removal.
+ dba = util::str::trim(dba);
+ // If the password was the only parameter in the access string, there is
+ // nothing more to do.
+ if (dba.empty()) {
+ return (mapped_tokens);
+ }
+ }
+
+ // We need to pass a string to is_any_of, not just char*. Otherwise
+ // there are cryptic warnings on Debian6 running g++ 4.4 in
+ // /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
+ // array bounds"
+ boost::split(tokens, dba, boost::is_any_of(string("\t ")));
+ BOOST_FOREACH(std::string token, tokens) {
+ size_t pos = token.find("=");
+ if (pos != string::npos) {
+ string name = token.substr(0, pos);
+ string value = token.substr(pos + 1);
+ mapped_tokens.insert(make_pair(name, value));
+ } else {
+ isc_throw(InvalidParameter, "Cannot parse " << token
+ << ", expected format is name=value");
+ }
+ }
+ } catch (const std::exception& ex) {
+ // We'd obscure the password if we could parse the access string.
+ DB_LOG_ERROR(DB_INVALID_ACCESS).arg(dbaccess);
+ throw;
+ }
+ }
+
+ return (mapped_tokens);
+}
+
+std::string
+DatabaseConnection::redactedAccessString(const ParameterMap& parameters) {
+ // Reconstruct the access string: start of with an empty string, then
+ // work through all the parameters in the original string and add them.
+ std::string access;
+ for (DatabaseConnection::ParameterMap::const_iterator i = parameters.begin();
+ i != parameters.end(); ++i) {
+
+ // Separate second and subsequent tokens are preceded by a space.
+ if (!access.empty()) {
+ access += " ";
+ }
+
+ // Append name of parameter...
+ access += i->first;
+ access += "=";
+
+ // ... and the value, except in the case of the password, where a
+ // redacted value is appended.
+ if (i->first == std::string("password")) {
+ access += "*****";
+ } else {
+ access += i->second;
+ }
+ }
+
+ return (access);
+}
+
+bool
+DatabaseConnection::configuredReadOnly() const {
+ std::string readonly_value = "false";
+ try {
+ readonly_value = getParameter("readonly");
+ boost::algorithm::to_lower(readonly_value);
+ } catch (...) {
+ // Parameter "readonly" hasn't been specified so we simply use
+ // the default value of "false".
+ }
+
+ if ((readonly_value != "false") && (readonly_value != "true")) {
+ isc_throw(DbInvalidReadOnly, "invalid value '" << readonly_value
+ << "' specified for boolean parameter 'readonly'");
+ }
+
+ return (readonly_value == "true");
+}
+
+void
+DatabaseConnection::makeReconnectCtl(const std::string& timer_name) {
+ string type = "unknown";
+ unsigned int retries = 0;
+ unsigned int interval = 0;
+
+ // Assumes that parsing ensures only valid values are present
+ try {
+ type = getParameter("type");
+ } catch (...) {
+ // Wasn't specified so we'll use default of "unknown".
+ }
+
+ std::string parm_str;
+ try {
+ parm_str = getParameter("max-reconnect-tries");
+ retries = boost::lexical_cast<unsigned int>(parm_str);
+ } catch (...) {
+ // Wasn't specified so we'll use default of 0;
+ }
+
+ try {
+ parm_str = getParameter("reconnect-wait-time");
+ interval = boost::lexical_cast<unsigned int>(parm_str);
+ } catch (...) {
+ // Wasn't specified so we'll use default of 0;
+ }
+
+ OnFailAction action = OnFailAction::STOP_RETRY_EXIT;
+ try {
+ parm_str = getParameter("on-fail");
+ action = ReconnectCtl::onFailActionFromText(parm_str);
+ } catch (...) {
+ // Wasn't specified so we'll use default of "stop-retry-exit";
+ }
+
+ reconnect_ctl_ = boost::make_shared<ReconnectCtl>(type, timer_name, retries,
+ interval, action);
+}
+
+bool
+DatabaseConnection::invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+ if (DatabaseConnection::db_lost_callback_) {
+ return (DatabaseConnection::db_lost_callback_(db_reconnect_ctl));
+ }
+
+ return (false);
+}
+
+bool
+DatabaseConnection::invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+ if (DatabaseConnection::db_recovered_callback_) {
+ return (DatabaseConnection::db_recovered_callback_(db_reconnect_ctl));
+ }
+
+ return (false);
+}
+
+bool
+DatabaseConnection::invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+ if (DatabaseConnection::db_failed_callback_) {
+ return (DatabaseConnection::db_failed_callback_(db_reconnect_ctl));
+ }
+
+ return (false);
+}
+
+isc::data::ElementPtr
+DatabaseConnection::toElement(const ParameterMap& params) {
+ isc::data::ElementPtr result = isc::data::Element::createMap();
+
+ for (auto param: params) {
+ std::string keyword = param.first;
+ std::string value = param.second;
+
+ if ((keyword == "lfc-interval") ||
+ (keyword == "connect-timeout") ||
+ (keyword == "read-timeout") ||
+ (keyword == "write-timeout") ||
+ (keyword == "tcp-user-timeout") ||
+ (keyword == "reconnect-wait-time") ||
+ (keyword == "max-reconnect-tries") ||
+ (keyword == "port") ||
+ (keyword == "max-row-errors")) {
+ // integer parameters
+ int64_t int_value;
+ try {
+ int_value = boost::lexical_cast<int64_t>(value);
+ result->set(keyword, isc::data::Element::create(int_value));
+ } catch (...) {
+ LOG_ERROR(database_logger, DATABASE_TO_JSON_INTEGER_ERROR)
+ .arg(keyword).arg(value);
+ }
+ } else if ((keyword == "persist") ||
+ (keyword == "readonly")) {
+ if (value == "true") {
+ result->set(keyword, isc::data::Element::create(true));
+ } else if (value == "false") {
+ result->set(keyword, isc::data::Element::create(false));
+ } else {
+ LOG_ERROR(database_logger, DATABASE_TO_JSON_BOOLEAN_ERROR)
+ .arg(keyword).arg(value);
+ }
+ } else if ((keyword == "type") ||
+ (keyword == "user") ||
+ (keyword == "password") ||
+ (keyword == "host") ||
+ (keyword == "name") ||
+ (keyword == "on-fail") ||
+ (keyword == "trust-anchor") ||
+ (keyword == "cert-file") ||
+ (keyword == "key-file") ||
+ (keyword == "cipher-list")) {
+ result->set(keyword, isc::data::Element::create(value));
+ } else {
+ LOG_ERROR(database_logger, DATABASE_TO_JSON_UNKNOWN_TYPE_ERROR)
+ .arg(keyword).arg(value);
+ }
+ }
+
+ return (result);
+}
+
+isc::data::ElementPtr
+DatabaseConnection::toElementDbAccessString(const std::string& dbaccess) {
+ ParameterMap params = parse(dbaccess);
+ return (toElement(params));
+}
+
+DbCallback DatabaseConnection::db_lost_callback_ = 0;
+DbCallback DatabaseConnection::db_recovered_callback_ = 0;
+DbCallback DatabaseConnection::db_failed_callback_ = 0;
+
+} // namespace db
+} // namespace isc
diff --git a/src/lib/database/database_connection.h b/src/lib/database/database_connection.h
new file mode 100644
index 0000000..eafc062
--- /dev/null
+++ b/src/lib/database/database_connection.h
@@ -0,0 +1,280 @@
+// Copyright (C) 2015-2023 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 DATABASE_CONNECTION_H
+#define DATABASE_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <exceptions/exceptions.h>
+#include <util/reconnect_ctl.h>
+#include <functional>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace db {
+
+/// @brief Exception thrown if name of database is not specified
+class NoDatabaseName : public Exception {
+public:
+ NoDatabaseName(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown on failure to open database
+class DbOpenError : public Exception {
+public:
+ DbOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown on failure to execute a database function
+class DbOperationError : public Exception {
+public:
+ DbOperationError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when a specific connection has been rendered unusable
+/// either through loss of connectivity or API lib error
+class DbConnectionUnusable : public Exception {
+public:
+ DbConnectionUnusable(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+/// @brief Invalid type exception
+///
+/// Thrown when the factory doesn't recognize the type of the backend.
+class InvalidType : public Exception {
+public:
+ InvalidType(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid Timeout
+///
+/// Thrown when the timeout specified for the database connection is invalid.
+class DbInvalidTimeout : public Exception {
+public:
+ DbInvalidTimeout(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid port number
+///
+/// Thrown when the port number specified for the database connection is invalid.
+class DbInvalidPort : public Exception {
+public:
+ DbInvalidPort(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid 'readonly' value specification.
+///
+/// Thrown when the value of the 'readonly' boolean parameter is invalid.
+class DbInvalidReadOnly : public Exception {
+public:
+ DbInvalidReadOnly(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Defines a callback prototype for propagating events upward
+typedef std::function<bool (util::ReconnectCtlPtr db_reconnect_ctl)> DbCallback;
+
+/// @brief Function which returns the IOService that can be used to recover the
+/// connection.
+///
+/// This accessor is used to lazy retrieve the IOService when the connection is
+/// lost. It is useful to retrieve it at a later time to support hook libraries
+/// which create managers on load and set IOService later on by using the
+/// dhcp4_srv_configured and dhcp6_srv_configured hooks.
+typedef std::function<isc::asiolink::IOServicePtr ()> IOServiceAccessor;
+
+/// @brief Pointer to an instance of IOServiceAccessor
+typedef boost::shared_ptr<IOServiceAccessor> IOServiceAccessorPtr;
+
+/// @brief Common database connection class.
+///
+/// This class provides functions that are common for establishing
+/// connection with different types of databases; enables operations
+/// on access parameters strings. In particular, it provides a way
+/// to parse parameters in key=value format. This class is expected
+/// to be a base class for all @ref isc::dhcp::LeaseMgr and possibly
+/// @ref isc::dhcp::BaseHostDataSource derived classes.
+class DatabaseConnection : public boost::noncopyable {
+public:
+
+ /// @brief Defines maximum value for time that can be reliably stored.
+ ///
+ /// @todo: Is this common for MySQL and Postgres? Maybe we should have
+ /// specific values for each backend?
+ ///
+ /// If I'm still alive I'll be too old to care. You fix it.
+ static const time_t MAX_DB_TIME;
+
+ /// @brief Database configuration parameter map
+ typedef std::map<std::string, std::string> ParameterMap;
+
+ /// @brief Constructor
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ /// @param callback The connection recovery callback.
+ DatabaseConnection(const ParameterMap& parameters,
+ DbCallback callback = DbCallback())
+ : parameters_(parameters), callback_(callback), unusable_(false) {
+ }
+
+ /// @brief Destructor
+ virtual ~DatabaseConnection(){};
+
+ /// @brief Instantiates a ReconnectCtl based on the connection's
+ /// reconnect parameters
+ ///
+ /// @param timer_name of the timer used for the ReconnectCtl object.
+ virtual void makeReconnectCtl(const std::string& timer_name);
+
+ /// @brief The reconnect settings.
+ ///
+ /// @return The reconnect settings.
+ util::ReconnectCtlPtr reconnectCtl() {
+ return (reconnect_ctl_);
+ }
+
+ /// @brief Returns value of a connection parameter.
+ ///
+ /// @param name Name of the parameter which value should be returned.
+ /// @return Value of one of the connection parameters.
+ /// @throw BadValue if parameter is not found
+ std::string getParameter(const std::string& name) const;
+
+ /// @brief Parse database access string
+ ///
+ /// Parses the string of "keyword=value" pairs and separates them
+ /// out into the map. A value of the password parameter may include
+ /// whitespace in which case it must be surrounded by apostrophes.
+ ///
+ /// @param dbaccess Database access string.
+ ///
+ /// @return @ref ParameterMap of keyword/value pairs.
+ static ParameterMap parse(const std::string& dbaccess);
+
+ /// @brief Redact database access string
+ ///
+ /// Takes the database parameters and returns a database access string
+ /// passwords replaced by asterisks. This string is used in log messages.
+ ///
+ /// @param parameters Database access parameters (output of "parse").
+ ///
+ /// @return Redacted database access string.
+ static std::string redactedAccessString(const ParameterMap& parameters);
+
+ /// @brief Convenience method checking if database should be opened with
+ /// read only access.
+ ///
+ /// @return true if "readonly" parameter is specified and set to true;
+ /// false if "readonly" parameter is not specified or it is specified
+ /// and set to false.
+ bool configuredReadOnly() const;
+
+ /// @brief Invokes the connection's lost connectivity callback
+ ///
+ /// @return Returns the result of the callback or false if there is no
+ /// callback.
+ static bool invokeDbLostCallback(const util::ReconnectCtlPtr& db_reconnect_ctl);
+
+ /// @brief Invokes the connection's restored connectivity callback
+ ///
+ /// @return Returns the result of the callback or false if there is no
+ /// callback.
+ static bool invokeDbRecoveredCallback(const util::ReconnectCtlPtr& db_reconnect_ctl);
+
+ /// @brief Invokes the connection's restore failed connectivity callback
+ ///
+ /// @return Returns the result of the callback or false if there is no
+ /// callback.
+ static bool invokeDbFailedCallback(const util::ReconnectCtlPtr& db_reconnect_ctl);
+
+ /// @brief Unparse a parameter map
+ ///
+ /// @param params the parameter map to unparse
+ /// @return a pointer to configuration
+ static isc::data::ElementPtr toElement(const ParameterMap& params);
+
+ /// @brief Unparse an access string
+ ///
+ /// @param dbaccess the database access string
+ /// @return a pointer to configuration
+ static isc::data::ElementPtr toElementDbAccessString(const std::string& dbaccess);
+
+ /// @brief Optional callback function to invoke if an opened connection is
+ /// lost
+ static DbCallback db_lost_callback_;
+
+ /// @brief Optional callback function to invoke if an opened connection
+ /// recovery succeeded
+ static DbCallback db_recovered_callback_;
+
+ /// @brief Optional callback function to invoke if an opened connection
+ /// recovery failed
+ static DbCallback db_failed_callback_;
+
+ /// @brief Throws an exception if the connection is not usable.
+ /// @throw DbConnectionUnusable
+ void checkUnusable() {
+ if (unusable_) {
+ isc_throw (DbConnectionUnusable, "Attempt to use an invalid connection");
+ }
+ }
+
+ /// @brief Flag which indicates if connection is unusable.
+ ///
+ /// @return true if the connection is unusable, false otherwise
+ bool isUnusable() {
+ return (unusable_);
+ }
+
+protected:
+ /// @brief Sets the unusable flag to true.
+ void markUnusable() { unusable_ = true; }
+
+private:
+
+ /// @brief List of parameters passed in dbconfig
+ ///
+ /// That will be mostly used for storing database name, username,
+ /// password and other parameters required for DB access. It is not
+ /// intended to keep any DHCP-related parameters.
+ ParameterMap parameters_;
+
+protected:
+
+ /// @brief The callback used to recover the connection.
+ DbCallback callback_;
+
+private:
+
+ /// @brief Indicates if the connection can no longer be used for normal
+ /// operations. Typically a connection is marked unusable after an unrecoverable
+ /// DB error. There may be a time when the connection exists, after
+ /// such an event, during which it cannot be used for anything beyond checking
+ /// parameters and error information. This flag can be used as a guard in
+ /// code to prevent inadvertent use of a broken connection.
+ bool unusable_;
+
+ /// @brief Reconnect settings.
+ util::ReconnectCtlPtr reconnect_ctl_;
+};
+
+} // namespace db
+} // namespace isc
+
+#endif // DATABASE_CONNECTION_H
diff --git a/src/lib/database/db_exceptions.h b/src/lib/database/db_exceptions.h
new file mode 100644
index 0000000..9656ab0
--- /dev/null
+++ b/src/lib/database/db_exceptions.h
@@ -0,0 +1,99 @@
+// Copyright (C) 2015-2023 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 DB_EXCEPTIONS_H
+#define DB_EXCEPTIONS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace db {
+
+/// @brief Multiple lease records found where one expected
+class MultipleRecords : public Exception {
+public:
+ MultipleRecords(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Data is truncated
+class DataTruncated : public Exception {
+public:
+ DataTruncated(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Database duplicate entry error
+class DuplicateEntry : public Exception {
+public:
+ DuplicateEntry(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Key is NULL but was specified NOT NULL
+class NullKeyError : public Exception {
+public:
+ NullKeyError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Attempt to modify data in read-only database.
+class ReadOnlyDb : public Exception {
+public:
+ ReadOnlyDb(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Upper bound address is lower than lower bound address while
+/// retrieving a range of leases.
+class InvalidRange : public Exception {
+public:
+ InvalidRange(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid address family used as input to Lease Manager.
+class InvalidAddressFamily : public Exception {
+public:
+ InvalidAddressFamily(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Error detected in the database configuration.
+class DbConfigError : public Exception {
+public:
+ DbConfigError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Error when specified database could not be found in the server
+/// configuration.
+class NoSuchDatabase : public Exception {
+public:
+ NoSuchDatabase(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Specification of the database backend to be used yields
+/// multiple results.
+class AmbiguousDatabase : public Exception {
+public:
+ AmbiguousDatabase(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Thrown when it is expected that some rows are affected,
+/// usually during a DELETE or an UPDATE, but none are.
+class NoRowsAffected : public Exception {
+public:
+ NoRowsAffected(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+} // namespace isc
+} // namespace db
+
+#endif
diff --git a/src/lib/database/db_log.cc b/src/lib/database/db_log.cc
new file mode 100644
index 0000000..395bd57
--- /dev/null
+++ b/src/lib/database/db_log.cc
@@ -0,0 +1,137 @@
+// Copyright (C) 2018-2023 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/.
+
+/// Defines the logger used by the NSAS
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <database/db_log.h>
+#include <database/db_messages.h>
+
+using namespace isc::log;
+
+namespace isc {
+namespace db {
+
+/// @brief Database logging levels.
+const int DB_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
+
+/// @brief Map of translated messages.
+const DbLogger::MessageMap db_message_map = {
+ { DB_INVALID_ACCESS, DATABASE_INVALID_ACCESS },
+
+ { PGSQL_DEALLOC_ERROR, DATABASE_PGSQL_DEALLOC_ERROR },
+ { PGSQL_FATAL_ERROR, DATABASE_PGSQL_FATAL_ERROR },
+ { PGSQL_START_TRANSACTION, DATABASE_PGSQL_START_TRANSACTION },
+ { PGSQL_COMMIT, DATABASE_PGSQL_COMMIT },
+ { PGSQL_ROLLBACK, DATABASE_PGSQL_ROLLBACK },
+ { PGSQL_CREATE_SAVEPOINT, DATABASE_PGSQL_CREATE_SAVEPOINT },
+ { PGSQL_ROLLBACK_SAVEPOINT, DATABASE_PGSQL_ROLLBACK_SAVEPOINT },
+ { PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED, DATABASE_PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED },
+
+ { MYSQL_FATAL_ERROR, DATABASE_MYSQL_FATAL_ERROR },
+ { MYSQL_START_TRANSACTION, DATABASE_MYSQL_START_TRANSACTION },
+ { MYSQL_COMMIT, DATABASE_MYSQL_COMMIT },
+ { MYSQL_ROLLBACK, DATABASE_MYSQL_ROLLBACK },
+};
+
+isc::log::Logger database_logger("database");
+
+DbLogger db_logger_translator(database_logger, db_message_map);
+
+DbLoggerStack db_logger_stack = { db_logger_translator };
+
+std::mutex db_logger_mutex;
+
+const MessageID&
+DbLogger::translateMessage(const DbMessageID& id) const {
+ try {
+ return (map_.at(id));
+ } catch (const std::out_of_range&) {
+ isc_throw(isc::Unexpected, "can't map message: " << id);
+ }
+}
+
+void checkDbLoggerStack() {
+ if (db_logger_stack.empty()) {
+ isc_throw(isc::Unexpected, "database logger stack is empty");
+ }
+}
+
+template <>
+isc::log::Logger::Formatter
+DB_LOG<fatal>::formatter(DbMessageID const message_id,
+ int const /* debug_level = 0 */) {
+ return isc::db::db_logger_stack.back().logger_.fatal(
+ isc::db::db_logger_stack.back().translateMessage(message_id));
+}
+
+template <>
+isc::log::Logger::Formatter
+DB_LOG<error>::formatter(DbMessageID const message_id,
+ int const /* debug_level = 0 */) {
+ return isc::db::db_logger_stack.back().logger_.error(
+ isc::db::db_logger_stack.back().translateMessage(message_id));
+}
+
+template <>
+isc::log::Logger::Formatter
+DB_LOG<warn>::formatter(DbMessageID const message_id,
+ int const /* debug_level = 0 */) {
+ return isc::db::db_logger_stack.back().logger_.warn(
+ isc::db::db_logger_stack.back().translateMessage(message_id));
+}
+
+template <>
+isc::log::Logger::Formatter
+DB_LOG<info>::formatter(DbMessageID const message_id,
+ int const /* debug_level = 0 */) {
+ return isc::db::db_logger_stack.back().logger_.info(
+ isc::db::db_logger_stack.back().translateMessage(message_id));
+}
+
+template <>
+isc::log::Logger::Formatter
+DB_LOG<debug>::formatter(DbMessageID const message_id,
+ int const debug_level /* = 0 */) {
+ return isc::db::db_logger_stack.back().logger_.debug(
+ debug_level,
+ isc::db::db_logger_stack.back().translateMessage(message_id));
+}
+
+template <>
+bool
+DB_LOG<fatal>::isEnabled(int const /* debug_level = 0 */) const {
+ return db_logger_stack.back().logger_.isFatalEnabled();
+}
+
+template <>
+bool
+DB_LOG<error>::isEnabled(int const /* debug_level = 0 */) const {
+ return db_logger_stack.back().logger_.isErrorEnabled();
+}
+
+template <>
+bool
+DB_LOG<warn>::isEnabled(int const /* debug_level = 0 */) const {
+ return db_logger_stack.back().logger_.isWarnEnabled();
+}
+
+template <>
+bool
+DB_LOG<info>::isEnabled(int const /* debug_level = 0 */) const {
+ return db_logger_stack.back().logger_.isInfoEnabled();
+}
+
+template <>
+bool
+DB_LOG<debug>::isEnabled(int const debug_level /* = 0 */) const {
+ return db_logger_stack.back().logger_.isDebugEnabled(debug_level);
+}
+
+} // namespace db
+} // namespace isc
diff --git a/src/lib/database/db_log.h b/src/lib/database/db_log.h
new file mode 100644
index 0000000..a873df7
--- /dev/null
+++ b/src/lib/database/db_log.h
@@ -0,0 +1,215 @@
+// Copyright (C) 2018-2023 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 DB_LOG_H
+#define DB_LOG_H
+
+#include <log/macros.h>
+
+#include <map>
+#include <mutex>
+#include <list>
+
+/// @file db_log.h
+///
+/// We want to reuse the database backend connection and exchange code
+/// for other uses, in particular for hook libraries. But this code
+/// includes some calls to the system logger for debug and uncommon
+/// cases and of course we do not want to get log messages from
+/// a hook library to seem to come from DHCP server core.
+///
+/// The solution is to use a database logger which calls the right
+/// logger with mapped messages.
+
+namespace isc {
+namespace db {
+
+///@{
+/// @brief Database logging levels
+///
+/// Defines the levels used to output debug messages in the database
+/// support. Note that higher numbers equate to more verbose (and detailed)
+/// output.
+
+/// @brief Additional information
+///
+/// Record detailed tracing. This is generally reserved for tracing access to
+/// the lease database.
+extern const int DB_DBG_TRACE_DETAIL;
+
+///@}
+
+/// @brief Common database library logger.
+extern isc::log::Logger database_logger;
+
+///@{
+/// @brief Database messages
+///
+enum DbMessageID {
+ DB_INVALID_ACCESS,
+
+ PGSQL_DEALLOC_ERROR,
+ PGSQL_FATAL_ERROR,
+ PGSQL_START_TRANSACTION,
+ PGSQL_COMMIT,
+ PGSQL_ROLLBACK,
+ PGSQL_CREATE_SAVEPOINT,
+ PGSQL_ROLLBACK_SAVEPOINT,
+ PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED,
+
+ MYSQL_FATAL_ERROR,
+ MYSQL_START_TRANSACTION,
+ MYSQL_COMMIT,
+ MYSQL_ROLLBACK,
+};
+///@}
+
+/// @brief Database logger class
+///
+class DbLogger {
+public:
+ /// @brief Translation map type
+ typedef std::map<DbMessageID, isc::log::MessageID> MessageMap;
+
+ /// @brief Constructor
+ ///
+ /// @param logger logger which will be called
+ /// @param map message id translation map
+ DbLogger(isc::log::Logger& logger, const MessageMap& map)
+ : logger_(logger), map_(map) {
+ }
+
+ /// @brief Translate message
+ ///
+ /// @param id database message id
+ /// @return logger message
+ /// @throw Unexpected if the id is not in the message map
+ const isc::log::MessageID& translateMessage(const DbMessageID& id) const;
+
+ /// @brief The logger
+ isc::log::Logger& logger_;
+
+ /// @brief The translation map
+ const MessageMap& map_;
+};
+
+/// @brief Database logger stack
+typedef std::list<DbLogger> DbLoggerStack;
+
+/// @brief Global database logger stack (initialized to database logger)
+extern DbLoggerStack db_logger_stack;
+
+/// @brief Global mutex to protect logger stack
+extern std::mutex db_logger_mutex;
+
+/// @brief Check database logger stack
+///
+/// @throw Unexpected if the stack is empty
+void checkDbLoggerStack();
+
+/// @brief log type enumerations for use in DB_LOG specializations
+enum log_type_t {
+ fatal,
+ error,
+ warn,
+ info,
+ debug,
+};
+
+/// @brief DB_LOG_* logic
+template <log_type_t log_type>
+struct DB_LOG {
+ /// @brief To preserve the old way of logging, this constructor facilitates
+ /// initiating the DB_LOG_* chain call.
+ DB_LOG(DbMessageID const message_id, int const debug_level = 0) {
+ std::lock_guard<std::mutex> lock(isc::db::db_logger_mutex);
+ isc::db::checkDbLoggerStack();
+ if (isEnabled()) {
+ formatter_ = formatter(message_id, debug_level);
+ }
+ }
+
+ /// @brief Pass parameters to replace logger placeholders.
+ ///
+ /// @param first the parameter to be processed now
+ /// @param args the parameters to be processes in recursive calls
+ ///
+ /// @return reference to this object so that these calls may be chained.
+ template <typename T, typename... Args>
+ DB_LOG& arg(T first, Args... args) {
+ formatter_.arg(first);
+ return arg(args...);
+ }
+
+ /// @brief The last invocation of the arg() which is without parameters.
+ ///
+ /// Required when using variadic arguments.
+ ///
+ /// @return reference to this object so that these calls may be chained.
+ DB_LOG& arg() {
+ return *this;
+ }
+
+private:
+ /// @brief Initializes the logging formatter.
+ ///
+ /// @param message_id one of the DbMessageID enums
+ /// @param debug_level one of debug levels specified in log_dbglevels.h
+ ///
+ /// @return the formatter responsible for logging
+ isc::log::Logger::Formatter
+ formatter(DbMessageID const message_id, int const debug_level = 0);
+
+ /// @brief Check if the logger is ready to log.
+ ///
+ /// @param debug_level required only for debug log type
+ ///
+ /// @return true if the logger is enabled, false otherwise
+ bool isEnabled(int const debug_level = 0) const;
+
+ /// @brief the formatter responsible for logging
+ isc::log::Logger::Formatter formatter_;
+};
+
+/// @brief all DB_LOG specializations
+/// @{
+struct DB_LOG_FATAL : DB_LOG<fatal> {
+ DB_LOG_FATAL(DbMessageID const message_id) : DB_LOG(message_id) {
+ }
+};
+
+struct DB_LOG_ERROR : DB_LOG<error> {
+ DB_LOG_ERROR(DbMessageID const message_id) : DB_LOG(message_id) {
+ }
+};
+
+struct DB_LOG_WARN : DB_LOG<warn> {
+ DB_LOG_WARN(DbMessageID const message_id) : DB_LOG(message_id) {
+ }
+};
+
+struct DB_LOG_INFO : DB_LOG<info> {
+ DB_LOG_INFO(DbMessageID const message_id) : DB_LOG(message_id) {
+ }
+};
+
+struct DB_LOG_DEBUG : DB_LOG<debug> {
+ DB_LOG_DEBUG(int const debug_level, DbMessageID const message_id)
+ : DB_LOG(message_id, debug_level) {
+ }
+};
+///@}
+
+/// @brief DHCP server database message map
+extern const db::DbLogger::MessageMap db_message_map;
+
+/// @brief Database logger translator.
+extern db::DbLogger db_logger_translator;
+
+} // namespace db
+} // namespace isc
+
+#endif // DB_LOG_H
diff --git a/src/lib/database/db_messages.cc b/src/lib/database/db_messages.cc
new file mode 100644
index 0000000..b680baa
--- /dev/null
+++ b/src/lib/database/db_messages.cc
@@ -0,0 +1,55 @@
+// File created from ../../../src/lib/database/db_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace db {
+
+extern const isc::log::MessageID DATABASE_INVALID_ACCESS = "DATABASE_INVALID_ACCESS";
+extern const isc::log::MessageID DATABASE_MYSQL_COMMIT = "DATABASE_MYSQL_COMMIT";
+extern const isc::log::MessageID DATABASE_MYSQL_FATAL_ERROR = "DATABASE_MYSQL_FATAL_ERROR";
+extern const isc::log::MessageID DATABASE_MYSQL_ROLLBACK = "DATABASE_MYSQL_ROLLBACK";
+extern const isc::log::MessageID DATABASE_MYSQL_START_TRANSACTION = "DATABASE_MYSQL_START_TRANSACTION";
+extern const isc::log::MessageID DATABASE_PGSQL_COMMIT = "DATABASE_PGSQL_COMMIT";
+extern const isc::log::MessageID DATABASE_PGSQL_CREATE_SAVEPOINT = "DATABASE_PGSQL_CREATE_SAVEPOINT";
+extern const isc::log::MessageID DATABASE_PGSQL_DEALLOC_ERROR = "DATABASE_PGSQL_DEALLOC_ERROR";
+extern const isc::log::MessageID DATABASE_PGSQL_FATAL_ERROR = "DATABASE_PGSQL_FATAL_ERROR";
+extern const isc::log::MessageID DATABASE_PGSQL_ROLLBACK = "DATABASE_PGSQL_ROLLBACK";
+extern const isc::log::MessageID DATABASE_PGSQL_ROLLBACK_SAVEPOINT = "DATABASE_PGSQL_ROLLBACK_SAVEPOINT";
+extern const isc::log::MessageID DATABASE_PGSQL_START_TRANSACTION = "DATABASE_PGSQL_START_TRANSACTION";
+extern const isc::log::MessageID DATABASE_PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED = "DATABASE_PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED";
+extern const isc::log::MessageID DATABASE_TO_JSON_BOOLEAN_ERROR = "DATABASE_TO_JSON_BOOLEAN_ERROR";
+extern const isc::log::MessageID DATABASE_TO_JSON_INTEGER_ERROR = "DATABASE_TO_JSON_INTEGER_ERROR";
+extern const isc::log::MessageID DATABASE_TO_JSON_UNKNOWN_TYPE_ERROR = "DATABASE_TO_JSON_UNKNOWN_TYPE_ERROR";
+
+} // namespace db
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DATABASE_INVALID_ACCESS", "invalid database access string: %1",
+ "DATABASE_MYSQL_COMMIT", "committing to MySQL database",
+ "DATABASE_MYSQL_FATAL_ERROR", "Unrecoverable MySQL error occurred: %1 for <%2>, reason: %3 (error code: %4).",
+ "DATABASE_MYSQL_ROLLBACK", "rolling back MySQL database",
+ "DATABASE_MYSQL_START_TRANSACTION", "starting new MySQL transaction",
+ "DATABASE_PGSQL_COMMIT", "committing to PostgreSQL database",
+ "DATABASE_PGSQL_CREATE_SAVEPOINT", "creating a new PostgreSQL savepoint: %1",
+ "DATABASE_PGSQL_DEALLOC_ERROR", "An error occurred deallocating SQL statements while closing the PostgreSQL lease database: %1",
+ "DATABASE_PGSQL_FATAL_ERROR", "Unrecoverable PostgreSQL error occurred: Statement: <%1>, reason: %2 (error code: %3).",
+ "DATABASE_PGSQL_ROLLBACK", "rolling back PostgreSQL database",
+ "DATABASE_PGSQL_ROLLBACK_SAVEPOINT", "rolling back PostgreSQL database to savepoint: $1",
+ "DATABASE_PGSQL_START_TRANSACTION", "starting a new PostgreSQL transaction",
+ "DATABASE_PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED", "tcp_user_timeout is not supported in this PostgreSQL version",
+ "DATABASE_TO_JSON_BOOLEAN_ERROR", "Internal logic error: invalid boolean value found in database connection parameters: %1=%2",
+ "DATABASE_TO_JSON_INTEGER_ERROR", "Internal logic error: invalid integer value found in database connection parameters: %1=%2",
+ "DATABASE_TO_JSON_UNKNOWN_TYPE_ERROR", "Internal logic error: unknown element found in database connection parameters: %1=%2",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/database/db_messages.h b/src/lib/database/db_messages.h
new file mode 100644
index 0000000..ad367e2
--- /dev/null
+++ b/src/lib/database/db_messages.h
@@ -0,0 +1,31 @@
+// File created from ../../../src/lib/database/db_messages.mes
+
+#ifndef DB_MESSAGES_H
+#define DB_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace db {
+
+extern const isc::log::MessageID DATABASE_INVALID_ACCESS;
+extern const isc::log::MessageID DATABASE_MYSQL_COMMIT;
+extern const isc::log::MessageID DATABASE_MYSQL_FATAL_ERROR;
+extern const isc::log::MessageID DATABASE_MYSQL_ROLLBACK;
+extern const isc::log::MessageID DATABASE_MYSQL_START_TRANSACTION;
+extern const isc::log::MessageID DATABASE_PGSQL_COMMIT;
+extern const isc::log::MessageID DATABASE_PGSQL_CREATE_SAVEPOINT;
+extern const isc::log::MessageID DATABASE_PGSQL_DEALLOC_ERROR;
+extern const isc::log::MessageID DATABASE_PGSQL_FATAL_ERROR;
+extern const isc::log::MessageID DATABASE_PGSQL_ROLLBACK;
+extern const isc::log::MessageID DATABASE_PGSQL_ROLLBACK_SAVEPOINT;
+extern const isc::log::MessageID DATABASE_PGSQL_START_TRANSACTION;
+extern const isc::log::MessageID DATABASE_PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED;
+extern const isc::log::MessageID DATABASE_TO_JSON_BOOLEAN_ERROR;
+extern const isc::log::MessageID DATABASE_TO_JSON_INTEGER_ERROR;
+extern const isc::log::MessageID DATABASE_TO_JSON_UNKNOWN_TYPE_ERROR;
+
+} // namespace db
+} // namespace isc
+
+#endif // DB_MESSAGES_H
diff --git a/src/lib/database/db_messages.mes b/src/lib/database/db_messages.mes
new file mode 100644
index 0000000..acec88c
--- /dev/null
+++ b/src/lib/database/db_messages.mes
@@ -0,0 +1,105 @@
+# Copyright (C) 2012-2023 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/.
+
+$NAMESPACE isc::db
+
+% DATABASE_INVALID_ACCESS invalid database access string: %1
+This is logged when an attempt has been made to parse a database access string
+and the attempt ended in error. The access string in question - which
+should be of the form 'keyword=value keyword=value...' is included in
+the message.
+
+% DATABASE_MYSQL_COMMIT committing to MySQL database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the MySQL settings,
+the committal may not include a write to disk.
+
+% DATABASE_MYSQL_FATAL_ERROR Unrecoverable MySQL error occurred: %1 for <%2>, reason: %3 (error code: %4).
+An error message indicating that communication with the MySQL database server
+has been lost. If automatic recovery has been enabled, then the server will
+attempt to recover connectivity. If not, then the server will exit with a
+non-zero exit code. The cause of such an error is most likely a network issue
+or the MySQL server has gone down.
+
+% DATABASE_MYSQL_ROLLBACK rolling back MySQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DATABASE_MYSQL_START_TRANSACTION starting new MySQL transaction
+A debug message issued when a new MySQL transaction is being started.
+This message is typically not issued when inserting data into a
+single table because the server doesn't explicitly start
+transactions in this case. This message is issued when data is
+inserted into multiple tables with multiple INSERT statements
+and there may be a need to rollback the whole transaction if
+any of these INSERT statements fail.
+
+% DATABASE_PGSQL_COMMIT committing to PostgreSQL database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the PostgreSQL settings,
+the committal may not include a write to disk.
+
+% DATABASE_PGSQL_CREATE_SAVEPOINT creating a new PostgreSQL savepoint: %1
+The code is issuing a call to create a savepoint within the current
+transaction. Database modifications made up to this point will be preserved
+should a subsequent call to rollback to this savepoint occurs prior to the
+transaction being committed.
+
+% DATABASE_PGSQL_DEALLOC_ERROR An error occurred deallocating SQL statements while closing the PostgreSQL lease database: %1
+This is an error message issued when a DHCP server (either V4 or V6) experienced
+and error freeing database SQL resources as part of closing its connection to
+the PostgreSQL database. The connection is closed as part of normal server
+shutdown. This error is most likely a programmatic issue that is highly
+unlikely to occur or negatively impact server operation.
+
+% DATABASE_PGSQL_FATAL_ERROR Unrecoverable PostgreSQL error occurred: Statement: <%1>, reason: %2 (error code: %3).
+An error message indicating that communication with the PostgreSQL database server
+has been lost. If automatic recovery has been enabled, then the server will
+attempt to recover the connectivity. If not, then the server will exit with a
+non-zero exit code. The cause of such an error is most likely a network issue
+or the PostgreSQL server has gone down.
+
+% DATABASE_PGSQL_ROLLBACK rolling back PostgreSQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DATABASE_PGSQL_ROLLBACK_SAVEPOINT rolling back PostgreSQL database to savepoint: $1
+The code is issuing a call to rollback to the given savepoint. Any database
+modifications that were made after the savepoint was created will be rolled back
+and not committed to the database.
+
+% DATABASE_PGSQL_START_TRANSACTION starting a new PostgreSQL transaction
+A debug message issued when a new PostgreSQL transaction is being started.
+This message is typically not issued when inserting data into a
+single table because the server doesn't explicitly start
+transactions in this case. This message is issued when data is
+inserted into multiple tables with multiple INSERT statements
+and there may be a need to rollback the whole transaction if
+any of these INSERT statements fail.
+
+% DATABASE_PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED tcp_user_timeout is not supported in this PostgreSQL version
+This warning message is issued when a user has configured the tcp_user_timeout
+parameter in the connection to the PostgreSQL database but the installed database
+does not support this parameter. It is supported by the PostgreSQL version 12 or later.
+The parameter setting will be ignored.
+
+% DATABASE_TO_JSON_BOOLEAN_ERROR Internal logic error: invalid boolean value found in database connection parameters: %1=%2
+This error message is printed when conversion to JSON of the internal state is requested,
+but the connection string contains a boolean parameter with invalid value. It is a programming
+error. The software will continue operation, but the returned JSON data will be syntactically
+valid, but incomplete. The culprit parameter will not be converted.
+
+% DATABASE_TO_JSON_INTEGER_ERROR Internal logic error: invalid integer value found in database connection parameters: %1=%2
+This error message is printed when conversion to JSON of the internal state is requested,
+but the connection string contains the integer parameter with a wrong value. It is a programming
+error. The software will continue operation, but the returned JSON data will be syntactically
+valid, but incomplete. The culprit parameter will not be converted.
+
+% DATABASE_TO_JSON_UNKNOWN_TYPE_ERROR Internal logic error: unknown element found in database connection parameters: %1=%2
+This error message is printed when conversion to JSON of the internal state is requested,
+but the connection string contains unrecognized parameter. It is a programming error.
+The software will continue operation, but the returned JSON data will be syntactically
+valid, but incomplete. The unknown parameter will not be converted.
diff --git a/src/lib/database/dbaccess_parser.cc b/src/lib/database/dbaccess_parser.cc
new file mode 100644
index 0000000..2048b3b
--- /dev/null
+++ b/src/lib/database/dbaccess_parser.cc
@@ -0,0 +1,293 @@
+// Copyright (C) 2012-2023 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 <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/dbaccess_parser.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <map>
+#include <string>
+#include <utility>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace db {
+
+
+// Factory function to build the parser
+DbAccessParser::DbAccessParser()
+ : values_() {
+}
+
+// Parse the configuration and check that the various keywords are consistent.
+void
+DbAccessParser::parse(std::string& access_string,
+ ConstElementPtr database_config) {
+
+ // To cope with incremental updates, the strategy is:
+ // 1. Take a copy of the stored keyword/value pairs.
+ // 2. Update the copy with the passed keywords.
+ // 3. Perform validation checks on the updated keyword/value pairs.
+ // 4. If all is OK, update the stored keyword/value pairs.
+ // 5. Save resulting database access string in the Configuration
+ // Manager.
+
+ // Note only range checks can fail with a database_config from
+ // a flex/bison parser.
+
+ // 1. Take a copy of the stored keyword/value pairs.
+ DatabaseConnection::ParameterMap values_copy = values_;
+
+ int64_t lfc_interval = 0;
+ int64_t connect_timeout = 0;
+ int64_t read_timeout = 0;
+ int64_t write_timeout = 0;
+ int64_t tcp_user_timeout = 0;
+ int64_t port = 0;
+ int64_t max_reconnect_tries = 0;
+ int64_t reconnect_wait_time = 0;
+ int64_t max_row_errors = 0;
+
+ // 2. Update the copy with the passed keywords.
+ for (std::pair<std::string, ConstElementPtr> param : database_config->mapValue()) {
+ try {
+ if ((param.first == "persist") ||
+ (param.first == "readonly")) {
+ values_copy[param.first] = (param.second->boolValue() ?
+ "true" : "false");
+
+ } else if (param.first == "lfc-interval") {
+ lfc_interval = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(lfc_interval);
+
+ } else if (param.first == "connect-timeout") {
+ connect_timeout = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(connect_timeout);
+
+ } else if (param.first == "read-timeout") {
+ read_timeout = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(read_timeout);
+
+ } else if (param.first == "write-timeout") {
+ write_timeout = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(write_timeout);
+
+ } else if (param.first == "tcp-user-timeout") {
+ tcp_user_timeout = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(tcp_user_timeout);
+
+ } else if (param.first == "max-reconnect-tries") {
+ max_reconnect_tries = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(max_reconnect_tries);
+
+ } else if (param.first == "reconnect-wait-time") {
+ reconnect_wait_time = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(reconnect_wait_time);
+
+ } else if (param.first == "port") {
+ port = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(port);
+
+ } else if (param.first == "max-row-errors") {
+ max_row_errors = param.second->intValue();
+ values_copy[param.first] =
+ boost::lexical_cast<std::string>(max_row_errors);
+ } else {
+
+ // all remaining string parameters
+ // type
+ // user
+ // password
+ // host
+ // name
+ // on-fail
+ // trust-anchor
+ // cert-file
+ // key-file
+ // cipher-list
+ values_copy[param.first] = param.second->stringValue();
+ }
+ } catch (const isc::data::TypeError& ex) {
+ // Append position of the element.
+ isc_throw(DbConfigError, "invalid value type specified for "
+ "parameter '" << param.first << "' ("
+ << param.second->getPosition() << ")");
+ }
+ }
+
+ // 3. Perform validation checks on the updated set of keyword/values.
+ //
+ // a. Check if the "type" keyword exists and thrown an exception if not.
+ auto type_ptr = values_copy.find("type");
+ if (type_ptr == values_copy.end()) {
+ isc_throw(DbConfigError,
+ "database access parameters must "
+ "include the keyword 'type' to determine type of database "
+ "to be accessed (" << database_config->getPosition() << ")");
+ }
+
+ // b. Check if the 'type' keyword known and throw an exception if not.
+ //
+ // Please note when you add a new database backend you have to add
+ // the new type here and in server grammars.
+ string dbtype = type_ptr->second;
+ if ((dbtype != "memfile") &&
+ (dbtype != "mysql") &&
+ (dbtype != "postgresql")) {
+ ConstElementPtr value = database_config->get("type");
+ isc_throw(DbConfigError, "unknown backend database type: " << dbtype
+ << " (" << value->getPosition() << ")");
+ }
+
+ // c. Check that the lfc-interval is within a reasonable range.
+ if ((lfc_interval < 0) ||
+ (lfc_interval > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("lfc-interval");
+ isc_throw(DbConfigError, "lfc-interval value: " << lfc_interval
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+
+ // d. Check that the timeouts are within a reasonable range.
+ if ((connect_timeout < 0) ||
+ (connect_timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("connect-timeout");
+ isc_throw(DbConfigError, "connect-timeout value: " << connect_timeout
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+ if ((read_timeout < 0) ||
+ (read_timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("read-timeout");
+ isc_throw(DbConfigError, "read-timeout value: " << read_timeout
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+ if (read_timeout > 0 && (dbtype != "mysql")) {
+ ConstElementPtr value = database_config->get("read-timeout");
+ isc_throw(DbConfigError, "read-timeout value is only supported by the mysql backend"
+ << " (" << value->getPosition() << ")");
+ }
+ if ((write_timeout < 0) ||
+ (write_timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("write-timeout");
+ isc_throw(DbConfigError, "write-timeout value: " << write_timeout
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+ if (write_timeout > 0 && (dbtype != "mysql")) {
+ ConstElementPtr value = database_config->get("write-timeout");
+ isc_throw(DbConfigError, "write-timeout value is only supported by the mysql backend"
+ << " (" << value->getPosition() << ")");
+ }
+ if ((tcp_user_timeout < 0) ||
+ (tcp_user_timeout > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("tcp-user-timeout");
+ isc_throw(DbConfigError, "tcp-user-timeout value: " << tcp_user_timeout
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+ if (tcp_user_timeout > 0 && (dbtype != "postgresql")) {
+ ConstElementPtr value = database_config->get("tcp-user-timeout");
+ isc_throw(DbConfigError, "tcp-user-timeout value is only supported by the postgresql backend"
+ << " (" << value->getPosition() << ")");
+ }
+
+ // e. Check that the port is within a reasonable range.
+ if ((port < 0) ||
+ (port > std::numeric_limits<uint16_t>::max())) {
+ ConstElementPtr value = database_config->get("port");
+ isc_throw(DbConfigError, "port value: " << port
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint16_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+
+ // f. Check that the max-row-errors is within a reasonable range.
+ if ((max_row_errors < 0) ||
+ (max_row_errors > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("max-row-errors");
+ isc_throw(DbConfigError, "max-row-errors value: " << max_row_errors
+ << " is out of range, expected value: 0.."
+ << std::numeric_limits<uint32_t>::max()
+ << " (" << value->getPosition() << ")");
+ }
+
+ // Check that the max-reconnect-tries is reasonable.
+ if (max_reconnect_tries < 0) {
+ ConstElementPtr value = database_config->get("max-reconnect-tries");
+ isc_throw(DbConfigError,
+ "max-reconnect-tries cannot be less than zero: ("
+ << value->getPosition() << ")");
+ }
+
+ // Check that the reconnect-wait-time is reasonable.
+ if ((reconnect_wait_time < 0) ||
+ (reconnect_wait_time > std::numeric_limits<uint32_t>::max())) {
+ ConstElementPtr value = database_config->get("reconnect-wait-time");
+ isc_throw(DbConfigError, "reconnect-wait-time " << reconnect_wait_time
+ << " must be in range 0...MAX_UINT32 (4294967295) "
+ << "(" << value->getPosition() << ")");
+ }
+
+ // 4. If all is OK, update the stored keyword/value pairs. We do this by
+ // swapping contents - values_copy is destroyed immediately after the
+ // operation (when the method exits), so we are not interested in its new
+ // value.
+ values_.swap(values_copy);
+
+ // 5. Save the database access string in the Configuration Manager.
+ access_string = getDbAccessString();
+}
+
+// Create the database access string
+std::string
+DbAccessParser::getDbAccessString() const {
+
+ // Construct the database access string from all keywords and values in the
+ // parameter map where the value is not null.
+ string dbaccess;
+ for (auto keyval : values_) {
+ if (!keyval.second.empty()) {
+
+ // Separate keyword/value pair from predecessor (if there is one).
+ if (!dbaccess.empty()) {
+ dbaccess += std::string(" ");
+ }
+
+ // Add the keyword/value pair to the access string.
+ auto val = keyval.second;
+ if (val.find_first_of("\t ") != string::npos){
+ val = "'" + val + "'";
+ }
+ dbaccess += (keyval.first + std::string("=") + val);
+ }
+ }
+
+ return (dbaccess);
+}
+
+} // namespace db
+} // namespace isc
diff --git a/src/lib/database/dbaccess_parser.h b/src/lib/database/dbaccess_parser.h
new file mode 100644
index 0000000..30a4ec9
--- /dev/null
+++ b/src/lib/database/dbaccess_parser.h
@@ -0,0 +1,83 @@
+// Copyright (C) 2012-2023 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 DBACCESS_PARSER_H
+#define DBACCESS_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <database/database_connection.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+namespace isc {
+namespace db {
+
+/// @brief Parse Database Parameters
+///
+/// This class is the parser for the database configuration. This is a
+/// map under the top-level "lease-database", "hosts-database" and
+/// "config-database" elements, and comprises a map of strings.
+class DbAccessParser: public isc::data::SimpleParser {
+public:
+ /// @brief Constructor
+ DbAccessParser();
+
+ /// The destructor.
+ virtual ~DbAccessParser()
+ {}
+
+ /// @brief Parse configuration value.
+ ///
+ /// Parses the set of strings forming the database access specification and
+ /// checks that all are OK. In particular it checks:
+ ///
+ /// - "type" is "memfile", "mysql" or "postgresql"
+ /// - "lfc-interval" is a number from the range of 0 to 4294967295.
+ /// - "connect-timeout" is a number from the range of 0 to 4294967295.
+ /// - "port" is a number from the range of 0 to 65535.
+ ///
+ /// Once all has been validated, constructs the database access string.
+ ///
+ /// @param [out] access_string Generated database access string.
+ /// @param database_config The configuration value for the "*-database"
+ /// identifier.
+ ///
+ /// @throw isc::dhcp::DbConfigError The connection parameters or their
+ /// combination is invalid.
+ void parse(std::string& access_string,
+ isc::data::ConstElementPtr database_config);
+
+ /// @brief Get database access parameters
+ ///
+ /// Used in testing to check that the configuration information has been
+ /// parsed correctly.
+ ///
+ /// @return Reference to the internal map of keyword/value pairs
+ /// representing database access information. This is valid only
+ /// for so long as the parser remains in existence.
+ const DatabaseConnection::ParameterMap& getDbAccessParameters() const {
+ return (values_);
+ }
+protected:
+
+ /// @brief Construct database access string
+ ///
+ /// Constructs the database access string from the stored parameters.
+ ///
+ /// @return Database access string
+ std::string getDbAccessString() const;
+
+private:
+
+ DatabaseConnection::ParameterMap values_; ///< Stored parameter values
+};
+
+} // namespace db
+} // namespace isc
+
+#endif // DBACCESS_PARSER_H
diff --git a/src/lib/database/server.cc b/src/lib/database/server.cc
new file mode 100644
index 0000000..1b6fcd1
--- /dev/null
+++ b/src/lib/database/server.cc
@@ -0,0 +1,44 @@
+// Copyright (C) 2019-2020 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 <database/server.h>
+#include <exceptions/exceptions.h>
+#include <boost/make_shared.hpp>
+
+using namespace isc::db;
+using namespace isc::data;
+
+namespace isc {
+namespace db {
+
+Server::Server(const ServerTag& tag, const std::string& description)
+ : BaseStampedElement(), server_tag_(tag), description_(description) {
+
+ if (description_.length() > 65536) {
+ isc_throw(BadValue, "server description must not be longer than"
+ " 65536 characters");
+ }
+}
+
+ServerPtr
+Server::create(const ServerTag& tag, const std::string& description) {
+ return (boost::make_shared<Server>(tag, description));
+}
+
+ElementPtr
+Server::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ result->set("server-tag", Element::create(getServerTagAsText()));
+ result->set("description", Element::create(getDescription()));
+
+ return (result);
+}
+
+} // end of namespace isc::db
+} // end of namespace isc
diff --git a/src/lib/database/server.h b/src/lib/database/server.h
new file mode 100644
index 0000000..8ca8a43
--- /dev/null
+++ b/src/lib/database/server.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2019 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 DB_SERVER_H
+#define DB_SERVER_H
+
+#include <cc/base_stamped_element.h>
+#include <cc/cfg_to_element.h>
+#include <cc/server_tag.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace db {
+
+class Server;
+
+/// @brief Shared pointer to the @c Server class.
+typedef boost::shared_ptr<Server> ServerPtr;
+
+/// @brief Represents information about a Kea server in the database.
+///
+/// The configuration backend holds the information about the servers
+/// for which the backend holds the configuration information. The
+/// information includes the server tag (unique name), server description
+/// provided by the administrator and the metadata.
+///
+/// This class extends the base class with the server description field.
+class Server : public data::BaseStampedElement, public data::CfgToElement {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param tag server tag.
+ /// @param description server description.
+ Server(const data::ServerTag& tag, const std::string& description);
+
+ /// @brief Factory function to be used to create an instance of the
+ /// @c Server object.
+ ///
+ /// @param tag server tag.
+ /// @param description server description.
+ ///
+ /// @return Pointer to the server instance.
+ /// @throw BadValue if the server tag exceeds 256 characters or the
+ /// description exceeds 65536 characters.
+ static ServerPtr create(const data::ServerTag& tag,
+ const std::string& description = "");
+
+ /// @brief Returns server tag.
+ data::ServerTag getServerTag() const {
+ return (server_tag_);
+ }
+
+ /// @brief Returns server tag as text.
+ ///
+ /// @return Server tag as text.
+ std::string getServerTagAsText() const {
+ return (server_tag_.get());
+ }
+
+ /// @brief Returns the description of the server.
+ ///
+ /// @return Description of the server or an empty string if no
+ /// description was specified.
+ std::string getDescription() const {
+ return (description_);
+ }
+
+ /// @brief Unparses server object.
+ ///
+ /// @return A pointer to unparsed server configuration.
+ virtual data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief Server tag.
+ data::ServerTag server_tag_;
+
+ /// @brief Description of the server.
+ std::string description_;
+};
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif // DB_SERVER_H
diff --git a/src/lib/database/server_collection.cc b/src/lib/database/server_collection.cc
new file mode 100644
index 0000000..0785bdf
--- /dev/null
+++ b/src/lib/database/server_collection.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2019-2020 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 <database/server_collection.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace db {
+
+ServerPtr
+ServerFetcher::get(const ServerCollection& collection,
+ const ServerTag& server_tag) {
+ auto& index = collection.get<ServerTagIndexTag>();
+ auto s = index.find(server_tag.get());
+ if (s != index.end()) {
+ return (*s);
+ }
+ return (ServerPtr());
+}
+
+} // end of namespace isc::db
+} // end of namespace isc
diff --git a/src/lib/database/server_collection.h b/src/lib/database/server_collection.h
new file mode 100644
index 0000000..fe9d91f
--- /dev/null
+++ b/src/lib/database/server_collection.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2019 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 DB_SERVER_COLLECTION_H
+#define DB_SERVER_COLLECTION_H
+
+#include <database/server.h>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index_container.hpp>
+
+namespace isc {
+namespace db {
+
+/// @brief Tag identifying an index by server tag.
+struct ServerTagIndexTag { };
+
+/// @brief Multi index container for @c Server.
+///
+/// It merely contains one index at the moment, but the number of
+/// indexes is likely to grow.
+typedef boost::multi_index_container<
+ ServerPtr,
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<ServerTagIndexTag>,
+ boost::multi_index::const_mem_fun<Server, std::string,
+ &Server::getServerTagAsText>
+ >
+ >
+> ServerCollection;
+
+/// @brief Utility class used to fetch @c Server objects from the
+/// @c ServerCollection.
+class ServerFetcher {
+public:
+
+ /// @brief Fetches server from the collection by tag.
+ ///
+ /// @return Pointer to the @c Server object or null if no such object
+ /// was found.
+ static ServerPtr get(const ServerCollection& collection,
+ const data::ServerTag& server_tag);
+};
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/database/server_selector.cc b/src/lib/database/server_selector.cc
new file mode 100644
index 0000000..18fbdbd
--- /dev/null
+++ b/src/lib/database/server_selector.cc
@@ -0,0 +1,51 @@
+// Copyright (C) 2019-2020 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 <database/server_selector.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace db {
+
+ServerSelector
+ServerSelector::MULTIPLE(const std::set<std::string>& server_tags) {
+ if (server_tags.empty()) {
+ isc_throw(InvalidOperation, "ServerSelector: expecting at least one"
+ " server tag");
+ }
+
+ std::set<ServerTag> tags;
+
+ // Create a set of tags from strings.
+ for (auto tag : server_tags) {
+ tags.insert(ServerTag(tag));
+ }
+
+ ServerSelector selector(tags);
+ return (selector);
+}
+
+ServerSelector::ServerSelector(const Type& type)
+ : type_(type), tags_() {
+ if (type_ == Type::ALL) {
+ tags_.insert(ServerTag());
+ }
+}
+
+ServerSelector::ServerSelector(const ServerTag& server_tag)
+ : type_(server_tag.amAll() ? Type::ALL : Type::SUBSET), tags_({server_tag}) {
+}
+
+ServerSelector::ServerSelector(const std::set<ServerTag>& server_tags)
+ : type_(Type::SUBSET), tags_(server_tags) {
+}
+
+} // end of namespace isc::db
+} // end of namespace isc
diff --git a/src/lib/database/server_selector.h b/src/lib/database/server_selector.h
new file mode 100644
index 0000000..a9dcccc
--- /dev/null
+++ b/src/lib/database/server_selector.h
@@ -0,0 +1,165 @@
+// Copyright (C) 2018-2019,2021 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 SERVER_SELECTOR_H
+#define SERVER_SELECTOR_H
+
+#include <cc/server_tag.h>
+#include <set>
+#include <string>
+
+namespace isc {
+namespace db {
+
+/// @brief Server selector for associating objects in a database with
+/// specific servers.
+///
+/// Configuration information stored in the configuration backends can be
+/// associated with selected servers, all servers or no particular server.
+/// For example: a particular subnet definition in the database may be
+/// associated with one server or can be shared by multiple servers.
+/// In the latter case, a subnet may be associated with a subset of
+/// servers or all servers. An administrator may also add the
+/// configuration data into the database and do not associate this data
+/// with any particular server.
+///
+/// When fetching the configuration data from a database or when storing
+/// data in the database there is a need to specify which servers this
+/// data is associated with. The @c ServerSelector class represents
+/// such associations.
+///
+/// It includes three modes of selection: UNASSIGNED, ALL and SUBSET and
+/// several factory functions making associations described above.
+///
+/// The @c ServerSelector class should be used in objects derived from
+/// @c BaseConfigBackendPool and in objects derived from
+/// @c BaseConfigBackend to indicate which servers the specific calls
+/// exposed by these objects refer to.
+///
+/// @todo Add server selector for selecting only those configuration
+/// elements that are associated with all servers (and not with any
+/// particular server tags). Translating this to SQL queries it would
+/// probably be an empty or non-existing server tag.
+class ServerSelector {
+public:
+
+ /// @brief Type of the server selection.
+ enum class Type {
+ UNASSIGNED,
+ ALL,
+ SUBSET,
+ ANY
+ };
+
+ /// @brief Factory returning "unassigned" server selector.
+ static ServerSelector UNASSIGNED() {
+ ServerSelector selector(Type::UNASSIGNED);
+ return (selector);
+ }
+
+ /// @brief Factory returning "all servers" selector.
+ static ServerSelector ALL() {
+ ServerSelector selector(Type::ALL);
+ return (selector);
+ }
+
+ /// @brief Factory returning selector of one server.
+ ///
+ /// @param server_tag tag of the single server to be selected.
+ static ServerSelector ONE(const std::string& server_tag) {
+ ServerSelector selector((data::ServerTag(server_tag)));
+ return (selector);
+ }
+
+ /// @brief Factory returning "multiple servers" selector.
+ ///
+ /// @param server_tags set of server tags to be selected.
+ /// @throw InvalidOperation if no server tags provided.
+ static ServerSelector MULTIPLE(const std::set<std::string>& server_tags);
+
+ /// @brief Factory returning "any server" selector.
+ static ServerSelector ANY() {
+ ServerSelector selector(Type::ANY);
+ return (selector);
+ }
+
+ /// @brief Returns type of the selector.
+ Type getType() const {
+ return (type_);
+ }
+
+ /// @brief Returns tags associated with the selector.
+ ///
+ /// @return server tags for multiple selections and for one server,
+ /// empty set for all servers and and unassigned.
+ std::set<data::ServerTag> getTags() const {
+ return (tags_);
+ }
+
+ /// @brief Convenience method checking if the server selector has no tags.
+ ///
+ /// @return true when the server selector has no tags, false otherwise.
+ bool hasNoTags() const {
+ return (tags_.empty());
+ }
+
+ /// @brief Convenience method checking if the server selector is "unassigned".
+ ///
+ /// @return true if the selector is "unassigned", false otherwise.
+ bool amUnassigned() const {
+ return (getType() == Type::UNASSIGNED);
+ }
+
+ /// @brief Convenience method checking if the server selector is "all".
+ ///
+ /// @return true if the selector is "all", false otherwise.
+ bool amAll() const {
+ return (getType() == Type::ALL);
+ }
+
+ /// @brief Convenience method checking if the server selector is "any".
+ ///
+ /// @return true if the selector is "any", false otherwise.
+ bool amAny() const {
+ return (getType() == Type::ANY);
+ }
+
+ /// @brief Convenience method checking if the server selector has multiple tags.
+ ///
+ /// @return true if it has multiple tags, false otherwise.
+ bool hasMultipleTags() const {
+ return (tags_.size() > 1);
+ }
+
+private:
+
+ /// @brief Constructor used for "unassigned" and "all" selection types.
+ ///
+ /// @param type selector type.
+ explicit ServerSelector(const Type& type);
+
+ /// @brief Constructor used for selecting a single server.
+ ///
+ /// @param server_tag tag of the server to be selected.
+ explicit ServerSelector(const data::ServerTag& server_tag);
+
+ /// @brief Constructor used for selecting multiple servers.
+ ///
+ /// @param server_tags set of server tags.
+ explicit ServerSelector(const std::set<data::ServerTag>& server_tags);
+
+ /// @brief Selection type used.
+ Type type_;
+
+ /// @brief Holds tags of explicitly selected servers.
+ std::set<data::ServerTag> tags_;
+};
+
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/database/tests/Makefile.am b/src/lib/database/tests/Makefile.am
new file mode 100644
index 0000000..f4cf4f0
--- /dev/null
+++ b/src/lib/database/tests/Makefile.am
@@ -0,0 +1,44 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\"
+
+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 += libdatabase_unittests
+
+libdatabase_unittests_SOURCES = audit_entry_unittest.cc
+libdatabase_unittests_SOURCES += backend_selector_unittest.cc
+libdatabase_unittests_SOURCES += database_connection_unittest.cc
+libdatabase_unittests_SOURCES += database_log_unittest.cc
+libdatabase_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdatabase_unittests_SOURCES += run_unittests.cc
+libdatabase_unittests_SOURCES += server_unittest.cc
+libdatabase_unittests_SOURCES += server_selector_unittest.cc
+
+libdatabase_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libdatabase_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libdatabase_unittests_LDADD = $(top_builddir)/src/lib/database/libkea-database.la
+libdatabase_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+libdatabase_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdatabase_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdatabase_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdatabase_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdatabase_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdatabase_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/database/tests/Makefile.in b/src/lib/database/tests/Makefile.in
new file mode 100644
index 0000000..a1c7c64
--- /dev/null
+++ b/src/lib/database/tests/Makefile.in
@@ -0,0 +1,1130 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libdatabase_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/database/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libdatabase_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libdatabase_unittests_SOURCES_DIST = audit_entry_unittest.cc \
+ backend_selector_unittest.cc database_connection_unittest.cc \
+ database_log_unittest.cc dbaccess_parser_unittest.cc \
+ run_unittests.cc server_unittest.cc \
+ server_selector_unittest.cc
+@HAVE_GTEST_TRUE@am_libdatabase_unittests_OBJECTS = libdatabase_unittests-audit_entry_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdatabase_unittests-backend_selector_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdatabase_unittests-database_connection_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdatabase_unittests-database_log_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdatabase_unittests-dbaccess_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdatabase_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdatabase_unittests-server_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdatabase_unittests-server_selector_unittest.$(OBJEXT)
+libdatabase_unittests_OBJECTS = $(am_libdatabase_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libdatabase_unittests_DEPENDENCIES = $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(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 =
+libdatabase_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libdatabase_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)/libdatabase_unittests-audit_entry_unittest.Po \
+ ./$(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Po \
+ ./$(DEPDIR)/libdatabase_unittests-database_connection_unittest.Po \
+ ./$(DEPDIR)/libdatabase_unittests-database_log_unittest.Po \
+ ./$(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Po \
+ ./$(DEPDIR)/libdatabase_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libdatabase_unittests-server_selector_unittest.Po \
+ ./$(DEPDIR)/libdatabase_unittests-server_unittest.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 =
+SOURCES = $(libdatabase_unittests_SOURCES)
+DIST_SOURCES = $(am__libdatabase_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/config/tests\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libdatabase_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ audit_entry_unittest.cc \
+@HAVE_GTEST_TRUE@ backend_selector_unittest.cc \
+@HAVE_GTEST_TRUE@ database_connection_unittest.cc \
+@HAVE_GTEST_TRUE@ database_log_unittest.cc \
+@HAVE_GTEST_TRUE@ dbaccess_parser_unittest.cc run_unittests.cc \
+@HAVE_GTEST_TRUE@ server_unittest.cc \
+@HAVE_GTEST_TRUE@ server_selector_unittest.cc
+@HAVE_GTEST_TRUE@libdatabase_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libdatabase_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libdatabase_unittests_LDADD = $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(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/database/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/database/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
+
+libdatabase_unittests$(EXEEXT): $(libdatabase_unittests_OBJECTS) $(libdatabase_unittests_DEPENDENCIES) $(EXTRA_libdatabase_unittests_DEPENDENCIES)
+ @rm -f libdatabase_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libdatabase_unittests_LINK) $(libdatabase_unittests_OBJECTS) $(libdatabase_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-audit_entry_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-database_connection_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-database_log_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-server_selector_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabase_unittests-server_unittest.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 $@ $<
+
+libdatabase_unittests-audit_entry_unittest.o: audit_entry_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-audit_entry_unittest.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-audit_entry_unittest.Tpo -c -o libdatabase_unittests-audit_entry_unittest.o `test -f 'audit_entry_unittest.cc' || echo '$(srcdir)/'`audit_entry_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-audit_entry_unittest.Tpo $(DEPDIR)/libdatabase_unittests-audit_entry_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='audit_entry_unittest.cc' object='libdatabase_unittests-audit_entry_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-audit_entry_unittest.o `test -f 'audit_entry_unittest.cc' || echo '$(srcdir)/'`audit_entry_unittest.cc
+
+libdatabase_unittests-audit_entry_unittest.obj: audit_entry_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-audit_entry_unittest.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-audit_entry_unittest.Tpo -c -o libdatabase_unittests-audit_entry_unittest.obj `if test -f 'audit_entry_unittest.cc'; then $(CYGPATH_W) 'audit_entry_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/audit_entry_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-audit_entry_unittest.Tpo $(DEPDIR)/libdatabase_unittests-audit_entry_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='audit_entry_unittest.cc' object='libdatabase_unittests-audit_entry_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-audit_entry_unittest.obj `if test -f 'audit_entry_unittest.cc'; then $(CYGPATH_W) 'audit_entry_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/audit_entry_unittest.cc'; fi`
+
+libdatabase_unittests-backend_selector_unittest.o: backend_selector_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-backend_selector_unittest.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Tpo -c -o libdatabase_unittests-backend_selector_unittest.o `test -f 'backend_selector_unittest.cc' || echo '$(srcdir)/'`backend_selector_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Tpo $(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='backend_selector_unittest.cc' object='libdatabase_unittests-backend_selector_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-backend_selector_unittest.o `test -f 'backend_selector_unittest.cc' || echo '$(srcdir)/'`backend_selector_unittest.cc
+
+libdatabase_unittests-backend_selector_unittest.obj: backend_selector_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-backend_selector_unittest.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Tpo -c -o libdatabase_unittests-backend_selector_unittest.obj `if test -f 'backend_selector_unittest.cc'; then $(CYGPATH_W) 'backend_selector_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/backend_selector_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Tpo $(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='backend_selector_unittest.cc' object='libdatabase_unittests-backend_selector_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-backend_selector_unittest.obj `if test -f 'backend_selector_unittest.cc'; then $(CYGPATH_W) 'backend_selector_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/backend_selector_unittest.cc'; fi`
+
+libdatabase_unittests-database_connection_unittest.o: database_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-database_connection_unittest.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-database_connection_unittest.Tpo -c -o libdatabase_unittests-database_connection_unittest.o `test -f 'database_connection_unittest.cc' || echo '$(srcdir)/'`database_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-database_connection_unittest.Tpo $(DEPDIR)/libdatabase_unittests-database_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='database_connection_unittest.cc' object='libdatabase_unittests-database_connection_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-database_connection_unittest.o `test -f 'database_connection_unittest.cc' || echo '$(srcdir)/'`database_connection_unittest.cc
+
+libdatabase_unittests-database_connection_unittest.obj: database_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-database_connection_unittest.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-database_connection_unittest.Tpo -c -o libdatabase_unittests-database_connection_unittest.obj `if test -f 'database_connection_unittest.cc'; then $(CYGPATH_W) 'database_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/database_connection_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-database_connection_unittest.Tpo $(DEPDIR)/libdatabase_unittests-database_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='database_connection_unittest.cc' object='libdatabase_unittests-database_connection_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-database_connection_unittest.obj `if test -f 'database_connection_unittest.cc'; then $(CYGPATH_W) 'database_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/database_connection_unittest.cc'; fi`
+
+libdatabase_unittests-database_log_unittest.o: database_log_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-database_log_unittest.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-database_log_unittest.Tpo -c -o libdatabase_unittests-database_log_unittest.o `test -f 'database_log_unittest.cc' || echo '$(srcdir)/'`database_log_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-database_log_unittest.Tpo $(DEPDIR)/libdatabase_unittests-database_log_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='database_log_unittest.cc' object='libdatabase_unittests-database_log_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-database_log_unittest.o `test -f 'database_log_unittest.cc' || echo '$(srcdir)/'`database_log_unittest.cc
+
+libdatabase_unittests-database_log_unittest.obj: database_log_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-database_log_unittest.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-database_log_unittest.Tpo -c -o libdatabase_unittests-database_log_unittest.obj `if test -f 'database_log_unittest.cc'; then $(CYGPATH_W) 'database_log_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/database_log_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-database_log_unittest.Tpo $(DEPDIR)/libdatabase_unittests-database_log_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='database_log_unittest.cc' object='libdatabase_unittests-database_log_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-database_log_unittest.obj `if test -f 'database_log_unittest.cc'; then $(CYGPATH_W) 'database_log_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/database_log_unittest.cc'; fi`
+
+libdatabase_unittests-dbaccess_parser_unittest.o: dbaccess_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-dbaccess_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Tpo -c -o libdatabase_unittests-dbaccess_parser_unittest.o `test -f 'dbaccess_parser_unittest.cc' || echo '$(srcdir)/'`dbaccess_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Tpo $(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dbaccess_parser_unittest.cc' object='libdatabase_unittests-dbaccess_parser_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-dbaccess_parser_unittest.o `test -f 'dbaccess_parser_unittest.cc' || echo '$(srcdir)/'`dbaccess_parser_unittest.cc
+
+libdatabase_unittests-dbaccess_parser_unittest.obj: dbaccess_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-dbaccess_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Tpo -c -o libdatabase_unittests-dbaccess_parser_unittest.obj `if test -f 'dbaccess_parser_unittest.cc'; then $(CYGPATH_W) 'dbaccess_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dbaccess_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Tpo $(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dbaccess_parser_unittest.cc' object='libdatabase_unittests-dbaccess_parser_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-dbaccess_parser_unittest.obj `if test -f 'dbaccess_parser_unittest.cc'; then $(CYGPATH_W) 'dbaccess_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dbaccess_parser_unittest.cc'; fi`
+
+libdatabase_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-run_unittests.Tpo -c -o libdatabase_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-run_unittests.Tpo $(DEPDIR)/libdatabase_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdatabase_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libdatabase_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-run_unittests.Tpo -c -o libdatabase_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)/libdatabase_unittests-run_unittests.Tpo $(DEPDIR)/libdatabase_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdatabase_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libdatabase_unittests-server_unittest.o: server_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-server_unittest.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-server_unittest.Tpo -c -o libdatabase_unittests-server_unittest.o `test -f 'server_unittest.cc' || echo '$(srcdir)/'`server_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-server_unittest.Tpo $(DEPDIR)/libdatabase_unittests-server_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_unittest.cc' object='libdatabase_unittests-server_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-server_unittest.o `test -f 'server_unittest.cc' || echo '$(srcdir)/'`server_unittest.cc
+
+libdatabase_unittests-server_unittest.obj: server_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-server_unittest.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-server_unittest.Tpo -c -o libdatabase_unittests-server_unittest.obj `if test -f 'server_unittest.cc'; then $(CYGPATH_W) 'server_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-server_unittest.Tpo $(DEPDIR)/libdatabase_unittests-server_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_unittest.cc' object='libdatabase_unittests-server_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-server_unittest.obj `if test -f 'server_unittest.cc'; then $(CYGPATH_W) 'server_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_unittest.cc'; fi`
+
+libdatabase_unittests-server_selector_unittest.o: server_selector_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-server_selector_unittest.o -MD -MP -MF $(DEPDIR)/libdatabase_unittests-server_selector_unittest.Tpo -c -o libdatabase_unittests-server_selector_unittest.o `test -f 'server_selector_unittest.cc' || echo '$(srcdir)/'`server_selector_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-server_selector_unittest.Tpo $(DEPDIR)/libdatabase_unittests-server_selector_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_selector_unittest.cc' object='libdatabase_unittests-server_selector_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-server_selector_unittest.o `test -f 'server_selector_unittest.cc' || echo '$(srcdir)/'`server_selector_unittest.cc
+
+libdatabase_unittests-server_selector_unittest.obj: server_selector_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libdatabase_unittests-server_selector_unittest.obj -MD -MP -MF $(DEPDIR)/libdatabase_unittests-server_selector_unittest.Tpo -c -o libdatabase_unittests-server_selector_unittest.obj `if test -f 'server_selector_unittest.cc'; then $(CYGPATH_W) 'server_selector_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_selector_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabase_unittests-server_selector_unittest.Tpo $(DEPDIR)/libdatabase_unittests-server_selector_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_selector_unittest.cc' object='libdatabase_unittests-server_selector_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) $(libdatabase_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libdatabase_unittests-server_selector_unittest.obj `if test -f 'server_selector_unittest.cc'; then $(CYGPATH_W) 'server_selector_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_selector_unittest.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)/libdatabase_unittests-audit_entry_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-database_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-database_log_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-server_selector_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-server_unittest.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)/libdatabase_unittests-audit_entry_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-backend_selector_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-database_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-database_log_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-dbaccess_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-server_selector_unittest.Po
+ -rm -f ./$(DEPDIR)/libdatabase_unittests-server_unittest.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/database/tests/audit_entry_unittest.cc b/src/lib/database/tests/audit_entry_unittest.cc
new file mode 100644
index 0000000..1d41872
--- /dev/null
+++ b/src/lib/database/tests/audit_entry_unittest.cc
@@ -0,0 +1,362 @@
+// Copyright (C) 2019-2020 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 <database/audit_entry.h>
+#include <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::db;
+
+namespace {
+
+/// @brief Test fixture class for testing @c AuditEntry.
+class AuditEntryTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ AuditEntryTest()
+ : fixed_time_(now()) {
+ }
+
+ /// @brief Returns current time.
+ static boost::posix_time::ptime now() {
+ return (boost::posix_time::microsec_clock::local_time());
+ }
+
+ /// @brief Returns always the same time value.
+ ///
+ /// The value is initialized when the test it started.
+ boost::posix_time::ptime fixedTime() const {
+ return (fixed_time_);
+ }
+
+ /// @brief Checks if the given time value is "close" to the
+ /// current time.
+ ///
+ /// This is useful in tests when the @c AuditEntry class sets the
+ /// modification time to a default value (in its constructor).
+ /// Because the test doesn't know the exact value to which the
+ /// modification time is set, it merely checks that this value
+ /// is earlier than current time and within the range of 1s.
+ ///
+ /// @param t time value to be checked.
+ bool almostEqualTime(const boost::posix_time::ptime& t) const {
+ auto current = now();
+
+ // The provided value must be a valid time.
+ if (t.is_not_a_date_time()) {
+ ADD_FAILURE() << "provided value is not a date time";
+ return (false);
+ }
+
+ // It must be earlier than current time.
+ if (t > current) {
+ ADD_FAILURE() << "provided time value is later than current time";
+ return (false);
+ }
+
+ // The difference must be lower than 1 second.
+ boost::posix_time::time_duration dur = current - t;
+ return (dur.total_milliseconds() < 1000);
+ }
+
+ /// @brief Fixed time value initialized in the constructor.
+ ///
+ /// This is used in tests that require the exact time values.
+ boost::posix_time::ptime fixed_time_;
+};
+
+// Checks that the modification time value can be cast to a number.
+TEST_F(AuditEntryTest, modificationType) {
+ EXPECT_EQ(0, static_cast<int>(AuditEntry::ModificationType::CREATE));
+ EXPECT_EQ(1, static_cast<int>(AuditEntry::ModificationType::UPDATE));
+ EXPECT_EQ(2, static_cast<int>(AuditEntry::ModificationType::DELETE));
+}
+
+// Checks that the audit entry can be created.
+TEST_F(AuditEntryTest, create) {
+
+ AuditEntryPtr audit_entry;
+
+ {
+ SCOPED_TRACE("create with modification time");
+
+ ASSERT_NO_THROW(audit_entry = AuditEntry::create
+ ("dhcp4_subnet", 10, AuditEntry::ModificationType::DELETE,
+ fixedTime(), 123, "deleted subnet 10"));
+ EXPECT_EQ("dhcp4_subnet", audit_entry->getObjectType());
+ EXPECT_EQ(10, audit_entry->getObjectId());
+ EXPECT_EQ(AuditEntry::ModificationType::DELETE, audit_entry->getModificationType());
+ EXPECT_EQ(fixedTime(), audit_entry->getModificationTime());
+ EXPECT_EQ(123, audit_entry->getRevisionId());
+ EXPECT_EQ("deleted subnet 10", audit_entry->getLogMessage());
+ }
+
+ {
+ SCOPED_TRACE("create with default modification time");
+
+ ASSERT_NO_THROW(audit_entry = AuditEntry::create
+ ("dhcp4_option", 123, AuditEntry::ModificationType::CREATE,
+ 234, ""));
+ EXPECT_EQ("dhcp4_option", audit_entry->getObjectType());
+ EXPECT_EQ(123, audit_entry->getObjectId());
+ EXPECT_EQ(AuditEntry::ModificationType::CREATE, audit_entry->getModificationType());
+ EXPECT_TRUE(almostEqualTime(audit_entry->getModificationTime()));
+ EXPECT_EQ(234, audit_entry->getRevisionId());
+ EXPECT_TRUE(audit_entry->getLogMessage().empty());
+ }
+}
+
+// Checks that invalid values for the audit entry are rejected.
+TEST_F(AuditEntryTest, createFailures) {
+ {
+ SCOPED_TRACE("empty object type");
+ EXPECT_THROW(AuditEntry("", 10, AuditEntry::ModificationType::DELETE,
+ fixedTime(), 123, "deleted subnet 10"),
+ BadValue);
+ }
+
+ {
+ SCOPED_TRACE("not a date time");
+ EXPECT_THROW(AuditEntry("dhcp4_subnet", 10,
+ AuditEntry::ModificationType::DELETE,
+ boost::posix_time::ptime(), 123,
+ "deleted subnet 10"),
+ BadValue);
+ }
+}
+
+/// @brief Test fixture class for testing @c AuditEntryCollection.
+class AuditEntryCollectionTest : public AuditEntryTest {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates a collection of audit entries used in the tests.
+ AuditEntryCollectionTest()
+ : AuditEntryTest(), audit_entries_() {
+ createTestAuditEntries();
+ }
+
+ /// @brief Returns a time value being being a specified number of
+ /// seconds later or earlier than the time returned by @c fixedTime.
+ ///
+ /// @param secs offset in seconds since the @c fixedTime output. If
+ /// the parameter is negative, the returned time is earlier than the
+ /// fixed time. Otherwise, it is later than fixed time.
+ boost::posix_time::ptime diffTime(const long secs) {
+ if (secs < 0) {
+ return (fixedTime() - boost::posix_time::seconds(-secs));
+ }
+ return (fixedTime() + boost::posix_time::seconds(secs));
+ }
+
+ /// @brief Creates an @c AuditEntry instance and inserts it to
+ /// the @c audit_entries_ collection.
+ ///
+ /// @tparam Args types of the arguments to be passed to the @c AuditEntry
+ /// constructors.
+ /// @param args arguments to be passed to the @c AuditEntry constructors.
+ template<typename... Args>
+ void create(Args&& ...args) {
+ audit_entries_.insert(boost::make_shared<AuditEntry>(args...));
+ }
+
+ /// @brief Creates a collection of @c AuditEntry objects to be used by
+ /// the tests.
+ void createTestAuditEntries() {
+ create("dhcp4_subnet", 10, AuditEntry::ModificationType::CREATE,
+ diffTime(-5), 100, "added subnet 10");
+ create("dhcp4_shared_network", 1, AuditEntry::ModificationType::CREATE,
+ diffTime(-5), 110, "added shared network 1");
+ create("dhcp4_shared_network", 120, AuditEntry::ModificationType::UPDATE,
+ diffTime(-8), 90, "updated shared network 120");
+ create("dhcp4_subnet", 120, AuditEntry::ModificationType::DELETE,
+ diffTime(8), 130, "deleted subnet 120");
+ create("dhcp4_subnet", 1000, AuditEntry::ModificationType::CREATE,
+ diffTime(4), 120, "created subnet 1000");
+ create("dhcp4_option", 15, AuditEntry::ModificationType::UPDATE,
+ diffTime(16), 140, "updated option 15");
+ }
+
+ /// @brief Checks if the returned results range contains an @c AuditEntry
+ /// with a given object type and identifier.
+ ///
+ /// @param object_type expected object type.
+ /// @param object_id expected object id.
+ /// @param begin beginning of the results range to be examined.
+ /// @param end end of the results range to be examined.
+ template<typename Iterator>
+ bool includes(const std::string& object_type, const uint64_t object_id,
+ Iterator begin, Iterator end) {
+ // Iterate over the results range and look for the entry.
+ for (auto it = begin; it != end; ++it) {
+ if (((*it)->getObjectType() == object_type) &&
+ ((*it)->getObjectId() == object_id)) {
+ // Entry found.
+ return (true);
+ }
+ }
+
+ // Entry not found.
+ return (false);
+ }
+
+ /// @brief Checks if the returned results range contains an @c AuditEntry
+ /// with a given object and modification types, and object identifier.
+ ///
+ /// @param object_type expected object type.
+ /// @param object_id expected object id.
+ /// @param modification_type expected modification type.
+ /// @param begin beginning of the results range to be examined.
+ /// @param end end of the results range to be examined.
+ template<typename Iterator>
+ bool includes(const std::string& object_type, const uint64_t object_id,
+ const AuditEntry::ModificationType& modification_type,
+ Iterator begin, Iterator end) {
+ // Iterate over the results range and look for the entry.
+ for (auto it = begin; it != end; ++it) {
+ if (((*it)->getObjectType() == object_type) &&
+ ((*it)->getObjectId() == object_id) &&
+ ((*it)->getModificationType() == modification_type)) {
+ // Entry found.
+ return (true);
+ }
+ }
+
+ // Entry not found.
+ return (false);
+ }
+
+ /// @brief Audit entries used in the tests.
+ AuditEntryCollection audit_entries_;
+
+};
+
+// Checks that entries can be found by object type.
+TEST_F(AuditEntryCollectionTest, getByObjectType) {
+ const auto& object_type_idx = audit_entries_.get<AuditEntryObjectTypeTag>();
+
+ // Search for "dhcp4_subnet" objects.
+ auto range = object_type_idx.equal_range("dhcp4_subnet");
+ ASSERT_EQ(3, std::distance(range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_subnet", 10, range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_subnet", 120, range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_subnet", 1000, range.first, range.second));
+
+ // Search for "dhcp4_shared_network" objects.
+ range = object_type_idx.equal_range("dhcp4_shared_network");
+ ASSERT_EQ(2, std::distance(range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_shared_network", 1, range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_shared_network", 120, range.first, range.second));
+
+ // Search for "dhcp4_option" objects.
+ range = object_type_idx.equal_range("dhcp4_option");
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_option", 15, range.first, range.second));
+}
+
+// Checks that entries can be found by modification time.
+TEST_F(AuditEntryCollectionTest, getByModificationTime) {
+ const auto& mod_time_idx = audit_entries_.get<AuditEntryModificationTimeIdTag>();
+
+ // Search for objects later than fixed time - 10s.
+ auto lb = mod_time_idx.lower_bound(diffTime(-10));
+ ASSERT_EQ(6, std::distance(lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 10, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_shared_network", 120, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+
+ // Search for objects later than fixed time - 7s.
+ lb = mod_time_idx.lower_bound(diffTime(-7));
+ ASSERT_EQ(5, std::distance(lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 10, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+
+ // Search for objects later than fixed time - 1s.
+ lb = mod_time_idx.lower_bound(diffTime(-1));
+ ASSERT_EQ(3, std::distance(lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+
+ // Search for objects later than fixed time + 6s.
+ lb = mod_time_idx.lower_bound(diffTime(6));
+ ASSERT_EQ(2, std::distance(lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+
+ // Search for objects later than fixed time + 10s.
+ lb = mod_time_idx.lower_bound(diffTime(10));
+ ASSERT_EQ(1, std::distance(lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+
+ // Search for objects later than fixed time + 20s.
+ lb = mod_time_idx.lower_bound(diffTime(20));
+ // None found.
+ ASSERT_EQ(0, std::distance(lb, mod_time_idx.end()));
+}
+
+// Checks that entries can be found by modification time and id.
+TEST_F(AuditEntryCollectionTest, getByModificationTimeAndId) {
+ const auto& mod_time_idx = audit_entries_.get<AuditEntryModificationTimeIdTag>();
+
+ // Search for objects later than added added subnet 10.
+ auto mod = boost::make_tuple(diffTime(-5), 100 + 1);
+ auto lb = mod_time_idx.lower_bound(mod);
+ ASSERT_EQ(4, std::distance(lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+
+ // Check the order is time first, id after.
+ create("dhcp4_subnet", 1000, AuditEntry::ModificationType::UPDATE,
+ diffTime(-8), 200, "updated subnet 1000");
+ lb = mod_time_idx.lower_bound(mod);
+ ASSERT_EQ(4, std::distance(lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 120, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_subnet", 1000, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_shared_network", 1, lb, mod_time_idx.end()));
+ EXPECT_TRUE(includes("dhcp4_option", 15, lb, mod_time_idx.end()));
+}
+
+// Checks that entries can be found by object id.
+TEST_F(AuditEntryCollectionTest, getByObjectId) {
+ const auto& object_id_idx = audit_entries_.get<AuditEntryObjectIdTag>();
+
+ // Search for object id 10.
+ auto range = object_id_idx.equal_range(10);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_subnet", 10, range.first, range.second));
+
+ // Add another entry.
+ create("dhcp4_subnet", 10, AuditEntry::ModificationType::UPDATE,
+ diffTime(0), 111, "updated subnet 10");
+
+ // Now search should return two entries.
+ range = object_id_idx.equal_range(10);
+ ASSERT_EQ(2, std::distance(range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_subnet", 10,
+ AuditEntry::ModificationType::CREATE,
+ range.first, range.second));
+ EXPECT_TRUE(includes("dhcp4_subnet", 10,
+ AuditEntry::ModificationType::UPDATE,
+ range.first, range.second));
+}
+
+}
diff --git a/src/lib/database/tests/backend_selector_unittest.cc b/src/lib/database/tests/backend_selector_unittest.cc
new file mode 100644
index 0000000..d66ca78
--- /dev/null
+++ b/src/lib/database/tests/backend_selector_unittest.cc
@@ -0,0 +1,197 @@
+// Copyright (C) 2018-2022 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 <database/backend_selector.h>
+#include <testutils/test_to_element.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::db;
+using namespace isc::data;
+
+namespace {
+
+// Verifies defaults of the backend selector.
+TEST(BackendSelectorTest, defaults) {
+ BackendSelector sel;
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, sel.getBackendType());
+ EXPECT_TRUE(sel.getBackendHost().empty());
+ EXPECT_EQ(0, sel.getBackendPort());
+ EXPECT_TRUE(sel.amUnspecified());
+ EXPECT_EQ("unspecified", sel.toText());
+}
+
+// Verifies that the backend selector can be set to "unspecified".
+TEST(BackendSelectorTest, unspec) {
+ BackendSelector sel = BackendSelector::UNSPEC();
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, sel.getBackendType());
+ EXPECT_TRUE(sel.getBackendHost().empty());
+ EXPECT_EQ(0, sel.getBackendPort());
+ EXPECT_TRUE(sel.amUnspecified());
+ EXPECT_EQ("unspecified", sel.toText());
+}
+
+// Verifies that it is possible to select backend by type.
+TEST(BackendSelectorTest, backendTypeSpec) {
+ boost::scoped_ptr<BackendSelector> sel;
+ ASSERT_NO_THROW(
+ sel.reset(new BackendSelector(BackendSelector::Type::MYSQL))
+ );
+ EXPECT_EQ(BackendSelector::Type::MYSQL, sel->getBackendType());
+ EXPECT_TRUE(sel->getBackendHost().empty());
+ EXPECT_EQ(0, sel->getBackendPort());
+ EXPECT_FALSE(sel->amUnspecified());
+ EXPECT_EQ("type=mysql", sel->toText());
+}
+
+// Verifies that backend can be selected by host and port.
+TEST(BackendSelectorTest, backendHostPortSpec) {
+ boost::scoped_ptr<BackendSelector> sel;
+ ASSERT_NO_THROW(
+ sel.reset(new BackendSelector("myhost", 1234))
+ );
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+ EXPECT_EQ("myhost", sel->getBackendHost());
+ EXPECT_EQ(1234, sel->getBackendPort());
+ EXPECT_FALSE(sel->amUnspecified());
+ EXPECT_EQ("host=myhost,port=1234", sel->toText());
+}
+
+// Verifies that backend can be selected by host.
+TEST(BackendSelectorTest, backendHostSpec) {
+ boost::scoped_ptr<BackendSelector> sel;
+ ASSERT_NO_THROW(
+ sel.reset(new BackendSelector("otherhost"))
+ );
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+ EXPECT_EQ("otherhost", sel->getBackendHost());
+ EXPECT_EQ(0, sel->getBackendPort());
+ EXPECT_FALSE(sel->amUnspecified());
+ EXPECT_EQ("host=otherhost", sel->toText());
+}
+
+// Verifies that backend becomes unspecified if the access
+// map is empty.
+TEST(BackendSelectorTest, accessMapTypeUnSpec) {
+ ElementPtr access_map = Element::createMap();
+ boost::scoped_ptr<BackendSelector> sel;
+ ASSERT_NO_THROW(
+ sel.reset(new BackendSelector(access_map))
+ );
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+ EXPECT_TRUE(sel->getBackendHost().empty());
+ EXPECT_EQ(0, sel->getBackendPort());
+ EXPECT_TRUE(sel->amUnspecified());
+ EXPECT_EQ("unspecified", sel->toText());
+}
+
+// Verifies that backend can be selected by type using access map.
+TEST(BackendSelectorTest, accessMapTypeSpec) {
+ ElementPtr access_map = Element::createMap();
+ access_map->set("type", Element::create("mysql"));
+ boost::scoped_ptr<BackendSelector> sel;
+ ASSERT_NO_THROW(
+ sel.reset(new BackendSelector(access_map))
+ );
+ EXPECT_EQ(BackendSelector::Type::MYSQL, sel->getBackendType());
+ EXPECT_TRUE(sel->getBackendHost().empty());
+ EXPECT_EQ(0, sel->getBackendPort());
+ EXPECT_FALSE(sel->amUnspecified());
+ EXPECT_EQ("type=mysql", sel->toText());
+}
+
+// Verifies that backend can be selected by host and port using
+// access map.
+TEST(BackendSelectorTest, accessMapHostPortSpec) {
+ ElementPtr access_map = Element::createMap();
+ access_map->set("host", Element::create("myhost"));
+ access_map->set("port", Element::create(int64_t(1234)));
+ boost::scoped_ptr<BackendSelector> sel;
+ ASSERT_NO_THROW(
+ sel.reset(new BackendSelector(access_map))
+ );
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+ EXPECT_EQ("myhost", sel->getBackendHost());
+ EXPECT_EQ(1234, sel->getBackendPort());
+ EXPECT_FALSE(sel->amUnspecified());
+ EXPECT_EQ("host=myhost,port=1234", sel->toText());
+}
+
+// Verifies that the backend can be selected by host using access
+// map.
+TEST(BackendSelectorTest, accessMapHostSpec) {
+ ElementPtr access_map = Element::createMap();
+ access_map->set("host", Element::create("myhost"));
+ boost::scoped_ptr<BackendSelector> sel;
+ ASSERT_NO_THROW(
+ sel.reset(new BackendSelector(access_map))
+ );
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, sel->getBackendType());
+ EXPECT_EQ("myhost", sel->getBackendHost());
+ EXPECT_EQ(0, sel->getBackendPort());
+ EXPECT_FALSE(sel->amUnspecified());
+ EXPECT_EQ("host=myhost", sel->toText());
+}
+
+// Verifies that selecting backend by port only is not possible.
+TEST(BackendSelectorTest, accessMapPortSpec) {
+ ElementPtr access_map = Element::createMap();
+ access_map->set("port", Element::create(int64_t(1234)));
+ boost::scoped_ptr<BackendSelector> sel;
+ EXPECT_THROW(sel.reset(new BackendSelector(access_map)),
+ BadValue);
+}
+
+// Tests conversions of strings to backend types.
+TEST(BackendSelectorTest, stringToBackendType) {
+ EXPECT_EQ(BackendSelector::Type::MYSQL,
+ BackendSelector::stringToBackendType("mysql"));
+ EXPECT_EQ(BackendSelector::Type::POSTGRESQL,
+ BackendSelector::stringToBackendType("postgresql"));
+ EXPECT_THROW(BackendSelector::stringToBackendType("unsupported"),
+ BadValue);
+}
+
+// Tests conversions of backend types to strings.
+TEST(BackendSelectorTest, backendTypeToString) {
+ EXPECT_EQ("mysql",
+ BackendSelector::backendTypeToString(BackendSelector::Type::MYSQL));
+ EXPECT_EQ("postgresql",
+ BackendSelector::backendTypeToString(BackendSelector::Type::POSTGRESQL));
+}
+
+// Tests toElement from backend selectors.
+TEST(BackendSelectorTest, backendToElement) {
+ // Unspecified.
+ boost::scoped_ptr<BackendSelector> sel(new BackendSelector());
+ EXPECT_THROW(sel->toElement(), BadValue);
+
+ // Unspecified type.
+ sel.reset(new BackendSelector("myhost", 1234));
+ EXPECT_THROW(sel->toElement(), BadValue);
+
+ // Type only.
+ EXPECT_NO_THROW(sel.reset(new BackendSelector(BackendSelector::Type::MYSQL)));
+ ElementPtr expected = Element::createMap();
+ expected->set("type", Element::create("mysql"));
+ test::runToElementTest<BackendSelector>(expected, *sel);
+
+ // Add host.
+ expected->set("host", Element::create("myhost"));
+ EXPECT_NO_THROW(sel.reset(new BackendSelector(expected)));
+ test::runToElementTest<BackendSelector>(expected, *sel);
+
+ // Add port.
+ expected->set("port", Element::create(1234L));
+ EXPECT_NO_THROW(sel.reset(new BackendSelector(expected)));
+ test::runToElementTest<BackendSelector>(expected, *sel);
+}
+
+}
+
diff --git a/src/lib/database/tests/database_connection_unittest.cc b/src/lib/database/tests/database_connection_unittest.cc
new file mode 100644
index 0000000..b34f2e8
--- /dev/null
+++ b/src/lib/database/tests/database_connection_unittest.cc
@@ -0,0 +1,643 @@
+// Copyright (C) 2015-2023 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 <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <database/database_connection.h>
+#include <database/dbaccess_parser.h>
+#include <exceptions/exceptions.h>
+#include <gtest/gtest.h>
+
+#include <functional>
+
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+/// @brief Test fixture for exercising DbLostCallback invocation
+class DatabaseConnectionCallbackTest : public ::testing::Test {
+public:
+ /// Constructor
+ DatabaseConnectionCallbackTest()
+ : db_reconnect_ctl_(0) {
+ DatabaseConnection::db_lost_callback_ = 0;
+ DatabaseConnection::db_recovered_callback_ = 0;
+ DatabaseConnection::db_failed_callback_ = 0;
+ }
+
+ /// Destructor
+ ~DatabaseConnectionCallbackTest() {
+ DatabaseConnection::db_lost_callback_ = 0;
+ DatabaseConnection::db_recovered_callback_ = 0;
+ DatabaseConnection::db_failed_callback_ = 0;
+ }
+
+ /// @brief Callback to register with a DatabaseConnection
+ ///
+ /// @param db_reconnect_ctl ReconnectCtl containing reconnect
+ /// parameters
+ bool dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ isc_throw(isc::BadValue, "db_reconnect_ctl should not be null");
+ }
+
+ db_reconnect_ctl_ = db_reconnect_ctl;
+ return (true);
+ }
+
+ /// @brief Callback to register with a DatabaseConnection
+ ///
+ /// @param db_reconnect_ctl ReconnectCtl containing reconnect
+ /// parameters
+ bool dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ isc_throw(isc::BadValue, "db_reconnect_ctl should not be null");
+ }
+
+ db_reconnect_ctl_ = db_reconnect_ctl;
+ db_reconnect_ctl_->resetRetries();
+ return (true);
+ }
+
+ /// @brief Callback to register with a DatabaseConnection
+ ///
+ /// @param db_reconnect_ctl ReconnectCtl containing reconnect
+ /// parameters
+ bool dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ isc_throw(isc::BadValue, "db_reconnect_ctl should not be null");
+ }
+
+ db_reconnect_ctl_ = db_reconnect_ctl;
+ db_reconnect_ctl_->resetRetries();
+ return (false);
+ }
+
+ /// @brief Retainer for the control passed into the callback
+ ReconnectCtlPtr db_reconnect_ctl_;
+};
+
+/// @brief getParameter test
+///
+/// This test checks if the LeaseMgr can be instantiated and that it
+/// parses parameters string properly.
+TEST(DatabaseConnectionTest, getParameter) {
+
+ DatabaseConnection::ParameterMap pmap;
+ pmap[std::string("param1")] = std::string("value1");
+ pmap[std::string("param2")] = std::string("value2");
+ DatabaseConnection datasrc(pmap);
+
+ EXPECT_EQ("value1", datasrc.getParameter("param1"));
+ EXPECT_EQ("value2", datasrc.getParameter("param2"));
+ EXPECT_THROW(datasrc.getParameter("param3"), isc::BadValue);
+}
+
+/// @brief NoDbLostCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbLostCallback
+/// returns false if the connection has no registered DbLostCallback.
+TEST_F(DatabaseConnectionCallbackTest, NoDbLostCallback) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap[std::string("type")] = std::string("test");
+ pmap[std::string("max-reconnect-tries")] = std::string("3");
+ pmap[std::string("reconnect-wait-time")] = std::string("60000");
+ DatabaseConnection datasrc(pmap);
+ datasrc.makeReconnectCtl("timer");
+
+ bool ret = false;
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
+ EXPECT_FALSE(ret);
+ EXPECT_FALSE(db_reconnect_ctl_);
+}
+
+/// @brief NoDbRecoveredCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbRecoveredCallback
+/// returns false if the connection has no registered DbRecoveredCallback.
+TEST_F(DatabaseConnectionCallbackTest, NoDbRecoveredCallback) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap[std::string("type")] = std::string("test");
+ pmap[std::string("max-reconnect-tries")] = std::string("3");
+ pmap[std::string("reconnect-wait-time")] = std::string("60000");
+ DatabaseConnection datasrc(pmap);
+ datasrc.makeReconnectCtl("timer");
+
+ bool ret = false;
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbRecoveredCallback(datasrc.reconnectCtl()));
+ EXPECT_FALSE(ret);
+ EXPECT_FALSE(db_reconnect_ctl_);
+}
+
+/// @brief NoDbFailedCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbFailedCallback
+/// returns false if the connection has no registered DbFailedCallback.
+TEST_F(DatabaseConnectionCallbackTest, NoDbFailedCallback) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap[std::string("type")] = std::string("test");
+ pmap[std::string("max-reconnect-tries")] = std::string("3");
+ pmap[std::string("reconnect-wait-time")] = std::string("60000");
+ DatabaseConnection datasrc(pmap);
+ datasrc.makeReconnectCtl("timer");
+
+ bool ret = false;
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbFailedCallback(datasrc.reconnectCtl()));
+ EXPECT_FALSE(ret);
+ EXPECT_FALSE(db_reconnect_ctl_);
+}
+
+/// @brief dbLostCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbLostCallback
+/// safely invokes the registered DbCallback. It also tests
+/// operation of DbReconnectCtl retry accounting methods.
+TEST_F(DatabaseConnectionCallbackTest, dbLostCallback) {
+ /// Create a Database configuration that includes the reconnect
+ /// control parameters.
+ DatabaseConnection::ParameterMap pmap;
+ pmap[std::string("type")] = std::string("test");
+ pmap[std::string("max-reconnect-tries")] = std::string("3");
+ pmap[std::string("reconnect-wait-time")] = std::string("60000");
+
+ /// Install the callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&DatabaseConnectionCallbackTest::dbLostCallback, this, ph::_1);
+ /// Create the connection..
+ DatabaseConnection datasrc(pmap);
+ datasrc.makeReconnectCtl("timer");
+ bool ret = false;
+
+ /// We should be able to invoke the callback and get
+ /// the correct reconnect control parameters from it.
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
+ EXPECT_TRUE(ret);
+ ASSERT_TRUE(db_reconnect_ctl_);
+ ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+ ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+ ASSERT_EQ(3, db_reconnect_ctl_->maxRetries());
+ ASSERT_EQ(3, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+
+ /// Verify that checkRetries() correctly decrements
+ /// down to zero, and that retriesLeft() returns
+ /// the correct value.
+ for (int i = 3; i > 1 ; --i) {
+ ASSERT_EQ(i, db_reconnect_ctl_->retriesLeft());
+ ASSERT_TRUE(db_reconnect_ctl_->checkRetries());
+ }
+
+ /// Retries are exhausted, verify that's reflected.
+ EXPECT_FALSE(db_reconnect_ctl_->checkRetries());
+ EXPECT_EQ(0, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
+}
+
+/// @brief dbRecoveredCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbRecoveredCallback
+/// safely invokes the registered DbRecoveredCallback. It also tests
+/// operation of DbReconnectCtl retry reset method.
+TEST_F(DatabaseConnectionCallbackTest, dbRecoveredCallback) {
+ /// Create a Database configuration that includes the reconnect
+ /// control parameters.
+ DatabaseConnection::ParameterMap pmap;
+ pmap[std::string("type")] = std::string("test");
+ pmap[std::string("max-reconnect-tries")] = std::string("3");
+ pmap[std::string("reconnect-wait-time")] = std::string("60000");
+
+ /// Install the callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&DatabaseConnectionCallbackTest::dbLostCallback, this, ph::_1);
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&DatabaseConnectionCallbackTest::dbRecoveredCallback, this, ph::_1);
+ /// Create the connection..
+ DatabaseConnection datasrc(pmap);
+ datasrc.makeReconnectCtl("timer");
+ bool ret = false;
+
+ /// We should be able to invoke the callback and get
+ /// the correct reconnect control parameters from it.
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
+ EXPECT_TRUE(ret);
+ ASSERT_TRUE(db_reconnect_ctl_);
+ ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+ ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+ ASSERT_EQ(3, db_reconnect_ctl_->maxRetries());
+ ASSERT_EQ(3, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+
+ /// Verify that checkRetries() correctly decrements
+ /// down to zero, and that retriesLeft() returns
+ /// the correct value.
+ for (int i = 3; i > 1 ; --i) {
+ ASSERT_EQ(i, db_reconnect_ctl_->retriesLeft());
+ ASSERT_TRUE(db_reconnect_ctl_->checkRetries());
+ }
+
+ /// Retries are exhausted, verify that's reflected.
+ EXPECT_FALSE(db_reconnect_ctl_->checkRetries());
+ EXPECT_EQ(0, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
+
+ /// Reset the reconnect ctl object to verify that it is set again.
+ db_reconnect_ctl_.reset();
+
+ /// We should be able to invoke the callback and get
+ /// the correct reconnect control parameters from it.
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbRecoveredCallback(datasrc.reconnectCtl()));
+ EXPECT_TRUE(ret);
+ ASSERT_TRUE(db_reconnect_ctl_);
+ ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+ ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+ EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+
+ /// Retries are reset, verify that's reflected.
+ EXPECT_EQ(3, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
+}
+
+/// @brief dbFailedCallback
+///
+/// This test verifies that DatabaseConnection::invokeDbFailedCallback
+/// safely invokes the registered DbFailedCallback.
+TEST_F(DatabaseConnectionCallbackTest, dbFailedCallback) {
+ /// Create a Database configuration that includes the reconnect
+ /// control parameters.
+ DatabaseConnection::ParameterMap pmap;
+ pmap[std::string("type")] = std::string("test");
+ pmap[std::string("max-reconnect-tries")] = std::string("3");
+ pmap[std::string("reconnect-wait-time")] = std::string("60000");
+
+ /// Install the callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&DatabaseConnectionCallbackTest::dbLostCallback, this, ph::_1);
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&DatabaseConnectionCallbackTest::dbFailedCallback, this, ph::_1);
+ /// Create the connection..
+ DatabaseConnection datasrc(pmap);
+ datasrc.makeReconnectCtl("timer");
+ bool ret = false;
+
+ /// We should be able to invoke the callback and get
+ /// the correct reconnect control parameters from it.
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbLostCallback(datasrc.reconnectCtl()));
+ EXPECT_TRUE(ret);
+ ASSERT_TRUE(db_reconnect_ctl_);
+ ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+ ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+ ASSERT_EQ(3, db_reconnect_ctl_->maxRetries());
+ ASSERT_EQ(3, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+
+ /// Verify that checkRetries() correctly decrements
+ /// down to zero, and that retriesLeft() returns
+ /// the correct value.
+ for (int i = 3; i > 1 ; --i) {
+ ASSERT_EQ(i, db_reconnect_ctl_->retriesLeft());
+ ASSERT_TRUE(db_reconnect_ctl_->checkRetries());
+ }
+
+ /// Retries are exhausted, verify that's reflected.
+ EXPECT_FALSE(db_reconnect_ctl_->checkRetries());
+ EXPECT_EQ(0, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
+
+ /// Reset the reconnect ctl object to verify that it is set again.
+ db_reconnect_ctl_.reset();
+
+ /// We should be able to invoke the callback and get
+ /// the correct reconnect control parameters from it.
+ ASSERT_NO_THROW(ret = DatabaseConnection::invokeDbFailedCallback(datasrc.reconnectCtl()));
+ EXPECT_FALSE(ret);
+ ASSERT_TRUE(db_reconnect_ctl_);
+ ASSERT_EQ("test", db_reconnect_ctl_->backendType());
+ ASSERT_EQ("timer", db_reconnect_ctl_->timerName());
+ EXPECT_EQ(60000, db_reconnect_ctl_->retryInterval());
+
+ /// Retries are reset, verify that's reflected.
+ EXPECT_EQ(3, db_reconnect_ctl_->retriesLeft());
+ EXPECT_EQ(3, db_reconnect_ctl_->maxRetries());
+}
+
+// This test checks that a database access string can be parsed correctly.
+TEST(DatabaseConnectionTest, parse) {
+
+ DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(
+ "user=me password='forbidden' name=kea somethingelse= type=mysql");
+
+ EXPECT_EQ(5, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+ EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that it is allowed to specify password including whitespaces
+// assuming that the password is enclosed in ''.
+TEST(DatabaseConnectionTest, parsePasswordWithWhitespace) {
+
+ // Case 1: password in the middle.
+ DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(
+ "user=me password='forbidden with space' name=kea type=mysql");
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden with space", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Case 2: password at the end.
+ parameters = DatabaseConnection::parse("user=me name=kea type=mysql password='forbidden with space'");
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden with space", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Case 3: Empty password at the end.
+ parameters = DatabaseConnection::parse("user=me name=kea type=mysql password=''");
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Case 4: password at the beginning.
+ parameters = DatabaseConnection::parse("password='forbidden with space' user=me name=kea type=mysql");
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden with space", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Case 5: Empty password at the beginning.
+ parameters = DatabaseConnection::parse("password='' user=me name=kea type=mysql");
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+
+ // Case 6: Password is a sole parameter.
+ parameters = DatabaseConnection::parse("password='forbidden with spaces'");
+
+ EXPECT_EQ(1, parameters.size());
+ EXPECT_EQ("forbidden with spaces", parameters["password"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST(DatabaseConnectionTest, parseInvalid) {
+
+ // No tokens in the string, so we expect no parameters
+ std::string invalid = "";
+ DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(invalid);
+ EXPECT_EQ(0, parameters.size());
+
+ // With spaces, there are some tokens so we expect invalid parameter
+ // as there are no equals signs.
+ invalid = " \t ";
+ EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter);
+
+ invalid = " noequalshere ";
+ EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter);
+
+ // Mismatched single quote.
+ invalid = "password='xyz";
+ EXPECT_THROW(DatabaseConnection::parse(invalid), isc::InvalidParameter);
+
+ // A single "=" is valid string, but is placed here as the result is
+ // expected to be nothing.
+ invalid = "=";
+ parameters = DatabaseConnection::parse(invalid);
+ EXPECT_EQ(1, parameters.size());
+ EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactedAccessString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST(DatabaseConnectionTest, redactAccessString) {
+
+ DatabaseConnection::ParameterMap parameters =
+ DatabaseConnection::parse("user=me password=forbidden name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+ parameters = DatabaseConnection::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactedAccessString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST(DatabaseConnectionTest, redactAccessStringEmptyPassword) {
+
+ DatabaseConnection::ParameterMap parameters =
+ DatabaseConnection::parse("user=me name=kea type=mysql password=");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+ parameters = DatabaseConnection::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // ... and again to check that the position of the empty password in the
+ // string does not matter.
+ parameters = DatabaseConnection::parse("user=me password= name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ redacted = DatabaseConnection::redactedAccessString(parameters);
+ parameters = DatabaseConnection::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactedAccessString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begin with.
+TEST(DatabaseConnectionTest, redactAccessStringNoPassword) {
+
+ DatabaseConnection::ParameterMap parameters =
+ DatabaseConnection::parse("user=me name=kea type=mysql");
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+ parameters = DatabaseConnection::parse(redacted);
+
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+// Check that the toElementDbAccessString() handles all valid parameters
+// Note that because toElementDbAccessString() utilizes
+// toElement() this tests both.
+TEST(DatabaseConnection, toElementDbAccessStringValid) {
+ const char* configs[] = {
+ "{\n"
+ "\"connect-timeout\": 200, \n"
+ "\"on-fail\": \"stop-retry-exit\", \n"
+ "\"lfc-interval\" : 100, \n"
+ "\"host\": \"whatevah\", \n"
+ "\"max-reconnect-tries\": 5, \n"
+ "\"name\": \"name_str\", \n"
+ "\"password\": \"password_str\", \n"
+ "\"persist\" : true, \n"
+ "\"port\" : 300, \n"
+ "\"readonly\" : false, \n"
+ "\"reconnect-wait-time\": 99, \n"
+ "\"type\": \"memfile\", \n"
+ "\"user\": \"user_str\", \n"
+ "\"max-row-errors\": 50, \n"
+ "\"trust-anchor\": \"my-ca\", \n"
+ "\"cert-file\": \"my-cert.crt\", \n"
+ "\"key-file\": \"my-key.key\", \n"
+ "\"cipher-list\": \"AES\" \n"
+ "}\n"
+ };
+
+ DbAccessParser parser;
+ std::string access_str;
+ ConstElementPtr json_elements;
+
+ ASSERT_NO_THROW(json_elements = Element::fromJSON(configs[0]));
+ ASSERT_NO_THROW(parser.parse(access_str, json_elements));
+
+ ElementPtr round_trip = DatabaseConnection::toElementDbAccessString(access_str);
+
+ ASSERT_TRUE(json_elements->equals(*round_trip));
+}
+
+// Check that the toElementDbAccessString() handles Postgres backend
+// specific parameters.
+TEST(DatabaseConnection, toElementDbAccessStringValidPostgresql) {
+ const char* configs[] = {
+ "{\n"
+ "\"connect-timeout\": 200, \n"
+ "\"tcp-user-timeout\": 10, \n"
+ "\"type\": \"postgresql\", \n"
+ "\"user\": \"user_str\" \n"
+ "}\n"
+ };
+
+ DbAccessParser parser;
+ std::string access_str;
+ ConstElementPtr json_elements;
+
+ ASSERT_NO_THROW(json_elements = Element::fromJSON(configs[0]));
+ ASSERT_NO_THROW(parser.parse(access_str, json_elements));
+
+ ElementPtr round_trip = DatabaseConnection::toElementDbAccessString(access_str);
+
+ ASSERT_TRUE(json_elements->equals(*round_trip));
+}
+
+// Check that the toElementDbAccessString() handles MySQL backend
+// specific parameters.
+TEST(DatabaseConnection, toElementDbAccessStringValidMySql) {
+ const char* configs[] = {
+ "{\n"
+ "\"connect-timeout\": 200, \n"
+ "\"read-timeout\": 10, \n"
+ "\"write-timeout\": 10, \n"
+ "\"type\": \"mysql\", \n"
+ "\"user\": \"user_str\" \n"
+ "}\n"
+ };
+
+ DbAccessParser parser;
+ std::string access_str;
+ ConstElementPtr json_elements;
+
+ ASSERT_NO_THROW(json_elements = Element::fromJSON(configs[0]));
+ ASSERT_NO_THROW(parser.parse(access_str, json_elements));
+
+ ElementPtr round_trip = DatabaseConnection::toElementDbAccessString(access_str);
+
+ ASSERT_TRUE(json_elements->equals(*round_trip));
+}
+
+// Check that toElementDbAccessString() catches invalid parameters.
+// Note that because toElementDbAccessString() utilizes
+// toElement() this tests both.
+//
+// Test has been disabled. The recent change turned handling of unknown connection
+// string params. Instead of throwing, it logs an error and continues. This gives
+// us better resiliency. However, we don't have any means implemented to
+// test whether it was printed or not. It's reasonably easy to implement such a
+// check, but we don't have time for this.
+TEST(DatabaseConnection, DISABLED_toElementDbAccessStringInvalid) {
+ std::vector<std::string> access_strs = {
+ "bogus-param=memfile",
+ "lfc-interval=not-an-integer",
+ "connect-timeout=not-an-integer",
+ "port=not-an-integer",
+ "persist=not-boolean",
+ "readonly=not-boolean"
+ };
+
+ for (auto access_str : access_strs) {
+ /// @todo: verify that an ERROR is logged.
+ ASSERT_NO_THROW(DatabaseConnection::toElementDbAccessString(access_str));
+ }
+}
+
+// Check that toElementDbAccessString() handles empty access string
+// Note that because toElementDbAccessString() utilizes
+// toElement() this tests both.
+TEST(DatabaseConnection, toElementDbAccessStringEmpty) {
+ ConstElementPtr elements;
+ ASSERT_NO_THROW(elements = DatabaseConnection::toElementDbAccessString(""));
+ ASSERT_TRUE(elements);
+ ASSERT_EQ(0, elements->size());
+}
diff --git a/src/lib/database/tests/database_log_unittest.cc b/src/lib/database/tests/database_log_unittest.cc
new file mode 100644
index 0000000..1caa12c
--- /dev/null
+++ b/src/lib/database/tests/database_log_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (C) 2021 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 <database/db_log.h>
+
+#include <gtest/gtest.h>
+
+using isc::db::DB_DBG_TRACE_DETAIL;
+using isc::db::DB_INVALID_ACCESS;
+using isc::db::DB_LOG_FATAL;
+using isc::db::DB_LOG_ERROR;
+using isc::db::DB_LOG_WARN;
+using isc::db::DB_LOG_INFO;
+using isc::db::DB_LOG_DEBUG;
+using isc::db::db_logger_mutex;
+
+namespace {
+
+/// Test that the mutex unlocks after a call to DB_LOG.
+/// Let's use DB_INVALID_ACCESS as an example for all.
+TEST(DatabaseLogTest, mutexIsolation) {
+ DB_LOG_FATAL(DB_INVALID_ACCESS).arg("hello");
+ EXPECT_TRUE(db_logger_mutex.try_lock());
+ db_logger_mutex.unlock();
+
+ DB_LOG_ERROR(DB_INVALID_ACCESS).arg("hello");
+ EXPECT_TRUE(db_logger_mutex.try_lock());
+ db_logger_mutex.unlock();
+
+ DB_LOG_WARN(DB_INVALID_ACCESS).arg("hello");
+ EXPECT_TRUE(db_logger_mutex.try_lock());
+ db_logger_mutex.unlock();
+
+ DB_LOG_INFO(DB_INVALID_ACCESS).arg("hello");
+ EXPECT_TRUE(db_logger_mutex.try_lock());
+ db_logger_mutex.unlock();
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, DB_INVALID_ACCESS).arg("hello");
+ EXPECT_TRUE(db_logger_mutex.try_lock());
+ db_logger_mutex.unlock();
+}
+
+} // namespace
diff --git a/src/lib/database/tests/dbaccess_parser_unittest.cc b/src/lib/database/tests/dbaccess_parser_unittest.cc
new file mode 100644
index 0000000..29322e0
--- /dev/null
+++ b/src/lib/database/tests/dbaccess_parser_unittest.cc
@@ -0,0 +1,1001 @@
+// Copyright (C) 2012-2023 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 <cc/command_interpreter.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/dbaccess_parser.h>
+#include <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+#include <map>
+#include <string>
+
+using namespace std;
+using namespace isc;
+using namespace isc::db;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+/// @brief Database Access Parser test fixture class
+class DbAccessParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ DbAccessParserTest() {
+ }
+ /// @brief Destructor
+ ///
+ /// As some of the tests have the side-effect of altering the logging
+ /// settings (when the parser's "parse" method is called), ensure that
+ /// the logging is reset to the default after each test completes.
+ ~DbAccessParserTest() {
+ isc::log::setDefaultLoggingOutput();
+ }
+
+ /// @brief Build JSON String
+ ///
+ /// Given a array of "const char*" strings representing in order, keyword,
+ /// value, keyword, value, ... and terminated by a NULL, return a string
+ /// that represents the JSON map for the keywords and values.
+ ///
+ /// E.g. given the array of strings: alpha, one, beta, two, NULL, it would
+ /// return the string '{ "alpha": "one", "beta": "two" }'
+ ///
+ /// @param keyval Array of "const char*" strings in the order keyword,
+ /// value, keyword, value ... A NULL entry terminates the list.
+ ///
+ /// @return JSON map for the keyword value array.
+ std::string toJson(const char* keyval[]) {
+ const std::string quote = "\"";
+ const std::string colon = ":";
+ const std::string space = " ";
+
+ string result = "{ ";
+
+ for (size_t i = 0; keyval[i] != NULL; i+= 2) {
+ // Get the value. This should not be NULL. As ASSERT_NE will
+ // cause a return - which gives compilation problems as a return
+ // statement is expected to return a string - use EXPECT_NE and
+ // explicitly return if the expected array is incorrect.
+ EXPECT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
+ "Supplied reference keyword/value list does not contain values "
+ "for all keywords";
+ if (keyval[i + 1] == NULL) {
+ return (std::string(""));
+ }
+
+ // Add the separating comma if not the first.
+ if (i != 0) {
+ result += ", ";
+ }
+
+ // Add the keyword and value - make sure that they are quoted.
+ // The parameters which are not quoted are persist, readonly and
+ // lfc-interval as they are boolean and integer respectively.
+ result += quote + keyval[i] + quote + colon + space;
+ if (!quoteValue(std::string(keyval[i]))) {
+ result += keyval[i + 1];
+
+ } else {
+ result += quote + keyval[i + 1] + quote;
+ }
+ }
+
+ // Add the terminating brace
+ result += " }";
+
+ return (result);
+ }
+
+ /// @brief Check for Keywords
+ ///
+ /// Takes a database access string and checks it against a list of keywords
+ /// and values. It checks that:
+ ///
+ /// a. Every keyword in the string appears once and only once in the
+ /// list.
+ /// b. Every keyword in the list appears in the string.
+ /// c. Every keyword's value is the same as that in the string.
+ ///
+ /// To parse the access string, we use the parsing function in the
+ /// DHCP lease manager.
+ ///
+ /// @param trace_string String that will be used to set the value of a
+ /// SCOPED_TRACE for this call.
+ /// @param dbaccess set of database access parameters to check
+ /// @param keyval Array of "const char*" strings in the order keyword,
+ /// value, keyword, value ... A NULL entry terminates the list.
+ void checkAccessString(const char* trace_string,
+ const DatabaseConnection::ParameterMap& parameters,
+ const char* keyval[]) {
+ SCOPED_TRACE(trace_string);
+
+ // Construct a map of keyword value pairs.
+ std::map<string, string> expected;
+ size_t expected_count = 0;
+ for (size_t i = 0; keyval[i] != NULL; i += 2) {
+ // Get the value. This should not be NULL
+ ASSERT_NE(static_cast<const char*>(NULL), keyval[i + 1]) <<
+ "Supplied reference keyword/value list does not contain values "
+ "for all keywords";
+ expected[keyval[i]] = keyval[i + 1];
+
+ // One more keyword processed
+ ++expected_count;
+ }
+
+ // Check no duplicates in the test set of reference keywords.
+ ASSERT_EQ(expected_count, expected.size()) <<
+ "Supplied reference keyword/value list contains duplicate keywords";
+
+ // The passed parameter map should have the same number of entries as
+ // the reference set of keywords.
+ EXPECT_EQ(expected_count, parameters.size());
+
+ // Check that the keywords and keyword values are the same: loop
+ // through the keywords in the database access string.
+ for (DatabaseConnection::ParameterMap::const_iterator actual = parameters.begin();
+ actual != parameters.end(); ++actual) {
+
+ // Does the keyword exist in the set of expected keywords?
+ std::map<string, string>::iterator corresponding =
+ expected.find(actual->first);
+ ASSERT_TRUE(corresponding != expected.end());
+
+ // Keyword exists, is the value the same?
+ EXPECT_EQ(corresponding->second, actual->second);
+ }
+ }
+
+private:
+
+ /// @brief Checks if the value of the specified parameter should be
+ /// quoted in the configuration.
+ ///
+ /// @param parameter A parameter for which it should be checked whether
+ /// the value should be quoted or not.
+ ///
+ /// @return true if the value of the parameter should be quoted.
+ bool quoteValue(const std::string& parameter) const {
+ return ((parameter != "persist") && (parameter != "lfc-interval") &&
+ (parameter != "connect-timeout") &&
+ (parameter != "read-timeout") &&
+ (parameter != "write-timeout") &&
+ (parameter != "tcp-user-timeout") &&
+ (parameter != "port") &&
+ (parameter != "max-row-errors") &&
+ (parameter != "readonly"));
+ }
+
+};
+
+
+/// @brief Version of parser with protected methods public
+///
+/// Some of the methods in DbAccessParser are not required to be public in Kea.
+/// Instead of being declared "private", they are declared "protected" so that
+/// they can be accessed through a derived class in the unit tests.
+class TestDbAccessParser : public DbAccessParser {
+public:
+
+ /// @brief Constructor
+ TestDbAccessParser()
+ : DbAccessParser()
+ {}
+
+ /// @brief Destructor
+ virtual ~TestDbAccessParser()
+ {}
+
+ /// @brief Parse configuration value
+ ///
+ /// @param database_config Configuration to be parsed.
+ void parse(ConstElementPtr database_config) {
+ std::string db_access_string;
+ DbAccessParser::parse(db_access_string, database_config);
+ }
+
+ /// @brief Get database access parameters
+ ///
+ /// Used in testing to check that the configuration information has been
+ /// parsed corrected.
+ ///
+ /// @return Map of keyword/value pairs representing database access
+ /// information.
+ const DatabaseConnection::ParameterMap& getDbAccessParameters() const {
+ return (DbAccessParser::getDbAccessParameters());
+ }
+
+ /// @brief Construct database access string
+ ///
+ /// Constructs the database access string from the stored parameters.
+ ///
+ /// @return Database access string
+ std::string getDbAccessString() const {
+ return (DbAccessParser::getDbAccessString());
+ }
+};
+
+// Check that the parser works with a simple configuration.
+TEST_F(DbAccessParserTest, validTypeMemfile) {
+ const char* config[] = {"type", "memfile",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
+}
+
+// Check that the parser works with a simple configuration for host database.
+TEST_F(DbAccessParserTest, hosts) {
+ const char* config[] = {"type", "memfile",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
+}
+
+// Check that the parser works with a simple configuration that
+// includes empty elements.
+TEST_F(DbAccessParserTest, emptyKeyword) {
+ const char* config[] = {"type", "memfile",
+ "name", "",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid memfile", parser.getDbAccessParameters(), config);
+}
+
+// Check that the parser works with more complex configuration when
+// lease file path is specified for DHCPv4.
+TEST_F(DbAccessParserTest, persistV4Memfile) {
+ const char* config[] = {"type", "memfile",
+ "persist", "true",
+ "name", "/opt/var/lib/kea/kea-leases4.csv",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+
+ checkAccessString("Valid memfile", parser.getDbAccessParameters(),
+ config);
+}
+
+// Check that the parser works with more complex configuration when
+// lease file path is specified for DHCPv6.
+TEST_F(DbAccessParserTest, persistV6Memfile) {
+ const char* config[] = {"type", "memfile",
+ "persist", "true",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+
+ checkAccessString("Valid memfile", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser accepts the valid value of the
+// lfc-interval parameter.
+TEST_F(DbAccessParserTest, validLFCInterval) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "lfc-interval", "3600",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid LFC Interval", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// lfc-interval parameter.
+TEST_F(DbAccessParserTest, negativeLFCInterval) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "lfc-interval", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects the too large (greater than
+// the max uint32_t) value of the lfc-interval parameter.
+TEST_F(DbAccessParserTest, largeLFCInterval) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "lfc-interval", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// connect-timeout parameter.
+TEST_F(DbAccessParserTest, validConnectTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "connect-timeout", "3600",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid timeout", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// connect-timeout parameter.
+TEST_F(DbAccessParserTest, negativeConnectTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "connect-timeout", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the connecttimeout parameter.
+TEST_F(DbAccessParserTest, largeConnectTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "connect-timeout", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// read-timeout parameter.
+TEST_F(DbAccessParserTest, validReadTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "read-timeout", "3600",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid read timeout", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// read-timeout parameter.
+TEST_F(DbAccessParserTest, negativeReadTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "read-timeout", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the read-timeout parameter.
+TEST_F(DbAccessParserTest, largeReadTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "read-timeout", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// write-timeout parameter.
+TEST_F(DbAccessParserTest, validWriteTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "write-timeout", "3600",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid write timeout", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// write-timeout parameter.
+TEST_F(DbAccessParserTest, negativeWriteTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "write-timeout", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the write-timeout parameter.
+TEST_F(DbAccessParserTest, largeWriteTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "write-timeout", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// tcp-user-timeout parameter.
+TEST_F(DbAccessParserTest, validTcpUserTimeout) {
+ const char* config[] = {"type", "postgresql",
+ "name", "keatest",
+ "tcp-user-timeout", "3600",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid write timeout", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// tcp-user-timeout parameter.
+TEST_F(DbAccessParserTest, negativeTcpUserTimeout) {
+ const char* config[] = {"type", "postgresql",
+ "name", "keatest",
+ "tcp-user-timeout", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the tcp-user-timeout parameter.
+TEST_F(DbAccessParserTest, largeTcpUserTimeout) {
+ const char* config[] = {"type", "postgresql",
+ "name", "keatest",
+ "tcp-user-timeout", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test verifies that specifying the tcp-user-timeout for the
+// memfile backend is not allowed.
+TEST_F(DbAccessParserTest, memfileTcpUserTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "keatest",
+ "tcp-user-timeout", "10",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test verifies that specifying the tcp-user-timeout for the
+// mysql backend is not allowed.
+TEST_F(DbAccessParserTest, mysqlTcpUserTimeout) {
+ const char* config[] = {"type", "mysql",
+ "name", "keatest",
+ "tcp-user-timeout", "10",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test verifies that specifying the read-timeout for the
+// memfile backend is not allowed.
+TEST_F(DbAccessParserTest, memfileReadTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "keatest",
+ "read-timeout", "10",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test verifies that specifying the read-timeout for the
+// postgresql backend is not allowed.
+TEST_F(DbAccessParserTest, postgresqlReadTimeout) {
+ const char* config[] = {"type", "postgresql",
+ "name", "keatest",
+ "read-timeout", "10",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test verifies that specifying the write-timeout for the
+// memfile backend is not allowed.
+TEST_F(DbAccessParserTest, memfileWriteTimeout) {
+ const char* config[] = {"type", "memfile",
+ "name", "keatest",
+ "write-timeout", "10",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test verifies that specifying the write-timeout for the
+// postgresql backend is not allowed.
+TEST_F(DbAccessParserTest, postgresqlWriteTimeout) {
+ const char* config[] = {"type", "postgresql",
+ "name", "keatest",
+ "write-timeout", "10",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// port parameter.
+TEST_F(DbAccessParserTest, validPort) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "port", "3306",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid port", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// port parameter.
+TEST_F(DbAccessParserTest, negativePort) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "port", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint16_t) value of the timeout parameter.
+TEST_F(DbAccessParserTest, largePort) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "port", "65536",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts a value of zero for the
+// max-row-errors parameter.
+TEST_F(DbAccessParserTest, zeroMaxRowErrors) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "max-row-errors", "0",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Zero max-row-errors", parser.getDbAccessParameters(),
+ config);
+}
+
+// This test checks that the parser accepts the valid value of the
+// max-row-errors parameter.
+TEST_F(DbAccessParserTest, validZeroMaxRowErrors) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "max-row-errors", "50",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid max-row-errors", parser.getDbAccessParameters(),
+ config);
+}
+
+
+// This test checks that the parser rejects the negative value of the
+// max-row-errors parameter.
+TEST_F(DbAccessParserTest, negativeMaxRowErrors) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "max-row-errors", "-1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the max-row-errors parameter.
+TEST_F(DbAccessParserTest, largeMaxRowErrors) {
+ const char* config[] = {"type", "memfile",
+ "name", "/opt/var/lib/kea/kea-leases6.csv",
+ "max-row-errors", "4294967296",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// Check that the parser works with a valid MySQL configuration
+TEST_F(DbAccessParserTest, validTypeMysql) {
+ const char* config[] = {"type", "mysql",
+ "host", "erewhon",
+ "port", "3306",
+ "user", "kea",
+ "password", "keapassword",
+ "name", "keatest",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Valid mysql", parser.getDbAccessParameters(), config);
+}
+
+// A missing 'type' keyword should cause an exception to be thrown.
+TEST_F(DbAccessParserTest, missingTypeKeyword) {
+ const char* config[] = {"host", "erewhon",
+ "port", "3306",
+ "user", "kea",
+ "password", "keapassword",
+ "name", "keatest",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// Check reconfiguration. Checks that incremental changes applied to the
+// database configuration are incremental.
+TEST_F(DbAccessParserTest, incrementalChanges) {
+ const char* config1[] = {"type", "memfile",
+ NULL};
+
+ // Applying config2 will cause a wholesale change.
+ const char* config2[] = {"type", "mysql",
+ "host", "erewhon",
+ "port", "3306",
+ "user", "kea",
+ "password", "keapassword",
+ "name", "keatest",
+ NULL};
+
+ // Applying incremental2 should cause a change to config3.
+ const char* incremental2[] = {"user", "me",
+ "password", "meagain",
+ NULL};
+ const char* config3[] = {"type", "mysql",
+ "host", "erewhon",
+ "port", "3306",
+ "user", "me",
+ "password", "meagain",
+ "name", "keatest",
+ NULL};
+
+ // incremental3 will cause an exception. There should be no change
+ // to the returned value.
+ const char* incremental3[] = {"type", "invalid",
+ "user", "you",
+ "password", "youagain",
+ NULL};
+
+ // incremental4 is a compatible change and should cause a transition
+ // to config4.
+ const char* incremental4[] = {"user", "them",
+ "password", "",
+ NULL};
+ const char* config4[] = {"type", "mysql",
+ "host", "erewhon",
+ "port", "3306",
+ "user", "them",
+ "password", "",
+ "name", "keatest",
+ NULL};
+
+ TestDbAccessParser parser;
+
+ // First configuration string should cause a representation of that string
+ // to be held.
+ string json_config = toJson(config1);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Initial configuration", parser.getDbAccessParameters(),
+ config1);
+
+ // Applying a wholesale change will cause the access string to change
+ // to a representation of the new configuration.
+ json_config = toJson(config2);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Subsequent configuration", parser.getDbAccessParameters(),
+ config2);
+
+ // Applying an incremental change will cause the representation to change
+ // incrementally.
+ json_config = toJson(incremental2);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Incremental configuration", parser.getDbAccessParameters(),
+ config3);
+
+ // Applying the next incremental change should cause an exception to be
+ // thrown and there be no change to the access string.
+ json_config = toJson(incremental3);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+ checkAccessString("Incompatible incremental change", parser.getDbAccessParameters(),
+ config3);
+
+ // Applying an incremental change will cause the representation to change
+ // incrementally.
+ json_config = toJson(incremental4);
+ json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ EXPECT_NO_THROW(parser.parse(json_elements));
+ checkAccessString("Compatible incremental change", parser.getDbAccessParameters(),
+ config4);
+}
+
+// Check that the database access string is constructed correctly.
+TEST_F(DbAccessParserTest, getDbAccessString) {
+ const char* config[] = {"type", "mysql",
+ "host", "",
+ "name", "keatest",
+ "password", "password with spaces",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+
+ // Get the database access string
+ std::string dbaccess = parser.getDbAccessString();
+
+ // String should be either "type=mysql name=keatest" or
+ // "name=keatest type=mysql". The "host" entry is null, so should not be
+ // output.
+ EXPECT_EQ(dbaccess, "name=keatest password='password with spaces' type=mysql");
+}
+
+// Check that the configuration is accepted for the valid value
+// of "readonly".
+TEST_F(DbAccessParserTest, validReadOnly) {
+ const char* config[] = {"type", "mysql",
+ "user", "keatest",
+ "password", "keatest",
+ "name", "keatest",
+ "readonly", "true",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_NO_THROW(parser.parse(json_elements));
+
+ checkAccessString("Valid readonly parameter",
+ parser.getDbAccessParameters(),
+ config);
+}
+
+// Check that for the invalid value of the "readonly" parameter
+// an exception is thrown.
+TEST_F(DbAccessParserTest, invalidReadOnly) {
+ const char* config[] = {"type", "mysql",
+ "user", "keatest",
+ "password", "keatest",
+ "name", "keatest",
+ "readonly", "1",
+ NULL};
+
+ string json_config = toJson(config);
+ ConstElementPtr json_elements = Element::fromJSON(json_config);
+ EXPECT_TRUE(json_elements);
+
+ TestDbAccessParser parser;
+ EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// Check that multiple host storages are correctly parsed.
+TEST_F(DbAccessParserTest, multipleHost) {
+ const char* config1[] = {"type", "mysql",
+ "name", "keatest1",
+ NULL};
+ const char* config2[] = {"type", "mysql",
+ "name", "keatest2",
+ NULL};
+
+ string json_config1 = toJson(config1);
+ string json_config2 = toJson(config2);
+ ConstElementPtr json_elements1 = Element::fromJSON(json_config1);
+ ConstElementPtr json_elements2 = Element::fromJSON(json_config2);
+
+ TestDbAccessParser parser1;
+ TestDbAccessParser parser2;
+ EXPECT_NO_THROW(parser1.parse(json_elements1));
+ EXPECT_NO_THROW(parser2.parse(json_elements2));
+
+ checkAccessString("First config",
+ parser1.getDbAccessParameters(),
+ config1);
+ checkAccessString("Second config",
+ parser2.getDbAccessParameters(),
+ config2);
+}
+
+}; // Anonymous namespace
diff --git a/src/lib/database/tests/run_unittests.cc b/src/lib/database/tests/run_unittests.cc
new file mode 100644
index 0000000..4e83d4b
--- /dev/null
+++ b/src/lib/database/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/database/tests/server_selector_unittest.cc b/src/lib/database/tests/server_selector_unittest.cc
new file mode 100644
index 0000000..00328ba
--- /dev/null
+++ b/src/lib/database/tests/server_selector_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (C) 2018-2019 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 <cc/server_tag.h>
+#include <database/server_selector.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace {
+
+// Check that server selector can be set to UNASSIGNED.
+TEST(ServerSelectorTest, unassigned) {
+ ServerSelector selector = ServerSelector::UNASSIGNED();
+ EXPECT_EQ(ServerSelector::Type::UNASSIGNED, selector.getType());
+ EXPECT_TRUE(selector.amUnassigned());
+ EXPECT_TRUE(selector.hasNoTags());
+ EXPECT_FALSE(selector.hasMultipleTags());
+}
+
+// Check that server selector can be set to ALL.
+TEST(ServerSelectorTest, all) {
+ ServerSelector selector = ServerSelector::ALL();
+ EXPECT_EQ(ServerSelector::Type::ALL, selector.getType());
+ EXPECT_FALSE(selector.amUnassigned());
+
+ EXPECT_FALSE(selector.hasNoTags());
+ auto tags = selector.getTags();
+ EXPECT_EQ(1, tags.size());
+ EXPECT_EQ(1, tags.count(ServerTag("all")));
+ EXPECT_TRUE(selector.amAll());
+ EXPECT_FALSE(selector.hasMultipleTags());
+}
+
+// Check that a single server can be selected.
+TEST(ServerSelectorTest, one) {
+ ServerSelector selector = ServerSelector::ONE("some-tag");
+ EXPECT_EQ(ServerSelector::Type::SUBSET, selector.getType());
+ EXPECT_FALSE(selector.amUnassigned());
+
+ EXPECT_FALSE(selector.hasNoTags());
+ auto tags = selector.getTags();
+ ASSERT_EQ(1, tags.size());
+ EXPECT_EQ(1, tags.count(ServerTag("some-tag")));
+ EXPECT_FALSE(selector.hasMultipleTags());
+}
+
+// Check that multiple servers can be selected.
+TEST(ServerSelectorTest, multiple) {
+ ServerSelector selector = ServerSelector::MULTIPLE({ "tag1", "tag2", "tag3" });
+ EXPECT_EQ(ServerSelector::Type::SUBSET, selector.getType());
+ EXPECT_FALSE(selector.amUnassigned());
+
+ EXPECT_FALSE(selector.hasNoTags());
+ auto tags = selector.getTags();
+ ASSERT_EQ(3, tags.size());
+ EXPECT_EQ(1, tags.count(ServerTag("tag1")));
+ EXPECT_EQ(1, tags.count(ServerTag("tag2")));
+ EXPECT_EQ(1, tags.count(ServerTag("tag3")));
+ EXPECT_TRUE(selector.hasMultipleTags());
+}
+
+// Check that server selector can be set to ANY.
+TEST(ServerSelectorTest, any) {
+ ServerSelector selector = ServerSelector::ANY();
+ EXPECT_EQ(ServerSelector::Type::ANY, selector.getType());
+ EXPECT_FALSE(selector.amUnassigned());
+
+ EXPECT_TRUE(selector.hasNoTags());
+}
+
+}
diff --git a/src/lib/database/tests/server_unittest.cc b/src/lib/database/tests/server_unittest.cc
new file mode 100644
index 0000000..63e189b
--- /dev/null
+++ b/src/lib/database/tests/server_unittest.cc
@@ -0,0 +1,85 @@
+// Copyright (C) 2019 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 <database/server_collection.h>
+#include <testutils/test_to_element.h>
+#include <exceptions/exceptions.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::db;
+
+namespace {
+
+// Tests the constructor of the Server.
+TEST(ServerTest, constructor) {
+ ServerPtr server;
+
+ ASSERT_NO_THROW(
+ server = Server::create(ServerTag("xyz"), "my first server")
+ );
+ ASSERT_TRUE(server);
+ EXPECT_EQ("xyz", server->getServerTagAsText());
+ EXPECT_EQ("my first server", server->getDescription());
+}
+
+// Tests that too long description is rejected.
+TEST(ServerTest, tooLongDescription) {
+ EXPECT_THROW(Server::create(ServerTag("xyz"), std::string(65537, 'c')),
+ BadValue);
+}
+
+// Tests that toElement method returns expected JSON map.
+TEST(ServerTest, toElement) {
+ ServerPtr server1 = Server::create(ServerTag("foo"), "a server");
+ std::string expected1 = "{"
+ "\"server-tag\": \"foo\","
+ "\"description\": \"a server\""
+ " }";
+ isc::test::runToElementTest<Server>(expected1, *server1);
+
+ ServerPtr server2 = Server::create(ServerTag("bar"));
+ std::string expected2= "{"
+ "\"server-tag\": \"bar\","
+ "\"description\": \"\""
+ " }";
+ isc::test::runToElementTest<Server>(expected2, *server2);
+}
+
+// Tests that it is possible to fetch server by tag from the collection.
+TEST(ServerFetcherTest, getByTag) {
+ ServerCollection servers;
+
+ EXPECT_TRUE(servers.insert(Server::create(ServerTag("alpha"), "alpha description")).second);
+ EXPECT_TRUE(servers.insert(Server::create(ServerTag("beta"), "beta description")).second);
+ EXPECT_TRUE(servers.insert(Server::create(ServerTag("gamma"), "gamma description")).second);
+
+ // Inserting an element with duplicated server tag should be unsuccessful.
+ EXPECT_FALSE(servers.insert(Server::create(ServerTag("gamma"), "gamma 2 description")).second);
+
+ auto alpha = ServerFetcher::get(servers, ServerTag("alpha"));
+ ASSERT_TRUE(alpha);
+ EXPECT_EQ("alpha", alpha->getServerTagAsText());
+ EXPECT_EQ("alpha description", alpha->getDescription());
+
+ auto beta = ServerFetcher::get(servers, ServerTag("beta"));
+ ASSERT_TRUE(beta);
+ EXPECT_EQ("beta", beta->getServerTagAsText());
+ EXPECT_EQ("beta description", beta->getDescription());
+
+ auto gamma = ServerFetcher::get(servers, ServerTag("gamma"));
+ ASSERT_TRUE(gamma);
+ EXPECT_EQ("gamma", gamma->getServerTagAsText());
+ EXPECT_EQ("gamma description", gamma->getDescription());
+
+ // Null pointer should be returned when a given server does not exist.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("non-existent")));
+}
+
+}
diff --git a/src/lib/database/testutils/Makefile.am b/src/lib/database/testutils/Makefile.am
new file mode 100644
index 0000000..3cf82e0
--- /dev/null
+++ b/src/lib/database/testutils/Makefile.am
@@ -0,0 +1,21 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+TEST_CA_DIR = $(abs_srcdir)/../../asiolink/testutils/ca
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libdatabasetest.la
+
+libdatabasetest_la_SOURCES = schema.cc schema.h
+
+libdatabasetest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libdatabasetest_la_CPPFLAGS = $(AM_CPPFLAGS)
+libdatabasetest_la_LDFLAGS = $(AM_LDFLAGS)
+
+endif
diff --git a/src/lib/database/testutils/Makefile.in b/src/lib/database/testutils/Makefile.in
new file mode 100644
index 0000000..51277d9
--- /dev/null
+++ b/src/lib/database/testutils/Makefile.in
@@ -0,0 +1,860 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/database/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libdatabasetest_la_LIBADD =
+am__libdatabasetest_la_SOURCES_DIST = schema.cc schema.h
+@HAVE_GTEST_TRUE@am_libdatabasetest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libdatabasetest_la-schema.lo
+libdatabasetest_la_OBJECTS = $(am_libdatabasetest_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 =
+libdatabasetest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdatabasetest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libdatabasetest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libdatabasetest_la_rpath =
+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)/libdatabasetest_la-schema.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 = $(libdatabasetest_la_SOURCES)
+DIST_SOURCES = $(am__libdatabasetest_la_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DTEST_CA_DIR=\"$(TEST_CA_DIR)\"
+TEST_CA_DIR = $(abs_srcdir)/../../asiolink/testutils/ca
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libdatabasetest.la
+@HAVE_GTEST_TRUE@libdatabasetest_la_SOURCES = schema.cc schema.h
+@HAVE_GTEST_TRUE@libdatabasetest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdatabasetest_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libdatabasetest_la_LDFLAGS = $(AM_LDFLAGS)
+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/database/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/database/testutils/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libdatabasetest.la: $(libdatabasetest_la_OBJECTS) $(libdatabasetest_la_DEPENDENCIES) $(EXTRA_libdatabasetest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libdatabasetest_la_LINK) $(am_libdatabasetest_la_rpath) $(libdatabasetest_la_OBJECTS) $(libdatabasetest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdatabasetest_la-schema.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)$(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 $@ $<
+
+libdatabasetest_la-schema.lo: schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdatabasetest_la_CPPFLAGS) $(CPPFLAGS) $(libdatabasetest_la_CXXFLAGS) $(CXXFLAGS) -MT libdatabasetest_la-schema.lo -MD -MP -MF $(DEPDIR)/libdatabasetest_la-schema.Tpo -c -o libdatabasetest_la-schema.lo `test -f 'schema.cc' || echo '$(srcdir)/'`schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdatabasetest_la-schema.Tpo $(DEPDIR)/libdatabasetest_la-schema.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='schema.cc' object='libdatabasetest_la-schema.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) $(libdatabasetest_la_CPPFLAGS) $(CPPFLAGS) $(libdatabasetest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdatabasetest_la-schema.lo `test -f 'schema.cc' || echo '$(srcdir)/'`schema.cc
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libdatabasetest_la-schema.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-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)/libdatabasetest_la-schema.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/database/testutils/schema.cc b/src/lib/database/testutils/schema.cc
new file mode 100644
index 0000000..b396b9e
--- /dev/null
+++ b/src/lib/database/testutils/schema.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2016-2023 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>
+
+using namespace std;
+
+namespace isc {
+namespace db {
+namespace test {
+
+// Connection strings.
+// Database: keatest
+// Host: localhost
+// Username: keatest
+// Password: keatest
+
+const char* INVALID_TYPE = "type=unknown";
+const char* VALID_NAME = "name=keatest";
+const char* INVALID_NAME = "name=invalidname";
+const char* VALID_HOST = "host=localhost";
+const char* VALID_HOST_TCP = "host=127.0.0.1";
+const char* INVALID_HOST = "host=invalidhost";
+const char* INVALID_PORT_1 = "port=65536";
+const char* VALID_USER = "user=keatest";
+const char* VALID_READONLY_USER = "user=keatest_readonly";
+const char* VALID_SECURE_USER = "user=keatest_secure";
+const char* INVALID_USER = "user=invaliduser";
+const char* VALID_PASSWORD = "password=keatest";
+const char* INVALID_PASSWORD = "password=invalid";
+const char* VALID_TIMEOUT = "connect-timeout=10";
+const char* INVALID_TIMEOUT_1 = "connect-timeout=foo";
+const char* INVALID_TIMEOUT_2 = "connect-timeout=-17";
+const char* INVALID_TIMEOUT_3 = "connect-timeout=0";
+const char* VALID_READ_TIMEOUT = "read-timeout=11";
+const char* VALID_READ_TIMEOUT_ZERO = "read-timeout=0";
+const char* INVALID_READ_TIMEOUT_1 = "read-timeout=bar";
+const char* VALID_WRITE_TIMEOUT = "write-timeout=12";
+const char* VALID_WRITE_TIMEOUT_ZERO = "write-timeout=0";
+const char* INVALID_WRITE_TIMEOUT_1 = "write-timeout=baz";
+const char* VALID_TCP_USER_TIMEOUT = "tcp-user-timeout=8";
+const char* VALID_TCP_USER_TIMEOUT_ZERO = "tcp-user-timeout=0";
+const char* INVALID_TCP_USER_TIMEOUT_1 = "-7";
+const char* VALID_READONLY_DB = "readonly=true";
+const char* INVALID_READONLY_DB = "readonly=5";
+const char* VALID_CERT = "cert-file=" TEST_CA_DIR "/kea-client.crt";
+const char* VALID_KEY = "key-file=" TEST_CA_DIR "/kea-client.key";
+const char* INVALID_KEY = "key-file=" TEST_CA_DIR "/kea-other.key";
+const char* VALID_CA = "trust-anchor=" TEST_CA_DIR "/kea-ca.crt";
+const char* VALID_CIPHER = "cipher-list=AES";
+
+string connectionString(const char* type, const char* name, const char* host,
+ const char* user, const char* password,
+ const char* timeout, const char* readonly_db,
+ const char* cert_file, const char* key_file,
+ const char* trust_anchor, const char* cipher) {
+ const string space = " ";
+ string result = "";
+
+ if (type) {
+ result += string(type);
+ }
+
+ if (name) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(name);
+ }
+
+ if (host) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(host);
+ }
+
+ if (user) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(user);
+ }
+
+ if (password) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(password);
+ }
+
+ if (timeout) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(timeout);
+ }
+
+ if (readonly_db) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(readonly_db);
+ }
+
+ if (cert_file) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(cert_file);
+ }
+
+ if (key_file) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(key_file);
+ }
+
+ if (trust_anchor) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(trust_anchor);
+ }
+
+ if (cipher) {
+ if (! result.empty()) {
+ result += space;
+ }
+ result += string(cipher);
+ }
+
+ return (result);
+}
+
+bool
+softWipeEnabled() {
+ const char* const wipe_only = getenv("KEA_TEST_DB_WIPE_DATA_ONLY");
+ if (wipe_only && (std::string(wipe_only) == std::string("false"))) {
+ return (false);
+ }
+
+ return (true);
+}
+
+}
+}
+}
diff --git a/src/lib/database/testutils/schema.h b/src/lib/database/testutils/schema.h
new file mode 100644
index 0000000..c20c47d
--- /dev/null
+++ b/src/lib/database/testutils/schema.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2016-2023 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 SCHEMA_H
+#define SCHEMA_H
+
+#include <config.h>
+#include <cstdlib>
+#include <string>
+
+namespace isc {
+namespace db {
+namespace test {
+
+extern const char* INVALID_TYPE;
+extern const char* VALID_NAME;
+extern const char* INVALID_NAME;
+extern const char* VALID_HOST;
+extern const char* VALID_HOST_TCP;
+extern const char* INVALID_HOST;
+extern const char* INVALID_PORT_1;
+extern const char* VALID_USER;
+extern const char* VALID_READONLY_USER;
+extern const char* VALID_SECURE_USER;
+extern const char* INVALID_USER;
+extern const char* VALID_PASSWORD;
+extern const char* INVALID_PASSWORD;
+extern const char* VALID_TIMEOUT;
+extern const char* INVALID_TIMEOUT_1;
+extern const char* INVALID_TIMEOUT_2;
+extern const char* INVALID_TIMEOUT_3;
+extern const char* VALID_READ_TIMEOUT;
+extern const char* VALID_READ_TIMEOUT_ZERO;
+extern const char* INVALID_READ_TIMEOUT_1;
+extern const char* VALID_WRITE_TIMEOUT;
+extern const char* VALID_WRITE_TIMEOUT_ZERO;
+extern const char* INVALID_WRITE_TIMEOUT_1;
+extern const char* VALID_TCP_USER_TIMEOUT;
+extern const char* VALID_TCP_USER_TIMEOUT_ZERO;
+extern const char* INVALID_TCP_USER_TIMEOUT_1;
+extern const char* VALID_READONLY_DB;
+extern const char* INVALID_READONLY_DB;
+extern const char* VALID_CERT;
+extern const char* VALID_KEY;
+extern const char* INVALID_KEY;
+extern const char* VALID_CA;
+extern const char* VALID_CIPHER;
+
+/// @brief Given a combination of strings above, produce a connection string.
+///
+/// @param type type of the database
+/// @param name name of the database to connect to
+/// @param host hostname
+/// @param user username used to authenticate during connection attempt
+/// @param password password used to authenticate during connection attempt
+/// @param timeout timeout used during connection attempt
+/// @param readonly_db specifies if database is read only
+/// @param cert_file specifies the client certificate file
+/// @param key_file specifies the private key file
+/// @param trust_anchor specifies the trust anchor aka cert authority
+/// @param cipher specifies the cipher list
+/// @return string containing all specified parameters
+std::string connectionString(const char* type, const char* name = 0,
+ const char* host = 0, const char* user = 0,
+ const char* password = 0, const char* timeout = 0,
+ const char* readonly_db = 0,
+ const char* cert_file = 0,
+ const char* key_file = 0,
+ const char* trust_anchor = 0,
+ const char* cipher = 0);
+
+/// @brief Determines if wiping only the data between tests is enabled
+///
+/// @return true if the environment variable, KEA_TEST_DB_WIPE_DATA_ONLY is
+/// defined as "true" or if it is not present.
+bool softWipeEnabled();
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
new file mode 100644
index 0000000..541e6b1
--- /dev/null
+++ b/src/lib/dhcp/Makefile.am
@@ -0,0 +1,157 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-dhcp++.la
+libkea_dhcp___la_SOURCES =
+libkea_dhcp___la_SOURCES += classify.cc classify.h
+libkea_dhcp___la_SOURCES += dhcp6.h dhcp4.h
+libkea_dhcp___la_SOURCES += duid.cc duid.h
+libkea_dhcp___la_SOURCES += duid_factory.cc duid_factory.h
+libkea_dhcp___la_SOURCES += docsis3_option_defs.h
+libkea_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
+libkea_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
+libkea_dhcp___la_SOURCES += iface_mgr_bsd.cc
+libkea_dhcp___la_SOURCES += iface_mgr_error_handler.h
+libkea_dhcp___la_SOURCES += iface_mgr_linux.cc
+libkea_dhcp___la_SOURCES += iface_mgr_sun.cc
+libkea_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
+libkea_dhcp___la_SOURCES += opaque_data_tuple.cc opaque_data_tuple.h
+libkea_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
+libkea_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
+libkea_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
+libkea_dhcp___la_SOURCES += option6_auth.cc option6_auth.h
+libkea_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
+libkea_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
+libkea_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
+libkea_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
+libkea_dhcp___la_SOURCES += option6_pdexclude.cc option6_pdexclude.h
+libkea_dhcp___la_SOURCES += option6_status_code.cc option6_status_code.h
+libkea_dhcp___la_SOURCES += option.cc option.h
+libkea_dhcp___la_SOURCES += option_custom.cc option_custom.h
+libkea_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
+libkea_dhcp___la_SOURCES += option_definition.cc option_definition.h
+libkea_dhcp___la_SOURCES += option4_dnr.cc option4_dnr.h
+libkea_dhcp___la_SOURCES += option6_dnr.cc option6_dnr.h
+libkea_dhcp___la_SOURCES += option_int.h
+libkea_dhcp___la_SOURCES += option_int_array.h
+libkea_dhcp___la_SOURCES += option_opaque_data_tuples.cc option_opaque_data_tuples.h
+libkea_dhcp___la_SOURCES += option_space.cc option_space.h
+libkea_dhcp___la_SOURCES += option_space_container.h
+libkea_dhcp___la_SOURCES += option_string.cc option_string.h
+libkea_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
+libkea_dhcp___la_SOURCES += option_vendor_class.cc option_vendor_class.h
+libkea_dhcp___la_SOURCES += packet_queue.h
+libkea_dhcp___la_SOURCES += packet_queue_mgr.h
+libkea_dhcp___la_SOURCES += packet_queue_mgr4.cc packet_queue_mgr4.h
+libkea_dhcp___la_SOURCES += packet_queue_mgr6.cc packet_queue_mgr6.h
+libkea_dhcp___la_SOURCES += packet_queue_ring.h
+libkea_dhcp___la_SOURCES += pkt.cc pkt.h
+libkea_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libkea_dhcp___la_SOURCES += pkt4o6.cc pkt4o6.h
+libkea_dhcp___la_SOURCES += pkt6.cc pkt6.h
+libkea_dhcp___la_SOURCES += pkt_filter.h pkt_filter.cc
+libkea_dhcp___la_SOURCES += pkt_filter6.h pkt_filter6.cc
+libkea_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
+libkea_dhcp___la_SOURCES += pkt_filter_inet6.cc pkt_filter_inet6.h
+libkea_dhcp___la_SOURCES += pkt_template.h
+libkea_dhcp___la_SOURCES += socket_info.h
+
+# Utilize Linux Packet Filtering on Linux.
+if OS_LINUX
+libkea_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
+endif
+
+# Utilize Berkeley Packet Filtering on BSD.
+if OS_BSD
+libkea_dhcp___la_SOURCES += pkt_filter_bpf.cc pkt_filter_bpf.h
+endif
+
+libkea_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
+libkea_dhcp___la_SOURCES += std_option_defs.h
+
+libkea_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcp___la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_dhcp___la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_dhcp___la_LIBADD += $(BOOST_LIBS)
+libkea_dhcp___la_LIBADD += $(CRYPTO_LIBS)
+libkea_dhcp___la_LDFLAGS = -no-undefined -version-info 74:0:0
+libkea_dhcp___la_LDFLAGS += $(CRYPTO_LDFLAGS)
+
+EXTRA_DIST = README libdhcp++.dox
+
+# Specify the headers for copying into the installation directory tree. User-
+# written libraries may need access to all libdhcp++ headers.
+libkea_dhcp___includedir = $(pkgincludedir)/dhcp
+libkea_dhcp___include_HEADERS = \
+ classify.h \
+ dhcp4.h \
+ dhcp6.h \
+ docsis3_option_defs.h \
+ duid.h \
+ duid_factory.h \
+ hwaddr.h \
+ iface_mgr.h \
+ iface_mgr_error_handler.h \
+ libdhcp++.h \
+ opaque_data_tuple.h \
+ option.h \
+ option4_addrlst.h \
+ option4_client_fqdn.h \
+ option6_addrlst.h \
+ option6_auth.h \
+ option6_client_fqdn.h \
+ option6_ia.h \
+ option6_iaaddr.h \
+ option6_iaprefix.h \
+ option6_pdexclude.h \
+ option6_status_code.h \
+ option_custom.h \
+ option_data_types.h \
+ option_definition.h \
+ option_int.h \
+ option_int_array.h \
+ option_opaque_data_tuples.h \
+ option_space.h \
+ option_space_container.h \
+ option_string.h \
+ option_vendor.h \
+ option_vendor_class.h \
+ packet_queue.h \
+ packet_queue_mgr.h \
+ packet_queue_mgr4.h \
+ packet_queue_mgr6.h \
+ packet_queue_ring.h \
+ pkt.h \
+ pkt4.h \
+ pkt4o6.h \
+ pkt6.h \
+ pkt_filter.h \
+ pkt_filter6.h \
+ pkt_filter_inet.h \
+ pkt_filter_inet6.h \
+ pkt_template.h \
+ protocol_util.h \
+ socket_info.h \
+ std_option_defs.h
+
+if OS_LINUX
+libkea_dhcp___include_HEADERS += \
+ pkt_filter_lpf.h
+endif
+
+if OS_BSD
+libkea_dhcp___include_HEADERS += \
+ pkt_filter_bpf.h
+endif
diff --git a/src/lib/dhcp/Makefile.in b/src/lib/dhcp/Makefile.in
new file mode 100644
index 0000000..bb8b805
--- /dev/null
+++ b/src/lib/dhcp/Makefile.in
@@ -0,0 +1,1576 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+
+# Utilize Linux Packet Filtering on Linux.
+@OS_LINUX_TRUE@am__append_1 = pkt_filter_lpf.cc pkt_filter_lpf.h
+
+# Utilize Berkeley Packet Filtering on BSD.
+@OS_BSD_TRUE@am__append_2 = pkt_filter_bpf.cc pkt_filter_bpf.h
+@OS_LINUX_TRUE@am__append_3 = \
+@OS_LINUX_TRUE@ pkt_filter_lpf.h
+
+@OS_BSD_TRUE@am__append_4 = \
+@OS_BSD_TRUE@ pkt_filter_bpf.h
+
+subdir = src/lib/dhcp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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__libkea_dhcp___include_HEADERS_DIST) $(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)$(libkea_dhcp___includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_dhcp___la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__libkea_dhcp___la_SOURCES_DIST = classify.cc classify.h dhcp6.h \
+ dhcp4.h duid.cc duid.h duid_factory.cc duid_factory.h \
+ docsis3_option_defs.h hwaddr.cc hwaddr.h iface_mgr.cc \
+ iface_mgr.h iface_mgr_bsd.cc iface_mgr_error_handler.h \
+ iface_mgr_linux.cc iface_mgr_sun.cc libdhcp++.cc libdhcp++.h \
+ opaque_data_tuple.cc opaque_data_tuple.h option4_addrlst.cc \
+ option4_addrlst.h option4_client_fqdn.cc option4_client_fqdn.h \
+ option6_addrlst.cc option6_addrlst.h option6_auth.cc \
+ option6_auth.h option6_client_fqdn.cc option6_client_fqdn.h \
+ option6_ia.cc option6_ia.h option6_iaaddr.cc option6_iaaddr.h \
+ option6_iaprefix.cc option6_iaprefix.h option6_pdexclude.cc \
+ option6_pdexclude.h option6_status_code.cc \
+ option6_status_code.h option.cc option.h option_custom.cc \
+ option_custom.h option_data_types.cc option_data_types.h \
+ option_definition.cc option_definition.h option4_dnr.cc \
+ option4_dnr.h option6_dnr.cc option6_dnr.h option_int.h \
+ option_int_array.h option_opaque_data_tuples.cc \
+ option_opaque_data_tuples.h option_space.cc option_space.h \
+ option_space_container.h option_string.cc option_string.h \
+ option_vendor.cc option_vendor.h option_vendor_class.cc \
+ option_vendor_class.h packet_queue.h packet_queue_mgr.h \
+ packet_queue_mgr4.cc packet_queue_mgr4.h packet_queue_mgr6.cc \
+ packet_queue_mgr6.h packet_queue_ring.h pkt.cc pkt.h pkt4.cc \
+ pkt4.h pkt4o6.cc pkt4o6.h pkt6.cc pkt6.h pkt_filter.h \
+ pkt_filter.cc pkt_filter6.h pkt_filter6.cc pkt_filter_inet.cc \
+ pkt_filter_inet.h pkt_filter_inet6.cc pkt_filter_inet6.h \
+ pkt_template.h socket_info.h pkt_filter_lpf.cc \
+ pkt_filter_lpf.h pkt_filter_bpf.cc pkt_filter_bpf.h \
+ protocol_util.cc protocol_util.h std_option_defs.h
+@OS_LINUX_TRUE@am__objects_1 = libkea_dhcp___la-pkt_filter_lpf.lo
+@OS_BSD_TRUE@am__objects_2 = libkea_dhcp___la-pkt_filter_bpf.lo
+am_libkea_dhcp___la_OBJECTS = libkea_dhcp___la-classify.lo \
+ libkea_dhcp___la-duid.lo libkea_dhcp___la-duid_factory.lo \
+ libkea_dhcp___la-hwaddr.lo libkea_dhcp___la-iface_mgr.lo \
+ libkea_dhcp___la-iface_mgr_bsd.lo \
+ libkea_dhcp___la-iface_mgr_linux.lo \
+ libkea_dhcp___la-iface_mgr_sun.lo \
+ libkea_dhcp___la-libdhcp++.lo \
+ libkea_dhcp___la-opaque_data_tuple.lo \
+ libkea_dhcp___la-option4_addrlst.lo \
+ libkea_dhcp___la-option4_client_fqdn.lo \
+ libkea_dhcp___la-option6_addrlst.lo \
+ libkea_dhcp___la-option6_auth.lo \
+ libkea_dhcp___la-option6_client_fqdn.lo \
+ libkea_dhcp___la-option6_ia.lo \
+ libkea_dhcp___la-option6_iaaddr.lo \
+ libkea_dhcp___la-option6_iaprefix.lo \
+ libkea_dhcp___la-option6_pdexclude.lo \
+ libkea_dhcp___la-option6_status_code.lo \
+ libkea_dhcp___la-option.lo libkea_dhcp___la-option_custom.lo \
+ libkea_dhcp___la-option_data_types.lo \
+ libkea_dhcp___la-option_definition.lo \
+ libkea_dhcp___la-option4_dnr.lo \
+ libkea_dhcp___la-option6_dnr.lo \
+ libkea_dhcp___la-option_opaque_data_tuples.lo \
+ libkea_dhcp___la-option_space.lo \
+ libkea_dhcp___la-option_string.lo \
+ libkea_dhcp___la-option_vendor.lo \
+ libkea_dhcp___la-option_vendor_class.lo \
+ libkea_dhcp___la-packet_queue_mgr4.lo \
+ libkea_dhcp___la-packet_queue_mgr6.lo libkea_dhcp___la-pkt.lo \
+ libkea_dhcp___la-pkt4.lo libkea_dhcp___la-pkt4o6.lo \
+ libkea_dhcp___la-pkt6.lo libkea_dhcp___la-pkt_filter.lo \
+ libkea_dhcp___la-pkt_filter6.lo \
+ libkea_dhcp___la-pkt_filter_inet.lo \
+ libkea_dhcp___la-pkt_filter_inet6.lo $(am__objects_1) \
+ $(am__objects_2) libkea_dhcp___la-protocol_util.lo
+libkea_dhcp___la_OBJECTS = $(am_libkea_dhcp___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_dhcp___la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_dhcp___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_dhcp___la-classify.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-duid.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option4_dnr.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_dnr.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_space.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_string.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo \
+ ./$(DEPDIR)/libkea_dhcp___la-protocol_util.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_dhcp___la_SOURCES)
+DIST_SOURCES = $(am__libkea_dhcp___la_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
+am__libkea_dhcp___include_HEADERS_DIST = classify.h dhcp4.h dhcp6.h \
+ docsis3_option_defs.h duid.h duid_factory.h hwaddr.h \
+ iface_mgr.h iface_mgr_error_handler.h libdhcp++.h \
+ opaque_data_tuple.h option.h option4_addrlst.h \
+ option4_client_fqdn.h option6_addrlst.h option6_auth.h \
+ option6_client_fqdn.h option6_ia.h option6_iaaddr.h \
+ option6_iaprefix.h option6_pdexclude.h option6_status_code.h \
+ option_custom.h option_data_types.h option_definition.h \
+ option_int.h option_int_array.h option_opaque_data_tuples.h \
+ option_space.h option_space_container.h option_string.h \
+ option_vendor.h option_vendor_class.h packet_queue.h \
+ packet_queue_mgr.h packet_queue_mgr4.h packet_queue_mgr6.h \
+ packet_queue_ring.h pkt.h pkt4.h pkt4o6.h pkt6.h pkt_filter.h \
+ pkt_filter6.h pkt_filter_inet.h pkt_filter_inet6.h \
+ pkt_template.h protocol_util.h socket_info.h std_option_defs.h \
+ pkt_filter_lpf.h pkt_filter_bpf.h
+HEADERS = $(libkea_dhcp___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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-dhcp++.la
+libkea_dhcp___la_SOURCES = classify.cc classify.h dhcp6.h dhcp4.h \
+ duid.cc duid.h duid_factory.cc duid_factory.h \
+ docsis3_option_defs.h hwaddr.cc hwaddr.h iface_mgr.cc \
+ iface_mgr.h iface_mgr_bsd.cc iface_mgr_error_handler.h \
+ iface_mgr_linux.cc iface_mgr_sun.cc libdhcp++.cc libdhcp++.h \
+ opaque_data_tuple.cc opaque_data_tuple.h option4_addrlst.cc \
+ option4_addrlst.h option4_client_fqdn.cc option4_client_fqdn.h \
+ option6_addrlst.cc option6_addrlst.h option6_auth.cc \
+ option6_auth.h option6_client_fqdn.cc option6_client_fqdn.h \
+ option6_ia.cc option6_ia.h option6_iaaddr.cc option6_iaaddr.h \
+ option6_iaprefix.cc option6_iaprefix.h option6_pdexclude.cc \
+ option6_pdexclude.h option6_status_code.cc \
+ option6_status_code.h option.cc option.h option_custom.cc \
+ option_custom.h option_data_types.cc option_data_types.h \
+ option_definition.cc option_definition.h option4_dnr.cc \
+ option4_dnr.h option6_dnr.cc option6_dnr.h option_int.h \
+ option_int_array.h option_opaque_data_tuples.cc \
+ option_opaque_data_tuples.h option_space.cc option_space.h \
+ option_space_container.h option_string.cc option_string.h \
+ option_vendor.cc option_vendor.h option_vendor_class.cc \
+ option_vendor_class.h packet_queue.h packet_queue_mgr.h \
+ packet_queue_mgr4.cc packet_queue_mgr4.h packet_queue_mgr6.cc \
+ packet_queue_mgr6.h packet_queue_ring.h pkt.cc pkt.h pkt4.cc \
+ pkt4.h pkt4o6.cc pkt4o6.h pkt6.cc pkt6.h pkt_filter.h \
+ pkt_filter.cc pkt_filter6.h pkt_filter6.cc pkt_filter_inet.cc \
+ pkt_filter_inet.h pkt_filter_inet6.cc pkt_filter_inet6.h \
+ pkt_template.h socket_info.h $(am__append_1) $(am__append_2) \
+ protocol_util.cc protocol_util.h std_option_defs.h
+libkea_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcp___la_LIBADD = \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(BOOST_LIBS) $(CRYPTO_LIBS)
+libkea_dhcp___la_LDFLAGS = -no-undefined -version-info 74:0:0 \
+ $(CRYPTO_LDFLAGS)
+EXTRA_DIST = README libdhcp++.dox
+
+# Specify the headers for copying into the installation directory tree. User-
+# written libraries may need access to all libdhcp++ headers.
+libkea_dhcp___includedir = $(pkgincludedir)/dhcp
+libkea_dhcp___include_HEADERS = classify.h dhcp4.h dhcp6.h \
+ docsis3_option_defs.h duid.h duid_factory.h hwaddr.h \
+ iface_mgr.h iface_mgr_error_handler.h libdhcp++.h \
+ opaque_data_tuple.h option.h option4_addrlst.h \
+ option4_client_fqdn.h option6_addrlst.h option6_auth.h \
+ option6_client_fqdn.h option6_ia.h option6_iaaddr.h \
+ option6_iaprefix.h option6_pdexclude.h option6_status_code.h \
+ option_custom.h option_data_types.h option_definition.h \
+ option_int.h option_int_array.h option_opaque_data_tuples.h \
+ option_space.h option_space_container.h option_string.h \
+ option_vendor.h option_vendor_class.h packet_queue.h \
+ packet_queue_mgr.h packet_queue_mgr4.h packet_queue_mgr6.h \
+ packet_queue_ring.h pkt.h pkt4.h pkt4o6.h pkt6.h pkt_filter.h \
+ pkt_filter6.h pkt_filter_inet.h pkt_filter_inet6.h \
+ pkt_template.h protocol_util.h socket_info.h std_option_defs.h \
+ $(am__append_3) $(am__append_4)
+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/dhcp/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcp/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-dhcp++.la: $(libkea_dhcp___la_OBJECTS) $(libkea_dhcp___la_DEPENDENCIES) $(EXTRA_libkea_dhcp___la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_dhcp___la_LINK) -rpath $(libdir) $(libkea_dhcp___la_OBJECTS) $(libkea_dhcp___la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-classify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-duid.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option4_dnr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_dnr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_space.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_string.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp___la-protocol_util.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)$(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 $@ $<
+
+libkea_dhcp___la-classify.lo: classify.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-classify.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-classify.Tpo -c -o libkea_dhcp___la-classify.lo `test -f 'classify.cc' || echo '$(srcdir)/'`classify.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-classify.Tpo $(DEPDIR)/libkea_dhcp___la-classify.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify.cc' object='libkea_dhcp___la-classify.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-classify.lo `test -f 'classify.cc' || echo '$(srcdir)/'`classify.cc
+
+libkea_dhcp___la-duid.lo: duid.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-duid.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-duid.Tpo -c -o libkea_dhcp___la-duid.lo `test -f 'duid.cc' || echo '$(srcdir)/'`duid.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-duid.Tpo $(DEPDIR)/libkea_dhcp___la-duid.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid.cc' object='libkea_dhcp___la-duid.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-duid.lo `test -f 'duid.cc' || echo '$(srcdir)/'`duid.cc
+
+libkea_dhcp___la-duid_factory.lo: duid_factory.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-duid_factory.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-duid_factory.Tpo -c -o libkea_dhcp___la-duid_factory.lo `test -f 'duid_factory.cc' || echo '$(srcdir)/'`duid_factory.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-duid_factory.Tpo $(DEPDIR)/libkea_dhcp___la-duid_factory.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory.cc' object='libkea_dhcp___la-duid_factory.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-duid_factory.lo `test -f 'duid_factory.cc' || echo '$(srcdir)/'`duid_factory.cc
+
+libkea_dhcp___la-hwaddr.lo: hwaddr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-hwaddr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-hwaddr.Tpo -c -o libkea_dhcp___la-hwaddr.lo `test -f 'hwaddr.cc' || echo '$(srcdir)/'`hwaddr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-hwaddr.Tpo $(DEPDIR)/libkea_dhcp___la-hwaddr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr.cc' object='libkea_dhcp___la-hwaddr.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-hwaddr.lo `test -f 'hwaddr.cc' || echo '$(srcdir)/'`hwaddr.cc
+
+libkea_dhcp___la-iface_mgr.lo: iface_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr.Tpo -c -o libkea_dhcp___la-iface_mgr.lo `test -f 'iface_mgr.cc' || echo '$(srcdir)/'`iface_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr.cc' object='libkea_dhcp___la-iface_mgr.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr.lo `test -f 'iface_mgr.cc' || echo '$(srcdir)/'`iface_mgr.cc
+
+libkea_dhcp___la-iface_mgr_bsd.lo: iface_mgr_bsd.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr_bsd.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Tpo -c -o libkea_dhcp___la-iface_mgr_bsd.lo `test -f 'iface_mgr_bsd.cc' || echo '$(srcdir)/'`iface_mgr_bsd.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_bsd.cc' object='libkea_dhcp___la-iface_mgr_bsd.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr_bsd.lo `test -f 'iface_mgr_bsd.cc' || echo '$(srcdir)/'`iface_mgr_bsd.cc
+
+libkea_dhcp___la-iface_mgr_linux.lo: iface_mgr_linux.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr_linux.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Tpo -c -o libkea_dhcp___la-iface_mgr_linux.lo `test -f 'iface_mgr_linux.cc' || echo '$(srcdir)/'`iface_mgr_linux.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_linux.cc' object='libkea_dhcp___la-iface_mgr_linux.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr_linux.lo `test -f 'iface_mgr_linux.cc' || echo '$(srcdir)/'`iface_mgr_linux.cc
+
+libkea_dhcp___la-iface_mgr_sun.lo: iface_mgr_sun.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-iface_mgr_sun.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Tpo -c -o libkea_dhcp___la-iface_mgr_sun.lo `test -f 'iface_mgr_sun.cc' || echo '$(srcdir)/'`iface_mgr_sun.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Tpo $(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_sun.cc' object='libkea_dhcp___la-iface_mgr_sun.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-iface_mgr_sun.lo `test -f 'iface_mgr_sun.cc' || echo '$(srcdir)/'`iface_mgr_sun.cc
+
+libkea_dhcp___la-libdhcp++.lo: libdhcp++.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-libdhcp++.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-libdhcp++.Tpo -c -o libkea_dhcp___la-libdhcp++.lo `test -f 'libdhcp++.cc' || echo '$(srcdir)/'`libdhcp++.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-libdhcp++.Tpo $(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++.cc' object='libkea_dhcp___la-libdhcp++.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-libdhcp++.lo `test -f 'libdhcp++.cc' || echo '$(srcdir)/'`libdhcp++.cc
+
+libkea_dhcp___la-opaque_data_tuple.lo: opaque_data_tuple.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-opaque_data_tuple.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Tpo -c -o libkea_dhcp___la-opaque_data_tuple.lo `test -f 'opaque_data_tuple.cc' || echo '$(srcdir)/'`opaque_data_tuple.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Tpo $(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple.cc' object='libkea_dhcp___la-opaque_data_tuple.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-opaque_data_tuple.lo `test -f 'opaque_data_tuple.cc' || echo '$(srcdir)/'`opaque_data_tuple.cc
+
+libkea_dhcp___la-option4_addrlst.lo: option4_addrlst.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option4_addrlst.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option4_addrlst.Tpo -c -o libkea_dhcp___la-option4_addrlst.lo `test -f 'option4_addrlst.cc' || echo '$(srcdir)/'`option4_addrlst.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option4_addrlst.Tpo $(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst.cc' object='libkea_dhcp___la-option4_addrlst.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option4_addrlst.lo `test -f 'option4_addrlst.cc' || echo '$(srcdir)/'`option4_addrlst.cc
+
+libkea_dhcp___la-option4_client_fqdn.lo: option4_client_fqdn.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option4_client_fqdn.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Tpo -c -o libkea_dhcp___la-option4_client_fqdn.lo `test -f 'option4_client_fqdn.cc' || echo '$(srcdir)/'`option4_client_fqdn.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Tpo $(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn.cc' object='libkea_dhcp___la-option4_client_fqdn.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option4_client_fqdn.lo `test -f 'option4_client_fqdn.cc' || echo '$(srcdir)/'`option4_client_fqdn.cc
+
+libkea_dhcp___la-option6_addrlst.lo: option6_addrlst.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_addrlst.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_addrlst.Tpo -c -o libkea_dhcp___la-option6_addrlst.lo `test -f 'option6_addrlst.cc' || echo '$(srcdir)/'`option6_addrlst.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_addrlst.Tpo $(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst.cc' object='libkea_dhcp___la-option6_addrlst.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_addrlst.lo `test -f 'option6_addrlst.cc' || echo '$(srcdir)/'`option6_addrlst.cc
+
+libkea_dhcp___la-option6_auth.lo: option6_auth.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_auth.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_auth.Tpo -c -o libkea_dhcp___la-option6_auth.lo `test -f 'option6_auth.cc' || echo '$(srcdir)/'`option6_auth.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_auth.Tpo $(DEPDIR)/libkea_dhcp___la-option6_auth.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth.cc' object='libkea_dhcp___la-option6_auth.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_auth.lo `test -f 'option6_auth.cc' || echo '$(srcdir)/'`option6_auth.cc
+
+libkea_dhcp___la-option6_client_fqdn.lo: option6_client_fqdn.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_client_fqdn.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Tpo -c -o libkea_dhcp___la-option6_client_fqdn.lo `test -f 'option6_client_fqdn.cc' || echo '$(srcdir)/'`option6_client_fqdn.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Tpo $(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn.cc' object='libkea_dhcp___la-option6_client_fqdn.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_client_fqdn.lo `test -f 'option6_client_fqdn.cc' || echo '$(srcdir)/'`option6_client_fqdn.cc
+
+libkea_dhcp___la-option6_ia.lo: option6_ia.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_ia.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_ia.Tpo -c -o libkea_dhcp___la-option6_ia.lo `test -f 'option6_ia.cc' || echo '$(srcdir)/'`option6_ia.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_ia.Tpo $(DEPDIR)/libkea_dhcp___la-option6_ia.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia.cc' object='libkea_dhcp___la-option6_ia.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_ia.lo `test -f 'option6_ia.cc' || echo '$(srcdir)/'`option6_ia.cc
+
+libkea_dhcp___la-option6_iaaddr.lo: option6_iaaddr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_iaaddr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Tpo -c -o libkea_dhcp___la-option6_iaaddr.lo `test -f 'option6_iaaddr.cc' || echo '$(srcdir)/'`option6_iaaddr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Tpo $(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr.cc' object='libkea_dhcp___la-option6_iaaddr.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_iaaddr.lo `test -f 'option6_iaaddr.cc' || echo '$(srcdir)/'`option6_iaaddr.cc
+
+libkea_dhcp___la-option6_iaprefix.lo: option6_iaprefix.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_iaprefix.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Tpo -c -o libkea_dhcp___la-option6_iaprefix.lo `test -f 'option6_iaprefix.cc' || echo '$(srcdir)/'`option6_iaprefix.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Tpo $(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix.cc' object='libkea_dhcp___la-option6_iaprefix.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_iaprefix.lo `test -f 'option6_iaprefix.cc' || echo '$(srcdir)/'`option6_iaprefix.cc
+
+libkea_dhcp___la-option6_pdexclude.lo: option6_pdexclude.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_pdexclude.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Tpo -c -o libkea_dhcp___la-option6_pdexclude.lo `test -f 'option6_pdexclude.cc' || echo '$(srcdir)/'`option6_pdexclude.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Tpo $(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude.cc' object='libkea_dhcp___la-option6_pdexclude.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_pdexclude.lo `test -f 'option6_pdexclude.cc' || echo '$(srcdir)/'`option6_pdexclude.cc
+
+libkea_dhcp___la-option6_status_code.lo: option6_status_code.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_status_code.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_status_code.Tpo -c -o libkea_dhcp___la-option6_status_code.lo `test -f 'option6_status_code.cc' || echo '$(srcdir)/'`option6_status_code.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_status_code.Tpo $(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code.cc' object='libkea_dhcp___la-option6_status_code.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_status_code.lo `test -f 'option6_status_code.cc' || echo '$(srcdir)/'`option6_status_code.cc
+
+libkea_dhcp___la-option.lo: option.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option.Tpo -c -o libkea_dhcp___la-option.lo `test -f 'option.cc' || echo '$(srcdir)/'`option.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option.Tpo $(DEPDIR)/libkea_dhcp___la-option.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option.cc' object='libkea_dhcp___la-option.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option.lo `test -f 'option.cc' || echo '$(srcdir)/'`option.cc
+
+libkea_dhcp___la-option_custom.lo: option_custom.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_custom.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_custom.Tpo -c -o libkea_dhcp___la-option_custom.lo `test -f 'option_custom.cc' || echo '$(srcdir)/'`option_custom.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_custom.Tpo $(DEPDIR)/libkea_dhcp___la-option_custom.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom.cc' object='libkea_dhcp___la-option_custom.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_custom.lo `test -f 'option_custom.cc' || echo '$(srcdir)/'`option_custom.cc
+
+libkea_dhcp___la-option_data_types.lo: option_data_types.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_data_types.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_data_types.Tpo -c -o libkea_dhcp___la-option_data_types.lo `test -f 'option_data_types.cc' || echo '$(srcdir)/'`option_data_types.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_data_types.Tpo $(DEPDIR)/libkea_dhcp___la-option_data_types.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types.cc' object='libkea_dhcp___la-option_data_types.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_data_types.lo `test -f 'option_data_types.cc' || echo '$(srcdir)/'`option_data_types.cc
+
+libkea_dhcp___la-option_definition.lo: option_definition.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_definition.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_definition.Tpo -c -o libkea_dhcp___la-option_definition.lo `test -f 'option_definition.cc' || echo '$(srcdir)/'`option_definition.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_definition.Tpo $(DEPDIR)/libkea_dhcp___la-option_definition.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition.cc' object='libkea_dhcp___la-option_definition.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_definition.lo `test -f 'option_definition.cc' || echo '$(srcdir)/'`option_definition.cc
+
+libkea_dhcp___la-option4_dnr.lo: option4_dnr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option4_dnr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option4_dnr.Tpo -c -o libkea_dhcp___la-option4_dnr.lo `test -f 'option4_dnr.cc' || echo '$(srcdir)/'`option4_dnr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option4_dnr.Tpo $(DEPDIR)/libkea_dhcp___la-option4_dnr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_dnr.cc' object='libkea_dhcp___la-option4_dnr.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option4_dnr.lo `test -f 'option4_dnr.cc' || echo '$(srcdir)/'`option4_dnr.cc
+
+libkea_dhcp___la-option6_dnr.lo: option6_dnr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option6_dnr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option6_dnr.Tpo -c -o libkea_dhcp___la-option6_dnr.lo `test -f 'option6_dnr.cc' || echo '$(srcdir)/'`option6_dnr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option6_dnr.Tpo $(DEPDIR)/libkea_dhcp___la-option6_dnr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_dnr.cc' object='libkea_dhcp___la-option6_dnr.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option6_dnr.lo `test -f 'option6_dnr.cc' || echo '$(srcdir)/'`option6_dnr.cc
+
+libkea_dhcp___la-option_opaque_data_tuples.lo: option_opaque_data_tuples.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_opaque_data_tuples.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Tpo -c -o libkea_dhcp___la-option_opaque_data_tuples.lo `test -f 'option_opaque_data_tuples.cc' || echo '$(srcdir)/'`option_opaque_data_tuples.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Tpo $(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples.cc' object='libkea_dhcp___la-option_opaque_data_tuples.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_opaque_data_tuples.lo `test -f 'option_opaque_data_tuples.cc' || echo '$(srcdir)/'`option_opaque_data_tuples.cc
+
+libkea_dhcp___la-option_space.lo: option_space.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_space.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_space.Tpo -c -o libkea_dhcp___la-option_space.lo `test -f 'option_space.cc' || echo '$(srcdir)/'`option_space.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_space.Tpo $(DEPDIR)/libkea_dhcp___la-option_space.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space.cc' object='libkea_dhcp___la-option_space.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_space.lo `test -f 'option_space.cc' || echo '$(srcdir)/'`option_space.cc
+
+libkea_dhcp___la-option_string.lo: option_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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_string.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_string.Tpo -c -o libkea_dhcp___la-option_string.lo `test -f 'option_string.cc' || echo '$(srcdir)/'`option_string.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_string.Tpo $(DEPDIR)/libkea_dhcp___la-option_string.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string.cc' object='libkea_dhcp___la-option_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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_string.lo `test -f 'option_string.cc' || echo '$(srcdir)/'`option_string.cc
+
+libkea_dhcp___la-option_vendor.lo: option_vendor.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_vendor.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_vendor.Tpo -c -o libkea_dhcp___la-option_vendor.lo `test -f 'option_vendor.cc' || echo '$(srcdir)/'`option_vendor.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_vendor.Tpo $(DEPDIR)/libkea_dhcp___la-option_vendor.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor.cc' object='libkea_dhcp___la-option_vendor.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_vendor.lo `test -f 'option_vendor.cc' || echo '$(srcdir)/'`option_vendor.cc
+
+libkea_dhcp___la-option_vendor_class.lo: option_vendor_class.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-option_vendor_class.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-option_vendor_class.Tpo -c -o libkea_dhcp___la-option_vendor_class.lo `test -f 'option_vendor_class.cc' || echo '$(srcdir)/'`option_vendor_class.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-option_vendor_class.Tpo $(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class.cc' object='libkea_dhcp___la-option_vendor_class.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-option_vendor_class.lo `test -f 'option_vendor_class.cc' || echo '$(srcdir)/'`option_vendor_class.cc
+
+libkea_dhcp___la-packet_queue_mgr4.lo: packet_queue_mgr4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-packet_queue_mgr4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Tpo -c -o libkea_dhcp___la-packet_queue_mgr4.lo `test -f 'packet_queue_mgr4.cc' || echo '$(srcdir)/'`packet_queue_mgr4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Tpo $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4.cc' object='libkea_dhcp___la-packet_queue_mgr4.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-packet_queue_mgr4.lo `test -f 'packet_queue_mgr4.cc' || echo '$(srcdir)/'`packet_queue_mgr4.cc
+
+libkea_dhcp___la-packet_queue_mgr6.lo: packet_queue_mgr6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-packet_queue_mgr6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Tpo -c -o libkea_dhcp___la-packet_queue_mgr6.lo `test -f 'packet_queue_mgr6.cc' || echo '$(srcdir)/'`packet_queue_mgr6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Tpo $(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6.cc' object='libkea_dhcp___la-packet_queue_mgr6.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-packet_queue_mgr6.lo `test -f 'packet_queue_mgr6.cc' || echo '$(srcdir)/'`packet_queue_mgr6.cc
+
+libkea_dhcp___la-pkt.lo: pkt.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt.Tpo -c -o libkea_dhcp___la-pkt.lo `test -f 'pkt.cc' || echo '$(srcdir)/'`pkt.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt.Tpo $(DEPDIR)/libkea_dhcp___la-pkt.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt.cc' object='libkea_dhcp___la-pkt.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt.lo `test -f 'pkt.cc' || echo '$(srcdir)/'`pkt.cc
+
+libkea_dhcp___la-pkt4.lo: pkt4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt4.Tpo -c -o libkea_dhcp___la-pkt4.lo `test -f 'pkt4.cc' || echo '$(srcdir)/'`pkt4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt4.Tpo $(DEPDIR)/libkea_dhcp___la-pkt4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4.cc' object='libkea_dhcp___la-pkt4.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt4.lo `test -f 'pkt4.cc' || echo '$(srcdir)/'`pkt4.cc
+
+libkea_dhcp___la-pkt4o6.lo: pkt4o6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt4o6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt4o6.Tpo -c -o libkea_dhcp___la-pkt4o6.lo `test -f 'pkt4o6.cc' || echo '$(srcdir)/'`pkt4o6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt4o6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6.cc' object='libkea_dhcp___la-pkt4o6.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt4o6.lo `test -f 'pkt4o6.cc' || echo '$(srcdir)/'`pkt4o6.cc
+
+libkea_dhcp___la-pkt6.lo: pkt6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt6.Tpo -c -o libkea_dhcp___la-pkt6.lo `test -f 'pkt6.cc' || echo '$(srcdir)/'`pkt6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6.cc' object='libkea_dhcp___la-pkt6.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt6.lo `test -f 'pkt6.cc' || echo '$(srcdir)/'`pkt6.cc
+
+libkea_dhcp___la-pkt_filter.lo: pkt_filter.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter.Tpo -c -o libkea_dhcp___la-pkt_filter.lo `test -f 'pkt_filter.cc' || echo '$(srcdir)/'`pkt_filter.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter.cc' object='libkea_dhcp___la-pkt_filter.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter.lo `test -f 'pkt_filter.cc' || echo '$(srcdir)/'`pkt_filter.cc
+
+libkea_dhcp___la-pkt_filter6.lo: pkt_filter6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter6.Tpo -c -o libkea_dhcp___la-pkt_filter6.lo `test -f 'pkt_filter6.cc' || echo '$(srcdir)/'`pkt_filter6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6.cc' object='libkea_dhcp___la-pkt_filter6.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter6.lo `test -f 'pkt_filter6.cc' || echo '$(srcdir)/'`pkt_filter6.cc
+
+libkea_dhcp___la-pkt_filter_inet.lo: pkt_filter_inet.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_inet.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Tpo -c -o libkea_dhcp___la-pkt_filter_inet.lo `test -f 'pkt_filter_inet.cc' || echo '$(srcdir)/'`pkt_filter_inet.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet.cc' object='libkea_dhcp___la-pkt_filter_inet.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_inet.lo `test -f 'pkt_filter_inet.cc' || echo '$(srcdir)/'`pkt_filter_inet.cc
+
+libkea_dhcp___la-pkt_filter_inet6.lo: pkt_filter_inet6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_inet6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Tpo -c -o libkea_dhcp___la-pkt_filter_inet6.lo `test -f 'pkt_filter_inet6.cc' || echo '$(srcdir)/'`pkt_filter_inet6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6.cc' object='libkea_dhcp___la-pkt_filter_inet6.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_inet6.lo `test -f 'pkt_filter_inet6.cc' || echo '$(srcdir)/'`pkt_filter_inet6.cc
+
+libkea_dhcp___la-pkt_filter_lpf.lo: pkt_filter_lpf.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_lpf.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Tpo -c -o libkea_dhcp___la-pkt_filter_lpf.lo `test -f 'pkt_filter_lpf.cc' || echo '$(srcdir)/'`pkt_filter_lpf.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf.cc' object='libkea_dhcp___la-pkt_filter_lpf.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_lpf.lo `test -f 'pkt_filter_lpf.cc' || echo '$(srcdir)/'`pkt_filter_lpf.cc
+
+libkea_dhcp___la-pkt_filter_bpf.lo: pkt_filter_bpf.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-pkt_filter_bpf.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Tpo -c -o libkea_dhcp___la-pkt_filter_bpf.lo `test -f 'pkt_filter_bpf.cc' || echo '$(srcdir)/'`pkt_filter_bpf.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Tpo $(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf.cc' object='libkea_dhcp___la-pkt_filter_bpf.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-pkt_filter_bpf.lo `test -f 'pkt_filter_bpf.cc' || echo '$(srcdir)/'`pkt_filter_bpf.cc
+
+libkea_dhcp___la-protocol_util.lo: protocol_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp___la-protocol_util.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp___la-protocol_util.Tpo -c -o libkea_dhcp___la-protocol_util.lo `test -f 'protocol_util.cc' || echo '$(srcdir)/'`protocol_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp___la-protocol_util.Tpo $(DEPDIR)/libkea_dhcp___la-protocol_util.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util.cc' object='libkea_dhcp___la-protocol_util.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_dhcp___la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp___la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp___la-protocol_util.lo `test -f 'protocol_util.cc' || echo '$(srcdir)/'`protocol_util.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_dhcp___includeHEADERS: $(libkea_dhcp___include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_dhcp___include_HEADERS)'; test -n "$(libkea_dhcp___includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_dhcp___includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_dhcp___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)$(libkea_dhcp___includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_dhcp___includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_dhcp___includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_dhcp___include_HEADERS)'; test -n "$(libkea_dhcp___includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_dhcp___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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_dhcp___includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-classify.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_dnr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_dnr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_space.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_string.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-protocol_util.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-libkea_dhcp___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_dhcp___la-classify.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-duid_factory.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-hwaddr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_bsd.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_linux.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-iface_mgr_sun.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-libdhcp++.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-opaque_data_tuple.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_addrlst.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_client_fqdn.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option4_dnr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_addrlst.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_auth.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_client_fqdn.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_dnr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_ia.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaaddr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_iaprefix.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_pdexclude.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option6_status_code.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_custom.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_data_types.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_definition.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_opaque_data_tuples.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_space.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_string.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-option_vendor_class.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-packet_queue_mgr6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt4o6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_bpf.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_inet6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-pkt_filter_lpf.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp___la-protocol_util.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-libkea_dhcp___includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_dhcp___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-libkea_dhcp___includeHEADERS
+
+.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/dhcp/README b/src/lib/dhcp/README
new file mode 100644
index 0000000..c5e70f2
--- /dev/null
+++ b/src/lib/dhcp/README
@@ -0,0 +1,9 @@
+This directory holds implementation for DHCP libraries:
+
+libdhcp++ - this is a generic purpose DHCP library. Please be careful
+what is put here. It is going to be shared by various servers (the "regular"
+one and the embedded as well), clients, relays and performance tools.
+
+libdhcpsrv - Server specific code goes in here. It will be used by
+dhcp4 and dhcp6 server.
+
diff --git a/src/lib/dhcp/classify.cc b/src/lib/dhcp/classify.cc
new file mode 100644
index 0000000..7b0be45
--- /dev/null
+++ b/src/lib/dhcp/classify.cc
@@ -0,0 +1,76 @@
+// Copyright (C) 2014-2022 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 <cc/data.h>
+#include <dhcp/classify.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+using namespace isc::data;
+
+ClientClasses::ClientClasses(const std::string& class_names)
+ : container_() {
+ std::vector<std::string> split_text;
+ boost::split(split_text, class_names, boost::is_any_of(","),
+ boost::algorithm::token_compress_off);
+ for (size_t i = 0; i < split_text.size(); ++i) {
+ std::string trimmed = util::str::trim(split_text[i]);
+ // Ignore empty class names.
+ if (!trimmed.empty()) {
+ insert(ClientClass(trimmed));
+ }
+ }
+}
+
+void
+ClientClasses::erase(const ClientClass& class_name) {
+ auto& idx = container_.get<ClassNameTag>();
+ auto it = idx.find(class_name);
+ if (it != idx.end()) {
+ static_cast<void>(idx.erase(it));
+ }
+}
+
+bool
+ClientClasses::contains(const ClientClass& x) const {
+ auto const& idx = container_.get<ClassNameTag>();
+ return (idx.count(x) != 0);
+}
+
+std::string
+ClientClasses::toText(const std::string& separator) const {
+ std::stringstream s;
+ for (const_iterator class_it = cbegin(); class_it != cend(); ++class_it) {
+ if (class_it != cbegin()) {
+ s << separator;
+ }
+ s << *class_it;
+ }
+ return (s.str());
+}
+
+ElementPtr
+ClientClasses::toElement() const {
+ ElementPtr result(Element::createList());
+ for (ClientClass c : container_) {
+ result->add(Element::create(c));
+ }
+ return (result);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h
new file mode 100644
index 0000000..34977a5
--- /dev/null
+++ b/src/lib/dhcp/classify.h
@@ -0,0 +1,208 @@
+// Copyright (C) 2014-2022 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 CLASSIFY_H
+#define CLASSIFY_H
+
+#include <cc/data.h>
+
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/identity.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+
+#include <string>
+
+/// @file classify.h
+///
+/// @brief Defines elements for storing the names of client classes
+///
+/// This file defines common elements used to track the client classes
+/// that may be associated with a given packet. In order to minimize the
+/// exposure of the DHCP library to server side concepts such as client
+/// classification the classes herein provide a mechanism to maintain lists
+/// of class names, rather than the classes they represent. It is the
+/// upper layers' prerogative to use these names as they see fit.
+///
+/// @todo This file should be moved to dhcpsrv eventually as the classification
+/// is server side concept. Client has no notion of classifying incoming server
+/// messages as it usually talks to only one server. That move is not possible
+/// yet, as the Pkt4 and Pkt6 classes have server-side implementation, even
+/// though they reside in the dhcp directory.
+
+namespace isc {
+namespace dhcp {
+
+ /// @brief Defines a single class name.
+ typedef std::string ClientClass;
+
+ /// @brief Tag for the sequence index.
+ struct ClassSequenceTag { };
+
+ /// @brief Tag for the name index.
+ struct ClassNameTag { };
+
+ /// @brief the client class multi-index.
+ typedef boost::multi_index_container<
+ ClientClass,
+ boost::multi_index::indexed_by<
+ // First index is the sequence one.
+ boost::multi_index::sequenced<
+ boost::multi_index::tag<ClassSequenceTag>
+ >,
+ // Second index is the name hash one.
+ boost::multi_index::hashed_unique<
+ boost::multi_index::tag<ClassNameTag>,
+ boost::multi_index::identity<ClientClass>
+ >
+ >
+ > ClientClassContainer;
+
+ /// @brief Defines a subclass to template class relation.
+ struct SubClassRelation {
+ /// @brief Constructor.
+ SubClassRelation(const ClientClass& class_def, const ClientClass& subclass) :
+ class_def_(class_def), class_(subclass) {
+ }
+
+ /// @brief The class definition name.
+ ClientClass class_def_;
+
+ /// @brief The class or subclass name.
+ ClientClass class_;
+ };
+
+ /// @brief Tag for the sequence index.
+ struct TemplateClassSequenceTag { };
+
+ /// @brief Tag for the name index.
+ struct TemplateClassNameTag { };
+
+ /// @brief the subclass multi-index.
+ typedef boost::multi_index_container<
+ SubClassRelation,
+ boost::multi_index::indexed_by<
+ // First index is the sequence one.
+ boost::multi_index::sequenced<
+ boost::multi_index::tag<TemplateClassSequenceTag>
+ >,
+ // Second index is the name hash one.
+ boost::multi_index::hashed_unique<
+ boost::multi_index::tag<TemplateClassNameTag>,
+ boost::multi_index::member<SubClassRelation,
+ ClientClass,
+ &SubClassRelation::class_def_>
+ >
+ >
+ > SubClassRelationContainer;
+
+ /// @brief Container for storing client class names
+ ///
+ /// Both a list to iterate on it in insert order and unordered
+ /// set of names for existence.
+ class ClientClasses {
+ public:
+
+ /// @brief Type of iterators
+ typedef ClientClassContainer::const_iterator const_iterator;
+ typedef ClientClassContainer::iterator iterator;
+
+ /// @brief Default constructor.
+ ClientClasses() : container_() {
+ }
+
+ /// @brief Constructor from comma separated values.
+ ///
+ /// @param class_names A string containing a client classes separated
+ /// with commas. The class names are trimmed before insertion to the set.
+ ClientClasses(const std::string& class_names);
+
+ /// @brief Insert an element.
+ ///
+ /// @param class_name The name of the class to insert
+ void insert(const ClientClass& class_name) {
+ static_cast<void>(container_.push_back(class_name));
+ }
+
+ /// @brief Erase element by name.
+ ///
+ /// @param class_name The name of the class to erase.
+ void erase(const ClientClass& class_name);
+
+ /// @brief Check if classes is empty.
+ bool empty() const {
+ return (container_.empty());
+ }
+
+ /// @brief Returns the number of classes.
+ ///
+ /// @note; in C++ 11 list size complexity is constant so
+ /// there is no advantage to use the set part.
+ size_t size() const {
+ return (container_.size());
+ }
+
+ /// @brief Iterators to the first element.
+ /// @{
+ const_iterator cbegin() const {
+ return (container_.cbegin());
+ }
+ const_iterator begin() const {
+ return (container_.begin());
+ }
+ iterator begin() {
+ return (container_.begin());
+ }
+ /// @}
+
+ /// @brief Iterators to the past the end element.
+ /// @{
+ const_iterator cend() const {
+ return (container_.cend());
+ }
+ const_iterator end() const {
+ return (container_.end());
+ }
+ iterator end() {
+ return (container_.end());
+ }
+ /// @}
+
+ /// @brief returns if class x belongs to the defined classes
+ ///
+ /// @param x client class to be checked
+ /// @return true if x belongs to the classes
+ bool contains(const ClientClass& x) const;
+
+ /// @brief Clears containers.
+ void clear() {
+ container_.clear();
+ }
+
+ /// @brief Returns all class names as text
+ ///
+ /// @param separator Separator to be used between class names. The
+ /// default separator comprises comma sign followed by space
+ /// character.
+ ///
+ /// @return the string representation of all classes
+ std::string toText(const std::string& separator = ", ") const;
+
+ /// @brief Returns all class names as an ElementPtr of type ListElement
+ ///
+ /// @return the list
+ isc::data::ElementPtr toElement() const;
+
+ private:
+ /// @brief container part
+ ClientClassContainer container_;
+ };
+}
+}
+
+#endif /* CLASSIFY_H */
diff --git a/src/lib/dhcp/dhcp4.h b/src/lib/dhcp/dhcp4.h
new file mode 100644
index 0000000..eb4b1d2
--- /dev/null
+++ b/src/lib/dhcp/dhcp4.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2004-2023 Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * 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/.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This software has been written for Internet Systems Consortium
+ * by Ted Lemon in cooperation with Vixie Enterprises. To learn more
+ * about Internet Systems Consortium, see ``https://www.isc.org''.
+ * To learn more about Vixie Enterprises, see ``http://www.vix.com''.
+ */
+
+/*
+ * NOTE: This files is imported from ISC DHCP. It uses C notation.
+ * Format kept for easier merge.
+ */
+
+#ifndef DHCP_H
+#define DHCP_H
+
+#include <stdint.h>
+
+/// @note Code points in comments are those assigned by IANA
+/// but not yet implemented in Kea.
+/// To implement a standard option, remove the comment characters,
+/// add an entry in std_option_defs.h, add a stdOptionDefs4 unit test
+/// in tests/libdhcp++_unittest.cc and update dhcp4-std-options-list-part2
+/// in the dhcp4-srv.xml source file of the user guide.
+
+namespace isc {
+namespace dhcp {
+
+/* IPv4 Broadcast address */
+#define DHCP_IPV4_BROADCAST_ADDRESS "255.255.255.255"
+
+/* BOOTP (rfc951) message types */
+enum BOOTPTypes {
+ BOOTREQUEST = 1,
+ BOOTREPLY = 2
+};
+
+/* Possible values for flags field... */
+static const uint16_t BOOTP_BROADCAST = 32768L;
+
+/// @brief Possible values for hardware type (htype) field.
+enum HType {
+ HTYPE_UNDEFINED = 0, ///< not specified or undefined
+ HTYPE_ETHER = 1, ///< Ethernet 10Mbps
+ HTYPE_DOCSIS = 1, ///< The traffic captures we have from cable modems as
+ /// well as this list by IANA:
+ /// http://www.iana.org/assignments/
+ /// arp-parameters/arp-parameters.xhtml suggest that
+ /// Ethernet (1) should be used in DOCSIS environment.
+ HTYPE_IEEE802 = 6, ///< IEEE 802.2 Token Ring
+ HTYPE_FDDI = 8 ///< FDDI
+ /// TODO Add infiniband here
+};
+
+/* DHCP Option codes: */
+enum DHCPOptionType {
+ DHO_PAD = 0, /* RFC2132 */
+ DHO_SUBNET_MASK = 1, /* RFC2132 */
+ DHO_TIME_OFFSET = 2, /* RFC2132 */
+ DHO_ROUTERS = 3, /* RFC2132 */
+ DHO_TIME_SERVERS = 4, /* RFC2132 */
+ DHO_NAME_SERVERS = 5, /* RFC2132 */
+ DHO_DOMAIN_NAME_SERVERS = 6, /* RFC2132 */
+ DHO_LOG_SERVERS = 7, /* RFC2132 */
+ DHO_COOKIE_SERVERS = 8, /* RFC2132 */
+ DHO_LPR_SERVERS = 9, /* RFC2132 */
+ DHO_IMPRESS_SERVERS = 10, /* RFC2132 */
+ DHO_RESOURCE_LOCATION_SERVERS = 11, /* RFC2132 */
+ DHO_HOST_NAME = 12, /* RFC2132 */
+ DHO_BOOT_SIZE = 13, /* RFC2132 */
+ DHO_MERIT_DUMP = 14, /* RFC2132 */
+ DHO_DOMAIN_NAME = 15, /* RFC2132 */
+ DHO_SWAP_SERVER = 16, /* RFC2132 */
+ DHO_ROOT_PATH = 17, /* RFC2132 */
+ DHO_EXTENSIONS_PATH = 18, /* RFC2132 */
+ DHO_IP_FORWARDING = 19, /* RFC2132 */
+ DHO_NON_LOCAL_SOURCE_ROUTING = 20, /* RFC2132 */
+ DHO_POLICY_FILTER = 21, /* RFC2132 */
+ DHO_MAX_DGRAM_REASSEMBLY = 22, /* RFC2132 */
+ DHO_DEFAULT_IP_TTL = 23, /* RFC2132 */
+ DHO_PATH_MTU_AGING_TIMEOUT = 24, /* RFC2132 */
+ DHO_PATH_MTU_PLATEAU_TABLE = 25, /* RFC2132 */
+ DHO_INTERFACE_MTU = 26, /* RFC2132 */
+ DHO_ALL_SUBNETS_LOCAL = 27, /* RFC2132 */
+ DHO_BROADCAST_ADDRESS = 28, /* RFC2132 */
+ DHO_PERFORM_MASK_DISCOVERY = 29, /* RFC2132 */
+ DHO_MASK_SUPPLIER = 30, /* RFC2132 */
+ DHO_ROUTER_DISCOVERY = 31, /* RFC2132 */
+ DHO_ROUTER_SOLICITATION_ADDRESS = 32, /* RFC2132 */
+ DHO_STATIC_ROUTES = 33, /* RFC2132 */
+ DHO_TRAILER_ENCAPSULATION = 34, /* RFC2132 */
+ DHO_ARP_CACHE_TIMEOUT = 35, /* RFC2132 */
+ DHO_IEEE802_3_ENCAPSULATION = 36, /* RFC2132 */
+ DHO_DEFAULT_TCP_TTL = 37, /* RFC2132 */
+ DHO_TCP_KEEPALIVE_INTERVAL = 38, /* RFC2132 */
+ DHO_TCP_KEEPALIVE_GARBAGE = 39, /* RFC2132 */
+ DHO_NIS_DOMAIN = 40, /* RFC2132 */
+ DHO_NIS_SERVERS = 41, /* RFC2132 */
+ DHO_NTP_SERVERS = 42, /* RFC2132 */
+ DHO_VENDOR_ENCAPSULATED_OPTIONS = 43, /* RFC2132 */
+ DHO_NETBIOS_NAME_SERVERS = 44, /* RFC2132 */
+ DHO_NETBIOS_DD_SERVER = 45, /* RFC2132 */
+ DHO_NETBIOS_NODE_TYPE = 46, /* RFC2132 */
+ DHO_NETBIOS_SCOPE = 47, /* RFC2132 */
+ DHO_FONT_SERVERS = 48, /* RFC2132 */
+ DHO_X_DISPLAY_MANAGER = 49, /* RFC2132 */
+ DHO_DHCP_REQUESTED_ADDRESS = 50, /* RFC2132 */
+ DHO_DHCP_LEASE_TIME = 51, /* RFC2132 */
+ DHO_DHCP_OPTION_OVERLOAD = 52, /* RFC2132 */
+ DHO_DHCP_MESSAGE_TYPE = 53, /* RFC2132 */
+ DHO_DHCP_SERVER_IDENTIFIER = 54, /* RFC2132 */
+ DHO_DHCP_PARAMETER_REQUEST_LIST = 55, /* RFC2132 */
+ DHO_DHCP_MESSAGE = 56, /* RFC2132 */
+ DHO_DHCP_MAX_MESSAGE_SIZE = 57, /* RFC2132 */
+ DHO_DHCP_RENEWAL_TIME = 58, /* RFC2132 */
+ DHO_DHCP_REBINDING_TIME = 59, /* RFC2132 */
+ DHO_VENDOR_CLASS_IDENTIFIER = 60, /* RFC2132 */
+ DHO_DHCP_CLIENT_IDENTIFIER = 61, /* RFC2132 */
+ DHO_NWIP_DOMAIN_NAME = 62, /* RFC2242 */
+ DHO_NWIP_SUBOPTIONS = 63, /* RFC2242 */
+ DHO_NISP_DOMAIN_NAME = 64, /* RFC2132 */
+ DHO_NISP_SERVER_ADDR = 65, /* RFC2132 */
+ DHO_TFTP_SERVER_NAME = 66, /* RFC2132 */
+ DHO_BOOT_FILE_NAME = 67, /* RFC2132 */
+ DHO_HOME_AGENT_ADDRS = 68, /* RFC2132 */
+ DHO_SMTP_SERVER = 69, /* RFC2132 */
+ DHO_POP3_SERVER = 70, /* RFC2132 */
+ DHO_NNTP_SERVER = 71, /* RFC2132 */
+ DHO_WWW_SERVER = 72, /* RFC2132 */
+ DHO_FINGER_SERVER = 73, /* RFC2132 */
+ DHO_IRC_SERVER = 74, /* RFC2132 */
+ DHO_STREETTALK_SERVER = 75, /* RFC2132 */
+ DHO_STDASERVER = 76, /* RFC2132 */
+ DHO_USER_CLASS = 77, /* RFC3004 */
+ DHO_DIRECTORY_AGENT = 78, /* RFC2610 */
+ DHO_SERVICE_SCOPE = 79, /* RFC2610 */
+// DHO_RAPID_COMMIT = 80, /* RFC4039 */
+ DHO_FQDN = 81, /* RFC4702 */
+ DHO_DHCP_AGENT_OPTIONS = 82, /* RFC3046 */
+// DHO_ISNS = 83, /* RFC4174 */
+ // 84 is removed/unassigned
+ DHO_NDS_SERVERS = 85, /* RFC2241 */
+ DHO_NDS_TREE_NAME = 86, /* RFC2241 */
+ DHO_NDS_CONTEXT = 87, /* RFC2241 */
+ DHO_BCMCS_DOMAIN_NAME_LIST = 88, /* RFC4280 */
+ DHO_BCMCS_IPV4_ADDR = 89, /* RFC4280 */
+ DHO_AUTHENTICATE = 90, /* RFC3118 */
+ DHO_CLIENT_LAST_TRANSACTION_TIME = 91, /* RFC4388 */
+ DHO_ASSOCIATED_IP = 92, /* RFC4388 */
+ DHO_SYSTEM = 93, /* RFC4578 */
+ DHO_NDI = 94, /* RFC4578 */
+// DHO_LDAP = 95, /* RFC3679 */
+ // 96 is removed/unassigned
+ DHO_UUID_GUID = 97, /* RFC4578 */
+ DHO_USER_AUTH = 98, /* RFC2485 */
+ DHO_GEOCONF_CIVIC = 99, /* RFC4776 */
+ DHO_PCODE = 100, /* RFC4833 */
+ DHO_TCODE = 101, /* RFC4833 */
+ // 102-107 are removed/unassigned
+ DHO_V6_ONLY_PREFERRED = 108, /* RFC8925 */
+ // 109-111 are removed/unassigned
+ DHO_NETINFO_ADDR = 112, /* RFC3679 */
+ DHO_NETINFO_TAG = 113, /* RFC3679 */
+ // URL option was replaced with captive portal.
+ // DHO_URL = 114, /* RFC3679 */
+ DHO_V4_CAPTIVE_PORTAL = 114, /* RFC8910 */
+
+ // 115 is removed/unassigned
+ DHO_AUTO_CONFIG = 116, /* RFC2563 */
+ DHO_NAME_SERVICE_SEARCH = 117, /* RFC2937 */
+ DHO_SUBNET_SELECTION = 118, /* RFC3011 */
+ DHO_DOMAIN_SEARCH = 119, /* RFC3397 */
+// DHO_SIP_SERVERS = 120, /* RFC3361 */
+// DHO_CLASSLESS_STATIC_ROUTE = 121, /* RFC3442 */
+// DHO_CCC = 122, /* RFC3495 */
+// DHO_GEOCONF = 123, /* RFC6225 */
+ DHO_VIVCO_SUBOPTIONS = 124, /* RFC3925 */
+ DHO_VIVSO_SUBOPTIONS = 125, /* RFC3925 */
+ // 126-127 are removed/unassigned
+ // 128-135 have multiple definitions including PXE
+ DHO_PANA_AGENT = 136, /* RFC5192 */
+ DHO_V4_LOST = 137, /* RFC5223 */
+ DHO_CAPWAP_AC_V4 = 138, /* RFC5417 */
+// DHO_IPV4_ADDR_MOS = 139, /* RFC5678 */
+// DHO_IPV4_FQDN_MOS = 140, /* RFC5678 */
+ DHO_SIP_UA_CONF_SERVICE_DOMAINS = 141, /* RFC6011 */
+// DHO_IPV4_ADDR_ANDSF = 142, /* RFC6153 */
+ DHO_V4_SZTP_REDIRECT = 143, /* RFC8572 */
+// DHO_GEOLOC = 144, /* RFC6225 */
+// DHO_FORCERENEW_NONCE_CAPABLE = 145, /* RFC6704 */
+ DHO_RDNSS_SELECT = 146, /* RFC6731 */
+ // 147-149 are removed/unassigned
+ // 150 have multiple definitions
+ DHO_STATUS_CODE = 151, /* RFC6926 */
+ DHO_BASE_TIME = 152, /* RFC6926 */
+ DHO_START_TIME_OF_STATE = 153, /* RFC6926 */
+ DHO_QUERY_START_TIME = 154, /* RFC6926 */
+ DHO_QUERY_END_TIME = 155, /* RFC6926 */
+ DHO_DHCP_STATE = 156, /* RFC6926 */
+ DHO_DATA_SOURCE = 157, /* RFC6926 */
+// DHO_V4_PCP_SERVER = 158, /* RFC7291 */
+ DHO_V4_PORTPARAMS = 159, /* RFC7618 */
+ // 160 used to be assigned in RFC7710, but was removed in RFC8910
+ // The Captive Portal option now uses code 114.
+// DHO_MUD_URL_V4 = 161, /* RFC8520 */
+ DHO_V4_DNR = 162, /* RFC-ietf-add-dnr */
+ // 163-209 are removed/unassigned
+// DHO_PATH_PREFIX = 210, /* RFC5071 */
+// DHO_REBOOT_TIME = 211, /* RFC5071 */
+ DHO_6RD = 212, /* RFC5969 */
+ DHO_V4_ACCESS_DOMAIN = 213, /* RFC5986 */
+ // 214-219 are removed/unassigned
+// DHO_SUBNET_ALLOC = 220, /* RFC6656 */
+// DHO_VSS = 221, /* RFC6607 */
+ // 222-223 are removed/unassigned
+ // 224-254 are reserved for private use
+
+ DHO_END = 255 /* RFC2132 */
+};
+
+/* DHCP message types. */
+enum DHCPMessageType {
+ DHCP_NOTYPE = 0, ///< Message Type option missing
+ DHCPDISCOVER = 1,
+ DHCPOFFER = 2,
+ DHCPREQUEST = 3,
+ DHCPDECLINE = 4,
+ DHCPACK = 5,
+ DHCPNAK = 6,
+ DHCPRELEASE = 7,
+ DHCPINFORM = 8,
+// DHCPFORCERENEW = 9,
+ DHCPLEASEQUERY = 10,
+ DHCPLEASEUNASSIGNED = 11,
+ DHCPLEASEUNKNOWN = 12,
+ DHCPLEASEACTIVE = 13,
+ DHCPBULKLEASEQUERY = 14,
+ DHCPLEASEQUERYDONE = 15,
+// DHCPACTIVELEASEQUERY = 16,
+ DHCPLEASEQUERYSTATUS = 17,
+ DHCPTLS = 18,
+ DHCP_TYPES_EOF
+};
+
+static const uint16_t DHCP4_CLIENT_PORT = 68;
+static const uint16_t DHCP4_SERVER_PORT = 67;
+
+/// Magic cookie validating dhcp options field (and bootp vendor
+/// extensions field).
+static const uint32_t DHCP_OPTIONS_COOKIE = 0x63825363;
+
+/// Relay Agent Information suboption types.
+enum RAISubOptionType {
+ RAI_OPTION_AGENT_CIRCUIT_ID = 1, // RFC3046
+ RAI_OPTION_REMOTE_ID = 2, // RFC3046
+ /* option 3 is reserved and will never be assigned */
+ RAI_OPTION_DOCSIS_DEVICE_CLASS = 4, // RFC3256
+ RAI_OPTION_LINK_SELECTION = 5, // RFC3527
+ RAI_OPTION_SUBSCRIBER_ID = 6, // RFC3993
+ RAI_OPTION_RADIUS = 7, // RFC4014
+ RAI_OPTION_AUTH = 8, // RFC4030
+ RAI_OPTION_VSI = 9, // RFC4243
+ RAI_OPTION_RELAY_FLAGS = 10, // RFC5010
+ RAI_OPTION_SERVER_ID_OVERRIDE = 11, // RFC5107
+ RAI_OPTION_RELAY_ID = 12, // RFC6925
+ RAI_OPTION_ACCESS_TECHNO_TYPE = 13, // RFC7839
+ RAI_OPTION_ACCESS_NETWORK_NAME = 14, // RFC7839
+ RAI_OPTION_ACCESS_POINT_NAME = 15, // RFC7839
+ RAI_OPTION_ACCESS_POINT_BSSID = 16, // RFC7839
+ RAI_OPTION_OPERATOR_ID = 17, // RFC7839
+ RAI_OPTION_OPERATOR_REALM = 18, // RFC7839
+ RAI_OPTION_RELAY_PORT = 19, // RFC8357
+ RAI_OPTION_VIRTUAL_SUBNET_SELECT = 151, // RFC6607
+ RAI_OPTION_VIRTUAL_SUBNET_SELECT_CTRL = 152 // RFC6607
+};
+
+// TODO: Following are leftovers from dhcp.h import from ISC DHCP
+// They will be converted to C++-style defines once they will start
+// to be used.
+#if 0
+/* FQDN suboptions: */
+#define FQDN_NO_CLIENT_UPDATE 1
+#define FQDN_SERVER_UPDATE 2
+#define FQDN_ENCODED 3
+#define FQDN_RCODE1 4
+#define FQDN_RCODE2 5
+#define FQDN_HOSTNAME 6
+#define FQDN_DOMAINNAME 7
+#define FQDN_FQDN 8
+#define FQDN_SUBOPTION_COUNT 8
+
+/* Enterprise Suboptions: */
+#define VENDOR_ISC_SUBOPTIONS 2495
+
+#endif
+
+/* Client identifier types */
+static const uint8_t CLIENT_ID_OPTION_TYPE_DUID = 255;
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif /* DHCP_H */
diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h
new file mode 100644
index 0000000..bb89c20
--- /dev/null
+++ b/src/lib/dhcp/dhcp6.h
@@ -0,0 +1,342 @@
+// Copyright (C) 2006-2023 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 DHCP6_H
+#define DHCP6_H
+
+#include <stdint.h>
+
+/// @note Code points in comments are those assigned by IANA
+/// but not yet implemented in Kea.
+/// To implement a standard option, remove the comment characters,
+/// add an entry in std_option_defs.h, add a stdOptionDefs6 unit test
+/// in tests/libdhcp++_unittest.cc and update dhcp6-std-options-list in
+/// the dhcp6-srv.xml source file of the user guide.
+
+/* DHCPv6 Option codes: */
+enum DHCPv6OptionType {
+ D6O_CLIENTID = 1, /* RFC8415 */
+ D6O_SERVERID = 2, /* RFC8415 */
+ D6O_IA_NA = 3, /* RFC8415 */
+ D6O_IA_TA = 4, /* RFC8415 */
+ D6O_IAADDR = 5, /* RFC8415 */
+ D6O_ORO = 6, /* RFC8415 */
+ D6O_PREFERENCE = 7, /* RFC8415 */
+ D6O_ELAPSED_TIME = 8, /* RFC8415 */
+ D6O_RELAY_MSG = 9, /* RFC8415 */
+ // Option code 10 is unassigned.
+ D6O_AUTH = 11, /* RFC8415 */
+ D6O_UNICAST = 12, /* RFC8415 */
+ D6O_STATUS_CODE = 13, /* RFC8415 */
+ D6O_RAPID_COMMIT = 14, /* RFC8415 */
+ D6O_USER_CLASS = 15, /* RFC8415 */
+ D6O_VENDOR_CLASS = 16, /* RFC8415 */
+ D6O_VENDOR_OPTS = 17, /* RFC8415 */
+ D6O_INTERFACE_ID = 18, /* RFC8415 */
+ D6O_RECONF_MSG = 19, /* RFC8415 */
+ D6O_RECONF_ACCEPT = 20, /* RFC8415 */
+ D6O_SIP_SERVERS_DNS = 21, /* RFC3319 */
+ D6O_SIP_SERVERS_ADDR = 22, /* RFC3319 */
+ D6O_NAME_SERVERS = 23, /* RFC3646 */
+ D6O_DOMAIN_SEARCH = 24, /* RFC3646 */
+ D6O_IA_PD = 25, /* RFC8415, RFC3633 */
+ D6O_IAPREFIX = 26, /* RFC8415, RFC3633 */
+ D6O_NIS_SERVERS = 27, /* RFC3898 */
+ D6O_NISP_SERVERS = 28, /* RFC3898 */
+ D6O_NIS_DOMAIN_NAME = 29, /* RFC3898 */
+ D6O_NISP_DOMAIN_NAME = 30, /* RFC3898 */
+ D6O_SNTP_SERVERS = 31, /* RFC4075 */
+ D6O_INFORMATION_REFRESH_TIME = 32, /* RFC8415, RFC4242 */
+ D6O_BCMCS_SERVER_D = 33, /* RFC4280 */
+ D6O_BCMCS_SERVER_A = 34, /* RFC4280 */
+ // Option code 35 is unassigned.
+ D6O_GEOCONF_CIVIC = 36, /* RFC4776 */
+ D6O_REMOTE_ID = 37, /* RFC4649 */
+ D6O_SUBSCRIBER_ID = 38, /* RFC4580 */
+ D6O_CLIENT_FQDN = 39, /* RFC4704 */
+ D6O_PANA_AGENT = 40, /* RFC5192 */
+ D6O_NEW_POSIX_TIMEZONE = 41, /* RFC4833 */
+ D6O_NEW_TZDB_TIMEZONE = 42, /* RFC4833 */
+ D6O_ERO = 43, /* RFC4994 */
+ D6O_LQ_QUERY = 44, /* RFC5007 */
+ D6O_CLIENT_DATA = 45, /* RFC5007 */
+ D6O_CLT_TIME = 46, /* RFC5007 */
+ D6O_LQ_RELAY_DATA = 47, /* RFC5007 */
+ D6O_LQ_CLIENT_LINK = 48, /* RFC5007 */
+// D6O_MIP6_HNIDF = 49, /* RFC6610 */
+// D6O_MIP6_VDINF = 50, /* RFC6610 */
+ D6O_V6_LOST = 51, /* RFC5223 */
+ D6O_CAPWAP_AC_V6 = 52, /* RFC5417 */
+ D6O_RELAY_ID = 53, /* RFC5460 */
+// D6O_IPV6_ADDRESS_MOS = 54, /* RFC5678 */
+// D6O_IPV6_FQDN_MOS = 55, /* RFC5678 */
+// D6O_NTP_SERVER = 56, /* RFC5908 */
+ D6O_V6_ACCESS_DOMAIN = 57, /* RFC5986 */
+ D6O_SIP_UA_CS_LIST = 58, /* RFC6011 */
+ D6O_BOOTFILE_URL = 59, /* RFC5970 */
+ D6O_BOOTFILE_PARAM = 60, /* RFC5970 */
+ D6O_CLIENT_ARCH_TYPE = 61, /* RFC5970 */
+ D6O_NII = 62, /* RFC5970 */
+// D6O_GEOLOCATION = 63, /* RFC6225 */
+ D6O_AFTR_NAME = 64, /* RFC6334 */
+ D6O_ERP_LOCAL_DOMAIN_NAME = 65, /* RFC6440 */
+ D6O_RSOO = 66, /* RFC6422 */
+ D6O_PD_EXCLUDE = 67, /* RFC6603 */
+// D6O_VSS = 68, /* RFC6607 */
+// D6O_MIP6_IDINF = 69, /* RFC6610 */
+// D6O_MIP6_UDINF = 70, /* RFC6610 */
+// D6O_MIP6_HNP = 71, /* RFC6610 */
+// D6O_MIP6_HAA = 72, /* RFC6610 */
+// D6O_MIP6_HAF = 73, /* RFC6610 */
+ D6O_RDNSS_SELECTION = 74, /* RFC6731 */
+// D6O_KRB_PRINCIPAL_NAME = 75, /* RFC6784 */
+// D6O_KRB_REALM_NAME = 76, /* RFC6784 */
+// D6O_KRB_DEFAULT_REALM_NAME = 77, /* RFC6784 */
+// D6O_KRB_KDC = 78, /* RFC6784 */
+ D6O_CLIENT_LINKLAYER_ADDR = 79, /* RFC6939 */
+ D6O_LINK_ADDRESS = 80, /* RFC6977 */
+// D6O_RADIUS = 81, /* RFC7037 */
+ D6O_SOL_MAX_RT = 82, /* RFC8415, RFC7083 */
+ D6O_INF_MAX_RT = 83, /* RFC8415, RFC7083 */
+// D6O_ADDRSEL = 84, /* RFC7078 */
+// D6O_ADDRSEL_TABLE = 85, /* RFC7078 */
+// D6O_V6_PCP_SERVER = 86, /* RFC7291 */
+ D6O_DHCPV4_MSG = 87, /* RFC7341 */
+ D6O_DHCPV4_O_DHCPV6_SERVER = 88, /* RFC7341 */
+ D6O_S46_RULE = 89, /* RFC7598 */
+ D6O_S46_BR = 90, /* RFC7598, RFC8539 */
+ D6O_S46_DMR = 91, /* RFC7598 */
+ D6O_S46_V4V6BIND = 92, /* RFC7598 */
+ D6O_S46_PORTPARAMS = 93, /* RFC7598 */
+ D6O_S46_CONT_MAPE = 94, /* RFC7598 */
+ D6O_S46_CONT_MAPT = 95, /* RFC7598 */
+ D6O_S46_CONT_LW = 96, /* RFC7598 */
+// D6O_4RD = 97, /* RFC7600 */
+// D6O_4RD_MAP_RULE = 98, /* RFC7600 */
+// D6O_4RD_NON_MAP_RULE = 99, /* RFC7600 */
+// D6O_LQ_BASE_TIME = 100, /* RFC7653 */
+// D6O_LQ_START_TIME = 101, /* RFC7653 */
+// D6O_LQ_END_TIME = 102, /* RFC7653 */
+ D6O_V6_CAPTIVE_PORTAL = 103, /* RFC8910 */
+// D6O_MPL_PARAMETERS = 104, /* RFC7774 */
+// D6O_ANI_ATT = 105, /* RFC7839 */
+// D6O_ANI_NETWORK_NAME = 106, /* RFC7839 */
+// D6O_ANI_AP_NAME = 107, /* RFC7839 */
+// D6O_ANI_AP_BSSID = 108, /* RFC7839 */
+// D6O_ANI_OPERATOR_ID = 109, /* RFC7839 */
+// D6O_ANI_OPERATOR_REALM = 110, /* RFC7839 */
+// D6O_S46_PRIORITY = 111, /* RFC8026 */
+ // Option code 112 is unassigned.
+// D6O_V6_PREFIX64 = 113, /* RFC8115 */
+// D6O_F_BINDING_STATUS = 114, /* RFC8156 */
+// D6O_F_CONNECT_FLAGS = 115, /* RFC8156 */
+// D6O_F_DNS_REMOVAL_INFO = 116, /* RFC8156 */
+// D6O_F_DNS_HOST_NAME = 117, /* RFC8156 */
+// D6O_F_DNS_ZONE_NAME = 118, /* RFC8156 */
+// D6O_F_DNS_FLAGS = 119, /* RFC8156 */
+// D6O_F_EXPIRATION_TIME = 120, /* RFC8156 */
+// D6O_F_MAX_UNACKED_BNDUPD = 121, /* RFC8156 */
+// D6O_F_MCLT = 122, /* RFC8156 */
+// D6O_F_PARTNER_LIFETIME = 123, /* RFC8156 */
+// D6O_F_PARTNER_LIFETIME_SENT = 124, /* RFC8156 */
+// D6O_F_PARTNER_DOWN_TIME = 125, /* RFC8156 */
+// D6O_F_PARTNER_RAW_CLT_TIME = 126, /* RFC8156 */
+// D6O_F_PROTOCOL_VERSION = 127, /* RFC8156 */
+// D6O_F_KEEPALIVE_TIME = 128, /* RFC8156 */
+// D6O_F_RECONFIGURE_DATA = 129, /* RFC8156 */
+// D6O_F_RELATIONSHIP_NAME = 130, /* RFC8156 */
+// D6O_F_SERVER_FLAGS = 131, /* RFC8156 */
+// D6O_F_SERVER_STATE = 132, /* RFC8156 */
+// D6O_F_START_TIME_OF_STATE = 133, /* RFC8156 */
+// D6O_F_STATE_EXPIRATION_TIME = 134, /* RFC8156 */
+ D6O_RELAY_SOURCE_PORT = 135, /* RFC8357 */
+ D60_V6_SZTP_REDIRECT = 136, /* RFC8572 */
+ // Option codes 137-142 are unassigned.
+ D6O_IPV6_ADDRESS_ANDSF = 143, /* RFC6153 */
+ D6O_V6_DNR = 144 /* RFC-ietf-add-dnr */
+};
+
+/*
+ * Status Codes, from RFC 8415 section 21.13, 5007, 5460.
+ */
+enum DHCPv6StatusCode {
+ STATUS_Success = 0,
+ STATUS_UnspecFail = 1,
+ STATUS_NoAddrsAvail = 2,
+ STATUS_NoBinding = 3,
+ STATUS_NotOnLink = 4,
+ STATUS_UseMulticast = 5,
+ STATUS_NoPrefixAvail = 6,
+ STATUS_UnknownQueryType = 7,
+ STATUS_MalformedQuery = 8,
+ STATUS_NotConfigured = 9,
+ STATUS_NotAllowed = 10,
+ STATUS_QueryTerminated = 11,
+/* RFC7653 */
+// STATUS_DataMissing = 12,
+// STATUS_CatchUpComplete = 13,
+// STATUS_NotSupported = 14,
+// STATUS_TLSConnectionRefused = 15,
+/* RFC8156 */
+// STATUS_AddressInUse = 16,
+// STATUS_ConfigurationConflict = 17,
+// STATUS_MissingBindingInformation = 18,
+// STATUS_OutdatedBindingInformation = 19,
+// STATUS_ServerShuttingDown = 20,
+// STATUS_DNSUpdateNotSupported = 21,
+// STATUS_ExcessiveTimeSkew = 22
+};
+
+/*
+ * DHCPv6 message types, defined in section 7.3 of RFC 8415
+ */
+enum DHCPv6MessageType {
+ DHCPV6_NOTYPE = 0,
+ DHCPV6_SOLICIT = 1,
+ DHCPV6_ADVERTISE = 2,
+ DHCPV6_REQUEST = 3,
+ DHCPV6_CONFIRM = 4,
+ DHCPV6_RENEW = 5,
+ DHCPV6_REBIND = 6,
+ DHCPV6_REPLY = 7,
+ DHCPV6_RELEASE = 8,
+ DHCPV6_DECLINE = 9,
+ DHCPV6_RECONFIGURE = 10,
+ DHCPV6_INFORMATION_REQUEST = 11,
+ DHCPV6_RELAY_FORW = 12,
+ DHCPV6_RELAY_REPL = 13,
+ /* RFC 5007 */
+ DHCPV6_LEASEQUERY = 14,
+ DHCPV6_LEASEQUERY_REPLY = 15,
+ /* RFC 5460 */
+ DHCPV6_LEASEQUERY_DONE = 16,
+ DHCPV6_LEASEQUERY_DATA = 17,
+ /* RFC 6977 */
+ DHCPV6_RECONFIGURE_REQUEST = 18,
+ DHCPV6_RECONFIGURE_REPLY = 19,
+ /* RFC 7341 */
+ DHCPV6_DHCPV4_QUERY = 20,
+ DHCPV6_DHCPV4_RESPONSE = 21,
+ /* RFC 7653 */
+ DHCPV6_ACTIVELEASEQUERY = 22,
+ DHCPV6_STARTTLS = 23,
+ /* RFC 8156 */
+ DHCPV6_BNDUPD = 24,
+ DHCPV6_BNDREPLY = 25,
+ DHCPV6_POOLREQ = 26,
+ DHCPV6_POOLRESP = 27,
+ DHCPV6_UPDREQ = 28,
+ DHCPV6_UPDREQALL = 29,
+ DHCPV6_UPDDONE = 30,
+ DHCPV6_CONNECT = 31,
+ DHCPV6_CONNECTREPLY = 32,
+ DHCPV6_DISCONNECT = 33,
+ DHCPV6_STATE = 34,
+ DHCPV6_CONTACT = 35,
+ DHCPV6_TYPES_EOF
+};
+
+extern const char *dhcpv6_type_names[];
+extern const int dhcpv6_type_name_max;
+
+// DUID type definitions (RFC 8415 section 11).
+// see isc::dhcp::DUID::DUIDType enum in dhcp/duid.h
+
+// Define hardware types
+// Taken from http://www.iana.org/assignments/arp-parameters/
+static const uint16_t HWTYPE_ETHERNET = 0x0001;
+static const uint16_t HWTYPE_INFINIBAND = 0x0020;
+
+// Taken from https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers
+static const uint32_t ENTERPRISE_ID_ISC = 2495;
+
+/* DHCPv4-over-DHCPv6 (RFC 7341) inter-process communication. These are option
+ codes for the ISC vendor specific options used in 4o6 */
+static const uint16_t ISC_V6_4O6_INTERFACE = 60000;
+static const uint16_t ISC_V6_4O6_SRC_ADDRESS = 60001;
+static const uint16_t ISC_V6_4O6_SRC_PORT = 60002;
+
+/* Offsets into IA_*'s where Option spaces commence. */
+static const uint16_t IA_NA_OFFSET = 12; /* IAID, T1, T2, all 4 octets each */
+static const uint16_t IA_TA_OFFSET = 4; /* IAID only, 4 octets */
+static const uint16_t IA_PD_OFFSET = 12; /* IAID, T1, T2, all 4 octets each */
+
+/* Offset into IAADDR's where Option spaces commence. */
+static const uint16_t IAADDR_OFFSET = 24;
+
+/* Offset into IAPREFIX's where Option spaces commence. */
+static const uint16_t IAPREFIX_OFFSET = 25;
+
+/* Offset into LQ_QUERY's where Option spaces commence. */
+static const uint16_t LQ_QUERY_OFFSET = 17;
+
+/*
+ * DHCPv6 well-known multicast addresses, from section 7.1 of RFC 8415
+ */
+// TODO
+#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS "ff02::1:2"
+#define ALL_DHCP_SERVERS "ff05::1:3"
+
+static const uint16_t DHCP6_CLIENT_PORT = 546;
+static const uint16_t DHCP6_SERVER_PORT = 547;
+
+/*
+ * DHCPv6 Retransmission Constants (RFC 8415 section 7.6, RFC 5007)
+ */
+
+// TODO
+#define SOL_MAX_DELAY 1
+#define SOL_TIMEOUT 1
+#define SOL_MAX_RT 120
+#define REQ_TIMEOUT 1
+#define REQ_MAX_RT 30
+#define REQ_MAX_RC 10
+#define CNF_MAX_DELAY 1
+#define CNF_TIMEOUT 1
+#define CNF_MAX_RT 4
+#define CNF_MAX_RD 10
+#define REN_TIMEOUT 10
+#define REN_MAX_RT 600
+#define REB_TIMEOUT 10
+#define REB_MAX_RT 600
+#define INF_MAX_DELAY 1
+#define INF_TIMEOUT 1
+#define INF_MAX_RT 120
+#define REL_TIMEOUT 1
+#define REL_MAX_RC 5
+#define DEC_TIMEOUT 1
+#define DEC_MAX_RC 5
+#define REC_TIMEOUT 2
+#define REC_MAX_RC 8
+#define HOP_COUNT_LIMIT 32
+#define LQ6_TIMEOUT 1
+#define LQ6_MAX_RT 10
+#define LQ6_MAX_RC 5
+
+/* Leasequery query-types (RFC 5007, RFC 5460) */
+
+#define LQ6QT_BY_ADDRESS 1
+#define LQ6QT_BY_CLIENTID 2
+#define LQ6QT_BY_RELAY_ID 3
+#define LQ6QT_BY_LINK_ADDRESS 4
+#define LQ6QT_BY_REMOTE_ID 5
+
+/*
+ * DUID time starts 2000-01-01.
+ * This constant is the number of seconds since 1970-01-01,
+ * when the Unix epoch began.
+ */
+#define DUID_TIME_EPOCH 946684800
+
+/* Information-Request Time option (RFC 8415) */
+
+#define IRT_DEFAULT 86400
+#define IRT_MINIMUM 600
+
+/* DHCPv4-query message flags (see RFC7341) */
+#define DHCPV4_QUERY_FLAGS_UNICAST (1 << 23)
+
+#endif /* DHCP6_H */
diff --git a/src/lib/dhcp/docsis3_option_defs.h b/src/lib/dhcp/docsis3_option_defs.h
new file mode 100644
index 0000000..3b02fec
--- /dev/null
+++ b/src/lib/dhcp/docsis3_option_defs.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2013-2021 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 DOCSIS3_OPTION_DEFS_H
+#define DOCSIS3_OPTION_DEFS_H
+
+#include <dhcp/std_option_defs.h>
+#include <dhcp/option_data_types.h>
+
+/// @brief global docsis3 option spaces
+#define DOCSIS3_V4_OPTION_SPACE "docsis3-v4"
+#define DOCSIS3_V6_OPTION_SPACE "docsis3-v6"
+
+namespace isc {
+namespace dhcp {
+
+#define VENDOR_ID_CABLE_LABS 4491
+
+#define DOCSIS3_V4_ORO 1
+#define DOCSIS3_V4_TFTP_SERVERS 2
+
+/// @brief Definitions of standard DHCPv4 options.
+const OptionDefParams DOCSIS3_V4_OPTION_DEFINITIONS[] = {
+ { "oro", DOCSIS3_V4_ORO,
+ DOCSIS3_V4_OPTION_SPACE, OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" },
+ { "tftp-servers", DOCSIS3_V4_TFTP_SERVERS,
+ DOCSIS3_V4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }
+};
+
+/// Number of option definitions defined.
+const int DOCSIS3_V4_OPTION_DEFINITIONS_SIZE =
+ sizeof(DOCSIS3_V4_OPTION_DEFINITIONS) /
+ sizeof(DOCSIS3_V4_OPTION_DEFINITIONS[0]);
+
+/// @todo define remaining docsis3 v6 codes
+#define DOCSIS3_V6_ORO 1
+#define DOCSIS3_V6_DEVICE_TYPE 2
+#define DOCSIS3_V6_VENDOR_NAME 10
+#define DOCSIS3_V6_TFTP_SERVERS 32
+#define DOCSIS3_V6_CONFIG_FILE 33
+#define DOCSIS3_V6_SYSLOG_SERVERS 34
+#define DOCSIS3_V6_DEVICE_ID 36
+#define DOCSIS3_V6_TIME_SERVERS 37
+#define DOCSIS3_V6_TIME_OFFSET 38
+
+// The following DOCSIS3 options are inserted by the CMTS (which acts as
+// a relay agent)
+#define DOCSIS3_V6_CMTS_CM_MAC 1026
+
+/// @brief Definitions of standard DHCPv6 options.
+const OptionDefParams DOCSIS3_V6_OPTION_DEFINITIONS[] = {
+ { "oro", DOCSIS3_V6_ORO,
+ DOCSIS3_V6_OPTION_SPACE, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+ { "device-type", DOCSIS3_V6_DEVICE_TYPE,
+ DOCSIS3_V6_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "vendor-type", DOCSIS3_V6_VENDOR_NAME,
+ DOCSIS3_V6_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "tftp-servers", DOCSIS3_V6_TFTP_SERVERS,
+ DOCSIS3_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "time-servers", DOCSIS3_V6_TIME_SERVERS,
+ DOCSIS3_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "config-file", DOCSIS3_V6_CONFIG_FILE,
+ DOCSIS3_V6_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "syslog-servers", DOCSIS3_V6_SYSLOG_SERVERS,
+ DOCSIS3_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "device-id", DOCSIS3_V6_DEVICE_ID,
+ DOCSIS3_V6_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "time-offset", DOCSIS3_V6_TIME_OFFSET,
+ DOCSIS3_V6_OPTION_SPACE, OPT_INT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "cmts-cm-mac", DOCSIS3_V6_CMTS_CM_MAC,
+ DOCSIS3_V6_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" }
+ // @todo add definitions for all remaining options.
+};
+
+/// Number of option definitions defined.
+const int DOCSIS3_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(DOCSIS3_V6_OPTION_DEFINITIONS) /
+ sizeof(DOCSIS3_V6_OPTION_DEFINITIONS[0]);
+
+/// The class as specified in vendor-class option by the devices
+extern const char* DOCSIS3_CLASS_EROUTER;
+extern const char* DOCSIS3_CLASS_MODEM;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DOCSIS3_OPTION_DEFS_H
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
new file mode 100644
index 0000000..2338118
--- /dev/null
+++ b/src/lib/dhcp/duid.cc
@@ -0,0 +1,79 @@
+// Copyright (C) 2012-2023 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 <dhcp/duid.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+#include <iomanip>
+#include <cctype>
+#include <sstream>
+#include <vector>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+IdentifierBaseType::~IdentifierBaseType() {
+}
+
+constexpr size_t DUID::MIN_DUID_LEN;
+constexpr size_t DUID::MAX_DUID_LEN;
+
+DUID::DUID(const std::vector<uint8_t>& data) : IdentifierType<3, 130>(data) {
+}
+
+DUID::DUID(const uint8_t* data, size_t len) : IdentifierType<3, 130>(data, len) {
+}
+
+const std::vector<uint8_t>& DUID::getDuid() const {
+ return (data_);
+}
+
+DUID::DUIDType DUID::getType() const {
+ if (data_.size() < 2) {
+ return (DUID_UNKNOWN);
+ }
+ uint16_t type = (data_[0] << 8) + data_[1];
+ if (type < DUID_MAX) {
+ return (static_cast<DUID::DUIDType>(type));
+ } else {
+ return (DUID_UNKNOWN);
+ }
+}
+
+DUID
+DUID::fromText(const std::string& text) {
+ return (DUID(IdentifierType::fromText(text)));
+}
+
+const DUID&
+DUID::EMPTY() {
+ static DUID empty({0, 0, 0});
+ return (empty);
+}
+
+constexpr size_t ClientId::MIN_CLIENT_ID_LEN;
+constexpr size_t ClientId::MAX_CLIENT_ID_LEN;
+
+ClientId::ClientId(const std::vector<uint8_t>& data) : IdentifierType<2, 255>(data) {
+}
+
+ClientId::ClientId(const uint8_t *data, size_t len) : IdentifierType<2, 255>(data, len) {
+}
+
+const std::vector<uint8_t>& ClientId::getClientId() const {
+ return (data_);
+}
+
+ClientIdPtr ClientId::fromText(const std::string& text) {
+ return (ClientIdPtr(new ClientId(IdentifierType::fromText(text))));
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
new file mode 100644
index 0000000..6f72ccc
--- /dev/null
+++ b/src/lib/dhcp/duid.h
@@ -0,0 +1,270 @@
+// Copyright (C) 2012-2023 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 DUID_H
+#define DUID_H
+
+#include <asiolink/io_address.h>
+#include <util/strutil.h>
+#include <boost/shared_ptr.hpp>
+#include <vector>
+#include <stdint.h>
+#include <unistd.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base type used to define a common smart pointer for all derived types.
+class IdentifierBaseType {
+protected:
+ /// @brief Pure virtual destructor.
+ ///
+ /// This class can not be instantiated.
+ virtual ~IdentifierBaseType() = 0;
+};
+
+/// @brief Shared pointer to a IdentifierType
+typedef boost::shared_ptr<IdentifierBaseType> IdentifierBaseTypePtr;
+
+template<size_t min_size, size_t max_size>
+class IdentifierType : public IdentifierBaseType {
+public:
+
+ /// @brief Constructor from vector
+ ///
+ /// @param data The data used to create the IdentifierType
+ IdentifierType(const std::vector<uint8_t>& data) {
+ if (data.size() < min_size) {
+ isc_throw(isc::BadValue, "identifier is too short (" << data.size()
+ << "), at least "<< min_size << " is required");
+ }
+ if (data.size() > max_size) {
+ isc_throw(isc::BadValue, "identifier is too large (" << data.size()
+ << "), at most " << max_size << " is required");
+ }
+ data_ = data;
+ }
+
+ /// @brief Constructor from array and array size
+ ///
+ /// @param data The data used to create the Identifier
+ /// @param len The data len used to create the Identifier
+ IdentifierType(const uint8_t* data, size_t len) {
+ if (len < min_size) {
+ isc_throw(isc::BadValue, "identifier is too short (" << len
+ << "), at least "<< min_size << " is required");
+ }
+ if (len > max_size) {
+ isc_throw(isc::BadValue, "identifier is too large (" << len
+ << "), at most " << max_size << " is required");
+ }
+ data_ = std::vector<uint8_t>(data, data + len);
+ }
+
+ /// @brief Return the minimum size of the acceptable data.
+ ///
+ /// @return the minimum size of the acceptable data.
+ static constexpr size_t getMinSize() {
+ return (min_size);
+ }
+
+ /// @brief Return the maximum size of the acceptable data.
+ ///
+ /// @return the maximum size of the acceptable data.
+ static constexpr size_t getMaxSize() {
+ return (max_size);
+ }
+
+ /// @brief Returns textual representation of the identifier (e.g. 00:01:02:03:ff)
+ ///
+ /// @return textual representation of the identifier (e.g. 00:01:02:03:ff)
+ std::string toText() const {
+ std::stringstream tmp;
+ tmp << std::hex;
+ bool delim = false;
+ for (auto const data : data_) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(data);
+ delim = true;
+ }
+ return (tmp.str());
+ }
+
+ /// @brief This static function parses an Identifier specified in the
+ /// textual format.
+ ///
+ /// @param text Identifier in the hexadecimal format with digits
+ /// representing individual bytes separated by colons.
+ /// @return The data resulted from parsing the textual format.
+ static std::vector<uint8_t> fromText(const std::string& text) {
+ std::vector<uint8_t> binary;
+ util::str::decodeFormattedHexString(text, binary);
+ return (binary);
+ }
+
+ /// @brief Compares two identifiers for equality
+ ///
+ /// @return True if the two identifiers are equal, false otherwise.
+ bool operator==(const IdentifierType& other) const {
+ return (data_ == other.data_);
+ }
+
+ /// @brief Compares two identifiers for inequality
+ ///
+ /// @return True if the two identifiers are different, false otherwise.
+ bool operator!=(const IdentifierType& other) const {
+ return (data_ != other.data_);
+ }
+
+protected:
+
+ /// @brief The actual content of the Identifier
+ std::vector<uint8_t> data_;
+};
+
+/// @brief Shared pointer to a DUID
+class DUID;
+typedef boost::shared_ptr<DUID> DuidPtr;
+
+/// @brief Holds DUID (DHCPv6 Unique Identifier)
+///
+/// This class holds DUID, that is used in client-id, server-id and
+/// several other options. It is used to identify DHCPv6 entity.
+class DUID : public IdentifierType<3, 130> {
+public:
+
+ /// @brief minimum duid size
+ ///
+ /// The minimal DUID size specified in RFC 8415, section 11.1 is 3:
+ /// 2 fixed octets for the type + 1 minimum octet for the value.
+ static constexpr size_t MIN_DUID_LEN = IdentifierType::getMinSize();
+
+ /// @brief maximum duid size
+ ///
+ /// The maximum DUID size specified in RFC 8415, section 11.1 is 130:
+ /// 2 fixed octets for the type + 128 maximum octets for the value.
+ static constexpr size_t MAX_DUID_LEN = IdentifierType::getMaxSize();
+
+ /// @brief specifies DUID type
+ typedef enum {
+ DUID_UNKNOWN = 0, ///< invalid/unknown type
+ DUID_LLT = 1, ///< link-layer + time, see RFC3315, section 11.2
+ DUID_EN = 2, ///< enterprise-id, see RFC3315, section 11.3
+ DUID_LL = 3, ///< link-layer, see RFC3315, section 11.4
+ DUID_UUID = 4, ///< UUID, see RFC3315, section 11.5
+ DUID_MAX ///< not a real type, just maximum defined value + 1
+ } DUIDType;
+
+ /// @brief Constructor from vector
+ ///
+ /// @param data The data used to create the DUID
+ DUID(const std::vector<uint8_t>& data);
+
+ /// @brief Constructor from array and array size
+ ///
+ /// @param data The data used to create the DUID
+ /// @param len The data len used to create the DUID
+ DUID(const uint8_t* data, size_t len);
+
+ /// @brief Returns a const reference to the actual DUID value
+ ///
+ /// @warning Since this function returns a reference to the vector (not a
+ /// copy) the returned object must be used with caution because it remains
+ /// valid only for the time period when the object which returned it is
+ /// valid.
+ ///
+ /// @return A reference to a vector holding a DUID.
+ const std::vector<uint8_t>& getDuid() const;
+
+ /// @brief Defines the constant "empty" DUID
+ ///
+ /// In general, empty DUID is not allowed. The only case where it is really
+ /// valid is to designate declined IPv6 Leases. We have a broad assumption
+ /// that the Lease->duid_ must always be set. However, declined lease
+ /// doesn't have any DUID associated with it. Hence we need a way to
+ /// indicate that fact.
+ //
+ /// @return reference to the static constant empty DUID
+ static const DUID& EMPTY();
+
+ /// @brief Returns the DUID type
+ DUIDType getType() const;
+
+ /// @brief Create DUID from the textual format.
+ ///
+ /// This static function parses a DUID specified in the textual format.
+ ///
+ /// @param text DUID in the hexadecimal format with digits representing
+ /// individual bytes separated by colons.
+ ///
+ /// @throw isc::BadValue if parsing the DUID failed.
+ static DUID fromText(const std::string& text);
+};
+
+/// @brief Forward declaration to the @c ClientId class.
+class ClientId;
+/// @brief Shared pointer to a Client ID.
+typedef boost::shared_ptr<ClientId> ClientIdPtr;
+
+/// @brief Holds Client identifier or client IPv4 address
+///
+/// This class is intended to be a generic IPv4 client identifier. It can hold
+/// a client-id
+class ClientId : public IdentifierType<2, 255> {
+public:
+
+ /// @brief Minimum size of a client ID
+ ///
+ /// Excerpt from RFC2132, section 9.14.
+ /// The code for this option is 61, and its minimum length is 2.
+ static constexpr size_t MIN_CLIENT_ID_LEN = IdentifierType::getMinSize();
+
+ /// @brief Maximum size of a client ID
+ ///
+ /// @note RFC 2131 does not specify an upper length of a client ID, but the
+ /// byte used to specify the option size byte can only go up to 255.
+ static constexpr size_t MAX_CLIENT_ID_LEN = IdentifierType::getMaxSize();
+
+ /// @brief Constructor based on vector<uint8_t>
+ ///
+ /// @param data The data used to create the ClientId
+ ClientId(const std::vector<uint8_t>& data);
+
+ /// @brief Constructor based on array and array size
+ ///
+ /// @param data The data used to create the ClientId
+ /// @param len The data len used to create the ClientId
+ ClientId(const uint8_t* data, size_t len);
+
+ /// @brief Returns reference to the client-id data.
+ ///
+ /// @warning Since this function returns a reference to the vector (not a
+ /// copy) the returned object must be used with caution because it remains
+ /// valid only for the time period when the object which returned it is
+ /// valid.
+ ///
+ /// @return A reference to a vector holding a client identifier.
+ const std::vector<uint8_t>& getClientId() const;
+
+ /// @brief Create client identifier from the textual format.
+ ///
+ /// This static function creates the instance of the @c ClientId from the
+ /// textual format.
+ ///
+ /// @param text Client identifier in the textual format.
+ ///
+ /// @return Pointer to the instance of the @c ClientId.
+ /// @throw isc::BadValue if parsing the client identifier failed.
+ /// @throw isc::OutOfRange if the client identifier is truncated.
+ static ClientIdPtr fromText(const std::string& text);
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif /* DUID_H */
diff --git a/src/lib/dhcp/duid_factory.cc b/src/lib/dhcp/duid_factory.cc
new file mode 100644
index 0000000..1a02344
--- /dev/null
+++ b/src/lib/dhcp/duid_factory.cc
@@ -0,0 +1,409 @@
+// Copyright (C) 2015-2023 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 <dhcp/duid_factory.h>
+#include <dhcp/iface_mgr.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+#include <util/range_utilities.h>
+#include <util/strutil.h>
+#include <ctime>
+#include <fstream>
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+using namespace isc::util;
+using namespace isc::util::str;
+
+namespace {
+
+/// @brief Length of the DUID type field.
+const size_t DUID_TYPE_LEN = 2;
+
+/// @brief Minimal length of the MAC address.
+const size_t MIN_MAC_LEN = 6;
+
+/// @brief Length of the enterprise ID field.
+const size_t ENTERPRISE_ID_LEN = 4;
+
+/// @brief Default length of the variable length identifier in the DUID-EN.
+const size_t DUID_EN_IDENTIFIER_LEN = 6;
+
+}
+
+namespace isc {
+namespace dhcp {
+
+DUIDFactory::DUIDFactory(const std::string& storage_location)
+ : storage_location_(trim(storage_location)), duid_() {
+}
+
+bool
+DUIDFactory::isStored() const {
+ return (!storage_location_.empty());
+}
+
+void
+DUIDFactory::createLLT(const uint16_t htype, const uint32_t time_in,
+ const std::vector<uint8_t>& ll_identifier) {
+ // We'll need DUID stored in the file to compare it against the
+ // new configuration. If the new configuration indicates that some
+ // bits of the DUID should be generated we'll first try to use the
+ // values stored in the file to prevent DUID from changing if possible.
+ readFromFile();
+
+ uint16_t htype_current = 0;
+ uint32_t time_current = 0;
+ std::vector<uint8_t> identifier_current;
+
+ // If DUID exists in the file, try to use it as much as possible.
+ if (duid_) {
+ std::vector<uint8_t> duid_vec = duid_->getDuid();
+ if ((duid_->getType() == DUID::DUID_LLT) && (duid_vec.size() > 8)) {
+ htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2);
+ time_current = readUint32(&duid_vec[4], duid_vec.size() - 4);
+ identifier_current.assign(duid_vec.begin() + 8, duid_vec.end());
+ }
+ }
+
+ uint32_t time_out = time_in;
+ // If time is unspecified (ANY), then use the time from current DUID or
+ // set it to current time.
+ if (time_out == 0) {
+ time_out = (time_current != 0 ? time_current :
+ static_cast<uint32_t>(time(NULL) - DUID_TIME_EPOCH));
+ }
+
+ std::vector<uint8_t> ll_identifier_out = ll_identifier;
+ uint16_t htype_out = htype;
+
+ // If link layer address unspecified, use address of one of the
+ // interfaces present in the system. Also, update the link
+ // layer type accordingly.
+ if (ll_identifier_out.empty()) {
+ // If DUID doesn't exist yet, generate a new identifier.
+ if (identifier_current.empty()) {
+ createLinkLayerId(ll_identifier_out, htype_out);
+ } else {
+ // Use current identifier and hardware type.
+ ll_identifier_out = identifier_current;
+ htype_out = htype_current;
+ }
+
+ } else if (htype_out == 0) {
+ // If link layer type unspecified and link layer address
+ // is specified, use current type or HTYPE_ETHER.
+ htype_out = ((htype_current != 0) ? htype_current :
+ static_cast<uint16_t>(HTYPE_ETHER));
+
+ }
+
+ // Render DUID.
+ std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(time_out) +
+ sizeof(htype_out));
+ writeUint16(DUID::DUID_LLT, &duid_out[0], 2);
+ writeUint16(htype_out, &duid_out[2], 2);
+ writeUint32(time_out, &duid_out[4], 4);
+ duid_out.insert(duid_out.end(), ll_identifier_out.begin(),
+ ll_identifier_out.end());
+
+ // Set new DUID and persist in a file.
+ set(duid_out);
+}
+
+void
+DUIDFactory::createEN(const uint32_t enterprise_id,
+ const std::vector<uint8_t>& identifier) {
+ // We'll need DUID stored in the file to compare it against the
+ // new configuration. If the new configuration indicates that some
+ // bits of the DUID should be generated we'll first try to use the
+ // values stored in the file to prevent DUID from changing if possible.
+ readFromFile();
+
+ uint32_t enterprise_id_current = 0;
+ std::vector<uint8_t> identifier_current;
+
+ // If DUID exists in the file, try to use it as much as possible.
+ if (duid_) {
+ std::vector<uint8_t> duid_vec = duid_->getDuid();
+ if ((duid_->getType() == DUID::DUID_EN) && (duid_vec.size() > 6)) {
+ enterprise_id_current = readUint32(&duid_vec[2], duid_vec.size() - 2);
+ identifier_current.assign(duid_vec.begin() + 6, duid_vec.end());
+ }
+ }
+
+ // Enterprise id 0 means "unspecified". In this case, try to use existing
+ // DUID's enterprise id, or use ISC enterprise id.
+ uint32_t enterprise_id_out = enterprise_id;
+ if (enterprise_id_out == 0) {
+ if (enterprise_id_current != 0) {
+ enterprise_id_out = enterprise_id_current;
+ } else {
+ enterprise_id_out = ENTERPRISE_ID_ISC;
+ }
+ }
+
+ // Render DUID.
+ std::vector<uint8_t> duid_out(DUID_TYPE_LEN + ENTERPRISE_ID_LEN);
+ writeUint16(DUID::DUID_EN, &duid_out[0], DUID_TYPE_LEN);
+ writeUint32(enterprise_id_out, &duid_out[2], ENTERPRISE_ID_LEN);
+
+ // If no identifier specified, we'll have to use the one from the
+ // DUID file or generate new.
+ if (identifier.empty()) {
+ // No DUID file, so generate new.
+ if (identifier_current.empty()) {
+ // Identifier is empty, so we have to extend the DUID by 6 bytes
+ // to fit the random identifier.
+ duid_out.resize(DUID_TYPE_LEN + ENTERPRISE_ID_LEN +
+ DUID_EN_IDENTIFIER_LEN);
+ // Variable length identifier consists of random numbers. The generated
+ // identifier is always 6 bytes long.
+ ::srandom(time(NULL));
+ fillRandom(duid_out.begin() + DUID_TYPE_LEN + ENTERPRISE_ID_LEN,
+ duid_out.end());
+
+ } else {
+ // Append existing identifier.
+ duid_out.insert(duid_out.end(), identifier_current.begin(),
+ identifier_current.end());
+ }
+
+ } else {
+ // Append the specified identifier to the end of DUID.
+ duid_out.insert(duid_out.end(), identifier.begin(), identifier.end());
+ }
+
+ // Set new DUID and persist in a file.
+ set(duid_out);
+}
+
+void
+DUIDFactory::createLL(const uint16_t htype,
+ const std::vector<uint8_t>& ll_identifier) {
+ // We'll need DUID stored in the file to compare it against the
+ // new configuration. If the new configuration indicates that some
+ // bits of the DUID should be generated we'll first try to use the
+ // values stored in the file to prevent DUID from changing if possible.
+ readFromFile();
+
+ uint16_t htype_current = 0;
+ std::vector<uint8_t> identifier_current;
+
+ // If DUID exists in the file, try to use it as much as possible.
+ if (duid_) {
+ std::vector<uint8_t> duid_vec = duid_->getDuid();
+ if ((duid_->getType() == DUID::DUID_LL) && (duid_vec.size() > 4)) {
+ htype_current = readUint16(&duid_vec[2], duid_vec.size() - 2);
+ identifier_current.assign(duid_vec.begin() + 4, duid_vec.end());
+ }
+ }
+
+ std::vector<uint8_t> ll_identifier_out = ll_identifier;
+ uint16_t htype_out = htype;
+
+ // If link layer address unspecified, use address of one of the
+ // interfaces present in the system. Also, update the link
+ // layer type accordingly.
+ if (ll_identifier_out.empty()) {
+ // If DUID doesn't exist yet, generate a new identifier.
+ if (identifier_current.empty()) {
+ createLinkLayerId(ll_identifier_out, htype_out);
+ } else {
+ // Use current identifier and hardware type.
+ ll_identifier_out = identifier_current;
+ htype_out = htype_current;
+ }
+
+ } else if (htype_out == 0) {
+ // If link layer type unspecified and link layer address
+ // is specified, use current type or HTYPE_ETHER.
+ htype_out = ((htype_current != 0) ? htype_current :
+ static_cast<uint16_t>(HTYPE_ETHER));
+
+ }
+
+ // Render DUID.
+ std::vector<uint8_t> duid_out(DUID_TYPE_LEN + sizeof(htype_out));
+ writeUint16(DUID::DUID_LL, &duid_out[0], 2);
+ writeUint16(htype_out, &duid_out[2], 2);
+ duid_out.insert(duid_out.end(), ll_identifier_out.begin(),
+ ll_identifier_out.end());
+
+ // Set new DUID and persist in a file.
+ set(duid_out);
+}
+
+void
+DUIDFactory::createLinkLayerId(std::vector<uint8_t>& identifier,
+ uint16_t& htype) const {
+ // Let's find suitable interface.
+ for (IfacePtr iface : IfaceMgr::instance().getIfaces()) {
+ // All the following checks could be merged into one multi-condition
+ // statement, but let's keep them separated as perhaps one day
+ // we will grow knobs to selectively turn them on or off. Also,
+ // this code is used only *once* during first start on a new machine
+ // and then server-id is stored. (or at least it will be once
+ // DUID storage is implemented)
+
+ // I wish there was a this_is_a_real_physical_interface flag...
+
+ // MAC address should be at least 6 bytes. Although there is no such
+ // requirement in any RFC, all decent physical interfaces (Ethernet,
+ // WiFi, InfiniBand, etc.) have at least 6 bytes long MAC address.
+ // We want to/ base our DUID on real hardware address, rather than
+ // virtual interface that pretends that underlying IP address is its
+ // MAC.
+ if (iface->getMacLen() < MIN_MAC_LEN) {
+ continue;
+ }
+
+ // Let's don't use loopback.
+ if (iface->flag_loopback_) {
+ continue;
+ }
+
+ // Let's skip downed interfaces. It is better to use working ones.
+ if (!iface->flag_up_) {
+ continue;
+ }
+
+ // Some interfaces (like lo on Linux) report 6-bytes long
+ // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
+ // to generate DUID.
+ if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
+ continue;
+ }
+
+ // Assign link layer address and type.
+ identifier.assign(iface->getMac(), iface->getMac() + iface->getMacLen());
+ htype = iface->getHWType();
+
+ // If it looks like an Ethernet interface we should be happy
+ if ((htype == static_cast<uint16_t>(HTYPE_ETHER)) &&
+ (iface->getMacLen() == 6)) {
+ break;
+ }
+ }
+
+ // We failed to find an interface which link layer address could be
+ // used for generating DUID-LLT.
+ if (identifier.empty()) {
+ isc_throw(Unexpected, "unable to find suitable interface for "
+ "generating a DUID-LLT");
+ }
+}
+
+void
+DUIDFactory::set(const std::vector<uint8_t>& duid_vector) {
+ // Check the minimal length.
+ if (duid_vector.size() < DUID::MIN_DUID_LEN) {
+ isc_throw(BadValue, "generated DUID must have at least "
+ << DUID::MIN_DUID_LEN << " bytes");
+ }
+
+ // Store DUID in a file if file location specified.
+ if (isStored()) {
+ std::ofstream ofs;
+ try {
+ ofs.open(storage_location_.c_str(), std::ofstream::out |
+ std::ofstream::trunc);
+ if (!ofs.good()) {
+ isc_throw(InvalidOperation, "unable to open DUID file "
+ << storage_location_ << " for writing");
+ }
+
+ // Create temporary DUID object.
+ DUID duid(duid_vector);
+
+ // Write DUID to file.
+ ofs << duid.toText();
+ if (!ofs.good()) {
+ isc_throw(InvalidOperation, "unable to write to DUID file "
+ << storage_location_);
+ }
+ } catch (...) {
+ // Close stream before leaving the function.
+ ofs.close();
+ throw;
+ }
+ ofs.close();
+ }
+
+ duid_.reset(new DUID(duid_vector));
+}
+
+DuidPtr
+DUIDFactory::get() {
+ // If DUID is initialized, return it.
+ if (duid_) {
+ return (duid_);
+ }
+
+ // Try to read DUID from file, if it exists.
+ readFromFile();
+ if (duid_) {
+ return (duid_);
+ }
+
+ // DUID doesn't exist, so we need to create it.
+ const std::vector<uint8_t> empty_vector;
+ try {
+ // There is no file with a DUID or the DUID stored in the file is
+ // invalid. We need to generate a new DUID.
+ createLLT(0, 0, empty_vector);
+
+ } catch (...) {
+ // It is possible that the creation of the DUID-LLT failed if there
+ // are no suitable interfaces present in the system.
+ }
+
+ if (!duid_) {
+ // Fall back to creation of DUID enterprise. If that fails we allow
+ // for propagating exception to indicate a fatal error. This may
+ // be the case if we failed to write it to a file.
+ createEN(0, empty_vector);
+ }
+
+ return (duid_);
+}
+
+void
+DUIDFactory::readFromFile() {
+ duid_.reset();
+
+ std::ostringstream duid_str;
+ if (isStored()) {
+ std::ifstream ifs;
+ ifs.open(storage_location_.c_str(), std::ifstream::in);
+ if (ifs.good()) {
+ std::string read_contents;
+ while (!ifs.eof() && ifs.good()) {
+ ifs >> read_contents;
+ duid_str << read_contents;
+ }
+ }
+ ifs.close();
+
+ // If we have read anything from the file, let's try to use it to
+ // create a DUID.
+ if (duid_str.tellp() != std::streampos(0)) {
+ try {
+ duid_.reset(new DUID(DUID::fromText(duid_str.str())));
+
+ } catch (...) {
+ // The contents of this file don't represent a valid DUID.
+ // We'll need to generate it.
+ }
+ }
+ }
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/duid_factory.h b/src/lib/dhcp/duid_factory.h
new file mode 100644
index 0000000..d3d6f71
--- /dev/null
+++ b/src/lib/dhcp/duid_factory.h
@@ -0,0 +1,185 @@
+// Copyright (C) 2015-2018 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 DUID_FACTORY_H
+#define DUID_FACTORY_H
+
+#include <dhcp/duid.h>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Factory for generating DUIDs (DHCP Unique Identifiers).
+///
+/// DHCPv6 clients and servers are identified by DUIDs (see RFC 8415).
+/// DUIDs are unique identifiers carried in the appropriate DHCP
+/// options. RFC 8415 defines 4 types of DUIDs:
+/// -# DUID-LLT
+/// -# DUID-EN
+/// -# DUID-LL
+/// -# DUID-UUID
+///
+/// of which the DUID-LLT is recommended for all general purpose computing
+/// devices. Future specifications may define new DUID types. The current
+/// implementation of the class only supports DUID types defined in RFC 8415.
+///
+/// In most cases DUIDs can be generated automatically, i.e. no manual
+/// configuration is required. For example, DUID-LLT is composed of the
+/// current time and link layer address and type of one of the network
+/// interfaces. Once the DUID is generated it should be stored in the persistent
+/// storage and used by a server or client even when the network interface which
+/// address had been used to generate the DUID is removed.
+///
+/// In some cases administrators may elect to use other types of DUIDs, which
+/// are easier to generate (in case of lack of persistent storage or when
+/// specifics of the device favors some generation methods), e.g. DUID-EN
+/// doesn't rely on the link layer addresses of interfaces present in the
+/// system.
+///
+/// In some cases administrators may want to influence the value of the
+/// generated DUID. For example, DUID-EN includes enterprise identifier and
+/// the administrator may want to select this identifier.
+///
+/// This class allows for selecting a type of DUID to be generated. It also
+/// allows for setting desired values for the components of the DUIDs
+/// being generated, while leaving other components unspecified. For example
+/// an administrator may elect to set the enterprise id for the DUID-EN
+/// and leave the variable length identifier unspecified. The variable
+/// length identifier will be autogenerated.
+///
+/// This class is also responsible for storing the generated DUID in a
+/// file. The location of this file is specified in the class constructor.
+/// If this location is not specified the DUID is not stored, i.e. is
+/// lost when the server or client shuts down. However, the DUID may be
+/// reconstructed according to the configuration of the client or server
+/// when they are back online.
+class DUIDFactory : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param storage_location Absolute path to the file where DUID is
+ /// stored.
+ DUIDFactory(const std::string& storage_location = "");
+
+ /// @brief Checks if generated DUID will be stored in the file.
+ ///
+ /// @return true if generated DUIDs are stored in a file, false
+ /// otherwise.
+ bool isStored() const;
+
+ /// @brief Generates DUID-LLT.
+ ///
+ /// This method generates DUID-LLT (Link Layer plus Time).
+ ///
+ /// @param htype Hardware type. If this is set to 0 and link layer
+ /// address is empty a value from existing DUID or a default value
+ /// of @c HTYPE_ETHER is used. Otherwise a link layer type of selected
+ /// interface is used.
+ /// @param time_in Explicit value of time for the DUID. If this is
+ /// set to 0 a value from existing DUID or current time is used,
+ /// otherwise a value specified is used.
+ /// @param ll_identifier Data to be used as link layer address. If
+ /// this is an empty vector this method will try to use link layer
+ /// address from existing DUID. If there is no DUID yet, it will
+ /// iterate over all active interfaces and will pick link layer
+ /// address of one of them.
+ ///
+ /// @throw isc::Unexpected if none of the interfaces includes has a
+ /// suitable link layer address.
+ void createLLT(const uint16_t htype, const uint32_t time_in,
+ const std::vector<uint8_t>& ll_identifier);
+
+ /// @brief Generates DUID-EN.
+ ///
+ /// This method generates DUID-EN (DUID Enterprise).
+ ///
+ /// @param enterprise_id Enterprise id. If this value is 0, a value
+ /// from existing DUID is used or ISC's enterprise id if there is
+ /// no DUID yet.
+ /// @param identifier Data to be used as variable length identifier.
+ /// If this is an empty vector, an identifier from existing DUID is
+ /// used. If there is no DUID yet, the 6-bytes long vector with random
+ /// values is generated.
+ void createEN(const uint32_t enterprise_id,
+ const std::vector<uint8_t>& identifier);
+
+ /// @brief Generates DUID-LL.
+ ///
+ /// This method generates DUID-LL (Link Layer).
+ ///
+ /// @param htype Hardware type. If this is set to 0 and link layer
+ /// address is empty a value from existing DUID or a default value
+ /// of @c HTYPE_ETHER is used. Otherwise a link layer type of selected
+ /// interface is used.
+ /// @param ll_identifier Data to be used as link layer address. If
+ /// this is an empty vector this method will try to use link layer
+ /// address from existing DUID. If there is no DUID yet, it will
+ /// iterate over all active interfaces and will pick link layer
+ /// address of one of them.
+ ///
+ /// @throw isc::Unexpected if none of the interfaces includes has a
+ /// suitable link layer address.
+ void createLL(const uint16_t htype,
+ const std::vector<uint8_t>& ll_identifier);
+
+ /// @brief Returns current DUID.
+ ///
+ /// This method first checks if the DUID has been generated, i.e. as a
+ /// result of calling DUIDFactory::createLLT. If the DUID hasn't been
+ /// generated, this method will try to read the DUID from the persistent
+ /// storage. If the DUID is found in persistent storage it is returned.
+ /// Otherwise, the DUID-LLT is generated and returned. In some cases the
+ /// generation of the DUID-LLT may fail, e.g. when there are no interfaces
+ /// with a suitable link layer address. In this case, this method will
+ /// generate DUID-EN, with the ISC enterprise id. If this fails, e.g. as a
+ /// result of error while storing the generated DUID-EN, exception
+ /// is thrown.
+ ///
+ /// @return Instance of the DUID read from file, or generated.
+ DuidPtr get();
+
+private:
+
+ /// @brief Creates link layer identifier.
+ ///
+ /// This method iterates over existing network interfaces and finds the
+ /// one with a suitable link layer address to generate a DUID-LLT or
+ /// DUID-LL. It uses selected link layer address to generate identifier
+ /// held in those DUID types.
+ ///
+ /// @param [out] identifier Link layer address for the DUID.
+ /// @param [out] htype Link layer type to be included in the DUID.
+ void createLinkLayerId(std::vector<uint8_t>& identifier,
+ uint16_t& htype) const;
+
+ /// @brief Sets a new DUID as current.
+ ///
+ /// The generated DUID is stored in the file, if such file is specified.
+ /// The new DUID will be returned when @c DUIDFactory::get is called.
+ ///
+ /// @param duid_vector New DUID represented as vector of bytes.
+ void set(const std::vector<uint8_t>& duid_vector);
+
+ /// @brief Reads DUID from file, if file exists.
+ void readFromFile();
+
+ /// @brief Location of the file holding generated DUID (if specified).
+ std::string storage_location_;
+
+ /// @brief Pointer to generated DUID.
+ DuidPtr duid_;
+
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif /* DUID_FACTORY_H */
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
new file mode 100644
index 0000000..9416f2d
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.cc
@@ -0,0 +1,86 @@
+// Copyright (C) 2012-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 <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
+#include <util/strutil.h>
+#include <iomanip>
+#include <sstream>
+#include <vector>
+#include <string.h>
+
+namespace isc {
+namespace dhcp {
+
+const uint32_t HWAddr::HWADDR_SOURCE_ANY = 0xffffffff;
+const uint32_t HWAddr::HWADDR_SOURCE_UNKNOWN = 0x00000000;
+const uint32_t HWAddr::HWADDR_SOURCE_RAW = 0x00000001;
+const uint32_t HWAddr::HWADDR_SOURCE_DUID = 0x00000002;
+const uint32_t HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL = 0x00000004;
+const uint32_t HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION = 0x00000008;
+const uint32_t HWAddr::HWADDR_SOURCE_REMOTE_ID = 0x00000010;
+const uint32_t HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID = 0x00000020;
+const uint32_t HWAddr::HWADDR_SOURCE_DOCSIS_CMTS = 0x00000040;
+const uint32_t HWAddr::HWADDR_SOURCE_DOCSIS_MODEM = 0x00000080;
+
+HWAddr::HWAddr()
+ :htype_(HTYPE_ETHER), source_(0) {
+}
+
+HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint16_t htype)
+ :hwaddr_(hwaddr, hwaddr + len), htype_(htype), source_(0) {
+ if (len > MAX_HWADDR_LEN) {
+ isc_throw(isc::BadValue, "hwaddr length exceeds MAX_HWADDR_LEN");
+ }
+}
+
+HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint16_t htype)
+ :hwaddr_(hwaddr), htype_(htype), source_(0) {
+ if (hwaddr.size() > MAX_HWADDR_LEN) {
+ isc_throw(isc::BadValue,
+ "address vector size exceeds MAX_HWADDR_LEN");
+ }
+}
+
+std::string HWAddr::toText(bool include_htype) const {
+ std::stringstream tmp;
+ if (include_htype) {
+ tmp << "hwtype=" << static_cast<unsigned int>(htype_) << " ";
+ }
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
+ it != hwaddr_.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
+HWAddr
+HWAddr::fromText(const std::string& text, const uint16_t htype) {
+ std::vector<uint8_t> binary;
+ util::str::decodeColonSeparatedHexString(text, binary);
+ return (HWAddr(binary, htype));
+}
+
+bool HWAddr::operator==(const HWAddr& other) const {
+ return ((this->htype_ == other.htype_) &&
+ (this->hwaddr_ == other.hwaddr_));
+}
+
+bool HWAddr::operator!=(const HWAddr& other) const {
+ return !(*this == other);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
new file mode 100644
index 0000000..8e98470
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.h
@@ -0,0 +1,159 @@
+// Copyright (C) 2013-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/.
+
+#ifndef HWADDR_H
+#define HWADDR_H
+
+#include <vector>
+#include <stdint.h>
+#include <stddef.h>
+#include <dhcp/dhcp4.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Hardware type that represents information from DHCPv4 packet
+struct HWAddr {
+public:
+
+ /// @brief Size of an ethernet hardware address.
+ static const size_t ETHERNET_HWADDR_LEN = 6;
+
+ /// @brief Maximum size of a hardware address.
+ static const size_t MAX_HWADDR_LEN = 20;
+
+ /// @defgroup hw_sources Specifies where a given MAC/hardware address was
+ /// obtained.
+ ///
+ /// @brief The list covers all possible MAC/hw address sources.
+ ///
+ /// @{
+
+ /// Not really a type, only used in getMAC() calls.
+ static const uint32_t HWADDR_SOURCE_ANY;
+
+ /// Used when actual origin is not known, e.g. when reading from a
+ /// lease database that didn't store that information.
+ static const uint32_t HWADDR_SOURCE_UNKNOWN;
+
+ /// Obtained first hand from raw socket (100% reliable).
+ static const uint32_t HWADDR_SOURCE_RAW;
+
+ /// Extracted from DUID-LL or DUID-LLT (not 100% reliable as the client
+ /// can send fake DUID).
+ static const uint32_t HWADDR_SOURCE_DUID;
+
+ /// Extracted from IPv6 link-local address. Not 100% reliable, as the
+ /// client can use different IID other than EUI-64, e.g. Windows supports
+ /// RFC4941 and uses random values instead of EUI-64.
+ static const uint32_t HWADDR_SOURCE_IPV6_LINK_LOCAL;
+
+ /// Get it from RFC6939 option. (A relay agent can insert client link layer
+ /// address option). Note that a skilled attacker can fake that by sending
+ /// his request relayed, so the legitimate relay will think it's a second
+ /// relay.
+ static const uint32_t HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION;
+
+ /// A relay can insert remote-id. In some deployments it contains a MAC
+ /// address (RFC4649).
+ static const uint32_t HWADDR_SOURCE_REMOTE_ID;
+
+ /// A relay can insert a subscriber-id option. In some deployments it
+ /// contains a MAC address (RFC4580).
+ static const uint32_t HWADDR_SOURCE_SUBSCRIBER_ID;
+
+ /// A CMTS (acting as DHCP relay agent) that supports DOCSIS standard
+ /// can insert DOCSIS options that contain client's MAC address.
+ /// This specific option is suboption 1026 in vendor-class option with
+ /// vendor-id=4491. Client in this context would be a cable modem.
+ static const uint32_t HWADDR_SOURCE_DOCSIS_CMTS;
+
+ /// A cable modem (acting as DHCP client) that supports DOCSIS standard
+ /// can insert DOCSIS options that contain client's MAC address.
+ /// This specific option is suboption 36 in vendor-class option with
+ /// vendor-id=4491.
+ static const uint32_t HWADDR_SOURCE_DOCSIS_MODEM;
+
+ /// @}
+
+ /// @brief default constructor
+ HWAddr();
+
+ /// @brief constructor, based on C-style pointer and length
+ /// @param hwaddr pointer to hardware address
+ /// @param len length of the address pointed by hwaddr
+ /// @param htype hardware type
+ HWAddr(const uint8_t* hwaddr, size_t len, uint16_t htype);
+
+ /// @brief constructor, based on C++ vector<uint8_t>
+ /// @param hwaddr const reference to hardware address
+ /// @param htype hardware type
+ HWAddr(const std::vector<uint8_t>& hwaddr, uint16_t htype);
+
+ // Vector that keeps the actual hardware address
+ std::vector<uint8_t> hwaddr_;
+
+ /// Hardware type
+ ///
+ /// @note It used to be uint8_t as used in DHCPv4. However, since we're
+ /// expanding MAC addresses support to DHCPv6 that uses hw_type as
+ /// 16 bits, we need to be able to store that wider format.
+ uint16_t htype_;
+
+ /// @brief Hardware address source
+ ///
+ /// This variable specifies how the hardware address was obtained.
+ /// @todo This is a stub implementation. Proper implementation will move
+ /// constants from Pkt::HWADDR_SOURCE_* here. Currently always initialized
+ /// to zero.
+ uint32_t source_;
+
+ /// @brief Returns textual representation of a hardware address
+ /// (e.g. 00:01:02:03:04:05)
+ ///
+ /// @param include_htype Boolean value which controls whether the hardware
+ /// type is included in the returned string (true), or not (false).
+ ///
+ /// @return Hardware address in the textual format.
+ std::string toText(bool include_htype = true) const;
+
+ /// @brief Creates instance of the hardware address from textual format.
+ ///
+ /// This function parses HW address specified as text and creates the
+ /// corresponding @c HWAddr instance. The hexadecimal digits representing
+ /// individual bytes of the hardware address should be separated with
+ /// colons. Typically, two digits per byte are used. However, this function
+ /// allows for 1 digit per HW address byte. In this case, the digit is
+ /// prepended with '0' during conversion to binary value.
+ ///
+ /// This function can be used to perform a reverse operation to the
+ /// @c HWAddr::toText(false).
+ ///
+ /// The instance created by this function sets HTYPE_ETHER as a hardware
+ /// type.
+ ///
+ /// @param text HW address in the textual format.
+ /// @param htype Hardware type.
+ ///
+ /// @return Instance of the HW address created from text.
+ static HWAddr fromText(const std::string& text,
+ const uint16_t htype = HTYPE_ETHER);
+
+ /// @brief Compares two hardware addresses for equality
+ bool operator==(const HWAddr& other) const;
+
+ /// @brief Compares two hardware addresses for inequality
+ bool operator!=(const HWAddr& other) const;
+};
+
+/// @brief Shared pointer to a hardware address structure
+typedef boost::shared_ptr<HWAddr> HWAddrPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // HWADDR_H
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
new file mode 100644
index 0000000..c23a9e9
--- /dev/null
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -0,0 +1,1989 @@
+// Copyright (C) 2011-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_error.h>
+#include <asiolink/udp_endpoint.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/pkt_filter_inet6.h>
+#include <exceptions/exceptions.h>
+#include <util/io/pktinfo_utilities.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cstring>
+#include <errno.h>
+#include <fstream>
+#include <functional>
+#include <limits>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+
+#ifndef FD_COPY
+#define FD_COPY(orig, copy) \
+ do { \
+ memmove(copy, orig, sizeof(fd_set)); \
+ } while (0)
+#endif
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace isc::util::io;
+using namespace isc::util::io::internal;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp {
+
+IfaceMgr&
+IfaceMgr::instance() {
+ return (*instancePtr());
+}
+
+const IfaceMgrPtr&
+IfaceMgr::instancePtr() {
+ static IfaceMgrPtr iface_mgr(new IfaceMgr());
+ return (iface_mgr);
+}
+
+Iface::Iface(const std::string& name, unsigned int ifindex)
+ : name_(name), ifindex_(ifindex), mac_len_(0), hardware_type_(0),
+ flag_loopback_(false), flag_up_(false), flag_running_(false),
+ flag_multicast_(false), flag_broadcast_(false), flags_(0),
+ inactive4_(false), inactive6_(false) {
+ // Sanity checks.
+ if (name.empty()) {
+ isc_throw(BadValue, "Interface name must not be empty");
+ }
+ memset(mac_, 0, sizeof(mac_));
+}
+
+void
+Iface::closeSockets() {
+ // Close IPv4 sockets.
+ closeSockets(AF_INET);
+ // Close IPv6 sockets.
+ closeSockets(AF_INET6);
+}
+
+void
+Iface::closeSockets(const uint16_t family) {
+ // Check that the correct 'family' value has been specified.
+ // The possible values are AF_INET or AF_INET6. Note that, in
+ // the current code they are used to differentiate that the
+ // socket is used to transmit IPv4 or IPv6 traffic. However,
+ // the actual family types of the sockets may be different,
+ // e.g. for LPF we are using raw sockets of AF_PACKET family.
+ //
+ // @todo Consider replacing the AF_INET and AF_INET6 with some
+ // enum which will not be confused with the actual socket type.
+ if ((family != AF_INET) && (family != AF_INET6)) {
+ isc_throw(BadValue, "Invalid socket family " << family
+ << " specified when requested to close all sockets"
+ << " which belong to this family");
+ }
+
+ // Search for the socket of the specific type.
+ SocketCollection::iterator sock = sockets_.begin();
+ while (sock != sockets_.end()) {
+ if (sock->family_ == family) {
+ // Close and delete the socket and move to the
+ // next one.
+ close(sock->sockfd_);
+ // Close fallback socket if open.
+ if (sock->fallbackfd_ >= 0) {
+ close(sock->fallbackfd_);
+ }
+ sockets_.erase(sock++);
+
+ } else {
+ // Different type of socket. Let's move
+ // to the next one.
+ ++sock;
+
+ }
+ }
+}
+
+std::string
+Iface::getFullName() const {
+ ostringstream tmp;
+ tmp << name_ << "/" << ifindex_;
+ return (tmp.str());
+}
+
+std::string
+Iface::getPlainMac() const {
+ ostringstream tmp;
+ tmp.fill('0');
+ tmp << hex;
+ for (int i = 0; i < mac_len_; i++) {
+ tmp.width(2);
+ tmp << static_cast<int>(mac_[i]);
+ if (i < mac_len_-1) {
+ tmp << ":";
+ }
+ }
+ return (tmp.str());
+}
+
+void Iface::setMac(const uint8_t* mac, size_t len) {
+ if (len > MAX_MAC_LEN) {
+ isc_throw(OutOfRange, "Interface " << getFullName()
+ << " was detected to have link address of length "
+ << len << ", but maximum supported length is "
+ << MAX_MAC_LEN);
+ }
+ mac_len_ = len;
+ if (len > 0) {
+ memcpy(mac_, mac, len);
+ }
+}
+
+bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
+ for (AddressCollection::iterator a = addrs_.begin();
+ a!=addrs_.end(); ++a) {
+ if (a->get() == addr) {
+ addrs_.erase(a);
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool Iface::delSocket(const uint16_t sockfd) {
+ list<SocketInfo>::iterator sock = sockets_.begin();
+ while (sock!=sockets_.end()) {
+ if (sock->sockfd_ == sockfd) {
+ close(sockfd);
+ // Close fallback socket if open.
+ if (sock->fallbackfd_ >= 0) {
+ close(sock->fallbackfd_);
+ }
+ sockets_.erase(sock);
+ return (true); //socket found
+ }
+ ++sock;
+ }
+ return (false); // socket not found
+}
+
+IfaceMgr::IfaceMgr()
+ : packet_filter_(new PktFilterInet()),
+ packet_filter6_(new PktFilterInet6()),
+ test_mode_(false), allow_loopback_(false) {
+
+ // Ensure that PQMs have been created to guarantee we have
+ // default packet queues in place.
+ try {
+ packet_queue_mgr4_.reset(new PacketQueueMgr4());
+ packet_queue_mgr6_.reset(new PacketQueueMgr6());
+ } catch (const std::exception& ex) {
+ isc_throw(Unexpected, "Failed to create PacketQueueManagers: " << ex.what());
+ }
+
+ detect_callback_ = std::bind(&IfaceMgr::checkDetectIfaces, this, ph::_1);
+
+ try {
+
+ // required for sending/receiving packets
+ // let's keep it in front, just in case someone
+ // wants to send anything during initialization
+ detectIfaces();
+
+ } catch (const std::exception& ex) {
+ isc_throw(IfaceDetectError, ex.what());
+ }
+}
+
+void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
+ for (Address a : unicasts_) {
+ if (a.get() == addr) {
+ isc_throw(BadValue, "Address " << addr
+ << " already defined on the " << name_ << " interface.");
+ }
+ }
+ unicasts_.push_back(Optional<IOAddress>(addr));
+}
+
+bool
+Iface::getAddress4(isc::asiolink::IOAddress& address) const {
+ // Iterate over existing addresses assigned to the interface.
+ // Try to find the one that is IPv4.
+ for (Address addr : getAddresses()) {
+ // If address is IPv4, we assign it to the function argument
+ // and return true.
+ if (addr.get().isV4()) {
+ address = addr.get();
+ return (true);
+ }
+ }
+ // There is no IPv4 address assigned to this interface.
+ return (false);
+}
+
+bool
+Iface::hasAddress(const isc::asiolink::IOAddress& address) const {
+ for (Address addr : getAddresses()) {
+ if (address == addr.get()) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+void
+Iface::addAddress(const isc::asiolink::IOAddress& addr) {
+ if (!hasAddress(addr)) {
+ addrs_.push_back(Address(addr));
+ }
+}
+
+void
+Iface::setActive(const IOAddress& address, const bool active) {
+ for (AddressCollection::iterator addr_it = addrs_.begin();
+ addr_it != addrs_.end(); ++addr_it) {
+ if (address == addr_it->get()) {
+ addr_it->unspecified(!active);
+ return;
+ }
+ }
+ isc_throw(BadValue, "specified address " << address << " was not"
+ " found on the interface " << getName());
+}
+
+void
+Iface::setActive(const bool active) {
+ for (AddressCollection::iterator addr_it = addrs_.begin();
+ addr_it != addrs_.end(); ++addr_it) {
+ addr_it->unspecified(!active);
+ }
+}
+
+unsigned int
+Iface::countActive4() const {
+ uint16_t count = 0;
+ for (Address addr : addrs_) {
+ if (!addr.unspecified() && addr.get().isV4()) {
+ ++count;
+ }
+ }
+ return (count);
+}
+
+void IfaceMgr::closeSockets() {
+ // Clears bound addresses.
+ clearBoundAddresses();
+
+ // Stops the receiver thread if there is one.
+ stopDHCPReceiver();
+
+ for (IfacePtr iface : ifaces_) {
+ iface->closeSockets();
+ }
+}
+
+void IfaceMgr::stopDHCPReceiver() {
+ if (isDHCPReceiverRunning()) {
+ dhcp_receiver_->stop();
+ }
+
+ dhcp_receiver_.reset();
+
+ if (getPacketQueue4()) {
+ getPacketQueue4()->clear();
+ }
+
+ if (getPacketQueue6()) {
+ getPacketQueue6()->clear();
+ }
+}
+
+IfaceMgr::~IfaceMgr() {
+ closeSockets();
+}
+
+bool
+IfaceMgr::isDirectResponseSupported() const {
+ return (packet_filter_->isDirectResponseSupported());
+}
+
+void
+IfaceMgr::addExternalSocket(int socketfd, SocketCallback callback) {
+ if (socketfd < 0) {
+ isc_throw(BadValue, "Attempted to install callback for invalid socket "
+ << socketfd);
+ }
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ for (SocketCallbackInfo s : callbacks_) {
+ // There's such a socket description there already.
+ // Update the callback and we're done
+ if (s.socket_ == socketfd) {
+ s.callback_ = callback;
+ return;
+ }
+ }
+
+ // Add a new entry to the callbacks vector
+ SocketCallbackInfo x;
+ x.socket_ = socketfd;
+ x.callback_ = callback;
+ callbacks_.push_back(x);
+}
+
+void
+IfaceMgr::deleteExternalSocket(int socketfd) {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ deleteExternalSocketInternal(socketfd);
+}
+
+void
+IfaceMgr::deleteExternalSocketInternal(int socketfd) {
+ for (SocketCallbackInfoContainer::iterator s = callbacks_.begin();
+ s != callbacks_.end(); ++s) {
+ if (s->socket_ == socketfd) {
+ callbacks_.erase(s);
+ return;
+ }
+ }
+}
+
+int
+IfaceMgr::purgeBadSockets() {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ std::vector<int> bad_fds;
+ for (SocketCallbackInfo s : callbacks_) {
+ errno = 0;
+ if (fcntl(s.socket_, F_GETFD) < 0 && (errno == EBADF)) {
+ bad_fds.push_back(s.socket_);
+ }
+ }
+
+ for (auto bad_fd : bad_fds) {
+ deleteExternalSocketInternal(bad_fd);
+ }
+
+ return (bad_fds.size());
+}
+
+void
+IfaceMgr::deleteAllExternalSockets() {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ callbacks_.clear();
+}
+
+void
+IfaceMgr::setPacketFilter(const PktFilterPtr& packet_filter) {
+ // Do not allow null pointer.
+ if (!packet_filter) {
+ isc_throw(InvalidPacketFilter, "NULL packet filter object specified for"
+ " DHCPv4");
+ }
+ // Different packet filters use different socket types. It does not make
+ // sense to allow the change of packet filter when there are IPv4 sockets
+ // open because they can't be used by the receive/send functions of the
+ // new packet filter. Below, we check that there are no open IPv4 sockets.
+ // If we find at least one, we have to fail. However, caller still has a
+ // chance to replace the packet filter if he closes sockets explicitly.
+ if (hasOpenSocket(AF_INET)) {
+ // There is at least one socket open, so we have to fail.
+ isc_throw(PacketFilterChangeDenied,
+ "it is not allowed to set new packet"
+ << " filter when there are open IPv4 sockets - need"
+ << " to close them first");
+ }
+ // Everything is fine, so replace packet filter.
+ packet_filter_ = packet_filter;
+}
+
+void
+IfaceMgr::setPacketFilter(const PktFilter6Ptr& packet_filter) {
+ if (!packet_filter) {
+ isc_throw(InvalidPacketFilter, "NULL packet filter object specified for"
+ " DHCPv6");
+ }
+
+ if (hasOpenSocket(AF_INET6)) {
+ // There is at least one socket open, so we have to fail.
+ isc_throw(PacketFilterChangeDenied,
+ "it is not allowed to set new packet"
+ << " filter when there are open IPv6 sockets - need"
+ << " to close them first");
+ }
+
+ packet_filter6_ = packet_filter;
+}
+
+bool
+IfaceMgr::hasOpenSocket(const uint16_t family) const {
+ // Iterate over all interfaces and search for open sockets.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo sock : iface->getSockets()) {
+ // Check if the socket matches specified family.
+ if (sock.family_ == family) {
+ // There is at least one socket open, so return.
+ return (true);
+ }
+ }
+ }
+ // There are no open sockets found for the specified family.
+ return (false);
+}
+
+bool
+IfaceMgr::hasOpenSocket(const IOAddress& addr) const {
+ // Fast track for IPv4 using bound addresses.
+ if (addr.isV4() && !bound_address_.empty()) {
+ return (bound_address_.count(addr.toUint32()) != 0);
+ }
+ // Iterate over all interfaces and search for open sockets.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo sock : iface->getSockets()) {
+ // Check if the socket address matches the specified address or
+ // if address is unspecified (in6addr_any).
+ if (sock.addr_ == addr) {
+ return (true);
+ } else if (sock.addr_.isV6Zero()) {
+ // Handle the case that the address is unspecified (any).
+ // This happens only with IPv6 so we do not check IPv4.
+ // In this case, we should check if the specified address
+ // belongs to any of the interfaces.
+ for (IfacePtr it : ifaces_) {
+ for (Iface::Address a : it->getAddresses()) {
+ if (addr == a.get()) {
+ return (true);
+ }
+ }
+ }
+ // The address does not belongs to any interface.
+ return (false);
+ }
+ }
+ }
+ // There are no open sockets found for the specified family.
+ return (false);
+}
+
+bool
+IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
+ IfaceMgrErrorMsgCallback error_handler,
+ const bool skip_opened) {
+ int count = 0;
+ int bcast_num = 0;
+
+ for (IfacePtr iface : ifaces_) {
+ // Clear any errors from previous socket opening.
+ iface->clearErrors();
+
+ // If the interface is inactive, there is nothing to do. Simply
+ // proceed to the next detected interface.
+ if (iface->inactive4_) {
+ continue;
+ }
+
+ // If the interface has been specified in the configuration that
+ // it should be used to listen the DHCP traffic we have to check
+ // that the interface configuration is valid and that the interface
+ // is not a loopback interface. In both cases, we want to report
+ // that the socket will not be opened.
+ // Relax the check when the loopback interface was explicitly
+ // allowed
+ if (iface->flag_loopback_ && !allow_loopback_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "must not open socket on the loopback"
+ " interface " << iface->getName());
+ continue;
+ }
+
+ if (!iface->flag_up_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "the interface " << iface->getName()
+ << " is down");
+ continue;
+ }
+
+ if (!iface->flag_running_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "the interface " << iface->getName()
+ << " is not running");
+ continue;
+ }
+
+ IOAddress out_address("0.0.0.0");
+ if (!iface->getAddress4(out_address)) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "the interface " << iface->getName()
+ << " has no usable IPv4 addresses configured");
+ continue;
+ }
+
+ for (Iface::Address addr : iface->getAddresses()) {
+ // Skip non-IPv4 addresses and those that weren't selected..
+ if (addr.unspecified() || !addr.get().isV4()) {
+ continue;
+ }
+
+ // If selected interface is broadcast capable set appropriate
+ // options on the socket so as it can receive and send broadcast
+ // messages.
+ bool is_open_as_broadcast = iface->flag_broadcast_ && use_bcast;
+
+ // The DHCP server must have means to determine which interface
+ // the broadcast packets are coming from. This is achieved by
+ // binding a socket to the device (interface) and specialized
+ // packet filters (e.g. BPF and LPF) implement this mechanism.
+ // If the PktFilterInet (generic one) is used, the socket is
+ // bound to INADDR_ANY which effectively binds the socket to
+ // all addresses on all interfaces. So, only one of those can
+ // be opened. Currently, the direct response support is
+ // provided by the PktFilterLPF and PktFilterBPF, so by checking
+ // the support for direct response we actually determine that
+ // one of those objects is in use. For all other objects we
+ // assume that binding to the device is not supported and we
+ // cease opening sockets and display the appropriate message.
+ if (is_open_as_broadcast && !isDirectResponseSupported() && bcast_num > 0) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "Binding socket to an interface is not"
+ " supported on this OS; therefore only"
+ " one socket listening to broadcast traffic"
+ " can be opened. Sockets will not be opened"
+ " on remaining interfaces");
+ continue;
+ }
+
+ // Skip the address that already has a bound socket. It allows
+ // for preventing bind errors or re-opening sockets.
+ if (!skip_opened || !IfaceMgr::hasOpenSocket(addr.get())) {
+ try {
+ // We haven't open any broadcast sockets yet, so we can
+ // open at least one more or
+ // not broadcast capable, do not set broadcast flags.
+ IfaceMgr::openSocket(iface->getName(), addr.get(), port,
+ is_open_as_broadcast,
+ is_open_as_broadcast);
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "Failed to open socket on interface "
+ << iface->getName()
+ << ", reason: "
+ << ex.what());
+ continue;
+ }
+ }
+
+ if (is_open_as_broadcast) {
+ // Binding socket to an interface is not supported so we
+ // can't open any more broadcast sockets. Increase the
+ // number of open broadcast sockets.
+ ++bcast_num;
+ }
+
+ ++count;
+ }
+ }
+
+ // If we have open sockets, start the receiver.
+ if (count > 0) {
+ // Collects bound addresses.
+ collectBoundAddresses();
+
+ // Starts the receiver thread (if queueing is enabled).
+ startDHCPReceiver(AF_INET);
+ }
+
+ return (count > 0);
+}
+
+bool
+IfaceMgr::openSockets6(const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler,
+ const bool skip_opened) {
+ int count = 0;
+
+ for (IfacePtr iface : ifaces_) {
+ // Clear any errors from previous socket opening.
+ iface->clearErrors();
+
+ // If the interface is inactive, there is nothing to do. Simply
+ // proceed to the next detected interface.
+ if (iface->inactive6_) {
+ continue;
+ }
+
+ // If the interface has been specified in the configuration that
+ // it should be used to listen the DHCP traffic we have to check
+ // that the interface configuration is valid and that the interface
+ // is not a loopback interface. In both cases, we want to report
+ // that the socket will not be opened.
+ // Relax the check when the loopback interface was explicitly
+ // allowed
+ if (iface->flag_loopback_ && !allow_loopback_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "must not open socket on the loopback"
+ " interface " << iface->getName());
+ continue;
+ } else if (!iface->flag_up_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "the interface " << iface->getName()
+ << " is down");
+ continue;
+ } else if (!iface->flag_running_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "the interface " << iface->getName()
+ << " is not running");
+ continue;
+ }
+
+ // Open unicast sockets if there are any unicast addresses defined
+ for (Iface::Address addr : iface->getUnicasts()) {
+ // Skip the address that already has a bound socket. It allows
+ // for preventing bind errors or re-opening sockets.
+ // The @ref IfaceMgr::hasOpenSocket(addr) does match the "::"
+ // address on BSD and Solaris on any interface, so we make sure that
+ // that interface actually has opened sockets by checking the number
+ // of sockets to be non zero.
+ if (!skip_opened || !IfaceMgr::hasOpenSocket(addr) ||
+ !iface->getSockets().size()) {
+ try {
+ IfaceMgr::openSocket(iface->getName(), addr, port, false, false);
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "Failed to open unicast socket on interface "
+ << iface->getName()
+ << ", reason: " << ex.what());
+ continue;
+ }
+ }
+
+ count++;
+ }
+
+ for (Iface::Address addr : iface->getAddresses()) {
+
+ // Skip all but V6 addresses.
+ if (!addr.get().isV6()) {
+ continue;
+ }
+
+ // Bind link-local addresses only. Otherwise we bind several sockets
+ // on interfaces that have several global addresses. For examples
+ // with interface with 2 global addresses, we would bind 3 sockets
+ // (one for link-local and two for global). That would result in
+ // getting each message 3 times.
+ if (!addr.get().isV6LinkLocal()){
+ continue;
+ }
+
+ // Run OS-specific function to open a socket capable of receiving
+ // packets sent to All_DHCP_Relay_Agents_and_Servers multicast
+ // address.
+
+ // Skip the address that already has a bound socket. It allows
+ // for preventing bind errors or re-opening sockets.
+ // The @ref IfaceMgr::hasOpenSocket(addr) does match the "::"
+ // address on BSD and Solaris on any interface, so we make sure that
+ // the interface actually has opened sockets by checking the number
+ // of sockets to be non zero.
+ if (!skip_opened || !IfaceMgr::hasOpenSocket(addr) ||
+ !iface->getSockets().size()) {
+ try {
+ // Pass a null pointer as an error handler to avoid
+ // suppressing an exception in a system-specific function.
+ IfaceMgr::openMulticastSocket(*iface, addr, port);
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, iface,
+ "Failed to open multicast socket on interface "
+ << iface->getName() << ", reason: " << ex.what());
+ continue;
+ }
+ }
+
+ ++count;
+ }
+ }
+
+ // If we have open sockets, start the receiver.
+ if (count > 0) {
+ // starts the receiver thread (if queueing is enabled).
+ startDHCPReceiver(AF_INET6);
+ }
+ return (count > 0);
+}
+
+void
+IfaceMgr::startDHCPReceiver(const uint16_t family) {
+ if (isDHCPReceiverRunning()) {
+ isc_throw(InvalidOperation, "a receiver thread already exists");
+ }
+
+ switch (family) {
+ case AF_INET:
+ // If the queue doesn't exist, packet queing has been configured
+ // as disabled. If there is no queue, we do not create a receiver.
+ if(!getPacketQueue4()) {
+ return;
+ }
+
+ dhcp_receiver_.reset(new WatchedThread());
+ dhcp_receiver_->start(std::bind(&IfaceMgr::receiveDHCP4Packets, this));
+ break;
+ case AF_INET6:
+ // If the queue doesn't exist, packet queing has been configured
+ // as disabled. If there is no queue, we do not create a receiver.
+ if(!getPacketQueue6()) {
+ return;
+ }
+
+ dhcp_receiver_.reset(new WatchedThread());
+ dhcp_receiver_->start(std::bind(&IfaceMgr::receiveDHCP6Packets, this));
+ break;
+ default:
+ isc_throw (BadValue, "startDHCPReceiver: invalid family: " << family);
+ break;
+ }
+}
+
+void
+IfaceMgr::addInterface(const IfacePtr& iface) {
+ for (const IfacePtr& existing : ifaces_) {
+ if ((existing->getName() == iface->getName()) ||
+ (existing->getIndex() == iface->getIndex())) {
+ isc_throw(Unexpected, "Can't add " << iface->getFullName() <<
+ " when " << existing->getFullName() <<
+ " already exists.");
+ }
+ }
+ ifaces_.push_back(iface);
+}
+
+void
+IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
+ for (IfacePtr iface : ifaces_) {
+ const Iface::AddressCollection& addrs = iface->getAddresses();
+
+ out << "Detected interface " << iface->getFullName()
+ << ", hwtype=" << iface->getHWType()
+ << ", mac=" << iface->getPlainMac();
+ out << ", flags=" << hex << iface->flags_ << dec << "("
+ << (iface->flag_loopback_?"LOOPBACK ":"")
+ << (iface->flag_up_?"UP ":"")
+ << (iface->flag_running_?"RUNNING ":"")
+ << (iface->flag_multicast_?"MULTICAST ":"")
+ << (iface->flag_broadcast_?"BROADCAST ":"")
+ << ")" << endl;
+ out << " " << addrs.size() << " addr(s):";
+
+ for (Iface::Address addr : addrs) {
+ out << " " << addr.get().toText();
+ }
+ out << endl;
+ }
+}
+
+IfacePtr
+IfaceCollection::getIface(const unsigned int ifindex) {
+ return (getIfaceInternal(ifindex, MultiThreadingMgr::instance().getMode()));
+}
+
+IfacePtr
+IfaceCollection::getIface(const std::string& ifname) {
+ return (getIfaceInternal(ifname, MultiThreadingMgr::instance().getMode()));
+}
+
+IfacePtr
+IfaceCollection::getIfaceInternal(const unsigned int ifindex, const bool need_lock) {
+ if (ifindex == UNSET_IFINDEX) {
+ isc_throw(BadValue, "interface index was not set");
+ }
+ if (need_lock) {
+ lock_guard<mutex> lock(mutex_);
+ if (cache_ && (cache_->getIndex() == ifindex)) {
+ return (cache_);
+ }
+ } else {
+ if (cache_ && (cache_->getIndex() == ifindex)) {
+ return (cache_);
+ }
+ }
+ const auto& idx = ifaces_container_.get<1>();
+ auto it = idx.find(ifindex);
+ if (it == idx.end()) {
+ return (IfacePtr()); // not found
+ }
+ if (need_lock) {
+ lock_guard<mutex> lock(mutex_);
+ cache_ = *it;
+ return (cache_);
+ } else {
+ cache_ = *it;
+ return (cache_);
+ }
+}
+
+IfacePtr
+IfaceCollection::getIfaceInternal(const std::string& ifname, const bool need_lock) {
+ if (need_lock) {
+ lock_guard<mutex> lock(mutex_);
+ if (cache_ && (cache_->getName() == ifname)) {
+ return (cache_);
+ }
+ } else {
+ if (cache_ && (cache_->getName() == ifname)) {
+ return (cache_);
+ }
+ }
+ const auto& idx = ifaces_container_.get<2>();
+ auto it = idx.find(ifname);
+ if (it == idx.end()) {
+ return (IfacePtr()); // not found
+ }
+ if (need_lock) {
+ lock_guard<mutex> lock(mutex_);
+ cache_ = *it;
+ return (cache_);
+ } else {
+ cache_ = *it;
+ return (cache_);
+ }
+}
+
+IfacePtr
+IfaceMgr::getIface(const unsigned int ifindex) {
+ return (ifaces_.getIface(ifindex));
+}
+
+IfacePtr
+IfaceMgr::getIface(const std::string& ifname) {
+ if (ifname.empty()) {
+ return (IfacePtr()); // empty
+ }
+ return (ifaces_.getIface(ifname));
+}
+
+IfacePtr
+IfaceMgr::getIface(const PktPtr& pkt) {
+ if (pkt->indexSet()) {
+ return (getIface(pkt->getIndex()));
+ } else {
+ return (getIface(pkt->getIface()));
+ }
+}
+
+void
+IfaceMgr::clearIfaces() {
+ ifaces_.clear();
+}
+
+void
+IfaceMgr::clearBoundAddresses() {
+ bound_address_.clear();
+}
+
+void
+IfaceMgr::collectBoundAddresses() {
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo sock : iface->getSockets()) {
+ const IOAddress& addr = sock.addr_;
+ if (!addr.isV4()) {
+ continue;
+ }
+ if (bound_address_.count(addr.toUint32()) == 0) {
+ bound_address_.insert(addr);
+ }
+ }
+ }
+}
+
+void
+IfaceMgr::clearUnicasts() {
+ for (IfacePtr iface : ifaces_) {
+ iface->clearUnicasts();
+ }
+}
+
+int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
+ const uint16_t port, const bool receive_bcast,
+ const bool send_bcast) {
+ IfacePtr iface = getIface(ifname);
+ if (!iface) {
+ isc_throw(BadValue, "There is no " << ifname << " interface present.");
+ }
+ if (addr.isV4()) {
+ return openSocket4(*iface, addr, port, receive_bcast, send_bcast);
+
+ } else if (addr.isV6()) {
+ return openSocket6(*iface, addr, port, receive_bcast);
+
+ } else {
+ isc_throw(BadValue, "Failed to detect family of address: "
+ << addr);
+ }
+}
+
+int IfaceMgr::openSocketFromIface(const std::string& ifname,
+ const uint16_t port,
+ const uint8_t family) {
+ // Search for specified interface among detected interfaces.
+ for (IfacePtr iface : ifaces_) {
+ if ((iface->getFullName() != ifname) &&
+ (iface->getName() != ifname)) {
+ continue;
+ }
+
+ // Interface is now detected. Search for address on interface
+ // that matches address family (v6 or v4).
+ Iface::AddressCollection addrs = iface->getAddresses();
+ Iface::AddressCollection::iterator addr_it = addrs.begin();
+ while (addr_it != addrs.end()) {
+ if (addr_it->get().getFamily() == family) {
+ // We have interface and address so let's open socket.
+ // This may cause isc::Unexpected exception.
+ return (openSocket(iface->getName(), *addr_it, port, false));
+ }
+ ++addr_it;
+ }
+ // If we are at the end of address collection it means that we found
+ // interface but there is no address for family specified.
+ if (addr_it == addrs.end()) {
+ // Stringify the family value to append it to exception string.
+ std::string family_name("AF_INET");
+ if (family == AF_INET6) {
+ family_name = "AF_INET6";
+ }
+ // We did not find address on the interface.
+ isc_throw(SocketConfigError, "There is no address for interface: "
+ << ifname << ", port: " << port << ", address "
+ " family: " << family_name);
+ }
+ }
+ // If we got here it means that we had not found the specified interface.
+ // Otherwise we would have returned from previous exist points.
+ isc_throw(BadValue, "There is no " << ifname << " interface present.");
+}
+
+int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
+ const uint16_t port) {
+ // Search through detected interfaces and addresses to match
+ // local address we got.
+ for (IfacePtr iface : ifaces_) {
+ for (Iface::Address a : iface->getAddresses()) {
+
+ // Local address must match one of the addresses
+ // on detected interfaces. If it does, we have
+ // address and interface detected so we can open
+ // socket.
+ if (a.get() == addr) {
+ // Open socket using local interface, address and port.
+ // This may cause isc::Unexpected exception.
+ return (openSocket(iface->getName(), a, port, false));
+ }
+ }
+ }
+ // If we got here it means that we did not find specified address
+ // on any available interface.
+ isc_throw(BadValue, "There is no such address " << addr);
+}
+
+int IfaceMgr::openSocketFromRemoteAddress(const IOAddress& remote_addr,
+ const uint16_t port) {
+ try {
+ // Get local address to be used to connect to remote location.
+ IOAddress local_address(getLocalAddress(remote_addr, port));
+ return openSocketFromAddress(local_address, port);
+ } catch (const Exception& e) {
+ isc_throw(SocketConfigError, e.what());
+ }
+}
+
+isc::asiolink::IOAddress
+IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
+ // Create remote endpoint, we will be connecting to it.
+ boost::scoped_ptr<const UDPEndpoint>
+ remote_endpoint(static_cast<const UDPEndpoint*>
+ (UDPEndpoint::create(IPPROTO_UDP, remote_addr, port)));
+ if (!remote_endpoint) {
+ isc_throw(Unexpected, "Unable to create remote endpoint");
+ }
+
+ // Create socket that will be used to connect to remote endpoint.
+ boost::asio::io_service io_service;
+ boost::asio::ip::udp::socket sock(io_service);
+
+ boost::system::error_code err_code;
+ // If remote address is broadcast address we have to
+ // allow this on the socket.
+ if (remote_addr.isV4() &&
+ (remote_addr == IOAddress(DHCP_IPV4_BROADCAST_ADDRESS))) {
+ // Socket has to be open prior to setting the broadcast
+ // option. Otherwise set_option will complain about
+ // bad file descriptor.
+
+ // @todo: We don't specify interface in any way here. 255.255.255.255
+ // We can very easily end up with a socket working on a different
+ // interface.
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ sock.open(boost::asio::ip::udp::v4(), err_code);
+ if (err_code) {
+ const char* errstr = strerror(errno);
+ isc_throw(Unexpected, "failed to open UDPv4 socket, reason:"
+ << errstr);
+ }
+ sock.set_option(boost::asio::socket_base::broadcast(true), err_code);
+ if (err_code) {
+ sock.close();
+ isc_throw(Unexpected, "failed to enable broadcast on the socket");
+ }
+ }
+
+ // Try to connect to remote endpoint and check if attempt is successful.
+ sock.connect(remote_endpoint->getASIOEndpoint(), err_code);
+ if (err_code) {
+ sock.close();
+ isc_throw(Unexpected, "failed to connect to remote endpoint.");
+ }
+
+ // Once we are connected socket object holds local endpoint.
+ boost::asio::ip::udp::socket::endpoint_type local_endpoint =
+ sock.local_endpoint();
+ boost::asio::ip::address local_address(local_endpoint.address());
+
+ // Close the socket.
+ sock.close();
+
+ // Return address of local endpoint.
+ return IOAddress(local_address);
+}
+
+int
+IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr, const uint16_t port,
+ const bool receive_bcast, const bool send_bcast) {
+ // Assuming that packet filter is not null, because its modifier checks it.
+ SocketInfo info = packet_filter_->openSocket(iface, addr, port,
+ receive_bcast, send_bcast);
+ iface.addSocket(info);
+
+ return (info.sockfd_);
+}
+
+bool
+IfaceMgr::send(const Pkt6Ptr& pkt) {
+ IfacePtr iface = getIface(pkt);
+ if (!iface) {
+ isc_throw(BadValue, "Unable to send DHCPv6 message. Invalid interface ("
+ << pkt->getIface() << ") specified.");
+ }
+
+ // Assuming that packet filter is not null, because its modifier checks it.
+ // The packet filter returns an int but in fact it either returns 0 or throws.
+ return (packet_filter6_->send(*iface, getSocket(pkt), pkt) == 0);
+}
+
+bool
+IfaceMgr::send(const Pkt4Ptr& pkt) {
+ IfacePtr iface = getIface(pkt);
+ if (!iface) {
+ isc_throw(BadValue, "Unable to send DHCPv4 message. Invalid interface ("
+ << pkt->getIface() << ") specified.");
+ }
+
+ // Assuming that packet filter is not null, because its modifier checks it.
+ // The packet filter returns an int but in fact it either returns 0 or throws.
+ return (packet_filter_->send(*iface, getSocket(pkt).sockfd_, pkt) == 0);
+}
+
+Pkt4Ptr IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
+ if (isDHCPReceiverRunning()) {
+ return (receive4Indirect(timeout_sec, timeout_usec));
+ }
+
+ return (receive4Direct(timeout_sec, timeout_usec));
+}
+
+Pkt4Ptr IfaceMgr::receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
+ // Sanity check for microsecond timeout.
+ if (timeout_usec >= 1000000) {
+ isc_throw(BadValue, "fractional timeout must be shorter than"
+ " one million microseconds");
+ }
+
+ fd_set sockets;
+ int maxfd = 0;
+
+ FD_ZERO(&sockets);
+
+ // if there are any callbacks for external sockets registered...
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ if (!callbacks_.empty()) {
+ for (SocketCallbackInfo s : callbacks_) {
+ // Add this socket to listening set
+ addFDtoSet(s.socket_, maxfd, &sockets);
+ }
+ }
+ }
+
+ // Add Receiver ready watch socket
+ addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::READY), maxfd, &sockets);
+
+ // Add Receiver error watch socket
+ addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::ERROR), maxfd, &sockets);
+
+ // Set timeout for our next select() call. If there are
+ // no DHCP packets to read, then we'll wait for a finite
+ // amount of time for an IO event. Otherwise, we'll
+ // poll (timeout = 0 secs). We need to poll, even if
+ // DHCP packets are waiting so we don't starve external
+ // sockets under heavy DHCP load.
+ struct timeval select_timeout;
+ if (getPacketQueue4()->empty()) {
+ select_timeout.tv_sec = timeout_sec;
+ select_timeout.tv_usec = timeout_usec;
+ } else {
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 0;
+ }
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout);
+
+ if ((result == 0) && getPacketQueue4()->empty()) {
+ // nothing received and timeout has been reached
+ return (Pkt4Ptr());
+ } else if (result < 0) {
+ // In most cases we would like to know whether select() returned
+ // an error because of a signal being received or for some other
+ // reason. This is because DHCP servers use signals to trigger
+ // certain actions, like reconfiguration or graceful shutdown.
+ // By catching a dedicated exception the caller will know if the
+ // error returned by the function is due to the reception of the
+ // signal or for some other reason.
+ if (errno == EINTR) {
+ isc_throw(SignalInterruptOnSelect, strerror(errno));
+ } else if (errno == EBADF) {
+ int cnt = purgeBadSockets();
+ isc_throw(SocketReadError,
+ "SELECT interrupted by one invalid sockets, purged "
+ << cnt << " socket descriptors");
+ } else {
+ isc_throw(SocketReadError, strerror(errno));
+ }
+ }
+
+ // We only check external sockets if select detected an event.
+ if (result > 0) {
+ // Check for receiver thread read errors.
+ if (dhcp_receiver_->isReady(WatchedThread::ERROR)) {
+ string msg = dhcp_receiver_->getLastError();
+ dhcp_receiver_->clearReady(WatchedThread::ERROR);
+ isc_throw(SocketReadError, msg);
+ }
+
+ // Let's find out which external socket has the data
+ SocketCallbackInfo ex_sock;
+ bool found = false;
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ for (SocketCallbackInfo s : callbacks_) {
+ if (!FD_ISSET(s.socket_, &sockets)) {
+ continue;
+ }
+ found = true;
+
+ // something received over external socket
+ if (s.callback_) {
+ // Note the external socket to call its callback without
+ // the lock taken so it can be deleted.
+ ex_sock = s;
+ break;
+ }
+ }
+ }
+
+ if (ex_sock.callback_) {
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ ex_sock.callback_(ex_sock.socket_);
+ }
+ if (found) {
+ return (Pkt4Ptr());
+ }
+ }
+
+ // If we're here it should only be because there are DHCP packets waiting.
+ Pkt4Ptr pkt = getPacketQueue4()->dequeuePacket();
+ if (!pkt) {
+ dhcp_receiver_->clearReady(WatchedThread::READY);
+ }
+
+ return (pkt);
+}
+
+Pkt4Ptr IfaceMgr::receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
+ // Sanity check for microsecond timeout.
+ if (timeout_usec >= 1000000) {
+ isc_throw(BadValue, "fractional timeout must be shorter than"
+ " one million microseconds");
+ }
+ boost::scoped_ptr<SocketInfo> candidate;
+ fd_set sockets;
+ int maxfd = 0;
+
+ FD_ZERO(&sockets);
+
+ /// @todo: marginal performance optimization. We could create the set once
+ /// and then use its copy for select(). Please note that select() modifies
+ /// provided set to indicated which sockets have something to read.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ // Only deal with IPv4 addresses.
+ if (s.addr_.isV4()) {
+ // Add this socket to listening set
+ addFDtoSet(s.sockfd_, maxfd, &sockets);
+ }
+ }
+ }
+
+ // if there are any callbacks for external sockets registered...
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ if (!callbacks_.empty()) {
+ for (SocketCallbackInfo s : callbacks_) {
+ // Add this socket to listening set
+ addFDtoSet(s.socket_, maxfd, &sockets);
+ }
+ }
+ }
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = timeout_sec;
+ select_timeout.tv_usec = timeout_usec;
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout);
+
+ if (result == 0) {
+ // nothing received and timeout has been reached
+ return (Pkt4Ptr()); // null
+
+ } else if (result < 0) {
+ // In most cases we would like to know whether select() returned
+ // an error because of a signal being received or for some other
+ // reason. This is because DHCP servers use signals to trigger
+ // certain actions, like reconfiguration or graceful shutdown.
+ // By catching a dedicated exception the caller will know if the
+ // error returned by the function is due to the reception of the
+ // signal or for some other reason.
+ if (errno == EINTR) {
+ isc_throw(SignalInterruptOnSelect, strerror(errno));
+ } else if (errno == EBADF) {
+ int cnt = purgeBadSockets();
+ isc_throw(SocketReadError,
+ "SELECT interrupted by one invalid sockets, purged "
+ << cnt << " socket descriptors");
+ } else {
+ isc_throw(SocketReadError, strerror(errno));
+ }
+ }
+
+ // Let's find out which socket has the data
+ SocketCallbackInfo ex_sock;
+ bool found = false;
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ for (SocketCallbackInfo s : callbacks_) {
+ if (!FD_ISSET(s.socket_, &sockets)) {
+ continue;
+ }
+ found = true;
+
+ // something received over external socket
+ if (s.callback_) {
+ // Note the external socket to call its callback without
+ // the lock taken so it can be deleted.
+ ex_sock = s;
+ break;
+ }
+ }
+ }
+
+ if (ex_sock.callback_) {
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ ex_sock.callback_(ex_sock.socket_);
+ }
+ if (found) {
+ return (Pkt4Ptr());
+ }
+
+ // Let's find out which interface/socket has the data
+ IfacePtr recv_if;
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ if (FD_ISSET(s.sockfd_, &sockets)) {
+ candidate.reset(new SocketInfo(s));
+ break;
+ }
+ }
+ if (candidate) {
+ recv_if = iface;
+ break;
+ }
+ }
+
+ if (!candidate || !recv_if) {
+ isc_throw(SocketReadError, "received data over unknown socket");
+ }
+
+ // Now we have a socket, let's get some data from it!
+ // Assuming that packet filter is not null, because its modifier checks it.
+ return (packet_filter_->receive(*recv_if, *candidate));
+}
+
+Pkt6Ptr
+IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
+ if (isDHCPReceiverRunning()) {
+ return (receive6Indirect(timeout_sec, timeout_usec));
+ }
+
+ return (receive6Direct(timeout_sec, timeout_usec));
+}
+
+void
+IfaceMgr::addFDtoSet(int fd, int& maxfd, fd_set* sockets) {
+ if (!sockets) {
+ isc_throw(BadValue, "addFDtoSet: sockets can't be null");
+ }
+
+ FD_SET(fd, sockets);
+ if (maxfd < fd) {
+ maxfd = fd;
+ }
+}
+
+Pkt6Ptr
+IfaceMgr::receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
+ // Sanity check for microsecond timeout.
+ if (timeout_usec >= 1000000) {
+ isc_throw(BadValue, "fractional timeout must be shorter than"
+ " one million microseconds");
+ }
+
+ boost::scoped_ptr<SocketInfo> candidate;
+ fd_set sockets;
+ int maxfd = 0;
+
+ FD_ZERO(&sockets);
+
+ /// @todo: marginal performance optimization. We could create the set once
+ /// and then use its copy for select(). Please note that select() modifies
+ /// provided set to indicated which sockets have something to read.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ // Only deal with IPv6 addresses.
+ if (s.addr_.isV6()) {
+ // Add this socket to listening set
+ addFDtoSet(s.sockfd_, maxfd, &sockets);
+ }
+ }
+ }
+
+ // if there are any callbacks for external sockets registered...
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ if (!callbacks_.empty()) {
+ for (SocketCallbackInfo s : callbacks_) {
+ // Add this socket to listening set
+ addFDtoSet(s.socket_, maxfd, &sockets);
+ }
+ }
+ }
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = timeout_sec;
+ select_timeout.tv_usec = timeout_usec;
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout);
+
+ if (result == 0) {
+ // nothing received and timeout has been reached
+ return (Pkt6Ptr()); // null
+
+ } else if (result < 0) {
+ // In most cases we would like to know whether select() returned
+ // an error because of a signal being received or for some other
+ // reason. This is because DHCP servers use signals to trigger
+ // certain actions, like reconfiguration or graceful shutdown.
+ // By catching a dedicated exception the caller will know if the
+ // error returned by the function is due to the reception of the
+ // signal or for some other reason.
+ if (errno == EINTR) {
+ isc_throw(SignalInterruptOnSelect, strerror(errno));
+ } else if (errno == EBADF) {
+ int cnt = purgeBadSockets();
+ isc_throw(SocketReadError,
+ "SELECT interrupted by one invalid sockets, purged "
+ << cnt << " socket descriptors");
+ } else {
+ isc_throw(SocketReadError, strerror(errno));
+ }
+ }
+
+ // Let's find out which socket has the data
+ SocketCallbackInfo ex_sock;
+ bool found = false;
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ for (SocketCallbackInfo s : callbacks_) {
+ if (!FD_ISSET(s.socket_, &sockets)) {
+ continue;
+ }
+ found = true;
+
+ // something received over external socket
+ if (s.callback_) {
+ // Note the external socket to call its callback without
+ // the lock taken so it can be deleted.
+ ex_sock = s;
+ break;
+ }
+ }
+ }
+
+ if (ex_sock.callback_) {
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ ex_sock.callback_(ex_sock.socket_);
+ }
+ if (found) {
+ return (Pkt6Ptr());
+ }
+
+ // Let's find out which interface/socket has the data
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ if (FD_ISSET(s.sockfd_, &sockets)) {
+ candidate.reset(new SocketInfo(s));
+ break;
+ }
+ }
+ if (candidate) {
+ break;
+ }
+ }
+
+ if (!candidate) {
+ isc_throw(SocketReadError, "received data over unknown socket");
+ }
+ // Assuming that packet filter is not null, because its modifier checks it.
+ return (packet_filter6_->receive(*candidate));
+}
+
+Pkt6Ptr
+IfaceMgr::receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
+ // Sanity check for microsecond timeout.
+ if (timeout_usec >= 1000000) {
+ isc_throw(BadValue, "fractional timeout must be shorter than"
+ " one million microseconds");
+ }
+
+ fd_set sockets;
+ int maxfd = 0;
+
+ FD_ZERO(&sockets);
+
+ // if there are any callbacks for external sockets registered...
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ if (!callbacks_.empty()) {
+ for (SocketCallbackInfo s : callbacks_) {
+ // Add this socket to listening set
+ addFDtoSet(s.socket_, maxfd, &sockets);
+ }
+ }
+ }
+
+ // Add Receiver ready watch socket
+ addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::READY), maxfd, &sockets);
+
+ // Add Receiver error watch socket
+ addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::ERROR), maxfd, &sockets);
+
+ // Set timeout for our next select() call. If there are
+ // no DHCP packets to read, then we'll wait for a finite
+ // amount of time for an IO event. Otherwise, we'll
+ // poll (timeout = 0 secs). We need to poll, even if
+ // DHCP packets are waiting so we don't starve external
+ // sockets under heavy DHCP load.
+ struct timeval select_timeout;
+ if (getPacketQueue6()->empty()) {
+ select_timeout.tv_sec = timeout_sec;
+ select_timeout.tv_usec = timeout_usec;
+ } else {
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 0;
+ }
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ int result = select(maxfd + 1, &sockets, 0, 0, &select_timeout);
+
+ if ((result == 0) && getPacketQueue6()->empty()) {
+ // nothing received and timeout has been reached
+ return (Pkt6Ptr());
+ } else if (result < 0) {
+ // In most cases we would like to know whether select() returned
+ // an error because of a signal being received or for some other
+ // reason. This is because DHCP servers use signals to trigger
+ // certain actions, like reconfiguration or graceful shutdown.
+ // By catching a dedicated exception the caller will know if the
+ // error returned by the function is due to the reception of the
+ // signal or for some other reason.
+ if (errno == EINTR) {
+ isc_throw(SignalInterruptOnSelect, strerror(errno));
+ } else if (errno == EBADF) {
+ int cnt = purgeBadSockets();
+ isc_throw(SocketReadError,
+ "SELECT interrupted by one invalid sockets, purged "
+ << cnt << " socket descriptors");
+ } else {
+ isc_throw(SocketReadError, strerror(errno));
+ }
+ }
+
+ // We only check external sockets if select detected an event.
+ if (result > 0) {
+ // Check for receiver thread read errors.
+ if (dhcp_receiver_->isReady(WatchedThread::ERROR)) {
+ string msg = dhcp_receiver_->getLastError();
+ dhcp_receiver_->clearReady(WatchedThread::ERROR);
+ isc_throw(SocketReadError, msg);
+ }
+
+ // Let's find out which external socket has the data
+ SocketCallbackInfo ex_sock;
+ bool found = false;
+ {
+ std::lock_guard<std::mutex> lock(callbacks_mutex_);
+ for (SocketCallbackInfo s : callbacks_) {
+ if (!FD_ISSET(s.socket_, &sockets)) {
+ continue;
+ }
+ found = true;
+
+ // something received over external socket
+ if (s.callback_) {
+ // Note the external socket to call its callback without
+ // the lock taken so it can be deleted.
+ ex_sock = s;
+ break;
+ }
+ }
+ }
+
+ if (ex_sock.callback_) {
+ // Calling the external socket's callback provides its service
+ // layer access without integrating any specific features
+ // in IfaceMgr
+ ex_sock.callback_(ex_sock.socket_);
+ }
+ if (found) {
+ return (Pkt6Ptr());
+ }
+ }
+
+ // If we're here it should only be because there are DHCP packets waiting.
+ Pkt6Ptr pkt = getPacketQueue6()->dequeuePacket();
+ if (!pkt) {
+ dhcp_receiver_->clearReady(WatchedThread::READY);
+ }
+
+ return (pkt);
+}
+
+void
+IfaceMgr::receiveDHCP4Packets() {
+ fd_set sockets;
+ int maxfd = 0;
+
+ FD_ZERO(&sockets);
+
+ // Add terminate watch socket.
+ addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::TERMINATE), maxfd, &sockets);
+
+ // Add Interface sockets.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ // Only deal with IPv4 addresses.
+ if (s.addr_.isV4()) {
+ // Add this socket to listening set.
+ addFDtoSet(s.sockfd_, maxfd, &sockets);
+ }
+ }
+ }
+
+ for (;;) {
+ // Check the watch socket.
+ if (dhcp_receiver_->shouldTerminate()) {
+ return;
+ }
+
+ fd_set rd_set;
+ FD_COPY(&sockets, &rd_set);
+
+ // zero out the errno to be safe.
+ errno = 0;
+
+ // Select with null timeouts to wait indefinitely an event
+ int result = select(maxfd + 1, &rd_set, 0, 0, 0);
+
+ // Re-check the watch socket.
+ if (dhcp_receiver_->shouldTerminate()) {
+ return;
+ }
+
+ if (result == 0) {
+ // nothing received?
+ continue;
+
+ } else if (result < 0) {
+ // This thread should not get signals?
+ if (errno != EINTR) {
+ // Signal the error to receive4.
+ dhcp_receiver_->setError(strerror(errno));
+ // We need to sleep in case of the error condition to
+ // prevent the thread from tight looping when result
+ // gets negative.
+ sleep(1);
+ }
+ continue;
+ }
+
+ // Let's find out which interface/socket has data.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ if (FD_ISSET(s.sockfd_, &sockets)) {
+ receiveDHCP4Packet(*iface, s);
+ // Can take time so check one more time the watch socket.
+ if (dhcp_receiver_->shouldTerminate()) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+}
+
+void
+IfaceMgr::receiveDHCP6Packets() {
+ fd_set sockets;
+ int maxfd = 0;
+
+ FD_ZERO(&sockets);
+
+ // Add terminate watch socket.
+ addFDtoSet(dhcp_receiver_->getWatchFd(WatchedThread::TERMINATE), maxfd, &sockets);
+
+ // Add Interface sockets.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ // Only deal with IPv6 addresses.
+ if (s.addr_.isV6()) {
+ // Add this socket to listening set.
+ addFDtoSet(s.sockfd_ , maxfd, &sockets);
+ }
+ }
+ }
+
+ for (;;) {
+ // Check the watch socket.
+ if (dhcp_receiver_->shouldTerminate()) {
+ return;
+ }
+
+ fd_set rd_set;
+ FD_COPY(&sockets, &rd_set);
+
+ // zero out the errno to be safe.
+ errno = 0;
+
+ // Note we wait until something happen.
+ int result = select(maxfd + 1, &rd_set, 0, 0, 0);
+
+ // Re-check the watch socket.
+ if (dhcp_receiver_->shouldTerminate()) {
+ return;
+ }
+
+ if (result == 0) {
+ // nothing received?
+ continue;
+ } else if (result < 0) {
+ // This thread should not get signals?
+ if (errno != EINTR) {
+ // Signal the error to receive6.
+ dhcp_receiver_->setError(strerror(errno));
+ // We need to sleep in case of the error condition to
+ // prevent the thread from tight looping when result
+ // gets negative.
+ sleep(1);
+ }
+ continue;
+ }
+
+ // Let's find out which interface/socket has data.
+ for (IfacePtr iface : ifaces_) {
+ for (SocketInfo s : iface->getSockets()) {
+ if (FD_ISSET(s.sockfd_, &sockets)) {
+ receiveDHCP6Packet(s);
+ // Can take time so check one more time the watch socket.
+ if (dhcp_receiver_->shouldTerminate()) {
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+IfaceMgr::receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info) {
+ int len;
+
+ int result = ioctl(socket_info.sockfd_, FIONREAD, &len);
+ if (result < 0) {
+ // Signal the error to receive4.
+ dhcp_receiver_->setError(strerror(errno));
+ return;
+ }
+ if (len == 0) {
+ // Nothing to read.
+ return;
+ }
+
+ Pkt4Ptr pkt;
+
+ try {
+ pkt = packet_filter_->receive(iface, socket_info);
+ } catch (const std::exception& ex) {
+ dhcp_receiver_->setError(strerror(errno));
+ } catch (...) {
+ dhcp_receiver_->setError("packet filter receive() failed");
+ }
+
+ if (pkt) {
+ getPacketQueue4()->enqueuePacket(pkt, socket_info);
+ dhcp_receiver_->markReady(WatchedThread::READY);
+ }
+}
+
+void
+IfaceMgr::receiveDHCP6Packet(const SocketInfo& socket_info) {
+ int len;
+
+ int result = ioctl(socket_info.sockfd_, FIONREAD, &len);
+ if (result < 0) {
+ // Signal the error to receive6.
+ dhcp_receiver_->setError(strerror(errno));
+ return;
+ }
+ if (len == 0) {
+ // Nothing to read.
+ return;
+ }
+
+ Pkt6Ptr pkt;
+
+ try {
+ pkt = packet_filter6_->receive(socket_info);
+ } catch (const std::exception& ex) {
+ dhcp_receiver_->setError(ex.what());
+ } catch (...) {
+ dhcp_receiver_->setError("packet filter receive() failed");
+ }
+
+ if (pkt) {
+ getPacketQueue6()->enqueuePacket(pkt, socket_info);
+ dhcp_receiver_->markReady(WatchedThread::READY);
+ }
+}
+
+uint16_t
+IfaceMgr::getSocket(const isc::dhcp::Pkt6Ptr& pkt) {
+ IfacePtr iface = getIface(pkt);
+ if (!iface) {
+ isc_throw(IfaceNotFound, "Tried to find socket for non-existent interface");
+ }
+
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+
+ Iface::SocketCollection::const_iterator candidate = socket_collection.end();
+
+ Iface::SocketCollection::const_iterator s;
+ for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
+
+ // We should not merge those conditions for debugging reasons.
+
+ // V4 sockets are useless for sending v6 packets.
+ if (s->family_ != AF_INET6) {
+ continue;
+ }
+
+ // Sockets bound to multicast address are useless for sending anything.
+ if (s->addr_.isV6Multicast()) {
+ continue;
+ }
+
+ if (s->addr_ == pkt->getLocalAddr()) {
+ // This socket is bound to the source address. This is perfect
+ // match, no need to look any further.
+ return (s->sockfd_);
+ }
+
+ // If we don't have any other candidate, this one will do
+ if (candidate == socket_collection.end()) {
+ candidate = s;
+ } else {
+ // If we want to send something to link-local and the socket is
+ // bound to link-local or we want to send to global and the socket
+ // is bound to global, then use it as candidate
+ if ( (pkt->getRemoteAddr().isV6LinkLocal() &&
+ s->addr_.isV6LinkLocal()) ||
+ (!pkt->getRemoteAddr().isV6LinkLocal() &&
+ !s->addr_.isV6LinkLocal()) ) {
+ candidate = s;
+ }
+ }
+ }
+
+ if (candidate != socket_collection.end()) {
+ return (candidate->sockfd_);
+ }
+
+ isc_throw(SocketNotFound, "Interface " << iface->getFullName()
+ << " does not have any suitable IPv6 sockets open.");
+}
+
+SocketInfo
+IfaceMgr::getSocket(const isc::dhcp::Pkt4Ptr& pkt) {
+ IfacePtr iface = getIface(pkt);
+ if (!iface) {
+ isc_throw(IfaceNotFound, "Tried to find socket for non-existent interface");
+ }
+
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ // A candidate being an end of the iterator marks that it is a beginning of
+ // the socket search and that the candidate needs to be set to the first
+ // socket found.
+ Iface::SocketCollection::const_iterator candidate = socket_collection.end();
+ Iface::SocketCollection::const_iterator s;
+ for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
+ if (s->family_ == AF_INET) {
+ if (s->addr_ == pkt->getLocalAddr()) {
+ return (*s);
+ }
+
+ if (candidate == socket_collection.end()) {
+ candidate = s;
+ }
+ }
+ }
+
+ if (candidate == socket_collection.end()) {
+ isc_throw(SocketNotFound, "Interface " << iface->getFullName()
+ << " does not have any suitable IPv4 sockets open.");
+ }
+
+ return (*candidate);
+}
+
+bool
+IfaceMgr::configureDHCPPacketQueue(uint16_t family, data::ConstElementPtr queue_control) {
+ if (isDHCPReceiverRunning()) {
+ isc_throw(InvalidOperation, "Cannot reconfigure queueing"
+ " while DHCP receiver thread is running");
+ }
+
+ bool enable_queue = false;
+ if (queue_control) {
+ try {
+ enable_queue = data::SimpleParser::getBoolean(queue_control, "enable-queue");
+ } catch (...) {
+ // @todo - for now swallow not found errors.
+ // if not present we assume default
+ }
+ }
+
+ if (enable_queue) {
+ // Try to create the queue as configured.
+ if (family == AF_INET) {
+ packet_queue_mgr4_->createPacketQueue(queue_control);
+ } else {
+ packet_queue_mgr6_->createPacketQueue(queue_control);
+ }
+ } else {
+ // Destroy the current queue (if one), this inherently disables threading.
+ if (family == AF_INET) {
+ packet_queue_mgr4_->destroyPacketQueue();
+ } else {
+ packet_queue_mgr6_->destroyPacketQueue();
+ }
+ }
+
+ return (enable_queue);
+}
+
+void
+Iface::addError(std::string const& message) {
+ errors_.push_back(message);
+}
+
+void
+Iface::clearErrors() {
+ errors_.clear();
+}
+
+Iface::ErrorBuffer const&
+Iface::getErrors() const {
+ return errors_;
+}
+
+bool
+IfaceMgr::checkDetectIfaces(bool update_only) {
+ if (test_mode_ && update_only) {
+ return (false);
+ }
+ return (true);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
new file mode 100644
index 0000000..ca0a536
--- /dev/null
+++ b/src/lib/dhcp/iface_mgr.h
@@ -0,0 +1,1628 @@
+// Copyright (C) 2011-2023 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 IFACE_MGR_H
+#define IFACE_MGR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/packet_queue_mgr4.h>
+#include <dhcp/packet_queue_mgr6.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter6.h>
+#include <util/optional.h>
+#include <util/watch_socket.h>
+#include <util/watched_thread.h>
+
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <functional>
+#include <list>
+#include <vector>
+#include <mutex>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief IfaceMgr exception thrown thrown when interface detection fails.
+class IfaceDetectError : public Exception {
+public:
+ IfaceDetectError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when it is not allowed to set new Packet Filter.
+class PacketFilterChangeDenied : public Exception {
+public:
+ PacketFilterChangeDenied(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when a call to select is interrupted by a signal.
+class SignalInterruptOnSelect : public Exception {
+public:
+ SignalInterruptOnSelect(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief IfaceMgr exception thrown thrown when socket opening
+/// or configuration failed.
+class SocketConfigError : public Exception {
+public:
+ SocketConfigError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief IfaceMgr exception thrown thrown when error occurred during
+/// reading data from socket.
+class SocketReadError : public Exception {
+public:
+ SocketReadError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief IfaceMgr exception thrown thrown when error occurred during
+/// sending data through socket.
+class SocketWriteError : public Exception {
+public:
+ SocketWriteError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief IfaceMgr exception thrown when there is no suitable interface.
+class IfaceNotFound : public Exception {
+public:
+ IfaceNotFound(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief IfaceMgr exception thrown when there is no suitable socket found.
+class SocketNotFound : public Exception {
+public:
+ SocketNotFound(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a single network interface
+///
+/// Iface structure represents network interface with all useful
+/// information, like name, interface index, MAC address and
+/// list of assigned addresses
+///
+/// This class also holds the pointer to the socket read buffer.
+/// Functions reading from the socket may utilize this buffer to store the
+/// data being read from the socket. The advantage of using the
+/// pre-allocated buffer is that the buffer is allocated only once, rather
+/// than on every read. In addition, some OS specific code (e.g. BPF)
+/// may require use of fixed-size buffers. The size of such a buffer is
+/// returned by the OS kernel when the socket is opened. Hence, it is
+/// convenient to allocate the buffer when the socket is being opened and
+/// utilize it throughout the lifetime of the socket.
+///
+/// In order to avoid potentially expensive copies of the @c Iface objects
+/// holding pre-allocated buffers and multiple containers, this class is
+/// noncopyable.
+class Iface : public boost::noncopyable {
+public:
+
+ /// Maximum MAC address length (Infiniband uses 20 bytes)
+ static const unsigned int MAX_MAC_LEN = 20;
+
+ /// @brief Address type.
+ typedef util::Optional<asiolink::IOAddress> Address;
+
+ /// Type that defines list of addresses
+ typedef std::list<Address> AddressCollection;
+
+ /// @brief Type that holds a list of socket information.
+ ///
+ /// @warning The type of the container used here must guarantee
+ /// that the iterators do not invalidate when erase() is called.
+ /// This is because, the \ref closeSockets function removes
+ /// elements selectively by calling erase on the element to be
+ /// removed and further iterates through remaining elements.
+ ///
+ /// @todo: Add SocketCollectionConstIter type
+ typedef std::list<SocketInfo> SocketCollection;
+
+ /// @brief Type definition for a list of error messages
+ using ErrorBuffer = std::vector<std::string>;
+
+ /// @brief Iface constructor.
+ ///
+ /// Creates Iface object that represents network interface.
+ ///
+ /// @param name name of the interface
+ /// @param ifindex interface index (unique integer identifier)
+ /// @throw BadValue when name is empty.
+ Iface(const std::string& name, unsigned int ifindex);
+
+ /// @brief Destructor.
+ ~Iface() { }
+
+ /// @brief Closes all open sockets on interface.
+ void closeSockets();
+
+ /// @brief Closes all IPv4 or IPv6 sockets.
+ ///
+ /// This function closes sockets of the specific 'type' and closes them.
+ /// The 'type' of the socket indicates whether it is used to send IPv4
+ /// or IPv6 packets. The allowed values of the parameter are AF_INET and
+ /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
+ /// to realize that the actual types of sockets may be different than
+ /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
+ /// always used AF_INET sockets for IPv4 traffic. This is no longer the
+ /// case when the Direct IPv4 traffic must be supported. In order to support
+ /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
+ /// family sockets on Linux.
+ ///
+ /// @todo Replace the AF_INET and AF_INET6 values with an enum
+ /// which will not be confused with the actual socket type.
+ ///
+ /// @param family type of the sockets to be closed (AF_INET or AF_INET6)
+ ///
+ /// @throw BadValue if family value is different than AF_INET or AF_INET6.
+ void closeSockets(const uint16_t family);
+
+ /// @brief Returns full interface name as "ifname/ifindex" string.
+ ///
+ /// @return string with interface name
+ std::string getFullName() const;
+
+ /// @brief Returns link-layer address a plain text.
+ ///
+ /// @return MAC address as a plain text (string)
+ std::string getPlainMac() const;
+
+ /// @brief Sets MAC address of the interface.
+ ///
+ /// @param mac pointer to MAC address buffer
+ /// @param macLen length of mac address
+ void setMac(const uint8_t* mac, size_t macLen);
+
+ /// @brief Returns MAC length.
+ ///
+ /// @return length of MAC address
+ size_t getMacLen() const { return mac_len_; }
+
+ /// @brief Returns pointer to MAC address.
+ ///
+ /// Note: Returned pointer is only valid as long as the interface object
+ /// that returned it.
+ const uint8_t* getMac() const { return mac_; }
+
+ /// @brief Sets flag_*_ fields based on bitmask value returned by OS
+ ///
+ /// @note Implementation of this method is OS-dependent as bits have
+ /// different meaning on each OS.
+ /// We need to make it 64 bits, because Solaris uses 64, not 32 bits.
+ ///
+ /// @param flags bitmask value returned by OS in interface detection
+ void setFlags(uint64_t flags);
+
+ /// @brief Returns interface index.
+ ///
+ /// @return interface index
+ unsigned int getIndex() const { return ifindex_; }
+
+ /// @brief Returns interface name.
+ ///
+ /// @return interface name
+ std::string getName() const { return name_; };
+
+ /// @brief Sets up hardware type of the interface.
+ ///
+ /// @param type hardware type
+ void setHWType(uint16_t type ) { hardware_type_ = type; }
+
+ /// @brief Returns hardware type of the interface.
+ ///
+ /// @return hardware type
+ uint16_t getHWType() const { return hardware_type_; }
+
+ /// @brief Returns all addresses available on an interface.
+ ///
+ /// The returned addresses are encapsulated in the @c util::Optional
+ /// class to be able to selectively flag some of the addresses as active
+ /// (when optional value is specified) or inactive (when optional value
+ /// is specified). If the address is marked as active, the
+ /// @c IfaceMgr::openSockets4 method will open socket and bind to this
+ /// address. Otherwise, it will not bind any socket to this address.
+ /// This is useful when an interface has multiple IPv4 addresses assigned.
+ ///
+ /// Care should be taken to not use this collection after Iface object
+ /// ceases to exist. That is easy in most cases as Iface objects are
+ /// created by IfaceMgr that is a singleton an is expected to be
+ /// available at all time. We may revisit this if we ever decide to
+ /// implement dynamic interface detection, but such fancy feature would
+ /// mostly be useful for clients with wifi/vpn/virtual interfaces.
+ ///
+ /// @return collection of addresses
+ const AddressCollection& getAddresses() const { return addrs_; }
+
+ /// @brief Returns IPv4 address assigned to the interface.
+ ///
+ /// This function looks for an IPv4 address assigned to the interface
+ /// and returns it through the argument.
+ ///
+ /// @param [out] address IPv4 address assigned to the interface.
+ ///
+ /// @return Boolean value which informs whether IPv4 address has been found
+ /// for the interface (if true), or not (false).
+ bool getAddress4(isc::asiolink::IOAddress& address) const;
+
+ /// @brief Check if the interface has the specified address assigned.
+ ///
+ /// @param address Address to be checked.
+ /// @return true if address is assigned to the interface, false otherwise.
+ bool hasAddress(const isc::asiolink::IOAddress& address) const;
+
+ /// @brief Adds an address to an interface.
+ ///
+ /// This only adds an address to collection, it does not physically
+ /// configure address on actual network interface.
+ ///
+ /// @param addr address to be added
+ void addAddress(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Activates or deactivates address for the interface.
+ ///
+ /// This method marks a specified address on the interface active or
+ /// inactive. If the address is marked inactive, the
+ /// @c IfaceMgr::openSockets4 method will NOT open socket for this address.
+ ///
+ /// @param address An address which should be activated, deactivated.
+ /// @param active A boolean flag which indicates that the specified address
+ /// should be active (if true) or inactive (if false).
+ ///
+ /// @throw BadValue if specified address doesn't exist for the interface.
+ void setActive(const isc::asiolink::IOAddress& address, const bool active);
+
+ /// @brief Activates or deactivates all addresses for the interface.
+ ///
+ /// This method marks all addresses on the interface active or inactive.
+ /// If the address is marked inactive, the @c IfaceMgr::openSockets4
+ /// method will NOT open socket for this address.
+ ///
+ /// @param active A boolean flag which indicates that the addresses
+ /// should be active (if true) or inactive (if false).
+ void setActive(const bool active);
+
+ /// @brief Returns a number of activated IPv4 addresses on the interface.
+ unsigned int countActive4() const;
+
+ /// @brief Deletes an address from an interface.
+ ///
+ /// This only deletes address from collection, it does not physically
+ /// remove address configuration from actual network interface.
+ ///
+ /// @param addr address to be removed.
+ ///
+ /// @return true if removal was successful (address was in collection),
+ /// false otherwise
+ bool delAddress(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Adds socket descriptor to an interface.
+ ///
+ /// @param sock SocketInfo structure that describes socket.
+ void addSocket(const SocketInfo& sock) {
+ sockets_.push_back(sock);
+ }
+
+ /// @brief Closes socket.
+ ///
+ /// Closes socket and removes corresponding SocketInfo structure
+ /// from an interface.
+ ///
+ /// @param sockfd socket descriptor to be closed/removed.
+ /// @return true if there was such socket, false otherwise
+ bool delSocket(uint16_t sockfd);
+
+ /// @brief Returns collection of all sockets added to interface.
+ ///
+ /// When new socket is created with @ref IfaceMgr::openSocket
+ /// it is added to sockets collection on particular interface.
+ /// If socket is opened by other means (e.g. function that does
+ /// not use @ref IfaceMgr::openSocket) it will not be available
+ /// in this collection. Note that functions like
+ /// @ref IfaceMgr::openSocketFromIface use
+ /// @ref IfaceMgr::openSocket internally.
+ /// The returned reference is only valid during the lifetime of
+ /// the IfaceMgr object that returned it.
+ ///
+ /// @return collection of sockets added to interface
+ const SocketCollection& getSockets() const { return sockets_; }
+
+ /// @brief Removes any unicast addresses
+ ///
+ /// Removes any unicast addresses that the server was configured to
+ /// listen on
+ void clearUnicasts() {
+ unicasts_.clear();
+ }
+
+ /// @brief Adds unicast the server should listen on
+ ///
+ /// @throw BadValue if specified address is already defined on interface
+ /// @param addr unicast address to listen on
+ void addUnicast(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Returns a container of addresses the server should listen on
+ ///
+ /// @return address collection (may be empty)
+ const AddressCollection& getUnicasts() const {
+ return unicasts_;
+ }
+
+ /// @brief Returns the pointer to the buffer used for data reading.
+ ///
+ /// The returned pointer is only valid during the lifetime of the
+ /// object which returns it or until the buffer is resized.
+ /// This function is meant to be used with socket API to gather
+ /// data from the socket.
+ ///
+ /// @return Pointer to the first element of the read buffer or
+ /// NULL if the buffer is empty.
+ uint8_t* getReadBuffer() {
+ if (read_buffer_.empty()) {
+ return (0);
+ }
+ return (&read_buffer_[0]);
+ }
+
+ /// @brief Returns the current size of the socket read buffer.
+ size_t getReadBufferSize() const {
+ return (read_buffer_.size());
+ }
+
+ /// @brief Reallocates the socket read buffer.
+ ///
+ /// @param new_size New size of the buffer.
+ void resizeReadBuffer(const size_t new_size) {
+ read_buffer_.resize(new_size);
+ }
+
+ /// @brief Add an error to the list of messages.
+ ///
+ /// @param message the error message
+ void addError(std::string const& message);
+
+ /// @brief Clears all errors.
+ void clearErrors();
+
+ /// @brief Get the consistent list of error messages.
+ ///
+ /// @return the list of messages
+ ErrorBuffer const& getErrors() const;
+
+protected:
+ /// Socket used to send data.
+ SocketCollection sockets_;
+
+ /// Network interface name.
+ std::string name_;
+
+ /// Interface index (a value that uniquely identifies an interface).
+ unsigned int ifindex_;
+
+ /// List of assigned addresses.
+ AddressCollection addrs_;
+
+ /// List of unicast addresses the server should listen on
+ AddressCollection unicasts_;
+
+ /// Link-layer address.
+ uint8_t mac_[MAX_MAC_LEN];
+
+ /// Length of link-layer address (usually 6).
+ size_t mac_len_;
+
+ /// Hardware type.
+ uint16_t hardware_type_;
+
+public:
+ /// @todo: Make those fields protected once we start supporting more
+ /// than just Linux
+
+ /// Specifies if selected interface is loopback.
+ bool flag_loopback_;
+
+ /// Specifies if selected interface is up.
+ bool flag_up_;
+
+ /// Flag specifies if selected interface is running
+ /// (e.g. cable plugged in, wifi associated).
+ bool flag_running_;
+
+ /// Flag specifies if selected interface is multicast capable.
+ bool flag_multicast_;
+
+ /// Flag specifies if selected interface is broadcast capable.
+ bool flag_broadcast_;
+
+ /// Interface flags (this value is as is returned by OS,
+ /// it may mean different things on different OSes).
+ /// Solaris based os have unsigned long flags field (64 bits).
+ /// It is usually 32 bits, though.
+ uint64_t flags_;
+
+ /// Indicates that IPv4 sockets should (true) or should not (false)
+ /// be opened on this interface.
+ bool inactive4_;
+
+ /// Indicates that IPv6 sockets should (true) or should not (false)
+ /// be opened on this interface.
+ bool inactive6_;
+
+private:
+
+ /// @brief The buffer holding the data read from the socket.
+ ///
+ /// See @c Iface manager description for details.
+ std::vector<uint8_t> read_buffer_;
+
+ /// @brief List of errors that occurred since the last attempt to open sockets
+ ///
+ /// This list needs to always have a consistent view of the errors. They should all belong to
+ /// the same session of socket opening i.e. the same call to openSockets[46]. This is currently
+ /// ensured by openSockets[46] and all the places where these errors are being used i.e. the
+ /// status-get handler, being sequential.
+ ErrorBuffer errors_;
+};
+
+/// @brief Type definition for the pointer to an @c Iface object.
+typedef boost::shared_ptr<Iface> IfacePtr;
+
+/// @brief Collection of pointers to network interfaces.
+class IfaceCollection {
+public:
+
+ /// @brief Multi index container for network interfaces.
+ ///
+ /// This container allows to search for a network interfaces using
+ /// three indexes:
+ /// - sequenced: used to access elements in the order they have
+ /// been added to the container.
+ /// - interface index: used to access an interface using its index.
+ /// - interface name: used to access an interface using its name.
+ /// Note that indexes and names are unique.
+ typedef boost::multi_index_container<
+ // Container comprises elements of IfacePtr type.
+ IfacePtr,
+ // Here we start enumerating various indexes.
+ boost::multi_index::indexed_by<
+ // Sequenced index allows accessing elements in the same way
+ // as elements in std::list. Sequenced is the index #0.
+ boost::multi_index::sequenced<>,
+ // Start definition of index #1.
+ boost::multi_index::hashed_unique<
+ // Use the interface index as the key.
+ boost::multi_index::const_mem_fun<
+ Iface, unsigned int, &Iface::getIndex
+ >
+ >,
+ // Start definition of index #2.
+ boost::multi_index::hashed_unique<
+ // Use the interface name as the key.
+ boost::multi_index::const_mem_fun<
+ Iface, std::string, &Iface::getName
+ >
+ >
+ >
+ > IfaceContainer;
+
+ /// @brief Begin iterator.
+ ///
+ /// @return The container sequence begin iterator.
+ IfaceContainer::const_iterator begin() const {
+ return (ifaces_container_.begin());
+ }
+
+ /// @brief End iterator.
+ ///
+ /// @return The container sequence end iterator.
+ IfaceContainer::const_iterator end() const {
+ return (ifaces_container_.end());
+ }
+
+ /// @brief Empty predicate.
+ ///
+ /// @return If the container is empty true else false.
+ bool empty() const {
+ return (ifaces_container_.empty());
+ }
+
+ /// @brief Return the number of interfaces.
+ ///
+ /// @return The container size.
+ size_t size() const {
+ return (ifaces_container_.size());
+ }
+
+ /// @brief Clear the collection.
+ void clear() {
+ cache_.reset();
+ ifaces_container_.clear();
+ }
+
+ /// @brief Adds an interface to the collection.
+ ///
+ /// The interface is added at the end of sequence.
+ ///
+ /// @param iface reference to Iface object.
+ void push_back(const IfacePtr& iface) {
+ ifaces_container_.push_back(iface);
+ }
+
+ /// @brief Lookup by interface index.
+ ///
+ /// @param ifindex The index of the interface to find.
+ /// @return The interface with the index or null.
+ IfacePtr getIface(const unsigned int ifindex);
+
+ /// @brief Lookup by interface name.
+ ///
+ /// @param ifname The name of the interface to find.
+ /// @return The interface with the name or null.
+ IfacePtr getIface(const std::string& ifname);
+
+private:
+ /// @brief Lookup by interface index.
+ ///
+ /// @param ifindex The index of the interface to find.
+ /// @param need_lock True when the cache operation needs to hold the mutex.
+ /// @return The interface with the index or null.
+ IfacePtr getIfaceInternal(const unsigned int ifindex, const bool need_lock);
+
+ /// @brief Lookup by interface name.
+ ///
+ /// The mutex must be held when called from a packet processing thread.
+ ///
+ /// @param ifname The name of the interface to find.
+ /// @param need_lock True when the cache operation needs to hold the mutex.
+ /// @return The interface with the name or null.
+ IfacePtr getIfaceInternal(const std::string& ifname, const bool need_lock);
+
+ /// @brief The mutex for protecting the cache from concurrent
+ /// access from packet processing threads.
+ std::mutex mutex_;
+
+ /// @brief The last interface returned by a lookup method.
+ ///
+ /// A lookup method first tries the cache: if it matches the cache is
+ /// returned without an expensive lookup in the container. If it does
+ /// not match and a value is found in the container the cache is
+ /// updated with this value.
+ /// The cache should perform well when active interfaces are a small
+ /// subset of the whole interface set, or when consecutive packets
+ /// come from the same interface.
+ IfacePtr cache_;
+
+ /// @brief The container.
+ IfaceContainer ifaces_container_;
+};
+
+/// @brief Type definition for the unordered set of IPv4 bound addresses.
+///
+/// In order to make @c hasOpenSocket with an IPv4 address faster bound
+/// addresses should be collected after calling @c CfgIface::openSockets.
+typedef boost::multi_index_container<
+ /// Container comprises elements of asiolink::IOAddress type.
+ asiolink::IOAddress,
+ // Here we start enumerating the only index.
+ boost::multi_index::indexed_by<
+ // Start definition of index #0.
+ boost::multi_index::hashed_unique<
+ // Use the address in its network order integer form as the key.
+ boost::multi_index::const_mem_fun<
+ asiolink::IOAddress, uint32_t, &asiolink::IOAddress::toUint32
+ >
+ >
+ >
+> BoundAddresses;
+
+/// @brief Forward declaration to the @c IfaceMgr.
+class IfaceMgr;
+
+/// @brief Type definition for the pointer to the @c IfaceMgr.
+typedef boost::shared_ptr<IfaceMgr> IfaceMgrPtr;
+
+/// @brief This type describes the callback function invoked when error occurs
+/// in the IfaceMgr.
+///
+/// @param errmsg An error message.
+typedef
+std::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback;
+
+/// @brief Handles network interfaces, transmission and reception.
+///
+/// IfaceMgr is an interface manager class that detects available network
+/// interfaces, configured addresses, link-local addresses, and provides
+/// API for using sockets.
+///
+class IfaceMgr : public boost::noncopyable {
+public:
+ /// @brief Defines callback used when data is received over external sockets.
+ ///
+ /// @param fd socket descriptor of the ready socket
+ typedef std::function<void (int fd)> SocketCallback;
+
+ /// @brief Defines callback used when detecting interfaces.
+ ///
+ /// @param update_only Only add interfaces that do not exist and update
+ /// existing interfaces.
+ ///
+ /// @return true if callback exited with no issue and @ref detectIfaces
+ /// should continue with specific system calls, false otherwise.
+ typedef std::function<bool (bool)> DetectCallback;
+
+ /// Keeps callback information for external sockets.
+ struct SocketCallbackInfo {
+ /// Socket descriptor of the external socket.
+ int socket_;
+
+ /// A callback that will be called when data arrives over socket_.
+ SocketCallback callback_;
+ };
+
+ /// Defines storage container for callbacks for external sockets
+ typedef std::list<SocketCallbackInfo> SocketCallbackInfoContainer;
+
+ /// @brief Packet reception buffer size
+ ///
+ /// RFC 8415 states that server responses may be
+ /// fragmented if they are over MTU. There is no
+ /// text whether client's packets may be larger
+ /// than 1500. For now, we can assume that
+ /// we don't support packets larger than 1500.
+ static const uint32_t RCVBUFSIZE = 1500;
+
+ /// IfaceMgr is a singleton class. This method returns reference
+ /// to its sole instance.
+ ///
+ /// @return the only existing instance of interface manager
+ static IfaceMgr& instance();
+
+ /// @brief Returns pointer to the sole instance of the interface manager.
+ ///
+ /// This method returns the pointer to the instance of the interface manager
+ /// which can be held in singleton objects that depend on it. This will
+ /// eliminate issues with the static deinitialization fiasco between this
+ /// object and dependent singleton objects.
+ ///
+ /// The @c IfaceMgr::instance method should be considered deprecated.
+ ///
+ /// @return Shared pointer to the @c IfaceMgr instance.
+ static const IfaceMgrPtr& instancePtr();
+
+ /// @brief Destructor.
+ ///
+ /// Closes open sockets.
+ virtual ~IfaceMgr();
+
+ /// @brief Sets or clears the test mode for @c IfaceMgr.
+ ///
+ /// Various unit test may set this flag to true, to indicate that the
+ /// @c IfaceMgr is in the test mode. There are places in the code that
+ /// modify the behavior depending if the @c IfaceMgr is in the test
+ /// mode or not.
+ ///
+ /// @param test_mode A flag which indicates that the @c IfaceMgr is in the
+ /// test mode (if true), or not (if false).
+ void setTestMode(const bool test_mode) {
+ test_mode_ = test_mode;
+ }
+
+ /// @brief Checks if the @c IfaceMgr is in the test mode.
+ ///
+ /// @return true if the @c IfaceMgr is in the test mode, false otherwise.
+ bool isTestMode() const {
+ return (test_mode_);
+ }
+
+ /// @brief Allows or disallows the loopback interface
+ ///
+ /// By default the loopback interface is not considered when opening
+ /// sockets. This flag provides a way to relax this constraint.
+ ///
+ void setAllowLoopBack(const bool allow_loopback) {
+ allow_loopback_ = allow_loopback;
+ }
+
+ /// @brief Check if packet be sent directly to the client having no address.
+ ///
+ /// Checks if IfaceMgr can send DHCPv4 packet to the client
+ /// who hasn't got address assigned. If this is not supported
+ /// broadcast address should be used to send response to
+ /// the client.
+ ///
+ /// @return true if direct response is supported.
+ bool isDirectResponseSupported() const;
+
+ /// @brief Returns interface specified interface index
+ ///
+ /// @param ifindex index of searched interface
+ ///
+ /// @return interface with requested index (or null if no such
+ /// interface is present)
+ ///
+ IfacePtr getIface(const unsigned int ifindex);
+
+ /// @brief Returns interface with specified interface name
+ ///
+ /// @param ifname name of searched interface
+ ///
+ /// @return interface with requested name (or null if no such
+ /// interface is present)
+ IfacePtr getIface(const std::string& ifname);
+
+ /// @brief Returns interface with specified packet
+ ///
+ /// @note When the interface index is set (@c Pkt::indexSet
+ /// returns true) it is searched for, if it is not set
+ /// the name instead is searched for.
+ ///
+ /// @param pkt packet with interface index and name
+ ///
+ /// @return interface with packet interface index or name
+ /// (or null if no such interface is present)
+ IfacePtr getIface(const PktPtr& pkt);
+
+ /// @brief Returns container with all interfaces.
+ ///
+ /// This reference is only valid as long as IfaceMgr is valid. However,
+ /// since IfaceMgr is a singleton and is expected to be destroyed after
+ /// main() function completes, you should not worry much about this.
+ ///
+ /// @return container with all interfaces.
+ const IfaceCollection& getIfaces() { return (ifaces_); }
+
+ /// @brief Removes detected interfaces.
+ ///
+ /// This method removes all detected interfaces. This method should be
+ /// used by unit tests to supply a custom set of interfaces. For example:
+ /// a unit test may create a pool of fake interfaces and use the custom
+ /// @c PktFilter class to mimic socket operation on these interfaces.
+ void clearIfaces();
+
+ /// @brief Set a callback to perform operations before executing specific
+ /// system calls.
+ ///
+ /// @param cb The callback used before executing specific system calls.
+ void setDetectCallback(const DetectCallback& cb) {
+ detect_callback_ = cb;
+ }
+
+ /// @brief Check if the specific system calls used to detect interfaces
+ /// should be executed.
+ ///
+ /// @param update_only Only add interfaces that do not exist and update
+ /// existing interfaces.
+ ///
+ /// @return true if the specific system calls should be executed, false
+ /// otherwise causing the @ref detectIfaces to return immediately.
+ bool checkDetectIfaces(bool update_only);
+
+ /// @brief Detects network interfaces.
+ ///
+ /// If the @ref detect_callback_ returns true, the specific system calls are
+ /// executed, otherwise the @ref detectIfaces will return immediately.
+ ///
+ /// @param update_only Only add interfaces that do not exist and update
+ /// existing interfaces.
+ void detectIfaces(bool update_only = false);
+
+ /// @brief Clears unicast addresses on all interfaces.
+ void clearUnicasts();
+
+ /// @brief Clears the addresses all sockets are bound to.
+ void clearBoundAddresses();
+
+ /// @brief Collect the addresses all sockets are bound to.
+ void collectBoundAddresses();
+
+ /// @brief Return most suitable socket for transmitting specified IPv6 packet.
+ ///
+ /// This method takes Pkt6Ptr (see overloaded implementation that takes
+ /// Pkt4Ptr) and chooses appropriate socket to send it. This method
+ /// may throw if specified packet does not have outbound interface specified,
+ /// no such interface exists, or specified interface does not have any
+ /// appropriate sockets open.
+ ///
+ /// @param pkt a packet to be transmitted
+ ///
+ /// @return a socket descriptor
+ /// @throw SocketNotFound If no suitable socket found.
+ /// @throw IfaceNotFound If interface is not set for the packet.
+ uint16_t getSocket(const isc::dhcp::Pkt6Ptr& pkt);
+
+ /// @brief Return most suitable socket for transmitting specified IPv4 packet.
+ ///
+ /// This method uses the local address assigned to the packet and tries
+ /// to match it with addresses to which sockets are bound for the particular
+ /// interface. If the match is not found, the method returns the first IPv4
+ /// socket found for the particular interface. In case, there are no IPv4
+ /// sockets assigned to the interface the exception is thrown.
+ ///
+ /// @param pkt A packet to be transmitted. It must hold a local address and
+ /// a valid pointer to the interface.
+ ///
+ /// @return A structure describing a socket.
+ /// @throw SocketNotFound if no suitable socket found.
+ SocketInfo getSocket(const isc::dhcp::Pkt4Ptr& pkt);
+
+ /// Debugging method that prints out all available interfaces.
+ ///
+ /// @param out specifies stream to print list of interfaces to
+ void printIfaces(std::ostream& out = std::cout);
+
+ /// @brief Sends an IPv6 packet.
+ ///
+ /// Sends an IPv6 packet. All parameters for actual transmission are specified in
+ /// Pkt6 structure itself. That includes destination address, src/dst port
+ /// and interface over which data will be sent.
+ ///
+ /// @param pkt packet to be sent
+ ///
+ /// @throw isc::BadValue if invalid interface specified in the packet.
+ /// @throw isc::dhcp::SocketWriteError if sendmsg() failed to send packet.
+ /// @return true if sending was successful
+ bool send(const Pkt6Ptr& pkt);
+
+ /// @brief Sends an IPv4 packet.
+ ///
+ /// Sends an IPv4 packet. All parameters for actual transmission are specified
+ /// in Pkt4 structure itself. That includes destination address, src/dst
+ /// port and interface over which data will be sent.
+ ///
+ /// @param pkt a packet to be sent
+ ///
+ /// @throw isc::BadValue if invalid interface specified in the packet.
+ /// @throw isc::dhcp::SocketWriteError if sendmsg() failed to send packet.
+ /// @return true if sending was successful
+ bool send(const Pkt4Ptr& pkt);
+
+ /// @brief Receive IPv4 packets or data from external sockets
+ ///
+ /// Wrapper around calls to either @c receive4Direct or @c
+ /// receive4Indirect. The former is called when packet queueing is
+ /// disabled, the latter when it is enabled.
+ ///
+ /// @param timeout_sec specifies integral part of the timeout (in seconds)
+ /// @param timeout_usec specifies fractional part of the timeout
+ /// (in microseconds)
+ ///
+ /// @return Pkt4 object representing received packet (or null)
+ Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
+ /// @brief Receive IPv4 packets or data from external sockets
+ ///
+ /// Wrapper around calls to either @c receive4Direct or @c
+ /// receive4Indirect. The former is called when packet queueing is
+ /// disabled, the latter when it is enabled.
+ ///
+ /// @param timeout_sec specifies integral part of the timeout (in seconds)
+ /// @param timeout_usec specifies fractional part of the timeout
+ /// (in microseconds)
+ ///
+ /// @return Pkt4 object representing received packet (or null)
+ Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
+ /// Opens UDP/IP socket and binds it to address, interface and port.
+ ///
+ /// Specific type of socket (UDP/IPv4 or UDP/IPv6) depends on passed addr
+ /// family.
+ ///
+ /// @param ifname name of the interface
+ /// @param addr address to be bound.
+ /// @param port UDP port.
+ /// @param receive_bcast configure IPv4 socket to receive broadcast
+ /// messages or IPv6 socket to join multicast group.
+ /// @param send_bcast configure IPv4 socket to send broadcast messages.
+ /// This parameter is ignored for IPv6 sockets.
+ ///
+ /// Method will throw if socket creation, socket binding or multicast
+ /// join fails.
+ ///
+ /// @return socket descriptor, if socket creation, binding and multicast
+ /// group join were all successful.
+ int openSocket(const std::string& ifname,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast = false,
+ const bool send_bcast = false);
+
+ /// @brief Opens UDP/IP socket and binds it to interface specified.
+ ///
+ /// This method differs from \ref openSocket in that it does not require
+ /// the specification of a local address to which socket will be bound.
+ /// Instead, the method searches through the addresses on the specified
+ /// interface and selects one that matches the address family.
+ ///
+ /// @note This method does not join the socket to the multicast group.
+ ///
+ /// @param ifname name of the interface
+ /// @param port UDP port
+ /// @param family address family (AF_INET or AF_INET6)
+ /// @return socket descriptor, if socket creation and binding was
+ /// successful.
+ /// @throw isc::Unexpected if failed to create and bind socket.
+ /// @throw isc::BadValue if there is no address on specified interface
+ /// that belongs to given family.
+ int openSocketFromIface(const std::string& ifname,
+ const uint16_t port,
+ const uint8_t family);
+
+ /// @brief Opens UDP/IP socket and binds to address specified
+ ///
+ /// This methods differs from \ref openSocket in that it does not require
+ /// the specification of the interface to which the socket will be bound.
+ ///
+ /// @note This method does not join the socket to the multicast group.
+ ///
+ /// @param addr address to be bound
+ /// @param port UDP port
+ /// @return socket descriptor, if socket creation and binding was
+ /// successful.
+ /// @throw isc::Unexpected if failed to create and bind socket
+ /// @throw isc::BadValue if specified address is not available on
+ /// any interface
+ int openSocketFromAddress(const isc::asiolink::IOAddress& addr,
+ const uint16_t port);
+
+ /// @brief Opens UDP/IP socket to be used to connect to remote address
+ ///
+ /// This method identifies the local address to be used to connect to the
+ /// remote address specified as argument. Once the local address is
+ /// identified, \ref openSocket is called to open a socket and bind it to
+ /// the interface, address and port.
+ ///
+ /// @note This method does not join the socket to a multicast group.
+ ///
+ /// @param remote_addr remote address to connect to
+ /// @param port UDP port
+ /// @return socket descriptor, if socket creation and binding was
+ /// successful.
+ /// @throw isc::Unexpected if failed to create and bind socket
+ int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr,
+ const uint16_t port);
+
+ /// @brief Opens IPv6 sockets on detected interfaces.
+ ///
+ /// This method opens sockets only on interfaces which have the
+ /// @c inactive6_ field set to false (are active). If the interface is active
+ /// but it is not running, it is down, or is a loopback interface when
+ /// loopback is not allowed, an error is reported.
+ ///
+ /// If sockets were successfully opened, it calls @ startDHCPReceiver to
+ /// start the receiver thread (if packet queueing is enabled).
+ ///
+ /// On the systems with multiple interfaces, it is often desired that the
+ /// failure to open a socket on a particular interface doesn't cause a
+ /// fatal error and sockets should be opened on remaining interfaces.
+ /// However, the warning about the failure for the particular socket should
+ /// be communicated to the caller. The libdhcp++ is a common library with
+ /// no logger associated with it. Most of the functions in this library
+ /// communicate errors via exceptions. In case of openSockets6 function
+ /// exception must not be thrown if the function is supposed to continue
+ /// opening sockets, despite an error. Therefore, if such a behavior is
+ /// desired, the error handler function can be passed as a parameter.
+ /// This error handler is called (if present) with an error string.
+ /// Typically, error handler will simply log an error using an application
+ /// logger, but it can do more sophisticated error handling too.
+ ///
+ /// @todo It is possible that additional parameters will have to be added
+ /// to the error handler, e.g. Iface if it was really supposed to do
+ /// some more sophisticated error handling.
+ ///
+ /// If the error handler is not installed (is null), the exception is thrown
+ /// for each failure (default behavior).
+ ///
+ /// @warning This function does not check if there has been any sockets
+ /// already open by the @c IfaceMgr. Therefore a caller should call
+ /// @c IfaceMgr::closeSockets() before calling this function.
+ /// If there are any sockets open, the function may either throw an
+ /// exception or invoke an error handler on attempt to bind the new socket
+ /// to the same address and port.
+ ///
+ /// @param port specifies port number (usually DHCP6_SERVER_PORT)
+ /// @param error_handler a pointer to an error handler function which is
+ /// called by the openSockets6 when it fails to open a socket. This
+ /// parameter can be null to indicate that the callback should not be used.
+ /// @param skip_opened skip the addresses that already have the opened port
+ ///
+ /// @throw SocketOpenFailure if tried and failed to open socket.
+ /// @return true if any sockets were open
+ bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT,
+ IfaceMgrErrorMsgCallback error_handler = 0,
+ const bool skip_opened = false);
+
+ /// @brief Opens IPv4 sockets on detected interfaces.
+ ///
+ /// This method opens sockets only on interfaces which have the
+ /// @c inactive4_ field set to false (are active). If the interface is active
+ /// but it is not running, it is down, or is a loopback interface when
+ /// loopback is not allowed, an error is reported.
+ ///
+ /// The type of the socket being open depends on the selected Packet Filter
+ /// represented by a class derived from @c isc::dhcp::PktFilter abstract
+ /// class.
+ ///
+ /// If sockets were successfully opened, it calls @ startDHCPReceiver to
+ /// start the receiver thread (if packet queueing is enabled).
+ ///
+ /// It is possible to specify whether sockets should be broadcast capable.
+ /// In most of the cases, the sockets should support broadcast traffic, e.g.
+ /// DHCPv4 server and relay need to listen to broadcast messages sent by
+ /// clients. If the socket has to be open on the particular interface, this
+ /// interface must have broadcast flag set. If this condition is not met,
+ /// the socket will be created in the unicast-only mode. If there are
+ /// multiple broadcast-capable interfaces present, they may be all open
+ /// in a broadcast mode only if the OS supports SO_BINDTODEVICE (bind socket
+ /// to a device) socket option. If this option is not supported, only the
+ /// first broadcast-capable socket will be opened in the broadcast mode.
+ /// The error will be reported for sockets being opened on other interfaces.
+ /// If the socket is bound to a device (interface), the broadcast traffic
+ /// sent to this interface will be received on this interface only.
+ /// This allows the DHCPv4 server or relay to detect the interface on which
+ /// the broadcast message has been received. This interface is later used
+ /// to send a response.
+ ///
+ /// On the systems with multiple interfaces, it is often desired that the
+ /// failure to open a socket on a particular interface doesn't cause a
+ /// fatal error and sockets should be opened on remaining interfaces.
+ /// However, the warning about the failure for the particular socket should
+ /// be communicated to the caller. The libdhcp++ is a common library with
+ /// no logger associated with it. Most of the functions in this library
+ /// communicate errors via exceptions. In case of openSockets4 function
+ /// exception must not be thrown if the function is supposed to continue
+ /// opening sockets, despite an error. Therefore, if such a behavior is
+ /// desired, the error handler function can be passed as a parameter.
+ /// This error handler is called (if present) with an error string.
+ /// Typically, error handler will simply log an error using an application
+ /// logger, but it can do more sophisticated error handling too.
+ ///
+ /// @todo It is possible that additional parameters will have to be added
+ /// to the error handler, e.g. Iface if it was really supposed to do
+ /// some more sophisticated error handling.
+ ///
+ /// If the error handler is not installed (is null), the exception is thrown
+ /// for each failure (default behavior).
+ ///
+ /// @warning This function does not check if there has been any sockets
+ /// already open by the @c IfaceMgr. Therefore a caller should call
+ /// @c IfaceMgr::closeSockets() before calling this function.
+ /// If there are any sockets open, the function may either throw an
+ /// exception or invoke an error handler on attempt to bind the new socket
+ /// to the same address and port.
+ ///
+ /// @param port specifies port number (usually DHCP4_SERVER_PORT)
+ /// @param use_bcast configure sockets to support broadcast messages.
+ /// @param error_handler a pointer to an error handler function which is
+ /// called by the openSockets4 when it fails to open a socket. This
+ /// parameter can be null to indicate that the callback should not be used.
+ /// @param skip_opened skip the addresses that already have the opened port
+ ///
+ /// @throw SocketOpenFailure if tried and failed to open socket and callback
+ /// function hasn't been specified.
+ /// @return true if any sockets were open
+ bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
+ const bool use_bcast = true,
+ IfaceMgrErrorMsgCallback error_handler = 0,
+ const bool skip_opened = false);
+
+ /// @brief Closes all open sockets.
+ ///
+ /// It calls @c stopDHCPReceiver to stop the receiver thread and then
+ /// it closes all open interface sockets.
+ ///
+ /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
+ void closeSockets();
+
+ /// @brief Returns number of detected interfaces.
+ ///
+ /// @return number of detected interfaces
+ uint16_t countIfaces() { return ifaces_.size(); }
+
+ /// @brief Adds external socket and a callback
+ ///
+ /// Specifies external socket and a callback that will be called
+ /// when data will be received over that socket.
+ ///
+ /// @param socketfd socket descriptor
+ /// @param callback callback function
+ void addExternalSocket(int socketfd, SocketCallback callback);
+
+ /// @brief Deletes external socket
+ ///
+ /// @param socketfd socket descriptor
+ void deleteExternalSocket(int socketfd);
+
+ /// @brief Scans registered socket set and removes any that are invalid.
+ ///
+ /// Walks the list of registered external sockets and tests each for
+ /// validity. If any are found to be invalid they are removed. This is
+ /// primarily a self-defense mechanism against hook libs or other users
+ /// of external sockets that may leave a closed socket registered by
+ /// mistake.
+ ///
+ /// @return A count of the sockets purged.
+ int purgeBadSockets();
+
+ /// @brief Deletes all external sockets.
+ void deleteAllExternalSockets();
+
+ /// @brief Set packet filter object to handle sending and receiving DHCPv4
+ /// messages.
+ ///
+ /// Packet filter objects provide means for the @c IfaceMgr to open sockets
+ /// for IPv4 packets reception and sending. This function sets custom packet
+ /// filter (represented by a class derived from PktFilter) to be used by
+ /// @c IfaceMgr. Note that there must be no IPv4 sockets open when this
+ /// function is called. Call closeSockets(AF_INET) to close all hanging IPv4
+ /// sockets opened by the current packet filter object.
+ ///
+ /// @param packet_filter a pointer to the new packet filter object to be
+ /// used by @c IfaceMgr.
+ ///
+ /// @throw InvalidPacketFilter if provided packet filter object is null.
+ /// @throw PacketFilterChangeDenied if there are open IPv4 sockets.
+ void setPacketFilter(const PktFilterPtr& packet_filter);
+
+ /// @brief Set packet filter object to handle sending and receiving DHCPv6
+ /// messages.
+ ///
+ /// Packet filter objects provide means for the @c IfaceMgr to open sockets
+ /// for IPv6 packets reception and sending. This function sets the new
+ /// instance of the packet filter which will be used by @c IfaceMgr to send
+ /// and receive DHCPv6 messages, until replaced by another packet filter.
+ ///
+ /// It is required that DHCPv6 messages are send and received using methods
+ /// of the same object that was used to open socket. Therefore, it is
+ /// required that all IPv6 sockets are closed prior to calling this
+ /// function. Call closeSockets(AF_INET6) to close all hanging IPv6 sockets
+ /// opened by the current packet filter object.
+ ///
+ /// @param packet_filter a pointer to the new packet filter object to be
+ /// used by @c IfaceMgr.
+ ///
+ /// @throw isc::dhcp::InvalidPacketFilter if specified object is null.
+ /// @throw isc::dhcp::PacketFilterChangeDenied if there are open IPv6
+ /// sockets.
+ void setPacketFilter(const PktFilter6Ptr& packet_filter);
+
+ /// @brief Set Packet Filter object to handle send/receive packets.
+ ///
+ /// This function sets Packet Filter object to be used by IfaceMgr,
+ /// appropriate for the current OS. Setting the argument to 'true'
+ /// indicates that function should set a packet filter class
+ /// which supports direct responses to clients having no address
+ /// assigned yet. Filters picked by this function will vary, depending
+ /// on the OS being used. There is no guarantee that there is an
+ /// implementation that supports this feature on a particular OS.
+ /// If there isn't, the PktFilterInet object will be set. If the
+ /// argument is set to 'false', PktFilterInet object instance will
+ /// be set as the Packet Filter regardless of the OS type.
+ ///
+ /// @param direct_response_desired specifies whether the Packet Filter
+ /// object being set should support direct traffic to the host
+ /// not having address assigned.
+ void setMatchingPacketFilter(const bool direct_response_desired = false);
+
+ /// @brief Adds an interface to list of known interfaces.
+ ///
+ /// @param iface reference to Iface object.
+ /// @note This function must be public because it has to be callable
+ /// from unit tests.
+ /// @throw Unexpected when name or index already exists.
+ void addInterface(const IfacePtr& iface);
+
+ /// @brief Checks if there is at least one socket of the specified family
+ /// open.
+ ///
+ /// @param family A socket family.
+ ///
+ /// @return true if there is at least one socket open, false otherwise.
+ bool hasOpenSocket(const uint16_t family) const;
+
+ /// @brief Checks if there is a socket open and bound to an address.
+ ///
+ /// This function checks if one of the sockets opened by the IfaceMgr is
+ /// bound to the IP address specified as the method parameter. If the
+ /// socket is bound to the port (and address is unspecified), the
+ /// method will check if the address passed in the argument is configured
+ /// on an interface.
+ /// Note: On BSD and Solaris the socket is opened for "::" address instead
+ /// of the link-local address or the "ff02::1:2" address. If there are
+ /// multiple interfaces joining the multicast group, this function matches
+ /// the "::" address bound by any interface, not necessary the one with the
+ /// specified link-local address and returns true.
+ ///
+ /// @param addr Address of the socket being searched.
+ ///
+ /// @return true if there is a socket bound to the specified address.
+ bool hasOpenSocket(const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief Fetches the DHCPv4 packet queue manager
+ ///
+ /// @return pointer to the packet queue mgr
+ PacketQueueMgr4Ptr getPacketQueueMgr4() {
+ return (packet_queue_mgr4_);
+ }
+
+ /// @brief Fetches the DHCPv4 receiver packet queue.
+ ///
+ /// Incoming packets are read by the receiver thread and
+ /// added to this queue. @c receive4() dequeues and
+ /// returns them.
+ /// @return pointer to the packet queue
+ PacketQueue4Ptr getPacketQueue4() {
+ return (packet_queue_mgr4_->getPacketQueue());
+ }
+
+ /// @brief Fetches the DHCPv6 packet queue manager
+ ///
+ /// @return pointer to the packet queue mgr
+ PacketQueueMgr6Ptr getPacketQueueMgr6() {
+ return (packet_queue_mgr6_);
+ }
+
+ /// @brief Fetches the DHCPv6 receiver packet queue.
+ ///
+ /// Incoming packets are read by the receiver thread and
+ /// added to this queue. @c receive6() dequeues and
+ /// returns them.
+ /// @return pointer to the packet queue
+ PacketQueue6Ptr getPacketQueue6() {
+ return (packet_queue_mgr6_->getPacketQueue());
+ }
+
+ /// @brief Starts DHCP packet receiver.
+ ///
+ /// Starts the DHCP packet receiver thread for the given.
+ /// protocol, AF_NET or AF_INET6, if the packet queue
+ /// exists, otherwise it simply returns.
+ ///
+ /// @param family indicates which receiver to start,
+ /// (AF_INET or AF_INET6)
+ ///
+ /// @throw Unexpected if the thread already exists.
+ void startDHCPReceiver(const uint16_t family);
+
+ /// @brief Stops the DHCP packet receiver.
+ ///
+ /// If the thread exists, it is stopped, deleted, and
+ /// the packet queue is flushed.
+ void stopDHCPReceiver();
+
+ /// @brief Returns true if there is a receiver exists and its
+ /// thread is currently running.
+ bool isDHCPReceiverRunning() const {
+ return (dhcp_receiver_ != 0 && dhcp_receiver_->isRunning());
+ }
+
+ /// @brief Configures DHCP packet queue
+ ///
+ /// If the given configuration enables packet queueing, then the
+ /// appropriate queue is created. Otherwise, the existing queue is
+ /// destroyed. If the receiver thread is running when this function
+ /// is invoked, it will throw.
+ ///
+ /// @param family indicates which receiver to start,
+ /// (AF_INET or AF_INET6)
+ /// @param queue_control configuration containing "dhcp-queue-control"
+ /// content
+ /// @return true if packet queueing has been enabled, false otherwise
+ /// @throw InvalidOperation if the receiver thread is currently running.
+ bool configureDHCPPacketQueue(const uint16_t family,
+ data::ConstElementPtr queue_control);
+
+ /// @brief Convenience method for adding an descriptor to a set
+ ///
+ /// @param fd descriptor to add
+ /// @param[out] maxfd maximum fd value in the set. If the new fd is
+ /// larger than it's current value, it will be updated to new fd value
+ /// @param sockets pointer to the set of sockets
+ /// @throw BadValue if sockets is null
+ static void addFDtoSet(int fd, int& maxfd, fd_set* sockets);
+
+ // don't use private, we need derived classes in tests
+protected:
+
+ /// @brief Protected constructor.
+ ///
+ /// Protected constructor. This is a singleton class. We don't want
+ /// anyone to create instances of IfaceMgr. Use instance() method instead.
+ IfaceMgr();
+
+ /// @brief Opens IPv4 socket.
+ ///
+ /// Please do not use this method directly. Use openSocket instead.
+ ///
+ /// This method may throw exception if socket creation fails.
+ ///
+ /// @param iface reference to interface structure.
+ /// @param addr an address the created socket should be bound to
+ /// @param port a port that created socket should be bound to
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @return socket descriptor
+ int openSocket4(Iface& iface, const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool receive_bcast = false,
+ const bool send_bcast = false);
+
+ /// @brief Receive IPv4 packets directly or data from external sockets.
+ ///
+ /// Attempts to receive a single DHCPv4 message over any of the open
+ /// IPv4 sockets. If reception is successful and all information about
+ /// its sender is obtained, an Pkt4 object is created and returned.
+ ///
+ /// This method also checks if data arrived over registered external socket.
+ /// This data may be of a different protocol family than AF_INET.
+ ///
+ /// @param timeout_sec specifies integral part of the timeout (in seconds)
+ /// @param timeout_usec specifies fractional part of the timeout
+ /// (in microseconds)
+ ///
+ /// @throw isc::BadValue if timeout_usec is greater than one million
+ /// @throw isc::dhcp::SocketReadError if error occurred when receiving a
+ /// packet.
+ /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is
+ /// interrupted by a signal.
+ ///
+ /// @return Pkt4 object representing received packet (or null)
+ Pkt4Ptr receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
+ /// @brief Receive IPv4 packets indirectly or data from external sockets.
+ ///
+ /// Attempts to receive a single DHCPv4 message from the packet queue.
+ /// The queue is populated by the receiver thread. If a packet is waiting
+ /// in the queue, a Pkt4 returned.
+ ///
+ /// This method also checks if data arrived over registered external socket.
+ /// This data may be of a different protocol family than AF_INET.
+ ///
+ /// @param timeout_sec specifies integral part of the timeout (in seconds)
+ /// @param timeout_usec specifies fractional part of the timeout
+ /// (in microseconds)
+ ///
+ /// @throw isc::BadValue if timeout_usec is greater than one million
+ /// @throw isc::dhcp::SocketReadError if error occurred when receiving a
+ /// packet.
+ /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is
+ /// interrupted by a signal.
+ ///
+ /// @return Pkt4 object representing received packet (or null)
+ Pkt4Ptr receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
+ /// @brief Opens IPv6 socket.
+ ///
+ /// Please do not use this method directly. Use openSocket instead.
+ ///
+ /// This method may throw exception if socket creation fails.
+ ///
+ /// @param iface reference to interface structure.
+ /// @param addr an address the created socket should be bound to
+ /// @param port a port that created socket should be bound to
+ /// @param join_multicast A boolean parameter which indicates whether
+ /// socket should join All_DHCP_Relay_Agents_and_servers multicast
+ /// group.
+ ///
+ /// @return socket descriptor
+ int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr,
+ uint16_t port, const bool join_multicast);
+
+ /// @brief Receive IPv6 packets directly or data from external sockets.
+ ///
+ /// Attempts to receive a single DHCPv6 message over any of the open
+ /// IPv6 sockets. If reception is successful and all information about
+ /// its sender is obtained, an Pkt6 object is created and returned.
+ ///
+ /// This method also checks if data arrived over registered external socket.
+ /// This data may be of a different protocol family than AF_INET.
+ ///
+ /// @param timeout_sec specifies integral part of the timeout (in seconds)
+ /// @param timeout_usec specifies fractional part of the timeout
+ /// (in microseconds)
+ ///
+ /// @throw isc::BadValue if timeout_usec is greater than one million
+ /// @throw isc::dhcp::SocketReadError if error occurred when receiving a
+ /// packet.
+ /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is
+ /// interrupted by a signal.
+ ///
+ /// @return Pkt6 object representing received packet (or null)
+ Pkt6Ptr receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
+ /// @brief Receive IPv6 packets indirectly or data from external sockets.
+ ///
+ /// Attempts to receive a single DHCPv6 message from the packet queue.
+ /// The queue is populated by the receiver thread. If a packet is waiting
+ /// in the queue, a Pkt6 returned.
+ ///
+ /// This method also checks if data arrived over registered external socket.
+ /// This data may be of a different protocol family than AF_INET.
+ ///
+ /// @param timeout_sec specifies integral part of the timeout (in seconds)
+ /// @param timeout_usec specifies fractional part of the timeout
+ /// (in microseconds)
+ ///
+ /// @throw isc::BadValue if timeout_usec is greater than one million
+ /// @throw isc::dhcp::SocketReadError if error occurred when receiving a
+ /// packet.
+ /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is
+ /// interrupted by a signal.
+ ///
+ /// @return Pkt6 object representing received packet (or null)
+ Pkt6Ptr receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0);
+
+ /// @brief List of available interfaces
+ IfaceCollection ifaces_;
+
+ /// @brief Unordered set of IPv4 bound addresses.
+ BoundAddresses bound_address_;
+
+ // TODO: Also keep this interface on Iface once interface detection
+ // is implemented. We may need it e.g. to close all sockets on
+ // specific interface
+ //int recvsock_; // TODO: should be fd_set eventually, but we have only
+ //int sendsock_; // 2 sockets for now. Will do for until next release
+
+ // We can't use the same socket, as receiving socket
+ // is bound to multicast address. And we all know what happens
+ // to people who try to use multicast as source address.
+
+private:
+ /// @brief Identifies local network address to be used to
+ /// connect to remote address.
+ ///
+ /// This method identifies local network address that can be used
+ /// to connect to remote address specified.
+ /// It first creates socket and makes attempt to connect
+ /// to remote location via this socket. If connection
+ /// is established successfully, the local address to which
+ /// socket is bound is returned.
+ ///
+ /// @param remote_addr remote address to connect to
+ /// @param port port to be used
+ /// @return local address to be used to connect to remote address
+ /// @throw isc::Unexpected if unable to identify local address
+ isc::asiolink::IOAddress
+ getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
+ const uint16_t port);
+
+ /// @brief Open an IPv6 socket with multicast support.
+ ///
+ /// This function opens a socket capable of receiving messages sent to
+ /// the All_DHCP_Relay_Agents_and_Servers (ff02::1:2) multicast address.
+ /// The socket is bound to the in6addr_any address and the IPV6_JOIN_GROUP
+ /// option is set to point to the ff02::1:2 multicast address.
+ ///
+ /// @note This function is intended to be called internally by the
+ /// @c IfaceMgr::openSockets6. It is not intended to be called from any
+ /// other function.
+ ///
+ /// @param iface Interface on which socket should be open.
+ /// @param addr Link-local address to bind the socket to.
+ /// @param port Port number to bind socket to.
+ /// @param error_handler Error handler function to be called when an
+ /// error occurs during opening a socket, or null if exception should
+ /// be thrown upon error.
+ bool openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler = 0);
+
+ /// @brief DHCPv4 receiver method.
+ ///
+ /// Loops forever reading DHCPv4 packets from the interface sockets
+ /// and adds them to the packet queue. It monitors the "terminate"
+ /// watch socket, and exits if it is marked ready. This is method
+ /// is used as the worker function in the thread created by @c
+ /// startDHCP4Receiver(). It currently uses select() to monitor
+ /// socket readiness. If the select errors out (other than EINTR),
+ /// it marks the "error" watch socket as ready.
+ void receiveDHCP4Packets();
+
+ /// @brief Receives a single DHCPv4 packet from an interface socket
+ ///
+ /// Called by @c receiveDHPC4Packets when a socket fd is flagged as
+ /// ready. It uses the DHCPv4 packet filter to receive a single packet
+ /// from the given interface socket, adds it to the packet queue, and
+ /// marks the "receive" watch socket ready. If an error occurs during
+ /// the read, the "error" watch socket is marked ready.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ void receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief DHCPv6 receiver method.
+ ///
+ /// Loops forever reading DHCPv6 packets from the interface sockets
+ /// and adds them to the packet queue. It monitors the "terminate"
+ /// watch socket, and exits if it is marked ready. This is method
+ /// is used as the worker function in the thread created by @c
+ /// startDHCP6Receiver(). It currently uses select() to monitor
+ /// socket readiness. If the select errors out (other than EINTR),
+ /// it marks the "error" watch socket as ready.
+ void receiveDHCP6Packets();
+
+ /// @brief Receives a single DHCPv6 packet from an interface socket
+ ///
+ /// Called by @c receiveDHPC6Packets when a socket fd is flagged as
+ /// ready. It uses the DHCPv6 packet filter to receive a single packet
+ /// from the given interface socket, adds it to the packet queue, and
+ /// marks the "receive" watch socket ready. If an error occurs during
+ /// the read, the "error" watch socket is marked ready.
+ ///
+ /// @param socket_info structure holding socket information
+ void receiveDHCP6Packet(const SocketInfo& socket_info);
+
+ /// @brief Deletes external socket with the callbacks_mutex_ taken
+ ///
+ /// @param socketfd socket descriptor
+ void deleteExternalSocketInternal(int socketfd);
+
+ /// Holds instance of a class derived from PktFilter, used by the
+ /// IfaceMgr to open sockets and send/receive packets through these
+ /// sockets. It is possible to supply custom object using
+ /// setPacketFilter method. Various Packet Filters differ mainly by using
+ /// different types of sockets, e.g. SOCK_DGRAM, SOCK_RAW and different
+ /// families, e.g. AF_INET, AF_PACKET etc. Another possible type of
+ /// Packet Filter is the one used for unit testing, which doesn't
+ /// open sockets but rather mimics their behavior (mock object).
+ PktFilterPtr packet_filter_;
+
+ /// Holds instance of a class derived from PktFilter6, used by the
+ /// IfaceMgr to manage sockets used to send and receive DHCPv6
+ /// messages. It is possible to supply a custom object using
+ /// setPacketFilter method.
+ PktFilter6Ptr packet_filter6_;
+
+ /// @brief Contains list of callbacks for external sockets
+ SocketCallbackInfoContainer callbacks_;
+
+ /// @brief Mutex to protect callbacks_ against concurrent access
+ std::mutex callbacks_mutex_;
+
+ /// @brief Indicates if the IfaceMgr is in the test mode.
+ bool test_mode_;
+
+ /// @brief Detect callback used to perform actions before system dependent
+ /// function calls.
+ ///
+ /// If the @ref detect_callback_ returns true, the specific system calls are
+ /// executed, otherwise the @ref detectIfaces will return immediately.
+ DetectCallback detect_callback_;
+
+ /// @brief Allows to use loopback
+ bool allow_loopback_;
+
+ /// @brief Manager for DHCPv4 packet implementations and queues
+ PacketQueueMgr4Ptr packet_queue_mgr4_;
+
+ /// @brief Manager for DHCPv6 packet implementations and queues
+ PacketQueueMgr6Ptr packet_queue_mgr6_;
+
+ /// @brief DHCP packet receiver.
+ isc::util::WatchedThreadPtr dhcp_receiver_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // IFACE_MGR_H
diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc
new file mode 100644
index 0000000..4ae0f0e
--- /dev/null
+++ b/src/lib/dhcp/iface_mgr_bsd.cc
@@ -0,0 +1,200 @@
+// Copyright (C) 2011-2023 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>
+
+#if defined(OS_BSD)
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
+#include <dhcp/pkt_filter_bpf.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <exceptions/exceptions.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if_dl.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace isc {
+namespace dhcp {
+
+/// This is a BSD specific interface detection method.
+void
+IfaceMgr::detectIfaces(bool update_only) {
+ if (detect_callback_) {
+ if (!detect_callback_(update_only)) {
+ return;
+ }
+ }
+
+ struct ifaddrs* iflist = 0;// The whole interface list
+ struct ifaddrs* ifptr = 0; // The interface we're processing now
+
+ // Gets list of ifaddrs struct
+ if (getifaddrs(&iflist) != 0) {
+ isc_throw(Unexpected, "Network interfaces detection failed.");
+ }
+
+ typedef map<string, IfacePtr> IfaceLst;
+ IfaceLst::iterator iface_iter;
+ IfaceLst ifaces;
+
+ // First lookup for getting interfaces ...
+ for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+ const char * ifname = ifptr->ifa_name;
+ uint ifindex = 0;
+
+ if (!(ifindex = if_nametoindex(ifname))) {
+ // Interface name does not have corresponding index ...
+ freeifaddrs(iflist);
+ isc_throw(Unexpected, "Interface " << ifname << " has no index");
+ }
+
+ iface_iter = ifaces.find(ifname);
+ if (iface_iter != ifaces.end()) {
+ continue;
+ }
+
+ IfacePtr iface;
+ if (update_only) {
+ iface = getIface(ifname);
+ }
+ if (!iface) {
+ iface.reset(new Iface(ifname, ifindex));
+ }
+ iface->setFlags(ifptr->ifa_flags);
+ ifaces.insert(pair<string, IfacePtr>(ifname, iface));
+ }
+
+ // Second lookup to get MAC and IP addresses
+ for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+ iface_iter = ifaces.find(ifptr->ifa_name);
+ if (iface_iter == ifaces.end()) {
+ continue;
+ }
+ // Common byte pointer for following data
+ const uint8_t * ptr = 0;
+ if (ifptr->ifa_addr->sa_family == AF_LINK) {
+ // HWAddr
+ struct sockaddr_dl * ldata =
+ reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+ ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata));
+
+ iface_iter->second->setHWType(ldata->sdl_type);
+ iface_iter->second->setMac(ptr, ldata->sdl_alen);
+ } else if (ifptr->ifa_addr->sa_family == AF_INET6) {
+ // IPv6 Addr
+ struct sockaddr_in6 * adata =
+ reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+ ptr = reinterpret_cast<uint8_t *>(&adata->sin6_addr);
+
+ IOAddress a = IOAddress::fromBytes(AF_INET6, ptr);
+ iface_iter->second->addAddress(a);
+ } else {
+ // IPv4 Addr
+ struct sockaddr_in * adata =
+ reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+ ptr = reinterpret_cast<uint8_t *>(&adata->sin_addr);
+
+ IOAddress a = IOAddress::fromBytes(AF_INET, ptr);
+ iface_iter->second->addAddress(a);
+ }
+ }
+
+ freeifaddrs(iflist);
+
+ // Interfaces registering
+ for (IfaceLst::const_iterator iface_iter = ifaces.begin();
+ iface_iter != ifaces.end(); ++iface_iter) {
+ IfacePtr iface;
+ if (update_only) {
+ iface = getIface(iface_iter->first);
+ }
+ if (!iface) {
+ addInterface(iface_iter->second);
+ }
+ }
+}
+
+/// @brief sets flag_*_ fields
+///
+/// Like Linux version, os specific flags
+///
+/// @params flags
+void Iface::setFlags(uint64_t flags) {
+ flags_ = flags;
+
+ flag_loopback_ = flags & IFF_LOOPBACK;
+ flag_up_ = flags & IFF_UP;
+ flag_running_ = flags & IFF_RUNNING;
+ flag_multicast_ = flags & IFF_MULTICAST;
+ flag_broadcast_ = flags & IFF_BROADCAST;
+}
+
+void
+IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) {
+ // If direct response is desired we have to use BPF. If the direct
+ // response is not desired we use datagram socket supported by the
+ // PktFilterInet class. Note however that on BSD systems binding the
+ // datagram socket to the device is not supported and the server would
+ // have no means to determine on which interface the packet has been
+ // received. Hence, it is discouraged to use PktFilterInet for the
+ // server.
+ if (direct_response_desired) {
+ setPacketFilter(PktFilterPtr(new PktFilterBPF()));
+
+ } else {
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+ }
+}
+
+bool
+IfaceMgr::openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler) {
+ try {
+ // This should open a socket, bind it to link-local address
+ // and join multicast group.
+ openSocket(iface.getName(), addr, port, iface.flag_multicast_);
+
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(),
+ "Failed to open link-local socket on "
+ "interface " << iface.getName() << ": "
+ << ex.what());
+ return (false);
+
+ }
+ return (true);
+}
+
+int
+IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
+ const bool join_multicast) {
+ // On BSD, we bind the socket to in6addr_any and join multicast group
+ // to receive multicast traffic. So, if the multicast is requested,
+ // replace the address specified by the caller with the "unspecified"
+ // address.
+ IOAddress actual_address = join_multicast ? IOAddress("::") : addr;
+ SocketInfo info = packet_filter6_->openSocket(iface, actual_address, port,
+ join_multicast);
+ iface.addSocket(info);
+ return (info.sockfd_);
+}
+
+} // end of isc::dhcp namespace
+} // end of dhcp namespace
+
+#endif
diff --git a/src/lib/dhcp/iface_mgr_error_handler.h b/src/lib/dhcp/iface_mgr_error_handler.h
new file mode 100644
index 0000000..631d25f
--- /dev/null
+++ b/src/lib/dhcp/iface_mgr_error_handler.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2014-2022 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 IFACE_MGR_ERROR_HANDLER_H
+#define IFACE_MGR_ERROR_HANDLER_H
+
+/// @brief A macro which handles an error in IfaceMgr.
+///
+/// There are certain cases when IfaceMgr may hit an error which shouldn't
+/// result in interruption of the function processing. A typical case is
+/// the function which opens sockets on available interfaces for a DHCP
+/// server. If this function fails to open a socket on a specific interface
+/// (for example, there is another socket already open on this interface
+/// and bound to the same address and port), it is desired that the server
+/// logs a warning but will try to open sockets on other interfaces. In order
+/// to log an error, the IfaceMgr will use the error handler function provided
+/// by the server and pass an error string to it. When the handler function
+/// returns, the IfaceMgr will proceed to open other sockets. It is allowed
+/// that the error handler function is not installed (is NULL). In these
+/// cases it is expected that the exception is thrown instead. A possible
+/// solution would be to enclose this conditional behavior in a function.
+/// However, despite the hate for macros, the macro seems to be a bit
+/// better solution in this case as it allows to conveniently pass an
+/// error string in a stream (not as a string).
+///
+/// @param ex_type Exception to be thrown if error_handler is NULL.
+/// @param handler Error handler function to be called or NULL to indicate
+/// that exception should be thrown instead.
+/// @param iface Pointer to the interface for which the error is logged. Can be null.
+/// @param stream stream object holding an error string.
+#define IFACEMGR_ERROR(ex_type, handler, iface, stream) \
+{ \
+ std::ostringstream ieoss__; \
+ ieoss__ << stream; \
+ std::string const error(ieoss__.str()); \
+ if (iface) { \
+ iface->addError(error); \
+ } \
+ if (handler) { \
+ handler(error); \
+ } else { \
+ isc_throw(ex_type, error); \
+ } \
+} \
+
+#endif // IFACE_MGR_ERROR_HANDLER_H
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
new file mode 100644
index 0000000..1fb2aaf
--- /dev/null
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -0,0 +1,621 @@
+// Copyright (C) 2011-2023 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/.
+
+/// @file
+/// Access to interface information on Linux is via netlink, a socket-based
+/// method for transferring information between the kernel and user processes.
+///
+/// For detailed information about netlink interface, please refer to
+/// http://en.wikipedia.org/wiki/Netlink and RFC3549. Comments in the
+/// detectIfaces() method (towards the end of this file) provide an overview
+/// on how the netlink interface is used here.
+///
+/// Note that this interface is very robust and allows many operations:
+/// add/get/set/delete links, addresses, routes, queuing, manipulation of
+/// traffic classes, manipulation of neighbourhood tables and even the ability
+/// to do something with address labels. Getting a list of interfaces with
+/// addresses configured on it is just a small subset of all possible actions.
+
+#include <config.h>
+
+#if defined(OS_LINUX)
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/pkt_filter_lpf.h>
+#include <exceptions/exceptions.h>
+#include <util/io/sockaddr_util.h>
+
+#include <boost/array.hpp>
+#include <boost/static_assert.hpp>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <net/if.h>
+#include <linux/rtnetlink.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util::io::internal;
+
+BOOST_STATIC_ASSERT(IFLA_MAX>=IFA_MAX);
+
+namespace {
+
+/// @brief This class offers utility methods for netlink connection.
+///
+/// See IfaceMgr::detectIfaces() (Linux implementation, towards the end of this
+/// file) for example usage.
+class Netlink
+{
+public:
+
+/// @brief Holds pointers to netlink messages.
+///
+/// netlink (a Linux interface for getting information about network
+/// interfaces) uses memory aliasing. Linux kernel returns a memory
+/// blob that should be interpreted as series of nlmessages. There
+/// are different nlmsg structures defined with varying size. They
+/// have one thing common - initial fields are laid out in the same
+/// way as nlmsghdr. Therefore different messages can be represented
+/// as nlmsghdr with followed variable number of bytes that are
+/// message-specific. The only reasonable way to represent this in
+/// C++ is to use vector of pointers to nlmsghdr (the common structure).
+ typedef vector<nlmsghdr*> NetlinkMessages;
+
+/// @brief Holds pointers to interface or address attributes.
+///
+/// Note that to get address info, a shorter (IFA_MAX rather than IFLA_MAX)
+/// table could be used, but we will use the bigger one anyway to
+/// make the code reusable.
+///
+/// rtattr is a generic structure, similar to sockaddr. It is defined
+/// in linux/rtnetlink.h and shown here for documentation purposes only:
+///
+/// struct rtattr {
+/// unsigned short<>rta_len;
+/// unsigned short<>rta_type;
+/// };
+ typedef boost::array<struct rtattr*, IFLA_MAX + 1> RTattribPtrs;
+
+ Netlink() : fd_(-1), seq_(0), dump_(0) {
+ memset(&local_, 0, sizeof(struct sockaddr_nl));
+ memset(&peer_, 0, sizeof(struct sockaddr_nl));
+ }
+
+ ~Netlink() {
+ rtnl_close_socket();
+ }
+
+
+ void rtnl_open_socket();
+ void rtnl_send_request(int family, int type);
+ void rtnl_store_reply(NetlinkMessages& storage, const nlmsghdr* msg);
+ void parse_rtattr(RTattribPtrs& table, rtattr* rta, int len);
+ void ipaddrs_get(Iface& iface, NetlinkMessages& addr_info);
+ void rtnl_process_reply(NetlinkMessages& info);
+ void release_list(NetlinkMessages& messages);
+ void rtnl_close_socket();
+
+private:
+ int fd_; // Netlink file descriptor
+ sockaddr_nl local_; // Local addresses
+ sockaddr_nl peer_; // Remote address
+ uint32_t seq_; // Counter used for generating unique sequence numbers
+ uint32_t dump_; // Number of expected message response
+};
+
+/// @brief defines a size of a sent netlink buffer
+const static size_t SNDBUF_SIZE = 32768;
+
+/// @brief defines a size of a received netlink buffer
+const static size_t RCVBUF_SIZE = 32768;
+
+/// @brief Opens netlink socket and initializes handle structure.
+///
+/// @throw isc::Unexpected Thrown if socket configuration fails.
+void Netlink::rtnl_open_socket() {
+
+ fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (fd_ < 0) {
+ isc_throw(Unexpected, "Failed to create NETLINK socket.");
+ }
+
+ if (fcntl(fd_, F_SETFD, FD_CLOEXEC) < 0) {
+ isc_throw(Unexpected, "Failed to set close-on-exec in NETLINK socket.");
+ }
+
+ if (setsockopt(fd_, SOL_SOCKET, SO_SNDBUF, &SNDBUF_SIZE, sizeof(SNDBUF_SIZE)) < 0) {
+ isc_throw(Unexpected, "Failed to set send buffer in NETLINK socket.");
+ }
+
+ if (setsockopt(fd_, SOL_SOCKET, SO_RCVBUF, &RCVBUF_SIZE, sizeof(RCVBUF_SIZE)) < 0) {
+ isc_throw(Unexpected, "Failed to set receive buffer in NETLINK socket.");
+ }
+
+ local_.nl_family = AF_NETLINK;
+ local_.nl_groups = 0;
+
+ if (::bind(fd_, convertSockAddr(&local_), sizeof(local_)) < 0) {
+ isc_throw(Unexpected, "Failed to bind netlink socket.");
+ }
+
+ socklen_t addr_len = sizeof(local_);
+ if (getsockname(fd_, convertSockAddr(&local_), &addr_len) < 0) {
+ isc_throw(Unexpected, "Getsockname for netlink socket failed.");
+ }
+
+ // just 2 sanity checks and we are done
+ if ( (addr_len != sizeof(local_)) ||
+ (local_.nl_family != AF_NETLINK) ) {
+ isc_throw(Unexpected, "getsockname() returned unexpected data for netlink socket.");
+ }
+}
+
+/// @brief Closes netlink communication socket
+void Netlink::rtnl_close_socket() {
+ if (fd_ != -1) {
+ close(fd_);
+ }
+ fd_ = -1;
+}
+
+/// @brief Sends request over NETLINK socket.
+///
+/// @param family requested information family.
+/// @param type request type (RTM_GETLINK or RTM_GETADDR).
+void Netlink::rtnl_send_request(int family, int type) {
+ struct Req {
+ nlmsghdr netlink_header;
+ rtgenmsg generic;
+ };
+ Req req; // we need this type named for offsetof() used in assert
+ struct sockaddr_nl nladdr;
+
+ // do a sanity check. Verify that Req structure is aligned properly
+ BOOST_STATIC_ASSERT(sizeof(nlmsghdr) == offsetof(Req, generic));
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ // According to netlink(7) manpage, mlmsg_seq must be set to a sequence
+ // number and is used to track messages. That is just a value that is
+ // opaque to kernel, and user-space code is supposed to use it to match
+ // incoming responses to sent requests. That is not really useful as we
+ // send a single request and get a single response at a time. However, we
+ // obey the man page suggestion and just set this to monotonically
+ // increasing numbers.
+ seq_++;
+
+ // This will be used to finding correct response (responses
+ // sent by kernel are supposed to have the same sequence number
+ // as the request we sent).
+ dump_ = seq_;
+
+ memset(&req, 0, sizeof(req));
+ req.netlink_header.nlmsg_len = sizeof(req);
+ req.netlink_header.nlmsg_type = type;
+ req.netlink_header.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
+ req.netlink_header.nlmsg_pid = 0;
+ req.netlink_header.nlmsg_seq = seq_;
+ req.generic.rtgen_family = family;
+
+ int status = sendto(fd_, static_cast<void*>(&req), sizeof(req), 0,
+ static_cast<struct sockaddr*>(static_cast<void*>(&nladdr)),
+ sizeof(nladdr));
+
+ if (status<0) {
+ isc_throw(Unexpected, "Failed to send " << sizeof(nladdr)
+ << " bytes over netlink socket.");
+ }
+}
+
+/// @brief Appends nlmsg to a storage.
+///
+/// This method copies pointed nlmsg to a newly allocated memory
+/// and adds it to storage.
+///
+/// @param storage A vector that holds pointers to netlink messages. The caller
+/// is responsible for freeing the pointed-to messages.
+/// @param msg A netlink message to be added.
+void Netlink::rtnl_store_reply(NetlinkMessages& storage, const struct nlmsghdr *msg) {
+ // we need to make a copy of this message. We really can't allocate
+ // nlmsghdr directly as it is only part of the structure. There are
+ // many message types with varying lengths and a common header.
+ struct nlmsghdr* copy = reinterpret_cast<struct nlmsghdr*>(new char[msg->nlmsg_len]);
+ memcpy(copy, msg, msg->nlmsg_len);
+
+ // push_back copies only pointer content, not the pointed-to object.
+ storage.push_back(copy);
+}
+
+/// @brief Parses rtattr message.
+///
+/// Some netlink messages represent address information. Such messages
+/// are concatenated collection of rtaddr structures. This function
+/// iterates over that list and stores pointers to those messages in
+/// flat array (table).
+///
+/// @param table rtattr Messages will be stored here
+/// @param rta Pointer to first rtattr object
+/// @param len Length (in bytes) of concatenated rtattr list.
+void Netlink::parse_rtattr(RTattribPtrs& table, struct rtattr* rta, int len) {
+ std::fill(table.begin(), table.end(), static_cast<struct rtattr*>(NULL));
+ // RTA_OK and RTA_NEXT() are macros defined in linux/rtnetlink.h
+ // they are used to handle rtattributes. RTA_OK checks if the structure
+ // pointed by rta is reasonable and passes all sanity checks.
+ // RTA_NEXT() returns pointer to the next rtattr structure that
+ // immediately follows pointed rta structure. See aforementioned
+ // header for details.
+ while (RTA_OK(rta, len)) {
+ if (rta->rta_type < table.size()) {
+ table[rta->rta_type] = rta;
+ }
+ rta = RTA_NEXT(rta,len);
+ }
+ if (len) {
+ isc_throw(Unexpected, "Failed to parse RTATTR in netlink message.");
+ }
+}
+
+/// @brief Parses addr_info and appends appropriate addresses to Iface object.
+///
+/// Netlink is a fine, but convoluted interface. It returns a concatenated
+/// collection of netlink messages. Some of those messages convey information
+/// about addresses. Those messages are in fact appropriate header followed
+/// by concatenated lists of rtattr structures that define various pieces
+/// of address information.
+///
+/// @param iface interface representation (addresses will be added here)
+/// @param addr_info collection of parsed netlink messages
+void Netlink::ipaddrs_get(Iface& iface, NetlinkMessages& addr_info) {
+ uint8_t addr[V6ADDRESS_LEN];
+ RTattribPtrs rta_tb;
+
+ for (NetlinkMessages::const_iterator msg = addr_info.begin();
+ msg != addr_info.end(); ++msg) {
+ ifaddrmsg* ifa = static_cast<ifaddrmsg*>(NLMSG_DATA(*msg));
+
+ // These are not the addresses you are looking for
+ if (ifa->ifa_index != iface.getIndex()) {
+ continue;
+ }
+
+ if ((ifa->ifa_family == AF_INET6) || (ifa->ifa_family == AF_INET)) {
+ std::fill(rta_tb.begin(), rta_tb.end(), static_cast<rtattr*>(NULL));
+ parse_rtattr(rta_tb, IFA_RTA(ifa), (*msg)->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+ if (!rta_tb[IFA_LOCAL]) {
+ rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
+ }
+ if (!rta_tb[IFA_ADDRESS]) {
+ rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
+ }
+
+ memcpy(addr, RTA_DATA(rta_tb[IFLA_ADDRESS]),
+ ifa->ifa_family==AF_INET?V4ADDRESS_LEN:V6ADDRESS_LEN);
+ IOAddress a = IOAddress::fromBytes(ifa->ifa_family, addr);
+ iface.addAddress(a);
+
+ /// TODO: Read lifetimes of configured IPv6 addresses
+ }
+ }
+}
+
+/// @brief Processes reply received over netlink socket.
+///
+/// This method parses the received buffer (a collection of concatenated
+/// netlink messages), copies each received message to newly allocated
+/// memory and stores pointers to it in the "info" container.
+///
+/// @param info received netlink messages will be stored here. It is the
+/// caller's responsibility to release the memory associated with the
+/// messages by calling the release_list() method.
+void Netlink::rtnl_process_reply(NetlinkMessages& info) {
+ sockaddr_nl nladdr;
+ iovec iov;
+ msghdr msg;
+ memset(&msg, 0, sizeof(msghdr));
+ msg.msg_name = &nladdr;
+ msg.msg_namelen = sizeof(nladdr);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ char buf[RCVBUF_SIZE];
+
+ iov.iov_base = buf;
+ iov.iov_len = sizeof(buf);
+ while (true) {
+ int status = recvmsg(fd_, &msg, 0);
+
+ if (status < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ isc_throw(Unexpected, "Error " << errno
+ << " while processing reply from netlink socket.");
+ }
+
+ if (status == 0) {
+ isc_throw(Unexpected, "EOF while reading netlink socket.");
+ }
+
+ nlmsghdr* header = static_cast<nlmsghdr*>(static_cast<void*>(buf));
+ while (NLMSG_OK(header, status)) {
+
+ // Received a message not addressed to our process, or not
+ // with a sequence number we are expecting. Ignore, and
+ // look at the next one.
+ if (nladdr.nl_pid != 0 ||
+ header->nlmsg_pid != local_.nl_pid ||
+ header->nlmsg_seq != dump_) {
+ header = NLMSG_NEXT(header, status);
+ continue;
+ }
+
+ if (header->nlmsg_type == NLMSG_DONE) {
+ // End of message.
+ return;
+ }
+
+ if (header->nlmsg_type == NLMSG_ERROR) {
+ nlmsgerr* err = static_cast<nlmsgerr*>(NLMSG_DATA(header));
+ if (header->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
+ // We are really out of luck here. We can't even say what is
+ // wrong as error message is truncated. D'oh.
+ isc_throw(Unexpected, "Netlink reply read failed.");
+ } else {
+ isc_throw(Unexpected, "Netlink reply read error " << -err->error);
+ }
+ // Never happens we throw before we reach here
+ return;
+ }
+
+ // store the data
+ rtnl_store_reply(info, header);
+
+ header = NLMSG_NEXT(header, status);
+ }
+ if (msg.msg_flags & MSG_TRUNC) {
+ isc_throw(Unexpected, "Message received over netlink truncated.");
+ }
+ if (status) {
+ isc_throw(Unexpected, "Trailing garbage of " << status << " bytes received over netlink.");
+ }
+ }
+}
+
+/// @brief releases nlmsg structure
+///
+/// @param messages Set of messages to be freed.
+void Netlink::release_list(NetlinkMessages& messages) {
+ // let's free local copies of stored messages
+ for (NetlinkMessages::iterator msg = messages.begin(); msg != messages.end(); ++msg) {
+ delete[] (*msg);
+ }
+
+ // and get rid of the message pointers as well
+ messages.clear();
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Detect available interfaces on Linux systems.
+///
+/// Uses the socket-based netlink protocol to retrieve the list of interfaces
+/// from the Linux kernel.
+void IfaceMgr::detectIfaces(bool update_only) {
+ if (detect_callback_) {
+ if (!detect_callback_(update_only)) {
+ return;
+ }
+ }
+
+ // Copies of netlink messages about links will be stored here.
+ Netlink::NetlinkMessages link_info;
+
+ // Copies of netlink messages about addresses will be stored here.
+ Netlink::NetlinkMessages addr_info;
+
+ // Socket descriptors and other rtnl-related parameters.
+ Netlink nl;
+
+ // Table with pointers to address attributes.
+ Netlink::RTattribPtrs attribs_table;
+ std::fill(attribs_table.begin(), attribs_table.end(),
+ static_cast<struct rtattr*>(NULL));
+
+ // Open socket
+ nl.rtnl_open_socket();
+
+ // Now we have open functional socket, let's use it!
+ // Ask for list of network interfaces...
+ nl.rtnl_send_request(AF_PACKET, RTM_GETLINK);
+
+ // Get reply and store it in link_info list:
+ // response is received as with any other socket - just a series
+ // of bytes. They are representing collection of netlink messages
+ // concatenated together. rtnl_process_reply will parse this
+ // buffer, copy each message to a newly allocated memory and
+ // store pointers to it in link_info. This allocated memory will
+ // be released later. See release_info(link_info) below.
+ nl.rtnl_process_reply(link_info);
+
+ // Now ask for list of addresses (AF_UNSPEC = of any family)
+ // Let's repeat, but this time ask for any addresses.
+ // That includes IPv4, IPv6 and any other address families that
+ // are happen to be supported by this system.
+ nl.rtnl_send_request(AF_UNSPEC, RTM_GETADDR);
+
+ // Get reply and store it in addr_info list.
+ // Again, we will allocate new memory and store messages in
+ // addr_info. It will be released later using release_info(addr_info).
+ nl.rtnl_process_reply(addr_info);
+
+ // Now build list with interface names
+ for (Netlink::NetlinkMessages::iterator msg = link_info.begin();
+ msg != link_info.end(); ++msg) {
+ // Required to display information about interface
+ struct ifinfomsg* interface_info = static_cast<ifinfomsg*>(NLMSG_DATA(*msg));
+ int len = (*msg)->nlmsg_len;
+ len -= NLMSG_LENGTH(sizeof(*interface_info));
+ nl.parse_rtattr(attribs_table, IFLA_RTA(interface_info), len);
+
+ // valgrind reports *possible* memory leak in the line below, but it is
+ // bogus. Nevertheless, the whole interface definition has been split
+ // into three separate steps for easier debugging.
+ const char* tmp = static_cast<const char*>(RTA_DATA(attribs_table[IFLA_IFNAME]));
+ string iface_name(tmp); // <--- bogus valgrind warning here
+ // This is guaranteed both by the if_nametoindex() implementation
+ // and by kernel dev_new_index() code. In fact 0 is impossible too...
+ if (interface_info->ifi_index < 0) {
+ isc_throw(OutOfRange, "negative interface index");
+ }
+ IfacePtr iface;
+ bool created = true;
+
+ if (update_only) {
+ iface = getIface(iface_name);
+ if (iface) {
+ created = false;
+ }
+ }
+
+ if (!iface) {
+ iface.reset(new Iface(iface_name, interface_info->ifi_index));
+ }
+
+ iface->setHWType(interface_info->ifi_type);
+ iface->setFlags(interface_info->ifi_flags);
+
+ // Does interface have LL_ADDR?
+ if (attribs_table[IFLA_ADDRESS]) {
+ iface->setMac(static_cast<const uint8_t*>(RTA_DATA(attribs_table[IFLA_ADDRESS])),
+ RTA_PAYLOAD(attribs_table[IFLA_ADDRESS]));
+ } else {
+ // Tunnels can have no LL_ADDR. RTA_PAYLOAD doesn't check it and
+ // try to dereference it in this manner
+ }
+
+ nl.ipaddrs_get(*iface, addr_info);
+
+ // addInterface can now throw so protect against memory leaks.
+ try {
+ if (created) {
+ addInterface(iface);
+ }
+ } catch (...) {
+ nl.release_list(link_info);
+ nl.release_list(addr_info);
+ throw;
+ }
+ }
+
+ nl.release_list(link_info);
+ nl.release_list(addr_info);
+}
+
+/// @brief sets flag_*_ fields.
+///
+/// This implementation is OS-specific as bits have different meaning
+/// on different OSes.
+///
+/// @param flags flags bitfield read from OS
+void Iface::setFlags(uint64_t flags) {
+ flags_ = flags;
+
+ flag_loopback_ = flags & IFF_LOOPBACK;
+ flag_up_ = flags & IFF_UP;
+ flag_running_ = flags & IFF_RUNNING;
+ flag_multicast_ = flags & IFF_MULTICAST;
+ flag_broadcast_ = flags & IFF_BROADCAST;
+}
+
+void
+IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) {
+ if (direct_response_desired) {
+ setPacketFilter(PktFilterPtr(new PktFilterLPF()));
+
+ } else {
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+ }
+}
+
+bool
+IfaceMgr::openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler) {
+ // This variable will hold a descriptor of the socket bound to
+ // link-local address. It may be required for us to close this
+ // socket if an attempt to open and bind a socket to multicast
+ // address fails.
+ int sock;
+ try {
+ sock = openSocket(iface.getName(), addr, port, iface.flag_multicast_);
+
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(),
+ "Failed to open link-local socket on "
+ "interface " << iface.getName() << ": "
+ << ex.what());
+ return (false);
+
+ }
+
+ // In order to receive multicast traffic another socket is opened
+ // and bound to the multicast address.
+
+ /// @todo The DHCPv6 requires multicast so we may want to think
+ /// whether we want to open the socket on a multicast-incapable
+ /// interface or not. For now, we prefer to be liberal and allow
+ /// it for some odd use cases which may utilize non-multicast
+ /// interfaces. Perhaps a warning should be emitted if the
+ /// interface is not a multicast one.
+ if (iface.flag_multicast_) {
+ try {
+ openSocket(iface.getName(),
+ IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+ port);
+ } catch (const Exception& ex) {
+ // An attempt to open and bind a socket to multicast address
+ // has failed. We have to close the socket we previously
+ // bound to link-local address - this is everything or
+ // nothing strategy.
+ iface.delSocket(sock);
+ IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(),
+ "Failed to open multicast socket on"
+ " interface " << iface.getName()
+ << ", reason: " << ex.what());
+ return (false);
+ }
+ }
+ // Both sockets have opened successfully.
+ return (true);
+}
+
+int
+IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
+ const bool join_multicast) {
+ // Assuming that packet filter is not NULL, because its modifier checks it.
+ SocketInfo info = packet_filter6_->openSocket(iface, addr, port,
+ join_multicast);
+ iface.addSocket(info);
+
+ return (info.sockfd_);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // if defined(LINUX)
diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc
new file mode 100644
index 0000000..f8ab65c
--- /dev/null
+++ b/src/lib/dhcp/iface_mgr_sun.cc
@@ -0,0 +1,189 @@
+// Copyright (C) 2011-2023 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>
+
+#if defined(OS_SUN)
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp/iface_mgr_error_handler.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <exceptions/exceptions.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if_dl.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace isc {
+namespace dhcp {
+
+/// This is a Solaris specific interface detection code. It works on Solaris 11
+/// only, as earlier versions did not support getifaddrs() API.
+void
+IfaceMgr::detectIfaces(bool update_only) {
+ if (detect_callback_) {
+ if (!detect_callback_(update_only)) {
+ return;
+ }
+ }
+
+ struct ifaddrs* iflist = 0;// The whole interface list
+ struct ifaddrs* ifptr = 0; // The interface we're processing now
+
+ // Gets list of ifaddrs struct
+ if (getifaddrs(&iflist) != 0) {
+ isc_throw(Unexpected, "Network interfaces detection failed.");
+ }
+
+ typedef map<string, IfacePtr> IfaceLst;
+ IfaceLst::iterator iface_iter;
+ IfaceLst ifaces;
+
+ // First lookup for getting interfaces ...
+ for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+ const char * ifname = ifptr->ifa_name;
+ uint ifindex = 0;
+
+ if (!(ifindex = if_nametoindex(ifname))) {
+ // Interface name does not have corresponding index ...
+ freeifaddrs(iflist);
+ isc_throw(Unexpected, "Interface " << ifname << " has no index");
+ }
+
+ iface_iter = ifaces.find(ifname);
+ if (iface_iter != ifaces.end()) {
+ continue;
+ }
+
+ IfacePtr iface;
+ if (update_only) {
+ iface = getIface(ifname);
+ }
+ if (!iface) {
+ iface.reset(new Iface(ifname, ifindex));
+ }
+ iface->setFlags(ifptr->ifa_flags);
+ ifaces.insert(pair<string, IfacePtr>(ifname, iface));
+ }
+
+ // Second lookup to get MAC and IP addresses
+ for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+ iface_iter = ifaces.find(ifptr->ifa_name);
+ if (iface_iter == ifaces.end()) {
+ continue;
+ }
+ // Common byte pointer for following data
+ const uint8_t * ptr = 0;
+ if (ifptr->ifa_addr->sa_family == AF_LINK) {
+ // HWAddr
+ struct sockaddr_dl * ldata =
+ reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+ ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata));
+
+ iface_iter->second->setHWType(ldata->sdl_type);
+ iface_iter->second->setMac(ptr, ldata->sdl_alen);
+ } else if (ifptr->ifa_addr->sa_family == AF_INET6) {
+ // IPv6 Addr
+ struct sockaddr_in6 * adata =
+ reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+ ptr = reinterpret_cast<uint8_t *>(&adata->sin6_addr);
+
+ IOAddress a = IOAddress::fromBytes(AF_INET6, ptr);
+ iface_iter->second->addAddress(a);
+ } else {
+ // IPv4 Addr
+ struct sockaddr_in * adata =
+ reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+ ptr = reinterpret_cast<uint8_t *>(&adata->sin_addr);
+
+ IOAddress a = IOAddress::fromBytes(AF_INET, ptr);
+ iface_iter->second->addAddress(a);
+ }
+ }
+
+ freeifaddrs(iflist);
+
+ // Interfaces registering
+ for (IfaceLst::const_iterator iface_iter = ifaces.begin();
+ iface_iter != ifaces.end(); ++iface_iter) {
+ IfacePtr iface;
+ if (update_only) {
+ iface = getIface(iface_iter->first);
+ }
+ if (!iface) {
+ addInterface(iface_iter->second);
+ }
+ }
+}
+
+/// @brief sets flag_*_ fields
+///
+/// Like Linux version, os specific flags
+///
+/// @params flags
+void Iface::setFlags(uint64_t flags) {
+ flags_ = flags;
+
+ flag_loopback_ = flags & IFF_LOOPBACK;
+ flag_up_ = flags & IFF_UP;
+ flag_running_ = flags & IFF_RUNNING;
+ flag_multicast_ = flags & IFF_MULTICAST;
+ flag_broadcast_ = flags & IFF_BROADCAST;
+}
+
+void
+IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
+ // @todo Currently we ignore the preference to use direct traffic
+ // because it hasn't been implemented for Solaris.
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+}
+
+bool
+IfaceMgr::openMulticastSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ IfaceMgrErrorMsgCallback error_handler) {
+ try {
+ // This should open a socket, bind it to link-local address
+ // and join multicast group.
+ openSocket(iface.getName(), addr, port, iface.flag_multicast_);
+
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler, IfacePtr(),
+ "Failed to open link-local socket on "
+ "interface " << iface.getName() << ": "
+ << ex.what());
+ return (false);
+
+ }
+ return (true);
+}
+
+int
+IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port,
+ const bool join_multicast) {
+ // On Solaris, we bind the socket to in6addr_any and join multicast group
+ // to receive multicast traffic. So, if the multicast is requested,
+ // replace the address specified by the caller with the "unspecified"
+ // address.
+ IOAddress actual_address = join_multicast ? IOAddress("::") : addr;
+ SocketInfo info = packet_filter6_->openSocket(iface, actual_address, port,
+ join_multicast);
+ iface.addSocket(info);
+ return (info.sockfd_);
+}
+
+} // end of isc::dhcp namespace
+} // end of dhcp namespace
+
+#endif
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
new file mode 100644
index 0000000..5512a96
--- /dev/null
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -0,0 +1,1383 @@
+// Copyright (C) 2011-2023 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/std_option_defs.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <util/buffer.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <limits>
+#include <list>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+namespace {
+
+/// @brief the option definitions and the respective space mapping
+///
+/// used for easier initialization of option definitions by space name
+const OptionDefParamsEncapsulation OPTION_DEF_PARAMS[] = {
+ { STANDARD_V4_OPTION_DEFINITIONS, STANDARD_V4_OPTION_DEFINITIONS_SIZE, DHCP4_OPTION_SPACE },
+ { STANDARD_V6_OPTION_DEFINITIONS, STANDARD_V6_OPTION_DEFINITIONS_SIZE, DHCP6_OPTION_SPACE },
+ { DOCSIS3_V4_OPTION_DEFINITIONS, DOCSIS3_V4_OPTION_DEFINITIONS_SIZE, DOCSIS3_V4_OPTION_SPACE },
+ { DOCSIS3_V6_OPTION_DEFINITIONS, DOCSIS3_V6_OPTION_DEFINITIONS_SIZE, DOCSIS3_V6_OPTION_SPACE },
+ { ISC_V6_OPTION_DEFINITIONS, ISC_V6_OPTION_DEFINITIONS_SIZE, ISC_V6_OPTION_SPACE },
+ { MAPE_V6_OPTION_DEFINITIONS, MAPE_V6_OPTION_DEFINITIONS_SIZE, MAPE_V6_OPTION_SPACE },
+ { MAPT_V6_OPTION_DEFINITIONS, MAPT_V6_OPTION_DEFINITIONS_SIZE, MAPT_V6_OPTION_SPACE },
+ { LW_V6_OPTION_DEFINITIONS, LW_V6_OPTION_DEFINITIONS_SIZE, LW_V6_OPTION_SPACE },
+ { V4V6_RULE_OPTION_DEFINITIONS, V4V6_RULE_OPTION_DEFINITIONS_SIZE, V4V6_RULE_OPTION_SPACE },
+ { V4V6_BIND_OPTION_DEFINITIONS, V4V6_BIND_OPTION_DEFINITIONS_SIZE, V4V6_BIND_OPTION_SPACE },
+ { DHCP_AGENT_OPTION_DEFINITIONS, DHCP_AGENT_OPTION_DEFINITIONS_SIZE, DHCP_AGENT_OPTION_SPACE },
+ { LAST_RESORT_V4_OPTION_DEFINITIONS, LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE, LAST_RESORT_V4_OPTION_SPACE },
+ { NULL, 0, "" }
+};
+
+} // namespace
+
+} // namespace dhcp
+} // namespace isc
+
+// static array with factories for options
+map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
+
+// static array with factories for options
+map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
+
+// Static container with option definitions grouped by option space.
+OptionDefContainers LibDHCP::option_defs_;
+
+// Static container with option definitions created in runtime.
+StagedValue<OptionDefSpaceContainer> LibDHCP::runtime_option_defs_;
+
+// Null container.
+const OptionDefContainerPtr null_option_def_container_(new OptionDefContainer());
+
+// Those two vendor classes are used for cable modems:
+
+/// DOCSIS3.0 compatible cable modem
+const char* isc::dhcp::DOCSIS3_CLASS_MODEM = "docsis3.0";
+
+/// DOCSIS3.0 cable modem that has router built-in
+const char* isc::dhcp::DOCSIS3_CLASS_EROUTER = "eRouter1.0";
+
+// Let's keep it in .cc file. Moving it to .h would require including optionDefParams
+// definitions there
+void initOptionSpace(OptionDefContainerPtr& defs,
+ const OptionDefParams* params,
+ size_t params_size);
+
+bool LibDHCP::initialized_ = LibDHCP::initOptionDefs();
+
+const OptionDefContainerPtr
+LibDHCP::getOptionDefs(const string& space) {
+ auto const& container = option_defs_.find(space);
+ if (container != option_defs_.end()) {
+ return (container->second);
+ }
+
+ return (null_option_def_container_);
+}
+
+const OptionDefContainerPtr
+LibDHCP::getVendorOptionDefs(const Option::Universe u, const uint32_t vendor_id) {
+ if (Option::V4 == u) {
+ if (VENDOR_ID_CABLE_LABS == vendor_id) {
+ return getOptionDefs(DOCSIS3_V4_OPTION_SPACE);
+ }
+ } else if (Option::V6 == u) {
+ if (VENDOR_ID_CABLE_LABS == vendor_id) {
+ return getOptionDefs(DOCSIS3_V6_OPTION_SPACE);
+ } else if (ENTERPRISE_ID_ISC == vendor_id) {
+ return getOptionDefs(ISC_V6_OPTION_SPACE);
+ }
+ }
+
+ return (null_option_def_container_);
+}
+
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const string& space, const uint16_t code) {
+ const OptionDefContainerPtr& defs = getOptionDefs(space);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const string& space, const string& name) {
+ const OptionDefContainerPtr& defs = getOptionDefs(space);
+ const OptionDefContainerNameIndex& idx = defs->get<2>();
+ const OptionDefContainerNameRange& range = idx.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
+ const string& name) {
+ const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
+
+ if (!defs) {
+ return (OptionDefinitionPtr());
+ }
+
+ const OptionDefContainerNameIndex& idx = defs->get<2>();
+ const OptionDefContainerNameRange& range = idx.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
+ const uint16_t code) {
+ const OptionDefContainerPtr& defs = getVendorOptionDefs(u, vendor_id);
+
+ if (!defs) {
+ // Weird universe or unknown vendor_id. We don't care. No definitions
+ // one way or another
+ // What is it anyway?
+ return (OptionDefinitionPtr());
+ }
+
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getRuntimeOptionDef(const string& space, const uint16_t code) {
+ OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
+ const OptionDefContainerTypeIndex& index = container->get<1>();
+ const OptionDefContainerTypeRange& range = index.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getRuntimeOptionDef(const string& space, const string& name) {
+ OptionDefContainerPtr container = runtime_option_defs_.getValue().getItems(space);
+ const OptionDefContainerNameIndex& index = container->get<2>();
+ const OptionDefContainerNameRange& range = index.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefContainerPtr
+LibDHCP::getRuntimeOptionDefs(const string& space) {
+ return (runtime_option_defs_.getValue().getItems(space));
+}
+
+void
+LibDHCP::setRuntimeOptionDefs(const OptionDefSpaceContainer& defs) {
+ OptionDefSpaceContainer defs_copy;
+ list<string> option_space_names = defs.getOptionSpaceNames();
+ for (auto const& name : option_space_names) {
+ OptionDefContainerPtr container = defs.getItems(name);
+ for (auto const& def : *container) {
+ OptionDefinitionPtr def_copy(new OptionDefinition(*def));
+ defs_copy.addItem(def_copy);
+ }
+ }
+ runtime_option_defs_ = defs_copy;
+}
+
+void
+LibDHCP::clearRuntimeOptionDefs() {
+ runtime_option_defs_.reset();
+}
+
+void
+LibDHCP::revertRuntimeOptionDefs() {
+ runtime_option_defs_.revert();
+}
+
+void
+LibDHCP::commitRuntimeOptionDefs() {
+ runtime_option_defs_.commit();
+}
+
+OptionDefinitionPtr
+LibDHCP::getLastResortOptionDef(const string& space, const uint16_t code) {
+ OptionDefContainerPtr container = getLastResortOptionDefs(space);
+ const OptionDefContainerTypeIndex& index = container->get<1>();
+ const OptionDefContainerTypeRange& range = index.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+LibDHCP::getLastResortOptionDef(const string& space, const string& name) {
+ OptionDefContainerPtr container = getLastResortOptionDefs(space);
+ const OptionDefContainerNameIndex& index = container->get<2>();
+ const OptionDefContainerNameRange& range = index.equal_range(name);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+
+ return (OptionDefinitionPtr());
+}
+
+OptionDefContainerPtr
+LibDHCP::getLastResortOptionDefs(const string& space) {
+ if (space == DHCP4_OPTION_SPACE) {
+ return getOptionDefs(LAST_RESORT_V4_OPTION_SPACE);
+ }
+
+ return (null_option_def_container_);
+}
+
+bool
+LibDHCP::shouldDeferOptionUnpack(const string& space, const uint16_t code) {
+ return ((space == DHCP4_OPTION_SPACE) &&
+ ((code == DHO_VENDOR_ENCAPSULATED_OPTIONS) ||
+ ((code >= 224) && (code <= 254))));
+}
+
+OptionPtr
+LibDHCP::optionFactory(Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf) {
+ FactoryMap::iterator it;
+ if (u == Option::V4) {
+ it = v4factories_.find(type);
+ if (it == v4factories_.end()) {
+ isc_throw(BadValue, "factory function not registered "
+ "for DHCP v4 option type " << type);
+ }
+ } else if (u == Option::V6) {
+ it = v6factories_.find(type);
+ if (it == v6factories_.end()) {
+ isc_throw(BadValue, "factory function not registered "
+ "for DHCPv6 option type " << type);
+ }
+ } else {
+ isc_throw(BadValue, "invalid universe specified (expected "
+ "Option::V4 or Option::V6");
+ }
+ return (it->second(u, type, buf));
+}
+
+size_t
+LibDHCP::unpackOptions6(const OptionBuffer& buf, const string& option_space,
+ OptionCollection& options,
+ size_t* relay_msg_offset /* = 0 */,
+ size_t* relay_msg_len /* = 0 */) {
+ size_t offset = 0;
+ size_t length = buf.size();
+ size_t last_offset = 0;
+
+ // Get the list of standard option definitions.
+ const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
+ // Runtime option definitions for non standard option space and if
+ // the definition doesn't exist within the standard option definitions.
+ const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
+
+ // @todo Once we implement other option spaces we should add else clause
+ // here and gather option definitions for them. For now leaving option_defs
+ // empty will imply creation of generic Option.
+
+ // Get the search indexes #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
+ const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
+
+ // The buffer being read comprises a set of options, each starting with
+ // a two-byte type code and a two-byte length field.
+ while (offset < length) {
+ // Save the current offset for backtracking
+ last_offset = offset;
+
+ // Check if there is room for another option
+ if (offset + 4 > length) {
+ // Still something but smaller than an option
+ return (last_offset);
+ }
+
+ // Parse the option header
+ uint16_t opt_type = readUint16(&buf[offset], 2);
+ offset += 2;
+
+ uint16_t opt_len = readUint16(&buf[offset], 2);
+ offset += 2;
+
+ if (offset + opt_len > length) {
+ // We peeked at the option header of the next option, but
+ // discovered that it would end up beyond buffer end, so
+ // the option is truncated. Hence we can't parse
+ // it. Therefore we revert back by those bytes (as if
+ // we never parsed them).
+ //
+ // @note it is the responsibility of the caller to throw
+ // an exception on partial parsing
+ return (last_offset);
+ }
+
+ if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
+ // remember offset of the beginning of the relay-msg option
+ *relay_msg_offset = offset;
+ *relay_msg_len = opt_len;
+
+ // do not create that relay-msg option
+ offset += opt_len;
+ continue;
+ }
+
+ if (opt_type == D6O_VENDOR_OPTS) {
+ if (offset + 4 > length) {
+ // Truncated vendor-option. We expect at least
+ // 4 bytes for the enterprise-id field. Let's roll back
+ // option code + option length (4 bytes) and return.
+ return (last_offset);
+ }
+
+ // Parse this as vendor option
+ OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ options.insert(std::make_pair(opt_type, vendor_opt));
+
+ offset += opt_len;
+ continue;
+ }
+
+ // Get all definitions with the particular option code. Note
+ // that option code is non-unique within this container
+ // however at this point we expect to get one option
+ // definition with the particular code. If more are returned
+ // we report an error.
+ OptionDefContainerTypeRange range;
+ // Number of option definitions returned.
+ size_t num_defs = 0;
+
+ // We previously did the lookup only for dhcp6 option space, but with the
+ // addition of S46 options, we now do it for every space.
+ range = idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+
+ // Standard option definitions do not include the definition for
+ // our option or we're searching for non-standard option. Try to
+ // find the definition among runtime option definitions.
+ if (num_defs == 0) {
+ range = runtime_idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+ }
+
+ OptionPtr opt;
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option"
+ " definitions for option type " << opt_type <<
+ " returned. Currently it is not supported to initialize"
+ " multiple option definitions for the same option code."
+ " This will be supported once support for option spaces"
+ " is implemented");
+ } else if (num_defs == 0) {
+ // @todo Don't crash if definition does not exist because
+ // only a few option definitions are initialized right
+ // now. In the future we will initialize definitions for
+ // all options and we will remove this elseif. For now,
+ // return generic option.
+ opt = OptionPtr(new Option(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ } else {
+ try {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ } catch (const SkipThisOptionError&) {
+ opt.reset();
+ }
+ }
+
+ // add option to options
+ if (opt) {
+ options.insert(std::make_pair(opt_type, opt));
+ }
+
+ offset += opt_len;
+ }
+
+ last_offset = offset;
+ return (last_offset);
+}
+
+size_t
+LibDHCP::unpackOptions4(const OptionBuffer& buf, const string& option_space,
+ OptionCollection& options, list<uint16_t>& deferred,
+ bool check) {
+ size_t offset = 0;
+ size_t last_offset = 0;
+
+ // Special case when option_space is dhcp4.
+ bool space_is_dhcp4 = (option_space == DHCP4_OPTION_SPACE);
+
+ // Get the list of standard option definitions.
+ const OptionDefContainerPtr& option_defs = LibDHCP::getOptionDefs(option_space);
+ // Runtime option definitions for non standard option space and if
+ // the definition doesn't exist within the standard option definitions.
+ const OptionDefContainerPtr& runtime_option_defs = LibDHCP::getRuntimeOptionDefs(option_space);
+
+ // Get the search indexes #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex& idx = option_defs->get<1>();
+ const OptionDefContainerTypeIndex& runtime_idx = runtime_option_defs->get<1>();
+
+ // Flexible PAD and END parsing.
+ bool flex_pad = (check && (runtime_idx.count(DHO_PAD) == 0));
+ bool flex_end = (check && (runtime_idx.count(DHO_END) == 0));
+
+ // The buffer being read comprises a set of options, each starting with
+ // a one-byte type code and a one-byte length field.
+ while (offset < buf.size()) {
+ // Save the current offset for backtracking
+ last_offset = offset;
+
+ // Get the option type
+ uint8_t opt_type = buf[offset++];
+
+ // DHO_END is a special, one octet long option
+ // Valid in dhcp4 space or when check is true and
+ // there is a sub-option configured for this code.
+ if ((opt_type == DHO_END) && (space_is_dhcp4 || flex_end)) {
+ // just return. Don't need to add DHO_END option
+ // Don't return offset because it makes this condition
+ // and partial parsing impossible to recognize.
+ return (last_offset);
+ }
+
+ // DHO_PAD is just a padding after DHO_END. Let's continue parsing
+ // in case we receive a message without DHO_END.
+ // Valid in dhcp4 space or when check is true and
+ // there is a sub-option configured for this code.
+ if ((opt_type == DHO_PAD) && (space_is_dhcp4 || flex_pad)) {
+ continue;
+ }
+
+ if (offset + 1 > buf.size()) {
+ // We peeked at the option header of the next option, but
+ // discovered that it would end up beyond buffer end, so
+ // the option is truncated. Hence we can't parse
+ // it. Therefore we revert back (as if we never parsed it).
+ //
+ // @note it is the responsibility of the caller to throw
+ // an exception on partial parsing
+ return (last_offset);
+ }
+
+ uint8_t opt_len = buf[offset++];
+ if (offset + opt_len > buf.size()) {
+ // We peeked at the option header of the next option, but
+ // discovered that it would end up beyond buffer end, so
+ // the option is truncated. Hence we can't parse
+ // it. Therefore we revert back (as if we never parsed it).
+ return (last_offset);
+ }
+
+ // While an empty Host Name option is non-RFC compliant, some clients
+ // do send it. In the spirit of being liberal, we'll just drop it,
+ // rather than the dropping the whole packet. We do not have a
+ // way to log this from here but meh... a PCAP will show it arriving,
+ // and we know we drop it.
+ if (space_is_dhcp4 && opt_len == 0 && opt_type == DHO_HOST_NAME) {
+ continue;
+ }
+
+ // Get all definitions with the particular option code. Note
+ // that option code is non-unique within this container
+ // however at this point we expect to get one option
+ // definition with the particular code. If more are returned
+ // we report an error.
+ OptionDefContainerTypeRange range;
+ // Number of option definitions returned.
+ size_t num_defs = 0;
+
+ // Previously we did the lookup only for "dhcp4" option space, but there
+ // may be standard options in other spaces (e.g. radius). So we now do
+ // the lookup for every space.
+ range = idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+
+ // Standard option definitions do not include the definition for
+ // our option or we're searching for non-standard option. Try to
+ // find the definition among runtime option definitions.
+ if (num_defs == 0) {
+ range = runtime_idx.equal_range(opt_type);
+ num_defs = std::distance(range.first, range.second);
+ }
+
+ // Check if option unpacking must be deferred
+ if (shouldDeferOptionUnpack(option_space, opt_type)) {
+ num_defs = 0;
+ // Store deferred option only once.
+ bool found = false;
+ for (auto const& existing : deferred) {
+ if (existing == opt_type) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ deferred.push_back(opt_type);
+ }
+ }
+
+ if (space_is_dhcp4 &&
+ (opt_type == DHO_VIVSO_SUBOPTIONS ||
+ opt_type == DHO_VIVCO_SUBOPTIONS)) {
+ num_defs = 0;
+ }
+
+ OptionPtr opt;
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option"
+ " definitions for option type " <<
+ static_cast<int>(opt_type) <<
+ " returned. Currently it is not supported to initialize"
+ " multiple option definitions for the same option code."
+ " This will be supported once support for option spaces"
+ " is implemented");
+ } else if (num_defs == 0) {
+ opt = OptionPtr(new Option(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ opt->setEncapsulatedSpace(DHCP4_OPTION_SPACE);
+ } else {
+ try {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ } catch (const SkipThisOptionError&) {
+ opt.reset();
+ }
+ }
+
+ // If we have the option, insert it
+ if (opt) {
+ options.insert(std::make_pair(opt_type, opt));
+ }
+
+ offset += opt_len;
+ }
+ last_offset = offset;
+ return (last_offset);
+}
+
+bool
+LibDHCP::fuseOptions4(OptionCollection& options) {
+ bool result = false;
+ // We need to loop until all options have been fused.
+ for (;;) {
+ uint32_t found = 0;
+ bool found_suboptions = false;
+ // Iterate over all options in the container.
+ for (auto const& option : options) {
+ OptionPtr candidate = option.second;
+ OptionCollection& sub_options = candidate->getMutableOptions();
+ // Fuse suboptions recursively, if any.
+ if (sub_options.size()) {
+ // Fusing suboptions might result in new options with multiple
+ // options having the same code, so we need to iterate again
+ // until no option needs fusing.
+ found_suboptions = LibDHCP::fuseOptions4(sub_options);
+ if (found_suboptions) {
+ result = true;
+ }
+ }
+ OptionBuffer data;
+ OptionCollection suboptions;
+ // Make a copy of the options so we can safely iterate over the
+ // old container.
+ OptionCollection copy = options;
+ for (auto const& old_option : copy) {
+ if (old_option.first == option.first) {
+ // Copy the option data to the buffer.
+ data.insert(data.end(), old_option.second->getData().begin(),
+ old_option.second->getData().end());
+ suboptions.insert(old_option.second->getOptions().begin(),
+ old_option.second->getOptions().end());
+ // Other options might need fusing, so we need to iterate
+ // again until no options needs fusing.
+ found++;
+ }
+ }
+ if (found > 1) {
+ result = true;
+ // Erase the old options from the new container so that only the
+ // new option is present.
+ copy.erase(option.first);
+ // Create new option with entire data.
+ OptionPtr new_option(new Option(candidate->getUniverse(),
+ candidate->getType(), data));
+ // Recreate suboptions container.
+ new_option->getMutableOptions() = suboptions;
+ // Add the new option to the new container.
+ copy.insert(make_pair(candidate->getType(), new_option));
+ // After all options have been fused and new option added,
+ // update the option container with the new container.
+ options = copy;
+ break;
+ } else {
+ found = 0;
+ }
+ }
+ // No option needs fusing, so we can exit the loop.
+ if ((found <= 1) && !found_suboptions) {
+ break;
+ }
+ }
+ return (result);
+}
+
+namespace { // Anonymous namespace.
+
+// VIVCO part of extendVendorOptions4.
+
+void
+extendVivco(OptionCollection& options) {
+ typedef vector<OpaqueDataTuple> TuplesCollection;
+ map<uint32_t, TuplesCollection> vendors_tuples;
+ const auto& range = options.equal_range(DHO_VIVCO_SUBOPTIONS);
+ for (auto it = range.first; it != range.second; ++it) {
+ uint32_t offset = 0;
+ auto const& data = it->second->getData();
+ size_t size;
+ while ((size = data.size() - offset) != 0) {
+ if (size < sizeof(uint32_t)) {
+ options.erase(DHO_VIVCO_SUBOPTIONS);
+ isc_throw(SkipRemainingOptionsError,
+ "Truncated vendor-class information option"
+ << ", length=" << size);
+ }
+ uint32_t vendor_id = readUint32(&data[offset], data.size());
+ offset += 4;
+ try {
+ // From OptionVendorClass::unpack.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE,
+ data.begin() + offset, data.end());
+ vendors_tuples[vendor_id].push_back(tuple);
+ offset += tuple.getTotalLength();
+ } catch (const OpaqueDataTupleError&) {
+ // Ignore this kind of error and continue.
+ break;
+ } catch (const isc::Exception&) {
+ options.erase(DHO_VIVCO_SUBOPTIONS);
+ throw;
+ }
+ }
+ }
+ if (vendors_tuples.empty()) {
+ return;
+ }
+ // Delete the initial option.
+ options.erase(DHO_VIVCO_SUBOPTIONS);
+ // Create a new instance of OptionVendor for each enterprise ID.
+ for (auto const& vendor : vendors_tuples) {
+ if (vendor.second.empty()) {
+ continue;
+ }
+ OptionVendorClassPtr vendor_opt(new OptionVendorClass(Option::V4,
+ vendor.first));
+ for (size_t i = 0; i < vendor.second.size(); ++i) {
+ if (i == 0) {
+ vendor_opt->setTuple(0, vendor.second[0]);
+ } else {
+ vendor_opt->addTuple(vendor.second[i]);
+ }
+ }
+ // Add the new instance of VendorOption with respective sub-options for
+ // this enterprise ID.
+ options.insert(std::make_pair(DHO_VIVCO_SUBOPTIONS, vendor_opt));
+ }
+}
+
+// VIVSO part of extendVendorOptions4.
+
+void
+extendVivso(OptionCollection& options) {
+ map<uint32_t, OptionCollection> vendors_data;
+ const auto& range = options.equal_range(DHO_VIVSO_SUBOPTIONS);
+ for (auto it = range.first; it != range.second; ++it) {
+ uint32_t offset = 0;
+ auto const& data = it->second->getData();
+ size_t size;
+ while ((size = data.size() - offset) != 0) {
+ if (size < sizeof(uint32_t)) {
+ options.erase(DHO_VIVSO_SUBOPTIONS);
+ isc_throw(SkipRemainingOptionsError,
+ "Truncated vendor-specific information option"
+ << ", length=" << size);
+ }
+ uint32_t vendor_id = readUint32(&data[offset], data.size());
+ offset += 4;
+ const OptionBuffer vendor_buffer(data.begin() + offset, data.end());
+ try {
+ offset += LibDHCP::unpackVendorOptions4(vendor_id, vendor_buffer,
+ vendors_data[vendor_id]);
+ } catch (const SkipThisOptionError&) {
+ // Ignore this kind of error and continue.
+ break;
+ } catch (const isc::Exception&) {
+ options.erase(DHO_VIVSO_SUBOPTIONS);
+ throw;
+ }
+ }
+ }
+ if (vendors_data.empty()) {
+ return;
+ }
+ // Delete the initial option.
+ options.erase(DHO_VIVSO_SUBOPTIONS);
+ // Create a new instance of OptionVendor for each enterprise ID.
+ for (auto const& vendor : vendors_data) {
+ OptionVendorPtr vendor_opt(new OptionVendor(Option::V4, vendor.first));
+ for (auto const& option : vendor.second) {
+ vendor_opt->addOption(option.second);
+ }
+ // Add the new instance of VendorOption with respective sub-options for
+ // this enterprise ID.
+ options.insert(std::make_pair(DHO_VIVSO_SUBOPTIONS, vendor_opt));
+ }
+}
+
+} // end of anonymous namespace.
+
+void
+LibDHCP::extendVendorOptions4(OptionCollection& options) {
+ extendVivco(options);
+ extendVivso(options);
+}
+
+size_t
+LibDHCP::unpackVendorOptions6(const uint32_t vendor_id, const OptionBuffer& buf,
+ OptionCollection& options) {
+ size_t offset = 0;
+ size_t length = buf.size();
+
+ // Get the list of option definitions for this particular vendor-id
+ const OptionDefContainerPtr& option_defs =
+ LibDHCP::getVendorOptionDefs(Option::V6, vendor_id);
+
+ // Get the search index #1. It allows to search for option definitions
+ // using option code. If there's no such vendor-id space, we're out of luck
+ // anyway.
+ const OptionDefContainerTypeIndex* idx = NULL;
+ if (option_defs) {
+ idx = &(option_defs->get<1>());
+ }
+
+ // The buffer being read comprises a set of options, each starting with
+ // a two-byte type code and a two-byte length field.
+ while (offset < length) {
+ if (offset + 4 > length) {
+ isc_throw(SkipRemainingOptionsError,
+ "Vendor option parse failed: truncated header");
+ }
+
+ uint16_t opt_type = readUint16(&buf[offset], 2);
+ offset += 2;
+
+ uint16_t opt_len = readUint16(&buf[offset], 2);
+ offset += 2;
+
+ if (offset + opt_len > length) {
+ isc_throw(SkipRemainingOptionsError,
+ "Vendor option parse failed. Tried to parse "
+ << offset + opt_len << " bytes from " << length
+ << "-byte long buffer.");
+ }
+
+ OptionPtr opt;
+ opt.reset();
+
+ // If there is a definition for such a vendor option...
+ if (idx) {
+ // Get all definitions with the particular option
+ // code. Note that option code is non-unique within this
+ // container however at this point we expect to get one
+ // option definition with the particular code. If more are
+ // returned we report an error.
+ const OptionDefContainerTypeRange& range =
+ idx->equal_range(opt_type);
+ // Get the number of returned option definitions for the
+ // option code.
+ size_t num_defs = std::distance(range.first, range.second);
+
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported
+ // right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option"
+ " definitions for option type " << opt_type <<
+ " returned. Currently it is not supported to"
+ " initialize multiple option definitions for the"
+ " same option code. This will be supported once"
+ " support for option spaces is implemented");
+ } else if (num_defs == 1) {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ }
+ }
+
+ // This can happen in one of 2 cases:
+ // 1. we do not have definitions for that vendor-space
+ // 2. we do have definitions, but that particular option was
+ // not defined
+
+ if (!opt) {
+ opt = OptionPtr(new Option(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ }
+
+ // add option to options
+ if (opt) {
+ options.insert(std::make_pair(opt_type, opt));
+ }
+ offset += opt_len;
+ }
+
+ return (offset);
+}
+
+size_t
+LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffer& buf,
+ OptionCollection& options) {
+ size_t offset = 0;
+
+ // Get the list of standard option definitions.
+ const OptionDefContainerPtr& option_defs =
+ LibDHCP::getVendorOptionDefs(Option::V4, vendor_id);
+ // Get the search index #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex* idx = NULL;
+ if (option_defs) {
+ idx = &(option_defs->get<1>());
+ }
+
+ // The buffer being read comprises a set of options, each starting with
+ // a one-byte type code and a one-byte length field.
+ while (offset < buf.size()) {
+ // Note that Vendor-Specific info option (RFC3925) has a
+ // different option format than Vendor-Spec info for
+ // DHCPv6. (there's additional layer of data-length)
+ uint8_t data_len = buf[offset++];
+
+ if (offset + data_len > buf.size()) {
+ // The option is truncated.
+ isc_throw(SkipRemainingOptionsError,
+ "Attempt to parse truncated vendor option");
+ }
+
+ uint8_t offset_end = offset + data_len;
+
+ // beginning of data-chunk parser
+ while (offset < offset_end) {
+ uint8_t opt_type = buf[offset++];
+
+ // No DHO_END or DHO_PAD in vendor options
+
+ if (offset + 1 > offset_end) {
+ // opt_type must be cast to integer so as it is not
+ // treated as unsigned char value (a number is
+ // presented in error message).
+ isc_throw(SkipRemainingOptionsError,
+ "Attempt to parse truncated vendor option "
+ << static_cast<int>(opt_type));
+ }
+
+ uint8_t opt_len = buf[offset++];
+ if (offset + opt_len > offset_end) {
+ isc_throw(SkipRemainingOptionsError,
+ "Option parse failed. Tried to parse "
+ << offset + opt_len << " bytes from " << buf.size()
+ << "-byte long buffer.");
+ }
+
+ OptionPtr opt;
+ opt.reset();
+
+ if (idx) {
+ // Get all definitions with the particular option
+ // code. Note that option code is non-unique within
+ // this container however at this point we expect to
+ // get one option definition with the particular
+ // code. If more are returned we report an error.
+ const OptionDefContainerTypeRange& range =
+ idx->equal_range(opt_type);
+ // Get the number of returned option definitions for
+ // the option code.
+ size_t num_defs = std::distance(range.first, range.second);
+
+ if (num_defs > 1) {
+ // Multiple options of the same code are not
+ // supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple"
+ " option definitions for option type "
+ << opt_type << " returned. Currently it is"
+ " not supported to initialize multiple option"
+ " definitions for the same option code."
+ " This will be supported once support for"
+ " option spaces is implemented");
+ } else if (num_defs == 1) {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ isc_throw_assert(def);
+ opt = def->optionFactory(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
+ }
+ }
+
+ if (!opt) {
+ opt = OptionPtr(new Option(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ }
+
+ options.insert(std::make_pair(opt_type, opt));
+ offset += opt_len;
+
+ } // end of data-chunk
+
+ break; // end of the vendor block.
+ }
+ return (offset);
+}
+
+void
+LibDHCP::packOptions4(OutputBuffer& buf, const OptionCollection& options,
+ bool top, bool check) {
+ OptionCollection agent;
+ OptionPtr end;
+
+ // We only look for type when we're the top level
+ // call that starts packing for options for a packet.
+ // This way we avoid doing type logic in all ensuing
+ // recursive calls.
+ if (top) {
+ auto x = options.find(DHO_DHCP_MESSAGE_TYPE);
+ if (x != options.end()) {
+ x->second->pack(buf, check);
+ }
+ }
+
+ for (auto const& option : options) {
+ // TYPE is already done, RAI and END options must be last.
+ switch (option.first) {
+ case DHO_DHCP_MESSAGE_TYPE:
+ break;
+ case DHO_DHCP_AGENT_OPTIONS:
+ agent.insert(make_pair(DHO_DHCP_AGENT_OPTIONS, option.second));
+ break;
+ case DHO_END:
+ end = option.second;
+ break;
+ default:
+ option.second->pack(buf, check);
+ break;
+ }
+ }
+
+ // Add the RAI option if it exists.
+ for (auto const& option : agent) {
+ option.second->pack(buf, check);
+ }
+
+ // And at the end the END option.
+ if (end) {
+ end->pack(buf, check);
+ }
+}
+
+bool
+LibDHCP::splitOptions4(OptionCollection& options,
+ ScopedOptionsCopyContainer& scoped_options,
+ uint32_t used) {
+ bool result = false;
+ // We need to loop until all options have been split.
+ uint32_t tries = 0;
+ for (;; tries++) {
+ // Let's not do this forever if there is a bug hiding here somewhere...
+ // 65535 times should be enough for any packet load...
+ if (tries == std::numeric_limits<uint16_t>::max()) {
+ isc_throw(Unexpected, "packet split failed after trying "
+ << tries << " times.");
+ }
+ bool found = false;
+ // Make a copy of the options so we can safely iterate over the
+ // old container.
+ OptionCollection copy = options;
+ // Iterate over all options in the container.
+ for (auto const& option : options) {
+ OptionPtr candidate = option.second;
+ OptionCollection& sub_options = candidate->getMutableOptions();
+ // Split suboptions recursively, if any.
+ OptionCollection distinct_options;
+ bool updated = false;
+ bool found_suboptions = false;
+ // There are 3 cases when the total size is larger than (255 - used):
+ // 1. option has no suboptions and has large data
+ // 2. option has large suboptions and has no data
+ // 3. option has both options and suboptions:
+ // 3.1. suboptions are large and data is large
+ // 3.2. suboptions are large and data is small
+ // 3.3. suboptions are small and data is large
+ // 3.4. suboptions are small and data is small but combined they are large
+ // All other combinations reside in total size smaller than (255 - used):
+ // 4. no split of any suboption or data:
+ // 4.1 option has no suboptions and has small data
+ // 4.2 option has small suboptions and has no data
+ // 4.3 option has both small suboptions and small data
+ // 4.4 option has no suboptions and has no data
+ if (sub_options.size()) {
+ // The 2. and 3. and 4.2 and 4.3 cases are handled here (the suboptions part).
+ ScopedOptionsCopyPtr candidate_scoped_options(new ScopedSubOptionsCopy(candidate));
+ found_suboptions = LibDHCP::splitOptions4(sub_options, scoped_options,
+ used + candidate->getHeaderLen());
+ // There are 3 cases here:
+ // 2. option has large suboptions and has no data
+ // 3. option has both options and suboptions:
+ // 3.1. suboptions are large and data is large so there is suboption splitting
+ // and found_suboptions is true
+ // 3.2. suboptions are large and data is small so there is suboption splitting
+ // and found_suboptions is true
+ // 3.3. suboptions are small and data is large so there is no suboption splitting
+ // and found_suboptions is false
+ // 3.4. suboptions are small and data is small so there is no suboption splitting
+ // and found_suboptions is false but combined they are large
+ // 4. no split of any suboption or data
+ // Also split if the overflow is caused by adding the suboptions
+ // to the option data.
+ if (found_suboptions || candidate->len() > (255 - used)) {
+ // The 2. and 3. cases are handled here (the suboptions part).
+ updated = true;
+ scoped_options.push_back(candidate_scoped_options);
+ // Erase the old options from the new container so that only
+ // the new options are present.
+ copy.erase(option.first);
+ result = true;
+ // If there are suboptions which have been split, one parent
+ // option will be created for each of the chunk of the
+ // suboptions. If the suboptions have not been split,
+ // but they cause overflow when added to the option data,
+ // one parent option will contain the option data and one
+ // parent option will be created for each suboption.
+ // This will guarantee that none of the options plus
+ // suboptions will have more than 255 bytes.
+ for (auto sub_option : candidate->getMutableOptions()) {
+ OptionPtr data_sub_option(new Option(candidate->getUniverse(),
+ candidate->getType(),
+ OptionBuffer(0)));
+ data_sub_option->addOption(sub_option.second);
+ distinct_options.insert(make_pair(candidate->getType(), data_sub_option));
+ }
+ }
+ }
+ // The 1. and 3. and 4. cases are handled here (the data part).
+ // Create a new option containing only data that needs to be split
+ // and no suboptions (which are inserted in completely separate
+ // options which are added at the end).
+ OptionPtr data_option(new Option(candidate->getUniverse(),
+ candidate->getType(),
+ OptionBuffer(candidate->getData().begin(),
+ candidate->getData().end())));
+ OutputBuffer buf(0);
+ data_option->pack(buf, false);
+ uint32_t header_len = candidate->getHeaderLen();
+ // At least 1 + header length bytes must be available.
+ if (used >= 255 - header_len) {
+ isc_throw(BadValue, "there is no space left to split option "
+ << candidate->getType() << " after parent already used "
+ << used);
+ }
+ // Maximum option buffer size is 255 - header size - buffer size
+ // already used by parent options.
+ uint8_t len = 255 - header_len - used;
+ // Current option size after split is the sum of the data and the
+ // header size. The suboptions are serialized in separate options.
+ // The header is duplicated in all new options, but the rest of the
+ // data must be split and serialized.
+ uint32_t size = buf.getLength() - header_len;
+ // Only split if data does not fit in the current option.
+ // There are 3 cases here:
+ // 1. option has no suboptions and has large data
+ // 3. option has both options and suboptions:
+ // 3.1. suboptions are large and data is large
+ // 3.2. suboptions are large and data is small
+ // 3.3. suboptions are small and data is large
+ // 3.4. suboptions are small and data is small but combined they are large
+ // 4. no split of any suboption or data
+ if (size > len) {
+ // The 1. and 3.1. and 3.3 cases are handled here (the data part).
+ // Erase the old option from the new container so that only new
+ // options are present.
+ if (!updated) {
+ updated = true;
+ // Erase the old options from the new container so that only
+ // the new options are present.
+ copy.erase(option.first);
+ result = true;
+ }
+ uint32_t offset = 0;
+ // Drain the option buffer in multiple new options until all
+ // data is serialized.
+ for (; offset != size;) {
+ // Adjust the data length of the new option if remaining
+ // data is less than the 255 - header size (for the last
+ // option).
+ if (size - offset < len) {
+ len = size - offset;
+ }
+ // Create new option with data starting from offset and
+ // containing truncated length.
+ const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+ data += header_len;
+ OptionPtr new_option(new Option(candidate->getUniverse(),
+ candidate->getType(),
+ OptionBuffer(data + offset,
+ data + offset + len)));
+ // Adjust the offset for remaining data to be written to the
+ // next new option.
+ offset += len;
+ // Add the new option to the new container.
+ copy.insert(make_pair(candidate->getType(), new_option));
+ }
+ } else if ((candidate->len() > (255 - used)) && size) {
+ // The 3.2 and 3.4 cases are handled here (the data part).
+ // Also split if the overflow is caused by adding the suboptions
+ // to the option data (which should be of non zero size).
+ // Add the new option to the new container.
+ copy.insert(make_pair(candidate->getType(), data_option));
+ }
+ if (updated) {
+ // Add the new options containing the split suboptions, if any,
+ // to the new container.
+ copy.insert(distinct_options.begin(), distinct_options.end());
+ // After all new options have been split and added, update the
+ // option container with the new container.
+ options = copy;
+ // Other options might need splitting, so we need to iterate
+ // again until no option needs splitting.
+ found = true;
+ break;
+ }
+ }
+ // No option needs splitting, so we can exit the loop.
+ if (!found) {
+ break;
+ }
+ }
+ return (result);
+}
+
+void
+LibDHCP::packOptions6(OutputBuffer& buf, const OptionCollection& options) {
+ for (auto const& option : options) {
+ option.second->pack(buf);
+ }
+}
+
+void
+LibDHCP::OptionFactoryRegister(Option::Universe u, uint16_t opt_type,
+ Option::Factory* factory) {
+ switch (u) {
+ case Option::V6:
+ {
+ if (v6factories_.find(opt_type) != v6factories_.end()) {
+ isc_throw(BadValue, "There is already DHCPv6 factory registered "
+ << "for option type " << opt_type);
+ }
+ v6factories_[opt_type] = factory;
+ return;
+ }
+ case Option::V4:
+ {
+ // Option 0 is special (a one octet-long, equal 0) PAD option. It is never
+ // instantiated as an Option object, but rather consumed during packet parsing.
+ if (opt_type == 0) {
+ isc_throw(BadValue, "Cannot redefine PAD option (code=0)");
+ }
+ // Option 255 is never instantiated as an option object. It is special
+ // (a one-octet equal 255) option that is added at the end of all options
+ // during packet assembly. It is also silently consumed during packet parsing.
+ if (opt_type > 254) {
+ isc_throw(BadValue, "Too big option type for DHCPv4, only 0-254 allowed.");
+ }
+ if (v4factories_.find(opt_type) != v4factories_.end()) {
+ isc_throw(BadValue, "There is already DHCPv4 factory registered "
+ << "for option type " << opt_type);
+ }
+ v4factories_[opt_type] = factory;
+ return;
+ }
+ default:
+ isc_throw(BadValue, "Invalid universe type specified.");
+ }
+
+ return;
+}
+
+bool
+LibDHCP::initOptionDefs() {
+ for (uint32_t i = 0; OPTION_DEF_PARAMS[i].optionDefParams; ++i) {
+ string space = OPTION_DEF_PARAMS[i].space;
+ option_defs_[space] = OptionDefContainerPtr(new OptionDefContainer);
+ initOptionSpace(option_defs_[space],
+ OPTION_DEF_PARAMS[i].optionDefParams,
+ OPTION_DEF_PARAMS[i].size);
+ }
+
+ return (true);
+}
+
+uint32_t
+LibDHCP::optionSpaceToVendorId(const string& option_space) {
+ // 8 is a minimal length of "vendor-X" format
+ if ((option_space.size() < 8) || (option_space.substr(0,7) != "vendor-")) {
+ return (0);
+ }
+
+ int64_t check;
+ try {
+ // text after "vendor-", supposedly numbers only
+ string x = option_space.substr(7);
+
+ check = boost::lexical_cast<int64_t>(x);
+ } catch (const boost::bad_lexical_cast &) {
+ return (0);
+ }
+
+ if ((check < 0) || (check > std::numeric_limits<uint32_t>::max())) {
+ return (0);
+ }
+
+ // value is small enough to fit
+ return (static_cast<uint32_t>(check));
+}
+
+void
+initOptionSpace(OptionDefContainerPtr& defs, const OptionDefParams* params,
+ size_t params_size) {
+ // Container holding vendor options is typically not initialized, as it
+ // is held in map of null pointers. We need to initialize here in this
+ // case.
+ if (!defs) {
+ defs.reset(new OptionDefContainer());
+ } else {
+ defs->clear();
+ }
+
+ for (size_t i = 0; i < params_size; ++i) {
+ string encapsulates(params[i].encapsulates);
+ if (!encapsulates.empty() && params[i].array) {
+ isc_throw(isc::BadValue, "invalid standard option definition: "
+ << "option with code '" << params[i].code
+ << "' may not encapsulate option space '"
+ << encapsulates << "' because the definition"
+ << " indicates that this option comprises an array"
+ << " of values");
+ }
+
+ // Depending whether an option encapsulates an option space or not
+ // we pick different constructor to create an instance of the option
+ // definition.
+ OptionDefinitionPtr definition;
+ if (encapsulates.empty()) {
+ // Option does not encapsulate any option space.
+ definition.reset(new OptionDefinition(params[i].name,
+ params[i].code,
+ params[i].space,
+ params[i].type,
+ params[i].array));
+ } else {
+ // Option does encapsulate an option space.
+ definition.reset(new OptionDefinition(params[i].name,
+ params[i].code,
+ params[i].space,
+ params[i].type,
+ params[i].encapsulates));
+
+ }
+
+ for (size_t rec = 0; rec < params[i].records_size; ++rec) {
+ definition->addRecordField(params[i].records[rec]);
+ }
+
+ try {
+ definition->validate();
+ } catch (const isc::Exception&) {
+ // This is unlikely event that validation fails and may
+ // be only caused by programming error. To guarantee the
+ // data consistency we clear all option definitions that
+ // have been added so far and pass the exception forward.
+ defs->clear();
+ throw;
+ }
+
+ // option_defs is a multi-index container with no unique indexes
+ // so push_back can't fail).
+ static_cast<void>(defs->push_back(definition));
+ }
+}
diff --git a/src/lib/dhcp/libdhcp++.dox b/src/lib/dhcp/libdhcp++.dox
new file mode 100644
index 0000000..ef47dd7
--- /dev/null
+++ b/src/lib/dhcp/libdhcp++.dox
@@ -0,0 +1,280 @@
+// Copyright (C) 2012-2020 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/.
+
+/**
+@page libdhcp libkea-dhcp++ - Low Level DHCP Library
+
+@section libdhcpIntro Libdhcp++ Library Introduction
+
+libdhcp++ is an all-purpose DHCP-manipulation library, written in
+C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
+options parsing and assembly, interface detection (currently on
+Linux, FreeBSD, NetBSD, OpenBSD, Max OS X, and Solaris 11) and socket operations.
+It is a generic purpose library that
+can be used by server, client, relay, performance tools and other DHCP-related
+tools. For server specific library, see \ref libdhcpsrv. Please do not
+add any server-specific code to libdhcp++ and use \ref libdhcpsrv instead.
+
+The following classes for packet manipulation are implemented:
+
+- isc::dhcp::Pkt4 - represents a DHCPv4 packet.
+- isc::dhcp::Pkt6 - represents a DHCPv6 packet.
+- isc::dhcp::Pkt4o6 - represents a DHCPv4-over-DHCPv6 packet.
+
+The following pointer types are defined: \c Pkt4Ptr, \c Pkt6Ptr and Pkt4o6Ptr.
+They are smart pointers using the \c boost::shared_ptr type. There are no const
+versions of packet types defined, as we assume that hooks can modify any
+aspect of the packet at almost any stage of processing.
+
+Both packet types use a collection of \ref isc::dhcp::Option objects to
+represent DHCPv4 and DHCPv6 options. The base class @c Option can be used to
+represent generic option that contains collection of
+bytes. Depending on whether the option is instantiated as a DHCPv4 or DHCPv6
+option, it will adjust its header (DHCPv4 options use 1 octet for
+type and 1 octet for length, while DHCPv6 options use 2 bytes for
+each).
+
+There are many specialized classes that are intended to handle options having
+specific formats:
+- isc::dhcp::Option4AddrLst -- DHCPv4 option, contains one or more IPv4 addresses;
+- isc::dhcp::Option6AddrLst -- DHCPv6 option, contains one or more IPv6 addresses;
+- isc::dhcp::Option4ClientFqdn -- DHCPv4 Client FQDN option;
+- isc::dhcp::Option6ClientFqdn -- DHCPv6 Client FQDN option;
+- isc::dhcp::Option6IAAddr -- DHCPv6 option, represents an IAADDR option (an option that
+ contains IPv6 address with extra parameters);
+- isc::dhcp::Option6IAPrefix -- DHCPv6 option, represents an IAPREFIX option (an option
+ that contains IPv6 prefix in prefix delegation);
+- isc::dhcp::Option6IA -- DHCPv6 option used to store IA_NA and its suboptions.
+- isc::dhcp::Option6StatusCode -- DHCPv6 option, carries a status code to the client;
+- isc::dhcp::OptionCustom -- Represents an option having many different formats, where
+ data fields can be accessed in a convenient way;
+- isc::dhcp::OptionInt -- DHCPv4 or DHCPv6 option, carries a single numeric value;
+- isc::dhcp::OptionString -- DHCPv4 or DHCPv6 option, carries a text value;
+- isc::dhcp::OptionVendor -- DHCPv4 or DHCPv6 option, carries Vendor Specific
+ Information;
+- isc::dhcp::OptionVendorClass -- DHCPv4 or DHCPv6 option, contains vendor class
+ information.
+
+Various options can store sub-options (i.e. options that are stored within an
+option rather than in a message directly). This functionality is commonly used in
+DHCPv6, but is rarely used in DHCPv4. \ref isc::dhcp::Option::addOption(),
+\ref isc::dhcp::Option::delOption(), \ref isc::dhcp::Option::getOption() can
+be used to add, remove and retrieve sub-options from within an option.
+
+@section libdhcpDhcp4o6 DHCPv4-over-DHCPv6 support
+
+The DHCPv4-over-DHCPv6 packet class (\c Pkt4o6) is derived from
+the DHCPv4 packet class (\c Pkt4) with:
+
+- un extra member pointing to the encapsulating DHCPv6 packet, accessible
+ by \ref isc::dhcp::Pkt4o6::getPkt6()
+- a specialized isc::dhcp::Pkt::pack() method which builds the wire-format
+ data of the whole DHCPv6-over-DHCPv4 packet.
+
+To avoid the extra overhead of dynamic casts the isc::dhcp::Pkt4::isDhcp4o6()
+virtual method returns true for \c Pkt4o6 instances and false for others.
+
+@section libdhcpRelay Relay v6 support in Pkt6
+
+DHCPv6 clients that are not connected to the same link as DHCPv6
+servers need relays to reach the server. Each relay receives a message
+on a client facing interface, encapsulates it into RELAY_MSG option
+and sends as RELAY_FORW message towards the server (or the next relay,
+which is closer to the server). This procedure can be repeated up to
+32 times. Kea is able to support up to 32 relays. Each traversed relay
+may add certain options. The most obvious example is interface-id
+option, but there may be other options as well. Each relay may add such
+an option, regardless of whether other relays added it before. Thanks
+to encapsulation, those options are separated and it is possible to
+differentiate which relay inserted specific instance of an option.
+
+Interface-id is used to identify a subnet (or interface) the original message
+came from and is used for that purpose on two occasions. First, the server
+uses the interface-id included by the first relay (the one closest to
+the client) to select appropriate subnet for a given request. Server includes
+that interface-id in its copy, when sending data back to the client.
+This will be used by the relay to choose proper interface when forwarding
+response towards the client.
+
+The Pkt6 class has a public \c Pkt6::relay_info_ field, which is of type \c Pkt6::RelayInfo.
+This is a simple structure that represents the information in each RELAY_FORW
+or RELAY_REPL message. It is important to understand the order in which
+the data appear here. Consider the following network:
+
+\verbatim
+client-------relay1-----relay2-----relay3----server
+\endverbatim
+
+Client will transmit SOLICIT message. Relay1 will forward it as
+RELAY_FORW with SOLICIT in it. Relay2 forward it as RELAY_FORW with
+RELAY_FORW with SOLICIT in it. Finally the third relay will add yet
+another RELAY_FORW around it. The server will parse the packet and
+create \c Pkt6 object for it. Its relay_info_ will have 3
+elements. Packet parsing is done in reverse order, compare to the
+order the packet traversed in the network. The first element
+(relay_info_[0]) will represent relay3 information (the "last" relay or
+in other words the one closest to the server). The second element
+will represent relay2. The third element (relay_info_[2]) will represent
+the first relay (relay1) or in other words the one closest to the client.
+
+Packets sent by the server must maintain the same encapsulation order.
+This is easy to do - just copy data from client's message object into
+server's response object. See @ref isc::dhcp::Pkt6::RelayInfo for details.
+
+@section libdhcpIfaceMgr Interface Manager
+
+Interface Manager (or IfaceMgr) is an abstraction layer for low-level
+network operations. In particular, it provides information about existing
+network interfaces See @ref isc::dhcp::Iface class and
+@ref isc::dhcp::IfaceMgr::detectIfaces() and @ref isc::dhcp::IfaceMgr::getIface().
+
+Generic parts of the code are contained in the @ref isc::dhcp::IfaceMgr class in
+src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
+files, e.g. iface_mgr_linux.cc, iface_mgr_bsd. The separation should be
+maintained when developing additional code.
+
+Other useful methods are dedicated to transmission
+(\ref isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
+(\ref isc::dhcp::IfaceMgr::receive4() and \ref isc::dhcp::IfaceMgr::receive6()).
+Note that \c receive4() and \c receive6() methods may return NULL, e.g.
+when timeout is reached or if the DHCP daemon receives a signal.
+
+@section libdhcpPktFilter Switchable Packet Filter objects used by Interface Manager
+
+The well known problem of DHCPv4 implementation is that it must be able to
+provision devices which don't have an IPv4 address yet (the IPv4 address is
+one of the configuration parameters provided by DHCP server to a client).
+One way to communicate with such a device is to send server's response to
+a broadcast address. An obvious drawback of this approach is that the server's
+response will be received and processed by all clients in the particular
+network. Therefore, the preferred approach is that the server unicasts its
+response to a new address being assigned for the client. This client will
+identify itself as a target of this message by checking chaddr and/or
+Client Identifier value. At the same time, the other clients in the network
+will not receive the unicast message. The major problem that arises with this
+approach is that the client without an IP address doesn't respond to ARP
+messages. As a result, server's response will not be sent over IP/UDP
+socket because the system kernel will fail to resolve client's link-layer
+address.
+
+Kea supports the use of raw sockets to create a complete Data-link/IP/UDP/DHCPv4
+stack. By creating each layer of the outgoing packet, the Kea logic has full
+control over the frame contents and it may bypass the use of ARP to inject the
+link layer address into the frame.
+
+The low level operations on raw sockets are implemented within the "packet
+filtering" classes derived from @c isc::dhcp::PktFilter. The implementation
+of these classes is specific to the operating system. On Linux the
+@c isc::dhcp::PktFilterLPF is used. On BSD systems the
+@c isc::dhcp::PktFilterBPF is used.
+
+The raw sockets are bound to a specific interface, not to the IP address/UDP port.
+Therefore, the system kernel doesn't have means to verify that Kea is listening
+to the DHCP traffic on the specific address and port. This has two major implications:
+- It is possible to run another DHCPv4 sever instance which will bind socket to the
+same address and port.
+- An attempt to send a unicast message to the DHCPv4 server will result in ICMP
+"Port Unreachable" message being sent by the kernel (which is unaware that the
+DHCPv4 service is actually running).
+
+In order to overcome these issues, the packet filtering classes open a
+regular IP/UDP socket which coexists with the raw socket. The socket is referred
+to as "fallback socket" in the Kea code. All packets received through this socket
+are discarded.
+
+@section libdhcpPktFilter6 Switchable Packet Filters for DHCPv6
+
+The DHCPv6 implementation doesn't suffer from the problems described in \ref
+libdhcpPktFilter. Therefore, the socket creation and methods used to send
+and receive DHCPv6 messages are common for all OSes. However, there is
+still a need to customize the operations on the sockets to reliably unit test
+the \ref isc::dhcp::IfaceMgr logic.
+
+The \ref isc::dhcp::IfaceMgr::openSockets6 function examines configuration
+of detected interfaces for their availability to listen DHCPv6 traffic. For
+all running interfaces (except local loopback) it will try to open a socket
+and bind it to the link-local or global unicast address. The socket will
+not be opened on the interface which is down or for which it was explicitly
+specified that it should not be used to listen to DHCPv6 messages. There is
+a substantial amount of logic in this function that has to be unit tested for
+various interface configurations, e.g.:
+- multiple interfaces with link-local addresses only
+- multiple interfaces, some of them having global unicast addresses,
+- multiple interfaces, some of them disabled
+- no interfaces
+
+The \ref isc::dhcp::IfaceMgr::openSockets6 function attempts to open
+sockets on detected interfaces. At the same time, the number of interfaces,
+and their configuration is specific to OS where the tests are being run.
+So the test doesn't have any means to configure interfaces for the test case
+being run. Moreover, a unit test should not change the configuration of the
+system. For example, a change to the configuration of the interface which
+is used to access the machine running a test, may effectively break the
+access to this machine.
+
+In order to overcome the problem described above, the unit tests use
+fake interfaces which can be freely added, configured and removed from the
+\ref isc::dhcp::IfaceMgr. Obviously, it is not possible to open a socket
+on a fake interface, nor use it to send or receive IP packets. To mimic
+socket operations on fake interfaces it is required that the functions
+which open sockets, send messages and receive messages have to be
+customizable. This is achieved by implementation of replaceable packet
+filter objects which derive from the \ref isc::dhcp::PktFilter6 class.
+The default implementation of this class is \ref isc::dhcp::PktFilterInet6
+which creates a regular datagram IPv6/UDPv6 socket. The unit tests use a
+stub implementation isc::dhcp::test::PktFilter6Stub which contains no-op
+functions.
+
+Use \ref isc::dhcp::IfaceMgr::setPacketFilter function to set the custom packet
+filter object to be used by Interface Manager.
+
+@section libdhcpErrorLogging Logging non-fatal errors in IfaceMgr
+
+The libdhcp++ is a common library, meant to be used by various components,
+such as DHCP servers, relays and clients. It is also used by a perfdhcp
+benchmarking application. It provides a basic capabilities for these
+applications to perform operations on DHCP messages such as encoding
+or decoding them. It also provides capabilities to perform low level
+operations on sockets. Since libdhcp++ is a common library, its dependency
+on other BINDX modules should be minimal. In particular, errors occurring
+in the libdhcp++ are reported using exceptions, not a BINDX logger. This
+works well in most cases, but there are some cases in which it is
+undesired for a function to throw an exception in case of non-fatal error.
+
+The typical case, when exception should not be thrown, is when the \ref
+isc::dhcp::IfaceMgr::openSockets4 or \ref isc::dhcp::IfaceMgr::openSockets6
+fails to open a socket on one of the interfaces. This should not preclude
+the function from attempting to open sockets on other interfaces, which
+would be the case if exception was thrown.
+
+In such cases the IfaceMgr makes use of error handler callback function
+which may be installed by a caller. This function must implement the
+isc::dhcp::IfaceMgrErrorMsgCallback. Note that it is allowed to pass a NULL
+value instead, which would result falling back to a default behavior and
+exception will be thrown. If non-NULL value is provided, the
+\ref isc::dhcp::IfaceMgr will call error handler function and pass an
+error string as an argument. The handler function may use its logging
+mechanism to log this error message. In particular, the DHCP server
+will use BINDX logger to log the error message.
+
+@section libdhcpMTConsiderations Multi-Threading Consideration for DHCP library
+
+By default APIs provided by the DHCP library are not thread safe.
+For instance packets or options are not thread safe. Exception are:
+
+ - external sockets are thread safe (the container used to manage external
+ socket is thread safe so one can for instance delete an external socket
+ at any time).
+
+ - interface lookup cache is Kea thread safe (i.e. thread safe when the
+ multi-threading mode is true).
+
+ - interface send method is thread safe (mainly because it does not change
+ any internal state).
+
+ - packet queue ring is thread safe.
+
+*/
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
new file mode 100644
index 0000000..452b2b9
--- /dev/null
+++ b/src/lib/dhcp/libdhcp++.h
@@ -0,0 +1,459 @@
+// Copyright (C) 2011-2023 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 LIBDHCP_H
+#define LIBDHCP_H
+
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space_container.h>
+#include <dhcp/option_space.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <util/buffer.h>
+#include <util/staged_value.h>
+
+#include <iostream>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A pointer to a ScopedPktOptionsCopy object instantiated using Pkt4.
+typedef ScopedPktOptionsCopy<Pkt4> ScopedPkt4OptionsCopy;
+/// @brief A pointer to a ScopedPktOptionsCopy object instantiated using Pkt6.
+typedef ScopedPktOptionsCopy<Pkt6> ScopedPkt6OptionsCopy;
+/// @brief A pointer to a ScopedSubOptionsCopy object.
+typedef std::shared_ptr<ScopedSubOptionsCopy> ScopedOptionsCopyPtr;
+/// @brief A container of ScopedOptionsCopyPtr objects.
+typedef std::vector<ScopedOptionsCopyPtr> ScopedOptionsCopyContainer;
+
+struct ManagedScopedOptionsCopyContainer {
+ /// @brief Constructor.
+ ManagedScopedOptionsCopyContainer() {
+ }
+
+ /// @brief Destructor.
+ ~ManagedScopedOptionsCopyContainer() {
+ // Destroy the scoped options in same order so that parent options
+ // (stored last) are kept alive longer.
+ for (auto& scoped : scoped_options_) {
+ scoped.reset();
+ }
+ }
+
+ /// @brief The container.
+ ScopedOptionsCopyContainer scoped_options_;
+};
+
+class LibDHCP {
+
+public:
+
+ /// Map of factory functions.
+ typedef std::map<unsigned short, Option::Factory*> FactoryMap;
+
+ /// @brief Returns collection of option definitions.
+ ///
+ /// This method returns a collection of option definitions for a specified
+ /// option space.
+ ///
+ /// @param space Option space.
+ ///
+ /// @return Pointer to a collection of option definitions.
+ static const OptionDefContainerPtr getOptionDefs(const std::string& space);
+
+ /// @brief Return the first option definition matching a
+ /// particular option code.
+ ///
+ /// @param space option space.
+ /// @param code option code.
+ ///
+ /// @return reference to an option definition being requested
+ /// or NULL pointer if option definition has not been found.
+ static OptionDefinitionPtr getOptionDef(const std::string& space,
+ const uint16_t code);
+
+ /// @brief Return the definition of option having a specified name.
+ ///
+ /// @param space option space.
+ /// @param name Option name.
+ ///
+ /// @return Pointer to the option definition or NULL pointer if option
+ /// definition has not been found.
+ static OptionDefinitionPtr getOptionDef(const std::string& space,
+ const std::string& name);
+
+ /// @brief Returns vendor option definition for a given vendor-id and code
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id enterprise-id for a given vendor
+ /// @param code option code
+ /// @return reference to an option definition being requested
+ /// or NULL pointer if option definition has not been found.
+ static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u,
+ const uint32_t vendor_id,
+ const uint16_t code);
+
+ /// @brief Returns vendor option definition for a given vendor-id and
+ /// option name.
+ ///
+ /// @param u Universe (V4 or V6)
+ /// @param vendor_id Enterprise-id for a given vendor
+ /// @param name Option name.
+ ///
+ /// @return A pointer to an option definition or NULL pointer if
+ /// no option definition found.
+ static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u,
+ const uint32_t vendor_id,
+ const std::string& name);
+
+
+ /// @brief Returns runtime (non-standard) option definition by space and
+ /// option code.
+ ///
+ /// @param space Option space name.
+ /// @param code Option code.
+ ///
+ /// @return Pointer to option definition or NULL if it doesn't exist.
+ static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space,
+ const uint16_t code);
+
+ /// @brief Returns runtime (non-standard) option definition by space and
+ /// option name.
+ ///
+ /// @param space Option space name.
+ /// @param name Option name.
+ ///
+ /// @return Pointer to option definition or NULL if it doesn't exist.
+ static OptionDefinitionPtr getRuntimeOptionDef(const std::string& space,
+ const std::string& name);
+
+ /// @brief Returns runtime (non-standard) option definitions for specified
+ /// option space name.
+ ///
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the container holding option definitions or NULL.
+ static OptionDefContainerPtr getRuntimeOptionDefs(const std::string& space);
+
+ /// @brief Returns last resort option definition by space and option code.
+ ///
+ /// @param space Option space name.
+ /// @param code Option code.
+ ///
+ /// @return Pointer to option definition or NULL if it doesn't exist.
+ static OptionDefinitionPtr getLastResortOptionDef(const std::string& space,
+ const uint16_t code);
+
+ /// @brief Returns last resort option definition by space and option name.
+ ///
+ /// @param space Option space name.
+ /// @param name Option name.
+ ///
+ /// @return Pointer to option definition or NULL if it doesn't exist.
+ static OptionDefinitionPtr getLastResortOptionDef(const std::string& space,
+ const std::string& name);
+
+ /// @brief Returns last resort option definitions for specified option
+ /// space name.
+ ///
+ /// @param space Option space name.
+ ///
+ /// @return Pointer to the container holding option definitions or NULL.
+ static OptionDefContainerPtr getLastResortOptionDefs(const std::string& space);
+
+ /// @brief Checks if an option unpacking has to be deferred.
+ ///
+ /// DHCPv4 option 43 and 224-254 unpacking is done after classification.
+ ///
+ /// @param space Option space name.
+ /// @param code Option code.
+ ///
+ /// @return True if option processing should be deferred.
+ static bool shouldDeferOptionUnpack(const std::string& space,
+ const uint16_t code);
+
+ /// @brief Factory function to create instance of option.
+ ///
+ /// Factory method creates instance of specified option. The option
+ /// to be created has to have corresponding factory function
+ /// registered with \ref LibDHCP::OptionFactoryRegister.
+ ///
+ /// @param u universe of the option (V4 or V6)
+ /// @param type option-type
+ /// @param buf option-buffer
+ ///
+ /// @return instance of option.
+ ///
+ /// @throw isc::InvalidOperation if there is no factory function registered
+ /// for the specified option type.
+ static isc::dhcp::OptionPtr optionFactory(isc::dhcp::Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf);
+
+ /// @brief Stores DHCPv4 options in a buffer.
+ ///
+ /// Stores all options defined in options containers in a on-wire
+ /// format in output buffer specified by buf.
+ ///
+ /// May throw different exceptions if option assembly fails. There
+ /// may be different reasons (option too large, option malformed,
+ /// too many options etc.)
+ ///
+ /// This is v4 specific version, which stores DHCP message type first,
+ /// and the Relay Agent Information option and END options last. This
+ /// function is initially called to pack the options for a packet in
+ /// @ref Pkt4::pack(). That call leads to it being called recursively in
+ /// @ref Option::packOptions(). Thus the logic used to output the
+ /// message type should only be executed by the top-most. This is governed
+ /// by the parameter top, below.
+ ///
+ /// @param buf output buffer (assembled options will be stored here)
+ /// @param options collection of options to store to
+ /// @param top indicates if this is the first call to pack the options.
+ /// When true logic to emit the message type first is executed. It
+ /// defaults to false.
+ /// @param check indicates if the code should be more flexible with
+ /// PAD and END options. If true, PAD and END options will not be parsed.
+ /// This is useful for partial parsing and slightly broken packets.
+ static void packOptions4(isc::util::OutputBuffer& buf,
+ const isc::dhcp::OptionCollection& options,
+ bool top = false, bool check = true);
+
+ /// @brief Split long options in multiple options with the same option code
+ /// (RFC3396).
+ ///
+ /// @param options The option container which needs to be updated with split
+ /// options.
+ /// @param scopedOptions temporary storage for options that are going to be
+ /// split. See @ref ScopedPktOptionsCopy for explanation.
+ /// @param used The size of the buffer that has already been used by the
+ /// parent option effectively shrinking the maximum supported length for
+ /// each options in the container.
+ /// @return True if any option has been split, false otherwise.
+ static bool splitOptions4(isc::dhcp::OptionCollection& options,
+ ScopedOptionsCopyContainer& scopedOptions,
+ uint32_t used = 0);
+
+ /// @brief Stores DHCPv6 options in a buffer.
+ ///
+ /// Stores all options defined in options containers in a on-wire
+ /// format in output buffer specified by buf.
+ ///
+ /// May throw different exceptions if option assembly fails. There
+ /// may be different reasons (option too large, option malformed,
+ /// too many options etc.)
+ ///
+ /// Currently there's no special logic in it. Options are stored in
+ /// the order of their option codes.
+ ///
+ /// @param buf output buffer (assembled options will be stored here)
+ /// @param options collection of options to store to
+ static void packOptions6(isc::util::OutputBuffer& buf,
+ const isc::dhcp::OptionCollection& options);
+
+ /// @brief Parses provided buffer as DHCPv6 options and creates
+ /// Option objects.
+ ///
+ /// Parses provided buffer and stores created Option objects in
+ /// options container. The last two parameters are optional and
+ /// are used in relay parsing. If they are specified, relay-msg
+ /// option is not created, but rather those two parameters are
+ /// specified to point out where the relay-msg option resides and
+ /// what is its length. This is a performance optimization that
+ /// avoids unnecessary copying of potentially large relay-msg
+ /// option. It is not used for anything, except in the next
+ /// iteration its content will be treated as buffer to be parsed.
+ ///
+ /// @param buf Buffer to be parsed.
+ /// @param option_space A name of the option space which holds definitions
+ /// to be used to parse options in the packets.
+ /// @param options Reference to option container. Options will be
+ /// put here.
+ /// @param relay_msg_offset reference to a size_t structure. If specified,
+ /// offset to beginning of relay_msg option will be stored in it.
+ /// @param relay_msg_len reference to a size_t structure. If specified,
+ /// length of the relay_msg option will be stored in it.
+ /// @return offset to the first byte after the last successfully
+ /// parsed option
+ ///
+ /// @note This function throws when an option type is defined more
+ /// than once, and it calls option building routines which can throw.
+ /// Partial parsing does not throw: it is the responsibility of the
+ /// caller to handle this condition.
+ static size_t unpackOptions6(const OptionBuffer& buf,
+ const std::string& option_space,
+ isc::dhcp::OptionCollection& options,
+ size_t* relay_msg_offset = 0,
+ size_t* relay_msg_len = 0);
+
+ /// @brief Fuse multiple options with the same option code in long options
+ /// (RFC3396).
+ ///
+ /// @param options The option container which needs to be updated with fused
+ /// options.
+ /// @return True if any option has been fused, false otherwise.
+ static bool fuseOptions4(isc::dhcp::OptionCollection& options);
+
+ /// @brief Extend vendor options from fused options in multiple OptionVendor
+ /// or OptionVendorClass options and add respective suboptions or values.
+ ///
+ /// @param options The option container which needs to be updated with
+ /// extended vendor options.
+ static void extendVendorOptions4(isc::dhcp::OptionCollection& options);
+
+ /// @brief Parses provided buffer as DHCPv4 options and creates
+ /// Option objects.
+ ///
+ /// Parses provided buffer and stores created Option objects
+ /// in options container.
+ ///
+ /// @param buf Buffer to be parsed.
+ /// @param option_space A name of the option space which holds definitions
+ /// to be used to parse options in the packets.
+ /// @param options Reference to option container. Options will be
+ /// put here.
+ /// @param deferred Reference to an option code list. Options which
+ /// processing is deferred will be put here.
+ /// @param flexible_pad_end Parse options 0 and 255 as PAD and END
+ /// when they are not defined in the option space.
+ /// @return offset to the first byte after the last successfully
+ /// parsed option or the offset of the DHO_END option type.
+ ///
+ /// The unpackOptions6 note applies too.
+ static size_t unpackOptions4(const OptionBuffer& buf,
+ const std::string& option_space,
+ isc::dhcp::OptionCollection& options,
+ std::list<uint16_t>& deferred,
+ bool flexible_pad_end = false);
+
+ /// Registers factory method that produces options of specific option types.
+ ///
+ /// @throw isc::BadValue if provided the type is already registered, has
+ /// too large a value or an invalid universe is specified.
+ ///
+ /// @param u universe of the option (V4 or V6)
+ /// @param type option-type
+ /// @param factory function pointer
+ static void OptionFactoryRegister(Option::Universe u,
+ uint16_t type,
+ Option::Factory * factory);
+
+ /// @brief Returns option definitions for given universe and vendor
+ ///
+ /// @param u option universe
+ /// @param vendor_id enterprise-id of a given vendor
+ ///
+ /// @return a container for a given vendor (or NULL if no option
+ /// definitions are defined)
+ static const OptionDefContainerPtr getVendorOptionDefs(Option::Universe u,
+ const uint32_t vendor_id);
+
+ /// @brief Parses provided buffer as DHCPv6 vendor options and creates
+ /// Option objects.
+ ///
+ /// Parses provided buffer and stores created Option objects
+ /// in options container.
+ ///
+ /// @param vendor_id enterprise-id of the vendor
+ /// @param buf Buffer to be parsed.
+ /// @param options Reference to option container. Suboptions will be
+ /// put here.
+ /// @return offset to the first byte after the last successfully
+ /// parsed suboption
+ ///
+ /// @note unpackVendorOptions6 throws when it fails to parse a suboption
+ /// so the return value is currently always the buffer length.
+ static size_t unpackVendorOptions6(const uint32_t vendor_id,
+ const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options);
+
+ /// @brief Parses provided buffer as DHCPv4 vendor options and creates
+ /// Option objects.
+ ///
+ /// Parses provided buffer and stores created Option objects
+ /// in options container.
+ ///
+ /// @param vendor_id enterprise-id of the vendor
+ /// @param buf Buffer to be parsed.
+ /// @param options Reference to option container. Suboptions will be
+ /// put here.
+ /// @return offset to the first byte after the last successfully
+ /// parsed suboption
+ ///
+ /// The unpackVendorOptions6 note applies
+ static size_t unpackVendorOptions4(const uint32_t vendor_id,
+ const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options);
+
+
+ /// @brief Copies option definitions created at runtime.
+ ///
+ /// Copied option definitions will be used as "runtime" option definitions.
+ /// A typical use case is to set option definitions specified by the user
+ /// in the server configuration. These option definitions should be removed
+ /// or replaced with new option definitions upon reconfiguration.
+ ///
+ /// @param defs Const reference to a container holding option definitions
+ /// grouped by option spaces.
+ static void setRuntimeOptionDefs(const OptionDefSpaceContainer& defs);
+
+ /// @brief Removes runtime option definitions.
+ static void clearRuntimeOptionDefs();
+
+ /// @brief Reverts uncommitted changes to runtime option definitions.
+ static void revertRuntimeOptionDefs();
+
+ /// @brief Commits runtime option definitions.
+ static void commitRuntimeOptionDefs();
+
+ /// @brief Converts option space name to vendor id.
+ ///
+ /// If the option space name is specified in the following format:
+ /// "vendor-X" where X is an uint32_t number, it is assumed to be
+ /// a vendor space and the uint32_t number is returned by this function.
+ /// If the option space name is invalid this method will return 0, which
+ /// is not a valid vendor-id, to signal an error.
+ ///
+ /// @todo remove this function once when the conversion is dealt by the
+ /// appropriate functions returning options by option space names.
+ ///
+ /// @param option_space Option space name.
+ /// @return vendor id.
+ static uint32_t optionSpaceToVendorId(const std::string& option_space);
+
+private:
+
+ /// Initialize DHCP option definitions.
+ ///
+ /// The method creates option definitions for all DHCP options.
+ ///
+ /// @throw std::bad alloc if system went out of memory.
+ /// @throw MalformedOptionDefinition if any of the definitions
+ /// are incorrect. This is programming error.
+ static bool initOptionDefs();
+
+ /// flag which indicates initialization state
+ static bool initialized_;
+
+ /// pointers to factories that produce DHCPv6 options
+ static FactoryMap v4factories_;
+
+ /// pointers to factories that produce DHCPv6 options
+ static FactoryMap v6factories_;
+
+ /// Container that holds option definitions for various option spaces.
+ static OptionDefContainers option_defs_;
+
+ /// Container for additional option definitions created in runtime.
+ static util::StagedValue<OptionDefSpaceContainer> runtime_option_defs_;
+};
+
+}
+}
+
+#endif // LIBDHCP_H
diff --git a/src/lib/dhcp/opaque_data_tuple.cc b/src/lib/dhcp/opaque_data_tuple.cc
new file mode 100644
index 0000000..e09f7a7
--- /dev/null
+++ b/src/lib/dhcp/opaque_data_tuple.cc
@@ -0,0 +1,148 @@
+// Copyright (C) 2014-2022 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 <dhcp/opaque_data_tuple.h>
+
+namespace isc {
+namespace dhcp {
+
+OpaqueDataTuple::OpaqueDataTuple(LengthFieldType length_field_type)
+ : length_field_type_(length_field_type) {
+}
+
+void
+OpaqueDataTuple::append(const std::string& text) {
+ // Don't do anything if text is empty.
+ if (!text.empty()) {
+ append(&text[0], text.size());
+ }
+}
+
+void
+OpaqueDataTuple::assign(const std::string& text) {
+ // If empty string is being assigned, reset the buffer.
+ if (text.empty()) {
+ clear();
+ } else {
+ assign(&text[0], text.size());
+ }
+}
+
+void
+OpaqueDataTuple::clear() {
+ data_.clear();
+}
+
+bool
+OpaqueDataTuple::equals(const std::string& other) const {
+ return (getText() == other);
+}
+
+std::string
+OpaqueDataTuple::getText() const {
+ // Convert the data carried in the buffer to a string.
+ return (std::string(data_.begin(), data_.end()));
+}
+
+void
+OpaqueDataTuple::pack(isc::util::OutputBuffer& buf) const {
+ if ((1 << (getDataFieldSize() * 8)) <= getLength()) {
+ isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the"
+ " opaque data field, because current data length "
+ << getLength() << " exceeds the maximum size for the length"
+ << " field size " << getDataFieldSize());
+ }
+
+ if (getDataFieldSize() == 1) {
+ buf.writeUint8(static_cast<uint8_t>(getLength()));
+ } else {
+ buf.writeUint16(getLength());
+ }
+
+ if (getLength() > 0) {
+ buf.writeData(&getData()[0], getLength());
+ }
+}
+
+int
+OpaqueDataTuple::getDataFieldSize() const {
+ return (length_field_type_ == LENGTH_1_BYTE ? 1 : 2);
+}
+
+OpaqueDataTuple&
+OpaqueDataTuple::operator=(const std::string& other) {
+ // Replace existing data with the new data converted from a string.
+ assign(&other[0], other.length());
+ return (*this);
+}
+
+bool
+OpaqueDataTuple::operator==(const std::string& other) const {
+ return (equals(other));
+}
+
+bool
+OpaqueDataTuple::operator!=(const std::string& other) {
+ return (!equals(other));
+}
+
+std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple) {
+ os << tuple.getText();
+ return (os);
+}
+
+std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple) {
+ // We will replace the current data with new data.
+ tuple.clear();
+ char buf[256];
+ // Read chunks of data as long as we haven't reached the end of the stream.
+ while (!is.eof()) {
+ is.read(buf, sizeof(buf));
+ // Append the chunk of data we have just read. It is fine if the
+ // gcount is 0, because append() function will check that.
+ tuple.append(buf, is.gcount());
+ }
+ // Clear eof flag, so as the stream can be read again.
+ is.clear();
+ return (is);
+}
+
+void
+OpaqueDataTuple::unpack(OpaqueDataTuple::InputIterator begin, OpaqueDataTuple::InputIterator end) {
+ // The buffer must at least hold the size of the data.
+ if (std::distance(begin, end) < getDataFieldSize()) {
+ isc_throw(OpaqueDataTupleError,
+ "unable to parse the opaque data tuple, the buffer"
+ " length is " << std::distance(begin, end)
+ << ", expected at least " << getDataFieldSize());
+ }
+ // Read the data length from the length field, depending on the
+ // size of the data field (1 or 2 bytes).
+ size_t len = getDataFieldSize() == 1 ? *begin :
+ isc::util::readUint16(&(*begin), std::distance(begin, end));
+ // Now that we have the expected data size, let's check that the
+ // reminder of the buffer is long enough.
+ begin += getDataFieldSize();
+ // Attempt to parse as a length-value pair.
+ if (std::distance(begin, end) < len) {
+ if (Option::lenient_parsing_) {
+ // Fallback to parsing the rest of the option as a single value.
+ len = std::distance(begin, end);
+ } else {
+ isc_throw(OpaqueDataTupleError,
+ "unable to parse the opaque data tuple, "
+ "the buffer length is " << std::distance(begin, end)
+ << ", but the tuple length is " << len);
+ }
+ }
+ // The buffer length is long enough to read the desired amount of data.
+ assign(begin, len);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcp/opaque_data_tuple.h b/src/lib/dhcp/opaque_data_tuple.h
new file mode 100644
index 0000000..66a53dc
--- /dev/null
+++ b/src/lib/dhcp/opaque_data_tuple.h
@@ -0,0 +1,291 @@
+// Copyright (C) 2014-2023 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 OPAQUE_DATA_TUPLE_H
+#define OPAQUE_DATA_TUPLE_H
+
+#include <dhcp/option.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when the operation on @c OpaqueDataTuple
+/// object results in an error.
+class OpaqueDataTupleError : public Exception {
+public:
+ OpaqueDataTupleError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Represents a single instance of the opaque data preceded by length.
+///
+/// Some of the DHCP options, such as Vendor Class option (16) in DHCPv6 or
+/// V-I Vendor Class option (124) in DHCPv4 may carry multiple pairs of
+/// opaque-data preceded by its length. Such pairs are called tuples. This class
+/// represents a single instance of the tuple in the DHCPv4 or DHCPv6 option.
+///
+/// Although, the primary purpose of this class is to represent data tuples in
+/// Vendor Class options, there may be other options defined in the future that
+/// may have similar structure and this class can be used to represent the data
+/// tuples in these new options too.
+///
+/// This class exposes a set of convenience methods to assign and retrieve the
+/// opaque data from the tuple. It also implements a method to render the tuple
+/// data into a wire format, as well as a method to create an instance of the
+/// tuple from the wire format.
+class OpaqueDataTuple {
+public:
+
+ /// @brief Size of the length field in the tuple.
+ ///
+ /// In the wire format, the tuple consists of the two fields: one holding
+ /// a length of the opaque data size, second holding opaque data. The first
+ /// field's size may be equal to 1 or 2 bytes. Usually, the tuples carried
+ /// in the DHCPv6 options have 2 byte long length fields, the tuples carried
+ /// in DHCPv4 options have 1 byte long length fields.
+ enum LengthFieldType {
+ LENGTH_EMPTY = -1,
+ LENGTH_1_BYTE,
+ LENGTH_2_BYTES
+ };
+
+ /// @brief Defines a type of the data buffer used to hold the opaque data.
+ using Buffer = std::vector<uint8_t>;
+ using InputIterator = Buffer::const_iterator;
+
+ /// @brief Default constructor.
+ ///
+ /// @param length_field_type Indicates a length of the field which holds
+ /// the size of the tuple.
+ OpaqueDataTuple(LengthFieldType length_field_type);
+
+ /// @brief Constructor
+ ///
+ /// Creates a tuple from on-wire data. It calls @c OpaqueDataTuple::unpack
+ /// internally.
+ ///
+ /// @param length_field_type Indicates the length of the field holding the
+ /// opaque data size.
+ /// @param begin Iterator pointing to the beginning of the buffer holding
+ /// wire data.
+ /// @param end Iterator pointing to the end of the buffer holding wire data.
+ /// @throw It may throw an exception if the @c unpack throws.
+ OpaqueDataTuple(LengthFieldType length_field_type,
+ InputIterator begin,
+ InputIterator end)
+ : length_field_type_(length_field_type) {
+ unpack(begin, end);
+ }
+
+ /// @brief Appends data to the tuple.
+ ///
+ /// This function appends the data of the specified length to the tuple.
+ /// If the specified buffer length is greater than the size of the buffer,
+ /// the behavior of this function is undefined.
+ ///
+ /// @param data Iterator pointing to the beginning of the buffer being
+ /// appended. The source buffer may be an STL object or an array of
+ /// characters. In the latter case, the pointer to the beginning of this
+ /// array should be passed.
+ /// @param len Length of the source buffer.
+ /// @tparam InputIterator Type of the iterator pointing to the beginning of
+ /// the source buffer.
+ void append(const char* data, const size_t len) {
+ data_.insert(data_.end(), data, data + len);
+ }
+ void append(InputIterator data, const size_t len) {
+ data_.insert(data_.end(), data, data + len);
+ }
+
+ /// @brief Appends string to the tuple.
+ ///
+ /// In most cases, the tuple will carry a string. This function appends the
+ /// string to the tuple.
+ ///
+ /// @param text String to be appended in the tuple.
+ void append(const std::string& text);
+
+ /// @brief Assigns data to the tuple.
+ ///
+ /// This function replaces existing data in the tuple with the new data.
+ /// If the specified buffer length is greater than the size of the buffer,
+ /// the behavior of this function is undefined.
+ /// @param data Iterator pointing to the beginning of the buffer being
+ /// assigned. The source buffer may be an STL object or an array of
+ /// characters. In the latter case, the pointer to the beginning of this
+ /// array should be passed.
+ /// @param len Length of the source buffer.
+ void assign(const char* data, const size_t len) {
+ data_.assign(data, data + len);
+ }
+ void assign(InputIterator data, const size_t len) {
+ data_.assign(data, data + len);
+ }
+
+ /// @brief Assigns string data to the tuple.
+ ///
+ /// In most cases, the tuple will carry a string. This function sets the
+ /// string to the tuple.
+ ///
+ /// @param text String to be assigned to the tuple.
+ void assign(const std::string& text);
+
+ /// @brief Removes the contents of the tuple.
+ void clear();
+
+ /// @brief Checks if the data carried in the tuple match the string.
+ ///
+ /// @param other String to compare tuple data against.
+ bool equals(const std::string& other) const;
+
+ /// @brief Returns tuple length data field type.
+ LengthFieldType getLengthFieldType() const {
+ return (length_field_type_);
+ }
+
+ /// @brief Returns the length of the data in the tuple.
+ size_t getLength() const {
+ return (data_.size());
+ }
+
+ /// @brief Returns a total size of the tuple, including length field.
+ size_t getTotalLength() const {
+ return (getDataFieldSize() + getLength());
+ }
+
+ /// @brief Returns a reference to the buffer holding tuple data.
+ ///
+ /// @warning The returned reference is valid only within the lifetime
+ /// of the object which returned it. The use of the returned reference
+ /// after the object has been destroyed yelds undefined behavior.
+ const Buffer& getData() const {
+ return (data_);
+ }
+
+ /// @brief Return the tuple data in the textual format.
+ std::string getText() const;
+
+ /// @brief Renders the tuple to a buffer in the wire format.
+ ///
+ /// This function creates the following wire representation of the tuple:
+ /// - 1 or 2 bytes holding a length of the data.
+ /// - variable number of bytes holding data.
+ /// and writes it to the specified buffer. The new are appended to the
+ /// buffer, so as data existing in the buffer is preserved.
+ ///
+ /// The tuple is considered malformed if one of the following occurs:
+ /// - the size of the data is 0 (tuple is empty),
+ /// - the size of the data is greater than 255 and the size of the length
+ /// field is 1 byte (see @c LengthFieldType).
+ /// - the size of the data is greater than 65535 and the size of the length
+ /// field is 2 bytes (see @c LengthFieldType).
+ ///
+ /// Function will throw an exception if trying to render malformed tuple.
+ ///
+ /// @param [out] buf Buffer to which the data is rendered.
+ ///
+ /// @throw OpaqueDataTupleError if failed to render the data to the
+ /// buffer because the tuple is malformed.
+ void pack(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Parses wire data and creates a tuple from it.
+ ///
+ /// This function parses on-wire data stored in the provided buffer and
+ /// stores it in the tuple object. The wire data must include at least the
+ /// data field of the length matching the specified @c LengthFieldType.
+ /// The remaining buffer length (excluding the length field) must be equal
+ /// or greater than the length carried in the length field. If any of these
+ /// two conditions is not met, an exception is thrown.
+ ///
+ /// This function allows opaque data with the length of 0.
+ ///
+ /// @param begin Iterator pointing to the beginning of the buffer holding
+ /// wire data.
+ /// @param end Iterator pointing to the end of the buffer holding wire data.
+ /// @tparam InputIterator Type of the iterators passed to this function.
+ void unpack(InputIterator begin, InputIterator end);
+
+ /// @name Assignment and comparison operators.
+ //{@
+
+ /// @brief Assignment operator.
+ ///
+ /// This operator assigns the string data to the tuple.
+ ///
+ /// @param other string to be assigned to the tuple.
+ /// @return Tuple object after assignment.
+ OpaqueDataTuple& operator=(const std::string& other);
+
+ /// @brief Equality operator.
+ ///
+ /// This operator compares the string given as an argument to the data
+ /// carried in the tuple in the textual format.
+ ///
+ /// @param other String to compare the tuple against.
+ /// @return true if data carried in the tuple is equal to the string.
+ bool operator==(const std::string& other) const;
+
+ /// @brief Inequality operator.
+ ///
+ /// This operator compares the string given as an argument to the data
+ /// carried in the tuple for inequality.
+ ///
+ /// @param other String to compare the tuple against.
+ /// @return true if the data carried in the tuple is unequal the given
+ /// string.
+ bool operator!=(const std::string& other);
+ //@}
+
+ /// @brief Returns the size of the tuple length field.
+ ///
+ /// The returned value depends on the @c LengthFieldType set for the tuple.
+ int getDataFieldSize() const;
+
+private:
+
+ /// @brief Buffer which holds the opaque tuple data.
+ Buffer data_;
+
+ /// @brief Holds a type of tuple size field (1 byte long or 2 bytes long).
+ LengthFieldType length_field_type_;
+};
+
+/// @brief Pointer to the @c OpaqueDataTuple object.
+typedef boost::shared_ptr<OpaqueDataTuple> OpaqueDataTuplePtr;
+
+/// @brief Inserts the @c OpaqueDataTuple as a string into stream.
+///
+/// This operator gets the tuple data in the textual format and inserts it
+/// into the output stream.
+///
+/// @param os Stream object on which insertion is performed.
+/// @param tuple Object encapsulating a tuple which data in the textual format
+/// is inserted into the stream.
+/// @return Reference to the same stream but after insertion operation.
+std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple);
+
+/// @brief Inserts data carried in the stream into the tuple.
+///
+/// this operator inserts data carried in the input stream and inserts it to
+/// the @c OpaqueDataTuple object. The existing data is replaced with new data.
+///
+/// @param is Input stream from which the data will be inserted.
+/// @param tuple @c OpaqueDataTuple object to which the data will be inserted.
+/// @return Input stream after insertion to the tuple is performed.
+std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple);
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
new file mode 100644
index 0000000..95353ef
--- /dev/null
+++ b/src/lib/dhcp/option.cc
@@ -0,0 +1,389 @@
+// Copyright (C) 2011-2023 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 <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_space.h>
+#include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+#include <util/io_utilities.h>
+
+#include <boost/make_shared.hpp>
+
+#include <iomanip>
+#include <list>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <string.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+OptionPtr
+Option::factory(Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf) {
+ return (LibDHCP::optionFactory(u, type, buf));
+}
+
+Option::Option(Universe u, uint16_t type)
+ : universe_(u), type_(type) {
+ check();
+}
+
+Option::Option(Universe u, uint16_t type, const OptionBuffer& data)
+ : universe_(u), type_(type), data_(data) {
+ check();
+}
+
+Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : universe_(u), type_(type), data_(first, last) {
+ check();
+}
+
+Option::Option(const Option& option)
+ : universe_(option.universe_), type_(option.type_),
+ data_(option.data_), options_(),
+ encapsulated_space_(option.encapsulated_space_) {
+ option.getOptionsCopy(options_);
+}
+
+OptionPtr
+Option::create(Universe u, uint16_t type) {
+ return (boost::make_shared<Option>(u, type));
+}
+
+OptionPtr
+Option::create(Universe u, uint16_t type, const OptionBuffer& data) {
+ return (boost::make_shared<Option>(u, type, data));
+}
+
+Option&
+Option::operator=(const Option& rhs) {
+ if (&rhs != this) {
+ universe_ = rhs.universe_;
+ type_ = rhs.type_;
+ data_ = rhs.data_;
+ rhs.getOptionsCopy(options_);
+ encapsulated_space_ = rhs.encapsulated_space_;
+ }
+ return (*this);
+}
+
+OptionPtr
+Option::clone() const {
+ return (cloneInternal<Option>());
+}
+
+void
+Option::check() const {
+ if ((universe_ != V4) && (universe_ != V6)) {
+ isc_throw(BadValue, "Invalid universe type specified. "
+ << "Only V4 and V6 are allowed.");
+ }
+
+ if (universe_ == V4) {
+ if (type_ > 255) {
+ isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. "
+ << "For DHCPv4 allowed type range is 0..255");
+ }
+ }
+
+ // no need to check anything for DHCPv6. It allows full range (0-64k) of
+ // both types and data size.
+}
+
+void Option::pack(isc::util::OutputBuffer& buf, bool check) const {
+ // Write a header.
+ packHeader(buf, check);
+ // Write data.
+ if (!data_.empty()) {
+ buf.writeData(&data_[0], data_.size());
+ }
+ // Write sub-options.
+ packOptions(buf, check);
+}
+
+void
+Option::packHeader(isc::util::OutputBuffer& buf, bool check) const {
+ if (universe_ == V4) {
+ if (check && len() > 255) {
+ isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. "
+ << "At most 255 bytes are supported.");
+ }
+
+ buf.writeUint8(type_);
+ buf.writeUint8(len() - getHeaderLen());
+
+ } else {
+ buf.writeUint16(type_);
+ buf.writeUint16(len() - getHeaderLen());
+ }
+}
+
+void
+Option::packOptions(isc::util::OutputBuffer& buf, bool check) const {
+ switch (universe_) {
+ case V4:
+ LibDHCP::packOptions4(buf, options_, false, check);
+ return;
+ case V6:
+ LibDHCP::packOptions6(buf, options_);
+ return;
+ default:
+ isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+ }
+}
+
+void Option::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ setData(begin, end);
+}
+
+void
+Option::unpackOptions(const OptionBuffer& buf) {
+ list<uint16_t> deferred;
+ switch (universe_) {
+ case V4:
+ LibDHCP::unpackOptions4(buf, getEncapsulatedSpace(),
+ options_, deferred,
+ getType() == DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ return;
+ case V6:
+ LibDHCP::unpackOptions6(buf, getEncapsulatedSpace(), options_);
+ return;
+ default:
+ isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+ }
+}
+
+uint16_t Option::len() const {
+ // Returns length of the complete option (data length + DHCPv4/DHCPv6
+ // option header)
+
+ // length of the whole option is header and data stored in this option...
+ size_t length = getHeaderLen() + data_.size();
+
+ // ... and sum of lengths of all suboptions
+ for (auto const& option : options_) {
+ length += option.second->len();
+ }
+
+ // note that this is not equal to length field. This value denotes
+ // number of bytes required to store this option. length option should
+ // contain (len()-getHeaderLen()) value.
+ return (static_cast<uint16_t>(length));
+}
+
+bool
+Option::valid() const {
+ if (universe_ != V4 &&
+ universe_ != V6) {
+ return (false);
+ }
+
+ return (true);
+}
+
+OptionPtr Option::getOption(uint16_t opt_type) const {
+ auto const& x = options_.find(opt_type);
+ if (x != options_.end()) {
+ return (x->second);
+ }
+ return OptionPtr(); // NULL
+}
+
+void
+Option::getOptionsCopy(OptionCollection& options_copy) const {
+ OptionCollection local_options;
+ for (auto const& option : options_) {
+ OptionPtr copy = option.second->clone();
+ local_options.insert(std::make_pair(option.second->getType(), copy));
+ }
+ // All options copied successfully, so assign them to the output
+ // parameter.
+ options_copy.swap(local_options);
+}
+
+bool Option::delOption(uint16_t opt_type) {
+ auto const& x = options_.find(opt_type);
+ if (x != options_.end()) {
+ options_.erase(x);
+ return (true); // delete successful
+ }
+ return (false); // option not found, can't delete
+}
+
+std::string Option::toText(int indent) const {
+ std::stringstream output;
+ output << headerToText(indent) << ": ";
+
+ for (unsigned int i = 0; i < data_.size(); i++) {
+ if (i) {
+ output << ":";
+ }
+ output << setfill('0') << setw(2) << hex
+ << static_cast<unsigned short>(data_[i]);
+ }
+
+ // Append suboptions.
+ output << suboptionsToText(indent + 2);
+
+ return (output.str());
+}
+
+std::string
+Option::toString() const {
+ /// @todo: Implement actual conversion in derived classes.
+ return (toText(0));
+}
+
+std::vector<uint8_t>
+Option::toBinary(const bool include_header) const {
+ OutputBuffer buf(len());
+ try {
+ // The RFC3396 adds support for long options split over multiple options
+ // using the same code.
+ pack(buf, false);
+
+ } catch (const std::exception &ex) {
+ isc_throw(OutOfRange, "unable to obtain hexadecimal representation"
+ " of option " << getType() << ": " << ex.what());
+ }
+ const uint8_t* option_data = static_cast<const uint8_t*>(buf.getData());
+
+ // Assign option data to a vector, with or without option header depending
+ // on the value of "include_header" flag.
+ std::vector<uint8_t> option_vec(option_data + (include_header ? 0 : getHeaderLen()),
+ option_data + buf.getLength());
+ return (option_vec);
+}
+
+std::string
+Option::toHexString(const bool include_header) const {
+ // Prepare binary version of the option.
+ std::vector<uint8_t> option_vec = toBinary(include_header);
+
+ // Return hexadecimal representation prepended with 0x or empty string
+ // if option has no payload and the header fields are excluded.
+ std::ostringstream s;
+ if (!option_vec.empty()) {
+ s << "0x" << encode::encodeHex(option_vec);
+ }
+ return (s.str());
+}
+
+std::string
+Option::headerToText(const int indent, const std::string& type_name) const {
+ std::stringstream output;
+ for (int i = 0; i < indent; i++)
+ output << " ";
+
+ int field_len = (getUniverse() == V4 ? 3 : 5);
+ output << "type=" << std::setw(field_len) << std::setfill('0')
+ << type_;
+
+ if (!type_name.empty()) {
+ output << "(" << type_name << ")";
+ }
+
+ output << ", len=" << std::setw(field_len) << std::setfill('0')
+ << len() - getHeaderLen();
+ return (output.str());
+}
+
+std::string
+Option::suboptionsToText(const int indent) const {
+ std::stringstream output;
+
+ if (!options_.empty()) {
+ output << "," << std::endl << "options:";
+ for (auto const& opt : options_) {
+ output << std::endl << opt.second->toText(indent);
+ }
+ }
+
+ return (output.str());
+}
+
+uint16_t
+Option::getHeaderLen() const {
+ switch (universe_) {
+ case V4:
+ return OPTION4_HDR_LEN; // header length for v4
+ case V6:
+ return OPTION6_HDR_LEN; // header length for v6
+ }
+ return 0; // should not happen
+}
+
+void Option::addOption(OptionPtr opt) {
+ if (this == opt.get()) {
+ // Do not allow options to be added to themselves as this
+ // can lead to infinite recursion.
+ isc_throw(InvalidOperation, "option cannot be added to itself: " << toText());
+ }
+
+ options_.insert(make_pair(opt->getType(), opt));
+}
+
+uint8_t Option::getUint8() const {
+ if (data_.size() < sizeof(uint8_t) ) {
+ isc_throw(OutOfRange, "Attempt to read uint8 from option " << type_
+ << " that has size " << data_.size());
+ }
+ return (data_[0]);
+}
+
+uint16_t Option::getUint16() const {
+ // readUint16() checks and throws OutOfRange if data_ is too small.
+ return (readUint16(&data_[0], data_.size()));
+}
+
+uint32_t Option::getUint32() const {
+ // readUint32() checks and throws OutOfRange if data_ is too small.
+ return (readUint32(&data_[0], data_.size()));
+}
+
+void Option::setUint8(uint8_t value) {
+ data_.resize(sizeof(value));
+ data_[0] = value;
+}
+
+void Option::setUint16(uint16_t value) {
+ data_.resize(sizeof(value));
+ writeUint16(value, &data_[0], data_.size());
+}
+
+void Option::setUint32(uint32_t value) {
+ data_.resize(sizeof(value));
+ writeUint32(value, &data_[0], data_.size());
+}
+
+bool Option::equals(const OptionPtr& other) const {
+ return (equals(*other));
+}
+
+bool Option::equals(const Option& other) const {
+ return ((getType() == other.getType()) &&
+ (getData() == other.getData()));
+}
+
+Option::~Option() {
+}
+
+bool Option::lenient_parsing_;
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
new file mode 100644
index 0000000..c469d97
--- /dev/null
+++ b/src/lib/dhcp/option.h
@@ -0,0 +1,608 @@
+// Copyright (C) 2011-2022 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 OPTION_H
+#define OPTION_H
+
+#include <util/buffer.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief buffer types used in DHCP code.
+///
+/// Dereferencing OptionBuffer iterator will point out to contiguous memory.
+typedef std::vector<uint8_t> OptionBuffer;
+
+/// iterator for walking over OptionBuffer
+typedef OptionBuffer::iterator OptionBufferIter;
+
+/// const_iterator for walking over OptionBuffer
+typedef OptionBuffer::const_iterator OptionBufferConstIter;
+
+/// pointer to a DHCP buffer
+typedef boost::shared_ptr<OptionBuffer> OptionBufferPtr;
+
+/// shared pointer to Option object
+class Option;
+typedef boost::shared_ptr<Option> OptionPtr;
+
+/// A collection of DHCP (v4 or v6) options
+typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
+
+/// A pointer to an OptionCollection
+typedef boost::shared_ptr<OptionCollection> OptionCollectionPtr;
+
+/// @brief Exception thrown during option unpacking
+/// This exception is thrown when an error has occurred, unpacking
+/// an option from a packet and we wish to abandon any any further
+/// unpacking efforts and allow the server to attempt to process
+/// the packet as it stands. In other words, the option that failed
+/// is perhaps optional, and rather than drop the packet as unusable
+/// we wish to attempt to process it.
+class SkipRemainingOptionsError : public Exception {
+public:
+ SkipRemainingOptionsError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown during option unpacking
+/// This exception is thrown when an error has occurred unpacking
+/// an option from a packet and rather than drop the whole packet, we
+/// wish to simply skip over the option (i.e. omit it from the unpacked
+/// results), and resume unpacking with the next option in the buffer.
+/// The intent is to allow us to be liberal with what we receive, and
+/// skip nonsensical options rather than drop the whole packet. This
+/// exception is thrown, for instance, when string options are found to
+/// be empty or to contain only nuls.
+class SkipThisOptionError : public Exception {
+public:
+ SkipThisOptionError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+class Option {
+public:
+ /// length of the usual DHCPv4 option header (there are exceptions)
+ const static size_t OPTION4_HDR_LEN = 2;
+
+ /// length of any DHCPv6 option header
+ const static size_t OPTION6_HDR_LEN = 4;
+
+ /// defines option universe DHCPv4 or DHCPv6
+ enum Universe { V4, V6 };
+
+
+ /// @brief a factory function prototype
+ ///
+ /// @param u option universe (DHCPv4 or DHCPv6)
+ /// @param type option type
+ /// @param buf pointer to a buffer
+ ///
+ /// @todo Passing a separate buffer for each option means that a copy
+ /// was done. We can avoid it by passing 2 iterators.
+ ///
+ /// @return a pointer to a created option object
+ typedef OptionPtr Factory(Option::Universe u, uint16_t type, const OptionBuffer& buf);
+
+ /// @brief Factory function to create instance of option.
+ ///
+ /// Factory method creates instance of specified option. The option
+ /// to be created has to have corresponding factory function
+ /// registered with \ref LibDHCP::OptionFactoryRegister.
+ ///
+ /// @param u universe of the option (V4 or V6)
+ /// @param type option-type
+ /// @param buf option-buffer
+ ///
+ /// @return instance of option.
+ ///
+ /// @throw isc::InvalidOperation if there is no factory function
+ /// registered for specified option type.
+ static OptionPtr factory(Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf);
+
+ /// @brief Factory function to create instance of option.
+ ///
+ /// Factory method creates instance of specified option. The option
+ /// to be created has to have corresponding factory function
+ /// registered with \ref LibDHCP::OptionFactoryRegister.
+ /// This method creates empty \ref OptionBuffer object. Use this
+ /// factory function if it is not needed to pass custom buffer.
+ ///
+ /// @param u universe of the option (V4 or V6)
+ /// @param type option-type
+ ///
+ /// @return instance of option.
+ ///
+ /// @throw isc::InvalidOperation if there is no factory function
+ /// registered for specified option type.
+ static OptionPtr factory(Option::Universe u, uint16_t type) {
+ return factory(u, type, OptionBuffer());
+ }
+
+ /// @brief ctor, used for options constructed, usually during transmission
+ ///
+ /// @param u option universe (DHCPv4 or DHCPv6)
+ /// @param type option type
+ Option(Universe u, uint16_t type);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// This constructor takes vector<uint8_t>& which is used in cases
+ /// when content of the option will be copied and stored within
+ /// option object. V4 Options follow that approach already.
+ /// @todo Migrate V6 options to that approach.
+ ///
+ /// @param u specifies universe (V4 or V6)
+ /// @param type option type (0-255 for V4 and 0-65535 for V6)
+ /// @param data content of the option
+ Option(Universe u, uint16_t type, const OptionBuffer& data);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// This constructor is similar to the previous one, but it does not take
+ /// the whole vector<uint8_t>, but rather subset of it.
+ ///
+ /// @todo This can be templated to use different containers, not just
+ /// vector. Prototype should look like this:
+ /// template<typename InputIterator> Option(Universe u, uint16_t type,
+ /// InputIterator first, InputIterator last);
+ ///
+ /// vector<int8_t> myData;
+ /// Example usage: new Option(V4, 123, myData.begin()+1, myData.end()-1)
+ /// This will create DHCPv4 option of type 123 that contains data from
+ /// trimmed (first and last byte removed) myData vector.
+ ///
+ /// @param u specifies universe (V4 or V6)
+ /// @param type option type (0-255 for V4 and 0-65535 for V6)
+ /// @param first iterator to the first element that should be copied
+ /// @param last iterator to the next element after the last one
+ /// to be copied.
+ Option(Universe u, uint16_t type, OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor.
+ ///
+ /// This constructor makes a deep copy of the option and all of the
+ /// suboptions. It calls @ref getOptionsCopy to deep copy suboptions.
+ ///
+ /// @param source Option to be copied.
+ Option(const Option& source);
+
+ /// @brief Factory function creating an instance of the @c Option.
+ ///
+ /// This function should be used to create an instance of the DHCP
+ /// option within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param u specifies universe (V4 or V6)
+ /// @param type option type (0-255 for V4 and 0-65535 for V6)
+ ///
+ /// @return Pointer to the @c Option instance.
+ static OptionPtr create(Universe u, uint16_t type);
+
+ /// @brief Factory function creating an instance of the @c Option.
+ ///
+ /// This function should be used to create an instance of the DHCP
+ /// option within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param u specifies universe (V4 or V6)
+ /// @param type option type (0-255 for V4 and 0-65535 for V6)
+ /// @param data content of the option
+ ///
+ /// @return Pointer to the @c Option instance.
+ static OptionPtr create(Universe u, uint16_t type, const OptionBuffer& data);
+
+ /// @brief Assignment operator.
+ ///
+ /// The assignment operator performs a deep copy of the option and
+ /// its suboptions. It calls @ref getOptionsCopy to deep copy
+ /// suboptions.
+ ///
+ /// @param rhs Option to be assigned.
+ Option& operator=(const Option& rhs);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ ///
+ /// This function must be overridden in the derived classes to make
+ /// a copy of the derived type. The simplest way to do it is by
+ /// calling @ref cloneInternal function with an appropriate template
+ /// parameter.
+ ///
+ /// @return Pointer to the copy of the option.
+ virtual OptionPtr clone() const;
+
+ /// @brief returns option universe (V4 or V6)
+ ///
+ /// @return universe type
+ Universe getUniverse() const {
+ return (universe_);
+ }
+
+ /// @brief Writes option in wire-format to a buffer.
+ ///
+ /// Writes option in wire-format to buffer, returns pointer to first unused
+ /// byte after stored option (that is useful for writing options one after
+ /// another).
+ ///
+ /// @param buf pointer to a buffer
+ /// @param check flag which indicates if checking the option length is
+ /// required (used only in V4)
+ ///
+ /// @throw BadValue Universe of the option is neither V4 nor V6.
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printing text
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns string representation of the value
+ ///
+ /// This is terse representation used in cases where client classification
+ /// refers to a specific option.
+ ///
+ /// @return string that represents the value of the option.
+ virtual std::string toString() const;
+
+ /// @brief Returns binary representation of the option.
+ ///
+ /// @param include_header Boolean flag which indicates if the output should
+ /// also contain header fields. The default is that it shouldn't include
+ /// header fields.
+ ///
+ /// @return Vector holding binary representation of the option.
+ virtual std::vector<uint8_t> toBinary(const bool include_header = false) const;
+
+ /// @brief Returns string containing hexadecimal representation of option.
+ ///
+ /// @param include_header Boolean flag which indicates if the output should
+ /// also contain header fields. The default is that it shouldn't include
+ /// header fields.
+ ///
+ /// @return String containing hexadecimal representation of the option.
+ virtual std::string toHexString(const bool include_header = false) const;
+
+ /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
+ ///
+ /// @return option type
+ uint16_t getType() const {
+ return (type_);
+ }
+
+ /// Returns length of the complete option (data length + DHCPv4/DHCPv6
+ /// option header)
+ ///
+ /// @return length of the option
+ virtual uint16_t len() const;
+
+ /// @brief Returns length of header (2 for v4, 4 for v6)
+ ///
+ /// @return length of option header
+ virtual uint16_t getHeaderLen() const;
+
+ /// returns if option is valid (e.g. option may be truncated)
+ ///
+ /// @return true, if option is valid
+ virtual bool valid() const;
+
+ /// Returns pointer to actual data.
+ ///
+ /// @return pointer to actual data (or reference to an empty vector
+ /// if there is no data)
+ virtual const OptionBuffer& getData() const {
+ return (data_);
+ }
+
+ /// Adds a sub-option.
+ ///
+ /// Some DHCPv6 options can have suboptions. This method allows adding
+ /// options within options.
+ ///
+ /// Note: option is passed by value. That is very convenient as it allows
+ /// downcasting from any derived classes, e.g. shared_ptr<Option6_IA> type
+ /// can be passed directly, without any casts. That would not be possible
+ /// with passing by reference. addOption() is expected to be used in
+ /// many places. Requiring casting is not feasible.
+ ///
+ /// @param opt shared pointer to a suboption that is going to be added.
+ void addOption(OptionPtr opt);
+
+ /// Returns shared_ptr to suboption of specific type
+ ///
+ /// @param type type of requested suboption
+ ///
+ /// @return shared_ptr to requested suboption
+ OptionPtr getOption(uint16_t type) const;
+
+ /// @brief Returns all encapsulated options.
+ ///
+ /// @warning This function returns a reference to the container holding
+ /// encapsulated options, which is valid as long as the object which
+ /// returned it exists.
+ const OptionCollection& getOptions() const {
+ return (options_);
+ }
+
+ /// @brief Returns all encapsulated options.
+ ///
+ /// @warning This function returns a reference to the container holding
+ /// encapsulated options, which is valid as long as the object which
+ /// returned it exists. Any changes to the container will be reflected
+ /// in the option content.
+ OptionCollection& getMutableOptions() {
+ return (options_);
+ }
+
+ /// @brief Performs deep copy of suboptions.
+ ///
+ /// This method calls @ref clone method to deep copy each option.
+ ///
+ /// @param [out] options_copy Container where copied options are stored.
+ void getOptionsCopy(OptionCollection& options_copy) const;
+
+ /// Attempts to delete first suboption of requested type
+ ///
+ /// @param type Type of option to be deleted.
+ ///
+ /// @return true if option was deleted, false if no such option existed
+ bool delOption(uint16_t type);
+
+ /// @brief Returns content of first byte.
+ ///
+ /// @throw isc::OutOfRange Thrown if the option has a length of 0.
+ ///
+ /// @return value of the first byte
+ uint8_t getUint8() const;
+
+ /// @brief Returns content of first word.
+ ///
+ /// @throw isc::OutOfRange Thrown if the option has a length less than 2.
+ ///
+ /// @return uint16_t value stored on first two bytes
+ uint16_t getUint16() const;
+
+ /// @brief Returns content of first double word.
+ ///
+ /// @throw isc::OutOfRange Thrown if the option has a length less than 4.
+ ///
+ /// @return uint32_t value stored on first four bytes
+ uint32_t getUint32() const;
+
+ /// @brief Sets content of this option to a single uint8 value.
+ ///
+ /// Option it resized appropriately (to length of 1 octet).
+ ///
+ /// @param value value to be set
+ void setUint8(uint8_t value);
+
+ /// @brief Sets content of this option to a single uint16 value.
+ ///
+ /// Option it resized appropriately (to length of 2 octets).
+ ///
+ /// @param value value to be set
+ void setUint16(uint16_t value);
+
+ /// @brief Sets content of this option to a single uint32 value.
+ ///
+ /// Option it resized appropriately (to length of 4 octets).
+ ///
+ /// @param value value to be set
+ void setUint32(uint32_t value);
+
+ /// @brief Sets content of this option from buffer.
+ ///
+ /// Option will be resized to length of buffer.
+ ///
+ /// @param first iterator pointing to beginning of buffer to copy.
+ /// @param last iterator pointing to end of buffer to copy.
+ ///
+ /// @tparam InputIterator type of the iterator representing the
+ /// limits of the buffer to be assigned to a data_ buffer.
+ template<typename InputIterator>
+ void setData(InputIterator first, InputIterator last) {
+ data_.assign(first, last);
+ }
+
+ /// @brief Sets the name of the option space encapsulated by this option.
+ ///
+ /// @param encapsulated_space name of the option space encapsulated by
+ /// this option.
+ void setEncapsulatedSpace(const std::string& encapsulated_space) {
+ encapsulated_space_ = encapsulated_space;
+ }
+
+ /// @brief Returns the name of the option space encapsulated by this option.
+ ///
+ /// @return name of the option space encapsulated by this option.
+ std::string getEncapsulatedSpace() const {
+ return (encapsulated_space_);
+ }
+
+ /// just to force that every option has virtual dtor
+ virtual ~Option();
+
+ /// @brief Checks if options are equal.
+ ///
+ /// This method calls a virtual @c equals function to compare objects.
+ /// This method is not meant to be overridden in the derived classes.
+ /// Instead, the other @c equals function must be overridden.
+ ///
+ /// @param other Pointer to the option to compare this option to.
+ /// @return true if both options are equal, false otherwise.
+ bool equals(const OptionPtr& other) const;
+
+ /// @brief Checks if two options are equal.
+ ///
+ /// Equality verifies option type and option content. Care should
+ /// be taken when using this method. Implementation for derived
+ /// classes should be provided when this method is expected to be
+ /// used. It is safe in general, as the first check (different types)
+ /// will detect differences between base Option and derived
+ /// objects.
+ ///
+ /// @param other Instance of the option to compare to.
+ ///
+ /// @return true if options are equal, false otherwise.
+ virtual bool equals(const Option& other) const;
+
+ /// @brief Governs whether options should be parsed less strictly.
+ ///
+ /// Populated on configuration commit.
+ ///
+ /// When enabled:
+ /// * Tuples are parsed as length-value pairs as usual, but if a length
+ /// surpasses the total option length, the rest of the option buffer is
+ /// parsed as the next value. This more commonly affects DHCPv6's vendor
+ /// class option (16), but it also affects custom options that are defined
+ /// with tuple fields.
+ static bool lenient_parsing_;
+
+protected:
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ ///
+ /// The deep copy of the option is performed by calling copy
+ /// constructor of the option of a given type. Derived classes call
+ /// this method in the implementations of @ref clone methods to
+ /// create a copy of the option of their type.
+ ///
+ /// @tparam OptionType Type of the option of which a clone should
+ /// be created.
+ template<typename OptionType>
+ OptionPtr cloneInternal() const {
+ const OptionType* cast_this = dynamic_cast<const OptionType*>(this);
+ if (cast_this) {
+ return (boost::shared_ptr<OptionType>(new OptionType(*cast_this)));
+ }
+ return (OptionPtr());
+ }
+
+ /// @brief Store option's header in a buffer.
+ ///
+ /// This method writes option's header into a buffer in the
+ /// on-wire format. The universe set for the particular option
+ /// is used to determine whether option code and length are
+ /// stored as 2-byte (for DHCPv6) or single-byte (for DHCPv4)
+ /// values. For DHCPv4 options, this method checks if the
+ /// length does not exceed 255 bytes and throws exception if
+ /// it does.
+ /// This method is used by derived classes to pack option's
+ /// header into a buffer. This method should not be called
+ /// directly by other classes.
+ ///
+ /// @param [out] buf output buffer.
+ /// @param check if set to false, allows options larger than 255 for v4
+ void packHeader(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Store sub options in a buffer.
+ ///
+ /// This method stores all sub-options defined for a particular
+ /// option in a on-wire format in output buffer provided.
+ /// This function is called by pack function in this class or
+ /// derived classes that override pack.
+ ///
+ /// @param [out] buf output buffer.
+ /// @param check if set to false, allows options larger than 255 for v4
+ ///
+ /// @todo The set of exceptions thrown by this function depend on
+ /// exceptions thrown by pack methods invoked on objects
+ /// representing sub options. We should consider whether to aggregate
+ /// those into one exception which can be documented here.
+ void packOptions(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Builds a collection of sub options from the buffer.
+ ///
+ /// This method parses the provided buffer and builds a collection
+ /// of objects representing sub options. This function may throw
+ /// different exceptions when option assembly fails.
+ ///
+ /// @param buf buffer to be parsed.
+ ///
+ /// @todo The set of exceptions thrown by this function depend on
+ /// exceptions thrown by unpack methods invoked on objects
+ /// representing sub options. We should consider whether to aggregate
+ /// those into one exception which can be documented here.
+ void unpackOptions(const OptionBuffer& buf);
+
+ /// @brief Returns option header in the textual format.
+ ///
+ /// This protected method should be called by the derived classes in
+ /// their respective @c toText implementations.
+ ///
+ /// @param indent Number of spaces to insert before the text.
+ /// @param type_name Option type name. If empty, the option name
+ /// is omitted.
+ ///
+ /// @return Option header in the textual format.
+ std::string headerToText(const int indent = 0,
+ const std::string& type_name = "") const;
+
+ /// @brief Returns collection of suboptions in the textual format.
+ ///
+ /// This protected method should be called by the derived classes
+ /// in their respective @c toText implementations to append the
+ /// suboptions held by this option. Note that there are some
+ /// option types which don't have suboptions because they contain
+ /// variable length fields. For such options this method is not
+ /// called.
+ ///
+ /// @param indent Number of spaces to insert before the text.
+ ///
+ //// @return Suboptions in the textual format.
+ std::string suboptionsToText(const int indent = 0) const;
+
+ /// @brief A protected method used for option correctness.
+ ///
+ /// It is used in constructors. In there are any problems detected
+ /// (like specifying type > 255 for DHCPv4 option), it will throw
+ /// BadValue or OutOfRange exceptions.
+ void check() const;
+
+ /// option universe (V4 or V6)
+ Universe universe_;
+
+ /// option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
+ uint16_t type_;
+
+ /// contains content of this data
+ OptionBuffer data_;
+
+ /// collection for storing suboptions
+ OptionCollection options_;
+
+ /// Name of the option space being encapsulated by this option.
+ std::string encapsulated_space_;
+
+ /// @todo probably 2 different containers have to be used for v4 (unique
+ /// options) and v6 (options with the same type can repeat)
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_H
diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc
new file mode 100644
index 0000000..33e0049
--- /dev/null
+++ b/src/lib/dhcp/option4_addrlst.cc
@@ -0,0 +1,128 @@
+// Copyright (C) 2011-2022 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 <asiolink/io_address.h>
+#include <dhcp/option4_addrlst.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <iomanip>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <string.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+Option4AddrLst::Option4AddrLst(uint8_t type)
+ : Option(V4, type) {
+}
+
+Option4AddrLst::Option4AddrLst(uint8_t type, const AddressContainer& addrs)
+ : Option(V4, type) {
+ setAddresses(addrs);
+ // don't set addrs_ directly. setAddresses() will do additional checks.
+}
+
+
+Option4AddrLst::Option4AddrLst(uint8_t type, OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(V4, type) {
+ if ( (distance(first, last) % V4ADDRESS_LEN) ) {
+ isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_
+ << " has invalid length=" << distance(first, last)
+ << ", must be divisible by 4.");
+ }
+
+ while (first != last) {
+ const uint8_t* ptr = &(*first);
+ addAddress(IOAddress(readUint32(ptr, distance(first, last))));
+ first += V4ADDRESS_LEN;
+ }
+}
+
+Option4AddrLst::Option4AddrLst(uint8_t type, const IOAddress& addr)
+ : Option(V4, type) {
+ setAddress(addr);
+}
+
+OptionPtr
+Option4AddrLst::clone() const {
+ return (cloneInternal<Option4AddrLst>());
+}
+
+void
+Option4AddrLst::pack(isc::util::OutputBuffer& buf, bool check) const {
+ if (check && addrs_.size() * V4ADDRESS_LEN > 255) {
+ isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_ << " is too big."
+ << "At most 255 bytes are supported.");
+ }
+
+ buf.writeUint8(type_);
+ buf.writeUint8(len() - getHeaderLen());
+
+ AddressContainer::const_iterator addr = addrs_.begin();
+
+ while (addr != addrs_.end()) {
+ buf.writeUint32(addr->toUint32());
+ ++addr;
+ }
+}
+
+void Option4AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
+ if (!addr.isV4()) {
+ isc_throw(BadValue, "Can't store non-IPv4 address in "
+ << "Option4AddrLst option");
+ }
+ addrs_.clear();
+ addAddress(addr);
+}
+
+void Option4AddrLst::setAddresses(const AddressContainer& addrs) {
+ // Do not copy it as a whole. addAddress() does sanity checks.
+ // i.e. throw if someone tries to set IPv6 address.
+ addrs_.clear();
+ for (AddressContainer::const_iterator addr = addrs.begin();
+ addr != addrs.end(); ++addr) {
+ addAddress(*addr);
+ }
+}
+
+void Option4AddrLst::addAddress(const isc::asiolink::IOAddress& addr) {
+ if (!addr.isV4()) {
+ isc_throw(BadValue, "Can't store non-IPv4 address in "
+ << "Option4AddrLst option");
+ }
+ addrs_.push_back(addr);
+}
+
+uint16_t Option4AddrLst::len() const {
+ // Returns length of the complete option (option header + data length)
+ return (getHeaderLen() + addrs_.size() * V4ADDRESS_LEN);
+}
+
+std::string Option4AddrLst::toText(int indent) const {
+ std::stringstream output;
+ output << headerToText(indent) << ":";
+
+ for (AddressContainer::const_iterator addr = addrs_.begin();
+ addr != addrs_.end(); ++addr) {
+ output << " " << (*addr);
+ }
+
+ return (output.str());
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option4_addrlst.h b/src/lib/dhcp/option4_addrlst.h
new file mode 100644
index 0000000..7e17aae
--- /dev/null
+++ b/src/lib/dhcp/option4_addrlst.h
@@ -0,0 +1,165 @@
+// Copyright (C) 2011-2022 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 OPTION4_ADDRLST_H
+#define OPTION4_ADDRLST_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <util/buffer.h>
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration to Option4AddrLst class.
+class Option4AddrLst;
+
+/// A pointer to the Option4AddrLst object.
+typedef boost::shared_ptr<Option4AddrLst> Option4AddrLstPtr;
+
+/// @brief DHCPv4 Option class for handling list of IPv4 addresses.
+///
+/// This class handles a list of IPv4 addresses. An example of such option
+/// is dns-servers option. It can also be used to handle a single address.
+class Option4AddrLst : public isc::dhcp::Option {
+public:
+
+ /// Defines a collection of IPv4 addresses.
+ typedef std::vector<isc::asiolink::IOAddress> AddressContainer;
+
+ /// @brief Constructor, creates an option with empty list of addresses.
+ ///
+ /// Creates empty option that can hold addresses. Addresses can be added
+ /// with addAddress(), setAddress() or setAddresses().
+ ///
+ /// @param type option type
+ Option4AddrLst(uint8_t type);
+
+ /// @brief Constructor, creates an option with a list of addresses.
+ ///
+ /// Creates an option that contains specified list of IPv4 addresses.
+ ///
+ /// @param type option type
+ /// @param addrs container with a list of addresses
+ Option4AddrLst(uint8_t type, const AddressContainer& addrs);
+
+ /// @brief Constructor, creates an option with a single address.
+ ///
+ /// Creates an option that contains a single address.
+ ///
+ /// @param type option type
+ /// @param addr a single address that will be stored as 1-elem. address list
+ Option4AddrLst(uint8_t type, const isc::asiolink::IOAddress& addr);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// TODO: This can be templated to use different containers, not just
+ /// vector. Prototype should look like this:
+ /// template<typename InputIterator> Option(Universe u, uint16_t type,
+ /// InputIterator first, InputIterator last);
+ ///
+ /// vector<int8_t> myData;
+ /// Example usage: new Option(V4, 123, myData.begin()+1, myData.end()-1)
+ /// This will create DHCPv4 option of type 123 that contains data from
+ /// trimmed (first and last byte removed) myData vector.
+ ///
+ /// @param type option type (0-255 for V4 and 0-65535 for V6)
+ /// @param first iterator to the first element that should be copied
+ /// @param last iterator to the next element after the last one
+ /// to be copied.
+ Option4AddrLst(uint8_t type, OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Writes option in a wire-format to a buffer.
+ ///
+ /// Method will throw if option storing fails for some reason.
+ ///
+ /// @param buf output buffer (option will be stored there)
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printing text
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0) const;
+
+ /// Returns length of the complete option (data length + DHCPv4/DHCPv6
+ /// option header)
+ ///
+ /// @return length of the option
+ virtual uint16_t len() const;
+
+ /// @brief Returns vector with addresses.
+ ///
+ /// We return a copy of our list. Although this includes overhead,
+ /// it also makes this list safe to use after this option object
+ /// is no longer available. As options are expected to hold only
+ /// a few (1-3) addresses, the overhead is not that big.
+ ///
+ /// @return address container with addresses
+ AddressContainer getAddresses() const { return addrs_; };
+
+ /// @brief Sets addresses list.
+ ///
+ /// Clears existing list of addresses and adds a single address to that
+ /// list. This is very convenient method for options that are supposed to
+ /// only a single option. See addAddress() if you want to add
+ /// address to existing list or setAddresses() if you want to
+ /// set the whole list at once.
+ ///
+ /// Passed address must be IPv4 address. Otherwise BadValue exception
+ /// will be thrown.
+ ///
+ /// @param addrs address collection to be set
+ void setAddresses(const AddressContainer& addrs);
+
+ /// @brief Clears address list and sets a single address.
+ ///
+ /// Clears existing list of addresses and adds a single address to that
+ /// list. This is very convenient method for options that are supposed to
+ /// only a single option. See addAddress() if you want to add
+ /// address to existing list or setAddresses() if you want to
+ /// set the whole list at once.
+ ///
+ /// Passed address must be IPv4 address. Otherwise BadValue exception
+ /// will be thrown.
+ ///
+ /// @param addr an address that is going to be set as 1-element address list
+ void setAddress(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Adds address to existing list of addresses.
+ ///
+ /// Adds a single address to that list. See setAddress() if you want to
+ /// define only a single address or setAddresses() if you want to
+ /// set the whole list at once.
+ ///
+ /// Passed address must be IPv4 address. Otherwise BadValue exception
+ /// will be thrown.
+ ///
+ /// @param addr an address that is going to be added to existing list
+ void addAddress(const isc::asiolink::IOAddress& addr);
+
+protected:
+ /// contains list of addresses
+ AddressContainer addrs_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION4_ADDRLST_H
diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc
new file mode 100644
index 0000000..7869c0e
--- /dev/null
+++ b/src/lib/dhcp/option4_client_fqdn.cc
@@ -0,0 +1,554 @@
+// Copyright (C) 2013-2022 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 <dhcp/dhcp4.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements the logic for the Option6ClientFqdn class.
+///
+/// The purpose of the class is to separate the implementation details
+/// of the Option4ClientFqdn class from the interface. This implementation
+/// uses libdns classes to process FQDNs. At some point it may be
+/// desired to split libdhcp++ from libdns. In such case the
+/// implementation of this class may be changed. The declaration of the
+/// Option6ClientFqdn class holds the pointer to implementation, so
+/// the transition to a different implementation would not affect the
+/// header file.
+class Option4ClientFqdnImpl {
+public:
+ /// Holds flags carried by the option.
+ uint8_t flags_;
+ /// Holds RCODE1 and RCODE2 values.
+ Option4ClientFqdn::Rcode rcode1_;
+ Option4ClientFqdn::Rcode rcode2_;
+ /// Holds the pointer to a domain name carried in the option.
+ boost::shared_ptr<isc::dns::Name> domain_name_;
+ /// Indicates whether domain name is partial or fully qualified.
+ Option4ClientFqdn::DomainNameType domain_name_type_;
+
+ /// @brief Constructor, from domain name.
+ ///
+ /// @param flags A value of the flags option field.
+ /// @param rcode An object representing the RCODE1 and RCODE2 values.
+ /// @param domain_name A domain name carried by the option given in the
+ /// textual format.
+ /// @param name_type A value which indicates whether domain-name is partial
+ /// or fully qualified.
+ Option4ClientFqdnImpl(const uint8_t flags,
+ const Option4ClientFqdn::Rcode& rcode,
+ const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type);
+
+ /// @brief Constructor, from wire data.
+ ///
+ /// @param first An iterator pointing to the beginning of the option data
+ /// in the wire format.
+ /// @param last An iterator pointing to the end of the option data in the
+ /// wire format.
+ Option4ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor.
+ ///
+ /// @param source An object being copied.
+ Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source);
+
+ /// @brief Assignment operator.
+ ///
+ /// @param source An object which is being assigned.
+ Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source);
+
+ /// @brief Set a new domain name for the option.
+ ///
+ /// @param domain_name A new domain name to be assigned.
+ /// @param name_type A value which indicates whether the domain-name is
+ /// partial or fully qualified.
+ void setDomainName(const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type);
+
+ /// @brief Check if flags are valid.
+ ///
+ /// In particular, this function checks if the N and S bits are not
+ /// set to 1 in the same time.
+ ///
+ /// @param flags A value carried by the flags field of the option.
+ /// @param check_mbz A boolean value which indicates if this function should
+ /// check if the MBZ bits are set (if true). This parameter should be set
+ /// to false when validating flags in the received message. This is because
+ /// server should ignore MBZ bits in received messages.
+ /// @throw InvalidOption6FqdnFlags if flags are invalid.
+ static void checkFlags(const uint8_t flags, const bool check_mbz);
+
+ /// @brief Parse the Option provided in the wire format.
+ ///
+ /// @param first An iterator pointing to the beginning of the option data
+ /// in the wire format.
+ /// @param last An iterator pointing to the end of the option data in the
+ /// wire format.
+ void parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Parse domain-name encoded in the canonical format.
+ ///
+ void parseCanonicalDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Parse domain-name encoded in the deprecated ASCII format.
+ ///
+ /// @param first An iterator pointing to the beginning of the option data
+ /// where domain-name is stored.
+ /// @param last An iterator pointing to the end of the option data where
+ /// domain-name is stored.
+ void parseASCIIDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+};
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const uint8_t flags,
+ const Option4ClientFqdn::Rcode& rcode,
+ const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not passed
+ // by reference. Note that, it accepts the non-const enum value
+ // to be passed by value. In both cases it is unnecessary to
+ // pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option4ClientFqdn::DomainNameType name_type)
+ : flags_(flags),
+ rcode1_(rcode),
+ rcode2_(rcode),
+ domain_name_(),
+ domain_name_type_(name_type) {
+
+ // Check if flags are correct. Also, check that MBZ bits are not set. If
+ // they are, throw exception.
+ checkFlags(flags_, true);
+ // Set domain name. It may throw an exception if domain name has wrong
+ // format.
+ setDomainName(domain_name, name_type);
+}
+
+Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
+ rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
+ parseWireData(first, last);
+ // Verify that flags value was correct. This constructor is used to parse
+ // incoming packet, so don't check MBZ bits. They are ignored because we
+ // don't want to discard the whole option because MBZ bits are set.
+ checkFlags(flags_, false);
+}
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
+ : flags_(source.flags_),
+ rcode1_(source.rcode1_),
+ rcode2_(source.rcode2_),
+ domain_name_(),
+ domain_name_type_(source.domain_name_type_) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+ }
+}
+
+Option4ClientFqdnImpl&
+// This assignment operator handles assignment to self, it copies all
+// required values.
+// cppcheck-suppress operatorEqToSelf
+Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+ } else {
+ domain_name_.reset();
+ }
+
+ // Assignment is exception safe.
+ flags_ = source.flags_;
+ rcode1_ = source.rcode1_;
+ rcode2_ = source.rcode2_;
+ domain_name_type_ = source.domain_name_type_;
+
+ return (*this);
+}
+
+void
+Option4ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not passed
+ // by reference. Note that, it accepts the non-const enum
+ // to be passed by value. In both cases it is unnecessary to
+ // pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option4ClientFqdn::DomainNameType name_type) {
+ // domain-name must be trimmed. Otherwise, string comprising spaces only
+ // would be treated as a fully qualified name.
+ std::string name = isc::util::str::trim(domain_name);
+ if (name.empty()) {
+ if (name_type == Option4ClientFqdn::FULL) {
+ isc_throw(InvalidOption4FqdnDomainName,
+ "fully qualified domain-name must not be empty"
+ << " when setting new domain-name for DHCPv4 Client"
+ << " FQDN Option");
+ }
+ // The special case when domain-name is empty is marked by setting the
+ // pointer to the domain-name object to NULL.
+ domain_name_.reset();
+
+ } else {
+ try {
+ // The second argument indicates that the name should be converted
+ // to lower case.
+ domain_name_.reset(new isc::dns::Name(name, true));
+
+ } catch (const Exception&) {
+ isc_throw(InvalidOption4FqdnDomainName,
+ "invalid domain-name value '"
+ << domain_name << "' when setting new domain-name for"
+ << " DHCPv4 Client FQDN Option");
+
+ }
+ }
+
+ domain_name_type_ = name_type;
+}
+
+void
+Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
+ // The Must Be Zero (MBZ) bits must not be set.
+ if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) {
+ isc_throw(InvalidOption4FqdnFlags,
+ "invalid DHCPv4 Client FQDN Option flags: 0x"
+ << std::hex << static_cast<int>(flags) << std::dec);
+ }
+
+ // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
+ // MUST be 0. Checking it here.
+ if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
+ == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
+ isc_throw(InvalidOption4FqdnFlags,
+ "both N and S flag of the DHCPv4 Client FQDN Option are set."
+ << " According to RFC 4702, if the N bit is 1 the S bit"
+ << " MUST be 0");
+ }
+}
+
+void
+Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+
+ // Buffer must comprise at least one byte with the flags.
+ // The domain-name may be empty.
+ if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
+ isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
+ << DHO_FQDN << ") is truncated");
+ }
+
+ // Parse flags
+ flags_ = *(first++);
+
+ // Parse RCODE1 and RCODE2.
+ rcode1_ = Option4ClientFqdn::Rcode(*(first++));
+ rcode2_ = Option4ClientFqdn::Rcode(*(first++));
+
+ try {
+ if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) {
+ parseCanonicalDomainName(first, last);
+
+ } else {
+ parseASCIIDomainName(first, last);
+
+ }
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOption4FqdnDomainName,
+ "failed to parse the domain-name in DHCPv4 Client FQDN"
+ << " Option: " << ex.what());
+ }
+}
+
+void
+Option4ClientFqdnImpl::parseCanonicalDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ // Parse domain-name if any.
+ if (std::distance(first, last) > 0) {
+ // The FQDN may comprise a partial domain-name. In this case it lacks
+ // terminating 0. If this is the case, we will need to add zero at
+ // the end because Name object constructor requires it.
+ if (*(last - 1) != 0) {
+ // Create temporary buffer and add terminating zero.
+ OptionBuffer buf(first, last);
+ buf.push_back(0);
+ // Reset domain name.
+ isc::util::InputBuffer name_buf(&buf[0], buf.size());
+ // The second argument indicates that the name should be converted
+ // to lower case.
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ // Terminating zero was missing, so set the domain-name type
+ // to partial.
+ domain_name_type_ = Option4ClientFqdn::PARTIAL;
+ } else {
+ // We are dealing with fully qualified domain name so there is
+ // no need to add terminating zero. Simply pass the buffer to
+ // Name object constructor.
+ isc::util::InputBuffer name_buf(&(*first),
+ std::distance(first, last));
+ // The second argument indicates that the name should be converted
+ // to lower case.
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ // Set the domain-type to fully qualified domain name.
+ domain_name_type_ = Option4ClientFqdn::FULL;
+ }
+ }
+}
+
+void
+Option4ClientFqdnImpl::parseASCIIDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ if (std::distance(first, last) > 0) {
+ std::string domain_name(first, last);
+ // The second argument indicates that the name should be converted
+ // to lower case.
+ domain_name_.reset(new isc::dns::Name(domain_name, true));
+ domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
+ Option4ClientFqdn::FULL : Option4ClientFqdn::PARTIAL;
+ }
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
+ : Option(Option::V4, DHO_FQDN),
+ impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
+ const Rcode& rcode,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type)
+ : Option(Option::V4, DHO_FQDN),
+ impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
+ domain_name_type)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(Option::V4, DHO_FQDN, first, last),
+ impl_(new Option4ClientFqdnImpl(first, last)) {
+}
+
+Option4ClientFqdn::~Option4ClientFqdn() {
+ delete (impl_);
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
+ : Option(source),
+ impl_(new Option4ClientFqdnImpl(*source.impl_)) {
+}
+
+OptionPtr
+Option4ClientFqdn::clone() const {
+ return (cloneInternal<Option4ClientFqdn>());
+}
+
+Option4ClientFqdn&
+// This assignment operator handles assignment to self, it uses copy
+// constructor of Option4ClientFqdnImpl to copy all required values.
+// cppcheck-suppress operatorEqToSelf
+Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
+ Option::operator=(source);
+ Option4ClientFqdnImpl* old_impl = impl_;
+ impl_ = new Option4ClientFqdnImpl(*source.impl_);
+ delete(old_impl);
+ return (*this);
+}
+
+bool
+Option4ClientFqdn::getFlag(const uint8_t flag) const {
+ // Caller should query for one of the: E, N, S or O flags. Any other value
+ /// is invalid and results in the exception.
+ if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
+ isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
+ << " Option flag specified, expected E, N, S or O");
+ }
+
+ return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
+ // Check that flag is in range between 0x1 and 0x7. Although it is
+ // discouraged this check doesn't preclude the caller from setting
+ // multiple flags concurrently.
+ if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+ isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
+ << " Option flag 0x" << std::hex
+ << static_cast<int>(flag) << std::dec
+ << " is being set. Expected combination of E, N, S and O");
+ }
+
+ // Copy the current flags into local variable. That way we will be able
+ // to test new flags settings before applying them.
+ uint8_t new_flag = impl_->flags_;
+ if (set_flag) {
+ new_flag |= flag;
+ } else {
+ new_flag &= ~flag;
+ }
+
+ // Check new flags. If they are valid, apply them. Also, check that MBZ
+ // bits are not set.
+ Option4ClientFqdnImpl::checkFlags(new_flag, true);
+ impl_->flags_ = new_flag;
+}
+
+std::pair<Option4ClientFqdn::Rcode, Option4ClientFqdn::Rcode>
+Option4ClientFqdn::getRcode() const {
+ return (std::make_pair(impl_->rcode1_, impl_->rcode2_));
+}
+
+void
+Option4ClientFqdn::setRcode(const Rcode& rcode) {
+ impl_->rcode1_ = rcode;
+ impl_->rcode2_ = rcode;
+}
+
+void
+Option4ClientFqdn::resetFlags() {
+ impl_->flags_ = 0;
+}
+
+std::string
+Option4ClientFqdn::getDomainName() const {
+ if (impl_->domain_name_) {
+ return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+ PARTIAL));
+ }
+ // If an object holding domain-name is NULL it means that the domain-name
+ // is empty.
+ return ("");
+}
+
+void
+Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+ // If domain-name is empty, do nothing.
+ if (!impl_->domain_name_) {
+ return;
+ }
+
+ if (getFlag(FLAG_E)) {
+ // Domain name, encoded as a set of labels.
+ isc::dns::LabelSequence labels(*impl_->domain_name_);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ if (impl_->domain_name_type_ == PARTIAL) {
+ --read_len;
+ }
+ buf.writeData(data, read_len);
+ }
+
+ } else {
+ std::string domain_name = getDomainName();
+ if (!domain_name.empty()) {
+ buf.writeData(&domain_name[0], domain_name.size());
+ }
+
+ }
+}
+
+void
+Option4ClientFqdn::setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type) {
+ impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option4ClientFqdn::resetDomainName() {
+ setDomainName("", PARTIAL);
+}
+
+Option4ClientFqdn::DomainNameType
+Option4ClientFqdn::getDomainNameType() const {
+ return (impl_->domain_name_type_);
+}
+
+void
+Option4ClientFqdn::pack(isc::util::OutputBuffer& buf, bool check) const {
+ // Header = option code and length.
+ packHeader(buf, check);
+ // Flags field.
+ buf.writeUint8(impl_->flags_);
+ // RCODE1 and RCODE2
+ buf.writeUint8(impl_->rcode1_.getCode());
+ buf.writeUint8(impl_->rcode2_.getCode());
+ // Domain name.
+ packDomainName(buf);
+}
+
+void
+Option4ClientFqdn::unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ setData(first, last);
+ impl_->parseWireData(first, last);
+ // Check that the flags in the received option are valid. Ignore MBZ bits,
+ // because we don't want to discard the whole option because of MBZ bits
+ // being set.
+ impl_->checkFlags(impl_->flags_, false);
+}
+
+std::string
+Option4ClientFqdn::toText(int indent) const {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ stream << in << "type=" << type_ << " (CLIENT_FQDN), "
+ << "flags: ("
+ << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+ << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
+ << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+ << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+ << "domain-name='" << getDomainName() << "' ("
+ << (getDomainNameType() == PARTIAL ? "partial" : "full")
+ << ")";
+
+ return (stream.str());
+}
+
+uint16_t
+Option4ClientFqdn::len() const {
+ uint16_t domain_name_length = 0;
+ // Try to calculate the length of the domain name only if there is
+ // any domain name specified.
+ if (impl_->domain_name_) {
+ // If the E flag is specified, the domain name is encoded in the
+ // canonical format. The length of the domain name depends on
+ // whether it is a partial or fully qualified names. For the
+ // former the length is 1 octet lesser because it lacks terminating
+ // zero.
+ if (getFlag(FLAG_E)) {
+ domain_name_length = impl_->domain_name_type_ == FULL ?
+ impl_->domain_name_->getLength() :
+ impl_->domain_name_->getLength() - 1;
+
+ // ASCII encoded domain name. Its length is just a length of the
+ // string holding the name.
+ } else {
+ domain_name_length = getDomainName().length();
+ }
+ }
+
+ return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option4_client_fqdn.h b/src/lib/dhcp/option4_client_fqdn.h
new file mode 100644
index 0000000..c34d6b7
--- /dev/null
+++ b/src/lib/dhcp/option4_client_fqdn.h
@@ -0,0 +1,377 @@
+// Copyright (C) 2013-2022 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 OPTION4_CLIENT_FQDN_H
+#define OPTION4_CLIENT_FQDN_H
+
+#include <dhcp/option.h>
+#include <dns/name.h>
+
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid flags have been specified for
+/// DHCPv4 Client FQDN %Option.
+class InvalidOption4FqdnFlags : public Exception {
+public:
+ InvalidOption4FqdnFlags(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption4FqdnDomainName : public Exception {
+public:
+ InvalidOption4FqdnDomainName(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option4ClientFqdn class.
+class Option4ClientFqdnImpl;
+
+/// @brief Represents DHCPv4 Client FQDN %Option (code 81).
+///
+/// This option has been defined in the RFC 4702 and it has a following
+/// structure:
+/// - Code (1 octet) - option code (always equal to 81).
+/// - Len (1 octet) - a length of the option.
+/// - Flags (1 octet) - a field carrying "NEOS" flags described below.
+/// - RCODE1 (1 octet) - deprecated field which should be set to 0 by the client
+/// and set to 255 by the server.
+/// - RCODE2 (1 octet) - deprecated, should be used in the same way as RCODE1.
+/// - Domain Name - variable length field comprising partial or fully qualified
+/// domain name.
+///
+/// The flags field has the following structure:
+/// @code
+/// 0 1 2 3 4 5 6 7
+/// +-+-+-+-+-+-+-+-+
+/// | MBZ |N|E|O|S|
+/// +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+/// Update,
+/// - E flag specifies encoding of the Domain Name field. If this flag is set
+/// to 1 it indicates canonical wire format without compression. 0 indicates
+/// the deprecated ASCII format.
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference set with the S bit.
+/// - S flag specifies whether server should (1) or should not (0) perform
+/// forward (FQDN-to-address) updates.
+///
+/// This class exposes a set of functions to modify flags and check their
+/// correctness.
+///
+/// Domain names being carried by DHCPv4 Client Fqdn %Option can be fully
+/// qualified or partial. Partial domain names are encoded similar to the
+/// fully qualified domain names, except that they lack terminating zero
+/// at the end of their wire representation (or lack of dot at the end, in
+/// case of ASCII encoding). It is also accepted to create an instance of
+/// this option which has empty domain-name. Clients use empty domain-names
+/// to indicate that server should generate complete fully qualified
+/// domain-name.
+///
+/// Since domain names are case insensitive (see RFC 4343), this class
+/// converts them to lower case format regardless if they are received over
+/// the wire or created from strings.
+///
+/// @warning: The RFC4702 section 2.3.1 states that the clients and servers
+/// should use character sets specified in RFC952, section 2.1 for ASCII-encoded
+/// domain-names. This class doesn't detect the character set violation for
+/// ASCII-encoded domain-name. It could be implemented in the future but it is
+/// not important now for two reasons:
+/// - ASCII encoding is deprecated
+/// - clients SHOULD obey restrictions but if they don't, server may still
+/// process the option
+///
+/// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that
+/// server sets them to 255. This class allows to set the value for these
+/// fields and both fields are always set to the same value. There is no way
+/// to set them separately (e.g. set different value for RCODE1 and RCODE2).
+/// However, there are no use cases which would require it.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
+class Option4ClientFqdn : public Option {
+public:
+
+ ///
+ /// @name A set of constants used to identify and set bits in the flags field
+ //@{
+ static const uint8_t FLAG_S = 0x01; ///< Bit S
+ static const uint8_t FLAG_O = 0x02; ///< Bit O
+ static const uint8_t FLAG_E = 0x04; ///< Bit E
+ static const uint8_t FLAG_N = 0x08; ///< Bit N
+ //@}
+
+ /// @brief Mask which zeroes MBZ flag bits.
+ static const uint8_t FLAG_MASK = 0xF;
+
+ /// @brief Represents the value of one of the RCODE1 or RCODE2 fields.
+ ///
+ /// Typically, RCODE values are set to 255 by the server and to 0 by the
+ /// clients (as per RFC 4702).
+ class Rcode {
+ public:
+ Rcode(const uint8_t rcode)
+ : rcode_(rcode) { }
+
+ /// @brief Returns the value of the RCODE.
+ ///
+ /// Returned value can be directly used to create the on-wire format
+ /// of the DHCPv4 Client FQDN %Option.
+ uint8_t getCode() const {
+ return (rcode_);
+ }
+
+ private:
+ uint8_t rcode_;
+ };
+
+
+ /// @brief Type of the domain-name: partial or full.
+ enum DomainNameType {
+ PARTIAL,
+ FULL
+ };
+
+ /// @brief The size in bytes of the fixed fields within DHCPv4 Client Fqdn
+ /// %Option.
+ ///
+ /// The fixed fields are:
+ /// - Flags
+ /// - RCODE1
+ /// - RCODE2
+ static const uint16_t FIXED_FIELDS_LEN = 3;
+
+ /// @brief Constructor, creates option instance using flags and domain name.
+ ///
+ /// This constructor is used to create an instance of the option which will
+ /// be included in outgoing messages.
+ ///
+ /// Note that the RCODE values are encapsulated by the Rcode object (not a
+ /// simple uint8_t value). This helps to prevent a caller from confusing the
+ /// flags value with rcode value (both are uint8_t values). For example:
+ /// if caller swaps the two, it will be detected in the compilation time.
+ /// Also, this API encourages the caller to use two predefined functions:
+ /// @c RCODE_SERVER and @c RCODE_CLIENT to set the value of RCODE. These
+ /// functions generate objects which represent the only valid values to be
+ /// be passed to the constructor (255 and 0 respectively). Other
+ /// values should not be used. However, it is still possible that the other
+ /// entity (client or server) sends the option with invalid value. Although,
+ /// the RCODE values are ignored, there should be a way to represent such
+ /// invalid RCODE value. The Rcode class is capable of representing it.
+ ///
+ /// @param flags a combination of flags to be stored in flags field.
+ /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+ /// fields of the option. Both fields are assigned the same value
+ /// encapsulated by the parameter.
+ /// @param domain_name a name to be stored in the domain-name field.
+ /// @param domain_name_type indicates if the domain name is partial
+ /// or full.
+ /// @throw InvalidOption4FqdnFlags if value of the flags field is wrong.
+ /// @throw InvalidOption4FqdnDomainName if the domain-name is invalid.
+ explicit Option4ClientFqdn(const uint8_t flags,
+ const Rcode& rcode,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type = FULL);
+
+ /// @brief Constructor, creates option instance with empty domain name.
+ ///
+ /// This constructor creates an instance of the option with empty
+ /// domain-name. This domain-name is marked partial.
+ ///
+ /// @param flags a combination of flags to be stored in flags field.
+ /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+ /// fields. Both fields are assigned the same value encapsulated by this
+ /// parameter.
+ /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid.
+ Option4ClientFqdn(const uint8_t flags, const Rcode& rcode);
+
+ /// @brief Constructor, creates an option instance from part of the buffer.
+ ///
+ /// This constructor is mainly used to parse options in the received
+ /// messages. Function parameters specify buffer bounds from which the
+ /// option should be created. The size of the buffer chunk, specified by
+ /// the constructor's parameters should be equal or larger than the size
+ /// of the option. Otherwise, constructor will throw an exception.
+ ///
+ /// @param first the lower bound of the buffer to create option from.
+ /// @param last the upper bound of the buffer to create option from.
+ /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid.
+ /// @throw InvalidOption4FqdnDomainName if the domain-name carried by the
+ /// option is invalid.
+ /// @throw OutOfRange if the option is truncated.
+ explicit Option4ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor
+ Option4ClientFqdn(const Option4ClientFqdn& source);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Destructor
+ virtual ~Option4ClientFqdn();
+
+ /// @brief Assignment operator
+ Option4ClientFqdn& operator=(const Option4ClientFqdn& source);
+
+ /// @brief Checks if the specified flag of the DHCPv4 Client FQDN %Option
+ /// is set.
+ ///
+ /// @param flag A value specifying a bit within flags field to be checked.
+ /// It must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O,
+ /// @c FLAG_N.
+ ///
+ /// @return true if the bit of the specified flags bit is set, false
+ /// otherwise.
+ /// @throw InvalidOption4ClientFlags if specified flag which value is to be
+ /// returned is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O).
+ bool getFlag(const uint8_t flag) const;
+
+ /// @brief Modifies the value of the specified DHCPv4 Client Fqdn %Option
+ /// flag.
+ ///
+ /// @param flag A value specifying a bit within flags field to be set. It
+ /// must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O, @c FLAG_N.
+ /// @param set a boolean value which indicates whether flag should be
+ /// set (true), or cleared (false).
+ /// @throw InvalidOption4ClientFlags if specified flag which value is to be
+ /// set is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O).
+ void setFlag(const uint8_t flag, const bool set);
+
+ /// @brief Sets the flag field value to 0.
+ void resetFlags();
+
+ /// @brief Returns @c Rcode objects representing value of RCODE1 and RCODE2.
+ ///
+ /// @return Pair of Rcode objects of which first is the RCODE1 and the
+ /// second is RCODE2.
+ std::pair<Rcode, Rcode> getRcode() const;
+
+ /// @brief Set Rcode value.
+ ///
+ /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2.
+ /// Both fields are assigned the same value.
+ void setRcode(const Rcode& rcode);
+
+ /// @brief Returns the domain-name in the text format.
+ ///
+ /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+ /// If domain-name is fully qualified, it has the dot at the end (e.g.
+ /// myhost.example.com.).
+ ///
+ /// @return domain-name in the text format.
+ std::string getDomainName() const;
+
+ /// @brief Writes domain-name in the wire format into a buffer.
+ ///
+ /// The data being written are appended at the end of the buffer.
+ ///
+ /// @param [out] buf buffer where domain-name will be written.
+ void packDomainName(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Set new domain-name.
+ ///
+ /// @param domain_name domain name field value in the text format.
+ /// @param domain_name_type type of the domain name: partial or fully
+ /// qualified.
+ /// @throw InvalidOption4FqdnDomainName if the specified domain-name is
+ /// invalid.
+ void setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type);
+
+ /// @brief Set empty domain-name.
+ ///
+ /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+ /// with empty partial domain-name. It is exception safe.
+ void resetDomainName();
+
+ /// @brief Returns enumerator value which indicates whether domain-name is
+ /// partial or full.
+ ///
+ /// @return An enumerator value indicating whether domain-name is partial
+ /// or full.
+ DomainNameType getDomainNameType() const;
+
+ /// @brief Writes option in the wire format into a buffer.
+ ///
+ /// @param [out] buf output buffer where option data will be stored.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses option from the received buffer.
+ ///
+ /// Method creates an instance of the DHCPv4 Client FQDN %Option from the
+ /// wire format. Parameters specify the bounds of the buffer to read option
+ /// data from. The size of the buffer limited by the specified parameters
+ /// should be equal or larger than size of the option (including its
+ /// header). Otherwise exception will be thrown.
+ ///
+ /// @param first lower bound of the buffer to parse option from.
+ /// @param last upper bound of the buffer to parse option from.
+ virtual void unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// The string returned by the method comprises the bit value of each
+ /// option flag and the domain-name.
+ ///
+ /// @param indent number of spaces before printed text.
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns length of the complete option (data length +
+ /// DHCPv4 option header).
+ ///
+ /// @return length of the option.
+ virtual uint16_t len() const;
+
+ ///
+ /// @name Well known Rcode declarations for DHCPv4 Client FQDN %Option
+ ///
+ //@{
+ /// @brief Rcode being set by the server.
+ inline static const Rcode& RCODE_SERVER() {
+ static Rcode rcode(255);
+ return (rcode);
+ }
+
+ /// @brief Rcode being set by the client.
+ inline static const Rcode& RCODE_CLIENT() {
+ static Rcode rcode(0);
+ return (rcode);
+ }
+ //@}
+
+private:
+
+ /// @brief A pointer to the implementation.
+ Option4ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option4ClientFqdn object.
+typedef boost::shared_ptr<Option4ClientFqdn> Option4ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION4_CLIENT_FQDN_H
diff --git a/src/lib/dhcp/option4_dnr.cc b/src/lib/dhcp/option4_dnr.cc
new file mode 100644
index 0000000..f1b50cb
--- /dev/null
+++ b/src/lib/dhcp/option4_dnr.cc
@@ -0,0 +1,489 @@
+// Copyright (C) 2023 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 <dhcp/option4_dnr.h>
+#include <dns/labelsequence.h>
+#include <util/strutil.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option4Dnr::Option4Dnr(OptionBufferConstIter begin, OptionBufferConstIter end)
+ : Option(V4, DHO_V4_DNR) {
+ unpack(begin, end);
+}
+
+OptionPtr
+Option4Dnr::clone() const {
+ return (cloneInternal<Option4Dnr>());
+}
+
+void
+Option4Dnr::pack(OutputBuffer& buf, bool check) const {
+ packHeader(buf, check);
+ for (const DnrInstance& dnr_instance : dnr_instances_) {
+ buf.writeUint16(dnr_instance.getDnrInstanceDataLength());
+ buf.writeUint16(dnr_instance.getServicePriority());
+ buf.writeUint8(dnr_instance.getAdnLength());
+ dnr_instance.packAdn(buf);
+ if (dnr_instance.isAdnOnlyMode()) {
+ continue;
+ }
+
+ buf.writeUint8(dnr_instance.getAddrLength());
+ dnr_instance.packAddresses(buf);
+ dnr_instance.packSvcParams(buf);
+ }
+}
+
+void
+Option4Dnr::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ setData(begin, end);
+ while (begin != end) {
+ DnrInstance dnr_instance(V4);
+ if (std::distance(begin, end) < dnr_instance.getMinimalLength()) {
+ isc_throw(OutOfRange, dnr_instance.getLogPrefix()
+ << "DNR instance data truncated to size "
+ << std::distance(begin, end));
+ }
+
+ // Unpack DnrInstanceDataLength.
+ dnr_instance.unpackDnrInstanceDataLength(begin, end);
+
+ const OptionBufferConstIter dnr_instance_end = begin +
+ dnr_instance.getDnrInstanceDataLength();
+
+ // Unpack Service priority.
+ dnr_instance.unpackServicePriority(begin);
+
+ // Unpack ADN len + ADN.
+ dnr_instance.unpackAdn(begin, dnr_instance_end);
+
+ if (begin == dnr_instance_end) {
+ // ADN only mode, other fields are not included.
+ addDnrInstance(dnr_instance);
+ continue;
+ }
+
+ dnr_instance.setAdnOnlyMode(false);
+
+ // Unpack Addr Len + IPv4 Address(es).
+ dnr_instance.unpackAddresses(begin, dnr_instance_end);
+
+ // SvcParams (variable length) field is last.
+ dnr_instance.unpackSvcParams(begin, dnr_instance_end);
+
+ addDnrInstance(dnr_instance);
+ }
+}
+
+std::string
+Option4Dnr::toText(int indent) const {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ stream << in << "type=" << type_ << "(V4_DNR), "
+ << "len=" << (len() - getHeaderLen());
+ int i = 0;
+ for (const DnrInstance& dnr_instance : dnr_instances_) {
+ stream << ", DNR Instance " << ++i
+ << "(Instance len=" << dnr_instance.getDnrInstanceDataLength() << ", "
+ << dnr_instance.getDnrInstanceAsText() << ")";
+ }
+
+ return (stream.str());
+}
+
+uint16_t
+Option4Dnr::len() const {
+ uint16_t len = OPTION4_HDR_LEN;
+ for (const DnrInstance& dnr_instance : dnr_instances_) {
+ len += dnr_instance.getDnrInstanceDataLength() +
+ dnr_instance.getDnrInstanceDataLengthSize();
+ }
+
+ return (len);
+}
+
+void
+Option4Dnr::addDnrInstance(DnrInstance& dnr_instance) {
+ dnr_instances_.push_back(dnr_instance);
+}
+
+const std::unordered_set<std::string> DnrInstance::FORBIDDEN_SVC_PARAMS = {"ipv4hint", "ipv6hint"};
+
+DnrInstance::DnrInstance(Option::Universe universe)
+ : universe_(universe), dnr_instance_data_length_(0), service_priority_(0),
+ adn_length_(0), addr_length_(0), svc_params_length_(0),
+ adn_only_mode_(true), dnr_instance_data_length_size_(0),
+ adn_length_size_(0), addr_length_size_(0), minimal_length_(0) {
+ initMembers();
+}
+
+DnrInstance::DnrInstance(Option::Universe universe,
+ const uint16_t service_priority,
+ const std::string& adn,
+ const DnrInstance::AddressContainer& ip_addresses,
+ const std::string& svc_params)
+ : universe_(universe), dnr_instance_data_length_(0),
+ service_priority_(service_priority), adn_length_(0),
+ addr_length_(0), ip_addresses_(ip_addresses), svc_params_length_(0),
+ adn_only_mode_(true), svc_params_(svc_params),
+ dnr_instance_data_length_size_(0), adn_length_size_(0),
+ addr_length_size_(0), minimal_length_(0) {
+ initMembers();
+ setAdn(adn);
+ checkFields();
+}
+
+DnrInstance::DnrInstance(Option::Universe universe,
+ const uint16_t service_priority,
+ const std::string& adn)
+ : universe_(universe), dnr_instance_data_length_(0),
+ service_priority_(service_priority), adn_length_(0),
+ addr_length_(0), svc_params_length_(0), adn_only_mode_(true),
+ dnr_instance_data_length_size_(0), adn_length_size_(0),
+ addr_length_size_(0), minimal_length_(0) {
+ initMembers();
+ setAdn(adn);
+}
+
+void
+DnrInstance::packAdn(OutputBuffer& buf) const {
+ if (!adn_) {
+ // This should not happen since Encrypted DNS options are designed
+ // to always include an authentication domain name.
+ isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
+ << "Mandatory Authentication Domain Name fully "
+ "qualified domain-name is missing");
+ }
+
+ isc::dns::LabelSequence label_sequence(*adn_);
+ if (label_sequence.getDataLength() > 0) {
+ size_t data_length = 0;
+ const uint8_t* data = label_sequence.getData(&data_length);
+ buf.writeData(data, data_length);
+ }
+}
+
+void
+DnrInstance::packAddresses(OutputBuffer& buf) const {
+ AddressContainer::const_iterator address = ip_addresses_.begin();
+ while (address != ip_addresses_.end()) {
+ buf.writeUint32(address->toUint32());
+ ++address;
+ }
+}
+
+void
+DnrInstance::packSvcParams(OutputBuffer& buf) const {
+ if (svc_params_length_ > 0) {
+ buf.writeData(&(*svc_params_.begin()), svc_params_length_);
+ }
+}
+
+std::string
+DnrInstance::getAdnAsText() const {
+ return (adn_) ? (adn_->toText()) : ("");
+}
+
+void
+DnrInstance::setAdn(const std::string& adn) {
+ std::string trimmed_adn = str::trim(adn);
+ if (trimmed_adn.empty()) {
+ isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
+ << "Mandatory Authentication Domain Name fully "
+ "qualified domain-name must not be empty");
+ }
+
+ try {
+ adn_.reset(new isc::dns::Name(trimmed_adn, true));
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
+ << "Failed to parse "
+ "fully qualified domain-name from string - "
+ << ex.what());
+ }
+
+ size_t adn_len = 0;
+ isc::dns::LabelSequence label_sequence(*adn_);
+ label_sequence.getData(&adn_len);
+ if (adn_len > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(InvalidOptionDnrDomainName, getLogPrefix() << "Given ADN FQDN length " << adn_len
+ << " is bigger than uint_16 MAX");
+ }
+
+ adn_length_ = adn_len;
+ if (universe_ == Option::V4) {
+ dnr_instance_data_length_ = dnrInstanceLen();
+ }
+}
+
+void
+DnrInstance::unpackAdn(OptionBufferConstIter& begin, OptionBufferConstIter end) {
+ OpaqueDataTuple::LengthFieldType lft = OptionDataTypeUtil::getTupleLenFieldType(universe_);
+ OpaqueDataTuple adn_tuple(lft);
+ try {
+ adn_tuple.unpack(begin, end);
+ } catch (const Exception& ex) {
+ isc_throw(BadValue, getLogPrefix() << "failed to unpack ADN data"
+ << " - " << ex.what());
+ }
+
+ adn_length_ = adn_tuple.getLength();
+
+ // Encrypted DNS options are designed to always include an authentication domain name,
+ // so when there is no FQDN included, we shall throw an exception.
+ if (adn_length_ == 0) {
+ isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
+ << "Mandatory Authentication Domain Name fully "
+ "qualified domain-name is missing");
+ }
+
+ InputBuffer name_buf(adn_tuple.getData().data(), adn_length_);
+ try {
+ adn_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOptionDnrDomainName, getLogPrefix()
+ << "Failed to parse "
+ "fully qualified domain-name from wire format "
+ "- " << ex.what());
+ }
+
+ begin += adn_length_ + getAdnLengthSize();
+}
+
+void
+DnrInstance::checkSvcParams(bool from_wire_data) {
+ std::string svc_params = str::trim(svc_params_);
+ if (svc_params.empty()) {
+ isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
+ << "Provided Svc Params field is empty");
+ }
+
+ if (!from_wire_data) {
+ // If Service Params field was not parsed from on-wire data,
+ // but actually was provided with ctor, let's calculate svc_params_length_.
+ auto svc_params_len = svc_params.length();
+ if (svc_params_len > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(InvalidOptionDnrSvcParams, getLogPrefix()
+ << "Given Svc Params length " << svc_params_len
+ << " is bigger than uint_16 MAX");
+ }
+
+ svc_params_length_ = svc_params_len;
+ // If Service Params field was not parsed from on-wire data,
+ // but actually was provided with ctor, let's replace it with trimmed value.
+ svc_params_ = svc_params;
+ }
+
+ // SvcParams are a whitespace-separated list, with each SvcParam
+ // consisting of a SvcParamKey=SvcParamValue pair or a standalone SvcParamKey.
+ // SvcParams in presentation format MAY appear in any order, but keys MUST NOT be repeated.
+
+ // Let's put all elements of a whitespace-separated list into a vector.
+ std::vector<std::string> tokens = str::tokens(svc_params, " ");
+
+ // Set of keys used to check if a key is not repeated.
+ std::unordered_set<std::string> keys;
+ // String sanitizer is used to check keys syntax.
+ str::StringSanitizerPtr sanitizer;
+ // SvcParamKeys are lower-case alphanumeric strings. Key names
+ // contain 1-63 characters from the ranges "a"-"z", "0"-"9", and "-".
+ std::string regex = "[^a-z0-9-]";
+ sanitizer.reset(new str::StringSanitizer(regex, ""));
+
+ // Now let's check each SvcParamKey=SvcParamValue pair.
+ for (const std::string& token : tokens) {
+ std::vector<std::string> key_val = str::tokens(token, "=");
+ if (key_val.size() > 2) {
+ isc_throw(InvalidOptionDnrSvcParams,
+ getLogPrefix() << "Wrong Svc Params syntax - more than one "
+ "equals sign found in SvcParamKey=SvcParamValue pair");
+ }
+
+ // SvcParam Key related checks come below.
+ std::string key = key_val[0];
+ if (key.length() > 63) {
+ isc_throw(InvalidOptionDnrSvcParams,
+ getLogPrefix() << "Wrong Svc Params syntax - key had more than 63 "
+ "characters - " << key);
+ }
+
+ if (FORBIDDEN_SVC_PARAMS.find(key) != FORBIDDEN_SVC_PARAMS.end()) {
+ isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key "
+ << key << " must not be used");
+ }
+
+ auto insert_res = keys.insert(key);
+ if (!insert_res.second) {
+ isc_throw(InvalidOptionDnrSvcParams, getLogPrefix() << "Wrong Svc Params syntax - key "
+ << key << " was duplicated");
+ }
+
+ std::string sanitized_key = sanitizer->scrub(key);
+ if (sanitized_key.size() < key.size()) {
+ isc_throw(InvalidOptionDnrSvcParams,
+ getLogPrefix()
+ << "Wrong Svc Params syntax - invalid character used in key - " << key);
+ }
+ }
+}
+
+void
+DnrInstance::checkFields() {
+ if (svc_params_.empty() && ip_addresses_.empty()) {
+ // ADN only mode, nothing more to do.
+ return;
+ }
+
+ if (!svc_params_.empty() && ip_addresses_.empty()) {
+ // As per draft-ietf-add-dnr 3.1.8:
+ // If additional data is supplied (i.e. not ADN only mode),
+ // the option includes at least one valid IP address.
+ isc_throw(OutOfRange, getLogPrefix() << "No IP address given. Since this is not ADN only "
+ "mode, at least one valid IP address must "
+ "be included");
+ }
+
+ if (!svc_params_.empty()) {
+ checkSvcParams(false);
+ }
+
+ adn_only_mode_ = false;
+ const uint8_t addr_field_len = (universe_ == Option::V4) ? V4ADDRESS_LEN : V6ADDRESS_LEN;
+ const uint16_t max_addr_len = (universe_ == Option::V4) ? std::numeric_limits<uint8_t>::max() :
+ std::numeric_limits<uint16_t>::max();
+ auto addr_len = ip_addresses_.size() * addr_field_len;
+ if (addr_len > max_addr_len) {
+ isc_throw(OutOfRange, getLogPrefix() << "Given IP addresses length " << addr_len
+ << " is bigger than MAX " << max_addr_len);
+ }
+
+ addr_length_ = addr_len;
+ if (universe_ == Option::V4) {
+ dnr_instance_data_length_ = dnrInstanceLen();
+ }
+}
+
+std::string
+DnrInstance::getDnrInstanceAsText() const {
+ std::ostringstream stream;
+ stream << "service_priority=" << service_priority_ << ", adn_length=" << adn_length_ << ", "
+ << "adn='" << getAdnAsText() << "'";
+ if (!adn_only_mode_) {
+ stream << ", addr_length=" << addr_length_ << ", address(es):";
+ for (const auto& address : ip_addresses_) {
+ stream << " " << address.toText();
+ }
+
+ if (svc_params_length_ > 0) {
+ stream << ", svc_params='" + svc_params_ + "'";
+ }
+ }
+
+ return (stream.str());
+}
+
+uint16_t
+DnrInstance::dnrInstanceLen() const {
+ uint16_t len = SERVICE_PRIORITY_SIZE + adn_length_ + getAdnLengthSize();
+ if (!adn_only_mode_) {
+ len += addr_length_ + getAddrLengthSize() + svc_params_length_;
+ }
+
+ return (len);
+}
+
+void
+DnrInstance::addIpAddress(const IOAddress& ip_address) {
+ ip_addresses_.push_back(ip_address);
+}
+
+void
+DnrInstance::unpackDnrInstanceDataLength(OptionBufferConstIter& begin, OptionBufferConstIter end) {
+ dnr_instance_data_length_ = readUint16(&*begin, getDnrInstanceDataLengthSize());
+ begin += getDnrInstanceDataLengthSize();
+ if (std::distance(begin, end) < dnr_instance_data_length_) {
+ isc_throw(OutOfRange, getLogPrefix()
+ << "DNR instance data truncated to size "
+ << std::distance(begin, end) << " but it was supposed to be "
+ << dnr_instance_data_length_);
+ }
+}
+
+void
+DnrInstance::unpackServicePriority(OptionBufferConstIter& begin) {
+ service_priority_ = readUint16(&*begin, SERVICE_PRIORITY_SIZE);
+ begin += SERVICE_PRIORITY_SIZE;
+}
+
+void
+DnrInstance::unpackAddresses(OptionBufferConstIter& begin, const OptionBufferConstIter end) {
+ OpaqueDataTuple addr_tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ try {
+ addr_tuple.unpack(begin, end);
+ } catch (const Exception& ex) {
+ isc_throw(BadValue, getLogPrefix() << "failed to unpack IP Addresses data"
+ << " - " << ex.what());
+ }
+
+ addr_length_ = addr_tuple.getLength();
+ // It MUST be a multiple of 4.
+ if ((addr_length_ % V4ADDRESS_LEN) != 0) {
+ isc_throw(OutOfRange, getLogPrefix()
+ << "Addr Len=" << addr_length_ << " is not divisible by 4");
+ }
+
+ // As per draft-ietf-add-dnr 3.1.8:
+ // If additional data is supplied (i.e. not ADN only mode),
+ // the option includes at least one valid IP address.
+ if (addr_length_ == 0) {
+ isc_throw(OutOfRange, getLogPrefix()
+ << "Addr Len=" << addr_length_
+ << " but it must contain at least one valid IP address");
+ }
+
+ begin += getAddrLengthSize();
+ OptionBufferConstIter addr_begin = begin;
+ OptionBufferConstIter addr_end = addr_begin + addr_length_;
+
+ while (addr_begin != addr_end) {
+ const uint8_t* ptr = &(*addr_begin);
+ addIpAddress(IOAddress(readUint32(ptr, std::distance(addr_begin, addr_end))));
+ addr_begin += V4ADDRESS_LEN;
+ begin += V4ADDRESS_LEN;
+ }
+}
+
+void
+DnrInstance::unpackSvcParams(OptionBufferConstIter& begin, OptionBufferConstIter end) {
+ svc_params_length_ = std::distance(begin, end);
+ if (svc_params_length_ > 0) {
+ svc_params_.assign(begin, end);
+ checkSvcParams();
+ begin += svc_params_length_;
+ }
+}
+
+void
+DnrInstance::initMembers() {
+ dnr_instance_data_length_size_ = (universe_ == Option::V6) ? 0 : 2;
+ adn_length_size_ = (universe_ == Option::V6) ? 2 : 1;
+ addr_length_size_ = (universe_ == Option::V6) ? 2 : 1;
+ minimal_length_ = dnr_instance_data_length_size_ + SERVICE_PRIORITY_SIZE + adn_length_size_;
+ log_prefix_ =
+ (universe_ == Option::V4) ?
+ ("DHCPv4 Encrypted DNS Option (" + std::to_string(DHO_V4_DNR) + ") malformed: ") :
+ ("DHCPv6 Encrypted DNS Option (" + std::to_string(D6O_V6_DNR) + ") malformed: ");
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcp/option4_dnr.h b/src/lib/dhcp/option4_dnr.h
new file mode 100644
index 0000000..c79b2d4
--- /dev/null
+++ b/src/lib/dhcp/option4_dnr.h
@@ -0,0 +1,526 @@
+// Copyright (C) 2023 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 OPTION4_DNR_H
+#define OPTION4_DNR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <dns/name.h>
+
+#include <unordered_set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOptionDnrDomainName : public Exception {
+public:
+ InvalidOptionDnrDomainName(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {
+ }
+};
+
+/// @brief Exception thrown when Service parameters have wrong format.
+class InvalidOptionDnrSvcParams : public Exception {
+public:
+ InvalidOptionDnrSvcParams(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {
+ }
+};
+
+/// @brief Represents DNR Instance which is used both in DHCPv4
+/// and DHCPv6 Encrypted DNS %Option.
+///
+/// DNR Instance includes the configuration data of an encrypted DNS resolver.
+/// It is used to build OPTION_V4_DNR (code 162). There may be multiple DNR Instances
+/// in one OPTION_V4_DNR %Option. OPTION_V6_DNR (code 144) is using very similar structure,
+/// only that there must be only one DNR Instance per one OPTION_V6_DNR %Option. That's why
+/// @c Option6Dnr class can derive from this @c DnrInstance class, whereas @c Option4Dnr class
+/// should have a container of @c DnrInstance's.
+///
+/// DNR Instance Data Format has been defined in the @c draft-ietf-add-dnr (to be replaced
+/// with published RFC).
+class DnrInstance {
+public:
+ /// @brief A Type defined for container holding IP addresses.
+ typedef std::vector<isc::asiolink::IOAddress> AddressContainer;
+
+ /// @brief Size in octets of Service Priority field.
+ static const uint8_t SERVICE_PRIORITY_SIZE = 2;
+
+ /// @brief Set of forbidden SvcParams.
+ ///
+ /// The service parameters MUST NOT include
+ /// "ipv4hint" or "ipv6hint" SvcParams as they are superseded by the
+ /// included IP addresses.
+ static const std::unordered_set<std::string> FORBIDDEN_SVC_PARAMS;
+
+ /// @brief Constructor of the empty DNR Instance.
+ ///
+ /// @param universe either V4 or V6 Option universe
+ explicit DnrInstance(Option::Universe universe);
+
+ /// @brief Constructor of the DNR Instance with all fields from params.
+ ///
+ /// Constructor of the DNR Instance where all fields
+ /// i.e. Service priority, ADN, IP address(es) and Service params
+ /// are provided as ctor parameters.
+ ///
+ /// @param universe either V4 or V6 Option universe
+ /// @param service_priority Service priority
+ /// @param adn ADN FQDN
+ /// @param ip_addresses Container of IP addresses
+ /// @param svc_params Service Parameters
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN
+ /// @throw InvalidOptionDnrSvcParams Thrown when @c checkSvcParams(from_wire_data) throws
+ /// @throw OutOfRange Thrown in case of no IP addresses found or when IP addresses length
+ /// is too big
+ DnrInstance(Option::Universe universe,
+ uint16_t service_priority,
+ const std::string& adn,
+ const AddressContainer& ip_addresses,
+ const std::string& svc_params);
+
+ /// @brief Constructor of the DNR Instance in ADN only mode.
+ ///
+ /// Constructor of the DNR Instance in ADN only mode
+ /// i.e. only Service priority and ADN FQDN
+ /// are provided as ctor parameters.
+ ///
+ /// @param universe either V4 or V6 Option universe
+ /// @param service_priority Service priority
+ /// @param adn ADN FQDN
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN
+ DnrInstance(Option::Universe universe, uint16_t service_priority, const std::string& adn);
+
+ /// @brief Default destructor.
+ virtual ~DnrInstance() = default;
+
+ /// @brief Getter of the @c dnr_instance_data_length_.
+ ///
+ /// @return Length of all following data inside this DNR instance in octets.
+ uint16_t getDnrInstanceDataLength() const {
+ return (dnr_instance_data_length_);
+ }
+
+ /// @brief Getter of the @c service_priority_.
+ ///
+ /// @return The priority of this DNR instance compared to other instances.
+ uint16_t getServicePriority() const {
+ return (service_priority_);
+ }
+
+ /// @brief Getter of the @c adn_length_.
+ ///
+ /// @return Length of the authentication-domain-name data in octets.
+ uint16_t getAdnLength() const {
+ return (adn_length_);
+ }
+
+ /// @brief Returns the Authentication domain name in the text format.
+ ///
+ /// FQDN data stored in @c adn_ is converted into text format and returned.
+ ///
+ /// @return Authentication domain name in the text format.
+ std::string getAdnAsText() const;
+
+ /// @brief Returns string representation of the DNR instance.
+ ///
+ /// @return String with text representation.
+ std::string getDnrInstanceAsText() const;
+
+ /// @brief Getter of the @c addr_length_.
+ ///
+ /// @return Length of enclosed IP addresses in octets.
+ uint16_t getAddrLength() const {
+ return (addr_length_);
+ }
+
+ /// @brief Getter of the @c svc_params_length_.
+ ///
+ /// @return Length of Service Parameters field in octets.
+ uint16_t getSvcParamsLength() const {
+ return (svc_params_length_);
+ }
+
+ /// @brief Returns vector with addresses.
+ ///
+ /// We return a copy of our list. Although this includes overhead,
+ /// it also makes this list safe to use after this option object
+ /// is no longer available. As options are expected to hold only
+ /// a few (1-3) addresses, the overhead is not that big.
+ ///
+ /// @return Address container with addresses.
+ AddressContainer getAddresses() const {
+ return (ip_addresses_);
+ }
+
+ /// @brief Getter of the @c svc_params_ field.
+ ///
+ /// @return Returns Service Parameters as a string.
+ const std::string& getSvcParams() const {
+ return (svc_params_);
+ }
+
+ /// @brief Returns minimal length of the DNR instance data (without headers) in octets.
+ ///
+ /// @return Minimal length of the DNR instance data (without headers) in octets.
+ uint8_t getMinimalLength() const {
+ return (minimal_length_);
+ }
+
+ /// @brief Returns size in octets of Addr Length field.
+ uint8_t getAddrLengthSize() const {
+ return (addr_length_size_);
+ }
+
+ /// @brief Returns size in octets of DNR Instance Data Length field.
+ uint8_t getDnrInstanceDataLengthSize() const {
+ return (dnr_instance_data_length_size_);
+ }
+
+ /// @brief Returns size in octets of ADN Length field.
+ uint8_t getAdnLengthSize() const {
+ return (adn_length_size_);
+ }
+
+ /// @brief Returns Log prefix depending on V4/V6 Option universe.
+ ///
+ /// @return Log prefix as a string which can be used for prints when throwing an exception.
+ std::string getLogPrefix() const {
+ return (log_prefix_);
+ }
+
+ /// @brief Returns whether ADN only mode is enabled or disabled.
+ bool isAdnOnlyMode() const {
+ return (adn_only_mode_);
+ }
+
+ /// @brief Sets Authentication domain name from given string.
+ ///
+ /// Sets FQDN of the encrypted DNS resolver from given string.
+ /// It may throw an exception if parsing of the FQDN fails or if
+ /// provided FQDN length is bigger than uint16_t Max.
+ /// It also calculates and sets value of Addr length field.
+ ///
+ /// @param adn string representation of ADN FQDN
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN
+ /// from given string.
+ void setAdn(const std::string& adn);
+
+ /// @brief Setter of the @c adn_only_mode_ field.
+ ///
+ /// @param adn_only_mode enabled/disabled setting
+ void setAdnOnlyMode(bool adn_only_mode) {
+ adn_only_mode_ = adn_only_mode;
+ }
+
+ /// @brief Writes the ADN FQDN in the wire format into a buffer.
+ ///
+ /// The Authentication Domain Name - fully qualified domain name of the encrypted
+ /// DNS resolver data is appended at the end of the buffer.
+ ///
+ /// @param [out] buf buffer where ADN FQDN will be written.
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown when mandatory field ADN is empty.
+ void packAdn(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Writes the IP address(es) in the wire format into a buffer.
+ ///
+ /// The IP address(es) (@c ip_addresses_) data is appended at the end
+ /// of the buffer.
+ ///
+ /// @param [out] buf buffer where IP address(es) will be written.
+ virtual void packAddresses(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Writes the Service Parameters in the wire format into a buffer.
+ ///
+ /// The Service Parameters (@c svc_params_) data is appended at the end
+ /// of the buffer.
+ ///
+ /// @param [out] buf buffer where SvcParams will be written.
+ void packSvcParams(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Unpacks DNR Instance Data Length from wire data buffer and stores
+ /// it in @c dnr_instance_data_length_.
+ ///
+ /// It may throw in case of malformed data detected during parsing.
+ ///
+ /// @param begin beginning of the buffer from which the field will be read
+ /// @param end end of the buffer from which the field will be read
+ ///
+ /// @throw OutOfRange Thrown in case of truncated data detected.
+ void unpackDnrInstanceDataLength(OptionBufferConstIter& begin, OptionBufferConstIter end);
+
+ /// @brief Unpacks Service Priority from wire data buffer and stores it in @c service_priority_.
+ ///
+ /// @param begin beginning of the buffer from which the field will be read
+ void unpackServicePriority(OptionBufferConstIter& begin);
+
+ /// @brief Unpacks the ADN from given wire data buffer and stores it in @c adn_ field.
+ ///
+ /// It may throw in case of malformed data detected during parsing.
+ ///
+ /// @param begin beginning of the buffer from which the ADN will be read
+ /// @param end end of the buffer from which the ADN will be read
+ ///
+ /// @throw BadValue Thrown in case of any issue with unpacking opaque data of the ADN.
+ /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN
+ /// from given wire data.
+ void unpackAdn(OptionBufferConstIter& begin, OptionBufferConstIter end);
+
+ /// @brief Unpacks IP address(es) from wire data and stores it/them in @c ip_addresses_.
+ ///
+ /// It may throw in case of malformed data detected during parsing.
+ ///
+ /// @param begin beginning of the buffer from which the field will be read
+ /// @param end end of the buffer from which the field will be read
+ ///
+ /// @throw BadValue Thrown in case of any issue with unpacking opaque data of the IP addresses.
+ /// @throw OutOfRange Thrown in case of malformed data detected during parsing e.g.
+ /// Addr Len not divisible by 4, Addr Len is 0.
+ virtual void unpackAddresses(OptionBufferConstIter& begin, OptionBufferConstIter end);
+
+ /// @brief Unpacks Service Parameters from wire data buffer and stores it in @c svc_params_.
+ ///
+ /// It may throw in case of malformed data detected during parsing.
+ ///
+ /// @param begin beginning of the buffer from which the field will be read
+ /// @param end end of the buffer from which the field will be read
+ void unpackSvcParams(OptionBufferConstIter& begin, OptionBufferConstIter end);
+
+ /// @brief Checks SvcParams field if encoded correctly and throws in case of issue found.
+ ///
+ /// The field should be encoded following the rules in
+ /// Section 2.1 of [I-D.ietf-dnsop-svcb-https]. SvcParams are
+ /// a whitespace-separated list, with each SvcParam consisting of
+ /// a SvcParamKey=SvcParamValue pair or a standalone SvcParamKey.
+ ///
+ /// @note It is user's responsibility to provide correct configuration
+ /// of @c SvcParams as described in Section 2.1 of [I-D.ietf-dnsop-svcb-https].
+ /// Currently, SvcParamValue is not verified. Proper syntax of SvcParamValue
+ /// is described in Appendix A of [I-D.ietf-dnsop-svcb-https].
+ ///
+ /// @param from_wire_data used to determine whether SvcParams data comes
+ /// from unpacked wire data or from ctor param
+ ///
+ /// @throw InvalidOptionDnrSvcParams Thrown in case of any issue found when checking
+ /// @c ServiceParams field syntax
+ void checkSvcParams(bool from_wire_data = true);
+
+ /// @brief Checks IP address(es) field if data is correct and throws in case of issue found.
+ ///
+ /// Fields lengths are also calculated and saved to member variables.
+ ///
+ /// @throw OutOfRange Thrown in case of no IP addresses found or when IP addresses length
+ /// is too big
+ /// @throw InvalidOptionDnrSvcParams Thrown when @c checkSvcParams(from_wire_data) throws
+ void checkFields();
+
+ /// @brief Adds IP address to @c ip_addresses_ container.
+ ///
+ /// @param ip_address IP address to be added
+ void addIpAddress(const asiolink::IOAddress& ip_address);
+
+protected:
+ /// @brief Either V4 or V6 Option universe.
+ Option::Universe universe_;
+
+ /// @brief Authentication domain name field of variable length.
+ ///
+ /// Authentication domain name field of variable length holding
+ /// a fully qualified domain name of the encrypted DNS resolver.
+ /// This field is formatted as specified in Section 10 of RFC8415.
+ boost::shared_ptr<isc::dns::Name> adn_;
+
+ /// @brief Length of all following data inside this DNR instance in octets.
+ ///
+ /// This field is only used for DHCPv4 Encrypted DNS %Option.
+ uint16_t dnr_instance_data_length_;
+
+ /// @brief The priority of this instance compared to other DNR instances.
+ uint16_t service_priority_;
+
+ /// @brief Length of the authentication-domain-name data in octets.
+ uint16_t adn_length_;
+
+ /// @brief Length of included IP addresses in octets.
+ uint16_t addr_length_;
+
+ /// @brief Vector container holding one or more IP addresses.
+ ///
+ /// One or more IP addresses to reach the encrypted DNS resolver.
+ /// In case of DHCPv4, both private and public IPv4 addresses can
+ /// be included in this field.
+ /// In case of DHCPv6, an address can be link-local, ULA, or GUA.
+ AddressContainer ip_addresses_;
+
+ /// @brief Length of Service Parameters field in octets.
+ uint16_t svc_params_length_;
+
+ /// @brief Flag stating whether ADN only mode is used or not.
+ ///
+ /// "Addr Length", "IP(v4/v6) Address(es)", and "Service Parameters (SvcParams)"
+ /// fields are not present if the ADN-only mode is used.
+ bool adn_only_mode_;
+
+ /// @brief Service Parameters (SvcParams) (variable length).
+ ///
+ /// Specifies a set of service parameters that are encoded
+ /// following the rules in Section 2.1 of [I-D.ietf-dnsop-svcb-https].
+ std::string svc_params_;
+
+ /// @brief Calculates and returns length of DNR Instance data in octets.
+ /// @return length of DNR Instance data in octets.
+ uint16_t dnrInstanceLen() const;
+
+private:
+ /// @brief Size in octets of DNR Instance Data Length field.
+ ///
+ /// @note This field is used only in case of V4 DNR option.
+ uint8_t dnr_instance_data_length_size_;
+
+ /// @brief Size in octets of ADN Length field.
+ uint8_t adn_length_size_;
+
+ /// @brief Size in octets of Addr Length field.
+ uint8_t addr_length_size_;
+
+ /// @brief Minimal length of the DNR instance data (without headers) in octets.
+ ///
+ /// @note If the ADN-only mode is used, then "Addr Length", "ip(v4/v6)-address(es)",
+ /// and "Service Parameters (SvcParams)" fields are not present.
+ /// So minimal length of data is calculated by adding 2 octets for Service Priority,
+ /// octets needed for ADN Length and octets needed for DNR Instance Data Length
+ /// (only in case of DHCPv4).
+ uint8_t minimal_length_;
+
+ /// @brief Log prefix as a string which can be used for prints when throwing an exception.
+ std::string log_prefix_;
+
+ /// @brief Initializes private member variables basing on option's V4/V6 Universe.
+ ///
+ /// @note It must be called in all types of constructors of class @c DnrInstance .
+ void initMembers();
+};
+
+/// @brief Represents DHCPv4 Encrypted DNS %Option (code 162).
+///
+/// This option has been defined in the @c draft-ietf-add-dnr (to be replaced
+/// with published RFC) and it has a following structure:
+/// - option-code = 162 (1 octet)
+/// - option-len (1 octet)
+/// - multiple (one or more) DNR Instance Data
+///
+/// DNR Instance Data structure:
+/// - DNR Instance Data Length (2 octets)
+/// - Service Priority (2 octets)
+/// - ADN Length (1 octet)
+/// - Authentication Domain Name (variable length)
+/// - Addr Length (1 octet)
+/// - IPv4 Address(es) (variable length)
+/// - Service Parameters (variable length).
+class Option4Dnr : public Option {
+public:
+ /// @brief A Type defined for container holding DNR Instances.
+ typedef std::vector<DnrInstance> DnrInstanceContainer;
+
+ /// @brief Constructor of the %Option from on-wire data.
+ ///
+ /// This constructor creates an instance of the option using a buffer with
+ /// on-wire data. It may throw an exception if the @c unpack method throws.
+ ///
+ /// @param begin Iterator pointing to the beginning of the buffer holding an
+ /// option.
+ /// @param end Iterator pointing to the end of the buffer holding an option.
+ ///
+ /// @throw OutOfRange Thrown in case of truncated data. May be also thrown when
+ /// @c DnrInstance::unpackDnrInstanceDataLength(begin,end) throws.
+ /// @throw BadValue Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ /// @throw InvalidOptionDnrDomainName Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ Option4Dnr(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Constructor of the empty %Option.
+ ///
+ /// No DNR instances are included in the %Option when using this ctor.
+ /// They must be added afterwards.
+ /// No fields data included in the %Option when using this ctor.
+ /// They must be added afterwards.
+ Option4Dnr() : Option(V4, DHO_V4_DNR) {}
+
+ /// @brief Adds given DNR instance to Option's DNR Instance container.
+ /// @param dnr_instance DNR instance to be added
+ void addDnrInstance(DnrInstance& dnr_instance);
+
+ /// @brief Getter of the @c dnr_instances_ field.
+ /// @return Reference to Option's DNR Instance container
+ const DnrInstanceContainer& getDnrInstances() const {
+ return (dnr_instances_);
+ }
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ ///
+ /// @return Pointer to the copy of the option.
+ OptionPtr clone() const override;
+
+ /// @brief Writes option in wire-format to a buffer.
+ ///
+ /// Writes option in wire-format to buffer, returns pointer to first unused
+ /// byte after stored option (that is useful for writing options one after
+ /// another).
+ ///
+ /// @param buf pointer to a buffer
+ /// @param check flag which indicates if checking the option length is
+ /// required (used only in V4)
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown when Option's mandatory field ADN is empty.
+ /// @throw OutOfRange Thrown when @c check param set to @c true and
+ /// @c Option::packHeader(buf,check) throws.
+ void pack(util::OutputBuffer& buf, bool check = true) const override;
+
+ /// @brief Parses received wire data buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw OutOfRange Thrown in case of truncated data. May be also thrown when
+ /// @c DnrInstance::unpackDnrInstanceDataLength(begin,end) throws.
+ /// @throw BadValue Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ /// @throw InvalidOptionDnrDomainName Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) override;
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printing text
+ ///
+ /// @return string with text representation.
+ std::string toText(int indent = 0) const override;
+
+ /// @brief Returns length of the complete option (data length + DHCPv4/DHCPv6
+ /// option header)
+ ///
+ /// @return length of the option
+ uint16_t len() const override;
+
+protected:
+ /// @brief Container holding DNR Instances.
+ DnrInstanceContainer dnr_instances_;
+};
+
+/// A pointer to the @c OptionDnr4 object.
+typedef boost::shared_ptr<Option4Dnr> Option4DnrPtr;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // OPTION4_DNR_H
diff --git a/src/lib/dhcp/option6_addrlst.cc b/src/lib/dhcp/option6_addrlst.cc
new file mode 100644
index 0000000..a219d7c
--- /dev/null
+++ b/src/lib/dhcp/option6_addrlst.cc
@@ -0,0 +1,112 @@
+// Copyright (C) 2011-2022 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_addrlst.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option6AddrLst::Option6AddrLst(uint16_t type, const AddressContainer& addrs)
+ : Option(V6, type), addrs_(addrs) {
+}
+
+Option6AddrLst::Option6AddrLst(uint16_t type, const isc::asiolink::IOAddress& addr)
+ : Option(V6, type), addrs_(1,addr) {
+}
+
+Option6AddrLst::Option6AddrLst(uint16_t type, OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(V6, type) {
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6AddrLst::clone() const {
+ return (cloneInternal<Option6AddrLst>());
+}
+
+void
+Option6AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
+ if (!addr.isV6()) {
+ isc_throw(BadValue, "Can't store non-IPv6 address in Option6AddrLst option");
+ }
+
+ addrs_.clear();
+ addrs_.push_back(addr);
+}
+
+void
+Option6AddrLst::setAddresses(const AddressContainer& addrs) {
+ addrs_ = addrs;
+}
+
+void Option6AddrLst::pack(isc::util::OutputBuffer& buf, bool) const {
+ buf.writeUint16(type_);
+
+ // len() returns complete option length.
+ // len field contains length without 4-byte option header
+ buf.writeUint16(len() - getHeaderLen());
+
+ for (AddressContainer::const_iterator addr=addrs_.begin();
+ addr!=addrs_.end(); ++addr) {
+ if (!addr->isV6()) {
+ isc_throw(isc::BadValue, addr->toText()
+ << " is not an IPv6 address");
+ }
+ // If an address is IPv6 address it should have assumed
+ // length of V6ADDRESS_LEN.
+ buf.writeData(&addr->toBytes()[0], V6ADDRESS_LEN);
+ }
+}
+
+void Option6AddrLst::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if ((distance(begin, end) % V6ADDRESS_LEN) != 0) {
+ isc_throw(OutOfRange, "Option " << type_
+ << " malformed: len=" << distance(begin, end)
+ << " is not divisible by 16.");
+ }
+ while (begin != end) {
+ addrs_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin)));
+ begin += V6ADDRESS_LEN;
+ }
+}
+
+std::string Option6AddrLst::toText(int indent) const {
+ stringstream output;
+ output << headerToText(indent) << ":";
+
+ for (AddressContainer::const_iterator addr = addrs_.begin();
+ addr != addrs_.end(); ++addr) {
+ output << " " << *addr;
+ }
+ return (output.str());
+}
+
+uint16_t Option6AddrLst::len() const {
+ return (OPTION6_HDR_LEN + addrs_.size() * V6ADDRESS_LEN);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/option6_addrlst.h b/src/lib/dhcp/option6_addrlst.h
new file mode 100644
index 0000000..c171b1d
--- /dev/null
+++ b/src/lib/dhcp/option6_addrlst.h
@@ -0,0 +1,98 @@
+// Copyright (C) 2011-2022 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 OPTION6_ADDRLST_H
+#define OPTION6_ADDRLST_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief DHCPv6 Option class for handling list of IPv6 addresses.
+///
+/// This class handles a list of IPv6 addresses. An example of such option
+/// is dns-servers option. It can also be used to handle single address.
+class Option6AddrLst: public Option {
+
+public:
+ /// a container for (IPv6) addresses
+ typedef std::vector<isc::asiolink::IOAddress> AddressContainer;
+
+ /// @brief Constructor used during option generation.
+ ///
+ /// @param type option type
+ /// @param addrs vector of addresses to be stored
+ Option6AddrLst(uint16_t type, const AddressContainer& addrs);
+
+ /// @brief Simplified constructor for a single address
+ ///
+ /// @param type option type
+ /// @param addr a single address to be stored
+ Option6AddrLst(uint16_t type, const isc::asiolink::IOAddress& addr);
+
+ /// @brief Constructor used for parsing received option
+ ///
+ /// @param type option type
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ Option6AddrLst(uint16_t type, OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ virtual OptionPtr clone() const;
+
+ /// @brief Assembles on-wire form of this option
+ ///
+ /// @param buf pointer to packet buffer
+ /// @param check if set to false, allows options larger than 255 for v4
+ void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received data
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Sets a single address.
+ ///
+ /// @param addr a single address to be added
+ void setAddress(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Sets list of addresses.
+ ///
+ /// @param addrs a vector of addresses to be added
+ void setAddresses(const AddressContainer& addrs);
+
+ /// @brief Returns vector with addresses.
+ ///
+ /// We return a copy of our list. Although this includes overhead,
+ /// it also makes this list safe to use after this option object
+ /// is no longer available. As options are expected to hold only
+ /// a few (1-3) addresses, the overhead is not that big.
+ ///
+ /// @return address container with addresses
+ AddressContainer getAddresses() const { return addrs_; };
+
+ // returns data length (data length + DHCPv4/DHCPv6 option header)
+ virtual uint16_t len() const;
+
+protected:
+ AddressContainer addrs_;
+};
+
+/// @brief Pointer to the @c Option6AddrLst object.
+typedef boost::shared_ptr<Option6AddrLst> Option6AddrLstPtr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_ADDRLST_H
diff --git a/src/lib/dhcp/option6_auth.cc b/src/lib/dhcp/option6_auth.cc
new file mode 100644
index 0000000..b859886
--- /dev/null
+++ b/src/lib/dhcp/option6_auth.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2018-2022 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 <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_auth.h>
+#include <dhcp/option_space.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+#include <util/encode/hex.h>
+
+#include <sstream>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+ Option6Auth::Option6Auth(const uint8_t proto, const uint8_t algo,
+ const uint8_t method, const uint64_t rdm,
+ const std::vector<uint8_t>& info)
+ : Option(Option::V6, D6O_AUTH),
+ protocol_(proto), algorithm_(algo),
+ rdm_method_(method), rdm_value_(rdm),
+ auth_info_(info) {
+}
+
+OptionPtr
+Option6Auth::clone() const {
+ return (cloneInternal<Option6Auth>());
+}
+
+void
+Option6Auth::pack(isc::util::OutputBuffer& buf, bool) const {
+ if (buf.getCapacity() < (OPTION6_AUTH_MIN_LEN + OPTION6_HASH_MSG_LEN + OPTION6_HDR)) {
+ isc_throw(OutOfRange, "Option " << type_ << "Buffer too small for"
+ "packing data");
+ }
+
+ //header = option code + length
+ buf.writeUint16(type_);
+ // length = 11 bytes fixed field length+ length of auth information
+ buf.writeUint16(11 + uint16_t(auth_info_.size()));
+ // protocol 1 byte
+ buf.writeUint8(protocol_);
+ // algorithm 1 byte
+ buf.writeUint8(algorithm_);
+ // replay detection method
+ buf.writeUint8(rdm_method_);
+ // replay detection value
+ buf.writeUint64( rdm_value_);
+ // authentication information for reconfig msg
+ // should have zero
+
+ for (auto i : auth_info_) {
+ buf.writeUint8(i);
+ }
+}
+
+void
+Option6Auth::packHashInput(isc::util::OutputBuffer& buf) const {
+ if (buf.getCapacity() < (OPTION6_AUTH_MIN_LEN + OPTION6_HASH_MSG_LEN + OPTION6_HDR)) {
+ isc_throw(OutOfRange, "Option " << type_ << "Buffer too small for"
+ "computing hash input");
+ }
+
+ //header = option code + length
+ buf.writeUint16(type_);
+ // length = 11 bytes fixed field length+ length of auth information
+ buf.writeUint16(OPTION6_AUTH_MIN_LEN + OPTION6_HASH_MSG_LEN);
+ // protocol 1 byte
+ buf.writeUint8(protocol_);
+ // algorithm 1 byte
+ buf.writeUint8(algorithm_);
+ // replay detection method
+ buf.writeUint8(rdm_method_);
+ // replay detection value
+ buf.writeUint64(rdm_value_);
+ // authentication information for reconfig msg
+ // should have zero
+ for (uint8_t i = 0; i < OPTION6_HASH_MSG_LEN; i++) {
+ buf.writeUint8(0);
+ }
+}
+
+void
+Option6Auth::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ // throw if it contains length less than minimum size of the auth option
+ if (distance(begin, end) < Option6Auth::OPTION6_AUTH_MIN_LEN) {
+ isc_throw(OutOfRange, "Option " << type_ << " truncated");
+ }
+
+ protocol_ = *begin;
+ begin += sizeof(uint8_t);
+
+ algorithm_ = *begin;
+ begin += sizeof(uint8_t);
+
+ rdm_method_ = *begin;
+ begin += sizeof(uint8_t);
+
+ rdm_value_ = isc::util::readUint64(&(*begin), sizeof(uint64_t));
+ begin += sizeof(uint64_t);
+
+ auth_info_.erase(auth_info_.begin(), auth_info_.end());
+ std::for_each(begin, end, [this](uint8_t msgdata)
+ { auth_info_.push_back(msgdata); });
+}
+
+std::string
+Option6Auth::toText(int indent) const {
+ stringstream output;
+ std::string in(indent, ' '); //base indent
+
+ output << in << "protocol=" << static_cast<int>(protocol_)
+ << ", algorithm=" << static_cast<int>(algorithm_)
+ << ", rdm method=" << static_cast<int>(rdm_method_)
+ << ", rdm value=" << rdm_value_
+ << ", value=" << isc::util::encode::encodeHex(auth_info_);
+
+ return output.str();
+}
+
+} // end namespace dhcp
+} // end namespace isc
diff --git a/src/lib/dhcp/option6_auth.h b/src/lib/dhcp/option6_auth.h
new file mode 100644
index 0000000..fe2a4d5
--- /dev/null
+++ b/src/lib/dhcp/option6_auth.h
@@ -0,0 +1,145 @@
+// Copyright (C) 2018-2022 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 OPTION6_AUTH_H
+#define OPTION6_AUTH_H
+#endif
+
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+class Option6Auth;
+
+/// A pointer to the @c isc::dhcp::Option6Auth object.
+typedef boost::shared_ptr<Option6Auth> Option6AuthPtr;
+
+/// @brief This class represents Authentication (11) DHCPv6 option.
+///
+/// For details, see RFC 8415 Section 21.11.
+class Option6Auth: public Option {
+
+public:
+ static const uint8_t OPTION6_AUTH_MIN_LEN = 11;
+ static const uint8_t OPTION6_HASH_MSG_LEN = 16;
+ static const uint8_t OPTION6_HDR = 4;
+ /// @brief Constructor, used for auth options while transmitting
+ ///
+ /// @param proto protocol type
+ /// @param algo algorithm type
+ /// @param method remote detection method
+ /// @param rdm replay detection value
+ /// @param info authentication info.
+ Option6Auth(const uint8_t proto, const uint8_t algo, const uint8_t method,
+ const uint64_t rdm, const std::vector<uint8_t>& info);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param buf buffer (option will be stored here)
+ /// @param check if set to false, allows options larger than 255 for v4
+ void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// Writes option in wire-format to buf, for computing hash
+ /// auth info filled with 0 for a length of 128 bits
+ /// returns with pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param buf buffer (option will be stored here)
+ void packHashInput(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// Provides human readable text representation
+ ///
+ /// @param indent number of leading space characters
+ ///
+ /// @return string with text representation
+ virtual std::string toText(int indent = 0) const;
+
+ /// Set protocol type
+ ///
+ /// @param proto protocol type to be set
+ void setProtocol(uint8_t proto) { protocol_ = proto; }
+
+ /// Set hash algorithm type
+ ///
+ /// @param algo hash algorithm type to be set
+ void setHashAlgo(uint8_t algo) { algorithm_ = algo; }
+
+ /// Set replay detection method type
+ ///
+ /// @param method replay detection method to be set
+ void setReplyDetectionMethod(uint8_t method) { rdm_method_ = method; }
+
+ /// Set replay detection method value
+ ///
+ /// @param value replay detection method value to be set
+ void setReplyDetectionValue(uint64_t value) { rdm_value_ = value; }
+
+ /// Set authentication information
+ ///
+ /// @param auth_info authentication information to be set
+ void setAuthInfo(const std::vector<uint8_t>& auth_info) { auth_info_ = auth_info; }
+
+ /// Returns protocol type
+ ///
+ /// @return protocol value
+ uint8_t getProtocol() const { return protocol_; }
+
+ /// Returns hash algorithm type
+ ///
+ /// @return hash algorithm value
+ uint8_t getHashAlgo() const { return algorithm_; }
+
+ /// Returns replay detection method type
+ ///
+ /// @return replay detection method type value
+ uint8_t getReplyDetectionMethod() const { return rdm_method_; }
+
+ /// Return replay detection mechanism
+ ///
+ /// @return replay detection method value
+ uint64_t getReplyDetectionValue() const { return rdm_value_; }
+
+ /// Return authentication information
+ ///
+ /// @return authentication information value
+ std::vector<uint8_t> getAuthInfo() const { return auth_info_; }
+
+protected:
+ /// keeps protocol type
+ uint8_t protocol_;
+
+ /// keeps hash algorithm value
+ uint8_t algorithm_;
+
+ /// keeps replay detection method type
+ uint8_t rdm_method_;
+
+ /// keeps replay detection method value
+ uint64_t rdm_value_;
+
+ /// keeps authentication information
+ std::vector<uint8_t> auth_info_;
+};
+
+} // isc::dhcp namespace
+} // isc namespace
diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc
new file mode 100644
index 0000000..573a6ba
--- /dev/null
+++ b/src/lib/dhcp/option6_client_fqdn.cc
@@ -0,0 +1,456 @@
+// Copyright (C) 2013-2022 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 <dhcp/dhcp6.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements the logic for the Option6ClientFqdn class.
+///
+/// The purpose of the class is to separate the implementation details
+/// of the Option6ClientFqdn class from the interface. This implementation
+/// uses kea-libdns classes to process FQDNs. At some point it may be
+/// desired to split kea-libdhcp++ from kea-libdns. In such case the
+/// implementation of this class may be changed. The declaration of the
+/// Option6ClientFqdn class holds the pointer to implementation, so
+/// the transition to a different implementation would not affect the
+/// header file.
+class Option6ClientFqdnImpl {
+public:
+ /// Holds flags carried by the option.
+ uint8_t flags_;
+ /// Holds the pointer to a domain name carried in the option.
+ boost::shared_ptr<isc::dns::Name> domain_name_;
+ /// Indicates whether domain name is partial or fully qualified.
+ Option6ClientFqdn::DomainNameType domain_name_type_;
+
+ /// @brief Constructor, from domain name.
+ ///
+ /// @param flags A value of the flags option field.
+ /// @param domain_name A domain name carried by the option given in the
+ /// textual format.
+ /// @param name_type A value which indicates whether domain-name
+ /// is partial or fully qualified.
+ Option6ClientFqdnImpl(const uint8_t flags,
+ const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ /// @brief Constructor, from wire data.
+ ///
+ /// @param first An iterator pointing to the beginning of the option data
+ /// in the wire format.
+ /// @param last An iterator pointing to the end of the option data in the
+ /// wire format.
+ Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor.
+ ///
+ /// @param source An object being copied.
+ Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source);
+
+ /// @brief Assignment operator.
+ ///
+ /// @param source An object which is being assigned.
+ Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source);
+
+ /// @brief Set a new domain name for the option.
+ ///
+ /// @param domain_name A new domain name to be assigned.
+ /// @param name_type A value which indicates whether the domain-name is
+ /// partial or fully qualified.
+ void setDomainName(const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ /// @brief Check if flags are valid.
+ ///
+ /// In particular, this function checks if the N and S bits are not
+ /// set to 1 in the same time.
+ ///
+ /// @param flags A value carried by the flags field of the option.
+ /// @param check_mbz A boolean value which indicates if this function should
+ /// check if the MBZ bits are set (if true). This parameter should be set
+ /// to false when validating flags in the received message. This is because
+ /// server should ignore MBZ bits in received messages.
+ /// @throw InvalidOption6FqdnFlags if flags are invalid.
+ static void checkFlags(const uint8_t flags, const bool check_mbz);
+
+ /// @brief Parse the Option provided in the wire format.
+ ///
+ /// @param first An iterator pointing to the beginning of the option data
+ /// in the wire format.
+ /// @param last An iterator pointing to the end of the option data in the
+ /// wire format.
+ void parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+};
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const uint8_t flags,
+ const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not
+ // passed by reference. Note that it accepts the non-const
+ // enum to be passed by value. In both cases it is
+ // unnecessary to pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option6ClientFqdn::DomainNameType name_type)
+ : flags_(flags),
+ domain_name_(),
+ domain_name_type_(name_type) {
+
+ // Check if flags are correct. Also check if MBZ bits are set.
+ checkFlags(flags_, true);
+ // Set domain name. It may throw an exception if domain name has wrong
+ // format.
+ setDomainName(domain_name, name_type);
+}
+
+Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ parseWireData(first, last);
+ // Verify that flags value was correct. Do not check if MBZ bits are
+ // set because we should ignore those bits in received message.
+ checkFlags(flags_, false);
+}
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
+ : flags_(source.flags_),
+ domain_name_(),
+ domain_name_type_(source.domain_name_type_) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+ }
+}
+
+Option6ClientFqdnImpl&
+// This assignment operator handles assignment to self, it copies all
+// required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+ } else {
+ domain_name_.reset();
+
+ }
+
+ // This assignment should be exception safe.
+ flags_ = source.flags_;
+ domain_name_type_ = source.domain_name_type_;
+
+ return (*this);
+}
+
+void
+Option6ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not
+ // passed by reference. Note that it accepts the non-const
+ // enum to be passed by value. In both cases it is
+ // unnecessary to pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option6ClientFqdn::DomainNameType name_type) {
+ // domain-name must be trimmed. Otherwise, string comprising spaces only
+ // would be treated as a fully qualified name.
+ std::string name = isc::util::str::trim(domain_name);
+ if (name.empty()) {
+ if (name_type == Option6ClientFqdn::FULL) {
+ isc_throw(InvalidOption6FqdnDomainName,
+ "fully qualified domain-name must not be empty"
+ << " when setting new domain-name for DHCPv6 Client"
+ << " FQDN Option");
+ }
+ // The special case when domain-name is empty is marked by setting the
+ // pointer to the domain-name object to NULL.
+ domain_name_.reset();
+
+ } else {
+ try {
+ domain_name_.reset(new isc::dns::Name(name, true));
+
+ } catch (const Exception&) {
+ isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
+ << domain_name << "' when setting new domain-name for"
+ << " DHCPv6 Client FQDN Option");
+
+ }
+ }
+
+ domain_name_type_ = name_type;
+}
+
+void
+Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
+ // The Must Be Zero (MBZ) bits must not be set.
+ if (check_mbz && ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0)) {
+ isc_throw(InvalidOption6FqdnFlags,
+ "invalid DHCPv6 Client FQDN Option flags: 0x"
+ << std::hex << static_cast<int>(flags) << std::dec);
+ }
+
+ // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
+ // MUST be 0. Checking it here.
+ if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
+ == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
+ isc_throw(InvalidOption6FqdnFlags,
+ "both N and S flag of the DHCPv6 Client FQDN Option are set."
+ << " According to RFC 4704, if the N bit is 1 the S bit"
+ << " MUST be 0");
+ }
+}
+
+void
+Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+
+ // Buffer must comprise at least one byte with the flags.
+ // The domain-name may be empty.
+ if (std::distance(first, last) < Option6ClientFqdn::FLAG_FIELD_LEN) {
+ isc_throw(OutOfRange, "DHCPv6 Client FQDN Option ("
+ << D6O_CLIENT_FQDN << ") is truncated. Minimal option"
+ << " size is " << Option6ClientFqdn::FLAG_FIELD_LEN
+ << ", got option with size " << std::distance(first, last));
+ }
+
+ // Parse flags
+ flags_ = *(first++);
+
+ // Parse domain-name if any.
+ if (std::distance(first, last) > 0) {
+ // The FQDN may comprise a partial domain-name. In this case it lacks
+ // terminating 0. If this is the case, we will need to add zero at
+ // the end because Name object constructor requires it.
+ if (*(last - 1) != 0) {
+ // Create temporary buffer and add terminating zero.
+ OptionBuffer buf(first, last);
+ buf.push_back(0);
+ // Reset domain name.
+ isc::util::InputBuffer name_buf(&buf[0], buf.size());
+ try {
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception&) {
+ isc_throw(InvalidOption6FqdnDomainName, "failed to parse "
+ "partial domain-name from wire format");
+ }
+ // Terminating zero was missing, so set the domain-name type
+ // to partial.
+ domain_name_type_ = Option6ClientFqdn::PARTIAL;
+ } else {
+ // We are dealing with fully qualified domain name so there is
+ // no need to add terminating zero. Simply pass the buffer to
+ // Name object constructor.
+ isc::util::InputBuffer name_buf(&(*first),
+ std::distance(first, last));
+ try {
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception&) {
+ isc_throw(InvalidOption6FqdnDomainName, "failed to parse "
+ "fully qualified domain-name from wire format");
+ }
+ // Set the domain-type to fully qualified domain name.
+ domain_name_type_ = Option6ClientFqdn::FULL;
+ }
+ }
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
+ impl_(new Option6ClientFqdnImpl(first, last)) {
+}
+
+Option6ClientFqdn::~Option6ClientFqdn() {
+ delete(impl_);
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
+ : Option(source),
+ impl_(new Option6ClientFqdnImpl(*source.impl_)) {
+}
+
+OptionPtr
+Option6ClientFqdn::clone() const {
+ return (cloneInternal<Option6ClientFqdn>());
+}
+
+Option6ClientFqdn&
+// This assignment operator handles assignment to self, it uses copy
+// constructor of Option6ClientFqdnImpl to copy all required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+ Option::operator=(source);
+ Option6ClientFqdnImpl* old_impl = impl_;
+ impl_ = new Option6ClientFqdnImpl(*source.impl_);
+ delete(old_impl);
+ return (*this);
+}
+
+bool
+Option6ClientFqdn::getFlag(const uint8_t flag) const {
+ // Caller should query for one of the: N, S or O flags. Any other
+ // value is invalid.
+ if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
+ isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+ << " Option flag specified, expected N, S or O");
+ }
+
+ return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option6ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
+ // Check that flag is in range between 0x1 and 0x7. Note that this
+ // allows to set or clear multiple flags concurrently. Setting
+ // concurrent bits is discouraged (see header file) but it is not
+ // checked here so it will work.
+ if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+ isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+ << " Option flag 0x" << std::hex
+ << static_cast<int>(flag) << std::dec
+ << " is being set. Expected: N, S or O");
+ }
+
+ // Copy the current flags into local variable. That way we will be able
+ // to test new flags settings before applying them.
+ uint8_t new_flag = impl_->flags_;
+ if (set_flag) {
+ new_flag |= flag;
+ } else {
+ new_flag &= ~flag;
+ }
+
+ // Check new flags. If they are valid, apply them.
+ Option6ClientFqdnImpl::checkFlags(new_flag, true);
+ impl_->flags_ = new_flag;
+}
+
+void
+Option6ClientFqdn::resetFlags() {
+ impl_->flags_ = 0;
+}
+
+std::string
+Option6ClientFqdn::getDomainName() const {
+ if (impl_->domain_name_) {
+ return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+ PARTIAL));
+ }
+ // If an object holding domain-name is NULL it means that the domain-name
+ // is empty.
+ return ("");
+}
+
+void
+Option6ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+ // There is nothing to do if domain-name is empty.
+ if (!impl_->domain_name_) {
+ return;
+ }
+
+ // Domain name, encoded as a set of labels.
+ isc::dns::LabelSequence labels(*impl_->domain_name_);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ if (impl_->domain_name_type_ == PARTIAL) {
+ --read_len;
+ }
+ buf.writeData(data, read_len);
+ }
+}
+
+void
+Option6ClientFqdn::setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type) {
+ impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option6ClientFqdn::resetDomainName() {
+ setDomainName("", PARTIAL);
+}
+
+Option6ClientFqdn::DomainNameType
+Option6ClientFqdn::getDomainNameType() const {
+ return (impl_->domain_name_type_);
+}
+
+void
+Option6ClientFqdn::pack(isc::util::OutputBuffer& buf, bool) const {
+ // Header = option code and length.
+ packHeader(buf);
+ // Flags field.
+ buf.writeUint8(impl_->flags_);
+ // Domain name.
+ packDomainName(buf);
+}
+
+void
+Option6ClientFqdn::unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ setData(first, last);
+ impl_->parseWireData(first, last);
+ // Check that the flags in the received option are valid. Ignore MBZ bits
+ // because we don't want to discard the whole option because of MBZ bits
+ // being set.
+ impl_->checkFlags(impl_->flags_, false);
+}
+
+std::string
+Option6ClientFqdn::toText(int indent) const {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ stream << in << "type=" << type_ << "(CLIENT_FQDN), "
+ << "flags: ("
+ << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+ << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+ << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+ << "domain-name='" << getDomainName() << "' ("
+ << (getDomainNameType() == PARTIAL ? "partial" : "full")
+ << ")";
+
+ return (stream.str());
+}
+
+uint16_t
+Option6ClientFqdn::len() const {
+ uint16_t domain_name_length = 0;
+ if (impl_->domain_name_) {
+ // If domain name is partial, the NULL terminating character
+ // is not included and the option. Length has to be adjusted.
+ domain_name_length = impl_->domain_name_type_ == FULL ?
+ impl_->domain_name_->getLength() :
+ impl_->domain_name_->getLength() - 1;
+ }
+ return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option6_client_fqdn.h b/src/lib/dhcp/option6_client_fqdn.h
new file mode 100644
index 0000000..7abe704
--- /dev/null
+++ b/src/lib/dhcp/option6_client_fqdn.h
@@ -0,0 +1,271 @@
+// Copyright (C) 2013-2022 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 OPTION6_CLIENT_FQDN_H
+#define OPTION6_CLIENT_FQDN_H
+
+#include <dhcp/option.h>
+#include <dns/name.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid flags have been specified for
+/// DHCPv6 Client Fqdn %Option.
+class InvalidOption6FqdnFlags : public Exception {
+public:
+ InvalidOption6FqdnFlags(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption6FqdnDomainName : public Exception {
+public:
+ InvalidOption6FqdnDomainName(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option6ClientFqdn class.
+class Option6ClientFqdnImpl;
+
+/// @brief Represents DHCPv6 Client FQDN %Option (code 39).
+///
+/// This option has been defined in the RFC 4704 and it has a following
+/// structure:
+/// - option-code = 39 (2 octets)
+/// - option-len (2 octets)
+/// - flags (1 octet)
+/// - domain-name - variable length field comprising partial or fully qualified
+/// domain name.
+///
+/// The flags field has the following structure:
+/// @code
+/// 0 1 2 3 4 5 6 7
+/// +-+-+-+-+-+-+-+-+
+/// | MBZ |N|O|S|
+/// +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+/// Update,
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference set with the S bit.
+/// - S flag specifies whether server should (1) or should not (0) perform
+/// forward (FQDN-to-address) updates.
+///
+/// This class exposes a set of functions to modify flags and check their
+/// correctness.
+///
+/// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully
+/// qualified or partial. Partial domain names are encoded similar to the
+/// fully qualified domain names, except that they lack terminating zero
+/// at the end of their wire representation. It is also accepted to create an
+/// instance of this option which has empty domain-name. Clients use empty
+/// domain-names to indicate that server should generate complete fully
+/// qualified domain-name.
+///
+/// Since domain names are case insensitive (see RFC 4343), this class
+/// converts them to lower case format regardless if they are received over
+/// the wire or created from strings.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
+class Option6ClientFqdn : public Option {
+public:
+
+ ///
+ ///@name A set of constants setting respective bits in 'flags' field
+ //@{
+ static const uint8_t FLAG_S = 0x01; ///< S bit.
+ static const uint8_t FLAG_O = 0x02; ///< O bit.
+ static const uint8_t FLAG_N = 0x04; ///< N bit.
+ //@}
+
+ /// @brief Mask which zeroes MBZ flag bits.
+ static const uint8_t FLAG_MASK = 0x7;
+
+ /// @brief The length of the flag field within DHCPv6 Client Fqdn %Option.
+ static const uint16_t FLAG_FIELD_LEN = 1;
+
+ /// @brief Type of the domain-name: partial or full.
+ enum DomainNameType {
+ PARTIAL,
+ FULL
+ };
+
+ /// @brief Constructor, creates option instance using flags and domain name.
+ ///
+ /// This constructor is used to create instance of the option which will be
+ /// included in outgoing messages.
+ ///
+ /// @param flags a combination of flag bits to be stored in flags field.
+ /// @param domain_name a name to be stored in the domain-name field.
+ /// @param domain_name_type indicates if the domain name is partial
+ /// or full.
+ explicit Option6ClientFqdn(const uint8_t flags,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type = FULL);
+
+ /// @brief Constructor, creates option instance using flags.
+ ///
+ /// This constructor creates an instance of the option with empty
+ /// domain-name. This domain-name is marked partial.
+ ///
+ /// @param flags A combination of flag bits to be stored in flags field.
+ Option6ClientFqdn(const uint8_t flags);
+
+ /// @brief Constructor, creates an option instance from part of the buffer.
+ ///
+ /// This constructor is mainly used to parse options in the received
+ /// messages. Function parameters specify buffer bounds from which the
+ /// option should be created. The size of the buffer chunk, specified by
+ /// the constructor's parameters should be equal or larger than the size
+ /// of the option. Otherwise, constructor will throw an exception.
+ ///
+ /// @param first the lower bound of the buffer to create option from.
+ /// @param last the upper bound of the buffer to create option from.
+ explicit Option6ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor
+ Option6ClientFqdn(const Option6ClientFqdn& source);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Destructor
+ virtual ~Option6ClientFqdn();
+
+ /// @brief Assignment operator
+ Option6ClientFqdn& operator=(const Option6ClientFqdn& source);
+
+ /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
+ /// is set.
+ ///
+ /// This method checks the single bit of flags field. Therefore, a caller
+ /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+ /// an argument of the function. Attempt to use any other value (including
+ /// combinations of these constants) will result in exception.
+ ///
+ /// @param flag A value specifying the flags bit to be checked. It can be
+ /// one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
+ ///
+ /// @return true if the bit of the specified flag is set, false otherwise.
+ bool getFlag(const uint8_t flag) const;
+
+ /// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option
+ /// flag.
+ ///
+ /// This method sets the single bit of flags field. Therefore, a caller
+ /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+ /// an argument of the function. Attempt to use any other value (including
+ /// combinations of these constants) will result in exception.
+ ///
+ /// @param flag A value specifying the flags bit to be modified. It can
+ /// be one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
+ /// @param set a boolean value which indicates whether flag should be
+ /// set (true), or cleared (false).
+ void setFlag(const uint8_t flag, const bool set);
+
+ /// @brief Sets the flag field value to 0.
+ void resetFlags();
+
+ /// @brief Returns the domain-name in the text format.
+ ///
+ /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+ /// If domain-name is fully qualified, it has the dot at the end (e.g.
+ /// myhost.example.com.).
+ ///
+ /// @return domain-name in the text format.
+ std::string getDomainName() const;
+
+ /// @brief Writes domain-name in the wire format into a buffer.
+ ///
+ /// The data being written are appended at the end of the buffer.
+ ///
+ /// @param [out] buf buffer where domain-name will be written.
+ void packDomainName(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Set new domain-name.
+ ///
+ /// @param domain_name domain name field value in the text format.
+ /// @param domain_name_type type of the domain name: partial or fully
+ /// qualified.
+ void setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type);
+
+ /// @brief Set empty domain-name.
+ ///
+ /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+ /// with empty partial domain-name. It is exception safe.
+ void resetDomainName();
+
+ /// @brief Returns enumerator value which indicates whether domain-name is
+ /// partial or full.
+ ///
+ /// @return An enumerator value indicating whether domain-name is partial
+ /// or full.
+ DomainNameType getDomainNameType() const;
+
+ /// @brief Writes option in the wire format into a buffer.
+ ///
+ /// @param [out] buf output buffer where option data will be stored.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses option from the received buffer.
+ ///
+ /// Method creates an instance of the DHCPv6 Client FQDN %Option from the
+ /// wire format. Parameters specify the bounds of the buffer to read option
+ /// data from. The size of the buffer limited by the specified parameters
+ /// should be equal or larger than size of the option (including its
+ /// header). Otherwise exception will be thrown.
+ ///
+ /// @param first lower bound of the buffer to parse option from.
+ /// @param last upper bound of the buffer to parse option from.
+ virtual void unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// The string returned by the method comprises the bit value of each
+ /// option flag and the domain-name.
+ ///
+ /// @param indent number of spaces before printed text.
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns length of the complete option (data length +
+ /// DHCPv6 option header).
+ ///
+ /// @return length of the option.
+ virtual uint16_t len() const;
+
+private:
+
+ /// @brief A pointer to the implementation.
+ Option6ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option6ClientFqdn object.
+typedef boost::shared_ptr<Option6ClientFqdn> Option6ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION6_CLIENT_FQDN_H
diff --git a/src/lib/dhcp/option6_dnr.cc b/src/lib/dhcp/option6_dnr.cc
new file mode 100644
index 0000000..3d7bb0f
--- /dev/null
+++ b/src/lib/dhcp/option6_dnr.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 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 <dhcp/option6_dnr.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+Option6Dnr::Option6Dnr(OptionBufferConstIter begin, OptionBufferConstIter end)
+ : Option(V6, D6O_V6_DNR), DnrInstance(V6) {
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6Dnr::clone() const {
+ return (cloneInternal<Option6Dnr>());
+}
+
+void
+Option6Dnr::pack(util::OutputBuffer& buf, bool check) const {
+ packHeader(buf, check);
+
+ buf.writeUint16(service_priority_);
+ buf.writeUint16(adn_length_);
+ packAdn(buf);
+ if (adn_only_mode_) {
+ return;
+ }
+
+ buf.writeUint16(addr_length_);
+ packAddresses(buf);
+ packSvcParams(buf);
+}
+
+void
+Option6Dnr::packAddresses(util::OutputBuffer& buf) const {
+ for (const auto& address : ip_addresses_) {
+ if (!address.isV6()) {
+ isc_throw(isc::BadValue, getLogPrefix()
+ << address.toText() << " is not an IPv6 address");
+ }
+
+ buf.writeData(&address.toBytes()[0], V6ADDRESS_LEN);
+ }
+}
+
+void
+Option6Dnr::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ if (std::distance(begin, end) < getMinimalLength()) {
+ isc_throw(OutOfRange, getLogPrefix()
+ << "data truncated to size " << std::distance(begin, end));
+ }
+
+ setData(begin, end);
+
+ // First two octets of Option data is Service Priority - this is mandatory field.
+ unpackServicePriority(begin);
+
+ // Next come two octets of ADN Length plus the ADN data itself (variable length).
+ // This is Opaque Data Tuple so let's use this class to retrieve the ADN data.
+ unpackAdn(begin, end);
+
+ if (begin == end) {
+ // ADN only mode, other fields are not included.
+ return;
+ }
+
+ adn_only_mode_ = false;
+
+ unpackAddresses(begin, end);
+
+ // SvcParams (variable length) field is last.
+ unpackSvcParams(begin, end);
+}
+
+std::string
+Option6Dnr::toText(int indent) const {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ stream << in << "type=" << type_ << "(V6_DNR), "
+ << "len=" << (len() - getHeaderLen()) << ", " << getDnrInstanceAsText();
+ return (stream.str());
+}
+
+uint16_t
+Option6Dnr::len() const {
+ return (OPTION6_HDR_LEN + dnrInstanceLen());
+}
+
+void
+Option6Dnr::unpackAddresses(OptionBufferConstIter& begin, OptionBufferConstIter end) {
+ if (std::distance(begin, end) < getAddrLengthSize()) {
+ isc_throw(OutOfRange, getLogPrefix() << "after"
+ " ADN field, there should be at least "
+ "2 bytes long Addr Length field");
+ }
+
+ // Next come two octets of Addr Length.
+ addr_length_ = isc::util::readUint16(&(*begin), getAddrLengthSize());
+ begin += getAddrLengthSize();
+ // It MUST be a multiple of 16.
+ if ((addr_length_ % V6ADDRESS_LEN) != 0) {
+ isc_throw(OutOfRange, getLogPrefix()
+ << "Addr Len=" << addr_length_ << " is not divisible by 16");
+ }
+
+ // As per draft-ietf-add-dnr 3.1.8:
+ // If additional data is supplied (i.e. not ADN only mode),
+ // the option includes at least one valid IP address.
+ if (addr_length_ == 0) {
+ isc_throw(OutOfRange, getLogPrefix()
+ << "Addr Len=" << addr_length_
+ << " but it must contain at least one valid IP address");
+ }
+
+ // Check if IPv6 Address(es) field is not truncated.
+ if (std::distance(begin, end) < addr_length_) {
+ isc_throw(OutOfRange, getLogPrefix() << "Addr Len=" << addr_length_
+ << " but IPv6 address(es) are truncated to len="
+ << std::distance(begin, end));
+ }
+
+ // Let's unpack the ipv6-address(es).
+ auto addr_end = begin + addr_length_;
+ while (begin != addr_end) {
+ try {
+ ip_addresses_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin)));
+ } catch (const Exception& ex) {
+ isc_throw(BadValue, getLogPrefix() << "failed to parse IPv6 address"
+ << " - " << ex.what());
+ }
+
+ begin += V6ADDRESS_LEN;
+ }
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcp/option6_dnr.h b/src/lib/dhcp/option6_dnr.h
new file mode 100644
index 0000000..6cf38e5
--- /dev/null
+++ b/src/lib/dhcp/option6_dnr.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2023 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 OPTION6_DNR_H
+#define OPTION6_DNR_H
+
+#include <dhcp/option4_dnr.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents DHCPv6 Encrypted DNS %Option (code 144).
+///
+/// This option has been defined in the @c draft-ietf-add-dnr (to be replaced
+/// with published RFC) and it has a following structure:
+/// - option-code = 144 (2 octets)
+/// - option-len (2 octets)
+/// - Service Priority (2 octets)
+/// - ADN Length (2 octets)
+/// - Authentication Domain Name (variable length)
+/// - Addr Length (2 octets)
+/// - IPv6 Address(es) (variable length)
+/// - Service Parameters (variable length).
+class Option6Dnr : public Option, public DnrInstance {
+public:
+ /// @brief Constructor of the %Option from on-wire data.
+ ///
+ /// This constructor creates an instance of the option using a buffer with
+ /// on-wire data. It may throw an exception if the @c unpack method throws.
+ ///
+ /// @param begin Iterator pointing to the beginning of the buffer holding an
+ /// option.
+ /// @param end Iterator pointing to the end of the buffer holding an option.
+ ///
+ /// @throw OutOfRange Thrown in case of truncated data.
+ /// @throw BadValue Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ /// @throw InvalidOptionDnrDomainName Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ Option6Dnr(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Constructor of the %Option with all fields from params.
+ ///
+ /// Constructor of the %Option where all fields
+ /// i.e. Service priority, ADN, IP address(es) and Service params
+ /// are provided as ctor parameters.
+ ///
+ /// @param service_priority Service priority
+ /// @param adn ADN FQDN
+ /// @param ip_addresses Container of IP addresses
+ /// @param svc_params Service Parameters
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN
+ /// @throw InvalidOptionDnrSvcParams Thrown when @c checkSvcParams(from_wire_data) throws
+ /// @throw OutOfRange Thrown in case of no IP addresses found or when IP addresses length
+ /// is too big
+ Option6Dnr(const uint16_t service_priority,
+ const std::string& adn,
+ const Option6Dnr::AddressContainer& ip_addresses,
+ const std::string& svc_params)
+ : Option(V6, D6O_V6_DNR), DnrInstance(V6, service_priority, adn, ip_addresses, svc_params) {
+ }
+
+ /// @brief Constructor of the %Option in ADN only mode.
+ ///
+ /// Constructor of the %Option in ADN only mode
+ /// i.e. only Service priority and ADN FQDN
+ /// are provided as ctor parameters.
+ ///
+ /// @param service_priority Service priority
+ /// @param adn ADN FQDN
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown in case of any issue with parsing ADN
+ Option6Dnr(const uint16_t service_priority, const std::string& adn)
+ : Option(V6, D6O_V6_DNR), DnrInstance(V6, service_priority, adn) {}
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ ///
+ /// @return Pointer to the copy of the option.
+ OptionPtr clone() const override;
+
+ /// @brief Writes option in wire-format to a buffer.
+ ///
+ /// Writes option in wire-format to buffer, returns pointer to first unused
+ /// byte after stored option (that is useful for writing options one after
+ /// another).
+ ///
+ /// @param buf pointer to a buffer
+ /// @param check flag which indicates if checking the option length is
+ /// required (used only in V4)
+ ///
+ /// @throw InvalidOptionDnrDomainName Thrown when Option's mandatory field ADN is empty.
+ void pack(util::OutputBuffer& buf, bool check = false) const override;
+
+ /// @brief Parses received wire data buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw OutOfRange Thrown in case of truncated data.
+ /// @throw BadValue Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ /// @throw InvalidOptionDnrDomainName Thrown when @c DnrInstance::unpackAdn(begin,end) throws.
+ void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) override;
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printing text
+ ///
+ /// @return string with text representation.
+ std::string toText(int indent = 0) const override;
+
+ /// @brief Returns length of the complete option (data length + DHCPv4/DHCPv6
+ /// option header)
+ ///
+ /// @return length of the option
+ uint16_t len() const override;
+
+ /// @brief Writes the IP address(es) in the wire format into a buffer.
+ ///
+ /// The IP address(es) (@c ip_addresses_) data is appended at the end
+ /// of the buffer.
+ ///
+ /// @param [out] buf buffer where IP address(es) will be written.
+ ///
+ /// @throw BadValue Thrown when trying to pack address which is not an IPv6 address
+ void packAddresses(isc::util::OutputBuffer& buf) const override;
+
+ /// @brief Unpacks IP address(es) from wire data and stores it/them in @c ip_addresses_.
+ ///
+ /// It may throw in case of malformed data detected during parsing.
+ ///
+ /// @param begin beginning of the buffer from which the field will be read
+ /// @param end end of the buffer from which the field will be read
+ ///
+ /// @throw OutOfRange Thrown in case of malformed data detected during parsing e.g.
+ /// Addr Len not divisible by 16, Addr Len is 0, addresses data truncated etc.
+ void unpackAddresses(OptionBufferConstIter& begin, OptionBufferConstIter end) override;
+};
+
+/// A pointer to the @c Option6Dnr object.
+typedef boost::shared_ptr<Option6Dnr> Option6DnrPtr;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // OPTION6_DNR_H
diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc
new file mode 100644
index 0000000..2b41490
--- /dev/null
+++ b/src/lib/dhcp/option6_ia.cc
@@ -0,0 +1,120 @@
+// Copyright (C) 2011-2022 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 <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option_space.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <arpa/inet.h>
+#include <sstream>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option6IA::Option6IA(uint16_t type, uint32_t iaid)
+ :Option(Option::V6, type), iaid_(iaid), t1_(0), t2_(0) {
+
+ // IA_TA has different layout than IA_NA and IA_PD. We can't use this class
+ if (type == D6O_IA_TA) {
+ isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has "
+ "a different layout");
+ }
+
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+}
+
+Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ :Option(Option::V6, type) {
+
+ // IA_TA has different layout than IA_NA and IA_PD. We can't use this class
+ if (type == D6O_IA_TA) {
+ isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has "
+ "a different layout");
+ }
+
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6IA::clone() const {
+ return (cloneInternal<Option6IA>());
+}
+
+void Option6IA::pack(isc::util::OutputBuffer& buf, bool) const {
+ buf.writeUint16(type_);
+ buf.writeUint16(len() - OPTION6_HDR_LEN);
+ buf.writeUint32(iaid_);
+ buf.writeUint32(t1_);
+ buf.writeUint32(t2_);
+
+ packOptions(buf);
+}
+
+void Option6IA::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ // IA_NA and IA_PD have 12 bytes content (iaid, t1, t2 fields)
+ // followed by 0 or more sub-options.
+ if (distance(begin, end) < OPTION6_IA_LEN) {
+ isc_throw(OutOfRange, "Option " << type_ << " truncated");
+ }
+ iaid_ = readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(uint32_t);
+ t1_ = readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(uint32_t);
+
+ t2_ = readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(uint32_t);
+
+ unpackOptions(OptionBuffer(begin, end));
+}
+
+std::string Option6IA::toText(int indent) const {
+ stringstream output;
+
+ switch(getType()) {
+ case D6O_IA_NA:
+ output << headerToText(indent, "IA_NA");
+ break;
+ case D6O_IA_PD:
+ output << headerToText(indent, "IA_PD");
+ break;
+ default:
+ output << headerToText(indent);
+ }
+
+ output << ": iaid=" << iaid_ << ", t1=" << t1_ << ", t2=" << t2_
+ << suboptionsToText(indent + 2);
+
+ return (output.str());
+}
+
+uint16_t Option6IA::len() const {
+
+ uint16_t length = OPTION6_HDR_LEN /*header (4)*/ +
+ OPTION6_IA_LEN /* option content (12) */;
+
+ // length of all suboptions
+ for (OptionCollection::const_iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h
new file mode 100644
index 0000000..9ea7c3e
--- /dev/null
+++ b/src/lib/dhcp/option6_ia.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2011-2022 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 OPTION_IA_H
+#define OPTION_IA_H
+
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+class Option6IA;
+
+/// A pointer to the @c Option6IA object.
+typedef boost::shared_ptr<Option6IA> Option6IAPtr;
+
+class Option6IA: public Option {
+
+public:
+ /// Length of IA_NA and IA_PD content
+ const static size_t OPTION6_IA_LEN = 12;
+
+ /// @brief Ctor, used for constructed options, usually during transmission.
+ ///
+ /// @param type option type (usually 4 for IA_NA, 25 for IA_PD)
+ /// @param iaid identity association identifier (id of IA)
+ Option6IA(uint16_t type, uint32_t iaid);
+
+ /// @brief Ctor, used for received options.
+ ///
+ /// @param type option type (usually 4 for IA_NA, 25 for IA_PD)
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ Option6IA(uint16_t type, OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param buf buffer (option will be stored here)
+ /// @param check if set to false, allows options larger than 255 for v4
+ void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// Provides human readable text representation
+ ///
+ /// @param indent number of leading space characters
+ ///
+ /// @return string with text representation
+ virtual std::string toText(int indent = 0) const;
+
+ /// Sets T1 timer.
+ ///
+ /// @param t1 t1 value to be set
+ void setT1(uint32_t t1) { t1_ = t1; }
+
+ /// Sets T2 timer.
+ ///
+ /// @param t2 t2 value to be set
+ void setT2(uint32_t t2) { t2_ = t2; }
+
+ /// Sets Identity Association Identifier.
+ ///
+ /// @param iaid IAID value to be set
+ void setIAID(uint32_t iaid) { iaid_ = iaid; }
+
+ /// Returns IA identifier.
+ ///
+ /// @return IAID value.
+ ///
+ uint32_t getIAID() const { return iaid_; }
+
+ /// Returns T1 timer.
+ ///
+ /// @return T1 value.
+ uint32_t getT1() const { return t1_; }
+
+ /// Returns T2 timer.
+ ///
+ /// @return T2 value.
+ uint32_t getT2() const { return t2_; }
+
+ /// @brief returns complete length of option
+ ///
+ /// Returns length of this option, including option header and suboptions
+ ///
+ /// @return length of this option
+ virtual uint16_t len() const;
+
+protected:
+
+ /// keeps IA identifier
+ uint32_t iaid_;
+
+ /// keeps T1 timer value
+ uint32_t t1_;
+
+ /// keeps T2 timer value
+ uint32_t t2_;
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_IA_H
diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc
new file mode 100644
index 0000000..1737cd1
--- /dev/null
+++ b/src/lib/dhcp/option6_iaaddr.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2011-2022 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_space.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <sstream>
+
+#include <stdint.h>
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr,
+ uint32_t pref, uint32_t valid)
+ :Option(V6, type), addr_(addr), preferred_(pref),
+ valid_(valid) {
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+ if (!addr.isV6()) {
+ isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
+ }
+}
+
+Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end)
+ :Option(V6, type), addr_("::") {
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6IAAddr::clone() const {
+ return (cloneInternal<Option6IAAddr>());
+}
+
+void Option6IAAddr::pack(isc::util::OutputBuffer& buf, bool) const {
+
+ buf.writeUint16(type_);
+
+ // len() returns complete option length. len field contains
+ // length without 4-byte option header
+ buf.writeUint16(len() - getHeaderLen());
+
+ if (!addr_.isV6()) {
+ isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
+ }
+ buf.writeData(&addr_.toBytes()[0], isc::asiolink::V6ADDRESS_LEN);
+
+ buf.writeUint32(preferred_);
+ buf.writeUint32(valid_);
+
+ // parse suboption (there shouldn't be any for IAADDR)
+ packOptions(buf);
+}
+
+void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) {
+ if ( distance(begin, end) < OPTION6_IAADDR_LEN) {
+ isc_throw(OutOfRange, "Option " << type_ << " truncated");
+ }
+
+ // 16 bytes: IPv6 address
+ addr_ = IOAddress::fromBytes(AF_INET6, &(*begin));
+ begin += V6ADDRESS_LEN;
+
+ preferred_ = readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(uint32_t);
+
+ valid_ = readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(uint32_t);
+
+ unpackOptions(OptionBuffer(begin, end));
+}
+
+std::string Option6IAAddr::toText(int indent) const {
+ std::stringstream output;
+ output << headerToText(indent, "IAADDR") << ": "
+ << "address=" << addr_
+ << ", preferred-lft=" << preferred_
+ << ", valid-lft=" << valid_;
+
+ output << suboptionsToText(indent + 2);
+ return (output.str());
+}
+
+uint16_t Option6IAAddr::len() const {
+
+ uint16_t length = OPTION6_HDR_LEN + OPTION6_IAADDR_LEN;
+
+ // length of all suboptions
+ // TODO implement:
+ // protected: unsigned short Option::lenHelper(int header_size);
+ for (OptionCollection::const_iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/option6_iaaddr.h b/src/lib/dhcp/option6_iaaddr.h
new file mode 100644
index 0000000..8a091cf
--- /dev/null
+++ b/src/lib/dhcp/option6_iaaddr.h
@@ -0,0 +1,129 @@
+// Copyright (C) 2011-2022 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 OPTION6_IAADDR_H
+#define OPTION6_IAADDR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+class Option6IAAddr;
+
+/// A pointer to the @c isc::dhcp::Option6IAAddr object.
+typedef boost::shared_ptr<Option6IAAddr> Option6IAAddrPtr;
+
+class Option6IAAddr: public Option {
+
+public:
+ /// length of the fixed part of the IAADDR option
+ static const size_t OPTION6_IAADDR_LEN = 24;
+
+ /// @brief Constructor, used for options constructed (during transmission).
+ ///
+ /// @throw BadValue if specified addr is not IPv6
+ ///
+ /// @param type option type
+ /// @param addr reference to an address
+ /// @param preferred address preferred lifetime (in seconds)
+ /// @param valid address valid lifetime (in seconds)
+ Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr,
+ uint32_t preferred, uint32_t valid);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// @throw OutOfRange if specified option is truncated
+ ///
+ /// @param type option type
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Writes option in wire-format.
+ ///
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param buf pointer to a buffer
+ /// @param check if set to false, allows options larger than 255 for v4
+ void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printing text
+ ///
+ /// @return string with text representation.
+ virtual std::string
+ toText(int indent = 0) const;
+
+
+ /// sets address in this option.
+ ///
+ /// @param addr address to be sent in this option
+ void setAddress(const isc::asiolink::IOAddress& addr) { addr_ = addr; }
+
+ /// Sets preferred lifetime (in seconds)
+ ///
+ /// @param pref address preferred lifetime (in seconds)
+ ///
+ void setPreferred(unsigned int pref) { preferred_=pref; }
+
+ /// Sets valid lifetime (in seconds).
+ ///
+ /// @param valid address valid lifetime (in seconds)
+ ///
+ void setValid(unsigned int valid) { valid_=valid; }
+
+ /// Returns address contained within this option.
+ ///
+ /// @return address
+ isc::asiolink::IOAddress
+ getAddress() const { return addr_; }
+
+ /// Returns preferred lifetime of an address.
+ ///
+ /// @return preferred lifetime (in seconds)
+ unsigned int
+ getPreferred() const { return preferred_; }
+
+ /// Returns valid lifetime of an address.
+ ///
+ /// @return valid lifetime (in seconds)
+ unsigned int
+ getValid() const { return valid_; }
+
+ /// returns data length (data length + DHCPv4/DHCPv6 option header)
+ virtual uint16_t len() const;
+
+protected:
+ /// contains an IPv6 address
+ isc::asiolink::IOAddress addr_;
+
+ /// contains preferred-lifetime timer (in seconds)
+ unsigned int preferred_;
+
+ /// contains valid-lifetime timer (in seconds)
+ unsigned int valid_;
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_IA_H
diff --git a/src/lib/dhcp/option6_iaprefix.cc b/src/lib/dhcp/option6_iaprefix.cc
new file mode 100644
index 0000000..fcca8b0
--- /dev/null
+++ b/src/lib/dhcp/option6_iaprefix.cc
@@ -0,0 +1,148 @@
+// Copyright (C) 2013-2022 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option_space.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <sstream>
+
+#include <stdint.h>
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& prefix,
+ uint8_t prefix_len, uint32_t pref, uint32_t valid)
+ :Option6IAAddr(type, prefix, pref, valid), prefix_len_(prefix_len) {
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+ // Option6IAAddr will check if prefix is IPv6 and will throw if it is not
+ if (prefix_len > 128) {
+ isc_throw(BadValue, static_cast<unsigned>(prefix_len)
+ << " is not a valid prefix length. "
+ << "Allowed range is 0..128");
+ }
+}
+
+Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end)
+ :Option6IAAddr(type, begin, end) {
+ setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6IAPrefix::clone() const {
+ return (cloneInternal<Option6IAPrefix>());
+}
+
+void Option6IAPrefix::pack(isc::util::OutputBuffer& buf, bool) const {
+ if (!addr_.isV6()) {
+ isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");
+ }
+
+ buf.writeUint16(type_);
+
+ // len() returns complete option length. len field contains
+ // length without 4-byte option header
+ buf.writeUint16(len() - getHeaderLen());
+
+ buf.writeUint32(preferred_);
+ buf.writeUint32(valid_);
+ buf.writeUint8(prefix_len_);
+
+ buf.writeData(&addr_.toBytes()[0], isc::asiolink::V6ADDRESS_LEN);
+
+ // store encapsulated options (the only defined so far is PD_EXCLUDE)
+ packOptions(buf);
+}
+
+void Option6IAPrefix::unpack(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) {
+ if ( distance(begin, end) < OPTION6_IAPREFIX_LEN) {
+ isc_throw(OutOfRange, "Option " << type_ << " truncated");
+ }
+
+ preferred_ = readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(uint32_t);
+
+ valid_ = readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(uint32_t);
+
+ prefix_len_ = *begin;
+ begin += sizeof(uint8_t);
+
+ // 16 bytes: IPv6 address
+ OptionBuffer address_with_mask;
+ mask(begin, begin + V6ADDRESS_LEN, prefix_len_, address_with_mask);
+ addr_ = IOAddress::fromBytes(AF_INET6, &(*address_with_mask.begin()));
+ begin += V6ADDRESS_LEN;
+
+ // unpack encapsulated options (the only defined so far is PD_EXCLUDE)
+ unpackOptions(OptionBuffer(begin, end));
+}
+
+std::string Option6IAPrefix::toText(int indent) const {
+ std::stringstream output;
+ output << headerToText(indent, "IAPREFIX") << ": "
+ << "prefix=" << addr_ << "/" << static_cast<int>(prefix_len_)
+ << ", preferred-lft=" << preferred_
+ << ", valid-lft=" << valid_;
+
+ output << suboptionsToText(indent + 2);
+ return (output.str());
+}
+
+uint16_t Option6IAPrefix::len() const {
+
+ uint16_t length = OPTION6_HDR_LEN + OPTION6_IAPREFIX_LEN;
+
+ // length of all suboptions
+ for (OptionCollection::const_iterator it = options_.begin();
+ it != options_.end(); ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+}
+
+void
+Option6IAPrefix::mask(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end,
+ const uint8_t len,
+ OptionBuffer& output_address) const {
+ output_address.resize(16, 0);
+ if (len >= 128) {
+ std::copy(begin, end, output_address.begin());
+ } else if (len > 0) {
+ // All the bits that represent whole octets of the prefix are copied with
+ // no change.
+ std::copy(begin, begin + static_cast<uint8_t>(len/8), output_address.begin());
+ // The remaining significant bits of the last octet have to be left unchanged,
+ // but the remaining bits of this octet must be set to zero. The number of
+ // significant bits is calculated as a reminder from the division of the
+ // prefix length by 8 (by size of the octet). The number of bits to be set
+ // to zero is therefore calculated as: 8 - (len % 8).
+ // Next, the mask is created by shifting the 0xFF by the number of bits
+ // to be set to 0. By performing logical AND of this mask with the original
+ // value of the last octet we get the final value for the new octet.
+ output_address[len/8] = (*(begin + len/8) & (0xFF << (8 - (len % 8))));
+ }
+}
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/option6_iaprefix.h b/src/lib/dhcp/option6_iaprefix.h
new file mode 100644
index 0000000..3d9a7f2
--- /dev/null
+++ b/src/lib/dhcp/option6_iaprefix.h
@@ -0,0 +1,148 @@
+// Copyright (C) 2013-2022 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 OPTION6_IAPREFIX_H
+#define OPTION6_IAPREFIX_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option.h>
+
+namespace isc {
+namespace dhcp {
+
+
+/// @brief Class that represents IAPREFIX option in DHCPv6
+///
+/// It is based on a similar class that handles addresses. There are major
+/// differences, though. The fields are in different order. There is also
+/// additional prefix length field.
+///
+/// It should be noted that to get a full prefix (2 values: base prefix, and
+/// a prefix length) 2 methods are used: getAddress() and getLength(). Although
+/// using getAddress() to obtain base prefix is somewhat counter-intuitive at
+/// first, it becomes obvious when one realizes that an address is a special
+/// case of a prefix with /128. It makes everyone's life much easier, because
+/// the base prefix doubles as a regular address in many cases, e.g. when
+/// searching for a lease.
+///
+/// When searching for a prefix in the database or simply comparing two prefixes
+/// for equality, it is important that only the significant parts of the
+/// prefixes are compared. It is possible that the client or a server sends a
+/// prefix which has non-significant bits (beyond prefix length) set. The
+/// server or client receiving such a prefix should be liberal and not discard
+/// this prefix. It should rather ignore the non-significant bits. Therefore
+/// the unpack() function, which parses the prefix from the wire, always sets
+/// the non-significant bits to 0 so as two prefixes received on the wire can
+/// be compared without additional processing.
+///
+/// @todo Currently, the constructor which creates the option from the textual
+/// format doesn't set non-significant bits to 0. This is because it is assumed
+/// that the prefixes from the string are created locally (not received over the
+/// wire) and should be validated before the option is created. If we wanted
+/// to set non-significant bits to 0 when the prefix is created from the textual
+/// format it would have some performance implications, because the option would
+/// need to be turned into wire format, appropriate bits set to 0 and then
+/// option would need to be created again from the wire format. We may consider
+/// doing it if we find a use case where it is required.
+class Option6IAPrefix : public Option6IAAddr {
+
+public:
+ /// length of the fixed part of the IAPREFIX option
+ static const size_t OPTION6_IAPREFIX_LEN = 25;
+
+ /// @brief Constructor, used for options constructed (during transmission).
+ ///
+ /// @param type option type
+ /// @param addr reference to an address
+ /// @param prefix_length length (1-128)
+ /// @param preferred address preferred lifetime (in seconds)
+ /// @param valid address valid lifetime (in seconds)
+ Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& addr,
+ uint8_t prefix_length, uint32_t preferred, uint32_t valid);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// @throw OutOfRange if buffer is too short
+ ///
+ /// @param type option type
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Writes option in wire-format.
+ ///
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @throw BadValue if the address is not IPv6
+ ///
+ /// @param buf pointer to a buffer
+ /// @param check if set to false, allows options larger than 255 for v4
+ void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// This function calls the @c Option6IAPrefix::mask function to set the
+ /// non-significant bits of the prefix (bits beyond the length of the
+ /// prefix) to zero. See the @c Option6IAPrefix class documentation for
+ /// details why it is done.
+ ///
+ /// @throw OutOfRange when buffer is shorter than 25 bytes
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printing text
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0) const;
+
+ /// sets address in this option.
+ ///
+ /// @param prefix prefix to be sent in this option
+ /// @param length prefix length
+ void setPrefix(const isc::asiolink::IOAddress& prefix,
+ uint8_t length) { addr_ = prefix; prefix_len_ = length; }
+
+ uint8_t getLength() const { return prefix_len_; }
+
+ /// returns data length (data length + DHCPv4/DHCPv6 option header)
+ virtual uint16_t len() const;
+
+private:
+
+ /// @brief Apply mask of the specific length to the IPv6 address.
+ ///
+ /// @param begin Iterator pointing to the buffer holding IPv6 address.
+ /// @param end Iterator pointing to the end of the buffer holding IPv6
+ /// address.
+ /// @param len Length of the mask to be applied.
+ /// @param [out] output_address Reference to the buffer where the address
+ /// with a mask applied is output.
+ void mask(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end,
+ const uint8_t len,
+ OptionBuffer& output_address) const;
+
+ uint8_t prefix_len_;
+};
+
+/// @brief Pointer to the @c Option6IAPrefix object.
+typedef boost::shared_ptr<Option6IAPrefix> Option6IAPrefixPtr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_IAPREFIX_H
diff --git a/src/lib/dhcp/option6_pdexclude.cc b/src/lib/dhcp/option6_pdexclude.cc
new file mode 100644
index 0000000..b71a820
--- /dev/null
+++ b/src/lib/dhcp/option6_pdexclude.cc
@@ -0,0 +1,237 @@
+// Copyright (C) 2016-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option6_pdexclude.h>
+#include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+#include <util/io_utilities.h>
+
+#include <boost/dynamic_bitset.hpp>
+#include <iostream>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length,
+ const isc::asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_length)
+ : Option(V6, D6O_PD_EXCLUDE),
+ excluded_prefix_length_(excluded_prefix_length),
+ subnet_id_() {
+
+ // Expecting v6 prefixes of sane length.
+ if (!delegated_prefix.isV6() || !excluded_prefix.isV6() ||
+ (delegated_prefix_length > 128) || (excluded_prefix_length_ > 128)) {
+ isc_throw(BadValue, "invalid delegated or excluded prefix values specified: "
+ << delegated_prefix << "/"
+ << static_cast<int>(delegated_prefix_length) << ", "
+ << excluded_prefix << "/"
+ << static_cast<int>(excluded_prefix_length_));
+ }
+
+ // Excluded prefix must be longer than the delegated prefix length.
+ if (excluded_prefix_length_ <= delegated_prefix_length) {
+ isc_throw(BadValue, "length of the excluded prefix "
+ << excluded_prefix << "/"
+ << static_cast<int>(excluded_prefix_length_)
+ << " must be greater than the length of the"
+ " delegated prefix " << delegated_prefix << "/"
+ << static_cast<int>(delegated_prefix_length));
+ }
+
+ // Both prefixes must share common part with a length equal to the
+ // delegated prefix length.
+ std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
+ boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(),
+ delegated_prefix_bytes.rend());
+
+ std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix.toBytes();
+ boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(),
+ excluded_prefix_bytes.rend());
+
+
+ // See RFC6603, section 4.2: assert(p1>>s == p2>>s)
+ const uint8_t delta = 128 - delegated_prefix_length;
+
+ if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) {
+ isc_throw(BadValue, "excluded prefix "
+ << excluded_prefix << "/"
+ << static_cast<int>(excluded_prefix_length_)
+ << " must have the same common prefix part of "
+ << static_cast<int>(delegated_prefix_length)
+ << " as the delegated prefix "
+ << delegated_prefix << "/"
+ << static_cast<int>(delegated_prefix_length));
+ }
+
+
+ // Shifting prefix by delegated prefix length leaves us with only a
+ // subnet id part of the excluded prefix.
+ excluded_prefix_bits <<= delegated_prefix_length;
+
+ // Calculate subnet id length.
+ const uint8_t subnet_id_length = getSubnetIDLength(delegated_prefix_length,
+ excluded_prefix_length);
+ for (uint8_t i = 0; i < subnet_id_length; ++i) {
+ // Retrieve bit representation of the current byte.
+ const boost::dynamic_bitset<uint8_t> first_byte = excluded_prefix_bits >> 120;
+
+ // Convert it to a numeric value.
+ uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());
+
+ // Zero padded excluded_prefix_bits follow when excluded_prefix_length_ is
+ // not divisible by 8.
+ if (i == subnet_id_length - 1) {
+ uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length;
+ if (length_delta % 8 != 0) {
+ uint8_t mask = 0xFF;
+ mask <<= (8 - (length_delta % 8));
+ val &= mask;
+ }
+ }
+ // Store calculated value in a buffer.
+ subnet_id_.push_back(val);
+
+ // Go to the next byte.
+ excluded_prefix_bits <<= 8;
+ }
+}
+
+Option6PDExclude::Option6PDExclude(OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(V6, D6O_PD_EXCLUDE),
+ excluded_prefix_length_(0),
+ subnet_id_() {
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6PDExclude::clone() const {
+ return (cloneInternal<Option6PDExclude>());
+}
+
+void
+Option6PDExclude::pack(isc::util::OutputBuffer& buf, bool) const {
+ // Make sure that the subnet identifier is valid. It should never
+ // be empty.
+ if ((excluded_prefix_length_ == 0) || subnet_id_.empty()) {
+ isc_throw(BadValue, "subnet identifier of a Prefix Exclude option"
+ " must not be empty");
+ }
+
+ // Header = option code and length.
+ packHeader(buf);
+
+ // Excluded prefix length is always 1 byte long field.
+ buf.writeUint8(excluded_prefix_length_);
+
+ // Write the subnet identifier.
+ buf.writeData(static_cast<const void*>(&subnet_id_[0]), subnet_id_.size());
+}
+
+void
+Option6PDExclude::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+
+ // At this point we don't know the excluded prefix length, but the
+ // minimum requirement is that reminder of this option includes the
+ // excluded prefix length and at least 1 byte of the IPv6 subnet id.
+ if (std::distance(begin, end) < 2) {
+ isc_throw(BadValue, "truncated Prefix Exclude option");
+ }
+
+ // We can safely read the excluded prefix length and move forward.
+ uint8_t excluded_prefix_length = *begin++;
+ if (excluded_prefix_length == 0) {
+ isc_throw(BadValue, "excluded prefix length must not be 0");
+ }
+
+ std::vector<uint8_t> subnet_id_bytes(begin, end);
+
+ // Subnet id parsed, proceed to the end of the option.
+ begin = end;
+
+ uint8_t last_bits_num = excluded_prefix_length % 8;
+ if (last_bits_num > 0) {
+ *subnet_id_bytes.rbegin() = (*subnet_id_bytes.rbegin() >> (8 - last_bits_num)
+ << (8 - (last_bits_num)));
+ }
+
+ excluded_prefix_length_ = excluded_prefix_length;
+ subnet_id_.swap(subnet_id_bytes);
+}
+
+uint16_t
+Option6PDExclude::len() const {
+ return (getHeaderLen() + sizeof(excluded_prefix_length_) + subnet_id_.size());
+}
+
+std::string
+Option6PDExclude::toText(int indent) const {
+ std::ostringstream s;
+ s << headerToText(indent) << ": ";
+ s << "excluded-prefix-len=" << static_cast<unsigned>(excluded_prefix_length_)
+ << ", subnet-id=0x" << util::encode::encodeHex(subnet_id_);
+ return (s.str());
+}
+
+asiolink::IOAddress
+Option6PDExclude::getExcludedPrefix(const IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length) const {
+ // Get binary representation of the delegated prefix.
+ std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix.toBytes();
+ // We need to calculate how many bytes include the useful data and assign
+ // zeros to remaining bytes (beyond the prefix length).
+ const uint8_t bytes_length = (delegated_prefix_length / 8) +
+ static_cast<uint8_t>(delegated_prefix_length % 8 != 0);
+ std::fill(delegated_prefix_bytes.begin() + bytes_length,
+ delegated_prefix_bytes.end(), 0);
+
+ // Convert the delegated prefix to bit format.
+ boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(),
+ delegated_prefix_bytes.rend());
+
+ boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_.rbegin(),
+ subnet_id_.rend());
+
+ // Concatenate the delegated prefix with subnet id. The resulting prefix
+ // is an excluded prefix in bit format.
+ for (int i = subnet_id_bits.size() - 1; i >= 0; --i) {
+ bits.set(128 - delegated_prefix_length - subnet_id_bits.size() + i,
+ subnet_id_bits.test(i));
+ }
+
+ // Convert the prefix to binary format.
+ std::vector<uint8_t> bytes(V6ADDRESS_LEN);
+ boost::to_block_range(bits, bytes.rbegin());
+
+ // And create a prefix object from bytes.
+ return (IOAddress::fromBytes(AF_INET6, &bytes[0]));
+}
+
+uint8_t
+Option6PDExclude::getSubnetIDLength(const uint8_t delegated_prefix_length,
+ const uint8_t excluded_prefix_length) const {
+ uint8_t subnet_id_length_bits = excluded_prefix_length -
+ delegated_prefix_length - 1;
+ uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1;
+ return (subnet_id_length);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/option6_pdexclude.h b/src/lib/dhcp/option6_pdexclude.h
new file mode 100644
index 0000000..bf409d8
--- /dev/null
+++ b/src/lib/dhcp/option6_pdexclude.h
@@ -0,0 +1,130 @@
+// Copyright (C) 2016-2022 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 OPTION6_PDEXCLUDE_H
+#define OPTION6_PDEXCLUDE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief DHCPv6 option class representing Prefix Exclude Option (RFC 6603).
+///
+/// This class represents DHCPv6 Prefix Exclude option (67). This option is
+/// carried in the IA Prefix option and it conveys a single prefix which is
+/// used by the delegating router to communicate with a requesting router on
+/// the requesting router's uplink. This prefix is not used on the
+/// requesting router's downlinks (is excluded from other delegated prefixes).
+class Option6PDExclude: public Option {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param delegated_prefix Delegated prefix.
+ /// @param delegated_prefix_length Delegated prefix length.
+ /// @param excluded_prefix Excluded prefix.
+ /// @param excluded_prefix_length Excluded prefix length.
+ ///
+ /// @throw BadValue if prefixes are invalid, if excluded prefix length
+ /// is not greater than delegated prefix length or if common parts of
+ /// prefixes does not match.
+ Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length,
+ const isc::asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_length);
+
+ /// @brief Constructor, creates option instance from part of the buffer.
+ ///
+ /// This constructor is mostly used to parse Prefix Exclude options in the
+ /// received messages.
+ ///
+ /// @param begin Lower bound of the buffer to create option from.
+ /// @param end Upper bound of the buffer to create option from.
+ Option6PDExclude(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Writes option in wire-format to a buffer.
+ ///
+ /// Writes option in wire-format to buffer, returns pointer to first unused
+ /// byte after stored option (that is useful for writing options one after
+ /// another).
+ ///
+ /// The format of the option includes excluded prefix length specified as
+ /// a number of bits. It also includes IPv6 subnet ID field which is
+ /// computed from the delegated and excluded prefixes, according to the
+ /// section 4.2 of RFC 6603.
+ ///
+ /// @param [out] buf Pointer to a buffer.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns length of the complete option (data length + DHCPv6
+ /// option header)
+ ///
+ /// @return length of the option
+ virtual uint16_t len() const;
+
+ /// @brief Returns Prefix Exclude option in textual format.
+ ///
+ /// @param indent Number of spaces to be inserted before the text.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns excluded prefix.
+ ///
+ /// Assembles excluded prefix from a delegated prefix and IPv6 subnet id
+ /// specified as in RFC6603, section 4.2.
+ ///
+ /// @param delegated_prefix Delegated prefix for which excluded prefix will
+ /// be returned.
+ /// @param delegated_prefix_length Delegated prefix length.
+ asiolink::IOAddress
+ getExcludedPrefix(const asiolink::IOAddress& delegated_prefix,
+ const uint8_t delegated_prefix_length) const;
+
+ /// @brief Returns excluded prefix length.
+ uint8_t getExcludedPrefixLength() const {
+ return (excluded_prefix_length_);
+ }
+
+ /// @brief Returns an excluded prefix in a binary format.
+ const std::vector<uint8_t>& getExcludedPrefixSubnetID() const {
+ return (subnet_id_);
+ }
+
+private:
+
+ /// @brief Returns IPv6 subnet ID length in octets.
+ ///
+ /// The IPv6 subnet ID length is between 1 and 16 octets.
+ uint8_t getSubnetIDLength(const uint8_t delegated_prefix_length,
+ const uint8_t excluded_prefix_length) const;
+
+ /// @brief Holds excluded prefix length.
+ uint8_t excluded_prefix_length_;
+
+ /// @brief Subnet identifier as described in RFC6603, section 4.2.
+ std::vector<uint8_t> subnet_id_;
+};
+
+/// @brief Pointer to the @ref Option6PDExclude object.
+typedef boost::shared_ptr<Option6PDExclude> Option6PDExcludePtr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION6_PDEXCLUDE_H
diff --git a/src/lib/dhcp/option6_status_code.cc b/src/lib/dhcp/option6_status_code.cc
new file mode 100644
index 0000000..8e70919
--- /dev/null
+++ b/src/lib/dhcp/option6_status_code.cc
@@ -0,0 +1,218 @@
+// Copyright (C) 2015-2022 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/option_data_types.h>
+#include <util/io_utilities.h>
+#include <iterator>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Minimum length of the option (when status message is empty).
+const size_t OPTION6_STATUS_CODE_MIN_LEN = sizeof(uint16_t);
+const size_t OPTION4_SLP_SERVICE_SCOPEMIN_LEN = sizeof(uint8_t);
+
+}; // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+Option6StatusCode::Option6StatusCode(const uint16_t status_code,
+ const std::string& status_message)
+ : Option(Option::V6, D6O_STATUS_CODE),
+ status_code_(status_code), status_message_(status_message) {
+}
+
+Option6StatusCode::Option6StatusCode(OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(Option::V6, D6O_STATUS_CODE),
+ status_code_(STATUS_Success), status_message_() {
+
+ // Parse data
+ unpack(begin, end);
+}
+
+OptionPtr
+Option6StatusCode::clone() const {
+ return (cloneInternal<Option6StatusCode>());
+}
+
+void
+Option6StatusCode::pack(isc::util::OutputBuffer& buf, bool) const {
+ // Pack option header.
+ packHeader(buf);
+ // Write numeric status code.
+ buf.writeUint16(getStatusCode());
+ // If there is any status message, write it.
+ if (!status_message_.empty()) {
+ buf.writeData(&status_message_[0], status_message_.size());
+ }
+
+ // Status code has no options, so leave here.
+}
+
+void
+Option6StatusCode::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ // Make sure that the option is not truncated.
+ if (std::distance(begin, end) < OPTION6_STATUS_CODE_MIN_LEN) {
+ isc_throw(OutOfRange, "Status Code option ("
+ << D6O_STATUS_CODE << ") truncated");
+ }
+
+ status_code_ = util::readUint16(&(*begin), std::distance(begin, end));
+ begin += sizeof(uint16_t);
+
+ status_message_.assign(begin, end);
+}
+
+uint16_t
+Option6StatusCode::len() const {
+ return (getHeaderLen() + sizeof(uint16_t) + status_message_.size());
+}
+
+std::string
+Option6StatusCode::toText(int indent) const {
+ std::ostringstream output;
+ output << headerToText(indent) << ": " << dataToText();
+
+ return (output.str());
+}
+
+std::string
+Option6StatusCode::dataToText() const {
+ std::ostringstream output;
+ // Add status code name and numeric status code.
+ output << getStatusCodeName() << "(" << getStatusCode() << ") ";
+
+ // Include status message in quotes if status code is
+ // non-empty.
+ if (!status_message_.empty()) {
+ output << "\"" << status_message_ << "\"";
+
+ } else {
+ output << "(no status message)";
+ }
+
+ return (output.str());
+}
+
+std::string
+Option6StatusCode::getStatusCodeName() const {
+ switch (getStatusCode()) {
+ case STATUS_Success:
+ return ("Success");
+ case STATUS_UnspecFail:
+ return ("UnspecFail");
+ case STATUS_NoAddrsAvail:
+ return ("NoAddrsAvail");
+ case STATUS_NoBinding:
+ return ("NoBinding");
+ case STATUS_NotOnLink:
+ return ("NotOnLink");
+ case STATUS_UseMulticast:
+ return ("UseMulticast");
+ case STATUS_NoPrefixAvail:
+ return ("NoPrefixAvail");
+ case STATUS_UnknownQueryType:
+ return ("UnknownQueryType");
+ case STATUS_MalformedQuery:
+ return ("MalformedQuery");
+ case STATUS_NotConfigured:
+ return ("NotConfigured");
+ case STATUS_NotAllowed:
+ return ("NotAllowed");
+ default:
+ ;
+ }
+ return ("(unknown status code)");
+}
+
+Option4SlpServiceScope::Option4SlpServiceScope(const bool mandatory_flag,
+ const std::string& scope_list)
+ : Option(Option::V4, DHO_SERVICE_SCOPE),
+ mandatory_flag_(mandatory_flag), scope_list_(scope_list) {
+}
+
+Option4SlpServiceScope::Option4SlpServiceScope(OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(Option::V4, DHO_SERVICE_SCOPE),
+ mandatory_flag_(false), scope_list_() {
+
+ // Parse data
+ unpack(begin, end);
+}
+
+OptionPtr
+Option4SlpServiceScope::clone() const {
+ return (cloneInternal<Option4SlpServiceScope>());
+}
+
+void
+Option4SlpServiceScope::pack(isc::util::OutputBuffer& buf, bool check) const {
+ // Pack option header.
+ packHeader(buf, check);
+ // Write mandatory flag.
+ buf.writeUint8(static_cast<uint8_t>(getMandatoryFlag() ? 1 : 0));
+ // If there is any scope list, write it.
+ if (!scope_list_.empty()) {
+ buf.writeData(&scope_list_[0], scope_list_.size());
+ }
+
+ // SLP service scope has no options, so leave here.
+}
+
+void
+Option4SlpServiceScope::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ // Make sure that the option is not truncated.
+ if (std::distance(begin, end) < OPTION4_SLP_SERVICE_SCOPEMIN_LEN) {
+ isc_throw(OutOfRange, "SLP Service Scope option ("
+ << DHO_SERVICE_SCOPE << ") truncated");
+ }
+
+ if (*begin == 1) {
+ mandatory_flag_ = true;
+ } else if (*begin == 0) {
+ mandatory_flag_ = false;
+ } else {
+ isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+ << " value. Invalid value " << static_cast<int>(*begin));
+ }
+ begin += sizeof(uint8_t);
+
+ scope_list_.assign(begin, end);
+}
+
+uint16_t
+Option4SlpServiceScope::len() const {
+ return (getHeaderLen() + sizeof(uint8_t) + scope_list_.size());
+}
+
+std::string
+Option4SlpServiceScope::toText(int indent) const {
+ std::ostringstream output;
+ output << headerToText(indent) << ": " << dataToText();
+
+ return (output.str());
+}
+
+std::string
+Option4SlpServiceScope::dataToText() const {
+ std::ostringstream output;
+ output << "mandatory:" << getMandatoryFlag();
+ output << ", scope-list:\"" << scope_list_ << "\"";
+ return (output.str());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/option6_status_code.h b/src/lib/dhcp/option6_status_code.h
new file mode 100644
index 0000000..f133c5d
--- /dev/null
+++ b/src/lib/dhcp/option6_status_code.h
@@ -0,0 +1,207 @@
+// Copyright (C) 2015-2022 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 OPTION6_STATUS_CODE_H
+#define OPTION6_STATUS_CODE_H
+
+#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+class Option6StatusCode;
+
+/// @brief Pointer to the @c isc::dhcp::Option6StatusCode.
+typedef boost::shared_ptr<Option6StatusCode> Option6StatusCodePtr;
+
+/// @brief This class represents Status Code option (13) from RFC 8415.
+class Option6StatusCode: public Option {
+public:
+ /// @brief Constructor, used for options constructed (during transmission).
+ ///
+ /// @param status_code Numeric status code, e.g. STATUS_NoAddrsAvail.
+ /// @param status_message Textual message for the statuscode.
+ Option6StatusCode(const uint16_t status_code, const std::string& status_message);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// @throw OutOfRange if specified option is truncated
+ ///
+ /// @param begin Iterator to first byte of option data
+ /// @param end Iterator to end of option data (first byte after option end).
+ Option6StatusCode(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Writes option in wire-format.
+ ///
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param [out] buf Pointer to the output buffer.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// @throw OutOfRange if specified option is truncated
+ ///
+ /// @param begin Iterator to first byte of option data
+ /// @param end Iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns total length of the option.
+ ///
+ /// The returned length is a sum of the option header and data fields.
+ virtual uint16_t len() const;
+
+ /// @brief Returns textual representation of the option.
+ ///
+ /// @param indent Number of spaces before printing text.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns textual representation of the option data.
+ ///
+ /// This method returns only the status code and the status
+ /// message. It excludes the option header.
+ std::string dataToText() const;
+
+ /// @brief Returns numeric status code.
+ uint16_t getStatusCode() const {
+ return (status_code_);
+ }
+
+ /// @brief Returns the name of the status code.
+ std::string getStatusCodeName() const;
+
+ /// @brief Sets new numeric status code.
+ ///
+ /// @param status_code New numeric status code.
+ void setStatusCode(const uint16_t status_code) {
+ status_code_ = status_code;
+ }
+
+ /// @brief Returns status message.
+ const std::string& getStatusMessage() const {
+ return (status_message_);
+ }
+
+ /// @brief Sets new status message.
+ ///
+ /// @param status_message New status message (empty string is allowed).
+ void setStatusMessage(const std::string& status_message) {
+ status_message_ = status_message;
+ }
+
+private:
+ /// @brief Numeric status code.
+ uint16_t status_code_;
+
+ /// @brief Textual message.
+ std::string status_message_;
+};
+
+/// The SLP Service Scope option has a similar layout...
+
+class Option4SlpServiceScope;
+
+/// @brief Pointer to the @c isc::dhcp::Option4SlpServiceScope.
+typedef boost::shared_ptr<Option4SlpServiceScope> Option4SlpServiceScopePtr;
+
+/// @brief This class represents SLP Service Scope option (79) from RFC2610.
+class Option4SlpServiceScope: public Option {
+public:
+ /// @brief Constructor, used for options constructed (during transmission).
+ ///
+ /// @param mandatory_flag Mandatory flag.
+ /// @param scope_list Textual scope list, may be empty
+ Option4SlpServiceScope(const bool mandatory_flag, const std::string& scope_list);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// @throw OutOfRange if specified option is truncated
+ ///
+ /// @param begin Iterator to first byte of option data
+ /// @param end Iterator to end of option data (first byte after option end).
+ Option4SlpServiceScope(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Writes option in wire-format.
+ ///
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param [out] buf Pointer to the output buffer.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// @throw OutOfRange if specified option is truncated
+ /// @throw BadDataTypeCast if first byte is not 0 or 1
+ ///
+ /// @param begin Iterator to first byte of option data
+ /// @param end Iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns total length of the option.
+ ///
+ /// The returned length is a sum of the option header and data fields.
+ virtual uint16_t len() const;
+
+ /// @brief Returns textual representation of the option.
+ ///
+ /// @param indent Number of spaces before printing text.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns textual representation of the option data.
+ ///
+ /// This method returns only the status code and the status
+ /// message. It excludes the option header.
+ std::string dataToText() const;
+
+ /// @brief Returns mandatory flag
+ bool getMandatoryFlag() const {
+ return (mandatory_flag_);
+ }
+
+ /// @brief Sets new mandatory flag.
+ ///
+ /// @param mandatory_flag New numeric status code.
+ void setMandatoryFlag(const bool mandatory_flag) {
+ mandatory_flag_ = mandatory_flag;
+ }
+
+ /// @brief Returns scope list.
+ const std::string& getScopeList() const {
+ return (scope_list_);
+ }
+
+ /// @brief Sets new scope list.
+ ///
+ /// @param scope_list New scope list (empty string is allowed).
+ void setScopeList(std::string& scope_list) {
+ scope_list_ = scope_list;
+ }
+
+private:
+ /// @brief Mandatory flag.
+ bool mandatory_flag_;
+
+ /// @brief Scope list.
+ std::string scope_list_;
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION6_STATUS_CODE_H
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
new file mode 100644
index 0000000..1da78fd
--- /dev/null
+++ b/src/lib/dhcp/option_custom.cc
@@ -0,0 +1,732 @@
+// Copyright (C) 2012-2023 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 <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_custom.h>
+#include <exceptions/isc_assert.h>
+#include <util/encode/hex.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u)
+ : Option(u, def.getCode(), OptionBuffer()),
+ definition_(def) {
+ setEncapsulatedSpace(def.getEncapsulatedSpace());
+ createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u,
+ const OptionBuffer& data)
+ : Option(u, def.getCode(), data.begin(), data.end()),
+ definition_(def) {
+ setEncapsulatedSpace(def.getEncapsulatedSpace());
+ createBuffers(getData());
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u,
+ OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(u, def.getCode(), first, last),
+ definition_(def) {
+ setEncapsulatedSpace(def.getEncapsulatedSpace());
+ createBuffers(getData());
+}
+
+OptionPtr
+OptionCustom::clone() const {
+ return (cloneInternal<OptionCustom>());
+}
+
+void
+OptionCustom::addArrayDataField(const IOAddress& address) {
+ checkArrayType();
+
+ if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
+ (address.isV6() && definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address << ". Expected a valid IPv"
+ << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ?
+ "4" : "6") << " address.");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const std::string& value) {
+ checkArrayType();
+
+ OpaqueDataTuple::LengthFieldType lft = OptionDataTypeUtil::getTupleLenFieldType(getUniverse());
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeTuple(value, lft, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const OpaqueDataTuple& value) {
+ checkArrayType();
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeTuple(value, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const bool value) {
+ checkArrayType();
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeBool(value, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix) {
+ checkArrayType();
+
+ if (definition_.getType() != OPT_IPV6_PREFIX_TYPE) {
+ isc_throw(BadDataTypeCast, "IPv6 prefix can be specified only for"
+ " an option comprising an array of IPv6 prefix values");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const PSIDLen& psid_len, const PSID& psid) {
+ checkArrayType();
+
+ if (definition_.getType() != OPT_PSID_TYPE) {
+ isc_throw(BadDataTypeCast, "PSID value can be specified onlu for"
+ " an option comprising an array of PSID length / value"
+ " tuples");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::checkIndex(const uint32_t index) const {
+ if (index >= buffers_.size()) {
+ isc_throw(isc::OutOfRange, "specified data field index " << index
+ << " is out of range.");
+ }
+}
+
+void
+OptionCustom::createBuffer(OptionBuffer& buffer,
+ const OptionDataType data_type) const {
+ // For data types that have a fixed size we can use the
+ // utility function to get the buffer's size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+
+ // For variable data sizes the utility function returns zero.
+ // It is ok for string values because the default string
+ // is 'empty'. However for FQDN the empty value is not valid
+ // so we initialize it to '.'. For prefix there is a prefix
+ // length fixed field.
+ if (data_size == 0) {
+ if (data_type == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buffer);
+
+ } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+ OptionDataTypeUtil::writePrefix(PrefixLen(0),
+ IOAddress::IPV6_ZERO_ADDRESS(),
+ buffer);
+ }
+ } else {
+ // At this point we can resize the buffer. Note that
+ // for string values we are setting the empty buffer
+ // here.
+ buffer.resize(data_size);
+ }
+}
+
+void
+OptionCustom::createBuffers() {
+ definition_.validate();
+
+ std::vector<OptionBuffer> buffers;
+
+ OptionDataType data_type = definition_.getType();
+ // This function is called when an empty data buffer has been
+ // passed to the constructor. In such cases values for particular
+ // data fields will be set using modifier functions but for now
+ // we need to initialize a set of buffers that are specified
+ // for an option by its definition. Since there is no data yet,
+ // we are going to fill these buffers with default values.
+ if (data_type == OPT_RECORD_TYPE) {
+ // For record types we need to iterate over all data fields
+ // specified in option definition and create corresponding
+ // buffers for each of them.
+ const OptionDefinition::RecordFieldsCollection fields =
+ definition_.getRecordFields();
+
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ OptionBuffer buf;
+ createBuffer(buf, *field);
+ // We have the buffer with default value prepared so we
+ // add it to the set of buffers.
+ buffers.push_back(buf);
+ }
+ } else if (!definition_.getArrayType() &&
+ data_type != OPT_EMPTY_TYPE) {
+ // For either 'empty' options we don't have to create any buffers
+ // for obvious reason. For arrays we also don't create any buffers
+ // yet because the set of fields that belong to the array is open
+ // ended so we can't allocate required buffers until we know how
+ // many of them are needed.
+ // For non-arrays we have a single value being held by the option
+ // so we have to allocate exactly one buffer.
+ OptionBuffer buf;
+ createBuffer(buf, data_type);
+ // Add a buffer that we have created and leave.
+ buffers.push_back(buf);
+ }
+ // The 'swap' is used here because we want to make sure that we
+ // don't touch buffers_ until we successfully allocate all
+ // buffers to be stored there.
+ std::swap(buffers, buffers_);
+}
+
+size_t
+OptionCustom::bufferLength(const OptionDataType data_type, bool in_array,
+ OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) const {
+ // For fixed-size data type such as boolean, integer, even
+ // IP address we can use the utility function to get the required
+ // buffer size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+
+ // For variable size types (e.g. string) the function above will
+ // return 0 so we need to do a runtime check of the length.
+ if (data_size == 0) {
+ // FQDN is a special data type as it stores variable length data
+ // but the data length is encoded in the buffer. The easiest way
+ // to obtain the length of the data is to read the FQDN. The
+ // utility function will return the size of the buffer on success.
+ if (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(begin, end));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else if (!definition_.getArrayType() &&
+ ((data_type == OPT_BINARY_TYPE) ||
+ (data_type == OPT_STRING_TYPE))) {
+ // In other case we are dealing with string or binary value
+ // which size can't be determined. Thus we consume the
+ // remaining part of the buffer for it. Note that variable
+ // size data can be laid at the end of the option only and
+ // that the validate() function in OptionDefinition object
+ // should have checked wheter it is a case for this option.
+ data_size = std::distance(begin, end);
+ } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+ // The size of the IPV6 prefix type is determined as
+ // one byte (which is the size of the prefix in bits)
+ // followed by the prefix bits (right-padded with
+ // zeros to the nearest octet boundary)
+ if ((begin == end) && !in_array)
+ return 0;
+ PrefixTuple prefix =
+ OptionDataTypeUtil::readPrefix(OptionBuffer(begin, end));
+ // Data size comprises 1 byte holding a prefix length and the
+ // prefix length (in bytes) rounded to the nearest byte boundary.
+ data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
+ } else if (data_type == OPT_TUPLE_TYPE) {
+ OpaqueDataTuple::LengthFieldType lft =
+ OptionDataTypeUtil::getTupleLenFieldType(getUniverse());
+ std::string value =
+ OptionDataTypeUtil::readTuple(OptionBuffer(begin, end), lft);
+ data_size = value.size();
+ // The size of the buffer holding a tuple is always
+ // 1 or 2 byte larger than the size of the string
+ data_size += getUniverse() == Option::V4 ? 1 : 2;
+ } else {
+ // If we reached the end of buffer we assume that this option is
+ // truncated because there is no remaining data to initialize
+ // an option field.
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+ }
+
+ return data_size;
+}
+
+void
+OptionCustom::createBuffers(const OptionBuffer& data_buf) {
+ // Check that the option definition is correct as we are going
+ // to use it to split the data_ buffer into set of sub buffers.
+ definition_.validate();
+
+ std::vector<OptionBuffer> buffers;
+ OptionBuffer::const_iterator data = data_buf.begin();
+
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ // An option comprises a record of data fields. We need to
+ // get types of these data fields to allocate enough space
+ // for each buffer.
+ const OptionDefinition::RecordFieldsCollection& fields =
+ definition_.getRecordFields();
+
+ // Go over all data fields within a record.
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ size_t data_size = bufferLength(*field, false,
+ data, data_buf.end());
+
+ // Our data field requires that there is a certain chunk of
+ // data left in the buffer. If not, option is truncated.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+
+ // Store the created buffer.
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ // Proceed to the next data field.
+ data += data_size;
+ }
+
+ // Get extra buffers when the last field is an array.
+ if (definition_.getArrayType()) {
+ while (data != data_buf.end()) {
+ // Code copied from the standard array case
+ size_t data_size = bufferLength(fields.back(), true,
+ data, data_buf.end());
+ isc_throw_assert(data_size > 0);
+ if (std::distance(data, data_buf.end()) < data_size) {
+ break;
+ }
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ data += data_size;
+ }
+ }
+
+ // Unpack suboptions if any.
+ else if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
+ unpackOptions(OptionBuffer(data, data_buf.end()));
+ }
+
+ } else if (data_type != OPT_EMPTY_TYPE) {
+ // If data_type value is other than OPT_RECORD_TYPE, our option is
+ // empty (have no data at all) or it comprises one or more
+ // data fields of the same type. The type of those fields
+ // is held in the data_type variable so let's use it to determine
+ // a size of buffers.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ // The check below will fail if the input buffer is too short
+ // for the data size being held by this option.
+ // Note that data_size returned by getDataTypeLen may be zero
+ // if variable length data is being held by the option but
+ // this will not cause this check to throw exception.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+ // For an array of values we are taking different path because
+ // we have to handle multiple buffers.
+ if (definition_.getArrayType()) {
+ while (data != data_buf.end()) {
+ data_size = bufferLength(data_type, true, data, data_buf.end());
+ // We don't perform other checks for data types that can't be
+ // used together with array indicator such as strings, empty field
+ // etc. This is because OptionDefinition::validate function should
+ // have checked this already. Thus data_size must be greater than
+ // zero.
+ isc_throw_assert(data_size > 0);
+ // Get chunks of data and store as a collection of buffers.
+ // Truncate any remaining part which length is not divisible by
+ // data_size. Note that it is ok to truncate the data if and only
+ // if the data buffer is long enough to keep at least one value.
+ // This has been checked above already.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ break;
+ }
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ data += data_size;
+ }
+ } else {
+ // For non-arrays the data_size can be zero because
+ // getDataTypeLen returns zero for variable size data types
+ // such as strings. Simply take whole buffer.
+ data_size = bufferLength(data_type, false, data, data_buf.end());
+ if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) {
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ data += data_size;
+ } else {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+
+ // Unpack suboptions if any.
+ if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
+ unpackOptions(OptionBuffer(data, data_buf.end()));
+ }
+ }
+ } else {
+ // Unpack suboptions if any.
+ if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
+ unpackOptions(OptionBuffer(data, data_buf.end()));
+ }
+ }
+ // If everything went ok we can replace old buffer set with new ones.
+ std::swap(buffers_, buffers);
+}
+
+std::string
+OptionCustom::dataFieldToText(const OptionDataType data_type,
+ const uint32_t index) const {
+ std::ostringstream text;
+
+ // Get the value of the data field.
+ switch (data_type) {
+ case OPT_BINARY_TYPE:
+ text << util::encode::encodeHex(readBinary(index));
+ break;
+ case OPT_BOOLEAN_TYPE:
+ text << (readBoolean(index) ? "true" : "false");
+ break;
+ case OPT_INT8_TYPE:
+ text << static_cast<int>(readInteger<int8_t>(index));
+ break;
+ case OPT_INT16_TYPE:
+ text << readInteger<int16_t>(index);
+ break;
+ case OPT_INT32_TYPE:
+ text << readInteger<int32_t>(index);
+ break;
+ case OPT_UINT8_TYPE:
+ text << static_cast<unsigned>(readInteger<uint8_t>(index));
+ break;
+ case OPT_UINT16_TYPE:
+ text << readInteger<uint16_t>(index);
+ break;
+ case OPT_UINT32_TYPE:
+ text << readInteger<uint32_t>(index);
+ break;
+ case OPT_IPV4_ADDRESS_TYPE:
+ case OPT_IPV6_ADDRESS_TYPE:
+ text << readAddress(index);
+ break;
+ case OPT_FQDN_TYPE:
+ text << "\"" << readFqdn(index) << "\"";
+ break;
+ case OPT_TUPLE_TYPE:
+ text << "\"" << readTuple(index) << "\"";
+ break;
+ case OPT_STRING_TYPE:
+ text << "\"" << readString(index) << "\"";
+ break;
+ case OPT_PSID_TYPE:
+ {
+ PSIDTuple t = readPsid(index);
+ text << "len=" << t.first.asUnsigned() << ",psid=" << t.second.asUint16();
+ }
+ default:
+ ;
+ }
+
+ // Append data field type in brackets.
+ text << " (" << OptionDataTypeUtil::getDataTypeName(data_type) << ")";
+
+ return (text.str());
+}
+
+void
+OptionCustom::pack(isc::util::OutputBuffer& buf, bool check) const {
+
+ // Pack DHCP header (V4 or V6).
+ packHeader(buf, check);
+
+ // Write data from buffers.
+ for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
+ it != buffers_.end(); ++it) {
+ // In theory the createBuffers function should have taken
+ // care that there are no empty buffers added to the
+ // collection but it is almost always good to make sure.
+ if (!it->empty()) {
+ buf.writeData(&(*it)[0], it->size());
+ }
+ }
+
+ // Write suboptions.
+ packOptions(buf, check);
+}
+
+
+IOAddress
+OptionCustom::readAddress(const uint32_t index) const {
+ checkIndex(index);
+
+ // The address being read can be either IPv4 or IPv6. The decision
+ // is made based on the buffer length. If it holds 4 bytes it is IPv4
+ // address, if it holds 16 bytes it is IPv6.
+ if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) {
+ return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET));
+ } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) {
+ return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
+ } else {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " IP address. Invalid buffer length "
+ << buffers_[index].size() << ".");
+ }
+}
+
+void
+OptionCustom::writeAddress(const IOAddress& address,
+ const uint32_t index) {
+ checkIndex(index);
+
+ if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
+ (address.isV6() && buffers_[index].size() != V6ADDRESS_LEN)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address << ". Expected a valid IPv"
+ << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
+ << " address.");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ std::swap(buf, buffers_[index]);
+}
+
+const OptionBuffer&
+OptionCustom::readBinary(const uint32_t index) const {
+ checkIndex(index);
+ return (buffers_[index]);
+}
+
+void
+OptionCustom::writeBinary(const OptionBuffer& buf,
+ const uint32_t index) {
+ checkIndex(index);
+ buffers_[index] = buf;
+}
+
+std::string
+OptionCustom::readTuple(const uint32_t index) const {
+ checkIndex(index);
+ OpaqueDataTuple::LengthFieldType lft = OptionDataTypeUtil::getTupleLenFieldType(getUniverse());
+ return (OptionDataTypeUtil::readTuple(buffers_[index], lft));
+}
+
+void
+OptionCustom::readTuple(OpaqueDataTuple& tuple,
+ const uint32_t index) const {
+ checkIndex(index);
+ OptionDataTypeUtil::readTuple(buffers_[index], tuple);
+}
+
+void
+OptionCustom::writeTuple(const std::string& value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OpaqueDataTuple::LengthFieldType lft = OptionDataTypeUtil::getTupleLenFieldType(getUniverse());
+ OptionDataTypeUtil::writeTuple(value, lft, buffers_[index]);
+}
+
+void
+OptionCustom::writeTuple(const OpaqueDataTuple& value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OptionDataTypeUtil::writeTuple(value, buffers_[index]);
+}
+
+bool
+OptionCustom::readBoolean(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readBool(buffers_[index]));
+}
+
+void
+OptionCustom::writeBoolean(const bool value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OptionDataTypeUtil::writeBool(value, buffers_[index]);
+}
+
+std::string
+OptionCustom::readFqdn(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readFqdn(buffers_[index]));
+}
+
+void
+OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
+ checkIndex(index);
+
+ // Create a temporary buffer where the FQDN will be written.
+ OptionBuffer buf;
+ // Try to write to the temporary buffer rather than to the
+ // buffers_ member directly guarantees that we don't modify
+ // (clear) buffers_ until we are sure that the provided FQDN
+ // is valid.
+ OptionDataTypeUtil::writeFqdn(fqdn, buf);
+ // If we got to this point it means that the FQDN is valid.
+ // We can move the contents of the temporary buffer to the
+ // target buffer.
+ std::swap(buffers_[index], buf);
+}
+
+PrefixTuple
+OptionCustom::readPrefix(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readPrefix(buffers_[index]));
+}
+
+void
+OptionCustom::writePrefix(const PrefixLen& prefix_len,
+ const IOAddress& prefix,
+ const uint32_t index) {
+ checkIndex(index);
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+ // If there are no errors while writing PSID to a buffer, we can
+ // replace the current buffer with a new buffer.
+ std::swap(buffers_[index], buf);
+}
+
+
+PSIDTuple
+OptionCustom::readPsid(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readPsid(buffers_[index]));
+}
+
+void
+OptionCustom::writePsid(const PSIDLen& psid_len, const PSID& psid,
+ const uint32_t index) {
+ checkIndex(index);
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+ // If there are no errors while writing PSID to a buffer, we can
+ // replace the current buffer with a new buffer.
+ std::swap(buffers_[index], buf);
+}
+
+
+std::string
+OptionCustom::readString(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readString(buffers_[index]));
+}
+
+void
+OptionCustom::writeString(const std::string& text, const uint32_t index) {
+ checkIndex(index);
+
+ // Let's clear a buffer as we want to replace the value of the
+ // whole buffer. If we fail to clear the buffer the data will
+ // be appended.
+ buffers_[index].clear();
+ // If the text value is empty we can leave because the buffer
+ // is already empty.
+ if (!text.empty()) {
+ OptionDataTypeUtil::writeString(text, buffers_[index]);
+ }
+}
+
+void
+OptionCustom::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ initialize(begin, end);
+}
+
+uint16_t
+OptionCustom::len() const {
+ // The length of the option is a sum of option header ...
+ size_t length = getHeaderLen();
+
+ // ... lengths of all buffers that hold option data ...
+ for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin();
+ buf != buffers_.end(); ++buf) {
+ length += buf->size();
+ }
+
+ // ... and lengths of all suboptions
+ for (OptionCollection::const_iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+
+ return (static_cast<uint16_t>(length));
+}
+
+void OptionCustom::initialize(const OptionBufferConstIter first,
+ const OptionBufferConstIter last) {
+ setData(first, last);
+
+ // Chop the data_ buffer into set of buffers that represent
+ // option fields data.
+ createBuffers(getData());
+}
+
+std::string OptionCustom::toText(int indent) const {
+ std::stringstream output;
+
+ output << headerToText(indent) << ":";
+
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ const OptionDefinition::RecordFieldsCollection& fields =
+ definition_.getRecordFields();
+
+ // For record types we iterate over fields defined in
+ // option definition and match the appropriate buffer
+ // with them.
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ output << " " << dataFieldToText(*field, std::distance(fields.begin(),
+ field));
+ }
+
+ // If the last record field is an array iterate on extra buffers
+ if (definition_.getArrayType()) {
+ for (unsigned int i = fields.size(); i < getDataFieldsNum(); ++i) {
+ output << " " << dataFieldToText(fields.back(), i);
+ }
+ }
+ } else {
+ // For non-record types we iterate over all buffers
+ // and print the data type set globally for an option
+ // definition. We take the same code path for arrays
+ // and non-arrays as they only differ in such a way that
+ // non-arrays have just single data field.
+ for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
+ output << " " << dataFieldToText(definition_.getType(), i);
+ }
+ }
+
+ // Append suboptions.
+ output << suboptionsToText(indent + 2);
+
+ return (output.str());
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
new file mode 100644
index 0000000..9208651
--- /dev/null
+++ b/src/lib/dhcp/option_custom.h
@@ -0,0 +1,511 @@
+// Copyright (C) 2012-2022 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 OPTION_CUSTOM_H
+#define OPTION_CUSTOM_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <exceptions/isc_assert.h>
+#include <util/io_utilities.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Option with defined data fields represented as buffers that can
+/// be accessed using data field index.
+///
+/// This class represents an option which has defined structure: data fields
+/// of specific types and order. Those fields can be accessed using indexes,
+/// where index 0 represents first data field within an option. The last
+/// field can be accessed using index equal to 'number of fields' - 1.
+/// Internally, the option data is stored as a collection of OptionBuffer
+/// objects, each representing data for a particular data field. This data
+/// can be converted to the actual data type using methods implemented
+/// within this class. This class is used to represent those options that
+/// can't be represented by any other specialized class (this excludes the
+/// Option class which is generic and can be used to represent any option).
+class OptionCustom : public Option {
+public:
+
+ /// @brief Constructor, used for options to be sent.
+ ///
+ /// This constructor creates an instance of an option with default
+ /// data set for all data fields. The option buffers are allocated
+ /// according to data size being stored in particular data fields.
+ /// For variable size data empty buffers are created.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6)
+ OptionCustom(const OptionDefinition& def, Universe u);
+
+ /// @brief Constructor, used for options to be sent.
+ ///
+ /// This constructor creates an instance of an option from the whole
+ /// supplied buffer. This constructor is mainly used to create an
+ /// instances of options to be stored in outgoing DHCP packets.
+ /// The buffer used to create the instance of an option can be
+ /// created from the option data specified in server's configuration.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6).
+ /// @param data content of the option.
+ ///
+ /// @throw OutOfRange if option buffer is truncated.
+ ///
+ /// @todo list all exceptions thrown by ctor.
+ OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// This constructor creates an instance an option from the portion
+ /// of the buffer specified by iterators. This is mainly useful when
+ /// parsing received packets. Such packets are represented by a single
+ /// buffer holding option data and all sub options. Methods that are
+ /// parsing a packet, supply relevant portions of the packet buffer
+ /// to this constructor to create option instances out of it.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6).
+ /// @param first iterator to the first element that should be copied.
+ /// @param last iterator to the next element after the last one
+ /// to be copied.
+ ///
+ /// @throw OutOfRange if option buffer is truncated.
+ ///
+ /// @todo list all exceptions thrown by ctor.
+ OptionCustom(const OptionDefinition& def, Universe u,
+ OptionBufferConstIter first, OptionBufferConstIter last);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const;
+
+ /// @brief Create new buffer and set its value as an IP address.
+ ///
+ /// @param address IPv4 or IPv6 address to be written to
+ /// a buffer being created.
+ void addArrayDataField(const asiolink::IOAddress& address);
+
+ /// @brief Create new buffer and store boolean value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ void addArrayDataField(const bool value);
+
+ /// @brief Create new buffer and store integer value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ /// @tparam T integer type of the value being stored.
+ template<typename T>
+ void addArrayDataField(const T value) {
+ checkArrayType();
+ OptionDataType data_type = definition_.getType();
+ // Handle record last field.
+ if (data_type == OPT_RECORD_TYPE) {
+ data_type = definition_.getRecordFields().back();
+ }
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ buffers_.push_back(buf);
+ }
+
+ /// @brief Create new buffer and store tuple value in it
+ ///
+ /// @param value value to be stored as a tuple in the created buffer.
+ void addArrayDataField(const std::string& value);
+
+ /// @brief Create new buffer and store tuple value in it
+ ///
+ /// @param value value to be stored as a tuple in the created buffer.
+ void addArrayDataField(const OpaqueDataTuple& value);
+
+ /// @brief Create new buffer and store variable length prefix in it.
+ ///
+ /// @param prefix_len Prefix length.
+ /// @param prefix Prefix.
+ void addArrayDataField(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix);
+
+ /// @brief Create new buffer and store PSID length / value in it.
+ ///
+ /// @param psid_len PSID length.
+ /// @param psid PSID.
+ void addArrayDataField(const PSIDLen& psid_len, const PSID& psid);
+
+ /// @brief Return a number of the data fields.
+ ///
+ /// @return number of data fields held by the option.
+ uint32_t getDataFieldsNum() const { return (buffers_.size()); }
+
+ /// @brief Read a buffer as IP address.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return IP address read from a buffer.
+ /// @throw isc::OutOfRange if index is out of range.
+ asiolink::IOAddress readAddress(const uint32_t index = 0) const;
+
+ /// @brief Write an IP address into a buffer.
+ ///
+ /// @param address IP address being written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid.
+ void writeAddress(const asiolink::IOAddress& address,
+ const uint32_t index = 0);
+
+ /// @brief Read a buffer as binary data.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @return read buffer holding binary data.
+ const OptionBuffer& readBinary(const uint32_t index = 0) const;
+
+ /// @brief Write binary data into a buffer.
+ ///
+ /// @param buf buffer holding binary data to be written.
+ /// @param index buffer index.
+ void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
+
+ /// @brief Read a buffer as length and string tuple.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @return string read from a buffer.
+ std::string readTuple(const uint32_t index = 0) const;
+
+ /// @brief Read a buffer into a length and string tuple.
+ ///
+ /// @param tuple tuple to fill.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void readTuple(OpaqueDataTuple& tuple, const uint32_t index = 0) const;
+
+ /// @brief Write a length and string tuple into a buffer.
+ ///
+ /// @param value value to be written.
+ /// @param index buffer index.
+ void writeTuple(const std::string& value, const uint32_t index = 0);
+
+ /// @brief Write a length and string tuple into a buffer.
+ ///
+ /// @param value value to be written.
+ /// @param index buffer index.
+ void writeTuple(const OpaqueDataTuple& value, const uint32_t index = 0);
+
+ /// @brief Read a buffer as boolean value.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @return read boolean value.
+ bool readBoolean(const uint32_t index = 0) const;
+
+ /// @brief Write a boolean value into a buffer.
+ ///
+ /// @param value boolean value to be written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeBoolean(const bool value, const uint32_t index = 0);
+
+ /// @brief Read a buffer as FQDN.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if buffer index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if a buffer being read
+ /// does not hold a valid FQDN.
+ /// @return string representation if FQDN.
+ std::string readFqdn(const uint32_t index = 0) const;
+
+ /// @brief Write an FQDN into a buffer.
+ ///
+ /// @param fqdn text representation of FQDN.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeFqdn(const std::string& fqdn, const uint32_t index = 0);
+
+ /// @brief Read a buffer as integer value.
+ ///
+ /// @param index buffer index.
+ /// @tparam integer type of a value being returned.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
+ /// @return read integer value.
+ template<typename T>
+ T readInteger(const uint32_t index = 0) const {
+ // Check that the index is not out of range.
+ checkIndex(index);
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
+ // When we created the buffer we have checked that it has a
+ // valid size so this condition here should be always fulfilled.
+ isc_throw_assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
+ // Read an integer value.
+ return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
+ }
+
+ /// @brief Write an integer value into a buffer.
+ ///
+ /// @param value integer value to be written.
+ /// @param index buffer index.
+ /// @tparam T integer type of a value being written.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
+ template<typename T>
+ void writeInteger(const T value, const uint32_t index = 0) {
+ // Check that the index is not out of range.
+ checkIndex(index);
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
+ // Get some temporary buffer.
+ OptionBuffer buf;
+ // Try to write to the buffer.
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ // If successful, replace the old buffer with new one.
+ std::swap(buffers_[index], buf);
+ }
+
+ /// @brief Read a buffer as variable length prefix.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return Prefix length / value tuple.
+ /// @throw isc::OutOfRange of index is out of range.
+ PrefixTuple readPrefix(const uint32_t index = 0) const;
+
+ /// @brief Write prefix length and value into a buffer.
+ ///
+ /// @param prefix_len Prefix length.
+ /// @param prefix Prefix value.
+ /// @param index Buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writePrefix(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix,
+ const uint32_t index = 0);
+
+ /// @brief Read a buffer as a PSID length / value tuple.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return PSID length / value tuple.
+ /// @throw isc::OutOfRange of index is out of range.
+ PSIDTuple readPsid(const uint32_t index = 0) const;
+
+ /// @brief Write PSID length / value into a buffer.
+ ///
+ /// @param psid_len PSID length value.
+ /// @param psid PSID value in the range of 0 .. 2^(PSID length).
+ /// @param index buffer index.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if PSID length or value is
+ /// invalid.
+ /// @throw isc::OutOfRange if index is out of range.
+ void writePsid(const PSIDLen& psid_len, const PSID& psid,
+ const uint32_t index = 0);
+
+ /// @brief Read a buffer as string value.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return string value read from buffer.
+ /// @throw isc::OutOfRange if index is out of range.
+ std::string readString(const uint32_t index = 0) const;
+
+ /// @brief Write a string value into a buffer.
+ ///
+ /// @param text the string value to be written.
+ /// @param index buffer index.
+ void writeString(const std::string& text,
+ const uint32_t index = 0);
+
+ /// @brief Writes DHCP option in a wire format to a buffer.
+ ///
+ /// @param buf output buffer (option will be stored there).
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printed text.
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns length of the complete option (data length +
+ /// DHCPv4/DHCPv6 option header)
+ ///
+ /// @return length of the option
+ virtual uint16_t len() const;
+
+ /// @brief Sets content of this option from buffer.
+ ///
+ /// Option will be resized to length of buffer.
+ ///
+ /// @param first iterator pointing to beginning of buffer to copy.
+ /// @param last iterator pointing to end of buffer to copy.
+ void initialize(const OptionBufferConstIter first,
+ const OptionBufferConstIter last);
+
+private:
+
+ /// @brief Verify that the option comprises an array of values.
+ ///
+ /// This helper function is used by createArrayEntry functions
+ /// and throws an exception if the particular option is not
+ /// an array.
+ ///
+ /// @throw isc::InvalidOperation if option is not an array.
+ inline void checkArrayType() const {
+ if (!definition_.getArrayType()) {
+ isc_throw(InvalidOperation, "failed to add new array entry to an"
+ << " option. The option is not an array.");
+ }
+ }
+
+ /// @brief Verify that the integer type is consistent with option
+ /// field type.
+ ///
+ /// This convenience function checks that the data type specified as T
+ /// is consistent with a type of a data field identified by index.
+ ///
+ /// @param index data field index.
+ /// @tparam data type to be validated.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if the type is invalid.
+ template<typename T>
+ // cppcheck-suppress unusedPrivateFunction
+ void checkDataType(const uint32_t index) const;
+
+ /// @brief Check if data field index is valid.
+ ///
+ /// @param index Data field index to check.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void checkIndex(const uint32_t index) const;
+
+ /// @brief Create a non initialized buffer.
+ ///
+ /// @param buffer buffer to update.
+ /// @param data_type data type of buffer.
+ void createBuffer(OptionBuffer& buffer,
+ const OptionDataType data_type) const;
+
+ /// @brief Create a collection of non initialized buffers.
+ void createBuffers();
+
+ /// @brief Return length of a buffer.
+ ///
+ /// @param data_type data type of buffer.
+ /// @param in_array true is called from the array case
+ /// @param begin iterator to first byte of input data.
+ /// @param end iterator to end of input data.
+ ///
+ /// @return size of data to copy to the buffer.
+ /// @throw isc::OutOfRange if option buffer is truncated.
+ size_t bufferLength(const OptionDataType data_type, bool in_array,
+ OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) const;
+
+ /// @brief Create collection of buffers representing data field values.
+ ///
+ /// @param data_buf a buffer to be parsed.
+ void createBuffers(const OptionBuffer& data_buf);
+
+ /// @brief Return a text representation of a data field.
+ ///
+ /// @param data_type data type of a field.
+ /// @param index data field buffer index within a custom option.
+ ///
+ /// @return text representation of a data field.
+ std::string dataFieldToText(const OptionDataType data_type,
+ const uint32_t index) const;
+
+ /// Make this function private as we don't want it to be invoked
+ /// on OptionCustom object. We rather want that initialize to
+ /// be called instead.
+ using Option::setData;
+
+ /// Option definition used to create an option.
+ OptionDefinition definition_;
+
+ /// The collection of buffers holding data for option fields.
+ /// The order of buffers corresponds to the order of option
+ /// fields.
+ std::vector<OptionBuffer> buffers_;
+};
+
+/// A pointer to the OptionCustom object.
+typedef boost::shared_ptr<OptionCustom> OptionCustomPtr;
+
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+ // Check that the requested return type is a supported integer.
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+ " is not a supported integer type.");
+ }
+
+ // Get the option definition type.
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ definition_.getRecordFields();
+ if (definition_.getArrayType()) {
+ // If the array flag is set the last record field is an array.
+ if (index < record_fields.size()) {
+ // Get the data type to be returned.
+ data_type = record_fields[index];
+ } else {
+ // Get the data type to be returned from the last record field.
+ data_type = record_fields.back();
+ }
+ } else {
+ // When we initialized buffers we have already checked that
+ // the number of these buffers is equal to number of option
+ // fields in the record so the condition below should be met.
+ isc_throw_assert(index < record_fields.size());
+ // Get the data type to be returned.
+ data_type = record_fields[index];
+ }
+ }
+
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition for field"
+ " index " << index);
+ }
+}
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_CUSTOM_H
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
new file mode 100644
index 0000000..934ff54
--- /dev/null
+++ b/src/lib/dhcp/option_data_types.cc
@@ -0,0 +1,617 @@
+// Copyright (C) 2012-2023 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 <dhcp/option_data_types.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
+#include <util/strutil.h>
+#include <util/encode/hex.h>
+#include <algorithm>
+#include <limits>
+
+using namespace isc::asiolink;
+
+namespace {
+/// @brief Bit mask used to compute PSID value.
+///
+/// The mask represents the useful bits of the PSID. The value 0 is a special
+/// case because the RFC explicitly specifies that PSID value should be ignored
+/// if psid_len is 0.
+std::vector<uint16_t> psid_bitmask = { 0xffff,
+ 0x8000, 0xc000, 0xe000, 0xf000,
+ 0xf800, 0xfc00, 0xfe00, 0xff00,
+ 0xff80, 0xffc0, 0xffe0, 0xfff0,
+ 0xfff8, 0xfffc, 0xfffe, 0xffff
+};
+}
+
+namespace isc {
+namespace dhcp {
+
+OptionDataTypeUtil::OptionDataTypeUtil() {
+ data_types_["empty"] = OPT_EMPTY_TYPE;
+ data_types_["binary"] = OPT_BINARY_TYPE;
+ data_types_["boolean"] = OPT_BOOLEAN_TYPE;
+ data_types_["int8"] = OPT_INT8_TYPE;
+ data_types_["int16"] = OPT_INT16_TYPE;
+ data_types_["int32"] = OPT_INT32_TYPE;
+ data_types_["uint8"] = OPT_UINT8_TYPE;
+ data_types_["uint16"] = OPT_UINT16_TYPE;
+ data_types_["uint32"] = OPT_UINT32_TYPE;
+ data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
+ data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
+ data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE;
+ data_types_["psid"] = OPT_PSID_TYPE;
+ data_types_["string"] = OPT_STRING_TYPE;
+ data_types_["tuple"] = OPT_TUPLE_TYPE;
+ data_types_["fqdn"] = OPT_FQDN_TYPE;
+ data_types_["record"] = OPT_RECORD_TYPE;
+
+ data_type_names_[OPT_EMPTY_TYPE] = "empty";
+ data_type_names_[OPT_BINARY_TYPE] = "binary";
+ data_type_names_[OPT_BOOLEAN_TYPE] = "boolean";
+ data_type_names_[OPT_INT8_TYPE] = "int8";
+ data_type_names_[OPT_INT16_TYPE] = "int16";
+ data_type_names_[OPT_INT32_TYPE] = "int32";
+ data_type_names_[OPT_UINT8_TYPE] = "uint8";
+ data_type_names_[OPT_UINT16_TYPE] = "uint16";
+ data_type_names_[OPT_UINT32_TYPE] = "uint32";
+ data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
+ data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
+ data_type_names_[OPT_IPV6_PREFIX_TYPE] = "ipv6-prefix";
+ data_type_names_[OPT_PSID_TYPE] = "psid";
+ data_type_names_[OPT_STRING_TYPE] = "string";
+ data_type_names_[OPT_TUPLE_TYPE] = "tuple";
+ data_type_names_[OPT_FQDN_TYPE] = "fqdn";
+ data_type_names_[OPT_RECORD_TYPE] = "record";
+ // The "unknown" data type is declared here so as
+ // it can be returned by reference by a getDataTypeName
+ // function it no other type is suitable. Other than that
+ // this is unused.
+ data_type_names_[OPT_UNKNOWN_TYPE] = "unknown";
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataType(const std::string& data_type) {
+ return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type));
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const {
+ std::map<std::string, OptionDataType>::const_iterator data_type_it =
+ data_types_.find(data_type);
+ if (data_type_it != data_types_.end()) {
+ return (data_type_it->second);
+ }
+ return (OPT_UNKNOWN_TYPE);
+}
+
+int
+OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
+ switch (data_type) {
+ case OPT_BOOLEAN_TYPE:
+ case OPT_INT8_TYPE:
+ case OPT_UINT8_TYPE:
+ return (1);
+
+ case OPT_INT16_TYPE:
+ case OPT_UINT16_TYPE:
+ return (2);
+
+ case OPT_INT32_TYPE:
+ case OPT_UINT32_TYPE:
+ return (4);
+
+ case OPT_IPV4_ADDRESS_TYPE:
+ return (asiolink::V4ADDRESS_LEN);
+
+ case OPT_IPV6_ADDRESS_TYPE:
+ return (asiolink::V6ADDRESS_LEN);
+
+ case OPT_PSID_TYPE:
+ return (3);
+
+ default:
+ ;
+ }
+ return (0);
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) {
+ return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type));
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const {
+ std::map<OptionDataType, std::string>::const_iterator data_type_it =
+ data_type_names_.find(data_type);
+ if (data_type_it != data_type_names_.end()) {
+ return (data_type_it->second);
+ }
+ return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second);
+}
+
+OptionDataTypeUtil&
+OptionDataTypeUtil::instance() {
+ static OptionDataTypeUtil instance;
+ return (instance);
+}
+
+asiolink::IOAddress
+OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
+ const short family) {
+ using namespace isc::asiolink;
+ if (family == AF_INET) {
+ if (buf.size() < V4ADDRESS_LEN) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " IPv4 address. Invalid buffer size: " << buf.size());
+ }
+ return (IOAddress::fromBytes(AF_INET, &buf[0]));
+ } else if (family == AF_INET6) {
+ if (buf.size() < V6ADDRESS_LEN) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " IPv6 address. Invalid buffer size: " << buf.size());
+ }
+ return (IOAddress::fromBytes(AF_INET6, &buf[0]));
+ } else {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " IP address. Invalid family: " << family);
+ }
+}
+
+void
+OptionDataTypeUtil::writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ const std::vector<uint8_t>& vec = address.toBytes();
+ buf.insert(buf.end(), vec.begin(), vec.end());
+}
+
+void
+OptionDataTypeUtil::writeBinary(const std::string& hex_str,
+ std::vector<uint8_t>& buf) {
+ // Binary value means that the value is encoded as a string
+ // of hexadecimal digits. We need to decode this string
+ // to the binary format here.
+ OptionBuffer binary;
+ try {
+ util::encode::decodeHex(hex_str, binary);
+ } catch (const Exception& ex) {
+ isc_throw(BadDataTypeCast, "unable to cast " << hex_str
+ << " to binary data type: " << ex.what());
+ }
+ // Decode was successful so append decoded binary value
+ // to the buffer.
+ buf.insert(buf.end(), binary.begin(), binary.end());
+}
+
+OpaqueDataTuple::LengthFieldType
+OptionDataTypeUtil::getTupleLenFieldType(Option::Universe u) {
+ if (u == Option::V4) {
+ return (OpaqueDataTuple::LENGTH_1_BYTE);
+ }
+ return (OpaqueDataTuple::LENGTH_2_BYTES);
+}
+
+std::string
+OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype) {
+ if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) {
+ if (buf.size() < 1) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length). Invalid buffer size: "
+ << buf.size());
+ }
+ uint8_t len = buf[0];
+ if (buf.size() < 1 + len) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length " << static_cast<unsigned>(len)
+ << "). Invalid buffer size: " << buf.size());
+ }
+ std::string value;
+ value.resize(len);
+ std::memcpy(&value[0], &buf[1], len);
+ return (value);
+ } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) {
+ if (buf.size() < 2) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length). Invalid buffer size: "
+ << buf.size());
+ }
+ uint16_t len = isc::util::readUint16(&buf[0], 2);
+ if (buf.size() < 2 + len) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple (length " << len
+ << "). Invalid buffer size: " << buf.size());
+ }
+ std::string value;
+ value.resize(len);
+ std::memcpy(&value[0], &buf[2], len);
+ return (value);
+ } else {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " tuple. Invalid length type field: "
+ << static_cast<unsigned>(lengthfieldtype));
+ }
+}
+
+void
+OptionDataTypeUtil::readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple& tuple) {
+ try {
+ tuple.unpack(buf.begin(), buf.end());
+ } catch (const OpaqueDataTupleError& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writeTuple(const std::string& value,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype,
+ std::vector<uint8_t>& buf) {
+ if (lengthfieldtype == OpaqueDataTuple::LENGTH_1_BYTE) {
+ if (value.size() > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << value.size() << " larger than "
+ << +std::numeric_limits<uint8_t>::max() << ")");
+ }
+ buf.push_back(static_cast<uint8_t>(value.size()));
+
+ } else if (lengthfieldtype == OpaqueDataTuple::LENGTH_2_BYTES) {
+ if (value.size() > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << value.size() << " larger than "
+ << std::numeric_limits<uint16_t>::max() << ")");
+ }
+ buf.resize(buf.size() + 2);
+ isc::util::writeUint16(static_cast<uint16_t>(value.size()),
+ &buf[buf.size() - 2], 2);
+ } else {
+ isc_throw(BadDataTypeCast, "unable to write data to the buffer as"
+ << " tuple. Invalid length type field: "
+ << static_cast<unsigned>(lengthfieldtype));
+ }
+ buf.insert(buf.end(), value.begin(), value.end());
+}
+
+void
+OptionDataTypeUtil::writeTuple(const OpaqueDataTuple& tuple,
+ std::vector<uint8_t>& buf) {
+ if (tuple.getLength() == 0) {
+ isc_throw(BadDataTypeCast, "invalid empty tuple value");
+ }
+ if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_1_BYTE) {
+ if (tuple.getLength() > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << tuple.getLength() << " larger than "
+ << +std::numeric_limits<uint8_t>::max() << ")");
+ }
+ buf.push_back(static_cast<uint8_t>(tuple.getLength()));
+
+ } else if (tuple.getLengthFieldType() == OpaqueDataTuple::LENGTH_2_BYTES) {
+ if (tuple.getLength() > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(BadDataTypeCast, "invalid tuple value (size "
+ << tuple.getLength() << " larger than "
+ << std::numeric_limits<uint16_t>::max() << ")");
+ }
+ buf.resize(buf.size() + 2);
+ isc::util::writeUint16(static_cast<uint16_t>(tuple.getLength()),
+ &buf[buf.size() - 2], 2);
+ } else {
+ isc_throw(BadDataTypeCast, "unable to write data to the buffer as"
+ << " tuple. Invalid length type field: "
+ << tuple.getLengthFieldType());
+ }
+ buf.insert(buf.end(), tuple.getData().begin(), tuple.getData().end());
+}
+
+bool
+OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+ << " value. Invalid buffer size " << buf.size());
+ }
+ if (buf[0] == 1) {
+ return (true);
+ } else if (buf[0] == 0) {
+ return (false);
+ }
+ isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+ << " value. Invalid value " << static_cast<int>(buf[0]));
+}
+
+void
+OptionDataTypeUtil::writeBool(const bool value,
+ std::vector<uint8_t>& buf) {
+ buf.push_back(static_cast<uint8_t>(value ? 1 : 0));
+}
+
+std::string
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+ // If buffer is empty emit an error.
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
+ << " The buffer is empty.");
+ }
+ // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN.
+ isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size());
+ try {
+ // Try to create an object from the buffer. If exception is thrown
+ // it means that the buffer doesn't hold a valid domain name (invalid
+ // syntax).
+ isc::dns::Name name(in_buf);
+ return (name.toText());
+ } catch (const isc::Exception& ex) {
+ // Unable to convert the data in the buffer into FQDN.
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf,
+ bool downcase) {
+ try {
+ isc::dns::Name name(fqdn, downcase);
+ isc::dns::LabelSequence labels(name);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ buf.insert(buf.end(), data, data + read_len);
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+unsigned int
+OptionDataTypeUtil::getLabelCount(const std::string& text_name) {
+ // The isc::dns::Name class doesn't accept empty names. However, in some
+ // cases we may be dealing with empty names (e.g. sent by the DHCP clients).
+ // Empty names should not be sent as hostnames but if they are, for some
+ // reason, we don't want to throw an exception from this function. We
+ // rather want to signal empty name by returning 0 number of labels.
+ if (text_name.empty()) {
+ return (0);
+ }
+ try {
+ isc::dns::Name name(text_name);
+ return (name.getLabelCount());
+ } catch (const isc::Exception& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+PrefixTuple
+OptionDataTypeUtil::readPrefix(const std::vector<uint8_t>& buf) {
+ // Prefix typically consists of the prefix length and the
+ // actual value. If prefix length is 0, the buffer length should
+ // be at least 1 byte to hold this length value.
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read prefix length from "
+ "a truncated buffer");
+ }
+
+ // Surround everything with try-catch to unify exceptions being
+ // thrown by various functions and constructors.
+ try {
+ // Try to create PrefixLen object from the prefix length held
+ // in the buffer. This may cause an exception if the length is
+ // invalid (greater than 128).
+ PrefixLen prefix_len(buf.at(0));
+
+ // Convert prefix length to bytes, because we operate on bytes,
+ // rather than bits.
+ uint8_t prefix_len_bytes = (prefix_len.asUint8() / 8);
+ // Check if we need to zero pad any bits. This is the case when
+ // the prefix length is not divisible by 8 (bits per byte). The
+ // calculations below may require some explanations. We first
+ // perform prefix_len % 8 to get the number of useful bits beyond
+ // the current prefix_len_bytes value. By substracting it from 8
+ // we get the number of zero padded bits, but with the special
+ // case of 8 when the result of substraction is 0. The value of
+ // 8 really means no padding so we make a modulo division once
+ // again to turn 8s to 0s.
+ const uint8_t zero_padded_bits =
+ static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+ // If there are zero padded bits, it means that we need an extra
+ // byte to be retrieved from the buffer.
+ if (zero_padded_bits > 0) {
+ ++prefix_len_bytes;
+ }
+
+ // Make sure that the buffer is long enough. We substract 1 to
+ // also account for the fact that the buffer includes a prefix
+ // length besides a prefix.
+ if ((buf.size() - 1) < prefix_len_bytes) {
+ isc_throw(BadDataTypeCast, "unable to read a prefix having length of "
+ << prefix_len.asUnsigned() << " from a truncated buffer");
+ }
+
+ // It is possible for a prefix to be zero if the prefix length
+ // is zero.
+ IOAddress prefix(IOAddress::IPV6_ZERO_ADDRESS());
+
+ // If there is anything more than prefix length is this buffer
+ // we need to read it.
+ if (buf.size() > 1) {
+ // Buffer has to be copied, because we will modify its
+ // contents by setting certain bits to 0, if necessary.
+ std::vector<uint8_t> prefix_buf(buf.begin() + 1, buf.end());
+ // All further conversions require that the buffer length is
+ // 16 bytes.
+ if (prefix_buf.size() < V6ADDRESS_LEN) {
+ prefix_buf.resize(V6ADDRESS_LEN);
+ if (prefix_len_bytes < prefix_buf.size()) {
+ // Zero all bits in the buffer beyond prefix length
+ // position.
+ std::fill(prefix_buf.begin() + prefix_len_bytes,
+ prefix_buf.end(), 0);
+
+ if (zero_padded_bits) {
+ // There is a byte that require zero padding. We
+ // achieve that by shifting the value of that byte
+ // back and forth by the number of zeroed bits.
+ prefix_buf.at(prefix_len_bytes - 1) =
+ (prefix_buf.at(prefix_len_bytes - 1)
+ >> zero_padded_bits)
+ << zero_padded_bits;
+ }
+ }
+ }
+ // Convert the buffer to the IOAddress object.
+ prefix = IOAddress::fromBytes(AF_INET6, &prefix_buf[0]);
+ }
+
+ return (std::make_pair(prefix_len, prefix));
+
+ } catch (const BadDataTypeCast& ex) {
+ // Pass through the BadDataTypeCast exceptions.
+ throw;
+
+ } catch (const std::exception& ex) {
+ // If an exception of a different type has been thrown, insert
+ // a text that indicates that the failure occurred during reading
+ // the prefix and modify exception type to BadDataTypeCast.
+ isc_throw(BadDataTypeCast, "unable to read a prefix from a buffer: "
+ << ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writePrefix(const PrefixLen& prefix_len,
+ const IOAddress& prefix,
+ std::vector<uint8_t>& buf) {
+ // Prefix must be an IPv6 prefix.
+ if (!prefix.isV6()) {
+ isc_throw(BadDataTypeCast, "illegal prefix value "
+ << prefix);
+ }
+
+ // We don't need to validate the prefix_len value, because it is
+ // already validated by the PrefixLen class.
+ buf.push_back(prefix_len.asUint8());
+
+ // Convert the prefix length to a number of bytes.
+ uint8_t prefix_len_bytes = prefix_len.asUint8() / 8;
+ // Check if there are any bits that require zero padding. See the
+ // commentary in readPrefix to see how this is calculated.
+ const uint8_t zero_padded_bits =
+ static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+ // If zero padding is needed it means that we need to extend the
+ // buffer to hold the "partially occupied" byte.
+ if (zero_padded_bits > 0) {
+ ++prefix_len_bytes;
+ }
+
+ // Convert the prefix to byte representation and append it to
+ // our output buffer.
+ std::vector<uint8_t> prefix_bytes = prefix.toBytes();
+ buf.insert(buf.end(), prefix_bytes.begin(),
+ prefix_bytes.begin() + prefix_len_bytes);
+ // If the last byte requires zero padding we achieve that by shifting
+ // bits back and forth by the number of insignificant bits.
+ if (zero_padded_bits) {
+ *buf.rbegin() = (*buf.rbegin() >> zero_padded_bits) << zero_padded_bits;
+ }
+}
+
+PSIDTuple
+OptionDataTypeUtil::readPsid(const std::vector<uint8_t>& buf) {
+ if (buf.size() < 3) {
+ isc_throw(BadDataTypeCast, "unable to read PSID from the buffer."
+ << " Invalid buffer size " << buf.size()
+ << ". Expected 3 bytes (PSID length and PSID value)");
+ }
+
+ // Read PSID length.
+ uint8_t psid_len = buf[0];
+
+ // PSID length must not be greater than 16 bits.
+ if (psid_len > (sizeof(uint16_t) * 8)) {
+ isc_throw(BadDataTypeCast, "invalid PSID length value "
+ << static_cast<unsigned>(psid_len)
+ << ", this value is expected to be in range of 0 to 16");
+ }
+
+ // Read two bytes of PSID value.
+ uint16_t psid = isc::util::readUint16(&buf[1], 2);
+
+ // We need to check that the PSID value does not exceed the maximum value
+ // for a specified PSID length. That means that all bits placed further than
+ // psid_len from the left must be set to 0.
+ // The value 0 is a special case because the RFC explicitly says that the
+ // PSID value should be ignored if psid_len is 0.
+ if ((psid & ~psid_bitmask[psid_len]) != 0) {
+ isc_throw(BadDataTypeCast, "invalid PSID value " << psid
+ << " for a specified PSID length "
+ << static_cast<unsigned>(psid_len));
+ }
+
+ // All is good, so we can convert the PSID value read from the buffer to
+ // the port set number.
+ if (psid_len == 0) {
+ // Shift by 16 always gives zero (CID 1398333)
+ psid = 0;
+ } else {
+ psid >>= (sizeof(psid) * 8 - psid_len);
+ }
+ return (std::make_pair(PSIDLen(psid_len), PSID(psid)));
+}
+
+void
+OptionDataTypeUtil::writePsid(const PSIDLen& psid_len, const PSID& psid,
+ std::vector<uint8_t>& buf) {
+ if (psid_len.asUint8() > (sizeof(psid) * 8)) {
+ isc_throw(BadDataTypeCast, "invalid PSID length value "
+ << psid_len.asUnsigned()
+ << ", this value is expected to be in range of 0 to 16");
+ }
+
+ if ((psid_len.asUint8() > 0) &&
+ (psid.asUint16() > (0xFFFF >> (sizeof(uint16_t) * 8 - psid_len.asUint8())))) {
+ isc_throw(BadDataTypeCast, "invalid PSID value " << psid.asUint16()
+ << " for a specified PSID length "
+ << psid_len.asUnsigned());
+ }
+
+ buf.resize(buf.size() + 3);
+ buf.at(buf.size() - 3) = psid_len.asUint8();
+ isc::util::writeUint16(static_cast<uint16_t>
+ (psid.asUint16() << (sizeof(uint16_t) * 8 - psid_len.asUint8())),
+ &buf[buf.size() - 2], 2);
+}
+
+std::string
+OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
+ std::string value;
+ if (!buf.empty()) {
+ // Per RFC 2132, section 2 we need to drop trailing NULLs
+ auto begin = buf.begin();
+ auto end = util::str::seekTrimmed(begin, buf.end(), 0x0);
+ if (std::distance(begin, end) == 0) {
+ isc_throw(isc::OutOfRange, "string value carried by the option "
+ "contained only NULLs");
+ }
+
+ value.insert(value.end(), begin, end);
+ }
+
+ return (value);
+}
+
+void
+OptionDataTypeUtil::writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ if (value.size() > 0) {
+ buf.insert(buf.end(), value.begin(), value.end());
+ }
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
new file mode 100644
index 0000000..4a941ed
--- /dev/null
+++ b/src/lib/dhcp/option_data_types.h
@@ -0,0 +1,678 @@
+// Copyright (C) 2012-2023 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 OPTION_DATA_TYPES_H
+#define OPTION_DATA_TYPES_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
+
+#include <stdint.h>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid type specified as template parameter.
+class InvalidDataType : public Exception {
+public:
+ InvalidDataType(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception to be thrown when cast to the data type was unsuccessful.
+class BadDataTypeCast : public Exception {
+public:
+ BadDataTypeCast(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Data types of DHCP option fields.
+///
+/// @warning The order of data types matters: OPT_UNKNOWN_TYPE
+/// must always be the last position. Also, OPT_RECORD_TYPE
+/// must be at last but one position. This is because some
+/// functions perform sanity checks on data type values using
+/// '>' operators, assuming that all values beyond the
+/// OPT_RECORD_TYPE are invalid.
+enum OptionDataType {
+ OPT_EMPTY_TYPE,
+ OPT_BINARY_TYPE,
+ OPT_BOOLEAN_TYPE,
+ OPT_INT8_TYPE,
+ OPT_INT16_TYPE,
+ OPT_INT32_TYPE,
+ OPT_UINT8_TYPE,
+ OPT_UINT16_TYPE,
+ OPT_UINT32_TYPE,
+ OPT_ANY_ADDRESS_TYPE,
+ OPT_IPV4_ADDRESS_TYPE,
+ OPT_IPV6_ADDRESS_TYPE,
+ OPT_IPV6_PREFIX_TYPE,
+ OPT_PSID_TYPE,
+ OPT_STRING_TYPE,
+ OPT_TUPLE_TYPE,
+ OPT_FQDN_TYPE,
+ OPT_RECORD_TYPE,
+ OPT_UNKNOWN_TYPE
+};
+
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+ const char* name; // option name
+ uint16_t code; // option code
+ const char* space; // option space
+ OptionDataType type; // data type
+ bool array; // is array
+ const OptionDataType* records; // record fields
+ size_t records_size; // number of fields in a record
+ const char* encapsulates; // option space encapsulated by the
+ // particular option.
+};
+
+/// @brief Encapsulation of option definition parameters and the structure size.
+struct OptionDefParamsEncapsulation {
+ const struct OptionDefParams* optionDefParams; // parameters structure
+ const int size; // structure size
+ const char* space; // option space
+};
+
+/// @brief Trait class for data types supported in DHCP option definitions.
+///
+/// This is useful to check whether the type specified as template parameter
+/// is supported by classes like OptionInt, OptionIntArray and some template
+/// factory functions in OptionDefinition class.
+template<typename T>
+struct OptionDataTypeTraits {
+ static const bool valid = false;
+ static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_UNKNOWN_TYPE;
+};
+
+/// binary type is supported
+template<>
+struct OptionDataTypeTraits<OptionBuffer> {
+ static const bool valid = true;
+ static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_BINARY_TYPE;
+};
+
+/// bool type is supported
+template<>
+struct OptionDataTypeTraits<bool> {
+ static const bool valid = true;
+ static const int len = sizeof(uint8_t);
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_BOOLEAN_TYPE;
+};
+
+/// int8_t type is supported.
+template<>
+struct OptionDataTypeTraits<int8_t> {
+ static const bool valid = true;
+ static const int len = 1;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_INT8_TYPE;
+};
+
+/// int16_t type is supported.
+template<>
+struct OptionDataTypeTraits<int16_t> {
+ static const bool valid = true;
+ static const int len = 2;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_INT16_TYPE;
+};
+
+/// int32_t type is supported.
+template<>
+struct OptionDataTypeTraits<int32_t> {
+ static const bool valid = true;
+ static const int len = 4;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_INT32_TYPE;
+};
+
+/// uint8_t type is supported.
+template<>
+struct OptionDataTypeTraits<uint8_t> {
+ static const bool valid = true;
+ static const int len = 1;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_UINT8_TYPE;
+};
+
+/// uint16_t type is supported.
+template<>
+struct OptionDataTypeTraits<uint16_t> {
+ static const bool valid = true;
+ static const int len = 2;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_UINT16_TYPE;
+};
+
+/// uint32_t type is supported.
+template<>
+struct OptionDataTypeTraits<uint32_t> {
+ static const bool valid = true;
+ static const int len = 4;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_UINT32_TYPE;
+};
+
+/// IPv4 and IPv6 address type is supported
+template<>
+struct OptionDataTypeTraits<asiolink::IOAddress> {
+ static const bool valid = true;
+ // The len value is used to determine the size of the data
+ // to be written to an option buffer. IOAddress object may
+ // either represent an IPv4 or IPv6 addresses which have
+ // different lengths. Thus we can't put fixed value here.
+ // The length of a data to be written into an option buffer
+ // have to be determined in the runtime for a particular
+ // IOAddress object. Thus setting len to zero.
+ static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_ANY_ADDRESS_TYPE;
+};
+
+/// string type is supported
+template<>
+struct OptionDataTypeTraits<std::string> {
+ static const bool valid = true;
+ // The len value is used to determine the size of the data
+ // to be written to an option buffer. For strings this
+ // size is unknown until we actually deal with the particular
+ // string to be written. Thus setting it to zero.
+ static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_STRING_TYPE;
+};
+
+/// @brief Encapsulates PSID length.
+class PSIDLen {
+public:
+
+ /// @brief Default constructor.
+ PSIDLen() : psid_len_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// It checks that the specified value is not greater than
+ /// 16, which is a maximum value for the PSID length.
+ ///
+ /// @param psid_len PSID length.
+ /// @throw isc::OutOfRange If specified PSID length is greater than 16.
+ explicit PSIDLen(const uint8_t psid_len)
+ : psid_len_(psid_len) {
+ if (psid_len_ > sizeof(uint16_t) * 8) {
+ isc_throw(isc::OutOfRange, "invalid value "
+ << asUnsigned() << " of PSID length");
+ }
+ }
+
+ /// @brief Returns PSID length as uint8_t value.
+ uint8_t asUint8() const {
+ return (psid_len_);
+ }
+
+ /// @brief Returns PSID length as unsigned int.
+ ///
+ /// This is useful to convert the value to a numeric type which
+ /// can be logged directly. Note that the uint8_t value has to
+ /// be cast to an integer value to be logged as a number. This
+ /// is because the uint8_t is often implemented as char, in which
+ /// case directly logging an uint8_t value prints a character rather
+ /// than a number.
+ unsigned int asUnsigned() const {
+ return (static_cast<unsigned>(psid_len_));
+ }
+
+private:
+
+ /// @brief PSID length.
+ uint8_t psid_len_;
+};
+
+/// @brief Encapsulates PSID value.
+class PSID {
+public:
+
+ /// @brief Default constructor.
+ PSID() : psid_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor doesn't perform any checks on the input data.
+ ///
+ /// @param psid PSID value.
+ explicit PSID(const uint16_t psid)
+ : psid_(psid) {
+ }
+
+ /// @brief Returns PSID value as a number.
+ uint16_t asUint16() const {
+ return (psid_);
+ }
+
+private:
+
+ /// @brief PSID value.
+ uint16_t psid_;
+
+};
+
+/// @brief Defines a pair of PSID length / value.
+typedef std::pair<PSIDLen, PSID> PSIDTuple;
+
+/// @brief Encapsulates prefix length.
+class PrefixLen {
+public:
+
+ /// @brief Default constructor.
+ PrefixLen() : prefix_len_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor checks if the specified prefix length is
+ /// in the range of 0 to 128.
+ ///
+ /// @param prefix_len Prefix length value.
+ /// @throw isc::OutOfRange If specified prefix length is greater than 128.
+ explicit PrefixLen(const uint8_t prefix_len)
+ : prefix_len_(prefix_len) {
+ }
+
+ /// @brief Returns prefix length as uint8_t value.
+ uint8_t asUint8() const {
+ return (prefix_len_);
+ }
+
+ /// @brief Returns prefix length as unsigned int.
+ ///
+ /// This is useful to convert the value to a numeric type which
+ /// can be logged directly. See @ref PSIDLen::asUnsigned for the
+ /// use cases of this accessor.
+ unsigned int asUnsigned() const {
+ return (static_cast<unsigned>(prefix_len_));
+ }
+
+private:
+
+ /// @brief Prefix length.
+ uint8_t prefix_len_;
+};
+
+/// @brief Defines a pair of prefix length / value.
+typedef std::pair<PrefixLen, asiolink::IOAddress> PrefixTuple;
+
+/// @brief Utility class for option data types.
+///
+/// This class provides a set of utility functions to operate on
+/// supported DHCP option data types. It includes conversion
+/// between enumerator values representing data types and data
+/// type names. It also includes a set of functions that write
+/// data into option buffers and read data from option buffers.
+/// The data being written and read are converted from/to actual
+/// data types.
+/// @note This is a singleton class but it can be accessed via
+/// static methods only.
+class OptionDataTypeUtil {
+public:
+
+ /// @brief Return option data type from its name.
+ ///
+ /// @param data_type data type name.
+ /// @return option data type.
+ static OptionDataType getDataType(const std::string& data_type);
+
+ /// @brief Return option data type name from the data type enumerator.
+ ///
+ /// @param data_type option data type.
+ /// @return option data type name.
+ static const std::string& getDataTypeName(const OptionDataType data_type);
+
+ /// @brief Get data type buffer length.
+ ///
+ /// This function returns the size of a particular data type.
+ /// Values returned by this function correspond to the data type
+ /// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and
+ /// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate
+ /// the fixed length of the data being written into the buffer,
+ /// not the size of the particular data type. Thus for data types
+ /// such as string, binary etc. for which the buffer length can't
+ /// be determined this function returns 0.
+ /// In addition, this function returns the data sizes for
+ /// IPV4_ADDRESS_TYPE and IPV6_ADDRESS_TYPE as their buffer
+ /// representations have fixed data lengths: 4 and 16 respectively.
+ ///
+ /// @param data_type data type which size is to be returned.
+ /// @return data type size or zero for variable length types.
+ static int getDataTypeLen(const OptionDataType data_type);
+
+ /// @brief Read IPv4 or IPv6 address from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @param family address family: AF_INET or AF_INET6.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
+ /// @return address being read.
+ static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
+ const short family);
+
+ /// @brief Append IPv4 or IPv6 address to a buffer.
+ ///
+ /// @param address IPv4 or IPv6 address.
+ /// @param [out] buf output buffer.
+ static void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Append hex-encoded binary values to a buffer.
+ ///
+ /// @param hex_str string representing a binary value encoded
+ /// with hexadecimal digits (without 0x prefix).
+ /// @param [out] buf output buffer.
+ static void writeBinary(const std::string& hex_str,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Read length and string tuple from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6)
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
+ /// @return string being read.
+ static std::string readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype);
+
+ /// @brief Read length and string tuple from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @param tuple reference of the tuple to read into
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
+ static void readTuple(const std::vector<uint8_t>& buf,
+ OpaqueDataTuple& tuple);
+
+ /// @brief Append length and string tuple to a buffer
+ ///
+ /// @param value length and string tuple
+ /// @param lengthfieldtype LENGTH_1_BYTE (DHCPv4) or LENGTH_2_BYTES (DHCPv6)
+ /// @param [out] buf output buffer.
+ static void writeTuple(const std::string& value,
+ OpaqueDataTuple::LengthFieldType lengthfieldtype,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Append length and string tuple to a buffer
+ ///
+ /// @param tuple length and string tuple
+ /// @param [out] buf output buffer.
+ static void writeTuple(const OpaqueDataTuple& tuple,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Returns Length Field Type for a tuple.
+ ///
+ /// Returns Length Field Type for a tuple basing on the given
+ /// Option v4/v6 Universe.
+ ///
+ /// @param u specifies universe (V4 or V6)
+ /// @return By default 1 octet Length Field Type for V4 option
+ /// or 2 octets Length Field Type for V6 option
+ static OpaqueDataTuple::LengthFieldType getTupleLenFieldType(Option::Universe u);
+
+ /// @brief Read boolean value from a buffer.
+ ///
+ /// @param buf input buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated or the value is invalid (neither 1 nor 0).
+ /// @return boolean value read from a buffer.
+ static bool readBool(const std::vector<uint8_t>& buf);
+
+ /// @brief Append boolean value into a buffer.
+ ///
+ /// The bool value is encoded in a buffer in such a way that
+ /// "1" means "true" and "0" means "false".
+ ///
+ /// @param value boolean value to be written.
+ /// @param [out] buf output buffer.
+ static void writeBool(const bool value, std::vector<uint8_t>& buf);
+
+ /// @brief Read integer value from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @tparam integer type of the returned value.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer
+ /// is truncated.
+ /// @return integer value being read.
+ template<typename T>
+ static T readInt(const std::vector<uint8_t>& buf) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
+ " by readInteger is unsupported integer type");
+ }
+
+ if (buf.size() < OptionDataTypeTraits<T>::len) {
+ isc_throw(isc::dhcp::BadDataTypeCast,
+ "failed to read an integer value from a buffer"
+ << " - buffer is truncated.");
+ }
+
+ T value;
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ value = *(buf.begin());
+ break;
+ case 2:
+ // Calling readUint16 works either for unsigned
+ // or signed types.
+ value = isc::util::readUint16(&(*buf.begin()), buf.size());
+ break;
+ case 4:
+ // Calling readUint32 works either for unsigned
+ // or signed types.
+ value = isc::util::readUint32(&(*buf.begin()), buf.size());
+ break;
+ default:
+ // This should not happen because we made checks on data types
+ // but it does not hurt to keep throw statement here.
+ isc_throw(isc::dhcp::InvalidDataType,
+ "invalid size of the data type to be read as integer.");
+ }
+ return (value);
+ }
+
+ /// @brief Append integer or unsigned integer value to a buffer.
+ ///
+ /// @param value an integer value to be written into a buffer.
+ /// @param [out] buf output buffer.
+ /// @tparam data type of the value.
+ template<typename T>
+ static void writeInt(const T value,
+ std::vector<uint8_t>& buf) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(InvalidDataType, "provided data type is not the supported.");
+ }
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ buf.push_back(static_cast<uint8_t>(value));
+ break;
+ case 2:
+ buf.resize(buf.size() + 2);
+ isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2], 2);
+ break;
+ case 4:
+ buf.resize(buf.size() + 4);
+ isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4], 4);
+ break;
+ default:
+ // The cases above cover whole range of possible data lengths because
+ // we check at the beginning of this function that given data type is
+ // a supported integer type which can be only 1,2 or 4 bytes long.
+ ;
+ }
+ }
+
+ /// @brief Read FQDN from a buffer as a string value.
+ ///
+ /// The format of an FQDN within a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param buf input buffer holding a FQDN.
+ ///
+ /// @throw BadDataTypeCast if a FQDN stored within a buffer is
+ /// invalid (e.g. empty, contains invalid characters, truncated).
+ /// @return fully qualified domain name in a text form.
+ static std::string readFqdn(const std::vector<uint8_t>& buf);
+
+ /// @brief Append FQDN into a buffer.
+ ///
+ /// This method appends the Fully Qualified Domain Name (FQDN)
+ /// represented as string value into a buffer. The format of
+ /// the FQDN being stored into a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param fqdn fully qualified domain name to be written.
+ /// @param [out] buf output buffer.
+ /// @param downcase indicates if the FQDN should be converted to lower
+ /// case (if true). By default it is not converted.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
+ /// is invalid.
+ static void writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf,
+ const bool downcase = false);
+
+ /// @brief Return the number of labels in the Name.
+ ///
+ /// If the specified name is empty the 0 is returned.
+ ///
+ /// @param text_name A text representation of the name.
+ ///
+ /// @return A number of labels in the provided name or 0 if the
+ /// name string is empty.
+ /// @throw isc::dhcp::BadDataTypeCast if provided name is malformed.
+ static unsigned int getLabelCount(const std::string& text_name);
+
+ /// @brief Read prefix from a buffer.
+ ///
+ /// This method reads prefix length and a prefix value from a buffer.
+ /// The prefix value has variable length and this length is determined
+ /// from the first byte of the buffer. If the length is not divisible
+ /// by 8, the prefix is padded with zeros to the next byte boundary.
+ ///
+ /// @param buf input buffer holding a prefix length / prefix tuple.
+ ///
+ /// @return Prefix length and value.
+ static PrefixTuple readPrefix(const std::vector<uint8_t>& buf);
+
+ /// @brief Append prefix into a buffer.
+ ///
+ /// This method writes prefix length (1 byte) followed by a variable
+ /// length prefix.
+ ///
+ /// @param prefix_len Prefix length in bits (0 to 128).
+ /// @param prefix Prefix value.
+ /// @param [out] buf Output buffer.
+ static void writePrefix(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Read PSID length / value tuple from a buffer.
+ ///
+ /// This method reads three bytes from a buffer. The first byte
+ /// holds a PSID length value. The remaining two bytes contain a
+ /// zero padded PSID value.
+ ///
+ /// @return PSID length / value tuple.
+ /// @throw isc::dhcp::BadDataTypeCast if PSID length or value held
+ /// in the buffer is incorrect or the buffer is truncated.
+ static PSIDTuple readPsid(const std::vector<uint8_t>& buf);
+
+ /// @brief Append PSID length/value into a buffer.
+ ///
+ /// This method appends 1 byte of PSID length and 2 bytes of PSID
+ /// value into a buffer. The PSID value contains a PSID length
+ /// number of significant bits, followed by 16 - PSID length
+ /// zero bits.
+ ///
+ /// @param psid_len PSID length in the range of 0 to 16 holding the
+ /// number of significant bits within the PSID value.
+ /// @param psid PSID value, where the lowest value is 0, and the
+ /// highest value is 2^(PSID length)-1.
+ /// @param [out] buf output buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if specified psid_len or
+ /// psid value is incorrect.
+ static void writePsid(const PSIDLen& psid_len, const PSID& psid,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Read string value from a buffer.
+ ///
+ /// To be compliant with RFC 2132, Sec. 2, trailing NULLs are trimmed.
+ /// @param buf input buffer.
+ ///
+ /// @return string value being read.
+ /// @throw isc::dhcp::OutOfRange is the payload contains only NULLs.
+ static std::string readString(const std::vector<uint8_t>& buf);
+
+ /// @brief Write UTF8-encoded string into a buffer.
+ ///
+ /// @param value string value to be written into a buffer.
+ /// @param [out] buf output buffer.
+ static void writeString(const std::string& value,
+ std::vector<uint8_t>& buf);
+private:
+
+ /// The container holding mapping of data type names to
+ /// data types enumerator.
+ std::map<std::string, OptionDataType> data_types_;
+
+ /// The container holding mapping of data types to data
+ /// type names.
+ std::map<OptionDataType, std::string> data_type_names_;
+
+ /// @brief Private constructor.
+ ///
+ /// This constructor is private because this class should
+ /// be used as singleton (through static public functions).
+ OptionDataTypeUtil();
+
+ /// @brief Return instance of OptionDataTypeUtil
+ ///
+ /// This function is used by some of the public static functions
+ /// to create an instance of OptionDataTypeUtil class.
+ /// When this instance is called it calls the classes constructor
+ /// and initializes some of the private data members.
+ ///
+ /// @return instance of OptionDataTypeUtil singleton.
+ static OptionDataTypeUtil& instance();
+
+ /// @brief Return option data type from its name.
+ ///
+ /// @param data_type data type name.
+ /// @return option data type.
+ OptionDataType getDataTypeImpl(const std::string& data_type) const;
+
+ /// @brief Return option data type name from the data type enumerator.
+ ///
+ /// @param data_type option data type.
+ /// @return option data type name.
+ const std::string& getDataTypeNameImpl(const OptionDataType data_type) const;
+};
+
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_DATA_TYPES_H
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
new file mode 100644
index 0000000..847e4b0
--- /dev/null
+++ b/src/lib/dhcp/option_definition.cc
@@ -0,0 +1,916 @@
+// Copyright (C) 2012-2023 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option4_dnr.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_dnr.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <util/encode/hex.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
+#include <util/strutil.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/dynamic_bitset.hpp>
+#include <boost/make_shared.hpp>
+#include <sstream>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+OptionDefinition::OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const bool array_type /* = false */)
+ : name_(name),
+ code_(code),
+ type_(OPT_UNKNOWN_TYPE),
+ array_type_(array_type),
+ encapsulated_space_(""),
+ record_fields_(),
+ user_context_(),
+ option_space_name_(space) {
+ // Data type is held as enum value by this class.
+ // Use the provided option type string to get the
+ // corresponding enum value.
+ type_ = OptionDataTypeUtil::getDataType(type);
+}
+
+OptionDefinition::OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const bool array_type /* = false */)
+ : name_(name),
+ code_(code),
+ type_(type),
+ array_type_(array_type),
+ encapsulated_space_(""),
+ option_space_name_(space){
+}
+
+OptionDefinition::OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const char* encapsulated_space)
+ : name_(name),
+ code_(code),
+ // Data type is held as enum value by this class.
+ // Use the provided option type string to get the
+ // corresponding enum value.
+ type_(OptionDataTypeUtil::getDataType(type)),
+ array_type_(false),
+ encapsulated_space_(encapsulated_space),
+ record_fields_(),
+ user_context_(),
+ option_space_name_(space) {
+}
+
+OptionDefinition::OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const char* encapsulated_space)
+ : name_(name),
+ code_(code),
+ type_(type),
+ array_type_(false),
+ encapsulated_space_(encapsulated_space),
+ record_fields_(),
+ user_context_(),
+ option_space_name_(space) {
+}
+
+OptionDefinitionPtr
+OptionDefinition::create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const bool array_type) {
+ return (boost::make_shared<OptionDefinition>(name, code, space, type, array_type));
+}
+
+OptionDefinitionPtr
+OptionDefinition::create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const bool array_type) {
+ return (boost::make_shared<OptionDefinition>(name, code, space, type, array_type));
+}
+
+OptionDefinitionPtr
+OptionDefinition::create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const char* encapsulated_space) {
+ return (boost::make_shared<OptionDefinition>(name, code, space, type, encapsulated_space));
+}
+
+OptionDefinitionPtr
+OptionDefinition::create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const char* encapsulated_space) {
+ return (boost::make_shared<OptionDefinition>(name, code, space, type, encapsulated_space));
+}
+
+bool
+OptionDefinition::equals(const OptionDefinition& other) const {
+ return (name_ == other.name_ &&
+ code_ == other.code_ &&
+ type_ == other.type_ &&
+ array_type_ == other.array_type_ &&
+ encapsulated_space_ == other.encapsulated_space_ &&
+ record_fields_ == other.record_fields_ &&
+ option_space_name_ == other.option_space_name_);
+}
+
+void
+OptionDefinition::addRecordField(const std::string& data_type_name) {
+ OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name);
+ addRecordField(data_type);
+}
+
+void
+OptionDefinition::addRecordField(const OptionDataType data_type) {
+ if (type_ != OPT_RECORD_TYPE) {
+ isc_throw(isc::InvalidOperation,
+ "'record' option type must be used instead of '"
+ << OptionDataTypeUtil::getDataTypeName(type_)
+ << "' to add data fields to the record");
+ }
+ if (data_type >= OPT_RECORD_TYPE ||
+ data_type == OPT_ANY_ADDRESS_TYPE ||
+ data_type == OPT_EMPTY_TYPE) {
+ isc_throw(isc::BadValue,
+ "attempted to add invalid data type '"
+ << OptionDataTypeUtil::getDataTypeName(data_type)
+ << "' to the record.");
+ }
+ record_fields_.push_back(data_type);
+}
+
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const {
+
+ try {
+ // Some of the options are represented by the specialized classes derived
+ // from Option class (e.g. IA_NA, IAADDR). Although, they can be also
+ // represented by the generic classes, we want the object of the specialized
+ // type to be returned. Therefore, we first check that if we are dealing
+ // with such an option. If the instance is returned we just exit at this
+ // point. If not, we will search for a generic option type to return.
+ OptionPtr option = factorySpecialFormatOption(u, begin, end);
+ if (option) {
+ return (option);
+ }
+
+ switch (type_) {
+ case OPT_EMPTY_TYPE:
+ if (getEncapsulatedSpace().empty()) {
+ return (factoryEmpty(u, type));
+ } else {
+ return (OptionPtr(new OptionCustom(*this, u, begin, end)));
+ }
+
+ case OPT_BINARY_TYPE:
+ return (factoryGeneric(u, type, begin, end));
+
+ case OPT_UINT8_TYPE:
+ return (array_type_ ?
+ factoryIntegerArray<uint8_t>(u, type, begin, end) :
+ factoryInteger<uint8_t>(u, type, getEncapsulatedSpace(),
+ begin, end));
+
+ case OPT_INT8_TYPE:
+ return (array_type_ ?
+ factoryIntegerArray<int8_t>(u, type, begin, end) :
+ factoryInteger<int8_t>(u, type, getEncapsulatedSpace(),
+ begin, end));
+
+ case OPT_UINT16_TYPE:
+ return (array_type_ ?
+ factoryIntegerArray<uint16_t>(u, type, begin, end) :
+ factoryInteger<uint16_t>(u, type, getEncapsulatedSpace(),
+ begin, end));
+
+ case OPT_INT16_TYPE:
+ return (array_type_ ?
+ factoryIntegerArray<uint16_t>(u, type, begin, end) :
+ factoryInteger<int16_t>(u, type, getEncapsulatedSpace(),
+ begin, end));
+
+ case OPT_UINT32_TYPE:
+ return (array_type_ ?
+ factoryIntegerArray<uint32_t>(u, type, begin, end) :
+ factoryInteger<uint32_t>(u, type, getEncapsulatedSpace(),
+ begin, end));
+
+ case OPT_INT32_TYPE:
+ return (array_type_ ?
+ factoryIntegerArray<uint32_t>(u, type, begin, end) :
+ factoryInteger<int32_t>(u, type, getEncapsulatedSpace(),
+ begin, end));
+
+ case OPT_IPV4_ADDRESS_TYPE:
+ // If definition specifies that an option is an array
+ // of IPv4 addresses we return an instance of specialized
+ // class (OptionAddrLst4). For non-array types there is no
+ // specialized class yet implemented so we drop through
+ // to return an instance of OptionCustom.
+ if (array_type_) {
+ return (factoryAddrList4(type, begin, end));
+ }
+ break;
+
+ case OPT_IPV6_ADDRESS_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
+ if (array_type_) {
+ return (factoryAddrList6(type, begin, end));
+ }
+ break;
+
+ case OPT_STRING_TYPE:
+ return (OptionPtr(new OptionString(u, type, begin, end)));
+
+ case OPT_TUPLE_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
+ if (array_type_) {
+ return (factoryOpaqueDataTuples(u, type, begin, end));
+ }
+ break;
+
+ default:
+ // Do nothing. We will return generic option a few lines down.
+ ;
+ }
+ return (OptionPtr(new OptionCustom(*this, u, begin, end)));
+ } catch (const SkipThisOptionError&) {
+ // We need to throw this one as is.
+ throw;
+ } catch (const SkipRemainingOptionsError&) {
+ // We need to throw this one as is.
+ throw;
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOptionValue, ex.what());
+ }
+}
+
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+ const OptionBuffer& buf) const {
+ return (optionFactory(u, type, buf.begin(), buf.end()));
+}
+
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+ const std::vector<std::string>& values) const {
+ OptionBuffer buf;
+ if (!array_type_ && type_ != OPT_RECORD_TYPE) {
+ if (values.empty()) {
+ if (type_ != OPT_EMPTY_TYPE) {
+ isc_throw(InvalidOptionValue, "no option value specified");
+ }
+ } else {
+ writeToBuffer(u, util::str::trim(values[0]), type_, buf);
+ }
+ } else if (array_type_ && type_ != OPT_RECORD_TYPE) {
+ for (size_t i = 0; i < values.size(); ++i) {
+ writeToBuffer(u, util::str::trim(values[i]), type_, buf);
+ }
+ } else if (type_ == OPT_RECORD_TYPE) {
+ const RecordFieldsCollection& records = getRecordFields();
+ if (records.size() > values.size()) {
+ isc_throw(InvalidOptionValue, "number of data fields for the option"
+ << " type '" << getCode() << "' is greater than number"
+ << " of values provided.");
+ }
+ for (size_t i = 0; i < records.size(); ++i) {
+ writeToBuffer(u, util::str::trim(values[i]), records[i], buf);
+ }
+ if (array_type_ && (values.size() > records.size())) {
+ for (size_t i = records.size(); i < values.size(); ++i) {
+ writeToBuffer(u, util::str::trim(values[i]),
+ records.back(), buf);
+ }
+ }
+ }
+ return (optionFactory(u, type, buf.begin(), buf.end()));
+}
+
+void
+OptionDefinition::validate() const {
+
+ using namespace boost::algorithm;
+
+ std::ostringstream err_str;
+
+ // Allowed characters in the option name are: lower or
+ // upper case letters, digits, underscores and hyphens.
+ // Empty option spaces are not allowed.
+ if (!all(name_, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) ||
+ name_.empty() ||
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option name.
+ all(find_head(name_, 1), boost::is_any_of(std::string("-_"))) ||
+ all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
+ err_str << "invalid option name '" << name_ << "'";
+
+ } else if (!OptionSpace::validateName(option_space_name_)) {
+ err_str << "invalid option space name: '"
+ << option_space_name_ << "'";
+
+ } else if (!encapsulated_space_.empty() &&
+ !OptionSpace::validateName(encapsulated_space_)) {
+ err_str << "invalid encapsulated option space name: '"
+ << encapsulated_space_ << "'";
+
+ } else if (type_ >= OPT_UNKNOWN_TYPE) {
+ // Option definition must be of a known type.
+ err_str << "option type " << type_ << " not supported.";
+
+ } else if (type_ == OPT_RECORD_TYPE) {
+ // At least two data fields should be added to the record. Otherwise
+ // non-record option definition could be used.
+ if (getRecordFields().size() < 2) {
+ err_str << "invalid number of data fields: "
+ << getRecordFields().size()
+ << " specified for the option of type 'record'. Expected at"
+ << " least 2 fields.";
+
+ } else {
+ // If the number of fields is valid we have to check if their order
+ // is valid too. We check that string or binary data fields are not
+ // laid before other fields. But we allow that they are laid at the
+ // end of an option.
+ const RecordFieldsCollection& fields = getRecordFields();
+ for (RecordFieldsConstIter it = fields.begin();
+ it != fields.end(); ++it) {
+ if (*it == OPT_STRING_TYPE &&
+ it < fields.end() - 1) {
+ err_str << "string data field can't be laid before data"
+ << " fields of other types.";
+ break;
+ }
+ if (*it == OPT_BINARY_TYPE &&
+ it < fields.end() - 1) {
+ err_str << "binary data field can't be laid before data"
+ << " fields of other types.";
+ break;
+ }
+ // Empty type is not allowed within a record.
+ if (*it == OPT_EMPTY_TYPE) {
+ err_str << "empty data type can't be stored as a field in"
+ << " an option record.";
+ break;
+ }
+ }
+ // If the array flag is set the last field is an array.
+ if (err_str.str().empty() && array_type_) {
+ const OptionDataType& last_type = fields.back();
+ if (last_type == OPT_STRING_TYPE) {
+ err_str
+ << "array of strings is not a valid option definition.";
+ } else if (last_type == OPT_BINARY_TYPE) {
+ err_str << "array of binary values is not a valid option "
+ "definition.";
+ }
+ // Empty type was already checked.
+ }
+ }
+
+ } else if (array_type_) {
+ if (type_ == OPT_STRING_TYPE) {
+ // Array of strings is not allowed because there is no way
+ // to determine the size of a particular string and thus there
+ // it no way to tell when other data fields begin.
+ err_str << "array of strings is not a valid option definition.";
+ } else if (type_ == OPT_BINARY_TYPE) {
+ err_str << "array of binary values is not"
+ << " a valid option definition.";
+
+ } else if (type_ == OPT_EMPTY_TYPE) {
+ err_str << "array of empty value is not"
+ << " a valid option definition.";
+
+ }
+ }
+
+ // Non-empty error string means that we have hit the error. We throw
+ // exception and include error string.
+ if (!err_str.str().empty()) {
+ isc_throw(MalformedOptionDefinition, err_str.str());
+ }
+}
+
+bool
+OptionDefinition::haveCompressedFqdnListFormat() const {
+ return (haveType(OPT_FQDN_TYPE) && getArrayType());
+}
+
+bool
+OptionDefinition::convertToBool(const std::string& value_str) const {
+ // Case-insensitive check that the input is one of: "true" or "false".
+ if (boost::iequals(value_str, "true")) {
+ return (true);
+
+ } else if (boost::iequals(value_str, "false")) {
+ return (false);
+
+ }
+
+ // The input string is neither "true" nor "false", so let's check
+ // if it is not an integer wrapped in a string.
+ int result;
+ try {
+ result = boost::lexical_cast<int>(value_str);
+
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(BadDataTypeCast, "unable to covert the value '"
+ << value_str << "' to boolean data type");
+ }
+ // The boolean value is encoded in DHCP option as 0 or 1. Therefore,
+ // we only allow a user to specify those values for options which
+ // have boolean fields.
+ if (result != 1 && result != 0) {
+ isc_throw(BadDataTypeCast, "unable to convert '" << value_str
+ << "' to boolean data type");
+ }
+ return (static_cast<bool>(result));
+}
+
+template<typename T>
+T
+OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
+ const {
+ // The lexical cast should be attempted when converting to an integer
+ // value only.
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(BadDataTypeCast,
+ "must not convert '" << value_str
+ << "' to non-integer data type");
+ }
+
+ // We use the 64-bit value here because it has wider range than
+ // any other type we use here and it allows to detect out of
+ // bounds conditions e.g. negative value specified for uintX_t
+ // data type. Obviously if the value exceeds the limits of int64
+ // this function will not handle that properly.
+ int64_t result = 0;
+ try {
+ result = boost::lexical_cast<int64_t>(value_str);
+
+ } catch (const boost::bad_lexical_cast&) {
+ // boost::lexical_cast does not handle hexadecimal
+ // but stringstream does so do it the hard way.
+ std::stringstream ss;
+ ss << std::hex << value_str;
+ ss >> result;
+ if (ss.fail() || !ss.eof()) {
+ isc_throw(BadDataTypeCast, "unable to convert the value '"
+ << value_str << "' to integer data type");
+ }
+ }
+ // Perform range checks.
+ if (OptionDataTypeTraits<T>::integer_type) {
+ if (result > numeric_limits<T>::max() ||
+ result < numeric_limits<T>::min()) {
+ isc_throw(BadDataTypeCast, "unable to convert '"
+ << value_str << "' to numeric type. This value is "
+ "expected to be in the range of "
+ << +numeric_limits<T>::min() << ".."
+ << +numeric_limits<T>::max());
+ }
+ }
+ return (static_cast<T>(result));
+}
+
+void
+OptionDefinition::writeToBuffer(Option::Universe u,
+ const std::string& value,
+ const OptionDataType type,
+ OptionBuffer& buf) const {
+ // We are going to write value given by value argument to the buffer.
+ // The actual type of the value is given by second argument. Check
+ // this argument to determine how to write this value to the buffer.
+ switch (type) {
+ case OPT_BINARY_TYPE:
+ OptionDataTypeUtil::writeBinary(value, buf);
+ return;
+ case OPT_BOOLEAN_TYPE:
+ // We encode the true value as 1 and false as 0 on 8 bits.
+ // That way we actually waste 7 bits but it seems to be the
+ // simpler way to encode boolean.
+ // @todo Consider if any other encode methods can be used.
+ OptionDataTypeUtil::writeBool(convertToBool(value), buf);
+ return;
+ case OPT_INT8_TYPE:
+ OptionDataTypeUtil::writeInt<uint8_t>
+ (lexicalCastWithRangeCheck<int8_t>(value),
+ buf);
+ return;
+ case OPT_INT16_TYPE:
+ OptionDataTypeUtil::writeInt<uint16_t>
+ (lexicalCastWithRangeCheck<int16_t>(value),
+ buf);
+ return;
+ case OPT_INT32_TYPE:
+ OptionDataTypeUtil::writeInt<uint32_t>
+ (lexicalCastWithRangeCheck<int32_t>(value),
+ buf);
+ return;
+ case OPT_UINT8_TYPE:
+ OptionDataTypeUtil::writeInt<uint8_t>
+ (lexicalCastWithRangeCheck<uint8_t>(value),
+ buf);
+ return;
+ case OPT_UINT16_TYPE:
+ OptionDataTypeUtil::writeInt<uint16_t>
+ (lexicalCastWithRangeCheck<uint16_t>(value),
+ buf);
+ return;
+ case OPT_UINT32_TYPE:
+ OptionDataTypeUtil::writeInt<uint32_t>
+ (lexicalCastWithRangeCheck<uint32_t>(value),
+ buf);
+ return;
+ case OPT_IPV4_ADDRESS_TYPE:
+ case OPT_IPV6_ADDRESS_TYPE:
+ {
+ asiolink::IOAddress address(value);
+ if (!address.isV4() && !address.isV6()) {
+ isc_throw(BadDataTypeCast, "provided address "
+ << address
+ << " is not a valid IPv4 or IPv6 address.");
+ }
+ OptionDataTypeUtil::writeAddress(address, buf);
+ return;
+ }
+ case OPT_IPV6_PREFIX_TYPE:
+ {
+ std::string txt = value;
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+
+ if (pos == string::npos) {
+ isc_throw(BadDataTypeCast, "provided address/prefix "
+ << value
+ << " is not valid.");
+ }
+
+ std::string txt_address = txt.substr(0, pos);
+ isc::asiolink::IOAddress address = isc::asiolink::IOAddress(txt_address);
+ if (!address.isV6()) {
+ isc_throw(BadDataTypeCast, "provided address "
+ << txt_address
+ << " is not a valid IPv4 or IPv6 address.");
+ }
+
+ std::string txt_prefix = txt.substr(pos + 1);
+ uint8_t len = 0;
+ try {
+ // start with the first character after /
+ len = lexicalCastWithRangeCheck<uint8_t>(txt_prefix);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided prefix "
+ << txt_prefix
+ << " is not valid.");
+ }
+
+ // Write a prefix.
+ OptionDataTypeUtil::writePrefix(PrefixLen(len), address, buf);
+
+ return;
+ }
+ case OPT_PSID_TYPE:
+ {
+ std::string txt = value;
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+
+ if (pos == string::npos) {
+ isc_throw(BadDataTypeCast, "provided PSID value "
+ << value << " is not valid");
+ }
+
+ const std::string txt_psid = txt.substr(0, pos);
+ const std::string txt_psid_len = txt.substr(pos + 1);
+
+ uint16_t psid = 0;
+ uint8_t psid_len = 0;
+
+ try {
+ psid = lexicalCastWithRangeCheck<uint16_t>(txt_psid);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided PSID "
+ << txt_psid << " is not valid");
+ }
+
+ try {
+ psid_len = lexicalCastWithRangeCheck<uint8_t>(txt_psid_len);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided PSID length "
+ << txt_psid_len << " is not valid");
+ }
+
+ OptionDataTypeUtil::writePsid(PSIDLen(psid_len), PSID(psid), buf);
+ return;
+ }
+ case OPT_STRING_TYPE:
+ OptionDataTypeUtil::writeString(value, buf);
+ return;
+ case OPT_FQDN_TYPE:
+ OptionDataTypeUtil::writeFqdn(value, buf);
+ return;
+ case OPT_TUPLE_TYPE:
+ {
+ // In case of V4_SZTP_REDIRECT option #143, bootstrap-server-list is formatted
+ // as a list of tuples "uri-length;URI" where uri-length is coded on 2 octets,
+ // which is not typical for V4 Universe.
+ OpaqueDataTuple::LengthFieldType lft = getCode() == DHO_V4_SZTP_REDIRECT
+ ? OpaqueDataTuple::LENGTH_2_BYTES
+ : OptionDataTypeUtil::getTupleLenFieldType(u);
+ OptionDataTypeUtil::writeTuple(value, lft, buf);
+ return;
+ }
+ default:
+ // We hit this point because invalid option data type has been specified
+ // This may be the case because 'empty' or 'record' data type has been
+ // specified. We don't throw exception here because it will be thrown
+ // at the exit point from this function.
+ ;
+ }
+ isc_throw(isc::BadValue, "attempt to write invalid option data field type"
+ " into the option buffer: " << type);
+
+}
+
+OptionPtr
+OptionDefinition::factoryAddrList4(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin,
+ end));
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryAddrList6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin,
+ end));
+ return (option);
+}
+
+
+OptionPtr
+OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type) {
+ OptionPtr option(new Option(u, type));
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ OptionPtr option(new Option(u, type, begin, end));
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryIA6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) {
+ isc_throw(isc::OutOfRange, "input option buffer has invalid size,"
+ << " expected at least " << Option6IA::OPTION6_IA_LEN
+ << " bytes");
+ }
+ boost::shared_ptr<Option6IA> option(new Option6IA(type, begin, end));
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryIAAddr6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) {
+ isc_throw(isc::OutOfRange,
+ "input option buffer has invalid size, expected at least "
+ << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
+ }
+ boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin,
+ end));
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryIAPrefix6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) < Option6IAPrefix::OPTION6_IAPREFIX_LEN) {
+ isc_throw(isc::OutOfRange,
+ "input option buffer has invalid size, expected at least "
+ << Option6IAPrefix::OPTION6_IAPREFIX_LEN << " bytes");
+ }
+ boost::shared_ptr<Option6IAPrefix> option(new Option6IAPrefix(type, begin,
+ end));
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ boost::shared_ptr<OptionOpaqueDataTuples>
+ option(new OptionOpaqueDataTuples(u, type, begin, end));
+
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end,
+ OpaqueDataTuple::LengthFieldType length_field_type) {
+ boost::shared_ptr<OptionOpaqueDataTuples>
+ option(new OptionOpaqueDataTuples(u, type, begin, end, length_field_type));
+
+ return (option);
+}
+
+OptionPtr
+OptionDefinition::factoryFqdnList(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const {
+
+ const std::vector<uint8_t> data(begin, end);
+ if (data.empty()) {
+ isc_throw(InvalidOptionValue, "FQDN list option has invalid length of 0");
+ }
+ InputBuffer in_buf(static_cast<const void*>(&data[0]), data.size());
+ std::vector<uint8_t> out_buf;
+ out_buf.reserve(data.size());
+ while (in_buf.getPosition() < in_buf.getLength()) {
+ // Reuse readFqdn and writeFqdn code but on the whole buffer
+ // so the DNS name code handles compression for us.
+ try {
+ isc::dns::Name name(in_buf);
+ isc::dns::LabelSequence labels(name);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* label = labels.getData(&read_len);
+ out_buf.insert(out_buf.end(), label, label + read_len);
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(InvalidOptionValue, ex.what());
+ }
+ }
+ return OptionPtr(new OptionCustom(*this, u,
+ out_buf.begin(), out_buf.end()));
+}
+
+OptionPtr
+OptionDefinition::factorySpecialFormatOption(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const {
+ if ((u == Option::V6) && haveSpace(DHCP6_OPTION_SPACE)) {
+ switch (getCode()) {
+ case D6O_IA_NA:
+ case D6O_IA_PD:
+ // Record of 3 uint32, no array.
+ return (factoryIA6(getCode(), begin, end));
+
+ case D6O_IAADDR:
+ // Record of an IPv6 address followed by 2 uint32, no array.
+ return (factoryIAAddr6(getCode(), begin, end));
+
+ case D6O_IAPREFIX:
+ // Record of 2 uint32, one uint8 and an IPv6 address, no array.
+ return (factoryIAPrefix6(getCode(), begin, end));
+
+ case D6O_CLIENT_FQDN:
+ // Record of one uint8 and one FQDN, no array.
+ return (OptionPtr(new Option6ClientFqdn(begin, end)));
+
+ case D6O_VENDOR_OPTS:
+ // Type uint32.
+ // Vendor-Specific Information (option code 17).
+ return (OptionPtr(new OptionVendor(Option::V6, begin, end)));
+
+ case D6O_VENDOR_CLASS:
+ // Record of one uint32 and one string.
+ // Vendor Class (option code 16).
+ return (OptionPtr(new OptionVendorClass(Option::V6, begin, end)));
+
+ case D6O_STATUS_CODE:
+ // Record of one uint16 and one string.
+ // Status Code (option code 13).
+ return (OptionPtr(new Option6StatusCode(begin, end)));
+
+ case D6O_BOOTFILE_PARAM:
+ // Array of tuples.
+ // Bootfile params (option code 60).
+ return (factoryOpaqueDataTuples(Option::V6, getCode(), begin, end));
+
+ case D6O_PD_EXCLUDE:
+ // Type IPv6 prefix.
+ // Prefix Exclude (option code 67),
+ return (OptionPtr(new Option6PDExclude(begin, end)));
+
+ case D6O_V6_DNR:
+ return (OptionPtr(new Option6Dnr(begin, end)));
+
+ default:
+ break;
+ }
+ } else if ((u == Option::V4) && haveSpace(DHCP4_OPTION_SPACE)) {
+ switch (getCode()) {
+ case DHO_SERVICE_SCOPE:
+ // Record of a boolean and a string.
+ return (OptionPtr(new Option4SlpServiceScope(begin, end)));
+
+ case DHO_FQDN:
+ // Record of 3 uint8 and a FQDN, no array.
+ return (OptionPtr(new Option4ClientFqdn(begin, end)));
+
+ case DHO_VIVCO_SUBOPTIONS:
+ // Record of uint32 followed by binary.
+ // V-I Vendor Class (option code 124).
+ return (OptionPtr(new OptionVendorClass(Option::V4, begin, end)));
+
+ case DHO_VIVSO_SUBOPTIONS:
+ // Type uint32.
+ // Vendor-Specific Information (option code 125).
+ return (OptionPtr(new OptionVendor(Option::V4, begin, end)));
+
+ case DHO_V4_SZTP_REDIRECT:
+ // Array of tuples.
+ // DHCPv4 SZTP Redirect Option (option code 143).
+ return (factoryOpaqueDataTuples(Option::V4, getCode(), begin, end, OpaqueDataTuple::LENGTH_2_BYTES));
+
+ case DHO_V4_DNR:
+ return (OptionPtr(new Option4Dnr(begin, end)));
+
+ default:
+ break;
+ }
+ }
+ if ((u == Option::V4) && haveCompressedFqdnListFormat()) {
+ return (factoryFqdnList(Option::V4, begin, end));
+ }
+ return (OptionPtr());
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
new file mode 100644
index 0000000..c76dcfe
--- /dev/null
+++ b/src/lib/dhcp/option_definition.h
@@ -0,0 +1,887 @@
+// Copyright (C) 2012-2023 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 OPTION_DEFINITION_H
+#define OPTION_DEFINITION_H
+
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_space_container.h>
+#include <cc/stamped_element.h>
+#include <cc/user_context.h>
+
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid option value has been
+/// specified for a particular option definition.
+class InvalidOptionValue : public Exception {
+public:
+ InvalidOptionValue(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception to be thrown when option definition is invalid.
+class MalformedOptionDefinition : public Exception {
+public:
+ MalformedOptionDefinition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception to be thrown when the particular option definition
+/// duplicates existing option definition.
+class DuplicateOptionDefinition : public Exception {
+public:
+ DuplicateOptionDefinition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to OptionDefinition.
+class OptionDefinition;
+
+/// @brief Pointer to option definition object.
+typedef boost::shared_ptr<OptionDefinition> OptionDefinitionPtr;
+
+/// @brief Forward declaration to OptionInt.
+///
+/// This forward declaration is needed to access the OptionInt class without
+/// having to include the option_int.h header file. It is required because
+/// this header includes libdhcp++.h, and including option_int.h would cause
+/// circular inclusion between libdhcp++.h, option_definition.h and
+/// option6_int.h.
+template<typename T>
+class OptionInt;
+
+/// @brief Forward declaration to OptionIntArray.
+///
+/// This forward declaration is needed to access the OptionIntArray class
+/// without having to include the option_int_array.h header file. It is
+/// required because this header includes libdhcp++.h, and including
+/// option_int_array.h would cause circular inclusion between libdhcp++.h,
+/// option_definition.h and option_int_array.h.
+template<typename T>
+class OptionIntArray;
+
+/// @brief Base class representing a DHCP option definition.
+///
+/// This is a base class representing a DHCP option definition, which describes
+/// the format of the option. In particular, it defines:
+/// - option name,
+/// - option code,
+/// - option space,
+/// - data fields order and their types,
+/// - sub options space that the particular option encapsulates.
+///
+/// The option type specifies the data type(s) which an option conveys. If
+/// this is a single value the option type points to the data type of the
+/// value. For example, DHCPv6 option 8 comprises a two-byte option code, a
+/// two-byte option length and two-byte field that carries a uint16 value
+/// (RFC 8415 - http://ietf.org/rfc/rfc8415.txt). In such a case, the option
+/// type is defined as "uint16". Length and string tuples are a length
+/// on one (DHCPv4) or two (DHCPv6) bytes followed by a string of
+/// the given length.
+///
+/// When the option has a more complex structure, the option type may be
+/// defined as "array", "record" or even "array of records".
+///
+/// Array types should be used when the option contains multiple contiguous
+/// data values of the same type laid. For example, DHCPv6 option 6 includes
+/// multiple fields holding uint16 codes of requested DHCPv6 options (RFC 8415).
+/// Such an option can be represented with this class by setting the option
+/// type to "uint16" and the array indicator (array_type) to true. The number
+/// of elements in the array is effectively unlimited (although it is actually
+/// limited by the maximal DHCPv6 option length).
+///
+/// Should the option comprise data fields of different types, the "record"
+/// option type is used. In such cases the data field types within the record
+/// are specified using \ref OptionDefinition::addRecordField.
+///
+/// When the OptionDefinition object has been successfully created, it can be
+/// queried to return the appropriate option factory function for the specified
+/// specified option format. There are a number of "standard" factory functions
+/// that cover well known (common) formats. If the particular format does not
+/// match any common format the generic factory function is returned.
+///
+/// The following data type strings are supported:
+/// - "empty" (option does not contain data fields)
+/// - "boolean"
+/// - "int8"
+/// - "int16"
+/// - "int32"
+/// - "uint8"
+/// - "uint16"
+/// - "uint32"
+/// - "ipv4-address" (IPv4 Address)
+/// - "ipv6-address" (IPv6 Address)
+/// - "ipv6-prefix" (IPv6 variable length prefix)
+/// - "psid" (PSID length / value)
+/// - "string"
+/// - "fqdn" (fully qualified name)
+/// - "tuple" (length and string)
+/// - "record" (set of data fields of different types)
+///
+/// @todo Extend the comment to describe "generic factories".
+/// @todo Extend this class with more factory functions.
+/// @todo Derive from UserContext without breaking the multi index.
+class OptionDefinition : public data::StampedElement {
+public:
+
+ /// List of fields within the record.
+ typedef std::vector<OptionDataType> RecordFieldsCollection;
+ /// Const iterator for record data fields.
+ typedef std::vector<OptionDataType>::const_iterator RecordFieldsConstIter;
+
+ /// @brief Constructor.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type as string.
+ /// @param array_type array indicator, if true it indicates that the
+ /// option fields are the array.
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const bool array_type = false);
+
+ /// @brief Constructor.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type.
+ /// @param array_type array indicator, if true it indicates that the
+ /// option fields are the array.
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const bool array_type = false);
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the name of the option space that is
+ /// encapsulated by this option. The encapsulated option space
+ /// identifies sub-options that are carried within this option.
+ /// This constructor does not allow to set array indicator
+ /// because options comprising an array of data fields must
+ /// not be used with sub-options.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type given as string.
+ /// @param encapsulated_space name of the option space being
+ /// encapsulated by this option.
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const char* encapsulated_space);
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the name of the option space that is
+ /// encapsulated by this option. The encapsulated option space
+ /// identifies sub-options that are carried within this option.
+ /// This constructor does not allow to set array indicator
+ /// because options comprising an array of data fields must
+ /// not be used with sub-options.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type.
+ /// @param encapsulated_space name of the option space being
+ /// encapsulated by this option.
+ explicit OptionDefinition(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const char* encapsulated_space);
+
+ /// @brief Factory function creating an instance of the @c OptionDefinition.
+ ///
+ /// This function should be used to create an instance of the option
+ /// definition within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type as string.
+ /// @param array_type array indicator, if true it indicates that the
+ /// option fields are the array.
+ ///
+ /// @return Pointer to the @c OptionDefinition instance.
+ static OptionDefinitionPtr create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const bool array_type = false);
+
+ /// @brief Factory function creating an instance of the @c OptionDefinition.
+ ///
+ /// This function should be used to create an instance of the option
+ /// definition within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type.
+ /// @param array_type array indicator, if true it indicates that the
+ /// option fields are the array.
+ ///
+ /// @return Pointer to the @c OptionDefinition instance.
+ static OptionDefinitionPtr create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const bool array_type = false);
+
+ /// @brief Factory function creating an instance of the @c OptionDefinition.
+ ///
+ /// This function should be used to create an instance of the option
+ /// definition within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type given as string.
+ /// @param encapsulated_space name of the option space being
+ /// encapsulated by this option.
+ ///
+ /// @return Pointer to the @c OptionDefinition instance.
+ static OptionDefinitionPtr create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const std::string& type,
+ const char* encapsulated_space);
+
+ /// @brief Factory function creating an instance of the @c OptionDefinition.
+ ///
+ /// This function should be used to create an instance of the option
+ /// definition within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param name option name.
+ /// @param code option code.
+ /// @param space option space.
+ /// @param type option data type.
+ /// @param encapsulated_space name of the option space being
+ /// encapsulated by this option.
+ ///
+ /// @return Pointer to the @c OptionDefinition instance.
+ static OptionDefinitionPtr create(const std::string& name,
+ const uint16_t code,
+ const std::string& space,
+ const OptionDataType type,
+ const char* encapsulated_space);
+
+ /// @name Comparison functions and operators.
+ ///
+ //@{
+ /// @brief Check if option definition is equal to other.
+ ///
+ /// @param other Option definition to compare to.
+ ///
+ /// @return true if two option definitions are equal, false otherwise.
+ bool equals(const OptionDefinition& other) const;
+
+ /// @brief Equality operator.
+ ///
+ /// @param other Option definition to compare to.
+ ///
+ /// @return true if two option definitions are equal, false otherwise.
+ bool operator==(const OptionDefinition& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other Option definition to compare to.
+ ///
+ /// @return true if option definitions are not equal, false otherwise.
+ bool operator!=(const OptionDefinition& other) const {
+ return (!equals(other));
+ }
+ //@}
+
+ /// @brief Adds data field to the record.
+ ///
+ /// @param data_type_name name of the data type for the field.
+ ///
+ /// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE.
+ /// @throw isc::BadValue if specified invalid data type.
+ void addRecordField(const std::string& data_type_name);
+
+ /// @brief Adds data field to the record.
+ ///
+ /// @param data_type data type for the field.
+ ///
+ /// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE.
+ /// @throw isc::BadValue if specified invalid data type.
+ void addRecordField(const OptionDataType data_type);
+
+ /// @brief Return array type indicator.
+ ///
+ /// The method returns the bool value to indicate whether the option is a
+ /// single value or an array of values.
+ ///
+ /// @return true if option comprises an array of values.
+ bool getArrayType() const { return (array_type_); }
+
+ /// @brief Return option code.
+ ///
+ /// @return option code.
+ uint16_t getCode() const { return (code_); }
+
+ /// @brief Return name of the encapsulated option space.
+ ///
+ /// @return name of the encapsulated option space.
+ std::string getEncapsulatedSpace() const {
+ return (encapsulated_space_);
+ }
+
+ /// @brief Return option name.
+ ///
+ /// @return option name.
+ std::string getName() const { return (name_); }
+
+ /// @brief Return list of record fields.
+ ///
+ /// @return list of record fields.
+ const RecordFieldsCollection& getRecordFields() const {
+ return (record_fields_);
+ }
+
+ /// @brief Returns option space name.
+ ///
+ /// @return Option space name.
+ std::string getOptionSpaceName() const {
+ return (option_space_name_);
+ }
+
+ /// @brief Return option data type.
+ ///
+ /// @return option data type.
+ OptionDataType getType() const { return (type_); };
+
+ /// @brief Returns const pointer to the user context
+ data::ConstElementPtr getContext() const {
+ return (user_context_.getContext());
+ }
+
+ /// @brief Sets user context.
+ /// @param ctx user context to be stored.
+ void setContext(const data::ConstElementPtr& ctx) {
+ user_context_.setContext(ctx);
+ }
+
+ /// @brief Merge unparse a user_context object.
+ ///
+ /// Add user-context to map, but only if defined. Omit if it was not.
+ /// Extract comment so it will be pretty-printed first.
+ ///
+ /// @param map A pointer to map where the user context will be unparsed.
+ void contextToElement(data::ElementPtr map) const {
+ user_context_.contextToElement(map);
+ }
+
+ /// @brief Check if the option definition is valid.
+ ///
+ /// Note that it is a responsibility of the code that created
+ /// the OptionDefinition object to validate that it is valid.
+ /// This function will not be called internally anywhere in this
+ /// class to verify that the option definition is valid. Using
+ /// invalid option definition to create an instance of the
+ /// DHCP option leads to undefined behavior.
+ ///
+ /// @throw MalformedOptionDefinition option definition is invalid.
+ void validate() const;
+
+ /// @brief Option factory.
+ ///
+ /// This function creates an instance of DHCP option using
+ /// provided chunk of buffer. This function may be used to
+ /// create option which is to be sent in the outgoing packet.
+ ///
+ /// @warning calling this function on invalid option definition
+ /// yields undefined behavior. Use \ref validate to test that
+ /// the option definition is valid.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin beginning of the option buffer.
+ /// @param end end of the option buffer.
+ ///
+ /// @return instance of the DHCP option.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr optionFactory(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const;
+
+ /// @brief Option factory.
+ ///
+ /// This function creates an instance of DHCP option using
+ /// whole provided buffer. This function may be used to
+ /// create option which is to be sent in the outgoing packet.
+ ///
+ /// @warning calling this function on invalid option definition
+ /// yields undefined behavior. Use \ref validate to test that
+ /// the option definition is valid.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param buf option buffer.
+ ///
+ /// @return instance of the DHCP option.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr optionFactory(Option::Universe u, uint16_t type,
+ const OptionBuffer& buf = OptionBuffer()) const;
+
+ /// @brief Option factory.
+ ///
+ /// This function creates an instance of DHCP option using the vector
+ /// of strings which carry data values for option data fields.
+ /// The order of values in the vector corresponds to the order of data
+ /// fields in the option. The supplied string values are cast to
+ /// their actual data types which are determined based on the
+ /// option definition. If cast fails due to type mismatch, an exception
+ /// is thrown. This factory function can be used to create option
+ /// instance when user specified option value in the <b>comma separated
+ /// values</b> format in the configuration database. Provided string
+ /// must be tokenized into the vector of string values and this vector
+ /// can be supplied to this function.
+ ///
+ /// @warning calling this function on invalid option definition
+ /// yields undefined behavior. Use \ref validate to test that
+ /// the option definition is valid.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param values a vector of values to be used to set data for an option.
+ ///
+ /// @return instance of the DHCP option.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr optionFactory(Option::Universe u, uint16_t type,
+ const std::vector<std::string>& values) const;
+
+ /// @brief Factory to create option with address list.
+ ///
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of IPv4 addresses.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of IPv4 addresses.
+ ///
+ /// @throw isc::OutOfRange if length of the provided option buffer
+ /// is not multiple of IPV4 address length.
+ static OptionPtr factoryAddrList4(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Factory to create option with address list.
+ ///
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of IPv6 addresses.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of IPv6 addresses.
+ ///
+ /// @throw isc::OutOfaRange if length of provided option buffer
+ /// is not multiple of IPV6 address length.
+ static OptionPtr factoryAddrList6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Empty option factory.
+ ///
+ /// @param u universe (V6 or V4).
+ /// @param type option type.
+ static OptionPtr factoryEmpty(Option::Universe u, uint16_t type);
+
+ /// @brief Factory to create generic option.
+ ///
+ /// @param u universe (V6 or V4).
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ static OptionPtr factoryGeneric(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Factory for IA-type of option.
+ ///
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ ///
+ /// @throw isc::OutOfRange if provided option buffer is too short or
+ /// too long. Expected size is 12 bytes.
+ static OptionPtr factoryIA6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Factory for IAADDR-type of option.
+ ///
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ ///
+ /// @throw isc::OutOfRange if provided option buffer is too short or
+ /// too long. Expected size is 24 bytes.
+ static OptionPtr factoryIAAddr6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Factory for IAPREFIX-type of option.
+ ///
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ ///
+ /// @throw isc::OutOfRange if provided option buffer is too short.
+ /// Expected minimum size is 25 bytes.
+ static OptionPtr factoryIAPrefix6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Factory to create option with tuple list.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of tuples.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of tuples.
+ ///
+ /// @return instance of the DHCP option.
+ static OptionPtr factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Factory to create option with tuple list with explict
+ /// tuple's length field type.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of tuples.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of tuples.
+ /// @param length_field_type explicit tuple's length field type.
+ ///
+ /// @return instance of the DHCP option.
+ static OptionPtr factoryOpaqueDataTuples(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end,
+ OpaqueDataTuple::LengthFieldType length_field_type);
+
+ /// @brief Factory function to create option with integer value.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param encapsulated_space An option space being encapsulated by the
+ /// options created by this factory function. The options which belong to
+ /// encapsulated option space are sub options of this option.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ /// @tparam T type of the data field (must be one of the uintX_t or intX_t).
+ ///
+ /// @throw isc::OutOfRange if provided option buffer length is invalid.
+ template<typename T>
+ static OptionPtr factoryInteger(Option::Universe u, uint16_t type,
+ const std::string& encapsulated_space,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ OptionPtr option(new OptionInt<T>(u, type, 0));
+ option->setEncapsulatedSpace(encapsulated_space);
+ option->unpack(begin, end);
+ return (option);
+ }
+
+ /// @brief Factory function to create option with array of integer values.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ /// @tparam T type of the data field (must be one of the uintX_t or intX_t).
+ ///
+ /// @throw isc::OutOfRange if provided option buffer length is invalid.
+ template<typename T>
+ static OptionPtr factoryIntegerArray(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ OptionPtr option(new OptionIntArray<T>(u, type, begin, end));
+ return (option);
+ }
+
+private:
+
+ /// @brief Check if the option has format of CompressedFqdnList options.
+ bool haveCompressedFqdnListFormat() const;
+
+ /// @brief Factory function to create option with a compressed FQDN list.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
+ ///
+ /// @return instance of the DHCP option where FQDNs are uncompressed.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr factoryFqdnList(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const;
+
+ /// @brief Creates an instance of an option having special format.
+ ///
+ /// The option with special formats are encapsulated by the dedicated
+ /// classes derived from @c Option class. In particular these are:
+ /// - IA_NA
+ /// - IAADDR
+ /// - FQDN
+ /// - VIVSO.
+ ///
+ /// @param u A universe (V4 or V6).
+ /// @param begin beginning of the option buffer.
+ /// @param end end of the option buffer.
+ ///
+ /// @return An instance of the option having special format or NULL if
+ /// such an option can't be created because an option with the given
+ /// option code hasn't got the special format.
+ OptionPtr factorySpecialFormatOption(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const;
+
+ /// @brief Check if specified type matches option definition type.
+ ///
+ /// @return true if specified type matches option definition type.
+ inline bool haveType(const OptionDataType type) const {
+ return (type == type_);
+ }
+
+ /// @brief Check if specified type matches option definition space.
+ ///
+ /// @return true if specified type matches option definition space.
+ inline bool haveSpace(const std::string& space) const {
+ return (space == option_space_name_);
+ }
+
+ /// @brief Converts a string value to a boolean value.
+ ///
+ /// This function converts the value represented as string to a boolean
+ /// value. The following conversions are acceptable:
+ /// - "true" => true
+ /// - "false" => false
+ /// - "1" => true
+ /// - "0" => false
+ /// The first two conversions are case insensitive, so as conversions from
+ /// strings such as "TRUE", "trUE" etc. will be accepted. Note that the
+ /// only acceptable integer values, carried as strings are: "0" and "1".
+ /// For other values, e.g. "2", "3" etc. an exception will be thrown
+ /// during conversion.
+ ///
+ /// @param value_str Input value.
+ ///
+ /// @return boolean representation of the string specified as the parameter.
+ /// @throw isc::dhcp::BadDataTypeCast if failed to perform the conversion.
+ bool convertToBool(const std::string& value_str) const;
+
+ /// @brief Perform lexical cast of the value and validate its range.
+ ///
+ /// This function performs lexical cast of a string value to integer
+ /// value and checks if the resulting value is within a range of a
+ /// target type. The target type should be one of the supported
+ /// integer types. Hexadecimal input is supported.
+ ///
+ /// @param value_str input value given as string.
+ /// @tparam T target integer type for lexical cast.
+ ///
+ /// @return Integer value after conversion from the string.
+ /// @throw isc::dhcp::BadDataTypeCast if conversion was not successful.
+ template<typename T>
+ T lexicalCastWithRangeCheck(const std::string& value_str) const;
+
+ /// @brief Write the string value into the provided buffer.
+ ///
+ /// This method writes the given value to the specified buffer.
+ /// The provided string value may represent data of different types.
+ /// The actual data type is specified with the second argument.
+ /// Based on a value of this argument, this function will first
+ /// try to cast the string value to the particular data type and
+ /// if it is successful it will store the data in the buffer
+ /// in a binary format.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param value string representation of the value to be written.
+ /// @param type the actual data type to be stored.
+ /// @param [in, out] buf buffer where the value is to be stored.
+ ///
+ /// @throw BadDataTypeCast if data write was unsuccessful.
+ void writeToBuffer(Option::Universe u, const std::string& value,
+ const OptionDataType type, OptionBuffer& buf) const;
+
+ /// Option name.
+ std::string name_;
+ /// Option code.
+ uint16_t code_;
+ /// Option data type.
+ OptionDataType type_;
+ /// Indicates whether option is a single value or array.
+ bool array_type_;
+ /// Name of the space being encapsulated by this option.
+ std::string encapsulated_space_;
+ /// Collection of data fields within the record.
+ RecordFieldsCollection record_fields_;
+ /// User context
+ data::UserContext user_context_;
+ /// Option space name
+ std::string option_space_name_;
+};
+
+
+/// @brief Multi index container for DHCP option definitions.
+///
+/// This container allows to search for DHCP option definition
+/// using two indexes:
+/// - sequenced: used to access elements in the order they have
+/// been added to the container
+/// - option code: used to search definitions of options
+/// with a specified option code (aka option type).
+/// Note that this container can hold multiple options with the
+/// same code. For this reason, the latter index can be used to
+/// obtain a range of options for a particular option code.
+///
+/// @todo: need an index to search options using option space name
+/// once option spaces are implemented.
+typedef boost::multi_index_container<
+ // Container comprises elements of OptionDefinition type.
+ OptionDefinitionPtr,
+ // Here we start enumerating various indexes.
+ boost::multi_index::indexed_by<
+ // Sequenced index allows accessing elements in the same way
+ // as elements in std::list. Sequenced is an index #0.
+ boost::multi_index::sequenced<>,
+ // Start definition of index #1.
+ boost::multi_index::hashed_non_unique<
+ // Use option type as the index key. The type is held
+ // in OptionDefinition object so we have to call
+ // OptionDefinition::getCode to retrieve this key
+ // for each element. The option code is non-unique so
+ // multiple elements with the same option code can
+ // be returned by this index.
+ boost::multi_index::const_mem_fun<
+ OptionDefinition,
+ uint16_t,
+ &OptionDefinition::getCode
+ >
+ >,
+ // Start definition of index #2
+ boost::multi_index::hashed_non_unique<
+ // Use option name as the index key. This value is
+ // returned by the @c OptionDefinition::getName
+ // method.
+ boost::multi_index::const_mem_fun<
+ OptionDefinition,
+ std::string,
+ &OptionDefinition::getName
+ >
+ >,
+ // Start definition of index #3
+ boost::multi_index::ordered_non_unique<
+ // Use option definition modification time as the index key.
+ // This value is returned by the BaseStampedElement::getModificationTime
+ boost::multi_index::const_mem_fun<
+ data::BaseStampedElement,
+ boost::posix_time::ptime,
+ &data::StampedElement::getModificationTime
+ >
+ >,
+ // Start definition of index #4.
+ // Use StampedElement::getId as a key.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<OptionIdIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement, uint64_t,
+ &data::BaseStampedElement::getId>
+ >
+ >
+> OptionDefContainer;
+
+/// Pointer to an option definition container.
+typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+
+/// Container that holds option definitions for various option spaces.
+typedef std::map<std::string, OptionDefContainerPtr> OptionDefContainers;
+
+/// Container that holds various vendor option containers
+typedef std::map<uint32_t, OptionDefContainerPtr> VendorOptionDefContainers;
+
+/// Type of the index #1 - option type.
+typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
+/// Pair of iterators to represent the range of options definitions
+/// having the same option type value. The first element in this pair
+/// represents the beginning of the range, the second element
+/// represents the end.
+typedef std::pair<OptionDefContainerTypeIndex::const_iterator,
+ OptionDefContainerTypeIndex::const_iterator> OptionDefContainerTypeRange;
+
+/// Type of the index #2 - option name.
+typedef OptionDefContainer::nth_index<2>::type OptionDefContainerNameIndex;
+/// Pair of iterators to represent the range of options definitions
+/// having the same option name. The first element in this pair
+/// represents the beginning of the range, the second element
+/// represents the end.
+typedef std::pair<OptionDefContainerNameIndex::const_iterator,
+ OptionDefContainerNameIndex::const_iterator> OptionDefContainerNameRange;
+
+/// Base type of option definition space container.
+typedef OptionSpaceContainer<
+ OptionDefContainer, OptionDefinitionPtr, std::string
+> BaseOptionDefSpaceContainer;
+
+/// @brief Class of option definition space container.
+class OptionDefSpaceContainer : public BaseOptionDefSpaceContainer {
+public:
+
+ /// @brief Adds a new option definition to the container.
+ ///
+ /// The option definition already contains the option space.
+ ///
+ /// @note: this method hides the parent one so it becomes hard to get
+ /// a mismatch between the option definition and the space container.
+ ///
+ /// @param def reference to the option definition being added.
+ void addItem(const OptionDefinitionPtr& def) {
+ BaseOptionDefSpaceContainer::addItem(def, def->getOptionSpaceName());
+ }
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_DEFINITION_H
diff --git a/src/lib/dhcp/option_int.h b/src/lib/dhcp/option_int.h
new file mode 100644
index 0000000..10a4cc6
--- /dev/null
+++ b/src/lib/dhcp/option_int.h
@@ -0,0 +1,250 @@
+// Copyright (C) 2012-2022 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 OPTION_INT_H
+#define OPTION_INT_H
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_space.h>
+#include <util/io_utilities.h>
+
+#include <stdint.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+template<typename T>
+class OptionInt;
+
+/// @defgroup option_int_defs Typedefs for OptionInt class.
+///
+/// @brief Classes that represent options comprising an integer.
+///
+/// @{
+typedef OptionInt<uint8_t> OptionUint8;
+typedef boost::shared_ptr<OptionUint8> OptionUint8Ptr;
+typedef OptionInt<uint16_t> OptionUint16;
+typedef boost::shared_ptr<OptionUint16> OptionUint16Ptr;
+typedef OptionInt<uint32_t> OptionUint32;
+typedef boost::shared_ptr<OptionUint32> OptionUint32Ptr;
+/// @}
+
+/// This template class represents DHCP option with single value.
+/// This value is of integer type and can be any of the following:
+/// - uint8_t,
+/// - uint16_t,
+/// - uint32_t,
+/// - int8_t,
+/// - int16_t,
+/// - int32_t.
+///
+/// @tparam T data field type (see above).
+template<typename T>
+class OptionInt: public Option {
+private:
+
+ /// @brief Pointer to the option object for a specified type T.
+ typedef boost::shared_ptr<OptionInt<T> > OptionIntTypePtr;
+
+public:
+ /// @brief Constructor.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param type option type.
+ /// @param value option value.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ /// @todo Extend constructor to set encapsulated option space name.
+ OptionInt(Option::Universe u, uint16_t type, T value)
+ : Option(u, type), value_(value) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
+ }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates option from a buffer. This constructor
+ /// may throw exception if \ref unpack function throws during buffer
+ /// parsing.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param type option type.
+ /// @param begin iterator to first byte of option data.
+ /// @param end iterator to end of option data (first byte after option end).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ /// @todo Extend constructor to set encapsulated option space name.
+ OptionInt(Option::Universe u, uint16_t type, OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(u, type) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ setEncapsulatedSpace(u == Option::V4 ? DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
+ unpack(begin, end);
+ }
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const {
+ return (cloneInternal<OptionInt<T> >());
+ }
+
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param [out] buf buffer (option will be stored here)
+ /// @param check if set to false, allows options larger than 255 for v4
+ ///
+ /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const {
+ // Pack option header.
+ packHeader(buf, check);
+ // Depending on the data type length we use different utility functions
+ // writeUint16 or writeUint32 which write the data in the network byte
+ // order to the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ buf.writeUint8(value_);
+ break;
+ case 2:
+ buf.writeUint16(value_);
+ break;
+ case 4:
+ buf.writeUint32(value_);
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ packOptions(buf, check);
+ }
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+ /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ if (distance(begin, end) < sizeof(T)) {
+ isc_throw(OutOfRange, "OptionInt " << getType() << " truncated");
+ }
+ // @todo consider what to do if buffer is longer than data type.
+
+ // Depending on the data type length we use different utility functions
+ // readUint16 or readUint32 which read the data laid in the network byte
+ // order from the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ int data_size_len = OptionDataTypeTraits<T>::len;
+ switch (data_size_len) {
+ case 1:
+ value_ = *begin;
+ break;
+ case 2:
+ value_ = isc::util::readUint16(&(*begin),
+ std::distance(begin, end));
+ break;
+ case 4:
+ value_ = isc::util::readUint32(&(*begin),
+ std::distance(begin, end));
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ // Use local variable to set a new value for this iterator.
+ // When using OptionDataTypeTraits<T>::len directly some versions
+ // of clang complain about unresolved reference to
+ // OptionDataTypeTraits structure during linking.
+ begin += data_size_len;
+ unpackOptions(OptionBuffer(begin, end));
+ }
+
+ /// @brief Set option value.
+ ///
+ /// @param value new option value.
+ void setValue(T value) { value_ = value; }
+
+ /// @brief Return option value.
+ ///
+ /// @return option value.
+ T getValue() const { return value_; }
+
+ /// @brief returns complete length of option
+ ///
+ /// Returns length of this option, including option header and suboptions
+ ///
+ /// @return length of this option
+ virtual uint16_t len() const {
+ // Calculate the length of the header.
+ uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
+ // The data length is equal to size of T.
+ length += sizeof(T);;
+ // length of all suboptions
+ for (OptionCollection::const_iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+ }
+
+ /// @brief Returns option carrying an integer value in the textual
+ /// format.
+ ///
+ /// The returned value also includes the suboptions if present.
+ ///
+ /// @param indent Number of spaces to be inserted before the text.
+ virtual std::string toText(int indent = 0) const {
+ std::stringstream output;
+ output << headerToText(indent) << ": ";
+
+ // For 1 byte long data types we need to cast to the integer
+ // because they are usually implemented as "char" types, in
+ // which case the character rather than number would be printed.
+ if (OptionDataTypeTraits<T>::len == 1) {
+ output << static_cast<int>(getValue());
+ } else {
+ output << getValue();
+ }
+
+ // Append data type name.
+ output << " ("
+ << OptionDataTypeUtil::getDataTypeName(OptionDataTypeTraits<T>::type)
+ << ")";
+
+ // Append suboptions.
+ output << suboptionsToText(indent + 2);
+
+ return (output.str());
+ }
+
+private:
+
+ T value_; ///< Value conveyed by the option.
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_INT_H
diff --git a/src/lib/dhcp/option_int_array.h b/src/lib/dhcp/option_int_array.h
new file mode 100644
index 0000000..70803d7
--- /dev/null
+++ b/src/lib/dhcp/option_int_array.h
@@ -0,0 +1,295 @@
+// Copyright (C) 2012-2022 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 OPTION_INT_ARRAY_H
+#define OPTION_INT_ARRAY_H
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <util/io_utilities.h>
+#include <boost/shared_ptr.hpp>
+#include <sstream>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration of OptionIntArray.
+template<typename T>
+class OptionIntArray;
+
+/// @defgroup option_int_array_defs Typedefs for OptionIntArray class.
+///
+/// @brief Classes that represent options comprising array of integers.
+///
+/// @{
+typedef OptionIntArray<uint8_t> OptionUint8Array;
+typedef boost::shared_ptr<OptionUint8Array> OptionUint8ArrayPtr;
+typedef OptionIntArray<uint16_t> OptionUint16Array;
+typedef boost::shared_ptr<OptionUint16Array> OptionUint16ArrayPtr;
+typedef OptionIntArray<uint32_t> OptionUint32Array;
+typedef boost::shared_ptr<OptionUint32Array> OptionUint32ArrayPtr;
+/// @}
+
+/// This template class represents DHCP (v4 or v6) option with an
+/// array of integer values. The type of the elements in the array
+/// can be any of the following:
+/// - uint8_t,
+/// - uint16_t,
+/// - uint32_t,
+/// - int8_t,
+/// - int16_t,
+/// - int32_t.
+///
+/// @warning Since this option may convey variable number of integer
+/// values, sub-options are should not be added in this option as
+/// there is no way to distinguish them from other data. The API will
+/// allow addition of sub-options but they will be ignored during
+/// packing and unpacking option data.
+///
+/// @tparam T data field type (see above).
+template<typename T>
+class OptionIntArray : public Option {
+private:
+
+ /// @brief Pointer to the option type for the specified T.
+ typedef boost::shared_ptr<OptionIntArray<T> > OptionIntArrayTypePtr;
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates option with empty values vector.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionIntArray(const Option::Universe u, const uint16_t type)
+ : Option(u, type),
+ values_(0) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param buf buffer with option data (must not be empty).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is empty or its length
+ /// is not multiple of size of the data type in bytes.
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionIntArray(const Option::Universe u, const uint16_t type,
+ const OptionBuffer& buf)
+ : Option(u, type) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ unpack(buf.begin(), buf.end());
+ }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates option from a buffer. This constructor
+ /// may throw exception if \ref unpack function throws during buffer
+ /// parsing.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator to first byte of option data.
+ /// @param end iterator to end of option data (first byte after option end).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is empty or its length
+ /// is not multiple of size of the data type in bytes.
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionIntArray(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin, OptionBufferConstIter end)
+ : Option(u, type) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ unpack(begin, end);
+ }
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ virtual OptionPtr clone() const {
+ return (cloneInternal<OptionIntArray<T> >());
+ }
+
+ /// @brief Adds a new value to the array.
+ ///
+ /// @param value a value being added.
+ void addValue(const T value) {
+ values_.push_back(value);
+ }
+
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param [out] buf buffer (option will be stored here)
+ /// @param check if set to false, allows options larger than 255 for v4
+ ///
+ /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const {
+ // Pack option header.
+ packHeader(buf, check);
+ // Pack option data.
+ for (size_t i = 0; i < values_.size(); ++i) {
+ // Depending on the data type length we use different utility functions
+ // writeUint16 or writeUint32 which write the data in the network byte
+ // order to the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ buf.writeUint8(values_[i]);
+ break;
+ case 2:
+ buf.writeUint16(values_[i]);
+ break;
+ case 4:
+ buf.writeUint32(values_[i]);
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ }
+ // We don't pack sub-options here because we have array-type option.
+ // We don't allow sub-options in array-type options as there is no
+ // way to distinguish them from the data fields on option reception.
+ }
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ if (distance(begin, end) == 0) {
+ isc_throw(OutOfRange, "option " << getType() << " empty");
+ }
+ if (distance(begin, end) % sizeof(T) != 0) {
+ isc_throw(OutOfRange, "OptionIntArray " << getType() << " truncated");
+ }
+ // @todo consider what to do if buffer is longer than data type.
+
+ values_.clear();
+ while (begin != end) {
+ // Depending on the data type length we use different utility functions
+ // readUint16 or readUint32 which read the data laid in the network byte
+ // order from the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ int data_size_len = OptionDataTypeTraits<T>::len;
+ switch (data_size_len) {
+ case 1:
+ values_.push_back(*begin);
+ break;
+ case 2:
+ values_.push_back(isc::util::readUint16(&(*begin),
+ std::distance(begin, end)));
+ break;
+ case 4:
+ values_.push_back(isc::util::readUint32(&(*begin),
+ std::distance(begin, end)));
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ // Use local variable to set a new value for this iterator.
+ // When using OptionDataTypeTraits<T>::len directly some versions
+ // of clang complain about unresolved reference to
+ // OptionDataTypeTraits structure during linking.
+ begin += data_size_len;
+ }
+ // We do not unpack sub-options here because we have array-type option.
+ // Such option have variable number of data fields, thus there is no
+ // way to assess where sub-options start.
+ }
+
+ /// @brief Return collection of option values.
+ ///
+ /// @return collection of values.
+ const std::vector<T>& getValues() const { return (values_); }
+
+ /// @brief Set option values.
+ ///
+ /// @param values collection of values to be set for option.
+ void setValues(const std::vector<T>& values) { values_ = values; }
+
+ /// @brief returns complete length of option
+ ///
+ /// Returns length of this option, including option header and suboptions
+ ///
+ /// @return length of this option
+ virtual uint16_t len() const {
+ uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
+ length += values_.size() * sizeof(T);
+ // length of all suboptions
+ for (OptionCollection::const_iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+ }
+
+ /// @brief Returns textual representation of the option.
+ ///
+ /// @param indent Number of space characters to be inserted before
+ /// the text.
+ ///
+ /// @return textual representation of the option.
+ virtual std::string toText(int indent = 0) const {
+ std::stringstream output;
+ output << headerToText(indent) << ":";
+
+ std::string data_type = OptionDataTypeUtil::getDataTypeName(OptionDataTypeTraits<T>::type);
+ for (typename std::vector<T>::const_iterator value = values_.begin();
+ value != values_.end(); ++value) {
+ output << " ";
+
+ // For 1 byte long data types we need to cast to the integer
+ // because they are usually implemented as "char" types, in
+ // which case the character rather than number would be printed.
+ if (OptionDataTypeTraits<T>::len == 1) {
+ output << static_cast<int>(*value);
+
+ } else {
+ output << *value;
+ }
+
+ // Append data type.
+ output << "(" << data_type << ")";
+ }
+
+ return (output.str());
+ }
+
+private:
+
+ std::vector<T> values_;
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_INT_ARRAY_H
diff --git a/src/lib/dhcp/option_opaque_data_tuples.cc b/src/lib/dhcp/option_opaque_data_tuples.cc
new file mode 100644
index 0000000..ea8fa7c
--- /dev/null
+++ b/src/lib/dhcp/option_opaque_data_tuples.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2015-2023 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 <dhcp/opaque_data_tuple.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u,
+ const uint16_t type,
+ OpaqueDataTuple::LengthFieldType length_field_type)
+ : Option(u, type), length_field_type_(length_field_type) {
+ if (length_field_type_ == OpaqueDataTuple::LENGTH_EMPTY) {
+ length_field_type_ = OptionDataTypeUtil::getTupleLenFieldType(u);
+ }
+}
+
+OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u,
+ const uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end,
+ OpaqueDataTuple::LengthFieldType length_field_type)
+ : Option(u, type), length_field_type_(length_field_type) {
+ if (length_field_type_ == OpaqueDataTuple::LENGTH_EMPTY) {
+ length_field_type_ = OptionDataTypeUtil::getTupleLenFieldType(u);
+ }
+ unpack(begin, end);
+}
+
+OptionPtr
+OptionOpaqueDataTuples::clone() const {
+ return (cloneInternal<OptionOpaqueDataTuples>());
+}
+
+void
+OptionOpaqueDataTuples::pack(isc::util::OutputBuffer& buf, bool check) const {
+ packHeader(buf, check);
+
+ for (TuplesCollection::const_iterator it = tuples_.begin();
+ it != tuples_.end(); ++it) {
+ it->pack(buf);
+ }
+ // That's it. We don't pack any sub-options here, because this option
+ // must not contain sub-options.
+}
+
+void
+OptionOpaqueDataTuples::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ // We are skipping typical OutOfRange check for Option#unpack(begin, end),
+ // since empty collection of tuples is also a valid case where
+ // std::distance(begin, end) = 0
+
+ // Start reading opaque data.
+ size_t offset = 0;
+ while (offset < std::distance(begin, end)) {
+ // Parse a tuple.
+ OpaqueDataTuple tuple(length_field_type_, begin + offset, end);
+ addTuple(tuple);
+ // The tuple has been parsed correctly which implies that it is safe to
+ // advance the offset by its total length.
+ offset += tuple.getTotalLength();
+ }
+}
+
+void
+OptionOpaqueDataTuples::addTuple(const OpaqueDataTuple& tuple) {
+ if (tuple.getLengthFieldType() != length_field_type_) {
+ isc_throw(isc::BadValue, "attempted to add opaque data tuple having"
+ " invalid size of the length field "
+ << tuple.getDataFieldSize() << " to opaque data tuple option");
+ }
+
+ tuples_.push_back(tuple);
+}
+
+
+void
+OptionOpaqueDataTuples::setTuple(const size_t at, const OpaqueDataTuple& tuple) {
+ if (at >= getTuplesNum()) {
+ isc_throw(isc::OutOfRange, "attempted to set an opaque data for the"
+ " opaque data tuple option at position " << at << " which"
+ " is out of range");
+
+ } else if (tuple.getLengthFieldType() != length_field_type_) {
+ isc_throw(isc::BadValue, "attempted to set opaque data tuple having"
+ " invalid size of the length field "
+ << tuple.getDataFieldSize() << " to opaque data tuple option");
+ }
+
+ tuples_[at] = tuple;
+}
+
+OpaqueDataTuple
+OptionOpaqueDataTuples::getTuple(const size_t at) const {
+ if (at >= getTuplesNum()) {
+ isc_throw(isc::OutOfRange, "attempted to get an opaque data for the"
+ " opaque data tuple option at position " << at << " which is"
+ " out of range. There are only " << getTuplesNum() << " tuples");
+ }
+ return (tuples_[at]);
+}
+
+bool
+OptionOpaqueDataTuples::hasTuple(const std::string& tuple_str) const {
+ // Iterate over existing tuples (there shouldn't be many of them),
+ // and try to match the searched one.
+ for (TuplesCollection::const_iterator it = tuples_.begin();
+ it != tuples_.end(); ++it) {
+ if (*it == tuple_str) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+uint16_t
+OptionOpaqueDataTuples::len() const {
+ // The option starts with the header.
+ uint16_t length = getHeaderLen();
+ // Now iterate over existing tuples and add their size.
+ for (TuplesCollection::const_iterator it = tuples_.begin();
+ it != tuples_.end(); ++it) {
+ length += it->getTotalLength();
+ }
+
+ return (length);
+}
+
+std::string
+OptionOpaqueDataTuples::toText(int indent) const {
+ std::ostringstream s;
+
+ // Apply indentation
+ s << std::string(indent, ' ');
+
+ // Print type and length
+ s << "type=" << getType() << ", len=" << len() - getHeaderLen() << std::dec;
+ // Iterate over all tuples and print their size and contents.
+ for (unsigned i = 0; i < getTuplesNum(); ++i) {
+ // Print the tuple.
+ s << ", data-len" << i << "=" << getTuple(i).getLength();
+ s << ", data" << i << "='" << getTuple(i) << "'";
+ }
+
+ return (s.str());
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcp/option_opaque_data_tuples.h b/src/lib/dhcp/option_opaque_data_tuples.h
new file mode 100644
index 0000000..4edfbeb
--- /dev/null
+++ b/src/lib/dhcp/option_opaque_data_tuples.h
@@ -0,0 +1,164 @@
+// Copyright (C) 2015-2023 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 OPTION_OPAQUE_DATA_TUPLES_H
+#define OPTION_OPAQUE_DATA_TUPLES_H
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <util/buffer.h>
+#include <boost/shared_ptr.hpp>
+#include <dhcp/option_data_types.h>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This class encapsulates a collection of data tuples and could be
+/// used by multiple options. It is tailored for use with the DHCPv6
+/// Bootfile-param option (option 60).
+///
+/// The format of the option is described in section 3.2 of RFC5970.
+/// This option may carry an arbitrary number of tuples carrying opaque data.
+/// Each tuple consists of a field holding the length of the opaque data
+/// followed by a string containing the data itself. For option 60 each
+/// length field is 2 bytes long and the data is a UTF-8 string that is not
+/// null terminated.
+///
+/// @todo The class is similar to the class used by the DHCPv6 Vendor Class
+/// (16) and DHCPv4 V-I Vendor Class (124) options, though they include an
+/// enterprise (or vendor) ID in the option. In the future it may
+/// make sense to rewrite the OptionVendorClass to derive from this class.
+class OptionOpaqueDataTuples : public Option {
+public:
+
+ /// @brief Collection of opaque data tuples carried by the option.
+ typedef std::vector<OpaqueDataTuple> TuplesCollection;
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates an instance of an OptionOpaqueDataTuples
+ /// that can be used for an option such as DHCPv6 Bootfile Parameters (60).
+ ///
+ /// @param u universe (v4 or v6).
+ /// @param type option type
+ /// @param length_field_type Indicates a length of the field which holds
+ /// the size of the tuple. If not provided explicitly, it is evaluated
+ /// basing on Option's v4/v6 universe.
+ OptionOpaqueDataTuples(Option::Universe u,
+ const uint16_t type,
+ OpaqueDataTuple::LengthFieldType length_field_type = OpaqueDataTuple::LENGTH_EMPTY);
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates an instance of the option using a buffer with
+ /// on-wire data. It may throw an exception if the @c unpack method throws.
+ ///
+ /// @param u universe (v4 or v6)
+ /// @param type option type
+ /// @param begin Iterator pointing to the beginning of the buffer holding an
+ /// option.
+ /// @param end Iterator pointing to the end of the buffer holding an option.
+ /// @param length_field_type Indicates a length of the field which holds
+ /// the size of the tuple. If not provided explicitly, it is evaluated
+ /// basing on Option's v4/v6 universe.
+ OptionOpaqueDataTuples(Option::Universe u,
+ const uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end,
+ OpaqueDataTuple::LengthFieldType length_field_type = OpaqueDataTuple::LENGTH_EMPTY);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ OptionPtr clone() const;
+
+ /// @brief Renders option into the buffer in the wire format.
+ ///
+ /// @param [out] buf Buffer to which the option is rendered.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses buffer holding an option.
+ ///
+ /// This function parses the buffer holding an option and initializes option
+ /// properties: the collection of tuples.
+ ///
+ /// @param begin Iterator pointing to the beginning of the buffer holding an
+ /// option.
+ /// @param end Iterator pointing to the end of the buffer holding an option.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Adds a new opaque data tuple to the option.
+ ///
+ /// @param tuple Tuple to be added.
+ /// @throw isc::BadValue if the type of the tuple doesn't match the
+ /// universe this option belongs to.
+ void addTuple(const OpaqueDataTuple& tuple);
+
+ /// @brief Replaces tuple at the specified index with a new tuple.
+ ///
+ /// This function replaces an opaque data tuple at the specified position
+ /// with the new tuple. If the specified index is out of range an exception
+ /// is thrown.
+ ///
+ /// @param at Index at which the tuple should be replaced.
+ /// @param tuple Tuple to be set.
+ /// @throw isc::OutOfRange if the tuple position is out of range.
+ /// @throw isc::BadValue if the type of the tuple doesn't match the
+ /// universe this option belongs to.
+ void setTuple(const size_t at, const OpaqueDataTuple& tuple);
+
+ /// @brief Returns opaque data tuple at the specified position.
+ ///
+ /// If the specified position is out of range an exception is thrown.
+ ///
+ /// @param at Index for which tuple to get.
+ /// @throw isc::OutOfRange if the tuple position is out of range.
+ OpaqueDataTuple getTuple(const size_t at) const;
+
+ /// @brief Returns the number of opaque data tuples added to the option.
+ size_t getTuplesNum() const {
+ return (tuples_.size());
+ }
+
+ /// @brief Returns collection of opaque data tuples carried in the option.
+ const TuplesCollection& getTuples() const {
+ return (tuples_);
+ }
+
+ /// @brief Checks if the object holds the opaque data tuple with the
+ /// specified string.
+ ///
+ /// @param tuple_str String representation of the tuple being searched.
+ /// @return true if the specified tuple exists for this option.
+ bool hasTuple(const std::string& tuple_str) const;
+
+ /// @brief Returns the full length of the option, including option header.
+ virtual uint16_t len() const;
+
+ /// @brief Returns text representation of the option.
+ ///
+ /// @param indent Number of space characters before text.
+ /// @return Text representation of the option.
+ virtual std::string toText(int indent = 0) const;
+
+private:
+ /// @brief length of the field which holds the size of the tuple.
+ OpaqueDataTuple::LengthFieldType length_field_type_;
+
+ /// @brief Collection of opaque data tuples carried by the option.
+ TuplesCollection tuples_;
+
+};
+
+/// @brief Defines a pointer to the @c OptionOpaqueDataTuples.
+typedef boost::shared_ptr<OptionOpaqueDataTuples> OptionOpaqueDataTuplesPtr;
+
+}
+}
+
+#endif // OPTION_OPAQUE_DATA_TUPLES_H
diff --git a/src/lib/dhcp/option_space.cc b/src/lib/dhcp/option_space.cc
new file mode 100644
index 0000000..3be318b
--- /dev/null
+++ b/src/lib/dhcp/option_space.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2012-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 <dhcp/option_space.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+namespace isc {
+namespace dhcp {
+
+OptionSpace::OptionSpace(const std::string& name, const bool vendor_space)
+ : name_(name), vendor_space_(vendor_space) {
+ // Check that provided option space name is valid.
+ if (!validateName(name_)) {
+ isc_throw(InvalidOptionSpace, "Invalid option space name "
+ << name_);
+ }
+}
+
+bool
+OptionSpace::validateName(const std::string& name) {
+
+ using namespace boost::algorithm;
+
+ // Allowed characters are: lower or upper case letters, digits,
+ // underscores and hyphens. Empty option spaces are not allowed.
+ if (all(name, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) &&
+ !name.empty() &&
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) &&
+ !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) {
+ return (true);
+
+ }
+ return (false);
+}
+
+OptionSpace6::OptionSpace6(const std::string& name)
+ : OptionSpace(name),
+ enterprise_number_(0) {
+}
+
+OptionSpace6::OptionSpace6(const std::string& name,
+ const uint32_t enterprise_number)
+ : OptionSpace(name, true),
+ enterprise_number_(enterprise_number) {
+}
+
+void
+OptionSpace6::setVendorSpace(const uint32_t enterprise_number) {
+ enterprise_number_ = enterprise_number;
+ OptionSpace::setVendorSpace();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_space.h b/src/lib/dhcp/option_space.h
new file mode 100644
index 0000000..4826b0f
--- /dev/null
+++ b/src/lib/dhcp/option_space.h
@@ -0,0 +1,183 @@
+// Copyright (C) 2012-2020 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 OPTION_SPACE_H
+#define OPTION_SPACE_H
+
+#include <dhcp/std_option_defs.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid option space
+/// is specified.
+class InvalidOptionSpace : public Exception {
+public:
+ InvalidOptionSpace(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// OptionSpace forward declaration.
+class OptionSpace;
+/// A pointer to OptionSpace object.
+typedef boost::shared_ptr<OptionSpace> OptionSpacePtr;
+/// A collection of option spaces.
+typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection;
+
+/// @brief DHCP option space.
+///
+/// This class represents single option space. The option spaces are used
+/// to group DHCP options having unique option codes. The special type
+/// of the option space is so called "vendor specific option space".
+/// It groups sub-options being sent within Vendor Encapsulated Options.
+/// For DHCPv4 it is the option with code 43. The option spaces are
+/// assigned to option instances represented by isc::dhcp::Option and
+/// other classes derived from it. Each particular option may belong to
+/// multiple option spaces.
+/// This class may be used to represent any DHCPv4 option space. If the
+/// option space is to group DHCPv4 Vendor Encapsulated Options then
+/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace
+/// or the argument passed to the constructor. In theory, this class can
+/// be also used to represent non-vendor specific DHCPv6 option space
+/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6
+/// class should be used instead.
+///
+/// @note this class is intended to be used to represent DHCPv4 option
+/// spaces only. However, it hasn't been called OptionSpace4 (that would
+/// suggest that it is specific to DHCPv4) because it can be also
+/// used to represent some DHCPv6 option spaces and is a base class
+/// for \ref OptionSpace6. Thus, if one declared the container as follows:
+/// @code
+/// std::vector<OptionSpace4> container;
+/// @endcode
+/// it would suggest that the container holds DHCPv4 option spaces while
+/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6
+/// object could be upcast to OptionSpace4. This confusion does not appear
+/// when OptionSpace is used as a name for the base class.
+class OptionSpace {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param name option space name.
+ /// @param vendor_space boolean value that indicates that the object
+ /// describes the vendor specific option space.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref validateName function to check that the specified name is
+ /// correct.
+ OptionSpace(const std::string& name, const bool vendor_space = false);
+
+ /// @brief Return option space name.
+ ///
+ /// @return option space name.
+ const std::string& getName() const { return (name_); }
+
+ /// @brief Mark option space as non-vendor space.
+ void clearVendorSpace() {
+ vendor_space_ = false;
+ }
+
+ /// @brief Check if option space is vendor specific.
+ ///
+ /// @return boolean value that indicates if the object describes
+ /// the vendor specific option space.
+ bool isVendorSpace() const { return (vendor_space_); }
+
+ /// @brief Mark option space as vendor specific.
+ void setVendorSpace() {
+ vendor_space_ = true;
+ }
+
+ /// @brief Checks that the provided option space name is valid.
+ ///
+ /// It is expected that option space name consists of upper or
+ /// lower case letters or digits. Also, it may contain underscores
+ /// or dashes. Other characters are prohibited. The empty option
+ /// space names are invalid.
+ ///
+ /// @param name option space name to be validated.
+ ///
+ /// @return true if the option space is valid, else it returns false.
+ static bool validateName(const std::string& name);
+
+private:
+ std::string name_; ///< Holds option space name.
+
+ bool vendor_space_; ///< Is this the vendor space?
+
+};
+
+/// @brief DHCPv6 option space with enterprise number assigned.
+///
+/// This class extends the base class with the support for enterprise numbers.
+/// The enterprise numbers are assigned by IANA to various organizations
+/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific
+/// Information Options (VSIO). For more information refer to RFC 8415.
+/// All option spaces that group VSIO options must have enterprise number
+/// set. It can be set using a constructor or \ref setVendorSpace function.
+/// The extra functionality of this class (enterprise numbers) allows to
+/// represent DHCPv6 vendor-specific option spaces but this class is also
+/// intended to be used for all other DHCPv6 option spaces. That way all
+/// DHCPv6 option spaces can be stored in the container holding OptionSpace6
+/// objects. Also, it is easy to mark vendor-specific option space as non-vendor
+/// specific option space (and the other way around) without a need to cast
+/// between OptionSpace and OptionSpace6 types.
+class OptionSpace6 : public OptionSpace {
+public:
+
+ /// @brief Constructor for non-vendor-specific options.
+ ///
+ /// This constructor marks option space as non-vendor specific.
+ ///
+ /// @param name option space name.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name);
+
+ /// @brief Constructor for vendor-specific options.
+ ///
+ /// This constructor marks option space as vendor specific and sets
+ /// enterprise number to a given value.
+ ///
+ /// @param name option space name.
+ /// @param enterprise_number enterprise number.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name, const uint32_t enterprise_number);
+
+ /// @brief Return enterprise number for the option space.
+ ///
+ /// @return enterprise number.
+ uint32_t getEnterpriseNumber() const { return (enterprise_number_); }
+
+ /// @brief Mark option space as vendor specific.
+ ///
+ /// @param enterprise_number enterprise number.
+ void setVendorSpace(const uint32_t enterprise_number);
+
+private:
+
+ uint32_t enterprise_number_; ///< IANA assigned enterprise number.
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // OPTION_SPACE_H
diff --git a/src/lib/dhcp/option_space_container.h b/src/lib/dhcp/option_space_container.h
new file mode 100644
index 0000000..d1c2952
--- /dev/null
+++ b/src/lib/dhcp/option_space_container.h
@@ -0,0 +1,184 @@
+// Copyright (C) 2013-2019 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 OPTION_SPACE_CONTAINER_H
+#define OPTION_SPACE_CONTAINER_H
+
+#include <exceptions/exceptions.h>
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A tag for accessing DHCP options and definitions by id.
+struct OptionIdIndexTag { };
+
+/// @brief Simple container for option spaces holding various items.
+///
+/// This helper class is used to store items of various types
+/// that are grouped by option space names. Each option space is
+/// mapped to a container that holds items which specifically can
+/// be OptionDefinition objects or Subnet::OptionDescriptor structures.
+///
+/// @tparam ContainerType of the container holding items within
+/// option space.
+/// @tparam ItemType type of the item being held by the container.
+/// @tparam Selector a string (for option spaces) or uint32_t (for vendor options)
+template<typename ContainerType, typename ItemType, typename Selector>
+class OptionSpaceContainer {
+public:
+
+ /// Pointer to the container.
+ typedef boost::shared_ptr<ContainerType> ItemsContainerPtr;
+
+ /// @brief Indicates the container is empty
+ ///
+ /// @return true when the container is empty
+ bool empty() const {
+ return (option_space_map_.empty());
+ }
+
+ /// @brief Adds a new item to the option_space.
+ ///
+ /// @param item reference to the item being added.
+ /// @param option_space name or vendor-id of the option space
+ void addItem(const ItemType& item, const Selector& option_space) {
+ ItemsContainerPtr items = getItems(option_space);
+ // Assume that the push_back() can't fail even when the
+ // ContainerType is a multi index container, i.e., assume
+ // there is no unique index which can raise a conflict.
+ static_cast<void>(items->push_back(item));
+ option_space_map_[option_space] = items;
+ }
+
+ /// @brief Get all items for the particular option space.
+ ///
+ /// @warning when there are no items for the specified option
+ /// space an empty container is created and returned. However
+ /// this container is not added to the list of option spaces.
+ ///
+ /// @param option_space name or vendor-id of the option space.
+ ///
+ /// @return pointer to the container holding items.
+ ItemsContainerPtr getItems(const Selector& option_space) const {
+ const typename OptionSpaceMap::const_iterator& items =
+ option_space_map_.find(option_space);
+ if (items == option_space_map_.end()) {
+ return (ItemsContainerPtr(new ContainerType()));
+ }
+ return (items->second);
+ }
+
+ /// @brief Get a list of existing option spaces.
+ ///
+ /// @return a list of option spaces.
+ ///
+ /// @todo This function is likely to be removed once
+ /// we create a structure of OptionSpaces defined
+ /// through the configuration manager.
+ std::list<Selector> getOptionSpaceNames() const {
+ std::list<Selector> names;
+ for (typename OptionSpaceMap::const_iterator space =
+ option_space_map_.begin();
+ space != option_space_map_.end(); ++space) {
+ names.push_back(space->first);
+ }
+ return (names);
+ }
+
+ /// @brief Remove all items from the container.
+ void clearItems() {
+ option_space_map_.clear();
+ }
+
+ /// @brief Remove all options or option definitions with a given
+ /// database identifier.
+ ///
+ /// Note that there are cases when there will be multiple options
+ /// or option definitions having the same id (typically id of 0).
+ /// When configuration backend is in use it sets the unique ids
+ /// from the database. In cases when the configuration backend is
+ /// not used, the ids default to 0. Passing the id of 0 would
+ /// result in deleting all options or option definitions that were
+ /// not added via the database.
+ ///
+ /// @param id Identifier of the items to be deleted.
+ ///
+ /// @return Number of deleted options or option definitions.
+ uint64_t deleteItems(const uint64_t id) {
+ uint64_t num_deleted = 0;
+ for (auto space : option_space_map_) {
+ auto container = space.second;
+ auto& index = container->template get<OptionIdIndexTag>();
+ num_deleted += index.erase(id);
+ }
+
+ return (num_deleted);
+ }
+
+ /// @brief Check if two containers are equal.
+ ///
+ /// This method checks if option space container contains exactly
+ /// the same selectors and that there are exactly the same items
+ /// added for each selector. The order of items doesn't matter.
+ ///
+ /// @param other Other container to compare to.
+ ///
+ /// @return true if containers are equal, false otherwise.
+ bool equals(const OptionSpaceContainer& other) const {
+ for (typename OptionSpaceMap::const_iterator it =
+ option_space_map_.begin(); it != option_space_map_.end();
+ ++it) {
+
+ typename OptionSpaceMap::const_iterator other_it =
+ other.option_space_map_.find(it->first);
+ if (other_it == other.option_space_map_.end()) {
+ return (false);
+ }
+
+ // If containers have different sizes it is an indication that
+ // they are unequal.
+ if (it->second->size() != other_it->second->size()) {
+ return (false);
+ }
+
+ // If they have the same sizes, we have to compare each element.
+ for (typename ContainerType::const_iterator items_it =
+ it->second->begin();
+ items_it != it->second->end(); ++items_it) {
+
+ bool match_found = false;
+ for (typename ContainerType::const_iterator other_items_it =
+ other_it->second->begin();
+ other_items_it != other_it->second->end();
+ ++other_items_it) {
+ if (items_it->equals(*other_items_it)) {
+ match_found = true;
+ break;
+ }
+ }
+
+ if (!match_found) {
+ return (false);
+ }
+ }
+ }
+ return (true);
+ }
+
+private:
+
+ /// A map holding container (option space name or vendor-id is the key).
+ typedef std::map<Selector, ItemsContainerPtr> OptionSpaceMap;
+ OptionSpaceMap option_space_map_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // OPTION_SPACE_CONTAINER_H
diff --git a/src/lib/dhcp/option_string.cc b/src/lib/dhcp/option_string.cc
new file mode 100644
index 0000000..5f1d0d4
--- /dev/null
+++ b/src/lib/dhcp/option_string.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2013-2022 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 <dhcp/option_string.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+OptionString::OptionString(const Option::Universe u, const uint16_t type,
+ const std::string& value)
+ : Option(u, type) {
+ // Try to assign the provided string value. This will throw exception
+ // if the provided value is empty.
+ setValue(value);
+}
+
+OptionString::OptionString(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(u, type) {
+ // Decode the data. This will throw exception if the buffer is
+ // truncated.
+ unpack(begin, end);
+}
+
+OptionPtr
+OptionString::clone() const {
+ return (cloneInternal<OptionString>());
+}
+
+std::string
+OptionString::getValue() const {
+ const OptionBuffer& data = getData();
+ return (std::string(data.begin(), data.end()));
+}
+
+void
+OptionString::setValue(const std::string& value) {
+ // Sanity check that the string value is at least one byte long.
+ // This is a requirement for all currently defined options which
+ // carry a string value.
+ if (value.empty()) {
+ isc_throw(isc::OutOfRange, "string value carried by the option '"
+ << getType() << "' must not be empty");
+ }
+
+ // Trim off any trailing nuls.
+ auto begin = value.begin();
+ auto end = util::str::seekTrimmed(begin, value.end(), 0x0);
+
+ if (std::distance(begin, end) == 0) {
+ isc_throw(isc::OutOfRange, "string value carried by the option '"
+ << getType() << "' contained only nuls");
+ }
+
+ // Now set the value.
+ setData(begin, end);
+}
+
+
+uint16_t
+OptionString::len() const {
+ return (getHeaderLen() + getData().size());
+}
+
+void
+OptionString::pack(isc::util::OutputBuffer& buf, bool check) const {
+ // Pack option header.
+ packHeader(buf, check);
+ // Pack data.
+ const OptionBuffer& data = getData();
+ buf.writeData(&data[0], data.size());
+
+ // That's it. We don't pack any sub-options here, because this option
+ // must not contain sub-options.
+}
+
+void
+OptionString::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ // Trim off trailing nul(s)
+ end = util::str::seekTrimmed(begin, end, 0x0);
+ if (std::distance(begin, end) == 0) {
+ isc_throw(isc::dhcp::SkipThisOptionError, "failed to parse an option '"
+ << getType() << "' holding string value"
+ << "' was empty or contained only nuls");
+ }
+
+ // Now set the data.
+ setData(begin, end);
+}
+
+std::string
+OptionString::toText(int indent) const {
+ std::ostringstream output;
+ output << headerToText(indent) << ": "
+ << "\"" << getValue() << "\" (string)";
+
+ return (output.str());
+}
+
+std::string
+OptionString::toString() const {
+ return (getValue());
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_string.h b/src/lib/dhcp/option_string.h
new file mode 100644
index 0000000..241305a
--- /dev/null
+++ b/src/lib/dhcp/option_string.h
@@ -0,0 +1,128 @@
+// Copyright (C) 2013-2022 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 OPTION_STRING_H
+#define OPTION_STRING_H
+
+#include <dhcp/option.h>
+#include <util/buffer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Class which represents an option carrying a single string value.
+///
+/// This class represents an option carrying a single string value.
+/// Currently this class imposes that the minimal length of the carried
+/// string is 1. Per RFC 2132, Sec 2 trailing NULLs are trimmed during
+/// either construction or unpacking.
+///
+/// @todo In the future this class may be extended with some more string
+/// content checks and encoding methods if required.
+class OptionString : public Option {
+public:
+
+ /// @brief Constructor, used to create options to be sent.
+ ///
+ /// This constructor creates an instance of option which carries a
+ /// string value specified as constructor's parameter. This constructor
+ /// is most often used to create an instance of an option which will
+ /// be sent in the outgoing packet. Trailing NULLs will be trimmed.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option code.
+ /// @param value a string value to be carried by the option.
+ ///
+ /// @throw isc::OutOfRange if provided string is empty.
+ OptionString(const Option::Universe u, const uint16_t type,
+ const std::string& value);
+
+ /// @brief Constructor, used for receiving options.
+ ///
+ /// This constructor creates an instance of the option from the provided
+ /// chunk of buffer. This buffer may hold the data received on the wire.
+ /// Trailing NULLs will be trimmed.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option code.
+ /// @param begin iterator pointing to the first byte of the buffer chunk.
+ /// @param end iterator pointing to the last byte of the buffer chunk.
+ ///
+ /// @throw isc::OutOfRange if provided buffer is truncated.
+ OptionString(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ OptionPtr clone() const;
+
+ /// @brief Returns length of the whole option, including header.
+ ///
+ /// @return length of the whole option.
+ virtual uint16_t len() const;
+
+ /// @brief Returns the string value held by the option.
+ ///
+ /// @return string value held by the option.
+ std::string getValue() const;
+
+ /// @brief Sets the string value to be held by the option.
+ ///
+ /// Trailing NULLs will be trimmed.
+ ///
+ /// @param value string value to be set.
+ ///
+ /// @throw isc::OutOfRange if a string value to be set is empty.
+ void setValue(const std::string& value);
+
+ /// @brief Creates on-wire format of the option.
+ ///
+ /// This function creates on-wire format of the option and appends it to
+ /// the data existing in the provided buffer. The internal buffer's pointer
+ /// is moved to the end of stored data.
+ ///
+ /// @param [out] buf output buffer where the option will be stored.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Decodes option data from the provided buffer.
+ ///
+ /// This function decodes option data from the provided buffer. Note that
+ /// it does not decode the option code and length, so the iterators must
+ /// point to the beginning and end of the option payload respectively.
+ /// The size of the decoded payload must be at least 1 byte.
+ /// Trailing NULLs will be trimmed.
+ ///
+ /// @param begin the iterator pointing to the option payload.
+ /// @param end the iterator pointing to the end of the option payload.
+ ///
+ /// @throw isc::OutOfRange if provided buffer is truncated.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns option information in the textual format.
+ ///
+ /// @param indent Number of space characters to be inserted before
+ /// the text.
+ ///
+ /// @return Option information in the textual format.
+ virtual std::string toText(int indent = 0) const;
+
+ /// @brief Returns actual value of the option in string format.
+ ///
+ /// This method is used in client classification.
+ /// @return Content of the option.
+ virtual std::string toString() const;
+};
+
+/// Pointer to the OptionString object.
+typedef boost::shared_ptr<OptionString> OptionStringPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_STRING_H
diff --git a/src/lib/dhcp/option_vendor.cc b/src/lib/dhcp/option_vendor.cc
new file mode 100644
index 0000000..bc47935
--- /dev/null
+++ b/src/lib/dhcp/option_vendor.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2013-2023 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_vendor.h>
+#include <sstream>
+
+using namespace isc::dhcp;
+
+OptionVendor::OptionVendor(Option::Universe u, const uint32_t vendor_id)
+ : Option(u, u == Option::V4 ?
+ static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) :
+ static_cast<uint16_t>(D6O_VENDOR_OPTS)), vendor_id_(vendor_id) {
+}
+
+OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(u, u == Option::V4?
+ static_cast<uint16_t>(DHO_VIVSO_SUBOPTIONS) :
+ static_cast<uint16_t>(D6O_VENDOR_OPTS)), vendor_id_(0) {
+ unpack(begin, end);
+}
+
+OptionPtr
+OptionVendor::clone() const {
+ return (cloneInternal<OptionVendor>());
+}
+
+void OptionVendor::pack(isc::util::OutputBuffer& buf, bool check) const {
+ packHeader(buf, check);
+
+ // Store vendor-id
+ buf.writeUint32(vendor_id_);
+
+ // The format is slightly different for v4
+ if (universe_ == Option::V4) {
+ // Calculate and store data-len as follows:
+ // data-len = total option length - header length
+ // - enterprise id field length - data-len field size
+ // length of all suboptions
+ uint8_t length = 0;
+ for (auto const& opt : options_) {
+ length += opt.second->len();
+ }
+ buf.writeUint8(length);
+ }
+
+ packOptions(buf, check);
+}
+
+void OptionVendor::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ // We throw SkipRemainingOptionsError so callers can
+ // abandon further unpacking, if desired.
+ if (distance(begin, end) < sizeof(uint32_t)) {
+ isc_throw(SkipRemainingOptionsError,
+ "Truncated vendor-specific information option"
+ << ", length=" << distance(begin, end));
+ }
+
+ vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
+
+ OptionBuffer vendor_buffer(begin + 4, end);
+
+ if (universe_ == Option::V6) {
+ LibDHCP::unpackVendorOptions6(vendor_id_, vendor_buffer, options_);
+ } else {
+ LibDHCP::unpackVendorOptions4(vendor_id_, vendor_buffer, options_);
+ }
+}
+
+uint16_t OptionVendor::len() const {
+ uint16_t length = getHeaderLen();
+
+ length += sizeof(uint32_t); // Vendor-id field
+
+ // Data-len field exists in DHCPv4 vendor options only
+ if (universe_ == Option::V4) {
+ length += sizeof(uint8_t); // data-len
+ }
+
+ // length of all suboptions
+ for (auto const& opt : options_) {
+ length += opt.second->len();
+ }
+ return (length);
+}
+
+std::string
+OptionVendor::toText(int indent) const {
+ std::stringstream output;
+ output << headerToText(indent) << ": ";
+
+ output << vendor_id_ << " (uint32)";
+
+ // For the DHCPv4 there is one more field.
+ if (getUniverse() == Option::V4) {
+ uint32_t length = 0;
+ for (auto const& opt : options_) {
+ length += opt.second->len();
+ }
+ output << " " << length << " (uint8)";
+ }
+
+ // Append suboptions.
+ output << suboptionsToText(indent + 2);
+
+ return (output.str());
+}
diff --git a/src/lib/dhcp/option_vendor.h b/src/lib/dhcp/option_vendor.h
new file mode 100644
index 0000000..43a0aae
--- /dev/null
+++ b/src/lib/dhcp/option_vendor.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2013-2023 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 OPTION_VENDOR_H
+#define OPTION_VENDOR_H
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <util/io_utilities.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// Indexes for fields in vendor-class (17) DHCPv6 option
+const int VENDOR_CLASS_ENTERPRISE_ID_INDEX = 0;
+const int VENDOR_CLASS_DATA_LEN_INDEX = 1;
+const int VENDOR_CLASS_STRING_INDEX = 2;
+
+/// @brief This class represents vendor-specific information option.
+///
+/// As specified in RFC3925, the option formatting is slightly different
+/// for DHCPv4 than DHCPv6. The DHCPv4 Option includes additional field
+/// holding vendor data length.
+class OptionVendor: public Option {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id vendor enterprise-id (unique 32 bit integer)
+ OptionVendor(Option::Universe u, const uint32_t vendor_id);
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates option from a buffer. This constructor
+ /// may throw exception if \ref unpack function throws during buffer
+ /// parsing.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param begin iterator to first byte of option data.
+ /// @param end iterator to end of option data (first byte after option end).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+ /// @todo Extend constructor to set encapsulated option space name.
+ OptionVendor(Option::Universe u, OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ OptionPtr clone() const;
+
+ /// @brief Writes option in wire-format to buf, returns pointer to first
+ /// unused byte after stored option.
+ ///
+ /// @param [out] buf buffer (option will be stored here)
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw isc::SkipRemainingOptionsBuffer if an error is encountered
+ /// unpacking the option. This exception is thrown to indicate to the
+ /// caller that a: remaining options cannot be parsed and b: the packet
+ /// should be considered for processing anyway.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Sets enterprise identifier
+ ///
+ /// @param vendor_id vendor identifier
+ void setVendorId(const uint32_t vendor_id) {
+ vendor_id_ = vendor_id;
+ }
+
+ /// @brief Returns enterprise identifier
+ ///
+ /// @return enterprise identifier
+ uint32_t getVendorId() const {
+ return (vendor_id_);
+ }
+
+ /// @brief returns complete length of option
+ ///
+ /// Returns length of this option, including option header and suboptions
+ ///
+ /// @return length of this option
+ virtual uint16_t len() const;
+
+ /// @brief Returns the option in the textual format.
+ ///
+ /// @param indent Number of spaces to be inserted before the text.
+ ///
+ /// @return Vendor option in the textual format.
+ virtual std::string toText(int indent = 0) const;
+
+private:
+
+ /// @brief Enterprise-id
+ uint32_t vendor_id_;
+};
+
+/// Pointer to a vendor option
+typedef boost::shared_ptr<OptionVendor> OptionVendorPtr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_VENDOR_H
diff --git a/src/lib/dhcp/option_vendor_class.cc b/src/lib/dhcp/option_vendor_class.cc
new file mode 100644
index 0000000..df4cf1c
--- /dev/null
+++ b/src/lib/dhcp/option_vendor_class.cc
@@ -0,0 +1,202 @@
+// Copyright (C) 2014-2023 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 <dhcp/opaque_data_tuple.h>
+#include <dhcp/option_vendor_class.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+OptionVendorClass::OptionVendorClass(Option::Universe u,
+ const uint32_t vendor_id)
+ : Option(u, getOptionCode(u)), vendor_id_(vendor_id) {
+ if (u == Option::V4) {
+ addTuple(OpaqueDataTuple(OpaqueDataTuple::LENGTH_1_BYTE));
+ }
+}
+
+OptionVendorClass::OptionVendorClass(Option::Universe u,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(u, getOptionCode(u)) {
+ unpack(begin, end);
+}
+
+OptionPtr
+OptionVendorClass::clone() const {
+ return (cloneInternal<OptionVendorClass>());
+}
+
+void
+OptionVendorClass::pack(isc::util::OutputBuffer& buf, bool check) const {
+ packHeader(buf, check);
+
+ buf.writeUint32(getVendorId());
+
+ for (TuplesCollection::const_iterator it = tuples_.begin();
+ it != tuples_.end(); ++it) {
+ // For DHCPv4 V-I Vendor Class option, there is enterprise id before
+ // every tuple.
+ if ((getUniverse() == V4) && (it != tuples_.begin())) {
+ buf.writeUint32(getVendorId());
+ }
+ it->pack(buf);
+
+ }
+ // That's it. We don't pack any sub-options here, because this option
+ // must not contain sub-options.
+}
+
+void
+OptionVendorClass::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) < getMinimalLength() - getHeaderLen()) {
+ isc_throw(OutOfRange, "parsed Vendor Class option data truncated to"
+ " size " << std::distance(begin, end));
+ }
+ // Option must contain at least one enterprise id. It is ok to read 4-byte
+ // value here because we have checked that the buffer he minimal length.
+ vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
+ begin += sizeof(vendor_id_);
+
+ // Start reading opaque data.
+ size_t offset = 0;
+ while (offset < std::distance(begin, end)) {
+ // Parse a tuple.
+ OpaqueDataTuple tuple(OptionDataTypeUtil::getTupleLenFieldType(getUniverse()),
+ begin + offset, end);
+ addTuple(tuple);
+ // The tuple has been parsed correctly which implies that it is safe to
+ // advance the offset by its total length.
+ offset += tuple.getTotalLength();
+ // For DHCPv4 option, there is enterprise id before every opaque data
+ // tuple. Let's read it, unless we have already reached the end of
+ // buffer.
+ if ((getUniverse() == V4) && (begin + offset != end)) {
+ // Get the other enterprise id.
+ uint32_t other_id = isc::util::readUint32(&(*(begin + offset)),
+ distance(begin + offset,
+ end));
+ // Throw if there are two different enterprise ids.
+ if (other_id != vendor_id_) {
+ isc_throw(isc::BadValue, "V-I Vendor Class option with two "
+ "different enterprise ids: " << vendor_id_
+ << " and " << other_id);
+ }
+ // Advance the offset by the size of enterprise id.
+ offset += sizeof(vendor_id_);
+ // If the offset already ran over the buffer length or there is
+ // no space left for the empty tuple (thus we add 1), we have
+ // to signal the option truncation.
+ if (offset + 1 >= std::distance(begin, end)) {
+ isc_throw(isc::OutOfRange, "truncated DHCPv4 V-I Vendor Class"
+ " option - it should contain enterprise id followed"
+ " by opaque data field tuple");
+ }
+ }
+ }
+}
+
+void
+OptionVendorClass::addTuple(const OpaqueDataTuple& tuple) {
+ if (tuple.getLengthFieldType() != OptionDataTypeUtil::getTupleLenFieldType(getUniverse())) {
+ isc_throw(isc::BadValue, "attempted to add opaque data tuple having"
+ " invalid size of the length field "
+ << tuple.getDataFieldSize() << " to Vendor Class option");
+ }
+
+ tuples_.push_back(tuple);
+}
+
+
+void
+OptionVendorClass::setTuple(const size_t at, const OpaqueDataTuple& tuple) {
+ if (at >= getTuplesNum()) {
+ isc_throw(isc::OutOfRange, "attempted to set an opaque data for the"
+ " vendor option at position " << at << " which is out of"
+ " range");
+
+ } else if (tuple.getLengthFieldType() != OptionDataTypeUtil::getTupleLenFieldType(getUniverse())) {
+ isc_throw(isc::BadValue, "attempted to set opaque data tuple having"
+ " invalid size of the length field "
+ << tuple.getDataFieldSize() << " to Vendor Class option");
+ }
+
+ tuples_[at] = tuple;
+}
+
+OpaqueDataTuple
+OptionVendorClass::getTuple(const size_t at) const {
+ if (at >= getTuplesNum()) {
+ isc_throw(isc::OutOfRange, "attempted to get an opaque data for the"
+ " vendor option at position " << at << " which is out of"
+ " range. There are only " << getTuplesNum() << " tuples");
+ }
+ return (tuples_[at]);
+}
+
+bool
+OptionVendorClass::hasTuple(const std::string& tuple_str) const {
+ // Iterate over existing tuples (there shouldn't be many of them),
+ // and try to match the searched one.
+ for (TuplesCollection::const_iterator it = tuples_.begin();
+ it != tuples_.end(); ++it) {
+ if (*it == tuple_str) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+
+uint16_t
+OptionVendorClass::len() const {
+ // The option starts with the header and enterprise id.
+ uint16_t length = getHeaderLen() + sizeof(uint32_t);
+ // Now iterate over existing tuples and add their size.
+ for (TuplesCollection::const_iterator it = tuples_.begin();
+ it != tuples_.end(); ++it) {
+ // For DHCPv4 V-I Vendor Class option, there is enterprise id before
+ // every tuple.
+ if ((getUniverse() == V4) && (it != tuples_.begin())) {
+ length += sizeof(uint32_t);
+ }
+ length += it->getTotalLength();
+
+ }
+
+ return (length);
+}
+
+std::string
+OptionVendorClass::toText(int indent) const {
+ std::ostringstream s;
+
+ // Apply indentation
+ s << std::string(indent, ' ');
+ // Print type, length and first occurrence of enterprise id.
+ s << "type=" << getType() << ", len=" << len() - getHeaderLen() << ", "
+ " enterprise id=0x" << std::hex << getVendorId() << std::dec;
+ // Iterate over all tuples and print their size and contents.
+ for (unsigned i = 0; i < getTuplesNum(); ++i) {
+ // The DHCPv4 V-I Vendor Class has enterprise id before every tuple.
+ if ((getUniverse() == V4) && (i > 0)) {
+ s << ", enterprise id=0x" << std::hex << getVendorId() << std::dec;
+ }
+ // Print the tuple.
+ s << ", data-len" << i << "=" << getTuple(i).getLength();
+ s << ", vendor-class-data" << i << "='" << getTuple(i) << "'";
+ }
+
+ return (s.str());
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcp/option_vendor_class.h b/src/lib/dhcp/option_vendor_class.h
new file mode 100644
index 0000000..d0bc148
--- /dev/null
+++ b/src/lib/dhcp/option_vendor_class.h
@@ -0,0 +1,201 @@
+// Copyright (C) 2014-2023 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 OPTION_VENDOR_CLASS_H
+#define OPTION_VENDOR_CLASS_H
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <util/buffer.h>
+#include <boost/shared_ptr.hpp>
+#include <dhcp/option_data_types.h>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This class encapsulates DHCPv6 Vendor Class and DHCPv4 V-I Vendor
+/// Class options.
+///
+/// The format of DHCPv6 Vendor Class option (16) is described in section 21.16
+/// of RFC 8415 and the format of the DHCPv4 V-I Vendor Class option (124) is
+/// described in section 3 of RFC3925. Each of these options carries enterprise
+/// id followed by the collection of tuples carrying opaque data. A single tuple
+/// consists of the field holding opaque data length and the actual data.
+/// In case of the DHCPv4 V-I Vendor Class each tuple is preceded by the
+/// 4-byte long enterprise id. Also, the field which carries the length of
+/// the tuple is 1-byte long for DHCPv4 V-I Vendor Class and 2-bytes long
+/// for the DHCPv6 Vendor Class option.
+///
+/// Whether the encapsulated format is DHCPv4 V-I Vendor Class or DHCPv6
+/// Vendor Class option is controlled by the @c u (universe) parameter passed
+/// to the constructor.
+///
+/// @Currently, the enterprise id field is set to a value of the first
+/// enterprise id occurrence in the parsed option. This assumes that
+/// all tuples in the same option are for the same vendor.
+class OptionVendorClass : public Option {
+public:
+
+ /// @brief Collection of opaque data tuples carried by the option.
+ typedef std::vector<OpaqueDataTuple> TuplesCollection;
+
+ /// @brief Constructor.
+ ///
+ /// This constructor instance of the DHCPv4 V-I Vendor Class option (124)
+ /// or DHCPv6 Vendor Class option (16), depending on universe specified.
+ /// If the universe is v4, the constructor adds one empty tuple to the
+ /// option, as per RFC3925, section 3. the complete option must hold at
+ /// least one data-len field for opaque data. If the specified universe
+ /// is v6, the constructor adds no tuples.
+ ///
+ /// @param u universe (v4 or v6).
+ /// @param vendor_id vendor enterprise id (unique 32-bit integer).
+ OptionVendorClass(Option::Universe u, const uint32_t vendor_id);
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates an instance of the option using a buffer with
+ /// on-wire data. It may throw an exception if the @c unpack method throws.
+ ///
+ /// @param u universe (v4 or v6)
+ /// @param begin Iterator pointing to the beginning of the buffer holding an
+ /// option.
+ /// @param end Iterator pointing to the end of the buffer holding an option.
+ OptionVendorClass(Option::Universe u, OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Copies this option and returns a pointer to the copy.
+ OptionPtr clone() const;
+
+ /// @brief Renders option into the buffer in the wire format.
+ ///
+ /// @param [out] buf Buffer to which the option is rendered.
+ /// @param check if set to false, allows options larger than 255 for v4
+ virtual void pack(isc::util::OutputBuffer& buf, bool check = true) const;
+
+ /// @brief Parses buffer holding an option.
+ ///
+ /// This function parses the buffer holding an option and initializes option
+ /// properties: enterprise ids and the collection of tuples.
+ ///
+ /// @param begin Iterator pointing to the beginning of the buffer holding an
+ /// option.
+ /// @param end Iterator pointing to the end of the buffer holding an option.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns enterprise id.
+ uint32_t getVendorId() const {
+ return (vendor_id_);
+ }
+
+ /// @brief Adds a new opaque data tuple to the option.
+ ///
+ /// @param tuple Tuple to be added.
+ /// @throw isc::BadValue if the type of the tuple doesn't match the
+ /// universe this option belongs to.
+ void addTuple(const OpaqueDataTuple& tuple);
+
+ /// @brief Replaces tuple at the specified index with a new tuple.
+ ///
+ /// This function replaces an opaque data tuple at the specified position
+ /// with the new tuple. If the specified index is out of range an exception
+ /// is thrown.
+ ///
+ /// @param at Index at which the tuple should be replaced.
+ /// @param tuple Tuple to be set.
+ /// @throw isc::OutOfRange if the tuple position is out of range.
+ /// @throw isc::BadValue if the type of the tuple doesn't match the
+ /// universe this option belongs to.
+ void setTuple(const size_t at, const OpaqueDataTuple& tuple);
+
+ /// @brief Returns opaque data tuple at the specified position.
+ ///
+ /// If the specified position is out of range an exception is thrown.
+ ///
+ /// @param at Index for which tuple to get.
+ /// @throw isc::OutOfRange if the tuple position is out of range.
+ OpaqueDataTuple getTuple(const size_t at) const;
+
+ /// @brief Returns the number of opaque data tuples added to the option.
+ size_t getTuplesNum() const {
+ return (tuples_.size());
+ }
+
+ /// @brief Returns collection of opaque data tuples carried in the option.
+ const TuplesCollection& getTuples() const {
+ return (tuples_);
+ }
+
+ /// @brief Checks if the Vendor Class holds the opaque data tuple with the
+ /// specified string.
+ ///
+ /// @param tuple_str String representation of the tuple being searched.
+ /// @return true if the specified tuple exists for this option.
+ bool hasTuple(const std::string& tuple_str) const;
+
+ /// @brief Returns the full length of the option, including option header.
+ virtual uint16_t len() const;
+
+ /// @brief Returns text representation of the option.
+ ///
+ /// @param indent Number of space characters before text.
+ /// @return Text representation of the option.
+ virtual std::string toText(int indent = 0) const;
+
+private:
+
+ /// @brief Returns option code appropriate for the specified universe.
+ ///
+ /// This function is called by the constructor to map the specified
+ /// universe to the option code.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @return DHCPv4 V-I Vendor Class or DHCPv6 Vendor Class option code.
+ static uint16_t getOptionCode(Option::Universe u) {
+ if (u == V4) {
+ return (DHO_VIVCO_SUBOPTIONS);
+ } else {
+ return (D6O_VENDOR_CLASS);
+ }
+ }
+
+ /// @brief Returns minimal length of the option for the given universe.
+ ///
+ /// For DHCPv6, The Vendor Class option mandates a 2-byte
+ /// OPTION_VENDOR_CLASS followed by a 2-byte option-len with a 4-byte
+ /// enterprise-number. While section 21.16 of RFC 8415 specifies that the
+ /// information contained within the data area can contain one or more
+ /// opaque fields, the inclusion of the vendor-class-data is not mandatory
+ /// and therefore not factored into the overall possible minimum length.
+ ///
+ /// For DHCPv4, The V-I Vendor Class option mandates a 1-byte option-code
+ /// followed by a 1-byte option-len with a 4-byte enterprise-number.
+ /// While section 3 of RFC3925 specifies that the information contained
+ /// within the per-vendor data area can contain one or more opaque fields,
+ /// the inclusion of the vendor-class-data is not mandatory and therefore
+ /// not factored into the overall possible minimum length.
+ uint16_t getMinimalLength() const {
+ return (getUniverse() == Option::V4 ? 6 : 8);
+ }
+
+ /// @brief Enterprise ID.
+ uint32_t vendor_id_;
+
+ /// @brief Collection of opaque data tuples carried by the option.
+ TuplesCollection tuples_;
+
+};
+
+/// @brief Defines a pointer to the @c OptionVendorClass.
+typedef boost::shared_ptr<OptionVendorClass> OptionVendorClassPtr;
+
+}
+}
+
+#endif // OPTION_VENDOR_CLASS_H
diff --git a/src/lib/dhcp/packet_queue.h b/src/lib/dhcp/packet_queue.h
new file mode 100644
index 0000000..04affc0
--- /dev/null
+++ b/src/lib/dhcp/packet_queue.h
@@ -0,0 +1,141 @@
+// Copyright (C) 2018-2022 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 PACKET_QUEUE_H
+#define PACKET_QUEUE_H
+
+#include <cc/data.h>
+#include <dhcp/socket_info.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+#include <sstream>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief Invalid queue parameter exception
+///
+/// Thrown when packet queue is supplied an invalid/missing parameter
+class InvalidQueueParameter : public Exception {
+public:
+ InvalidQueueParameter(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Enumerates choices between the two ends of the queue.
+enum class QueueEnd {
+ FRONT, // Typically the end packets are read from
+ BACK // Typically the end packets are written to
+};
+
+/// @brief Interface for managing a queue of inbound DHCP packets
+///
+/// This class serves as the abstract interface for packet queue
+/// implementations which may be used by @c IfaceMgr to store
+/// inbound packets until they are a dequeued for processing.
+///
+/// @tparam PacketTypePtr Type of packet the queue contains.
+/// This expected to be either isc::dhcp::Pkt4Ptr or isc::dhcp::Pkt6Ptr
+///
+/// @note Derivations of this class MUST BE thread-safe.
+template<typename PacketTypePtr>
+class PacketQueue {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param queue_type logical name of the queue implementation
+ /// Typically this is assigned by the factory that creates the
+ /// queue. It is the logical name used to register queue
+ /// implementations.
+ explicit PacketQueue(const std::string& queue_type)
+ : queue_type_(queue_type) {}
+
+ /// Virtual destructor
+ virtual ~PacketQueue(){};
+
+ /// @brief Adds a packet to the queue
+ ///
+ /// Adds the packet to the queue. Derivations determine
+ /// which packets to queue and how to queue them.
+ ///
+ /// @param packet packet to enqueue
+ /// @param source socket the packet came from
+ virtual void enqueuePacket(PacketTypePtr packet, const SocketInfo& source) = 0;
+
+ /// @brief Dequeues the next packet from the queue
+ ///
+ /// Dequeues the next packet (if any) and returns it. Derivations determine
+ /// how packets are dequeued.
+ ///
+ /// @return A pointer to dequeued packet, or an empty pointer
+ /// if the queue is empty.
+ virtual PacketTypePtr dequeuePacket() = 0;
+
+ /// @brief return True if the queue is empty.
+ virtual bool empty() const = 0;
+
+ /// @brief Returns the current number of packets in the buffer.
+ virtual size_t getSize() const = 0;
+
+ /// @brief Discards all packets currently in the buffer.
+ virtual void clear() = 0;
+
+ /// @brief Fetches operational information about the current state of
+ /// the queue
+ ///
+ /// Creates and returns an ElementPtr containing a single entry,
+ /// "queue-type". Derivations are expected to call this method first
+ /// and then add their own values. Since implementations may vary
+ /// widely on data of interest, this is structured as an ElementPtr
+ /// for broad latitude.
+ ///
+ /// @return an ElementPtr containing elements for values of interest
+ virtual data::ElementPtr getInfo() const {
+ data::ElementPtr info = data::Element::createMap();
+ info->set("queue-type", data::Element::create(queue_type_));
+ return (info);
+ }
+
+ /// @brief Fetches a JSON string representation of queue operational info
+ ///
+ /// This method calls @c getInfo() and then converts that into JSON text.
+ ///
+ /// @return string of JSON text
+ std::string getInfoStr() const {
+ data::ElementPtr info = getInfo();
+ std::ostringstream os;
+ info->toJSON(os);
+ return (os.str());
+ }
+
+ /// @return Fetches the logical name of the type of this queue.
+ std::string getQueueType() {
+ return (queue_type_);
+ };
+
+private:
+ /// @brief Logical name of the this queue's implementation type.
+ std::string queue_type_;
+
+};
+
+/// @brief Defines pointer to the DHCPv4 queue interface used at the application level.
+/// This is the type understood by IfaceMgr and the type that should be returned by
+/// DHCPv4 packet queue factories.
+typedef boost::shared_ptr<PacketQueue<Pkt4Ptr>> PacketQueue4Ptr;
+
+/// @brief Defines pointer to the DHCPv6 queue interface used at the application level.
+/// This is the type understood by IfaceMgr and the type that should be returned by
+/// DHCPv6 packet queue factories.
+typedef boost::shared_ptr<PacketQueue<Pkt6Ptr>> PacketQueue6Ptr;
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // PACKET_QUEUE_H
diff --git a/src/lib/dhcp/packet_queue_mgr.h b/src/lib/dhcp/packet_queue_mgr.h
new file mode 100644
index 0000000..4bb009a
--- /dev/null
+++ b/src/lib/dhcp/packet_queue_mgr.h
@@ -0,0 +1,189 @@
+// Copyright (C) 2018-2021 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 PACKET_QUEUE_MGR_H
+#define PACKET_QUEUE_MGR_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcp/packet_queue.h>
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Invalid Queue type exception
+///
+/// Thrown when a packet queue manager doesn't recognize the type of the queue.
+class InvalidQueueType : public Exception {
+public:
+ InvalidQueueType(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Packet Queue Managers (PQM).
+///
+/// Base class to manage the registry of packet queue implementations
+/// and the creation of and access to the current packet queue.
+///
+/// @tparam PacktQueueTypePtr Base type of packet queues managed by
+/// the manager (e.g. PacketQueue4Ptr, PacketQueue6Ptr).
+template<typename PacketQueueTypePtr>
+class PacketQueueMgr {
+public:
+ /// @brief Defines the type of the packet queue factory function.
+ ///
+ /// Factory function returns a pointer to the instance of the packet
+ /// queue created.
+ typedef std::function<PacketQueueTypePtr(data::ConstElementPtr)> Factory;
+
+ /// @brief Constructor.
+ PacketQueueMgr()
+ : factories_(), packet_queue_() {
+ }
+
+ /// @brief Registers new queue factory function for a given queue type.
+ ///
+ /// The typical usage of this function is to make the PQM aware of a
+ /// packet queue implementation. This implementation may exist
+ /// in a hooks library. In such a case, this function should be called from
+ /// the @c load function in this library. When the queue impl is registered,
+ /// the server will use it when required by the configuration, i.e. a
+ /// user specifies it in "queue-control:queue-type"
+ ///
+ /// If the given queue type has already been registered, perhaps
+ /// by another hooks library, the PQM will refuse to register another
+ /// of the same type.
+ ///
+ /// @param queue_type Queue type, e.g. "kea-ring4"
+ /// @param factory Pointer to the queue factory function.
+ ///
+ /// @return true if the queue type has been successfully registered, false
+ /// if the type already exists.
+ bool registerPacketQueueFactory(const std::string& queue_type,
+ Factory factory) {
+ // Check if this backend has been already registered.
+ if (factories_.count(queue_type)) {
+ return (false);
+ }
+
+ // Register the new backend.
+ factories_.insert(std::make_pair(queue_type, factory));
+ return (true);
+ }
+
+ /// @brief Unregisters the queue factory function for a given type.
+ ///
+ /// This function is used to remove the factory function for a given type.
+ /// Typically, it would be called when unloading the hook library which
+ /// loaded the type, and thus called by the library's @c unload function.
+ /// In addition to removing the factory, it will also destroy the current
+ /// queue if it is of the same queue-type as the factory being removed.
+ /// This avoids the nastiness that occurs when objecs are left in existence
+ /// after their library is unloaded.
+ ///
+ /// @param queue_type queue type, e.g. "kea-ring4".
+ ///
+ /// @return false if no factory for the given type was unregistered, true
+ /// if the factory was removed.
+ bool unregisterPacketQueueFactory(const std::string& queue_type) {
+ // Look for it.
+ auto index = factories_.find(queue_type);
+
+ // Not there so nothing to do.
+ if (index == factories_.end()) {
+ return (false);
+ }
+
+ // If the queue is of the type being unregistered, then remove it. We don't
+ // a queue instance outliving its library.
+ if ((packet_queue_) && (packet_queue_->getQueueType() == queue_type)) {
+ packet_queue_.reset();
+ }
+
+ // Remove the factory.
+ factories_.erase(index);
+
+ return (true);
+ }
+
+ /// @brief Create an instance of a packet queue.
+ ///
+ /// Replace the current packet queue with a new one based on the
+ /// given configuration parameters. The set of parameters must
+ /// contain at least "queue-type". This value is used to locate
+ /// the registered queue factory to invoke to create the new queue.
+ ///
+ /// The factory is passed the parameters verbatim for its use in
+ /// creating the new queue. Factories are expected to throw exceptions
+ /// on creation failure. Note the existing queue is not altered or
+ /// replaced unless the new queue is successfully created.
+ ///
+ /// @throw InvalidQueueParameter if parameters is not map that contains
+ /// "queue-type", InvalidQueueType if the queue type requested is not
+ /// supported.
+ /// @throw Unexpected if the backend factory function returned NULL.
+ void createPacketQueue(data::ConstElementPtr parameters) {
+ if (!parameters) {
+ isc_throw(Unexpected, "createPacketQueue - queue parameters is null");
+ }
+
+ // Get the database type to locate a factory function.
+ std::string queue_type ;
+ try {
+ queue_type = data::SimpleParser::getString(parameters, "queue-type");
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidQueueParameter, "queue-type missing or invalid: " << ex.what());
+ }
+
+ // Look up the factory.
+ auto index = factories_.find(queue_type);
+
+ // Punt if there is no matching factory.
+ if (index == factories_.end()) {
+ isc_throw(InvalidQueueType, "The type of the packet queue: '" <<
+ queue_type << "' is not supported"); }
+
+ // Call the factory to create the new queue.
+ // Factories should throw InvalidQueueParameter if given
+ // bad values in the control.
+ auto new_queue = index->second(parameters);
+ if (!new_queue) {
+ isc_throw(Unexpected, "Packet queue " << queue_type <<
+ " factory returned NULL");
+ }
+
+ // Replace the existing queue with the new one.
+ packet_queue_ = new_queue;
+ }
+
+ /// @brief Returns underlying packet queue.
+ PacketQueueTypePtr getPacketQueue() const {
+ return (packet_queue_);
+ }
+
+ /// @brief Destroys the current packet queue.
+ /// Any queued packets will be discarded.
+ void destroyPacketQueue() {
+ packet_queue_.reset();
+ }
+
+protected:
+ /// @brief A map holding registered backend factory functions.
+ std::map<std::string, Factory> factories_;
+
+ /// @brief the current queue_ ?
+ PacketQueueTypePtr packet_queue_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // PACKET_QUEUE_MGR_H
diff --git a/src/lib/dhcp/packet_queue_mgr4.cc b/src/lib/dhcp/packet_queue_mgr4.cc
new file mode 100644
index 0000000..1918a6d
--- /dev/null
+++ b/src/lib/dhcp/packet_queue_mgr4.cc
@@ -0,0 +1,36 @@
+// Copyright (C) 2018 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 <dhcp/packet_queue_ring.h>
+#include <dhcp/packet_queue_mgr4.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+const std::string PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 = "kea-ring4";
+
+PacketQueueMgr4::PacketQueueMgr4() {
+ // Register default queue factory
+ registerPacketQueueFactory(DEFAULT_QUEUE_TYPE4, [](data::ConstElementPtr parameters)
+ -> PacketQueue4Ptr {
+ size_t capacity;
+ try {
+ capacity = data::SimpleParser::getInteger(parameters, "capacity");
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidQueueParameter, DEFAULT_QUEUE_TYPE4 << " factory:"
+ " 'capacity' parameter is missing/invalid: " << ex.what());
+ }
+
+ PacketQueue4Ptr queue(new PacketQueueRing4(DEFAULT_QUEUE_TYPE4, capacity));
+ return (queue);
+ });
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/packet_queue_mgr4.h b/src/lib/dhcp/packet_queue_mgr4.h
new file mode 100644
index 0000000..33fd4e4
--- /dev/null
+++ b/src/lib/dhcp/packet_queue_mgr4.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2018 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 PACKET_QUEUE_MGR4_H
+#define PACKET_QUEUE_MGR4_H
+
+#include <dhcp/packet_queue_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet Queue Manager for DHPCv4 servers.
+///
+/// Implements the "manager" class which holds information about the
+/// supported DHCPv4 packet queue implementations and provides management
+/// of the current queue instance.
+class PacketQueueMgr4 : public PacketQueueMgr<PacketQueue4Ptr> {
+
+public:
+ /// @brief Logical name of the pre-registered, default queue implementation
+ static const std::string DEFAULT_QUEUE_TYPE4;
+
+ /// It registers a default factory for DHCPv4 queues.
+ PacketQueueMgr4();
+
+ /// @brief virtual Destructor
+ virtual ~PacketQueueMgr4(){}
+};
+
+/// @brief Defines a shared pointer to PacketQueueMgr4
+typedef boost::shared_ptr<PacketQueueMgr4> PacketQueueMgr4Ptr;
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // PACKET_QUEUE_MGR4_H
diff --git a/src/lib/dhcp/packet_queue_mgr6.cc b/src/lib/dhcp/packet_queue_mgr6.cc
new file mode 100644
index 0000000..2f08fdf
--- /dev/null
+++ b/src/lib/dhcp/packet_queue_mgr6.cc
@@ -0,0 +1,36 @@
+// Copyright (C) 2018 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 <dhcp/packet_queue_ring.h>
+#include <dhcp/packet_queue_mgr6.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+const std::string PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 = "kea-ring6";
+
+PacketQueueMgr6::PacketQueueMgr6() {
+ // Register default queue factory
+ registerPacketQueueFactory(DEFAULT_QUEUE_TYPE6, [](data::ConstElementPtr parameters)
+ -> PacketQueue6Ptr {
+ size_t capacity;
+ try {
+ capacity = data::SimpleParser::getInteger(parameters, "capacity");
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidQueueParameter, DEFAULT_QUEUE_TYPE6 << " factory:"
+ " 'capacity' parameter is missing/invalid: " << ex.what());
+ }
+
+ PacketQueue6Ptr queue(new PacketQueueRing6(DEFAULT_QUEUE_TYPE6, capacity));
+ return (queue);
+ });
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/packet_queue_mgr6.h b/src/lib/dhcp/packet_queue_mgr6.h
new file mode 100644
index 0000000..7711ac9
--- /dev/null
+++ b/src/lib/dhcp/packet_queue_mgr6.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2018 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 PACKET_QUEUE_MGR6_H
+#define PACKET_QUEUE_MGR6_H
+
+#include <dhcp/packet_queue_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet Queue Manager for DHPCv6 servers.
+///
+/// Implements the "manager" class which holds information about the
+/// supported DHCPv6 packet queue implementations and provides management
+/// of the current queue instance.
+class PacketQueueMgr6 : public PacketQueueMgr<PacketQueue6Ptr>,
+ public boost::noncopyable {
+public:
+ /// @brief Logical name of the pre-registered, default queue implementation
+ static const std::string DEFAULT_QUEUE_TYPE6;
+
+ /// @brief constructor.
+ ///
+ /// It registers a default factory for DHCPv6 queues.
+ PacketQueueMgr6();
+
+ /// @brief virtual Destructor
+ virtual ~PacketQueueMgr6(){}
+};
+
+/// @brief Defines a shared pointer to PacketQueueMgr6
+typedef boost::shared_ptr<PacketQueueMgr6> PacketQueueMgr6Ptr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // PACKET_QUEUE_MGR6_H
diff --git a/src/lib/dhcp/packet_queue_ring.h b/src/lib/dhcp/packet_queue_ring.h
new file mode 100644
index 0000000..1b6ae91
--- /dev/null
+++ b/src/lib/dhcp/packet_queue_ring.h
@@ -0,0 +1,263 @@
+// Copyright (C) 2018-2022 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 PACKET_QUEUE_RING_H
+#define PACKET_QUEUE_RING_H
+
+#include <dhcp/packet_queue.h>
+
+#include <boost/circular_buffer.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <sstream>
+#include <mutex>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief Provides a ring-buffer implementation of the PacketQueue interface.
+///
+/// @tparam PacketTypePtr Type of packet the queue contains.
+/// This expected to be either isc::dhcp::Pkt4Ptr or isc::dhcp::Pkt6Ptr
+template<typename PacketTypePtr>
+class PacketQueueRing : public PacketQueue<PacketTypePtr> {
+public:
+ /// @brief Minimum queue capacity permitted. Below five is pretty much
+ /// nonsensical.
+ static const size_t MIN_RING_CAPACITY = 5;
+
+ /// @brief Constructor
+ ///
+ /// @param queue_type logical name of the queue implementation
+ /// @param capacity maximum number of packets the queue can hold
+ PacketQueueRing(const std::string& queue_type, size_t capacity)
+ : PacketQueue<PacketTypePtr>(queue_type) {
+ queue_.set_capacity(capacity);
+ mutex_.reset(new std::mutex);
+ }
+
+ /// @brief virtual Destructor
+ virtual ~PacketQueueRing(){};
+
+ /// @brief Adds a packet to the queue
+ ///
+ /// Calls @c shouldDropPacket to determine if the packet should be queued
+ /// or dropped. If it should be queued it is added to the end of the
+ /// queue specified by the "to" parameter.
+ ///
+ /// @param packet packet to enqueue
+ /// @param source socket the packet came from
+ virtual void enqueuePacket(PacketTypePtr packet, const SocketInfo& source) {
+ if (!shouldDropPacket(packet, source)) {
+ pushPacket(packet);
+ }
+ }
+
+ /// @brief Dequeues the next packet from the queue
+ ///
+ /// Dequeues the next packet (if any) and returns it.
+ ///
+ /// @return A pointer to dequeued packet, or an empty pointer
+ /// if the queue is empty.
+ virtual PacketTypePtr dequeuePacket() {
+ eatPackets(QueueEnd::FRONT);
+ return (popPacket());
+ }
+
+ /// @brief Determines if a packet should be discarded.
+ ///
+ /// This function is called in @c enqueuePackets for each packet
+ /// in its packet list. It provides an opportunity to examine the
+ /// packet and its source and decide whether it should be dropped
+ /// or added to the queue. Derivations are expected to provide
+ /// implementations based on their own requirements. Bear in mind
+ /// that the packet has NOT been unpacked at this point. The default
+ /// implementation simply returns false (i.e. keep the packet).
+ ///
+ /// @return true if the packet should be dropped, false if it should be
+ /// kept.
+ virtual bool shouldDropPacket(PacketTypePtr /* packet */,
+ const SocketInfo& /* source */) {
+ return (false);
+ }
+
+ /// @brief Discards packets from one end of the queue.
+ ///
+ /// This function is called at the beginning of @c dequeuePacket and
+ /// provides an opportunity to examine and discard packets from
+ /// the queue prior to dequeuing the next packet to be
+ /// processed. Derivations are expected to provide implementations
+ /// based on their own requirements. The default implementation is to
+ /// to simply return without skipping any packets.
+ ///
+ /// @return The number of packets discarded.
+ virtual int eatPackets(const QueueEnd& /* from */) {
+ return (0);
+ }
+
+ /// @brief Pushes a packet onto the queue
+ ///
+ /// Adds a packet onto the end of queue specified.
+ ///
+ /// @param packet packet to add to the queue
+ /// @param to specifies the end of the queue to which the packet
+ /// should be added.
+ virtual void pushPacket(PacketTypePtr& packet, const QueueEnd& to=QueueEnd::BACK) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ if (to == QueueEnd::BACK) {
+ queue_.push_back(packet);
+ } else {
+ queue_.push_front(packet);
+ }
+ }
+
+ /// @brief Pops a packet from the queue
+ ///
+ /// Removes a packet from the end of the queue specified and returns it.
+ ///
+ /// @param from specifies the end of the queue from which the packet
+ /// should be taken. It locks the queue's Mutex upon entry.
+ ///
+ /// @return A pointer to dequeued packet, or an empty pointer
+ /// if the queue is empty.
+ virtual PacketTypePtr popPacket(const QueueEnd& from = QueueEnd::FRONT) {
+ PacketTypePtr packet;
+ std::lock_guard<std::mutex> lock(*mutex_);
+
+ if (queue_.empty()) {
+ return (packet);
+ }
+
+ if (from == QueueEnd::FRONT) {
+ packet = queue_.front();
+ queue_.pop_front();
+ } else {
+ packet = queue_.back();
+ queue_.pop_back();
+ }
+
+ return (packet);
+ }
+
+
+ /// @brief Gets the packet currently at one end of the queue
+ ///
+ /// Returns a pointer the packet at the specified end of the
+ /// queue without dequeuing it.
+ ///
+ /// @param from specifies which end of the queue to examine.
+ ///
+ /// @return A pointer to packet, or an empty pointer if the
+ /// queue is empty.
+ virtual const PacketTypePtr peek(const QueueEnd& from=QueueEnd::FRONT) const {
+ PacketTypePtr packet;
+ if (!queue_.empty()) {
+ packet = (from == QueueEnd::FRONT ? queue_.front() : queue_.back());
+ }
+
+ return (packet);
+ }
+
+ /// @brief Returns True if the queue is empty.
+ virtual bool empty() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (queue_.empty());
+ }
+
+ /// @brief Returns the maximum number of packets allowed in the buffer.
+ virtual size_t getCapacity() const {
+ return (queue_.capacity());
+ }
+
+ /// @brief Sets the maximum number of packets allowed in the buffer.
+ ///
+ /// @todo - do we want to change size on the fly? This might need
+ /// to be private, called only by constructor
+ ///
+ /// @throw BadValue if capacity is too low.
+ virtual void setCapacity(size_t capacity) {
+ if (capacity < MIN_RING_CAPACITY) {
+ isc_throw(BadValue, "Queue capacity of " << capacity
+ << " is invalid. It must be at least "
+ << MIN_RING_CAPACITY);
+ }
+
+ /// @todo should probably throw if it's zero
+ queue_.set_capacity(capacity);
+ }
+
+ /// @brief Returns the current number of packets in the buffer.
+ virtual size_t getSize() const {
+ return (queue_.size());
+ }
+
+ /// @brief Discards all packets currently in the buffer.
+ virtual void clear() {
+ queue_.clear();
+ }
+
+ /// @brief Fetches pertinent information
+ virtual data::ElementPtr getInfo() const {
+ data::ElementPtr info = PacketQueue<PacketTypePtr>::getInfo();
+ info->set("capacity", data::Element::create(static_cast<int64_t>(getCapacity())));
+ info->set("size", data::Element::create(static_cast<int64_t>(getSize())));
+ return (info);
+ }
+
+private:
+
+ /// @brief Packet queue
+ boost::circular_buffer<PacketTypePtr> queue_;
+
+ /// @brief Mutex for protecting queue accesses.
+ boost::scoped_ptr<std::mutex> mutex_;
+};
+
+
+/// @brief DHCPv4 packet queue buffer implementation
+///
+/// This implementation does not (currently) add any drop
+/// or packet skip logic, it operates as a verbatim ring
+/// queue for DHCPv4 packets.
+///
+class PacketQueueRing4 : public PacketQueueRing<Pkt4Ptr> {
+public:
+ /// @brief Constructor
+ ///
+ /// @param queue_type logical name of the queue implementation
+ /// @param capacity maximum number of packets the queue can hold
+ PacketQueueRing4(const std::string& queue_type, size_t capacity)
+ : PacketQueueRing(queue_type, capacity) {
+ };
+
+ /// @brief virtual Destructor
+ virtual ~PacketQueueRing4(){}
+};
+
+/// @brief DHCPv6 packet queue buffer implementation
+///
+/// This implementation does not (currently) add any drop
+/// or packet skip logic, it operates as a verbatim ring
+/// queue for DHCPv6 packets.
+///
+class PacketQueueRing6 : public PacketQueueRing<Pkt6Ptr> {
+public:
+ /// @brief Constructor
+ ///
+ /// @param queue_type logical name of the queue implementation
+ /// @param capacity maximum number of packets the queue can hold
+ PacketQueueRing6(const std::string& queue_type, size_t capacity)
+ : PacketQueueRing(queue_type, capacity) {
+ };
+
+ /// @brief virtual Destructor
+ virtual ~PacketQueueRing6(){}
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // PACKET_QUEUE_RING_H
diff --git a/src/lib/dhcp/pkt.cc b/src/lib/dhcp/pkt.cc
new file mode 100644
index 0000000..2622460
--- /dev/null
+++ b/src/lib/dhcp/pkt.cc
@@ -0,0 +1,314 @@
+// Copyright (C) 2014-2023 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 <utility>
+#include <dhcp/pkt.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/hwaddr.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+Pkt::Pkt(uint32_t transid, const isc::asiolink::IOAddress& local_addr,
+ const isc::asiolink::IOAddress& remote_addr, uint16_t local_port,
+ uint16_t remote_port)
+ : transid_(transid), iface_(""), ifindex_(UNSET_IFINDEX), local_addr_(local_addr),
+ remote_addr_(remote_addr), local_port_(local_port),
+ remote_port_(remote_port), buffer_out_(0), copy_retrieved_options_(false) {
+}
+
+Pkt::Pkt(const uint8_t* buf, uint32_t len, const isc::asiolink::IOAddress& local_addr,
+ const isc::asiolink::IOAddress& remote_addr, uint16_t local_port,
+ uint16_t remote_port)
+ : transid_(0), iface_(""), ifindex_(UNSET_IFINDEX), local_addr_(local_addr),
+ remote_addr_(remote_addr), local_port_(local_port),
+ remote_port_(remote_port), buffer_out_(0), copy_retrieved_options_(false) {
+ if (len != 0) {
+ if (buf == NULL) {
+ isc_throw(InvalidParameter, "data buffer passed to Pkt is NULL");
+ }
+ data_.resize(len);
+ memcpy(&data_[0], buf, len);
+ }
+}
+
+OptionCollection
+Pkt::cloneOptions() {
+ OptionCollection options;
+ for (auto const& option : options_) {
+ options.emplace(std::make_pair(option.second->getType(), option.second->clone()));
+ }
+ return (options);
+}
+
+void
+Pkt::addOption(const OptionPtr& opt) {
+ options_.insert(std::pair<int, OptionPtr>(opt->getType(), opt));
+}
+
+OptionPtr
+Pkt::getNonCopiedOption(const uint16_t type) const {
+ const auto& x = options_.find(type);
+ if (x != options_.end()) {
+ return (x->second);
+ }
+ return (OptionPtr());
+}
+
+OptionPtr
+Pkt::getOption(const uint16_t type) {
+ const auto& x = options_.find(type);
+ if (x != options_.end()) {
+ if (copy_retrieved_options_) {
+ OptionPtr option_copy = x->second->clone();
+ x->second = option_copy;
+ }
+ return (x->second);
+ }
+ return (OptionPtr()); // NULL
+}
+
+OptionCollection
+Pkt::getNonCopiedOptions(const uint16_t opt_type) const {
+ std::pair<OptionCollection::const_iterator,
+ OptionCollection::const_iterator> range = options_.equal_range(opt_type);
+ return (OptionCollection(range.first, range.second));
+}
+
+OptionCollection
+Pkt::getOptions(const uint16_t opt_type) {
+ OptionCollection options_copy;
+
+ std::pair<OptionCollection::iterator,
+ OptionCollection::iterator> range = options_.equal_range(opt_type);
+ // If options should be copied on retrieval, we should now iterate over
+ // matching options, copy them and replace the original ones with new
+ // instances.
+ if (copy_retrieved_options_) {
+ for (OptionCollection::iterator opt_it = range.first;
+ opt_it != range.second; ++opt_it) {
+ OptionPtr option_copy = opt_it->second->clone();
+ opt_it->second = option_copy;
+ }
+ }
+ // Finally, return updated options. This can also be empty in some cases.
+ return (OptionCollection(range.first, range.second));
+}
+
+bool
+Pkt::delOption(uint16_t type) {
+ const auto& x = options_.find(type);
+ if (x != options_.end()) {
+ options_.erase(x);
+ return (true); // delete successful
+ } else {
+ return (false); // can't find option to be deleted
+ }
+}
+
+bool
+Pkt::inClass(const ClientClass& client_class) {
+ return (classes_.contains(client_class));
+}
+
+void
+Pkt::addClass(const ClientClass& client_class, bool required) {
+ ClientClasses& classes = !required ? classes_ : required_classes_;
+ if (!classes.contains(client_class)) {
+ classes.insert(client_class);
+ static_cast<void>(subclasses_.push_back(SubClassRelation(client_class, client_class)));
+ }
+}
+
+void
+Pkt::addSubClass(const ClientClass& class_def, const ClientClass& subclass) {
+ if (!classes_.contains(class_def)) {
+ classes_.insert(class_def);
+ static_cast<void>(subclasses_.push_back(SubClassRelation(class_def, subclass)));
+ }
+ if (!classes_.contains(subclass)) {
+ classes_.insert(subclass);
+ static_cast<void>(subclasses_.push_back(SubClassRelation(subclass, subclass)));
+ }
+}
+
+void
+Pkt::updateTimestamp() {
+ timestamp_ = boost::posix_time::microsec_clock::universal_time();
+}
+
+void Pkt::repack() {
+ if (!data_.empty()) {
+ buffer_out_.writeData(&data_[0], data_.size());
+ }
+}
+
+void
+Pkt::setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& hw_addr) {
+ setHWAddrMember(htype, hlen, hw_addr, remote_hwaddr_);
+}
+
+void
+Pkt::setRemoteHWAddr(const HWAddrPtr& hw_addr) {
+ if (!hw_addr) {
+ isc_throw(BadValue, "Setting remote HW address to NULL is"
+ << " forbidden.");
+ }
+ remote_hwaddr_ = hw_addr;
+}
+
+void
+Pkt::setHWAddrMember(const uint8_t htype, const uint8_t,
+ const std::vector<uint8_t>& hw_addr,
+ HWAddrPtr& storage) {
+ storage.reset(new HWAddr(hw_addr, htype));
+}
+
+HWAddrPtr
+Pkt::getMAC(uint32_t hw_addr_src) {
+ HWAddrPtr mac;
+
+ /// @todo: Implement an array of method pointers instead of set of ifs
+
+ // Method 1: from raw sockets.
+ if (hw_addr_src & HWAddr::HWADDR_SOURCE_RAW) {
+ mac = getRemoteHWAddr();
+ if (mac) {
+ mac->source_ = HWAddr::HWADDR_SOURCE_RAW;
+ return (mac);
+ } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_RAW) {
+ // If we're interested only in RAW sockets as source of that info,
+ // there's no point in trying other options.
+ return (HWAddrPtr());
+ }
+ }
+
+ // Method 2: From client link-layer address option inserted by a relay
+ if (hw_addr_src & HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) {
+ mac = getMACFromIPv6RelayOpt();
+ if (mac) {
+ return (mac);
+ } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION) {
+ // If we're interested only in RFC6939 link layer address as source
+ // of that info, there's no point in trying other options.
+ return (HWAddrPtr());
+ }
+ }
+
+ // Method 3: Extracted from DUID-LLT or DUID-LL
+ if(hw_addr_src & HWAddr::HWADDR_SOURCE_DUID) {
+ mac = getMACFromDUID();
+ if (mac) {
+ return (mac);
+ } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_DUID) {
+ // If the only source allowed is DUID then we can skip the other
+ // methods.
+ return (HWAddrPtr());
+ }
+ }
+
+ // Method 4: Extracted from source IPv6 link-local address
+ if (hw_addr_src & HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL) {
+ mac = getMACFromSrcLinkLocalAddr();
+ if (mac) {
+ return (mac);
+ } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL) {
+ // If we're interested only in link-local addr as source of that
+ // info, there's no point in trying other options.
+ return (HWAddrPtr());
+ }
+ }
+
+ // Method 5: From remote-id option inserted by a relay
+ if(hw_addr_src & HWAddr::HWADDR_SOURCE_REMOTE_ID) {
+ mac = getMACFromRemoteIdRelayOption();
+ if (mac) {
+ return (mac);
+ } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_REMOTE_ID) {
+ // If the only source allowed is remote-id option then we can skip
+ // the other methods.
+ return (HWAddrPtr());
+ }
+ }
+
+ // Method 6: From subscriber-id option inserted by a relay
+
+ // Method 7: From docsis options
+ if (hw_addr_src & HWAddr::HWADDR_SOURCE_DOCSIS_CMTS) {
+ mac = getMACFromDocsisCMTS();
+ if (mac) {
+ return (mac);
+ } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_DOCSIS_CMTS) {
+ // If we're interested only in CMTS options as a source of that
+ // info, there's no point in trying other options.
+ return (HWAddrPtr());
+ }
+ }
+
+ // Method 8: From docsis options
+ if (hw_addr_src & HWAddr::HWADDR_SOURCE_DOCSIS_MODEM) {
+ mac = getMACFromDocsisModem();
+ if (mac) {
+ return (mac);
+ } else if (hw_addr_src == HWAddr::HWADDR_SOURCE_DOCSIS_MODEM) {
+ // If we're interested only in CMTS options as a source of that
+ // info, there's no point in trying other options.
+ return (HWAddrPtr());
+ }
+ }
+
+ // Ok, none of the methods were suitable. Return NULL.
+ return (HWAddrPtr());
+}
+
+HWAddrPtr
+Pkt::getMACFromIPv6(const isc::asiolink::IOAddress& addr) {
+ HWAddrPtr mac;
+
+ if (addr.isV6LinkLocal()) {
+ std::vector<uint8_t> bin = addr.toBytes();
+
+ // Double check that it's of appropriate size
+ if ((bin.size() == isc::asiolink::V6ADDRESS_LEN) &&
+ // Check that it's link-local (starts with fe80).
+ (bin[0] == 0xfe) && (bin[1] == 0x80) &&
+ // Check that u bit is set and g is clear.
+ // See Section 2.5.1 of RFC2373 for details.
+ ((bin[8] & 3) == 2) &&
+ // And that the IID is of EUI-64 type.
+ (bin[11] == 0xff) && (bin[12] == 0xfe)) {
+
+ // Remove 8 most significant bytes
+ bin.erase(bin.begin(), bin.begin() + 8);
+
+ // Ok, we're down to EUI-64 only now: XX:XX:XX:ff:fe:XX:XX:XX
+ bin.erase(bin.begin() + 3, bin.begin() + 5);
+
+ // MAC-48 to EUI-64 involves inverting u bit (see explanation
+ // in Section 2.5.1 of RFC2373). We need to revert that.
+ bin[0] = bin[0] ^ 2;
+
+ // Let's get the interface this packet was received on.
+ // We need it to get hardware type
+ IfacePtr iface = IfaceMgr::instance().getIface(iface_);
+ uint16_t hwtype = 0; // not specified
+ if (iface) {
+ hwtype = iface->getHWType();
+ }
+
+ mac.reset(new HWAddr(bin, hwtype));
+ mac->source_ = HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL;
+ }
+ }
+
+ return (mac);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/pkt.h b/src/lib/dhcp/pkt.h
new file mode 100644
index 0000000..f17f9f1
--- /dev/null
+++ b/src/lib/dhcp/pkt.h
@@ -0,0 +1,954 @@
+// Copyright (C) 2014-2023 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 PKT_H
+#define PKT_H
+
+#include <asiolink/io_address.h>
+#include <util/buffer.h>
+#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/classify.h>
+#include <hooks/callout_handle_associate.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <limits>
+#include <utility>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief A value used to signal that the interface index was not set.
+/// That means that more than UNSET_IFINDEX interfaces are not supported.
+/// That's fine, since it would have overflowed with UNSET_IFINDEX + 1 anyway.
+constexpr unsigned int UNSET_IFINDEX = std::numeric_limits<unsigned int>::max();
+
+/// @brief RAII object enabling copying options retrieved from the
+/// packet.
+///
+/// This object enables copying retrieved options from a packet within
+/// a scope in which this object exists. When the object goes out of scope
+/// copying options is disabled. This is applicable in cases when the
+/// server is going to invoke a callout (hook library) where copying options
+/// must be enabled by default. When the callouts return copying options
+/// should be disabled. The use of RAII object eliminates the need for
+/// explicitly re-disabling options copying and is safer in case of
+/// exceptions thrown by callouts and a presence of multiple exit points.
+///
+/// @tparam PktType Type of the packet, e.g. Pkt4, Pkt6, Pkt4o6.
+template<typename PktType>
+class ScopedEnableOptionsCopy {
+public:
+
+ /// @brief Pointer to an encapsulated packet.
+ typedef boost::shared_ptr<PktType> PktTypePtr;
+
+ /// @brief Constructor.
+ ///
+ /// Enables options copying on a packet(s).
+ ///
+ /// @param pkt1 Pointer to first packet.
+ /// @param pkt2 Optional pointer to the second packet.
+ ScopedEnableOptionsCopy(const PktTypePtr& pkt1,
+ const PktTypePtr& pkt2 = PktTypePtr())
+ : pkts_(pkt1, pkt2) {
+ if (pkt1) {
+ pkt1->setCopyRetrievedOptions(true);
+ }
+ if (pkt2) {
+ pkt2->setCopyRetrievedOptions(true);
+ }
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Disables options copying on a packets.
+ ~ScopedEnableOptionsCopy() {
+ if (pkts_.first) {
+ pkts_.first->setCopyRetrievedOptions(false);
+ }
+ if (pkts_.second) {
+ pkts_.second->setCopyRetrievedOptions(false);
+ }
+ }
+
+private:
+
+ /// @brief Holds a pair of pointers of the packets.
+ std::pair<PktTypePtr, PktTypePtr> pkts_;
+};
+
+/// @brief Base class for classes representing DHCP messages.
+///
+/// This is a base class that holds common information (e.g. source
+/// and destination ports) and operations (e.g. add, get, delete options)
+/// for derived classes representing both DHCPv4 and DHCPv6 messages.
+/// The @c Pkt4 and @c Pkt6 classes derive from it.
+///
+/// @note This is abstract class. Please instantiate derived classes
+/// such as @c Pkt4 or @c Pkt6.
+class Pkt : public hooks::CalloutHandleAssociate {
+protected:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor is typically used for transmitted messages as it
+ /// creates an empty (no options) packet. The constructor is protected,
+ /// so only derived classes can call it. Pkt class cannot be instantiated
+ /// anyway, because it is an abstract class.
+ ///
+ /// @param transid transaction-id
+ /// @param local_addr local IPv4 or IPv6 address
+ /// @param remote_addr remote IPv4 or IPv6 address
+ /// @param local_port local UDP (one day also TCP) port
+ /// @param remote_port remote UDP (one day also TCP) port
+ Pkt(uint32_t transid, const isc::asiolink::IOAddress& local_addr,
+ const isc::asiolink::IOAddress& remote_addr, uint16_t local_port,
+ uint16_t remote_port);
+
+ /// @brief Constructor.
+ ///
+ /// This constructor is typically used for received messages as it takes
+ /// a buffer that's going to be parsed as one of arguments. The constructor
+ /// is protected, so only derived classes can call it. Pkt class cannot be
+ /// instantiated anyway, because it is an abstract class.
+ ///
+ /// @param buf pointer to a buffer that contains on-wire data
+ /// @param len length of the pointer specified in buf
+ /// @param local_addr local IPv4 or IPv6 address
+ /// @param remote_addr remote IPv4 or IPv6 address
+ /// @param local_port local UDP (one day also TCP) port
+ /// @param remote_port remote UDP (one day also TCP) port
+ Pkt(const uint8_t* buf, uint32_t len,
+ const isc::asiolink::IOAddress& local_addr,
+ const isc::asiolink::IOAddress& remote_addr, uint16_t local_port,
+ uint16_t remote_port);
+
+public:
+
+ /// @brief Prepares on-wire format of DHCP (either v4 or v6) packet.
+ ///
+ /// Prepares on-wire format of message and all its options.
+ /// A caller must ensure that options are stored in options_ field
+ /// prior to calling this method.
+ ///
+ /// Output buffer will be stored in buffer_out_.
+ /// The buffer_out_ should be cleared before writing to the buffer
+ /// in the derived classes.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective
+ /// implementations of this method.
+ ///
+ /// @throw InvalidOperation if packing fails
+ virtual void pack() = 0;
+
+ /// @brief Parses on-wire form of DHCP (either v4 or v6) packet.
+ ///
+ /// Parses received packet, stored in on-wire format in data_.
+ ///
+ /// Will create a collection of option objects that will
+ /// be stored in options_ container.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective
+ /// implementations of this method.
+ ///
+ /// Method will throw exception if packet parsing fails.
+ ///
+ /// @throw tbd
+ virtual void unpack() = 0;
+
+ /// @brief Returns reference to output buffer.
+ ///
+ /// Returned buffer will contain reasonable data only for
+ /// output (TX) packet and after pack() was called.
+ ///
+ /// RX packet or TX packet before pack() will return buffer with
+ /// zero length. This buffer is returned as non-const, so hooks
+ /// framework (and user's callouts) can modify them if needed
+ ///
+ /// @note This buffer is only valid till object that returned it exists.
+ ///
+ /// @return reference to output buffer
+ isc::util::OutputBuffer& getBuffer() {
+ return (buffer_out_);
+ }
+
+ /// @brief Adds an option to this packet.
+ ///
+ /// Derived classes may provide more specialized implementations.
+ /// In particular @c Pkt4 provides one that checks if option is
+ /// unique.
+ ///
+ /// @param opt option to be added.
+ virtual void addOption(const OptionPtr& opt);
+
+ /// @brief Attempts to delete first suboption of requested type.
+ ///
+ /// If there are several options of the same type present, only
+ /// the first option will be deleted.
+ ///
+ /// @param type Type of option to be deleted.
+ ///
+ /// @return true if option was deleted, false if no such option existed
+ bool delOption(uint16_t type);
+
+ /// @brief Returns text representation primary packet identifiers
+ ///
+ /// This method is intended to be used to provide as a consistent way to
+ /// identify packets within log statements. Derivations should supply
+ /// there own implementation.
+ ///
+ /// @return string with text representation
+ virtual std::string getLabel() const {
+ isc_throw(NotImplemented, "Pkt::getLabel()");
+ }
+
+ /// @brief Returns text representation of the packet.
+ ///
+ /// This function is useful mainly for debugging.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective
+ /// implementations of this method.
+ ///
+ /// @return string with text representation
+ virtual std::string toText() const = 0;
+
+ /// @brief Returns packet size in binary format.
+ ///
+ /// Returns size of the packet in on-wire format or size needed to store
+ /// it in on-wire format.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective
+ /// implementations of this method.
+ ///
+ /// @return packet size in bytes
+ virtual size_t len() = 0;
+
+ /// @brief Returns message type (e.g. 1 = SOLICIT).
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective
+ /// implementations of this method.
+ ///
+ /// @return message type
+ virtual uint8_t getType() const = 0;
+
+ /// @brief Sets message type (e.g. 1 = SOLICIT).
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt4 and @c Pkt6 class have respective
+ /// implementations of this method.
+ ///
+ /// @param type message type to be set
+ virtual void setType(uint8_t type) = 0;
+
+ /// @brief Returns name of the DHCP message.
+ ///
+ /// For all unsupported messages the derived classes must return
+ /// "UNKNOWN".
+ ///
+ /// @return Pointer to "const" string containing DHCP message name.
+ /// The implementations in the derived classes should statically
+ /// allocate returned strings and the caller must not release the
+ /// returned pointer.
+ virtual const char* getName() const = 0;
+
+ /// @brief Sets transaction-id value.
+ ///
+ /// @param transid transaction-id to be set.
+ void setTransid(uint32_t transid) {
+ transid_ = transid;
+ }
+
+ /// @brief Returns value of transaction-id field.
+ ///
+ /// @return transaction-id
+ uint32_t getTransid() const {
+ return (transid_);
+ }
+
+ /// @brief Checks whether a client belongs to a given class.
+ ///
+ /// @param client_class name of the class
+ /// @return true if belongs
+ bool inClass(const isc::dhcp::ClientClass& client_class);
+
+ /// @brief Adds a specified class to the packet.
+ ///
+ /// A class can be added to the same packet repeatedly. Any additional
+ /// attempts to add to a packet the class already added, will be
+ /// ignored silently.
+ ///
+ /// @param client_class name of the class to be added
+ /// @param required the class is marked for required evaluation
+ void addClass(const isc::dhcp::ClientClass& client_class,
+ bool required = false);
+
+ /// @brief Adds a specified subclass to the packet.
+ ///
+ /// A subclass can be added to the same packet repeatedly. Any additional
+ /// attempts to add to a packet the subclass already added, will be
+ /// ignored silently.
+ ///
+ /// @param class_def name of the class definition to be added
+ /// @param subclass name of the subclass to be added
+ void addSubClass(const isc::dhcp::ClientClass& class_def,
+ const isc::dhcp::ClientClass& subclass);
+
+ /// @brief Returns the class set
+ ///
+ /// @note This should be used only to iterate over the class set.
+ /// @param required return classes or required to be evaluated classes.
+ /// @return if required is false (the default) the classes the
+ /// packet belongs to else the classes which are required to be
+ /// evaluated.
+ const ClientClasses& getClasses(bool required = false) const {
+ return (!required ? classes_ : required_classes_);
+ }
+
+ /// @brief Returns the class set including template classes associated with
+ /// subclasses
+ ///
+ /// @note This should be used only to iterate over the class set.
+ /// @note SubClasses are always last.
+ /// @param required return classes or required to be evaluated classes.
+ /// @return if required is false (the default) the classes the
+ /// packet belongs to else the classes which are required to be
+ /// evaluated.
+ const SubClassRelationContainer& getSubClassesRelations() const {
+ return (subclasses_);
+ }
+
+ /// @brief Unparsed data (in received packets).
+ ///
+ /// @warning This public member is accessed by derived
+ /// classes directly. One of such derived classes is
+ /// @ref perfdhcp::PerfPkt6. The impact on derived classes'
+ /// behavior must be taken into consideration before making
+ /// changes to this member such as access scope restriction or
+ /// data format change etc.
+ OptionBuffer data_;
+
+protected:
+
+ /// @brief Returns the first option of specified type without copying.
+ ///
+ /// This method is internally used by the @ref Pkt class and derived
+ /// classes to retrieve a pointer to the specified option. This
+ /// method doesn't copy the option before returning it to the
+ /// caller.
+ ///
+ /// @param type Option type.
+ ///
+ /// @return Pointer to the option of specified type or NULL pointer
+ /// if such option is not present.
+ OptionPtr getNonCopiedOption(const uint16_t type) const;
+
+ /// @brief Returns all option instances of specified type without
+ /// copying.
+ ///
+ /// This is a variant of @ref getOptions method, which returns a collection
+ /// of options without copying them. This method should be only used by
+ /// the @ref Pkt6 class and derived classes. Any external callers should
+ /// use @ref getOptions which copies option instances before returning them
+ /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+ ///
+ /// @param opt_type Option code.
+ ///
+ /// @return Collection of options found.
+ OptionCollection getNonCopiedOptions(const uint16_t opt_type) const;
+
+public:
+
+ /// @brief Clones all options so that they can be safely modified.
+ ///
+ /// @return A container with option clones.
+ OptionCollection cloneOptions();
+
+ /// @brief Returns the first option of specified type.
+ ///
+ /// Returns the first option of specified type. Note that in DHCPv6 several
+ /// instances of the same option are allowed (and frequently used).
+ ///
+ /// The options will be only returned after unpack() is called.
+ ///
+ /// @param type option type we are looking for
+ ///
+ /// @return pointer to found option (or NULL)
+ OptionPtr getOption(const uint16_t type);
+
+ /// @brief Returns all instances of specified type.
+ ///
+ /// Returns all instances of options of the specified type. DHCPv6 protocol
+ /// allows (and uses frequently) multiple instances.
+ ///
+ /// @param type option type we are looking for
+ /// @return instance of option collection with requested options
+ isc::dhcp::OptionCollection getOptions(const uint16_t type);
+
+ /// @brief Controls whether the option retrieved by the @ref Pkt::getOption
+ /// should be copied before being returned.
+ ///
+ /// Setting this value to true enables the mechanism of copying options
+ /// retrieved from the packet to prevent accidental modifications of
+ /// options that shouldn't be modified. The typical use case for this
+ /// mechanism is to prevent hook library from modifying instance of
+ /// an option within the packet that would also affect the value for
+ /// this option within the Kea configuration structures.
+ ///
+ /// Kea doesn't copy option instances which it stores in the packet.
+ /// It merely copy pointers into the packets. Thus, any modification
+ /// to an option would change the value of this option in the
+ /// Kea configuration. To prevent this, option copying should be
+ /// enabled prior to passing the pointer to a packet to a hook library.
+ ///
+ /// Not only does this method cause the server to copy
+ /// an option, but the copied option also replaces the original
+ /// option within the packet. The option can be then freely modified
+ /// and the modifications will only affect the instance of this
+ /// option within the packet but not within the server configuration.
+ ///
+ /// @param copy Indicates if the options should be copied when
+ /// retrieved (if true), or not copied (if false).
+ virtual void setCopyRetrievedOptions(const bool copy) {
+ copy_retrieved_options_ = copy;
+ }
+
+ /// @brief Returns whether the copying of retrieved options is enabled.
+ ///
+ /// Also see @ref setCopyRetrievedOptions.
+ ///
+ /// @return true if retrieved options are copied.
+ bool isCopyRetrievedOptions() const {
+ return (copy_retrieved_options_);
+ }
+
+ /// @brief Update packet timestamp.
+ ///
+ /// Updates packet timestamp. This method is invoked
+ /// by interface manager just before sending or
+ /// just after receiving it.
+ /// @throw isc::Unexpected if timestamp update failed
+ void updateTimestamp();
+
+ /// @brief Returns packet timestamp.
+ ///
+ /// Returns packet timestamp value updated when
+ /// packet is received or send.
+ ///
+ /// @return packet timestamp.
+ const boost::posix_time::ptime& getTimestamp() const {
+ return timestamp_;
+ }
+
+ /// @brief Set packet timestamp.
+ ///
+ /// Sets packet timestamp to arbitrary value.
+ /// It is used by perfdhcp tool and should not be used elsewhere.
+ void setTimestamp(boost::posix_time::ptime& timestamp) {
+ timestamp_ = timestamp;
+ }
+
+ /// @brief Copies content of input buffer to output buffer.
+ ///
+ /// This is mostly a diagnostic function. It is being used for sending
+ /// received packet. Received packet is stored in data_, but
+ /// transmitted data is stored in buffer_out_. If we want to send packet
+ /// that we just received, a copy between those two buffers is necessary.
+ void repack();
+
+ /// @brief Sets remote IP address.
+ ///
+ /// @param remote specifies remote address
+ void setRemoteAddr(const isc::asiolink::IOAddress& remote) {
+ remote_addr_ = remote;
+ }
+
+ /// @brief Returns remote IP address.
+ ///
+ /// @return remote address
+ const isc::asiolink::IOAddress& getRemoteAddr() const {
+ return (remote_addr_);
+ }
+
+ /// @brief Sets local IP address.
+ ///
+ /// @param local specifies local address
+ void setLocalAddr(const isc::asiolink::IOAddress& local) {
+ local_addr_ = local;
+ }
+
+ /// @brief Returns local IP address.
+ ///
+ /// @return local address
+ const isc::asiolink::IOAddress& getLocalAddr() const {
+ return (local_addr_);
+ }
+
+ /// @brief Sets local UDP (and soon TCP) port.
+ ///
+ /// This sets a local port, i.e. destination port for recently received
+ /// packet or a source port for to be transmitted packet.
+ ///
+ /// @param local specifies local port
+ void setLocalPort(uint16_t local) {
+ local_port_ = local;
+ }
+
+ /// @brief Returns local UDP (and soon TCP) port.
+ ///
+ /// This sets a local port, i.e. destination port for recently received
+ /// packet or a source port for to be transmitted packet.
+ ///
+ /// @return local port
+ uint16_t getLocalPort() const {
+ return (local_port_);
+ }
+
+ /// @brief Sets remote UDP (and soon TCP) port.
+ ///
+ /// This sets a remote port, i.e. source port for recently received
+ /// packet or a destination port for to be transmitted packet.
+ ///
+ /// @param remote specifies remote port
+ void setRemotePort(uint16_t remote) {
+ remote_port_ = remote;
+ }
+
+ /// @brief Returns remote port.
+ ///
+ /// @return remote port
+ uint16_t getRemotePort() const {
+ return (remote_port_);
+ }
+
+ /// @brief Sets interface index.
+ ///
+ /// @param ifindex specifies interface index.
+ void setIndex(const unsigned int ifindex) {
+ ifindex_ = ifindex;
+ }
+
+ /// @brief Resets interface index to negative value.
+ void resetIndex() {
+ ifindex_ = UNSET_IFINDEX;
+ }
+
+ /// @brief Returns interface index.
+ ///
+ /// @return interface index
+ int getIndex() const {
+ return (ifindex_);
+ }
+
+ /// @brief Checks if interface index has been set.
+ ///
+ /// @return true if interface index set, false otherwise.
+ bool indexSet() const {
+ return (ifindex_ != UNSET_IFINDEX);
+ }
+
+ /// @brief Returns interface name.
+ ///
+ /// Returns interface name over which packet was received or is
+ /// going to be transmitted.
+ ///
+ /// @return interface name
+ std::string getIface() const {
+ return (iface_);
+ }
+
+ /// @brief Sets interface name.
+ ///
+ /// Sets interface name over which packet was received or is
+ /// going to be transmitted.
+ ///
+ /// @param iface The interface name
+ void setIface(const std::string& iface) {
+ iface_ = iface;
+ }
+
+ /// @brief Sets remote hardware address.
+ ///
+ /// Sets hardware address (MAC) from an existing HWAddr structure.
+ /// The remote address is a destination address for outgoing
+ /// packet and source address for incoming packet. When this
+ /// is an outgoing packet, this address will be used to
+ /// construct the link layer header.
+ ///
+ /// @param hw_addr structure representing HW address.
+ ///
+ /// @throw BadValue if addr is null
+ void setRemoteHWAddr(const HWAddrPtr& hw_addr);
+
+ /// @brief Sets remote hardware address.
+ ///
+ /// Sets the destination hardware (MAC) address for the outgoing packet
+ /// or source HW address for the incoming packet. When this
+ /// is an outgoing packet this address will be used to construct
+ /// the link layer header.
+ ///
+ /// @note mac_addr must be a buffer of at least hlen bytes.
+ ///
+ /// In a typical case, hlen field would be redundant, as it could
+ /// be extracted from mac_addr.size(). However, the difference is
+ /// when running on exotic hardware, like Infiniband, that had
+ /// MAC addresses 20 bytes long. In that case, hlen is set to zero
+ /// in DHCPv4.
+ ///
+ /// @param htype hardware type (will be sent in htype field)
+ /// @param hlen hardware length (will be sent in hlen field)
+ /// @param hw_addr pointer to hardware address
+ void setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& hw_addr);
+
+ /// @brief Returns the remote HW address obtained from raw sockets.
+ ///
+ /// @return remote HW address.
+ HWAddrPtr getRemoteHWAddr() const {
+ return (remote_hwaddr_);
+ }
+
+ /// @brief Returns MAC address.
+ ///
+ /// The difference between this method and getRemoteHWAddr() is that
+ /// getRemoteHWAddr() returns only what was obtained from raw sockets.
+ /// This method is more generic and can attempt to obtain MAC from
+ /// varied sources: raw sockets, client-id, link-local IPv6 address,
+ /// and various relay options.
+ ///
+ /// @note Technically the proper term for this information is a link layer
+ /// address, but it is frequently referred to MAC or hardware address.
+ /// Since we're calling the feature "MAC addresses in DHCPv6", we decided
+ /// to keep the name of getMAC().
+ ///
+ /// hw_addr_src takes a combination of bit values specified in
+ /// HWADDR_SOURCE_* constants.
+ ///
+ /// @param hw_addr_src a bitmask that specifies hardware address source
+ HWAddrPtr getMAC(uint32_t hw_addr_src);
+
+ /// @brief Virtual destructor.
+ ///
+ /// There is nothing to clean up here, but since there are virtual methods,
+ /// we define virtual destructor to ensure that derived classes will have
+ /// a virtual one, too.
+ virtual ~Pkt() {
+ }
+
+ /// @brief Classes this packet belongs to.
+ ///
+ /// This field is public, so the code outside of Pkt4 or Pkt6 class can
+ /// iterate over existing classes. Having it public also solves the problem
+ /// of returned reference lifetime. It is preferred to use @ref inClass and
+ /// @ref addClass to operate on this field.
+ ClientClasses classes_;
+
+ /// @brief Classes which are required to be evaluated.
+ ///
+ /// The comment on @ref classes_ applies here.
+ ///
+ /// Before output option processing these classes will be evaluated
+ /// and if evaluation status is true added to the previous collection.
+ ClientClasses required_classes_;
+
+ /// @brief SubClasses this packet belongs to.
+ ///
+ /// This field is public, so the code outside of Pkt4 or Pkt6 class can
+ /// iterate over existing classes. Having it public also solves the problem
+ /// of returned reference lifetime. It is preferred to use @ref inClass and
+ /// @ref addSubClass to operate on this field.
+ SubClassRelationContainer subclasses_;
+
+ /// @brief Collection of options present in this message.
+ ///
+ /// @warning This public member is accessed by derived
+ /// classes directly. One of such derived classes is
+ /// @ref perfdhcp::PerfPkt6. The impact on derived classes'
+ /// behavior must be taken into consideration before making
+ /// changes to this member such as access scope restriction or
+ /// data format change etc.
+ isc::dhcp::OptionCollection options_;
+
+protected:
+
+ /// @brief Attempts to obtain MAC address from source link-local
+ /// IPv6 address
+ ///
+ /// This method is called from getMAC(HWADDR_SOURCE_IPV6_LINK_LOCAL)
+ /// and should not be called directly. It is not 100% reliable.
+ /// The source IPv6 address does not necessarily have to be link-local
+ /// (may be global or ULA) and even if it's link-local, it doesn't
+ /// necessarily be based on EUI-64. For example, Windows supports
+ /// RFC4941, which randomized IID part of the link-local address.
+ /// If this method fails, it will return NULL.
+ ///
+ /// For direct message, it attempts to use remote_addr_ field. For relayed
+ /// message, it uses peer-addr of the first relay.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt6 class have respective implementation.
+ /// This method is not applicable to DHCPv4.
+ ///
+ /// @return hardware address (or NULL)
+ virtual HWAddrPtr getMACFromSrcLinkLocalAddr() = 0;
+
+ /// @brief Attempts to obtain MAC address from relay option
+ /// client-linklayer-addr
+ ///
+ /// This method is called from getMAC(HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION)
+ /// and should not be called directly. It will extract the client's
+ /// MAC/Hardware address from option client_linklayer_addr (RFC6939)
+ /// inserted by the relay agent closest to the client.
+ /// If this method fails, it will return NULL.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt6 class have respective implementation.
+ /// This method is not applicable to DHCPv4.
+ ///
+ /// @return hardware address (or NULL)
+ virtual HWAddrPtr getMACFromIPv6RelayOpt() = 0;
+
+ /// @brief Attempts to obtain MAC address from DUID-LL or DUID-LLT.
+ ///
+ /// This method is called from getMAC(HWADDR_SOURCE_DUID) and should not be
+ /// called directly. It will attempt to extract MAC address information
+ /// from DUID if its type is LLT or LL. If this method fails, it will
+ /// return NULL.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt6 class have respective implementation.
+ /// This method is not applicable to DHCPv4.
+ ///
+ /// @return hardware address (or NULL)
+ virtual HWAddrPtr getMACFromDUID() = 0;
+
+ /// @brief Attempts to obtain MAC address from remote-id relay option.
+ ///
+ /// This method is called from getMAC(HWADDR_SOURCE_REMOTE_ID) and should not be
+ /// called directly. It will attempt to extract MAC address information
+ /// from remote-id option inserted by a relay agent closest to the client.
+ /// If this method fails, it will return NULL.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt6 class have respective implementation.
+ /// This method is not applicable to DHCPv4.
+ ///
+ /// @return hardware address (or NULL)
+ virtual HWAddrPtr getMACFromRemoteIdRelayOption() = 0;
+
+ /// @brief Attempts to convert IPv6 address into MAC.
+ ///
+ /// Utility method that attempts to convert link-local IPv6 address to the
+ /// MAC address. That works only for link-local IPv6 addresses that are
+ /// based on EUI-64.
+ ///
+ /// @note This method uses hardware type of the interface the packet was
+ /// received on. If you have multiple access technologies in your network
+ /// (e.g. client connected to WiFi that relayed the traffic to the server
+ /// over Ethernet), hardware type may be invalid.
+ ///
+ /// @param addr IPv6 address to be converted
+ /// @return hardware address (or NULL)
+ HWAddrPtr
+ getMACFromIPv6(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Attempts to extract MAC/Hardware address from DOCSIS options
+ /// inserted by the modem itself.
+ ///
+ /// This is a generic mechanism for extracting hardware address from the
+ /// DOCSIS options.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt6 class have respective implementation.
+ /// This method is currently not implemented in DHCPv4.
+ ///
+ /// @return hardware address (if necessary DOCSIS suboptions are present)
+ virtual HWAddrPtr getMACFromDocsisModem() = 0;
+
+ /// @brief Attempts to extract MAC/Hardware address from DOCSIS options
+ /// inserted by the CMTS (the relay agent)
+ ///
+ /// This is a generic mechanism for extracting hardware address from the
+ /// DOCSIS options.
+ ///
+ /// @note This is a pure virtual method and must be implemented in
+ /// the derived classes. The @c Pkt6 class have respective implementation.
+ /// This method is currently not implemented in DHCPv4.
+ ///
+ /// @return hardware address (if necessary DOCSIS suboptions are present)
+ virtual HWAddrPtr getMACFromDocsisCMTS() = 0;
+
+ /// Transaction-id (32 bits for v4, 24 bits for v6)
+ uint32_t transid_;
+
+ /// Name of the network interface the packet was received/to be sent over.
+ std::string iface_;
+
+ /// @brief Interface index.
+ ///
+ /// Each network interface has assigned an unique ifindex.
+ /// It is a functional equivalent of a name, but sometimes more useful, e.g.
+ /// when using odd systems that allow spaces in interface names.
+ unsigned int ifindex_;
+
+ /// @brief Local IP (v4 or v6) address.
+ ///
+ /// Specifies local IPv4 or IPv6 address. It is a destination address for
+ /// received packet, and a source address if it packet is being transmitted.
+ isc::asiolink::IOAddress local_addr_;
+
+ /// @brief Remote IP address.
+ ///
+ /// Specifies local IPv4 or IPv6 address. It is source address for received
+ /// packet and a destination address for packet being transmitted.
+ isc::asiolink::IOAddress remote_addr_;
+
+ /// local TDP or UDP port
+ uint16_t local_port_;
+
+ /// remote TCP or UDP port
+ uint16_t remote_port_;
+
+ /// Output buffer (used during message transmission)
+ ///
+ /// @warning This protected member is accessed by derived
+ /// classes directly. One of such derived classes is
+ /// @ref perfdhcp::PerfPkt6. The impact on derived classes'
+ /// behavior must be taken into consideration before making
+ /// changes to this member such as access scope restriction or
+ /// data format change etc.
+ isc::util::OutputBuffer buffer_out_;
+
+ /// @brief Indicates if a copy of the retrieved option should be
+ /// returned when @ref Pkt::getOption is called.
+ ///
+ /// @see the documentation for @ref Pkt::setCopyRetrievedOptions.
+ bool copy_retrieved_options_;
+
+ /// packet timestamp
+ boost::posix_time::ptime timestamp_;
+
+ // remote HW address (src if receiving packet, dst if sending packet)
+ HWAddrPtr remote_hwaddr_;
+
+private:
+
+ /// @brief Generic method that validates and sets HW address.
+ ///
+ /// This is a generic method used by all modifiers of this class
+ /// which set class members representing HW address.
+ ///
+ /// @param htype hardware type.
+ /// @param hlen hardware length.
+ /// @param hw_addr pointer to actual hardware address.
+ /// @param [out] storage pointer to a class member to be modified.
+ ///
+ /// @throw isc::OutOfRange if invalid HW address specified.
+ virtual void setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& hw_addr,
+ HWAddrPtr& storage);
+};
+
+/// @brief A pointer to either Pkt4 or Pkt6 packet
+typedef boost::shared_ptr<isc::dhcp::Pkt> PktPtr;
+
+/// @brief RAII object enabling duplication of the stored options and restoring
+/// the original options on destructor.
+///
+/// This object enables duplication of the stored options and restoring the
+/// original options on destructor. When the object goes out of scope, the
+/// initial options are restored. This is applicable in cases when the server is
+/// going to invoke a callout (hook library) where the list of options in the
+/// packet will be modified. This can also be used to restore the initial
+/// suboptions of an option when the suboptions are changed (e.g. when splitting
+/// long options and suboptions). The use of RAII object eliminates the need for
+/// explicitly copying and restoring the list of options and is safer in case of
+/// exceptions thrown by callouts and a presence of multiple exit points.
+class ScopedSubOptionsCopy {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates a copy of the initial options on an option.
+ ///
+ /// @param opt Pointer to the option.
+ ScopedSubOptionsCopy(const OptionPtr& opt) : option_(opt) {
+ if (opt) {
+ options_ = opt->getMutableOptions();
+ }
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Restores the initial options on a packet.
+ ~ScopedSubOptionsCopy() {
+ if (option_) {
+ option_->getMutableOptions() = options_;
+ }
+ }
+
+private:
+
+ /// @brief Holds a pointer to the option.
+ OptionPtr option_;
+
+ /// @brief Holds the initial options.
+ OptionCollection options_;
+};
+
+/// @brief RAII object enabling duplication of the stored options and restoring
+/// the original options on destructor.
+///
+/// This object enables duplication of the stored options and restoring the
+/// original options on destructor. When the object goes out of scope, the
+/// initial options are restored. This is applicable in cases when the server is
+/// going to invoke a callout (hook library) where the list of options in the
+/// packet will be modified. The use of RAII object eliminates the need for
+/// explicitly copying and restoring the list of options and is safer in case of
+/// exceptions thrown by callouts and a presence of multiple exit points.
+///
+/// @tparam PktType Type of the packet, e.g. Pkt4, Pkt6, Pkt4o6.
+template<typename PktType>
+class ScopedPktOptionsCopy {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates a copy of the initial options on a packet.
+ ///
+ /// @param pkt Pointer to the packet.
+ ScopedPktOptionsCopy(PktType& pkt) : pkt_(pkt), options_(pkt.options_) {
+ pkt_.options_ = pkt_.cloneOptions();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Restores the initial options on a packet.
+ ~ScopedPktOptionsCopy() {
+ pkt_.options_ = options_;
+ }
+
+private:
+
+ /// @brief Holds a reference to the packet.
+ PktType& pkt_;
+
+ /// @brief Holds the initial options.
+ OptionCollection options_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
new file mode 100644
index 0000000..595adfb
--- /dev/null
+++ b/src/lib/dhcp/pkt4.cc
@@ -0,0 +1,618 @@
+// Copyright (C) 2011-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_int.h>
+#include <dhcp/pkt4.h>
+#include <exceptions/exceptions.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Default address used in Pkt4 constructor
+const IOAddress DEFAULT_ADDRESS("0.0.0.0");
+}
+
+namespace isc {
+namespace dhcp {
+
+Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
+ : Pkt(transid, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT, DHCP4_CLIENT_PORT),
+ op_(DHCPTypeToBootpType(msg_type)), hwaddr_(new HWAddr()), hops_(0), secs_(0), flags_(0),
+ ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS),
+ giaddr_(DEFAULT_ADDRESS) {
+ memset(sname_, 0, MAX_SNAME_LEN);
+ memset(file_, 0, MAX_FILE_LEN);
+
+ setType(msg_type);
+}
+
+Pkt4::Pkt4(const uint8_t* data, size_t len)
+ : Pkt(data, len, DEFAULT_ADDRESS, DEFAULT_ADDRESS, DHCP4_SERVER_PORT, DHCP4_CLIENT_PORT),
+ op_(BOOTREQUEST), hwaddr_(new HWAddr()), hops_(0), secs_(0), flags_(0),
+ ciaddr_(DEFAULT_ADDRESS), yiaddr_(DEFAULT_ADDRESS), siaddr_(DEFAULT_ADDRESS),
+ giaddr_(DEFAULT_ADDRESS) {
+
+ if (len < DHCPV4_PKT_HDR_LEN) {
+ isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
+ << ") received, at least " << DHCPV4_PKT_HDR_LEN
+ << " is expected.");
+ }
+ memset(sname_, 0, MAX_SNAME_LEN);
+ memset(file_, 0, MAX_FILE_LEN);
+}
+
+size_t
+Pkt4::len() {
+ size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header
+
+ // ... and sum of lengths of all options
+ for (const auto& it : options_) {
+ length += it.second->len();
+ }
+
+ return (length);
+}
+
+void
+Pkt4::pack() {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
+ }
+
+ // This object is necessary to restore the packet options after performing
+ // splitOptions4 when function scope ends. It creates a container of option
+ // clones which are split and packed.
+ ScopedPkt4OptionsCopy scoped_options(*this);
+
+ // Clear the output buffer to make sure that consecutive calls to pack()
+ // will not result in concatenation of multiple packet copies.
+ buffer_out_.clear();
+
+ try {
+ size_t hw_len = hwaddr_->hwaddr_.size();
+
+ buffer_out_.writeUint8(op_);
+ buffer_out_.writeUint8(hwaddr_->htype_);
+ buffer_out_.writeUint8(hw_len < MAX_CHADDR_LEN ?
+ hw_len : MAX_CHADDR_LEN);
+ buffer_out_.writeUint8(hops_);
+ buffer_out_.writeUint32(transid_);
+ buffer_out_.writeUint16(secs_);
+ buffer_out_.writeUint16(flags_);
+ buffer_out_.writeUint32(ciaddr_.toUint32());
+ buffer_out_.writeUint32(yiaddr_.toUint32());
+ buffer_out_.writeUint32(siaddr_.toUint32());
+ buffer_out_.writeUint32(giaddr_.toUint32());
+
+
+ if ((hw_len > 0) && (hw_len <= MAX_CHADDR_LEN)) {
+ // write up to 16 bytes of the hardware address (CHADDR field is 16
+ // bytes long in DHCPv4 message).
+ buffer_out_.writeData(&hwaddr_->hwaddr_[0],
+ (hw_len < MAX_CHADDR_LEN ?
+ hw_len : MAX_CHADDR_LEN) );
+ hw_len = MAX_CHADDR_LEN - hw_len;
+ } else {
+ hw_len = MAX_CHADDR_LEN;
+ }
+
+ // write (len) bytes of padding
+ if (hw_len > 0) {
+ vector<uint8_t> zeros(hw_len, 0);
+ buffer_out_.writeData(&zeros[0], hw_len);
+ }
+
+ buffer_out_.writeData(sname_, MAX_SNAME_LEN);
+ buffer_out_.writeData(file_, MAX_FILE_LEN);
+
+ // write DHCP magic cookie
+ buffer_out_.writeUint32(DHCP_OPTIONS_COOKIE);
+
+ /// Create a ManagedScopedOptionsCopyContainer to handle storing and
+ /// restoration of copied options.
+ ManagedScopedOptionsCopyContainer scoped_options;
+
+ // The RFC3396 adds support for long options split over multiple options
+ // using the same code.
+ // The long options are split in multiple CustomOption instances which
+ // hold the data. As a result, the option type of the newly created
+ // options will differ from the ones instantiated by the
+ // @ref OptionDefinition::optionFactory. At this stage the server should
+ // not do anything useful with the options beside packing.
+ LibDHCP::splitOptions4(options_, scoped_options.scoped_options_);
+
+ // Call packOptions4() with parameter,"top", true. This invokes
+ // logic to emit the message type option first.
+ LibDHCP::packOptions4(buffer_out_, options_, true);
+
+ // add END option that indicates end of options
+ // (End option is very simple, just a 255 octet)
+ buffer_out_.writeUint8(DHO_END);
+ } catch(const Exception& e) {
+ // An exception is thrown and message will be written to Logger
+ isc_throw(InvalidOperation, e.what());
+ }
+}
+
+void
+Pkt4::unpack() {
+ // input buffer (used during message reception)
+ isc::util::InputBuffer buffer_in(&data_[0], data_.size());
+
+ if (buffer_in.getLength() < DHCPV4_PKT_HDR_LEN) {
+ isc_throw(OutOfRange, "Received truncated DHCPv4 packet (len="
+ << buffer_in.getLength() << " received, at least "
+ << DHCPV4_PKT_HDR_LEN << "is expected");
+ }
+
+ op_ = buffer_in.readUint8();
+ uint8_t htype = buffer_in.readUint8();
+ uint8_t hlen = buffer_in.readUint8();
+ hops_ = buffer_in.readUint8();
+ transid_ = buffer_in.readUint32();
+ secs_ = buffer_in.readUint16();
+ flags_ = buffer_in.readUint16();
+ ciaddr_ = IOAddress(buffer_in.readUint32());
+ yiaddr_ = IOAddress(buffer_in.readUint32());
+ siaddr_ = IOAddress(buffer_in.readUint32());
+ giaddr_ = IOAddress(buffer_in.readUint32());
+
+ vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
+ buffer_in.readVector(hw_addr, MAX_CHADDR_LEN);
+ buffer_in.readData(sname_, MAX_SNAME_LEN);
+ buffer_in.readData(file_, MAX_FILE_LEN);
+
+ hw_addr.resize(hlen);
+
+ hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
+
+ if (buffer_in.getLength() == buffer_in.getPosition()) {
+ // this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
+ // particular, it does not have magic cookie, a 4 byte sequence that
+ // differentiates between DHCP and RFC 951 BOOTP packets.
+ isc_throw(InvalidOperation, "Received BOOTP packet without vendor information extensions.");
+ }
+
+ if (buffer_in.getLength() - buffer_in.getPosition() < 4) {
+ // there is not enough data to hold magic DHCP cookie
+ isc_throw(Unexpected, "Truncated or no DHCP packet.");
+ }
+
+ uint32_t magic = buffer_in.readUint32();
+ if (magic != DHCP_OPTIONS_COOKIE) {
+ isc_throw(Unexpected, "Invalid or missing DHCP magic cookie");
+ }
+
+ size_t opts_len = buffer_in.getLength() - buffer_in.getPosition();
+ vector<uint8_t> opts_buffer;
+
+ // Use readVector because a function which parses option requires
+ // a vector as an input.
+ buffer_in.readVector(opts_buffer, opts_len);
+
+ size_t offset = LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE, options_, deferred_options_, false);
+
+ // If offset is not equal to the size and there is no DHO_END,
+ // then something is wrong here. We either parsed past input
+ // buffer (bug in our code) or we haven't parsed everything
+ // (received trailing garbage or truncated option).
+ //
+ // Invoking Jon Postel's law here: be conservative in what you send, and be
+ // liberal in what you accept. There's no easy way to log something from
+ // libdhcp++ library, so we just choose to be silent about remaining
+ // bytes. We also need to quell compiler warning about unused offset
+ // variable.
+ //
+ // if ((offset != size) && (opts_buffer[offset] != DHO_END)) {
+ // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size
+ // << ", were able to parse " << offset << " bytes.");
+ // }
+ (void)offset;
+
+ // The RFC3396 adds support for multiple options using the same code fused
+ // into long options.
+ // All instances of the same option are fused together, including merging
+ // the suboption lists and fusing suboptions. As a result, the options will
+ // store more than 255 bytes of data and the regex parsers can effectively
+ // access the entire data.
+ LibDHCP::fuseOptions4(options_);
+
+ // Kea supports multiple vendor options so it needs to split received and
+ // fused options in multiple OptionVendor instances.
+ LibDHCP::extendVendorOptions4(options_);
+
+ // No need to call check() here. There are thorough tests for this
+ // later (see Dhcp4Srv::accept()). We want to drop the packet later,
+ // so we'll be able to log more detailed drop reason.
+}
+
+uint8_t Pkt4::getType() const {
+ OptionPtr generic = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE);
+ if (!generic) {
+ return (DHCP_NOTYPE);
+ }
+
+ // Check if Message Type is specified as OptionInt<uint8_t>
+ boost::shared_ptr<OptionInt<uint8_t> > type_opt =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic);
+ if (type_opt) {
+ return (type_opt->getValue());
+ }
+
+ // Try to use it as generic option
+ return (generic->getUint8());
+}
+
+void Pkt4::setType(uint8_t dhcp_type) {
+ OptionPtr opt = getNonCopiedOption(DHO_DHCP_MESSAGE_TYPE);
+ if (opt) {
+
+ // There is message type option already, update it. It seems that
+ // we do have two types of objects representing message-type option.
+ // It would be more preferable to use only one type, but there's no
+ // easy way to enforce it.
+ //
+ // One is an instance of the Option class. It stores type in
+ // Option::data_, so Option::setUint8() and Option::getUint8() can be
+ // used. The other one is an instance of OptionInt<uint8_t> and
+ // it stores message type as integer, hence
+ // OptionInt<uint8_t>::getValue() and OptionInt<uint8_t>::setValue()
+ // should be used.
+ boost::shared_ptr<OptionInt<uint8_t> > type_opt =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >(opt);
+ if (type_opt) {
+ type_opt->setValue(dhcp_type);
+ } else {
+ opt->setUint8(dhcp_type);
+ }
+ } else {
+ // There is no message type option yet, add it
+ opt.reset(new OptionInt<uint8_t>(Option::V4, DHO_DHCP_MESSAGE_TYPE,
+ dhcp_type));
+ addOption(opt);
+ }
+}
+
+const char*
+Pkt4::getName(const uint8_t type) {
+ static const char* DHCPDISCOVER_NAME = "DHCPDISCOVER";
+ static const char* DHCPOFFER_NAME = "DHCPOFFER";
+ static const char* DHCPREQUEST_NAME = "DHCPREQUEST";
+ static const char* DHCPDECLINE_NAME = "DHCPDECLINE";
+ static const char* DHCPACK_NAME = "DHCPACK";
+ static const char* DHCPNAK_NAME = "DHCPNAK";
+ static const char* DHCPRELEASE_NAME = "DHCPRELEASE";
+ static const char* DHCPINFORM_NAME = "DHCPINFORM";
+ static const char* DHCPLEASEQUERY_NAME = "DHCPLEASEQUERY";
+ static const char* DHCPLEASEUNASSIGNED_NAME = "DHCPLEASEUNASSIGNED";
+ static const char* DHCPLEASEUNKNOWN_NAME = "DHCPLEASEUNKNOWN";
+ static const char* DHCPLEASEACTIVE_NAME = "DHCPLEASEACTIVE";
+ static const char* DHCPBULKLEASEQUERY_NAME = "DHCPBULKLEASEQUERY";
+ static const char* DHCPLEASEQUERYDONE_NAME = "DHCPLEASEQUERYDONE";
+ static const char* DHCPLEASEQUERYSTATUS_NAME = "DHCPLEASEQUERYSTATUS";
+ static const char* DHCPTLS_NAME = "DHCPTLS";
+ static const char* UNKNOWN_NAME = "UNKNOWN";
+
+ switch (type) {
+ case DHCPDISCOVER:
+ return (DHCPDISCOVER_NAME);
+
+ case DHCPOFFER:
+ return (DHCPOFFER_NAME);
+
+ case DHCPREQUEST:
+ return (DHCPREQUEST_NAME);
+
+ case DHCPDECLINE:
+ return (DHCPDECLINE_NAME);
+
+ case DHCPACK:
+ return (DHCPACK_NAME);
+
+ case DHCPNAK:
+ return (DHCPNAK_NAME);
+
+ case DHCPRELEASE:
+ return (DHCPRELEASE_NAME);
+
+ case DHCPINFORM:
+ return (DHCPINFORM_NAME);
+
+ case DHCPLEASEQUERY:
+ return (DHCPLEASEQUERY_NAME);
+
+ case DHCPLEASEUNASSIGNED:
+ return (DHCPLEASEUNASSIGNED_NAME);
+
+ case DHCPLEASEUNKNOWN:
+ return (DHCPLEASEUNKNOWN_NAME);
+
+ case DHCPLEASEACTIVE:
+ return (DHCPLEASEACTIVE_NAME);
+
+ case DHCPBULKLEASEQUERY:
+ return (DHCPBULKLEASEQUERY_NAME);
+
+ case DHCPLEASEQUERYDONE:
+ return (DHCPLEASEQUERYDONE_NAME);
+
+ case DHCPLEASEQUERYSTATUS:
+ return (DHCPLEASEQUERYSTATUS_NAME);
+
+ case DHCPTLS:
+ return (DHCPTLS_NAME);
+
+ default:
+ ;
+ }
+ return (UNKNOWN_NAME);
+}
+
+const char*
+Pkt4::getName() const {
+ // getType() is now exception safe. Even if there's no option 53 (message
+ // type), it now returns 0 rather than throw. getName() is able to handle
+ // 0 and unknown message types.
+ return (Pkt4::getName(getType()));
+}
+
+std::string
+Pkt4::getLabel() const {
+
+ /// @todo If and when client id is extracted into Pkt4, this method should
+ /// use the instance member rather than fetch it every time.
+ std::string suffix;
+ ClientIdPtr client_id;
+ OptionPtr client_opt = getNonCopiedOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (client_opt) {
+ try {
+ client_id = ClientIdPtr(new ClientId(client_opt->getData()));
+ } catch (...) {
+ // ClientId may throw if the client-id is too short.
+ suffix = " (malformed client-id)";
+ }
+ }
+
+ std::ostringstream label;
+ try {
+ label << makeLabel(hwaddr_, client_id, transid_);
+ } catch (...) {
+ // This should not happen with the current code, but we may add extra
+ // sanity checks in the future that would possibly throw if
+ // the hwaddr length is 0.
+ label << " (malformed hw address)";
+ }
+
+ label << suffix;
+ return (label.str());
+}
+
+std::string
+Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id,
+ const uint32_t transid) {
+ // Create label with HW address and client identifier.
+ stringstream label;
+ label << makeLabel(hwaddr, client_id);
+
+ // Append transaction id.
+ label << ", tid=0x" << hex << transid << dec;
+
+ return label.str();
+}
+
+std::string
+Pkt4::makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id) {
+ stringstream label;
+ label << "[" << (hwaddr ? hwaddr->toText() : "no hwaddr info")
+ << "], cid=[" << (client_id ? client_id->toText() : "no info")
+ << "]";
+
+ return label.str();
+}
+
+std::string
+Pkt4::toText() const {
+ stringstream output;
+ output << "local_address=" << local_addr_ << ":" << local_port_
+ << ", remote_address=" << remote_addr_
+ << ":" << remote_port_ << ", msg_type=";
+
+ // Try to obtain message type.
+ uint8_t msg_type = getType();
+ if (msg_type != DHCP_NOTYPE) {
+ output << getName(msg_type) << " (" << static_cast<int>(msg_type) << ")";
+ } else {
+ // Message Type option is missing.
+ output << "(missing)";
+ }
+
+ output << ", transid=0x" << hex << transid_ << dec;
+
+ if (!options_.empty()) {
+ output << "," << std::endl << "options:";
+ for (const auto& opt : options_) {
+ try {
+ output << std::endl << opt.second->toText(2);
+ } catch (...) {
+ output << "(unknown)" << std::endl;
+ }
+ }
+
+ } else {
+ output << ", message contains no options";
+ }
+
+ return (output.str());
+}
+
+void
+Pkt4::setHWAddr(uint8_t htype, uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, hwaddr_);
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting DHCPv4 chaddr field to NULL"
+ << " is forbidden");
+ }
+ hwaddr_ = addr;
+}
+
+void
+Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr,
+ HWAddrPtr& hw_addr) {
+ /// @todo Rewrite this once support for client-identifier option
+ /// is implemented (ticket 1228?)
+ if (hlen > MAX_CHADDR_LEN) {
+ isc_throw(OutOfRange, "Hardware address (len=" << static_cast<uint32_t>(hlen)
+ << ") too long. Max " << MAX_CHADDR_LEN << " supported.");
+
+ } else if (mac_addr.empty() && (hlen > 0) ) {
+ isc_throw(OutOfRange, "Invalid HW Address specified");
+ }
+
+ /// @todo: what if mac_addr.size() doesn't match hlen?
+ /// We would happily copy over hardware address that is possibly
+ /// too long or doesn't match hlen value.
+ hw_addr.reset(new HWAddr(mac_addr, htype));
+}
+
+void
+Pkt4::setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, local_hwaddr_);
+}
+
+void
+Pkt4::setLocalHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting local HW address to NULL is"
+ << " forbidden.");
+ }
+ local_hwaddr_ = addr;
+}
+
+void
+Pkt4::setSname(const uint8_t* sname, size_t snameLen /*= MAX_SNAME_LEN*/) {
+ if (snameLen > MAX_SNAME_LEN) {
+ isc_throw(OutOfRange, "sname field (len=" << snameLen
+ << ") too long, Max " << MAX_SNAME_LEN << " supported.");
+
+ } else if (sname == NULL) {
+ isc_throw(InvalidParameter, "Invalid sname specified");
+ }
+
+ std::copy(sname, (sname + snameLen), sname_);
+ if (snameLen < MAX_SNAME_LEN) {
+ std::fill((sname_ + snameLen), (sname_ + MAX_SNAME_LEN), 0);
+ }
+
+ // No need to store snameLen as any empty space is filled with 0s
+}
+
+void
+Pkt4::setFile(const uint8_t* file, size_t fileLen /*= MAX_FILE_LEN*/) {
+ if (fileLen > MAX_FILE_LEN) {
+ isc_throw(OutOfRange, "file field (len=" << fileLen
+ << ") too long, Max " << MAX_FILE_LEN << " supported.");
+
+ } else if (file == NULL) {
+ isc_throw(InvalidParameter, "Invalid file name specified");
+ }
+
+ std::copy(file, (file + fileLen), file_);
+ if (fileLen < MAX_FILE_LEN) {
+ std::fill((file_ + fileLen), (file_ + MAX_FILE_LEN), 0);
+ }
+
+ // No need to store fileLen as any empty space is filled with 0s
+}
+
+uint8_t
+// cppcheck-suppress unusedFunction
+Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
+ switch (dhcpType) {
+ case DHCPDISCOVER:
+ case DHCPREQUEST:
+ case DHCPDECLINE:
+ case DHCPRELEASE:
+ case DHCPINFORM:
+ case DHCPLEASEQUERY:
+ case DHCPBULKLEASEQUERY:
+ return (BOOTREQUEST);
+
+ case DHCPACK:
+ case DHCPNAK:
+ case DHCPOFFER:
+ case DHCPLEASEUNASSIGNED:
+ case DHCPLEASEUNKNOWN:
+ case DHCPLEASEACTIVE:
+ case DHCPLEASEQUERYDONE:
+ return (BOOTREPLY);
+
+ default:
+ isc_throw(OutOfRange, "Invalid message type: "
+ << static_cast<int>(dhcpType) );
+ }
+}
+
+uint8_t
+Pkt4::getHtype() const {
+ if (!hwaddr_) {
+ return (HTYPE_UNDEFINED);
+ }
+ return (hwaddr_->htype_);
+}
+
+uint8_t
+Pkt4::getHlen() const {
+ if (!hwaddr_) {
+ return (0);
+ }
+ uint8_t len = hwaddr_->hwaddr_.size();
+ return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN);
+}
+
+void
+Pkt4::addOption(const OptionPtr& opt) {
+ // Check for uniqueness (DHCPv4 options must be unique)
+ if (getNonCopiedOption(opt->getType())) {
+ isc_throw(BadValue, "Option " << opt->getType()
+ << " already present in this message.");
+ }
+
+ Pkt::addOption(opt);
+}
+
+bool
+Pkt4::isRelayed() const {
+ return (!giaddr_.isV4Zero() && !giaddr_.isV4Bcast());
+}
+
+std::string
+Pkt4::getHWAddrLabel() const {
+ std::ostringstream label;
+ label << "hwaddr=";
+ hwaddr_ ? label << hwaddr_->toText(false) : label << "(undefined)";
+ return (label.str());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
new file mode 100644
index 0000000..cdf3d13
--- /dev/null
+++ b/src/lib/dhcp/pkt4.h
@@ -0,0 +1,560 @@
+// Copyright (C) 2011-2023 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 PKT4_H
+#define PKT4_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <util/buffer.h>
+#include <dhcp/option.h>
+#include <dhcp/classify.h>
+#include <dhcp/pkt.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <iostream>
+#include <vector>
+#include <set>
+#include <list>
+
+#include <time.h>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief Represents DHCPv4 packet
+///
+/// This class represents a single DHCPv4 packet. It handles both incoming
+/// and transmitted packets, parsing incoming options, options handling
+/// (add, get, remove), on-wire assembly, sanity checks and other operations.
+/// This specific class has several DHCPv4-specific methods, but it uses a lot
+/// of common operations from its base @c Pkt class that is shared with Pkt6.
+class Pkt4 : public Pkt {
+public:
+
+ /// length of the CHADDR field in DHCPv4 message
+ const static size_t MAX_CHADDR_LEN = 16;
+
+ /// length of the SNAME field in DHCPv4 message
+ const static size_t MAX_SNAME_LEN = 64;
+
+ /// length of the FILE field in DHCPv4 message
+ const static size_t MAX_FILE_LEN = 128;
+
+ /// specifies DHCPv4 packet header length (fixed part)
+ const static size_t DHCPV4_PKT_HDR_LEN = 236;
+
+ /// Mask for the value of flags field in the DHCPv4 message
+ /// to check whether client requested broadcast response.
+ const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
+
+ /// Constructor, used in replying to a message.
+ ///
+ /// @param msg_type type of message (e.g. DHCPDISCOVER=1)
+ /// @param transid transaction-id
+ Pkt4(uint8_t msg_type, uint32_t transid);
+
+ /// @brief Constructor, used in message reception.
+ ///
+ /// Creates new message. Pkt4 will copy data to bufferIn_
+ /// buffer on creation.
+ ///
+ /// @param data pointer to received data
+ /// @param len size of buffer to be allocated for this packet.
+ Pkt4(const uint8_t* data, size_t len);
+
+ /// @brief Prepares on-wire format of DHCPv4 packet.
+ ///
+ /// Prepares on-wire format of message and all its options.
+ /// Options must be stored in options_ field.
+ /// Output buffer will be stored in buffer_out_.
+ /// The buffer_out_ is cleared before writing to the buffer.
+ ///
+ /// @throw InvalidOperation if packing fails
+ virtual void pack();
+
+ /// @brief Parses on-wire form of DHCPv4 packet.
+ ///
+ /// Parses received packet, stored in on-wire format in bufferIn_.
+ ///
+ /// Will create a collection of option objects that will
+ /// be stored in options_ container.
+ ///
+ /// Method with throw exception if packet parsing fails.
+ virtual void unpack();
+
+ /// @brief Returns text representation of the primary packet identifiers
+ ///
+ /// This method is intended to be used to provide a consistent way to
+ /// identify packets within log statements. It is an instance-level
+ /// wrapper around static makeLabel(). See this method for string
+ /// content.
+ ///
+ /// This method is exception safe.
+ ///
+ /// @return string with text representation
+ std::string getLabel() const;
+
+ /// @brief Returns text representation of the given packet identifiers
+ ///
+ /// @param hwaddr - hardware address to include in the string, it may be
+ /// NULL.
+ /// @param client_id - client id to include in the string, it may be NULL.
+ /// to include in the string
+ /// @param transid - numeric transaction id to include in the string
+ ///
+ /// @return string with text representation
+ static std::string makeLabel(const HWAddrPtr& hwaddr,
+ const ClientIdPtr& client_id,
+ const uint32_t transid);
+
+ /// @brief Returns text representation of the given packet identifiers.
+ ///
+ /// This variant of the method does not include transaction id.
+ ///
+ /// @param hwaddr hardware address to include in the string, it may be
+ /// NULL.
+ /// @param client_id client id to include in the string, it may be NULL.
+ static std::string makeLabel(const HWAddrPtr& hwaddr, const ClientIdPtr& client_id);
+
+ /// @brief Returns text representation of the packet.
+ ///
+ /// This function is useful mainly for debugging.
+ ///
+ /// @return string with text representation
+ std::string toText() const;
+
+ /// @brief Returns the size of the required buffer to build the packet.
+ ///
+ /// Returns the size of the required buffer to build the packet with
+ /// the current set of packet options.
+ ///
+ /// @return number of bytes required to build this packet
+ size_t len();
+
+ /// @brief Sets hops field.
+ ///
+ /// @param hops value to be set
+ void setHops(uint8_t hops) { hops_ = hops; };
+
+ /// @brief Returns hops field.
+ ///
+ /// @return hops field
+ uint8_t getHops() const { return (hops_); };
+
+ // Note: There's no need to manipulate OP field directly,
+ // thus no setOp() method. See op_ comment.
+
+ /// @brief Returns op field.
+ ///
+ /// @return op field
+ uint8_t getOp() const { return (op_); };
+
+ /// @brief Sets secs field.
+ ///
+ /// @param secs value to be set
+ void setSecs(uint16_t secs) { secs_ = secs; };
+
+ /// @brief Returns secs field.
+ ///
+ /// @return secs field
+ uint16_t getSecs() const { return (secs_); };
+
+ /// @brief Sets flags field.
+ ///
+ /// @param flags value to be set
+ void setFlags(uint16_t flags) { flags_ = flags; };
+
+ /// @brief Returns flags field.
+ ///
+ /// @return flags field
+ uint16_t getFlags() const { return (flags_); };
+
+ /// @brief Returns ciaddr field.
+ ///
+ /// @return ciaddr field
+ const isc::asiolink::IOAddress&
+ getCiaddr() const { return (ciaddr_); };
+
+ /// @brief Sets ciaddr field.
+ ///
+ /// @param ciaddr value to be set
+ void
+ setCiaddr(const isc::asiolink::IOAddress& ciaddr) { ciaddr_ = ciaddr; };
+
+ /// @brief Returns siaddr field.
+ ///
+ /// @return siaddr field
+ const isc::asiolink::IOAddress&
+ getSiaddr() const { return (siaddr_); };
+
+ /// @brief Sets siaddr field.
+ ///
+ /// @param siaddr value to be set
+ void
+ setSiaddr(const isc::asiolink::IOAddress& siaddr) { siaddr_ = siaddr; };
+
+ /// @brief Returns yiaddr field.
+ ///
+ /// @return yiaddr field
+ const isc::asiolink::IOAddress&
+ getYiaddr() const { return (yiaddr_); };
+
+ /// @brief Sets yiaddr field.
+ ///
+ /// @param yiaddr value to be set
+ void
+ setYiaddr(const isc::asiolink::IOAddress& yiaddr) { yiaddr_ = yiaddr; };
+
+ /// @brief Returns giaddr field.
+ ///
+ /// @return giaddr field
+ const isc::asiolink::IOAddress&
+ getGiaddr() const { return (giaddr_); };
+
+ /// @brief Sets giaddr field.
+ ///
+ /// @param giaddr value to be set
+ void
+ setGiaddr(const isc::asiolink::IOAddress& giaddr) { giaddr_ = giaddr; };
+
+ /// @brief Returns DHCP message type (e.g. 1 = DHCPDISCOVER).
+ ///
+ /// This method is exception safe. For packets without DHCP Message Type
+ /// option, it returns DHCP_NOTYPE (0).
+ ///
+ /// @return message type
+ uint8_t getType() const;
+
+ /// @brief Sets DHCP message type (e.g. 1 = DHCPDISCOVER).
+ ///
+ /// @param type message type to be set
+ void setType(uint8_t type);
+
+ /// @brief Returns name of the DHCP message for a given type number.
+ ///
+ /// This method is exception safe. For messages without DHCP Message Type
+ /// options, it returns UNKNOWN.
+ ///
+ /// @param type DHCPv4 message type which name should be returned.
+ ///
+ /// @return Pointer to the "const" string containing DHCP message name.
+ /// If the message type is unsupported the "UNKNOWN" is returned.
+ /// The caller must not release the returned pointer.
+ static const char* getName(const uint8_t type);
+
+ /// @brief Returns name of the DHCP message.
+ ///
+ /// @return Pointer to the "const" string containing DHCP message name.
+ /// If the message type is unsupported the "UNKNOWN" is returned.
+ /// The caller must not release the returned pointer.
+ const char* getName() const;
+
+ /// @brief Returns sname field
+ ///
+ /// Note: This is 64 bytes long field. It doesn't have to be
+ /// null-terminated. Do not use strlen() or similar on it.
+ ///
+ /// @return sname field
+ const OptionBuffer
+ getSname() const { return (std::vector<uint8_t>(sname_, &sname_[MAX_SNAME_LEN])); };
+
+ /// @brief Sets sname field.
+ ///
+ /// @param sname value to be set
+ /// @param sname_len length of the sname buffer (up to MAX_SNAME_LEN)
+ void setSname(const uint8_t* sname, size_t sname_len);
+
+ /// @brief Returns file field
+ ///
+ /// Note: This is 128 bytes long field. It doesn't have to be
+ /// null-terminated. Do not use strlen() or similar on it.
+ ///
+ /// @return pointer to file field
+ const OptionBuffer
+ getFile() const { return (std::vector<uint8_t>(file_, &file_[MAX_FILE_LEN])); };
+
+ /// Sets file field
+ ///
+ /// @param file value to be set
+ /// @param file_len length of the file buffer (up to MAX_FILE_LEN)
+ void
+ setFile(const uint8_t* file, size_t file_len);
+
+ /// @brief Sets hardware address.
+ ///
+ /// Sets parameters of hardware address. hlen specifies
+ /// length of mac_addr buffer. Content of mac_addr buffer
+ /// will be copied to appropriate field.
+ ///
+ /// Note: mac_addr must be a buffer of at least hlen bytes.
+ ///
+ /// @param htype hardware type (will be sent in htype field)
+ /// @param hlen hardware length (will be sent in hlen field)
+ /// @param mac_addr pointer to hardware address
+ void setHWAddr(uint8_t htype, uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr);
+
+ /// @brief Sets hardware address
+ ///
+ /// Sets hardware address, based on existing HWAddr structure
+ /// @param addr already filled in HWAddr structure
+ /// @throw BadValue if addr is null
+ void setHWAddr(const HWAddrPtr& addr);
+
+ /// Returns htype field
+ ///
+ /// @return hardware type
+ uint8_t
+ getHtype() const;
+
+ /// Returns hlen field
+ ///
+ /// @return hardware address length
+ uint8_t
+ getHlen() const;
+
+ /// @brief returns hardware address information
+ /// @return hardware address structure
+ HWAddrPtr getHWAddr() const { return (hwaddr_); }
+
+ /// @brief Returns text representation of the hardware address
+ ///
+ /// Returns text representation of the hardware address (e.g. hwaddr=00:01:02:03:04:05).
+ /// If there is no defined hardware address, it returns @c hwaddr=(undefined).
+ ///
+ /// @return string with text representation
+ std::string getHWAddrLabel() const;
+
+ /// @brief Add an option.
+ ///
+ /// @note: to avoid throwing when adding multiple options
+ /// with the same type use @ref Pkt::addOption.
+ ///
+ /// @throw BadValue if option with that type is already present.
+ ///
+ /// @param opt option to be added
+ virtual void
+ addOption(const OptionPtr& opt);
+
+ /// @brief Sets local HW address.
+ ///
+ /// Sets the source HW address for the outgoing packet or
+ /// destination HW address for the incoming packet.
+ ///
+ /// @note mac_addr must be a buffer of at least hlen bytes.
+ ///
+ /// @param htype hardware type (will be sent in htype field)
+ /// @param hlen hardware length (will be sent in hlen field)
+ /// @param mac_addr pointer to hardware address
+ void setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr);
+
+ /// @brief Sets local HW address.
+ ///
+ /// Sets hardware address from an existing HWAddr structure.
+ /// The local address is a source address for outgoing
+ /// packet and destination address for incoming packet.
+ ///
+ /// @param addr structure representing HW address.
+ ///
+ /// @throw BadValue if addr is null
+ void setLocalHWAddr(const HWAddrPtr& addr);
+
+ /// @brief Returns local HW address.
+ ///
+ /// @return local HW addr.
+ HWAddrPtr getLocalHWAddr() const {
+ return (local_hwaddr_);
+ }
+
+ /// @brief Returns a reference to option codes which unpacking
+ /// will be deferred.
+ ///
+ /// Only options 43 and 224-254 are subject of deferred
+ /// unpacking: when the packet unpacking is performed, each time
+ /// such an option is found, it is unpacked as an unknown option
+ /// and the code added in this list.
+ ///
+ /// @return List of codes of options which unpacking is deferred.
+ std::list<uint16_t>& getDeferredOptions() {
+ return (deferred_options_);
+ }
+
+ /// @brief Checks if a DHCPv4 message has been relayed.
+ ///
+ /// This function returns a boolean value which indicates whether a DHCPv4
+ /// message has been relayed (if true is returned) or not (if false).
+ ///
+ /// The message is considered relayed if the giaddr field is non-zero and
+ /// non-broadcast.
+ ///
+ /// @return Boolean value which indicates whether the message is relayed
+ /// (true) or non-relayed (false).
+ bool isRelayed() const;
+
+ /// @brief Checks if a DHCPv4 message has been transported over DHCPv6
+ ///
+ /// @return Boolean value which indicates whether the message is
+ /// transported over DHCPv6 (true) or native DHCPv4 (false)
+ virtual bool isDhcp4o6() const {
+ return (false);
+ }
+
+private:
+
+ /// @brief Generic method that validates and sets HW address.
+ ///
+ /// This is a generic method used by all modifiers of this class
+ /// which set class members representing HW address.
+ ///
+ /// @param htype hardware type.
+ /// @param hlen hardware length.
+ /// @param mac_addr pointer to actual hardware address.
+ /// @param [out] hw_addr pointer to a class member to be modified.
+ ///
+ /// @trow isc::OutOfRange if invalid HW address specified.
+ virtual void setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr,
+ HWAddrPtr& hw_addr);
+
+protected:
+
+ /// converts DHCP message type to BOOTP op type
+ ///
+ /// @param dhcpType DHCP message type (e.g. DHCPDISCOVER)
+ ///
+ /// @return BOOTP type (BOOTREQUEST or BOOTREPLY)
+ uint8_t
+ DHCPTypeToBootpType(uint8_t dhcpType);
+
+ /// @brief No-op
+ ///
+ /// This method returns hardware address generated from the IPv6 link-local
+ /// address. As there is no IPv4-equivalent, it always returns NULL.
+ /// We need this stub implementation here, to keep all the get hardware
+ /// address logic in the base class.
+ ///
+ /// @return always NULL
+ virtual HWAddrPtr getMACFromSrcLinkLocalAddr() {
+ return (HWAddrPtr());
+ }
+
+ /// @brief No-op
+ ///
+ /// This method returns hardware address extracted from an IPv6 relay agent.
+ /// option. As there is no IPv4-equivalent, it always returns NULL.
+ /// We need this stub implementation here, to keep all the get hardware
+ /// address logic in the base class.
+ ///
+ /// @return always NULL
+ virtual HWAddrPtr getMACFromIPv6RelayOpt() {
+ return (HWAddrPtr());
+ }
+
+ /// @brief No-op
+ ///
+ /// This is a DHCPv4 version of the function that attempts to extract
+ /// MAC address from the options inserted by a cable modem. It is currently
+ /// not implemented for v4.
+ ///
+ /// @return always NULL
+ virtual HWAddrPtr getMACFromDocsisModem() {
+ return (HWAddrPtr());
+ }
+
+ /// @brief No-op
+ ///
+ /// This method returns hardware address extracted from DUID.
+ /// Currently it is a no-op, even though there's RFC that defines how to
+ /// use DUID in DHCPv4 (see RFC4361). We may implement it one day.
+ ///
+ /// @return always NULL
+ virtual HWAddrPtr getMACFromDUID(){
+ return (HWAddrPtr());
+ }
+
+ /// @brief No-op
+ ///
+ /// This is a DHCPv4 version of the function that attempts to extract
+ /// MAC address from the options inserted by a CMTS. It is currently
+ /// not implemented for v4.
+ ///
+ /// @return always NULL
+ virtual HWAddrPtr getMACFromDocsisCMTS() {
+ return (HWAddrPtr());
+ }
+
+ /// @brief No-op
+ ///
+ /// This method returns hardware address extracted from remote-id relay option.
+ /// Currently it is a no-op, it always returns NULL.
+ ///
+ /// @return always NULL
+ virtual HWAddrPtr getMACFromRemoteIdRelayOption() {
+ return (HWAddrPtr());
+ }
+
+ /// @brief local HW address (dst if receiving packet, src if sending packet)
+ HWAddrPtr local_hwaddr_;
+
+ // @brief List of deferred option codes
+ std::list<uint16_t> deferred_options_;
+
+ /// @brief message operation code
+ ///
+ /// Note: This is legacy BOOTP field. There's no need to manipulate it
+ /// directly. Its value is set based on DHCP message type. Note that
+ /// DHCPv4 protocol reuses BOOTP message format, so this field is
+ /// kept due to BOOTP format. This is NOT DHCPv4 type (DHCPv4 message
+ /// type is kept in message type option).
+ uint8_t op_;
+
+ /// @brief link-layer address and hardware information
+ /// represents 3 fields: htype (hardware type, 1 byte), hlen (length of the
+ /// hardware address, up to 16) and chaddr (hardware address field,
+ /// 16 bytes)
+ HWAddrPtr hwaddr_;
+
+ /// Number of relay agents traversed
+ uint8_t hops_;
+
+ /// elapsed (number of seconds since beginning of transmission)
+ uint16_t secs_;
+
+ /// flags
+ uint16_t flags_;
+
+ /// ciaddr field (32 bits): Client's IP address
+ isc::asiolink::IOAddress ciaddr_;
+
+ /// yiaddr field (32 bits): Client's IP address ("your"), set by server
+ isc::asiolink::IOAddress yiaddr_;
+
+ /// siaddr field (32 bits): next server IP address in boot process(e.g.TFTP)
+ isc::asiolink::IOAddress siaddr_;
+
+ /// giaddr field (32 bits): Gateway IP address
+ isc::asiolink::IOAddress giaddr_;
+
+ /// sname field (64 bytes)
+ uint8_t sname_[MAX_SNAME_LEN];
+
+ /// file field (128 bytes)
+ uint8_t file_[MAX_FILE_LEN];
+
+ // end of real DHCPv4 fields
+}; // Pkt4 class
+
+/// @brief A pointer to Pkt4 object.
+typedef boost::shared_ptr<Pkt4> Pkt4Ptr;
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif
diff --git a/src/lib/dhcp/pkt4o6.cc b/src/lib/dhcp/pkt4o6.cc
new file mode 100644
index 0000000..8374b34
--- /dev/null
+++ b/src/lib/dhcp/pkt4o6.cc
@@ -0,0 +1,60 @@
+// Copyright (C) 2015-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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt4o6.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+Pkt4o6::Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6)
+ :Pkt4(&pkt4[0], pkt4.size()), pkt6_(pkt6)
+{
+ static_cast<void>(pkt6->delOption(D6O_DHCPV4_MSG));
+ setIface(pkt6->getIface());
+ setIndex(pkt6->getIndex());
+ setRemoteAddr(pkt6->getRemoteAddr());
+}
+
+Pkt4o6::Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6)
+ :Pkt4(*pkt4), pkt6_(pkt6) {
+}
+
+void Pkt4o6::pack() {
+ // Convert wire-format Pkt4 data in the form of OptionBuffer.
+ Pkt4::pack();
+ OutputBuffer& buf = getBuffer();
+ const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
+ OptionBuffer msg(ptr, ptr + buf.getLength());
+
+ // Build the DHCPv4 Message option for the DHCPv6 message, and pack the
+ // entire stuff.
+ OptionPtr dhcp4_msg(new Option(Option::V6, D6O_DHCPV4_MSG, msg));
+ pkt6_->addOption(dhcp4_msg);
+ pkt6_->pack();
+}
+
+void
+Pkt4o6::setCopyRetrievedOptions(const bool copy) {
+ Pkt4::setCopyRetrievedOptions(copy);
+ // Copy the new setting to the encapsulated instance of Pkt6.
+ pkt6_->setCopyRetrievedOptions(copy);
+}
+
+
+} // end of namespace isc::dhcp
+
+} // end of namespace isc
diff --git a/src/lib/dhcp/pkt4o6.h b/src/lib/dhcp/pkt4o6.h
new file mode 100644
index 0000000..be46460
--- /dev/null
+++ b/src/lib/dhcp/pkt4o6.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2015-2018 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 PKT4O6_H
+#define PKT4O6_H
+
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief Represents DHCPv4-over-DHCPv6 packet
+///
+/// This class derives from @c Pkt4 in order to be handled by
+/// the DHCPv4 server code. It includes a shared pointer to the
+/// DHCPv6 message too.
+///
+/// This is an implementation of the DHCPv4-query/response DHCPv6 messages
+/// defined in RFC 7341 (http://ietf.org/rfc/rfc7341.txt).
+/// See also
+/// https://gitlab.isc.org/isc-projects/kea/wikis/designs/dhcpv4o6-design
+/// for design discussions.
+class Pkt4o6 : public Pkt4 {
+public:
+
+ /// @brief Constructor, used in message reception.
+ ///
+ /// @param pkt4 Content of the DHCPv4-message option
+ /// @param pkt6 encapsulating unpacked DHCPv6 message
+ /// the DHCPv4 message option will be removed
+ Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6);
+
+ /// @brief Constructor, used in replying to a message
+ ///
+ /// @param pkt4 DHCPv4 message
+ /// @param pkt6 DHCPv6 message
+ Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6);
+
+ /// @brief Returns encapsulating DHCPv6 message
+ Pkt6Ptr getPkt6() const { return (pkt6_); }
+
+ /// @brief Prepares on-wire format of DHCPv4-over-DHCPv6 packet.
+ ///
+ /// Calls pack() on both DHCPv4 and DHCPv6 parts
+ /// Inserts the DHCPv4-message option
+ /// @ref Pkt4::pack and @ref Pkt6::pack
+ virtual void pack();
+
+ /// @brief Checks if a DHCPv4 message has been transported over DHCPv6
+ ///
+ /// @return Boolean value which indicates whether the message is
+ /// transported over DHCPv6 (true) or native DHCPv4 (false)
+ virtual bool isDhcp4o6() const {
+ return (true);
+ }
+
+ /// @brief Overrides the @ref Pkt::setCopyRetrievedOptions to also
+ /// set the flag for encapsulated @ref Pkt6 instance.
+ ///
+ /// When the flag is set for the instance of the @ref Pkt4o6 the
+ /// encapsulated Pkt6, retrieved with @ref Pkt4o6::getPkt6, will
+ /// inherit this setting.
+ ///
+ /// @param copy Indicates if the options should be copied when
+ /// retrieved (if true), or not copied (if false).
+ virtual void setCopyRetrievedOptions(const bool copy);
+
+private:
+ /// Encapsulating DHCPv6 message
+ Pkt6Ptr pkt6_;
+
+}; // Pkt4o6 class
+
+/// @brief A pointer to Pkt4o6 object.
+typedef boost::shared_ptr<Pkt4o6> Pkt4o6Ptr;
+
+} // isc::dhcp namespace
+
+} // isc namespace
+
+#endif
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
new file mode 100644
index 0000000..b02bf39
--- /dev/null
+++ b/src/lib/dhcp/pkt6.cc
@@ -0,0 +1,1045 @@
+// Copyright (C) 2011-2022 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 <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <util/io_utilities.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/duid.h>
+#include <dhcp/iface_mgr.h>
+
+#include <iterator>
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+
+/// @brief Default address used in Pkt6 constructor
+const IOAddress DEFAULT_ADDRESS6("::");
+
+namespace isc {
+namespace dhcp {
+
+Pkt6::RelayInfo::RelayInfo()
+ : msg_type_(0), hop_count_(0), linkaddr_(DEFAULT_ADDRESS6),
+ peeraddr_(DEFAULT_ADDRESS6), relay_msg_len_(0) {
+}
+
+std::string Pkt6::RelayInfo::toText() const {
+ stringstream tmp;
+ tmp << "msg-type=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
+ << "), hop-count=" << static_cast<int>(hop_count_) << "," << endl
+ << "link-address=" << linkaddr_.toText()
+ << ", peer-address=" << peeraddr_.toText() << ", "
+ << options_.size() << " option(s)" << endl;
+ for (const auto& option : options_) {
+ tmp << option.second->toText() << endl;
+ }
+ return (tmp.str());
+}
+
+Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */)
+ : Pkt(buf, buf_len, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
+ msg_type_(0) {
+}
+
+Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/)
+ : Pkt(transid, DEFAULT_ADDRESS6, DEFAULT_ADDRESS6, 0, 0), proto_(proto),
+ msg_type_(msg_type) {
+}
+
+size_t Pkt6::len() {
+ if (relay_info_.empty()) {
+ return (directLen());
+ } else {
+ // Unfortunately we need to re-calculate relay size every time, because
+ // we need to make sure that once a new option is added, its extra size
+ // is reflected in Pkt6::len().
+ calculateRelaySizes();
+ return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0]));
+ }
+}
+
+void
+Pkt6::prepareGetAnyRelayOption(const RelaySearchOrder& order,
+ int& start, int& end, int& direction) const {
+ switch (order) {
+ case RELAY_SEARCH_FROM_CLIENT:
+ // Search backwards
+ start = relay_info_.size() - 1;
+ end = 0;
+ direction = -1;
+ break;
+ case RELAY_SEARCH_FROM_SERVER:
+ // Search forward
+ start = 0;
+ end = relay_info_.size() - 1;
+ direction = 1;
+ break;
+ case RELAY_GET_FIRST:
+ // Look at the innermost relay only
+ start = relay_info_.size() - 1;
+ end = start;
+ direction = 1;
+ break;
+ case RELAY_GET_LAST:
+ // Look at the outermost relay only
+ start = 0;
+ end = 0;
+ direction = 1;
+ }
+}
+
+OptionPtr
+Pkt6::getNonCopiedAnyRelayOption(const uint16_t option_code,
+ const RelaySearchOrder& order) const {
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionPtr());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ prepareGetAnyRelayOption(order, start, end, direction);
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ for (int i = start; i != end + direction; i += direction) {
+ OptionPtr opt = getNonCopiedRelayOption(option_code, i);
+ if (opt) {
+ return (opt);
+ }
+ }
+
+ // We iterated over specified relays and haven't found what we were
+ // looking for
+ return (OptionPtr());
+}
+
+OptionPtr
+Pkt6::getAnyRelayOption(const uint16_t option_code,
+ const RelaySearchOrder& order) {
+
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionPtr());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ prepareGetAnyRelayOption(order, start, end, direction);
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ for (int i = start; i != end + direction; i += direction) {
+ OptionPtr opt = getRelayOption(option_code, i);
+ if (opt) {
+ return (opt);
+ }
+ }
+
+ // We iterated over specified relays and haven't found what we were
+ // looking for
+ return (OptionPtr());
+}
+
+OptionCollection
+Pkt6::getNonCopiedAllRelayOptions(const uint16_t option_code,
+ const RelaySearchOrder& order) const {
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionCollection());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ prepareGetAnyRelayOption(order, start, end, direction);
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ OptionCollection opts;
+ for (int i = start; i != end + direction; i += direction) {
+ std::pair<OptionCollection::const_iterator,
+ OptionCollection::const_iterator> range =
+ relay_info_[i].options_.equal_range(option_code);
+ opts.insert(range.first, range.second);
+ }
+ return (opts);
+}
+
+OptionCollection
+Pkt6::getAllRelayOptions(const uint16_t option_code,
+ const RelaySearchOrder& order) {
+
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionCollection());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ prepareGetAnyRelayOption(order, start, end, direction);
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ OptionCollection opts;
+ for (int i = start; i != end + direction; i += direction) {
+ std::pair<OptionCollection::iterator,
+ OptionCollection::iterator> range =
+ relay_info_[i].options_.equal_range(option_code);
+ // If options should be copied on retrieval, we should now iterate over
+ // matching options, copy them and replace the original ones with new
+ // instances.
+ if (copy_retrieved_options_) {
+ for (OptionCollection::iterator opt_it = range.first;
+ opt_it != range.second; ++opt_it) {
+ OptionPtr option_copy = opt_it->second->clone();
+ opt_it->second = option_copy;
+ }
+ }
+ opts.insert(range.first, range.second);
+ }
+ return (opts);
+}
+
+OptionPtr
+Pkt6::getNonCopiedRelayOption(const uint16_t opt_type,
+ const uint8_t relay_level) const {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed "
+ << relay_info_.size() << " time(s)."
+ << " There is no info about "
+ << relay_level + 1 << " relay.");
+ }
+
+ OptionCollection::const_iterator x = relay_info_[relay_level].options_.find(opt_type);
+ if (x != relay_info_[relay_level].options_.end()) {
+ return (x->second);
+ }
+
+ return (OptionPtr());
+}
+
+OptionPtr
+Pkt6::getRelayOption(const uint16_t opt_type, const uint8_t relay_level) {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed "
+ << relay_info_.size() << " time(s)."
+ << " There is no info about "
+ << relay_level + 1 << " relay.");
+ }
+
+ OptionCollection::iterator x = relay_info_[relay_level].options_.find(opt_type);
+ if (x != relay_info_[relay_level].options_.end()) {
+ if (copy_retrieved_options_) {
+ OptionPtr relay_option_copy = x->second->clone();
+ x->second = relay_option_copy;
+ }
+ return (x->second);
+ }
+
+ return (OptionPtr());
+}
+
+OptionCollection
+Pkt6::getNonCopiedRelayOptions(const uint16_t opt_type,
+ const uint8_t relay_level) const {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed "
+ << relay_info_.size() << " time(s)."
+ << " There is no info about "
+ << relay_level + 1 << " relay.");
+ }
+
+ std::pair<OptionCollection::const_iterator,
+ OptionCollection::const_iterator> range =
+ relay_info_[relay_level].options_.equal_range(opt_type);
+ return (OptionCollection(range.first, range.second));
+}
+
+OptionCollection
+Pkt6::getRelayOptions(const uint16_t opt_type,
+ const uint8_t relay_level) {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed "
+ << relay_info_.size() << " time(s)."
+ << " There is no info about "
+ << relay_level + 1 << " relay.");
+ }
+
+ OptionCollection options_copy;
+
+ std::pair<OptionCollection::iterator,
+ OptionCollection::iterator> range =
+ relay_info_[relay_level].options_.equal_range(opt_type);
+ // If options should be copied on retrieval, we should now iterate over
+ // matching options, copy them and replace the original ones with new
+ // instances.
+ if (copy_retrieved_options_) {
+ for (OptionCollection::iterator opt_it = range.first;
+ opt_it != range.second; ++opt_it) {
+ OptionPtr option_copy = opt_it->second->clone();
+ opt_it->second = option_copy;
+ }
+ }
+ // Finally, return updated options. This can also be empty in some cases.
+ return (OptionCollection(range.first, range.second));
+}
+
+const isc::asiolink::IOAddress&
+Pkt6::getRelay6LinkAddress(uint8_t relay_level) const {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
+ << " There is no info about " << relay_level + 1 << " relay.");
+ }
+
+ return (relay_info_[relay_level].linkaddr_);
+}
+
+const isc::asiolink::IOAddress&
+Pkt6::getRelay6PeerAddress(uint8_t relay_level) const {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
+ << " There is no info about " << relay_level + 1 << " relay.");
+ }
+
+ return (relay_info_[relay_level].peeraddr_);
+}
+
+uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
+ uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
+ + Option::OPTION6_HDR_LEN; // header of the relay-msg option
+
+ for (const auto& opt : relay.options_) {
+ len += (opt.second)->len();
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::calculateRelaySizes() {
+
+ uint16_t len = directLen(); // start with length of all options
+
+ for (int relay_index = relay_info_.size(); relay_index > 0; --relay_index) {
+ relay_info_[relay_index - 1].relay_msg_len_ = len;
+ len += getRelayOverhead(relay_info_[relay_index - 1]);
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::directLen() const {
+ uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
+
+ for (const auto& it : options_) {
+ length += it.second->len();
+ }
+
+ return (length);
+}
+
+
+void
+Pkt6::pack() {
+ switch (proto_) {
+ case UDP:
+ packUDP();
+ break;
+ case TCP:
+ packTCP();
+ break;
+ default:
+ isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
+ }
+}
+
+void
+Pkt6::packUDP() {
+ try {
+ // Make sure that the buffer is empty before we start writing to it.
+ buffer_out_.clear();
+
+ // is this a relayed packet?
+ if (!relay_info_.empty()) {
+
+ // calculate size needed for each relay (if there is only one relay,
+ // then it will be equal to "regular" length + relay-forw header +
+ // size of relay-msg option header + possibly size of interface-id
+ // option (if present). If there is more than one relay, the whole
+ // process is called iteratively for each relay.
+ calculateRelaySizes();
+
+ // Now for each relay, we need to...
+ for (vector<RelayInfo>::iterator relay = relay_info_.begin();
+ relay != relay_info_.end(); ++relay) {
+
+ // build relay-forw/relay-repl header (see RFC 8415, section 9)
+ buffer_out_.writeUint8(relay->msg_type_);
+ buffer_out_.writeUint8(relay->hop_count_);
+ buffer_out_.writeData(&(relay->linkaddr_.toBytes()[0]),
+ isc::asiolink::V6ADDRESS_LEN);
+ buffer_out_.writeData(&relay->peeraddr_.toBytes()[0],
+ isc::asiolink::V6ADDRESS_LEN);
+
+ // store every option in this relay scope. Usually that will be
+ // only interface-id, but occasionally other options may be
+ // present here as well (vendor-opts for Cable modems,
+ // subscriber-id, remote-id, options echoed back from Echo
+ // Request Option, etc.)
+ for (const auto& opt : relay->options_) {
+ (opt.second)->pack(buffer_out_);
+ }
+
+ // and include header relay-msg option. Its payload will be
+ // generated in the next iteration (if there are more relays)
+ // or outside the loop (if there are no more relays and the
+ // payload is a direct message)
+ buffer_out_.writeUint16(D6O_RELAY_MSG);
+ buffer_out_.writeUint16(relay->relay_msg_len_);
+ }
+
+ }
+
+ // DHCPv6 header: message-type (1 octet) + transaction id (3 octets)
+ buffer_out_.writeUint8(msg_type_);
+ // store 3-octet transaction-id
+ buffer_out_.writeUint8( (transid_ >> 16) & 0xff );
+ buffer_out_.writeUint8( (transid_ >> 8) & 0xff );
+ buffer_out_.writeUint8( (transid_) & 0xff );
+
+ // the rest are options
+ LibDHCP::packOptions6(buffer_out_, options_);
+ }
+ catch (const Exception& e) {
+ // An exception is thrown and message will be written to Logger
+ isc_throw(InvalidOperation, e.what());
+ }
+}
+
+void
+Pkt6::packTCP() {
+ /// TODO Implement this function.
+ isc_throw(NotImplemented, "DHCPv6 over TCP (bulk leasequery and failover)"
+ " not implemented yet.");
+}
+
+void
+Pkt6::unpack() {
+ switch (proto_) {
+ case UDP:
+ return unpackUDP();
+ case TCP:
+ return unpackTCP();
+ default:
+ isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
+ }
+}
+
+void
+Pkt6::unpackUDP() {
+ if (data_.size() < 4) {
+ isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size "
+ << data_.size() << ", DHCPv6 header alone has 4 bytes.");
+ }
+ msg_type_ = data_[0];
+ switch (msg_type_) {
+ case DHCPV6_SOLICIT:
+ case DHCPV6_ADVERTISE:
+ case DHCPV6_REQUEST:
+ case DHCPV6_CONFIRM:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ case DHCPV6_REPLY:
+ case DHCPV6_DECLINE:
+ case DHCPV6_RECONFIGURE:
+ case DHCPV6_INFORMATION_REQUEST:
+ case DHCPV6_DHCPV4_QUERY:
+ case DHCPV6_DHCPV4_RESPONSE:
+ default: // assume that unknown messages are not using relay format
+ {
+ return (unpackMsg(data_.begin(), data_.end()));
+ }
+ case DHCPV6_RELAY_FORW:
+ case DHCPV6_RELAY_REPL:
+ return (unpackRelayMsg());
+ }
+}
+
+void
+Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) {
+ size_t size = std::distance(begin, end);
+ if (size < 4) {
+ // truncated message (less than 4 bytes)
+ isc_throw(BadValue, "Received truncated UDP DHCPv6 packet of size "
+ << data_.size() << ", DHCPv6 header alone has 4 bytes.");
+ }
+
+ msg_type_ = *begin++;
+
+ transid_ = ( (*begin++) << 16 ) +
+ ((*begin++) << 8) + (*begin++);
+ transid_ = transid_ & 0xffffff;
+
+ // See below about invoking Postel's law, as we aren't using
+ // size we don't need to update it. If we do so in the future
+ // perhaps for stats gathering we can uncomment this.
+ // size -= sizeof(uint32_t); // We just parsed 4 bytes header
+
+ OptionBuffer opt_buffer(begin, end);
+
+ // If custom option parsing function has been set, use this function
+ // to parse options. Otherwise, use standard function from libdhcp.
+ size_t offset = LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, options_);
+
+ // If offset is not equal to the size, then something is wrong here. We
+ // either parsed past input buffer (bug in our code) or we haven't parsed
+ // everything (received trailing garbage or truncated option).
+ //
+ // Invoking Jon Postel's law here: be conservative in what you send, and be
+ // liberal in what you accept. There's no easy way to log something from
+ // libdhcp++ library, so we just choose to be silent about remaining
+ // bytes. We also need to quell compiler warning about unused offset
+ // variable.
+ //
+ // if (offset != size) {
+ // isc_throw(BadValue, "Received DHCPv6 buffer of size " << size
+ // << ", were able to parse " << offset << " bytes.");
+ // }
+ (void)offset;
+}
+
+void
+Pkt6::unpackRelayMsg() {
+
+ // we use offset + bufsize, because we want to avoid creating unnecessary
+ // copies. There may be up to 32 relays. While using InputBuffer would
+ // be probably a bit cleaner, copying data up to 32 times is unacceptable
+ // price here. Hence a single buffer with offsets and lengths.
+ size_t bufsize = data_.size();
+ size_t offset = 0;
+
+ while (bufsize >= DHCPV6_RELAY_HDR_LEN) {
+
+ RelayInfo relay;
+
+ size_t relay_msg_offset = 0;
+ size_t relay_msg_len = 0;
+
+ // parse fixed header first (first 34 bytes)
+ relay.msg_type_ = data_[offset++];
+ relay.hop_count_ = data_[offset++];
+ relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
+
+ // parse the rest as options
+ OptionBuffer opt_buffer(&data_[offset], &data_[offset] + bufsize);
+
+ // If custom option parsing function has been set, use this function
+ // to parse options. Otherwise, use standard function from libdhcp.
+ LibDHCP::unpackOptions6(opt_buffer, DHCP6_OPTION_SPACE, relay.options_,
+ &relay_msg_offset, &relay_msg_len);
+
+ /// @todo: check that each option appears at most once
+ //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
+ //relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID);
+ //relay.remote_id_ = options->getOption(D6O_REMOTE_ID);
+
+ if (relay_msg_offset == 0 || relay_msg_len == 0) {
+ isc_throw(BadValue, "Mandatory relay-msg option missing");
+ }
+
+ // store relay information parsed so far
+ addRelayInfo(relay);
+
+ /// @todo: implement ERO (Echo Request Option, RFC 4994) here
+
+ if (relay_msg_len >= bufsize) {
+ // length of the relay_msg option extends beyond end of the message
+ isc_throw(Unexpected, "Relay-msg option is truncated.");
+ }
+ uint8_t inner_type = data_[offset + relay_msg_offset];
+ offset += relay_msg_offset; // offset is relative
+ bufsize = relay_msg_len; // length is absolute
+
+ if ( (inner_type != DHCPV6_RELAY_FORW) &&
+ (inner_type != DHCPV6_RELAY_REPL)) {
+ // Ok, the inner message is not encapsulated, let's decode it
+ // directly
+ return (unpackMsg(data_.begin() + offset, data_.begin() + offset
+ + relay_msg_len));
+ }
+
+ // Oh well, there's inner relay-forw or relay-repl inside. Let's
+ // unpack it as well. The next loop iteration will take care
+ // of that.
+ }
+
+ if ( (offset == data_.size()) && (bufsize == 0) ) {
+ // message has been parsed completely
+ return;
+ }
+
+ /// @todo: log here that there are additional unparsed bytes
+}
+
+void
+Pkt6::addRelayInfo(const RelayInfo& relay) {
+ if (relay_info_.size() > HOP_COUNT_LIMIT) {
+ isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times");
+ }
+
+ /// @todo: Implement type checks here (e.g. we could receive relay-forw in relay-repl)
+ relay_info_.push_back(relay);
+}
+
+void
+Pkt6::unpackTCP() {
+ isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
+ "not implemented yet.");
+}
+
+HWAddrPtr
+Pkt6::getMACFromDUID() {
+ HWAddrPtr mac;
+ OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ return (mac);
+ }
+
+ uint8_t hlen = opt_duid->getData().size();
+ if (!hlen) {
+ return (mac);
+ }
+ vector<uint8_t> hw_addr(hlen, 0);
+ std::vector<unsigned char> duid_data = opt_duid->getData();
+
+ // Read the first two bytes. That duid type.
+ uint16_t duid_type = util::readUint16(&duid_data[0], duid_data.size());
+
+ switch (duid_type) {
+ case DUID::DUID_LL:
+ {
+ // 2 bytes of duid type, 2 bytes of hardware type and at least
+ // 1 byte of actual identification
+ if (duid_data.size() >= 5) {
+ uint16_t hwtype = util::readUint16(&duid_data[2],
+ duid_data.size() - 2);
+ mac.reset(new HWAddr(&duid_data[4], duid_data.size() - 4, hwtype));
+ }
+ break;
+ }
+ case DUID::DUID_LLT:
+ {
+ // 2 bytes of duid type, 2 bytes of hardware, 4 bytes for timestamp,
+ // and at least 1 byte of actual identification
+ if (duid_data.size() >= 9) {
+ uint16_t hwtype = util::readUint16(&duid_data[2],
+ duid_data.size() - 2);
+ mac.reset(new HWAddr(&duid_data[8], duid_data.size() - 8, hwtype));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (mac) {
+ mac->source_ = HWAddr::HWADDR_SOURCE_DUID;
+ }
+
+ return (mac);
+}
+
+std::string
+Pkt6::makeLabel(const DuidPtr duid, const uint32_t transid,
+ const HWAddrPtr& hwaddr) {
+ // Create label with DUID and HW address.
+ std::stringstream label;
+ label << makeLabel(duid, hwaddr);
+
+ // Append transaction id.
+ label << ", tid=0x" << std::hex << transid << std::dec;
+
+ return (label.str());
+}
+
+std::string
+Pkt6::makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr) {
+ std::stringstream label;
+ // DUID should be present at all times, so explicitly inform when
+ // it is no present (no info).
+ label << "duid=[" << (duid ? duid->toText() : "no info")
+ << "]";
+
+ // HW address is typically not carried in the DHCPv6 messages
+ // and can be extracted using various, but not fully reliable,
+ // techniques. If it is not present, don't print anything.
+ if (hwaddr) {
+ label << ", [" << hwaddr->toText() << "]";
+ }
+
+ return (label.str());
+}
+
+std::string
+Pkt6::getLabel() const {
+ /// @todo Do not print HW address as it is unclear how it should
+ /// be retrieved if there is no access to user configuration which
+ /// specifies the order of various techniques to be used to retrieve
+ /// it.
+ return (makeLabel(getClientId(), getTransid(), HWAddrPtr()));}
+
+std::string
+Pkt6::toText() const {
+ stringstream tmp;
+
+ // First print the basics
+ tmp << "localAddr=[" << local_addr_ << "]:" << local_port_
+ << " remoteAddr=[" << remote_addr_ << "]:" << remote_port_ << endl;
+ tmp << "msgtype=" << static_cast<int>(msg_type_) << "(" << getName(msg_type_)
+ << "), transid=0x" <<
+ hex << transid_ << dec << endl;
+
+ // Then print the options
+ for (const auto& opt : options_) {
+ tmp << opt.second->toText() << std::endl;
+ }
+
+ // Finally, print the relay information (if present)
+ if (!relay_info_.empty()) {
+ tmp << relay_info_.size() << " relay(s):" << endl;
+ int cnt = 0;
+ for (const auto& relay : relay_info_) {
+ tmp << "relay[" << cnt++ << "]: " << relay.toText();
+ }
+ } else {
+ tmp << "No relays traversed." << endl;
+ }
+ return tmp.str();
+}
+
+DuidPtr
+Pkt6::getClientId() const {
+ OptionPtr opt_duid = getNonCopiedOption(D6O_CLIENTID);
+ try {
+ // This will throw if the DUID length is larger than 128 bytes
+ // or is too short.
+ return (opt_duid ? DuidPtr(new DUID(opt_duid->getData())) : DuidPtr());
+ } catch (...) {
+ // Do nothing. This method is used only by getLabel(), which is
+ // used for logging purposes. We should not throw, but rather
+ // report no DUID. We should not log anything, as we're in the
+ // process of logging something for this packet. So the only
+ // choice left is to return an empty pointer.
+ }
+ return (DuidPtr());
+}
+
+const char*
+Pkt6::getName(const uint8_t type) {
+ static const char* ADVERTISE = "ADVERTISE";
+ static const char* CONFIRM = "CONFIRM";
+ static const char* DECLINE = "DECLINE";
+ static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
+ static const char* LEASEQUERY = "LEASEQUERY";
+ static const char* LEASEQUERY_DATA = "LEASEQUERY_DATA";
+ static const char* LEASEQUERY_DONE = "LEASEQUERY_DONE";
+ static const char* LEASEQUERY_REPLY = "LEASEQUERY_REPLY";
+ static const char* REBIND = "REBIND";
+ static const char* RECONFIGURE = "RECONFIGURE";
+ static const char* RELAY_FORW = "RELAY_FORWARD";
+ static const char* RELAY_REPL = "RELAY_REPLY";
+ static const char* RELEASE = "RELEASE";
+ static const char* RENEW = "RENEW";
+ static const char* REPLY = "REPLY";
+ static const char* REQUEST = "REQUEST";
+ static const char* SOLICIT = "SOLICIT";
+ static const char* DHCPV4_QUERY = "DHCPV4_QUERY";
+ static const char* DHCPV4_RESPONSE = "DHCPV4_RESPONSE";
+ static const char* UNKNOWN = "UNKNOWN";
+
+ switch (type) {
+ case DHCPV6_ADVERTISE:
+ return (ADVERTISE);
+
+ case DHCPV6_CONFIRM:
+ return (CONFIRM);
+
+ case DHCPV6_DECLINE:
+ return (DECLINE);
+
+ case DHCPV6_INFORMATION_REQUEST:
+ return (INFORMATION_REQUEST);
+
+ case DHCPV6_LEASEQUERY:
+ return (LEASEQUERY);
+
+ case DHCPV6_LEASEQUERY_DATA:
+ return (LEASEQUERY_DATA);
+
+ case DHCPV6_LEASEQUERY_DONE:
+ return (LEASEQUERY_DONE);
+
+ case DHCPV6_LEASEQUERY_REPLY:
+ return (LEASEQUERY_REPLY);
+
+ case DHCPV6_REBIND:
+ return (REBIND);
+
+ case DHCPV6_RECONFIGURE:
+ return (RECONFIGURE);
+
+ case DHCPV6_RELAY_FORW:
+ return (RELAY_FORW);
+
+ case DHCPV6_RELAY_REPL:
+ return (RELAY_REPL);
+
+ case DHCPV6_RELEASE:
+ return (RELEASE);
+
+ case DHCPV6_RENEW:
+ return (RENEW);
+
+ case DHCPV6_REPLY:
+ return (REPLY);
+
+ case DHCPV6_REQUEST:
+ return (REQUEST);
+
+ case DHCPV6_SOLICIT:
+ return (SOLICIT);
+
+ case DHCPV6_DHCPV4_QUERY:
+ return (DHCPV4_QUERY);
+
+ case DHCPV6_DHCPV4_RESPONSE:
+ return (DHCPV4_RESPONSE);
+
+ default:
+ ;
+ }
+ return (UNKNOWN);
+}
+
+const char* Pkt6::getName() const {
+ return (getName(getType()));
+}
+
+void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
+
+ // We use index rather than iterator, because we need that as a parameter
+ // passed to getNonCopiedRelayOption()
+ for (size_t i = 0; i < question->relay_info_.size(); ++i) {
+ RelayInfo info;
+ info.msg_type_ = DHCPV6_RELAY_REPL;
+ info.hop_count_ = question->relay_info_[i].hop_count_;
+ info.linkaddr_ = question->relay_info_[i].linkaddr_;
+ info.peeraddr_ = question->relay_info_[i].peeraddr_;
+
+ // Is there an interface-id option in this nesting level?
+ // If there is, we need to echo it back
+ OptionPtr opt = question->getNonCopiedRelayOption(D6O_INTERFACE_ID, i);
+ // taken from question->RelayInfo_[i].options_
+ if (opt) {
+ info.options_.insert(make_pair(opt->getType(), opt));
+ }
+
+ // Same for relay-source-port option
+ opt = question->getNonCopiedRelayOption(D6O_RELAY_SOURCE_PORT, i);
+ if (opt) {
+ info.options_.insert(make_pair(opt->getType(), opt));
+ }
+
+ /// @todo: Implement support for ERO (Echo Request Option, RFC4994)
+
+ // Add this relay-forw info (client's message) to our relay-repl
+ // message (server's response)
+ relay_info_.push_back(info);
+ }
+}
+
+HWAddrPtr
+Pkt6::getMACFromSrcLinkLocalAddr() {
+ if (relay_info_.empty()) {
+ // This is a direct message, use source address
+ return (getMACFromIPv6(remote_addr_));
+ }
+
+ // This is a relayed message, get the peer-addr from the first relay-forw
+ return (getMACFromIPv6(relay_info_[relay_info_.size() - 1].peeraddr_));
+}
+
+HWAddrPtr
+Pkt6::getMACFromIPv6RelayOpt() {
+ HWAddrPtr mac;
+
+ // This is not a direct message
+ if (!relay_info_.empty()) {
+ // RFC6969 Section 6: Look for the client_linklayer_addr option on the
+ // relay agent closest to the client
+ OptionPtr opt = getAnyRelayOption(D6O_CLIENT_LINKLAYER_ADDR,
+ RELAY_GET_FIRST);
+ if (opt) {
+ const OptionBuffer data = opt->getData();
+ // This client link address option is supposed to be
+ // 2 bytes of link-layer type followed by link-layer address.
+ if (data.size() >= 3) {
+ // +2, -2 means to skip the initial 2 bytes which are
+ // hwaddress type
+ mac.reset(new HWAddr(&data[0] + 2, data.size() - 2,
+ opt->getUint16()));
+
+ mac->source_ = HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION;
+ }
+ }
+ }
+
+ return mac;
+}
+
+HWAddrPtr
+Pkt6::getMACFromDocsisModem() {
+ HWAddrPtr mac;
+ OptionVendorPtr vendor;
+ for (auto opt : getNonCopiedOptions(D6O_VENDOR_OPTS)) {
+ if (opt.first != D6O_VENDOR_OPTS) {
+ continue;
+ }
+ vendor = boost::dynamic_pointer_cast< OptionVendor>(opt.second);
+ // Check if this is indeed DOCSIS3 environment
+ if (!vendor || vendor->getVendorId() != VENDOR_ID_CABLE_LABS) {
+ continue;
+ }
+ // If it is, try to get device-id option
+ OptionPtr device_id = vendor->getOption(DOCSIS3_V6_DEVICE_ID);
+ if (device_id) {
+ // If the option contains any data, use it as MAC address
+ if (!device_id->getData().empty()) {
+ mac.reset(new HWAddr(device_id->getData(), HTYPE_DOCSIS));
+ mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_MODEM;
+ break;
+ }
+ }
+ }
+
+ return mac;
+}
+
+HWAddrPtr
+Pkt6::getMACFromDocsisCMTS() {
+ if (relay_info_.empty()) {
+ return (HWAddrPtr());
+ }
+
+ // If the message passed through a CMTS, there'll
+ // CMTS-specific options in it.
+ HWAddrPtr mac;
+ OptionVendorPtr vendor;
+ for (auto opt : getAllRelayOptions(D6O_VENDOR_OPTS,
+ RELAY_SEARCH_FROM_CLIENT)) {
+ if (opt.first != D6O_VENDOR_OPTS) {
+ continue;
+ }
+ vendor = boost::dynamic_pointer_cast< OptionVendor>(opt.second);
+ // Check if this is indeed DOCSIS3 environment
+ if (!vendor || vendor->getVendorId() != VENDOR_ID_CABLE_LABS) {
+ continue;
+ }
+ // Try to get cable modem mac
+ OptionPtr cm_mac = vendor->getOption(DOCSIS3_V6_CMTS_CM_MAC);
+
+ // If the option contains any data, use it as MAC address
+ if (cm_mac && !cm_mac->getData().empty()) {
+ mac.reset(new HWAddr(cm_mac->getData(), HTYPE_DOCSIS));
+ mac->source_ = HWAddr::HWADDR_SOURCE_DOCSIS_CMTS;
+ break;
+ }
+ }
+
+ return (mac);
+}
+
+HWAddrPtr
+Pkt6::getMACFromRemoteIdRelayOption() {
+ HWAddrPtr mac;
+
+ // If this is relayed message
+ if (!relay_info_.empty()) {
+ // Get remote-id option from a relay agent closest to the client
+ OptionPtr opt = getAnyRelayOption(D6O_REMOTE_ID, RELAY_GET_FIRST);
+ if (opt) {
+ const OptionBuffer data = opt->getData();
+ // This remote-id option is supposed to be 4 bytes of
+ // of enterprise-number followed by remote-id.
+ if (data.size() >= 5) {
+ // Let's get the interface this packet was received on.
+ // We need it to get the hardware type.
+ IfacePtr iface = IfaceMgr::instance().getIface(iface_);
+ uint16_t hwtype = 0; // not specified
+
+ // If we get the interface HW type, great! If not,
+ // let's not panic.
+ if (iface) {
+ hwtype = iface->getHWType();
+ }
+
+ size_t len = data.size() - 4;
+
+ if (len > HWAddr::MAX_HWADDR_LEN) {
+ len = HWAddr::MAX_HWADDR_LEN;
+ }
+
+ // Skip the initial 4 bytes which are enterprise-number.
+ mac.reset(new HWAddr(&data[0] + 4, len, hwtype));
+ mac->source_ = HWAddr::HWADDR_SOURCE_REMOTE_ID;
+ }
+ }
+ }
+
+ return (mac);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
new file mode 100644
index 0000000..1ae23be
--- /dev/null
+++ b/src/lib/dhcp/pkt6.h
@@ -0,0 +1,627 @@
+// Copyright (C) 2011-2022 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 PKT6_H
+#define PKT6_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <iostream>
+#include <set>
+
+#include <time.h>
+
+namespace isc {
+
+namespace dhcp {
+
+class Pkt6;
+
+/// @brief A pointer to Pkt6 packet
+typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
+
+/// @brief Represents a DHCPv6 packet
+///
+/// This class represents a single DHCPv6 packet. It handles both incoming
+/// and transmitted packets, parsing incoming options, options handling
+/// (add, get, remove), on-wire assembly, sanity checks and other operations.
+/// This specific class has several DHCPv6-specific methods, but it uses a lot
+/// of common operations from its base @c Pkt class that is shared with Pkt4.
+///
+/// This class also handles relayed packets. For example, a RELAY-FORW message
+/// with a SOLICIT inside will be represented as SOLICIT and the RELAY-FORW
+/// layers will be stored in relay_info_ vector.
+class Pkt6 : public Pkt {
+public:
+ /// specifies non-relayed DHCPv6 packet header length (over UDP)
+ const static size_t DHCPV6_PKT_HDR_LEN = 4;
+
+ /// specifies relay DHCPv6 packet header length (over UDP)
+ const static size_t DHCPV6_RELAY_HDR_LEN = 34;
+
+ /// DHCPv6 transport protocol
+ enum DHCPv6Proto {
+ UDP = 0, // most packets are UDP
+ TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover)
+ };
+
+ /// @brief defines relay search pattern
+ ///
+ /// Defines order in which options are searched in a message that
+ /// passed through multiple relays. RELAY_SEACH_FROM_CLIENT will
+ /// start search from the relay that was the closest to the client
+ /// (i.e. innermost in the encapsulated message, which also means
+ /// this was the first relay that forwarded packet received by the
+ /// server and this will be the last relay that will handle the
+ /// response that server sent towards the client.).
+ /// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the
+ /// relay closest to the server (i.e. outermost in the encapsulated
+ /// message, which also means it was the last relay that relayed
+ /// the received message and will be the first one to process
+ /// server's response). RELAY_GET_FIRST will try to get option from
+ /// the first relay only (closest to the client), RELAY_GET_LAST will
+ /// try to get option form the last relay (closest to the server).
+ enum RelaySearchOrder {
+ RELAY_SEARCH_FROM_CLIENT = 1,
+ RELAY_SEARCH_FROM_SERVER = 2,
+ RELAY_GET_FIRST = 3,
+ RELAY_GET_LAST = 4
+ };
+
+ /// @brief structure that describes a single relay information
+ ///
+ /// Client sends messages. Each relay along its way will encapsulate the message.
+ /// This structure represents all information added by a single relay.
+ struct RelayInfo {
+
+ /// @brief default constructor
+ RelayInfo();
+
+ /// @brief Returns printable representation of the relay information.
+ /// @return text representation of the structure (used in debug logging)
+ std::string toText() const;
+
+ uint8_t msg_type_; ///< message type (RELAY-FORW oro RELAY-REPL)
+ uint8_t hop_count_; ///< number of traversed relays (up to 32)
+ isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply
+ isc::asiolink::IOAddress peeraddr_;///< fixed field in relay-forw/relay-reply
+
+ /// @brief length of the relay_msg_len
+ /// Used when calculating length during pack/unpack
+ uint16_t relay_msg_len_;
+
+ /// options received from a specified relay, except relay-msg option
+ isc::dhcp::OptionCollection options_;
+ };
+
+ /// Constructor, used in replying to a message
+ ///
+ /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
+ /// @param transid transaction-id
+ /// @param proto protocol (TCP or UDP)
+ Pkt6(uint8_t msg_type,
+ uint32_t transid,
+ DHCPv6Proto proto = UDP);
+
+ /// Constructor, used in message transmission
+ ///
+ /// Creates new message. Transaction-id will randomized.
+ ///
+ /// @param buf pointer to a buffer of received packet content
+ /// @param len size of buffer of received packet content
+ /// @param proto protocol (usually UDP, but TCP will be supported eventually)
+ Pkt6(const uint8_t* buf, uint32_t len, DHCPv6Proto proto = UDP);
+
+ /// @brief Prepares on-wire format.
+ ///
+ /// Prepares on-wire format of message and all its options.
+ /// Options must be stored in options_ field.
+ /// Output buffer will be stored in data_. Length
+ /// will be set in data_len_.
+ /// The output buffer is cleared before new data is written to it.
+ ///
+ /// @throw BadValue if packet protocol is invalid, InvalidOperation
+ /// if packing fails, or NotImplemented if protocol is TCP (IPv6 over TCP is
+ /// not yet supported).
+ virtual void pack();
+
+ /// @brief Dispatch method that handles binary packet parsing.
+ ///
+ /// This method calls appropriate dispatch function (unpackUDP or
+ /// unpackTCP).
+ ///
+ /// @throw tbd
+ virtual void unpack();
+
+ /// @brief Returns protocol of this packet (UDP or TCP).
+ ///
+ /// @return protocol type
+ DHCPv6Proto getProto() {
+ return (proto_);
+ }
+
+ /// @brief Sets protocol of this packet.
+ ///
+ /// @param proto protocol (UDP or TCP)
+ void setProto(DHCPv6Proto proto = UDP) {
+ proto_ = proto;
+ }
+
+ /// @brief Returns text representation of the given packet identifiers.
+ ///
+ /// @note The parameters are ordered from the one that should be available
+ /// almost at all times, to the one that is optional. This allows for
+ /// providing default values for the parameters that may not be available
+ /// in some places in the code where @c Pkt6::makeLabel is called.
+ ///
+ /// @param duid Pointer to the client identifier or NULL.
+ /// @param transid Numeric transaction id to include in the string.
+ /// @param hwaddr Hardware address to include in the string or NULL.
+ ///
+ /// @return String with text representation of the packet identifiers.
+ static std::string makeLabel(const DuidPtr duid, const uint32_t transid,
+ const HWAddrPtr& hwaddr);
+
+ /// @brief Returns text representation of the given packet identifiers.
+ ///
+ /// This variant of the method does not include transaction id.
+ ///
+ /// @param duid Pointer to the client identifier or NULL.
+ /// @param hwaddr Hardware address to include in the string or NULL.
+ ///
+ /// @return String with text representation of the packet identifiers.
+ static std::string makeLabel(const DuidPtr duid, const HWAddrPtr& hwaddr);
+
+ /// @brief Returns text representation of the primary packet identifiers
+ ///
+ /// This method is intended to be used to provide a consistent way to
+ /// identify packets within log statements. It is an instance-level
+ /// wrapper around static makeLabel(). See this method for string
+ /// content.
+ ///
+ /// @note Currently this method doesn't include the HW address in the
+ /// returned text.
+ ///
+ /// @return string with text representation
+ virtual std::string getLabel() const;
+
+ /// @brief Returns text representation of the packet.
+ ///
+ /// This function is useful mainly for debugging.
+ ///
+ /// @return string with text representation
+ virtual std::string toText() const;
+
+ /// @brief Returns length of the packet.
+ ///
+ /// This function returns size required to hold this packet.
+ /// It includes DHCPv6 header and all options stored in
+ /// options_ field.
+ ///
+ /// Note: It does not return proper length of incoming packets
+ /// before they are unpacked.
+ ///
+ /// @return number of bytes required to assemble this packet
+ virtual size_t len();
+
+ /// @brief Returns message type (e.g. 1 = SOLICIT).
+ ///
+ /// @return message type
+ virtual uint8_t getType() const { return (msg_type_); }
+
+ /// @brief Sets message type (e.g. 1 = SOLICIT).
+ ///
+ /// @param type message type to be set
+ virtual void setType(uint8_t type) { msg_type_=type; };
+
+ /// @brief Retrieves the DUID from the Client Identifier option.
+ ///
+ /// This method is exception safe.
+ ///
+ /// @return Pointer to the DUID or NULL if the option doesn't exist.
+ DuidPtr getClientId() const;
+
+
+protected:
+
+ /// @brief Returns pointer to an option inserted by relay agent.
+ ///
+ /// This is a variant of the @ref Pkt6::getRelayOption function which
+ /// never copies an option returned. This method should be only used by
+ /// the @ref Pkt6 class and derived classes. Any external callers should
+ /// use @ref getRelayOption which copies the option before returning it
+ /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+ ///
+ /// @param opt_type Code of the requested option.
+ /// @param relay_level Nesting level as described for
+ /// @ref Pkt6::getRelayOption.
+ ///
+ /// @return Pointer to the option or null if such option doesn't exist.
+ OptionPtr getNonCopiedRelayOption(const uint16_t opt_type,
+ const uint8_t relay_level) const;
+
+ /// @brief Returns all option instances inserted by relay agent.
+ ///
+ /// This is a variant of the @ref Pkt6::getRelayOptions function which
+ /// never copies an option returned. This method should be only used by
+ /// the @ref Pkt6 class and derived classes. Any external callers should
+ /// use @ref getRelayOption which copies the option before returning it
+ /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+ ///
+ /// @param opt_type Code of the requested option.
+ /// @param relay_level Nesting level as described for
+ /// @ref Pkt6::getRelayOption.
+ ///
+ /// @return Collection of options found.
+ OptionCollection getNonCopiedRelayOptions(const uint16_t opt_type,
+ const uint8_t relay_level) const;
+
+public:
+
+ /// @brief Returns option inserted by relay
+ ///
+ /// Returns an option from specified relay scope (inserted by a given relay
+ /// if this is received packet or to be decapsulated by a given relay if
+ /// this is a transmitted packet). nesting_level specifies which relay
+ /// scope is to be used. 0 is the outermost encapsulation (relay closest to
+ /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
+ /// (relay closest to the client).
+ ///
+ /// @throw isc::OutOfRange if nesting level has invalid value.
+ ///
+ /// @param option_code code of the requested option
+ /// @param nesting_level see description above
+ ///
+ /// @return pointer to the option (or null if there is no such option)
+ OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
+
+ /// @brief Returns options inserted by relay
+ ///
+ /// Returns options from specified relay scope (inserted by a given relay
+ /// if this is received packet or to be decapsulated by a given relay if
+ /// this is a transmitted packet). nesting_level specifies which relay
+ /// scope is to be used. 0 is the outermost encapsulation (relay closest to
+ /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
+ /// (relay closest to the client).
+ ///
+ /// @throw isc::OutOfRange if nesting level has invalid value.
+ ///
+ /// @param option_code code of the requested option
+ /// @param nesting_level see description above
+ ///
+ /// @return Collection of options found.
+ OptionCollection getRelayOptions(uint16_t option_code,
+ uint8_t nesting_level);
+
+private:
+
+ /// @brief Prepares parameters for loop used in @ref getAnyRelayOption
+ /// and @ref getNonCopiedAnyRelayOption.
+ ///
+ /// The methods retrieving "any" relay option iterate over the relay
+ /// info structures to find the matching option. This method returns
+ /// the index of the first and last relay info structure to be used
+ /// for this iteration. It also returns the direction in which the
+ /// iteration should be performed.
+ ///
+ /// @param order Option search order (see @ref RelaySearchOrder).
+ /// @param [out] start Index of the relay information structure from
+ /// which the search should be started.
+ /// @param [out] end Index of the relay information structure on which
+ /// the option searches should stop.
+ /// @param [out] direction Equals to -1 for backwards searches, and
+ /// equals to 1 for forward searches.
+ void prepareGetAnyRelayOption(const RelaySearchOrder& order,
+ int& start, int& end, int& direction) const;
+
+protected:
+
+ /// @brief Returns pointer to an instance of specified option.
+ ///
+ /// This is a variant of @ref getAnyRelayOption but it never copies
+ /// an option returned. This method should be only used by
+ /// the @ref Pkt6 class and derived classes. Any external callers should
+ /// use @ref getAnyRelayOption which copies the option before returning it
+ /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+ ///
+ /// @param option_code Searched option.
+ /// @param order Option search order (see @ref RelaySearchOrder).
+ ///
+ /// @return Option pointer or null, if no option matches specified criteria.
+ OptionPtr getNonCopiedAnyRelayOption(const uint16_t option_code,
+ const RelaySearchOrder& order) const;
+
+ /// @brief Returns pointers to instances of specified option.
+ ///
+ /// This is a variant of @ref getAllRelayOptions but it never copies
+ /// an option returned. This method should be only used by
+ /// the @ref Pkt6 class and derived classes. Any external callers should
+ /// use @ref getAnyRelayOption which copies the option before returning it
+ /// when the @ref Pkt::copy_retrieved_options_ flag is set to true.
+ ///
+ /// @param option_code Searched option.
+ /// @param order Option search order (see @ref RelaySearchOrder).
+ ///
+ /// @return Collection of options found.
+ OptionCollection getNonCopiedAllRelayOptions(const uint16_t option_code,
+ const RelaySearchOrder& order) const;
+
+public:
+
+ /// @brief Return first instance of a specified option
+ ///
+ /// When a client's packet traverses multiple relays, each passing relay may
+ /// insert extra options. This method allows the specific instance of a given
+ /// option to be obtained (e.g. closest to the client, closest to the server,
+ /// etc.) See @ref RelaySearchOrder for a detailed description.
+ ///
+ /// @param option_code searched option
+ /// @param order option search order (see @ref RelaySearchOrder)
+ /// @return option pointer (or null if no option matches specified criteria)
+ OptionPtr getAnyRelayOption(const uint16_t option_code,
+ const RelaySearchOrder& order);
+
+ /// @brief Return first instances of a specified option
+ ///
+ /// When a client's packet traverses multiple relays, each passing
+ /// relay may insert extra options. This method allows the
+ /// specific instances of a given option to be obtained in the
+ /// specified order (e.g. first closest to the client, first
+ /// closest to the server, etc.) See @ref RelaySearchOrder for a
+ /// detailed description.
+ ///
+ /// @param option_code searched option
+ /// @param order option search order (see @ref RelaySearchOrder)
+ /// @return Collection of options found.
+ OptionCollection getAllRelayOptions(const uint16_t option_code,
+ const RelaySearchOrder& order);
+
+ /// @brief return the link address field from a relay option
+ ///
+ /// As with @c Pkt6::getRelayOption this returns information from the
+ /// specified relay scope. The relay_level specifies which relay
+ /// scope is to be used. 0 is the outermost encapsulation (relay closest
+ /// to the server). pkt->relay_info_.size() -1 is the innermost encapsulation
+ /// (relay closest to the client).
+ ///
+ /// @throw isc::OutOfRange if relay level has an invalid value.
+ ///
+ /// @param relay_level see description above
+ ///
+ /// @return pointer to the link address field
+ const isc::asiolink::IOAddress&
+ getRelay6LinkAddress(uint8_t relay_level) const;
+
+ /// @brief return the peer address field from a relay option
+ ///
+ /// As with @c Pkt6::getRelayOption this returns information from the
+ /// specified relay scope. The relay_level specifies which relay
+ /// scope is to be used. 0 is the outermost encapsulation (relay closest
+ /// to the server). pkt->relay_info_.size() -1 is the innermost encapsulation
+ /// (relay closest to the client).
+ ///
+ /// @throw isc::OutOfRange if relay level has an invalid value.
+ ///
+ /// @param relay_level see description above
+ ///
+ /// @return pointer to the peer address field
+ const isc::asiolink::IOAddress&
+ getRelay6PeerAddress(uint8_t relay_level) const;
+
+ /// @brief add information about one traversed relay
+ ///
+ /// This adds information about one traversed relay, i.e.
+ /// one relay-forw or relay-repl level of encapsulation.
+ ///
+ /// @param relay structure with necessary relay information
+ void addRelayInfo(const RelayInfo& relay);
+
+ /// @brief Returns name of the DHCPv6 message for a given type number.
+ ///
+ /// As the operation of the method does not depend on any server state, it
+ /// is declared static. There is also non-static getName() method that
+ /// works on Pkt6 objects.
+ ///
+ /// @param type DHCPv6 message type which name should be returned.
+ ///
+ /// @return Pointer to "const" string containing the message name. If
+ /// the message type is unknown the "UNKNOWN" is returned. The caller
+ /// must not release the returned pointer.
+ static const char* getName(const uint8_t type);
+
+ /// @brief Returns name of the DHCPv6 message.
+ ///
+ /// This method requires an object. There is also a static version, which
+ /// requires one parameter (type).
+ ///
+ /// @return Pointer to "const" string containing the message name. If
+ /// the message type is unknown the "UNKNOWN" is returned. The caller
+ /// must not release the returned pointer.
+ const char* getName() const;
+
+ /// @brief copies relay information from client's packet to server's response
+ ///
+ /// This information is not simply copied over. Some parameter are
+ /// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc.
+ ///
+ /// @param question client's packet
+ void copyRelayInfo(const Pkt6Ptr& question);
+
+ /// @brief Relay information.
+ ///
+ /// This is a public field. Otherwise we hit one of the two problems:
+ /// we return reference to an internal field (and that reference could
+ /// be potentially used past Pkt6 object lifetime causing badness) or
+ /// we return a copy (which is inefficient and also causes any updates
+ /// to be impossible). Therefore public field is considered the best
+ /// (or least bad) solution.
+ ///
+ /// This vector is arranged in the order packet is encapsulated, i.e.
+ /// relay[0] was the outermost encapsulation (relay closest to the server),
+ /// relay[last] was the innermost encapsulation (relay closest to the
+ /// client).
+ std::vector<RelayInfo> relay_info_;
+
+protected:
+
+ /// @brief Attempts to generate MAC/Hardware address from IPv6 link-local
+ /// address.
+ ///
+ /// This method uses source IPv6 address for direct messages and the
+ /// peeraddr or the first relay that saw that packet. It may fail if the
+ /// address is not link-local or does not use EUI-64 identifier.
+ ///
+ /// @return Hardware address (or NULL)
+ virtual HWAddrPtr getMACFromSrcLinkLocalAddr();
+
+ /// @brief Extract MAC/Hardware address from client link-layer address
+ // option inserted by a relay agent (RFC6939).
+ ///
+ /// This method extracts the client's hardware address from the
+ // client-linklayer-addr option inserted by the relay agent closest to
+ // the client.
+ ///
+ /// @return Hardware address (or NULL)
+ virtual HWAddrPtr getMACFromIPv6RelayOpt();
+
+ /// @brief Extract MAC/Hardware address from client-id.
+ ///
+ /// This method attempts to extract MAC/Hardware address from DUID sent
+ /// as client-id. This method may fail, as only DUID-LLT and DUID-LL are
+ /// based on link-layer addresses. Client may use other valid DUID types
+ /// and this method will fail.
+ ///
+ /// @return Hardware address (or NULL)
+ virtual HWAddrPtr getMACFromDUID();
+
+ /// @brief Attempts to extract MAC/Hardware address from DOCSIS options
+ /// inserted by the modem itself.
+ ///
+ /// The mechanism extracts that information from DOCSIS option
+ /// (vendor-specific info, vendor-id=4491, suboption 36). Note that
+ /// in a DOCSIS capable network, the MAC address information is provided
+ /// several times. The first is specified by the modem itself. The second
+ /// is added by the CMTS, which acts as a relay agent. This method
+ /// attempts to extract the former. See @ref getMACFromDocsisCMTS
+ /// for a similar method that extracts from the CMTS (relay) options.
+ ///
+ /// @return hardware address (if DOCSIS suboption 36 is present)
+ virtual HWAddrPtr getMACFromDocsisModem();
+
+ /// @brief Attempts to extract MAC/Hardware address from DOCSIS options.
+ ///
+ /// The DHCPv6 mechanism extracts that information from DOCSIS option
+ /// (vendor-specific info, vendor-id=4491, suboption 1026). Note that
+ /// in a DOCSIS capable network, the MAC address information is provided
+ /// several times. The first is specified by the modem itself. The second
+ /// is added by the CMTS, which acts as a relay agent. This method
+ /// attempts to extract the latter. See @ref getMACFromDocsisModem
+ /// for a similar method that extracts from the modem (client) options.
+ ///
+ /// @return hardware address (if DOCSIS suboption 1026 is present)
+ virtual HWAddrPtr getMACFromDocsisCMTS();
+
+ /// @brief Attempts to obtain MAC address from remote-id relay option.
+ ///
+ /// This method is called from getMAC(HWADDR_SOURCE_REMOTE_ID) and should not be
+ /// called directly. It will attempt to extract MAC address information
+ /// from remote-id option inserted by a relay agent closest to the client.
+ /// If this method fails, it will return NULL.
+ ///
+ /// @return hardware address (or NULL)
+ virtual HWAddrPtr getMACFromRemoteIdRelayOption();
+
+ /// @brief Builds on wire packet for TCP transmission.
+ ///
+ /// @todo This function is not implemented yet.
+ ///
+ /// @throw NotImplemented, IPv6 over TCP is not yet supported.
+ void packTCP();
+
+ /// @brief Builds on wire packet for UDP transmission.
+ ///
+ /// @throw InvalidOperation if packing fails
+ void packUDP();
+
+ /// @brief Parses on-wire form of TCP DHCPv6 packet.
+ ///
+ /// Parses received packet, stored in on-wire format in data_.
+ /// data_len_ must be set to indicate data length.
+ /// Will create a collection of option objects that will
+ /// be stored in options_ container.
+ ///
+ /// @todo This function is not implemented yet.
+ ///
+ /// @throw tbd
+ void unpackTCP();
+
+ /// @brief Parses on-wire form of UDP DHCPv6 packet.
+ ///
+ /// Parses received packet, stored in on-wire format in data_.
+ /// data_len_ must be set to indicate data length.
+ /// Will create a collection of option objects that will
+ /// be stored in options_ container.
+ ///
+ /// @throw tbd
+ void unpackUDP();
+
+ /// @brief Unpacks direct (non-relayed) message.
+ ///
+ /// This method unpacks specified buffer range as a direct
+ /// (e.g. solicit or request) message. This method is called from
+ /// unpackUDP() when received message is detected to be direct.
+ ///
+ /// @param begin start of the buffer
+ /// @param end end of the buffer
+ /// @throw tbd
+ void unpackMsg(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end);
+
+ /// @brief Unpacks relayed message (RELAY-FORW or RELAY-REPL).
+ ///
+ /// This method is called from unpackUDP() when received message
+ /// is detected to be relay-message. It goes iteratively over
+ /// all relays (if there are multiple encapsulation levels).
+ ///
+ /// @throw tbd
+ void unpackRelayMsg();
+
+ /// @brief Calculates overhead introduced in specified relay.
+ ///
+ /// It is used when calculating message size and packing message
+ /// @param relay RelayInfo structure that holds information about relay
+ /// @return number of bytes needed to store relay information
+ uint16_t getRelayOverhead(const RelayInfo& relay) const;
+
+ /// @brief Calculates overhead for all relays defined for this message.
+ /// @return number of bytes needed to store all relay information
+ uint16_t calculateRelaySizes();
+
+ /// @brief Calculates size of the message as if it was not relayed at all.
+ ///
+ /// This is equal to len() if the message was not relayed.
+ /// @return number of bytes required to store the message
+ uint16_t directLen() const;
+
+ /// UDP (usually) or TCP (bulk leasequery or failover)
+ DHCPv6Proto proto_;
+
+ /// DHCPv6 message type
+ uint8_t msg_type_;
+
+}; // Pkt6 class
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif
diff --git a/src/lib/dhcp/pkt_filter.cc b/src/lib/dhcp/pkt_filter.cc
new file mode 100644
index 0000000..770d71d
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter.cc
@@ -0,0 +1,72 @@
+// Copyright (C) 2013-2019 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+
+#include <sys/socket.h>
+#include <fcntl.h>
+
+namespace isc {
+namespace dhcp {
+
+int
+PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr,
+ const uint16_t port) {
+ // Create socket.
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ isc_throw(SocketConfigError, "failed to create fallback socket for"
+ " address " << addr << ", port " << port
+ << ", reason: " << strerror(errno));
+ }
+ // Set the close-on-exec flag.
+ if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
+ << " on fallback socket for address " << addr
+ << ", port " << port
+ << ", reason: " << strerror(errno));
+ }
+ // Bind the socket to a specified address and port.
+ struct sockaddr_in addr4;
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+ addr4.sin_addr.s_addr = htonl(addr.toUint32());
+ addr4.sin_port = htons(port);
+
+ if (bind(sock, reinterpret_cast<struct sockaddr*>(&addr4),
+ sizeof(addr4)) < 0) {
+ // Get the error message immediately after the bind because the
+ // invocation to close() below would override the errno.
+ char* errmsg = strerror(errno);
+ // Remember to close the socket if we failed to bind it.
+ close(sock);
+ isc_throw(SocketConfigError, "failed to bind fallback socket to"
+ " address " << addr << ", port " << port
+ << ", reason: " << errmsg
+ << " - is another DHCP server running?");
+ }
+
+ // Set socket to non-blocking mode. This is to prevent the read from the
+ // fallback socket to block message processing on the primary socket.
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
+ // Get the error message immediately after the bind because the
+ // invocation to close() below would override the errno.
+ char* errmsg = strerror(errno);
+ close(sock);
+ isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the"
+ " fallback socket, bound to " << addr << ", port "
+ << port << ", reason: " << errmsg);
+ }
+ // Successfully created and bound a fallback socket. Return a descriptor.
+ return (sock);
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter.h b/src/lib/dhcp/pkt_filter.h
new file mode 100644
index 0000000..e175bef
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter.h
@@ -0,0 +1,139 @@
+// Copyright (C) 2013-2017 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 PKT_FILTER_H
+#define PKT_FILTER_H
+
+#include <dhcp/pkt4.h>
+#include <asiolink/io_address.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid packet filter object specified.
+class InvalidPacketFilter : public Exception {
+public:
+ InvalidPacketFilter(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// Forward declaration to the structure describing a socket.
+struct SocketInfo;
+
+/// Forward declaration to the class representing interface
+class Iface;
+
+/// @brief Abstract packet handling class
+///
+/// This class represents low level method to send and receive DHCP packet.
+/// Different methods, represented by classes derived from this class, use
+/// different socket families and socket types. Also, various packet filtering
+/// methods can be implemented by derived classes, e.g. Linux Packet
+/// Filtering (LPF) or Berkeley Packet Filtering (BPF).
+///
+/// Low-level code operating on sockets may require special privileges to execute.
+/// For example: opening raw socket or opening socket on low port number requires
+/// root privileges. This makes it impossible or very hard to unit test the IfaceMgr.
+/// In order to overcome this problem, it is recommended to create mock object derived
+/// from this class that mimics the behavior of the real packet handling class making
+/// IfaceMgr testable.
+class PktFilter {
+public:
+
+ /// @brief Virtual Destructor
+ virtual ~PktFilter() { }
+
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// Checks if the Packet Filter class has capability to send a packet
+ /// directly to the client having no address assigned. This capability
+ /// is used by DHCPv4 servers which respond to the clients they assign
+ /// addresses to. Not all classes derived from PktFilter support this
+ /// because it requires injection of the destination host HW address to
+ /// the link layer header of the packet.
+ ///
+ /// @return true of the direct response is supported.
+ virtual bool isDirectResponseSupported() const = 0;
+
+ /// @brief Open primary and fallback socket.
+ ///
+ /// A method implementation in the derived class may open one or two
+ /// sockets:
+ /// - a primary socket - used for communication with clients. DHCP messages
+ /// received using this socket are processed and the same socket is used
+ /// to send a response to the client.
+ /// - a fallback socket which is optionally opened if there is a need for
+ /// the presence of the socket which can be bound to a specific IP address
+ /// and UDP port (e.g. raw primary socket can't be). For the details, see
+ /// the documentation of @c isc::dhcp::SocketInfo.
+ ///
+ /// @param iface Interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number.
+ /// @param receive_bcast Configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @return A structure describing a primary and fallback socket.
+ virtual SocketInfo openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) = 0;
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @return Received packet
+ virtual Pkt4Ptr receive(Iface& iface,
+ const SocketInfo& socket_info) = 0;
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param iface interface to be used to send packet
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending the packet. It is 0 if successful.
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt) = 0;
+
+protected:
+
+ /// @brief Default implementation to open a fallback socket.
+ ///
+ /// This method provides a means to open a fallback socket and bind it
+ /// to a given IPv4 address and UDP port. This function may be used by the
+ /// derived classes to create a fallback socket. It can be overridden
+ /// in the derived classes if it happens to be insufficient on some
+ /// environments.
+ ///
+ /// The fallback socket is meant to be opened together with the other socket
+ /// (a.k.a. primary socket) used to receive and handle DHCPv4 traffic. The
+ /// traffic received through the fallback should be dropped. The reasoning
+ /// behind opening the fallback socket is explained in the documentation of
+ /// @c isc::dhcp::SocketInfo structure.
+ ///
+ /// @param addr An IPv4 address to bind the socket to.
+ /// @param port A port number to bind socket to.
+ ///
+ /// @return A fallback socket descriptor. This descriptor should be assigned
+ /// to the @c fallbackfd_ field of the @c isc::dhcp::SocketInfo structure.
+ /// @throw isc::dhcp::SocketConfigError if socket opening, binding or
+ /// configuration fails.
+ virtual int openFallbackSocket(const isc::asiolink::IOAddress& addr,
+ const uint16_t port);
+};
+
+/// Pointer to a PktFilter object.
+typedef boost::shared_ptr<PktFilter> PktFilterPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_H
diff --git a/src/lib/dhcp/pkt_filter6.cc b/src/lib/dhcp/pkt_filter6.cc
new file mode 100644
index 0000000..b72de5c
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter6.cc
@@ -0,0 +1,38 @@
+// Copyright (C) 2013-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 <dhcp/pkt_filter6.h>
+
+namespace isc {
+namespace dhcp {
+
+bool
+PktFilter6::joinMulticast(int sock, const std::string& ifname,
+ const std::string & mcast) {
+
+ struct ipv6_mreq mreq;
+ memset(&mreq, 0, sizeof(ipv6_mreq));
+
+ // Convert the multicast address to a binary form.
+ if (inet_pton(AF_INET6, mcast.c_str(), &mreq.ipv6mr_multiaddr) <= 0) {
+ return (false);
+ }
+ // Set the interface being used.
+ mreq.ipv6mr_interface = if_nametoindex(ifname.c_str());
+ // Join the multicast group.
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq)) < 0) {
+ return (false);
+ }
+
+ return (true);
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter6.h b/src/lib/dhcp/pkt_filter6.h
new file mode 100644
index 0000000..c7eb9e3
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter6.h
@@ -0,0 +1,141 @@
+// Copyright (C) 2013-2015,2021 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 PKT_FILTER6_H
+#define PKT_FILTER6_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration to the structure describing a socket.
+struct SocketInfo;
+
+/// Forward declaration to the class representing interface
+class Iface;
+
+/// @brief Abstract packet handling class for DHCPv6.
+///
+/// This class defines methods for performing low level operations on IPv6
+/// socket:
+/// - open socket,
+/// - send DHCPv6 message through the socket,
+/// - receive DHCPv6 through the socket.
+///
+/// Methods exposed by this class are called through the @c IfaceMgr only. They
+/// are not meant to be called directly, except unit testing.
+///
+/// The @c IfaceMgr is responsible for managing the pool of sockets. In
+/// particular, @c IfaceMgr detects interfaces suitable to send/receive DHCPv6
+/// messages. When it intends to open a socket on a particular interface, it
+/// will call the PktFilter6::openSocket. If this call is successful, the
+/// structure describing a new socket is returned.
+///
+/// In order to send or receive a DHCPv6 message through this socket,
+/// the @c IfaceMgr must use PktFilter6::send or PktFilter6::receive
+/// functions of the same class that has been used to open a socket,
+/// i.e. all send/receive operations should be performed using this
+/// particular class.
+///
+/// The major motivation behind creating a separate class, to manage low level
+/// operations using sockets, is to make @c IfaceMgr unit testable. By providing
+/// a stub implementation of this class which mimics the behavior of the real
+/// socket handling class, it is possible to simulate and test various
+/// conditions. For example, the @c IfaceMgr::openSockets function will try to
+/// open sockets on all available interfaces. The test doesn't have any means
+/// to know which interfaces are present. In addition, even if the network
+/// interface detection was implemented on the test side, there is no guarantee
+/// that the particular system has sufficient number of suitable IPv6-enabled
+/// interfaces available for a particular test. Moreover, the test may need
+/// to tweak some of the interface configuration to cover certain test
+/// scenarios. The proposed solution is to not use the actual interfaces
+/// but simply create a pool of fake interfaces which configuration
+/// can be freely modified by a test. This however implies that operations
+/// on sockets must be simulated.
+///
+/// @note This class is named after @c PktFilter abstract class which exposes
+/// similar interface for DHCPv4. However, the PktFilter class is devoted to
+/// solve the problem of sending DHCPv4 messages to the hosts which don't have
+/// an IP address yet (a.k.a. direct DHCPv4 traffic). Where required, the
+/// custom implementations of @c PktFilter are provided to send and receive
+/// messages through raw sockets. In order to filter out the desired traffic
+/// Linux Packet Filtering or Berkeley Packet Filtering is used, hence the
+/// name of the class. In case of DHCPv6 regular IPv6/UDPv6 sockets are used
+/// and derived classes do not use Linux or Berkeley Packet Filtering.
+class PktFilter6 {
+public:
+
+ /// @brief Virtual Destructor.
+ virtual ~PktFilter6() { }
+
+ /// @brief Opens a socket.
+ ///
+ /// This function open an IPv6 socket on an interface and binds it to a
+ /// specified UDP port and IPv6 address.
+ ///
+ /// @param iface Interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number.
+ /// @param join_multicast A boolean parameter which indicates whether
+ /// socket should join All_DHCP_Relay_Agents_and_servers multicast
+ /// group.
+ ///
+ /// @return A structure describing a primary and fallback socket.
+ virtual SocketInfo openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool join_multicast) = 0;
+
+ /// @brief Receives DHCPv6 message on the interface.
+ ///
+ /// This function receives a single DHCPv6 message through using a socket
+ /// open on a specified interface.
+ ///
+ /// @param socket_info A structure holding socket information.
+ ///
+ /// @return A pointer to received message.
+ virtual Pkt6Ptr receive(const SocketInfo& socket_info) = 0;
+
+ /// @brief Sends DHCPv6 message through a specified interface and socket.
+ ///
+ /// This function sends a DHCPv6 message through a specified interface and
+ /// socket. In general, there may be multiple sockets open on a single
+ /// interface as a single interface may have multiple IPv6 addresses.
+ ///
+ /// @param iface Interface to be used to send packet.
+ /// @param sockfd A socket descriptor
+ /// @param pkt A packet to be sent.
+ ///
+ /// @return A result of sending the message. It is 0 if successful.
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt6Ptr& pkt) = 0;
+
+ /// @brief Joins IPv6 multicast group on a socket.
+ ///
+ /// This function joins the socket to the specified multicast group.
+ /// The socket descriptor must point to a valid socket bound to the
+ /// in6addr_any prior to calling this function.
+ ///
+ /// @param sock A socket descriptor (socket must be bound).
+ /// @param ifname An interface name (for link-scoped multicast groups).
+ /// @param mcast A multicast address to join (e.g. "ff02::1:2").
+ ///
+ /// @return true if multicast join was successful
+ static bool joinMulticast(int sock, const std::string& ifname,
+ const std::string & mcast);
+
+};
+
+
+/// Pointer to a PktFilter object.
+typedef boost::shared_ptr<PktFilter6> PktFilter6Ptr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER6_H
diff --git a/src/lib/dhcp/pkt_filter_bpf.cc b/src/lib/dhcp/pkt_filter_bpf.cc
new file mode 100644
index 0000000..20d0098
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_bpf.cc
@@ -0,0 +1,606 @@
+// Copyright (C) 2014-2017 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 <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_bpf.h>
+#include <dhcp/protocol_util.h>
+#include <exceptions/exceptions.h>
+#include <algorithm>
+#include <net/bpf.h>
+#include <netinet/if_ether.h>
+
+namespace {
+
+using namespace isc::dhcp;
+
+/// @brief Maximum number of attempts to open BPF device.
+const unsigned int MAX_BPF_OPEN_ATTEMPTS = 100;
+
+/// @brief Length of the header containing the address family for the packet
+/// received on local loopback interface.
+const unsigned int BPF_LOCAL_LOOPBACK_HEADER_LEN = 4;
+
+/// The following structure defines a Berkeley Packet Filter program to perform
+/// packet filtering. The program operates on Ethernet packets. To help with
+/// interpretation of the program, for the types of Ethernet packets we are
+/// interested in, the header layout is:
+///
+/// 6 bytes Destination Ethernet Address
+/// 6 bytes Source Ethernet Address
+/// 2 bytes Ethernet packet type
+///
+/// 20 bytes Fixed part of IP header
+/// variable Variable part of IP header
+///
+/// 2 bytes UDP Source port
+/// 2 bytes UDP destination port
+/// 4 bytes Rest of UDP header
+///
+/// Each instruction is preceded with the comment giving the instruction
+/// number within a BPF program, in the following format: #123.
+///
+/// @todo We may want to extend the filter to receive packets sent
+/// to the particular IP address assigned to the interface or
+/// broadcast address.
+struct bpf_insn ethernet_ip_udp_filter [] = {
+ // Make sure this is an IP packet: check the half-word (two bytes)
+ // at offset 12 in the packet (the Ethernet packet type). If it
+ // is, advance to the next instruction. If not, advance 11
+ // instructions (which takes execution to the last instruction in
+ // the sequence: "drop it").
+ // #0
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET),
+ // #1
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 11),
+
+ // Make sure it's a UDP packet. The IP protocol is at offset
+ // 9 in the IP header so, adding the Ethernet packet header size
+ // of 14 bytes gives an absolute byte offset in the packet of 23.
+ // #2
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+ ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
+ // #3
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9),
+
+ // Make sure this isn't a fragment by checking that the fragment
+ // offset field in the IP header is zero. This field is the
+ // least-significant 13 bits in the bytes at offsets 6 and 7 in
+ // the IP header, so the half-word at offset 20 (6 + size of
+ // Ethernet header) is loaded and an appropriate mask applied.
+ // #4
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET),
+ // #5
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0),
+
+ // Check the packet's destination address. The program will only
+ // allow the packets sent to the broadcast address or unicast
+ // to the specific address on the interface. By default, this
+ // address is set to 0 and must be set to the specific value
+ // when the raw socket is created and the program is attached
+ // to it. The caller must assign the address to the
+ // prog.bf_insns[8].k in the network byte order.
+ // #6
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS,
+ ETHERNET_HEADER_LEN + IP_DEST_ADDR_OFFSET),
+ // If this is a broadcast address, skip the next check.
+ // #7
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 1, 0),
+ // If this is not broadcast address, compare it with the unicast
+ // address specified for the interface.
+ // #8
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 4),
+
+ // Get the IP header length. This is achieved by the following
+ // (special) instruction that, given the offset of the start
+ // of the IP header (offset 14) loads the IP header length.
+ // #9
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN),
+
+ // Make sure it's to the right port. The following instruction
+ // adds the previously extracted IP header length to the given
+ // offset to locate the correct byte. The given offset of 16
+ // comprises the length of the Ethernet header (14) plus the offset
+ // of the UDP destination port (2) within the UDP header.
+ // #10
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT),
+ // The following instruction tests against the default DHCP server port,
+ // but the action port is actually set in PktFilterBPF::openSocket().
+ // N.B. The code in that method assumes that this instruction is at
+ // offset 11 in the program. If this is changed, openSocket() must be
+ // updated.
+ // #11
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
+
+ // If we passed all the tests, ask for the whole packet.
+ // #12
+ BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+ // Otherwise, drop it.
+ // #13
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+/// The following structure defines a BPF program to perform packet filtering
+/// on local loopback interface. The packets received on this interface do not
+/// contain the regular link-layer header, but rather a 4-byte long pseudo
+/// header containing the address family. The reminder of the packet contains
+/// IP header, UDP header and a DHCP message.
+///
+/// Each instruction is preceded with the comment giving the instruction
+/// number within a BPF program, in the following format: #123.
+struct bpf_insn loopback_ip_udp_filter [] = {
+ // Make sure this is an IP packet. The pseudo header comprises a 4-byte
+ // long value identifying the address family, which should be set to
+ // AF_INET. The default value used here (0xFFFFFFFF) must be overridden
+ // with htonl(AF_INET) from within the openSocket function.
+ // #0
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0),
+ // #1
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xFFFFFFFF, 0, 11),
+
+ // Make sure it's a UDP packet. The IP protocol is at offset
+ // 9 in the IP header so, adding the pseudo header size 4 bytes
+ // gives an absolute byte offset in the packet of 13.
+ // #2
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+ BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
+ // #3
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9),
+
+ // Make sure this isn't a fragment by checking that the fragment
+ // offset field in the IP header is zero. This field is the
+ // least-significant 13 bits in the bytes at offsets 6 and 7 in
+ // the IP header, so the half-word at offset 10 (6 + size of
+ // pseudo header) is loaded and an appropriate mask applied.
+ // #4
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
+ BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_FLAGS_OFFSET),
+ // #5
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0),
+
+ // Check the packet's destination address. The program will only
+ // allow the packets sent to the broadcast address or unicast
+ // to the specific address on the interface. By default, this
+ // address is set to 0 and must be set to the specific value
+ // when the raw socket is created and the program is attached
+ // to it. The caller must assign the address to the
+ // prog.bf_insns[8].k in the network byte order.
+ // #6
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS,
+ BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_DEST_ADDR_OFFSET),
+ // If this is a broadcast address, skip the next check.
+ // #7
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 1, 0),
+ // If this is not broadcast address, compare it with the unicast
+ // address specified for the interface.
+ // #8
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 4),
+
+ // Get the IP header length. This is achieved by the following
+ // (special) instruction that, given the offset of the start
+ // of the IP header (offset 4) loads the IP header length.
+ // #9
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, BPF_LOCAL_LOOPBACK_HEADER_LEN),
+
+ // Make sure it's to the right port. The following instruction
+ // adds the previously extracted IP header length to the given
+ // offset to locate the correct byte. The given offset of 6
+ // comprises the length of the pseudo header (4) plus the offset
+ // of the UDP destination port (2) within the UDP header.
+ // #10
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND,
+ BPF_LOCAL_LOOPBACK_HEADER_LEN + UDP_DEST_PORT),
+ // The following instruction tests against the default DHCP server port,
+ // but the action port is actually set in PktFilterBPF::openSocket().
+ // N.B. The code in that method assumes that this instruction is at
+ // offset 11 in the program. If this is changed, openSocket() must be
+ // updated.
+ // #11
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
+
+ // If we passed all the tests, ask for the whole packet.
+ // #12
+ BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+ // Otherwise, drop it.
+ // #13
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+
+}
+
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+SocketInfo
+PktFilterBPF::openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool,
+ const bool) {
+
+ // Open fallback socket first. If it fails, it will give us an indication
+ // that there is another service (perhaps DHCP server) running.
+ // The function will throw an exception and effectively cease opening
+ // the BPF device below.
+ int fallback = openFallbackSocket(addr, port);
+
+ // Fallback has opened, so let's open the BPF device that we will be
+ // using for receiving and sending packets. The BPF device is opened
+ // by opening a file /dev/bpf%d where %d is a number. There may be
+ // devices already open so we will try them one by one and open the
+ // one that is not busy.
+ int sock = -1;
+ for (unsigned int bpf_dev = 0;
+ bpf_dev < MAX_BPF_OPEN_ATTEMPTS && (sock < 0);
+ ++bpf_dev) {
+ std::ostringstream s;
+ s << "/dev/bpf" << bpf_dev;
+ sock = open(s.str().c_str(), O_RDWR, 0);
+ if (sock < 0) {
+ // If device is busy, try another one.
+ if (errno == EBUSY) {
+ continue;
+ }
+ // All other errors are fatal, so close the fallback socket
+ // and throw.
+ close(fallback);
+ isc_throw(SocketConfigError,
+ "Failed to open BPF device " << s.str());
+ }
+ }
+
+ // Set the close-on-exec flag.
+ if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
+ close(fallback);
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
+ << " on BPF device with interface " << iface.getName());
+ }
+
+ // The BPF device is now open. Now it needs to be configured.
+
+ // Associate the device with the interface name.
+ struct ifreq iface_data;
+ memset(&iface_data, 0, sizeof(iface_data));
+ std::strncpy(iface_data.ifr_name, iface.getName().c_str(),
+ std::min(static_cast<int>(IFNAMSIZ),
+ static_cast<int>(iface.getName().length())));
+ if (ioctl(sock, BIOCSETIF, &iface_data) < 0) {
+ close(fallback);
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to associate BPF device "
+ " with interface " << iface.getName());
+ }
+
+ // Get the BPF version supported by the kernel. Every application
+ // must check this version against the current version in use.
+ struct bpf_version ver;
+ if (ioctl(sock, BIOCVERSION, &ver) < 0) {
+ close(fallback);
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to obtain the BPF version"
+ " number from the kernel");
+ }
+ // Major BPF version must match and the minor version that the kernel
+ // runs must be at least the current version in use.
+ if ((ver.bv_major != BPF_MAJOR_VERSION) ||
+ (ver.bv_minor < BPF_MINOR_VERSION)) {
+ close(fallback);
+ close(sock);
+ isc_throw(SocketConfigError, "Invalid BPF version: "
+ << ver.bv_major << "." << ver.bv_minor
+ << " Expected at least version:"
+ << BPF_MAJOR_VERSION << "."
+ << BPF_MINOR_VERSION);
+ }
+
+ // Get the size of the read buffer for this device. We will need to
+ // allocate the buffer of this size for packet reads.
+ unsigned int buf_len = 0;
+ if (ioctl(sock, BIOCGBLEN, &buf_len) < 0) {
+ close(fallback);
+ close(sock);
+ isc_throw(SocketConfigError, "Unable to obtain the required"
+ " buffer length for reads from BPF device");
+ }
+
+ if (buf_len < sizeof(bpf_hdr)) {
+ isc_throw(SocketConfigError, "read buffer length returned by the"
+ " kernel for the BPF device associated with the interface"
+ << iface.getName() << " is lower than the BPF header"
+ " length: this condition is impossible unless the"
+ " operating system is really broken!");
+ }
+
+ // Set the filter program so as we only get packets we are interested in.
+ struct bpf_program prog;
+ memset(&prog, 0, sizeof(bpf_program));
+ if (iface.flag_loopback_) {
+ prog.bf_insns = loopback_ip_udp_filter;
+ prog.bf_len = sizeof(loopback_ip_udp_filter) / sizeof(struct bpf_insn);
+ // The address family is AF_INET. It can't be hardcoded in the BPF program
+ // because we need to make the host to network order conversion using htonl
+ // and conversion can't be done within the BPF program structure as it
+ // doesn't work on some systems.
+ prog.bf_insns[1].k = htonl(AF_INET);
+
+ } else {
+ prog.bf_insns = ethernet_ip_udp_filter;
+ prog.bf_len = sizeof(ethernet_ip_udp_filter) / sizeof(struct bpf_insn);
+ }
+
+ // Configure the BPF program to receive unicast packets sent to the
+ // specified address. The program will also allow packets sent to the
+ // 255.255.255.255 broadcast address.
+ prog.bf_insns[8].k = addr.toUint32();
+
+ // Configure the BPF program to receive packets on the specified port.
+ prog.bf_insns[11].k = port;
+
+ // Actually set the filter program for the device.
+ if (ioctl(sock, BIOCSETF, &prog) < 0) {
+ close(fallback);
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to install BPF filter"
+ " program");
+ }
+
+ // Configure the BPF device to use the immediate mode. This ensures
+ // that the read function returns immediately, instead of waiting
+ // for the kernel to fill up the buffer, which would likely cause
+ // read hangs.
+ int flag = 1;
+ if (ioctl(sock, BIOCIMMEDIATE, &flag) < 0) {
+ close(fallback);
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set promiscuous mode for"
+ " BPF device");
+ }
+
+ // Everything is ok, allocate the read buffer and return the socket
+ // (BPF device descriptor) to the caller.
+ try {
+ iface.resizeReadBuffer(buf_len);
+
+ } catch (...) {
+ close(fallback);
+ close(sock);
+ throw;
+ }
+ return (SocketInfo(addr, port, sock, fallback));
+}
+
+Pkt4Ptr
+PktFilterBPF::receive(Iface& iface, const SocketInfo& socket_info) {
+ // When using BPF, the read buffer must be allocated for the interface.
+ // If it is not allocated, it is a programmatic error.
+ if (iface.getReadBufferSize() == 0) {
+ isc_throw(SocketConfigError, "socket read buffer empty"
+ " for the interface: " << iface.getName());
+ }
+
+ // First let's get some data from the fallback socket. The data will be
+ // discarded but we don't want the socket buffer to bloat. We get the
+ // packets from the socket in loop but most of the time the loop will
+ // end after receiving one packet. The call to recv returns immediately
+ // when there is no data left on the socket because the socket is
+ // non-blocking.
+ // @todo In the normal conditions, both the primary socket and the fallback
+ // socket are in sync as they are set to receive packets on the same
+ // address and port. The reception of packets on the fallback socket
+ // shouldn't cause significant lags in packet reception. If we find in the
+ // future that it does, the sort of threshold could be set for the maximum
+ // bytes received on the fallback socket in a single round. Further
+ // optimizations would include an asynchronous read from the fallback socket
+ // when the DHCP server is idle.
+ int datalen;
+ do {
+ datalen = recv(socket_info.fallbackfd_, iface.getReadBuffer(),
+ iface.getReadBufferSize(), 0);
+ } while (datalen > 0);
+
+ datalen = read(socket_info.sockfd_, iface.getReadBuffer(),
+ iface.getReadBufferSize());
+ // If negative value is returned by read(), it indicates that an
+ // error occurred. If returned value is 0, no data was read from the
+ // socket. In both cases something has gone wrong, because we expect
+ // that a chunk of data is there. We signal the lack of data by
+ // returning an empty packet.
+ if (datalen <= 0) {
+ return Pkt4Ptr();
+ }
+ datalen = BPF_WORDALIGN(datalen);
+
+ // Holds BPF header.
+ struct bpf_hdr bpfh;
+
+ /// @todo BPF may occasionally append more than one packet in a
+ /// single read. Our current libdhcp++ API is oriented towards receiving
+ /// one packet at the time so we just pick first usable packet here
+ /// and drop other packets. In the future the additional packets should
+ /// be queued and processed. For now, we just iterate over the packets
+ /// in the buffer and pick the first usable one.
+ int offset = 0;
+ while (offset < datalen) {
+ // Check if the BPF header fits in the reminder of the buffer.
+ // If it doesn't something is really wrong.
+ if (datalen - offset < sizeof(bpf_hdr)) {
+ isc_throw(SocketReadError, "packet received over the BPF device on"
+ " interface " << iface.getName() << " has a truncated "
+ " BPF header");
+ }
+
+ // Copy the BPF header.
+ memcpy(static_cast<void*>(&bpfh),
+ static_cast<void*>(iface.getReadBuffer()),
+ sizeof(bpfh));
+
+ // Check if the captured data fit into the reminder of the buffer.
+ // Again, something is really wrong here if it doesn't fit.
+ if (offset + bpfh.bh_hdrlen + bpfh.bh_caplen > datalen) {
+ isc_throw(SocketReadError, "packet received from the BPF device"
+ << " attached to interface " << iface.getName()
+ << " is truncated");
+ }
+
+ // Check if the whole packet has been captured.
+ if (bpfh.bh_caplen != bpfh.bh_datalen) {
+ // Not whole packet captured, proceed to next received packet.
+ offset = BPF_WORDALIGN(offset + bpfh.bh_hdrlen + bpfh.bh_caplen);
+ continue;
+ }
+
+ // All checks passed, let's use the packet at the offset found.
+ // Typically it will be at offset 0.
+ break;
+ };
+
+ // No parsable packet found, so return.
+ if (offset >= datalen) {
+ return (Pkt4Ptr());
+ }
+
+ // Skip the BPF header and create the buffer holding a frame.
+ InputBuffer buf(iface.getReadBuffer() + offset + bpfh.bh_hdrlen,
+ datalen - bpfh.bh_hdrlen - offset);
+
+
+ // @todo: This is awkward way to solve the chicken and egg problem
+ // whereby we don't know the offset where DHCP data start in the
+ // received buffer when we create the packet object. In general case,
+ // the IP header has variable length. The information about its length
+ // is stored in one of its fields. Therefore, we have to decode the
+ // packet to get the offset of the DHCP data. The dummy object is
+ // created so as we can pass it to the functions which decode IP stack
+ // and find actual offset of the DHCP data.
+ // Once we find the offset we can create another Pkt4 object from
+ // the reminder of the input buffer and set the IP addresses and
+ // ports from the dummy packet. We should consider doing it
+ // in some more elegant way.
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+ // On local loopback interface the ethernet header is not present.
+ // Instead, there is a 4-byte long pseudo header containing the
+ // address family in the host byte order. Note that this header
+ // is present in the received messages on OSX, but should not be
+ // included in the sent messages on OSX.
+ if (iface.flag_loopback_) {
+ if (buf.getLength() < BPF_LOCAL_LOOPBACK_HEADER_LEN) {
+ isc_throw(SocketReadError, "packet received on local loopback"
+ " interface " << iface.getName() << " doesn't contain"
+ " the pseudo header with the address family type");
+ }
+ // Advance to the position of the IP header. We don't check the
+ // contents of the pseudo header because the BPF filter should have
+ // filtered out the packets with address family other than AF_INET.
+ buf.setPosition(BPF_LOCAL_LOOPBACK_HEADER_LEN);
+
+ // Since we don't decode the real link-layer header we need to
+ // supply the hardware address ourselves.
+ dummy_pkt->setLocalHWAddr(HWAddrPtr(new HWAddr()));
+ dummy_pkt->setRemoteHWAddr(HWAddrPtr(new HWAddr()));
+
+ } else {
+ // If we are on the interface other than local loopback, assume
+ // the ethernet header. For now we don't support any other data
+ // link layer.
+ decodeEthernetHeader(buf, dummy_pkt);
+ }
+
+ // Decode IP/UDP headers.
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Read the DHCP data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+
+ // Decode DHCP data into the Pkt4 object.
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+
+ // Set the appropriate packet members using data collected from
+ // the decoded headers.
+ pkt->setIndex(iface.getIndex());
+ pkt->setIface(iface.getName());
+ pkt->setLocalAddr(dummy_pkt->getLocalAddr());
+ pkt->setRemoteAddr(dummy_pkt->getRemoteAddr());
+ pkt->setLocalPort(dummy_pkt->getLocalPort());
+ pkt->setRemotePort(dummy_pkt->getRemotePort());
+ pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
+ pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
+
+ return (pkt);
+}
+
+int
+PktFilterBPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
+
+ OutputBuffer buf(14);
+
+ // Some interfaces may have no HW address - e.g. loopback interface.
+ // For these interfaces the HW address length is 0. If this is the case,
+ // then we will rely on the functions which construct the IP/UDP headers
+ // to provide a default HW address. Otherwise, create the HW address
+ // object using the HW address of the interface.
+ if (iface.getMacLen() > 0) {
+ HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
+ iface.getHWType()));
+ pkt->setLocalHWAddr(hwaddr);
+ }
+
+ // Loopback interface requires special treatment. It doesn't
+ // use the ethernet header but rather a 4-byte long pseudo header
+ // holding an address family type (see bpf.c in OS sources).
+ // On OSX, it even lacks pseudo header.
+#if !defined (OS_OSX)
+ if (iface.flag_loopback_) {
+ writeAFPseudoHeader(AF_INET, buf);
+ }
+#endif
+
+ // If this is not a loopback interface create Ethernet frame header.
+ if (!iface.flag_loopback_) {
+ // Ethernet frame header.
+ // Note that we don't validate whether HW addresses in 'pkt'
+ // are valid because they are validated by the function called.
+ writeEthernetHeader(pkt, buf);
+ }
+
+ // IP and UDP header
+ writeIpUdpHeader(pkt, buf);
+
+ // DHCPv4 message
+ buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength());
+
+ int result = write(sockfd, buf.getData(), buf.getLength());
+ if (result < 0) {
+ isc_throw(SocketWriteError, "failed to send DHCPv4 packet: "
+ << strerror(errno));
+ }
+
+ return (0);
+}
+
+void
+PktFilterBPF::writeAFPseudoHeader(const uint32_t address_family,
+ util::OutputBuffer& out_buf) {
+ // Copy address family to the temporary buffer and preserve the
+ // bytes order.
+ uint8_t af_buf[4];
+ memcpy(static_cast<void*>(af_buf),
+ static_cast<const void*>(&address_family),
+ sizeof(af_buf));
+ // Write the data into the buffer.
+ out_buf.writeData(af_buf, sizeof(af_buf));
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter_bpf.h b/src/lib/dhcp/pkt_filter_bpf.h
new file mode 100644
index 0000000..9413848
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_bpf.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2014-2017 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 PKT_FILTER_BPF_H
+#define PKT_FILTER_BPF_H
+
+#include <dhcp/pkt_filter.h>
+
+#include <util/buffer.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet handling class using Berkeley Packet Filtering (BPF)
+///
+/// The BPF is supported on the BSD-like operating systems. It allows for access
+/// to low level layers of the inbound and outbound packets. This is specifically
+/// useful when the DHCP server is allocating new address to the client.
+///
+/// The response being sent to the client must include the HW address in the
+/// datalink layer. When the regular datagram socket is used the kernel will
+/// determine the HW address of the destination using ARP. In the case when
+/// the DHCP server is allocating the new address for the client the ARP can't
+/// be used because it requires the destination to have the IP address.
+///
+/// The DHCP server utilizes HW address sent by the client in the DHCP message
+/// and stores it in the datalink layer of the outbound packet. The BPF provides
+/// the means for crafting the whole packet (including datalink and network
+/// layers) and injecting the hardware address of the client.
+///
+/// The DHCP server receiving the messages sent from the directly connected
+/// clients to the broadcast address must be able to determine the interface
+/// on which the message arrives. The Linux kernel provides the SO_BINDTODEVICE
+/// socket option which allows for binding the socket to the particular
+/// interface. This option is not implemented on the BSD-like operating
+/// systems. This implies that there may be only one datagram socket listening
+/// to broadcast messages and this socket would receive the traffic on all
+/// interfaces. This effectively precludes the server from identifying the
+/// interface on which the packet arrived. The BPF resolves this problem.
+/// The BPF device (socket) can be attached to the selected interface using
+/// the ioctl function.
+///
+/// In nutshell, the BPF device is created by opening the file /dev/bpf%d
+/// where %d is a number. The BPF device is configured by issuing ioctl
+/// commands listed here: http://www.freebsd.org/cgi/man.cgi?bpf(4).
+/// The specific configuration used by Kea DHCP server is described in
+/// documentation of @c PktFilterBPF::openSocket.
+///
+/// Use of BPF requires Kea to encode and decode the datalink and network
+/// layer headers. Currently Kea supports encoding and decoding ethernet
+/// frames on physical interfaces and pseudo headers received on local
+/// loopback interface.
+class PktFilterBPF : public PktFilter {
+public:
+
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// This class supports direct responses to the host without address.
+ ///
+ /// @return true always.
+ virtual bool isDirectResponseSupported() const {
+ return (true);
+ }
+
+ /// @brief Open primary and fallback socket.
+ ///
+ /// This method opens the BPF device and applies the following
+ /// configuration to it:
+ /// - attach the device to the specified interface
+ /// - set filter program to receive DHCP messages encapsulated in UDP
+ /// packets
+ /// - set immediate mode which causes the read function to return
+ /// immediately and do not wait for the whole read buffer to be filled
+ /// by the kernel (to avoid hangs)
+ ///
+ /// It also obtains the following configuration from the kernel:
+ /// - major and minor version of the BPF (and checks if it is valid)
+ /// - length of the buffer to be used to receive the data from the socket
+ ///
+ /// @param iface Interface descriptor. Note that the function (re)allocates
+ /// the socket read buffer according to the buffer size returned by the
+ /// kernel.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number.
+ /// @param receive_bcast Configure socket to receive broadcast messages
+ /// @param send_bcast Configure socket to send broadcast messages.
+ ///
+ /// @return A structure describing a primary and fallback socket.
+ virtual SocketInfo openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @return Received packet
+ virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param iface interface to be used to send packet
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending a packet. It is 0 if successful.
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt);
+
+private:
+
+ /// @brief Writes pseudo header containing an address family into a buffer.
+ ///
+ /// BPF utilizes the pseudo headers to pass the ancillary data between the
+ /// kernel and the application. For example, when the packet is to be sent
+ /// over the local loopback interface the pseudo header must be added before
+ /// the network layer header to indicate the address family. Other link
+ /// layer header (e.g. ethernet) is not used for local loopback interface.
+ ///
+ /// The header written by this method consists of 4 bytes and contains the
+ /// address family value in host byte order. See sys/socket.h for the
+ /// address family values. Typically it will be AF_INET.
+ ///
+ /// This function doesn't throw.
+ ///
+ /// @param address_family Address family (e.g. AF_INET).
+ /// @param [out] out_buf buffer where a header is written.
+ void writeAFPseudoHeader(const uint32_t address_family,
+ util::OutputBuffer& out_buf);
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_BPF_H
diff --git a/src/lib/dhcp/pkt_filter_inet.cc b/src/lib/dhcp/pkt_filter_inet.cc
new file mode 100644
index 0000000..5fedd30
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.cc
@@ -0,0 +1,285 @@
+// Copyright (C) 2013-2020 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <errno.h>
+#include <cstring>
+#include <fcntl.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+const size_t
+PktFilterInet::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo));
+
+SocketInfo
+PktFilterInet::openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) {
+ struct sockaddr_in addr4;
+ memset(&addr4, 0, sizeof(sockaddr));
+ addr4.sin_family = AF_INET;
+ addr4.sin_port = htons(port);
+
+ // If we are to receive broadcast messages we have to bind
+ // to "ANY" address.
+ if (receive_bcast && iface.flag_broadcast_) {
+ addr4.sin_addr.s_addr = INADDR_ANY;
+ } else {
+ addr4.sin_addr.s_addr = htonl(addr.toUint32());
+ }
+
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ isc_throw(SocketConfigError, "Failed to create UDP4 socket.");
+ }
+
+ // Set the close-on-exec flag.
+ if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
+ << " on socket " << sock);
+ }
+
+#ifdef SO_BINDTODEVICE
+ if (receive_bcast && iface.flag_broadcast_) {
+ // Bind to device so as we receive traffic on a specific interface.
+ if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(),
+ iface.getName().length() + 1) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set SO_BINDTODEVICE option"
+ << " on socket " << sock);
+ }
+ }
+#endif
+
+ if (send_bcast && iface.flag_broadcast_) {
+ // Enable sending to broadcast address.
+ int flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set SO_BROADCAST option"
+ << " on socket " << sock);
+ }
+ }
+
+ if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to bind socket " << sock
+ << " to " << addr
+ << "/port=" << port);
+ }
+
+ // On Linux systems IP_PKTINFO socket option is supported. This
+ // option is used to retrieve destination address of the packet.
+#if defined (IP_PKTINFO) && defined (OS_LINUX)
+ int flag = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag)) != 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "setsockopt: IP_PKTINFO: failed.");
+ }
+
+ // On BSD systems IP_RECVDSTADDR is used instead of IP_PKTINFO.
+#elif defined (IP_RECVDSTADDR) && defined (OS_BSD)
+ int flag = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, &flag, sizeof(flag)) != 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "setsockopt: IP_RECVDSTADDR: failed.");
+ }
+#endif
+
+ SocketInfo sock_desc(addr, port, sock);
+ return (sock_desc);
+
+}
+
+Pkt4Ptr
+PktFilterInet::receive(Iface& iface, const SocketInfo& socket_info) {
+ struct sockaddr_in from_addr;
+ uint8_t buf[IfaceMgr::RCVBUFSIZE];
+ uint8_t control_buf[CONTROL_BUF_LEN];
+
+ memset(&control_buf[0], 0, CONTROL_BUF_LEN);
+ memset(&from_addr, 0, sizeof(from_addr));
+
+ // Initialize our message header structure.
+ struct msghdr m;
+ memset(&m, 0, sizeof(m));
+
+ // Point so we can get the from address.
+ m.msg_name = &from_addr;
+ m.msg_namelen = sizeof(from_addr);
+
+ struct iovec v;
+ v.iov_base = static_cast<void*>(buf);
+ v.iov_len = IfaceMgr::RCVBUFSIZE;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ // Getting the interface is a bit more involved.
+ //
+ // We set up some space for a "control message". We have
+ // previously asked the kernel to give us packet
+ // information (when we initialized the interface), so we
+ // should get the destination address from that.
+ m.msg_control = &control_buf[0];
+ m.msg_controllen = CONTROL_BUF_LEN;
+
+ int result = recvmsg(socket_info.sockfd_, &m, 0);
+ if (result < 0) {
+ isc_throw(SocketReadError, "failed to receive UDP4 data");
+ }
+
+ // We have all data let's create Pkt4 object.
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
+
+ pkt->updateTimestamp();
+
+ unsigned int ifindex = iface.getIndex();
+
+ IOAddress from(htonl(from_addr.sin_addr.s_addr));
+ uint16_t from_port = htons(from_addr.sin_port);
+
+ // Set receiving interface based on information, which socket was used to
+ // receive data. OS-specific info (see os_receive4()) may be more reliable,
+ // so this value may be overwritten.
+ pkt->setIndex(ifindex);
+ pkt->setIface(iface.getName());
+ pkt->setRemoteAddr(from);
+ pkt->setRemotePort(from_port);
+ pkt->setLocalPort(socket_info.port_);
+
+// Linux systems support IP_PKTINFO option which is used to retrieve the
+// destination address of the received packet. On BSD systems IP_RECVDSTADDR
+// is used instead.
+#if defined (IP_PKTINFO) && defined (OS_LINUX)
+ struct in_pktinfo* pktinfo;
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IP) &&
+ (cmsg->cmsg_type == IP_PKTINFO)) {
+ pktinfo = reinterpret_cast<struct in_pktinfo*>(CMSG_DATA(cmsg));
+
+ pkt->setIndex(pktinfo->ipi_ifindex);
+ pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr)));
+ break;
+
+ // This field is useful, when we are bound to unicast
+ // address e.g. 192.0.2.1 and the packet was sent to
+ // broadcast. This will return broadcast address, not
+ // the address we are bound to.
+
+ // XXX: Perhaps we should uncomment this:
+ // to_addr = pktinfo->ipi_spec_dst;
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+
+#elif defined (IP_RECVDSTADDR) && defined (OS_BSD)
+ struct in_addr* to_addr;
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IP) &&
+ (cmsg->cmsg_type == IP_RECVDSTADDR)) {
+ to_addr = reinterpret_cast<struct in_addr*>(CMSG_DATA(cmsg));
+ pkt->setLocalAddr(IOAddress(htonl(to_addr->s_addr)));
+ break;
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+
+#endif
+
+ return (pkt);
+}
+
+int
+PktFilterInet::send(const Iface&, uint16_t sockfd, const Pkt4Ptr& pkt) {
+ uint8_t control_buf[CONTROL_BUF_LEN];
+ memset(&control_buf[0], 0, CONTROL_BUF_LEN);
+
+ // Set the target address we're sending to.
+ sockaddr_in to;
+ memset(&to, 0, sizeof(to));
+ to.sin_family = AF_INET;
+ to.sin_port = htons(pkt->getRemotePort());
+ to.sin_addr.s_addr = htonl(pkt->getRemoteAddr().toUint32());
+
+ struct msghdr m;
+ // Initialize our message header structure.
+ memset(&m, 0, sizeof(m));
+ m.msg_name = &to;
+ m.msg_namelen = sizeof(to);
+
+ // Set the data buffer we're sending. (Using this wacky
+ // "scatter-gather" stuff... we only have a single chunk
+ // of data to send, so we declare a single vector entry.)
+ struct iovec v;
+ memset(&v, 0, sizeof(v));
+ // iov_base field is of void * type. We use it for packet
+ // transmission, so this buffer will not be modified.
+ v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
+ v.iov_len = pkt->getBuffer().getLength();
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+// In the future the OS-specific code may be abstracted to a different
+// file but for now we keep it here because there is no code yet, which
+// is specific to non-Linux systems.
+#if defined (IP_PKTINFO) && defined (OS_LINUX)
+ // Setting the interface is a bit more involved.
+ //
+ // We have to create a "control message", and set that to
+ // define the IPv4 packet information. We set the source address
+ // to handle correctly interfaces with multiple addresses.
+ m.msg_control = &control_buf[0];
+ m.msg_controllen = CONTROL_BUF_LEN;
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
+ memset(pktinfo, 0, sizeof(struct in_pktinfo));
+
+ // In some cases the index of the outbound interface is not set. This
+ // is a matter of configuration. When the server is configured to
+ // determine the outbound interface based on routing information,
+ // the index is left unset (negative).
+ if (pkt->indexSet()) {
+ pktinfo->ipi_ifindex = pkt->getIndex();
+ }
+
+ // When the DHCP server is using routing to determine the outbound
+ // interface, the local address is also left unset.
+ if (!pkt->getLocalAddr().isV4Zero()) {
+ pktinfo->ipi_spec_dst.s_addr = htonl(pkt->getLocalAddr().toUint32());
+ }
+
+ m.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
+#endif
+
+ pkt->updateTimestamp();
+
+ int result = sendmsg(sockfd, &m, 0);
+ if (result < 0) {
+ isc_throw(SocketWriteError, "pkt4 send failed: sendmsg() returned "
+ " with an error: " << strerror(errno));
+ }
+
+ return (0);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter_inet.h b/src/lib/dhcp/pkt_filter_inet.h
new file mode 100644
index 0000000..12f764e
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.h
@@ -0,0 +1,89 @@
+// Copyright (C) 2013-2020 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 PKT_FILTER_INET_H
+#define PKT_FILTER_INET_H
+
+#include <dhcp/pkt_filter.h>
+#include <boost/scoped_array.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet handling class using AF_INET socket family
+///
+/// This class provides methods to send and receive packet via socket using
+/// AF_INET family and SOCK_DGRAM type.
+class PktFilterInet : public PktFilter {
+public:
+
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// This Packet Filter sends packets through AF_INET datagram sockets, so
+ /// it can't inject the HW address of the destination host into the packet.
+ /// Therefore this class does not support direct responses.
+ ///
+ /// @return false always.
+ virtual bool isDirectResponseSupported() const {
+ return (false);
+ }
+
+ /// @brief Open primary and fallback socket.
+ ///
+ /// @param iface Interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number.
+ /// @param receive_bcast Configure socket to receive broadcast messages
+ /// @param send_bcast Configure socket to send broadcast messages.
+ ///
+ /// @return A structure describing a primary and fallback socket.
+ /// @throw isc::dhcp::SocketConfigError if error occurs when opening,
+ /// binding or configuring the socket.
+ virtual SocketInfo openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @return Received packet
+ /// @throw isc::dhcp::SocketReadError if an error occurs during reception
+ /// of the packet.
+ /// @throw An exception thrown by the isc::dhcp::Pkt4 object if DHCPv4
+ /// message parsing fails.
+ virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// This function will use local address specified in the @c pkt as a source
+ /// address for the packet and the interface index to select the index
+ /// through which the packet will be sent. However, if these values
+ /// are not specified for the packet (zero IP address and negative
+ /// interface index), this function will rely on the routing information
+ /// to determine the right outbound interface and source address.
+ ///
+ /// @param iface interface to be used to send packet
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending a packet. It is 0 if successful.
+ /// @throw isc::dhcp::SocketWriteError if an error occurs during sending
+ /// a DHCP message through the socket.
+ virtual int send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt);
+
+private:
+ /// Length of the socket control buffer.
+ static const size_t CONTROL_BUF_LEN;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_INET_H
diff --git a/src/lib/dhcp/pkt_filter_inet6.cc b/src/lib/dhcp/pkt_filter_inet6.cc
new file mode 100644
index 0000000..b7c05af
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet6.cc
@@ -0,0 +1,339 @@
+// Copyright (C) 2013-2023 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter_inet6.h>
+#include <exceptions/isc_assert.h>
+#include <util/io/pktinfo_utilities.h>
+
+#include <fcntl.h>
+#include <netinet/in.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+const size_t
+PktFilterInet6::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo));
+
+SocketInfo
+PktFilterInet6::openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool join_multicast) {
+ struct sockaddr_in6 addr6;
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_port = htons(port);
+ // sin6_scope_id must be set to interface index for link-local addresses.
+ // For unspecified addresses we set the scope id to the interface index
+ // to handle the case when the IfaceMgr is opening a socket which will
+ // join the multicast group. Such socket is bound to in6addr_any.
+ if (addr.isV6Multicast() ||
+ (addr.isV6LinkLocal() && (addr != IOAddress("::1"))) ||
+ (addr == IOAddress("::"))) {
+ addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
+ }
+
+ // Copy the address if it has been specified.
+ if (addr != IOAddress("::")) {
+ memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
+ }
+#ifdef HAVE_SA_LEN
+ addr6.sin6_len = sizeof(addr6);
+#endif
+
+ // @todo use sockcreator once it becomes available
+
+ // make a socket
+ int sock = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
+ }
+
+ // Set the close-on-exec flag.
+ if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
+ << " on IPv6 socket.");
+ }
+
+ // Set SO_REUSEADDR option.
+ int flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&flag, sizeof(flag)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Can't set SO_REUSEADDR option on IPv6"
+ " socket.");
+ }
+
+#ifdef SO_REUSEPORT
+ // Set SO_REUSEPORT has to be set to open multiple sockets and bind to
+ // in6addr_any (binding to port). Binding to port is required on some
+ // operating systems, e.g. NetBSD and OpenBSD so as the socket can
+ // join the socket to multicast group.
+ // RedHat 6.4 defines SO_REUSEPORT but the kernel does not support it
+ // and returns ENOPROTOOPT so ignore this error. Other versions may be
+ // affected, too.
+ if ((setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
+ (char *)&flag, sizeof(flag)) < 0) &&
+ (errno != ENOPROTOOPT)) {
+ close(sock);
+ isc_throw(SocketConfigError, "Can't set SO_REUSEPORT option on IPv6"
+ " socket.");
+ }
+#endif
+
+#ifdef IPV6_V6ONLY
+ // Set IPV6_V6ONLY to get only IPv6 packets.
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&flag, sizeof(flag)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Can't set IPV6_V6ONLY option on "
+ "IPv6 socket.");
+ }
+#endif
+
+ if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
+ // Get the error message immediately after the bind because the
+ // invocation to close() below would override the errno.
+ char* errmsg = strerror(errno);
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to "
+ << addr.toText() << "/port=" << port
+ << ": " << errmsg);
+ }
+
+#ifdef IPV6_RECVPKTINFO
+ // RFC3542 - a new way
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+ &flag, sizeof(flag)) != 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "setsockopt: IPV6_RECVPKTINFO failed.");
+ }
+#else
+ // RFC2292 - an old way
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
+ &flag, sizeof(flag)) != 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "setsockopt: IPV6_PKTINFO: failed.");
+ }
+#endif
+
+ // Join All_DHCP_Relay_Agents_and_Servers multicast group if
+ // requested.
+ if (join_multicast &&
+ !joinMulticast(sock, iface.getName(),
+ std::string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS))) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to join "
+ << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
+ << " multicast group.");
+ }
+
+ return (SocketInfo(addr, port, sock));
+}
+
+Pkt6Ptr
+PktFilterInet6::receive(const SocketInfo& socket_info) {
+ // Now we have a socket, let's get some data from it!
+ uint8_t buf[IfaceMgr::RCVBUFSIZE];
+ uint8_t control_buf[CONTROL_BUF_LEN];
+ memset(&control_buf[0], 0, CONTROL_BUF_LEN);
+ struct sockaddr_in6 from;
+ memset(&from, 0, sizeof(from));
+
+ // Initialize our message header structure.
+ struct msghdr m;
+ memset(&m, 0, sizeof(m));
+
+ // Point so we can get the from address.
+ m.msg_name = &from;
+ m.msg_namelen = sizeof(from);
+
+ // Set the data buffer we're receiving. (Using this wacky
+ // "scatter-gather" stuff... but we that doesn't really make
+ // sense for us, so we use a single vector entry.)
+ struct iovec v;
+ memset(&v, 0, sizeof(v));
+ v.iov_base = static_cast<void*>(buf);
+ v.iov_len = IfaceMgr::RCVBUFSIZE;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ // Getting the interface is a bit more involved.
+ //
+ // We set up some space for a "control message". We have
+ // previously asked the kernel to give us packet
+ // information (when we initialized the interface), so we
+ // should get the destination address from that.
+ m.msg_control = &control_buf[0];
+ m.msg_controllen = CONTROL_BUF_LEN;
+
+ int result = recvmsg(socket_info.sockfd_, &m, 0);
+
+ struct in6_addr to_addr;
+ memset(&to_addr, 0, sizeof(to_addr));
+
+ unsigned int ifindex = UNSET_IFINDEX;
+ if (result >= 0) {
+ struct in6_pktinfo* pktinfo = NULL;
+
+
+ // If we did read successfully, then we need to loop
+ // through the control messages we received and
+ // find the one with our destination address.
+ //
+ // We also keep a flag to see if we found it. If we
+ // didn't, then we consider this to be an error.
+ bool found_pktinfo = false;
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
+ (cmsg->cmsg_type == IPV6_PKTINFO)) {
+ pktinfo = util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
+ to_addr = pktinfo->ipi6_addr;
+ ifindex = pktinfo->ipi6_ifindex;
+ found_pktinfo = true;
+ break;
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+ if (!found_pktinfo) {
+ isc_throw(SocketReadError, "unable to find pktinfo");
+ }
+ } else {
+ isc_throw(SocketReadError, "failed to receive data");
+ }
+
+ // Filter out packets sent to global unicast address (not link local and
+ // not multicast) if the socket is set to listen multicast traffic and
+ // is bound to in6addr_any. The traffic sent to global unicast address is
+ // received via dedicated socket.
+ IOAddress local_addr = IOAddress::fromBytes(AF_INET6,
+ reinterpret_cast<const uint8_t*>(&to_addr));
+ if ((socket_info.addr_ == IOAddress("::")) &&
+ !(local_addr.isV6Multicast() || local_addr.isV6LinkLocal())) {
+ return (Pkt6Ptr());
+ }
+
+ // Let's create a packet.
+ Pkt6Ptr pkt;
+ try {
+ pkt = Pkt6Ptr(new Pkt6(buf, result));
+ } catch (const std::exception& ex) {
+ isc_throw(SocketReadError, "failed to create new packet");
+ }
+
+ pkt->updateTimestamp();
+
+ pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
+ reinterpret_cast<const uint8_t*>(&to_addr)));
+ pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
+ reinterpret_cast<const uint8_t*>(&from.sin6_addr)));
+ pkt->setRemotePort(ntohs(from.sin6_port));
+ pkt->setIndex(ifindex);
+
+ IfacePtr received = IfaceMgr::instance().getIface(pkt->getIndex());
+ if (received) {
+ pkt->setIface(received->getName());
+ } else {
+ isc_throw(SocketReadError, "received packet over unknown interface"
+ << "(ifindex=" << pkt->getIndex() << ")");
+ }
+
+ return (pkt);
+
+}
+
+int
+PktFilterInet6::send(const Iface&, uint16_t sockfd, const Pkt6Ptr& pkt) {
+ uint8_t control_buf[CONTROL_BUF_LEN];
+ memset(&control_buf[0], 0, CONTROL_BUF_LEN);
+
+ // Set the target address we're sending to.
+ sockaddr_in6 to;
+ memset(&to, 0, sizeof(to));
+ to.sin6_family = AF_INET6;
+ to.sin6_port = htons(pkt->getRemotePort());
+ memcpy(&to.sin6_addr,
+ &pkt->getRemoteAddr().toBytes()[0],
+ 16);
+ to.sin6_scope_id = pkt->getIndex();
+
+ // Initialize our message header structure.
+ struct msghdr m;
+ memset(&m, 0, sizeof(m));
+ m.msg_name = &to;
+ m.msg_namelen = sizeof(to);
+
+ // Set the data buffer we're sending. (Using this wacky
+ // "scatter-gather" stuff... we only have a single chunk
+ // of data to send, so we declare a single vector entry.)
+
+ // As v structure is a C-style is used for both sending and
+ // receiving data, it is shared between sending and receiving
+ // (sendmsg and recvmsg). It is also defined in system headers,
+ // so we have no control over its definition. To set iov_base
+ // (defined as void*) we must use const cast from void *.
+ // Otherwise C++ compiler would complain that we are trying
+ // to assign const void* to void*.
+ struct iovec v;
+ memset(&v, 0, sizeof(v));
+ v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
+ v.iov_len = pkt->getBuffer().getLength();
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ // Setting the interface is a bit more involved.
+ //
+ // We have to create a "control message", and set that to
+ // define the IPv6 packet information. We could set the
+ // source address if we wanted, but we can safely let the
+ // kernel decide what that should be.
+ m.msg_control = &control_buf[0];
+ m.msg_controllen = CONTROL_BUF_LEN;
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m);
+
+ // FIXME: Code below assumes that cmsg is not NULL, but
+ // CMSG_FIRSTHDR() is coded to return NULL as a possibility. The
+ // following assertion should never fail, but if it did and you came
+ // here, fix the code. :)
+ isc_throw_assert(cmsg != NULL);
+
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+ struct in6_pktinfo *pktinfo =
+ util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
+ memset(pktinfo, 0, sizeof(struct in6_pktinfo));
+ pktinfo->ipi6_ifindex = pkt->getIndex();
+ // According to RFC3542, section 20.2, the msg_controllen field
+ // may be set using CMSG_SPACE (which includes padding) or
+ // using CMSG_LEN. Both forms appear to work fine on Linux, FreeBSD,
+ // NetBSD, but OpenBSD appears to have a bug, discussed here:
+ // https://marc.info/?l=openbsd-bugs&m=123485913417684&w=2
+ // which causes sendmsg to return EINVAL if the CMSG_LEN is
+ // used to set the msg_controllen value.
+ m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+
+ pkt->updateTimestamp();
+
+ int result = sendmsg(sockfd, &m, 0);
+ if (result < 0) {
+ isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
+ " with an error: " << strerror(errno));
+ }
+
+ return (0);
+}
+
+}
+}
diff --git a/src/lib/dhcp/pkt_filter_inet6.h b/src/lib/dhcp/pkt_filter_inet6.h
new file mode 100644
index 0000000..8d40a44
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet6.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2013-2020 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 PKT_FILTER_INET6_H
+#define PKT_FILTER_INET6_H
+
+#include <dhcp/pkt_filter6.h>
+#include <boost/scoped_array.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A DHCPv6 packet handling class using datagram sockets.
+///
+/// This class opens a datagram IPv6/UDPv6 socket. It also implements functions
+/// to send and receive DHCPv6 messages through this socket. It is a default
+/// class to be used by @c IfaceMgr to access IPv6 sockets.
+class PktFilterInet6 : public PktFilter6 {
+public:
+
+ /// @brief Opens a socket.
+ ///
+ /// This function opens an IPv6 socket on an interface and binds it to a
+ /// specified UDP port and IP address. The @c addr value may be set to
+ /// "::" in which case the address is unspecified and the socket is
+ /// bound to in6addr_any.
+ ///
+ /// @param iface Interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number.
+ /// @param join_multicast A boolean parameter which indicates whether
+ /// socket should join All_DHCP_Relay_Agents_and_servers multicast
+ /// group.
+ ///
+ /// @return A structure describing a primary and fallback socket.
+ /// @throw isc::dhcp::SocketConfigError if error occurred when opening
+ /// or configuring a socket.
+ virtual SocketInfo openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool join_multicast);
+
+ /// @brief Receives DHCPv6 message on the interface.
+ ///
+ /// This function receives a single DHCPv6 message through a socket
+ /// open on a specified interface. This function will block if there is
+ /// no message waiting on the specified socket. Therefore the @c IfaceMgr
+ /// must first check that there is any message on the socket (using
+ /// select function) prior to calling this function.
+ ///
+ /// If the message is received through the socket bound to "any"
+ /// (in6addr_any) address this function will drop this message if it has
+ /// been sent to an address other than multicast or link-local.
+ ///
+ /// @param socket_info A structure holding socket information.
+ ///
+ /// @return A pointer to received message.
+ /// @throw isc::dhcp::SocketReadError if error occurred during packet
+ /// reception.
+ virtual Pkt6Ptr receive(const SocketInfo& socket_info);
+
+ /// @brief Sends DHCPv6 message through a specified interface and socket.
+ ///
+ /// The function sends a DHCPv6 message through a specified interface and
+ /// socket. In general, there may be multiple sockets open on a single
+ /// interface as a single interface may have multiple IPv6 addresses.
+ ///
+ /// @param iface Interface to be used to send packet.
+ /// @param sockfd A socket descriptor
+ /// @param pkt A packet to be sent.
+ ///
+ /// @return A result of sending the message. It is 0 if successful.
+ /// @throw isc::dhcp::SocketWriteError if error occurred when sending a
+ /// packet.
+ virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt);
+
+private:
+ /// Length of the socket control buffer.
+ static const size_t CONTROL_BUF_LEN;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_INET6_H
diff --git a/src/lib/dhcp/pkt_filter_lpf.cc b/src/lib/dhcp/pkt_filter_lpf.cc
new file mode 100644
index 0000000..791e863
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.cc
@@ -0,0 +1,340 @@
+// Copyright (C) 2013-2021 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 <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <exceptions/exceptions.h>
+#include <fcntl.h>
+#include <net/ethernet.h>
+#include <linux/filter.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+
+namespace {
+
+using namespace isc::dhcp;
+
+/// The following structure defines a Berkeley Packet Filter program to perform
+/// packet filtering. The program operates on Ethernet packets. To help with
+/// interpretation of the program, for the types of Ethernet packets we are
+/// interested in, the header layout is:
+///
+/// 6 bytes Destination Ethernet Address
+/// 6 bytes Source Ethernet Address
+/// 2 bytes Ethernet packet type
+///
+/// 20 bytes Fixed part of IP header
+/// variable Variable part of IP header
+///
+/// 2 bytes UDP Source port
+/// 2 bytes UDP destination port
+/// 4 bytes Rest of UDP header
+///
+/// Each instruction is preceded with the comments giving the instruction
+/// number within a BPF program, in the following format: #123.
+///
+/// @todo We may want to extend the filter to receive packets sent
+/// to the particular IP address assigned to the interface or
+/// broadcast address.
+struct sock_filter dhcp_sock_filter [] = {
+ // Make sure this is an IP packet: check the half-word (two bytes)
+ // at offset 12 in the packet (the Ethernet packet type). If it
+ // is, advance to the next instruction. If not, advance 11
+ // instructions (which takes execution to the last instruction in
+ // the sequence: "drop it").
+ // #0
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET),
+ // #1
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 11),
+
+ // Make sure it's a UDP packet. The IP protocol is at offset
+ // 9 in the IP header so, adding the Ethernet packet header size
+ // of 14 bytes gives an absolute byte offset in the packet of 23.
+ // #2
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+ ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
+ // #3
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9),
+
+ // Make sure this isn't a fragment by checking that the fragment
+ // offset field in the IP header is zero. This field is the
+ // least-significant 13 bits in the bytes at offsets 6 and 7 in
+ // the IP header, so the half-word at offset 20 (6 + size of
+ // Ethernet header) is loaded and an appropriate mask applied.
+ // #4
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET),
+ // #5
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0),
+
+ // Check the packet's destination address. The program will only
+ // allow the packets sent to the broadcast address or unicast
+ // to the specific address on the interface. By default, this
+ // address is set to 0 and must be set to the specific value
+ // when the raw socket is created and the program is attached
+ // to it. The caller must assign the address to the
+ // prog.bf_insns[8].k in the network byte order.
+ // #6
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS,
+ ETHERNET_HEADER_LEN + IP_DEST_ADDR_OFFSET),
+ // If this is a broadcast address, skip the next check.
+ // #7
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 1, 0),
+ // If this is not broadcast address, compare it with the unicast
+ // address specified for the interface.
+ // #8
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 4),
+
+ // Get the IP header length. This is achieved by the following
+ // (special) instruction that, given the offset of the start
+ // of the IP header (offset 14) loads the IP header length.
+ // #9
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN),
+
+ // Make sure it's to the right port. The following instruction
+ // adds the previously extracted IP header length to the given
+ // offset to locate the correct byte. The given offset of 16
+ // comprises the length of the Ethernet header (14) plus the offset
+ // of the UDP destination port (2) within the UDP header.
+ // #10
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT),
+ // The following instruction tests against the default DHCP server port,
+ // but the action port is actually set in PktFilterBPF::openSocket().
+ // N.B. The code in that method assumes that this instruction is at
+ // offset 11 in the program. If this is changed, openSocket() must be
+ // updated.
+ // #11
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
+
+ // If we passed all the tests, ask for the whole packet.
+ // #12
+ BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+ // Otherwise, drop it.
+ // #13
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+}
+
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+SocketInfo
+PktFilterLPF::openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool,
+ const bool) {
+
+ // Open fallback socket first. If it fails, it will give us an indication
+ // that there is another service (perhaps DHCP server) running.
+ // The function will throw an exception and effectively cease opening
+ // raw socket below.
+ int fallback = openFallbackSocket(addr, port);
+
+ // The fallback is open, so we are good to open primary socket.
+ int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (sock < 0) {
+ close(fallback);
+ isc_throw(SocketConfigError, "Failed to create raw LPF socket");
+ }
+
+ // Set the close-on-exec flag.
+ if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
+ close(sock);
+ close(fallback);
+ isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
+ << " on the socket " << sock);
+ }
+
+ // Create socket filter program. This program will only allow incoming UDP
+ // traffic which arrives on the specific (DHCP) port). It will also filter
+ // out all fragmented packets.
+ struct sock_fprog filter_program;
+ memset(&filter_program, 0, sizeof(filter_program));
+
+ filter_program.filter = dhcp_sock_filter;
+ filter_program.len = sizeof(dhcp_sock_filter) / sizeof(struct sock_filter);
+
+ // Configure the filter program to receive unicast packets sent to the
+ // specified address. The program will also allow packets sent to the
+ // 255.255.255.255 broadcast address.
+ dhcp_sock_filter[8].k = addr.toUint32();
+
+ // Override the default port value.
+ dhcp_sock_filter[11].k = port;
+ // Apply the filter.
+ if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
+ sizeof(filter_program)) < 0) {
+ close(sock);
+ close(fallback);
+ isc_throw(SocketConfigError, "Failed to install packet filtering program"
+ << " on the socket " << sock);
+ }
+
+ struct sockaddr_ll sa;
+ memset(&sa, 0, sizeof(sockaddr_ll));
+ sa.sll_family = AF_PACKET;
+ sa.sll_ifindex = iface.getIndex();
+
+ // For raw sockets we construct IP headers on our own, so we don't bind
+ // socket to IP address but to the interface. We will later use the
+ // Linux Packet Filtering to filter out these packets that we are
+ // interested in.
+ if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
+ sizeof(sa)) < 0) {
+ close(sock);
+ close(fallback);
+ isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
+ << "' to interface '" << iface.getName() << "'");
+ }
+
+ // Set socket to non-blocking mode.
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
+ // Get the error message immediately after the bind because the
+ // invocation to close() below would override the errno.
+ char* errmsg = strerror(errno);
+ close(sock);
+ close(fallback);
+ isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the"
+ " LPF socket '" << sock << "' to interface '"
+ << iface.getName() << "', reason: " << errmsg);
+ }
+
+ return (SocketInfo(addr, port, sock, fallback));
+
+}
+
+Pkt4Ptr
+PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) {
+ uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
+ // First let's get some data from the fallback socket. The data will be
+ // discarded but we don't want the socket buffer to bloat. We get the
+ // packets from the socket in loop but most of the time the loop will
+ // end after receiving one packet. The call to recv returns immediately
+ // when there is no data left on the socket because the socket is
+ // non-blocking.
+ // @todo In the normal conditions, both the primary socket and the fallback
+ // socket are in sync as they are set to receive packets on the same
+ // address and port. The reception of packets on the fallback socket
+ // shouldn't cause significant lags in packet reception. If we find in the
+ // future that it does, the sort of threshold could be set for the maximum
+ // bytes received on the fallback socket in a single round. Further
+ // optimizations would include an asynchronous read from the fallback socket
+ // when the DHCP server is idle.
+ int datalen;
+ do {
+ datalen = recv(socket_info.fallbackfd_, raw_buf, sizeof(raw_buf), 0);
+ } while (datalen > 0);
+
+ // Now that we finished getting data from the fallback socket, we
+ // have to get the data from the raw socket too.
+ int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
+ // If negative value is returned by read(), it indicates that an
+ // error occurred. If returned value is 0, no data was read from the
+ // socket. In both cases something has gone wrong, because we expect
+ // that a chunk of data is there. We signal the lack of data by
+ // returning an empty packet.
+ if (data_len <= 0) {
+ return Pkt4Ptr();
+ }
+
+ InputBuffer buf(raw_buf, data_len);
+
+ // @todo: This is awkward way to solve the chicken and egg problem
+ // whereby we don't know the offset where DHCP data start in the
+ // received buffer when we create the packet object. In general case,
+ // the IP header has variable length. The information about its length
+ // is stored in one of its fields. Therefore, we have to decode the
+ // packet to get the offset of the DHCP data. The dummy object is
+ // created so as we can pass it to the functions which decode IP stack
+ // and find actual offset of the DHCP data.
+ // Once we find the offset we can create another Pkt4 object from
+ // the reminder of the input buffer and set the IP addresses and
+ // ports from the dummy packet. We should consider doing it
+ // in some more elegant way.
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+ // Decode ethernet, ip and udp headers.
+ decodeEthernetHeader(buf, dummy_pkt);
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Read the DHCP data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+
+ // Decode DHCP data into the Pkt4 object.
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+
+ // Set the appropriate packet members using data collected from
+ // the decoded headers.
+ pkt->setIndex(iface.getIndex());
+ pkt->setIface(iface.getName());
+ pkt->setLocalAddr(dummy_pkt->getLocalAddr());
+ pkt->setRemoteAddr(dummy_pkt->getRemoteAddr());
+ pkt->setLocalPort(dummy_pkt->getLocalPort());
+ pkt->setRemotePort(dummy_pkt->getRemotePort());
+ pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
+ pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
+
+ return (pkt);
+}
+
+int
+PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
+
+ OutputBuffer buf(14);
+
+ // Some interfaces may have no HW address - e.g. loopback interface.
+ // For these interfaces the HW address length is 0. If this is the case,
+ // then we will rely on the functions which construct the IP/UDP headers
+ // to provide a default HW addres. Otherwise, create the HW address
+ // object using the HW address of the interface.
+ if (iface.getMacLen() > 0) {
+ HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
+ iface.getHWType()));
+ pkt->setLocalHWAddr(hwaddr);
+ }
+
+
+ // Ethernet frame header.
+ // Note that we don't validate whether HW addresses in 'pkt'
+ // are valid because they are checked by the function called.
+ writeEthernetHeader(pkt, buf);
+
+ // IP and UDP header
+ writeIpUdpHeader(pkt, buf);
+
+ // DHCPv4 message
+ buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength());
+
+ sockaddr_ll sa;
+ memset(&sa, 0x0, sizeof(sa));
+ sa.sll_family = AF_PACKET;
+ sa.sll_ifindex = iface.getIndex();
+ sa.sll_protocol = htons(ETH_P_IP);
+ sa.sll_halen = 6;
+
+ int result = sendto(sockfd, buf.getData(), buf.getLength(), 0,
+ reinterpret_cast<const struct sockaddr*>(&sa),
+ sizeof(sockaddr_ll));
+ if (result < 0) {
+ isc_throw(SocketWriteError, "failed to send DHCPv4 packet, errno="
+ << errno << " (check errno.h)");
+ }
+
+ return (0);
+
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter_lpf.h b/src/lib/dhcp/pkt_filter_lpf.h
new file mode 100644
index 0000000..04c2181
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2013-2017 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 PKT_FILTER_LPF_H
+#define PKT_FILTER_LPF_H
+
+#include <dhcp/pkt_filter.h>
+
+#include <util/buffer.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet handling class using Linux Packet Filtering
+///
+/// This class provides methods to send and receive DHCPv4 messages using raw
+/// sockets and Linux Packet Filtering. It is used by @c isc::dhcp::IfaceMgr
+/// to send DHCPv4 messages to the hosts which don't have an IPv4 address
+/// assigned yet.
+class PktFilterLPF : public PktFilter {
+public:
+
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// This class supports direct responses to the host without address.
+ ///
+ /// @return true always.
+ virtual bool isDirectResponseSupported() const {
+ return (true);
+ }
+
+ /// @brief Open primary and fallback socket.
+ ///
+ /// @param iface Interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number.
+ /// @param receive_bcast Configure socket to receive broadcast messages
+ /// @param send_bcast Configure socket to send broadcast messages.
+ ///
+ /// @return A structure describing a primary and fallback socket.
+ virtual SocketInfo openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return Received packet
+ virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param iface interface to be used to send packet
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return result of sending a packet. It is 0 if successful.
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt);
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_LPF_H
diff --git a/src/lib/dhcp/pkt_template.h b/src/lib/dhcp/pkt_template.h
new file mode 100644
index 0000000..8f58bed
--- /dev/null
+++ b/src/lib/dhcp/pkt_template.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2022 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 ISC_PKT_TEMPLATE_H
+#define ISC_PKT_TEMPLATE_H
+
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <util/dhcp_space.h>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief adapters for linking templates to qualified names
+/// @{
+namespace {
+
+template <isc::util::DhcpSpace D>
+struct AdapterPkt {};
+
+template <>
+struct AdapterPkt<isc::util::DHCPv4> {
+ using type = Pkt4;
+};
+
+template <>
+struct AdapterPkt<isc::util::DHCPv6> {
+ using type = Pkt6;
+};
+
+} // namespace
+
+template <isc::util::DhcpSpace D>
+using PktT = typename AdapterPkt<D>::type;
+
+template <isc::util::DhcpSpace D>
+using PktTPtr = boost::shared_ptr<PktT<D>>;
+/// @}
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // ISC_PKT_TEMPLATE_H
diff --git a/src/lib/dhcp/protocol_util.cc b/src/lib/dhcp/protocol_util.cc
new file mode 100644
index 0000000..9728cf4
--- /dev/null
+++ b/src/lib/dhcp/protocol_util.cc
@@ -0,0 +1,240 @@
+// Copyright (C) 2013-2022 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/protocol_util.h>
+#include <boost/static_assert.hpp>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+decodeEthernetHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+ // The size of the buffer to be parsed must not be lower
+ // then the size of the Ethernet frame header.
+ if (buf.getLength() - buf.getPosition() < ETHERNET_HEADER_LEN) {
+ isc_throw(InvalidPacketHeader, "size of ethernet header in received "
+ << "packet is invalid, expected at least "
+ << ETHERNET_HEADER_LEN << " bytes, received "
+ << buf.getLength() - buf.getPosition() << " bytes");
+ }
+ // Packet object must not be NULL. We want to output some values
+ // to this object.
+ if (!pkt) {
+ isc_throw(BadValue, "NULL packet object provided when parsing ethernet"
+ " frame header");
+ }
+
+ // The size of the single address is always lower then the size of
+ // the header that holds this address. Otherwise, it is a programming
+ // error that we want to detect in the compilation time.
+ BOOST_STATIC_ASSERT(ETHERNET_HEADER_LEN > HWAddr::ETHERNET_HWADDR_LEN);
+
+ // Remember initial position.
+ size_t start_pos = buf.getPosition();
+
+ // Read the destination HW address.
+ std::vector<uint8_t> dest_addr;
+ buf.readVector(dest_addr, HWAddr::ETHERNET_HWADDR_LEN);
+ pkt->setLocalHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, dest_addr);
+ // Read the source HW address.
+ std::vector<uint8_t> src_addr;
+ buf.readVector(src_addr, HWAddr::ETHERNET_HWADDR_LEN);
+ pkt->setRemoteHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, src_addr);
+ // Move the buffer read pointer to the end of the Ethernet frame header.
+ buf.setPosition(start_pos + ETHERNET_HEADER_LEN);
+}
+
+void
+decodeIpUdpHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+ // The size of the buffer must be at least equal to the minimal size of
+ // the IPv4 packet header plus UDP header length.
+ if (buf.getLength() - buf.getPosition() < MIN_IP_HEADER_LEN + UDP_HEADER_LEN) {
+ isc_throw(InvalidPacketHeader, "the total size of the IP and UDP headers in "
+ << "received packet is invalid, expected at least "
+ << MIN_IP_HEADER_LEN + UDP_HEADER_LEN
+ << " bytes, received " << buf.getLength() - buf.getPosition()
+ << " bytes");
+ }
+
+ // Packet object must not be NULL.
+ if (!pkt) {
+ isc_throw(BadValue, "NULL packet object provided when parsing IP and UDP"
+ " packet headers");
+ }
+
+ BOOST_STATIC_ASSERT(IP_SRC_ADDR_OFFSET < MIN_IP_HEADER_LEN);
+
+ // Remember initial position of the read pointer.
+ size_t start_pos = buf.getPosition();
+
+ // Read IP header length (mask most significant bits as they indicate IP version).
+ uint8_t ip_len = buf.readUint8() & 0xF;
+ // IP length is the number of 4 byte chunks that construct IPv4 header.
+ // It must not be lower than 5 because first 20 bytes are fixed.
+ if (ip_len < 5) {
+ isc_throw(InvalidPacketHeader, "Value of the length of the IP header must not be"
+ << " lower than 5 words. The length of the received header is "
+ << static_cast<unsigned>(ip_len) << ".");
+ }
+
+ // Seek to the position of source IP address.
+ buf.setPosition(start_pos + IP_SRC_ADDR_OFFSET);
+ // Read source address.
+ pkt->setRemoteAddr(IOAddress(buf.readUint32()));
+ // Read destination address.
+ pkt->setLocalAddr(IOAddress(buf.readUint32()));
+
+ // Skip IP header options (if any) to start of the
+ // UDP header.
+ buf.setPosition(start_pos + ip_len * 4);
+
+ // Read source port from UDP header.
+ pkt->setRemotePort(buf.readUint16());
+ // Read destination port from UDP header.
+ pkt->setLocalPort(buf.readUint16());
+
+ // Set the pointer position to the first byte o the
+ // UDP payload (DHCP packet).
+ buf.setPosition(start_pos + ip_len * 4 + UDP_HEADER_LEN);
+}
+
+void
+writeEthernetHeader(const Pkt4Ptr& pkt, OutputBuffer& out_buf) {
+ // Set destination HW address.
+ HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+ if (remote_addr) {
+ if (remote_addr->hwaddr_.size() != HWAddr::ETHERNET_HWADDR_LEN) {
+ isc_throw(BadValue, "invalid size of the remote HW address "
+ << remote_addr->hwaddr_.size() << " when constructing"
+ << " an ethernet frame header; expected size is"
+ << " " << HWAddr::ETHERNET_HWADDR_LEN);
+ } else if (!pkt->isRelayed() &&
+ (pkt->getFlags() & Pkt4::FLAG_BROADCAST_MASK)) {
+ out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN,255)[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ } else {
+ out_buf.writeData(&remote_addr->hwaddr_[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ }
+ } else {
+ // HW address has not been specified. This is possible when receiving
+ // packet through a logical interface (e.g. lo). In such cases, we
+ // don't want to fail but rather provide a default HW address, which
+ // consists of zeros.
+ out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ }
+
+ // Set source HW address.
+ HWAddrPtr local_addr = pkt->getLocalHWAddr();
+ if (local_addr) {
+ if (local_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) {
+ out_buf.writeData(&local_addr->hwaddr_[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ } else {
+ isc_throw(BadValue, "invalid size of the local HW address "
+ << local_addr->hwaddr_.size() << " when constructing"
+ << " an ethernet frame header; expected size is"
+ << " " << HWAddr::ETHERNET_HWADDR_LEN);
+ }
+ } else {
+ // Provide default HW address.
+ out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ }
+
+ // Type IP.
+ out_buf.writeUint16(ETHERNET_TYPE_IP);
+}
+
+void
+writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf) {
+
+ out_buf.writeUint8(0x45); // IP version 4, IP header length 5
+ out_buf.writeUint8(IPTOS_LOWDELAY); // DSCP and ECN
+ out_buf.writeUint16(28 + pkt->getBuffer().getLength()); // Total length.
+ out_buf.writeUint16(0); // Identification
+ out_buf.writeUint16(0x4000); // Disable fragmentation.
+ out_buf.writeUint8(128); // TTL
+ out_buf.writeUint8(IPPROTO_UDP); // Protocol UDP.
+ out_buf.writeUint16(0); // Temporarily set checksum to 0.
+ out_buf.writeUint32(pkt->getLocalAddr().toUint32()); // Source address.
+ out_buf.writeUint32(pkt->getRemoteAddr().toUint32()); // Destination address.
+
+ // Calculate pseudo header checksum. It will be necessary to compute
+ // UDP checksum.
+ // Get the UDP length. This includes udp header's and data length.
+ uint32_t udp_len = 8 + pkt->getBuffer().getLength();
+ // The magic number "8" indicates the offset where the source address
+ // is stored in the buffer. This offset is counted here from the
+ // current tail of the buffer. Starting from this offset we calculate
+ // the checksum using 8 following bytes of data. This will include
+ // 4 bytes of source address and 4 bytes of destination address.
+ // The IPPROTO_UDP and udp_len are also added up to the checksum.
+ uint16_t pseudo_hdr_checksum =
+ calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 8,
+ 8, IPPROTO_UDP + udp_len);
+
+ // Calculate IP header checksum.
+ uint16_t ip_checksum = ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData())
+ + out_buf.getLength() - 20, 20);
+ // Write checksum in the IP header. The offset of the checksum is 10 bytes
+ // back from the tail of the current buffer.
+ out_buf.writeUint16At(ip_checksum, out_buf.getLength() - 10);
+
+ // Start UDP header.
+ out_buf.writeUint16(pkt->getLocalPort()); // Source port.
+ out_buf.writeUint16(pkt->getRemotePort()); // Destination port.
+ out_buf.writeUint16(udp_len); // Length of the header and data.
+
+ // Checksum is calculated from the contents of UDP header, data and pseudo ip header.
+ // The magic number "6" indicates that the UDP header starts at offset 6 from the
+ // tail of the current buffer. These 6 bytes contain source and destination port
+ // as well as the length of the header.
+ uint16_t udp_checksum =
+ ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 6, 6,
+ calcChecksum(static_cast<const uint8_t*>(pkt->getBuffer().getData()),
+ pkt->getBuffer().getLength(),
+ pseudo_hdr_checksum));
+ // Write UDP checksum.
+ out_buf.writeUint16(udp_checksum);
+}
+
+uint16_t
+calcChecksum(const uint8_t* buf, const uint32_t buf_size, uint32_t sum) {
+ uint32_t i;
+ for (i = 0; i < (buf_size & ~1U); i += 2) {
+ uint16_t chunk = buf[i] << 8 | buf[i + 1];
+ sum += chunk;
+ if (sum > 0xFFFF) {
+ sum -= 0xFFFF;
+ }
+ }
+ // If one byte has left, we also need to add it to the checksum.
+ if (i < buf_size) {
+ sum += buf[i] << 8;
+ if (sum > 0xFFFF) {
+ sum -= 0xFFFF;
+ }
+ }
+
+ return (sum);
+
+}
+
+}
+}
diff --git a/src/lib/dhcp/protocol_util.h b/src/lib/dhcp/protocol_util.h
new file mode 100644
index 0000000..fcd8473
--- /dev/null
+++ b/src/lib/dhcp/protocol_util.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2013-2021 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 PROTOCOL_UTIL_H
+#define PROTOCOL_UTIL_H
+
+#include <dhcp/pkt4.h>
+#include <util/buffer.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when error occurred during parsing packet's headers.
+///
+/// This exception is thrown when parsing link, Internet or Transport layer
+/// header has failed.
+class InvalidPacketHeader : public Exception {
+public:
+ InvalidPacketHeader(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// Size of the Ethernet frame header.
+static const size_t ETHERNET_HEADER_LEN = 14;
+/// Offset of the 2-byte word in the Ethernet packet which
+/// holds the type of the protocol it encapsulates.
+static const size_t ETHERNET_PACKET_TYPE_OFFSET = 12;
+/// This value is held in the Ethertype field of Ethernet frame
+/// and indicates that an IP packet is encapsulated with this
+/// frame. In the standard headers, there is an ETHERTYPE_IP,
+/// constant which serves the same purpose. However, it is more
+/// convenient to have our constant because we avoid
+/// inclusion of additional headers, which have different names
+/// and locations on different OSes.
+static const uint16_t ETHERNET_TYPE_IP = 0x0800;
+
+/// Minimal IPv4 header length.
+static const size_t MIN_IP_HEADER_LEN = 20;
+/// Offset in the IP header where the flags field starts.
+static const size_t IP_FLAGS_OFFSET = 6;
+/// Offset of the byte in IP header which holds the type
+/// of the protocol it encapsulates.
+static const size_t IP_PROTO_TYPE_OFFSET = 9;
+/// Offset of source address in the IPv4 header.
+static const size_t IP_SRC_ADDR_OFFSET = 12;
+/// Offset of destination address in the IPv4 header.
+static const size_t IP_DEST_ADDR_OFFSET = 16;
+
+/// UDP header length.
+static const size_t UDP_HEADER_LEN = 8;
+/// Offset within UDP header where destination port is held.
+static const size_t UDP_DEST_PORT = 2;
+
+/// @brief Decode the Ethernet header.
+///
+/// This function reads Ethernet frame header from the provided
+/// buffer at the current read position. The source HW address
+/// is read from the header and assigned as client address in
+/// the pkt object. The buffer read pointer is set to the end
+/// of the Ethernet frame header if read was successful.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding header to be parsed.
+/// @param [out] pkt packet object receiving HW source address read from header.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeEthernetHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Decode IP and UDP header.
+///
+/// This function reads IP and UDP headers from the provided buffer
+/// at the current read position. The source and destination IP
+/// addresses and ports and read from these headers and stored in
+/// the appropriate members of the pkt object.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding headers to be parsed.
+/// @param [out] pkt packet object where IP addresses and ports
+/// are stored.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeIpUdpHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Writes ethernet frame header into a buffer.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt packet object holding source and destination HW address.
+/// @param [out] out_buf buffer where a header is written.
+void writeEthernetHeader(const Pkt4Ptr& pkt,
+ util::OutputBuffer& out_buf);
+
+/// @brief Writes both IP and UDP header into output buffer
+///
+/// This utility function assembles IP and UDP packet headers for the
+/// provided DHCPv4 message. The source and destination addresses and
+/// ports stored in the pkt object are copied as source and destination
+/// addresses and ports into IP/UDP headers.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt DHCPv4 packet to be sent in IP packet
+/// @param [out] out_buf buffer where an IP header is written
+void writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf);
+
+/// @brief Calculates checksum for provided buffer
+///
+/// This function returns the sum of 16-bit values from the provided
+/// buffer. If the third parameter is specified, it indicates the
+/// initial checksum value. This parameter can be a result of
+/// calcChecksum function's invocation on different data buffer.
+/// The IP or UDP checksum value is a complement of the result returned
+/// by this function. However, this function does not compute complement
+/// of the summed values. It must be calculated outside of this function
+/// before writing the value to the packet buffer.
+///
+/// The IP header checksum calculation algorithm has been defined in
+/// <a href="https://tools.ietf.org/html/rfc791#page-14">RFC 791</a>
+///
+/// @param buf buffer for which the checksum is calculated.
+/// @param buf_size size of the buffer for which checksum is calculated.
+/// @param sum initial checksum value, other values will be added to it.
+///
+/// @return calculated checksum.
+uint16_t calcChecksum(const uint8_t* buf, const uint32_t buf_size,
+ uint32_t sum = 0);
+
+}
+}
+#endif // PROTOCOL_UTIL_H
diff --git a/src/lib/dhcp/socket_info.h b/src/lib/dhcp/socket_info.h
new file mode 100644
index 0000000..f81f7fd
--- /dev/null
+++ b/src/lib/dhcp/socket_info.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2018 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 DHCP_SOCKET_INFO_H
+#define DHCP_SOCKET_INFO_H
+
+#include <asiolink/io_address.h>
+#include <cstdint>
+
+
+namespace isc {
+
+namespace dhcp {
+
+/// Holds information about socket.
+struct SocketInfo {
+
+ isc::asiolink::IOAddress addr_; /// bound address
+ uint16_t port_; /// socket port
+ uint16_t family_; /// IPv4 or IPv6
+
+ /// @brief Socket descriptor (a.k.a. primary socket).
+ int sockfd_;
+
+ /// @brief Fallback socket descriptor.
+ ///
+ /// This socket descriptor holds the handle to the fallback socket.
+ /// The fallback socket is created when there is a need for the regular
+ /// datagram socket to be bound to an IP address and port, besides
+ /// primary socket (sockfd_) which is actually used to receive and process
+ /// the DHCP messages. The fallback socket (if exists) is always associated
+ /// with the primary socket. In particular, the need for the fallback socket
+ /// arises when raw socket is a primary one. When primary socket is open,
+ /// it is bound to an interface not the address and port. The implications
+ /// include the possibility that the other process (e.g. the other instance
+ /// of DHCP server) will bind to the same address and port through which the
+ /// raw socket receives the DHCP messages. Another implication is that the
+ /// kernel, being unaware of the DHCP server operating through the raw
+ /// socket, will respond with the ICMP "Destination port unreachable"
+ /// messages when DHCP messages are only received through the raw socket.
+ /// In order to workaround the issues mentioned here, the fallback socket
+ /// should be opened so as/ the kernel is aware that the certain address
+ /// and port is in use.
+ ///
+ /// The fallback description is supposed to be set to a negative value if
+ /// the fallback socket is closed (not open).
+ int fallbackfd_;
+
+ /// @brief SocketInfo constructor.
+ ///
+ /// @param addr An address the socket is bound to.
+ /// @param port A port the socket is bound to.
+ /// @param sockfd Socket descriptor.
+ /// @param fallbackfd A descriptor of the fallback socket.
+ SocketInfo(const isc::asiolink::IOAddress& addr, const uint16_t port,
+ const int sockfd, const int fallbackfd = -1)
+ : addr_(addr), port_(port), family_(addr.getFamily()),
+ sockfd_(sockfd), fallbackfd_(fallbackfd) { }
+
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // DHCP_SOCKET_INFO_H
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
new file mode 100644
index 0000000..ccd3ae1
--- /dev/null
+++ b/src/lib/dhcp/std_option_defs.h
@@ -0,0 +1,765 @@
+// Copyright (C) 2012-2023 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 STD_OPTION_DEFS_H
+#define STD_OPTION_DEFS_H
+
+#include <dhcp/option_data_types.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_space.h>
+
+/// @brief global std option spaces
+#define DHCP4_OPTION_SPACE "dhcp4"
+#define DHCP6_OPTION_SPACE "dhcp6"
+#define ISC_V6_OPTION_SPACE "4o6"
+#define MAPE_V6_OPTION_SPACE "s46-cont-mape-options"
+#define MAPT_V6_OPTION_SPACE "s46-cont-mapt-options"
+#define LW_V6_OPTION_SPACE "s46-cont-lw-options"
+#define V4V6_RULE_OPTION_SPACE "s46-rule-options"
+#define V4V6_BIND_OPTION_SPACE "s46-v4v6bind-options"
+#define LAST_RESORT_V4_OPTION_SPACE "last-resort-v4"
+
+/// @brief encapsulated option spaces
+#define DHCP_AGENT_OPTION_SPACE "dhcp-agent-options-space"
+#define VENDOR_ENCAPSULATED_OPTION_SPACE "vendor-encapsulated-options-space"
+
+// NOTE:
+// When adding a new space, make sure you also update
+// src/lib/yang/adaptor_option.cc
+
+namespace isc {
+namespace dhcp {
+
+namespace {
+
+/// @brief Declare an array holding parameters used to create instance
+/// of a definition for option comprising a record of data fields.
+///
+/// @param name name of the array being declared.
+#ifndef RECORD_DECL
+#define RECORD_DECL(name, ...) const OptionDataType name[] = { __VA_ARGS__ }
+#endif
+
+/// @brief A pair of values: one pointing to the array holding types of
+/// data fields belonging to the record, and size of this array.
+///
+/// @param name name of the array holding data fields' types.
+#ifndef RECORD_DEF
+#define RECORD_DEF(name) name, sizeof(name) / sizeof(name[0])
+#endif
+
+#ifndef NO_RECORD_DEF
+#define NO_RECORD_DEF 0, 0
+#endif
+
+// SLP Directory Agent option.
+RECORD_DECL(DIRECTORY_AGENT_RECORDS, OPT_BOOLEAN_TYPE, OPT_IPV4_ADDRESS_TYPE);
+
+// SLP Service Scope option.
+//
+// The scope list is optional.
+RECORD_DECL(SERVICE_SCOPE_RECORDS, OPT_BOOLEAN_TYPE, OPT_STRING_TYPE);
+
+// fqdn option record fields.
+//
+// Note that the flags field indicates the type of domain
+// name encoding. There is a choice between deprecated
+// ASCII encoding and compressed encoding described in
+// RFC 1035, section 3.1. The latter could be handled
+// by OPT_FQDN_TYPE but we can't use it here because
+// clients may request ASCII encoding.
+RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+ OPT_FQDN_TYPE);
+
+// V-I Vendor Class record fields.
+//
+// Opaque data is represented here by the binary data field.
+RECORD_DECL(VIVCO_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+
+// RFC4578 (PXE) record fields
+//
+// Three 1 byte fields to describe a network interface: type, major and minor
+RECORD_DECL(CLIENT_NDI_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
+// A client identifier: a 1 byte type field followed by opaque data depending on the type
+RECORD_DECL(UUID_GUID_RECORDS, OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+
+// RFC6731 DHCPv4 Recursive DNS Server Selection option.
+//
+// Flag, two addresses and domain list
+RECORD_DECL(V4_RDNSS_SELECT_RECORDS, OPT_UINT8_TYPE, OPT_IPV4_ADDRESS_TYPE,
+ OPT_IPV4_ADDRESS_TYPE, OPT_FQDN_TYPE);
+
+// RFC6926 DHCPv4 Bulk Leasequery Status Code option.
+RECORD_DECL(V4_STATUS_CODE_RECORDS, OPT_UINT8_TYPE, OPT_STRING_TYPE);
+
+// RFC7618 DHCPv4 Port Parameter option.
+//
+// PSID offset, PSID-len and PSID
+RECORD_DECL(V4_PORTPARAMS_RECORDS, OPT_UINT8_TYPE, OPT_PSID_TYPE);
+
+// RFC5969 DHCPv6 6RD option.
+//
+// two 8 bit lengthes, an IPv6 address and one or more IPv4 addresses
+RECORD_DECL(OPT_6RD_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+ OPT_IPV6_ADDRESS_TYPE, OPT_IPV4_ADDRESS_TYPE);
+
+// RFC-draft-ietf-add-dnr DHCPv4 DNR option.
+//
+// DNR Instance Data Length (2 octets), Service Priority (2 octets),
+// ADN Length (1 octet), ADN FQDN.
+// Opaque data is represented here by the binary data field.
+// It may contain Addr Length (1 octet), IPv4 address(es), SvcParams,
+// and next DNR instances as binary data.
+RECORD_DECL(V4_DNR_RECORDS, OPT_UINT16_TYPE, OPT_UINT16_TYPE, OPT_UINT8_TYPE,
+ OPT_FQDN_TYPE, OPT_BINARY_TYPE);
+
+/// @brief Definitions of standard DHCPv4 options.
+const OptionDefParams STANDARD_V4_OPTION_DEFINITIONS[] = {
+ { "subnet-mask", DHO_SUBNET_MASK, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "time-offset", DHO_TIME_OFFSET, DHCP4_OPTION_SPACE, OPT_INT32_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "routers", DHO_ROUTERS, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true,
+ NO_RECORD_DEF, "" },
+ { "time-servers", DHO_TIME_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "name-servers", DHO_NAME_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "domain-name-servers", DHO_DOMAIN_NAME_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "log-servers", DHO_LOG_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "cookie-servers", DHO_COOKIE_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "lpr-servers", DHO_LPR_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "impress-servers", DHO_IMPRESS_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "resource-location-servers", DHO_RESOURCE_LOCATION_SERVERS,
+ DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "host-name", DHO_HOST_NAME, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "boot-size", DHO_BOOT_SIZE, DHCP4_OPTION_SPACE, OPT_UINT16_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "merit-dump", DHO_MERIT_DUMP, DHCP4_OPTION_SPACE, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "domain-name", DHO_DOMAIN_NAME, DHCP4_OPTION_SPACE, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "swap-server", DHO_SWAP_SERVER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "root-path", DHO_ROOT_PATH, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "extensions-path", DHO_EXTENSIONS_PATH, DHCP4_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "ip-forwarding", DHO_IP_FORWARDING, DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "non-local-source-routing", DHO_NON_LOCAL_SOURCE_ROUTING,
+ DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "policy-filter", DHO_POLICY_FILTER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "max-dgram-reassembly", DHO_MAX_DGRAM_REASSEMBLY, DHCP4_OPTION_SPACE,
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "default-ip-ttl", DHO_DEFAULT_IP_TTL, DHCP4_OPTION_SPACE, OPT_UINT8_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "path-mtu-aging-timeout", DHO_PATH_MTU_AGING_TIMEOUT, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "path-mtu-plateau-table", DHO_PATH_MTU_PLATEAU_TABLE, DHCP4_OPTION_SPACE,
+ OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+ { "interface-mtu", DHO_INTERFACE_MTU, DHCP4_OPTION_SPACE, OPT_UINT16_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "all-subnets-local", DHO_ALL_SUBNETS_LOCAL, DHCP4_OPTION_SPACE,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "broadcast-address", DHO_BROADCAST_ADDRESS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "perform-mask-discovery", DHO_PERFORM_MASK_DISCOVERY, DHCP4_OPTION_SPACE,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "mask-supplier", DHO_MASK_SUPPLIER, DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "router-discovery", DHO_ROUTER_DISCOVERY, DHCP4_OPTION_SPACE,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "router-solicitation-address", DHO_ROUTER_SOLICITATION_ADDRESS,
+ DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "static-routes", DHO_STATIC_ROUTES, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "trailer-encapsulation", DHO_TRAILER_ENCAPSULATION, DHCP4_OPTION_SPACE,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "arp-cache-timeout", DHO_ARP_CACHE_TIMEOUT, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "ieee802-3-encapsulation", DHO_IEEE802_3_ENCAPSULATION,
+ DHCP4_OPTION_SPACE, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "default-tcp-ttl", DHO_DEFAULT_TCP_TTL, DHCP4_OPTION_SPACE,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "tcp-keepalive-interval", DHO_TCP_KEEPALIVE_INTERVAL, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "tcp-keepalive-garbage", DHO_TCP_KEEPALIVE_GARBAGE, DHCP4_OPTION_SPACE,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+ { "nis-domain", DHO_NIS_DOMAIN, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "nis-servers", DHO_NIS_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "ntp-servers", DHO_NTP_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ /// vendor-encapsulated-options (43) is deferred
+ { "netbios-name-servers", DHO_NETBIOS_NAME_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "netbios-dd-server", DHO_NETBIOS_DD_SERVER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "netbios-node-type", DHO_NETBIOS_NODE_TYPE, DHCP4_OPTION_SPACE,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "netbios-scope", DHO_NETBIOS_SCOPE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "font-servers", DHO_FONT_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "x-display-manager", DHO_X_DISPLAY_MANAGER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "dhcp-requested-address", DHO_DHCP_REQUESTED_ADDRESS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-lease-time", DHO_DHCP_LEASE_TIME, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-option-overload", DHO_DHCP_OPTION_OVERLOAD, DHCP4_OPTION_SPACE,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-message-type", DHO_DHCP_MESSAGE_TYPE, DHCP4_OPTION_SPACE,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-server-identifier", DHO_DHCP_SERVER_IDENTIFIER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-parameter-request-list", DHO_DHCP_PARAMETER_REQUEST_LIST,
+ DHCP4_OPTION_SPACE, OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" },
+ { "dhcp-message", DHO_DHCP_MESSAGE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "dhcp-max-message-size", DHO_DHCP_MAX_MESSAGE_SIZE, DHCP4_OPTION_SPACE,
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-renewal-time", DHO_DHCP_RENEWAL_TIME, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER,
+ DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER, DHCP4_OPTION_SPACE,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, DHCP4_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "nwip-suboptions", DHO_NWIP_SUBOPTIONS, DHCP4_OPTION_SPACE,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "nisplus-domain-name", DHO_NISP_DOMAIN_NAME, DHCP4_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "nisplus-servers", DHO_NISP_SERVER_ADDR, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "tftp-server-name", DHO_TFTP_SERVER_NAME, DHCP4_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "boot-file-name", DHO_BOOT_FILE_NAME, DHCP4_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "mobile-ip-home-agent", DHO_HOME_AGENT_ADDRS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "smtp-server", DHO_SMTP_SERVER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "pop-server", DHO_POP3_SERVER, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "nntp-server", DHO_NNTP_SERVER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "www-server", DHO_WWW_SERVER, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "finger-server", DHO_FINGER_SERVER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "irc-server", DHO_IRC_SERVER, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "streettalk-server", DHO_STREETTALK_SERVER, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "streettalk-directory-assistance-server", DHO_STDASERVER,
+ DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "user-class", DHO_USER_CLASS, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "slp-directory-agent", DHO_DIRECTORY_AGENT, DHCP4_OPTION_SPACE,
+ OPT_RECORD_TYPE, true, RECORD_DEF(DIRECTORY_AGENT_RECORDS), "" },
+ { "slp-service-scope", DHO_SERVICE_SCOPE, DHCP4_OPTION_SPACE,
+ OPT_RECORD_TYPE, false, RECORD_DEF(SERVICE_SCOPE_RECORDS), "" },
+ { "fqdn", DHO_FQDN, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(FQDN_RECORDS), "" },
+ { "dhcp-agent-options", DHO_DHCP_AGENT_OPTIONS, DHCP4_OPTION_SPACE,
+ OPT_EMPTY_TYPE, false, NO_RECORD_DEF, DHCP_AGENT_OPTION_SPACE },
+ { "nds-servers", DHO_NDS_SERVERS, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "nds-tree-name", DHO_NDS_TREE_NAME, DHCP4_OPTION_SPACE, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "nds-context", DHO_NDS_CONTEXT, DHCP4_OPTION_SPACE, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "bcms-controller-names", DHO_BCMCS_DOMAIN_NAME_LIST, DHCP4_OPTION_SPACE,
+ OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+ { "bcms-controller-address", DHO_BCMCS_IPV4_ADDR, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ // Unfortunately the AUTHENTICATE option contains a 64-bit
+ // data field called 'replay-detection' that can't be added
+ // as a record field to a custom option. Also, there is no
+ // dedicated option class to handle it so we simply return
+ // binary option type for now.
+ // @todo implement a class to handle AUTH option.
+ { "authenticate", DHO_AUTHENTICATE, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "client-last-transaction-time", DHO_CLIENT_LAST_TRANSACTION_TIME,
+ DHCP4_OPTION_SPACE, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "associated-ip", DHO_ASSOCIATED_IP, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "client-system", DHO_SYSTEM, DHCP4_OPTION_SPACE, OPT_UINT16_TYPE, true,
+ NO_RECORD_DEF, "" },
+ { "client-ndi", DHO_NDI, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(CLIENT_NDI_RECORDS), "" },
+ { "uuid-guid", DHO_UUID_GUID, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(UUID_GUID_RECORDS), "" },
+ { "uap-servers", DHO_USER_AUTH, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "geoconf-civic", DHO_GEOCONF_CIVIC, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "pcode", DHO_PCODE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "tcode", DHO_TCODE, DHCP4_OPTION_SPACE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "v6-only-preferred", DHO_V6_ONLY_PREFERRED, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "netinfo-server-address", DHO_NETINFO_ADDR, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "netinfo-server-tag", DHO_NETINFO_TAG, DHCP4_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "v4-captive-portal", DHO_V4_CAPTIVE_PORTAL, DHCP4_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "auto-config", DHO_AUTO_CONFIG, DHCP4_OPTION_SPACE, OPT_UINT8_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "name-service-search", DHO_NAME_SERVICE_SEARCH, DHCP4_OPTION_SPACE,
+ OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+ { "subnet-selection", DHO_SUBNET_SELECTION, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "domain-search", DHO_DOMAIN_SEARCH, DHCP4_OPTION_SPACE, OPT_FQDN_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS, DHCP4_OPTION_SPACE,
+ OPT_RECORD_TYPE, false, RECORD_DEF(VIVCO_RECORDS), "" },
+ // Vendor-Identifying Vendor Specific Information option payload begins with a
+ // 32-bit log enterprise number, followed by a tuple of data-len/option-data.
+ // The format defined here includes 32-bit field holding enterprise number.
+ // This allows for specifying option-data information where the enterprise-id
+ // is represented by a uint32_t value. Previously we represented this option
+ // as a binary, but that would imply that enterprise number would have to be
+ // represented in binary format in the server configuration. That would be
+ // inconvenient and non-intuitive.
+ /// @todo We need to extend support for vendor options with ability to specify
+ /// multiple enterprise numbers for a single option. Perhaps it would be
+ /// ok to specify multiple instances of the "vivso-suboptions" which will be
+ /// combined in a single option by the server before responding to a client.
+ { "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "pana-agent", DHO_PANA_AGENT, DHCP4_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "v4-lost", DHO_V4_LOST, DHCP4_OPTION_SPACE, OPT_FQDN_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "capwap-ac-v4", DHO_CAPWAP_AC_V4, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "sip-ua-cs-domains", DHO_SIP_UA_CONF_SERVICE_DOMAINS, DHCP4_OPTION_SPACE,
+ OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+ { "v4-sztp-redirect", DHO_V4_SZTP_REDIRECT, DHCP4_OPTION_SPACE, OPT_TUPLE_TYPE,
+ true, NO_RECORD_DEF, ""},
+ { "rdnss-selection", DHO_RDNSS_SELECT, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE,
+ true, RECORD_DEF(V4_RDNSS_SELECT_RECORDS), "" },
+ { "status-code", DHO_STATUS_CODE, DHCP4_OPTION_SPACE,
+ OPT_RECORD_TYPE, false, RECORD_DEF(V4_STATUS_CODE_RECORDS), "" },
+ { "base-time", DHO_BASE_TIME, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "start-time-of-state", DHO_START_TIME_OF_STATE, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "query-start-time", DHO_QUERY_START_TIME, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "query-end-time", DHO_QUERY_END_TIME, DHCP4_OPTION_SPACE,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "dhcp-state", DHO_DHCP_STATE, DHCP4_OPTION_SPACE,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "data-source", DHO_DATA_SOURCE, DHCP4_OPTION_SPACE,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "v4-portparams", DHO_V4_PORTPARAMS, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(V4_PORTPARAMS_RECORDS), "" },
+ { "v4-dnr", DHO_V4_DNR, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(V4_DNR_RECORDS), "" },
+ { "option-6rd", DHO_6RD, DHCP4_OPTION_SPACE, OPT_RECORD_TYPE, true,
+ RECORD_DEF(OPT_6RD_RECORDS), "" },
+ { "v4-access-domain", DHO_V4_ACCESS_DOMAIN, DHCP4_OPTION_SPACE,
+ OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" }
+
+ // @todo add definitions for all remaining options.
+};
+
+/// Number of option definitions defined.
+const int STANDARD_V4_OPTION_DEFINITIONS_SIZE =
+ sizeof(STANDARD_V4_OPTION_DEFINITIONS) /
+ sizeof(STANDARD_V4_OPTION_DEFINITIONS[0]);
+
+/// Definitions of DHCPv4 agent options.
+const OptionDefParams DHCP_AGENT_OPTION_DEFINITIONS[] = {
+ { "circuit-id", RAI_OPTION_AGENT_CIRCUIT_ID,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "remote-id", RAI_OPTION_REMOTE_ID,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "docsis-device-class", RAI_OPTION_DOCSIS_DEVICE_CLASS,
+ DHCP_AGENT_OPTION_SPACE, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "link-selection", RAI_OPTION_LINK_SELECTION,
+ DHCP_AGENT_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "subscriber-id", RAI_OPTION_SUBSCRIBER_ID,
+ DHCP_AGENT_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "radius", RAI_OPTION_RADIUS,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "auth", RAI_OPTION_AUTH,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "vendor-specific-info", RAI_OPTION_VSI,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "relay-flags", RAI_OPTION_RELAY_FLAGS,
+ DHCP_AGENT_OPTION_SPACE, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+ { "server-id-override", RAI_OPTION_SERVER_ID_OVERRIDE,
+ DHCP_AGENT_OPTION_SPACE, OPT_IPV4_ADDRESS_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "relay-id", RAI_OPTION_RELAY_ID,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "access-techno-type", RAI_OPTION_ACCESS_TECHNO_TYPE,
+ DHCP_AGENT_OPTION_SPACE, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "access-network-name", RAI_OPTION_ACCESS_NETWORK_NAME,
+ DHCP_AGENT_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "access-point-name", RAI_OPTION_ACCESS_POINT_NAME,
+ DHCP_AGENT_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "access-point-bssid", RAI_OPTION_ACCESS_POINT_BSSID,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "operator-id", RAI_OPTION_OPERATOR_ID,
+ DHCP_AGENT_OPTION_SPACE, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "operator-realm", RAI_OPTION_OPERATOR_REALM,
+ DHCP_AGENT_OPTION_SPACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "relay-port", RAI_OPTION_RELAY_PORT,
+ DHCP_AGENT_OPTION_SPACE, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "virtual-subnet-select", RAI_OPTION_VIRTUAL_SUBNET_SELECT,
+ DHCP_AGENT_OPTION_SPACE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "virtual-subnet-select-ctrl", RAI_OPTION_VIRTUAL_SUBNET_SELECT_CTRL,
+ DHCP_AGENT_OPTION_SPACE, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "" }
+};
+
+const int DHCP_AGENT_OPTION_DEFINITIONS_SIZE =
+ sizeof(DHCP_AGENT_OPTION_DEFINITIONS) /
+ sizeof(DHCP_AGENT_OPTION_DEFINITIONS[0]);
+
+/// Last resort definitions (only option 43 for now, these definitions
+/// are applied in deferred unpacking when none is found).
+const OptionDefParams LAST_RESORT_V4_OPTION_DEFINITIONS[] = {
+ { "vendor-encapsulated-options", DHO_VENDOR_ENCAPSULATED_OPTIONS,
+ DHCP4_OPTION_SPACE, OPT_EMPTY_TYPE, false, NO_RECORD_DEF,
+ VENDOR_ENCAPSULATED_OPTION_SPACE }
+};
+
+const int LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE =
+ sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS) /
+ sizeof(LAST_RESORT_V4_OPTION_DEFINITIONS[0]);
+
+/// Start Definition of DHCPv6 options
+
+// client-fqdn
+RECORD_DECL(CLIENT_FQDN_RECORDS, OPT_UINT8_TYPE, OPT_FQDN_TYPE);
+// geoconf-civic
+RECORD_DECL(GEOCONF_CIVIC_RECORDS, OPT_UINT8_TYPE, OPT_UINT16_TYPE,
+ OPT_BINARY_TYPE);
+// iaddr
+RECORD_DECL(IAADDR_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT32_TYPE);
+// ia-na
+RECORD_DECL(IA_NA_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-pd
+RECORD_DECL(IA_PD_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-prefix
+RECORD_DECL(IA_PREFIX_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-query
+RECORD_DECL(LQ_QUERY_RECORDS, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-relay-data
+RECORD_DECL(LQ_RELAY_DATA_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
+// remote-id
+RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// s46-rule
+RECORD_DECL(S46_RULE, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+ OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE);
+// s46-v4v6bind
+RECORD_DECL(S46_V4V6BIND, OPT_IPV4_ADDRESS_TYPE, OPT_IPV6_PREFIX_TYPE);
+// s46-portparams
+RECORD_DECL(S46_PORTPARAMS, OPT_UINT8_TYPE, OPT_PSID_TYPE);
+// status-code
+RECORD_DECL(V6_STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
+// vendor-class
+RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// rdnss-selection
+RECORD_DECL(V6_RDNSS_SELECT_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_UINT8_TYPE,
+ OPT_FQDN_TYPE);
+// sedhcpv6 signature
+RECORD_DECL(SIGNATURE_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+ OPT_BINARY_TYPE);
+
+// RFC5970 (PXE) Class record fields
+//
+// Three 1 byte fileds to describe a network interface: type, major and minor
+RECORD_DECL(CLIENT_NII_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE);
+
+// RFC-draft-ietf-add-dnr DHCPv6 DNR option.
+//
+// Service Priority (2 octets), ADN Length (2 octets), ADN FQDN.
+// Opaque data is represented here by the binary data field.
+// It may contain Addr Length (2 octets), IPv6 address(es), SvcParams.
+RECORD_DECL(V6_DNR_RECORDS, OPT_UINT16_TYPE, OPT_UINT16_TYPE, OPT_FQDN_TYPE, OPT_BINARY_TYPE);
+
+/// Standard DHCPv6 option definitions.
+///
+/// @warning in this array, the initializers are provided for all
+/// OptionDefParams struct's members despite initializers for
+/// 'records' and 'record_size' could be omitted for entries for
+/// which 'type' does not equal to OPT_RECORD_TYPE. If initializers
+/// are omitted the corresponding values should default to 0.
+/// This however does not work on Solaris (GCC) which issues a
+/// warning about lack of initializers for some struct members
+/// causing build to fail.
+const OptionDefParams STANDARD_V6_OPTION_DEFINITIONS[] = {
+ { "clientid", D6O_CLIENTID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "serverid", D6O_SERVERID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "ia-na", D6O_IA_NA, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(IA_NA_RECORDS), "" },
+ { "ia-ta", D6O_IA_TA, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "iaaddr", D6O_IAADDR, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(IAADDR_RECORDS), "" },
+ { "oro", D6O_ORO, DHCP6_OPTION_SPACE, OPT_UINT16_TYPE, true, NO_RECORD_DEF,
+ "" },
+ { "preference", D6O_PREFERENCE, DHCP6_OPTION_SPACE, OPT_UINT8_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "elapsed-time", D6O_ELAPSED_TIME, DHCP6_OPTION_SPACE, OPT_UINT16_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "relay-msg", D6O_RELAY_MSG, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ // Unfortunately the AUTH option contains a 64-bit data field
+ // called 'replay-detection' that can't be added as a record
+ // field to a custom option. Also, there is no dedicated
+ // option class to handle it so we simply return binary
+ // option type for now.
+ // @todo implement a class to handle AUTH option.
+ { "auth", D6O_AUTH, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "unicast", D6O_UNICAST, DHCP6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "status-code", D6O_STATUS_CODE, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(V6_STATUS_CODE_RECORDS), "" },
+ { "rapid-commit", D6O_RAPID_COMMIT, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "user-class", D6O_USER_CLASS, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "vendor-class", D6O_VENDOR_CLASS, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(VENDOR_CLASS_RECORDS), "" },
+ { "vendor-opts", D6O_VENDOR_OPTS, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "interface-id", D6O_INTERFACE_ID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "reconf-msg", D6O_RECONF_MSG, DHCP6_OPTION_SPACE, OPT_UINT8_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "reconf-accept", D6O_RECONF_ACCEPT, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "sip-server-dns", D6O_SIP_SERVERS_DNS, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "sip-server-addr", D6O_SIP_SERVERS_ADDR, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "dns-servers", D6O_NAME_SERVERS, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "domain-search", D6O_DOMAIN_SEARCH, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "ia-pd", D6O_IA_PD, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(IA_PD_RECORDS), "" },
+ { "iaprefix", D6O_IAPREFIX, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(IA_PREFIX_RECORDS), "" },
+ { "nis-servers", D6O_NIS_SERVERS, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "nisp-servers", D6O_NISP_SERVERS, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "nis-domain-name", D6O_NIS_DOMAIN_NAME, DHCP6_OPTION_SPACE,
+ OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+ { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, DHCP6_OPTION_SPACE,
+ OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+ { "sntp-servers", D6O_SNTP_SERVERS, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
+ DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+ { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, DHCP6_OPTION_SPACE,
+ OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+ { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "geoconf-civic", D6O_GEOCONF_CIVIC, DHCP6_OPTION_SPACE,
+ OPT_RECORD_TYPE, false, RECORD_DEF(GEOCONF_CIVIC_RECORDS), "" },
+ { "remote-id", D6O_REMOTE_ID, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(REMOTE_ID_RECORDS), "" },
+ { "subscriber-id", D6O_SUBSCRIBER_ID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "client-fqdn", D6O_CLIENT_FQDN, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(CLIENT_FQDN_RECORDS), "" },
+ { "pana-agent", D6O_PANA_AGENT, DHCP6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, DHCP6_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, DHCP6_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "ero", D6O_ERO, DHCP6_OPTION_SPACE, OPT_UINT16_TYPE, true,
+ NO_RECORD_DEF, "" },
+ { "lq-query", D6O_LQ_QUERY, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(LQ_QUERY_RECORDS), DHCP6_OPTION_SPACE },
+ { "client-data", D6O_CLIENT_DATA, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE,
+ false, NO_RECORD_DEF, DHCP6_OPTION_SPACE },
+ { "clt-time", D6O_CLT_TIME, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "lq-relay-data", D6O_LQ_RELAY_DATA, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(LQ_RELAY_DATA_RECORDS), "" },
+ { "lq-client-link", D6O_LQ_CLIENT_LINK, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "v6-lost", D6O_V6_LOST, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "capwap-ac-v6", D6O_CAPWAP_AC_V6, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "relay-id", D6O_RELAY_ID, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "v6-access-domain", D6O_V6_ACCESS_DOMAIN, DHCP6_OPTION_SPACE,
+ OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+ { "sip-ua-cs-list", D6O_SIP_UA_CS_LIST, DHCP6_OPTION_SPACE,
+ OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+ { "bootfile-url", D6O_BOOTFILE_URL, DHCP6_OPTION_SPACE, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "bootfile-param", D6O_BOOTFILE_PARAM, DHCP6_OPTION_SPACE, OPT_TUPLE_TYPE,
+ true, NO_RECORD_DEF, "" },
+ { "client-arch-type", D6O_CLIENT_ARCH_TYPE, DHCP6_OPTION_SPACE,
+ OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+ { "nii", D6O_NII, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(CLIENT_NII_RECORDS), "" },
+ { "aftr-name", D6O_AFTR_NAME, DHCP6_OPTION_SPACE, OPT_FQDN_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "erp-local-domain-name", D6O_ERP_LOCAL_DOMAIN_NAME, DHCP6_OPTION_SPACE
+ , OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+ { "rsoo", D6O_RSOO, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE, false,
+ NO_RECORD_DEF, "rsoo-opts" },
+ { "pd-exclude", D6O_PD_EXCLUDE, DHCP6_OPTION_SPACE, OPT_IPV6_PREFIX_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "rdnss-selection", D6O_RDNSS_SELECTION, DHCP6_OPTION_SPACE,
+ OPT_RECORD_TYPE, true, RECORD_DEF(V6_RDNSS_SELECT_RECORDS), "" },
+ { "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, DHCP6_OPTION_SPACE,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+ { "link-address", D6O_LINK_ADDRESS, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "solmax-rt", D6O_SOL_MAX_RT, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "inf-max-rt", D6O_INF_MAX_RT, DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "dhcpv4-message", D6O_DHCPV4_MSG, DHCP6_OPTION_SPACE, OPT_BINARY_TYPE,
+ false, NO_RECORD_DEF, "" },
+ { "dhcp4o6-server-addr", D6O_DHCPV4_O_DHCPV6_SERVER, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "v6-captive-portal", D6O_V6_CAPTIVE_PORTAL, DHCP6_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "relay-source-port", D6O_RELAY_SOURCE_PORT, DHCP6_OPTION_SPACE,
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+ { "v6-sztp-redirect", D60_V6_SZTP_REDIRECT, DHCP6_OPTION_SPACE,
+ OPT_TUPLE_TYPE, true, NO_RECORD_DEF, "" },
+ { "ipv6-address-andsf", D6O_IPV6_ADDRESS_ANDSF, DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "s46-cont-mape", D6O_S46_CONT_MAPE, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE,
+ false, NO_RECORD_DEF, MAPE_V6_OPTION_SPACE },
+ { "s46-cont-mapt", D6O_S46_CONT_MAPT, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE,
+ false, NO_RECORD_DEF, MAPT_V6_OPTION_SPACE },
+ { "s46-cont-lw", D6O_S46_CONT_LW, DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE,
+ false, NO_RECORD_DEF, LW_V6_OPTION_SPACE },
+ { "v6-dnr", D6O_V6_DNR, DHCP6_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(V6_DNR_RECORDS), "" }
+
+ // @todo There is still a bunch of options for which we have to provide
+ // definitions but we don't do it because they are not really
+ // critical right now.
+};
+
+/// Number of option definitions defined.
+const int STANDARD_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(STANDARD_V6_OPTION_DEFINITIONS) /
+ sizeof(STANDARD_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief Definitions of vendor-specific DHCPv6 options, defined by ISC.
+/// 4o6-* options are used for inter-process communication. For details, see
+/// https://gitlab.isc.org/isc-projects/kea/wikis/designs/dhcpv4o6-design
+///
+/// @todo: As those options are defined by ISC, they do not belong in std_option_defs.h.
+/// We need to move them to a separate file, e.g. isc_option_defs.h
+const OptionDefParams ISC_V6_OPTION_DEFINITIONS[] = {
+ { "4o6-interface", ISC_V6_4O6_INTERFACE, ISC_V6_OPTION_SPACE,
+ OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, ISC_V6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+ { "4o6-source-port", ISC_V6_4O6_SRC_PORT, ISC_V6_OPTION_SPACE,
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" }
+};
+
+const int ISC_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(ISC_V6_OPTION_DEFINITIONS) /
+ sizeof(ISC_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief MAPE option definitions
+const OptionDefParams MAPE_V6_OPTION_DEFINITIONS[] = {
+ { "s46-br", D6O_S46_BR, MAPE_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "s46-rule", D6O_S46_RULE, MAPE_V6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(S46_RULE), V4V6_RULE_OPTION_SPACE }
+};
+
+const int MAPE_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(MAPE_V6_OPTION_DEFINITIONS) /
+ sizeof(MAPE_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief MAPT option definitions
+const OptionDefParams MAPT_V6_OPTION_DEFINITIONS[] = {
+ { "s46-rule", D6O_S46_RULE, MAPT_V6_OPTION_SPACE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(S46_RULE), V4V6_RULE_OPTION_SPACE },
+ { "s46-dmr", D6O_S46_DMR, MAPT_V6_OPTION_SPACE, OPT_IPV6_PREFIX_TYPE,
+ false, NO_RECORD_DEF, "" }
+};
+
+const int MAPT_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(MAPT_V6_OPTION_DEFINITIONS) /
+ sizeof(MAPT_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief LW option definitions
+const OptionDefParams LW_V6_OPTION_DEFINITIONS[] = {
+ { "s46-br", D6O_S46_BR, LW_V6_OPTION_SPACE, OPT_IPV6_ADDRESS_TYPE, false,
+ NO_RECORD_DEF, "" },
+ { "s46-v4v6bind", D6O_S46_V4V6BIND, LW_V6_OPTION_SPACE, OPT_RECORD_TYPE,
+ false, RECORD_DEF(S46_V4V6BIND), V4V6_BIND_OPTION_SPACE }
+};
+
+const int LW_V6_OPTION_DEFINITIONS_SIZE =
+ sizeof(LW_V6_OPTION_DEFINITIONS) /
+ sizeof(LW_V6_OPTION_DEFINITIONS[0]);
+
+/// @brief Rule option definitions
+const OptionDefParams V4V6_RULE_OPTION_DEFINITIONS[] = {
+ { "s46-portparams", D6O_S46_PORTPARAMS, V4V6_RULE_OPTION_SPACE,
+ OPT_RECORD_TYPE, false, RECORD_DEF(S46_PORTPARAMS), "" }
+};
+
+const int V4V6_RULE_OPTION_DEFINITIONS_SIZE =
+ sizeof(V4V6_RULE_OPTION_DEFINITIONS) /
+ sizeof(V4V6_RULE_OPTION_DEFINITIONS[0]);
+
+/// @brief Bind option definitions
+const OptionDefParams V4V6_BIND_OPTION_DEFINITIONS[] = {
+ { "s46-portparams", D6O_S46_PORTPARAMS, V4V6_BIND_OPTION_SPACE,
+ OPT_RECORD_TYPE, false, RECORD_DEF(S46_PORTPARAMS), "" }
+};
+
+const int V4V6_BIND_OPTION_DEFINITIONS_SIZE =
+ sizeof(V4V6_BIND_OPTION_DEFINITIONS) /
+ sizeof(V4V6_BIND_OPTION_DEFINITIONS[0]);
+
+} // namespace
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // STD_OPTION_DEFS_H
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
new file mode 100644
index 0000000..23d2f42
--- /dev/null
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -0,0 +1,124 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+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
+
+# Creates a library which is shared by various unit tests which require
+# configuration of fake interfaces.
+# The libdhcp++ does not link with this library because this would cause
+# build failures being a result of concurrency between build of this
+# library and the unit tests when make -j option was used, as they
+# are built out of the same makefile. Instead, the libdhcp++ tests link to
+# files belonging to this library, directly.
+noinst_LTLIBRARIES = libdhcptest.la
+
+libdhcptest_la_SOURCES = iface_mgr_test_config.cc iface_mgr_test_config.h
+libdhcptest_la_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h
+libdhcptest_la_SOURCES += pkt_filter6_test_stub.cc pkt_filter6_test_stub.h
+libdhcptest_la_SOURCES += pkt_captures4.cc pkt_captures6.cc pkt_captures.h
+libdhcptest_la_SOURCES += packet_queue_testutils.h
+libdhcptest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libdhcptest_la_CPPFLAGS = $(AM_CPPFLAGS)
+libdhcptest_la_LDFLAGS = $(AM_LDFLAGS)
+libdhcptest_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+
+TESTS += libdhcp++_unittests
+
+libdhcp___unittests_SOURCES = run_unittests.cc
+libdhcp___unittests_SOURCES += classify_unittest.cc
+libdhcp___unittests_SOURCES += duid_factory_unittest.cc
+libdhcp___unittests_SOURCES += hwaddr_unittest.cc
+libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
+libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h
+libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
+libdhcp___unittests_SOURCES += opaque_data_tuple_unittest.cc
+libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
+libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
+libdhcp___unittests_SOURCES += option4_dnr_unittest.cc
+libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
+libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc
+libdhcp___unittests_SOURCES += option6_auth_unittest.cc
+libdhcp___unittests_SOURCES += option6_dnr_unittest.cc
+libdhcp___unittests_SOURCES += option6_ia_unittest.cc
+libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
+libdhcp___unittests_SOURCES += option6_iaprefix_unittest.cc
+libdhcp___unittests_SOURCES += option6_pdexclude_unittest.cc
+libdhcp___unittests_SOURCES += option6_status_code_unittest.cc
+libdhcp___unittests_SOURCES += option_int_unittest.cc
+libdhcp___unittests_SOURCES += option_int_array_unittest.cc
+libdhcp___unittests_SOURCES += option_data_types_unittest.cc
+libdhcp___unittests_SOURCES += option_definition_unittest.cc
+libdhcp___unittests_SOURCES += option_copy_unittest.cc
+libdhcp___unittests_SOURCES += option_custom_unittest.cc
+libdhcp___unittests_SOURCES += option_opaque_data_tuples_unittest.cc
+libdhcp___unittests_SOURCES += option_unittest.cc
+libdhcp___unittests_SOURCES += option_space_unittest.cc
+libdhcp___unittests_SOURCES += option_string_unittest.cc
+libdhcp___unittests_SOURCES += option_vendor_unittest.cc
+libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc
+libdhcp___unittests_SOURCES += pkt_captures4.cc pkt_captures6.cc pkt_captures.h
+libdhcp___unittests_SOURCES += packet_queue4_unittest.cc
+libdhcp___unittests_SOURCES += packet_queue6_unittest.cc
+libdhcp___unittests_SOURCES += packet_queue_mgr4_unittest.cc
+libdhcp___unittests_SOURCES += packet_queue_mgr6_unittest.cc
+libdhcp___unittests_SOURCES += packet_queue_testutils.h
+libdhcp___unittests_SOURCES += pkt4_unittest.cc
+libdhcp___unittests_SOURCES += pkt6_unittest.cc
+libdhcp___unittests_SOURCES += pkt4o6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_test_stub.cc pkt_filter_test_stub.h
+libdhcp___unittests_SOURCES += pkt_filter6_test_stub.cc pkt_filter_test_stub.h
+libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc
+libdhcp___unittests_SOURCES += pkt_filter6_test_utils.h pkt_filter6_test_utils.cc
+
+# Utilize Linux Packet Filtering on Linux.
+if OS_LINUX
+libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
+endif
+
+# Utilize Berkeley Packet Filtering on BSD.
+if OS_BSD
+libdhcp___unittests_SOURCES += pkt_filter_bpf_unittest.cc
+endif
+
+libdhcp___unittests_SOURCES += protocol_util_unittest.cc
+libdhcp___unittests_SOURCES += duid_unittest.cc
+
+libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libdhcp___unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+libdhcp___unittests_LDADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcp___unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+libdhcp___unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dhcp/tests/Makefile.in b/src/lib/dhcp/tests/Makefile.in
new file mode 100644
index 0000000..2bf0328
--- /dev/null
+++ b/src/lib/dhcp/tests/Makefile.in
@@ -0,0 +1,2176 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libdhcp++_unittests
+
+# Utilize Linux Packet Filtering on Linux.
+@HAVE_GTEST_TRUE@@OS_LINUX_TRUE@am__append_2 = pkt_filter_lpf_unittest.cc
+
+# Utilize Berkeley Packet Filtering on BSD.
+@HAVE_GTEST_TRUE@@OS_BSD_TRUE@am__append_3 = pkt_filter_bpf_unittest.cc
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/dhcp/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libdhcp++_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libdhcptest_la_DEPENDENCIES = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+am__libdhcptest_la_SOURCES_DIST = iface_mgr_test_config.cc \
+ iface_mgr_test_config.h pkt_filter_test_stub.cc \
+ pkt_filter_test_stub.h pkt_filter6_test_stub.cc \
+ pkt_filter6_test_stub.h pkt_captures4.cc pkt_captures6.cc \
+ pkt_captures.h packet_queue_testutils.h
+@HAVE_GTEST_TRUE@am_libdhcptest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libdhcptest_la-iface_mgr_test_config.lo \
+@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_filter_test_stub.lo \
+@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_filter6_test_stub.lo \
+@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_captures4.lo \
+@HAVE_GTEST_TRUE@ libdhcptest_la-pkt_captures6.lo
+libdhcptest_la_OBJECTS = $(am_libdhcptest_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 =
+libdhcptest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libdhcptest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libdhcptest_la_rpath =
+am__libdhcp___unittests_SOURCES_DIST = run_unittests.cc \
+ classify_unittest.cc duid_factory_unittest.cc \
+ hwaddr_unittest.cc iface_mgr_unittest.cc \
+ iface_mgr_test_config.cc iface_mgr_test_config.h \
+ libdhcp++_unittest.cc opaque_data_tuple_unittest.cc \
+ option4_addrlst_unittest.cc option4_client_fqdn_unittest.cc \
+ option4_dnr_unittest.cc option6_addrlst_unittest.cc \
+ option6_client_fqdn_unittest.cc option6_auth_unittest.cc \
+ option6_dnr_unittest.cc option6_ia_unittest.cc \
+ option6_iaaddr_unittest.cc option6_iaprefix_unittest.cc \
+ option6_pdexclude_unittest.cc option6_status_code_unittest.cc \
+ option_int_unittest.cc option_int_array_unittest.cc \
+ option_data_types_unittest.cc option_definition_unittest.cc \
+ option_copy_unittest.cc option_custom_unittest.cc \
+ option_opaque_data_tuples_unittest.cc option_unittest.cc \
+ option_space_unittest.cc option_string_unittest.cc \
+ option_vendor_unittest.cc option_vendor_class_unittest.cc \
+ pkt_captures4.cc pkt_captures6.cc pkt_captures.h \
+ packet_queue4_unittest.cc packet_queue6_unittest.cc \
+ packet_queue_mgr4_unittest.cc packet_queue_mgr6_unittest.cc \
+ packet_queue_testutils.h pkt4_unittest.cc pkt6_unittest.cc \
+ pkt4o6_unittest.cc pkt_filter_unittest.cc \
+ pkt_filter_inet_unittest.cc pkt_filter_inet6_unittest.cc \
+ pkt_filter_test_stub.cc pkt_filter_test_stub.h \
+ pkt_filter6_test_stub.cc pkt_filter_test_utils.h \
+ pkt_filter_test_utils.cc pkt_filter6_test_utils.h \
+ pkt_filter6_test_utils.cc pkt_filter_lpf_unittest.cc \
+ pkt_filter_bpf_unittest.cc protocol_util_unittest.cc \
+ duid_unittest.cc
+@HAVE_GTEST_TRUE@@OS_LINUX_TRUE@am__objects_1 = libdhcp___unittests-pkt_filter_lpf_unittest.$(OBJEXT)
+@HAVE_GTEST_TRUE@@OS_BSD_TRUE@am__objects_2 = libdhcp___unittests-pkt_filter_bpf_unittest.$(OBJEXT)
+@HAVE_GTEST_TRUE@am_libdhcp___unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-classify_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-duid_factory_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-hwaddr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-iface_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-iface_mgr_test_config.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-libdhcp++_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-opaque_data_tuple_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_addrlst_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_client_fqdn_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option4_dnr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_addrlst_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_client_fqdn_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_auth_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_dnr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_ia_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_iaaddr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_iaprefix_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_pdexclude_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option6_status_code_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_int_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_int_array_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_data_types_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_definition_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_copy_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_custom_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_opaque_data_tuples_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_space_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_string_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_vendor_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-option_vendor_class_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_captures4.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_captures6.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue_mgr4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-packet_queue_mgr6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt4o6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_inet_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_inet6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_test_stub.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter6_test_stub.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter_test_utils.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-pkt_filter6_test_utils.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-protocol_util_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp___unittests-duid_unittest.$(OBJEXT)
+libdhcp___unittests_OBJECTS = $(am_libdhcp___unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libdhcp___unittests_DEPENDENCIES = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+libdhcp___unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libdhcp___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)/libdhcp___unittests-classify_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po \
+ ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po \
+ ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po \
+ ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po \
+ ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo \
+ ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo \
+ ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo \
+ ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo \
+ ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.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 = $(libdhcptest_la_SOURCES) $(libdhcp___unittests_SOURCES)
+DIST_SOURCES = $(am__libdhcptest_la_SOURCES_DIST) \
+ $(am__libdhcp___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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp/tests\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Creates a library which is shared by various unit tests which require
+# configuration of fake interfaces.
+# The libdhcp++ does not link with this library because this would cause
+# build failures being a result of concurrency between build of this
+# library and the unit tests when make -j option was used, as they
+# are built out of the same makefile. Instead, the libdhcp++ tests link to
+# files belonging to this library, directly.
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libdhcptest.la
+@HAVE_GTEST_TRUE@libdhcptest_la_SOURCES = iface_mgr_test_config.cc \
+@HAVE_GTEST_TRUE@ iface_mgr_test_config.h \
+@HAVE_GTEST_TRUE@ pkt_filter_test_stub.cc \
+@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \
+@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.cc \
+@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.h pkt_captures4.cc \
+@HAVE_GTEST_TRUE@ pkt_captures6.cc pkt_captures.h \
+@HAVE_GTEST_TRUE@ packet_queue_testutils.h
+@HAVE_GTEST_TRUE@libdhcptest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdhcptest_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libdhcptest_la_LDFLAGS = $(AM_LDFLAGS)
+@HAVE_GTEST_TRUE@libdhcptest_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+@HAVE_GTEST_TRUE@libdhcp___unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ classify_unittest.cc duid_factory_unittest.cc \
+@HAVE_GTEST_TRUE@ hwaddr_unittest.cc iface_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ iface_mgr_test_config.cc \
+@HAVE_GTEST_TRUE@ iface_mgr_test_config.h libdhcp++_unittest.cc \
+@HAVE_GTEST_TRUE@ opaque_data_tuple_unittest.cc \
+@HAVE_GTEST_TRUE@ option4_addrlst_unittest.cc \
+@HAVE_GTEST_TRUE@ option4_client_fqdn_unittest.cc \
+@HAVE_GTEST_TRUE@ option4_dnr_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_addrlst_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_client_fqdn_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_auth_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_dnr_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_ia_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_iaaddr_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_iaprefix_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_pdexclude_unittest.cc \
+@HAVE_GTEST_TRUE@ option6_status_code_unittest.cc \
+@HAVE_GTEST_TRUE@ option_int_unittest.cc \
+@HAVE_GTEST_TRUE@ option_int_array_unittest.cc \
+@HAVE_GTEST_TRUE@ option_data_types_unittest.cc \
+@HAVE_GTEST_TRUE@ option_definition_unittest.cc \
+@HAVE_GTEST_TRUE@ option_copy_unittest.cc \
+@HAVE_GTEST_TRUE@ option_custom_unittest.cc \
+@HAVE_GTEST_TRUE@ option_opaque_data_tuples_unittest.cc \
+@HAVE_GTEST_TRUE@ option_unittest.cc option_space_unittest.cc \
+@HAVE_GTEST_TRUE@ option_string_unittest.cc \
+@HAVE_GTEST_TRUE@ option_vendor_unittest.cc \
+@HAVE_GTEST_TRUE@ option_vendor_class_unittest.cc \
+@HAVE_GTEST_TRUE@ pkt_captures4.cc pkt_captures6.cc \
+@HAVE_GTEST_TRUE@ pkt_captures.h packet_queue4_unittest.cc \
+@HAVE_GTEST_TRUE@ packet_queue6_unittest.cc \
+@HAVE_GTEST_TRUE@ packet_queue_mgr4_unittest.cc \
+@HAVE_GTEST_TRUE@ packet_queue_mgr6_unittest.cc \
+@HAVE_GTEST_TRUE@ packet_queue_testutils.h pkt4_unittest.cc \
+@HAVE_GTEST_TRUE@ pkt6_unittest.cc pkt4o6_unittest.cc \
+@HAVE_GTEST_TRUE@ pkt_filter_unittest.cc \
+@HAVE_GTEST_TRUE@ pkt_filter_inet_unittest.cc \
+@HAVE_GTEST_TRUE@ pkt_filter_inet6_unittest.cc \
+@HAVE_GTEST_TRUE@ pkt_filter_test_stub.cc \
+@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \
+@HAVE_GTEST_TRUE@ pkt_filter6_test_stub.cc \
+@HAVE_GTEST_TRUE@ pkt_filter_test_stub.h \
+@HAVE_GTEST_TRUE@ pkt_filter_test_utils.h \
+@HAVE_GTEST_TRUE@ pkt_filter_test_utils.cc \
+@HAVE_GTEST_TRUE@ pkt_filter6_test_utils.h \
+@HAVE_GTEST_TRUE@ pkt_filter6_test_utils.cc $(am__append_2) \
+@HAVE_GTEST_TRUE@ $(am__append_3) protocol_util_unittest.cc \
+@HAVE_GTEST_TRUE@ duid_unittest.cc
+@HAVE_GTEST_TRUE@libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libdhcp___unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libdhcp___unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdhcp___unittests_LDADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_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/dhcp/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcp/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
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libdhcptest.la: $(libdhcptest_la_OBJECTS) $(libdhcptest_la_DEPENDENCIES) $(EXTRA_libdhcptest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libdhcptest_la_LINK) $(am_libdhcptest_la_rpath) $(libdhcptest_la_OBJECTS) $(libdhcptest_la_LIBADD) $(LIBS)
+
+libdhcp++_unittests$(EXEEXT): $(libdhcp___unittests_OBJECTS) $(libdhcp___unittests_DEPENDENCIES) $(EXTRA_libdhcp___unittests_DEPENDENCIES)
+ @rm -f libdhcp++_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libdhcp___unittests_LINK) $(libdhcp___unittests_OBJECTS) $(libdhcp___unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp___unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.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)$(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 $@ $<
+
+libdhcptest_la-iface_mgr_test_config.lo: iface_mgr_test_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-iface_mgr_test_config.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Tpo -c -o libdhcptest_la-iface_mgr_test_config.lo `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcptest_la-iface_mgr_test_config.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-iface_mgr_test_config.lo `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc
+
+libdhcptest_la-pkt_filter_test_stub.lo: pkt_filter_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_filter_test_stub.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Tpo -c -o libdhcptest_la-pkt_filter_test_stub.lo `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcptest_la-pkt_filter_test_stub.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_filter_test_stub.lo `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc
+
+libdhcptest_la-pkt_filter6_test_stub.lo: pkt_filter6_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_filter6_test_stub.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Tpo -c -o libdhcptest_la-pkt_filter6_test_stub.lo `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcptest_la-pkt_filter6_test_stub.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_filter6_test_stub.lo `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc
+
+libdhcptest_la-pkt_captures4.lo: pkt_captures4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_captures4.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_captures4.Tpo -c -o libdhcptest_la-pkt_captures4.lo `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_captures4.Tpo $(DEPDIR)/libdhcptest_la-pkt_captures4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcptest_la-pkt_captures4.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_captures4.lo `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc
+
+libdhcptest_la-pkt_captures6.lo: pkt_captures6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcptest_la-pkt_captures6.lo -MD -MP -MF $(DEPDIR)/libdhcptest_la-pkt_captures6.Tpo -c -o libdhcptest_la-pkt_captures6.lo `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcptest_la-pkt_captures6.Tpo $(DEPDIR)/libdhcptest_la-pkt_captures6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcptest_la-pkt_captures6.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) $(libdhcptest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcptest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcptest_la-pkt_captures6.lo `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc
+
+libdhcp___unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo -c -o libdhcp___unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo $(DEPDIR)/libdhcp___unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp___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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libdhcp___unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-run_unittests.Tpo -c -o libdhcp___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)/libdhcp___unittests-run_unittests.Tpo $(DEPDIR)/libdhcp___unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp___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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libdhcp___unittests-classify_unittest.o: classify_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-classify_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo -c -o libdhcp___unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo $(DEPDIR)/libdhcp___unittests-classify_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='libdhcp___unittests-classify_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-classify_unittest.o `test -f 'classify_unittest.cc' || echo '$(srcdir)/'`classify_unittest.cc
+
+libdhcp___unittests-classify_unittest.obj: classify_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-classify_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo -c -o libdhcp___unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-classify_unittest.Tpo $(DEPDIR)/libdhcp___unittests-classify_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='classify_unittest.cc' object='libdhcp___unittests-classify_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-classify_unittest.obj `if test -f 'classify_unittest.cc'; then $(CYGPATH_W) 'classify_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/classify_unittest.cc'; fi`
+
+libdhcp___unittests-duid_factory_unittest.o: duid_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_factory_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo -c -o libdhcp___unittests-duid_factory_unittest.o `test -f 'duid_factory_unittest.cc' || echo '$(srcdir)/'`duid_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory_unittest.cc' object='libdhcp___unittests-duid_factory_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_factory_unittest.o `test -f 'duid_factory_unittest.cc' || echo '$(srcdir)/'`duid_factory_unittest.cc
+
+libdhcp___unittests-duid_factory_unittest.obj: duid_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_factory_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo -c -o libdhcp___unittests-duid_factory_unittest.obj `if test -f 'duid_factory_unittest.cc'; then $(CYGPATH_W) 'duid_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_factory_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_factory_unittest.cc' object='libdhcp___unittests-duid_factory_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_factory_unittest.obj `if test -f 'duid_factory_unittest.cc'; then $(CYGPATH_W) 'duid_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_factory_unittest.cc'; fi`
+
+libdhcp___unittests-hwaddr_unittest.o: hwaddr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-hwaddr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo -c -o libdhcp___unittests-hwaddr_unittest.o `test -f 'hwaddr_unittest.cc' || echo '$(srcdir)/'`hwaddr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr_unittest.cc' object='libdhcp___unittests-hwaddr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-hwaddr_unittest.o `test -f 'hwaddr_unittest.cc' || echo '$(srcdir)/'`hwaddr_unittest.cc
+
+libdhcp___unittests-hwaddr_unittest.obj: hwaddr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-hwaddr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo -c -o libdhcp___unittests-hwaddr_unittest.obj `if test -f 'hwaddr_unittest.cc'; then $(CYGPATH_W) 'hwaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hwaddr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hwaddr_unittest.cc' object='libdhcp___unittests-hwaddr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-hwaddr_unittest.obj `if test -f 'hwaddr_unittest.cc'; then $(CYGPATH_W) 'hwaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hwaddr_unittest.cc'; fi`
+
+libdhcp___unittests-iface_mgr_unittest.o: iface_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo -c -o libdhcp___unittests-iface_mgr_unittest.o `test -f 'iface_mgr_unittest.cc' || echo '$(srcdir)/'`iface_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_unittest.cc' object='libdhcp___unittests-iface_mgr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_unittest.o `test -f 'iface_mgr_unittest.cc' || echo '$(srcdir)/'`iface_mgr_unittest.cc
+
+libdhcp___unittests-iface_mgr_unittest.obj: iface_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo -c -o libdhcp___unittests-iface_mgr_unittest.obj `if test -f 'iface_mgr_unittest.cc'; then $(CYGPATH_W) 'iface_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_unittest.cc' object='libdhcp___unittests-iface_mgr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_unittest.obj `if test -f 'iface_mgr_unittest.cc'; then $(CYGPATH_W) 'iface_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_unittest.cc'; fi`
+
+libdhcp___unittests-iface_mgr_test_config.o: iface_mgr_test_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_test_config.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo -c -o libdhcp___unittests-iface_mgr_test_config.o `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcp___unittests-iface_mgr_test_config.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_test_config.o `test -f 'iface_mgr_test_config.cc' || echo '$(srcdir)/'`iface_mgr_test_config.cc
+
+libdhcp___unittests-iface_mgr_test_config.obj: iface_mgr_test_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-iface_mgr_test_config.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo -c -o libdhcp___unittests-iface_mgr_test_config.obj `if test -f 'iface_mgr_test_config.cc'; then $(CYGPATH_W) 'iface_mgr_test_config.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_test_config.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Tpo $(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iface_mgr_test_config.cc' object='libdhcp___unittests-iface_mgr_test_config.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-iface_mgr_test_config.obj `if test -f 'iface_mgr_test_config.cc'; then $(CYGPATH_W) 'iface_mgr_test_config.cc'; else $(CYGPATH_W) '$(srcdir)/iface_mgr_test_config.cc'; fi`
+
+libdhcp___unittests-libdhcp++_unittest.o: libdhcp++_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-libdhcp++_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo -c -o libdhcp___unittests-libdhcp++_unittest.o `test -f 'libdhcp++_unittest.cc' || echo '$(srcdir)/'`libdhcp++_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++_unittest.cc' object='libdhcp___unittests-libdhcp++_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-libdhcp++_unittest.o `test -f 'libdhcp++_unittest.cc' || echo '$(srcdir)/'`libdhcp++_unittest.cc
+
+libdhcp___unittests-libdhcp++_unittest.obj: libdhcp++_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-libdhcp++_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo -c -o libdhcp___unittests-libdhcp++_unittest.obj `if test -f 'libdhcp++_unittest.cc'; then $(CYGPATH_W) 'libdhcp++_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/libdhcp++_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Tpo $(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libdhcp++_unittest.cc' object='libdhcp___unittests-libdhcp++_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-libdhcp++_unittest.obj `if test -f 'libdhcp++_unittest.cc'; then $(CYGPATH_W) 'libdhcp++_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/libdhcp++_unittest.cc'; fi`
+
+libdhcp___unittests-opaque_data_tuple_unittest.o: opaque_data_tuple_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-opaque_data_tuple_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo -c -o libdhcp___unittests-opaque_data_tuple_unittest.o `test -f 'opaque_data_tuple_unittest.cc' || echo '$(srcdir)/'`opaque_data_tuple_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple_unittest.cc' object='libdhcp___unittests-opaque_data_tuple_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-opaque_data_tuple_unittest.o `test -f 'opaque_data_tuple_unittest.cc' || echo '$(srcdir)/'`opaque_data_tuple_unittest.cc
+
+libdhcp___unittests-opaque_data_tuple_unittest.obj: opaque_data_tuple_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-opaque_data_tuple_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo -c -o libdhcp___unittests-opaque_data_tuple_unittest.obj `if test -f 'opaque_data_tuple_unittest.cc'; then $(CYGPATH_W) 'opaque_data_tuple_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opaque_data_tuple_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Tpo $(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opaque_data_tuple_unittest.cc' object='libdhcp___unittests-opaque_data_tuple_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-opaque_data_tuple_unittest.obj `if test -f 'opaque_data_tuple_unittest.cc'; then $(CYGPATH_W) 'opaque_data_tuple_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opaque_data_tuple_unittest.cc'; fi`
+
+libdhcp___unittests-option4_addrlst_unittest.o: option4_addrlst_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_addrlst_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo -c -o libdhcp___unittests-option4_addrlst_unittest.o `test -f 'option4_addrlst_unittest.cc' || echo '$(srcdir)/'`option4_addrlst_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst_unittest.cc' object='libdhcp___unittests-option4_addrlst_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_addrlst_unittest.o `test -f 'option4_addrlst_unittest.cc' || echo '$(srcdir)/'`option4_addrlst_unittest.cc
+
+libdhcp___unittests-option4_addrlst_unittest.obj: option4_addrlst_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_addrlst_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo -c -o libdhcp___unittests-option4_addrlst_unittest.obj `if test -f 'option4_addrlst_unittest.cc'; then $(CYGPATH_W) 'option4_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_addrlst_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_addrlst_unittest.cc' object='libdhcp___unittests-option4_addrlst_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_addrlst_unittest.obj `if test -f 'option4_addrlst_unittest.cc'; then $(CYGPATH_W) 'option4_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_addrlst_unittest.cc'; fi`
+
+libdhcp___unittests-option4_client_fqdn_unittest.o: option4_client_fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_client_fqdn_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option4_client_fqdn_unittest.o `test -f 'option4_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option4_client_fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn_unittest.cc' object='libdhcp___unittests-option4_client_fqdn_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_client_fqdn_unittest.o `test -f 'option4_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option4_client_fqdn_unittest.cc
+
+libdhcp___unittests-option4_client_fqdn_unittest.obj: option4_client_fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_client_fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option4_client_fqdn_unittest.obj `if test -f 'option4_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option4_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_client_fqdn_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_client_fqdn_unittest.cc' object='libdhcp___unittests-option4_client_fqdn_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_client_fqdn_unittest.obj `if test -f 'option4_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option4_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_client_fqdn_unittest.cc'; fi`
+
+libdhcp___unittests-option4_dnr_unittest.o: option4_dnr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_dnr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo -c -o libdhcp___unittests-option4_dnr_unittest.o `test -f 'option4_dnr_unittest.cc' || echo '$(srcdir)/'`option4_dnr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_dnr_unittest.cc' object='libdhcp___unittests-option4_dnr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_dnr_unittest.o `test -f 'option4_dnr_unittest.cc' || echo '$(srcdir)/'`option4_dnr_unittest.cc
+
+libdhcp___unittests-option4_dnr_unittest.obj: option4_dnr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option4_dnr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo -c -o libdhcp___unittests-option4_dnr_unittest.obj `if test -f 'option4_dnr_unittest.cc'; then $(CYGPATH_W) 'option4_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_dnr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option4_dnr_unittest.cc' object='libdhcp___unittests-option4_dnr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option4_dnr_unittest.obj `if test -f 'option4_dnr_unittest.cc'; then $(CYGPATH_W) 'option4_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option4_dnr_unittest.cc'; fi`
+
+libdhcp___unittests-option6_addrlst_unittest.o: option6_addrlst_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_addrlst_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo -c -o libdhcp___unittests-option6_addrlst_unittest.o `test -f 'option6_addrlst_unittest.cc' || echo '$(srcdir)/'`option6_addrlst_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst_unittest.cc' object='libdhcp___unittests-option6_addrlst_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_addrlst_unittest.o `test -f 'option6_addrlst_unittest.cc' || echo '$(srcdir)/'`option6_addrlst_unittest.cc
+
+libdhcp___unittests-option6_addrlst_unittest.obj: option6_addrlst_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_addrlst_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo -c -o libdhcp___unittests-option6_addrlst_unittest.obj `if test -f 'option6_addrlst_unittest.cc'; then $(CYGPATH_W) 'option6_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_addrlst_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_addrlst_unittest.cc' object='libdhcp___unittests-option6_addrlst_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_addrlst_unittest.obj `if test -f 'option6_addrlst_unittest.cc'; then $(CYGPATH_W) 'option6_addrlst_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_addrlst_unittest.cc'; fi`
+
+libdhcp___unittests-option6_client_fqdn_unittest.o: option6_client_fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_client_fqdn_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option6_client_fqdn_unittest.o `test -f 'option6_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option6_client_fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn_unittest.cc' object='libdhcp___unittests-option6_client_fqdn_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_client_fqdn_unittest.o `test -f 'option6_client_fqdn_unittest.cc' || echo '$(srcdir)/'`option6_client_fqdn_unittest.cc
+
+libdhcp___unittests-option6_client_fqdn_unittest.obj: option6_client_fqdn_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_client_fqdn_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo -c -o libdhcp___unittests-option6_client_fqdn_unittest.obj `if test -f 'option6_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option6_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_client_fqdn_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_client_fqdn_unittest.cc' object='libdhcp___unittests-option6_client_fqdn_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_client_fqdn_unittest.obj `if test -f 'option6_client_fqdn_unittest.cc'; then $(CYGPATH_W) 'option6_client_fqdn_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_client_fqdn_unittest.cc'; fi`
+
+libdhcp___unittests-option6_auth_unittest.o: option6_auth_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_auth_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo -c -o libdhcp___unittests-option6_auth_unittest.o `test -f 'option6_auth_unittest.cc' || echo '$(srcdir)/'`option6_auth_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth_unittest.cc' object='libdhcp___unittests-option6_auth_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_auth_unittest.o `test -f 'option6_auth_unittest.cc' || echo '$(srcdir)/'`option6_auth_unittest.cc
+
+libdhcp___unittests-option6_auth_unittest.obj: option6_auth_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_auth_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo -c -o libdhcp___unittests-option6_auth_unittest.obj `if test -f 'option6_auth_unittest.cc'; then $(CYGPATH_W) 'option6_auth_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_auth_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_auth_unittest.cc' object='libdhcp___unittests-option6_auth_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_auth_unittest.obj `if test -f 'option6_auth_unittest.cc'; then $(CYGPATH_W) 'option6_auth_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_auth_unittest.cc'; fi`
+
+libdhcp___unittests-option6_dnr_unittest.o: option6_dnr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_dnr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo -c -o libdhcp___unittests-option6_dnr_unittest.o `test -f 'option6_dnr_unittest.cc' || echo '$(srcdir)/'`option6_dnr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_dnr_unittest.cc' object='libdhcp___unittests-option6_dnr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_dnr_unittest.o `test -f 'option6_dnr_unittest.cc' || echo '$(srcdir)/'`option6_dnr_unittest.cc
+
+libdhcp___unittests-option6_dnr_unittest.obj: option6_dnr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_dnr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo -c -o libdhcp___unittests-option6_dnr_unittest.obj `if test -f 'option6_dnr_unittest.cc'; then $(CYGPATH_W) 'option6_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_dnr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_dnr_unittest.cc' object='libdhcp___unittests-option6_dnr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_dnr_unittest.obj `if test -f 'option6_dnr_unittest.cc'; then $(CYGPATH_W) 'option6_dnr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_dnr_unittest.cc'; fi`
+
+libdhcp___unittests-option6_ia_unittest.o: option6_ia_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_ia_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo -c -o libdhcp___unittests-option6_ia_unittest.o `test -f 'option6_ia_unittest.cc' || echo '$(srcdir)/'`option6_ia_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia_unittest.cc' object='libdhcp___unittests-option6_ia_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_ia_unittest.o `test -f 'option6_ia_unittest.cc' || echo '$(srcdir)/'`option6_ia_unittest.cc
+
+libdhcp___unittests-option6_ia_unittest.obj: option6_ia_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_ia_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo -c -o libdhcp___unittests-option6_ia_unittest.obj `if test -f 'option6_ia_unittest.cc'; then $(CYGPATH_W) 'option6_ia_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_ia_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_ia_unittest.cc' object='libdhcp___unittests-option6_ia_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_ia_unittest.obj `if test -f 'option6_ia_unittest.cc'; then $(CYGPATH_W) 'option6_ia_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_ia_unittest.cc'; fi`
+
+libdhcp___unittests-option6_iaaddr_unittest.o: option6_iaaddr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaaddr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo -c -o libdhcp___unittests-option6_iaaddr_unittest.o `test -f 'option6_iaaddr_unittest.cc' || echo '$(srcdir)/'`option6_iaaddr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr_unittest.cc' object='libdhcp___unittests-option6_iaaddr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaaddr_unittest.o `test -f 'option6_iaaddr_unittest.cc' || echo '$(srcdir)/'`option6_iaaddr_unittest.cc
+
+libdhcp___unittests-option6_iaaddr_unittest.obj: option6_iaaddr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaaddr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo -c -o libdhcp___unittests-option6_iaaddr_unittest.obj `if test -f 'option6_iaaddr_unittest.cc'; then $(CYGPATH_W) 'option6_iaaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaaddr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaaddr_unittest.cc' object='libdhcp___unittests-option6_iaaddr_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaaddr_unittest.obj `if test -f 'option6_iaaddr_unittest.cc'; then $(CYGPATH_W) 'option6_iaaddr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaaddr_unittest.cc'; fi`
+
+libdhcp___unittests-option6_iaprefix_unittest.o: option6_iaprefix_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaprefix_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo -c -o libdhcp___unittests-option6_iaprefix_unittest.o `test -f 'option6_iaprefix_unittest.cc' || echo '$(srcdir)/'`option6_iaprefix_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix_unittest.cc' object='libdhcp___unittests-option6_iaprefix_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaprefix_unittest.o `test -f 'option6_iaprefix_unittest.cc' || echo '$(srcdir)/'`option6_iaprefix_unittest.cc
+
+libdhcp___unittests-option6_iaprefix_unittest.obj: option6_iaprefix_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_iaprefix_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo -c -o libdhcp___unittests-option6_iaprefix_unittest.obj `if test -f 'option6_iaprefix_unittest.cc'; then $(CYGPATH_W) 'option6_iaprefix_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaprefix_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_iaprefix_unittest.cc' object='libdhcp___unittests-option6_iaprefix_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_iaprefix_unittest.obj `if test -f 'option6_iaprefix_unittest.cc'; then $(CYGPATH_W) 'option6_iaprefix_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_iaprefix_unittest.cc'; fi`
+
+libdhcp___unittests-option6_pdexclude_unittest.o: option6_pdexclude_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_pdexclude_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo -c -o libdhcp___unittests-option6_pdexclude_unittest.o `test -f 'option6_pdexclude_unittest.cc' || echo '$(srcdir)/'`option6_pdexclude_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude_unittest.cc' object='libdhcp___unittests-option6_pdexclude_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_pdexclude_unittest.o `test -f 'option6_pdexclude_unittest.cc' || echo '$(srcdir)/'`option6_pdexclude_unittest.cc
+
+libdhcp___unittests-option6_pdexclude_unittest.obj: option6_pdexclude_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_pdexclude_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo -c -o libdhcp___unittests-option6_pdexclude_unittest.obj `if test -f 'option6_pdexclude_unittest.cc'; then $(CYGPATH_W) 'option6_pdexclude_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_pdexclude_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_pdexclude_unittest.cc' object='libdhcp___unittests-option6_pdexclude_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_pdexclude_unittest.obj `if test -f 'option6_pdexclude_unittest.cc'; then $(CYGPATH_W) 'option6_pdexclude_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_pdexclude_unittest.cc'; fi`
+
+libdhcp___unittests-option6_status_code_unittest.o: option6_status_code_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_status_code_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo -c -o libdhcp___unittests-option6_status_code_unittest.o `test -f 'option6_status_code_unittest.cc' || echo '$(srcdir)/'`option6_status_code_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code_unittest.cc' object='libdhcp___unittests-option6_status_code_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_status_code_unittest.o `test -f 'option6_status_code_unittest.cc' || echo '$(srcdir)/'`option6_status_code_unittest.cc
+
+libdhcp___unittests-option6_status_code_unittest.obj: option6_status_code_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option6_status_code_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo -c -o libdhcp___unittests-option6_status_code_unittest.obj `if test -f 'option6_status_code_unittest.cc'; then $(CYGPATH_W) 'option6_status_code_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_status_code_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option6_status_code_unittest.cc' object='libdhcp___unittests-option6_status_code_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option6_status_code_unittest.obj `if test -f 'option6_status_code_unittest.cc'; then $(CYGPATH_W) 'option6_status_code_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option6_status_code_unittest.cc'; fi`
+
+libdhcp___unittests-option_int_unittest.o: option_int_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo -c -o libdhcp___unittests-option_int_unittest.o `test -f 'option_int_unittest.cc' || echo '$(srcdir)/'`option_int_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_unittest.cc' object='libdhcp___unittests-option_int_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_unittest.o `test -f 'option_int_unittest.cc' || echo '$(srcdir)/'`option_int_unittest.cc
+
+libdhcp___unittests-option_int_unittest.obj: option_int_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo -c -o libdhcp___unittests-option_int_unittest.obj `if test -f 'option_int_unittest.cc'; then $(CYGPATH_W) 'option_int_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_unittest.cc' object='libdhcp___unittests-option_int_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_unittest.obj `if test -f 'option_int_unittest.cc'; then $(CYGPATH_W) 'option_int_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_unittest.cc'; fi`
+
+libdhcp___unittests-option_int_array_unittest.o: option_int_array_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_array_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo -c -o libdhcp___unittests-option_int_array_unittest.o `test -f 'option_int_array_unittest.cc' || echo '$(srcdir)/'`option_int_array_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_array_unittest.cc' object='libdhcp___unittests-option_int_array_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_array_unittest.o `test -f 'option_int_array_unittest.cc' || echo '$(srcdir)/'`option_int_array_unittest.cc
+
+libdhcp___unittests-option_int_array_unittest.obj: option_int_array_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_int_array_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo -c -o libdhcp___unittests-option_int_array_unittest.obj `if test -f 'option_int_array_unittest.cc'; then $(CYGPATH_W) 'option_int_array_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_array_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_int_array_unittest.cc' object='libdhcp___unittests-option_int_array_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_int_array_unittest.obj `if test -f 'option_int_array_unittest.cc'; then $(CYGPATH_W) 'option_int_array_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_int_array_unittest.cc'; fi`
+
+libdhcp___unittests-option_data_types_unittest.o: option_data_types_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_data_types_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo -c -o libdhcp___unittests-option_data_types_unittest.o `test -f 'option_data_types_unittest.cc' || echo '$(srcdir)/'`option_data_types_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types_unittest.cc' object='libdhcp___unittests-option_data_types_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_data_types_unittest.o `test -f 'option_data_types_unittest.cc' || echo '$(srcdir)/'`option_data_types_unittest.cc
+
+libdhcp___unittests-option_data_types_unittest.obj: option_data_types_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_data_types_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo -c -o libdhcp___unittests-option_data_types_unittest.obj `if test -f 'option_data_types_unittest.cc'; then $(CYGPATH_W) 'option_data_types_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_data_types_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_data_types_unittest.cc' object='libdhcp___unittests-option_data_types_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_data_types_unittest.obj `if test -f 'option_data_types_unittest.cc'; then $(CYGPATH_W) 'option_data_types_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_data_types_unittest.cc'; fi`
+
+libdhcp___unittests-option_definition_unittest.o: option_definition_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_definition_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo -c -o libdhcp___unittests-option_definition_unittest.o `test -f 'option_definition_unittest.cc' || echo '$(srcdir)/'`option_definition_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition_unittest.cc' object='libdhcp___unittests-option_definition_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_definition_unittest.o `test -f 'option_definition_unittest.cc' || echo '$(srcdir)/'`option_definition_unittest.cc
+
+libdhcp___unittests-option_definition_unittest.obj: option_definition_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_definition_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo -c -o libdhcp___unittests-option_definition_unittest.obj `if test -f 'option_definition_unittest.cc'; then $(CYGPATH_W) 'option_definition_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_definition_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_definition_unittest.cc' object='libdhcp___unittests-option_definition_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_definition_unittest.obj `if test -f 'option_definition_unittest.cc'; then $(CYGPATH_W) 'option_definition_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_definition_unittest.cc'; fi`
+
+libdhcp___unittests-option_copy_unittest.o: option_copy_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_copy_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo -c -o libdhcp___unittests-option_copy_unittest.o `test -f 'option_copy_unittest.cc' || echo '$(srcdir)/'`option_copy_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_copy_unittest.cc' object='libdhcp___unittests-option_copy_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_copy_unittest.o `test -f 'option_copy_unittest.cc' || echo '$(srcdir)/'`option_copy_unittest.cc
+
+libdhcp___unittests-option_copy_unittest.obj: option_copy_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_copy_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo -c -o libdhcp___unittests-option_copy_unittest.obj `if test -f 'option_copy_unittest.cc'; then $(CYGPATH_W) 'option_copy_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_copy_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_copy_unittest.cc' object='libdhcp___unittests-option_copy_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_copy_unittest.obj `if test -f 'option_copy_unittest.cc'; then $(CYGPATH_W) 'option_copy_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_copy_unittest.cc'; fi`
+
+libdhcp___unittests-option_custom_unittest.o: option_custom_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_custom_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo -c -o libdhcp___unittests-option_custom_unittest.o `test -f 'option_custom_unittest.cc' || echo '$(srcdir)/'`option_custom_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom_unittest.cc' object='libdhcp___unittests-option_custom_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_custom_unittest.o `test -f 'option_custom_unittest.cc' || echo '$(srcdir)/'`option_custom_unittest.cc
+
+libdhcp___unittests-option_custom_unittest.obj: option_custom_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_custom_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo -c -o libdhcp___unittests-option_custom_unittest.obj `if test -f 'option_custom_unittest.cc'; then $(CYGPATH_W) 'option_custom_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_custom_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_custom_unittest.cc' object='libdhcp___unittests-option_custom_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_custom_unittest.obj `if test -f 'option_custom_unittest.cc'; then $(CYGPATH_W) 'option_custom_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_custom_unittest.cc'; fi`
+
+libdhcp___unittests-option_opaque_data_tuples_unittest.o: option_opaque_data_tuples_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_opaque_data_tuples_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.o `test -f 'option_opaque_data_tuples_unittest.cc' || echo '$(srcdir)/'`option_opaque_data_tuples_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples_unittest.cc' object='libdhcp___unittests-option_opaque_data_tuples_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.o `test -f 'option_opaque_data_tuples_unittest.cc' || echo '$(srcdir)/'`option_opaque_data_tuples_unittest.cc
+
+libdhcp___unittests-option_opaque_data_tuples_unittest.obj: option_opaque_data_tuples_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_opaque_data_tuples_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.obj `if test -f 'option_opaque_data_tuples_unittest.cc'; then $(CYGPATH_W) 'option_opaque_data_tuples_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_opaque_data_tuples_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_opaque_data_tuples_unittest.cc' object='libdhcp___unittests-option_opaque_data_tuples_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_opaque_data_tuples_unittest.obj `if test -f 'option_opaque_data_tuples_unittest.cc'; then $(CYGPATH_W) 'option_opaque_data_tuples_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_opaque_data_tuples_unittest.cc'; fi`
+
+libdhcp___unittests-option_unittest.o: option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo -c -o libdhcp___unittests-option_unittest.o `test -f 'option_unittest.cc' || echo '$(srcdir)/'`option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_unittest.cc' object='libdhcp___unittests-option_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_unittest.o `test -f 'option_unittest.cc' || echo '$(srcdir)/'`option_unittest.cc
+
+libdhcp___unittests-option_unittest.obj: option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo -c -o libdhcp___unittests-option_unittest.obj `if test -f 'option_unittest.cc'; then $(CYGPATH_W) 'option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_unittest.cc' object='libdhcp___unittests-option_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_unittest.obj `if test -f 'option_unittest.cc'; then $(CYGPATH_W) 'option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_unittest.cc'; fi`
+
+libdhcp___unittests-option_space_unittest.o: option_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_space_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo -c -o libdhcp___unittests-option_space_unittest.o `test -f 'option_space_unittest.cc' || echo '$(srcdir)/'`option_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_space_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space_unittest.cc' object='libdhcp___unittests-option_space_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_space_unittest.o `test -f 'option_space_unittest.cc' || echo '$(srcdir)/'`option_space_unittest.cc
+
+libdhcp___unittests-option_space_unittest.obj: option_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_space_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo -c -o libdhcp___unittests-option_space_unittest.obj `if test -f 'option_space_unittest.cc'; then $(CYGPATH_W) 'option_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_space_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_space_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_space_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_space_unittest.cc' object='libdhcp___unittests-option_space_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_space_unittest.obj `if test -f 'option_space_unittest.cc'; then $(CYGPATH_W) 'option_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_space_unittest.cc'; fi`
+
+libdhcp___unittests-option_string_unittest.o: option_string_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_string_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo -c -o libdhcp___unittests-option_string_unittest.o `test -f 'option_string_unittest.cc' || echo '$(srcdir)/'`option_string_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_string_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string_unittest.cc' object='libdhcp___unittests-option_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_string_unittest.o `test -f 'option_string_unittest.cc' || echo '$(srcdir)/'`option_string_unittest.cc
+
+libdhcp___unittests-option_string_unittest.obj: option_string_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_string_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo -c -o libdhcp___unittests-option_string_unittest.obj `if test -f 'option_string_unittest.cc'; then $(CYGPATH_W) 'option_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_string_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_string_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_string_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_string_unittest.cc' object='libdhcp___unittests-option_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_string_unittest.obj `if test -f 'option_string_unittest.cc'; then $(CYGPATH_W) 'option_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_string_unittest.cc'; fi`
+
+libdhcp___unittests-option_vendor_unittest.o: option_vendor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo -c -o libdhcp___unittests-option_vendor_unittest.o `test -f 'option_vendor_unittest.cc' || echo '$(srcdir)/'`option_vendor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_unittest.cc' object='libdhcp___unittests-option_vendor_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_unittest.o `test -f 'option_vendor_unittest.cc' || echo '$(srcdir)/'`option_vendor_unittest.cc
+
+libdhcp___unittests-option_vendor_unittest.obj: option_vendor_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo -c -o libdhcp___unittests-option_vendor_unittest.obj `if test -f 'option_vendor_unittest.cc'; then $(CYGPATH_W) 'option_vendor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_unittest.cc' object='libdhcp___unittests-option_vendor_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_unittest.obj `if test -f 'option_vendor_unittest.cc'; then $(CYGPATH_W) 'option_vendor_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_unittest.cc'; fi`
+
+libdhcp___unittests-option_vendor_class_unittest.o: option_vendor_class_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_class_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo -c -o libdhcp___unittests-option_vendor_class_unittest.o `test -f 'option_vendor_class_unittest.cc' || echo '$(srcdir)/'`option_vendor_class_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class_unittest.cc' object='libdhcp___unittests-option_vendor_class_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_class_unittest.o `test -f 'option_vendor_class_unittest.cc' || echo '$(srcdir)/'`option_vendor_class_unittest.cc
+
+libdhcp___unittests-option_vendor_class_unittest.obj: option_vendor_class_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-option_vendor_class_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo -c -o libdhcp___unittests-option_vendor_class_unittest.obj `if test -f 'option_vendor_class_unittest.cc'; then $(CYGPATH_W) 'option_vendor_class_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_class_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Tpo $(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='option_vendor_class_unittest.cc' object='libdhcp___unittests-option_vendor_class_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-option_vendor_class_unittest.obj `if test -f 'option_vendor_class_unittest.cc'; then $(CYGPATH_W) 'option_vendor_class_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/option_vendor_class_unittest.cc'; fi`
+
+libdhcp___unittests-pkt_captures4.o: pkt_captures4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures4.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo -c -o libdhcp___unittests-pkt_captures4.o `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures4.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcp___unittests-pkt_captures4.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures4.o `test -f 'pkt_captures4.cc' || echo '$(srcdir)/'`pkt_captures4.cc
+
+libdhcp___unittests-pkt_captures4.obj: pkt_captures4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures4.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo -c -o libdhcp___unittests-pkt_captures4.obj `if test -f 'pkt_captures4.cc'; then $(CYGPATH_W) 'pkt_captures4.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures4.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures4.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures4.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures4.cc' object='libdhcp___unittests-pkt_captures4.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures4.obj `if test -f 'pkt_captures4.cc'; then $(CYGPATH_W) 'pkt_captures4.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures4.cc'; fi`
+
+libdhcp___unittests-pkt_captures6.o: pkt_captures6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures6.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo -c -o libdhcp___unittests-pkt_captures6.o `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures6.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcp___unittests-pkt_captures6.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures6.o `test -f 'pkt_captures6.cc' || echo '$(srcdir)/'`pkt_captures6.cc
+
+libdhcp___unittests-pkt_captures6.obj: pkt_captures6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_captures6.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo -c -o libdhcp___unittests-pkt_captures6.obj `if test -f 'pkt_captures6.cc'; then $(CYGPATH_W) 'pkt_captures6.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures6.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_captures6.Tpo $(DEPDIR)/libdhcp___unittests-pkt_captures6.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_captures6.cc' object='libdhcp___unittests-pkt_captures6.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_captures6.obj `if test -f 'pkt_captures6.cc'; then $(CYGPATH_W) 'pkt_captures6.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_captures6.cc'; fi`
+
+libdhcp___unittests-packet_queue4_unittest.o: packet_queue4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo -c -o libdhcp___unittests-packet_queue4_unittest.o `test -f 'packet_queue4_unittest.cc' || echo '$(srcdir)/'`packet_queue4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue4_unittest.cc' object='libdhcp___unittests-packet_queue4_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue4_unittest.o `test -f 'packet_queue4_unittest.cc' || echo '$(srcdir)/'`packet_queue4_unittest.cc
+
+libdhcp___unittests-packet_queue4_unittest.obj: packet_queue4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo -c -o libdhcp___unittests-packet_queue4_unittest.obj `if test -f 'packet_queue4_unittest.cc'; then $(CYGPATH_W) 'packet_queue4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue4_unittest.cc' object='libdhcp___unittests-packet_queue4_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue4_unittest.obj `if test -f 'packet_queue4_unittest.cc'; then $(CYGPATH_W) 'packet_queue4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue4_unittest.cc'; fi`
+
+libdhcp___unittests-packet_queue6_unittest.o: packet_queue6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo -c -o libdhcp___unittests-packet_queue6_unittest.o `test -f 'packet_queue6_unittest.cc' || echo '$(srcdir)/'`packet_queue6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue6_unittest.cc' object='libdhcp___unittests-packet_queue6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue6_unittest.o `test -f 'packet_queue6_unittest.cc' || echo '$(srcdir)/'`packet_queue6_unittest.cc
+
+libdhcp___unittests-packet_queue6_unittest.obj: packet_queue6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo -c -o libdhcp___unittests-packet_queue6_unittest.obj `if test -f 'packet_queue6_unittest.cc'; then $(CYGPATH_W) 'packet_queue6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue6_unittest.cc' object='libdhcp___unittests-packet_queue6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue6_unittest.obj `if test -f 'packet_queue6_unittest.cc'; then $(CYGPATH_W) 'packet_queue6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue6_unittest.cc'; fi`
+
+libdhcp___unittests-packet_queue_mgr4_unittest.o: packet_queue_mgr4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr4_unittest.o `test -f 'packet_queue_mgr4_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4_unittest.cc' object='libdhcp___unittests-packet_queue_mgr4_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr4_unittest.o `test -f 'packet_queue_mgr4_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr4_unittest.cc
+
+libdhcp___unittests-packet_queue_mgr4_unittest.obj: packet_queue_mgr4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr4_unittest.obj `if test -f 'packet_queue_mgr4_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr4_unittest.cc' object='libdhcp___unittests-packet_queue_mgr4_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr4_unittest.obj `if test -f 'packet_queue_mgr4_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr4_unittest.cc'; fi`
+
+libdhcp___unittests-packet_queue_mgr6_unittest.o: packet_queue_mgr6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr6_unittest.o `test -f 'packet_queue_mgr6_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6_unittest.cc' object='libdhcp___unittests-packet_queue_mgr6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr6_unittest.o `test -f 'packet_queue_mgr6_unittest.cc' || echo '$(srcdir)/'`packet_queue_mgr6_unittest.cc
+
+libdhcp___unittests-packet_queue_mgr6_unittest.obj: packet_queue_mgr6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-packet_queue_mgr6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo -c -o libdhcp___unittests-packet_queue_mgr6_unittest.obj `if test -f 'packet_queue_mgr6_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='packet_queue_mgr6_unittest.cc' object='libdhcp___unittests-packet_queue_mgr6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-packet_queue_mgr6_unittest.obj `if test -f 'packet_queue_mgr6_unittest.cc'; then $(CYGPATH_W) 'packet_queue_mgr6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/packet_queue_mgr6_unittest.cc'; fi`
+
+libdhcp___unittests-pkt4_unittest.o: pkt4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo -c -o libdhcp___unittests-pkt4_unittest.o `test -f 'pkt4_unittest.cc' || echo '$(srcdir)/'`pkt4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4_unittest.cc' object='libdhcp___unittests-pkt4_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4_unittest.o `test -f 'pkt4_unittest.cc' || echo '$(srcdir)/'`pkt4_unittest.cc
+
+libdhcp___unittests-pkt4_unittest.obj: pkt4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo -c -o libdhcp___unittests-pkt4_unittest.obj `if test -f 'pkt4_unittest.cc'; then $(CYGPATH_W) 'pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4_unittest.cc' object='libdhcp___unittests-pkt4_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4_unittest.obj `if test -f 'pkt4_unittest.cc'; then $(CYGPATH_W) 'pkt4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4_unittest.cc'; fi`
+
+libdhcp___unittests-pkt6_unittest.o: pkt6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo -c -o libdhcp___unittests-pkt6_unittest.o `test -f 'pkt6_unittest.cc' || echo '$(srcdir)/'`pkt6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6_unittest.cc' object='libdhcp___unittests-pkt6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt6_unittest.o `test -f 'pkt6_unittest.cc' || echo '$(srcdir)/'`pkt6_unittest.cc
+
+libdhcp___unittests-pkt6_unittest.obj: pkt6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo -c -o libdhcp___unittests-pkt6_unittest.obj `if test -f 'pkt6_unittest.cc'; then $(CYGPATH_W) 'pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt6_unittest.cc' object='libdhcp___unittests-pkt6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt6_unittest.obj `if test -f 'pkt6_unittest.cc'; then $(CYGPATH_W) 'pkt6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt6_unittest.cc'; fi`
+
+libdhcp___unittests-pkt4o6_unittest.o: pkt4o6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4o6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo -c -o libdhcp___unittests-pkt4o6_unittest.o `test -f 'pkt4o6_unittest.cc' || echo '$(srcdir)/'`pkt4o6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6_unittest.cc' object='libdhcp___unittests-pkt4o6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4o6_unittest.o `test -f 'pkt4o6_unittest.cc' || echo '$(srcdir)/'`pkt4o6_unittest.cc
+
+libdhcp___unittests-pkt4o6_unittest.obj: pkt4o6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt4o6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo -c -o libdhcp___unittests-pkt4o6_unittest.obj `if test -f 'pkt4o6_unittest.cc'; then $(CYGPATH_W) 'pkt4o6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4o6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt4o6_unittest.cc' object='libdhcp___unittests-pkt4o6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt4o6_unittest.obj `if test -f 'pkt4o6_unittest.cc'; then $(CYGPATH_W) 'pkt4o6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt4o6_unittest.cc'; fi`
+
+libdhcp___unittests-pkt_filter_unittest.o: pkt_filter_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_unittest.o `test -f 'pkt_filter_unittest.cc' || echo '$(srcdir)/'`pkt_filter_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_unittest.cc' object='libdhcp___unittests-pkt_filter_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_unittest.o `test -f 'pkt_filter_unittest.cc' || echo '$(srcdir)/'`pkt_filter_unittest.cc
+
+libdhcp___unittests-pkt_filter_unittest.obj: pkt_filter_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_unittest.obj `if test -f 'pkt_filter_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_unittest.cc' object='libdhcp___unittests-pkt_filter_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_unittest.obj `if test -f 'pkt_filter_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_unittest.cc'; fi`
+
+libdhcp___unittests-pkt_filter_inet_unittest.o: pkt_filter_inet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet_unittest.o `test -f 'pkt_filter_inet_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet_unittest.cc' object='libdhcp___unittests-pkt_filter_inet_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet_unittest.o `test -f 'pkt_filter_inet_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet_unittest.cc
+
+libdhcp___unittests-pkt_filter_inet_unittest.obj: pkt_filter_inet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet_unittest.obj `if test -f 'pkt_filter_inet_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet_unittest.cc' object='libdhcp___unittests-pkt_filter_inet_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet_unittest.obj `if test -f 'pkt_filter_inet_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet_unittest.cc'; fi`
+
+libdhcp___unittests-pkt_filter_inet6_unittest.o: pkt_filter_inet6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet6_unittest.o `test -f 'pkt_filter_inet6_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6_unittest.cc' object='libdhcp___unittests-pkt_filter_inet6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet6_unittest.o `test -f 'pkt_filter_inet6_unittest.cc' || echo '$(srcdir)/'`pkt_filter_inet6_unittest.cc
+
+libdhcp___unittests-pkt_filter_inet6_unittest.obj: pkt_filter_inet6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_inet6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_inet6_unittest.obj `if test -f 'pkt_filter_inet6_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_inet6_unittest.cc' object='libdhcp___unittests-pkt_filter_inet6_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_inet6_unittest.obj `if test -f 'pkt_filter_inet6_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_inet6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_inet6_unittest.cc'; fi`
+
+libdhcp___unittests-pkt_filter_test_stub.o: pkt_filter_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_stub.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter_test_stub.o `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcp___unittests-pkt_filter_test_stub.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_stub.o `test -f 'pkt_filter_test_stub.cc' || echo '$(srcdir)/'`pkt_filter_test_stub.cc
+
+libdhcp___unittests-pkt_filter_test_stub.obj: pkt_filter_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_stub.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter_test_stub.obj `if test -f 'pkt_filter_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_stub.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_stub.cc' object='libdhcp___unittests-pkt_filter_test_stub.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_stub.obj `if test -f 'pkt_filter_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_stub.cc'; fi`
+
+libdhcp___unittests-pkt_filter6_test_stub.o: pkt_filter6_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_stub.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter6_test_stub.o `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcp___unittests-pkt_filter6_test_stub.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_stub.o `test -f 'pkt_filter6_test_stub.cc' || echo '$(srcdir)/'`pkt_filter6_test_stub.cc
+
+libdhcp___unittests-pkt_filter6_test_stub.obj: pkt_filter6_test_stub.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_stub.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo -c -o libdhcp___unittests-pkt_filter6_test_stub.obj `if test -f 'pkt_filter6_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter6_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_stub.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_stub.cc' object='libdhcp___unittests-pkt_filter6_test_stub.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_stub.obj `if test -f 'pkt_filter6_test_stub.cc'; then $(CYGPATH_W) 'pkt_filter6_test_stub.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_stub.cc'; fi`
+
+libdhcp___unittests-pkt_filter_test_utils.o: pkt_filter_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter_test_utils.o `test -f 'pkt_filter_test_utils.cc' || echo '$(srcdir)/'`pkt_filter_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_utils.cc' object='libdhcp___unittests-pkt_filter_test_utils.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_utils.o `test -f 'pkt_filter_test_utils.cc' || echo '$(srcdir)/'`pkt_filter_test_utils.cc
+
+libdhcp___unittests-pkt_filter_test_utils.obj: pkt_filter_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter_test_utils.obj `if test -f 'pkt_filter_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_utils.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_test_utils.cc' object='libdhcp___unittests-pkt_filter_test_utils.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_test_utils.obj `if test -f 'pkt_filter_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_test_utils.cc'; fi`
+
+libdhcp___unittests-pkt_filter6_test_utils.o: pkt_filter6_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter6_test_utils.o `test -f 'pkt_filter6_test_utils.cc' || echo '$(srcdir)/'`pkt_filter6_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_utils.cc' object='libdhcp___unittests-pkt_filter6_test_utils.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_utils.o `test -f 'pkt_filter6_test_utils.cc' || echo '$(srcdir)/'`pkt_filter6_test_utils.cc
+
+libdhcp___unittests-pkt_filter6_test_utils.obj: pkt_filter6_test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter6_test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo -c -o libdhcp___unittests-pkt_filter6_test_utils.obj `if test -f 'pkt_filter6_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_utils.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter6_test_utils.cc' object='libdhcp___unittests-pkt_filter6_test_utils.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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter6_test_utils.obj `if test -f 'pkt_filter6_test_utils.cc'; then $(CYGPATH_W) 'pkt_filter6_test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter6_test_utils.cc'; fi`
+
+libdhcp___unittests-pkt_filter_lpf_unittest.o: pkt_filter_lpf_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_lpf_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_lpf_unittest.o `test -f 'pkt_filter_lpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_lpf_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf_unittest.cc' object='libdhcp___unittests-pkt_filter_lpf_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_lpf_unittest.o `test -f 'pkt_filter_lpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_lpf_unittest.cc
+
+libdhcp___unittests-pkt_filter_lpf_unittest.obj: pkt_filter_lpf_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_lpf_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_lpf_unittest.obj `if test -f 'pkt_filter_lpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_lpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_lpf_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_lpf_unittest.cc' object='libdhcp___unittests-pkt_filter_lpf_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_lpf_unittest.obj `if test -f 'pkt_filter_lpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_lpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_lpf_unittest.cc'; fi`
+
+libdhcp___unittests-pkt_filter_bpf_unittest.o: pkt_filter_bpf_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_bpf_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_bpf_unittest.o `test -f 'pkt_filter_bpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_bpf_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf_unittest.cc' object='libdhcp___unittests-pkt_filter_bpf_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_bpf_unittest.o `test -f 'pkt_filter_bpf_unittest.cc' || echo '$(srcdir)/'`pkt_filter_bpf_unittest.cc
+
+libdhcp___unittests-pkt_filter_bpf_unittest.obj: pkt_filter_bpf_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-pkt_filter_bpf_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo -c -o libdhcp___unittests-pkt_filter_bpf_unittest.obj `if test -f 'pkt_filter_bpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_bpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_bpf_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Tpo $(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pkt_filter_bpf_unittest.cc' object='libdhcp___unittests-pkt_filter_bpf_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-pkt_filter_bpf_unittest.obj `if test -f 'pkt_filter_bpf_unittest.cc'; then $(CYGPATH_W) 'pkt_filter_bpf_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pkt_filter_bpf_unittest.cc'; fi`
+
+libdhcp___unittests-protocol_util_unittest.o: protocol_util_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-protocol_util_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo -c -o libdhcp___unittests-protocol_util_unittest.o `test -f 'protocol_util_unittest.cc' || echo '$(srcdir)/'`protocol_util_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util_unittest.cc' object='libdhcp___unittests-protocol_util_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-protocol_util_unittest.o `test -f 'protocol_util_unittest.cc' || echo '$(srcdir)/'`protocol_util_unittest.cc
+
+libdhcp___unittests-protocol_util_unittest.obj: protocol_util_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-protocol_util_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo -c -o libdhcp___unittests-protocol_util_unittest.obj `if test -f 'protocol_util_unittest.cc'; then $(CYGPATH_W) 'protocol_util_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/protocol_util_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Tpo $(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='protocol_util_unittest.cc' object='libdhcp___unittests-protocol_util_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-protocol_util_unittest.obj `if test -f 'protocol_util_unittest.cc'; then $(CYGPATH_W) 'protocol_util_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/protocol_util_unittest.cc'; fi`
+
+libdhcp___unittests-duid_unittest.o: duid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_unittest.o -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo -c -o libdhcp___unittests-duid_unittest.o `test -f 'duid_unittest.cc' || echo '$(srcdir)/'`duid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_unittest.cc' object='libdhcp___unittests-duid_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_unittest.o `test -f 'duid_unittest.cc' || echo '$(srcdir)/'`duid_unittest.cc
+
+libdhcp___unittests-duid_unittest.obj: duid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp___unittests-duid_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo -c -o libdhcp___unittests-duid_unittest.obj `if test -f 'duid_unittest.cc'; then $(CYGPATH_W) 'duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp___unittests-duid_unittest.Tpo $(DEPDIR)/libdhcp___unittests-duid_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_unittest.cc' object='libdhcp___unittests-duid_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) $(libdhcp___unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp___unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp___unittests-duid_unittest.obj `if test -f 'duid_unittest.cc'; then $(CYGPATH_W) 'duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_unittest.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) $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-classify_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.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-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)/libdhcp___unittests-classify_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_factory_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-duid_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-hwaddr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_test_config.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-iface_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-libdhcp++_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-opaque_data_tuple_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_addrlst_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_client_fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option4_dnr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_addrlst_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_auth_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_client_fqdn_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_dnr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_ia_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaaddr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_iaprefix_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_pdexclude_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option6_status_code_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_copy_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_custom_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_data_types_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_definition_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_array_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_int_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_opaque_data_tuples_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_space_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_string_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_class_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-option_vendor_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-packet_queue_mgr6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt4o6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures4.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_captures6.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_stub.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter6_test_utils.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_bpf_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_inet_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_lpf_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_stub.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_test_utils.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-pkt_filter_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-protocol_util_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcp___unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcptest_la-iface_mgr_test_config.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures4.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_captures6.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter6_test_stub.Plo
+ -rm -f ./$(DEPDIR)/libdhcptest_la-pkt_filter_test_stub.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:
+
+.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-noinstLTLIBRARIES 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/dhcp/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc
new file mode 100644
index 0000000..abc73dd
--- /dev/null
+++ b/src/lib/dhcp/tests/classify_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright (C) 2011-2022 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 <dhcp/classify.h>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+// Trivial test for now as ClientClass is a std::string.
+TEST(ClassifyTest, ClientClass) {
+
+ ClientClass x("foo");
+ EXPECT_EQ("foo", x);
+
+ x = "baz";
+ EXPECT_EQ("baz", x);
+}
+
+// Checks if ClientClasses object is able to store classes' names and then
+// properly return values of contains() method.
+TEST(ClassifyTest, ClientClasses) {
+ ClientClasses classes;
+
+ EXPECT_FALSE(classes.contains(""));
+ EXPECT_FALSE(classes.contains("alpha"));
+ EXPECT_FALSE(classes.contains("beta"));
+ EXPECT_FALSE(classes.contains("gamma"));
+ classes.insert("beta");
+ EXPECT_FALSE(classes.contains(""));
+ EXPECT_FALSE(classes.contains("alpha"));
+ EXPECT_TRUE (classes.contains("beta"));
+ EXPECT_FALSE(classes.contains("gamma"));
+
+ classes.insert("alpha");
+ classes.insert("gamma");
+ EXPECT_TRUE (classes.contains("alpha"));
+ EXPECT_TRUE (classes.contains("beta"));
+ EXPECT_TRUE (classes.contains("gamma"));
+}
+
+// Check if ClientClasses object can be created from the string of comma
+// separated values.
+TEST(ClassifyTest, ClientClassesFromString) {
+ {
+ ClientClasses classes("alpha, beta, gamma");
+ EXPECT_EQ(3, classes.size());
+ EXPECT_FALSE(classes.contains(""));
+ EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_TRUE(classes.contains("beta"));
+ EXPECT_TRUE(classes.contains("gamma"));
+ }
+
+ {
+ ClientClasses classes("alpha, , beta ,");
+ EXPECT_EQ(2, classes.size());
+ EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_FALSE(classes.contains(""));
+ EXPECT_TRUE(classes.contains("beta"));
+ }
+
+ {
+ ClientClasses classes("");
+ EXPECT_TRUE(classes.empty());
+ }
+
+ {
+ ClientClasses classes(" ");
+ EXPECT_TRUE(classes.empty());
+ }
+
+ {
+ ClientClasses classes(", ,, ,");
+ EXPECT_TRUE(classes.empty());
+ }
+}
+
+// Check if one can iterate over a ClientClasses object
+TEST(ClassifyTest, ClientClassesIterator) {
+ ClientClasses classes("alpha, beta, gamma");
+ size_t count = 0;
+ bool seenalpha = false;
+ bool seenbeta = false;
+ bool seengamma = false;
+ bool seendelta = false;
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
+ ++count;
+ if (*it == "alpha") {
+ seenalpha = true;
+ } else if (*it == "beta") {
+ seenbeta = true;
+ } else if (*it == "gamma") {
+ seengamma = true;
+ } else if (*it == "delta") {
+ seendelta = true;
+ } else {
+ ADD_FAILURE() << "Got unexpected " << *it;
+ }
+ }
+ EXPECT_EQ(count, classes.size());
+ EXPECT_TRUE(seenalpha);
+ EXPECT_TRUE(seenbeta);
+ EXPECT_TRUE(seengamma);
+ EXPECT_FALSE(seendelta);
+}
+
+// Check that the ClientClasses::toText function returns
+// correct values.
+TEST(ClassifyTest, ClientClassesToText) {
+ // No classes.
+ ClientClasses classes;
+ EXPECT_TRUE(classes.toText().empty());
+
+ // Insert single class name and see if it doesn't include spurious
+ // comma after it.
+ classes.insert("alpha");
+ EXPECT_EQ("alpha", classes.toText());
+
+ // Insert next class name and see that both classes are present.
+ classes.insert("gamma");
+ EXPECT_EQ("alpha, gamma", classes.toText());
+
+ // Insert third class and make sure they get ordered in insert order.
+ classes.insert("beta");
+ EXPECT_EQ("alpha, gamma, beta", classes.toText());
+
+ // Check non-standard separator.
+ EXPECT_EQ("alpha.gamma.beta", classes.toText("."));
+}
+
+// Check that the ClientClasses::toElement function returns
+// correct values.
+TEST(ClassifyTest, ClientClassesToElement) {
+ // No classes.
+ ClientClasses classes;
+ EXPECT_TRUE(classes.toElement()->empty());
+
+ // Insert single class name and see that it's there.
+ classes.insert("alpha");
+ EXPECT_EQ("[ \"alpha\" ]", classes.toElement()->str());
+
+ // Insert next class name and see that both classes are present.
+ classes.insert("gamma");
+ EXPECT_EQ("[ \"alpha\", \"gamma\" ]", classes.toElement()->str());
+
+ // Insert third class and make sure they get ordered in insert order.
+ classes.insert("beta");
+ EXPECT_EQ("[ \"alpha\", \"gamma\", \"beta\" ]", classes.toElement()->str());
+}
+
+// Check that selected class can be erased.
+TEST(ClassifyTest, Erase) {
+ ClientClasses classes;
+
+ classes.insert("alpha");
+ classes.insert("beta");
+ EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_TRUE(classes.contains("beta"));
+
+ classes.erase("beta");
+ EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_FALSE(classes.contains("beta"));
+
+ classes.erase("alpha");
+ EXPECT_FALSE(classes.contains("alpha"));
+ EXPECT_FALSE(classes.contains("beta"));
+}
diff --git a/src/lib/dhcp/tests/duid_factory_unittest.cc b/src/lib/dhcp/tests/duid_factory_unittest.cc
new file mode 100644
index 0000000..1669983
--- /dev/null
+++ b/src/lib/dhcp/tests/duid_factory_unittest.cc
@@ -0,0 +1,529 @@
+// Copyright (C) 2015-2017 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 <dhcp/dhcp4.h>
+#include <dhcp/duid_factory.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <testutils/io_utils.h>
+#include <util/encode/hex.h>
+#include <util/range_utilities.h>
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+#include <ctime>
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Name of the file holding DUID generated during a test.
+const std::string DEFAULT_DUID_FILE = "duid-factory-test.duid";
+
+/// @brief Test fixture class for @c DUIDFactory.
+class DUIDFactoryTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates fake interface configuration. It also creates an instance
+ /// of the @c DUIDFactory object used throughout the tests.
+ DUIDFactoryTest();
+
+ /// @brief Destructor.
+ virtual ~DUIDFactoryTest();
+
+ /// @brief Returns absolute path to a test DUID storage.
+ ///
+ /// @param duid_file_name Name of the file holding test DUID.
+ std::string absolutePath(const std::string& duid_file_name) const;
+
+ /// @brief Removes default DUID file used in the tests.
+ ///
+ /// This method is called from both constructor and destructor.
+ void removeDefaultFile() const;
+
+ /// @brief Returns contents of the DUID file.
+ std::string readDefaultFile() const;
+
+ /// @brief Converts string of hexadecimal digits to vector.
+ ///
+ /// @param hex String representation.
+ /// @return Vector created from the converted string.
+ std::vector<uint8_t> toVector(const std::string& hex) const;
+
+ /// @brief Converts vector to string of hexadecimal digits.
+ ///
+ /// @param vec Input vector.
+ /// @return String of hexadecimal digits converted from vector.
+ std::string toString(const std::vector<uint8_t>& vec) const;
+
+ /// @brief Converts current time to a string of hexadecimal digits.
+ ///
+ /// @return Time represented as text.
+ std::string timeAsHexString() const;
+
+ /// @brief Tests creation of a DUID-LLT.
+ ///
+ /// @param expected_htype Expected link layer type as string.
+ /// @param expected_time Expected time as string.
+ /// @param time_equal Indicates if @c expected time should be
+ /// compared for equality with the time being part of a DUID
+ /// (if true), or the time being part of the DUID should be
+ /// less or equal current time (if false).
+ /// @param expected_hwaddr Expected link layer type as string.
+ void testLLT(const std::string& expected_htype,
+ const std::string& expected_time,
+ const bool time_equal,
+ const std::string& expected_hwaddr);
+
+ /// @brief Tests creation of a DUID-LLT.
+ ///
+ /// @param expected_htype Expected link layer type as string.
+ /// @param expected_time Expected time as string.
+ /// @param time_equal Indicates if @c expected time should be
+ /// compared for equality with the time being part of a DUID
+ /// (if true), or the time being part of the DUID should be
+ /// less or equal current time (if false).
+ /// @param expected_hwaddr Expected link layer type as string.
+ /// @param factory_ref Reference to DUID factory.
+ void testLLT(const std::string& expected_htype,
+ const std::string& expected_time,
+ const bool time_equal,
+ const std::string& expected_hwaddr,
+ DUIDFactory& factory_ref);
+
+ /// @brief Tests creation of a DUID-EN.
+ ///
+ /// @param expected_enterprise_id Expected enterprise id as string.
+ /// @param expected_identifier Expected variable length identifier
+ /// as string. If empty string specified the test method only checks
+ /// that generated identifier consists of some random values.
+ void testEN(const std::string& expected_enterprise_id,
+ const std::string& expected_identifier = "");
+
+ /// @brief Tests creation of a DUID-EN.
+ ///
+ /// @param expected_enterprise_id Expected enterprise id as string.
+ /// @param expected_identifier Expected variable length identifier
+ /// as string. If empty string specified the test method only checks
+ /// that generated identifier consists of some random values.
+ /// @param factory_ref Reference to DUID factory.
+ void testEN(const std::string& expected_enterprise_id,
+ const std::string& expected_identifier,
+ DUIDFactory& factory_ref);
+
+ /// @brief Tests creation of a DUID-LL.
+ ///
+ /// @param expected_htype Expected link layer type as string.
+ /// @param expected_hwaddr Expected link layer type as string.
+ void testLL(const std::string& expected_htype,
+ const std::string& expected_hwaddr);
+
+ /// @brief Tests creation of a DUID-LL.
+ ///
+ /// @param expected_htype Expected link layer type as string.
+ /// @param expected_hwaddr Expected link layer type as string.
+ /// @param factory_ref Reference to DUID factory.
+ void testLL(const std::string& expected_htype,
+ const std::string& expected_hwaddr,
+ DUIDFactory& factory_ref);
+
+ /// @brief Returns reference to a default factory.
+ DUIDFactory& factory() {
+ return (factory_);
+ }
+
+private:
+
+ /// @brief Creates fake interface configuration.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+ /// @brief Holds default instance of the @c DUIDFactory class, being
+ /// used throughout the tests.
+ DUIDFactory factory_;
+
+};
+
+DUIDFactoryTest::DUIDFactoryTest()
+ : iface_mgr_test_config_(true),
+ factory_(absolutePath(DEFAULT_DUID_FILE)) {
+ removeDefaultFile();
+}
+
+DUIDFactoryTest::~DUIDFactoryTest() {
+ removeDefaultFile();
+}
+
+std::string
+DUIDFactoryTest::absolutePath(const std::string& duid_file_name) const {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << duid_file_name;
+ return (s.str());
+}
+
+void
+DUIDFactoryTest::removeDefaultFile() const {
+ static_cast<void>(remove(absolutePath(DEFAULT_DUID_FILE).c_str()));
+}
+
+std::string
+DUIDFactoryTest::readDefaultFile() const {
+ return (isc::test::readFile(absolutePath(DEFAULT_DUID_FILE)));
+}
+
+std::vector<uint8_t>
+DUIDFactoryTest::toVector(const std::string& hex) const {
+ std::vector<uint8_t> vec;
+ try {
+ util::encode::decodeHex(hex, vec);
+ } catch (...) {
+ ADD_FAILURE() << "toVector: the following string " << hex
+ << " is not a valid hex string";
+ }
+
+ return (vec);
+}
+
+std::string
+DUIDFactoryTest::toString(const std::vector<uint8_t>& vec) const {
+ try {
+ return (util::encode::encodeHex(vec));
+ } catch (...) {
+ ADD_FAILURE() << "toString: unable to encode vector to"
+ " hexadecimal string";
+ }
+ return ("");
+}
+
+std::string
+DUIDFactoryTest::timeAsHexString() const {
+ time_t current_time = time(NULL) - DUID_TIME_EPOCH;
+ std::ostringstream s;
+ s << std::hex << std::setw(8) << std::setfill('0') << current_time;
+ return (boost::to_upper_copy<std::string>(s.str()));
+}
+
+void
+DUIDFactoryTest::testLLT(const std::string& expected_htype,
+ const std::string& expected_time,
+ const bool time_equal,
+ const std::string& expected_hwaddr) {
+ testLLT(expected_htype, expected_time, time_equal, expected_hwaddr,
+ factory());
+}
+
+void
+DUIDFactoryTest::testLLT(const std::string& expected_htype,
+ const std::string& expected_time,
+ const bool time_equal,
+ const std::string& expected_hwaddr,
+ DUIDFactory& factory_ref) {
+ DuidPtr duid = factory_ref.get();
+ ASSERT_TRUE(duid);
+ ASSERT_GE(duid->getDuid().size(), 14);
+ std::string duid_text = toString(duid->getDuid());
+
+ // DUID type LLT
+ EXPECT_EQ("0001", duid_text.substr(0, 4));
+ // Link layer type HTYPE_ETHER
+ EXPECT_EQ(expected_htype, duid_text.substr(4, 4));
+
+ // Verify if time is correct.
+ if (time_equal) {
+ // Strict time check.
+ EXPECT_EQ(expected_time, duid_text.substr(8, 8));
+ } else {
+ // Timestamp equal or less current time.
+ EXPECT_LE(duid_text.substr(8, 8), expected_time);
+ }
+
+ // MAC address of the interface.
+ EXPECT_EQ(expected_hwaddr, duid_text.substr(16));
+
+ // Compare DUID with the one stored in the file.
+ EXPECT_EQ(duid->toText(), readDefaultFile());
+}
+
+void
+DUIDFactoryTest::testEN(const std::string& expected_enterprise_id,
+ const std::string& expected_identifier) {
+ testEN(expected_enterprise_id, expected_identifier, factory());
+}
+
+void
+DUIDFactoryTest::testEN(const std::string& expected_enterprise_id,
+ const std::string& expected_identifier,
+ DUIDFactory& factory_ref) {
+ DuidPtr duid = factory_ref.get();
+ ASSERT_TRUE(duid);
+ ASSERT_GE(duid->getDuid().size(), 8);
+ std::string duid_text = toString(duid->getDuid());
+
+ // DUID type EN.
+ EXPECT_EQ("0002", duid_text.substr(0, 4));
+ // Verify enterprise ID.
+ EXPECT_EQ(expected_enterprise_id, duid_text.substr(4, 8));
+
+ // If no expected identifier, we should at least check that the
+ // generated identifier contains some random non-zero digits.
+ if (expected_identifier.empty()) {
+ EXPECT_FALSE(isRangeZero(duid->getDuid().begin(),
+ duid->getDuid().end()));
+ } else {
+ // Check if identifier matches.
+ EXPECT_EQ(expected_identifier, duid_text.substr(12));
+ }
+
+ // Compare DUID with the one stored in the file.
+ EXPECT_EQ(duid->toText(), readDefaultFile());
+}
+
+void
+DUIDFactoryTest::testLL(const std::string& expected_htype,
+ const std::string& expected_hwaddr) {
+ testLL(expected_htype, expected_hwaddr, factory());
+}
+
+void
+DUIDFactoryTest::testLL(const std::string& expected_htype,
+ const std::string& expected_hwaddr,
+ DUIDFactory& factory_ref) {
+ DuidPtr duid = factory_ref.get();
+ ASSERT_TRUE(duid);
+ ASSERT_GE(duid->getDuid().size(), 8);
+ std::string duid_text = toString(duid->getDuid());
+
+ // DUID type LL
+ EXPECT_EQ("0003", duid_text.substr(0, 4));
+ // Link layer type.
+ EXPECT_EQ(expected_htype, duid_text.substr(4, 4));
+
+ // MAC address of the interface.
+ EXPECT_EQ(expected_hwaddr, duid_text.substr(8));
+
+ // Compare DUID with the one stored in the file.
+ EXPECT_EQ(duid->toText(), readDefaultFile());
+}
+
+
+// This test verifies that the factory class will generate the entire
+// DUID-LLT if there are no explicit values specified for the
+// time, link layer type and link layer address.
+TEST_F(DUIDFactoryTest, createLLT) {
+ // Use 0 values for time and link layer type and empty vector for
+ // the link layer address. The createLLT function will need to
+ // use current time, HTYPE_ETHER and MAC address of one of the
+ // interfaces.
+ ASSERT_NO_THROW(factory().createLLT(0, 0, std::vector<uint8_t>()));
+ testLLT("0001", timeAsHexString(), false, "080808080808");
+}
+
+// This test verifies that the factory class creates a DUID-LLT from
+// the explicitly specified time, when link layer type and address are
+// generated.
+TEST_F(DUIDFactoryTest, createLLTExplicitTime) {
+ ASSERT_NO_THROW(factory().createLLT(0, 0xABCDEF, std::vector<uint8_t>()));
+ testLLT("0001", "00ABCDEF", true, "080808080808");
+}
+
+// This test verifies that the factory class creates DUID-LLT with
+// the link layer type of the interface which link layer address
+// is used to generate the DUID.
+TEST_F(DUIDFactoryTest, createLLTExplicitHtype) {
+ ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0, std::vector<uint8_t>()));
+ testLLT("0001", timeAsHexString(), false, "080808080808");
+}
+
+// This test verifies that the factory class creates DUID-LLT from
+// explicitly specified link layer address, when other parameters
+// are generated.
+TEST_F(DUIDFactoryTest, createLLTExplicitLinkLayerAddress) {
+ ASSERT_NO_THROW(factory().createLLT(0, 0, toVector("121212121212")));
+ testLLT("0001", timeAsHexString(), false, "121212121212");
+}
+
+// This test verifies that the factory function creates DUID-LLT from
+// all values explicitly specified.
+TEST_F(DUIDFactoryTest, createLLTAllExplicitParameters) {
+ ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA,
+ toVector("24242424242424242424")));
+ testLLT("0008", "FAFAFAFA", true, "24242424242424242424");
+}
+
+// This test verifies that the createLLT function will try to reuse existing
+// DUID for the non-explicitly specified values.
+TEST_F(DUIDFactoryTest, createLLTReuse) {
+ // Create DUID-LLT and store it in a file.
+ ASSERT_NO_THROW(factory().createLLT(HTYPE_FDDI, 0xFAFAFAFA,
+ toVector("242424242424")));
+ // Create another factory class, which uses the same file.
+ DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE));
+ // Create DUID-LLT without specifying hardware type, time and
+ // link layer address. The factory function should use the
+ // values in the existing DUID.
+ ASSERT_NO_THROW(factory2.createLLT(0, 0, std::vector<uint8_t>()));
+ testLLT("0008", "FAFAFAFA", true, "242424242424", factory2);
+
+ // Try to reuse only a time value.
+ DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory3.createLLT(HTYPE_ETHER, 0,
+ toVector("121212121212")));
+ testLLT("0001", "FAFAFAFA", true, "121212121212", factory3);
+
+ // Reuse only a hardware type.
+ DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory4.createLLT(0, 0x23432343,
+ toVector("455445544554")));
+ testLLT("0001", "23432343", true, "455445544554", factory4);
+
+ // Reuse link layer address. Note that in this case the hardware
+ // type is set to the type of the interface from which hardware
+ // address is obtained and the explicit value is ignored.
+ DUIDFactory factory5(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory5.createLLT(HTYPE_FDDI, 0x11111111,
+ std::vector<uint8_t>()));
+ testLLT("0001", "11111111", true, "455445544554", factory5);
+}
+
+// This test verifies that the DUID-EN can be generated entirely. Such
+// generated DUID contains ISC enterprise id and the random identifier.
+TEST_F(DUIDFactoryTest, createEN) {
+ ASSERT_NO_THROW(factory().createEN(0, std::vector<uint8_t>()));
+ testEN("000009BF");
+}
+
+// This test verifies that the DUID-EN may contain custom enterprise id.
+TEST_F(DUIDFactoryTest, createENExplicitEnterpriseId) {
+ ASSERT_NO_THROW(factory().createEN(0xABCDEFAB, std::vector<uint8_t>()));
+ testEN("ABCDEFAB");
+}
+
+// This test verifies that DUID-EN may contain custom variable length
+// identifier and default enterprise id.
+TEST_F(DUIDFactoryTest, createENExplicitIdentifier) {
+ ASSERT_NO_THROW(factory().createEN(0, toVector("1212121212121212")));
+ testEN("000009BF", "1212121212121212");
+}
+
+// This test verifies that DUID-EN can be created from explicit enterprise id
+// and identifier.
+TEST_F(DUIDFactoryTest, createENAllExplicitParameters) {
+ ASSERT_NO_THROW(factory().createEN(0x01020304, toVector("ABCD")));
+ testEN("01020304", "ABCD");
+}
+
+// This test verifies that the createEN function will try to reuse existing
+// DUID for the non-explicitly specified values.
+TEST_F(DUIDFactoryTest, createENReuse) {
+ // Create DUID-EN and store it in a file.
+ ASSERT_NO_THROW(factory().createEN(0xFAFAFAFA, toVector("242424242424")));
+ // Create another factory class, which uses the same file.
+ DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory2.createEN(0, std::vector<uint8_t>()));
+ testEN("FAFAFAFA", "242424242424", factory2);
+
+ // Reuse only enterprise id.
+ DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory3.createEN(0, toVector("121212121212")));
+ testEN("FAFAFAFA", "121212121212", factory3);
+
+ // Reuse only variable length identifier.
+ DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory4.createEN(0x1234, std::vector<uint8_t>()));
+ testEN("00001234", "121212121212", factory4);
+}
+
+// This test verifies that the DUID-LL is generated when neither link layer
+// type nor address is specified.
+TEST_F(DUIDFactoryTest, createLL) {
+ ASSERT_NO_THROW(factory().createLL(0, std::vector<uint8_t>()));
+ testLL("0001", "080808080808");
+}
+
+// This test verifies that the DUID-LL is generated and the link layer type
+// used is taken from the interface used to generate link layer address.
+TEST_F(DUIDFactoryTest, createLLExplicitHtype) {
+ ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, std::vector<uint8_t>()));
+ testLL("0001", "080808080808");
+}
+
+// This test verifies that DUID-LL is created from explicitly provided
+// link layer type and address.
+TEST_F(DUIDFactoryTest, createLLAllExplicitParameters) {
+ ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424")));
+ testLL("0008", "242424242424");
+}
+
+// This test verifies that DUID-LLT is created when caller wants to obtain
+// it and it doesn't exist.
+TEST_F(DUIDFactoryTest, createLLTIfNotExists) {
+ DuidPtr duid;
+ ASSERT_NO_THROW(duid = factory().get());
+ ASSERT_TRUE(duid);
+ EXPECT_EQ(DUID::DUID_LLT, duid->getType());
+}
+
+// This test verifies that DUID-EN when there is no suitable interface to
+// use to create DUID-LLT.
+TEST_F(DUIDFactoryTest, createENIfNotExists) {
+ // Remove interfaces. The DUID-LLT is a default type but it requires
+ // that an interface with a suitable link-layer address is present
+ // in the system. By removing the interfaces we cause the factory
+ // to fail to generate DUID-LLT. It should fall back to DUID-EN.
+ IfaceMgr::instance().clearIfaces();
+
+ DuidPtr duid;
+ ASSERT_NO_THROW(duid = factory().get());
+ ASSERT_TRUE(duid);
+ EXPECT_EQ(DUID::DUID_EN, duid->getType());
+}
+
+// This test verifies that the createLL function will try to reuse existing
+// DUID for the non-explicitly specified values.
+TEST_F(DUIDFactoryTest, createLLReuse) {
+ // Create DUID-EN and store it in a file.
+ ASSERT_NO_THROW(factory().createLL(HTYPE_FDDI, toVector("242424242424")));
+ // Create another factory class, which uses the same file.
+ DUIDFactory factory2(absolutePath(DEFAULT_DUID_FILE));
+ // Create DUID-LL without specifying hardware type, time and
+ // link layer address. The factory function should use the
+ // values in the existing DUID.
+ ASSERT_NO_THROW(factory2.createLL(0, std::vector<uint8_t>()));
+ testLL("0008", "242424242424", factory2);
+
+ // Reuse only hardware type
+ DUIDFactory factory3(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory3.createLL(0, toVector("121212121212")));
+ testLL("0008", "121212121212", factory3);
+
+ // Reuse link layer address. Note that when the link layer address is
+ // reused, the explicit value of hardware type is reused too and the
+ // explicit value of hardware type is ignored.
+ DUIDFactory factory4(absolutePath(DEFAULT_DUID_FILE));
+ ASSERT_NO_THROW(factory4.createLL(HTYPE_ETHER, std::vector<uint8_t>()));
+ testLL("0008", "121212121212", factory4);
+}
+
+// This test verifies that it is possible to override a DUID.
+TEST_F(DUIDFactoryTest, override) {
+ // Create default DUID-LLT.
+ ASSERT_NO_THROW(static_cast<void>(factory().get()));
+ testLLT("0001", timeAsHexString(), false, "080808080808");
+
+ ASSERT_NO_THROW(factory().createEN(0, toVector("12131415")));
+ testEN("000009BF", "12131415");
+}
+
+} // End anonymous namespace
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
new file mode 100644
index 0000000..b567c86
--- /dev/null
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright (C) 2011-2023 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 <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+using namespace isc::dhcp;
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(DuidTest, constructor) {
+
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+
+ vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+ boost::scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
+ boost::scoped_ptr<DUID> duid2(new DUID(data2));
+
+ vector<uint8_t> vecdata = duid1->getDuid();
+ EXPECT_TRUE(data2 == vecdata);
+ EXPECT_EQ(DUID::DUID_LLT, duid1->getType());
+
+ vecdata = duid2->getDuid();
+ EXPECT_TRUE(data2 == vecdata);
+
+ EXPECT_EQ(DUID::DUID_LLT, duid2->getType());
+}
+
+// This test verifies if DUID size restrictions are implemented
+// properly.
+TEST(DuidTest, size) {
+
+ // Ensure that our size constant is RFC-compliant.
+ ASSERT_EQ(130, DUID::MAX_DUID_LEN);
+
+ uint8_t data[DUID::MAX_DUID_LEN + 1];
+ vector<uint8_t> data2;
+ for (uint8_t i = 0; i < DUID::MAX_DUID_LEN + 1; ++i) {
+ data[i] = i;
+ if (i < DUID::MAX_DUID_LEN) {
+ data2.push_back(i);
+ }
+ }
+ ASSERT_EQ(data2.size(), DUID::MAX_DUID_LEN);
+
+ boost::scoped_ptr<DUID> duidmaxsize1(new DUID(data, DUID::MAX_DUID_LEN));
+ boost::scoped_ptr<DUID> duidmaxsize2(new DUID(data2));
+
+ EXPECT_THROW(
+ boost::scoped_ptr<DUID> toolarge1(new DUID(data, DUID::MAX_DUID_LEN + 1)),
+ BadValue);
+
+ // that's one too much
+ data2.push_back(128);
+
+ EXPECT_THROW(
+ boost::scoped_ptr<DUID> toolarge2(new DUID(data2)),
+ BadValue);
+
+ // empty duids are not allowed
+ vector<uint8_t> empty;
+ EXPECT_THROW(
+ boost::scoped_ptr<DUID> emptyDuid(new DUID(empty)),
+ BadValue);
+
+ EXPECT_THROW(
+ boost::scoped_ptr<DUID> emptyDuid2(new DUID(data, 0)),
+ BadValue);
+}
+
+// This test verifies if the implementation supports all defined
+// DUID types.
+TEST(DuidTest, getType) {
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6};
+ uint8_t en[] = {0, 2, 2, 3, 4, 5, 6};
+ uint8_t ll[] = {0, 3, 2, 3, 4, 5, 6};
+ uint8_t uuid[] = {0, 4, 2, 3, 4, 5, 6};
+ uint8_t invalid[] = {0,55, 2, 3, 4, 5, 6};
+
+ boost::scoped_ptr<DUID> duid_llt(new DUID(llt, sizeof(llt)));
+ boost::scoped_ptr<DUID> duid_en(new DUID(en, sizeof(en)));
+ boost::scoped_ptr<DUID> duid_ll(new DUID(ll, sizeof(ll)));
+ boost::scoped_ptr<DUID> duid_uuid(new DUID(uuid, sizeof(uuid)));
+ boost::scoped_ptr<DUID> duid_invalid(new DUID(invalid, sizeof(invalid)));
+
+ EXPECT_EQ(DUID::DUID_LLT, duid_llt->getType());
+ EXPECT_EQ(DUID::DUID_EN, duid_en->getType());
+ EXPECT_EQ(DUID::DUID_LL, duid_ll->getType());
+ EXPECT_EQ(DUID::DUID_UUID, duid_uuid->getType());
+ EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
+}
+
+// This test checks that the DUID instance can be created from the textual
+// format and that error is reported if the textual format is invalid.
+TEST(DuidTest, fromText) {
+ boost::scoped_ptr<DUID> duid;
+ // DUID with only decimal digits.
+ ASSERT_NO_THROW(
+ duid.reset(new DUID(DUID::fromText("00:01:02:03:04:05:06")))
+ );
+ EXPECT_EQ("00:01:02:03:04:05:06", duid->toText());
+ // DUID with some hexadecimal digits (upper case and lower case).
+ ASSERT_NO_THROW(
+ duid.reset(new DUID(DUID::fromText("00:aa:bb:CD:ee:EF:ab")))
+ );
+ EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", duid->toText());
+ // DUID with one digit for a particular byte.
+ ASSERT_NO_THROW(
+ duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab")))
+ );
+ EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText());
+ // Repeated colon sign is not allowed.
+ EXPECT_THROW(
+ duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab"))),
+ isc::BadValue
+ );
+ // DUID with excessive number of digits for one of the bytes.
+ EXPECT_THROW(
+ duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))),
+ isc::BadValue
+ );
+}
+
+// Test checks if the toText() returns valid texual representation
+TEST(DuidTest, toText) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+ DUID duid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+}
+
+// This test verifies that empty DUID returns proper value
+TEST(DuidTest, empty) {
+ DuidPtr empty;
+ EXPECT_NO_THROW(empty.reset(new DUID(DUID::EMPTY())));
+
+ // This method must return something
+ ASSERT_TRUE(empty);
+
+ // Ok, technically empty is not really empty, it's just type 0 (DUID_UNKNOWN)
+ // followed by a single byte with value of 0.
+ EXPECT_EQ(empty->getDuid().size(), 3);
+ EXPECT_EQ(empty->getDuid(), std::vector<uint8_t>({0, 0, 0}));
+ EXPECT_EQ("00:00:00", empty->toText());
+
+ EXPECT_TRUE(*empty == DUID::EMPTY());
+
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+ DUID duid(data1, sizeof(data1));
+
+ EXPECT_FALSE(duid == DUID::EMPTY());
+}
+
+// This test checks if the comparison operators are sane.
+TEST(DuidTest, operators) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ uint8_t data2[] = {0, 1, 2, 3, 4};
+ uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+ uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+ boost::scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
+ boost::scoped_ptr<DUID> duid2(new DUID(data2, sizeof(data2)));
+ boost::scoped_ptr<DUID> duid3(new DUID(data3, sizeof(data3)));
+ boost::scoped_ptr<DUID> duid4(new DUID(data4, sizeof(data4)));
+
+ EXPECT_TRUE(*duid1 == *duid4);
+ EXPECT_FALSE(*duid1 == *duid2);
+ EXPECT_FALSE(*duid1 == *duid3);
+
+ EXPECT_FALSE(*duid1 != *duid4);
+ EXPECT_TRUE(*duid1 != *duid2);
+ EXPECT_TRUE(*duid1 != *duid3);
+}
+
+// This test verifies if the ClientId constructors are working properly
+// and passed parameters are used
+TEST(ClientIdTest, constructor) {
+ IOAddress addr2("192.0.2.1");
+ IOAddress addr3("2001:db8:1::1");
+
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+ // checks for C-style constructor (uint8_t * + len)
+ boost::scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
+ vector<uint8_t> vecdata = id1->getClientId();
+ EXPECT_TRUE(data2 == vecdata);
+
+ // checks for vector-based constructor
+ boost::scoped_ptr<ClientId> id2(new ClientId(data2));
+ vecdata = id2->getClientId();
+ EXPECT_TRUE(data2 == vecdata);
+}
+
+// Check that client-id sizes are reasonable
+TEST(ClientIdTest, size) {
+ // Ensure that our size constant is RFC-compliant.
+ ASSERT_EQ(255, ClientId::MAX_CLIENT_ID_LEN);
+
+ uint8_t data[ClientId::MAX_CLIENT_ID_LEN + 1];
+ vector<uint8_t> data2;
+ for (uint16_t i = 0; i < ClientId::MAX_CLIENT_ID_LEN + 1; ++i) {
+ data[i] = static_cast<uint8_t>(i);
+ if (i < ClientId::MAX_CLIENT_ID_LEN) {
+ data2.push_back(i);
+ }
+ }
+ ASSERT_EQ(data2.size(), ClientId::MAX_CLIENT_ID_LEN);
+
+ boost::scoped_ptr<ClientId> duidmaxsize1(new ClientId(data, ClientId::MAX_CLIENT_ID_LEN));
+ boost::scoped_ptr<ClientId> duidmaxsize2(new ClientId(data2));
+
+ EXPECT_THROW(
+ boost::scoped_ptr<ClientId> toolarge1(new ClientId(data, ClientId::MAX_CLIENT_ID_LEN + 1)),
+ BadValue);
+
+ // that's one too much
+ data2.push_back(0);
+
+ EXPECT_THROW(
+ boost::scoped_ptr<ClientId> toolarge2(new ClientId(data2)),
+ BadValue);
+
+ // empty client-ids are not allowed
+ vector<uint8_t> empty;
+ EXPECT_THROW(
+ boost::scoped_ptr<ClientId> empty_client_id1(new ClientId(empty)),
+ BadValue);
+
+ EXPECT_THROW(
+ boost::scoped_ptr<ClientId> empty_client_id2(new ClientId(data, 0)),
+ BadValue);
+
+ // client-id must be at least 2 bytes long
+ vector<uint8_t> shorty(1,17); // just a single byte with value 17
+ EXPECT_THROW(
+ boost::scoped_ptr<ClientId> too_short_client_id1(new ClientId(shorty)),
+ BadValue);
+ EXPECT_THROW(
+ boost::scoped_ptr<ClientId> too_short_client_id1(new ClientId(data, 1)),
+ BadValue);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(ClientIdTest, operators) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ uint8_t data2[] = {0, 1, 2, 3, 4};
+ uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+ uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+ boost::scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
+ boost::scoped_ptr<ClientId> id2(new ClientId(data2, sizeof(data2)));
+ boost::scoped_ptr<ClientId> id3(new ClientId(data3, sizeof(data3)));
+ boost::scoped_ptr<ClientId> id4(new ClientId(data4, sizeof(data4)));
+
+ EXPECT_TRUE(*id1 == *id4);
+ EXPECT_FALSE(*id1 == *id2);
+ EXPECT_FALSE(*id1 == *id3);
+
+ EXPECT_FALSE(*id1 != *id4);
+ EXPECT_TRUE(*id1 != *id2);
+ EXPECT_TRUE(*id1 != *id3);
+}
+
+// Test checks if the toText() returns valid texual representation
+TEST(ClientIdTest, toText) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+ ClientId clientid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
+}
+
+// This test checks that the ClientId instance can be created from the textual
+// format and that error is reported if the textual format is invalid.
+TEST(ClientIdTest, fromText) {
+ ClientIdPtr cid;
+ // ClientId with only decimal digits.
+ ASSERT_NO_THROW(
+ cid = ClientId::fromText("00:01:02:03:04:05:06")
+ );
+ EXPECT_EQ("00:01:02:03:04:05:06", cid->toText());
+ // ClientId with some hexadecimal digits (upper case and lower case).
+ ASSERT_NO_THROW(
+ cid = ClientId::fromText("00:aa:bb:CD:ee:EF:ab")
+ );
+ EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", cid->toText());
+ // ClientId with one digit for a particular byte.
+ ASSERT_NO_THROW(
+ cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
+ );
+ EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
+ // ClientId without any colons is allowed.
+ ASSERT_NO_THROW(
+ cid = ClientId::fromText("0010abcdee");
+ );
+ EXPECT_EQ("00:10:ab:cd:ee", cid->toText());
+ // Repeated colon sign in the ClientId is not allowed.
+ EXPECT_THROW(
+ ClientId::fromText("00::bb:D:ee:EF:ab"),
+ isc::BadValue
+
+ );
+ // ClientId with excessive number of digits for one of the bytes.
+ EXPECT_THROW(
+ ClientId::fromText("00:01:021:03:04:05:06"),
+ isc::BadValue
+ );
+ // ClientId with two spaces between the colons should not be allowed.
+ EXPECT_THROW(
+ ClientId::fromText("00:01: :03:04:05:06"),
+ isc::BadValue
+ );
+
+ // ClientId with one space between the colons should not be allowed.
+ EXPECT_THROW(
+ ClientId::fromText("00:01: :03:04:05:06"),
+ isc::BadValue
+ );
+
+ // ClientId with three spaces between the colons should not be allowed.
+ EXPECT_THROW(
+ ClientId::fromText("00:01: :03:04:05:06"),
+ isc::BadValue
+ );
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
new file mode 100644
index 0000000..16ec478
--- /dev/null
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -0,0 +1,170 @@
+// Copyright (C) 2012-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 <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+using boost::scoped_ptr;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(HWAddrTest, constructor) {
+
+ const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ const uint8_t htype = HTYPE_ETHER;
+ vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+ // Over the limit data
+ vector<uint8_t> big_data_vector(HWAddr::MAX_HWADDR_LEN + 1, 0);
+
+ scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype));
+ scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype));
+ scoped_ptr<HWAddr> hwaddr3(new HWAddr());
+
+ EXPECT_TRUE(data2 == hwaddr1->hwaddr_);
+ EXPECT_EQ(htype, hwaddr1->htype_);
+
+ EXPECT_TRUE(data2 == hwaddr2->hwaddr_);
+ EXPECT_EQ(htype, hwaddr2->htype_);
+
+ EXPECT_EQ(0, hwaddr3->hwaddr_.size());
+ EXPECT_EQ(htype, hwaddr3->htype_);
+
+ // Check that over the limit data length throws exception
+ EXPECT_THROW(HWAddr(&big_data_vector[0], big_data_vector.size(), HTYPE_ETHER),
+ BadValue);
+
+ // Check that over the limit vector throws exception
+ EXPECT_THROW(HWAddr(big_data_vector, HTYPE_ETHER), BadValue);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(HWAddrTest, operators) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ uint8_t data2[] = {0, 1, 2, 3, 4};
+ uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+ uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+ uint8_t htype1 = HTYPE_ETHER;
+ uint8_t htype2 = HTYPE_FDDI;
+
+ scoped_ptr<HWAddr> hw1(new HWAddr(data1, sizeof(data1), htype1));
+ scoped_ptr<HWAddr> hw2(new HWAddr(data2, sizeof(data2), htype1));
+ scoped_ptr<HWAddr> hw3(new HWAddr(data3, sizeof(data3), htype1));
+ scoped_ptr<HWAddr> hw4(new HWAddr(data4, sizeof(data4), htype1));
+
+ // MAC address the same as data1 and data4, but different hardware type
+ scoped_ptr<HWAddr> hw5(new HWAddr(data4, sizeof(data4), htype2));
+
+ EXPECT_TRUE(*hw1 == *hw4);
+ EXPECT_FALSE(*hw1 == *hw2);
+ EXPECT_FALSE(*hw1 == *hw3);
+
+ EXPECT_FALSE(*hw1 != *hw4);
+ EXPECT_TRUE(*hw1 != *hw2);
+ EXPECT_TRUE(*hw1 != *hw3);
+
+ EXPECT_FALSE(*hw1 == *hw5);
+ EXPECT_FALSE(*hw4 == *hw5);
+
+ EXPECT_TRUE(*hw1 != *hw5);
+ EXPECT_TRUE(*hw4 != *hw5);
+}
+
+// Checks that toText() method produces appropriate text representation
+TEST(HWAddrTest, toText) {
+ uint8_t data[] = {0, 1, 2, 3, 4, 5};
+ uint8_t htype = 15;
+
+ HWAddrPtr hw(new HWAddr(data, sizeof(data), htype));
+
+ EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
+
+ // In some cases we don't want htype value to be included. Check that
+ // it can be forced.
+ EXPECT_EQ("00:01:02:03:04:05", hw->toText(false));
+}
+
+TEST(HWAddrTest, stringConversion) {
+
+ // Check that an empty vector returns an appropriate string
+ HWAddr hwaddr;
+ std::string result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 "), result);
+
+ // ... that a single-byte string is OK
+ hwaddr.hwaddr_.push_back(0xc3);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3"), result);
+
+ // ... and that a multi-byte string works
+ hwaddr.hwaddr_.push_back(0x7);
+ hwaddr.hwaddr_.push_back(0xa2);
+ hwaddr.hwaddr_.push_back(0xe8);
+ hwaddr.hwaddr_.push_back(0x42);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
+}
+
+// Checks that the HW address can be created from the textual format.
+TEST(HWAddrTest, fromText) {
+ scoped_ptr<HWAddr> hwaddr;
+ // Create HWAddr from text.
+ ASSERT_NO_THROW(
+ hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:A:bc:d:67")));
+ );
+ EXPECT_EQ("00:01:0a:bc:0d:67", hwaddr->toText(false));
+
+ // HWAddr class should allow empty address.
+ ASSERT_NO_THROW(
+ hwaddr.reset(new HWAddr(HWAddr::fromText("")));
+ );
+ EXPECT_TRUE(hwaddr->toText(false).empty());
+
+ // HWAddr should not allow multiple consecutive colons.
+ EXPECT_THROW(
+ hwaddr.reset(new HWAddr(HWAddr::fromText("00::01:00:bc:0d:67"))),
+ isc::BadValue
+ );
+
+ // There should be no more than two digits per byte of the HW addr.
+ EXPECT_THROW(
+ hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))),
+ isc::BadValue
+ );
+
+}
+
+// Checks that 16 bits values can be stored in HWaddr
+TEST(HWAddrTest, 16bits) {
+
+ uint8_t data[] = {0, 1, 2, 3, 4, 5};
+ uint16_t htype = 257;
+ HWAddrPtr hw(new HWAddr(data, sizeof(data), htype));
+
+ EXPECT_EQ("hwtype=257 00:01:02:03:04:05", hw->toText());
+
+
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.cc b/src/lib/dhcp/tests/iface_mgr_test_config.cc
new file mode 100644
index 0000000..26191d5
--- /dev/null
+++ b/src/lib/dhcp/tests/iface_mgr_test_config.cc
@@ -0,0 +1,211 @@
+// Copyright (C) 2014-2023 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 <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/pkt_filter_inet6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_filter_test_stub.h>
+#include <dhcp/tests/pkt_filter6_test_stub.h>
+
+#include <boost/foreach.hpp>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) {
+ IfaceMgr::instance().setTestMode(true);
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().getPacketQueueMgr4()->destroyPacketQueue();
+ IfaceMgr::instance().getPacketQueueMgr6()->destroyPacketQueue();
+ packet_filter4_ = PktFilterPtr(new PktFilterTestStub());
+ packet_filter6_ = PktFilter6Ptr(new PktFilter6TestStub());
+ IfaceMgr::instance().setPacketFilter(packet_filter4_);
+ IfaceMgr::instance().setPacketFilter(packet_filter6_);
+
+ // Create default set of fake interfaces: lo, eth0, eth1 and eth1961.
+ if (default_config) {
+ createIfaces();
+ }
+}
+
+IfaceMgrTestConfig::~IfaceMgrTestConfig() {
+ IfaceMgr::instance().stopDHCPReceiver();
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().getPacketQueueMgr4()->destroyPacketQueue();
+ IfaceMgr::instance().getPacketQueueMgr6()->destroyPacketQueue();
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+ IfaceMgr::instance().setPacketFilter(PktFilter6Ptr(new PktFilterInet6()));
+ IfaceMgr::instance().setTestMode(false);
+ IfaceMgr::instance().detectIfaces();
+}
+
+void
+IfaceMgrTestConfig::addAddress(const std::string& iface_name,
+ const IOAddress& address) {
+ IfacePtr iface = IfaceMgr::instance().getIface(iface_name);
+ if (!iface) {
+ isc_throw(isc::BadValue, "interface '" << iface_name
+ << "' doesn't exist");
+ }
+ iface->addAddress(address);
+}
+
+void
+IfaceMgrTestConfig::addIface(const IfacePtr& iface) {
+ IfaceMgr::instance().addInterface(iface);
+}
+
+void
+IfaceMgrTestConfig::addIface(const std::string& name,
+ const unsigned int ifindex) {
+ IfaceMgr::instance().addInterface(createIface(name, ifindex));
+}
+
+IfacePtr
+IfaceMgrTestConfig::createIface(const std::string& name,
+ const unsigned int ifindex,
+ const std::string& mac) {
+ IfacePtr iface(new Iface(name, ifindex));
+ if (name == "lo") {
+ iface->flag_loopback_ = true;
+ // Don't open sockets on the loopback interface.
+ iface->inactive4_ = true;
+ iface->inactive6_ = true;
+ } else {
+ iface->inactive4_ = false;
+ iface->inactive6_ = false;
+ }
+ iface->flag_multicast_ = true;
+ // On BSD systems, the SO_BINDTODEVICE option is not supported.
+ // Therefore the IfaceMgr will throw an exception on attempt to
+ // open sockets on more than one broadcast-capable interface at
+ // the same time. In order to prevent this error, we mark all
+ // interfaces broadcast-incapable for unit testing.
+ iface->flag_broadcast_ = false;
+ iface->flag_up_ = true;
+ iface->flag_running_ = true;
+
+ // Set MAC address.
+ HWAddr hwaddr = HWAddr::fromText(mac);
+ std::vector<uint8_t> mac_vec = hwaddr.hwaddr_;
+ iface->setMac(&mac_vec[0], mac_vec.size());
+ iface->setHWType(HTYPE_ETHER);
+
+ return (iface);
+}
+
+void
+IfaceMgrTestConfig::createIfaces() {
+ // local loopback
+ addIface("lo", LO_INDEX);
+ addAddress("lo", IOAddress("127.0.0.1"));
+ addAddress("lo", IOAddress("::1"));
+ // eth0
+ addIface("eth0", ETH0_INDEX);
+ addAddress("eth0", IOAddress("10.0.0.1"));
+ addAddress("eth0", IOAddress("fe80::3a60:77ff:fed5:cdef"));
+ addAddress("eth0", IOAddress("2001:db8:1::1"));
+ // eth1
+ addIface("eth1", ETH1_INDEX);
+ addAddress("eth1", IOAddress("192.0.2.3"));
+ addAddress("eth1", IOAddress("192.0.2.5"));
+ addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:abcd"));
+ // eth1961
+ addIface("eth1961", ETH1961_INDEX);
+ addAddress("eth1961", IOAddress("198.51.100.1"));
+ addAddress("eth1961", IOAddress("fe80::3a60:77ff:fed5:9876"));
+}
+
+void
+IfaceMgrTestConfig::setDirectResponse(const bool direct_resp) {
+ boost::shared_ptr<PktFilterTestStub> stub =
+ boost::dynamic_pointer_cast<PktFilterTestStub>(getPacketFilter4());
+ if (!stub) {
+ isc_throw(isc::Unexpected, "unable to set direct response capability for"
+ " test packet filter - current packet filter is not"
+ " of a PktFilterTestStub");
+ }
+ stub->direct_response_supported_ = direct_resp;
+}
+
+void
+IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
+ const FlagLoopback& loopback,
+ const FlagUp& up,
+ const FlagRunning& running,
+ const FlagInactive4& inactive4,
+ const FlagInactive6& inactive6) {
+ IfacePtr iface = IfaceMgr::instance().getIface(name);
+ if (iface == NULL) {
+ isc_throw(isc::BadValue, "interface '" << name << "' doesn't exist");
+ }
+ iface->flag_loopback_ = loopback.flag_;
+ iface->flag_up_ = up.flag_;
+ iface->flag_running_ = running.flag_;
+ iface->inactive4_ = inactive4.flag_;
+ iface->inactive6_ = inactive6.flag_;
+}
+
+bool
+IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
+ const int family) const {
+ IfacePtr iface = IfaceMgr::instance().getIface(iface_name);
+ if (iface == NULL) {
+ isc_throw(Unexpected, "No such interface '" << iface_name << "'");
+ }
+
+ BOOST_FOREACH(SocketInfo sock, iface->getSockets()) {
+ if (sock.family_ == family) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+IfaceMgrTestConfig::socketOpen(const std::string& iface_name,
+ const std::string& address) const {
+ IfacePtr iface = IfaceMgr::instance().getIface(iface_name);
+ if (!iface) {
+ isc_throw(Unexpected, "No such interface '" << iface_name << "'");
+ }
+
+ BOOST_FOREACH(SocketInfo sock, iface->getSockets()) {
+ if ((sock.family_ == AF_INET) &&
+ (sock.addr_ == IOAddress(address))) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+IfaceMgrTestConfig::unicastOpen(const std::string& iface_name) const {
+ IfacePtr iface = IfaceMgr::instance().getIface(iface_name);
+ if (!iface) {
+ isc_throw(Unexpected, "No such interface '" << iface_name << "'");
+ }
+
+ BOOST_FOREACH(SocketInfo sock, iface->getSockets()) {
+ if ((!sock.addr_.isV6LinkLocal()) &&
+ (!sock.addr_.isV6Multicast())) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcp/tests/iface_mgr_test_config.h b/src/lib/dhcp/tests/iface_mgr_test_config.h
new file mode 100644
index 0000000..2839e4e
--- /dev/null
+++ b/src/lib/dhcp/tests/iface_mgr_test_config.h
@@ -0,0 +1,273 @@
+// Copyright (C) 2014-2023 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 IFACE_MGR_TEST_CONFIG_H
+#define IFACE_MGR_TEST_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+//@{
+/// @brief Index of the lo fake interface.
+const uint32_t LO_INDEX = 0;
+
+/// @brief Index of the eth0 fake interface.
+const uint32_t ETH0_INDEX = 1;
+
+/// @brief Index of the eth1 fake interface.
+const uint32_t ETH1_INDEX = 2;
+
+/// @brief Index of the eth1961 fake interface.
+const uint32_t ETH1961_INDEX = 1962;
+//@}
+
+///
+/// @name Set of structures describing interface flags.
+///
+/// These flags encapsulate the boolean type to pass the flags values
+/// to @c IfaceMgrTestConfig methods. If the values passed to these methods
+/// were not encapsulated by the types defined here, the API would become
+/// prone to errors like swapping parameters being passed to specific functions.
+/// For example, in the call to @c IfaceMgrTestConfig::setIfaceFlags:
+/// @code
+/// IfaceMgrTestConfig test_config(true);
+/// test_config.setIfaceFlags("eth1", false, false, true, false, false);
+/// @endcode
+///
+/// it is quite likely that the developer by mistake swaps the values and
+/// assigns them to wrong flags. When the flags are encapsulated with dedicated
+/// structs, the compiler will return an error if values are swapped. For
+/// example:
+/// @code
+/// IfaceMgrTestConfig test_config(true);
+/// test_config.setIfaceFlags("eth1", FlagLoopback(false), FlagUp(false),
+/// FlagRunning(true), FlagInactive4(false),
+/// FlagInactive6(false));
+/// @endcode
+/// will succeed, but the following code will result in the compilation error
+/// and thus protect a developer from making an error:
+/// @code
+/// IfaceMgrTestConfig test_config(true);
+/// test_config.setIfaceFlags("eth1", FlagLoopback(false),
+/// FlagRunning(true), FlagUp(false),
+/// FlagInactive4(false), FlagInactive6(false));
+/// @endcode
+///
+//@{
+/// @brief Structure describing the loopback interface flag.
+struct FlagLoopback {
+ explicit FlagLoopback(bool flag) : flag_(flag) { }
+ bool flag_;
+};
+
+/// @brief Structure describing the up interface flag.
+struct FlagUp {
+ explicit FlagUp(bool flag) : flag_(flag) { }
+ bool flag_;
+};
+
+/// @brief Structure describing the running interface flag.
+struct FlagRunning {
+ explicit FlagRunning(bool flag) : flag_(flag) { }
+ bool flag_;
+};
+
+/// @brief Structure describing the inactive4 interface flag.
+struct FlagInactive4 {
+ explicit FlagInactive4(bool flag) : flag_(flag) { }
+ bool flag_;
+};
+
+/// @brief Structure describing the inactive6 interface flag.
+struct FlagInactive6 {
+ explicit FlagInactive6(bool flag) : flag_(flag) { }
+ bool flag_;
+};
+//@}
+
+/// @brief Convenience class for configuring @c IfaceMgr for unit testing.
+///
+/// This class is used by various unit tests which test the code relaying
+/// on IfaceMgr. The use of this class is not limited to libdhcp++ validation.
+/// There are other libraries and applications (e.g. DHCP servers) which
+/// depend on @c IfaceMgr.
+///
+/// During the normal operation, the @c IfaceMgr detects interfaces present
+/// on the machine where it is running. It also provides the means for
+/// applications to open sockets on these interfaces and perform other
+/// IO operations. This however creates dependency of the applications
+/// using @c IfaceMgr on the physical properties of the system and effectively
+/// makes it very hard to unit test the dependent code.
+///
+/// Unit tests usually require that @c IfaceMgr holds a list of well known
+/// interfaces with the well known set of IP addresses and other properties
+/// (a.k.a. interface flags). The solution which works for many test scenarios
+/// is to provide a set of well known fake interfaces, by bypassing the
+/// standard interface detection procedure and manually adding @c Iface objects
+/// which encapsulate the fake interfaces. As a consequence, it becomes
+/// impossible to test IO operations (e.g. sending packets) because real sockets
+/// can't be opened on these interfaces. The @c PktFilterTestStub class
+/// is used by this class to mimic behavior of IO operations on fake sockets.
+///
+/// This class provides a set of convenience functions that should be called
+/// by unit tests to configure the @c IfaceMgr with fake interfaces.
+///
+/// The class allows the caller to create custom fake interfaces (with custom
+/// IPv4 and IPv6 addresses, flags etc.), but it also provides a default
+/// test configuration for interfaces as follows:
+/// - lo #0
+/// - 127.0.0.1
+/// - ::1
+/// - eth0 #1
+/// - 10.0.0.1
+/// - fe80::3a60:77ff:fed5:cdef
+/// - 2001:db8:1::1
+/// - eth1 #2
+/// - 192.0.2.3
+/// - fe80::3a60:77ff:fed5:abcd
+/// - eth1961 #1962
+/// - 198.51.100.1
+/// - fe80::3a60:77ff:fed5:9876
+///
+/// For all interfaces the following flags are set:
+/// - multicast
+/// - up
+/// - running
+class IfaceMgrTestConfig : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// It closes all sockets opened by @c IfaceMgr and removes all interfaces
+ /// being used by @c IfaceMgr.
+ IfaceMgrTestConfig(const bool default_config = false);
+
+ /// @brief Destructor.
+ ///
+ /// Closes all currently opened sockets, removes current interfaces and
+ /// sets the default packet filtering classes. The default packet filtering
+ /// classes are used for IO operations on real sockets/interfaces.
+ /// Receiver is stopped.
+ ///
+ /// Destructor also re-detects real interfaces.
+ ~IfaceMgrTestConfig();
+
+ /// @brief Adds new IPv4 or IPv6 address to the interface.
+ ///
+ /// @param iface_name Name of the interface on which new address should
+ /// be configured.
+ /// @param address IPv4 or IPv6 address to be configured on the interface.
+ void addAddress(const std::string& iface_name,
+ const asiolink::IOAddress& address);
+
+ /// @brief Configures new interface for the @c IfaceMgr.
+ ///
+ /// @param iface Object encapsulating interface to be added.
+ void addIface(const IfacePtr& iface);
+
+ /// @brief Configures new interface for the @c IfaceMgr.
+ ///
+ /// @param name Name of the new interface.
+ /// @param ifindex Index for a new interface.
+ void addIface(const std::string& name, const unsigned int ifindex);
+
+ /// @brief Create an object representing interface.
+ ///
+ /// Apart from creating an interface, this function also sets the
+ /// interface flags:
+ /// - loopback flag if interface name is "lo"
+ /// - up always true
+ /// - running always true
+ /// - inactive4 set to false for non-loopback interface
+ /// - inactive6 set to false for non-loopback interface
+ /// - multicast always to true
+ /// - broadcast always to false
+ ///
+ /// @param name A name of the interface to be created.
+ /// @param ifindex An index of the interface to be created.
+ /// @param mac The mac of the interface.
+ ///
+ /// @return An object representing interface.
+ static IfacePtr createIface(const std::string& name,
+ const unsigned int ifindex,
+ const std::string& mac = "08:08:08:08:08:08");
+
+ /// @brief Creates a default (example) set of fake interfaces.
+ void createIfaces();
+
+ /// @brief Returns currently used packet filter for DHCPv4.
+ PktFilterPtr getPacketFilter4() const {
+ return (packet_filter4_);
+ }
+
+ /// @brief Sets the direct response capability for current packet filter.
+ ///
+ /// The test uses stub implementation of packet filter object. It is
+ /// possible to configure that object to report having a capability
+ /// to directly respond to clients which don't have an address yet.
+ /// This function sets this property for packet filter object.
+ ///
+ /// @param direct_resp Value to be set.
+ ///
+ /// @throw isc::Unexpected if unable to set the property.
+ void setDirectResponse(const bool direct_resp);
+
+ /// @brief Sets various flags on the specified interface.
+ ///
+ /// This function configures interface with new values for flags.
+ ///
+ /// @param name Interface name.
+ /// @param loopback Specifies if interface is a loopback interface.
+ /// @param up Specifies if the interface is up.
+ /// @param running Specifies if the interface is running.
+ /// @param inactive4 Specifies if the interface is inactive for V4
+ /// traffic, i.e. @c IfaceMgr opens V4 sockets on this interface.
+ /// @param inactive6 Specifies if the interface is inactive for V6
+ /// traffic, i.e. @c IfaceMgr opens V6 sockets on this interface.
+ void setIfaceFlags(const std::string& name,
+ const FlagLoopback& loopback,
+ const FlagUp& up,
+ const FlagRunning& running,
+ const FlagInactive4& inactive4,
+ const FlagInactive6& inactive6);
+
+ /// @brief Checks if socket of the specified family is opened on interface.
+ ///
+ /// @param iface_name Interface name.
+ /// @param family One of: AF_INET or AF_INET6
+ bool socketOpen(const std::string& iface_name, const int family) const;
+
+ /// @brief Checks is socket is opened on the interface and bound to a
+ /// specified address.
+ ///
+ /// @param iface_name Interface name.
+ /// @param address Address to which the socket is bound.
+ bool socketOpen(const std::string& iface_name,
+ const std::string& address) const;
+
+ /// @brief Checks if unicast socket is opened on interface.
+ ///
+ /// @param iface_name Interface name.
+ bool unicastOpen(const std::string& iface_name) const;
+
+private:
+ /// @brief Currently used packet filter for DHCPv4.
+ PktFilterPtr packet_filter4_;
+
+ /// @brief Currently used packet filter for DHCPv6.
+ PktFilter6Ptr packet_filter6_;
+};
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // IFACE_MGR_TEST_CONFIG_H
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
new file mode 100644
index 0000000..2761319
--- /dev/null
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -0,0 +1,3612 @@
+// Copyright (C) 2011-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_filter6_test_utils.h>
+#include <dhcp/tests/packet_queue_testutils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <fcntl.h>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using boost::scoped_ptr;
+namespace ph = std::placeholders;
+
+namespace {
+
+// Note this is for the *real* loopback interface, *not* the fake one.
+// So in tests using it you have LOOPBACK_NAME, LOOPBACK_INDEX and
+// no "eth0" nor "eth1". In tests not using it you can have "lo", LO_INDEX,
+// "eth0" or "eth1".
+// Name of loopback interface detection.
+const size_t BUF_SIZE = 32;
+// Can be overwritten to "lo0" for instance on BSD systems.
+char LOOPBACK_NAME[BUF_SIZE] = "lo";
+// In fact is never 0, 1 is by far the most likely.
+uint32_t LOOPBACK_INDEX = 0;
+
+// Ports used during testing
+const uint16_t PORT1 = 10547; // V6 socket
+const uint16_t PORT2 = 10548; // V4 socket
+
+// On some systems measured duration of receive6() and receive4() appears to be
+// shorter than select() timeout. This may be the case if different time
+// resolutions are used by these functions. For such cases we set the
+// tolerance to 0.01s.
+const uint32_t TIMEOUT_TOLERANCE = 10000;
+
+// Macro for making select wait time arguments for receive functions
+#define RECEIVE_WAIT_MS(m) 0,(m*1000)
+
+/// This test verifies that the socket read buffer can be used to
+/// receive the data and that the data can be read from it.
+TEST(IfaceTest, readBuffer) {
+ // Create fake interface object.
+ Iface iface("em0", 0);
+ // The size of read buffer should initially be 0 and the returned
+ // pointer should be NULL.
+ ASSERT_EQ(0, iface.getReadBufferSize());
+ EXPECT_EQ(NULL, iface.getReadBuffer());
+
+ // Let's resize the buffer.
+ iface.resizeReadBuffer(256);
+ // Check that the buffer has expected size.
+ ASSERT_EQ(256, iface.getReadBufferSize());
+ // The returned pointer should now be non-NULL.
+ uint8_t* buf_ptr = iface.getReadBuffer();
+ ASSERT_FALSE(buf_ptr == NULL);
+
+ // Use the pointer to set some data.
+ for (size_t i = 0; i < iface.getReadBufferSize(); ++i) {
+ buf_ptr[i] = i;
+ }
+
+ // Get the pointer again and validate the data.
+ buf_ptr = iface.getReadBuffer();
+ ASSERT_EQ(256, iface.getReadBufferSize());
+ for (size_t i = 0; i < iface.getReadBufferSize(); ++i) {
+ // Use assert so as it fails on the first failure, no need
+ // to continue further checks.
+ ASSERT_EQ(i, buf_ptr[i]);
+ }
+}
+
+// Check that counting the number of active addresses on the interface
+// works as expected.
+TEST(IfaceTest, countActive4) {
+ Iface iface("eth0", 0);
+ ASSERT_EQ(0, iface.countActive4());
+
+ iface.addAddress(IOAddress("192.168.0.2"));
+ ASSERT_EQ(1, iface.countActive4());
+
+ iface.addAddress(IOAddress("2001:db8:1::1"));
+ ASSERT_EQ(1, iface.countActive4());
+
+ iface.addAddress(IOAddress("192.168.0.3"));
+ ASSERT_EQ(2, iface.countActive4());
+
+ ASSERT_NO_THROW(iface.setActive(IOAddress("192.168.0.2"), false));
+ ASSERT_EQ(1, iface.countActive4());
+
+ ASSERT_NO_THROW(iface.setActive(IOAddress("192.168.0.3"), false));
+ ASSERT_EQ(0, iface.countActive4());
+}
+
+/// Mock object implementing PktFilter class. It is used by
+/// IfaceMgrTest::setPacketFilter to verify that IfaceMgr::setPacketFilter
+/// sets this object as a handler for opening sockets. This dummy
+/// class simply records that openSocket function was called by
+/// the IfaceMgr as expected.
+///
+/// @todo This class currently doesn't verify that send/receive functions
+/// were called. In order to test it, there is a need to supply dummy
+/// function performing select() on certain sockets. The system select()
+/// call will fail when dummy socket descriptor is provided and thus
+/// TestPktFilter::receive will never be called. The appropriate extension
+/// to IfaceMgr is planned along with implementation of other "Packet
+/// Filters" such as these supporting Linux Packet Filtering and
+/// Berkeley Packet Filtering.
+class TestPktFilter : public PktFilter {
+public:
+
+ /// Constructor
+ TestPktFilter()
+ : open_socket_called_(false) {
+ }
+
+ virtual bool isDirectResponseSupported() const {
+ return (false);
+ }
+
+ /// @brief Pretend to open a socket.
+ ///
+ /// This function doesn't open a real socket. It always returns the
+ /// same fake socket descriptor. It also records the fact that it has
+ /// been called in the public open_socket_called_ member.
+ /// As in the case of opening a real socket, this function will check
+ /// if there is another fake socket "bound" to the same address and port.
+ /// If there is, it will throw an exception. This allows to simulate the
+ /// conditions when one of the sockets can't be open because there is
+ /// a socket already open and test how IfaceMgr will handle it.
+ ///
+ /// @param iface An interface on which the socket is to be opened.
+ /// @param addr An address to which the socket is to be bound.
+ /// @param port A port to which the socket is to be bound.
+ virtual SocketInfo openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool join_multicast,
+ const bool) {
+ // Check if there is any other socket bound to the specified address
+ // and port on this interface.
+ const Iface::SocketCollection& sockets = iface.getSockets();
+ for (Iface::SocketCollection::const_iterator socket = sockets.begin();
+ socket != sockets.end(); ++socket) {
+ if (((socket->addr_ == addr) ||
+ ((socket->addr_ == IOAddress("::")) && join_multicast)) &&
+ socket->port_ == port) {
+ isc_throw(SocketConfigError, "test socket bind error");
+ }
+ }
+ open_socket_called_ = true;
+ return (SocketInfo(addr, port, 255));
+ }
+
+ /// Does nothing
+ virtual Pkt4Ptr receive(Iface&, const SocketInfo&) {
+ return (Pkt4Ptr());
+ }
+
+ /// Does nothing
+ virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
+ return (0);
+ }
+
+ /// Holds the information whether openSocket was called on this
+ /// object after its creation.
+ bool open_socket_called_;
+};
+
+class NakedIfaceMgr: public IfaceMgr {
+ // "Naked" Interface Manager, exposes internal fields
+public:
+
+ /// @brief Constructor.
+ NakedIfaceMgr() {
+ loDetect();
+ }
+
+ /// @brief detects name of the loopback interface
+ ///
+ /// This method detects name of the loopback interface.
+ static void loDetect() {
+ // Poor man's interface detection. It will go away as soon as proper
+ // interface detection is implemented
+ if (if_nametoindex("lo") > 0) {
+ snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo");
+ } else if (if_nametoindex("lo0") > 0) {
+ snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo0");
+ } else {
+ cout << "Failed to detect loopback interface. Neither "
+ << "lo nor lo0 worked. I give up." << endl;
+ FAIL();
+ }
+ LOOPBACK_INDEX = if_nametoindex(LOOPBACK_NAME);
+ }
+
+ /// @brief Returns the collection of existing interfaces.
+ IfaceCollection& getIfacesLst() { return (ifaces_); }
+
+ /// @brief This function creates fictitious interfaces with fictitious
+ /// addresses.
+ ///
+ /// These interfaces can be used in tests that don't actually try
+ /// to open the sockets on these interfaces. Some tests use mock
+ /// objects to mimic sockets being open. These interfaces are
+ /// suitable for such tests.
+ void createIfaces() {
+
+ ifaces_.clear();
+
+ // local loopback
+ IfacePtr lo = createIface("lo", LO_INDEX);
+ lo->addAddress(IOAddress("127.0.0.1"));
+ lo->addAddress(IOAddress("::1"));
+ ifaces_.push_back(lo);
+ // eth0
+ IfacePtr eth0 = createIface("eth0", ETH0_INDEX);
+ eth0->addAddress(IOAddress("10.0.0.1"));
+ eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+ eth0->addAddress(IOAddress("2001:db8:1::1"));
+ ifaces_.push_back(eth0);
+ // eth1
+ IfacePtr eth1 = createIface("eth1", ETH1_INDEX);
+ eth1->addAddress(IOAddress("192.0.2.3"));
+ eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
+ ifaces_.push_back(eth1);
+ }
+
+ /// @brief Create an object representing interface.
+ ///
+ /// Apart from creating an interface, this function also sets the
+ /// interface flags:
+ /// - loopback flag if interface name is "lo"
+ /// - up always true
+ /// - running always true
+ /// - inactive always to false
+ /// - multicast always to true
+ /// - broadcast always to false
+ ///
+ /// If one needs to modify the default flag settings, the setIfaceFlags
+ /// function should be used.
+ ///
+ /// @param name A name of the interface to be created.
+ /// @param ifindex An index of the interface to be created.
+ ///
+ /// @return An object representing interface.
+ static IfacePtr createIface(const std::string& name, const unsigned int ifindex) {
+ IfacePtr iface(new Iface(name, ifindex));
+ if (name == "lo") {
+ iface->flag_loopback_ = true;
+ // Don't open sockets on loopback interface.
+ iface->inactive4_ = true;
+ iface->inactive6_ = true;
+ } else {
+ iface->inactive4_ = false;
+ iface->inactive6_ = false;
+ }
+ iface->flag_multicast_ = true;
+ // On BSD systems, the SO_BINDTODEVICE option is not supported.
+ // Therefore the IfaceMgr will throw an exception on attempt to
+ // open sockets on more than one broadcast-capable interface at
+ // the same time. In order to prevent this error, we mark all
+ // interfaces broadcast-incapable for unit testing.
+ iface->flag_broadcast_ = false;
+ iface->flag_up_ = true;
+ iface->flag_running_ = true;
+ return (iface);
+ }
+
+ /// @brief Checks if the specified interface has a socket bound to a
+ /// specified address.
+ ///
+ /// @param iface_name A name of the interface.
+ /// @param addr An address to be checked for binding.
+ ///
+ /// @return true if there is a socket bound to the specified address.
+ bool isBound(const std::string& iface_name, const std::string& addr) {
+ IfacePtr iface = getIface(iface_name);
+ if (!iface) {
+ ADD_FAILURE() << "the interface " << iface_name << " doesn't exist";
+ return (false);
+ }
+ const Iface::SocketCollection& sockets = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+ sock != sockets.end(); ++sock) {
+ if (sock->addr_ == IOAddress(addr)) {
+ return (true);
+
+ } else if ((sock->addr_ == IOAddress("::")) &&
+ (IOAddress(addr).isV6LinkLocal())) {
+ BOOST_FOREACH(Iface::Address a, iface->getAddresses()) {
+ if (a.get() == IOAddress(addr)) {
+ return (true);
+ }
+ }
+ }
+ }
+ return (false);
+ }
+
+ /// @brief Modify flags on the interface.
+ ///
+ /// @param name A name of the interface.
+ /// @param loopback A new value of the loopback flag.
+ /// @param up A new value of the up flag.
+ /// @param running A new value of the running flag.
+ /// @param inactive A new value of the inactive flag.
+ void setIfaceFlags(const std::string& name, const bool loopback,
+ const bool up, const bool running,
+ const bool inactive4,
+ const bool inactive6) {
+ for (IfacePtr iface : ifaces_) {
+ if (iface->getName() == name) {
+ iface->flag_loopback_ = loopback;
+ iface->flag_up_ = up;
+ iface->flag_running_ = running;
+ iface->inactive4_ = inactive4;
+ iface->inactive6_ = inactive6;
+ }
+ }
+ }
+};
+
+/// @brief A test fixture class for IfaceMgr.
+///
+/// @todo Sockets being opened by IfaceMgr tests should be managed by
+/// the test fixture. In particular, the class should close sockets after
+/// each test. Current approach where test cases are responsible for
+/// closing sockets is resource leak prone, especially in case of the
+/// test failure path.
+class IfaceMgrTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ IfaceMgrTest()
+ : errors_count_(0) {
+ }
+
+ ~IfaceMgrTest() {
+ }
+
+ /// @brief Tests the number of IPv6 sockets on interface
+ ///
+ /// This function checks the expected number of open IPv6 sockets on the
+ /// specified interface. On non-Linux systems, sockets are bound to a
+ /// link-local address and the number of unicast addresses specified.
+ /// On Linux systems, there is one more socket bound to a ff02::1:2
+ /// multicast address.
+ ///
+ /// @param iface An interface on which sockets are open.
+ /// @param unicast_num A number of unicast addresses bound.
+ /// @param link_local_num A number of link local addresses bound.
+ void checkSocketsCount6(const Iface& iface, const int unicast_num,
+ const int link_local_num = 1) {
+ // On local-loopback interface, there should be no sockets.
+ if (iface.flag_loopback_) {
+ ASSERT_TRUE(iface.getSockets().empty())
+ << "expected empty socket set on loopback interface "
+ << iface.getName();
+ return;
+ }
+#if defined OS_LINUX
+ // On Linux, for each link-local address there may be an
+ // additional socket opened and bound to ff02::1:2. This socket
+ // is only opened if the interface is multicast-capable.
+ ASSERT_EQ(unicast_num + (iface.flag_multicast_ ? link_local_num : 0)
+ + link_local_num, iface.getSockets().size())
+ << "invalid number of sockets on interface "
+ << iface.getName();
+#else
+ // On non-Linux, there is no additional socket.
+ ASSERT_EQ(unicast_num + link_local_num, iface.getSockets().size())
+ << "invalid number of sockets on interface "
+ << iface.getName();
+
+#endif
+ }
+
+ // Get the number of IPv4 or IPv6 sockets on the loopback interface
+ int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
+ // Get all sockets.
+ Iface::SocketCollection sockets = iface.getSockets();
+
+ // Loop through sockets and try to find the ones which match the
+ // specified type.
+ int sockets_count = 0;
+ for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+ sock != sockets.end(); ++sock) {
+ // Match found, increase the counter.
+ if (sock->family_ == family) {
+ ++sockets_count;
+ }
+ }
+ return (sockets_count);
+ }
+
+ /// @brief returns socket bound to a specific address (or NULL)
+ ///
+ /// A helper function, used to pick a socketinfo that is bound to a given
+ /// address.
+ ///
+ /// @param sockets sockets collection
+ /// @param addr address the socket is bound to
+ ///
+ /// @return socket info structure (or NULL)
+ const isc::dhcp::SocketInfo*
+ getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets,
+ const IOAddress& addr) {
+ for (isc::dhcp::Iface::SocketCollection::const_iterator s =
+ sockets.begin(); s != sockets.end(); ++s) {
+ if (s->addr_ == addr) {
+ return (&(*s));
+ }
+ }
+ return (NULL);
+ }
+
+ /// @brief Implements an IfaceMgr error handler.
+ ///
+ /// This function can be installed as an error handler for the
+ /// IfaceMgr::openSockets4 function. The error handler is invoked
+ /// when an attempt to open a particular socket fails for any reason.
+ /// Typically, the error handler will log a warning. When the error
+ /// handler returns, the openSockets4 function should continue opening
+ /// sockets on other interfaces.
+ ///
+ /// @param errmsg An error string indicating the reason for failure.
+ void ifaceMgrErrorHandler(const std::string&) {
+ // Increase the counter of invocations to this function. By checking
+ // this number, a test may check if the expected number of errors
+ // has occurred.
+ ++errors_count_;
+ }
+
+ /// @brief Tests the ability to send and receive DHCPv6 packets
+ ///
+ /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the
+ /// given queue configuration. It then calls IfaceMgr::startDHCPReceiver
+ /// and verifies whether or not the receive thread has been started as
+ /// expected. Next it creates a generic DHCPv6 packet and sends it over
+ /// the loop back interface. It invokes IfaceMgr::receive6 to receive the
+ /// packet sent, and compares to the packets for equality.
+ ///
+ /// @param dhcp_queue_control dhcp-queue-control contents to use for the test
+ /// @param exp_queue_enabled flag that indicates if packet queuing is expected
+ /// to be enabled.
+ void sendReceive6Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented
+ // let's assume that every supported OS have lo interface
+ IOAddress lo_addr("::1");
+ int socket1 = 0, socket2 = 0;
+ EXPECT_NO_THROW(
+ socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547);
+ socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10546);
+ );
+
+ EXPECT_GE(socket1, 0);
+ EXPECT_GE(socket2, 0);
+
+ // Configure packet queueing as desired.
+ bool queue_enabled = false;
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, dhcp_queue_control));
+
+ // Verify that we have a queue only if we expected one.
+ ASSERT_EQ(exp_queue_enabled, queue_enabled);
+
+ // Thread should only start when there is a packet queue.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
+ ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning());
+
+ // If the thread is already running, trying to start it again should fail.
+ if (queue_enabled) {
+ ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation);
+ // Should still have one running.
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ // Let's build our DHCPv6 packet.
+ // prepare dummy payload
+ uint8_t data[128];
+ for (uint8_t i = 0; i < 128; i++) {
+ data[i] = i;
+ }
+
+ Pkt6Ptr sendPkt = Pkt6Ptr(new Pkt6(data, 128));
+ sendPkt->repack();
+ sendPkt->setRemotePort(10547);
+ sendPkt->setRemoteAddr(IOAddress("::1"));
+ sendPkt->setIndex(LOOPBACK_INDEX);
+ sendPkt->setIface(LOOPBACK_NAME);
+
+ // Send the packet.
+ EXPECT_EQ(true, ifacemgr->send(sendPkt));
+
+ // Now, let's try and receive it.
+ Pkt6Ptr rcvPkt;
+ rcvPkt = ifacemgr->receive6(10);
+ ASSERT_TRUE(rcvPkt); // received our own packet
+
+ // let's check that we received what was sent
+ ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size());
+ EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
+ rcvPkt->data_.size()));
+
+ EXPECT_EQ(sendPkt->getRemoteAddr(), rcvPkt->getRemoteAddr());
+
+ // since we opened 2 sockets on the same interface and none of them is multicast,
+ // none is preferred over the other for sending data, so we really should not
+ // assume the one or the other will always be chosen for sending data. Therefore
+ // we should accept both values as source ports.
+ EXPECT_TRUE((rcvPkt->getRemotePort() == 10546) || (rcvPkt->getRemotePort() == 10547));
+
+ // Stop the thread. This should be no harm/no foul if we're not
+ // queueuing. Either way, we should not have a thread afterwards.
+ ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ /// @brief Tests the ability to send and receive DHCPv4 packets
+ ///
+ /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the
+ /// given queue configuration. It then calls IfaceMgr::startDHCPReceiver
+ /// and verifies whether or not the receive thread has been started as
+ /// expected. Next it creates a DISCOVER packet and sends it over
+ /// the loop back interface. It invokes IfaceMgr::receive4 to receive the
+ /// packet sent, and compares to the packets for equality.
+ ///
+ /// @param dhcp_queue_control dhcp-queue-control contents to use for the test
+ /// @param exp_queue_enabled flag that indicates if packet queuing is expected
+ /// to be enabled.
+ void sendReceive4Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented.
+ // Let's assume that every supported OS has lo interface
+ IOAddress lo_addr("127.0.0.1");
+ int socket1 = 0;
+ EXPECT_NO_THROW(
+ socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr,
+ DHCP4_SERVER_PORT + 10000);
+ );
+
+ EXPECT_GE(socket1, 0);
+
+ // Configure packet queueing as desired.
+ bool queue_enabled = false;
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, dhcp_queue_control));
+
+ // Verify that we have a queue only if we expected one.
+ ASSERT_EQ(exp_queue_enabled, queue_enabled);
+
+ // Thread should only start when there is a packet queue.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+ ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning());
+
+ // If the thread is already running, trying to start it again should fail.
+ if (queue_enabled) {
+ ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation);
+ // Should still have one running.
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ // Let's construct the packet to send.
+ boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
+ sendPkt->setLocalAddr(IOAddress("127.0.0.1"));
+ sendPkt->setLocalPort(DHCP4_SERVER_PORT + 10000 + 1);
+ sendPkt->setRemotePort(DHCP4_SERVER_PORT + 10000);
+ sendPkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ sendPkt->setIndex(LOOPBACK_INDEX);
+ sendPkt->setIface(string(LOOPBACK_NAME));
+ sendPkt->setHops(6);
+ sendPkt->setSecs(42);
+ sendPkt->setCiaddr(IOAddress("192.0.2.1"));
+ sendPkt->setSiaddr(IOAddress("192.0.2.2"));
+ sendPkt->setYiaddr(IOAddress("192.0.2.3"));
+ sendPkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present.
+ // Workarounds (creating DHCP Message Type Option by hand) are no longer
+ // needed as setDhcpType() is called in constructor.
+
+ uint8_t sname[] = "That's just a string that will act as SNAME";
+ sendPkt->setSname(sname, strlen((const char*)sname));
+ uint8_t file[] = "/another/string/that/acts/as/a/file_name.txt";
+ sendPkt->setFile(file, strlen((const char*)file));
+
+ ASSERT_NO_THROW(
+ sendPkt->pack();
+ );
+
+ // OK, Send the PACKET!
+ bool result = false;
+ EXPECT_NO_THROW(result = ifacemgr->send(sendPkt));
+ EXPECT_TRUE(result);
+
+ // Now let's try and receive it.
+ boost::shared_ptr<Pkt4> rcvPkt;
+ ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10));
+ ASSERT_TRUE(rcvPkt); // received our own packet
+ ASSERT_NO_THROW(
+ rcvPkt->unpack();
+ );
+
+ // let's check that we received what was sent
+ EXPECT_EQ(sendPkt->len(), rcvPkt->len());
+ EXPECT_EQ("127.0.0.1", rcvPkt->getRemoteAddr().toText());
+ EXPECT_EQ(sendPkt->getRemotePort(), rcvPkt->getLocalPort());
+ EXPECT_EQ(sendPkt->getHops(), rcvPkt->getHops());
+ EXPECT_EQ(sendPkt->getOp(), rcvPkt->getOp());
+ EXPECT_EQ(sendPkt->getSecs(), rcvPkt->getSecs());
+ EXPECT_EQ(sendPkt->getFlags(), rcvPkt->getFlags());
+ EXPECT_EQ(sendPkt->getCiaddr(), rcvPkt->getCiaddr());
+ EXPECT_EQ(sendPkt->getSiaddr(), rcvPkt->getSiaddr());
+ EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
+ EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
+ EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
+ EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
+ EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
+ EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
+ EXPECT_EQ(sendPkt->getHlen(), rcvPkt->getHlen());
+
+ // since we opened 2 sockets on the same interface and none of them is multicast,
+ // none is preferred over the other for sending data, so we really should not
+ // assume the one or the other will always be chosen for sending data. We should
+ // skip checking source port of sent address.
+
+ // Close the socket. Further we will test if errors are reported
+ // properly on attempt to use closed socket.
+ close(socket1);
+
+ // @todo Closing the socket does NOT cause a read error out of the
+ // receiveDHCP<X>Packets() select. Apparently this is because the
+ // thread is already inside the select when the socket is closed,
+ // and (at least under Centos 7.5), this does not interrupt the
+ // select. For now, we'll only test this for direct receive.
+ if (!queue_enabled) {
+ EXPECT_THROW(ifacemgr->receive4(10), SocketReadError);
+ }
+
+ // Verify write fails.
+ EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
+
+ // Stop the thread. This should be no harm/no foul if we're not
+ // queueuing. Either way, we should not have a thread afterwards.
+ ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ /// @brief Verifies that IfaceMgr DHCPv4 receive calls detect and
+ /// purge external sockets that have gone bad without affecting
+ /// affecting normal operations. It can be run with or without
+ /// packet queuing.
+ ///
+ /// @param use_queue determines if packet queuing is used or not.
+ void purgeExternalSockets4Test(bool use_queue = false) {
+ bool callback_ok = false;
+ bool callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ if (use_queue) {
+ bool queue_enabled = false;
+ data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500);
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, config));
+ ASSERT_TRUE(queue_enabled);
+
+ // Thread should only start when there is a packet queue.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0],
+ [&callback_ok, &pipefd](int fd) {
+ callback_ok = (pipefd[0] == fd);
+ }));
+
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0],
+ [&callback2_ok, &secondpipe](int fd) {
+ callback2_ok = (secondpipe[0] == fd);
+ }));
+
+ // Verify a call with no data and normal external sockets works ok.
+ Pkt4Ptr pkt4;
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
+
+ // No callback invocations and no DHCPv4 pkt.
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+ EXPECT_FALSE(pkt4);
+
+ // Now close the first pipe. This should make it's external socket invalid.
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ // We call receive4() which should detect and remove the invalid socket.
+ try {
+ pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10));
+ ADD_FAILURE() << "receive4 should have failed";
+ } catch (const SocketReadError& ex) {
+ EXPECT_EQ(std::string("SELECT interrupted by one invalid sockets,"
+ " purged 1 socket descriptors"),
+ std::string(ex.what()));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "wrong exception thrown: " << ex.what();
+ }
+
+ // No callback invocations and no DHCPv4 pkt.
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+ EXPECT_FALSE(pkt4);
+
+ // Now check whether the second callback is still functional
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // Call receive4 again, this should work.
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
+
+ // Should have callback2 data only.
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+ EXPECT_FALSE(pkt4);
+
+ // Stop the thread. This should be no harm/no foul if we're not
+ // queueuing. Either way, we should not have a thread afterwards.
+ ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ /// @brief Verifies that IfaceMgr DHCPv6 receive calls detect and
+ /// purge external sockets that have gone bad without affecting
+ /// affecting normal operations. It can be run with or without
+ /// packet queuing.
+ ///
+ /// @param use_queue determines if packet queuing is used or not.
+ void purgeExternalSockets6Test(bool use_queue = false) {
+ bool callback_ok = false;
+ bool callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ if (use_queue) {
+ bool queue_enabled = false;
+ data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500);
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, config));
+ ASSERT_TRUE(queue_enabled);
+
+ // Thread should only start when there is a packet queue.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0],
+ [&callback_ok, &pipefd](int fd) {
+ callback_ok = (pipefd[0] == fd);
+ }));
+
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0],
+ [&callback2_ok, &secondpipe](int fd) {
+ callback2_ok = (secondpipe[0] == fd);
+ }));
+
+ // Verify a call with no data and normal external sockets works ok.
+ Pkt6Ptr pkt6;
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+
+ // No callback invocations and no DHCPv6 pkt.
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+ EXPECT_FALSE(pkt6);
+
+ // Now close the first pipe. This should make it's external socket invalid.
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ // We call receive6() which should detect and remove the invalid socket.
+ try {
+ pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10));
+ ADD_FAILURE() << "receive6 should have failed";
+ } catch (const SocketReadError& ex) {
+ EXPECT_EQ(std::string("SELECT interrupted by one invalid sockets,"
+ " purged 1 socket descriptors"),
+ std::string(ex.what()));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "wrong exception thrown: " << ex.what();
+ }
+
+ // No callback invocations and no DHCPv6 pkt.
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+ EXPECT_FALSE(pkt6);
+
+ // Now check whether the second callback is still functional
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // Call receive6 again, this should work.
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+
+ // Should have callback2 data only.
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+ EXPECT_FALSE(pkt6);
+
+ // Stop the thread. This should be no harm/no foul if we're not
+ // queueuing. Either way, we should not have a thread afterwards.
+ ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+ }
+
+ /// Holds the invocation counter for ifaceMgrErrorHandler.
+ int errors_count_;
+};
+
+// We need some known interface to work reliably. Loopback interface is named
+// lo on Linux and lo0 on BSD boxes. We need to find out which is available.
+// This is not a real test, but rather a workaround that will go away when
+// interface detection is implemented on all OSes.
+TEST_F(IfaceMgrTest, loDetect) {
+ NakedIfaceMgr::loDetect();
+}
+
+// Uncomment this test to create packet writer. It will
+// write incoming DHCPv6 packets as C arrays. That is useful
+// for generating test sequences based on actual traffic
+//
+// TODO: this potentially should be moved to a separate tool
+//
+
+#if 0
+TEST_F(IfaceMgrTest, dhcp6Sniffer) {
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented
+
+ static_cast<void>(remove("interfaces.txt"));
+
+ ofstream interfaces("interfaces.txt", ios::ate);
+ interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
+ interfaces.close();
+
+ boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr();
+
+ Pkt6Ptr pkt;
+ int cnt = 0;
+ cout << "---8X-----------------------------------------" << endl;
+ while (true) {
+ pkt.reset(ifacemgr->receive());
+
+ cout << "// this code is autogenerated. Do NOT edit." << endl;
+ cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
+ cout << "Pkt6 *capture" << cnt++ << "() {" << endl;
+ cout << " Pkt6* pkt;" << endl;
+ cout << " pkt = new Pkt6(" << pkt->data_len_ << ");" << endl;
+ cout << " pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl;
+ cout << " pkt->remote_addr_ = IOAddress(\""
+ << pkt->remote_addr_ << "\");" << endl;
+ cout << " pkt->local_port_ = " << pkt-> local_port_ << ";" << endl;
+ cout << " pkt->local_addr_ = IOAddress(\""
+ << pkt->local_addr_ << "\");" << endl;
+ cout << " pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl;
+ cout << " pkt->iface_ = \"" << pkt->iface_ << "\";" << endl;
+
+ // TODO it is better to declare statically initialize the array
+ // and then memcpy it to packet.
+ for (int i=0; i< pkt->data_len_; i++) {
+ cout << " pkt->data_[" << i << "]="
+ << (int)(unsigned char)pkt->data_[i] << "; ";
+ if (!(i%4))
+ cout << endl;
+ }
+ cout << endl;
+ cout << " return (pkt);" << endl;
+ cout << "}" << endl << endl;
+
+ pkt.reset();
+ }
+ cout << "---8X-----------------------------------------" << endl;
+
+ // Never happens. Infinite loop is infinite
+}
+#endif
+
+// This test verifies that creation of the IfaceMgr instance doesn't
+// cause an exception.
+TEST_F(IfaceMgrTest, instance) {
+ EXPECT_NO_THROW(IfaceMgr::instance());
+}
+
+// Basic tests for Iface inner class.
+TEST_F(IfaceMgrTest, ifaceClass) {
+
+ IfacePtr iface(new Iface("eth5", 7));
+ EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
+
+ EXPECT_THROW_MSG(iface.reset(new Iface("", 10)), BadValue,
+ "Interface name must not be empty");
+
+ EXPECT_NO_THROW(iface.reset(new Iface("big-index", 66666)));
+ EXPECT_EQ(66666, iface->getIndex());
+}
+
+// This test checks the getIface by index method.
+TEST_F(IfaceMgrTest, getIfaceByIndex) {
+ NakedIfaceMgr ifacemgr;
+
+ // Create a set of fake interfaces. At the same time, remove the actual
+ // interfaces that have been detected by the IfaceMgr.
+ ifacemgr.createIfaces();
+
+ // Getting an unset index should throw.
+ EXPECT_THROW_MSG(ifacemgr.getIface(UNSET_IFINDEX), BadValue, "interface index was not set");
+
+ // Historically -1 was used as an unset value. Let's also check that it throws in case we didn't
+ // migrate all code to UNSET_IFINDEX and in case the values diverge.
+ EXPECT_THROW_MSG(ifacemgr.getIface(-1), BadValue, "interface index was not set");
+
+ // Get the first interface defined.
+ IfacePtr iface(ifacemgr.getIface(0));
+ ASSERT_TRUE(iface);
+ EXPECT_EQ("lo", iface->getName());
+
+ // Attemt to get an undefined interface.
+ iface = ifacemgr.getIface(3);
+ EXPECT_FALSE(iface);
+
+ // Check that we can go past INT_MAX.
+ unsigned int int_max(numeric_limits<int>::max());
+ iface = ifacemgr.getIface(int_max);
+ EXPECT_FALSE(iface);
+ iface = ifacemgr.createIface("wlan0", int_max);
+ ifacemgr.addInterface(iface);
+ iface = ifacemgr.getIface(int_max);
+ EXPECT_TRUE(iface);
+ iface = ifacemgr.getIface(int_max + 1);
+ EXPECT_FALSE(iface);
+ iface = ifacemgr.createIface("wlan1", int_max + 1);
+ ifacemgr.addInterface(iface);
+ iface = ifacemgr.getIface(int_max + 1);
+ EXPECT_TRUE(iface);
+}
+
+// This test checks the getIface by packet method.
+TEST_F(IfaceMgrTest, getIfaceByPkt) {
+ NakedIfaceMgr ifacemgr;
+ // Create a set of fake interfaces. At the same time, remove the actual
+ // interfaces that have been detected by the IfaceMgr.
+ ifacemgr.createIfaces();
+
+ // Try IPv4 packet by name.
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1234));
+ IfacePtr iface = ifacemgr.getIface(pkt4);
+ EXPECT_FALSE(iface);
+ pkt4->setIface("eth0");
+ iface = ifacemgr.getIface(pkt4);
+ EXPECT_TRUE(iface);
+ EXPECT_FALSE(pkt4->indexSet());
+
+ // Try IPv6 packet by index.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456));
+ iface = ifacemgr.getIface(pkt6);
+ EXPECT_FALSE(iface);
+ ASSERT_TRUE(ifacemgr.getIface("eth0"));
+ pkt6->setIndex(ifacemgr.getIface("eth0")->getIndex() + 1);
+ iface = ifacemgr.getIface(pkt6);
+ ASSERT_TRUE(iface);
+ EXPECT_TRUE(pkt6->indexSet());
+
+ // Index has precedence when both name and index are available.
+ EXPECT_EQ("eth1", iface->getName());
+ pkt6->setIface("eth0");
+ iface = ifacemgr.getIface(pkt6);
+ ASSERT_TRUE(iface);
+ EXPECT_EQ("eth1", iface->getName());
+
+ // Not existing name fails.
+ pkt4->setIface("eth2");
+ iface = ifacemgr.getIface(pkt4);
+ EXPECT_FALSE(iface);
+
+ // Not existing index fails.
+ pkt6->setIndex(3);
+ iface = ifacemgr.getIface(pkt6);
+ ASSERT_FALSE(iface);
+
+ // Test that resetting the index is verifiable.
+ pkt4->resetIndex();
+ EXPECT_FALSE(pkt4->indexSet());
+ pkt6->resetIndex();
+ EXPECT_FALSE(pkt6->indexSet());
+
+ // Test that you can also reset the index via setIndex().
+ pkt4->setIndex(UNSET_IFINDEX);
+ EXPECT_FALSE(pkt4->indexSet());
+ pkt6->setIndex(UNSET_IFINDEX);
+ EXPECT_FALSE(pkt6->indexSet());
+}
+
+// Test that the IPv4 address can be retrieved for the interface.
+TEST_F(IfaceMgrTest, ifaceGetAddress) {
+ Iface iface("eth0", 0);
+
+ IOAddress addr("::1");
+ // Initially, the Iface has no addresses assigned.
+ EXPECT_FALSE(iface.getAddress4(addr));
+ // Add some addresses with IPv4 address in the middle.
+ iface.addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+ iface.addAddress(IOAddress("10.1.2.3"));
+ iface.addAddress(IOAddress("2001:db8:1::2"));
+ // The v4 address should be returned.
+ EXPECT_TRUE(iface.getAddress4(addr));
+ EXPECT_EQ("10.1.2.3", addr.toText());
+ // Delete the IPv4 address and leave only two IPv6 addresses.
+ ASSERT_NO_THROW(iface.delAddress(IOAddress("10.1.2.3")));
+ // The IPv4 address should not be returned.
+ EXPECT_FALSE(iface.getAddress4(addr));
+ // Add a different IPv4 address at the end of the list.
+ iface.addAddress(IOAddress("192.0.2.3"));
+ // This new address should now be returned.
+ EXPECT_TRUE(iface.getAddress4(addr));
+ EXPECT_EQ("192.0.2.3", addr.toText());
+}
+
+// This test checks if it is possible to check that the specific address is
+// assigned to the interface.
+TEST_F(IfaceMgrTest, ifaceHasAddress) {
+ IfaceMgrTestConfig config(true);
+
+ IfacePtr iface = IfaceMgr::instance().getIface("eth0");
+ ASSERT_TRUE(iface);
+ EXPECT_TRUE(iface->hasAddress(IOAddress("10.0.0.1")));
+ EXPECT_FALSE(iface->hasAddress(IOAddress("10.0.0.2")));
+ EXPECT_TRUE(iface->hasAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")));
+ EXPECT_TRUE(iface->hasAddress(IOAddress("2001:db8:1::1")));
+ EXPECT_FALSE(iface->hasAddress(IOAddress("2001:db8:1::2")));
+}
+
+// This test checks it is not allowed to add duplicate interfaces.
+TEST_F(IfaceMgrTest, addInterface) {
+ IfaceMgrTestConfig config(true);
+
+ IfacePtr dup_name(new Iface("eth1", 123));
+ EXPECT_THROW_MSG(IfaceMgr::instance().addInterface(dup_name), Unexpected,
+ "Can't add eth1/123 when eth1/2 already exists.");
+ IfacePtr dup_index(new Iface("eth2", 2));
+ EXPECT_THROW_MSG(IfaceMgr::instance().addInterface(dup_index), Unexpected,
+ "Can't add eth2/2 when eth1/2 already exists.");
+
+ IfacePtr eth2(new Iface("eth2", 3));
+ EXPECT_NO_THROW(IfaceMgr::instance().addInterface(eth2));
+}
+
+// TODO: Implement getPlainMac() test as soon as interface detection
+// is implemented.
+TEST_F(IfaceMgrTest, getIface) {
+
+ cout << "Interface checks. Please ignore socket binding errors." << endl;
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Interface name, ifindex
+ IfacePtr iface1(new Iface("lo1", 100));
+ IfacePtr iface2(new Iface("eth9", 101));
+ IfacePtr iface3(new Iface("en99", 102));
+ IfacePtr iface4(new Iface("e1000g4", 103));
+ cout << "This test assumes that there are less than 100 network interfaces"
+ << " in the tested system and there are no lo1, eth9, en99, e1000g4"
+ << " or wifi15 interfaces present." << endl;
+
+ // Note: real interfaces may be detected as well
+ ifacemgr->getIfacesLst().push_back(iface1);
+ ifacemgr->getIfacesLst().push_back(iface2);
+ ifacemgr->getIfacesLst().push_back(iface3);
+ ifacemgr->getIfacesLst().push_back(iface4);
+
+ cout << "There are " << ifacemgr->getIfacesLst().size()
+ << " interfaces." << endl;
+ for (IfacePtr iface : ifacemgr->getIfacesLst()) {
+ cout << " " << iface->getFullName() << endl;
+ }
+
+ // Check that interface can be retrieved by ifindex
+ IfacePtr tmp = ifacemgr->getIface(102);
+ ASSERT_TRUE(tmp);
+
+ EXPECT_EQ("en99", tmp->getName());
+ EXPECT_EQ(102, tmp->getIndex());
+
+ // Check that interface can be retrieved by name
+ tmp = ifacemgr->getIface("lo1");
+ ASSERT_TRUE(tmp);
+
+ EXPECT_EQ("lo1", tmp->getName());
+ EXPECT_EQ(100, tmp->getIndex());
+
+ // Check that non-existing interfaces are not returned
+ EXPECT_FALSE(ifacemgr->getIface("wifi15") );
+}
+
+TEST_F(IfaceMgrTest, clearIfaces) {
+ NakedIfaceMgr ifacemgr;
+ // Create a set of fake interfaces. At the same time, remove the actual
+ // interfaces that have been detected by the IfaceMgr.
+ ifacemgr.createIfaces();
+
+ ASSERT_GT(ifacemgr.countIfaces(), 0);
+
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ ASSERT_NO_THROW(ifacemgr.openSockets4());
+
+ ifacemgr.clearIfaces();
+
+ EXPECT_EQ(0, ifacemgr.countIfaces());
+}
+
+// Verify that we have the expected default DHCPv4 packet queue.
+TEST_F(IfaceMgrTest, packetQueue4) {
+ NakedIfaceMgr ifacemgr;
+
+ // Should not have a queue at start up.
+ ASSERT_FALSE(ifacemgr.getPacketQueue4());
+
+ // Verify that we can create a queue with default factory.
+ data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 2000);
+ ASSERT_NO_THROW(ifacemgr.getPacketQueueMgr4()->createPacketQueue(config));
+ CHECK_QUEUE_INFO(ifacemgr.getPacketQueue4(), "{ \"capacity\": 2000, \"queue-type\": \""
+ << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }");
+}
+
+// Verify that we have the expected default DHCPv6 packet queue.
+TEST_F(IfaceMgrTest, packetQueue6) {
+ NakedIfaceMgr ifacemgr;
+
+ // Should not have a queue at start up.
+ ASSERT_FALSE(ifacemgr.getPacketQueue6());
+
+ // Verify that we can create a queue with default factory.
+ data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 2000);
+ ASSERT_NO_THROW(ifacemgr.getPacketQueueMgr6()->createPacketQueue(config));
+ CHECK_QUEUE_INFO(ifacemgr.getPacketQueue6(), "{ \"capacity\": 2000, \"queue-type\": \""
+ << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }");
+}
+
+TEST_F(IfaceMgrTest, receiveTimeout6) {
+ using namespace boost::posix_time;
+ std::cout << "Testing DHCPv6 packet reception timeouts."
+ << " Test will block for a few seconds when waiting"
+ << " for timeout to occur." << std::endl;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ // Open socket on the lo interface.
+ IOAddress lo_addr("::1");
+ int socket1 = 0;
+ ASSERT_NO_THROW(
+ socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547)
+ );
+ // Socket is open if result is non-negative.
+ ASSERT_GE(socket1, 0);
+ // Start receiver.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
+
+ // Remember when we call receive6().
+ ptime start_time = microsec_clock::universal_time();
+ // Call receive with timeout of 1s + 400000us = 1.4s.
+ Pkt6Ptr pkt;
+ ASSERT_NO_THROW(pkt = ifacemgr->receive6(1, 400000));
+ // Remember when call to receive6() ended.
+ ptime stop_time = microsec_clock::universal_time();
+ // We did not send a packet to lo interface so we expect that
+ // nothing has been received and timeout has been reached.
+ ASSERT_FALSE(pkt);
+ // Calculate duration of call to receive6().
+ time_duration duration = stop_time - start_time;
+ // We stop the clock when the call completes so it does not
+ // precisely reflect the receive timeout. However the
+ // uncertainty should be low enough to expect that measured
+ // value is in the range <1.4s; 1.7s>.
+ EXPECT_GE(duration.total_microseconds(),
+ 1400000 - TIMEOUT_TOLERANCE);
+ EXPECT_LE(duration.total_microseconds(), 1700000);
+
+ // Test timeout shorter than 1s.
+ start_time = microsec_clock::universal_time();
+ ASSERT_NO_THROW(pkt = ifacemgr->receive6(0, 500000));
+ stop_time = microsec_clock::universal_time();
+ ASSERT_FALSE(pkt);
+ duration = stop_time - start_time;
+ // Check if measured duration is within <0.5s; 0.8s>.
+ EXPECT_GE(duration.total_microseconds(),
+ 500000 - TIMEOUT_TOLERANCE);
+ EXPECT_LE(duration.total_microseconds(), 800000);
+
+ // Test with invalid fractional timeout values.
+ EXPECT_THROW(ifacemgr->receive6(0, 1000000), isc::BadValue);
+ EXPECT_THROW(ifacemgr->receive6(1, 1000010), isc::BadValue);
+
+ // Stop receiver.
+ EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver());
+}
+
+TEST_F(IfaceMgrTest, receiveTimeout4) {
+ using namespace boost::posix_time;
+ std::cout << "Testing DHCPv4 packet reception timeouts."
+ << " Test will block for a few seconds when waiting"
+ << " for timeout to occur." << std::endl;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ // Open socket on the lo interface.
+ IOAddress lo_addr("127.0.0.1");
+ int socket1 = 0;
+ ASSERT_NO_THROW(
+ socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10067)
+ );
+ // Socket is open if returned value is non-negative.
+ ASSERT_GE(socket1, 0);
+
+ // Start receiver.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+
+ Pkt4Ptr pkt;
+ // Remember when we call receive4().
+ ptime start_time = microsec_clock::universal_time();
+ // Call receive with timeout of 2s + 300000us = 2.3s.
+ ASSERT_NO_THROW(pkt = ifacemgr->receive4(2, 300000));
+ // Remember when call to receive4() ended.
+ ptime stop_time = microsec_clock::universal_time();
+ // We did not send a packet to lo interface so we expect that
+ // nothing has been received and timeout has been reached.
+ ASSERT_FALSE(pkt);
+ // Calculate duration of call to receive4().
+ time_duration duration = stop_time - start_time;
+ // We stop the clock when the call completes so it does not
+ // precisely reflect the receive timeout. However the
+ // uncertainty should be low enough to expect that measured
+ // value is in the range <2.3s; 2.6s>.
+ EXPECT_GE(duration.total_microseconds(),
+ 2300000 - TIMEOUT_TOLERANCE);
+ EXPECT_LE(duration.total_microseconds(), 2600000);
+
+ // Test timeout shorter than 1s.
+ start_time = microsec_clock::universal_time();
+ ASSERT_NO_THROW(pkt = ifacemgr->receive4(0, 400000));
+ stop_time = microsec_clock::universal_time();
+ ASSERT_FALSE(pkt);
+ duration = stop_time - start_time;
+ // Check if measured duration is within <0.4s; 0.7s>.
+ EXPECT_GE(duration.total_microseconds(),
+ 400000 - TIMEOUT_TOLERANCE);
+ EXPECT_LE(duration.total_microseconds(), 700000);
+
+ // Test with invalid fractional timeout values.
+ EXPECT_THROW(ifacemgr->receive4(0, 1000000), isc::BadValue);
+ EXPECT_THROW(ifacemgr->receive4(2, 1000005), isc::BadValue);
+
+ // Stop receiver.
+ EXPECT_NO_THROW(ifacemgr->stopDHCPReceiver());
+}
+
+TEST_F(IfaceMgrTest, multipleSockets) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Container for initialized socket descriptors
+ std::list<uint16_t> init_sockets;
+
+ // Create socket #1
+ int socket1 = 0;
+ ASSERT_NO_THROW(
+ socket1 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT1, AF_INET);
+ );
+ ASSERT_GE(socket1, 0);
+ init_sockets.push_back(socket1);
+
+ // Create socket #2
+ IOAddress lo_addr("127.0.0.1");
+ int socket2 = 0;
+ ASSERT_NO_THROW(
+ socket2 = ifacemgr->openSocketFromRemoteAddress(lo_addr, PORT2);
+ );
+ ASSERT_GE(socket2, 0);
+ init_sockets.push_back(socket2);
+
+ // Get loopback interface. If we don't find one we are unable to run
+ // this test but we don't want to fail.
+ IfacePtr iface_ptr = ifacemgr->getIface(LOOPBACK_NAME);
+ if (iface_ptr == NULL) {
+ cout << "Local loopback interface not found. Skipping test. " << endl;
+ return;
+ }
+ ASSERT_EQ(LOOPBACK_INDEX, iface_ptr->getIndex());
+ // Once sockets have been successfully opened, they are supposed to
+ // be on the list. Here we start to test if all expected sockets
+ // are on the list and no other (unexpected) socket is there.
+ Iface::SocketCollection sockets = iface_ptr->getSockets();
+ int matched_sockets = 0;
+ for (std::list<uint16_t>::iterator init_sockets_it =
+ init_sockets.begin();
+ init_sockets_it != init_sockets.end(); ++init_sockets_it) {
+ // Set socket descriptors non blocking in order to be able
+ // to call recv() on them without hang.
+ int flags = fcntl(*init_sockets_it, F_GETFL, 0);
+ ASSERT_GE(flags, 0);
+ ASSERT_GE(fcntl(*init_sockets_it, F_SETFL, flags | O_NONBLOCK), 0);
+ // recv() is expected to result in EWOULDBLOCK error on non-blocking
+ // socket in case socket is valid but simply no data are coming in.
+ char buf;
+ recv(*init_sockets_it, &buf, 1, MSG_PEEK);
+ EXPECT_EQ(EWOULDBLOCK, errno);
+ // Apart from the ability to use the socket we want to make
+ // sure that socket on the list is the one that we created.
+ for (Iface::SocketCollection::const_iterator socket_it =
+ sockets.begin(); socket_it != sockets.end(); ++socket_it) {
+ if (*init_sockets_it == socket_it->sockfd_) {
+ // This socket is the one that we created.
+ ++matched_sockets;
+ break;
+ }
+ }
+ }
+ // All created sockets have been matched if this condition works.
+ EXPECT_EQ(sockets.size(), matched_sockets);
+
+ // closeSockets() is the other function that we want to test. It
+ // is supposed to close all sockets so as we will not be able to use
+ // them anymore communication.
+ ifacemgr->closeSockets();
+
+ // Closed sockets are supposed to be removed from the list
+ sockets = iface_ptr->getSockets();
+ ASSERT_EQ(0, sockets.size());
+
+ // We are still in possession of socket descriptors that we created
+ // on the beginning of this test. We can use them to check whether
+ // closeSockets() only removed them from the list or they have been
+ // really closed.
+ for (std::list<uint16_t>::const_iterator init_sockets_it =
+ init_sockets.begin();
+ init_sockets_it != init_sockets.end(); ++init_sockets_it) {
+ // recv() must result in error when using invalid socket.
+ char buf;
+ static_cast<void>(recv(*init_sockets_it, &buf, 1, MSG_PEEK));
+ // EWOULDBLOCK would mean that socket is valid/open but
+ // simply no data is received so we have to check for
+ // other errors.
+ EXPECT_NE(EWOULDBLOCK, errno);
+ }
+}
+
+TEST_F(IfaceMgrTest, sockets6) {
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented.
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ IOAddress lo_addr("::1");
+
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ pkt6->setIface(LOOPBACK_NAME);
+
+ // Bind multicast socket to port 10547
+ int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547);
+ EXPECT_GE(socket1, 0); // socket >= 0
+
+ EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6));
+
+ // Bind unicast socket to port 10548
+ int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10548);
+ EXPECT_GE(socket2, 0);
+
+ // Removed code for binding socket twice to the same address/port
+ // as it caused problems on some platforms (e.g. Mac OS X)
+
+ // Close sockets here because the following tests will want to
+ // open sockets on the same ports.
+ ifacemgr->closeSockets();
+
+ // Use address that is not assigned to LOOPBACK iface.
+ IOAddress invalidAddr("::2");
+ EXPECT_THROW(
+ ifacemgr->openSocket(LOOPBACK_NAME, invalidAddr, 10547),
+ SocketConfigError
+ );
+
+ // Use non-existing interface name.
+ EXPECT_THROW(
+ ifacemgr->openSocket("non_existing_interface", lo_addr, 10548),
+ BadValue
+ );
+
+ // Do not call closeSockets() because it is called by IfaceMgr's
+ // virtual destructor.
+}
+
+TEST_F(IfaceMgrTest, socketsFromIface) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Open v6 socket on loopback interface and bind to port
+ int socket1 = 0;
+ EXPECT_NO_THROW(
+ socket1 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT1, AF_INET6);
+ );
+ // Socket descriptor must be non-negative integer
+ EXPECT_GE(socket1, 0);
+ close(socket1);
+
+ // Open v4 socket on loopback interface and bind to different port
+ int socket2 = 0;
+ EXPECT_NO_THROW(
+ socket2 = ifacemgr->openSocketFromIface(LOOPBACK_NAME, PORT2, AF_INET);
+ );
+ // socket descriptor must be non-negative integer
+ EXPECT_GE(socket2, 0);
+ close(socket2);
+
+ // Close sockets here because the following tests will want to
+ // open sockets on the same ports.
+ ifacemgr->closeSockets();
+
+ // Use invalid interface name.
+ EXPECT_THROW(
+ ifacemgr->openSocketFromIface("non_existing_interface", PORT1, AF_INET),
+ BadValue
+ );
+
+ // Do not call closeSockets() because it is called by IfaceMgr's
+ // virtual destructor.
+}
+
+
+TEST_F(IfaceMgrTest, socketsFromAddress) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Open v6 socket on loopback interface and bind to port
+ int socket1 = 0;
+ IOAddress lo_addr6("::1");
+ EXPECT_NO_THROW(
+ socket1 = ifacemgr->openSocketFromAddress(lo_addr6, PORT1);
+ );
+ // socket descriptor must be non-negative integer
+ EXPECT_GE(socket1, 0);
+
+ // Open v4 socket on loopback interface and bind to different port
+ int socket2 = 0;
+ IOAddress lo_addr("127.0.0.1");
+ EXPECT_NO_THROW(
+ socket2 = ifacemgr->openSocketFromAddress(lo_addr, PORT2);
+ );
+ // socket descriptor must be positive integer
+ EXPECT_GE(socket2, 0);
+
+ // Close sockets here because the following tests will want to
+ // open sockets on the same ports.
+ ifacemgr->closeSockets();
+
+ // Use non-existing address.
+ IOAddress invalidAddr("1.2.3.4");
+ EXPECT_THROW(
+ ifacemgr->openSocketFromAddress(invalidAddr, PORT1), BadValue
+ );
+
+ // Do not call closeSockets() because it is called by IfaceMgr's
+ // virtual destructor.
+}
+
+TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Open v6 socket to connect to remote address.
+ // Loopback address is the only one that we know
+ // so let's treat it as remote address.
+ int socket1 = 0;
+ IOAddress lo_addr6("::1");
+ EXPECT_NO_THROW(
+ socket1 = ifacemgr->openSocketFromRemoteAddress(lo_addr6, PORT1);
+ );
+ EXPECT_GE(socket1, 0);
+
+ // Open v4 socket to connect to remote address.
+ int socket2 = 0;
+ IOAddress lo_addr("127.0.0.1");
+ EXPECT_NO_THROW(
+ socket2 = ifacemgr->openSocketFromRemoteAddress(lo_addr, PORT2);
+ );
+ EXPECT_GE(socket2, 0);
+
+ // Close sockets here because the following tests will want to
+ // open sockets on the same ports.
+ ifacemgr->closeSockets();
+
+ // There used to be a check here that verified the ability to open
+ // suitable socket for sending broadcast request. However,
+ // there is no guarantee for such test to work on all systems
+ // because some systems may have no broadcast capable interfaces at all.
+ // Thus, this check has been removed.
+
+ // Do not call closeSockets() because it is called by IfaceMgr's
+ // virtual destructor.
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
+ // testing socket operation in a portable way is tricky
+ // without interface detection implemented
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ IOAddress lo_addr("::1");
+ IOAddress mcastAddr("ff02::1:2");
+
+ // bind multicast socket to port 10547
+ int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, mcastAddr, 10547);
+ EXPECT_GE(socket1, 0); // socket > 0
+
+ // expect success. This address/port is already bound, but
+ // we are using SO_REUSEADDR, so we can bind it twice
+ int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, mcastAddr, 10547);
+ EXPECT_GE(socket2, 0);
+
+ // there's no good way to test negative case here.
+ // we would need non-multicast interface. We will be able
+ // to iterate thru available interfaces and check if there
+ // are interfaces without multicast-capable flag.
+
+ close(socket1);
+ close(socket2);
+}
+
+// Verifies that basic DHPCv6 packet send and receive operates
+// in either direct or indirect mode.
+TEST_F(IfaceMgrTest, sendReceive6) {
+ data::ElementPtr queue_control;
+
+ // Given an empty pointer, queueing should be disabled.
+ // This should do direct reception.
+ sendReceive6Test(queue_control, false);
+
+ // Now let's populate queue control.
+ queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false);
+ // With queueing disabled, we should use direct reception.
+ sendReceive6Test(queue_control, false);
+
+ // Queuing enabled, indirection reception should work.
+ queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
+ sendReceive6Test(queue_control, true);
+}
+
+// Verifies that basic DHPCv4 packet send and receive operates
+// in either direct or indirect mode.
+TEST_F(IfaceMgrTest, sendReceive4) {
+ data::ElementPtr queue_control;
+
+ // Given an empty pointer, queueing should be disabled.
+ // This should do direct reception.
+ sendReceive4Test(queue_control, false);
+
+ // Now let's populate queue control.
+ queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false);
+ // With queueing disabled, we should use direct reception.
+ sendReceive4Test(queue_control, false);
+
+ // Queuing enabled, indirection reception should work.
+ queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
+ sendReceive4Test(queue_control, true);
+}
+
+// Verifies that it is possible to set custom packet filter object
+// to handle sockets opening and send/receive operation.
+TEST_F(IfaceMgrTest, setPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Try to set NULL packet filter object and make sure it is rejected.
+ boost::shared_ptr<TestPktFilter> custom_packet_filter;
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ isc::dhcp::InvalidPacketFilter);
+
+ // Create valid object and check if it can be set.
+ custom_packet_filter.reset(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+
+ // Try to open socket using IfaceMgr. It should call the openSocket() function
+ // on the packet filter object we have set.
+ IOAddress lo_addr("127.0.0.1");
+ int socket1 = 0;
+ EXPECT_NO_THROW(
+ socket1 = iface_mgr->openSocket(LOOPBACK_NAME, lo_addr,
+ DHCP4_SERVER_PORT + 10000);
+ );
+
+ // Check that openSocket function was called.
+ EXPECT_TRUE(custom_packet_filter->open_socket_called_);
+ // This function always returns fake socket descriptor equal to 255.
+ EXPECT_EQ(255, socket1);
+
+ // Replacing current packet filter object while there are IPv4
+ // sockets open is not allowed!
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ PacketFilterChangeDenied);
+
+ // So, let's close the open sockets and retry. Now it should succeed.
+ iface_mgr->closeSockets();
+ EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+}
+
+// This test checks that the default packet filter for DHCPv6 can be replaced
+// with the custom one.
+TEST_F(IfaceMgrTest, setPacketFilter6) {
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Try to set NULL packet filter object and make sure it is rejected.
+ boost::shared_ptr<PktFilter6Stub> custom_packet_filter;
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ isc::dhcp::InvalidPacketFilter);
+
+ // Create valid object and check if it can be set.
+ custom_packet_filter.reset(new PktFilter6Stub());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+
+ // Try to open socket using IfaceMgr. It should call the openSocket()
+ // function on the packet filter object we have set.
+ IOAddress lo_addr("::1");
+ int socket1 = 0;
+ EXPECT_NO_THROW(
+ socket1 = iface_mgr->openSocket(LOOPBACK_NAME, lo_addr,
+ DHCP6_SERVER_PORT + 10000);
+ );
+ // Check that openSocket function has been actually called on the packet
+ // filter object.
+ EXPECT_EQ(1, custom_packet_filter->open_socket_count_);
+ // Also check that the returned socket descriptor has an expected value.
+ EXPECT_EQ(0, socket1);
+
+ // Replacing current packet filter object, while there are sockets open,
+ // is not allowed!
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ PacketFilterChangeDenied);
+
+ // So, let's close the sockets and retry. Now it should succeed.
+ iface_mgr->closeSockets();
+ EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+}
+
+#if defined OS_LINUX || OS_BSD
+
+// This test is only supported on Linux and BSD systems. It checks
+// if it is possible to use the IfaceMgr to select the packet filter
+// object which can be used to send direct responses to the host
+// which doesn't have an address yet.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Let IfaceMgr figure out which Packet Filter to use when
+ // direct response capability is not desired. It should pick
+ // PktFilterInet on Linux.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+ // The PktFilterInet is supposed to report lack of direct
+ // response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+ // There is working implementation of direct responses on Linux
+ // and BSD (using PktFilterLPF and PktFilterBPF. When direct
+ // responses are desired the object of this class should be set.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+ // This object should report that direct responses are supported.
+ EXPECT_TRUE(iface_mgr->isDirectResponseSupported());
+}
+
+// This test checks that it is not possible to open two sockets: IP/UDP
+// and raw socket and bind to the same address and port. The
+// raw socket should be opened together with the fallback IP/UDP socket.
+// The fallback socket should fail to open when there is another IP/UDP
+// socket bound to the same address and port. Failing to open the fallback
+// socket should preclude the raw socket from being open.
+TEST_F(IfaceMgrTest, checkPacketFilterRawSocket) {
+ IOAddress lo_addr("127.0.0.1");
+ int socket1 = -1, socket2 = -1;
+ // Create two instances of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr1(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr1);
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr2(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr2);
+
+ // Let IfaceMgr figure out which Packet Filter to use when
+ // direct response capability is not desired. It should pick
+ // PktFilterInet.
+ EXPECT_NO_THROW(iface_mgr1->setMatchingPacketFilter(false));
+ // Let's open a loopback socket with handy unpriviliged port number
+ socket1 = iface_mgr1->openSocket(LOOPBACK_NAME, lo_addr,
+ DHCP4_SERVER_PORT + 10000);
+
+ EXPECT_GE(socket1, 0);
+
+ // Then the second use PkFilterLPF mode
+ EXPECT_NO_THROW(iface_mgr2->setMatchingPacketFilter(true));
+
+ // The socket is open and bound. Another attempt to open socket and
+ // bind to the same address and port should result in an exception.
+ EXPECT_THROW(
+ socket2 = iface_mgr2->openSocket(LOOPBACK_NAME, lo_addr,
+ DHCP4_SERVER_PORT + 10000),
+ isc::dhcp::SocketConfigError
+ );
+ // Surprisingly we managed to open another socket. We have to close it
+ // to prevent resource leak.
+ if (socket2 >= 0) {
+ close(socket2);
+ ADD_FAILURE() << "Two sockets opened and bound to the same IP"
+ " address and UDP port";
+ }
+
+ if (socket1 >= 0) {
+ close(socket1);
+ }
+}
+
+#else
+
+// Note: This test will only run on non-Linux and non-BSD systems.
+// This test checks whether it is possible to use IfaceMgr to figure
+// out which Packet Filter object should be used when direct responses
+// to hosts, having no address assigned are desired or not desired.
+// Since direct responses aren't supported on systems other than Linux
+// and BSD the function under test should always set object of
+// PktFilterInet type as current Packet Filter. This object does not
+//support direct responses. Once implementation is added on systems
+// other than BSD and Linux the OS specific version of the test will
+// be removed.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Let IfaceMgr figure out which Packet Filter to use when
+ // direct response capability is not desired. It should pick
+ // PktFilterInet.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+ // The PktFilterInet is supposed to report lack of direct
+ // response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+ // On non-Linux systems, we are missing the direct traffic
+ // implementation. Therefore, we expect that PktFilterInet
+ // object will be set.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+ // This object should report lack of direct response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+}
+
+#endif
+
+TEST_F(IfaceMgrTest, socket4) {
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Let's assume that every supported OS have lo interface.
+ IOAddress lo_addr("127.0.0.1");
+ // Use unprivileged port (it's convenient for running tests as non-root).
+ int socket1 = 0;
+
+ EXPECT_NO_THROW(
+ socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr,
+ DHCP4_SERVER_PORT + 10000);
+ );
+
+ EXPECT_GE(socket1, 0);
+
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
+ pkt->setIface(LOOPBACK_NAME);
+ pkt->setIndex(LOOPBACK_INDEX);
+
+ // Expect that we get the socket that we just opened.
+ EXPECT_EQ(socket1, ifacemgr->getSocket(pkt).sockfd_);
+
+ close(socket1);
+}
+
+// This test verifies that IPv4 sockets are open on all interfaces (except
+// loopback), when interfaces are up, running and active (not disabled from
+// the DHCP configuration).
+TEST_F(IfaceMgrTest, openSockets4) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ // Use the custom packet filter object. This object mimics the socket
+ // opening operation - the real socket is not open.
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
+
+ // Expect that the sockets are open on both eth0 and eth1.
+ EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size());
+ // Socket shouldn't have been opened on loopback.
+ EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+ EXPECT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty());
+}
+
+// This test verifies that IPv4 sockets are open on the loopback interface
+// when the loopback is active and allowed.
+TEST_F(IfaceMgrTest, openSockets4Loopback) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ // Allow the loopback interface.
+ ifacemgr.setAllowLoopBack(true);
+
+ // Make the loopback interface active.
+ ifacemgr.getIface("lo")->inactive4_ = false;
+
+ // Use the custom packet filter object. This object mimics the socket
+ // opening operation - the real socket is not open.
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
+
+ // Expect that the sockets are open on all interfaces.
+ EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface(LO_INDEX)->getSockets().size());
+}
+
+// This test verifies that the socket is not open on the interface which is
+// down, but sockets are open on all other non-loopback interfaces.
+TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
+ IfaceMgrTestConfig config(true);
+
+ // Boolean parameters specify that eth0 is:
+ // - not a loopback
+ // - is "down" (not up)
+ // - is not running
+ // - is active (is not inactive)
+ config.setIfaceFlags("eth0", FlagLoopback(false), FlagUp(false),
+ FlagRunning(false), FlagInactive4(false),
+ FlagInactive6(false));
+ ASSERT_FALSE(IfaceMgr::instance().getIface("eth0")->flag_up_);
+ ASSERT_FALSE(IfaceMgr::instance().getIface(ETH0_INDEX)->flag_up_);
+
+ // Install an error handler before trying to open sockets. This handler
+ // should be called when the IfaceMgr fails to open socket on an interface
+ // on which the server is configured to listen.
+ isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
+ std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1);
+
+ ASSERT_NO_THROW(IfaceMgr::instance().openSockets4(DHCP4_SERVER_PORT, true,
+ error_handler));
+ // Since the interface is down, an attempt to open a socket should result
+ // in error.
+ EXPECT_EQ(1, errors_count_);
+
+ // There should be no socket on eth0 open, because interface was down.
+ EXPECT_TRUE(IfaceMgr::instance().getIface("eth0")->getSockets().empty());
+ EXPECT_TRUE(IfaceMgr::instance().getIface(ETH0_INDEX)->getSockets().empty());
+
+ // Expecting that the socket is open on eth1 because it was up, running
+ // and active.
+ EXPECT_EQ(2, IfaceMgr::instance().getIface("eth1")->getSockets().size());
+ EXPECT_EQ(2, IfaceMgr::instance().getIface(ETH1_INDEX)->getSockets().size());
+ // Same for eth1961.
+ EXPECT_EQ(1, IfaceMgr::instance().getIface("eth1961")->getSockets().size());
+ EXPECT_EQ(1, IfaceMgr::instance().getIface(ETH1961_INDEX)->getSockets().size());
+ // Never open socket on loopback interface.
+ EXPECT_TRUE(IfaceMgr::instance().getIface("lo")->getSockets().empty());
+ EXPECT_TRUE(IfaceMgr::instance().getIface(LO_INDEX)->getSockets().empty());
+}
+
+// This test verifies that the socket is not open on the interface which is
+// disabled from the DHCP configuration, but sockets are open on all other
+// non-loopback interfaces.
+TEST_F(IfaceMgrTest, openSockets4IfaceInactive) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Boolean parameters specify that eth1 is:
+ // - not a loopback
+ // - is up
+ // - is running
+ // - is inactive
+ ifacemgr.setIfaceFlags("eth1", false, true, true, true, false);
+ ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_);
+ ASSERT_TRUE(ifacemgr.getIface(ETH1_INDEX)->inactive4_);
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
+
+ // The socket on eth0 should be open because interface is up, running and
+ // active (not disabled through DHCP configuration, for example).
+ EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size());
+ // There should be no socket open on eth1 because it was marked inactive.
+ EXPECT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty());
+ EXPECT_TRUE(ifacemgr.getIface(ETH1_INDEX)->getSockets().empty());
+ // Sockets are not open on loopback interfaces too.
+ EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+ EXPECT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty());
+}
+
+// Test that exception is thrown when trying to bind a new socket to the port
+// and address which is already in use by another socket.
+TEST_F(IfaceMgrTest, openSockets4NoErrorHandler) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Open socket on eth1. The openSockets4 should detect that this
+ // socket has been already open and an attempt to open another socket
+ // and bind to this address and port should fail.
+ ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"),
+ DHCP4_SERVER_PORT));
+
+ // The function throws an exception when it tries to open a socket
+ // and bind it to the address in use.
+ EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0),
+ isc::dhcp::SocketConfigError);
+}
+
+// Test that the external error handler is called when trying to bind a new
+// socket to the address and port being in use. The sockets on the other
+// interfaces should open just fine.
+TEST_F(IfaceMgrTest, openSocket4ErrorHandler) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Open socket on eth0.
+ ASSERT_NO_THROW(ifacemgr.openSocket("eth0", IOAddress("10.0.0.1"),
+ DHCP4_SERVER_PORT));
+
+ // Install an error handler before trying to open sockets. This handler
+ // should be called when the IfaceMgr fails to open socket on eth0.
+ isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
+ std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1);
+ // The openSockets4 should detect that there is another socket already
+ // open and bound to the same address and port. An attempt to open
+ // another socket and bind to this address and port should fail.
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler));
+ // We expect that an error occurred when we tried to open a socket on
+ // eth0, but the socket on eth1 should open just fine.
+ EXPECT_EQ(1, errors_count_);
+
+ // Reset errors count.
+ errors_count_ = 0;
+
+ // Now that we have two sockets open, we can try this again but this time
+ // we should get two errors: one when opening a socket on eth0, another one
+ // when opening a socket on eth1.
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler));
+ EXPECT_EQ(2, errors_count_);
+}
+
+// Test that no exception is thrown when a port is already bound but skip open
+// flag is provided.
+TEST_F(IfaceMgrTest, openSockets4SkipOpen) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Open socket on eth1. The openSockets4 should detect that this
+ // socket has been already open and an attempt to open another socket
+ // and bind to this address and port should fail.
+ ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"),
+ DHCP4_SERVER_PORT));
+
+ // The function doesn't throw an exception when it tries to open a socket
+ // and bind it to the address in use but the skip open flag is provided.
+ EXPECT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0, true));
+
+ // Check that the other port is bound.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1")));
+}
+
+// This test verifies that the function correctly checks that the v4 socket is
+// open and bound to a specific address.
+TEST_F(IfaceMgrTest, hasOpenSocketForAddress4) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ // Use the custom packet filter object. This object mimics the socket
+ // opening operation - the real socket is not open.
+ boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0));
+
+ // Expect that the sockets are open on both eth0 and eth1.
+ ASSERT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
+ ASSERT_EQ(1, ifacemgr.getIface(ETH0_INDEX)->getSockets().size());
+ ASSERT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
+ ASSERT_EQ(1, ifacemgr.getIface(ETH1_INDEX)->getSockets().size());
+ // Socket shouldn't have been opened on loopback.
+ ASSERT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
+ ASSERT_TRUE(ifacemgr.getIface(LO_INDEX)->getSockets().empty());
+
+ // Check that there are sockets bound to addresses that we have
+ // set for interfaces.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("192.0.2.3")));
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1")));
+ // Check that there is no socket for the address which is not
+ // configured on any interface.
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("10.1.1.1")));
+
+ // Check that v4 sockets are open, but no v6 socket is open.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET));
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET6));
+}
+
+// This test checks that the sockets are open and bound to link local addresses
+// only, if unicast addresses are not specified.
+TEST_F(IfaceMgrTest, openSockets6LinkLocal) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Check that the number of sockets is correct on each interface.
+ checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth0"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0);
+
+ // Sockets on eth0 should be bound to link-local and should not be bound
+ // to global unicast address, even though this address is configured on
+ // the eth0.
+ EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
+ // Socket on eth1 should be bound to link local only.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // If we are on Linux, there is one more socket bound to ff02::1:2
+#if defined OS_LINUX
+ EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+ EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+#endif
+}
+
+// This test checks that the sockets are open on the loopback interface
+// when the loopback is active and allowed.
+TEST_F(IfaceMgrTest, openSockets6Loopback) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ // Allow the loopback interface.
+ ifacemgr.setAllowLoopBack(true);
+
+ // Make the loopback interface active.
+ ifacemgr.getIface("lo")->inactive6_ = false;
+
+ // The loopback interface has no link-local (as for Linux but not BSD)
+ // so add one.
+ ifacemgr.getIface("lo")->addUnicast(IOAddress("::1"));
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Check that the loopback interface has at least an open socket.
+ EXPECT_EQ(1, ifacemgr.getIface("lo")->getSockets().size());
+ EXPECT_EQ(1, ifacemgr.getIface(LO_INDEX)->getSockets().size());
+
+ // This socket should be bound to ::1
+ EXPECT_TRUE(ifacemgr.isBound("lo", "::1"));
+}
+
+// This test checks that socket is not open on the interface which doesn't
+// have a link-local address.
+TEST_F(IfaceMgrTest, openSockets6NoLinkLocal) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Remove a link local address from eth0. If there is no link-local
+ // address, the socket should not open.
+ ASSERT_TRUE(ifacemgr.getIface("eth0")->
+ delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")));
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Check that the number of sockets is correct on each interface.
+ checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0);
+ // The third parameter specifies that the number of link-local
+ // addresses for eth0 is equal to 0.
+ checkSocketsCount6(*ifacemgr.getIface("eth0"), 0, 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0, 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth1"), 0, 1);
+ checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0, 1);
+
+ // There should be no sockets open on eth0 because it neither has
+ // link-local nor global unicast addresses.
+ EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
+ // Socket on eth1 should be bound to link local only.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // If we are on Linux, there is one more socket bound to ff02::1:2
+#if defined OS_LINUX
+ EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+#endif
+}
+
+// This test checks that socket is open on the non-multicast-capable
+// interface. This socket simply doesn't join multicast group.
+TEST_F(IfaceMgrTest, openSockets6NotMulticast) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Make eth0 multicast-incapable.
+ ifacemgr.getIface("eth0")->flag_multicast_ = false;
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Check that the number of sockets is correct on each interface.
+ checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth0"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0);
+
+ // Sockets on eth0 should be bound to link-local and should not be bound
+ // to global unicast address, even though this address is configured on
+ // the eth0.
+ EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
+ // The eth0 is not a multicast-capable interface, so the socket should
+ // not be bound to the multicast address.
+ EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+ // Socket on eth1 should be bound to link local only.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // If we are on Linux, there is one more socket bound to ff02::1:2
+ // on eth1.
+#if defined OS_LINUX
+ EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+#endif
+}
+
+// This test checks that the sockets are opened and bound to link local
+// and unicast addresses which have been explicitly specified.
+TEST_F(IfaceMgrTest, openSockets6Unicast) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Configure the eth0 to open socket on the unicast address, apart
+ // from link-local address.
+ ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Check that we have correct number of sockets on each interface.
+ checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address.
+ checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1);
+ checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0);
+
+ // eth0 should have two sockets, one bound to link-local, another one
+ // bound to unicast address.
+ EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
+ // eth1 should have one socket, bound to link-local address.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // If we are on Linux, there is one more socket bound to ff02::1:2
+#if defined OS_LINUX
+ EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+ EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+#endif
+}
+
+// This test checks that the socket is open and bound to a global unicast
+// address if the link-local address does not exist for the particular
+// interface.
+TEST_F(IfaceMgrTest, openSockets6UnicastOnly) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Configure the eth0 to open socket on the unicast address, apart
+ // from link-local address.
+ ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
+ // Explicitly remove the link-local address from eth0.
+ ASSERT_TRUE(ifacemgr.getIface("eth0")->
+ delAddress(IOAddress("fe80::3a60:77ff:fed5:cdef")));
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Check that we have correct number of sockets on each interface.
+ checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth0"), 1, 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1, 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0);
+
+ // The link-local address is not present on eth0. Therefore, no socket
+ // must be bound to this address, nor to multicast address.
+ EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+ // There should be one socket bound to unicast address.
+ EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
+ // eth1 should have one socket, bound to link-local address.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // If we are on Linux, there is one more socket bound to ff02::1:2
+#if defined OS_LINUX
+ EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+#endif
+}
+
+// This test checks that no sockets are open for the interface which is down.
+TEST_F(IfaceMgrTest, openSockets6IfaceDown) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Configure the eth0 to open socket on the unicast address, apart
+ // from link-local address.
+ ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
+
+ // Boolean parameters specify that eth0 is:
+ // - not a loopback
+ // - is "down" (not up)
+ // - is not running
+ // - is active for both v4 and v6
+ ifacemgr.setIfaceFlags("eth0", false, false, false, false, false);
+
+ // Install an error handler before trying to open sockets. This handler
+ // should be called when the IfaceMgr fails to open socket on eth0.
+ isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
+ std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1);
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT,
+ error_handler));
+ EXPECT_TRUE(success);
+
+ // Opening socket on the interface which is not configured, should
+ // result in error.
+ EXPECT_EQ(1, errors_count_);
+
+ // Check that we have correct number of sockets on each interface.
+ checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0);
+ // There should be no sockets on eth0 because interface is down.
+ ASSERT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty());
+ ASSERT_TRUE(ifacemgr.getIface(ETH0_INDEX)->getSockets().empty());
+ checkSocketsCount6(*ifacemgr.getIface("eth1"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(ETH1_INDEX), 0);
+
+ // eth0 should have no sockets because the interface is down.
+ EXPECT_FALSE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_FALSE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
+ EXPECT_FALSE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+ // eth1 should have one socket, bound to link-local address.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // If we are on Linux, there is one more socket bound to ff02::1:2
+#if defined OS_LINUX
+ EXPECT_TRUE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+#endif
+}
+
+// This test checks that no sockets are open for the interface which is
+// inactive.
+TEST_F(IfaceMgrTest, openSockets6IfaceInactive) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Configure the eth0 to open socket on the unicast address, apart
+ // from link-local address.
+ ifacemgr.getIface("eth0")->addUnicast(IOAddress("2001:db8:1::1"));
+
+ // Boolean parameters specify that eth1 is:
+ // - not a loopback
+ // - is up
+ // - is running
+ // - is active for v4
+ // - is inactive for v6
+ ifacemgr.setIfaceFlags("eth1", false, true, true, false, true);
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Check that we have correct number of sockets on each interface.
+ checkSocketsCount6(*ifacemgr.getIface("lo"), 0);
+ checkSocketsCount6(*ifacemgr.getIface(LO_INDEX), 0);
+ checkSocketsCount6(*ifacemgr.getIface("eth0"), 1); // one unicast address
+ checkSocketsCount6(*ifacemgr.getIface(ETH0_INDEX), 1);
+ // There should be no sockets on eth1 because interface is inactive
+ ASSERT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty());
+ ASSERT_TRUE(ifacemgr.getIface(ETH1_INDEX)->getSockets().empty());
+
+ // eth0 should have one socket bound to a link-local address, another one
+ // bound to unicast address.
+ EXPECT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_TRUE(ifacemgr.isBound("eth0", "2001:db8:1::1"));
+
+ // eth1 shouldn't have a socket bound to link local address because
+ // interface is inactive.
+ EXPECT_FALSE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+ EXPECT_FALSE(ifacemgr.isBound("eth1", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+
+ // If we are on Linux, there is one more socket bound to ff02::1:2
+#if defined OS_LINUX
+ EXPECT_TRUE(ifacemgr.isBound("eth0", ALL_DHCP_RELAY_AGENTS_AND_SERVERS));
+#endif
+}
+
+// Test that the openSockets6 function does not throw if there are no interfaces
+// detected. This function is expected to return false.
+TEST_F(IfaceMgrTest, openSockets6NoIfaces) {
+ NakedIfaceMgr ifacemgr;
+ // Remove existing interfaces.
+ ifacemgr.getIfacesLst().clear();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // This value indicates if at least one socket opens. There are no
+ // interfaces, so it should be set to false.
+ bool socket_open = false;
+ ASSERT_NO_THROW(socket_open = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_FALSE(socket_open);
+}
+
+// Test that the external error handler is called when trying to bind a new
+// socket to the address and port being in use. The sockets on the other
+// interfaces should open just fine.
+TEST_F(IfaceMgrTest, openSockets6ErrorHandler) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Open multicast socket on eth0.
+ ASSERT_NO_THROW(ifacemgr.openSocket("eth0",
+ IOAddress("fe80::3a60:77ff:fed5:cdef"),
+ DHCP6_SERVER_PORT, true));
+
+ // Install an error handler before trying to open sockets. This handler
+ // should be called when the IfaceMgr fails to open socket on eth0.
+ isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
+ std::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, ph::_1);
+ // The openSockets6 should detect that a socket has been already
+ // opened on eth0 and an attempt to open another socket and bind to
+ // the same address and port should fail.
+ ASSERT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, error_handler));
+ // We expect that an error occurred when we tried to open a socket on
+ // eth0, but the socket on eth1 should open just fine.
+ EXPECT_EQ(1, errors_count_);
+
+ // Reset errors count.
+ errors_count_ = 0;
+
+ // Now that we have two sockets open, we can try this again but this time
+ // we should get two errors: one when opening a socket on eth0, another one
+ // when opening a socket on eth1.
+ ASSERT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, error_handler));
+ EXPECT_EQ(2, errors_count_);
+}
+
+// Test that no exception is thrown when a port is already bound but skip open
+// flag is provided.
+TEST_F(IfaceMgrTest, openSockets6SkipOpen) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Open socket on eth0. The openSockets6 should detect that this
+ // socket has been already open and an attempt to open another socket
+ // and bind to this address and port should fail.
+ ASSERT_NO_THROW(ifacemgr.openSocket("eth0",
+ IOAddress("fe80::3a60:77ff:fed5:cdef"),
+ DHCP6_SERVER_PORT, true));
+
+ // The function doesn't throw an exception when it tries to open a socket
+ // and bind it to the address in use but the skip open flag is provided.
+ EXPECT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, 0, true));
+
+ // Check that the other port is bound.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+}
+
+// This test verifies that the function correctly checks that the v6 socket is
+// open and bound to a specific address.
+TEST_F(IfaceMgrTest, hasOpenSocketForAddress6) {
+ NakedIfaceMgr ifacemgr;
+
+ // Remove all real interfaces and create a set of dummy interfaces.
+ ifacemgr.createIfaces();
+
+ boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
+
+ // Simulate opening sockets using the dummy packet filter.
+ bool success = false;
+ ASSERT_NO_THROW(success = ifacemgr.openSockets6(DHCP6_SERVER_PORT));
+ EXPECT_TRUE(success);
+
+ // Make sure that the sockets are bound as expected.
+ ASSERT_TRUE(ifacemgr.isBound("eth0", "fe80::3a60:77ff:fed5:cdef"));
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
+
+ // There should be v6 sockets only, no v4 sockets.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(AF_INET6));
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(AF_INET));
+
+ // Check that there are sockets bound to the addresses we have configured
+ // for interfaces.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:cdef")));
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:fed5:abcd")));
+ // Check that there is no socket bound to the address which hasn't been
+ // configured on any interface.
+ EXPECT_FALSE(ifacemgr.hasOpenSocket(IOAddress("fe80::3a60:77ff:feed:1")));
+}
+
+// Test the Iface structure itself
+TEST_F(IfaceMgrTest, iface) {
+ boost::scoped_ptr<Iface> iface;
+ EXPECT_NO_THROW(iface.reset(new Iface("eth0",1)));
+
+ EXPECT_EQ("eth0", iface->getName());
+ EXPECT_EQ(1, iface->getIndex());
+ EXPECT_EQ("eth0/1", iface->getFullName());
+
+ // Let's make a copy of this address collection.
+ Iface::AddressCollection addrs = iface->getAddresses();
+
+ EXPECT_EQ(0, addrs.size());
+
+ IOAddress addr1("192.0.2.6");
+ iface->addAddress(addr1);
+
+ addrs = iface->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("192.0.2.6", addrs.begin()->get().toText());
+
+ // No such address, should return false.
+ EXPECT_FALSE(iface->delAddress(IOAddress("192.0.8.9")));
+
+ // This address is present, delete it!
+ EXPECT_TRUE(iface->delAddress(IOAddress("192.0.2.6")));
+
+ // Not really necessary, previous reference still points to the same
+ // collection. Let's do it anyway, as test code may serve as example
+ // usage code as well.
+ addrs = iface->getAddresses();
+
+ EXPECT_EQ(0, addrs.size());
+
+ EXPECT_NO_THROW(iface.reset());
+}
+
+TEST_F(IfaceMgrTest, iface_methods) {
+ Iface iface("foo", 1234);
+
+ iface.setHWType(42);
+ EXPECT_EQ(42, iface.getHWType());
+
+ ASSERT_LT(Iface::MAX_MAC_LEN + 10, 255);
+
+ uint8_t mac[Iface::MAX_MAC_LEN+10];
+ for (uint8_t i = 0; i < Iface::MAX_MAC_LEN + 10; i++) {
+ mac[i] = 255 - i;
+ }
+
+ EXPECT_EQ("foo", iface.getName());
+ EXPECT_EQ(1234, iface.getIndex());
+
+ // MAC is too long. Exception should be thrown and
+ // MAC length should not be set.
+ EXPECT_THROW(
+ iface.setMac(mac, Iface::MAX_MAC_LEN + 1),
+ OutOfRange
+ );
+
+ // MAC length should stay not set as exception was thrown.
+ EXPECT_EQ(0, iface.getMacLen());
+
+ // Setting maximum length MAC should be ok.
+ iface.setMac(mac, Iface::MAX_MAC_LEN);
+
+ // For some reason constants cannot be used directly in EXPECT_EQ
+ // as this produces linking error.
+ size_t len = Iface::MAX_MAC_LEN;
+ EXPECT_EQ(len, iface.getMacLen());
+ EXPECT_EQ(0, memcmp(mac, iface.getMac(), iface.getMacLen()));
+}
+
+TEST_F(IfaceMgrTest, socketInfo) {
+
+ // Check that socketinfo for IPv4 socket is functional
+ SocketInfo sock1(IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7, 7);
+ EXPECT_EQ(7, sock1.sockfd_);
+ EXPECT_EQ(-1, sock1.fallbackfd_);
+ EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
+ EXPECT_EQ(AF_INET, sock1.family_);
+ EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
+
+ // Check that non-default value of the fallback socket descriptor is set
+ SocketInfo sock2(IOAddress("192.0.2.53"), DHCP4_SERVER_PORT + 8, 8, 10);
+ EXPECT_EQ(8, sock2.sockfd_);
+ EXPECT_EQ(10, sock2.fallbackfd_);
+ EXPECT_EQ("192.0.2.53", sock2.addr_.toText());
+ EXPECT_EQ(AF_INET, sock2.family_);
+ EXPECT_EQ(DHCP4_SERVER_PORT + 8, sock2.port_);
+
+ // Check that socketinfo for IPv6 socket is functional
+ SocketInfo sock3(IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9, 9);
+ EXPECT_EQ(9, sock3.sockfd_);
+ EXPECT_EQ(-1, sock3.fallbackfd_);
+ EXPECT_EQ("2001:db8:1::56", sock3.addr_.toText());
+ EXPECT_EQ(AF_INET6, sock3.family_);
+ EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock3.port_);
+
+ // Now let's test if IfaceMgr handles socket info properly
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ IfacePtr loopback = ifacemgr->getIface(LOOPBACK_NAME);
+ ASSERT_TRUE(loopback);
+ loopback->addSocket(sock1);
+ loopback->addSocket(sock2);
+ loopback->addSocket(sock3);
+
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456));
+
+ // pkt6 does not have interface set yet
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt6),
+ IfaceNotFound
+ );
+
+ // Try to send over non-existing interface
+ pkt6->setIface("nosuchinterface45");
+ pkt6->setIndex(12345);
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt6),
+ IfaceNotFound
+ );
+
+ // Index is now checked first
+ pkt6->setIface(LOOPBACK_NAME);
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt6),
+ IfaceNotFound
+ );
+
+ // This will work
+ pkt6->setIndex(LOOPBACK_INDEX);
+ EXPECT_EQ(9, ifacemgr->getSocket(pkt6));
+
+ bool deleted = false;
+ EXPECT_NO_THROW(
+ deleted = ifacemgr->getIface(LOOPBACK_NAME)->delSocket(9);
+ );
+ EXPECT_EQ(true, deleted);
+
+ // It should throw again, there's no usable socket anymore
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt6),
+ SocketNotFound
+ );
+
+ // Repeat for pkt4
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1));
+
+ // pkt4 does not have interface set yet.
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt4),
+ IfaceNotFound
+ );
+
+ // Try to send over non-existing interface.
+ pkt4->setIface("nosuchinterface45");
+ pkt4->setIndex(12345);
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt4),
+ IfaceNotFound
+ );
+
+ // Index is now checked first.
+ pkt4->setIface(LOOPBACK_NAME);
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt4),
+ IfaceNotFound
+ );
+
+ // Socket info is set, packet has well defined interface. It should work.
+ pkt4->setIndex(LOOPBACK_INDEX);
+ EXPECT_EQ(7, ifacemgr->getSocket(pkt4).sockfd_);
+
+ // Set the local address to check if the socket for this address will
+ // be returned.
+ pkt4->setLocalAddr(IOAddress("192.0.2.56"));
+ EXPECT_EQ(7, ifacemgr->getSocket(pkt4).sockfd_);
+
+ // Modify the local address and expect that the other socket will be
+ // returned.
+ pkt4->setLocalAddr(IOAddress("192.0.2.53"));
+ EXPECT_EQ(8, ifacemgr->getSocket(pkt4).sockfd_);
+
+ EXPECT_NO_THROW(
+ ifacemgr->getIface(LOOPBACK_NAME)->delSocket(7);
+ ifacemgr->getIface(LOOPBACK_NAME)->delSocket(8);
+ );
+
+ // It should throw again, there's no usable socket anymore.
+ EXPECT_THROW(
+ ifacemgr->getSocket(pkt4),
+ SocketNotFound
+ );
+}
+
+#if defined(OS_BSD)
+#include <net/if_dl.h>
+#endif
+
+#include <sys/socket.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
+/// @brief Checks the index of this interface
+/// @param iface Kea interface structure to be checked
+/// @param ifptr original structure returned by getifaddrs
+/// @return true if index is returned properly
+bool
+checkIfIndex(const Iface & iface,
+ struct ifaddrs *& ifptr) {
+ return (iface.getIndex() == if_nametoindex(ifptr->ifa_name));
+}
+
+/// @brief Checks if the interface has proper flags set
+/// @param iface Kea interface structure to be checked
+/// @param ifptr original structure returned by getifaddrs
+/// @return true if flags are set properly
+bool
+checkIfFlags(const Iface & iface,
+ struct ifaddrs *& ifptr) {
+ bool flag_loopback_ = ifptr->ifa_flags & IFF_LOOPBACK;
+ bool flag_up_ = ifptr->ifa_flags & IFF_UP;
+ bool flag_running_ = ifptr->ifa_flags & IFF_RUNNING;
+ bool flag_multicast_ = ifptr->ifa_flags & IFF_MULTICAST;
+
+ return ((iface.flag_loopback_ == flag_loopback_) &&
+ (iface.flag_up_ == flag_up_) &&
+ (iface.flag_running_ == flag_running_) &&
+ (iface.flag_multicast_ == flag_multicast_));
+}
+
+/// @brief Checks if MAC Address/IP Addresses are properly well formed
+/// @param iface Kea interface structure to be checked
+/// @param ifptr original structure returned by getifaddrs
+/// @return true if addresses are returned properly
+bool
+checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) {
+ const unsigned char * p = 0;
+#if defined(OS_LINUX)
+ // Workaround for Linux ...
+ if(ifptr->ifa_data != 0) {
+ // We avoid localhost as it has no MAC Address
+ if (!strncmp(iface.getName().c_str(), "lo", 2)) {
+ return (true);
+ }
+
+ struct ifreq ifr;
+ memset(& ifr.ifr_name, 0, sizeof ifr.ifr_name);
+ strncpy(ifr.ifr_name, iface.getName().c_str(), sizeof(ifr.ifr_name) - 1);
+
+ int s = -1; // Socket descriptor
+
+ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ isc_throw(Unexpected, "Cannot create AF_INET socket");
+ }
+
+ if (ioctl(s, SIOCGIFHWADDR, & ifr) < 0) {
+ close(s);
+ isc_throw(Unexpected, "Cannot set SIOCGIFHWADDR flag");
+ }
+
+ const uint8_t * p =
+ reinterpret_cast<uint8_t *>(ifr.ifr_ifru.ifru_hwaddr.sa_data);
+
+ close(s);
+
+ /// @todo: Check MAC address length. For some interfaces it is
+ /// different than 6. Some have 0, while some exotic ones (like
+ /// infiniband) have 20.
+ return (!memcmp(p, iface.getMac(), iface.getMacLen()));
+ }
+#endif
+
+ if(!ifptr->ifa_addr) {
+ return (false);
+ }
+
+ switch(ifptr->ifa_addr->sa_family) {
+#if defined(OS_BSD)
+ case AF_LINK: {
+ // We avoid localhost as it has no MAC Address
+ if (!strncmp(iface.getName().c_str(), "lo", 2)) {
+ return (true);
+ }
+
+ struct sockaddr_dl * hwdata =
+ reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+ p = reinterpret_cast<uint8_t *>(LLADDR(hwdata));
+
+ // Extract MAC address length
+ if (hwdata->sdl_alen != iface.getMacLen()) {
+ return (false);
+ }
+
+ return (!memcmp(p, iface.getMac(), hwdata->sdl_alen));
+ }
+#endif
+ case AF_INET: {
+ struct sockaddr_in * v4data =
+ reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+ p = reinterpret_cast<uint8_t *>(& v4data->sin_addr);
+
+ IOAddress addrv4 = IOAddress::fromBytes(AF_INET, p);
+
+ BOOST_FOREACH(Iface::Address a, iface.getAddresses()) {
+ if(a.get().isV4() && (a.get()) == addrv4) {
+ return (true);
+ }
+ }
+
+ return (false);
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 * v6data =
+ reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+ p = reinterpret_cast<uint8_t *>(& v6data->sin6_addr);
+
+ IOAddress addrv6 = IOAddress::fromBytes(AF_INET6, p);
+
+ BOOST_FOREACH(Iface::Address a, iface.getAddresses()) {
+ if (a.get().isV6() && (a.get() == addrv6)) {
+ return (true);
+ }
+ }
+
+ return (false);
+ }
+ default:
+ return (true);
+ }
+}
+
+/// This test checks that the IfaceMgr detects interfaces correctly and
+/// that detected interfaces have correct properties.
+TEST_F(IfaceMgrTest, detectIfaces) {
+ NakedIfaceMgr ifacemgr;
+
+ // We are using struct ifaddrs as it is the only good portable one
+ // ifreq and ioctls are far from portable. For BSD ifreq::ifa_flags field
+ // is only a short which, nowadays, can be negative
+ struct ifaddrs *iflist = 0, *ifptr = 0;
+ ASSERT_EQ(0, getifaddrs(&iflist))
+ << "Unit test failed to detect interfaces.";
+
+ // Go over all interfaces detected by the unit test and see if they
+ // match with the interfaces detected by IfaceMgr.
+ for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+ // When more than one IPv4 address is assigned to the particular
+ // physical interface, virtual interfaces may be created for each
+ // additional IPv4 address. For example, when multiple addresses
+ // are assigned to the eth0 interface, additional virtual interfaces
+ // will be eth0:0, eth0:1 etc. This is the case on some Linux
+ // distributions. The getifaddrs will return virtual interfaces,
+ // with single address each, but the Netlink-based implementation
+ // (used by IfaceMgr) will rather hold a list of physical interfaces
+ // with multiple IPv4 addresses assigned. This means that the test
+ // can't use a name of the interface returned by getifaddrs to match
+ // with the interface name held by IfaceMgr. Instead, we use the
+ // index of the interface because the virtual interfaces have the
+ // same indexes as the physical interfaces.
+ IfacePtr i = ifacemgr.getIface(if_nametoindex(ifptr->ifa_name));
+
+ // If the given interface was also detected by the IfaceMgr,
+ // check that its properties are correct.
+ if (i != NULL) {
+ // Check if interface index is reported properly
+ EXPECT_TRUE(checkIfIndex(*i, ifptr))
+ << "Non-matching index of the detected interface "
+ << i->getName();
+
+ // Check if flags are reported properly
+ EXPECT_TRUE(checkIfFlags(*i, ifptr))
+ << "Non-matching flags of the detected interface "
+ << i->getName();
+
+ // Check if addresses are reported properly
+ EXPECT_TRUE(checkIfAddrs(*i, ifptr))
+ << "Non-matching addresses on the detected interface "
+ << i->getName();
+
+ } else {
+ // The interface detected here seems to be missing in the
+ // IfaceMgr.
+ ADD_FAILURE() << "Interface " << ifptr->ifa_name
+ << " not detected by the Interface Manager";
+ }
+ }
+
+ freeifaddrs(iflist);
+ iflist = 0;
+}
+
+volatile bool callback_ok;
+volatile bool callback2_ok;
+
+void my_callback(int /* fd */) {
+ callback_ok = true;
+}
+
+void my_callback2(int /* fd */) {
+ callback2_ok = true;
+}
+
+// Tests if a single external socket and its callback can be passed and
+// it is supported properly by receive4() method.
+TEST_F(IfaceMgrTest, SingleExternalSocket4) {
+
+ callback_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+
+ Pkt4Ptr pkt4;
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // Our callback should not be called this time (there was no data)
+ EXPECT_FALSE(callback_ok);
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // Now, send some data over pipe (38 bytes)
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // There was some data, so this time callback should be called
+ EXPECT_TRUE(callback_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+}
+
+// Tests if multiple external sockets and their callbacks can be passed and
+// it is supported properly by receive4() method.
+TEST_F(IfaceMgrTest, MultipleExternalSockets4) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ Pkt4Ptr pkt4;
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // Our callbacks should not be called this time (there was no data)
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // Now, send some data over the first pipe (38 bytes)
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // There was some data, so this time callback should be called
+ EXPECT_TRUE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // Read the data sent, because our test callbacks are too dumb to actually
+ // do it. We don't care about the content read, because we're testing
+ // the callbacks, not pipes.
+ char buf[80];
+ EXPECT_EQ(38, read(pipefd[0], buf, 80));
+
+ // Clear the status...
+ callback_ok = false;
+ callback2_ok = false;
+
+ // And try again, using the second pipe
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+// Tests if existing external socket can be deleted and that such deletion does
+// not affect any other existing sockets. Tests uses receive4()
+TEST_F(IfaceMgrTest, DeleteExternalSockets4) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ // Now delete the first session socket
+ EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0]));
+
+ // Now check whether the second callback is still functional
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ Pkt4Ptr pkt4;
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt4);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // Let's reset the status
+ callback_ok = false;
+ callback2_ok = false;
+
+ // Now let's send something over the first callback that was unregistered.
+ // We should NOT receive any callback.
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // Now check that the first callback is NOT called.
+ ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(1));
+ EXPECT_FALSE(callback_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+// Tests that an existing external socket that becomes invalid
+// is detected and purged, without affecting other sockets.
+// Tests uses receive4() without queuing.
+TEST_F(IfaceMgrTest, purgeExternalSockets4Direct) {
+ purgeExternalSockets4Test();
+}
+
+
+// Tests that an existing external socket that becomes invalid
+// is detected and purged, without affecting other sockets.
+// Tests uses receive4() with queuing.
+TEST_F(IfaceMgrTest, purgeExternalSockets4Indirect) {
+ purgeExternalSockets4Test(true);
+}
+
+// Tests if a single external socket and its callback can be passed and
+// it is supported properly by receive6() method.
+TEST_F(IfaceMgrTest, SingleExternalSocket6) {
+
+ callback_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ Pkt6Ptr pkt6;
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // Our callback should not be called this time (there was no data)
+ EXPECT_FALSE(callback_ok);
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // Now, send some data over pipe (38 bytes)
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_TRUE(callback_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+}
+
+// Tests if multiple external sockets and their callbacks can be passed and
+// it is supported properly by receive6() method.
+TEST_F(IfaceMgrTest, MultipleExternalSockets6) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ Pkt6Ptr pkt6;
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // Our callbacks should not be called this time (there was no data)
+ EXPECT_FALSE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // Now, send some data over the first pipe (38 bytes)
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_TRUE(callback_ok);
+ EXPECT_FALSE(callback2_ok);
+
+ // Read the data sent, because our test callbacks are too dumb to actually
+ // do it. We don't care about the content read, because we're testing
+ // the callbacks, not pipes.
+ char buf[80];
+ EXPECT_EQ(38, read(pipefd[0], buf, 80));
+
+ // Clear the status...
+ callback_ok = false;
+ callback2_ok = false;
+
+ // And try again, using the second pipe
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+// Tests if existing external socket can be deleted and that such deletion does
+// not affect any other existing sockets. Tests uses receive6()
+TEST_F(IfaceMgrTest, DeleteExternalSockets6) {
+
+ callback_ok = false;
+ callback2_ok = false;
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Create first pipe and register it as extra socket
+ int pipefd[2];
+ EXPECT_TRUE(pipe(pipefd) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0], my_callback));
+
+ // Let's create a second pipe and register it as well
+ int secondpipe[2];
+ EXPECT_TRUE(pipe(secondpipe) == 0);
+ EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0], my_callback2));
+
+ // Now delete the first session socket
+ EXPECT_NO_THROW(ifacemgr->deleteExternalSocket(pipefd[0]));
+
+ // Now check whether the second callback is still functional
+ EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+ // ... and repeat
+ Pkt6Ptr pkt6;
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+
+ // IfaceMgr should not process control socket data as incoming packets
+ EXPECT_FALSE(pkt6);
+
+ // There was some data, so this time callback should be called
+ EXPECT_FALSE(callback_ok);
+ EXPECT_TRUE(callback2_ok);
+
+ // Let's reset the status
+ callback_ok = false;
+ callback2_ok = false;
+
+ // Now let's send something over the first callback that was unregistered.
+ // We should NOT receive any callback.
+ EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+ // Now check that the first callback is NOT called.
+ ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(1));
+ EXPECT_FALSE(callback_ok);
+
+ // close both pipe ends
+ close(pipefd[1]);
+ close(pipefd[0]);
+
+ close(secondpipe[1]);
+ close(secondpipe[0]);
+}
+
+// Tests that an existing external socket that becomes invalid
+// is detected and purged, without affecting other sockets.
+// Tests uses receive6() without queuing.
+TEST_F(IfaceMgrTest, purgeExternalSockets6Direct) {
+ purgeExternalSockets6Test();
+}
+
+
+// Tests that an existing external socket that becomes invalid
+// is detected and purged, without affecting other sockets.
+// Tests uses receive6() with queuing.
+TEST_F(IfaceMgrTest, purgeExternalSockets6Indirect) {
+ purgeExternalSockets6Test(true);
+}
+
+// Test checks if the unicast sockets can be opened.
+// This test is now disabled, because there is no reliable way to test it. We
+// can't even use loopback, because openSockets() skips loopback interface
+// (as it should be, because DHCP server is not supposed to listen on loopback).
+TEST_F(IfaceMgrTest, DISABLED_openUnicastSockets) {
+ /// @todo Need to implement a test that is able to check whether we can open
+ /// unicast sockets. There are 2 problems with it:
+ /// 1. We need to have a non-link-local address on an interface that is
+ /// up, running, IPv6 and multicast capable
+ /// 2. We need that information on every OS that we run tests on. So far
+ /// we are only supporting interface detection in Linux.
+ ///
+ /// To achieve this, we will probably need a pre-test setup, similar to what
+ /// BIND9 is doing (i.e. configuring well known addresses on loopback).
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Get the interface (todo: which interface)
+ IfacePtr iface = ifacemgr->getIface("eth0");
+ ASSERT_TRUE(iface);
+ iface->inactive6_ = false;
+
+ // Tell the interface that it should bind to this global interface
+ EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1")));
+
+ // Tell IfaceMgr to open sockets. This should trigger at least 2 sockets
+ // to open on eth0: link-local and global. On some systems (Linux), an
+ // additional socket for multicast may be opened.
+ EXPECT_TRUE(ifacemgr->openSockets6(PORT1));
+
+ const Iface::SocketCollection& sockets = iface->getSockets();
+ ASSERT_GE(2, sockets.size());
+
+ // Global unicast should be first
+ EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("2001:db8::1")));
+ EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("figure-out-link-local-addr")));
+}
+
+// Checks if there is a protection against unicast duplicates.
+TEST_F(IfaceMgrTest, unicastDuplicates) {
+ NakedIfaceMgr ifacemgr;
+
+ IfacePtr iface = ifacemgr.getIface(LOOPBACK_NAME);
+ if (!iface) {
+ cout << "Local loopback interface not found. Skipping test. " << endl;
+ return;
+ }
+
+ // Tell the interface that it should bind to this global interface
+ // It is the first attempt so it should succeed
+ EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1")));
+
+ // Tell the interface that it should bind to this global interface
+ // It is the second attempt so it should fail
+ EXPECT_THROW(iface->addUnicast(IOAddress("2001:db8::1")), BadValue);
+}
+
+// This test requires addresses 2001:db8:15c::1/128 and fe80::1/64 to be
+// configured on loopback interface
+//
+// Useful commands:
+// ip a a 2001:db8:15c::1/128 dev lo
+// ip a a fe80::1/64 dev lo
+//
+// If you do not issue those commands before running this test, it will fail.
+TEST_F(IfaceMgrTest, DISABLED_getSocket) {
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented.
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ IOAddress lo_addr("::1");
+ IOAddress link_local("fe80::1");
+ IOAddress global("2001:db8:15c::1");
+
+ IOAddress dst_link_local("fe80::dead:beef");
+ IOAddress dst_global("2001:db8:15c::dead:beef");
+
+ // Bind loopback address
+ int socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547);
+ EXPECT_GE(socket1, 0); // socket >= 0
+
+ // Bind link-local address
+ int socket2 = ifacemgr->openSocket(LOOPBACK_NAME, link_local, 10547);
+ EXPECT_GE(socket2, 0);
+
+ int socket3 = ifacemgr->openSocket(LOOPBACK_NAME, global, 10547);
+ EXPECT_GE(socket3, 0);
+
+ // Let's make sure those sockets are unique
+ EXPECT_NE(socket1, socket2);
+ EXPECT_NE(socket2, socket3);
+ EXPECT_NE(socket3, socket1);
+
+ // Create a packet
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ pkt6->setIface(LOOPBACK_NAME);
+ pkt6->setIndex(LOOPBACK_INDEX);
+
+ // Check that packets sent to link-local will get socket bound to link local
+ pkt6->setLocalAddr(global);
+ pkt6->setRemoteAddr(dst_global);
+ EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6));
+
+ // Check that packets sent to link-local will get socket bound to link local
+ pkt6->setLocalAddr(link_local);
+ pkt6->setRemoteAddr(dst_link_local);
+ EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6));
+
+ // Close sockets here because the following tests will want to
+ // open sockets on the same ports.
+ ifacemgr->closeSockets();
+}
+
+// Verifies DHCPv4 behavior of configureDHCPPacketQueue()
+TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest4) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // First let's make sure there is no queue and no thread.
+ ASSERT_FALSE(ifacemgr->getPacketQueue4());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ bool queue_enabled = false;
+ // Given an empty pointer, we should default to no queue.
+ data::ConstElementPtr queue_control;
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+ EXPECT_FALSE(queue_enabled);
+ EXPECT_FALSE(ifacemgr->getPacketQueue4());
+ // configureDHCPPacketQueue() should never start the thread.
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Verify that calling startDHCPReceiver with no queue, does NOT start the thread.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Now let's try with a populated queue control, but with enable-queue = false.
+ queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false);
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+ EXPECT_FALSE(queue_enabled);
+ EXPECT_FALSE(ifacemgr->getPacketQueue4());
+ // configureDHCPPacketQueue() should never start the thread.
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Now let's enable the queue.
+ queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+ ASSERT_TRUE(queue_enabled);
+ // Verify we have correctly created the queue.
+ CHECK_QUEUE_INFO(ifacemgr->getPacketQueue4(), "{ \"capacity\": 500, \"queue-type\": \""
+ << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }");
+ // configureDHCPPacketQueue() should never start the thread.
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Calling startDHCPReceiver with a queue, should start the thread.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+
+ // Verify that calling startDHCPReceiver when the thread is running, throws.
+ ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation);
+
+ // Create a disabled config.
+ queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false);
+
+ // Trying to reconfigure with a running thread should throw.
+ ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control),
+ InvalidOperation);
+
+ // We should still have our queue and the thread should still be running.
+ EXPECT_TRUE(ifacemgr->getPacketQueue4());
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+
+ // Now let's stop stop the thread.
+ ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+ // Stopping the thread should not destroy the queue.
+ ASSERT_TRUE(ifacemgr->getPacketQueue4());
+
+ // Reconfigure with the queue turned off. We should have neither queue nor thread.
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+ EXPECT_FALSE(ifacemgr->getPacketQueue4());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+}
+
+// Verifies DHCPv6 behavior of configureDHCPPacketQueue()
+TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest6) {
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // First let's make sure there is no queue and no thread.
+ ASSERT_FALSE(ifacemgr->getPacketQueue6());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ bool queue_enabled = false;
+ // Given an empty pointer, we should default to no queue.
+ data::ConstElementPtr queue_control;
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+ EXPECT_FALSE(queue_enabled);
+ EXPECT_FALSE(ifacemgr->getPacketQueue6());
+ // configureDHCPPacketQueue() should never start the thread.
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Verify that calling startDHCPReceiver with no queue, does NOT start the thread.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Now let's try with a populated queue control, but with enable-queue = false.
+ queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false);
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
+ EXPECT_FALSE(queue_enabled);
+ EXPECT_FALSE(ifacemgr->getPacketQueue6());
+ // configureDHCPPacketQueue() should never start the thread.
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Now let's enable the queue.
+ queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
+ ASSERT_TRUE(queue_enabled);
+ // Verify we have correctly created the queue.
+ CHECK_QUEUE_INFO(ifacemgr->getPacketQueue6(), "{ \"capacity\": 500, \"queue-type\": \""
+ << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }");
+ // configureDHCPPacketQueue() should never start the thread.
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+ // Calling startDHCPReceiver with a queue, should start the thread.
+ ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+
+ // Verify that calling startDHCPReceiver when the thread is running, throws.
+ ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation);
+
+ // Create a disabled config.
+ queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false);
+
+ // Trying to reconfigure with a running thread should throw.
+ ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control),
+ InvalidOperation);
+
+ // We should still have our queue and the thread should still be running.
+ EXPECT_TRUE(ifacemgr->getPacketQueue6());
+ ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+
+ // Now let's stop stop the thread.
+ ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+ // Stopping the thread should not destroy the queue.
+ ASSERT_TRUE(ifacemgr->getPacketQueue6());
+
+ // Reconfigure with the queue turned off. We should have neither queue nor thread.
+ ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
+ EXPECT_FALSE(ifacemgr->getPacketQueue6());
+ ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+}
+
+}
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
new file mode 100644
index 0000000..7c05b97
--- /dev/null
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -0,0 +1,3584 @@
+// Copyright (C) 2011-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcp/option6_status_code.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <util/thread_pool.h>
+
+#include <boost/pointer_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+#include <typeinfo>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+// DHCPv6 suboptions of Vendor Options Option.
+/// @todo move to src/lib/dhcp/docsis3_option_defs.h once #3194 is merged.
+const uint16_t OPTION_CMTS_CAPS = 1025;
+const uint16_t OPTION_CM_MAC = 1026;
+
+class LibDhcpTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Removes runtime option definitions.
+ LibDhcpTest() {
+ LibDHCP::clearRuntimeOptionDefs();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes runtime option definitions.
+ virtual ~LibDhcpTest() {
+ LibDHCP::clearRuntimeOptionDefs();
+ }
+
+ /// @brief Generic factory function to create any option.
+ ///
+ /// Generic factory function to create any option.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param type option-type
+ /// @param buf option-buffer
+ static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
+ const OptionBuffer& buf) {
+ return (OptionPtr(new Option(u, type, buf)));
+ }
+
+ /// @brief Test DHCPv4 option definition.
+ ///
+ /// This function tests if option definition for standard
+ /// option has been initialized correctly.
+ ///
+ /// @param code option code.
+ /// @param begin iterator pointing at beginning of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing at end of a buffer to be
+ /// used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
+ static void testStdOptionDefs4(const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type,
+ const std::string& encapsulates = "") {
+ // Use V4 universe.
+ testStdOptionDefs(Option::V4, DHCP4_OPTION_SPACE, code, begin, end,
+ expected_type, encapsulates);
+ }
+
+ /// @brief Test DHCPv6 option definition.
+ ///
+ /// This function tests if option definition for standard
+ /// option has been initialized correctly.
+ ///
+ /// @param code option code.
+ /// @param begin iterator pointing at beginning of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing at end of a buffer to be
+ /// used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
+ static void testStdOptionDefs6(const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type,
+ const std::string& encapsulates = "") {
+ // Use V6 universe.
+ testStdOptionDefs(Option::V6, DHCP6_OPTION_SPACE, code, begin,
+ end, expected_type, encapsulates);
+ }
+
+ /// @brief Test DHCPv6 option definition in a given option space.
+ ///
+ /// This function tests if option definition for an option from a
+ /// given option space has been initialized correctly.
+ ///
+ /// @param option_space option space.
+ /// @param code option code.
+ /// @param begin iterator pointing at beginning of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing at end of a buffer to be
+ /// used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
+ static void testOptionDefs6(const std::string& option_space,
+ const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type,
+ const std::string& encapsulates = "") {
+ testStdOptionDefs(Option::V6, option_space, code, begin,
+ end, expected_type, encapsulates);
+ }
+
+ /// @brief Create a sample DHCPv4 option 82 with suboptions.
+ static OptionBuffer createAgentInformationOption() {
+ const uint8_t opt_data[] = {
+ 0x52, 0x0E, // Agent Information Option (length = 14)
+ // Suboptions start here...
+ 0x01, 0x04, // Agent Circuit ID (length = 4)
+ 0x20, 0x00, 0x00, 0x02, // ID
+ 0x02, 0x06, // Agent Remote ID
+ 0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x14 // ID
+ };
+ return (OptionBuffer(opt_data, opt_data + sizeof(opt_data)));
+ }
+
+ /// @brief Create option definitions and store in the container.
+ ///
+ /// @param spaces_num Number of option spaces to be created.
+ /// @param defs_num Number of option definitions to be created for
+ /// each option space.
+ /// @param [out] defs Container to which option definitions should be
+ /// added.
+ static void createRuntimeOptionDefs(const uint16_t spaces_num,
+ const uint16_t defs_num,
+ OptionDefSpaceContainer& defs) {
+ for (uint16_t space = 0; space < spaces_num; ++space) {
+ std::ostringstream space_name;
+ space_name << "option-space-" << space;
+ for (uint16_t code = 0; code < defs_num; ++code) {
+ std::ostringstream name;
+ name << "name-for-option-" << code;
+ OptionDefinitionPtr opt_def(new OptionDefinition(name.str(),
+ code,
+ space_name.str(),
+ "string"));
+ defs.addItem(opt_def);
+ }
+ }
+ }
+
+ /// @brief Test if runtime option definitions have been added.
+ ///
+ /// This method uses the same naming conventions for space names and
+ /// options names as @c createRuntimeOptionDefs method.
+ ///
+ /// @param spaces_num Number of option spaces to be tested.
+ /// @param defs_num Number of option definitions that should exist
+ /// in each option space.
+ /// @param should_exist Boolean value which indicates if option
+ /// definitions should exist. If this is false, this function will
+ /// check that they don't exist.
+ static void testRuntimeOptionDefs(const uint16_t spaces_num,
+ const uint16_t defs_num,
+ const bool should_exist) {
+ for (uint16_t space = 0; space < spaces_num; ++space) {
+ std::ostringstream space_name;
+ space_name << "option-space-" << space;
+ for (uint16_t code = 0; code < defs_num; ++code) {
+ std::ostringstream name;
+ name << "name-for-option-" << code;
+ OptionDefinitionPtr opt_def =
+ LibDHCP::getRuntimeOptionDef(space_name.str(), name.str());
+ if (should_exist) {
+ ASSERT_TRUE(opt_def);
+ } else {
+ ASSERT_FALSE(opt_def);
+ }
+ }
+ }
+ }
+
+ /// @brief Test which verifies that split options throws if there is no
+ /// space left in the packet buffer.
+ ///
+ /// @param option The packet option.
+ static void splitOptionNoBuffer(OptionPtr option) {
+ isc::util::OutputBuffer buf(0);
+ OptionCollection col;
+ col.insert(std::make_pair(231, option));
+ ManagedScopedOptionsCopyContainer scoped_options;
+ ASSERT_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_, 253), BadValue);
+ }
+
+ /// @brief Test which verifies that split options works if there is only one
+ /// byte available for data in the packet buffer.
+ ///
+ /// @param option The packet option.
+ static void splitOptionOneByteLeftBuffer(OptionPtr option) {
+ isc::util::OutputBuffer buf(0);
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234));
+ OptionCollection& col = pkt->options_;
+ col.clear();
+ col.insert(std::make_pair(231, option));
+ std::string expected = pkt->toText();
+ {
+ ScopedPkt4OptionsCopy initial_scoped_options(*pkt);
+ ManagedScopedOptionsCopyContainer scoped_options;
+ ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_, 252));
+ ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true));
+ ASSERT_NE(expected, pkt->toText());
+
+ ASSERT_EQ(64, col.size());
+ uint8_t index = 0;
+ for (auto const& opt : col) {
+ ASSERT_EQ(opt.first, 231);
+ ASSERT_EQ(1, opt.second->getData().size());
+ ASSERT_EQ(index, opt.second->getData()[0]);
+ index++;
+ }
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ }
+
+ /// @brief Test which verifies that split options for v4 is working correctly.
+ ///
+ /// @param bottom_opt The packet option.
+ /// @param middle_opt The packet option.
+ /// @param top_opt The packet option.
+ static void splitOptionWithSuboptionAtLimit(OptionPtr bottom_opt,
+ OptionPtr middle_opt,
+ OptionPtr top_opt) {
+ uint32_t bottom_size = 128;
+ uint32_t middle_size = 1;
+ uint32_t top_size = 249;
+ isc::util::OutputBuffer buf(0);
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234));
+ OptionCollection& col = pkt->options_;
+ col.clear();
+ col.insert(std::make_pair(170, bottom_opt));
+ uint32_t index = 0;
+ uint8_t opt_count = 0;
+ std::string expected = pkt->toText();
+ {
+ ScopedPkt4OptionsCopy initial_scoped_options(*pkt);
+ ManagedScopedOptionsCopyContainer scoped_options;
+ ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_));
+ ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true));
+ ASSERT_NE(expected, pkt->toText());
+
+ for (auto const& opt : col) {
+ ASSERT_LE(opt.second->len(), 255);
+ }
+
+ ASSERT_EQ(3 * bottom_opt->getHeaderLen() + 2 * middle_opt->getHeaderLen() +
+ top_opt->getHeaderLen() + bottom_size + middle_size + top_size,
+ buf.getLength());
+
+ ASSERT_EQ(3, col.size());
+ for (auto const& bottom_subopt : col) {
+ ASSERT_EQ(bottom_subopt.second->getType(), 170);
+ if (opt_count == 0) {
+ // First option contains only data (0..127) and no suboptions.
+ ASSERT_EQ(bottom_subopt.second->getData().size(), bottom_size);
+ index = 0;
+ for (auto const& value : bottom_subopt.second->getData()) {
+ ASSERT_EQ(value, static_cast<uint8_t>(index));
+ index++;
+ }
+ ASSERT_EQ(bottom_subopt.second->getOptions().size(), 0);
+ } else {
+ // All other options contain no data and suboption 171.
+ ASSERT_EQ(bottom_subopt.second->getOptions().size(), 1);
+ for (auto const& middle_subopt : bottom_subopt.second->getOptions()) {
+ ASSERT_EQ(middle_subopt.first, 171);
+ if (opt_count == 1) {
+ // First suboption 171 contains only data (0) and no suboptions.
+ ASSERT_EQ(middle_subopt.second->getData().size(), middle_size);
+ index = 0;
+ for (auto const& value : middle_subopt.second->getData()) {
+ ASSERT_EQ(value, static_cast<uint8_t>(index));
+ index++;
+ }
+ ASSERT_EQ(middle_subopt.second->getOptions().size(), 0);
+ } else {
+ // Second suboption 171 contains no data and suboption 172.
+ ASSERT_EQ(middle_subopt.second->getData().size(), 0);
+ ASSERT_EQ(middle_subopt.second->getOptions().size(), 1);
+ auto const& top_subopt = middle_subopt.second->getOptions().find(172);
+ ASSERT_NE(top_subopt, middle_subopt.second->getOptions().end());
+ ASSERT_EQ(top_subopt->second->getType(), 172);
+ // Suboption 172 contains only data (0..248) and no suboptions.
+ ASSERT_EQ(top_subopt->second->getData().size(), top_size);
+ index = 0;
+ for (auto const& value : top_subopt->second->getData()) {
+ ASSERT_EQ(value, static_cast<uint8_t>(index));
+ index++;
+ }
+ ASSERT_EQ(top_subopt->second->getOptions().size(), 0);
+ }
+ }
+ }
+ opt_count++;
+ }
+ }
+ ASSERT_EQ(expected, pkt->toText());
+
+ OptionCollection col_back;
+ std::list<uint16_t> deferred_options;
+
+ size_t opts_len = buf.getLength();
+ vector<uint8_t> opts_buffer;
+ InputBuffer buffer_in(buf.getData(), opts_len);
+
+ // Use readVector because a function which parses option requires
+ // a vector as an input.
+ buffer_in.readVector(opts_buffer, opts_len);
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE,
+ col_back, deferred_options));
+
+ ASSERT_EQ(3, col_back.size());
+ // The values for option counter are:
+ // 0 - first option 170 with data only
+ // 1 - second option 170 with suboption 171 with data only
+ // 2 - third option 170 with suboption 171 with suboption 172
+ // 3 - suboption 172
+ opt_count = 0;
+ for (auto const& bottom_subopt : col_back) {
+ ASSERT_EQ(bottom_subopt.second->getType(), 170);
+ if (opt_count == 0) {
+ // First option contains only data (0..127) and no suboptions.
+ ASSERT_EQ(bottom_subopt.second->getData().size(), bottom_size);
+ index = 0;
+ for (auto const& value : bottom_subopt.second->getData()) {
+ ASSERT_EQ(value, static_cast<uint8_t>(index));
+ index++;
+ }
+ ASSERT_EQ(bottom_subopt.second->getOptions().size(), 0);
+ } else {
+ // All other options contain no data and suboption 171.
+ // Using unpackOptions4 will not create suboptions, so entire data is serialized
+ // in the option buffer.
+ ASSERT_EQ(bottom_subopt.second->getOptions().size(), 0);
+ // 1. and 4. The option 171 code.
+ index = 171;
+ bool data = false;
+ for (auto const& value : bottom_subopt.second->getData()) {
+ ASSERT_EQ(value, static_cast<uint8_t>(index));
+ if (index == 171 && opt_count == 1 && !data) {
+ // 2. The option 171 data size (1) - only data.
+ index = middle_size;
+ } else if (index == middle_size && opt_count == 1 && !data) {
+ // 3. The option 171 data (0).
+ index = 0;
+ data = true;
+ } else if (index == 171 && opt_count == 2 && !data) {
+ // 5. The option 171 size - only suboptions (option 172).
+ index = top_size + top_opt->getHeaderLen();
+ } else if (index == top_size + top_opt->getHeaderLen() && opt_count == 2 && !data) {
+ // 6. The option 172 code.
+ index = 172;
+ } else if (index == 172 && opt_count == 2 && !data) {
+ // 7. The option 172 data size (249) - only data.
+ index = top_size;
+ } else if (index == top_size && opt_count == 2 && !data) {
+ // 8. The option 172 data (0..248).
+ index = 0;
+ data = true;
+ opt_count++;
+ } else {
+ index++;
+ }
+ }
+ }
+ opt_count++;
+ }
+ }
+
+ /// @brief Test which verifies that split options for v4 is working correctly.
+ ///
+ /// @param option The packet option.
+ static void splitLongOption(OptionPtr option) {
+ isc::util::OutputBuffer buf(0);
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234));
+ OptionCollection& col = pkt->options_;
+ col.clear();
+ col.insert(std::make_pair(231, option));
+ std::string expected = pkt->toText();
+ {
+ ScopedPkt4OptionsCopy initial_scoped_options(*pkt);
+ ManagedScopedOptionsCopyContainer scoped_options;
+ ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_));
+ ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true));
+ ASSERT_NE(expected, pkt->toText());
+
+ ASSERT_EQ(11, col.size());
+ ASSERT_EQ(2560 + 11 * option->getHeaderLen(), buf.getLength());
+ }
+ ASSERT_EQ(expected, pkt->toText());
+
+ OptionCollection col_back;
+ std::list<uint16_t> deferred_options;
+
+ size_t opts_len = buf.getLength();
+ vector<uint8_t> opts_buffer;
+ InputBuffer buffer_in(buf.getData(), opts_len);
+
+ // Use readVector because a function which parses option requires
+ // a vector as an input.
+ buffer_in.readVector(opts_buffer, opts_len);
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE,
+ col_back, deferred_options));
+
+ uint32_t index = 0;
+ ASSERT_EQ(11, col_back.size());
+ for (auto const& opt : col_back) {
+ ASSERT_EQ(opt.first, 231);
+ for (auto const& value : opt.second->getData()) {
+ ASSERT_EQ(value, static_cast<uint8_t>(index));
+ index++;
+ }
+ }
+ ASSERT_EQ(index, 2560);
+ }
+
+ /// @brief Test which verifies that split options for v4 is working correctly
+ /// even if every suboption is smaller than 255 bytes, but the parent option
+ /// still overflows.
+ ///
+ /// @param rai The packet option.
+ /// @param circuit_id_opt The packet option.
+ /// @param remote_id_opt The packet option.
+ /// @param subscriber_id_opt The packet option.
+ static void splitOptionWithSuboptionWhichOverflow(OptionPtr rai,
+ OptionPtr circuit_id_opt,
+ OptionPtr remote_id_opt,
+ OptionPtr subscriber_id_opt) {
+ isc::util::OutputBuffer buf(0);
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234));
+ OptionCollection& col = pkt->options_;
+ col.clear();
+ col.insert(std::make_pair(DHO_DHCP_AGENT_OPTIONS, rai));
+ std::string expected = pkt->toText();
+ {
+ ScopedPkt4OptionsCopy initial_scoped_options(*pkt);
+ ManagedScopedOptionsCopyContainer scoped_options;
+ ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_));
+ ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true));
+ ASSERT_NE(expected, pkt->toText());
+
+ ASSERT_EQ(3, col.size());
+ ASSERT_EQ(3 * rai->getHeaderLen() + circuit_id_opt->getHeaderLen() +
+ remote_id_opt->getHeaderLen() + subscriber_id_opt->getHeaderLen() +
+ 3 * 128, buf.getLength());
+ }
+ ASSERT_EQ(expected, pkt->toText());
+
+ OptionCollection col_back;
+ std::list<uint16_t> deferred_options;
+
+ size_t opts_len = buf.getLength();
+ vector<uint8_t> opts_buffer;
+ InputBuffer buffer_in(buf.getData(), opts_len);
+
+ // Use readVector because a function which parses option requires
+ // a vector as an input.
+ buffer_in.readVector(opts_buffer, opts_len);
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE,
+ col_back, deferred_options));
+
+ uint8_t index = 0;
+ uint8_t opt_number = 0;
+ uint32_t opt_type = RAI_OPTION_AGENT_CIRCUIT_ID;
+ ASSERT_EQ(3, col_back.size());
+ for (auto const& option : col_back) {
+ ASSERT_EQ(option.first, DHO_DHCP_AGENT_OPTIONS);
+ for (auto const& sub_option : option.second->getOptions()) {
+ if (sub_option.first != opt_type) {
+ opt_type = sub_option.first;
+ ASSERT_EQ(index, 128);
+ index = 0;
+ opt_number++;
+ }
+ if (opt_number == 0) {
+ ASSERT_EQ(sub_option.first, RAI_OPTION_AGENT_CIRCUIT_ID);
+ } else if (opt_number == 1) {
+ ASSERT_EQ(sub_option.first, RAI_OPTION_REMOTE_ID);
+ } else if (opt_number == 2){
+ ASSERT_EQ(sub_option.first, RAI_OPTION_SUBSCRIBER_ID);
+ }
+ for (auto const& value : sub_option.second->getData()) {
+ ASSERT_EQ(value, index);
+ index++;
+ }
+ }
+ }
+ ASSERT_EQ(index, 128);
+ }
+
+ /// @brief Test which verifies that split options for v4 is working correctly.
+ ///
+ /// @param rai The packet option.
+ /// @param circuit_id_opt The packet option.
+ /// @param remote_id_opt The packet option.
+ /// @param subscriber_id_opt The packet option.
+ void splitLongOptionWithLongSuboption(OptionPtr rai,
+ OptionPtr circuit_id_opt,
+ OptionPtr remote_id_opt,
+ OptionPtr subscriber_id_opt) {
+ isc::util::OutputBuffer buf(0);
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 1234));
+ OptionCollection& col = pkt->options_;
+ col.clear();
+ col.insert(std::make_pair(DHO_DHCP_AGENT_OPTIONS, rai));
+ std::string expected = pkt->toText();
+ {
+ ScopedPkt4OptionsCopy initial_scoped_options(*pkt);
+ ManagedScopedOptionsCopyContainer scoped_options;
+ ASSERT_NO_THROW(LibDHCP::splitOptions4(col, scoped_options.scoped_options_));
+ ASSERT_NO_THROW(LibDHCP::packOptions4(buf, col, true));
+ ASSERT_NE(expected, pkt->toText());
+
+ ASSERT_EQ(23, col.size());
+ ASSERT_EQ((11 + 1 + 11) * rai->getHeaderLen() + 11 * circuit_id_opt->getHeaderLen() +
+ remote_id_opt->getHeaderLen() + 11 * subscriber_id_opt->getHeaderLen() +
+ 2560 + 64 + 2560, buf.getLength());
+ }
+ ASSERT_EQ(expected, pkt->toText());
+
+ OptionCollection col_back;
+ std::list<uint16_t> deferred_options;
+
+ size_t opts_len = buf.getLength();
+ vector<uint8_t> opts_buffer;
+ InputBuffer buffer_in(buf.getData(), opts_len);
+
+ // Use readVector because a function which parses option requires
+ // a vector as an input.
+ buffer_in.readVector(opts_buffer, opts_len);
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(opts_buffer, DHCP4_OPTION_SPACE,
+ col_back, deferred_options));
+
+ uint32_t index = 0;
+ uint8_t opt_number = 0;
+ uint32_t opt_type = RAI_OPTION_AGENT_CIRCUIT_ID;
+ ASSERT_EQ(23, col_back.size());
+ for (auto const& option : col_back) {
+ ASSERT_EQ(option.first, DHO_DHCP_AGENT_OPTIONS);
+ for (auto const& sub_option : option.second->getOptions()) {
+ if (sub_option.first != opt_type) {
+ opt_type = sub_option.first;
+ if (opt_number == 0) {
+ ASSERT_EQ(index, 2560);
+ } else if (opt_number == 1) {
+ ASSERT_EQ(index, 64);
+ } else if (opt_number == 2){
+ ASSERT_EQ(index, 2560);
+ }
+ index = 0;
+ opt_number++;
+ }
+ if (opt_number == 0) {
+ ASSERT_EQ(sub_option.first, RAI_OPTION_AGENT_CIRCUIT_ID);
+ } else if (opt_number == 1) {
+ ASSERT_EQ(sub_option.first, RAI_OPTION_REMOTE_ID);
+ } else if (opt_number == 2){
+ ASSERT_EQ(sub_option.first, RAI_OPTION_SUBSCRIBER_ID);
+ }
+ for (auto const& value : sub_option.second->getData()) {
+ ASSERT_EQ(value, static_cast<uint8_t>(index));
+ index++;
+ }
+ }
+ }
+ ASSERT_EQ(index, 2560);
+ }
+
+private:
+
+ /// @brief Test DHCPv4 or DHCPv6 option definition.
+ ///
+ /// This function tests if option definition for standard
+ /// option has been initialized correctly.
+ ///
+ /// @param option_space option space.
+ /// @param code option code.
+ /// @param begin iterator pointing at beginning of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing at end of a buffer to be
+ /// used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ /// @param encapsulates name of the option space being encapsulated
+ /// by the option.
+ static void testStdOptionDefs(const Option::Universe& u,
+ const std::string& option_space,
+ const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type,
+ const std::string& encapsulates) {
+ // Get all option definitions, we will use them to extract
+ // the definition for a particular option code.
+ // We don't have to initialize option definitions here because they
+ // are initialized in the class's constructor.
+ OptionDefContainerPtr options = LibDHCP::getOptionDefs(option_space);
+ // Get the container index #1. This one allows for searching
+ // option definitions using option code.
+ const OptionDefContainerTypeIndex& idx = options->get<1>();
+ // Get 'all' option definitions for a particular option code.
+ // For standard options we expect that the range returned
+ // will contain single option as their codes are unique.
+ OptionDefContainerTypeRange range = idx.equal_range(code);
+ ASSERT_EQ(1, std::distance(range.first, range.second))
+ << "Standard option definition for the code " << code
+ << " has not been found.";
+ // If we have single option definition returned, the
+ // first iterator holds it.
+ OptionDefinitionPtr def = *(range.first);
+ // It should not happen that option definition is NULL but
+ // let's make sure (test should take things like that into
+ // account).
+ ASSERT_TRUE(def) << "Option definition for the code "
+ << code << " is NULL.";
+ // Check that option definition is valid.
+ ASSERT_NO_THROW(def->validate())
+ << "Option definition for the option code " << code
+ << " is invalid";
+ // Check that the valid encapsulated option space name
+ // has been specified.
+ EXPECT_EQ(encapsulates, def->getEncapsulatedSpace()) <<
+ "opt name: " << def->getName();
+ OptionPtr option;
+ // Create the option.
+ ASSERT_NO_THROW(option = def->optionFactory(u, code, begin, end))
+ << "Option creation failed for option code " << code;
+ // Make sure it is not NULL.
+ ASSERT_TRUE(option);
+ // And the actual object type is the one that we expect.
+ // Note that for many options there are dedicated classes
+ // derived from Option class to represent them.
+ const Option* optptr = option.get();
+ EXPECT_TRUE(typeid(*optptr) == expected_type)
+ << "Invalid class returned for option code " << code;
+ }
+};
+
+// The DHCPv6 options in the wire format, used by multiple tests.
+const uint8_t v6packed[] = {
+ 0, 1, 0, 5, 100, 101, 102, 103, 104, // CLIENT_ID (9 bytes)
+ 0, 2, 0, 3, 105, 106, 107, // SERVER_ID (7 bytes)
+ 0, 14, 0, 0, // RAPID_COMMIT (0 bytes)
+ 0, 6, 0, 4, 108, 109, 110, 111, // ORO (8 bytes)
+ 0, 8, 0, 2, 112, 113, // ELAPSED_TIME (6 bytes)
+ // Vendor Specific Information Option starts here
+ 0x00, 0x11, // VSI Option Code
+ 0x00, 0x16, // VSI Option Length
+ 0x00, 0x00, 0x11, 0x8B, // Enterprise ID
+ 0x04, 0x01, // CMTS Capabilities Option
+ 0x00, 0x04, // Length
+ 0x01, 0x02,
+ 0x03, 0x00, // DOCSIS Version Number
+ 0x04, 0x02, // CM MAC Address Suboption
+ 0x00, 0x06, // Length
+ 0x74, 0x56, 0x12, 0x29, 0x97, 0xD0, // Actual MAC Address
+};
+
+TEST_F(LibDhcpTest, optionFactory) {
+ OptionBuffer buf;
+ // Factory functions for specific options must be registered before
+ // they can be used to create options instances. Otherwise exception
+ // is raised.
+ EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf),
+ isc::BadValue);
+
+ // Let's register some factory functions (two v4 and one v6 function).
+ // Registration may trigger exception if function for the specified
+ // option has been registered already.
+ ASSERT_NO_THROW(
+ LibDHCP::OptionFactoryRegister(Option::V4, DHO_SUBNET_MASK,
+ &LibDhcpTest::genericOptionFactory);
+ );
+ ASSERT_NO_THROW(
+ LibDHCP::OptionFactoryRegister(Option::V4, DHO_TIME_OFFSET,
+ &LibDhcpTest::genericOptionFactory);
+ );
+ ASSERT_NO_THROW(
+ LibDHCP::OptionFactoryRegister(Option::V6, D6O_CLIENTID,
+ &LibDhcpTest::genericOptionFactory);
+ );
+
+ // Invoke factory functions for all options (check if registration
+ // was successful).
+ OptionPtr opt_subnet_mask;
+ opt_subnet_mask = LibDHCP::optionFactory(Option::V4,
+ DHO_SUBNET_MASK,
+ buf);
+ // Check if non-NULL DHO_SUBNET_MASK option pointer has been returned.
+ ASSERT_TRUE(opt_subnet_mask);
+ // Validate if type and universe is correct.
+ EXPECT_EQ(Option::V4, opt_subnet_mask->getUniverse());
+ EXPECT_EQ(DHO_SUBNET_MASK, opt_subnet_mask->getType());
+ // Expect that option does not have content..
+ EXPECT_EQ(0, opt_subnet_mask->len() - opt_subnet_mask->getHeaderLen());
+
+ // Fill the time offset buffer with 4 bytes of data. Each byte set to 1.
+ OptionBuffer time_offset_buf(4, 1);
+ OptionPtr opt_time_offset;
+ opt_time_offset = LibDHCP::optionFactory(Option::V4,
+ DHO_TIME_OFFSET,
+ time_offset_buf);
+ // Check if non-NULL DHO_TIME_OFFSET option pointer has been returned.
+ ASSERT_TRUE(opt_time_offset);
+ // Validate if option length, type and universe is correct.
+ EXPECT_EQ(Option::V4, opt_time_offset->getUniverse());
+ EXPECT_EQ(DHO_TIME_OFFSET, opt_time_offset->getType());
+ EXPECT_EQ(time_offset_buf.size(),
+ opt_time_offset->len() - opt_time_offset->getHeaderLen());
+ // Validate data in the option.
+ EXPECT_TRUE(std::equal(time_offset_buf.begin(), time_offset_buf.end(),
+ opt_time_offset->getData().begin()));
+
+ // Fill the client id buffer with 20 bytes of data. Each byte set to 2.
+ OptionBuffer clientid_buf(20, 2);
+ OptionPtr opt_clientid;
+ opt_clientid = LibDHCP::optionFactory(Option::V6,
+ D6O_CLIENTID,
+ clientid_buf);
+ // Check if non-NULL D6O_CLIENTID option pointer has been returned.
+ ASSERT_TRUE(opt_clientid);
+ // Validate if option length, type and universe is correct.
+ EXPECT_EQ(Option::V6, opt_clientid->getUniverse());
+ EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType());
+ EXPECT_EQ(clientid_buf.size(), opt_clientid->len() - opt_clientid->getHeaderLen());
+ // Validate data in the option.
+ EXPECT_TRUE(std::equal(clientid_buf.begin(), clientid_buf.end(),
+ opt_clientid->getData().begin()));
+}
+
+TEST_F(LibDhcpTest, packOptions6) {
+ OptionBuffer buf(512);
+ isc::dhcp::OptionCollection opts; // list of options
+
+ // generate content for options
+ for (unsigned i = 0; i < 64; i++) {
+ buf[i]=i+100;
+ }
+
+ OptionPtr opt1(new Option(Option::V6, 1, buf.begin() + 0, buf.begin() + 5));
+ OptionPtr opt2(new Option(Option::V6, 2, buf.begin() + 5, buf.begin() + 8));
+ OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 8));
+ OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
+ OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
+
+ OptionPtr cm_mac(new Option(Option::V6, OPTION_CM_MAC,
+ OptionBuffer(v6packed + 54, v6packed + 60)));
+
+ OptionPtr cmts_caps(new Option(Option::V6, OPTION_CMTS_CAPS,
+ OptionBuffer(v6packed + 46, v6packed + 50)));
+
+ boost::shared_ptr<OptionInt<uint32_t> >
+ vsi(new OptionInt<uint32_t>(Option::V6, D6O_VENDOR_OPTS,
+ VENDOR_ID_CABLE_LABS));
+ vsi->addOption(cm_mac);
+ vsi->addOption(cmts_caps);
+
+ opts.insert(make_pair(opt1->getType(), opt1));
+ opts.insert(make_pair(opt1->getType(), opt2));
+ opts.insert(make_pair(opt1->getType(), opt3));
+ opts.insert(make_pair(opt1->getType(), opt4));
+ opts.insert(make_pair(opt1->getType(), opt5));
+ opts.insert(make_pair(opt1->getType(), vsi));
+
+ OutputBuffer assembled(512);
+
+ EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts));
+ EXPECT_EQ(sizeof(v6packed), assembled.getLength());
+ EXPECT_EQ(0, memcmp(assembled.getData(), v6packed, sizeof(v6packed)));
+}
+
+TEST_F(LibDhcpTest, unpackOptions6) {
+ // just couple of random options
+ // Option is used as a simple option implementation
+ // More advanced uses are validated in tests dedicated for
+ // specific derived classes.
+ isc::dhcp::OptionCollection options; // list of options
+
+ OptionBuffer buf(512);
+ memcpy(&buf[0], v6packed, sizeof(v6packed));
+
+ EXPECT_NO_THROW ({
+ LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(v6packed)),
+ DHCP6_OPTION_SPACE, options);
+ });
+
+ EXPECT_EQ(options.size(), 6); // there should be 5 options
+
+ isc::dhcp::OptionCollection::const_iterator x = options.find(1);
+ ASSERT_FALSE(x == options.end()); // option 1 should exist
+ EXPECT_EQ(1, x->second->getType()); // this should be option 1
+ ASSERT_EQ(9, x->second->len()); // it should be of length 9
+ ASSERT_EQ(5, x->second->getData().size());
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 4, 5)); // data len=5
+
+ x = options.find(2);
+ ASSERT_FALSE(x == options.end()); // option 2 should exist
+ EXPECT_EQ(2, x->second->getType()); // this should be option 2
+ ASSERT_EQ(7, x->second->len()); // it should be of length 7
+ ASSERT_EQ(3, x->second->getData().size());
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 13, 3)); // data len=3
+
+ x = options.find(14);
+ ASSERT_FALSE(x == options.end()); // option 14 should exist
+ EXPECT_EQ(14, x->second->getType()); // this should be option 14
+ ASSERT_EQ(4, x->second->len()); // it should be of length 4
+ EXPECT_EQ(0, x->second->getData().size()); // data len = 0
+
+ x = options.find(6);
+ ASSERT_FALSE(x == options.end()); // option 6 should exist
+ EXPECT_EQ(6, x->second->getType()); // this should be option 6
+ ASSERT_EQ(8, x->second->len()); // it should be of length 8
+ // Option with code 6 is the OPTION_ORO. This option is
+ // represented by the OptionIntArray<uint16_t> class which
+ // comprises the set of uint16_t values. We need to cast the
+ // returned pointer to this type to get values stored in it.
+ boost::shared_ptr<OptionIntArray<uint16_t> > opt_oro =
+ boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(x->second);
+ // This value will be NULL if cast was unsuccessful. This is the case
+ // when returned option has different type than expected.
+ ASSERT_TRUE(opt_oro);
+ // Get set of uint16_t values.
+ std::vector<uint16_t> opts = opt_oro->getValues();
+ // Prepare the reference data.
+ std::vector<uint16_t> expected_opts;
+ expected_opts.push_back(0x6C6D); // equivalent to: 108, 109
+ expected_opts.push_back(0x6E6F); // equivalent to 110, 111
+ ASSERT_EQ(expected_opts.size(), opts.size());
+ // Validated if option has been unpacked correctly.
+ EXPECT_TRUE(std::equal(expected_opts.begin(), expected_opts.end(),
+ opts.begin()));
+
+ x = options.find(8);
+ ASSERT_FALSE(x == options.end()); // option 8 should exist
+ EXPECT_EQ(8, x->second->getType()); // this should be option 8
+ ASSERT_EQ(6, x->second->len()); // it should be of length 9
+ // Option with code 8 is OPTION_ELAPSED_TIME. This option is
+ // represented by Option6Int<uint16_t> value that holds single
+ // uint16_t value.
+ boost::shared_ptr<OptionInt<uint16_t> > opt_elapsed_time =
+ boost::dynamic_pointer_cast<OptionInt<uint16_t> >(x->second);
+ // This value will be NULL if cast was unsuccessful. This is the case
+ // when returned option has different type than expected.
+ ASSERT_TRUE(opt_elapsed_time);
+ // Returned value should be equivalent to two byte values: 112, 113
+ EXPECT_EQ(0x7071, opt_elapsed_time->getValue());
+
+ // Check if Vendor Specific Information Option along with suboptions
+ // have been parsed correctly.
+ x = options.find(D6O_VENDOR_OPTS);
+ EXPECT_FALSE(x == options.end());
+ EXPECT_EQ(D6O_VENDOR_OPTS, x->second->getType());
+ EXPECT_EQ(26, x->second->len());
+
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(x->second);
+ ASSERT_TRUE(vendor);
+ ASSERT_EQ(vendor->getVendorId(), VENDOR_ID_CABLE_LABS);
+
+ // CM MAC Address Option
+ OptionPtr cm_mac = vendor->getOption(OPTION_CM_MAC);
+ ASSERT_TRUE(cm_mac);
+ EXPECT_EQ(OPTION_CM_MAC, cm_mac->getType());
+ ASSERT_EQ(10, cm_mac->len());
+ EXPECT_EQ(0, memcmp(&cm_mac->getData()[0], v6packed + 54, 6));
+
+ // CMTS Capabilities
+ OptionPtr cmts_caps = vendor->getOption(OPTION_CMTS_CAPS);
+ ASSERT_TRUE(cmts_caps);
+ EXPECT_EQ(OPTION_CMTS_CAPS, cmts_caps->getType());
+ ASSERT_EQ(8, cmts_caps->len());
+ EXPECT_EQ(0, memcmp(&cmts_caps->getData()[0], v6packed + 46, 4));
+
+ x = options.find(0);
+ EXPECT_TRUE(x == options.end()); // option 0 not found
+
+ x = options.find(256); // 256 is htons(1) on little endians. Worth checking
+ EXPECT_TRUE(x == options.end()); // option 1 not found
+
+ x = options.find(7);
+ EXPECT_TRUE(x == options.end()); // option 2 not found
+
+ x = options.find(32000);
+ EXPECT_TRUE(x == options.end()); // option 32000 not found */
+}
+
+// Check parsing of an empty DHCPv6 option.
+TEST_F(LibDhcpTest, unpackEmptyOption6) {
+ // Create option definition for the option code 1024 without fields.
+ OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 1024,
+ DHCP6_OPTION_SPACE,
+ "empty", false));
+
+ // Use it as runtime option definition within standard options space.
+ // The tested code should find this option definition within runtime
+ // option definitions set when it detects that this definition is
+ // not a standard definition.
+ OptionDefSpaceContainer defs;
+ defs.addItem(opt_def);
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Create the buffer holding the structure of the empty option.
+ OptionBuffer buf = {
+ 0x04, 0x00, // option code = 1024
+ 0x00, 0x00 // option length = 0
+ };
+
+ // Parse options.
+ OptionCollection options;
+ ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE,
+ options));
+
+ // There should be one option.
+ ASSERT_EQ(1, options.size());
+ OptionPtr option_empty = options.begin()->second;
+ ASSERT_TRUE(option_empty);
+ EXPECT_EQ(1024, option_empty->getType());
+ EXPECT_EQ(4, option_empty->len());
+}
+
+// This test verifies that the following option structure can be parsed:
+// - option (option space 'foobar')
+// - sub option (option space 'foo')
+// - sub option (option space 'bar')
+TEST_F(LibDhcpTest, unpackSubOptions6) {
+ // Create option definition for each level of encapsulation. Each option
+ // definition is for the option code 1. Options may have the same
+ // option code because they belong to different option spaces.
+
+ // Top level option encapsulates options which belong to 'space-foo'.
+ OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1,
+ "space-foobar",
+ "uint32",
+ "space-foo"));
+ // Middle option encapsulates options which belong to 'space-bar'
+ OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1,
+ "space-foo",
+ "uint16",
+ "space-bar"));
+ // Low level option doesn't encapsulate any option space.
+ OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
+ "space-bar",
+ "uint8"));
+
+ // Register created option definitions as runtime option definitions.
+ OptionDefSpaceContainer defs;
+ ASSERT_NO_THROW(defs.addItem(opt_def));
+ ASSERT_NO_THROW(defs.addItem(opt_def2));
+ ASSERT_NO_THROW(defs.addItem(opt_def3));
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Create the buffer holding the structure of options.
+ OptionBuffer buf = {
+ // First option starts here.
+ 0x00, 0x01, // option code = 1
+ 0x00, 0x0F, // option length = 15
+ 0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
+ // Sub option starts here.
+ 0x00, 0x01, // option code = 1
+ 0x00, 0x07, // option length = 7
+ 0x01, 0x02, // this option carries uint16 value
+ // Last option starts here.
+ 0x00, 0x01, // option code = 1
+ 0x00, 0x01, // option length = 1
+ 0x00 // This option carries a single uint8 value and has no sub options.
+ };
+
+ // Parse options.
+ OptionCollection options;
+ ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, "space-foobar", options, 0, 0));
+
+ // There should be one top level option.
+ ASSERT_EQ(1, options.size());
+ boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
+ boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
+ second);
+ ASSERT_TRUE(option_foobar);
+ EXPECT_EQ(1, option_foobar->getType());
+ EXPECT_EQ(0x00010203, option_foobar->getValue());
+ // There should be a middle level option held in option_foobar.
+ boost::shared_ptr<OptionInt<uint16_t> > option_foo =
+ boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
+ getOption(1));
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(1, option_foo->getType());
+ EXPECT_EQ(0x0102, option_foo->getValue());
+ // Finally, there should be a low level option under option_foo.
+ boost::shared_ptr<OptionInt<uint8_t> > option_bar =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1));
+ ASSERT_TRUE(option_bar);
+ EXPECT_EQ(1, option_bar->getType());
+ EXPECT_EQ(0x0, option_bar->getValue());
+}
+
+/// V4 Options being used to test pack/unpack operations.
+/// These are variable length options only so as there
+/// is no restriction on the data length being carried by them.
+/// For simplicity, we assign data of the length 3 for each
+/// of them.
+static uint8_t v4_opts[] = {
+ 12, 3, 0, 1, 2, // Hostname
+ 60, 3, 10, 11, 12, // Class Id
+ 14, 3, 20, 21, 22, // Merit Dump File
+ 254, 3, 30, 31, 32, // Reserved
+ 128, 3, 40, 41, 42, // Vendor specific
+ 125, 11, 0, 0, 0x11, 0x8B, // V-I Vendor-Specific Information (Cable Labs)
+ 6, 2, 4, 10, 0, 0, 10, // TFTP servers suboption (2)
+ 43, 2, // Vendor Specific Information
+ 0xDC, 0, // VSI suboption
+ 0x52, 0x19, // RAI
+ 0x01, 0x04, 0x20, 0x00, 0x02, 0x00, // Agent Circuit ID
+ 0x02, 0x06, 0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x00, // Agent Remote ID
+ 0x09, 0x09, 0x00, 0x00, 0x11, 0x8B, 0x04, // Vendor Specific Information
+ 0x01, 0x02, 0x03, 0x00 // Vendor Specific Information continued
+};
+
+// This test verifies that split options throws if there is no space left in the
+// packet buffer.
+TEST_F(LibDhcpTest, splitOptionNoBuffer) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (uint32_t i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+
+ splitOptionNoBuffer(option);
+}
+
+// This test verifies that split options throws if there is no space left in the
+// packet buffer.
+TEST_F(LibDhcpTest, splitOptionNoBufferMultiThreading) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (uint32_t i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+
+ typedef function<void()> CallBack;
+ ThreadPool<CallBack> tp;
+ tp.start(256);
+
+ // Options are shared between threads to mimic the server defined options
+ // in the packet which are added from running configuration.
+ for (uint32_t count = 0; count < 1024; ++count) {
+ auto const& work = [&] {
+ splitOptionNoBuffer(option);
+ };
+
+ boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work);
+ tp.add(call_back);
+ }
+ ASSERT_TRUE(tp.wait(10));
+}
+
+// This test verifies that split options works if there is only one byte
+// available for data in the packet buffer.
+TEST_F(LibDhcpTest, splitOptionOneByteLeftBuffer) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(64);
+ for (uint32_t i = 0; i < 64; ++i) {
+ buf_in[i] = i;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+
+ splitOptionOneByteLeftBuffer(option);
+}
+
+// This test verifies that split options works if there is only one byte
+// available for data in the packet buffer.
+TEST_F(LibDhcpTest, splitOptionOneByteLeftBufferMultiThreading) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(64);
+ for (uint32_t i = 0; i < 64; ++i) {
+ buf_in[i] = i;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+
+ typedef function<void()> CallBack;
+ ThreadPool<CallBack> tp;
+ tp.start(256);
+
+ // Options are shared between threads to mimic the server defined options
+ // in the packet which are added from running configuration.
+ for (uint32_t count = 0; count < 1024; ++count) {
+ auto const& work = [&] {
+ splitOptionOneByteLeftBuffer(option);
+ };
+
+ boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work);
+ tp.add(call_back);
+ }
+ ASSERT_TRUE(tp.wait(10));
+}
+
+// This test verifies that split options for v4 is working correctly.
+TEST_F(LibDhcpTest, splitOptionWithSuboptionAtLimit) {
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ uint32_t bottom_size = 128;
+ OptionBuffer bottom_buf_in(bottom_size);
+ for (uint32_t i = 0; i < bottom_size; ++i) {
+ bottom_buf_in[i] = i;
+ }
+
+ OptionDefinitionPtr top_def(new OptionDefinition("top", 170, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, "miggle"));
+ OptionPtr bottom_opt(new OptionCustom(*top_def, Option::V4, bottom_buf_in));
+ ASSERT_TRUE(bottom_opt);
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ uint32_t middle_size = 1;
+ OptionBuffer middle_buf_in(middle_size);
+ for (uint32_t i = 0; i < middle_size; ++i) {
+ middle_buf_in[i] = i;
+ }
+
+ OptionDefinitionPtr middle_def(new OptionDefinition("top", 171, "middle", OPT_BINARY_TYPE, ""));
+ OptionPtr middle_opt(new OptionCustom(*middle_def, Option::V4, middle_buf_in));
+ ASSERT_TRUE(middle_opt);
+ bottom_opt->addOption(middle_opt);
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ uint32_t top_size = 249;
+ OptionBuffer top_buf_in(top_size);
+ for (uint32_t i = 0; i < top_size; ++i) {
+ top_buf_in[i] = i;
+ }
+
+ OptionPtr top_opt(new Option(Option::V4, 172, top_buf_in));
+ ASSERT_TRUE(top_opt);
+ middle_opt->addOption(top_opt);
+
+ OptionDefSpaceContainer defs;
+ defs.addItem(top_def);
+ defs.addItem(middle_def);
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ splitOptionWithSuboptionAtLimit(bottom_opt, middle_opt, top_opt);
+}
+
+// This test verifies that split options for v4 is working correctly.
+TEST_F(LibDhcpTest, splitOptionWithSuboptionAtLimitMultiThreading) {
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ uint32_t bottom_size = 128;
+ OptionBuffer bottom_buf_in(bottom_size);
+ for (uint32_t i = 0; i < bottom_size; ++i) {
+ bottom_buf_in[i] = i;
+ }
+
+ OptionDefinitionPtr top_def(new OptionDefinition("top", 170, DHCP4_OPTION_SPACE, OPT_BINARY_TYPE, "miggle"));
+ OptionPtr bottom_opt(new OptionCustom(*top_def, Option::V4, bottom_buf_in));
+ ASSERT_TRUE(bottom_opt);
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ uint32_t middle_size = 1;
+ OptionBuffer middle_buf_in(middle_size);
+ for (uint32_t i = 0; i < middle_size; ++i) {
+ middle_buf_in[i] = i;
+ }
+
+ OptionDefinitionPtr middle_def(new OptionDefinition("top", 171, "middle", OPT_BINARY_TYPE, ""));
+ OptionPtr middle_opt(new OptionCustom(*middle_def, Option::V4, middle_buf_in));
+ ASSERT_TRUE(middle_opt);
+ bottom_opt->addOption(middle_opt);
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ uint32_t top_size = 249;
+ OptionBuffer top_buf_in(top_size);
+ for (uint32_t i = 0; i < top_size; ++i) {
+ top_buf_in[i] = i;
+ }
+
+ OptionPtr top_opt(new Option(Option::V4, 172, top_buf_in));
+ ASSERT_TRUE(top_opt);
+ middle_opt->addOption(top_opt);
+
+ OptionDefSpaceContainer defs;
+ defs.addItem(top_def);
+ defs.addItem(middle_def);
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ typedef function<void()> CallBack;
+ ThreadPool<CallBack> tp;
+ tp.start(256);
+
+ // Options are shared between threads to mimic the server defined options
+ // in the packet which are added from running configuration.
+ for (uint32_t count = 0; count < 1024; ++count) {
+ auto const& work = [&] {
+ splitOptionWithSuboptionAtLimit(bottom_opt, middle_opt, top_opt);
+ };
+
+ boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work);
+ tp.add(call_back);
+ }
+ ASSERT_TRUE(tp.wait(10));
+}
+
+// This test verifies that split options for v4 is working correctly.
+TEST_F(LibDhcpTest, splitLongOption) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (uint32_t i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+
+ splitLongOption(option);
+}
+
+// This test verifies that split options for v4 is working correctly.
+TEST_F(LibDhcpTest, splitLongOptionMultiThreading) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (uint32_t i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+
+ typedef function<void()> CallBack;
+ ThreadPool<CallBack> tp;
+ tp.start(256);
+
+ // Options are shared between threads to mimic the server defined options
+ // in the packet which are added from running configuration.
+ for (uint32_t count = 0; count < 1024; ++count) {
+ auto const& work = [&] {
+ splitLongOption(option);
+ };
+
+ boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work);
+ tp.add(call_back);
+ }
+ ASSERT_TRUE(tp.wait(10));
+}
+
+// This test verifies that split options for v4 is working correctly even if
+// every suboption is smaller than 255 bytes, but the parent option still
+// overflows.
+TEST_F(LibDhcpTest, splitOptionWithSuboptionWhichOverflow) {
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ // Create RAI options which should be fused by the server.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(128);
+ for (uint32_t i = 0; i < 128; ++i) {
+ buf_in[i] = i;
+ }
+
+ OptionPtr circuit_id_opt(new Option(Option::V4,
+ RAI_OPTION_AGENT_CIRCUIT_ID, buf_in));
+ ASSERT_TRUE(circuit_id_opt);
+ rai->addOption(circuit_id_opt);
+
+ OptionPtr remote_id_opt(new Option(Option::V4,
+ RAI_OPTION_REMOTE_ID, buf_in));
+ ASSERT_TRUE(remote_id_opt);
+ rai->addOption(remote_id_opt);
+
+ OptionPtr subscriber_id_opt(new Option(Option::V4,
+ RAI_OPTION_SUBSCRIBER_ID, buf_in));
+ ASSERT_TRUE(subscriber_id_opt);
+ rai->addOption(subscriber_id_opt);
+
+ splitOptionWithSuboptionWhichOverflow(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt);
+}
+
+// This test verifies that split options for v4 is working correctly even if
+// every suboption is smaller than 255 bytes, but the parent option still
+// overflows.
+TEST_F(LibDhcpTest, splitOptionWithSuboptionWhichOverflowMultiThreading) {
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ // Create RAI options which should be fused by the server.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(128);
+ for (uint32_t i = 0; i < 128; ++i) {
+ buf_in[i] = i;
+ }
+
+ OptionPtr circuit_id_opt(new Option(Option::V4,
+ RAI_OPTION_AGENT_CIRCUIT_ID, buf_in));
+ ASSERT_TRUE(circuit_id_opt);
+ rai->addOption(circuit_id_opt);
+
+ OptionPtr remote_id_opt(new Option(Option::V4,
+ RAI_OPTION_REMOTE_ID, buf_in));
+ ASSERT_TRUE(remote_id_opt);
+ rai->addOption(remote_id_opt);
+
+ OptionPtr subscriber_id_opt(new Option(Option::V4,
+ RAI_OPTION_SUBSCRIBER_ID, buf_in));
+ ASSERT_TRUE(subscriber_id_opt);
+ rai->addOption(subscriber_id_opt);
+
+ typedef function<void()> CallBack;
+ ThreadPool<CallBack> tp;
+ tp.start(256);
+
+ // Options are shared between threads to mimic the server defined options
+ // in the packet which are added from running configuration.
+ for (uint32_t count = 0; count < 1024; ++count) {
+ auto const& work = [&] {
+ splitOptionWithSuboptionWhichOverflow(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt);
+ };
+
+ boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work);
+ tp.add(call_back);
+ }
+ ASSERT_TRUE(tp.wait(10));
+}
+
+// This test verifies that split options for v4 is working correctly.
+TEST_F(LibDhcpTest, splitLongOptionWithLongSuboption) {
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ // Create RAI options which should be fused by the server.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (uint32_t i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ OptionPtr circuit_id_opt(new Option(Option::V4,
+ RAI_OPTION_AGENT_CIRCUIT_ID, buf_in));
+ ASSERT_TRUE(circuit_id_opt);
+ rai->addOption(circuit_id_opt);
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer small_buf_in(64);
+ for (uint32_t i = 0; i < 64; ++i) {
+ small_buf_in[i] = i;
+ }
+
+ OptionPtr remote_id_opt(new Option(Option::V4,
+ RAI_OPTION_REMOTE_ID, small_buf_in));
+ ASSERT_TRUE(remote_id_opt);
+ rai->addOption(remote_id_opt);
+
+ OptionPtr subscriber_id_opt(new Option(Option::V4,
+ RAI_OPTION_SUBSCRIBER_ID, buf_in));
+ ASSERT_TRUE(subscriber_id_opt);
+ rai->addOption(subscriber_id_opt);
+
+ splitLongOptionWithLongSuboption(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt);
+}
+
+// This test verifies that split options for v4 is working correctly.
+TEST_F(LibDhcpTest, splitLongOptionWithLongSuboptionMultiThreading) {
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ // Create RAI options which should be fused by the server.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(2560);
+ for (uint32_t i = 0; i < 2560; ++i) {
+ buf_in[i] = i;
+ }
+
+ OptionPtr circuit_id_opt(new Option(Option::V4,
+ RAI_OPTION_AGENT_CIRCUIT_ID, buf_in));
+ ASSERT_TRUE(circuit_id_opt);
+ rai->addOption(circuit_id_opt);
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer small_buf_in(64);
+ for (uint32_t i = 0; i < 64; ++i) {
+ small_buf_in[i] = i;
+ }
+
+ OptionPtr remote_id_opt(new Option(Option::V4,
+ RAI_OPTION_REMOTE_ID, small_buf_in));
+ ASSERT_TRUE(remote_id_opt);
+ rai->addOption(remote_id_opt);
+
+ OptionPtr subscriber_id_opt(new Option(Option::V4,
+ RAI_OPTION_SUBSCRIBER_ID, buf_in));
+ ASSERT_TRUE(subscriber_id_opt);
+ rai->addOption(subscriber_id_opt);
+
+ typedef function<void()> CallBack;
+ ThreadPool<CallBack> tp;
+ tp.start(256);
+
+ // Options are shared between threads to mimic the server defined options
+ // in the packet which are added from running configuration.
+ for (uint32_t count = 0; count < 1024; ++count) {
+ auto const& work = [&] {
+ splitLongOptionWithLongSuboption(rai, circuit_id_opt, remote_id_opt, subscriber_id_opt);
+ };
+
+ boost::shared_ptr<CallBack> call_back = boost::make_shared<CallBack>(work);
+ tp.add(call_back);
+ }
+ ASSERT_TRUE(tp.wait(10));
+}
+
+// This test verifies that fuse options for v4 is working correctly.
+TEST_F(LibDhcpTest, fuseLongOption) {
+ OptionCollection col;
+
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ for (uint32_t i = 0; i < 256; ++i) {
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(64);
+ for (uint32_t j = 0; j < 64; ++j) {
+ buf_in[j] = j;
+ }
+
+ boost::shared_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(option.reset(new OptionCustom(opt_def, Option::V4, buf_in)));
+ ASSERT_TRUE(option);
+ col.insert(std::make_pair(231, option));
+ }
+ ASSERT_EQ(256, col.size());
+ LibDHCP::fuseOptions4(col);
+
+ ASSERT_EQ(1, col.size());
+ uint8_t index = 0;
+ for (auto const& option : col) {
+ for (auto const& value : option.second->getData()) {
+ ASSERT_EQ(index, value);
+ index++;
+ if (index == 64) {
+ index = 0;
+ }
+ }
+ }
+}
+
+// This test verifies that fuse options for v4 is working correctly.
+TEST_F(LibDhcpTest, fuseLongOptionWithLongSuboption) {
+ OptionCollection col;
+
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ // Create RAI options which should be fused by the server.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+
+ for (uint32_t i = 0; i < 256; ++i) {
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(64);
+ for (uint32_t j = 0; j < 64; ++j) {
+ buf_in[j] = j;
+ }
+
+ OptionPtr circuit_id_opt(new Option(Option::V4,
+ RAI_OPTION_AGENT_CIRCUIT_ID, buf_in));
+ ASSERT_TRUE(circuit_id_opt);
+ rai->addOption(circuit_id_opt);
+ }
+ col.insert(std::make_pair(213, rai));
+ ASSERT_EQ(1, col.size());
+ ASSERT_EQ(256, col.begin()->second->getOptions().size());
+ LibDHCP::fuseOptions4(col);
+
+ ASSERT_EQ(1, col.size());
+ ASSERT_EQ(1, col.begin()->second->getOptions().size());
+ uint8_t index = 0;
+ for (auto const& option : col.begin()->second->getOptions()) {
+ for (auto const& value : option.second->getData()) {
+ ASSERT_EQ(index, value);
+ index++;
+ if (index == 64) {
+ index = 0;
+ }
+ }
+ }
+}
+
+// This test checks that the server can receive multiple vendor options
+// (code 124) with some using the same enterprise ID and some using a different
+// enterprise ID. It should also be able to extend one option which contains
+// multiple enterprise IDs in multiple instances of OptionVendor.
+// The extendVendorOptions4 should be able to create one instance for each
+// enterprise ID, each with it's respective tuples.
+// Some of the test scenarios are not following RFCs, but people out there are
+// like to do it anyway. We want Kea to be robust and handle such scenarios,
+// therefore we're testing also for non-conformant behavior.
+TEST_F(LibDhcpTest, extendVivco) {
+ OptionBuffer data1 = {
+ 0, 0, 0, 1, // enterprise id 1
+ 5, // length 5
+ 0x66, 0x69, 0x72, 0x73, 0x74, // 'first'
+ 0, 0, 0, 1, // enterprise id 1
+ 6, // length 6
+ 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64 // 'second'
+ };
+ OptionPtr opt1(new Option(Option::V4, DHO_VIVCO_SUBOPTIONS,
+ data1.cbegin(), data1.cend()));
+ OptionBuffer data2 = {
+ 0, 0, 0, 2, // enterprise id 2
+ 5, // length 5
+ 0x65, 0x78, 0x74, 0x72, 0x61 // 'extra'
+ };
+ OptionPtr opt2(new Option(Option::V4, DHO_VIVCO_SUBOPTIONS,
+ data2.cbegin(), data2.cend()));
+ OptionBuffer data3 = {
+ 0, 0, 0, 1, // enterprise id 1
+ 5, // length 5
+ 0x74, 0x68, 0x69, 0x72, 0x64 // 'third'
+ };
+ OptionPtr opt3(new Option(Option::V4, DHO_VIVCO_SUBOPTIONS,
+ data3.cbegin(), data3.cend()));
+ OptionCollection options;
+ options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt1));
+ options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt2));
+ options.insert(make_pair(DHO_VIVCO_SUBOPTIONS, opt3));
+ EXPECT_EQ(options.size(), 3);
+ EXPECT_NO_THROW(LibDHCP::fuseOptions4(options));
+ EXPECT_EQ(options.size(), 1);
+ EXPECT_NO_THROW(LibDHCP::extendVendorOptions4(options));
+ EXPECT_EQ(options.size(), 2);
+ EXPECT_EQ(options.count(DHO_VIVCO_SUBOPTIONS), 2);
+ for (auto const& option : options) {
+ ASSERT_EQ(option.second->getType(), DHO_VIVCO_SUBOPTIONS);
+ OptionVendorClassPtr vendor =
+ boost::dynamic_pointer_cast<OptionVendorClass>(option.second);
+ ASSERT_TRUE(vendor);
+ if (vendor->getVendorId() == 1) {
+ ASSERT_EQ(vendor->getTuplesNum(), 3);
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(tuple = vendor->getTuple(0));
+ EXPECT_EQ(5, tuple.getLength());
+ EXPECT_EQ("first", tuple.getText());
+ ASSERT_NO_THROW(tuple = vendor->getTuple(1));
+ EXPECT_EQ(6, tuple.getLength());
+ EXPECT_EQ("second", tuple.getText());
+ ASSERT_NO_THROW(tuple = vendor->getTuple(2));
+ EXPECT_EQ(5, tuple.getLength());
+ EXPECT_EQ("third", tuple.getText());
+ } else if (vendor->getVendorId() == 2) {
+ ASSERT_EQ(vendor->getTuplesNum(), 1);
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(tuple = vendor->getTuple(0));
+ EXPECT_EQ(5, tuple.getLength());
+ EXPECT_EQ("extra", tuple.getText());
+ } else {
+ FAIL() << "unexpected vendor type: " << vendor->getVendorId();
+ }
+ }
+}
+
+// This test checks that the server can receive multiple vendor options
+// (code 125) with some using the same enterprise ID and some using a different
+// enterprise ID. It should also be able to extend one option which contains
+// multiple enterprise IDs in multiple instances of OptionVendor.
+// The extendVendorOptions4 should be able to create one instance for each
+// enterprise ID, each with it's respective suboptions.
+// Some of the test scenarios are not following RFCs, but people out there are
+// like to do it anyway. We want Kea to be robust and handle such scenarios,
+// therefore we're testing also for non-conformant behavior.
+TEST_F(LibDhcpTest, extendVivso) {
+ OptionPtr suboption;
+ OptionVendorPtr opt1(new OptionVendor(Option::V4, 1));
+ suboption.reset(new OptionString(Option::V4, 16, "first"));
+ opt1->addOption(suboption);
+ OptionVendorPtr opt2(new OptionVendor(Option::V4, 1));
+ suboption.reset(new OptionString(Option::V4, 32, "second"));
+ opt2->addOption(suboption);
+ OptionVendorPtr opt3(new OptionVendor(Option::V4, 2));
+ suboption.reset(new OptionString(Option::V4, 128, "extra"));
+ opt3->addOption(suboption);
+ OptionVendorPtr opt4(new OptionVendor(Option::V4, 1));
+ suboption.reset(new OptionString(Option::V4, 64, "third"));
+ opt4->addOption(suboption);
+ OptionCollection container;
+ container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt1));
+ container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt2));
+ OptionCollection options;
+ for (auto const& option : container) {
+ const OptionBuffer& buffer = option.second->toBinary();
+ options.insert(make_pair(option.second->getType(),
+ OptionPtr(new Option(Option::V4,
+ option.second->getType(),
+ buffer))));
+ }
+ ASSERT_NO_THROW(LibDHCP::fuseOptions4(options));
+ ASSERT_EQ(options.size(), 1);
+ ASSERT_EQ(options.count(DHO_VIVSO_SUBOPTIONS), 1);
+ ASSERT_EQ(options.find(DHO_VIVSO_SUBOPTIONS)->second->getType(), DHO_VIVSO_SUBOPTIONS);
+ container.clear();
+ container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, options.begin()->second));
+ container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt3));
+ container.insert(make_pair(DHO_VIVSO_SUBOPTIONS, opt4));
+ ASSERT_EQ(container.size(), 3);
+ options.clear();
+ for (auto const& option : container) {
+ const OptionBuffer& buffer = option.second->toBinary();
+ options.insert(make_pair(option.second->getType(),
+ OptionPtr(new Option(Option::V4,
+ option.second->getType(),
+ buffer))));
+ }
+ ASSERT_EQ(options.size(), 3);
+ LibDHCP::extendVendorOptions4(options);
+ ASSERT_EQ(options.size(), 2);
+ ASSERT_EQ(options.count(DHO_VIVSO_SUBOPTIONS), 2);
+ for (auto const& option : options) {
+ ASSERT_EQ(option.second->getType(), DHO_VIVSO_SUBOPTIONS);
+ OptionCollection suboptions = option.second->getOptions();
+ OptionPtr suboption;
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(option.second);
+ ASSERT_TRUE(vendor);
+ if (vendor->getVendorId() == 1) {
+ ASSERT_EQ(suboptions.size(), 3);
+ suboption = option.second->getOption(16);
+ ASSERT_TRUE(suboption);
+ suboption = option.second->getOption(32);
+ ASSERT_TRUE(suboption);
+ suboption = option.second->getOption(64);
+ ASSERT_TRUE(suboption);
+ } else if (vendor->getVendorId() == 2) {
+ ASSERT_EQ(suboptions.size(), 1);
+ suboption = option.second->getOption(128);
+ ASSERT_TRUE(suboption);
+ } else {
+ FAIL() << "unexpected vendor type: " << vendor->getVendorId();
+ }
+ }
+}
+
+// This test verifies that pack options for v4 is working correctly.
+TEST_F(LibDhcpTest, packOptions4) {
+ vector<uint8_t> payload[5];
+ for (unsigned i = 0; i < 5; i++) {
+ payload[i].resize(3);
+ payload[i][0] = i * 10;
+ payload[i][1] = i * 10 + 1;
+ payload[i][2] = i * 10 + 2;
+ }
+
+ OptionPtr opt1(new Option(Option::V4, 12, payload[0]));
+ OptionPtr opt2(new Option(Option::V4, 60, payload[1]));
+ OptionPtr opt3(new Option(Option::V4, 14, payload[2]));
+ OptionPtr opt4(new Option(Option::V4, 254, payload[3]));
+ OptionPtr opt5(new Option(Option::V4, 128, payload[4]));
+
+ // Create vendor option instance with DOCSIS3.0 enterprise id.
+ OptionVendorPtr vivsi(new OptionVendor(Option::V4, VENDOR_ID_CABLE_LABS));
+ vivsi->addOption(OptionPtr(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS,
+ IOAddress("10.0.0.10"))));
+
+ OptionPtr vsi(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS,
+ OptionBuffer()));
+ vsi->addOption(OptionPtr(new Option(Option::V4, 0xDC, OptionBuffer())));
+
+ // Add RAI option, which comprises 3 sub-options.
+
+ // Get the option definition for RAI option. This option is represented
+ // by OptionCustom which requires a definition to be passed to
+ // the constructor.
+ OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def);
+ // Create RAI option.
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));
+
+ // The sub-options are created using the bits of v4_opts buffer because
+ // we want to use this buffer as a reference to verify that produced
+ // option in on-wire format is correct.
+
+ // Create Circuit ID sub-option and add to RAI.
+ OptionPtr circuit_id(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID,
+ OptionBuffer(v4_opts + 46,
+ v4_opts + 50)));
+ rai->addOption(circuit_id);
+
+ // Create Remote ID option and add to RAI.
+ OptionPtr remote_id(new Option(Option::V4, RAI_OPTION_REMOTE_ID,
+ OptionBuffer(v4_opts + 52, v4_opts + 58)));
+ rai->addOption(remote_id);
+
+ // Create Vendor Specific Information and add to RAI.
+ OptionPtr rai_vsi(new Option(Option::V4, RAI_OPTION_VSI,
+ OptionBuffer(v4_opts + 60, v4_opts + 69)));
+ rai->addOption(rai_vsi);
+
+ isc::dhcp::OptionCollection opts; // list of options
+ // Note that we insert each option under the same option code into
+ // the map. This guarantees that options are packed in the same order
+ // they were added. Otherwise, options would get sorted by code and
+ // the resulting buffer wouldn't match with the reference buffer.
+ opts.insert(make_pair(opt1->getType(), opt1));
+ opts.insert(make_pair(opt1->getType(), opt2));
+ opts.insert(make_pair(opt1->getType(), opt3));
+ opts.insert(make_pair(opt1->getType(), opt4));
+ opts.insert(make_pair(opt1->getType(), opt5));
+ opts.insert(make_pair(opt1->getType(), vivsi));
+ opts.insert(make_pair(opt1->getType(), vsi));
+ opts.insert(make_pair(opt1->getType(), rai));
+
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts));
+ ASSERT_EQ(buf.getLength(), sizeof(v4_opts));
+ EXPECT_EQ(0, memcmp(v4_opts, buf.getData(), sizeof(v4_opts)));
+}
+
+// This test verifies that pack options for v4 is working correctly
+// and RAI option is packed last.
+TEST_F(LibDhcpTest, packOptions4Order) {
+ uint8_t expected[] = {
+ 12, 3, 0, 1, 2, // Just a random option
+ 99, 3, 10, 11, 12, // Another random option
+ 82, 3, 20, 21, 22 // Relay Agent Info option
+ };
+
+ vector<uint8_t> payload[3];
+ for (unsigned i = 0; i < 3; i++) {
+ payload[i].resize(3);
+ payload[i][0] = i*10;
+ payload[i][1] = i*10+1;
+ payload[i][2] = i*10+2;
+ }
+
+ OptionPtr opt12(new Option(Option::V4, 12, payload[0]));
+ OptionPtr opt99(new Option(Option::V4, 99, payload[1]));
+ OptionPtr opt82(new Option(Option::V4, 82, payload[2]));
+
+ // Let's create options. They are added in 82,12,99, but the should be
+ // packed in 12,99,82 order (82, which is RAI, should go last)
+ isc::dhcp::OptionCollection opts;
+ opts.insert(make_pair(opt82->getType(), opt82));
+ opts.insert(make_pair(opt12->getType(), opt12));
+ opts.insert(make_pair(opt99->getType(), opt99));
+
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts));
+ ASSERT_EQ(buf.getLength(), sizeof(expected));
+ EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
+}
+
+TEST_F(LibDhcpTest, unpackOptions4) {
+ vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts));
+ isc::dhcp::OptionCollection options; // list of options
+ list<uint16_t> deferred;
+
+ ASSERT_NO_THROW(
+ LibDHCP::unpackOptions4(v4packed, DHCP4_OPTION_SPACE, options,
+ deferred, false);
+ );
+
+ ASSERT_NO_THROW(LibDHCP::extendVendorOptions4(options));
+
+ isc::dhcp::OptionCollection::const_iterator x = options.find(12);
+ ASSERT_FALSE(x == options.end()); // option 1 should exist
+ // Option 12 holds a string so let's cast it to an appropriate type.
+ OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second);
+ ASSERT_TRUE(option12);
+ EXPECT_EQ(12, option12->getType()); // this should be option 12
+ ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option12->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3
+
+ x = options.find(60);
+ ASSERT_FALSE(x == options.end()); // option 2 should exist
+ EXPECT_EQ(60, x->second->getType()); // this should be option 60
+ ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->second->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 7, 3)); // data len=3
+
+ x = options.find(14);
+ ASSERT_FALSE(x == options.end()); // option 3 should exist
+ OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x->second);
+ ASSERT_TRUE(option14);
+ EXPECT_EQ(14, option14->getType()); // this should be option 14
+ ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option14->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 12, 3)); // data len=3
+
+ x = options.find(254);
+ ASSERT_FALSE(x == options.end()); // option 4 should exist
+ EXPECT_EQ(254, x->second->getType()); // this should be option 254
+ ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->second->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 17, 3)); // data len=3
+
+ x = options.find(128);
+ ASSERT_FALSE(x == options.end()); // option 5 should exist
+ EXPECT_EQ(128, x->second->getType()); // this should be option 128
+ ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->second->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 22, 3)); // data len=3
+
+ // Verify that V-I Vendor Specific Information option is parsed correctly.
+ x = options.find(125);
+ ASSERT_FALSE(x == options.end());
+ OptionVendorPtr vivsi = boost::dynamic_pointer_cast<OptionVendor>(x->second);
+ ASSERT_TRUE(vivsi);
+ EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType());
+ EXPECT_EQ(VENDOR_ID_CABLE_LABS, vivsi->getVendorId());
+ OptionCollection suboptions = vivsi->getOptions();
+
+ // There should be one suboption of V-I VSI.
+ ASSERT_EQ(1, suboptions.size());
+ // This vendor option has a standard definition and thus should be
+ // converted to appropriate class, i.e. Option4AddrLst. If this cast
+ // fails, it means that its definition was not used while it was
+ // parsed.
+ Option4AddrLstPtr tftp =
+ boost::dynamic_pointer_cast<Option4AddrLst>(suboptions.begin()->second);
+ ASSERT_TRUE(tftp);
+ EXPECT_EQ(DOCSIS3_V4_TFTP_SERVERS, tftp->getType());
+ EXPECT_EQ(6, tftp->len());
+ Option4AddrLst::AddressContainer addresses = tftp->getAddresses();
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ("10.0.0.10", addresses[0].toText());
+
+ // Checking DHCP Relay Agent Information Option.
+ x = options.find(DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_FALSE(x == options.end());
+ EXPECT_EQ(DHO_DHCP_AGENT_OPTIONS, x->second->getType());
+ // RAI is represented by OptionCustom.
+ OptionCustomPtr rai = boost::dynamic_pointer_cast<OptionCustom>(x->second);
+ ASSERT_TRUE(rai);
+ // RAI should have 3 sub-options: Circuit ID, Agent Remote ID, Vendor
+ // Specific Information option. Note that by parsing these suboptions we
+ // are checking that unpackOptions4 differentiates between standard option
+ // space called "dhcp4" and other option spaces. These sub-options do not
+ // belong to standard option space and should be parsed using different
+ // option definitions.
+
+ // Check that Circuit ID option is among parsed options.
+ OptionPtr rai_option = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID);
+ ASSERT_TRUE(rai_option);
+ EXPECT_EQ(RAI_OPTION_AGENT_CIRCUIT_ID, rai_option->getType());
+ ASSERT_EQ(6, rai_option->len());
+ EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 46, 4));
+
+ // Check that Remote ID option is among parsed options.
+ rai_option = rai->getOption(RAI_OPTION_REMOTE_ID);
+ ASSERT_TRUE(rai_option);
+ EXPECT_EQ(RAI_OPTION_REMOTE_ID, rai_option->getType());
+ ASSERT_EQ(8, rai_option->len());
+ EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 52, 6));
+
+ // Check that Vendor Specific Information option is among parsed options.
+ rai_option = rai->getOption(RAI_OPTION_VSI);
+ ASSERT_TRUE(rai_option);
+ EXPECT_EQ(RAI_OPTION_VSI, rai_option->getType());
+ ASSERT_EQ(11, rai_option->len());
+ EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 60, 9));
+
+ // Make sure, that option other than those above is not present.
+ EXPECT_FALSE(rai->getOption(10));
+
+ // Check the same for the global option space.
+ x = options.find(0);
+ EXPECT_TRUE(x == options.end()); // option 0 not found
+
+ x = options.find(1);
+ EXPECT_TRUE(x == options.end()); // option 1 not found
+
+ x = options.find(2);
+ EXPECT_TRUE(x == options.end()); // option 2 not found
+
+}
+
+// Check parsing of an empty option.
+TEST_F(LibDhcpTest, unpackEmptyOption4) {
+ // Create option definition for the option code 254 without fields.
+ OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 254,
+ DHCP4_OPTION_SPACE,
+ "empty", false));
+
+ // Use it as runtime option definition within standard options space.
+ // The tested code should find this option definition within runtime
+ // option definitions set when it detects that this definition is
+ // not a standard definition.
+ OptionDefSpaceContainer defs;
+ defs.addItem(opt_def);
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Create the buffer holding the structure of the empty option.
+ OptionBuffer buf = {
+ 0xFE, // option code = 254
+ 0x00 // option length = 0
+ };
+
+ // Parse options.
+ OptionCollection options;
+ list<uint16_t> deferred;
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE,
+ options, deferred, false));
+
+ // There should be one option.
+ ASSERT_EQ(1, options.size());
+ OptionPtr option_empty = options.begin()->second;
+ ASSERT_TRUE(option_empty);
+ EXPECT_EQ(254, option_empty->getType());
+ EXPECT_EQ(2, option_empty->len());
+}
+
+// This test verifies that the following option structure can be parsed:
+// - option (option space 'foobar')
+// - sub option (option space 'foo')
+// - sub option (option space 'bar')
+// @todo Add more thorough unit tests for unpackOptions.
+TEST_F(LibDhcpTest, unpackSubOptions4) {
+ // Create option definition for each level of encapsulation. Each option
+ // definition is for the option code 1. Options may have the same
+ // option code because they belong to different option spaces.
+
+ // Top level option encapsulates options which belong to 'space-foo'.
+ OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1,
+ "space-foobar",
+ "uint32",
+ "space-foo")); \
+ // Middle option encapsulates options which belong to 'space-bar'
+ OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1,
+ "space-foo",
+ "uint16",
+ "space-bar"));
+ // Low level option doesn't encapsulate any option space.
+ OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
+ "space-bar",
+ "uint8"));
+
+ // Register created option definitions as runtime option definitions.
+ OptionDefSpaceContainer defs;
+ ASSERT_NO_THROW(defs.addItem(opt_def));
+ ASSERT_NO_THROW(defs.addItem(opt_def2));
+ ASSERT_NO_THROW(defs.addItem(opt_def3));
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Create the buffer holding the structure of options.
+ OptionBuffer buf = {
+ // First option starts here.
+ 0x01, // option code = 1
+ 0x0B, // option length = 11
+ 0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
+ // Sub option starts here.
+ 0x01, // option code = 1
+ 0x05, // option length = 5
+ 0x01, 0x02, // this option carries uint16 value
+ // Last option starts here.
+ 0x01, // option code = 1
+ 0x01, // option length = 1
+ 0x00 // This option carries a single uint8
+ // value and has no sub options.
+ };
+
+ // Parse options.
+ OptionCollection options;
+ list<uint16_t> deferred;
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, "space-foobar",
+ options, deferred, false));
+
+ // There should be one top level option.
+ ASSERT_EQ(1, options.size());
+ boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
+ boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
+ second);
+ ASSERT_TRUE(option_foobar);
+ EXPECT_EQ(1, option_foobar->getType());
+ EXPECT_EQ(0x00010203, option_foobar->getValue());
+ // There should be a middle level option held in option_foobar.
+ boost::shared_ptr<OptionInt<uint16_t> > option_foo =
+ boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
+ getOption(1));
+ ASSERT_TRUE(option_foo);
+ EXPECT_EQ(1, option_foo->getType());
+ EXPECT_EQ(0x0102, option_foo->getValue());
+ // Finally, there should be a low level option under option_foo.
+ boost::shared_ptr<OptionInt<uint8_t> > option_bar =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1));
+ ASSERT_TRUE(option_bar);
+ EXPECT_EQ(1, option_bar->getType());
+ EXPECT_EQ(0x0, option_bar->getValue());
+}
+
+// Verifies that options 0 (PAD) and 255 (END) are handled as PAD and END
+// in and only in the dhcp4 space.
+TEST_F(LibDhcpTest, unpackPadEnd) {
+ // Create option definition for the container.
+ OptionDefinitionPtr opt_def(new OptionDefinition("container", 200,
+ DHCP4_OPTION_SPACE,
+ "empty", "my-space"));
+ // Create option definition for option 0.
+ OptionDefinitionPtr opt_def0(new OptionDefinition("zero", 0,
+ "my-space", "uint8"));
+
+ // Create option definition for option 255.
+ OptionDefinitionPtr opt_def255(new OptionDefinition("max", 255,
+ "my-space", "uint8"));
+
+ // Create option definition for another option.
+ OptionDefinitionPtr opt_def2(new OptionDefinition("my-option", 1,
+ "my-space", "string"));
+
+ // Register created option definitions as runtime option definitions.
+ OptionDefSpaceContainer defs;
+ ASSERT_NO_THROW(defs.addItem(opt_def));
+ ASSERT_NO_THROW(defs.addItem(opt_def0));
+ ASSERT_NO_THROW(defs.addItem(opt_def255));
+ ASSERT_NO_THROW(defs.addItem(opt_def2));
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Create the buffer holding the structure of options.
+ OptionBuffer buf = {
+ // Add a PAD
+ 0x00, // option code = 0 (PAD)
+ // Container option starts here.
+ 0xc8, // option code = 200 (container)
+ 0x0b, // option length = 11
+ // Suboption 0.
+ 0x00, 0x01, 0x00, // code = 0, length = 1, content = 0
+ // Suboption 255.
+ 0xff, 0x01, 0xff, // code = 255, length = 1, content = 255
+ // Suboption 1.
+ 0x01, 0x03, 0x66, 0x6f, 0x6f, // code = 1, length = 2, content = "foo"
+ // END
+ 0xff,
+ // Extra bytes at tail.
+ 0x01, 0x02, 0x03, 0x04
+ };
+
+ // Parse options.
+ OptionCollection options;
+ list<uint16_t> deferred;
+ size_t offset = 0;
+ ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE,
+ options, deferred, false));
+
+ // Returned offset should point to the END.
+ EXPECT_EQ(0xff, buf[offset]);
+
+ // There should be one top level option.
+ ASSERT_EQ(1, options.size());
+
+ // Get it.
+ OptionPtr option = options.begin()->second;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(200, option->getType());
+
+ // There should be 3 suboptions.
+ ASSERT_EQ(3, option->getOptions().size());
+
+ // Get suboption 0.
+ boost::shared_ptr<OptionInt<uint8_t> > sub0 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (option->getOption(0));
+ ASSERT_TRUE(sub0);
+ EXPECT_EQ(0, sub0->getType());
+ EXPECT_EQ(0, sub0->getValue());
+
+ // Get suboption 255.
+ boost::shared_ptr<OptionInt<uint8_t> > sub255 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (option->getOption(255));
+ ASSERT_TRUE(sub255);
+ EXPECT_EQ(255, sub255->getType());
+ EXPECT_EQ(255, sub255->getValue());
+
+ // Get suboption 1.
+ boost::shared_ptr<OptionString> sub =
+ boost::dynamic_pointer_cast<OptionString>(option->getOption(1));
+ ASSERT_TRUE(sub);
+ EXPECT_EQ(1, sub->getType());
+ EXPECT_EQ("foo", sub->getValue());
+}
+
+// Verifies that option 0 (PAD) is handled as PAD in option 43 (so when
+// flexible pad end flag is true) only when option 0 (PAD) is not defined.
+TEST_F(LibDhcpTest, option43Pad) {
+ string space = "my-option43-space";
+
+ // Create option definition for option 1.
+ OptionDefinitionPtr opt_def1(new OptionDefinition("one", 1, space, "binary"));
+
+ // Create option definition for option 2.
+ OptionDefinitionPtr opt_def2(new OptionDefinition("two", 2, space, "uint8"));
+
+ // Register created option definitions as runtime option definitions.
+ OptionDefSpaceContainer defs;
+ ASSERT_NO_THROW(defs.addItem(opt_def1));
+ ASSERT_NO_THROW(defs.addItem(opt_def2));
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ // Create the buffer holding an option 43 content.
+ OptionBuffer buf = {
+ // Suboption 0,
+ 0x00, 0x01, 0x00, // code = 0, length = 1, content = 0
+ // or option code = 0 (PAD) followed by
+ // code = 1, length = 0
+ // Suboption 2.
+ 0x02, 0x01, 0x01, // code = 2, length = 1, content = 1
+ };
+
+ // Parse options.
+ OptionCollection options;
+ list<uint16_t> deferred;
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, space, options, deferred, true));
+
+ // There should be 2 suboptions (1 and 2) because no sub-option 0
+ // was defined so code 0 means PAD.
+ ASSERT_EQ(2, options.size());
+
+ // Get suboption 1.
+ OptionPtr sub1 = options.begin()->second;
+ ASSERT_TRUE(sub1);
+ EXPECT_EQ(1, sub1->getType());
+ EXPECT_EQ(0, sub1->len() - sub1->getHeaderLen());
+
+ // Get suboption 2.
+ boost::shared_ptr<OptionInt<uint8_t> > sub2 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (options.rbegin()->second);
+ ASSERT_TRUE(sub2);
+ EXPECT_EQ(2, sub2->getType());
+ EXPECT_EQ(1, sub2->getValue());
+
+ // Create option definition for option 0 and register it.
+ OptionDefinitionPtr opt_def0(new OptionDefinition("zero", 0, space, "uint8"));
+ ASSERT_NO_THROW(defs.addItem(opt_def0));
+ LibDHCP::clearRuntimeOptionDefs();
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ options.clear();
+ ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, space, options, deferred, true));
+
+ // There should be 2 suboptions (0 and 1).
+ EXPECT_EQ(2, options.size());
+
+ // Get suboption 0
+ boost::shared_ptr<OptionInt<uint8_t> > sub0 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (options.begin()->second);
+ ASSERT_TRUE(sub0);
+ EXPECT_EQ(0, sub0->getType());
+ EXPECT_EQ(0, sub0->getValue());
+
+ // Get suboption 2.
+ sub2 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (options.rbegin()->second);
+ ASSERT_TRUE(sub2);
+ EXPECT_EQ(2, sub2->getType());
+ EXPECT_EQ(1, sub2->getValue());
+}
+
+// Verifies that option 255 (END) is handled as END in option 43 (so when
+//flexible pad end flag is true) only when option 255 (END) is not defined.
+TEST_F(LibDhcpTest, option43End) {
+ string space = "my-option43-space";
+
+ // Create the buffer holding an option 43 content.
+ OptionBuffer buf = {
+ // Suboption 255,
+ 0xff, 0x01, 0x02 // code = 255, length = 1, content = 2
+ };
+
+ // Parse options.
+ OptionCollection options;
+ list<uint16_t> deferred;
+ size_t offset = 0;
+ ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, space,
+ options, deferred, true));
+
+ // Parsing should stop at the first byte.
+ EXPECT_EQ(0, offset);
+
+ // There should be 0 suboptions.
+ EXPECT_EQ(0, options.size());
+
+ // Create option definition for option 255.
+ OptionDefinitionPtr opt_def255(new OptionDefinition("max", 255, space, "uint8"));
+
+ // Register created option definition as runtime option definitions.
+ OptionDefSpaceContainer defs;
+ ASSERT_NO_THROW(defs.addItem(opt_def255));
+ LibDHCP::setRuntimeOptionDefs(defs);
+ LibDHCP::commitRuntimeOptionDefs();
+
+ options.clear();
+ ASSERT_NO_THROW(offset = LibDHCP::unpackOptions4(buf, space,
+ options, deferred, true));
+
+ // There should be 1 suboption.
+ ASSERT_EQ(1, options.size());
+
+ // Get suboption 255.
+ boost::shared_ptr<OptionInt<uint8_t> > sub255 =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >
+ (options.begin()->second);
+ ASSERT_TRUE(sub255);
+ EXPECT_EQ(255, sub255->getType());
+ EXPECT_EQ(2, sub255->getValue());
+}
+
+// Verify the option 43 END bug is fixed (#950: option code 255 was not
+// parse at END, now it is not parse at END only when an option code 255
+// is defined in the corresponding option space).
+TEST_F(LibDhcpTest, option43Factory) {
+ // Create the buffer holding the structure of option 43 content.
+ OptionBuffer buf = {
+ // Suboption 1.
+ 0x01, 0x00, // option code = 1, option length = 0
+ // END
+ 0xff
+ };
+
+ // Get last resort definition.
+ OptionDefinitionPtr def =
+ LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 43);
+ ASSERT_TRUE(def);
+
+ // Apply the definition.
+ OptionPtr option;
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V4, 43, buf));
+ ASSERT_TRUE(option);
+ EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, option->getType());
+ EXPECT_EQ(def->getEncapsulatedSpace(), option->getEncapsulatedSpace());
+
+ // There should be 1 suboption.
+ EXPECT_EQ(1, option->getOptions().size());
+
+ // Get suboption 1.
+ OptionPtr sub1 = option->getOption(1);
+ ASSERT_TRUE(sub1);
+ EXPECT_EQ(1, sub1->getType());
+ EXPECT_EQ(0, sub1->len() - sub1->getHeaderLen());
+
+ // Of course no suboption 255.
+ EXPECT_FALSE(option->getOption(255));
+}
+
+// Verifies that an Host Name (option 12), will be dropped when empty,
+// while subsequent options will still be unpacked.
+TEST_F(LibDhcpTest, emptyHostName) {
+ uint8_t opts[] = {
+ 12, 0, // Empty Hostname
+ 60, 3, 10, 11, 12 // Class Id
+ };
+
+ vector<uint8_t> packed(opts, opts + sizeof(opts));
+ isc::dhcp::OptionCollection options; // list of options
+ list<uint16_t> deferred;
+
+ ASSERT_NO_THROW(
+ LibDHCP::unpackOptions4(packed, DHCP4_OPTION_SPACE, options, deferred, false);
+ );
+
+ // Host Name should not exist, we quietly drop it when empty.
+ isc::dhcp::OptionCollection::const_iterator x = options.find(12);
+ ASSERT_TRUE(x == options.end());
+
+ // Verify Option 60 exists correctly
+ x = options.find(60);
+ ASSERT_FALSE(x == options.end());
+ EXPECT_EQ(60, x->second->getType());
+ ASSERT_EQ(3, x->second->getData().size());
+ EXPECT_EQ(5, x->second->len());
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], opts + 4, 3));
+};
+
+TEST_F(LibDhcpTest, stdOptionDefs4) {
+ // Create a buffer that holds dummy option data.
+ // It will be used to create most of the options.
+ std::vector<uint8_t> buf(48, 1);
+ OptionBufferConstIter begin = buf.begin();
+ OptionBufferConstIter end = buf.end();
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_MASK, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TIME_OFFSET, begin, begin + 4,
+ typeid(OptionInt<int32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROUTERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TIME_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_LOG_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_COOKIE_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_LPR_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_IMPRESS_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_RESOURCE_LOCATION_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_HOST_NAME, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BOOT_SIZE, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_MERIT_DUMP, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SWAP_SERVER, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROOT_PATH, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_EXTENSIONS_PATH, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_IP_FORWARDING, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NON_LOCAL_SOURCE_ROUTING, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_POLICY_FILTER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_MAX_DGRAM_REASSEMBLY, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_IP_TTL, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_AGING_TIMEOUT, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_PLATEAU_TABLE, begin, begin + 10,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_INTERFACE_MTU, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ALL_SUBNETS_LOCAL, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BROADCAST_ADDRESS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PERFORM_MASK_DISCOVERY, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_MASK_SUPPLIER, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_DISCOVERY, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_SOLICITATION_ADDRESS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_STATIC_ROUTES, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TRAILER_ENCAPSULATION, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ARP_CACHE_TIMEOUT, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_IEEE802_3_ENCAPSULATION, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_TCP_TTL, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_INTERVAL, begin,
+ begin + 4, typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_GARBAGE, begin, begin + 1,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NIS_DOMAIN, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NIS_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NTP_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NAME_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_DD_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NODE_TYPE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_SCOPE, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_FONT_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_X_DISPLAY_MANAGER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REQUESTED_ADDRESS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_LEASE_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_OPTION_OVERLOAD, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE_TYPE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_SERVER_IDENTIFIER, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_PARAMETER_REQUEST_LIST, begin, end,
+ typeid(OptionUint8Array));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MAX_MESSAGE_SIZE, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_RENEWAL_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REBINDING_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NWIP_DOMAIN_NAME, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NWIP_SUBOPTIONS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NISP_DOMAIN_NAME, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NISP_SERVER_ADDR, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TFTP_SERVER_NAME, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BOOT_FILE_NAME, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_HOME_AGENT_ADDRS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SMTP_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_POP3_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NNTP_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_WWW_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_FINGER_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_IRC_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_STREETTALK_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_STDASERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 5,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 9,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 45,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, end,
+ typeid(Option4SlpServiceScope));
+
+ // Check also with empty scope list
+ LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, begin + 1,
+ typeid(Option4SlpServiceScope));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, begin + 3,
+ typeid(Option4ClientFqdn));
+
+ // The following option requires well formed buffer to be created from.
+ // Not just a dummy one. This buffer includes some suboptions.
+ OptionBuffer agent_info_buf = createAgentInformationOption();
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS,
+ agent_info_buf.begin(),
+ agent_info_buf.end(),
+ typeid(OptionCustom),
+ "dhcp-agent-options-space");
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NDS_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NDS_TREE_NAME, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NDS_CONTEXT, begin, end,
+ typeid(OptionString));
+
+ // Prepare buffer holding an array of FQDNs.
+ const char fqdn_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_DOMAIN_NAME_LIST,
+ fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_IPV4_ADDR, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_AUTHENTICATE, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_CLIENT_LAST_TRANSACTION_TIME,
+ begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ASSOCIATED_IP, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_AUTO_CONFIG, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVICE_SEARCH, begin, begin + 4,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_SELECTION, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SYSTEM, begin, end,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NDI, begin, begin + 3,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_UUID_GUID, begin, begin + 17,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_USER_AUTH, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_GEOCONF_CIVIC, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PCODE, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TCODE, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_V6_ONLY_PREFERRED, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_ADDR, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_TAG, begin, end,
+ typeid(OptionString));
+
+ /* Option 114 URL (RFC 3679) was retired and replaced with CAPTIVE_PORTAL (RFC8910)
+ which was previously 160, but was reassigned (compare RFC7710 and RFC8910) */
+
+ LibDhcpTest::testStdOptionDefs4(DHO_V4_CAPTIVE_PORTAL, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
+ fqdn_buf.end(), typeid(OptionCustom));
+
+ // V-I Vendor option requires specially crafted data.
+ const char vivco_data[] = {
+ 1, 2, 3, 4, // enterprise id
+ 3, 1, 2, 3 // first byte is opaque data length, the rest is opaque data
+ };
+ std::vector<uint8_t> vivco_buf(vivco_data, vivco_data + sizeof(vivco_data));
+ const char vivsio_data[] = {
+ 1, 2, 3, 4, // enterprise id
+ 4, // first byte is vendor block length
+ 1, 2, 3, 4 // option type=1 length=2
+ };
+ std::vector<uint8_t> vivsio_buf(vivsio_data, vivsio_data + sizeof(vivsio_data));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(),
+ vivco_buf.end(), typeid(OptionVendorClass));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(),
+ vivsio_buf.end(), typeid(OptionVendor));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PANA_AGENT, begin, end,
+ typeid(Option4AddrLst));
+
+ // Prepare buffer holding one FQDN.
+ const char fqdn1_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn1_buf(fqdn1_data,
+ fqdn1_data + sizeof(fqdn1_data));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_V4_LOST, fqdn1_buf.begin(),
+ fqdn1_buf.end(), typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_CAPWAP_AC_V4, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SIP_UA_CONF_SERVICE_DOMAINS,
+ fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ // V4_SZTP_REDIRECT is using an array of tuples so let's create example data
+ const char opaque_tuple_data[] = {
+ 0, 3, 1, 2, 3 // first 2 bytes are opaque data length, the rest is opaque data
+ };
+ std::vector<uint8_t> opaque_tuple_buf(opaque_tuple_data,
+ opaque_tuple_data + sizeof(opaque_tuple_data));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_V4_SZTP_REDIRECT,
+ opaque_tuple_buf.begin(),
+ opaque_tuple_buf.end(),
+ typeid(OptionOpaqueDataTuples));
+
+ std::vector<uint8_t> rdnss1_buf(begin, begin + 9);
+ rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end());
+
+ LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss1_buf.begin(),
+ rdnss1_buf.end(),
+ typeid(OptionCustom));
+
+ std::vector<uint8_t> rdnss_buf(begin, begin + 9);
+ rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end());
+
+ LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss_buf.begin(),
+ rdnss_buf.end(),
+ typeid(OptionCustom));
+
+ // Initialize test buffer for Vendor Class option.
+ const char status_code_data[] = {
+ 0x02, 0x65, 0x72, 0x72, 0x6f, 0x72
+ };
+ std::vector<uint8_t> status_code_buf(status_code_data,
+ status_code_data + sizeof(status_code_data));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_STATUS_CODE, status_code_buf.begin(),
+ status_code_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BASE_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_START_TIME_OF_STATE, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_QUERY_START_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_QUERY_END_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_STATE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DATA_SOURCE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_V4_PORTPARAMS, begin, begin + 4,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 22,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 46,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_V4_ACCESS_DOMAIN, fqdn1_buf.begin(),
+ fqdn1_buf.end(), typeid(OptionCustom));
+}
+
+// Test that definitions of standard options have been initialized
+// correctly.
+// @todo Only limited number of option definitions are now created
+// This test have to be extended once all option definitions are
+// created.
+TEST_F(LibDhcpTest, stdOptionDefs6) {
+ // Create a buffer that holds dummy option data.
+ // It will be used to create most of the options.
+ std::vector<uint8_t> buf(48, 1);
+ OptionBufferConstIter begin = buf.begin();
+ OptionBufferConstIter end = buf.end();
+
+ // Prepare buffer holding one FQDN.
+ const char data1[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data1.
+ std::vector<uint8_t> fqdn1_buf(data1, data1 + sizeof(data1));
+
+ // Prepare buffer holding an array of FQDNs.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(data, data + sizeof(data));
+
+ // Prepare buffer holding a vendor option
+ const char vopt_data[] = {
+ 1, 2, 3, 4, // enterprise=0x1020304
+ 0, 100, // type=100
+ 0, 6, // length=6
+ 102, 111, 111, 98, 97, 114 // data="foobar"
+ };
+ // Initialize a vector with the suboption data.
+ std::vector<uint8_t> vopt_buf(vopt_data, vopt_data + sizeof(vopt_data));
+
+ // The CLIENT_FQDN holds a uint8_t value and FQDN. We have
+ // to add the uint8_t value to it and then append the buffer
+ // holding some valid FQDN.
+ std::vector<uint8_t> client_fqdn_buf(1);
+ client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(),
+ fqdn_buf.end());
+
+ // Initialize test buffer for Vendor Class option.
+ const char vclass_data[] = {
+ 0x00, 0x01, 0x02, 0x03,
+ 0x00, 0x01, 0x02
+ };
+ std::vector<uint8_t> vclass_buf(vclass_data,
+ vclass_data + sizeof(vclass_data));
+
+ // Initialize test buffer for Bootfile Param option.
+ const char bparam_data[] = {
+ 0x00, 0x01, 0x02
+ };
+ std::vector<uint8_t> bparam_buf(bparam_data,
+ bparam_data + sizeof(bparam_data));
+
+ // The actual test starts here for all supported option codes.
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, begin, end,
+ typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, begin, end,
+ typeid(Option6IAAddr));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ORO, begin, end,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_AUTH, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_UNICAST, begin, begin + 16,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, begin, end,
+ typeid(Option6StatusCode));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, vclass_buf.begin(),
+ vclass_buf.end(),
+ typeid(OptionVendorClass));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, vopt_buf.begin(),
+ vopt_buf.end(),
+ typeid(OptionVendor));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf.begin(),
+ fqdn_buf.end(), typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, begin, end,
+ typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, begin, begin + 25,
+ typeid(Option6IAPrefix));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
+ begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf.begin(),
+ client_fqdn_buf.end(),
+ typeid(Option6ClientFqdn));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ERO, begin, end,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, begin, end,
+ typeid(OptionCustom), DHCP6_OPTION_SPACE);
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, begin, end,
+ typeid(OptionCustom), DHCP6_OPTION_SPACE);
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_V6_LOST,
+ fqdn1_buf.begin(), fqdn1_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CAPWAP_AC_V6, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RELAY_ID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_V6_ACCESS_DOMAIN,
+ fqdn1_buf.begin(), fqdn1_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_UA_CS_LIST,
+ fqdn_buf.begin(), fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_URL, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_PARAM, bparam_buf.begin(),
+ bparam_buf.end(),
+ typeid(OptionOpaqueDataTuples));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_ARCH_TYPE, begin, end,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NII, begin, begin + 3,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_AFTR_NAME, fqdn1_buf.begin(),
+ fqdn1_buf.end(), typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ERP_LOCAL_DOMAIN_NAME,
+ fqdn_buf.begin(), fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RSOO, begin, end,
+ typeid(OptionCustom),
+ "rsoo-opts");
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PD_EXCLUDE, begin, end,
+ typeid(Option6PDExclude));
+
+ std::vector<uint8_t> rdnss1_buf(begin, begin + 17);
+ rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end());
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss1_buf.begin(),
+ rdnss1_buf.end(),
+ typeid(OptionCustom));
+
+ std::vector<uint8_t> rdnss_buf(begin, begin + 17);
+ rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end());
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss_buf.begin(),
+ rdnss_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_LINKLAYER_ADDR, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LINK_ADDRESS, begin, begin + 16,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SOL_MAX_RT, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INF_MAX_RT, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_MSG, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_O_DHCPV6_SERVER, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_V6_CAPTIVE_PORTAL, begin, end,
+ typeid(OptionString));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RELAY_SOURCE_PORT, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ // V6_SZTP_REDIRECT is using an array of tuples so let's create example data
+ const char opaque_tuple_data[] = {
+ 0, 3, 1, 2, 3 // first 2 bytes are opaque data length, the rest is opaque data
+ };
+ std::vector<uint8_t> opaque_tuple_buf(opaque_tuple_data,
+ opaque_tuple_data + sizeof(opaque_tuple_data));
+
+ LibDhcpTest::testStdOptionDefs6(D60_V6_SZTP_REDIRECT,
+ opaque_tuple_buf.begin(),
+ opaque_tuple_buf.end(),
+ typeid(OptionOpaqueDataTuples));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IPV6_ADDRESS_ANDSF, begin, end,
+ typeid(Option6AddrLst));
+
+ // RFC7598 options
+ LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
+ typeid(OptionCustom), V4V6_RULE_OPTION_SPACE);
+ LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
+ typeid(OptionCustom), V4V6_RULE_OPTION_SPACE);
+ LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
+ typeid(OptionCustom));
+ LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
+ typeid(OptionCustom));
+ LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_DMR, begin, end,
+ typeid(OptionCustom));
+ LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_V4V6BIND, begin,
+ end, typeid(OptionCustom),
+ V4V6_BIND_OPTION_SPACE);
+ LibDhcpTest::testOptionDefs6(V4V6_RULE_OPTION_SPACE, D6O_S46_PORTPARAMS,
+ begin, end, typeid(OptionCustom), "");
+ LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPE, begin, end,
+ typeid(OptionCustom),
+ MAPE_V6_OPTION_SPACE);
+ LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPT, begin, end,
+ typeid(OptionCustom),
+ MAPT_V6_OPTION_SPACE);
+ LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_LW, begin, end,
+ typeid(OptionCustom),
+ LW_V6_OPTION_SPACE);
+
+}
+
+// This test checks if the DHCPv6 option definition can be searched by
+// an option name.
+TEST_F(LibDhcpTest, getOptionDefByName6) {
+ // Get all definitions.
+ const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP6_OPTION_SPACE);
+ // For each definition try to find it using option name.
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ OptionDefinitionPtr def_by_name =
+ LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, (*def)->getName());
+ ASSERT_TRUE(def_by_name);
+ ASSERT_TRUE(**def == *def_by_name);
+ }
+}
+
+// This test checks if the DHCPv4 option definition can be searched by
+// an option name.
+TEST_F(LibDhcpTest, getOptionDefByName4) {
+ // Get all definitions.
+ const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP4_OPTION_SPACE);
+ // For each definition try to find it using option name.
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ OptionDefinitionPtr def_by_name =
+ LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, (*def)->getName());
+ ASSERT_TRUE(def_by_name);
+ ASSERT_TRUE(**def == *def_by_name);
+ }
+}
+
+// This test checks if the definition of the DHCPv6 vendor option can
+// be searched by option name.
+TEST_F(LibDhcpTest, getVendorOptionDefByName6) {
+ const OptionDefContainerPtr& defs =
+ LibDHCP::getVendorOptionDefs(Option::V6, VENDOR_ID_CABLE_LABS);
+ ASSERT_TRUE(defs);
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ OptionDefinitionPtr def_by_name =
+ LibDHCP::getVendorOptionDef(Option::V6, VENDOR_ID_CABLE_LABS,
+ (*def)->getName());
+ ASSERT_TRUE(def_by_name);
+ ASSERT_TRUE(**def == *def_by_name);
+ }
+}
+
+// This test checks if the definition of the DHCPv4 vendor option can
+// be searched by option name.
+TEST_F(LibDhcpTest, getVendorOptionDefByName4) {
+ const OptionDefContainerPtr& defs =
+ LibDHCP::getVendorOptionDefs(Option::V4, VENDOR_ID_CABLE_LABS);
+ ASSERT_TRUE(defs);
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ OptionDefinitionPtr def_by_name =
+ LibDHCP::getVendorOptionDef(Option::V4, VENDOR_ID_CABLE_LABS,
+ (*def)->getName());
+ ASSERT_TRUE(def_by_name);
+ ASSERT_TRUE(**def == *def_by_name);
+ }
+}
+
+// This test checks handling of uncompressed FQDN list.
+TEST_F(LibDhcpTest, fqdnList) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ // Prepare buffer holding an array of FQDNs.
+ const uint8_t fqdn[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ /* This size is used later so protect ourselves against changes */
+ static_assert(sizeof(fqdn) == 40,
+ "incorrect uncompressed domain list size");
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn));
+
+ OptionPtr option;
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ fqdn_buf.begin(),
+ fqdn_buf.end()));
+ ASSERT_TRUE(option);
+ OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_TRUE(names);
+ EXPECT_EQ(sizeof(fqdn), names->len() - names->getHeaderLen());
+ ASSERT_EQ(3, names->getDataFieldsNum());
+ EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
+ EXPECT_EQ("example.com.", names->readFqdn(1));
+ EXPECT_EQ("com.", names->readFqdn(2));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
+ fqdn_buf.end(), typeid(OptionCustom));
+}
+
+// This test checks handling of compressed FQDN list.
+// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
+// compression algorithm).
+TEST_F(LibDhcpTest, fqdnListCompressed) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ const uint8_t compressed[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 192, 9, // pointer to example.com
+ 192, 17 // pointer to com
+ };
+ std::vector<uint8_t> compressed_buf(compressed,
+ compressed + sizeof(compressed));
+ OptionPtr option;
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ compressed_buf.begin(),
+ compressed_buf.end()));
+ ASSERT_TRUE(option);
+ OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_TRUE(names);
+ /* Use the uncompress length here (cf fqdnList) */
+ EXPECT_EQ(40, names->len() - names->getHeaderLen());
+ ASSERT_EQ(3, names->getDataFieldsNum());
+ EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
+ EXPECT_EQ("example.com.", names->readFqdn(1));
+ EXPECT_EQ("com.", names->readFqdn(2));
+}
+
+// Check that incorrect FQDN list compression is rejected.
+// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
+// compression algorithm).
+TEST_F(LibDhcpTest, fqdnListBad) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ const uint8_t bad[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 192, 80, // too big/forward pointer
+ 192, 11 // pointer to com
+ };
+ std::vector<uint8_t> bad_buf(bad, bad + sizeof(bad));
+
+ OptionPtr option;
+ EXPECT_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ bad_buf.begin(),
+ bad_buf.end()),
+ InvalidOptionValue);
+}
+
+// Check that empty (truncated) option is rejected.
+TEST_F(LibDhcpTest, fqdnListTrunc) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DOMAIN_SEARCH);
+ ASSERT_TRUE(def);
+
+ std::vector<uint8_t> empty;
+
+ OptionPtr option;
+ EXPECT_THROW(option = def->optionFactory(Option::V4,
+ DHO_DOMAIN_SEARCH,
+ empty.begin(),
+ empty.end()),
+ InvalidOptionValue);
+}
+
+// tests whether v6 vendor-class option can be parsed properly.
+TEST_F(LibDhcpTest, vendorClass6) {
+ isc::dhcp::OptionCollection options; // Will store parsed option here
+
+ // Exported from wireshark: vendor-class option with enterprise-id = 4491
+ // and a single data entry containing "eRouter1.0"
+ string vendor_class_hex = "001000100000118b000a65526f75746572312e30";
+ OptionBuffer bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(vendor_class_hex, bin);
+
+ ASSERT_NO_THROW ({
+ LibDHCP::unpackOptions6(bin, DHCP6_OPTION_SPACE, options);
+ });
+
+ EXPECT_EQ(options.size(), 1); // There should be 1 option.
+
+ // Option vendor-class should be there
+ ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end());
+
+ // It should be of type OptionVendorClass
+ boost::shared_ptr<OptionVendorClass> vclass =
+ boost::dynamic_pointer_cast<OptionVendorClass>(options.begin()->second);
+ ASSERT_TRUE(vclass);
+
+ // Let's investigate if the option content is correct
+
+ // 3 fields expected: vendor-id, data-len and data
+ EXPECT_EQ(VENDOR_ID_CABLE_LABS, vclass->getVendorId());
+ EXPECT_EQ(20, vclass->len());
+ ASSERT_EQ(1, vclass->getTuplesNum());
+ EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText());
+}
+
+// This test verifies that it is possible to add runtime option definitions,
+// retrieve them and remove them.
+TEST_F(LibDhcpTest, setRuntimeOptionDefs) {
+ // Create option definitions in 5 namespaces.
+ OptionDefSpaceContainer defs;
+ createRuntimeOptionDefs(5, 100, defs);
+
+ // Apply option definitions.
+ ASSERT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs));
+
+ // Retrieve all inserted option definitions.
+ testRuntimeOptionDefs(5, 100, true);
+
+ // Attempting to retrieve non existing definitions.
+ EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-non-existent", 1));
+ EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-0", 145));
+
+ // Remove all runtime option definitions.
+ ASSERT_NO_THROW(LibDHCP::clearRuntimeOptionDefs());
+
+ // All option definitions should be gone now.
+ testRuntimeOptionDefs(5, 100, false);
+}
+
+// This test verifies the processing of option 43
+TEST_F(LibDhcpTest, option43) {
+ // Check shouldDeferOptionUnpack()
+ EXPECT_TRUE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 43));
+ EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 44));
+ EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP6_OPTION_SPACE, 43));
+
+ // Check last resort
+ OptionDefinitionPtr def;
+ def = LibDHCP::getLastResortOptionDef(DHCP6_OPTION_SPACE, 43);
+ EXPECT_FALSE(def);
+ def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 44);
+ EXPECT_FALSE(def);
+ def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 43);
+ ASSERT_TRUE(def);
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(43, def->getCode());
+ EXPECT_EQ(VENDOR_ENCAPSULATED_OPTION_SPACE, def->getEncapsulatedSpace());
+ EXPECT_EQ("vendor-encapsulated-options", def->getName());
+ EXPECT_EQ(0, def->getRecordFields().size());
+ EXPECT_EQ(OptionDataType::OPT_EMPTY_TYPE, def->getType());
+
+ OptionDefinitionPtr def_by_name =
+ LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE,
+ "vendor-encapsulated-options");
+ EXPECT_TRUE(def_by_name);
+ EXPECT_EQ(def, def_by_name);
+}
+
+// RFC7598 defines several options for configuration of lw4over6 devices.
+// These options are have complex structure, so dedicated tests are needed
+// to test them reliably.
+TEST_F(LibDhcpTest, sw46options) {
+ // This constant defines the following structure:
+ // MAP-E container
+ // - BR address option
+ // - S46 rule option
+ // - portparameters
+ // - S46 rule option
+ std::vector<uint8_t> mape_bin = {
+ 0, 94, 0, 64, // MAP-E container with 3 suboptions
+
+ 0, 90, 0, 16, // BR address
+ 0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, // 2001:db8::abcd
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd,
+
+ 0, 89, 0, 16+8, // S46 rule
+ 128, // flags = 128 (F flag set)
+ 4, // ea-len
+ 30, // prefix4-len
+ 192, 0, 2, 192, // ipv4-prefix = 192.168.0.192
+ 64, // prefix6-len = /64
+ 0x20, 0x01, 0xd, 0xb8, 0, 1, 2, 3, // ipv6-prefix = 2001:db8::1:203::/64
+
+ 0, 93, 0, 4, // S46_PORTPARAMS option
+ 8, 6, 0xfc, 0x00, // offset is 8, psid-len 6, psid is fc00
+
+ 0, 89, 0, 12, // S46 rule
+ 0, // flags = 0 (F flag clear)
+ 6, // ea-len
+ 32, // prefix4-len
+ 192, 0, 2, 1, // ipv4-prefix = 192.168.0.1
+ 32, // prefix6-len = /32
+ 0x20, 0x01, 0xd, 0xb8 // ipv6-prefix = 2001:db8::/32
+ };
+
+ // List of parsed options will be stored here.
+ isc::dhcp::OptionCollection options;
+
+ OptionBuffer buf(mape_bin);
+
+ size_t parsed = 0;
+
+ EXPECT_NO_THROW (parsed = LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE, options));
+ EXPECT_EQ(mape_bin.size(), parsed);
+
+ // We expect to have exactly one option (with tons of suboptions, but we'll
+ // get to that in a minute)
+ EXPECT_EQ(1, options.size());
+ auto opt = options.find(D6O_S46_CONT_MAPE);
+ ASSERT_FALSE(opt == options.end());
+
+ // Ok, let's iterate over the options. Start with the top one.
+ using boost::shared_ptr;
+ shared_ptr<OptionCustom> mape = dynamic_pointer_cast<OptionCustom>(opt->second);
+ ASSERT_TRUE(mape);
+ EXPECT_EQ(D6O_S46_CONT_MAPE, mape->getType());
+ EXPECT_EQ(68, mape->len());
+ EXPECT_EQ(64, mape->getData().size());
+
+ // Let's check if there's a border router option.
+ ASSERT_TRUE(mape->getOption(D6O_S46_BR));
+
+ // Make sure the option is of proper type, not just plain Option
+ shared_ptr<OptionCustom> br =
+ dynamic_pointer_cast<OptionCustom>(mape->getOption(D6O_S46_BR));
+ ASSERT_TRUE(br);
+ EXPECT_EQ("type=00090, len=00016: 2001:db8::abcd (ipv6-address)", br->toText());
+
+ // Now let's check the suboptions. There should be 3 (BR, 2x rule)
+ const OptionCollection& subopts = mape->getOptions();
+ ASSERT_EQ(3, subopts.size());
+ EXPECT_EQ(1, subopts.count(D6O_S46_BR));
+ EXPECT_EQ(2, subopts.count(D6O_S46_RULE));
+
+ // Let's check the rules. There should be two of them.
+ auto range = subopts.equal_range(D6O_S46_RULE);
+ ASSERT_EQ(2, std::distance(range.first, range.second));
+ OptionPtr opt1 = range.first->second;
+ OptionPtr opt2 = (++range.first)->second;
+ shared_ptr<OptionCustom> rule1 = dynamic_pointer_cast<OptionCustom>(opt1);
+ shared_ptr<OptionCustom> rule2 = dynamic_pointer_cast<OptionCustom>(opt2);
+ ASSERT_TRUE(rule1);
+ ASSERT_TRUE(rule2);
+
+ EXPECT_EQ("type=00089, len=00024: 128 (uint8) 4 (uint8) 30 (uint8) "
+ "192.0.2.192 (ipv4-address) (ipv6-prefix),\noptions:\n"
+ " type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", rule1->toText());
+
+ EXPECT_EQ("type=00089, len=00012: 0 (uint8) 6 (uint8) 32 (uint8) "
+ "192.0.2.1 (ipv4-address) (ipv6-prefix)", rule2->toText());
+
+ // Finally, check that the subsuboption in the first rule is ok
+ OptionPtr subsubopt = opt1->getOption(D6O_S46_PORTPARAMS);
+ shared_ptr<OptionCustom> portparam = dynamic_pointer_cast<OptionCustom>(subsubopt);
+ ASSERT_TRUE(portparam);
+
+ EXPECT_EQ("type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", portparam->toText());
+}
+
+} // namespace
diff --git a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
new file mode 100644
index 0000000..974a0bf
--- /dev/null
+++ b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
@@ -0,0 +1,523 @@
+// Copyright (C) 2014-2021 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 <dhcp/opaque_data_tuple.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+struct OpaqueDataTupleLenientParsing : ::testing::Test {
+ void SetUp() final override {
+ // Retain the current setting for future restoration.
+ previous_ = Option::lenient_parsing_;
+
+ // Enable lenient parsing.
+ Option::lenient_parsing_ = true;
+ }
+
+ void TearDown() final override {
+ // Restore.
+ Option::lenient_parsing_ = previous_;
+ }
+
+ bool previous_;
+};
+
+// This test checks that when the default constructor is called, the data buffer
+// is empty.
+TEST(OpaqueDataTuple, constructor) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // There should be no data in the tuple.
+ EXPECT_EQ(0, tuple.getLength());
+ EXPECT_TRUE(tuple.getData().empty());
+ EXPECT_TRUE(tuple.getText().empty());
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse1Byte) {
+ OpaqueDataTuple::Buffer wire_data = {
+ 0x0B, // Length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE, wire_data.begin(),
+ wire_data.end());
+
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse2Bytes) {
+ OpaqueDataTuple::Buffer wire_data = {
+ 0x00, 0x0B, // Length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES, wire_data.begin(),
+ wire_data.end());
+
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+
+// This test checks that it is possible to set the tuple data using raw buffer.
+TEST(OpaqueDataTuple, assignData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple buffer should be empty.
+ OpaqueDataTuple::Buffer buf = tuple.getData();
+ ASSERT_TRUE(buf.empty());
+ // Prepare some input data and assign to the tuple.
+ OpaqueDataTuple::Buffer data1 = {
+ 0xCA, 0xFE, 0xBE, 0xEF
+ };
+ tuple.assign(data1.begin(), data1.size());
+ // Tuple should now hold the data we assigned.
+ ASSERT_EQ(data1.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data1);
+
+ // Prepare the other set of data and assign to the tuple.
+ OpaqueDataTuple::Buffer data2 = {
+ 1, 2, 3, 4, 5, 6
+ };
+ tuple.assign(data2.begin(), data2.size());
+ // The new data should have replaced the old data.
+ ASSERT_EQ(data2.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data2);
+}
+
+// This test checks that it is possible to append the data to the tuple using
+// raw buffer.
+TEST(OpaqueDataTuple, appendData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple buffer should be empty.
+ OpaqueDataTuple::Buffer buf = tuple.getData();
+ ASSERT_TRUE(buf.empty());
+ // Prepare some input data and append to the empty tuple.
+ OpaqueDataTuple::Buffer data1 = {
+ 0xCA, 0xFE, 0xBE, 0xEF
+ };
+ tuple.append(data1.begin(), data1.size());
+ // The tuple should now hold only the data we appended.
+ ASSERT_EQ(data1.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data1);
+ // Prepare the new set of data and append.
+ OpaqueDataTuple::Buffer data2 = {
+ 1, 2, 3, 4, 5, 6
+ };
+ tuple.append(data2.begin(), data2.size());
+ // We expect that the tuple now has both sets of data we appended. In order
+ // to verify that, we have to concatenate the input data1 and data2.
+ OpaqueDataTuple::Buffer data12(data1.begin(), data1.end());
+ data12.insert(data12.end(), data2.begin(), data2.end());
+ // The size of the tuple should be a sum of data1 and data2 lengths.
+ ASSERT_EQ(data1.size() + data2.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data12);
+}
+
+// This test checks that it is possible to assign the string to the tuple.
+TEST(OpaqueDataTuple, assignString) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially, the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // Assign some string data.
+ tuple.assign("Some string");
+ // Verify that the data has been assigned.
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Some string", tuple.getText());
+ // Assign some other string.
+ tuple.assign("Different string");
+ // The new string should have replaced the old string.
+ EXPECT_EQ(16, tuple.getLength());
+ EXPECT_EQ("Different string", tuple.getText());
+}
+
+// This test checks that it is possible to append the string to the tuple.
+TEST(OpaqueDataTuple, appendString) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // Append the string to it.
+ tuple.append("First part");
+ ASSERT_EQ(10, tuple.getLength());
+ ASSERT_EQ("First part", tuple.getText());
+ // Now append the other string.
+ tuple.append(" and second part");
+ EXPECT_EQ(26, tuple.getLength());
+ // The resulting data in the tuple should be a concatenation of both
+ // strings.
+ EXPECT_EQ("First part and second part", tuple.getText());
+}
+
+// This test verifies that equals function correctly checks that the tuple
+// holds a given string but it doesn't hold the other. This test also
+// checks the assignment operator for the tuple.
+TEST(OpaqueDataTuple, equals) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Tuple is supposed to be empty so it is not equal xyz.
+ EXPECT_FALSE(tuple.equals("xyz"));
+ // Assign xyz.
+ EXPECT_NO_THROW(tuple = "xyz");
+ // The tuple should be equal xyz, but not abc.
+ EXPECT_FALSE(tuple.equals("abc"));
+ EXPECT_TRUE(tuple.equals("xyz"));
+ // Assign abc to the tuple.
+ EXPECT_NO_THROW(tuple = "abc");
+ // It should be now opposite.
+ EXPECT_TRUE(tuple.equals("abc"));
+ EXPECT_FALSE(tuple.equals("xyz"));
+}
+
+// This test checks that the conversion of the data in the tuple to the string
+// is performed correctly.
+TEST(OpaqueDataTuple, getText) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple should be empty.
+ ASSERT_TRUE(tuple.getText().empty());
+ // ASCII representation of 'Hello world'.
+ const char as_ascii[] = {
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+ // Assign it to the tuple.
+ tuple.assign(as_ascii, sizeof(as_ascii));
+ // Conversion to string should give as the original text.
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies the behavior of (in)equality and assignment operators.
+TEST(OpaqueDataTuple, operators) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Tuple should be empty initially.
+ ASSERT_EQ(0, tuple.getLength());
+ // Check assignment.
+ EXPECT_NO_THROW(tuple = "Hello World");
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_TRUE(tuple == "Hello World");
+ EXPECT_TRUE(tuple != "Something else");
+ // Assign something else to make sure it affects the tuple.
+ EXPECT_NO_THROW(tuple = "Something else");
+ EXPECT_EQ(14, tuple.getLength());
+ EXPECT_TRUE(tuple == "Something else");
+ EXPECT_TRUE(tuple != "Hello World");
+}
+
+// This test verifies that the tuple is inserted in the textual format to the
+// output stream.
+TEST(OpaqueDataTuple, operatorOutputStream) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // The tuple should be empty initially.
+ ASSERT_EQ(0, tuple.getLength());
+ // The tuple is empty, so assigning its content to the output stream should
+ // be no-op and result in the same text in the stream.
+ std::ostringstream s;
+ s << "Some text";
+ EXPECT_NO_THROW(s << tuple);
+ EXPECT_EQ("Some text", s.str());
+ // Now, let's assign some text to the tuple and call operator again.
+ // The new text should be added to the stream.
+ EXPECT_NO_THROW(tuple = " and some other text");
+ EXPECT_NO_THROW(s << tuple);
+ EXPECT_EQ(s.str(), "Some text and some other text");
+}
+
+// This test verifies that the value of the tuple can be initialized from the
+// input stream.
+TEST(OpaqueDataTuple, operatorInputStream) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // The tuple should be empty initially.
+ ASSERT_EQ(0, tuple.getLength());
+ // The input stream has some text. This text should be appended to the
+ // tuple.
+ std::istringstream s;
+ s.str("Some text");
+ EXPECT_NO_THROW(s >> tuple);
+ EXPECT_EQ("Some text", tuple.getText());
+ // Now, let's assign some other text to the stream. This new text should be
+ // assigned to the tuple.
+ s.str("And some other");
+ EXPECT_NO_THROW(s >> tuple);
+ EXPECT_EQ("And some other", tuple.getText());
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 1 byte.
+TEST(OpaqueDataTuple, pack1Byte) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ // Initially, the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // It turns out that Option 124 can be sent with 0 length Opaque Data
+ // See #2021 for more details
+ OutputBuffer out_buf(10);
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ ASSERT_EQ(1, out_buf.getLength());
+ const uint8_t* zero_len = static_cast<const uint8_t*>(out_buf.getData());
+ ASSERT_EQ(0, *zero_len);
+ // Reset the output buffer for another test.
+ out_buf.clear();
+ // Set the data for tuple.
+ std::vector<uint8_t> data;
+ for (uint8_t i = 0; i < 100; ++i) {
+ data.push_back(i);
+ }
+ tuple.assign(data.begin(), data.size());
+ // Packing the data should succeed.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ // The rendered buffer should be 101 bytes long - 1 byte for length,
+ // 100 bytes for the actual data.
+ ASSERT_EQ(101, out_buf.getLength());
+ // Get the rendered data into the vector for convenience.
+ std::vector<uint8_t>
+ render_data(static_cast<const uint8_t*>(out_buf.getData()),
+ static_cast<const uint8_t*>(out_buf.getData()) + 101);
+ // The first byte is a length byte. It should hold the length of 100.
+ EXPECT_EQ(100, render_data[0]);
+ // Verify that the rendered data is correct.
+ EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+ data.begin()));
+ // Reset the output buffer for another test.
+ out_buf.clear();
+ // Fill in the tuple buffer so as it reaches maximum allowed length. The
+ // maximum length is 255 when the size of the length field is one byte.
+ for (uint8_t i = 100; i < 255; ++i) {
+ data.push_back(i);
+ }
+ ASSERT_EQ(255, data.size());
+ tuple.assign(data.begin(), data.size());
+ // The pack() should be successful again.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ // The rendered buffer should be 256 bytes long. The first byte holds the
+ // opaque data length, the remaining bytes hold the actual data.
+ ASSERT_EQ(256, out_buf.getLength());
+ // Check that the data is correct.
+ render_data.assign(static_cast<const uint8_t*>(out_buf.getData()),
+ static_cast<const uint8_t*>(out_buf.getData()) + 256);
+ EXPECT_EQ(255, render_data[0]);
+ EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+ data.begin()));
+ // Clear output buffer for another test.
+ out_buf.clear();
+ // Add one more value to the tuple. Now, the resulting buffer should exceed
+ // the maximum length. An attempt to pack() should fail.
+ data.push_back(255);
+ tuple.assign(data.begin(), data.size());
+ EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 2 bytes.
+TEST(OpaqueDataTuple, pack2Bytes) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially, the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // It turns out that Option 124 can be sent with 0 length Opaque Data
+ // See #2021 for more details
+ OutputBuffer out_buf(10);
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ ASSERT_EQ(2, out_buf.getLength());
+ const uint16_t* zero_len = static_cast<const uint16_t*>(out_buf.getData());
+ ASSERT_EQ(0, *zero_len);
+ // Reset the output buffer for another test.
+ out_buf.clear();
+ // Set the data for tuple.
+ std::vector<uint8_t> data;
+ for (unsigned i = 0; i < 512; ++i) {
+ data.push_back(i & 0xff);
+ }
+ tuple.assign(data.begin(), data.size());
+ // Packing the data should succeed.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ // The rendered buffer should be 514 bytes long - 2 bytes for length,
+ // 512 bytes for the actual data.
+ ASSERT_EQ(514, out_buf.getLength());
+ // Get the rendered data into the vector for convenience.
+ std::vector<uint8_t>
+ render_data(static_cast<const uint8_t*>(out_buf.getData()),
+ static_cast<const uint8_t*>(out_buf.getData()) + 514);
+ // The first two bytes hold the length of 512.
+ uint16_t len = (render_data[0] << 8) + render_data[1];
+ EXPECT_EQ(512, len);
+ // Verify that the rendered data is correct.
+ EXPECT_TRUE(std::equal(render_data.begin() + 2, render_data.end(),
+ data.begin()));
+
+ // Without clearing the output buffer, try to do it again. The pack should
+ // append the data to the current buffer.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ EXPECT_EQ(1028, out_buf.getLength());
+
+ // Check that we can render the buffer of the maximal allowed size.
+ data.assign(65535, 1);
+ ASSERT_NO_THROW(tuple.assign(data.begin(), data.size()));
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+
+ out_buf.clear();
+
+ // Append one additional byte. The total length of the tuple now exceeds
+ // the maximal value. An attempt to render it should throw an exception.
+ data.assign(1, 1);
+ ASSERT_NO_THROW(tuple.append(data.begin(), data.size()));
+ EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack1Byte) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {
+ 0x0B, // Length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack1ByteZeroLength) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_NO_THROW(tuple = "Hello world");
+ ASSERT_NE(tuple.getLength(), 0);
+
+ OpaqueDataTuple::Buffer wire_data = {
+ 0
+ };
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+
+ EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verifies that the tuple having a length of 0, followed by no
+// data, is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack1ByteZeroLengthNoData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {0};
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+}
+
+// This test verifies that the tuple having a length of 0, followed by no
+// data, is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack2ByteZeroLengthNoData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ OpaqueDataTuple::Buffer wire_data = {0, 0};
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+}
+
+// This test verifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {};
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// This test verifies that exception is thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack1ByteTruncatedBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {
+ 10, 2, 3
+ };
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack2Byte) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ OpaqueDataTuple::Buffer wire_data;
+ // Set tuple length to 400 (0x190).
+ wire_data.push_back(1);
+ wire_data.push_back(0x90);
+ // Fill in the buffer with some data.
+ for (int i = 0; i < 400; ++i) {
+ wire_data.push_back(i);
+ }
+ // The unpack should succeed.
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ // The decoded length should be 400.
+ ASSERT_EQ(400, tuple.getLength());
+ // And the data should match.
+ EXPECT_TRUE(std::equal(wire_data.begin() + 2, wire_data.end(),
+ tuple.getData().begin()));
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack2ByteZeroLength) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Set some data for the tuple.
+ EXPECT_NO_THROW(tuple = "Hello world");
+ ASSERT_NE(tuple.getLength(), 0);
+ // The buffer holds just a length field with the value of 0.
+ OpaqueDataTuple::Buffer wire_data = {
+ 0, 0
+ };
+ // The empty tuple should be successfully decoded.
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ // The data should be replaced with an empty buffer.
+ EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack2ByteEmptyBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ OpaqueDataTuple::Buffer wire_data = {};
+ // Pass empty buffer (first iterator equal to second iterator).
+ // This should not be accepted.
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// This test verifies that exception is thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack2ByteTruncatedBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Specify the data with the length of 10, but limit the buffer size to
+ // 2 bytes.
+ OpaqueDataTuple::Buffer wire_data = {
+ 0, 10, 2, 3
+ };
+ // This should fail because the buffer is truncated.
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// Test that an exception is not thrown when parsing in lenient mode.
+TEST_F(OpaqueDataTupleLenientParsing, unpack) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Specify the data with the length of 10, but limit the buffer size to 2.
+ OpaqueDataTuple::Buffer wire_data = {
+ 0, 10, 2, 3
+ };
+ EXPECT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ EXPECT_EQ(tuple.getData(), OpaqueDataTuple::Buffer({2, 3}));
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
new file mode 100644
index 0000000..b66247f
--- /dev/null
+++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
@@ -0,0 +1,275 @@
+// Copyright (C) 2011-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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/option.h>
+#include <dhcp/option4_addrlst.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+using boost::scoped_ptr;
+
+namespace {
+
+// a sample data (list of 4 addresses)
+const uint8_t sampledata[] = {
+ 192, 0, 2, 3, // 192.0.2.3
+ 255, 255, 255, 0, // 255.255.255.0 - popular netmask
+ 0, 0, 0 , 0, // used for default routes or (any address)
+ 127, 0, 0, 1 // loopback
+};
+
+// expected on-wire format for an option with 1 address
+const uint8_t expected1[] = { // 1 address
+ DHO_DOMAIN_NAME_SERVERS, 4, // type, length
+ 192, 0, 2, 3, // 192.0.2.3
+};
+
+// expected on-wire format for an option with 4 addresses
+const uint8_t expected4[] = { // 4 addresses
+ 254, 16, // type = 254, len = 16
+ 192, 0, 2, 3, // 192.0.2.3
+ 255, 255, 255, 0, // 255.255.255.0 - popular netmask
+ 0, 0, 0 ,0, // used for default routes or (any address)
+ 127, 0, 0, 1 // loopback
+};
+
+class Option4AddrLstTest : public ::testing::Test {
+protected:
+
+ Option4AddrLstTest():
+ vec_(vector<uint8_t>(300, 0)) // 300 bytes long filled with 0s
+ {
+ sampleAddrs_.push_back(IOAddress("192.0.2.3"));
+ sampleAddrs_.push_back(IOAddress("255.255.255.0"));
+ sampleAddrs_.push_back(IOAddress("0.0.0.0"));
+ sampleAddrs_.push_back(IOAddress("127.0.0.1"));
+ }
+
+ vector<uint8_t> vec_;
+ Option4AddrLst::AddressContainer sampleAddrs_;
+
+};
+
+TEST_F(Option4AddrLstTest, parse1) {
+
+ memcpy(&vec_[0], sampledata, sizeof(sampledata));
+
+ // Just one address
+ scoped_ptr<Option4AddrLst> opt1;
+ EXPECT_NO_THROW(
+ opt1.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ vec_.begin(),
+ vec_.begin()+4));
+ // Use just first address (4 bytes), not the whole
+ // sampledata
+ );
+
+ EXPECT_EQ(Option::V4, opt1->getUniverse());
+
+ EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt1->getType());
+ EXPECT_EQ(6, opt1->len()); // 2 (header) + 4 (1x IPv4 addr)
+
+ Option4AddrLst::AddressContainer addrs = opt1->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+
+ EXPECT_NO_THROW(opt1.reset());
+}
+
+TEST_F(Option4AddrLstTest, parse4) {
+
+ vector<uint8_t> buffer(300, 0); // 300 bytes long filled with 0s
+
+ memcpy(&buffer[0], sampledata, sizeof(sampledata));
+
+ // 4 addresses
+ scoped_ptr<Option4AddrLst> opt4;
+ EXPECT_NO_THROW(
+ opt4.reset(new Option4AddrLst(254,
+ buffer.begin(),
+ buffer.begin()+sizeof(sampledata)));
+ );
+
+ EXPECT_EQ(Option::V4, opt4->getUniverse());
+
+ EXPECT_EQ(254, opt4->getType());
+ EXPECT_EQ(18, opt4->len()); // 2 (header) + 16 (4x IPv4 addrs)
+
+ Option4AddrLst::AddressContainer addrs = opt4->getAddresses();
+ ASSERT_EQ(4, addrs.size());
+
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+ EXPECT_EQ("255.255.255.0", addrs[1].toText());
+ EXPECT_EQ("0.0.0.0", addrs[2].toText());
+ EXPECT_EQ("127.0.0.1", addrs[3].toText());
+
+ EXPECT_NO_THROW(opt4.reset());
+}
+
+TEST_F(Option4AddrLstTest, assembly1) {
+
+ scoped_ptr<Option4AddrLst> opt;
+ EXPECT_NO_THROW(
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ IOAddress("192.0.2.3")));
+ );
+ EXPECT_EQ(Option::V4, opt->getUniverse());
+ EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType());
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(1, addrs.size() );
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(
+ opt->pack(buf);
+ );
+
+ ASSERT_EQ(6, opt->len());
+ ASSERT_EQ(6, buf.getLength());
+
+ EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6));
+
+ EXPECT_NO_THROW(opt.reset());
+
+ // This is old-fashioned option. We don't serve IPv6 types here!
+ EXPECT_THROW(
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ IOAddress("2001:db8::1"))),
+ BadValue
+ );
+}
+
+TEST_F(Option4AddrLstTest, assembly4) {
+
+ scoped_ptr<Option4AddrLst> opt;
+ EXPECT_NO_THROW(
+ opt.reset(new Option4AddrLst(254, sampleAddrs_));
+ );
+ EXPECT_EQ(Option::V4, opt->getUniverse());
+ EXPECT_EQ(254, opt->getType());
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(4, addrs.size() );
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+ EXPECT_EQ("255.255.255.0", addrs[1].toText());
+ EXPECT_EQ("0.0.0.0", addrs[2].toText());
+ EXPECT_EQ("127.0.0.1", addrs[3].toText());
+
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(
+ opt->pack(buf);
+ );
+
+ ASSERT_EQ(18, opt->len()); // 2(header) + 4xsizeof(IPv4addr)
+ ASSERT_EQ(18, buf.getLength());
+
+ ASSERT_EQ(0, memcmp(expected4, buf.getData(), 18));
+
+ EXPECT_NO_THROW(opt.reset());
+
+ // This is old-fashioned option. We don't serve IPv6 types here!
+ sampleAddrs_.push_back(IOAddress("2001:db8::1"));
+ EXPECT_THROW(
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_)),
+ BadValue
+ );
+}
+
+// This test verifies that an option (e.g., mobile-ip-home-agent) can be empty.
+TEST_F(Option4AddrLstTest, empty) {
+
+ scoped_ptr<Option4AddrLst> opt;
+ // the mobile-ip-home-agent option can be empty
+ EXPECT_NO_THROW(opt.reset(new Option4AddrLst(DHO_HOME_AGENT_ADDRS)));
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(0, addrs.size());
+ EXPECT_NO_THROW(opt.reset());
+}
+
+TEST_F(Option4AddrLstTest, setAddress) {
+
+ scoped_ptr<Option4AddrLst> opt;
+ EXPECT_NO_THROW(
+ opt.reset(new Option4AddrLst(123, IOAddress("1.2.3.4")));
+ );
+ opt->setAddress(IOAddress("192.0.255.255"));
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(1, addrs.size() );
+ EXPECT_EQ("192.0.255.255", addrs[0].toText());
+
+ // We should accept IPv4-only addresses.
+ EXPECT_THROW(
+ opt->setAddress(IOAddress("2001:db8::1")),
+ BadValue
+ );
+
+ EXPECT_NO_THROW(opt.reset());
+}
+
+TEST_F(Option4AddrLstTest, setAddresses) {
+
+ scoped_ptr<Option4AddrLst> opt;
+
+ EXPECT_NO_THROW(
+ opt.reset(new Option4AddrLst(123)); // Empty list
+ );
+
+ opt->setAddresses(sampleAddrs_);
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(4, addrs.size() );
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+ EXPECT_EQ("255.255.255.0", addrs[1].toText());
+ EXPECT_EQ("0.0.0.0", addrs[2].toText());
+ EXPECT_EQ("127.0.0.1", addrs[3].toText());
+
+ // We should accept IPv4-only addresses.
+ sampleAddrs_.push_back(IOAddress("2001:db8::1"));
+ EXPECT_THROW(
+ opt->setAddresses(sampleAddrs_),
+ BadValue
+ );
+
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// This test checks that the option holding IPv4 address list can
+// be converted to textual format.
+TEST_F(Option4AddrLstTest, toText) {
+ Option4AddrLst opt(111);
+ // Generate a few IPv4 addresses.
+ Option4AddrLst::AddressContainer addresses;
+ for (int i = 2; i < 6; ++i) {
+ std::stringstream s;
+ s << "192.0.2." << i;
+ addresses.push_back(IOAddress(s.str()));
+ }
+ opt.setAddresses(addresses);
+
+ EXPECT_EQ("type=111, len=016: 192.0.2.2 192.0.2.3 192.0.2.4 192.0.2.5",
+ opt.toText());
+}
+
+} // namespace
diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
new file mode 100644
index 0000000..27987fc
--- /dev/null
+++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
@@ -0,0 +1,1029 @@
+// Copyright (C) 2013-2018 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 <dhcp/option4_client_fqdn.h>
+#include <dns/name.h>
+#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option4ClientFqdnTest, constructEmptyName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Constructor should not accept empty fully qualified domain name.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "",
+ Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+ // This check is similar to previous one, but using domain-name comprising
+ // a single space character. This should be treated as empty domain-name.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ " ",
+ Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+
+ // Try different constructor.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER()))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option4ClientFqdnTest, copyConstruct) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option4ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option4ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option_copy->getDomainNameType());
+
+ // Do another test with different parameters to verify that parameters
+ // change when copied object is changed.
+
+ // Create an option with different parameters.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "example",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Call copy-constructor to copy the option.
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option4ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ option.reset();
+
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("example", option_copy->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with the domain-name
+// encoded in the canonical format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWire) {
+ // The E flag sets the domain-name format to canonical.
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 77, 121, 104, 111, 115, 116, // Myhost.
+ 7, 69, 120, 97, 109, 112, 108, 101, // Example.
+ 3, 67, 111, 109, 0 // Com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with the domain-name
+// encoded in the ASCII format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireASCII) {
+ // The E flag is set to zero which indicates that the domain name
+ // is encoded in the ASCII format. The "dot" character at the end
+ // indicates that the domain-name is fully qualified.
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 77, 121, 104, 111, 115, 116, 46, // Myhost.
+ 69, 120, 97, 109, 112, 108, 101, 46, // Example.
+ 67, 111, 109, 46 // Com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option4ClientFqdnTest, constructFromWireTruncated) {
+ // Empty buffer is invalid. It should be at least one octet long.
+ OptionBuffer in_buf;
+ for (uint8_t i = 0; i < 3; ++i) {
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ OutOfRange) << "Test of the truncated buffer failed for"
+ << " buffer length " << static_cast<int>(i);
+ in_buf.push_back(0);
+ }
+
+ // Buffer is now 3 bytes long, so it should not fail now.
+ EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+}
+
+// This test verifies that exception is thrown when invalid domain-name
+// in canonical format is carried in the option.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidName) {
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 5, 99, 111, 109, 0 // com. (invalid label length 5)
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that exception is thrown when invalid domain-name
+// in ASCII format is carried in the option.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidASCIIName) {
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 109, 121, 104, 111, 115, 116, 46, 46, // myhost.. (double dot!)
+ 101, 120, 97, 109, 112, 108, 101, 46, // example.
+ 99, 111, 109, 46 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name encoded in canonical format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartial) {
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_N | Option4ClientFqdn:: FLAG_E, // flags
+ 255, // RCODE1
+ 255, // RCODE2
+ 6, 77, 121, 104, 111, 115, 116 // Myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name encoded in ASCII format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartialASCII) {
+ // There is no "dot" character at the end, so the domain-name is partial.
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_N, // flags
+ 255, // RCODE1
+ 255, // RCODE2
+ 109, 121, 104, 111, 115, 116, 46, // myhost.
+ 101, 120, 97, 109, 112, 108, 101 // example
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireEmpty) {
+ // Initialize the 3-byte long buffer. All bytes initialized to 0:
+ // Flags, RCODE1 and RCODE2.
+ OptionBuffer in_buf(3, 0);
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ // domain-name field should be empty because on-wire data comprised
+ // flags field only.
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option4ClientFqdnTest, assignment) {
+ // Usually the smart pointer is used to declare options and call
+ // constructor within assert. Thanks to this approach, the option instance
+ // is in the function scope and only initialization is done within assert.
+ // In this particular test we can't use smart pointers because we are
+ // testing assignment operator like this:
+ //
+ // option2 = option;
+ //
+ // The two asserts below do not create the instances that we will used to
+ // test assignment. They just attempt to create instances of the options
+ // with the same parameters as those that will be created for the actual
+ // assignment test. If these asserts do not fail, we can create options
+ // for the assignment test, do not surround them with asserts and be sure
+ // they will not throw.
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_N |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option4ClientFqdn option(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.Example.com",
+ Option4ClientFqdn::FULL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_E));
+ ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost.example.com.", option.getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option.getDomainNameType());
+
+ // Create a second option.
+ Option4ClientFqdn option2(Option4ClientFqdn::FLAG_N |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "Myhost",
+ Option4ClientFqdn::PARTIAL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+ ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+
+ // Make self-assignment.
+ ASSERT_NO_THROW(option2 = option2);
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option4ClientFqdnTest, constructInvalidFlags) {
+ // First, check that constructor does not throw an exception when
+ // valid flags values are provided. That way we eliminate the issue
+ // that constructor always throws exception.
+ uint8_t flags = 0;
+ ASSERT_NO_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ // Invalid flags: The maximal value is 0xF when all flag bits are set
+ // (00001111b). The flag value of 0x18 sets the bit from the Must Be
+ // Zero (MBZ) bitset (00011000b).
+ flags = 0x18;
+ EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"),
+ InvalidOption4FqdnFlags);
+
+ // According to RFC 4702, section 2.1. if the N bit is set the S bit MUST
+ // be zero. If both are set, constructor is expected to throw.
+ flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"),
+ InvalidOption4FqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidFlags) {
+ // Create a buffer which holds flags field only. Set valid flag field at
+ // at first to make sure that constructor doesn't always throw an exception.
+ OptionBuffer in_buf(3, 0);
+ in_buf[0] = Option4ClientFqdn::FLAG_S;
+ ASSERT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+
+ // Replace the flags with invalid value and verify that constructor throws
+ // appropriate exception.
+ in_buf[0] = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption4FqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option4ClientFqdnTest, constructInvalidName) {
+ // First, check that constructor does not throw when valid domain name
+ // is specified. That way we eliminate the possibility that constructor
+ // always throws exception.
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ // Specify invalid domain name and expect that exception is thrown.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "my...host.example.com"),
+ InvalidOption4FqdnDomainName);
+
+ // Do the same test for the domain-name in ASCII format.
+ ASSERT_NO_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "my...host.example.com"),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than N, E, O, S was specified.
+TEST(Option4ClientFqdnTest, getFlag) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The value of 0x3 (binary 0011) is invalid because it specifies two bits
+ // in the flags field which value is to be checked.
+ EXPECT_THROW(option->getFlag(0x3), InvalidOption4FqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(Option4ClientFqdnTest, setFlag) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // All flags should be set to 0 initially.
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+ // Set E = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_E, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+ // Set N = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set O = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // Set S = 1, this should throw exception because S and N must not
+ // be set in the same time.
+ ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true),
+ InvalidOption4FqdnFlags);
+
+ // Set E = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set N = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set S = 1, this should not result in exception because N has been
+ // cleared.
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+
+ // Set N = 1, this should result in exception because S = 1
+ ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true),
+ InvalidOption4FqdnFlags);
+
+ // Set O = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // Try out of bounds settings.
+ uint8_t flags = 0;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags);
+
+ flags = 0x18;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option4ClientFqdnTest, resetFlags) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Check that flags we set in the constructor are set.
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+ option->resetFlags();
+
+ // After reset, all flags should be 0.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option4ClientFqdnTest, setDomainName) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.Example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Partial domain-name.
+ ASSERT_NO_THROW(option->setDomainName("myHost",
+ Option4ClientFqdn::PARTIAL));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-name.
+ ASSERT_NO_THROW(option->setDomainName("example.Com",
+ Option4ClientFqdn::FULL));
+ EXPECT_EQ("example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Empty domain name (partial). This should be successful.
+ ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-names must not be empty.
+ EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+ EXPECT_THROW(option->setDomainName(" ", Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option4ClientFqdnTest, resetDomainName) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "Myhost.Example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Set the domain-name to empty one.
+ ASSERT_NO_THROW(option->resetDomainName());
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies on-wire format of the option is correctly created.
+TEST(Option4ClientFqdnTest, pack) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "Myhost.Example.Com"))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 23, // header
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies on-wire format of the Client FQDN option
+// output in deprecated ASCII format.
+TEST(Option4ClientFqdnTest, packASCII) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "Myhost.Example.Com"))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 22, // header
+ Option4ClientFqdn::FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 109, 121, 104, 111, 115, 116, 46, // myhost.
+ 101, 120, 97, 109, 112, 108, 101, 46, // example.
+ 99, 111, 109, 46 // com.
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+
+}
+
+// This test verifies on-wire format of the option with partial domain name
+// is correctly created.
+TEST(Option4ClientFqdnTest, packPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 10, // header
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that it is possible to encode option with empty
+// domain-name in the on-wire format.
+TEST(Option4ClientFqdnTest, packEmpty) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT()))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 3, // header
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0 // RCODE2
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option4ClientFqdnTest, unpack) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option4ClientFqdnTest, unpackPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option4ClientFqdnTest, unpackTruncated) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT()))
+ );
+ ASSERT_TRUE(option);
+
+ // Empty buffer is invalid. It should be at least 1 octet long.
+ OptionBuffer in_buf;
+ EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option4ClientFqdnTest, toText) {
+ // Create option instance. Check that constructor doesn't throw.
+ uint8_t flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The base indentation of the option will be set to 2. It should appear
+ // as follows.
+ std::string ref_string =
+ " type=81 (CLIENT_FQDN), flags: (N=1, E=1, O=1, S=0), "
+ "domain-name='myhost.example.com.' (full)";
+ const int indent = 2;
+ EXPECT_EQ(ref_string, option->toText(indent));
+
+ // Create another option with different parameters:
+ // - flags set to 0
+ // - domain-name is now partial, not fully qualified
+ // Also, remove base indentation.
+ flags = 0;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ref_string =
+ "type=81 (CLIENT_FQDN), flags: (N=0, E=0, O=0, S=0), "
+ "domain-name='myhost' (partial)";
+ EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(Option4ClientFqdnTest, len) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // This option comprises a header (2 octets), flag field (1 octet),
+ // RCODE1 and RCODE2 (2 octets) and wire representation of the
+ // domain name (length equal to the length of the string representation
+ // of the domain name + 1).
+ EXPECT_EQ(25, option->len());
+
+ // Let's check that the size will change when domain name of a different
+ // size is used.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "example.com"))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(18, option->len());
+
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(12, option->len());
+
+ // Another test for partial domain name but this time the domain name
+ // contains two labels.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(20, option->len());
+
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned when ASCII encoding for FQDN is in use.
+TEST(Option4ClientFqdnTest, lenAscii) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // This option comprises a header (2 octets), flag field (1 octet),
+ // RCODE1 and RCODE2 (2 octets) and the domain name in the ASCII format.
+ // The length of the domain name in the ASCII format is 19 - length
+ // of the string plus terminating dot.
+ EXPECT_EQ(24, option->len());
+
+ // Let's change the domain name and see if the length is different.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(17, option->len());
+
+ // Let's test the length of the option when the partial domain name is
+ // specified.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // For partial names, there is no terminating dot, so the length of the
+ // domain name is equal to the length of the "myhost".
+ EXPECT_EQ(11, option->len());
+
+ // Another check for partial domain name but this time the domain name
+ // contains two labels.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(19, option->len());
+
+
+ // A special case is an empty domain name for which the returned length
+ // should be a sum of the header length, RCODE1, RCODE2 and flag fields
+ // length.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(5, option->len());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option4_dnr_unittest.cc b/src/lib/dhcp/tests/option4_dnr_unittest.cc
new file mode 100644
index 0000000..60f8cf3
--- /dev/null
+++ b/src/lib/dhcp/tests/option4_dnr_unittest.cc
@@ -0,0 +1,793 @@
+// Copyright (C) 2023 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 <dhcp/option4_dnr.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+// This test verifies constructor of the empty Option4Dnr class.
+TEST(Option4DnrTest, emptyCtor) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(DHO_V4_DNR, option->getType());
+}
+
+// This test verifies constructor of the empty Option4Dnr class together
+// with adding ADN-only-mode DNR instance to option's DNR instances.
+TEST(Option4DnrTest, oneAdnOnlyModeInstance) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Prepare example DNR instance to add.
+ DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com.");
+
+ // Add DNR instance.
+ option->addDnrInstance(dnr_1);
+
+ // Check if member variables were correctly set inside DNR instances.
+ EXPECT_EQ(1, option->getDnrInstances().size());
+ EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority());
+ EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength());
+ EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText());
+
+ // This is ADN only mode, so Addr Length and SvcParams Length
+ // are both expected to be zero.
+ EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength());
+ EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength());
+
+ // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len
+ // is set to ADN Len (21) + 3 = 24.
+ // expected len: 1x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers)
+ // = 28
+ EXPECT_EQ(28, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=162(V4_DNR), len=26, "
+ "DNR Instance 1(Instance len=24, service_priority=1, "
+ "adn_length=21, adn='myhost1.example.com.')",
+ option->toText());
+}
+
+// This test verifies constructor of the empty Option4Dnr class together
+// with adding multiple ADN-only-mode DNR instances to option's DNR instances.
+TEST(Option4DnrTest, multipleAdnOnlyModeInstances) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(DHO_V4_DNR, option->getType());
+
+ // Prepare example DNR instances to add.
+ DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com.");
+ DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.");
+ DnrInstance dnr_3 = DnrInstance(Option::V4, 3, "myhost3.example.com.");
+
+ // Add DNR instances.
+ option->addDnrInstance(dnr_1);
+ option->addDnrInstance(dnr_2);
+ option->addDnrInstance(dnr_3);
+
+ // Check if member variables were correctly set inside DNR instances.
+ EXPECT_EQ(3, option->getDnrInstances().size());
+ EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority());
+ EXPECT_EQ(2, option->getDnrInstances()[1].getServicePriority());
+ EXPECT_EQ(3, option->getDnrInstances()[2].getServicePriority());
+ EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength());
+ EXPECT_EQ(21, option->getDnrInstances()[1].getAdnLength());
+ EXPECT_EQ(21, option->getDnrInstances()[2].getAdnLength());
+ EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText());
+ EXPECT_EQ("myhost2.example.com.", option->getDnrInstances()[1].getAdnAsText());
+ EXPECT_EQ("myhost3.example.com.", option->getDnrInstances()[2].getAdnAsText());
+
+ // This is ADN only mode, so Addr Length and SvcParams Length
+ // are both expected to be zero.
+ EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength());
+ EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength());
+ EXPECT_EQ(0, option->getDnrInstances()[1].getAddrLength());
+ EXPECT_EQ(0, option->getDnrInstances()[1].getSvcParamsLength());
+ EXPECT_EQ(0, option->getDnrInstances()[2].getAddrLength());
+ EXPECT_EQ(0, option->getDnrInstances()[2].getSvcParamsLength());
+
+ // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len
+ // is set to ADN Len (21) + 3 = 24.
+ // expected len: 3x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers)
+ // = 78 + 2 = 80
+ EXPECT_EQ(80, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=162(V4_DNR), len=78, "
+ "DNR Instance 1(Instance len=24, service_priority=1, "
+ "adn_length=21, adn='myhost1.example.com.'), "
+ "DNR Instance 2(Instance len=24, service_priority=2, "
+ "adn_length=21, adn='myhost2.example.com.'), "
+ "DNR Instance 3(Instance len=24, service_priority=3, "
+ "adn_length=21, adn='myhost3.example.com.')",
+ option->toText());
+}
+
+// This test verifies constructor of the empty Option4Dnr class together
+// with adding to option's DNR instances:
+// 1. ADN-only-mode DNR instance
+// 2. All fields included (IP addresses and service params also) DNR instance.
+TEST(Option4DnrTest, mixedDnrInstances) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Prepare example DNR instance to add.
+ DnrInstance::AddressContainer addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("192.168.0.2"));
+ std::string svc_params = "key123=val key234=val2 key345";
+
+ DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com.");
+ DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.", addresses, svc_params);
+
+ // Add DNR instance.
+ option->addDnrInstance(dnr_1);
+ option->addDnrInstance(dnr_2);
+
+ // Check if member variables were correctly set inside DNR instances.
+ EXPECT_EQ(2, option->getDnrInstances().size());
+ EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority());
+ EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength());
+ EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText());
+ EXPECT_EQ(2, option->getDnrInstances()[1].getServicePriority());
+ EXPECT_EQ(21, option->getDnrInstances()[1].getAdnLength());
+ EXPECT_EQ("myhost2.example.com.", option->getDnrInstances()[1].getAdnAsText());
+
+ EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength());
+ EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength());
+ EXPECT_EQ(2, option->getDnrInstances()[1].getAddresses().size());
+ EXPECT_EQ(8, option->getDnrInstances()[1].getAddrLength());
+ EXPECT_EQ(29, option->getDnrInstances()[1].getSvcParamsLength());
+ EXPECT_EQ("192.168.0.1", option->getDnrInstances()[1].getAddresses()[0].toText());
+ EXPECT_EQ("192.168.0.2", option->getDnrInstances()[1].getAddresses()[1].toText());
+ EXPECT_EQ(svc_params, option->getDnrInstances()[1].getSvcParams());
+
+ // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len
+ // is set to ADN Len (21) + 3 = 24.
+ // expected len: 1x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers)
+ // + 24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len) + 1 (Addr Len)
+ // + 8 (IP addresses) + 29 (svc params)
+ // = 92
+ EXPECT_EQ(92, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=162(V4_DNR), len=90, "
+ "DNR Instance 1(Instance len=24, service_priority=1, "
+ "adn_length=21, adn='myhost1.example.com.'), "
+ "DNR Instance 2(Instance len=62, service_priority=2, "
+ "adn_length=21, adn='myhost2.example.com.', "
+ "addr_length=8, address(es): 192.168.0.1 192.168.0.2, "
+ "svc_params='key123=val key234=val2 key345')",
+ option->toText());
+}
+
+// This test verifies option packing into wire data.
+// Provided data to pack contains 1 DNR instance:
+// 1. ADN only mode
+TEST(Option4DnrTest, packOneAdnOnlyModeInstance) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Prepare example DNR instance to add.
+ DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com.");
+
+ // Add DNR instance.
+ option->addDnrInstance(dnr_1);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ DHO_V4_DNR, // Option code
+ 26, // Option len=26 dec
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00 // com.
+ };
+
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies option packing into wire data.
+// Provided data to pack contains 3 DNR instances:
+// 1. ADN only mode
+// 2. ADN only mode
+// 3. ADN only mode
+TEST(Option4DnrTest, packMultipleAdnOnlyModeInstances) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(DHO_V4_DNR, option->getType());
+
+ // Prepare example DNR instances to add.
+ DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com.");
+ DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.");
+ DnrInstance dnr_3 = DnrInstance(Option::V4, 3, "myhost3.example.com.");
+
+ // Add DNR instances.
+ option->addDnrInstance(dnr_1);
+ option->addDnrInstance(dnr_2);
+ option->addDnrInstance(dnr_3);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ DHO_V4_DNR, // Option code
+ 78, // Option len=78 dec
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x03, // Service priority is 3 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '3', // FQDN: myhost3.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00 // com.
+ };
+
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies option packing into wire data.
+// Provided data to pack contains 2 DNR instances:
+// 1. ADN only mode
+// 2. All fields included (IP addresses and service params also).
+TEST(Option4DnrTest, packMixedDnrInstances) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Prepare example DNR instance to add.
+ DnrInstance::AddressContainer addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("192.168.0.2"));
+ std::string svc_params = "key123=val key234=val2 key345";
+
+ DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com.");
+ DnrInstance dnr_2 = DnrInstance(Option::V4, 2, "myhost2.example.com.", addresses, svc_params);
+
+ // Add DNR instance.
+ option->addDnrInstance(dnr_1);
+ option->addDnrInstance(dnr_2);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ DHO_V4_DNR, // Option code
+ 90, // Option len=90 dec
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 62, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 8, // Addr Len
+ 192, 168, 0, 1, // IP address 1
+ 192, 168, 0, 2, // IP address 2
+ 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params
+ 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params
+ '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params
+ 'e', 'y', '3', '4', '5' // Svc Params
+ };
+
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies option constructor from wire data.
+TEST(Option4DnrTest, onWireDataCtor) {
+ // Prepare data to decode - ADN only mode 1 DNR instance.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00 // com.
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+}
+
+// This test verifies option constructor from wire data in terms
+// of proper data unpacking.
+// Provided wire data contains 1 DNR instance:
+// 1. ADN only mode
+TEST(Option4DnrTest, unpackOneAdnOnly) {
+ // Prepare data to decode - ADN only mode 1 DNR instance.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00 // com.
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(DHO_V4_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ EXPECT_EQ(24, option->getDnrInstances()[0].getDnrInstanceDataLength());
+ EXPECT_EQ(1, option->getDnrInstances()[0].getServicePriority());
+ EXPECT_EQ(21, option->getDnrInstances()[0].getAdnLength());
+ EXPECT_EQ("myhost1.example.com.", option->getDnrInstances()[0].getAdnAsText());
+
+ // This is ADN only mode, so Addr Length and SvcParams Length
+ // are both expected to be zero.
+ EXPECT_EQ(0, option->getDnrInstances()[0].getAddrLength());
+ EXPECT_EQ(0, option->getDnrInstances()[0].getSvcParamsLength());
+
+ // BTW let's check if len() works ok. In ADN only mode, DNR Instance Data Len
+ // is set to ADN Len (21) + 3 = 24.
+ // expected len: 1x(24 (ADN+ADN Len+Service priority) + 2 (DNR Instance Data Len)) + 2 (headers)
+ // = 28
+ EXPECT_EQ(28, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=162(V4_DNR), len=26, "
+ "DNR Instance 1(Instance len=24, service_priority=1, "
+ "adn_length=21, adn='myhost1.example.com.')",
+ option->toText());
+}
+
+// This test verifies option constructor from wire data in terms
+// of proper data unpacking.
+// Provided wire data contains 1 DNR instance:
+// 1. All fields included (IP addresses and service params also).
+TEST(Option4DnrTest, unpackOneDnrInstance) {
+ // Prepare data to decode - 1 DNR instance.
+ const uint8_t buf_data[] = {
+ 0x00, 62, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 8, // Addr Len
+ 192, 168, 0, 1, // IP address 1
+ 192, 168, 0, 2, // IP address 2
+ 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params
+ 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params
+ '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params
+ 'e', 'y', '3', '4', '5' // Svc Params
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(DHO_V4_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ const DnrInstance& dnr_i = option->getDnrInstances()[0];
+ EXPECT_EQ(62, dnr_i.getDnrInstanceDataLength());
+ EXPECT_EQ(1, dnr_i.getServicePriority());
+ EXPECT_EQ(21, dnr_i.getAdnLength());
+ EXPECT_EQ("myhost1.example.com.", dnr_i.getAdnAsText());
+ EXPECT_EQ(8, dnr_i.getAddrLength());
+ EXPECT_EQ(29, dnr_i.getSvcParamsLength());
+ EXPECT_EQ(2, dnr_i.getAddresses().size());
+ EXPECT_EQ("192.168.0.1", dnr_i.getAddresses()[0].toText());
+ EXPECT_EQ("192.168.0.2", dnr_i.getAddresses()[1].toText());
+ EXPECT_EQ("key123=val key234=val2 key345", dnr_i.getSvcParams());
+ EXPECT_EQ(66, option->len());
+}
+
+// This test verifies option constructor from wire data in terms
+// of proper data unpacking.
+// Provided wire data contains 2 DNR instances:
+// 1. ADN only mode
+// 2. All fields included (IP addresses and service params also).
+TEST(Option4DnrTest, unpackMixedDnrInstances) {
+ // Prepare data to decode - 2 DNR instances.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 62, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 8, // Addr Len
+ 192, 168, 0, 1, // IP address 1
+ 192, 168, 0, 2, // IP address 2
+ 'k', 'e', 'y', '1', '2', '3', '=', 'v', // Svc Params
+ 'a', 'l', ' ', 'k', 'e', 'y', '2', '3', // Svc Params
+ '4', '=', 'v', 'a', 'l', '2', ' ', 'k', // Svc Params
+ 'e', 'y', '3', '4', '5' // Svc Params
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(DHO_V4_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ const DnrInstance& dnr_1 = option->getDnrInstances()[0];
+ EXPECT_EQ(24, dnr_1.getDnrInstanceDataLength());
+ EXPECT_EQ(1, dnr_1.getServicePriority());
+ EXPECT_EQ(21, dnr_1.getAdnLength());
+ EXPECT_EQ("myhost1.example.com.", dnr_1.getAdnAsText());
+ EXPECT_EQ(0, dnr_1.getAddrLength());
+ EXPECT_EQ(0, dnr_1.getSvcParamsLength());
+
+ const DnrInstance& dnr_2 = option->getDnrInstances()[1];
+ EXPECT_EQ(62, dnr_2.getDnrInstanceDataLength());
+ EXPECT_EQ(2, dnr_2.getServicePriority());
+ EXPECT_EQ(21, dnr_2.getAdnLength());
+ EXPECT_EQ("myhost2.example.com.", dnr_2.getAdnAsText());
+ EXPECT_EQ(8, dnr_2.getAddrLength());
+ EXPECT_EQ(29, dnr_2.getSvcParamsLength());
+ EXPECT_EQ(2, dnr_2.getAddresses().size());
+ EXPECT_EQ("192.168.0.1", dnr_2.getAddresses()[0].toText());
+ EXPECT_EQ("192.168.0.2", dnr_2.getAddresses()[1].toText());
+ EXPECT_EQ("key123=val key234=val2 key345", dnr_2.getSvcParams());
+
+ EXPECT_EQ(92, option->len());
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - mandatory fields are truncated - Service Priority and ADN Len truncated.
+TEST(Option4DnrTest, unpackTruncatedDnrInstanceDataLen) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 62 // DNR Instance Data Len truncated
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - DNR instance data truncated when compared to DNR Instance Data Len field.
+TEST(Option4DnrTest, unpackTruncatedDnrInstanceData) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 62, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21 // ADN Length is 21 dec
+ // the rest of DNR instance data is truncated
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - ADN field data truncated.
+TEST(Option4DnrTest, unpackTruncatedAdn) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 3, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21 // ADN Length is 21 dec
+ // ADN data is missing.
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), BadValue);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - ADN FQDN contains only whitespace - non valid FQDN.
+TEST(Option4DnrTest, unpackInvalidFqdnAdn) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 4, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 1, // ADN Length is 1 dec
+ ' ' // ADN contains only whitespace
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - ADN Length is 0 and no ADN FQDN at all.
+TEST(Option4DnrTest, unpackNoFqdnAdn) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 3, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 0 // ADN Length is 0 dec
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - IPv4 address(es) field data truncated.
+TEST(Option4DnrTest, unpackTruncatedIpAddress) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 25, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 8 // Addr Len
+ // the rest of DNR instance data is truncated.
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), BadValue);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - Addr length is 0 and no IPv4 addresses at all.
+TEST(Option4DnrTest, unpackNoIpAddress) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 25, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0 // Addr Len = 0
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - Addr length is not a multiple of 4.
+TEST(Option4DnrTest, unpackIpAddressNon4Modulo) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 32, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 7, // Addr Len
+ 192, 168, 0, 1, // IP address 1
+ 192, 168, 0 // IP address 2 truncated
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - SvcParams Key contains char that is not allowed.
+TEST(Option4DnrTest, unpackvcParamsInvalidCharKey) {
+ // Prepare malformed data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 24, // DNR Instance Data Len
+ 0x00, 0x01, // Service priority is 1 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '1', // FQDN: myhost1.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 39, // DNR Instance Data Len
+ 0x00, 0x02, // Service priority is 2 dec
+ 21, // ADN Length is 21 dec
+ 0x07, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, '2', // FQDN: myhost2.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 8, // Addr Len
+ 192, 168, 0, 1, // IP address 1
+ 192, 168, 0, 2, // IP address 2 truncated
+ 'k', 'e', 'y', '+', '2', '3' // Svc Params key has forbidden char +
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ // Create option instance. Check that constructor throws an exception while doing unpack.
+ Option4DnrPtr option;
+ EXPECT_THROW(option.reset(new Option4Dnr(buf.begin(), buf.end())), InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option4DnrTest, toText) {
+ // Create option instance. Check that constructor doesn't throw.
+ Option4DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option4Dnr()));
+ ASSERT_TRUE(option);
+
+ // Prepare example DNR instance to add.
+ DnrInstance dnr_1 = DnrInstance(Option::V4, 1, "myhost1.example.com.");
+
+ // Add DNR instance.
+ option->addDnrInstance(dnr_1);
+
+ // Let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ const int indent = 4;
+ std::string expected = " type=162(V4_DNR), len=26, " // the indentation of 4 spaces
+ "DNR Instance 1(Instance len=24, service_priority=1, "
+ "adn_length=21, adn='myhost1.example.com.')";
+ EXPECT_EQ(expected, option->toText(indent));
+}
+
+} // namespace \ No newline at end of file
diff --git a/src/lib/dhcp/tests/option6_addrlst_unittest.cc b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
new file mode 100644
index 0000000..ba2190d
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
@@ -0,0 +1,276 @@
+// 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_addrlst.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+using boost::scoped_ptr;
+
+namespace {
+class Option6AddrLstTest : public ::testing::Test {
+public:
+ Option6AddrLstTest(): buf_(255), out_buf_(255) {
+ for (unsigned i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+ OptionBuffer buf_;
+ OutputBuffer out_buf_;
+};
+
+TEST_F(Option6AddrLstTest, basic) {
+
+ // Limiting tests to just a 2001:db8::/32 is *wrong*.
+ // Good tests check corner cases as well.
+ // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff checks
+ // for integer overflow.
+ // ff02::face:b00c checks if multicast addresses
+ // can be represented properly.
+
+ uint8_t sampledata[] = {
+ // 2001:db8:1::dead:beef
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
+ 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef,
+
+ // ff02::face:b00c
+ 0xff, 02, 0, 0, 0, 0, 0 , 0,
+ 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c,
+
+ // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+ uint8_t expected1[] = {
+ D6O_NAME_SERVERS/256, D6O_NAME_SERVERS%256,//type
+ 0, 16, // len = 16 (1 address)
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
+ 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef,
+
+ };
+
+ uint8_t expected2[] = {
+ D6O_SIP_SERVERS_ADDR/256, D6O_SIP_SERVERS_ADDR%256,
+ 0, 32, // len = 32 (2 addresses)
+ // 2001:db8:1::dead:beef
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
+ 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef,
+
+ // ff02::face:b00c
+ 0xff, 02, 0, 0, 0, 0, 0 , 0,
+ 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c,
+
+ // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+ uint8_t expected3[] = {
+ D6O_NIS_SERVERS/256, D6O_NIS_SERVERS%256,
+ 0, 48,
+ // 2001:db8:1::dead:beef
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
+ 0, 0, 0, 0, 0xde, 0xad, 0xbe, 0xef,
+
+ // ff02::face:b00c
+ 0xff, 02, 0, 0, 0, 0, 0 , 0,
+ 0, 0, 0, 0, 0xfa, 0xce, 0xb0, 0x0c,
+
+ // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+ memcpy(&buf_[0], sampledata, 48);
+
+ // Just a single address
+ scoped_ptr<Option6AddrLst> opt1;
+ EXPECT_NO_THROW(
+ opt1.reset(new Option6AddrLst(D6O_NAME_SERVERS,
+ buf_.begin(), buf_.begin() + 16));
+ );
+
+ EXPECT_EQ(Option::V6, opt1->getUniverse());
+
+ EXPECT_EQ(D6O_NAME_SERVERS, opt1->getType());
+ EXPECT_EQ(20, opt1->len());
+ Option6AddrLst::AddressContainer addrs = opt1->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ IOAddress addr = addrs[0];
+ EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
+
+ // Pack this option
+ opt1->pack(out_buf_);
+
+ EXPECT_EQ(20, out_buf_.getLength());
+ EXPECT_EQ(0, memcmp(expected1, out_buf_.getData(), 20));
+
+ // Two addresses
+ scoped_ptr<Option6AddrLst> opt2;
+ EXPECT_NO_THROW(
+ opt2.reset(new Option6AddrLst(D6O_SIP_SERVERS_ADDR,
+ buf_.begin(), buf_.begin() + 32));
+ );
+ EXPECT_EQ(D6O_SIP_SERVERS_ADDR, opt2->getType());
+ EXPECT_EQ(36, opt2->len());
+ addrs = opt2->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText());
+ EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
+
+ // Pack this option
+ out_buf_.clear();
+ opt2->pack(out_buf_);
+
+ EXPECT_EQ(36, out_buf_.getLength() );
+ EXPECT_EQ(0, memcmp(expected2, out_buf_.getData(), 36));
+
+ // Three addresses
+ scoped_ptr<Option6AddrLst> opt3;
+ EXPECT_NO_THROW(
+ opt3.reset(new Option6AddrLst(D6O_NIS_SERVERS,
+ buf_.begin(), buf_.begin() + 48));
+ );
+
+ EXPECT_EQ(D6O_NIS_SERVERS, opt3->getType());
+ EXPECT_EQ(52, opt3->len());
+ addrs = opt3->getAddresses();
+ ASSERT_EQ(3, addrs.size());
+ EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText());
+ EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
+ EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addrs[2].toText());
+
+ // Pack this option
+ out_buf_.clear();
+ opt3->pack(out_buf_);
+
+ EXPECT_EQ(52, out_buf_.getLength());
+ EXPECT_EQ(0, memcmp(expected3, out_buf_.getData(), 52));
+
+ EXPECT_NO_THROW(opt1.reset());
+ EXPECT_NO_THROW(opt2.reset());
+ EXPECT_NO_THROW(opt3.reset());
+}
+
+TEST_F(Option6AddrLstTest, constructors) {
+
+ scoped_ptr<Option6AddrLst> opt1;
+ EXPECT_NO_THROW(
+ opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
+ );
+ EXPECT_EQ(Option::V6, opt1->getUniverse());
+ EXPECT_EQ(1234, opt1->getType());
+
+ Option6AddrLst::AddressContainer addrs = opt1->getAddresses();
+ ASSERT_EQ(1, addrs.size() );
+ EXPECT_EQ("::1", addrs[0].toText());
+
+ addrs.clear();
+ addrs.push_back(IOAddress(string("fe80::1234")));
+ addrs.push_back(IOAddress(string("2001:db8:1::baca")));
+
+ scoped_ptr<Option6AddrLst> opt2;
+ EXPECT_NO_THROW(
+ opt2.reset(new Option6AddrLst(5678, addrs));
+ );
+
+ Option6AddrLst::AddressContainer check = opt2->getAddresses();
+ ASSERT_EQ(2, check.size() );
+ EXPECT_EQ("fe80::1234", check[0].toText());
+ EXPECT_EQ("2001:db8:1::baca", check[1].toText());
+
+ EXPECT_NO_THROW(opt1.reset());
+ EXPECT_NO_THROW(opt2.reset());
+}
+
+TEST_F(Option6AddrLstTest, setAddress) {
+ scoped_ptr<Option6AddrLst> opt1;
+ EXPECT_NO_THROW(
+ opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
+ );
+ opt1->setAddress(IOAddress("2001:db8:1::2"));
+ /// TODO It used to be ::2 address, but io_address represents
+ /// it as ::0.0.0.2. Purpose of this test is to verify
+ /// that setAddress() works, not deal with subtleties of
+ /// io_address handling of IPv4-mapped IPv6 addresses, we
+ /// switched to a more common address. User interested
+ /// in pursuing this matter further is encouraged to look
+ /// at section 2.5.5 of RFC4291 (and possibly implement
+ /// a test for IOAddress)
+
+ Option6AddrLst::AddressContainer addrs = opt1->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
+
+ EXPECT_NO_THROW(opt1.reset());
+}
+
+// This test checks that the option holding IPv6 address list can
+// be converted to textual format.
+TEST_F(Option6AddrLstTest, toText) {
+ Option6AddrLst opt(1234, IOAddress("2001:db8:1::1"));
+ // Generate a few IPv6 addresses.
+ Option6AddrLst::AddressContainer addresses;
+ for (int i = 2; i < 6; ++i) {
+ std::stringstream s;
+ s << "2001:db8:1::" << i;
+ addresses.push_back(IOAddress(s.str()));
+ }
+ opt.setAddresses(addresses);
+
+ EXPECT_EQ("type=01234, len=00064: 2001:db8:1::2 2001:db8:1::3 "
+ "2001:db8:1::4 2001:db8:1::5", opt.toText());
+}
+
+// A helper for the 'empty' test. Exercise public interfaces of an empty
+// Option6AddrLst. It assumes the option type is D6O_DHCPV4_O_DHCPV6_SERVER.
+void
+checkEmpty(Option6AddrLst& addrs) {
+ const uint8_t expected[] = {
+ D6O_DHCPV4_O_DHCPV6_SERVER/256, D6O_DHCPV4_O_DHCPV6_SERVER%256,
+ 0, 0
+ };
+ EXPECT_EQ(4, addrs.len()); // just 2-byte type and 2-byte len fields
+ EXPECT_EQ("type=00088, len=00000:", addrs.toText());
+
+ OutputBuffer out_buf(255);
+ addrs.pack(out_buf);
+ EXPECT_EQ(4, out_buf.getLength());
+ EXPECT_EQ(0, memcmp(expected, out_buf.getData(), 4));
+}
+
+// Confirms no disruption happens for an empty set of addresses.
+TEST_F(Option6AddrLstTest, empty) {
+ boost::scoped_ptr<Option6AddrLst> addrs(
+ new Option6AddrLst(D6O_DHCPV4_O_DHCPV6_SERVER,
+ Option6AddrLst::AddressContainer()));
+ checkEmpty(*addrs);
+
+ const OptionBuffer empty_buf;
+ addrs.reset(new Option6AddrLst(D6O_DHCPV4_O_DHCPV6_SERVER,
+ empty_buf.begin(), empty_buf.end()));
+ checkEmpty(*addrs);
+}
+
+} // namespace
diff --git a/src/lib/dhcp/tests/option6_auth_unittest.cc b/src/lib/dhcp/tests/option6_auth_unittest.cc
new file mode 100644
index 0000000..41b7799
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_auth_unittest.cc
@@ -0,0 +1,166 @@
+// Copyright (C) 2018-2020 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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_auth.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+using boost::scoped_ptr;
+
+namespace {
+class Option6AuthTest : public ::testing::Test {
+public:
+ Option6AuthTest(): buff_(28) {
+ }
+ OptionBuffer buff_;
+};
+
+// check constructor, setters and getters
+TEST_F(Option6AuthTest, basic) {
+
+ scoped_ptr<Option6Auth> auth;
+ ASSERT_NO_THROW(auth.reset(new Option6Auth(1,2,0,0x9000,{'a','b','c','d'})));
+
+ ASSERT_EQ(1, auth->getProtocol());
+ ASSERT_EQ(2, auth->getHashAlgo());
+ ASSERT_EQ(0, auth->getReplyDetectionMethod());
+ ASSERT_EQ(0x9000, auth->getReplyDetectionValue());
+
+ std::vector<uint8_t> test_buf = {'a','b','c','d'};
+ ASSERT_EQ(test_buf, auth->getAuthInfo());
+
+ auth->setProtocol(2);
+ auth->setHashAlgo(3);
+ auth->setReplyDetectionMethod(1);
+ auth->setReplyDetectionValue(109034830);
+ auth->setAuthInfo({1,2,3,4});
+
+ ASSERT_EQ(2, auth->getProtocol());
+ ASSERT_EQ(3, auth->getHashAlgo());
+ ASSERT_EQ(1, auth->getReplyDetectionMethod());
+ ASSERT_EQ(109034830, auth->getReplyDetectionValue());
+
+ test_buf = {1,2,3,4};
+ ASSERT_EQ(test_buf, auth->getAuthInfo());
+}
+
+//Check if all the fields are properly parsed and stored
+// todo define userdefined literal and add packing function to it
+TEST_F(Option6AuthTest, parseFields) {
+ buff_[0] = 0xa1; //protocol
+ buff_[1] = 0xa2; //algo
+ buff_[2] = 0xa3; //rdm method
+ buff_[3] = 0xa4; //rdm value
+ buff_[4] = 0xa5; //rdm value
+ buff_[5] = 0xa6; //rdm value
+ buff_[6] = 0xa7; //rdm value
+ buff_[7] = 0xa8; //rdm value
+ buff_[8] = 0xa9; //rdm value
+ buff_[9] = 0xaa; //rdm value
+ buff_[10] = 0xab; //rdm value
+ for ( uint8_t i = 11; i < 27; i++ ) {
+ buff_[i] = 0xa8; //auth info 16 bytes
+ }
+
+ scoped_ptr<Option6Auth> auth;
+ auth.reset(new Option6Auth(1,2,0,9000,{'a','b','c','d'}));
+
+ auth->unpack(buff_.begin(), buff_.begin()+27); //26 element is 16 byte offset from 10
+
+ std::vector<uint8_t> test_buf(16,0xa8);
+ ASSERT_EQ(0xa1, auth->getProtocol());
+ ASSERT_EQ(0xa2, auth->getHashAlgo());
+ ASSERT_EQ(0xa3, auth->getReplyDetectionMethod());
+ ASSERT_EQ(0xa4a5a6a7a8a9aaab, auth->getReplyDetectionValue());
+ ASSERT_EQ(test_buf, auth->getAuthInfo());
+}
+
+//Check of the options are correctly packed and set
+TEST_F(Option6AuthTest, setFields) {
+ scoped_ptr<Option6Auth> auth;
+ std::vector<uint8_t> test_buf(16,0xa8);
+ auth.reset(new Option6Auth(1,2,0,0x0090000000000000,test_buf));
+
+ isc::util::OutputBuffer buf(31);//4 header + fixed 11 and key 16
+ ASSERT_NO_THROW(auth->pack(buf));
+
+ const uint8_t ref_data[] = {
+ 0, 11, 0, 27, 1, 2, 0, //header , proto algo method
+ 0, 0x90, 0, 0, 0, 0, 0, 0, //64 bit rdm field
+ 0xa8, 0xa8, 0xa8, 0xa8, //128 bits/16 byte key
+ 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8
+ };
+ //first check if they are of equal size
+ ASSERT_EQ(buf.getLength(), sizeof(ref_data));
+
+ //evaluate the contents of the option byte by byte
+ ASSERT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+TEST_F(Option6AuthTest, checkHashInput) {
+ scoped_ptr<Option6Auth> auth;
+
+ std::vector<uint8_t> test_buf(16,0xa8);
+ std::vector<uint8_t> hash_op(16,0x00);
+ auth.reset(new Option6Auth(1,2,0,0x0102030405060708,test_buf));
+
+ isc::util::OutputBuffer buf(31);
+ ASSERT_NO_THROW(auth->packHashInput(buf));
+ //auth info must be 0 for calculating the checksum
+ const uint8_t ref_data[] = {
+ 0, 11, 0, 27, 1, 2, 0, //header , proto algo method
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, //64 bit rdm field
+ 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key
+ 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key
+ 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key
+ 0x00, 0x00, 0x00, 0x00, //128 bits/16 byte key
+ };
+ //first check if they are of equal size
+ ASSERT_EQ(buf.getLength(), sizeof(ref_data));
+
+ //evaluate the contents of the option byte by byte
+ ASSERT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+TEST_F(Option6AuthTest, negativeCase) {
+ scoped_ptr<Option6Auth> auth;
+
+ std::vector<uint8_t> test_buf(16,0xa8);
+ auth.reset(new Option6Auth(1,2,0,0x0102030405060708,test_buf));
+ //allocate less space to force an exception to be thrown
+ isc::util::OutputBuffer buf(20);
+
+ ASSERT_THROW(auth->pack(buf), isc::OutOfRange);
+ ASSERT_THROW(auth->packHashInput(buf), isc::OutOfRange);
+}
+
+// Checks whether the to text conversion is working ok.
+TEST_F(Option6AuthTest, toText) {
+ scoped_ptr<Option6Auth> auth;
+ auth.reset(new Option6Auth(1,2,0,9000,{'a','b','c','d'}));
+
+ string exp_txt = " protocol=1, algorithm=2, rdm method=0, rdm value=9000, value=61626364";
+
+ std::cout << auth->toText(2) << std::endl;
+
+}
+
+} //end namespace
diff --git a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
new file mode 100644
index 0000000..c293489
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
@@ -0,0 +1,819 @@
+// Copyright (C) 2013-2018 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 <dhcp/option6_client_fqdn.h>
+#include <dns/name.h>
+#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option6ClientFqdnTest, constructEmptyName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Constructor should not accept empty fully qualified domain name.
+ EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+ // This check is similar to previous one, but using domain-name comprising
+ // a single space character. This should be treated as empty domain-name.
+ EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ",
+ Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+
+ // Try different constructor.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option6ClientFqdnTest, copyConstruct) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option6ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option_copy->getDomainNameType());
+
+ // Do another test with different parameters to verify that parameters
+ // change when copied object is changed.
+
+ // Create an option with different parameters.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "example",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Call copy-constructor to copy the option.
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ option.reset();
+
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("example", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option, when
+// domain-name is empty.
+TEST(Option6ClientFqdnTest, copyConstructEmptyDomainName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S));
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option6ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWire) {
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 77, 121, 104, 111, 115, 116, // Myhost.
+ 7, 69, 120, 97, 109, 112, 108, 101, // Example.
+ 3, 67, 111, 109, 0 // Com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// Verify that exception is thrown if the domain-name label is
+// longer than 63.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) {
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+ in_buf.push_back(70);
+ in_buf.insert(in_buf.end(), 70, 109);
+ in_buf.push_back(0);
+
+ EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption6FqdnDomainName);
+}
+
+// Verify that exception is thrown if the overall length of the domain-name
+// is over 255.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) {
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+ // Construct the FQDN from 26 labels, each having a size of 10.
+ for (int i = 0; i < 26; ++i) {
+ // Append the length of each label.
+ in_buf.push_back(10);
+ // Append the actual label.
+ in_buf.insert(in_buf.end(), 10, 109);
+ }
+ // Terminate FQDN with a dot.
+ in_buf.push_back(0);
+
+ EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
+ // Empty buffer is invalid. It should be at least one octet long.
+ OptionBuffer in_buf;
+ ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ OutOfRange);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWirePartial) {
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_N, // flags
+ 6, 77, 121, 104, 111, 115, 116 // Myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWireEmpty) {
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ // domain-name field should be empty because on-wire data comprised
+ // flags field only.
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option6ClientFqdnTest, assignment) {
+ // Usually the smart pointer is used to declare options and call
+ // constructor within assert. Thanks to this approach, the option instance
+ // is in the function scope and only initialization is done within assert.
+ // In this particular test we can't use smart pointers because we are
+ // testing assignment operator like this:
+ //
+ // option2 = option;
+ //
+ // The two asserts below do not create the instances that we will used to
+ // test assignment. They just attempt to create instances of the options
+ // with the same parameters as those that will be created for the actual
+ // assignment test. If these asserts do not fail, we can create options
+ // for the assignment test, do not surround them with asserts and be sure
+ // they will not throw.
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option6ClientFqdn option(Option6ClientFqdn::FLAG_S,
+ "Myhost.Example.Com",
+ Option6ClientFqdn::FULL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost.example.com.", option.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType());
+
+ // Create a second option.
+ Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL);
+
+ // Verify tha the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another, when the domain-name is empty.
+TEST(Option6ClientFqdnTest, assignmentEmptyDomainName) {
+ ASSERT_NO_THROW(
+ Option6ClientFqdn(static_cast<uint8_t>(Option6ClientFqdn::FLAG_S))
+ );
+
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option6ClientFqdn option(Option6ClientFqdn::FLAG_S);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("", option.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::PARTIAL, option.getDomainNameType());
+
+ // Create a second option.
+ Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("", option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option6ClientFqdnTest, constructInvalidFlags) {
+ // First, check that constructor does not throw an exception when
+ // valid flags values are provided. That way we eliminate the issue
+ // that constructor always throws exception.
+ uint8_t flags = 0;
+ ASSERT_NO_THROW(Option6ClientFqdn(flags, "myhost.example.com"));
+
+ // Invalid flags: The maximal value is 0x7 when all flag bits are set
+ // (00000111b). The flag value of 0x14 sets the bit from the Must Be
+ // Zero (MBZ) bitset (00001100b).
+ flags = 0x14;
+ EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
+ InvalidOption6FqdnFlags);
+
+ // According to RFC 4704, section 4.1. if the N bit is set the S bit MUST
+ // be zero. If both are set, constructor is expected to throw.
+ flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
+ InvalidOption6FqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option6ClientFqdnTest, constructFromWireInvalidFlags) {
+ // Create a buffer which holds flags field only. Set valid flag field at
+ // at first to make sure that constructor doesn't always throw an exception.
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_N);
+ ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()));
+
+ // Replace the flags with invalid value and verify that constructor throws
+ // appropriate exception.
+ in_buf[0] = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption6FqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option6ClientFqdnTest, constructInvalidName) {
+ // First, check that constructor does not throw when valid domain name
+ // is specified. That way we eliminate the possibility that constructor
+ // always throws exception.
+ ASSERT_NO_THROW(Option6ClientFqdn(0, "myhost.example.com"));
+
+ // Specify invalid domain name and expect that exception is thrown.
+ EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"),
+ InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than FLAG_N, FLAG_S, FLAG_O is specified.
+TEST(Option6ClientFqdnTest, getFlag) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The 0x3 (binary 011) specifies two distinct bits in the flags field.
+ // This value is ambiguous for getFlag function and this function doesn't
+ // know which flag the caller is attempting to check.
+ EXPECT_THROW(option->getFlag(0x3), InvalidOption6FqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(Option6ClientFqdnTest, setFlag) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // All flags should be set to 0 initially.
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // Set N = 1
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+ // Set O = 1
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, true));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // Set S = 1, this should throw exception because S and N must not
+ // be set in the same time.
+ ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true),
+ InvalidOption6FqdnFlags);
+
+ // Set N = 0
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+ // Set S = 1, this should not result in exception because N has been
+ // cleared.
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+
+ // Set N = 1, this should result in exception because S = 1
+ ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true),
+ InvalidOption6FqdnFlags);
+
+ // Set O = 0
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // Try out of bounds settings.
+ uint8_t flags = 0;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
+
+ flags = 0x14;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option6ClientFqdnTest, resetFlags) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S |
+ Option6ClientFqdn::FLAG_O,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Check that flags we set in the constructor are set.
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+ option->resetFlags();
+
+ // After reset, all flags should be 0.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option6ClientFqdnTest, setDomainName) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Partial domain-name.
+ ASSERT_NO_THROW(option->setDomainName("Myhost",
+ Option6ClientFqdn::PARTIAL));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-name.
+ ASSERT_NO_THROW(option->setDomainName("Example.com",
+ Option6ClientFqdn::FULL));
+ EXPECT_EQ("example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Empty domain name (partial). This should be successful.
+ ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-names must not be empty.
+ EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+ EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option6ClientFqdnTest, resetDomainName) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Set the domain-name to empty one.
+ ASSERT_NO_THROW(option->resetDomainName());
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies on-wire format of the option is correctly created.
+TEST(Option6ClientFqdnTest, pack) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option6ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0, 39, 0, 21, // header
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies on-wire format of the option with partial domain name
+// is correctly created.
+TEST(Option6ClientFqdnTest, packPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option6ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags, "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0, 39, 0, 8, // header
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that it is possible to encode the option which carries
+// empty domain-name in the wire format.
+TEST(Option6ClientFqdnTest, packEmpty) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option6ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(5);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0, 39, 0, 1, // header
+ Option6ClientFqdn::FLAG_S // flags
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option6ClientFqdnTest, unpack) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 77, 121, 104, 111, 115, 116, // Myhost.
+ 7, 69, 120, 97, 109, 112, 108, 101, // Example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option6ClientFqdnTest, unpackPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 77, 121, 104, 111, 115, 116 // Myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option6ClientFqdnTest, unpackTruncated) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+ );
+ ASSERT_TRUE(option);
+
+ // Empty buffer is invalid. It should be at least 1 octet long.
+ OptionBuffer in_buf;
+ EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option6ClientFqdnTest, toText) {
+ // Create option instance. Check that constructor doesn't throw.
+ uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags,
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The base indentation of the option will be set to 2. It should appear
+ // as follows.
+ std::string ref_string =
+ " type=39(CLIENT_FQDN), flags: (N=1, O=1, S=0), "
+ "domain-name='myhost.example.com.' (full)";
+ const int indent = 2;
+ EXPECT_EQ(ref_string, option->toText(indent));
+
+ // Create another option with different parameters:
+ // - flags set to 0
+ // - domain-name is now partial, not fully qualified
+ // Also, remove base indentation.
+ flags = 0;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags, "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ref_string =
+ "type=39(CLIENT_FQDN), flags: (N=0, O=0, S=0), "
+ "domain-name='myhost' (partial)";
+ EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(Option6ClientFqdnTest, len) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // This option comprises a header (4 octets), flag field (1 octet),
+ // and wire representation of the domain name (length equal to the
+ // length of the string representation of the domain name + 1).
+ EXPECT_EQ(25, option->len());
+
+ // Use different domain name to check if the length also changes
+ // as expected.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "example.com"))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(18, option->len());
+
+ // Let's check that the size will change when domain name of a different
+ // size is used.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "example.com"))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(18, option->len());
+
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(12, option->len());
+
+ // Another test for partial domain name but this time using
+ // two labels.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost.example",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(20, option->len());
+
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option6_dnr_unittest.cc b/src/lib/dhcp/tests/option6_dnr_unittest.cc
new file mode 100644
index 0000000..29c082d
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_dnr_unittest.cc
@@ -0,0 +1,650 @@
+// Copyright (C) 2023 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 <dhcp/option6_dnr.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies option constructor from wire data.
+// Provided wire data is in the ADN only mode i.e. only
+// Service priority and Authentication domain name FQDN
+// fields are present.
+TEST(Option6DnrTest, onWireCtorAdnOnlyMode) {
+ // Prepare data to decode - ADN only mode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00 // Com.
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ EXPECT_EQ(0x8001, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ("myhost.example.com.", option->getAdnAsText());
+
+ // This is ADN only mode, so Addr Length and SvcParams Length
+ // are both expected to be zero.
+ EXPECT_EQ(0, option->getAddrLength());
+ EXPECT_EQ(0, option->getSvcParamsLength());
+
+ // BTW let's check if len() works ok.
+ // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28.
+ EXPECT_EQ(28, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=144(V6_DNR), len=24, "
+ "service_priority=32769, adn_length=20, "
+ "adn='myhost.example.com.'",
+ option->toText());
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - mandatory fields are truncated.
+TEST(Option6DnrTest, onWireCtorDataTruncated) {
+ // Prepare data to decode - data too short.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01 // Service priority is 32769 dec, other data is missing
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OutOfRange exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - ADN FQDN contains only whitespace - non valid FQDN.
+TEST(Option6DnrTest, onWireCtorOnlyWhitespaceFqdn) {
+ // Prepare data to decode - ADN only mode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x02, // ADN Length is 2 dec
+ 0x01, 0x20 // FQDN consists only of whitespace
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws InvalidOptionDnrDomainName exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - ADN Length is 0 and no ADN FQDN at all.
+TEST(Option6DnrTest, onWireCtorNoAdnFqdn) {
+ // Prepare data to decode - ADN only mode.
+ const uint8_t buf_data[] = {
+ 0x00, 0x01, // Service priority is 1 dec
+ 0x00, 0x00 // ADN Length is 0 dec
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Encrypted DNS options are designed to ALWAYS include
+ // an authentication domain name, so check that constructor throws
+ // InvalidOptionDnrDomainName exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrDomainName);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - FQDN data is truncated.
+TEST(Option6DnrTest, onWireCtorTruncatedFqdn) {
+ // Prepare data to decode - ADN only mode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74 // FQDN data is truncated
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws BadValue exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), BadValue);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - Addr Length field truncated.
+TEST(Option6DnrTest, onWireCtorAddrLenTruncated) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x10 // Truncated Addr Len field
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OutOfRange exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - Addr length is 0 and no IPv6 addresses at all.
+TEST(Option6DnrTest, onWireCtorAddrLenZero) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x00 // Addr Len field value = 0
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OutOfRange exception.
+ // If additional data is supplied (i.e. not ADN only mode),
+ // the option includes at least one valid IP address.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - Addr length is not a multiple of 16.
+TEST(Option6DnrTest, onWireCtorAddrLenNot16Modulo) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0xFF, 0xFE // Addr Len is not a multiple of 16
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OutOfRange exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies option constructor from wire data.
+// Provided wire data contains also IPv6 addresses.
+TEST(Option6DnrTest, onWireCtorValidIpV6Addresses) {
+ // Prepare data to decode
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x30, // Addr Len field value = 48 dec
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ff02::face:b00c
+ 0x00, 0x00, 0x00, 0x00, 0xfa, 0xce, 0xb0, 0x0c,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ EXPECT_EQ(0x8001, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ("myhost.example.com.", option->getAdnAsText());
+ EXPECT_EQ(48, option->getAddrLength());
+ const Option6Dnr::AddressContainer& addresses = option->getAddresses();
+ EXPECT_EQ(3, addresses.size());
+ EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText());
+ EXPECT_EQ("ff02::face:b00c", addresses[1].toText());
+ EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addresses[2].toText());
+ EXPECT_EQ(0, option->getSvcParamsLength());
+
+ // BTW let's check if len() works ok.
+ // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28
+ // + 48 (3 IP addresses) + 2 (Addr Len) = 78.
+ EXPECT_EQ(78, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=144(V6_DNR), len=74, "
+ "service_priority=32769, adn_length=20, "
+ "adn='myhost.example.com.', "
+ "addr_length=48, "
+ "address(es): 2001:db8:1::dead:beef "
+ "ff02::face:b00c "
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ option->toText());
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - IPv6 addresses are truncated.
+TEST(Option6DnrTest, onWireCtorTruncatedIpV6Addresses) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x30, // Addr Len field value = 48 dec
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ 0xff, 0x02, 0x00 // IPv6 address truncated
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws OutOfRange exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies option constructor from wire data.
+// Provided wire data contains also IPv6 address and Svc Params.
+TEST(Option6DnrTest, onWireCtorSvcParamsIncluded) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x10, // Addr Len field value = 16 dec
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ 'a', 'b', 'c' // example SvcParams data
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+
+ // Check if data was unpacked correctly from wire data.
+ EXPECT_EQ(0x8001, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ("myhost.example.com.", option->getAdnAsText());
+ EXPECT_EQ(16, option->getAddrLength());
+ const Option6Dnr::AddressContainer& addresses = option->getAddresses();
+ EXPECT_EQ(1, addresses.size());
+ EXPECT_EQ("2001:db8:1::dead:beef", addresses[0].toText());
+ EXPECT_EQ(3, option->getSvcParamsLength());
+ EXPECT_EQ("abc", option->getSvcParams());
+
+ // BTW let's check if len() works ok.
+ // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28
+ // + 16 (IP address) + 2 (Addr Len) + 3 (SvcParams) = 49.
+ EXPECT_EQ(49, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=144(V6_DNR), len=45, "
+ "service_priority=32769, adn_length=20, "
+ "adn='myhost.example.com.', "
+ "addr_length=16, "
+ "address(es): 2001:db8:1::dead:beef, "
+ "svc_params='abc'",
+ option->toText());
+}
+
+// Test checks that exception is thrown when trying to unpack malformed wire data
+// - SvcParams Key contains char that is not allowed.
+TEST(Option6DnrTest, onWireCtorSvcParamsInvalidCharKey) {
+ // Prepare data to decode with invalid SvcParams.
+ const uint8_t buf_data[] = {
+ 0x80, 0x01, // Service priority is 32769 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x4D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: Myhost.
+ 0x07, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // Example.
+ 0x03, 0x43, 0x6F, 0x6D, 0x00, // Com.
+ 0x00, 0x10, // Addr Len field value = 48 dec
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ 'a', '+', 'c' // Allowed "a"-"z", "0"-"9", and "-"
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ // Create option instance. Check that constructor throws InvalidOptionDnrSvcParams exception.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(buf.begin(), buf.end())), InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies option constructor in ADN only mode.
+// Service priority and ADN are provided via ctor.
+TEST(Option6DnrTest, adnOnlyModeCtor) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn)));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+ EXPECT_EQ(service_priority, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ(adn, option->getAdnAsText());
+
+ // This is ADN only mode, so Addr Length and SvcParams Length
+ // are both expected to be zero.
+ EXPECT_EQ(0, option->getAddrLength());
+ EXPECT_EQ(0, option->getSvcParamsLength());
+
+ // BTW let's check if len() works ok.
+ // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28.
+ EXPECT_EQ(28, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=144(V6_DNR), len=24, "
+ "service_priority=9, adn_length=20, "
+ "adn='myhost.example.com.'",
+ option->toText());
+}
+
+// This test verifies that option constructor in ADN only mode throws
+// an exception when mandatory ADN is empty.
+TEST(Option6DnrTest, adnOnlyModeCtorNoFqdn) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn; // invalid empty ADN
+
+ // Create option instance. Check that constructor throws.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn)), InvalidOptionDnrDomainName);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies option constructor where all fields
+// i.e. Service priority, ADN, IP address(es) and Service params
+// are provided as ctor parameters.
+TEST(Option6DnrTest, allFieldsCtor) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "alpn";
+
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)));
+ ASSERT_TRUE(option);
+
+ // Check if member variables were correctly set by ctor.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(D6O_V6_DNR, option->getType());
+ EXPECT_EQ(service_priority, option->getServicePriority());
+ EXPECT_EQ(20, option->getAdnLength());
+ EXPECT_EQ(adn, option->getAdnAsText());
+ EXPECT_EQ(16, option->getAddrLength());
+ EXPECT_EQ(4, option->getSvcParamsLength());
+ EXPECT_EQ(svc_params, option->getSvcParams());
+
+ // BTW let's check if len() works ok.
+ // expected len: 20 (FQDN) + 2 (ADN Len) + 2 (Service priority) + 4 (headers) = 28
+ // + 16 (IPv6) + 2 (Addr Len) + 4 (Svc Params) = 50.
+ EXPECT_EQ(50, option->len());
+
+ // BTW let's check if toText() works ok.
+ // toText() len does not count in headers len.
+ EXPECT_EQ("type=144(V6_DNR), len=46, "
+ "service_priority=9, adn_length=20, "
+ "adn='myhost.example.com.', addr_length=16, "
+ "address(es): 2001:db8:1::baca, svc_params='alpn'",
+ option->toText());
+}
+
+// This test verifies that option constructor throws
+// an exception when option fields provided via ctor are malformed
+// - no IPv6 address provided.
+TEST(Option6DnrTest, allFieldsCtorNoIpAddress) {
+ // Prepare example parameters
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ const Option6Dnr::AddressContainer addresses; // no IPv6 address in here
+ const std::string svc_params = "alpn";
+
+ // Create option instance. Check that constructor throws.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)),
+ OutOfRange);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies that option constructor throws
+// an exception when option fields provided via ctor are malformed
+// - Svc Params key=val pair has 2 equal signs.
+TEST(Option6DnrTest, svcParamsTwoEqualSignsPerParam) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "key123=val1=val2 key234"; // invalid svc param - 2 equal signs
+
+ // Create option instance. Check that constructor throws.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)),
+ InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies that option constructor throws
+// an exception when option fields provided via ctor are malformed
+// - Svc Params forbidden key provided.
+TEST(Option6DnrTest, svcParamsForbiddenKey) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "key123=val1 ipv6hint"; // forbidden svc param key - ipv6hint
+
+ // Create option instance. Check that constructor throws.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)),
+ InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies that option constructor throws
+// an exception when option fields provided via ctor are malformed
+// - Svc Params key was repeated.
+TEST(Option6DnrTest, svcParamsKeyRepeated) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "key123=val1 key234 key123"; // svc param key key123 repeated
+
+ // Create option instance. Check that constructor throws.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)),
+ InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies that option constructor throws
+// an exception when option fields provided via ctor are malformed
+// - Svc Params key is too long.
+TEST(Option6DnrTest, svcParamsKeyTooLong) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "thisisveryveryveryvery"
+ "veryveryveryveryveryvery"
+ "veryveryveryveryvlongkey"; // svc param key longer than 63
+
+ // Create option instance. Check that constructor throws.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)),
+ InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies that option constructor throws
+// an exception when option fields provided via ctor are malformed
+// - Svc Params key has chars that are not allowed.
+TEST(Option6DnrTest, svcParamsKeyHasInvalidChar) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "alpn=h2 NOT_ALLOWED_CHARS_KEY=123"; // svc param key has forbidden chars
+
+ // Create option instance. Check that constructor throws.
+ Option6DnrPtr option;
+ EXPECT_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)),
+ InvalidOptionDnrSvcParams);
+ ASSERT_FALSE(option);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option6DnrTest, toText) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::baca"));
+ const std::string svc_params = "alpn";
+
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)));
+ ASSERT_TRUE(option);
+
+ const int indent = 4;
+ std::string expected = " type=144(V6_DNR), len=46, " // the indentation of 4 spaces
+ "service_priority=9, adn_length=20, "
+ "adn='myhost.example.com.', addr_length=16, "
+ "address(es): 2001:db8:1::baca, svc_params='alpn'";
+ EXPECT_EQ(expected, option->toText(indent));
+}
+
+// This test verifies on-wire format of the option is correctly created in ADN only mode.
+TEST(Option6DnrTest, packAdnOnlyMode) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn)));
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0x00, D6O_V6_DNR, // Option code
+ 0x00, 24, // Option len=24 dec
+ 0x00, 0x09, // Service priority is 9 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: myhost.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00 // com.
+ };
+
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies on-wire format of the option is correctly created when
+// IP addresses and Svc Params are also included.
+TEST(Option6DnrTest, pack) {
+ // Prepare example parameters.
+ const uint16_t service_priority = 9;
+ const std::string adn = "myhost.example.com.";
+ Option6Dnr::AddressContainer addresses;
+ addresses.push_back(isc::asiolink::IOAddress("2001:db8:1::dead:beef"));
+ addresses.push_back(isc::asiolink::IOAddress("ff02::face:b00c"));
+ const std::string svc_params = "alpn";
+
+ // Create option instance. Check that constructor doesn't throw.
+ Option6DnrPtr option;
+ EXPECT_NO_THROW(option.reset(new Option6Dnr(service_priority, adn, addresses, svc_params)));
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0x00, D6O_V6_DNR, // Option code
+ 0x00, 62, // Option len=62 dec
+ 0x00, 0x09, // Service priority is 9 dec
+ 0x00, 0x14, // ADN Length is 20 dec
+ 0x06, 0x6D, 0x79, 0x68, 0x6F, 0x73, 0x74, // FQDN: myhost.
+ 0x07, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example.
+ 0x03, 0x63, 0x6F, 0x6D, 0x00, // com.
+ 0x00, 0x20, // Addr Len field value = 32 dec
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, // 2001:db8:1::dead:beef
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ff02::face:b00c
+ 0x00, 0x00, 0x00, 0x00, 0xfa, 0xce, 0xb0, 0x0c,
+ 'a', 'l', 'p', 'n' // Svc Params
+ };
+
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+} // namespace \ No newline at end of file
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
new file mode 100644
index 0000000..53d121f
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -0,0 +1,360 @@
+// Copyright (C) 2011-2017 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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <util/buffer.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+using boost::scoped_ptr;
+
+namespace {
+class Option6IATest : public ::testing::Test {
+public:
+ Option6IATest(): buf_(255), outBuf_(255) {
+ for (unsigned i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+
+ /// @brief performs basic checks on IA option
+ ///
+ /// Check that an option can be built based on incoming buffer and that
+ /// the option contains expected values.
+ /// @param type specifies option type (IA_NA or IA_PD)
+ void checkIA(uint16_t type) {
+ buf_[0] = 0xa1; // iaid
+ buf_[1] = 0xa2;
+ buf_[2] = 0xa3;
+ buf_[3] = 0xa4;
+
+ buf_[4] = 0x81; // T1
+ buf_[5] = 0x02;
+ buf_[6] = 0x03;
+ buf_[7] = 0x04;
+
+ buf_[8] = 0x84; // T2
+ buf_[9] = 0x03;
+ buf_[10] = 0x02;
+ buf_[11] = 0x01;
+
+ // Create an option
+ // unpack() is called from constructor
+ scoped_ptr<Option6IA> opt;
+ ASSERT_NO_THROW(opt.reset(new Option6IA(type, buf_.begin(),
+ buf_.begin() + 12)));
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(type, opt->getType());
+ EXPECT_EQ(0xa1a2a3a4, opt->getIAID());
+ EXPECT_EQ(0x81020304, opt->getT1());
+ EXPECT_EQ(0x84030201, opt->getT2());
+
+ // Pack this option again in the same buffer, but in
+ // different place
+
+ // Test for pack()
+ ASSERT_NO_THROW(opt->pack(outBuf_));
+
+ // 12 bytes header + 4 bytes content
+ EXPECT_EQ(12, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(type, opt->getType());
+
+ EXPECT_EQ(16, outBuf_.getLength()); // length(IA_NA) = 16
+
+ // Check if pack worked properly:
+ InputBuffer out(outBuf_.getData(), outBuf_.getLength());
+
+ // - if option type is correct
+ EXPECT_EQ(type, out.readUint16());
+
+ // - if option length is correct
+ EXPECT_EQ(12, out.readUint16());
+
+ // - if iaid is correct
+ EXPECT_EQ(0xa1a2a3a4, out.readUint32() );
+
+ // - if T1 is correct
+ EXPECT_EQ(0x81020304, out.readUint32() );
+
+ // - if T1 is correct
+ EXPECT_EQ(0x84030201, out.readUint32() );
+
+ EXPECT_NO_THROW(opt.reset());
+ }
+
+ OptionBuffer buf_;
+ OutputBuffer outBuf_;
+};
+
+TEST_F(Option6IATest, basic) {
+ checkIA(D6O_IA_NA);
+}
+
+TEST_F(Option6IATest, pdBasic) {
+ checkIA(D6O_IA_PD);
+}
+
+// Check that this class cannot be used for IA_TA (IA_TA has no T1, T2 fields
+// and people tend to think that if it's good for IA_NA and IA_PD, it can
+// be used for IA_TA as well and that is not true)
+TEST_F(Option6IATest, taForbidden) {
+ EXPECT_THROW(Option6IA(D6O_IA_TA, buf_.begin(), buf_.begin() + 50),
+ BadValue);
+
+ EXPECT_THROW(Option6IA(D6O_IA_TA, 123), BadValue);
+}
+
+// Check that getters/setters are working as expected.
+TEST_F(Option6IATest, simple) {
+ scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 1234));
+
+ // Check that the values are really different than what we are about
+ // to set them to.
+ EXPECT_NE(2345, ia->getT1());
+ EXPECT_NE(3456, ia->getT2());
+
+ ia->setT1(2345);
+ ia->setT2(3456);
+
+ EXPECT_EQ(Option::V6, ia->getUniverse());
+ EXPECT_EQ(D6O_IA_NA, ia->getType());
+ EXPECT_EQ(1234, ia->getIAID());
+ EXPECT_EQ(2345, ia->getT1());
+ EXPECT_EQ(3456, ia->getT2());
+
+ ia->setIAID(890);
+ EXPECT_EQ(890, ia->getIAID());
+
+ EXPECT_NO_THROW(ia.reset());
+}
+
+// test if the option can build suboptions
+TEST_F(Option6IATest, suboptionsPack) {
+
+ scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 0x13579ace));
+ ia->setT1(0x2345);
+ ia->setT2(0x3456);
+
+ OptionPtr sub1(new Option(Option::V6, 0xcafe));
+
+ boost::shared_ptr<Option6IAAddr> addr1(
+ new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000));
+
+ ia->addOption(sub1);
+ ia->addOption(addr1);
+
+ ASSERT_EQ(28, addr1->len());
+ ASSERT_EQ(4, sub1->len());
+ ASSERT_EQ(48, ia->len());
+
+ // This contains expected on-wire format
+ uint8_t expected[] = {
+ D6O_IA_NA/256, D6O_IA_NA%256, // type
+ 0, 44, // length
+ 0x13, 0x57, 0x9a, 0xce, // iaid
+ 0, 0, 0x23, 0x45, // T1
+ 0, 0, 0x34, 0x56, // T2
+
+ // iaaddr suboption
+ D6O_IAADDR/256, D6O_IAADDR%256, // type
+ 0, 24, // len
+ 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
+ 0, 0, 0x50, 0, // preferred-lifetime
+ 0, 0, 0x70, 0, // valid-lifetime
+
+ // suboption
+ 0xca, 0xfe, // type
+ 0, 0 // len
+ };
+
+ ia->pack(outBuf_);
+
+ ASSERT_EQ(48, outBuf_.getLength());
+ EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 48));
+ EXPECT_NO_THROW(ia.reset());
+}
+
+// test if IA_PD option can build IAPREFIX suboptions
+TEST_F(Option6IATest, pdSuboptionsPack) {
+
+ // Let's build IA_PD
+ scoped_ptr<Option6IA> ia;
+ ASSERT_NO_THROW(ia.reset(new Option6IA(D6O_IA_PD, 0x13579ace)));
+ ia->setT1(0x2345);
+ ia->setT2(0x3456);
+
+ // Put some dummy option in it
+ OptionPtr sub1(new Option(Option::V6, 0xcafe));
+
+ // Put a valid IAPREFIX option in it
+ boost::shared_ptr<Option6IAPrefix> addr1(
+ new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1234:5678::abcd"),
+ 91, 0x5000, 0x7000));
+
+ ia->addOption(sub1);
+ ia->addOption(addr1);
+
+ ASSERT_EQ(29, addr1->len());
+ ASSERT_EQ(4, sub1->len());
+ ASSERT_EQ(49, ia->len());
+
+ uint8_t expected[] = {
+ D6O_IA_PD/256, D6O_IA_PD%256, // type
+ 0, 45, // length
+ 0x13, 0x57, 0x9a, 0xce, // iaid
+ 0, 0, 0x23, 0x45, // T1
+ 0, 0, 0x34, 0x56, // T2
+
+ // iaprefix suboption
+ D6O_IAPREFIX/256, D6O_IAPREFIX%256, // type
+ 0, 25, // len
+ 0, 0, 0x50, 0, // preferred-lifetime
+ 0, 0, 0x70, 0, // valid-lifetime
+ 91, // prefix length
+ 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
+
+ // suboption
+ 0xca, 0xfe, // type
+ 0, 0 // len
+ };
+
+ ia->pack(outBuf_);
+ ASSERT_EQ(49, outBuf_.getLength());
+
+ EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 49));
+
+ EXPECT_NO_THROW(ia.reset());
+}
+
+// test if option can parse suboptions
+TEST_F(Option6IATest, suboptionsUnpack) {
+ // sizeof (expected) = 48 bytes
+ const uint8_t expected[] = {
+ D6O_IA_NA / 256, D6O_IA_NA % 256, // type
+ 0, 28, // length
+ 0x13, 0x57, 0x9a, 0xce, // iaid
+ 0, 0, 0x23, 0x45, // T1
+ 0, 0, 0x34, 0x56, // T2
+
+ // iaaddr suboption
+ D6O_IAADDR / 256, D6O_IAADDR % 256, // type
+ 0, 24, // len
+ 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
+ 0, 0, 0x50, 0, // preferred-lifetime
+ 0, 0, 0x70, 0, // valid-lifetime
+
+ // suboption
+ 0xca, 0xfe, // type
+ 0, 0 // len
+ };
+ ASSERT_EQ(48, sizeof(expected));
+
+ memcpy(&buf_[0], expected, sizeof(expected));
+
+ scoped_ptr<Option6IA> ia;
+ EXPECT_NO_THROW(
+ ia.reset(new Option6IA(D6O_IA_NA, buf_.begin() + 4,
+ buf_.begin() + sizeof(expected)));
+ );
+ ASSERT_TRUE(ia);
+
+ EXPECT_EQ(D6O_IA_NA, ia->getType());
+ EXPECT_EQ(0x13579ace, ia->getIAID());
+ EXPECT_EQ(0x2345, ia->getT1());
+ EXPECT_EQ(0x3456, ia->getT2());
+
+ OptionPtr subopt = ia->getOption(D6O_IAADDR);
+ ASSERT_NE(OptionPtr(), subopt); // non-NULL
+
+ // Checks for address option
+ Option6IAAddrPtr addr =
+ boost::dynamic_pointer_cast<Option6IAAddr>(subopt);
+ ASSERT_TRUE(addr);
+
+ EXPECT_EQ(D6O_IAADDR, addr->getType());
+ EXPECT_EQ(28, addr->len());
+ EXPECT_EQ(0x5000, addr->getPreferred());
+ EXPECT_EQ(0x7000, addr->getValid());
+ EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText());
+
+ // Checks for dummy option
+ subopt = ia->getOption(0xcafe);
+ ASSERT_TRUE(subopt); // should be non-NULL
+
+ EXPECT_EQ(0xcafe, subopt->getType());
+ EXPECT_EQ(4, subopt->len());
+ // There should be no data at all
+ EXPECT_EQ(0, subopt->getData().size());
+
+ subopt = ia->getOption(1); // get option 1
+ ASSERT_FALSE(subopt); // should be NULL
+
+ EXPECT_NO_THROW(ia.reset());
+}
+
+// This test checks that the IA_NA option is correctly converted to the
+// textual format.
+TEST_F(Option6IATest, toTextNA) {
+ Option6IA ia(D6O_IA_NA, 1234);
+ ia.setT1(200);
+ ia.setT2(300);
+
+ ia.addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::1"),
+ 500, 600)));
+ ia.addOption(OptionPtr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1::2"),
+ 450, 550)));
+
+ EXPECT_EQ("type=00003(IA_NA), len=00068: iaid=1234, t1=200, t2=300,\n"
+ "options:\n"
+ " type=00005(IAADDR), len=00024: address=2001:db8:1::1, "
+ "preferred-lft=500, valid-lft=600\n"
+ " type=00005(IAADDR), len=00024: address=2001:db8:1::2, "
+ "preferred-lft=450, valid-lft=550", ia.toText());
+}
+
+// This test checks that the IA_PD option is correctly converted to the
+// textual format.
+TEST_F(Option6IATest, toTextPD) {
+ Option6IA ia(D6O_IA_PD, 2345);
+ ia.setT1(200);
+ ia.setT2(300);
+
+ ia.addOption(OptionPtr(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"),
+ 72, 500, 600)));
+ ia.addOption(OptionPtr(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"),
+ 64, 450, 550)));
+
+ EXPECT_EQ("type=00025(IA_PD), len=00070: iaid=2345, t1=200, t2=300,\n"
+ "options:\n"
+ " type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/72, "
+ "preferred-lft=500, valid-lft=600\n"
+ " type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/64, "
+ "preferred-lft=450, valid-lft=550",
+ ia.toText());
+}
+
+}
diff --git a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
new file mode 100644
index 0000000..d748e83
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
@@ -0,0 +1,138 @@
+// 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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option6_iaaddr.h>
+#include <util/buffer.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+class Option6IAAddrTest : public ::testing::Test {
+public:
+ Option6IAAddrTest() : buf_(255), outBuf_(255) {
+ for (unsigned i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+ OptionBuffer buf_;
+ OutputBuffer outBuf_;
+};
+
+TEST_F(Option6IAAddrTest, basic) {
+ for (int i = 0; i < 255; i++) {
+ buf_[i] = 0;
+ }
+ buf_[0] = 0x20;
+ buf_[1] = 0x01;
+ buf_[2] = 0x0d;
+ buf_[3] = 0xb8;
+ buf_[4] = 0x00;
+ buf_[5] = 0x01;
+ buf_[12] = 0xde;
+ buf_[13] = 0xad;
+ buf_[14] = 0xbe;
+ buf_[15] = 0xef; // 2001:db8:1::dead:beef
+
+ buf_[16] = 0x00;
+ buf_[17] = 0x00;
+ buf_[18] = 0x03;
+ buf_[19] = 0xe8; // 1000
+
+ buf_[20] = 0xb2;
+ buf_[21] = 0xd0;
+ buf_[22] = 0x5e;
+ buf_[23] = 0x00; // 3,000,000,000
+
+ // Create an option (unpack content)
+ boost::scoped_ptr<Option6IAAddr> opt(new Option6IAAddr(D6O_IAADDR,
+ buf_.begin(),
+ buf_.begin() + 24));
+
+ // Pack this option
+ opt->pack(outBuf_);
+
+ EXPECT_EQ(28, outBuf_.getLength());
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+
+ // 4 bytes header + 4 bytes content
+ EXPECT_EQ("2001:db8:1::dead:beef", opt->getAddress().toText());
+ EXPECT_EQ(1000, opt->getPreferred());
+ EXPECT_EQ(3000000000U, opt->getValid());
+
+ EXPECT_EQ(D6O_IAADDR, opt->getType());
+
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAAddr::OPTION6_IAADDR_LEN,
+ opt->len());
+
+ // Check if pack worked properly:
+ const uint8_t* out = (const uint8_t*)outBuf_.getData();
+
+ // - if option type is correct
+ EXPECT_EQ(D6O_IAADDR, out[0]*256 + out[1]);
+
+ // - if option length is correct
+ EXPECT_EQ(24, out[2]*256 + out[3]);
+
+ // - if option content is correct
+ EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 24));
+
+ EXPECT_NO_THROW(opt.reset());
+}
+
+/// @todo: Write test for (type, addr, pref, valid) constructor
+/// See option6_iaprefix_unittest.cc for similar test
+
+// Tests if broken usage causes exception to be thrown
+TEST_F(Option6IAAddrTest, negative) {
+
+ // Too short. Minimum length is 24
+ EXPECT_THROW(Option6IAAddr(D6O_IAADDR, buf_.begin(), buf_.begin() + 23),
+ OutOfRange);
+
+ // This option is for IPv6 addresses only
+ EXPECT_THROW(Option6IAAddr(D6O_IAADDR, isc::asiolink::IOAddress("192.0.2.1"),
+ 1000, 2000), BadValue);
+}
+
+// Tests that option can be converted to textual format.
+TEST_F(Option6IAAddrTest, toText) {
+ // Create option without suboptions.
+ Option6IAAddr opt(D6O_IAADDR, IOAddress("2001:db8:1::1"), 300, 400);
+ EXPECT_EQ("type=00005(IAADDR), len=00024: address=2001:db8:1::1,"
+ " preferred-lft=300, valid-lft=400",
+ opt.toText());
+
+ // Add suboptions and make sure they are printed.
+ opt.addOption(OptionPtr(new OptionUint32(Option::V6, 123, 234)));
+ opt.addOption(OptionPtr(new OptionUint32(Option::V6, 222, 333)));
+
+ EXPECT_EQ("type=00005(IAADDR), len=00040: address=2001:db8:1::1,"
+ " preferred-lft=300, valid-lft=400,\noptions:\n"
+ " type=00123, len=00004: 234 (uint32)\n"
+ " type=00222, len=00004: 333 (uint32)",
+ opt.toText());
+
+}
+
+}
diff --git a/src/lib/dhcp/tests/option6_iaprefix_unittest.cc b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc
new file mode 100644
index 0000000..2bd8be3
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_iaprefix_unittest.cc
@@ -0,0 +1,271 @@
+// Copyright (C) 2011-2017 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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option6_iaprefix.h>
+#include <util/buffer.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::asiolink;
+
+namespace {
+class Option6IAPrefixTest : public ::testing::Test {
+public:
+ Option6IAPrefixTest() : buf_(255), out_buf_(255) {
+ for (unsigned i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+
+ /// @brief creates on-wire representation of IAPREFIX option
+ ///
+ /// buf_ field is set up to have IAPREFIX with preferred=1000,
+ /// valid=3000000000 and prefix being 2001:db8:1:0:afaf:0:dead:beef/77
+ void setExampleBuffer() {
+ for (int i = 0; i < 255; i++) {
+ buf_[i] = 0;
+ }
+
+ buf_[ 0] = 0x00;
+ buf_[ 1] = 0x00;
+ buf_[ 2] = 0x03;
+ buf_[ 3] = 0xe8; // preferred lifetime = 1000
+
+ buf_[ 4] = 0xb2;
+ buf_[ 5] = 0xd0;
+ buf_[ 6] = 0x5e;
+ buf_[ 7] = 0x00; // valid lifetime = 3,000,000,000
+
+ buf_[ 8] = 77; // Prefix length = 77
+
+ buf_[ 9] = 0x20;
+ buf_[10] = 0x01;
+ buf_[11] = 0x0d;
+ buf_[12] = 0xb8;
+ buf_[13] = 0x00;
+ buf_[14] = 0x01;
+ buf_[17] = 0xaf;
+ buf_[18] = 0xaf;
+ buf_[21] = 0xde;
+ buf_[22] = 0xad;
+ buf_[23] = 0xbe;
+ buf_[24] = 0xef; // 2001:db8:1:0:afaf:0:dead:beef
+ }
+
+
+ /// @brief Checks whether specified IAPREFIX option meets expected values
+ ///
+ /// To be used with option generated by setExampleBuffer
+ ///
+ /// @param opt IAPREFIX option being tested
+ /// @param expected_type expected option type
+ /// @param expected_length Expected length of the prefix.
+ /// @param expected_address Expected prefix value.
+ void checkOption(Option6IAPrefix& opt, const uint16_t expected_type,
+ const uint8_t expected_length,
+ const IOAddress& expected_address) {
+
+ // Check if all fields have expected values
+ EXPECT_EQ(Option::V6, opt.getUniverse());
+ EXPECT_EQ(expected_type, opt.getType());
+ EXPECT_EQ(expected_address, opt.getAddress());
+ EXPECT_EQ(1000, opt.getPreferred());
+ EXPECT_EQ(3000000000U, opt.getValid());
+ // uint8_t is often represented as a character type (char). Convert it
+ // to integer so as it is logged as a numeric value instead.
+ EXPECT_EQ(static_cast<int>(expected_length),
+ static_cast<int>(opt.getLength()));
+
+ // 4 bytes header + 25 bytes content
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAPrefix::OPTION6_IAPREFIX_LEN,
+ opt.len());
+ }
+
+ /// @brief Checks whether content of output buffer is correct
+ ///
+ /// Output buffer is expected to be filled with an option matching
+ /// buf_ content as defined in setExampleBuffer().
+ ///
+ /// @param expected_type expected option type
+ void checkOutputBuffer(uint16_t expected_type) {
+ // Check if pack worked properly:
+ const uint8_t* out = static_cast<const uint8_t*>(out_buf_.getData());
+
+ // - if option type is correct
+ EXPECT_EQ(expected_type, out[0]*256 + out[1]);
+
+ // - if option length is correct
+ EXPECT_EQ(25, out[2]*256 + out[3]);
+
+ // - if option content is correct
+ EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 25));
+ }
+
+ OptionBuffer buf_;
+ OutputBuffer out_buf_;
+};
+
+// Tests if a received option is parsed correctly. For the prefix length between
+// 0 and 128 the non-significant bits should be set to 0.
+TEST_F(Option6IAPrefixTest, parseShort) {
+
+ setExampleBuffer();
+
+ // Create an option (unpack content)
+ boost::scoped_ptr<Option6IAPrefix> opt;
+ ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(),
+ buf_.begin() + 25)));
+ ASSERT_TRUE(opt);
+
+ // Pack this option
+ opt->pack(out_buf_);
+ EXPECT_EQ(29, out_buf_.getLength());
+
+ // The non-significant bits (above 77) of the received prefix should be
+ // set to zero.
+ checkOption(*opt, D6O_IAPREFIX, 77, IOAddress("2001:db8:1:0:afa8::"));
+
+ // Set non-significant bits in the reference buffer to 0, so as the buffer
+ // can be directly compared with the option buffer.
+ buf_[18] = 0xa8;
+ buf_.insert(buf_.begin() + 19, 5, 0);
+ checkOutputBuffer(D6O_IAPREFIX);
+
+ // Check that option can be disposed safely
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Tests if a received option holding prefix of 128 bits is parsed correctly.
+TEST_F(Option6IAPrefixTest, parseLong) {
+
+ setExampleBuffer();
+ // Set prefix length to the maximal value.
+ buf_[8] = 128;
+
+ // Create an option (unpack content)
+ boost::scoped_ptr<Option6IAPrefix> opt;
+ ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(),
+ buf_.begin() + 25)));
+ ASSERT_TRUE(opt);
+
+ // Pack this option
+ opt->pack(out_buf_);
+ EXPECT_EQ(29, out_buf_.getLength());
+
+ checkOption(*opt, D6O_IAPREFIX, 128,
+ IOAddress("2001:db8:1:0:afaf:0:dead:beef"));
+
+ checkOutputBuffer(D6O_IAPREFIX);
+
+ // Check that option can be disposed safely
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Check that the prefix having length of zero is represented as a "::".
+TEST_F(Option6IAPrefixTest, parseZero) {
+ setExampleBuffer();
+ // Set prefix length to 0.
+ buf_[8] = 0;
+
+ // Create an option (unpack content)
+ boost::scoped_ptr<Option6IAPrefix> opt;
+ ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(D6O_IAPREFIX, buf_.begin(),
+ buf_.begin() + 25)));
+ ASSERT_TRUE(opt);
+
+ // Pack this option
+ opt->pack(out_buf_);
+ EXPECT_EQ(29, out_buf_.getLength());
+
+ checkOption(*opt, D6O_IAPREFIX, 0, IOAddress("::"));
+
+ // Fill the address in the reference buffer with zeros.
+ buf_.insert(buf_.begin() + 9, 16, 0);
+ checkOutputBuffer(D6O_IAPREFIX);
+
+ // Check that option can be disposed safely
+ EXPECT_NO_THROW(opt.reset());
+}
+
+
+// Checks whether a new option can be built correctly
+TEST_F(Option6IAPrefixTest, build) {
+
+ boost::scoped_ptr<Option6IAPrefix> opt;
+ setExampleBuffer();
+
+ ASSERT_NO_THROW(opt.reset(new Option6IAPrefix(12345,
+ IOAddress("2001:db8:1:0:afaf:0:dead:beef"), 77,
+ 1000, 3000000000u)));
+ ASSERT_TRUE(opt);
+
+ checkOption(*opt, 12345, 77, IOAddress("2001:db8:1:0:afaf:0:dead:beef"));
+
+ // Check if we can build it properly
+ EXPECT_NO_THROW(opt->pack(out_buf_));
+ EXPECT_EQ(29, out_buf_.getLength());
+ checkOutputBuffer(12345);
+
+ // Check that option can be disposed safely
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Checks negative cases
+TEST_F(Option6IAPrefixTest, negative) {
+
+ // Truncated option (at least 25 bytes is needed)
+ EXPECT_THROW(Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), buf_.begin() + 24),
+ OutOfRange);
+
+ // Empty option
+ EXPECT_THROW(Option6IAPrefix(D6O_IAPREFIX, buf_.begin(), buf_.begin()),
+ OutOfRange);
+
+ // This is for IPv6 prefixes only
+ EXPECT_THROW(Option6IAPrefix(12345, IOAddress("192.0.2.1"), 77, 1000, 2000),
+ BadValue);
+
+ // Prefix length can't be larger than 128
+ EXPECT_THROW(Option6IAPrefix(12345, IOAddress("2001:db8:1::"),
+ 255, 1000, 2000),
+ BadValue);
+}
+
+// Checks if the option is converted to textual format correctly.
+TEST_F(Option6IAPrefixTest, toText) {
+ // Create option without suboptions.
+ Option6IAPrefix opt(D6O_IAPREFIX, IOAddress("2001:db8:1::"), 64, 300, 400);
+ EXPECT_EQ("type=00026(IAPREFIX), len=00025: prefix=2001:db8:1::/64,"
+ " preferred-lft=300, valid-lft=400",
+ opt.toText());
+
+ // Add suboptions and make sure they are printed.
+ opt.addOption(OptionPtr(new OptionUint32(Option::V6, 123, 234)));
+ opt.addOption(OptionPtr(new OptionUint32(Option::V6, 222, 333)));
+
+ EXPECT_EQ("type=00026(IAPREFIX), len=00041: prefix=2001:db8:1::/64,"
+ " preferred-lft=300, valid-lft=400,\noptions:\n"
+ " type=00123, len=00004: 234 (uint32)\n"
+ " type=00222, len=00004: 333 (uint32)",
+ opt.toText());
+}
+
+}
diff --git a/src/lib/dhcp/tests/option6_pdexclude_unittest.cc b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc
new file mode 100644
index 0000000..b119fc2
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_pdexclude_unittest.cc
@@ -0,0 +1,170 @@
+// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC")
+//
+// Author: Andrei Pavel <andrei.pavel@qualitance.com>
+//
+// 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 <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/option6_pdexclude.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace asiolink;
+
+namespace {
+
+// Prefix constants used in unit tests.
+const IOAddress v4("192.0.2.0");
+const IOAddress bee0("2001:db8:dead:bee0::");
+const IOAddress beef("2001:db8:dead:beef::");
+const IOAddress cafe("2001:db8:dead:cafe::");
+const IOAddress beef01("2001:db8:dead:beef::01");
+
+// This test verifies that the constructor sets parameters appropriately.
+TEST(Option6PDExcludeTest, constructor) {
+ Option6PDExclude option = Option6PDExclude(beef, 56, beef01, 60);
+
+ EXPECT_EQ(bee0, option.getExcludedPrefix(beef, 56));
+ EXPECT_EQ(60, option.getExcludedPrefixLength());
+ EXPECT_EQ("E0", util::encode::encodeHex(option.getExcludedPrefixSubnetID()));
+
+ // Total length is a sum of option header length, excluded prefix
+ // length (always 1 byte) and delegated prefix length - excluded prefix
+ // length rounded to bytes.
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + 1 + 1, option.len());
+
+ // v4 prefix is not accepted.
+ EXPECT_THROW(Option6PDExclude(v4, 56, beef01, 64), BadValue);
+ EXPECT_THROW(Option6PDExclude(beef, 56, v4, 64), BadValue);
+ // Length greater than 128 is not accepted.
+ EXPECT_THROW(Option6PDExclude(beef, 128, beef01, 129), BadValue);
+ // Excluded prefix length must be greater than delegated prefix length.
+ EXPECT_THROW(Option6PDExclude(beef, 56, beef01, 56), BadValue);
+ // Both prefixes shifted by 56 must be equal (see RFC6603, section 4.2).
+ EXPECT_THROW(Option6PDExclude(cafe, 56, beef01, 64), BadValue);
+}
+
+// This test verifies that on-wire format of the Prefix Exclude option is
+// created properly.
+TEST(Option6PDExcludeTest, pack) {
+ // Expected wire format of the option.
+ const uint8_t expected_data[] = {
+ 0x00, 0x43, // option code 67
+ 0x00, 0x02, // option length 2
+ 0x3F, 0x70 // excluded prefix length 63 + subnet id
+ };
+ std::vector<uint8_t> expected_vec(expected_data,
+ expected_data + sizeof(expected_data));
+ // Generate wire format of the option.
+ util::OutputBuffer buf(128);
+ Option6PDExcludePtr option;
+ ASSERT_NO_THROW(option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
+ 59,
+ IOAddress("2001:db8:dead:beef::"),
+ 63)));
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Check that size matches.
+ ASSERT_EQ(expected_vec.size(), buf.getLength());
+
+ // Check that the generated wire format is correct.
+ const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+ std::vector<uint8_t> vec(data, data + buf.getLength());
+ ASSERT_TRUE(std::equal(vec.begin(), vec.end(), expected_vec.begin()));
+}
+
+// This test verifies parsing option wire format with subnet id of
+// 1 byte.
+TEST(Option6PDExcludeTest, unpack1ByteSubnetId) {
+ const uint8_t data[] = {
+ 0x00, 0x43, // option code 67
+ 0x00, 0x02, // option length 2
+ 0x40, 0x78 // excluded prefix length 60 + subnet id
+ };
+ std::vector<uint8_t> vec(data, data + sizeof(data));
+
+ // Parse option.
+ Option6PDExcludePtr option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6PDExclude(vec.begin() + 4, vec.end()))
+ );
+
+ // Make sure that the option has been parsed correctly.
+ EXPECT_EQ("2001:db8:dead:beef::",
+ option->getExcludedPrefix(IOAddress("2001:db8:dead:bee0::1"), 59).toText());
+ EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
+
+// This test verifies parsing option wire format with subnet id of
+// 2 bytes.
+TEST(Option6PDExcludeTest, unpack2ByteSubnetId) {
+ const uint8_t data[] = {
+ 0x00, 0x43, // option code 67
+ 0x00, 0x02, // option length
+ 0x40, 0xbe, 0xef // excluded prefix length 60 + subnet id
+ };
+ std::vector<uint8_t> vec(data, data + sizeof(data));
+
+ // Parse option.
+ Option6PDExcludePtr option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6PDExclude(vec.begin() + 4, vec.end()))
+ );
+
+ // Make sure that the option has been parsed correctly.
+ EXPECT_EQ("2001:db8:dead:beef::",
+ option->getExcludedPrefix(IOAddress("2001:db8:dead::"), 48).toText());
+ EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
+
+// This test verifies that errors are reported when option buffer contains
+// invalid option data.
+TEST(Option6PDExcludeTest, unpackErrors) {
+ const uint8_t data[] = {
+ 0x00, 0x43,
+ 0x00, 0x02,
+ 0x40, 0x78
+ };
+ std::vector<uint8_t> vec(data, data + sizeof(data));
+
+ // Option has no IPv6 subnet id.
+ EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end() - 1),
+ BadValue);
+
+ // IPv6 subnet id is 0.
+ vec[4] = 0x00;
+ EXPECT_THROW(Option6PDExclude(vec.begin() + 4, vec.end()),
+ BadValue);
+}
+
+// This test verifies conversion of the Prefix Exclude option to the
+// textual format.
+TEST(Option6PDExcludeTest, toText) {
+ Option6PDExclude option(bee0, 59, beef, 64);
+ EXPECT_EQ("type=00067, len=00002: excluded-prefix-len=64, subnet-id=0x78",
+ option.toText());
+}
+
+// This test verifies calculation of the Prefix Exclude option length.
+TEST(Option6PDExcludeTest, len) {
+ Option6PDExcludePtr option;
+ // The IPv6 subnet id is 2 bytes long. Hence the total length is
+ // 2 bytes (option code) + 2 bytes (option length) + 1 byte
+ // (excluded prefix length) + 2 bytes (IPv6 subnet id) = 7 bytes.
+ ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 48, beef, 64)));
+ EXPECT_EQ(7, option->len());
+
+ // IPv6 subnet id is 1 byte long. The total length is 6.
+ ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 59, beef, 64)));
+ EXPECT_EQ(6, option->len());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option6_status_code_unittest.cc b/src/lib/dhcp/tests/option6_status_code_unittest.cc
new file mode 100644
index 0000000..34e7887
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_status_code_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (C) 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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_status_code.h>
+#include <gtest/gtest.h>
+#include <cstring>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that the option can be created and that the
+// accessor methods return correct values used for the object
+// construction.
+TEST(Option6StatusCodeTest, accessors) {
+ Option6StatusCode status1(STATUS_NoAddrsAvail, "Sorry, NoAddrsAvail");
+ EXPECT_EQ(STATUS_NoAddrsAvail, status1.getStatusCode());
+ EXPECT_EQ("Sorry, NoAddrsAvail", status1.getStatusMessage());
+
+ Option6StatusCode status2(STATUS_NoBinding, "There is NoBinding");
+ EXPECT_EQ(STATUS_NoBinding, status2.getStatusCode());
+ EXPECT_EQ("There is NoBinding", status2.getStatusMessage());
+}
+
+// This test verifies that the status code and status message may
+// be modified.
+TEST(Option6StatusCodeTest, modifiers) {
+ Option6StatusCode status(STATUS_NoAddrsAvail, "Sorry, NoAddrsAvail");
+ ASSERT_EQ(STATUS_NoAddrsAvail, status.getStatusCode());
+ ASSERT_EQ("Sorry, NoAddrsAvail", status.getStatusMessage());
+
+ ASSERT_NO_THROW(status.setStatusCode(STATUS_Success));
+ ASSERT_NO_THROW(status.setStatusMessage("Success"));
+
+ EXPECT_EQ(STATUS_Success, status.getStatusCode());
+ EXPECT_EQ("Success", status.getStatusMessage());
+}
+
+// This test verifies that the option returns its length correctly.
+TEST(Option6StatusCodeTest, length) {
+ Option6StatusCode status(STATUS_Success, "");
+ EXPECT_EQ(6, status.len());
+
+ ASSERT_NO_THROW(status.setStatusMessage("non-empty message"));
+ EXPECT_EQ(23, status.len());
+}
+
+// This test verifies that the option can be encoded into the wire
+// format.
+TEST(Option6StatusCodeTest, pack) {
+ Option6StatusCode status(STATUS_NoBinding, "text");
+ util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(status.pack(buf));
+
+ const uint8_t ref[] = {
+ 0, 13, // Option code is 13
+ 0, 6, // Length is 6
+ 0, 3, // NoBinding
+ 't', 'e', 'x', 't'
+ };
+
+ ASSERT_EQ(sizeof(ref), buf.getLength());
+ const void* packed = buf.getData();
+ EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), packed, sizeof(ref)));
+}
+
+// This test verifies that the option can be encoded into the
+// wire format when the status message is empty.
+TEST(Option6StatusCodeTest, packEmptyStatusMessage) {
+ Option6StatusCode status(STATUS_NoAddrsAvail, "");
+ util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(status.pack(buf));
+
+ const uint8_t ref[] = {
+ 0, 13, // Option code is 13
+ 0, 2, // Length is 2
+ 0, 2, // NoAddrsAvail
+ };
+
+ ASSERT_EQ(sizeof(ref), buf.getLength());
+ const void* packed = buf.getData();
+ EXPECT_EQ(0, memcmp(static_cast<const void*>(ref), packed, sizeof(ref)));
+}
+
+
+// This test verifies that the option can be parsed from the wire
+// format.
+TEST(Option6StatusCodeTest, unpack) {
+ const uint8_t wire_data[] = {
+ 0, 1, // status code = UnspecFail
+ 'x', 'y', 'z', // short text: xyz
+ };
+ OptionBuffer buf(wire_data, wire_data + sizeof(wire_data));
+
+ // Create option from buffer.
+ Option6StatusCodePtr status;
+ ASSERT_NO_THROW(status.reset(new Option6StatusCode(buf.begin(), buf.end())));
+
+ // Verify that the data was parsed correctly.
+ EXPECT_EQ(STATUS_UnspecFail, status->getStatusCode());
+ EXPECT_EQ("xyz", status->getStatusMessage());
+
+ // Remove the status message and leave only the status code.
+ buf.resize(2);
+ // Modify the status code.
+ buf[1] = 0;
+
+ ASSERT_NO_THROW(status.reset(new Option6StatusCode(buf.begin(), buf.end())));
+ EXPECT_EQ(STATUS_Success, status->getStatusCode());
+ EXPECT_TRUE(status->getStatusMessage().empty());
+}
+
+// This test verifies that the option data can be presented
+// in the textual form.
+TEST(Option6StatusCodeTest, dataToText) {
+ Option6StatusCode status(STATUS_NoBinding, "Sorry, no binding");
+ EXPECT_EQ("NoBinding(3) \"Sorry, no binding\"",
+ status.dataToText());
+}
+
+// This test verifies that the option can be presented in the
+// textual form.
+TEST(Option6StatusCodeTest, toText) {
+ Option6StatusCode status(STATUS_NoAddrsAvail, "Sorry, no address");
+ EXPECT_EQ("type=00013, len=00019: NoAddrsAvail(2) \"Sorry, no address\"",
+ status.toText());
+
+ Option6StatusCode status_empty(STATUS_NoBinding, "");
+ EXPECT_EQ("type=00013, len=00002: NoBinding(3) (no status message)",
+ status_empty.toText());
+}
+
+
+/// @brief Test that the status code name is returned correctly.
+///
+/// @param expected_name Expected name.
+/// @param status_code Status code for which test is performed.
+void testStatusName(const std::string& expected_name,
+ const uint16_t status_code) {
+ Option6StatusCode status(status_code, "some text");
+ EXPECT_EQ(expected_name, status.getStatusCodeName());
+}
+
+// This test verifies that the status code name is
+// returned correctly.
+TEST(Option6StatusCodeTest, getStatusCodeName) {
+ testStatusName("Success", STATUS_Success);
+ testStatusName("UnspecFail", STATUS_UnspecFail);
+ testStatusName("NoAddrsAvail", STATUS_NoAddrsAvail);
+ testStatusName("NoBinding", STATUS_NoBinding);
+ testStatusName("NotOnLink", STATUS_NotOnLink);
+ testStatusName("UseMulticast", STATUS_UseMulticast);
+ testStatusName("NoPrefixAvail", STATUS_NoPrefixAvail);
+ testStatusName("UnknownQueryType", STATUS_UnknownQueryType);
+ testStatusName("MalformedQuery", STATUS_MalformedQuery);
+ testStatusName("NotConfigured", STATUS_NotConfigured);
+ testStatusName("NotAllowed", STATUS_NotAllowed);
+ testStatusName("(unknown status code)", 1234);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_copy_unittest.cc b/src/lib/dhcp/tests/option_copy_unittest.cc
new file mode 100644
index 0000000..9d7a385
--- /dev/null
+++ b/src/lib/dhcp/tests/option_copy_unittest.cc
@@ -0,0 +1,790 @@
+// Copyright (C) 2016-2021 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option6_status_code.h>
+#include <util/buffer.h>
+
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Type of the "copy" operation to be performed in a test.
+///
+/// Possible operations are:
+/// - copy construction,
+/// - cloning with Option::clone,
+/// - assignment.
+enum OpType {
+ COPY,
+ CLONE,
+ ASSIGN
+};
+
+/// @brief Generic test for deep copy of an option.
+///
+/// This test can use one of the three supported operations to deep copy
+/// an option: copy construction, cloning or assignment.
+///
+/// After copying the option the following parameters checked if they
+/// have been copied (copied by the Option class):
+/// - universe,
+/// - option type,
+/// - encapsulated space,
+/// - data.
+///
+/// This test also checks that the sub options have been copied by checking
+/// that:
+/// - options' types match,
+/// - binary representations are equal,
+/// - pointers to the options are unequal (to make sure that the option has
+/// been copied, rather than the pointer).
+///
+/// @param op_type Copy operation to be performed.
+/// @param option Source option.
+/// @param option_copy Destination option. Note that this option may be
+/// initially set to a non-null value. For the "copy" and "clone" operations
+/// the pointer will be reset, so there is no sense to initialize this
+/// object to a non-null value. However, for the assignment testing it is
+/// recommended to initialize the option_copy to point to an option having
+/// different parameters to verify that all parameters have been overridden
+/// by the assignment operation.
+template<typename OptionType>
+void testCopyAssign(const OpType& op_type,
+ boost::shared_ptr<OptionType>& option,
+ boost::shared_ptr<OptionType>& option_copy) {
+ // Set the encapsulated to 'foo' because tests usually don't set that
+ // value.
+ option->setEncapsulatedSpace("foo");
+
+ // Create two sub options of different types to later check that they
+ // are copied.
+ OptionUint16Ptr sub1 = OptionUint16Ptr(new OptionUint16(Option::V4, 10, 234));
+ Option4AddrLstPtr sub2 =
+ Option4AddrLstPtr(new Option4AddrLst(11, IOAddress("192.0.2.3")));
+ option->addOption(sub1);
+ option->addOption(sub2);
+
+ // Copy option by copy construction, cloning or assignment.
+ switch (op_type) {
+ case COPY:
+ option_copy.reset(new OptionType(*option));
+ break;
+ case CLONE:
+ option_copy = boost::dynamic_pointer_cast<OptionType>(option->clone());
+ ASSERT_TRUE(option_copy);
+ break;
+ case ASSIGN:
+ option_copy->setEncapsulatedSpace("bar");
+ *option_copy = *option;
+ break;
+ default:
+ ADD_FAILURE() << "unsupported operation";
+ return;
+ }
+
+ // Verify that basic parameters have been copied.
+ EXPECT_EQ(option->getUniverse(), option_copy->getUniverse());
+ EXPECT_EQ(option->getType(), option_copy->getType());
+ EXPECT_EQ(option->len(), option_copy->len());
+ EXPECT_EQ(option->getEncapsulatedSpace(), option_copy->getEncapsulatedSpace());
+ EXPECT_TRUE(std::equal(option->getData().begin(), option->getData().end(),
+ option_copy->getData().begin()));
+
+ // Retrieve sub options so as they can be compared.
+ const OptionCollection& option_subs = option->getOptions();
+ const OptionCollection& option_copy_subs = option_copy->getOptions();
+ ASSERT_EQ(option_subs.size(), option_copy_subs.size());
+
+ // Iterate over source options.
+ OptionCollection::const_iterator it_copy = option_copy_subs.begin();
+ for (OptionCollection::const_iterator it = option_subs.begin();
+ it != option_subs.end(); ++it, ++it_copy) {
+ // The option codes should be equal in both containers.
+ EXPECT_EQ(it->first, it_copy->first);
+ // Pointers must be unequal because the expectation is that options
+ // are copied, rather than pointers.
+ EXPECT_NE(it->second, it_copy->second);
+ Option* opt_ptr = it->second.get();
+ Option* opt_copy_ptr = it_copy->second.get();
+ // The C++ types must match.
+ EXPECT_TRUE(typeid(*opt_ptr) == typeid(*opt_copy_ptr));
+ }
+
+ // Final check is to compare their binary representations.
+ std::vector<uint8_t> buf = option->toBinary(true);
+ std::vector<uint8_t> buf_copy = option_copy->toBinary(true);
+
+ ASSERT_EQ(buf.size(), buf_copy.size());
+ EXPECT_TRUE(std::equal(buf_copy.begin(), buf_copy.end(), buf.begin()));
+}
+
+// **************************** Option ***************************
+
+/// @brief Test deep copy of option encapsulated by Option type.
+///
+/// @param op_type Copy operation type.
+void testOption(const OpType& op_type) {
+ OptionBuffer buf(10, 1);
+ OptionPtr option(new Option(Option::V4, 1, buf));
+ OptionPtr option_copy(new Option(Option::V6, 1000));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Save binary representation of the original option. We will
+ // be later comparing it with a copied option to make sure that
+ // modification of the original option doesn't affect the copy.
+ std::vector<uint8_t> binary_copy = option_copy->toBinary(true);
+
+ // Modify the original option.
+ OptionBuffer buf_modified(10, 2);
+ option->setData(buf_modified.begin(), buf_modified.end());
+
+ // Retrieve the binary representation of the copy to verify that
+ // it hasn't been modified.
+ std::vector<uint8_t> binary_copy_after = option_copy->toBinary(true);
+
+ ASSERT_EQ(binary_copy.size(), binary_copy_after.size());
+ EXPECT_TRUE(std::equal(binary_copy_after.begin(), binary_copy_after.end(),
+ binary_copy.begin()));
+}
+
+TEST(OptionCopyTest, optionConstructor) {
+ testOption(COPY);
+}
+
+TEST(OptionCopyTest, optionClone) {
+ testOption(CLONE);
+}
+
+TEST(OptionCopyTest, optionAssignment) {
+ testOption(ASSIGN);
+}
+
+// **************************** OptionInt ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionInt type.
+///
+/// @param op_type Copy operation type.
+void testOptionInt(const OpType& op_type) {
+ OptionUint16Ptr option(new OptionUint16(Option::V4, 1, 12345));
+ OptionUint16Ptr option_copy(new OptionUint16(Option::V6, 10, 11111));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify value in the original option.
+ option->setValue(9);
+
+ // The value in the copy should not be affected.
+ EXPECT_EQ(12345, option_copy->getValue());
+}
+
+TEST(OptionCopyTest, optionIntConstructor) {
+ testOptionInt(COPY);
+}
+
+TEST(OptionCopyTest, optionIntClone) {
+ testOptionInt(CLONE);
+}
+
+TEST(OptionCopyTest, optionIntAssignment) {
+ testOptionInt(ASSIGN);
+}
+
+// ************************* OptionIntArray ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionIntArray type.
+///
+/// @param op_type Copy operation type.
+void testOptionIntArray(const OpType& op_type) {
+ OptionUint32ArrayPtr option(new OptionUint32Array(Option::V4, 1));;
+ option->addValue(2345);
+ option->addValue(3456);
+ OptionUint32ArrayPtr option_copy(new OptionUint32Array(Option::V6, 10));
+ option_copy->addValue(5678);
+ option_copy->addValue(6789);
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the values in the original option.
+ option->setValues(std::vector<uint32_t>(2, 7));
+
+ // The values in the copy should not be affected.
+ std::vector<uint32_t> values_copy = option_copy->getValues();
+ ASSERT_EQ(2, values_copy.size());
+ EXPECT_EQ(2345, values_copy[0]);
+ EXPECT_EQ(3456, values_copy[1]);
+}
+
+TEST(OptionCopyTest, optionIntArrayConstructor) {
+ testOptionIntArray(COPY);
+}
+
+TEST(OptionCopyTest, optionIntArrayClone) {
+ testOptionIntArray(CLONE);
+}
+
+TEST(OptionCopyTest, optionIntArrayAssignment) {
+ testOptionIntArray(ASSIGN);
+}
+
+// ************************* Option4AddrLst ***************************
+
+/// @brief Test deep copy of option encapsulated by Option4AddrLst or
+/// Option6AddrLst type.
+///
+/// @param op_type Copy operation type.
+/// @param option_address Address carried in the source option.
+/// @param option_copy_address Address carried in the destination option.
+/// @param option_modified_address Address to which the original address
+/// is modified to check that this modification doesn't affect option
+/// copy.
+/// @tparam OptionType Option4AddrLst or Option6AddrLst.
+template<typename OptionType>
+void testOptionAddrLst(const OpType& op_type,
+ const IOAddress& option_address,
+ const IOAddress& option_copy_address,
+ const IOAddress& option_modified_address) {
+ typedef boost::shared_ptr<OptionType> OptionTypePtr;
+ OptionTypePtr option(new OptionType(1, option_address));
+ OptionTypePtr option_copy(new OptionType(10, option_copy_address));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the address in the original option.
+ option->setAddress(option_modified_address);
+
+ // The address in the copy should not be affected.
+ typename OptionType::AddressContainer addrs_copy = option_copy->getAddresses();
+ ASSERT_EQ(1, addrs_copy.size());
+ EXPECT_EQ(option_address.toText(), addrs_copy[0].toText());
+}
+
+/// @brief Test deep copy of option encapsulated by Option4AddrLst type.
+///
+/// @param op_type Copy operation type.
+void testOption4AddrLst(const OpType& op_type) {
+ testOptionAddrLst<Option4AddrLst>(op_type,
+ IOAddress("127.0.0.1"),
+ IOAddress("192.0.2.111"),
+ IOAddress("127.0.0.1"));
+}
+
+TEST(OptionCopyTest, option4AddrLstConstructor) {
+ testOption4AddrLst(COPY);
+}
+
+TEST(OptionCopyTest, option4AddrLstClone) {
+ testOption4AddrLst(CLONE);
+}
+
+TEST(OptionCopyTest, option4AddrLstAssignment) {
+ testOption4AddrLst(ASSIGN);
+}
+
+// ************************* Option6AddrLst ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6AddrLst type.
+///
+/// @param op_type Copy operation type.
+void testOption6AddrLst(const OpType& op_type) {
+ testOptionAddrLst<Option6AddrLst>(op_type,
+ IOAddress("2001:db8:1::2"),
+ IOAddress("3001::cafe"),
+ IOAddress("3000:1::1"));
+}
+
+TEST(OptionCopyTest, option6AddrLstConstructor) {
+ testOption6AddrLst(COPY);
+}
+
+TEST(OptionCopyTest, option6AddrLstClone) {
+ testOption6AddrLst(CLONE);
+}
+
+TEST(OptionCopyTest, option6AddrLstAssignment) {
+ testOption6AddrLst(ASSIGN);
+}
+
+// *************************** Option6IA ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6IA type.
+///
+/// @param op_type Copy operation type.
+void testOption6IA(const OpType& op_type) {
+ Option6IAPtr option(new Option6IA(D6O_IA_NA, 1234));
+ option->setT1(1000);
+ option->setT2(2000);
+ Option6IAPtr option_copy(new Option6IA(D6O_IA_PD, 5678));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the values in the original option.
+ option->setT1(3000);
+ option->setT2(4000);
+ option->setIAID(5678);
+
+ // The values in the copy should not be affected.
+ EXPECT_EQ(1000, option_copy->getT1());
+ EXPECT_EQ(2000, option_copy->getT2());
+ EXPECT_EQ(1234, option_copy->getIAID());
+}
+
+TEST(OptionCopyTest, option6IAConstructor) {
+ testOption6IA(COPY);
+}
+
+TEST(OptionCopyTest, option6IAClone) {
+ testOption6IA(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAAssignment) {
+ testOption6IA(ASSIGN);
+}
+
+// *************************** Option6IAAddr ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6IAAddr type.
+///
+/// @param op_type Copy operation type.
+void testOption6IAAddr(const OpType& op_type) {
+ Option6IAAddrPtr option(new Option6IAAddr(D6O_IAADDR,
+ IOAddress("2001:db8:1::1"),
+ 60, 90));
+ Option6IAAddrPtr option_copy(new Option6IAAddr(D6O_IAADDR,
+ IOAddress("2001:db8:1::2"),
+ 50, 80));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the values in the original option.
+ option->setAddress(IOAddress("2001:db8:1::3"));
+ option->setPreferred(1000);
+ option->setValid(2000);
+
+ // The values in the copy should not be affected.
+ EXPECT_EQ("2001:db8:1::1", option_copy->getAddress().toText());
+ EXPECT_EQ(60, option_copy->getPreferred());
+ EXPECT_EQ(90, option_copy->getValid());
+}
+
+TEST(OptionCopyTest, option6IAAddrConstructor) {
+ testOption6IAAddr(COPY);
+}
+
+TEST(OptionCopyTest, option6IAAddrClone) {
+ testOption6IAAddr(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAAddrAssignment) {
+ testOption6IAAddr(ASSIGN);
+}
+
+// *************************** Option6IAPrefix ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6IAPrefix type.
+///
+/// @param op_type Copy operation type.
+void testOption6IAPrefix(const OpType& op_type) {
+ Option6IAPrefixPtr option(new Option6IAPrefix(D6O_IAPREFIX,
+ IOAddress("3000::"),
+ 64, 60, 90));
+ Option6IAPrefixPtr option_copy(new Option6IAPrefix(D6O_IAPREFIX,
+ IOAddress("3001::"),
+ 48, 50, 80));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the values in the original option.
+ option->setPrefix(IOAddress("3002::"), 32);
+ option->setPreferred(1000);
+ option->setValid(2000);
+
+ // The values in the copy should not be affected.
+ EXPECT_EQ("3000::", option_copy->getAddress().toText());
+ EXPECT_EQ(64, option_copy->getLength());
+ EXPECT_EQ(60, option_copy->getPreferred());
+ EXPECT_EQ(90, option_copy->getValid());
+}
+
+TEST(OptionCopyTest, option6IAPrefixConstructor) {
+ testOption6IAPrefix(COPY);
+}
+
+TEST(OptionCopyTest, option6IAPrefixClone) {
+ testOption6IAPrefix(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAPrefixAssignment) {
+ testOption6IAPrefix(ASSIGN);
+}
+
+// *************************** Option6StatusCode ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6StatusCode type.
+///
+/// @param op_type Copy operation type.
+void testOption6StatusCode(const OpType& op_type) {
+ Option6StatusCodePtr option(new Option6StatusCode(STATUS_NoBinding,
+ "no binding"));
+ Option6StatusCodePtr option_copy(new Option6StatusCode(STATUS_Success,
+ "success"));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the values in the original option.
+ option->setStatusCode(STATUS_NoAddrsAvail);
+ option->setStatusMessage("foo");
+
+ // The values in the copy should not be affected.
+ EXPECT_EQ(STATUS_NoBinding, option_copy->getStatusCode());
+ EXPECT_EQ("no binding", option_copy->getStatusMessage());
+}
+
+TEST(OptionCopyTest, option6StatusCodeConstructor) {
+ testOption6StatusCode(COPY);
+}
+
+TEST(OptionCopyTest, option6StatusCodeClone) {
+ testOption6StatusCode(CLONE);
+}
+
+TEST(OptionCopyTest, option6StatusCodeAssignment) {
+ testOption6StatusCode(ASSIGN);
+}
+
+// *************************** OptionString ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionString type.
+///
+/// @param op_type Copy operation type.
+void testOptionString(const OpType& op_type) {
+ OptionStringPtr option(new OptionString(Option::V4, 1, "option value"));
+ OptionStringPtr option_copy(new OptionString(Option::V6, 10,
+ "another value"));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the string in the original option.
+ option->setValue("foo");
+
+ // The string in the copy should not be affected.
+ EXPECT_EQ("option value", option_copy->getValue());
+}
+
+TEST(OptionCopyTest, optionStringConstructor) {
+ testOptionString(COPY);
+}
+
+TEST(OptionCopyTest, optionStringClone) {
+ testOptionString(CLONE);
+}
+
+TEST(OptionCopyTest, optionStringAssignment) {
+ testOptionString(ASSIGN);
+}
+
+// *************************** OptionVendor ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionVendor type.
+///
+/// @param op_type Copy operation type.
+void testOptionVendor(const OpType& op_type) {
+ OptionVendorPtr option(new OptionVendor(Option::V4, 2986));
+ OptionVendorPtr option_copy(new OptionVendor(Option::V6, 1111));
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the vendor id in the original option.
+ option->setVendorId(2222);
+
+ // The vendor id in the copy should not be affected.
+ EXPECT_EQ(2986, option_copy->getVendorId());
+}
+
+TEST(OptionCopyTest, optionVendorConstructor) {
+ testOptionVendor(COPY);
+}
+
+TEST(OptionCopyTest, optionVendorClone) {
+ testOptionVendor(CLONE);
+}
+
+TEST(OptionCopyTest, optionVendorAssignment) {
+ testOptionVendor(ASSIGN);
+}
+
+// *********************** OptionVendorClass ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionVendorClass type.
+///
+/// @param op_type Copy operation type.
+void testOptionVendorClass(const OpType& op_type) {
+ // Create a DHCPv4 option with a single tuple.
+ OptionVendorClassPtr option(new OptionVendorClass(Option::V4, 2986));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "vendor-class-value";
+ option->setTuple(0, tuple);
+
+ // Create a DHCPv6 option with a single tuple.
+ OptionVendorClassPtr option_copy(new OptionVendorClass(Option::V6,
+ 1111));
+ OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "vendor-class-assigned";
+ option_copy->addTuple(tuple_copy);
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the tuple in the original option and add one more tuple.
+ tuple = "modified-vendor-class-value";
+ option->setTuple(0, tuple);
+ tuple = "another-modified-vendor-class-value";
+ option->addTuple(tuple);
+
+ // That change shouldn't affect the original option. It should still
+ // contain a single tuple with the original value.
+ ASSERT_EQ(1, option_copy->getTuplesNum());
+ tuple = option_copy->getTuple(0);
+ EXPECT_TRUE(tuple.equals("vendor-class-value"));
+}
+
+TEST(OptionCopyTest, optionVendorClassConstructor) {
+ testOptionVendorClass(COPY);
+}
+
+TEST(OptionCopyTest, optionVendorClassClone) {
+ testOptionVendorClass(CLONE);
+}
+
+TEST(OptionCopyTest, optionVendorClassAssignment) {
+ testOptionVendorClass(ASSIGN);
+}
+
+// ************************** Option4ClientFqdn ***************************
+
+/// @brief Test deep copy of option encapsulated by Option4ClientFqdn or
+/// Option6ClientFqdn type.
+///
+/// @param op_type Copy operation type.
+/// @param option Option to be copied.
+/// @param option_copy Destination option. Note that this option may be
+/// initially set to a non-null value. For the "copy" and "clone" operations
+/// the pointer will be reset, so there is no sense to initialize this
+/// object to a non-null value. However, for the assignment testing it is
+/// recommended to initialize the option_copy to point to an option having
+/// different parameters to verify that all parameters have been overridden
+/// by the assignment operation.
+///
+/// @tparam OptionType Option4ClientFqdn or Option6ClientFqdn.
+template<typename OptionType>
+void testOptionClientFqdn(const OpType& op_type,
+ boost::shared_ptr<OptionType>& option,
+ boost::shared_ptr<OptionType>& option_copy) {
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the values in the original option.
+ option->setDomainName("newname", OptionType::PARTIAL);
+ option->setFlag(OptionType::FLAG_S, false);
+ option->setFlag(OptionType::FLAG_N, true);
+
+ // Rcode is carried on the in the DHCPv4 Client FQDN option.
+ // If the OptionType is pointing to a DHCPv6 option the dynamic
+ // cast will result in NULL pointer and we'll not check the
+ // RCODE.
+ Option4ClientFqdnPtr option4 =
+ boost::dynamic_pointer_cast<Option4ClientFqdn>(option);
+ if (option4) {
+ option4->setRcode(64);
+ }
+
+ // Verify that common parameters haven't been modified in the
+ // copied option by the change in the original option.
+ EXPECT_EQ("myname.example.org.", option_copy->getDomainName());
+ EXPECT_EQ(OptionType::FULL, option_copy->getDomainNameType());
+ EXPECT_TRUE(option_copy->getFlag(OptionType::FLAG_S));
+ EXPECT_FALSE(option_copy->getFlag(OptionType::FLAG_N));
+
+ // If we're dealing with DHCPv4 Client FQDN, we also need to
+ // test RCODE.
+ Option4ClientFqdnPtr option_copy4 =
+ boost::dynamic_pointer_cast<Option4ClientFqdn>(option_copy);
+ if (option_copy4) {
+ EXPECT_EQ(255, option_copy4->getRcode().first.getCode());
+ EXPECT_EQ(255, option_copy4->getRcode().second.getCode());
+ }
+}
+
+/// @brief Test deep copy of option encapsulated by Option4ClientFqdn type.
+///
+/// @param op_type Copy operation type.
+void testOption4ClientFqdn(const OpType& op_type) {
+ Option4ClientFqdnPtr
+ option(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::Rcode(255),
+ "myname.example.org"));
+ Option4ClientFqdnPtr
+ option_copy(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+ Option4ClientFqdn::Rcode(0),
+ "other.example.org"));
+
+ ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option4ClientFqdn>(op_type, option,
+ option_copy));
+}
+
+TEST(OptionCopyTest, option4ClientFqdnConstructor) {
+ testOption4ClientFqdn(COPY);
+}
+
+TEST(OptionCopyTest, option4ClientFqdnClone) {
+ testOption4ClientFqdn(CLONE);
+}
+
+TEST(OptionCopyTest, option4ClientFqdnAssignment) {
+ testOption4ClientFqdn(ASSIGN);
+}
+
+// ************************** Option6ClientFqdn ***************************
+
+/// @brief Test deep copy of option encapsulated by Option6ClientFqdn type.
+///
+/// @param op_type Copy operation type.
+void testOption6ClientFqdn(const OpType& op_type) {
+ Option6ClientFqdnPtr
+ option(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myname.example.org"));
+ Option6ClientFqdnPtr
+ option_copy(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "other.example.org"));
+
+ ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option6ClientFqdn>(op_type, option,
+ option_copy));
+}
+
+TEST(OptionCopyTest, option6ClientFqdnConstructor) {
+ testOption6ClientFqdn(COPY);
+}
+
+TEST(OptionCopyTest, option6ClientFqdnClone) {
+ testOption6ClientFqdn(CLONE);
+}
+
+TEST(OptionCopyTest, option6ClientFqdnAssignment) {
+ testOption6ClientFqdn(ASSIGN);
+}
+
+// **************************** OptionCustom ***************************
+
+/// @brief Test deep copy of option encapsulated by OptionCustom type.
+///
+/// @param op_type Copy operation type.
+void testOptionCustom(const OpType& op_type) {
+ // Create option with a single field carrying 16-bits integer.
+ OptionDefinition def("foo", 1, "my-space", "uint16", true);
+ OptionCustomPtr option(new OptionCustom(def, Option::V4));
+ option->addArrayDataField<uint16_t>(5555);
+
+ // Create option with two fields carrying IPv4 address and 32-bit
+ // integer.
+ OptionDefinition def_copy("bar", 10, "my-space", "record");
+ def_copy.addRecordField("ipv4-address");
+ def_copy.addRecordField("uint32");
+ OptionCustomPtr option_copy(new OptionCustom(def_copy, Option::V6));
+ option_copy->writeAddress(IOAddress("192.0.0.2"));
+ option_copy->writeInteger<uint32_t>(12, 1);
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the original option value.
+ option->writeInteger<uint16_t>(1000);
+
+ // The copied option should not be affected.
+ ASSERT_EQ(1, option_copy->getDataFieldsNum());
+ EXPECT_EQ(5555, option_copy->readInteger<uint16_t>());
+}
+
+TEST(OptionCopyTest, optionCustomConstructor) {
+ testOptionCustom(COPY);
+}
+
+TEST(OptionCopyTest, optionCustomClone) {
+ testOptionCustom(CLONE);
+}
+
+TEST(OptionCopyTest, optionCustomAssignment) {
+ testOptionCustom(ASSIGN);
+}
+
+// ************************ OptionOpaqueDataTuples ***********************
+
+/// @brief Test deep copy of option encapsulated by OptionOpaqueDataTuples type.
+///
+/// @param op_type Copy operation type.
+void testOptionOpaqueDataTuples(const OpType& op_type) {
+ OptionOpaqueDataTuplesPtr option(new OptionOpaqueDataTuples(Option::V4, 1));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "a string";
+ option->addTuple(tuple);
+ tuple = "another string";
+ option->addTuple(tuple);
+ OptionOpaqueDataTuplesPtr option_copy(new OptionOpaqueDataTuples(Option::V6, 10));
+ OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple_copy = "copy string";
+ option_copy->addTuple(tuple_copy);
+
+ ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+ // Modify the value in the first tuple and add one more tuple.
+ tuple = "modified-first-tuple";
+ option->setTuple(0, tuple);
+ tuple = "modified-second-tuple";
+ option->setTuple(1, tuple);
+
+ // This should not affect the values in the original option.
+ ASSERT_EQ(2, option_copy->getTuplesNum());
+ EXPECT_TRUE(option_copy->getTuple(0).equals("a string"));
+ EXPECT_TRUE(option_copy->getTuple(1).equals("another string"));
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesConstructor) {
+ testOptionOpaqueDataTuples(COPY);
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesClone) {
+ testOptionOpaqueDataTuples(CLONE);
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesAssign) {
+ testOptionOpaqueDataTuples(ASSIGN);
+}
+
+}
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
new file mode 100644
index 0000000..4053c1e
--- /dev/null
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -0,0 +1,2510 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <dhcp/option_custom.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+ IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
+/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ OptionCustomTest() { }
+
+ /// @brief Appends DHCPv4 suboption in the on-wire format to the buffer.
+ ///
+ /// @param buf A buffer to which suboption is appended.
+ void appendV4Suboption(OptionBuffer& buf) {
+ const uint8_t subopt_data[] = {
+ 0x01, 0x02, // Option type = 1, length = 2
+ 0x01, 0x02 // Two bytes of data
+ };
+ buf.insert(buf.end(), subopt_data, subopt_data + sizeof(subopt_data));
+ }
+
+ /// @brief Check if the parsed option has a suboption.
+ ///
+ /// @param opt An option in which suboption is expected.
+ /// @return Assertion result indicating that the suboption is
+ /// present (success) or missing (failure).
+ ::testing::AssertionResult hasV4Suboption(OptionCustom* opt) {
+ OptionPtr subopt = opt->getOption(1);
+ if (!subopt) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "Suboption of OptionCustom"
+ " is missing"));
+ }
+ return (::testing::AssertionSuccess());
+ }
+
+ /// @brief Appends DHCPv6 suboption in the on-wire format to the buffer.
+ ///
+ /// @param buf A buffer to which suboption is appended.
+ void appendV6Suboption(OptionBuffer& buf) {
+ const uint8_t subopt_data[] = {
+ 0x00, 0x01, // Option type = 1
+ 0x00, 0x04, // Option length = 4
+ 0x01, 0x02, 0x03, 0x04 // Four bytes of data
+ };
+ buf.insert(buf.end(), subopt_data, subopt_data + sizeof(subopt_data));
+ }
+
+ /// @brief Check if the parsed option has a suboption.
+ ///
+ /// @param opt An option in which suboption is expected.
+ /// @return Assertion result indicating that the suboption is
+ /// present (success) or missing (failure).
+ ::testing::AssertionResult hasV6Suboption(OptionCustom* opt) {
+ OptionPtr subopt = opt->getOption(1);
+ if (!subopt) {
+ return (::testing::AssertionFailure(::testing::Message()
+ << "Suboption of OptionCustom"
+ " is missing"));
+ }
+ return (::testing::AssertionSuccess());
+ }
+
+ /// @brief Write IP address into a buffer.
+ ///
+ /// @param address address to be written.
+ /// @param [out] buf output buffer.
+ void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ const std::vector<uint8_t>& vec = address.toBytes();
+ buf.insert(buf.end(), vec.begin(), vec.end());
+ }
+
+ /// @brief Write integer (signed or unsigned) into a buffer.
+ ///
+ /// @param value integer value.
+ /// @param [out] buf output buffer.
+ /// @tparam integer type.
+ template<typename T>
+ void writeInt(T value, std::vector<uint8_t>& buf) {
+ switch (sizeof(T)) {
+ case 4:
+ buf.push_back((value >> 24) & 0xFF);
+ /* falls through */
+ case 3:
+ buf.push_back((value >> 16) & 0xFF);
+ /* falls through */
+ case 2:
+ buf.push_back((value >> 8) & 0xFF);
+ /* falls through */
+ case 1:
+ buf.push_back(value & 0xFF);
+ break;
+ default:
+ // This loop is incorrectly compiled by some old g++?!
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
+ }
+ }
+
+ /// @brief Write a string into a buffer.
+ ///
+ /// @param value string to be written into a buffer.
+ /// @param buf output buffer.
+ void writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ buf.resize(buf.size() + value.size());
+ std::copy_backward(value.c_str(), value.c_str() + value.size(),
+ buf.end());
+ }
+};
+
+// The purpose of this test is to check that parameters passed to
+// a custom option's constructor are used to initialize class
+// members.
+TEST_F(OptionCustomTest, constructor) {
+ // Create option definition for a DHCPv6 option.
+ OptionDefinition opt_def1("OPTION_FOO", 1000, "my-space", "boolean", true);
+
+ // Initialize some dummy buffer that holds single boolean value.
+ OptionBuffer buf;
+ buf.push_back(1);
+
+ // Create DHCPv6 option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def1, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // Check if constructor initialized the universe and type correctly.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(1000, option->getType());
+
+ // Do another round of testing for DHCPv4 option.
+ OptionDefinition opt_def2("OPTION_FOO", 232, "my-space", "boolean");
+
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def2, Option::V4, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(232, option->getType());
+
+ // Try to create an option using 'empty data' constructor
+ OptionDefinition opt_def3("OPTION_FOO", 1000, "my-space", "uint32");
+
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def3, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(1000, option->getType());
+}
+
+// The purpose of this test is to verify that 'empty' option definition can
+// be used to create an instance of custom option.
+TEST_F(OptionCustomTest, emptyData) {
+ OptionDefinition opt_def("option-foo", 232, "my-space", "empty",
+ "option-foo-space");
+
+ // Create a buffer holding 1 suboption.
+ OptionBuffer buf;
+ appendV4Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+ buf.end()));
+ );
+
+ ASSERT_TRUE(option);
+
+ // Option is 'empty' so no data fields are expected.
+ EXPECT_EQ(0, option->getDataFieldsNum());
+
+ // Check that suboption has been parsed.
+ EXPECT_TRUE(hasV4Suboption(option.get()));
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a binary value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, binaryData) {
+ OptionDefinition opt_def("option-foo", 231, "my-space", "binary",
+ "option-foo-space");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(14);
+ for (unsigned i = 0; i < 14; ++i) {
+ buf_in[i] = i;
+ }
+
+ // Append suboption data. This data should NOT be recognized when
+ // option has a binary format.
+ appendV4Suboption(buf_in);
+
+ // Use scoped pointer because it allows to declare the option
+ // in the function scope and initialize it under ASSERT.
+ boost::scoped_ptr<OptionCustom> option;
+ // Custom option may throw exception if the provided buffer is
+ // malformed.
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // The custom option should hold just one buffer that can be
+ // accessed using index 0.
+ OptionBuffer buf_out;
+ ASSERT_NO_THROW(buf_out = option->readBinary(0));
+
+ // Read buffer must match exactly with the buffer used to
+ // create option instance.
+ ASSERT_EQ(buf_in.size(), buf_out.size());
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+ // Check that option with "no data" is rejected.
+ buf_in.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(),
+ buf_in.end())),
+ isc::OutOfRange
+ );
+
+ // Suboptions are not recognized for the binary formats because as it is
+ // a variable length format. Therefore, we expect that there are no
+ // suboptions in the parsed option.
+ EXPECT_FALSE(option->getOption(1));
+}
+
+// The purpose of this test is to verify that an option definition comprising
+// a single boolean value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "boolean",
+ "option-foo-space");
+
+ OptionBuffer buf;
+ // Push back the value that represents 'false'.
+ buf.push_back(0);
+
+ // Append suboption. It should be present in the parsed packet.
+ appendV6Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Initialize the value to true because we want to make sure
+ // that it is modified to 'false' by readBoolean below.
+ bool value = true;
+
+ // Read the boolean value from only one available buffer indexed
+ // with 0. It is expected to be 'false'.
+ ASSERT_NO_THROW(value = option->readBoolean(0));
+ EXPECT_FALSE(value);
+
+ // There should be one suboption present.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that the option with "no data" is rejected.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+ buf.end())),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as a DHCPv4 tuple.
+TEST_F(OptionCustomTest, tupleData4) {
+ OptionDefinition opt_def("option-foo", 232, "my-space", "tuple",
+ "option-foo-space");
+
+ const char data[] = {
+ 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Append suboption. It should be present in the parsed packet.
+ appendV4Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Check it
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("foobar", value);
+
+ // Now as a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_NO_THROW(option->readTuple(tuple, 0));
+ EXPECT_EQ("foobar", tuple.getText());
+
+ // There should be one suboption present.
+ EXPECT_TRUE(hasV4Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.begin() + 6)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Check that the option with "no data" is rejected.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.end())),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as a DHCPv6 tuple.
+TEST_F(OptionCustomTest, tupleData6) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple",
+ "option-foo-space");
+
+ const char data[] = {
+ 0, 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Append suboption. It should be present in the parsed packet.
+ appendV6Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Check it
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("foobar", value);
+
+ // Now as a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ EXPECT_NO_THROW(option->readTuple(tuple, 0));
+ EXPECT_EQ("foobar", tuple.getText());
+
+ // There should be one suboption present.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 1)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 7)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as FQDN.
+TEST_F(OptionCustomTest, fqdnData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "fqdn",
+ "option-foo-space");
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // The FQDN has a certain boundary. Right after FQDN it should be
+ // possible to append suboption and parse it correctly.
+ appendV6Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ // This option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 4)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 16-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int16Data) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "int16",
+ "option-foo-space");
+
+ OptionBuffer buf;
+ // Store signed integer value in the input buffer.
+ writeInt<int16_t>(-234, buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Initialize value to 0 explicitly to make sure that is
+ // modified by readInteger function to expected -234.
+ int16_t value = 0;
+ ASSERT_NO_THROW(value = option->readInteger<int16_t>(0));
+ EXPECT_EQ(-234, value);
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that the option is not created when a buffer is
+ // too short (1 byte instead of 2 bytes).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 1)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 32-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int32Data) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "int32",
+ "option-foo-space");
+
+ OptionBuffer buf;
+ writeInt<int32_t>(-234, buf);
+
+ // Append one suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Initialize value to 0 explicitly to make sure that is
+ // modified by readInteger function to expected -234.
+ int32_t value = 0;
+ ASSERT_NO_THROW(value = option->readInteger<int32_t>(0));
+ EXPECT_EQ(-234, value);
+
+ // The parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that the option is not created when a buffer is
+ // too short (3 bytes instead of 4 bytes).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 3)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv4 address can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address",
+ "option-foo-space");
+
+ // Create input buffer.
+ OptionBuffer buf;
+ writeAddress(IOAddress("192.168.100.50"), buf);
+
+ // Append one suboption.
+ appendV4Suboption(buf);
+
+ // Create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ IOAddress address("127.0.0.1");
+ // Read IPv4 address from using index 0.
+ ASSERT_NO_THROW(address = option->readAddress(0));
+
+ EXPECT_EQ("192.168.100.50", address.toText());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV4Suboption(option.get()));
+
+ // Check that option is not created if the provided buffer is
+ // too short (use 3 bytes instead of 4).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.begin() + 3)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv6 address can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-address",
+ "option-foo-space");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeAddress(IOAddress("2001:db8:1::100"), buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // IPv6 address.
+ IOAddress address("::1");
+ // Read an address from buffer #0.
+ ASSERT_NO_THROW(address = option->readAddress(0));
+
+ EXPECT_EQ("2001:db8:1::100", address.toText());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+ // Check that option is not created if the provided buffer is
+ // too short (use 15 bytes instead of 16).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+ buf.begin() + 15)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single variable length prefix can be used to create an instance of custom
+// option.
+TEST_F(OptionCustomTest, prefixData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix",
+ "option-foo-space");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeInt<uint8_t>(32, buf);
+ writeInt<uint32_t>(0x30000001, buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // a prefix.
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ // Read prefix from buffer #0.
+ ASSERT_NO_THROW(prefix = option->readPrefix(0));
+
+ // The prefix comprises a prefix length and prefix value.
+ EXPECT_EQ(32, prefix.first.asUnsigned());
+ EXPECT_EQ("3000:1::", prefix.second.toText());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single PSID can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, psidData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "psid",
+ "option-foo-space");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeInt<uint8_t>(4, buf);
+ writeInt<uint16_t>(0x8000, buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // a PSID length / PSID value tuple.
+ PSIDTuple psid;
+ // Read PSID length / PSID value from buffer #0.
+ ASSERT_NO_THROW(psid = option->readPsid(0));
+
+ // The PSID comprises a PSID length and PSID value.
+ EXPECT_EQ(4, psid.first.asUnsigned());
+ EXPECT_EQ(0x08, psid.second.asUint16());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// string value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, stringData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "string",
+ "option-foo-space");
+
+ // Create an input buffer holding some string value.
+ OptionBuffer buf;
+ writeString("hello world!", buf);
+
+ // Append suboption. It should not be detected because the string field
+ // has variable length.
+ appendV6Suboption(buf);
+
+ // Append suboption. Since the option has variable length string field,
+ // the suboption should not be recognized.
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should now comprise single string value that
+ // can be accessed using index 0.
+ std::string value;
+ ASSERT_NO_THROW(value = option->readString(0));
+
+ // The initial part of the string should contain the actual string.
+ // The rest of it is a garbage from an attempt to decode suboption
+ // as a string.
+ ASSERT_EQ(20, value.size());
+ EXPECT_EQ("hello world!", value.substr(0, 12));
+
+ // No suboption should be present.
+ EXPECT_FALSE(option->getOption(1));
+
+ // Check that option will not be created if empty buffer is provided.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of boolean values can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "boolean", true);
+
+ // Create a buffer with 5 values that represent array of
+ // booleans.
+ OptionBuffer buf(5);
+ buf[0] = 1; // true
+ buf[1] = 0; // false
+ buf[2] = 0; // false
+ buf[3] = 1; // true
+ buf[4] = 1; // true
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 5 data fields.
+ ASSERT_EQ(5, option->getDataFieldsNum());
+
+ // Read values from custom option using indexes 0..4 and
+ // check that they are valid.
+ bool value0 = false;
+ ASSERT_NO_THROW(value0 = option->readBoolean(0));
+ EXPECT_TRUE(value0);
+
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+
+ bool value2 = true;
+ ASSERT_NO_THROW(value2 = option->readBoolean(2));
+ EXPECT_FALSE(value2);
+
+ bool value3 = false;
+ ASSERT_NO_THROW(value3 = option->readBoolean(3));
+ EXPECT_TRUE(value3);
+
+ bool value4 = false;
+ ASSERT_NO_THROW(value4 = option->readBoolean(4));
+ EXPECT_TRUE(value4);
+
+ // Check that empty buffer can't be used to create option holding
+ // array of boolean values.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of 32-bit signed integer values can be used to create an instance
+// of custom option.
+TEST_F(OptionCustomTest, uint32DataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "uint32", true);
+
+ // Create an input buffer that holds 4 uint32 values that
+ // represent an array.
+ std::vector<uint32_t> values;
+ values.push_back(71234);
+ values.push_back(12234);
+ values.push_back(54362);
+ values.push_back(1234);
+
+ // Store these values in a buffer.
+ OptionBuffer buf;
+ for (size_t i = 0; i < values.size(); ++i) {
+ writeInt<uint32_t>(values[i], buf);
+ }
+ // Create custom option using the input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ // Note that we just use a part of the whole buffer here: 13 bytes. We want to
+ // check that buffer length which is non-divisible by 4 (size of uint32_t) is
+ // accepted and only 3 (instead of 4) elements will be stored in a custom option.
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 13));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Expect only 3 values.
+ for (int i = 0; i < 3; ++i) {
+ uint32_t value = 0;
+ ASSERT_NO_THROW(value = option->readInteger<uint32_t>(i));
+ EXPECT_EQ(values[i], value);
+ }
+
+ // Check that too short buffer can't be used to create the option.
+ // Using buffer having length of 3 bytes. The length of 4 bytes is
+ // a minimal length to create the option with single uint32_t value.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+ buf.begin() + 3)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv4 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address",
+ true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("127.0.0.1"));
+ addresses.push_back(IOAddress("10.10.1.2"));
+
+ // Store the collection of IPv4 addresses into the buffer.
+ OptionBuffer buf;
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv4 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i], address);
+ }
+
+ // Check that it is ok if buffer length is not a multiple of IPv4
+ // address length. Resize it by two bytes.
+ buf.resize(buf.size() + 2);
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+
+ // Check that option is not created when the provided buffer
+ // is too short. At least a buffer length of 4 bytes is needed.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+ buf.begin() + 2)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address",
+ true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("2001:db8:1::3"));
+ addresses.push_back(IOAddress("::1"));
+ addresses.push_back(IOAddress("fe80::3"));
+
+ // Store the collection of IPv6 addresses into the buffer.
+ OptionBuffer buf;
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv6 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("fe80::4");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i], address);
+ }
+
+ // Check that it is ok if buffer length is not a multiple of IPv6
+ // address length. Resize it by two bytes.
+ buf.resize(buf.size() + 2);
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+
+ // Check that option is not created when the provided buffer
+ // is too short. At least a buffer length of 16 bytes is needed.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+ buf.begin() + 15)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option comprising
+// an array of FQDN values can be created from a buffer which holds
+// multiple FQDN values encoded as described in the RFC1035, section
+// 3.1
+TEST_F(OptionCustomTest, fqdnDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "fqdn", true);
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Create a buffer that holds two FQDNs.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Create an option from using a buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We expect that two FQDN values have been extracted
+ // from a buffer.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Validate both values.
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ std::string domain1 = option->readFqdn(1);
+ EXPECT_EQ("example.com.", domain1);
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 prefixes can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, prefixDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix",
+ true);
+
+ // The following buffer comprises three prefixes with different
+ // prefix lengths.
+ const uint8_t data[] = {
+ 32, 0x30, 0x01, 0x00, 0x01, // 3001:1::/32
+ 16, 0x30, 0x00, // 3000::/16
+ 48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01 // 2001:db8:1::/48
+ };
+
+ // Initialize input buffer
+ OptionBuffer buf(data,
+ data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields with 3 prefixes.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ PrefixTuple prefix0(ZERO_PREFIX_TUPLE);
+ PrefixTuple prefix1(ZERO_PREFIX_TUPLE);
+ PrefixTuple prefix2(ZERO_PREFIX_TUPLE);
+
+ ASSERT_NO_THROW(prefix0 = option->readPrefix(0));
+ ASSERT_NO_THROW(prefix1 = option->readPrefix(1));
+ ASSERT_NO_THROW(prefix2 = option->readPrefix(2));
+
+ EXPECT_EQ(32, prefix0.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix0.second.toText());
+
+ EXPECT_EQ(16, prefix1.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix1.second.toText());
+
+ EXPECT_EQ(48, prefix2.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix2.second.toText());
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of PSIDs can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, psidDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", true);
+
+ // The following buffer comprises three PSIDs.
+ const uint8_t data[] = {
+ 4, 0x80, 0x00, // PSID len = 4, PSID = '1000 000000000000b'
+ 6, 0xD4, 0x00, // PSID len = 6, PSID = '110101 0000000000b'
+ 1, 0x80, 0x00 // PSID len = 1, PSID = '1 000000000000000b'
+ };
+ // Initialize input buffer.
+ OptionBuffer buf(data,
+ data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields with 3 PSIDs.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ PSIDTuple psid0;
+ PSIDTuple psid1;
+ PSIDTuple psid2;
+
+ ASSERT_NO_THROW(psid0 = option->readPsid(0));
+ ASSERT_NO_THROW(psid1 = option->readPsid(1));
+ ASSERT_NO_THROW(psid2 = option->readPsid(2));
+
+ // PSID value is equal to '1000b' (8).
+ EXPECT_EQ(4, psid0.first.asUnsigned());
+ EXPECT_EQ(0x08, psid0.second.asUint16());
+
+ // PSID value is equal to '110101b' (0x35).
+ EXPECT_EQ(6, psid1.first.asUnsigned());
+ EXPECT_EQ(0x35, psid1.second.asUint16());
+
+ // PSID value is equal to '1b' (1).
+ EXPECT_EQ(1, psid2.first.asUnsigned());
+ EXPECT_EQ(0x01, psid2.second.asUint16());
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as DHCPv4 tuples.
+TEST_F(OptionCustomTest, tupleDataArray4) {
+ OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", true);
+
+ const char data[] = {
+ 5, 104, 101, 108, 108, 111, // "hello"
+ 1, 32, // " "
+ 5, 119, 111, 114, 108, 100 // "world"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check them
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("hello", value);
+ ASSERT_NO_THROW(value = option->readTuple(1));
+ EXPECT_EQ(" ", value);
+ ASSERT_NO_THROW(value = option->readTuple(2));
+ EXPECT_EQ("world", value);
+
+ // There should be no suboption present.
+ EXPECT_FALSE(hasV4Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4,
+ buf.begin(), buf.begin() + 12)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as DHCPv6 tuples.
+TEST_F(OptionCustomTest, tupleDataArray6) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", true);
+
+ const char data[] = {
+ 0, 5, 104, 101, 108, 108, 111, // "hello"
+ 0, 1, 32, // " "
+ 0, 5, 119, 111, 114, 108, 100 // "world"
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check them
+ std::string value;
+ ASSERT_NO_THROW(value = option->readTuple(0));
+ EXPECT_EQ("hello", value);
+ ASSERT_NO_THROW(value = option->readTuple(1));
+ EXPECT_EQ(" ", value);
+ ASSERT_NO_THROW(value = option->readTuple(2));
+ EXPECT_EQ("world", value);
+
+ // There should be no suboption present.
+ EXPECT_FALSE(hasV6Suboption(option.get()));
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 8)),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 16)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a record of fixed-size fields can be used to create an option with a
+// suboption.
+TEST_F(OptionCustomTest, recordDataWithSuboption) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "record",
+ "option-foo-space");
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+
+ // Create a buffer with two fields: 4-byte number and IPv4 address.
+ OptionBuffer buf;
+ writeInt<uint32_t>(0x01020304, buf);
+ writeAddress(IOAddress("192.168.0.1"), buf);
+
+ // Append a suboption. It should be correctly parsed because option fields
+ // preceding this option have fixed (known) size.
+ appendV6Suboption(buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+ buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have two data fields parsed.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Validate values in fields.
+ uint32_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint32_t>(0));
+ EXPECT_EQ(0x01020304, value0);
+
+ IOAddress value1 = 0;
+ ASSERT_NO_THROW(value1 = option->readAddress(1));
+ EXPECT_EQ("192.168.0.1", value1.toText());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a record of various data fields can be used to create an instance of
+// custom option.
+TEST_F(OptionCustomTest, recordData) {
+ // Create the definition of an option which comprises
+ // a record of fields of different types.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("psid"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ const char fqdn_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
+ OptionBuffer buf;
+ // Initialize field 0 to 8712.
+ writeInt<uint16_t>(8712, buf);
+ // Initialize field 1 to 'true'
+ buf.push_back(static_cast<unsigned short>(1));
+ // Initialize field 2 to 'mydomain.example.com'.
+ buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+ // Initialize field 3 to IPv4 address.
+ writeAddress(IOAddress("192.168.0.1"), buf);
+ // Initialize field 4 to IPv6 address.
+ writeAddress(IOAddress("2001:db8:1::1"), buf);
+ // Initialize field 5 PSID len and PSID value.
+ writeInt<uint8_t>(6, buf);
+ writeInt<uint16_t>(0xD400, buf);
+ // Initialize field 6 to string value.
+ writeString("ABCD", buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 6 data fields.
+ ASSERT_EQ(7, option->getDataFieldsNum());
+
+ // Verify value in the field 0.
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(8712, value0);
+
+ // Verify value in the field 1.
+ bool value1 = false;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+
+ // Verify value in the field 2.
+ std::string value2 = "";
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("mydomain.example.com.", value2);
+
+ // Verify value in the field 3.
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+
+ // Verify value in the field 4.
+ IOAddress value4("::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+ // Verify value in the field 5.
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(6, value5.first.asUnsigned());
+ EXPECT_EQ(0x35, value5.second.asUint16());
+
+ // Verify value in the field 6.
+ std::string value6;
+ ASSERT_NO_THROW(value6 = option->readString(6));
+ EXPECT_EQ("ABCD", value6);
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a record of various data fields with an array for the last can be used
+// to create an instance of custom option.
+TEST_F(OptionCustomTest, recordArrayData) {
+ // Create the definition of an option which comprises
+ // a record of fields of different types.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record", true);
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("psid"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ const char fqdn_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
+ OptionBuffer buf;
+ // Initialize field 0 to 8712.
+ writeInt<uint16_t>(8712, buf);
+ // Initialize field 1 to 'true'
+ writeInt<uint8_t>(1, buf);
+ // Initialize field 2 to 'mydomain.example.com'.
+ buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+ // Initialize field 3 to IPv4 address.
+ writeAddress(IOAddress("192.168.0.1"), buf);
+ // Initialize field 4 to IPv6 address.
+ writeAddress(IOAddress("2001:db8:1::1"), buf);
+ // Initialize field 5 PSID len and PSID value.
+ writeInt<uint8_t>(6, buf);
+ writeInt<uint16_t>(0xD400, buf);
+ // Initialize last field 6 to a pair of int 12345678 and 87654321.
+ writeInt<uint32_t>(12345678, buf);
+ writeInt<uint32_t>(87654321, buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 7+1 data fields.
+ ASSERT_EQ(8, option->getDataFieldsNum());
+
+ // Verify value in the field 0.
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(8712, value0);
+
+ // Verify value in the field 1.
+ bool value1 = false;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+
+ // Verify value in the field 2.
+ std::string value2 = "";
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("mydomain.example.com.", value2);
+
+ // Verify value in the field 3.
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+
+ // Verify value in the field 4.
+ IOAddress value4("::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+ // Verify value in the field 5.
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(6, value5.first.asUnsigned());
+ EXPECT_EQ(0x35, value5.second.asUint16());
+
+ // Verify value in the field 6.
+ uint32_t value6;
+ ASSERT_NO_THROW(value6 = option->readInteger<uint32_t>(6));
+ EXPECT_EQ(12345678, value6);
+
+ // Verify value in the extra field 7.
+ uint32_t value7;
+ ASSERT_NO_THROW(value7 = option->readInteger<uint32_t>(7));
+ EXPECT_EQ(87654321, value7);
+}
+
+// The purpose of this test is to verify that truncated buffer
+// can't be used to create an option being a record of value of
+// different types.
+TEST_F(OptionCustomTest, recordDataTruncated) {
+ // Create the definition of an option which comprises
+ // a record of fields of different types.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ OptionBuffer buf;
+ // Initialize field 0.
+ writeInt<uint16_t>(8712, buf);
+ // Initialize field 1 to IPv6 address.
+ writeAddress(IOAddress("2001:db8:1::1"), buf);
+ // Initialize field 2 to string value.
+ writeString("ABCD", buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+
+ // Constructor should not throw exception here because the length of the
+ // buffer meets the minimum length. The first 19 bytes hold data for
+ // all option fields: uint16, IPv4 address and first letter of string.
+ // Note that string will be truncated but this is acceptable because
+ // constructor have no way to determine the length of the original string.
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 19));
+ );
+
+ // Reduce the buffer length by one byte should cause the constructor
+ // to fail. This is because 18 bytes can only hold first two data fields:
+ // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
+ // 3 data fields for this option but the length of the data is insufficient
+ // to initialize 3 data field.
+
+ // @todo:
+ // Currently the code was modified to allow empty string or empty binary data
+ // Potentially change this back to EXPECT_THROW(..., OutOfRange) once we
+ // decide how to treat zero length strings and binary data (they are typically
+ // valid or invalid on a per option basis, so there likely won't be a single
+ // one answer to all)
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18))
+ );
+
+ // Try to further reduce the length of the buffer to make it insufficient
+ // to even initialize the second data field.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 17)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that an option comprising
+// single data field with binary data can be used and that this
+// binary data is properly initialized to a default value. This
+// test also checks that it is possible to override this default
+// value.
+TEST_F(OptionCustomTest, setBinaryData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "binary");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Get the default binary value.
+ OptionBuffer buf;
+ ASSERT_NO_THROW(option->readBinary());
+ // The buffer is by default empty.
+ EXPECT_TRUE(buf.empty());
+ // Prepare input buffer with some dummy data.
+ OptionBuffer buf_in(10);
+ for (size_t i = 0; i < buf_in.size(); ++i) {
+ buf_in[i] = i;
+ }
+ // Try to override the default binary buffer.
+ ASSERT_NO_THROW(option->writeBinary(buf_in));
+ // And check that it has been actually overridden.
+ ASSERT_NO_THROW(buf = option->readBinary());
+ ASSERT_EQ(buf_in.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that an option comprising
+// single boolean data field can be created and that its default
+// value can be overridden by a new value.
+TEST_F(OptionCustomTest, setBooleanData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "boolean");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+ // Check that the default boolean value is false.
+ bool value = false;
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_FALSE(value);
+ // Check that we can override the default value.
+ ASSERT_NO_THROW(option->writeBoolean(true));
+ // Finally, check that it has been actually overridden.
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_TRUE(value);
+}
+
+/// The purpose of this test is to verify that the data field value
+/// can be overridden by a new value.
+TEST_F(OptionCustomTest, setUint32Data) {
+ // Create a definition of an option that holds single
+ // uint32 value.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "uint32");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // The default value for integer data fields is 0.
+ uint32_t value = 0;
+ ASSERT_NO_THROW(option->readInteger<uint32_t>());
+ EXPECT_EQ(0, value);
+
+ // Try to set the data field value to something different
+ // than 0.
+ ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234));
+
+ // Verify that it has been set.
+ ASSERT_NO_THROW(value = option->readInteger<uint32_t>());
+ EXPECT_EQ(1234, value);
+}
+
+// The purpose of this test is to verify that an option comprising
+// single IPv4 address can be created and that this address can
+// be overridden by a new value.
+TEST_F(OptionCustomTest, setIpv4AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "my-space", "ipv4-address");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ asiolink::IOAddress address("127.0.0.1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("0.0.0.0", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("192.168.0.1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single IPv6 address can be created and that this address can
+// be overridden by a new value.
+TEST_F(OptionCustomTest, setIpv6AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ asiolink::IOAddress address("::1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("::", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("2001:db8:1::1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// a prefix can be created and that the prefix can be overridden by
+// a new value.
+TEST_F(OptionCustomTest, setPrefixData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Make sure the default prefix is set.
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(prefix = option->readPrefix());
+ EXPECT_EQ(0, prefix.first.asUnsigned());
+ EXPECT_EQ("::", prefix.second.toText());
+
+ // Write prefix.
+ ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), IOAddress("2001:db8:1::")));
+
+ // Read prefix back and make sure it is the one we just set.
+ ASSERT_NO_THROW(prefix = option->readPrefix());
+ EXPECT_EQ(48, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// a single PSID can be created and that the PSID can be overridden
+// by a new value.
+TEST_F(OptionCustomTest, setPsidData) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "psid");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Make sure the default PSID is set.
+ PSIDTuple psid;
+ ASSERT_NO_THROW(psid = option->readPsid());
+ EXPECT_EQ(0, psid.first.asUnsigned());
+ EXPECT_EQ(0, psid.second.asUint16());
+
+ // Write PSID.
+ ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8)));
+
+ // Read PSID back and make sure it is the one we just set.
+ ASSERT_NO_THROW(psid = option->readPsid());
+ EXPECT_EQ(4, psid.first.asUnsigned());
+ EXPECT_EQ(8, psid.second.asUint16());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single string value can be created and that this value
+// is initialized to the default value. Also, this test checks that
+// this value can be overwritten by a new value.
+TEST_F(OptionCustomTest, setStringData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "string");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Get the default value of the option.
+ std::string value;
+ ASSERT_NO_THROW(value = option->readString());
+ // By default the string data field is empty.
+ EXPECT_TRUE(value.empty());
+ // Write some text to this field.
+ ASSERT_NO_THROW(option->writeString("hello world"));
+ // Check that it has been actually written.
+ EXPECT_NO_THROW(value = option->readString());
+ EXPECT_EQ("hello world", value);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// a default FQDN value can be created and that this value can be
+/// overridden after the option has been created.
+TEST_F(OptionCustomTest, setFqdnData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "fqdn");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+ // Read a default FQDN value from the option.
+ std::string fqdn;
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ(".", fqdn);
+ // Try override the default FQDN value.
+ ASSERT_NO_THROW(option->writeFqdn("example.com"));
+ // Check that the value has been actually overridden.
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ("example.com.", fqdn);
+}
+
+// The purpose of this test is to verify that an option carrying
+// an array of boolean values can be created with no values
+// initially and that values can be later added to it.
+TEST_F(OptionCustomTest, setBooleanDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "boolean", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add some boolean values to it.
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+ ASSERT_NO_THROW(option->addArrayDataField(false));
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+
+ // Verify that the new data fields can be added.
+ bool value0 = false;
+ ASSERT_NO_THROW(value0 = option->readBoolean(0));
+ EXPECT_TRUE(value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ bool value2 = false;
+ ASSERT_NO_THROW(value2 = option->readBoolean(2));
+ EXPECT_TRUE(value2);
+}
+
+// The purpose of this test is to verify that am option carrying
+// an array of 16-bit signed integer values can be created with
+// no values initially and that the values can be later added to it.
+TEST_F(OptionCustomTest, setUint16DataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "uint16", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new data fields holding integer values.
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222));
+
+ // We should now have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that the values have been correctly set.
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(67, value0);
+ uint16_t value1 = 0;
+ ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1));
+ EXPECT_EQ(876, value1);
+ uint16_t value2 = 0;
+ ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2));
+ EXPECT_EQ(32222, value2);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv4 address can be created with no addresses and that
+/// multiple IPv4 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv4AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "my-space", "ipv4-address",
+ true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ // Expect that the array does not contain any data fields yet.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 IPv4 addresses.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3")));
+
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that all IP addresses have been set correctly.
+ IOAddress address0("127.0.0.1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("192.168.0.1", address0.toText());
+ IOAddress address1("127.0.0.1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("192.168.0.2", address1.toText());
+ IOAddress address2("127.0.0.1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("192.168.0.3", address2.toText());
+
+ // Add invalid address (IPv6 instead of IPv4).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("2001:db8:1::1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv6 address can be created with no addresses and that
+/// multiple IPv6 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address",
+ true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new IPv6 addresses into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3")));
+
+ // We should have now 3 addresses added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that they have correct values set.
+ IOAddress address0("::1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("2001:db8:1::1", address0.toText());
+ IOAddress address1("::1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("2001:db8:1::2", address1.toText());
+ IOAddress address2("::1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("2001:db8:1::3", address2.toText());
+
+ // Add invalid address (IPv4 instead of IPv6).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("192.168.0.1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of PSIDs can be created with no PSIDs and that PSIDs can be
+/// later added after the option has been created.
+TEST_F(OptionCustomTest, setPSIDPrefixArray) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "psid", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new PSIDs
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(4), PSID(1)));
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(0), PSID(123)));
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(1), PSID(1)));
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ PSIDTuple psid0 = option->readPsid(0);
+ EXPECT_EQ(4, psid0.first.asUnsigned());
+ EXPECT_EQ(1, psid0.second.asUint16());
+ });
+
+ ASSERT_NO_THROW({
+ PSIDTuple psid1 = option->readPsid(1);
+ EXPECT_EQ(0, psid1.first.asUnsigned());
+ EXPECT_EQ(0, psid1.second.asUint16());
+ });
+
+ ASSERT_NO_THROW({
+ PSIDTuple psid2 = option->readPsid(2);
+ EXPECT_EQ(1, psid2.first.asUnsigned());
+ EXPECT_EQ(1, psid2.second.asUint16());
+ });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of IPv6 prefixes can be created with no prefixes and that
+/// prefixes can be later added after the option has been created.
+TEST_F(OptionCustomTest, setIPv6PrefixDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "ipv6-prefix",
+ true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new IPv6 prefixes into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(64),
+ IOAddress("2001:db8:1::")));
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(32),
+ IOAddress("3001:1::")));
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(16),
+ IOAddress("3000::")));
+
+ // We should have now 3 addresses added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ PrefixTuple prefix0 = option->readPrefix(0);
+ EXPECT_EQ(64, prefix0.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix1 = option->readPrefix(1);
+ EXPECT_EQ(32, prefix1.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix1.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix2 = option->readPrefix(2);
+ EXPECT_EQ(16, prefix2.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix2.second.toText());
+ });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of DHCPv4 tuples can be created with no tuples and that
+/// tuples can be later added after the option has been created.
+TEST_F(OptionCustomTest, setTupleDataArray4) {
+ OptionDefinition opt_def("option-foo", 232, "my-space", "tuple", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new DHCPv4 tuple into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(std::string("hello")));
+ ASSERT_NO_THROW(option->addArrayDataField(std::string(" ")));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple.append("world");
+ ASSERT_NO_THROW(option->addArrayDataField(tuple));
+
+ // We should have now 3 tuples added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(0);
+ EXPECT_EQ("hello", value);
+ });
+
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(1);
+ EXPECT_EQ(" ", value);
+ });
+
+ ASSERT_NO_THROW({
+ OpaqueDataTuple value(OpaqueDataTuple::LENGTH_1_BYTE);
+ option->readTuple(value, 2);
+ EXPECT_EQ("world", value.getText());
+ });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of DHCPv6 tuples can be created with no tuples and that
+/// tuples can be later added after the option has been created.
+TEST_F(OptionCustomTest, setTupleDataArray6) {
+ OptionDefinition opt_def("option-foo", 1000, "my-space", "tuple", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new DHCPv6 tuple into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(std::string("hello")));
+ ASSERT_NO_THROW(option->addArrayDataField(std::string(" ")));
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple.append("world");
+ ASSERT_NO_THROW(option->addArrayDataField(tuple));
+
+ // We should have now 3 tuples added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(0);
+ EXPECT_EQ("hello", value);
+ });
+
+ ASSERT_NO_THROW({
+ std::string value = option->readTuple(1);
+ EXPECT_EQ(" ", value);
+ });
+
+ ASSERT_NO_THROW({
+ OpaqueDataTuple value(OpaqueDataTuple::LENGTH_2_BYTES);
+ option->readTuple(value, 2);
+ EXPECT_EQ("world", value.getText());
+ });
+}
+
+TEST_F(OptionCustomTest, setRecordData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record");
+
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("psid"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
+ ASSERT_NO_THROW(opt_def.addRecordField("tuple"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // The number of elements should be equal to number of elements
+ // in the record.
+ ASSERT_EQ(9, option->getDataFieldsNum());
+
+ // Check that the default values have been correctly set.
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(0, value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ std::string value2;
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ(".", value2);
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("0.0.0.0", value3.toText());
+ IOAddress value4("2001:db8:1::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("::", value4.toText());
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(0, value5.first.asUnsigned());
+ EXPECT_EQ(0, value5.second.asUint16());
+ PrefixTuple value6(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(0, value6.first.asUnsigned());
+ EXPECT_EQ("::", value6.second.toText());
+ std::string value7 = "abc";
+ // Tuple has no default value
+ EXPECT_THROW(option->readTuple(7), BadDataTypeCast);
+ std::string value8 = "xyz";
+ ASSERT_NO_THROW(value8 = option->readString(8));
+ EXPECT_TRUE(value8.empty());
+
+ // Override each value with a new value.
+ ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+ ASSERT_NO_THROW(option->writeBoolean(true, 1));
+ ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+ ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5));
+ ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
+ IOAddress("2001:db8:1::"), 6));
+ ASSERT_NO_THROW(option->writeTuple("foobar", 7));
+ ASSERT_NO_THROW(option->writeString("hello world", 8));
+
+ // Check that the new values have been correctly set.
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(1234, value0);
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("example.com.", value2);
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::100", value4.toText());
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(4, value5.first.asUnsigned());
+ EXPECT_EQ(8, value5.second.asUint16());
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(48, value6.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", value6.second.toText());
+ ASSERT_NO_THROW(value7 = option->readTuple(7));
+ EXPECT_EQ(value7, "foobar");
+ ASSERT_NO_THROW(value8 = option->readString(8));
+ EXPECT_EQ(value8, "hello world");
+}
+
+TEST_F(OptionCustomTest, setRecordArrayData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record", true);
+
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("psid"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
+ ASSERT_NO_THROW(opt_def.addRecordField("tuple"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // The number of elements should be equal to number of elements
+ // in the record.
+ ASSERT_EQ(9, option->getDataFieldsNum());
+
+ // Check that the default values have been correctly set.
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(0, value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ std::string value2;
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ(".", value2);
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("0.0.0.0", value3.toText());
+ IOAddress value4("2001:db8:1::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("::", value4.toText());
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(0, value5.first.asUnsigned());
+ EXPECT_EQ(0, value5.second.asUint16());
+ PrefixTuple value6(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(0, value6.first.asUnsigned());
+ EXPECT_EQ("::", value6.second.toText());
+ std::string value7 = "abc";
+ // Tuple has no default value
+ EXPECT_THROW(option->readTuple(7), BadDataTypeCast);
+ uint32_t value8;
+ ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8));
+ EXPECT_EQ(0, value8);
+
+ // Override each value with a new value.
+ ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+ ASSERT_NO_THROW(option->writeBoolean(true, 1));
+ ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+ ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5));
+ ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
+ IOAddress("2001:db8:1::"), 6));
+ ASSERT_NO_THROW(option->writeTuple("foobar", 7));
+ ASSERT_NO_THROW(option->writeInteger<uint32_t>(12345678, 8));
+ ASSERT_NO_THROW(option->addArrayDataField<uint32_t>(87654321));
+
+ // Check that the new values have been correctly set.
+ ASSERT_EQ(10, option->getDataFieldsNum());
+
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(1234, value0);
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("example.com.", value2);
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::100", value4.toText());
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(4, value5.first.asUnsigned());
+ EXPECT_EQ(8, value5.second.asUint16());
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(48, value6.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", value6.second.toText());
+ ASSERT_NO_THROW(value7 = option->readTuple(7));
+ EXPECT_EQ(value7, "foobar");
+ ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8));
+ EXPECT_EQ(12345678, value8);
+ uint32_t value9;
+ ASSERT_NO_THROW(value9 = option->readInteger<uint32_t>(9));
+ EXPECT_EQ(87654321, value9);
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv4 custom option works correctly.
+TEST_F(OptionCustomTest, pack4) {
+ OptionDefinition opt_def("OPTION_FOO", 234, "my-space", "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("uint8"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ OptionBuffer buf;
+ writeInt<uint8_t>(1, buf);
+ writeInt<uint16_t>(1000, buf);
+ writeInt<uint32_t>(100000, buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ util::OutputBuffer buf_out(7);
+ ASSERT_NO_THROW(option->pack(buf_out));
+ ASSERT_EQ(9, buf_out.getLength());
+
+ // The original buffer holds the option data but it lacks a header.
+ // We append data length and option code so as it can be directly
+ // compared with the output buffer that holds whole option.
+ buf.insert(buf.begin(), 7);
+ buf.insert(buf.begin(), 234);
+
+ // Validate the buffer.
+ EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv6 custom option works correctly.
+TEST_F(OptionCustomTest, pack6) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ OptionBuffer buf;
+ buf.push_back(1);
+ writeInt<uint16_t>(1000, buf);
+ writeString("hello world", buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ util::OutputBuffer buf_out(buf.size() + option->getHeaderLen());
+ ASSERT_NO_THROW(option->pack(buf_out));
+ ASSERT_EQ(buf.size() + option->getHeaderLen(), buf_out.getLength());
+
+ // The original buffer holds the option data but it lacks a header.
+ // We append data length and option code so as it can be directly
+ // compared with the output buffer that holds whole option.
+ OptionBuffer tmp;
+ writeInt<uint16_t>(1000, tmp);
+ writeInt<uint16_t>(buf.size(), tmp);
+ buf.insert(buf.begin(), tmp.begin(), tmp.end());
+
+ // Validate the buffer.
+ EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that unpack function works
+// correctly for a custom option.
+TEST_F(OptionCustomTest, unpack) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "ipv4-address",
+ true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("127.0.0.1"));
+ addresses.push_back(IOAddress("10.10.1.2"));
+
+ // Store the collection of IPv4 addresses into the buffer.
+ OptionBuffer buf;
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv4 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i], address);
+ }
+
+ // Remove all addresses we had added. We are going to replace
+ // them with a new set of addresses.
+ addresses.clear();
+
+ // Add new addresses.
+ addresses.push_back(IOAddress("10.1.2.3"));
+ addresses.push_back(IOAddress("85.26.43.234"));
+
+ // Clear the buffer as we need to store new addresses in it.
+ buf.clear();
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Perform 'unpack'.
+ ASSERT_NO_THROW(option->unpack(buf.begin(), buf.end()));
+
+ // Now we should have only 2 data fields.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Verify that the addresses have been overwritten.
+ for (int i = 0; i < 2; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i], address);
+ }
+}
+
+// The purpose of this test is to verify that unpack function works
+// correctly for a custom option with record and trailing array.
+TEST_F(OptionCustomTest, unpackRecordArray) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "my-space", "record", true);
+
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+
+ // Initialize reference data.
+ OptionBuffer buf;
+ writeInt<uint16_t>(8712, buf);
+
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("127.0.0.1"));
+ addresses.push_back(IOAddress("10.10.1.2"));
+
+ // Store the collection of IPv4 addresses into the buffer.
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 4 data fields.
+ ASSERT_EQ(4, option->getDataFieldsNum());
+
+ // We expect a 16 bit integer
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(8712, value0);
+
+ // ... and 3 IPv4 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i + 1));
+ EXPECT_EQ(addresses[i], address);
+ }
+
+ std::string text = option->toText();
+ EXPECT_EQ("type=231, len=014: 8712 (uint16) 192.168.0.1 (ipv4-address) "
+ "127.0.0.1 (ipv4-address) 10.10.1.2 (ipv4-address)", text);
+}
+
+// The purpose of this test is to verify that new data can be set for
+// a custom option.
+TEST_F(OptionCustomTest, initialize) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "my-space", "ipv6-address",
+ true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("2001:db8:1::3"));
+ addresses.push_back(IOAddress("::1"));
+ addresses.push_back(IOAddress("fe80::3"));
+
+ // Store the collection of IPv6 addresses into the buffer.
+ OptionBuffer buf;
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv6 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("fe80::4");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i], address);
+ }
+
+ // Clear addresses we had previously added.
+ addresses.clear();
+
+ // Store new addresses.
+ addresses.push_back(IOAddress("::1"));
+ addresses.push_back(IOAddress("fe80::10"));
+
+ // Clear the buffer as we need to store new addresses in it.
+ buf.clear();
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Replace the option data.
+ ASSERT_NO_THROW(option->initialize(buf.begin(), buf.end()));
+
+ // Now we should have only 2 data fields.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Check that it has been replaced.
+ for (int i = 0; i < 2; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i], address);
+ }
+}
+
+// The purpose of this test is to verify that an invalid index
+// value can't be used to access option data fields.
+TEST_F(OptionCustomTest, invalidIndex) {
+ OptionDefinition opt_def("OPTION_FOO", 999, "my-space", "uint32", true);
+
+ OptionBuffer buf;
+ for (int i = 0; i < 10; ++i) {
+ writeInt<uint32_t>(i, buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We expect that there are 10 uint32_t values stored in
+ // the option. The 10th element is accessed by index eq 9.
+ // Check that 9 is accepted.
+ EXPECT_NO_THROW(option->readInteger<uint32_t>(9));
+
+ // Check that index value beyond 9 is not accepted.
+ EXPECT_THROW(option->readInteger<uint32_t>(10), isc::OutOfRange);
+ EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
+}
+
+// This test checks that the custom option holding a record of data
+// fields can be presented in the textual format.
+TEST_F(OptionCustomTest, toTextRecord) {
+ OptionDefinition opt_def("foo", 123, "my-space", "record");
+ opt_def.addRecordField("uint32");
+ opt_def.addRecordField("string");
+
+ OptionCustom option(opt_def, Option::V4);
+ option.writeInteger<uint32_t>(10);
+ option.writeString("lorem ipsum", 1);
+
+ EXPECT_EQ("type=123, len=015: 10 (uint32) \"lorem ipsum\" (string)",
+ option.toText());
+}
+
+// This test checks that the custom option holding other data type
+// than "record" be presented in the textual format.
+TEST_F(OptionCustomTest, toTextNoRecord) {
+ OptionDefinition opt_def("foo", 234, "my-space", "uint32");
+
+ OptionCustom option(opt_def, Option::V6);
+ option.writeInteger<uint32_t>(123456);
+
+ OptionDefinition sub_opt_def("bar", 333, "my-space", "fqdn");
+ OptionCustomPtr sub_opt(new OptionCustom(sub_opt_def, Option::V6));
+ sub_opt->writeFqdn("myhost.example.org.");
+ option.addOption(sub_opt);
+
+ EXPECT_EQ("type=00234, len=00028: 123456 (uint32),\n"
+ "options:\n"
+ " type=00333, len=00020: \"myhost.example.org.\" (fqdn)",
+ option.toText());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc
new file mode 100644
index 0000000..6a70c04
--- /dev/null
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -0,0 +1,928 @@
+// Copyright (C) 2012-2021 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 <dhcp/option_data_types.h>
+#include <gtest/gtest.h>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+ IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
+/// @brief Test class for option data type utilities.
+class OptionDataTypesTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ OptionDataTypesTest() { }
+
+ /// @brief Write IP address into a buffer.
+ ///
+ /// @param address address to be written.
+ /// @param [out] buf output buffer.
+ void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ const std::vector<uint8_t>& vec = address.toBytes();
+ buf.insert(buf.end(), vec.begin(), vec.end());
+ }
+
+ /// @brief Write integer (signed or unsigned) into a buffer.
+ ///
+ /// @param value integer value.
+ /// @param [out] buf output buffer.
+ /// @tparam integer type.
+ template<typename T>
+ void writeInt(T value, std::vector<uint8_t>& buf) {
+ switch (sizeof(T)) {
+ case 4:
+ buf.push_back((value >> 24) & 0xFF);
+ /* falls through */
+ case 3:
+ buf.push_back((value >> 16) & 0xFF);
+ /* falls through */
+ case 2:
+ buf.push_back((value >> 8) & 0xFF);
+ /* falls through */
+ case 1:
+ buf.push_back(value & 0xFF);
+ break;
+ default:
+ // This loop is incorrectly compiled by some old g++?!
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
+ }
+ }
+
+ /// @brief Write a string into a buffer.
+ ///
+ /// @param value string to be written into a buffer.
+ /// @param buf output buffer.
+ void writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ buf.resize(buf.size() + value.size());
+ std::copy_backward(value.c_str(), value.c_str() + value.size(),
+ buf.end());
+ }
+};
+
+// The goal of this test is to verify that the getLabelCount returns the
+// correct number of labels in the domain name specified as a string
+// parameter.
+TEST_F(OptionDataTypesTest, getLabelCount) {
+ EXPECT_EQ(0, OptionDataTypeUtil::getLabelCount(""));
+ EXPECT_EQ(1, OptionDataTypeUtil::getLabelCount("."));
+ EXPECT_EQ(2, OptionDataTypeUtil::getLabelCount("example"));
+ EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com"));
+ EXPECT_EQ(3, OptionDataTypeUtil::getLabelCount("example.com."));
+ EXPECT_EQ(4, OptionDataTypeUtil::getLabelCount("myhost.example.com"));
+ EXPECT_THROW(OptionDataTypeUtil::getLabelCount(".abc."),
+ isc::dhcp::BadDataTypeCast);
+}
+
+// The goal of this test is to verify that an IPv4 address being
+// stored in a buffer (wire format) can be read into IOAddress
+// object.
+TEST_F(OptionDataTypesTest, readAddress) {
+ // Create some IPv4 address.
+ asiolink::IOAddress address("192.168.0.1");
+ // And store it in a buffer in a wire format.
+ std::vector<uint8_t> buf;
+ writeAddress(address, buf);
+
+ // Now, try to read the IP address with a utility function
+ // being under test.
+ asiolink::IOAddress address_out("127.0.0.1");
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET));
+
+ // Check that the read address matches address that
+ // we used as input.
+ EXPECT_EQ(address, address_out);
+
+ // Check that an attempt to read the buffer as IPv6 address
+ // causes an error as the IPv6 address needs at least 16 bytes
+ // long buffer.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Do another test like this for IPv6 address.
+ address = asiolink::IOAddress("2001:db8:1:0::1");
+ writeAddress(address, buf);
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6));
+ EXPECT_EQ(address, address_out);
+
+ // Truncate the buffer and expect an error to be reported when
+ // trying to read it.
+ buf.resize(buf.size() - 1);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The goal of this test is to verify that an IPv6 address
+// is properly converted to wire format and stored in a
+// buffer.
+TEST_F(OptionDataTypesTest, writeAddress) {
+ // Encode an IPv6 address 2001:db8:1::1 in wire format.
+ // This will be used as reference data to validate if
+ // an IPv6 address is stored in a buffer properly.
+ const uint8_t data[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1
+ };
+ std::vector<uint8_t> buf_in(data, data + sizeof(data));
+
+ // Create IPv6 address object.
+ asiolink::IOAddress address("2001:db8:1::1");
+ // Define the output buffer to write IP address to.
+ std::vector<uint8_t> buf_out;
+ // Write the address to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ // Make sure that input and output buffers have the same size
+ // so we can compare them.
+ ASSERT_EQ(buf_in.size(), buf_out.size());
+ // And finally compare them.
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+ buf_out.clear();
+
+ // Do similar test for IPv4 address.
+ address = asiolink::IOAddress("192.168.0.1");
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ ASSERT_EQ(4, buf_out.size());
+ // Verify that the IP address has been written correctly.
+ EXPECT_EQ(192, buf_out[0]);
+ EXPECT_EQ(168, buf_out[1]);
+ EXPECT_EQ(0, buf_out[2]);
+ EXPECT_EQ(1, buf_out[3]);
+}
+
+// The purpose of this test is to verify that binary data represented
+// as a string of hexadecimal digits can be written to a buffer.
+TEST_F(OptionDataTypesTest, writeBinary) {
+ // Prepare the reference data.
+ const char data[] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+ 0x6, 0x7, 0x8, 0x9, 0xA, 0xB
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+ // Create empty vector where binary data will be written to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(
+ OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf)
+ );
+ // Verify that the buffer contains valid data.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that the tuple value stored
+TEST_F(OptionDataTypesTest, readTuple) {
+ // The string
+ std::string value = "hello world";
+ // Create an input buffer.
+ std::vector<uint8_t> buf;
+ // DHCPv4 tuples use 1 byte length
+ writeInt<uint8_t>(static_cast<uint8_t>(value.size()), buf);
+ writeString(value, buf);
+
+ // Read the string from the buffer.
+ std::string result;
+ ASSERT_NO_THROW(
+ result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_1_BYTE);
+ );
+ // Check that it is valid.
+ EXPECT_EQ(value, result);
+
+ // Read the tuple from the buffer.
+ OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple4));
+ // Check that it is valid.
+ EXPECT_EQ(value, tuple4.getText());
+
+ buf.clear();
+
+ // DHCPv6 tuples use 2 byte length
+ writeInt<uint16_t>(static_cast<uint16_t>(value.size()), buf);
+ writeString(value, buf);
+
+ // Read the string from the buffer.
+ ASSERT_NO_THROW(
+ result = OptionDataTypeUtil::readTuple(buf, OpaqueDataTuple::LENGTH_2_BYTES);
+ );
+ // Check that it is valid.
+ EXPECT_EQ(value, result);
+
+ // Read the tuple from the buffer.
+ OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(OptionDataTypeUtil::readTuple(buf, tuple6));
+ // Check that it is valid.
+ EXPECT_EQ(value, tuple6.getText());
+}
+
+// The purpose of this test is to verify that a tuple value
+// are correctly encoded in a buffer (string version)
+TEST_F(OptionDataTypesTest, writeTupleString) {
+ // The string
+ std::string value = "hello world";
+ // Create an output buffer.
+ std::vector<uint8_t> buf;
+
+ // Encode it in DHCPv4
+ OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_1_BYTE, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 1, buf.size());
+ std::vector<uint8_t> expected;
+ writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+
+ buf.clear();
+
+ // Encode it in DHCPv6
+ OptionDataTypeUtil::writeTuple(value, OpaqueDataTuple::LENGTH_2_BYTES, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 2, buf.size());
+ expected.clear();
+ writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+}
+
+// The purpose of this test is to verify that a tuple value
+// are correctly encoded in a buffer (tuple version)
+TEST_F(OptionDataTypesTest, writeTuple) {
+ // The string
+ std::string value = "hello world";
+ // Create a DHCPv4 tuple
+ OpaqueDataTuple tuple4(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple4.append(value);
+ // Create an output buffer.
+ std::vector<uint8_t> buf;
+
+ // Encode it in DHCPv4
+ OptionDataTypeUtil::writeTuple(tuple4, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 1, buf.size());
+ std::vector<uint8_t> expected;
+ writeInt<uint8_t>(static_cast<uint8_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+
+ buf.clear();
+
+ // Create a DHCPv6 tuple
+ OpaqueDataTuple tuple6(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple6.append(value);
+
+ // Encode it in DHCPv6
+ OptionDataTypeUtil::writeTuple(tuple6, buf);
+
+ // Check that it is valid.
+ ASSERT_EQ(value.size() + 2, buf.size());
+ expected.clear();
+ writeInt<uint16_t>(static_cast<uint16_t>(value.size()), expected);
+ writeString(value, expected);
+ EXPECT_EQ(0, std::memcmp(&buf[0], &expected[0], buf.size()));
+}
+
+// The purpose of this test is to verify that the boolean value stored
+// in a buffer is correctly read from this buffer.
+TEST_F(OptionDataTypesTest, readBool) {
+ // Create an input buffer.
+ std::vector<uint8_t> buf;
+ // 'true' value is encoded as 1 ('false' is encoded as 0)
+ buf.push_back(1);
+
+ // Read the value from the buffer.
+ bool value = false;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ // Verify the value.
+ EXPECT_TRUE(value);
+ // Check if 'false' is read correctly either.
+ buf[0] = 0;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ EXPECT_FALSE(value);
+
+ // Check that invalid value causes exception.
+ buf[0] = 5;
+ ASSERT_THROW(
+ OptionDataTypeUtil::readBool(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that boolean values
+// are correctly encoded in a buffer as '1' for 'true' and
+// '0' for 'false' values.
+TEST_F(OptionDataTypesTest, writeBool) {
+ // Create a buffer we will write to.
+ std::vector<uint8_t> buf;
+ // Write the 'true' value to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf));
+ // We should now have 'true' value stored in a buffer.
+ ASSERT_EQ(1, buf.size());
+ EXPECT_EQ(buf[0], 1);
+ // Let's append another value to make sure that it is not always
+ // 'true' value being written.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf));
+ ASSERT_EQ(2, buf.size());
+ // Check that the first value has not changed.
+ EXPECT_EQ(buf[0], 1);
+ // Check the second value is correct.
+ EXPECT_EQ(buf[1], 0);
+}
+
+// The purpose of this test is to verify that the integer values
+// of different types are correctly read from a buffer.
+TEST_F(OptionDataTypesTest, readInt) {
+ std::vector<uint8_t> buf;
+
+ // Write an 8-bit unsigned integer value to the buffer.
+ writeInt<uint8_t>(129, buf);
+ uint8_t valueUint8 = 0;
+ // Read the value and check that it is valid.
+ ASSERT_NO_THROW(
+ valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf);
+ );
+ EXPECT_EQ(129, valueUint8);
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Clear the buffer for the next check we are going to do.
+ buf.clear();
+
+ // Test uint16_t value.
+ writeInt<uint16_t>(1234, buf);
+ uint16_t valueUint16 = 0;
+ ASSERT_NO_THROW(
+ valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf);
+ );
+ EXPECT_EQ(1234, valueUint16);
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Test uint32_t value.
+ writeInt<uint32_t>(56789, buf);
+ uint32_t valueUint32 = 0;
+ ASSERT_NO_THROW(
+ valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf);
+ );
+ EXPECT_EQ(56789, valueUint32);
+ buf.clear();
+
+ // Test int8_t value.
+ writeInt<int8_t>(-65, buf);
+ int8_t valueInt8 = 0;
+ ASSERT_NO_THROW(
+ valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf);
+ );
+ EXPECT_EQ(-65, valueInt8);
+ buf.clear();
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int16_t value.
+ writeInt<int16_t>(2345, buf);
+ int32_t valueInt16 = 0;
+ ASSERT_NO_THROW(
+ valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf);
+ );
+ EXPECT_EQ(2345, valueInt16);
+ buf.clear();
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int32_t value.
+ writeInt<int32_t>(-16543, buf);
+ int32_t valueInt32 = 0;
+ ASSERT_NO_THROW(
+ valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf);
+ );
+ EXPECT_EQ(-16543, valueInt32);
+
+ buf.clear();
+}
+
+// The purpose of this test is to verify that integer values of different
+// types are correctly written to a buffer.
+TEST_F(OptionDataTypesTest, writeInt) {
+ // Prepare the reference buffer.
+ const uint8_t data[] = {
+ 0x7F, // 127
+ 0x03, 0xFF, // 1023
+ 0x00, 0x00, 0x10, 0x00, // 4096
+ 0xFF, 0xFF, 0xFC, 0x00, // -1024
+ 0x02, 0x00, // 512
+ 0x81 // -127
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+
+ // Fill in the buffer with data. Each write operation appends an
+ // integer value. Eventually the buffer holds all values and should
+ // match with the reference buffer.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf));
+
+ // Make sure that the buffer has the same size as the reference
+ // buffer.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ // Compare buffers.
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that FQDN is read from
+// a buffer and returned as a text. The representation of the FQDN
+// in the buffer complies with RFC1035, section 3.1.
+// This test also checks that if invalid (truncated) FQDN is stored
+// in a buffer the appropriate exception is returned when trying to
+// read it as a string.
+TEST_F(OptionDataTypesTest, readFqdn) {
+ // The binary representation of the "mydomain.example.com".
+ // Values: 8, 7, 3 and 0 specify the lengths of subsequent
+ // labels within the FQDN.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Make a vector out of the data.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Read the buffer as FQDN and verify its correctness.
+ std::string fqdn;
+ EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
+ EXPECT_EQ("mydomain.example.com.", fqdn);
+
+ // By resizing the buffer we simulate truncation. The first
+ // length field (8) indicate that the first label's size is
+ // 8 but the actual buffer size is 5. Expect that conversion
+ // fails.
+ buf.resize(5);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Another special case: provide an empty buffer.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that FQDN's syntax is validated
+// and that FQDN is correctly written to a buffer in a format described
+// in RFC1035 section 3.1.
+TEST_F(OptionDataTypesTest, writeFqdn) {
+ // Create empty buffer. The FQDN will be written to it.
+ OptionBuffer buf;
+ // Write a domain name into the buffer in the format described
+ // in RFC1035 section 3.1. This function should not throw
+ // exception because domain name is well formed.
+ EXPECT_NO_THROW(
+ OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf)
+ );
+ // The length of the data is 22 (8 bytes for "mydomain" label,
+ // 7 bytes for "example" label, 3 bytes for "com" label and
+ // finally 4 bytes positions between labels where length
+ // information is stored.
+ ASSERT_EQ(22, buf.size());
+
+ // Verify that length fields between labels hold valid values.
+ EXPECT_EQ(8, buf[0]); // length of "mydomain"
+ EXPECT_EQ(7, buf[9]); // length of "example"
+ EXPECT_EQ(3, buf[17]); // length of "com"
+ EXPECT_EQ(0, buf[21]); // zero byte at the end.
+
+ // Verify that labels are valid.
+ std::string label0(buf.begin() + 1, buf.begin() + 9);
+ EXPECT_EQ("mydomain", label0);
+
+ std::string label1(buf.begin() + 10, buf.begin() + 17);
+ EXPECT_EQ("example", label1);
+
+ std::string label2(buf.begin() + 18, buf.begin() + 21);
+ EXPECT_EQ("com", label2);
+
+ // The tested function is supposed to append data to a buffer
+ // so let's check that it is a case by appending another domain.
+ OptionDataTypeUtil::writeFqdn("hello.net", buf);
+
+ // The buffer length should be now longer.
+ ASSERT_EQ(33, buf.size());
+
+ // Check the length fields for new labels being appended.
+ EXPECT_EQ(5, buf[22]);
+ EXPECT_EQ(3, buf[28]);
+
+ // And check that labels are ok.
+ std::string label3(buf.begin() + 23, buf.begin() + 28);
+ EXPECT_EQ("hello", label3);
+
+ std::string label4(buf.begin() + 29, buf.begin() + 32);
+ EXPECT_EQ("net", label4);
+
+ // Check that invalid (empty) FQDN is rejected and expected
+ // exception type is thrown.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Check another invalid domain name (with repeated dot).
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("example..com", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the variable length prefix
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readPrefix) {
+ std::vector<uint8_t> buf;
+
+ // Prefix 2001:db8::/64
+ writeInt<uint8_t>(64, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0, buf);
+
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(64, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix 2001:db8::/63
+ writeInt<uint8_t>(63, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(63, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix 2001:db8:c0000. Note that the last four bytes are filled with
+ // 0xFF (all bits set). When the prefix is read those non-significant
+ // bits (beyond prefix length) should be ignored (read as 0). Only first
+ // two bits of 0xFFFFFFFF should be read, thus 0xC000, rather than 0xFFFF.
+ writeInt<uint8_t>(34, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0xFFFFFFFF, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(34, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:c000::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix having a length of 0.
+ writeInt<uint8_t>(0, buf);
+ writeInt<uint16_t>(0x2001, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(0, prefix.first.asUnsigned());
+ EXPECT_EQ("::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix having a maximum length of 128.
+ writeInt<uint8_t>(128, buf);
+ buf.insert(buf.end(), 16, 0x11);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(128, prefix.first.asUnsigned());
+ EXPECT_EQ("1111:1111:1111:1111:1111:1111:1111:1111",
+ prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix length is greater than 128. This should result in an
+ // error.
+ writeInt<uint8_t>(129, buf);
+ writeInt<uint16_t>(0x3000, buf);
+ buf.resize(17);
+
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // Buffer truncated. Prefix length of 10 requires at least 2 bytes,
+ // but there is only one byte.
+ writeInt<uint8_t>(10, buf);
+ writeInt<uint8_t>(1, buf);
+
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the variable length prefix
+// is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePrefix) {
+ // Initialize a buffer and store some value in it. We'll want to make
+ // sure that the prefix being written will not override this value, but
+ // will rather be appended.
+ std::vector<uint8_t> buf(1, 1);
+
+ // Prefix 2001:db8:FFFF::/34 is equal to 2001:db8:C000::/34 because
+ // there are only 34 significant bits. All other bits must be zeroed.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(34),
+ IOAddress("2001:db8:FFFF::"),
+ buf));
+ ASSERT_EQ(7, buf.size());
+
+ EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+ EXPECT_EQ(34, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0x20, static_cast<unsigned>(buf[2]));
+ EXPECT_EQ(0x01, static_cast<unsigned>(buf[3]));
+ EXPECT_EQ(0x0D, static_cast<unsigned>(buf[4]));
+ EXPECT_EQ(0xB8, static_cast<unsigned>(buf[5]));
+ EXPECT_EQ(0xC0, static_cast<unsigned>(buf[6]));
+
+ buf.clear();
+
+ // Prefix length is 0. The entire prefix should be ignored.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(0),
+ IOAddress("2001:db8:FFFF::"),
+ buf));
+ ASSERT_EQ(1, buf.size());
+ EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+
+ buf.clear();
+
+ // Prefix having a maximum length of 128.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(128),
+ IOAddress("2001:db8::FF"),
+ buf));
+
+ // We should now have a 17 bytes long buffer. 1 byte goes for a prefix
+ // length field, the remaining ones hold the prefix.
+ ASSERT_EQ(17, buf.size());
+ // Because the prefix is 16 bytes long, we can simply use the
+ // IOAddress convenience function to read it back and compare
+ // it with the textual representation. This is simpler than
+ // comparing each byte separately.
+ IOAddress prefix_read = IOAddress::fromBytes(AF_INET6, &buf[1]);
+ EXPECT_EQ("2001:db8::ff", prefix_read.toText());
+
+ buf.clear();
+
+ // It is illegal to use IPv4 address as prefix.
+ EXPECT_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(4),
+ IOAddress("10.0.0.1"), buf),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the
+// PSID-len/PSID tuple can be read from a buffer.
+TEST_F(OptionDataTypesTest, readPsid) {
+ std::vector<uint8_t> buf;
+
+ // PSID length is 6 (bits)
+ writeInt<uint8_t>(6, buf);
+ // 0xA400 is represented as 1010010000000000b, which is equivalent
+ // of portset 0x29 (101001b).
+ writeInt<uint16_t>(0xA400, buf);
+
+ PSIDTuple psid;
+ ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(0x29, psid.second.asUint16());
+
+ buf.clear();
+
+ // PSID length is 16 (bits)
+ writeInt<uint8_t>(16, buf);
+ // 0xF000 is represented as 1111000000000000b, which is equivalent
+ // of portset 0xF000.
+ writeInt<uint16_t>(0xF000, buf);
+
+ ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+ EXPECT_EQ(16, psid.first.asUnsigned());
+ EXPECT_EQ(0xF000, psid.second.asUint16());
+
+ buf.clear();
+
+ // PSID length is 0, in which case PSID should be ignored.
+ writeInt<uint8_t>(0, buf);
+ // Let's put some junk into the PSID field to make sure it will
+ // be ignored.
+ writeInt<uint16_t>(0x1234, buf);
+ ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+ EXPECT_EQ(0, psid.first.asUnsigned());
+ EXPECT_EQ(0, psid.second.asUint16());
+
+ buf.clear();
+
+ // PSID length greater than 16 is not allowed.
+ writeInt<uint8_t>(17, buf);
+ writeInt<uint16_t>(0, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // PSID length is 3 bits, but the PSID value is 11 (1011b), so it
+ // is encoded on 4 bits, rather than 3.
+ writeInt<uint8_t>(3, buf);
+ writeInt<uint16_t>(0xB000, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // Buffer is truncated - 2 bytes instead of 3.
+ writeInt<uint8_t>(4, buf);
+ writeInt<uint8_t>(0xF0, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+
+ // Check for out of range values.
+ for (int i = 1; i < 16; ++i) {
+ buf.clear();
+ writeInt<uint8_t>(i, buf);
+ writeInt<uint16_t>(0xFFFF << (15 - i), buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+ }
+
+}
+
+// The purpose of this test is to verify that the PSID-len/PSID
+// tuple is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePsid) {
+ // Let's create a buffer with some data in it. We want to make
+ // sure that the existing data remain untouched when we write
+ // PSID to the buffer.
+ std::vector<uint8_t> buf(1, 1);
+ // PSID length is 4 (bits), PSID value is 8.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(4), PSID(8), buf));
+ ASSERT_EQ(4, buf.size());
+ // The byte which existed in the buffer should still hold the
+ // same value.
+ EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+ // PSID length should be written as specified in the function call.
+ EXPECT_EQ(4, static_cast<unsigned>(buf[1]));
+ // The PSID structure is as follows:
+ // UUUUPPPPPPPPPPPP, where "U" are useful bits on which we code
+ // the PSID. "P" are zero padded bits. The PSID value 8 is coded
+ // on four useful bits as '1000b'. That means that the PSID value
+ // encoded in the PSID field is: '1000000000000000b', which is
+ // 0x8000. The next two EXPECT_EQ statements verify that.
+ EXPECT_EQ(0x80, static_cast<unsigned>(buf[2]));
+ EXPECT_EQ(0x00, static_cast<unsigned>(buf[3]));
+
+ // Clear the buffer to make sure we don't append to the
+ // existing data.
+ buf.clear();
+
+ // The PSID length of 0 causes the PSID value (of 6) to be ignored.
+ // As a result, the buffer should hold only zeros.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(0), PSID(6), buf));
+ ASSERT_EQ(3, buf.size());
+ EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+ EXPECT_EQ(0, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0, static_cast<unsigned>(buf[2]));
+
+ buf.clear();
+
+ // Another test case, to verify that we can use the maximum length
+ // of PSID (16 bits).
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(16), PSID(5), buf));
+ ASSERT_EQ(3, buf.size());
+ // PSID length should be written with no change.
+ EXPECT_EQ(16, static_cast<unsigned>(buf[0]));
+ // Check PSID value.
+ EXPECT_EQ(0x00, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0x05, static_cast<unsigned>(buf[2]));
+
+ // PSID length of 17 exceeds the maximum allowed value of 16.
+ EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(17), PSID(1), buf),
+ OutOfRange);
+
+ // Check for out of range values.
+ for (int i = 1; i < 16; ++i) {
+ EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(i), PSID(1 << i), buf),
+ BadDataTypeCast);
+ }
+}
+
+// The purpose of this test is to verify that the string
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readString) {
+
+ // Prepare a buffer with some string in it.
+ std::vector<uint8_t> buf;
+ writeString("hello world", buf);
+
+ // Read the string from the buffer.
+ std::string value;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readString(buf);
+ );
+ // Check that it is valid.
+ EXPECT_EQ("hello world", value);
+
+ // Only nulls should throw.
+ OptionBuffer buffer = { 0, 0 };
+ ASSERT_THROW(OptionDataTypeUtil::readString(buffer), isc::OutOfRange);
+
+ // One trailing null should trim off.
+ buffer = {'o', 'n', 'e', 0 };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(3, value.length());
+ EXPECT_EQ(value, std::string("one"));
+
+ // More than one trailing null should trim off.
+ buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(5, value.length());
+ EXPECT_EQ(value, std::string("three"));
+
+ // Embedded null should be left in place.
+ buffer = { 'e', 'm', 0, 'b', 'e', 'd' };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(6, value.length());
+ EXPECT_EQ(value, (std::string{"em\0bed", 6}));
+
+ // Leading null should be left in place.
+ buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' };
+ ASSERT_NO_THROW(value = OptionDataTypeUtil::readString(buffer));
+ EXPECT_EQ(8, value.length());
+ EXPECT_EQ(value, (std::string{"\0leading", 8}));
+}
+
+// The purpose of this test is to verify that a string can be
+// stored in a buffer correctly.
+TEST_F(OptionDataTypesTest, writeString) {
+ // Prepare a buffer with a reference data.
+ std::vector<uint8_t> buf_ref;
+ writeString("hello world!", buf_ref);
+ // Create empty buffer we will write to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf));
+ // Compare two buffers.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
new file mode 100644
index 0000000..e19523f
--- /dev/null
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -0,0 +1,2108 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option4_dnr.h>
+#include <dhcp/option6_dnr.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief OptionDefinition test class.
+///
+/// This class does not do anything useful but we keep
+/// it around for the future.
+class OptionDefinitionTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ OptionDefinitionTest() { }
+
+};
+
+// The purpose of this test is to verify that OptionDefinition
+// constructor initializes its members correctly.
+TEST_F(OptionDefinitionTest, constructor) {
+ // Specify the option data type as string. This should get converted
+ // to enum value returned by getType().
+ OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName());
+ EXPECT_EQ(1, opt_def1.getCode());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def1.getOptionSpaceName());
+ EXPECT_EQ(OPT_STRING_TYPE, opt_def1.getType());
+ EXPECT_FALSE(opt_def1.getArrayType());
+ EXPECT_TRUE(opt_def1.getEncapsulatedSpace().empty());
+ EXPECT_NO_THROW(opt_def1.validate());
+
+ // Specify the option data type as an enum value.
+ OptionDefinition opt_def2("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT,
+ DHCP6_OPTION_SPACE, OPT_EMPTY_TYPE);
+ EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName());
+ EXPECT_EQ(14, opt_def2.getCode());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def2.getOptionSpaceName());
+ EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType());
+ EXPECT_FALSE(opt_def2.getArrayType());
+ EXPECT_TRUE(opt_def2.getEncapsulatedSpace().empty());
+ EXPECT_NO_THROW(opt_def2.validate());
+
+ // Specify encapsulated option space name and option data type
+ // as enum value.
+ OptionDefinition opt_def3("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+ DHCP6_OPTION_SPACE, OPT_UINT32_TYPE, "isc");
+ EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def3.getName());
+ EXPECT_EQ(D6O_VENDOR_OPTS, opt_def3.getCode());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def3.getOptionSpaceName());
+ EXPECT_EQ(OPT_UINT32_TYPE, opt_def3.getType());
+ EXPECT_FALSE(opt_def3.getArrayType());
+ EXPECT_EQ("isc", opt_def3.getEncapsulatedSpace());
+ EXPECT_NO_THROW(opt_def3.validate());
+
+ // Specify encapsulated option space name and option data type
+ // as string value.
+ OptionDefinition opt_def4("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+ DHCP6_OPTION_SPACE, "uint32", "isc");
+ EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def4.getName());
+ EXPECT_EQ(D6O_VENDOR_OPTS, opt_def4.getCode());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def4.getOptionSpaceName());
+ EXPECT_EQ(OPT_UINT32_TYPE, opt_def4.getType());
+ EXPECT_FALSE(opt_def4.getArrayType());
+ EXPECT_EQ("isc", opt_def4.getEncapsulatedSpace());
+ EXPECT_NO_THROW(opt_def4.validate());
+
+ // Check if it is possible to set that option is an array.
+ OptionDefinition opt_def5("OPTION_NIS_SERVERS", 27,
+ DHCP6_OPTION_SPACE,
+ OPT_IPV6_ADDRESS_TYPE,
+ true);
+ EXPECT_EQ("OPTION_NIS_SERVERS", opt_def5.getName());
+ EXPECT_EQ(27, opt_def5.getCode());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, opt_def5.getOptionSpaceName());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def5.getType());
+ EXPECT_TRUE(opt_def5.getArrayType());
+ EXPECT_NO_THROW(opt_def5.validate());
+
+ // The created object is invalid if invalid data type is specified but
+ // constructor shouldn't throw exception. The object is validated after
+ // it has been created.
+ EXPECT_NO_THROW(
+ OptionDefinition opt_def6("OPTION_SERVERID",
+ OPT_UNKNOWN_TYPE + 10,
+ DHCP6_OPTION_SPACE,
+ OPT_STRING_TYPE);
+ );
+}
+
+// This test checks that the copy constructor works properly.
+TEST_F(OptionDefinitionTest, copyConstructor) {
+ OptionDefinition opt_def("option-foo", 27, "my-space", "record", true);
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ OptionDefinition opt_def_copy(opt_def);
+ EXPECT_EQ("option-foo", opt_def_copy.getName());
+ EXPECT_EQ(27, opt_def_copy.getCode());
+ EXPECT_EQ("my-space", opt_def_copy.getOptionSpaceName());
+ EXPECT_TRUE(opt_def_copy.getArrayType());
+ EXPECT_TRUE(opt_def_copy.getEncapsulatedSpace().empty());
+ ASSERT_EQ(OPT_RECORD_TYPE, opt_def_copy.getType());
+ const OptionDefinition::RecordFieldsCollection fields =
+ opt_def_copy.getRecordFields();
+ ASSERT_EQ(2, fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, fields[0]);
+ EXPECT_EQ(OPT_STRING_TYPE, fields[1]);
+
+ // Let's make another test to check if encapsulated option space is
+ // copied properly.
+ OptionDefinition opt_def2("option-bar", 30, "my-space", "uint32", "isc");
+ OptionDefinition opt_def_copy2(opt_def2);
+ EXPECT_EQ("option-bar", opt_def_copy2.getName());
+ EXPECT_EQ(30, opt_def_copy2.getCode());
+ EXPECT_EQ("my-space", opt_def_copy2.getOptionSpaceName());
+ EXPECT_FALSE(opt_def_copy2.getArrayType());
+ EXPECT_EQ(OPT_UINT32_TYPE, opt_def_copy2.getType());
+ EXPECT_EQ("isc", opt_def_copy2.getEncapsulatedSpace());
+}
+
+// This test checks that the factory function taking string option
+// data type as argument creates a valid instance.
+TEST_F(OptionDefinitionTest, createStringType) {
+ auto def = OptionDefinition::create("option-foo", 123, "my-space",
+ "uint16", "isc");
+ ASSERT_TRUE(def);
+
+ EXPECT_EQ("option-foo", def->getName());
+ EXPECT_EQ(123, def->getCode());
+ EXPECT_EQ("my-space", def->getOptionSpaceName());
+ EXPECT_EQ(OPT_UINT16_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ("isc", def->getEncapsulatedSpace());
+}
+
+// This test checks that the factory function taking enum option
+// data type as argument creates a valid instance.
+TEST_F(OptionDefinitionTest, createEnumType) {
+ auto def = OptionDefinition::create("option-foo", 123, "my-space",
+ OPT_UINT16_TYPE, "isc");
+ ASSERT_TRUE(def);
+
+ EXPECT_EQ("option-foo", def->getName());
+ EXPECT_EQ(123, def->getCode());
+ EXPECT_EQ("my-space", def->getOptionSpaceName());
+ EXPECT_EQ(OPT_UINT16_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ("isc", def->getEncapsulatedSpace());
+}
+
+// This test checks that the factory function creating an array and
+// taking string option data type as argument creates a valid instance.
+TEST_F(OptionDefinitionTest, createStringTypeArray) {
+ auto def = OptionDefinition::create("option-foo", 123, "my-space",
+ "uint16", true);
+ ASSERT_TRUE(def);
+
+ EXPECT_EQ("option-foo", def->getName());
+ EXPECT_EQ(123, def->getCode());
+ EXPECT_EQ("my-space", def->getOptionSpaceName());
+ EXPECT_EQ(OPT_UINT16_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+// This test checks that the factory function creating an array and
+// taking enum option data type as argument creates a valid instance.
+TEST_F(OptionDefinitionTest, createEnumTypeArray) {
+ auto def = OptionDefinition::create("option-foo", 123, "my-space",
+ OPT_UINT16_TYPE, true);
+ ASSERT_TRUE(def);
+
+ EXPECT_EQ("option-foo", def->getName());
+ EXPECT_EQ(123, def->getCode());
+ EXPECT_EQ("my-space", def->getOptionSpaceName());
+ EXPECT_EQ(OPT_UINT16_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+// This test checks that two option definitions may be compared for equality.
+TEST_F(OptionDefinitionTest, equality) {
+ // Equal definitions.
+ EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ == OptionDefinition("option-foo", 5, "my-space", "uint16", false));
+ EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ != OptionDefinition("option-foo", 5, "my-space", "uint16", false));
+
+ // Differ by name.
+ EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ == OptionDefinition("option-foobar", 5, "my-space", "uint16", false));
+ EXPECT_FALSE(OptionDefinition("option-bar", 5, "my-space", "uint16", false)
+ == OptionDefinition("option-foo", 5, "my-space", "uint16", false));
+ EXPECT_TRUE(OptionDefinition("option-bar", 5, "my-space", "uint16", false)
+ != OptionDefinition("option-foo", 5, "my-space", "uint16", false));
+
+ // Differ by option code.
+ EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ == OptionDefinition("option-foo", 6, "my-space", "uint16", false));
+ EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ != OptionDefinition("option-foo", 6, "my-space", "uint16", false));
+
+ // Differ by option space name.
+ EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ == OptionDefinition("option-foo", 5, "mi-space", "uint16", false));
+ EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ != OptionDefinition("option-foo", 5, "mi-space", "uint16", false));
+
+ // Differ by type of the data.
+ EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ == OptionDefinition("option-foo", 5, "my-space", "uint32", false));
+ EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ != OptionDefinition("option-foo", 5, "my-space", "uint32", false));
+
+ // Differ by array-type property.
+ EXPECT_FALSE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ == OptionDefinition("option-foo", 5, "my-space", "uint16", true));
+ EXPECT_TRUE(OptionDefinition("option-foo", 5, "my-space", "uint16", false)
+ != OptionDefinition("option-foo", 5, "my-space", "uint16", true));
+
+ // Differ by record fields.
+ OptionDefinition def1("option-foo", 5, "my-space", "record");
+ OptionDefinition def2("option-foo", 5, "my-space", "record");
+
+ // There are no record fields specified yet, so initially they have
+ // to be equal.
+ ASSERT_TRUE(def1 == def2);
+ ASSERT_FALSE(def1 != def2);
+
+ // Add some record fields.
+ ASSERT_NO_THROW(def1.addRecordField("uint16"));
+ ASSERT_NO_THROW(def2.addRecordField("uint16"));
+
+ // Definitions should still remain equal.
+ ASSERT_TRUE(def1 == def2);
+ ASSERT_FALSE(def1 != def2);
+
+ // Add additional record field to one of the definitions but not the
+ // other. They should now be unequal.
+ ASSERT_NO_THROW(def1.addRecordField("string"));
+ ASSERT_FALSE(def1 == def2);
+ ASSERT_TRUE(def1 != def2);
+
+ // Add the same record field to the other definition. They should now
+ // be equal again.
+ ASSERT_NO_THROW(def2.addRecordField("string"));
+ EXPECT_TRUE(def1 == def2);
+ EXPECT_FALSE(def1 != def2);
+}
+
+// The purpose of this test is to verify that various data fields
+// can be specified for an option definition when this definition
+// is marked as 'record' and that fields can't be added if option
+// definition is not marked as 'record'.
+TEST_F(OptionDefinitionTest, addRecordField) {
+ // We can only add fields to record if the option type has been
+ // specified as 'record'. We try all other types but 'record'
+ // here and expect exception to be thrown.
+ for (int i = 0; i < OPT_UNKNOWN_TYPE; ++i) {
+ // Do not try for 'record' type because this is the only
+ // type for which adding record will succeed.
+ if (i == OPT_RECORD_TYPE) {
+ continue;
+ }
+ OptionDefinition opt_def("OPTION_IAADDR", 5, DHCP6_OPTION_SPACE,
+ static_cast<OptionDataType>(i));
+ EXPECT_THROW(opt_def.addRecordField("uint8"), isc::InvalidOperation);
+ }
+
+ // Positive scenario starts here.
+ OptionDefinition opt_def("OPTION_IAADDR", 5, DHCP6_OPTION_SPACE, "record");
+ EXPECT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
+ // It should not matter if we specify field type by its name or using enum.
+ EXPECT_NO_THROW(opt_def.addRecordField(OPT_UINT32_TYPE));
+
+ // Check what we have actually added.
+ OptionDefinition::RecordFieldsCollection fields = opt_def.getRecordFields();
+ ASSERT_EQ(3, fields.size());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, fields[0]);
+ EXPECT_EQ(OPT_UINT32_TYPE, fields[1]);
+ EXPECT_EQ(OPT_UINT32_TYPE, fields[2]);
+
+ // Let's try some more negative scenarios: use invalid data types.
+ EXPECT_THROW(opt_def.addRecordField("unknown_type_xyz"), isc::BadValue);
+ OptionDataType invalid_type =
+ static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + 10);
+ EXPECT_THROW(opt_def.addRecordField(invalid_type), isc::BadValue);
+
+ // It is bad if we use 'record' option type but don't specify
+ // at least two fields.
+ OptionDefinition opt_def2("OPTION_EMPTY_RECORD", 100, "my-space", "record");
+ EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
+ opt_def2.addRecordField("uint8");
+ EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
+ opt_def2.addRecordField("uint32");
+ EXPECT_NO_THROW(opt_def2.validate());
+}
+
+// The purpose of this test is to check that validate() function
+// reports errors for invalid option definitions.
+TEST_F(OptionDefinitionTest, validate) {
+ // Not supported option type string is not allowed.
+ OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "non-existent-type");
+ EXPECT_THROW(opt_def1.validate(), MalformedOptionDefinition);
+
+ // Not supported option type enum value is not allowed.
+ OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, OPT_UNKNOWN_TYPE);
+ EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def3("OPTION_CLIENTID", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE,
+ static_cast<OptionDataType>(OPT_UNKNOWN_TYPE
+ + 2));
+ EXPECT_THROW(opt_def3.validate(), MalformedOptionDefinition);
+
+ // Empty option name is not allowed.
+ OptionDefinition opt_def4("", D6O_CLIENTID, DHCP6_OPTION_SPACE, "string");
+ EXPECT_THROW(opt_def4.validate(), MalformedOptionDefinition);
+
+ // Option name must not contain spaces.
+ OptionDefinition opt_def5(" OPTION_CLIENTID", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
+
+ // Option name must not contain spaces.
+ OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
+
+ // Option name may contain lower case letters.
+ OptionDefinition opt_def7("option_clientid", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_NO_THROW(opt_def7.validate());
+
+ // Using digits in option name is legal.
+ OptionDefinition opt_def8("option_123", D6O_CLIENTID, DHCP6_OPTION_SPACE,
+ "string");
+ EXPECT_NO_THROW(opt_def8.validate());
+
+ // Using hyphen is legal.
+ OptionDefinition opt_def9("option-clientid", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_NO_THROW(opt_def9.validate());
+
+ // Using hyphen or underscore at the beginning or at the end
+ // of the option name is not allowed.
+ OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string");
+ EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition);
+
+ // Empty option space name is not allowed.
+ OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "", "string");
+ EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition);
+
+ // Option name must not contain spaces.
+ OptionDefinition opt_def15("OPTION_CLIENTID", D6O_CLIENTID, " space",
+ "string");
+ EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition);
+
+ // Option name must not contain spaces.
+ OptionDefinition opt_def16("OPTION_CLIENTID", D6O_CLIENTID, "my space",
+ "string");
+ EXPECT_THROW(opt_def16.validate(), MalformedOptionDefinition);
+
+ // Option name may contain upper case letters.
+ OptionDefinition opt_def17("OPTION_CLIENTID", D6O_CLIENTID, "SPACE",
+ "string");
+ EXPECT_NO_THROW(opt_def17.validate());
+
+ // Using digits in option name is legal.
+ OptionDefinition opt_def18("OPTION_CLIENTID", D6O_CLIENTID, "space_123",
+ "string");
+ EXPECT_NO_THROW(opt_def18.validate());
+
+ // Using hyphen is legal.
+ OptionDefinition opt_def19("OPTION_CLIENTID", D6O_CLIENTID, "my-space",
+ "string");
+ EXPECT_NO_THROW(opt_def19.validate());
+
+ // Using hyphen or underscore at the beginning or at the end
+ // of the option name is not allowed.
+ OptionDefinition opt_def20("OPTION_CLIENTID", D6O_CLIENTID, "-space",
+ "string");
+ EXPECT_THROW(opt_def20.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def21("OPTION_CLIENTID", D6O_CLIENTID, "_space",
+ "string");
+ EXPECT_THROW(opt_def21.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def22("OPTION_CLIENTID", D6O_CLIENTID, "space_",
+ "string");
+ EXPECT_THROW(opt_def22.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def23("OPTION_CLIENTID", D6O_CLIENTID, "space-",
+ "string");
+ EXPECT_THROW(opt_def23.validate(), MalformedOptionDefinition);
+
+ // Having array of strings does not make sense because there is no way
+ // to determine string's length.
+ OptionDefinition opt_def24("OPTION_CLIENTID", D6O_CLIENTID,
+ DHCP6_OPTION_SPACE, "string", true);
+ EXPECT_THROW(opt_def24.validate(), MalformedOptionDefinition);
+
+ // It does not make sense to have string field within the record before
+ // other fields because there is no way to determine the length of this
+ // string and thus there is no way to determine where the other field
+ // begins.
+ OptionDefinition opt_def25("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ DHCP6_OPTION_SPACE, "record");
+ opt_def25.addRecordField("string");
+ opt_def25.addRecordField("uint16");
+ EXPECT_THROW(opt_def25.validate(), MalformedOptionDefinition);
+
+ // ... but it is ok if the string value is the last one.
+ OptionDefinition opt_def26("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ DHCP6_OPTION_SPACE, "record");
+ opt_def26.addRecordField("uint8");
+ opt_def26.addRecordField("string");
+ EXPECT_NO_THROW(opt_def26.validate());
+
+ // ... at least if it is not an array.
+ OptionDefinition opt_def27("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ DHCP6_OPTION_SPACE, "record", true);
+ opt_def27.addRecordField("uint8");
+ opt_def27.addRecordField("string");
+ EXPECT_THROW(opt_def27.validate(), MalformedOptionDefinition);
+
+ // Check invalid encapsulated option space name.
+ OptionDefinition opt_def28("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+ DHCP6_OPTION_SPACE, "uint32",
+ "invalid%space%name");
+ EXPECT_THROW(opt_def28.validate(), MalformedOptionDefinition);
+}
+
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv6 addresses will return an instance
+// of option with a list of IPv6 addresses.
+TEST_F(OptionDefinitionTest, ipv6AddressArray) {
+ OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
+ DHCP6_OPTION_SPACE, "ipv6-address", true);
+
+ // Create a list of some V6 addresses.
+ std::vector<asiolink::IOAddress> addrs;
+ addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329"));
+ addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319"));
+ addrs.push_back(asiolink::IOAddress("::1"));
+ addrs.push_back(asiolink::IOAddress("::2"));
+
+ // Write addresses to the buffer.
+ OptionBuffer buf(addrs.size() * asiolink::V6ADDRESS_LEN);
+ for (size_t i = 0; i < addrs.size(); ++i) {
+ const std::vector<uint8_t>& vec = addrs[i].toBytes();
+ ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size());
+ std::copy(vec.begin(), vec.end(),
+ buf.begin() + i * asiolink::V6ADDRESS_LEN);
+ }
+ // Create DHCPv6 option from this buffer. Once option is created it is
+ // supposed to have internal list of addresses that it parses out from
+ // the provided buffer.
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option6AddrLst));
+ boost::shared_ptr<Option6AddrLst> option_cast_v6 =
+ boost::static_pointer_cast<Option6AddrLst>(option_v6);
+ ASSERT_TRUE(option_cast_v6);
+ // Get the list of parsed addresses from the option object.
+ std::vector<asiolink::IOAddress> addrs_returned =
+ option_cast_v6->getAddresses();
+ // The list of addresses must exactly match addresses that we
+ // stored in the buffer to create the option from it.
+ EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
+
+ // The provided buffer's length must be a multiple of V6 address length.
+ // Let's extend the buffer by one byte so as this condition is not
+ // fulfilled anymore.
+ buf.insert(buf.end(), 1, 1);
+ // It should throw exception then.
+ EXPECT_THROW(
+ opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf),
+ InvalidOptionValue
+ );
+}
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv6 addresses will return an instance
+// of option with a list of IPv6 addresses. Array of IPv6 addresses
+// is specified as a vector of strings (each string represents single
+// IPv6 address).
+TEST_F(OptionDefinitionTest, ipv6AddressArrayTokenized) {
+ OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
+ DHCP6_OPTION_SPACE, "ipv6-address", true);
+
+ // Create a vector of some V6 addresses.
+ std::vector<asiolink::IOAddress> addrs;
+ addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329"));
+ addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319"));
+ addrs.push_back(asiolink::IOAddress("::1"));
+ addrs.push_back(asiolink::IOAddress("::2"));
+
+ // Create a vector of strings representing addresses given above.
+ std::vector<std::string> addrs_str;
+ for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
+ it != addrs.end(); ++it) {
+ addrs_str.push_back(it->toText());
+ }
+
+ // Create DHCPv6 option using the list of IPv6 addresses given in the
+ // string form.
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS,
+ addrs_str);
+ );
+ // Non-null pointer option is supposed to be returned and it
+ // should have Option6AddrLst type.
+ ASSERT_TRUE(option_v6);
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option6AddrLst));
+ // Cast to the actual option type to get IPv6 addresses from it.
+ boost::shared_ptr<Option6AddrLst> option_cast_v6 =
+ boost::static_pointer_cast<Option6AddrLst>(option_v6);
+ // Check that cast was successful.
+ ASSERT_TRUE(option_cast_v6);
+ // Get the list of parsed addresses from the option object.
+ std::vector<asiolink::IOAddress> addrs_returned =
+ option_cast_v6->getAddresses();
+ // Returned addresses must match the addresses that have been used to create
+ // the option instance.
+ EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
+}
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv4 addresses will return an instance
+// of option with a list of IPv4 addresses.
+TEST_F(OptionDefinitionTest, ipv4AddressArray) {
+ OptionDefinition opt_def("OPTION_NAME_SERVERS", D6O_NIS_SERVERS,
+ DHCP6_OPTION_SPACE, "ipv4-address", true);
+
+ // Create a list of some V6 addresses.
+ std::vector<asiolink::IOAddress> addrs;
+ addrs.push_back(asiolink::IOAddress("192.168.0.1"));
+ addrs.push_back(asiolink::IOAddress("172.16.1.1"));
+ addrs.push_back(asiolink::IOAddress("127.0.0.1"));
+ addrs.push_back(asiolink::IOAddress("213.41.23.12"));
+
+ // Write addresses to the buffer.
+ OptionBuffer buf(addrs.size() * asiolink::V4ADDRESS_LEN);
+ for (size_t i = 0; i < addrs.size(); ++i) {
+ const std::vector<uint8_t> vec = addrs[i].toBytes();
+ ASSERT_EQ(asiolink::V4ADDRESS_LEN, vec.size());
+ std::copy(vec.begin(), vec.end(),
+ buf.begin() + i * asiolink::V4ADDRESS_LEN);
+ }
+ // Create DHCPv6 option from this buffer. Once option is created it is
+ // supposed to have internal list of addresses that it parses out from
+ // the provided buffer.
+ OptionPtr option_v4;
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_NAME_SERVERS, buf)
+ );
+ const Option* optptr = option_v4.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option4AddrLst));
+ // Get the list of parsed addresses from the option object.
+ boost::shared_ptr<Option4AddrLst> option_cast_v4 =
+ boost::static_pointer_cast<Option4AddrLst>(option_v4);
+ std::vector<asiolink::IOAddress> addrs_returned =
+ option_cast_v4->getAddresses();
+ // The list of addresses must exactly match addresses that we
+ // stored in the buffer to create the option from it.
+ EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
+
+ // The provided buffer's length must be a multiple of V4 address length.
+ // Let's extend the buffer by one byte so as this condition is not
+ // fulfilled anymore.
+ buf.insert(buf.end(), 1, 1);
+ // It should throw exception then.
+ EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, buf),
+ InvalidOptionValue);
+}
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv4 addresses will return an instance
+// of option with a list of IPv4 addresses. The array of IPv4 addresses
+// is specified as a vector of strings (each string represents single
+// IPv4 address).
+TEST_F(OptionDefinitionTest, ipv4AddressArrayTokenized) {
+ OptionDefinition opt_def("OPTION_NIS_SERVERS", DHO_NIS_SERVERS,
+ DHCP4_OPTION_SPACE, "ipv4-address", true);
+
+ // Create a vector of some V6 addresses.
+ std::vector<asiolink::IOAddress> addrs;
+ addrs.push_back(asiolink::IOAddress("192.168.0.1"));
+ addrs.push_back(asiolink::IOAddress("172.16.1.1"));
+ addrs.push_back(asiolink::IOAddress("127.0.0.1"));
+ addrs.push_back(asiolink::IOAddress("213.41.23.12"));
+
+ // Create a vector of strings representing addresses given above.
+ std::vector<std::string> addrs_str;
+ for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
+ it != addrs.end(); ++it) {
+ addrs_str.push_back(it->toText());
+ }
+
+ // Create DHCPv4 option using the list of IPv4 addresses given in the
+ // string form.
+ OptionPtr option_v4;
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS,
+ addrs_str);
+ );
+ // Non-null pointer option is supposed to be returned and it
+ // should have Option6AddrLst type.
+ ASSERT_TRUE(option_v4);
+ const Option* optptr = option_v4.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option4AddrLst));
+ // Cast to the actual option type to get IPv4 addresses from it.
+ boost::shared_ptr<Option4AddrLst> option_cast_v4 =
+ boost::static_pointer_cast<Option4AddrLst>(option_v4);
+ // Check that cast was successful.
+ ASSERT_TRUE(option_cast_v4);
+ // Get the list of parsed addresses from the option object.
+ std::vector<asiolink::IOAddress> addrs_returned =
+ option_cast_v4->getAddresses();
+ // Returned addresses must match the addresses that have been used to create
+ // the option instance.
+ EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
+}
+
+// The purpose of this test is to verify that option definition for
+// 'empty' option can be created and that it returns 'empty' option.
+TEST_F(OptionDefinitionTest, empty) {
+ OptionDefinition opt_def("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT,
+ DHCP6_OPTION_SPACE, "empty");
+
+ // Create option instance and provide empty buffer as expected.
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option));
+ // Expect 'empty' DHCPv6 option.
+ EXPECT_EQ(Option::V6, option_v6->getUniverse());
+ EXPECT_EQ(4, option_v6->getHeaderLen());
+ EXPECT_EQ(0, option_v6->getData().size());
+
+ // Repeat the same test scenario for DHCPv4 option.
+ OptionPtr option_v4;
+ ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, OptionBuffer()));
+ // Expect 'empty' DHCPv4 option.
+ EXPECT_EQ(Option::V4, option_v4->getUniverse());
+ EXPECT_EQ(2, option_v4->getHeaderLen());
+ EXPECT_EQ(0, option_v4->getData().size());
+}
+
+// The purpose of this test is to verify that when the empty option encapsulates
+// some option space, an instance of the OptionCustom is returned and its
+// suboptions are decoded.
+TEST_F(OptionDefinitionTest, emptyWithSuboptions) {
+ // Create an instance of the 'empty' option definition. This option
+ // encapsulates 'option-foo-space' so when we create a new option
+ // with this definition the OptionCustom should be returned. The
+ // Option Custom is generic option which support variety of formats
+ // and supports decoding suboptions.
+ OptionDefinition opt_def("option-foo", 1024, "my-space", "empty",
+ "option-foo-space");
+ // Define a suboption.
+ const uint8_t subopt_data[] = {
+ 0x04, 0x01, // Option code 1025
+ 0x00, 0x04, // Option len = 4
+ 0x01, 0x02, 0x03, 0x04 // Option data
+ };
+
+ // Create an option, having option code 1024 from the definition. Pass
+ // the option buffer containing suboption.
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1024,
+ OptionBuffer(subopt_data,
+ subopt_data +
+ sizeof(subopt_data)))
+ );
+ // Returned option should be of the OptionCustom type.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+ // Sanity-check length, universe etc.
+ EXPECT_EQ(Option::V6, option_v6->getUniverse());
+ EXPECT_EQ(4, option_v6->getHeaderLen());
+ // This option should have one suboption with the code of 1025.
+ OptionPtr subopt_v6 = option_v6->getOption(1025);
+ EXPECT_TRUE(subopt_v6);
+ // Check that this suboption holds valid data.
+ EXPECT_EQ(1025, subopt_v6->getType());
+ EXPECT_EQ(Option::V6, subopt_v6->getUniverse());
+ EXPECT_EQ(0, memcmp(&subopt_v6->getData()[0], subopt_data + 4, 4));
+
+ // @todo consider having a similar test for V4.
+}
+
+// The purpose of this test is to verify that definition can be
+// creates for the option that holds binary data.
+TEST_F(OptionDefinitionTest, binary) {
+ // Binary option is the one that is represented by the generic
+ // Option class. In fact all options can be represented by this
+ // class but for some of them it is just natural. The SERVERID
+ // option consists of the option code, length and binary data so
+ // this one was picked for this test.
+ OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID,
+ DHCP6_OPTION_SPACE, "binary");
+
+ // Prepare some dummy data (serverid): 0, 1, 2 etc.
+ OptionBuffer buf(14);
+ for (unsigned i = 0; i < 14; ++i) {
+ buf[i] = i;
+ }
+ // Create option instance with the factory function.
+ // If the OptionDefinition code works properly than
+ // object of the type Option should be returned.
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_SERVERID, buf);
+ );
+ // Expect base option type returned.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option));
+ // Sanity check on universe, length and size. These are
+ // the basic parameters identifying any option.
+ EXPECT_EQ(Option::V6, option_v6->getUniverse());
+ EXPECT_EQ(4, option_v6->getHeaderLen());
+ ASSERT_EQ(buf.size(), option_v6->getData().size());
+
+ // Get the server id data from the option and compare
+ // against reference buffer. They are expected to match.
+ EXPECT_TRUE(std::equal(option_v6->getData().begin(),
+ option_v6->getData().end(),
+ buf.begin()));
+
+ // Repeat the same test scenario for DHCPv4 option.
+ OptionPtr option_v4;
+ ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, buf));
+ // Expect 'empty' DHCPv4 option.
+ EXPECT_EQ(Option::V4, option_v4->getUniverse());
+ EXPECT_EQ(2, option_v4->getHeaderLen());
+ ASSERT_EQ(buf.size(), option_v4->getData().size());
+
+ EXPECT_TRUE(std::equal(option_v6->getData().begin(),
+ option_v6->getData().end(),
+ buf.begin()));
+}
+
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IA_NA option is used. This option comprises three uint32 fields.
+TEST_F(OptionDefinitionTest, recordIA6) {
+ // This option consists of IAID, T1 and T2 fields (each 4 bytes long).
+ const int option6_ia_len = 12;
+
+ // Get the factory function pointer.
+ OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, DHCP6_OPTION_SPACE,
+ "record", false);
+ // Each data field is uint32.
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
+ }
+
+ // Check the positive scenario.
+ OptionBuffer buf(12);
+ for (size_t i = 0; i < buf.size(); ++i) {
+ buf[i] = i;
+ }
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IA_NA, buf));
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option6IA));
+ boost::shared_ptr<Option6IA> option_cast_v6 =
+ boost::static_pointer_cast<Option6IA>(option_v6);
+ EXPECT_EQ(0x00010203, option_cast_v6->getIAID());
+ EXPECT_EQ(0x04050607, option_cast_v6->getT1());
+ EXPECT_EQ(0x08090A0B, option_cast_v6->getT2());
+
+ // The length of the buffer must be at least 12 bytes.
+ // Check too short buffer.
+ EXPECT_THROW(
+ opt_def.optionFactory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)),
+ InvalidOptionValue
+ );
+}
+
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IAADDR option is used.
+TEST_F(OptionDefinitionTest, recordIAAddr6) {
+ // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
+ // valid-lifetime fields (each 4 bytes long).
+ const int option6_iaaddr_len = 24;
+
+ OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, DHCP6_OPTION_SPACE,
+ "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ // Check the positive scenario.
+ OptionPtr option_v6;
+ asiolink::IOAddress addr_v6("2001:0db8::ff00:0042:8329");
+ OptionBuffer buf(asiolink::V6ADDRESS_LEN);
+ ASSERT_TRUE(addr_v6.isV6());
+ const std::vector<uint8_t>& vec = addr_v6.toBytes();
+ ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size());
+ std::copy(vec.begin(), vec.end(), buf.begin());
+
+ for (unsigned i = 0;
+ i < option6_iaaddr_len - asiolink::V6ADDRESS_LEN;
+ ++i) {
+ buf.push_back(i);
+ }
+ ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, buf));
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option6IAAddr));
+ boost::shared_ptr<Option6IAAddr> option_cast_v6 =
+ boost::static_pointer_cast<Option6IAAddr>(option_v6);
+ EXPECT_EQ(addr_v6, option_cast_v6->getAddress());
+ EXPECT_EQ(0x00010203, option_cast_v6->getPreferred());
+ EXPECT_EQ(0x04050607, option_cast_v6->getValid());
+
+ // The length of the buffer must be at least 12 bytes.
+ // Check too short buffer.
+ EXPECT_THROW(
+ opt_def.optionFactory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)),
+ InvalidOptionValue
+ );
+}
+
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IAADDR option is used. The data for the option is specified as
+// a vector of strings. Each string carries the data for the corresponding
+// data field.
+TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) {
+ // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
+ // valid-lifetime fields (each 4 bytes long).
+ OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, DHCP6_OPTION_SPACE,
+ "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ // Check the positive scenario.
+ std::vector<std::string> data_field_values;
+ data_field_values.push_back("2001:0db8::ff00:0042:8329");
+ data_field_values.push_back("1234");
+ data_field_values.push_back("5678");
+
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR,
+ data_field_values));
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option6IAAddr));
+ boost::shared_ptr<Option6IAAddr> option_cast_v6 =
+ boost::static_pointer_cast<Option6IAAddr>(option_v6);
+ EXPECT_EQ("2001:db8::ff00:42:8329", option_cast_v6->getAddress().toText());
+ EXPECT_EQ(1234, option_cast_v6->getPreferred());
+ EXPECT_EQ(5678, option_cast_v6->getValid());
+}
+
+// The purpose of this test is to verify that the definition for option
+// that comprises a boolean value can be created and that this definition
+// can be used to create and option with a single boolean value.
+TEST_F(OptionDefinitionTest, boolValue) {
+ // The IP Forwarding option comprises one boolean value.
+ OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING,
+ DHCP4_OPTION_SPACE, "boolean");
+
+ OptionPtr option_v4;
+ // Use an option buffer which holds one value of 1 (true).
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ OptionBuffer(1, 1));
+ );
+ const Option* optptr = option_v4.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+ // Validate parsed value in the received option.
+ boost::shared_ptr<OptionCustom> option_cast_v4 =
+ boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_TRUE(option_cast_v4->readBoolean());
+
+ // Repeat the test above, but set the value to 0 (false).
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ OptionBuffer(1, 0));
+ );
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_FALSE(option_cast_v4->readBoolean());
+
+ // Try to provide zero-length buffer. Expect exception.
+ EXPECT_THROW(
+ opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, OptionBuffer()),
+ InvalidOptionValue
+ );
+
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single boolean value can be created and that this definition
+// can be used to create an option holding a single boolean value. The
+// boolean value is converted from a string which is expected to hold
+// the following values: "true", "false", "1" or "0". For all other
+// values exception should be thrown.
+TEST_F(OptionDefinitionTest, boolTokenized) {
+ OptionDefinition opt_def("ip-forwarding", DHO_IP_FORWARDING,
+ DHCP6_OPTION_SPACE, "boolean");
+
+ OptionPtr option_v4;
+ std::vector<std::string> values;
+ // Specify a value for the option instance being created.
+ values.push_back("true");
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ const Option* optptr = option_v4.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+ // Validate the value.
+ OptionCustomPtr option_cast_v4 =
+ boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_TRUE(option_cast_v4->readBoolean());
+
+ // Repeat the test but for "false" value this time.
+ values[0] = "false";
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ optptr = option_v4.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+ // Validate the value.
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_FALSE(option_cast_v4->readBoolean());
+
+ // Check if that will work for numeric values.
+ values[0] = "0";
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ optptr = option_v4.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+ // Validate the value.
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_FALSE(option_cast_v4->readBoolean());
+
+ // Swap numeric values and test if it works for "true" case.
+ values[0] = "1";
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING,
+ values);
+ );
+ optptr = option_v4.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+ // Validate the value.
+ option_cast_v4 = boost::static_pointer_cast<OptionCustom>(option_v4);
+ EXPECT_TRUE(option_cast_v4->readBoolean());
+
+ // A conversion of non-numeric value to boolean should fail if
+ // this value is neither "true" nor "false".
+ values[0] = "garbage";
+ EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values),
+ isc::dhcp::BadDataTypeCast);
+
+ // A conversion of numeric value to boolean should fail if this value
+ // is neither "0" nor "1".
+ values[0] = "2";
+ EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_IP_FORWARDING, values),
+ isc::dhcp::BadDataTypeCast);
+
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint8 value can be created and that this definition
+// can be used to create an option with single uint8 value.
+TEST_F(OptionDefinitionTest, uint8) {
+ OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE,
+ DHCP6_OPTION_SPACE, "uint8");
+
+ OptionPtr option_v6;
+ // Try to use correct buffer length = 1 byte.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE,
+ OptionBuffer(1, 1));
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint8_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6);
+ EXPECT_EQ(1, option_cast_v6->getValue());
+
+ // Try to provide zero-length buffer. Expect exception.
+ EXPECT_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer()),
+ InvalidOptionValue
+ );
+
+ // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint8 value can be created and that this definition
+// can be used to create an option with single uint8 value.
+TEST_F(OptionDefinitionTest, uint8Tokenized) {
+ OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE,
+ DHCP6_OPTION_SPACE, "uint8");
+
+ OptionPtr option_v6;
+ std::vector<std::string> values;
+ values.push_back("123");
+ values.push_back("456");
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint8_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6);
+ EXPECT_EQ(123, option_cast_v6->getValue());
+
+ // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint16 value can be created and that this definition
+// can be used to create an option with single uint16 value.
+TEST_F(OptionDefinitionTest, uint16) {
+ OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME,
+ DHCP6_OPTION_SPACE, "uint16");
+
+ OptionPtr option_v6;
+ // Try to use correct buffer length = 2 bytes.
+ OptionBuffer buf;
+ buf.push_back(1);
+ buf.push_back(2);
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, buf);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint16_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6);
+ EXPECT_EQ(0x0102, option_cast_v6->getValue());
+
+ // Try to provide zero-length buffer. Expect exception.
+ EXPECT_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)),
+ InvalidOptionValue
+ );
+
+ // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint16 value can be created and that this definition
+// can be used to create an option with single uint16 value.
+TEST_F(OptionDefinitionTest, uint16Tokenized) {
+ OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME,
+ DHCP6_OPTION_SPACE, "uint16");
+
+ OptionPtr option_v6;
+
+ std::vector<std::string> values;
+ values.push_back("1234");
+ values.push_back("5678");
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, values);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint16_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6);
+ EXPECT_EQ(1234, option_cast_v6->getValue());
+
+ // @todo Add more cases for DHCPv4
+
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint32 value can be created and that this definition
+// can be used to create an option with single uint32 value.
+TEST_F(OptionDefinitionTest, uint32) {
+ OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME,
+ DHCP6_OPTION_SPACE, "uint32");
+
+ OptionPtr option_v6;
+ OptionBuffer buf;
+ buf.push_back(1);
+ buf.push_back(2);
+ buf.push_back(3);
+ buf.push_back(4);
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, buf);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint32_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6);
+ EXPECT_EQ(0x01020304, option_cast_v6->getValue());
+
+ // Try to provide too short buffer. Expect exception.
+ EXPECT_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)),
+ InvalidOptionValue
+ );
+
+ // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint32 value can be created and that this definition
+// can be used to create an option with single uint32 value.
+TEST_F(OptionDefinitionTest, uint32Tokenized) {
+ OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME,
+ DHCP6_OPTION_SPACE, "uint32");
+
+ OptionPtr option_v6;
+ std::vector<std::string> values;
+ values.push_back("123456");
+ values.push_back("789");
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, values);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionInt<uint32_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6);
+ EXPECT_EQ(123456, option_cast_v6->getValue());
+
+ // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint16 values can be created and that this definition
+// can be used to create option with an array of uint16 values.
+TEST_F(OptionDefinitionTest, uint16Array) {
+ // Let's define some dummy option.
+ const uint16_t opt_code = 79;
+ OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "my-space",
+ "uint16", true);
+
+ OptionPtr option_v6;
+ // Positive scenario, initiate the buffer with length being
+ // multiple of uint16_t size.
+ // buffer elements will be: 0x112233.
+ OptionBuffer buf(6);
+ for (unsigned i = 0; i < 6; ++i) {
+ buf[i] = i / 2;
+ }
+ // Constructor should succeed because buffer has correct size.
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint16_t>));
+ boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6);
+ // Get the values from the initiated options and validate.
+ std::vector<uint16_t> values = option_cast_v6->getValues();
+ for (size_t i = 0; i < values.size(); ++i) {
+ // Expected value is calculated using on the same pattern
+ // as the one we used to initiate buffer:
+ // for i=0, expected = 0x00, for i = 1, expected == 0x11 etc.
+ uint16_t expected = (i << 8) | i;
+ EXPECT_EQ(expected, values[i]);
+ }
+
+ // Provided buffer size must be greater than zero. Check if we
+ // get exception if we provide zero-length buffer.
+ EXPECT_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
+ InvalidOptionValue
+ );
+ // Buffer length must be multiple of data type size.
+ EXPECT_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
+ InvalidOptionValue
+ );
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint16 values can be created and that this definition
+// can be used to create option with an array of uint16 values.
+TEST_F(OptionDefinitionTest, uint16ArrayTokenized) {
+ // Let's define some dummy option.
+ const uint16_t opt_code = 79;
+ OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "my-space",
+ "uint16", true);
+
+ OptionPtr option_v6;
+ std::vector<std::string> str_values;
+ str_values.push_back("12345");
+ str_values.push_back("5679");
+ str_values.push_back("12");
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint16_t>));
+ boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6);
+ // Get the values from the initiated options and validate.
+ std::vector<uint16_t> values = option_cast_v6->getValues();
+ EXPECT_EQ(12345, values[0]);
+ EXPECT_EQ(5679, values[1]);
+ EXPECT_EQ(12, values[2]);
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint32 values can be created and that this definition
+// can be used to create option with an array of uint32 values.
+TEST_F(OptionDefinitionTest, uint32Array) {
+ // Let's define some dummy option.
+ const uint16_t opt_code = 80;
+
+ OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "my-space",
+ "uint32", true);
+
+ OptionPtr option_v6;
+ // Positive scenario, initiate the buffer with length being
+ // multiple of uint16_t size.
+ // buffer elements will be: 0x111122223333.
+ OptionBuffer buf(12);
+ for (size_t i = 0; i < buf.size(); ++i) {
+ buf[i] = i / 4;
+ }
+ // Constructor should succeed because buffer has correct size.
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint32_t>));
+ boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6);
+ // Get the values from the initiated options and validate.
+ std::vector<uint32_t> values = option_cast_v6->getValues();
+ for (size_t i = 0; i < values.size(); ++i) {
+ // Expected value is calculated using on the same pattern
+ // as the one we used to initiate buffer:
+ // for i=0, expected = 0x0000, for i = 1, expected == 0x1111 etc.
+ uint32_t expected = 0x01010101 * i;
+ EXPECT_EQ(expected, values[i]);
+ }
+
+ // Provided buffer size must be greater than zero. Check if we
+ // get exception if we provide zero-length buffer.
+ EXPECT_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
+ InvalidOptionValue
+ );
+ // Buffer length must be multiple of data type size.
+ EXPECT_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
+ InvalidOptionValue
+ );
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint32 values can be created and that this definition
+// can be used to create option with an array of uint32 values.
+TEST_F(OptionDefinitionTest, uint32ArrayTokenized) {
+ // Let's define some dummy option.
+ const uint16_t opt_code = 80;
+
+ OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "my-space",
+ "uint32", true);
+
+ OptionPtr option_v6;
+ std::vector<std::string> str_values;
+ str_values.push_back("123456");
+ // Try with hexadecimal
+ str_values.push_back("0x7");
+ str_values.push_back("256");
+ str_values.push_back("1111");
+
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
+ );
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionIntArray<uint32_t>));
+ boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6);
+ // Get the values from the initiated options and validate.
+ std::vector<uint32_t> values = option_cast_v6->getValues();
+ EXPECT_EQ(123456, values[0]);
+ EXPECT_EQ(7, values[1]);
+ EXPECT_EQ(256, values[2]);
+ EXPECT_EQ(1111, values[3]);
+}
+
+// The purpose of this test is to verify that the definition can be created
+// for the option that comprises string value in the UTF8 format.
+TEST_F(OptionDefinitionTest, utf8StringTokenized) {
+ // Let's create some dummy option.
+ const uint16_t opt_code = 80;
+ OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "my-space",
+ "string");
+
+ std::vector<std::string> values;
+ values.push_back("Hello World");
+ values.push_back("this string should not be included in the option");
+ OptionPtr option_v6;
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
+ );
+ ASSERT_TRUE(option_v6);
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionString));
+ OptionStringPtr option_v6_string =
+ boost::static_pointer_cast<OptionString>(option_v6);
+ EXPECT_TRUE(values[0] == option_v6_string->getValue());
+}
+
+// The purpose of this test is to check that non-integer data type can't
+// be used for factoryInteger function.
+TEST_F(OptionDefinitionTest, integerInvalidType) {
+ // The template function factoryInteger<> accepts integer values only
+ // as template typename. Here we try passing different type and
+ // see if it rejects it.
+ OptionBuffer buf(1);
+ EXPECT_THROW(
+ OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, DHCP6_OPTION_SPACE,
+ buf.begin(), buf.end()),
+ isc::dhcp::InvalidDataType
+ );
+}
+
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, prefix) {
+ OptionDefinition opt_def("option-prefix", 1000, "my-space", "ipv6-prefix");
+
+ // Create a buffer holding a prefix.
+ OptionBuffer buf;
+ buf.push_back(32);
+ buf.push_back(0x30);
+ buf.push_back(0x00);
+ buf.resize(5);
+
+ OptionPtr option_v6;
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PrefixTuple prefix = option_cast_v6->readPrefix();
+ EXPECT_EQ(32, prefix.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and that the instance of this option can be
+// created by specifying the prefix in the textual format.
+TEST_F(OptionDefinitionTest, prefixTokenized) {
+ OptionDefinition opt_def("option-prefix", 1000, "my-space", "ipv6-prefix");
+
+ OptionPtr option_v6;
+ // Specify a single prefix.
+ std::vector<std::string> values(1, "2001:db8:1::/64");
+
+ // Create an instance of the option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PrefixTuple prefix = option_cast_v6->readPrefix();
+ EXPECT_EQ(64, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with an array
+// of IPv6 prefixes can be created and that the instance of this
+// option can be created by specifying multiple prefixes in the
+// textual format.
+TEST_F(OptionDefinitionTest, prefixArrayTokenized) {
+ OptionDefinition opt_def("option-prefix", 1000, "my-space",
+ "ipv6-prefix", true);
+
+ OptionPtr option_v6;
+
+ // Specify 3 prefixes
+ std::vector<std::string> values;
+ values.push_back("2001:db8:1:: /64");
+ values.push_back("3000::/ 32");
+ values.push_back("3001:1:: / 48");
+
+ // Create an instance of an option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the option class returned is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+ // There should be 3 prefixes in this option.
+ ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix0 = option_cast_v6->readPrefix(0);
+ EXPECT_EQ(64, prefix0.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix1 = option_cast_v6->readPrefix(1);
+ EXPECT_EQ(32, prefix1.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix1.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix2 = option_cast_v6->readPrefix(2);
+ EXPECT_EQ(48, prefix2.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix2.second.toText());
+ });
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, psid) {
+ OptionDefinition opt_def("option-psid", 1000, "my-space", "psid");
+
+ OptionPtr option_v6;
+
+ // Create a buffer holding PSID.
+ OptionBuffer buf;
+ buf.push_back(6);
+ buf.push_back(0x4);
+ buf.push_back(0x0);
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PSIDTuple psid = option_cast_v6->readPsid();
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(1, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and that the instance of this option can be
+// created by specifying PSID length and value in the textual format.
+TEST_F(OptionDefinitionTest, psidTokenized) {
+ OptionDefinition opt_def("option-psid", 1000, "my-space", "psid");
+
+ OptionPtr option_v6;
+ // Specify a single PSID with a length of 6 and value of 3.
+ std::vector<std::string> values(1, "3 / 6");
+
+ // Create an instance of the option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PSIDTuple psid = option_cast_v6->readPsid();
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(3, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with an array
+// of PSIDs can be created and that the instance of this option can be
+// created by specifying multiple PSIDs in the textual format.
+TEST_F(OptionDefinitionTest, psidArrayTokenized) {
+ OptionDefinition opt_def("option-psid", 1000, "my-space", "psid", true);
+
+ OptionPtr option_v6;
+
+ // Specify 3 PSIDs.
+ std::vector<std::string> values;
+ values.push_back("3 / 6");
+ values.push_back("0/1");
+ values.push_back("7 / 3");
+
+ // Create an instance of an option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the option class returned is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+ // There should be 3 PSIDs in this option.
+ ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+ // Check their values.
+ PSIDTuple psid0;
+ PSIDTuple psid1;
+ PSIDTuple psid2;
+
+ psid0 = option_cast_v6->readPsid(0);
+ EXPECT_EQ(6, psid0.first.asUnsigned());
+ EXPECT_EQ(3, psid0.second.asUint16());
+
+ psid1 = option_cast_v6->readPsid(1);
+ EXPECT_EQ(1, psid1.first.asUnsigned());
+ EXPECT_EQ(0, psid1.second.asUint16());
+
+ psid2 = option_cast_v6->readPsid(2);
+ EXPECT_EQ(3, psid2.first.asUnsigned());
+ EXPECT_EQ(7, psid2.second.asUint16());
+}
+
+// This test verifies that a definition of an option with a single DHCPv4
+// tuple can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, tuple4) {
+ OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple");
+
+ OptionPtr option;
+
+ // Create a buffer holding tuple
+ const char data[] = {
+ 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+ OptionBuffer buf(data, data + sizeof(data));
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv6
+// tuple can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, tuple6) {
+ OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple");
+
+ OptionPtr option;
+
+ // Create a buffer holding tuple
+ const char data[] = {
+ 0, 6, 102, 111, 111, 98, 97, 114 // "foobar"
+ };
+ OptionBuffer buf(data, data + sizeof(data));
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv4
+// tuple can be created and that the instance of this option can be
+// created by specifying tuple value in the textual format.
+TEST_F(OptionDefinitionTest, tuple4Tokenized) {
+ OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple");
+
+ OptionPtr option;
+ // Specify a single tuple with "foobar" content.
+ std::vector<std::string> values(1, "foobar");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with a single DHCPv6
+// tuple can be created and that the instance of this option can be
+// created by specifying tuple value in the textual format.
+TEST_F(OptionDefinitionTest, tuple6Tokenized) {
+ OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple");
+
+ OptionPtr option;
+ // Specify a single tuple with "foobar" content.
+ std::vector<std::string> values(1, "foobar");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast =
+ boost::dynamic_pointer_cast<OptionCustom>(option);
+ ASSERT_EQ(1, option_cast->getDataFieldsNum());
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_NO_THROW(option_cast->readTuple(tuple));
+ EXPECT_EQ("foobar", tuple.getText());
+}
+
+// This test verifies that a definition of an option with an array
+// of DHCPv4 tuples can be created and that the instance of this option
+// can be created by specifying multiple DHCPv4 tuples in the textual format.
+TEST_F(OptionDefinitionTest, tuple4ArrayTokenized) {
+ OptionDefinition opt_def("option-tuple", 232, "my-space", "tuple", true);
+
+ OptionPtr option;
+
+ // Specify 3 tuples.
+ std::vector<std::string> values;
+ values.push_back("hello");
+ values.push_back("the");
+ values.push_back("world");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, 232, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples));
+
+ // Validate the value.
+ OptionOpaqueDataTuplesPtr option_cast =
+ boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option);
+
+ // There should be 3 tuples in this option.
+ ASSERT_EQ(3, option_cast->getTuplesNum());
+
+ // Check their values.
+ OpaqueDataTuple tuple0 = option_cast->getTuple(0);
+ EXPECT_EQ("hello", tuple0.getText());
+
+ OpaqueDataTuple tuple1 = option_cast->getTuple(1);
+ EXPECT_EQ("the", tuple1.getText());
+
+ OpaqueDataTuple tuple2 = option_cast->getTuple(2);
+ EXPECT_EQ("world", tuple2.getText());
+}
+
+// This test verifies that a definition of an option with an array
+// of DHCPv4 tuples can be created and that the instance of this option
+// can be created by specifying multiple DHCPv4 tuples in the textual format.
+// This test also verifies specific v4 Option #143 where tuple's string length
+// is coded on 2 octets instead of 1 as usual.
+TEST_F(OptionDefinitionTest, tuple4ArrayOption143) {
+ OptionDefinition opt_def("option-tuple", DHO_V4_SZTP_REDIRECT, DHCP4_OPTION_SPACE, "tuple", true);
+
+ OptionPtr option;
+
+ // Specify 3 tuples.
+ std::vector<std::string> values;
+ values.push_back("hello");
+ values.push_back("the");
+ values.push_back("world");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V4, DHO_V4_SZTP_REDIRECT, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples));
+
+ // Validate the value.
+ OptionOpaqueDataTuplesPtr option_cast =
+ boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option);
+
+ // There should be 3 tuples in this option.
+ ASSERT_EQ(3, option_cast->getTuplesNum());
+
+ // Check their values.
+ OpaqueDataTuple tuple0 = option_cast->getTuple(0);
+ EXPECT_EQ("hello", tuple0.getText());
+
+ OpaqueDataTuple tuple1 = option_cast->getTuple(1);
+ EXPECT_EQ("the", tuple1.getText());
+
+ OpaqueDataTuple tuple2 = option_cast->getTuple(2);
+ EXPECT_EQ("world", tuple2.getText());
+}
+
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the V4-DNR option is used (code 162) in ADN only mode, only one DNR instance.
+// Option's fields are specified as a vector of strings.
+TEST_F(OptionDefinitionTest, recordOption4DnrAdnOnly) {
+ OptionDefinition opt_def("option-dnr", DHO_V4_DNR, DHCP4_OPTION_SPACE, "record", false);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_UINT8_TYPE);
+ opt_def.addRecordField(OPT_FQDN_TYPE);
+ opt_def.addRecordField(OPT_BINARY_TYPE);
+
+ OptionPtr option;
+
+ // Specify option's fields for ADN only mode.
+ std::vector<std::string> values;
+ values.push_back("26"); // DNR instance data Len
+ values.push_back("1234"); // service priority
+ values.push_back("23"); // ADN Len
+ values.push_back("Example.Some.Host.Org."); // ADN FQDN
+ values.push_back(""); // leave empty Binary type
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V4, DHO_V4_DNR, values););
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option4Dnr));
+
+ // Validate that option's fields were correctly parsed from strings.
+ Option4DnrPtr option_cast = boost::dynamic_pointer_cast<Option4Dnr>(option);
+
+ auto dnr_instances = option_cast->getDnrInstances();
+
+ // Only one DNR instance is expected.
+ ASSERT_EQ(1, dnr_instances.size());
+
+ DnrInstance& dnr = dnr_instances[0];
+ ASSERT_EQ(26, dnr.getDnrInstanceDataLength());
+ ASSERT_EQ(1234, dnr.getServicePriority());
+ ASSERT_EQ(true, dnr.isAdnOnlyMode());
+ ASSERT_EQ("example.some.host.org.", dnr.getAdnAsText());
+ ASSERT_EQ(23, dnr.getAdnLength());
+ ASSERT_EQ(0, dnr.getAddrLength());
+ ASSERT_EQ(0, dnr.getSvcParamsLength());
+}
+
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the V4-DNR option is used (code 162) with ADN, IP addresses and Service
+// Parameters included. Option's fields are specified as a vector of strings.
+// Multiple DNR instances are configured in this test.
+TEST_F(OptionDefinitionTest, recordOption4Dnr) {
+ OptionDefinition opt_def("option-dnr", DHO_V4_DNR, DHCP4_OPTION_SPACE, "record", false);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_UINT8_TYPE);
+ opt_def.addRecordField(OPT_FQDN_TYPE);
+ opt_def.addRecordField(OPT_BINARY_TYPE);
+
+ OptionPtr option;
+
+ // Specify option's fields - multiple DNR instances.
+ std::vector<std::string> values;
+ values.push_back("54"); // DNR instance #1 data Len
+ values.push_back("1234"); // service priority
+ values.push_back("23"); // ADN Len
+ values.push_back("Example.Some.Host.Org."); // ADN FQDN
+ values.push_back("08 " // Addr Len
+ "c0 a8 00 01" // IP 192.168.0.1
+ "c0 a8 00 02" // IP 192.168.0.2
+ "6b 65 79 31 3d 76 61 6c 31 20 " // SvcParams "key1=val1 "
+ "6b 65 79 32 3d 76 61 6c 32 " // SvcParams "key2=val2"
+ "00 34 " // DNR instance #2 data Len 52
+ "10 e1 " // service priority 4321
+ "15 " // ADN Len 21
+ "07 6D 79 68 6F 73 74 31 " // ADN FQDN myhost1.
+ "07 65 78 61 6D 70 6C 65 " // example.
+ "03 63 6F 6D 00 " // com.
+ "08 " // Addr Len 8
+ "c0 a9 00 01" // IP 192.169.0.1
+ "c0 a9 00 02" // IP 192.169.0.2
+ "6b 65 79 33 3d 76 61 6c 33 20 " // SvcParams "key3=val3 "
+ "6b 65 79 34 3d 76 61 6c 34 " // SvcParams "key4=val4"
+ );
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V4, DHO_V4_DNR, values););
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option4Dnr));
+
+ // Validate that option's fields were correctly parsed from strings.
+ Option4DnrPtr option_cast = boost::dynamic_pointer_cast<Option4Dnr>(option);
+
+ auto dnr_instances = option_cast->getDnrInstances();
+
+ // Two DNR instances are expected.
+ ASSERT_EQ(2, dnr_instances.size());
+
+ // Let's check 1st DNR instance.
+ DnrInstance& dnr_1 = dnr_instances[0];
+ ASSERT_EQ(54, dnr_1.getDnrInstanceDataLength());
+ ASSERT_EQ(1234, dnr_1.getServicePriority());
+ ASSERT_EQ(false, dnr_1.isAdnOnlyMode());
+ ASSERT_EQ(23, dnr_1.getAdnLength());
+ ASSERT_EQ("example.some.host.org.", dnr_1.getAdnAsText());
+ ASSERT_EQ(8, dnr_1.getAddrLength());
+ ASSERT_EQ(19, dnr_1.getSvcParamsLength());
+ auto addresses_1 = dnr_1.getAddresses();
+ ASSERT_EQ(2, addresses_1.size());
+ ASSERT_EQ("192.168.0.1", addresses_1[0].toText());
+ ASSERT_EQ("192.168.0.2", addresses_1[1].toText());
+ ASSERT_EQ("key1=val1 key2=val2", dnr_1.getSvcParams());
+
+ // Let's check 2nd DNR instance.
+ DnrInstance& dnr_2 = dnr_instances[1];
+ ASSERT_EQ(52, dnr_2.getDnrInstanceDataLength());
+ ASSERT_EQ(4321, dnr_2.getServicePriority());
+ ASSERT_EQ(false, dnr_2.isAdnOnlyMode());
+ ASSERT_EQ(21, dnr_2.getAdnLength());
+ ASSERT_EQ("myhost1.example.com.", dnr_2.getAdnAsText());
+ ASSERT_EQ(8, dnr_2.getAddrLength());
+ ASSERT_EQ(19, dnr_2.getSvcParamsLength());
+ auto addresses_2 = dnr_2.getAddresses();
+ ASSERT_EQ(2, addresses_2.size());
+ ASSERT_EQ("192.169.0.1", addresses_2[0].toText());
+ ASSERT_EQ("192.169.0.2", addresses_2[1].toText());
+ ASSERT_EQ("key3=val3 key4=val4", dnr_2.getSvcParams());
+}
+
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the V6-DNR option is used (code 144) in ADN only mode.
+// Option's fields are specified as a vector of strings.
+TEST_F(OptionDefinitionTest, recordOption6DnrAdnOnly) {
+ OptionDefinition opt_def("option-dnr", D6O_V6_DNR, DHCP6_OPTION_SPACE, "record", false);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_FQDN_TYPE);
+ opt_def.addRecordField(OPT_BINARY_TYPE);
+
+ OptionPtr option;
+
+ // Specify option's fields for ADN only mode.
+ std::vector<std::string> values;
+ values.push_back("1234"); // service priority
+ values.push_back("23"); // ADN Len
+ values.push_back("Example.Some.Host.Org."); // ADN FQDN
+ values.push_back(""); // leave empty Binary type
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V6, D6O_V6_DNR, values););
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option6Dnr));
+
+ // Validate that option's fields were correctly parsed from strings.
+ Option6DnrPtr option_cast = boost::dynamic_pointer_cast<Option6Dnr>(option);
+
+ ASSERT_EQ(1234, option_cast->getServicePriority());
+ ASSERT_EQ(true, option_cast->isAdnOnlyMode());
+ ASSERT_EQ("example.some.host.org.", option_cast->getAdnAsText());
+ ASSERT_EQ(23, option_cast->getAdnLength());
+ ASSERT_EQ(0, option_cast->getAddrLength());
+ ASSERT_EQ(0, option_cast->getSvcParamsLength());
+}
+
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the V6-DNR option is used (code 144) with ADN, IP addresses and Service
+// Parameters included. Option's fields are specified as a vector of strings.
+TEST_F(OptionDefinitionTest, recordOption6Dnr) {
+ OptionDefinition opt_def("option-dnr", D6O_V6_DNR, DHCP6_OPTION_SPACE, "record", false);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_UINT16_TYPE);
+ opt_def.addRecordField(OPT_FQDN_TYPE);
+ opt_def.addRecordField(OPT_BINARY_TYPE);
+
+ OptionPtr option;
+
+ // Specify option's fields: service priority, ADN, IP addresses and SvcParams.
+ std::vector<std::string> values;
+ values.push_back("1234"); // service priority
+ values.push_back("23"); // ADN Len
+ values.push_back("Example.Some.Host.Org."); // ADN FQDN
+ values.push_back("00 20 " // Addr Len
+ "20 01 0d b8 00 01 00 00 00 00 00 00 de ad be ef " // IP 2001:db8:1::dead:beef
+ "ff 02 00 00 00 00 00 00 00 00 00 00 fa ce b0 0c " // IP ff02::face:b00c
+ "6b 65 79 31 3d 76 61 6c 31 20 " // SvcParams "key1=val1 "
+ "6b 65 79 32 3d 76 61 6c 32" // SvcParams "key2=val2"
+ );
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(option = opt_def.optionFactory(Option::V6, D6O_V6_DNR, values););
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(Option6Dnr));
+
+ // Validate that option's fields were correctly parsed from strings.
+ Option6DnrPtr option_cast = boost::dynamic_pointer_cast<Option6Dnr>(option);
+
+ ASSERT_EQ(1234, option_cast->getServicePriority());
+ ASSERT_EQ(false, option_cast->isAdnOnlyMode());
+ ASSERT_EQ("example.some.host.org.", option_cast->getAdnAsText());
+ ASSERT_EQ(23, option_cast->getAdnLength());
+ ASSERT_EQ(32, option_cast->getAddrLength());
+ auto addresses = option_cast->getAddresses();
+ ASSERT_EQ(2, addresses.size());
+ ASSERT_EQ("2001:db8:1::dead:beef", addresses[0].toText());
+ ASSERT_EQ("ff02::face:b00c", addresses[1].toText());
+ ASSERT_EQ("key1=val1 key2=val2", option_cast->getSvcParams());
+}
+
+// This test verifies that a definition of an option with an array
+// of DHCPv6 tuples can be created and that the instance of this option
+// can be created by specifying multiple DHCPv6 tuples in the textual format.
+TEST_F(OptionDefinitionTest, tuple6ArrayTokenized) {
+ OptionDefinition opt_def("option-tuple", 1000, "my-space", "tuple", true);
+
+ OptionPtr option;
+
+ // Specify 3 tuples.
+ std::vector<std::string> values;
+ values.push_back("hello");
+ values.push_back("the");
+ values.push_back("world");
+
+ // Create an instance of this option using the definition.
+ ASSERT_NO_THROW(
+ option = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionOpaqueDataTuples));
+
+ // Validate the value.
+ OptionOpaqueDataTuplesPtr option_cast =
+ boost::dynamic_pointer_cast<OptionOpaqueDataTuples>(option);
+
+ // There should be 3 tuples in this option.
+ ASSERT_EQ(3, option_cast->getTuplesNum());
+
+ // Check their values.
+ OpaqueDataTuple tuple0 = option_cast->getTuple(0);
+ EXPECT_EQ("hello", tuple0.getText());
+
+ OpaqueDataTuple tuple1 = option_cast->getTuple(1);
+ EXPECT_EQ("the", tuple1.getText());
+
+ OpaqueDataTuple tuple2 = option_cast->getTuple(2);
+ EXPECT_EQ("world", tuple2.getText());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_int_array_unittest.cc b/src/lib/dhcp/tests/option_int_array_unittest.cc
new file mode 100644
index 0000000..ba60554
--- /dev/null
+++ b/src/lib/dhcp/tests/option_int_array_unittest.cc
@@ -0,0 +1,486 @@
+// Copyright (C) 2012-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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int_array.h>
+#include <util/buffer.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// @brief OptionIntArray test class.
+class OptionIntArrayTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes the option buffer with some data.
+ OptionIntArrayTest(): buf_(255), out_buf_(255) {
+ for (unsigned i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+
+ /// @brief Test parsing buffer into array of int8_t or uint8_t values.
+ ///
+ /// @warning this function does not perform type check. Make
+ /// sure that only int8_t or uint8_t type is used.
+ ///
+ /// @param u universe (v4 or V6).
+ /// @tparam T int8_t or uint8_t.
+ template<typename T>
+ void bufferToIntTest8(const Option::Universe u) {
+ // Create option that conveys array of multiple uint8_t or int8_t values.
+ // In fact there is no need to use this template class for array
+ // of uint8_t values because Option class is sufficient - it
+ // returns the buffer which is actually the array of uint8_t.
+ // However, since we allow using uint8_t types with this template
+ // class we have to test it here.
+ boost::shared_ptr<OptionIntArray<T> > opt;
+ const int opt_len = 10;
+ const uint16_t opt_code = 80;
+
+ // Constructor throws exception if provided buffer is empty.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Provided buffer is not empty so it should not throw exception.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(),
+ buf_.begin() + opt_len))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ // Option should return the collection of int8_t or uint8_t values that
+ // we can match with the buffer we used to create the option.
+ std::vector<T> values = opt->getValues();
+ // We need to copy values from the buffer to apply sign if signed
+ // type is used.
+ std::vector<T> reference_values;
+ for (int i = 0; i < opt_len; ++i) {
+ // Values have been read from the buffer in network
+ // byte order. We put them back in the same order here.
+ reference_values.push_back(static_cast<T>(buf_[i]));
+ }
+
+ // Compare the values against the reference buffer.
+ ASSERT_EQ(opt_len, values.size());
+ EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.begin()
+ + opt_len, values.begin()));
+
+ // test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 10 bytes.
+ EXPECT_EQ(10, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(opt_code, opt->getType());
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+
+ if (u == Option::V4) {
+ // The total length is 10 bytes for data and 2 bytes for a header.
+ ASSERT_EQ(12, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(10, out.readUint8());
+ } else {
+ // The total length is 10 bytes for data and 4 bytes for a header.
+ ASSERT_EQ(14, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(10, out.readUint16());
+ }
+
+ // if data is correct
+ std::vector<uint8_t> out_data;
+ ASSERT_NO_THROW(out.readVector(out_data, opt_len));
+ ASSERT_EQ(opt_len, out_data.size());
+ EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
+ }
+
+ /// @brief Test parsing buffer into array of int16_t or uint16_t values.
+ ///
+ /// @warning this function does not perform type check. Make
+ /// sure that only int16_t or uint16_t type is used.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @tparam T int16_t or uint16_t.
+ template<typename T>
+ void bufferToIntTest16(const Option::Universe u) {
+ // Create option that conveys array of multiple uint16_t or int16_t values.
+ boost::shared_ptr<OptionIntArray<T> > opt;
+ const int opt_len = 20;
+ const uint16_t opt_code = 81;
+
+ // Constructor throws exception if provided buffer is empty.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Constructor throws exception if provided buffer's length is not
+ // multiple of 2-bytes.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 5),
+ isc::OutOfRange
+ );
+
+ // Now the buffer length is correct.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(),
+ buf_.begin() + opt_len))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ // Option should return vector of uint16_t values which should be
+ // constructed from the buffer we provided.
+ std::vector<T> values = opt->getValues();
+ ASSERT_EQ(opt_len, values.size() * sizeof(T));
+ // Create reference values from the buffer so as we can
+ // simply compare two vectors.
+ std::vector<T> reference_values;
+ for (int i = 0; i < opt_len; i += 2) {
+ reference_values.push_back((buf_[i] << 8) |
+ buf_[i + 1]);
+ }
+ EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(),
+ values.begin()));
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 20 bytes.
+ EXPECT_EQ(20, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(opt_code, opt->getType());
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+
+ if (u == Option::V4) {
+ // The total length is 20 bytes for data and 2 bytes for a header.
+ ASSERT_EQ(22, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(20, out.readUint8());
+ } else {
+ // The total length is 20 bytes for data and 4 bytes for a header.
+ ASSERT_EQ(24, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(20, out.readUint16());
+ }
+ // if data is correct
+ std::vector<uint8_t> out_data;
+ ASSERT_NO_THROW(out.readVector(out_data, opt_len));
+ ASSERT_EQ(opt_len, out_data.size());
+ EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
+ }
+
+ /// @brief Test parsing buffer into array of int32_t or uint32_t values.
+ ///
+ /// @warning this function does not perform type check. Make
+ /// sure that only int32_t or uint32_t type is used.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @tparam T int32_t or uint32_t.
+ template<typename T>
+ void bufferToIntTest32(const Option::Universe u) {
+ // Create option that conveys array of multiple uint16_t values.
+ boost::shared_ptr<OptionIntArray<T> > opt;
+ const int opt_len = 40;
+ const uint16_t opt_code = 82;
+
+ // Constructor throws exception if provided buffer is empty.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Constructor throws exception if provided buffer's length is not
+ // multiple of 4-bytes.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 9),
+ isc::OutOfRange
+ );
+
+ // Now the buffer length is correct.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(),
+ buf_.begin() + opt_len))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ // Option should return vector of uint32_t values which should be
+ // constructed from the buffer we provided.
+ std::vector<T> values = opt->getValues();
+ ASSERT_EQ(opt_len, values.size() * sizeof(T));
+ // Create reference values from the buffer so as we can
+ // simply compare two vectors.
+ std::vector<T> reference_values;
+ for (int i = 0; i < opt_len; i += 4) {
+ reference_values.push_back((buf_[i] << 24) |
+ (buf_[i + 1] << 16 & 0x00FF0000) |
+ (buf_[i + 2] << 8 & 0xFF00) |
+ (buf_[i + 3] & 0xFF));
+ }
+ EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(),
+ values.begin()));
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 40 bytes.
+ EXPECT_EQ(40, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(opt_code, opt->getType());
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+
+ if (u == Option::V4) {
+ // The total length is 40 bytes for data and 2 bytes for a header.
+ ASSERT_EQ(42, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(40, out.readUint8());
+ } else {
+ // The total length is 40 bytes for data and 4 bytes for a header.
+ ASSERT_EQ(44, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(40, out.readUint16());
+ }
+
+ // if data is correct
+ std::vector<uint8_t> out_data;
+ ASSERT_NO_THROW(out.readVector(out_data, opt_len));
+ ASSERT_EQ(opt_len, out_data.size());
+ EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
+ }
+
+ /// @brief Test ability to set all values.
+ ///
+ /// @tparam T numeric type to perform the test for.
+ template<typename T>
+ void setValuesTest() {
+ const uint16_t opt_code = 100;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<T> >
+ opt(new OptionIntArray<T>(Option::V6, opt_code));
+ // Initialize vector with some data and pass to the option.
+ std::vector<T> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<uint8_t>::max() - i);
+ }
+ opt->setValues(values);
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<T> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+ }
+
+ /// @brief Test ability to add values one by one.
+ ///
+ /// @tparam T numeric type to perform the test for.
+ template<typename T>
+ void addValuesTest() {
+ const uint16_t opt_code = 100;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<T> >
+ opt(new OptionIntArray<T>(Option::V6, opt_code));
+ // Initialize vector with some data and add the same data
+ // to the option.
+ std::vector<T> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<T>::max() - i);
+ opt->addValue(numeric_limits<T>::max() - i);
+ }
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<T> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+ }
+
+ OptionBuffer buf_; ///< Option buffer
+ OutputBuffer out_buf_; ///< Output buffer
+};
+
+/// @todo: below, there is a bunch of tests for options that
+/// convey unsigned values. We should maybe extend these tests for
+/// signed types too.
+
+TEST_F(OptionIntArrayTest, useInvalidType) {
+ const uint16_t opt_code = 80;
+ EXPECT_THROW(
+ boost::scoped_ptr<
+ OptionIntArray<bool> >(new OptionIntArray<bool>(Option::V6, opt_code,
+ OptionBuffer(5))),
+ InvalidDataType
+ );
+
+ EXPECT_THROW(
+ boost::scoped_ptr<
+ OptionIntArray<int64_t> >(new OptionIntArray<int64_t>(Option::V6,
+ opt_code,
+ OptionBuffer(10))),
+ InvalidDataType
+ );
+
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint8V4) {
+ bufferToIntTest8<uint8_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint8V6) {
+ bufferToIntTest8<uint8_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt8V4) {
+ bufferToIntTest8<int8_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt8V6) {
+ bufferToIntTest8<int8_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint16V4) {
+ bufferToIntTest16<uint16_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint16V6) {
+ bufferToIntTest16<uint16_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt16V4) {
+ bufferToIntTest16<int16_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt16V6) {
+ bufferToIntTest16<int16_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint32V4) {
+ bufferToIntTest32<uint32_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint32V6) {
+ bufferToIntTest32<uint32_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt32V4) {
+ bufferToIntTest32<int32_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt32V6) {
+ bufferToIntTest32<int32_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, setValuesUint8) {
+ setValuesTest<uint8_t>();
+}
+
+TEST_F(OptionIntArrayTest, setValuesInt8) {
+ setValuesTest<int8_t>();
+}
+
+TEST_F(OptionIntArrayTest, setValuesUint16) {
+ setValuesTest<uint16_t>();
+}
+
+TEST_F(OptionIntArrayTest, setValuesInt16) {
+ setValuesTest<int16_t>();
+}
+
+TEST_F(OptionIntArrayTest, setValuesUint32) {
+ setValuesTest<uint16_t>();
+}
+
+TEST_F(OptionIntArrayTest, setValuesInt32) {
+ setValuesTest<int16_t>();
+}
+
+TEST_F(OptionIntArrayTest, addValuesUint8) {
+ addValuesTest<uint8_t>();
+}
+
+TEST_F(OptionIntArrayTest, addValuesInt8) {
+ addValuesTest<int8_t>();
+}
+
+TEST_F(OptionIntArrayTest, addValuesUint16) {
+ addValuesTest<uint16_t>();
+}
+
+TEST_F(OptionIntArrayTest, addValuesInt16) {
+ addValuesTest<int16_t>();
+}
+
+TEST_F(OptionIntArrayTest, addValuesUint32) {
+ addValuesTest<uint16_t>();
+}
+
+TEST_F(OptionIntArrayTest, addValuesInt32) {
+ addValuesTest<int16_t>();
+}
+
+// This test checks that the option is correctly converted into
+// the textual format.
+TEST_F(OptionIntArrayTest, toText) {
+ OptionUint32Array option(Option::V4, 128);
+ option.addValue(1);
+ option.addValue(32);
+ option.addValue(324);
+
+ EXPECT_EQ("type=128, len=012: 1(uint32) 32(uint32) 324(uint32)",
+ option.toText());
+}
+
+// This test checks that the option holding multiple uint8 values
+// is correctly converted to the textual format.
+TEST_F(OptionIntArrayTest, toTextUint8) {
+ OptionUint8Array option(Option::V4, 128);
+ option.addValue(1);
+ option.addValue(7);
+ option.addValue(15);
+
+ EXPECT_EQ("type=128, len=003: 1(uint8) 7(uint8) 15(uint8)",
+ option.toText());
+}
+
+
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_int_unittest.cc b/src/lib/dhcp/tests/option_int_unittest.cc
new file mode 100644
index 0000000..a400173
--- /dev/null
+++ b/src/lib/dhcp/tests/option_int_unittest.cc
@@ -0,0 +1,571 @@
+// Copyright (C) 2012-2022 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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int.h>
+#include <util/buffer.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// Option code being used in many test cases.
+const uint16_t TEST_OPT_CODE = 232;
+
+/// @brief OptionInt test class.
+class OptionIntTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes the option buffer with some data.
+ OptionIntTest(): buf_(255), out_buf_(255) {
+ for (unsigned i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+
+ /// @brief Basic test for int8 and uint8 types.
+ ///
+ /// @note this function does not perform type check. Make
+ /// sure that only int8_t or uint8_t type is used.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @tparam T int8_t or uint8_t.
+ template<typename T>
+ void basicTest8(const Option::Universe u) {
+ // Create option that conveys single 8 bit integer value.
+ boost::shared_ptr<OptionInt<T> > opt;
+ // Initialize buffer with this value.
+ buf_[0] = 0xa1;
+ // Constructor may throw in case provided buffer is too short.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u,
+ TEST_OPT_CODE,
+ buf_.begin(),
+ buf_.begin() + 1))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // Option should return the same value that we initialized the first
+ // byte of the buffer with.
+ EXPECT_EQ(static_cast<T>(0xa1), opt->getValue());
+
+ // test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 1 byte.
+ EXPECT_EQ(1, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // The total length is 1 byte for data and 2 bytes or 4 bytes
+ // for option code and option length.
+ if (u == Option::V4) {
+ EXPECT_EQ(3, out_buf_.getLength());
+ } else {
+ EXPECT_EQ(5, out_buf_.getLength());
+ }
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+ if (u == Option::V4) {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(1, out.readUint8());
+ } else {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(1, out.readUint16());
+ }
+ // if data is correct
+ EXPECT_EQ(0xa1, out.readUint8() );
+ }
+
+ /// @brief Basic test for int16 and uint16 types.
+ ///
+ /// @note this function does not perform type check. Make
+ /// sure that only int16_t or uint16_t type is used.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @tparam T int16_t or uint16_t.
+ template<typename T>
+ void basicTest16(const Option::Universe u) {
+ // Create option that conveys single 16-bit integer value.
+ boost::shared_ptr<OptionInt<T> > opt;
+ // Initialize buffer with uint16_t value.
+ buf_[0] = 0xa1;
+ buf_[1] = 0xa2;
+ // Constructor may throw in case provided buffer is too short.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u,
+ TEST_OPT_CODE,
+ buf_.begin(),
+ buf_.begin() + 2))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // Option should return the value equal to the contents of first
+ // and second byte of the buffer.
+ EXPECT_EQ(static_cast<T>(0xa1a2), opt->getValue());
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 2 bytes.
+ EXPECT_EQ(2, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // The total length is 2 bytes for data and 2 or 4 bytes for a header.
+ if (u == Option::V4) {
+ EXPECT_EQ(4, out_buf_.getLength());
+ } else {
+ EXPECT_EQ(6, out_buf_.getLength());
+ }
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+ if (u == Option::V4) {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(2, out.readUint8());
+ } else {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(2, out.readUint16());
+ }
+ // if data is correct
+ EXPECT_EQ(0xa1a2, out.readUint16() );
+ }
+
+ /// @brief Basic test for int32 and uint32 types.
+ ///
+ /// @note this function does not perform type check. Make
+ /// sure that only int32_t or uint32_t type is used.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @tparam T int32_t or uint32_t.
+ template<typename T>
+ void basicTest32(const Option::Universe u) {
+ // Create option that conveys single 32-bit integer value.
+ boost::shared_ptr<OptionInt<T> > opt;
+ // Initialize buffer with 32-bit integer value.
+ buf_[0] = 0xa1;
+ buf_[1] = 0xa2;
+ buf_[2] = 0xa3;
+ buf_[3] = 0xa4;
+ // Constructor may throw in case provided buffer is too short.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u,
+ TEST_OPT_CODE,
+ buf_.begin(),
+ buf_.begin() + 4))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // Option should return the value equal to the value made of
+ // first 4 bytes of the buffer.
+ EXPECT_EQ(static_cast<T>(0xa1a2a3a4), opt->getValue());
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 4 bytes.
+ EXPECT_EQ(4, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // The total length is 4 bytes for data and 2 or 4 bytes for a header.
+ if (u == Option::V4) {
+ EXPECT_EQ(6, out_buf_.getLength());
+ } else {
+ EXPECT_EQ(8, out_buf_.getLength());
+ }
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+ if (u == Option::V4) {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(4, out.readUint8());
+ } else {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(4, out.readUint16());
+ }
+ // if data is correct
+ EXPECT_EQ(0xa1a2a3a4, out.readUint32());
+ }
+
+ OptionBuffer buf_; ///< Option buffer
+ OutputBuffer out_buf_; ///< Output buffer
+};
+
+/// @todo: below, there is a bunch of tests for options that
+/// convey unsigned value. We should maybe extend these tests for
+/// signed types too.
+
+TEST_F(OptionIntTest, useInvalidType) {
+ EXPECT_THROW(
+ boost::scoped_ptr<OptionInt<bool> >(new OptionInt<bool>(Option::V6,
+ D6O_ELAPSED_TIME, 10)),
+ InvalidDataType
+ );
+
+ EXPECT_THROW(
+ boost::scoped_ptr<OptionInt<int64_t> >(new OptionInt<int64_t>(Option::V6,
+ D6O_ELAPSED_TIME, 10)),
+ InvalidDataType
+ );
+
+}
+
+TEST_F(OptionIntTest, basicUint8V4) {
+ basicTest8<uint8_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicUint8V6) {
+ basicTest8<uint8_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicUint16V4) {
+ basicTest16<uint16_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicUint16V6) {
+ basicTest16<uint16_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicUint32V4) {
+ basicTest32<uint32_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicUint32V6) {
+ basicTest32<uint32_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicInt8V4) {
+ basicTest8<int8_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicInt8V6) {
+ basicTest8<int8_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicInt16V4) {
+ basicTest16<int16_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicInt16V6) {
+ basicTest16<int16_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicInt32V4) {
+ basicTest32<int32_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicInt32V6) {
+ basicTest32<int32_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, setValueUint8) {
+ boost::shared_ptr<OptionInt<uint8_t> > opt(new OptionInt<uint8_t>(Option::V6,
+ D6O_PREFERENCE, 123));
+ // Check if constructor initialized the option value correctly.
+ EXPECT_EQ(123, opt->getValue());
+ // Override the value.
+ opt->setValue(111);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_PREFERENCE, opt->getType());
+ // Check if the value has been overridden.
+ EXPECT_EQ(111, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueInt8) {
+ boost::shared_ptr<OptionInt<int8_t> > opt(new OptionInt<int8_t>(Option::V6,
+ D6O_PREFERENCE, -123));
+ // Check if constructor initialized the option value correctly.
+ EXPECT_EQ(-123, opt->getValue());
+ // Override the value.
+ opt->setValue(-111);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_PREFERENCE, opt->getType());
+ // Check if the value has been overridden.
+ EXPECT_EQ(-111, opt->getValue());
+}
+
+
+TEST_F(OptionIntTest, setValueUint16) {
+ boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V6,
+ D6O_ELAPSED_TIME, 123));
+ // Check if constructor initialized the option value correctly.
+ EXPECT_EQ(123, opt->getValue());
+ // Override the value.
+ opt->setValue(0x0102);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
+ // Check if the value has been overridden.
+ EXPECT_EQ(0x0102, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueInt16) {
+ boost::shared_ptr<OptionInt<int16_t> > opt(new OptionInt<int16_t>(Option::V6,
+ D6O_ELAPSED_TIME, -16500));
+ // Check if constructor initialized the option value correctly.
+ EXPECT_EQ(-16500, opt->getValue());
+ // Override the value.
+ opt->setValue(-20100);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
+ // Check if the value has been overridden.
+ EXPECT_EQ(-20100, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueUint32) {
+ boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6,
+ D6O_CLT_TIME, 123));
+ // Check if constructor initialized the option value correctly.
+ EXPECT_EQ(123, opt->getValue());
+ // Override the value.
+ opt->setValue(0x01020304);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_CLT_TIME, opt->getType());
+ // Check if the value has been overridden.
+ EXPECT_EQ(0x01020304, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueInt32) {
+ boost::shared_ptr<OptionInt<int32_t> > opt(new OptionInt<int32_t>(Option::V6,
+ D6O_CLT_TIME, -120100));
+ // Check if constructor initialized the option value correctly.
+ EXPECT_EQ(-120100, opt->getValue());
+ // Override the value.
+ opt->setValue(-125000);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_CLT_TIME, opt->getType());
+ // Check if the value has been overridden.
+ EXPECT_EQ(-125000, opt->getValue());
+}
+
+TEST_F(OptionIntTest, packSuboptions4) {
+ boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V4,
+ TEST_OPT_CODE,
+ 0x0102));
+ // Add sub option with some 4 bytes of data (each byte set to 1)
+ OptionPtr sub1(new Option(Option::V4, TEST_OPT_CODE + 1, OptionBuffer(4, 1)));
+ // Add sub option with some 5 bytes of data (each byte set to 2)
+ OptionPtr sub2(new Option(Option::V4, TEST_OPT_CODE + 2, OptionBuffer(5, 2)));
+
+ // Add suboptions.
+ opt->addOption(sub1);
+ opt->addOption(sub2);
+
+ // Prepare reference data: option + suboptions in wire format.
+ uint8_t expected[] = {
+ TEST_OPT_CODE, 15, // option header
+ 0x01, 0x02, // data, uint16_t value = 0x0102
+ TEST_OPT_CODE + 1, 0x04, 0x01, 0x01, 0x01, 0x01, // sub1
+ TEST_OPT_CODE + 2, 0x05, 0x02, 0x02, 0x02, 0x02, 0x02 // sub2
+ };
+
+ // Create on-wire format of option and suboptions.
+ opt->pack(out_buf_);
+ // Compare the on-wire data with the reference buffer.
+ ASSERT_EQ(sizeof(expected), out_buf_.getLength());
+ EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, sizeof(expected)));
+}
+
+TEST_F(OptionIntTest, packSuboptions6) {
+ // option code is really uint16_t, but using uint8_t
+ // for easier conversion to uint8_t array.
+ uint8_t opt_code = 80;
+
+ boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6,
+ opt_code, 0x01020304));
+ OptionPtr sub1(new Option(Option::V6, 0xcafe));
+
+ boost::shared_ptr<Option6IAAddr> addr1(
+ new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000));
+
+ opt->addOption(sub1);
+ opt->addOption(addr1);
+
+ ASSERT_EQ(28, addr1->len());
+ ASSERT_EQ(4, sub1->len());
+ ASSERT_EQ(40, opt->len());
+
+ uint8_t expected[] = {
+ 0, opt_code, // type
+ 0, 36, // length
+ 0x01, 0x02, 0x03, 0x04, // uint32_t value
+
+ // iaaddr suboption
+ D6O_IAADDR / 256, D6O_IAADDR % 256, // type
+ 0, 24, // len
+ 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
+ 0, 0, 0x50, 0, // preferred-lifetime
+ 0, 0, 0x70, 0, // valid-lifetime
+
+ // suboption
+ 0xca, 0xfe, // type
+ 0, 0 // len
+ };
+
+ // Create on-wire format of option and suboptions.
+ opt->pack(out_buf_);
+ // Compare the on-wire data with the reference buffer.
+ ASSERT_EQ(40, out_buf_.getLength());
+ EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, 40));
+}
+
+TEST_F(OptionIntTest, unpackSuboptions4) {
+ // Prepare reference data.
+ const uint8_t expected[] = {
+ TEST_OPT_CODE, 0x0A, // option code and length
+ 0x01, 0x02, 0x03, 0x04, // data, uint32_t value = 0x01020304
+ TEST_OPT_CODE + 1, 0x4, 0x01, 0x01, 0x01, 0x01 // suboption
+ };
+ // Make sure that the buffer size is sufficient to copy the
+ // elements from the array.
+ ASSERT_GE(buf_.size(), sizeof(expected));
+ // Copy the data to a vector so as we can pass it to the
+ // OptionInt's constructor.
+ memcpy(&buf_[0], expected, sizeof(expected));
+
+ // Create an option.
+ boost::shared_ptr<OptionInt<uint32_t> > opt;
+ EXPECT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionInt<uint32_t> >(new OptionInt<uint32_t>(Option::V4, TEST_OPT_CODE,
+ buf_.begin() + 2,
+ buf_.begin() + sizeof(expected)));
+ );
+ ASSERT_TRUE(opt);
+
+ // Verify that it has expected type and data.
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ EXPECT_EQ(0x01020304, opt->getValue());
+
+ // Expect that there is the sub option with the particular
+ // option code added.
+ OptionPtr subopt = opt->getOption(TEST_OPT_CODE + 1);
+ ASSERT_TRUE(subopt);
+ // Check that this option has correct universe and code.
+ EXPECT_EQ(Option::V4, subopt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE + 1, subopt->getType());
+ // Check the sub option's data.
+ OptionBuffer subopt_buf = subopt->getData();
+ ASSERT_EQ(4, subopt_buf.size());
+ // The data in the input buffer starts at offset 8.
+ EXPECT_TRUE(std::equal(subopt_buf.begin(), subopt_buf.end(), buf_.begin() + 8));
+}
+
+TEST_F(OptionIntTest, unpackSuboptions6) {
+ // option code is really uint16_t, but using uint8_t
+ // for easier conversion to uint8_t array.
+ const uint8_t opt_code = 80;
+ // Prepare reference data.
+ uint8_t expected[] = {
+ 0, opt_code, // type
+ 0, 34, // length
+ 0x01, 0x02, // uint16_t value
+
+ // iaaddr suboption
+ D6O_IAADDR / 256, D6O_IAADDR % 256, // type
+ 0, 24, // len
+ 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
+ 0, 0, 0x50, 0, // preferred-lifetime
+ 0, 0, 0x70, 0, // valid-lifetime
+
+ // suboption
+ 0xca, 0xfe, // type
+ 0, 0 // len
+ };
+ ASSERT_EQ(38, sizeof(expected));
+
+ // Make sure that the buffer's size is sufficient to
+ // copy the elements from the array.
+ ASSERT_GE(buf_.size(), sizeof(expected));
+ memcpy(&buf_[0], expected, sizeof(expected));
+
+ boost::shared_ptr<OptionInt<uint16_t> > opt;
+ EXPECT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionInt<uint16_t> >(new OptionInt<uint16_t>(Option::V6, opt_code,
+ buf_.begin() + 4,
+ buf_.begin() + sizeof(expected)));
+ );
+ ASSERT_TRUE(opt);
+
+ EXPECT_EQ(opt_code, opt->getType());
+ EXPECT_EQ(0x0102, opt->getValue());
+
+ // Checks for address option
+ OptionPtr subopt = opt->getOption(D6O_IAADDR);
+ ASSERT_TRUE(subopt);
+ boost::shared_ptr<Option6IAAddr> addr(boost::dynamic_pointer_cast<Option6IAAddr>(subopt));
+ ASSERT_TRUE(addr);
+
+ EXPECT_EQ(D6O_IAADDR, addr->getType());
+ EXPECT_EQ(28, addr->len());
+ EXPECT_EQ(0x5000, addr->getPreferred());
+ EXPECT_EQ(0x7000, addr->getValid());
+ EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText());
+
+ // Checks for dummy option
+ subopt = opt->getOption(0xcafe);
+ ASSERT_TRUE(subopt); // should be non-NULL
+
+ EXPECT_EQ(0xcafe, subopt->getType());
+ EXPECT_EQ(4, subopt->len());
+ // There should be no data at all
+ EXPECT_EQ(0, subopt->getData().size());
+
+ // Try to get non-existent option.
+ subopt = opt->getOption(1);
+ // Expecting NULL which means that option does not exist.
+ ASSERT_FALSE(subopt);
+}
+
+// This test checks that the toText function returns the option in the
+// textual format correctly.
+TEST_F(OptionIntTest, toText) {
+ OptionUint32 option(Option::V4, 128, 345678);
+ EXPECT_EQ("type=128, len=004: 345678 (uint32)", option.toText());
+
+ option.addOption(OptionPtr(new OptionUint16(Option::V4, 1, 234)));
+ option.addOption(OptionPtr(new OptionUint8(Option::V4, 3, 22)));
+ EXPECT_EQ("type=128, len=011: 345678 (uint32),\n"
+ "options:\n"
+ " type=001, len=002: 234 (uint16)\n"
+ " type=003, len=001: 22 (uint8)",
+ option.toText());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc b/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc
new file mode 100644
index 0000000..01dcc18
--- /dev/null
+++ b/src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc
@@ -0,0 +1,666 @@
+// Copyright (C) 2015-2023 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 <dhcp/option_opaque_data_tuples.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+// This test checks that the DHCPv4 option constructor sets the default
+// properties to the expected values.
+TEST(OptionOpaqueDataTuples, constructor4) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS);
+ // Option length is 1 byte for option code + 1 byte for option size
+ EXPECT_EQ(2, data_tuple.len());
+ // There should be no tuples.
+ EXPECT_EQ(0, data_tuple.getTuplesNum());
+}
+
+// This test checks that the DHCPv4 option constructor sets the default
+// properties to the expected values.
+TEST(OptionOpaqueDataTuples, constructor4_with_ltf) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT,
+ OpaqueDataTuple::LENGTH_2_BYTES);
+ // Option length is 1 byte for option code + 1 byte for option size
+ EXPECT_EQ(2, data_tuple.len());
+ // There should be no tuples.
+ EXPECT_EQ(0, data_tuple.getTuplesNum());
+}
+
+// This test checks that the DHCPv6 option constructor sets the default
+// properties to the expected values.
+TEST(OptionOpaqueDataTuples, constructor6) {
+ OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM);
+ // Option length is 2 bytes for option code + 2 bytes for option size
+ EXPECT_EQ(4, data_tuple.len());
+ // There should be no tuples.
+ EXPECT_EQ(0, data_tuple.getTuplesNum());
+}
+
+// This test verifies that it is possible to append the opaque data tuple
+// to the option and then retrieve it.
+TEST(OptionOpaqueDataTuples, addTuple4) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS);
+ // Initially there should be no tuples (for DHCPv4).
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Create a new tuple and add it to the option.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "xyz";
+ data_tuple.addTuple(tuple);
+ // The option should now hold one tuple.
+ ASSERT_EQ(1, data_tuple.getTuplesNum());
+ EXPECT_EQ("xyz", data_tuple.getTuple(0).getText());
+ // Add another tuple.
+ tuple = "abc";
+ data_tuple.addTuple(tuple);
+ // The option should now hold exactly two tuples in the order in which
+ // they were added.
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ EXPECT_EQ("xyz", data_tuple.getTuple(0).getText());
+ EXPECT_EQ("abc", data_tuple.getTuple(1).getText());
+
+ // Check that hasTuple correctly identifies existing tuples.
+ EXPECT_TRUE(data_tuple.hasTuple("xyz"));
+ EXPECT_TRUE(data_tuple.hasTuple("abc"));
+ EXPECT_FALSE(data_tuple.hasTuple("other"));
+
+ // Attempt to add the tuple with 2 byte long length field should fail
+ // for DHCPv4 option.
+ OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES);
+ EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue);
+
+ // Similarly, adding a tuple with 1 bytes long length field should
+ // fail for DHCPv6 option.
+ OptionOpaqueDataTuples data_tuple2(Option::V6, D6O_BOOTFILE_PARAM);
+ OpaqueDataTuple tuple3(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_THROW(data_tuple2.addTuple(tuple3), isc::BadValue);
+}
+
+// This test verifies that it is possible to append the opaque data tuple
+// to the option and then retrieve it.
+TEST(OptionOpaqueDataTuples, addTuple6) {
+ OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM);
+ // Initially there should be no tuples (for DHCPv6).
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Create a new tuple and add it to the option.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "xyz";
+ data_tuple.addTuple(tuple);
+ // The option should now hold one tuple.
+ ASSERT_EQ(1, data_tuple.getTuplesNum());
+ EXPECT_EQ("xyz", data_tuple.getTuple(0).getText());
+ // Add another tuple.
+ tuple = "abc";
+ data_tuple.addTuple(tuple);
+ // The option should now hold exactly two tuples in the order in which
+ // they were added.
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ EXPECT_EQ("xyz", data_tuple.getTuple(0).getText());
+ EXPECT_EQ("abc", data_tuple.getTuple(1).getText());
+
+ // Check that hasTuple correctly identifies existing tuples.
+ EXPECT_TRUE(data_tuple.hasTuple("xyz"));
+ EXPECT_TRUE(data_tuple.hasTuple("abc"));
+ EXPECT_FALSE(data_tuple.hasTuple("other"));
+
+ // Attempt to add the tuple with 1 byte long length field should fail
+ // for DHCPv6 option.
+ OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue);
+
+ // Similarly, adding a tuple with 2 bytes long length field should
+ // fail for DHCPv4 option.
+ OptionOpaqueDataTuples data_tuple2(Option::V4, DHO_VIVCO_SUBOPTIONS);
+ OpaqueDataTuple tuple3(OpaqueDataTuple::LENGTH_2_BYTES);
+ EXPECT_THROW(data_tuple2.addTuple(tuple3), isc::BadValue);
+}
+
+// This test checks that it is possible to replace existing tuple.
+TEST(OptionOpaqueDataTuples, setTuple4) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS);
+ // Initially there should be no tuples (for DHCPv4).
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Add a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "xyz";
+ data_tuple.addTuple(tuple);
+
+ // Add another one.
+ tuple = "abc";
+ data_tuple.addTuple(tuple);
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ ASSERT_EQ("abc", data_tuple.getTuple(1).getText());
+
+ // Try to replace them with new tuples.
+ tuple = "new_xyz";
+ ASSERT_NO_THROW(data_tuple.setTuple(0, tuple));
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ EXPECT_EQ("new_xyz", data_tuple.getTuple(0).getText());
+
+ tuple = "new_abc";
+ ASSERT_NO_THROW(data_tuple.setTuple(1, tuple));
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ EXPECT_EQ("new_abc", data_tuple.getTuple(1).getText());
+
+ // For out of range position, exception should be thrown.
+ tuple = "foo";
+ EXPECT_THROW(data_tuple.setTuple(2, tuple), isc::OutOfRange);
+}
+
+// This test checks that it is possible to replace existing tuple.
+TEST(OptionOpaqueDataTuples, setTuple6) {
+ OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM);
+ // Initially there should be no tuples (for DHCPv6).
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Add a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "xyz";
+ data_tuple.addTuple(tuple);
+
+ // Add another one.
+ tuple = "abc";
+ data_tuple.addTuple(tuple);
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ ASSERT_EQ("abc", data_tuple.getTuple(1).getText());
+
+ // Try to replace them with new tuples.
+ tuple = "new_xyz";
+ ASSERT_NO_THROW(data_tuple.setTuple(0, tuple));
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ EXPECT_EQ("new_xyz", data_tuple.getTuple(0).getText());
+
+ tuple = "new_abc";
+ ASSERT_NO_THROW(data_tuple.setTuple(1, tuple));
+ ASSERT_EQ(2, data_tuple.getTuplesNum());
+ EXPECT_EQ("new_abc", data_tuple.getTuple(1).getText());
+
+ // For out of range position, exception should be thrown.
+ tuple = "foo";
+ EXPECT_THROW(data_tuple.setTuple(2, tuple), isc::OutOfRange);
+}
+
+// Check that the returned length of the DHCPv4 option is correct.
+TEST(OptionOpaqueDataTuples, len4) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS);
+ ASSERT_EQ(2, data_tuple.len());
+ // Add first tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "xyz";
+ ASSERT_NO_THROW(data_tuple.addTuple(tuple));
+ // The total length grows by 1 byte of the length field and 3 bytes
+ // consumed by 'xyz'.
+ EXPECT_EQ(6, data_tuple.len());
+ // Add another tuple and check that the total size gets increased.
+ tuple = "abc";
+ data_tuple.addTuple(tuple);
+ EXPECT_EQ(10, data_tuple.len());
+}
+
+// Check that the returned length of the DHCPv4 option is correct when
+// LTF is passed explicitly in constructor.
+TEST(OptionOpaqueDataTuples, len4_constructor_with_ltf) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT, buf.begin(),
+ buf.end(), OpaqueDataTuple::LENGTH_2_BYTES);
+ // Expected len = 20 = 2 (v4 headers) + 2 (LFT) + 11 (1st tuple) + 2 (LFT) + 3 (2nd tuple)
+ ASSERT_EQ(20, data_tuple.len());
+}
+
+// Check that the returned length of the DHCPv6 option is correct.
+TEST(OptionOpaqueDataTuples, len6) {
+ OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM);
+ ASSERT_EQ(4, data_tuple.len());
+ // Add first tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "xyz";
+ ASSERT_NO_THROW(data_tuple.addTuple(tuple));
+ // The total length grows by 2 bytes of the length field and 3 bytes
+ // consumed by 'xyz'.
+ EXPECT_EQ(9, data_tuple.len());
+ // Add another tuple and check that the total size gets increased.
+ tuple = "abc";
+ data_tuple.addTuple(tuple);
+ EXPECT_EQ(14, data_tuple.len());
+}
+
+// Check that the DHCPv4 option is rendered to the buffer in wire format.
+TEST(OptionOpaqueDataTuples, pack4) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS);
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Add tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "Hello world";
+ data_tuple.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ data_tuple.addTuple(tuple);
+
+ // Render the data to the buffer.
+ OutputBuffer buf(10);
+ ASSERT_NO_THROW(data_tuple.pack(buf));
+ ASSERT_EQ(18, buf.getLength());
+
+ // Prepare reference data.
+ const uint8_t ref[] = {
+ 0x7C, 0x10, // option 124, length 16
+ 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ // Compare the buffer with reference data.
+ EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+ static_cast<const void*>(buf.getData()),
+ buf.getLength()));
+}
+
+// Check that the DHCPv4 option is rendered to the buffer in wire format,
+// when tuple's length field is coded on 2 octets.
+TEST(OptionOpaqueDataTuples, pack4_with_ltf) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT,
+ OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Add tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "Hello world";
+ data_tuple.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ data_tuple.addTuple(tuple);
+
+ // Render the data to the buffer.
+ OutputBuffer buf(10);
+ ASSERT_NO_THROW(data_tuple.pack(buf));
+ ASSERT_EQ(20, buf.getLength());
+
+ // Prepare reference data.
+ const uint8_t ref[] = {
+ 0x8F, 0x12, // option 143, length 18
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ // Compare the buffer with reference data.
+ EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+ static_cast<const void*>(buf.getData()),
+ buf.getLength()));
+}
+
+// Check that the DHCPv6 option is rendered to the buffer in wire format.
+TEST(OptionOpaqueDataTuples, pack6) {
+ OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM);
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Add tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "Hello world";
+ data_tuple.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ data_tuple.addTuple(tuple);
+
+ // Render the data to the buffer.
+ OutputBuffer buf(10);
+ ASSERT_NO_THROW(data_tuple.pack(buf));
+ ASSERT_EQ(22, buf.getLength());
+
+ // Prepare reference data.
+ const uint8_t ref[] = {
+ 0x00, 0x3C, 0x00, 0x12, // option 60, length 18
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ // Compare the buffer with reference data.
+ EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+ static_cast<const void*>(buf.getData()),
+ buf.getLength()));
+}
+
+// This function checks that the DHCPv4 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionOpaqueDataTuples, unpack4) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4,
+ DHO_VIVCO_SUBOPTIONS,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, data_tuple->getType());
+ ASSERT_EQ(2, data_tuple->getTuplesNum());
+ EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText());
+ EXPECT_EQ("foo", data_tuple->getTuple(1).getText());
+}
+
+// This function checks that the DHCPv4 option with two opaque data tuples
+// is parsed correctly. Tuple's LTF is passed explicitly in constructor.
+TEST(OptionOpaqueDataTuples, unpack4_constructor_with_ltf) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4,
+ DHO_V4_SZTP_REDIRECT,
+ buf.begin(),
+ buf.end(),
+ OpaqueDataTuple::LENGTH_2_BYTES));
+ );
+ EXPECT_EQ(DHO_V4_SZTP_REDIRECT, data_tuple->getType());
+ ASSERT_EQ(2, data_tuple->getTuplesNum());
+ EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText());
+ EXPECT_EQ("foo", data_tuple->getTuple(1).getText());
+}
+
+// This function checks that the DHCPv6 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionOpaqueDataTuples, unpack6) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6,
+ D6O_BOOTFILE_PARAM,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType());
+ ASSERT_EQ(2, data_tuple->getTuplesNum());
+ EXPECT_EQ("Hello world", data_tuple->getTuple(0).getText());
+ EXPECT_EQ("foo", data_tuple->getTuple(1).getText());
+}
+
+// This test checks that the DHCPv4 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionOpaqueDataTuples, unpack4EmptyTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {0x00}; // tuple length is 0
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4,
+ DHO_VIVCO_SUBOPTIONS,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, data_tuple->getType());
+ ASSERT_EQ(1, data_tuple->getTuplesNum());
+ EXPECT_TRUE(data_tuple->getTuple(0).getText().empty());
+}
+
+// This test checks that the DHCPv4 option with opaque data of size 0
+// is correctly parsed. Tuple's LTF is passed explicitly in constructor.
+TEST(OptionOpaqueDataTuples, unpack4EmptyTuple_constructor_with_ltf) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {0x00, 0x00}; // tuple length is 0
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4,
+ DHO_V4_SZTP_REDIRECT,
+ buf.begin(),
+ buf.end(),
+ OpaqueDataTuple::LENGTH_2_BYTES));
+ );
+ EXPECT_EQ(DHO_V4_SZTP_REDIRECT, data_tuple->getType());
+ ASSERT_EQ(1, data_tuple->getTuplesNum());
+ EXPECT_TRUE(data_tuple->getTuple(0).getText().empty());
+}
+
+// This test checks that the DHCPv6 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionOpaqueDataTuples, unpack6EmptyTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {0x00, 0x00}; // tuple length is 0
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6,
+ D6O_BOOTFILE_PARAM,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType());
+ ASSERT_EQ(1, data_tuple->getTuplesNum());
+ EXPECT_TRUE(data_tuple->getTuple(0).getText().empty());
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv4 option
+TEST(OptionOpaqueDataTuples, unpack4Truncated) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!)
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ EXPECT_THROW(OptionOpaqueDataTuples(Option::V4,
+ DHO_VIVCO_SUBOPTIONS,
+ buf.begin(),
+ buf.end()),
+ isc::dhcp::OpaqueDataTupleError);
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv4 option,
+// when tuple's length field is coded on 2 octets.
+TEST(OptionOpaqueDataTuples, unpack4Truncated_with_ltf) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!)
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ EXPECT_THROW(OptionOpaqueDataTuples(Option::V4,
+ DHO_V4_SZTP_REDIRECT,
+ buf.begin(),
+ buf.end(),
+ OpaqueDataTuple::LENGTH_2_BYTES),
+ isc::dhcp::OpaqueDataTupleError);
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv6
+// bootfile-param option
+TEST(OptionOpaqueDataTuples, unpack6Truncated) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!)
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ EXPECT_THROW(OptionOpaqueDataTuples(Option::V6,
+ D6O_BOOTFILE_PARAM,
+ buf.begin(),
+ buf.end()),
+ isc::dhcp::OpaqueDataTupleError);
+}
+
+// This test checks that the DHCPv4 option containing no opaque
+// data is parsed correctly.
+TEST(OptionOpaqueDataTuples, unpack4NoTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4,
+ DHO_VIVCO_SUBOPTIONS,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, data_tuple->getType());
+ EXPECT_EQ(0, data_tuple->getTuplesNum());
+}
+
+// This test checks that the DHCPv4 option containing no opaque
+// data is parsed correctly when tuple's length field is coded on 2 octets.
+TEST(OptionOpaqueDataTuples, unpack4NoTuple_with_ltf) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V4,
+ DHO_V4_SZTP_REDIRECT,
+ buf.begin(),
+ buf.end(),
+ OpaqueDataTuple::LENGTH_2_BYTES));
+ );
+ EXPECT_EQ(DHO_V4_SZTP_REDIRECT, data_tuple->getType());
+ EXPECT_EQ(0, data_tuple->getTuplesNum());
+}
+
+// This test checks that the DHCPv6 bootfile-param option containing no opaque
+// data is parsed correctly.
+TEST(OptionOpaqueDataTuples, unpack6NoTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionOpaqueDataTuplesPtr data_tuple;
+ ASSERT_NO_THROW(
+ data_tuple = OptionOpaqueDataTuplesPtr(new OptionOpaqueDataTuples(Option::V6,
+ D6O_BOOTFILE_PARAM,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(D6O_BOOTFILE_PARAM, data_tuple->getType());
+ EXPECT_EQ(0, data_tuple->getTuplesNum());
+}
+
+// Verifies correctness of the text representation of the DHCPv4 option.
+TEST(OptionOpaqueDataTuples, toText4) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_VIVCO_SUBOPTIONS);
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Lets add a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "Hello world";
+ data_tuple.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ data_tuple.addTuple(tuple);
+ // Check that the text representation of the option is as expected.
+ EXPECT_EQ("type=124, len=16,"
+ " data-len0=11, data0='Hello world',"
+ " data-len1=3, data1='foo'",
+ data_tuple.toText());
+
+ // Check that indentation works.
+ EXPECT_EQ(" type=124, len=16,"
+ " data-len0=11, data0='Hello world',"
+ " data-len1=3, data1='foo'",
+ data_tuple.toText(2));
+}
+
+// Verifies correctness of the text representation of the DHCPv4 option when
+// tuple's length field is coded on 2 octets.
+TEST(OptionOpaqueDataTuples, toText4_with_ltf) {
+ OptionOpaqueDataTuples data_tuple(Option::V4, DHO_V4_SZTP_REDIRECT, OpaqueDataTuple::LENGTH_2_BYTES);
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Lets add a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "Hello world";
+ data_tuple.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ data_tuple.addTuple(tuple);
+ // Check that the text representation of the option is as expected.
+ EXPECT_EQ("type=143, len=18,"
+ " data-len0=11, data0='Hello world',"
+ " data-len1=3, data1='foo'",
+ data_tuple.toText());
+
+ // Check that indentation works.
+ EXPECT_EQ(" type=143, len=18,"
+ " data-len0=11, data0='Hello world',"
+ " data-len1=3, data1='foo'",
+ data_tuple.toText(2));
+}
+
+// Verifies correctness of the text representation of the DHCPv6 option.
+TEST(OptionOpaqueDataTuples, toText6) {
+ OptionOpaqueDataTuples data_tuple(Option::V6, D6O_BOOTFILE_PARAM);
+ ASSERT_EQ(0, data_tuple.getTuplesNum());
+ // Lets add a tuple
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "Hello world";
+ data_tuple.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ data_tuple.addTuple(tuple);
+ // Check that the text representation of the option is as expected.
+ EXPECT_EQ("type=60, len=18,"
+ " data-len0=11, data0='Hello world',"
+ " data-len1=3, data1='foo'",
+ data_tuple.toText());
+
+ // Check that indentation works.
+ EXPECT_EQ(" type=60, len=18,"
+ " data-len0=11, data0='Hello world',"
+ " data-len1=3, data1='foo'",
+ data_tuple.toText(2));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/option_space_unittest.cc b/src/lib/dhcp/tests/option_space_unittest.cc
new file mode 100644
index 0000000..77b25aa
--- /dev/null
+++ b/src/lib/dhcp/tests/option_space_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright (C) 2012-2017 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 <dhcp/option_space.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+using namespace isc;
+
+namespace {
+
+// The purpose of this test is to verify that the constructor
+// creates an object with members initialized to correct values.
+TEST(OptionSpaceTest, constructor) {
+ // Create some option space.
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Create another object with different values
+ // to check that the values will change.
+ OptionSpace space2("abc", false);
+ EXPECT_EQ("abc", space2.getName());
+ EXPECT_FALSE(space2.isVendorSpace());
+
+ // Verify that constructor throws exception if invalid
+ // option space name is provided.
+ EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify that the vendor-space flag
+// can be overridden.
+TEST(OptionSpaceTest, setVendorSpace) {
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Override the vendor space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+// The purpose of this test is to verify that the static function
+// to validate the option space name works correctly.
+TEST(OptionSpaceTest, validateName) {
+ // Positive test scenarios: letters, digits, dashes, underscores
+ // lower/upper case allowed.
+ EXPECT_TRUE(OptionSpace::validateName("abc"));
+ EXPECT_TRUE(OptionSpace::validateName("dash-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("digits0912"));
+ EXPECT_TRUE(OptionSpace::validateName("1234"));
+ EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
+
+ // Negative test scenarios: empty strings, dots, spaces are not
+ // allowed
+ EXPECT_FALSE(OptionSpace::validateName(""));
+ EXPECT_FALSE(OptionSpace::validateName(" "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc "));
+ EXPECT_FALSE(OptionSpace::validateName("isc "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc with-space"));
+
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ EXPECT_FALSE(OptionSpace::validateName("-isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc-"));
+ EXPECT_FALSE(OptionSpace::validateName("_isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc_"));
+
+ // Test other special characters
+ const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+ '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'',
+ '\\', '|', '<','>', ',', '.', '?', '~', '`' };
+ for (int i = 0; i < sizeof(specials); ++i) {
+ std::ostringstream stream;
+ // Concatenate valid option space name: "abc" with an invalid character.
+ // That way we get option space names like: "abc!", "abc$" etc. It is
+ // expected that the validating function fails form them.
+ stream << "abc" << specials[i];
+ EXPECT_FALSE(OptionSpace::validateName(stream.str()))
+ << "Test failed for special character '" << specials[i] << "'.";
+ }
+}
+
+// The purpose of this test is to verify that the constructors of the
+// OptionSpace6 class set the class members to correct values.
+TEST(OptionSpace6Test, constructor) {
+ // Create some option space and do not specify enterprise number.
+ // In such case the vendor space flag is expected to be
+ // set to false.
+ OptionSpace6 space1("abcd");
+ EXPECT_EQ("abcd", space1.getName());
+ EXPECT_FALSE(space1.isVendorSpace());
+
+ // Create an option space and specify an enterprise number. In this
+ // case the vendor space flag is expected to be set to true and the
+ // enterprise number should be set to a desired value.
+ OptionSpace6 space2("abcd", 2145);
+ EXPECT_EQ("abcd", space2.getName());
+ EXPECT_TRUE(space2.isVendorSpace());
+ EXPECT_EQ(2145, space2.getEnterpriseNumber());
+
+ // Verify that constructors throw an exception when invalid option
+ // space name has been specified.
+ EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace);
+ EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify an option space can be marked
+// vendor option space and enterprise number can be set.
+TEST(OptionSpace6Test, setVendorSpace) {
+ OptionSpace6 space("isc");
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_FALSE(space.isVendorSpace());
+
+ // Mark it vendor option space and set enterprise id.
+ space.setVendorSpace(1234);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(1234, space.getEnterpriseNumber());
+
+ // Override the enterprise number to make sure and make sure that
+ // the new number is returned by the object.
+ space.setVendorSpace(2345);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(2345, space.getEnterpriseNumber());
+
+ // Clear the vendor option space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/option_string_unittest.cc b/src/lib/dhcp/tests/option_string_unittest.cc
new file mode 100644
index 0000000..38eca81
--- /dev/null
+++ b/src/lib/dhcp/tests/option_string_unittest.cc
@@ -0,0 +1,241 @@
+// Copyright (C) 2013-2019,2021 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 <dhcp/option_string.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief OptionString test class.
+class OptionStringTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes the test buffer with some data.
+ OptionStringTest() {
+ std::string test_string("This is a test string");
+ buf_.assign(test_string.begin(), test_string.end());
+ }
+
+ OptionBuffer buf_;
+
+};
+
+// This test verifies that the constructor which creates an option instance
+// from a string value will create it properly.
+TEST_F(OptionStringTest, constructorFromString) {
+ const std::string optv4_value = "some option";
+ OptionString optv4(Option::V4, 123, optv4_value);
+ EXPECT_EQ(Option::V4, optv4.getUniverse());
+ EXPECT_EQ(123, optv4.getType());
+ EXPECT_EQ(optv4_value, optv4.getValue());
+ EXPECT_EQ(Option::OPTION4_HDR_LEN + optv4_value.size(), optv4.len());
+
+ // Do another test with the same constructor to make sure that
+ // different set of parameters would initialize the class members
+ // to different values.
+ const std::string optv6_value = "other option";
+ OptionString optv6(Option::V6, 234, optv6_value);
+ EXPECT_EQ(Option::V6, optv6.getUniverse());
+ EXPECT_EQ(234, optv6.getType());
+ EXPECT_EQ("other option", optv6.getValue());
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + optv6_value.size(), optv6.len());
+
+ // Check that an attempt to use empty string in the constructor
+ // will result in an exception.
+ EXPECT_THROW(OptionString(Option::V6, 123, ""), isc::OutOfRange);
+
+ // Check that an attempt to use string containing only nulls
+ // in the constructor will result in an exception.
+ std::string nulls{"\0\0",2};
+ EXPECT_THROW(OptionString(Option::V6, 123, nulls), isc::OutOfRange);
+}
+
+// This test verifies that the constructor which creates an option instance
+// from a buffer, holding option payload, will create it properly.
+// This function calls unpack() internally thus test test is considered
+// to cover testing of unpack() functionality.
+TEST_F(OptionStringTest, constructorFromBuffer) {
+ // Attempt to create an option using empty buffer should result in
+ // an exception.
+ EXPECT_THROW(
+ OptionString(Option::V4, 234, buf_.begin(), buf_.begin()),
+ isc::dhcp::SkipThisOptionError
+ );
+
+ // NULLs should result in an exception.
+ std::vector<uint8_t>nulls = { 0, 0, 0 };
+ EXPECT_THROW(
+ OptionString(Option::V4, 234, nulls.begin(), nulls.begin()),
+ isc::dhcp::SkipThisOptionError
+ );
+
+ // Declare option as a scoped pointer here so as its scope is
+ // function wide. The initialization (constructor invocation)
+ // is pushed to the ASSERT_NO_THROW macro below, as it may
+ // throw exception if buffer is truncated.
+ boost::scoped_ptr<OptionString> optv4;
+ ASSERT_NO_THROW(
+ optv4.reset(new OptionString(Option::V4, 234, buf_.begin(), buf_.end()));
+ );
+ // Make sure that it has been initialized to non-NULL value.
+ ASSERT_TRUE(optv4);
+
+ // Test the instance of the created option.
+ const std::string optv4_value = "This is a test string";
+ EXPECT_EQ(Option::V4, optv4->getUniverse());
+ EXPECT_EQ(234, optv4->getType());
+ EXPECT_EQ(Option::OPTION4_HDR_LEN + buf_.size(), optv4->len());
+ EXPECT_EQ(optv4_value, optv4->getValue());
+
+ // Do the same test for V6 option.
+ boost::scoped_ptr<OptionString> optv6;
+ ASSERT_NO_THROW(
+ // Let's reduce the size of the buffer by one byte and see if our option
+ // will absorb this little change.
+ optv6.reset(new OptionString(Option::V6, 123, buf_.begin(), buf_.end() - 1));
+ );
+ // Make sure that it has been initialized to non-NULL value.
+ ASSERT_TRUE(optv6);
+
+ // Test the instance of the created option.
+ const std::string optv6_value = "This is a test strin";
+ EXPECT_EQ(Option::V6, optv6->getUniverse());
+ EXPECT_EQ(123, optv6->getType());
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + buf_.size() - 1, optv6->len());
+ EXPECT_EQ(optv6_value, optv6->getValue());
+}
+
+// This test verifies that the current option value can be overridden
+// with new value, using setValue method.
+TEST_F(OptionStringTest, setValue) {
+ // Create an instance of the option and set some initial value.
+ OptionString optv4(Option::V4, 123, "some option");
+ EXPECT_EQ("some option", optv4.getValue());
+ // Replace the value with the new one, and make sure it has
+ // been successful.
+ EXPECT_NO_THROW(optv4.setValue("new option value"));
+ EXPECT_EQ("new option value", optv4.getValue());
+ // Try to set to an empty string. It should throw exception.
+ EXPECT_THROW(optv4.setValue(""), isc::OutOfRange);
+}
+
+// This test verifies that the pack function encodes the option in
+// a on-wire format properly.
+TEST_F(OptionStringTest, pack) {
+ // Create an instance of the option.
+ std::string option_value("sample option value");
+ OptionString optv4(Option::V4, 123, option_value);
+ // Encode the option in on-wire format.
+ OutputBuffer buf(Option::OPTION4_HDR_LEN);
+ EXPECT_NO_THROW(optv4.pack(buf));
+
+ // Sanity check the length of the buffer.
+ ASSERT_EQ(Option::OPTION4_HDR_LEN + option_value.length(),
+ buf.getLength());
+ // Copy the contents of the OutputBuffer to InputBuffer because
+ // the latter has API to read data from it.
+ InputBuffer test_buf(buf.getData(), buf.getLength());
+ // First byte holds option code.
+ EXPECT_EQ(123, test_buf.readUint8());
+ // Second byte holds option length.
+ EXPECT_EQ(option_value.size(), test_buf.readUint8());
+ // Read the option data.
+ std::vector<uint8_t> data;
+ test_buf.readVector(data, test_buf.getLength() - test_buf.getPosition());
+ // And create a string from it.
+ std::string test_string(data.begin(), data.end());
+ // This string should be equal to the string used to create
+ // option's instance.
+ EXPECT_TRUE(option_value == test_string);
+}
+
+// This test checks that the DHCP option holding a single string is
+// correctly returned in the textual format.
+TEST_F(OptionStringTest, toText) {
+ // V4 option
+ std::string option_value("lorem ipsum");
+ OptionString optv4(Option::V4, 122, option_value);
+ EXPECT_EQ("type=122, len=011: \"lorem ipsum\" (string)", optv4.toText());
+
+ // V6 option
+ option_value = "is a filler text";
+ OptionString optv6(Option::V6, 512, option_value);
+ EXPECT_EQ("type=00512, len=00016: \"is a filler text\" (string)", optv6.toText());
+}
+
+// This test checks proper handling of trailing and embedded NULLs in
+// data use to create or option value.
+TEST_F(OptionStringTest, setValueNullsHandling) {
+ OptionString optv4(Option::V4, 123, "123");
+
+ // Only nulls should throw.
+ ASSERT_THROW(optv4.setValue(std::string{"\0\0", 2}), isc::OutOfRange);
+
+ // One trailing null should trim off.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"one\0", 4}));
+ EXPECT_EQ(3, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("one"));
+
+ // More than one trailing null should trim off.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"three\0\0\0", 8}));
+ EXPECT_EQ(5, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("three"));
+
+ // Embedded null should be left in place.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"em\0bed", 6}));
+ EXPECT_EQ(6, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6}));
+
+ // Leading null should be left in place.
+ ASSERT_NO_THROW(optv4.setValue(std::string{"\0leading", 8}));
+ EXPECT_EQ(8, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8}));
+}
+
+// This test checks proper handling of trailing and embedded NULLs in
+// data use to create or option value.
+TEST_F(OptionStringTest, unpackNullsHandling) {
+ OptionString optv4(Option::V4, 123, "123");
+
+ // Only nulls should throw.
+ OptionBuffer buffer = { 0, 0 };
+ ASSERT_THROW(optv4.unpack(buffer.begin(), buffer.end()), isc::dhcp::SkipThisOptionError);
+
+ // One trailing null should trim off.
+ buffer = {'o', 'n', 'e', 0 };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(3, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("one"));
+
+ // More than one trailing null should trim off.
+ buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(5, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), std::string("three"));
+
+ // Embedded null should be left in place.
+ buffer = { 'e', 'm', 0, 'b', 'e', 'd' };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(6, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"em\0bed", 6}));
+
+ // Leading null should be left in place.
+ buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' };
+ ASSERT_NO_THROW(optv4.unpack(buffer.begin(), buffer.end()));
+ EXPECT_EQ(8, optv4.getValue().length());
+ EXPECT_EQ(optv4.getValue(), (std::string{"\0leading", 8}));
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
new file mode 100644
index 0000000..b2c36d3
--- /dev/null
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -0,0 +1,651 @@
+// Copyright (C) 2011-2023 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 <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+using boost::scoped_ptr;
+
+namespace {
+
+/// @brief A class which derives from option and exposes protected members.
+class NakedOption : public Option {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets the universe and option type to arbitrary test values.
+ NakedOption() : Option(Option::V6, 258) {
+ }
+
+ using Option::unpackOptions;
+ using Option::cloneInternal;
+};
+
+class OptionTest : public ::testing::Test {
+public:
+ OptionTest(): buf_(255), outBuf_(255) {
+ for (unsigned i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+ OptionBuffer buf_;
+ OutputBuffer outBuf_;
+};
+
+// Basic tests for V4 functionality
+TEST_F(OptionTest, v4_basic) {
+
+ scoped_ptr<Option> opt;
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17)));
+
+ EXPECT_EQ(Option::V4, opt->getUniverse());
+ EXPECT_EQ(17, opt->getType());
+ EXPECT_EQ(0, opt->getData().size());
+ EXPECT_EQ(2, opt->len()); // just v4 header
+
+ EXPECT_NO_THROW(opt.reset());
+
+ // V4 options have type 0...255
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), OutOfRange);
+
+ // 0 / PAD and 255 / END are no longer forbidden
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 0)));
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 255)));
+}
+
+const uint8_t dummyPayload[] =
+{ 1, 2, 3, 4};
+
+TEST_F(OptionTest, v4_data1) {
+
+ vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
+
+ scoped_ptr<Option> opt;
+
+ // Create DHCPv4 option of type 123 that contains 4 bytes of data.
+ ASSERT_NO_THROW(opt.reset(new Option(Option::V4, 123, data)));
+
+ // Check that content is reported properly
+ EXPECT_EQ(123, opt->getType());
+ vector<uint8_t> optData = opt->getData();
+ ASSERT_EQ(optData.size(), data.size());
+ EXPECT_TRUE(optData == data);
+ EXPECT_EQ(2, opt->getHeaderLen());
+ EXPECT_EQ(6, opt->len());
+
+ // Now store that option into a buffer
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(opt->pack(buf));
+
+ // Check content of that buffer:
+ // 2 byte header + 4 bytes data
+ ASSERT_EQ(6, buf.getLength());
+
+ // That's how this option is supposed to look like
+ uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
+
+ /// TODO: use vector<uint8_t> getData() when it will be implemented
+ EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
+
+ // Check that we can destroy that option
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// This is almost the same test as v4_data1, but it uses a different
+// constructor
+TEST_F(OptionTest, v4_data2) {
+
+ vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
+
+ vector<uint8_t> expData = data;
+
+ // Add fake data in front and end. Main purpose of this test is to check
+ // that only subset of the whole vector can be used for creating option.
+ data.insert(data.begin(), 56);
+ data.push_back(67);
+
+ // Data contains extra garbage at beginning and at the end. It should be
+ // ignored, as we pass iterators to proper data. Only subset (limited by
+ // iterators) of the vector should be used.
+ // expData contains expected content (just valid data, without garbage).
+ scoped_ptr<Option> opt;
+
+ // Create DHCPv4 option of type 123 that contains
+ // 4 bytes (sizeof(dummyPayload).
+ ASSERT_NO_THROW(
+ opt.reset(new Option(Option::V4, 123, data.begin() + 1,
+ data.end() - 1));
+ );
+
+ // Check that content is reported properly
+ EXPECT_EQ(123, opt->getType());
+ vector<uint8_t> optData = opt->getData();
+ ASSERT_EQ(optData.size(), expData.size());
+ EXPECT_TRUE(optData == expData);
+ EXPECT_EQ(2, opt->getHeaderLen());
+ EXPECT_EQ(6, opt->len());
+
+ // Now store that option into a buffer
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(opt->pack(buf));
+
+ // Check content of that buffer
+
+ // 2 byte header + 4 bytes data
+ ASSERT_EQ(6, buf.getLength());
+
+ // That's how this option is supposed to look like
+ uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
+
+ /// TODO: use vector<uint8_t> getData() when it will be implemented
+ EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
+
+ // Check that we can destroy that option
+ EXPECT_NO_THROW(opt.reset());
+}
+
+TEST_F(OptionTest, v4_toText) {
+
+ vector<uint8_t> buf(3);
+ buf[0] = 0;
+ buf[1] = 0xf;
+ buf[2] = 0xff;
+
+ Option opt(Option::V4, 253, buf);
+
+ EXPECT_EQ("type=253, len=003: 00:0f:ff", opt.toText());
+}
+
+// Test converting option to the hexadecimal representation.
+TEST_F(OptionTest, v4_toHexString) {
+ std::vector<uint8_t> payload;
+ for (unsigned int i = 0; i < 16; ++i) {
+ payload.push_back(static_cast<uint8_t>(i));
+ }
+ Option opt(Option::V4, 122, payload);
+ EXPECT_EQ("0x000102030405060708090A0B0C0D0E0F", opt.toHexString());
+ EXPECT_EQ("0x7A10000102030405060708090A0B0C0D0E0F",
+ opt.toHexString(true));
+
+ // Test empty option.
+ Option opt_empty(Option::V4, 65, std::vector<uint8_t>());
+ EXPECT_TRUE(opt_empty.toHexString().empty());
+ EXPECT_EQ("0x4100", opt_empty.toHexString(true));
+
+ // Test too long option. We can't simply create such option by
+ // providing a long payload, because class constructor would not
+ // accept it. Instead we'll add two long sub options after we
+ // create an option instance.
+ Option opt_too_long(Option::V4, 33);
+ // Both suboptions have payloads of 150 bytes.
+ std::vector<uint8_t> long_payload(150, 1);
+ OptionPtr sub1(new Option(Option::V4, 100, long_payload));
+ OptionPtr sub2(new Option(Option::V4, 101, long_payload));
+ opt_too_long.addOption(sub1);
+ opt_too_long.addOption(sub2);
+
+ // The toHexString() should not throw exception.
+ EXPECT_NO_THROW(opt_too_long.toHexString());
+}
+
+// Tests simple constructor
+TEST_F(OptionTest, v6_basic) {
+
+ scoped_ptr<Option> opt(new Option(Option::V6, 1));
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(1, opt->getType());
+ EXPECT_EQ(0, opt->getData().size());
+ EXPECT_EQ(4, opt->len()); // Just v6 header
+
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Tests constructor used in packet reception. Option contains actual data
+TEST_F(OptionTest, v6_data1) {
+ for (unsigned i = 0; i < 32; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ // Create option with seven bytes of data.
+ scoped_ptr<Option> opt(new Option(Option::V6, 333, // Type
+ buf_.begin() + 3, // Begin offset
+ buf_.begin() + 10)); // End offset
+ EXPECT_EQ(333, opt->getType());
+
+ ASSERT_EQ(11, opt->len());
+ ASSERT_EQ(7, opt->getData().size());
+ EXPECT_EQ(0, memcmp(&buf_[3], &opt->getData()[0], 7) );
+
+ opt->pack(outBuf_);
+ EXPECT_EQ(11, outBuf_.getLength());
+
+ const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
+ EXPECT_EQ(out[0], 333 / 256); // Type
+ EXPECT_EQ(out[1], 333 % 256);
+
+ EXPECT_EQ(out[2], 0); // Length
+ EXPECT_EQ(out[3], 7);
+
+ // Payload
+ EXPECT_EQ(0, memcmp(&buf_[3], out + 4, 7));
+
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Another test that tests the same thing, just with different input parameters.
+TEST_F(OptionTest, v6_data2) {
+
+ buf_[0] = 0xa1;
+ buf_[1] = 0xa2;
+ buf_[2] = 0xa3;
+ buf_[3] = 0xa4;
+
+ // Create an option (unpack content)
+ scoped_ptr<Option> opt(new Option(Option::V6, D6O_CLIENTID,
+ buf_.begin(), buf_.begin() + 4));
+
+ // Pack this option
+ opt->pack(outBuf_);
+
+ // 4 bytes header + 4 bytes content
+ EXPECT_EQ(8, opt->len());
+ EXPECT_EQ(D6O_CLIENTID, opt->getType());
+
+ EXPECT_EQ(8, outBuf_.getLength());
+
+ // Check if pack worked properly:
+ // If option type is correct
+ const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
+
+ EXPECT_EQ(D6O_CLIENTID, out[0] * 256 + out[1]);
+
+ // If option length is correct
+ EXPECT_EQ(4, out[2] * 256 + out[3]);
+
+ // If option content is correct
+ EXPECT_EQ(0, memcmp(&buf_[0], out + 4, 4));
+
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Check that an option can contain 2 suboptions:
+// opt1
+// +----opt2
+// |
+// +----opt3
+//
+TEST_F(OptionTest, v6_suboptions1) {
+ for (unsigned i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+ buf_.begin(), // 3 bytes of data
+ buf_.begin() + 3));
+ OptionPtr opt2(new Option(Option::V6, 13));
+ OptionPtr opt3(new Option(Option::V6, 7,
+ buf_.begin() + 3,
+ buf_.begin() + 8)); // 5 bytes of data
+ opt1->addOption(opt2);
+ opt1->addOption(opt3);
+ // opt2 len = 4 (just header)
+ // opt3 len = 9 4(header)+5(data)
+ // opt1 len = 7 + suboptions() = 7 + 4 + 9 = 20
+
+ EXPECT_EQ(4, opt2->len());
+ EXPECT_EQ(9, opt3->len());
+ EXPECT_EQ(20, opt1->len());
+
+ uint8_t expected[] = {
+ 0xff, 0xff, 0, 16, 100, 101, 102,
+ 0, 7, 0, 5, 103, 104, 105, 106, 107,
+ 0, 13, 0, 0 // no data at all
+ };
+
+ opt1->pack(outBuf_);
+ EXPECT_EQ(20, outBuf_.getLength());
+
+ // Payload
+ EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
+
+ EXPECT_NO_THROW(opt1.reset());
+}
+
+// Check that an option can contain nested suboptions:
+// opt1
+// +----opt2
+// |
+// +----opt3
+//
+TEST_F(OptionTest, v6_suboptions2) {
+ for (unsigned i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+ buf_.begin(), buf_.begin() + 3));
+ OptionPtr opt2(new Option(Option::V6, 13));
+ OptionPtr opt3(new Option(Option::V6, 7,
+ buf_.begin() + 3,
+ buf_.begin() + 8));
+ opt1->addOption(opt2);
+ opt2->addOption(opt3);
+ // opt3 len = 9 4(header)+5(data)
+ // opt2 len = 4 (just header) + len(opt3)
+ // opt1 len = 7 + len(opt2)
+
+ uint8_t expected[] = {
+ 0xff, 0xff, 0, 16, 100, 101, 102,
+ 0, 13, 0, 9,
+ 0, 7, 0, 5, 103, 104, 105, 106, 107,
+ };
+
+ opt1->pack(outBuf_);
+ EXPECT_EQ(20, outBuf_.getLength());
+
+ // Payload
+ EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
+
+ EXPECT_NO_THROW(opt1.reset());
+}
+
+TEST_F(OptionTest, v6_addgetdel) {
+ for (unsigned i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> parent(new Option(Option::V6, 65535)); // Type
+ OptionPtr opt1(new Option(Option::V6, 1));
+ OptionPtr opt2(new Option(Option::V6, 2));
+ OptionPtr opt3(new Option(Option::V6, 2));
+
+ parent->addOption(opt1);
+ parent->addOption(opt2);
+
+ // getOption() test
+ EXPECT_EQ(opt1, parent->getOption(1));
+ EXPECT_EQ(opt2, parent->getOption(2));
+
+ // Expect NULL
+ EXPECT_EQ(OptionPtr(), parent->getOption(4));
+
+ // Now there are 2 options of type 2
+ parent->addOption(opt3);
+
+ // Let's delete one of them
+ EXPECT_EQ(true, parent->delOption(2));
+
+ // There still should be the other option 2
+ EXPECT_NE(OptionPtr(), parent->getOption(2));
+
+ // Let's delete the other option 2
+ EXPECT_EQ(true, parent->delOption(2));
+
+ // No more options with type=2
+ EXPECT_EQ(OptionPtr(), parent->getOption(2));
+
+ // Let's try to delete - should fail
+ EXPECT_TRUE(false == parent->delOption(2));
+}
+
+TEST_F(OptionTest, v6_toText) {
+ buf_[0] = 0;
+ buf_[1] = 0xf;
+ buf_[2] = 0xff;
+
+ OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 ));
+ EXPECT_EQ("type=00258, len=00003: 00:0f:ff", opt->toText());
+}
+
+// Test converting option to the hexadecimal representation.
+TEST_F(OptionTest, v6_toHexString) {
+ std::vector<uint8_t> payload;
+ for (unsigned int i = 0; i < 16; ++i) {
+ payload.push_back(static_cast<uint8_t>(i));
+ }
+ Option opt(Option::V6, 12202, payload);
+ EXPECT_EQ("0x000102030405060708090A0B0C0D0E0F", opt.toHexString());
+ EXPECT_EQ("0x2FAA0010000102030405060708090A0B0C0D0E0F",
+ opt.toHexString(true));
+
+ // Test empty option.
+ Option opt_empty(Option::V6, 65000, std::vector<uint8_t>());
+ EXPECT_TRUE(opt_empty.toHexString().empty());
+ EXPECT_EQ("0xFDE80000", opt_empty.toHexString(true));
+}
+
+TEST_F(OptionTest, getUintX) {
+
+ buf_[0] = 0x5;
+ buf_[1] = 0x4;
+ buf_[2] = 0x3;
+ buf_[3] = 0x2;
+ buf_[4] = 0x1;
+
+ // Five options with varying lengths
+ OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
+ OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+ OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
+ OptionPtr opt4(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 4));
+ OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 5));
+
+ EXPECT_EQ(5, opt1->getUint8());
+ EXPECT_THROW(opt1->getUint16(), OutOfRange);
+ EXPECT_THROW(opt1->getUint32(), OutOfRange);
+
+ EXPECT_EQ(5, opt2->getUint8());
+ EXPECT_EQ(0x0504, opt2->getUint16());
+ EXPECT_THROW(opt2->getUint32(), OutOfRange);
+
+ EXPECT_EQ(5, opt3->getUint8());
+ EXPECT_EQ(0x0504, opt3->getUint16());
+ EXPECT_THROW(opt3->getUint32(), OutOfRange);
+
+ EXPECT_EQ(5, opt4->getUint8());
+ EXPECT_EQ(0x0504, opt4->getUint16());
+ EXPECT_EQ(0x05040302, opt4->getUint32());
+
+ // The same as for 4-byte long, just get first 1,2 or 4 bytes
+ EXPECT_EQ(5, opt5->getUint8());
+ EXPECT_EQ(0x0504, opt5->getUint16());
+ EXPECT_EQ(0x05040302, opt5->getUint32());
+
+}
+
+TEST_F(OptionTest, setUintX) {
+ OptionPtr opt1(new Option(Option::V4, 125));
+ OptionPtr opt2(new Option(Option::V4, 125));
+ OptionPtr opt4(new Option(Option::V4, 125));
+
+ // Verify setUint8
+ opt1->setUint8(255);
+ EXPECT_EQ(255, opt1->getUint8());
+ opt1->pack(outBuf_);
+ EXPECT_EQ(3, opt1->len());
+ EXPECT_EQ(3, outBuf_.getLength());
+ uint8_t exp1[] = {125, 1, 255};
+ EXPECT_TRUE(0 == memcmp(exp1, outBuf_.getData(), 3));
+
+ // Verify getUint16
+ outBuf_.clear();
+ opt2->setUint16(12345);
+ opt2->pack(outBuf_);
+ EXPECT_EQ(12345, opt2->getUint16());
+ EXPECT_EQ(4, opt2->len());
+ EXPECT_EQ(4, outBuf_.getLength());
+ uint8_t exp2[] = {125, 2, 12345/256, 12345%256};
+ EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4));
+
+ // Verify getUint32
+ outBuf_.clear();
+ opt4->setUint32(0x12345678);
+ opt4->pack(outBuf_);
+ EXPECT_EQ(0x12345678, opt4->getUint32());
+ EXPECT_EQ(6, opt4->len());
+ EXPECT_EQ(6, outBuf_.getLength());
+ uint8_t exp4[] = {125, 4, 0x12, 0x34, 0x56, 0x78};
+ EXPECT_TRUE(0 == memcmp(exp4, outBuf_.getData(), 6));
+}
+
+TEST_F(OptionTest, setData) {
+ // Verify data override with new buffer larger than initial option buffer
+ // size.
+ OptionPtr opt1(new Option(Option::V4, 125,
+ buf_.begin(), buf_.begin() + 10));
+ buf_.resize(20, 1);
+ opt1->setData(buf_.begin(), buf_.end());
+ opt1->pack(outBuf_);
+ ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size());
+ const uint8_t* test_data = static_cast<const uint8_t*>(outBuf_.getData());
+ EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
+ buf_.size()));
+
+ // Verify data override with new buffer shorter than initial option buffer
+ // size.
+ OptionPtr opt2(new Option(Option::V4, 125,
+ buf_.begin(), buf_.begin() + 10));
+ outBuf_.clear();
+ buf_.resize(5, 1);
+ opt2->setData(buf_.begin(), buf_.end());
+ opt2->pack(outBuf_);
+ ASSERT_EQ(outBuf_.getLength() - opt1->getHeaderLen(), buf_.size());
+ test_data = static_cast<const uint8_t*>(outBuf_.getData());
+ EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
+ buf_.size()));
+}
+
+// This test verifies that options can be compared using equals(OptionPtr)
+// method.
+TEST_F(OptionTest, equalsWithPointers) {
+
+ // Five options with varying lengths
+ OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
+ OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+ OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
+
+ // The same content as opt2, but different type
+ OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2));
+
+ // Another instance with the same type and content as opt2
+ OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+
+ EXPECT_TRUE(opt1->equals(opt1));
+
+ EXPECT_FALSE(opt1->equals(opt2));
+ EXPECT_FALSE(opt1->equals(opt3));
+ EXPECT_FALSE(opt1->equals(opt4));
+
+ EXPECT_TRUE(opt2->equals(opt5));
+}
+
+// This test verifies that options can be compared using equals(Option) method.
+TEST_F(OptionTest, equals) {
+
+ // Five options with varying lengths
+ Option opt1(Option::V6, 258, buf_.begin(), buf_.begin() + 1);
+ Option opt2(Option::V6, 258, buf_.begin(), buf_.begin() + 2);
+ Option opt3(Option::V6, 258, buf_.begin(), buf_.begin() + 3);
+
+ // The same content as opt2, but different type
+ Option opt4(Option::V6, 1, buf_.begin(), buf_.begin() + 2);
+
+ // Another instance with the same type and content as opt2
+ Option opt5(Option::V6, 258, buf_.begin(), buf_.begin() + 2);
+
+ EXPECT_TRUE(opt1.equals(opt1));
+
+ EXPECT_FALSE(opt1.equals(opt2));
+ EXPECT_FALSE(opt1.equals(opt3));
+ EXPECT_FALSE(opt1.equals(opt4));
+
+ EXPECT_TRUE(opt2.equals(opt5));
+}
+
+// This test verifies that the name of the option space being encapsulated by
+// the particular option can be set.
+TEST_F(OptionTest, setEncapsulatedSpace) {
+ Option optv6(Option::V6, 258);
+ EXPECT_TRUE(optv6.getEncapsulatedSpace().empty());
+
+ optv6.setEncapsulatedSpace(DHCP6_OPTION_SPACE);
+ EXPECT_EQ(DHCP6_OPTION_SPACE, optv6.getEncapsulatedSpace());
+
+ Option optv4(Option::V4, 125);
+ EXPECT_TRUE(optv4.getEncapsulatedSpace().empty());
+
+ optv4.setEncapsulatedSpace(DHCP4_OPTION_SPACE);
+ EXPECT_EQ(DHCP4_OPTION_SPACE, optv4.getEncapsulatedSpace());
+}
+
+// This test verifies that cloneInternal returns NULL pointer if
+// non-compatible type is used as a template argument.
+// By non-compatible it is meant that the option instance doesn't
+// dynamic_cast to the type specified as template argument.
+// In our case, the NakedOption doesn't cast to OptionUint8 as the
+// latter is not derived from NakedOption.
+TEST_F(OptionTest, cloneInternal) {
+ NakedOption option;
+ OptionPtr clone;
+ // This shouldn't throw nor cause segmentation fault.
+ ASSERT_NO_THROW(clone = option.cloneInternal<OptionUint8>());
+ EXPECT_FALSE(clone);
+}
+
+// This test verifies that empty option factory function creates
+// a valid option instance.
+TEST_F(OptionTest, create) {
+ auto option = Option::create(Option::V4, 123);
+ ASSERT_TRUE(option);
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(123, option->getType());
+}
+
+// This test verifies that option factory function creates a
+// valid option instance.
+TEST_F(OptionTest, createPayload) {
+ auto option = Option::create(Option::V4, 123, buf_);
+ ASSERT_TRUE(option);
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(123, option->getType());
+ EXPECT_EQ(buf_, option->getData());
+}
+
+// Verify that options cannot be added to themselves as suboptions.
+TEST_F(OptionTest, optionsCannotContainThemselves) {
+ OptionBuffer buf1 {0xaa, 0xbb};
+ OptionBuffer buf2 {0xcc, 0xdd};
+ OptionPtr option = Option::create(Option::V4, 123, buf1);
+ OptionPtr option2 = Option::create(Option::V4, 124, buf2);
+ ASSERT_TRUE(option);
+ ASSERT_NO_THROW(option->addOption(option2));
+ EXPECT_THROW_MSG(option->addOption(option), InvalidOperation,
+ "option cannot be added to itself: type=123, len=006: aa:bb,\noptions:\n"
+ " type=124, len=002: cc:dd");
+}
+
+}
diff --git a/src/lib/dhcp/tests/option_vendor_class_unittest.cc b/src/lib/dhcp/tests/option_vendor_class_unittest.cc
new file mode 100644
index 0000000..40a36b8
--- /dev/null
+++ b/src/lib/dhcp/tests/option_vendor_class_unittest.cc
@@ -0,0 +1,611 @@
+// Copyright (C) 2014-2023 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 <dhcp/option_vendor_class.h>
+#include <util/buffer.h>
+#include <testutils/gtest_utils.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+struct OptionVendorClassLenientParsing : ::testing::Test {
+ void SetUp() final override {
+ // Retain the current setting for future restoration.
+ previous_ = Option::lenient_parsing_;
+
+ // Enable lenient parsing.
+ Option::lenient_parsing_ = true;
+ }
+
+ void TearDown() final override {
+ // Restore.
+ Option::lenient_parsing_ = previous_;
+ }
+
+ bool previous_;
+};
+
+// This test checks that the DHCPv4 option constructor sets the default
+// properties to the expected values. This constructor should add an
+// empty opaque data tuple (it is essentially the same as adding a 1-byte
+// long field which carries a value of 0).
+TEST(OptionVendorClass, constructor4) {
+ OptionVendorClass vendor_class(Option::V4, 1234);
+ EXPECT_EQ(1234, vendor_class.getVendorId());
+ // Option length is 1 byte for header + 1 byte for option size +
+ // 4 bytes of enterprise id + 1 byte for opaque data.
+ EXPECT_EQ(7, vendor_class.len());
+ // There should be one empty tuple.
+ ASSERT_EQ(1, vendor_class.getTuplesNum());
+ EXPECT_EQ(0, vendor_class.getTuple(0).getLength());
+}
+
+// This test checks that the DHCPv6 option constructor sets the default
+// properties to the expected values.
+TEST(OptionVendorClass, constructor6) {
+ OptionVendorClass vendor_class(Option::V6, 2345);
+ EXPECT_EQ(2345, vendor_class.getVendorId());
+ // Option length is 2 bytes for option code + 2 bytes for option size +
+ // 4 bytes of enterprise id.
+ EXPECT_EQ(8, vendor_class.len());
+ // There should be no tuples.
+ EXPECT_EQ(0, vendor_class.getTuplesNum());
+}
+
+// This test verifies that it is possible to append the opaque data tuple
+// to the option and then retrieve it.
+TEST(OptionVendorClass, addTuple) {
+ OptionVendorClass vendor_class(Option::V6, 2345);
+ // Initially there should be no tuples (for DHCPv6).
+ ASSERT_EQ(0, vendor_class.getTuplesNum());
+ // Create a new tuple and add it to the option.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "xyz";
+ vendor_class.addTuple(tuple);
+ // The option should now hold one tuple.
+ ASSERT_EQ(1, vendor_class.getTuplesNum());
+ EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+ // Add another tuple.
+ tuple = "abc";
+ vendor_class.addTuple(tuple);
+ // The option should now hold exactly two tuples in the order in which
+ // they were added.
+ ASSERT_EQ(2, vendor_class.getTuplesNum());
+ EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+ EXPECT_EQ("abc", vendor_class.getTuple(1).getText());
+
+ // Check that hasTuple correctly identifies existing tuples.
+ EXPECT_TRUE(vendor_class.hasTuple("xyz"));
+ EXPECT_TRUE(vendor_class.hasTuple("abc"));
+ EXPECT_FALSE(vendor_class.hasTuple("other"));
+
+ // Attempt to add the tuple with 1 byte long length field should fail
+ // for DHCPv6 option.
+ OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
+}
+
+// This test checks that it is possible to replace existing tuple.
+TEST(OptionVendorClass, setTuple) {
+ OptionVendorClass vendor_class(Option::V4, 1234);
+ // The DHCPv4 option should carry one empty tuple.
+ ASSERT_EQ(1, vendor_class.getTuplesNum());
+ ASSERT_TRUE(vendor_class.getTuple(0).getText().empty());
+ // Replace the empty tuple with non-empty one.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "xyz";
+ ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+ // There should be one tuple with updated data.
+ ASSERT_EQ(1, vendor_class.getTuplesNum());
+ EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+
+ // Add another one.
+ tuple = "abc";
+ vendor_class.addTuple(tuple);
+ ASSERT_EQ(2, vendor_class.getTuplesNum());
+ ASSERT_EQ("abc", vendor_class.getTuple(1).getText());
+
+ // Try to replace them with new tuples.
+ tuple = "new_xyz";
+ ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+ ASSERT_EQ(2, vendor_class.getTuplesNum());
+ EXPECT_EQ("new_xyz", vendor_class.getTuple(0).getText());
+
+ tuple = "new_abc";
+ ASSERT_NO_THROW(vendor_class.setTuple(1, tuple));
+ ASSERT_EQ(2, vendor_class.getTuplesNum());
+ EXPECT_EQ("new_abc", vendor_class.getTuple(1).getText());
+
+ // For out of range position, exception should be thrown.
+ tuple = "foo";
+ EXPECT_THROW(vendor_class.setTuple(2, tuple), isc::OutOfRange);
+
+ // Attempt to add the tuple with 2 byte long length field should fail
+ // for DHCPv4 option.
+ OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES);
+ EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
+}
+
+// Check that the returned length of the DHCPv4 option is correct.
+TEST(OptionVendorClass, len4) {
+ OptionVendorClass vendor_class(Option::V4, 1234);
+ ASSERT_EQ(7, vendor_class.len());
+ // Replace the default empty tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "xyz";
+ ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+ // The total length should get increased by the size of 'xyz'.
+ EXPECT_EQ(10, vendor_class.len());
+ // Add another tuple.
+ tuple = "abc";
+ vendor_class.addTuple(tuple);
+ // The total size now grows by the additional enterprise id and the
+ // 1 byte of the tuple length field and 3 bytes of 'abc'.
+ EXPECT_EQ(18, vendor_class.len());
+}
+
+// Check that the returned length of the DHCPv6 option is correct.
+TEST(OptionVendorClass, len6) {
+ OptionVendorClass vendor_class(Option::V6, 1234);
+ ASSERT_EQ(8, vendor_class.len());
+ // Add first tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "xyz";
+ ASSERT_NO_THROW(vendor_class.addTuple(tuple));
+ // The total length grows by 2 bytes of the length field and 3 bytes
+ // consumed by 'xyz'.
+ EXPECT_EQ(13, vendor_class.len());
+ // Add another tuple and check that the total size gets increased.
+ tuple = "abc";
+ vendor_class.addTuple(tuple);
+ EXPECT_EQ(18, vendor_class.len());
+}
+
+// Check that the option is rendered to the buffer in wire format.
+TEST(OptionVendorClass, pack4) {
+ OptionVendorClass vendor_class(Option::V4, 1234);
+ ASSERT_EQ(1, vendor_class.getTuplesNum());
+ // By default, there is an empty tuple in the option. Let's replace
+ // it with the tuple with some data.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "Hello world";
+ vendor_class.setTuple(0, tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ vendor_class.addTuple(tuple);
+
+ // Render the data to the buffer.
+ OutputBuffer buf(10);
+ ASSERT_NO_THROW(vendor_class.pack(buf));
+ ASSERT_EQ(26, buf.getLength());
+
+ // Prepare reference data.
+ const uint8_t ref[] = {
+ 0x7C, 0x18, // option 124, length 24
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 3, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ // Compare the buffer with reference data.
+ EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+ static_cast<const void*>(buf.getData()), 26));
+}
+
+// Check that the DHCPv6 option is rendered to the buffer in wire format.
+TEST(OptionVendorClass, pack6) {
+ OptionVendorClass vendor_class(Option::V6, 1234);
+ ASSERT_EQ(0, vendor_class.getTuplesNum());
+ // Add tuple.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "Hello world";
+ vendor_class.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ vendor_class.addTuple(tuple);
+
+ // Render the data to the buffer.
+ OutputBuffer buf(10);
+ ASSERT_NO_THROW(vendor_class.pack(buf));
+ ASSERT_EQ(26, buf.getLength());
+
+ // Prepare reference data.
+ const uint8_t ref[] = {
+ 0x00, 0x10, 0x00, 0x16, // option 16, length 22
+ 0x00, 0x00, 0x04, 0xD2, // enterprise id 1234
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ // Compare the buffer with reference data.
+ EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+ static_cast<const void*>(buf.getData()),
+ buf.getLength()));
+}
+
+// This function checks that the DHCPv4 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionVendorClass, unpack4) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 3, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(2, vendor_class->getTuplesNum());
+ EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+ EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+}
+
+// This function checks that the DHCPv4 option with two different enterprise
+// ids can't be parsed.
+TEST(OptionVendorClass, twoEnterpriseIds) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0, 0, 0x16, 0x2E, // enterprise id 5678
+ 3, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+ std::string msg = "V-I Vendor Class option with two different ";
+ msg += "enterprise ids: 1234 and 5678";
+
+ ASSERT_THROW_MSG(OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+ buf.begin(),
+ buf.end())),
+ BadValue, msg);
+}
+
+// This function checks that the DHCPv6 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionVendorClass, unpack6) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(2, vendor_class->getTuplesNum());
+ EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+ EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+}
+
+
+// This test checks that the DHCPv6 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionVendorClass, unpack4EmptyTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, // tuple length is 0
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(1, vendor_class->getTuplesNum());
+ EXPECT_TRUE(vendor_class->getTuple(0).getText().empty());
+}
+
+// This test checks that the DHCPv6 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionVendorClass, unpack6EmptyTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, 0x00 // tuple length is 0
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(1, vendor_class->getTuplesNum());
+ EXPECT_TRUE(vendor_class->getTuple(0).getText().empty());
+}
+
+// This test checks that the DHCPv4 option without opaque data is
+// correctly parsed.
+TEST(OptionVendorClass, unpack4NoTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2 // enterprise id 1234
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ EXPECT_EQ(0, vendor_class->getTuplesNum());
+}
+
+// This test checks that the DHCPv6 option without opaque data is
+// correctly parsed.
+TEST(OptionVendorClass, unpack6NoTuple) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2 // enterprise id 1234
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+ buf.begin(),
+ buf.end()));
+ );
+ EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ EXPECT_EQ(0, vendor_class->getTuplesNum());
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv4
+// V-I Vendor Class option.
+TEST(OptionVendorClass, unpack4Truncated) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ EXPECT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()),
+ isc::OutOfRange);
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv6
+// Vendor Class option.
+TEST(OptionVendorClass, unpack6Truncated) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!)
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ EXPECT_THROW(OptionVendorClass (Option::V6, buf.begin(), buf.end()),
+ isc::dhcp::OpaqueDataTupleError);
+}
+
+// Verifies correctness of the text representation of the DHCPv4 option.
+TEST(OptionVendorClass, toText4) {
+ OptionVendorClass vendor_class(Option::V4, 1234);
+ ASSERT_EQ(1, vendor_class.getTuplesNum());
+ // By default, there is an empty tuple in the option. Let's replace
+ // it with the tuple with some data.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ tuple = "Hello world";
+ vendor_class.setTuple(0, tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ vendor_class.addTuple(tuple);
+ // Check that the text representation of the option is as expected.
+ EXPECT_EQ("type=124, len=24, enterprise id=0x4d2,"
+ " data-len0=11, vendor-class-data0='Hello world',"
+ " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'",
+ vendor_class.toText());
+
+ // Check that indentation works.
+ EXPECT_EQ(" type=124, len=24, enterprise id=0x4d2,"
+ " data-len0=11, vendor-class-data0='Hello world',"
+ " enterprise id=0x4d2, data-len1=3, vendor-class-data1='foo'",
+ vendor_class.toText(3));
+}
+
+// Verifies correctness of the text representation of the DHCPv6 option.
+TEST(OptionVendorClass, toText6) {
+ OptionVendorClass vendor_class(Option::V6, 1234);
+ ASSERT_EQ(0, vendor_class.getTuplesNum());
+ // By default, there is an empty tuple in the option. Let's replace
+ // it with the tuple with some data.
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ tuple = "Hello world";
+ vendor_class.addTuple(tuple);
+ // And add another tuple so as resulting option is a bit more complex.
+ tuple = "foo";
+ vendor_class.addTuple(tuple);
+ // Check that the text representation of the option is as expected.
+ EXPECT_EQ("type=16, len=22, enterprise id=0x4d2,"
+ " data-len0=11, vendor-class-data0='Hello world',"
+ " data-len1=3, vendor-class-data1='foo'",
+ vendor_class.toText());
+
+ // Check that indentation works.
+ EXPECT_EQ(" type=16, len=22, enterprise id=0x4d2,"
+ " data-len0=11, vendor-class-data0='Hello world',"
+ " data-len1=3, vendor-class-data1='foo'",
+ vendor_class.toText(2));
+}
+
+// Test that a well formed DHCPv6 option with two opaque data tuples is parsed
+// correctly when lenient mode is enabled.
+TEST_F(OptionVendorClassLenientParsing, unpack6WellFormed) {
+ // Enable lenient parsing.
+ bool const previous(Option::lenient_parsing_);
+ Option::lenient_parsing_ = true;
+
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(
+ new OptionVendorClass(Option::V6, buf.begin(), buf.end())););
+
+ EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(2, vendor_class->getTuplesNum());
+ EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+ EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+
+ // Restore.
+ Option::lenient_parsing_ = previous;
+}
+
+// Test that the DHCPv6 option with truncated or over-extending (depends on
+// perspective) buffers is parsed correctly when lenient mode is enabled.
+TEST_F(OptionVendorClassLenientParsing, unpack6FirstLengthIsBad) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, 0x0C, // tuple length is 12 (should be 11)
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x03, // tuple length is 3
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(
+ new OptionVendorClass(Option::V6, buf.begin(), buf.end())););
+
+ EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(2, vendor_class->getTuplesNum());
+ // The first value will have one extra byte.
+ EXPECT_EQ(std::string("Hello world") + '\0',
+ vendor_class->getTuple(0).getText());
+ // The length would have internally been interpreted as {0x03, 0x66} == 870,
+ // but the parser would have stopped at the end of the option, so the second
+ // value should be "oo".
+ EXPECT_EQ("oo", vendor_class->getTuple(1).getText());
+}
+
+// Test that the DHCPv6 option with truncated or over-extending (depends on
+// perspective) buffers is parsed correctly when lenient mode is enabled.
+TEST_F(OptionVendorClassLenientParsing, unpack6SecondLengthIsBad) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, 0x0B, // tuple length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x04, // tuple length is 4 (should be 3)
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(
+ new OptionVendorClass(Option::V6, buf.begin(), buf.end())););
+
+ EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(2, vendor_class->getTuplesNum());
+ EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+ // The length would have internally been interpreted as {0x00, 0x04} == 4,
+ // but the parser would have stopped at the end of the option, so the second
+ // value should be "foo" just like normal.
+ EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+}
+
+// Test that the DHCPv6 option with truncated or over-extending (depends on
+// perspective) buffers is parsed correctly when lenient mode is enabled.
+TEST_F(OptionVendorClassLenientParsing, unpack6BothLengthsAreBad) {
+ // Prepare data to decode.
+ const uint8_t buf_data[] = {
+ 0, 0, 0x4, 0xD2, // enterprise id 1234
+ 0x00, 0x0C, // tuple length is 12 (should be 11)
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64, // world
+ 0x00, 0x04, // tuple length is 4 (should be 3)
+ 0x66, 0x6F, 0x6F // foo
+ };
+ OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+ OptionVendorClassPtr vendor_class;
+ ASSERT_NO_THROW(
+ vendor_class = OptionVendorClassPtr(
+ new OptionVendorClass(Option::V6, buf.begin(), buf.end())););
+
+ EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+ EXPECT_EQ(1234, vendor_class->getVendorId());
+ ASSERT_EQ(2, vendor_class->getTuplesNum());
+ // The first value will have one extra byte.
+ EXPECT_EQ(std::string("Hello world") + '\0',
+ vendor_class->getTuple(0).getText());
+ // The length would have internally been interpreted as {0x04, 0x66} == 1126,
+ // but the parser would have stopped at the end of the option, so the second
+ // value should be "oo".
+ EXPECT_EQ("oo", vendor_class->getTuple(1).getText());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/option_vendor_unittest.cc b/src/lib/dhcp/tests/option_vendor_unittest.cc
new file mode 100644
index 0000000..bb76400
--- /dev/null
+++ b/src/lib/dhcp/tests/option_vendor_unittest.cc
@@ -0,0 +1,257 @@
+// Copyright (C) 2013-2017 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+using boost::scoped_ptr;
+
+namespace {
+
+class OptionVendorTest : public ::testing::Test {
+public:
+ OptionVendorTest() {
+ }
+
+ OptionBuffer createV4VendorOptions() {
+
+ // Copied from wireshark, file docsis-*-CG3000DCR-Registration-Filtered.cap
+ // packet #1
+ /* V-I Vendor-specific Information (125)
+ Length: 127
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ Suboption 1: Option Request
+ Suboption 5: Modem capabilities */
+ string from_wireshark = "7d7f0000118b7a01010205750101010201030301010401"
+ "0105010106010107010f0801100901030a01010b01180c01010d0200400e020010"
+ "0f010110040000000211010014010015013f1601011701011801041901041a0104"
+ "1b01201c01021d01081e01201f0110200110210102220101230100240100250101"
+ "260200ff270101";
+
+ OptionBuffer bin;
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(from_wireshark, bin);
+
+ return (bin);
+ }
+
+ OptionBuffer createV6VendorOption() {
+
+ // Copied from wireshark, docsis-CG3000DCR-Registration-v6CMM-Filtered.cap
+ // packet #1 (v6 vendor option with lots of cable modem specific data)
+ string from_wireshark = "001100ff0000118b0001000a0020002100220025002600"
+ "02000345434d0003000b45434d3a45524f555445520004000d3242523232395534"
+ "303034344300050004312e30340006000856312e33332e303300070007322e332e"
+ "3052320008000630303039354200090009434733303030444352000a00074e6574"
+ "6765617200230077057501010102010303010104010105010106010107010f0801"
+ "100901030a01010b01180c01010d0200400e0200100f0101100400000002110100"
+ "14010015013f1601011701011801041901041a01041b01201c01021d01081e0120"
+ "1f0110200110210102220101230100240100250101260200ff2701010024000620"
+ "e52ab81514";
+ /* Vendor-specific Information
+ Option: Vendor-specific Information (17)
+ Length: 255
+ Value: 0000118b0001000a00200021002200250026000200034543...
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ Suboption 1: Option Request = 32 33 34 37 38
+ Suboption 2: Device Type = "ECM"
+ Suboption 3: Embedded Components = "ECM:EROUTER"
+ Suboption 4: Serial Number = "2BR229U40044C"
+ Suboption 5: Hardware Version = "1.04"
+ Suboption 6: Software Version = "V1.33.03"
+ Suboption 7: Boot ROM Version = "2.3.0R2"
+ Suboption 8: Organization Unique Identifier = "00095B"
+ Suboption 9: Model Number = "CG3000DCR"
+ Suboption 10: Vendor Name = "Netgear"
+ Suboption 35: TLV5 = 057501010102010303010104010105010106010107010f08...
+ Suboption 36: Device Identifier = 20e52ab81514 */
+
+ OptionBuffer bin;
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(from_wireshark, bin);
+
+ return (bin);
+ }
+};
+
+// Basic test for v4 vendor option functionality
+TEST_F(OptionVendorTest, v4Basic) {
+
+ uint32_t vendor_id = 1234;
+
+ scoped_ptr<Option> opt;
+ EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V4, vendor_id)));
+
+ EXPECT_EQ(Option::V4, opt->getUniverse());
+ EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, opt->getType());
+
+ // Minimal length is 7: 1(type) + 1(length) + 4(vendor-id) + datalen(1)
+ EXPECT_EQ(7, opt->len());
+
+ // Check destructor
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Basic test for v6 vendor option functionality
+TEST_F(OptionVendorTest, v6Basic) {
+
+ uint32_t vendor_id = 1234;
+
+ scoped_ptr<Option> opt;
+ EXPECT_NO_THROW(opt.reset(new OptionVendor(Option::V6, vendor_id)));
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_VENDOR_OPTS, opt->getType());
+
+ // Minimal length is 8: 2(type) + 2(length) + 4(vendor-id)
+ EXPECT_EQ(8, opt->len());
+
+ // Check destructor
+ EXPECT_NO_THROW(opt.reset());
+}
+
+// Tests whether we can parse v4 vendor options properly
+TEST_F(OptionVendorTest, v4Parse) {
+ OptionBuffer binary = createV4VendorOptions();
+
+ // Let's create vendor option based on incoming buffer
+ OptionVendorPtr vendor;
+ ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2,
+ binary.end())));
+
+ // We know that there are supposed to be 2 options inside
+ EXPECT_TRUE(vendor->getOption(DOCSIS3_V4_ORO));
+ EXPECT_TRUE(vendor->getOption(5));
+}
+
+// Tests whether we can parse and then pack a v4 option.
+TEST_F(OptionVendorTest, packUnpack4) {
+ OptionBuffer binary = createV4VendorOptions();
+
+ OptionVendorPtr vendor;
+
+ // Create vendor option (ignore the first 2 bytes, these are option code
+ // and option length
+ ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V4, binary.begin() + 2,
+ binary.end())));
+
+ OutputBuffer output(0);
+
+ EXPECT_NO_THROW(vendor->pack(output));
+
+ ASSERT_EQ(binary.size(), output.getLength());
+
+ // We're lucky, because the packet capture we have happens to have options
+ // with monotonically increasing values (1 and 5), so our pack() method
+ // will pack them in exactly the same order as in the original.
+ EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength()));
+}
+
+// Tests whether we can parse v6 vendor options properly
+TEST_F(OptionVendorTest, v6Parse) {
+ OptionBuffer binary = createV6VendorOption();
+
+ OptionVendorPtr vendor;
+ // Create vendor option (ignore the first 4 bytes. These are option code
+ // (2 bytes) and option length (2 bytes).
+ ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4,
+ binary.end())));
+
+ OptionPtr opt;
+ opt = vendor->getOption(DOCSIS3_V6_ORO);
+ ASSERT_TRUE(opt);
+ OptionUint16ArrayPtr oro =
+ boost::dynamic_pointer_cast<OptionUint16Array>(opt);
+
+ // Check that all remaining expected options are there
+ EXPECT_TRUE(vendor->getOption(2));
+ EXPECT_TRUE(vendor->getOption(3));
+ EXPECT_TRUE(vendor->getOption(4));
+ EXPECT_TRUE(vendor->getOption(5));
+ EXPECT_TRUE(vendor->getOption(6));
+ EXPECT_TRUE(vendor->getOption(7));
+ EXPECT_TRUE(vendor->getOption(8));
+ EXPECT_TRUE(vendor->getOption(9));
+ EXPECT_TRUE(vendor->getOption(10));
+ EXPECT_TRUE(vendor->getOption(35));
+ EXPECT_TRUE(vendor->getOption(36));
+
+ // Check that there are no other options there
+ for (uint16_t i = 11; i < 35; ++i) {
+ EXPECT_FALSE(vendor->getOption(i));
+ }
+
+ for (uint16_t i = 37; i < 65535; ++i) {
+ EXPECT_FALSE(vendor->getOption(i));
+ }
+}
+
+// Tests whether we can parse and then pack a v6 option.
+TEST_F(OptionVendorTest, packUnpack6) {
+ OptionBuffer binary = createV6VendorOption();
+
+ OptionVendorPtr vendor;
+
+ // Create vendor option (ignore the first 4 bytes. These are option code
+ // (2 bytes) and option length (2 bytes).
+ ASSERT_NO_THROW(vendor.reset(new OptionVendor(Option::V6, binary.begin() + 4,
+ binary.end())));
+
+ OutputBuffer output(0);
+
+ EXPECT_NO_THROW(vendor->pack(output));
+
+ ASSERT_EQ(binary.size(), output.getLength());
+ EXPECT_FALSE(memcmp(&binary[0], output.getData(), output.getLength()));
+}
+
+// Tests that the vendor option is correctly returned in the textual
+// format for DHCPv4 case.
+TEST_F(OptionVendorTest, toText4) {
+ OptionVendor option(Option::V4, 1024);
+ option.addOption(OptionPtr(new OptionUint32(Option::V4, 1, 100)));
+
+ EXPECT_EQ("type=125, len=011: 1024 (uint32) 6 (uint8),\n"
+ "options:\n"
+ " type=001, len=004: 100 (uint32)",
+ option.toText());
+}
+
+// Tests that the vendor option is correctly returned in the textual
+// format for DHCPv6 case.
+TEST_F(OptionVendorTest, toText6) {
+ OptionVendor option(Option::V6, 2048);
+ option.addOption(OptionPtr(new OptionUint16(Option::V6, 1, 100)));
+
+ EXPECT_EQ("type=00017, len=00010: 2048 (uint32),\n"
+ "options:\n"
+ " type=00001, len=00002: 100 (uint16)",
+ option.toText());
+}
+
+}
diff --git a/src/lib/dhcp/tests/packet_queue4_unittest.cc b/src/lib/dhcp/tests/packet_queue4_unittest.cc
new file mode 100644
index 0000000..58164f2
--- /dev/null
+++ b/src/lib/dhcp/tests/packet_queue4_unittest.cc
@@ -0,0 +1,294 @@
+// Copyright (C) 2018-2019,2021 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 <dhcp/packet_queue_ring.h>
+#include <dhcp/tests/packet_queue_testutils.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief DHCPv4 queue with implements drop and eat logic
+///
+/// This class derives from the default DHCPv4 ring queue
+/// and provides implementations for shouldDropPacket() and
+/// eatPackets(). This permits a full exercising of the
+/// PacketQueue interface as well as the basic v4 ring queue
+/// mechanics.
+class TestQueue4 : public PacketQueueRing4 {
+public:
+ /// @brief Constructor
+ ///
+ /// @param queue_size maximum number of packets the queue can hold
+ TestQueue4(size_t queue_size)
+ : PacketQueueRing4("kea-ring4", queue_size), drop_enabled_(false), eat_count_(0) {
+ };
+
+ /// @brief virtual Destructor
+ virtual ~TestQueue4(){};
+
+ /// @brief Determines is a packet should be dropped.
+ ///
+ /// If drop is enabled and either the packet transaction
+ /// id or the socket source port are even numbers, drop the packet
+ ///
+ /// @param packet the packet under consideration
+ /// @param source the socket the packet came from
+ ///
+ /// @return True if the packet should be dropped.
+ virtual bool shouldDropPacket(Pkt4Ptr packet,
+ const SocketInfo& source) {
+ if (drop_enabled_) {
+ return ((packet->getTransid() % 2 == 0) ||
+ (source.port_ % 2 == 0));
+ }
+
+ return (false);
+ }
+
+ /// @brief Discards a number of packets from one end of the queue
+ ///
+ /// Dequeue and discard eat_count_ packets from the given end of
+ /// the queue_.
+ ///
+ /// @param from end of the queue from which packets should discarded
+ ///
+ /// @return The number of packets discarded.
+ virtual int eatPackets(const QueueEnd& from) {
+ int eaten = 0;
+ for ( ; eaten < eat_count_; ++eaten) {
+ Pkt4Ptr pkt = popPacket(from);
+ if (!pkt) {
+ break;
+ }
+ }
+
+ return (eaten);
+ }
+
+ bool drop_enabled_;
+ int eat_count_;
+};
+
+// Verifies use of the generic PacketQueue interface to
+// construct a queue implementation.
+TEST(PacketQueueRing4, interfaceBasics) {
+ // Verify we can create a queue
+ PacketQueue4Ptr q(new PacketQueueRing4("kea-ring4",100));
+ ASSERT_TRUE(q);
+
+ // It should be empty.
+ EXPECT_TRUE(q->empty());
+
+ // Type should match.
+ EXPECT_EQ("kea-ring4", q->getQueueType());
+
+ // Fetch the queue info and verify it has all the expected values.
+ checkInfo(q, "{ \"capacity\": 100, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
+}
+
+// Verifies the higher level functions of queueing and dequeueing
+// from the ring buffer.
+TEST(PacketQueueRing4, enqueueDequeueTest) {
+ PacketQueue4Ptr q(new PacketQueueRing4("kea-ring4", 3));
+
+ // Fetch the queue info and verify it has all the expected values.
+ checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring4\", \"size\": 0 }");
+
+ // Enqueue five packets. The first two should be pushed off.
+ SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10);
+
+ for (int i = 1; i < 6; ++i) {
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i));
+ ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1));
+ }
+
+
+ // Fetch the queue info and verify it has all the expected values.
+ checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring4\", \"size\": 3 }");
+
+ // We should have transids 1003,1004,1005
+ Pkt4Ptr pkt;
+ for (int i = 3; i < 6; ++i) {
+ ASSERT_NO_THROW(pkt = q->dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1000 + i, pkt->getTransid());
+ }
+
+ // Queue should be empty.
+ ASSERT_TRUE(q->empty());
+
+ // Dequeuing should fail safely, with an empty return.
+ ASSERT_NO_THROW(pkt = q->dequeuePacket());
+ ASSERT_FALSE(pkt);
+
+ // Enqueue three more packets.
+ for (int i = 0; i < 3; ++i) {
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i));
+ ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1));
+ }
+
+ checkIntStat(q, "size", 3);
+
+ // Let's flush the buffer and then verify it is empty.
+ q->clear();
+ EXPECT_TRUE(q->empty());
+ checkIntStat(q, "size", 0);
+}
+
+// Verifies peeking, pushing, and popping which
+// are unique to PacketQueueRing<> derivations.
+TEST(PacketQueueRing4, peekPushPopTest) {
+ PacketQueueRing4 q("kea-ring4", 3);
+
+ // Push five packets onto the end. The first two should get pushed off.
+ for (int i = 1; i < 6; ++i) {
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1000+i));
+ ASSERT_NO_THROW(q.pushPacket(pkt));
+ }
+
+ // We should have three.
+ ASSERT_EQ(3, q.getSize());
+
+ // We should have transids 1005,1004,1003 (back to front)
+
+ // Peek front should be transid 1003.
+ Pkt4Ptr pkt;
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+
+ // Peek back should be transid 1005.
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1005, pkt->getTransid());
+
+ // Pop front should return transid 1003.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+
+ // Pop back should return transid 1005.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1005, pkt->getTransid());
+
+ // Peek front should be transid 1004.
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1004, pkt->getTransid());
+
+ // Peek back should be transid 1004.
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1004, pkt->getTransid());
+
+ // Pop front should return transid 1004.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1004, pkt->getTransid());
+
+ // Pop front should return an empty pointer.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK));
+ ASSERT_FALSE(pkt);
+}
+
+// Verifies enqueuing operations when drop logic is enabled.
+// This accesses it's queue instance as a TestQueue4, rather than
+// a PacketQueue4Ptr, to provide access to TestQueue4 specifics.
+TEST(TestQueue4, shouldDropPacketTest) {
+ TestQueue4 q(100);
+ EXPECT_TRUE(q.empty());
+ ASSERT_FALSE(q.drop_enabled_);
+ ASSERT_EQ(0, q.eat_count_);
+
+ SocketInfo sock_even(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
+ SocketInfo sock_odd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11);
+
+ // Drop is not enabled.
+ // We should be able to enqueue a packet with even numbered values.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1002));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even));
+ ASSERT_EQ(1, q.getSize());
+
+ // We should be able to enqueue a packet with odd numbered values.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 1003));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd));
+ ASSERT_EQ(2, q.getSize());
+
+ // Enable drop logic.
+ q.drop_enabled_ = true;
+
+ // We should not be able to add one with an even-numbered transid.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 1004));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd));
+ ASSERT_EQ(2, q.getSize());
+
+ // We should not be able to add one with from even-numbered port.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 1005));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even));
+ EXPECT_EQ(2, q.getSize());
+
+ // We should be able to add one with an odd-numbered values.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 1007));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd));
+ EXPECT_EQ(3, q.getSize());
+
+ // Dequeue them and make sure they are as expected: 1002,1003, and 1007.
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1002, pkt->getTransid());
+
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1007, pkt->getTransid());
+
+ // Queue should be empty.
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_FALSE(pkt);
+}
+
+// Verifies dequeuing operations when eat packets is enabled.
+// This accesses it's queue instance as a TestQueue4, rather than
+// a PacketQueue4Ptr, to provide access to TestQueue4 specifics.
+TEST(TestQueue4, eatPacketsTest) {
+ TestQueue4 q(100);
+ EXPECT_TRUE(q.empty());
+ ASSERT_FALSE(q.drop_enabled_);
+ ASSERT_EQ(0, q.eat_count_);
+
+ SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
+
+ Pkt4Ptr pkt;
+ // Let's add five packets.
+ for (int i = 1; i < 6; ++i) {
+ pkt.reset(new Pkt4(DHCPDISCOVER, 1000 + i));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock));
+ ASSERT_EQ(i, q.getSize());
+ }
+
+ // Setting eat count to two and dequeuing should discard 1001
+ // and 1002, resulting in a dequeue of 1003.
+ q.eat_count_ = 2;
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+ EXPECT_EQ(2, q.getSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/packet_queue6_unittest.cc b/src/lib/dhcp/tests/packet_queue6_unittest.cc
new file mode 100644
index 0000000..52fb0dc
--- /dev/null
+++ b/src/lib/dhcp/tests/packet_queue6_unittest.cc
@@ -0,0 +1,295 @@
+// Copyright (C) 2018,2021 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 <dhcp/dhcp6.h>
+#include <dhcp/packet_queue_ring.h>
+#include <dhcp/tests/packet_queue_testutils.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief DHCPv6 queue with implements drop and eat logic
+///
+/// This class derives from the default DHCPv6 ring queue
+/// and provides implementations for shouldDropPacket() and
+/// eatPackets(). This permits a full exercising of the
+/// PacketQueue interface as well as the basic v6 ring queue
+/// mechanics.
+class TestQueue6 : public PacketQueueRing6 {
+public:
+ /// @brief Constructor
+ ///
+ /// @param queue_size maximum number of packets the queue can hold
+ TestQueue6(size_t queue_size)
+ : PacketQueueRing6("kea-ring6", queue_size), drop_enabled_(false), eat_count_(0) {
+ };
+
+ /// @brief virtual Destructor
+ virtual ~TestQueue6(){};
+
+ /// @brief Determines is a packet should be dropped.
+ ///
+ /// If drop is enabled and either the packet transaction
+ /// id or the socket source port are even numbers, drop the packet
+ ///
+ /// @param packet the packet under consideration
+ /// @param source the socket the packet came from
+ ///
+ /// @return True if the packet should be dropped.
+ virtual bool shouldDropPacket(Pkt6Ptr packet,
+ const SocketInfo& source) {
+ if (drop_enabled_) {
+ return ((packet->getTransid() % 2 == 0) ||
+ (source.port_ % 2 == 0));
+ }
+
+ return (false);
+ }
+
+ /// @brief Discards a number of packets from one end of the queue
+ ///
+ /// Dequeue and discard eat_count_ packets from the given end of
+ /// the queue_.
+ ///
+ /// @param from end of the queue from which packets should discarded
+ ///
+ /// @return The number of packets discarded.
+ virtual int eatPackets(const QueueEnd& from) {
+ int eaten = 0;
+ for ( ; eaten < eat_count_; ++eaten) {
+ Pkt6Ptr pkt = popPacket(from);
+ if (!pkt) {
+ break;
+ }
+ }
+
+ return (eaten);
+ }
+
+ bool drop_enabled_;
+ int eat_count_;
+};
+
+// Verifies use of the generic PacketQueue interface to
+// construct a queue implementation.
+TEST(PacketQueueRing6, interfaceBasics) {
+ // Verify we can create a queue
+ PacketQueue6Ptr q(new PacketQueueRing6("kea-ring6",100));
+ ASSERT_TRUE(q);
+
+ // It should be empty.
+ EXPECT_TRUE(q->empty());
+
+ // Type should match.
+ EXPECT_EQ("kea-ring6", q->getQueueType());
+
+ // Fetch the queue info and verify it has all the expected values.
+ checkInfo(q, "{ \"capacity\": 100, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
+}
+
+// Verifies the higher level functions of queueing and dequeueing
+// from the ring buffer.
+TEST(PacketQueueRing6, enqueueDequeueTest) {
+ PacketQueue6Ptr q(new PacketQueueRing6("kea-ring6", 3));
+
+ // Fetch the queue info and verify it has all the expected values.
+ checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring6\", \"size\": 0 }");
+
+ // Enqueue five packets. The first two should be pushed off.
+ SocketInfo sock1(isc::asiolink::IOAddress("127.0.0.1"), 777, 10);
+
+ for (int i = 1; i < 6; ++i) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i));
+ ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1));
+ }
+
+
+ // Fetch the queue info and verify it has all the expected values.
+ checkInfo(q, "{ \"capacity\": 3, \"queue-type\": \"kea-ring6\", \"size\": 3 }");
+
+ // We should have transids 1003,1004,1005
+ Pkt6Ptr pkt;
+ for (int i = 3; i < 6; ++i) {
+ ASSERT_NO_THROW(pkt = q->dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1000 + i, pkt->getTransid());
+ }
+
+ // Queue should be empty.
+ ASSERT_TRUE(q->empty());
+
+ // Dequeuing should fail safely, with an empty return.
+ ASSERT_NO_THROW(pkt = q->dequeuePacket());
+ ASSERT_FALSE(pkt);
+
+ // Enqueue three more packets.
+ for (int i = 0; i < 3; ++i) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i));
+ ASSERT_NO_THROW(q->enqueuePacket(pkt, sock1));
+ }
+
+ checkIntStat(q, "size", 3);
+
+ // Let's flush the buffer and then verify it is empty.
+ q->clear();
+ EXPECT_TRUE(q->empty());
+ checkIntStat(q, "size", 0);
+}
+
+// Verifies peeking, pushing, and popping which
+// are unique to PacketQueueRing<> derivations.
+TEST(PacketQueueRing6, peekPushPopTest) {
+ PacketQueueRing6 q("kea-ring6", 3);
+
+ // Push five packets onto the end. The first two should get pushed off.
+ for (int i = 1; i < 6; ++i) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1000+i));
+ ASSERT_NO_THROW(q.pushPacket(pkt));
+ }
+
+ // We should have three.
+ ASSERT_EQ(3, q.getSize());
+
+ // We should have transids 1005,1004,1003 (back to front)
+
+ // Peek front should be transid 1003.
+ Pkt6Ptr pkt;
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+
+ // Peek back should be transid 1005.
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1005, pkt->getTransid());
+
+ // Pop front should return transid 1003.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+
+ // Pop back should return transid 1005.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1005, pkt->getTransid());
+
+ // Peek front should be transid 1004.
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1004, pkt->getTransid());
+
+ // Peek back should be transid 1004.
+ ASSERT_NO_THROW(pkt = q.peek(QueueEnd::BACK));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1004, pkt->getTransid());
+
+ // Pop front should return transid 1004.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::FRONT));
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1004, pkt->getTransid());
+
+ // Pop front should return an empty pointer.
+ ASSERT_NO_THROW(pkt = q.popPacket(QueueEnd::BACK));
+ ASSERT_FALSE(pkt);
+}
+
+// Verifies enqueuing operations when drop logic is enabled.
+// This accesses it's queue instance as a TestQueue6, rather than
+// a PacketQueue6Ptr, to provide access to TestQueue6 specifics.
+TEST(TestQueue6, shouldDropPacketTest) {
+ TestQueue6 q(100);
+ EXPECT_TRUE(q.empty());
+ ASSERT_FALSE(q.drop_enabled_);
+ ASSERT_EQ(0, q.eat_count_);
+
+ SocketInfo sock_even(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
+ SocketInfo sock_odd(isc::asiolink::IOAddress("127.0.0.1"), 777, 11);
+
+ // Drop is not enabled.
+ // We should be able to enqueue a packet with even numbered values.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 1002));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even));
+ ASSERT_EQ(1, q.getSize());
+
+ // We should be able to enqueue a packet with odd numbered values.
+ pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1003));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd));
+ ASSERT_EQ(2, q.getSize());
+
+ // Enable drop logic.
+ q.drop_enabled_ = true;
+
+ // We should not be able to add one with an even-numbered transid.
+ pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1004));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd));
+ ASSERT_EQ(2, q.getSize());
+
+ // We should not be able to add one with from even-numbered port.
+ pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1005));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_even));
+ EXPECT_EQ(2, q.getSize());
+
+ // We should be able to add one with an odd-numbered values.
+ pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1007));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock_odd));
+ EXPECT_EQ(3, q.getSize());
+
+ // Dequeue them and make sure they are as expected: 1002,1003, and 1007.
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1002, pkt->getTransid());
+
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1007, pkt->getTransid());
+
+ // Queue should be empty.
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_FALSE(pkt);
+}
+
+// Verifies dequeuing operations when eat packets is enabled.
+// This accesses it's queue instance as a TestQueue6, rather than
+// a PacketQueue6Ptr, to provide access to TestQueue6 specifics.
+TEST(TestQueue6, eatPacketsTest) {
+ TestQueue6 q(100);
+ EXPECT_TRUE(q.empty());
+ ASSERT_FALSE(q.drop_enabled_);
+ ASSERT_EQ(0, q.eat_count_);
+
+ SocketInfo sock(isc::asiolink::IOAddress("127.0.0.1"), 888, 10);
+
+ Pkt6Ptr pkt;
+ // Let's add five packets.
+ for (int i = 1; i < 6; ++i) {
+ pkt.reset(new Pkt6(DHCPV6_SOLICIT, 1000 + i));
+ ASSERT_NO_THROW(q.enqueuePacket(pkt, sock));
+ ASSERT_EQ(i, q.getSize());
+ }
+
+ // Setting eat count to two and dequeuing should discard 1001
+ // and 1002, resulting in a dequeue of 1003.
+ q.eat_count_ = 2;
+ ASSERT_NO_THROW(pkt = q.dequeuePacket());
+ ASSERT_TRUE(pkt);
+ EXPECT_EQ(1003, pkt->getTransid());
+ EXPECT_EQ(2, q.getSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc b/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc
new file mode 100644
index 0000000..90aa61e
--- /dev/null
+++ b/src/lib/dhcp/tests/packet_queue_mgr4_unittest.cc
@@ -0,0 +1,144 @@
+// Copyright (C) 2018-2021 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 <dhcp/packet_queue_ring.h>
+#include <dhcp/packet_queue_mgr4.h>
+#include <dhcp/tests/packet_queue_testutils.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+/// @brief Convenience function for construction a dhcp-queue-control element map
+///
+/// @param queue_type logical name of the queue implementation type
+/// @param capacity maximum queue capacity
+/// @param enable_queue bool value to ascribe to the 'enable-queue' parameter, defaults to true
+data::ElementPtr
+isc::dhcp::test::makeQueueConfig(const std::string& queue_type, size_t capacity, bool enable_queue /* = true */) {
+ data::ElementPtr config = data::Element::createMap();
+ config->set("enable-queue", data::Element::create(enable_queue));
+ config->set("queue-type", data::Element::create(queue_type));
+ config->set("capacity", data::Element::create(static_cast<long int>(capacity)));
+ return (config);
+}
+
+namespace {
+
+/// @brief Test fixture for exercising the DHCPv4 Packet Queue Manager (PQM)
+class PacketQueueMgr4Test : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Note that it instantiates the PQM singleton.
+ PacketQueueMgr4Test()
+ : default_queue_type_(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4) {
+ packet_queue_mgr4_.reset(new PacketQueueMgr4());
+ }
+
+ /// @brief Destructor
+ virtual ~PacketQueueMgr4Test(){}
+
+ /// @brief Registers a queue type factory
+ ///
+ /// @param queue_type logical name of the queue implementation
+ ///
+ /// @return true if the registration succeeded, false otherwise
+ bool addCustomQueueType(const std::string& queue_type) {
+ bool did_it =
+ mgr().registerPacketQueueFactory(queue_type,
+ [](data::ConstElementPtr parameters)
+ -> PacketQueue4Ptr {
+ std::string queue_type ;
+ try {
+ queue_type = data::SimpleParser::getString(parameters, "queue-type");
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidQueueParameter,
+ "queue-type missing or invalid: " << ex.what());
+ }
+
+ size_t capacity;
+ try {
+ capacity = data::SimpleParser::getInteger(parameters, "capacity");
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidQueueParameter,
+ "'capacity' missing or invalid: " << ex.what());
+ }
+
+ return (PacketQueue4Ptr(new PacketQueueRing4(queue_type, capacity)));
+ });
+
+ return did_it;
+ }
+
+ /// @brief Fetches a pointer to the PQM singleton
+ PacketQueueMgr4& mgr() {
+ return (*packet_queue_mgr4_);
+ };
+
+ /// @brief Tests the current packet queue info against expected content
+ ///
+ /// @param exp_json JSON text describing the expected packet queue info
+ /// contents
+ void checkMyInfo(const std::string& exp_json) {
+ checkInfo((mgr().getPacketQueue()), exp_json);
+ }
+
+ /// @brief default queue type used for a given test
+ std::string default_queue_type_;
+
+ /// @brief Packet Queue manager instance
+ PacketQueueMgr4Ptr packet_queue_mgr4_;
+};
+
+// Verifies that DHCPv4 PQM provides a default queue factory
+TEST_F(PacketQueueMgr4Test, defaultQueue) {
+ // Should not be a queue at start-up
+ ASSERT_FALSE(mgr().getPacketQueue());
+
+ // Verify that we can create a queue with default factory.
+ data::ConstElementPtr config = makeQueueConfig(default_queue_type_, 2000);
+ ASSERT_NO_THROW(mgr().createPacketQueue(config));
+ CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \""
+ << default_queue_type_ << "\", \"size\": 0 }");
+}
+
+// Verifies that PQM registry and creation of custom queue implementations.
+TEST_F(PacketQueueMgr4Test, customQueueType) {
+
+ // Verify that we cannot create a queue for a non-existant type
+ data::ConstElementPtr config = makeQueueConfig("custom-queue", 2000);
+ ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType);
+
+ // Register our adjustable-type factory
+ ASSERT_TRUE(addCustomQueueType("custom-queue"));
+
+ // Verify that we can create a custom queue.
+ ASSERT_NO_THROW(mgr().createPacketQueue(config));
+ checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }");
+
+ // Now unregister the factory.
+ ASSERT_NO_THROW(mgr().unregisterPacketQueueFactory("custom-queue"));
+ // Queue should be gone too.
+ ASSERT_FALSE(mgr().getPacketQueue());
+
+ // Try and recreate the custom queue, type should be invalid.
+ ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType);
+
+ // Verify we can create a default type queue with non-default capacity.
+ config = makeQueueConfig(default_queue_type_, 2000);
+ ASSERT_NO_THROW(mgr().createPacketQueue(config));
+ CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \""
+ << default_queue_type_ << "\", \"size\": 0 }");
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc b/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc
new file mode 100644
index 0000000..b97f7e9
--- /dev/null
+++ b/src/lib/dhcp/tests/packet_queue_mgr6_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright (C) 2018-2021 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 <dhcp/packet_queue_ring.h>
+#include <dhcp/packet_queue_mgr6.h>
+#include <dhcp/tests/packet_queue_testutils.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture for exercising the DHCPv6 Packet Queue Manager (PQM)
+class PacketQueueMgr6Test : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Note that it instantiates the PQM singleton.
+ PacketQueueMgr6Test()
+ : default_queue_type_(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6) {
+ packet_queue_mgr6_.reset(new PacketQueueMgr6());
+
+ }
+
+ /// @brief Destructor
+ ///
+ /// It destroys the PQM singleton.
+ virtual ~PacketQueueMgr6Test(){}
+
+ /// @brief Registers a queue type factory
+ ///
+ /// @param queue_type logical name of the queue implementation
+ ///
+ /// @return true if the registration succeeded, false otherwise
+ bool addCustomQueueType(const std::string& queue_type) {
+ bool did_it =
+ mgr().registerPacketQueueFactory(queue_type,
+ [](data::ConstElementPtr parameters)
+ -> PacketQueue6Ptr {
+ std::string queue_type ;
+ try {
+ queue_type = data::SimpleParser::getString(parameters, "queue-type");
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidQueueParameter,
+ "queue-type missing or invalid: " << ex.what());
+ }
+
+ size_t capacity;
+ try {
+ capacity = data::SimpleParser::getInteger(parameters, "capacity");
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidQueueParameter,
+ "'capacity' missing or invalid: " << ex.what());
+ }
+
+ return (PacketQueue6Ptr(new PacketQueueRing6(queue_type, capacity)));
+ });
+
+ return did_it;
+ }
+
+ /// @brief Fetches a pointer to the PQM singleton
+ PacketQueueMgr6& mgr() {
+ return (*packet_queue_mgr6_);
+ };
+
+ /// @brief Tests the current packet queue info against expected content
+ ///
+ /// @param exp_json JSON text describing the expected packet queue info
+ /// contents
+ void checkMyInfo(const std::string& exp_json) {
+ checkInfo((mgr().getPacketQueue()), exp_json);
+ }
+
+ /// @brief default queue type used for a given test
+ std::string default_queue_type_;
+
+ /// @brief Packet Queue manager instance
+ PacketQueueMgr6Ptr packet_queue_mgr6_;
+};
+
+// Verifies that DHCPv6 PQM provides a default queue factory
+TEST_F(PacketQueueMgr6Test, defaultQueue) {
+ // Should not be a queue at start-up
+ ASSERT_FALSE(mgr().getPacketQueue());
+
+ // Verify that we can create a queue with default factory.
+ data::ConstElementPtr config = makeQueueConfig(default_queue_type_, 2000);
+ ASSERT_NO_THROW(mgr().createPacketQueue(config));
+ CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \""
+ << default_queue_type_ << "\", \"size\": 0 }");
+}
+
+// Verifies that PQM registry and creation of custom queue implementations.
+TEST_F(PacketQueueMgr6Test, customQueueType) {
+
+ // Verify that we cannot create a queue for a non-existant type
+ data::ConstElementPtr config = makeQueueConfig("custom-queue", 2000);
+ ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType);
+
+ // Register our adjustable-type factory
+ ASSERT_TRUE(addCustomQueueType("custom-queue"));
+
+ // Verify that we can create a custom queue.
+ ASSERT_NO_THROW(mgr().createPacketQueue(config));
+ checkMyInfo("{ \"capacity\": 2000, \"queue-type\": \"custom-queue\", \"size\": 0 }");
+
+ // Now unregister the factory.
+ ASSERT_NO_THROW(mgr().unregisterPacketQueueFactory("custom-queue"));
+ // Queue should be gone too.
+ ASSERT_FALSE(mgr().getPacketQueue());
+
+ // Try and recreate the custom queue, type should be invalid.
+ ASSERT_THROW(mgr().createPacketQueue(config), InvalidQueueType);
+
+ // Verify we can create a default type queue with non-default capacity.
+ config = makeQueueConfig(default_queue_type_, 2000);
+ ASSERT_NO_THROW(mgr().createPacketQueue(config));
+ CHECK_QUEUE_INFO (mgr().getPacketQueue(), "{ \"capacity\": 2000, \"queue-type\": \""
+ << default_queue_type_ << "\", \"size\": 0 }");
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/packet_queue_testutils.h b/src/lib/dhcp/tests/packet_queue_testutils.h
new file mode 100644
index 0000000..45a0042
--- /dev/null
+++ b/src/lib/dhcp/tests/packet_queue_testutils.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2018-2023 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 PACKET_QUEUE_TESTUTILS_H
+#define PACKET_QUEUE_TESTUTILS_H
+
+#include <config.h>
+
+#include <dhcp/packet_queue.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+template<typename PacketQueuePtrType>
+void checkInfo(PacketQueuePtrType queue, const std::string& exp_json) {
+ ASSERT_TRUE(queue) << "packet queue ptr is null";
+ // Fetch the queue info and verify it has all the expected values.
+ data::ElementPtr info;
+ ASSERT_NO_THROW(info = queue->getInfo());
+ ASSERT_TRUE(info);
+ data::ElementPtr exp_elems;
+ ASSERT_NO_THROW(exp_elems = data::Element::fromJSON(exp_json)) <<
+ " exp_elems is invalid JSON : " << exp_json << " test is broken";
+ EXPECT_TRUE(exp_elems->equals(*info));
+}
+
+#define CHECK_QUEUE_INFO(queue, stream) \
+ { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ checkInfo(queue, oss__.str().c_str());\
+ }
+
+
+template<typename PacketQueuePtrType>
+void checkIntStat(PacketQueuePtrType queue, const std::string& name, size_t exp_value) {
+ ASSERT_TRUE(queue) << "packet queue ptr is null";
+ data::ElementPtr info;
+ ASSERT_NO_THROW(info = queue->getInfo());
+ ASSERT_TRUE(info);
+
+ data::ConstElementPtr elem;
+ ASSERT_NO_THROW(elem = info->get(name)) << "stat: " << name << " not in info" << std::endl;
+ ASSERT_TRUE(elem);
+
+ int64_t value = 0;
+ ASSERT_NO_THROW(value = elem->intValue());
+ EXPECT_EQ(exp_value, value) << "stat: " << name << " is wrong" << std::endl;;
+}
+
+extern data::ElementPtr makeQueueConfig(const std::string& queue_type, size_t capacity, bool enable_queue=true);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // PACKET_QUEUE_TESTUTILS_H
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
new file mode 100644
index 0000000..70bc624
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -0,0 +1,1529 @@
+// Copyright (C) 2011-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/pkt4.h>
+#include <exceptions/exceptions.h>
+#include <testutils/gtest_utils.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <pkt_captures.h>
+
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/static_assert.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+// Don't import the entire boost namespace. It will unexpectedly hide uint8_t
+// for some systems.
+using boost::scoped_ptr;
+
+namespace {
+
+/// V4 Options being used for pack/unpack testing.
+/// For test simplicity, all selected options have
+/// variable length data so as there are no restrictions
+/// on a length of their data.
+static uint8_t v4_opts[] = {
+ 53, 1, 2, // Message Type (required to not throw exception during unpack)
+ 12, 3, 0, 1, 2, // Hostname
+ 14, 3, 10, 11, 12, // Merit Dump File
+ 60, 3, 20, 21, 22, // Class Id
+ 128, 3, 30, 31, 32, // Vendor specific
+ 254, 3, 40, 41, 42, // Reserved
+};
+
+// Sample data
+const uint8_t dummyOp = BOOTREQUEST;
+const uint8_t dummyHtype = 6;
+const uint8_t dummyHlen = 6;
+const uint8_t dummyHops = 13;
+const uint32_t dummyTransid = 0x12345678;
+const uint16_t dummySecs = 42;
+const uint16_t dummyFlags = BOOTP_BROADCAST;
+
+const IOAddress dummyCiaddr("192.0.2.1");
+const IOAddress dummyYiaddr("1.2.3.4");
+const IOAddress dummySiaddr("192.0.2.255");
+const IOAddress dummyGiaddr("255.255.255.255");
+
+// a dummy MAC address
+const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
+
+// A dummy MAC address, padded with 0s
+const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+// Let's use some creative test content here (128 chars + \0)
+const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit. Proin mollis placerat metus, at "
+ "lacinia orci ornare vitae. Mauris amet.";
+
+// Yet another type of test content (64 chars + \0)
+const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit posuere.";
+
+BOOST_STATIC_ASSERT(sizeof(dummyFile) == Pkt4::MAX_FILE_LEN + 1);
+BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1);
+
+
+class Pkt4Test : public ::testing::Test {
+public:
+ Pkt4Test() {
+ }
+
+ /// @brief Generates test packet.
+ ///
+ /// Allocates and generates test packet, with all fixed fields set to non-zero
+ /// values. Content is not always reasonable.
+ ///
+ /// See generateTestPacket2() function that returns exactly the same packet in
+ /// on-wire format.
+ ///
+ /// @return pointer to allocated Pkt4 object.
+ Pkt4Ptr generateTestPacket1() {
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid));
+
+ vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr
+ + sizeof(dummyMacAddr));
+
+ // hwType = 6(ETHERNET), hlen = 6(MAC address len)
+ pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr);
+ pkt->setHops(dummyHops); // 13 relays. Wow!
+ // Transaction-id is already set.
+ pkt->setSecs(dummySecs);
+ pkt->setFlags(dummyFlags); // all flags set
+ pkt->setCiaddr(dummyCiaddr);
+ pkt->setYiaddr(dummyYiaddr);
+ pkt->setSiaddr(dummySiaddr);
+ pkt->setGiaddr(dummyGiaddr);
+ // Chaddr already set with setHWAddr().
+ pkt->setSname(dummySname, 64);
+ pkt->setFile(dummyFile, 128);
+
+ return (pkt);
+ }
+
+ /// @brief Generates test packet.
+ ///
+ /// Allocates and generates on-wire buffer that represents test packet, with all
+ /// fixed fields set to non-zero values. Content is not always reasonable.
+ ///
+ /// See generateTestPacket1() function that returns exactly the same packet as
+ /// Pkt4 object.
+ ///
+ /// @return pointer to allocated Pkt4 object
+ // Returns a vector containing a DHCPv4 packet header.
+ vector<uint8_t> generateTestPacket2() {
+
+ // That is only part of the header. It contains all "short" fields,
+ // larger fields are constructed separately.
+ uint8_t hdr[] = {
+ 1, 6, 6, 13, // op, htype, hlen, hops,
+ 0x12, 0x34, 0x56, 0x78, // transaction-id
+ 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
+ 192, 0, 2, 1, // ciaddr
+ 1, 2, 3, 4, // yiaddr
+ 192, 0, 2, 255, // siaddr
+ 255, 255, 255, 255, // giaddr
+ };
+
+ // Initialize the vector with the header fields defined above.
+ vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+
+ // Append the large header fields.
+ copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
+ copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
+ copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+
+ // Should now have all the header, so check. The "static_cast" is used
+ // to get round an odd bug whereby the linker appears not to find the
+ // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
+
+ return (buf);
+ }
+
+ /// @brief Verify that the options are correct after parsing.
+ ///
+ /// @param pkt A packet holding parsed options.
+ void verifyParsedOptions(const Pkt4Ptr& pkt) {
+ EXPECT_TRUE(pkt->getOption(12));
+ EXPECT_TRUE(pkt->getOption(60));
+ EXPECT_TRUE(pkt->getOption(14));
+ EXPECT_TRUE(pkt->getOption(128));
+ EXPECT_TRUE(pkt->getOption(254));
+
+ // Verify the packet type is correct.
+ ASSERT_EQ(DHCPOFFER, pkt->getType());
+
+ // First option after message type starts at 3.
+ uint8_t *opt_data_ptr = v4_opts + 3;
+
+ // Option 12 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ boost::shared_ptr<Option> x = pkt->getOption(12);
+ ASSERT_TRUE(x); // option 1 should exist
+ OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
+
+ ASSERT_TRUE(option12);
+ EXPECT_EQ(12, option12->getType()); // this should be option 12
+ ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option12->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], opt_data_ptr + 2, 2)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(14);
+ ASSERT_TRUE(x); // option 14 should exist
+ // Option 14 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
+ ASSERT_TRUE(option14);
+ EXPECT_EQ(14, option14->getType()); // this should be option 14
+ ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option14->len()); // total option length 5
+
+ EXPECT_EQ(0, memcmp(&option14->getValue()[0], opt_data_ptr + 2, 3)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(60);
+ ASSERT_TRUE(x); // option 60 should exist
+ EXPECT_EQ(60, x->getType()); // this should be option 60
+ ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(128);
+ ASSERT_TRUE(x); // option 3 should exist
+ EXPECT_EQ(128, x->getType()); // this should be option 254
+ ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3
+ opt_data_ptr += x->len();
+
+ x = pkt->getOption(254);
+ ASSERT_TRUE(x); // option 3 should exist
+ EXPECT_EQ(254, x->getType()); // this should be option 254
+ ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+ EXPECT_EQ(5, x->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&x->getData()[0], opt_data_ptr + 2, 3)); // data len=3
+ }
+
+};
+
+
+TEST_F(Pkt4Test, constructor) {
+
+ ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
+ scoped_ptr<Pkt4> pkt;
+
+ // Just some dummy payload.
+ uint8_t testData[250];
+ for (uint8_t i = 0; i < 250; i++) {
+ testData[i] = i;
+ }
+
+ // Positive case1. Normal received packet.
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
+
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+
+ EXPECT_NO_THROW(pkt.reset());
+
+ // Positive case2. Normal outgoing packet.
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
+
+ // DHCPv4 packet must be at least 236 bytes long, with Message Type
+ // Option taking extra 3 bytes it is 239
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+ EXPECT_EQ(0xffffffff, pkt->getTransid());
+ EXPECT_NO_THROW(pkt.reset());
+
+ // Negative case. Should drop truncated messages.
+ EXPECT_THROW(
+ pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
+ OutOfRange
+ );
+}
+
+
+TEST_F(Pkt4Test, fixedFields) {
+
+ boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
+
+ // OK, let's check packet values
+ EXPECT_EQ(dummyOp, pkt->getOp());
+ EXPECT_EQ(dummyHtype, pkt->getHtype());
+ EXPECT_EQ(dummyHlen, pkt->getHlen());
+ EXPECT_EQ(dummyHops, pkt->getHops());
+ EXPECT_EQ(dummyTransid, pkt->getTransid());
+ EXPECT_EQ(dummySecs, pkt->getSecs());
+ EXPECT_EQ(dummyFlags, pkt->getFlags());
+
+ EXPECT_EQ(dummyCiaddr, pkt->getCiaddr());
+ EXPECT_EQ(dummyYiaddr, pkt->getYiaddr());
+ EXPECT_EQ(dummySiaddr, pkt->getSiaddr());
+ EXPECT_EQ(dummyGiaddr, pkt->getGiaddr());
+
+ // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes
+ // long and its length depends on hlen value (it is up to 16 bytes now).
+ ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen);
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
+
+ EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64));
+
+ EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], 128));
+
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+}
+
+TEST_F(Pkt4Test, fixedFieldsPack) {
+ boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
+ vector<uint8_t> expectedFormat = generateTestPacket2();
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // Minimum packet size is 236 bytes + 3 bytes of mandatory
+ // DHCP Message Type Option
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
+
+ // Redundant but MUCH easier for debug in gdb
+ const uint8_t* exp = &expectedFormat[0];
+ const uint8_t* got = static_cast<const uint8_t*>(pkt->getBuffer().getData());
+
+ EXPECT_EQ(0, memcmp(exp, got, Pkt4::DHCPV4_PKT_HDR_LEN));
+}
+
+/// TODO Uncomment when ticket #1226 is implemented
+TEST_F(Pkt4Test, fixedFieldsUnpack) {
+ vector<uint8_t> expectedFormat = generateTestPacket2();
+
+ expectedFormat.push_back(0x63); // magic cookie
+ expectedFormat.push_back(0x82);
+ expectedFormat.push_back(0x53);
+ expectedFormat.push_back(0x63);
+
+ expectedFormat.push_back(0x35); // message-type
+ expectedFormat.push_back(0x1);
+ expectedFormat.push_back(0x1);
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0],
+ expectedFormat.size()));;
+
+
+ EXPECT_NO_THROW(
+ pkt->unpack()
+ );
+
+ // OK, let's check packet values
+ EXPECT_EQ(dummyOp, pkt->getOp());
+ EXPECT_EQ(dummyHtype, pkt->getHtype());
+ EXPECT_EQ(dummyHlen, pkt->getHlen());
+ EXPECT_EQ(dummyHops, pkt->getHops());
+ EXPECT_EQ(dummyTransid, pkt->getTransid());
+ EXPECT_EQ(dummySecs, pkt->getSecs());
+ EXPECT_EQ(dummyFlags, pkt->getFlags());
+
+ EXPECT_EQ(dummyCiaddr, pkt->getCiaddr());
+ EXPECT_EQ("1.2.3.4", pkt->getYiaddr().toText());
+ EXPECT_EQ("192.0.2.255", pkt->getSiaddr().toText());
+ EXPECT_EQ("255.255.255.255", pkt->getGiaddr().toText());
+
+ // chaddr is always 16 bytes long and contains link-layer addr (MAC)
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
+
+ ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size());
+ EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
+
+ ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_FILE_LEN), pkt->getFile().size());
+ EXPECT_EQ(0, memcmp(dummyFile, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN));
+
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+}
+
+// This test is for hardware addresses (htype, hlen and chaddr fields)
+TEST_F(Pkt4Test, hwAddr) {
+
+ vector<uint8_t> mac;
+ uint8_t expectedChaddr[Pkt4::MAX_CHADDR_LEN];
+
+ // We resize vector to specified length. It is more natural for fixed-length
+ // field, than clear it (shrink size to 0) and push_back each element
+ // (growing length back to MAX_CHADDR_LEN).
+ mac.resize(Pkt4::MAX_CHADDR_LEN);
+
+ scoped_ptr<Pkt4> pkt;
+ // let's test each hlen, from 0 till 16
+ for (size_t macLen = 0; macLen < Pkt4::MAX_CHADDR_LEN; macLen++) {
+ for (size_t i = 0; i < Pkt4::MAX_CHADDR_LEN; i++) {
+ mac[i] = 0;
+ expectedChaddr[i] = 0;
+ }
+ for (size_t i = 0; i < macLen; i++) {
+ mac[i] = 128 + i;
+ expectedChaddr[i] = 128 + i;
+ }
+
+ // type and transaction doesn't matter in this test
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setHWAddr(255 - macLen * 10, // just weird htype
+ macLen,
+ mac);
+ EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
+ Pkt4::MAX_CHADDR_LEN));
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // CHADDR starts at offset 28 in DHCP packet
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 28;
+
+ EXPECT_EQ(0, memcmp(ptr, expectedChaddr, Pkt4::MAX_CHADDR_LEN));
+
+ pkt.reset();
+ }
+
+ /// TODO: extend this test once options support is implemented. HW address
+ /// longer than 16 bytes should be stored in client-identifier option
+}
+
+TEST_F(Pkt4Test, msgTypes) {
+
+ struct msgType {
+ uint8_t dhcp;
+ uint8_t bootp;
+ };
+
+ msgType types[] = {
+ {DHCPDISCOVER, BOOTREQUEST},
+ {DHCPOFFER, BOOTREPLY},
+ {DHCPREQUEST, BOOTREQUEST},
+ {DHCPDECLINE, BOOTREQUEST},
+ {DHCPACK, BOOTREPLY},
+ {DHCPNAK, BOOTREPLY},
+ {DHCPRELEASE, BOOTREQUEST},
+ {DHCPINFORM, BOOTREQUEST},
+ {DHCPLEASEQUERY, BOOTREQUEST},
+ {DHCPLEASEUNASSIGNED, BOOTREPLY},
+ {DHCPLEASEUNKNOWN, BOOTREPLY},
+ {DHCPLEASEACTIVE, BOOTREPLY}
+ };
+
+ scoped_ptr<Pkt4> pkt;
+ for (size_t i = 0; i < sizeof(types) / sizeof(msgType); i++) {
+ pkt.reset(new Pkt4(types[i].dhcp, 0));
+ EXPECT_EQ(types[i].dhcp, pkt->getType());
+ EXPECT_EQ(types[i].bootp, pkt->getOp());
+ pkt.reset();
+ }
+
+ EXPECT_THROW(
+ pkt.reset(new Pkt4(100, 0)), // There's no message type 100
+ OutOfRange
+ );
+}
+
+// This test verifies handling of sname field
+TEST_F(Pkt4Test, sname) {
+
+ uint8_t sname[Pkt4::MAX_SNAME_LEN];
+
+ scoped_ptr<Pkt4> pkt;
+ // Let's test each sname length, from 0 till 64 (included)
+ for (size_t snameLen = 0; snameLen <= Pkt4::MAX_SNAME_LEN; ++snameLen) {
+ for (size_t i = 0; i < snameLen; ++i) {
+ sname[i] = i + 1;
+ }
+ if (snameLen < Pkt4::MAX_SNAME_LEN) {
+ for (size_t i = snameLen; i < Pkt4::MAX_SNAME_LEN; ++i) {
+ sname[i] = 0;
+ }
+ }
+
+ // Type and transaction doesn't matter in this test
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setSname(sname, snameLen);
+
+ EXPECT_EQ(0, memcmp(sname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // SNAME starts at offset 44 in DHCP packet
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 44;
+ EXPECT_EQ(0, memcmp(ptr, sname, Pkt4::MAX_SNAME_LEN));
+
+ pkt.reset();
+ }
+
+ // Check that a null argument generates an exception.
+ Pkt4 pkt4(DHCPOFFER, 1234);
+ EXPECT_THROW(pkt4.setSname(NULL, Pkt4::MAX_SNAME_LEN), InvalidParameter);
+ EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter);
+
+ // Check that a too long argument generates an exception
+ // (the actual content doesn't matter).
+ uint8_t bigsname[Pkt4::MAX_SNAME_LEN + 1];
+ EXPECT_THROW(pkt4.setSname(bigsname, Pkt4::MAX_SNAME_LEN + 1), OutOfRange);
+}
+
+TEST_F(Pkt4Test, file) {
+
+ uint8_t file[Pkt4::MAX_FILE_LEN];
+
+ scoped_ptr<Pkt4> pkt;
+ // Let's test each file length, from 0 till 128 (included).
+ for (size_t fileLen = 0; fileLen <= Pkt4::MAX_FILE_LEN; ++fileLen) {
+ for (size_t i = 0; i < fileLen; ++i) {
+ file[i] = i + 1;
+ }
+ if (fileLen < Pkt4::MAX_FILE_LEN) {
+ for (size_t i = fileLen; i < Pkt4::MAX_FILE_LEN; ++i) {
+ file[i] = 0;
+ }
+ }
+
+ // Type and transaction doesn't matter in this test.
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setFile(file, fileLen);
+
+ EXPECT_EQ(0, memcmp(file, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN));
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ // FILE starts at offset 108 in DHCP packet.
+ const uint8_t* ptr =
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 108;
+ EXPECT_EQ(0, memcmp(ptr, file, Pkt4::MAX_FILE_LEN));
+
+ pkt.reset();
+ }
+
+ // Check that a null argument generates an exception.
+ Pkt4 pkt4(DHCPOFFER, 1234);
+ EXPECT_THROW(pkt4.setFile(NULL, Pkt4::MAX_FILE_LEN), InvalidParameter);
+ EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter);
+
+ // Check that a too long argument generates an exception
+ // (the actual content doesn't matter).
+ uint8_t bigfile[Pkt4::MAX_FILE_LEN + 1];
+ EXPECT_THROW(pkt4.setFile(bigfile, Pkt4::MAX_FILE_LEN + 1), OutOfRange);
+}
+
+TEST_F(Pkt4Test, options) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
+
+ vector<uint8_t> payload[5];
+ for (uint8_t i = 0; i < 5; i++) {
+ payload[i].push_back(i * 10);
+ payload[i].push_back(i * 10 + 1);
+ payload[i].push_back(i * 10 + 2);
+ }
+
+ boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
+ boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1]));
+ boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2]));
+ boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3]));
+ boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4]));
+
+ pkt->addOption(opt1);
+ pkt->addOption(opt2);
+ pkt->addOption(opt3);
+ pkt->addOption(opt4);
+ pkt->addOption(opt5);
+
+ EXPECT_TRUE(pkt->getOption(12));
+ EXPECT_TRUE(pkt->getOption(60));
+ EXPECT_TRUE(pkt->getOption(14));
+ EXPECT_TRUE(pkt->getOption(128));
+ EXPECT_TRUE(pkt->getOption(254));
+ EXPECT_FALSE(pkt->getOption(127)); // no such option
+
+ // Options are unique in DHCPv4. It should not be possible
+ // to add more than one option of the same type.
+ EXPECT_THROW(
+ pkt->addOption(opt1),
+ BadValue
+ );
+
+ EXPECT_NO_THROW(
+ pkt->pack();
+ );
+
+ const OutputBuffer& buf = pkt->getBuffer();
+ // Check that all options are stored, they should take sizeof(v4_opts),
+ // DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
+ sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4_opts) + 1,
+ buf.getLength());
+
+ // That that this extra data actually contain our options
+ const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
+
+ // Rewind to end of fixed part.
+ ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
+
+ EXPECT_EQ(0, memcmp(ptr, v4_opts, sizeof(v4_opts)));
+ EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4_opts))));
+
+ // delOption() checks
+ EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+ EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+ EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
+ EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
+
+ EXPECT_NO_THROW(pkt.reset());
+}
+
+// Check that multiple options of the same type may be retrieved by
+// using getOptions, Also check that retrieved options are copied when
+// setCopyRetrievedOptions is enabled.
+TEST_F(Pkt4Test, getOptions) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
+ OptionPtr opt1(new Option(Option::V4, 1));
+ OptionPtr opt2(new Option(Option::V4, 1));
+ OptionPtr opt3(new Option(Option::V4, 2));
+ OptionPtr opt4(new Option(Option::V4, 2));
+
+ pkt->addOption(opt1);
+ pkt->Pkt::addOption(opt2);
+ pkt->Pkt::addOption(opt3);
+ pkt->Pkt::addOption(opt4);
+
+ // Retrieve options with option code 1.
+ OptionCollection options = pkt->getOptions(1);
+ ASSERT_EQ(2, options.size());
+
+ OptionCollection::const_iterator opt_it;
+
+ // Make sure that the first option is returned. We're using the pointer
+ // to opt1 to find the option.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt1));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Make sure that the second option is returned.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt2));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Retrieve options with option code 2.
+ options = pkt->getOptions(2);
+ ASSERT_EQ(2, options.size());
+
+ // opt3 and opt4 should exist.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt3));
+ EXPECT_TRUE(opt_it != options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt4));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Enable copying options when they are retrieved.
+ pkt->setCopyRetrievedOptions(true);
+
+ options = pkt->getOptions(1);
+ ASSERT_EQ(2, options.size());
+
+ // Both retrieved options should be copied so an attempt to find them
+ // using option pointer should fail. Original pointers should have
+ // been replaced with new instances.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt1));
+ EXPECT_TRUE(opt_it == options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt2));
+ EXPECT_TRUE(opt_it == options.end());
+
+ // Return instances of options with the option code 1 and make sure
+ // that copies of the options were used to replace original options
+ // in the packet.
+ pkt->setCopyRetrievedOptions(false);
+ OptionCollection options_modified = pkt->getOptions(1);
+ for (OptionCollection::const_iterator opt_it_modified = options_modified.begin();
+ opt_it_modified != options_modified.end(); ++opt_it_modified) {
+ opt_it = std::find(options.begin(), options.end(), *opt_it_modified);
+ ASSERT_TRUE(opt_it != options.end());
+ }
+
+ // Let's check that remaining two options haven't been affected by
+ // retrieving the options with option code 1.
+ options = pkt->getOptions(2);
+ ASSERT_EQ(2, options.size());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt3));
+ EXPECT_TRUE(opt_it != options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt4));
+ EXPECT_TRUE(opt_it != options.end());
+}
+
+// This test verifies that it is possible to control whether a pointer
+// to an option or a pointer to a copy of an option is returned by the
+// packet object.
+TEST_F(Pkt4Test, setCopyRetrievedOptions) {
+ // Create option 1 with two sub options.
+ OptionPtr option1(new Option(Option::V4, 1));
+ OptionPtr sub1(new Option(Option::V4, 1));
+ OptionPtr sub2(new Option(Option::V4, 2));
+
+ option1->addOption(sub1);
+ option1->addOption(sub2);
+
+ // Create option 2 with two sub options.
+ OptionPtr option2(new Option(Option::V4, 2));
+ OptionPtr sub3(new Option(Option::V4, 1));
+ OptionPtr sub4(new Option(Option::V4, 2));
+
+ option2->addOption(sub3);
+ option2->addOption(sub4);
+
+ // Add both options to a packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
+ pkt->addOption(option1);
+ pkt->addOption(option2);
+
+ // Retrieve options and make sure that the pointers to the original
+ // option instances are returned.
+ ASSERT_TRUE(option1 == pkt->getOption(1));
+ ASSERT_TRUE(option2 == pkt->getOption(2));
+
+ // Now force copying the options when they are retrieved.
+ pkt->setCopyRetrievedOptions(true);
+ EXPECT_TRUE(pkt->isCopyRetrievedOptions());
+
+ // Option pointer returned must point to a new instance of option 2.
+ OptionPtr option2_copy = pkt->getOption(2);
+ EXPECT_FALSE(option2 == option2_copy);
+
+ // Disable copying.
+ pkt->setCopyRetrievedOptions(false);
+ EXPECT_FALSE(pkt->isCopyRetrievedOptions());
+
+ // Expect that the original pointer is returned. This guarantees that
+ // option1 wasn't affected by copying option 2.
+ OptionPtr option1_copy = pkt->getOption(1);
+ EXPECT_TRUE(option1 == option1_copy);
+
+ // Again, enable copying options.
+ pkt->setCopyRetrievedOptions(true);
+
+ // This time a pointer to new option instance should be returned.
+ option1_copy = pkt->getOption(1);
+ EXPECT_FALSE(option1 == option1_copy);
+}
+
+// This test verifies that the options are unpacked from the packet correctly.
+TEST_F(Pkt4Test, unpackOptions) {
+
+ vector<uint8_t> expectedFormat = generateTestPacket2();
+
+ expectedFormat.push_back(0x63);
+ expectedFormat.push_back(0x82);
+ expectedFormat.push_back(0x53);
+ expectedFormat.push_back(0x63);
+
+ for (size_t i = 0; i < sizeof(v4_opts); i++) {
+ expectedFormat.push_back(v4_opts[i]);
+ }
+
+ // now expectedFormat contains fixed format and 5 options
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0],
+ expectedFormat.size()));
+
+ EXPECT_NO_THROW(
+ pkt->unpack()
+ );
+
+ verifyParsedOptions(pkt);
+}
+
+// Checks if the code is able to handle a malformed option
+TEST_F(Pkt4Test, unpackMalformed) {
+
+ vector<uint8_t> orig = generateTestPacket2();
+
+ orig.push_back(0x63);
+ orig.push_back(0x82);
+ orig.push_back(0x53);
+ orig.push_back(0x63);
+
+ orig.push_back(53); // Message Type
+ orig.push_back(1); // length=1
+ orig.push_back(2); // type=2
+
+ orig.push_back(12); // Hostname
+ orig.push_back(3); // length=3
+ orig.push_back(102); // data="foo"
+ orig.push_back(111);
+ orig.push_back(111);
+
+ // That's our original content. It should be sane.
+ Pkt4Ptr success(new Pkt4(&orig[0], orig.size()));
+ EXPECT_NO_THROW(success->unpack());
+
+ // With the exception of END and PAD an option must have a length byte
+ vector<uint8_t> nolength = orig;
+ nolength.resize(orig.size() - 4);
+ Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size()));
+ EXPECT_NO_THROW(no_length_pkt->unpack());
+
+ // The unpack() operation doesn't throw but there is no option 12
+ EXPECT_FALSE(no_length_pkt->getOption(12));
+
+ // Truncated data is not accepted too but doesn't throw
+ vector<uint8_t> shorty = orig;
+ shorty.resize(orig.size() - 1);
+ Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size()));
+ EXPECT_NO_THROW(too_short_pkt->unpack());
+
+ // The unpack() operation doesn't throw but there is no option 12
+ EXPECT_FALSE(no_length_pkt->getOption(12));
+}
+
+// Checks if the code is able to handle a malformed vendor option
+TEST_F(Pkt4Test, unpackVendorMalformed) {
+
+ vector<uint8_t> orig = generateTestPacket2();
+
+ orig.push_back(0x63);
+ orig.push_back(0x82);
+ orig.push_back(0x53);
+ orig.push_back(0x63);
+
+ orig.push_back(53); // Message Type
+ orig.push_back(1); // length=1
+ orig.push_back(2); // type=2
+
+ orig.push_back(125); // vivso suboptions
+ size_t full_len_index = orig.size();
+ orig.push_back(15); // length=15
+ orig.push_back(1); // vendor_id=0x1020304
+ orig.push_back(2);
+ orig.push_back(3);
+ orig.push_back(4);
+ size_t data_len_index = orig.size();
+ orig.push_back(10); // data-len=10
+ orig.push_back(128); // suboption type=128
+ orig.push_back(3); // suboption length=3
+ orig.push_back(102); // data="foo"
+ orig.push_back(111);
+ orig.push_back(111);
+ orig.push_back(129); // suboption type=129
+ orig.push_back(3); // suboption length=3
+ orig.push_back(99); // data="bar"
+ orig.push_back(98);
+ orig.push_back(114);
+
+ // That's our original content. It should be sane.
+ Pkt4Ptr success(new Pkt4(&orig[0], orig.size()));
+ EXPECT_NO_THROW(success->unpack());
+
+ // Data-len must match
+ vector<uint8_t> baddatalen = orig;
+ baddatalen.resize(orig.size() - 5);
+ baddatalen[full_len_index] = 10;
+ Pkt4Ptr bad_data_len_pkt(new Pkt4(&baddatalen[0], baddatalen.size()));
+ EXPECT_THROW(bad_data_len_pkt->unpack(), SkipRemainingOptionsError);
+
+ // A suboption must have a length byte
+ vector<uint8_t> nolength = orig;
+ nolength.resize(orig.size() - 4);
+ nolength[full_len_index] = 11;
+ nolength[data_len_index] = 6;
+ Pkt4Ptr no_length_pkt(new Pkt4(&nolength[0], nolength.size()));
+ EXPECT_THROW(no_length_pkt->unpack(), SkipRemainingOptionsError);
+
+ // Truncated data is not accepted either
+ vector<uint8_t> shorty = orig;
+ shorty.resize(orig.size() - 1);
+ shorty[full_len_index] = 14;
+ shorty[data_len_index] = 9;
+ Pkt4Ptr too_short_pkt(new Pkt4(&shorty[0], shorty.size()));
+ EXPECT_THROW(too_short_pkt->unpack(), SkipRemainingOptionsError);
+}
+
+// This test verifies methods that are used for manipulating meta fields
+// i.e. fields that are not part of DHCPv4 (e.g. interface name).
+TEST_F(Pkt4Test, metaFields) {
+
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ pkt->setIface("loooopback");
+ pkt->setIndex(42);
+ pkt->setRemoteAddr(IOAddress("1.2.3.4"));
+ pkt->setLocalAddr(IOAddress("4.3.2.1"));
+
+ EXPECT_EQ("loooopback", pkt->getIface());
+ EXPECT_EQ(42, pkt->getIndex());
+ EXPECT_EQ("1.2.3.4", pkt->getRemoteAddr().toText());
+ EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
+}
+
+TEST_F(Pkt4Test, Timestamp) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+
+ // Just after construction timestamp is invalid
+ ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time());
+
+ // Update packet time.
+ pkt->updateTimestamp();
+
+ // Get updated packet time.
+ boost::posix_time::ptime ts_packet = pkt->getTimestamp();
+
+ // After timestamp is updated it should be date-time.
+ ASSERT_FALSE(ts_packet.is_not_a_date_time());
+
+ // Check current time.
+ boost::posix_time::ptime ts_now =
+ boost::posix_time::microsec_clock::universal_time();
+
+ // Calculate period between packet time and now.
+ boost::posix_time::time_period ts_period(ts_packet, ts_now);
+
+ // Duration should be positive or zero.
+ EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
+}
+
+TEST_F(Pkt4Test, hwaddr) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+
+ HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+
+ // setting NULL hardware address is not allowed
+ EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue);
+
+ pkt->setHWAddr(hwaddr);
+
+ EXPECT_EQ(hw_type, pkt->getHtype());
+
+ EXPECT_EQ(sizeof(hw), pkt->getHlen());
+
+ EXPECT_TRUE(hwaddr == pkt->getHWAddr());
+}
+
+// This test verifies that the packet remote and local HW address can
+// be set and returned.
+TEST_F(Pkt4Test, hwaddrSrcRemote) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
+ const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };
+ const uint8_t hw_type = 123;
+
+ HWAddrPtr dst_hwaddr(new HWAddr(dst_hw, sizeof(src_hw), hw_type));
+ HWAddrPtr src_hwaddr(new HWAddr(src_hw, sizeof(src_hw), hw_type));
+
+ // Check that we can set the local address.
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(dst_hwaddr));
+ EXPECT_TRUE(dst_hwaddr == pkt->getLocalHWAddr());
+
+ // Check that we can set the remote address.
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(src_hwaddr));
+ EXPECT_TRUE(src_hwaddr == pkt->getRemoteHWAddr());
+
+ // Can't set the NULL addres.
+ EXPECT_THROW(pkt->setRemoteHWAddr(HWAddrPtr()), BadValue);
+ EXPECT_THROW(pkt->setLocalHWAddr(HWAddrPtr()), BadValue);
+
+ // Test alternative way to set local address.
+ const uint8_t dst_hw2[] = { 19, 20, 21, 22, 23, 24 };
+ std::vector<uint8_t> dst_hw_vec(dst_hw2, dst_hw2 + sizeof(dst_hw2));
+ const uint8_t hw_type2 = 234;
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(hw_type2, sizeof(dst_hw2), dst_hw_vec));
+ HWAddrPtr local_addr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(local_addr);
+ EXPECT_EQ(hw_type2, local_addr->htype_);
+ EXPECT_TRUE(std::equal(dst_hw_vec.begin(), dst_hw_vec.end(),
+ local_addr->hwaddr_.begin()));
+
+ // Set remote address.
+ const uint8_t src_hw2[] = { 25, 26, 27, 28, 29, 30 };
+ std::vector<uint8_t> src_hw_vec(src_hw2, src_hw2 + sizeof(src_hw2));
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(hw_type2, sizeof(src_hw2), src_hw_vec));
+ HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(remote_addr);
+ EXPECT_EQ(hw_type2, remote_addr->htype_);
+ EXPECT_TRUE(std::equal(src_hw_vec.begin(), src_hw_vec.end(),
+ remote_addr->hwaddr_.begin()));
+}
+
+// This test verifies that the check for a message being relayed is correct.
+TEST_F(Pkt4Test, isRelayed) {
+ Pkt4 pkt(DHCPDISCOVER, 1234);
+ // By default, the hops and giaddr should be 0.
+ ASSERT_TRUE(pkt.getGiaddr().isV4Zero());
+ ASSERT_EQ(0, pkt.getHops());
+ // For zero giaddr the packet is non-relayed.
+ EXPECT_FALSE(pkt.isRelayed());
+ // Set giaddr but leave hops = 0.
+ pkt.setGiaddr(IOAddress("10.0.0.1"));
+ EXPECT_TRUE(pkt.isRelayed());
+ // After setting hops the message should still be relayed.
+ pkt.setHops(10);
+ EXPECT_TRUE(pkt.isRelayed());
+ // Set giaddr to 0. The message is now not-relayed.
+ pkt.setGiaddr(IOAddress(IOAddress::IPV4_ZERO_ADDRESS()));
+ EXPECT_FALSE(pkt.isRelayed());
+ // Setting the giaddr to 255.255.255.255 should not cause it to
+ // be relayed message.
+ pkt.setGiaddr(IOAddress(IOAddress::IPV4_BCAST_ADDRESS()));
+ EXPECT_FALSE(pkt.isRelayed());
+}
+
+// Tests whether a packet can be assigned to a class and later
+// checked if it belongs to a given class
+TEST_F(Pkt4Test, clientClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ EXPECT_TRUE(pkt.getClasses().empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ ASSERT_FALSE(pkt.getClasses().empty());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.inClass("foo"));
+}
+
+// Tests whether a packet can be marked to evaluate later a class and
+// after check if a given class is in the collection
+TEST_F(Pkt4Test, deferredClientClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_TRUE(pkt.getClasses(true).empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
+ EXPECT_EQ(1, pkt.getClasses(true).size());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM, true);
+ EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
+ EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
+}
+
+// Tests whether a packet can be assigned to a subclass and later
+// checked if it belongs to a given subclass
+TEST_F(Pkt4Test, templateClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any subclass)
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+ EXPECT_TRUE(pkt.getClasses().empty());
+
+ // Add to the first subclass
+ pkt.addSubClass("template-interface-name", "SPAWN_template-interface-name_eth0");
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+ ASSERT_FALSE(pkt.getClasses().empty());
+
+ // Add to a second subclass
+ pkt.addSubClass("template-interface-id", "SPAWN_template-interface-id_interface-id0");
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+
+ // Check that it's ok to add to the same subclass repeatedly
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-bar", "SPAWN_template-bar_bar"));
+
+ // Check that the packet belongs to 'SPAWN_template-foo_bar'
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-foo_bar"));
+
+ // Check that the packet belongs to 'SPAWN_template-bar_bar'
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-bar_bar"));
+}
+
+// Tests whether MAC can be obtained and that MAC sources are not
+// confused.
+TEST_F(Pkt4Test, getMAC) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // DHCPv4 packet by default doesn't have MAC address specified.
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+
+ // Let's invent a MAC
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+
+ // Now let's pretend that we obtained it from raw sockets
+ pkt.setRemoteHWAddr(dummy_hwaddr);
+
+ // Now we should be able to get something
+ ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+
+ // Check that the returned MAC is indeed the expected one
+ ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+}
+
+// Tests that getLabel/makeLabel methods produces the expected strings based on
+// packet content.
+TEST_F(Pkt4Test, getLabel) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Verify makeLabel() handles empty values
+ EXPECT_EQ ("[no hwaddr info], cid=[no info], tid=0x0",
+ Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr(), 0));
+
+ // Verify an "empty" packet label is as we expect
+ EXPECT_EQ ("[hwtype=1 ], cid=[no info], tid=0x4d2",
+ pkt.getLabel());
+
+ // Set that packet hardware address, then verify getLabel
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+ pkt.setHWAddr(dummy_hwaddr);
+
+ EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c],"
+ " cid=[no info], tid=0x4d2", pkt.getLabel());
+
+ // Add a client id to the packet then verify getLabel
+ OptionBuffer clnt_id(4);
+ for (uint8_t i = 0; i < 4; i++) {
+ clnt_id[i] = 100 + i;
+ }
+
+ OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ clnt_id.begin(), clnt_id.begin() + 4));
+ pkt.addOption(opt);
+
+ EXPECT_EQ ("[hwtype=123 02:04:06:08:0a:0c],"
+ " cid=[64:65:66:67], tid=0x4d2",
+ pkt.getLabel());
+
+}
+
+// Test that empty client identifier option doesn't cause an exception from
+// Pkt4::getLabel.
+TEST_F(Pkt4Test, getLabelEmptyClientId) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Create empty client identifier option.
+ OptionPtr empty_opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER));
+ pkt.addOption(empty_opt);
+
+ EXPECT_EQ("[hwtype=1 ], cid=[no info], tid=0x4d2"
+ " (malformed client-id)", pkt.getLabel());
+}
+
+// Tests that the variant of makeLabel which doesn't include transaction
+// id produces expected output.
+TEST_F(Pkt4Test, makeLabelWithoutTransactionId) {
+ EXPECT_EQ("[no hwaddr info], cid=[no info]",
+ Pkt4::makeLabel(HWAddrPtr(), ClientIdPtr()));
+
+ // Test non-null hardware address.
+ HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06", 123)));
+ EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[no info]",
+ Pkt4::makeLabel(hwaddr, ClientIdPtr()));
+
+ // Test non-null client identifier and non-null hardware address.
+ ClientIdPtr cid = ClientId::fromText("01:02:03:04");
+ EXPECT_EQ("[hwtype=123 01:02:03:04:05:06], cid=[01:02:03:04]",
+ Pkt4::makeLabel(hwaddr, cid));
+
+ // Test non-nnull client identifier and null hardware address.
+ EXPECT_EQ("[no hwaddr info], cid=[01:02:03:04]",
+ Pkt4::makeLabel(HWAddrPtr(), cid));
+}
+
+// Tests that the correct DHCPv4 message name is returned for various
+// message types.
+TEST_F(Pkt4Test, getName) {
+ // Check all possible packet types
+ for (int itype = 0; itype < 256; ++itype) {
+ uint8_t type = itype;
+
+ switch (type) {
+ case DHCPDISCOVER:
+ EXPECT_STREQ("DHCPDISCOVER", Pkt4::getName(type));
+ break;
+
+ case DHCPOFFER:
+ EXPECT_STREQ("DHCPOFFER", Pkt4::getName(type));
+ break;
+
+ case DHCPREQUEST:
+ EXPECT_STREQ("DHCPREQUEST", Pkt4::getName(type));
+ break;
+
+ case DHCPDECLINE:
+ EXPECT_STREQ("DHCPDECLINE", Pkt4::getName(type));
+ break;
+
+ case DHCPACK:
+ EXPECT_STREQ("DHCPACK", Pkt4::getName(type));
+ break;
+
+ case DHCPNAK:
+ EXPECT_STREQ("DHCPNAK", Pkt4::getName(type));
+ break;
+
+ case DHCPRELEASE:
+ EXPECT_STREQ("DHCPRELEASE", Pkt4::getName(type));
+ break;
+
+ case DHCPINFORM:
+ EXPECT_STREQ("DHCPINFORM", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEQUERY:
+ EXPECT_STREQ("DHCPLEASEQUERY", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEUNASSIGNED:
+ EXPECT_STREQ("DHCPLEASEUNASSIGNED", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEUNKNOWN:
+ EXPECT_STREQ("DHCPLEASEUNKNOWN", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEACTIVE:
+ EXPECT_STREQ("DHCPLEASEACTIVE", Pkt4::getName(type));
+ break;
+
+ case DHCPBULKLEASEQUERY:
+ EXPECT_STREQ("DHCPBULKLEASEQUERY", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEQUERYDONE:
+ EXPECT_STREQ("DHCPLEASEQUERYDONE", Pkt4::getName(type));
+ break;
+
+ case DHCPLEASEQUERYSTATUS:
+ EXPECT_STREQ("DHCPLEASEQUERYSTATUS", Pkt4::getName(type));
+ break;
+
+ case DHCPTLS:
+ EXPECT_STREQ("DHCPTLS", Pkt4::getName(type));
+ break;
+
+ default:
+ EXPECT_STREQ("UNKNOWN", Pkt4::getName(type));
+ }
+ }
+}
+
+// This test checks that the packet data are correctly converted to the
+// textual format.
+TEST_F(Pkt4Test, toText) {
+ Pkt4 pkt(DHCPDISCOVER, 2543);
+ pkt.setLocalAddr(IOAddress("192.0.2.34"));
+ pkt.setRemoteAddr(IOAddress("192.10.33.4"));
+
+ pkt.addOption(OptionPtr(new Option4AddrLst(123, IOAddress("192.0.2.3"))));
+ pkt.addOption(OptionPtr(new OptionUint32(Option::V4, 156, 123456)));
+ pkt.addOption(OptionPtr(new OptionString(Option::V4, 87, "lorem ipsum")));
+
+ EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, "
+ "msg_type=DHCPDISCOVER (1), transid=0x9ef,\n"
+ "options:\n"
+ " type=053, len=001: 1 (uint8)\n"
+ " type=087, len=011: \"lorem ipsum\" (string)\n"
+ " type=123, len=004: 192.0.2.3\n"
+ " type=156, len=004: 123456 (uint32)",
+ pkt.toText());
+
+ // Now remove all options, including Message Type and check if the
+ // information about lack of any options is displayed properly.
+ pkt.delOption(123);
+ pkt.delOption(156);
+ pkt.delOption(87);
+ pkt.delOption(53);
+
+ EXPECT_EQ("local_address=192.0.2.34:67, remote_address=192.10.33.4:68, "
+ "msg_type=(missing), transid=0x9ef, "
+ "message contains no options",
+ pkt.toText());
+
+}
+
+// Sanity check. Verifies that the getName() and getType()
+// don't throw.
+TEST_F(Pkt4Test, getType) {
+
+ Pkt4 pkt(DHCPDISCOVER, 2543);
+ pkt.delOption(DHO_DHCP_MESSAGE_TYPE);
+
+ ASSERT_NO_THROW(pkt.getType());
+ ASSERT_NO_THROW(pkt.getName());
+
+ // The method has to return something that is not NULL,
+ // even if the packet doesn't have Message Type option.
+ EXPECT_TRUE(pkt.getName());
+}
+
+// Verifies that when the VIVSO option 125 has length that is too
+// short (i.e. less than sizeof(uint8_t), unpack throws a
+// SkipRemainingOptionsError exception
+TEST_F(Pkt4Test, truncatedVendorLength) {
+
+ // Build a good discover packet
+ Pkt4Ptr pkt = dhcp::test::PktCaptures::discoverWithValidVIVSO();
+
+ // Unpacking should not throw
+ ASSERT_NO_THROW(pkt->unpack());
+ ASSERT_EQ(DHCPDISCOVER, pkt->getType());
+
+ // VIVSO option should be there
+ OptionPtr x = pkt->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_TRUE(x);
+ ASSERT_EQ(DHO_VIVSO_SUBOPTIONS, x->getType());
+ OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x);
+ ASSERT_TRUE(vivso);
+ EXPECT_EQ(133+2, vivso->len()); // data + opt code + len
+
+ // Build a bad discover packet
+ pkt = dhcp::test::PktCaptures::discoverWithTruncatedVIVSO();
+
+ // Unpack should throw Skip exception
+ ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError);
+ ASSERT_EQ(DHCPDISCOVER, pkt->getType());
+
+ // VIVSO option should not be there
+ x = pkt->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_FALSE(x);
+}
+
+// Verifies that we handle text options that contain trailing
+// and embedded NULLs correctly. Per RFC 2132, Sec 2 we should
+// be stripping trailing NULLs. We've agreed to permit
+// embedded NULLs (for now).
+TEST_F(Pkt4Test, nullTerminatedOptions) {
+ // Construct the onwire packet.
+ vector<uint8_t> base_msg = generateTestPacket2();
+ base_msg.push_back(0x63); // magic cookie
+ base_msg.push_back(0x82);
+ base_msg.push_back(0x53);
+ base_msg.push_back(0x63);
+
+ base_msg.push_back(0x35); // message-type
+ base_msg.push_back(0x1);
+ base_msg.push_back(0x1);
+
+ int base_size = base_msg.size();
+
+ // We'll create four text options, with various combinations of NULLs.
+ vector<uint8_t> hostname = { DHO_HOST_NAME, 5, 't', 'w', 'o', 0, 0 };
+ vector<uint8_t> merit_dump = { DHO_MERIT_DUMP, 4, 'o', 'n', 'e', 0 };
+ vector<uint8_t> root_path = { DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e' };
+ vector<uint8_t> domain_name = { DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd' };
+
+ // Add the options to the onwire packet.
+ vector<uint8_t> test_msg = base_msg;
+ test_msg.insert(test_msg.end(), hostname.begin(), hostname.end());
+ test_msg.insert(test_msg.end(), root_path.begin(), root_path.end());
+ test_msg.insert(test_msg.end(), merit_dump.begin(), merit_dump.end());
+ test_msg.insert(test_msg.end(), domain_name.begin(), domain_name.end());
+ test_msg.push_back(DHO_END);
+
+ boost::shared_ptr<Pkt4> pkt(new Pkt4(&test_msg[0], test_msg.size()));
+
+ // Unpack the onwire packet.
+ EXPECT_NO_THROW(
+ pkt->unpack()
+ );
+
+ EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+
+ OptionPtr opt;
+ OptionStringPtr opstr;
+
+ // Now let's verify that each text option is as expected.
+ ASSERT_TRUE(opt = pkt->getOption(DHO_HOST_NAME));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("two", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_MERIT_DUMP));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("one", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_ROOT_PATH));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(4, opstr->getValue().length());
+ EXPECT_EQ("none", opstr->getValue());
+
+ ASSERT_TRUE(opt = pkt->getOption(DHO_DOMAIN_NAME));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(6, opstr->getValue().length());
+ std::string embed{"em\0bed", 6};
+ EXPECT_EQ(embed, opstr->getValue());
+
+
+ // Next we pack the packet, to make sure trailing NULLs have
+ // been eliminated, embedded NULLs are intact.
+ EXPECT_NO_THROW(
+ pkt->pack()
+ );
+
+ // Create a vector of our expected packed option data.
+ vector<uint8_t> packed_opts =
+ {
+ DHO_HOST_NAME, 3, 't', 'w', 'o',
+ DHO_MERIT_DUMP, 3, 'o', 'n', 'e',
+ DHO_DOMAIN_NAME, 6, 'e', 'm', 0, 'b', 'e', 'd',
+ DHO_ROOT_PATH, 4, 'n', 'o', 'n', 'e',
+ };
+
+ const uint8_t* packed = static_cast<const uint8_t*>(pkt->getBuffer().getData());
+ int packed_len = pkt->getBuffer().getLength();
+
+ // Packed message options should be 3 bytes smaller than original onwire data.
+ int dif = packed_len - test_msg.size();
+ ASSERT_EQ(-3, dif);
+
+ // Make sure the packed content is as expected.
+ EXPECT_EQ(0, memcmp(&packed[base_size], &packed_opts[0], packed_opts.size()));
+}
+
+// Checks that unpacking correctly handles SkipThisOptionError by
+// omitting the offending option from the unpacked options.
+TEST_F(Pkt4Test, testSkipThisOptionError) {
+ vector<uint8_t> orig = generateTestPacket2();
+
+ orig.push_back(0x63);
+ orig.push_back(0x82);
+ orig.push_back(0x53);
+ orig.push_back(0x63);
+
+ orig.push_back(53); // Message Type
+ orig.push_back(1); // length=1
+ orig.push_back(2); // type=2
+
+ orig.push_back(14); // merit-dump
+ orig.push_back(3); // length=3
+ orig.push_back(0x61); // data="abc"
+ orig.push_back(0x62);
+ orig.push_back(0x63);
+
+ orig.push_back(12); // Hostname
+ orig.push_back(3); // length=3
+ orig.push_back(0); // data= all nulls
+ orig.push_back(0);
+ orig.push_back(0);
+
+ orig.push_back(17); // root-path
+ orig.push_back(3); // length=3
+ orig.push_back(0x64); // data="def"
+ orig.push_back(0x65);
+ orig.push_back(0x66);
+
+ // Unpacking should not throw.
+ Pkt4Ptr pkt(new Pkt4(&orig[0], orig.size()));
+ ASSERT_NO_THROW_LOG(pkt->unpack());
+
+ // We should have option 14 = "abc".
+ OptionPtr opt;
+ OptionStringPtr opstr;
+ ASSERT_TRUE(opt = pkt->getOption(14));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("abc", opstr->getValue());
+
+ // We should not have option 12.
+ EXPECT_FALSE(opt = pkt->getOption(12));
+
+ // We should have option 17 = "def".
+ ASSERT_TRUE(opt = pkt->getOption(17));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("def", opstr->getValue());
+}
+
+// Tests that getHWAddrLabel method produces the expected strings based on
+// packet content.
+TEST_F(Pkt4Test, getHWAddrLabel) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Verify getHWAddrLabel() handles empty values
+ EXPECT_EQ ("hwaddr=", pkt.getHWAddrLabel());
+
+ // Testing undefined hwaddr case is not possible
+ EXPECT_THROW(pkt.setHWAddr(nullptr), BadValue);
+
+ // Set that packet hardware address, then verify getLabel
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+ pkt.setHWAddr(dummy_hwaddr);
+
+ EXPECT_EQ ("hwaddr=02:04:06:08:0a:0c", pkt.getHWAddrLabel());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt4o6_unittest.cc b/src/lib/dhcp/tests/pkt4o6_unittest.cc
new file mode 100644
index 0000000..9cbdf19
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt4o6_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright (C) 2015-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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/pkt4o6.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief A Fixture class dedicated to testing of the Pkt4o6 class that
+/// represents a DHCPv4-over-DHCPv6 packet.
+class Pkt4o6Test : public ::testing::Test {
+protected:
+ Pkt4o6Test() :
+ data6_(6, 0),
+ pkt6_(new Pkt6(&data6_[0], data6_.size())),
+ pkt4_(new Pkt4(DHCPDISCOVER, 0x12345678))
+ {
+ pkt4_->pack();
+ const uint8_t* cp = static_cast<const uint8_t*>(
+ pkt4_->getBuffer().getData());
+ buffer4_.assign(cp, cp + pkt4_->getBuffer().getLength());
+ }
+
+protected:
+ // commonly used test data
+ const std::vector<uint8_t> data6_; // data for Pkt6 (content unimportant)
+ Pkt6Ptr pkt6_; // DHCPv6 message for 4o6
+ Pkt4Ptr pkt4_; // DHCPv4 message for 4o6
+ OptionBuffer buffer4_; // wire-format data buffer of pkt4_
+};
+
+// This test verifies that the constructors are working as expected.
+TEST_F(Pkt4o6Test, construct) {
+ // Construct 4o6 packet, unpack the data to examine it
+ boost::scoped_ptr<Pkt4o6> pkt4o6(new Pkt4o6(buffer4_, pkt6_));
+ pkt4o6->unpack();
+ // Inspect its internal to confirm it's built as expected. We also test
+ // isDhcp4o6() here.
+ EXPECT_TRUE(pkt4o6->isDhcp4o6());
+ EXPECT_EQ(pkt6_, pkt4o6->getPkt6());
+ EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType());
+
+ // Same check for the other constructor. It relies on the internal
+ // behavior of Pkt4's copy constructor, so we need to first unpack pkt4.
+ pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size()));
+ pkt4_->unpack();
+ pkt4o6.reset(new Pkt4o6(pkt4_, pkt6_));
+ EXPECT_TRUE(pkt4o6->isDhcp4o6());
+ EXPECT_EQ(pkt6_, pkt4o6->getPkt6());
+ EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType());
+}
+
+// This test verifies that the pack() method handles the building
+// process correctly.
+TEST_F(Pkt4o6Test, pack) {
+ // prepare unpacked DHCPv4 packet (see the note in constructor test)
+ pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size()));
+ pkt4_->unpack();
+
+ // Construct 4o6 packet to be tested and pack the data.
+ Pkt4o6 pkt4o6(pkt4_, pkt6_);
+ pkt4o6.pack();
+
+ // The packed data should be:
+ // 4-byte DHCPv6 message header
+ // 4-byte header part of DHCPv4 message option
+ // Raw DHCPv4 message (data stored in buffer4_)
+ EXPECT_EQ(4 + 4 + buffer4_.size(),
+ pkt4o6.getPkt6()->getBuffer().getLength());
+
+ // Check the DHCPv4 message option content (Pkt4o6 class is not responsible
+ // for making it valid, so we won't examine it)
+ const uint8_t* cp = static_cast<const uint8_t*>(
+ pkt4o6.getPkt6()->getBuffer().getData());
+ EXPECT_EQ(0, cp[4]);
+ EXPECT_EQ(D6O_DHCPV4_MSG, cp[5]);
+ EXPECT_EQ((buffer4_.size() >> 8) & 0xff, cp[6]);
+ EXPECT_EQ(buffer4_.size() & 0xff, cp[7]);
+ EXPECT_EQ(0, memcmp(&cp[8], &buffer4_[0], buffer4_.size()));
+}
+
+// This test verifies that the flag indicating that the retrieved options
+// should be copied is transferred between the DHCPv4 packet and the
+// DHCPv6 packet being a member of Pkt4o6 class.
+TEST_F(Pkt4o6Test, setCopyRetrievedOptions) {
+ // Create Pkt4o6 and initially expect that the flag is set to false.
+ Pkt4o6 pkt4o6(pkt4_, pkt6_);
+ ASSERT_FALSE(pkt4o6.isCopyRetrievedOptions());
+ Pkt6Ptr pkt6 = pkt4o6.getPkt6();
+ ASSERT_TRUE(pkt6);
+ ASSERT_FALSE(pkt6->isCopyRetrievedOptions());
+
+ // Set the flag to true for Pkt4o6.
+ pkt4o6.setCopyRetrievedOptions(true);
+ pkt6 = pkt4o6.getPkt6();
+ ASSERT_TRUE(pkt6);
+ EXPECT_TRUE(pkt6->isCopyRetrievedOptions());
+
+ // Repeat the same test but set the flag to false.
+ pkt4o6.setCopyRetrievedOptions(false);
+ EXPECT_FALSE(pkt4o6.isCopyRetrievedOptions());
+ pkt6 = pkt4o6.getPkt6();
+ ASSERT_TRUE(pkt6);
+ EXPECT_FALSE(pkt6->isCopyRetrievedOptions());
+}
+
+/// @todo: Add a test that handles actual DHCP4o6 traffic capture
+/// once we get it. We should add the capture to pkt_captures{4,6}.cc
+}
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
new file mode 100644
index 0000000..616b894
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -0,0 +1,2373 @@
+// Copyright (C) 2011-2022 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <testutils/gtest_utils.h>
+#include <util/range_utilities.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/pointer_cast.hpp>
+#include <util/encode/hex.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <utility>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using boost::scoped_ptr;
+
+namespace {
+
+class NakedPkt6 : public Pkt6 {
+public:
+
+ /// @brief Constructor, used in replying to a message
+ ///
+ /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
+ /// @param transid transaction-id
+ /// @param proto protocol (TCP or UDP)
+ NakedPkt6(const uint8_t msg_type, const uint32_t transid,
+ const DHCPv6Proto& proto = UDP)
+ : Pkt6(msg_type, transid, proto) {
+ }
+
+ /// @brief Constructor, used in message transmission
+ ///
+ /// Creates new message. Transaction-id will randomized.
+ ///
+ /// @param buf pointer to a buffer of received packet content
+ /// @param len size of buffer of received packet content
+ /// @param proto protocol (usually UDP, but TCP will be supported eventually)
+ NakedPkt6(const uint8_t* buf, const uint32_t len,
+ const DHCPv6Proto& proto = UDP)
+ : Pkt6(buf, len, proto) {
+ }
+
+ using Pkt::getNonCopiedOptions;
+ using Pkt6::getNonCopiedRelayOption;
+ using Pkt6::getNonCopiedRelayOptions;
+ using Pkt6::getNonCopiedAnyRelayOption;
+ using Pkt6::getNonCopiedAllRelayOptions;
+};
+
+typedef boost::shared_ptr<NakedPkt6> NakedPkt6Ptr;
+
+class Pkt6Test : public ::testing::Test {
+public:
+ Pkt6Test() {
+ }
+
+ /// @brief generates an option with given code (and length) and
+ /// random content
+ ///
+ /// @param code option code
+ /// @param len data length (data will be randomized)
+ ///
+ /// @return pointer to the new option
+ OptionPtr generateRandomOption(uint16_t code, size_t len = 10) {
+ OptionBuffer data(len);
+ util::fillRandom(data.begin(), data.end());
+ return OptionPtr(new Option(Option::V6, code, data));
+ }
+
+ /// @brief Create a wire representation of the test packet and clone it.
+ ///
+ /// The purpose of this function is to create a packet to be used to
+ /// check that packet parsing works correctly. The unpack() function
+ /// requires that the data_ field of the object holds the data to be
+ /// parsed. This function creates an on-wire representation of the
+ /// packet by calling pack(). But, the pack() function stores the
+ /// on-wire representation into the output buffer (not the data_ field).
+ /// For this reason, it is not enough to return the packet on which
+ /// pack() is called. This function returns a clone of this packet
+ /// which is created using a constructor taking a buffer and buffer
+ /// length as an input. This constructor is normally used to parse
+ /// received packets. It stores the packet in a data_ field and
+ /// therefore unpack() can be called to parse it.
+ ///
+ /// @param parent Packet from which the new packet should be created.
+ Pkt6Ptr packAndClone(Pkt6Ptr& parent) {
+ OptionPtr opt1(new Option(Option::V6, 1));
+ OptionPtr opt2(new Option(Option::V6, 2));
+ OptionPtr opt3(new Option(Option::V6, 100));
+ // Let's not use zero-length option type 3 as it is IA_NA
+
+ parent->addOption(opt1);
+ parent->addOption(opt2);
+ parent->addOption(opt3);
+
+ EXPECT_NO_THROW(parent->pack());
+
+ // Create second packet,based on assembled data from the first one
+ Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*>
+ (parent->getBuffer().getData()),
+ parent->getBuffer().getLength()));
+ return (clone);
+
+ }
+};
+
+TEST_F(Pkt6Test, constructor) {
+ uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
+ scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data)));
+
+ EXPECT_EQ(6, pkt1->data_.size());
+ EXPECT_EQ(0, memcmp( &pkt1->data_[0], data, sizeof(data)));
+}
+
+/// @brief returns captured actual SOLICIT packet
+///
+/// Captured SOLICIT packet with transid=0x3d79fb and options: client-id,
+/// in_na, dns-server, elapsed-time, option-request
+/// This code was autogenerated (see src/bin/dhcp6/tests/iface_mgr_unittest.c),
+/// but we spent some time to make is less ugly than it used to be.
+///
+/// @return pointer to Pkt6 that represents received SOLICIT
+Pkt6Ptr capture1() {
+ uint8_t data[98];
+ data[0] = 1;
+ data[1] = 1; data[2] = 2; data[3] = 3; data[4] = 0;
+ data[5] = 1; data[6] = 0; data[7] = 14; data[8] = 0;
+ data[9] = 1; data[10] = 0; data[11] = 1; data[12] = 21;
+ data[13] = 158; data[14] = 60; data[15] = 22; data[16] = 0;
+ data[17] = 30; data[18] = 140; data[19] = 155; data[20] = 115;
+ data[21] = 73; data[22] = 0; data[23] = 3; data[24] = 0;
+ data[25] = 40; data[26] = 0; data[27] = 0; data[28] = 0;
+ data[29] = 1; data[30] = 255; data[31] = 255; data[32] = 255;
+ data[33] = 255; data[34] = 255; data[35] = 255; data[36] = 255;
+ data[37] = 255; data[38] = 0; data[39] = 5; data[40] = 0;
+ data[41] = 24; data[42] = 32; data[43] = 1; data[44] = 13;
+ data[45] = 184; data[46] = 0; data[47] = 1; data[48] = 0;
+ data[49] = 0; data[50] = 0; data[51] = 0; data[52] = 0;
+ data[53] = 0; data[54] = 0; data[55] = 0; data[56] = 18;
+ data[57] = 52; data[58] = 255; data[59] = 255; data[60] = 255;
+ data[61] = 255; data[62] = 255; data[63] = 255; data[64] = 255;
+ data[65] = 255; data[66] = 0; data[67] = 23; data[68] = 0;
+ data[69] = 16; data[70] = 32; data[71] = 1; data[72] = 13;
+ data[73] = 184; data[74] = 0; data[75] = 1; data[76] = 0;
+ data[77] = 0; data[78] = 0; data[79] = 0; data[80] = 0;
+ data[81] = 0; data[82] = 0; data[83] = 0; data[84] = 221;
+ data[85] = 221; data[86] = 0; data[87] = 8; data[88] = 0;
+ data[89] = 2; data[90] = 0; data[91] = 100; data[92] = 0;
+ data[93] = 6; data[94] = 0; data[95] = 2; data[96] = 0;
+ data[97] = 23;
+
+ Pkt6Ptr pkt(new Pkt6(data, sizeof(data)));
+ pkt->setRemotePort(546);
+ pkt->setRemoteAddr(IOAddress("fe80::21e:8cff:fe9b:7349"));
+ pkt->setLocalPort(0);
+ pkt->setLocalAddr(IOAddress("ff02::1:2"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+
+ return (pkt);
+}
+
+/// @brief creates doubly relayed solicit message
+///
+/// This is a traffic capture exported from wireshark. It includes a SOLICIT
+/// message that passed through two relays. Each relay include interface-id,
+/// remote-id and relay-forw encapsulation. It is especially interesting,
+/// because of the following properties:
+/// - double encapsulation
+/// - first relay inserts relay-msg before extra options
+/// - second relay inserts relay-msg after extra options
+/// - both relays are from different vendors
+/// - interface-id are different for each relay
+/// - first relay inserts valid remote-id
+/// - second relay inserts remote-id with empty vendor data
+/// - the solicit message requests for custom options in ORO
+/// - there are option types in RELAY-FORW that do not appear in SOLICIT
+/// - there are option types in SOLICT that do not appear in RELAY-FORW
+///
+/// RELAY-FORW
+/// - relay message option
+/// - RELAY-FORW
+/// - interface-id option
+/// - remote-id option
+/// - RELAY-FORW
+/// SOLICIT
+/// - client-id option
+/// - ia_na option
+/// - elapsed time
+/// - ORO
+/// - interface-id option
+/// - remote-id option
+///
+/// The original capture was posted to dibbler users mailing list.
+///
+/// @return created double relayed SOLICIT message
+Pkt6Ptr capture2() {
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c01200108880db800010000000000000000fe80000000000000020021fffe5c"
+ "18a90009007d0c0000000000000000000000000000000000fe80000000000000"
+ "020021fffe5c18a9001200154953414d3134342065746820312f312f30352f30"
+ "310025000400000de900090036016b4fe20001000e0001000118b03341000021"
+ "5c18a90003000c00000001ffffffffffffffff00080002000000060006001700"
+ "f200f30012001c4953414d3134347c3239397c697076367c6e743a76703a313a"
+ "313130002500120000197f0001000118b033410000215c18a9";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ NakedPkt6Ptr pkt(new NakedPkt6(&bin[0], bin.size()));
+ pkt->setRemotePort(547);
+ pkt->setRemoteAddr(IOAddress("fe80::1234"));
+ pkt->setLocalPort(547);
+ pkt->setLocalAddr(IOAddress("ff05::1:3"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+ return (boost::dynamic_pointer_cast<Pkt6>(pkt));
+}
+
+TEST_F(Pkt6Test, unpack_solicit1) {
+ Pkt6Ptr sol(capture1());
+
+ ASSERT_NO_THROW(sol->unpack());
+
+ // Check for length
+ EXPECT_EQ(98, sol->len() );
+
+ // Check for type
+ EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() );
+
+ // Check that all present options are returned
+ EXPECT_TRUE(sol->getOption(D6O_CLIENTID)); // client-id is present
+ EXPECT_TRUE(sol->getOption(D6O_IA_NA)); // IA_NA is present
+ EXPECT_TRUE(sol->getOption(D6O_ELAPSED_TIME)); // elapsed is present
+ EXPECT_TRUE(sol->getOption(D6O_NAME_SERVERS));
+ EXPECT_TRUE(sol->getOption(D6O_ORO));
+
+ // Let's check that non-present options are not returned
+ EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing
+ EXPECT_FALSE(sol->getOption(D6O_IA_TA));
+ EXPECT_FALSE(sol->getOption(D6O_IAADDR));
+}
+
+TEST_F(Pkt6Test, packUnpack) {
+ // Create an on-wire representation of the test packet and clone it.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
+ Pkt6Ptr clone = packAndClone(pkt);
+
+ // Now recreate options list
+ ASSERT_NO_THROW(clone->unpack());
+
+ // transid, message-type should be the same as before
+ EXPECT_EQ(0x020304, clone->getTransid());
+ EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
+
+ EXPECT_TRUE(clone->getOption(1));
+ EXPECT_TRUE(clone->getOption(2));
+ EXPECT_TRUE(clone->getOption(100));
+ EXPECT_FALSE(clone->getOption(4));
+}
+
+// Checks if the code is able to handle malformed packet
+TEST_F(Pkt6Test, unpackMalformed) {
+ // Get a packet. We're really interested in its on-wire
+ // representation only.
+ Pkt6Ptr donor(capture1());
+
+ // That's our original content. It should be sane.
+ OptionBuffer orig = donor->data_;
+
+ Pkt6Ptr success(new Pkt6(&orig[0], orig.size()));
+ EXPECT_NO_THROW(success->unpack());
+
+ // Insert trailing garbage.
+ OptionBuffer malform1 = orig;
+ malform1.push_back(123);
+
+ // Let's check a truncated packet. Moderately sane DHCPv6 packet should at
+ // least have four bytes header. Zero bytes is definitely not a valid one.
+ OptionBuffer empty(1); // Let's allocate one byte, so we won't be
+ // dereferencing an empty buffer.
+
+ Pkt6Ptr empty_pkt(new Pkt6(&empty[0], 0));
+ EXPECT_THROW(empty_pkt->unpack(), isc::BadValue);
+
+ // Neither is 3 bytes long.
+ OptionBuffer shorty;
+ shorty.push_back(DHCPV6_SOLICIT);
+ shorty.push_back(1);
+ shorty.push_back(2);
+ Pkt6Ptr too_short_pkt(new Pkt6(&shorty[0], shorty.size()));
+ EXPECT_THROW(too_short_pkt->unpack(), isc::BadValue);
+
+ // The code should complain about remaining bytes that can't be parsed
+ // but doesn't do so yet.
+ Pkt6Ptr trailing_garbage(new Pkt6(&malform1[0], malform1.size()));
+ EXPECT_NO_THROW(trailing_garbage->unpack());
+
+ // A strict approach would assume the code will reject the whole packet,
+ // but we decided to follow Jon Postel's law and be silent about
+ // received malformed or truncated options.
+
+ // Add an option that is truncated
+ OptionBuffer malform2 = orig;
+ malform2.push_back(0);
+ malform2.push_back(123); // 0, 123 - option code = 123
+ malform2.push_back(0);
+ malform2.push_back(1); // 0, 1 - option length = 1
+ // Option content would go here, but it's missing
+
+ Pkt6Ptr trunc_option(new Pkt6(&malform2[0], malform2.size()));
+
+ // The unpack() operation should succeed...
+ EXPECT_NO_THROW(trunc_option->unpack());
+
+ // ... but there should be no option 123 as it was malformed.
+ EXPECT_FALSE(trunc_option->getOption(123));
+
+ // Check with truncated length field
+ Pkt6Ptr trunc_length(new Pkt6(&malform2[0], malform2.size() - 1));
+ EXPECT_NO_THROW(trunc_length->unpack());
+ EXPECT_FALSE(trunc_length->getOption(123));
+
+ // Check with missing length field
+ Pkt6Ptr no_length(new Pkt6(&malform2[0], malform2.size() - 2));
+ EXPECT_NO_THROW(no_length->unpack());
+ EXPECT_FALSE(no_length->getOption(123));
+
+ // Check with truncated type field
+ Pkt6Ptr trunc_type(new Pkt6(&malform2[0], malform2.size() - 3));
+ EXPECT_NO_THROW(trunc_type->unpack());
+ EXPECT_FALSE(trunc_type->getOption(123));
+}
+
+// Checks if the code is able to handle a malformed vendor option
+TEST_F(Pkt6Test, unpackVendorMalformed) {
+ // Get a packet. We're really interested in its on-wire
+ // representation only.
+ Pkt6Ptr donor(capture1());
+
+ // Add a vendor option
+ OptionBuffer orig = donor->data_;
+
+ orig.push_back(0); // vendor options
+ orig.push_back(17);
+ orig.push_back(0);
+ size_t len_index = orig.size();
+ orig.push_back(18); // length=18
+ orig.push_back(1); // vendor_id=0x1020304
+ orig.push_back(2);
+ orig.push_back(3);
+ orig.push_back(4);
+ orig.push_back(1); // suboption type=0x101
+ orig.push_back(1);
+ orig.push_back(0); // suboption length=3
+ orig.push_back(3);
+ orig.push_back(102); // data="foo"
+ orig.push_back(111);
+ orig.push_back(111);
+ orig.push_back(1); // suboption type=0x102
+ orig.push_back(2);
+ orig.push_back(0); // suboption length=3
+ orig.push_back(3);
+ orig.push_back(99); // data="bar'
+ orig.push_back(98);
+ orig.push_back(114);
+
+ Pkt6Ptr success(new Pkt6(&orig[0], orig.size()));
+ EXPECT_NO_THROW(success->unpack());
+
+ // Truncated vendor option is not accepted but doesn't throw
+ vector<uint8_t> shortv = orig;
+ shortv[len_index] = 20;
+ Pkt6Ptr too_short_vendor_pkt(new Pkt6(&shortv[0], shortv.size()));
+ EXPECT_NO_THROW(too_short_vendor_pkt->unpack());
+
+ // Truncated option header is not accepted
+ vector<uint8_t> shorth = orig;
+ shorth.resize(orig.size() - 4);
+ shorth[len_index] = 12;
+ Pkt6Ptr too_short_header_pkt(new Pkt6(&shorth[0], shorth.size()));
+ EXPECT_THROW(too_short_header_pkt->unpack(), SkipRemainingOptionsError);
+
+ // Truncated option data is not accepted
+ vector<uint8_t> shorto = orig;
+ shorto.resize(orig.size() - 2);
+ shorto[len_index] = 16;
+ Pkt6Ptr too_short_option_pkt(new Pkt6(&shorto[0], shorto.size()));
+ EXPECT_THROW(too_short_option_pkt->unpack(), SkipRemainingOptionsError);
+}
+
+// This test verifies that options can be added (addOption()), retrieved
+// (getOption(), getOptions()) and deleted (delOption()).
+TEST_F(Pkt6Test, addGetDelOptions) {
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, random()));
+
+ OptionPtr opt1(new Option(Option::V6, 1));
+ OptionPtr opt2(new Option(Option::V6, 2));
+ OptionPtr opt3(new Option(Option::V6, 2));
+
+ parent->addOption(opt1);
+ parent->addOption(opt2);
+
+ // getOption() test
+ EXPECT_EQ(opt1, parent->getOption(1));
+ EXPECT_EQ(opt2, parent->getOption(2));
+
+ // Expect NULL
+ EXPECT_EQ(OptionPtr(), parent->getOption(4));
+
+ // Now there are 2 options of type 2
+ parent->addOption(opt3);
+
+ OptionCollection options = parent->getOptions(2);
+ EXPECT_EQ(2, options.size()); // there should be 2 instances
+
+ // Both options must be of type 2 and there must not be
+ // any other type returned
+ for (OptionCollection::const_iterator x= options.begin();
+ x != options.end(); ++x) {
+ EXPECT_EQ(2, x->second->getType());
+ }
+
+ // Try to get a single option. Normally for singular options
+ // it is better to use getOption(), but getOptions() must work
+ // as well
+ options = parent->getOptions(1);
+ ASSERT_EQ(1, options.size());
+
+ EXPECT_EQ(1, (*options.begin()).second->getType());
+ EXPECT_EQ(opt1, options.begin()->second);
+
+ // Let's delete one of them
+ EXPECT_EQ(true, parent->delOption(2));
+
+ // There still should be the other option 2
+ EXPECT_NE(OptionPtr(), parent->getOption(2));
+
+ // Let's delete the other option 2
+ EXPECT_EQ(true, parent->delOption(2));
+
+ // No more options with type=2
+ EXPECT_EQ(OptionPtr(), parent->getOption(2));
+
+ // Let's try to delete - should fail
+ EXPECT_TRUE(false == parent->delOption(2));
+
+ // Finally try to get a non-existent option
+ options = parent->getOptions(1234);
+ EXPECT_EQ(0, options.size());
+}
+
+// Check that multiple options of the same type may be retrieved by using
+// getOptions or getNonCopiedOptions. In the former case, also check
+// that retrieved options are copied when setCopyRetrievedOptions is
+// enabled.
+TEST_F(Pkt6Test, getOptions) {
+ NakedPkt6 pkt(DHCPV6_SOLICIT, 1234);
+ OptionPtr opt1(new Option(Option::V6, 1));
+ OptionPtr opt2(new Option(Option::V6, 1));
+ OptionPtr opt3(new Option(Option::V6, 2));
+ OptionPtr opt4(new Option(Option::V6, 2));
+
+ pkt.addOption(opt1);
+ pkt.addOption(opt2);
+ pkt.addOption(opt3);
+ pkt.addOption(opt4);
+
+ // Retrieve options with option code 1.
+ OptionCollection options = pkt.getOptions(1);
+ ASSERT_EQ(2, options.size());
+
+ OptionCollection::const_iterator opt_it;
+
+ // Make sure that the first option is returned. We're using the pointer
+ // to opt1 to find the option.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt1));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Make sure that the second option is returned.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt2));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Retrieve options with option code 2.
+ options = pkt.getOptions(2);
+
+ // opt3 and opt4 should exist.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt3));
+ EXPECT_TRUE(opt_it != options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt4));
+ EXPECT_TRUE(opt_it != options.end());
+
+ // Enable copying options when they are retrieved.
+ pkt.setCopyRetrievedOptions(true);
+
+ options = pkt.getOptions(1);
+ ASSERT_EQ(2, options.size());
+
+ // Both retrieved options should be copied so an attempt to find them
+ // using option pointer should fail. Original pointers should have
+ // been replaced with new instances.
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt1));
+ EXPECT_TRUE(opt_it == options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(1, opt2));
+ EXPECT_TRUE(opt_it == options.end());
+
+ // Return instances of options with the option code 1 and make sure
+ // that copies of the options were used to replace original options
+ // in the packet.
+ OptionCollection options_modified = pkt.getNonCopiedOptions(1);
+ for (OptionCollection::const_iterator opt_it_modified = options_modified.begin();
+ opt_it_modified != options_modified.end(); ++opt_it_modified) {
+ opt_it = std::find(options.begin(), options.end(), *opt_it_modified);
+ ASSERT_TRUE(opt_it != options.end());
+ }
+
+ // Let's check that remaining two options haven't been affected by
+ // retrieving the options with option code 1.
+ options = pkt.getNonCopiedOptions(2);
+ ASSERT_EQ(2, options.size());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt3));
+ EXPECT_TRUE(opt_it != options.end());
+
+ opt_it = std::find(options.begin(), options.end(),
+ std::pair<const unsigned int, OptionPtr>(2, opt4));
+ EXPECT_TRUE(opt_it != options.end());
+}
+
+TEST_F(Pkt6Test, Timestamp) {
+ boost::scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
+
+ // Just after construction timestamp is invalid
+ ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time());
+
+ // Update packet time.
+ pkt->updateTimestamp();
+
+ // Get updated packet time.
+ boost::posix_time::ptime ts_packet = pkt->getTimestamp();
+
+ // After timestamp is updated it should be date-time.
+ ASSERT_FALSE(ts_packet.is_not_a_date_time());
+
+ // Check current time.
+ boost::posix_time::ptime ts_now =
+ boost::posix_time::microsec_clock::universal_time();
+
+ // Calculate period between packet time and now.
+ boost::posix_time::time_period ts_period(ts_packet, ts_now);
+
+ // Duration should be positive or zero.
+ EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
+}
+
+// This test verifies that getName() method returns proper
+// packet type names.
+TEST_F(Pkt6Test, getName) {
+ // Check all possible packet types
+ for (unsigned itype = 0; itype < 256; ++itype) {
+ uint8_t type = itype;
+
+ switch (type) {
+ case DHCPV6_ADVERTISE:
+ EXPECT_STREQ("ADVERTISE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_CONFIRM:
+ EXPECT_STREQ("CONFIRM", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_DECLINE:
+ EXPECT_STREQ("DECLINE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_DHCPV4_QUERY:
+ EXPECT_STREQ("DHCPV4_QUERY", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_DHCPV4_RESPONSE:
+ EXPECT_STREQ("DHCPV4_RESPONSE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_INFORMATION_REQUEST:
+ EXPECT_STREQ("INFORMATION_REQUEST",
+ Pkt6::getName(type));
+ break;
+
+ case DHCPV6_LEASEQUERY:
+ EXPECT_STREQ("LEASEQUERY", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_LEASEQUERY_DATA:
+ EXPECT_STREQ("LEASEQUERY_DATA", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_LEASEQUERY_DONE:
+ EXPECT_STREQ("LEASEQUERY_DONE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_LEASEQUERY_REPLY:
+ EXPECT_STREQ("LEASEQUERY_REPLY", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_REBIND:
+ EXPECT_STREQ("REBIND", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RECONFIGURE:
+ EXPECT_STREQ("RECONFIGURE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RELAY_FORW:
+ EXPECT_STREQ("RELAY_FORWARD", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RELAY_REPL:
+ EXPECT_STREQ("RELAY_REPLY", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RELEASE:
+ EXPECT_STREQ("RELEASE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RENEW:
+ EXPECT_STREQ("RENEW", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_REPLY:
+ EXPECT_STREQ("REPLY", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_REQUEST:
+ EXPECT_STREQ("REQUEST", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_SOLICIT:
+ EXPECT_STREQ("SOLICIT", Pkt6::getName(type));
+ break;
+
+ default:
+ EXPECT_STREQ("UNKNOWN", Pkt6::getName(type));
+ }
+ }
+}
+
+// This test verifies that a fancy solicit that passed through two
+// relays can be parsed properly. See capture2() method description
+// for details regarding the packet.
+TEST_F(Pkt6Test, relayUnpack) {
+ Pkt6Ptr msg(capture2());
+
+ EXPECT_NO_THROW(msg->unpack());
+
+ EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+ EXPECT_EQ(217, msg->len());
+
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ OptionPtr opt;
+
+ // Part 1: Check options inserted by the first relay
+
+ // There should be 2 options in first relay
+ EXPECT_EQ(2, msg->relay_info_[0].options_.size());
+
+ // There should be interface-id option
+ EXPECT_EQ(1, msg->getRelayOptions(D6O_INTERFACE_ID, 0).size());
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 0));
+ OptionBuffer data = opt->getData();
+ EXPECT_EQ(32, opt->len()); // 28 bytes of data + 4 bytes header
+ EXPECT_EQ(data.size(), 28);
+ // That's a strange interface-id, but this is a real life example
+ EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28));
+
+ // Get the remote-id option
+ EXPECT_EQ(1, msg->getRelayOptions(D6O_REMOTE_ID, 0).size());
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0));
+ EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header
+ boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+ uint32_t vendor_id = custom->readInteger<uint32_t>(0);
+ EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks
+
+ uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0,
+ 0x33, 0x41, 0x00, 0x00, 0x21, 0x5c,
+ 0x18, 0xa9 };
+ OptionBuffer remote_id = custom->readBinary(1);
+ ASSERT_EQ(sizeof(expected_remote_id), remote_id.size());
+ ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size()));
+
+ // Part 2: Check options inserted by the second relay
+
+ // Get the interface-id from the second relay
+ EXPECT_EQ(1, msg->getRelayOptions(D6O_INTERFACE_ID, 1).size());
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1));
+ data = opt->getData();
+ EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header
+ EXPECT_EQ(data.size(), 21);
+ EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21));
+
+ // Get the remote-id option
+ EXPECT_EQ(1, msg->getRelayOptions(D6O_REMOTE_ID, 1).size());
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1));
+ EXPECT_EQ(8, opt->len());
+ custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+ vendor_id = custom->readInteger<uint32_t>(0);
+ EXPECT_EQ(3561, vendor_id); // 3561 = Broadband Forum
+ // @todo: See if we can validate empty remote-id field
+
+ // Let's check if there is no leak between options stored in
+ // the SOLICIT message and the relay.
+ EXPECT_TRUE(msg->getRelayOptions(D6O_IA_NA, 1).empty());
+ EXPECT_FALSE(opt = msg->getRelayOption(D6O_IA_NA, 1));
+
+
+ // Part 3: Let's check options in the message itself
+ // This is not redundant compared to other direct messages tests,
+ // as we parsed it differently
+ EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+ EXPECT_EQ(0x6b4fe2, msg->getTransid());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID));
+ EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header
+ uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0,
+ 0x33, 0x41, 0x00, 0x00, 0x21, 0x5c,
+ 0x18, 0xa9 };
+ data = opt->getData();
+ ASSERT_EQ(data.size(), sizeof(expected_client_id));
+ ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size()));
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_IA_NA));
+ boost::shared_ptr<Option6IA> ia =
+ boost::dynamic_pointer_cast<Option6IA>(opt);
+ ASSERT_TRUE(ia);
+ EXPECT_EQ(1, ia->getIAID());
+ EXPECT_EQ(0xffffffff, ia->getT1());
+ EXPECT_EQ(0xffffffff, ia->getT2());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_ELAPSED_TIME));
+ EXPECT_EQ(6, opt->len()); // 2 bytes of data + 4 bytes of header
+ boost::shared_ptr<OptionInt<uint16_t> > elapsed =
+ boost::dynamic_pointer_cast<OptionInt<uint16_t> > (opt);
+ ASSERT_TRUE(elapsed);
+ EXPECT_EQ(0, elapsed->getValue());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_ORO));
+ boost::shared_ptr<OptionIntArray<uint16_t> > oro =
+ boost::dynamic_pointer_cast<OptionIntArray<uint16_t> > (opt);
+ const std::vector<uint16_t> oro_list = oro->getValues();
+ EXPECT_EQ(3, oro_list.size());
+ EXPECT_EQ(23, oro_list[0]);
+ EXPECT_EQ(242, oro_list[1]);
+ EXPECT_EQ(243, oro_list[2]);
+}
+
+// This test verified that message with relay information can be
+// packed and then unpacked.
+TEST_F(Pkt6Test, relayPack) {
+
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+
+ Pkt6::RelayInfo relay1;
+ relay1.msg_type_ = DHCPV6_RELAY_REPL;
+ relay1.hop_count_ = 17; // not very meaningful, but useful for testing
+ relay1.linkaddr_ = IOAddress("2001:db8::1");
+ relay1.peeraddr_ = IOAddress("fe80::abcd");
+
+ uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8};
+ vector<uint8_t> relay_data(relay_opt_data,
+ relay_opt_data + sizeof(relay_opt_data));
+
+ OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));
+
+ relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1));
+
+ OptionPtr opt1(new Option(Option::V6, 100));
+ OptionPtr opt2(new Option(Option::V6, 101));
+ OptionPtr opt3(new Option(Option::V6, 102));
+ // Let's not use zero-length option type 3 as it is IA_NA
+
+ parent->addRelayInfo(relay1);
+
+ parent->addOption(opt1);
+ parent->addOption(opt2);
+ parent->addOption(opt3);
+
+ EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType());
+
+ EXPECT_NO_THROW(parent->pack());
+
+ EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN
+ + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
+ + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header
+ + Option::OPTION6_HDR_LEN // Relay-msg
+ + optRelay1->len(),
+ parent->len());
+
+ // Create second packet,based on assembled data from the first one
+ scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>(
+ parent->getBuffer().getData()),
+ parent->getBuffer().getLength()));
+
+ // Now recreate options list
+ EXPECT_NO_THROW( clone->unpack() );
+
+ // transid, message-type should be the same as before
+ EXPECT_EQ(parent->getTransid(), parent->getTransid());
+ EXPECT_EQ(DHCPV6_ADVERTISE, clone->getType());
+
+ EXPECT_TRUE( clone->getOption(100));
+ EXPECT_TRUE( clone->getOption(101));
+ EXPECT_TRUE( clone->getOption(102));
+ EXPECT_FALSE(clone->getOption(103));
+
+ // Now check relay info
+ ASSERT_EQ(1, clone->relay_info_.size());
+ EXPECT_EQ(DHCPV6_RELAY_REPL, clone->relay_info_[0].msg_type_);
+ EXPECT_EQ(17, clone->relay_info_[0].hop_count_);
+ EXPECT_EQ("2001:db8::1", clone->relay_info_[0].linkaddr_.toText());
+ EXPECT_EQ("fe80::abcd", clone->relay_info_[0].peeraddr_.toText());
+
+ // There should be exactly one option
+ EXPECT_EQ(1, clone->relay_info_[0].options_.size());
+ EXPECT_EQ(1, clone->getRelayOptions(200, 0).size());
+ OptionPtr opt = clone->getRelayOption(200, 0);
+ EXPECT_TRUE(opt);
+ EXPECT_EQ(opt->getType() , optRelay1->getType());
+ EXPECT_EQ(opt->len(), optRelay1->len());
+ OptionBuffer data = opt->getData();
+ ASSERT_EQ(data.size(), sizeof(relay_opt_data));
+ EXPECT_EQ(0, memcmp(&data[0], relay_opt_data, sizeof(relay_opt_data)));
+
+ // As we have a nicely built relay packet we can check
+ // that the functions to get the peer and link addresses work
+ EXPECT_EQ("2001:db8::1", clone->getRelay6LinkAddress(0).toText());
+ EXPECT_EQ("fe80::abcd", clone->getRelay6PeerAddress(0).toText());
+
+ vector<uint8_t>binary = clone->getRelay6LinkAddress(0).toBytes();
+ uint8_t expected0[] = {0x20, 1, 0x0d, 0xb8, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1};
+ EXPECT_EQ(0, memcmp(expected0, &binary[0], 16));
+}
+
+TEST_F(Pkt6Test, getRelayOption) {
+ NakedPkt6Ptr msg(boost::dynamic_pointer_cast<NakedPkt6>(capture2()));
+ ASSERT_TRUE(msg);
+
+ ASSERT_NO_THROW(msg->unpack());
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ OptionPtr opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0);
+ ASSERT_TRUE(opt_iface_id);
+
+ OptionPtr opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0);
+ ASSERT_TRUE(opt_iface_id_returned);
+
+ EXPECT_TRUE(opt_iface_id == opt_iface_id_returned);
+
+ msg->setCopyRetrievedOptions(true);
+
+ opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0);
+ EXPECT_FALSE(opt_iface_id == opt_iface_id_returned);
+
+ opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0);
+ EXPECT_TRUE(opt_iface_id == opt_iface_id_returned);
+}
+
+TEST_F(Pkt6Test, getRelayOptions) {
+ NakedPkt6Ptr msg(boost::dynamic_pointer_cast<NakedPkt6>(capture2()));
+ ASSERT_TRUE(msg);
+
+ ASSERT_NO_THROW(msg->unpack());
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ OptionCollection opts_iface_id =
+ msg->getNonCopiedRelayOptions(D6O_INTERFACE_ID, 0);
+ ASSERT_EQ(1, opts_iface_id.size());
+
+ OptionPtr opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0);
+ ASSERT_TRUE(opt_iface_id);
+
+ OptionCollection opts_iface_id_returned =
+ msg->getRelayOptions(D6O_INTERFACE_ID, 0);
+ ASSERT_EQ(1, opts_iface_id_returned.size());
+
+ OptionPtr opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0);
+ ASSERT_TRUE(opt_iface_id_returned);
+
+ EXPECT_TRUE(opt_iface_id == opt_iface_id_returned);
+ EXPECT_TRUE(opts_iface_id == opts_iface_id_returned);
+ EXPECT_TRUE(opts_iface_id.begin()->second == opt_iface_id);
+ EXPECT_TRUE(opts_iface_id_returned.begin()->second == opt_iface_id_returned);
+
+ msg->setCopyRetrievedOptions(true);
+
+ opts_iface_id_returned = msg->getRelayOptions(D6O_INTERFACE_ID, 0);
+ ASSERT_EQ(1, opts_iface_id_returned.size());
+ opt_iface_id_returned = msg->getRelayOption(D6O_INTERFACE_ID, 0);
+ EXPECT_FALSE(opt_iface_id == opt_iface_id_returned);
+ EXPECT_FALSE(opts_iface_id.begin()->second == opt_iface_id_returned);
+ EXPECT_FALSE(opts_iface_id_returned.begin()->second == opt_iface_id);
+ EXPECT_FALSE(opts_iface_id_returned.begin()->second == opt_iface_id_returned);
+
+ opt_iface_id = msg->getNonCopiedRelayOption(D6O_INTERFACE_ID, 0);
+ EXPECT_TRUE(opt_iface_id == opt_iface_id_returned);
+
+ opts_iface_id_returned = msg->getNonCopiedRelayOptions(D6O_INTERFACE_ID, 0);
+ opts_iface_id = msg->getNonCopiedRelayOptions(D6O_INTERFACE_ID, 0);
+ EXPECT_TRUE(opts_iface_id == opts_iface_id_returned);
+}
+
+// This test verifies that options added by relays to the message can be
+// accessed and retrieved properly
+TEST_F(Pkt6Test, getAnyRelayOption) {
+
+ boost::scoped_ptr<NakedPkt6> msg(new NakedPkt6(DHCPV6_ADVERTISE, 0x020304));
+ msg->addOption(generateRandomOption(300));
+
+ // generate options for relay1
+ Pkt6::RelayInfo relay1;
+
+ // generate 3 options with code 200,201,202 and random content
+ OptionPtr relay1_opt1(generateRandomOption(200));
+ OptionPtr relay1_opt2(generateRandomOption(201));
+ OptionPtr relay1_opt3(generateRandomOption(202));
+
+ relay1.options_.insert(make_pair(200, relay1_opt1));
+ relay1.options_.insert(make_pair(201, relay1_opt2));
+ relay1.options_.insert(make_pair(202, relay1_opt3));
+ msg->addRelayInfo(relay1);
+
+ // generate options for relay2
+ Pkt6::RelayInfo relay2;
+ OptionPtr relay2_opt1(new Option(Option::V6, 100));
+ OptionPtr relay2_opt2(new Option(Option::V6, 101));
+ OptionPtr relay2_opt3(new Option(Option::V6, 102));
+ OptionPtr relay2_opt4(new Option(Option::V6, 200));
+ // the same code as relay1_opt3
+ relay2.options_.insert(make_pair(100, relay2_opt1));
+ relay2.options_.insert(make_pair(101, relay2_opt2));
+ relay2.options_.insert(make_pair(102, relay2_opt3));
+ relay2.options_.insert(make_pair(200, relay2_opt4));
+ msg->addRelayInfo(relay2);
+
+ // generate options for relay3
+ Pkt6::RelayInfo relay3;
+ OptionPtr relay3_opt1(generateRandomOption(200, 7));
+ relay3.options_.insert(make_pair(200, relay3_opt1));
+ msg->addRelayInfo(relay3);
+
+ // Ok, so we now have a packet that traversed the following network:
+ // client---relay3---relay2---relay1---server
+
+ // First check that the getAnyRelayOption does not confuse client options
+ // and relay options
+ // 300 is a client option, present in the message itself.
+ OptionPtr opt =
+ msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+ EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_SEARCH_FROM_CLIENT).empty());
+ EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_SEARCH_FROM_SERVER).empty());
+ EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_GET_FIRST).empty());
+ EXPECT_TRUE(msg->getAllRelayOptions(300, Pkt6::RELAY_GET_LAST).empty());
+
+ // Option 200 is added in every relay.
+
+ // We want to get that one inserted by relay3 (first match, starting from
+ // closest to the client.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay3_opt1));
+ EXPECT_TRUE(opt == relay3_opt1);
+
+ // Check collections.
+ OptionCollection opts0 =
+ msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_EQ(3, opts0.size());
+ vector<OptionPtr> lopts0;
+ for (auto it : opts0) {
+ lopts0.push_back(it.second);
+ }
+ ASSERT_EQ(3, lopts0.size());
+ EXPECT_TRUE(lopts0[0] == opt);
+ EXPECT_TRUE(lopts0[0] == relay3_opt1);
+ EXPECT_TRUE(lopts0[1] == relay2_opt4);
+ EXPECT_TRUE(lopts0[2] == relay1_opt1);
+ OptionCollection opts =
+ msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_TRUE(opts == opts0);
+
+ // We want to get that one inserted by relay1 (first match, starting from
+ // closest to the server.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay1_opt1));
+ EXPECT_TRUE(opt == relay1_opt1);
+
+ // Check collections.
+ opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_EQ(3, opts.size());
+ vector<OptionPtr> lopts;
+ for (auto it : opts) {
+ lopts.push_back(it.second);
+ }
+ ASSERT_EQ(3, lopts.size());
+ EXPECT_TRUE(lopts[0] == opt);
+ EXPECT_TRUE(lopts[0] == relay1_opt1);
+ EXPECT_TRUE(lopts[1] == relay2_opt4);
+ EXPECT_TRUE(lopts[2] == relay3_opt1);
+ EXPECT_TRUE(opts == msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER));
+
+ // Check reverse order.
+ vector<OptionPtr> ropts;
+ for (auto it = opts.rbegin(); it != opts.rend(); ++it) {
+ ropts.push_back(it->second);
+ }
+ EXPECT_TRUE(lopts0 == ropts);
+
+ // We just want option from the first relay (closest to the client)
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay3_opt1));
+ EXPECT_TRUE(opt == relay3_opt1);
+ opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_FIRST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opt == opts.begin()->second);
+ opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_FIRST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opts.begin()->second == relay3_opt1);
+
+ // We just want option from the last relay (closest to the server)
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay1_opt1));
+ EXPECT_TRUE(opt == relay1_opt1);
+ opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_LAST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opt == opts.begin()->second);
+ opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_LAST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opts.begin()->second == relay1_opt1);
+
+ // Enable copying options when they are retrieved and redo the tests
+ // but expect that options are still equal but different pointers
+ // are returned.
+ msg->setCopyRetrievedOptions(true);
+
+ // From client.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay3_opt1));
+ EXPECT_FALSE(opt == relay3_opt1);
+ // Test that option copy has replaced the original option within the
+ // packet. We achieve that by calling a variant of the method which
+ // retrieved non-copied option.
+ relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(relay3_opt1);
+ EXPECT_TRUE(opt == relay3_opt1);
+
+ // Check collections.
+ opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ lopts0.clear();
+ for (auto it : opts) {
+ lopts0.push_back(it.second);
+ }
+ ASSERT_EQ(3, lopts0.size());
+ EXPECT_TRUE(lopts0[0] == opt);
+ EXPECT_TRUE(lopts0[0] == relay3_opt1);
+ EXPECT_TRUE(lopts0[1] == relay2_opt4);
+ EXPECT_TRUE(lopts0[2] == relay1_opt1);
+ opts = msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ lopts.clear();
+ for (auto it : opts) {
+ lopts.push_back(it.second);
+ }
+ ASSERT_EQ(3, lopts.size());
+ EXPECT_TRUE(relay3_opt1->equals(lopts[0]));
+ EXPECT_FALSE(lopts[0] == lopts0[0]);
+ EXPECT_TRUE(relay2_opt4->equals(lopts[1]));
+ EXPECT_FALSE(lopts[1] == lopts0[1]);
+ EXPECT_TRUE(relay1_opt1->equals(lopts[2]));
+ EXPECT_FALSE(lopts[2] == lopts0[2]);
+ // Get current values for next tests.
+ relay3_opt1 = lopts[0];
+ relay2_opt4 = lopts[1];
+ relay1_opt1 = lopts[2];
+
+ // From server.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay1_opt1));
+ EXPECT_FALSE(opt == relay1_opt1);
+ relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(relay1_opt1);
+ EXPECT_TRUE(opt == relay1_opt1);
+
+ // Check collections.
+ opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ lopts0.clear();
+ for (auto it : opts) {
+ lopts0.push_back(it.second);
+ }
+ ASSERT_EQ(3, lopts0.size());
+ EXPECT_TRUE(lopts0[0] == opt);
+ EXPECT_TRUE(lopts0[0] == relay1_opt1);
+ EXPECT_TRUE(lopts0[1] == relay2_opt4);
+ EXPECT_TRUE(lopts0[2] == relay3_opt1);
+ opts = msg->getAllRelayOptions(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ lopts.clear();
+ for (auto it : opts) {
+ lopts.push_back(it.second);
+ }
+ ASSERT_EQ(3, lopts.size());
+ EXPECT_TRUE(relay1_opt1->equals(lopts[0]));
+ EXPECT_FALSE(lopts[0] == lopts0[0]);
+ EXPECT_TRUE(relay2_opt4->equals(lopts[1]));
+ EXPECT_FALSE(lopts[1] == lopts0[1]);
+ EXPECT_TRUE(relay3_opt1->equals(lopts[2]));
+ EXPECT_FALSE(lopts[2] == lopts0[2]);
+
+ // First.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay3_opt1));
+ EXPECT_FALSE(opt == relay3_opt1);
+ relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+ ASSERT_TRUE(relay3_opt1);
+ EXPECT_TRUE(opt == relay3_opt1);
+ opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_FIRST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opt == opts.begin()->second);
+ opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_FIRST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_FALSE(opts.begin()->second == relay3_opt1);
+ relay3_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+ EXPECT_TRUE(opts.begin()->second == relay3_opt1);
+
+ // Last.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay1_opt1));
+ EXPECT_FALSE(opt == relay1_opt1);
+ relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+ ASSERT_TRUE(relay1_opt1);
+ EXPECT_TRUE(opt == relay1_opt1);
+ opts = msg->getNonCopiedAllRelayOptions(200, Pkt6::RELAY_GET_LAST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opt == opts.begin()->second);
+ opts = msg->getAllRelayOptions(200, Pkt6::RELAY_GET_LAST);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_FALSE(opts.begin()->second == relay1_opt1);
+ relay1_opt1 = msg->getNonCopiedAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+ EXPECT_TRUE(opts.begin()->second == relay1_opt1);
+
+ // Disable copying options and continue with other tests.
+ msg->setCopyRetrievedOptions(false);
+
+ // Let's try to ask for something that is inserted by the middle relay
+ // only.
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay2_opt1));
+ opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opts.begin()->second == relay2_opt1);
+ opts = msg->getAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(relay2_opt1->equals(opts.begin()->second));
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(relay2_opt1));
+ opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(opts.begin()->second == relay2_opt1);
+ opts = msg->getAllRelayOptions(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_EQ(1, opts.size());
+ EXPECT_TRUE(relay2_opt1->equals(opts.begin()->second));
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+ opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_GET_FIRST);
+ EXPECT_TRUE(opts.empty());
+ opts = msg->getAllRelayOptions(100, Pkt6::RELAY_GET_FIRST);
+ EXPECT_TRUE(opts.empty());
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+ opts = msg->getNonCopiedAllRelayOptions(100, Pkt6::RELAY_GET_LAST);
+ EXPECT_TRUE(opts.empty());
+ opts = msg->getAllRelayOptions(100, Pkt6::RELAY_GET_LAST);
+ EXPECT_TRUE(opts.empty());
+
+ // Finally, try to get an option that does not exist
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+ opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_GET_FIRST);
+ EXPECT_TRUE(opts.empty());
+ opts = msg->getAllRelayOptions(500, Pkt6::RELAY_GET_FIRST);
+ EXPECT_TRUE(opts.empty());
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+ opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_GET_LAST);
+ EXPECT_TRUE(opts.empty());
+ opts = msg->getAllRelayOptions(500, Pkt6::RELAY_GET_LAST);
+ EXPECT_TRUE(opts.empty());
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_FALSE(opt);
+ opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_TRUE(opts.empty());
+ opts = msg->getAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_TRUE(opts.empty());
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_FALSE(opt);
+ opts = msg->getNonCopiedAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_TRUE(opts.empty());
+ opts = msg->getAllRelayOptions(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_TRUE(opts.empty());
+}
+
+// Tests whether Pkt6::toText() properly prints out all parameters, including
+// relay options: remote-id, interface-id.
+TEST_F(Pkt6Test, toText) {
+
+ // This packet contains doubly relayed solicit. The inner-most
+ // relay-forward contains interface-id and remote-id. We will
+ // check that these are printed correctly.
+ Pkt6Ptr msg(capture2());
+ EXPECT_NO_THROW(msg->unpack());
+
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ string expected =
+ "localAddr=[ff05::1:3]:547 remoteAddr=[fe80::1234]:547\n"
+ "msgtype=1(SOLICIT), transid=0x6b4fe2\n"
+ "type=00001, len=00014: 00:01:00:01:18:b0:33:41:00:00:21:5c:18:a9\n"
+ "type=00003(IA_NA), len=00012: iaid=1, t1=4294967295, t2=4294967295\n"
+ "type=00006, len=00006: 23(uint16) 242(uint16) 243(uint16)\n"
+ "type=00008, len=00002: 0 (uint16)\n"
+ "2 relay(s):\n"
+ "relay[0]: msg-type=12(RELAY_FORWARD), hop-count=1,\n"
+ "link-address=2001:888:db8:1::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n"
+ "type=00018, len=00028: 49:53:41:4d:31:34:34:7c:32:39:39:7c:69:70:76:36:7c:6e:74:3a:76:70:3a:31:3a:31:31:30\n"
+ "type=00037, len=00018: 6527 (uint32) 0001000118B033410000215C18A9 (binary)\n"
+ "relay[1]: msg-type=12(RELAY_FORWARD), hop-count=0,\n"
+ "link-address=::, peer-address=fe80::200:21ff:fe5c:18a9, 2 option(s)\n"
+ "type=00018, len=00021: 49:53:41:4d:31:34:34:20:65:74:68:20:31:2f:31:2f:30:35:2f:30:31\n"
+ "type=00037, len=00004: 3561 (uint32) (binary)\n";
+
+ EXPECT_EQ(expected, msg->toText());
+}
+
+// Tests whether a packet can be assigned to a class and later
+// checked if it belongs to a given class
+TEST_F(Pkt6Test, clientClasses) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ EXPECT_TRUE(pkt.getClasses().empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+ ASSERT_FALSE(pkt.getClasses().empty());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM);
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+ EXPECT_NO_THROW(pkt.addClass("foo"));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.inClass("foo"));
+}
+
+// Tests whether a packet can be marked to evaluate later a class and
+// after check if a given class is in the collection
+TEST_F(Pkt6Test, deferredClientClasses) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_TRUE(pkt.getClasses(true).empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
+ EXPECT_EQ(1, pkt.getClasses(true).size());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM, true);
+ EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
+ EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
+}
+
+// Tests whether a packet can be assigned to a subclass and later
+// checked if it belongs to a given subclass
+TEST_F(Pkt6Test, templateClasses) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Default values (do not belong to any subclass)
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+ EXPECT_TRUE(pkt.getClasses().empty());
+
+ // Add to the first subclass
+ pkt.addSubClass("template-interface-name", "SPAWN_template-interface-name_eth0");
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_FALSE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+ ASSERT_FALSE(pkt.getClasses().empty());
+
+ // Add to a second subclass
+ pkt.addSubClass("template-interface-id", "SPAWN_template-interface-id_interface-id0");
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-name_eth0"));
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-interface-id_interface-id0"));
+
+ // Check that it's ok to add to the same subclass repeatedly
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-foo", "SPAWN_template-foo_bar"));
+ EXPECT_NO_THROW(pkt.addSubClass("template-bar", "SPAWN_template-bar_bar"));
+
+ // Check that the packet belongs to 'SPAWN_template-foo_bar'
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-foo_bar"));
+
+ // Check that the packet belongs to 'SPAWN_template-bar_bar'
+ EXPECT_TRUE(pkt.inClass("SPAWN_template-bar_bar"));
+}
+
+// Tests whether MAC can be obtained and that MAC sources are not
+// confused.
+TEST_F(Pkt6Test, getMAC) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // DHCPv6 packet by default doesn't have MAC address specified.
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+
+ // We haven't specified source IPv6 address, so this method should
+ // fail, too
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+ // Let's check if setting IPv6 address improves the situation.
+ IOAddress linklocal_eui64("fe80::204:06ff:fe08:0a0c");
+ pkt.setRemoteAddr(linklocal_eui64);
+ HWAddrPtr mac;
+ ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_);
+
+ ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_);
+
+ ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL |
+ HWAddr::HWADDR_SOURCE_RAW));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, mac->source_);
+
+ pkt.setRemoteAddr(IOAddress("::"));
+
+ // Let's invent a MAC
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+
+ // Now let's pretend that we obtained it from raw sockets
+ pkt.setRemoteHWAddr(dummy_hwaddr);
+
+ // Now we should be able to get something
+ ASSERT_TRUE(mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_);
+
+ ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_);
+
+ EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL |
+ HWAddr::HWADDR_SOURCE_RAW));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, mac->source_);
+
+ // Check that the returned MAC is indeed the expected one
+ ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
+ ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
+}
+
+// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
+// address properly (for direct message).
+TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_direct) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Let's get the first interface
+ IfacePtr iface = IfaceMgr::instance().getIface(1);
+ ASSERT_TRUE(iface);
+
+ // and set source interface data properly. getMACFromIPv6LinkLocal attempts
+ // to use source interface to obtain hardware type
+ pkt.setIface(iface->getName());
+ pkt.setIndex(iface->getIndex());
+
+ // Note that u and g bits (the least significant ones of the most
+ // significant byte) have special meaning and must not be set in MAC.
+ // u bit is always set in EUI-64. g is always cleared.
+ IOAddress global("2001:db8::204:06ff:fe08:0a:0c");
+ IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c");
+ IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10");
+
+ // If received from a global address, this method should fail
+ pkt.setRemoteAddr(global);
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+ // If received from link-local that is EUI-64 based, it should succeed
+ pkt.setRemoteAddr(linklocal_eui64);
+ HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+ ASSERT_TRUE(found);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_);
+
+ stringstream tmp;
+ tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c";
+ EXPECT_EQ(tmp.str(), found->toText(true));
+}
+
+// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
+// address properly (for relayed message).
+TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_singleRelay) {
+
+ // Let's create a Solicit first...
+ Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+ // ... and pretend it was relayed by a single relay.
+ Pkt6::RelayInfo info;
+ pkt.addRelayInfo(info);
+ ASSERT_EQ(1, pkt.relay_info_.size());
+
+ // Let's get the first interface
+ IfacePtr iface = IfaceMgr::instance().getIface(1);
+ ASSERT_TRUE(iface);
+
+ // and set source interface data properly. getMACFromIPv6LinkLocal attempts
+ // to use source interface to obtain hardware type
+ pkt.setIface(iface->getName());
+ pkt.setIndex(iface->getIndex());
+
+ IOAddress global("2001:db8::204:06ff:fe08:0a:0c"); // global address
+ IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10"); // no fffe
+ IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c"); // valid EUI-64
+
+ // If received from a global address, this method should fail
+ pkt.relay_info_[0].peeraddr_ = global;
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+ // If received from a link-local that does not use EUI-64, it should fail
+ pkt.relay_info_[0].peeraddr_ = linklocal_noneui64;
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
+
+ // If received from link-local that is EUI-64 based, it should succeed
+ pkt.relay_info_[0].peeraddr_ = linklocal_eui64;
+ HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+ ASSERT_TRUE(found);
+
+ stringstream tmp;
+ tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c";
+ EXPECT_EQ(tmp.str(), found->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_);
+}
+
+// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
+// address properly (for a message relayed multiple times).
+TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_multiRelay) {
+
+ // Let's create a Solicit first...
+ Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+ // ... and pretend it was relayed via 3 relays. Keep in mind that
+ // the relays are stored in relay_info_ in the encapsulation order
+ // rather than in traverse order. The following simulates:
+ // client --- relay1 --- relay2 --- relay3 --- server
+ IOAddress linklocal1("fe80::200:ff:fe00:1"); // valid EUI-64
+ IOAddress linklocal2("fe80::200:ff:fe00:2"); // valid EUI-64
+ IOAddress linklocal3("fe80::200:ff:fe00:3"); // valid EUI-64
+
+ // Let's add info about relay3. This was the last relay, so it added the
+ // outermost encapsulation layer, so it was parsed first during reception.
+ // Its peer-addr field contains an address of relay2, so it's useless for
+ // this method.
+ Pkt6::RelayInfo info;
+ info.peeraddr_ = linklocal3;
+ pkt.addRelayInfo(info);
+
+ // Now add info about relay2. Its peer-addr contains an address of the
+ // previous relay (relay1). Still useless for us.
+ info.peeraddr_ = linklocal2;
+ pkt.addRelayInfo(info);
+
+ // Finally add the first relay. This is the relay that received the packet
+ // from the client directly, so its peer-addr field contains an address of
+ // the client. The method should get that address and build MAC from it.
+ info.peeraddr_ = linklocal1;
+ pkt.addRelayInfo(info);
+ ASSERT_EQ(3, pkt.relay_info_.size());
+
+ // Let's get the first interface
+ IfacePtr iface = IfaceMgr::instance().getIface(1);
+ ASSERT_TRUE(iface);
+
+ // and set source interface data properly. getMACFromIPv6LinkLocal attempts
+ // to use source interface to obtain hardware type
+ pkt.setIface(iface->getName());
+ pkt.setIndex(iface->getIndex());
+
+ // The method should return MAC based on the first relay that was closest
+ HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+ ASSERT_TRUE(found);
+
+ // Let's check the info now.
+ stringstream tmp;
+ tmp << "hwtype=" << iface->getHWType() << " 00:00:00:00:00:01";
+ EXPECT_EQ(tmp.str(), found->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, found->source_);
+}
+
+// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC)
+// address properly from a single relayed message.
+TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_singleRelay) {
+
+ // Let's create a Solicit first...
+ Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+ // Packets that are not relayed should fail
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION));
+
+ // Now pretend it was relayed by a single relay.
+ Pkt6::RelayInfo info;
+
+ // generate options with code 79 and client link layer address
+ const uint8_t opt_data[] = {
+ 0x00, 0x01, // Ethertype
+ 0x0a, 0x1b, 0x0b, 0x01, 0xca, 0xfe // MAC
+ };
+ OptionPtr relay_opt(new Option(Option::V6, 79,
+ OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
+ info.options_.insert(make_pair(relay_opt->getType(), relay_opt));
+
+ pkt.addRelayInfo(info);
+ ASSERT_EQ(1, pkt.relay_info_.size());
+
+ HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION);
+ ASSERT_TRUE(found);
+
+ stringstream tmp;
+ tmp << "hwtype=1 0a:1b:0b:01:ca:fe";
+ EXPECT_EQ(tmp.str(), found->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, found->source_);
+}
+
+// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC)
+// address properly from a message relayed by multiple servers.
+TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_multipleRelay) {
+
+ // Let's create a Solicit first...
+ Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+ // Now pretend it was relayed two times. The relay closest to the server
+ // adds link-layer-address information against the RFC, the process fails.
+ Pkt6::RelayInfo info1;
+ uint8_t opt_data[] = {
+ 0x00, 0x01, // Ethertype
+ 0x1a, 0x30, 0x0b, 0xfa, 0xc0, 0xfe // MAC
+ };
+ OptionPtr relay_opt1(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR,
+ OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
+
+ info1.options_.insert(make_pair(relay_opt1->getType(), relay_opt1));
+ pkt.addRelayInfo(info1);
+
+ // Second relay, closest to the client has not implemented RFC6939
+ Pkt6::RelayInfo info2;
+ pkt.addRelayInfo(info2);
+ ASSERT_EQ(2, pkt.relay_info_.size());
+
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION));
+
+ // Let's envolve the packet with a third relay (now the closest to the client)
+ // that inserts the correct client_linklayer_addr option.
+ Pkt6::RelayInfo info3;
+
+ // We reuse the option and modify the MAC to be sure we get the right address
+ opt_data[2] = 0xfa;
+ OptionPtr relay_opt3(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR,
+ OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
+ info3.options_.insert(make_pair(relay_opt3->getType(), relay_opt3));
+ pkt.addRelayInfo(info3);
+ ASSERT_EQ(3, pkt.relay_info_.size());
+
+ // Now extract the MAC address from the relayed option
+ HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION);
+ ASSERT_TRUE(found);
+
+ stringstream tmp;
+ tmp << "hwtype=1 fa:30:0b:fa:c0:fe";
+ EXPECT_EQ(tmp.str(), found->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION,found->source_);
+}
+
+TEST_F(Pkt6Test, getMACFromDUID) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Although MACs are typically 6 bytes long, let's make this test a bit
+ // more challenging and use odd MAC lengths.
+
+ uint8_t duid_llt[] = { 0, 1, // type (DUID-LLT)
+ 0, 7, // hwtype (7 - just a randomly picked value)
+ 1, 2, 3, 4, // timestamp
+ 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 // MAC address (7 bytes)
+ };
+
+ uint8_t duid_ll[] = { 0, 3, // type (DUID-LL)
+ 0, 11, // hwtype (11 - just a randomly picked value)
+ 0xa, 0xb, 0xc, 0xd, 0xe // MAC address (5 bytes)
+ };
+
+ uint8_t duid_en[] = { 0, 2, // type (DUID-EN)
+ 1, 2, 3, 4, // enterprise-id
+ 0xa, 0xb, 0xc // opaque data
+ };
+
+ OptionPtr clientid1(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
+ duid_llt, duid_llt + sizeof(duid_llt))));
+ OptionPtr clientid2(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
+ duid_ll, duid_ll + sizeof(duid_ll))));
+ OptionPtr clientid3(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
+ duid_en, duid_en + sizeof(duid_en))));
+
+ // Packet does not have any client-id, this call should fail
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID));
+
+ // Let's test DUID-LLT. This should work.
+ pkt.addOption(clientid1);
+ HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID);
+ ASSERT_TRUE(mac);
+ EXPECT_EQ("hwtype=7 0a:0b:0c:0d:0e:0f:10", mac->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, mac->source_);
+
+ // Let's test DUID-LL. This should work.
+ ASSERT_TRUE(pkt.delOption(D6O_CLIENTID));
+ pkt.addOption(clientid2);
+ mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID);
+ ASSERT_TRUE(mac);
+ EXPECT_EQ("hwtype=11 0a:0b:0c:0d:0e", mac->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, mac->source_);
+
+ // Finally, let's try DUID-EN. This should fail, as EN type does not
+ // contain any MAC address information.
+ ASSERT_TRUE(pkt.delOption(D6O_CLIENTID));
+ pkt.addOption(clientid3);
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID));
+}
+
+// Test checks whether getMAC(DOCSIS_MODEM) is working properly.
+// We only have a small number of actual traffic captures from
+// cable networks, so the scope of unit-tests is somewhat limited.
+TEST_F(Pkt6Test, getMAC_DOCSIS_Modem) {
+
+ // Let's use a captured traffic. The one we have comes from a
+ // modem with MAC address 10:0d:7f:00:07:88.
+ Pkt6Ptr pkt = PktCaptures::captureDocsisRelayedSolicit();
+ ASSERT_NO_THROW(pkt->unpack());
+
+ // The method should return MAC based on the vendor-specific info,
+ // suboption 36, which is inserted by the modem itself.
+ HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM);
+ ASSERT_TRUE(found);
+
+ // Let's check the info.
+ EXPECT_EQ("hwtype=1 10:0d:7f:00:07:88", found->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM, found->source_);
+
+ // Now let's remove the option
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<
+ OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS));
+ ASSERT_TRUE(vendor);
+ ASSERT_TRUE(vendor->delOption(DOCSIS3_V6_DEVICE_ID));
+
+ // Ok, there's no more suboption 36. Now getMAC() should fail.
+ EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM));
+}
+
+// Test checks whether getMAC(DOCSIS_CMTS) is working properly.
+// We only have a small number of actual traffic captures from
+// cable networks, so the scope of unit-tests is somewhat limited.
+TEST_F(Pkt6Test, getMAC_DOCSIS_CMTS) {
+
+ // Let's use a captured traffic. The one we have comes from a
+ // modem with MAC address 20:e5:2a:b8:15:14.
+ Pkt6Ptr pkt = PktCaptures::captureeRouterRelayedSolicit();
+ ASSERT_NO_THROW(pkt->unpack());
+
+ // The method should return MAC based on the vendor-specific info,
+ // suboption 36, which is inserted by the modem itself.
+ HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS);
+ ASSERT_TRUE(found);
+
+ // Let's check the info.
+ EXPECT_EQ("hwtype=1 20:e5:2a:b8:15:14", found->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS, found->source_);
+
+ // Now let's remove the suboption 1026 that is inserted by the
+ // relay.
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<
+ OptionVendor>(pkt->getAnyRelayOption(D6O_VENDOR_OPTS,
+ isc::dhcp::Pkt6::RELAY_SEARCH_FROM_CLIENT));
+ ASSERT_TRUE(vendor);
+ EXPECT_TRUE(vendor->delOption(DOCSIS3_V6_CMTS_CM_MAC));
+
+ EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS));
+}
+
+// Test checks whether getMACFromRemoteIdRelayOption() returns the hardware (MAC)
+// address properly from a relayed message.
+TEST_F(Pkt6Test, getMACFromRemoteIdRelayOption) {
+
+ // Create a solicit message.
+ Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+ // This should fail as the message is't relayed yet.
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID));
+
+ // Let's get the first interface
+ IfacePtr iface = IfaceMgr::instance().getIface(1);
+ ASSERT_TRUE(iface);
+
+ // and set source interface data properly. getMACFromIPv6LinkLocal attempts
+ // to use source interface to obtain hardware type
+ pkt.setIface(iface->getName());
+ pkt.setIndex(iface->getIndex());
+
+ // Generate option data with randomly picked enterprise number and remote-id
+ const uint8_t opt_data[] = {
+ 1, 2, 3, 4, // enterprise-number
+ 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // remote-id can be used as a standard MAC
+ };
+
+ // Create option with number 37 (remote-id relay agent option)
+ OptionPtr relay_opt(new Option(Option::V6, D6O_REMOTE_ID,
+ OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
+
+ // First simulate relaying message without adding remote-id option
+ Pkt6::RelayInfo info;
+ pkt.addRelayInfo(info);
+ ASSERT_EQ(1, pkt.relay_info_.size());
+
+ // This should fail as the remote-id option isn't there
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID));
+
+ // Now add this option to the relayed message
+ info.options_.insert(make_pair(relay_opt->getType(), relay_opt));
+ pkt.addRelayInfo(info);
+ ASSERT_EQ(2, pkt.relay_info_.size());
+
+ // This should work now
+ HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID);
+ ASSERT_TRUE(mac);
+
+ stringstream tmp;
+ tmp << "hwtype=" << (int)iface->getHWType() << " 0a:0b:0c:0d:0e:0f";
+
+ EXPECT_EQ(tmp.str(), mac->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac->source_);
+}
+
+// Test checks whether getMACFromRemoteIdRelayOption() returns the hardware (MAC)
+// address properly from a relayed message (even if the remote-id is longer than
+// 20 bytes).
+TEST_F(Pkt6Test, getMACFromRemoteIdRelayOptionExtendedValue) {
+
+ // Create a solicit message.
+ Pkt6 pkt(DHCPV6_SOLICIT, 1234);
+
+ // This should fail as the message is't relayed yet.
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID));
+
+ // Let's get the first interface
+ IfacePtr iface = IfaceMgr::instance().getIface(1);
+ ASSERT_TRUE(iface);
+
+ // and set source interface data properly. getMACFromIPv6LinkLocal attempts
+ // to use source interface to obtain hardware type
+ pkt.setIface(iface->getName());
+ pkt.setIndex(iface->getIndex());
+
+ // Generate option data with randomly picked enterprise number and remote-id
+ const uint8_t opt_data[] = {
+ 1, 2, 3, 4, // enterprise-number
+ 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // remote-id can be longer than 20 bytes,
+ 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // truncate it so that is can be used as
+ 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, // a standard MAC
+ 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
+ };
+
+ // Create option with number 37 (remote-id relay agent option)
+ OptionPtr relay_opt(new Option(Option::V6, D6O_REMOTE_ID,
+ OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
+
+ // First simulate relaying message without adding remote-id option
+ Pkt6::RelayInfo info;
+ pkt.addRelayInfo(info);
+ ASSERT_EQ(1, pkt.relay_info_.size());
+
+ // This should fail as the remote-id option isn't there
+ EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID));
+
+ // Now add this option to the relayed message
+ info.options_.insert(make_pair(relay_opt->getType(), relay_opt));
+ pkt.addRelayInfo(info);
+ ASSERT_EQ(2, pkt.relay_info_.size());
+
+ // This should work now
+ HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID);
+ ASSERT_TRUE(mac);
+
+ stringstream tmp;
+ tmp << "hwtype=" << (int)iface->getHWType()
+ << " 0a:0b:0c:0d:0e:0f:0a:0b:0c:0d:0e:0f:0a:0b:0c:0d:0e:0f:0a:0b";
+
+ EXPECT_EQ(tmp.str(), mac->toText(true));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac->source_);
+}
+
+// This test verifies that a solicit that passed through two relays is parsed
+// properly. In particular the second relay (outer encapsulation) included RSOO
+// (Relay Supplied Options option). This test checks whether it was parsed
+// properly. See captureRelayed2xRSOO() description for details.
+TEST_F(Pkt6Test, rsoo) {
+ Pkt6Ptr msg = dhcp::test::PktCaptures::captureRelayed2xRSOO();
+
+ EXPECT_NO_THROW(msg->unpack());
+
+ EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+ EXPECT_EQ(217, msg->len());
+
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ // There should be an RSOO option in the outermost relay
+ OptionPtr opt = msg->getRelayOption(D6O_RSOO, 1);
+ ASSERT_TRUE(opt);
+
+ EXPECT_EQ(D6O_RSOO, opt->getType());
+ const OptionCollection& rsoo = opt->getOptions();
+ ASSERT_EQ(2, rsoo.size());
+
+ OptionPtr rsoo1 = opt->getOption(255);
+ OptionPtr rsoo2 = opt->getOption(256);
+
+ ASSERT_TRUE(rsoo1);
+ ASSERT_TRUE(rsoo2);
+
+ EXPECT_EQ(8, rsoo1->len()); // 4 bytes of data + header
+ EXPECT_EQ(13, rsoo2->len()); // 9 bytes of data + header
+
+}
+
+// Verify that the DUID can be extracted from the DHCPv6 packet
+// holding Client Identifier option.
+TEST_F(Pkt6Test, getClientId) {
+ // Create a packet.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x2312));
+ // Initially, the packet should hold no DUID.
+ EXPECT_FALSE(pkt->getClientId());
+
+ // Create DUID and add it to the packet.
+ const uint8_t duid_data[] = { 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 0 };
+ OptionBuffer duid_vec(duid_data, duid_data + sizeof(duid_data) - 1);
+ pkt->addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+ duid_vec.begin(),
+ duid_vec.end())));
+
+ // Simulate the packet transmission over the wire, i.e. create on
+ // wire representation of the packet, and then parse it.
+ Pkt6Ptr pkt_clone = packAndClone(pkt);
+ ASSERT_NO_THROW(pkt_clone->unpack());
+
+ // This time the DUID should be returned.
+ DuidPtr duid = pkt_clone->getClientId();
+ ASSERT_TRUE(duid);
+
+ // And it should be equal to the one that we used to create
+ // the packet.
+ EXPECT_TRUE(duid->getDuid() == duid_vec);
+}
+
+// This test verifies that it is possible to obtain the packet
+// identifiers (DUID, HW Address, transaction id) in the textual
+// format.
+TEST_F(Pkt6Test, makeLabel) {
+ DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303")));
+ HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06",
+ HTYPE_ETHER)));
+
+ // Specify DUID and no HW Address.
+ EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], tid=0x123",
+ Pkt6::makeLabel(duid, 0x123, HWAddrPtr()));
+
+ // Specify HW Address and no DUID.
+ EXPECT_EQ("duid=[no info], [hwtype=1 01:02:03:04:05:06], tid=0x123",
+ Pkt6::makeLabel(DuidPtr(), 0x123, hwaddr));
+
+ // Specify both DUID and HW Address.
+ EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], "
+ "[hwtype=1 01:02:03:04:05:06], tid=0x123",
+ Pkt6::makeLabel(duid, 0x123, hwaddr));
+
+ // Specify neither DUID nor HW Address.
+ EXPECT_EQ("duid=[no info], tid=0x0",
+ Pkt6::makeLabel(DuidPtr(), 0x0, HWAddrPtr()));
+}
+
+// Tests that the variant of makeLabel which doesn't include transaction
+// id produces expected output.
+TEST_F(Pkt6Test, makeLabelWithoutTransactionId) {
+ DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303")));
+ HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText("01:02:03:04:05:06",
+ HTYPE_ETHER)));
+
+ // Specify DUID and no HW Address.
+ EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03]",
+ Pkt6::makeLabel(duid, HWAddrPtr()));
+
+ // Specify HW Address and no DUID.
+ EXPECT_EQ("duid=[no info], [hwtype=1 01:02:03:04:05:06]",
+ Pkt6::makeLabel(DuidPtr(), hwaddr));
+
+ // Specify both DUID and HW Address.
+ EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], "
+ "[hwtype=1 01:02:03:04:05:06]",
+ Pkt6::makeLabel(duid, hwaddr));
+
+ // Specify neither DUID nor HW Address.
+ EXPECT_EQ("duid=[no info]", Pkt6::makeLabel(DuidPtr(), HWAddrPtr()));
+}
+
+// This test verifies that it is possible to obtain the packet
+// identifiers in the textual format from the packet instance.
+TEST_F(Pkt6Test, getLabel) {
+ // Create a packet.
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 0x2312));
+ EXPECT_EQ("duid=[no info], tid=0x2312",
+ pkt->getLabel());
+
+ DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303")));
+ pkt->addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+ duid->getDuid().begin(),
+ duid->getDuid().end())));
+
+ // Simulate the packet transmission over the wire, i.e. create on
+ // wire representation of the packet, and then parse it.
+ Pkt6Ptr pkt_clone = packAndClone(pkt);
+ ASSERT_NO_THROW(pkt_clone->unpack());
+
+ EXPECT_EQ("duid=[01:02:02:02:02:03:03:03:03:03:03], tid=0x2312",
+ pkt_clone->getLabel());
+
+}
+
+// Test that empty client identifier option doesn't cause an exception from
+// Pkt6::getLabel.
+TEST_F(Pkt6Test, getLabelEmptyClientId) {
+ // Create a packet.
+ Pkt6 pkt(DHCPV6_SOLICIT, 0x2312);
+
+ // Add empty client identifier option.
+ pkt.addOption(OptionPtr(new Option(Option::V6, D6O_CLIENTID)));
+ EXPECT_EQ("duid=[no info], tid=0x2312", pkt.getLabel());
+}
+
+// Verifies that when the VIVSO, 17, has length that is too
+// short (i.e. less than sizeof(uint8_t), unpack throws a
+// SkipRemainingOptionsError exception
+TEST_F(Pkt6Test, truncatedVendorLength) {
+
+ // Build a good Solicit packet
+ Pkt6Ptr pkt = dhcp::test::PktCaptures::captureSolicitWithVIVSO();
+
+ // Unpacking should not throw
+ ASSERT_NO_THROW(pkt->unpack());
+ ASSERT_EQ(DHCPV6_SOLICIT, pkt->getType());
+
+ // VIVSO option should be there
+ OptionPtr x = pkt->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(x);
+ ASSERT_EQ(D6O_VENDOR_OPTS, x->getType());
+ OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x);
+ ASSERT_TRUE(vivso);
+ EXPECT_EQ(8, vivso->len()); // data + opt code + len
+
+ // Build a bad Solicit packet
+ pkt = dhcp::test::PktCaptures::captureSolicitWithTruncatedVIVSO();
+
+ // Unpack should throw Skip exception
+ ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError);
+ ASSERT_EQ(DHCPV6_SOLICIT, pkt->getType());
+
+ // VIVSO option should not be there
+ x = pkt->getOption(D6O_VENDOR_OPTS);
+ ASSERT_FALSE(x);
+}
+
+// Checks that unpacking correctly handles SkipThisOptionError by
+// omitting the offending option from the unpacked options.
+TEST_F(Pkt6Test, testSkipThisOptionError) {
+ // Get a packet. We're really interested in its on-wire
+ // representation only.
+ Pkt6Ptr donor(capture1());
+
+ // That's our original content. It should be sane.
+ OptionBuffer orig = donor->data_;
+
+ orig.push_back(0);
+ orig.push_back(41); // new-posix-timezone
+ orig.push_back(0);
+ orig.push_back(3); // length=3
+ orig.push_back(0x61); // data="abc"
+ orig.push_back(0x62);
+ orig.push_back(0x63);
+
+ orig.push_back(0);
+ orig.push_back(59); // bootfile-url
+ orig.push_back(0);
+ orig.push_back(3); // length=3
+ orig.push_back(0); // data= all nulls
+ orig.push_back(0);
+ orig.push_back(0);
+
+ orig.push_back(0);
+ orig.push_back(42); // new-tzdb-timezone
+ orig.push_back(0);
+ orig.push_back(3); // length=3
+ orig.push_back(0x64); // data="def"
+ orig.push_back(0x65);
+ orig.push_back(0x66);
+
+ // Unpacking should not throw.
+ Pkt6Ptr pkt(new Pkt6(&orig[0], orig.size()));
+ ASSERT_NO_THROW_LOG(pkt->unpack());
+
+ // We should have option 41 = "abc".
+ OptionPtr opt;
+ OptionStringPtr opstr;
+ ASSERT_TRUE(opt = pkt->getOption(41));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("abc", opstr->getValue());
+
+ // We should not have option 59.
+ EXPECT_FALSE(opt = pkt->getOption(59));
+
+ // We should have option 42 = "def".
+ ASSERT_TRUE(opt = pkt->getOption(42));
+ ASSERT_TRUE(opstr = boost::dynamic_pointer_cast<OptionString>(opt));
+ EXPECT_EQ(3, opstr->getValue().length());
+ EXPECT_EQ("def", opstr->getValue());
+}
+
+// This test verifies that LQ_QUERY_OPTIONs can be created, packed,
+// and unpacked correctly.
+TEST_F(Pkt6Test, lqQueryOption) {
+
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_LQ_QUERY);
+ ASSERT_TRUE(def) << "D6O_LQ_QUERY is not undefined";
+
+ OptionCustomPtr lq_option(new OptionCustom(*def, Option::V6));
+ ASSERT_TRUE(lq_option);
+
+ // Add query type (77 is technically not valid but better visually).
+ uint8_t orig_type = 77;
+ ASSERT_NO_THROW_LOG(lq_option->writeInteger<uint8_t>(77,0));
+
+ // Add query link address
+ IOAddress orig_link("2001:db8::1");
+ ASSERT_NO_THROW_LOG(lq_option->writeAddress(orig_link, 1));
+
+ // Now add supported sub-options: D6O_IAADR, D6O_CLIENTID, and D6O_ORO
+ // We are ingoring the fact that a query containing both a D6O_IAADDR
+ // and a D6O_CLIENTID is not technically valid. We only care that the
+ // sub options will pack and unpack.
+
+ // Add a D6O_IAADDR option
+ Option6IAAddrPtr orig_iaaddr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::2"), 0, 0));
+ ASSERT_TRUE(orig_iaaddr);
+ ASSERT_NO_THROW_LOG(lq_option->addOption(orig_iaaddr));
+
+ // Add a D6O_CLIENTID option
+ DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303")));
+ OptionPtr orig_clientid(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
+ duid->getDuid().begin(), duid->getDuid().end())));
+ ASSERT_NO_THROW_LOG(lq_option->addOption(orig_clientid));
+
+ // Add a D6O_ORO option
+ OptionUint16ArrayPtr orig_oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(orig_oro);
+ orig_oro->addValue(1234);
+ ASSERT_NO_THROW_LOG(lq_option->addOption(orig_oro));
+
+ // Now let's create a packet to which to add our new lq_option.
+ Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY, 0x2312));
+ orig->addOption(lq_option);
+ ASSERT_NO_THROW_LOG(orig->pack());
+
+ // Now create second packet,based on assembled data from the first one
+ Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*>
+ (orig->getBuffer().getData()),
+ orig->getBuffer().getLength()));
+
+ // Unpack it.
+ ASSERT_NO_THROW_LOG(clone->unpack());
+
+ // We should be able to find our query option.
+ OptionPtr opt;
+ opt = clone->getOption(D6O_LQ_QUERY);
+ ASSERT_TRUE(opt);
+ OptionCustomPtr clone_query = boost::dynamic_pointer_cast<OptionCustom>(opt);
+ ASSERT_TRUE(clone_query);
+
+ // Verify the query type is right.
+ uint8_t clone_type;
+ ASSERT_NO_THROW_LOG(clone_type = clone_query->readInteger<uint8_t>(0));
+ EXPECT_EQ(orig_type, clone_type);
+
+ // Verify the query link address is right.
+ IOAddress clone_link("::");
+ ASSERT_NO_THROW_LOG(clone_link = clone_query->readAddress(1));
+ EXPECT_EQ(orig_link, clone_link);
+
+ // Verify the suboptions.
+
+ // Verify the D6O_IAADDR option
+ opt = clone_query->getOption(D6O_IAADDR);
+ ASSERT_TRUE(opt);
+ Option6IAAddrPtr clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt);
+ ASSERT_TRUE(clone_iaaddr);
+ EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr));
+
+ // Verify the D6O_CLIENTID option
+ opt = clone_query->getOption(D6O_CLIENTID);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(*orig_clientid));
+
+ // Verify the D6O_ORO option
+ opt = clone_query->getOption(D6O_ORO);
+ ASSERT_TRUE(opt);
+ OptionUint16ArrayPtr clone_oro = boost::dynamic_pointer_cast<OptionUint16Array>(opt);
+ ASSERT_TRUE(clone_oro);
+ EXPECT_TRUE(clone_oro->equals(*orig_oro));
+}
+
+// This test verifies that D6O_CLIENT_DATA options can be created, packed,
+// and unpacked correctly.
+TEST_F(Pkt6Test, clientDataOption) {
+
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_CLIENT_DATA);
+ ASSERT_TRUE(def) << "D6O_CLIENT_DATA is not undefined";
+
+ OptionCustomPtr cd_option(new OptionCustom(*def, Option::V6));
+ ASSERT_TRUE(cd_option);
+
+ // Now add supported sub-options: D6O_CLIENTID, D6O_IAADR, D6O_IAAPREFIX,
+ // and D6O_CLTT
+
+ // Add a D6O_CLIENTID option
+ DuidPtr duid(new DUID(DUID::fromText("0102020202030303030303")));
+ OptionPtr orig_clientid(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
+ duid->getDuid().begin(), duid->getDuid().end())));
+ ASSERT_NO_THROW_LOG(cd_option->addOption(orig_clientid));
+
+ // Add a D6O_IAADDR option
+ Option6IAAddrPtr orig_iaaddr1(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::1"), 0, 0));
+ ASSERT_TRUE(orig_iaaddr1);
+ ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaaddr1));
+
+ // Add another D6O_IAADDR option
+ Option6IAAddrPtr orig_iaaddr2(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8::2"), 0, 0));
+ ASSERT_TRUE(orig_iaaddr2);
+ ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaaddr2));
+
+ // Add a D6O_IAPREFIX option
+ Option6IAAddrPtr orig_iaprefix1(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:1::"), 64, 0, 0));
+ ASSERT_TRUE(orig_iaprefix1);
+ ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaprefix1));
+
+ // Add another D6O_IAPREFIX option
+ Option6IAAddrPtr orig_iaprefix2(new Option6IAPrefix(D6O_IAPREFIX, IOAddress("2001:db8:2::"), 64, 0, 0));
+ ASSERT_TRUE(orig_iaprefix2);
+ ASSERT_NO_THROW_LOG(cd_option->addOption(orig_iaprefix2));
+
+ // Add a D6O_CLT_TIME option
+ OptionUint32Ptr orig_cltt(new OptionInt<uint32_t>(Option::V6, D6O_CLT_TIME, 4000));
+ ASSERT_TRUE(orig_cltt);
+ ASSERT_NO_THROW_LOG(cd_option->addOption(orig_cltt));
+
+ // Now let's create a packet to which to add our new client data option.
+ Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY_REPLY, 0x2312));
+ orig->addOption(cd_option);
+ ASSERT_NO_THROW_LOG(orig->pack());
+
+ // Now create second packet,based on assembled data from the first one
+ Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*>
+ (orig->getBuffer().getData()),
+ orig->getBuffer().getLength()));
+
+ // Unpack it.
+ ASSERT_NO_THROW_LOG(clone->unpack());
+
+ // We should be able to find our client data option.
+ OptionPtr opt;
+ opt = clone->getOption(D6O_CLIENT_DATA);
+ ASSERT_TRUE(opt);
+ OptionCustomPtr clone_cd_option = boost::dynamic_pointer_cast<OptionCustom>(opt);
+ ASSERT_TRUE(clone_cd_option);
+
+ // Verify the suboptions.
+ opt = clone_cd_option->getOption(D6O_CLIENTID);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equals(*orig_clientid));
+
+ // Verify the first address option
+ opt = clone_cd_option->getOption(D6O_IAADDR);
+ ASSERT_TRUE(opt);
+ Option6IAAddrPtr clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt);
+ ASSERT_TRUE(clone_iaaddr);
+ EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr1));
+
+ // Verify the second address option.
+ opt = clone_cd_option->getOption(D6O_IAADDR);
+ ASSERT_TRUE(opt);
+ clone_iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(opt);
+ ASSERT_TRUE(clone_iaaddr);
+ EXPECT_TRUE(clone_iaaddr->equals(*orig_iaaddr2));
+
+ // Verify the first prefix option.
+ opt = clone_cd_option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(opt);
+ Option6IAPrefixPtr clone_iaprefix = boost::dynamic_pointer_cast<Option6IAPrefix>(opt);
+ ASSERT_TRUE(clone_iaprefix);
+ EXPECT_TRUE(clone_iaprefix->equals(*orig_iaprefix1));
+
+ // Verify the second prefix option.
+ opt = clone_cd_option->getOption(D6O_IAPREFIX);
+ ASSERT_TRUE(opt);
+ clone_iaprefix = boost::dynamic_pointer_cast<Option6IAPrefix>(opt);
+ ASSERT_TRUE(clone_iaprefix);
+ EXPECT_TRUE(clone_iaprefix->equals(*orig_iaprefix2));
+
+ // Verify the CLT option.
+ opt = clone_cd_option->getOption(D6O_CLT_TIME);
+ ASSERT_TRUE(opt);
+ OptionUint32Ptr clone_cltt = boost::dynamic_pointer_cast<OptionUint32>(opt);
+ ASSERT_TRUE(clone_cltt);
+ EXPECT_TRUE(clone_cltt->equals(*orig_cltt));
+}
+
+// This test verifies that D6O_LQ_RELAY_DATA options can be created, packed,
+// and unpacked correctly.
+TEST_F(Pkt6Test, relayDataOption) {
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, D6O_LQ_RELAY_DATA);
+ ASSERT_TRUE(def) << "D6O_LQ_RELAY_DATA is not undefined";
+
+ OptionCustomPtr rd_option(new OptionCustom(*def, Option::V6));
+ ASSERT_TRUE(rd_option);
+
+ // Write out the peer address.
+ IOAddress orig_address("2001:db8::1");
+ rd_option->writeAddress(orig_address, 0);
+
+ // Write out the binary data (in real life this is a RELAY_FORW message)
+ std::vector<uint8_t>orig_data({ 01,02,03,04,05,06 });
+ rd_option->writeBinary(orig_data, 1);
+
+ // Now let's create a packet to which to add our new relay data option.
+ Pkt6Ptr orig(new Pkt6(DHCPV6_LEASEQUERY_REPLY, 0x2312));
+ orig->addOption(rd_option);
+ ASSERT_NO_THROW_LOG(orig->pack());
+
+ // Now create second packet,based on assembled data from the first one
+ Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*>
+ (orig->getBuffer().getData()),
+ orig->getBuffer().getLength()));
+ // Unpack it.
+ ASSERT_NO_THROW_LOG(clone->unpack());
+
+ // We should be able to find our client data option.
+ OptionPtr opt;
+ opt = clone->getOption(D6O_LQ_RELAY_DATA);
+ ASSERT_TRUE(opt);
+ OptionCustomPtr clone_rd_option = boost::dynamic_pointer_cast<OptionCustom>(opt);
+ ASSERT_TRUE(clone_rd_option);
+
+ // Verify the address field.
+ IOAddress clone_addr("::");
+ ASSERT_NO_THROW_LOG(clone_addr = clone_rd_option->readAddress(0));
+ EXPECT_EQ(orig_address, clone_addr);
+
+ // Verify the binary field
+ OptionBuffer clone_data;
+ ASSERT_NO_THROW_LOG(clone_data = clone_rd_option->readBinary(1));
+ EXPECT_EQ(orig_data, clone_data);
+}
+
+} // namespace
diff --git a/src/lib/dhcp/tests/pkt_captures.h b/src/lib/dhcp/tests/pkt_captures.h
new file mode 100644
index 0000000..7063e1a
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_captures.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2014-2019,2021 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 PKT_CAPTURES_H
+#define PKT_CAPTURES_H
+
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+class PktCaptures {
+public:
+
+ /// @brief returns captured DISCOVER that went through a relay
+ ///
+ /// See method code for a detailed explanation. This is a discover from
+ /// docsis3.0 device (Cable Modem)
+ ///
+ /// @return relayed DISCOVER
+ static isc::dhcp::Pkt4Ptr captureRelayedDiscover();
+
+ /// @brief returns captured DISCOVER that went through a relay
+ ///
+ /// See method code for a detailed explanation. This is a discover from
+ /// eRouter1.0 device (CPE device integrated with cable modem)
+ ///
+ /// @return relayed DISCOVER
+ static isc::dhcp::Pkt4Ptr captureRelayedDiscover2();
+
+ /// @brief returns captured DISCOVER that went through a relay
+ ///
+ /// See method code for a detailed explanation. This is a discover from
+ /// a buggy relay device with a bad suboption.
+ ///
+ /// @return relayed DISCOVER
+ static isc::dhcp::Pkt4Ptr captureBadRelayedDiscover();
+
+ /// @brief returns captured DISCOVER that contains a valid VIVSO option
+ ///
+ /// See method code for a detailed explanation.
+ ///
+ /// @return relayed DISCOVER
+ static isc::dhcp::Pkt4Ptr discoverWithValidVIVSO();
+
+ /// @brief returns captured DISCOVER that contains a truncated VIVSO option
+ ///
+ /// See method code for a detailed explanation.
+ ///
+ /// @return relayed DISCOVER
+ static isc::dhcp::Pkt4Ptr discoverWithTruncatedVIVSO();
+
+ /// @brief returns captured DISCOVER from Genexis hardware.
+ ///
+ /// This device in uncommon, because it doesn't send VIVSO in Discover, but
+ /// expects one in Offer.
+ /// @return DISCOVER.
+ static isc::dhcp::Pkt4Ptr discoverGenexis();
+
+ // see pkt_captures6.cc for descriptions
+ // The descriptions are too large and too closely related to the
+ // code, so it is kept in .cc rather than traditionally in .h
+ static isc::dhcp::Pkt6Ptr captureSimpleSolicit();
+ static isc::dhcp::Pkt6Ptr captureRelayedSolicit();
+ static isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit();
+ static isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit();
+ static isc::dhcp::Pkt6Ptr captureCableLabsShortVendorClass();
+ static isc::dhcp::Pkt6Ptr captureRelayed2xRSOO();
+ static isc::dhcp::Pkt6Ptr captureSolicitWithVIVSO();
+ static isc::dhcp::Pkt6Ptr captureSolicitWithTruncatedVIVSO();
+
+protected:
+ /// @brief Auxiliary method that sets Pkt6 fields
+ ///
+ /// Used to reconstruct captured packets. Sets UDP ports, interface names,
+ /// and other fields to some believable values.
+ /// @param pkt packet that will have its fields set
+ static void captureSetDefaultFields(const isc::dhcp::Pkt6Ptr& pkt);
+
+
+ /// @brief generates a DHCPv4 packet based on provided hex string
+ ///
+ /// @return created packet
+ static isc::dhcp::Pkt4Ptr packetFromCapture(const std::string& hex_string);
+
+ /// @brief sets default fields in a captured packet
+ ///
+ /// Sets UDP ports, addresses and interface.
+ ///
+ /// @param pkt packet to have default fields set
+ static void captureSetDefaultFields(const isc::dhcp::Pkt4Ptr& pkt);
+};
+
+}; // end of namespace isc::dhcp::test
+}; // end of namespace isc::dhcp
+}; // end of namespace isc
+
+#endif
diff --git a/src/lib/dhcp/tests/pkt_captures4.cc b/src/lib/dhcp/tests/pkt_captures4.cc
new file mode 100644
index 0000000..0f2f44f
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_captures4.cc
@@ -0,0 +1,387 @@
+// Copyright (C) 2013-2023 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 <asiolink/io_address.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <util/encode/hex.h>
+#include <string>
+
+/// @file pkt_captures4.cc
+///
+/// @brief contains packet captures imported from Wireshark
+///
+/// These are actual packets captured over wire. They are used in various
+/// tests.
+///
+/// The procedure to export Wireshark -> unit-tests is manual, but rather
+/// easy to follow:
+/// 1. Open a file in wireshark
+/// 2. Find the packet you want to export
+/// 3. There's a protocol stack (Frame, Ethernet, IPv6, UDP, DHCPv6, ...)
+/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream
+/// 5. Paste it as: string hex_string="[paste here]";
+/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
+/// 7. Make sure you describe the capture appropriately
+/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
+/// 9. To easily copy packet description, click File... -> Extract packet
+/// dissections -> as plain text file...
+/// (Make sure that the packet is expanded in the view. The text file will
+/// contain whatever expansion level you have in the graphical tree.)
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Pkt4Ptr PktCaptures::packetFromCapture(const std::string& hex_string) {
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt4Ptr pkt(new Pkt4(&bin[0], bin.size()));
+ captureSetDefaultFields(pkt);
+
+ return (pkt);
+}
+
+void PktCaptures::captureSetDefaultFields(const Pkt4Ptr& pkt) {
+ pkt->setRemotePort(546);
+ pkt->setRemoteAddr(IOAddress("10.0.0.2"));
+ pkt->setLocalPort(0);
+ pkt->setLocalAddr(IOAddress("10.0.0.1"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+}
+
+Pkt4Ptr PktCaptures::captureRelayedDiscover() {
+
+/* This is packet 1 from capture
+ dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
+
+User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
+ Source port: bootps (67)
+ Destination port: bootps (67)
+ Length: 541
+ Checksum: 0x2181 [validation disabled]
+
+Bootstrap Protocol
+ Message type: Boot Request (1)
+ Hardware type: Ethernet
+ Hardware address length: 6
+ Hops: 1
+ Transaction ID: 0x5d05478d
+ Seconds elapsed: 0
+ Bootp flags: 0x0000 (Unicast)
+ Client IP address: 0.0.0.0 (0.0.0.0)
+ Your (client) IP address: 0.0.0.0 (0.0.0.0)
+ Next server IP address: 0.0.0.0 (0.0.0.0)
+ Relay agent IP address: 10.254.226.1 (10.254.226.1)
+ Client MAC address: Netgear_b8:15:14 (20:e5:2a:b8:15:14)
+ Client hardware address padding: 00000000000000000000
+ Server host name not given
+ Boot file name not given
+ Magic cookie: DHCP
+ Option: (53) DHCP Message Type
+ Option: (55) Parameter Request List
+ Option: (60) Vendor class identifier (docsis3.0)
+ Option: (125) V-I Vendor-specific Information
+ - suboption 1 (Option Request): requesting option 2
+ - suboption 5 (Modem Caps): 117 bytes
+ Option: (43) Vendor-Specific Information (CableLabs)
+ Option: (61) Client identifier
+ Option: (57) Maximum DHCP Message Size
+ Option: (82) Agent Information Option
+ Option: (255) End */
+
+ string hex_string =
+ "010106015d05478d000000000000000000000000000000000afee20120e52ab8151400"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000638253633501013707"
+ "0102030407067d3c0a646f63736973332e303a7d7f0000118b7a010102057501010102"
+ "010303010104010105010106010107010f0801100901030a01010b01180c01010d0200"
+ "400e0200100f010110040000000211010014010015013f160101170101180104190104"
+ "1a01041b01201c01021d01081e01201f01102001102101022201012301002401002501"
+ "01260200ff2701012b59020345434d030b45434d3a45524f55544552040d3242523232"
+ "39553430303434430504312e3034060856312e33332e30330707322e332e3052320806"
+ "30303039354209094347333030304443520a074e657467656172fe01083d0fff2ab815"
+ "140003000120e52ab81514390205dc5219010420000002020620e52ab8151409090000"
+ "118b0401020300ff";
+
+ return (packetFromCapture(hex_string));
+}
+
+Pkt4Ptr PktCaptures::captureRelayedDiscover2() {
+
+/* This is packet 5 from capture
+ dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
+
+User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
+Bootstrap Protocol
+ Message type: Boot Request (1)
+ Hardware type: Ethernet (0x01)
+ Hardware address length: 6
+ Hops: 1
+ Transaction ID: 0x5d05478f
+ Seconds elapsed: 5
+ Bootp flags: 0x0000 (Unicast)
+ Client IP address: 0.0.0.0 (0.0.0.0)
+ Your (client) IP address: 0.0.0.0 (0.0.0.0)
+ Next server IP address: 0.0.0.0 (0.0.0.0)
+ Relay agent IP address: 10.254.226.1 (10.254.226.1)
+ Client MAC address: Netgear_b8:15:15 (20:e5:2a:b8:15:15)
+ Client hardware address padding: 00000000000000000000
+ Server host name not given
+ Boot file name not given
+ Magic cookie: DHCP
+ Option: (53) DHCP Message Type
+ Option: (55) Parameter Request List
+ Option: (43) Vendor-Specific Information
+ Option: (60) Vendor class identifier (eRouter1.0)
+ Option: (15) Domain Name
+ Option: (61) Client identifier
+ Option: (57) Maximum DHCP Message Size
+ Option: (82) Agent Information Option
+ Option: (255) End */
+
+ string hex_string =
+ "010106015d05478f000500000000000000000000000000000afee20120e52ab8151500"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000063825363350101370e"
+ "480102030406070c0f171a36337a2b63020745524f55544552030b45434d3a45524f55"
+ "544552040d324252323239553430303434430504312e3034060856312e33332e303307"
+ "07322e332e305232080630303039354209094347333030304443520a074e6574676561"
+ "720f0745524f555445523c0a65526f75746572312e300f14687364312e70612e636f6d"
+ "636173742e6e65742e3d0fff2ab815150003000120e52ab81515390205dc5219010420"
+ "000002020620e52ab8151409090000118b0401020300ff";
+
+ return (packetFromCapture(hex_string));
+}
+
+Pkt4Ptr PktCaptures::captureBadRelayedDiscover() {
+
+/* Modified packet 1 with a bad RAI.
+
+Bootstrap Protocol
+ Message type: Boot Request (1)
+ Hardware type: Ethernet
+ Hardware address length: 6
+ Hops: 1
+ Transaction ID: 0x5d05478d
+ Seconds elapsed: 0
+ Bootp flags: 0x0000 (Unicast)
+ Client IP address: 0.0.0.0 (0.0.0.0)
+ Your (client) IP address: 0.0.0.0 (0.0.0.0)
+ Next server IP address: 0.0.0.0 (0.0.0.0)
+ Relay agent IP address: 10.254.226.1 (10.254.226.1)
+ Client MAC address: Netgear_b8:15:14 (20:e5:2a:b8:15:14)
+ Client hardware address padding: 00000000000000000000
+ Server host name not given
+ Boot file name not given
+ Magic cookie: DHCP
+ Option: (53) DHCP Message Type
+ Option: (55) Parameter Request List
+ Option: (60) Vendor class identifier (docsis3.0)
+ Option: (125) V-I Vendor-specific Information
+ - suboption 1 (Option Request): requesting option 2
+ - suboption 5 (Modem Caps): 117 bytes
+ Option: (43) Vendor-Specific Information (CableLabs)
+ Option: (61) Client identifier
+ Option: (57) Maximum DHCP Message Size
+ Option: (82) Agent Information Option */
+
+ string hex_string =
+ "010106015d05478d000000000000000000000000000000000afee20120e52ab8151400"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000638253633501013707"
+ "0102030407067d3c0a646f63736973332e303a7d7f0000118b7a010102057501010102"
+ "010303010104010105010106010107010f0801100901030a01010b01180c01010d0200"
+ "400e0200100f010110040000000211010014010015013f160101170101180104190104"
+ "1a01041b01201c01021d01081e01201f01102001102101022201012301002401002501"
+ "01260200ff2701012b59020345434d030b45434d3a45524f55544552040d3242523232"
+ "39553430303434430504312e3034060856312e33332e30330707322e332e3052320806"
+ "30303039354209094347333030304443520a074e657467656172fe01083d0fff2ab815"
+ "140003000120e52ab81514390205dc52205141000102030405060708090a0b0c0d0e0f"
+ "101112131415161718191a1b1c1d";
+
+ return (packetFromCapture(hex_string));
+}
+
+Pkt4Ptr PktCaptures::discoverWithValidVIVSO() {
+/* DISCOVER that contains a valid VIVSO option 125
+User Datagram Protocol, Src Port: 67, Dst Port: 67
+Bootstrap Protocol (Discover)
+ Message type: Boot Request (1)
+ Hardware type: Ethernet (0x01)
+ Hardware address length: 6
+ Hops: 1
+ Transaction ID: 0x2d5d43cb
+ Seconds elapsed: 0
+ Bootp flags: 0x8000, Broadcast flag (Broadcast)
+ Client IP address: 0.0.0.0
+ Your (client) IP address: 0.0.0.0
+ Next server IP address: 0.0.0.0
+ Relay agent IP address: 10.206.80.1
+ Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af)
+ Client hardware address padding: 00000000000000000000
+ Server host name not given
+ Boot file name not given
+ Magic cookie: DHCP
+ Option: (53) DHCP Message Type (Discover)
+ Option: (55) Parameter Request List
+ Option: (60) Vendor class identifier
+ Option: (125) V-I Vendor-specific Information
+ Option: (43) Vendor-Specific Information (CableLabs)
+ Option: (61) Client identifier
+ Option: (57) Maximum DHCP Message Size
+ Option: (82) Agent Information Option
+ Option: (255) End
+*/
+ string hex_string =
+ "010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000063825363350"
+ "10137070102030407067d3c0a646f63736973332e303a7d850000118b80010102057b"
+ "01010102010303010104010105010106010107010f0801100901030a01010b01180c0"
+ "1010d0201000e0201000f010110040000000211010113010114010015013f16010117"
+ "01011801041901041a01041b01201c01021d01081e01201f011020011021010222010"
+ "1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552"
+ "4f5554455208030020400418333936373739343234343335353037373031303134303"
+ "035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f"
+ "5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727"
+ "06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005"
+ "03f802067896845ef7af090b0000118b06010401020300ff";
+
+ return (packetFromCapture(hex_string));
+}
+
+Pkt4Ptr PktCaptures::discoverWithTruncatedVIVSO() {
+/* DISCOVER that contains VIVSO option 125 with an INVALID length of 01
+User Datagram Protocol, Src Port: 67, Dst Port: 67
+Bootstrap Protocol (Discover)
+ Message type: Boot Request (1)
+ Hardware type: Ethernet (0x01)
+ Hardware address length: 6
+ Hops: 1
+ Transaction ID: 0x2d5d43cb
+ Seconds elapsed: 0
+ Bootp flags: 0x8000, Broadcast flag (Broadcast)
+ Client IP address: 0.0.0.0
+ Your (client) IP address: 0.0.0.0
+ Next server IP address: 0.0.0.0
+ Relay agent IP address: 10.206.80.1
+ Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af)
+ Client hardware address padding: 00000000000000000000
+ Server host name not given
+ Boot file name not given
+ Magic cookie: DHCP
+ Option: (53) DHCP Message Type (Discover)
+ Option: (55) Parameter Request List
+ Option: (60) Vendor class identifier
+ Option: (125) V-I Vendor-specific Information
+ Option: (43) Vendor-Specific Information (CableLabs)
+ Option: (61) Client identifier
+ Option: (57) Maximum DHCP Message Size
+ Option: (82) Agent Information Option
+ Option: (255) End
+*/
+ string hex_string =
+ "010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000000000000000"
+ "000000000000000000000000000000000000000000000000000000000063825363350"
+ "10137070102030407067d3c0a646f63736973332e303a7d010000118b80010102057b"
+ "01010102010303010104010105010106010107010f0801100901030a01010b01180c0"
+ "1010d0201000e0201000f010110040000000211010113010114010015013f16010117"
+ "01011801041901041a01041b01201c01021d01081e01201f011020011021010222010"
+ "1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552"
+ "4f5554455208030020400418333936373739343234343335353037373031303134303"
+ "035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f"
+ "5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727"
+ "06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005"
+ "03f802067896845ef7af090b0000118b06010401020300ff";
+
+ return (packetFromCapture(hex_string));
+}
+
+Pkt4Ptr PktCaptures::discoverGenexis() {
+
+/* Bootstrap Protocol (Discover)
+ Message type: Boot Request (1)
+ Hardware type: Ethernet (0x01)
+ Hardware address length: 6
+ Hops: 0
+ Transaction ID: 0x946a5b5a
+ Seconds elapsed: 0
+ Bootp flags: 0x8000, Broadcast flag (Broadcast)
+ Client IP address: 0.0.0.0
+ Your (client) IP address: 0.0.0.0
+ Next server IP address: 0.0.0.0
+ Relay agent IP address: 0.0.0.0
+ Client MAC address: GenexisB_6a:1a:93 (00:0f:94:6a:1a:93)
+ Client hardware address padding: 00000000000000000000
+ Server host name not given
+ Boot file name not given
+ Magic cookie: DHCP
+ Option: (53) DHCP Message Type (Discover)
+ Option: (60) Vendor class identifier (HMC1000.v1.3.0-R,Element-P1090,genexis.eu)
+ Option: (51) IP Address Lease Time
+ Option: (55) Parameter Request List
+ Option: (255) End
+ Padding: 000000000000000000000000000000000000000000000000... */
+
+ string hex_string =
+ "01010600946a5b5a0000800000000000000000000000000000000000000f946a1a9300"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000638253633501013c29"
+ "484d43313030302e76312e332e302d522c456c656d656e742d50313039302c67656e65"
+ "7869732e65753304ffffffff37040103067dff00000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "0000000000000000000000000000000000000000000000000000000000000000000000"
+ "00000000000000000000000000000000";
+ return (packetFromCapture(hex_string));
+}
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp/tests/pkt_captures6.cc b/src/lib/dhcp/tests/pkt_captures6.cc
new file mode 100644
index 0000000..01e11b7
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_captures6.cc
@@ -0,0 +1,509 @@
+// Copyright (C) 2013-2023 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 <dhcp/pkt6.h>
+#include <dhcp/tests/pkt_captures.h>
+#include <util/encode/hex.h>
+#include <string>
+
+/// @file pkt_captures6.cc
+///
+/// @brief contains packet captures imported from Wireshark
+///
+/// These are actual packets captured over wire. They are used in various
+/// tests.
+///
+/// The procedure to export Wireshark -> unit-tests is manual, but rather
+/// easy to follow:
+/// 1. Open a file in wireshark
+/// 2. Find the packet you want to export
+/// 3. There's a protocol stack (Frame, Ethernet, IPv6, UDP, DHCPv6, ...)
+/// 4. Right click on DHCPv6 -> Copy -> Bytes -> Hex Stream
+/// 5. Paste it as: string hex_string="[paste here]";
+/// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
+/// 7. Make sure you describe the capture appropriately
+/// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
+/// 9. To easily copy packet description, click File... -> Extract packet
+/// dissections -> as plain text file...
+/// (Make sure that the packet is expanded in the view. The text file will
+/// contain whatever expansion level you have in the graphical tree.)
+
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void PktCaptures::captureSetDefaultFields(const Pkt6Ptr& pkt) {
+ pkt->setRemotePort(546);
+ pkt->setRemoteAddr(IOAddress("fe80::1"));
+ pkt->setLocalPort(0);
+ pkt->setLocalAddr(IOAddress("ff02::1:2"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+}
+
+// This function returns buffer for very simple Solicit
+Pkt6Ptr PktCaptures::captureSimpleSolicit() {
+ uint8_t data[] = {
+ 1, // type 1 = SOLICIT
+ 0xca, 0xfe, 0x01, // trans-id = 0xcafe01
+ 0, 1, // option type 1 (client-id)
+ 0, 10, // option length 10
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // DUID
+ 0, 3, // option type 3 (IA_NA)
+ 0, 12, // option length 12
+ 0, 0, 0, 1, // iaid = 1
+ 0, 0, 0, 0, // T1 = 0
+ 0, 0, 0, 0 // T2 = 0
+ };
+
+ Pkt6Ptr pkt(new Pkt6(data, sizeof(data)));
+ captureSetDefaultFields(pkt);
+
+ return (pkt);
+}
+
+Pkt6Ptr PktCaptures::captureRelayedSolicit() {
+
+ // This is a very simple relayed SOLICIT message:
+ // RELAY-FORW
+ // - interface-id
+ // - relay-message
+ // - SOLICIT
+ // - client-id
+ // - IA_NA (iaid=1, t1=0, t2=0)
+ // - ORO (7)
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c0500000000000000000000000000000000fc00000000000000000000000000000900"
+ "12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c"
+ "000000010000000000000000000600020007";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ captureSetDefaultFields(pkt);
+
+ return (pkt);
+}
+
+/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 cable modem)
+Pkt6Ptr PktCaptures::captureDocsisRelayedSolicit() {
+
+ // This is an actual DOCSIS packet
+ // RELAY-FORW (12)
+ // - Relay Message
+ // - SOLICIT (1)
+ // - client-id
+ // - IA_NA (iaid=7f000788, t2=0, t2=0)
+ // - IAAddress (::, pref=0,valid=0)
+ // - rapid-commit
+ // - elapsed
+ // - ORO
+ // - Reconfigure-accept
+ // - Vendor-Class ("docsis3.0")
+ // - Vendor-specific Info
+ // - subopt 1: Option request = 32,33,34,37,38
+ // - subopt 36: Device identifier
+ // - subopt 35: TLV5
+ // - subopt 2: Device type = ECM
+ // - subopt 3: Embedded components
+ // - subopt 4: Serial Number
+ // - subopt 5: Hardware version
+ // - subopt 6: Software version
+ // - subopt 7: Boot ROM Version
+ // - subopt 8: Organization Unique Identifier
+ // - subopt 9: Model Number
+ // - subopt 10: Vendor Name (Netgear)
+ // - subopt 15: unknown
+ // - Interface-Id
+ // - Vendor-specific Information
+ // - Suboption 1025: CMTS capabilities
+ // - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800"
+ "090189010d397f0001000a00030001100d7f000788000300287f000788000000000000"
+ "000000050018000000000000000000000000000000000000000000000000000e000000"
+ "0800020000000600020011001400000010000f0000118b0009646f63736973332e3000"
+ "1101200000118b0001000a0020002100220025002600240006100d7f00078800230081"
+ "0101010201030301010401010501010601010701180801080901000a01010b01180c01"
+ "010d0200400e0200100f01011004000000021101011301011401001501381601011701"
+ "011801041901041a01041b01281c01021d01081e01201f011020011821010222010123"
+ "010124011825010126020040270101120701100d7f00078a0002000345434d0003000b"
+ "45434d3a45524f555445520004000d3335463132395550303030353200050004332e31"
+ "310006000956312e30312e31315400070013505350552d426f6f7420312e302e31362e"
+ "323200080006303030393542000900084347343030305444000a00074e657467656172"
+ "000f000745524f5554455200120012427531264361312f3000100d7f00078800000011"
+ "00160000118b040100040102030004020006100d7f000788";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ captureSetDefaultFields(pkt);
+ return (pkt);
+}
+
+/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 eRouter)
+Pkt6Ptr PktCaptures::captureeRouterRelayedSolicit() {
+
+/* Packet description exported from wireshark:
+DHCPv6
+ Message type: Relay-forw (12)
+ Hopcount: 0
+ Link address: 2001:558:ffa8::1 (2001:558:ffa8::1)
+ Peer address: fe80::22e5:2aff:feb8:1515 (fe80::22e5:2aff:feb8:1515)
+ Relay Message
+ Option: Relay Message (9)
+ Length: 241
+ Value: 01a90044000e000000140000000600080011001700180019...
+ DHCPv6
+ Message type: Solicit (1)
+ Transaction ID: 0xa90044
+ Rapid Commit
+ Option: Rapid Commit (14)
+ Length: 0
+ Reconfigure Accept
+ Option: Reconfigure Accept (20)
+ Length: 0
+ Option Request
+ Option: Option Request (6)
+ Length: 8
+ Value: 0011001700180019
+ Requested Option code: Vendor-specific Information (17)
+ Requested Option code: DNS recursive name server (23)
+ Requested Option code: Domain Search List (24)
+ Requested Option code: Identity Association for Prefix Delegation (25)
+ Vendor Class
+ Option: Vendor Class (16)
+ Length: 16
+ Value: 0000118b000a65526f75746572312e30
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ vendor-class-data: eRouter1.0
+ Vendor-specific Information
+ Option: Vendor-specific Information (17)
+ Length: 112
+ Value: 0000118b0002000745524f555445520003000b45434d3a45...
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ Suboption: Device Type = (2)"EROUTER"
+ Suboption: Embedded Components = (3)"ECM:EROUTER"
+ Suboption: Serial Number = (4)"2BR229U40044C"
+ Suboption: Hardware Version = (5)"1.04"
+ Suboption: Software Version = (6)"V1.33.03"
+ Suboption: Boot ROM Version = (7)"2.3.0R2"
+ Suboption: Organization Unique Identifier = (8)"00095B"
+ Suboption: Model Number = (9)"CG3000DCR"
+ Suboption: Vendor Name = (10)"Netgear"
+ Client Identifier
+ Option: Client Identifier (1)
+ Length: 10
+ Value: 0003000120e52ab81515
+ DUID: 0003000120e52ab81515
+ DUID Type: link-layer address (3)
+ Hardware type: Ethernet (1)
+ Link-layer address: 20:e5:2a:b8:15:15
+ Identity Association for Prefix Delegation
+ Option: Identity Association for Prefix Delegation (25)
+ Length: 41
+ Value: 2ab815150000000000000000001a00190000000000000000...
+ IAID: 2ab81515
+ T1: 0
+ T2: 0
+ IA Prefix
+ Option: IA Prefix (26)
+ Length: 25
+ Value: 000000000000000038000000000000000000000000000000...
+ Preferred lifetime: 0
+ Valid lifetime: 0
+ Prefix length: 56
+ Prefix address: :: (::)
+ Identity Association for Non-temporary Address
+ Option: Identity Association for Non-temporary Address (3)
+ Length: 12
+ Value: 2ab815150000000000000000
+ IAID: 2ab81515
+ T1: 0
+ T2: 0
+ Elapsed time
+ Option: Elapsed time (8)
+ Length: 2
+ Value: 0000
+ Elapsed time: 0 ms
+ Vendor-specific Information
+ Option: Vendor-specific Information (17)
+ Length: 22
+ Value: 0000118b0402000620e52ab815140401000401020300
+ Enterprise ID: Cable Television Laboratories, Inc. (4491)
+ Suboption: CM MAC Address Option = (1026)20:e5:2a:b8:15:14
+ Suboption: CMTS Capabilities Option : (1025)
+ Interface-Id
+ Option: Interface-Id (18)
+ Length: 4
+ Value: 00000022
+ Interface-ID:
+ Remote Identifier
+ Option: Remote Identifier (37)
+ Length: 14
+ Value: 0000101300015c228d4110000122
+ Enterprise ID: Arris Interactive LLC (4115)
+ Remote-ID: 00015c228d4110000122
+ DHCP option 53
+ Option: Unknown (53)
+ Length: 10
+ Value: 0003000100015c228d3d
+ DUID: 0003000100015c228d3d
+ DUID Type: link-layer address (3)
+ Hardware type: Ethernet (1)
+ Link-layer address: 00:01:5c:22:8d:3d */
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c0020010558ffa800000000000000000001fe8000000000000022e52afffeb8151500"
+ "0900f101a90044000e000000140000000600080011001700180019001000100000118b"
+ "000a65526f75746572312e30001100700000118b0002000745524f555445520003000b"
+ "45434d3a45524f555445520004000d3242523232395534303034344300050004312e30"
+ "340006000856312e33332e303300070007322e332e3052320008000630303039354200"
+ "090009434733303030444352000a00074e6574676561720001000a0003000120e52ab8"
+ "1515001900292ab815150000000000000000001a001900000000000000003800000000"
+ "0000000000000000000000000003000c2ab81515000000000000000000080002000000"
+ "1100160000118b0402000620e52ab81514040100040102030000120004000000220025"
+ "000e0000101300015c228d41100001220035000a0003000100015c228d3d";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ captureSetDefaultFields(pkt);
+ return (pkt);
+}
+
+Pkt6Ptr PktCaptures::captureCableLabsShortVendorClass() {
+ // This is a simple non-relayed Solicit:
+ // - client-identifier
+ // - IA_NA
+ // - Vendor Class (4 bytes)
+ // - enterprise-id 4491
+ // - Vendor-specific Information
+ // - enterprise-id 4491
+ std::string hex_string =
+ "01671cb90001000e0001000152ea903a08002758f1e80003000c00004bd10000000000"
+ "000000001000040000118b0011000a0000118b000100020020";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ captureSetDefaultFields(pkt);
+ return (pkt);
+
+}
+
+/// @brief creates doubly relayed solicit message
+///
+/// This is a traffic capture exported from wireshark and manually modified
+/// to include necessary options (RSOO). It includes a SOLICIT message
+/// that passed through two relays. It is especially interesting,
+/// because of the following properties:
+/// - double encapsulation
+/// - first relay inserts relay-msg before extra options
+/// - second relay inserts relay-msg after extra options
+/// - both relays are from different vendors
+/// - interface-id are different for each relay
+/// - first relay inserts valid remote-id
+/// - second relay inserts remote-id with empty vendor data
+/// - the solicit message requests for custom options in ORO
+/// - there are option types in RELAY-FORW that do not appear in SOLICIT
+/// - there are option types in SOLICT that do not appear in RELAY-FORW
+///
+/// RELAY-FORW
+/// - relay message option
+/// - RELAY-FORW
+/// - rsoo (66)
+/// - option 255 (len 4)
+/// - option 256 (len 9)
+/// - remote-id option (37)
+/// - relay message option
+/// - SOLICIT
+/// - client-id option
+/// - ia_na option
+/// - elapsed time
+/// - ORO
+/// - interface-id option (18)
+/// - remote-id option (37)
+///
+/// The original capture was posted to dibbler users mailing list.
+///
+/// @return created double relayed SOLICIT message
+isc::dhcp::Pkt6Ptr PktCaptures::captureRelayed2xRSOO() {
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a9"
+ "0009007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c18a9"
+ "00420015" // RSOO (includes ...
+ "00ff000401020304" // ... option 255, len 4, content 0x01020304
+ "01000009010203040506070809" // ... option 256, len 9, content 0x010203040506070809
+ "0025000400000de9" // remote-id
+ "00090036" // relay-msg, len 54
+ "016b4fe2" // solicit"
+ "0001000e0001000118b033410000215c18a9" // client-id
+ "0003000c00000001ffffffffffffffff" // ia-na
+ "000800020000"
+ "00060006001700f200f3"
+ "0012001c4953414d3134347c3239397c697076367c6e743a76703a313a" // vendor-class
+ "313130002500120000197f0001000118b033410000215c18a9";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ pkt->setRemotePort(547);
+ pkt->setRemoteAddr(IOAddress("fe80::1234"));
+ pkt->setLocalPort(547);
+ pkt->setLocalAddr(IOAddress("ff05::1:3"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+ return (pkt);
+}
+
+isc::dhcp::Pkt6Ptr PktCaptures::captureSolicitWithVIVSO() {
+
+ // Message type: Solicit (1)
+ // Transaction ID: 0xba048e
+ // Client Identifier
+ // Option: Client Identifier (1)
+ // Length: 10
+ // Value: 0003000108002725d3f4
+ // DUID: 0003000108002725d3f4
+ // DUID Type: link-layer address (3)
+ // Hardware type: Ethernet (1)
+ // Link-layer address: 08:00:27:25:d3:f4
+ // Identity Association for Non-temporary Address
+ // Option: Identity Association for Non-temporary Address (3)
+ // Length: 40
+ // Value: 00aabbcc0000000000000000000500180000000000000000...
+ // IAID: 00aabbcc
+ // T1: 0
+ // T2: 0
+ // IA Address
+ // Option: IA Address (5)
+ // Length: 24
+ // Value: 000000000000000000000000000000000000000000000000
+ // IPv6 address: ::
+ // Preferred lifetime: 0
+ // Valid lifetime:
+ // Option Request
+ // Option: Option Request (6)
+ // Length: 6
+ // Value: 00d100d2000c
+ // Vendor-specific Information
+ // Option: Vendor-specific Information (17)
+ // Length: 4
+ // Value: 00001e61
+ // Enterprise ID: E-DYNAMICS.ORG (7777)
+ string hex_string =
+ "01ba048e0001000a0003000108002725d3f40003002800aabbcc"
+ "00000000000000000005001800000000000000000000000000000"
+ "00000000000000000000006000600d100d2000c0011000400001e61";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ pkt->setRemotePort(547);
+ pkt->setRemoteAddr(IOAddress("fe80::1234"));
+ pkt->setLocalPort(547);
+ pkt->setLocalAddr(IOAddress("ff05::1:3"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+ return (pkt);
+}
+
+isc::dhcp::Pkt6Ptr PktCaptures::captureSolicitWithTruncatedVIVSO() {
+
+ // Message type: Solicit (1)
+ // Transaction ID: 0xba048e
+ // Client Identifier
+ // Option: Client Identifier (1)
+ // Length: 10
+ // Value: 0003000108002725d3f4
+ // DUID: 0003000108002725d3f4
+ // DUID Type: link-layer address (3)
+ // Hardware type: Ethernet (1)
+ // Link-layer address: 08:00:27:25:d3:f4
+ // Identity Association for Non-temporary Address
+ // Option: Identity Association for Non-temporary Address (3)
+ // Length: 40
+ // Value: 00aabbcc0000000000000000000500180000000000000000...
+ // IAID: 00aabbcc
+ // T1: 0
+ // T2: 0
+ // IA Address
+ // Option: IA Address (5)
+ // Length: 24
+ // Value: 000000000000000000000000000000000000000000000000
+ // IPv6 address: ::
+ // Preferred lifetime: 0
+ // Valid lifetime:
+ // Option Request
+ // Option: Option Request (6)
+ // Length: 6
+ // Value: 00d100d2000c
+ // Vendor-specific Information
+ // Option: Vendor-specific Information (17)
+ // Length: 1 <-------- length too short!
+ // Value: 00001e61
+ // Enterprise ID: E-DYNAMICS.ORG (7777)
+ string hex_string =
+ "01ba048e0001000a0003000108002725d3f40003002800aabbcc"
+ "00000000000000000005001800000000000000000000000000000"
+ "00000000000000000000006000600d100d2000c0011000100001e61";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+ pkt->setRemotePort(547);
+ pkt->setRemoteAddr(IOAddress("fe80::1234"));
+ pkt->setLocalPort(547);
+ pkt->setLocalAddr(IOAddress("ff05::1:3"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+ return (pkt);
+}
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.cc b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc
new file mode 100644
index 0000000..b582cc4
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.cc
@@ -0,0 +1,48 @@
+// Copyright (C) 2014-2022 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 <fcntl.h>
+
+#include <dhcp/tests/pkt_filter6_test_stub.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilter6TestStub::PktFilter6TestStub() : open_socket_callback_() {
+}
+
+SocketInfo
+PktFilter6TestStub::openSocket(const Iface&,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool) {
+ if (open_socket_callback_) {
+ open_socket_callback_(port);
+ }
+
+ return (SocketInfo(addr, port, 0));
+}
+
+Pkt6Ptr
+PktFilter6TestStub::receive(const SocketInfo&) {
+ return Pkt6Ptr();
+}
+
+bool
+PktFilter6TestStub::joinMulticast(int, const std::string&,
+ const std::string &) {
+ return (true);
+}
+
+int
+PktFilter6TestStub::send(const Iface&, uint16_t, const Pkt6Ptr&) {
+ return (0);
+}
+
+}
+}
+}
diff --git a/src/lib/dhcp/tests/pkt_filter6_test_stub.h b/src/lib/dhcp/tests/pkt_filter6_test_stub.h
new file mode 100644
index 0000000..964dc89
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter6_test_stub.h
@@ -0,0 +1,107 @@
+// Copyright (C) 2014-2022 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 PKT_FILTER6_TEST_STUB_H
+#define PKT_FILTER6_TEST_STUB_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter6.h>
+#include <dhcp/pkt6.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief An open socket callback that can be use for a testing purposes.
+///
+/// @param port Port number to bind socket to.
+typedef std::function<void(uint16_t port)> PktFilter6OpenSocketCallback;
+
+/// @brief A stub implementation of the PktFilter6 class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::PktFilter6
+/// class. It is used by unit tests, which test protected methods of the
+/// @c isc::dhcp::test::PktFilter6 class. The implemented abstract methods are
+/// no-op.
+class PktFilter6TestStub : public PktFilter6 {
+public:
+
+ /// @brief Constructor.
+ PktFilter6TestStub();
+
+ /// @brief Simulate opening of the socket.
+ ///
+ /// This function simulates opening a primary socket. In reality, it doesn't
+ /// open a socket but the socket descriptor returned in the SocketInfo
+ /// structure is always set to 0.
+ ///
+ /// @param iface An interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number to bind socket to.
+ /// @param join_multicast A flag which indicates if the socket should be
+ /// configured to join multicast (if true).
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return A SocketInfo structure with the socket descriptor set to 0. The
+ /// fallback socket descriptor is set to a negative value.
+ virtual SocketInfo openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool join_multicast);
+
+ /// @brief Simulate reception of the DHCPv6 message.
+ ///
+ /// @param socket_info A structure holding socket information.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return always a NULL object.
+ virtual Pkt6Ptr receive(const SocketInfo& socket_info);
+
+ /// @brief Simulates sending a DHCPv6 message.
+ ///
+ /// This function does nothing.
+ ///
+ /// @param iface An interface to be used to send DHCPv6 message.
+ /// @param port A port used to send a message.
+ /// @param pkt A DHCPv6 to be sent.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return 0.
+ virtual int send(const Iface& iface, uint16_t port, const Pkt6Ptr& pkt);
+
+ /// @brief Simulate joining IPv6 multicast group on a socket.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @param sock A socket descriptor (socket must be bound).
+ /// @param ifname An interface name (for link-scoped multicast groups).
+ /// @param mcast A multicast address to join (e.g. "ff02::1:2").
+ ///
+ /// @return true if multicast join was successful
+ static bool joinMulticast(int sock, const std::string& ifname,
+ const std::string & mcast);
+
+ /// @brief Set an open socket callback. Use it for testing
+ /// purposes, e.g. counting the number of calls or throwing an exception.
+ void setOpenSocketCallback(PktFilter6OpenSocketCallback callback) {
+ open_socket_callback_ = callback;
+ }
+
+private:
+
+ /// @brief The callback used when opening socket.
+ PktFilter6OpenSocketCallback open_socket_callback_;
+};
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER6_TEST_STUB_H
diff --git a/src/lib/dhcp/tests/pkt_filter6_test_utils.cc b/src/lib/dhcp/tests/pkt_filter6_test_utils.cc
new file mode 100644
index 0000000..83afc1c
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter6_test_utils.cc
@@ -0,0 +1,206 @@
+// Copyright (C) 2013-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 <asiolink/io_address.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/pkt_filter6_test_utils.h>
+
+#include <boost/foreach.hpp>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilter6Test::PktFilter6Test(const uint16_t port)
+ : port_(port),
+ sock_info_(isc::asiolink::IOAddress("::1"), port, -1, -1),
+ send_msg_sock_(-1) {
+ // Initialize ifname_ and ifindex_.
+ loInit();
+ // Initialize test_message_.
+ initTestMessage();
+}
+
+PktFilter6Test::~PktFilter6Test() {
+ // Cleanup after each test. This guarantees
+ // that the sockets do not hang after a test.
+ if (sock_info_.sockfd_ >= 0) {
+ close(sock_info_.sockfd_);
+ }
+ if (sock_info_.fallbackfd_ >=0) {
+ close(sock_info_.fallbackfd_);
+ }
+ if (send_msg_sock_ >= 0) {
+ close(send_msg_sock_);
+ }
+}
+
+void
+PktFilter6Test::initTestMessage() {
+ // Let's create a DHCPv6 message instance.
+ test_message_.reset(new Pkt6(DHCPV6_ADVERTISE, 123));
+
+ // Set required fields.
+ test_message_->setLocalAddr(IOAddress("::1"));
+ test_message_->setRemoteAddr(IOAddress("::1"));
+ test_message_->setRemotePort(port_);
+ test_message_->setLocalPort(port_ + 1);
+ test_message_->setIndex(ifindex_);
+ test_message_->setIface(ifname_);
+
+ try {
+ test_message_->pack();
+ } catch (const isc::Exception& ex) {
+ ADD_FAILURE() << "failed to create test message for PktFilter6Test";
+ }
+}
+
+void
+PktFilter6Test::loInit() {
+ if (if_nametoindex("lo") > 0) {
+ ifname_ = "lo";
+ ifindex_ = if_nametoindex("lo");
+
+ } else if (if_nametoindex("lo0") > 0) {
+ ifname_ = "lo0";
+ ifindex_ = if_nametoindex("lo0");
+
+ } else {
+ std::cout << "Failed to detect loopback interface. Neither "
+ << "lo nor lo0 worked. Giving up." << std::endl;
+ FAIL();
+
+ }
+}
+
+void
+PktFilter6Test::sendMessage() {
+ // DHCPv6 message will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("::1");
+
+ // Initialize the source address and port.
+ struct sockaddr_in6 addr6;
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_port = htons(port_);
+ memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
+
+ // Open socket and bind to source address and port.
+ send_msg_sock_ = socket(AF_INET6, SOCK_DGRAM, 0);
+ ASSERT_GE(send_msg_sock_, 0);
+
+ ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr6,
+ sizeof(addr6)), 0);
+
+ // Set the destination address and port.
+ struct sockaddr_in6 dest_addr6;
+ memset(&dest_addr6, 0, sizeof(sockaddr_in6));
+ dest_addr6.sin6_family = AF_INET6;
+ dest_addr6.sin6_port = htons(port_ + 1);
+ memcpy(&dest_addr6.sin6_addr, &addr.toBytes()[0], 16);
+
+ // Initialize the message header structure, required by sendmsg.
+ struct msghdr m;
+ memset(&m, 0, sizeof(m));
+ m.msg_name = &dest_addr6;
+ m.msg_namelen = sizeof(dest_addr6);
+ // The iovec structure holds the packet data.
+ struct iovec v;
+ memset(&v, 0, sizeof(v));
+ v.iov_base = const_cast<void *>(test_message_->getBuffer().getData());
+ v.iov_len = test_message_->getBuffer().getLength();
+ // Assign the iovec to msghdr structure.
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+ // We should be able to send the whole message. The sendmsg function should
+ // return the number of bytes sent, which is equal to the size of our
+ // message.
+ ASSERT_EQ(sendmsg(send_msg_sock_, &m, 0),
+ test_message_->getBuffer().getLength());
+ close(send_msg_sock_);
+ send_msg_sock_ = -1;
+
+}
+
+void
+PktFilter6Test::testDgramSocket(const int sock) const {
+ // Check that socket has been opened.
+ ASSERT_GE(sock, 0);
+
+ // Verify that the socket belongs to AF_INET family.
+ sockaddr_in6 sock_address;
+ socklen_t sock_address_len = sizeof(sock_address);
+ ASSERT_EQ(0, getsockname(sock,
+ reinterpret_cast<sockaddr*>(&sock_address),
+ &sock_address_len));
+ EXPECT_EQ(AF_INET6, sock_address.sin6_family);
+
+ // Verify that the socket is bound the appropriate address.
+ char straddr[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &sock_address.sin6_addr, straddr, sizeof(straddr));
+ std::string bind_addr(straddr);
+ EXPECT_EQ("::1", bind_addr);
+
+ // Verify that the socket is bound to appropriate port.
+ EXPECT_EQ(port_, ntohs(sock_address.sin6_port));
+
+ // Verify that the socket has SOCK_DGRAM type.
+ int sock_type;
+ socklen_t sock_type_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE,
+ &sock_type, &sock_type_len));
+ EXPECT_EQ(SOCK_DGRAM, sock_type);
+}
+
+void
+PktFilter6Test::testRcvdMessage(const Pkt6Ptr& rcvd_msg) const {
+ // Currently, we don't send any payload in the message.
+ // Let's just check that the transaction id matches so as we
+ // are sure that we received the message that we expected.
+ EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid());
+}
+
+PktFilter6Stub::PktFilter6Stub()
+ : open_socket_count_ (0) {
+}
+
+SocketInfo
+PktFilter6Stub::openSocket(const Iface& iface, const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool) {
+ // Check if there is any other socket bound to the specified address
+ // and port on this interface.
+ BOOST_FOREACH(SocketInfo socket, iface.getSockets()) {
+ if ((socket.addr_ == addr) && (socket.port_ == port)) {
+ isc_throw(SocketConfigError, "test socket bind error");
+ }
+ }
+ ++open_socket_count_;
+ return (SocketInfo(addr, port, 0));
+}
+
+Pkt6Ptr
+PktFilter6Stub::receive(const SocketInfo&) {
+ return Pkt6Ptr();
+}
+
+int
+PktFilter6Stub::send(const Iface&, uint16_t, const Pkt6Ptr&) {
+ return (0);
+}
+
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/tests/pkt_filter6_test_utils.h b/src/lib/dhcp/tests/pkt_filter6_test_utils.h
new file mode 100644
index 0000000..c005322
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter6_test_utils.h
@@ -0,0 +1,152 @@
+// Copyright (C) 2013-2023 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 PKT_FILTER6_TEST_UTILS_H
+#define PKT_FILTER6_TEST_UTILS_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture class for testing classes derived from PktFilter6 class.
+///
+/// This class implements a simple algorithm checking presence of the loopback
+/// interface and initializing its index. It assumes that the loopback interface
+/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the
+/// constructor will report a failure.
+///
+/// @todo The interface detection algorithm should be more generic. This will
+/// be possible once the cross-OS interface detection is implemented.
+class PktFilter6Test : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor initializes sock_info_ structure to a default value.
+ /// The socket descriptors should be set to a negative value to indicate
+ /// that no socket has been opened. Specific tests will reinitialize this
+ /// structure with the values of the open sockets. For non-negative socket
+ /// descriptors, the class destructor will close associated sockets.
+ PktFilter6Test(const uint16_t port);
+
+ /// @brief Destructor
+ ///
+ /// Closes open sockets (if any).
+ virtual ~PktFilter6Test();
+
+ /// @brief Initializes DHCPv6 message used by tests.
+ void initTestMessage();
+
+ /// @brief Detect loopback interface.
+ ///
+ /// @todo this function will be removed once cross-OS interface
+ /// detection is implemented
+ void loInit();
+
+ /// @brief Sends a single DHCPv6 message to the loopback address.
+ ///
+ /// This function opens a datagram socket and binds it to the local loopback
+ /// address and client port. The client's port is assumed to be port_ + 1.
+ /// The send_msg_sock_ member holds the socket descriptor so as the socket
+ /// is closed automatically in the destructor. If the function succeeds to
+ /// send a DHCPv6 message, the socket is closed so as the function can be
+ /// called again within the same test.
+ void sendMessage();
+
+ /// @brief Test that the datagram socket is opened correctly.
+ ///
+ /// This function is used by multiple tests.
+ ///
+ /// @param sock A descriptor of the open socket.
+ void testDgramSocket(const int sock) const;
+
+ /// @brief Checks if the received message matches the test_message_.
+ ///
+ /// @param rcvd_msg An instance of the message to be tested.
+ void testRcvdMessage(const Pkt6Ptr& rcvd_msg) const;
+
+ std::string ifname_; ///< Loopback interface name.
+ unsigned int ifindex_; ///< Loopback interface index.
+ uint16_t port_; ///< A port number used for the test.
+ isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info.
+ int send_msg_sock_; ///< Holds a descriptor of the socket used by
+ ///< sendMessage function.
+ Pkt6Ptr test_message_; ///< A DHCPv6 message used by tests.
+
+};
+
+/// @brief A stub implementation of the PktFilter6 class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::PktFilter class.
+/// The methods of this class mimic operations on sockets, but they neither
+/// open actual sockets, nor perform any send nor receive operations on them.
+class PktFilter6Stub : public PktFilter6 {
+public:
+
+ /// @brief Constructor
+ PktFilter6Stub();
+
+ /// @brief Simulate opening of a socket.
+ ///
+ /// This function simulates opening a socket. In reality, it doesn't open a
+ /// socket but the socket descriptor returned in the SocketInfo structure is
+ /// always set to 0. On each call to this function, the counter of
+ /// invocations is increased by one. This is useful to check if packet
+ /// filter object has been correctly installed and is used by @c IfaceMgr.
+ /// As in the case of opening a real socket, this function will check
+ /// if there is another fake socket "bound" to the same address and port.
+ /// If there is, it will throw an exception. This allows to simulate the
+ /// conditions when one of the sockets can't be open because there is
+ /// a socket already open and test how IfaceMgr will handle it.
+ ///
+ /// @param iface Interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number.
+ /// @param join_multicast A boolean parameter which indicates whether
+ /// socket should join All_DHCP_Relay_Agents_and_servers multicast
+ /// group.
+ ///
+ /// @return A structure describing a primary and fallback socket.
+ virtual SocketInfo openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool join_multicast);
+
+ /// @brief Simulate reception of the DHCPv6 message.
+ ///
+ /// @param socket_info A structure holding socket information.
+ ///
+ /// @return Always a NULL object.
+ virtual Pkt6Ptr receive(const SocketInfo& socket_info);
+
+ /// @brief Simulate sending a DHCPv6 message.
+ ///
+ /// This function does nothing.
+ ///
+ /// @param iface Interface to be used to send packet.
+ /// @param sockfd A socket descriptor
+ /// @param pkt A packet to be sent.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return 0.
+ virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt);
+
+ /// Holds the number of invocations to PktFilter6Stub::openSocket.
+ int open_socket_count_;
+
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // PKT_FILTER6_TEST_UTILS_H
diff --git a/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc
new file mode 100644
index 0000000..164b057
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc
@@ -0,0 +1,235 @@
+// Copyright (C) 2014-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 <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_bpf.h>
+#include <dhcp/protocol_util.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <net/bpf.h>
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 4096;
+
+// Test fixture class inherits from the class common for all packet
+// filter tests.
+class PktFilterBPFTest : public isc::dhcp::test::PktFilterTest {
+public:
+ PktFilterBPFTest() : PktFilterTest(PORT) {
+ }
+};
+
+// This test verifies that the PktFilterBPF class reports its capability
+// to send packets to the host having no IP address assigned.
+TEST_F(PktFilterBPFTest, isDirectResponseSupported) {
+ // Create object under test.
+ PktFilterBPF pkt_filter;
+ // Must support direct responses.
+ EXPECT_TRUE(pkt_filter.isDirectResponseSupported());
+}
+
+// All tests below require root privileges to execute successfully. If
+// they are run as non-root user they will fail due to insufficient privileges
+// to open raw network sockets. Therefore, they should remain disabled by default
+// and "DISABLED_" tags should not be removed. If one is willing to run these
+// tests please run "make check" as root and enable execution of disabled tests
+// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order
+// to run tests from this particular file, set the GTEST_FILTER environmental
+// variable to "PktFilterBPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS
+// setting.
+
+// This test verifies that the raw AF_PACKET family socket can
+// be opened and bound to the specific interface.
+TEST_F(PktFilterBPFTest, DISABLED_openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ iface.flag_loopback_ = true;
+ // Set loopback address.
+ IOAddress addr("127.0.0.1");
+
+ // Try to open socket.
+ PktFilterBPF pkt_filter;
+ ASSERT_NO_THROW(
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ );
+
+ // Check that the primary socket has been opened.
+ ASSERT_GE(sock_info_.sockfd_, 0);
+ // Check that the fallback socket has been opened too.
+ ASSERT_GE(sock_info_.fallbackfd_, 0);
+}
+
+// This test verifies correctness of sending DHCP packet through the BPF
+// device attached to local loopback interface. Note that this is not exactly
+// the same as sending over the hardware interface (e.g. ethernet) because the
+// packet format is different on local loopback interface when using the
+// BPF. The key difference is that the pseudo header containing address
+// family is sent instead of link-layer header. Ideally we would run this
+// test over the real interface but since we don't know what interfaces
+// are present in the particular system we have to stick to local loopback
+// interface as this one is almost always present.
+TEST_F(PktFilterBPFTest, DISABLED_send) {
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ iface.flag_loopback_ = true;
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterBPF pkt_filter;
+
+ // Open BPF device.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ // Returned descriptor must not be negative. 0 is valid.
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sock_info_.sockfd_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ /// Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = read(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE);
+ ASSERT_GT(result, 0);
+
+ // Each packet is prepended with the BPF header structure. We have to
+ // parse this structure to locate the position of the address family
+ // pseudo header.
+ struct bpf_hdr bpfh;
+ memcpy(static_cast<void*>(&bpfh), static_cast<void*>(rcv_buf),
+ sizeof(bpf_hdr));
+ // bh_hdrlen contains the total length of the BPF header, including
+ // alignment. We will use this value to skip over the BPF header and
+ // parse the contents of the packet that we are interested in.
+ uint32_t bpfh_len = bpfh.bh_hdrlen;
+ // Address Family pseudo header contains the address family of the
+ // packet (used for local loopback interface instead of the link-layer
+ // header such as ethernet frame header).
+ uint32_t af = 0;
+ memcpy(static_cast<void*>(&af),
+ static_cast<void*>(rcv_buf + bpfh_len), 4);
+ // Check the value in the pseudo header. If this is incorrect, something
+ // is really broken, so let's exit.
+ ASSERT_EQ(AF_INET, af);
+
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+ // Create the input buffer from the reminder of the packet. This should
+ // only contain the IP/UDP headers and the DHCP message.
+ InputBuffer buf(rcv_buf + bpfh_len + 4, result - bpfh_len - 4);
+ ASSERT_GE(buf.getLength(), test_message_->len());
+
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Create the DHCPv4 packet from the received data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+ Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+}
+
+// This test verifies correctness of reception of the DHCP packet over
+// raw socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterBPFTest, DISABLED_receive) {
+
+ // Packet will be received over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ iface.flag_loopback_ = true;
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterBPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send DHCPv4 message to the local loopback address and server's port.
+ sendMessage();
+
+ // Receive the packet using BPF packet filter.
+ Pkt4Ptr rcvd_pkt;
+ ASSERT_NO_THROW(rcvd_pkt = pkt_filter.receive(iface, sock_info_));
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+ testRcvdMessageAddressPort(rcvd_pkt);
+}
+
+// This test verifies that if the packet is received over the raw
+// socket and its destination address doesn't match the address
+// to which the socket is "bound", the packet is dropped.
+TEST_F(PktFilterBPFTest, DISABLED_filterOutUnicast) {
+
+ // Packet will be received over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ iface.flag_loopback_ = true;
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterBPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // The message sent to the local loopback interface will have an
+ // invalid (non-existing) destination address. The socket should
+ // drop this packet.
+ sendMessage(IOAddress("128.0.0.1"));
+
+ // Perform select on the socket to make sure that the packet has
+ // been dropped.
+
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sock_info_.sockfd_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+ ASSERT_LE(result, 0);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc
new file mode 100644
index 0000000..77104dc
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_inet6_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright (C) 2013-2019 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 <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter_inet6.h>
+#include <dhcp/tests/pkt_filter6_test_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10546;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+// Test fixture class inherits from the class common for all packet
+// filter tests.
+class PktFilterInet6Test : public isc::dhcp::test::PktFilter6Test {
+public:
+ PktFilterInet6Test() : PktFilter6Test(PORT) {
+ }
+};
+
+// This test verifies that the INET6 datagram socket is correctly opened and
+// bound to the appropriate address and port.
+TEST_F(PktFilterInet6Test, openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ // Set loopback address.
+ IOAddress addr("::1");
+
+ // Try to open socket.
+ PktFilterInet6 pkt_filter;
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, true);
+ // For the packet filter in use, the fallback socket shouldn't be opened.
+ // Fallback is typically opened for raw IPv4 sockets.
+ EXPECT_LT(sock_info_.fallbackfd_, 0);
+
+ // Test the primary socket.
+ testDgramSocket(sock_info_.sockfd_);
+}
+
+// This test verifies that the packet is correctly sent over the INET6
+// datagram socket.
+TEST_F(PktFilterInet6Test, send) {
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("::1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet6 pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, true);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send the packet over the socket.
+ int result = -1;
+ ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
+ ASSERT_EQ(0, result);
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sock_info_.sockfd_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ // Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
+ ASSERT_GT(result, 0);
+
+ // Create the DHCPv6 packet from the received data.
+ Pkt6Ptr rcvd_pkt(new Pkt6(rcv_buf, result));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+
+}
+
+// This test verifies that the DHCPv6 packet is correctly received via
+// INET6 datagram socket and that it matches sent packet.
+TEST_F(PktFilterInet6Test, receive) {
+
+ // Packet will be received over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("::1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet6 pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT + 1, true);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send a DHCPv6 message to the local loopback address and server's port.
+ // ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
+ sendMessage();
+
+ // Receive the packet.
+ Pkt6Ptr rcvd_pkt = pkt_filter.receive(sock_info_);
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+ }
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
new file mode 100644
index 0000000..a8d495c
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright (C) 2015-2019 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 <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+// Test fixture class inherits from the class common for all packet
+// filter tests.
+class PktFilterInetTest : public isc::dhcp::test::PktFilterTest {
+public:
+ PktFilterInetTest() : PktFilterTest(PORT) {
+ }
+};
+
+// This test verifies that the PktFilterInet class reports its lack
+// of capability to send packets to the host having no IP address
+// assigned.
+TEST_F(PktFilterInetTest, isDirectResponseSupported) {
+ // Create object under test.
+ PktFilterInet pkt_filter;
+ // This Packet Filter class does not support direct responses
+ // under any conditions.
+ EXPECT_FALSE(pkt_filter.isDirectResponseSupported());
+}
+
+// This test verifies that the INET datagram socket is correctly opened and
+// bound to the appropriate address and port.
+TEST_F(PktFilterInetTest, openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ // Set loopback address.
+ IOAddress addr("127.0.0.1");
+
+ // Try to open socket.
+ PktFilterInet pkt_filter;
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT,
+ false, false);
+ // For the packet filter in use, the fallback socket shouldn't be opened.
+ // Fallback is typically opened for raw sockets.
+ EXPECT_LT(sock_info_.fallbackfd_, 0);
+
+ // Test the primary socket.
+ testDgramSocket(sock_info_.sockfd_);
+}
+
+// This test verifies that the packet is correctly sent over the INET
+// datagram socket.
+TEST_F(PktFilterInetTest, send) {
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send the packet over the socket.
+ int result = -1;
+ ASSERT_NO_THROW(result = pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
+ ASSERT_EQ(0, result);
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sock_info_.sockfd_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ // Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
+ ASSERT_GT(result, 0);
+
+ // Create the DHCPv4 packet from the received data.
+ Pkt4Ptr rcvd_pkt(new Pkt4(rcv_buf, result));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+
+}
+
+// This test verifies that the DHCPv4 packet is correctly received via
+// INET datagram socket and that it matches sent packet.
+TEST_F(PktFilterInetTest, receive) {
+
+ // Packet will be received over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send a DHCPv4 message to the local loopback address and server's port.
+ sendMessage();
+
+ // Receive the packet.
+ Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_);
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+ testRcvdMessageAddressPort(rcvd_pkt);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
new file mode 100644
index 0000000..9471b33
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright (C) 2013-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 <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <linux/if_packet.h>
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+// Test fixture class inherits from the class common for all packet
+// filter tests.
+class PktFilterLPFTest : public isc::dhcp::test::PktFilterTest {
+public:
+ PktFilterLPFTest() : PktFilterTest(PORT) {
+ }
+};
+
+// This test verifies that the PktFilterLPF class reports its capability
+// to send packets to the host having no IP address assigned.
+TEST_F(PktFilterLPFTest, isDirectResponseSupported) {
+ // Create object under test.
+ PktFilterLPF pkt_filter;
+ // Must support direct responses.
+ EXPECT_TRUE(pkt_filter.isDirectResponseSupported());
+}
+
+// All tests below require root privileges to execute successfully. If
+// they are run as non-root user they will fail due to insufficient privileges
+// to open raw network sockets. Therefore, they should remain disabled by default
+// and "DISABLED_" tags should not be removed. If one is willing to run these
+// tests please run "make check" as root and enable execution of disabled tests
+// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order
+// to run tests from this particular file, set the GTEST_FILTER environmental
+// variable to "PktFilterLPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS
+// setting.
+
+// This test verifies that the raw AF_PACKET family socket can
+// be opened and bound to the specific interface.
+TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ // Set loopback address.
+ IOAddress addr("127.0.0.1");
+
+ // Try to open socket.
+ PktFilterLPF pkt_filter;
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+
+ // Check that the primary socket has been opened.
+ ASSERT_GE(sock_info_.sockfd_, 0);
+ // Check that the fallback socket has been opened too.
+ ASSERT_GE(sock_info_.fallbackfd_, 0);
+
+ // Verify that the socket belongs to AF_PACKET family.
+ sockaddr_ll sock_address;
+ socklen_t sock_address_len = sizeof(sock_address);
+ ASSERT_EQ(0, getsockname(sock_info_.sockfd_,
+ reinterpret_cast<sockaddr*>(&sock_address),
+ &sock_address_len));
+ EXPECT_EQ(AF_PACKET, sock_address.sll_family);
+
+ // Verify that the socket is bound to appropriate interface.
+ EXPECT_EQ(ifindex_, sock_address.sll_ifindex);
+
+ // Verify that the socket has SOCK_RAW type.
+ int sock_type;
+ socklen_t sock_type_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(sock_info_.sockfd_, SOL_SOCKET, SO_TYPE,
+ &sock_type, &sock_type_len));
+ EXPECT_EQ(SOCK_RAW, sock_type);
+}
+
+// This test verifies correctness of sending DHCP packet through the raw
+// socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_send) {
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterLPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sock_info_.sockfd_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ // Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
+ ASSERT_GT(result, 0);
+
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+ InputBuffer buf(rcv_buf, result);
+
+ // Decode ethernet, ip and udp headers.
+ decodeEthernetHeader(buf, dummy_pkt);
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Create the DHCPv4 packet from the received data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+ Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+}
+
+// This test verifies correctness of reception of the DHCP packet over
+// raw socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_receive) {
+
+ // Packet will be received over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterLPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // Send DHCPv4 message to the local loopback address and server's port.
+ sendMessage();
+
+ // Receive the packet using LPF packet filter.
+ Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_);
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Check if the received message is correct.
+ testRcvdMessage(rcvd_pkt);
+ testRcvdMessageAddressPort(rcvd_pkt);
+}
+
+// This test verifies that if the packet is received over the raw
+// socket and its destination address doesn't match the address
+// to which the socket is "bound", the packet is dropped.
+TEST_F(PktFilterLPFTest, DISABLED_filterOutUnicast) {
+
+ // Packet will be received over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ iface.flag_loopback_ = true;
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterLPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(sock_info_.sockfd_, 0);
+
+ // The message sent to the local loopback interface will have an
+ // invalid (non-existing) destination address. The socket should
+ // drop this packet.
+ sendMessage(IOAddress("128.0.0.1"));
+
+ // Perform select on the socket to make sure that the packet has
+ // been dropped.
+
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(sock_info_.sockfd_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+ ASSERT_LE(result, 0);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.cc b/src/lib/dhcp/tests/pkt_filter_test_stub.cc
new file mode 100644
index 0000000..7e87af5
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_test_stub.cc
@@ -0,0 +1,57 @@
+// Copyright (C) 2014-2022 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 <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <dhcp/tests/pkt_filter_test_stub.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilterTestStub::PktFilterTestStub()
+ : direct_response_supported_(true), open_socket_callback_() {
+}
+
+bool
+PktFilterTestStub::isDirectResponseSupported() const {
+ return (direct_response_supported_);
+}
+
+SocketInfo
+PktFilterTestStub::openSocket(Iface&,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool, const bool) {
+ int fd = open("/dev/null", O_RDONLY);
+ if (fd < 0) {
+ const char* errmsg = strerror(errno);
+ isc_throw(Unexpected,
+ "PktFilterTestStub: cannot open /dev/null:" << errmsg);
+ }
+
+ if (open_socket_callback_) {
+ open_socket_callback_(port);
+ }
+
+ return (SocketInfo(addr, port, fd));
+}
+
+Pkt4Ptr
+PktFilterTestStub::receive(Iface&, const SocketInfo&) {
+ return Pkt4Ptr();
+}
+
+int
+PktFilterTestStub::send(const Iface&, uint16_t, const Pkt4Ptr&) {
+ return (0);
+}
+
+}
+}
+}
diff --git a/src/lib/dhcp/tests/pkt_filter_test_stub.h b/src/lib/dhcp/tests/pkt_filter_test_stub.h
new file mode 100644
index 0000000..2bde4c9
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_test_stub.h
@@ -0,0 +1,116 @@
+// Copyright (C) 2014-2022 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 PKT_FILTER_TEST_STUB_H
+#define PKT_FILTER_TEST_STUB_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt4.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief An open socket callback that can be use for a testing purposes.
+///
+/// @param port Port number to bind socket to.
+typedef std::function<void(uint16_t port)> PktFilterOpenSocketCallback;
+
+/// @brief A stub implementation of the PktFilter class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::PktFilter
+/// class. It is used by unit tests, which test protected methods of the
+/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
+/// no-op.
+class PktFilterTestStub : public PktFilter {
+public:
+
+ /// @brief Constructor.
+ PktFilterTestStub();
+
+ /// @brief Checks if the direct DHCPv4 response is supported.
+ ///
+ /// This function checks if the direct response capability is supported,
+ /// i.e. if the server can respond to the client which doesn't have an
+ /// address yet. For this dummy class, the true is always returned.
+ ///
+ /// @return always true.
+ virtual bool isDirectResponseSupported() const;
+
+ /// @brief Simulate opening of the socket.
+ ///
+ /// This function simulates opening a primary socket. Rather than open
+ /// an actual socket, the stub performs a read-only open of "/dev/null".
+ /// The fd returned by this open saved as the socket's descriptor in the
+ /// SocketInfo structure. This way the filter consumes an actual
+ /// descriptor and retains it until its socket is closed.
+ ///
+ /// @param iface An interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number to bind socket to.
+ /// @param receive_bcast A flag which indicates if socket should be
+ /// configured to receive broadcast packets (if true).
+ /// @param send_bcast A flag which indicates if the socket should be
+ /// configured to send broadcast packets (if true).
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return A SocketInfo structure with the socket descriptor set to 0. The
+ /// fallback socket descriptor is set to a negative value.
+ virtual SocketInfo openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Simulate reception of the DHCPv4 message.
+ ///
+ /// @param iface An interface to be used to receive DHCPv4 message.
+ /// @param sock_info A descriptor of the primary and fallback sockets.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return always a NULL object.
+ virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info);
+
+ /// @brief Simulates sending a DHCPv4 message.
+ ///
+ /// This function does nothing.
+ ///
+ /// @param iface An interface to be used to send DHCPv4 message.
+ /// @param port A port used to send a message.
+ /// @param pkt A DHCPv4 to be sent.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return 0.
+ virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt);
+
+ // Change the scope of the protected function so as they can be unit tested.
+ using PktFilter::openFallbackSocket;
+
+ /// @brief Set an open socket callback. Use it for testing
+ /// purposes, e.g. counting the number of calls or throwing an exception.
+ void setOpenSocketCallback(PktFilterOpenSocketCallback callback) {
+ open_socket_callback_ = callback;
+ }
+
+ /// @brief Flag which indicates if direct response capability is supported.
+ bool direct_response_supported_;
+
+private:
+
+ /// @brief The callback used when opening socket.
+ PktFilterOpenSocketCallback open_socket_callback_;
+};
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_TEST_STUB_H
diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.cc b/src/lib/dhcp/tests/pkt_filter_test_utils.cc
new file mode 100644
index 0000000..7b5a79e
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_test_utils.cc
@@ -0,0 +1,196 @@
+// Copyright (C) 2013-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 <asiolink/io_address.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PktFilterTest::PktFilterTest(const uint16_t port)
+ : port_(port),
+ sock_info_(isc::asiolink::IOAddress("127.0.0.1"), port, -1, -1),
+ send_msg_sock_(-1) {
+ // Initialize ifname_ and ifindex_.
+ loInit();
+ // Initialize test_message_.
+ initTestMessage();
+}
+
+PktFilterTest::~PktFilterTest() {
+ // Cleanup after each test. This guarantees
+ // that the sockets do not hang after a test.
+ if (sock_info_.sockfd_ >= 0) {
+ close(sock_info_.sockfd_);
+ }
+ if (sock_info_.fallbackfd_ >=0) {
+ close(sock_info_.fallbackfd_);
+ }
+ if (send_msg_sock_ >= 0) {
+ close(send_msg_sock_);
+ }
+}
+
+void
+PktFilterTest::initTestMessage() {
+ // Let's create a DHCPv4 message instance.
+ test_message_.reset(new Pkt4(DHCPOFFER, 0));
+
+ // Set required fields.
+ test_message_->setLocalAddr(IOAddress("127.0.0.1"));
+ test_message_->setRemoteAddr(IOAddress("127.0.0.1"));
+ test_message_->setRemotePort(port_);
+ test_message_->setLocalPort(port_ + 1);
+ test_message_->setIndex(ifindex_);
+ test_message_->setIface(ifname_);
+ test_message_->setHops(6);
+ test_message_->setSecs(42);
+ test_message_->setCiaddr(IOAddress("192.0.2.1"));
+ test_message_->setSiaddr(IOAddress("192.0.2.2"));
+ test_message_->setYiaddr(IOAddress("192.0.2.3"));
+ test_message_->setGiaddr(IOAddress("192.0.2.4"));
+
+ try {
+ test_message_->pack();
+ } catch (const isc::Exception& ex) {
+ ADD_FAILURE() << "failed to create test message for PktFilterTest";
+ }
+}
+
+void
+PktFilterTest::loInit() {
+ if (if_nametoindex("lo") > 0) {
+ ifname_ = "lo";
+ ifindex_ = if_nametoindex("lo");
+
+ } else if (if_nametoindex("lo0") > 0) {
+ ifname_ = "lo0";
+ ifindex_ = if_nametoindex("lo0");
+
+ } else {
+ std::cout << "Failed to detect loopback interface. Neither "
+ << "lo nor lo0 worked. Giving up." << std::endl;
+ FAIL();
+
+ }
+}
+
+void
+PktFilterTest::sendMessage(const IOAddress& dest) {
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ struct sockaddr_in addr4;
+ memset(&addr4, 0, sizeof(sockaddr));
+ addr4.sin_family = AF_INET;
+ addr4.sin_port = htons(port_ + 1);
+
+ send_msg_sock_ = socket(AF_INET, SOCK_DGRAM, 0);
+ ASSERT_GE(send_msg_sock_, 0);
+
+ ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr4,
+ sizeof(addr4)), 0);
+
+ struct sockaddr_in dest_addr4;
+ memset(&dest_addr4, 0, sizeof(sockaddr));
+ dest_addr4.sin_family = AF_INET;
+ dest_addr4.sin_port = htons(port_);
+ dest_addr4.sin_addr.s_addr = htonl(dest.toUint32());
+ ASSERT_EQ(sendto(send_msg_sock_, test_message_->getBuffer().getData(),
+ test_message_->getBuffer().getLength(), 0,
+ reinterpret_cast<struct sockaddr*>(&dest_addr4),
+ sizeof(sockaddr)), test_message_->getBuffer().getLength());
+ close(send_msg_sock_);
+ send_msg_sock_ = -1;
+
+}
+
+void
+PktFilterTest::testDgramSocket(const int sock) const {
+ // Check that socket has been opened.
+ ASSERT_GE(sock, 0);
+
+ // Verify that the socket belongs to AF_INET family.
+ sockaddr_in sock_address;
+ socklen_t sock_address_len = sizeof(sock_address);
+ ASSERT_EQ(0, getsockname(sock,
+ reinterpret_cast<sockaddr*>(&sock_address),
+ &sock_address_len));
+ EXPECT_EQ(AF_INET, sock_address.sin_family);
+
+ // Verify that the socket is bound the appropriate address.
+ const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
+ EXPECT_EQ("127.0.0.1", bind_addr);
+
+ // Verify that the socket is bound to appropriate port.
+ EXPECT_EQ(port_, ntohs(sock_address.sin_port));
+
+ // Verify that the socket has SOCK_DGRAM type.
+ int sock_type;
+ socklen_t sock_type_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE,
+ &sock_type, &sock_type_len));
+ EXPECT_EQ(SOCK_DGRAM, sock_type);
+}
+
+void
+PktFilterTest::testRcvdMessage(const Pkt4Ptr& rcvd_msg) const {
+ EXPECT_EQ(test_message_->getHops(), rcvd_msg->getHops());
+ EXPECT_EQ(test_message_->getOp(), rcvd_msg->getOp());
+ EXPECT_EQ(test_message_->getSecs(), rcvd_msg->getSecs());
+ EXPECT_EQ(test_message_->getFlags(), rcvd_msg->getFlags());
+ EXPECT_EQ(test_message_->getCiaddr(), rcvd_msg->getCiaddr());
+ EXPECT_EQ(test_message_->getSiaddr(), rcvd_msg->getSiaddr());
+ EXPECT_EQ(test_message_->getYiaddr(), rcvd_msg->getYiaddr());
+ EXPECT_EQ(test_message_->getGiaddr(), rcvd_msg->getGiaddr());
+ EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid());
+ EXPECT_TRUE(test_message_->getSname() == rcvd_msg->getSname());
+ EXPECT_TRUE(test_message_->getFile() == rcvd_msg->getFile());
+ EXPECT_EQ(test_message_->getHtype(), rcvd_msg->getHtype());
+ EXPECT_EQ(test_message_->getHlen(), rcvd_msg->getHlen());
+}
+
+void
+PktFilterTest::testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const {
+ EXPECT_EQ(test_message_->getRemoteAddr(), rcvd_msg->getLocalAddr());
+ EXPECT_EQ(test_message_->getLocalAddr(), rcvd_msg->getRemoteAddr());
+ EXPECT_EQ(test_message_->getRemotePort(), rcvd_msg->getLocalPort());
+ EXPECT_EQ(test_message_->getLocalPort(), rcvd_msg->getRemotePort());
+}
+
+bool
+PktFilterStub::isDirectResponseSupported() const {
+ return (true);
+}
+
+SocketInfo
+PktFilterStub::openSocket(Iface&,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool, const bool) {
+ return (SocketInfo(addr, port, 0));
+}
+
+Pkt4Ptr
+PktFilterStub::receive(Iface&, const SocketInfo&) {
+ return Pkt4Ptr();
+}
+
+int
+PktFilterStub::send(const Iface&, uint16_t, const Pkt4Ptr&) {
+ return (0);
+}
+
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_test_utils.h b/src/lib/dhcp/tests/pkt_filter_test_utils.h
new file mode 100644
index 0000000..4be1d58
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_test_utils.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2013-2023 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 PKT_FILTER_TEST_UTILS_H
+#define PKT_FILTER_TEST_UTILS_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture class for testing classes derived from PktFilter class.
+///
+/// This class implements a simple algorithm checking presence of the loopback
+/// interface and initializing its index. It assumes that the loopback interface
+/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the
+/// constructor will report a failure.
+///
+/// @todo The interface detection algorithm should be more generic. This will
+/// be possible once the cross-OS interface detection is implemented.
+class PktFilterTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor initializes sock_info_ structure to a default value.
+ /// The socket descriptors should be set to a negative value to indicate
+ /// that no socket has been opened. Specific tests will reinitialize this
+ /// structure with the values of the open sockets. For non-negative socket
+ /// descriptors, the class destructor will close associated sockets.
+ PktFilterTest(const uint16_t port);
+
+ /// @brief Destructor
+ ///
+ /// Closes open sockets (if any).
+ virtual ~PktFilterTest();
+
+ /// @brief Initializes DHCPv4 message used by tests.
+ void initTestMessage();
+
+ /// @brief Detect loopback interface.
+ ///
+ /// @todo this function will be removed once cross-OS interface
+ /// detection is implemented
+ void loInit();
+
+ /// @brief Sends a single DHCPv4 message to the loopback address.
+ ///
+ /// This function opens a datagram socket and binds it to the local loopback
+ /// address and client port. The client's port is assumed to be port_ + 1.
+ /// The send_msg_sock_ member holds the socket descriptor so as the socket
+ /// is closed automatically in the destructor. If the function succeeds to
+ /// send a DHCPv4 message, the socket is closed so as the function can be
+ /// called again within the same test.
+ ///
+ /// @param dest Destination address for the packet.
+ void sendMessage(const asiolink::IOAddress& dest =
+ asiolink::IOAddress("127.0.0.1"));
+
+ /// @brief Test that the datagram socket is opened correctly.
+ ///
+ /// This function is used by multiple tests.
+ ///
+ /// @param sock A descriptor of the open socket.
+ void testDgramSocket(const int sock) const;
+
+ /// @brief Checks if a received message matches the test_message_.
+ ///
+ /// @param rcvd_msg An instance of the message to be tested.
+ void testRcvdMessage(const Pkt4Ptr& rcvd_msg) const;
+
+ /// @brief Checks if received message has appropriate addresses and
+ /// port values set.
+ ///
+ /// @param rcvd_msg An instance of the message to be tested.
+ void testRcvdMessageAddressPort(const Pkt4Ptr& rcvd_msg) const;
+
+ std::string ifname_; ///< Loopback interface name
+ unsigned int ifindex_; ///< Loopback interface index.
+ uint16_t port_; ///< A port number used for the test.
+ isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info.
+ int send_msg_sock_; ///< Holds a descriptor of the socket used by
+ ///< sendMessage function.
+ Pkt4Ptr test_message_; ///< A DHCPv4 message used by tests.
+
+};
+
+/// @brief A stub implementation of the PktFilter class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::PktFilter
+/// class. It is used by unit tests, which test protected methods of the
+/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
+/// no-op.
+class PktFilterStub : public PktFilter {
+public:
+
+ /// @brief Checks if the direct DHCPv4 response is supported.
+ ///
+ /// This function checks if the direct response capability is supported,
+ /// i.e. if the server can respond to the client which doesn't have an
+ /// address yet. For this dummy class, the true is always returned.
+ ///
+ /// @return always true.
+ virtual bool isDirectResponseSupported() const;
+
+ /// @brief Simulate opening of the socket.
+ ///
+ /// This function simulates opening a primary socket. In reality, it doesn't
+ /// open a socket but the socket descriptor returned in the SocketInfo
+ /// structure is always set to 0.
+ ///
+ /// @param iface An interface descriptor.
+ /// @param addr Address on the interface to be used to send packets.
+ /// @param port Port number to bind socket to.
+ /// @param receive_bcast A flag which indicates if socket should be
+ /// configured to receive broadcast packets (if true).
+ /// @param send_bcast A flag which indicates if the socket should be
+ /// configured to send broadcast packets (if true).
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return A SocketInfo structure with the socket descriptor set to 0. The
+ /// fallback socket descriptor is set to a negative value.
+ virtual SocketInfo openSocket(Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Simulate reception of the DHCPv4 message.
+ ///
+ /// @param iface An interface to be used to receive DHCPv4 message.
+ /// @param sock_info A descriptor of the primary and fallback sockets.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return always a NULL object.
+ virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info);
+
+ /// @brief Simulates sending a DHCPv4 message.
+ ///
+ /// This function does nothing.
+ ///
+ /// @param iface An interface to be used to send DHCPv4 message.
+ /// @param port A port used to send a message.
+ /// @param pkt A DHCPv4 to be sent.
+ ///
+ /// @note All parameters are ignored.
+ ///
+ /// @return 0.
+ virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt);
+
+ // Change the scope of the protected function so as they can be unit tested.
+ using PktFilter::openFallbackSocket;
+
+};
+
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // PKT_FILTER_TEST_UTILS_H
diff --git a/src/lib/dhcp/tests/pkt_filter_unittest.cc b/src/lib/dhcp/tests/pkt_filter_unittest.cc
new file mode 100644
index 0000000..5d04e32
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright (C) 2013-2017 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 <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/tests/pkt_filter_test_utils.h>
+#include <gtest/gtest.h>
+#include <fcntl.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+
+class PktFilterBaseClassTest : public isc::dhcp::test::PktFilterTest {
+public:
+ /// @brief Constructor
+ ///
+ /// Does nothing but setting up the UDP port for the test.
+ PktFilterBaseClassTest() : PktFilterTest(PORT) {
+ }
+};
+
+// This test verifies that the fallback socket is successfully opened and
+// bound using the protected function of the PktFilter class.
+TEST_F(PktFilterBaseClassTest, openFallbackSocket) {
+ // Open socket using the function under test. Note that, we don't have to
+ // close the socket on our own because the test fixture constructor
+ // will handle it.
+ PktFilterStub pkt_filter;
+ ASSERT_NO_THROW(sock_info_.fallbackfd_ =
+ pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT))
+ << "Failed to open fallback socket.";
+
+ // Test that the socket has been successfully created.
+ testDgramSocket(sock_info_.fallbackfd_);
+
+ // In addition, we should check that the fallback socket is non-blocking.
+ int sock_flags = fcntl(sock_info_.fallbackfd_, F_GETFL);
+ EXPECT_EQ(O_NONBLOCK, sock_flags & O_NONBLOCK)
+ << "Fallback socket is blocking, it should be non-blocking - check"
+ " fallback socket flags (fcntl).";
+
+ // Now that we have the socket open, let's try to open another one. This
+ // should cause a binding error.
+ int another_sock = -1;
+ EXPECT_THROW(another_sock =
+ pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT),
+ isc::dhcp::SocketConfigError)
+ << "it should be not allowed to open and bind two fallback sockets"
+ " to the same address and port. Surprisingly, the socket bound.";
+ // Hard to believe we managed to open another socket. But if so, we have
+ // to close it to prevent a resource leak.
+ if (another_sock >= 0) {
+ close(another_sock);
+ }
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc
new file mode 100644
index 0000000..55a2221
--- /dev/null
+++ b/src/lib/dhcp/tests/protocol_util_unittest.cc
@@ -0,0 +1,677 @@
+// Copyright (C) 2013-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+ /*/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+};*/
+
+/// The purpose of this test is to verify that the IP header checksum
+/// is calculated correctly.
+TEST(ProtocolUtilTest, checksum) {
+ // IPv4 header to be used to calculate checksum.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ 0x06, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xac, 0x10, 0x0a, 0x63, // Source IP address.
+ 0xac, 0x10, 0x0a, 0x0c // Destination IP address.
+ };
+ // Calculate size of the header array.
+ const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]);
+ // Get the actual checksum.
+ uint16_t chksum = ~calcChecksum(hdr, hdr_size);
+ // The 0xb1e6 value has been calculated by other means.
+ EXPECT_EQ(0xb1e6, chksum);
+ // Tested function may also take the initial value of the sum.
+ // Let's set it to 2 and see whether it is included in the
+ // calculation.
+ chksum = ~calcChecksum(hdr, hdr_size, 2);
+ // The checksum value should change.
+ EXPECT_EQ(0xb1e4, chksum);
+}
+
+// The purpose of this test is to verify that the Ethernet frame header
+// can be decoded correctly. In particular it verifies that the source
+// HW address can be extracted from it.
+TEST(ProtocolUtilTest, decodeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Prepare a buffer holding Ethernet frame header and 4 bytes of
+ // dummy data.
+ OutputBuffer buf(1);
+ buf.writeData(dest_hw_addr, sizeof(dest_hw_addr));
+ buf.writeData(src_hw_addr, sizeof(src_hw_addr));
+ buf.writeUint16(ETHERNET_TYPE_IP);
+ // Append dummy data. We will later check that this data is not
+ // removed or corrupted when reading the ethernet header.
+ buf.writeUint32(0x01020304);
+
+ // Create a buffer with truncated ethernet frame header..
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 6);
+ // But provide valid packet object to make sure that the function
+ // under test does not throw due to NULL pointer packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because header data is truncated.
+ EXPECT_THROW(decodeEthernetHeader(in_buf_truncated, pkt),
+ InvalidPacketHeader);
+
+ // Get not truncated buffer.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // But provide NULL packet object instead.
+ pkt.reset();
+ // It should throw again but a different exception.
+ EXPECT_THROW(decodeEthernetHeader(in_buf, pkt),
+ BadValue);
+ // Now provide, correct data.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ // It should not throw now.
+ ASSERT_NO_THROW(decodeEthernetHeader(in_buf, pkt));
+ // Verify that the destination HW address has been initialized...
+ HWAddrPtr checked_dest_hwaddr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(checked_dest_hwaddr);
+ // and is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_dest_hwaddr->htype_);
+ ASSERT_EQ(sizeof(dest_hw_addr), checked_dest_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(dest_hw_addr, dest_hw_addr + sizeof(dest_hw_addr),
+ checked_dest_hwaddr->hwaddr_.begin()));
+
+ // Verify that the HW address of the source has been initialized.
+ HWAddrPtr checked_src_hwaddr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(checked_src_hwaddr);
+ // And that it is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_src_hwaddr->htype_);
+ ASSERT_EQ(sizeof(src_hw_addr), checked_src_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(src_hw_addr, src_hw_addr + sizeof(src_hw_addr),
+ checked_src_hwaddr->hwaddr_.begin()));
+
+ // The entire ethernet packet header should have been read. This means
+ // that the internal buffer pointer should now point to its tail.
+ ASSERT_EQ(ETHERNET_HEADER_LEN, in_buf.getPosition());
+ // And the dummy data should be still readable and correct.
+ uint32_t dummy_data = in_buf.readUint32();
+ EXPECT_EQ(0x01020304, dummy_data);
+}
+
+/// The purpose of this test is to verify that the IP and UDP header
+/// is decoded correctly and appropriate values of IP addresses and
+/// ports are assigned to a Pkt4 object.
+TEST(ProtocolUtilTest, decodeIpUdpHeader) {
+ // IPv4 header to be parsed.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ IPPROTO_UDP, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xc0, 0x00, 0x02, 0x63, // Source IP address.
+ 0xc0, 0x00, 0x02, 0x0c, // Destination IP address.
+ 0x27, 0x54, // Source port
+ 0x27, 0x53, // Destination port
+ 0x00, 0x08, // UDP length
+ 0x00, 0x00 // Checksum
+ };
+
+ // Write header data to the buffer.
+ OutputBuffer buf(1);
+ buf.writeData(hdr, sizeof(hdr));
+ // Append some dummy data.
+ buf.writeUint32(0x01020304);
+
+ // Create an input buffer holding truncated headers.
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 10);
+ // Create non NULL Pkt4 object to make sure that the function under
+ // test does not throw due to invalid Pkt4 object.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because buffer holds truncated data.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf_truncated, pkt), InvalidPacketHeader);
+
+ // Create a valid input buffer (not truncated).
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // Set NULL Pkt4 object to verify that function under test will
+ // return exception as expected.
+ pkt.reset();
+ // And check whether it throws exception.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf, pkt), BadValue);
+
+ // Now, let's provide valid arguments and make sure it doesn't throw.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ ASSERT_TRUE(pkt);
+ EXPECT_NO_THROW(decodeIpUdpHeader(in_buf, pkt));
+
+ // Verify the source address and port.
+ EXPECT_EQ("192.0.2.99", pkt->getRemoteAddr().toText());
+ EXPECT_EQ(10068, pkt->getRemotePort());
+
+ // Verify the destination address and port.
+ EXPECT_EQ("192.0.2.12", pkt->getLocalAddr().toText());
+ EXPECT_EQ(10067, pkt->getLocalPort());
+
+ // Verify that the dummy data has not been corrupted and that the
+ // internal read pointer has been moved to the tail of the UDP
+ // header.
+ ASSERT_EQ(MIN_IP_HEADER_LEN + UDP_HEADER_LEN, in_buf.getPosition());
+ EXPECT_EQ(0x01020304, in_buf.readUint32());
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses.
+TEST(ProtocolUtilTest, writeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+
+ // Set invalid length (7) of the hw address. Fill it with
+ // values of 1.
+ std::vector<uint8_t> invalid_length_addr(7, 1);
+ HWAddrPtr remote_hw_addr(new HWAddr(invalid_length_addr, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+ // HW address is too long, so it should fail.
+ EXPECT_THROW(writeEthernetHeader(pkt, buf), BadValue);
+
+ // Finally, set a valid HW address.
+ remote_hw_addr.reset(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise valid destination
+ // HW address. Instead of using memory comparison functions
+ // we check bytes one-by-one. In case of mismatch we will
+ // get exact values that are mismatched. If memcmp was used
+ // the error message would not indicate the values of
+ // mismatched bytes.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(dest_hw_addr[i], buf[i]);
+ }
+
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses with the broadcast flag set.
+TEST(ProtocolUtilTest, writeEthernetHeaderBroadcast) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+ HWAddrPtr remote_hw_addr(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Set the broadcast flags.
+ pkt->setFlags(pkt->getFlags() | Pkt4::FLAG_BROADCAST_MASK);
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise broadcast destination
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(255, buf[i]);
+ }
+
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses with the broadcast flag set but the packet
+/// was relayed.
+TEST(ProtocolUtilTest, writeEthernetHeaderBroadcastRelayed) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+ HWAddrPtr remote_hw_addr(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Set the broadcast flags.
+ pkt->setFlags(pkt->getFlags() | Pkt4::FLAG_BROADCAST_MASK);
+
+ // Set a gateway address: the broadcast flag is now for
+ // the relay, no longer for the server.
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise valid destination
+ // HW address. Instead of using memory comparison functions
+ // we check bytes one-by-one. In case of mismatch we will
+ // get exact values that are mismatched. If memcmp was used
+ // the error message would not indicate the values of
+ // mismatched bytes.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(dest_hw_addr[i], buf[i]);
+ }
+
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (unsigned i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+TEST(ProtocolUtilTest, writeIpUdpHeader) {
+ // Create DHCPv4 packet. Some values held by this object are
+ // used by the utility function under test to figure out the
+ // contents of the IP and UDP headers, e.g. source and
+ // destination IP address or port number.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set local and remote address and port.
+ pkt->setLocalAddr(IOAddress("192.0.2.1"));
+ pkt->setRemoteAddr(IOAddress("192.0.2.111"));
+ pkt->setLocalPort(DHCP4_SERVER_PORT);
+ pkt->setRemotePort(DHCP4_CLIENT_PORT);
+
+ // Pack the contents of the packet.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Create output buffer. The headers will be written to it.
+ OutputBuffer buf(1);
+ // Write some dummy data to the buffer. We will check that the
+ // function under test appends to this data, not overrides it.
+ buf.writeUint16(0x0102);
+
+ // Write both IP and UDP headers.
+ writeIpUdpHeader(pkt, buf);
+
+ // The resulting size of the buffer must be 30. The 28 bytes are
+ // consumed by the IP and UDP headers. The other 2 bytes are dummy
+ // data at the beginning of the buffer.
+ ASSERT_EQ(30, buf.getLength());
+
+ // Make sure that the existing data in the buffer was not corrupted
+ // by the function under test.
+ EXPECT_EQ(0x01, buf[0]);
+ EXPECT_EQ(0x02, buf[1]);
+
+ // Copy the contents of the buffer to InputBuffer object. This object
+ // exposes convenient functions for reading.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+
+ // Check dummy data.
+ uint16_t dummy_data = in_buf.readUint16();
+ EXPECT_EQ(0x0102, dummy_data);
+
+ // The IP version and IHL are stored in the same octet (4 bits each).
+ uint8_t ver_len = in_buf.readUint8();
+ // The most significant bits define IP version.
+ uint8_t ip_ver = ver_len >> 4;
+ EXPECT_EQ(4, ip_ver);
+ // The least significant bits define header length (in 32-bits chunks).
+ uint8_t ip_len = ver_len & 0x0F;
+ EXPECT_EQ(5, ip_len);
+
+ // Get Differentiated Services Codepoint and Explicit Congestion
+ // Notification field.
+ uint8_t dscp_ecn = in_buf.readUint8();
+ EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn);
+
+ // Total length of IP packet. Includes UDP header and payload.
+ uint16_t total_len = in_buf.readUint16();
+ EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len);
+
+ // Identification field.
+ uint16_t ident = in_buf.readUint16();
+ EXPECT_EQ(0, ident);
+
+ // Fragmentation.
+ uint16_t fragment = in_buf.readUint16();
+ // Setting second most significant bit means no fragmentation.
+ EXPECT_EQ(0x4000, fragment);
+
+ // Get TTL
+ uint8_t ttl = in_buf.readUint8();
+ // Expect non-zero TTL.
+ EXPECT_GE(ttl, 1);
+
+ // Protocol type is UDP.
+ uint8_t proto = in_buf.readUint8();
+ EXPECT_EQ(static_cast<short>(IPPROTO_UDP), proto);
+
+ // Check that the checksum is correct. The reference checksum value
+ // has been calculated manually.
+ uint16_t ip_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x755c, ip_checksum);
+
+ // Validate source address.
+ // Initializing it to IPv6 address guarantees that it is not initialized
+ // to the value that we expect to be read from a header since the value
+ // read from a header will be IPv4.
+ IOAddress src_addr("::1");
+ // Read src address as an array of bytes because it is easily convertible
+ // to IOAddress object.
+ uint8_t src_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(src_addr_data, 4);
+ src_addr = IOAddress::fromBytes(AF_INET, src_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.1"), src_addr);
+
+ // Validate destination address.
+ IOAddress dest_addr("::1");
+ uint8_t dest_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(dest_addr_data, 4);
+ dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.111"), dest_addr);
+
+ // UDP header starts here.
+
+ // Check source port.
+ uint16_t src_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getLocalPort(), src_port);
+
+ // Check destination port.
+ uint16_t dest_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getRemotePort(), dest_port);
+
+ // UDP header and data length.
+ uint16_t udp_len = in_buf.readUint16();
+ EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len);
+
+ // Verify UDP checksum. The reference checksum has been calculated manually.
+ uint16_t udp_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x8817, udp_checksum);
+}
+
+/// Test that checks the RAII implementation of ScopedEnableOptionsCopy works
+/// as expected, restoring the copy retrieve options flag.
+TEST(ScopedEnableOptionsCopy, enableOptionsCopy) {
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543));
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ pkt->addOption(option);
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ {
+ ScopedEnableOptionsCopy<Pkt4> oc(pkt);
+ ASSERT_TRUE(pkt->isCopyRetrievedOptions());
+ OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME);
+ ASSERT_NE(option, option_copy);
+ option = option_copy;
+ }
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ {
+ try {
+ ScopedEnableOptionsCopy<Pkt4> oc(pkt);
+ ASSERT_TRUE(pkt->isCopyRetrievedOptions());
+ OptionPtr option_copy = pkt->getOption(DHO_BOOT_FILE_NAME);
+ ASSERT_NE(option, option_copy);
+ option = option_copy;
+ throw 0;
+ } catch (...) {
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ }
+ ASSERT_FALSE(pkt->isCopyRetrievedOptions());
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ }
+}
+
+/// Test that checks the RAII implementation of ScopedPkt4OptionsCopy works
+/// as expected, restoring the initial Pkt4 options.
+TEST(ScopedOptionsCopy, pkt4OptionsCopy) {
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 2543));
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ pkt->addOption(option);
+ OptionCollection options = pkt->options_;
+ size_t count = options.size();
+ ASSERT_NE(0, count);
+ ASSERT_EQ(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ std::string expected = pkt->toText();
+ pkt->pack();
+ OutputBuffer buf = pkt->getBuffer();
+ {
+ ScopedPkt4OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME));
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option);
+ {
+ try {
+ ScopedPkt4OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(DHO_BOOT_FILE_NAME));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(DHO_BOOT_FILE_NAME));
+ throw 0;
+ } catch (...) {
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+}
+
+/// Test that checks the RAII implementation of ScopedPkt6OptionsCopy works
+/// as expected, restoring the initial Pkt6 options.
+TEST(ScopedOptionsCopy, pkt6OptionsCopy) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_SOLICIT, 2543));
+ OptionPtr option = Option::create(Option::V6, D6O_BOOTFILE_URL);
+ pkt->addOption(option);
+ OptionCollection options = pkt->options_;
+ size_t count = options.size();
+ ASSERT_NE(0, count);
+ ASSERT_EQ(option, pkt->getOption(D6O_BOOTFILE_URL));
+ std::string expected = pkt->toText();
+ pkt->pack();
+ OutputBuffer buf = pkt->getBuffer();
+ {
+ ScopedPkt6OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(D6O_BOOTFILE_URL));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(D6O_BOOTFILE_URL);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL));
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option);
+ {
+ try {
+ ScopedPkt6OptionsCopy oc(*pkt);
+ ASSERT_NE(pkt->options_, options);
+ ASSERT_NE(option, pkt->getOption(D6O_BOOTFILE_URL));
+ pkt->pack();
+ ASSERT_EQ(buf.getLength(), pkt->getBuffer().getLength());
+ for (size_t index = 0; index < buf.getLength(); ++index) {
+ ASSERT_EQ(buf[index], pkt->getBuffer()[index]);
+ }
+ ASSERT_EQ(expected, pkt->toText());
+ pkt->delOption(D6O_BOOTFILE_URL);
+ ASSERT_EQ(pkt->options_.size(), count - 1);
+ ASSERT_FALSE(pkt->getOption(D6O_BOOTFILE_URL));
+ throw 0;
+ } catch (...) {
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option);
+ }
+ ASSERT_EQ(pkt->options_, options);
+ ASSERT_EQ(pkt->getOption(D6O_BOOTFILE_URL), option);
+ }
+}
+
+/// Test that checks the RAII implementation of ScopedSubOptionsCopy works
+/// as expected, restoring the initial option suboptions.
+TEST(ScopedOptionsCopy, subOptionsCopy) {
+ OptionPtr initial = Option::create(Option::V4, 231);
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ initial->addOption(option);
+ OptionCollection options = initial->getOptions();
+ size_t count = options.size();
+ ASSERT_NE(0, count);
+ ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME));
+ {
+ ScopedSubOptionsCopy oc(initial);
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME));
+ initial->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(initial->getOptions().size(), count - 1);
+ ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME));
+ }
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option);
+ {
+ try {
+ ScopedSubOptionsCopy oc(initial);
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(option, initial->getOption(DHO_BOOT_FILE_NAME));
+ initial->delOption(DHO_BOOT_FILE_NAME);
+ ASSERT_EQ(initial->getOptions().size(), count - 1);
+ ASSERT_FALSE(initial->getOption(DHO_BOOT_FILE_NAME));
+ throw 0;
+ } catch (...) {
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+ ASSERT_EQ(initial->getOptions(), options);
+ ASSERT_EQ(initial->getOption(DHO_BOOT_FILE_NAME), option);
+ }
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/run_unittests.cc b/src/lib/dhcp/tests/run_unittests.cc
new file mode 100644
index 0000000..e1c0801
--- /dev/null
+++ b/src/lib/dhcp/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// 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 <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/dhcp_ddns/Makefile.am b/src/lib/dhcp_ddns/Makefile.am
new file mode 100644
index 0000000..a1e10e5
--- /dev/null
+++ b/src/lib/dhcp_ddns/Makefile.am
@@ -0,0 +1,78 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = dhcp_ddns_messages.mes libdhcp_ddns.dox
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-dhcp_ddns.la
+libkea_dhcp_ddns_la_SOURCES =
+libkea_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
+libkea_dhcp_ddns_la_SOURCES += dhcp_ddns_messages.cc dhcp_ddns_messages.h
+libkea_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
+libkea_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
+libkea_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
+
+libkea_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS += -no-undefined -version-info 41:0:0
+
+libkea_dhcp_ddns_la_LIBADD = $(top_builddir)/src/lib/stats/libkea-stats.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_dhcp_ddns_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f dhcp_ddns_messages.h dhcp_ddns_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: dhcp_ddns_messages.h dhcp_ddns_messages.cc
+ @echo Message files regenerated
+
+dhcp_ddns_messages.h dhcp_ddns_messages.cc: dhcp_ddns_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+else
+
+messages dhcp_ddns_messages.h dhcp_ddns_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_dhcp_ddns_includedir = $(pkgincludedir)/dhcp_ddns
+libkea_dhcp_ddns_include_HEADERS = \
+ dhcp_ddns_log.h \
+ dhcp_ddns_messages.h \
+ ncr_io.h \
+ ncr_msg.h \
+ ncr_udp.h
diff --git a/src/lib/dhcp_ddns/Makefile.in b/src/lib/dhcp_ddns/Makefile.in
new file mode 100644
index 0000000..d8a131a
--- /dev/null
+++ b/src/lib/dhcp_ddns/Makefile.in
@@ -0,0 +1,1057 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/dhcp_ddns
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 \
+ $(libkea_dhcp_ddns_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)$(libkea_dhcp_ddns_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_dhcp_ddns_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_dhcp_ddns_la_OBJECTS = libkea_dhcp_ddns_la-dhcp_ddns_log.lo \
+ libkea_dhcp_ddns_la-dhcp_ddns_messages.lo \
+ libkea_dhcp_ddns_la-ncr_io.lo libkea_dhcp_ddns_la-ncr_msg.lo \
+ libkea_dhcp_ddns_la-ncr_udp.lo
+libkea_dhcp_ddns_la_OBJECTS = $(am_libkea_dhcp_ddns_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_dhcp_ddns_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_dhcp_ddns_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_dhcp_ddns_la-dhcp_ddns_log.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo \
+ ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.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_dhcp_ddns_la_SOURCES)
+DIST_SOURCES = $(libkea_dhcp_ddns_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 = $(libkea_dhcp_ddns_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = dhcp_ddns_messages.mes libdhcp_ddns.dox
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-dhcp_ddns.la
+libkea_dhcp_ddns_la_SOURCES = dhcp_ddns_log.cc dhcp_ddns_log.h \
+ dhcp_ddns_messages.cc dhcp_ddns_messages.h ncr_io.cc ncr_io.h \
+ ncr_msg.cc ncr_msg.h ncr_udp.cc ncr_udp.h
+libkea_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcp_ddns_la_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) \
+ -no-undefined -version-info 41:0:0
+libkea_dhcp_ddns_la_LIBADD = \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_dhcp_ddns_includedir = $(pkgincludedir)/dhcp_ddns
+libkea_dhcp_ddns_include_HEADERS = \
+ dhcp_ddns_log.h \
+ dhcp_ddns_messages.h \
+ ncr_io.h \
+ ncr_msg.h \
+ ncr_udp.h
+
+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/dhcp_ddns/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcp_ddns/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-dhcp_ddns.la: $(libkea_dhcp_ddns_la_OBJECTS) $(libkea_dhcp_ddns_la_DEPENDENCIES) $(EXTRA_libkea_dhcp_ddns_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_dhcp_ddns_la_LINK) -rpath $(libdir) $(libkea_dhcp_ddns_la_OBJECTS) $(libkea_dhcp_ddns_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.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)$(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 $@ $<
+
+libkea_dhcp_ddns_la-dhcp_ddns_log.lo: dhcp_ddns_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-dhcp_ddns_log.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Tpo -c -o libkea_dhcp_ddns_la-dhcp_ddns_log.lo `test -f 'dhcp_ddns_log.cc' || echo '$(srcdir)/'`dhcp_ddns_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_ddns_log.cc' object='libkea_dhcp_ddns_la-dhcp_ddns_log.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-dhcp_ddns_log.lo `test -f 'dhcp_ddns_log.cc' || echo '$(srcdir)/'`dhcp_ddns_log.cc
+
+libkea_dhcp_ddns_la-dhcp_ddns_messages.lo: dhcp_ddns_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-dhcp_ddns_messages.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Tpo -c -o libkea_dhcp_ddns_la-dhcp_ddns_messages.lo `test -f 'dhcp_ddns_messages.cc' || echo '$(srcdir)/'`dhcp_ddns_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_ddns_messages.cc' object='libkea_dhcp_ddns_la-dhcp_ddns_messages.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-dhcp_ddns_messages.lo `test -f 'dhcp_ddns_messages.cc' || echo '$(srcdir)/'`dhcp_ddns_messages.cc
+
+libkea_dhcp_ddns_la-ncr_io.lo: ncr_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-ncr_io.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Tpo -c -o libkea_dhcp_ddns_la-ncr_io.lo `test -f 'ncr_io.cc' || echo '$(srcdir)/'`ncr_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_io.cc' object='libkea_dhcp_ddns_la-ncr_io.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-ncr_io.lo `test -f 'ncr_io.cc' || echo '$(srcdir)/'`ncr_io.cc
+
+libkea_dhcp_ddns_la-ncr_msg.lo: ncr_msg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-ncr_msg.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Tpo -c -o libkea_dhcp_ddns_la-ncr_msg.lo `test -f 'ncr_msg.cc' || echo '$(srcdir)/'`ncr_msg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_msg.cc' object='libkea_dhcp_ddns_la-ncr_msg.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-ncr_msg.lo `test -f 'ncr_msg.cc' || echo '$(srcdir)/'`ncr_msg.cc
+
+libkea_dhcp_ddns_la-ncr_udp.lo: ncr_udp.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcp_ddns_la-ncr_udp.lo -MD -MP -MF $(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.Tpo -c -o libkea_dhcp_ddns_la-ncr_udp.lo `test -f 'ncr_udp.cc' || echo '$(srcdir)/'`ncr_udp.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.Tpo $(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_udp.cc' object='libkea_dhcp_ddns_la-ncr_udp.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_dhcp_ddns_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcp_ddns_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcp_ddns_la-ncr_udp.lo `test -f 'ncr_udp.cc' || echo '$(srcdir)/'`ncr_udp.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_dhcp_ddns_includeHEADERS: $(libkea_dhcp_ddns_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_dhcp_ddns_include_HEADERS)'; test -n "$(libkea_dhcp_ddns_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_dhcp_ddns_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_dhcp_ddns_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)$(libkea_dhcp_ddns_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_dhcp_ddns_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_dhcp_ddns_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_dhcp_ddns_include_HEADERS)'; test -n "$(libkea_dhcp_ddns_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_dhcp_ddns_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_dhcp_ddns_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.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-libkea_dhcp_ddns_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_dhcp_ddns_la-dhcp_ddns_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-dhcp_ddns_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_io.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_msg.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcp_ddns_la-ncr_udp.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_dhcp_ddns_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_dhcp_ddns_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_dhcp_ddns_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f dhcp_ddns_messages.h dhcp_ddns_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: dhcp_ddns_messages.h dhcp_ddns_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@dhcp_ddns_messages.h dhcp_ddns_messages.cc: dhcp_ddns_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages dhcp_ddns_messages.h dhcp_ddns_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/dhcp_ddns/dhcp_ddns_log.cc b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
new file mode 100644
index 0000000..2be7be5
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2013-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/.
+
+/// Defines the logger used by the top-level component of kea-dhcp_ddns.
+
+#include <config.h>
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Defines the logger used within lib dhcp_ddns.
+isc::log::Logger dhcp_ddns_logger("libdhcp-ddns");
+
+} // namespace dhcp_ddns
+} // namespace isc
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_log.h b/src/lib/dhcp_ddns/dhcp_ddns_log.h
new file mode 100644
index 0000000..cda7fdf
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2013-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/.
+
+#ifndef DHCP_DDNS_LOG_H
+#define DHCP_DDNS_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <dhcp_ddns/dhcp_ddns_messages.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// Define the logger for the "dhcp_ddns" logging.
+extern isc::log::Logger dhcp_ddns_logger;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif // DHCP_DDNS_LOG_H
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.cc b/src/lib/dhcp_ddns/dhcp_ddns_messages.cc
new file mode 100644
index 0000000..2c440c9
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.cc
@@ -0,0 +1,51 @@
+// File created from ../../../src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+extern const isc::log::MessageID DHCP_DDNS_INVALID_NCR = "DHCP_DDNS_INVALID_NCR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_FLUSH_IO_ERROR = "DHCP_DDNS_NCR_FLUSH_IO_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR = "DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_RECV_NEXT_ERROR = "DHCP_DDNS_NCR_RECV_NEXT_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_CLOSE_ERROR = "DHCP_DDNS_NCR_SEND_CLOSE_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_NEXT_ERROR = "DHCP_DDNS_NCR_SEND_NEXT_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR = "DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_CANCELED = "DHCP_DDNS_NCR_UDP_RECV_CANCELED";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_ERROR = "DHCP_DDNS_NCR_UDP_RECV_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_CANCELED = "DHCP_DDNS_NCR_UDP_SEND_CANCELED";
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_ERROR = "DHCP_DDNS_NCR_UDP_SEND_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR = "DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR = "DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR";
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR = "DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR";
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DHCP_DDNS_INVALID_NCR", "application received an invalid DNS update request: %1",
+ "DHCP_DDNS_NCR_FLUSH_IO_ERROR", "DHCP-DDNS Last send before stopping did not complete successfully: %1",
+ "DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR", "application encountered an error while closing the listener used to receive NameChangeRequests : %1",
+ "DHCP_DDNS_NCR_RECV_NEXT_ERROR", "application could not initiate the next read following a request receive.",
+ "DHCP_DDNS_NCR_SEND_CLOSE_ERROR", "DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests: %1",
+ "DHCP_DDNS_NCR_SEND_NEXT_ERROR", "DHCP-DDNS client could not initiate the next request send following send completion: %1",
+ "DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR", "NCR UDP watch socket failed to clear: %1",
+ "DHCP_DDNS_NCR_UDP_RECV_CANCELED", "UDP socket receive was canceled while listening for DNS Update requests",
+ "DHCP_DDNS_NCR_UDP_RECV_ERROR", "UDP socket receive error while listening for DNS Update requests: %1",
+ "DHCP_DDNS_NCR_UDP_SEND_CANCELED", "UDP socket send was canceled while sending a DNS Update request to DHCP_DDNS: %1",
+ "DHCP_DDNS_NCR_UDP_SEND_ERROR", "UDP socket send error while sending a DNS Update request: %1",
+ "DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR", "watch socket failed to close: %1",
+ "DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR", "unexpected exception thrown from the application receive completion handler: %1",
+ "DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR", "unexpected exception thrown from the DHCP-DDNS client send completion handler: %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.h b/src/lib/dhcp_ddns/dhcp_ddns_messages.h
new file mode 100644
index 0000000..cb85946
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.h
@@ -0,0 +1,29 @@
+// File created from ../../../src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+#ifndef DHCP_DDNS_MESSAGES_H
+#define DHCP_DDNS_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+extern const isc::log::MessageID DHCP_DDNS_INVALID_NCR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_FLUSH_IO_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_RECV_NEXT_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_CLOSE_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_SEND_NEXT_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_CANCELED;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_RECV_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_CANCELED;
+extern const isc::log::MessageID DHCP_DDNS_NCR_UDP_SEND_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR;
+extern const isc::log::MessageID DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif // DHCP_DDNS_MESSAGES_H
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
new file mode 100644
index 0000000..7c15bf9
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
@@ -0,0 +1,88 @@
+# Copyright (C) 2013-2020 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/.
+
+$NAMESPACE isc::dhcp_ddns
+
+% DHCP_DDNS_INVALID_NCR application received an invalid DNS update request: %1
+This is an error message that indicates that an invalid request to update
+a DNS entry was received by the application. Either the format or the content
+of the request is incorrect. The request will be ignored.
+
+% DHCP_DDNS_NCR_FLUSH_IO_ERROR DHCP-DDNS Last send before stopping did not complete successfully: %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+complete the last send prior to exiting send mode. This is a programmatic
+error, highly unlikely to occur, and should not impair the application's ability
+to process requests.
+
+% DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR application encountered an error while closing the listener used to receive NameChangeRequests : %1
+This is an error message that indicates the application was unable to close the
+listener connection used to receive NameChangeRequests. Closure may occur
+during the course of error recovery or during normal shutdown procedure. In
+either case the error is unlikely to impair the application's ability to
+process requests but it should be reported for analysis.
+
+% DHCP_DDNS_NCR_RECV_NEXT_ERROR application could not initiate the next read following a request receive.
+This is an error message indicating that NameChangeRequest listener could not
+start another read after receiving a request. While possible, this is highly
+unlikely and is probably a programmatic error. The application should recover
+on its own.
+
+% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests: %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+close the connection used to send NameChangeRequests. Closure may occur during
+the course of error recovery or during normal shutdown procedure. In either
+case the error is unlikely to impair the client's ability to send requests but
+it should be reported for analysis.
+
+% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion: %1
+This is an error message indicating that NameChangeRequest sender could not
+start another send after completing the send of the previous request. While
+possible, this is highly unlikely and is probably a programmatic error. The
+application should recover on its own.
+
+% DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR NCR UDP watch socket failed to clear: %1
+This is an error message that indicates the application was unable to reset the
+UDP NCR sender ready status after completing a send. This is programmatic error
+that should be reported. The application may or may not continue to operate
+correctly.
+
+% DHCP_DDNS_NCR_UDP_RECV_CANCELED UDP socket receive was canceled while listening for DNS Update requests
+This is a debug message indicating that the listening on a UDP socket
+for DNS update requests has been canceled. This is a normal part of
+suspending listening operations.
+
+% DHCP_DDNS_NCR_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
+This is an error message indicating that an I/O error occurred while listening
+over a UDP socket for DNS update requests. This could indicate a network
+connectivity or system resource issue.
+
+% DHCP_DDNS_NCR_UDP_SEND_CANCELED UDP socket send was canceled while sending a DNS Update request to DHCP_DDNS: %1
+This is an informational message indicating that sending requests via UDP
+socket to DHCP_DDNS has been interrupted. This is a normal part of suspending
+send operations.
+
+% DHCP_DDNS_NCR_UDP_SEND_ERROR UDP socket send error while sending a DNS Update request: %1
+This is an error message indicating that an IO error occurred while sending a
+DNS update request to DHCP_DDNS over a UDP socket. This could indicate a
+network connectivity or system resource issue.
+
+% DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR watch socket failed to close: %1
+This is an error message that indicates the application was unable to close
+the inbound or outbound side of a NCR sender's watch socket. While technically
+possible the error is highly unlikely to occur and should not impair the
+application's ability to process requests.
+
+% DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR unexpected exception thrown from the application receive completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's request receive completion handler. This is a
+programmatic error that needs to be reported. Dependent upon the nature of
+the error the application may or may not continue operating normally.
+
+% DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR unexpected exception thrown from the DHCP-DDNS client send completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's send completion handler. This is a programmatic
+error that needs to be reported. Dependent upon the nature of the error the
+client may or may not continue operating normally.
diff --git a/src/lib/dhcp_ddns/libdhcp_ddns.dox b/src/lib/dhcp_ddns/libdhcp_ddns.dox
new file mode 100644
index 0000000..c285fd8
--- /dev/null
+++ b/src/lib/dhcp_ddns/libdhcp_ddns.dox
@@ -0,0 +1,61 @@
+// Copyright (C) 2013-2020 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/.
+
+/**
+@page libdhcp_ddns libkea-dhcp_ddns - DHCP_DDNS Request I/O Library
+
+@section libdhcp_ddnsIntro Introduction
+
+This is a library of classes (in the isc::dhcp_ddns namespace) for sending
+and receiving requests used by ISC's DHCP-DDNS (aka D2) service to carry
+out DHCP-driven DNS updates. Each request contains the following information:
+
+ - change-type - indicates if this is a request to add or remove DNS entries
+ - forward-change - indicates if the forward DNS zone (the one that
+ contains name to address mappings) should be updated
+ - reverse-change - indicates if reverse DNS zone (which contains the
+ address to name mappings) should be updated
+ - fqdn - the fully qualified domain name whose DNS entries should be updated
+ - ip-address - IP address (v4 or v6) leased to the client with the
+ given FQDN
+ - dhcid - DHCID (a form of identification) of the client to whom the IP
+ address is leased
+ - lease-expires-on - timestamp containing the date/time the lease expires
+ - lease-length - duration in seconds for which the lease is valid.
+
+These requests are implemented in this library by the class,
+isc::dhcp_ddns::NameChangeRequest. This class provides services for
+constructing the requests as well as marshalling them to and from various
+transport formats. Currently, the only format supported is JSON, however the
+design of the classes in this library is such that supporting additional
+formats will be easy to add. The JSON "schema" is documented here:
+isc::dhcp_ddns::NameChangeRequest::fromJSON().
+
+For sending and receiving NameChangeRequests, this library supplies an abstract
+pair of classes, isc::dhcp_ddns::NameChangeSender and
+isc::dhcp_ddns::NameChangeListener. NameChangeSender defines the public
+interface that DHCP_DDNS clients, such as DHCP servers, use for sending
+requests to DHCP_DDNS. NameChangeListener is used by request consumers,
+primarily the DHCP_DDNS service, for receiving the requests.
+
+By providing abstract interfaces, the implementation isolates the senders and
+listeners from any underlying details of request transportation. This was done
+to allow support for a variety of transportation mechanisms. Currently, the
+only transport supported is via UDP Sockets.
+
+The UDP implementation is provided by isc::dhcp_ddns::NameChangeUDPSender
+and isc::dhcp_ddns::NameChangeUDPListener. The implementation is strictly
+unidirectional: there is no explicit acknowledgment of receipt of a
+request so, as it is UDP, no guarantee of delivery.
+
+@section libdhcp_ddnsMTConsiderations Multi-Threading Consideration for DHCP_DDNS Request I/O Library
+
+By default this library is not thread safe (it uses asynchronous I/O) at
+the exception of the Name Change Request sender class (@c
+isc::dhcp_ddns::NameChangeSender) which is Kea thread safe (i.e. it is
+thread safe when the multi-threading mode is true).
+
+*/
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
new file mode 100644
index 0000000..220fb3b
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -0,0 +1,499 @@
+// Copyright (C) 2013-2020 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 <asiolink/asio_wrapper.h>
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <mutex>
+
+namespace isc {
+namespace dhcp_ddns {
+
+using namespace isc::util;
+using namespace std;
+
+NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str) {
+ if (boost::iequals(protocol_str, "UDP")) {
+ return (NCR_UDP);
+ }
+
+ if (boost::iequals(protocol_str, "TCP")) {
+ return (NCR_TCP);
+ }
+
+ isc_throw(BadValue,
+ "Invalid NameChangeRequest protocol: " << protocol_str);
+}
+
+std::string ncrProtocolToString(NameChangeProtocol protocol) {
+ switch (protocol) {
+ case NCR_UDP:
+ return ("UDP");
+ case NCR_TCP:
+ return ("TCP");
+ default:
+ break;
+ }
+
+ std::ostringstream stream;
+ stream << "UNKNOWN(" << protocol << ")";
+ return (stream.str());
+}
+
+
+//************************** NameChangeListener ***************************
+
+NameChangeListener::NameChangeListener(RequestReceiveHandler&
+ recv_handler)
+ : listening_(false), io_pending_(false), recv_handler_(recv_handler) {
+};
+
+
+void
+NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
+ if (amListening()) {
+ // This amounts to a programmatic error.
+ isc_throw(NcrListenerError, "NameChangeListener is already listening");
+ }
+
+ // Call implementation dependent open.
+ try {
+ open(io_service);
+ } catch (const isc::Exception& ex) {
+ stopListening();
+ isc_throw(NcrListenerOpenError, "Open failed: " << ex.what());
+ }
+
+ // Set our status to listening.
+ setListening(true);
+
+ // Start the first asynchronous receive.
+ try {
+ receiveNext();
+ } catch (const isc::Exception& ex) {
+ stopListening();
+ isc_throw(NcrListenerReceiveError, "doReceive failed: " << ex.what());
+ }
+}
+
+void
+NameChangeListener::receiveNext() {
+ io_pending_ = true;
+ doReceive();
+}
+
+void
+NameChangeListener::stopListening() {
+ try {
+ // Call implementation dependent close.
+ close();
+ } catch (const isc::Exception &ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR)
+ .arg(ex.what());
+ }
+
+ // Set it false, no matter what. This allows us to at least try to
+ // re-open via startListening().
+ setListening(false);
+}
+
+void
+NameChangeListener::invokeRecvHandler(const Result result,
+ NameChangeRequestPtr& ncr) {
+ // Call the registered application layer handler.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ io_pending_ = false;
+ recv_handler_(result, ncr);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+
+ // Start the next IO layer asynchronous receive.
+ // In the event the handler above intervened and decided to stop listening
+ // we need to check that first.
+ if (amListening()) {
+ try {
+ receiveNext();
+ } catch (const isc::Exception& ex) {
+ // It is possible though unlikely, for doReceive to fail without
+ // scheduling the read. While, unlikely, it does mean the callback
+ // will not get called with a failure. A throw here would surface
+ // at the IOService::run (or run variant) invocation. So we will
+ // close the window by invoking the application handler with
+ // a failed result, and let the application layer sort it out.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
+ .arg(ex.what());
+
+ // Call the registered application layer handler.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ NameChangeRequestPtr empty;
+ try {
+ io_pending_ = false;
+ recv_handler_(ERROR, empty);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+ }
+ }
+}
+
+//************************* NameChangeSender ******************************
+
+NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
+ size_t send_queue_max)
+ : sending_(false), send_handler_(send_handler),
+ send_queue_max_(send_queue_max), io_service_(NULL), mutex_(new mutex) {
+
+ // Queue size must be big enough to hold at least 1 entry.
+ setQueueMaxSize(send_queue_max);
+}
+
+void
+NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
+ if (amSending()) {
+ // This amounts to a programmatic error.
+ isc_throw(NcrSenderError, "NameChangeSender is already sending");
+ }
+
+ // Call implementation dependent open.
+ try {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ startSendingInternal(io_service);
+ } else {
+ startSendingInternal(io_service);
+ }
+ } catch (const isc::Exception& ex) {
+ stopSending();
+ isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
+ }
+}
+
+void
+NameChangeSender::startSendingInternal(isc::asiolink::IOService& io_service) {
+ // Clear send marker.
+ ncr_to_send_.reset();
+
+ // Remember io service we're given.
+ io_service_ = &io_service;
+ open(io_service);
+
+ // Set our status to sending.
+ setSending(true);
+
+ // If there's any queued already.. we'll start sending.
+ sendNext();
+}
+
+void
+NameChangeSender::stopSending() {
+ // Set it send indicator to false, no matter what. This allows us to at
+ // least try to re-open via startSending(). Also, setting it false now,
+ // allows us to break sendNext() chain in invokeSendHandler.
+ setSending(false);
+
+ // If there is an outstanding IO to complete, attempt to process it.
+ if (ioReady() && io_service_ != NULL) {
+ try {
+ runReadyIO();
+ } catch (const std::exception& ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_NCR_FLUSH_IO_ERROR).arg(ex.what());
+ }
+ }
+
+ try {
+ // Call implementation dependent close.
+ close();
+ } catch (const isc::Exception &ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
+ }
+
+ io_service_ = NULL;
+}
+
+void
+NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
+ if (!amSending()) {
+ isc_throw(NcrSenderError, "sender is not ready to send");
+ }
+
+ if (!ncr) {
+ isc_throw(NcrSenderError, "request to send is empty");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ sendRequestInternal(ncr);
+ } else {
+ sendRequestInternal(ncr);
+ }
+}
+
+void
+NameChangeSender::sendRequestInternal(NameChangeRequestPtr& ncr) {
+ if (send_queue_.size() >= send_queue_max_) {
+ isc_throw(NcrSenderQueueFull,
+ "send queue has reached maximum capacity: "
+ << send_queue_max_);
+ }
+
+ // Put it on the queue.
+ send_queue_.push_back(ncr);
+
+ // Call sendNext to schedule the next one to go.
+ sendNext();
+}
+
+void
+NameChangeSender::sendNext() {
+ if (ncr_to_send_) {
+ // @todo Not sure if there is any risk of getting stuck here but
+ // an interval timer to defend would be good.
+ // In reality, the derivation should ensure they timeout themselves
+ return;
+ }
+
+ // If queue isn't empty, then get one from the front. Note we leave
+ // it on the front of the queue until we successfully send it.
+ if (!send_queue_.empty()) {
+ ncr_to_send_ = send_queue_.front();
+
+ // @todo start defense timer
+ // If a send were to hang and we timed it out, then timeout
+ // handler need to cycle thru open/close ?
+
+ // Call implementation dependent send.
+ doSend(ncr_to_send_);
+ }
+}
+
+void
+NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ invokeSendHandlerInternal(result);
+ } else {
+ invokeSendHandlerInternal(result);
+ }
+}
+
+void
+NameChangeSender::invokeSendHandlerInternal(const NameChangeSender::Result result) {
+ // @todo reset defense timer
+ if (result == SUCCESS) {
+ // It shipped so pull it off the queue.
+ send_queue_.pop_front();
+ }
+
+ // Invoke the completion handler passing in the result and a pointer
+ // the request involved.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ send_handler_(result, ncr_to_send_);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+
+ // Clear the pending ncr pointer.
+ ncr_to_send_.reset();
+
+ // Set up the next send
+ try {
+ if (amSending()) {
+ sendNext();
+ }
+ } catch (const isc::Exception& ex) {
+ // It is possible though unlikely, for sendNext to fail without
+ // scheduling the send. While, unlikely, it does mean the callback
+ // will not get called with a failure. A throw here would surface
+ // at the IOService::run (or run variant) invocation. So we will
+ // close the window by invoking the application handler with
+ // a failed result, and let the application layer sort it out.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
+ .arg(ex.what());
+
+ // Invoke the completion handler passing in failed result.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ send_handler_(ERROR, ncr_to_send_);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR).arg(ex.what());
+ }
+ }
+}
+
+void
+NameChangeSender::skipNext() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ skipNextInternal();
+ } else {
+ skipNextInternal();
+ }
+}
+
+void
+NameChangeSender::skipNextInternal() {
+ if (!send_queue_.empty()) {
+ // Discards the request at the front of the queue.
+ send_queue_.pop_front();
+ }
+}
+
+void
+NameChangeSender::clearSendQueue() {
+ if (amSending()) {
+ isc_throw(NcrSenderError, "Cannot clear queue while sending");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ send_queue_.clear();
+ } else {
+ send_queue_.clear();
+ }
+}
+
+void
+NameChangeSender::setQueueMaxSize(const size_t new_max) {
+ if (new_max == 0) {
+ isc_throw(NcrSenderError, "NameChangeSender:"
+ " queue size must be greater than zero");
+ }
+
+ send_queue_max_ = new_max;
+}
+
+size_t
+NameChangeSender::getQueueSize() const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ return (getQueueSizeInternal());
+ } else {
+ return (getQueueSizeInternal());
+ }
+}
+
+size_t
+NameChangeSender::getQueueSizeInternal() const {
+ return (send_queue_.size());
+}
+
+const NameChangeRequestPtr&
+NameChangeSender::peekAt(const size_t index) const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ return (peekAtInternal(index));
+ } else {
+ return (peekAtInternal(index));
+ }
+}
+
+const NameChangeRequestPtr&
+NameChangeSender::peekAtInternal(const size_t index) const {
+ auto size = getQueueSizeInternal();
+ if (index >= size) {
+ isc_throw(NcrSenderError,
+ "NameChangeSender::peekAt peek beyond end of queue attempted"
+ << " index: " << index << " queue size: " << size);
+ }
+
+ return (send_queue_.at(index));
+}
+
+bool
+NameChangeSender::isSendInProgress() const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ return ((ncr_to_send_) ? true : false);
+ } else {
+ return ((ncr_to_send_) ? true : false);
+ }
+}
+
+void
+NameChangeSender::assumeQueue(NameChangeSender& source_sender) {
+ if (source_sender.amSending()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " source sender is actively sending");
+ }
+
+ if (amSending()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " target sender is actively sending");
+ }
+
+ if (getQueueMaxSize() < source_sender.getQueueSize()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " source queue count exceeds target queue max");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_guard<mutex> lock(*mutex_);
+ assumeQueueInternal(source_sender);
+ } else {
+ assumeQueueInternal(source_sender);
+ }
+}
+
+void
+NameChangeSender::assumeQueueInternal(NameChangeSender& source_sender) {
+ if (!send_queue_.empty()) {
+ isc_throw(NcrSenderError, "Cannot assume queue:"
+ " target queue is not empty");
+ }
+
+ send_queue_.swap(source_sender.getSendQueue());
+}
+
+int
+NameChangeSender::getSelectFd() {
+ isc_throw(NotImplemented, "NameChangeSender::getSelectFd is not supported");
+}
+
+void
+NameChangeSender::runReadyIO() {
+ if (!io_service_) {
+ isc_throw(NcrSenderError, "NameChangeSender::runReadyIO"
+ " sender io service is null");
+ }
+
+ // We shouldn't be here if IO isn't ready to execute.
+ // By running poll we're guaranteed not to hang.
+ /// @todo Trac# 3325 requests that asiolink::IOService provide a
+ /// wrapper for poll().
+ io_service_->get_io_service().poll_one();
+}
+
+} // namespace dhcp_ddns
+} // namespace isc
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
new file mode 100644
index 0000000..664057d
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -0,0 +1,853 @@
+// Copyright (C) 2013-2020 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 NCR_IO_H
+#define NCR_IO_H
+
+/// @file ncr_io.h
+/// @brief This file defines abstract classes for exchanging NameChangeRequests.
+///
+/// These classes are used for sending and receiving requests to update DNS
+/// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately,
+/// NCRs must move through the following three layers in order to implement
+/// DHCP-DDNS:
+///
+/// * Application layer - the business layer which needs to
+/// transport NameChangeRequests, and is unaware of the means by which
+/// they are transported.
+///
+/// * NameChangeRequest layer - This is the layer which acts as the
+/// intermediary between the Application layer and the IO layer. It must
+/// be able to move NameChangeRequests to the IO layer as raw data and move
+/// raw data from the IO layer in the Application layer as
+/// NameChangeRequests.
+///
+/// * IO layer - the low-level layer that is directly responsible for
+/// sending and receiving data asynchronously and is supplied through
+/// other libraries. This layer is largely unaware of the nature of the
+/// data being transmitted. In other words, it doesn't know beans about
+/// NCRs.
+///
+/// The abstract classes defined here implement the latter, middle layer,
+/// the NameChangeRequest layer. There are two types of participants in this
+/// middle ground:
+///
+/// * listeners - Receive NCRs from one or more sources. The DHCP-DDNS
+/// application, (aka D2), is a listener. Listeners are embodied by the
+/// class, NameChangeListener.
+///
+/// * senders - sends NCRs to a given target. DHCP servers are senders.
+/// Senders are embodied by the class, NameChangeSender.
+///
+/// These two classes present a public interface for asynchronous
+/// communications that is independent of the IO layer mechanisms. While the
+/// type and details of the IO mechanism are not relevant to either class, it
+/// is presumed to use isc::asiolink library for asynchronous event processing.
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <deque>
+#include <mutex>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Defines the list of socket protocols supported.
+/// Currently only UDP is implemented.
+/// @todo TCP is intended to be implemented prior 1.0 release.
+/// @todo Give some thought to an ANY protocol which might try
+/// first as UDP then as TCP, etc.
+enum NameChangeProtocol {
+ NCR_UDP,
+ NCR_TCP
+};
+
+/// @brief Function which converts text labels to @ref NameChangeProtocol enums.
+///
+/// @param protocol_str text to convert to an enum.
+/// Valid string values: "UDP", "TCP"
+///
+/// @return NameChangeProtocol value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeProtocol stringToNcrProtocol(const std::string& protocol_str);
+
+/// @brief Function which converts @ref NameChangeProtocol enums to text labels.
+///
+/// @param protocol enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrProtocolToString(NameChangeProtocol protocol);
+
+/// @brief Exception thrown if an NcrListenerError encounters a general error.
+class NcrListenerError : public isc::Exception {
+public:
+ NcrListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrListenerOpenError : public isc::Exception {
+public:
+ NcrListenerOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO receive.
+class NcrListenerReceiveError : public isc::Exception {
+public:
+ NcrListenerReceiveError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for receiving NameChangeRequests.
+///
+/// NameChangeListener provides the means to:
+/// - Supply a callback to invoke upon receipt of an NCR or a listening
+/// error
+/// - Start listening using a given IOService instance to process events
+/// - Stop listening
+///
+/// It implements the high level logic flow to listen until a request arrives,
+/// invoke the implementation's handler and return to listening for the next
+/// request.
+///
+/// It provides virtual methods that allow derivations supply implementations
+/// to open the appropriate IO source, perform a listen, and close the IO
+/// source.
+///
+/// The overall design is based on a callback chain. The listener's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// receive inbound NameChangeRequests. The listener derivation will supply
+/// its own callback to the IO layer to process receive events from its IO
+/// source. This is referred to as the NameChangeRequest completion handler.
+/// It is through this handler that the NameChangeRequest layer gains access
+/// to the low level IO read service results. It is expected to assemble
+/// NameChangeRequests from the inbound data and forward them to the
+/// application layer by invoking the application layer callback registered
+/// with the listener.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestReceiveHandler. It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive inbound NCRs, a caller implements a derivation of the
+/// RequestReceiveHandler and supplies an instance of this derivation to the
+/// NameChangeListener constructor. This "registers" the handler with the
+/// listener.
+///
+/// To begin listening, the caller invokes the listener's startListener()
+/// method, passing in an IOService instance. This in turn will pass the
+/// IOService into the virtual method, open(). The open method is where the
+/// listener derivation performs the steps necessary to prepare its IO source
+/// for reception (e.g. opening a socket, connecting to a database).
+///
+/// Assuming the open is successful, startListener will call receiveNext, to
+/// initiate an asynchronous receive. This method calls the virtual method,
+/// doReceive(). The listener derivation uses doReceive to instigate an IO
+/// layer asynchronous receive passing in its IO layer callback to
+/// handle receive events from the IO source.
+///
+/// As stated earlier, the derivation's NameChangeRequest completion handler
+/// MUST invoke the application layer handler registered with the listener.
+/// This is done by passing in either a success status and a populated
+/// NameChangeRequest or an error status and an empty request into the
+/// listener's invokeRecvHandler method. This is the mechanism by which the
+/// listener's caller is handed inbound NCRs.
+class NameChangeListener {
+public:
+
+ /// @brief Defines the outcome of an asynchronous NCR receive
+ enum Result {
+ SUCCESS,
+ TIME_OUT,
+ STOPPED,
+ ERROR
+ };
+
+ /// @brief Abstract class for defining application layer receive callbacks.
+ ///
+ /// Applications which will receive NameChangeRequests must provide a
+ /// derivation of this class to the listener constructor in order to
+ /// receive NameChangeRequests.
+ class RequestReceiveHandler {
+ public:
+
+ /// @brief Function operator implementing a NCR receive callback.
+ ///
+ /// This method allows the application to receive the inbound
+ /// NameChangeRequests. It is intended to function as a hand off of
+ /// information and should probably not be time-consuming.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ ///
+ /// @throw This method MUST NOT throw.
+ virtual void operator ()(const Result result,
+ NameChangeRequestPtr& ncr) = 0;
+
+ virtual ~RequestReceiveHandler() {
+ }
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param recv_handler is a pointer the application layer handler to be
+ /// invoked each time a NCR is received or a receive error occurs.
+ NameChangeListener(RequestReceiveHandler& recv_handler);
+
+ /// @brief Destructor
+ virtual ~NameChangeListener() {
+ };
+
+ /// @brief Prepares the IO for reception and initiates the first receive.
+ ///
+ /// Calls the derivation's open implementation to initialize the IO layer
+ /// source for receiving inbound requests. If successful, it starts the
+ /// first asynchronous read by receiveNext.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ ///
+ /// @throw NcrListenError if the listener is already "listening" or
+ /// in the event the open or doReceive methods fail.
+ void startListening(isc::asiolink::IOService& io_service);
+
+ /// @brief Closes the IO source and stops listen logic.
+ ///
+ /// Calls the derivation's implementation of close and marks the state
+ /// as not listening.
+ void stopListening();
+
+protected:
+ /// @brief Initiates an asynchronous receive
+ ///
+ /// Sets context information to indicate that IO is in progress and invokes
+ /// the derivation's asynchronous receive method, doReceive. Note doReceive
+ /// should not be called outside this method to ensure context information
+ /// integrity.
+ ///
+ /// @throw Derivation's doReceive method may throw isc::Exception upon
+ /// error.
+ void receiveNext();
+
+ /// @brief Calls the NCR receive handler registered with the listener.
+ ///
+ /// This is the hook by which the listener's caller's NCR receive handler
+ /// is called. This method MUST be invoked by the derivation's
+ /// implementation of doReceive.
+ ///
+ /// NOTE:
+ /// The handler invoked by this method MUST NOT THROW. The handler is
+ /// at application level and should trap and handle any errors at
+ /// that level, rather than throw exceptions. If an error has occurred
+ /// prior to invoking the handler, it will be expressed in terms a failed
+ /// result being passed to the handler, not a throw. Therefore any
+ /// exceptions at the handler level are application issues and should be
+ /// dealt with at that level.
+ ///
+ /// This method does wrap the handler invocation within a try-catch
+ /// block as a fail-safe. The exception will be logged but the
+ /// receive logic will continue. What this implies is that continued
+ /// operation may or may not succeed as the application has violated
+ /// the interface contract.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr);
+
+ /// @brief Abstract method which opens the IO source for reception.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// prepare the IO source to receive requests.
+ ///
+ /// @param io_service is the IOService that process IO events.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+ /// @brief Abstract method which closes the IO source.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// "close" the IO source.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void close() = 0;
+
+ /// @brief Initiates an IO layer asynchronous read.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// initiate an asynchronous read of the IO source with the
+ /// derivation's IO layer handler as the IO completion callback.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void doReceive() = 0;
+
+public:
+
+ /// @brief Returns true if the listener is listening, false otherwise.
+ ///
+ /// A true value indicates that the IO source has been opened successfully,
+ /// and that receive loop logic is active. This implies that closing the
+ /// IO source will interrupt that operation, resulting in a callback
+ /// invocation.
+ ///
+ /// @return The listening mode.
+ bool amListening() const {
+ return (listening_);
+ }
+
+ /// @brief Returns true if the listener has an IO call in progress.
+ ///
+ /// A true value indicates that the listener has an asynchronous IO in
+ /// progress which will complete at some point in the future. Completion
+ /// of the call will invoke the registered callback. It is important to
+ /// understand that the listener and its related objects should not be
+ /// deleted while there is an IO call pending. This can result in the
+ /// IO service attempting to invoke methods on objects that are no longer
+ /// valid.
+ ///
+ /// @return The IO pending flag.
+ bool isIoPending() const {
+ return (io_pending_);
+ }
+
+private:
+ /// @brief Sets the listening indicator to the given value.
+ ///
+ /// Note, this method is private as it is used the base class is solely
+ /// responsible for managing the state.
+ ///
+ /// @param value is the new value to assign to the indicator.
+ void setListening(bool value) {
+ listening_ = value;
+ }
+
+ /// @brief Indicates if the listener is in listening mode.
+ bool listening_;
+
+ /// @brief Indicates that listener has an async IO pending completion.
+ bool io_pending_;
+
+ /// @brief Application level NCR receive completion handler.
+ RequestReceiveHandler& recv_handler_;
+};
+
+/// @brief Defines a smart pointer to an instance of a listener.
+typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr;
+
+
+/// @brief Thrown when a NameChangeSender encounters an error.
+class NcrSenderError : public isc::Exception {
+public:
+ NcrSenderError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrSenderOpenError : public isc::Exception {
+public:
+ NcrSenderOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderQueueFull : public isc::Exception {
+public:
+ NcrSenderQueueFull(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderSendError : public isc::Exception {
+public:
+ NcrSenderSendError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for sending NameChangeRequests.
+///
+/// NameChangeSender provides the means to:
+/// - Supply a callback to invoke upon completing the delivery of an NCR or a
+/// send error
+/// - Start sending using a given IOService instance to process events
+/// - Queue NCRs for delivery
+/// - Stop sending
+///
+/// It implements the high level logic flow to queue requests for delivery,
+/// and ship them one at a time, waiting for the send to complete prior to
+/// sending the next request in the queue. If a send fails, the request
+/// will remain at the front of queue and the send will be retried
+/// endlessly unless the caller dequeues the request. Note, it is presumed that
+/// a send failure is some form of IO error such as loss of connectivity and
+/// not a message content error. It should not be possible to queue an invalid
+/// message.
+///
+/// It should be noted that once a request is placed onto the send queue it
+/// will remain there until one of three things occur:
+/// * It is successfully delivered
+/// * @c NameChangeSender::skipNext() is called
+/// * @c NameChangeSender::clearSendQueue() is called
+///
+/// The queue contents are preserved across start and stop listening
+/// transitions. This is to provide for error recovery without losing
+/// undelivered requests.
+
+/// It provides virtual methods so derivations may supply implementations to
+/// open the appropriate IO sink, perform a send, and close the IO sink.
+///
+/// The overall design is based on a callback chain. The sender's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// be given send completion notifications. The sender derivation will employ
+/// its own callback at the IO layer to process send events from its IO sink.
+/// This callback is expected to forward the outcome of each asynchronous send
+/// to the application layer by invoking the application layer callback
+/// registered with the sender.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestSendHandler. It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive send completion notifications, a caller implements a
+/// derivation of the RequestSendHandler and supplies an instance of this
+/// derivation to the NameChangeSender constructor. This "registers" the
+/// handler with the sender.
+///
+/// To begin sending, the caller invokes the listener's startSending()
+/// method, passing in an IOService instance. This in turn will pass the
+/// IOService into the virtual method, open(). The open method is where the
+/// sender derivation performs the steps necessary to prepare its IO sink for
+/// output (e.g. opening a socket, connecting to a database). At this point,
+/// the sender is ready to send messages.
+///
+/// In order to send a request, the application layer invokes the sender
+/// method, sendRequest(), passing in the NameChangeRequest to send. This
+/// method places the request onto the back of the send queue, and then invokes
+/// the sender method, sendNext().
+///
+/// If there is already a send in progress when sendNext() is called, the method
+/// will return immediately rather than initiate the next send. This is to
+/// ensure that sends are processed sequentially.
+///
+/// If there is not a send in progress and the send queue is not empty,
+/// the sendNext method will pass the NCR at the front of the send queue into
+/// the virtual doSend() method.
+///
+/// The sender derivation uses this doSend() method to instigate an IO layer
+/// asynchronous send with its IO layer callback to handle send events from its
+/// IO sink.
+///
+/// As stated earlier, the derivation's IO layer callback MUST invoke the
+/// application layer handler registered with the sender. This is done by
+/// passing in a status indicating the outcome of the send into the sender's
+/// invokeSendHandler method. This is the mechanism by which the sender's
+/// caller is handed outbound notifications.
+
+/// After invoking the application layer handler, the invokeSendHandler method
+/// will call the sendNext() method to initiate the next send. This ensures
+/// that requests continue to dequeue and ship.
+///
+class NameChangeSender {
+public:
+
+ /// @brief Defines the type used for the request send queue.
+ typedef std::deque<NameChangeRequestPtr> SendQueue;
+
+ /// @brief Defines a default maximum number of entries in the send queue.
+ static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+ /// @brief Defines the outcome of an asynchronous NCR send.
+ enum Result {
+ SUCCESS,
+ TIME_OUT,
+ STOPPED,
+ ERROR
+ };
+
+ /// @brief Abstract class for defining application layer send callbacks.
+ ///
+ /// Applications which will send NameChangeRequests must provide a
+ /// derivation of this class to the sender constructor in order to
+ /// receive send outcome notifications.
+ class RequestSendHandler {
+ public:
+
+ /// @brief Function operator implementing a NCR send callback.
+ ///
+ /// This method allows the application to receive the outcome of
+ /// each send. It is intended to function as a hand off of information
+ /// and should probably not be time-consuming.
+ ///
+ /// @param result contains that send outcome status.
+ /// @param ncr is a pointer to the NameChangeRequest that was
+ /// delivered (or attempted).
+ ///
+ /// @throw This method MUST NOT throw.
+ virtual void operator ()(const Result result,
+ NameChangeRequestPtr& ncr) = 0;
+
+ virtual ~RequestSendHandler() {
+ }
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param send_handler is a pointer the application layer handler to be
+ /// invoked each time a NCR send attempt completes.
+ /// @param send_queue_max is the maximum number of entries allowed in the
+ /// send queue. Once the maximum number is reached, all calls to
+ /// sendRequest will fail with an exception.
+ NameChangeSender(RequestSendHandler& send_handler,
+ size_t send_queue_max = MAX_QUEUE_DEFAULT);
+
+ /// @brief Destructor
+ virtual ~NameChangeSender() {
+ }
+
+ /// @brief Prepares the IO for transmission.
+ ///
+ /// Calls the derivation's open implementation to initialize the IO layer
+ /// sink for sending outbound requests.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ ///
+ /// @throw NcrSenderError if the sender is already "sending" or
+ /// NcrSenderOpenError if the open fails.
+ void startSending(isc::asiolink::IOService & io_service);
+
+ /// @brief Closes the IO sink and stops send logic.
+ ///
+ /// Calls the derivation's implementation of close and marks the state
+ /// as not sending.
+ void stopSending();
+
+ /// @brief Queues the given request to be sent.
+ ///
+ /// The given request is placed at the back of the send queue and then
+ /// sendNext is invoked.
+ ///
+ /// @param ncr is the NameChangeRequest to send.
+ ///
+ /// @throw NcrSenderError if the sender is not in sending state or
+ /// the request is empty; NcrSenderQueueFull if the send queue has reached
+ /// capacity.
+ void sendRequest(NameChangeRequestPtr& ncr);
+
+ /// @brief Move all queued requests from a given sender into the send queue
+ ///
+ /// Moves all of the entries in the given sender's queue and places them
+ /// into send queue. This provides a mechanism of reassigning queued
+ /// messages from one sender to another. This is useful for dealing with
+ /// dynamic configuration changes.
+ ///
+ /// @param source_sender from whom the queued messages will be taken
+ ///
+ /// @throw NcrSenderError if either sender is in send mode, if the number of
+ /// messages in the source sender's queue is larger than this sender's
+ /// maximum queue size, or if this sender's queue is not empty.
+ void assumeQueue(NameChangeSender& source_sender);
+
+ /// @brief Returns a file descriptor suitable for use with select
+ ///
+ /// The value returned is an open file descriptor which can be used with
+ /// select() system call to monitor the sender for IO events. This allows
+ /// NameChangeSenders to be used in applications which use select, rather
+ /// than IOService to wait for IO events to occur.
+ ///
+ /// @warning Attempting other use of this value may lead to unpredictable
+ /// behavior in the sender.
+ ///
+ /// @return Returns an "open" file descriptor
+ ///
+ /// @throw NcrSenderError if the sender is not in send mode,
+ virtual int getSelectFd() = 0;
+
+ /// @brief Returns whether or not the sender has IO ready to process.
+ ///
+ /// @return true if the sender has at IO ready, false otherwise.
+ virtual bool ioReady() = 0;
+
+private:
+
+ /// @brief Prepares the IO for transmission in a thread safe context.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ void startSendingInternal(isc::asiolink::IOService & io_service);
+
+ /// @brief Queues the given request to be sent in a thread safe context.
+ ///
+ /// @param ncr is the NameChangeRequest to send.
+ ///
+ /// @throw NcrSenderQueueFull if the send queue has reached capacity.
+ void sendRequestInternal(NameChangeRequestPtr& ncr);
+
+ /// @brief Move all queued requests from a given sender into the send queue
+ /// in a thread safe context.
+ ///
+ /// @param source_sender from whom the queued messages will be taken
+ ///
+ /// @throw NcrSenderError if this sender's queue is not empty.
+ void assumeQueueInternal(NameChangeSender& source_sender);
+
+ /// @brief Calls the NCR send completion handler registered with the
+ /// sender in a thread safe context.
+ ///
+ /// @param result contains that send outcome status.
+ void invokeSendHandlerInternal(const NameChangeSender::Result result);
+
+ /// @brief Removes the request at the front of the send queue in a thread
+ /// safe context.
+ void skipNextInternal();
+
+ /// @brief Returns the number of entries currently in the send queue in a
+ /// thread safe context.
+ ///
+ /// @return the queue size.
+ size_t getQueueSizeInternal() const;
+
+ /// @brief Returns the entry at a given position in the queue in a thread
+ /// safe context.
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw NcrSenderError if the given index is beyond the
+ /// end of the queue.
+ const NameChangeRequestPtr& peekAtInternal(const size_t index) const;
+
+protected:
+
+ /// @brief Dequeues and sends the next request on the send queue in a thread
+ /// safe context.
+ ///
+ /// If there is already a send in progress just return. If there is not
+ /// a send in progress and the send queue is not empty the grab the next
+ /// message on the front of the queue and call doSend().
+ void sendNext();
+
+ /// @brief Calls the NCR send completion handler registered with the
+ /// sender.
+ ///
+ /// This is the hook by which the sender's caller's NCR send completion
+ /// handler is called. This method MUST be invoked by the derivation's
+ /// implementation of doSend. Note that if the send was a success, the
+ /// entry at the front of the queue is removed from the queue.
+ /// If not we leave it there so we can retry it. After we invoke the
+ /// handler we clear the pending ncr value and queue up the next send.
+ ///
+ /// NOTE:
+ /// The handler invoked by this method MUST NOT THROW. The handler is
+ /// application level logic and should trap and handle any errors at
+ /// that level, rather than throw exceptions. If IO errors have occurred
+ /// prior to invoking the handler, they are expressed in terms a failed
+ /// result being passed to the handler. Therefore any exceptions at the
+ /// handler level are application issues and should be dealt with at that
+ /// level.
+ ///
+ /// This method does wrap the handler invocation within a try-catch
+ /// block as a fail-safe. The exception will be logged but the
+ /// send logic will continue. What this implies is that continued
+ /// operation may or may not succeed as the application has violated
+ /// the interface contract.
+ ///
+ /// @param result contains that send outcome status.
+ void invokeSendHandler(const NameChangeSender::Result result);
+
+ /// @brief Abstract method which opens the IO sink for transmission.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// prepare the IO sink to send requests.
+ ///
+ /// @param io_service is the IOService that process IO events.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+ /// @brief Abstract method which closes the IO sink.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// "close" the IO sink.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void close() = 0;
+
+ /// @brief Initiates an IO layer asynchronous send
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// initiate an asynchronous send through the IO sink of the given NCR.
+ ///
+ /// @param ncr is a pointer to the NameChangeRequest to send.
+ /// derivation's IO layer handler as the IO completion callback.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void doSend(NameChangeRequestPtr& ncr) = 0;
+
+public:
+
+ /// @brief Removes the request at the front of the send queue
+ ///
+ /// This method can be used to avoid further retries of a failed
+ /// send. It is provided primarily as a just-in-case measure. Since
+ /// a failed send results in the same request being retried continuously
+ /// this method makes it possible to remove that entry, causing the
+ /// subsequent entry in the queue to be attempted on the next send.
+ /// It is presumed that sends will only fail due to some sort of
+ /// communications issue. In the unlikely event that a request is
+ /// somehow tainted and causes an send failure based on its content,
+ /// this method provides a means to remove the message.
+ void skipNext();
+
+ /// @brief Flushes all entries in the send queue
+ ///
+ /// This method can be used to discard all of the NCRs currently in the
+ /// the send queue. Note it may not be called while the sender is in
+ /// the sending state.
+ ///
+ /// @throw NcrSenderError if called and sender is in sending state.
+ void clearSendQueue();
+
+ /// @brief Returns true if the sender is in send mode, false otherwise.
+ ///
+ /// A true value indicates that the IO sink has been opened successfully,
+ /// and that send loop logic is active.
+ ///
+ /// @return The send mode.
+ bool amSending() const {
+ return (sending_);
+ }
+
+ /// @brief Returns true when a send is in progress.
+ ///
+ /// A true value indicates that a request is actively in the process of
+ /// being delivered.
+ ///
+ /// @return The send in progress flag.
+ bool isSendInProgress() const;
+
+ /// @brief Returns the maximum number of entries allowed in the send queue.
+ ///
+ /// @return The queue maximum size.
+ size_t getQueueMaxSize() const {
+ return (send_queue_max_);
+ }
+
+ /// @brief Sets the maximum queue size to the given value.
+ ///
+ /// Sets the maximum number of entries allowed in the queue to the
+ /// the given value.
+ ///
+ /// @param new_max the new value to use as the maximum
+ ///
+ /// @throw NcrSenderError if the value is less than one.
+ void setQueueMaxSize(const size_t new_max);
+
+ /// @brief Returns the number of entries currently in the send queue.
+ ///
+ /// @return The queue size.
+ size_t getQueueSize() const;
+
+ /// @brief Returns the entry at a given position in the queue.
+ ///
+ /// Note that the entry is not removed from the queue.
+ ///
+ /// @param index the index of the entry in the queue to fetch.
+ /// Valid values are 0 (front of the queue) to (queue size - 1).
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw NcrSenderError if the given index is beyond the
+ /// end of the queue.
+ const NameChangeRequestPtr& peekAt(const size_t index) const;
+
+ /// @brief Processes sender IO events
+ ///
+ /// Executes at most one ready handler on the sender's IO service. If
+ /// no handlers are ready it returns immediately.
+ ///
+ /// @warning - Running all ready handlers, in theory, could process all
+ /// messages currently queued.
+ ///
+ /// NameChangeSender daisy chains requests together in its completion
+ /// by one message completion's handler initiating the next message's send.
+ /// When using UDP, a send immediately marks its event handler as ready
+ /// to run. If this occurs inside a call to ioservice::poll() or run(),
+ /// that event will also be run. If that handler calls UDP send then
+ /// that send's handler will be marked ready and executed and so on. If
+ /// there were 1000 messages in the queue then all them would be sent from
+ /// within the context of one call to runReadyIO().
+ /// By running only one handler at time, we ensure that NCR IO activity
+ /// doesn't starve other processing. It is unclear how much of a real
+ /// threat this poses but for now it is best to err on the side of caution.
+ virtual void runReadyIO();
+
+protected:
+
+ /// @brief Returns a reference to the send queue.
+ ///
+ /// @return The send queue.
+ SendQueue& getSendQueue() {
+ return (send_queue_);
+ }
+
+private:
+
+ /// @brief Sets the sending indicator to the given value.
+ ///
+ /// Note, this method is private as it is used the base class is solely
+ /// responsible for managing the state.
+ ///
+ /// @param value is the new value to assign to the indicator.
+ void setSending(bool value) {
+ sending_ = value;
+ }
+
+ /// @brief Boolean indicator which tracks sending status.
+ bool sending_;
+
+ /// @brief A pointer to registered send completion handler.
+ RequestSendHandler& send_handler_;
+
+ /// @brief Maximum number of entries permitted in the send queue.
+ size_t send_queue_max_;
+
+ /// @brief Queue of the requests waiting to be sent.
+ SendQueue send_queue_;
+
+ /// @brief Pointer to the request which is in the process of being sent.
+ NameChangeRequestPtr ncr_to_send_;
+
+ /// @brief Pointer to the IOService currently being used by the sender.
+ /// @note We need to remember the io_service but we receive it by
+ /// reference. Use a raw pointer to store it. This value should never be
+ /// exposed and is only valid while in send mode.
+ asiolink::IOService* io_service_;
+
+ /// @brief The mutex used to protect internal state.
+ const boost::scoped_ptr<std::mutex> mutex_;
+};
+
+/// @brief Defines a smart pointer to an instance of a sender.
+typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc
new file mode 100644
index 0000000..ddc01e8
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.cc
@@ -0,0 +1,696 @@
+// Copyright (C) 2013-2021 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 <dhcp_ddns/ncr_msg.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+#include <util/encode/hex.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include <sstream>
+#include <limits>
+
+
+namespace isc {
+namespace dhcp_ddns {
+
+
+NameChangeFormat stringToNcrFormat(const std::string& fmt_str) {
+ if (boost::iequals(fmt_str, "JSON")) {
+ return FMT_JSON;
+ }
+
+ isc_throw(BadValue, "Invalid NameChangeRequest format: " << fmt_str);
+}
+
+
+std::string ncrFormatToString(NameChangeFormat format) {
+ if (format == FMT_JSON) {
+ return ("JSON");
+ }
+
+ std::ostringstream stream;
+ stream << "UNKNOWN(" << format << ")";
+ return (stream.str());
+}
+
+/********************************* D2Dhcid ************************************/
+
+namespace {
+
+///
+/// @name Constants which define DHCID identifier-type
+//@{
+/// DHCID created from client's HW address.
+const uint8_t DHCID_ID_HWADDR = 0x0;
+/// DHCID created from client identifier.
+const uint8_t DHCID_ID_CLIENTID = 0x1;
+/// DHCID created from DUID.
+const uint8_t DHCID_ID_DUID = 0x2;
+
+}
+
+D2Dhcid::D2Dhcid() {
+}
+
+D2Dhcid::D2Dhcid(const std::string& data) {
+ fromStr(data);
+}
+
+D2Dhcid::D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn) {
+ fromHWAddr(hwaddr, wire_fqdn);
+}
+
+D2Dhcid::D2Dhcid(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn) {
+ fromClientId(clientid_data, wire_fqdn);
+}
+
+D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn) {
+ fromDUID(duid, wire_fqdn);
+}
+
+
+void
+D2Dhcid::fromStr(const std::string& data) {
+ bytes_.clear();
+ try {
+ isc::util::encode::decodeHex(data, bytes_);
+ } catch (const isc::Exception& ex) {
+ isc_throw(NcrMessageError, "Invalid data in Dhcid: " << ex.what());
+ }
+}
+
+std::string
+D2Dhcid::toStr() const {
+ return (isc::util::encode::encodeHex(bytes_));
+}
+
+void
+D2Dhcid::fromClientId(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn) {
+ // IPv4 Client ID containing a DUID looks like this in RFC4361
+ // Type IAID DUID
+ // +-----+----+----+----+----+----+----+---
+ // | 255 | i1 | i2 | i3 | i4 | d1 | d2 |...
+ // +-----+----+----+----+----+----+----+---
+ if (!clientid_data.empty() && clientid_data[0] == 255) {
+ if (clientid_data.size() <= 5) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier, embedded DUID "
+ "length of: " << clientid_data.size() << ", is too short");
+ }
+ // RFC3315 states that the DUID is a type code of 2 octets followed
+ // by no more then 128 octets. So add the 5 from above and make sure
+ // the length is not too long.
+ if (clientid_data.size() > 135) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier, embedded DUID "
+ "length of: " << clientid_data.size() << ", is too long");
+ }
+ std::vector<uint8_t>::const_iterator start = clientid_data.begin() + 5;
+ std::vector<uint8_t>::const_iterator end = clientid_data.end();
+ std::vector<uint8_t> duid(start, end);
+ createDigest(DHCID_ID_DUID, duid, wire_fqdn);
+ } else {
+ createDigest(DHCID_ID_CLIENTID, clientid_data, wire_fqdn);
+ }
+}
+
+void
+D2Dhcid::fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn) {
+ if (!hwaddr) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from the HW address, "
+ "NULL pointer has been specified");
+ } else if (hwaddr->hwaddr_.empty()) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from the HW address, "
+ "HW address is empty");
+ }
+ std::vector<uint8_t> hwaddr_data;
+ hwaddr_data.push_back(hwaddr->htype_);
+ hwaddr_data.insert(hwaddr_data.end(), hwaddr->hwaddr_.begin(),
+ hwaddr->hwaddr_.end());
+ createDigest(DHCID_ID_HWADDR, hwaddr_data, wire_fqdn);
+}
+
+
+void
+D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn) {
+
+ createDigest(DHCID_ID_DUID, duid.getDuid(), wire_fqdn);
+}
+
+void
+D2Dhcid::createDigest(const uint8_t identifier_type,
+ const std::vector<uint8_t>& identifier_data,
+ const std::vector<uint8_t>& wire_fqdn) {
+ // We get FQDN in the wire format, so we don't know if it is
+ // valid. It is caller's responsibility to make sure it is in
+ // the valid format. Here we just make sure it is not empty.
+ if (wire_fqdn.empty()) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "empty FQDN used to create DHCID");
+ }
+
+ // It is a responsibility of the classes which encapsulate client
+ // identifiers, e.g. DUID, to validate the client identifier data.
+ // But let's be on the safe side and at least check that it is not
+ // empty.
+ if (identifier_data.empty()) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "empty DUID used to create DHCID");
+ }
+
+ // A data buffer will be used to compute the digest.
+ std::vector<uint8_t> data = identifier_data;
+
+ // Append FQDN in the wire format.
+ data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
+
+ // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
+
+ isc::util::OutputBuffer hash(0);
+ try {
+ // We have checked already that the DUID and FQDN aren't empty
+ // so it is safe to assume that the data buffer is not empty.
+ cryptolink::digest(&data[0], data.size(), cryptolink::SHA256, hash);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
+ "error while generating DHCID from DUID: "
+ << ex.what());
+ }
+
+ // The DHCID RDATA has the following structure:
+ //
+ // < identifier-type > < digest-type > < digest >
+ //
+ // where identifier type
+
+ // Let's allocate the space for the identifier-type (2 bytes) and
+ // digest-type (1 byte). This is 3 bytes all together.
+ bytes_.resize(3 + hash.getLength());
+ // Leave first byte 0 and set the second byte. Those two bytes
+ // form the identifier-type.
+ bytes_[1] = identifier_type;
+ // Third byte is always equal to 1, which specifies SHA-256 digest type.
+ bytes_[2] = 1;
+ // Now let's append the digest.
+ std::memcpy(&bytes_[3], hash.getData(), hash.getLength());
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Dhcid& dhcid) {
+ os << dhcid.toStr();
+ return (os);
+}
+
+
+
+/**************************** NameChangeRequest ******************************/
+
+NameChangeRequest::NameChangeRequest()
+ : change_type_(CHG_ADD), forward_change_(false),
+ reverse_change_(false), fqdn_(""), ip_io_address_("0.0.0.0"),
+ dhcid_(), lease_expires_on_(), lease_length_(0), conflict_resolution_(true),
+ status_(ST_NEW) {
+}
+
+NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length,
+ const bool conflict_resolution)
+ : change_type_(change_type), forward_change_(forward_change),
+ reverse_change_(reverse_change), fqdn_(fqdn), ip_io_address_("0.0.0.0"),
+ dhcid_(dhcid), lease_expires_on_(lease_expires_on),
+ lease_length_(lease_length), conflict_resolution_(conflict_resolution),
+ status_(ST_NEW) {
+
+ // User setter to validate fqdn.
+ setFqdn(fqdn);
+
+ // User setter to validate address.
+ setIpAddress(ip_address);
+
+ // Validate the contents. This will throw a NcrMessageError if anything
+ // is invalid.
+ validateContent();
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer) {
+ // Based on the format requested, pull the marshalled request from
+ // InputBuffer and pass it into the appropriate format-specific factory.
+ NameChangeRequestPtr ncr;
+ switch (format) {
+ case FMT_JSON: {
+ try {
+ // Get the length of the JSON text.
+ size_t len = buffer.readUint16();
+
+ // Read the text from the buffer into a vector.
+ std::vector<uint8_t> vec;
+ buffer.readVector(vec, len);
+
+ // Turn the vector into a string.
+ std::string string_data(vec.begin(), vec.end());
+
+ // Pass the string of JSON text into JSON factory to create the
+ // NameChangeRequest instance. Note the factory may throw
+ // NcrMessageError.
+ ncr = NameChangeRequest::fromJSON(string_data);
+ } catch (const isc::util::InvalidBufferPosition& ex) {
+ // Read error accessing data in InputBuffer.
+ isc_throw(NcrMessageError, "fromFormat: buffer read error: "
+ << ex.what());
+ }
+
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "fromFormat - invalid format");
+ break;
+ }
+
+ return (ncr);
+}
+
+void
+NameChangeRequest::toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const {
+ // Based on the format requested, invoke the appropriate format handler
+ // which will marshal this request's contents into the OutputBuffer.
+ switch (format) {
+ case FMT_JSON: {
+ // Invoke toJSON to create a JSON text of this request's contents.
+ std::string json = toJSON();
+ uint16_t length = json.size();
+
+ // Write the length of the JSON text to the OutputBuffer first, then
+ // write the JSON text itself.
+ buffer.writeUint16(length);
+ buffer.writeData(json.c_str(), length);
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "toFormat - invalid format");
+ break;
+ }
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromJSON(const std::string& json) {
+ // This method leverages the existing JSON parsing provided by isc::data
+ // library. Should this prove to be a performance issue, it may be that
+ // lighter weight solution would be appropriate.
+
+ // Turn the string of JSON text into an Element set.
+ isc::data::ElementPtr elements;
+ try {
+ elements = isc::data::Element::fromJSON(json);
+ } catch (const isc::data::JSONError& ex) {
+ isc_throw(NcrMessageError,
+ "Malformed NameChangeRequest JSON: " << ex.what());
+ }
+
+ // Get a map of the Elements, keyed by element name.
+ ElementMap element_map = elements->mapValue();
+ isc::data::ConstElementPtr element;
+
+ // Use default constructor to create a "blank" NameChangeRequest.
+ NameChangeRequestPtr ncr(new NameChangeRequest());
+
+ // For each member of NameChangeRequest, find its element in the map and
+ // call the appropriate Element-based setter. These setters may throw
+ // NcrMessageError if the given Element is the wrong type or its data
+ // content is lexically invalid. If the element is NOT found in the
+ // map, getElement will throw NcrMessageError indicating the missing
+ // member.
+ element = ncr->getElement("change-type", element_map);
+ ncr->setChangeType(element);
+
+ element = ncr->getElement("forward-change", element_map);
+ ncr->setForwardChange(element);
+
+ element = ncr->getElement("reverse-change", element_map);
+ ncr->setReverseChange(element);
+
+ element = ncr->getElement("fqdn", element_map);
+ ncr->setFqdn(element);
+
+ element = ncr->getElement("ip-address", element_map);
+ ncr->setIpAddress(element);
+
+ element = ncr->getElement("dhcid", element_map);
+ ncr->setDhcid(element);
+
+ element = ncr->getElement("lease-expires-on", element_map);
+ ncr->setLeaseExpiresOn(element);
+
+ element = ncr->getElement("lease-length", element_map);
+ ncr->setLeaseLength(element);
+
+ // For backward compatibility use-conflict-resolution is optional
+ // and defaults to true.
+ auto found = element_map.find("use-conflict-resolution");
+ if (found != element_map.end()) {
+ ncr->setConflictResolution(found->second);
+ } else {
+ ncr->setConflictResolution(true);
+ }
+
+ // All members were in the Element set and were correct lexically. Now
+ // validate the overall content semantically. This will throw an
+ // NcrMessageError if anything is amiss.
+ ncr->validateContent();
+
+ // Everything is valid, return the new instance.
+ return (ncr);
+}
+
+std::string
+NameChangeRequest::toJSON() const {
+ // Create a JSON string of this request's contents. Note that this method
+ // does NOT use the isc::data library as generating the output is straight
+ // forward.
+ std::ostringstream stream;
+
+ stream << "{\"change-type\":" << getChangeType() << ","
+ << "\"forward-change\":"
+ << (isForwardChange() ? "true" : "false") << ","
+ << "\"reverse-change\":"
+ << (isReverseChange() ? "true" : "false") << ","
+ << "\"fqdn\":\"" << getFqdn() << "\","
+ << "\"ip-address\":\"" << getIpAddress() << "\","
+ << "\"dhcid\":\"" << getDhcid().toStr() << "\","
+ << "\"lease-expires-on\":\"" << getLeaseExpiresOnStr() << "\","
+ << "\"lease-length\":" << getLeaseLength() << ","
+ << "\"use-conflict-resolution\":"
+ << (useConflictResolution() ? "true" : "false") << "}";
+
+ return (stream.str());
+}
+
+
+void
+NameChangeRequest::validateContent() {
+ //@todo This is an initial implementation which provides a minimal amount
+ // of validation. FQDN and DHCID members are all currently
+ // strings, these may be replaced with richer classes.
+ if (fqdn_ == "") {
+ isc_throw(NcrMessageError, "FQDN cannot be blank");
+ }
+
+ // Validate the DHCID.
+ if (dhcid_.getBytes().size() == 0) {
+ isc_throw(NcrMessageError, "DHCID cannot be blank");
+ }
+
+ // Ensure the request specifies at least one direction to update.
+ if (!forward_change_ && !reverse_change_) {
+ isc_throw(NcrMessageError,
+ "Invalid Request, forward and reverse flags are both false");
+ }
+}
+
+isc::data::ConstElementPtr
+NameChangeRequest::getElement(const std::string& name,
+ const ElementMap& element_map) const {
+ // Look for "name" in the element map.
+ ElementMap::const_iterator it = element_map.find(name);
+ if (it == element_map.end()) {
+ // Didn't find the element, so throw.
+ isc_throw(NcrMessageError,
+ "NameChangeRequest value missing for: " << name );
+ }
+
+ // Found the element, return it.
+ return (it->second);
+}
+
+void
+NameChangeRequest::setChangeType(const NameChangeType value) {
+ change_type_ = value;
+}
+
+
+void
+NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
+ long raw_value = -1;
+ try {
+ // Get the element's integer value.
+ raw_value = element->intValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for change_type: " << ex.what());
+ }
+
+ if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
+ // Value is not a valid change type.
+ isc_throw(NcrMessageError,
+ "Invalid data value for change_type: " << raw_value);
+ }
+
+ // Good to go, make the assignment.
+ setChangeType(static_cast<NameChangeType>(raw_value));
+}
+
+void
+NameChangeRequest::setForwardChange(const bool value) {
+ forward_change_ = value;
+}
+
+void
+NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for forward-change: " << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setForwardChange(value);
+}
+
+void
+NameChangeRequest::setReverseChange(const bool value) {
+ reverse_change_ = value;
+}
+
+void
+NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for reverse_change: " << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setReverseChange(value);
+}
+
+
+void
+NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
+ setFqdn(element->stringValue());
+}
+
+void
+NameChangeRequest::setFqdn(const std::string& value) {
+ try {
+ dns::Name tmp(value);
+ fqdn_ = tmp.toText();
+ } catch (const std::exception& ex) {
+ isc_throw(NcrMessageError,
+ "Invalid FQDN value: " << value << ", reason: "
+ << ex.what());
+ }
+}
+
+void
+NameChangeRequest::setIpAddress(const std::string& value) {
+ // Validate IP Address.
+ try {
+ ip_io_address_ = isc::asiolink::IOAddress(value);
+ } catch (const isc::asiolink::IOError&) {
+ isc_throw(NcrMessageError,
+ "Invalid ip address string for ip_address: " << value);
+ }
+}
+
+void
+NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
+ setIpAddress(element->stringValue());
+}
+
+
+void
+NameChangeRequest::setDhcid(const std::string& value) {
+ dhcid_.fromStr(value);
+}
+
+void
+NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
+ setDhcid(element->stringValue());
+}
+
+std::string
+NameChangeRequest::getLeaseExpiresOnStr() const {
+ return (isc::util::timeToText64(lease_expires_on_));
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
+ try {
+ lease_expires_on_ = isc::util::timeFromText64(value);
+ } catch (...) {
+ // We were given an invalid string, so throw.
+ isc_throw(NcrMessageError,
+ "Invalid date-time string: [" << value << "]");
+ }
+
+}
+
+void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
+ // Pull out the string value and pass it into the string setter.
+ setLeaseExpiresOn(element->stringValue());
+}
+
+void
+NameChangeRequest::setLeaseLength(const uint32_t value) {
+ lease_length_ = value;
+}
+
+void
+NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
+ long value = -1;
+ try {
+ // Get the element's integer value.
+ value = element->intValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for lease_length: " << ex.what());
+ }
+
+ // Make sure we the range is correct and value is positive.
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is too large for unsigned 32-bit integer.");
+ }
+ if (value < 0) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is negative. It must greater than or equal to zero ");
+ }
+
+ // Good to go, make the assignment.
+ setLeaseLength(static_cast<uint32_t>(value));
+}
+
+void
+NameChangeRequest::setConflictResolution(const bool value) {
+ conflict_resolution_ = value;
+}
+
+void
+NameChangeRequest::setConflictResolution(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (const isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for use-conflict-resolution: " << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setConflictResolution(value);
+}
+
+void
+NameChangeRequest::setStatus(const NameChangeStatus value) {
+ status_ = value;
+}
+
+std::string
+NameChangeRequest::toText() const {
+ std::ostringstream stream;
+
+ stream << "Type: " << static_cast<int>(change_type_) << " (";
+ switch (change_type_) {
+ case CHG_ADD:
+ stream << "CHG_ADD)\n";
+ break;
+ case CHG_REMOVE:
+ stream << "CHG_REMOVE)\n";
+ break;
+ default:
+ // Shouldn't be possible.
+ stream << "Invalid Value\n";
+ }
+
+ stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
+ << std::endl
+ << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
+ << std::endl
+ << "FQDN: [" << fqdn_ << "]" << std::endl
+ << "IP Address: [" << ip_io_address_ << "]" << std::endl
+ << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
+ << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
+ << "Lease Length: " << lease_length_ << std::endl
+ << "Conflict Resolution: " << (conflict_resolution_ ? "yes" : "no")
+ << std::endl;
+
+ return (stream.str());
+}
+
+bool
+NameChangeRequest::operator == (const NameChangeRequest& other) const {
+ return ((change_type_ == other.change_type_) &&
+ (forward_change_ == other.forward_change_) &&
+ (reverse_change_ == other.reverse_change_) &&
+ (fqdn_ == other.fqdn_) &&
+ (ip_io_address_ == other.ip_io_address_) &&
+ (dhcid_ == other.dhcid_) &&
+ (lease_expires_on_ == other.lease_expires_on_) &&
+ (lease_length_ == other.lease_length_) &&
+ (conflict_resolution_ == other.conflict_resolution_));
+}
+
+bool
+NameChangeRequest::operator != (const NameChangeRequest& other) const {
+ return (!(*this == other));
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
new file mode 100644
index 0000000..fe8dbb1
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -0,0 +1,761 @@
+// Copyright (C) 2013-2021 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 NCR_MSG_H
+#define NCR_MSG_H
+
+/// @file ncr_msg.h
+/// @brief This file provides the classes needed to embody, compose, and
+/// decompose DNS update requests that are sent by DHCP-DDNS clients to
+/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
+
+#include <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dns/name.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/time_utilities.h>
+
+#include <time.h>
+#include <string>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
+class NcrMessageError : public isc::Exception {
+public:
+ NcrMessageError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when there is an error occurred during computation
+/// of the DHCID.
+class DhcidRdataComputeError : public isc::Exception {
+public:
+ DhcidRdataComputeError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Defines the types of DNS updates that can be requested.
+enum NameChangeType {
+ CHG_ADD,
+ CHG_REMOVE
+};
+
+/// @brief Defines the runtime processing status values for requests.
+enum NameChangeStatus {
+ ST_NEW,
+ ST_PENDING,
+ ST_COMPLETED,
+ ST_FAILED
+};
+
+/// @brief Defines the list of data wire formats supported.
+enum NameChangeFormat {
+ FMT_JSON
+};
+
+/// @brief Function which converts labels to NameChangeFormat enum values.
+///
+/// @param fmt_str text to convert to an enum.
+/// Valid string values: "JSON"
+///
+/// @return NameChangeFormat value which maps to the given string.
+///
+/// @throw isc::BadValue if given a string value which does not map to an
+/// enum value.
+extern NameChangeFormat stringToNcrFormat(const std::string& fmt_str);
+
+/// @brief Function which converts NameChangeFormat enums to text labels.
+///
+/// @param format enum value to convert to label
+///
+/// @return std:string containing the text label if the value is valid, or
+/// "UNKNOWN" if not.
+extern std::string ncrFormatToString(NameChangeFormat format);
+
+/// @brief Container class for handling the DHCID value within a
+/// NameChangeRequest. It provides conversion to and from string for JSON
+/// formatting, but stores the data internally as unsigned bytes.
+class D2Dhcid {
+public:
+ /// @brief Default constructor
+ D2Dhcid();
+
+ /// @brief Constructor - Creates a new instance, populated by converting
+ /// a given string of digits into an array of unsigned bytes.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ D2Dhcid(const std::string& data);
+
+ /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+ /// HW address.
+ ///
+ /// @param hwaddr A pointer to the object encapsulating HW address.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+ /// client identifier carried in the Client Identifier option.
+ ///
+ /// @param clientid_data Holds the raw bytes representing client identifier.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ D2Dhcid(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+ /// @c isc::dhcp::DUID.
+ ///
+ /// @param duid An object representing DUID.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ D2Dhcid(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Returns the DHCID value as a string of hexadecimal digits.
+ ///
+ /// @return a string containing a contiguous stream of digits.
+ std::string toStr() const;
+
+ /// @brief Sets the DHCID value based on the given string.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void fromStr(const std::string& data);
+
+ /// @brief Sets the DHCID value based on the Client Identifier.
+ ///
+ /// @param clientid_data Holds the raw bytes representing client identifier.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ void fromClientId(const std::vector<uint8_t>& clientid_data,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Sets the DHCID value based on the DUID and FQDN.
+ ///
+ /// This function requires that the FQDN conforms to the section 3.5
+ /// of the RFC4701, which says that the FQDN must be in lowercase.
+ /// This function doesn't validate if it really converted.
+ ///
+ /// @param duid A @c isc::dhcp::DUID object encapsulating DUID.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ void fromDUID(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Sets the DHCID value based on the HW address and FQDN.
+ ///
+ /// @param hwaddr A pointer to the object encapsulating HW address.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ void fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Returns a reference to the DHCID byte vector.
+ ///
+ /// @return a reference to the vector.
+ const std::vector<uint8_t>& getBytes() const {
+ return (bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids for equality
+ bool operator==(const D2Dhcid& other) const {
+ return (this->bytes_ == other.bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids for inequality
+ bool operator!=(const D2Dhcid& other) const {
+ return (this->bytes_ != other.bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids lexically
+ bool operator<(const D2Dhcid& other) const {
+ return (this->bytes_ < other.bytes_);
+ }
+
+private:
+
+ /// @brief Creates the DHCID using specified identifier.
+ ///
+ /// This function creates the DHCID RDATA as specified in RFC4701,
+ /// section 3.5.
+ ///
+ /// @param identifier_type is a less significant byte of the identifier-type
+ /// defined in RFC4701.
+ /// @param identifier_data A buffer holding client identifier raw data -
+ /// e.g. DUID, data carried in the Client Identifier option or client's
+ /// HW address.
+ /// @param A on-wire canonical representation of the FQDN.
+ void createDigest(const uint8_t identifier_type,
+ const std::vector<uint8_t>& identifier_data,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Storage for the DHCID value in unsigned bytes.
+ std::vector<uint8_t> bytes_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const D2Dhcid& dhcid);
+
+class NameChangeRequest;
+/// @brief Defines a pointer to a NameChangeRequest.
+typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
+
+/// @brief Defines a map of Elements, keyed by their string name.
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief Represents a DHCP-DDNS client request.
+/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
+/// request DNS updates. Each message contains a single DNS change (either an
+/// add/update or a remove) for a single FQDN. It provides marshalling services
+/// for moving instances to and from the wire. Currently, the only format
+/// supported is JSON detailed here isc::dhcp_ddns::NameChangeRequest::fromJSON
+/// The class provides an interface such that other formats can be readily
+/// supported.
+class NameChangeRequest {
+public:
+ /// @brief Default Constructor.
+ ///
+ /// @todo Currently, fromWire makes use of the ability to create an empty
+ /// NameChangeRequest and then builds it bit by bit. This means that it
+ /// is technically possible to create one and attempt to use in ways
+ /// other than intended and its invalid content may or may not be handled
+ /// gracefully by consuming code. It might be wise to revisit this
+ /// structuring such that we do not use a default constructor and only
+ /// allow valid instantiations.
+ NameChangeRequest();
+
+ /// @brief Constructor. Full constructor, which provides parameters for
+ /// all of the class members, except status.
+ ///
+ /// @param change_type the type of change (Add or Update)
+ /// @param forward_change indicates if this change should be sent to forward
+ /// DNS servers.
+ /// @param reverse_change indicates if this change should be sent to reverse
+ /// DNS servers.
+ /// @param fqdn the domain name whose pointer record(s) should be
+ /// updated.
+ /// @param ip_address the ip address leased to the given FQDN.
+ /// @param dhcid the lease client's unique DHCID.
+ /// @param lease_expires_on a timestamp containing the date/time the lease
+ /// expires.
+ /// @param lease_length the amount of time in seconds for which the
+ /// lease is valid (TTL).
+ /// @param conflict_resolution indicates whether or not conflict resolution
+ /// (per RFC 4703) is enabled.
+ NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length,
+ const bool conflict_resolution = true);
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// buffer containing a marshalled request in a given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: The buffer is expected to contain a two byte unsigned integer
+ /// which specified the length of the JSON text; followed by the JSON
+ /// text itself. This method attempts to extract "length" characters
+ /// from the buffer. This data is used to create a character string that
+ /// is than treated as JSON which is then parsed into the data needed
+ /// to create a request instance.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the input buffer containing the marshalled request
+ ///
+ /// @return a pointer to the new NameChangeRequest
+ ///
+ /// @throw NcrMessageError if an error occurs creating new
+ /// request.
+ static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into the given buffer in the given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: Upon completion, the buffer will contain a two byte unsigned
+ /// integer which specifies the length of the JSON text; followed by the
+ /// JSON text itself. The JSON text contains the names and values for all
+ /// the request data needed to reassemble the request on the receiving
+ /// end. The JSON text in the buffer is NOT null-terminated. The format
+ /// is identical that described under
+ /// isc::dhcp_ddns::NameChangeRequest::fromJSON
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the output buffer to which the request should be
+ /// marshalled.
+ void toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const;
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// string containing a JSON rendition of a request.
+ ///
+ /// The JSON expected is described below. Note that a request must be
+ /// enclosed within curly brackets "{..}" and that whitespace is optional
+ /// (it is used in the following examples for clarity).
+ ///
+ /// @code
+ /// {
+ /// "change-type" : <integer>,
+ /// "forward-change" : <boolean>,
+ /// "reverse-change" : <boolean>,
+ /// "fqdn" : "<fqdn>",
+ /// "ip-address" : "<address>",
+ /// "dhcid" : "<hex_string>",
+ /// "lease-expires-on" : "<yyyymmddHHMMSS>",
+ /// "lease-length" : <secs>,
+ /// "use-conflict-resolution": <boolean>
+ /// }
+ /// @endcode
+ ///
+ /// - change-type - indicates whether this request is to add or update
+ /// DNS entries or to remove them. The value is an integer and is
+ /// 0 for add/update and 1 for remove.
+ /// - forward-change - indicates whether the forward (name to
+ /// address) DNS zone should be updated. The value is a string
+ /// representing a boolean. It is "true" if the zone should be updated
+ /// and "false" if not. (Unlike the keyword, the boolean value is
+ /// case-insensitive.)
+ /// - reverse-change - indicates whether the reverse (address to
+ /// name) DNS zone should be updated. The value is a string
+ /// representing a boolean. It is "true" if the zone should be updated
+ /// and "false" if not. (Unlike the keyword, the boolean value is
+ /// case-insensitive.)
+ /// - fqdn - fully qualified domain name such as "myhost.example.com.".
+ /// (Note that a trailing dot will be appended if not supplied.)
+ /// - ip-address - the IPv4 or IPv6 address of the client. The value
+ /// is a string representing the IP address (e.g. "192.168.0.1" or
+ /// "2001:db8:1::2").
+ /// - dhcid - identification of the DHCP client to whom the IP address has
+ /// been leased. The value is a string containing an even number of
+ /// hexadecimal digits without delimiters such as "2C010203040A7F8E3D"
+ /// (case insensitive).
+ /// - lease-expires-on - the date and time on which the lease expires.
+ /// The value is a string of the form "yyyymmddHHMMSS" where:
+ /// - yyyy - four digit year
+ /// - mm - month of year (1-12),
+ /// - dd - day of the month (1-31),
+ /// - HH - hour of the day (0-23)
+ /// - MM - minutes of the hour (0-59)
+ /// - SS - seconds of the minute (0-59)
+ /// - lease-length - the length of the lease in seconds. This is an
+ /// integer and may range between 1 and 4294967295 (2^32 - 1) inclusive.
+ /// - use-conflict-resolution - when true, follow RFC 4703 which uses
+ /// DHCID records to prohibit multiple clients from updating an FQDN
+ ///
+ /// Examples:
+ ///
+ /// Removal of an IPv4 address from the forward DNS zone only:
+ ///
+ /// @code
+ /// {
+ /// "change-type" : 1,
+ /// "forward-change" : true,
+ /// "reverse-change" : false,
+ /// "fqdn" : "myhost.example.com.",
+ /// "ip-address" : "192.168.2.1" ,
+ /// "dhcid" : "010203040A7F8E3D" ,
+ /// "lease-expires-on" : "20130121132405",
+ /// "lease-length" : 1300,
+ /// "use-conflict-resolution": true
+ /// }
+ /// @endcode
+ ///
+ /// Addition of an IPv6 address to both forward and reverse DNS zones:
+ ///
+ /// @code
+ /// {
+ /// "change-type" : 0,
+ /// "forward-change" : true,
+ /// "reverse-change" : true,
+ /// "fqdn" : "someother.example.com.",
+ /// "ip-address" : "2001::db8:1::2",
+ /// "dhcid" : "010203040A7F8E3D" , "
+ /// "lease-expires-on" : "20130121132405",
+ /// "lease-length" : 27400,
+ /// "use-conflict-resolution": true
+ /// }
+ /// @endcode
+ ///
+ /// @param json is a string containing the JSON text
+ ///
+ /// @return a pointer to the new NameChangeRequest
+ ///
+ /// @throw NcrMessageError if an error occurs creating new request.
+ static NameChangeRequestPtr fromJSON(const std::string& json);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into a string of JSON text.
+ ///
+ /// @return a string containing the JSON rendition of the request
+ std::string toJSON() const;
+
+ /// @brief Validates the content of a populated request. This method is
+ /// used by both the full constructor and from-wire marshalling to ensure
+ /// that the request is content valid. Currently it enforces the
+ /// following rules:
+ ///
+ /// - FQDN must not be blank.
+ /// - The IP address must be a valid address.
+ /// - The DHCID must not be blank.
+ /// - The lease expiration date must be a valid date/time.
+ /// - That at least one of the two direction flags, forward change and
+ /// reverse change is true.
+ ///
+ /// @todo This is an initial implementation which provides a minimal amount
+ /// of validation. FQDN, DHCID, and IP Address members are all currently
+ /// strings, these may be replaced with richer classes.
+ ///
+ /// @throw NcrMessageError if the request content violates any
+ /// of the validation rules.
+ void validateContent();
+
+ /// @brief Fetches the request change type.
+ ///
+ /// @return the change type
+ NameChangeType getChangeType() const {
+ return (change_type_);
+ }
+
+ /// @brief Sets the change type to the given value.
+ ///
+ /// @param value is the NameChangeType value to assign to the request.
+ void setChangeType(const NameChangeType value);
+
+ /// @brief Sets the change type to the value of the given Element.
+ ///
+ /// @param element is an integer Element containing the change type value.
+ ///
+ /// @throw NcrMessageError if the element is not an integer
+ /// Element or contains an invalid value.
+ void setChangeType(isc::data::ConstElementPtr element);
+
+ /// @brief Checks forward change flag.
+ ///
+ /// @return a true if the forward change flag is true.
+ bool isForwardChange() const {
+ return (forward_change_);
+ }
+
+ /// @brief Sets the forward change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the forward change
+ /// flag
+ void setForwardChange(const bool value);
+
+ /// @brief Sets the forward change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the forward change flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setForwardChange(isc::data::ConstElementPtr element);
+
+ /// @brief Checks reverse change flag.
+ ///
+ /// @return a true if the reverse change flag is true.
+ bool isReverseChange() const {
+ return (reverse_change_);
+ }
+
+ /// @brief Sets the reverse change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the reverse change
+ /// flag
+ void setReverseChange(const bool value);
+
+ /// @brief Sets the reverse change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the reverse change flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setReverseChange(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request FQDN
+ ///
+ /// @return a string containing the FQDN
+ const std::string getFqdn() const {
+ return (fqdn_);
+ }
+
+ /// @brief Sets the FQDN to the given value.
+ ///
+ /// @param value contains the new value to assign to the FQDN
+ void setFqdn(const std::string& value);
+
+ /// @brief Sets the FQDN to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the FQDN
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setFqdn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request IP address string.
+ ///
+ /// @return a string containing the IP address
+ std::string getIpAddress() const {
+ return (ip_io_address_.toText());
+ }
+
+ /// @brief Fetches the request IP address as an IOAddress.
+ ///
+ /// @return a asiolink::IOAddress containing the IP address
+ const asiolink::IOAddress& getIpIoAddress() const {
+ return (ip_io_address_);
+ }
+
+ /// @brief Returns true if the lease address is a IPv4 lease.
+ ///
+ /// @return boolean true if the lease address family is AF_INET.
+ bool isV4 () const {
+ return (ip_io_address_.isV4());
+ }
+
+ /// @brief Returns true if the lease address is a IPv6 lease.
+ ///
+ /// @return boolean true if the lease address family is AF_INET6.
+ bool isV6 () const {
+ return (ip_io_address_.isV6());
+ }
+
+ /// @brief Sets the IP address to the given value.
+ ///
+ /// @param value contains the new value to assign to the IP address
+ void setIpAddress(const std::string& value);
+
+ /// @brief Sets the IP address to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the IP address
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setIpAddress(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request DHCID
+ ///
+ /// @return a reference to the request's D2Dhcid
+ const D2Dhcid& getDhcid() const {
+ return (dhcid_);
+ }
+
+ /// @brief Sets the DHCID based on the given string value.
+ ///
+ /// @param value is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(const std::string& value);
+
+ /// @brief Sets the DHCID based on the value of the given Element.
+ ///
+ /// @param element is a string Element containing the string of hexadecimal
+ /// digits. (See setDhcid(std::string&) above.)
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request ID.
+ ///
+ /// @todo Currently this is the DHCID, in the future we may add a unique ID per
+ /// request to allow for correlating messages and events between the DHCP servers
+ /// and the D2 server. If we do that we shall also need to add or update other
+ /// functions to: set the request ID, add it to the JSON strings, etc. The
+ /// primary purpose of this function is to provide a consistent way to identify
+ /// requests for logging purposes.
+ ///
+ /// @return a string with the request's request ID (currently DHCID)
+ std::string getRequestId() const {
+ return (dhcid_.toStr());
+ }
+
+ /// @brief Fetches the request lease expiration
+ ///
+ /// @return the lease expiration as the number of seconds since
+ /// the (00:00:00 January 1, 1970)
+ uint64_t getLeaseExpiresOn() const {
+ return (lease_expires_on_);
+ }
+
+ /// @brief Fetches the request lease expiration as string.
+ ///
+ /// The format of the string returned is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @return a ISO date-time string of the lease expiration.
+ std::string getLeaseExpiresOnStr() const;
+
+ /// @brief Sets the lease expiration based on the given string.
+ ///
+ /// @param value is an date-time string from which to set the
+ /// lease expiration. The format of the input is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @throw NcrMessageError if the ISO string is invalid.
+ void setLeaseExpiresOn(const std::string& value);
+
+ /// @brief Sets the lease expiration based on the given Element.
+ ///
+ /// @param element is string Element containing a date-time string.
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element, or if the element value is an invalid date-time string.
+ void setLeaseExpiresOn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease length.
+ ///
+ /// @return an integer containing the lease length
+ uint32_t getLeaseLength() const {
+ return (lease_length_);
+ }
+
+ /// @brief Sets the lease length to the given value.
+ ///
+ /// @param value contains the new value to assign to the lease length
+ void setLeaseLength(const uint32_t value);
+
+ /// @brief Sets the lease length to the value of the given Element.
+ ///
+ /// @param element is a integer Element containing the lease length
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setLeaseLength(isc::data::ConstElementPtr element);
+
+ /// @brief Checks if conflict resolution is enabled
+ ///
+ /// @return a true if the conflict resolution is enabled.
+ bool useConflictResolution() const {
+ return (conflict_resolution_);
+ }
+
+ /// @brief Sets the conflict resolution flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the conflict
+ /// resolution flag
+ void setConflictResolution(const bool value);
+
+ /// @brief Sets the conflict resolution flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the conflict resolution flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setConflictResolution(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request status.
+ ///
+ /// @return the request status as a NameChangeStatus
+ NameChangeStatus getStatus() const {
+ return (status_);
+ }
+
+ /// @brief Sets the request status to the given value.
+ ///
+ /// @param value contains the new value to assign to request status
+ void setStatus(const NameChangeStatus value);
+
+ /// @brief Given a name, finds and returns an element from a map of
+ /// elements.
+ ///
+ /// @param name is the name of the desired element
+ /// @param element_map is the map of elements to search
+ ///
+ /// @return a pointer to the element if located
+ /// @throw NcrMessageError if the element cannot be found within
+ /// the map
+ isc::data::ConstElementPtr getElement(const std::string& name,
+ const ElementMap& element_map) const;
+
+ /// @brief Returns a text rendition of the contents of the request.
+ /// This method is primarily for logging purposes.
+ ///
+ /// @return a string containing the text.
+ std::string toText() const;
+
+ bool operator == (const NameChangeRequest& b) const;
+ bool operator != (const NameChangeRequest& b) const;
+
+private:
+ /// @brief Denotes the type of this change as either an Add or a Remove.
+ NameChangeType change_type_;
+
+ /// @brief Indicates if this change should sent to forward DNS servers.
+ bool forward_change_;
+
+ /// @brief Indicates if this change should sent to reverse DNS servers.
+ bool reverse_change_;
+
+ /// @brief The domain name whose DNS entry(ies) are to be updated.
+ /// @todo Currently, this is a std::string but may be replaced with
+ /// dns::Name which provides additional validation and domain name
+ /// manipulation.
+ std::string fqdn_;
+
+ /// @brief The ip address leased to the FQDN as an IOAddress.
+ ///
+ /// The lease address is used in many places, sometimes as a string
+ /// and sometimes as an IOAddress. To avoid converting back and forth
+ /// continually over the life span of an NCR, we do it once when the
+ /// ip address is actually set.
+ asiolink::IOAddress ip_io_address_;
+
+ /// @brief The lease client's unique DHCID.
+ /// @todo Currently, this is uses D2Dhcid it but may be replaced with
+ /// dns::DHCID which provides additional validation.
+ D2Dhcid dhcid_;
+
+ /// @brief The date-time the lease expires.
+ uint64_t lease_expires_on_;
+
+ /// @brief The amount of time in seconds for which the lease is valid (TTL).
+ uint32_t lease_length_;
+
+ /// @brief Indicates if conflict resolution is enabled.
+ bool conflict_resolution_;
+
+ /// @brief The processing status of the request. Used internally.
+ NameChangeStatus status_;
+};
+
+
+}; // end of isc::dhcp_ddns namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
new file mode 100644
index 0000000..ae37793
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -0,0 +1,386 @@
+// Copyright (C) 2013-2022 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 <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <stats/stats_mgr.h>
+
+#include <functional>
+
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp_ddns {
+
+//*************************** UDPCallback ***********************
+UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source,
+ const UDPCompletionHandler& handler)
+ : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
+ if (!handler) {
+ isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
+ }
+
+ if (!buffer) {
+ isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
+ }
+}
+
+void
+UDPCallback::operator ()(const boost::system::error_code error_code,
+ const size_t bytes_transferred) {
+
+ // Save the result state and number of bytes transferred.
+ setErrorCode(error_code);
+ setBytesTransferred(bytes_transferred);
+
+ // Invoke the NameChangeRequest layer completion handler.
+ // First argument is a boolean indicating success or failure.
+ // The second is a pointer to "this" callback object. By passing
+ // ourself in, we make all of the service related data available
+ // to the completion handler.
+ handler_(!error_code, this);
+}
+
+void
+UDPCallback::putData(const uint8_t* src, size_t len) {
+ if (!src) {
+ isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
+ }
+
+ if (len > data_->buf_size_) {
+ isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
+ }
+
+ memcpy (data_->buffer_.get(), src, len);
+ data_->put_len_ = len;
+}
+
+
+//*************************** NameChangeUDPListener ***********************
+NameChangeUDPListener::
+NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port, const NameChangeFormat format,
+ RequestReceiveHandler& ncr_recv_handler,
+ const bool reuse_address)
+ : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
+ port_(port), format_(format), reuse_address_(reuse_address) {
+ // Instantiate the receive callback. This gets passed into each receive.
+ // Note that the callback constructor is passed an instance method
+ // pointer to our completion handler method, receiveCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ recv_callback_.reset(new UDPCallback(buffer, RECV_BUF_MAX, data_source,
+ std::bind(&NameChangeUDPListener::receiveCompletionHandler,
+ this, ph::_1, ph::_2)));
+}
+
+NameChangeUDPListener::~NameChangeUDPListener() {
+ // Clean up.
+ stopListening();
+}
+
+void
+NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new boost::asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
+ boost::asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low level socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (const boost::system::system_error& ex) {
+ asio_socket_.reset();
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+
+ // Create the asiolink socket from the low level socket.
+ socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+}
+
+
+void
+NameChangeUDPListener::doReceive() {
+ // Call the socket's asynchronous receiving, passing ourself in as callback.
+ RawBufferPtr recv_buffer = recv_callback_->getBuffer();
+ socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
+ 0, recv_callback_->getDataSource().get(),
+ *recv_callback_);
+}
+
+void
+NameChangeUDPListener::close() {
+ // Whether we think we are listening or not, make sure we aren't.
+ // Since we are managing our own socket, we need to close it ourselves.
+ // NOTE that if there is a pending receive, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (const boost::system::system_error& ex) {
+ // It is really unlikely that this will occur.
+ // If we do reopen later it will be with a new socket
+ // instance. Repackage exception as one that is conformant
+ // with the interface.
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+}
+
+void
+NameChangeUDPListener::receiveCompletionHandler(const bool successful,
+ const UDPCallback *callback) {
+ NameChangeRequestPtr ncr;
+ Result result = SUCCESS;
+
+ if (successful) {
+ // Make an InputBuffer from our internal array
+ isc::util::InputBuffer input_buffer(callback->getData(),
+ callback->getBytesTransferred());
+
+ try {
+ ncr = NameChangeRequest::fromFormat(format_, input_buffer);
+ isc::stats::StatsMgr::instance().addValue("ncr-received",
+ static_cast<int64_t>(1));
+ } catch (const NcrMessageError& ex) {
+ // log it and go back to listening
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
+ isc::stats::StatsMgr::instance().addValue("ncr-invalid",
+ static_cast<int64_t>(1));
+
+ // Queue up the next receive.
+ // NOTE: We must call the base class, NEVER doReceive
+ receiveNext();
+ return;
+ }
+ } else {
+ boost::system::error_code error_code = callback->getErrorCode();
+ if (error_code.value() == boost::asio::error::operation_aborted) {
+ // A shutdown cancels all outstanding reads. For this reason,
+ // it can be an expected event, so log it as a debug message.
+ LOG_DEBUG(dhcp_ddns_logger, isc::log::DBGLVL_TRACE_BASIC,
+ DHCP_DDNS_NCR_UDP_RECV_CANCELED);
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+ .arg(error_code.message());
+ isc::stats::StatsMgr::instance().addValue("ncr-error",
+ static_cast<int64_t>(1));
+ result = ERROR;
+ }
+ }
+
+ // Call the application's registered request receive handler.
+ invokeRecvHandler(result, ncr);
+}
+
+
+//*************************** NameChangeUDPSender ***********************
+
+NameChangeUDPSender::
+NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const isc::asiolink::IOAddress& server_address,
+ const uint32_t server_port, const NameChangeFormat format,
+ RequestSendHandler& ncr_send_handler,
+ const size_t send_que_max, const bool reuse_address)
+ : NameChangeSender(ncr_send_handler, send_que_max),
+ ip_address_(ip_address), port_(port), server_address_(server_address),
+ server_port_(server_port), format_(format),
+ reuse_address_(reuse_address) {
+ // Instantiate the send callback. This gets passed into each send.
+ // Note that the callback constructor is passed the an instance method
+ // pointer to our completion handler, sendCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
+ std::bind(&NameChangeUDPSender::sendCompletionHandler,
+ this, ph::_1, ph::_2)));
+}
+
+NameChangeUDPSender::~NameChangeUDPSender() {
+ // Clean up.
+ stopSending();
+}
+
+void
+NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_, port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new boost::asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? boost::asio::ip::udp::v4() :
+ boost::asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(boost::asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low level socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (const boost::system::system_error& ex) {
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+
+ // Create the asiolink socket from the low level socket.
+ socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+
+ // Create the server endpoint
+ server_endpoint_.reset(new isc::asiolink::
+ UDPEndpoint(server_address_, server_port_));
+
+ send_callback_->setDataSource(server_endpoint_);
+
+ closeWatchSocket();
+ watch_socket_.reset(new util::WatchSocket());
+}
+
+void
+NameChangeUDPSender::close() {
+ // Whether we think we are sending or not, make sure we aren't.
+ // Since we are managing our own socket, we need to close it ourselves.
+ // NOTE that if there is a pending send, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (const boost::system::system_error& ex) {
+ // It is really unlikely that this will occur.
+ // If we do reopen later it will be with a new socket
+ // instance. Repackage exception as one that is conformant
+ // with the interface.
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+
+ closeWatchSocket();
+ watch_socket_.reset();
+}
+
+void
+NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
+ // Now use the NCR to write JSON to an output buffer.
+ isc::util::OutputBuffer ncr_buffer(SEND_BUF_MAX);
+ ncr->toFormat(format_, ncr_buffer);
+
+ // Copy the wire-ized request to callback. This way we know after
+ // send completes what we sent (or attempted to send).
+ send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
+ ncr_buffer.getLength());
+
+ // Call the socket's asynchronous send, passing our callback
+ socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
+ send_callback_->getDataSource().get(), *send_callback_);
+
+ // Set IO ready marker so sender activity is visible to select() or poll().
+ // Note, if this call throws it will manifest itself as a throw from
+ // from sendRequest() which the application calls directly and is documented
+ // as throwing exceptions; or caught inside invokeSendHandler() which
+ // will invoke the application's send_handler with an error status.
+ watch_socket_->markReady();
+}
+
+void
+NameChangeUDPSender::sendCompletionHandler(const bool successful,
+ const UDPCallback *send_callback) {
+ // Clear the IO ready marker.
+ try {
+ watch_socket_->clearReady();
+ } catch (const std::exception& ex) {
+ // This can only happen if the WatchSocket's select_fd has been
+ // compromised which is a programmatic error. We'll log the error
+ // here, then continue on and process the IO result we were given.
+ // WatchSocket issue will resurface on the next send as a closed
+ // fd in markReady(). This allows application's handler to deal
+ // with watch errors more uniformly.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_CLEAR_READY_ERROR)
+ .arg(ex.what());
+ }
+
+ Result result;
+ if (successful) {
+ result = SUCCESS;
+ } else {
+ // On a failure, log the error and set the result to ERROR.
+ boost::system::error_code error_code = send_callback->getErrorCode();
+ if (error_code.value() == boost::asio::error::operation_aborted) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_CANCELED)
+ .arg(error_code.message());
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_ERROR)
+ .arg(error_code.message());
+ result = ERROR;
+ }
+ }
+
+ // Call the application's registered request send handler.
+ invokeSendHandler(result);
+}
+
+int
+NameChangeUDPSender::getSelectFd() {
+ if (!amSending()) {
+ isc_throw(NotImplemented, "NameChangeUDPSender::getSelectFd"
+ " not in send mode");
+ }
+
+ return(watch_socket_->getSelectFd());
+}
+
+bool
+NameChangeUDPSender::ioReady() {
+ if (watch_socket_) {
+ return (watch_socket_->isReady());
+ }
+
+ return (false);
+}
+
+void
+NameChangeUDPSender::closeWatchSocket() {
+ if (watch_socket_) {
+ std::string error_string;
+ watch_socket_->closeSocket(error_string);
+ if (!error_string.empty()) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UDP_SENDER_WATCH_SOCKET_CLOSE_ERROR)
+ .arg(error_string);
+ }
+ }
+}
+
+} // end of isc::dhcp_ddns namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_udp.h b/src/lib/dhcp_ddns/ncr_udp.h
new file mode 100644
index 0000000..01284af
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.h
@@ -0,0 +1,588 @@
+// Copyright (C) 2013-2020 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 NCR_UDP_LISTENER_H
+#define NCR_UDP_LISTENER_H
+
+/// @file ncr_udp.h
+/// @brief This file provides UDP socket based implementation for sending and
+/// receiving NameChangeRequests
+///
+/// These classes are derived from the abstract classes, NameChangeListener
+/// and NameChangeSender (see ncr_io.h).
+///
+/// The following discussion will refer to three layers of communications:
+///
+/// * Application layer - This is the business layer which needs to
+/// transport NameChangeRequests, and is unaware of the means by which
+/// they are transported.
+///
+/// * IO layer - This is the low-level layer that is directly responsible
+/// for sending and receiving data asynchronously and is supplied through
+/// other libraries. This layer is largely unaware of the nature of the
+/// data being transmitted. In other words, it doesn't know beans about
+/// NCRs.
+///
+/// * NameChangeRequest layer - This is the layer which acts as the
+/// intermediary between the Application layer and the IO layer. It must
+/// be able to move NameChangeRequests to the IO layer as raw data and move
+/// raw data from the IO layer in the Application layer as
+/// NameChangeRequests.
+///
+/// This file defines NameChangeUDPListener class for receiving NCRs, and
+/// NameChangeUDPSender for sending NCRs.
+///
+/// Both the listener and sender implementations utilize the same underlying
+/// construct to move NCRs to and from a UDP socket. This construct consists
+/// of a set of classes centered around isc::asiolink::UDPSocket. UDPSocket
+/// is a templated class that supports asio asynchronous event processing; and
+/// which accepts as its parameter, the name of a callback class.
+///
+/// The asynchronous services provided by UDPSocket typically accept a buffer
+/// for transferring data (either in or out depending on the service direction)
+/// and an object which supplies a callback to invoke upon completion of the
+/// service.
+///
+/// The callback class must provide an operator() with the following signature:
+/// @code
+/// void operator ()(const boost::system::error_code error_code,
+/// const size_t bytes_transferred);
+/// @endcode
+///
+/// Upon completion of the service, the callback instance's operator() is
+/// invoked by the asio layer. It is given both a outcome result and the
+/// number of bytes either read or written, to or from the buffer supplied
+/// to the service.
+///
+/// Typically, an asiolink based implementation would simply implement the
+/// callback operator directly. However, the nature of the asiolink library
+/// is such that the callback object may be copied several times during course
+/// of a service invocation. This implies that any class being used as a
+/// callback class must be copyable. This is not always desirable. In order
+/// to separate the callback class from the NameChangeRequest, the construct
+/// defines the UDPCallback class for use as a copyable, callback object.
+///
+/// The UDPCallback class provides the asiolink layer callback operator(),
+/// which is invoked by the asiolink layer upon service completion. It
+/// contains:
+/// * a pointer to the transfer buffer
+/// * the capacity of the transfer buffer
+/// * a IO layer outcome result
+/// * the number of bytes transferred
+/// * a method pointer to a NameChangeRequest layer completion handler
+///
+/// This last item, is critical. It points to an instance method that
+/// will be invoked by the UDPCallback operator. This provides access to
+/// the outcome of the service call to the NameChangeRequest layer without
+/// that layer being used as the actual callback object.
+///
+/// The completion handler method signature is codified in the typedef,
+/// UDPCompletionHandler, and must be as follows:
+///
+/// @code
+/// void(const bool, const UDPCallback*)
+/// @endcode
+///
+/// Note that is accepts two parameters. The first is a boolean indicator
+/// which indicates if the service call completed successfully or not. The
+/// second is a pointer to the callback object invoked by the IOService upon
+/// completion of the service. The callback instance will contain all of the
+/// pertinent information about the invocation and outcome of the service.
+///
+/// Using the contents of the callback, it is the responsibility of the
+/// UDPCompletionHandler to interpret the results of the service invocation and
+/// pass the interpretation to the application layer via either
+/// NameChangeListener::invokeRecvHandler in the case of the UDP listener, or
+/// NameChangeSender::invokeSendHandler in the case of UDP sender.
+///
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <util/buffer.h>
+#include <util/watch_socket.h>
+
+#include <boost/shared_array.hpp>
+
+
+/// responsibility of the completion handler to perform the steps necessary
+/// to interpret the raw data provided by the service outcome. The
+/// UDPCallback operator implementation is mostly a pass through.
+///
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Thrown when a UDP level exception occurs.
+class NcrUDPError : public isc::Exception {
+public:
+ NcrUDPError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class UDPCallback;
+/// @brief Defines a function pointer for NameChangeRequest completion handlers.
+typedef std::function<void(const bool, const UDPCallback*)>
+ UDPCompletionHandler;
+
+/// @brief Defines a dynamically allocated shared array.
+typedef boost::shared_array<uint8_t> RawBufferPtr;
+
+typedef boost::shared_ptr<asiolink::UDPEndpoint> UDPEndpointPtr;
+
+/// @brief Implements the callback class passed into UDPSocket calls.
+///
+/// It serves as the link between the asiolink::UDPSocket asynchronous services
+/// and the NameChangeRequest layer. The class provides the asiolink layer
+/// callback operator(), which is invoked by the asiolink layer upon service
+/// completion. It contains all of the data pertinent to both the invocation
+/// and completion of a service, as well as a pointer to NameChangeRequest
+/// layer completion handler to invoke.
+///
+class UDPCallback {
+
+public:
+ /// @brief Container class which stores service invocation related data.
+ ///
+ /// Because the callback object may be copied numerous times during the
+ /// course of service invocation, it does not directly contain data values.
+ /// Rather it will retain a shared pointer to an instance of this structure
+ /// thus ensuring that all copies of the callback object, ultimately refer
+ /// to the same data values.
+ struct Data {
+
+ /// @brief Constructor
+ ///
+ /// @param buffer is a pointer to the data transfer buffer. This is
+ /// the buffer data will be written to on a read, or read from on a
+ /// send.
+ /// @param buf_size is the capacity of the buffer
+ /// @param data_source storage for UDP endpoint which supplied the data
+ Data(RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source)
+ : buffer_(buffer), buf_size_(buf_size), data_source_(data_source),
+ put_len_(0), error_code_(), bytes_transferred_(0) {
+ };
+
+ /// @brief A pointer to the data transfer buffer.
+ RawBufferPtr buffer_;
+
+ /// @brief Storage capacity of the buffer.
+ size_t buf_size_;
+
+ /// @brief The UDP endpoint that is the origin of the data transferred.
+ UDPEndpointPtr data_source_;
+
+ /// @brief Stores this size of the data within the buffer when written
+ /// there manually. (See UDPCallback::putData()) .
+ size_t put_len_;
+
+ /// @brief Stores the IO layer result code of the completed IO service.
+ boost::system::error_code error_code_;
+
+ /// @brief Stores the number of bytes transferred by completed IO
+ /// service.
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ size_t bytes_transferred_;
+
+ };
+
+ /// @brief Used as the callback object for UDPSocket services.
+ ///
+ /// @param buffer is a pointer to the data transfer buffer. This is
+ /// the buffer data will be written to on a read, or read from on a
+ /// send.
+ /// @param buf_size is the capacity of the buffer
+ /// @param data_source storage for UDP endpoint which supplied the data
+ /// @param handler is a method pointer to the completion handler that
+ /// is to be called by the operator() implementation.
+ ///
+ /// @throw NcrUDPError if either the handler or buffer pointers
+ /// are invalid.
+ UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source,
+ const UDPCompletionHandler& handler);
+
+ /// @brief Operator that will be invoked by the asiolink layer.
+ ///
+ /// @param error_code is the IO layer result code of the
+ /// completed IO service.
+ /// @param bytes_transferred is the number of bytes transferred by
+ /// completed IO.
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ void operator ()(const boost::system::error_code error_code,
+ const size_t bytes_transferred);
+
+ /// @brief Returns the number of bytes transferred by the completed IO
+ /// service.
+ ///
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ size_t getBytesTransferred() const {
+ return (data_->bytes_transferred_);
+ }
+
+ /// @brief Sets the number of bytes transferred.
+ ///
+ /// @param value is the new value to assign to bytes transferred.
+ void setBytesTransferred(const size_t value) {
+ data_->bytes_transferred_ = value;
+ }
+
+ /// @brief Returns the completed IO layer service outcome status.
+ boost::system::error_code getErrorCode() const {
+ return (data_->error_code_);
+ }
+
+ /// @brief Sets the completed IO layer service outcome status.
+ ///
+ /// @param value is the new value to assign to outcome status.
+ void setErrorCode(const boost::system::error_code value) {
+ data_->error_code_ = value;
+ }
+
+ /// @brief Returns the data transfer buffer.
+ RawBufferPtr getBuffer() const {
+ return (data_->buffer_);
+ }
+
+ /// @brief Returns the data transfer buffer capacity.
+ size_t getBufferSize() const {
+ return (data_->buf_size_);
+ }
+
+ /// @brief Returns a pointer the data transfer buffer content.
+ const uint8_t* getData() const {
+ return (data_->buffer_.get());
+ }
+
+ /// @brief Copies data into the data transfer buffer.
+ ///
+ /// Copies the given number of bytes from the given source buffer
+ /// into the data transfer buffer, and updates the value of put length.
+ /// This method may be used when performing sends to make a copy of
+ /// the "raw data" that was shipped (or attempted) accessible to the
+ /// upstream callback.
+ ///
+ /// @param src is a pointer to the data source from which to copy
+ /// @param len is the number of bytes to copy
+ ///
+ /// @throw NcrUDPError if the number of bytes to copy exceeds
+ /// the buffer capacity or if the source pointer is invalid.
+ void putData(const uint8_t* src, size_t len);
+
+ /// @brief Returns the number of bytes manually written into the
+ /// transfer buffer.
+ size_t getPutLen() const {
+ return (data_->put_len_);
+ }
+
+ /// @brief Sets the data source to the given endpoint.
+ ///
+ /// @param endpoint is the new value to assign to data source.
+ void setDataSource(UDPEndpointPtr& endpoint) {
+ data_->data_source_ = endpoint;
+ }
+
+ /// @brief Returns the UDP endpoint that provided the transferred data.
+ const UDPEndpointPtr& getDataSource() {
+ return (data_->data_source_);
+ }
+
+ private:
+ /// @brief NameChangeRequest layer completion handler to invoke.
+ UDPCompletionHandler handler_;
+
+ /// @brief Shared pointer to the service data container.
+ boost::shared_ptr<Data> data_;
+};
+
+/// @brief Convenience type for UDP socket based listener
+typedef isc::asiolink::UDPSocket<UDPCallback> NameChangeUDPSocket;
+
+/// @brief Provides the ability to receive NameChangeRequests via UDP socket
+///
+/// This class is a derivation of the NameChangeListener which is capable of
+/// receiving NameChangeRequests through a UDP socket. The caller need only
+/// supply network addressing and a RequestReceiveHandler instance to receive
+/// NameChangeRequests asynchronously.
+class NameChangeUDPListener : public NameChangeListener {
+public:
+ /// @brief Defines the maximum size packet that can be received.
+ static const size_t RECV_BUF_MAX = isc::asiolink::
+ UDPSocket<UDPCallback>::MIN_SIZE;
+
+ /// @brief Constructor
+ ///
+ /// @param ip_address is the network address on which to listen
+ /// @param port is the UDP port on which to listen
+ /// @param format is the wire format of the inbound requests. Currently
+ /// only JSON is supported
+ /// @param ncr_recv_handler the receive handler object to notify when
+ /// a receive completes.
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ ///
+ /// @throw base class throws NcrListenerError if handler is invalid.
+ NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const NameChangeFormat format,
+ RequestReceiveHandler& ncr_recv_handler,
+ const bool reuse_address = false);
+
+ /// @brief Destructor.
+ virtual ~NameChangeUDPListener();
+
+ /// @brief Opens a UDP socket using the given IOService.
+ ///
+ /// Creates a NameChangeUDPSocket bound to the listener's ip address
+ /// and port, that is monitored by the given IOService instance.
+ ///
+ /// @param io_service the IOService which will monitor the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void open(isc::asiolink::IOService& io_service);
+
+ /// @brief Closes the UDPSocket.
+ ///
+ /// It first invokes the socket's cancel method which should stop any
+ /// pending read and remove the socket callback from the IOService. It
+ /// then calls the socket's close method to actually close the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void close();
+
+ /// @brief Initiates an asynchronous read on the socket.
+ ///
+ /// Invokes the asyncReceive() method on the socket passing in the
+ /// recv_callback_ member's transfer buffer as the receive buffer, and
+ /// recv_callback_ itself as the callback object.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ void doReceive();
+
+ /// @brief Implements the NameChangeRequest level receive completion
+ /// handler.
+ ///
+ /// This method is invoked by the UPDCallback operator() implementation,
+ /// passing in the boolean success indicator and pointer to itself.
+ ///
+ /// If the indicator denotes success, then the method will attempt to
+ /// to construct a NameChangeRequest from the received data. If the
+ /// construction was successful, it will send the new NCR to the
+ /// application layer by calling invokeRecvHandler() with a success
+ /// status and a pointer to the new NCR.
+ ///
+ /// If the buffer contains invalid data such that construction fails,
+ /// the method will log the failure and then call doReceive() to start a
+ /// initiate the next receive.
+ ///
+ /// If the indicator denotes failure the method will log the failure and
+ /// notify the application layer by calling invokeRecvHandler() with
+ /// an error status and an empty pointer.
+ ///
+ /// @param successful boolean indicator that should be true if the
+ /// socket receive completed without error, false otherwise.
+ /// @param recv_callback pointer to the callback instance which handled
+ /// the socket receive completion.
+ void receiveCompletionHandler(const bool successful,
+ const UDPCallback* recv_callback);
+private:
+ /// @brief IP address on which to listen for requests.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief Port number on which to listen for requests.
+ uint32_t port_;
+
+ /// @brief Wire format of the inbound requests.
+ NameChangeFormat format_;
+
+ /// @brief Low level socket underneath the listening socket
+ boost::shared_ptr<boost::asio::ip::udp::socket> asio_socket_;
+
+ /// @brief NameChangeUDPSocket listening socket
+ boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+ /// @brief Pointer to the receive callback
+ boost::shared_ptr<UDPCallback> recv_callback_;
+
+ /// @brief Flag which enables the reuse address socket option if true.
+ bool reuse_address_;
+
+ ///
+ /// @name Copy and constructor assignment operator
+ ///
+ /// The copy constructor and assignment operator are private to avoid
+ /// potential issues with multiple listeners attempting to share sockets
+ /// and callbacks.
+private:
+ NameChangeUDPListener(const NameChangeUDPListener& source);
+ NameChangeUDPListener& operator=(const NameChangeUDPListener& source);
+ //@}
+};
+
+
+/// @brief Provides the ability to send NameChangeRequests via UDP socket
+///
+/// This class is a derivation of the NameChangeSender which is capable of
+/// sending NameChangeRequests through a UDP socket. The caller need only
+/// supply network addressing and a RequestSendHandler instance to send
+/// NameChangeRequests asynchronously.
+class NameChangeUDPSender : public NameChangeSender {
+public:
+
+ /// @brief Defines the maximum size packet that can be sent.
+ static const size_t SEND_BUF_MAX = NameChangeUDPListener::RECV_BUF_MAX;
+
+ /// @brief Constructor
+ ///
+ /// @param ip_address the IP address from which to send
+ /// @param port the port from which to send
+ /// @param server_address the IP address of the target listener
+ /// @param server_port is the IP port of the target listener
+ /// @param format is the wire format of the outbound requests.
+ /// @param ncr_send_handler the send handler object to notify when
+ /// when a send completes.
+ /// @param send_que_max sets the maximum number of entries allowed in
+ /// the send queue.
+ /// It defaults to NameChangeSender::MAX_QUEUE_DEFAULT
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ ///
+ NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port, const isc::asiolink::IOAddress& server_address,
+ const uint32_t server_port, const NameChangeFormat format,
+ RequestSendHandler& ncr_send_handler,
+ const size_t send_que_max = NameChangeSender::MAX_QUEUE_DEFAULT,
+ const bool reuse_address = false);
+
+ /// @brief Destructor
+ virtual ~NameChangeUDPSender();
+
+
+ /// @brief Opens a UDP socket using the given IOService.
+ ///
+ /// Creates a NameChangeUDPSocket bound to the sender's IP address
+ /// and port, that is monitored by the given IOService instance.
+ ///
+ /// @param io_service the IOService which will monitor the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void open(isc::asiolink::IOService& io_service);
+
+
+ /// @brief Closes the UDPSocket.
+ ///
+ /// It first invokes the socket's cancel method which should stop any
+ /// pending send and remove the socket callback from the IOService. It
+ /// then calls the socket's close method to actually close the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void close();
+
+ /// @brief Sends a given request asynchronously over the socket
+ ///
+ /// The given NameChangeRequest is converted to wire format and copied
+ /// into the send callback's transfer buffer. Then the socket's
+ /// asyncSend() method is called, passing in send_callback_ member's
+ /// transfer buffer as the send buffer and the send_callback_ itself
+ /// as the callback object.
+ /// @param ncr NameChangeRequest to send.
+ virtual void doSend(NameChangeRequestPtr& ncr);
+
+ /// @brief Implements the NameChangeRequest level send completion handler.
+ ///
+ /// This method is invoked by the UDPCallback operator() implementation,
+ /// passing in the boolean success indicator and pointer to itself.
+ ///
+ /// If the indicator denotes success, then the method will notify the
+ /// application layer by calling invokeSendHandler() with a success
+ /// status.
+ ///
+ /// If the indicator denotes failure the method will log the failure and
+ /// notify the application layer by calling invokeRecvHandler() with
+ /// an error status.
+ ///
+ /// @param successful boolean indicator that should be true if the
+ /// socket send completed without error, false otherwise.
+ /// @param send_callback pointer to the callback instance which handled
+ /// the socket receive completion.
+ void sendCompletionHandler(const bool successful,
+ const UDPCallback* send_callback);
+
+ /// @brief Returns a file descriptor suitable for use with select
+ ///
+ /// The value returned is an open file descriptor which can be used with
+ /// select() system call to monitor the sender for IO events. This allows
+ /// NameChangeUDPSenders to be used in applications which use select,
+ /// rather than IOService to wait for IO events to occur.
+ ///
+ /// @warning Attempting other use of this value may lead to unpredictable
+ /// behavior in the sender.
+ ///
+ /// @return Returns an "open" file descriptor
+ ///
+ /// @throw NcrSenderError if the sender is not in send mode,
+ virtual int getSelectFd();
+
+ /// @brief Returns whether or not the sender has IO ready to process.
+ ///
+ /// @return true if the sender has at IO ready, false otherwise.
+ virtual bool ioReady();
+
+private:
+
+ /// @brief Closes watch socket if the socket is open.
+ ///
+ /// This method closes watch socket if its instance exists and if the
+ /// socket is open. An error message is logged when this operation fails.
+ void closeWatchSocket();
+
+ /// @brief IP address from which to send.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief Port from which to send.
+ uint32_t port_;
+
+ /// @brief IP address of the target listener.
+ isc::asiolink::IOAddress server_address_;
+
+ /// @brief Port of the target listener.
+ uint32_t server_port_;
+
+ /// @brief Wire format of the outbound requests.
+ NameChangeFormat format_;
+
+ /// @brief Low level socket underneath the sending socket.
+ boost::shared_ptr<boost::asio::ip::udp::socket> asio_socket_;
+
+ /// @brief NameChangeUDPSocket sending socket.
+ boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+ /// @brief Endpoint of the target listener.
+ boost::shared_ptr<isc::asiolink::UDPEndpoint> server_endpoint_;
+
+ /// @brief Pointer to the send callback
+ boost::shared_ptr<UDPCallback> send_callback_;
+
+ /// @brief Flag which enables the reuse address socket option if true.
+ bool reuse_address_;
+
+ /// @brief Pointer to WatchSocket instance supplying the "select-fd".
+ util::WatchSocketPtr watch_socket_;
+};
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/tests/Makefile.am b/src/lib/dhcp_ddns/tests/Makefile.am
new file mode 100644
index 0000000..68ae6ba
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/Makefile.am
@@ -0,0 +1,48 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+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 += libdhcp_ddns_unittests
+
+libdhcp_ddns_unittests_SOURCES = run_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
+libdhcp_ddns_unittests_SOURCES += test_utils.cc test_utils.h
+
+libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libdhcp_ddns_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+libdhcp_ddns_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+libdhcp_ddns_unittests_LDADD = $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcp_ddns_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+libdhcp_ddns_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dhcp_ddns/tests/Makefile.in b/src/lib/dhcp_ddns/tests/Makefile.in
new file mode 100644
index 0000000..441af44
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/Makefile.in
@@ -0,0 +1,1075 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libdhcp_ddns_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/dhcp_ddns/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libdhcp_ddns_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libdhcp_ddns_unittests_SOURCES_DIST = run_unittests.cc \
+ ncr_unittests.cc ncr_udp_unittests.cc test_utils.cc \
+ test_utils.h
+@HAVE_GTEST_TRUE@am_libdhcp_ddns_unittests_OBJECTS = libdhcp_ddns_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp_ddns_unittests-ncr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp_ddns_unittests-ncr_udp_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcp_ddns_unittests-test_utils.$(OBJEXT)
+libdhcp_ddns_unittests_OBJECTS = $(am_libdhcp_ddns_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_DEPENDENCIES = $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) \
+@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 =
+libdhcp_ddns_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libdhcp_ddns_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)/libdhcp_ddns_unittests-ncr_udp_unittests.Po \
+ ./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po \
+ ./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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 = $(libdhcp_ddns_unittests_SOURCES)
+DIST_SOURCES = $(am__libdhcp_ddns_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ ncr_unittests.cc ncr_udp_unittests.cc \
+@HAVE_GTEST_TRUE@ test_utils.cc test_utils.h
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libdhcp_ddns_unittests_LDADD = $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_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/dhcp_ddns/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcp_ddns/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
+
+libdhcp_ddns_unittests$(EXEEXT): $(libdhcp_ddns_unittests_OBJECTS) $(libdhcp_ddns_unittests_DEPENDENCIES) $(EXTRA_libdhcp_ddns_unittests_DEPENDENCIES)
+ @rm -f libdhcp_ddns_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libdhcp_ddns_unittests_LINK) $(libdhcp_ddns_unittests_OBJECTS) $(libdhcp_ddns_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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 $@ $<
+
+libdhcp_ddns_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Tpo -c -o libdhcp_ddns_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp_ddns_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libdhcp_ddns_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Tpo -c -o libdhcp_ddns_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)/libdhcp_ddns_unittests-run_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcp_ddns_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libdhcp_ddns_unittests-ncr_unittests.o: ncr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_unittests.o `test -f 'ncr_unittests.cc' || echo '$(srcdir)/'`ncr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_unittests.cc' object='libdhcp_ddns_unittests-ncr_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_unittests.o `test -f 'ncr_unittests.cc' || echo '$(srcdir)/'`ncr_unittests.cc
+
+libdhcp_ddns_unittests-ncr_unittests.obj: ncr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_unittests.obj `if test -f 'ncr_unittests.cc'; then $(CYGPATH_W) 'ncr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_unittests.cc' object='libdhcp_ddns_unittests-ncr_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_unittests.obj `if test -f 'ncr_unittests.cc'; then $(CYGPATH_W) 'ncr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_unittests.cc'; fi`
+
+libdhcp_ddns_unittests-ncr_udp_unittests.o: ncr_udp_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_udp_unittests.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_udp_unittests.o `test -f 'ncr_udp_unittests.cc' || echo '$(srcdir)/'`ncr_udp_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_udp_unittests.cc' object='libdhcp_ddns_unittests-ncr_udp_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_udp_unittests.o `test -f 'ncr_udp_unittests.cc' || echo '$(srcdir)/'`ncr_udp_unittests.cc
+
+libdhcp_ddns_unittests-ncr_udp_unittests.obj: ncr_udp_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-ncr_udp_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo -c -o libdhcp_ddns_unittests-ncr_udp_unittests.obj `if test -f 'ncr_udp_unittests.cc'; then $(CYGPATH_W) 'ncr_udp_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_udp_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Tpo $(DEPDIR)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_udp_unittests.cc' object='libdhcp_ddns_unittests-ncr_udp_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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-ncr_udp_unittests.obj `if test -f 'ncr_udp_unittests.cc'; then $(CYGPATH_W) 'ncr_udp_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_udp_unittests.cc'; fi`
+
+libdhcp_ddns_unittests-test_utils.o: test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-test_utils.o -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo -c -o libdhcp_ddns_unittests-test_utils.o `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_utils.cc' object='libdhcp_ddns_unittests-test_utils.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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-test_utils.o `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+
+libdhcp_ddns_unittests-test_utils.obj: test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcp_ddns_unittests-test_utils.obj -MD -MP -MF $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo -c -o libdhcp_ddns_unittests-test_utils.obj `if test -f 'test_utils.cc'; then $(CYGPATH_W) 'test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/test_utils.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Tpo $(DEPDIR)/libdhcp_ddns_unittests-test_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_utils.cc' object='libdhcp_ddns_unittests-test_utils.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) $(libdhcp_ddns_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcp_ddns_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcp_ddns_unittests-test_utils.obj `if test -f 'test_utils.cc'; then $(CYGPATH_W) 'test_utils.cc'; else $(CYGPATH_W) '$(srcdir)/test_utils.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)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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)/libdhcp_ddns_unittests-ncr_udp_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-ncr_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcp_ddns_unittests-test_utils.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/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
new file mode 100644
index 0000000..b63ed8c
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
@@ -0,0 +1,1428 @@
+// Copyright (C) 2013-2021 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 <asiolink/interval_timer.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/multi_threading_mgr.h>
+#include <util/time_utilities.h>
+#include <test_utils.h>
+
+#include <boost/asio/ip/udp.hpp>
+
+#include <functional>
+#include <algorithm>
+
+#include <sys/select.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": false"
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true"
+ "}"
+};
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleListenHandler : public NameChangeListener::RequestReceiveHandler {
+public:
+ virtual void operator ()(const NameChangeListener::Result,
+ NameChangeRequestPtr&) {
+ }
+};
+
+/// @brief Tests the NameChangeUDPListener constructors.
+/// This test verifies that:
+/// 1. Given valid parameters, the listener constructor works
+TEST(NameChangeUDPListenerBasicTest, constructionTests) {
+ // Verify the default constructor works.
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = LISTENER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleListenHandler ncr_handler;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON,
+ ncr_handler));
+}
+
+/// @brief Tests NameChangeUDPListener starting and stopping listening .
+/// This test verifies that the listener will:
+/// 1. Enter listening state
+/// 2. If in the listening state, does not allow calls to start listening
+/// 3. Exist the listening state
+/// 4. Return to the listening state after stopping
+TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
+ // Verify the default constructor works.
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = LISTENER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleListenHandler ncr_handler;
+
+ NameChangeListenerPtr listener;
+ ASSERT_NO_THROW(listener.reset(
+ new NameChangeUDPListener(ip_address, port, FMT_JSON, ncr_handler)));
+
+ // Verify that we can start listening.
+ EXPECT_NO_THROW(listener->startListening(io_service));
+
+ // Verify that we are in listening mode.
+ EXPECT_TRUE(listener->amListening());
+ // Verify that a read is in progress.
+ EXPECT_TRUE(listener->isIoPending());
+
+ // Verify that attempting to listen when we already are is an error.
+ EXPECT_THROW(listener->startListening(io_service), NcrListenerError);
+
+ // Verify that we can stop listening.
+ EXPECT_NO_THROW(listener->stopListening());
+ EXPECT_FALSE(listener->amListening());
+
+ // Verify that IO pending is still true, as IO cancel event has not yet
+ // occurred.
+ EXPECT_TRUE(listener->isIoPending());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service.run_one());
+ EXPECT_FALSE(listener->isIoPending());
+
+ // Verify that attempting to stop listening when we are not is ok.
+ EXPECT_NO_THROW(listener->stopListening());
+
+ // Verify that we can re-enter listening.
+ EXPECT_NO_THROW(listener->startListening(io_service));
+ EXPECT_TRUE(listener->amListening());
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+ NameChangeRequestPtr received_ncr) {
+ return ((sent_ncr && received_ncr) &&
+ (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPListenerTest : public virtual ::testing::Test,
+ NameChangeListener::RequestReceiveHandler {
+public:
+ isc::asiolink::IOService io_service_;
+ NameChangeListener::Result result_;
+ NameChangeRequestPtr sent_ncr_;
+ NameChangeRequestPtr received_ncr_;
+ NameChangeListenerPtr listener_;
+ isc::asiolink::IntervalTimer test_timer_;
+
+ /// @brief Constructor
+ //
+ // Instantiates the listener member and the test timer. The timer is used
+ // to ensure a test doesn't go awry and hang forever.
+ NameChangeUDPListenerTest()
+ : io_service_(), result_(NameChangeListener::SUCCESS),
+ test_timer_(io_service_) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ listener_.reset(new NameChangeUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, *this, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&NameChangeUDPListenerTest::
+ testTimeoutHandler, this),
+ TEST_TIMEOUT);
+ }
+
+ virtual ~NameChangeUDPListenerTest(){
+ }
+
+
+ /// @brief Converts JSON string into an NCR and sends it to the listener.
+ ///
+ void sendNcr(const std::string& msg) {
+ // Build an NCR from json string. This verifies that the
+ // test string is valid.
+ ASSERT_NO_THROW(sent_ncr_ = NameChangeRequest::fromJSON(msg));
+
+ // Now use the NCR to write JSON to an output buffer.
+ isc::util::OutputBuffer ncr_buffer(1024);
+ ASSERT_NO_THROW(sent_ncr_->toFormat(FMT_JSON, ncr_buffer));
+
+ // Create a UDP socket through which our "sender" will send the NCR.
+ boost::asio::ip::udp::socket
+ udp_socket(io_service_.get_io_service(), boost::asio::ip::udp::v4());
+
+ // Create an endpoint pointed at the listener.
+ boost::asio::ip::udp::endpoint
+ listener_endpoint(boost::asio::ip::address::from_string(TEST_ADDRESS),
+ LISTENER_PORT);
+
+ // A response message is now ready to send. Send it!
+ // Note this uses a synchronous send so it ships immediately.
+ // If listener isn't in listening mode, it will get missed.
+ udp_socket.send_to(boost::asio::buffer(ncr_buffer.getData(),
+ ncr_buffer.getLength()),
+ listener_endpoint);
+ }
+
+ /// @brief RequestReceiveHandler operator implementation for receiving NCRs.
+ ///
+ /// The fixture acts as the "application" layer. It derives from
+ /// RequestReceiveHandler and as such implements operator() in order to
+ /// receive NCRs.
+ virtual void operator ()(const NameChangeListener::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR we received
+ result_ = result;
+ received_ncr_ = ncr;
+ }
+
+ /// @brief Handler invoked when test timeout is hit
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+};
+
+/// @brief Tests NameChangeUDPListener ability to receive NCRs.
+/// This test verifies that a listener can enter listening mode and
+/// receive NCRs in wire format on its UDP socket; reconstruct the
+/// NCRs and delivery them to the "application" layer.
+TEST_F(NameChangeUDPListenerTest, basicReceiveTests) {
+ // Verify we can enter listening mode.
+ ASSERT_FALSE(listener_->amListening());
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ ASSERT_TRUE(listener_->amListening());
+ ASSERT_TRUE(listener_->isIoPending());
+
+ // Iterate over a series of requests, sending and receiving one
+ /// at time.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ // We are not verifying ability to send, so if we can't test is over.
+ ASSERT_NO_THROW(sendNcr(valid_msgs[i]));
+
+ // Execute no more then one event, which should be receive complete.
+ EXPECT_NO_THROW(io_service_.run_one());
+
+ // Verify the "application" status value for a successful complete.
+ EXPECT_EQ(NameChangeListener::SUCCESS, result_);
+
+ // Verify the received request matches the sent request.
+ EXPECT_TRUE(checkSendVsReceived(sent_ncr_, received_ncr_));
+ }
+
+ // Verify we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+}
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
+public:
+ SimpleSendHandler() : pass_count_(0), error_count_(0) {
+ }
+
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr&) {
+ if (result == NameChangeSender::SUCCESS) {
+ ++pass_count_;
+ } else {
+ ++error_count_;
+ }
+ }
+
+ int pass_count_;
+ int error_count_;
+};
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPSenderBasicTest : public virtual ::testing::Test {
+public:
+ NameChangeUDPSenderBasicTest() {
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ ~NameChangeUDPSenderBasicTest() {
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+};
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST_F(NameChangeUDPSenderBasicTest, constructionTests) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Verify that constructing with an queue size of zero is not allowed.
+ EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+ ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+ NameChangeSenderPtr sender;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler)));
+
+ // Verify that send queue default max is correct.
+ size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+ EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+ // Verify that constructor with a valid custom queue size works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, 100)));
+
+ EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST_F(NameChangeUDPSenderBasicTest, constructionTestsMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Verify that constructing with an queue size of zero is not allowed.
+ EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+ ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+ NameChangeSenderPtr sender;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler)));
+
+ // Verify that send queue default max is correct.
+ size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+ EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+ // Verify that constructor with a valid custom queue size works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, 100)));
+
+ EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+TEST_F(NameChangeUDPSenderBasicTest, basicSendTests) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Verify that attempting to send when we already are is an error.
+ EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+ // Verify that we can stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that attempting to stop sending when we are not is ok.
+ EXPECT_NO_THROW(sender.stopSending());
+
+ // Verify that we can re-enter sending after stopping.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify select_fd is valid and currently shows no ready to read.
+ ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd);
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Iterate over a series of messages, sending each one. Since we
+ // do not invoke IOService::run, then the messages should accumulate
+ // in the queue.
+ NameChangeRequestPtr ncr;
+ NameChangeRequestPtr ncr2;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ // Verify that the queue count increments in step with each send.
+ EXPECT_EQ(i+1, sender.getQueueSize());
+
+ // Verify that peekAt(i) returns the NCR we just added.
+ ASSERT_NO_THROW(ncr2 = sender.peekAt(i));
+ ASSERT_TRUE(ncr2);
+ EXPECT_TRUE(*ncr == *ncr2);
+ }
+
+ // Verify that attempting to peek beyond the end of the queue, throws.
+ ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError);
+
+ // Verify that attempting to send an additional message results in a
+ // queue full exception.
+ EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+ // Loop for the number of valid messages. So long as there is at least
+ // on NCR in the queue, select-fd indicate ready to read. Invoke
+ // IOService::run_one. This should complete the send of exactly one
+ // message and the queue count should decrement accordingly.
+ for (int i = num_msgs; i > 0; i--) {
+ // Make sure select_fd does evaluates to ready via select and
+ // that ioReady() method agrees.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that the queue count decrements in step with each run.
+ EXPECT_EQ(i-1, sender.getQueueSize());
+ }
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that we can add back to the queue
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify that we can remove the current entry at the front of the queue.
+ EXPECT_NO_THROW(sender.skipNext());
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that flushing the queue is not allowed in sending state.
+ EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+ // Put num_msgs messages on the queue.
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+
+ // Make sure we have number of messages expected.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that the queue is preserved after leaving sending state.
+ EXPECT_EQ(num_msgs - 1, sender.getQueueSize());
+
+ // Verify that flushing the queue works when not sending.
+ EXPECT_NO_THROW(sender.clearSendQueue());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+TEST_F(NameChangeUDPSenderBasicTest, basicSendTestsMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Verify that attempting to send when we already are is an error.
+ EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+ // Verify that we can stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that attempting to stop sending when we are not is ok.
+ EXPECT_NO_THROW(sender.stopSending());
+
+ // Verify that we can re-enter sending after stopping.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify select_fd is valid and currently shows no ready to read.
+ ASSERT_NE(util::WatchSocket::SOCKET_NOT_VALID, select_fd);
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Iterate over a series of messages, sending each one. Since we
+ // do not invoke IOService::run, then the messages should accumulate
+ // in the queue.
+ NameChangeRequestPtr ncr;
+ NameChangeRequestPtr ncr2;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ // Verify that the queue count increments in step with each send.
+ EXPECT_EQ(i+1, sender.getQueueSize());
+
+ // Verify that peekAt(i) returns the NCR we just added.
+ ASSERT_NO_THROW(ncr2 = sender.peekAt(i));
+ ASSERT_TRUE(ncr2);
+ EXPECT_TRUE(*ncr == *ncr2);
+ }
+
+ // Verify that attempting to peek beyond the end of the queue, throws.
+ ASSERT_THROW(sender.peekAt(sender.getQueueSize()+1), NcrSenderError);
+
+ // Verify that attempting to send an additional message results in a
+ // queue full exception.
+ EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+ // Loop for the number of valid messages. So long as there is at least
+ // on NCR in the queue, select-fd indicate ready to read. Invoke
+ // IOService::run_one. This should complete the send of exactly one
+ // message and the queue count should decrement accordingly.
+ for (int i = num_msgs; i > 0; i--) {
+ // Make sure select_fd does evaluates to ready via select and
+ // that ioReady() method agrees.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that the queue count decrements in step with each run.
+ EXPECT_EQ(i-1, sender.getQueueSize());
+ }
+
+ // Make sure select_fd does evaluates to not ready via select and
+ // that ioReady() method agrees.
+ ASSERT_EQ(0, selectCheck(select_fd));
+ ASSERT_FALSE(sender.ioReady());
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that we can add back to the queue
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify that we can remove the current entry at the front of the queue.
+ EXPECT_NO_THROW(sender.skipNext());
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that flushing the queue is not allowed in sending state.
+ EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+ // Put num_msgs messages on the queue.
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+
+ // Make sure we have number of messages expected.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that the queue is preserved after leaving sending state.
+ EXPECT_EQ(num_msgs - 1, sender.getQueueSize());
+
+ // Verify that flushing the queue works when not sending.
+ EXPECT_NO_THROW(sender.clearSendQueue());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests that sending gets kick-started if the queue isn't empty
+/// when startSending is called.
+TEST_F(NameChangeUDPSenderBasicTest, autoStart) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Queue up messages.
+ NameChangeRequestPtr ncr;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+ // Make sure queue count is what we expect.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Stop sending.
+ ASSERT_NO_THROW(sender.stopSending());
+ ASSERT_FALSE(sender.amSending());
+
+ // We should have completed the first message only.
+ EXPECT_EQ(--num_msgs, sender.getQueueSize());
+
+ // Restart sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+
+ // We should be able to loop through remaining messages and send them.
+ for (int i = num_msgs; i > 0; i--) {
+ // ioReady() should evaluate to true.
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+ }
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests that sending gets kick-started if the queue isn't empty
+/// when startSending is called.
+TEST_F(NameChangeUDPSenderBasicTest, autoStartMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, SENDER_PORT, ip_address,
+ LISTENER_PORT, FMT_JSON, ncr_handler,
+ num_msgs, true);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Queue up messages.
+ NameChangeRequestPtr ncr;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ }
+ // Make sure queue count is what we expect.
+ EXPECT_EQ(num_msgs, sender.getQueueSize());
+
+ // Stop sending.
+ ASSERT_NO_THROW(sender.stopSending());
+ ASSERT_FALSE(sender.amSending());
+
+ // We should have completed the first message only.
+ EXPECT_EQ(--num_msgs, sender.getQueueSize());
+
+ // Restart sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+
+ // We should be able to loop through remaining messages and send them.
+ for (int i = num_msgs; i > 0; i--) {
+ // ioReady() should evaluate to true.
+ ASSERT_TRUE(sender.ioReady());
+
+ // Execute at one ready handler.
+ ASSERT_NO_THROW(sender.runReadyIO());
+ }
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0.
+TEST_F(NameChangeUDPSenderBasicTest, anyAddressSend) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOAddress any_address("0.0.0.0");
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Enter send mode.
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Create and queue up a message.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify we have a ready IO, then execute at one ready handler.
+ ASSERT_TRUE(sender.ioReady());
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that sender shows no IO ready.
+ // and that the queue is empty.
+ ASSERT_FALSE(sender.ioReady());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send with INADDR_ANY and port 0.
+TEST_F(NameChangeUDPSenderBasicTest, anyAddressSendMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOAddress any_address("0.0.0.0");
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(any_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Enter send mode.
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Create and queue up a message.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify we have a ready IO, then execute at one ready handler.
+ ASSERT_TRUE(sender.ioReady());
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify that sender shows no IO ready.
+ // and that the queue is empty.
+ ASSERT_FALSE(sender.ioReady());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Test the NameChangeSender::assumeQueue method.
+TEST_F(NameChangeUDPSenderBasicTest, assumeQueue) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+ NameChangeRequestPtr ncr;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create two senders with queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender1(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ NameChangeUDPSender sender2(ip_address, port+1, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Place sender1 into send mode and queue up messages.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+ }
+
+ // Make sure sender1's queue count is as expected.
+ ASSERT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Verify sender1 is sending, sender2 is not.
+ ASSERT_TRUE(sender1.amSending());
+ ASSERT_FALSE(sender2.amSending());
+
+ // Transfer from sender1 to sender2 should fail because
+ // sender1 is in send mode.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+ ASSERT_FALSE(sender1.amSending());
+ // Stopping should have completed the first message.
+ --num_msgs;
+ EXPECT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Transfer should succeed. Verify sender1 has none,
+ // and sender2 has num_msgs queued.
+ EXPECT_NO_THROW(sender2.assumeQueue(sender1));
+ EXPECT_EQ(0, sender1.getQueueSize());
+ EXPECT_EQ(num_msgs, sender2.getQueueSize());
+
+ // Reduce sender1's max queue size.
+ ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1));
+
+ // Transfer should fail as sender1's queue is not large enough.
+ ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError);
+
+ // Place sender1 into send mode and queue up a message.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+
+ // Try to transfer from sender1 to sender2. This should fail
+ // as sender2's queue is not empty.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+}
+
+/// @brief Test the NameChangeSender::assumeQueue method.
+TEST_F(NameChangeUDPSenderBasicTest, assumeQueueMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+ NameChangeRequestPtr ncr;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create two senders with queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender1(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ NameChangeUDPSender sender2(ip_address, port+1, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Place sender1 into send mode and queue up messages.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+ }
+
+ // Make sure sender1's queue count is as expected.
+ ASSERT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Verify sender1 is sending, sender2 is not.
+ ASSERT_TRUE(sender1.amSending());
+ ASSERT_FALSE(sender2.amSending());
+
+ // Transfer from sender1 to sender2 should fail because
+ // sender1 is in send mode.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+ ASSERT_FALSE(sender1.amSending());
+ // Stopping should have completed the first message.
+ --num_msgs;
+ EXPECT_EQ(num_msgs, sender1.getQueueSize());
+
+ // Transfer should succeed. Verify sender1 has none,
+ // and sender2 has num_msgs queued.
+ EXPECT_NO_THROW(sender2.assumeQueue(sender1));
+ EXPECT_EQ(0, sender1.getQueueSize());
+ EXPECT_EQ(num_msgs, sender2.getQueueSize());
+
+ // Reduce sender1's max queue size.
+ ASSERT_NO_THROW(sender1.setQueueMaxSize(num_msgs - 1));
+
+ // Transfer should fail as sender1's queue is not large enough.
+ ASSERT_THROW(sender1.assumeQueue(sender2), NcrSenderError);
+
+ // Place sender1 into send mode and queue up a message.
+ ASSERT_NO_THROW(sender1.startSending(io_service));
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+ ASSERT_NO_THROW(sender1.sendRequest(ncr));
+
+ // Take sender1 out of send mode.
+ ASSERT_NO_THROW(sender1.stopSending());
+
+ // Try to transfer from sender1 to sender2. This should fail
+ // as sender2's queue is not empty.
+ ASSERT_THROW(sender2.assumeQueue(sender1), NcrSenderError);
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class NameChangeUDPTest : public virtual ::testing::Test,
+ NameChangeListener::RequestReceiveHandler,
+ NameChangeSender::RequestSendHandler {
+public:
+ isc::asiolink::IOService io_service_;
+ NameChangeListener::Result recv_result_;
+ NameChangeSender::Result send_result_;
+ NameChangeListenerPtr listener_;
+ NameChangeSenderPtr sender_;
+ isc::asiolink::IntervalTimer test_timer_;
+
+ std::vector<NameChangeRequestPtr> sent_ncrs_;
+ std::vector<NameChangeRequestPtr> received_ncrs_;
+
+ NameChangeUDPTest()
+ : io_service_(), recv_result_(NameChangeListener::SUCCESS),
+ send_result_(NameChangeSender::SUCCESS), test_timer_(io_service_) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ // Create our listener instance. Note that reuse_address is true.
+ listener_.reset(
+ new NameChangeUDPListener(addr, LISTENER_PORT, FMT_JSON,
+ *this, true));
+
+ // Create our sender instance. Note that reuse_address is true.
+ sender_.reset(
+ new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
+ FMT_JSON, *this, 100, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&NameChangeUDPTest::testTimeoutHandler,
+ this),
+ TEST_TIMEOUT);
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ ~NameChangeUDPTest() {
+ // Disable multi-threading
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ void reset_results() {
+ sent_ncrs_.clear();
+ received_ncrs_.clear();
+ }
+
+ /// @brief Implements the receive completion handler.
+ virtual void operator ()(const NameChangeListener::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR received.
+ recv_result_ = result;
+ received_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Implements the send completion handler.
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR sent.
+ send_result_ = result;
+ sent_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Handler invoked when test timeout is hit
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+
+ /// @brief checks the received NCR content, ignoring the order if necessary.
+ ///
+ /// The UDP does not provide any guarantees regarding order. While in most cases
+ /// the NCRs are received in the order they're sent, that's not guaranteed. As such,
+ /// the test does the normal ordered check, which will pass in most cases. However,
+ /// we have documented Jenkins history that it sometimes fail. In those cases, we
+ /// need to go through a full check assuming the packets were received not in order.
+ /// If that fails, the test will print detailed information about the failure.
+ ///
+ /// This function returns a status, but it's not really necessary to check it as
+ /// it calls gtest macros to indicate failures.
+ ///
+ /// @param num_msgs number of received and sent messages
+ /// @param sent vector of sent NCRs
+ /// @param rcvd vector of received NCRs
+ /// @return true if all packets were received, false otherwise
+ bool checkUnordered(size_t num_msgs, const std::vector<NameChangeRequestPtr>& sent,
+ const std::vector<NameChangeRequestPtr>& rcvd) {
+ // Verify that what we sent matches what we received.
+ // WRONG ASSUMPTION HERE: UDP does not guarantee ordered delivery.
+ bool ok = true;
+ for (int i = 0; i < num_msgs; i++) {
+ if (!checkSendVsReceived(sent[i], rcvd[i])) {
+ // Ok, the data was not received in order.
+ ok = false;
+ break;
+ }
+ }
+ if (!ok) {
+ std::cout << "UDP data received not in order! Checking un ordered delivery"
+ << std::endl;
+ // We need to double iterate through the messages to check every one
+ // against one another.
+ for (int i = 0; i < num_msgs; i++) {
+ ok = false;
+ for (int j = 0; j < num_msgs; j++) {
+ if (checkSendVsReceived(sent[i], rcvd[j])) {
+ std::cout << "Found UDP packet " << i << ", received as " << j << "th"
+ << std::endl;
+ ok = true;
+ }
+ }
+ EXPECT_TRUE(ok) << "failed to find UDP packet " << i << " among total of "
+ << num_msgs << " messages received";
+ }
+ }
+ return (ok);
+ }
+};
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener. The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F(NameChangeUDPTest, roundTripTest) {
+ // Place the listener into listening state.
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ EXPECT_TRUE(listener_->amListening());
+
+ // Get the number of messages in the list of test messages.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(io_service_));
+ EXPECT_TRUE(sender_->amSending());
+
+ for (int i = 0; i < num_msgs; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender_->sendRequest(ncr);
+ EXPECT_EQ(i+1, sender_->getQueueSize());
+ }
+
+ // Execute callbacks until we have sent and received all of messages.
+ while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+ EXPECT_NO_THROW(io_service_.run_one());
+ }
+
+ // Send queue should be empty.
+ EXPECT_EQ(0, sender_->getQueueSize());
+
+ // We should have the same number of sends and receives as we do messages.
+ ASSERT_EQ(num_msgs, sent_ncrs_.size());
+ ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+ // Check if the payload was received, ignoring the order if necessary.
+ checkUnordered(num_msgs, sent_ncrs_, received_ncrs_);
+
+ // Verify that we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender_->stopSending());
+ EXPECT_FALSE(sender_->amSending());
+}
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener. The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F(NameChangeUDPTest, roundTripTestMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Place the listener into listening state.
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ EXPECT_TRUE(listener_->amListening());
+
+ // Get the number of messages in the list of test messages.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(io_service_));
+ EXPECT_TRUE(sender_->amSending());
+
+ for (int i = 0; i < num_msgs; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender_->sendRequest(ncr);
+ EXPECT_EQ(i+1, sender_->getQueueSize());
+ }
+
+ // Execute callbacks until we have sent and received all of messages.
+ while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+ EXPECT_NO_THROW(io_service_.run_one());
+ }
+
+ // Send queue should be empty.
+ EXPECT_EQ(0, sender_->getQueueSize());
+
+ // We should have the same number of sends and receives as we do messages.
+ ASSERT_EQ(num_msgs, sent_ncrs_.size());
+ ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+ // Verify that what we sent matches what we received. Ignore the order
+ // if necessary.
+ checkUnordered(num_msgs, sent_ncrs_, received_ncrs_);
+
+ // Verify that we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender_->stopSending());
+ EXPECT_FALSE(sender_->amSending());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendRequest() is called.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequest) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Create an NCR.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+
+ // Tamper with the watch socket by closing the select-fd.
+ close(sender.getSelectFd());
+
+ // Send should fail as we interfered by closing the select-fd.
+ ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError);
+
+ // Verify we didn't invoke the handler.
+ EXPECT_EQ(0, ncr_handler.pass_count_);
+ EXPECT_EQ(0, ncr_handler.error_count_);
+
+ // Request remains in the queue. Technically it was sent but its
+ // completion handler won't get called.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendRequest() is called.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedBeforeSendRequestMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Create an NCR.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[0]));
+
+ // Tamper with the watch socket by closing the select-fd.
+ close(sender.getSelectFd());
+
+ // Send should fail as we interfered by closing the select-fd.
+ ASSERT_THROW(sender.sendRequest(ncr), util::WatchSocketError);
+
+ // Verify we didn't invoke the handler.
+ EXPECT_EQ(0, ncr_handler.pass_count_);
+ EXPECT_EQ(0, ncr_handler.error_count_);
+
+ // Request remains in the queue. Technically it was sent but its
+ // completion handler won't get called.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendNext() is called during completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequest) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Tamper with the watch socket by closing the select-fd.
+ close (sender.getSelectFd());
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the first message. Doing completion handling, we will
+ // attempt to queue the second message which should fail.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to mark the watch socket ready, when
+// sendNext() is called during completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchClosedAfterSendRequestMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Tamper with the watch socket by closing the select-fd.
+ close (sender.getSelectFd());
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the first message. Doing completion handling, we will
+ // attempt to queue the second message which should fail.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+
+// Tests error handling of a failure to clear the watch socket during
+// completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadRead) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify that select_fd appears ready.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+
+ // Interfere by reading part of the marker from the select-fd.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+ ASSERT_NE(util::WatchSocket::MARKER, buf);
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the message. Doing completion handling clearing the
+ // watch socket should fail, which will close the socket, but not
+ // result in a throw.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+// Tests error handling of a failure to clear the watch socket during
+// completion handling.
+TEST_F(NameChangeUDPSenderBasicTest, watchSocketBadReadMultiThreading) {
+ // Enable multi-threading
+ MultiThreadingMgr::instance().setMode(true);
+
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Create the sender and put into send mode.
+ NameChangeUDPSender sender(ip_address, 0, ip_address, LISTENER_PORT,
+ FMT_JSON, ncr_handler, 100, true);
+ ASSERT_NO_THROW(sender.startSending(io_service));
+ ASSERT_TRUE(sender.amSending());
+
+ // Build and queue up 2 messages. No handlers will get called yet.
+ for (int i = 0; i < 2; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender.sendRequest(ncr);
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Fetch the sender's select-fd.
+ int select_fd = sender.getSelectFd();
+
+ // Verify that select_fd appears ready.
+ ASSERT_TRUE(selectCheck(select_fd) > 0);
+
+ // Interfere by reading part of the marker from the select-fd.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+ ASSERT_NE(util::WatchSocket::MARKER, buf);
+
+ // Run one handler. This should execute the send completion handler
+ // after sending the message. Doing completion handling clearing the
+ // watch socket should fail, which will close the socket, but not
+ // result in a throw.
+ ASSERT_NO_THROW(sender.runReadyIO());
+
+ // Verify handler got called twice. First request should have be sent
+ // without error, second call should have failed to send due to watch
+ // socket markReady failure.
+ EXPECT_EQ(1, ncr_handler.pass_count_);
+ EXPECT_EQ(1, ncr_handler.error_count_);
+
+ // The second request should still be in the queue.
+ EXPECT_EQ(1, sender.getQueueSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
new file mode 100644
index 0000000..b17d72b
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
@@ -0,0 +1,743 @@
+// Copyright (C) 2013-2023 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 <dhcp_ddns/ncr_io.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <util/time_utilities.h>
+
+#include <testutils/gtest_utils.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Missing use-conflict-resolution
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300 "
+ "}"
+};
+
+/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *invalid_msgs[] =
+{
+ // Invalid change type.
+ "{"
+ " \"change-type\" : 7 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid forward change.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : \"bogus\" , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid reverse change.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : 500 , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Forward and reverse change both false.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : false , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Blank FQDN
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Malformed FQDN
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \".bad_name\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Bad IP address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"xxxxxx\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300 "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Blank DHCID
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Odd number of digits in DHCID
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Text in DHCID
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid lease expiration string
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Non-integer for lease length.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : \"BOGUS\", "
+ " \"use-conflict-resolution\": true"
+ "}",
+ // Invalid use-conflict-resolution
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\": 777"
+ "}"
+};
+
+/// @brief Tests the NameChangeRequest constructors.
+/// This test verifies that:
+/// 1. Default constructor works.
+/// 2. "Full" constructor, when given valid parameter values, works.
+/// 3. "Full" constructor, given a blank FQDN fails
+/// 4. "Full" constructor, given an invalid IP Address FQDN fails
+/// 5. "Full" constructor, given a blank DHCID fails
+/// 6. "Full" constructor, given false for both forward and reverse fails
+TEST(NameChangeRequestTest, constructionTests) {
+ // Verify the default constructor works.
+ NameChangeRequestPtr ncr;
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
+ EXPECT_TRUE(ncr);
+
+ // Verify that full constructor works.
+ uint64_t expiry = isc::util::detail::gettimeWrapper();
+ D2Dhcid dhcid("010203040A7F8E3D");
+
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
+ CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", dhcid, expiry, 1300)));
+ EXPECT_TRUE(ncr);
+ ncr.reset();
+
+ // Verify blank FQDN is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that an invalid IP address is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+ "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that a blank DHCID is detected.
+ D2Dhcid blank_dhcid;
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that one or both of direction flags must be true.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+}
+
+/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
+/// It verifies that:
+/// 1. DHCID input strings must contain an even number of characters
+/// 2. DHCID input strings must contain only hexadecimal character digits
+/// 3. A valid DHCID string converts correctly.
+/// 4. Converting a D2Dhcid to a string works correctly.
+/// 5. Equality, inequality, and less-than-equal operators work.
+TEST(NameChangeRequestTest, dhcidTest) {
+ D2Dhcid dhcid;
+
+ // Odd number of digits should be rejected.
+ std::string test_str = "010203040A7F8E3";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Non digit content should be rejected.
+ test_str = "0102BOGUSA7F8E3D";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Verify that valid input converts into a proper byte array.
+ test_str = "010203040A7F8E3D";
+ ASSERT_NO_THROW(dhcid.fromStr(test_str));
+
+ // Create a test vector of expected byte contents.
+ const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
+ std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
+
+ // Fetch the byte vector from the dhcid and verify if equals the expected
+ // content.
+ const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
+ EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
+ EXPECT_TRUE (std::equal(expected_bytes.begin(),
+ expected_bytes.begin()+expected_bytes.size(),
+ converted_bytes.begin()));
+
+ // Convert the new dhcid back to string and verify it matches the original
+ // DHCID input string.
+ std::string next_str = dhcid.toStr();
+ EXPECT_EQ(test_str, next_str);
+
+ // Test equality, inequality, and less-than-equal operators
+ test_str="AABBCCDD";
+ EXPECT_NO_THROW(dhcid.fromStr(test_str));
+
+ D2Dhcid other_dhcid;
+ EXPECT_NO_THROW(other_dhcid.fromStr(test_str));
+
+ EXPECT_TRUE(dhcid == other_dhcid);
+ EXPECT_FALSE(dhcid != other_dhcid);
+
+ EXPECT_NO_THROW(other_dhcid.fromStr("BBCCDDEE"));
+ EXPECT_TRUE(dhcid < other_dhcid);
+
+}
+
+/// @brief Test fixture class for testing DHCID creation.
+class DhcidTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ DhcidTest() {
+ // 0x000000066d79686f7374076578616d706c6503636f6d00
+ const uint8_t fqdn_data[] = {
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ wire_fqdn_.assign(fqdn_data, fqdn_data + sizeof(fqdn_data));
+ }
+
+ /// @brief Destructor
+ virtual ~DhcidTest() {
+ }
+
+ std::vector<uint8_t> wire_fqdn_;
+};
+
+/// Tests that DHCID is correctly created from a DUID and FQDN. The final format
+/// of the DHCID is as follows:
+/// <identifier-type> <digest-type-code> <digest>
+/// where:
+/// - identifier-type (2 octets) is 0x0002.
+/// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
+/// - digest = SHA-256(<DUID> <FQDN>)
+/// Note: FQDN is given in the on-wire canonical format.
+TEST_F(DhcidTest, fromDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ DUID duid(duid_data, sizeof(duid_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0002012191B7B21AF97E0E656DF887C5E2D"
+ "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has minimal length (1).
+TEST_F(DhcidTest, fromMinDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ std::vector<uint8_t> duid_data(DUID::MIN_DUID_LEN, 1);
+ DUID duid(duid_data.data(), duid_data.size());
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "000201202F813E7D9C88BADA41250F2A662"
+ "97742BB9B3EB37C0981D4A905745A30BDD3";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has maximal length (128).
+TEST_F(DhcidTest, fromMaxDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ std::vector<uint8_t> duid_data(DUID::MAX_DUID_LEN, 1);
+ DUID duid(&duid_data[0], duid_data.size());
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0002015B9022851B4015AD78187BB9BDB98"
+ "7708C5EEA74140B28095ED36FE1EAFEE3F6";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// This test verifies that DHCID is properly computed from a buffer holding
+// client identifier data.
+TEST_F(DhcidTest, fromClientId) {
+ D2Dhcid dhcid;
+
+ // Create a buffer holding client id..
+ uint8_t clientid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ std::vector<uint8_t> clientid(clientid_data,
+ clientid_data + sizeof(clientid_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromClientId(clientid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0001012191B7B21AF97E0E656DF887C5E2D"
+ "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+
+ // Make sure that the empty FQDN is not accepted.
+ std::vector<uint8_t> empty_wire_fqdn;
+ EXPECT_THROW(dhcid.fromClientId(clientid, empty_wire_fqdn),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+
+ // Make sure that the empty client identifier is not accepted.
+ clientid.clear();
+ EXPECT_THROW(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+
+
+}
+
+// This test verifies that DHCID is properly computed from a buffer holding
+// client identifier data that contains a DUID.
+TEST_F(DhcidTest, fromClientIdDUID) {
+ D2Dhcid dhcid;
+
+ // Create a buffer holding client id..
+ uint8_t clientid_data[] = { 0xff, 0x5d, 0xe2, 0x6c, 0x15, 0x00, 0x02,
+ 0x00, 0x00, 0xab, 0x11, 0x9a, 0x57, 0x20, 0x95, 0x71, 0x61, 0xbd, 0xd0 };
+ std::vector<uint8_t> clientid(clientid_data,
+ clientid_data + sizeof(clientid_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromClientId(clientid, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ std::string dhcid_ref = "000201A250D060B9352AE68E5014B78D25"
+ "1C30EFB0D5F64E48303B2BC56E6938F129E7";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+
+ // Make sure that a too long client identifier is not accepted.
+ clientid.resize(136);
+ EXPECT_THROW_MSG(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier,"
+ " embedded DUID length of: 136, is too long");
+
+ // Make sure that a too short client identifier is not accepted.
+ clientid.resize(5);
+ EXPECT_THROW_MSG(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError,
+ "unable to compute DHCID from client identifier,"
+ " embedded DUID length of: 5, is too short");
+
+ // Make sure an empty client identifier is not accepted.
+ clientid.clear();
+ EXPECT_THROW_MSG(dhcid.fromClientId(clientid, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError,
+ "empty DUID used to create DHCID");
+}
+
+// This test verifies that DHCID is properly computed from a HW address.
+TEST_F(DhcidTest, fromHWAddr) {
+ D2Dhcid dhcid;
+
+ // Create a buffer holding client id..
+ uint8_t hwaddr_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+ HTYPE_ETHER));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromHWAddr(hwaddr, wire_fqdn_));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0000012247F6DC4423C3E8627434A9D686860"
+ "9D88948F78018B215EDCAA30C0C135035";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+
+ // Make sure that the empty FQDN is not accepted.
+ std::vector<uint8_t> empty_wire_fqdn;
+ EXPECT_THROW(dhcid.fromHWAddr(hwaddr, empty_wire_fqdn),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+
+ // Make sure that the NULL HW address is not accepted.
+ hwaddr.reset();
+ EXPECT_THROW(dhcid.fromHWAddr(hwaddr, wire_fqdn_),
+ isc::dhcp_ddns::DhcidRdataComputeError);
+}
+
+// test operator<< on D2Dhcid
+TEST(NameChangeRequestTest, leftShiftOperation) {
+ const D2Dhcid dhcid("010203040A7F8E3D");
+
+ ostringstream oss;
+ oss << dhcid;
+ EXPECT_EQ(dhcid.toStr(), oss.str());
+}
+
+/// @brief Verifies the fundamentals of converting from and to JSON.
+/// It verifies that:
+/// 1. A NameChangeRequest can be created from a valid JSON string.
+/// 2. A valid JSON string can be created from a NameChangeRequest
+TEST(NameChangeRequestTest, basicJsonTest) {
+ // Define valid JSON rendition of a request.
+ std::string msg_str = "{"
+ "\"change-type\":1,"
+ "\"forward-change\":true,"
+ "\"reverse-change\":false,"
+ "\"fqdn\":\"walah.walah.com.\","
+ "\"ip-address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease-expires-on\":\"20130121132405\","
+ "\"lease-length\":1300,"
+ "\"use-conflict-resolution\":true"
+ "}";
+
+ // Verify that a NameChangeRequests can be instantiated from the
+ // a valid JSON rendition.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(msg_str));
+ ASSERT_TRUE(ncr);
+
+ // Verify that the JSON string created by the new request equals the
+ // original input string.
+ std::string json_str = ncr->toJSON();
+ EXPECT_EQ(msg_str, json_str);
+
+ // Verify that the request ID matches the string from the DHCID.
+ std::string dhcid_str = "010203040A7F8E3D";
+ EXPECT_EQ(dhcid_str, ncr->getRequestId());
+}
+
+/// @brief Tests a variety of invalid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// content error. The list of messages is defined by the global array,
+/// invalid_messages. Currently that list contains the following invalid
+/// conditions:
+/// 1. Invalid change type
+/// 2. Invalid forward change
+/// 3. Invalid reverse change
+/// 4. Forward and reverse change both false
+/// 5. Invalid forward change
+/// 6. Blank FQDN
+/// 7. Bad IP address
+/// 8. Blank DHCID
+/// 9. Odd number of digits in DHCID
+/// 10. Text in DHCID
+/// 11. Invalid lease expiration string
+/// 12. Non-integer for lease length.
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, invalidMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should throw a NcrMessageError.
+ int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
+ NcrMessageError) << "Invalid message not caught idx: "
+ << i << std::endl << " text:[" << invalid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests a variety of valid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// valid request rendition. The list of messages is defined by the global
+/// array, valid_messages. Currently that list contains the following valid
+/// messages:
+/// 1. Valid, IPv4 Add
+/// 2. Valid, IPv4 Remove
+/// 3. Valid, IPv6 Add
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, validMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should succeed.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
+ << "Valid message failed, message idx: " << i
+ << std::endl << " text:[" << valid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests converting to and from JSON via isc::util buffer classes.
+/// This test verifies that:
+/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
+/// 2. A InputBuffer containing a valid JSON request rendition can be used
+/// to create a NameChangeRequest.
+TEST(NameChangeRequestTest, toFromBufferTest) {
+ // Define a string containing a valid JSON NameChangeRequest rendition.
+ std::string msg_str = "{"
+ "\"change-type\":1,"
+ "\"forward-change\":true,"
+ "\"reverse-change\":false,"
+ "\"fqdn\":\"walah.walah.com.\","
+ "\"ip-address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease-expires-on\":\"20130121132405\","
+ "\"lease-length\":1300,"
+ "\"use-conflict-resolution\":true"
+ "}";
+
+ // Create a request from JSON directly.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+
+ // Verify that we output the request as JSON text to a buffer
+ // without error.
+ isc::util::OutputBuffer output_buffer(1024);
+ ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+
+ // Make an InputBuffer from the OutputBuffer.
+ isc::util::InputBuffer input_buffer(output_buffer.getData(),
+ output_buffer.getLength());
+
+ // Verify that we can create a new request from the InputBuffer.
+ NameChangeRequestPtr ncr2;
+ ASSERT_NO_THROW(ncr2 =
+ NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+
+ // Convert the new request to JSON directly.
+ std::string final_str = ncr2->toJSON();
+
+ // Verify that the final string matches the original.
+ ASSERT_EQ(final_str, msg_str);
+}
+
+/// @brief Tests ip address modification and validation
+TEST(NameChangeRequestTest, ipAddresses) {
+ NameChangeRequest ncr;
+
+ // Verify that a valid IPv4 address works.
+ ASSERT_NO_THROW(ncr.setIpAddress("192.168.1.1"));
+ const asiolink::IOAddress& io_addr4 = ncr.getIpIoAddress();
+ EXPECT_EQ(ncr.getIpAddress(), io_addr4.toText());
+ EXPECT_TRUE(ncr.isV4());
+ EXPECT_FALSE(ncr.isV6());
+
+ // Verify that a valid IPv6 address works.
+ ASSERT_NO_THROW(ncr.setIpAddress("2001:1::f3"));
+ const asiolink::IOAddress& io_addr6 = ncr.getIpIoAddress();
+ EXPECT_EQ(ncr.getIpAddress(), io_addr6.toText());
+ EXPECT_FALSE(ncr.isV4());
+ EXPECT_TRUE(ncr.isV6());
+
+ // Verify that an invalid address fails.
+ ASSERT_THROW(ncr.setIpAddress("x001:1::f3"),NcrMessageError);
+}
+
+/// @brief Tests conversion of NameChangeFormat between enum and strings.
+TEST(NameChangeFormatTest, formatEnumConversion){
+ ASSERT_EQ(stringToNcrFormat("JSON"), dhcp_ddns::FMT_JSON);
+ ASSERT_EQ(stringToNcrFormat("jSoN"), dhcp_ddns::FMT_JSON);
+ ASSERT_THROW(stringToNcrFormat("bogus"), isc::BadValue);
+
+ ASSERT_EQ(ncrFormatToString(dhcp_ddns::FMT_JSON), "JSON");
+}
+
+/// @brief Tests conversion of NameChangeProtocol between enum and strings.
+TEST(NameChangeProtocolTest, protocolEnumConversion){
+ ASSERT_EQ(stringToNcrProtocol("UDP"), dhcp_ddns::NCR_UDP);
+ ASSERT_EQ(stringToNcrProtocol("udP"), dhcp_ddns::NCR_UDP);
+ ASSERT_EQ(stringToNcrProtocol("TCP"), dhcp_ddns::NCR_TCP);
+ ASSERT_EQ(stringToNcrProtocol("Tcp"), dhcp_ddns::NCR_TCP);
+ ASSERT_THROW(stringToNcrProtocol("bogus"), isc::BadValue);
+
+ ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_UDP), "UDP");
+ ASSERT_EQ(ncrProtocolToString(dhcp_ddns::NCR_TCP), "TCP");
+}
+
+TEST(NameChangeRequestTest, useConflictResolutionParsing) {
+ std::string base_json =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300 ";
+
+ std::string its_true(base_json + ",\"use-conflict-resolution\": true}");
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(its_true));
+ ASSERT_TRUE(ncr);
+ EXPECT_TRUE(ncr->useConflictResolution());
+
+ std::string its_false(base_json + ",\"use-conflict-resolution\": false}");
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(its_false));
+ ASSERT_TRUE(ncr);
+ EXPECT_FALSE(ncr->useConflictResolution());
+
+ std::string its_missing(base_json + "}");
+ ASSERT_NO_THROW_LOG(ncr = NameChangeRequest::fromJSON(its_true));
+ ASSERT_TRUE(ncr);
+ EXPECT_TRUE(ncr->useConflictResolution());
+}
+
+} // end of anonymous namespace
+
diff --git a/src/lib/dhcp_ddns/tests/run_unittests.cc b/src/lib/dhcp_ddns/tests/run_unittests.cc
new file mode 100644
index 0000000..e1c0801
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// 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 <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/dhcp_ddns/tests/test_utils.cc b/src/lib/dhcp_ddns/tests/test_utils.cc
new file mode 100644
index 0000000..0671307
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/test_utils.cc
@@ -0,0 +1,37 @@
+// Copyright (C) 2014-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 <sys/select.h>
+#include <sys/ioctl.h>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp_ddns {
+
+int selectCheck(int fd_to_check) {
+ fd_set read_fds;
+ int maxfd = 0;
+
+ FD_ZERO(&read_fds);
+
+ // Add this socket to listening set
+ FD_SET(fd_to_check, &read_fds);
+ maxfd = fd_to_check;
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 0;
+
+ return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/lib/dhcp_ddns/tests/test_utils.h b/src/lib/dhcp_ddns/tests/test_utils.h
new file mode 100644
index 0000000..256350e
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/test_utils.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2014-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/.
+
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+/// @file test_utils.h Common dhcp_ddns testing elements
+
+#include <gtest/gtest.h>
+
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Returns the result of select() given an fd to check for read status.
+///
+/// @param fd_to_check The file descriptor to test
+///
+/// @return Returns less than one on an error, 0 if the fd is not ready to
+/// read, > 0 if it is ready to read.
+int selectCheck(int fd_to_check);
+
+}; // namespace isc::dhcp_ddns;
+}; // namespace isc;
+
+#endif
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
new file mode 100644
index 0000000..1f85a1d
--- /dev/null
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -0,0 +1,418 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+SUBDIRS = . testutils tests
+
+# DATA_DIR is the directory where to put default CSV files and the DHCPv6
+# server ID file (i.e. the file where the server finds its DUID at startup).
+dhcp_data_dir = @localstatedir@/lib/@PACKAGE@
+kea_lfc_location = @prefix@/sbin/kea-lfc
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDHCP_DATA_DIR="\"$(dhcp_data_dir)\""
+AM_CPPFLAGS += -DTOP_BUILDDIR="\"$(top_builddir)\""
+# Set location of the kea-lfc binary.
+AM_CPPFLAGS += -DKEA_LFC_EXECUTABLE="\"$(kea_lfc_location)\""
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+if HAVE_MYSQL
+AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
+endif
+if HAVE_PGSQL
+AM_CPPFLAGS += $(PGSQL_CPPFLAGS)
+endif
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# The files in the subfolder must be explicitly specified here so
+# as they are copied to the distribution. The other option would
+# be to specify a whole 'parsers' folder here but that would also
+# copy all other files, e.g. gitignore, .git etc.
+# Whenever new file is added to the parsers folder, it must be
+# added here.
+EXTRA_DIST =
+EXTRA_DIST += parsers/client_class_def_parser.cc
+EXTRA_DIST += parsers/client_class_def_parser.h
+EXTRA_DIST += parsers/dhcp_parsers.cc
+EXTRA_DIST += parsers/dhcp_parsers.h
+EXTRA_DIST += parsers/expiration_config_parser.cc
+EXTRA_DIST += parsers/expiration_config_parser.h
+EXTRA_DIST += parsers/host_reservation_parser.cc
+EXTRA_DIST += parsers/host_reservation_parser.h
+EXTRA_DIST += parsers/host_reservations_list_parser.h
+EXTRA_DIST += parsers/ifaces_config_parser.cc
+EXTRA_DIST += parsers/ifaces_config_parser.h
+EXTRA_DIST += parsers/multi_threading_config_parser.cc
+EXTRA_DIST += parsers/multi_threading_config_parser.h
+EXTRA_DIST += parsers/option_data_parser.h
+EXTRA_DIST += parsers/sanity_checks_parser.cc
+EXTRA_DIST += parsers/sanity_checks_parser.h
+EXTRA_DIST += parsers/simple_parser4.cc
+EXTRA_DIST += parsers/simple_parser4.h
+EXTRA_DIST += parsers/simple_parser6.cc
+EXTRA_DIST += parsers/simple_parser6.h
+EXTRA_DIST += parsers/dhcp_queue_control_parser.cc
+EXTRA_DIST += parsers/dhcp_queue_control_parser.h
+
+# Devel guide diagrams
+EXTRA_DIST += images/pgsql_host_data_source.svg
+
+CLEANFILES = *.gcno *.gcda
+# Remove CSV files created by the CSVLeaseFile6 and CSVLeaseFile4 unit tests.
+CLEANFILES += *.csv
+
+lib_LTLIBRARIES = libkea-dhcpsrv.la
+libkea_dhcpsrv_la_SOURCES =
+libkea_dhcpsrv_la_SOURCES += allocation_state.cc allocation_state.h
+libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libkea_dhcpsrv_la_SOURCES += alloc_engine_log.cc alloc_engine_log.h
+libkea_dhcpsrv_la_SOURCES += alloc_engine_messages.h alloc_engine_messages.cc
+libkea_dhcpsrv_la_SOURCES += allocator.h allocator.cc
+libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
+libkea_dhcpsrv_la_SOURCES += cache_host_data_source.h
+libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
+libkea_dhcpsrv_la_SOURCES += cb_ctl_dhcp.h
+libkea_dhcpsrv_la_SOURCES += cb_ctl_dhcp4.cc cb_ctl_dhcp4.h
+libkea_dhcpsrv_la_SOURCES += cb_ctl_dhcp6.cc cb_ctl_dhcp6.h
+libkea_dhcpsrv_la_SOURCES += cfg_4o6.cc cfg_4o6.h
+libkea_dhcpsrv_la_SOURCES += cfg_consistency.cc cfg_consistency.h
+libkea_dhcpsrv_la_SOURCES += cfg_db_access.cc cfg_db_access.h
+libkea_dhcpsrv_la_SOURCES += cfg_duid.cc cfg_duid.h
+libkea_dhcpsrv_la_SOURCES += cfg_globals.cc cfg_globals.h
+libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
+libkea_dhcpsrv_la_SOURCES += cfg_hosts_util.cc cfg_hosts_util.h
+libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
+libkea_dhcpsrv_la_SOURCES += cfg_expiration.cc cfg_expiration.h
+libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h
+libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
+libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
+libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h
+libkea_dhcpsrv_la_SOURCES += cfg_shared_networks.cc cfg_shared_networks.h
+libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
+libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
+libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h
+libkea_dhcpsrv_la_SOURCES += cfg_multi_threading.cc cfg_multi_threading.h
+libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libkea_dhcpsrv_la_SOURCES += client_class_def.cc client_class_def.h
+libkea_dhcpsrv_la_SOURCES += config_backend_dhcp4.h
+libkea_dhcpsrv_la_SOURCES += config_backend_pool_dhcp4.cc config_backend_pool_dhcp4.h
+libkea_dhcpsrv_la_SOURCES += config_backend_dhcp4_mgr.cc config_backend_dhcp4_mgr.h
+libkea_dhcpsrv_la_SOURCES += config_backend_dhcp6.h
+libkea_dhcpsrv_la_SOURCES += config_backend_pool_dhcp6.cc config_backend_pool_dhcp6.h
+libkea_dhcpsrv_la_SOURCES += config_backend_dhcp6_mgr.cc config_backend_dhcp6_mgr.h
+libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
+libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
+libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
+libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
+libkea_dhcpsrv_la_SOURCES += db_type.h
+libkea_dhcpsrv_la_SOURCES += dhcp4o6_ipc.cc dhcp4o6_ipc.h
+libkea_dhcpsrv_la_SOURCES += dhcpsrv_exceptions.h
+libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
+libkea_dhcpsrv_la_SOURCES += dhcpsrv_messages.h dhcpsrv_messages.cc
+libkea_dhcpsrv_la_SOURCES += flq_allocation_state.cc flq_allocation_state.h
+libkea_dhcpsrv_la_SOURCES += flq_allocator.cc flq_allocator.h
+libkea_dhcpsrv_la_SOURCES += host.cc host.h
+libkea_dhcpsrv_la_SOURCES += host_container.h
+libkea_dhcpsrv_la_SOURCES += host_data_source_factory.cc host_data_source_factory.h
+libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
+libkea_dhcpsrv_la_SOURCES += hosts_log.cc hosts_log.h
+libkea_dhcpsrv_la_SOURCES += hosts_messages.h hosts_messages.cc
+libkea_dhcpsrv_la_SOURCES += ip_range.h ip_range.cc
+libkea_dhcpsrv_la_SOURCES += ip_range_permutation.h ip_range_permutation.cc
+libkea_dhcpsrv_la_SOURCES += iterative_allocation_state.cc iterative_allocation_state.h
+libkea_dhcpsrv_la_SOURCES += iterative_allocator.cc iterative_allocator.h
+libkea_dhcpsrv_la_SOURCES += key_from_key.h
+libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
+libkea_dhcpsrv_la_SOURCES += lease_file_loader.h
+libkea_dhcpsrv_la_SOURCES += lease_file_stats.h
+libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
+libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
+libkea_dhcpsrv_la_SOURCES += memfile_lease_limits.cc memfile_lease_limits.h
+libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
+libkea_dhcpsrv_la_SOURCES += memfile_lease_storage.h
+
+if HAVE_MYSQL
+libkea_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
+libkea_dhcpsrv_la_SOURCES += mysql_host_data_source.cc mysql_host_data_source.h
+endif
+
+libkea_dhcpsrv_la_SOURCES += ncr_generator.cc ncr_generator.h
+libkea_dhcpsrv_la_SOURCES += network.cc network.h
+libkea_dhcpsrv_la_SOURCES += network_state.cc network_state.h
+
+if HAVE_PGSQL
+libkea_dhcpsrv_la_SOURCES += pgsql_host_data_source.cc pgsql_host_data_source.h
+libkea_dhcpsrv_la_SOURCES += pgsql_lease_mgr.cc pgsql_lease_mgr.h
+endif
+
+libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
+libkea_dhcpsrv_la_SOURCES += random_allocation_state.cc random_allocation_state.h
+libkea_dhcpsrv_la_SOURCES += random_allocator.cc random_allocator.h
+libkea_dhcpsrv_la_SOURCES += resource_handler.cc resource_handler.h
+libkea_dhcpsrv_la_SOURCES += sanity_checker.cc sanity_checker.h
+libkea_dhcpsrv_la_SOURCES += shared_network.cc shared_network.h
+libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
+libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
+libkea_dhcpsrv_la_SOURCES += subnet_id.h
+libkea_dhcpsrv_la_SOURCES += subnet_selector.h
+libkea_dhcpsrv_la_SOURCES += timer_mgr.cc timer_mgr.h
+libkea_dhcpsrv_la_SOURCES += tracking_lease_mgr.cc tracking_lease_mgr.h
+libkea_dhcpsrv_la_SOURCES += utils.h
+libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h
+
+# Configuration parsers
+libkea_dhcpsrv_la_SOURCES += parsers/base_network_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/base_network_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/client_class_def_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/client_class_def_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.cc
+libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.h
+libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/duid_config_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/expiration_config_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/expiration_config_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/host_reservation_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/host_reservation_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/host_reservations_list_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/ifaces_config_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/multi_threading_config_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/multi_threading_config_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/dhcp_queue_control_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/dhcp_queue_control_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/sanity_checks_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/sanity_checks_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/shared_network_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/shared_network_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/shared_networks_list_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.cc
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser4.h
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.cc
+libkea_dhcpsrv_la_SOURCES += parsers/simple_parser6.h
+
+if ENABLE_AFL
+libkea_dhcpsrv_la_SOURCES += fuzz.cc fuzz.h
+libkea_dhcpsrv_la_SOURCES += fuzz_log.cc fuzz_log.h
+libkea_dhcpsrv_la_SOURCES += fuzz_messages.cc fuzz_messages.h
+endif
+
+libkea_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/process/libkea-process.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/http/libkea-http.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+
+if HAVE_MYSQL
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+endif
+if HAVE_PGSQL
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+endif
+
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_dhcpsrv_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+libkea_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 90:0:0
+libkea_dhcpsrv_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+if HAVE_MYSQL
+libkea_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+libkea_dhcpsrv_la_LDFLAGS += $(PGSQL_LIBS)
+endif
+
+# The message file should be in the distribution
+EXTRA_DIST += alloc_engine_messages.mes
+EXTRA_DIST += dhcpsrv_messages.mes
+EXTRA_DIST += hosts_messages.mes
+EXTRA_DIST += fuzz_messages.mes
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f alloc_engine_messages.h alloc_engine_messages.cc
+ rm -f dhcpsrv_messages.h dhcpsrv_messages.cc
+ rm -f hosts_messages.h hosts_messages.cc
+ rm -f fuzz_messages.h fuzz_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: alloc_engine_messages.h alloc_engine_messages.cc \
+ dhcpsrv_messages.h dhcpsrv_messages.cc \
+ hosts_messages.h hosts_messages.cc \
+ fuzz_messages.h fuzz_messages.cc
+ @echo Message files regenerated
+
+alloc_engine_messages.h alloc_engine_messages.cc: alloc_engine_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/alloc_engine_messages.mes
+
+dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+hosts_messages.h hosts_messages.cc: hosts_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/hosts_messages.mes
+
+fuzz_messages.h fuzz_messages.cc: fuzz_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/fuzz_messages.mes
+
+else
+
+messages: alloc_engine_messages.h alloc_engine_messages.cc \
+ dhcpsrv_messages.h dhcpsrv_messages.cc \
+ hosts_messages.h hosts_messages.cc \
+ fuzz_messages.h fuzz_messages.cc
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Distribute backend documentation
+# Database schema creation script moved to src/bin/admin
+EXTRA_DIST += database_backends.dox libdhcpsrv.dox
+
+# Specify the headers for copying into the installation directory tree.
+libkea_dhcpsrv_includedir = $(pkgincludedir)/dhcpsrv
+libkea_dhcpsrv_include_HEADERS = \
+ allocation_state.h \
+ alloc_engine.h \
+ alloc_engine_log.h \
+ alloc_engine_messages.h \
+ allocator.h \
+ base_host_data_source.h \
+ cache_host_data_source.h \
+ callout_handle_store.h \
+ cb_ctl_dhcp.h \
+ cb_ctl_dhcp4.h \
+ cb_ctl_dhcp6.h \
+ cfg_4o6.h \
+ cfg_consistency.h \
+ cfg_db_access.h \
+ cfg_duid.h \
+ cfg_expiration.h \
+ cfg_host_operations.h \
+ cfg_globals.h \
+ cfg_hosts.h \
+ cfg_hosts_util.h \
+ cfg_iface.h \
+ cfg_mac_source.h \
+ cfg_multi_threading.h \
+ cfg_option.h \
+ cfg_option_def.h \
+ cfg_rsoo.h \
+ cfg_shared_networks.h \
+ cfg_subnets4.h \
+ cfg_subnets6.h \
+ cfgmgr.h \
+ client_class_def.h \
+ config_backend_dhcp4.h \
+ config_backend_dhcp6.h \
+ config_backend_dhcp4_mgr.h \
+ config_backend_dhcp6_mgr.h \
+ config_backend_pool_dhcp4.h \
+ config_backend_pool_dhcp6.h \
+ csv_lease_file4.h \
+ csv_lease_file6.h \
+ dhcpsrv_exceptions.h \
+ dhcpsrv_messages.h \
+ d2_client_cfg.h \
+ d2_client_mgr.h \
+ db_type.h \
+ dhcp4o6_ipc.h \
+ dhcpsrv_log.h \
+ flq_allocation_state.h \
+ flq_allocator.h \
+ host.h \
+ host_container.h \
+ host_data_source_factory.h \
+ hosts_messages.h \
+ host_mgr.h \
+ hosts_log.h \
+ ip_range.h \
+ ip_range_permutation.h \
+ iterative_allocation_state.h \
+ iterative_allocator.h \
+ key_from_key.h \
+ lease.h \
+ lease_file_loader.h \
+ lease_file_stats.h \
+ lease_mgr.h \
+ lease_mgr_factory.h \
+ memfile_lease_limits.h \
+ memfile_lease_mgr.h \
+ memfile_lease_storage.h \
+ ncr_generator.h \
+ network.h \
+ network_state.h \
+ tracking_lease_mgr.h \
+ pool.h \
+ random_allocation_state.h \
+ random_allocator.h \
+ resource_handler.h \
+ sanity_checker.h \
+ shared_network.h \
+ srv_config.h \
+ subnet.h \
+ subnet_id.h \
+ subnet_selector.h \
+ timer_mgr.h \
+ utils.h \
+ writable_host_data_source.h
+
+if HAVE_MYSQL
+libkea_dhcpsrv_include_HEADERS += \
+ mysql_host_data_source.h \
+ mysql_lease_mgr.h
+endif
+
+if HAVE_PGSQL
+libkea_dhcpsrv_include_HEADERS += \
+ pgsql_host_data_source.h \
+ pgsql_lease_mgr.h
+endif
+
+# Specify parsers' headers for copying into installation directory tree.
+libkea_dhcpsrv_parsers_includedir = $(pkgincludedir)/dhcpsrv/parsers
+libkea_dhcpsrv_parsers_include_HEADERS = \
+ parsers/base_network_parser.h \
+ parsers/client_class_def_parser.h \
+ parsers/dhcp_parsers.h \
+ parsers/duid_config_parser.h \
+ parsers/expiration_config_parser.h \
+ parsers/host_reservation_parser.h \
+ parsers/host_reservations_list_parser.h \
+ parsers/ifaces_config_parser.h \
+ parsers/multi_threading_config_parser.h \
+ parsers/option_data_parser.h \
+ parsers/dhcp_queue_control_parser.h \
+ parsers/sanity_checks_parser.h \
+ parsers/shared_network_parser.h \
+ parsers/shared_networks_list_parser.h \
+ parsers/simple_parser4.h \
+ parsers/simple_parser6.h
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(dhcp_data_dir)
diff --git a/src/lib/dhcpsrv/Makefile.in b/src/lib/dhcpsrv/Makefile.in
new file mode 100644
index 0000000..342a444
--- /dev/null
+++ b/src/lib/dhcpsrv/Makefile.in
@@ -0,0 +1,2438 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_MYSQL_TRUE@am__append_1 = $(MYSQL_CPPFLAGS)
+@HAVE_PGSQL_TRUE@am__append_2 = $(PGSQL_CPPFLAGS)
+@HAVE_MYSQL_TRUE@am__append_3 = mysql_lease_mgr.cc mysql_lease_mgr.h \
+@HAVE_MYSQL_TRUE@ mysql_host_data_source.cc \
+@HAVE_MYSQL_TRUE@ mysql_host_data_source.h
+@HAVE_PGSQL_TRUE@am__append_4 = pgsql_host_data_source.cc \
+@HAVE_PGSQL_TRUE@ pgsql_host_data_source.h pgsql_lease_mgr.cc \
+@HAVE_PGSQL_TRUE@ pgsql_lease_mgr.h
+@ENABLE_AFL_TRUE@am__append_5 = fuzz.cc fuzz.h fuzz_log.cc fuzz_log.h \
+@ENABLE_AFL_TRUE@ fuzz_messages.cc fuzz_messages.h
+@HAVE_MYSQL_TRUE@am__append_6 = $(top_builddir)/src/lib/mysql/libkea-mysql.la
+@HAVE_PGSQL_TRUE@am__append_7 = $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+@HAVE_MYSQL_TRUE@am__append_8 = $(MYSQL_LIBS)
+@HAVE_PGSQL_TRUE@am__append_9 = $(PGSQL_LIBS)
+@HAVE_MYSQL_TRUE@am__append_10 = \
+@HAVE_MYSQL_TRUE@ mysql_host_data_source.h \
+@HAVE_MYSQL_TRUE@ mysql_lease_mgr.h
+
+@HAVE_PGSQL_TRUE@am__append_11 = \
+@HAVE_PGSQL_TRUE@ pgsql_host_data_source.h \
+@HAVE_PGSQL_TRUE@ pgsql_lease_mgr.h
+
+subdir = src/lib/dhcpsrv
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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__libkea_dhcpsrv_include_HEADERS_DIST) \
+ $(libkea_dhcpsrv_parsers_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)$(libkea_dhcpsrv_includedir)" \
+ "$(DESTDIR)$(libkea_dhcpsrv_parsers_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_dhcpsrv_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/eval/libkea-eval.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la $(am__append_6) \
+ $(am__append_7) \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am__libkea_dhcpsrv_la_SOURCES_DIST = allocation_state.cc \
+ allocation_state.h alloc_engine.cc alloc_engine.h \
+ alloc_engine_log.cc alloc_engine_log.h alloc_engine_messages.h \
+ alloc_engine_messages.cc allocator.h allocator.cc \
+ base_host_data_source.h cache_host_data_source.h \
+ callout_handle_store.h cb_ctl_dhcp.h cb_ctl_dhcp4.cc \
+ cb_ctl_dhcp4.h cb_ctl_dhcp6.cc cb_ctl_dhcp6.h cfg_4o6.cc \
+ cfg_4o6.h cfg_consistency.cc cfg_consistency.h \
+ cfg_db_access.cc cfg_db_access.h cfg_duid.cc cfg_duid.h \
+ cfg_globals.cc cfg_globals.h cfg_hosts.cc cfg_hosts.h \
+ cfg_hosts_util.cc cfg_hosts_util.h cfg_iface.cc cfg_iface.h \
+ cfg_expiration.cc cfg_expiration.h cfg_host_operations.cc \
+ cfg_host_operations.h cfg_option.cc cfg_option.h \
+ cfg_option_def.cc cfg_option_def.h cfg_rsoo.cc cfg_rsoo.h \
+ cfg_shared_networks.cc cfg_shared_networks.h cfg_subnets4.cc \
+ cfg_subnets4.h cfg_subnets6.cc cfg_subnets6.h \
+ cfg_mac_source.cc cfg_mac_source.h cfg_multi_threading.cc \
+ cfg_multi_threading.h cfgmgr.cc cfgmgr.h client_class_def.cc \
+ client_class_def.h config_backend_dhcp4.h \
+ config_backend_pool_dhcp4.cc config_backend_pool_dhcp4.h \
+ config_backend_dhcp4_mgr.cc config_backend_dhcp4_mgr.h \
+ config_backend_dhcp6.h config_backend_pool_dhcp6.cc \
+ config_backend_pool_dhcp6.h config_backend_dhcp6_mgr.cc \
+ config_backend_dhcp6_mgr.h csv_lease_file4.cc \
+ csv_lease_file4.h csv_lease_file6.cc csv_lease_file6.h \
+ d2_client_cfg.cc d2_client_cfg.h d2_client_mgr.cc \
+ d2_client_mgr.h db_type.h dhcp4o6_ipc.cc dhcp4o6_ipc.h \
+ dhcpsrv_exceptions.h dhcpsrv_log.cc dhcpsrv_log.h \
+ dhcpsrv_messages.h dhcpsrv_messages.cc flq_allocation_state.cc \
+ flq_allocation_state.h flq_allocator.cc flq_allocator.h \
+ host.cc host.h host_container.h host_data_source_factory.cc \
+ host_data_source_factory.h host_mgr.cc host_mgr.h hosts_log.cc \
+ hosts_log.h hosts_messages.h hosts_messages.cc ip_range.h \
+ ip_range.cc ip_range_permutation.h ip_range_permutation.cc \
+ iterative_allocation_state.cc iterative_allocation_state.h \
+ iterative_allocator.cc iterative_allocator.h key_from_key.h \
+ lease.cc lease.h lease_file_loader.h lease_file_stats.h \
+ lease_mgr.cc lease_mgr.h lease_mgr_factory.cc \
+ lease_mgr_factory.h memfile_lease_limits.cc \
+ memfile_lease_limits.h memfile_lease_mgr.cc \
+ memfile_lease_mgr.h memfile_lease_storage.h mysql_lease_mgr.cc \
+ mysql_lease_mgr.h mysql_host_data_source.cc \
+ mysql_host_data_source.h ncr_generator.cc ncr_generator.h \
+ network.cc network.h network_state.cc network_state.h \
+ pgsql_host_data_source.cc pgsql_host_data_source.h \
+ pgsql_lease_mgr.cc pgsql_lease_mgr.h pool.cc pool.h \
+ random_allocation_state.cc random_allocation_state.h \
+ random_allocator.cc random_allocator.h resource_handler.cc \
+ resource_handler.h sanity_checker.cc sanity_checker.h \
+ shared_network.cc shared_network.h srv_config.cc srv_config.h \
+ subnet.cc subnet.h subnet_id.h subnet_selector.h timer_mgr.cc \
+ timer_mgr.h tracking_lease_mgr.cc tracking_lease_mgr.h utils.h \
+ writable_host_data_source.h parsers/base_network_parser.cc \
+ parsers/base_network_parser.h \
+ parsers/client_class_def_parser.cc \
+ parsers/client_class_def_parser.h parsers/dhcp_parsers.cc \
+ parsers/dhcp_parsers.h parsers/duid_config_parser.cc \
+ parsers/duid_config_parser.h \
+ parsers/expiration_config_parser.cc \
+ parsers/expiration_config_parser.h \
+ parsers/host_reservation_parser.cc \
+ parsers/host_reservation_parser.h \
+ parsers/host_reservations_list_parser.h \
+ parsers/ifaces_config_parser.cc parsers/ifaces_config_parser.h \
+ parsers/multi_threading_config_parser.cc \
+ parsers/multi_threading_config_parser.h \
+ parsers/option_data_parser.cc parsers/option_data_parser.h \
+ parsers/dhcp_queue_control_parser.cc \
+ parsers/dhcp_queue_control_parser.h \
+ parsers/sanity_checks_parser.cc parsers/sanity_checks_parser.h \
+ parsers/shared_network_parser.cc \
+ parsers/shared_network_parser.h \
+ parsers/shared_networks_list_parser.h \
+ parsers/simple_parser4.cc parsers/simple_parser4.h \
+ parsers/simple_parser6.cc parsers/simple_parser6.h fuzz.cc \
+ fuzz.h fuzz_log.cc fuzz_log.h fuzz_messages.cc fuzz_messages.h
+@HAVE_MYSQL_TRUE@am__objects_1 = libkea_dhcpsrv_la-mysql_lease_mgr.lo \
+@HAVE_MYSQL_TRUE@ libkea_dhcpsrv_la-mysql_host_data_source.lo
+@HAVE_PGSQL_TRUE@am__objects_2 = \
+@HAVE_PGSQL_TRUE@ libkea_dhcpsrv_la-pgsql_host_data_source.lo \
+@HAVE_PGSQL_TRUE@ libkea_dhcpsrv_la-pgsql_lease_mgr.lo
+am__dirstamp = $(am__leading_dot)dirstamp
+@ENABLE_AFL_TRUE@am__objects_3 = libkea_dhcpsrv_la-fuzz.lo \
+@ENABLE_AFL_TRUE@ libkea_dhcpsrv_la-fuzz_log.lo \
+@ENABLE_AFL_TRUE@ libkea_dhcpsrv_la-fuzz_messages.lo
+am_libkea_dhcpsrv_la_OBJECTS = libkea_dhcpsrv_la-allocation_state.lo \
+ libkea_dhcpsrv_la-alloc_engine.lo \
+ libkea_dhcpsrv_la-alloc_engine_log.lo \
+ libkea_dhcpsrv_la-alloc_engine_messages.lo \
+ libkea_dhcpsrv_la-allocator.lo \
+ libkea_dhcpsrv_la-cb_ctl_dhcp4.lo \
+ libkea_dhcpsrv_la-cb_ctl_dhcp6.lo libkea_dhcpsrv_la-cfg_4o6.lo \
+ libkea_dhcpsrv_la-cfg_consistency.lo \
+ libkea_dhcpsrv_la-cfg_db_access.lo \
+ libkea_dhcpsrv_la-cfg_duid.lo libkea_dhcpsrv_la-cfg_globals.lo \
+ libkea_dhcpsrv_la-cfg_hosts.lo \
+ libkea_dhcpsrv_la-cfg_hosts_util.lo \
+ libkea_dhcpsrv_la-cfg_iface.lo \
+ libkea_dhcpsrv_la-cfg_expiration.lo \
+ libkea_dhcpsrv_la-cfg_host_operations.lo \
+ libkea_dhcpsrv_la-cfg_option.lo \
+ libkea_dhcpsrv_la-cfg_option_def.lo \
+ libkea_dhcpsrv_la-cfg_rsoo.lo \
+ libkea_dhcpsrv_la-cfg_shared_networks.lo \
+ libkea_dhcpsrv_la-cfg_subnets4.lo \
+ libkea_dhcpsrv_la-cfg_subnets6.lo \
+ libkea_dhcpsrv_la-cfg_mac_source.lo \
+ libkea_dhcpsrv_la-cfg_multi_threading.lo \
+ libkea_dhcpsrv_la-cfgmgr.lo \
+ libkea_dhcpsrv_la-client_class_def.lo \
+ libkea_dhcpsrv_la-config_backend_pool_dhcp4.lo \
+ libkea_dhcpsrv_la-config_backend_dhcp4_mgr.lo \
+ libkea_dhcpsrv_la-config_backend_pool_dhcp6.lo \
+ libkea_dhcpsrv_la-config_backend_dhcp6_mgr.lo \
+ libkea_dhcpsrv_la-csv_lease_file4.lo \
+ libkea_dhcpsrv_la-csv_lease_file6.lo \
+ libkea_dhcpsrv_la-d2_client_cfg.lo \
+ libkea_dhcpsrv_la-d2_client_mgr.lo \
+ libkea_dhcpsrv_la-dhcp4o6_ipc.lo \
+ libkea_dhcpsrv_la-dhcpsrv_log.lo \
+ libkea_dhcpsrv_la-dhcpsrv_messages.lo \
+ libkea_dhcpsrv_la-flq_allocation_state.lo \
+ libkea_dhcpsrv_la-flq_allocator.lo libkea_dhcpsrv_la-host.lo \
+ libkea_dhcpsrv_la-host_data_source_factory.lo \
+ libkea_dhcpsrv_la-host_mgr.lo libkea_dhcpsrv_la-hosts_log.lo \
+ libkea_dhcpsrv_la-hosts_messages.lo \
+ libkea_dhcpsrv_la-ip_range.lo \
+ libkea_dhcpsrv_la-ip_range_permutation.lo \
+ libkea_dhcpsrv_la-iterative_allocation_state.lo \
+ libkea_dhcpsrv_la-iterative_allocator.lo \
+ libkea_dhcpsrv_la-lease.lo libkea_dhcpsrv_la-lease_mgr.lo \
+ libkea_dhcpsrv_la-lease_mgr_factory.lo \
+ libkea_dhcpsrv_la-memfile_lease_limits.lo \
+ libkea_dhcpsrv_la-memfile_lease_mgr.lo $(am__objects_1) \
+ libkea_dhcpsrv_la-ncr_generator.lo \
+ libkea_dhcpsrv_la-network.lo \
+ libkea_dhcpsrv_la-network_state.lo $(am__objects_2) \
+ libkea_dhcpsrv_la-pool.lo \
+ libkea_dhcpsrv_la-random_allocation_state.lo \
+ libkea_dhcpsrv_la-random_allocator.lo \
+ libkea_dhcpsrv_la-resource_handler.lo \
+ libkea_dhcpsrv_la-sanity_checker.lo \
+ libkea_dhcpsrv_la-shared_network.lo \
+ libkea_dhcpsrv_la-srv_config.lo libkea_dhcpsrv_la-subnet.lo \
+ libkea_dhcpsrv_la-timer_mgr.lo \
+ libkea_dhcpsrv_la-tracking_lease_mgr.lo \
+ parsers/libkea_dhcpsrv_la-base_network_parser.lo \
+ parsers/libkea_dhcpsrv_la-client_class_def_parser.lo \
+ parsers/libkea_dhcpsrv_la-dhcp_parsers.lo \
+ parsers/libkea_dhcpsrv_la-duid_config_parser.lo \
+ parsers/libkea_dhcpsrv_la-expiration_config_parser.lo \
+ parsers/libkea_dhcpsrv_la-host_reservation_parser.lo \
+ parsers/libkea_dhcpsrv_la-ifaces_config_parser.lo \
+ parsers/libkea_dhcpsrv_la-multi_threading_config_parser.lo \
+ parsers/libkea_dhcpsrv_la-option_data_parser.lo \
+ parsers/libkea_dhcpsrv_la-dhcp_queue_control_parser.lo \
+ parsers/libkea_dhcpsrv_la-sanity_checks_parser.lo \
+ parsers/libkea_dhcpsrv_la-shared_network_parser.lo \
+ parsers/libkea_dhcpsrv_la-simple_parser4.lo \
+ parsers/libkea_dhcpsrv_la-simple_parser6.lo $(am__objects_3)
+libkea_dhcpsrv_la_OBJECTS = $(am_libkea_dhcpsrv_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_dhcpsrv_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_dhcpsrv_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_dhcpsrv_la-alloc_engine.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_log.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_messages.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-allocation_state.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-allocator.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp4.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp6.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_4o6.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_consistency.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_db_access.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_duid.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_expiration.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_globals.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_host_operations.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts_util.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_iface.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_mac_source.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_multi_threading.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option_def.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_rsoo.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_shared_networks.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets4.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets6.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-cfgmgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-client_class_def.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp4_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp6_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp4.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp6.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file4.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file6.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_cfg.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-dhcp4o6_ipc.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_log.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_messages.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocation_state.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocator.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_log.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_messages.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-host.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-host_data_source_factory.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-host_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-hosts_log.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-hosts_messages.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-ip_range.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-ip_range_permutation.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocation_state.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocator.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-lease.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr_factory.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_limits.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-mysql_host_data_source.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-mysql_lease_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-ncr_generator.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-network.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-network_state.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_host_data_source.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_lease_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-pool.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-random_allocation_state.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-random_allocator.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-resource_handler.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-sanity_checker.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-shared_network.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-srv_config.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-subnet.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-timer_mgr.Plo \
+ ./$(DEPDIR)/libkea_dhcpsrv_la-tracking_lease_mgr.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-base_network_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-client_class_def_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_parsers.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_queue_control_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-duid_config_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-expiration_config_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-host_reservation_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-ifaces_config_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-multi_threading_config_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-option_data_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-sanity_checks_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-shared_network_parser.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser4.Plo \
+ parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser6.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_dhcpsrv_la_SOURCES)
+DIST_SOURCES = $(am__libkea_dhcpsrv_la_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
+am__libkea_dhcpsrv_include_HEADERS_DIST = allocation_state.h \
+ alloc_engine.h alloc_engine_log.h alloc_engine_messages.h \
+ allocator.h base_host_data_source.h cache_host_data_source.h \
+ callout_handle_store.h cb_ctl_dhcp.h cb_ctl_dhcp4.h \
+ cb_ctl_dhcp6.h cfg_4o6.h cfg_consistency.h cfg_db_access.h \
+ cfg_duid.h cfg_expiration.h cfg_host_operations.h \
+ cfg_globals.h cfg_hosts.h cfg_hosts_util.h cfg_iface.h \
+ cfg_mac_source.h cfg_multi_threading.h cfg_option.h \
+ cfg_option_def.h cfg_rsoo.h cfg_shared_networks.h \
+ cfg_subnets4.h cfg_subnets6.h cfgmgr.h client_class_def.h \
+ config_backend_dhcp4.h config_backend_dhcp6.h \
+ config_backend_dhcp4_mgr.h config_backend_dhcp6_mgr.h \
+ config_backend_pool_dhcp4.h config_backend_pool_dhcp6.h \
+ csv_lease_file4.h csv_lease_file6.h dhcpsrv_exceptions.h \
+ dhcpsrv_messages.h d2_client_cfg.h d2_client_mgr.h db_type.h \
+ dhcp4o6_ipc.h dhcpsrv_log.h flq_allocation_state.h \
+ flq_allocator.h host.h host_container.h \
+ host_data_source_factory.h hosts_messages.h host_mgr.h \
+ hosts_log.h ip_range.h ip_range_permutation.h \
+ iterative_allocation_state.h iterative_allocator.h \
+ key_from_key.h lease.h lease_file_loader.h lease_file_stats.h \
+ lease_mgr.h lease_mgr_factory.h memfile_lease_limits.h \
+ memfile_lease_mgr.h memfile_lease_storage.h ncr_generator.h \
+ network.h network_state.h tracking_lease_mgr.h pool.h \
+ random_allocation_state.h random_allocator.h \
+ resource_handler.h sanity_checker.h shared_network.h \
+ srv_config.h subnet.h subnet_id.h subnet_selector.h \
+ timer_mgr.h utils.h writable_host_data_source.h \
+ mysql_host_data_source.h mysql_lease_mgr.h \
+ pgsql_host_data_source.h pgsql_lease_mgr.h
+HEADERS = $(libkea_dhcpsrv_include_HEADERS) \
+ $(libkea_dhcpsrv_parsers_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . testutils tests
+
+# DATA_DIR is the directory where to put default CSV files and the DHCPv6
+# server ID file (i.e. the file where the server finds its DUID at startup).
+dhcp_data_dir = @localstatedir@/lib/@PACKAGE@
+kea_lfc_location = @prefix@/sbin/kea-lfc
+# Set location of the kea-lfc binary.
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDHCP_DATA_DIR="\"$(dhcp_data_dir)\"" \
+ -DTOP_BUILDDIR="\"$(top_builddir)\"" \
+ -DKEA_LFC_EXECUTABLE="\"$(kea_lfc_location)\"" \
+ $(BOOST_INCLUDES) $(am__append_1) $(am__append_2)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# The files in the subfolder must be explicitly specified here so
+# as they are copied to the distribution. The other option would
+# be to specify a whole 'parsers' folder here but that would also
+# copy all other files, e.g. gitignore, .git etc.
+# Whenever new file is added to the parsers folder, it must be
+# added here.
+
+# Devel guide diagrams
+
+# The message file should be in the distribution
+
+# Distribute backend documentation
+# Database schema creation script moved to src/bin/admin
+EXTRA_DIST = parsers/client_class_def_parser.cc \
+ parsers/client_class_def_parser.h parsers/dhcp_parsers.cc \
+ parsers/dhcp_parsers.h parsers/expiration_config_parser.cc \
+ parsers/expiration_config_parser.h \
+ parsers/host_reservation_parser.cc \
+ parsers/host_reservation_parser.h \
+ parsers/host_reservations_list_parser.h \
+ parsers/ifaces_config_parser.cc parsers/ifaces_config_parser.h \
+ parsers/multi_threading_config_parser.cc \
+ parsers/multi_threading_config_parser.h \
+ parsers/option_data_parser.h parsers/sanity_checks_parser.cc \
+ parsers/sanity_checks_parser.h parsers/simple_parser4.cc \
+ parsers/simple_parser4.h parsers/simple_parser6.cc \
+ parsers/simple_parser6.h parsers/dhcp_queue_control_parser.cc \
+ parsers/dhcp_queue_control_parser.h \
+ images/pgsql_host_data_source.svg alloc_engine_messages.mes \
+ dhcpsrv_messages.mes hosts_messages.mes fuzz_messages.mes \
+ database_backends.dox libdhcpsrv.dox
+# Remove CSV files created by the CSVLeaseFile6 and CSVLeaseFile4 unit tests.
+CLEANFILES = *.gcno *.gcda *.csv
+lib_LTLIBRARIES = libkea-dhcpsrv.la
+
+# Configuration parsers
+libkea_dhcpsrv_la_SOURCES = allocation_state.cc allocation_state.h \
+ alloc_engine.cc alloc_engine.h alloc_engine_log.cc \
+ alloc_engine_log.h alloc_engine_messages.h \
+ alloc_engine_messages.cc allocator.h allocator.cc \
+ base_host_data_source.h cache_host_data_source.h \
+ callout_handle_store.h cb_ctl_dhcp.h cb_ctl_dhcp4.cc \
+ cb_ctl_dhcp4.h cb_ctl_dhcp6.cc cb_ctl_dhcp6.h cfg_4o6.cc \
+ cfg_4o6.h cfg_consistency.cc cfg_consistency.h \
+ cfg_db_access.cc cfg_db_access.h cfg_duid.cc cfg_duid.h \
+ cfg_globals.cc cfg_globals.h cfg_hosts.cc cfg_hosts.h \
+ cfg_hosts_util.cc cfg_hosts_util.h cfg_iface.cc cfg_iface.h \
+ cfg_expiration.cc cfg_expiration.h cfg_host_operations.cc \
+ cfg_host_operations.h cfg_option.cc cfg_option.h \
+ cfg_option_def.cc cfg_option_def.h cfg_rsoo.cc cfg_rsoo.h \
+ cfg_shared_networks.cc cfg_shared_networks.h cfg_subnets4.cc \
+ cfg_subnets4.h cfg_subnets6.cc cfg_subnets6.h \
+ cfg_mac_source.cc cfg_mac_source.h cfg_multi_threading.cc \
+ cfg_multi_threading.h cfgmgr.cc cfgmgr.h client_class_def.cc \
+ client_class_def.h config_backend_dhcp4.h \
+ config_backend_pool_dhcp4.cc config_backend_pool_dhcp4.h \
+ config_backend_dhcp4_mgr.cc config_backend_dhcp4_mgr.h \
+ config_backend_dhcp6.h config_backend_pool_dhcp6.cc \
+ config_backend_pool_dhcp6.h config_backend_dhcp6_mgr.cc \
+ config_backend_dhcp6_mgr.h csv_lease_file4.cc \
+ csv_lease_file4.h csv_lease_file6.cc csv_lease_file6.h \
+ d2_client_cfg.cc d2_client_cfg.h d2_client_mgr.cc \
+ d2_client_mgr.h db_type.h dhcp4o6_ipc.cc dhcp4o6_ipc.h \
+ dhcpsrv_exceptions.h dhcpsrv_log.cc dhcpsrv_log.h \
+ dhcpsrv_messages.h dhcpsrv_messages.cc flq_allocation_state.cc \
+ flq_allocation_state.h flq_allocator.cc flq_allocator.h \
+ host.cc host.h host_container.h host_data_source_factory.cc \
+ host_data_source_factory.h host_mgr.cc host_mgr.h hosts_log.cc \
+ hosts_log.h hosts_messages.h hosts_messages.cc ip_range.h \
+ ip_range.cc ip_range_permutation.h ip_range_permutation.cc \
+ iterative_allocation_state.cc iterative_allocation_state.h \
+ iterative_allocator.cc iterative_allocator.h key_from_key.h \
+ lease.cc lease.h lease_file_loader.h lease_file_stats.h \
+ lease_mgr.cc lease_mgr.h lease_mgr_factory.cc \
+ lease_mgr_factory.h memfile_lease_limits.cc \
+ memfile_lease_limits.h memfile_lease_mgr.cc \
+ memfile_lease_mgr.h memfile_lease_storage.h $(am__append_3) \
+ ncr_generator.cc ncr_generator.h network.cc network.h \
+ network_state.cc network_state.h $(am__append_4) pool.cc \
+ pool.h random_allocation_state.cc random_allocation_state.h \
+ random_allocator.cc random_allocator.h resource_handler.cc \
+ resource_handler.h sanity_checker.cc sanity_checker.h \
+ shared_network.cc shared_network.h srv_config.cc srv_config.h \
+ subnet.cc subnet.h subnet_id.h subnet_selector.h timer_mgr.cc \
+ timer_mgr.h tracking_lease_mgr.cc tracking_lease_mgr.h utils.h \
+ writable_host_data_source.h parsers/base_network_parser.cc \
+ parsers/base_network_parser.h \
+ parsers/client_class_def_parser.cc \
+ parsers/client_class_def_parser.h parsers/dhcp_parsers.cc \
+ parsers/dhcp_parsers.h parsers/duid_config_parser.cc \
+ parsers/duid_config_parser.h \
+ parsers/expiration_config_parser.cc \
+ parsers/expiration_config_parser.h \
+ parsers/host_reservation_parser.cc \
+ parsers/host_reservation_parser.h \
+ parsers/host_reservations_list_parser.h \
+ parsers/ifaces_config_parser.cc parsers/ifaces_config_parser.h \
+ parsers/multi_threading_config_parser.cc \
+ parsers/multi_threading_config_parser.h \
+ parsers/option_data_parser.cc parsers/option_data_parser.h \
+ parsers/dhcp_queue_control_parser.cc \
+ parsers/dhcp_queue_control_parser.h \
+ parsers/sanity_checks_parser.cc parsers/sanity_checks_parser.h \
+ parsers/shared_network_parser.cc \
+ parsers/shared_network_parser.h \
+ parsers/shared_networks_list_parser.h \
+ parsers/simple_parser4.cc parsers/simple_parser4.h \
+ parsers/simple_parser6.cc parsers/simple_parser6.h \
+ $(am__append_5)
+libkea_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_dhcpsrv_la_LIBADD = \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/eval/libkea-eval.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la $(am__append_6) \
+ $(am__append_7) \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+libkea_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 90:0:0 \
+ $(CRYPTO_LDFLAGS) $(am__append_8) $(am__append_9)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_dhcpsrv_includedir = $(pkgincludedir)/dhcpsrv
+libkea_dhcpsrv_include_HEADERS = allocation_state.h alloc_engine.h \
+ alloc_engine_log.h alloc_engine_messages.h allocator.h \
+ base_host_data_source.h cache_host_data_source.h \
+ callout_handle_store.h cb_ctl_dhcp.h cb_ctl_dhcp4.h \
+ cb_ctl_dhcp6.h cfg_4o6.h cfg_consistency.h cfg_db_access.h \
+ cfg_duid.h cfg_expiration.h cfg_host_operations.h \
+ cfg_globals.h cfg_hosts.h cfg_hosts_util.h cfg_iface.h \
+ cfg_mac_source.h cfg_multi_threading.h cfg_option.h \
+ cfg_option_def.h cfg_rsoo.h cfg_shared_networks.h \
+ cfg_subnets4.h cfg_subnets6.h cfgmgr.h client_class_def.h \
+ config_backend_dhcp4.h config_backend_dhcp6.h \
+ config_backend_dhcp4_mgr.h config_backend_dhcp6_mgr.h \
+ config_backend_pool_dhcp4.h config_backend_pool_dhcp6.h \
+ csv_lease_file4.h csv_lease_file6.h dhcpsrv_exceptions.h \
+ dhcpsrv_messages.h d2_client_cfg.h d2_client_mgr.h db_type.h \
+ dhcp4o6_ipc.h dhcpsrv_log.h flq_allocation_state.h \
+ flq_allocator.h host.h host_container.h \
+ host_data_source_factory.h hosts_messages.h host_mgr.h \
+ hosts_log.h ip_range.h ip_range_permutation.h \
+ iterative_allocation_state.h iterative_allocator.h \
+ key_from_key.h lease.h lease_file_loader.h lease_file_stats.h \
+ lease_mgr.h lease_mgr_factory.h memfile_lease_limits.h \
+ memfile_lease_mgr.h memfile_lease_storage.h ncr_generator.h \
+ network.h network_state.h tracking_lease_mgr.h pool.h \
+ random_allocation_state.h random_allocator.h \
+ resource_handler.h sanity_checker.h shared_network.h \
+ srv_config.h subnet.h subnet_id.h subnet_selector.h \
+ timer_mgr.h utils.h writable_host_data_source.h \
+ $(am__append_10) $(am__append_11)
+
+# Specify parsers' headers for copying into installation directory tree.
+libkea_dhcpsrv_parsers_includedir = $(pkgincludedir)/dhcpsrv/parsers
+libkea_dhcpsrv_parsers_include_HEADERS = \
+ parsers/base_network_parser.h \
+ parsers/client_class_def_parser.h \
+ parsers/dhcp_parsers.h \
+ parsers/duid_config_parser.h \
+ parsers/expiration_config_parser.h \
+ parsers/host_reservation_parser.h \
+ parsers/host_reservations_list_parser.h \
+ parsers/ifaces_config_parser.h \
+ parsers/multi_threading_config_parser.h \
+ parsers/option_data_parser.h \
+ parsers/dhcp_queue_control_parser.h \
+ parsers/sanity_checks_parser.h \
+ parsers/shared_network_parser.h \
+ parsers/shared_networks_list_parser.h \
+ parsers/simple_parser4.h \
+ parsers/simple_parser6.h
+
+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/dhcpsrv/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcpsrv/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}; \
+ }
+parsers/$(am__dirstamp):
+ @$(MKDIR_P) parsers
+ @: > parsers/$(am__dirstamp)
+parsers/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) parsers/$(DEPDIR)
+ @: > parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-base_network_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-client_class_def_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-dhcp_parsers.lo: parsers/$(am__dirstamp) \
+ parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-duid_config_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-expiration_config_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-host_reservation_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-ifaces_config_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-multi_threading_config_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-option_data_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-dhcp_queue_control_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-sanity_checks_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-shared_network_parser.lo: \
+ parsers/$(am__dirstamp) parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-simple_parser4.lo: parsers/$(am__dirstamp) \
+ parsers/$(DEPDIR)/$(am__dirstamp)
+parsers/libkea_dhcpsrv_la-simple_parser6.lo: parsers/$(am__dirstamp) \
+ parsers/$(DEPDIR)/$(am__dirstamp)
+
+libkea-dhcpsrv.la: $(libkea_dhcpsrv_la_OBJECTS) $(libkea_dhcpsrv_la_DEPENDENCIES) $(EXTRA_libkea_dhcpsrv_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_dhcpsrv_la_LINK) -rpath $(libdir) $(libkea_dhcpsrv_la_OBJECTS) $(libkea_dhcpsrv_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f parsers/*.$(OBJEXT)
+ -rm -f parsers/*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-allocation_state.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-allocator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_4o6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_consistency.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_db_access.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_duid.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_expiration.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_globals.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_host_operations.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts_util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_iface.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_mac_source.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_multi_threading.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option_def.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_rsoo.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_shared_networks.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-cfgmgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-client_class_def.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp4_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp6_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_cfg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-dhcp4o6_ipc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocation_state.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-fuzz.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-host.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-host_data_source_factory.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-host_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-hosts_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-hosts_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-ip_range.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-ip_range_permutation.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocation_state.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-lease.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr_factory.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_limits.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-mysql_host_data_source.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-mysql_lease_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-ncr_generator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-network.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-network_state.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_host_data_source.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_lease_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-random_allocation_state.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-random_allocator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-resource_handler.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-sanity_checker.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-shared_network.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-srv_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-subnet.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-timer_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dhcpsrv_la-tracking_lease_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-base_network_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-client_class_def_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_parsers.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_queue_control_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-duid_config_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-expiration_config_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-host_reservation_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-ifaces_config_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-multi_threading_config_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-option_data_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-sanity_checks_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-shared_network_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser6.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_dhcpsrv_la-allocation_state.lo: allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-allocation_state.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-allocation_state.Tpo -c -o libkea_dhcpsrv_la-allocation_state.lo `test -f 'allocation_state.cc' || echo '$(srcdir)/'`allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-allocation_state.Tpo $(DEPDIR)/libkea_dhcpsrv_la-allocation_state.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='allocation_state.cc' object='libkea_dhcpsrv_la-allocation_state.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-allocation_state.lo `test -f 'allocation_state.cc' || echo '$(srcdir)/'`allocation_state.cc
+
+libkea_dhcpsrv_la-alloc_engine.lo: alloc_engine.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-alloc_engine.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine.Tpo -c -o libkea_dhcpsrv_la-alloc_engine.lo `test -f 'alloc_engine.cc' || echo '$(srcdir)/'`alloc_engine.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine.Tpo $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine.cc' object='libkea_dhcpsrv_la-alloc_engine.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-alloc_engine.lo `test -f 'alloc_engine.cc' || echo '$(srcdir)/'`alloc_engine.cc
+
+libkea_dhcpsrv_la-alloc_engine_log.lo: alloc_engine_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-alloc_engine_log.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_log.Tpo -c -o libkea_dhcpsrv_la-alloc_engine_log.lo `test -f 'alloc_engine_log.cc' || echo '$(srcdir)/'`alloc_engine_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_log.Tpo $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_log.cc' object='libkea_dhcpsrv_la-alloc_engine_log.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-alloc_engine_log.lo `test -f 'alloc_engine_log.cc' || echo '$(srcdir)/'`alloc_engine_log.cc
+
+libkea_dhcpsrv_la-alloc_engine_messages.lo: alloc_engine_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-alloc_engine_messages.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_messages.Tpo -c -o libkea_dhcpsrv_la-alloc_engine_messages.lo `test -f 'alloc_engine_messages.cc' || echo '$(srcdir)/'`alloc_engine_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_messages.Tpo $(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_messages.cc' object='libkea_dhcpsrv_la-alloc_engine_messages.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-alloc_engine_messages.lo `test -f 'alloc_engine_messages.cc' || echo '$(srcdir)/'`alloc_engine_messages.cc
+
+libkea_dhcpsrv_la-allocator.lo: allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-allocator.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-allocator.Tpo -c -o libkea_dhcpsrv_la-allocator.lo `test -f 'allocator.cc' || echo '$(srcdir)/'`allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-allocator.Tpo $(DEPDIR)/libkea_dhcpsrv_la-allocator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='allocator.cc' object='libkea_dhcpsrv_la-allocator.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-allocator.lo `test -f 'allocator.cc' || echo '$(srcdir)/'`allocator.cc
+
+libkea_dhcpsrv_la-cb_ctl_dhcp4.lo: cb_ctl_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cb_ctl_dhcp4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp4.Tpo -c -o libkea_dhcpsrv_la-cb_ctl_dhcp4.lo `test -f 'cb_ctl_dhcp4.cc' || echo '$(srcdir)/'`cb_ctl_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp4.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cb_ctl_dhcp4.cc' object='libkea_dhcpsrv_la-cb_ctl_dhcp4.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cb_ctl_dhcp4.lo `test -f 'cb_ctl_dhcp4.cc' || echo '$(srcdir)/'`cb_ctl_dhcp4.cc
+
+libkea_dhcpsrv_la-cb_ctl_dhcp6.lo: cb_ctl_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cb_ctl_dhcp6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp6.Tpo -c -o libkea_dhcpsrv_la-cb_ctl_dhcp6.lo `test -f 'cb_ctl_dhcp6.cc' || echo '$(srcdir)/'`cb_ctl_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp6.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cb_ctl_dhcp6.cc' object='libkea_dhcpsrv_la-cb_ctl_dhcp6.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cb_ctl_dhcp6.lo `test -f 'cb_ctl_dhcp6.cc' || echo '$(srcdir)/'`cb_ctl_dhcp6.cc
+
+libkea_dhcpsrv_la-cfg_4o6.lo: cfg_4o6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_4o6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_4o6.Tpo -c -o libkea_dhcpsrv_la-cfg_4o6.lo `test -f 'cfg_4o6.cc' || echo '$(srcdir)/'`cfg_4o6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_4o6.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_4o6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_4o6.cc' object='libkea_dhcpsrv_la-cfg_4o6.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_4o6.lo `test -f 'cfg_4o6.cc' || echo '$(srcdir)/'`cfg_4o6.cc
+
+libkea_dhcpsrv_la-cfg_consistency.lo: cfg_consistency.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_consistency.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_consistency.Tpo -c -o libkea_dhcpsrv_la-cfg_consistency.lo `test -f 'cfg_consistency.cc' || echo '$(srcdir)/'`cfg_consistency.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_consistency.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_consistency.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_consistency.cc' object='libkea_dhcpsrv_la-cfg_consistency.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_consistency.lo `test -f 'cfg_consistency.cc' || echo '$(srcdir)/'`cfg_consistency.cc
+
+libkea_dhcpsrv_la-cfg_db_access.lo: cfg_db_access.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_db_access.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_db_access.Tpo -c -o libkea_dhcpsrv_la-cfg_db_access.lo `test -f 'cfg_db_access.cc' || echo '$(srcdir)/'`cfg_db_access.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_db_access.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_db_access.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_db_access.cc' object='libkea_dhcpsrv_la-cfg_db_access.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_db_access.lo `test -f 'cfg_db_access.cc' || echo '$(srcdir)/'`cfg_db_access.cc
+
+libkea_dhcpsrv_la-cfg_duid.lo: cfg_duid.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_duid.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_duid.Tpo -c -o libkea_dhcpsrv_la-cfg_duid.lo `test -f 'cfg_duid.cc' || echo '$(srcdir)/'`cfg_duid.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_duid.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_duid.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_duid.cc' object='libkea_dhcpsrv_la-cfg_duid.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_duid.lo `test -f 'cfg_duid.cc' || echo '$(srcdir)/'`cfg_duid.cc
+
+libkea_dhcpsrv_la-cfg_globals.lo: cfg_globals.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_globals.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_globals.Tpo -c -o libkea_dhcpsrv_la-cfg_globals.lo `test -f 'cfg_globals.cc' || echo '$(srcdir)/'`cfg_globals.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_globals.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_globals.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_globals.cc' object='libkea_dhcpsrv_la-cfg_globals.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_globals.lo `test -f 'cfg_globals.cc' || echo '$(srcdir)/'`cfg_globals.cc
+
+libkea_dhcpsrv_la-cfg_hosts.lo: cfg_hosts.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_hosts.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts.Tpo -c -o libkea_dhcpsrv_la-cfg_hosts.lo `test -f 'cfg_hosts.cc' || echo '$(srcdir)/'`cfg_hosts.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_hosts.cc' object='libkea_dhcpsrv_la-cfg_hosts.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_hosts.lo `test -f 'cfg_hosts.cc' || echo '$(srcdir)/'`cfg_hosts.cc
+
+libkea_dhcpsrv_la-cfg_hosts_util.lo: cfg_hosts_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_hosts_util.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts_util.Tpo -c -o libkea_dhcpsrv_la-cfg_hosts_util.lo `test -f 'cfg_hosts_util.cc' || echo '$(srcdir)/'`cfg_hosts_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts_util.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts_util.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_hosts_util.cc' object='libkea_dhcpsrv_la-cfg_hosts_util.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_hosts_util.lo `test -f 'cfg_hosts_util.cc' || echo '$(srcdir)/'`cfg_hosts_util.cc
+
+libkea_dhcpsrv_la-cfg_iface.lo: cfg_iface.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_iface.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_iface.Tpo -c -o libkea_dhcpsrv_la-cfg_iface.lo `test -f 'cfg_iface.cc' || echo '$(srcdir)/'`cfg_iface.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_iface.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_iface.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_iface.cc' object='libkea_dhcpsrv_la-cfg_iface.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_iface.lo `test -f 'cfg_iface.cc' || echo '$(srcdir)/'`cfg_iface.cc
+
+libkea_dhcpsrv_la-cfg_expiration.lo: cfg_expiration.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_expiration.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_expiration.Tpo -c -o libkea_dhcpsrv_la-cfg_expiration.lo `test -f 'cfg_expiration.cc' || echo '$(srcdir)/'`cfg_expiration.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_expiration.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_expiration.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_expiration.cc' object='libkea_dhcpsrv_la-cfg_expiration.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_expiration.lo `test -f 'cfg_expiration.cc' || echo '$(srcdir)/'`cfg_expiration.cc
+
+libkea_dhcpsrv_la-cfg_host_operations.lo: cfg_host_operations.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_host_operations.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_host_operations.Tpo -c -o libkea_dhcpsrv_la-cfg_host_operations.lo `test -f 'cfg_host_operations.cc' || echo '$(srcdir)/'`cfg_host_operations.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_host_operations.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_host_operations.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_host_operations.cc' object='libkea_dhcpsrv_la-cfg_host_operations.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_host_operations.lo `test -f 'cfg_host_operations.cc' || echo '$(srcdir)/'`cfg_host_operations.cc
+
+libkea_dhcpsrv_la-cfg_option.lo: cfg_option.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_option.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_option.Tpo -c -o libkea_dhcpsrv_la-cfg_option.lo `test -f 'cfg_option.cc' || echo '$(srcdir)/'`cfg_option.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_option.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_option.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_option.cc' object='libkea_dhcpsrv_la-cfg_option.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_option.lo `test -f 'cfg_option.cc' || echo '$(srcdir)/'`cfg_option.cc
+
+libkea_dhcpsrv_la-cfg_option_def.lo: cfg_option_def.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_option_def.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_option_def.Tpo -c -o libkea_dhcpsrv_la-cfg_option_def.lo `test -f 'cfg_option_def.cc' || echo '$(srcdir)/'`cfg_option_def.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_option_def.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_option_def.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_option_def.cc' object='libkea_dhcpsrv_la-cfg_option_def.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_option_def.lo `test -f 'cfg_option_def.cc' || echo '$(srcdir)/'`cfg_option_def.cc
+
+libkea_dhcpsrv_la-cfg_rsoo.lo: cfg_rsoo.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_rsoo.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_rsoo.Tpo -c -o libkea_dhcpsrv_la-cfg_rsoo.lo `test -f 'cfg_rsoo.cc' || echo '$(srcdir)/'`cfg_rsoo.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_rsoo.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_rsoo.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_rsoo.cc' object='libkea_dhcpsrv_la-cfg_rsoo.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_rsoo.lo `test -f 'cfg_rsoo.cc' || echo '$(srcdir)/'`cfg_rsoo.cc
+
+libkea_dhcpsrv_la-cfg_shared_networks.lo: cfg_shared_networks.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_shared_networks.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_shared_networks.Tpo -c -o libkea_dhcpsrv_la-cfg_shared_networks.lo `test -f 'cfg_shared_networks.cc' || echo '$(srcdir)/'`cfg_shared_networks.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_shared_networks.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_shared_networks.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_shared_networks.cc' object='libkea_dhcpsrv_la-cfg_shared_networks.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_shared_networks.lo `test -f 'cfg_shared_networks.cc' || echo '$(srcdir)/'`cfg_shared_networks.cc
+
+libkea_dhcpsrv_la-cfg_subnets4.lo: cfg_subnets4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_subnets4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets4.Tpo -c -o libkea_dhcpsrv_la-cfg_subnets4.lo `test -f 'cfg_subnets4.cc' || echo '$(srcdir)/'`cfg_subnets4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets4.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_subnets4.cc' object='libkea_dhcpsrv_la-cfg_subnets4.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_subnets4.lo `test -f 'cfg_subnets4.cc' || echo '$(srcdir)/'`cfg_subnets4.cc
+
+libkea_dhcpsrv_la-cfg_subnets6.lo: cfg_subnets6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_subnets6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets6.Tpo -c -o libkea_dhcpsrv_la-cfg_subnets6.lo `test -f 'cfg_subnets6.cc' || echo '$(srcdir)/'`cfg_subnets6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets6.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_subnets6.cc' object='libkea_dhcpsrv_la-cfg_subnets6.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_subnets6.lo `test -f 'cfg_subnets6.cc' || echo '$(srcdir)/'`cfg_subnets6.cc
+
+libkea_dhcpsrv_la-cfg_mac_source.lo: cfg_mac_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_mac_source.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_mac_source.Tpo -c -o libkea_dhcpsrv_la-cfg_mac_source.lo `test -f 'cfg_mac_source.cc' || echo '$(srcdir)/'`cfg_mac_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_mac_source.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_mac_source.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_mac_source.cc' object='libkea_dhcpsrv_la-cfg_mac_source.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_mac_source.lo `test -f 'cfg_mac_source.cc' || echo '$(srcdir)/'`cfg_mac_source.cc
+
+libkea_dhcpsrv_la-cfg_multi_threading.lo: cfg_multi_threading.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfg_multi_threading.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfg_multi_threading.Tpo -c -o libkea_dhcpsrv_la-cfg_multi_threading.lo `test -f 'cfg_multi_threading.cc' || echo '$(srcdir)/'`cfg_multi_threading.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfg_multi_threading.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfg_multi_threading.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_multi_threading.cc' object='libkea_dhcpsrv_la-cfg_multi_threading.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfg_multi_threading.lo `test -f 'cfg_multi_threading.cc' || echo '$(srcdir)/'`cfg_multi_threading.cc
+
+libkea_dhcpsrv_la-cfgmgr.lo: cfgmgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-cfgmgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-cfgmgr.Tpo -c -o libkea_dhcpsrv_la-cfgmgr.lo `test -f 'cfgmgr.cc' || echo '$(srcdir)/'`cfgmgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-cfgmgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-cfgmgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfgmgr.cc' object='libkea_dhcpsrv_la-cfgmgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-cfgmgr.lo `test -f 'cfgmgr.cc' || echo '$(srcdir)/'`cfgmgr.cc
+
+libkea_dhcpsrv_la-client_class_def.lo: client_class_def.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-client_class_def.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-client_class_def.Tpo -c -o libkea_dhcpsrv_la-client_class_def.lo `test -f 'client_class_def.cc' || echo '$(srcdir)/'`client_class_def.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-client_class_def.Tpo $(DEPDIR)/libkea_dhcpsrv_la-client_class_def.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_class_def.cc' object='libkea_dhcpsrv_la-client_class_def.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-client_class_def.lo `test -f 'client_class_def.cc' || echo '$(srcdir)/'`client_class_def.cc
+
+libkea_dhcpsrv_la-config_backend_pool_dhcp4.lo: config_backend_pool_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-config_backend_pool_dhcp4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp4.Tpo -c -o libkea_dhcpsrv_la-config_backend_pool_dhcp4.lo `test -f 'config_backend_pool_dhcp4.cc' || echo '$(srcdir)/'`config_backend_pool_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp4.Tpo $(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_pool_dhcp4.cc' object='libkea_dhcpsrv_la-config_backend_pool_dhcp4.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-config_backend_pool_dhcp4.lo `test -f 'config_backend_pool_dhcp4.cc' || echo '$(srcdir)/'`config_backend_pool_dhcp4.cc
+
+libkea_dhcpsrv_la-config_backend_dhcp4_mgr.lo: config_backend_dhcp4_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-config_backend_dhcp4_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp4_mgr.Tpo -c -o libkea_dhcpsrv_la-config_backend_dhcp4_mgr.lo `test -f 'config_backend_dhcp4_mgr.cc' || echo '$(srcdir)/'`config_backend_dhcp4_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp4_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp4_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_dhcp4_mgr.cc' object='libkea_dhcpsrv_la-config_backend_dhcp4_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-config_backend_dhcp4_mgr.lo `test -f 'config_backend_dhcp4_mgr.cc' || echo '$(srcdir)/'`config_backend_dhcp4_mgr.cc
+
+libkea_dhcpsrv_la-config_backend_pool_dhcp6.lo: config_backend_pool_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-config_backend_pool_dhcp6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp6.Tpo -c -o libkea_dhcpsrv_la-config_backend_pool_dhcp6.lo `test -f 'config_backend_pool_dhcp6.cc' || echo '$(srcdir)/'`config_backend_pool_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp6.Tpo $(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_pool_dhcp6.cc' object='libkea_dhcpsrv_la-config_backend_pool_dhcp6.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-config_backend_pool_dhcp6.lo `test -f 'config_backend_pool_dhcp6.cc' || echo '$(srcdir)/'`config_backend_pool_dhcp6.cc
+
+libkea_dhcpsrv_la-config_backend_dhcp6_mgr.lo: config_backend_dhcp6_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-config_backend_dhcp6_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp6_mgr.Tpo -c -o libkea_dhcpsrv_la-config_backend_dhcp6_mgr.lo `test -f 'config_backend_dhcp6_mgr.cc' || echo '$(srcdir)/'`config_backend_dhcp6_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp6_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp6_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_backend_dhcp6_mgr.cc' object='libkea_dhcpsrv_la-config_backend_dhcp6_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-config_backend_dhcp6_mgr.lo `test -f 'config_backend_dhcp6_mgr.cc' || echo '$(srcdir)/'`config_backend_dhcp6_mgr.cc
+
+libkea_dhcpsrv_la-csv_lease_file4.lo: csv_lease_file4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-csv_lease_file4.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file4.Tpo -c -o libkea_dhcpsrv_la-csv_lease_file4.lo `test -f 'csv_lease_file4.cc' || echo '$(srcdir)/'`csv_lease_file4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file4.Tpo $(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_lease_file4.cc' object='libkea_dhcpsrv_la-csv_lease_file4.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-csv_lease_file4.lo `test -f 'csv_lease_file4.cc' || echo '$(srcdir)/'`csv_lease_file4.cc
+
+libkea_dhcpsrv_la-csv_lease_file6.lo: csv_lease_file6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-csv_lease_file6.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file6.Tpo -c -o libkea_dhcpsrv_la-csv_lease_file6.lo `test -f 'csv_lease_file6.cc' || echo '$(srcdir)/'`csv_lease_file6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file6.Tpo $(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_lease_file6.cc' object='libkea_dhcpsrv_la-csv_lease_file6.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-csv_lease_file6.lo `test -f 'csv_lease_file6.cc' || echo '$(srcdir)/'`csv_lease_file6.cc
+
+libkea_dhcpsrv_la-d2_client_cfg.lo: d2_client_cfg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-d2_client_cfg.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-d2_client_cfg.Tpo -c -o libkea_dhcpsrv_la-d2_client_cfg.lo `test -f 'd2_client_cfg.cc' || echo '$(srcdir)/'`d2_client_cfg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-d2_client_cfg.Tpo $(DEPDIR)/libkea_dhcpsrv_la-d2_client_cfg.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_client_cfg.cc' object='libkea_dhcpsrv_la-d2_client_cfg.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-d2_client_cfg.lo `test -f 'd2_client_cfg.cc' || echo '$(srcdir)/'`d2_client_cfg.cc
+
+libkea_dhcpsrv_la-d2_client_mgr.lo: d2_client_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-d2_client_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-d2_client_mgr.Tpo -c -o libkea_dhcpsrv_la-d2_client_mgr.lo `test -f 'd2_client_mgr.cc' || echo '$(srcdir)/'`d2_client_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-d2_client_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-d2_client_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_client_mgr.cc' object='libkea_dhcpsrv_la-d2_client_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-d2_client_mgr.lo `test -f 'd2_client_mgr.cc' || echo '$(srcdir)/'`d2_client_mgr.cc
+
+libkea_dhcpsrv_la-dhcp4o6_ipc.lo: dhcp4o6_ipc.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-dhcp4o6_ipc.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-dhcp4o6_ipc.Tpo -c -o libkea_dhcpsrv_la-dhcp4o6_ipc.lo `test -f 'dhcp4o6_ipc.cc' || echo '$(srcdir)/'`dhcp4o6_ipc.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-dhcp4o6_ipc.Tpo $(DEPDIR)/libkea_dhcpsrv_la-dhcp4o6_ipc.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4o6_ipc.cc' object='libkea_dhcpsrv_la-dhcp4o6_ipc.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-dhcp4o6_ipc.lo `test -f 'dhcp4o6_ipc.cc' || echo '$(srcdir)/'`dhcp4o6_ipc.cc
+
+libkea_dhcpsrv_la-dhcpsrv_log.lo: dhcpsrv_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-dhcpsrv_log.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_log.Tpo -c -o libkea_dhcpsrv_la-dhcpsrv_log.lo `test -f 'dhcpsrv_log.cc' || echo '$(srcdir)/'`dhcpsrv_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_log.Tpo $(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcpsrv_log.cc' object='libkea_dhcpsrv_la-dhcpsrv_log.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-dhcpsrv_log.lo `test -f 'dhcpsrv_log.cc' || echo '$(srcdir)/'`dhcpsrv_log.cc
+
+libkea_dhcpsrv_la-dhcpsrv_messages.lo: dhcpsrv_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-dhcpsrv_messages.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_messages.Tpo -c -o libkea_dhcpsrv_la-dhcpsrv_messages.lo `test -f 'dhcpsrv_messages.cc' || echo '$(srcdir)/'`dhcpsrv_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_messages.Tpo $(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcpsrv_messages.cc' object='libkea_dhcpsrv_la-dhcpsrv_messages.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-dhcpsrv_messages.lo `test -f 'dhcpsrv_messages.cc' || echo '$(srcdir)/'`dhcpsrv_messages.cc
+
+libkea_dhcpsrv_la-flq_allocation_state.lo: flq_allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-flq_allocation_state.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-flq_allocation_state.Tpo -c -o libkea_dhcpsrv_la-flq_allocation_state.lo `test -f 'flq_allocation_state.cc' || echo '$(srcdir)/'`flq_allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-flq_allocation_state.Tpo $(DEPDIR)/libkea_dhcpsrv_la-flq_allocation_state.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='flq_allocation_state.cc' object='libkea_dhcpsrv_la-flq_allocation_state.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-flq_allocation_state.lo `test -f 'flq_allocation_state.cc' || echo '$(srcdir)/'`flq_allocation_state.cc
+
+libkea_dhcpsrv_la-flq_allocator.lo: flq_allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-flq_allocator.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-flq_allocator.Tpo -c -o libkea_dhcpsrv_la-flq_allocator.lo `test -f 'flq_allocator.cc' || echo '$(srcdir)/'`flq_allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-flq_allocator.Tpo $(DEPDIR)/libkea_dhcpsrv_la-flq_allocator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='flq_allocator.cc' object='libkea_dhcpsrv_la-flq_allocator.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-flq_allocator.lo `test -f 'flq_allocator.cc' || echo '$(srcdir)/'`flq_allocator.cc
+
+libkea_dhcpsrv_la-host.lo: host.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-host.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-host.Tpo -c -o libkea_dhcpsrv_la-host.lo `test -f 'host.cc' || echo '$(srcdir)/'`host.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-host.Tpo $(DEPDIR)/libkea_dhcpsrv_la-host.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host.cc' object='libkea_dhcpsrv_la-host.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-host.lo `test -f 'host.cc' || echo '$(srcdir)/'`host.cc
+
+libkea_dhcpsrv_la-host_data_source_factory.lo: host_data_source_factory.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-host_data_source_factory.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-host_data_source_factory.Tpo -c -o libkea_dhcpsrv_la-host_data_source_factory.lo `test -f 'host_data_source_factory.cc' || echo '$(srcdir)/'`host_data_source_factory.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-host_data_source_factory.Tpo $(DEPDIR)/libkea_dhcpsrv_la-host_data_source_factory.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_data_source_factory.cc' object='libkea_dhcpsrv_la-host_data_source_factory.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-host_data_source_factory.lo `test -f 'host_data_source_factory.cc' || echo '$(srcdir)/'`host_data_source_factory.cc
+
+libkea_dhcpsrv_la-host_mgr.lo: host_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-host_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-host_mgr.Tpo -c -o libkea_dhcpsrv_la-host_mgr.lo `test -f 'host_mgr.cc' || echo '$(srcdir)/'`host_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-host_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-host_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_mgr.cc' object='libkea_dhcpsrv_la-host_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-host_mgr.lo `test -f 'host_mgr.cc' || echo '$(srcdir)/'`host_mgr.cc
+
+libkea_dhcpsrv_la-hosts_log.lo: hosts_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-hosts_log.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-hosts_log.Tpo -c -o libkea_dhcpsrv_la-hosts_log.lo `test -f 'hosts_log.cc' || echo '$(srcdir)/'`hosts_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-hosts_log.Tpo $(DEPDIR)/libkea_dhcpsrv_la-hosts_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hosts_log.cc' object='libkea_dhcpsrv_la-hosts_log.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-hosts_log.lo `test -f 'hosts_log.cc' || echo '$(srcdir)/'`hosts_log.cc
+
+libkea_dhcpsrv_la-hosts_messages.lo: hosts_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-hosts_messages.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-hosts_messages.Tpo -c -o libkea_dhcpsrv_la-hosts_messages.lo `test -f 'hosts_messages.cc' || echo '$(srcdir)/'`hosts_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-hosts_messages.Tpo $(DEPDIR)/libkea_dhcpsrv_la-hosts_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hosts_messages.cc' object='libkea_dhcpsrv_la-hosts_messages.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-hosts_messages.lo `test -f 'hosts_messages.cc' || echo '$(srcdir)/'`hosts_messages.cc
+
+libkea_dhcpsrv_la-ip_range.lo: ip_range.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-ip_range.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-ip_range.Tpo -c -o libkea_dhcpsrv_la-ip_range.lo `test -f 'ip_range.cc' || echo '$(srcdir)/'`ip_range.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-ip_range.Tpo $(DEPDIR)/libkea_dhcpsrv_la-ip_range.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_range.cc' object='libkea_dhcpsrv_la-ip_range.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-ip_range.lo `test -f 'ip_range.cc' || echo '$(srcdir)/'`ip_range.cc
+
+libkea_dhcpsrv_la-ip_range_permutation.lo: ip_range_permutation.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-ip_range_permutation.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-ip_range_permutation.Tpo -c -o libkea_dhcpsrv_la-ip_range_permutation.lo `test -f 'ip_range_permutation.cc' || echo '$(srcdir)/'`ip_range_permutation.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-ip_range_permutation.Tpo $(DEPDIR)/libkea_dhcpsrv_la-ip_range_permutation.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_range_permutation.cc' object='libkea_dhcpsrv_la-ip_range_permutation.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-ip_range_permutation.lo `test -f 'ip_range_permutation.cc' || echo '$(srcdir)/'`ip_range_permutation.cc
+
+libkea_dhcpsrv_la-iterative_allocation_state.lo: iterative_allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-iterative_allocation_state.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-iterative_allocation_state.Tpo -c -o libkea_dhcpsrv_la-iterative_allocation_state.lo `test -f 'iterative_allocation_state.cc' || echo '$(srcdir)/'`iterative_allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-iterative_allocation_state.Tpo $(DEPDIR)/libkea_dhcpsrv_la-iterative_allocation_state.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iterative_allocation_state.cc' object='libkea_dhcpsrv_la-iterative_allocation_state.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-iterative_allocation_state.lo `test -f 'iterative_allocation_state.cc' || echo '$(srcdir)/'`iterative_allocation_state.cc
+
+libkea_dhcpsrv_la-iterative_allocator.lo: iterative_allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-iterative_allocator.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-iterative_allocator.Tpo -c -o libkea_dhcpsrv_la-iterative_allocator.lo `test -f 'iterative_allocator.cc' || echo '$(srcdir)/'`iterative_allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-iterative_allocator.Tpo $(DEPDIR)/libkea_dhcpsrv_la-iterative_allocator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iterative_allocator.cc' object='libkea_dhcpsrv_la-iterative_allocator.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-iterative_allocator.lo `test -f 'iterative_allocator.cc' || echo '$(srcdir)/'`iterative_allocator.cc
+
+libkea_dhcpsrv_la-lease.lo: lease.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-lease.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-lease.Tpo -c -o libkea_dhcpsrv_la-lease.lo `test -f 'lease.cc' || echo '$(srcdir)/'`lease.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-lease.Tpo $(DEPDIR)/libkea_dhcpsrv_la-lease.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease.cc' object='libkea_dhcpsrv_la-lease.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-lease.lo `test -f 'lease.cc' || echo '$(srcdir)/'`lease.cc
+
+libkea_dhcpsrv_la-lease_mgr.lo: lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-lease_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-lease_mgr.Tpo -c -o libkea_dhcpsrv_la-lease_mgr.lo `test -f 'lease_mgr.cc' || echo '$(srcdir)/'`lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-lease_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-lease_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_mgr.cc' object='libkea_dhcpsrv_la-lease_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-lease_mgr.lo `test -f 'lease_mgr.cc' || echo '$(srcdir)/'`lease_mgr.cc
+
+libkea_dhcpsrv_la-lease_mgr_factory.lo: lease_mgr_factory.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-lease_mgr_factory.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-lease_mgr_factory.Tpo -c -o libkea_dhcpsrv_la-lease_mgr_factory.lo `test -f 'lease_mgr_factory.cc' || echo '$(srcdir)/'`lease_mgr_factory.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-lease_mgr_factory.Tpo $(DEPDIR)/libkea_dhcpsrv_la-lease_mgr_factory.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_mgr_factory.cc' object='libkea_dhcpsrv_la-lease_mgr_factory.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-lease_mgr_factory.lo `test -f 'lease_mgr_factory.cc' || echo '$(srcdir)/'`lease_mgr_factory.cc
+
+libkea_dhcpsrv_la-memfile_lease_limits.lo: memfile_lease_limits.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-memfile_lease_limits.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_limits.Tpo -c -o libkea_dhcpsrv_la-memfile_lease_limits.lo `test -f 'memfile_lease_limits.cc' || echo '$(srcdir)/'`memfile_lease_limits.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_limits.Tpo $(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_limits.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_limits.cc' object='libkea_dhcpsrv_la-memfile_lease_limits.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-memfile_lease_limits.lo `test -f 'memfile_lease_limits.cc' || echo '$(srcdir)/'`memfile_lease_limits.cc
+
+libkea_dhcpsrv_la-memfile_lease_mgr.lo: memfile_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-memfile_lease_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_mgr.Tpo -c -o libkea_dhcpsrv_la-memfile_lease_mgr.lo `test -f 'memfile_lease_mgr.cc' || echo '$(srcdir)/'`memfile_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_mgr.cc' object='libkea_dhcpsrv_la-memfile_lease_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-memfile_lease_mgr.lo `test -f 'memfile_lease_mgr.cc' || echo '$(srcdir)/'`memfile_lease_mgr.cc
+
+libkea_dhcpsrv_la-mysql_lease_mgr.lo: mysql_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-mysql_lease_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-mysql_lease_mgr.Tpo -c -o libkea_dhcpsrv_la-mysql_lease_mgr.lo `test -f 'mysql_lease_mgr.cc' || echo '$(srcdir)/'`mysql_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-mysql_lease_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-mysql_lease_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_lease_mgr.cc' object='libkea_dhcpsrv_la-mysql_lease_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-mysql_lease_mgr.lo `test -f 'mysql_lease_mgr.cc' || echo '$(srcdir)/'`mysql_lease_mgr.cc
+
+libkea_dhcpsrv_la-mysql_host_data_source.lo: mysql_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-mysql_host_data_source.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-mysql_host_data_source.Tpo -c -o libkea_dhcpsrv_la-mysql_host_data_source.lo `test -f 'mysql_host_data_source.cc' || echo '$(srcdir)/'`mysql_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-mysql_host_data_source.Tpo $(DEPDIR)/libkea_dhcpsrv_la-mysql_host_data_source.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_host_data_source.cc' object='libkea_dhcpsrv_la-mysql_host_data_source.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-mysql_host_data_source.lo `test -f 'mysql_host_data_source.cc' || echo '$(srcdir)/'`mysql_host_data_source.cc
+
+libkea_dhcpsrv_la-ncr_generator.lo: ncr_generator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-ncr_generator.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-ncr_generator.Tpo -c -o libkea_dhcpsrv_la-ncr_generator.lo `test -f 'ncr_generator.cc' || echo '$(srcdir)/'`ncr_generator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-ncr_generator.Tpo $(DEPDIR)/libkea_dhcpsrv_la-ncr_generator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_generator.cc' object='libkea_dhcpsrv_la-ncr_generator.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-ncr_generator.lo `test -f 'ncr_generator.cc' || echo '$(srcdir)/'`ncr_generator.cc
+
+libkea_dhcpsrv_la-network.lo: network.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-network.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-network.Tpo -c -o libkea_dhcpsrv_la-network.lo `test -f 'network.cc' || echo '$(srcdir)/'`network.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-network.Tpo $(DEPDIR)/libkea_dhcpsrv_la-network.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='network.cc' object='libkea_dhcpsrv_la-network.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-network.lo `test -f 'network.cc' || echo '$(srcdir)/'`network.cc
+
+libkea_dhcpsrv_la-network_state.lo: network_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-network_state.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-network_state.Tpo -c -o libkea_dhcpsrv_la-network_state.lo `test -f 'network_state.cc' || echo '$(srcdir)/'`network_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-network_state.Tpo $(DEPDIR)/libkea_dhcpsrv_la-network_state.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='network_state.cc' object='libkea_dhcpsrv_la-network_state.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-network_state.lo `test -f 'network_state.cc' || echo '$(srcdir)/'`network_state.cc
+
+libkea_dhcpsrv_la-pgsql_host_data_source.lo: pgsql_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-pgsql_host_data_source.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-pgsql_host_data_source.Tpo -c -o libkea_dhcpsrv_la-pgsql_host_data_source.lo `test -f 'pgsql_host_data_source.cc' || echo '$(srcdir)/'`pgsql_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-pgsql_host_data_source.Tpo $(DEPDIR)/libkea_dhcpsrv_la-pgsql_host_data_source.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_host_data_source.cc' object='libkea_dhcpsrv_la-pgsql_host_data_source.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-pgsql_host_data_source.lo `test -f 'pgsql_host_data_source.cc' || echo '$(srcdir)/'`pgsql_host_data_source.cc
+
+libkea_dhcpsrv_la-pgsql_lease_mgr.lo: pgsql_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-pgsql_lease_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-pgsql_lease_mgr.Tpo -c -o libkea_dhcpsrv_la-pgsql_lease_mgr.lo `test -f 'pgsql_lease_mgr.cc' || echo '$(srcdir)/'`pgsql_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-pgsql_lease_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-pgsql_lease_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_lease_mgr.cc' object='libkea_dhcpsrv_la-pgsql_lease_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-pgsql_lease_mgr.lo `test -f 'pgsql_lease_mgr.cc' || echo '$(srcdir)/'`pgsql_lease_mgr.cc
+
+libkea_dhcpsrv_la-pool.lo: pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-pool.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-pool.Tpo -c -o libkea_dhcpsrv_la-pool.lo `test -f 'pool.cc' || echo '$(srcdir)/'`pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-pool.Tpo $(DEPDIR)/libkea_dhcpsrv_la-pool.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pool.cc' object='libkea_dhcpsrv_la-pool.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-pool.lo `test -f 'pool.cc' || echo '$(srcdir)/'`pool.cc
+
+libkea_dhcpsrv_la-random_allocation_state.lo: random_allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-random_allocation_state.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-random_allocation_state.Tpo -c -o libkea_dhcpsrv_la-random_allocation_state.lo `test -f 'random_allocation_state.cc' || echo '$(srcdir)/'`random_allocation_state.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-random_allocation_state.Tpo $(DEPDIR)/libkea_dhcpsrv_la-random_allocation_state.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_allocation_state.cc' object='libkea_dhcpsrv_la-random_allocation_state.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-random_allocation_state.lo `test -f 'random_allocation_state.cc' || echo '$(srcdir)/'`random_allocation_state.cc
+
+libkea_dhcpsrv_la-random_allocator.lo: random_allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-random_allocator.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-random_allocator.Tpo -c -o libkea_dhcpsrv_la-random_allocator.lo `test -f 'random_allocator.cc' || echo '$(srcdir)/'`random_allocator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-random_allocator.Tpo $(DEPDIR)/libkea_dhcpsrv_la-random_allocator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_allocator.cc' object='libkea_dhcpsrv_la-random_allocator.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-random_allocator.lo `test -f 'random_allocator.cc' || echo '$(srcdir)/'`random_allocator.cc
+
+libkea_dhcpsrv_la-resource_handler.lo: resource_handler.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-resource_handler.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-resource_handler.Tpo -c -o libkea_dhcpsrv_la-resource_handler.lo `test -f 'resource_handler.cc' || echo '$(srcdir)/'`resource_handler.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-resource_handler.Tpo $(DEPDIR)/libkea_dhcpsrv_la-resource_handler.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='resource_handler.cc' object='libkea_dhcpsrv_la-resource_handler.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-resource_handler.lo `test -f 'resource_handler.cc' || echo '$(srcdir)/'`resource_handler.cc
+
+libkea_dhcpsrv_la-sanity_checker.lo: sanity_checker.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-sanity_checker.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-sanity_checker.Tpo -c -o libkea_dhcpsrv_la-sanity_checker.lo `test -f 'sanity_checker.cc' || echo '$(srcdir)/'`sanity_checker.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-sanity_checker.Tpo $(DEPDIR)/libkea_dhcpsrv_la-sanity_checker.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sanity_checker.cc' object='libkea_dhcpsrv_la-sanity_checker.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-sanity_checker.lo `test -f 'sanity_checker.cc' || echo '$(srcdir)/'`sanity_checker.cc
+
+libkea_dhcpsrv_la-shared_network.lo: shared_network.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-shared_network.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-shared_network.Tpo -c -o libkea_dhcpsrv_la-shared_network.lo `test -f 'shared_network.cc' || echo '$(srcdir)/'`shared_network.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-shared_network.Tpo $(DEPDIR)/libkea_dhcpsrv_la-shared_network.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network.cc' object='libkea_dhcpsrv_la-shared_network.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-shared_network.lo `test -f 'shared_network.cc' || echo '$(srcdir)/'`shared_network.cc
+
+libkea_dhcpsrv_la-srv_config.lo: srv_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-srv_config.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-srv_config.Tpo -c -o libkea_dhcpsrv_la-srv_config.lo `test -f 'srv_config.cc' || echo '$(srcdir)/'`srv_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-srv_config.Tpo $(DEPDIR)/libkea_dhcpsrv_la-srv_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='srv_config.cc' object='libkea_dhcpsrv_la-srv_config.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-srv_config.lo `test -f 'srv_config.cc' || echo '$(srcdir)/'`srv_config.cc
+
+libkea_dhcpsrv_la-subnet.lo: subnet.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-subnet.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-subnet.Tpo -c -o libkea_dhcpsrv_la-subnet.lo `test -f 'subnet.cc' || echo '$(srcdir)/'`subnet.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-subnet.Tpo $(DEPDIR)/libkea_dhcpsrv_la-subnet.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='subnet.cc' object='libkea_dhcpsrv_la-subnet.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-subnet.lo `test -f 'subnet.cc' || echo '$(srcdir)/'`subnet.cc
+
+libkea_dhcpsrv_la-timer_mgr.lo: timer_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-timer_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-timer_mgr.Tpo -c -o libkea_dhcpsrv_la-timer_mgr.lo `test -f 'timer_mgr.cc' || echo '$(srcdir)/'`timer_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-timer_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-timer_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='timer_mgr.cc' object='libkea_dhcpsrv_la-timer_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-timer_mgr.lo `test -f 'timer_mgr.cc' || echo '$(srcdir)/'`timer_mgr.cc
+
+libkea_dhcpsrv_la-tracking_lease_mgr.lo: tracking_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-tracking_lease_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-tracking_lease_mgr.Tpo -c -o libkea_dhcpsrv_la-tracking_lease_mgr.lo `test -f 'tracking_lease_mgr.cc' || echo '$(srcdir)/'`tracking_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-tracking_lease_mgr.Tpo $(DEPDIR)/libkea_dhcpsrv_la-tracking_lease_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tracking_lease_mgr.cc' object='libkea_dhcpsrv_la-tracking_lease_mgr.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-tracking_lease_mgr.lo `test -f 'tracking_lease_mgr.cc' || echo '$(srcdir)/'`tracking_lease_mgr.cc
+
+parsers/libkea_dhcpsrv_la-base_network_parser.lo: parsers/base_network_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-base_network_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-base_network_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-base_network_parser.lo `test -f 'parsers/base_network_parser.cc' || echo '$(srcdir)/'`parsers/base_network_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-base_network_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-base_network_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/base_network_parser.cc' object='parsers/libkea_dhcpsrv_la-base_network_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-base_network_parser.lo `test -f 'parsers/base_network_parser.cc' || echo '$(srcdir)/'`parsers/base_network_parser.cc
+
+parsers/libkea_dhcpsrv_la-client_class_def_parser.lo: parsers/client_class_def_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-client_class_def_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-client_class_def_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-client_class_def_parser.lo `test -f 'parsers/client_class_def_parser.cc' || echo '$(srcdir)/'`parsers/client_class_def_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-client_class_def_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-client_class_def_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/client_class_def_parser.cc' object='parsers/libkea_dhcpsrv_la-client_class_def_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-client_class_def_parser.lo `test -f 'parsers/client_class_def_parser.cc' || echo '$(srcdir)/'`parsers/client_class_def_parser.cc
+
+parsers/libkea_dhcpsrv_la-dhcp_parsers.lo: parsers/dhcp_parsers.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-dhcp_parsers.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_parsers.Tpo -c -o parsers/libkea_dhcpsrv_la-dhcp_parsers.lo `test -f 'parsers/dhcp_parsers.cc' || echo '$(srcdir)/'`parsers/dhcp_parsers.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_parsers.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_parsers.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/dhcp_parsers.cc' object='parsers/libkea_dhcpsrv_la-dhcp_parsers.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-dhcp_parsers.lo `test -f 'parsers/dhcp_parsers.cc' || echo '$(srcdir)/'`parsers/dhcp_parsers.cc
+
+parsers/libkea_dhcpsrv_la-duid_config_parser.lo: parsers/duid_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-duid_config_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-duid_config_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-duid_config_parser.lo `test -f 'parsers/duid_config_parser.cc' || echo '$(srcdir)/'`parsers/duid_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-duid_config_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-duid_config_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/duid_config_parser.cc' object='parsers/libkea_dhcpsrv_la-duid_config_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-duid_config_parser.lo `test -f 'parsers/duid_config_parser.cc' || echo '$(srcdir)/'`parsers/duid_config_parser.cc
+
+parsers/libkea_dhcpsrv_la-expiration_config_parser.lo: parsers/expiration_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-expiration_config_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-expiration_config_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-expiration_config_parser.lo `test -f 'parsers/expiration_config_parser.cc' || echo '$(srcdir)/'`parsers/expiration_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-expiration_config_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-expiration_config_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/expiration_config_parser.cc' object='parsers/libkea_dhcpsrv_la-expiration_config_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-expiration_config_parser.lo `test -f 'parsers/expiration_config_parser.cc' || echo '$(srcdir)/'`parsers/expiration_config_parser.cc
+
+parsers/libkea_dhcpsrv_la-host_reservation_parser.lo: parsers/host_reservation_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-host_reservation_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-host_reservation_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-host_reservation_parser.lo `test -f 'parsers/host_reservation_parser.cc' || echo '$(srcdir)/'`parsers/host_reservation_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-host_reservation_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-host_reservation_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/host_reservation_parser.cc' object='parsers/libkea_dhcpsrv_la-host_reservation_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-host_reservation_parser.lo `test -f 'parsers/host_reservation_parser.cc' || echo '$(srcdir)/'`parsers/host_reservation_parser.cc
+
+parsers/libkea_dhcpsrv_la-ifaces_config_parser.lo: parsers/ifaces_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-ifaces_config_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-ifaces_config_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-ifaces_config_parser.lo `test -f 'parsers/ifaces_config_parser.cc' || echo '$(srcdir)/'`parsers/ifaces_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-ifaces_config_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-ifaces_config_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/ifaces_config_parser.cc' object='parsers/libkea_dhcpsrv_la-ifaces_config_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-ifaces_config_parser.lo `test -f 'parsers/ifaces_config_parser.cc' || echo '$(srcdir)/'`parsers/ifaces_config_parser.cc
+
+parsers/libkea_dhcpsrv_la-multi_threading_config_parser.lo: parsers/multi_threading_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-multi_threading_config_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-multi_threading_config_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-multi_threading_config_parser.lo `test -f 'parsers/multi_threading_config_parser.cc' || echo '$(srcdir)/'`parsers/multi_threading_config_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-multi_threading_config_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-multi_threading_config_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/multi_threading_config_parser.cc' object='parsers/libkea_dhcpsrv_la-multi_threading_config_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-multi_threading_config_parser.lo `test -f 'parsers/multi_threading_config_parser.cc' || echo '$(srcdir)/'`parsers/multi_threading_config_parser.cc
+
+parsers/libkea_dhcpsrv_la-option_data_parser.lo: parsers/option_data_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-option_data_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-option_data_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-option_data_parser.lo `test -f 'parsers/option_data_parser.cc' || echo '$(srcdir)/'`parsers/option_data_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-option_data_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-option_data_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/option_data_parser.cc' object='parsers/libkea_dhcpsrv_la-option_data_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-option_data_parser.lo `test -f 'parsers/option_data_parser.cc' || echo '$(srcdir)/'`parsers/option_data_parser.cc
+
+parsers/libkea_dhcpsrv_la-dhcp_queue_control_parser.lo: parsers/dhcp_queue_control_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-dhcp_queue_control_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_queue_control_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-dhcp_queue_control_parser.lo `test -f 'parsers/dhcp_queue_control_parser.cc' || echo '$(srcdir)/'`parsers/dhcp_queue_control_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_queue_control_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_queue_control_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/dhcp_queue_control_parser.cc' object='parsers/libkea_dhcpsrv_la-dhcp_queue_control_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-dhcp_queue_control_parser.lo `test -f 'parsers/dhcp_queue_control_parser.cc' || echo '$(srcdir)/'`parsers/dhcp_queue_control_parser.cc
+
+parsers/libkea_dhcpsrv_la-sanity_checks_parser.lo: parsers/sanity_checks_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-sanity_checks_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-sanity_checks_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-sanity_checks_parser.lo `test -f 'parsers/sanity_checks_parser.cc' || echo '$(srcdir)/'`parsers/sanity_checks_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-sanity_checks_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-sanity_checks_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/sanity_checks_parser.cc' object='parsers/libkea_dhcpsrv_la-sanity_checks_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-sanity_checks_parser.lo `test -f 'parsers/sanity_checks_parser.cc' || echo '$(srcdir)/'`parsers/sanity_checks_parser.cc
+
+parsers/libkea_dhcpsrv_la-shared_network_parser.lo: parsers/shared_network_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-shared_network_parser.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-shared_network_parser.Tpo -c -o parsers/libkea_dhcpsrv_la-shared_network_parser.lo `test -f 'parsers/shared_network_parser.cc' || echo '$(srcdir)/'`parsers/shared_network_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-shared_network_parser.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-shared_network_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/shared_network_parser.cc' object='parsers/libkea_dhcpsrv_la-shared_network_parser.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-shared_network_parser.lo `test -f 'parsers/shared_network_parser.cc' || echo '$(srcdir)/'`parsers/shared_network_parser.cc
+
+parsers/libkea_dhcpsrv_la-simple_parser4.lo: parsers/simple_parser4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-simple_parser4.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser4.Tpo -c -o parsers/libkea_dhcpsrv_la-simple_parser4.lo `test -f 'parsers/simple_parser4.cc' || echo '$(srcdir)/'`parsers/simple_parser4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser4.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/simple_parser4.cc' object='parsers/libkea_dhcpsrv_la-simple_parser4.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-simple_parser4.lo `test -f 'parsers/simple_parser4.cc' || echo '$(srcdir)/'`parsers/simple_parser4.cc
+
+parsers/libkea_dhcpsrv_la-simple_parser6.lo: parsers/simple_parser6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT parsers/libkea_dhcpsrv_la-simple_parser6.lo -MD -MP -MF parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser6.Tpo -c -o parsers/libkea_dhcpsrv_la-simple_parser6.lo `test -f 'parsers/simple_parser6.cc' || echo '$(srcdir)/'`parsers/simple_parser6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser6.Tpo parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parsers/simple_parser6.cc' object='parsers/libkea_dhcpsrv_la-simple_parser6.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o parsers/libkea_dhcpsrv_la-simple_parser6.lo `test -f 'parsers/simple_parser6.cc' || echo '$(srcdir)/'`parsers/simple_parser6.cc
+
+libkea_dhcpsrv_la-fuzz.lo: fuzz.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-fuzz.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-fuzz.Tpo -c -o libkea_dhcpsrv_la-fuzz.lo `test -f 'fuzz.cc' || echo '$(srcdir)/'`fuzz.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-fuzz.Tpo $(DEPDIR)/libkea_dhcpsrv_la-fuzz.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fuzz.cc' object='libkea_dhcpsrv_la-fuzz.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-fuzz.lo `test -f 'fuzz.cc' || echo '$(srcdir)/'`fuzz.cc
+
+libkea_dhcpsrv_la-fuzz_log.lo: fuzz_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-fuzz_log.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-fuzz_log.Tpo -c -o libkea_dhcpsrv_la-fuzz_log.lo `test -f 'fuzz_log.cc' || echo '$(srcdir)/'`fuzz_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-fuzz_log.Tpo $(DEPDIR)/libkea_dhcpsrv_la-fuzz_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fuzz_log.cc' object='libkea_dhcpsrv_la-fuzz_log.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-fuzz_log.lo `test -f 'fuzz_log.cc' || echo '$(srcdir)/'`fuzz_log.cc
+
+libkea_dhcpsrv_la-fuzz_messages.lo: fuzz_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_dhcpsrv_la-fuzz_messages.lo -MD -MP -MF $(DEPDIR)/libkea_dhcpsrv_la-fuzz_messages.Tpo -c -o libkea_dhcpsrv_la-fuzz_messages.lo `test -f 'fuzz_messages.cc' || echo '$(srcdir)/'`fuzz_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dhcpsrv_la-fuzz_messages.Tpo $(DEPDIR)/libkea_dhcpsrv_la-fuzz_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fuzz_messages.cc' object='libkea_dhcpsrv_la-fuzz_messages.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_dhcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(libkea_dhcpsrv_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dhcpsrv_la-fuzz_messages.lo `test -f 'fuzz_messages.cc' || echo '$(srcdir)/'`fuzz_messages.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf parsers/.libs parsers/_libs
+install-libkea_dhcpsrv_includeHEADERS: $(libkea_dhcpsrv_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_dhcpsrv_include_HEADERS)'; test -n "$(libkea_dhcpsrv_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_dhcpsrv_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_dhcpsrv_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)$(libkea_dhcpsrv_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_dhcpsrv_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_dhcpsrv_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_dhcpsrv_include_HEADERS)'; test -n "$(libkea_dhcpsrv_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_dhcpsrv_includedir)'; $(am__uninstall_files_from_dir)
+install-libkea_dhcpsrv_parsers_includeHEADERS: $(libkea_dhcpsrv_parsers_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_dhcpsrv_parsers_include_HEADERS)'; test -n "$(libkea_dhcpsrv_parsers_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_dhcpsrv_parsers_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_dhcpsrv_parsers_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)$(libkea_dhcpsrv_parsers_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_dhcpsrv_parsers_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_dhcpsrv_parsers_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_dhcpsrv_parsers_include_HEADERS)'; test -n "$(libkea_dhcpsrv_parsers_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_dhcpsrv_parsers_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_dhcpsrv_includedir)" "$(DESTDIR)$(libkea_dhcpsrv_parsers_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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)
+ -rm -f parsers/$(DEPDIR)/$(am__dirstamp)
+ -rm -f parsers/$(am__dirstamp)
+
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_4o6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_consistency.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_db_access.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_duid.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_expiration.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_globals.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_host_operations.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts_util.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_iface.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_mac_source.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_multi_threading.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option_def.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_rsoo.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_shared_networks.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfgmgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-client_class_def.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp4_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp6_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_cfg.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-dhcp4o6_ipc.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-host.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-host_data_source_factory.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-host_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-hosts_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-hosts_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-ip_range.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-ip_range_permutation.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-lease.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr_factory.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_limits.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-mysql_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-mysql_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-ncr_generator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-network.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-network_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-random_allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-random_allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-resource_handler.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-sanity_checker.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-shared_network.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-srv_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-subnet.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-timer_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-tracking_lease_mgr.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-base_network_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-client_class_def_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_parsers.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_queue_control_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-duid_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-expiration_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-host_reservation_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-ifaces_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-multi_threading_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-option_data_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-sanity_checks_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-shared_network_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser4.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser6.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-data-local \
+ install-libkea_dhcpsrv_includeHEADERS \
+ install-libkea_dhcpsrv_parsers_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_dhcpsrv_la-alloc_engine.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-alloc_engine_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cb_ctl_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_4o6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_consistency.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_db_access.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_duid.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_expiration.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_globals.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_host_operations.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_hosts_util.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_iface.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_mac_source.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_multi_threading.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_option_def.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_rsoo.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_shared_networks.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfg_subnets6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-cfgmgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-client_class_def.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp4_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_dhcp6_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-config_backend_pool_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file4.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-csv_lease_file6.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_cfg.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-d2_client_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-dhcp4o6_ipc.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-dhcpsrv_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-flq_allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-fuzz_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-host.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-host_data_source_factory.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-host_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-hosts_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-hosts_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-ip_range.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-ip_range_permutation.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-iterative_allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-lease.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-lease_mgr_factory.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_limits.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-memfile_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-mysql_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-mysql_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-ncr_generator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-network.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-network_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-pgsql_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-random_allocation_state.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-random_allocator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-resource_handler.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-sanity_checker.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-shared_network.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-srv_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-subnet.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-timer_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_dhcpsrv_la-tracking_lease_mgr.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-base_network_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-client_class_def_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_parsers.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-dhcp_queue_control_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-duid_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-expiration_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-host_reservation_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-ifaces_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-multi_threading_config_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-option_data_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-sanity_checks_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-shared_network_parser.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser4.Plo
+ -rm -f parsers/$(DEPDIR)/libkea_dhcpsrv_la-simple_parser6.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_dhcpsrv_includeHEADERS \
+ uninstall-libkea_dhcpsrv_parsers_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-data-local install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libkea_dhcpsrv_includeHEADERS \
+ install-libkea_dhcpsrv_parsers_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_dhcpsrv_includeHEADERS \
+ uninstall-libkea_dhcpsrv_parsers_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f alloc_engine_messages.h alloc_engine_messages.cc
+ rm -f dhcpsrv_messages.h dhcpsrv_messages.cc
+ rm -f hosts_messages.h hosts_messages.cc
+ rm -f fuzz_messages.h fuzz_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: alloc_engine_messages.h alloc_engine_messages.cc \
+@GENERATE_MESSAGES_TRUE@ dhcpsrv_messages.h dhcpsrv_messages.cc \
+@GENERATE_MESSAGES_TRUE@ hosts_messages.h hosts_messages.cc \
+@GENERATE_MESSAGES_TRUE@ fuzz_messages.h fuzz_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@alloc_engine_messages.h alloc_engine_messages.cc: alloc_engine_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/alloc_engine_messages.mes
+
+@GENERATE_MESSAGES_TRUE@dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+@GENERATE_MESSAGES_TRUE@hosts_messages.h hosts_messages.cc: hosts_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/hosts_messages.mes
+
+@GENERATE_MESSAGES_TRUE@fuzz_messages.h fuzz_messages.cc: fuzz_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/dhcpsrv/fuzz_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages: alloc_engine_messages.h alloc_engine_messages.cc \
+@GENERATE_MESSAGES_FALSE@ dhcpsrv_messages.h dhcpsrv_messages.cc \
+@GENERATE_MESSAGES_FALSE@ hosts_messages.h hosts_messages.cc \
+@GENERATE_MESSAGES_FALSE@ fuzz_messages.h fuzz_messages.cc
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(dhcp_data_dir)
+
+# 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/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
new file mode 100644
index 0000000..e7b1d82
--- /dev/null
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -0,0 +1,5250 @@
+// Copyright (C) 2012-2023 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 <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/option_int.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/alloc_engine_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/resource_handler.h>
+#include <dhcpsrv/shared_network.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <stats/stats_mgr.h>
+#include <util/encode/hex.h>
+#include <util/stopwatch.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/foreach.hpp>
+#include <boost/make_shared.hpp>
+
+#include <algorithm>
+#include <sstream>
+#include <stdint.h>
+#include <string.h>
+#include <utility>
+#include <vector>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
+using namespace isc::hooks;
+using namespace isc::stats;
+using namespace isc::util;
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// Structure that holds registered hook indexes
+struct AllocEngineHooks {
+ int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
+ int hook_index_lease4_renew_; ///< index for "lease4_renew" hook point
+ int hook_index_lease4_expire_; ///< index for "lease4_expire" hook point
+ int hook_index_lease4_recover_;///< index for "lease4_recover" hook point
+ int hook_index_lease6_select_; ///< index for "lease6_receive" hook point
+ int hook_index_lease6_renew_; ///< index for "lease6_renew" hook point
+ int hook_index_lease6_rebind_; ///< index for "lease6_rebind" hook point
+ int hook_index_lease6_expire_; ///< index for "lease6_expire" hook point
+ int hook_index_lease6_recover_;///< index for "lease6_recover" hook point
+
+ /// Constructor that registers hook points for AllocationEngine
+ AllocEngineHooks() {
+ hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
+ hook_index_lease4_renew_ = HooksManager::registerHook("lease4_renew");
+ hook_index_lease4_expire_ = HooksManager::registerHook("lease4_expire");
+ hook_index_lease4_recover_= HooksManager::registerHook("lease4_recover");
+ hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
+ hook_index_lease6_renew_ = HooksManager::registerHook("lease6_renew");
+ hook_index_lease6_rebind_ = HooksManager::registerHook("lease6_rebind");
+ hook_index_lease6_expire_ = HooksManager::registerHook("lease6_expire");
+ hook_index_lease6_recover_= HooksManager::registerHook("lease6_recover");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+AllocEngineHooks Hooks;
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+
+AllocEngine::AllocEngine(uint128_t const& attempts)
+ : attempts_(attempts), incomplete_v4_reclamations_(0),
+ incomplete_v6_reclamations_(0) {
+
+ // Register hook points
+ hook_index_lease4_select_ = Hooks.hook_index_lease4_select_;
+ hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+namespace {
+
+/// @brief Find reservations for the given subnet and address or delegated prefix.
+///
+/// @param subnet_id Subnet identifier for which the reservations should
+/// be found.
+/// @param address Address or delegated prefix for which the reservations
+/// should be found.
+///
+/// @return Collection of host reservations.
+ConstHostCollection
+getIPv6Resrv(const SubnetID& subnet_id, const IOAddress& address) {
+ ConstHostCollection reserved;
+ // The global parameter ip-reservations-unique controls whether it is allowed
+ // to specify multiple reservations for the same IP address or delegated prefix
+ // or IP reservations must be unique. Some host backends do not support the
+ // former, thus we can't always use getAll6 calls to get the reservations
+ // for the given IP. When we're in the default mode, when IP reservations
+ // are unique, we should call get6 (supported by all backends). If we're in
+ // the mode in which non-unique reservations are allowed the backends which
+ // don't support it are not used and we can safely call getAll6.
+ if (CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique()) {
+ auto host = HostMgr::instance().get6(subnet_id, address);
+ if (host) {
+ reserved.push_back(host);
+ }
+ } else {
+ auto hosts = HostMgr::instance().getAll6(subnet_id, address);
+ reserved.insert(reserved.end(), hosts.begin(), hosts.end());
+ }
+ return (reserved);
+}
+
+/// @brief Checks if the specified address belongs to one of the subnets
+/// within a shared network.
+///
+/// @param ctx Client context. Current subnet may be modified by this
+/// function when it belongs to a shared network.
+/// @param lease_type Type of the lease.
+/// @param address IPv6 address or prefix to be checked.
+/// @param check_subnet if true only subnets are checked else both subnets
+/// and pools are checked
+///
+/// @return true if address belongs to a pool in a selected subnet or in
+/// a pool within any of the subnets belonging to the current shared network.
+bool
+inAllowedPool(AllocEngine::ClientContext6& ctx, const Lease::Type& lease_type,
+ const IOAddress& address, bool check_subnet) {
+ // If the subnet belongs to a shared network we will be iterating
+ // over the subnets that belong to this shared network.
+ Subnet6Ptr current_subnet = ctx.subnet_;
+ auto const& classes = ctx.query_->getClasses();
+
+ while (current_subnet) {
+ if (current_subnet->clientSupported(classes)) {
+ if (check_subnet) {
+ if (current_subnet->inPool(lease_type, address)) {
+ return (true);
+ }
+ } else {
+ if (current_subnet->inPool(lease_type, address, classes)) {
+ return (true);
+ }
+ }
+ }
+
+ current_subnet = current_subnet->getNextSubnet(ctx.subnet_);
+ }
+
+ return (false);
+}
+
+}
+
+// ##########################################################################
+// # DHCPv6 lease allocation code starts here.
+// ##########################################################################
+
+namespace isc {
+namespace dhcp {
+
+AllocEngine::ClientContext6::ClientContext6()
+ : query_(), fake_allocation_(false),
+ early_global_reservations_lookup_(false), subnet_(), host_subnet_(),
+ duid_(), hwaddr_(), host_identifiers_(), hosts_(),
+ fwd_dns_update_(false), rev_dns_update_(false), hostname_(),
+ callout_handle_(), ias_(), ddns_params_() {
+}
+
+AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet,
+ const DuidPtr& duid,
+ const bool fwd_dns,
+ const bool rev_dns,
+ const std::string& hostname,
+ const bool fake_allocation,
+ const Pkt6Ptr& query,
+ const CalloutHandlePtr& callout_handle)
+ : query_(query), fake_allocation_(fake_allocation),
+ early_global_reservations_lookup_(false), subnet_(subnet),
+ duid_(duid), hwaddr_(), host_identifiers_(), hosts_(),
+ fwd_dns_update_(fwd_dns), rev_dns_update_(rev_dns), hostname_(hostname),
+ callout_handle_(callout_handle), allocated_resources_(), new_leases_(),
+ ias_(), ddns_params_() {
+
+ // Initialize host identifiers.
+ if (duid) {
+ addHostIdentifier(Host::IDENT_DUID, duid->getDuid());
+ }
+}
+
+AllocEngine::ClientContext6::IAContext::IAContext()
+ : iaid_(0), type_(Lease::TYPE_NA), hints_(), old_leases_(),
+ changed_leases_(), new_resources_(), ia_rsp_() {
+}
+
+void
+AllocEngine::ClientContext6::
+IAContext::addHint(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint32_t preferred,
+ const uint32_t valid) {
+ hints_.push_back(Resource(prefix, prefix_len, preferred, valid));
+}
+
+void
+AllocEngine::ClientContext6::
+IAContext::addHint(const Option6IAAddrPtr& iaaddr) {
+ if (!iaaddr) {
+ isc_throw(BadValue, "IAADDR option pointer is null.");
+ }
+ addHint(iaaddr->getAddress(), 128,
+ iaaddr->getPreferred(), iaaddr->getValid());
+}
+
+void
+AllocEngine::ClientContext6::
+IAContext::addHint(const Option6IAPrefixPtr& iaprefix) {
+ if (!iaprefix) {
+ isc_throw(BadValue, "IAPREFIX option pointer is null.");
+ }
+ addHint(iaprefix->getAddress(), iaprefix->getLength(),
+ iaprefix->getPreferred(), iaprefix->getValid());
+}
+
+void
+AllocEngine::ClientContext6::
+IAContext::addNewResource(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) {
+ static_cast<void>(new_resources_.insert(Resource(prefix, prefix_len)));
+}
+
+bool
+AllocEngine::ClientContext6::
+IAContext::isNewResource(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ return (static_cast<bool>(new_resources_.count(Resource(prefix,
+ prefix_len))));
+}
+
+void
+AllocEngine::ClientContext6::
+addAllocatedResource(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) {
+ static_cast<void>(allocated_resources_.insert(Resource(prefix,
+ prefix_len)));
+}
+
+bool
+AllocEngine::ClientContext6::
+isAllocated(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const {
+ return (static_cast<bool>
+ (allocated_resources_.count(Resource(prefix, prefix_len))));
+}
+
+ConstHostPtr
+AllocEngine::ClientContext6::currentHost() const {
+ Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
+ if (subnet && subnet->getReservationsInSubnet()) {
+ auto host = hosts_.find(subnet->getID());
+ if (host != hosts_.cend()) {
+ return (host->second);
+ }
+ }
+
+ return (globalHost());
+}
+
+ConstHostPtr
+AllocEngine::ClientContext6::globalHost() const {
+ Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_;
+ if (subnet && subnet_->getReservationsGlobal()) {
+ auto host = hosts_.find(SUBNET_ID_GLOBAL);
+ if (host != hosts_.cend()) {
+ return (host->second);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+bool
+AllocEngine::ClientContext6::hasGlobalReservation(const IPv6Resrv& resv) const {
+ ConstHostPtr ghost = globalHost();
+ return (ghost && ghost->hasReservation(resv));
+}
+
+DdnsParamsPtr
+AllocEngine::ClientContext6::getDdnsParams() {
+ // We already have it return it unless the context subnet has changed.
+ if (ddns_params_ && subnet_ && (subnet_->getID() == ddns_params_->getSubnetId())) {
+ return (ddns_params_);
+ }
+
+ // Doesn't exist yet or is stale, (re)create it.
+ if (subnet_) {
+ ddns_params_ = CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_);
+ return (ddns_params_);
+ }
+
+ // Asked for it without a subnet? This case really shouldn't occur but
+ // for now let's return an instance with default values.
+ return (DdnsParamsPtr(new DdnsParams()));
+}
+
+void
+AllocEngine::findReservation(ClientContext6& ctx) {
+ // If there is no subnet, there is nothing to do.
+ if (!ctx.subnet_) {
+ return;
+ }
+
+ auto subnet = ctx.subnet_;
+
+ // If already done just return.
+ if (ctx.early_global_reservations_lookup_ &&
+ !subnet->getReservationsInSubnet()) {
+ return;
+ }
+
+ // @todo: This code can be trivially optimized.
+ if (!ctx.early_global_reservations_lookup_ &&
+ subnet->getReservationsGlobal()) {
+ ConstHostPtr ghost = findGlobalReservation(ctx);
+ if (ghost) {
+ ctx.hosts_[SUBNET_ID_GLOBAL] = ghost;
+
+ // If we had only to fetch global reservations it is done.
+ if (!subnet->getReservationsInSubnet()) {
+ return;
+ }
+ }
+ }
+
+ std::map<SubnetID, ConstHostPtr> host_map;
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
+
+ // If the subnet belongs to a shared network it is usually going to be
+ // more efficient to make a query for all reservations for a particular
+ // client rather than a query for each subnet within this shared network.
+ // The only case when it is going to be less efficient is when there are
+ // more host identifier types in use than subnets within a shared network.
+ // As it breaks RADIUS use of host caching this can be disabled by the
+ // host manager.
+ const bool use_single_query = network &&
+ !HostMgr::instance().getDisableSingleQuery() &&
+ (network->getAllSubnets()->size() > ctx.host_identifiers_.size());
+
+ if (use_single_query) {
+ for (const IdentifierPair& id_pair : ctx.host_identifiers_) {
+ ConstHostCollection hosts = HostMgr::instance().getAll(id_pair.first,
+ &id_pair.second[0],
+ id_pair.second.size());
+ // Store the hosts in the temporary map, because some hosts may
+ // belong to subnets outside of the shared network. We'll need
+ // to eliminate them.
+ for (auto host = hosts.begin(); host != hosts.end(); ++host) {
+ if ((*host)->getIPv6SubnetID() != SUBNET_ID_GLOBAL) {
+ host_map[(*host)->getIPv6SubnetID()] = *host;
+ }
+ }
+ }
+ }
+
+ auto const& classes = ctx.query_->getClasses();
+
+ // We can only search for the reservation if a subnet has been selected.
+ while (subnet) {
+
+ // Only makes sense to get reservations if the client has access
+ // to the class and host reservations are enabled for this subnet.
+ if (subnet->clientSupported(classes) && subnet->getReservationsInSubnet()) {
+ // Iterate over configured identifiers in the order of preference
+ // and try to use each of them to search for the reservations.
+ if (use_single_query) {
+ if (host_map.count(subnet->getID()) > 0) {
+ ctx.hosts_[subnet->getID()] = host_map[subnet->getID()];
+ }
+ } else {
+ for (const IdentifierPair& id_pair : ctx.host_identifiers_) {
+ // Attempt to find a host using a specified identifier.
+ ConstHostPtr host = HostMgr::instance().get6(subnet->getID(),
+ id_pair.first,
+ &id_pair.second[0],
+ id_pair.second.size());
+ // If we found matching host for this subnet.
+ if (host) {
+ ctx.hosts_[subnet->getID()] = host;
+ break;
+ }
+ }
+ }
+ }
+
+ // We need to get to the next subnet if this is a shared network. If it
+ // is not (a plain subnet), getNextSubnet will return NULL and we're
+ // done here.
+ subnet = subnet->getNextSubnet(ctx.subnet_, classes);
+ }
+
+ // The hosts can be used by the server to return reserved options to
+ // the DHCP client. Such options must be encapsulated (i.e., they must
+ // include suboptions).
+ for (auto host : ctx.hosts_) {
+ host.second->encapsulateOptions();
+ }
+}
+
+ConstHostPtr
+AllocEngine::findGlobalReservation(ClientContext6& ctx) {
+ ConstHostPtr host;
+ for (const IdentifierPair& id_pair : ctx.host_identifiers_) {
+ // Attempt to find a host using a specified identifier.
+ host = HostMgr::instance().get6(SUBNET_ID_GLOBAL, id_pair.first,
+ &id_pair.second[0], id_pair.second.size());
+
+ // If we found matching global host we're done.
+ if (host) {
+ break;
+ }
+ }
+
+ return (host);
+}
+
+Lease6Collection
+AllocEngine::allocateLeases6(ClientContext6& ctx) {
+
+ try {
+ if (!ctx.subnet_) {
+ isc_throw(InvalidOperation, "Subnet is required for IPv6 lease allocation");
+ } else
+ if (!ctx.duid_) {
+ isc_throw(InvalidOperation, "DUID is mandatory for IPv6 lease allocation");
+ }
+
+ // Check if there are existing leases for that shared network and
+ // DUID/IAID.
+ Subnet6Ptr subnet = ctx.subnet_;
+ Lease6Collection all_leases =
+ LeaseMgrFactory::instance().getLeases6(ctx.currentIA().type_,
+ *ctx.duid_,
+ ctx.currentIA().iaid_);
+
+ // Iterate over the leases and eliminate those that are outside of
+ // our shared network.
+ Lease6Collection leases;
+ while (subnet) {
+ for (auto l : all_leases) {
+ if ((l)->subnet_id_ == subnet->getID()) {
+ leases.push_back(l);
+ }
+ }
+
+ subnet = subnet->getNextSubnet(ctx.subnet_);
+ }
+
+ // Now do the checks:
+ // Case 1. if there are no leases, and there are reservations...
+ // 1.1. are the reserved addresses are used by someone else?
+ // yes: we have a problem
+ // no: assign them => done
+ // Case 2. if there are leases and there are no reservations...
+ // 2.1 are the leases reserved for someone else?
+ // yes: release them, assign something else
+ // no: renew them => done
+ // Case 3. if there are leases and there are reservations...
+ // 3.1 are the leases matching reservations?
+ // yes: renew them => done
+ // no: release existing leases, assign new ones based on reservations
+ // Case 4/catch-all. if there are no leases and no reservations...
+ // assign new leases
+
+ // Case 1: There are no leases and there's a reservation for this host.
+ if (leases.empty() && !ctx.hosts_.empty()) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR)
+ .arg(ctx.query_->getLabel());
+
+ // Try to allocate leases that match reservations. Typically this will
+ // succeed, except cases where the reserved addresses are used by
+ // someone else.
+ allocateReservedLeases6(ctx, leases);
+
+ leases = updateLeaseData(ctx, leases);
+
+ // If not, we'll need to continue and will eventually fall into case 4:
+ // getting a regular lease. That could happen when we're processing
+ // request from client X, there's a reserved address A for X, but
+ // A is currently used by client Y. We can't immediately reassign A
+ // from X to Y, because Y keeps using it, so X would send Decline right
+ // away. Need to wait till Y renews, then we can release A, so it
+ // will become available for X.
+
+ // Case 2: There are existing leases and there are no reservations.
+ //
+ // There is at least one lease for this client and there are no reservations.
+ // We will return these leases for the client, but we may need to update
+ // FQDN information.
+ } else if (!leases.empty() && ctx.hosts_.empty()) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR)
+ .arg(ctx.query_->getLabel());
+
+ // Check if the existing leases are reserved for someone else.
+ // If they're not, we're ok to keep using them.
+ removeNonmatchingReservedLeases6(ctx, leases);
+
+ leases = updateLeaseData(ctx, leases);
+
+ // If leases are empty at this stage, it means that we used to have
+ // leases for this client, but we checked and those leases are reserved
+ // for someone else, so we lost them. We will need to continue and
+ // will finally end up in case 4 (no leases, no reservations), so we'll
+ // assign something new.
+
+ // Case 3: There are leases and there are reservations.
+ } else if (!leases.empty() && !ctx.hosts_.empty()) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_LEASES_HR)
+ .arg(ctx.query_->getLabel());
+
+ // First, check if have leases matching reservations, and add new
+ // leases if we don't have them.
+ allocateReservedLeases6(ctx, leases);
+
+ // leases now contain both existing and new leases that were created
+ // from reservations.
+
+ // Second, let's remove leases that are reserved for someone else.
+ // This applies to any existing leases. This will not happen frequently,
+ // but it may happen with the following chain of events:
+ // 1. client A gets address X;
+ // 2. reservation for client B for address X is made by a administrator;
+ // 3. client A reboots
+ // 4. client A requests the address (X) he got previously
+ removeNonmatchingReservedLeases6(ctx, leases);
+
+ // leases now contain existing and new leases, but we removed those
+ // leases that are reserved for someone else (non-matching reserved).
+
+ // There's one more check to do. Let's remove leases that are not
+ // matching reservations, i.e. if client X has address A, but there's
+ // a reservation for address B, we should release A and reassign B.
+ // Caveat: do this only if we have at least one reserved address.
+ removeNonreservedLeases6(ctx, leases);
+
+ // All checks are done. Let's hope we have some leases left.
+
+ // Update any leases we have left.
+ leases = updateLeaseData(ctx, leases);
+
+ // If we don't have any leases at this stage, it means that we hit
+ // one of the following cases:
+ // - we have a reservation, but it's not for this IAID/ia-type and
+ // we had to return the address we were using
+ // - we have a reservation for this iaid/ia-type, but the reserved
+ // address is currently used by someone else. We can't assign it
+ // yet.
+ // - we had an address, but we just discovered that it's reserved for
+ // someone else, so we released it.
+ }
+
+ if (leases.empty()) {
+ // Case 4/catch-all: One of the following is true:
+ // - we don't have leases and there are no reservations
+ // - we used to have leases, but we lost them, because they are now
+ // reserved for someone else
+ // - we have a reservation, but it is not usable yet, because the address
+ // is still used by someone else
+ //
+ // In any case, we need to go through normal lease assignment process
+ // for now. This is also a catch-all or last resort approach, when we
+ // couldn't find any reservations (or couldn't use them).
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_UNRESERVED)
+ .arg(ctx.query_->getLabel());
+
+ leases = allocateUnreservedLeases6(ctx);
+ }
+
+ if (!leases.empty()) {
+ // If there are any leases allocated, let's store in them in the
+ // IA context so as they are available when we process subsequent
+ // IAs.
+ BOOST_FOREACH(Lease6Ptr lease, leases) {
+ ctx.addAllocatedResource(lease->addr_, lease->prefixlen_);
+ ctx.new_leases_.push_back(lease);
+ }
+ return (leases);
+ }
+
+ } catch (const isc::Exception& e) {
+
+ // Some other error, return an empty lease.
+ LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_ERROR)
+ .arg(ctx.query_->getLabel())
+ .arg(e.what());
+ }
+
+ return (Lease6Collection());
+}
+
+Lease6Collection
+AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
+
+ Lease6Collection leases;
+
+ IOAddress hint = IOAddress::IPV6_ZERO_ADDRESS();
+ uint8_t hint_prefix_length = 128;
+ if (!ctx.currentIA().hints_.empty()) {
+ /// @todo: We support only one hint for now
+ hint = ctx.currentIA().hints_[0].getAddress();
+ hint_prefix_length = ctx.currentIA().hints_[0].getPrefixLength();
+ }
+
+ Subnet6Ptr original_subnet = ctx.subnet_;
+
+ Subnet6Ptr subnet = original_subnet;
+
+ SharedNetwork6Ptr network;
+
+ uint64_t total_attempts = 0;
+
+ // The following counter tracks the number of subnets with matching client
+ // classes from which the allocation engine attempted to assign leases.
+ uint64_t subnets_with_unavail_leases = 0;
+ // The following counter tracks the number of subnets in which there were
+ // no matching pools for the client.
+ uint64_t subnets_with_unavail_pools = 0;
+
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+
+ // In the case of PDs, the allocation engine will try to match pools with
+ // the delegated prefix length matching the one provided in the hint. If the
+ // hint does not provide a preferred delegated prefix length (value is 0),
+ // the allocation engine will match any pool (any greater delegated prefix
+ // length pool). The match type for the pools is ignored for non PDs.
+ Lease6Ptr hint_lease;
+ bool search_hint_lease = true;
+ Allocator::PrefixLenMatchType prefix_length_match = Allocator::PREFIX_LEN_EQUAL;
+ if (ctx.currentIA().type_ == Lease::TYPE_PD) {
+ // If the hint has a value of 128, the code might be broken as the hint
+ // was added with the default value 128 for prefix_len by the addHint
+ // function instead of 0. However 128 is not a valid value anyway so it
+ // is reset to 0 (use any delegated prefix length available).
+ if (hint_prefix_length == 128) {
+ hint_prefix_length = 0;
+ }
+ if (!hint_prefix_length) {
+ prefix_length_match = Allocator::PREFIX_LEN_HIGHER;
+ }
+ }
+
+ // Try the first allocation using PREFIX_LEN_EQUAL (or in case of PDs,
+ // PREFIX_LEN_HIGHER when there is no valid delegated prefix length in the
+ // provided hint)
+ Lease6Ptr lease = allocateBestMatch(ctx, hint_lease, search_hint_lease,
+ hint, hint_prefix_length, subnet,
+ network, total_attempts,
+ subnets_with_unavail_leases,
+ subnets_with_unavail_pools,
+ callout_status, prefix_length_match);
+
+ // Try the second allocation using PREFIX_LEN_LOWER only for PDs if the
+ // first allocation using PREFIX_LEN_EQUAL failed (there was a specific
+ // delegated prefix length hint requested).
+ if (!lease && ctx.currentIA().type_ == Lease::TYPE_PD &&
+ prefix_length_match == Allocator::PREFIX_LEN_EQUAL) {
+ prefix_length_match = Allocator::PREFIX_LEN_LOWER;
+ lease = allocateBestMatch(ctx, hint_lease, search_hint_lease, hint,
+ hint_prefix_length, subnet, network,
+ total_attempts, subnets_with_unavail_leases,
+ subnets_with_unavail_pools, callout_status,
+ prefix_length_match);
+ }
+
+ // Try the third allocation using PREFIX_LEN_HIGHER only for PDs if the
+ // second allocation using PREFIX_LEN_LOWER failed (there was a specific
+ // delegated prefix length hint requested).
+ if (!lease && ctx.currentIA().type_ == Lease::TYPE_PD &&
+ prefix_length_match == Allocator::PREFIX_LEN_LOWER) {
+ prefix_length_match = Allocator::PREFIX_LEN_HIGHER;
+ lease = allocateBestMatch(ctx, hint_lease, search_hint_lease, hint,
+ hint_prefix_length, subnet, network,
+ total_attempts, subnets_with_unavail_leases,
+ subnets_with_unavail_pools, callout_status,
+ prefix_length_match);
+ }
+
+ if (lease) {
+ leases.push_back(lease);
+ return (leases);
+ }
+
+ auto const& classes = ctx.query_->getClasses();
+
+ if (network) {
+ // The client is in the shared network. Let's log the high level message
+ // indicating which shared network the client belongs to.
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_SHARED_NETWORK)
+ .arg(ctx.query_->getLabel())
+ .arg(network->getName())
+ .arg(subnets_with_unavail_leases)
+ .arg(subnets_with_unavail_pools);
+ StatsMgr::instance().addValue("v6-allocation-fail-shared-network",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v6-allocation-fail-shared-network"),
+ static_cast<int64_t>(1));
+ } else {
+ // The client is not connected to a shared network. It is connected
+ // to a subnet. Let's log the ID of that subnet.
+ std::string shared_network = ctx.subnet_->getSharedNetworkName();
+ if (shared_network.empty()) {
+ shared_network = "(none)";
+ }
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_SUBNET)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.subnet_->toText())
+ .arg(ctx.subnet_->getID())
+ .arg(shared_network);
+ StatsMgr::instance().addValue("v6-allocation-fail-subnet",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v6-allocation-fail-subnet"),
+ static_cast<int64_t>(1));
+ }
+ if (total_attempts == 0) {
+ // In this case, it seems that none of the pools in the subnets could
+ // be used for that client, both in case the client is connected to
+ // a shared network or to a single subnet. Apparently, the client was
+ // rejected to use the pools because of the client classes' mismatch.
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_NO_POOLS)
+ .arg(ctx.query_->getLabel());
+ StatsMgr::instance().addValue("v6-allocation-fail-no-pools",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v6-allocation-fail-no-pools"),
+ static_cast<int64_t>(1));
+ } else {
+ // This is an old log message which provides a number of attempts
+ // made by the allocation engine to allocate a lease. The only case
+ // when we don't want to log this message is when the number of
+ // attempts is zero (condition above), because it would look silly.
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL)
+ .arg(ctx.query_->getLabel())
+ .arg(total_attempts);
+ StatsMgr::instance().addValue("v6-allocation-fail",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v6-allocation-fail"),
+ static_cast<int64_t>(1));
+ }
+
+ if (!classes.empty()) {
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES)
+ .arg(ctx.query_->getLabel())
+ .arg(classes.toText());
+ StatsMgr::instance().addValue("v6-allocation-fail-classes",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v6-allocation-fail-classes"),
+ static_cast<int64_t>(1));
+ }
+
+ // We failed to allocate anything. Let's return empty collection.
+ return (Lease6Collection());
+}
+
+Lease6Ptr
+AllocEngine::allocateBestMatch(ClientContext6& ctx,
+ Lease6Ptr& hint_lease,
+ bool& search_hint_lease,
+ const isc::asiolink::IOAddress& hint,
+ uint8_t hint_prefix_length,
+ Subnet6Ptr original_subnet,
+ SharedNetwork6Ptr& network,
+ uint64_t& total_attempts,
+ uint64_t& subnets_with_unavail_leases,
+ uint64_t& subnets_with_unavail_pools,
+ hooks::CalloutHandle::CalloutNextStep& callout_status,
+ Allocator::PrefixLenMatchType prefix_length_match) {
+ auto const& classes = ctx.query_->getClasses();
+ Pool6Ptr pool;
+ Subnet6Ptr subnet = original_subnet;
+
+ Lease6Ptr usable_hint_lease;
+ if (!search_hint_lease) {
+ usable_hint_lease = hint_lease;
+ }
+ for (; subnet; subnet = subnet->getNextSubnet(original_subnet)) {
+ if (!subnet->clientSupported(classes)) {
+ continue;
+ }
+
+ ctx.subnet_ = subnet;
+
+ // check if the hint is in pool and is available
+ // This is equivalent of subnet->inPool(hint), but returns the pool
+ pool = boost::dynamic_pointer_cast<Pool6>
+ (subnet->getPool(ctx.currentIA().type_, classes, hint));
+
+ // check if the pool is allowed
+ if (!pool || !pool->clientSupported(classes)) {
+ continue;
+ }
+
+ if (ctx.currentIA().type_ == Lease::TYPE_PD &&
+ !Allocator::isValidPrefixPool(prefix_length_match, pool,
+ hint_prefix_length)) {
+ continue;
+ }
+
+ bool in_subnet = subnet->getReservationsInSubnet();
+
+ /// @todo: We support only one hint for now
+ if (search_hint_lease) {
+ search_hint_lease = false;
+ hint_lease = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, hint);
+ usable_hint_lease = hint_lease;
+ }
+ if (!usable_hint_lease) {
+
+ // In-pool reservations: Check if this address is reserved for someone
+ // else. There is no need to check for whom it is reserved, because if
+ // it has been reserved for us we would have already allocated a lease.
+
+ ConstHostCollection hosts;
+ // When out-of-pool flag is true the server may assume that all host
+ // reservations are for addresses that do not belong to the dynamic
+ // pool. Therefore, it can skip the reservation checks when dealing
+ // with in-pool addresses.
+ if (in_subnet &&
+ (!subnet->getReservationsOutOfPool() ||
+ !subnet->inPool(ctx.currentIA().type_, hint))) {
+ hosts = getIPv6Resrv(subnet->getID(), hint);
+ }
+
+ if (hosts.empty()) {
+
+ // If the in-pool reservations are disabled, or there is no
+ // reservation for a given hint, we're good to go.
+
+ // The hint is valid and not currently used, let's create a
+ // lease for it
+ Lease6Ptr new_lease = createLease6(ctx, hint, pool->getLength(), callout_status);
+
+ // It can happen that the lease allocation failed (we could
+ // have lost the race condition. That means that the hint is
+ // no longer usable and we need to continue the regular
+ // allocation path.
+ if (new_lease) {
+ /// @todo: We support only one lease per ia for now
+ return (new_lease);
+ }
+ } else {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_HINT_RESERVED)
+ .arg(ctx.query_->getLabel())
+ .arg(hint.toText());
+ }
+
+ } else if (usable_hint_lease->expired()) {
+
+ // If the lease is expired, we may likely reuse it, but...
+ ConstHostCollection hosts;
+ // When out-of-pool flag is true the server may assume that all host
+ // reservations are for addresses that do not belong to the dynamic
+ // pool. Therefore, it can skip the reservation checks when dealing
+ // with in-pool addresses.
+ if (in_subnet &&
+ (!subnet->getReservationsOutOfPool() ||
+ !subnet->inPool(ctx.currentIA().type_, hint))) {
+ hosts = getIPv6Resrv(subnet->getID(), hint);
+ }
+
+ // Let's check if there is a reservation for this address.
+ if (hosts.empty()) {
+
+ // Copy an existing, expired lease so as it can be returned
+ // to the caller.
+ Lease6Ptr old_lease(new Lease6(*usable_hint_lease));
+ ctx.currentIA().old_leases_.push_back(old_lease);
+
+ /// We found a lease and it is expired, so we can reuse it
+ Lease6Ptr lease = reuseExpiredLease(usable_hint_lease, ctx,
+ pool->getLength(),
+ callout_status);
+
+ /// @todo: We support only one lease per ia for now
+ return (lease);
+
+ } else {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED)
+ .arg(ctx.query_->getLabel())
+ .arg(hint.toText());
+ }
+ }
+ }
+
+ // We have the choice in the order checking the lease and
+ // the reservation. The default is to begin by the lease
+ // if the multi-threading is disabled.
+ bool check_reservation_first = MultiThreadingMgr::instance().getMode();
+ // If multi-threading is disabled, honor the configured order for host
+ // reservations lookup.
+ if (!check_reservation_first) {
+ check_reservation_first = CfgMgr::instance().getCurrentCfg()->getReservationsLookupFirst();
+ }
+
+ // Need to check if the subnet belongs to a shared network. If so,
+ // we might be able to find a better subnet for lease allocation,
+ // for which it is more likely that there are some leases available.
+ // If we stick to the selected subnet, we may end up walking over
+ // the entire subnet (or more subnets) to discover that the pools
+ // have been exhausted. Using a subnet from which a lease was
+ // assigned most recently is an optimization which increases
+ // the likelihood of starting from the subnet which pools are not
+ // exhausted.
+
+ original_subnet->getSharedNetwork(network);
+ if (network) {
+ // This would try to find a subnet with the same set of classes
+ // as the current subnet, but with the more recent "usage timestamp".
+ // This timestamp is only updated for the allocations made with an
+ // allocator (unreserved lease allocations), not the static
+ // allocations or requested addresses.
+ original_subnet = network->getPreferredSubnet(original_subnet, ctx.currentIA().type_);
+ }
+
+ ctx.subnet_ = subnet = original_subnet;
+
+ for (; subnet; subnet = subnet->getNextSubnet(original_subnet)) {
+ if (!subnet->clientSupported(classes)) {
+ continue;
+ }
+
+ // The hint was useless (it was not provided at all, was used by someone else,
+ // was out of pool or reserved for someone else). Search the pool until first
+ // of the following occurs:
+ // - we find a free address
+ // - we find an address for which the lease has expired
+ // - we exhaust number of tries
+ uint128_t const possible_attempts =
+ subnet->getPoolCapacity(ctx.currentIA().type_,
+ classes,
+ prefix_length_match,
+ hint_prefix_length);
+
+ // If the number of tries specified in the allocation engine constructor
+ // is set to 0 (unlimited) or the pools capacity is lower than that number,
+ // let's use the pools capacity as the maximum number of tries. Trying
+ // more than the actual pools capacity is a waste of time. If the specified
+ // number of tries is lower than the pools capacity, use that number.
+ uint128_t const max_attempts =
+ (attempts_ == 0 || possible_attempts < attempts_) ?
+ possible_attempts :
+ attempts_;
+
+ if (max_attempts > 0) {
+ // If max_attempts is greater than 0, there are some pools in this subnet
+ // from which we can potentially get a lease.
+ ++subnets_with_unavail_leases;
+ } else {
+ // If max_attempts is 0, it is an indication that there are no pools
+ // in the subnet from which we can get a lease.
+ ++subnets_with_unavail_pools;
+ continue;
+ }
+
+ bool in_subnet = subnet->getReservationsInSubnet();
+ bool out_of_pool = subnet->getReservationsOutOfPool();
+
+ // Set the default status code in case the lease6_select callouts
+ // do not exist and the callout handle has a status returned by
+ // any of the callouts already invoked for this packet.
+ if (ctx.callout_handle_) {
+ ctx.callout_handle_->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+ }
+
+ for (uint64_t i = 0; i < max_attempts; ++i) {
+ ++total_attempts;
+
+ auto allocator = subnet->getAllocator(ctx.currentIA().type_);
+ IOAddress candidate = IOAddress::IPV6_ZERO_ADDRESS();
+
+ // The first step is to find out prefix length. It is 128 for
+ // non-PD leases.
+ uint8_t prefix_len = 128;
+ if (ctx.currentIA().type_ == Lease::TYPE_PD) {
+ candidate = allocator->pickPrefix(classes, pool, ctx.duid_,
+ prefix_length_match, hint,
+ hint_prefix_length);
+ if (pool) {
+ prefix_len = pool->getLength();
+ }
+ } else {
+ candidate = allocator->pickAddress(classes, ctx.duid_, hint);
+ }
+
+ // An allocator may return zero address when it has pools exhausted.
+ if (candidate.isV6Zero()) {
+ break;
+ }
+
+ // First check for reservation when it is the choice.
+ if (check_reservation_first && in_subnet && !out_of_pool) {
+ auto hosts = getIPv6Resrv(subnet->getID(), candidate);
+ if (!hosts.empty()) {
+ // Don't allocate.
+ continue;
+ }
+ }
+
+ // Check if the resource is busy i.e. can be being allocated
+ // by another thread to another client.
+ ResourceHandler resource_handler;
+ if (MultiThreadingMgr::instance().getMode() &&
+ !resource_handler.tryLock(ctx.currentIA().type_, candidate)) {
+ // Don't allocate.
+ continue;
+ }
+
+ // Look for an existing lease for the candidate.
+ Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
+ candidate);
+
+ if (!existing) {
+ /// In-pool reservations: Check if this address is reserved for someone
+ /// else. There is no need to check for whom it is reserved, because if
+ /// it has been reserved for us we would have already allocated a lease.
+ if (!check_reservation_first && in_subnet && !out_of_pool) {
+ auto hosts = getIPv6Resrv(subnet->getID(), candidate);
+ if (!hosts.empty()) {
+ // Don't allocate.
+ continue;
+ }
+ }
+
+ // there's no existing lease for selected candidate, so it is
+ // free. Let's allocate it.
+
+ ctx.subnet_ = subnet;
+ Lease6Ptr new_lease = createLease6(ctx, candidate, prefix_len, callout_status);
+ if (new_lease) {
+ // We are allocating a new lease (not renewing). So, the
+ // old lease should be NULL.
+ ctx.currentIA().old_leases_.clear();
+
+ return (new_lease);
+
+ } else if (ctx.callout_handle_ &&
+ (callout_status != CalloutHandle::NEXT_STEP_CONTINUE)) {
+ // Don't retry when the callout status is not continue.
+ break;
+ }
+
+ // Although the address was free just microseconds ago, it may have
+ // been taken just now. If the lease insertion fails, we continue
+ // allocation attempts.
+ } else if (existing->expired()) {
+ // Make sure it's not reserved.
+ if (!check_reservation_first && in_subnet && !out_of_pool) {
+ auto hosts = getIPv6Resrv(subnet->getID(), candidate);
+ if (!hosts.empty()) {
+ // Don't allocate.
+ continue;
+ }
+ }
+
+ // Copy an existing, expired lease so as it can be returned
+ // to the caller.
+ Lease6Ptr old_lease(new Lease6(*existing));
+ ctx.currentIA().old_leases_.push_back(old_lease);
+
+ ctx.subnet_ = subnet;
+ existing = reuseExpiredLease(existing, ctx, prefix_len,
+ callout_status);
+
+ return (existing);
+ }
+ }
+ }
+ return (Lease6Ptr());
+}
+
+void
+AllocEngine::allocateReservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases) {
+
+ // If there are no reservations or the reservation is v4, there's nothing to do.
+ if (ctx.hosts_.empty()) {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_NO_V6_HR)
+ .arg(ctx.query_->getLabel());
+ return;
+ }
+
+ // Let's convert this from Lease::Type to IPv6Reserv::Type
+ IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ?
+ IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
+
+ // We want to avoid allocating new lease for an IA if there is already
+ // a valid lease for which client has reservation. So, we first check if
+ // we already have a lease for a reserved address or prefix.
+ BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
+ if ((lease->valid_lft_ != 0)) {
+ if ((ctx.hosts_.count(lease->subnet_id_) > 0) &&
+ ctx.hosts_[lease->subnet_id_]->hasReservation(makeIPv6Resrv(*lease))) {
+ // We found existing lease for a reserved address or prefix.
+ // We'll simply extend the lifetime of the lease.
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS)
+ .arg(ctx.query_->getLabel())
+ .arg(lease->typeToText(lease->type_))
+ .arg(lease->addr_.toText());
+
+ // Besides IP reservations we're also going to return other reserved
+ // parameters, such as hostname. We want to hand out the hostname value
+ // from the same reservation entry as IP addresses. Thus, let's see if
+ // there is any hostname reservation.
+ if (!ctx.host_subnet_) {
+ SharedNetwork6Ptr network;
+ ctx.subnet_->getSharedNetwork(network);
+ if (network) {
+ // Remember the subnet that holds this preferred host
+ // reservation. The server will use it to return appropriate
+ // FQDN, classes etc.
+ ctx.host_subnet_ = network->getSubnet(lease->subnet_id_);
+ ConstHostPtr host = ctx.hosts_[lease->subnet_id_];
+ // If there is a hostname reservation here we should stick
+ // to this reservation. By updating the hostname in the
+ // context we make sure that the database is updated with
+ // this new value and the server doesn't need to do it and
+ // its processing performance is not impacted by the hostname
+ // updates.
+ if (host && !host->getHostname().empty()) {
+ // We have to determine whether the hostname is generated
+ // in response to client's FQDN or not. If yes, we will
+ // need to qualify the hostname. Otherwise, we just use
+ // the hostname as it is specified for the reservation.
+ OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+ ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+ qualifyName(host->getHostname(), *ctx.getDdnsParams(),
+ static_cast<bool>(fqdn));
+ }
+ }
+ }
+
+ // Got a lease for a reservation in this IA.
+ return;
+ }
+ }
+ }
+
+ // There is no lease for a reservation in this IA. So, let's now iterate
+ // over reservations specified and try to allocate one of them for the IA.
+
+ auto const& classes = ctx.query_->getClasses();
+ for (Subnet6Ptr subnet = ctx.subnet_; subnet;
+ subnet = subnet->getNextSubnet(ctx.subnet_)) {
+
+ SubnetID subnet_id = subnet->getID();
+
+ // No hosts for this subnet or the subnet not supported.
+ if (!subnet->clientSupported(classes) || ctx.hosts_.count(subnet_id) == 0) {
+ continue;
+ }
+
+ ConstHostPtr host = ctx.hosts_[subnet_id];
+
+ bool in_subnet = subnet->getReservationsInSubnet();
+
+ // Get the IPv6 reservations of specified type.
+ const IPv6ResrvRange& reservs = host->getIPv6Reservations(type);
+ BOOST_FOREACH(IPv6ResrvTuple type_lease_tuple, reservs) {
+ // We do have a reservation for address or prefix.
+ const IOAddress& addr = type_lease_tuple.second.getPrefix();
+ uint8_t prefix_len = type_lease_tuple.second.getPrefixLen();
+
+ // We have allocated this address/prefix while processing one of the
+ // previous IAs, so let's try another reservation.
+ if (ctx.isAllocated(addr, prefix_len)) {
+ continue;
+ }
+
+ // The out-of-pool flag indicates that no client should be assigned
+ // reserved addresses from within the dynamic pool, and for that
+ // reason look only for reservations that are outside the pools,
+ // hence the inPool check.
+ if (!in_subnet ||
+ (subnet->getReservationsOutOfPool() &&
+ subnet->inPool(ctx.currentIA().type_, addr))) {
+ continue;
+ }
+
+ // If there's a lease for this address, let's not create it.
+ // It doesn't matter whether it is for this client or for someone else.
+ if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_,
+ addr)) {
+
+ // Let's remember the subnet from which the reserved address has been
+ // allocated. We'll use this subnet for allocating other reserved
+ // resources.
+ ctx.subnet_ = subnet;
+
+ if (!ctx.host_subnet_) {
+ ctx.host_subnet_ = subnet;
+ if (!host->getHostname().empty()) {
+ // If there is a hostname reservation here we should stick
+ // to this reservation. By updating the hostname in the
+ // context we make sure that the database is updated with
+ // this new value and the server doesn't need to do it and
+ // its processing performance is not impacted by the hostname
+ // updates.
+
+ // We have to determine whether the hostname is generated
+ // in response to client's FQDN or not. If yes, we will
+ // need to qualify the hostname. Otherwise, we just use
+ // the hostname as it is specified for the reservation.
+ OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+ ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+ qualifyName(host->getHostname(), *ctx.getDdnsParams(),
+ static_cast<bool>(fqdn));
+ }
+ }
+
+ // Ok, let's create a new lease...
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+ Lease6Ptr lease = createLease6(ctx, addr, prefix_len, callout_status);
+
+ // ... and add it to the existing leases list.
+ existing_leases.push_back(lease);
+
+ if (ctx.currentIA().type_ == Lease::TYPE_NA) {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED)
+ .arg(addr.toText())
+ .arg(ctx.query_->getLabel());
+ } else {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED)
+ .arg(addr.toText())
+ .arg(static_cast<int>(prefix_len))
+ .arg(ctx.query_->getLabel());
+ }
+
+ // We found a lease for this client and this IA. Let's return.
+ // Returning after the first lease was assigned is useful if we
+ // have multiple reservations for the same client. If the client
+ // sends 2 IAs, the first time we call allocateReservedLeases6 will
+ // use the first reservation and return. The second time, we'll
+ // go over the first reservation, but will discover that there's
+ // a lease corresponding to it and will skip it and then pick
+ // the second reservation and turn it into the lease. This approach
+ // would work for any number of reservations.
+ return;
+ }
+ }
+ }
+
+ // Found no subnet reservations so now try the global reservation.
+ allocateGlobalReservedLeases6(ctx, existing_leases);
+}
+
+void
+AllocEngine::allocateGlobalReservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases) {
+ // Get the global host
+ ConstHostPtr ghost = ctx.globalHost();
+ if (!ghost) {
+ return;
+ }
+
+ // We want to avoid allocating a new lease for an IA if there is already
+ // a valid lease for which client has reservation. So, we first check if
+ // we already have a lease for a reserved address or prefix.
+ BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) {
+ if ((lease->valid_lft_ != 0) &&
+ (ghost->hasReservation(makeIPv6Resrv(*lease)))) {
+ // We found existing lease for a reserved address or prefix.
+ // We'll simply extend the lifetime of the lease.
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS)
+ .arg(ctx.query_->getLabel())
+ .arg(lease->typeToText(lease->type_))
+ .arg(lease->addr_.toText());
+
+ // Besides IP reservations we're also going to return other reserved
+ // parameters, such as hostname. We want to hand out the hostname value
+ // from the same reservation entry as IP addresses. Thus, let's see if
+ // there is any hostname reservation.
+ if (!ghost->getHostname().empty()) {
+ // We have to determine whether the hostname is generated
+ // in response to client's FQDN or not. If yes, we will
+ // need to qualify the hostname. Otherwise, we just use
+ // the hostname as it is specified for the reservation.
+ OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+ ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+ qualifyName(ghost->getHostname(), *ctx.getDdnsParams(),
+ static_cast<bool>(fqdn));
+ }
+
+ // Got a lease for a reservation in this IA.
+ return;
+ }
+ }
+
+ // There is no lease for a reservation in this IA. So, let's now iterate
+ // over reservations specified and try to allocate one of them for the IA.
+
+ // Let's convert this from Lease::Type to IPv6Reserv::Type
+ IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ?
+ IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD;
+
+ const IPv6ResrvRange& reservs = ghost->getIPv6Reservations(type);
+ BOOST_FOREACH(IPv6ResrvTuple type_lease_tuple, reservs) {
+ // We do have a reservation for address or prefix.
+ const IOAddress& addr = type_lease_tuple.second.getPrefix();
+ uint8_t prefix_len = type_lease_tuple.second.getPrefixLen();
+
+ // We have allocated this address/prefix while processing one of the
+ // previous IAs, so let's try another reservation.
+ if (ctx.isAllocated(addr, prefix_len)) {
+ continue;
+ }
+
+ // If there's a lease for this address, let's not create it.
+ // It doesn't matter whether it is for this client or for someone else.
+ if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, addr)) {
+
+ // Check the feasibility of this address within this shared-network.
+ // Assign the context's subnet accordingly.
+ // Only necessary for IA_NA
+ if (type == IPv6Resrv::TYPE_NA) {
+ bool valid_subnet = false;
+ auto subnet = ctx.subnet_;
+ while (subnet) {
+ if (subnet->inRange(addr)) {
+ valid_subnet = true;
+ break;
+ }
+
+ subnet = subnet->getNextSubnet(ctx.subnet_);
+ }
+
+ if (!valid_subnet) {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS6)
+ .arg(addr.toText())
+ .arg(labelNetworkOrSubnet(ctx.subnet_));
+ continue;
+ }
+
+ ctx.subnet_ = subnet;
+ }
+
+ if (!ghost->getHostname().empty()) {
+ // If there is a hostname reservation here we should stick
+ // to this reservation. By updating the hostname in the
+ // context we make sure that the database is updated with
+ // this new value and the server doesn't need to do it and
+ // its processing performance is not impacted by the hostname
+ // updates.
+
+ // We have to determine whether the hostname is generated
+ // in response to client's FQDN or not. If yes, we will
+ // need to qualify the hostname. Otherwise, we just use
+ // the hostname as it is specified for the reservation.
+ OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN);
+ ctx.hostname_ = CfgMgr::instance().getD2ClientMgr().
+ qualifyName(ghost->getHostname(), *ctx.getDdnsParams(),
+ static_cast<bool>(fqdn));
+ }
+
+ // Ok, let's create a new lease...
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+ Lease6Ptr lease = createLease6(ctx, addr, prefix_len, callout_status);
+
+ // ... and add it to the existing leases list.
+ existing_leases.push_back(lease);
+
+ if (ctx.currentIA().type_ == Lease::TYPE_NA) {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED)
+ .arg(addr.toText())
+ .arg(ctx.query_->getLabel());
+ } else {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED)
+ .arg(addr.toText())
+ .arg(static_cast<int>(prefix_len))
+ .arg(ctx.query_->getLabel());
+ }
+
+ // We found a lease for this client and this IA. Let's return.
+ // Returning after the first lease was assigned is useful if we
+ // have multiple reservations for the same client. If the client
+ // sends 2 IAs, the first time we call allocateReservedLeases6 will
+ // use the first reservation and return. The second time, we'll
+ // go over the first reservation, but will discover that there's
+ // a lease corresponding to it and will skip it and then pick
+ // the second reservation and turn it into the lease. This approach
+ // would work for any number of reservations.
+ return;
+ }
+ }
+}
+
+void
+AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases) {
+ // If there are no leases (so nothing to remove) just return.
+ if (existing_leases.empty() || !ctx.subnet_) {
+ return;
+ }
+ // If host reservation is disabled (so there are no reserved leases)
+ // use the simplified version.
+ if (!ctx.subnet_->getReservationsInSubnet() &&
+ !ctx.subnet_->getReservationsGlobal()) {
+ removeNonmatchingReservedNoHostLeases6(ctx, existing_leases);
+ return;
+ }
+
+ // We need a copy, so we won't be iterating over a container and
+ // removing from it at the same time. It's only a copy of pointers,
+ // so the operation shouldn't be that expensive.
+ Lease6Collection copy = existing_leases;
+
+ BOOST_FOREACH(const Lease6Ptr& candidate, copy) {
+ // If we have reservation we should check if the reservation is for
+ // the candidate lease. If so, we simply accept the lease.
+ IPv6Resrv resv = makeIPv6Resrv(*candidate);
+ if ((ctx.hasGlobalReservation(resv)) ||
+ ((ctx.hosts_.count(candidate->subnet_id_) > 0) &&
+ (ctx.hosts_[candidate->subnet_id_]->hasReservation(resv)))) {
+ // We have a subnet reservation
+ continue;
+ }
+
+ // The candidate address doesn't appear to be reserved for us.
+ // We have to make a bit more expensive operation here to retrieve
+ // the reservation for the candidate lease and see if it is
+ // reserved for someone else.
+ auto hosts = getIPv6Resrv(ctx.subnet_->getID(), candidate->addr_);
+ // If lease is not reserved to someone else, it means that it can
+ // be allocated to us from a dynamic pool, but we must check if
+ // this lease belongs to any pool. If it does, we can proceed to
+ // checking the next lease.
+ if (hosts.empty() && inAllowedPool(ctx, candidate->type_,
+ candidate->addr_, false)) {
+ continue;
+ }
+
+ if (!hosts.empty()) {
+ // Ok, we have a problem. This host has a lease that is reserved
+ // for someone else. We need to recover from this.
+ if (hosts.size() == 1) {
+ if (ctx.currentIA().type_ == Lease::TYPE_NA) {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE)
+ .arg(candidate->addr_.toText())
+ .arg(ctx.duid_->toText())
+ .arg(hosts.front()->getIdentifierAsText());
+ } else {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE)
+ .arg(candidate->addr_.toText())
+ .arg(static_cast<int>(candidate->prefixlen_))
+ .arg(ctx.duid_->toText())
+ .arg(hosts.front()->getIdentifierAsText());
+ }
+ } else {
+ if (ctx.currentIA().type_ == Lease::TYPE_NA) {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE)
+ .arg(candidate->addr_.toText())
+ .arg(ctx.duid_->toText())
+ .arg(hosts.size());
+ } else {
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE)
+ .arg(candidate->addr_.toText())
+ .arg(static_cast<int>(candidate->prefixlen_))
+ .arg(ctx.duid_->toText())
+ .arg(hosts.size());
+ }
+ }
+ }
+
+ // Remove this lease from LeaseMgr as it is reserved to someone
+ // else or doesn't belong to a pool.
+ if (!LeaseMgrFactory::instance().deleteLease(candidate)) {
+ // Concurrent delete performed by other instance which should
+ // properly handle dns and stats updates.
+ continue;
+ }
+
+ // Update DNS if needed.
+ queueNCR(CHG_REMOVE, candidate);
+
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", candidate->subnet_id_,
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(-1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(candidate->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(ctx.currentIA().type_, candidate->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ? "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(-1));
+ }
+ }
+
+ // In principle, we could trigger a hook here, but we will do this
+ // only if we get serious complaints from actual users. We want the
+ // conflict resolution procedure to really work and user libraries
+ // should not interfere with it.
+
+ // Add this to the list of removed leases.
+ ctx.currentIA().old_leases_.push_back(candidate);
+
+ // Let's remove this candidate from existing leases
+ removeLeases(existing_leases, candidate->addr_);
+ }
+}
+
+void
+AllocEngine::removeNonmatchingReservedNoHostLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases) {
+ // We need a copy, so we won't be iterating over a container and
+ // removing from it at the same time. It's only a copy of pointers,
+ // so the operation shouldn't be that expensive.
+ Lease6Collection copy = existing_leases;
+
+ BOOST_FOREACH(const Lease6Ptr& candidate, copy) {
+ // Lease can be allocated to us from a dynamic pool, but we must
+ // check if this lease belongs to any allowed pool. If it does,
+ // we can proceed to checking the next lease.
+ if (inAllowedPool(ctx, candidate->type_,
+ candidate->addr_, false)) {
+ continue;
+ }
+
+ // Remove this lease from LeaseMgr as it doesn't belong to a pool.
+ if (!LeaseMgrFactory::instance().deleteLease(candidate)) {
+ // Concurrent delete performed by other instance which should
+ // properly handle dns and stats updates.
+ continue;
+ }
+
+ // Update DNS if needed.
+ queueNCR(CHG_REMOVE, candidate);
+
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", candidate->subnet_id_,
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(-1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(candidate->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(candidate->type_, candidate->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName(candidate->type_ == Lease::TYPE_NA ? "pool" : "pd-pool", pool->getID(),
+ candidate->type_ == Lease::TYPE_NA ? "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(-1));
+ }
+ }
+
+ // Add this to the list of removed leases.
+ ctx.currentIA().old_leases_.push_back(candidate);
+
+ // Let's remove this candidate from existing leases
+ removeLeases(existing_leases, candidate->addr_);
+ }
+}
+
+bool
+AllocEngine::removeLeases(Lease6Collection& container, const asiolink::IOAddress& addr) {
+
+ bool removed = false;
+ for (Lease6Collection::iterator lease = container.begin();
+ lease != container.end(); ++lease) {
+ if ((*lease)->addr_ == addr) {
+ lease->reset();
+ removed = true;
+ }
+ }
+
+ // Remove all elements that have NULL value
+ container.erase(std::remove(container.begin(), container.end(), Lease6Ptr()),
+ container.end());
+
+ return (removed);
+}
+
+void
+AllocEngine::removeNonreservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases) {
+ // This method removes leases that are not reserved for this host.
+ // It will keep at least one lease, though, as a fallback.
+ int total = existing_leases.size();
+ if (total <= 1) {
+ return;
+ }
+
+ // This is officially not scary code anymore. iterates and marks specified
+ // leases for deletion, by setting appropriate pointers to NULL.
+ for (Lease6Collection::iterator lease = existing_leases.begin();
+ lease != existing_leases.end(); ++lease) {
+
+ // If there is reservation for this keep it.
+ IPv6Resrv resv = makeIPv6Resrv(*(*lease));
+ if (ctx.hasGlobalReservation(resv) ||
+ ((ctx.hosts_.count((*lease)->subnet_id_) > 0) &&
+ (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv)))) {
+ continue;
+ }
+
+ // @todo - If this is for a fake_allocation, we should probably
+ // not be deleting the lease or removing DNS entries. We should
+ // simply remove it from the list.
+ // We have reservations, but not for this lease. Release it.
+ // Remove this lease from LeaseMgr
+ if (!LeaseMgrFactory::instance().deleteLease(*lease)) {
+ // Concurrent delete performed by other instance which should
+ // properly handle dns and stats updates.
+ continue;
+ }
+
+ // Update DNS if required.
+ queueNCR(CHG_REMOVE, *lease);
+
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", (*lease)->subnet_id_,
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(-1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId((*lease)->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(ctx.currentIA().type_, (*lease)->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ? "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ? "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(-1));
+ }
+ }
+
+ /// @todo: Probably trigger a hook here
+
+ // Add this to the list of removed leases.
+ ctx.currentIA().old_leases_.push_back(*lease);
+
+ // Set this pointer to NULL. The pointer is still valid. We're just
+ // setting the Lease6Ptr to NULL value. We'll remove all NULL
+ // pointers once the loop is finished.
+ lease->reset();
+
+ if (--total == 1) {
+ // If there's only one lease left, break the loop.
+ break;
+ }
+ }
+
+ // Remove all elements that we previously marked for deletion (those that
+ // have NULL value).
+ existing_leases.erase(std::remove(existing_leases.begin(),
+ existing_leases.end(), Lease6Ptr()), existing_leases.end());
+}
+
+Lease6Ptr
+AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx,
+ uint8_t prefix_len,
+ CalloutHandle::CalloutNextStep& callout_status) {
+
+ if (!expired->expired()) {
+ isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+ }
+
+ if (expired->type_ != Lease::TYPE_PD) {
+ prefix_len = 128; // non-PD lease types must be always /128
+ }
+
+ if (!ctx.fake_allocation_) {
+ // The expired lease needs to be reclaimed before it can be reused.
+ // This includes declined leases for which probation period has
+ // elapsed.
+ reclaimExpiredLease(expired, ctx.callout_handle_);
+ }
+
+ // address, lease type and prefixlen (0) stay the same
+ expired->iaid_ = ctx.currentIA().iaid_;
+ expired->duid_ = ctx.duid_;
+
+ // Calculate life times.
+ getLifetimes6(ctx, expired->preferred_lft_, expired->valid_lft_);
+ expired->reuseable_valid_lft_ = 0;
+
+ expired->cltt_ = time(NULL);
+ expired->subnet_id_ = ctx.subnet_->getID();
+ expired->hostname_ = ctx.hostname_;
+ expired->fqdn_fwd_ = ctx.fwd_dns_update_;
+ expired->fqdn_rev_ = ctx.rev_dns_update_;
+ expired->prefixlen_ = prefix_len;
+ expired->state_ = Lease::STATE_DEFAULT;
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA,
+ ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA)
+ .arg(ctx.query_->getLabel())
+ .arg(expired->toText());
+
+ // Let's execute all callouts registered for lease6_select
+ if (ctx.callout_handle_ &&
+ HooksManager::calloutsPresent(hook_index_lease6_select_)) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_);
+
+ // Pass necessary arguments
+
+ // Pass the original packet
+ ctx.callout_handle_->setArgument("query6", ctx.query_);
+
+ // Subnet from which we do the allocation
+ ctx.callout_handle_->setArgument("subnet6", ctx.subnet_);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
+
+ // The lease that will be assigned to a client
+ ctx.callout_handle_->setArgument("lease6", expired);
+
+ // Call the callouts
+ HooksManager::callCallouts(hook_index_lease6_select_, *ctx.callout_handle_);
+
+ callout_status = ctx.callout_handle_->getStatus();
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_status == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
+ return (Lease6Ptr());
+ }
+
+ /// DROP status does not make sense here:
+ /// In general as the lease cannot be dropped the DROP action
+ /// has no object so SKIP is the right "cancel" status and
+ /// DROP should not be a synonym as it introduces ambiguity.
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handed to it.
+ ctx.callout_handle_->getArgument("lease6", expired);
+ }
+
+ if (!ctx.fake_allocation_) {
+ // Add (update) the extended information on the lease.
+ updateLease6ExtendedInfo(expired, ctx);
+
+ const auto& pool = ctx.subnet_->getPool(ctx.currentIA().type_, expired->addr_, false);
+ if (pool) {
+ expired->pool_id_ = pool->getID();
+ }
+
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease6(expired);
+
+ // If the lease is in the current subnet we need to account
+ // for the re-assignment of The lease.
+ if (ctx.subnet_->inPool(ctx.currentIA().type_, expired->addr_)) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds"),
+ static_cast<int64_t>(1));
+
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds")),
+ static_cast<int64_t>(1));
+ }
+
+ StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds",
+ static_cast<int64_t>(1));
+ }
+ }
+
+ // We do nothing for SOLICIT. We'll just update database when
+ // the client gets back to us with REQUEST message.
+
+ // it's not really expired at this stage anymore - let's return it as
+ // an updated lease
+ return (expired);
+}
+
+void
+AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& valid) {
+ // If the triplets are specified in one of our classes use it.
+ // We use the first one we find for each lifetime.
+ Triplet<uint32_t> candidate_preferred;
+ Triplet<uint32_t> candidate_valid;
+ const ClientClasses classes = ctx.query_->getClasses();
+ if (!classes.empty()) {
+ // Let's get class definitions
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+
+ // Iterate over the assigned class definitions.
+ int have_both = 0;
+ for (auto name = classes.cbegin();
+ name != classes.cend() && have_both < 2; ++name) {
+ ClientClassDefPtr cl = dict->findClass(*name);
+ if (candidate_preferred.unspecified() &&
+ (cl && (!cl->getPreferred().unspecified()))) {
+ candidate_preferred = cl->getPreferred();
+ ++have_both;
+ }
+
+ if (candidate_valid.unspecified() &&
+ (cl && (!cl->getValid().unspecified()))) {
+ candidate_valid = cl->getValid();
+ ++have_both;
+ }
+ }
+ }
+
+ // If no classes specified preferred lifetime, get it from the subnet.
+ if (!candidate_preferred) {
+ candidate_preferred = ctx.subnet_->getPreferred();
+ }
+
+ // If no classes specified valid lifetime, get it from the subnet.
+ if (!candidate_valid) {
+ candidate_valid = ctx.subnet_->getValid();
+ }
+
+ // Set the outbound parameters to the values we have so far.
+ preferred = candidate_preferred;
+ valid = candidate_valid;
+
+ // If client requested either value, use the requested value(s) bounded by
+ // the candidate triplet(s).
+ if (!ctx.currentIA().hints_.empty()) {
+ if (ctx.currentIA().hints_[0].getPreferred()) {
+ preferred = candidate_preferred.get(ctx.currentIA().hints_[0].getPreferred());
+ }
+
+ if (ctx.currentIA().hints_[0].getValid()) {
+ valid = candidate_valid.get(ctx.currentIA().hints_[0].getValid());
+ }
+ }
+
+ // If preferred isn't set or insane, calculate it as valid_lft * 0.625.
+ if (!preferred || preferred > valid) {
+ preferred = ((valid * 5)/8);
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME)
+ .arg(ctx.query_->getLabel())
+ .arg(preferred);
+ }
+}
+
+Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx,
+ const IOAddress& addr,
+ uint8_t prefix_len,
+ CalloutHandle::CalloutNextStep& callout_status) {
+
+ if (ctx.currentIA().type_ != Lease::TYPE_PD) {
+ prefix_len = 128; // non-PD lease types must be always /128
+ }
+
+ uint32_t preferred = 0;
+ uint32_t valid = 0;
+ getLifetimes6(ctx, preferred, valid);
+
+ Lease6Ptr lease(new Lease6(ctx.currentIA().type_, addr, ctx.duid_,
+ ctx.currentIA().iaid_, preferred,
+ valid, ctx.subnet_->getID(),
+ ctx.hwaddr_, prefix_len));
+
+ lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+ lease->fqdn_rev_ = ctx.rev_dns_update_;
+ lease->hostname_ = ctx.hostname_;
+
+ // Let's execute all callouts registered for lease6_select
+ if (ctx.callout_handle_ &&
+ HooksManager::calloutsPresent(hook_index_lease6_select_)) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_);
+
+ // Pass necessary arguments
+
+ // Pass the original packet
+ ctx.callout_handle_->setArgument("query6", ctx.query_);
+
+ // Subnet from which we do the allocation
+ ctx.callout_handle_->setArgument("subnet6", ctx.subnet_);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
+
+ // The lease that will be assigned to a client
+ ctx.callout_handle_->setArgument("lease6", lease);
+
+ // This is the first callout, so no need to clear any arguments
+ HooksManager::callCallouts(hook_index_lease6_select_, *ctx.callout_handle_);
+
+ callout_status = ctx.callout_handle_->getStatus();
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_status == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
+ return (Lease6Ptr());
+ }
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handed to it.
+ ctx.callout_handle_->getArgument("lease6", lease);
+ }
+
+ if (!ctx.fake_allocation_) {
+ // Add (update) the extended information on the lease.
+ updateLease6ExtendedInfo(lease, ctx);
+
+ const auto& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false);
+ if (pool) {
+ lease->pool_id_ = pool->getID();
+ }
+
+ // That is a real (REQUEST) allocation
+ bool status = LeaseMgrFactory::instance().addLease(lease);
+
+ if (status) {
+ // The lease insertion succeeded - if the lease is in the
+ // current subnet lets bump up the statistic.
+ if (ctx.subnet_->inPool(ctx.currentIA().type_, addr)) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds"),
+ static_cast<int64_t>(1));
+
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds")),
+ static_cast<int64_t>(1));
+ }
+
+ StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds",
+ static_cast<int64_t>(1));
+ }
+
+ // Record it so it won't be updated twice.
+ ctx.currentIA().addNewResource(addr, prefix_len);
+
+ return (lease);
+ } else {
+ // One of many failures with LeaseMgr (e.g. lost connection to the
+ // database, database failed etc.). One notable case for that
+ // is that we are working in multi-process mode and we lost a race
+ // (some other process got that address first)
+ return (Lease6Ptr());
+ }
+ } else {
+ // That is only fake (SOLICIT without rapid-commit) allocation
+
+ // It is for advertise only. We should not insert the lease and callers
+ // have already verified the lease does not exist in the database.
+ return (lease);
+ }
+}
+
+Lease6Collection
+AllocEngine::renewLeases6(ClientContext6& ctx) {
+ try {
+ if (!ctx.subnet_) {
+ isc_throw(InvalidOperation, "Subnet is required for allocation");
+ }
+
+ if (!ctx.duid_) {
+ isc_throw(InvalidOperation, "DUID is mandatory for allocation");
+ }
+
+ // Check if there are any leases for this client.
+ Subnet6Ptr subnet = ctx.subnet_;
+ Lease6Collection leases;
+ while (subnet) {
+ Lease6Collection leases_subnet =
+ LeaseMgrFactory::instance().getLeases6(ctx.currentIA().type_,
+ *ctx.duid_,
+ ctx.currentIA().iaid_,
+ subnet->getID());
+ leases.insert(leases.end(), leases_subnet.begin(), leases_subnet.end());
+
+ subnet = subnet->getNextSubnet(ctx.subnet_);
+ }
+
+ if (!leases.empty()) {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED)
+ .arg(ctx.query_->getLabel());
+
+ // Check if the existing leases are reserved for someone else.
+ // If they're not, we're ok to keep using them.
+ removeNonmatchingReservedLeases6(ctx, leases);
+ }
+
+ if (!ctx.hosts_.empty()) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_RENEW_HR)
+ .arg(ctx.query_->getLabel());
+
+ // If we have host reservation, allocate those leases.
+ allocateReservedLeases6(ctx, leases);
+
+ // There's one more check to do. Let's remove leases that are not
+ // matching reservations, i.e. if client X has address A, but there's
+ // a reservation for address B, we should release A and reassign B.
+ // Caveat: do this only if we have at least one reserved address.
+ removeNonreservedLeases6(ctx, leases);
+ }
+
+ // If we happen to removed all leases, get something new for this guy.
+ // Depending on the configuration, we may enable or disable granting
+ // new leases during renewals. This is controlled with the
+ // allow_new_leases_in_renewals_ field.
+ if (leases.empty()) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED)
+ .arg(ctx.query_->getLabel());
+
+ leases = allocateUnreservedLeases6(ctx);
+ }
+
+ // Extend all existing leases that passed all checks.
+ for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
+ if (ctx.currentIA().isNewResource((*l)->addr_,
+ (*l)->prefixlen_)) {
+ // This lease was just created so is already extended.
+ continue;
+ }
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL,
+ ALLOC_ENGINE_V6_EXTEND_LEASE)
+ .arg(ctx.query_->getLabel())
+ .arg((*l)->typeToText((*l)->type_))
+ .arg((*l)->addr_);
+ extendLease6(ctx, *l);
+ }
+
+ if (!leases.empty()) {
+ // If there are any leases allocated, let's store in them in the
+ // IA context so as they are available when we process subsequent
+ // IAs.
+ BOOST_FOREACH(Lease6Ptr lease, leases) {
+ ctx.addAllocatedResource(lease->addr_, lease->prefixlen_);
+ ctx.new_leases_.push_back(lease);
+ }
+ }
+
+ return (leases);
+
+ } catch (const isc::Exception& e) {
+
+ // Some other error, return an empty lease.
+ LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_EXTEND_ERROR)
+ .arg(ctx.query_->getLabel())
+ .arg(e.what());
+ }
+
+ return (Lease6Collection());
+}
+
+void
+AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) {
+
+ if (!lease || !ctx.subnet_) {
+ return;
+ }
+
+ // It is likely that the lease for which we're extending the lifetime doesn't
+ // belong to the current but a sibling subnet.
+ if (ctx.subnet_->getID() != lease->subnet_id_) {
+ SharedNetwork6Ptr network;
+ ctx.subnet_->getSharedNetwork(network);
+ if (network) {
+ Subnet6Ptr subnet = network->getSubnet(SubnetID(lease->subnet_id_));
+ // Found the actual subnet this lease belongs to. Stick to this
+ // subnet.
+ if (subnet) {
+ ctx.subnet_ = subnet;
+ }
+ }
+ }
+
+ // If the lease is not global and it is either out of range (NAs only) or it
+ // is not permitted by subnet client classification, delete it.
+ if (!(ctx.hasGlobalReservation(makeIPv6Resrv(*lease))) &&
+ (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) ||
+ !ctx.subnet_->clientSupported(ctx.query_->getClasses()))) {
+ // Oh dear, the lease is no longer valid. We need to get rid of it.
+
+ // Remove this lease from LeaseMgr
+ if (!LeaseMgrFactory::instance().deleteLease(lease)) {
+ // Concurrent delete performed by other instance which should
+ // properly handle dns and stats updates.
+ return;
+ }
+
+ // Updated DNS if required.
+ queueNCR(CHG_REMOVE, lease);
+
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(-1));
+
+ const auto& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(-1));
+ }
+
+ // Add it to the removed leases list.
+ ctx.currentIA().old_leases_.push_back(lease);
+
+ return;
+ }
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA,
+ ALLOC_ENGINE_V6_EXTEND_LEASE_DATA)
+ .arg(ctx.query_->getLabel())
+ .arg(lease->toText());
+
+ // Keep the old data in case the callout tells us to skip update.
+ Lease6Ptr old_data(new Lease6(*lease));
+
+ bool changed = false;
+
+ // Calculate life times.
+ uint32_t current_preferred_lft = lease->preferred_lft_;
+ getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
+
+ // If either has changed set the changed flag.
+ if ((lease->preferred_lft_ != current_preferred_lft) ||
+ (lease->valid_lft_ != lease->current_valid_lft_)) {
+ changed = true;
+ }
+
+ lease->cltt_ = time(NULL);
+ if ((lease->fqdn_fwd_ != ctx.fwd_dns_update_) ||
+ (lease->fqdn_rev_ != ctx.rev_dns_update_) ||
+ (lease->hostname_ != ctx.hostname_)) {
+ changed = true;
+ lease->hostname_ = ctx.hostname_;
+ lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+ lease->fqdn_rev_ = ctx.rev_dns_update_;
+ }
+ if ((!ctx.hwaddr_ && lease->hwaddr_) ||
+ (ctx.hwaddr_ &&
+ (!lease->hwaddr_ || (*ctx.hwaddr_ != *lease->hwaddr_)))) {
+ changed = true;
+ lease->hwaddr_ = ctx.hwaddr_;
+ }
+ if (lease->state_ != Lease::STATE_DEFAULT) {
+ changed = true;
+ lease->state_ = Lease::STATE_DEFAULT;
+ }
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA,
+ ALLOC_ENGINE_V6_EXTEND_NEW_LEASE_DATA)
+ .arg(ctx.query_->getLabel())
+ .arg(lease->toText());
+
+ bool skip = false;
+ // Get the callouts specific for the processed message and execute them.
+ int hook_point = ctx.query_->getType() == DHCPV6_RENEW ?
+ Hooks.hook_index_lease6_renew_ : Hooks.hook_index_lease6_rebind_;
+ if (HooksManager::calloutsPresent(hook_point)) {
+ CalloutHandlePtr callout_handle = ctx.callout_handle_;
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt6> query6_options_copy(ctx.query_);
+
+ // Pass the original packet
+ callout_handle->setArgument("query6", ctx.query_);
+
+ // Pass the lease to be updated
+ callout_handle->setArgument("lease6", lease);
+
+ // Pass the IA option to be sent in response
+ if (lease->type_ == Lease::TYPE_NA) {
+ callout_handle->setArgument("ia_na", ctx.currentIA().ia_rsp_);
+ } else {
+ callout_handle->setArgument("ia_pd", ctx.currentIA().ia_rsp_);
+ }
+
+ // Call all installed callouts
+ HooksManager::callCallouts(hook_point, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would actually renew the lease, so skip at this
+ // stage means "keep the old lease as it is".
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ skip = true;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+ DHCPSRV_HOOK_LEASE6_EXTEND_SKIP)
+ .arg(ctx.query_->getName());
+ }
+
+ /// DROP status does not make sense here.
+ }
+
+ if (!skip) {
+ bool update_stats = false;
+
+ // If the lease we're renewing has expired, we need to reclaim this
+ // lease before we can renew it.
+ if (old_data->expired()) {
+ reclaimExpiredLease(old_data, ctx.callout_handle_);
+
+ // If the lease is in the current subnet we need to account
+ // for the re-assignment of the lease.
+ if (ctx.subnet_->inPool(ctx.currentIA().type_, old_data->addr_)) {
+ update_stats = true;
+ }
+ changed = true;
+ }
+
+ // @todo should we call storeLease6ExtendedInfo() here ?
+ updateLease6ExtendedInfo(lease, ctx);
+ if (lease->extended_info_action_ == Lease6::ACTION_UPDATE) {
+ changed = true;
+ }
+
+ // Try to reuse the lease.
+ if (!changed) {
+ setLeaseReusable(lease, current_preferred_lft, ctx);
+ }
+
+ // Now that the lease has been reclaimed, we can go ahead and update it
+ // in the lease database.
+ if (lease->reuseable_valid_lft_ == 0) {
+ const auto& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false);
+ if (pool) {
+ lease->pool_id_ = pool->getID();
+ }
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+
+ if (update_stats) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds"),
+ static_cast<int64_t>(1));
+
+ const auto& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds")),
+ static_cast<int64_t>(1));
+ }
+
+ StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds",
+ static_cast<int64_t>(1));
+ }
+
+ } else {
+ // Copy back the original date to the lease. For MySQL it doesn't make
+ // much sense, but for memfile, the Lease6Ptr points to the actual lease
+ // in memfile, so the actual update is performed when we manipulate
+ // fields of returned Lease6Ptr, the actual updateLease6() is no-op.
+ *lease = *old_data;
+ }
+
+ // Add the old lease to the changed lease list. This allows the server
+ // to make decisions regarding DNS updates.
+ ctx.currentIA().changed_leases_.push_back(old_data);
+}
+
+Lease6Collection
+AllocEngine::updateLeaseData(ClientContext6& ctx, const Lease6Collection& leases) {
+ Lease6Collection updated_leases;
+ for (Lease6Collection::const_iterator lease_it = leases.begin();
+ lease_it != leases.end(); ++lease_it) {
+ Lease6Ptr lease(new Lease6(**lease_it));
+ if (ctx.currentIA().isNewResource(lease->addr_, lease->prefixlen_)) {
+ // This lease was just created so is already up to date.
+ updated_leases.push_back(lease);
+ continue;
+ }
+
+ lease->reuseable_valid_lft_ = 0;
+ lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+ lease->fqdn_rev_ = ctx.rev_dns_update_;
+ lease->hostname_ = ctx.hostname_;
+ uint32_t current_preferred_lft = lease->preferred_lft_;
+ if (lease->valid_lft_ == 0) {
+ // The lease was expired by a release: reset zero lifetimes.
+ getLifetimes6(ctx, lease->preferred_lft_, lease->valid_lft_);
+ }
+ if (!ctx.fake_allocation_) {
+ bool update_stats = false;
+
+ if (lease->state_ == Lease::STATE_EXPIRED_RECLAIMED) {
+ // Transition lease state to default (aka assigned)
+ lease->state_ = Lease::STATE_DEFAULT;
+
+ // If the lease is in the current subnet we need to account
+ // for the re-assignment of the lease.
+ if (inAllowedPool(ctx, ctx.currentIA().type_,
+ lease->addr_, true)) {
+ update_stats = true;
+ }
+ }
+
+ bool fqdn_changed = ((lease->type_ != Lease::TYPE_PD) &&
+ !(lease->hasIdenticalFqdn(**lease_it)));
+
+ lease->cltt_ = time(NULL);
+ if (!fqdn_changed) {
+ setLeaseReusable(lease, current_preferred_lft, ctx);
+ }
+ if (lease->reuseable_valid_lft_ == 0) {
+ ctx.currentIA().changed_leases_.push_back(*lease_it);
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+
+ if (update_stats) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_,
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", lease->subnet_id_,
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds"),
+ static_cast<int64_t>(1));
+
+ const auto& pool = ctx.subnet_->getPool(ctx.currentIA().type_, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds")),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "pool" : "pd-pool", pool->getID(),
+ ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds")),
+ static_cast<int64_t>(1));
+ }
+
+ StatsMgr::instance().addValue(ctx.currentIA().type_ == Lease::TYPE_NA ?
+ "cumulative-assigned-nas" : "cumulative-assigned-pds",
+ static_cast<int64_t>(1));
+ }
+ }
+
+ updated_leases.push_back(lease);
+ }
+
+ return (updated_leases);
+}
+
+void
+AllocEngine::reclaimExpiredLeases6(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_LEASES_RECLAMATION_START)
+ .arg(max_leases)
+ .arg(timeout);
+
+ try {
+ reclaimExpiredLeases6Internal(max_leases, timeout, remove_lease,
+ max_unwarned_cycles);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(alloc_engine_logger,
+ ALLOC_ENGINE_V6_LEASES_RECLAMATION_FAILED)
+ .arg(ex.what());
+ }
+}
+
+void
+AllocEngine::reclaimExpiredLeases6Internal(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles) {
+
+ // Create stopwatch and automatically start it to measure the time
+ // taken by the routine.
+ util::Stopwatch stopwatch;
+
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+
+ // This value indicates if we have been able to deal with all expired
+ // leases in this pass.
+ bool incomplete_reclamation = false;
+ Lease6Collection leases;
+ // The value of 0 has a special meaning - reclaim all.
+ if (max_leases > 0) {
+ // If the value is non-zero, the caller has limited the number of
+ // leases to reclaim. We obtain one lease more to see if there will
+ // be still leases left after this pass.
+ lease_mgr.getExpiredLeases6(leases, max_leases + 1);
+ // There are more leases expired leases than we will process in this
+ // pass, so we should mark it as an incomplete reclamation. We also
+ // remove this extra lease (which we don't want to process anyway)
+ // from the collection.
+ if (leases.size() > max_leases) {
+ leases.pop_back();
+ incomplete_reclamation = true;
+ }
+
+ } else {
+ // If there is no limitation on the number of leases to reclaim,
+ // we will try to process all. Hence, we don't mark it as incomplete
+ // reclamation just yet.
+ lease_mgr.getExpiredLeases6(leases, max_leases);
+ }
+
+ // Do not initialize the callout handle until we know if there are any
+ // lease6_expire callouts installed.
+ CalloutHandlePtr callout_handle;
+ if (!leases.empty() &&
+ HooksManager::calloutsPresent(Hooks.hook_index_lease6_expire_)) {
+ callout_handle = HooksManager::createCalloutHandle();
+ }
+
+ size_t leases_processed = 0;
+ BOOST_FOREACH(Lease6Ptr lease, leases) {
+
+ try {
+ // Reclaim the lease.
+ if (MultiThreadingMgr::instance().getMode()) {
+ // The reclamation is exclusive of packet processing.
+ WriteLockGuard exclusive(rw_mutex_);
+
+ reclaimExpiredLease(lease, remove_lease, callout_handle);
+ ++leases_processed;
+ } else {
+ reclaimExpiredLease(lease, remove_lease, callout_handle);
+ ++leases_processed;
+ }
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED)
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ }
+
+ // Check if we have hit the timeout for running reclamation routine and
+ // return if we have. We're checking it here, because we always want to
+ // allow reclaiming at least one lease.
+ if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) {
+ // Timeout. This will likely mean that we haven't been able to process
+ // all leases we wanted to process. The reclamation pass will be
+ // probably marked as incomplete.
+ if (!incomplete_reclamation) {
+ if (leases_processed < leases.size()) {
+ incomplete_reclamation = true;
+ }
+ }
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT)
+ .arg(timeout);
+ break;
+ }
+ }
+
+ // Stop measuring the time.
+ stopwatch.stop();
+
+ // Mark completion of the lease reclamation routine and present some stats.
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE)
+ .arg(leases_processed)
+ .arg(stopwatch.logFormatTotalDuration());
+
+ // Check if this was an incomplete reclamation and increase the number of
+ // consecutive incomplete reclamations.
+ if (incomplete_reclamation) {
+ ++incomplete_v6_reclamations_;
+ // If the number of incomplete reclamations is beyond the threshold, we
+ // need to issue a warning.
+ if ((max_unwarned_cycles > 0) &&
+ (incomplete_v6_reclamations_ > max_unwarned_cycles)) {
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW)
+ .arg(max_unwarned_cycles);
+ // We issued a warning, so let's now reset the counter.
+ incomplete_v6_reclamations_ = 0;
+ }
+
+ } else {
+ // This was a complete reclamation, so let's reset the counter.
+ incomplete_v6_reclamations_ = 0;
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES);
+ }
+}
+
+void
+AllocEngine::deleteExpiredReclaimedLeases6(const uint32_t secs) {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE)
+ .arg(secs);
+
+ uint64_t deleted_leases = 0;
+ try {
+ // Try to delete leases from the lease database.
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ deleted_leases = lease_mgr.deleteExpiredReclaimedLeases6(secs);
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED)
+ .arg(ex.what());
+ }
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE)
+ .arg(deleted_leases);
+}
+
+void
+AllocEngine::reclaimExpiredLeases4(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_LEASES_RECLAMATION_START)
+ .arg(max_leases)
+ .arg(timeout);
+
+ try {
+ reclaimExpiredLeases4Internal(max_leases, timeout, remove_lease,
+ max_unwarned_cycles);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(alloc_engine_logger,
+ ALLOC_ENGINE_V4_LEASES_RECLAMATION_FAILED)
+ .arg(ex.what());
+ }
+}
+
+void
+AllocEngine::reclaimExpiredLeases4Internal(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles) {
+
+ // Create stopwatch and automatically start it to measure the time
+ // taken by the routine.
+ util::Stopwatch stopwatch;
+
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+
+ // This value indicates if we have been able to deal with all expired
+ // leases in this pass.
+ bool incomplete_reclamation = false;
+ Lease4Collection leases;
+ // The value of 0 has a special meaning - reclaim all.
+ if (max_leases > 0) {
+ // If the value is non-zero, the caller has limited the number of
+ // leases to reclaim. We obtain one lease more to see if there will
+ // be still leases left after this pass.
+ lease_mgr.getExpiredLeases4(leases, max_leases + 1);
+ // There are more leases expired leases than we will process in this
+ // pass, so we should mark it as an incomplete reclamation. We also
+ // remove this extra lease (which we don't want to process anyway)
+ // from the collection.
+ if (leases.size() > max_leases) {
+ leases.pop_back();
+ incomplete_reclamation = true;
+ }
+
+ } else {
+ // If there is no limitation on the number of leases to reclaim,
+ // we will try to process all. Hence, we don't mark it as incomplete
+ // reclamation just yet.
+ lease_mgr.getExpiredLeases4(leases, max_leases);
+ }
+
+ // Do not initialize the callout handle until we know if there are any
+ // lease4_expire callouts installed.
+ CalloutHandlePtr callout_handle;
+ if (!leases.empty() &&
+ HooksManager::calloutsPresent(Hooks.hook_index_lease4_expire_)) {
+ callout_handle = HooksManager::createCalloutHandle();
+ }
+
+ size_t leases_processed = 0;
+ BOOST_FOREACH(Lease4Ptr lease, leases) {
+
+ try {
+ // Reclaim the lease.
+ if (MultiThreadingMgr::instance().getMode()) {
+ // The reclamation is exclusive of packet processing.
+ WriteLockGuard exclusive(rw_mutex_);
+
+ reclaimExpiredLease(lease, remove_lease, callout_handle);
+ ++leases_processed;
+ } else {
+ reclaimExpiredLease(lease, remove_lease, callout_handle);
+ ++leases_processed;
+ }
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED)
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ }
+
+ // Check if we have hit the timeout for running reclamation routine and
+ // return if we have. We're checking it here, because we always want to
+ // allow reclaiming at least one lease.
+ if ((timeout > 0) && (stopwatch.getTotalMilliseconds() >= timeout)) {
+ // Timeout. This will likely mean that we haven't been able to process
+ // all leases we wanted to process. The reclamation pass will be
+ // probably marked as incomplete.
+ if (!incomplete_reclamation) {
+ if (leases_processed < leases.size()) {
+ incomplete_reclamation = true;
+ }
+ }
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT)
+ .arg(timeout);
+ break;
+ }
+ }
+
+ // Stop measuring the time.
+ stopwatch.stop();
+
+ // Mark completion of the lease reclamation routine and present some stats.
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE)
+ .arg(leases_processed)
+ .arg(stopwatch.logFormatTotalDuration());
+
+ // Check if this was an incomplete reclamation and increase the number of
+ // consecutive incomplete reclamations.
+ if (incomplete_reclamation) {
+ ++incomplete_v4_reclamations_;
+ // If the number of incomplete reclamations is beyond the threshold, we
+ // need to issue a warning.
+ if ((max_unwarned_cycles > 0) &&
+ (incomplete_v4_reclamations_ > max_unwarned_cycles)) {
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW)
+ .arg(max_unwarned_cycles);
+ // We issued a warning, so let's now reset the counter.
+ incomplete_v4_reclamations_ = 0;
+ }
+
+ } else {
+ // This was a complete reclamation, so let's reset the counter.
+ incomplete_v4_reclamations_ = 0;
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES);
+ }
+}
+
+template<typename LeasePtrType>
+void
+AllocEngine::reclaimExpiredLease(const LeasePtrType& lease, const bool remove_lease,
+ const CalloutHandlePtr& callout_handle) {
+ reclaimExpiredLease(lease, remove_lease ? DB_RECLAIM_REMOVE : DB_RECLAIM_UPDATE,
+ callout_handle);
+}
+
+template<typename LeasePtrType>
+void
+AllocEngine::reclaimExpiredLease(const LeasePtrType& lease,
+ const CalloutHandlePtr& callout_handle) {
+ // This variant of the method is used by the code which allocates or
+ // renews leases. It may be the case that the lease has already been
+ // reclaimed, so there is nothing to do.
+ if (!lease->stateExpiredReclaimed()) {
+ reclaimExpiredLease(lease, DB_RECLAIM_LEAVE_UNCHANGED, callout_handle);
+ }
+}
+
+void
+AllocEngine::reclaimExpiredLease(const Lease6Ptr& lease,
+ const DbReclaimMode& reclaim_mode,
+ const CalloutHandlePtr& callout_handle) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V6_LEASE_RECLAIM)
+ .arg(Pkt6::makeLabel(lease->duid_, lease->hwaddr_))
+ .arg(lease->addr_.toText())
+ .arg(static_cast<int>(lease->prefixlen_));
+
+ // The skip flag indicates if the callouts have taken responsibility
+ // for reclaiming the lease. The callout will set this to true if
+ // it reclaims the lease itself. In this case the reclamation routine
+ // will not update DNS nor update the database.
+ bool skipped = false;
+ if (callout_handle) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ callout_handle->deleteAllArguments();
+ callout_handle->setArgument("lease6", lease);
+ callout_handle->setArgument("remove_lease", reclaim_mode == DB_RECLAIM_REMOVE);
+
+ HooksManager::callCallouts(Hooks.hook_index_lease6_expire_,
+ *callout_handle);
+
+ skipped = callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP;
+ }
+
+ /// DROP status does not make sense here.
+ /// Not sure if we need to support every possible status everywhere.
+
+ if (!skipped) {
+
+ // Generate removal name change request for D2, if required.
+ // This will return immediately if the DNS wasn't updated
+ // when the lease was created.
+ queueNCR(CHG_REMOVE, lease);
+
+ // Let's check if the lease that just expired is in DECLINED state.
+ // If it is, we need to perform a couple extra steps.
+ bool remove_lease = (reclaim_mode == DB_RECLAIM_REMOVE);
+ if (lease->state_ == Lease::STATE_DECLINED) {
+ // Do extra steps required for declined lease reclamation:
+ // - call the recover hook
+ // - bump decline-related stats
+ // - log separate message
+ // There's no point in keeping a declined lease after its
+ // reclamation. A declined lease doesn't have any client
+ // identifying information anymore. So we'll flag it for
+ // removal unless the hook has set the skip flag.
+ remove_lease = reclaimDeclined(lease);
+ }
+
+ if (reclaim_mode != DB_RECLAIM_LEAVE_UNCHANGED) {
+ // Reclaim the lease - depending on the configuration, set the
+ // expired-reclaimed state or simply remove it.
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ reclaimLeaseInDatabase<Lease6Ptr>(lease, remove_lease,
+ std::bind(&LeaseMgr::updateLease6,
+ &lease_mgr, ph::_1));
+ }
+ }
+
+ // Update statistics.
+
+ // Decrease number of assigned leases.
+ if (lease->type_ == Lease::TYPE_NA) {
+ // IA_NA
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ lease->subnet_id_,
+ "assigned-nas"),
+ static_cast<int64_t>(-1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(lease->type_, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "assigned-nas")),
+ static_cast<int64_t>(-1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "reclaimed-leases")),
+ static_cast<int64_t>(1));
+ }
+ }
+
+ } else if (lease->type_ == Lease::TYPE_PD) {
+ // IA_PD
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ lease->subnet_id_,
+ "assigned-pds"),
+ static_cast<int64_t>(-1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(lease->type_, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pd-pool" , pool->getID(),
+ "assigned-pds")),
+ static_cast<int64_t>(-1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pd-pool" , pool->getID(),
+ "reclaimed-leases")),
+ static_cast<int64_t>(1));
+ }
+ }
+ }
+
+ // Increase number of reclaimed leases for a subnet.
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ lease->subnet_id_,
+ "reclaimed-leases"),
+ static_cast<int64_t>(1));
+
+ // Increase total number of reclaimed leases.
+ StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1));
+}
+
+void
+AllocEngine::reclaimExpiredLease(const Lease4Ptr& lease,
+ const DbReclaimMode& reclaim_mode,
+ const CalloutHandlePtr& callout_handle) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_LEASE_RECLAIM)
+ .arg(Pkt4::makeLabel(lease->hwaddr_, lease->client_id_))
+ .arg(lease->addr_.toText());
+
+ // The skip flag indicates if the callouts have taken responsibility
+ // for reclaiming the lease. The callout will set this to true if
+ // it reclaims the lease itself. In this case the reclamation routine
+ // will not update DNS nor update the database.
+ bool skipped = false;
+ if (callout_handle) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ callout_handle->setArgument("lease4", lease);
+ callout_handle->setArgument("remove_lease", reclaim_mode == DB_RECLAIM_REMOVE);
+
+ HooksManager::callCallouts(Hooks.hook_index_lease4_expire_,
+ *callout_handle);
+
+ skipped = callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP;
+ }
+
+ /// DROP status does not make sense here.
+ /// Not sure if we need to support every possible status everywhere.
+
+ if (!skipped) {
+
+ // Generate removal name change request for D2, if required.
+ // This will return immediately if the DNS wasn't updated
+ // when the lease was created.
+ queueNCR(CHG_REMOVE, lease);
+ // Clear DNS fields so we avoid redundant removes.
+ lease->hostname_.clear();
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = false;
+
+ // Let's check if the lease that just expired is in DECLINED state.
+ // If it is, we need to perform a couple extra steps.
+ bool remove_lease = (reclaim_mode == DB_RECLAIM_REMOVE);
+ if (lease->state_ == Lease::STATE_DECLINED) {
+ // Do extra steps required for declined lease reclamation:
+ // - call the recover hook
+ // - bump decline-related stats
+ // - log separate message
+ // There's no point in keeping a declined lease after its
+ // reclamation. A declined lease doesn't have any client
+ // identifying information anymore. So we'll flag it for
+ // removal unless the hook has set the skip flag.
+ remove_lease = reclaimDeclined(lease);
+ }
+
+ if (reclaim_mode != DB_RECLAIM_LEAVE_UNCHANGED) {
+ // Reclaim the lease - depending on the configuration, set the
+ // expired-reclaimed state or simply remove it.
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ reclaimLeaseInDatabase<Lease4Ptr>(lease, remove_lease,
+ std::bind(&LeaseMgr::updateLease4,
+ &lease_mgr, ph::_1));
+ }
+ }
+
+ // Update statistics.
+
+ // Decrease number of assigned addresses.
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ lease->subnet_id_,
+ "assigned-addresses"),
+ static_cast<int64_t>(-1));
+
+ // Increase number of reclaimed leases for a subnet.
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ lease->subnet_id_,
+ "reclaimed-leases"),
+ static_cast<int64_t>(1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "assigned-addresses")),
+ static_cast<int64_t>(-1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "reclaimed-leases")),
+ static_cast<int64_t>(1));
+ }
+ }
+
+ // Increase total number of reclaimed leases.
+ StatsMgr::instance().addValue("reclaimed-leases", static_cast<int64_t>(1));
+}
+
+void
+AllocEngine::deleteExpiredReclaimedLeases4(const uint32_t secs) {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE)
+ .arg(secs);
+
+ uint64_t deleted_leases = 0;
+ try {
+ // Try to delete leases from the lease database.
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ deleted_leases = lease_mgr.deleteExpiredReclaimedLeases4(secs);
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED)
+ .arg(ex.what());
+ }
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE)
+ .arg(deleted_leases);
+}
+
+bool
+AllocEngine::reclaimDeclined(const Lease4Ptr& lease) {
+ if (!lease || (lease->state_ != Lease::STATE_DECLINED) ) {
+ return (true);
+ }
+
+ if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_recover_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Pass necessary arguments
+ callout_handle->setArgument("lease4", lease);
+
+ // Call the callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease4_recover_, *callout_handle);
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_RECOVER_SKIP)
+ .arg(lease->addr_.toText());
+ return (false);
+ }
+ }
+
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V4_DECLINED_RECOVERED)
+ .arg(lease->addr_.toText())
+ .arg(lease->valid_lft_);
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+
+ // Decrease subnet specific counter for currently declined addresses
+ stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_,
+ "declined-addresses"),
+ static_cast<int64_t>(-1));
+
+ stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_,
+ "reclaimed-declined-addresses"),
+ static_cast<int64_t>(1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(Lease::TYPE_V4, lease->addr_, false);
+ if (pool) {
+ stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "declined-addresses")),
+ static_cast<int64_t>(-1));
+
+ stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "reclaimed-declined-addresses")),
+ static_cast<int64_t>(1));
+ }
+ }
+
+ // Decrease global counter for declined addresses
+ stats_mgr.addValue("declined-addresses", static_cast<int64_t>(-1));
+
+ stats_mgr.addValue("reclaimed-declined-addresses", static_cast<int64_t>(1));
+
+ // Note that we do not touch assigned-addresses counters. Those are
+ // modified in whatever code calls this method.
+ return (true);
+}
+
+bool
+AllocEngine::reclaimDeclined(const Lease6Ptr& lease) {
+ if (!lease || (lease->state_ != Lease::STATE_DECLINED) ) {
+ return (true);
+ }
+
+ if (HooksManager::calloutsPresent(Hooks.hook_index_lease6_recover_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Pass necessary arguments
+ callout_handle->setArgument("lease6", lease);
+
+ // Call the callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease6_recover_, *callout_handle);
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_RECOVER_SKIP)
+ .arg(lease->addr_.toText());
+ return (false);
+ }
+ }
+
+ LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_DECLINED_RECOVERED)
+ .arg(lease->addr_.toText())
+ .arg(lease->valid_lft_);
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+
+ // Decrease subnet specific counter for currently declined addresses
+ stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_,
+ "declined-addresses"),
+ static_cast<int64_t>(-1));
+
+ stats_mgr.addValue(StatsMgr::generateName("subnet", lease->subnet_id_,
+ "reclaimed-declined-addresses"),
+ static_cast<int64_t>(1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getBySubnetId(lease->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(lease->type_, lease->addr_, false);
+ if (pool) {
+ stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "declined-addresses")),
+ static_cast<int64_t>(-1));
+
+ stats_mgr.addValue(StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool" , pool->getID(),
+ "reclaimed-declined-addresses")),
+ static_cast<int64_t>(1));
+ }
+ }
+
+ // Decrease global counter for declined addresses
+ stats_mgr.addValue("declined-addresses", static_cast<int64_t>(-1));
+
+ stats_mgr.addValue("reclaimed-declined-addresses", static_cast<int64_t>(1));
+
+ // Note that we do not touch assigned-nas counters. Those are
+ // modified in whatever code calls this method.
+
+ return (true);
+}
+
+void
+AllocEngine::clearReclaimedExtendedInfo(const Lease4Ptr& lease) const {
+ lease->relay_id_.clear();
+ lease->remote_id_.clear();
+ if (lease->getContext()) {
+ lease->setContext(ElementPtr());
+ }
+}
+
+void
+AllocEngine::clearReclaimedExtendedInfo(const Lease6Ptr& lease) const {
+ if (lease->getContext()) {
+ lease->extended_info_action_ = Lease6::ACTION_DELETE;
+ lease->setContext(ElementPtr());
+ }
+}
+
+template<typename LeasePtrType>
+void AllocEngine::reclaimLeaseInDatabase(const LeasePtrType& lease,
+ const bool remove_lease,
+ const std::function<void (const LeasePtrType&)>&
+ lease_update_fun) const {
+
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+
+ // Reclaim the lease - depending on the configuration, set the
+ // expired-reclaimed state or simply remove it.
+ if (remove_lease) {
+ lease_mgr.deleteLease(lease);
+ } else if (lease_update_fun) {
+ // Clear FQDN information as we have already sent the
+ // name change request to remove the DNS record.
+ lease->reuseable_valid_lft_ = 0;
+ lease->hostname_.clear();
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = false;
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ clearReclaimedExtendedInfo(lease);
+ lease_update_fun(lease);
+
+ } else {
+ return;
+ }
+
+ // Lease has been reclaimed.
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_LEASE_RECLAIMED)
+ .arg(lease->addr_.toText());
+}
+
+std::string
+AllocEngine::labelNetworkOrSubnet(SubnetPtr subnet) {
+ if (!subnet) {
+ return("<empty subnet>");
+ }
+
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ std::ostringstream ss;
+ if (network) {
+ ss << "shared-network: " << network->getName();
+ } else {
+ ss << "subnet id: " << subnet->getID();
+ }
+
+ return(ss.str());
+}
+
+} // namespace dhcp
+} // namespace isc
+
+// ##########################################################################
+// # DHCPv4 lease allocation code starts here.
+// ##########################################################################
+
+namespace {
+
+/// @brief Check if the specific address is reserved for another client.
+///
+/// This function finds a host reservation for a given address and then
+/// it verifies if the host identifier for this reservation is matching
+/// a host identifier found for the current client. If it does not, the
+/// address is assumed to be reserved for another client.
+///
+/// @note If reservations-out-of-pool flag is enabled, dynamic address that
+/// match reservations from within the dynamic pool will not be prevented to
+/// be assigned to any client.
+///
+/// @param address An address for which the function should check if
+/// there is a reservation for the different client.
+/// @param ctx Client context holding the data extracted from the
+/// client's message.
+///
+/// @return true if the address is reserved for another client.
+bool
+addressReserved(const IOAddress& address, const AllocEngine::ClientContext4& ctx) {
+ // When out-of-pool flag is true the server may assume that all host
+ // reservations are for addresses that do not belong to the dynamic pool.
+ // Therefore, it can skip the reservation checks when dealing with in-pool
+ // addresses.
+ if (ctx.subnet_ && ctx.subnet_->getReservationsInSubnet() &&
+ (!ctx.subnet_->getReservationsOutOfPool() ||
+ !ctx.subnet_->inPool(Lease::TYPE_V4, address))) {
+ // The global parameter ip-reservations-unique controls whether it is allowed
+ // to specify multiple reservations for the same IP address or delegated prefix
+ // or IP reservations must be unique. Some host backends do not support the
+ // former, thus we can't always use getAll4 calls to get the reservations
+ // for the given IP. When we're in the default mode, when IP reservations
+ // are unique, we should call get4 (supported by all backends). If we're in
+ // the mode in which non-unique reservations are allowed the backends which
+ // don't support it are not used and we can safely call getAll4.
+ ConstHostCollection hosts;
+ if (CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique()) {
+ // Reservations are unique. It is safe to call get4 to get the unique host.
+ ConstHostPtr host = HostMgr::instance().get4(ctx.subnet_->getID(), address);
+ if (host) {
+ hosts.push_back(host);
+ }
+ } else {
+ // Reservations can be non-unique. Need to get all reservations for that address.
+ hosts = HostMgr::instance().getAll4(ctx.subnet_->getID(), address);
+ }
+
+ for (auto host : hosts) {
+ for (const AllocEngine::IdentifierPair& id_pair : ctx.host_identifiers_) {
+ // If we find the matching host we know that this address is reserved
+ // for us and we can return immediately.
+ if (id_pair.first == host->getIdentifierType() &&
+ id_pair.second == host->getIdentifier()) {
+ return (false);
+ }
+ }
+ }
+ // We didn't find a matching host. If there are any reservations it means that
+ // address is reserved for another client or multiple clients. If there are
+ // no reservations address is not reserved for another client.
+ return (!hosts.empty());
+ }
+ return (false);
+}
+
+/// @brief Check if the context contains the reservation for the
+/// IPv4 address.
+///
+/// This convenience function checks if the context contains the reservation
+/// for the IPv4 address. Note that some reservations may not assign a
+/// static IPv4 address to the clients, but may rather reserve a hostname.
+/// Allocation engine should check if the existing reservation is made
+/// for the IPv4 address and if it is not, allocate the address from the
+/// dynamic pool. The allocation engine uses this function to check if
+/// the reservation is made for the IPv4 address.
+///
+/// @param [out] ctx Client context holding the data extracted from the
+/// client's message.
+///
+/// @return true if the context contains the reservation for the IPv4 address.
+bool
+hasAddressReservation(AllocEngine::ClientContext4& ctx) {
+ if (ctx.hosts_.empty()) {
+ return (false);
+ }
+
+ // Fetch the globally reserved address if there is one.
+ auto global_host = ctx.hosts_.find(SUBNET_ID_GLOBAL);
+ auto global_host_address = ((global_host != ctx.hosts_.end() && global_host->second) ?
+ global_host->second->getIPv4Reservation() :
+ IOAddress::IPV4_ZERO_ADDRESS());
+
+ // Start with currently selected subnet.
+ Subnet4Ptr subnet = ctx.subnet_;
+ while (subnet) {
+ // If global reservations are enabled for this subnet and there is
+ // globally reserved address and that address is feasible for this
+ // subnet, update the selected subnet and return true.
+ if (subnet->getReservationsGlobal() &&
+ (global_host_address != IOAddress::IPV4_ZERO_ADDRESS()) &&
+ (subnet->inRange(global_host_address))) {
+ ctx.subnet_ = subnet;
+ return (true);
+ }
+
+ if (subnet->getReservationsInSubnet()) {
+ auto host = ctx.hosts_.find(subnet->getID());
+ // The out-of-pool flag indicates that no client should be assigned
+ // reserved addresses from within the dynamic pool, and for that
+ // reason look only for reservations that are outside the pools,
+ // hence the inPool check.
+ if (host != ctx.hosts_.end() && host->second) {
+ auto reservation = host->second->getIPv4Reservation();
+ if (!reservation.isV4Zero() &&
+ (!subnet->getReservationsOutOfPool() ||
+ !subnet->inPool(Lease::TYPE_V4, reservation))) {
+ ctx.subnet_ = subnet;
+ return (true);
+ }
+ }
+ }
+
+ // No address reservation found here, so let's try another subnet
+ // within the same shared network.
+ subnet = subnet->getNextSubnet(ctx.subnet_, ctx.query_->getClasses());
+ }
+
+ if (global_host_address != IOAddress::IPV4_ZERO_ADDRESS()) {
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS)
+ .arg(ctx.currentHost()->getIPv4Reservation().toText())
+ .arg(AllocEngine::labelNetworkOrSubnet(ctx.subnet_));
+ }
+
+ return (false);
+}
+
+/// @brief Finds existing lease in the database.
+///
+/// This function searches for the lease in the database which belongs to the
+/// client requesting allocation. If the client has supplied the client
+/// identifier this identifier is used to look up the lease. If the lease is
+/// not found using the client identifier, an additional lookup is performed
+/// using the HW address, if supplied. If the lease is found using the HW
+/// address, the function also checks if the lease belongs to the client, i.e.
+/// there is no conflict between the client identifiers.
+///
+/// @param [out] ctx Context holding data extracted from the client's message,
+/// including the HW address and client identifier. The current subnet may be
+/// modified by this function if it belongs to a shared network.
+/// @param [out] client_lease A pointer to the lease returned by this function
+/// or null value if no has been lease found.
+void findClientLease(AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) {
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+
+ Subnet4Ptr original_subnet = ctx.subnet_;
+
+ auto const& classes = ctx.query_->getClasses();
+
+ // Client identifier is optional. First check if we can try to lookup
+ // by client-id.
+ bool try_clientid_lookup = (ctx.clientid_ &&
+ SharedNetwork4::subnetsIncludeMatchClientId(original_subnet, classes));
+
+ // If it is possible to use client identifier to try to find client's lease.
+ if (try_clientid_lookup) {
+ // Get all leases for this client identifier. When shared networks are
+ // in use it is more efficient to make a single query rather than
+ // multiple queries, one for each subnet.
+ Lease4Collection leases_client_id = lease_mgr.getLease4(*ctx.clientid_);
+
+ // Iterate over the subnets within the shared network to see if any client's
+ // lease belongs to them.
+ for (Subnet4Ptr subnet = original_subnet; subnet;
+ subnet = subnet->getNextSubnet(original_subnet, classes)) {
+
+ // If client identifier has been supplied and the server wasn't
+ // explicitly configured to ignore client identifiers for this subnet
+ // check if there is a lease within this subnet.
+ if (subnet->getMatchClientId()) {
+ for (auto l = leases_client_id.begin(); l != leases_client_id.end(); ++l) {
+ if ((*l)->subnet_id_ == subnet->getID()) {
+ // Lease found, so stick to this lease.
+ client_lease = (*l);
+ ctx.subnet_ = subnet;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // If no lease found using the client identifier, try the lookup using
+ // the HW address.
+ if (!client_lease && ctx.hwaddr_) {
+
+ // Get all leases for this HW address.
+ Lease4Collection leases_hw_address = lease_mgr.getLease4(*ctx.hwaddr_);
+
+ for (Subnet4Ptr subnet = original_subnet; subnet;
+ subnet = subnet->getNextSubnet(original_subnet, classes)) {
+ ClientIdPtr client_id;
+ if (subnet->getMatchClientId()) {
+ client_id = ctx.clientid_;
+ }
+
+ // Try to find the lease that matches current subnet and belongs to
+ // this client, so both HW address and client identifier match.
+ for (Lease4Collection::const_iterator client_lease_it = leases_hw_address.begin();
+ client_lease_it != leases_hw_address.end(); ++client_lease_it) {
+ Lease4Ptr existing_lease = *client_lease_it;
+ if ((existing_lease->subnet_id_ == subnet->getID()) &&
+ existing_lease->belongsToClient(ctx.hwaddr_, client_id)) {
+ // Found the lease of this client, so return it.
+ client_lease = existing_lease;
+ // We got a lease but the subnet it belongs to may differ from
+ // the original subnet. Let's now stick to this subnet.
+ ctx.subnet_ = subnet;
+ return;
+ }
+ }
+ }
+ }
+}
+
+/// @brief Checks if the specified address belongs to one of the subnets
+/// within a shared network.
+///
+/// @todo Update this function to take client classification into account.
+/// @note client classification in pools (vs subnets) is checked
+///
+/// @param ctx Client context. Current subnet may be modified by this
+/// function when it belongs to a shared network.
+/// @param address IPv4 address to be checked.
+///
+/// @return true if address belongs to a pool in a selected subnet or in
+/// a pool within any of the subnets belonging to the current shared network.
+bool
+inAllowedPool(AllocEngine::ClientContext4& ctx, const IOAddress& address) {
+ // If the subnet belongs to a shared network we will be iterating
+ // over the subnets that belong to this shared network.
+ Subnet4Ptr current_subnet = ctx.subnet_;
+ auto const& classes = ctx.query_->getClasses();
+
+ while (current_subnet) {
+ if (current_subnet->inPool(Lease::TYPE_V4, address, classes)) {
+ // We found a subnet that this address belongs to, so it
+ // seems that this subnet is the good candidate for allocation.
+ // Let's update the selected subnet.
+ ctx.subnet_ = current_subnet;
+ return (true);
+ }
+
+ current_subnet = current_subnet->getNextSubnet(ctx.subnet_, classes);
+ }
+
+ return (false);
+}
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+
+AllocEngine::ClientContext4::ClientContext4()
+ : early_global_reservations_lookup_(false),
+ subnet_(), clientid_(), hwaddr_(),
+ requested_address_(IOAddress::IPV4_ZERO_ADDRESS()),
+ fwd_dns_update_(false), rev_dns_update_(false),
+ hostname_(""), callout_handle_(), fake_allocation_(false), offer_lft_(0),
+ old_lease_(), new_lease_(), hosts_(), conflicting_lease_(),
+ query_(), host_identifiers_(), unknown_requested_addr_(false),
+ ddns_params_() {
+
+}
+
+AllocEngine::ClientContext4::ClientContext4(const Subnet4Ptr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const asiolink::IOAddress& requested_addr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const bool fake_allocation,
+ const uint32_t offer_lft)
+ : early_global_reservations_lookup_(false),
+ subnet_(subnet), clientid_(clientid), hwaddr_(hwaddr),
+ requested_address_(requested_addr),
+ fwd_dns_update_(fwd_dns_update), rev_dns_update_(rev_dns_update),
+ hostname_(hostname), callout_handle_(),
+ fake_allocation_(fake_allocation), offer_lft_(offer_lft), old_lease_(), new_lease_(),
+ hosts_(), host_identifiers_(), unknown_requested_addr_(false),
+ ddns_params_(new DdnsParams()) {
+
+ // Initialize host identifiers.
+ if (hwaddr) {
+ addHostIdentifier(Host::IDENT_HWADDR, hwaddr->hwaddr_);
+ }
+}
+
+ConstHostPtr
+AllocEngine::ClientContext4::currentHost() const {
+ if (subnet_ && subnet_->getReservationsInSubnet()) {
+ auto host = hosts_.find(subnet_->getID());
+ if (host != hosts_.cend()) {
+ return (host->second);
+ }
+ }
+
+ return (globalHost());
+}
+
+ConstHostPtr
+AllocEngine::ClientContext4::globalHost() const {
+ if (subnet_ && subnet_->getReservationsGlobal()) {
+ auto host = hosts_.find(SUBNET_ID_GLOBAL);
+ if (host != hosts_.cend()) {
+ return (host->second);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+DdnsParamsPtr
+AllocEngine::ClientContext4::getDdnsParams() {
+ // We already have it return it unless the context subnet has changed.
+ if (ddns_params_ && subnet_ && (subnet_->getID() == ddns_params_->getSubnetId())) {
+ return (ddns_params_);
+ }
+
+ // Doesn't exist yet or is stale, (re)create it.
+ if (subnet_) {
+ ddns_params_ = CfgMgr::instance().getCurrentCfg()->getDdnsParams(subnet_);
+ return (ddns_params_);
+ }
+
+ // Asked for it without a subnet? This case really shouldn't occur but
+ // for now let's return an instance with default values.
+ return (DdnsParamsPtr(new DdnsParams()));
+}
+
+Lease4Ptr
+AllocEngine::allocateLease4(ClientContext4& ctx) {
+ // The NULL pointer indicates that the old lease didn't exist. It may
+ // be later set to non NULL value if existing lease is found in the
+ // database.
+ ctx.old_lease_.reset();
+ ctx.new_lease_.reset();
+
+ // Before we start allocation process, we need to make sure that the
+ // selected subnet is allowed for this client. If not, we'll try to
+ // use some other subnet within the shared network. If there are no
+ // subnets allowed for this client within the shared network, we
+ // can't allocate a lease.
+ Subnet4Ptr subnet = ctx.subnet_;
+ auto const& classes = ctx.query_->getClasses();
+ if (subnet && !subnet->clientSupported(classes)) {
+ ctx.subnet_ = subnet->getNextSubnet(subnet, classes);
+ }
+
+ try {
+ if (!ctx.subnet_) {
+ isc_throw(BadValue, "Can't allocate IPv4 address without subnet");
+ }
+
+ if (!ctx.hwaddr_) {
+ isc_throw(BadValue, "HWAddr must be defined");
+ }
+
+ if (ctx.fake_allocation_) {
+ return (discoverLease4(ctx));
+ } else {
+ ctx.new_lease_ = requestLease4(ctx);
+ }
+
+ } catch (const isc::Exception& e) {
+ // Some other error, return an empty lease.
+ LOG_ERROR(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_ERROR)
+ .arg(ctx.query_->getLabel())
+ .arg(e.what());
+ }
+
+ return (ctx.new_lease_);
+}
+
+void
+AllocEngine::findReservation(ClientContext4& ctx) {
+ // If there is no subnet, there is nothing to do.
+ if (!ctx.subnet_) {
+ return;
+ }
+
+ auto subnet = ctx.subnet_;
+
+ // If already done just return.
+ if (ctx.early_global_reservations_lookup_ &&
+ !subnet->getReservationsInSubnet()) {
+ return;
+ }
+
+ // @todo: This code can be trivially optimized.
+ if (!ctx.early_global_reservations_lookup_ &&
+ subnet->getReservationsGlobal()) {
+ ConstHostPtr ghost = findGlobalReservation(ctx);
+ if (ghost) {
+ ctx.hosts_[SUBNET_ID_GLOBAL] = ghost;
+
+ // If we had only to fetch global reservations it is done.
+ if (!subnet->getReservationsInSubnet()) {
+ return;
+ }
+ }
+ }
+
+ std::map<SubnetID, ConstHostPtr> host_map;
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+
+ // If the subnet belongs to a shared network it is usually going to be
+ // more efficient to make a query for all reservations for a particular
+ // client rather than a query for each subnet within this shared network.
+ // The only case when it is going to be less efficient is when there are
+ // more host identifier types in use than subnets within a shared network.
+ // As it breaks RADIUS use of host caching this can be disabled by the
+ // host manager.
+ const bool use_single_query = network &&
+ !HostMgr::instance().getDisableSingleQuery() &&
+ (network->getAllSubnets()->size() > ctx.host_identifiers_.size());
+
+ if (use_single_query) {
+ for (const IdentifierPair& id_pair : ctx.host_identifiers_) {
+ ConstHostCollection hosts = HostMgr::instance().getAll(id_pair.first,
+ &id_pair.second[0],
+ id_pair.second.size());
+ // Store the hosts in the temporary map, because some hosts may
+ // belong to subnets outside of the shared network. We'll need
+ // to eliminate them.
+ for (auto host = hosts.begin(); host != hosts.end(); ++host) {
+ if ((*host)->getIPv4SubnetID() != SUBNET_ID_GLOBAL) {
+ host_map[(*host)->getIPv4SubnetID()] = *host;
+ }
+ }
+ }
+ }
+
+ auto const& classes = ctx.query_->getClasses();
+ // We can only search for the reservation if a subnet has been selected.
+ while (subnet) {
+
+ // Only makes sense to get reservations if the client has access
+ // to the class and host reservations are enabled for this subnet.
+ if (subnet->clientSupported(classes) && subnet->getReservationsInSubnet()) {
+ // Iterate over configured identifiers in the order of preference
+ // and try to use each of them to search for the reservations.
+ if (use_single_query) {
+ if (host_map.count(subnet->getID()) > 0) {
+ ctx.hosts_[subnet->getID()] = host_map[subnet->getID()];
+ }
+ } else {
+ for (const IdentifierPair& id_pair : ctx.host_identifiers_) {
+ // Attempt to find a host using a specified identifier.
+ ConstHostPtr host = HostMgr::instance().get4(subnet->getID(),
+ id_pair.first,
+ &id_pair.second[0],
+ id_pair.second.size());
+ // If we found matching host for this subnet.
+ if (host) {
+ ctx.hosts_[subnet->getID()] = host;
+ break;
+ }
+ }
+ }
+ }
+
+ // We need to get to the next subnet if this is a shared network. If it
+ // is not (a plain subnet), getNextSubnet will return NULL and we're
+ // done here.
+ subnet = subnet->getNextSubnet(ctx.subnet_, classes);
+ }
+
+ // The hosts can be used by the server to return reserved options to
+ // the DHCP client. Such options must be encapsulated (i.e., they must
+ // include suboptions).
+ for (auto host : ctx.hosts_) {
+ host.second->encapsulateOptions();
+ }
+}
+
+ConstHostPtr
+AllocEngine::findGlobalReservation(ClientContext4& ctx) {
+ ConstHostPtr host;
+ for (const IdentifierPair& id_pair : ctx.host_identifiers_) {
+ // Attempt to find a host using a specified identifier.
+ host = HostMgr::instance().get4(SUBNET_ID_GLOBAL, id_pair.first,
+ &id_pair.second[0], id_pair.second.size());
+
+ // If we found matching global host we're done.
+ if (host) {
+ break;
+ }
+ }
+
+ return (host);
+}
+
+Lease4Ptr
+AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
+ // Find an existing lease for this client. This function will return null
+ // if there is a conflict with existing lease and the allocation should
+ // not be continued.
+ Lease4Ptr client_lease;
+ findClientLease(ctx, client_lease);
+
+ // Fetch offer_lft to see if we're allocating on DISCOVER.
+ ctx.offer_lft_ = getOfferLft(ctx);
+
+ // new_lease will hold the pointer to the lease that we will offer to the
+ // caller.
+ Lease4Ptr new_lease;
+
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+
+ // Check if there is a reservation for the client. If there is, we want to
+ // assign the reserved address, rather than any other one.
+ if (hasAddressReservation(ctx)) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_DISCOVER_HR)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.currentHost()->getIPv4Reservation().toText());
+
+ // If the client doesn't have a lease or the leased address is different
+ // than the reserved one then let's try to allocate the reserved address.
+ // Otherwise the address that the client has is the one for which it
+ // has a reservation, so just renew it.
+ if (!client_lease || (client_lease->addr_ != ctx.currentHost()->getIPv4Reservation())) {
+ // The call below will return a pointer to the lease for the address
+ // reserved to this client, if the lease is available, i.e. is not
+ // currently assigned to any other client.
+ // Note that we don't remove the existing client's lease at this point
+ // because this is not a real allocation, we just offer what we can
+ // allocate in the DHCPREQUEST time.
+ new_lease = allocateOrReuseLease4(ctx.currentHost()->getIPv4Reservation(), ctx,
+ callout_status);
+ if (!new_lease) {
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.currentHost()->getIPv4Reservation().toText())
+ .arg(ctx.conflicting_lease_ ? ctx.conflicting_lease_->toText() :
+ "(no lease info)");
+ StatsMgr::instance().addValue(StatsMgr::generateName("subnet",
+ ctx.conflicting_lease_->subnet_id_,
+ "v4-reservation-conflicts"),
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue("v4-reservation-conflicts",
+ static_cast<int64_t>(1));
+ }
+
+ } else {
+ new_lease = renewLease4(client_lease, ctx);
+ }
+ }
+
+ // Client does not have a reservation or the allocation of the reserved
+ // address has failed, probably because the reserved address is in use
+ // by another client. If the client has a lease, we will check if we can
+ // offer this lease to the client. The lease can't be offered in the
+ // situation when it is reserved for another client or when the address
+ // is not in the dynamic pool. The former may be the result of adding the
+ // new reservation for the address used by this client. The latter may
+ // be due to the client using the reserved out-of-the pool address, for
+ // which the reservation has just been removed.
+ if (!new_lease && client_lease && inAllowedPool(ctx, client_lease->addr_) &&
+ !addressReserved(client_lease->addr_, ctx)) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE)
+ .arg(ctx.query_->getLabel());
+
+ // If offer-lifetime is shorter than the existing expiration, reset
+ // offer-lifetime to zero. This allows us to simply return the
+ // existing lease without updating it in the lease store.
+ if ((ctx.offer_lft_) &&
+ (time(NULL) + ctx.offer_lft_ < client_lease->getExpirationTime())) {
+ ctx.offer_lft_ = 0;
+ }
+
+ new_lease = renewLease4(client_lease, ctx);
+ }
+
+ // The client doesn't have any lease or the lease can't be offered
+ // because it is either reserved for some other client or the
+ // address is not in the dynamic pool.
+ // Let's use the client's hint (requested IP address), if the client
+ // has provided it, and try to offer it. This address must not be
+ // reserved for another client, and must be in the range of the
+ // dynamic pool.
+ if (!new_lease && !ctx.requested_address_.isV4Zero() &&
+ inAllowedPool(ctx, ctx.requested_address_) &&
+ !addressReserved(ctx.requested_address_, ctx)) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE)
+ .arg(ctx.requested_address_.toText())
+ .arg(ctx.query_->getLabel());
+
+ new_lease = allocateOrReuseLease4(ctx.requested_address_, ctx,
+ callout_status);
+ }
+
+ // The allocation engine failed to allocate all of the candidate
+ // addresses. We will now use the allocator to pick the address
+ // from the dynamic pool.
+ if (!new_lease) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_OFFER_NEW_LEASE)
+ .arg(ctx.query_->getLabel());
+
+ new_lease = allocateUnreservedLease4(ctx);
+ }
+
+ // Some of the methods like reuseExpiredLease4 may set the old lease to point
+ // to the lease which they remove/override. If it is not set, but we have
+ // found that the client has the lease the client's lease is the one
+ // to return as an old lease.
+ if (!ctx.old_lease_ && client_lease) {
+ ctx.old_lease_ = client_lease;
+ }
+
+ return (new_lease);
+}
+
+Lease4Ptr
+AllocEngine::requestLease4(AllocEngine::ClientContext4& ctx) {
+ // Find an existing lease for this client. This function will return null
+ // if there is a conflict with existing lease and the allocation should
+ // not be continued.
+ Lease4Ptr client_lease;
+ findClientLease(ctx, client_lease);
+
+ // When the client sends the DHCPREQUEST, it should always specify the
+ // address which it is requesting or renewing. That is, the client should
+ // either use the requested IP address option or set the ciaddr. However,
+ // we try to be liberal and allow the clients to not specify an address
+ // in which case the allocation engine will pick a suitable address
+ // for the client.
+ if (!ctx.requested_address_.isV4Zero()) {
+ // If the client has specified an address, make sure this address
+ // is not reserved for another client. If it is, stop here because
+ // we can't allocate this address.
+ if (addressReserved(ctx.requested_address_, ctx)) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.requested_address_.toText());
+
+ return (Lease4Ptr());
+ }
+
+ } else if (hasAddressReservation(ctx)) {
+ // The client hasn't specified an address to allocate, so the
+ // allocation engine needs to find an appropriate address.
+ // If there is a reservation for the client, let's try to
+ // allocate the reserved address.
+ ctx.requested_address_ = ctx.currentHost()->getIPv4Reservation();
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_USE_HR)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.requested_address_.toText());
+ }
+
+ if (!ctx.requested_address_.isV4Zero()) {
+ // There is a specific address to be allocated. Let's find out if
+ // the address is in use.
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(ctx.requested_address_);
+ // If the address is in use (allocated and not expired), we check
+ // if the address is in use by our client or another client.
+ // If it is in use by another client, the address can't be
+ // allocated.
+ if (existing && !existing->expired() &&
+ !existing->belongsToClient(ctx.hwaddr_, ctx.subnet_->getMatchClientId() ?
+ ctx.clientid_ : ClientIdPtr())) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_IN_USE)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.requested_address_.toText());
+
+ return (Lease4Ptr());
+ }
+
+ // If the client has a reservation but it is requesting a different
+ // address it is possible that the client was offered this different
+ // address because the reserved address is in use. We will have to
+ // check if the address is in use.
+ if (hasAddressReservation(ctx) &&
+ (ctx.currentHost()->getIPv4Reservation() != ctx.requested_address_)) {
+ existing =
+ LeaseMgrFactory::instance().getLease4(ctx.currentHost()->getIPv4Reservation());
+ // If the reserved address is not in use, i.e. the lease doesn't
+ // exist or is expired, and the client is requesting a different
+ // address, return NULL. The client should go back to the
+ // DHCPDISCOVER and the reserved address will be offered.
+ if (!existing || existing->expired()) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_INVALID)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.currentHost()->getIPv4Reservation().toText())
+ .arg(ctx.requested_address_.toText());
+
+ return (Lease4Ptr());
+ }
+ }
+
+ // The use of the out-of-pool addresses is only allowed when the requested
+ // address is reserved for the client. If the address is not reserved one
+ // and it doesn't belong to the dynamic pool, do not allocate it.
+ if ((!hasAddressReservation(ctx) ||
+ (ctx.currentHost()->getIPv4Reservation() != ctx.requested_address_)) &&
+ !inAllowedPool(ctx, ctx.requested_address_)) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.requested_address_);
+
+ ctx.unknown_requested_addr_ = true;
+ return (Lease4Ptr());
+ }
+ }
+
+ // We have gone through all the checks, so we can now allocate the address
+ // for the client.
+
+ // If the client is requesting an address which is assigned to the client
+ // let's just renew this address. Also, renew this address if the client
+ // doesn't request any specific address.
+ // Added extra checks: the address is reserved for this client or belongs
+ // to the dynamic pool for the case the pool class has changed before the
+ // request.
+ if (client_lease) {
+ if (((client_lease->addr_ == ctx.requested_address_) ||
+ ctx.requested_address_.isV4Zero()) &&
+ ((hasAddressReservation(ctx) &&
+ (ctx.currentHost()->getIPv4Reservation() == ctx.requested_address_)) ||
+ inAllowedPool(ctx, client_lease->addr_))) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.requested_address_);
+
+ return (renewLease4(client_lease, ctx));
+ }
+ }
+
+ // new_lease will hold the pointer to the allocated lease if we allocate
+ // successfully.
+ Lease4Ptr new_lease;
+
+ // The client doesn't have the lease or it is requesting an address
+ // which it doesn't have. Let's try to allocate the requested address.
+ if (!ctx.requested_address_.isV4Zero()) {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.requested_address_.toText());
+
+ // The call below will return a pointer to the lease allocated
+ // for the client if there is no lease for the requested address,
+ // or the existing lease has expired. If the allocation fails,
+ // e.g. because the lease is in use, we will return NULL to
+ // indicate that we were unable to allocate the lease.
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+ new_lease = allocateOrReuseLease4(ctx.requested_address_, ctx,
+ callout_status);
+
+ } else {
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS)
+ .arg(ctx.query_->getLabel());
+
+ // We will only get here if the client didn't specify which
+ // address it wanted to be allocated. The allocation engine will
+ // to pick the address from the dynamic pool.
+ new_lease = allocateUnreservedLease4(ctx);
+ }
+
+ // If we allocated the lease for the client, but the client already had a
+ // lease, we will need to return the pointer to the previous lease and
+ // the previous lease needs to be removed from the lease database.
+ if (new_lease && client_lease) {
+ ctx.old_lease_ = Lease4Ptr(new Lease4(*client_lease));
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE,
+ ALLOC_ENGINE_V4_REQUEST_REMOVE_LEASE)
+ .arg(ctx.query_->getLabel())
+ .arg(client_lease->addr_.toText());
+
+ if (LeaseMgrFactory::instance().deleteLease(client_lease)) {
+ // Need to decrease statistic for assigned addresses.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", client_lease->subnet_id_,
+ "assigned-addresses"),
+ static_cast<int64_t>(-1));
+
+ const auto& subnet = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getBySubnetId(client_lease->subnet_id_);
+ if (subnet) {
+ const auto& pool = subnet->getPool(Lease::TYPE_V4, client_lease->addr_, false);
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", subnet->getID(),
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-addresses")),
+ static_cast<int64_t>(-1));
+ }
+ }
+ }
+ }
+
+ // Return the allocated lease or NULL pointer if allocation was
+ // unsuccessful.
+ return (new_lease);
+}
+
+uint32_t
+AllocEngine::getOfferLft(const ClientContext4& ctx) {
+ // Not a DISCOVER or it's BOOTP, punt.
+ if ((!ctx.fake_allocation_) || (ctx.query_->inClass("BOOTP"))) {
+ return (0);
+ }
+
+ util::Optional<uint32_t> offer_lft;
+
+ // If specified in one of our classes use it.
+ // We use the first one we find.
+ const ClientClasses classes = ctx.query_->getClasses();
+ if (!classes.empty()) {
+ // Let's get class definitions
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+
+ // Iterate over the assigned class definitions.
+ for (ClientClasses::const_iterator name = classes.cbegin();
+ name != classes.cend(); ++name) {
+ ClientClassDefPtr cl = dict->findClass(*name);
+ if (cl && (!cl->getOfferLft().unspecified())) {
+ offer_lft = cl->getOfferLft();
+ break;
+ }
+ }
+ }
+
+ // If no classes specified it, get it from the subnet.
+ if (offer_lft.unspecified()) {
+ offer_lft = ctx.subnet_->getOfferLft();
+ }
+
+ return (offer_lft.unspecified() ? 0 : offer_lft.get());
+}
+
+uint32_t
+AllocEngine::getValidLft(const ClientContext4& ctx) {
+ // If it's BOOTP, use infinite valid lifetime.
+ if (ctx.query_->inClass("BOOTP")) {
+ return (Lease::INFINITY_LFT);
+ }
+
+ // Use the dhcp-lease-time content from the client if it's there.
+ uint32_t requested_lft = 0;
+ OptionPtr opt = ctx.query_->getOption(DHO_DHCP_LEASE_TIME);
+ if (opt) {
+ OptionUint32Ptr opt_lft = boost::dynamic_pointer_cast<OptionInt<uint32_t> >(opt);
+ if (opt_lft) {
+ requested_lft = opt_lft->getValue();
+ }
+ }
+
+ // If the triplet is specified in one of our classes use it.
+ // We use the first one we find.
+ Triplet<uint32_t> candidate_lft;
+ const ClientClasses classes = ctx.query_->getClasses();
+ if (!classes.empty()) {
+ // Let's get class definitions
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+
+ // Iterate over the assigned class definitions.
+ for (ClientClasses::const_iterator name = classes.cbegin();
+ name != classes.cend(); ++name) {
+ ClientClassDefPtr cl = dict->findClass(*name);
+ if (cl && (!cl->getValid().unspecified())) {
+ candidate_lft = cl->getValid();
+ break;
+ }
+ }
+ }
+
+ // If no classes specified it, get it from the subnet.
+ if (!candidate_lft) {
+ candidate_lft = ctx.subnet_->getValid();
+ }
+
+ // If client requested a value, use the value bounded by
+ // the candidate triplet.
+ if (requested_lft > 0) {
+ return (candidate_lft.get(requested_lft));
+ }
+
+ // Use the candidate's default value.
+ return (candidate_lft.get());
+}
+
+Lease4Ptr
+AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr,
+ CalloutHandle::CalloutNextStep& callout_status) {
+ if (!ctx.hwaddr_) {
+ isc_throw(BadValue, "Can't create a lease with NULL HW address");
+ }
+ if (!ctx.subnet_) {
+ isc_throw(BadValue, "Can't create a lease without a subnet");
+ }
+
+ // Get the context appropriate lifetime.
+ uint32_t valid_lft = (ctx.offer_lft_ ? ctx.offer_lft_ : getValidLft(ctx));
+
+ time_t now = time(NULL);
+
+ ClientIdPtr client_id;
+ if (ctx.subnet_->getMatchClientId()) {
+ client_id = ctx.clientid_;
+ }
+
+ Lease4Ptr lease(new Lease4(addr, ctx.hwaddr_, client_id,
+ valid_lft, now, ctx.subnet_->getID()));
+
+ // Set FQDN specific lease parameters.
+ lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+ lease->fqdn_rev_ = ctx.rev_dns_update_;
+ lease->hostname_ = ctx.hostname_;
+
+ // Add (update) the extended information on the lease.
+ static_cast<void>(updateLease4ExtendedInfo(lease, ctx));
+
+ // Let's execute all callouts registered for lease4_select
+ if (ctx.callout_handle_ &&
+ HooksManager::calloutsPresent(hook_index_lease4_select_)) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_);
+
+ // Pass necessary arguments
+ // Pass the original client query
+ ctx.callout_handle_->setArgument("query4", ctx.query_);
+
+ // Subnet from which we do the allocation (That's as far as we can go
+ // with using SubnetPtr to point to Subnet4 object. Users should not
+ // be confused with dynamic_pointer_casts. They should get a concrete
+ // pointer (Subnet4Ptr) pointing to a Subnet4 object.
+ Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+ ctx.callout_handle_->setArgument("subnet4", subnet4);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
+
+ // Are we allocating on DISCOVER? (i.e. offer_lft > 0).
+ ctx.callout_handle_->setArgument("offer_lft", ctx.offer_lft_);
+
+ // Pass the intended lease as well
+ ctx.callout_handle_->setArgument("lease4", lease);
+
+ // This is the first callout, so no need to clear any arguments
+ HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_);
+
+ callout_status = ctx.callout_handle_->getStatus();
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_status == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+ return (Lease4Ptr());
+ }
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handled to it.
+ ctx.callout_handle_->getArgument("lease4", lease);
+ }
+
+ if (ctx.fake_allocation_ && ctx.offer_lft_) {
+ // Turn them off before we persist, so we'll see it as different when
+ // we extend it in the REQUEST. This should cause us to do DDNS (if
+ // it's enabled).
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = false;
+ }
+
+ if (!ctx.fake_allocation_ || ctx.offer_lft_) {
+ const auto& pool = ctx.subnet_->getPool(Lease::TYPE_V4, lease->addr_, false);
+ if (pool) {
+ lease->pool_id_ = pool->getID();
+ }
+ // That is a real (REQUEST) allocation
+ bool status = LeaseMgrFactory::instance().addLease(lease);
+ if (status) {
+
+ // The lease insertion succeeded, let's bump up the statistic.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "assigned-addresses"),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "cumulative-assigned-addresses"),
+ static_cast<int64_t>(1));
+
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-addresses")),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("pool", pool->getID(),
+ "cumulative-assigned-addresses")),
+ static_cast<int64_t>(1));
+ }
+
+ StatsMgr::instance().addValue("cumulative-assigned-addresses",
+ static_cast<int64_t>(1));
+
+ return (lease);
+ } else {
+ // One of many failures with LeaseMgr (e.g. lost connection to the
+ // database, database failed etc.). One notable case for that
+ // is that we are working in multi-process mode and we lost a race
+ // (some other process got that address first)
+ return (Lease4Ptr());
+ }
+ } else {
+ // That is only fake (DISCOVER) allocation
+ // It is for OFFER only. We should not insert the lease and callers
+ // have already verified the lease does not exist in the database.
+ return (lease);
+ }
+}
+
+Lease4Ptr
+AllocEngine::renewLease4(const Lease4Ptr& lease,
+ AllocEngine::ClientContext4& ctx) {
+ if (!lease) {
+ isc_throw(BadValue, "null lease specified for renewLease4");
+ }
+
+ // Let's keep the old data. This is essential if we are using memfile
+ // (the lease returned points directly to the lease4 object in the database)
+ // We'll need it if we want to skip update (i.e. roll back renewal)
+ /// @todo: remove this?
+ Lease4Ptr old_values = boost::make_shared<Lease4>(*lease);
+ ctx.old_lease_.reset(new Lease4(*old_values));
+
+ // Update the lease with the information from the context.
+ // If there was no significant changes, try reuse.
+ lease->reuseable_valid_lft_ = 0;
+ if (!updateLease4Information(lease, ctx)) {
+ setLeaseReusable(lease, ctx);
+ }
+
+ if (!ctx.fake_allocation_ || ctx.offer_lft_) {
+ // If the lease is expired we have to reclaim it before
+ // re-assigning it to the client. The lease reclamation
+ // involves execution of hooks and DNS update.
+ if (ctx.old_lease_->expired()) {
+ reclaimExpiredLease(ctx.old_lease_, ctx.callout_handle_);
+ }
+
+ lease->state_ = Lease::STATE_DEFAULT;
+ }
+
+ bool skip = false;
+ // Execute all callouts registered for lease4_renew.
+ if (HooksManager::calloutsPresent(Hooks.hook_index_lease4_renew_)) {
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_);
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_);
+
+ // Subnet from which we do the allocation. Convert the general subnet
+ // pointer to a pointer to a Subnet4. Note that because we are using
+ // boost smart pointers here, we need to do the cast using the boost
+ // version of dynamic_pointer_cast.
+ Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+
+ // Pass the parameters. Note the clientid is passed only if match-client-id
+ // is set. This is done that way, because the lease4-renew hook point is
+ // about renewing a lease and the configuration parameter says the
+ // client-id should be ignored. Hence no clientid value if match-client-id
+ // is false.
+ ctx.callout_handle_->setArgument("query4", ctx.query_);
+ ctx.callout_handle_->setArgument("subnet4", subnet4);
+ ctx.callout_handle_->setArgument("clientid", subnet4->getMatchClientId() ?
+ ctx.clientid_ : ClientIdPtr());
+ ctx.callout_handle_->setArgument("hwaddr", ctx.hwaddr_);
+
+ // Pass the lease to be updated
+ ctx.callout_handle_->setArgument("lease4", lease);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease4_renew_,
+ *ctx.callout_handle_);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would actually renew the lease, so skip at this
+ // stage means "keep the old lease as it is".
+ if (ctx.callout_handle_->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+ skip = true;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+ DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
+ }
+
+ /// DROP status does not make sense here.
+ }
+
+ if ((!ctx.fake_allocation_ || ctx.offer_lft_) && !skip && (lease->reuseable_valid_lft_ == 0)) {
+ const auto& pool = ctx.subnet_->getPool(Lease::TYPE_V4, lease->addr_, false);
+ if (pool) {
+ lease->pool_id_ = pool->getID();
+ }
+
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(lease);
+
+ // We need to account for the re-assignment of the lease.
+ if (ctx.old_lease_->expired() || ctx.old_lease_->state_ == Lease::STATE_EXPIRED_RECLAIMED) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "assigned-addresses"),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "cumulative-assigned-addresses"),
+ static_cast<int64_t>(1));
+
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-addresses")),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("pool", pool->getID(),
+ "cumulative-assigned-addresses")),
+ static_cast<int64_t>(1));
+ }
+
+ StatsMgr::instance().addValue("cumulative-assigned-addresses",
+ static_cast<int64_t>(1));
+ }
+ }
+ if (skip) {
+ // Rollback changes (really useful only for memfile)
+ /// @todo: remove this?
+ *lease = *old_values;
+ }
+
+ return (lease);
+}
+
+Lease4Ptr
+AllocEngine::reuseExpiredLease4(Lease4Ptr& expired,
+ AllocEngine::ClientContext4& ctx,
+ CalloutHandle::CalloutNextStep& callout_status) {
+ if (!expired) {
+ isc_throw(BadValue, "null lease specified for reuseExpiredLease");
+ }
+
+ if (!ctx.subnet_) {
+ isc_throw(BadValue, "null subnet specified for the reuseExpiredLease");
+ }
+
+ if (!ctx.fake_allocation_ || ctx.offer_lft_) {
+ // The expired lease needs to be reclaimed before it can be reused.
+ // This includes declined leases for which probation period has
+ // elapsed.
+ reclaimExpiredLease(expired, ctx.callout_handle_);
+ expired->state_ = Lease::STATE_DEFAULT;
+ }
+
+ expired->reuseable_valid_lft_ = 0;
+ static_cast<void>(updateLease4Information(expired, ctx));
+
+ LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA,
+ ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA)
+ .arg(ctx.query_->getLabel())
+ .arg(expired->toText());
+
+ // Let's execute all callouts registered for lease4_select
+ if (ctx.callout_handle_ &&
+ HooksManager::calloutsPresent(hook_index_lease4_select_)) {
+
+ // Enable copying options from the packet within hook library.
+ ScopedEnableOptionsCopy<Pkt4> query4_options_copy(ctx.query_);
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(ctx.callout_handle_);
+
+ // Pass necessary arguments
+ // Pass the original client query
+ ctx.callout_handle_->setArgument("query4", ctx.query_);
+
+ // Subnet from which we do the allocation. Convert the general subnet
+ // pointer to a pointer to a Subnet4. Note that because we are using
+ // boost smart pointers here, we need to do the cast using the boost
+ // version of dynamic_pointer_cast.
+ Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(ctx.subnet_);
+ ctx.callout_handle_->setArgument("subnet4", subnet4);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ ctx.callout_handle_->setArgument("fake_allocation", ctx.fake_allocation_);
+ ctx.callout_handle_->setArgument("offer_lft", ctx.offer_lft_);
+
+ // The lease that will be assigned to a client
+ ctx.callout_handle_->setArgument("lease4", expired);
+
+ // Call the callouts
+ HooksManager::callCallouts(hook_index_lease4_select_, *ctx.callout_handle_);
+
+ callout_status = ctx.callout_handle_->getStatus();
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_status == CalloutHandle::NEXT_STEP_SKIP) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS,
+ DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+ return (Lease4Ptr());
+ }
+
+ /// DROP status does not make sense here.
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handed to it.
+ ctx.callout_handle_->getArgument("lease4", expired);
+ }
+
+ if (!ctx.fake_allocation_ || ctx.offer_lft_) {
+ const auto& pool = ctx.subnet_->getPool(Lease::TYPE_V4, expired->addr_, false);
+ if (pool) {
+ expired->pool_id_ = pool->getID();
+ }
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(expired);
+
+ // We need to account for the re-assignment of the lease.
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "assigned-addresses"),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "cumulative-assigned-addresses"),
+ static_cast<int64_t>(1));
+
+ if (pool) {
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-addresses")),
+ static_cast<int64_t>(1));
+
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ StatsMgr::generateName("pool", pool->getID(),
+ "cumulative-assigned-addresses")),
+ static_cast<int64_t>(1));
+ }
+
+ StatsMgr::instance().addValue("cumulative-assigned-addresses",
+ static_cast<int64_t>(1));
+ }
+
+ // We do nothing for SOLICIT. We'll just update database when
+ // the client gets back to us with REQUEST message.
+
+ // it's not really expired at this stage anymore - let's return it as
+ // an updated lease
+ return (expired);
+}
+
+Lease4Ptr
+AllocEngine::allocateOrReuseLease4(const IOAddress& candidate, ClientContext4& ctx,
+ CalloutHandle::CalloutNextStep& callout_status) {
+ ctx.conflicting_lease_.reset();
+
+ Lease4Ptr exist_lease = LeaseMgrFactory::instance().getLease4(candidate);
+ if (exist_lease) {
+ if (exist_lease->expired()) {
+ ctx.old_lease_ = Lease4Ptr(new Lease4(*exist_lease));
+ // reuseExpiredLease4() will reclaim the use which will
+ // queue an NCR remove it needed. Clear the DNS fields in
+ // the old lease to avoid a redundant remove in server logic.
+ ctx.old_lease_->hostname_.clear();
+ ctx.old_lease_->fqdn_fwd_ = false;
+ ctx.old_lease_->fqdn_rev_ = false;
+ return (reuseExpiredLease4(exist_lease, ctx, callout_status));
+
+ } else {
+ // If there is a lease and it is not expired, pass this lease back
+ // to the caller in the context. The caller may need to know
+ // which lease we're conflicting with.
+ ctx.conflicting_lease_ = exist_lease;
+ }
+
+ } else {
+ return (createLease4(ctx, candidate, callout_status));
+ }
+ return (Lease4Ptr());
+}
+
+Lease4Ptr
+AllocEngine::allocateUnreservedLease4(ClientContext4& ctx) {
+ Lease4Ptr new_lease;
+ Subnet4Ptr subnet = ctx.subnet_;
+
+ // Need to check if the subnet belongs to a shared network. If so,
+ // we might be able to find a better subnet for lease allocation,
+ // for which it is more likely that there are some leases available.
+ // If we stick to the selected subnet, we may end up walking over
+ // the entire subnet (or more subnets) to discover that the address
+ // pools have been exhausted. Using a subnet from which an address
+ // was assigned most recently is an optimization which increases
+ // the likelihood of starting from the subnet which address pools
+ // are not exhausted.
+ SharedNetwork4Ptr network;
+ ctx.subnet_->getSharedNetwork(network);
+ if (network) {
+ // This would try to find a subnet with the same set of classes
+ // as the current subnet, but with the more recent "usage timestamp".
+ // This timestamp is only updated for the allocations made with an
+ // allocator (unreserved lease allocations), not the static
+ // allocations or requested addresses.
+ ctx.subnet_ = subnet = network->getPreferredSubnet(ctx.subnet_);
+ }
+
+ // We have the choice in the order checking the lease and
+ // the reservation. The default is to begin by the lease
+ // if the multi-threading is disabled.
+ bool check_reservation_first = MultiThreadingMgr::instance().getMode();
+
+ Subnet4Ptr original_subnet = subnet;
+
+ uint128_t total_attempts = 0;
+
+ // The following counter tracks the number of subnets with matching client
+ // classes from which the allocation engine attempted to assign leases.
+ uint64_t subnets_with_unavail_leases = 0;
+ // The following counter tracks the number of subnets in which there were
+ // no matching pools for the client.
+ uint64_t subnets_with_unavail_pools = 0;
+
+ auto const& classes = ctx.query_->getClasses();
+
+ while (subnet) {
+ ClientIdPtr client_id;
+ if (subnet->getMatchClientId()) {
+ client_id = ctx.clientid_;
+ }
+
+ uint128_t const possible_attempts =
+ subnet->getPoolCapacity(Lease::TYPE_V4, classes);
+
+ // If the number of tries specified in the allocation engine constructor
+ // is set to 0 (unlimited) or the pools capacity is lower than that number,
+ // let's use the pools capacity as the maximum number of tries. Trying
+ // more than the actual pools capacity is a waste of time. If the specified
+ // number of tries is lower than the pools capacity, use that number.
+ uint128_t const max_attempts =
+ (attempts_ == 0 || possible_attempts < attempts_) ?
+ possible_attempts :
+ attempts_;
+
+ if (max_attempts > 0) {
+ // If max_attempts is greater than 0, there are some pools in this subnet
+ // from which we can potentially get a lease.
+ ++subnets_with_unavail_leases;
+ } else {
+ // If max_attempts is 0, it is an indication that there are no pools
+ // in the subnet from which we can get a lease.
+ ++subnets_with_unavail_pools;
+ }
+
+ bool exclude_first_last_24 = ((subnet->get().second <= 24) &&
+ CfgMgr::instance().getCurrentCfg()->getExcludeFirstLast24());
+
+ CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE;
+
+ for (uint128_t i = 0; i < max_attempts; ++i) {
+
+ ++total_attempts;
+
+ auto allocator = subnet->getAllocator(Lease::TYPE_V4);
+ IOAddress candidate = allocator->pickAddress(classes,
+ client_id,
+ ctx.requested_address_);
+
+ // An allocator may return zero address when it has pools exhausted.
+ if (candidate.isV4Zero()) {
+ break;
+ }
+
+ if (exclude_first_last_24) {
+ // Exclude .0 and .255 addresses.
+ auto const& bytes = candidate.toBytes();
+ if ((bytes.size() != 4) ||
+ (bytes[3] == 0) || (bytes[3] == 255U)) {
+ // Don't allocate.
+ continue;
+ }
+ }
+
+ // First check for reservation when it is the choice.
+ if (check_reservation_first && addressReserved(candidate, ctx)) {
+ // Don't allocate.
+ continue;
+ }
+
+ // Check if the resource is busy i.e. can be being allocated
+ // by another thread to another client.
+ ResourceHandler4 resource_handler;
+ if (MultiThreadingMgr::instance().getMode() &&
+ !resource_handler.tryLock4(candidate)) {
+ // Don't allocate.
+ continue;
+ }
+
+ // Check for an existing lease for the candidate address.
+ Lease4Ptr exist_lease = LeaseMgrFactory::instance().getLease4(candidate);
+ if (!exist_lease) {
+ // No existing lease, is it reserved?
+ if (check_reservation_first || !addressReserved(candidate, ctx)) {
+ // Not reserved use it.
+ new_lease = createLease4(ctx, candidate, callout_status);
+ }
+ } else {
+ // An lease exists, is expired, and not reserved use it.
+ if (exist_lease->expired() &&
+ (check_reservation_first || !addressReserved(candidate, ctx))) {
+ ctx.old_lease_ = Lease4Ptr(new Lease4(*exist_lease));
+ new_lease = reuseExpiredLease4(exist_lease, ctx, callout_status);
+ }
+ }
+
+ // We found a lease we can use, return it.
+ if (new_lease) {
+ return (new_lease);
+ }
+
+ if (ctx.callout_handle_ && (callout_status != CalloutHandle::NEXT_STEP_CONTINUE)) {
+ // Don't retry when the callout status is not continue.
+ subnet.reset();
+ break;
+ }
+ }
+
+ // This pointer may be set to NULL if hooks set SKIP status.
+ if (subnet) {
+ subnet = subnet->getNextSubnet(original_subnet, classes);
+
+ if (subnet) {
+ ctx.subnet_ = subnet;
+ }
+ }
+ }
+
+ if (network) {
+ // The client is in the shared network. Let's log the high level message
+ // indicating which shared network the client belongs to.
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_SHARED_NETWORK)
+ .arg(ctx.query_->getLabel())
+ .arg(network->getName())
+ .arg(subnets_with_unavail_leases)
+ .arg(subnets_with_unavail_pools);
+ StatsMgr::instance().addValue("v4-allocation-fail-shared-network",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v4-allocation-fail-shared-network"),
+ static_cast<int64_t>(1));
+ } else {
+ // The client is not connected to a shared network. It is connected
+ // to a subnet. Let's log some details about the subnet.
+ std::string shared_network = ctx.subnet_->getSharedNetworkName();
+ if (shared_network.empty()) {
+ shared_network = "(none)";
+ }
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_SUBNET)
+ .arg(ctx.query_->getLabel())
+ .arg(ctx.subnet_->toText())
+ .arg(ctx.subnet_->getID())
+ .arg(shared_network);
+ StatsMgr::instance().addValue("v4-allocation-fail-subnet",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v4-allocation-fail-subnet"),
+ static_cast<int64_t>(1));
+ }
+ if (total_attempts == 0) {
+ // In this case, it seems that none of the pools in the subnets could
+ // be used for that client, both in case the client is connected to
+ // a shared network or to a single subnet. Apparently, the client was
+ // rejected to use the pools because of the client classes' mismatch.
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_NO_POOLS)
+ .arg(ctx.query_->getLabel());
+ StatsMgr::instance().addValue("v4-allocation-fail-no-pools",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v4-allocation-fail-no-pools"),
+ static_cast<int64_t>(1));
+ } else {
+ // This is an old log message which provides a number of attempts
+ // made by the allocation engine to allocate a lease. The only case
+ // when we don't want to log this message is when the number of
+ // attempts is zero (condition above), because it would look silly.
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL)
+ .arg(ctx.query_->getLabel())
+ .arg(total_attempts);
+ StatsMgr::instance().addValue("v4-allocation-fail",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v4-allocation-fail"),
+ static_cast<int64_t>(1));
+ }
+
+ if (!classes.empty()) {
+ LOG_WARN(alloc_engine_logger, ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES)
+ .arg(ctx.query_->getLabel())
+ .arg(classes.toText());
+ StatsMgr::instance().addValue("v4-allocation-fail-classes",
+ static_cast<int64_t>(1));
+ StatsMgr::instance().addValue(
+ StatsMgr::generateName("subnet", ctx.subnet_->getID(),
+ "v4-allocation-fail-classes"),
+ static_cast<int64_t>(1));
+ }
+
+ return (new_lease);
+}
+
+bool
+AllocEngine::updateLease4Information(const Lease4Ptr& lease,
+ AllocEngine::ClientContext4& ctx) const {
+ bool changed = false;
+ if (lease->subnet_id_ != ctx.subnet_->getID()) {
+ changed = true;
+ lease->subnet_id_ = ctx.subnet_->getID();
+ }
+ if ((!ctx.hwaddr_ && lease->hwaddr_) ||
+ (ctx.hwaddr_ &&
+ (!lease->hwaddr_ || (*ctx.hwaddr_ != *lease->hwaddr_)))) {
+ changed = true;
+ lease->hwaddr_ = ctx.hwaddr_;
+ }
+ if (ctx.subnet_->getMatchClientId() && ctx.clientid_) {
+ if (!lease->client_id_ || (*ctx.clientid_ != *lease->client_id_)) {
+ changed = true;
+ lease->client_id_ = ctx.clientid_;
+ }
+ } else if (lease->client_id_) {
+ changed = true;
+ lease->client_id_ = ClientIdPtr();
+ }
+ lease->cltt_ = time(NULL);
+
+ // Get the context appropriate valid lifetime.
+ lease->valid_lft_ = (ctx.offer_lft_ ? ctx.offer_lft_ : getValidLft(ctx));
+
+ // Valid lifetime has changed.
+ if (lease->valid_lft_ != lease->current_valid_lft_) {
+ changed = true;
+ }
+
+ if ((lease->fqdn_fwd_ != ctx.fwd_dns_update_) ||
+ (lease->fqdn_rev_ != ctx.rev_dns_update_) ||
+ (lease->hostname_ != ctx.hostname_)) {
+ changed = true;
+ lease->fqdn_fwd_ = ctx.fwd_dns_update_;
+ lease->fqdn_rev_ = ctx.rev_dns_update_;
+ lease->hostname_ = ctx.hostname_;
+ }
+
+ // Add (update) the extended information on the lease.
+ if (updateLease4ExtendedInfo(lease, ctx)) {
+ changed = true;
+ }
+
+ return (changed);
+}
+
+bool
+AllocEngine::updateLease4ExtendedInfo(const Lease4Ptr& lease,
+ const AllocEngine::ClientContext4& ctx) const {
+ bool changed = false;
+
+ // If storage is not enabled then punt.
+ if (!ctx.subnet_->getStoreExtendedInfo()) {
+ return (changed);
+ }
+
+ // Look for relay agent information option (option 82)
+ OptionPtr rai = ctx.query_->getOption(DHO_DHCP_AGENT_OPTIONS);
+ if (!rai) {
+ // Pkt4 doesn't have it, so nothing to store (or update).
+ return (changed);
+ }
+
+ // Create a StringElement with the hex string for relay-agent-info.
+ ElementPtr relay_agent(new StringElement(rai->toHexString()));
+
+ // Now we wrap the agent info in a map. This allows for future expansion.
+ ElementPtr extended_info = Element::createMap();
+ extended_info->set("sub-options", relay_agent);
+
+ OptionPtr remote_id = rai->getOption(RAI_OPTION_REMOTE_ID);
+ if (remote_id) {
+ std::vector<uint8_t> bytes = remote_id->toBinary(false);
+ lease->remote_id_ = bytes;
+ if (bytes.size() > 0) {
+ extended_info->set("remote-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+
+ OptionPtr relay_id = rai->getOption(RAI_OPTION_RELAY_ID);
+ if (relay_id) {
+ std::vector<uint8_t> bytes = relay_id->toBinary(false);
+ lease->relay_id_ = bytes;
+ if (bytes.size() > 0) {
+ extended_info->set("relay-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+
+ // Get a mutable copy of the lease's current user context.
+ ConstElementPtr user_context = lease->getContext();
+ ElementPtr mutable_user_context;
+ if (user_context && (user_context->getType() == Element::map)) {
+ mutable_user_context = copy(user_context, 0);
+ } else {
+ mutable_user_context = Element::createMap();
+ }
+
+ // Get a mutable copy of the ISC entry.
+ ConstElementPtr isc = mutable_user_context->get("ISC");
+ ElementPtr mutable_isc;
+ if (isc && (isc->getType() == Element::map)) {
+ mutable_isc = copy(isc, 0);
+ } else {
+ mutable_isc = Element::createMap();
+ }
+
+ // Add/replace the extended info entry.
+ ConstElementPtr old_extended_info = mutable_isc->get("relay-agent-info");
+ if (!old_extended_info || (*old_extended_info != *extended_info)) {
+ changed = true;
+ mutable_isc->set("relay-agent-info", extended_info);
+ mutable_user_context->set("ISC", mutable_isc);
+ }
+
+ // Update the lease's user_context.
+ lease->setContext(mutable_user_context);
+
+ return (changed);
+}
+
+void
+AllocEngine::updateLease6ExtendedInfo(const Lease6Ptr& lease,
+ const AllocEngine::ClientContext6& ctx) const {
+ // The extended info action is a transient value but be safe so reset it.
+ lease->extended_info_action_ = Lease6::ACTION_IGNORE;
+
+ // If storage is not enabled then punt.
+ if (!ctx.subnet_->getStoreExtendedInfo()) {
+ return;
+ }
+
+ // If we do not have relay information, then punt.
+ if (ctx.query_->relay_info_.empty()) {
+ return;
+ }
+
+ // We need to convert the vector of RelayInfo instances in
+ // into an Element hierarchy like this:
+ // "relay-info": [
+ // {
+ // "hop": 123,
+ // "link": "2001:db8::1",
+ // "peer": "2001:db8::2",
+ // "options": "0x..."
+ // },..]
+ //
+ ElementPtr extended_info = Element::createList();
+ for (auto relay : ctx.query_->relay_info_) {
+ ElementPtr relay_elem = Element::createMap();
+ relay_elem->set("hop", ElementPtr(new IntElement(relay.hop_count_)));
+ relay_elem->set("link", ElementPtr(new StringElement(relay.linkaddr_.toText())));
+ relay_elem->set("peer", ElementPtr(new StringElement(relay.peeraddr_.toText())));
+
+ // If there are relay options, we'll pack them into a buffer and then
+ // convert that into a hex string. If there are no options, we omit
+ // then entry.
+ if (!relay.options_.empty()) {
+ OutputBuffer buf(128);
+ LibDHCP::packOptions6(buf, relay.options_);
+
+ if (buf.getLength() > 0) {
+ const uint8_t* cp = static_cast<const uint8_t*>(buf.getData());
+ std::vector<uint8_t> bytes;
+ std::stringstream ss;
+
+ bytes.assign(cp, cp + buf.getLength());
+ ss << "0x" << encode::encodeHex(bytes);
+ relay_elem->set("options", ElementPtr(new StringElement(ss.str())));
+ }
+
+ auto remote_id_it = relay.options_.find(D6O_REMOTE_ID);
+ if (remote_id_it != relay.options_.end()) {
+ OptionPtr remote_id = remote_id_it->second;
+ if (remote_id) {
+ std::vector<uint8_t> bytes = remote_id->toBinary(false);
+ if (bytes.size() > 0) {
+ relay_elem->set("remote-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+ }
+
+ auto relay_id_it = relay.options_.find(D6O_RELAY_ID);
+ if (relay_id_it != relay.options_.end()) {
+ OptionPtr relay_id = relay_id_it->second;
+ if (relay_id) {
+ std::vector<uint8_t> bytes = relay_id->toBinary(false);
+ if (bytes.size() > 0) {
+ relay_elem->set("relay-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+ }
+ }
+
+ extended_info->add(relay_elem);
+ }
+
+ // Get a mutable copy of the lease's current user context.
+ ConstElementPtr user_context = lease->getContext();
+ ElementPtr mutable_user_context;
+ if (user_context && (user_context->getType() == Element::map)) {
+ mutable_user_context = copy(user_context, 0);
+ } else {
+ mutable_user_context = Element::createMap();
+ }
+
+ // Get a mutable copy of the ISC entry.
+ ConstElementPtr isc = mutable_user_context->get("ISC");
+ ElementPtr mutable_isc;
+ if (isc && (isc->getType() == Element::map)) {
+ mutable_isc = copy(isc, 0);
+ } else {
+ mutable_isc = Element::createMap();
+ }
+
+ // Add/replace the extended info entry.
+ ConstElementPtr old_extended_info = mutable_isc->get("relay-info");
+ if (!old_extended_info || (*old_extended_info != *extended_info)) {
+ lease->extended_info_action_ = Lease6::ACTION_UPDATE;
+ mutable_isc->set("relay-info", extended_info);
+ mutable_user_context->set("ISC", mutable_isc);
+ }
+
+ // Update the lease's user context.
+ lease->setContext(mutable_user_context);
+}
+
+void
+AllocEngine::setLeaseReusable(const Lease4Ptr& lease,
+ const ClientContext4& ctx) const {
+ // Sanity.
+ lease->reuseable_valid_lft_ = 0;
+ const Subnet4Ptr& subnet = ctx.subnet_;
+ if (!subnet) {
+ return;
+ }
+ if (lease->state_ != Lease::STATE_DEFAULT) {
+ return;
+ }
+
+ // Always reuse infinite lifetime leases.
+ if (lease->valid_lft_ == Lease::INFINITY_LFT) {
+ lease->reuseable_valid_lft_ = Lease::INFINITY_LFT;
+ return;
+ }
+
+ // Refuse time not going forward.
+ if (lease->cltt_ < lease->current_cltt_) {
+ return;
+ }
+
+ uint32_t age = lease->cltt_ - lease->current_cltt_;
+ // Already expired.
+ if (age >= lease->current_valid_lft_) {
+ return;
+ }
+
+ // Try cache max age.
+ uint32_t max_age = 0;
+ if (!subnet->getCacheMaxAge().unspecified()) {
+ max_age = subnet->getCacheMaxAge().get();
+ if ((max_age == 0) || (age > max_age)) {
+ return;
+ }
+ }
+
+ // Try cache threshold.
+ if (!subnet->getCacheThreshold().unspecified()) {
+ double threshold = subnet->getCacheThreshold().get();
+ if ((threshold <= 0.) || (threshold > 1.)) {
+ return;
+ }
+ max_age = lease->valid_lft_ * threshold;
+ if (age > max_age) {
+ return;
+ }
+ }
+
+ // No cache.
+ if (max_age == 0) {
+ return;
+ }
+
+ // Seems to be reusable.
+ lease->reuseable_valid_lft_ = lease->current_valid_lft_ - age;
+}
+
+void
+AllocEngine::setLeaseReusable(const Lease6Ptr& lease,
+ uint32_t current_preferred_lft,
+ const ClientContext6& ctx) const {
+ // Sanity.
+ lease->reuseable_valid_lft_ = 0;
+ lease->reuseable_preferred_lft_ = 0;
+ const Subnet6Ptr& subnet = ctx.subnet_;
+ if (!subnet) {
+ return;
+ }
+ if (lease->state_ != Lease::STATE_DEFAULT) {
+ return;
+ }
+
+ // Refuse time not going forward.
+ if (lease->cltt_ < lease->current_cltt_) {
+ return;
+ }
+
+ uint32_t age = lease->cltt_ - lease->current_cltt_;
+ // Already expired.
+ if (age >= lease->current_valid_lft_) {
+ return;
+ }
+
+ // Try cache max age.
+ uint32_t max_age = 0;
+ if (!subnet->getCacheMaxAge().unspecified()) {
+ max_age = subnet->getCacheMaxAge().get();
+ if ((max_age == 0) || (age > max_age)) {
+ return;
+ }
+ }
+
+ // Try cache threshold.
+ if (!subnet->getCacheThreshold().unspecified()) {
+ double threshold = subnet->getCacheThreshold().get();
+ if ((threshold <= 0.) || (threshold > 1.)) {
+ return;
+ }
+ max_age = lease->valid_lft_ * threshold;
+ if (age > max_age) {
+ return;
+ }
+ }
+
+ // No cache.
+ if (max_age == 0) {
+ return;
+ }
+
+ // Seems to be reusable.
+ if ((current_preferred_lft == Lease::INFINITY_LFT) ||
+ (current_preferred_lft == 0)) {
+ // Keep these values.
+ lease->reuseable_preferred_lft_ = current_preferred_lft;
+ } else if (current_preferred_lft > age) {
+ lease->reuseable_preferred_lft_ = current_preferred_lft - age;
+ } else {
+ // Can be a misconfiguration so stay safe...
+ return;
+ }
+ if (lease->current_valid_lft_ == Lease::INFINITY_LFT) {
+ lease->reuseable_valid_lft_ = Lease::INFINITY_LFT;
+ } else {
+ lease->reuseable_valid_lft_ = lease->current_valid_lft_ - age;
+ }
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
new file mode 100644
index 0000000..b046cc2
--- /dev/null
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -0,0 +1,1877 @@
+// Copyright (C) 2012-2023 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 ALLOC_ENGINE_H
+#define ALLOC_ENGINE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/classify.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/srv_config.h>
+#include <hooks/callout_handle.h>
+#include <util/multi_threading_mgr.h>
+#include <util/readwrite_mutex.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <functional>
+#include <list>
+#include <map>
+#include <mutex>
+#include <set>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief DHCPv4 and DHCPv6 allocation engine
+///
+/// This class represents a DHCP allocation engine. It is responsible
+/// for picking subnets, choosing and allocating a lease, extending,
+/// renewing, releasing and possibly expiring leases.
+class AllocEngine : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Instantiates necessary services, required to run DHCP server.
+ /// In particular, creates IfaceMgr that will be responsible for
+ /// network interaction. Will instantiate lease manager, and load
+ /// old or create new DUID.
+ ///
+ /// @param attempts number of attempts for each lease allocation before
+ /// we give up (0 means unlimited)
+ AllocEngine(isc::util::uint128_t const& attempts);
+
+ /// @brief Destructor.
+ virtual ~AllocEngine() { }
+
+private:
+
+ /// @brief number of attempts before we give up lease allocation (0=unlimited)
+ isc::util::uint128_t attempts_;
+
+ /// @brief Hook name indexes (used in hooks callouts)
+ int hook_index_lease4_select_; ///< index for lease4_select hook
+ int hook_index_lease6_select_; ///< index for lease6_select hook
+
+public:
+
+ /// @brief Defines a single hint
+ ///
+ /// This is an entry that represents what the client had requested,
+ /// either an address or a prefix. Prefix length is 128 for regular
+ /// addresses. Optionally it provides wanted preferred and valid
+ /// lifetimes.
+ ///
+ /// @note Seems to be used only for DHCPv6.
+ class Resource {
+ public:
+
+ /// @brief Default constructor.
+ ///
+ /// @param address the address or prefix
+ /// @param prefix_len the prefix length (defaults to 128)
+ /// @param preferred the optional preferred lifetime,
+ /// defaults to 0, meaning not specified
+ /// @param valid the optional valid lifetime,
+ /// defaults to 0, meaning not specified
+ Resource(const isc::asiolink::IOAddress& address,
+ const uint8_t prefix_len = 128,
+ const uint32_t preferred = 0,
+ const uint32_t valid = 0)
+ : address_(address), prefix_len_(prefix_len),
+ preferred_(preferred), valid_(valid) {
+ }
+
+ /// @brief Returns the address.
+ ///
+ /// @return the address or prefix
+ isc::asiolink::IOAddress getAddress() const {
+ return (address_);
+ }
+
+ /// @brief Returns the prefix length.
+ ///
+ /// @return the prefix length
+ uint8_t getPrefixLength() const {
+ return (prefix_len_);
+ }
+
+ /// @brief Returns the optional preferred lifetime.
+ ///
+ /// @return the preferred lifetime (0 if not set)
+ uint32_t getPreferred() const {
+ return (preferred_);
+ }
+
+ /// @brief Returns the optional valid lifetime.
+ ///
+ /// @return the valid lifetime (0 if not set)
+ uint32_t getValid() const {
+ return (valid_);
+ }
+
+ /// @brief Compares two @c AllocEngine::Resource objects for equality.
+ ///
+ /// @param other object to be compared with this object
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool equals(const Resource& other) const {
+ return (address_ == other.address_ &&
+ prefix_len_ == other.prefix_len_);
+ }
+
+ /// @brief Equality operator.
+ ///
+ /// @param other object to be compared with this object
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool operator==(const Resource& other) const {
+ return (equals(other));
+ }
+
+ protected:
+
+ /// @brief The address or prefix.
+ isc::asiolink::IOAddress address_;
+
+ /// @brief The prefix length (128 for an address).
+ uint8_t prefix_len_;
+
+ /// @brief The preferred lifetime (0 when not set).
+ uint32_t preferred_;
+
+ /// @brief The valid lifetime (0 when not set).
+ uint32_t valid_;
+ };
+
+ /// @brief Resource compare class.
+ ///
+ /// Needed for using sets of Resource objects.
+ struct ResourceCompare {
+ /// @brief Compare operator
+ ///
+ /// @note Only the address/prefix part of resources is used.
+ /// @param lhr Left hand resource object
+ /// @param rhr Right hand resource object
+ ///
+ /// @return true if lhr is less than rhr, false otherwise
+ bool operator() (const Resource& lhr, const Resource& rhr) const {
+ if (lhr.getAddress() == rhr.getAddress()) {
+ return (lhr.getPrefixLength() < rhr.getPrefixLength());
+ } else {
+ return (lhr.getAddress() < rhr.getAddress());
+ }
+ }
+ };
+
+ /// @brief Container for client's hints.
+ typedef std::vector<Resource> HintContainer;
+
+ /// @brief Container holding allocated prefixes or addresses.
+ typedef std::set<Resource, ResourceCompare> ResourceContainer;
+
+ /// @brief A tuple holding host identifier type and value.
+ typedef std::pair<Host::IdentifierType, std::vector<uint8_t> > IdentifierPair;
+
+ /// @brief Map holding values to be used as host identifiers.
+ typedef std::list<IdentifierPair> IdentifierList;
+
+ /// @brief Context information for the DHCPv6 leases allocation.
+ ///
+ /// This structure holds a set of information provided by the DHCPv6
+ /// server to the allocation engine. In particular, it holds the
+ /// client identifying information, such as HW address or client
+ /// identifier. It also holds the information about the subnet that
+ /// the client is connected to.
+ ///
+ /// This structure is also used to pass some information from
+ /// the allocation engine back to the server, i.e. the old leases
+ /// which the client had before the allocation.
+ ///
+ /// This structure is expected to be common for a single client, even
+ /// if multiple IAs are used. Some of the fields will need to be
+ /// updated for every call (there's a separate call to the allocation
+ /// engine for each IA option).
+ ///
+ /// This structure is meant to be extended in the future, if more
+ /// information should be passed to the allocation engine. Note
+ /// that the big advantage of using the context structure to pass
+ /// information to the allocation engine methods is that adding
+ /// new information doesn't modify the API of the allocation engine.
+ struct ClientContext6 : public boost::noncopyable {
+
+ /// @name Parameters pertaining to DHCPv6 message
+ //@{
+
+ /// @brief A pointer to the client's message
+ ///
+ /// This is used exclusively for hook purposes.
+ Pkt6Ptr query_;
+
+ /// @brief Indicates if this is a real or fake allocation.
+ ///
+ /// The real allocation is when the allocation engine is supposed
+ /// to make an update in a lease database: create new lease, or
+ /// update existing lease.
+ bool fake_allocation_;
+
+ /// @brief Indicates if early global reservation is enabled.
+ ///
+ /// This caches the early-global-reservations-lookup value.
+ bool early_global_reservations_lookup_;
+
+ /// @brief Subnet selected for the client by the server.
+ Subnet6Ptr subnet_;
+
+ /// @brief Subnet from which host reservations should be retrieved.
+ ///
+ /// It can be NULL, in which case @c subnet_ value is used.
+ Subnet6Ptr host_subnet_;
+
+ /// @brief Client identifier
+ DuidPtr duid_;
+
+ /// @brief Hardware/MAC address (if available, may be NULL)
+ HWAddrPtr hwaddr_;
+
+ /// @brief A list holding host identifiers extracted from a message
+ /// received by the server.
+ IdentifierList host_identifiers_;
+
+ /// @brief Holds a map of hosts belonging to the client within different
+ /// subnets.
+ ///
+ /// Multiple hosts may appear when the client belongs to a shared
+ /// network.
+ std::map<SubnetID, ConstHostPtr> hosts_;
+
+ /// @brief A boolean value which indicates that server takes
+ /// responsibility for the forward DNS Update for this lease
+ /// (if true).
+ bool fwd_dns_update_;
+
+ /// @brief A boolean value which indicates that server takes
+ /// responsibility for the reverse DNS Update for this lease
+ /// (if true).
+ bool rev_dns_update_;
+
+ /// @brief Hostname.
+ ///
+ /// The server retrieves the hostname from the Client FQDN option,
+ /// Hostname option or the host reservation record for the client.
+ std::string hostname_;
+
+ /// @brief Callout handle associated with the client's message.
+ hooks::CalloutHandlePtr callout_handle_;
+
+ /// @brief Holds addresses and prefixes allocated for all IAs.
+ ResourceContainer allocated_resources_;
+
+ /// @brief A collection of newly allocated leases.
+ Lease6Collection new_leases_;
+
+ //@}
+
+ /// @brief Parameters pertaining to individual IAs.
+ struct IAContext {
+
+ /// @brief The IAID field from IA_NA or IA_PD that is being
+ /// processed
+ uint32_t iaid_;
+
+ /// @brief Lease type (IA or PD)
+ Lease::Type type_;
+
+ /// @brief Client's hints
+ ///
+ /// There will typically be just one address, but the protocol
+ /// allows more than one address or prefix for each IA container.
+ HintContainer hints_;
+
+ /// @brief A pointer to any old leases that the client had before
+ /// update but are no longer valid after the update/allocation.
+ ///
+ /// This collection is typically empty, except cases when we are
+ /// doing address reassignment, e.g. because there is a host
+ /// reservation that gives this address to someone else, so we had
+ /// to return the address, and give a new one to this client.
+ Lease6Collection old_leases_;
+
+ /// @brief A pointer to any leases that have changed FQDN
+ /// information.
+ ///
+ /// This list may contain old versions of the leases that are still
+ /// valid. In particular, it will contain a lease if the client's
+ /// FQDN has changed.
+ Lease6Collection changed_leases_;
+
+ /// @brief Holds addresses and prefixes allocated for this IA.
+ ///
+ /// This collection is used to update at most once new leases.
+ ResourceContainer new_resources_;
+
+ /// @brief A pointer to the IA_NA/IA_PD option to be sent in
+ /// response
+ Option6IAPtr ia_rsp_;
+
+ /// @brief Default constructor.
+ ///
+ /// Initializes @ref type_ to @c Lease::TYPE_NA and @ref iaid_ to 0.
+ IAContext();
+
+ /// @brief Convenience method adding new hint.
+ ///
+ /// @param prefix Prefix or address.
+ /// @param prefix_len Prefix length. Default is 128 for addresses.
+ /// @param preferred Wanted preferred lifetime. Default 0.
+ /// @param valid Wanted valid lifetime. Default 0.
+ void addHint(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len = 128,
+ const uint32_t preferred = 0,
+ const uint32_t valid = 0);
+
+ /// @brief Convenience method adding new hint from IAADDR option.
+ ///
+ /// @param iaaddr Pointer to IAADDR.
+ ///
+ /// @throw BadValue if iaaddr is null.
+ void addHint(const Option6IAAddrPtr& iaaddr);
+
+ /// @brief Convenience method adding new hint from IAPREFIX option.
+ ///
+ /// @param iaprefix Pointer to IAPREFIX.
+ ///
+ /// @throw BadValue if iaprefix is null.
+ void addHint(const Option6IAPrefixPtr& iaprefix);
+
+ /// @brief Convenience method adding new prefix or address.
+ ///
+ /// @param prefix Prefix or address
+ /// @param prefix_len Prefix length. Default is 128 for addresses.
+ void addNewResource(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len = 128);
+
+ /// @brief Checks if specified address or prefix was new.
+ ///
+ /// @param prefix Prefix or address
+ /// @param prefix_len Prefix length. Default is 128 for addresses.
+ bool isNewResource(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len = 128) const;
+ };
+
+ /// @brief Container holding IA specific contexts.
+ std::vector<IAContext> ias_;
+
+ /// @brief Returns the set of DDNS behavioral parameters based on
+ /// the selected subnet.
+ ///
+ /// If there is no selected subnet (i.e. subnet_ is empty), the
+ /// returned set will contain default values.
+ ///
+ /// @return pointer to a DdnsParams instance
+ DdnsParamsPtr getDdnsParams();
+
+ /// @brief Convenience method adding allocated prefix or address.
+ ///
+ /// @param prefix Prefix or address.
+ /// @param prefix_len Prefix length. Default is 128 for addresses.
+ void addAllocatedResource(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len = 128);
+
+ /// @brief Checks if specified address or prefix was allocated.
+ ///
+ /// @param prefix Prefix or address.
+ /// @param prefix_len Prefix length. Default is 128 for addresses.
+ bool isAllocated(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len = 128) const;
+
+ /// @brief Convenience function adding host identifier into
+ /// @ref host_identifiers_ list.
+ ///
+ /// @param id_type Identifier type.
+ /// @param identifier Identifier value.
+ void addHostIdentifier(const Host::IdentifierType& id_type,
+ const std::vector<uint8_t>& identifier) {
+ host_identifiers_.push_back(IdentifierPair(id_type, identifier));
+ }
+
+ /// @brief Returns IA specific context for the currently processed IA.
+ ///
+ /// If IA specific context doesn't exist, it is created.
+ ///
+ /// @return Reference to IA specific context.
+ IAContext& currentIA() {
+ if (ias_.empty()) {
+ createIAContext();
+ }
+ return (ias_.back());
+ }
+
+ /// @brief Creates new IA context.
+ ///
+ /// This method should be invoked prior to processing a next IA included
+ /// in the client's message.
+ void createIAContext() {
+ ias_.push_back(IAContext());
+ };
+
+ /// @brief Returns host from the most preferred subnet.
+ ///
+ /// If there is no such host and global reservations are enabled
+ /// returns the global host.
+ ///
+ /// @return Pointer to the host object.
+ ConstHostPtr currentHost() const;
+
+ /// @brief Returns global host reservation if there is one
+ ///
+ /// If the current subnet's reservations-global is true and
+ /// there is a global host (i.e. reservation belonging to
+ /// the global subnet), return it. Otherwise return an
+ /// empty pointer.
+ ///
+ /// @return Pointer to the host object.
+ ConstHostPtr globalHost() const;
+
+ /// @brief Determines if a global reservation exists
+ ///
+ /// @return true if there current subnet's reservations-global
+ /// is true and there is global host containing the given
+ /// lease reservation, false otherwise
+ bool hasGlobalReservation(const IPv6Resrv& resv) const;
+
+ /// @brief Default constructor.
+ ClientContext6();
+
+ /// @brief Constructor with parameters.
+ ///
+ /// Note that several less frequently used parameters (callout_handle,
+ /// old_leases, host) fields are not set. They should be set explicitly,
+ /// if needed.
+ ///
+ /// @param subnet subnet the allocation should come from
+ /// @param duid Client's DUID
+ /// @param fwd_dns A boolean value which indicates that server takes
+ /// responsibility for the forward DNS Update for this lease
+ /// (if true).
+ /// @param rev_dns A boolean value which indicates that server takes
+ /// responsibility for the reverse DNS Update for this lease
+ /// (if true).
+ /// @param hostname A fully qualified domain-name of the client.
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just
+ /// picking an address for SOLICIT that is not really allocated
+ /// (true)
+ /// @param query Pointer to the DHCPv6 message being processed.
+ /// @param callout_handle Callout handle associated with a client's
+ /// message
+ ClientContext6(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ const bool fwd_dns, const bool rev_dns,
+ const std::string& hostname, const bool fake_allocation,
+ const Pkt6Ptr& query,
+ const hooks::CalloutHandlePtr& callout_handle =
+ hooks::CalloutHandlePtr());
+
+ private:
+ /// @brief Contains a pointer to the DDNS parameters for selected
+ /// subnet. Set by the first call to getDdnsParams() made when
+ /// the context has a selected subnet (i.e. subnet_ is not empty).
+ DdnsParamsPtr ddns_params_;
+ };
+
+ /// @brief Allocates IPv6 leases for a given IA container
+ ///
+ /// This method uses the currently selected allocator to pick allocable
+ /// resources (i.e. addresses or prefixes) from specified subnet, creates
+ /// a lease (one or more, if needed) for that resources and then inserts
+ /// it into LeaseMgr (if this allocation is not fake, i.e. this is not a
+ /// response to SOLICIT).
+ ///
+ /// This method uses host reservation if @ref ClientContext6::hosts_ is set.
+ /// The easy way to set it is to call @ref findReservationDecl.
+ /// The host reservation is convenient, but incurs performance penalty,
+ /// so it can be tweaked on a per subnet basis. There are three possible modes:
+ /// 1. disabled (no host reservation at all). This is the most performant one
+ /// as the code can skip all checks;
+ /// 2. out-of-pool (only reservations that are outside
+ /// of the dynamic pools are allowed. This is a compromise - it requires
+ /// a sysadmin to be more careful with the reservations, but the code
+ /// can skip reservation checks while managing in-pool addresses);
+ /// 3. in-pool (which also allow out-of-pool; this is the most flexible
+ /// mode, but it means that the allocation engine has to do reservation
+ /// checks on every lease, even those dynamically assigned, which degrades
+ /// performance).
+ ///
+ /// The logic in this method is as follows:
+ /// -# Case 1. if there are no leases, and there are reservations...
+ /// Are the reserved addresses/prefixes used by someone else?
+ /// -# yes: we have a problem. We can't assign the reserved address yet,
+ /// because it is used by someone else. We can't immediately release
+ /// the lease as there is some other client that is currently using it.
+ /// We will temporarily assign a different, unreserved lease for this
+ /// client. In the mean time, the other client will hopefully get back
+ /// to us, so we could revoke his lease.
+ /// -# no: assign them => done
+ /// -# Case 2. if there are leases and there are no reservations...
+ /// Are the leases reserved for someone else?
+ /// -# yes: release them, assign something else
+ /// -# no: renew them => done
+ /// -# Case 3. if there are leases and there are reservations...
+ /// Are the leases matching reservations?
+ /// -# yes: renew them => done
+ /// -# no: release existing leases, assign new ones based on reservations
+ /// -# Case 4. if there are no leases and no reservations...
+ /// assign new leases (this is the "normal" case when the reservations
+ /// are disabled).
+ ///
+ /// @param ctx client context that passes all necessary information. See
+ /// @ref ClientContext6 for details.
+ ///
+ /// The following fields of ClientContext6 are used:
+ ///
+ /// @ref ClientContext6::subnet_ subnet the allocation should
+ /// come from<br/>
+ /// @ref ClientContext6::duid_ Client's DUID<br/>
+ /// @ref ClientContext6::IAContext::iaid_ iaid field from the IA_NA container
+ /// that client sent<br/>
+ /// @ref ClientContext6::IAContext::hints_ a hint that the client provided<br/>
+ /// @ref ClientContext6::IAContext::type_ lease type (IA, TA or PD)<br/>
+ /// @ref ClientContext6::fwd_dns_update_ A boolean value which indicates
+ /// that server takes responsibility for the forward DNS Update
+ /// for this lease (if true).<br/>
+ /// @ref ClientContext6::rev_dns_update_ A boolean value which indicates
+ /// that server takes responsibility for the reverse DNS Update for
+ /// this lease (if true).<br/>
+ /// @ref ClientContext6::hostname_ A fully qualified domain-name of the client.<br/>
+ /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false)
+ /// or just picking an address for SOLICIT that is not really
+ /// allocated (true)<br/>
+ /// @ref ClientContext6::callout_handle_ a callout handle (used in hooks). A
+ /// lease callouts will be executed if this parameter is passed.<br/>
+ /// @ref ClientContext6::IAContext::old_leases_ [out] Collection to which this
+ /// function
+ /// will append old leases. Leases are stored in the same order as in
+ /// the collection of new leases, being returned. For newly allocated
+ /// leases (not renewed) the NULL pointers are stored in this
+ /// collection as old leases.<br/>
+ /// @ref ClientContext6::hwaddr_ Hardware address (optional, may be null if
+ /// not available)<br/>
+ /// @ref ClientContext6::hosts_ Host reservations. allocateLeases6 will set
+ /// this field, if appropriate reservations are found.
+ ///
+ /// @return Allocated IPv6 leases (may be empty if allocation failed)
+ Lease6Collection
+ allocateLeases6(ClientContext6& ctx);
+
+ /// @brief Renews existing DHCPv6 leases for a given IA.
+ ///
+ /// This method updates the leases associated with a specified IA container.
+ /// It will extend the leases under normal circumstances, but sometimes
+ /// there may be reasons why not to do so. Such a reasons may be:
+ /// - client attempts to renew an address that is not valid
+ /// - client attempts to renew an address that is now reserved for someone
+ /// else (see host reservation)
+ /// - client's leases does not match his reservations
+ ///
+ /// This method will call the lease6_renew callout.
+ ///
+ /// @param ctx Message processing context. It holds various information
+ /// extracted from the client's message and required to allocate a lease.
+ /// In particular, @ref ClientContext6::IAContext::hints_ provides list
+ /// of addresses or
+ /// prefixes the client had sent. @ref ClientContext6::IAContext::old_leases_
+ /// will contain removed leases in this case.
+ ///
+ /// @return Returns renewed lease.
+ Lease6Collection renewLeases6(ClientContext6& ctx);
+
+ /// @brief Reclaims expired IPv6 leases.
+ ///
+ /// This method retrieves a collection of expired leases and reclaims them.
+ /// See
+ /// https://gitlab.isc.org/isc-projects/kea/wikis/designs/lease-expiration#leases-reclamation-routine
+ /// for the details.
+ ///
+ /// This method is executed periodically to act upon expired leases. This
+ /// includes for each lease:
+ /// - executing "lease_expire6" hook,
+ /// - removing DNS record for a lease,
+ /// - reclaiming a lease in the database, i.e. setting its state to
+ /// "expired-reclaimed" or removing it from the lease database,
+ /// - updating statistics of assigned and reclaimed leases
+ ///
+ /// Note: declined leases fall under the same expiration/reclamation
+ /// processing as normal leases. In principle, it would be more elegant
+ /// to have a separate processing for declined leases reclamation. However,
+ /// due to performance reasons we decided to use them together. Several
+ /// aspects were taken into consideration. First, normal leases are expected
+ /// to expire frequently, so in a typical deployment this method will have
+ /// some leases to process. Second, declined leases are expected to be very
+ /// rare event, so in most cases there won't be any declined expired leases.
+ /// Third, the calls to LeaseMgr to obtain all leases of specific expiration
+ /// criteria are expensive, so it is better to have one call rather than
+ /// two, especially if one of those calls is expected to usually return no
+ /// leases.
+ ///
+ /// It doesn't make sense to retain declined leases that are reclaimed,
+ /// because those leases don't contain any useful information (all client
+ /// identifying information was stripped when the leave was moved to the
+ /// declined state). Therefore remove_leases parameter is ignored for
+ /// declined leases. They are always removed.
+ ///
+ /// Also, for declined leases @ref reclaimDeclinedLease6 is
+ /// called. It conducts several declined specific operation (extra log
+ /// entry, stats dump, hooks).
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in milliseconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ /// @param max_unwarned_cycles A number of consecutive processing cycles
+ /// of expired leases, after which the system issues a warning if there
+ /// are still expired leases in the database. If this value is 0, the
+ /// warning is never issued.
+ void reclaimExpiredLeases6(const size_t max_leases, const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles = 0);
+
+ /// @brief Body of reclaimExpiredLeases6.
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in milliseconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ /// @param max_unwarned_cycles A number of consecutive processing cycles
+ /// of expired leases, after which the system issues a warning if there
+ /// are still expired leases in the database. If this value is 0, the
+ /// warning is never issued.
+ void reclaimExpiredLeases6Internal(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles = 0);
+
+ /// @brief Deletes reclaimed leases expired more than specified amount
+ /// of time ago.
+ ///
+ /// @param secs Minimum number of seconds after which the lease can be
+ /// deleted.
+ void deleteExpiredReclaimedLeases6(const uint32_t secs);
+
+ /// @brief Reclaims expired IPv4 leases.
+ ///
+ /// This method retrieves a collection of expired leases and reclaims them.
+ /// See
+ /// https://gitlab.isc.org/isc-projects/kea/wikis/designs/lease-expiration#leases-reclamation-routine
+ /// for the details.
+ ///
+ /// This method is executed periodically to act upon expired leases. This
+ /// includes for each lease:
+ /// - executing "lease_expire4" hook,
+ /// - removing DNS record for a lease,
+ /// - reclaiming a lease in the database, i.e. setting its state to
+ /// "expired-reclaimed" or removing it from the lease database,
+ /// - updating statistics of assigned and reclaimed leases
+ ///
+ /// Note: declined leases fall under the same expiration/reclamation
+ /// processing as normal leases. In principle, it would be more elegant
+ /// to have a separate processing for declined leases reclamation. However,
+ /// due to performance reasons we decided to use them together. Several
+ /// aspects were taken into consideration. First, normal leases are expected
+ /// to expire frequently, so in a typical deployment this method will have
+ /// some leases to process. Second, declined leases are expected to be very
+ /// rare event, so in most cases there won't be any declined expired leases.
+ /// Third, the calls to LeaseMgr to obtain all leases of specific expiration
+ /// criteria are expensive, so it is better to have one call rather than
+ /// two, especially if one of those calls is expected to usually return no
+ /// leases.
+ ///
+ /// It doesn't make sense to retain declined leases that are reclaimed,
+ /// because those leases don't contain any useful information (all client
+ /// identifying information was stripped when the leave was moved to the
+ /// declined state). Therefore remove_leases parameter is ignored for
+ /// declined leases. They are always removed.
+ ///
+ /// Also, for declined leases @ref reclaimDeclinedLease4 is
+ /// called. It conducts several declined specific operation (extra log
+ /// entry, stats dump, hooks).
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in milliseconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ /// @param max_unwarned_cycles A number of consecutive processing cycles
+ /// of expired leases, after which the system issues a warning if there
+ /// are still expired leases in the database. If this value is 0, the
+ /// warning is never issued.
+ void reclaimExpiredLeases4(const size_t max_leases, const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles = 0);
+
+ /// @brief Body of reclaimExpiredLeases4.
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in milliseconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ /// @param max_unwarned_cycles A number of consecutive processing cycles
+ /// of expired leases, after which the system issues a warning if there
+ /// are still expired leases in the database. If this value is 0, the
+ /// warning is never issued.
+ void reclaimExpiredLeases4Internal(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles = 0);
+
+ /// @brief Deletes reclaimed leases expired more than specified amount
+ /// of time ago.
+ ///
+ /// @param secs Minimum number of seconds after which the lease can be
+ /// deleted.
+ void deleteExpiredReclaimedLeases4(const uint32_t secs);
+
+ /// @anchor findReservationDecl
+ /// @brief Attempts to find appropriate host reservation.
+ ///
+ /// Attempts to find appropriate host reservation in HostMgr. If found, it
+ /// is set in the @ref ClientContext6::hosts_.
+ ///
+ /// @note When the out-of-pool flag is enabled, because the function is
+ /// called only once per DHCP message, the reservations that are in-subnet
+ /// are not filtered out as there is no sufficient information regarding the
+ /// selected subnet, shared network or lease types, but will be filtered out
+ /// at allocation time.
+ ///
+ /// @param ctx Client context that contains all necessary information.
+ static void findReservation(ClientContext6& ctx);
+
+ /// @brief Attempts to find the host reservation for the client.
+ ///
+ /// This method attempts to find a "global" host reservation matching the
+ /// client identifier. It will return the first global reservation that
+ /// matches per the configured list of host identifiers, or an empty
+ /// pointer if no matches are found.
+ ///
+ /// @param ctx Client context holding various information about the client.
+ ///
+ /// @return Pointer to the reservation found, or an empty pointer.
+ static ConstHostPtr findGlobalReservation(ClientContext6& ctx);
+
+ /// @brief Creates an IPv6Resrv instance from a Lease6
+ ///
+ /// @param lease Reference to the Lease6
+ ///
+ /// @return The newly formed IPv6Resrv instance
+ static IPv6Resrv makeIPv6Resrv(const Lease6& lease) {
+ if (lease.type_ == Lease::TYPE_NA) {
+ return (IPv6Resrv(IPv6Resrv::TYPE_NA, lease.addr_,
+ (lease.prefixlen_ ? lease.prefixlen_ : 128)));
+ }
+
+ return (IPv6Resrv(IPv6Resrv::TYPE_PD, lease.addr_, lease.prefixlen_));
+ }
+
+public:
+ /// @brief Determines the preferred and valid v6 lease lifetimes.
+ ///
+ /// A candidate triplet for both preferred and valid lifetimes will be
+ /// selected from the first class matched to the query which defines the
+ /// value or from the subnet if none do. Classes are searched in the order
+ /// they are assigned to the query.
+ ///
+ /// If the client requested a lifetime IA hint, then the
+ /// lifetime values returned will be the requested values bounded by
+ /// the candidate triplets. If the client did not request a value, then
+ /// it simply returns the candidate triplet's default value.
+ ///
+ /// @param ctx client context that passes all necessary information. See
+ /// @ref ClientContext6 for details.
+ /// @param [out] preferred set to the preferred lifetime that should be used.
+ /// @param [out] valid set to the valid lifetime that should be used.
+ static void getLifetimes6(ClientContext6& ctx, uint32_t& preferred,
+ uint32_t& valid);
+private:
+
+ /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+ ///
+ /// Creates a lease based on specified parameters and tries to insert it
+ /// into the database. That may fail in some cases, i.e. when there is another
+ /// allocation process and we lost a race to a specific lease.
+ ///
+ /// @param ctx client context that passes all necessary information. See
+ /// @ref ClientContext6 for details.
+ /// @param addr an address that was selected and is confirmed to be
+ /// available
+ /// @param prefix_len length of the prefix (for PD only)
+ /// should be 128 for other lease types
+ /// @param [out] callout_status callout returned by the lease6_select
+ ///
+ /// The following fields of the ctx structure are used:
+ /// @ref ClientContext6::subnet_ Subnet the lease is allocated from
+ /// @ref ClientContext6::duid_ Client's DUID
+ /// @ref ClientContext6::iaid_ IAID from the IA_NA container the client sent to us
+ /// @ref ClientContext6::type_ Lease type (IA, TA or PD)
+ /// @ref ClientContext6::fwd_dns_update_ A boolean value which indicates that server takes
+ /// responsibility for the forward DNS Update for this lease
+ /// (if true).
+ /// @ref ClientContext6::rev_dns_update_ A boolean value which indicates that server takes
+ /// responsibility for the reverse DNS Update for this lease
+ /// (if true).
+ /// @ref ClientContext6::hostname_ A fully qualified domain-name of the client.
+ /// @ref ClientContext6::hwaddr_ Hardware address (optional, may be null for Lease6)
+ /// @ref ClientContext6::callout_handle_ a callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed (and there are callouts
+ /// registered)
+ /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false) or just picking
+ /// an address for SOLICIT that is not really allocated (true)
+ ///
+ /// @return allocated lease (or NULL in the unlikely case of the lease just
+ /// became unavailable)
+ Lease6Ptr createLease6(ClientContext6& ctx,
+ const isc::asiolink::IOAddress& addr,
+ const uint8_t prefix_len,
+ hooks::CalloutHandle::CalloutNextStep& callout_status);
+
+ /// @brief Allocates a normal, in-pool, unreserved lease from the pool.
+ ///
+ /// It attempts to pick a hint first, then uses allocator iteratively until
+ /// an available (not used, not reserved) lease is found. In principle, it
+ /// may return more than one lease, but we currently handle only one.
+ /// This may change in the future.
+ ///
+ /// @note If reservations-out-of-pool flag is enabled, dynamic address that
+ /// match reservations from within the dynamic pool will not be prevented to
+ /// be assigned to any client.
+ ///
+ /// @param ctx client context that contains all details (subnet, client-id, etc.)
+ ///
+ /// @return collection of newly allocated leases
+ Lease6Collection allocateUnreservedLeases6(ClientContext6& ctx);
+
+ /// @brief Allocates a normal, in-pool, unreserved lease from the pool.
+ ///
+ /// @note This function is called by allocateUnreservedLeases6 and it tries
+ /// to allocate a lease matching hint prefix length if explicitly required.
+ ///
+ /// @param ctx client context that passes all necessary information. See
+ /// @ref ClientContext6 for details.
+ /// @param hint_lease the hint lease that is stored in the database. It is
+ /// updated according to search_hint_lease flag.
+ /// @param search_hint_lease flag which indicates if hint_lease should be
+ /// retrieved from the lease storage or if it is already retrieved.
+ /// @param hint the hint address that the client provided.
+ /// @param hint_prefix_length The hint prefix length that the client
+ /// provided. For NAs this value is always 128. For PDs, 0 means that
+ /// there is no hint and that any pool will suffice. The value 128
+ /// for PDs is most likely a bug in the code when calling the addHint
+ /// function with the default value for prefix_len parameter. This
+ /// value is not a valid delegated prefix length anyway so it is
+ /// treated the same as when there is no hint provided.
+ /// @param original_subnet the initial subnet selected for this client
+ /// @param network the shared network selected for this client (if any)
+ /// @param total_attempts the total number of attempt to allocate an address
+ /// for this client. This parameter contains the accumulative value
+ /// for previous calls and current call of this function for the
+ /// lease allocation for this client (current IAID).
+ /// @param subnets_with_unavail_leases the number of subnets which have no
+ /// address available for this client. This parameter contains the
+ /// accumulative value for previous calls and current call of this
+ /// function for the lease allocation for this client (current IAID).
+ /// @param subnets_with_unavail_pools the number of pools which have no
+ /// address available for the client. This parameter contains the
+ /// accumulative value for previous calls and current call of this
+ /// function for the lease allocation for this client (current IAID).
+ /// @param [out] callout_status callout returned by the lease6_select
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length. It is
+ /// used for allocating PDs only and it is ignored for any non PD
+ /// type.
+ ///
+ /// @return a new allocated address or null pointer if none is available
+ Lease6Ptr allocateBestMatch(ClientContext6& ctx,
+ Lease6Ptr& hint_lease,
+ bool& search_hint_lease,
+ const isc::asiolink::IOAddress& hint,
+ uint8_t hint_prefix_length,
+ Subnet6Ptr original_subnet,
+ SharedNetwork6Ptr& network,
+ uint64_t& total_attempts,
+ uint64_t& subnets_with_unavail_leases,
+ uint64_t& subnets_with_unavail_pools,
+ hooks::CalloutHandle::CalloutNextStep& callout_status,
+ Allocator::PrefixLenMatchType prefix_length_match);
+
+ /// @brief Creates new leases based on reservations.
+ ///
+ /// This method allocates new leases, based on host reservations.
+ /// Existing leases are specified in the existing_leases
+ /// parameter. It first checks for non-global reservations. A
+ /// new lease is not created, if there is a lease for specified
+ /// address on existing_leases list or there is a lease used by
+ /// someone else. It last calls @c allocateGlobalReservedLeases6
+ /// to accommodate subnets using global reservations.
+ ///
+ /// @note If reservations-out-of-pool flag is enabled, reservations from
+ /// within the dynamic pool will not be checked to be assigned to the
+ /// respective client.
+ ///
+ /// @param ctx client context that contains all details (subnet, client-id, etc.)
+ /// @param existing_leases leases that are already associated with the client
+ void
+ allocateReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases);
+
+ /// @brief Creates new leases based on global reservations.
+ ///
+ /// This method is used by @allocateReservedLeases6, to allocate new leases based
+ /// on global reservation if one exists and global reservations are enabled for
+ /// the selected subnet. It differs from it's caller by looking only at the global
+ /// reservation and therefore has no need to iterate over the selected subnet or it's
+ /// siblings looking for host reservations. Like it's caller, existing leases are
+ /// specified in existing_leases parameter. A new lease is not created, if there is
+ /// a lease for specified address on existing_leases list or there is a lease used by
+ /// someone else.
+ ///
+ /// @param ctx client context that contains all details (subnet, client-id, etc.)
+ /// @param existing_leases leases that are already associated with the client
+ void
+ allocateGlobalReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases);
+
+ /// @brief Removes leases that are reserved for someone else.
+ ///
+ /// Goes through the list specified in existing_leases and removes those that
+ /// are reserved by someone else or do not belong to an allowed pool.
+ /// The removed leases are added to the ctx.removed_leases_ collection.
+ ///
+ /// @param ctx client context that contains all details (subnet, client-id, etc.)
+ /// @param existing_leases [in/out] leases that should be checked
+ void
+ removeNonmatchingReservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases);
+
+ /// @brief Removes leases that are reserved for someone else.
+ ///
+ /// Simplified version of removeNonmatchingReservedLeases6 to be
+ /// used when host reservations are disabled.
+ ///
+ /// @param ctx client context that contains all details (subnet, client-id, etc.)
+ /// @param existing_leases [in/out] leases that should be checked
+ void
+ removeNonmatchingReservedNoHostLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases);
+
+ /// @brief Removed leases that are not reserved for this client
+ ///
+ /// This method iterates over existing_leases and will remove leases that are
+ /// not reserved for this client. It will leave at least one lease on the list,
+ /// if possible. The reason to run this method is that if there is a reservation
+ /// for address A for client X and client X already has a lease for a
+ /// different address B, we should assign A and release B. However,
+ /// if for some reason we can't assign A, keeping B would be better than
+ /// not having a lease at all. Hence we may keep B if that's the only lease
+ /// left.
+ ///
+ /// @param ctx client context that contains all details (subnet, client-id, etc.)
+ /// @param existing_leases [in/out] leases that should be checked
+ void
+ removeNonreservedLeases6(ClientContext6& ctx,
+ Lease6Collection& existing_leases);
+
+ /// @brief Reuses expired IPv6 lease
+ ///
+ /// Updates existing expired lease with new information. Lease database
+ /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
+ /// dummy allocation request (i.e. SOLICIT, fake_allocation = true).
+ ///
+ /// @param expired old, expired lease
+ /// @param ctx client context that contains all details.
+ /// @param prefix_len prefix length (for PD leases)
+ /// Should be 128 for other lease types
+ /// @param [out] callout_status callout returned by the lease6_select
+ ///
+ /// The following parameters are used from the ctx structure:
+ /// @ref ClientContext6::subnet_ Subnet the lease is allocated from
+ /// @ref ClientContext6::duid_ Client's DUID
+ /// @ref ClientContext6::iaid_ IAID from the IA_NA container the client sent to us
+ /// @ref ClientContext6::fwd_dns_update_ A boolean value which indicates that server takes
+ /// responsibility for the forward DNS Update for this lease
+ /// (if true).
+ /// @ref ClientContext6::rev_dns_update_ A boolean value which indicates that server takes
+ /// responsibility for the reverse DNS Update for this lease
+ /// (if true).
+ /// @ref ClientContext6::hostname_ A fully qualified domain-name of the client.
+ /// @ref ClientContext6::callout_handle_ a callout handle (used in hooks). A
+ /// lease callouts will be executed if this parameter is passed.
+ /// @ref ClientContext6::fake_allocation_ is this real i.e. REQUEST (false)
+ /// or just picking an address for SOLICIT that is not really
+ /// allocated (true)
+ ///
+ /// @return refreshed lease
+ ///
+ /// @throw BadValue if trying to recycle lease that is still valid
+ Lease6Ptr
+ reuseExpiredLease(Lease6Ptr& expired,
+ ClientContext6& ctx,
+ uint8_t prefix_len,
+ hooks::CalloutHandle::CalloutNextStep& callout_status);
+
+ /// @brief Updates FQDN and Client's Last Transmission Time
+ /// for a collection of leases.
+ ///
+ /// This method is executed when the server finds existing leases for a
+ /// client and updates some date for these leases if needed:
+ /// - client's last transmission time (cltt), if the lease to be returned
+ /// to the client should have its lifetime extended,
+ /// - FQDN data, when the client has negotiated new FQDN with the server.
+ ///
+ /// @param ctx IPv6 client context (old versions of the leases that had
+ /// FQDN data changed will be stored in ctx.changed_leases_,
+ /// ctx.fwd_dns_update, ctx.rev_dns_update, ctx.hostname_
+ /// and ctx.fake_allocation_ will be used.
+ /// @param leases Collection of leases for which lease data should be
+ /// updated.
+ ///
+ /// @return Collection of leases with updated data. Note that returned
+ /// collection holds updated FQDN data even for fake allocation.
+ Lease6Collection updateLeaseData(ClientContext6& ctx,
+ const Lease6Collection& leases);
+
+ /// @brief Utility function that removes all leases with a specified address
+ /// @param container A collection of Lease6 pointers
+ /// @param addr address to be removed
+ ///
+ /// @return true if removed (false otherwise)
+ static bool
+ removeLeases(Lease6Collection& container,
+ const asiolink::IOAddress& addr);
+
+ /// @brief Extends specified IPv6 lease
+ ///
+ /// This method attempts to extend the lease. It will call the lease6_renew
+ /// or lease6_rebind hooks (depending on the client's message specified in
+ /// ctx.query). The lease will be extended in LeaseMgr, unless the hooks
+ /// library will set the skip flag. The old lease is added to the
+ /// the context's changed_leases_ list which allows the server to make
+ /// decisions regarding DNS updates.
+ ///
+ /// @param ctx client context that passes all necessary information. See
+ /// @ref ClientContext6 for details.
+ /// @param lease IPv6 lease to be extended.
+ void extendLease6(ClientContext6& ctx, Lease6Ptr lease);
+
+ /// @brief Reclamation mode used by the variants of @c reclaimExpiredLease
+ /// methods.
+ ///
+ /// The following operations are supported:
+ /// - remove lease upon reclamation,
+ /// - update lease's state upon reclamation to 'expired-reclaimed',
+ /// - leave the lease in the database unchanged.
+ enum DbReclaimMode {
+ DB_RECLAIM_REMOVE,
+ DB_RECLAIM_UPDATE,
+ DB_RECLAIM_LEAVE_UNCHANGED
+ };
+
+ /// @brief Reclaim DHCPv4 or DHCPv6 lease with updating lease database.
+ ///
+ /// This method is called by the lease reclamation routine to reclaim the
+ /// lease and update the lease database according to the value of the
+ /// @c remove_lease parameter.
+ ///
+ /// @param lease Pointer to the DHCPv4 or DHCPv6 lease.
+ /// @param remove_lease A boolean flag indicating if the lease should be
+ /// removed from the lease database (if true) upon reclamation.
+ /// @param callout_handle Pointer to the callout handle.
+ /// @tparam LeasePtrPtr Lease type, i.e. @c Lease4Ptr or @c Lease6Ptr.
+ template<typename LeasePtrType>
+ void reclaimExpiredLease(const LeasePtrType& lease,
+ const bool remove_lease,
+ const hooks::CalloutHandlePtr& callout_handle);
+
+ /// @brief Reclaim DHCPv4 or DHCPv6 lease without updating lease database.
+ ///
+ /// This method is called by the methods allocating leases, when the lease
+ /// being allocated needs to be first reclaimed. These methods update the
+ /// lease database on their own, so this reclamation method doesn't update
+ /// the database on reclamation.
+ ///
+ /// @param lease Pointer to the DHCPv4 or DHCPv6 lease.
+ /// @param callout_handle Pointer to the callout handle.
+ /// @tparam LeasePtrType Lease type, i.e. @c Lease4Ptr or @c Lease6Ptr.
+ template<typename LeasePtrType>
+ void reclaimExpiredLease(const LeasePtrType& lease,
+ const hooks::CalloutHandlePtr& callout_handle);
+
+ /// @brief Reclaim DHCPv6 lease.
+ ///
+ /// This method variant accepts the @c reclaim_mode parameter which
+ /// controls if the reclaimed lease should be left in the database with
+ /// no change or if it should be removed or updated.
+ ///
+ /// @param lease Pointer to the DHCPv6 lease.
+ /// @param reclaim_mode Indicates what the method should do with the reclaimed
+ /// lease in the lease database.
+ /// @param callout_handle Pointer to the callout handle.
+ void reclaimExpiredLease(const Lease6Ptr& lease,
+ const DbReclaimMode& reclaim_mode,
+ const hooks::CalloutHandlePtr& callout_handle);
+
+ /// @brief Reclaim DHCPv4 lease.
+ ///
+ /// This method variant accepts the @c reclaim_mode parameter which
+ /// controls if the reclaimed lease should be left in the database with
+ /// no change or if it should be removed or updated.
+ ///
+ /// @param lease Pointer to the DHCPv4 lease.
+ /// @param reclaim_mode Indicates what the method should do with the reclaimed
+ /// lease in the lease database.
+ /// @param callout_handle Pointer to the callout handle.
+ void reclaimExpiredLease(const Lease4Ptr& lease,
+ const DbReclaimMode& reclaim_mode,
+ const hooks::CalloutHandlePtr& callout_handle);
+
+ /// @brief Marks lease as reclaimed in the database.
+ ///
+ /// This method is called internally by the leases reclamation routines.
+ /// Depending on the value of the @c remove_lease parameter this method
+ /// will delete the reclaimed lease from the database or set its sate
+ /// to "expired-reclaimed". In the latter case it will also clear the
+ /// FQDN information.
+ ///
+ /// This method may throw exceptions if the operation on the lease database
+ /// fails for any reason.
+ ///
+ /// @param lease Pointer to the lease.
+ /// @param remove_lease Boolean flag indicating if the lease should be
+ /// removed from the database (if true).
+ /// @param lease_update_fun Pointer to the function in the @c LeaseMgr to
+ /// be used to update the lease if the @c remove_lease is set to false.
+ ///
+ /// @tparam LeasePtrType One of the @c Lease6Ptr or @c Lease4Ptr.
+ template<typename LeasePtrType>
+ void reclaimLeaseInDatabase(const LeasePtrType& lease,
+ const bool remove_lease,
+ const std::function<void (const LeasePtrType&)>&
+ lease_update_fun) const;
+
+ /// @anchor reclaimDeclinedLease4
+ /// @brief Conducts steps necessary for reclaiming declined IPv4 lease.
+ ///
+ /// These are the additional steps required when recovering a declined lease:
+ /// - bump decline recovered stat
+ /// - log lease recovery
+ /// - call lease4_recover hook
+ ///
+ /// @param lease Lease to be reclaimed from Declined state
+ ///
+ /// @return true if it's ok to remove the lease (false = hooks status says
+ /// to keep it)
+ bool reclaimDeclined(const Lease4Ptr& lease);
+
+ /// @anchor reclaimDeclinedLease6
+ /// @brief Conducts steps necessary for reclaiming declined IPv6 lease.
+ ///
+ /// These are the additional steps required when recovering a declined lease:
+ /// - bump decline recovered stat
+ /// - log lease recovery
+ /// - call lease6_recover hook
+ ///
+ /// @param lease Lease to be reclaimed from Declined state
+ ///
+ /// @return true if it's ok to remove the lease (false = hooks status says
+ /// to keep it)
+ bool reclaimDeclined(const Lease6Ptr& lease);
+
+public:
+
+ /// @brief Context information for the DHCPv4 lease allocation.
+ ///
+ /// This structure holds a set of information provided by the DHCPv4
+ /// server to the allocation engine. In particular, it holds the
+ /// client identifying information, such as HW address or client
+ /// identifier. It also holds the information about the subnet that
+ /// the client is connected to.
+ ///
+ /// This structure is also used to pass some information from
+ /// the allocation engine back to the server, i.e. the old lease
+ /// which the client had before the allocation.
+ ///
+ /// This structure is meant to be extended in the future, if more
+ /// information should be passed to the allocation engine. Note
+ /// that the big advantage of using the context structure to pass
+ /// information to the allocation engine methods is that adding
+ /// new information doesn't modify the API of the allocation engine.
+ struct ClientContext4 : public boost::noncopyable {
+ /// @brief Indicates if early global reservation is enabled.
+ ///
+ /// This caches the early-global-reservations-lookup value.
+ bool early_global_reservations_lookup_;
+
+ /// @brief Subnet selected for the client by the server.
+ Subnet4Ptr subnet_;
+
+ /// @brief Client identifier from the DHCP message.
+ ClientIdPtr clientid_;
+
+ /// @brief HW address from the DHCP message.
+ HWAddrPtr hwaddr_;
+
+ /// @brief An address that the client desires.
+ ///
+ /// If this address is set to 0 it indicates that this address
+ /// is unspecified.
+ asiolink::IOAddress requested_address_;
+
+ /// @brief Perform forward DNS update.
+ bool fwd_dns_update_;
+
+ /// @brief Perform reverse DNS update.
+ bool rev_dns_update_;
+
+ /// @brief Hostname.
+ ///
+ /// The server retrieves the hostname from the Client FQDN option,
+ /// Hostname option or the host reservation record for the client.
+ std::string hostname_;
+
+ /// @brief Callout handle associated with the client's message.
+ hooks::CalloutHandlePtr callout_handle_;
+
+ /// @brief Indicates if this is a real or fake allocation.
+ ///
+ /// The real allocation is when the allocation engine is supposed
+ /// to make an update in a lease database: create new lease, or
+ /// update existing lease.
+ bool fake_allocation_;
+
+ /// @brief If not zero, then we will allocate on DISCOVER for this
+ /// amount of time.
+ uint32_t offer_lft_;
+
+ /// @brief A pointer to an old lease that the client had before update.
+ Lease4Ptr old_lease_;
+
+ /// @brief A pointer to a newly allocated lease.
+ Lease4Ptr new_lease_;
+
+ /// @brief Holds a map of hosts belonging to the client within different
+ /// subnets.
+ ///
+ /// Multiple hosts may appear when the client belongs to a shared
+ /// network.
+ std::map<SubnetID, ConstHostPtr> hosts_;
+
+ /// @brief A pointer to the object representing a lease in conflict.
+ ///
+ /// This pointer is set by some of the allocation methods when
+ /// the lease can't be allocated because there is another lease
+ /// which is in conflict with this allocation.
+ Lease4Ptr conflicting_lease_;
+
+ /// @brief A pointer to the client's message.
+ ///
+ /// This is used in logging to retrieve the client's and the
+ /// transaction identification information.
+ Pkt4Ptr query_;
+
+ /// @brief A list holding host identifiers extracted from a message
+ /// received by the server.
+ IdentifierList host_identifiers_;
+
+ /// @brief True when the address DHCPREQUEST'ed by client is not within
+ /// a dynamic pool the server knows about.
+ bool unknown_requested_addr_;
+
+ /// @brief Returns the set of DDNS behavioral parameters based on
+ /// the selected subnet.
+ ///
+ /// If there is no selected subnet (i.e. subnet_ is empty), the
+ /// returned set will contain default values.
+ ///
+ /// @return pointer to a DdnsParams instance
+ DdnsParamsPtr getDdnsParams();
+
+
+ /// @brief Convenience function adding host identifier into
+ /// @ref host_identifiers_ list.
+ ///
+ /// @param id_type Identifier type.
+ /// @param identifier Identifier value.
+ void addHostIdentifier(const Host::IdentifierType& id_type,
+ const std::vector<uint8_t>& identifier) {
+ host_identifiers_.push_back(IdentifierPair(id_type, identifier));
+ }
+
+ /// @brief Returns host for currently selected subnet.
+ ///
+ /// If there is no such host and global reservations are enabled
+ /// returns the global host.
+ ///
+ /// @return Pointer to the host object.
+ ConstHostPtr currentHost() const;
+
+ /// @brief Returns global host reservation if there is one
+ ///
+ /// If the current subnet's reservations-global is true and
+ /// there is a global host (i.e. reservation belonging to
+ /// the global subnet), return it. Otherwise return an
+ /// empty pointer.
+ ///
+ /// @return Pointer to the host object.
+ ConstHostPtr globalHost() const;
+
+ /// @brief Default constructor.
+ ClientContext4();
+
+ /// @brief Constructor with parameters
+ ///
+ /// @param subnet subnet the allocation should come from (mandatory)
+ /// @param clientid Client identifier (optional)
+ /// @param hwaddr Client's hardware address info (mandatory)
+ /// @param requested_addr A hint that the client provided (may be 0.0.0.0)
+ /// @param fwd_dns_update Indicates whether forward DNS
+ /// update will be performed for the client (true) or not (false).
+ /// @param rev_dns_update Indicates whether reverse DNS
+ /// update will be performed for the client (true) or not (false).
+ /// @param hostname A string carrying hostname to be used for DNS updates.
+ /// @param fake_allocation Is this real i.e. REQUEST (false)
+ /// or just picking an address for DISCOVER that is not really
+ /// allocated (true)
+ /// @param offer_lft When not zero, leases ARE allocated on DISCOVER and use
+ /// this value as lease lifetime.
+ ClientContext4(const Subnet4Ptr& subnet, const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const asiolink::IOAddress& requested_addr,
+ const bool fwd_dns_update, const bool rev_dns_update,
+ const std::string& hostname, const bool fake_allocation,
+ const uint32_t offer_lft = 0);
+ private:
+ /// @brief Contains a pointer to the DDNS parameters for selected
+ /// subnet. Set by the first call to getDdnsParams() made when
+ /// the context has a selected subnet (i.e. subnet_ is not empty).
+ DdnsParamsPtr ddns_params_;
+ };
+
+ /// @brief Pointer to the @c ClientContext4.
+ typedef boost::shared_ptr<ClientContext4> ClientContext4Ptr;
+
+ /// @brief Returns IPv4 lease.
+ ///
+ /// This method finds a lease for a client using the following algorithm:
+ /// - If a lease exists for the combination of the HW address or client id
+ /// and a subnet, try to use this lease for the client. If the client
+ /// has a reservation for an address for which the lease was created or
+ /// the client desires to renew the lease for this address (ciaddr or
+ /// requested IP address option), the server renews the lease for the
+ /// client. If the client desires a different address or the server has
+ /// a (potentially new) reservation for a different address for this
+ /// client, the existing lease is replaced with a new lease.
+ /// - If the client has no lease in the lease database the server will try
+ /// to allocate a new lease. If the client has a reservation for the
+ /// particular address or if it has specified a desired address the
+ /// server will check if the particular address is not allocated to
+ /// another client. If the address is available, the server will allocate
+ /// this address for the client.
+ /// - If the desired address is unavailable the server checks if the
+ /// lease for this address has expired. If the lease is expired, the
+ /// server will allocate this lease to the client. The relevant
+ /// information will be updated, e.g. new client HW address, host name
+ /// etc.
+ /// - If the desired address is in use by another client, the server will
+ /// try to allocate a different address. The server picks addresses from
+ /// a dynamic pool and checks if the address is available and that
+ /// it is not reserved for another client. If it is in use by another
+ /// client or if it is reserved for another client, the address is not
+ /// allocated. The server picks the next address and repeats this check.
+ /// Note that the server ceases allocation after the configured number
+ /// of unsuccessful attempts.
+ ///
+ /// The lease allocation process is slightly different for the
+ /// DHCPDISCOVER and DHCPREQUEST messages. In the former case, the client
+ /// may specify the requested IP address option with a desired address and
+ /// the server treats this address as a hint. This means that the server may
+ /// allocate a different address at its discretion and send it to the
+ /// client in the DHCPOFFER. If the client accepts this offer it specifies
+ /// this address in the requested IP address option in the DHCPREQUEST.
+ /// At this point, the allocation engine will use the requested IP address
+ /// as a hard requirement and if this address can't be allocated for
+ /// any reason, the allocation engine returns NULL lease. As a result,
+ /// the DHCP server sends a DHCPNAK to the client and the client
+ /// falls back to the DHCP server discovery.
+ ///
+ /// The only exception from this rule is when the client doesn't specify
+ /// a requested IP address option (invalid behavior) in which case the
+ /// allocation engine will try to allocate any address.
+ ///
+ /// If there is an address reservation specified for the particular client
+ /// the reserved address always takes precedence over addresses from the
+ /// dynamic pool or even an address currently allocated for this client.
+ ///
+ /// It is possible that the address reserved for the particular client
+ /// is in use by another client, e.g. as a result of pools reconfiguration.
+ /// In this case, when the client requests allocation of the reserved
+ /// address and the server determines that it is leased to someone else,
+ /// the allocation engine allocates a different address for this client.
+ ///
+ /// When the client having a lease returns to renew, the allocation engine
+ /// doesn't extend the lease for it and returns a NULL pointer. The client
+ /// falls back to the 4-way exchange and a different lease is allocated.
+ /// At this point, the reserved address is freed and can be allocated to
+ /// the client which holds this reservation. However, this client has a
+ /// lease for a different address at this time. When the client renews its
+ /// lease it receives the DHCPNAK and falls back to the DHCP server
+ /// discovery and obtains the lease for the reserved address.
+ ///
+ /// When a server should do DNS updates, it is required that allocation
+ /// returns the information about how the lease was obtained by the allocation
+ /// engine. In particular, the DHCP server should be able to check whether
+ /// an existing lease was returned, or a new lease was allocated. When an
+ /// existing lease was returned, the server should check whether the FQDN has
+ /// changed between the allocation of the old and new lease. If so, the server
+ /// should perform the appropriate DNS update. If not, the server may choose
+ /// to not perform the update. The information about the old lease is returned via
+ /// @c old_lease parameter. If NULL value is returned, it is an indication
+ /// that a new lease was allocated for the client. If non-NULL value is
+ /// returned, it is an indication that allocation engine reused/renewed an
+ /// existing lease.
+ ///
+ /// @param ctx client context that passes all necessary information. See
+ /// @ref ClientContext4 for details.
+ ///
+ /// The following fields of @ref ClientContext4 are used:
+ ///
+ /// - @ref ClientContext4::subnet_ subnet the allocation should come from
+ /// - @ref ClientContext4::clientid_ Client identifier
+ /// - @ref ClientContext4::hwaddr_ Client's hardware address info
+ /// - @ref ClientContext4::requested_address_ A hint that the client provided
+ /// - @ref ClientContext4::fwd_dns_update_ Indicates whether forward DNS
+ /// update will be performed for the client (true) or not (false).
+ /// - @ref ClientContext4::rev_dns_update_ Indicates whether reverse DNS
+ /// update will be performed for the client (true) or not (false).
+ /// - @ref ClientContext4::hostname_ A string carrying hostname to be used for
+ /// DNS updates.
+ /// - @ref ClientContext4::fake_allocation_ Is this real i.e. REQUEST (false)
+ /// or just picking an address for DISCOVER that is not really
+ /// allocated (true)
+ /// - @ref ClientContext4::callout_handle_ A callout handle (used in hooks).
+ /// A lease callouts will be executed if this parameter is passed.
+ /// - @ref ClientContext4::old_lease_ [out] Holds the pointer to a previous
+ /// instance of a lease. The NULL pointer indicates that lease didn't
+ /// exist prior to calling this function (e.g. new lease has been allocated).
+ ///
+ /// @return Allocated IPv4 lease (or NULL if allocation failed).
+ Lease4Ptr allocateLease4(ClientContext4& ctx);
+
+ /// @brief Attempts to find the host reservation for the client.
+ ///
+ /// Attempts to find appropriate host reservation in HostMgr. If found, it
+ /// is set in the @ref ClientContext4::hosts_.
+ ///
+ /// @note When the out-of-pool flag is enabled, because the function is
+ /// called only once per DHCP message, the reservations that are in-subnet
+ /// are not filtered out as there is no sufficient information regarding the
+ /// selected subnet or shared network, but will be filtered out at
+ /// allocation time.
+ ///
+ /// @param ctx Client context holding various information about the client.
+ static void findReservation(ClientContext4& ctx);
+
+ /// @brief Attempts to find the host reservation for the client.
+ ///
+ /// This method attempts to find a "global" host reservation matching the
+ /// client identifier. It will return the first global reservation that matches
+ /// per the configured list of host identifiers, or an empty pointer if no
+ /// matches are found.
+ ///
+ /// @param ctx Client context holding various information about the client.
+ ///
+ /// @return Pointer to the reservation found, or an empty pointer.
+ static ConstHostPtr findGlobalReservation(ClientContext4& ctx);
+
+ /// @brief Returns the valid lifetime based on the v4 context
+ ///
+ /// If the client query is a BOOTP query, the value returned will
+ /// be Lease::INFINITY_LFT.
+ ///
+ /// Otherwise, a candidate triplet will be selected from the first
+ /// class matched to the query which defines it or from the subnet
+ /// if none do. Classes are searched in the order they are assigned
+ /// to the query.
+ ///
+ /// If the client requested a lifetime value via DHCP option 51, then the
+ /// lifetime value returned will be the requested value bounded by
+ /// the candidate triplet. If the client did not request a value, then
+ /// it simply returns the candidate triplet's default value.
+ ///
+ /// @param ctx Client context holding various information about the client.
+ /// @return unsigned integer value of the valid lifetime to use.
+ static uint32_t getValidLft(const ClientContext4& ctx);
+
+ /// @brief Returns the offer lifetime based on the v4 context
+ ///
+ /// If the client query is a BOOTP query or something other than
+ /// DHCPDISCOVER, return 0.
+ ///
+ /// Otherwise, the value will be selected from the first
+ /// class matched to the query which defines it or from the subnet
+ /// if none do. Classes are searched in the order they are assigned
+ /// to the query.
+ ///
+ /// @param ctx Client context holding various information about the client.
+ /// @return unsigned integer value of the offer lifetime to use.
+ static uint32_t getOfferLft(const ClientContext4& ctx);
+
+private:
+
+ /// @brief Offers the lease.
+ ///
+ /// This method is called by the @c AllocEngine::allocateLease4 when
+ /// the server is processing a DHCPDISCOVER message, i.e. the fake
+ /// allocation case.
+ ///
+ /// This method doesn't modify leases in the lease database. It finds
+ /// the most suitable lease for the client and returns it to the caller.
+ /// The server uses this lease when it sends the DHCPOFFER to the
+ /// client from which it has received a DHCPDISCOVER message.
+ ///
+ /// The lease is found using the following algorithm:
+ /// -# If there is a reservation for the client, try to use the reserved
+ /// address. This may fail if the particular address is in use by
+ /// another client. In such case:
+ /// -# If the client has a lease, try to offer this lease. This may fail
+ /// if it turns out that this address is reserved for another client
+ /// or the address doesn't belong to the address pool. In such case:
+ /// -# Try to allocate the address provided by the client as a hint.
+ /// This may fail if the address is in use or is reserved by some
+ /// other client. In such case:
+ /// -# Try to offer an address from the dynamic pool.
+ ///
+ /// @throw various exceptions if the allocation goes wrong.
+ ///
+ /// @param ctx Client context holding the data extracted from the
+ /// client's message.
+ ///
+ /// @return A pointer to the offered lease, or NULL if no suitable lease
+ /// has been found.
+ Lease4Ptr discoverLease4(ClientContext4& ctx);
+
+ /// @brief Allocates the lease.
+ ///
+ /// This method is called by the @c AllocEngine::allocateLease4 when
+ /// the server is processing a DHCPREQUEST message, i.e. the real
+ /// allocation case.
+ ///
+ /// This method modifies the lease information in the lease database.
+ /// It adds new leases, modifies existing leases or deletes them.
+ ///
+ /// The method returns NULL to indicate that the lease allocation
+ /// has failed when any of the following occur:
+ /// -# The requested address is specified but is reserved for another
+ /// client.
+ /// -# The requested address is in use by another client.
+ /// -# There is a reservation for the particular client, the
+ /// reserved address is not in use by another client and the
+ /// requested address is different than the reserved address.
+ /// -# There is no reservation for the client and the requested address
+ /// is not in the dynamic pool.
+ ///
+ /// If none of the above occurs, the method will try to allocate the
+ /// lease for the client using the following algorithm:
+ /// -# If the client has a lease and the client is requesting the
+ /// address for which it has a lease, renew its lease.
+ /// -# If the client is requesting a different address than that for
+ /// which it has a lease, try to allocate the requested address.
+ /// This may fail if the address is in use by another client.
+ /// -# If the client is not requesting any specific address, allocate
+ /// the address from the dynamic pool.
+ ///
+ /// @throw various exceptions if the allocation goes wrong.
+ ///
+ /// @param ctx Client context holding the data extracted from the
+ /// client's message.
+ ///
+ /// @return A pointer to the allocated lease, or NULL if no suitable
+ /// lease could be allocated.
+ Lease4Ptr requestLease4(ClientContext4& ctx);
+
+ /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+ ///
+ /// Creates a lease based on specified parameters and tries to insert it
+ /// into the database. That may fail in some cases, e.g. when there is another
+ /// allocation process and we lost a race to a specific lease.
+ ///
+ /// @param ctx client context that contains additional parameters.
+ /// @param addr An address that was selected and is confirmed to be available
+ /// @param [out] callout_status callout returned by the lease6_select
+ ///
+ /// In particular, the following fields from Client context are used:
+ /// - @ref ClientContext4::subnet_ Subnet the lease is allocated from
+ /// - @ref ClientContext4::clientid_ Client identifier
+ /// - @ref ClientContext4::hwaddr_ Client's hardware address
+ /// - @ref ClientContext4::fwd_dns_update_ Indicates whether forward DNS update
+ /// will be performed for the client (true) or not (false).
+ /// - @ref ClientContext4::rev_dns_update_ Indicates whether reverse DNS update
+ /// will be performed for the client (true) or not (false).
+ /// - @ref ClientContext4::hostname_ A string carrying hostname to be used for
+ /// DNS updates.
+ /// - @ref ClientContext4::callout_handle_ a callout handle (used in hooks).
+ /// A lease callouts will be executed if this parameter is passed
+ /// (and there are callouts registered)
+ /// - @ref ClientContext4::fake_allocation_ Is this real i.e. REQUEST (false)
+ /// or just picking an address for DISCOVER that is not really
+ /// allocated (true)
+ ///
+ /// @return allocated lease (or NULL in the unlikely case of the lease just
+ /// become unavailable)
+ Lease4Ptr createLease4(const ClientContext4& ctx,
+ const isc::asiolink::IOAddress& addr,
+ hooks::CalloutHandle::CalloutNextStep& callout_status);
+
+ /// @brief Renews a DHCPv4 lease.
+ ///
+ /// This method updates the lease with the information from the provided
+ /// context and invokes the lease4_renew callout.
+ ///
+ /// The address of the lease being renewed is NOT updated.
+ ///
+ /// @param lease A lease to be renewed.
+ /// @param ctx Message processing context. It holds various information
+ /// extracted from the client's message and required to allocate a lease.
+ ///
+ /// @return Returns renewed lease. Note that the lease is only updated when
+ /// it is an actual allocation (not processing a DHCPDISCOVER message).
+ Lease4Ptr renewLease4(const Lease4Ptr& lease, ClientContext4& ctx);
+
+ /// @brief Reuses expired DHCPv4 lease.
+ ///
+ /// Makes a new allocation using an expired lease. The lease is updated with
+ /// the information from the provided context. Typically, an expired lease
+ /// which belonged to one client may be assigned to another client
+ /// which asked for the specific address.
+ ///
+ /// @param expired An old, expired lease.
+ /// @param ctx Message processing context. It holds various information
+ /// extracted from the client's message and required to allocate a lease.
+ /// @param [out] callout_status callout returned by the lease4_select
+ ///
+ /// @return Updated lease instance.
+ ///
+ /// @throw BadValue if trying to reuse a lease which is still valid or
+ /// when the provided parameters are invalid.
+ Lease4Ptr
+ reuseExpiredLease4(Lease4Ptr& expired, ClientContext4& ctx,
+ hooks::CalloutHandle::CalloutNextStep& callout_status);
+
+ /// @brief Allocates the lease by replacing an existing lease.
+ ///
+ /// This method checks if the lease database contains the lease for
+ /// the specified address. If the lease exists and has expired, it
+ /// reuses the expired lease. If the lease doesn't exist, it creates
+ /// the new lease.
+ ///
+ /// @param address Requested address for which the lease should be
+ /// allocated.
+ /// @param ctx Client context holding the data extracted from the
+ /// client's message.
+ /// @param [out] callout_status callout returned by the lease4_select
+ ///
+ /// @return A pointer to the allocated lease or NULL if the allocation
+ /// was not successful.
+ Lease4Ptr
+ allocateOrReuseLease4(const asiolink::IOAddress& address,
+ ClientContext4& ctx,
+ hooks::CalloutHandle::CalloutNextStep& callout_status);
+
+ /// @brief Allocates the lease from the dynamic pool.
+ ///
+ /// This method allocates the lease from the dynamic pool. It uses
+ /// one of the allocators to pick addresses from the pool and if the
+ /// address appears to be available, it allocates the new lease
+ /// using this address. The number of attempts depends on the size
+ /// of the dynamic pool. If all of the addresses in the pool have
+ /// been tried and all of them appeared to be in use, the allocation
+ /// fails. This is the case when the pool is exhausted.
+ ///
+ /// The time required to find a suitable lease depends on the current
+ /// pool utilization.
+ ///
+ /// @param ctx Client context holding the data extracted from the
+ /// client's message.
+ ///
+ /// @return A pointer to the allocated lease or NULL if the allocation
+ /// was not successful.
+ Lease4Ptr allocateUnreservedLease4(ClientContext4& ctx);
+
+ /// @brief Updates the specified lease with the information from a context.
+ ///
+ /// The context, specified as an argument to this method, holds various
+ /// information gathered from the client's message and passed to the
+ /// allocation engine. The allocation engine uses this information to make
+ /// lease allocation decisions. Some public methods of the allocation engine
+ /// requires updating the lease information with the data gathered from the
+ /// context, e.g. @c AllocEngine::reuseExpiredLease requires updating the
+ /// expired lease with fresh information from the context to create a
+ /// lease to be held for the client.
+ ///
+ /// Note that this doesn't update the lease address.
+ ///
+ /// @warning This method doesn't check if the pointer to the lease is
+ /// valid nor if the subnet to the pointer in the @c ctx is valid.
+ /// The caller is responsible for making sure that they are valid.
+ ///
+ /// @param [out] lease A pointer to the lease to be updated.
+ /// @param ctx A context containing information from the server about the
+ /// client and its message.
+ /// @return True if there was a significant (e.g. other than cltt) change,
+ /// false otherwise.
+ bool updateLease4Information(const Lease4Ptr& lease,
+ ClientContext4& ctx) const;
+
+protected:
+ /// @brief Stores additional client query parameters on a V4 lease
+ ///
+ /// Extended features such as LeaseQuery require additional parameters
+ /// to be stored for each lease, than we would otherwise retain.
+ /// This function adds that information to the lease's user-context.
+ /// (Note it is protected to facilitate unit testing).
+ ///
+ /// @warning This method doesn't check if the pointer to the lease is
+ /// valid nor if the subnet to the pointer in the @c ctx is valid.
+ /// The caller is responsible for making sure that they are valid.
+ ///
+ /// @param [out] lease A pointer to the lease to be updated.
+ /// @param ctx A context containing information from the server about the
+ /// client and its message.
+ /// @return True if there was a significant (e.g. other than cltt) change,
+ /// false otherwise.
+ bool updateLease4ExtendedInfo(const Lease4Ptr& lease,
+ const ClientContext4& ctx) const;
+
+ /// @brief Stores additional client query parameters on a V6 lease
+ ///
+ /// Extended features such as LeaseQuery and Reconfigure require
+ /// additional parameters to be stored for each lease, than we would
+ /// otherwise retain. This function adds that information to the
+ /// lease's user-context.
+ /// (Note it is protected to facilitate unit testing).
+ ///
+ /// @warning This method doesn't check if the pointer to the lease is
+ /// valid nor if the subnet to the pointer in the @c ctx is valid.
+ /// The caller is responsible for making sure that they are valid.
+ ///
+ /// @param [out] lease A pointer to the lease to be updated.
+ /// @param ctx A context containing information from the server about the
+ /// client and its message.
+ void updateLease6ExtendedInfo(const Lease6Ptr& lease,
+ const ClientContext6& ctx) const;
+
+ /// @brief Clear extended info from a reclaimed V4 lease
+ ///
+ /// @param [out] lease A pointer to the reclaimed lease.
+ void clearReclaimedExtendedInfo(const Lease4Ptr& lease) const;
+
+ /// @brief Clear extended info from a reclaimed V6 lease
+ ///
+ /// @param [out] lease A pointer to the reclaimed lease.
+ void clearReclaimedExtendedInfo(const Lease6Ptr& lease) const;
+
+private:
+
+ /// @brief Try to reuse an already allocated lease.
+ ///
+ /// This function computes and sets when acceptable the reusable
+ /// valid lifetime of an already allocated lease.
+ /// This uses the cache-threshold and cache-max-age parameters.
+ ///
+ /// A not zero value for the reusable valid lifetime means the
+ /// lease can reuse i.e.:
+ /// - the lease is not updated in the lease database.
+ /// - the previous value of the lease can be returned to the client.
+ ///
+ /// @param [in,out] lease A pointer to the lease to be updated.
+ /// @param subnet A pointer to the lease subnet.
+ void setLeaseReusable(const Lease4Ptr& lease,
+ const ClientContext4& ctx) const;
+
+ /// @brief Try to reuse an already allocated lease.
+ ///
+ /// This function computes and sets when acceptable the reusable
+ /// valid lifetime of an already allocated lease.
+ /// This uses the cache-threshold and cache-max-age parameters.
+ ///
+ /// A not zero value for the reusable valid lifetime means the
+ /// lease can reuse i.e.:
+ /// - the lease is not updated in the lease database.
+ /// - the previous value of the lease can be returned to the client.
+ ///
+ /// @param [in,out] lease A pointer to the lease to be updated.
+ /// @param current_preferred_lft Current preferred lease lifetime.
+ /// @param subnet A pointer to the lease subnet.
+ void setLeaseReusable(const Lease6Ptr& lease,
+ uint32_t current_preferred_lft,
+ const ClientContext6& ctx) const;
+
+private:
+
+ /// @brief Number of consecutive DHCPv4 leases' reclamations after
+ /// which there are still expired leases in the database.
+ uint16_t incomplete_v4_reclamations_;
+
+ /// @brief Number of consecutive DHCPv6 leases' reclamations after
+ /// which there are still expired leases in the database.
+ uint16_t incomplete_v6_reclamations_;
+
+public:
+
+ /// @brief Get the read-write mutex.
+ ///
+ /// This read-write mutex is used to make reclamation exclusive
+ /// of multi-threaded packet processing.
+ /// @return A reference to the read-write mutex.
+ isc::util::ReadWriteMutex& getReadWriteMutex() {
+ return (rw_mutex_);
+ }
+
+ /// @brief The read-write mutex.
+ isc::util::ReadWriteMutex rw_mutex_;
+
+ /// @brief Generates a label for subnet or shared-network from subnet
+ ///
+ /// Creates a string for the subnet and its ID for stand alone subnets
+ /// or the shared-network and its name if the given subnet belongs to a
+ /// shared-network.
+ ///
+ /// @param subnet pointer to the source subnet
+ /// @return string containing the generated label
+ static std::string labelNetworkOrSubnet(SubnetPtr subnet);
+};
+
+/// @brief A pointer to the @c AllocEngine object.
+typedef boost::shared_ptr<AllocEngine> AllocEnginePtr;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // ALLOC_ENGINE_H
diff --git a/src/lib/dhcpsrv/alloc_engine_log.cc b/src/lib/dhcpsrv/alloc_engine_log.cc
new file mode 100644
index 0000000..d3086de
--- /dev/null
+++ b/src/lib/dhcpsrv/alloc_engine_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2015-2018 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/.
+
+/// @brief Defines the logger used by the @c isc::dhcp::AllocEngine
+
+#include <config.h>
+
+#include "dhcpsrv/alloc_engine_log.h"
+
+namespace isc {
+namespace dhcp {
+
+extern const int ALLOC_ENGINE_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int ALLOC_ENGINE_DBG_RESULTS = isc::log::DBGLVL_TRACE_BASIC_DATA;
+extern const int ALLOC_ENGINE_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
+extern const int ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA =
+ isc::log::DBGLVL_TRACE_DETAIL_DATA;
+
+isc::log::Logger alloc_engine_logger("alloc-engine");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/alloc_engine_log.h b/src/lib/dhcpsrv/alloc_engine_log.h
new file mode 100644
index 0000000..14c1fa4
--- /dev/null
+++ b/src/lib/dhcpsrv/alloc_engine_log.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2015-2018 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 ALLOC_ENGINE_LOG_H
+#define ALLOC_ENGINE_LOG_H
+
+#include <dhcpsrv/alloc_engine_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+//@{
+/// @brief Logging levels for the @c AllocEngine.
+///
+/// Defines the levels used to output debug messages from the @c AllocEngine.
+
+/// @brief Traces normal operations
+extern const int ALLOC_ENGINE_DBG_TRACE;
+
+/// @brief Records the results of various operations.
+///
+/// Messages logged at this level will typically contain summary of the
+/// data retrieved.
+extern const int ALLOC_ENGINE_DBG_RESULTS;
+
+/// @brief Record detailed traces
+///
+/// Messages logged at this level will log detailed tracing information.
+extern const int ALLOC_ENGINE_DBG_TRACE_DETAIL;
+
+/// @brief Records detailed results of various operations.
+///
+/// Messages logged at this level will contain detailed results.
+extern const int ALLOC_ENGINE_DBG_TRACE_DETAIL_DATA;
+
+//@}
+
+/// @brief Logger for the @c AllocEngine.
+///
+/// Define the logger used to log messages in @c AllocEngine.
+extern isc::log::Logger alloc_engine_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // ALLOC_ENGINE_LOG_H
diff --git a/src/lib/dhcpsrv/alloc_engine_messages.cc b/src/lib/dhcpsrv/alloc_engine_messages.cc
new file mode 100644
index 0000000..060dcfe
--- /dev/null
+++ b/src/lib/dhcpsrv/alloc_engine_messages.cc
@@ -0,0 +1,181 @@
+// File created from ../../../src/lib/dhcpsrv/alloc_engine_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS = "ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS";
+extern const isc::log::MessageID ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS6 = "ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS6";
+extern const isc::log::MessageID ALLOC_ENGINE_LEASE_RECLAIMED = "ALLOC_ENGINE_LEASE_RECLAIMED";
+extern const isc::log::MessageID ALLOC_ENGINE_REMOVAL_NCR_FAILED = "ALLOC_ENGINE_REMOVAL_NCR_FAILED";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_ERROR = "ALLOC_ENGINE_V4_ALLOC_ERROR";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL = "ALLOC_ENGINE_V4_ALLOC_FAIL";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES = "ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_NO_POOLS = "ALLOC_ENGINE_V4_ALLOC_FAIL_NO_POOLS";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_SHARED_NETWORK = "ALLOC_ENGINE_V4_ALLOC_FAIL_SHARED_NETWORK";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_SUBNET = "ALLOC_ENGINE_V4_ALLOC_FAIL_SUBNET";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_DECLINED_RECOVERED = "ALLOC_ENGINE_V4_DECLINED_RECOVERED";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT = "ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_DISCOVER_HR = "ALLOC_ENGINE_V4_DISCOVER_HR";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE = "ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_FAILED = "ALLOC_ENGINE_V4_LEASES_RECLAMATION_FAILED";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW = "ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_START = "ALLOC_ENGINE_V4_LEASES_RECLAMATION_START";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT = "ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASE_RECLAIM = "ALLOC_ENGINE_V4_LEASE_RECLAIM";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED = "ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES = "ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE = "ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_OFFER_NEW_LEASE = "ALLOC_ENGINE_V4_OFFER_NEW_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE = "ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE = "ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE = "ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED = "ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED = "ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED = "ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE = "ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_INVALID = "ALLOC_ENGINE_V4_REQUEST_INVALID";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_IN_USE = "ALLOC_ENGINE_V4_REQUEST_IN_USE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL = "ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS = "ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_REMOVE_LEASE = "ALLOC_ENGINE_V4_REQUEST_REMOVE_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_USE_HR = "ALLOC_ENGINE_V4_REQUEST_USE_HR";
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA = "ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_ERROR = "ALLOC_ENGINE_V6_ALLOC_ERROR";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL = "ALLOC_ENGINE_V6_ALLOC_FAIL";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES = "ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_NO_POOLS = "ALLOC_ENGINE_V6_ALLOC_FAIL_NO_POOLS";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_SHARED_NETWORK = "ALLOC_ENGINE_V6_ALLOC_FAIL_SHARED_NETWORK";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_SUBNET = "ALLOC_ENGINE_V6_ALLOC_FAIL_SUBNET";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS = "ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_LEASES_HR = "ALLOC_ENGINE_V6_ALLOC_LEASES_HR";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR = "ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR = "ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_NO_V6_HR = "ALLOC_ENGINE_V6_ALLOC_NO_V6_HR";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_UNRESERVED = "ALLOC_ENGINE_V6_ALLOC_UNRESERVED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME = "ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_DECLINED_RECOVERED = "ALLOC_ENGINE_V6_DECLINED_RECOVERED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED = "ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED = "ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_ERROR = "ALLOC_ENGINE_V6_EXTEND_ERROR";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_LEASE = "ALLOC_ENGINE_V6_EXTEND_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_LEASE_DATA = "ALLOC_ENGINE_V6_EXTEND_LEASE_DATA";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_NEW_LEASE_DATA = "ALLOC_ENGINE_V6_EXTEND_NEW_LEASE_DATA";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_HINT_RESERVED = "ALLOC_ENGINE_V6_HINT_RESERVED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_HR_ADDR_GRANTED = "ALLOC_ENGINE_V6_HR_ADDR_GRANTED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_HR_PREFIX_GRANTED = "ALLOC_ENGINE_V6_HR_PREFIX_GRANTED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE = "ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_FAILED = "ALLOC_ENGINE_V6_LEASES_RECLAMATION_FAILED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW = "ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_START = "ALLOC_ENGINE_V6_LEASES_RECLAMATION_START";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT = "ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASE_RECLAIM = "ALLOC_ENGINE_V6_LEASE_RECLAIM";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED = "ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES = "ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE = "ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE = "ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED = "ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_HR = "ALLOC_ENGINE_V6_RENEW_HR";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED = "ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED = "ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA = "ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE = "ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE = "ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE = "ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE";
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE = "ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE";
+
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS", "ignoring globally reserved address %1, it falls outside %2",
+ "ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS6", "ignoring globally reserved address %1, it falls outside %2",
+ "ALLOC_ENGINE_LEASE_RECLAIMED", "successfully reclaimed lease %1",
+ "ALLOC_ENGINE_REMOVAL_NCR_FAILED", "sending removal name change request failed for lease %1: %2",
+ "ALLOC_ENGINE_V4_ALLOC_ERROR", "%1: error during attempt to allocate an IPv4 address: %2",
+ "ALLOC_ENGINE_V4_ALLOC_FAIL", "%1: failed to allocate an IPv4 address after %2 attempt(s)",
+ "ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES", "%1: Failed to allocate an IPv4 address for client with classes: %2",
+ "ALLOC_ENGINE_V4_ALLOC_FAIL_NO_POOLS", "%1: no pools were available for the address allocation",
+ "ALLOC_ENGINE_V4_ALLOC_FAIL_SHARED_NETWORK", "%1: failed to allocate an IPv4 address in the shared network %2: %3 subnets have no available addresses, %4 subnets have no matching pools",
+ "ALLOC_ENGINE_V4_ALLOC_FAIL_SUBNET", "%1: failed to allocate an IPv4 lease in the subnet %2, subnet-id %3, shared network %4",
+ "ALLOC_ENGINE_V4_DECLINED_RECOVERED", "IPv4 address %1 was recovered after %2 seconds of probation-period",
+ "ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT", "%1: conflicting reservation for address %2 with existing lease %3",
+ "ALLOC_ENGINE_V4_DISCOVER_HR", "client %1 sending DHCPDISCOVER has reservation for the address %2",
+ "ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE", "reclaimed %1 leases in %2",
+ "ALLOC_ENGINE_V4_LEASES_RECLAMATION_FAILED", "reclamation of expired leases failed: %1",
+ "ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW", "expired leases still exist after %1 reclamations",
+ "ALLOC_ENGINE_V4_LEASES_RECLAMATION_START", "starting reclamation of expired leases (limit = %1 leases or %2 milliseconds)",
+ "ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT", "timeout of %1 ms reached while reclaiming IPv4 leases",
+ "ALLOC_ENGINE_V4_LEASE_RECLAIM", "%1: reclaiming expired lease for address %2",
+ "ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED", "failed to reclaim the lease %1: %2",
+ "ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES", "all expired leases have been reclaimed",
+ "ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE", "allocation engine will try to offer existing lease to the client %1",
+ "ALLOC_ENGINE_V4_OFFER_NEW_LEASE", "allocation engine will try to offer new lease to the client %1",
+ "ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE", "allocation engine will try to offer requested lease %1 to the client %2",
+ "ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE", "begin deletion of reclaimed leases expired more than %1 seconds ago",
+ "ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE", "successfully deleted %1 expired-reclaimed leases",
+ "ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED", "deletion of expired-reclaimed leases failed: %1",
+ "ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED", "%1: requested address %2 is reserved",
+ "ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED", "%1: trying to allocate requested address %2",
+ "ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE", "%1: extending lifetime of the lease for address %2",
+ "ALLOC_ENGINE_V4_REQUEST_INVALID", "client %1 having a reservation for address %2 is requesting invalid address %3",
+ "ALLOC_ENGINE_V4_REQUEST_IN_USE", "%1: requested address %2 is in use",
+ "ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL", "client %1, which doesn't have a reservation, requested address %2 out of the dynamic pool",
+ "ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS", "client %1 hasn't specified an address - picking available address from the pool",
+ "ALLOC_ENGINE_V4_REQUEST_REMOVE_LEASE", "%1: removing previous client's lease %2",
+ "ALLOC_ENGINE_V4_REQUEST_USE_HR", "client %1 hasn't requested specific address, using reserved address %2",
+ "ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA", "%1: reusing expired lease, updated lease information: %2",
+ "ALLOC_ENGINE_V6_ALLOC_ERROR", "%1: error during attempt to allocate an IPv6 address: %2",
+ "ALLOC_ENGINE_V6_ALLOC_FAIL", "%1: failed to allocate an IPv6 lease after %2 attempt(s)",
+ "ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES", "%1: Failed to allocate an IPv6 address for client with classes: %2",
+ "ALLOC_ENGINE_V6_ALLOC_FAIL_NO_POOLS", "%1: no pools were available for the lease allocation",
+ "ALLOC_ENGINE_V6_ALLOC_FAIL_SHARED_NETWORK", "%1: failed to allocate a lease in the shared network %2: %3 subnets have no available leases, %4 subnets have no matching pools",
+ "ALLOC_ENGINE_V6_ALLOC_FAIL_SUBNET", "%1: failed to allocate an IPv6 lease in the subnet %2, subnet-id %3, shared network %4",
+ "ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS", "%1: lease type %2 for reserved address/prefix %3 already exists",
+ "ALLOC_ENGINE_V6_ALLOC_LEASES_HR", "leases and static reservations found for client %1",
+ "ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR", "no reservations found but leases exist for client %1",
+ "ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR", "no leases found but reservations exist for client %1",
+ "ALLOC_ENGINE_V6_ALLOC_NO_V6_HR", "%1: unable to allocate reserved leases - no IPv6 reservations",
+ "ALLOC_ENGINE_V6_ALLOC_UNRESERVED", "no static reservations available - trying to dynamically allocate leases for client %1",
+ "ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME", "%1: using a calculated preferred-lifetime of %2",
+ "ALLOC_ENGINE_V6_DECLINED_RECOVERED", "IPv6 address %1 was recovered after %2 seconds of probation-period",
+ "ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED", "%1: expired lease for the client's hint %2 is reserved for another client",
+ "ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED", "allocate new (unreserved) leases for the renewing client %1",
+ "ALLOC_ENGINE_V6_EXTEND_ERROR", "%1: allocation engine experienced error with attempting to extend lease lifetime: %2",
+ "ALLOC_ENGINE_V6_EXTEND_LEASE", "%1: extending lifetime of the lease type %2, address %3",
+ "ALLOC_ENGINE_V6_EXTEND_LEASE_DATA", "%1: detailed information about the lease being extended: %2",
+ "ALLOC_ENGINE_V6_EXTEND_NEW_LEASE_DATA", "%1: new lease information for the lease being extended: %2",
+ "ALLOC_ENGINE_V6_HINT_RESERVED", "%1: lease for the client's hint %2 is reserved for another client",
+ "ALLOC_ENGINE_V6_HR_ADDR_GRANTED", "reserved address %1 was assigned to client %2",
+ "ALLOC_ENGINE_V6_HR_PREFIX_GRANTED", "reserved prefix %1/%2 was assigned to client %3",
+ "ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE", "reclaimed %1 leases in %2",
+ "ALLOC_ENGINE_V6_LEASES_RECLAMATION_FAILED", "reclamation of expired leases failed: %1",
+ "ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW", "expired leases still exist after %1 reclamations",
+ "ALLOC_ENGINE_V6_LEASES_RECLAMATION_START", "starting reclamation of expired leases (limit = %1 leases or %2 milliseconds)",
+ "ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT", "timeout of %1 ms reached while reclaiming IPv6 leases",
+ "ALLOC_ENGINE_V6_LEASE_RECLAIM", "%1: reclaiming expired lease for prefix %2/%3",
+ "ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED", "failed to reclaim the lease %1: %2",
+ "ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES", "all expired leases have been reclaimed",
+ "ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE", "begin deletion of reclaimed leases expired more than %1 seconds ago",
+ "ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE", "successfully deleted %1 expired-reclaimed leases",
+ "ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED", "deletion of expired-reclaimed leases failed: %1",
+ "ALLOC_ENGINE_V6_RENEW_HR", "allocating leases reserved for the client %1 as a result of Renew",
+ "ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED", "%1: checking if existing client's leases are reserved for another client",
+ "ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED", "dynamically allocating leases for the renewing client %1",
+ "ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA", "%1: reusing expired lease, updated lease information: %2",
+ "ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE", "address %1 was revoked from client %2 as it is reserved for client %3",
+ "ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE", "prefix %1/%2 was revoked from client %3 as it is reserved for client %4",
+ "ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE", "address %1 was revoked from client %2 as it is reserved for %3 other clients",
+ "ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE", "prefix %1/%2 was revoked from client %3 as it is reserved for %4 other clients",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/dhcpsrv/alloc_engine_messages.h b/src/lib/dhcpsrv/alloc_engine_messages.h
new file mode 100644
index 0000000..e9cd896
--- /dev/null
+++ b/src/lib/dhcpsrv/alloc_engine_messages.h
@@ -0,0 +1,94 @@
+// File created from ../../../src/lib/dhcpsrv/alloc_engine_messages.mes
+
+#ifndef ALLOC_ENGINE_MESSAGES_H
+#define ALLOC_ENGINE_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS;
+extern const isc::log::MessageID ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS6;
+extern const isc::log::MessageID ALLOC_ENGINE_LEASE_RECLAIMED;
+extern const isc::log::MessageID ALLOC_ENGINE_REMOVAL_NCR_FAILED;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_ERROR;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_NO_POOLS;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_SHARED_NETWORK;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_ALLOC_FAIL_SUBNET;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_DECLINED_RECOVERED;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_DISCOVER_HR;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_FAILED;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_START;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASE_RECLAIM;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_OFFER_NEW_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_INVALID;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_IN_USE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_REMOVE_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REQUEST_USE_HR;
+extern const isc::log::MessageID ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_ERROR;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_NO_POOLS;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_SHARED_NETWORK;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_FAIL_SUBNET;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_LEASES_HR;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_NO_V6_HR;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_ALLOC_UNRESERVED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_DECLINED_RECOVERED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_ERROR;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_LEASE_DATA;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_EXTEND_NEW_LEASE_DATA;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_HINT_RESERVED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_HR_ADDR_GRANTED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_HR_PREFIX_GRANTED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_FAILED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_START;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASE_RECLAIM;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_HR;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE;
+extern const isc::log::MessageID ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // ALLOC_ENGINE_MESSAGES_H
diff --git a/src/lib/dhcpsrv/alloc_engine_messages.mes b/src/lib/dhcpsrv/alloc_engine_messages.mes
new file mode 100644
index 0000000..f6a50a2
--- /dev/null
+++ b/src/lib/dhcpsrv/alloc_engine_messages.mes
@@ -0,0 +1,627 @@
+# Copyright (C) 2015-2023 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/.
+
+$NAMESPACE isc::dhcp
+
+% ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS ignoring globally reserved address %1, it falls outside %2
+This debug message is issued when the allocation engine determines that
+the globally reserved address falls outside the selected subnet or
+shared-network. The server should ignore the reserved address and
+attempt a dynamic allocation.
+
+% ALLOC_ENGINE_IGNORING_UNSUITABLE_GLOBAL_ADDRESS6 ignoring globally reserved address %1, it falls outside %2
+This debug message is issued when the allocation engine determines that
+the globally reserved address falls outside the selected subnet or
+shared-network. The server should ignore the reserved address and
+attempt a dynamic allocation.
+
+% ALLOC_ENGINE_LEASE_RECLAIMED successfully reclaimed lease %1
+This debug message is logged when the allocation engine successfully
+reclaims a lease. The lease is now available for assignment.
+
+% ALLOC_ENGINE_REMOVAL_NCR_FAILED sending removal name change request failed for lease %1: %2
+This error message is logged when sending a removal NameChangeRequest
+to DHCP DDNS failed. This NameChangeRequest is usually generated when
+the lease reclamation routine acts upon expired leases. If a lease being
+reclaimed has a corresponding DNS entry it needs to be removed.
+This message indicates that removal of the DNS entry has failed.
+Nevertheless the lease will be reclaimed.
+
+% ALLOC_ENGINE_V4_ALLOC_ERROR %1: error during attempt to allocate an IPv4 address: %2
+An error occurred during an attempt to allocate an IPv4 address, the
+reason for the failure being contained in the message. The server will
+return a message to the client refusing a lease. The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V4_ALLOC_FAIL %1: failed to allocate an IPv4 address after %2 attempt(s)
+This is an old warning message issued when the allocation engine fails to allocate a
+lease for a client. This message includes a number of lease allocation attempts
+that the engine made before giving up. If the number of attempts is 0 because the
+engine was unable to use any of the address pools for the particular client, this
+message is not logged. Even though, several more detailed logs precede this message,
+it was left for backward compatibility.
+
+This message may indicate that your address pool is too small for the
+number of clients you are trying to service and should be expanded.
+Alternatively, if the you know that the number of concurrently active
+clients is less than the addresses you have available, you may want to
+consider reducing the lease lifetime. This way, addresses allocated
+to clients that are no longer active on the network will become available
+sooner.
+
+% ALLOC_ENGINE_V4_ALLOC_FAIL_CLASSES %1: Failed to allocate an IPv4 address for client with classes: %2
+This warning message is printed when Kea failed to allocate an address
+and the client's packet belongs to one or more classes. There may be several
+reasons why a lease was not assigned. One of them may be a case when all
+pools require packet to belong to certain classes and the incoming packet
+didn't belong to any of them. Another case where this information may be
+useful is to point out that the pool reserved to a given class has ran
+out of addresses. When you see this message, you may consider checking your
+pool size and your classification definitions.
+
+% ALLOC_ENGINE_V4_ALLOC_FAIL_NO_POOLS %1: no pools were available for the address allocation
+This warning message is issued when the allocation engine fails to
+allocate a lease because it could not use any configured pools for the
+particular client. It is also possible that all of the subnets from
+which the allocation engine attempted to assign an address lack address
+pools. In this case, it should be considered misconfiguration if an
+operator expects that some clients should be assigned dynamic addresses.
+A subnet may lack any pools only when all clients should be assigned
+reserved IP addresses.
+
+Suppose the subnets connected to a shared network or a single subnet to
+which the client belongs have pools configured. In that case, this
+message is an indication that none of the pools could be used for the
+client because the client does not belong to appropriate client classes.
+
+% ALLOC_ENGINE_V4_ALLOC_FAIL_SHARED_NETWORK %1: failed to allocate an IPv4 address in the shared network %2: %3 subnets have no available addresses, %4 subnets have no matching pools
+This warning message is issued when the allocation engine fails to allocate
+a lease for a client connected to a shared network. The shared network should
+contain at least one subnet, but typically it aggregates multiple subnets.
+This log message indicates that the allocation engine could not find and
+allocate any suitable lease in any of the subnets within the shared network.
+
+The first argument includes the client identification information. The
+second argument specifies the shared network name. The remaining two
+arguments provide additional information useful for debugging why the
+allocation engine could not assign a lease. The allocation engine tries
+to allocate addresses from different subnets in the shared network, and
+it may fail for some subnets because there are no leases available in
+those subnets or the free leases are reserved to other clients. The
+number of such subnets is specified in the third argument. For other
+subnets the allocation may fail because their pools may not be available
+to the particular client. These pools are guarded by client classes that
+the client does not belong to. The fourth argument specifies the number
+of such subnets. By looking at the values in the third and fourth argument,
+an operator can identify the situations when there are no addresses left
+in some of the pools. He or she can also identify a client classification
+misconfigurations causing some clients to be refused the service.
+
+% ALLOC_ENGINE_V4_ALLOC_FAIL_SUBNET %1: failed to allocate an IPv4 lease in the subnet %2, subnet-id %3, shared network %4
+This warning message is issued when the allocation engine fails to allocate
+a lease for a client connected to a subnet. The first argument includes the
+client identification information. The second and third arguments identify
+the subnet. The fourth argument specifies the shared network, if the subnet
+belongs to a shared network.
+
+There are many reasons for failing lease allocations. One of them may be the
+pools exhaustion or existing reservations for the free leases. However, in
+some cases, the allocation engine may fail to find a suitable pool for the
+client when the pools are only available to certain client classes, but the
+requesting client does not belong to them. Further log messages provide more
+information to distinguish between these different cases.
+
+% ALLOC_ENGINE_V4_DECLINED_RECOVERED IPv4 address %1 was recovered after %2 seconds of probation-period
+This informational message indicates that the specified address was reported
+as duplicate (client sent DECLINE) and the server marked this address as
+unavailable for a period of time. This time now has elapsed and the address
+has been returned to the available pool. This step concludes the decline recovery
+process.
+
+% ALLOC_ENGINE_V4_DISCOVER_ADDRESS_CONFLICT %1: conflicting reservation for address %2 with existing lease %3
+This warning message is issued when the DHCP server finds that the
+address reserved for the client can't be offered because this address
+is currently allocated to another client. The server will try to allocate
+a different address to the client to use until the conflict is resolved.
+The first argument includes the client identification information.
+
+% ALLOC_ENGINE_V4_DISCOVER_HR client %1 sending DHCPDISCOVER has reservation for the address %2
+This message is issued when the allocation engine determines that the
+client sending the DHCPDISCOVER has a reservation for the specified
+address. The allocation engine will try to offer this address to
+the client.
+
+% ALLOC_ENGINE_V4_LEASES_RECLAMATION_COMPLETE reclaimed %1 leases in %2
+This debug message is logged when the allocation engine completes
+reclamation of a set of expired leases. The maximum number of leases
+to be reclaimed in a single pass of the lease reclamation routine
+is configurable using 'max-reclaim-leases' parameter. However,
+the number of reclaimed leases may also be limited by the timeout
+value, configured with 'max-reclaim-time'. The message includes the
+number of reclaimed leases and the total time.
+
+% ALLOC_ENGINE_V4_LEASES_RECLAMATION_FAILED reclamation of expired leases failed: %1
+This error message is issued when the reclamation of the expired leases failed.
+The error message is displayed.
+
+% ALLOC_ENGINE_V4_LEASES_RECLAMATION_SLOW expired leases still exist after %1 reclamations
+This warning message is issued when the server has been unable to
+reclaim all expired leases in a specified number of consecutive
+attempts. This indicates that the value of "reclaim-timer-wait-time"
+may be too high. However, if this is just a short burst of leases'
+expirations the value does not have to be modified and the server
+should deal with this in subsequent reclamation attempts. If this
+is a result of a permanent increase of the server load, the value
+of "reclaim-timer-wait-time" should be decreased, or the
+values of "max-reclaim-leases" and "max-reclaim-time" should be
+increased to allow processing more leases in a single cycle.
+Alternatively, these values may be set to 0 to remove the
+limitations on the number of leases and duration. However, this
+may result in longer periods of server's unresponsiveness to
+DHCP packets, while it processes the expired leases.
+
+% ALLOC_ENGINE_V4_LEASES_RECLAMATION_START starting reclamation of expired leases (limit = %1 leases or %2 milliseconds)
+This debug message is issued when the allocation engine starts the
+reclamation of the expired leases. The maximum number of leases to
+be reclaimed and the timeout is included in the message. If any of
+these values is 0, it means "unlimited".
+
+% ALLOC_ENGINE_V4_LEASES_RECLAMATION_TIMEOUT timeout of %1 ms reached while reclaiming IPv4 leases
+This debug message is issued when the allocation engine hits the
+timeout for performing reclamation of the expired leases. The
+reclamation will now be interrupted and all leases which haven't
+been reclaimed, because of the timeout, will be reclaimed when the
+next scheduled reclamation is started. The argument is the timeout
+value expressed in milliseconds.
+
+% ALLOC_ENGINE_V4_LEASE_RECLAIM %1: reclaiming expired lease for address %2
+This debug message is issued when the server begins reclamation of the
+expired DHCPv4 lease. The first argument specifies the client identification
+information. The second argument holds the leased IPv4 address.
+
+% ALLOC_ENGINE_V4_LEASE_RECLAMATION_FAILED failed to reclaim the lease %1: %2
+This error message is logged when the allocation engine fails to
+reclaim an expired lease. The reason for the failure is included in the
+message. The error may be triggered in the lease expiration hook or
+while performing the operation on the lease database.
+
+% ALLOC_ENGINE_V4_NO_MORE_EXPIRED_LEASES all expired leases have been reclaimed
+This debug message is issued when the server reclaims all expired
+DHCPv4 leases in the database.
+
+% ALLOC_ENGINE_V4_OFFER_EXISTING_LEASE allocation engine will try to offer existing lease to the client %1
+This message is issued when the allocation engine determines that
+the client has a lease in the lease database, it doesn't have
+reservation for any other lease, and the leased address is not
+reserved for any other client. The allocation engine will try
+to offer the same lease to the client.
+
+% ALLOC_ENGINE_V4_OFFER_NEW_LEASE allocation engine will try to offer new lease to the client %1
+This message is issued when the allocation engine will try to
+offer a new lease to the client. This is the case when the
+client doesn't have any existing lease, it has no reservation
+or the existing or reserved address is leased to another client.
+Also, the client didn't specify a hint, or the address in
+the hint is in use.
+
+% ALLOC_ENGINE_V4_OFFER_REQUESTED_LEASE allocation engine will try to offer requested lease %1 to the client %2
+This message is issued when the allocation engine will try to
+offer the lease specified in the hint. This situation may occur
+when: (a) client doesn't have any reservations, (b) client has
+reservation but the reserved address is leased to another client.
+
+% ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE begin deletion of reclaimed leases expired more than %1 seconds ago
+This debug message is issued when the allocation engine begins
+deletion of the reclaimed leases which have expired more than
+a specified number of seconds ago. This operation is triggered
+periodically according to the "flush-reclaimed-timer-wait-time"
+parameter. The "hold-reclaimed-time" parameter defines a number
+of seconds for which the leases are stored before they are
+removed.
+
+% ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_COMPLETE successfully deleted %1 expired-reclaimed leases
+This debug message is issued when the server successfully deletes
+"expired-reclaimed" leases from the lease database. The number of
+deleted leases is included in the log message.
+
+% ALLOC_ENGINE_V4_RECLAIMED_LEASES_DELETE_FAILED deletion of expired-reclaimed leases failed: %1
+This error message is issued when the deletion of "expired-reclaimed"
+leases from the database failed. The error message is appended to
+the log message.
+
+% ALLOC_ENGINE_V4_REQUEST_ADDRESS_RESERVED %1: requested address %2 is reserved
+This message is issued when the allocation engine refused to
+allocate address requested by the client because this
+address is reserved for another client. The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V4_REQUEST_ALLOC_REQUESTED %1: trying to allocate requested address %2
+This message is issued when the allocation engine is trying
+to allocate (or reuse an expired) address which has been
+requested by the client. The first argument includes the
+client identification information.
+
+% ALLOC_ENGINE_V4_REQUEST_EXTEND_LEASE %1: extending lifetime of the lease for address %2
+This message is issued when the allocation engine determines
+that the client already has a lease whose lifetime can be
+extended, and which can be returned to the client.
+The first argument includes the client identification information.
+
+% ALLOC_ENGINE_V4_REQUEST_INVALID client %1 having a reservation for address %2 is requesting invalid address %3
+This message is logged when the client, having a reservation for
+one address, is requesting a different address. The client is
+only allowed to do this when the reserved address is in use by
+another client. However, the allocation engine has
+determined that the reserved address is available and the
+client should request the reserved address.
+
+% ALLOC_ENGINE_V4_REQUEST_IN_USE %1: requested address %2 is in use
+This message is issued when the client is requesting or has a
+reservation for an address which is in use. The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V4_REQUEST_OUT_OF_POOL client %1, which doesn't have a reservation, requested address %2 out of the dynamic pool
+This message is issued when the client has requested allocation
+of the address which doesn't belong to any address pool from
+which addresses are dynamically allocated. The client also
+doesn't have reservation for this address. This address
+could only be allocated if the client had reservation for it.
+
+% ALLOC_ENGINE_V4_REQUEST_PICK_ADDRESS client %1 hasn't specified an address - picking available address from the pool
+This message is logged when the client hasn't specified any
+preferred address (the client should always do it, but Kea
+tries to be forgiving). The allocation engine will try to pick an available
+address from the dynamic pool and allocate it to the client.
+
+% ALLOC_ENGINE_V4_REQUEST_REMOVE_LEASE %1: removing previous client's lease %2
+This message is logged when the allocation engine removes previous
+lease for the client because the client has been allocated new one.
+
+% ALLOC_ENGINE_V4_REQUEST_USE_HR client %1 hasn't requested specific address, using reserved address %2
+This message is issued when the client is not requesting any specific
+address but the allocation engine has determined that there is a
+reservation for this client. The allocation engine will try to
+allocate the reserved address.
+
+% ALLOC_ENGINE_V4_REUSE_EXPIRED_LEASE_DATA %1: reusing expired lease, updated lease information: %2
+This message is logged when the allocation engine is reusing
+an existing lease. The details of the updated lease are
+printed. The first argument includes the client identification
+information.
+
+% ALLOC_ENGINE_V6_ALLOC_ERROR %1: error during attempt to allocate an IPv6 address: %2
+An error occurred during an attempt to allocate an IPv6 address, the
+reason for the failure being contained in the message. The server will
+return a message to the client refusing a lease. The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V6_ALLOC_FAIL %1: failed to allocate an IPv6 lease after %2 attempt(s)
+This is an old warning message issued when the allocation engine fails to allocate a
+lease for a client. This message includes a number of lease allocation attempts
+that the engine made before giving up. If the number of attempts is 0 because the
+engine was unable to use any of the pools for the particular client, this message
+is not logged. Even though, several more detailed logs precede this message, it was
+left for backward compatibility.
+
+This message may indicate that your pool is too small for the number of clients
+you are trying to service and should be expanded. Alternatively, if the you know
+that the number of concurrently active clients is less than the leases you have
+available, you may want to consider reducing the lease lifetime. This way, leases
+allocated to clients that are no longer active on the network will become available
+sooner.
+
+% ALLOC_ENGINE_V6_ALLOC_FAIL_CLASSES %1: Failed to allocate an IPv6 address for client with classes: %2
+This warning message is printed when Kea failed to allocate an address
+and the client's packet belongs to one or more classes. There may be several
+reasons why a lease was not assigned. One of them may be a case when all
+pools require packet to belong to certain classes and the incoming packet
+didn't belong to any of them. Another case where this information may be
+useful is to point out that the pool reserved to a given class has ran
+out of addresses. When you see this message, you may consider checking your
+pool size and your classification definitions.
+
+% ALLOC_ENGINE_V6_ALLOC_FAIL_NO_POOLS %1: no pools were available for the lease allocation
+This warning message is issued when the allocation engine fails to
+allocate a lease because it could not use any configured pools for the
+particular client. It is also possible that all of the subnets from
+which the allocation engine attempted to assign an address lack address
+pools. In this case, it should be considered misconfiguration if an
+operator expects that some clients should be assigned dynamic addresses.
+A subnet may lack any pools only when all clients should be assigned
+reserved leases.
+
+Suppose the subnets connected to a shared network or a single subnet to
+which the client belongs have pools configured. In that case, this
+message is an indication that none of the pools could be used for the
+client because the client does not belong to appropriate client classes.
+
+% ALLOC_ENGINE_V6_ALLOC_FAIL_SHARED_NETWORK %1: failed to allocate a lease in the shared network %2: %3 subnets have no available leases, %4 subnets have no matching pools
+This warning message is issued when the allocation engine fails to allocate
+a lease for a client connected to a shared network. The shared network should
+contain at least one subnet, but typically it aggregates multiple subnets.
+This log message indicates that the allocation engine could not find and
+allocate any suitable lease in any of the subnets within the shared network.
+
+The first argument includes the client identification information. The
+second argument specifies the shared network name. The remaining two
+arguments provide additional information useful for debugging why the
+allocation engine could not assign a lease. The allocation engine tries
+to allocate leases from different subnets in the shared network, and
+it may fail for some subnets because there are no leases available in
+those subnets or the free leases are reserved to other clients. The
+number of such subnets is specified in the third argument. For other
+subnets the allocation may fail because their pools may not be available
+to the particular client. These pools are guarded by client classes that
+the client does not belong to. The fourth argument specifies the number
+of such subnets. By looking at the values in the third and fourth argument,
+an operator can identify the situations when there are no leases left
+in some of the pools. He or she can also identify client classification
+misconfigurations causing some clients to be refused the service.
+
+% ALLOC_ENGINE_V6_ALLOC_FAIL_SUBNET %1: failed to allocate an IPv6 lease in the subnet %2, subnet-id %3, shared network %4
+This warning message is issued when the allocation engine fails to allocate
+a lease for a client connected to a subnet. The first argument includes the
+client identification information. The second and third arguments identify
+the subnet. The fourth argument specifies the shared network, if the subnet
+belongs to a shared network.
+
+There are many reasons for failing lease allocations. One of them may be the
+pools exhaustion or existing reservations for the free leases. However, in
+some cases, the allocation engine may fail to find a suitable pool for the
+client when the pools are only available to certain client classes, but the
+requesting client does not belong to them. Further log messages provide more
+information to distinguish between these different cases.
+
+% ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS %1: lease type %2 for reserved address/prefix %3 already exists
+This debug message is issued when the allocation engine determines that
+the lease for the IPv6 address or prefix has already been allocated
+for the client and the client can continue using it. The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V6_ALLOC_LEASES_HR leases and static reservations found for client %1
+This message is logged when the allocation engine is in the process of
+allocating leases for the client, it found existing leases and static
+reservations for the client. The allocation engine will verify if
+existing leases match reservations. Those leases that are reserved for
+other clients and those that are not reserved for the client will
+be removed. All leases matching the reservations will be renewed
+and returned.
+
+% ALLOC_ENGINE_V6_ALLOC_LEASES_NO_HR no reservations found but leases exist for client %1
+This message is logged when the allocation engine is in the process if
+allocating leases for the client, there are no static reservations,
+but lease(s) exist for the client. The allocation engine will remove
+leases which are reserved for other clients, and return all
+remaining leases to the client.
+
+% ALLOC_ENGINE_V6_ALLOC_NO_LEASES_HR no leases found but reservations exist for client %1
+This message is logged when the allocation engine is in the process of
+allocating leases for the client. It hasn't found any existing leases
+for this client, but the client appears to have static reservations.
+The allocation engine will try to allocate the reserved resources for
+the client.
+
+% ALLOC_ENGINE_V6_ALLOC_NO_V6_HR %1: unable to allocate reserved leases - no IPv6 reservations
+This message is logged when the allocation engine determines that the
+client has no IPv6 reservations and thus the allocation engine will have
+to try to allocate allocating leases from the dynamic pool or stop
+the allocation process if none can be allocated. The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V6_ALLOC_UNRESERVED no static reservations available - trying to dynamically allocate leases for client %1
+This debug message is issued when the allocation engine will attempt
+to allocate leases from the dynamic pools. This may be due to one of
+(a) there are no reservations for this client, (b) there are
+reservations for the client but they are not usable because the addresses
+are in use by another client or (c) we had a reserved lease but that
+has now been allocated to another client.
+
+% ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME %1: using a calculated preferred-lifetime of %2
+This debug message indicates that the preferred-lifetime being returned
+to the client is defaulting to 62.5% of the valid-lifetime. This may
+occur if either the preferred-lifetime has not been explicitly configured,
+or the configured value is larger than the valid-lifetime. The arguments
+detail the client and the preferred-lifetime that will be used.
+
+% ALLOC_ENGINE_V6_DECLINED_RECOVERED IPv6 address %1 was recovered after %2 seconds of probation-period
+This informational message indicates that the specified address was reported
+as duplicate (client sent DECLINE) and the server marked this address as
+unavailable for a period of time. This time now has elapsed and the address
+has been returned to the available pool. This step concludes the decline recovery
+process.
+
+% ALLOC_ENGINE_V6_EXPIRED_HINT_RESERVED %1: expired lease for the client's hint %2 is reserved for another client
+This message is logged when the allocation engine finds that the
+expired lease for the client's hint can't be reused because it
+is reserved for another client. The first argument includes the
+client identification information.
+
+% ALLOC_ENGINE_V6_EXTEND_ALLOC_UNRESERVED allocate new (unreserved) leases for the renewing client %1
+This debug message is issued when the allocation engine is trying to
+allocate new leases for the renewing client because it was unable to
+renew any of the existing client's leases, e.g. because leases are
+reserved for another client or for any other reason.
+
+% ALLOC_ENGINE_V6_EXTEND_ERROR %1: allocation engine experienced error with attempting to extend lease lifetime: %2
+This error message indicates that an error was experienced during Renew
+or Rebind processing. Additional explanation is provided with this
+message. Depending on its nature, manual intervention may be required to
+continue processing messages from this particular client; other clients
+will be unaffected. The first argument includes the client identification
+information.
+
+% ALLOC_ENGINE_V6_EXTEND_LEASE %1: extending lifetime of the lease type %2, address %3
+This debug message is issued when the allocation engine is trying
+to extend lifetime of the lease. The first argument includes the
+client identification information.
+
+% ALLOC_ENGINE_V6_EXTEND_LEASE_DATA %1: detailed information about the lease being extended: %2
+This debug message prints detailed information about the lease which
+lifetime is being extended (renew or rebind). The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V6_EXTEND_NEW_LEASE_DATA %1: new lease information for the lease being extended: %2
+This debug message prints updated information about the lease to be
+extended. If the lease update is successful, the information printed
+by this message will be stored in the database. The first argument
+includes the client identification information.
+
+% ALLOC_ENGINE_V6_HINT_RESERVED %1: lease for the client's hint %2 is reserved for another client
+This message is logged when the allocation engine cannot allocate
+the lease using the client's hint because the lease for this hint
+is reserved for another client. The first argument includes the
+client identification information.
+
+% ALLOC_ENGINE_V6_HR_ADDR_GRANTED reserved address %1 was assigned to client %2
+This informational message signals that the specified client was assigned the address
+reserved for it.
+
+% ALLOC_ENGINE_V6_HR_PREFIX_GRANTED reserved prefix %1/%2 was assigned to client %3
+This informational message signals that the specified client was assigned the prefix
+reserved for it.
+
+% ALLOC_ENGINE_V6_LEASES_RECLAMATION_COMPLETE reclaimed %1 leases in %2
+This debug message is logged when the allocation engine completes
+reclamation of a set of expired leases. The maximum number of leases
+to be reclaimed in a single pass of the lease reclamation routine
+is configurable using 'max-reclaim-leases' parameter. However,
+the number of reclaimed leases may also be limited by the timeout
+value, configured with 'max-reclaim-time'. The message includes the
+number of reclaimed leases and the total time.
+
+% ALLOC_ENGINE_V6_LEASES_RECLAMATION_FAILED reclamation of expired leases failed: %1
+This error message is issued when the reclamation of the expired leases failed.
+The error message is displayed.
+
+% ALLOC_ENGINE_V6_LEASES_RECLAMATION_SLOW expired leases still exist after %1 reclamations
+This warning message is issued when the server has been unable to
+reclaim all expired leases in a specified number of consecutive
+attempts. This indicates that the value of "reclaim-timer-wait-time"
+may be too high. However, if this is just a short burst of leases'
+expirations the value does not have to be modified and the server
+should deal with this in subsequent reclamation attempts. If this
+is a result of a permanent increase of the server load, the value
+of "reclaim-timer-wait-time" should be decreased, or the
+values of "max-reclaim-leases" and "max-reclaim-time" should be
+increased to allow processing more leases in a single cycle.
+Alternatively, these values may be set to 0 to remove the
+limitations on the number of leases and duration. However, this
+may result in longer periods of server's unresponsiveness to
+DHCP packets, while it processes the expired leases.
+
+% ALLOC_ENGINE_V6_LEASES_RECLAMATION_START starting reclamation of expired leases (limit = %1 leases or %2 milliseconds)
+This debug message is issued when the allocation engine starts the
+reclamation of the expired leases. The maximum number of leases to
+be reclaimed and the timeout is included in the message. If any of
+these values is 0, it means "unlimited".
+
+% ALLOC_ENGINE_V6_LEASES_RECLAMATION_TIMEOUT timeout of %1 ms reached while reclaiming IPv6 leases
+This debug message is issued when the allocation engine hits the
+timeout for performing reclamation of the expired leases. The
+reclamation will now be interrupted and all leases which haven't
+been reclaimed, because of the timeout, will be reclaimed when the
+next scheduled reclamation is started. The argument is the timeout
+value expressed in milliseconds.
+
+% ALLOC_ENGINE_V6_LEASE_RECLAIM %1: reclaiming expired lease for prefix %2/%3
+This debug message is issued when the server begins reclamation of the
+expired DHCPv6 lease. The reclaimed lease may either be an address lease
+or delegated prefix. The first argument provides the client identification
+information. The other arguments specify the prefix and the prefix length
+for the lease. The prefix length for address lease is equal to 128.
+
+% ALLOC_ENGINE_V6_LEASE_RECLAMATION_FAILED failed to reclaim the lease %1: %2
+This error message is logged when the allocation engine fails to
+reclaim an expired lease. The reason for the failure is included in the
+message. The error may be triggered in the lease expiration hook or
+while performing the operation on the lease database.
+
+% ALLOC_ENGINE_V6_NO_MORE_EXPIRED_LEASES all expired leases have been reclaimed
+This debug message is issued when the server reclaims all expired
+DHCPv6 leases in the database.
+
+% ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE begin deletion of reclaimed leases expired more than %1 seconds ago
+This debug message is issued when the allocation engine begins
+deletion of the reclaimed leases which have expired more than
+a specified number of seconds ago. This operation is triggered
+periodically according to the "flush-reclaimed-timer-wait-time"
+parameter. The "hold-reclaimed-time" parameter defines a number
+of seconds for which the leases are stored before they are
+removed.
+
+% ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_COMPLETE successfully deleted %1 expired-reclaimed leases
+This debug message is issued when the server successfully deletes
+"expired-reclaimed" leases from the lease database. The number of
+deleted leases is included in the log message.
+
+% ALLOC_ENGINE_V6_RECLAIMED_LEASES_DELETE_FAILED deletion of expired-reclaimed leases failed: %1
+This error message is issued when the deletion of "expired-reclaimed"
+leases from the database failed. The error message is appended to
+the log message.
+
+% ALLOC_ENGINE_V6_RENEW_HR allocating leases reserved for the client %1 as a result of Renew
+This debug message is issued when the allocation engine tries to
+allocate reserved leases for the client sending a Renew message.
+The server will also remove any leases that the client is trying
+to renew that are not reserved for the client.
+
+% ALLOC_ENGINE_V6_RENEW_REMOVE_RESERVED %1: checking if existing client's leases are reserved for another client
+This message is logged when the allocation engine finds leases for
+the client and will check if these leases are reserved for another
+client. If they are, they will not be renewed for the client
+requesting their renewal. The first argument includes the client
+identification information.
+
+% ALLOC_ENGINE_V6_RENEW_REMOVE_UNRESERVED dynamically allocating leases for the renewing client %1
+This debug message is issued as the allocation engine is trying
+to dynamically allocate new leases for the renewing client. This
+is the case when the server couldn't renew any of the existing
+client's leases, e.g. because leased resources are reserved for
+another client.
+
+% ALLOC_ENGINE_V6_REUSE_EXPIRED_LEASE_DATA %1: reusing expired lease, updated lease information: %2
+This message is logged when the allocation engine is reusing
+an existing lease. The details of the updated lease are
+printed. The first argument includes the client identification
+information.
+
+% ALLOC_ENGINE_V6_REVOKED_ADDR_LEASE address %1 was revoked from client %2 as it is reserved for client %3
+This informational message is an indication that the specified IPv6
+address was used by client A but it is now reserved for client B. Client
+A has been told to stop using it so that it can be leased to client B.
+This is a normal occurrence during conflict resolution, which can occur
+in cases such as the system administrator adding a reservation for an
+address that is currently in use by another client. The server will fully
+recover from this situation, but clients will change their addresses.
+
+% ALLOC_ENGINE_V6_REVOKED_PREFIX_LEASE prefix %1/%2 was revoked from client %3 as it is reserved for client %4
+This informational message is an indication that the specified IPv6
+prefix was used by client A but it is now reserved for client B. Client
+A has been told to stop using it so that it can be leased to client B.
+This is a normal occurrence during conflict resolution, which can occur
+in cases such as the system administrator adding a reservation for an
+address that is currently in use by another client. The server will fully
+recover from this situation, but clients will change their prefixes.
+
+% ALLOC_ENGINE_V6_REVOKED_SHARED_ADDR_LEASE address %1 was revoked from client %2 as it is reserved for %3 other clients
+This informational message is an indication that the specified IPv6
+address was used by client A but it is now reserved for multiple other
+clients. Client A has been told to stop using it so that it can be
+leased to one of the clients having the reservation for it. This is a
+normal occurrence during conflict resolution, which can occur in cases
+such as the system administrator adding reservations for an address
+that is currently in use by another client. The server will fully
+recover from this situation, but clients will change their addresses.
+
+% ALLOC_ENGINE_V6_REVOKED_SHARED_PREFIX_LEASE prefix %1/%2 was revoked from client %3 as it is reserved for %4 other clients
+This informational message is an indication that the specified IPv6
+prefix was used by client A but it is now reserved for multiple other
+clients. Client A has been told to stop using it so that it can be
+leased to one of the clients having the reservation for it. This is a
+normal occurrence during conflict resolution, which can occur in cases
+such as the system administrator adding reservations for an address
+that is currently in use by another client. The server will fully
+recover from this situation, but clients will change their prefixes.
diff --git a/src/lib/dhcpsrv/allocation_state.cc b/src/lib/dhcpsrv/allocation_state.cc
new file mode 100644
index 0000000..e187c4a
--- /dev/null
+++ b/src/lib/dhcpsrv/allocation_state.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2022-2023 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 <dhcpsrv/allocation_state.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+SubnetAllocationState::SubnetAllocationState()
+ : AllocationState(), mutex_(new std::mutex) {
+ last_allocated_time_ = boost::posix_time::neg_infin;
+}
+
+boost::posix_time::ptime
+SubnetAllocationState::getLastAllocatedTime() const {
+ MultiThreadingLock lock(*mutex_);
+ return (last_allocated_time_);
+}
+
+void
+SubnetAllocationState::setCurrentAllocatedTimeInternal() {
+ last_allocated_time_ = boost::posix_time::microsec_clock::universal_time();
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/allocation_state.h b/src/lib/dhcpsrv/allocation_state.h
new file mode 100644
index 0000000..9c6bd0f
--- /dev/null
+++ b/src/lib/dhcpsrv/allocation_state.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2022-2023 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 ALLOCATION_STATE_H
+#define ALLOCATION_STATE_H
+
+#include <dhcpsrv/lease.h>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <mutex>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base class for representing allocation state in pools and subnets.
+///
+/// Allocators are used in Kea to implement different lease selection
+/// algorithms. They are stateful (i.e., they remember various information
+/// about the previous allocations) to work efficiently. For example, an
+/// iterative allocator must remember the last allocated address to pick
+/// the consecutive address when the new allocation request is issued.
+/// Allocation states differ between the allocators. State classes used
+/// by different allocators derive from this class.
+///
+/// The allocation states can be associated with pools and/or subnets.
+/// Both pool-specific and subnet-specific states derive from this class.
+class AllocationState {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~AllocationState() = default;
+};
+
+/// @brief Type of the pointer to the @c AllocationState.
+typedef boost::shared_ptr<AllocationState> AllocationStatePtr;
+
+/// @brief Common base class for subnet-specific allocation states.
+///
+/// All subnet-specific allocation states should derive from this class.
+/// It provides a mutex for thread-safe access to the class members.
+/// It maintains last allocation times for various lease types. These
+/// times are used by the shared networks to find the "preferred" subnet
+/// (i.e., a subnet from which the latest lease was assigned).
+class SubnetAllocationState : public AllocationState {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the mutex.
+ SubnetAllocationState();
+
+ /// @brief Returns last allocation time for the specified lease type.
+ ///
+ /// @return Last allocation time for the lease type or
+ /// @c boost::posix_time::neg_infin when no leases have been allocated
+ /// from this subnet yet.
+ boost::posix_time::ptime
+ getLastAllocatedTime() const;
+
+protected:
+
+ /// @brief Sets the last allocation time to current.
+ ///
+ /// This function should be called by derived classes. It should be
+ /// called in the thread-safe context.
+ void setCurrentAllocatedTimeInternal();
+
+ /// @brief Mutex used for thread-safe access to the state members.
+ boost::scoped_ptr<std::mutex> mutex_;
+
+ /// @brief Timestamp indicating when a lease has been last allocated
+ /// from the subnet.
+ boost::posix_time::ptime last_allocated_time_;
+};
+
+typedef boost::shared_ptr<SubnetAllocationState> SubnetAllocationStatePtr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // ALLOCATION_STATE_H
diff --git a/src/lib/dhcpsrv/allocator.cc b/src/lib/dhcpsrv/allocator.cc
new file mode 100644
index 0000000..5925689
--- /dev/null
+++ b/src/lib/dhcpsrv/allocator.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2022-2023 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 <dhcpsrv/allocator.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+Allocator::Allocator(Lease::Type type, const WeakSubnetPtr& subnet)
+ : inited_(false),
+ pool_type_(type),
+ subnet_id_(0),
+ subnet_(subnet) {
+ // Remember subnet ID in a separate variable. It may be needed in
+ // the destructor where the subnet weak pointer is unavailable.
+ subnet_id_ = subnet_.lock()->getID();
+}
+
+Allocator::~Allocator() {
+ if (!LeaseMgrFactory::haveInstance()) {
+ // If there is no lease manager instance, the callbacks are
+ // gone already anyway.
+ return;
+ }
+ // Remove the callbacks.
+ auto& lease_mgr = LeaseMgrFactory::instance();
+ lease_mgr.unregisterCallbacks(subnet_id_, pool_type_);
+}
+
+bool
+Allocator::isValidPrefixPool(Allocator::PrefixLenMatchType prefix_length_match,
+ PoolPtr pool, uint8_t hint_prefix_length) {
+ auto pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ if (!pool6) {
+ return (false);
+ }
+
+ if (!hint_prefix_length) {
+ return (true);
+ }
+
+ if (prefix_length_match == Allocator::PREFIX_LEN_EQUAL &&
+ pool6->getLength() != hint_prefix_length) {
+ return (false);
+ }
+
+ if (prefix_length_match == Allocator::PREFIX_LEN_LOWER &&
+ pool6->getLength() >= hint_prefix_length) {
+ return (false);
+ }
+
+ if (prefix_length_match == Allocator::PREFIX_LEN_HIGHER &&
+ pool6->getLength() <= hint_prefix_length) {
+ return (false);
+ }
+
+ return (true);
+}
+
+void
+Allocator::initAfterConfigure() {
+ if (inited_) {
+ return;
+ }
+ auto subnet = subnet_.lock();
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_ALLOCATOR)
+ .arg(getType())
+ .arg(Lease::typeToText(pool_type_))
+ .arg(subnet->toText());
+ initAfterConfigureInternal();
+ inited_ = true;
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/allocator.h b/src/lib/dhcpsrv/allocator.h
new file mode 100644
index 0000000..66f2302
--- /dev/null
+++ b/src/lib/dhcpsrv/allocator.h
@@ -0,0 +1,244 @@
+// Copyright (C) 2022-2023 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 ALLOCATOR_H
+#define ALLOCATOR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/classify.h>
+#include <dhcp/duid.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/pool.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <mutex>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of a @c Subnet.
+///
+/// We don't include the subnet header because it would cause a
+/// circular dependency.
+class Subnet;
+
+/// @brief Weak pointer to the @c Subnet.
+typedef boost::weak_ptr<Subnet> WeakSubnetPtr;
+
+/// An exception that is thrown when allocation module fails (e.g. due to
+/// lack of available addresses)
+class AllocFailed : public Exception {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ AllocFailed(const char* file, size_t line, const char* what)
+ : Exception(file, line, what) {}
+};
+
+/// @brief Base class for all address/prefix allocation algorithms.
+///
+/// This is an abstract class that should not be used directly, but rather
+/// specialized implementations should be used instead.
+///
+/// This class holds a weak pointer to the subnet owning it because
+/// it must not exist without the subnet. Also, it can't hold a shared
+/// pointer to the subnet because it would cause a circular dependency
+/// between the two.
+class Allocator {
+public:
+
+ /// @brief Type of preferred PD-pool prefix length selection criteria
+ enum PrefixLenMatchType {
+ PREFIX_LEN_EQUAL, // select PD-pools with specific prefix length
+ PREFIX_LEN_LOWER, // select PD-pools with lower prefix length
+ PREFIX_LEN_HIGHER // select PD-pools with higher prefix length
+ };
+
+ /// @brief Constructor
+ ///
+ /// Specifies which type of leases this allocator will assign.
+ ///
+ /// @param type specifies pool type (addresses, temporary addresses
+ /// or prefixes).
+ /// @param subnet weak pointer to the subnet owning the allocator.
+ Allocator(Lease::Type type, const WeakSubnetPtr& subnet);
+
+ /// @brief Virtual destructor.
+ ///
+ /// Removes all LeaseMgr callbacks it installed.
+ virtual ~Allocator();
+
+ /// @brief Returns allocator type string.
+ ///
+ /// @return allocator-specific type string.
+ virtual std::string getType() const = 0;
+
+ /// @brief Picks an address.
+ ///
+ /// This method returns one address from the available pools in the
+ /// specified subnet. It should not check if the address is used or
+ /// reserved - AllocEngine will check that and will call pickAddress
+ /// again if necessary. The number of times this method is called will
+ /// increase as the number of available leases will decrease.
+ ///
+ /// Pools which are not allowed for client classes are skipped.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param duid Client's DUID.
+ /// @param hint Client's hint.
+ ///
+ /// @return the next address.
+ virtual isc::asiolink::IOAddress
+ pickAddress(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr& duid,
+ const asiolink::IOAddress& hint) {
+ util::MultiThreadingLock lock(mutex_);
+ return (pickAddressInternal(client_classes, duid, hint));
+ }
+
+ /// @brief Picks a delegated prefix.
+ ///
+ /// This method returns one prefix from the available pools in the
+ /// specified subnet. It should not check if the prefix is used or
+ /// reserved - AllocEngine will check that and will call pickPrefix
+ /// again if necessary. The number of times this method is called will
+ /// increase as the number of available leases will decrease.
+ ///
+ /// Pools which are not allowed for client classes are skipped.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param pool the selected pool satisfying all required conditions.
+ /// @param duid Client's DUID.
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length.
+ /// @param hint Client's hint.
+ /// @param hint_prefix_length the hint prefix length that the client
+ /// provided. The 0 value means that there is no hint and that any
+ /// pool will suffice.
+ ///
+ /// @return the next prefix.
+ virtual isc::asiolink::IOAddress
+ pickPrefix(const ClientClasses& client_classes,
+ Pool6Ptr& pool,
+ const IdentifierBaseTypePtr& duid,
+ PrefixLenMatchType prefix_length_match,
+ const asiolink::IOAddress& hint,
+ uint8_t hint_prefix_length) {
+ util::MultiThreadingLock lock(mutex_);
+ return (pickPrefixInternal(client_classes, pool, duid,
+ prefix_length_match, hint,
+ hint_prefix_length));
+ }
+
+ /// @brief Check if the pool matches the selection criteria relative to the
+ /// provided hint prefix length.
+ ///
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length.
+ /// @param pool the pool checked for restricted delegated prefix length
+ /// value.
+ /// @param hint_prefix_length The hint prefix length that the client
+ /// provided. The 0 value means that there is no hint and that any
+ /// pool will suffice.
+ static bool isValidPrefixPool(Allocator::PrefixLenMatchType prefix_length_match,
+ PoolPtr pool, uint8_t hint_prefix_length);
+
+ /// @brief Performs allocator initialization after server's reconfiguration.
+ ///
+ /// Some allocators install callbacks in the lease manager to keep track of
+ /// the lease allocations. These callbacks may only be installed when the
+ /// lease manager instance is available (i.e., when the server finishes the
+ /// reconfiguration). Such callbacks can be installed in this function.
+ ///
+ /// In this function, the allocators can also re-build their allocation states.
+ void initAfterConfigure();
+
+protected:
+
+ /// @brief Allocator-specific initialization function.
+ ///
+ /// It is called by the @c initAfterConfigure and can be overridden in the
+ /// derived allocators.
+ virtual void initAfterConfigureInternal() {};
+
+private:
+
+ /// @brief Picks an address.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickAddress.
+ /// Derived classes must provide their specific implementations of
+ /// this function.
+ ///
+ /// @param client_classes list of classes client belongs to
+ /// @param duid Client's DUID
+ /// @param hint Client's hint
+ ///
+ /// @return the next address.
+ virtual isc::asiolink::IOAddress
+ pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr& duid,
+ const isc::asiolink::IOAddress& hint) = 0;
+
+ /// @brief Picks a delegated prefix.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickPrefix.
+ /// Derived classes must provide their specific implementations of
+ /// this function.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param pool the selected pool satisfying all required conditions.
+ /// @param duid Client's DUID.
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length.
+ /// @param hint Client's hint.
+ /// @param hint_prefix_length the hint prefix length that the client
+ /// provided. The 0 value means that there is no hint and that any
+ /// pool will suffice.
+ ///
+ /// @return the next prefix.
+ virtual isc::asiolink::IOAddress
+ pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool,
+ const IdentifierBaseTypePtr& duid,
+ PrefixLenMatchType prefix_length_match,
+ const isc::asiolink::IOAddress& hint,
+ uint8_t hint_prefix_length) = 0;
+
+protected:
+
+ /// @brief Indicates if the allocator has been initialized.
+ ///
+ /// It is set to true when @c initAfterConfigure has been called.
+ /// It prevents initializing the allocator several times.
+ bool inited_;
+
+ /// @brief Defines pool type allocation
+ Lease::Type pool_type_;
+
+ /// @brief ID of a subnet to which the allocator belongs.
+ SubnetID subnet_id_;
+
+ /// @brief Weak pointer to the subnet owning the allocator.
+ WeakSubnetPtr subnet_;
+
+ /// @brief The mutex to protect the allocated lease.
+ std::mutex mutex_;
+};
+
+/// Defines a pointer to an allocator.
+typedef boost::shared_ptr<Allocator> AllocatorPtr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // ALLOCATOR_H
diff --git a/src/lib/dhcpsrv/base_host_data_source.h b/src/lib/dhcpsrv/base_host_data_source.h
new file mode 100644
index 0000000..1ca5090
--- /dev/null
+++ b/src/lib/dhcpsrv/base_host_data_source.h
@@ -0,0 +1,580 @@
+// Copyright (C) 2014-2023 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 BASE_HOST_DATA_SOURCE_H
+#define BASE_HOST_DATA_SOURCE_H
+
+#include <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <dhcpsrv/host.h>
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+
+#include <limits>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when the duplicate @c Host object is detected.
+class DuplicateHost : public Exception {
+public:
+ DuplicateHost(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when a @c Host object is expected, but none are found.
+class HostNotFound : public Exception {
+public:
+ HostNotFound(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when an address is already reserved by a @c Host
+/// object (DuplicateHost is same identity, ReservedAddress same address).
+class ReservedAddress : public Exception {
+public:
+ ReservedAddress(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when invalid IP address has been specified for
+/// @c Host.
+class BadHostAddress : public isc::BadValue {
+public:
+ BadHostAddress(const char* file, size_t line, const char* what) :
+ isc::BadValue(file, line, what) { };
+};
+
+/// @brief Wraps value holding size of the page with host reservations.
+class HostPageSize {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param page_size page size value.
+ /// @throw OutOfRange if page size is 0 or greater than uint32_t numeric
+ /// limit.
+ explicit HostPageSize(const size_t page_size) : page_size_(page_size) {
+ if (page_size_ == 0) {
+ isc_throw(OutOfRange, "page size of retrieved hosts must not be 0");
+ }
+ if (page_size_ > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(OutOfRange, "page size of retrieved hosts must not be greate than "
+ << std::numeric_limits<uint32_t>::max());
+ }
+ }
+
+ const size_t page_size_; ///< Holds page size.
+};
+
+/// @brief Base interface for the classes implementing simple data source
+/// for host reservations.
+///
+/// This abstract class defines an interface for the classes implementing
+/// basic data source for host reservations. This interface allows for
+/// adding new reservations (represented by @c Host objects) and retrieving
+/// these reservations using various parameters such as HW address or DUID,
+/// subnet identifier (either IPv4 or IPv6) or reserved IP address.
+///
+/// This interface DOES NOT specify the methods to manage existing
+/// host reservations such as to remove one IPv6 reservation but leave
+/// other reservations. It also lacks the methods used for preparing
+/// the data to be added to the SQL database: commit, rollback etc.
+/// Such methods are declared in other interfaces.
+class BaseHostDataSource {
+public:
+
+ /// @brief Specifies the type of an identifier.
+ ///
+ /// This is currently used only by MySQL host data source for now, but
+ /// it is envisaged that it will be used by other host data sources
+ /// in the future. Also, this list will grow over time. It is likely
+ /// that we'll implement other identifiers in the future, e.g. remote-id.
+ ///
+ /// Those value correspond directly to dhcp_identifier_type in hosts
+ /// table in MySQL schema.
+ enum IdType {
+ ID_HWADDR = 0, ///< Hardware address
+ ID_DUID = 1 ///< DUID/client-id
+ };
+
+ /// @brief Default destructor implementation.
+ virtual ~BaseHostDataSource() { }
+
+ /// @brief Return all hosts connected to any subnet for which reservations
+ /// have been made using a specified identifier.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for a specified identifier. This method may return multiple hosts
+ /// because a particular client may have reservations in multiple subnets.
+ ///
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const = 0;
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id) const = 0;
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id) const = 0;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname.
+ ///
+ /// @note: as hostnames are case-insensitive the search key is given
+ /// in lower cases, search indexes should either be case-insensitive
+ /// or be case-sensitive using the lower case version of hostnames.
+ ///
+ /// @param hostname The lower case hostname.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAllbyHostname(const std::string& hostname) const = 0;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id) const = 0;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id) const = 0;
+
+ /// @brief Returns range of hosts in a DHCPv4 subnet.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const = 0;
+
+ /// @brief Returns range of hosts in a DHCPv6 subnet.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const = 0;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const = 0;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const = 0;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const asiolink::IOAddress& address) const = 0;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const = 0;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// One of the use cases for this method is to detect collisions between
+ /// dynamically allocated addresses and reserved addresses. When the new
+ /// address is assigned to a client, the allocation mechanism should check
+ /// if this address is not reserved for some other host and do not allocate
+ /// this address if reservation is present.
+ ///
+ /// Implementations of this method should guard against invalid addresses,
+ /// such as IPv6 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Const @c Host object using a specified IPv4 address.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const = 0;
+
+ /// @brief Returns all hosts connected to the IPv4 subnet and having
+ /// a reservation for a specified address.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv4 address within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv4 reservation. In that case, the number of hosts returned
+ /// by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv4 address in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv4 address is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv4 address is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const = 0;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const = 0;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Const @c Host object using a specified IPv6 prefix.
+ virtual ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const = 0;
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address or prefix.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Const @c Host object using a specified IPv6 address/prefix.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const = 0;
+
+ /// @brief Returns all hosts connected to the IPv6 subnet and having
+ /// a reservation for a specified address or delegated prefix (lease).
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix. In that case, the number of hosts
+ /// returned by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const = 0;
+
+ /// @brief Returns all hosts having a reservation for a specified
+ /// address or delegated prefix (lease) in all subnets.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host per subnet.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const asiolink::IOAddress& address) const = 0;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// The implementations of this method should guard against duplicate
+ /// reservations for the same host, where possible. For example, when the
+ /// reservation for the same HW address and subnet id is added twice, the
+ /// implementation should throw an exception. Note, that usually it is
+ /// impossible to guard against adding duplicated host, where one instance
+ /// is identified by HW address, another one by DUID.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ virtual void add(const HostPtr& host) = 0;
+
+ /// @brief Attempts to delete hosts by (subnet-id, address)
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) = 0;
+
+ /// @brief Attempts to delete a host by (subnet-id4, identifier, identifier-type)
+ ///
+ /// This method supports v4 hosts only.
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
+ /// @brief Attempts to delete a host by (subnet-id6, identifier, identifier-type)
+ ///
+ /// This method supports v6 hosts only.
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
+ /// @brief Attempts to update an existing host entry.
+ ///
+ /// The implementation is common to multiple host data sources, so let's
+ /// provide it in the base host data source. In some instances, it may
+ /// require synchronization e.g. with transactions in case of databases.
+ ///
+ /// @param host the host up to date with the requested changes
+ virtual void update(HostPtr const& host) {
+ bool deleted(false);
+ if (host->getIPv4SubnetID() != SUBNET_ID_UNUSED) {
+ std::vector<uint8_t> const& identifier(host->getIdentifier());
+ deleted = del4(host->getIPv4SubnetID(), host->getIdentifierType(), identifier.data(),
+ identifier.size());
+ } else if (host->getIPv6SubnetID() != SUBNET_ID_UNUSED) {
+ std::vector<uint8_t> const& identifier(host->getIdentifier());
+ deleted = del6(host->getIPv6SubnetID(), host->getIdentifierType(), identifier.data(),
+ identifier.size());
+ } else {
+ isc_throw(HostNotFound, "Mandatory 'subnet-id' parameter missing.");
+ }
+ if (!deleted) {
+ isc_throw(HostNotFound, "Host not updated (not found).");
+ }
+ add(host);
+ }
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const = 0;
+
+ /// @brief Return backend parameters
+ ///
+ /// Returns the backend parameters
+ ///
+ /// @return Parameters of the backend.
+ virtual isc::db::DatabaseConnection::ParameterMap getParameters() const {
+ return (isc::db::DatabaseConnection::ParameterMap());
+ }
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void commit() {};
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void rollback() {};
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// All backends are required to support the case when unique setting is
+ /// @c true and they must use this setting by default.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return true if the new setting was accepted by the backend or false
+ /// otherwise.
+ virtual bool setIPReservationsUnique(const bool unique) = 0;
+
+ /// @brief Flag which indicates if the host manager has at least one
+ /// unusable connection.
+ ///
+ /// @return true if there is at least one unusable connection, false
+ /// otherwise
+ virtual bool isUnusable() {
+ return (false);
+ }
+};
+
+/// @brief HostDataSource pointer
+typedef boost::shared_ptr<BaseHostDataSource> HostDataSourcePtr;
+
+/// @brief HostDataSource list
+typedef std::vector<HostDataSourcePtr> HostDataSourceList;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // BASE_HOST_DATA_SOURCE_H
diff --git a/src/lib/dhcpsrv/cache_host_data_source.h b/src/lib/dhcpsrv/cache_host_data_source.h
new file mode 100644
index 0000000..a0a32cc
--- /dev/null
+++ b/src/lib/dhcpsrv/cache_host_data_source.h
@@ -0,0 +1,68 @@
+// Copyright (C) 2018-2020 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 CACHE_HOST_DATA_SOURCE_H
+#define CACHE_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/base_host_data_source.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Abstract interface extending base simple data source for host
+/// reservations to host cache.
+/// Only the insert() method is required to use the cache.
+class CacheHostDataSource : public virtual BaseHostDataSource {
+public:
+
+ /// @brief Default destructor implementation.
+ virtual ~CacheHostDataSource() { }
+
+ /// @brief Insert a host into the cache.
+ ///
+ /// Similar to @c add() but with a different purpose.
+ ///
+ /// @param host Pointer to the new @c Host object being inserted.
+ /// @param overwrite false if doing nothing in case of conflicts
+ /// (and returning 1), true if removing conflicting entries
+ /// (and returning their number).
+ /// @return number of conflicts limited to one if overwrite is false.
+ virtual size_t insert(const ConstHostPtr& host, bool overwrite) = 0;
+
+ /// @brief Remove a host from the cache.
+ ///
+ /// Does the same as @c del, @c del4 or @c del6 but with
+ /// a more uniform interface and a different purpose.
+ ///
+ /// @note A pointer to a copy does not remove the object.
+ ///
+ /// @param host Pointer to the existing @c Host object being removed.
+ /// @return true when found and removed.
+ virtual bool remove(const HostPtr& host) = 0;
+
+ /// @brief Flush entries.
+ ///
+ /// @param count number of entries to remove, 0 means all.
+ virtual void flush(size_t count) = 0;
+
+ /// @brief Return the number of entries.
+ ///
+ /// @return the current number of active entries in the cache.
+ virtual size_t size() const = 0;
+
+ /// @brief Return the maximum number of entries.
+ ///
+ /// @return the maximum number of entries, 0 means unbound.
+ virtual size_t capacity() const = 0;
+};
+
+/// @brief CacheHostDataSource pointer.
+typedef boost::shared_ptr<CacheHostDataSource> CacheHostDataSourcePtr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CACHE_HOST_DATA_SOURCE_H
diff --git a/src/lib/dhcpsrv/callout_handle_store.h b/src/lib/dhcpsrv/callout_handle_store.h
new file mode 100644
index 0000000..402c9df
--- /dev/null
+++ b/src/lib/dhcpsrv/callout_handle_store.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2013-2018,2021 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 CALLOUT_HANDLE_STORE_H
+#define CALLOUT_HANDLE_STORE_H
+
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+#include <map>
+#include <set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief CalloutHandle Store
+///
+/// When using the Hooks Framework, there is a need to associate an
+/// isc::hooks::CalloutHandle object with each request passing through the
+/// server. For the DHCP servers, the association was provided by this function.
+///
+/// After introduction of "packets parking" feature this function was extended
+/// to keep association of packets with the callout handles in a map.
+/// However, it was later found that "garbage collection" of the unused
+/// handles is very hard. Trying to garbage collect handles at each invocation
+/// was highly inefficient and caused server's performance degradation.
+///
+/// The new approach is using on @c isc::hooks::CalloutHandleAssociate to
+/// associate objects with callout handles. This has a major benefit that
+/// callout handle instances are removed together with the packets associated
+/// with them.
+///
+/// This function uses this new approach and is kept for the compatibility with
+/// existing code.
+///
+/// @tparam T Pkt4Ptr or Pkt6Ptr object.
+/// @param pktptr Pointer to the packet being processed. This is typically a
+/// Pkt4Ptr or Pkt6Ptr object.
+///
+/// @return Shared pointer to a CalloutHandle. This is the previously-stored
+/// CalloutHandle if pktptr points to a packet that has been seen
+/// before or a new CalloutHandle if it points to a new one. An empty
+/// pointer is returned if pktptr is itself an empty pointer.
+template <typename T>
+isc::hooks::CalloutHandlePtr getCalloutHandle(const T& pktptr) {
+
+ if (pktptr) {
+ return (pktptr->getCalloutHandle());
+
+ }
+
+ return (isc::hooks::CalloutHandlePtr());
+}
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // CALLOUT_HANDLE_STORE_H
diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp.h b/src/lib/dhcpsrv/cb_ctl_dhcp.h
new file mode 100644
index 0000000..c34f6f6
--- /dev/null
+++ b/src/lib/dhcpsrv/cb_ctl_dhcp.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 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 CB_CTL_DHCP_H
+#define CB_CTL_DHCP_H
+
+#include <cc/stamped_value.h>
+#include <process/cb_ctl_base.h>
+#include <dhcpsrv/srv_config.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base class for implementing mechanisms to control the use
+/// of the Configuration Backends by DHCPv4 and DHCPv6 servers.
+///
+/// It includes common methods used by the DHCPv4 and DHCPv6 specific
+/// derivations.
+///
+/// @tparam ConfigBackendMgrType Type of the Config Backend Manager used
+/// by the server implementing this class. For example, for the DHCPv4
+/// server it will be @c ConfigBackendDHCPv4Mgr.
+template<typename ConfigBackendMgrType>
+class CBControlDHCP : public process::CBControlBase<ConfigBackendMgrType> {
+public:
+
+ /// @brief Constructor.
+ CBControlDHCP()
+ : process::CBControlBase<ConfigBackendMgrType>() {
+ }
+
+protected:
+
+ /// @brief Adds globals fetched from config backend(s) to a SrvConfig instance
+ ///
+ /// Iterates over the given collection of global parameters and adds them to the
+ /// given configuration's list of configured globals.
+ ///
+ /// @param external_cfg SrvConfig instance to update
+ /// @param cb_globals collection of global parameters supplied by configuration
+ /// backend
+ void addGlobalsToConfig(SrvConfigPtr external_cfg,
+ data::StampedValueCollection& cb_globals) const {
+ const auto& index = cb_globals.get<data::StampedValueNameIndexTag>();
+ for (auto cb_global = index.begin(); cb_global != index.end(); ++cb_global) {
+
+ if ((*cb_global)->amNull()) {
+ continue;
+ }
+
+ external_cfg->addConfiguredGlobal((*cb_global)->getName(),
+ (*cb_global)->getElementValue());
+ }
+ }
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CB_CTL_DHCP_H
diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp4.cc b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc
new file mode 100644
index 0000000..5ba211f
--- /dev/null
+++ b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc
@@ -0,0 +1,360 @@
+// Copyright (C) 2019-2023 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 <dhcpsrv/cb_ctl_dhcp4.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+
+using namespace isc::db;
+using namespace isc::data;
+using namespace isc::process;
+using namespace isc::hooks;
+
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct CbCtlHooks {
+ int hook_index_cb4_updated_; ///< index for "cb4_updated" hook point.
+
+ /// Constructor that registers hook points for CBControlDHCPv4.
+ CbCtlHooks() {
+ hook_index_cb4_updated_ = HooksManager::registerHook("cb4_updated");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+CbCtlHooks hooks_;
+
+}; // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+void
+CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& lb_modification_time,
+ const AuditEntryCollection& audit_entries) {
+
+ auto globals_fetched = false;
+ auto reconfig = audit_entries.empty();
+ auto cb_update = !reconfig;
+ auto current_cfg = CfgMgr::instance().getCurrentCfg();
+ auto staging_cfg = CfgMgr::instance().getStagingCfg();
+
+ // Let's first delete all the configuration elements for which DELETE audit
+ // entries are found. Although, this may break chronology of the audit in
+ // some cases it should not affect the end result of the data fetch. If the
+ // object was created and then subsequently deleted, we will first try to
+ // delete this object from the local configuration (which will fail because
+ // the object does not exist) and then we will try to fetch it from the
+ // database which will return no result.
+ if (cb_update) {
+
+ auto external_cfg = CfgMgr::instance().createExternalCfg();
+
+ // Get audit entries for deleted global parameters.
+ const auto& index = audit_entries.get<AuditEntryObjectTypeTag>();
+ auto range = index.equal_range(boost::make_tuple("dhcp4_global_parameter",
+ AuditEntry::ModificationType::DELETE));
+ if (range.first != range.second) {
+ // Some globals have been deleted. Since we currently don't track database
+ // identifiers of the global parameters we have to fetch all global
+ // parameters for this server. Next, we simply replace existing
+ // global parameters with the new parameters. This is slightly
+ // inefficient but only slightly. Note that this is a single
+ // database query and the number of global parameters is small.
+ data::StampedValueCollection globals;
+ globals = getMgr().getPool()->getAllGlobalParameters4(backend_selector, server_selector);
+ addGlobalsToConfig(external_cfg, globals);
+
+ // Add defaults.
+ external_cfg->applyDefaultsConfiguredGlobals(SimpleParser4::GLOBAL4_DEFAULTS);
+
+ // Sanity check it.
+ external_cfg->sanityChecksLifetime("valid-lifetime");
+
+ // Now that we successfully fetched the new global parameters, let's
+ // remove existing ones and merge them into the current configuration.
+ current_cfg->clearConfiguredGlobals();
+ CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence());
+ globals_fetched = true;
+ }
+
+ try {
+ // Get audit entries for deleted option definitions and delete each
+ // option definition from the current configuration for which the
+ // audit entry is found.
+ range = index.equal_range(boost::make_tuple("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getCfgOptionDef()->del((*entry)->getObjectId());
+ }
+
+ // Repeat the same for other configuration elements.
+
+ range = index.equal_range(boost::make_tuple("dhcp4_options",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getCfgOption()->del((*entry)->getObjectId());
+ }
+
+ range = index.equal_range(boost::make_tuple("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getClientClassDictionary()->removeClass((*entry)->getObjectId());
+ }
+
+ range = index.equal_range(boost::make_tuple("dhcp4_shared_network",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getCfgSharedNetworks4()->del((*entry)->getObjectId());
+ }
+
+ range = index.equal_range(boost::make_tuple("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ // If the deleted subnet belongs to a shared network and the
+ // shared network is not being removed, we need to detach the
+ // subnet from the shared network.
+ auto subnet = current_cfg->getCfgSubnets4()->getBySubnetId((*entry)->getObjectId());
+ if (subnet) {
+ // Check if the subnet belongs to a shared network.
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ // Detach the subnet from the shared network.
+ network->del(subnet->getID());
+ }
+ // Actually delete the subnet from the configuration.
+ current_cfg->getCfgSubnets4()->del((*entry)->getObjectId());
+ }
+ }
+
+ } catch (...) {
+ // Ignore errors thrown when attempting to delete a non-existing
+ // configuration entry. There is no guarantee that the deleted
+ // entry is actually there as we're not processing the audit
+ // chronologically.
+ }
+ }
+
+ // Create the external config into which we'll fetch backend config data.
+ auto external_cfg = CfgMgr::instance().createExternalCfg();
+
+ // First let's fetch the globals and add them to external config.
+ AuditEntryCollection updated_entries;
+ if (!globals_fetched) {
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp4_global_parameter");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ data::StampedValueCollection globals;
+ globals = getMgr().getPool()->getModifiedGlobalParameters4(backend_selector, server_selector,
+ lb_modification_time);
+ addGlobalsToConfig(external_cfg, globals);
+ globals_fetched = true;
+ }
+ }
+
+ // Now we fetch the option definitions and add them.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp4_option_def");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ OptionDefContainer option_defs =
+ getMgr().getPool()->getModifiedOptionDefs4(backend_selector, server_selector,
+ lb_modification_time);
+ for (auto option_def = option_defs.begin(); option_def != option_defs.end(); ++option_def) {
+ if (!audit_entries.empty() && !hasObjectId(updated_entries, (*option_def)->getId())) {
+ continue;
+ }
+ external_cfg->getCfgOptionDef()->add(*option_def);
+ }
+ }
+
+ // Next fetch the options. They are returned as a container of OptionDescriptors.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp4_options");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ OptionContainer options = getMgr().getPool()->getModifiedOptions4(backend_selector,
+ server_selector,
+ lb_modification_time);
+ for (auto option = options.begin(); option != options.end(); ++option) {
+ if (!audit_entries.empty() && !hasObjectId(updated_entries, (*option).getId())) {
+ continue;
+ }
+ external_cfg->getCfgOption()->add((*option), (*option).space_name_);
+ }
+ }
+
+ // Fetch client classes. They are returned in a ClientClassDictionary.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp4_client_class");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ ClientClassDictionary client_classes = getMgr().getPool()->getAllClientClasses4(backend_selector,
+ server_selector);
+ // Match expressions are not initialized for classes returned from the config backend.
+ // We have to ensure to initialize them before they can be used by the server.
+ client_classes.initMatchExpr(AF_INET);
+
+ // Class options also need to be created when returned from the config backend.
+ client_classes.createOptions(external_cfg->getCfgOptionDef());
+
+ external_cfg->setClientClassDictionary(boost::make_shared<ClientClassDictionary>(client_classes));
+ }
+
+ // Allocator selection at the global level can affect subnets and shared networks
+ // for which the allocator hasn't been specified explicitly. Let's see if the
+ // allocator has been specified at the global level.
+ std::string global_allocator;
+ auto allocator = external_cfg->getConfiguredGlobal(CfgGlobals::ALLOCATOR);
+ if (allocator && (allocator->getType() == Element::string)) {
+ global_allocator = allocator->stringValue();
+ }
+
+ // If we're fetching the changes from the config backend we also want
+ // to see if the global allocator has changed. Let's get the currently
+ // used allocator too.
+ auto allocator_changed = false;
+ // We're only affected by the allocator change if this is the update from
+ // the configuration backend.
+ if (cb_update) {
+ auto allocator = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::ALLOCATOR);
+ if (allocator && (allocator->getType() == Element::string)) {
+ allocator_changed = (global_allocator != allocator->stringValue());
+ }
+ }
+
+ // Now fetch the shared networks.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp4_shared_network");
+ }
+ SharedNetwork4Collection networks;
+ if (allocator_changed || reconfig) {
+ // A change of the allocator or the server reconfiguration can affect all
+ // shared networks. Get all shared networks.
+ networks = getMgr().getPool()->getAllSharedNetworks4(backend_selector, server_selector);
+
+ } else if (!updated_entries.empty()) {
+ // An update from the config backend when the global allocator hasn't changed
+ // means that we only need to handle the modified subnets.
+ networks = getMgr().getPool()->getModifiedSharedNetworks4(backend_selector, server_selector,
+ lb_modification_time);
+ }
+ // Iterate over all shared networks that may require reconfiguration.
+ for (auto network = networks.begin(); network != networks.end(); ++network) {
+ if (!allocator_changed && cb_update && !hasObjectId(updated_entries, (*network)->getId())) {
+ continue;
+ }
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a shared network we need to set a callback function
+ // for each network to allow for fetching global parameters.
+ (*network)->setFetchGlobalsFn([] () -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
+ });
+ (*network)->setDefaultAllocatorType(global_allocator);
+ external_cfg->getCfgSharedNetworks4()->add((*network));
+ }
+
+ // Next, fetch the subnets.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp4_subnet");
+ }
+ Subnet4Collection subnets;
+ if (allocator_changed || reconfig) {
+ // A change of the allocator or the server reconfiguration can affect all
+ // subnets. Get all subnets.
+ subnets = getMgr().getPool()->getAllSubnets4(backend_selector, server_selector);
+
+ } else if (!updated_entries.empty()) {
+ // An update from the config backend when the global allocator hasn't changed
+ // means that we only need to handle the modified subnets.
+ subnets = getMgr().getPool()->getModifiedSubnets4(backend_selector,
+ server_selector,
+ lb_modification_time);
+ }
+ // Iterate over all subnets that may require reconfiguration.
+ for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) {
+ if (!allocator_changed && cb_update && !hasObjectId(updated_entries, (*subnet)->getID())) {
+ continue;
+ }
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a subnet we need to set a callback function for each
+ // subnet to allow for fetching global parameters.
+ (*subnet)->setFetchGlobalsFn([] () -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
+ });
+ (*subnet)->setDefaultAllocatorType(global_allocator);
+ external_cfg->getCfgSubnets4()->add((*subnet));
+ }
+
+ if (reconfig) {
+ // If we're configuring the server after startup, we do not apply the
+ // ip-reservations-unique setting here. It will be applied when the
+ // configuration is committed.
+ external_cfg->sanityChecksLifetime(*staging_cfg, "valid-lifetime");
+ CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence());
+
+ } else {
+ if (globals_fetched) {
+ // ip-reservations-unique parameter requires special handling because
+ // setting it to false may be unsupported by some host backends.
+ bool ip_unique = true;
+ auto ip_unique_param = external_cfg->getConfiguredGlobal("ip-reservations-unique");
+ if (ip_unique_param && (ip_unique_param->getType() == Element::boolean)) {
+ ip_unique = ip_unique_param->boolValue();
+ }
+ // First try to use the new setting to configure the HostMgr because it
+ // may fail if the backend does not support it.
+ if (!HostMgr::instance().setIPReservationsUnique(ip_unique)) {
+ // The new setting is unsupported by the backend, so do not apply this
+ // setting at all.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_IPV4_RESERVATIONS_NON_UNIQUE_IGNORED);
+ external_cfg->addConfiguredGlobal("ip-reservations-unique", Element::create(true));
+ }
+ }
+ external_cfg->sanityChecksLifetime(*current_cfg, "valid-lifetime");
+ CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence());
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->initAllocatorsAfterConfigure();
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIG4_MERGED);
+
+ if (cb_update &&
+ HooksManager::calloutsPresent(hooks_.hook_index_cb4_updated_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Pass a shared pointer to audit entries.
+ AuditEntryCollectionPtr ptr(new AuditEntryCollection(audit_entries));
+ callout_handle->setArgument("audit_entries", ptr);
+
+ // Call the callouts
+ HooksManager::callCallouts(hooks_.hook_index_cb4_updated_, *callout_handle);
+
+ // Ignore the result.
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp4.h b/src/lib/dhcpsrv/cb_ctl_dhcp4.h
new file mode 100644
index 0000000..8e38727
--- /dev/null
+++ b/src/lib/dhcpsrv/cb_ctl_dhcp4.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 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 CB_CTL_DHCP4_H
+#define CB_CTL_DHCP4_H
+
+#include <dhcpsrv/cb_ctl_dhcp.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/srv_config.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implementation of the mechanisms to control the use of
+/// the Configuration Backends by the DHCPv4 server.
+///
+/// It implements fetching and merging DHCPv4 server configuration from
+/// the database into the staging or current configuration.
+///
+/// @tparam ConfigBackendMgrType Type of the Config Backend Manager used
+/// by the server implementing this class. For example, for the DHCPv4
+/// server it will be @c ConfigBackendDHCPv4Mgr.
+class CBControlDHCPv4 : public CBControlDHCP<ConfigBackendDHCPv4Mgr> {
+protected:
+
+ /// @brief DHCPv4 server specific method to fetch and apply back end
+ /// configuration into the local configuration.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param lb_modification_time Lower bound modification time for the
+ /// configuration elements to be fetched.
+ /// @param audit_entries Audit entries fetched from the database since
+ /// the last configuration update. This collection is empty if there
+ /// were no updates.
+ virtual void databaseConfigApply(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& lb_modification_time,
+ const db::AuditEntryCollection& audit_entries);
+};
+
+typedef boost::shared_ptr<CBControlDHCPv4> CBControlDHCPv4Ptr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CB_CTL_DHCP4_H
diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc
new file mode 100644
index 0000000..91b482e
--- /dev/null
+++ b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc
@@ -0,0 +1,376 @@
+// Copyright (C) 2019-2023 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 <dhcpsrv/cb_ctl_dhcp6.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+
+using namespace isc::db;
+using namespace isc::data;
+using namespace isc::process;
+using namespace isc::hooks;
+
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct CbCtlHooks {
+ int hook_index_cb6_updated_; ///< index for "cb6_updated" hook point.
+
+ /// Constructor that registers hook points for CBControlDHCPv6.
+ CbCtlHooks() {
+ hook_index_cb6_updated_ = HooksManager::registerHook("cb6_updated");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+CbCtlHooks hooks_;
+
+}; // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+void
+CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& lb_modification_time,
+ const db::AuditEntryCollection& audit_entries) {
+ bool globals_fetched = false;
+ auto reconfig = audit_entries.empty();
+ auto cb_update = !reconfig;
+ auto current_cfg = CfgMgr::instance().getCurrentCfg();
+ auto staging_cfg = CfgMgr::instance().getStagingCfg();
+
+ // Let's first delete all the configuration elements for which DELETE audit
+ // entries are found. Although, this may break chronology of the audit in
+ // some cases it should not affect the end result of the data fetch. If the
+ // object was created and then subsequently deleted, we will first try to
+ // delete this object from the local configuration (which will fail because
+ // the object does not exist) and then we will try to fetch it from the
+ // database which will return no result.
+ if (cb_update) {
+
+ auto external_cfg = CfgMgr::instance().createExternalCfg();
+
+ // Get audit entries for deleted global parameters.
+ const auto& index = audit_entries.get<AuditEntryObjectTypeTag>();
+ auto range = index.equal_range(boost::make_tuple("dhcp6_global_parameter",
+ AuditEntry::ModificationType::DELETE));
+ if (range.first != range.second) {
+ // Some globals have been deleted. Since we currently don't track database
+ // identifiers of the global parameters we have to fetch all global
+ // parameters for this server. Next, we simply replace existing
+ // global parameters with the new parameters. This is slightly
+ // inefficient but only slightly. Note that this is a single
+ // database query and the number of global parameters is small.
+ data::StampedValueCollection globals;
+ globals = getMgr().getPool()->getAllGlobalParameters6(backend_selector, server_selector);
+ addGlobalsToConfig(external_cfg, globals);
+
+ // Add defaults.
+ external_cfg->applyDefaultsConfiguredGlobals(SimpleParser6::GLOBAL6_DEFAULTS);
+
+ // Sanity check it.
+ external_cfg->sanityChecksLifetime("preferred-lifetime");
+ external_cfg->sanityChecksLifetime("valid-lifetime");
+
+ // Now that we successfully fetched the new global parameters, let's
+ // remove existing ones and merge them into the current configuration.
+ current_cfg->clearConfiguredGlobals();
+ CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence());
+ globals_fetched = true;
+ }
+
+ try {
+ // Get audit entries for deleted option definitions and delete each
+ // option definition from the current configuration for which the
+ // audit entry is found.
+ range = index.equal_range(boost::make_tuple("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getCfgOptionDef()->del((*entry)->getObjectId());
+ }
+
+ // Repeat the same for other configuration elements.
+
+ range = index.equal_range(boost::make_tuple("dhcp6_options",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getCfgOption()->del((*entry)->getObjectId());
+ }
+
+ range = index.equal_range(boost::make_tuple("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getClientClassDictionary()->removeClass((*entry)->getObjectId());
+ }
+
+ range = index.equal_range(boost::make_tuple("dhcp6_shared_network",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ current_cfg->getCfgSharedNetworks6()->del((*entry)->getObjectId());
+ }
+
+ range = index.equal_range(boost::make_tuple("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE));
+ for (auto entry = range.first; entry != range.second; ++entry) {
+ // If the deleted subnet belongs to a shared network and the
+ // shared network is not being removed, we need to detach the
+ // subnet from the shared network.
+ auto subnet = current_cfg->getCfgSubnets6()->getBySubnetId((*entry)->getObjectId());
+ if (subnet) {
+ // Check if the subnet belongs to a shared network.
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ // Detach the subnet from the shared network.
+ network->del(subnet->getID());
+ }
+ // Actually delete the subnet from the configuration.
+ current_cfg->getCfgSubnets6()->del((*entry)->getObjectId());
+ }
+ }
+
+ } catch (...) {
+ // Ignore errors thrown when attempting to delete a non-existing
+ // configuration entry. There is no guarantee that the deleted
+ // entry is actually there as we're not processing the audit
+ // chronologically.
+ }
+ }
+
+ // Create the external config into which we'll fetch backend config data.
+ SrvConfigPtr external_cfg = CfgMgr::instance().createExternalCfg();
+
+ // First let's fetch the globals and add them to external config.
+ AuditEntryCollection updated_entries;
+ if (!globals_fetched) {
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp6_global_parameter");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ data::StampedValueCollection globals;
+ globals = getMgr().getPool()->getModifiedGlobalParameters6(backend_selector, server_selector,
+ lb_modification_time);
+ addGlobalsToConfig(external_cfg, globals);
+ globals_fetched = true;
+ }
+ }
+
+ // Now we fetch the option definitions and add them.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp6_option_def");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ OptionDefContainer option_defs =
+ getMgr().getPool()->getModifiedOptionDefs6(backend_selector, server_selector,
+ lb_modification_time);
+ for (auto option_def = option_defs.begin(); option_def != option_defs.end(); ++option_def) {
+ if (!audit_entries.empty() && !hasObjectId(updated_entries, (*option_def)->getId())) {
+ continue;
+ }
+ external_cfg->getCfgOptionDef()->add(*option_def);
+ }
+ }
+
+ // Next fetch the options. They are returned as a container of OptionDescriptors.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp6_options");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ OptionContainer options = getMgr().getPool()->getModifiedOptions6(backend_selector,
+ server_selector,
+ lb_modification_time);
+ for (auto option = options.begin(); option != options.end(); ++option) {
+ if (!audit_entries.empty() && !hasObjectId(updated_entries, (*option).getId())) {
+ continue;
+ }
+ external_cfg->getCfgOption()->add((*option), (*option).space_name_);
+ }
+ }
+
+ // Fetch client classes. They are returned in a ClientClassDictionary.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp6_client_class");
+ }
+ if (reconfig || !updated_entries.empty()) {
+ ClientClassDictionary client_classes = getMgr().getPool()->getAllClientClasses6(backend_selector,
+ server_selector);
+ // Match expressions are not initialized for classes returned from the config backend.
+ // We have to ensure to initialize them before they can be used by the server.
+ client_classes.initMatchExpr(AF_INET6);
+
+ // Class options also need to be created when returned from the config backend.
+ client_classes.createOptions(external_cfg->getCfgOptionDef());
+
+ external_cfg->setClientClassDictionary(boost::make_shared<ClientClassDictionary>(client_classes));
+ }
+
+ // Allocator selection at the global level can affect subnets and shared networks
+ // for which the allocator hasn't been specified explicitly. Let's see if the
+ // allocator has been specified at the global level.
+ std::string global_allocator;
+ auto allocator = external_cfg->getConfiguredGlobal(CfgGlobals::ALLOCATOR);
+ if (allocator && (allocator->getType() == Element::string)) {
+ global_allocator = allocator->stringValue();
+ }
+
+ // Also, get the PD allocator.
+ std::string global_pd_allocator;
+ allocator = external_cfg->getConfiguredGlobal(CfgGlobals::PD_ALLOCATOR);
+ if (allocator && (allocator->getType() == Element::string)) {
+ global_pd_allocator = allocator->stringValue();
+ }
+
+ // If we're fetching the changes from the config backend we also want
+ // to see if the global allocator has changed. Let's get the currently
+ // used allocator too.
+ auto allocator_changed = false;
+ // We're only affected by the allocator change if this is the update from
+ // the configuration backend.
+ if (cb_update) {
+ auto allocator = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::ALLOCATOR);
+ if (allocator && (allocator->getType() == Element::string)) {
+ allocator_changed = (global_allocator != allocator->stringValue());
+ }
+
+ // The address allocator hasn't changed. So, let's check if the PD allocator
+ // has changed.
+ if (!allocator_changed) {
+ auto allocator = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::PD_ALLOCATOR);
+ if (allocator && (allocator->getType() == Element::string)) {
+ allocator_changed = (global_pd_allocator != allocator->stringValue());
+ }
+ }
+ }
+
+ // Now fetch the shared networks.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp6_shared_network");
+ }
+ SharedNetwork6Collection networks;
+ if (allocator_changed || reconfig) {
+ // A change of the allocator or the server reconfiguration can affect all
+ // shared networks. Get all shared networks.
+ networks = getMgr().getPool()->getAllSharedNetworks6(backend_selector, server_selector);
+ } else if (!updated_entries.empty()) {
+ networks = getMgr().getPool()->getModifiedSharedNetworks6(backend_selector, server_selector,
+ lb_modification_time);
+ }
+ for (auto network = networks.begin(); network != networks.end(); ++network) {
+ if (!allocator_changed && cb_update && !hasObjectId(updated_entries, (*network)->getId())) {
+ continue;
+ }
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a shared network we need to set a callback function
+ // for each network to allow for fetching global parameters.
+ (*network)->setFetchGlobalsFn([] () -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
+ });
+ (*network)->setDefaultAllocatorType(global_allocator);
+ (*network)->setDefaultPdAllocatorType(global_pd_allocator);
+ external_cfg->getCfgSharedNetworks6()->add((*network));
+ }
+
+ // Next we fetch subnets.
+ if (cb_update) {
+ updated_entries = fetchConfigElement(audit_entries, "dhcp6_subnet");
+ }
+ Subnet6Collection subnets;
+ if (allocator_changed || reconfig) {
+ // A change of the allocator or the server reconfiguration can affect all
+ // subnets. Get all subnets.
+ subnets = getMgr().getPool()->getAllSubnets6(backend_selector, server_selector);
+
+ } else if (!updated_entries.empty()) {
+ // An update from the config backend when the global allocator hasn't changed
+ // means that we only need to handle the modified subnets.
+ subnets = getMgr().getPool()->getModifiedSubnets6(backend_selector,
+ server_selector,
+ lb_modification_time);
+ }
+ // Iterate over all subnets that may require reconfiguration.
+ for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) {
+ if (!audit_entries.empty() && !hasObjectId(updated_entries, (*subnet)->getID())) {
+ continue;
+ }
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a subnet we need to set a callback function for each
+ // subnet to allow for fetching global parameters.
+ (*subnet)->setFetchGlobalsFn([] () -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
+ });
+ (*subnet)->setDefaultAllocatorType(global_allocator);
+ (*subnet)->setDefaultPdAllocatorType(global_pd_allocator);
+ external_cfg->getCfgSubnets6()->add((*subnet));
+ }
+
+ if (reconfig) {
+ // If we're configuring the server after startup, we do not apply the
+ // ip-reservations-unique setting here. It will be applied when the
+ // configuration is committed.
+ auto const& cfg = CfgMgr::instance().getStagingCfg();
+ external_cfg->sanityChecksLifetime(*cfg, "preferred-lifetime");
+ external_cfg->sanityChecksLifetime(*cfg, "valid-lifetime");
+ CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence());
+
+ } else {
+ if (globals_fetched) {
+ // ip-reservations-unique parameter requires special handling because
+ // setting it to false may be unsupported by some host backends.
+ bool ip_unique = true;
+ auto ip_unique_param = external_cfg->getConfiguredGlobal("ip-reservations-unique");
+ if (ip_unique_param && (ip_unique_param->getType() == Element::boolean)) {
+ ip_unique = ip_unique_param->boolValue();
+ }
+ // First try to use the new setting to configure the HostMgr because it
+ // may fail if the backend does not support it.
+ if (!HostMgr::instance().setIPReservationsUnique(ip_unique)) {
+ // The new setting is unsupported by the backend, so do not apply this
+ // setting at all.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_IPV6_RESERVATIONS_NON_UNIQUE_IGNORED);
+ external_cfg->addConfiguredGlobal("ip-reservations-unique", Element::create(true));
+ }
+ }
+ auto const& cfg = CfgMgr::instance().getCurrentCfg();
+ external_cfg->sanityChecksLifetime(*cfg, "preferred-lifetime");
+ external_cfg->sanityChecksLifetime(*cfg, "valid-lifetime");
+ CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence());
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->initAllocatorsAfterConfigure();
+ }
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIG6_MERGED);
+
+ if (cb_update &&
+ HooksManager::calloutsPresent(hooks_.hook_index_cb6_updated_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Use the RAII wrapper to make sure that the callout handle state is
+ // reset when this object goes out of scope. All hook points must do
+ // it to prevent possible circular dependency between the callout
+ // handle and its arguments.
+ ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+ // Pass a shared pointer to audit entries.
+ AuditEntryCollectionPtr ptr(new AuditEntryCollection(audit_entries));
+ callout_handle->setArgument("audit_entries", ptr);
+
+ // Call the callouts
+ HooksManager::callCallouts(hooks_.hook_index_cb6_updated_, *callout_handle);
+
+ // Ignore the result.
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp6.h b/src/lib/dhcpsrv/cb_ctl_dhcp6.h
new file mode 100644
index 0000000..4817556
--- /dev/null
+++ b/src/lib/dhcpsrv/cb_ctl_dhcp6.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2019 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 CB_CTL_DHCP6_H
+#define CB_CTL_DHCP6_H
+
+#include <dhcpsrv/cb_ctl_dhcp.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <dhcpsrv/srv_config.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implementation of the mechanisms to control the use of
+/// the Configuration Backends by the DHCPv6 server.
+///
+/// It implements fetching and merging DHCPv6 server configuration from
+/// the database into the staging or current configuration.
+///
+/// @tparam ConfigBackendMgrType Type of the Config Backend Manager used
+/// by the server implementing this class. For example, for the DHCPv6
+/// server it will be @c ConfigBackendDHCPv6Mgr.
+class CBControlDHCPv6 : public CBControlDHCP<ConfigBackendDHCPv6Mgr> {
+protected:
+
+ /// @brief DHCPv6 server specific method to fetch and apply back end
+ /// configuration into the local configuration.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param lb_modification_time Lower bound modification time for the
+ /// configuration elements to be fetched.
+ /// @param audit_entries Audit entries fetched from the database since
+ /// the last configuration update. This collection is empty if there
+ /// were no updates.
+ virtual void databaseConfigApply(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& lb_modification_time,
+ const db::AuditEntryCollection& audit_entries);
+};
+
+typedef boost::shared_ptr<CBControlDHCPv6> CBControlDHCPv6Ptr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CB_CTL_DHCP6_H
diff --git a/src/lib/dhcpsrv/cfg_4o6.cc b/src/lib/dhcpsrv/cfg_4o6.cc
new file mode 100644
index 0000000..3f69d3b
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_4o6.cc
@@ -0,0 +1,51 @@
+// Copyright (C) 2017-2019 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 <dhcp/option.h>
+#include <dhcpsrv/cfg_4o6.h>
+#include <string>
+#include <string.h>
+#include <sstream>
+#include <vector>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+ElementPtr
+Cfg4o6::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set 4o6-interface
+ result->set("4o6-interface", Element::create(iface4o6_));
+ // Set 4o6-subnet
+ if (!subnet4o6_.get().first.isV6Zero() || (subnet4o6_.get().second != 128u)) {
+ std::ostringstream oss;
+ oss << subnet4o6_.get().first << "/"
+ << static_cast<unsigned>(subnet4o6_.get().second);
+ result->set("4o6-subnet", Element::create(oss.str()));
+ } else {
+ result->set("4o6-subnet", Element::create(std::string()));
+ }
+ // Set 4o6-interface-id
+ if (interface_id_) {
+ std::vector<uint8_t> bin = interface_id_->toBinary();
+ std::string iid;
+ iid.resize(bin.size());
+ if (!bin.empty()) {
+ std::memcpy(&iid[0], &bin[0], bin.size());
+ }
+ result->set("4o6-interface-id", Element::create(iid));
+ } else {
+ result->set("4o6-interface-id", Element::create(std::string()));
+ }
+ return (result);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/cfg_4o6.h b/src/lib/dhcpsrv/cfg_4o6.h
new file mode 100644
index 0000000..22d7664
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_4o6.h
@@ -0,0 +1,106 @@
+// Copyright (C) 2015-2019 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 CFG_4OVER6_H
+#define CFG_4OVER6_H
+
+#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <util/optional.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This structure contains information about DHCP4o6 (RFC7341)
+///
+/// DHCP4o6 is completely optional. If it is not enabled, this structure
+/// does not contain any information.
+struct Cfg4o6 : public isc::data::CfgToElement {
+
+ /// the default constructor.
+ ///
+ /// Initializes fields to their default value.
+ Cfg4o6()
+ :enabled_(false), subnet4o6_(std::make_pair(asiolink::IOAddress("::"), 128u), true) {
+ }
+
+ /// @brief Returns whether the DHCP4o6 is enabled or not.
+ /// @return true if enabled
+ bool enabled() const {
+ return (enabled_);
+ }
+
+ /// @brief Sets the DHCP4o6 enabled status.
+ /// @param enabled specifies if the DHCP4o6 should be enabled or not
+ void enabled(bool enabled) {
+ enabled_ = enabled;
+ }
+
+ /// @brief Returns the DHCP4o6 interface.
+ /// @return value of the 4o6-interface parameter.
+ util::Optional<std::string> getIface4o6() const {
+ return (iface4o6_);
+ }
+
+ /// @brief Sets the 4o6-interface.
+ /// @param iface name of the network interface the 4o6 traffic is received on
+ void setIface4o6(const std::string& iface) {
+ iface4o6_ = iface;
+ enabled_ = true;
+ }
+
+ /// @brief Returns prefix/len for the IPv6 subnet.
+ /// @return prefix/length pair
+ util::Optional<std::pair<asiolink::IOAddress, uint8_t> > getSubnet4o6() const {
+ return (subnet4o6_);
+ }
+
+ /// @brief Sets the prefix/length information (content of the 4o6-subnet).
+ /// @param subnet IOAddress that represents a prefix
+ /// @param prefix specifies prefix length
+ void setSubnet4o6(const asiolink::IOAddress& subnet, uint8_t prefix) {
+ subnet4o6_ = std::make_pair(subnet, prefix);
+ enabled_ = true;
+ }
+
+ /// @brief Returns the interface-id.
+ /// @return the option representing interface-id (or NULL)
+ OptionPtr getInterfaceId() const {
+ return (interface_id_);
+ }
+
+ /// @brief Sets the interface-id
+ /// @param opt option to be used as interface-id match
+ void setInterfaceId(const OptionPtr& opt) {
+ interface_id_ = opt;
+ enabled_ = true;
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// Specifies if 4o6 is enabled on this subnet.
+ bool enabled_;
+
+ /// Specifies the network interface used as v4 subnet selector.
+ util::Optional<std::string> iface4o6_;
+
+ /// Specifies the IPv6 subnet used for v4 subnet selection.
+ util::Optional<std::pair<asiolink::IOAddress, uint8_t> > subnet4o6_;
+
+ /// Specifies the v6 interface-id used for v4 subnet selection.
+ OptionPtr interface_id_;
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif
diff --git a/src/lib/dhcpsrv/cfg_consistency.cc b/src/lib/dhcpsrv/cfg_consistency.cc
new file mode 100644
index 0000000..3756d1e
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_consistency.cc
@@ -0,0 +1,60 @@
+// Copyright (C) 2018-2022 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 <dhcpsrv/cfg_consistency.h>
+#include <cc/data.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+isc::data::ElementPtr CfgConsistency::toElement() const {
+ ElementPtr m(new MapElement());
+ ElementPtr l(new StringElement(sanityCheckToText(getLeaseSanityCheck())));
+ m->set("lease-checks", l);
+ ElementPtr x(new StringElement(sanityCheckToText(getExtendedInfoSanityCheck())));
+ m->set("extended-info-checks", x);
+
+ return (m);
+}
+
+std::string CfgConsistency::sanityCheckToText(LeaseSanity check_type) {
+ switch (check_type) {
+ case LEASE_CHECK_NONE:
+ return ("none");
+ case LEASE_CHECK_WARN:
+ return ("warn");
+ case LEASE_CHECK_FIX:
+ return ("fix");
+ case LEASE_CHECK_FIX_DEL:
+ return ("fix-del");
+ case LEASE_CHECK_DEL:
+ return ("del");
+ default:
+ return ("unknown");
+ }
+}
+
+std::string CfgConsistency::sanityCheckToText(ExtendedInfoSanity check_type) {
+ switch (check_type) {
+ case EXTENDED_INFO_CHECK_NONE:
+ return ("none");
+ case EXTENDED_INFO_CHECK_FIX:
+ return ("fix");
+ case EXTENDED_INFO_CHECK_STRICT:
+ return ("strict");
+ case EXTENDED_INFO_CHECK_PEDANTIC:
+ return ("pedantic");
+ default:
+ return ("unknown");
+ }
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/cfg_consistency.h b/src/lib/dhcpsrv/cfg_consistency.h
new file mode 100644
index 0000000..a05f004
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_consistency.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2018-2022 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 CFG_CONSISTENCY_H
+#define CFG_CONSISTENCY_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+
+namespace isc {
+namespace dhcp {
+
+
+/// @brief Parameters for various consistency checks.
+///
+class CfgConsistency : public isc::data::UserContext, public isc::data::CfgToElement {
+
+ public:
+
+ /// @brief Values for subnet-id sanity checks done for leases.
+ enum LeaseSanity {
+ LEASE_CHECK_NONE, // Skip sanity checks
+ LEASE_CHECK_WARN, // Print a warning if subnet-id is incorrect.
+ LEASE_CHECK_FIX, // If subnet-id is incorrect, try to fix it (try to pick
+ // appropriate subnet, but if it fails, keep the lease,
+ // despite its broken subnet-id.
+ LEASE_CHECK_FIX_DEL, // If subnet-id is incorrect, try to fix it (try to pick
+ // appropriate subnet. If it fails, delete broken lease.
+ LEASE_CHECK_DEL // Delete leases with invalid subnet-id.
+ };
+
+ /// @brief Values for extended info sanity checks done for leases.
+ enum ExtendedInfoSanity {
+ EXTENDED_INFO_CHECK_NONE, // Skip sanity checks.
+ EXTENDED_INFO_CHECK_FIX, // Fix extended info common inconsistencies.
+ EXTENDED_INFO_CHECK_STRICT, // Fix extended info inconsistencies which
+ // have an impact for Bulk Lease Query.
+ EXTENDED_INFO_CHECK_PEDANTIC // Fix all extended info inconsistencies.
+ };
+
+ /// @brief Constructor
+ CfgConsistency()
+ : lease_sanity_check_(LEASE_CHECK_NONE),
+ extended_info_sanity_check_(EXTENDED_INFO_CHECK_FIX) {
+ }
+
+ /// @brief Returns JSON representation
+ ///
+ /// @return Element pointer
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Sets specific sanity checks mode for leases.
+ ///
+ /// @param l sanity checks mode
+ void setLeaseSanityCheck(LeaseSanity l) {
+ lease_sanity_check_ = l;
+ }
+
+ /// @brief Returns specific sanity checks mode for leases.
+ ///
+ /// @return sanity checks mode
+ LeaseSanity getLeaseSanityCheck() const {
+ return (lease_sanity_check_);
+ }
+
+ /// @brief Converts lease sanity check value to printable text.
+ ///
+ /// @param check_type sanity mode to be converted
+ static std::string sanityCheckToText(LeaseSanity check_type);
+
+ /// @brief Sets specific sanity checks mode for extended info.
+ ///
+ /// @param l sanity checks mode
+ void setExtendedInfoSanityCheck(ExtendedInfoSanity l) {
+ extended_info_sanity_check_ = l;
+ }
+
+ /// @brief Returns specific sanity checks mode for extended info.
+ ///
+ /// @return sanity checks mode
+ ExtendedInfoSanity getExtendedInfoSanityCheck() const {
+ return (extended_info_sanity_check_);
+ }
+
+ /// @brief Converts extended info sanity check value to printable text.
+ ///
+ /// @param check_type sanity mode to be converted
+ static std::string sanityCheckToText(ExtendedInfoSanity check_type);
+
+ private:
+
+ /// @brief lease sanity checks mode.
+ LeaseSanity lease_sanity_check_;
+
+ /// @brief extended info sanity checks mode.
+ ExtendedInfoSanity extended_info_sanity_check_;
+};
+
+/// @brief Type used to for pointing to CfgConsistency structure
+typedef boost::shared_ptr<CfgConsistency> CfgConsistencyPtr;
+
+/// @brief Type used to for pointing to const CfgConsistency structure
+typedef boost::shared_ptr<const CfgConsistency> ConstCfgConsistencyPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif /* CFG_CONSISTENCY_H */
diff --git a/src/lib/dhcpsrv/cfg_db_access.cc b/src/lib/dhcpsrv/cfg_db_access.cc
new file mode 100644
index 0000000..e38f234
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_db_access.cc
@@ -0,0 +1,108 @@
+// Copyright (C) 2016-2023 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 <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/db_type.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+#include <vector>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+CfgDbAccess::CfgDbAccess()
+ : appended_parameters_(), lease_db_access_("type=memfile"),
+ host_db_access_(), ip_reservations_unique_(true),
+ extended_info_tables_enabled_(false) {
+}
+
+std::string
+CfgDbAccess::getLeaseDbAccessString() const {
+ return (getAccessString(lease_db_access_));
+}
+
+
+std::string
+CfgDbAccess::getHostDbAccessString() const {
+ if (host_db_access_.empty()) {
+ return ("");
+ } else {
+ return (getAccessString(host_db_access_.front()));
+ }
+}
+
+std::list<std::string>
+CfgDbAccess::getHostDbAccessStringList() const {
+ std::list<std::string> ret;
+ for (const std::string& dbaccess : host_db_access_) {
+ if (!dbaccess.empty()) {
+ ret.push_back(getAccessString(dbaccess));
+ }
+ }
+ return (ret);
+}
+
+void
+CfgDbAccess::createManagers() const {
+ // Recreate lease manager without preserving the registered callbacks.
+ LeaseMgrFactory::recreate(getLeaseDbAccessString(), false);
+
+ // Recreate host data source.
+ HostMgr::create();
+
+ // Restore the host cache.
+ if (HostDataSourceFactory::registeredFactory("cache")) {
+ HostMgr::addBackend("type=cache");
+ }
+
+ // Add database backends.
+ std::list<std::string> host_db_access_list = getHostDbAccessStringList();
+ for (std::string& hds : host_db_access_list) {
+ HostMgr::addBackend(hds);
+ }
+
+ // Check for a host cache.
+ HostMgr::checkCacheBackend(true);
+
+ // Populate the ip-reservations-unique global setting to HostMgr.
+ // This operation may fail if any of the host backends does not support
+ // the new setting. We throw an exception here to signal configuration
+ // error. The exception does not contain the backend name but the called
+ // function in HostMgr logs a warning message that contains the name of
+ // the backend.
+ if (!HostMgr::instance().setIPReservationsUnique(ip_reservations_unique_)) {
+ isc_throw(InvalidOperation, "unable to configure the server to allow "
+ "non unique IP reservations (ip-reservations-unique=false) "
+ "because some host backends in use do not support this "
+ "setting");
+ }
+}
+
+std::string
+CfgDbAccess::getAccessString(const std::string& access_string) const {
+ std::ostringstream s;
+ s << access_string;
+ // Only append additional parameters if any parameters are specified
+ // in a configuration. For host database, no parameters mean that
+ // database access is disabled and thus we don't want to append any
+ // parameters.
+ if ((s.tellp() != std::streampos(0)) && (!appended_parameters_.empty())) {
+ s << " " << appended_parameters_;
+ }
+
+ return (s.str());
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/cfg_db_access.h b/src/lib/dhcpsrv/cfg_db_access.h
new file mode 100644
index 0000000..b498357
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_db_access.h
@@ -0,0 +1,196 @@
+// Copyright (C) 2016-2022 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 CFG_DBACCESS_H
+#define CFG_DBACCESS_H
+
+#include <cc/cfg_to_element.h>
+#include <database/database_connection.h>
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <list>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds access parameters and the configuration of the
+/// lease and hosts database connection.
+///
+/// The database access strings use the same format as the strings
+/// passed to the @ref isc::dhcp::LeaseMgrFactory::create function.
+class CfgDbAccess {
+public:
+ /// @brief Constructor.
+ CfgDbAccess();
+
+ /// @brief Sets parameters which will be appended to the database
+ /// access strings.
+ ///
+ /// @param appended_parameters String holding collection of parameters
+ /// in the following format: "parameter0=value0 parameter1=value1 ...".
+ void setAppendedParameters(const std::string& appended_parameters) {
+ appended_parameters_ = appended_parameters;
+ }
+
+ /// @brief Retrieves lease database access string.
+ ///
+ /// @return Lease database access string with additional parameters
+ /// specified with @ref CfgDbAccess::setAppendedParameters.
+ std::string getLeaseDbAccessString() const;
+
+ /// @brief Sets lease database access string.
+ ///
+ /// @param lease_db_access New lease database access string.
+ void setLeaseDbAccessString(const std::string& lease_db_access) {
+ lease_db_access_ = lease_db_access;
+ }
+
+ /// @brief Retrieves host database access string.
+ ///
+ /// @return Host database access string with additional parameters
+ /// specified with @ref CfgDbAccess::setAppendedParameters.
+ std::string getHostDbAccessString() const;
+
+ /// @brief Sets host database access string.
+ ///
+ /// @param host_db_access New host database access string.
+ /// @param front Add at front if true, at back if false (default).
+ void setHostDbAccessString(const std::string& host_db_access,
+ bool front = false) {
+ if (front) {
+ host_db_access_.push_front(host_db_access);
+ } else {
+ host_db_access_.push_back(host_db_access);
+ }
+ }
+
+ /// @brief Retrieves host database access string.
+ ///
+ /// @return Database access strings with additional parameters
+ /// specified with @ref CfgDbAccess::setAppendedParameters
+ std::list<std::string> getHostDbAccessStringList() const;
+
+ /// @brief Modifies the setting imposing whether the IP reservations
+ /// are unique or can be non unique.
+ ///
+ /// This flag can be set to @c false when the server is explicitly
+ /// configured to allow multiple hosts to have the same IP reservation
+ /// in a subnet. In that case, the @c createManagers function will
+ /// attempt to use this setting for @c HostMgr.
+ ///
+ /// Note that the @c HostMgr can reject the new setting if any of the
+ /// host backends used does not support specifying multipe hosts with
+ /// the same IP address in a subnet.
+ ///
+ /// @param unique new setting to be used by @c HostMgr.
+ void setIPReservationsUnique(const bool unique) {
+ ip_reservations_unique_ = unique;
+ }
+
+ /// @brief Returns the setting indicating if the IP reservations are
+ /// unique or can be non unique.
+ ///
+ /// @return true if the IP reservations must be unique or false if
+ /// the reservations can be non unique.
+ bool getIPReservationsUnique() const {
+ return (ip_reservations_unique_);
+ }
+
+ /// @brief Modifies the setting whether the lease extended info tables
+ /// are enabled.
+ ///
+ /// @param enabled new setting to be used by @c LeaseMgr.
+ void setExtendedInfoTablesEnabled(const bool enabled) {
+ extended_info_tables_enabled_ = enabled;
+ }
+
+ /// @brief Returns the setting indicating if lease extended info tables
+ /// are enabled.
+ ///
+ /// @return true if lease extended info tables are enabled or false
+ /// if they are disabled.
+ bool getExtendedInfoTablesEnabled() const {
+ return (extended_info_tables_enabled_);
+ }
+
+ /// @brief Creates instance of lease manager and host data sources
+ /// according to the configuration specified.
+ void createManagers() const;
+
+protected:
+
+ /// @brief Returns lease or host database access string.
+ ///
+ /// @param access_string without additional (appended) parameters.
+ std::string getAccessString(const std::string& access_string) const;
+
+ /// @brief Parameters to be appended to the database access
+ /// strings.
+ std::string appended_parameters_;
+
+ /// @brief Holds lease database access string.
+ std::string lease_db_access_;
+
+ /// @brief Holds host database access strings.
+ std::list<std::string> host_db_access_;
+
+ /// @brief Holds the setting whether IP reservations should be unique
+ /// or can be non-unique.
+ bool ip_reservations_unique_;
+
+ /// @brief Holds the setting whether the lease extended info tables
+ /// are enabled or disabled. The default is disabled.
+ bool extended_info_tables_enabled_;
+};
+
+/// @brief A pointer to the @c CfgDbAccess.
+typedef boost::shared_ptr<CfgDbAccess> CfgDbAccessPtr;
+
+/// @brief A pointer to the const @c CfgDbAccess.
+typedef boost::shared_ptr<const CfgDbAccess> ConstCfgDbAccessPtr;
+
+/// @brief utility class for unparsing
+struct CfgLeaseDbAccess : public CfgDbAccess, public isc::data::CfgToElement {
+ /// @brief Constructor
+ CfgLeaseDbAccess(const CfgDbAccess& super) : CfgDbAccess(super) { }
+
+ /// @brief Unparse
+ ///
+ /// @ref isc::data::CfgToElement::toElement
+ ///
+ /// @result a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const {
+ return (db::DatabaseConnection::toElementDbAccessString(lease_db_access_));
+ }
+};
+
+struct CfgHostDbAccess : public CfgDbAccess, public isc::data::CfgToElement {
+ /// @brief Constructor
+ CfgHostDbAccess(const CfgDbAccess& super) : CfgDbAccess(super) { }
+
+ /// @brief Unparse
+ ///
+ /// @ref isc::data::CfgToElement::toElement
+ ///
+ /// @result a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const {
+ isc::data::ElementPtr result = isc::data::Element::createList();
+ for (const std::string& dbaccess : host_db_access_) {
+ isc::data::ElementPtr entry =
+ db::DatabaseConnection::toElementDbAccessString(dbaccess);
+ if (entry->size() > 0) {
+ result->add(entry);
+ }
+ }
+ return (result);
+ }
+};
+
+}
+}
+
+#endif // CFG_DBACCESS_H
diff --git a/src/lib/dhcpsrv/cfg_duid.cc b/src/lib/dhcpsrv/cfg_duid.cc
new file mode 100644
index 0000000..fe17f20
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_duid.cc
@@ -0,0 +1,122 @@
+// Copyright (C) 2015-2020 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 <dhcp/duid_factory.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <iostream>
+#include <string>
+#include <string.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::util::encode;
+using namespace isc::util::str;
+
+namespace isc {
+namespace dhcp {
+
+CfgDUID::CfgDUID()
+ : type_(DUID::DUID_LLT), identifier_(), htype_(0), time_(0),
+ enterprise_id_(0), persist_(true) {
+}
+
+void
+CfgDUID::setIdentifier(const std::string& identifier_as_hex) {
+ // Remove whitespaces.
+ const std::string identifier = trim(identifier_as_hex);
+ // Temporary result of parsing.
+ std::vector<uint8_t> binary;
+ if (!identifier.empty()) {
+ try {
+ // Decode identifier specified as a string of hexadecimal digits.
+ decodeHex(identifier, binary);
+ // All went ok, so let's replace identifier with a new value.
+ identifier_.swap(binary);
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "identifier specified in the DUID"
+ " configuration '" << identifier
+ << "' is not a valid string of hexadecimal digits");
+ }
+
+ } else {
+ // If specified identifier is empty, clear our internal identifier.
+ identifier_.clear();
+ }
+}
+
+DuidPtr
+CfgDUID::create(const std::string& duid_file_path) {
+ // Forget the current DUID.
+ current_duid_.reset();
+
+ // Use DUID factory to create a DUID instance.
+ DUIDFactory factory(persist() ? duid_file_path : "");
+
+ switch (getType()) {
+ case DUID::DUID_LLT:
+ factory.createLLT(getHType(), getTime(), getIdentifier());
+ break;
+ case DUID::DUID_EN:
+ factory.createEN(getEnterpriseId(), getIdentifier());
+ break;
+ case DUID::DUID_LL:
+ factory.createLL(getHType(), getIdentifier());
+ break;
+ default:
+ // This should actually never happen.
+ isc_throw(Unexpected, "invalid DUID type used " << getType()
+ << " to create a new DUID");
+ }
+
+ // Save the newly created DUID.
+ current_duid_ = factory.get();
+
+ // Return generated DUID.
+ return (current_duid_);
+}
+
+ElementPtr
+CfgDUID::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user context
+ contextToElement(result);
+ // The type item is required
+ std::string duid_type = "LLT";
+ switch (type_) {
+ case DUID::DUID_LLT:
+ break;
+ case DUID::DUID_EN:
+ duid_type = "EN";
+ break;
+ case DUID::DUID_LL:
+ duid_type = "LL";
+ break;
+ default:
+ isc_throw(ToElementError, "invalid DUID type: " << getType());
+ break;
+ }
+ result->set("type", Element::create(duid_type));
+ // Set the identifier
+ result->set("identifier",
+ Element::create(util::encode::encodeHex(identifier_)));
+ // Set the hardware type
+ result->set("htype", Element::create(htype_));
+ // Set the time
+ result->set("time", Element::create(static_cast<long long>(time_)));
+ // Set the enterprise id
+ result->set("enterprise-id",
+ Element::create(static_cast<long long>(enterprise_id_)));
+ // Set the persistence flag
+ result->set("persist", Element::create(persist_));
+ return (result);
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/cfg_duid.h b/src/lib/dhcpsrv/cfg_duid.h
new file mode 100644
index 0000000..6897ee6
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_duid.h
@@ -0,0 +1,171 @@
+// Copyright (C) 2015-2020 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 CFG_DUID_H
+#define CFG_DUID_H
+
+#include <dhcp/duid.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds manual configuration of the server identifier (DUID).
+///
+/// The DHCPv6 server uses DHCPv6 Unique Identifier (DUID) to identify itself
+/// to the clients. Typically, the server generates the DUID on the first
+/// startup and writes it to the persistent storage so as it doesn't change
+/// across restarts of the server. RFC 8415 defines different DUID types.
+/// Kea allows for selecting a type of DUID that the server should generate.
+/// It also allows for overriding entire default DUID or parts of it via
+/// configuration file. This class holds the DUID configuration specified
+/// in the server configuration file.
+class CfgDUID : public data::UserContext, public isc::data::CfgToElement {
+public:
+
+ /// @brief Constructor.
+ CfgDUID();
+
+ /// @brief Returns DUID type.
+ DUID::DUIDType getType() const {
+ return (type_);
+ }
+
+ /// @brief Sets DUID type.
+ void setType(const DUID::DUIDType& type) {
+ type_ = type;
+ }
+
+ /// @brief Returns identifier.
+ ///
+ /// Identifier is a link layer address for the DUID-LLT and DUID-LL. It
+ /// is also a variable length identifier in DUID-EN. It may be used for
+ /// all other existing and future DUID types when there is a need to
+ /// represent some variable length identifier.
+ ///
+ /// @return Vector holding an identifier belonging to a particular
+ /// DUID type.
+ std::vector<uint8_t> getIdentifier() const {
+ return (identifier_);
+ }
+
+ /// @brief Sets new identifier as hex string.
+ ///
+ /// @param identifier_as_hex String of hexadecimal digits representing
+ /// variable length identifier within a DUID.
+ void setIdentifier(const std::string& identifier_as_hex);
+
+ /// @brief Returns hardware type for DUID-LLT and DUID-LL.
+ uint16_t getHType() const {
+ return (htype_);
+ }
+
+ /// @brief Sets new hardware type for DUID-LLT and DUID-LL.
+ void setHType(const uint16_t htype) {
+ htype_ = htype;
+ }
+
+ /// @brief Returns time for the DUID-LLT.
+ uint32_t getTime() const {
+ return (time_);
+ }
+
+ /// @brief Sets new time for DUID-LLT.
+ void setTime(const uint32_t new_time) {
+ time_ = new_time;
+ }
+
+ /// @brief Returns enterprise id for the DUID-EN.
+ uint32_t getEnterpriseId() const {
+ return (enterprise_id_);
+ }
+
+ /// @brief Sets new enterprise id.
+ ///
+ /// @param enterprise_id New enterprise id.
+ void setEnterpriseId(const uint32_t enterprise_id) {
+ enterprise_id_ = enterprise_id;
+ }
+
+ /// @brief Checks if server identifier should be stored on disk.
+ ///
+ /// @return true if the server identifier should be stored on
+ /// the disk, false otherwise.
+ bool persist() const {
+ return (persist_);
+ }
+
+ /// @brief Sets a boolean flag indicating if the server identifier
+ /// should be stored on the disk (if true) or not (if false).
+ ///
+ /// @param persist New value of the flag.
+ void setPersist(const bool persist) {
+ persist_ = persist;
+ }
+
+ /// @brief Creates instance of a DUID from the current configuration.
+ ///
+ /// The newly created DUID is retained internally to make it accessible
+ /// anywhere.
+ ///
+ /// @param duid_file_path Absolute path to a DUID file.
+ /// @return Pointer to an instance of new DUID.
+ DuidPtr create(const std::string& duid_file_path);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Fetches the duid created by @ref create()
+ /// @return a pointer to created duid. Pointer will
+ /// empty if the duid has not yet been created.
+ const DuidPtr getCurrentDuid() const {
+ return (current_duid_);
+ }
+
+private:
+
+ /// @brief DUID type.
+ DUID::DUIDType type_;
+
+ /// @brief Variable length identifier in a DUID.
+ std::vector<uint8_t> identifier_;
+
+ /// @brief Hardware type.
+ uint16_t htype_;
+
+ /// @brief Time used for DUID-LLT.
+ uint32_t time_;
+
+ /// @brief Enterprise id used for DUID-EN.
+ uint32_t enterprise_id_;
+
+ /// @brief Boolean flag which indicates if server identifier should
+ /// be stored on the disk.
+ bool persist_;
+
+ DuidPtr current_duid_;
+};
+
+/// @name Pointers to the @c CfgDUID objects.
+//@{
+/// @brief Pointer to the Non-const object.
+typedef boost::shared_ptr<CfgDUID> CfgDUIDPtr;
+
+/// @brief Pointer to the const object.
+typedef boost::shared_ptr<const CfgDUID> ConstCfgDUIDPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_DUID_H
diff --git a/src/lib/dhcpsrv/cfg_expiration.cc b/src/lib/dhcpsrv/cfg_expiration.cc
new file mode 100644
index 0000000..2931740
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_expiration.cc
@@ -0,0 +1,141 @@
+// Copyright (C) 2015,2017 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 <dhcpsrv/cfg_expiration.h>
+#include <exceptions/exceptions.h>
+#include <limits>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+// Default values
+const uint16_t CfgExpiration::DEFAULT_RECLAIM_TIMER_WAIT_TIME = 10;
+const uint16_t CfgExpiration::DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 25;
+const uint32_t CfgExpiration::DEFAULT_HOLD_RECLAIMED_TIME = 3600;
+const uint32_t CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES = 100;
+const uint16_t CfgExpiration::DEFAULT_MAX_RECLAIM_TIME = 250;
+const uint16_t CfgExpiration::DEFAULT_UNWARNED_RECLAIM_CYCLES = 5;
+
+// Maximum values.
+const uint16_t CfgExpiration::LIMIT_RECLAIM_TIMER_WAIT_TIME =
+ std::numeric_limits<uint16_t>::max();
+const uint16_t CfgExpiration::LIMIT_FLUSH_RECLAIMED_TIMER_WAIT_TIME =
+ std::numeric_limits<uint16_t>::max();
+const uint32_t CfgExpiration::LIMIT_HOLD_RECLAIMED_TIME =
+ std::numeric_limits<uint32_t>::max();
+const uint32_t CfgExpiration::LIMIT_MAX_RECLAIM_LEASES =
+ std::numeric_limits<uint32_t>::max();
+const uint16_t CfgExpiration::LIMIT_MAX_RECLAIM_TIME = 10000;
+const uint16_t CfgExpiration::LIMIT_UNWARNED_RECLAIM_CYCLES =
+ std::numeric_limits<uint16_t>::max();
+
+// Timers' names
+const std::string CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME =
+ "reclaim-expired-leases";
+
+const std::string CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME =
+ "flush-reclaimed-leases";
+
+CfgExpiration::CfgExpiration(const bool test_mode)
+ : reclaim_timer_wait_time_(DEFAULT_RECLAIM_TIMER_WAIT_TIME),
+ flush_reclaimed_timer_wait_time_(DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME),
+ hold_reclaimed_time_(DEFAULT_HOLD_RECLAIMED_TIME),
+ max_reclaim_leases_(DEFAULT_MAX_RECLAIM_LEASES),
+ max_reclaim_time_(DEFAULT_MAX_RECLAIM_TIME),
+ unwarned_reclaim_cycles_(DEFAULT_UNWARNED_RECLAIM_CYCLES),
+ timer_mgr_(TimerMgr::instance()),
+ test_mode_(test_mode) {
+}
+
+void
+CfgExpiration::setReclaimTimerWaitTime(const int64_t reclaim_timer_wait_time) {
+ rangeCheck(reclaim_timer_wait_time, LIMIT_RECLAIM_TIMER_WAIT_TIME,
+ "reclaim-timer-wait-time");
+ reclaim_timer_wait_time_ = reclaim_timer_wait_time;
+}
+
+void
+CfgExpiration::setFlushReclaimedTimerWaitTime(const int64_t flush_reclaimed_wait_time) {
+ rangeCheck(flush_reclaimed_wait_time, LIMIT_FLUSH_RECLAIMED_TIMER_WAIT_TIME,
+ "flush-reclaimed-timer-wait-time");
+ flush_reclaimed_timer_wait_time_ = flush_reclaimed_wait_time;
+}
+
+void
+CfgExpiration::setHoldReclaimedTime(const int64_t hold_reclaimed_time) {
+ rangeCheck(hold_reclaimed_time, LIMIT_HOLD_RECLAIMED_TIME, "hold-reclaimed-time");
+ hold_reclaimed_time_ = hold_reclaimed_time;
+}
+
+void
+CfgExpiration::setMaxReclaimLeases(const int64_t max_reclaim_leases) {
+ rangeCheck(max_reclaim_leases, LIMIT_MAX_RECLAIM_LEASES, "max-reclaim-leases");
+ max_reclaim_leases_ = max_reclaim_leases;
+}
+
+void
+CfgExpiration::setMaxReclaimTime(const int64_t max_reclaim_time) {
+ rangeCheck(max_reclaim_time, LIMIT_MAX_RECLAIM_TIME, "max-reclaim-time");
+ max_reclaim_time_ = max_reclaim_time;
+}
+
+void
+CfgExpiration::setUnwarnedReclaimCycles(const int64_t unwarned_reclaim_cycles) {
+ rangeCheck(unwarned_reclaim_cycles, LIMIT_UNWARNED_RECLAIM_CYCLES,
+ "unwarned-reclaim-cycles");
+ unwarned_reclaim_cycles_ = unwarned_reclaim_cycles;
+}
+
+void
+CfgExpiration::rangeCheck(const int64_t value, const uint64_t max_value,
+ const std::string& config_parameter_name) const {
+ if (value < 0) {
+ isc_throw(OutOfRange, "value for configuration parameter '"
+ << config_parameter_name << "' must not be negative");
+
+ } else if (value > max_value) {
+ isc_throw(OutOfRange, "out range value '" << value << "' for configuration"
+ " parameter '" << config_parameter_name << "', expected maximum"
+ " value of '" << max_value << "'");
+ }
+}
+
+ElementPtr
+CfgExpiration::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set reclaim-timer-wait-time
+ result->set("reclaim-timer-wait-time",
+ Element::create(static_cast<long long>
+ (reclaim_timer_wait_time_)));
+ // Set flush-reclaimed-timer-wait-time
+ result->set("flush-reclaimed-timer-wait-time",
+ Element::create(static_cast<long long>
+ (flush_reclaimed_timer_wait_time_)));
+ // Set hold-reclaimed-time
+ result->set("hold-reclaimed-time",
+ Element::create(static_cast<long long>
+ (hold_reclaimed_time_)));
+ // Set max-reclaim-leases
+ result->set("max-reclaim-leases",
+ Element::create(static_cast<long long>
+ (max_reclaim_leases_)));
+ // Set max-reclaim-time
+ result->set("max-reclaim-time",
+ Element::create(static_cast<long long>
+ (max_reclaim_time_)));
+ // Set unwarned-reclaim-cycles
+ result->set("unwarned-reclaim-cycles",
+ Element::create(static_cast<long long>
+ (unwarned_reclaim_cycles_)));
+ return (result);
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/cfg_expiration.h b/src/lib/dhcpsrv/cfg_expiration.h
new file mode 100644
index 0000000..4dac5ce
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_expiration.h
@@ -0,0 +1,338 @@
+// Copyright (C) 2015-2022 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 CFG_EXPIRATION_H
+#define CFG_EXPIRATION_H
+
+#include <asiolink/interval_timer.h>
+#include <cc/cfg_to_element.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds configuration parameters pertaining to lease expiration
+/// and lease affinity.
+///
+/// This class holds the values of the following configuration parameters:
+///
+/// - reclaim-timer-wait-time - is the time between two cycles of processing
+/// expired leases, expressed in seconds, i.e. the time between the end of
+/// one cycle and the beginning of the next cycle. If this value is 0, the
+/// expired leases are not processed.
+///
+/// - flush-reclaimed-timer-wait-time - is the time between two cycles of
+/// recycling "expired-reclaimed" leases, expressed in seconds. If this
+/// value is 0, the expired leases are removed by the leases reclamation
+/// routine rather than recycling function. The recycling function is not
+/// executed and the value of the "hold-reclaimed-time" is ignored.
+///
+/// - hold-reclaimed-time -is the time for which "expired-reclaimed" leases
+/// are held in the lease database in the "expired-reclaimed" state after
+/// they expire. If this time is set to 0, the recycling function is not
+/// executed and the value of the "recycle-timer-wait-time" is ignored.
+/// This value is expressed in seconds.
+///
+/// - max-reclaim-leases - is the maximum number of leases to be processed
+/// in a single cycle. If this value is 0, all expired leases are
+/// processed in a single cycle, unless the maximum processing time
+/// (configured with the "max-time") parameter elapses first.
+///
+/// - max-reclaim-time - the maximum time that a single processing cycle
+/// may last, expressed in milliseconds. If this value is 0, there is no
+/// limitation for the maximum processing time. This value is expressed
+/// in milliseconds.
+///
+/// - unwarned-reclaim-cycles - is the number of consecutive processing
+/// cycles of expired leases, after which the system issues a warning if
+/// there are still expired leases in the database. If this value is 0,
+/// the warning is never issued.
+///
+/// The @c CfgExpiration class provides a collection of accessors and
+/// modifiers to manage the data. Each accessor checks if the given value
+/// is in range allowed for this value.
+class CfgExpiration : public isc::data::CfgToElement {
+public:
+
+ /// @name Default values.
+ ///@{
+ ///
+ /// @brief Default value for reclaim-timer-wait-time.
+ static const uint16_t DEFAULT_RECLAIM_TIMER_WAIT_TIME;
+
+ /// @brief Default value for flush-reclaimed-timer-wait-time.
+ static const uint16_t DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME;
+
+ /// @brief Default value for hold-reclaimed-time.
+ static const uint32_t DEFAULT_HOLD_RECLAIMED_TIME;
+
+ /// @brief Default value for max-reclaim-leases.
+ static const uint32_t DEFAULT_MAX_RECLAIM_LEASES;
+
+ /// @brief Default value for max-reclaim-time.
+ static const uint16_t DEFAULT_MAX_RECLAIM_TIME;
+
+ /// @brief Default value for unwarned-reclaim-cycles.
+ static const uint16_t DEFAULT_UNWARNED_RECLAIM_CYCLES;
+
+ ///@}
+
+ /// @name Upper limits for the parameters
+ ///@{
+ ///
+ /// @brief Maximum value for reclaim-timer-wait-time.
+ static const uint16_t LIMIT_RECLAIM_TIMER_WAIT_TIME;
+
+ /// @brief Maximum value for flush-reclaimed-timer-wait-time.
+ static const uint16_t LIMIT_FLUSH_RECLAIMED_TIMER_WAIT_TIME;
+
+ /// @brief Maximum value for hold-reclaimed-time.
+ static const uint32_t LIMIT_HOLD_RECLAIMED_TIME;
+
+ /// @brief Maximum value for max-reclaim-leases.
+ static const uint32_t LIMIT_MAX_RECLAIM_LEASES;
+
+ /// @brief Default value for max-reclaim-time.
+ static const uint16_t LIMIT_MAX_RECLAIM_TIME;
+
+ /// @brief Maximum value for unwarned-reclaim-cycles.
+ static const uint16_t LIMIT_UNWARNED_RECLAIM_CYCLES;
+
+ ///@}
+
+ /// @name Timers' names
+ ///@{
+
+ /// @brief Name of the timer for reclaiming expired leases.
+ static const std::string RECLAIM_EXPIRED_TIMER_NAME;
+
+ /// @brief Name of the timer for flushing reclaimed leases.
+ static const std::string FLUSH_RECLAIMED_TIMER_NAME;
+
+ ///@}
+
+ /// @brief Constructor.
+ ///
+ /// Sets all parameters to their defaults.
+ ///
+ /// @param test_mode Indicates if the instance should be created in the
+ /// test mode. In this mode the intervals for the timers are considered to
+ /// be specified in milliseconds, rather than seconds. This facilitates
+ /// testing execution of timers without the delays.
+ CfgExpiration(const bool test_mode = false);
+
+ /// @brief Returns reclaim-timer-wait-time
+ uint16_t getReclaimTimerWaitTime() const {
+ return (reclaim_timer_wait_time_);
+ }
+
+ /// @brief Sets reclaim-timer-wait-time
+ ///
+ /// @param reclaim_timer_wait_time New value.
+ void setReclaimTimerWaitTime(const int64_t reclaim_timer_wait_time);
+
+ /// @brief Returns flush-reclaimed-timer-wait-time.
+ uint16_t getFlushReclaimedTimerWaitTime() const {
+ return (flush_reclaimed_timer_wait_time_);
+ }
+
+ /// @brief Sets flush-reclaimed-timer-wait-time.
+ ///
+ /// @param flush_reclaimed_wait_time New value.
+ void setFlushReclaimedTimerWaitTime(const int64_t flush_reclaimed_wait_time);
+
+ /// @brief Returns hold-reclaimed-time.
+ uint32_t getHoldReclaimedTime() const {
+ return (hold_reclaimed_time_);
+ }
+
+ /// @brief Sets hold-reclaimed-time
+ ///
+ /// @param hold_reclaimed_time New value.
+ void setHoldReclaimedTime(const int64_t hold_reclaimed_time);
+
+ /// @brief Returns max-reclaim-leases.
+ uint32_t getMaxReclaimLeases() const {
+ return (max_reclaim_leases_);
+ }
+
+ /// @brief Sets max-reclaim-leases.
+ ///
+ /// @param max_reclaim_leases New value.
+ void setMaxReclaimLeases(const int64_t max_reclaim_leases);
+
+ /// @brief Returns max-reclaim-time.
+ uint16_t getMaxReclaimTime() const {
+ return (max_reclaim_time_);
+ }
+
+ /// @brief Sets max-reclaim-time.
+ ///
+ /// @param max_reclaim_time New value.
+ void setMaxReclaimTime(const int64_t max_reclaim_time);
+
+ /// @brief Returns unwarned-reclaim-cycles.
+ uint16_t getUnwarnedReclaimCycles() const {
+ return (unwarned_reclaim_cycles_);
+ }
+
+ /// @brief Sets unwarned-reclaim-cycles.
+ ///
+ /// @param unwarned_reclaim_cycles New value.
+ void setUnwarnedReclaimCycles(const int64_t unwarned_reclaim_cycles);
+
+ /// @brief Setup timers for the reclamation of expired leases according
+ /// to the configuration parameters.
+ ///
+ /// This method includes the logic for setting the interval timers
+ /// performing the reclamation of the expired leases and the removal
+ /// of expired-reclaimed leases.
+ ///
+ /// The following is the sample code illustrating how to call this function
+ /// to setup the leases reclamation for the DHCPv4 server.
+ /// @code
+ /// CfgExpiration cfg;
+ ///
+ /// (set some cfg values here)
+ ///
+ /// AllocEnginePtr alloc_engine(new AllocEngine(...));
+ /// cfg.setupTimers(&AllocEngine::reclaimExpiredLeases4,
+ /// &AllocEngine::deleteExpiredReclaimedLeases4,
+ /// alloc_engine.get());
+ /// @endcode
+ ///
+ /// @param reclaim_fun Pointer to the leases reclamation routine.
+ /// @param delete_fun Pointer to the function which removes the
+ /// expired-reclaimed leases from the lease database.
+ /// @param instance_ptr Pointer to the instance of the object which
+ /// implements the lease reclamation routine. Typically it will be
+ /// the pointer to the @c AllocEngine. In case of unit tests it
+ /// will be a pointer to some test class which provides stub
+ /// implementation of the leases reclamation routines.
+ /// @tparam Instance Instance of the object in which both functions
+ /// are implemented.
+ template<typename Instance>
+ void setupTimers(void (Instance::*reclaim_fun)(const size_t, const uint16_t,
+ const bool, const uint16_t),
+ void (Instance::*delete_fun)(const uint32_t),
+ Instance* instance_ptr) const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief Checks if the value being set by one of the modifiers is
+ /// within an allowed range.
+ ///
+ /// @param value Value to be checked.
+ /// @param max_value Maximum allowed value.
+ /// @param config_parameter_name A name of the configuration parameter
+ /// (used for logging purposes if value is out of range).
+ ///
+ /// @throw isc::OutOfRange if the value is negative or greater than
+ /// the maximum value.
+ void rangeCheck(const int64_t value, const uint64_t max_value,
+ const std::string& config_parameter_name) const;
+
+ /// @brief reclaim-timer-wait-time
+ uint16_t reclaim_timer_wait_time_;
+
+ /// @brief flush-reclaimed-timer-wait-time
+ uint16_t flush_reclaimed_timer_wait_time_;
+
+ /// @brief hold-reclaimed-time
+ uint32_t hold_reclaimed_time_;
+
+ /// @brief max-reclaim-leases
+ uint32_t max_reclaim_leases_;
+
+ /// @brief max-reclaim-time
+ uint16_t max_reclaim_time_;
+
+ /// @brief unwarned-reclaim-cycles.
+ uint16_t unwarned_reclaim_cycles_;
+
+ /// @brief Pointer to the instance of the Timer Manager.
+ TimerMgrPtr timer_mgr_;
+
+ /// @brief Indicates if the instance is in the test mode.
+ bool test_mode_;
+};
+
+/// @name Pointers to the @c CfgExpiration objects.
+///@{
+/// @brief Pointer to the Non-const object.
+typedef boost::shared_ptr<CfgExpiration> CfgExpirationPtr;
+
+/// @brief Pointer to the const object.
+typedef boost::shared_ptr<const CfgExpiration> ConstCfgExpirationPtr;
+
+///@}
+
+template<typename Instance>
+void
+CfgExpiration::setupTimers(void (Instance::*reclaim_fun)(const size_t,
+ const uint16_t,
+ const bool,
+ const uint16_t),
+ void (Instance::*delete_fun)(const uint32_t),
+ Instance* instance_ptr) const {
+ // One of the parameters passed to the leases' reclamation routine
+ // is a boolean value which indicates if reclaimed leases should
+ // be removed by the leases' reclamation routine. This is the case
+ // when the timer for flushing reclaimed leases is set to 0
+ // (disabled).
+ const bool flush_timer_disabled = (getFlushReclaimedTimerWaitTime() == 0);
+
+ // If the timer interval for the leases reclamation is non-zero
+ // the timer will be scheduled.
+ if (getReclaimTimerWaitTime() > 0) {
+ // In the test mode the interval is expressed in milliseconds.
+ // If this is not the test mode, the interval is in seconds.
+ const long reclaim_interval = test_mode_ ? getReclaimTimerWaitTime() :
+ 1000 * getReclaimTimerWaitTime();
+ // Register timer for leases' reclamation routine.
+ timer_mgr_->registerTimer(RECLAIM_EXPIRED_TIMER_NAME,
+ std::bind(reclaim_fun, instance_ptr,
+ getMaxReclaimLeases(),
+ getMaxReclaimTime(),
+ flush_timer_disabled,
+ getUnwarnedReclaimCycles()),
+ reclaim_interval,
+ asiolink::IntervalTimer::ONE_SHOT);
+ timer_mgr_->setup(RECLAIM_EXPIRED_TIMER_NAME);
+ }
+
+ // If the interval for the timer flushing expired-reclaimed leases
+ // is set we will schedule the timer.
+ if (!flush_timer_disabled) {
+ // The interval is specified in milliseconds if we're in the test mode.
+ // It is specified in seconds otherwise.
+ const long flush_interval = test_mode_ ?
+ getFlushReclaimedTimerWaitTime() :
+ 1000 * getFlushReclaimedTimerWaitTime();
+ // Register and setup the timer.
+ timer_mgr_->registerTimer(FLUSH_RECLAIMED_TIMER_NAME,
+ std::bind(delete_fun, instance_ptr,
+ getHoldReclaimedTime()),
+ flush_interval,
+ asiolink::IntervalTimer::ONE_SHOT);
+ timer_mgr_->setup(FLUSH_RECLAIMED_TIMER_NAME);
+ }
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // CFG_EXPIRATION_H
diff --git a/src/lib/dhcpsrv/cfg_globals.cc b/src/lib/dhcpsrv/cfg_globals.cc
new file mode 100644
index 0000000..a8083e0
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_globals.cc
@@ -0,0 +1,186 @@
+// Copyright (C) 2021-2023 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 <dhcpsrv/cfg_globals.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+const std::map<std::string, int>
+CfgGlobals::nameToIndex = {
+ // Common parameters.
+ { "valid-lifetime", VALID_LIFETIME },
+ { "min-valid-lifetime", MIN_VALID_LIFETIME },
+ { "max-valid-lifetime", MAX_VALID_LIFETIME },
+ { "renew-timer", RENEW_TIMER },
+ { "rebind-timer", REBIND_TIMER },
+ { "decline-probation-period", DECLINE_PROBATION_PERIOD },
+ { "dhcp4o6-port", DHCP4O6_PORT },
+ { "comment", COMMENT },
+ { "server-tag", SERVER_TAG },
+ { "reservation-mode", RESERVATION_MODE },
+ { "reservations-global", RESERVATIONS_GLOBAL },
+ { "reservations-in-subnet", RESERVATIONS_IN_SUBNET },
+ { "reservations-out-of-pool", RESERVATIONS_OUT_OF_POOL },
+ { "calculate-tee-times", CALCULATE_TEE_TIMES },
+ { "t1-percent", T1_PERCENT },
+ { "t2-percent", T2_PERCENT },
+ { "hostname-char-set", HOSTNAME_CHAR_SET },
+ { "hostname-char-replacement", HOSTNAME_CHAR_REPLACEMENT },
+ { "ddns-send-updates", DDNS_SEND_UPDATES },
+ { "ddns-override-no-update", DDNS_OVERRIDE_NO_UPDATE },
+ { "ddns-override-client-update", DDNS_OVERRIDE_CLIENT_UPDATE },
+ { "ddns-replace-client-name", DDNS_REPLACE_CLIENT_NAME },
+ { "ddns-generated-prefix", DDNS_GENERATED_PREFIX },
+ { "ddns-qualifying-suffix", DDNS_QUALIFYING_SUFFIX },
+ { "store-extended-info", STORE_EXTENDED_INFO },
+ { "statistic-default-sample-count", STATISTIC_DEFAULT_SAMPLE_COUNT },
+ { "statistic-default-sample-age", STATISTIC_DEFAULT_SAMPLE_AGE },
+ { "cache-threshold", CACHE_THRESHOLD },
+ { "cache-max-age", CACHE_MAX_AGE },
+ { "early-global-reservations-lookup", EARLY_GLOBAL_RESERVATIONS_LOOKUP },
+ { "ip-reservations-unique", IP_RESERVATIONS_UNIQUE },
+ { "reservations-lookup-first", RESERVATIONS_LOOKUP_FIRST },
+ { "ddns-update-on-renew", DDNS_UPDATE_ON_RENEW },
+ { "ddns-use-conflict-resolution", DDNS_USE_CONFLICT_RESOLUTION },
+ { "parked-packet-limit", PARKED_PACKET_LIMIT },
+ { "allocator", ALLOCATOR },
+ { "ddns-ttl-percent", DDNS_TTL_PERCENT },
+
+ // DHCPv4 specific parameters.
+ { "echo-client-id", ECHO_CLIENT_ID },
+ { "match-client-id", MATCH_CLIENT_ID },
+ { "authoritative", AUTHORITATIVE },
+ { "next-server", NEXT_SERVER },
+ { "server-hostname", SERVER_HOSTNAME },
+ { "boot-file-name", BOOT_FILE_NAME },
+ { "offer-lifetime", OFFER_LIFETIME },
+
+ // DHCPv6 specific parameters.
+ { "data-directory", DATA_DIRECTORY },
+ { "preferred-lifetime", PREFERRED_LIFETIME },
+ { "min-preferred-lifetime", MIN_PREFERRED_LIFETIME },
+ { "max-preferred-lifetime", MAX_PREFERRED_LIFETIME },
+ { "pd-allocator", PD_ALLOCATOR }
+};
+
+// Load time sanity check.
+namespace {
+struct CfgGlobalsChecks {
+ CfgGlobalsChecks() {
+ // Check the size for missing entries.
+ if (CfgGlobals::nameToIndex.size() != CfgGlobals::SIZE) {
+ isc_throw(Unexpected, "CfgGlobals::nameToIndex has "
+ << CfgGlobals::nameToIndex.size()
+ << " elements (expected " << CfgGlobals::SIZE << ")");
+ }
+
+ // Build the name vector.
+ std::vector<std::string> names;
+ names.resize(CfgGlobals::SIZE);
+ for (auto it = CfgGlobals::nameToIndex.cbegin();
+ it != CfgGlobals::nameToIndex.cend(); ++it) {
+ int idx = it->second;
+ if ((idx < 0) || (idx >= CfgGlobals::SIZE)) {
+ isc_throw(Unexpected, "invalid index " << idx
+ << " for name " << it->first);
+ }
+ if (!names[idx].empty()) {
+ isc_throw(Unexpected, "duplicated names for " << idx
+ << " got " << names[idx]);
+ }
+ names[idx] = it->first;
+ }
+
+ // No name should be empty.
+ for (int idx = 0; idx < CfgGlobals::SIZE; ++idx) {
+ if (names[idx].empty()) {
+ isc_throw(Unexpected, "missing name for " << idx);
+ }
+ }
+ }
+};
+
+CfgGlobalsChecks check;
+} // end of anonymous namespace
+
+CfgGlobals::CfgGlobals() : values_(SIZE) {
+}
+
+ConstElementPtr
+CfgGlobals::get(const std::string& name) const {
+ auto const& it = nameToIndex.find(name);
+ if (it == nameToIndex.cend()) {
+ isc_throw(NotFound, "invalid global parameter name '" << name << "'");
+ }
+ return (get(it->second));
+}
+
+ConstElementPtr
+CfgGlobals::get(int index) const {
+ if ((index < 0) || (index >= CfgGlobals::SIZE)) {
+ isc_throw(OutOfRange, "invalid global parameter index " << index);
+ }
+ return (values_[index]);
+}
+
+void
+CfgGlobals::set(const std::string& name, ConstElementPtr value) {
+ auto const& it = nameToIndex.find(name);
+ if (it == nameToIndex.cend()) {
+ isc_throw(NotFound, "invalid global parameter name '" << name << "'");
+ }
+ set(it->second, value);
+}
+
+void
+CfgGlobals::set(int index, ConstElementPtr value) {
+ if ((index < 0) || (index >= CfgGlobals::SIZE)) {
+ isc_throw(OutOfRange, "invalid global parameter index " << index);
+ }
+ values_[index] = value;
+}
+
+void
+CfgGlobals::clear() {
+ for (int idx = 0; idx < CfgGlobals::SIZE; ++idx) {
+ if (values_[idx]) {
+ values_[idx] = ConstElementPtr();
+ }
+ }
+}
+
+const CfgGlobals::MapType
+CfgGlobals::valuesMap() const {
+ MapType map;
+ for (auto it = nameToIndex.cbegin(); it != nameToIndex.cend(); ++it) {
+ int idx = it->second;
+ ConstElementPtr value = values_[idx];
+ if (value) {
+ map.insert(make_pair(it->first, value));
+ }
+ }
+ return (map);
+}
+
+ElementPtr
+CfgGlobals::toElement() const {
+ ElementPtr result = Element::createMap();
+ for (auto it = nameToIndex.cbegin(); it != nameToIndex.cend(); ++it) {
+ int idx = it->second;
+ ConstElementPtr value = values_[idx];
+ if (value) {
+ result->set(it->first, value);
+ }
+ }
+ return (result);
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/cfg_globals.h b/src/lib/dhcpsrv/cfg_globals.h
new file mode 100644
index 0000000..0dfaf66
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_globals.h
@@ -0,0 +1,168 @@
+// Copyright (C) 2021-2023 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 CFG_GLOBALS_H
+#define CFG_GLOBALS_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Class to store configured global parameters.
+///
+/// This class provides a direct access to a global parameter value
+/// using a vector as soon as the parameter name is known at compile
+/// time so in constant time vs in logarithm time using a map.
+class CfgGlobals : public isc::data::CfgToElement {
+public:
+
+ /// Class members.
+
+ /// @brief Enumeration of global parameters.
+ ///
+ /// The C++ compiler is required to start with 0 and to increment by 1
+ /// so gives an index.
+ ///
+ /// Names taken from @c SimpleParser4::GLOBAL4_PARAMETERS and
+ /// @c SimpleParser6::GLOBAL6_PARAMETERS, first part with common
+ /// parameters followed by DHCPv4 and DHCPv6 specific parameters.
+ /// Keep the order, enum element names is uppercase with - replaced by _.
+ enum Index : int {
+ // Common parameters.
+ VALID_LIFETIME,
+ MIN_VALID_LIFETIME,
+ MAX_VALID_LIFETIME,
+ RENEW_TIMER,
+ REBIND_TIMER,
+ DECLINE_PROBATION_PERIOD,
+ DHCP4O6_PORT,
+ COMMENT,
+ SERVER_TAG,
+ RESERVATION_MODE,
+ RESERVATIONS_GLOBAL,
+ RESERVATIONS_IN_SUBNET,
+ RESERVATIONS_OUT_OF_POOL,
+ CALCULATE_TEE_TIMES,
+ T1_PERCENT,
+ T2_PERCENT,
+ HOSTNAME_CHAR_SET,
+ HOSTNAME_CHAR_REPLACEMENT,
+ DDNS_SEND_UPDATES,
+ DDNS_OVERRIDE_NO_UPDATE,
+ DDNS_OVERRIDE_CLIENT_UPDATE,
+ DDNS_REPLACE_CLIENT_NAME,
+ DDNS_GENERATED_PREFIX,
+ DDNS_QUALIFYING_SUFFIX,
+ STORE_EXTENDED_INFO,
+ STATISTIC_DEFAULT_SAMPLE_COUNT,
+ STATISTIC_DEFAULT_SAMPLE_AGE,
+ CACHE_THRESHOLD,
+ CACHE_MAX_AGE,
+ EARLY_GLOBAL_RESERVATIONS_LOOKUP,
+ IP_RESERVATIONS_UNIQUE,
+ RESERVATIONS_LOOKUP_FIRST,
+ DDNS_UPDATE_ON_RENEW,
+ DDNS_USE_CONFLICT_RESOLUTION,
+ PARKED_PACKET_LIMIT,
+ ALLOCATOR,
+ DDNS_TTL_PERCENT,
+
+ // DHCPv4 specific parameters.
+ ECHO_CLIENT_ID,
+ MATCH_CLIENT_ID,
+ AUTHORITATIVE,
+ NEXT_SERVER,
+ SERVER_HOSTNAME,
+ BOOT_FILE_NAME,
+ OFFER_LIFETIME,
+
+ // DHCPv6 specific parameters.
+ DATA_DIRECTORY,
+ PREFERRED_LIFETIME,
+ MIN_PREFERRED_LIFETIME,
+ MAX_PREFERRED_LIFETIME,
+ PD_ALLOCATOR,
+
+ // Size sentinel.
+ SIZE
+ };
+
+ /// @brief Name to index map.
+ static const std::map<std::string, int> nameToIndex;
+
+ /// Instance members.
+
+ /// Constructor.
+ ///
+ /// Create a vector of null values.
+ CfgGlobals();
+
+ /// @brief Get a configured parameter value by name.
+ ///
+ /// @param name Name of the global parameter.
+ /// @return The value of the global parameter with the given name.
+ /// @throw NotFound if no global parameter has the given name.
+ isc::data::ConstElementPtr get(const std::string& name) const;
+
+ /// @brief Get a configured parameter value by index.
+ ///
+ /// @param index Index of the global parameter.
+ /// @return The value of the global parameter with the index.
+ /// @throw OutOfRange if the index is out of bounds.
+ isc::data::ConstElementPtr get(int index) const;
+
+ /// @brief Set a configured parameter value by name.
+ ///
+ /// @param name Name of the global parameter.
+ /// @param value Value of the configured parameter to set.
+ /// @throw NotFound if no global parameter has the given name.
+ void set(const std::string& name, isc::data::ConstElementPtr value);
+
+ /// @brief Set a configured parameter value by index.
+ ///
+ /// @param index Index of the global parameter.
+ /// @param value Value of the configured parameter to set.
+ /// @throw OutOfRange if the index is out of bounds.
+ void set(int index, isc::data::ConstElementPtr value);
+
+ /// @brief Clear configured parameter values.
+ void clear();
+
+ /// @brief Type of name and value map.
+ typedef std::map<std::string, isc::data::ConstElementPtr> MapType;
+
+ /// @brief Returns configured parameters as a map.
+ ///
+ /// @note: the map includes only set global parameters i.e.
+ /// ConstElementPtr values are never null.
+ const MapType valuesMap() const;
+
+ /// @brief Unparse configured global parameters.
+ ///
+ /// @return a pointer to unparsed global parameters.
+ isc::data::ElementPtr toElement() const;
+
+protected:
+ /// @brief Vectors of values.
+ std::vector<isc::data::ConstElementPtr> values_;
+};
+
+/// @brief Non-const shared pointer to a CfgGlobals instance.
+typedef boost::shared_ptr<CfgGlobals> CfgGlobalsPtr;
+
+/// @brief Const shared pointer to a CfgGlobals instance.
+typedef boost::shared_ptr<const CfgGlobals> ConstCfgGlobalsPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CFG_GLOBALS_H
diff --git a/src/lib/dhcpsrv/cfg_host_operations.cc b/src/lib/dhcpsrv/cfg_host_operations.cc
new file mode 100644
index 0000000..f588b9d
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_host_operations.cc
@@ -0,0 +1,73 @@
+// Copyright (C) 2016-2017 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 <dhcpsrv/cfg_host_operations.h>
+#include <algorithm>
+#include <string>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+CfgHostOperations::CfgHostOperations()
+ : identifier_types_() {
+}
+
+CfgHostOperationsPtr
+CfgHostOperations::createConfig4() {
+ // If this list is modified, please update reservations4-tuning section in
+ // doc/guide/dhcp4-srv.xml
+ CfgHostOperationsPtr cfg(new CfgHostOperations());
+ cfg->addIdentifierType("hw-address");
+ cfg->addIdentifierType("duid");
+ cfg->addIdentifierType("circuit-id");
+ cfg->addIdentifierType("client-id");
+ return (cfg);
+}
+
+CfgHostOperationsPtr
+CfgHostOperations::createConfig6() {
+ // If this list is modified, please update reservations6-tuning section in
+ // doc/guide/dhcp6-srv.xml
+ CfgHostOperationsPtr cfg(new CfgHostOperations());
+ cfg->addIdentifierType("hw-address");
+ cfg->addIdentifierType("duid");
+ return (cfg);
+}
+
+void
+CfgHostOperations::addIdentifierType(const std::string& identifier_name) {
+ Host::IdentifierType identifier_type = Host::getIdentifierType(identifier_name);
+ if (std::find(identifier_types_.begin(), identifier_types_.end(),
+ identifier_type) != identifier_types_.end()) {
+ isc_throw(isc::BadValue, "duplicate host identifier '"
+ << identifier_name << "'");
+ }
+ identifier_types_.push_back(identifier_type);
+}
+
+void
+CfgHostOperations::clearIdentifierTypes() {
+ identifier_types_.clear();
+}
+
+ElementPtr
+CfgHostOperations::toElement() const {
+ ElementPtr result = Element::createList();
+ for (IdentifierTypes::const_iterator id = identifier_types_.begin();
+ id != identifier_types_.end(); ++id) {
+ const std::string& name = Host::getIdentifierName(*id);
+ result->add(Element::create(name));
+ }
+ return (result);
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/cfg_host_operations.h b/src/lib/dhcpsrv/cfg_host_operations.h
new file mode 100644
index 0000000..ae9f658
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_host_operations.h
@@ -0,0 +1,97 @@
+// Copyright (C) 2016-2017 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 CFG_HOST_OPERATIONS_H
+#define CFG_HOST_OPERATIONS_H
+
+#include <cc/cfg_to_element.h>
+#include <dhcpsrv/host.h>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of the @ref CfgHostOperations.
+class CfgHostOperations;
+
+/// @name Pointers to the @ref CfgHostOperations objects.
+//@{
+/// @brief Pointer to the Non-const object.
+typedef boost::shared_ptr<CfgHostOperations> CfgHostOperationsPtr;
+
+/// @brief Pointer to the const object.
+typedef boost::shared_ptr<const CfgHostOperations>
+ConstCfgHostOperationsPtr;
+
+//@}
+
+/// @brief Represents global configuration for host reservations.
+///
+/// This class represents server configuration pertaining to host
+/// reservations.
+///
+/// Currently it only holds the ordered list of host identifiers
+/// to be used to search for reservations for a particular host.
+/// An administrator selects which identifiers the server should
+/// use and in which order to search for host reservations to
+/// optimize performance of the server.
+class CfgHostOperations : public isc::data::CfgToElement {
+public:
+
+ /// @brief Type of the container holding ordered list of identifiers.
+ typedef std::list<Host::IdentifierType> IdentifierTypes;
+
+ /// @brief Constructor.
+ ///
+ /// The default configuration:
+ /// - no identifiers selected for host reservations searches.
+ CfgHostOperations();
+
+ /// @name Factory functions for creating default configurations.
+ //@{
+ /// @brief Factory function for DHCPv4.
+ static CfgHostOperationsPtr createConfig4();
+
+ /// @brief Factory function for DHCPv6.
+ static CfgHostOperationsPtr createConfig6();
+ //@}
+
+ /// @brief Adds new identifier type to a collection of identifiers
+ /// to be used by the server to search for host reservations.
+ ///
+ /// @param identifier_name Name of the identifier to be added. It
+ /// must be one of the names supported by the @ref Host::getIdentifierType
+ /// function.
+ void addIdentifierType(const std::string& identifier_name);
+
+ /// @brief Returns const reference to ordered collection of identifiers
+ /// to be used by the server to search for host reservations.
+ const IdentifierTypes& getIdentifierTypes() const {
+ return (identifier_types_);
+ }
+
+ /// @brief Removes existing identifier types.
+ void clearIdentifierTypes();
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief Holds ordered collection of identifiers to be used by the
+ /// server to search for host reservations for a client.
+ IdentifierTypes identifier_types_;
+
+};
+
+}
+}
+
+#endif // CFG_HOST_OPERATIONS_H
diff --git a/src/lib/dhcpsrv/cfg_hosts.cc b/src/lib/dhcpsrv/cfg_hosts.cc
new file mode 100644
index 0000000..1b263f3
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_hosts.cc
@@ -0,0 +1,1276 @@
+// Copyright (C) 2014-2023 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 <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+#include <ostream>
+#include <string>
+#include <vector>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+ConstHostCollection
+CfgHosts::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal method.
+ ConstHostCollection collection;
+ getAllInternal<ConstHostCollection>(identifier_type, identifier_begin,
+ identifier_len, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal method.
+ HostCollection collection;
+ getAllInternal<HostCollection>(identifier_type, identifier_begin,
+ identifier_len, collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAll4(const SubnetID& subnet_id) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal4 method.
+ ConstHostCollection collection;
+ getAllInternal4<ConstHostCollection>(subnet_id, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAll4(const SubnetID& subnet_id) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal4 method.
+ HostCollection collection;
+ getAllInternal4<HostCollection>(subnet_id, collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAll6(const SubnetID& subnet_id) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal6 method.
+ ConstHostCollection collection;
+ getAllInternal6<ConstHostCollection>(subnet_id, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAll6(const SubnetID& subnet_id) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal6 method.
+ HostCollection collection;
+ getAllInternal6<HostCollection>(subnet_id, collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAllbyHostname(const std::string& hostname) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllbyHostnameInternal method.
+ ConstHostCollection collection;
+ getAllbyHostnameInternal<ConstHostCollection>(hostname, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAllbyHostname(const std::string& hostname) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllbyHostnameInternal method.
+ HostCollection collection;
+ getAllbyHostnameInternal<HostCollection>(hostname, collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllbyHostnameInternal4 method.
+ ConstHostCollection collection;
+ getAllbyHostnameInternal4<ConstHostCollection>(hostname, subnet_id, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllbyHostnameInternal4 method.
+ HostCollection collection;
+ getAllbyHostnameInternal4<HostCollection>(hostname, subnet_id, collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllbyHostnameInternal6 method.
+ ConstHostCollection collection;
+ getAllbyHostnameInternal6<ConstHostCollection>(hostname, subnet_id, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllbyHostnameInternal6 method.
+ HostCollection collection;
+ getAllbyHostnameInternal6<HostCollection>(hostname, subnet_id, collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getPage4(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal4 method.
+ ConstHostCollection collection;
+ getPageInternal4<ConstHostCollection>(subnet_id,
+ lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getPage4(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal4 method.
+ HostCollection collection;
+ getPageInternal4<HostCollection>(subnet_id,
+ lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getPage6(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal6 method.
+ ConstHostCollection collection;
+ getPageInternal6<ConstHostCollection>(subnet_id,
+ lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getPage6(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal6 method.
+ HostCollection collection;
+ getPageInternal6<HostCollection>(subnet_id,
+ lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getPage4(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal method.
+ ConstHostCollection collection;
+ getPageInternal<ConstHostCollection>(lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getPage4(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal method.
+ HostCollection collection;
+ getPageInternal<HostCollection>(lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getPage6(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal method.
+ ConstHostCollection collection;
+ getPageInternal<ConstHostCollection>(lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getPage6(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) {
+ // Do not issue logging message here because it will be logged by
+ // the getPageInternal method.
+ HostCollection collection;
+ getPageInternal<HostCollection>(lower_host_id,
+ page_size,
+ collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAll4(const IOAddress& address) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal4 method.
+ ConstHostCollection collection;
+ getAllInternal4<ConstHostCollection>(address, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAll4(const IOAddress& address) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal4 method.
+ HostCollection collection;
+ getAllInternal4<HostCollection>(address, collection);
+ return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAll6(const IOAddress& address) const {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal6 method.
+ ConstHostCollection collection;
+ getAllInternal6<ConstHostCollection>(address, collection);
+ return (collection);
+}
+
+HostCollection
+CfgHosts::getAll6(const IOAddress& address) {
+ // Do not issue logging message here because it will be logged by
+ // the getAllInternal6 method.
+ HostCollection collection;
+ getAllInternal6<HostCollection>(address, collection);
+ return (collection);
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier,
+ const size_t identifier_len,
+ Storage& storage) const {
+
+ // Convert host identifier into textual format for logging purposes.
+ // This conversion is exception free.
+ std::string identifier_text = Host::getIdentifierAsText(identifier_type,
+ identifier,
+ identifier_len);
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_IDENTIFIER)
+ .arg(identifier_text);
+
+ // Use the identifier and identifier type as a composite key.
+ const HostContainerIndex0& idx = hosts_.get<0>();
+ boost::tuple<const std::vector<uint8_t>, const Host::IdentifierType> t =
+ boost::make_tuple(std::vector<uint8_t>(identifier,
+ identifier + identifier_len),
+ identifier_type);
+
+ // Append each Host object to the storage.
+ for (HostContainerIndex0::iterator host = idx.lower_bound(t);
+ host != idx.upper_bound(t);
+ ++host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_IDENTIFIER_HOST)
+ .arg(identifier_text)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT)
+ .arg(identifier_text)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal4(const SubnetID& subnet_id,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID4)
+ .arg(subnet_id);
+
+ // Use try DHCPv4 subnet id.
+ const HostContainerIndex2& idx = hosts_.get<2>();
+
+ // Append each Host object to the storage.
+ for (HostContainerIndex2::iterator host = idx.lower_bound(subnet_id);
+ host != idx.upper_bound(subnet_id);
+ ++host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST)
+ .arg(subnet_id)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT)
+ .arg(subnet_id)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal6(const SubnetID& subnet_id,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID6)
+ .arg(subnet_id);
+
+ // Use try DHCPv6 subnet id.
+ const HostContainerIndex3& idx = hosts_.get<3>();
+
+ // Append each Host object to the storage.
+ for (HostContainerIndex3::iterator host = idx.lower_bound(subnet_id);
+ host != idx.upper_bound(subnet_id);
+ ++host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST)
+ .arg(subnet_id)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT)
+ .arg(subnet_id)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllbyHostnameInternal(const std::string& hostname,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_HOSTNAME)
+ .arg(hostname);
+
+ // Use try hostname.
+ const HostContainerIndex5& idx = hosts_.get<5>();
+
+ // Append each Host object to the storage.
+ for (HostContainerIndex5::iterator host = idx.lower_bound(hostname);
+ host != idx.upper_bound(hostname);
+ ++host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_HOSTNAME_HOST)
+ .arg(hostname)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_HOSTNAME_COUNT)
+ .arg(hostname)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllbyHostnameInternal4(const std::string& hostname,
+ const SubnetID& subnet_id,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4)
+ .arg(hostname)
+ .arg(subnet_id);
+
+ // Use try hostname.
+ const HostContainerIndex5& idx = hosts_.get<5>();
+
+ // Append each Host object to the storage.
+ for (HostContainerIndex5::iterator host = idx.lower_bound(hostname);
+ host != idx.upper_bound(hostname);
+ ++host) {
+ if ((*host)->getIPv4SubnetID() != subnet_id) {
+ continue;
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_HOST)
+ .arg(hostname)
+ .arg(subnet_id)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_COUNT)
+ .arg(hostname)
+ .arg(subnet_id)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllbyHostnameInternal6(const std::string& hostname,
+ const SubnetID& subnet_id,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6)
+ .arg(hostname)
+ .arg(subnet_id);
+
+ // Use try hostname.
+ const HostContainerIndex5& idx = hosts_.get<5>();
+
+ // Append each Host object to the storage.
+ for (HostContainerIndex5::iterator host = idx.lower_bound(hostname);
+ host != idx.upper_bound(hostname);
+ ++host) {
+ if ((*host)->getIPv6SubnetID() != subnet_id) {
+ continue;
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_HOST)
+ .arg(hostname)
+ .arg(subnet_id)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_COUNT)
+ .arg(hostname)
+ .arg(subnet_id)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getPageInternal(uint64_t lower_host_id,
+ const HostPageSize& page_size,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL);
+
+ // Use the host id last index.
+ const HostContainerIndex4& idx = hosts_.get<4>();
+ HostContainerIndex4::const_iterator host = idx.lower_bound(lower_host_id);
+
+ // Exclude the lower bound id when it is not zero.
+ if (lower_host_id &&
+ (host != idx.end()) && ((*host)->getHostId() == lower_host_id)) {
+ ++host;
+ }
+
+ // Return hosts within the page size.
+ for (; host != idx.end(); ++host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_HOST)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ if (storage.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_COUNT)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getPageInternal4(const SubnetID& subnet_id,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID4)
+ .arg(subnet_id);
+
+ // Use the host id last index.
+ const HostContainerIndex4& idx = hosts_.get<4>();
+ HostContainerIndex4::const_iterator host = idx.lower_bound(lower_host_id);
+
+ // Exclude the lower bound id when it is not zero.
+ if (lower_host_id &&
+ (host != idx.end()) && ((*host)->getHostId() == lower_host_id)) {
+ ++host;
+ }
+
+ // Return hosts in the subnet within the page size.
+ for (; host != idx.end(); ++host) {
+ if ((*host)->getIPv4SubnetID() != subnet_id) {
+ continue;
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST)
+ .arg(subnet_id)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ if (storage.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT)
+ .arg(subnet_id)
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getPageInternal6(const SubnetID& subnet_id,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size,
+ Storage& storage) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID6)
+ .arg(subnet_id);
+
+ // Use the host id last index.
+ const HostContainerIndex4& idx = hosts_.get<4>();
+ HostContainerIndex4::const_iterator host = idx.lower_bound(lower_host_id);
+
+ // Exclude the lower bound id when it is not zero.
+ if (lower_host_id &&
+ (host != idx.end()) && ((*host)->getHostId() == lower_host_id)) {
+ ++host;
+ }
+
+ // Return hosts in the subnet within the page size.
+ for (; host != idx.end(); ++host) {
+ if ((*host)->getIPv6SubnetID() != subnet_id) {
+ continue;
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST)
+ .arg(subnet_id)
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ if (storage.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+
+ // Log how many hosts have been found.
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT)
+ .arg(subnet_id)
+ .arg(storage.size());
+}
+
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal4(const IOAddress& address, Storage& storage) const {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_ADDRESS4)
+ .arg(address.toText());
+
+ // Must not specify address other than IPv4.
+ if (!address.isV4()) {
+ isc_throw(BadHostAddress, "must specify an IPv4 address when searching"
+ " for a host, specified address was " << address);
+ }
+ // Search for the Host using the reserved IPv4 address as a key.
+ const HostContainerIndex1& idx = hosts_.get<1>();
+ HostContainerIndex1Range r = idx.equal_range(address);
+ // Append each Host object to the storage.
+ for (HostContainerIndex1::iterator host = r.first; host != r.second;
+ ++host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_ADDRESS4_HOST)
+ .arg(address.toText())
+ .arg((*host)->toText());
+ storage.push_back(*host);
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_ADDRESS4_COUNT)
+ .arg(address.toText())
+ .arg(storage.size());
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal6(const IOAddress& address, Storage& storage) const {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_ADDRESS6)
+ .arg(address.toText());
+
+ // Must not specify address other than IPv6.
+ if (!address.isV6()) {
+ isc_throw(BadHostAddress, "must specify an IPv6 address when searching"
+ " for a host, specified address was " << address);
+ }
+ // Search for the Host using the reserved IPv6 address as a key.
+ const HostContainer6Index4& idx = hosts6_.get<4>();
+ HostContainer6Index4Range r = idx.equal_range(address);
+ // Append each Host object to the storage.
+ for (HostContainer6Index4::iterator reservation = r.first; reservation != r.second;
+ ++reservation) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_ADDRESS6_HOST)
+ .arg(address.toText())
+ .arg(reservation->host_->toText());
+ storage.push_back(reservation->host_);
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_ADDRESS6_COUNT)
+ .arg(address.toText())
+ .arg(storage.size());
+}
+
+ConstHostPtr
+CfgHosts::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ return (getHostInternal(subnet_id, false, identifier_type, identifier_begin,
+ identifier_len));
+}
+
+HostPtr
+CfgHosts::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ return (getHostInternal(subnet_id, false, identifier_type, identifier_begin,
+ identifier_len));
+}
+
+ConstHostPtr
+CfgHosts::get4(const SubnetID& subnet_id, const IOAddress& address) const {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4)
+ .arg(subnet_id).arg(address.toText());
+
+ ConstHostCollection hosts = getAll4(address);
+ for (ConstHostCollection::const_iterator host = hosts.begin();
+ host != hosts.end(); ++host) {
+ if ((*host)->getIPv4SubnetID() == subnet_id) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST)
+ .arg(subnet_id)
+ .arg(address.toText())
+ .arg((*host)->toText());
+ return (*host);
+ }
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL)
+ .arg(subnet_id).arg(address.toText());
+ return (ConstHostPtr());
+}
+
+ConstHostCollection
+CfgHosts::getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4)
+ .arg(subnet_id).arg(address.toText());
+
+ ConstHostCollection hosts;
+ for (auto host : getAll4(address)) {
+ if (host->getIPv4SubnetID() == subnet_id) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST)
+ .arg(subnet_id)
+ .arg(address.toText())
+ .arg(host->toText());
+ hosts.push_back(host);
+ }
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT)
+ .arg(subnet_id)
+ .arg(address.toText())
+ .arg(hosts.size());
+
+ return (hosts);
+}
+
+ConstHostPtr
+CfgHosts::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ return (getHostInternal(subnet_id, true, identifier_type, identifier_begin,
+ identifier_len));
+}
+
+HostPtr
+CfgHosts::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ return (getHostInternal(subnet_id, true, identifier_type, identifier_begin,
+ identifier_len));
+}
+
+ConstHostPtr
+CfgHosts::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
+ return (getHostInternal6<ConstHostPtr>(prefix, prefix_len));
+}
+
+HostPtr
+CfgHosts::get6(const IOAddress& prefix, const uint8_t prefix_len) {
+ return (getHostInternal6<HostPtr>(prefix, prefix_len));
+}
+
+ConstHostPtr
+CfgHosts::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ // Do not log here because getHostInternal6 logs.
+ return (getHostInternal6<ConstHostPtr, ConstHostCollection>(subnet_id, address));
+}
+
+HostPtr
+CfgHosts::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) {
+ // Do not log here because getHostInternal6 logs.
+ return (getHostInternal6<HostPtr, HostCollection>(subnet_id, address));
+}
+
+ConstHostCollection
+CfgHosts::getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ ConstHostCollection hosts;
+ getAllInternal6(subnet_id, address, hosts);
+ return (hosts);
+}
+
+template<typename ReturnType, typename Storage>
+ReturnType
+CfgHosts::getHostInternal6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6)
+ .arg(subnet_id).arg(address.toText());
+
+ Storage storage;
+ getAllInternal6<Storage>(subnet_id, address, storage);
+ switch (storage.size()) {
+ case 0:
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL)
+ .arg(subnet_id)
+ .arg(address.toText());
+ return (HostPtr());
+
+ case 1:
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST)
+ .arg(subnet_id)
+ .arg(address.toText())
+ .arg((*storage.begin())->toText());
+ return (*storage.begin());
+
+ default:
+ isc_throw(DuplicateHost, "more than one reservation found"
+ " for the host belonging to the subnet with id '"
+ << subnet_id << "' and using the address '"
+ << address.toText() << "'");
+ }
+
+}
+
+template<typename ReturnType>
+ReturnType
+CfgHosts::getHostInternal6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_PREFIX)
+ .arg(prefix.toText()).arg(static_cast<int>(prefix_len));
+
+ // Let's get all reservations that match subnet_id, address.
+ const HostContainer6Index0& idx = hosts6_.get<0>();
+ HostContainer6Index0Range r = make_pair(idx.lower_bound(prefix),
+ idx.upper_bound(prefix));
+ for (HostContainer6Index0::iterator resrv = r.first; resrv != r.second;
+ ++resrv) {
+ if (resrv->resrv_.getPrefixLen() == prefix_len) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ONE_PREFIX_HOST)
+ .arg(prefix.toText())
+ .arg(static_cast<int>(prefix_len))
+ .arg(resrv->host_->toText());
+ return (resrv->host_);
+ }
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ONE_PREFIX_NULL)
+ .arg(prefix.toText())
+ .arg(static_cast<int>(prefix_len));
+ return (ReturnType());
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address,
+ Storage& storage) const {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6)
+ .arg(subnet_id).arg(address.toText());
+
+ // Must not specify address other than IPv6.
+ if (!address.isV6()) {
+ isc_throw(BadHostAddress, "must specify an IPv6 address when searching"
+ " for a host, specified address was " << address);
+ }
+
+ // Let's get all reservations that match subnet_id, address.
+ const HostContainer6Index1& idx = hosts6_.get<1>();
+ HostContainer6Index1Range r = make_pair(idx.lower_bound(boost::make_tuple(subnet_id, address)),
+ idx.upper_bound(boost::make_tuple(subnet_id, address)));
+
+ // For each IPv6 reservation, add the host to the results list. Fortunately,
+ // in all sane cases, there will be only one such host. (Each host can have
+ // multiple addresses reserved, but for each (address, subnet_id) there should
+ // be at most one host reserving it).
+ for(HostContainer6Index1::iterator resrv = r.first; resrv != r.second; ++resrv) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE_DETAIL_DATA,
+ HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST)
+ .arg(subnet_id)
+ .arg(address.toText())
+ .arg(resrv->host_->toText());
+ storage.push_back(resrv->host_);
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT)
+ .arg(subnet_id)
+ .arg(address.toText())
+ .arg(storage.size());
+}
+
+HostPtr
+CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier,
+ const size_t identifier_len) const {
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER)
+ .arg(subnet6 ? "IPv6" : "IPv4")
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier, identifier_len));
+
+ // Get all hosts for a specified identifier. This may return multiple hosts
+ // for different subnets, but the number of hosts returned should be low
+ // because one host presumably doesn't show up in many subnets.
+ HostCollection hosts;
+ getAllInternal<HostCollection>(identifier_type, identifier, identifier_len,
+ hosts);
+
+ HostPtr host;
+ // Iterate over the returned hosts and select those for which the
+ // subnet id matches.
+ for (HostCollection::const_iterator host_it = hosts.begin();
+ host_it != hosts.end(); ++host_it) {
+ // Check if this is IPv4 subnet or IPv6 subnet.
+ SubnetID host_subnet_id = subnet6 ? (*host_it)->getIPv6SubnetID() :
+ (*host_it)->getIPv4SubnetID();
+
+ if (subnet_id == host_subnet_id) {
+ // If this is the first occurrence of the host for this subnet,
+ // remember it. But, if we find that this is second @c Host object
+ // for the same client, it is a misconfiguration. Most likely,
+ // the administrator has specified one reservation for a HW
+ // address and another one for the DUID, which gives an ambiguous
+ // result, and we don't know which reservation we should choose.
+ // Therefore, throw an exception.
+ if (!host) {
+ host = *host_it;
+
+ } else {
+ isc_throw(DuplicateHost, "more than one reservation found"
+ " for the host belonging to the subnet with id '"
+ << subnet_id << "' and using the identifier '"
+ << Host::getIdentifierAsText(identifier_type,
+ identifier,
+ identifier_len)
+ << "'");
+ }
+ }
+ }
+
+ if (host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier,
+ identifier_len))
+ .arg(host->toText());
+
+ } else {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier,
+ identifier_len));
+ }
+
+ return (host);
+}
+
+void
+CfgHosts::add(const HostPtr& host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_ADD_HOST)
+ .arg(host ? host->toText() : "(no-host)");
+
+ // Sanity check that the host is non-null.
+ if (!host) {
+ isc_throw(BadValue, "specified host object must not be NULL when it"
+ " is added to the configuration");
+ }
+
+ // At least one subnet ID must be used.
+ if (host->getIPv4SubnetID() == SUBNET_ID_UNUSED &&
+ host->getIPv6SubnetID() == SUBNET_ID_UNUSED) {
+ isc_throw(BadValue, "must not use both IPv4 and IPv6 subnet ids of"
+ " 0 when adding new host reservation");
+ }
+
+ add4(host);
+
+ add6(host);
+}
+
+void
+CfgHosts::add4(const HostPtr& host) {
+
+ HWAddrPtr hwaddr = host->getHWAddress();
+ DuidPtr duid = host->getDuid();
+
+ // Check for duplicates for the specified IPv4 subnet.
+ if (host->getIPv4SubnetID() != SUBNET_ID_UNUSED) {
+ if (hwaddr && !hwaddr->hwaddr_.empty() &&
+ get4(host->getIPv4SubnetID(), Host::IDENT_HWADDR,
+ &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size())) {
+ isc_throw(DuplicateHost, "failed to add new host using the HW"
+ << " address '" << hwaddr->toText(false)
+ << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
+ << "' as this host has already been added");
+ }
+ if (duid && !duid->getDuid().empty() &&
+ get4(host->getIPv4SubnetID(), Host::IDENT_DUID,
+ &duid->getDuid()[0], duid->getDuid().size())) {
+ isc_throw(DuplicateHost, "failed to add new host using the "
+ << "DUID '" << duid->toText()
+ << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
+ << "' as this host has already been added");
+ }
+ // Check for duplicates for the specified IPv6 subnet.
+ } else if (host->getIPv6SubnetID() != SUBNET_ID_UNUSED) {
+ if (duid && !duid->getDuid().empty() &&
+ get6(host->getIPv6SubnetID(), Host::IDENT_DUID,
+ &duid->getDuid()[0], duid->getDuid().size())) {
+ isc_throw(DuplicateHost, "failed to add new host using the "
+ << "DUID '" << duid->toText()
+ << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
+ << "' as this host has already been added");
+ }
+ if (hwaddr && !hwaddr->hwaddr_.empty() &&
+ get6(host->getIPv6SubnetID(), Host::IDENT_HWADDR,
+ &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size())) {
+ isc_throw(DuplicateHost, "failed to add new host using the HW"
+ << " address '" << hwaddr->toText(false)
+ << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
+ << "' as this host has already been added");
+ }
+ }
+
+ // Check if the address is already reserved for the specified IPv4 subnet.
+ if (ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero() &&
+ (host->getIPv4SubnetID() != SUBNET_ID_UNUSED) &&
+ get4(host->getIPv4SubnetID(), host->getIPv4Reservation())) {
+ isc_throw(ReservedAddress, "failed to add new host using the HW"
+ " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
+ << " and DUID '" << (duid ? duid->toText() : "(null)")
+ << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
+ << "' for the address " << host->getIPv4Reservation()
+ << ": There's already a reservation for this address");
+ }
+
+ // Check if the (identifier type, identifier) tuple is already used.
+ const std::vector<uint8_t>& id = host->getIdentifier();
+ if ((host->getIPv4SubnetID() != SUBNET_ID_UNUSED) && !id.empty()) {
+ if (get4(host->getIPv4SubnetID(), host->getIdentifierType(), &id[0],
+ id.size())) {
+ isc_throw(DuplicateHost, "failed to add duplicate IPv4 host using identifier: "
+ << Host::getIdentifierAsText(host->getIdentifierType(),
+ &id[0], id.size()));
+ }
+ }
+
+ // This is a new instance - add it.
+ host->setHostId(++next_host_id_);
+ hosts_.insert(host);
+}
+
+void
+CfgHosts::add6(const HostPtr& host) {
+
+ if (host->getIPv6SubnetID() == SUBNET_ID_UNUSED) {
+ // This is IPv4-only host. No need to add it to v6 tables.
+ return;
+ }
+
+ HWAddrPtr hwaddr = host->getHWAddress();
+ DuidPtr duid = host->getDuid();
+
+ // Get all reservations for this host.
+ IPv6ResrvRange reservations = host->getIPv6Reservations();
+
+ // Check if there are any IPv6 reservations.
+ if (std::distance(reservations.first, reservations.second) == 0) {
+ // If there aren't, we don't need to add this to hosts6_, which is used
+ // for getting hosts by their IPv6 address reservations.
+ return;
+ }
+
+ // Now for each reservation, insert corresponding (address, host) tuple.
+ for (IPv6ResrvIterator it = reservations.first; it != reservations.second;
+ ++it) {
+
+ if (ip_reservations_unique_) {
+ // If there's an entry for this (subnet-id, address), reject it.
+ if (get6(host->getIPv6SubnetID(), it->second.getPrefix())) {
+ isc_throw(DuplicateHost, "failed to add address reservation for "
+ << "host using the HW address '"
+ << (hwaddr ? hwaddr->toText(false) : "(null)")
+ << " and DUID '" << (duid ? duid->toText() : "(null)")
+ << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
+ << "' for address/prefix " << it->second.getPrefix()
+ << ": There's already reservation for this address/prefix");
+ }
+ }
+ hosts6_.insert(HostResrv6Tuple(it->second, host));
+ }
+}
+
+bool
+CfgHosts::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
+ size_t erased_hosts = 0;
+ size_t erased_addresses = 0;
+ if (addr.isV4()) {
+ HostContainerIndex4& idx = hosts_.get<4>();
+ // Delete IPv4 reservation and host.
+ for (auto host : getAll4(subnet_id, addr)) {
+ erased_hosts += idx.erase(host->getHostId());
+ }
+ erased_addresses = erased_hosts;
+ } else {
+ HostContainer6Index1& idx6 = hosts6_.get<1>();
+ HostContainerIndex4& idx = hosts_.get<4>();
+ // Delete IPv6 reservations.
+ const auto& range = idx6.equal_range(boost::make_tuple(subnet_id, addr));
+ erased_addresses = boost::distance(range);
+ // Delete hosts.
+ for (auto key = range.first; key != range.second; ++key) {
+ erased_hosts += idx.erase(key->host_->getHostId());
+ }
+ idx6.erase(range.first, range.second);
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_DEL)
+ .arg(erased_hosts)
+ .arg(erased_addresses)
+ .arg(subnet_id)
+ .arg(addr.toText());
+
+ return (erased_hosts != 0);
+}
+
+size_t
+CfgHosts::delAll4(const SubnetID& subnet_id) {
+ HostContainerIndex2& idx = hosts_.get<2>();
+ size_t erased = idx.erase(subnet_id);
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_DEL_ALL_SUBNET4)
+ .arg(erased)
+ .arg(subnet_id);
+
+ return (erased);
+}
+
+bool
+CfgHosts::del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ HostContainerIndex0& idx = hosts_.get<0>();
+ const auto t = boost::make_tuple(std::vector<uint8_t>(identifier_begin,
+ identifier_begin + identifier_len),
+ identifier_type);
+ const auto& range = idx.equal_range(t);
+ size_t erased = 0;
+ for (auto key = range.first; key != range.second;) {
+ if ((*key)->getIPv4SubnetID() != subnet_id) {
+ ++key;
+ // Skip hosts from other subnets.
+ continue;
+ }
+
+ key = idx.erase(key);
+ erased++;
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_DEL4)
+ .arg(erased)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin, identifier_len));
+
+ return (erased != 0);
+}
+
+size_t
+CfgHosts::delAll6(const SubnetID& subnet_id) {
+ // Delete IPv6 reservations.
+ HostContainer6Index2& idx6 = hosts6_.get<2>();
+ size_t erased_addresses = idx6.erase(subnet_id);
+
+ // Delete hosts.
+ HostContainerIndex3& idx = hosts_.get<3>();
+ size_t erased_hosts = idx.erase(subnet_id);
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_DEL_ALL_SUBNET6)
+ .arg(erased_hosts)
+ .arg(erased_addresses)
+ .arg(subnet_id);
+
+ return (erased_hosts);
+}
+
+bool
+CfgHosts::del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ HostContainerIndex0& idx = hosts_.get<0>();
+ HostContainer6Index3& idx6 = hosts6_.get<3>();
+
+ const auto t = boost::make_tuple(std::vector<uint8_t>(identifier_begin,
+ identifier_begin + identifier_len),
+ identifier_type);
+ const auto& range = idx.equal_range(t);
+ size_t erased_hosts = 0;
+ size_t erased_reservations = 0;
+ for (auto key = range.first; key != range.second;) {
+ if ((*key)->getIPv6SubnetID() != subnet_id) {
+ ++key;
+ // Skip hosts from other subnets.
+ continue;
+ }
+
+ // Delete host.
+ auto host_id = (*key)->getHostId();
+ key = idx.erase(key);
+ erased_hosts++;
+ // Delete reservations.
+ erased_reservations += idx6.erase(host_id);
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_CFG_DEL6)
+ .arg(erased_hosts)
+ .arg(erased_reservations)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin, identifier_len));
+
+ return (erased_hosts != 0);
+}
+
+bool
+CfgHosts::setIPReservationsUnique(const bool unique) {
+ ip_reservations_unique_ = unique;
+ return (true);
+}
+
+
+ElementPtr
+CfgHosts::toElement() const {
+ uint16_t family = CfgMgr::instance().getFamily();
+ if (family == AF_INET) {
+ return (toElement4());
+ } else if (family == AF_INET6) {
+ return (toElement6());
+ } else {
+ isc_throw(ToElementError, "CfgHosts::toElement: unknown "
+ "address family: " << family);
+ }
+}
+
+ElementPtr
+CfgHosts::toElement4() const {
+ CfgHostsList result;
+ // Iterate using arbitrary the index 0
+ const HostContainerIndex0& idx = hosts_.get<0>();
+ for (HostContainerIndex0::const_iterator host = idx.begin();
+ host != idx.end(); ++host) {
+
+ // Convert host to element representation
+ ElementPtr map = (*host)->toElement4();
+
+ // Push it on the list
+ SubnetID subnet_id = (*host)->getIPv4SubnetID();
+ result.add(subnet_id, map);
+ }
+ return (result.externalize());
+}
+
+ElementPtr
+CfgHosts::toElement6() const {
+ CfgHostsList result;
+ // Iterate using arbitrary the index 0
+ const HostContainerIndex0& idx = hosts_.get<0>();
+ for (HostContainerIndex0::const_iterator host = idx.begin();
+ host != idx.end(); ++host) {
+
+ // Convert host to Element representation
+ ElementPtr map = (*host)->toElement6();
+
+ // Push it on the list
+ SubnetID subnet_id = (*host)->getIPv6SubnetID();
+ result.add(subnet_id, map);
+ }
+ return (result.externalize());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h
new file mode 100644
index 0000000..fb6226a
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_hosts.h
@@ -0,0 +1,951 @@
+// Copyright (C) 2014-2023 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 CFG_HOSTS_H
+#define CFG_HOSTS_H
+
+#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_container.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/writable_host_data_source.h>
+#include <boost/shared_ptr.hpp>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents the host reservations specified in the configuration file.
+///
+/// This class holds a collection of the host reservations (@c Host objects)
+/// which can be retrieved using different search criteria.
+///
+/// In the typical case the reservations are searched using the client's MAC
+/// address or DUID and a subnet that the client is connected to. The
+/// reservations can be also retrieved using other parameters, such as reserved
+/// IP address.
+///
+/// The reservations are added to this object by the configuration parsers,
+/// when the new configuration is applied for the server. The reservations
+/// are retrieved by the @c HostMgr class when the server is allocating or
+/// renewing an address or prefix for the particular client.
+class CfgHosts : public BaseHostDataSource, public WritableHostDataSource,
+ public isc::data::CfgToElement {
+public:
+
+ /// @brief Destructor.
+ virtual ~CfgHosts() { }
+
+ /// @brief Return all hosts connected to any subnet for which reservations
+ /// have been made using a specified identifier.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for a specified identifier. This method may return multiple hosts
+ /// because a particular client may have reservations in multiple subnets.
+ ///
+ /// @param identifier_type One of the supported identifier types.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+ /// @brief Non-const version of the @c getAll const method.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for a specified identifier. This method may return multiple hosts
+ /// because a particular client may have reservations in multiple subnets.
+ ///
+ /// @param identifier_type One of the supported identifier types.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len);
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getAll4(const SubnetID& subnet_id);
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getAll6(const SubnetID& subnet_id);
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname.
+ ///
+ /// @param hostname The lower case hostname.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAllbyHostname(const std::string& hostname) const;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname.
+ ///
+ /// @param hostname The lower case hostname.
+ ///
+ /// @return Collection of @c Host objects.
+ virtual HostCollection
+ getAllbyHostname(const std::string& hostname);
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of @c Host objects.
+ virtual HostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id);
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of @c Host objects.
+ virtual HostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id);
+
+ /// @brief Returns range of hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection
+ getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects (may be empty).
+ virtual HostCollection
+ getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size);
+
+ /// @brief Returns range of hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection
+ getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects (may be empty).
+ virtual HostCollection
+ getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size);
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection
+ getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects (may be empty).
+ virtual HostCollection
+ getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size);
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection
+ getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects (may be empty).
+ virtual HostCollection
+ getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size);
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual HostCollection
+ getAll4(const asiolink::IOAddress& address);
+
+ /// @brief Returns a collection of hosts using the specified IPv6 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv6 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv6 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv6 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual HostCollection
+ getAll6(const asiolink::IOAddress& address);
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Non-const @c Host object for which reservation has been made
+ /// using the specified identifier.
+ virtual HostPtr
+ get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Const @c Host object using a specified IPv4 address.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv4 subnet and having
+ /// a reservation for a specified address.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv4 address within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv4 reservation. In that case, the number of hosts returned
+ /// by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv4 address in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv4 address is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv4 address is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Non-const @c Host object for which reservation has been made
+ /// using the specified identifier.
+ virtual HostPtr
+ get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Const @c Host object for which specified prefix is reserved.
+ virtual ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Non-const @c Host object for which specified prefix is
+ /// reserved.
+ virtual HostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address.
+ ///
+ /// @return Const @c Host object using a specified IPv6 address.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address.
+ ///
+ /// @return Const @c Host object using a specified IPv6 address.
+ virtual HostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& address);
+
+ /// @brief Returns all hosts connected to the IPv6 subnet and having
+ /// a reservation for a specified address or delegated prefix (lease).
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix. In that case, the number of hosts
+ /// returned by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ ///
+ /// @throw DuplicateHost If a host for a particular HW address or DUID
+ /// has already been added to the IPv4 or IPv6 subnet.
+ virtual void add(const HostPtr& host);
+
+ /// @brief Attempts to delete a hosts by address.
+ ///
+ /// This method supports both v4 and v6.
+ /// @todo: Not implemented.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete all hosts for a given IPv4 subnet.
+ ///
+ /// @param subnet_id Identifier of the subnet for which reservation should
+ /// be deleted.
+ /// @return Number of deleted hosts.
+ virtual size_t delAll4(const SubnetID& subnet_id);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier, identifier-type)
+ ///
+ /// This method supports v4 only.
+ /// @todo: Not implemented.
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false otherwise.
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete all hosts for a given IPv6 subnet.
+ ///
+ /// @param subnet_id Identifier of the subnet for which reservation should
+ /// be deleted.
+ /// @return Number of deleted hosts.
+ virtual size_t delAll6(const SubnetID& subnet_id);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier, identifier-type)
+ ///
+ /// This method supports v6 only.
+ /// @todo: Not implemented.
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false otherwise.
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("configuration file"));
+ }
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return always true because this data source supports both the case when
+ /// the addresses must be unique and when they may be non-unique.
+ virtual bool setIPReservationsUnique(const bool unique);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// host reservation lists are not autonomous so they are
+ /// not returned directly but with the subnet where they are
+ /// declared as:
+ /// @code
+ /// [
+ /// { "id": 123, "reservations": [ <resv1>, <resv2> ] },
+ /// { "id": 456, "reservations": [ <resv3 ] },
+ /// ...
+ /// ]
+ /// @endcode
+ ///
+ /// @ref isc::dhcp::CfgHostsList can be used to handle this
+ ///
+ /// @return a pointer to unparsed configuration
+ isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief Returns @c Host objects for the specific identifier and type.
+ ///
+ /// This private method is called by the @c CfgHosts::getAll
+ /// method which finds the @c Host objects using specified identifier.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param identifier_type The type of the supplied identifier.
+ /// @param identifier Pointer to a first byte of the identifier.
+ /// @param identifier_len Length of the identifier.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getAllInternal(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier,
+ const size_t identifier_len,
+ Storage& storage) const;
+
+ /// @brief Returns @c Host objects in a DHCPv4 subnet.
+ ///
+ /// This private method is called by the @c CfgHosts::getAll4
+ /// method which finds the @c Host objects in a specified subnet.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getAllInternal4(const SubnetID& subnet_id,
+ Storage& storage) const;
+
+ /// @brief Returns @c Host objects in a DHCPv6 subnet.
+ ///
+ /// This private method is called by the @c CfgHosts::getAll6
+ /// method which finds the @c Host objects in a specified subnet.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getAllInternal6(const SubnetID& subnet_id,
+ Storage& storage) const;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This private method is called by the @c CfgHosts::getAllbyHostname
+ /// method which finds the @c Host objects in a specified subnet.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getAllbyHostnameInternal(const std::string& hostname,
+ Storage& storage) const;
+
+ /// @brief Return all hosts with a hostname and a DHCPv4 subnet.
+ ///
+ /// This private method is called by the @c CfgHosts::getAllbyHostname4
+ /// method which finds the @c Host objects in a specified subnet.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getAllbyHostnameInternal4(const std::string& hostname,
+ const SubnetID& subnet_id,
+ Storage& storage) const;
+
+ /// @brief Return all hosts with a hostname and a DHCPv6 subnet.
+ ///
+ /// This private method is called by the @c CfgHosts::getAllbyHostname6
+ /// method which finds the @c Host objects in a specified subnet.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getAllbyHostnameInternal6(const std::string& hostname,
+ const SubnetID& subnet_id,
+ Storage& storage) const;
+
+ /// @brief Returns a page of @c Host objects in a DHCPv4 subnet.
+ ///
+ /// This private method is called by the @c CfgHosts::getPage4
+ /// method which finds the @c Host objects in a specified subnet.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getPageInternal4(const SubnetID& subnet_id,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size,
+ Storage& storage) const;
+
+ /// @brief Returns a page of @c Host objects in a DHCPv6 subnet.
+ ///
+ /// This private method is called by the @c CfgHosts::getPage6
+ /// method which finds the @c Host objects in a specified subnet.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getPageInternal6(const SubnetID& subnet_id,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size,
+ Storage& storage) const;
+
+ /// @brief Returns a page of @c Host objects.
+ ///
+ /// This private method is called by the @c CfgHosts::getPage4
+ /// and @c CfgHosts::getPage6 methods which find the @c Host objects.
+ /// The retrieved objects are appended to the @c storage container.
+ ///
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+ template<typename Storage>
+ void getPageInternal(uint64_t lower_host_id,
+ const HostPageSize& page_size,
+ Storage& storage) const;
+
+ /// @brief Returns @c Host objects for the specified IPv4 address.
+ ///
+ /// This private method is called by the @c CfgHosts::getAll4 methods
+ /// to retrieve the @c Host for which the specified IPv4 address is
+ /// reserved. The retrieved objects are appended to the @c storage
+ /// container.
+ ///
+ /// @param address IPv4 address.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+ template<typename Storage>
+ void getAllInternal4(const asiolink::IOAddress& address,
+ Storage& storage) const;
+
+ /// @brief Returns @c Host objects for the specified IPv6 address.
+ ///
+ /// This private method is called by the @c CfgHosts::getAll6 methods
+ /// to retrieve the @c Host for which the specified IPv6 address is
+ /// reserved. The retrieved objects are appended to the @c storage
+ /// container.
+ ///
+ /// @param address IPv6 address.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+ template<typename Storage>
+ void getAllInternal6(const asiolink::IOAddress& address,
+ Storage& storage) const;
+
+
+ /// @brief Returns @c Host objects for the specified (Subnet-id,IPv6 address) tuple.
+ ///
+ /// This private method is called by the @c CfgHosts::getAll6 methods
+ /// to retrieve the @c Host for which the specified IPv6 address is
+ /// reserved and is in specified subnet-id. The retrieved objects are
+ /// appended to the @c storage container.
+ ///
+ /// @param subnet_id Subnet Identifier.
+ /// @param address IPv6 address.
+ /// @param [out] storage Container to which the retrieved objects are
+ /// appended.
+ /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+ template<typename Storage>
+ void
+ getAllInternal6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address,
+ Storage& storage) const;
+
+ /// @brief Returns @c Host object connected to a subnet.
+ ///
+ /// This private method returns a pointer to the @c Host object using
+ /// a specified identifier and connected to an IPv4 or IPv6 subnet.
+ ///
+ /// @param subnet_id IPv4 or IPv6 subnet identifier.
+ /// @param subnet6 A boolean flag which indicates if the subnet identifier
+ /// points to a IPv4 (if false) or IPv6 subnet (if true).
+ /// @param identifier_type Identifier type.
+ /// @param identifier Pointer to a first byte of the buffer holding an
+ /// identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Pointer to the found host, or NULL if no host found.
+ /// @throw isc::dhcp::DuplicateHost if method found more than one matching
+ /// @c Host object.
+ HostPtr
+ getHostInternal(const SubnetID& subnet_id, const bool subnet6,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier,
+ const size_t identifier_len) const;
+
+ /// @brief Returns the @c Host object holding reservation for the IPv6
+ /// address and connected to the specific subnet.
+ ///
+ /// This private method is called by the public @c get6 method variants.
+ ///
+ /// @param subnet_id IPv6 subnet identifier.
+ /// @param address IPv6 address.
+ /// @tparam ReturnType One of @c HostPtr or @c ConstHostPtr
+ /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+ ///
+ /// @return Pointer to the found host, or NULL if no host found.
+ /// @throw isc::dhcp::DuplicateHost if method found more than one matching
+ /// @c Host object.
+ template<typename ReturnType, typename Storage>
+ ReturnType getHostInternal6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ template<typename ReturnType>
+ ReturnType getHostInternal6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// This is an internal method called by public @ref add. Contrary to its
+ /// name, this is useful for both IPv4 and IPv6 hosts, as this adds the
+ /// host to hosts_ storage that is shared by both families. Notes that
+ /// for IPv6 host additional steps may be required (see @ref add6).
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ ///
+ /// @throw DuplicateHost If a host for a particular HW address or DUID
+ /// has already been added to the IPv4 subnet.
+ virtual void add4(const HostPtr& host);
+
+ /// @brief Adds IPv6-specific reservation to hosts collection.
+ ///
+ /// This is an internal method called by public @ref add. This method adds
+ /// IPv6 reservations (IPv6 addresses or prefixes reserved) to the hosts6_
+ /// storage. Note the host has been added to the hosts_ already (in @ref add4).
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ ///
+ /// @throw DuplicateHost If a host for a particular HW address or DUID
+ /// or for the particular address or prefix has already been added to
+ /// the IPv6 subnet.
+ virtual void add6(const HostPtr& host);
+
+ /// @brief Next host id.
+ uint64_t next_host_id_ = 0;
+
+ /// @brief Multi-index container holding @c Host objects.
+ ///
+ /// It can be used for finding hosts by the following criteria:
+ /// - IPv4 address
+ /// - DUID
+ /// - HW/MAC address
+ /// - subnet ID
+ /// - host ID
+ HostContainer hosts_;
+
+ /// @brief Multi-index container holding @c Host objects with v6 reservations.
+ ///
+ /// It can be used for finding hosts by the following criteria:
+ /// - IPv6 address
+ /// - IPv6 prefix
+ HostContainer6 hosts6_;
+
+ /// @brief Holds the setting whether the IP reservations must be unique or
+ /// may be non-unique.
+ bool ip_reservations_unique_ = true;
+
+ /// @brief Unparse a configuration object (DHCPv4 reservations)
+ ///
+ /// @return a pointer to unparsed configuration
+ isc::data::ElementPtr toElement4() const;
+
+ /// @brief Unparse a configuration object (DHCPv6 reservations)
+ ///
+ /// @return a pointer to unparsed configuration
+ isc::data::ElementPtr toElement6() const;
+};
+
+/// @name Pointers to the @c CfgHosts objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgHosts> CfgHostsPtr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgHosts> ConstCfgHostsPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_HOSTS_H
diff --git a/src/lib/dhcpsrv/cfg_hosts_util.cc b/src/lib/dhcpsrv/cfg_hosts_util.cc
new file mode 100644
index 0000000..11c9e87
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_hosts_util.cc
@@ -0,0 +1,96 @@
+// Copyright (C) 2017-2018 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 <cc/data.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <exceptions/exceptions.h>
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void CfgHostsList::internalize(ConstElementPtr list) {
+ if (!list) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "argument is NULL");
+ }
+ if (list->getType() != Element::list) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "argument is not a list Element");
+ }
+ for (size_t i = 0; i < list->size(); ++i) {
+ ConstElementPtr item = list->get(i);
+ if (!item) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "null pointer from the list at " << i);
+ }
+ if (item->getType() != Element::map) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "not a map from the list at " << i);
+ }
+ if (item->size() != 2) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "bad map size from the list at " << i);
+ }
+ ConstElementPtr id = item->get("id");
+ if (!id) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "no id from a map at " << i);
+ }
+ if (id->getType() != Element::integer) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "not integer id from a map at " <<i);
+ }
+ SubnetID subnet_id = static_cast<SubnetID>(id->intValue());
+ ConstElementPtr resvs = item->get("reservations");
+ if (!resvs) {
+ isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+ "no reservations for subnet ID " << subnet_id);
+ }
+ map_.insert(std::make_pair(subnet_id,
+ boost::const_pointer_cast<Element>(resvs)));
+ }
+}
+
+ElementPtr CfgHostsList::externalize() const {
+ ElementPtr result = Element::createList();
+ for (CfgHostsMap::const_iterator item = map_.begin();
+ item != map_.end(); ++item) {
+ ElementPtr pair = Element::createMap();
+ pair->set("id", Element::create(static_cast<int64_t>(item->first)));
+ pair->set("reservations", item->second);
+ result->add(pair);
+ }
+ return (result);
+}
+
+void CfgHostsList::add(SubnetID id, isc::data::ElementPtr resv) {
+ CfgHostsMap::iterator item = map_.find(id);
+ if (item != map_.end()) {
+ item->second->add(resv);
+ } else {
+ ElementPtr resvs = Element::createList();
+ resvs->add(resv);
+ map_.insert(std::make_pair(id, resvs));
+ }
+}
+
+ConstElementPtr CfgHostsList::get(SubnetID id) const {
+ CfgHostsMap::const_iterator item = map_.find(id);
+ if (item != map_.end()) {
+ return (item->second);
+ } else {
+ return (Element::createList());
+ }
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/cfg_hosts_util.h b/src/lib/dhcpsrv/cfg_hosts_util.h
new file mode 100644
index 0000000..67803b9
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_hosts_util.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2017 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 CFG_HOSTS_UTIL_H
+#define CFG_HOSTS_UTIL_H
+
+#include <cc/data.h>
+#include <dhcpsrv/subnet_id.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class to represent host reservation configurations
+/// internally as a map keyed by subnet IDs, externally as a list Element.
+class CfgHostsList {
+public:
+
+ /// The type of the internal map
+ typedef std::map<SubnetID, isc::data::ElementPtr> CfgHostsMap;
+
+ /// @brief Internalize a list Element
+ ///
+ /// This method gets a list Element and builds the internal map from it.
+ ///
+ /// @param list the list Element
+ void internalize(isc::data::ConstElementPtr list);
+
+ /// @brief Externalize the map to a list Element
+ ///
+ /// @return a list Element representing all host reservations
+ isc::data::ElementPtr externalize() const;
+
+ /// @brief Add a host reservation to the map
+ void add(SubnetID id, isc::data::ElementPtr resv);
+
+ /// @brief Return the host reservations for a subnet ID
+ ///
+ /// @param id the subnet ID
+ /// @return a list Element with host reservations
+ isc::data::ConstElementPtr get(SubnetID id) const;
+
+private:
+ /// @brief The internal map
+ CfgHostsMap map_;
+};
+
+}
+}
+
+#endif // CFG_HOSTS_UTIL_H
diff --git a/src/lib/dhcpsrv/cfg_iface.cc b/src/lib/dhcpsrv/cfg_iface.cc
new file mode 100644
index 0000000..e129a4f
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_iface.cc
@@ -0,0 +1,607 @@
+// Copyright (C) 2014-2022 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 <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <util/reconnect_ctl.h>
+#include <util/multi_threading_mgr.h>
+#include <util/strutil.h>
+#include <algorithm>
+#include <functional>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp {
+
+const char* CfgIface::ALL_IFACES_KEYWORD = "*";
+
+CfgIface::OpenSocketsFailedCallback CfgIface::open_sockets_failed_callback_ = 0;
+
+CfgIface::CfgIface()
+ : wildcard_used_(false), socket_type_(SOCKET_RAW), re_detect_(false),
+ service_socket_require_all_(false), service_sockets_retry_wait_time_(5000),
+ service_sockets_max_retries_(0),
+ outbound_iface_(SAME_AS_INBOUND) {
+}
+
+void
+CfgIface::closeSockets() const {
+ IfaceMgr::instance().closeSockets();
+}
+
+bool
+CfgIface::equals(const CfgIface& other) const {
+ return (iface_set_ == other.iface_set_ &&
+ address_map_ == other.address_map_ &&
+ wildcard_used_ == other.wildcard_used_ &&
+ socket_type_ == other.socket_type_);
+}
+
+bool
+CfgIface::multipleAddressesPerInterfaceActive() {
+ for (IfacePtr iface : IfaceMgr::instance().getIfaces()) {
+ if (iface->countActive4() > 1) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+void
+CfgIface::openSockets(const uint16_t family, const uint16_t port,
+ const bool use_bcast) {
+ // Close any open sockets because we're going to modify some properties
+ // of the IfaceMgr. Those modifications require that sockets are closed.
+ closeSockets();
+ // The loopback interface can be used only when:
+ // - UDP socket will be used, i.e. not IPv4 and RAW socket
+ // - the loopback interface is in the interface set or the address map.
+ bool loopback_used_ = false;
+ if ((family == AF_INET6) || (socket_type_ == SOCKET_UDP)) {
+ // Check interface set
+ for (IfaceSet::const_iterator iface_name = iface_set_.begin();
+ iface_name != iface_set_.end(); ++iface_name) {
+ IfacePtr iface = IfaceMgr::instance().getIface(*iface_name);
+ if (iface && iface->flag_loopback_) {
+ loopback_used_ = true;
+ }
+ }
+ // Check address map
+ for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
+ unicast != address_map_.end(); ++unicast) {
+ IfacePtr iface = IfaceMgr::instance().getIface(unicast->first);
+ if (iface && iface->flag_loopback_) {
+ loopback_used_ = true;
+ }
+ }
+ }
+ // If wildcard interface '*' was not specified, set all interfaces to
+ // inactive state. We will later enable them selectively using the
+ // interface names specified by the user. If wildcard interface was
+ // specified, mark all interfaces active. Mark loopback inactive when
+ // not explicitly allowed.
+ setState(family, !wildcard_used_, !loopback_used_);
+ IfaceMgr& iface_mgr = IfaceMgr::instance();
+ // Remove selection of unicast addresses from all interfaces.
+ iface_mgr.clearUnicasts();
+ // Allow the loopback interface when required.
+ iface_mgr.setAllowLoopBack(loopback_used_);
+ // For the DHCPv4 server, if the user has selected that raw sockets
+ // should be used, we will try to configure the Interface Manager to
+ // support the direct responses to the clients that don't have the
+ // IP address. This should effectively turn on the use of raw
+ // sockets. However, this may be unsupported on some operating
+ // systems, so there is no guarantee.
+ if ((family == AF_INET) && (!IfaceMgr::instance().isTestMode())) {
+ iface_mgr.setMatchingPacketFilter(socket_type_ == SOCKET_RAW);
+ if ((socket_type_ == SOCKET_RAW) &&
+ !iface_mgr.isDirectResponseSupported()) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED);
+ }
+ }
+ // If there is no wildcard interface specified, we will have to iterate
+ // over the names specified by the caller and enable them.
+ if (!wildcard_used_) {
+ for (IfaceSet::const_iterator iface_name = iface_set_.begin();
+ iface_name != iface_set_.end(); ++iface_name) {
+ IfacePtr iface = IfaceMgr::instance().getIface(*iface_name);
+ // This shouldn't really happen because we are checking the
+ // names of interfaces when they are being added (use()
+ // function). But, if someone has triggered detection of
+ // interfaces since then, some interfaces may have disappeared.
+ if (iface == NULL) {
+ isc_throw(Unexpected,
+ "fail to open socket on interface '"
+ << *iface_name << "' as this interface doesn't"
+ " exist");
+
+ } else if (family == AF_INET) {
+ iface->inactive4_ = false;
+ setIfaceAddrsState(family, true, *iface);
+
+ } else {
+ iface->inactive6_ = false;
+ }
+ }
+ }
+
+ // Select unicast sockets for DHCPv6 or activate specific IPv4 addresses
+ // for DHCPv4.
+ for (ExplicitAddressMap::const_iterator unicast = address_map_.begin();
+ unicast != address_map_.end(); ++unicast) {
+ IfacePtr iface = IfaceMgr::instance().getIface(unicast->first);
+ if (iface == NULL) {
+ isc_throw(Unexpected,
+ "fail to open unicast socket on interface '"
+ << unicast->first << "' as this interface doesn't"
+ " exist");
+ }
+ if (family == AF_INET6) {
+ iface->addUnicast(unicast->second);
+ iface->inactive6_ = false;
+
+ } else {
+ iface->setActive(unicast->second, true);
+ iface->inactive4_ = false;
+ }
+ }
+
+ // Use broadcast only if we're using raw sockets. For the UDP sockets,
+ // we only handle the relayed (unicast) traffic.
+ const bool can_use_bcast = use_bcast && (socket_type_ == SOCKET_RAW);
+
+ // Opening multiple raw sockets handling brodcast traffic on the single
+ // interface may lead to processing the same message multiple times.
+ // We don't prohibit such configuration because raw sockets can as well
+ // handle the relayed traffic. We have to issue a warning, however, to
+ // draw administrator's attention.
+ if (family == AF_INET && can_use_bcast && multipleAddressesPerInterfaceActive()) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE);
+ }
+
+ reconnect_ctl_ = makeReconnectCtl();
+ auto sopen = openSocketsWithRetry(reconnect_ctl_, family, port, can_use_bcast);
+
+ if (!sopen) {
+ // If no socket were opened, log a warning because the server will
+ // not respond to any queries.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_NO_SOCKETS_OPEN);
+ }
+}
+
+std::pair<bool, bool>
+CfgIface::openSocketsForFamily(const uint16_t family, const uint16_t port,
+ const bool can_use_bcast, const bool skip_opened) {
+ bool no_errors = true;
+
+ // Set the callbacks which are called when the socket fails to open
+ // for some specific interface.
+ auto error_callback = [&no_errors](const std::string& errmsg) {
+ socketOpenErrorHandler(errmsg);
+ no_errors = false;
+ };
+
+ IfaceMgr::instance().detectIfaces(true);
+
+ bool sopen = false;
+ if (family == AF_INET) {
+ sopen = IfaceMgr::instance().openSockets4(port, can_use_bcast,
+ error_callback, skip_opened);
+ } else {
+ // use_bcast is ignored for V6.
+ sopen = IfaceMgr::instance().openSockets6(port, error_callback,
+ skip_opened);
+ }
+
+ return (std::make_pair(sopen, no_errors));
+}
+
+ReconnectCtlPtr CfgIface::makeReconnectCtl() const {
+ // Create unique timer name per instance.
+ std::string timer_name = "SocketReopenTimer";
+
+ auto on_fail_action = OnFailAction::SERVE_RETRY_CONTINUE;
+ if (CfgIface::getServiceSocketsRequireAll()) {
+ on_fail_action = OnFailAction::SERVE_RETRY_EXIT;
+ }
+
+ // Add one attempt for an initial call.
+ auto reconnect_ctl = boost::make_shared<ReconnectCtl>("Socket", timer_name,
+ CfgIface::getServiceSocketsMaxRetries(),
+ CfgIface::getServiceSocketsRetryWaitTime(),
+ on_fail_action);
+
+ return (reconnect_ctl);
+}
+
+bool
+CfgIface::openSocketsWithRetry(ReconnectCtlPtr reconnect_ctl,
+ const uint16_t family, const uint16_t port,
+ const bool can_use_bcast) {
+ MultiThreadingCriticalSection cs;
+
+ // Skip opened sockets in the retry calls.
+ bool is_initial_call = (reconnect_ctl->retriesLeft() == reconnect_ctl->maxRetries());
+ auto result_pair = openSocketsForFamily(family, port, can_use_bcast, !is_initial_call);
+ bool sopen = result_pair.first;
+ bool has_errors = !result_pair.second;
+
+ auto timer_name = reconnect_ctl->timerName();
+
+ // On the initial call, unregister the previous, pending timer.
+ if (is_initial_call && TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Has errors and can retry
+ if (has_errors && reconnect_ctl->retriesLeft() > 0) {
+ // Initial call is excluded from retries counter.
+ reconnect_ctl->checkRetries();
+ // Start the timer.
+ if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->registerTimer(timer_name,
+ std::bind(&CfgIface::openSocketsWithRetry,
+ reconnect_ctl, family,
+ port, can_use_bcast),
+ reconnect_ctl->retryInterval(),
+ asiolink::IntervalTimer::ONE_SHOT);
+ }
+ TimerMgr::instance()->setup(timer_name);
+ } else {
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+ // Has errors but retries exceed
+ if (has_errors) {
+ if (open_sockets_failed_callback_) {
+ open_sockets_failed_callback_(reconnect_ctl);
+ }
+ }
+ }
+
+ return (sopen);
+}
+
+void
+CfgIface::reset() {
+ wildcard_used_ = false;
+ iface_set_.clear();
+ address_map_.clear();
+ useSocketType(AF_INET, SOCKET_RAW);
+}
+
+void
+CfgIface::setState(const uint16_t family, const bool inactive,
+ const bool loopback_inactive) const {
+ for (IfacePtr iface : IfaceMgr::instance().getIfaces()) {
+ bool iface_inactive = iface->flag_loopback_ ? loopback_inactive : inactive;
+ if (family == AF_INET) {
+ iface->inactive4_ = iface_inactive;
+ } else {
+ iface->inactive6_ = iface_inactive;
+ }
+
+ // Activate/deactivate all addresses.
+ setIfaceAddrsState(family, !inactive, *iface);
+ }
+}
+
+void
+CfgIface::setIfaceAddrsState(const uint16_t family, const bool active,
+ Iface& iface) const {
+ // Activate/deactivate all addresses.
+ for (Iface::Address addr : iface.getAddresses()) {
+ if (addr.get().getFamily() == family) {
+ iface.setActive(addr.get(), active);
+ }
+ }
+}
+
+void
+CfgIface::socketOpenErrorHandler(const std::string& errmsg) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_OPEN_SOCKET_FAIL).arg(errmsg);
+}
+
+std::string
+CfgIface::socketTypeToText() const {
+ switch (socket_type_) {
+ case SOCKET_RAW:
+ return ("raw");
+
+ case SOCKET_UDP:
+ return ("udp");
+
+ default:
+ ;
+ }
+
+ isc_throw(Unexpected, "unsupported socket type " << socket_type_);
+}
+
+CfgIface::SocketType
+CfgIface::textToSocketType(const std::string& socket_type_name) const {
+ if (socket_type_name == "udp") {
+ return (SOCKET_UDP);
+
+ } else if (socket_type_name == "raw") {
+ return (SOCKET_RAW);
+
+ } else {
+ isc_throw(InvalidSocketType, "unsupported socket type '"
+ << socket_type_name << "'");
+ }
+}
+
+CfgIface::OutboundIface
+CfgIface::getOutboundIface() const {
+ return (outbound_iface_);
+}
+
+std::string
+CfgIface::outboundTypeToText() const {
+ switch (outbound_iface_) {
+ case SAME_AS_INBOUND:
+ return ("same-as-inbound");
+ case USE_ROUTING:
+ return ("use-routing");
+ default:
+ isc_throw(Unexpected, "unsupported outbound-type " << socket_type_);
+ }
+
+}
+
+CfgIface::OutboundIface
+CfgIface::textToOutboundIface(const std::string& txt) {
+ if (txt == "same-as-inbound") {
+ return (SAME_AS_INBOUND);
+
+ } else if (txt == "use-routing") {
+ return (USE_ROUTING);
+
+ } else {
+ isc_throw(BadValue, "unsupported outbound interface type '"
+ << txt << "'");
+ }
+}
+
+void
+CfgIface::setOutboundIface(const OutboundIface& outbound_iface) {
+ outbound_iface_ = outbound_iface;
+}
+
+void
+CfgIface::use(const uint16_t family, const std::string& iface_name) {
+ // The interface name specified may have two formats:
+ // - "interface-name", e.g. eth0
+ // - "interface-name/address", e.g. eth0/10.0.0.1 or eth/2001:db8:1::1
+ // The latter format is used to open unicast socket on the specified
+ // interface. Here we are detecting which format was used and we strip
+ // all extraneous spaces.
+ size_t pos = iface_name.find("/");
+ std::string name;
+ std::string addr_str;
+ // There is no unicast address so the whole string is an interface name.
+ if (pos == std::string::npos) {
+ name = util::str::trim(iface_name);
+ if (name.empty()) {
+ isc_throw(InvalidIfaceName,
+ "empty interface name used in configuration");
+
+ } else if (name != ALL_IFACES_KEYWORD) {
+ if (IfaceMgr::instance().getIface(name) == NULL) {
+ isc_throw(NoSuchIface, "interface '" << name
+ << "' doesn't exist in the system");
+ }
+
+ } else if (wildcard_used_) {
+ isc_throw(DuplicateIfaceName, "the wildcard interface '"
+ << ALL_IFACES_KEYWORD << "' can only be specified once");
+
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
+ wildcard_used_ = true;
+
+ }
+
+ } else {
+ // The interface name includes the address on which the socket should
+ // be opened, and we need to split interface name and the address to
+ // two variables.
+ name = util::str::trim(iface_name.substr(0, pos));
+ addr_str = util::str::trim(iface_name.substr(pos + 1));
+
+ // Interface name must not be empty.
+ if (name.empty()) {
+ isc_throw(InvalidIfaceName,
+ "empty interface name specified in the"
+ " interface configuration");
+
+ }
+ // An address following the interface name must not be empty.
+ if (addr_str.empty()) {
+ isc_throw(InvalidIfaceName,
+ "empty address specified in the interface"
+ << " configuration");
+
+ }
+
+ // Interface name must not be the wildcard name.
+ if (name == ALL_IFACES_KEYWORD) {
+ isc_throw(InvalidIfaceName,
+ "wildcard interface name '" << ALL_IFACES_KEYWORD
+ << "' must not be used in conjunction with an"
+ " address");
+
+ }
+
+ // Interface must exist.
+ IfacePtr iface = IfaceMgr::instance().getIface(name);
+ if (!iface) {
+ isc_throw(NoSuchIface, "interface '" << name
+ << "' doesn't exist in the system");
+
+ }
+
+ // Convert address string. This may throw an exception if the address
+ // is invalid.
+ IOAddress addr(addr_str);
+
+ // Validate V6 address.
+ if (family == AF_INET6) {
+ // Check that the address is a valid unicast address.
+ if (!addr.isV6() || addr.isV6Multicast()) {
+ isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
+ " a valid IPv6 unicast address");
+ }
+
+ // There are valid cases where link local address can be specified to
+ // receive unicast traffic, e.g. sent by relay agent.
+ if (addr.isV6LinkLocal()) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL)
+ .arg(addr.toText()).arg(name);
+ }
+
+ } else {
+ if (!addr.isV4()) {
+ isc_throw(InvalidIfaceName, "address '" << addr << "' is not"
+ " a valid IPv4 address");
+ }
+ }
+
+ // Interface must have this address assigned.
+ if (!iface->hasAddress(addr)) {
+ isc_throw(NoSuchAddress,
+ "interface '" << name << "' doesn't have address '"
+ << addr << "' assigned");
+ }
+
+ // For the IPv4, if the interface name was specified (instead of the interface-
+ // address tuple) all addresses are already activated. Adding an explicit address
+ // for the interface should result in error.
+ if ((family == AF_INET) && (iface_set_.find(iface->getName()) != iface_set_.end())) {
+ isc_throw(DuplicateIfaceName, "interface '" << iface->getName()
+ << "' has already been selected");
+ }
+
+ // Check if the address hasn't been selected already.
+ std::pair<const std::string, IOAddress> iface_address_tuple(name, addr);
+ if (std::find(address_map_.begin(), address_map_.end(),
+ iface_address_tuple) != address_map_.end()) {
+ isc_throw(DuplicateAddress, "must not select address '"
+ << addr << "' for interface '" << name << "' "
+ "because this address is already selected");
+ }
+
+ if (family == AF_INET6) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_UNICAST)
+ .arg(addr.toText()).arg(name);
+
+ } else {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_ADDRESS)
+ .arg(addr.toText()).arg(name);
+ }
+ address_map_.insert(std::pair<std::string, IOAddress>(name, addr));
+ }
+
+ // If interface name was explicitly specified without an address, we will
+ // insert the interface name to the set of enabled interfaces.
+ if ((name != ALL_IFACES_KEYWORD) && addr_str.empty()) {
+ // An interface has been selected or an IPv4 address on this interface
+ // has been selected it is not allowed to select the whole interface.
+ if ((iface_set_.find(name) != iface_set_.end()) ||
+ ((family == AF_INET) && address_map_.count(name) > 0)) {
+ isc_throw(DuplicateIfaceName, "interface '" << name
+ << "' has already been specified");
+ }
+
+ // Log that we're listening on the specific interface and that the
+ // address is not explicitly specified.
+ if (addr_str.empty()) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_ADD_IFACE).arg(name);
+ }
+ iface_set_.insert(name);
+ }
+}
+
+void
+CfgIface::useSocketType(const uint16_t family,
+ const SocketType& socket_type) {
+ if (family != AF_INET) {
+ isc_throw(InvalidSocketType, "socket type must not be specified for"
+ " the DHCPv6 server");
+ }
+ socket_type_ = socket_type;
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT)
+ .arg(socketTypeToText());
+}
+
+void
+CfgIface::useSocketType(const uint16_t family,
+ const std::string& socket_type_name) {
+ useSocketType(family, textToSocketType(socket_type_name));
+}
+
+ElementPtr
+CfgIface::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set user context
+ contextToElement(result);
+
+ // Set interfaces
+ ElementPtr ifaces = Element::createList();
+ if (wildcard_used_) {
+ ifaces->add(Element::create(std::string(ALL_IFACES_KEYWORD)));
+ }
+ for (IfaceSet::const_iterator iface = iface_set_.cbegin();
+ iface != iface_set_.cend(); ++iface) {
+ ifaces->add(Element::create(*iface));
+ }
+ for (ExplicitAddressMap::const_iterator address = address_map_.cbegin();
+ address != address_map_.cend(); ++address) {
+ std::string spec = address->first + "/" + address->second.toText();
+ ifaces->add(Element::create(spec));
+ }
+ result->set("interfaces", ifaces);
+
+ // Set dhcp-socket-type (no default because it is DHCPv4 specific)
+ // @todo emit raw if and only if DHCPv4
+ if (socket_type_ != SOCKET_RAW) {
+ result->set("dhcp-socket-type", Element::create(std::string("udp")));
+ }
+
+ if (outbound_iface_ != SAME_AS_INBOUND) {
+ result->set("outbound-interface", Element::create(outboundTypeToText()));
+ }
+
+ // Set re-detect
+ result->set("re-detect", Element::create(re_detect_));
+
+ // Set server socket binding
+ if (service_socket_require_all_) {
+ result->set("service-sockets-require-all", Element::create(service_socket_require_all_));
+ }
+
+ if (service_sockets_max_retries_ != 0) {
+ result->set("service-sockets-max-retries", Element::create(static_cast<int>(service_sockets_max_retries_)));
+ // If the max retries parameter is zero, the wait time is not used.
+ result->set("service-sockets-retry-wait-time", Element::create(static_cast<int>(service_sockets_retry_wait_time_)));
+ }
+
+ return (result);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/cfg_iface.h b/src/lib/dhcpsrv/cfg_iface.h
new file mode 100644
index 0000000..2ee8443
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_iface.h
@@ -0,0 +1,509 @@
+// Copyright (C) 2014-2022 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 CFG_IFACE_H
+#define CFG_IFACE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <util/reconnect_ctl.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <set>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when duplicated interface names specified.
+class DuplicateIfaceName : public Exception {
+public:
+ DuplicateIfaceName(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when specified interface name is invalid.
+class InvalidIfaceName : public Exception {
+public:
+ InvalidIfaceName(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when specified interface doesn't exist in a system.
+class NoSuchIface : public Exception {
+public:
+ NoSuchIface(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when duplicated address specified.
+class DuplicateAddress : public Exception {
+public:
+ DuplicateAddress(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when specified unicast address is not assigned
+/// to the interface specified.
+class NoSuchAddress : public Exception {
+public:
+ NoSuchAddress(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when invalid socket type has been specified
+/// for the given family.
+class InvalidSocketType : public Exception {
+public:
+ InvalidSocketType(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents selection of interfaces for DHCP server.
+///
+/// This class manages selection of interfaces on which the DHCP server is
+/// listening to queries. The interfaces are selected in the server
+/// configuration by their names or by the pairs of interface names and
+/// addresses, e.g. eth0/2001:db8:1::1 (DHCPv6) or e.g. eth0/192.168.8.1
+/// (DHCPv4).
+///
+/// This class also accepts "wildcard" interface name which, if specified,
+/// instructs the server to listen on all available interfaces.
+///
+/// Once interfaces have been specified the sockets (either IPv4 or IPv6)
+/// can be opened by calling @c CfgIface::openSockets function. Kea
+/// offers configuration parameters to control the types of sockets to be
+/// opened by the DHCPv4 server. In small deployments it is requires that
+/// the server can handle messages from the directly connected clients
+/// which don't have an address yet. Unicasting the response to such
+/// client is possible by the use of raw sockets. In larger deployments
+/// it is often the case that whole traffic is received via relays, and
+/// in such case the use of UDP sockets is preferred. The type of the
+/// sockets to be opened is specified using one of the
+/// @c CfgIface::useSocketType method variants. The @c CfgIface::SocketType
+/// enumeration specifies the possible values.
+///
+/// @warning This class makes use of the AF_INET and AF_INET6 family literals,
+/// but it doesn't verify that the address family value passed as @c uint16_t
+/// parameter is equal to one of them. It is a callers responsibility to
+/// guarantee that the address family value is correct.
+///
+/// The interface name is passed as an argument of the @ref CfgIface::use
+/// function which controls the selection of the interface on which the
+/// DHCP queries should be received by the server. The interface name
+/// passed as the argument of this function may appear in one of the following
+/// formats:
+/// - interface-name, e.g. eth0
+/// - interface-name/address, e.g. eth0/2001:db8:1::1 or eth0/192.168.8.1
+///
+/// Extraneous spaces surrounding the interface name and/or address
+/// are accepted. For example: eth0 / 2001:db8:1::1 will be accepted.
+///
+/// When only interface name is specified (without an address) it is allowed
+/// to use the "wildcard" interface name (*) which indicates that the server
+/// should open sockets on all interfaces. When IPv6 is in use, the sockets
+/// will be bound to the link local addresses. Wildcard interface names are
+/// not allowed when specifying a unicast address. For example:
+/// */2001:db8:1::1 is not allowed.
+///
+/// The DHCPv6 configuration accepts simultaneous use of the "interface-name"
+/// and "interface-name/address" tuple for the same interface, e.g.
+/// "eth0", "eth0/2001:db8:1::1" specifies that the server should open a
+/// socket and bind to link local address as well as open a socket bound to
+/// the specified unicast address.
+///
+/// The DHCPv4 configuration doesn't accept the simultaneous use of the
+/// "interface-name" and the "interface-name/address" tuple for the
+/// given interface. When the "interface-name" is specified it implies
+/// that the sockets will be opened on for all addresses configured on
+/// this interface. If the tuple of "interface-name/address" is specified
+/// there will be only one socket opened and bound to the specified address.
+/// This socket will be configured to listen to the broadcast messages
+/// reaching the interface as well as unicast messages sent to the address
+/// to which it is bound. It is allowed to select multiple addresses on the
+/// particular interface explicitly, e.g. "eth0/192.168.8.1",
+/// "eth0/192.168.8.2".
+class CfgIface : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+
+ /// @brief Socket type used by the DHCPv4 server.
+ enum SocketType {
+ /// Raw socket, used for direct DHCPv4 traffic.
+ SOCKET_RAW,
+ /// Datagram socket, i.e. IP/UDP socket.
+ SOCKET_UDP
+ };
+
+ /// @brief Indicates how outbound interface is selected for relayed traffic.
+ enum OutboundIface {
+ /// Server sends responses over the same interface on which queries are
+ /// received.
+ SAME_AS_INBOUND,
+ /// Server uses routing to determine the right interface to send response.
+ USE_ROUTING
+ };
+
+ /// @brief Keyword used to enable all interfaces.
+ ///
+ /// This keyword can be used instead of the interface name to specify
+ /// that DHCP server should listen on all interfaces.
+ static const char* ALL_IFACES_KEYWORD;
+
+ /// @brief Constructor.
+ CfgIface();
+
+ /// @brief Convenience function which closes all open sockets.
+ /// It stops the receiver thread too.
+ void closeSockets() const;
+
+ /// @brief Compares two @c CfgIface objects for equality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool equals(const CfgIface& other) const;
+
+ /// @brief Tries to open sockets on selected interfaces.
+ ///
+ /// This function opens sockets bound to link-local address as well as
+ /// sockets bound to unicast address. See @c CfgIface::use function
+ /// documentation for details how to specify interfaces and unicast
+ /// addresses to bind the sockets to.
+ /// This function starts the family receiver.
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param port Port number to be used to bind sockets to.
+ /// @param use_bcast A boolean flag which indicates if the broadcast
+ /// traffic should be received through the socket. This parameter is
+ /// ignored for IPv6.
+ void openSockets(const uint16_t family, const uint16_t port,
+ const bool use_bcast = true);
+
+ /// @brief Puts the interface configuration into default state.
+ ///
+ /// This function removes interface names from the set.
+ void reset();
+
+ /// @brief Select interface to be used to receive DHCP traffic.
+ ///
+ /// @ref CfgIface for a detail explanation of the interface name argument.
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param iface_name Explicit interface name, a wildcard name (*) of
+ /// the interface(s) or the pair of interface/unicast-address to be used
+ /// to receive DHCP traffic.
+ ///
+ /// @throw InvalidIfaceName If the interface name is incorrect, e.g. empty.
+ /// @throw NoSuchIface If the specified interface is not present.
+ /// @throw NoSuchAddress If the specified unicast address is not assigned
+ /// to the interface.
+ /// @throw DuplicateIfaceName If the interface is already selected, i.e.
+ /// @throw IOError when specified unicast address is invalid.
+ /// @c CfgIface::use has been already called for this interface.
+ void use(const uint16_t family, const std::string& iface_name);
+
+ /// @brief Sets the specified socket type to be used by the server.
+ ///
+ /// Supported socket types for DHCPv4 are:
+ /// - @c SOCKET_RAW
+ /// - @c SOCKET_UDP
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param socket_type Socket type.
+ ///
+ /// @throw InvalidSocketType if the unsupported socket type has been
+ /// specified for the address family. Currently, the socket type
+ /// can only be selected for the AF_INET family.
+ void useSocketType(const uint16_t family, const SocketType& socket_type);
+
+ /// @brief Sets the specified socket type specified in textual format.
+ ///
+ /// The following names of the socket types are currently supported, and
+ /// can be passed in the @c socket_type parameter:
+ /// - raw - for raw sockets,
+ /// - udp - for the IP/UDP datagram sockets,
+ ///
+ /// @param family Address family (AF_INET or AF_INET6)
+ /// @param socket_type_name Socket type in the textual format.
+ ///
+ /// @throw InvalidSocketType if the unsupported socket type has been
+ /// specified for the address family. Currently, the socket type
+ /// can only be selected for the AF_INET family.
+ void useSocketType(const uint16_t family,
+ const std::string& socket_type_name);
+
+ /// @brief Returns DHCP socket type used by the server.
+ SocketType getSocketType() const {
+ return (socket_type_);
+ }
+
+ /// @brief Returns the socket type in the textual format.
+ std::string socketTypeToText() const;
+
+ /// @brief Sets outbound interface selection mode.
+ ///
+ /// @param outbound_iface New outbound interface selection mode setting.
+ void setOutboundIface(const OutboundIface& outbound_iface);
+
+ /// @brief Returns outbound interface selection mode.
+ ///
+ /// @return Outbound interface selection mode.
+ OutboundIface getOutboundIface() const;
+
+ /// @brief Returns outbound interface selection mode as string.
+ ///
+ /// @return text representation of the outbound interface selection mode.
+ std::string outboundTypeToText() const;
+
+ /// @brief Converts text to outbound interface selection mode.
+ ///
+ /// @param txt either 'same-as-inbound' or 'use-routing'
+ /// @return Outbound interface selection mode.
+ static OutboundIface textToOutboundIface(const std::string& txt);
+
+ /// @brief Converts the socket type in the textual format to the type
+ /// represented by the @c SocketType.
+ ///
+ /// @throw InvalidSocketType if the specified value of the @c socket_type_name
+ /// is invalid.
+ SocketType textToSocketType(const std::string& socket_type_name) const;
+
+ /// @brief Equality operator.
+ ///
+ /// @param other Object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool operator==(const CfgIface& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other Object to be compared with this object.
+ ///
+ /// @return true if objects are not equal, false otherwise.
+ bool operator!=(const CfgIface& other) const {
+ return (!equals(other));
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Set the re-detect flag
+ ///
+ /// @param re_detect the new value of the flag
+ void setReDetect(bool re_detect) {
+ re_detect_ = re_detect;
+ }
+
+ /// @brief Set flag that Kea must successfully bind all socket services on init.
+ ///
+ /// @param require_all true if all sockets must be bound, false otherwise.
+ void setServiceSocketsRequireAll(bool require_all) {
+ service_socket_require_all_ = require_all;
+ }
+
+ /// @brief Indicates that Kea must successfully bind all socket services on init.
+ ///
+ /// @return true if all sockets must be bound, false otherwise.
+ bool getServiceSocketsRequireAll() const {
+ return (service_socket_require_all_);
+ }
+
+ /// @brief Set the socket service binding retry interval between attempts.
+ ///
+ /// @param interval Milliseconds between attempts.
+ void setServiceSocketsRetryWaitTime(uint32_t interval) {
+ service_sockets_retry_wait_time_ = interval;
+ }
+
+ /// @brief Indicates the socket service binding retry interval between attempts.
+ ///
+ /// @return Milliseconds between attempts.
+ uint32_t getServiceSocketsRetryWaitTime() const {
+ return (service_sockets_retry_wait_time_);
+ }
+
+ /// @brief Set a maximum number of service sockets bind attempts.
+ ///
+ /// @param max_retries Number of attempts. The value 0 disables retries.
+ void setServiceSocketsMaxRetries(uint32_t max_retries) {
+ service_sockets_max_retries_ = max_retries;
+ }
+
+ /// @brief Indicates the maximum number of service sockets bind attempts.
+ ///
+ /// @return Number of attempts.
+ uint32_t getServiceSocketsMaxRetries() const {
+ return (service_sockets_max_retries_);
+ }
+
+ /// @brief Get the reconnect controller.
+ ///
+ /// @return the reconnect controller
+ util::ReconnectCtlPtr getReconnectCtl() const {
+ return (reconnect_ctl_);
+ }
+
+ /// @brief Represents a callback invoked if all retries of the
+ /// opening sockets fail.
+ typedef std::function<void(util::ReconnectCtlPtr)> OpenSocketsFailedCallback;
+
+ /// @brief Optional callback function to invoke if all retries of the
+ /// opening sockets fail.
+ static OpenSocketsFailedCallback open_sockets_failed_callback_;
+
+private:
+
+ /// @brief Checks if multiple IPv4 addresses has been activated on any
+ /// interface.
+ ///
+ /// This method is useful to check if the current configuration uses
+ /// multiple IPv4 addresses on any interface. This is important when
+ /// using raw sockets to receive messages from the clients because
+ /// each packet may be received multiple times when it is sent from
+ /// a directly connected client. If this is the case, a warning must
+ /// be logged.
+ ///
+ /// @return true if multiple addresses are activated on any interface,
+ /// false otherwise.
+ static bool multipleAddressesPerInterfaceActive();
+
+ /// @brief Selects or deselects interfaces.
+ ///
+ /// This function selects all interfaces to receive DHCP traffic or
+ /// deselects all interfaces so as none of them receives a DHCP traffic.
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param inactive A boolean value which indicates if all interfaces
+ /// (except loopback) should be selected or deselected.
+ /// @param loopback_inactive A boolean value which indicates if loopback
+ /// interface should be selected or deselected.
+ /// should be deselected/inactive (true) or selected/active (false).
+ void setState(const uint16_t family, const bool inactive,
+ const bool loopback_inactive) const;
+
+ /// @brief Selects or deselects addresses on the interface.
+ ///
+ /// This function selects all address on the interface to receive DHCP
+ /// traffic or deselects all addresses so as none of them receives the
+ /// DHCP traffic.
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param active A boolean value which indicates if all addresses should
+ /// be active (if true), or inactive (if false).
+ /// @param iface An interface on which addresses are selected/deselected.
+ void setIfaceAddrsState(const uint16_t family, const bool active,
+ Iface& iface) const;
+
+ /// @brief Error handler for executed when opening a socket fail.
+ ///
+ /// A pointer to this function is passed to the @c IfaceMgr::openSockets4
+ /// or @c IfaceMgr::openSockets6. These functions call this handler when
+ /// they fail to open a socket. The handler logs an error passed in the
+ /// parameter.
+ ///
+ /// @param errmsg Error message being logged by the function.
+ static void socketOpenErrorHandler(const std::string& errmsg);
+
+ /// @brief Calls a family-specific function to open sockets.
+ ///
+ /// It is a static function for a safe call from a CfgIface instance or a
+ /// timer handler.
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param port Port number to be used to bind sockets to.
+ /// @param can_use_bcast A boolean flag which indicates if the broadcast
+ /// traffic should be received through the socket and the raw sockets are
+ /// used. For the UDP sockets, we only handle the relayed (unicast)
+ /// traffic. This parameter is ignored for IPv6.
+ /// @param skip_opened Omits the already opened sockets (doesn't try to
+ /// re-bind).
+ /// @return Pair of boolean flags. The first boolean is true if at least
+ /// one socket is successfully opened, and the second is true if no errors
+ /// occur.
+ static std::pair<bool, bool> openSocketsForFamily(const uint16_t family,
+ const uint16_t port,
+ const bool can_use_bcast,
+ const bool skip_opened);
+
+ /// @brief Creates a ReconnectCtl based on the configuration's
+ /// retry parameters.
+ ///
+ /// @return The reconnect control created using the configuration
+ /// parameters.
+ util::ReconnectCtlPtr makeReconnectCtl() const;
+
+ /// Calls the @c CfgIface::openSocketsForFamily function and retry it if
+ /// socket opening fails.
+ ///
+ /// @param reconnect_ctl Used to manage socket reconnection.
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param port Port number to be used to bind sockets to.
+ /// @param can_use_bcast A boolean flag which indicates if the broadcast
+ /// traffic should be received through the socket and the raw sockets are
+ /// used. For the UDP sockets, we only handle the relayed (unicast)
+ /// traffic. This parameter is ignored for IPv6.
+ ///
+ /// @return True if at least one socket opened successfully.
+ static bool openSocketsWithRetry(util::ReconnectCtlPtr reconnect_ctl,
+ const uint16_t family, const uint16_t port,
+ const bool can_use_bcast);
+
+ /// @brief Represents a set of interface names.
+ typedef std::set<std::string> IfaceSet;
+
+ /// @brief A set of interface names specified by the user.
+ IfaceSet iface_set_;
+
+ /// @brief A map of interfaces and addresses to which the server
+ /// should bind sockets.
+ typedef std::multimap<std::string, asiolink::IOAddress> ExplicitAddressMap;
+
+ /// @brief A map which holds the pairs of interface names and addresses
+ /// for which the sockets should be opened.
+ ExplicitAddressMap address_map_;
+
+ /// @brief A boolean value which indicates that the wildcard interface name
+ /// has been specified (*).
+ bool wildcard_used_;
+
+ /// @brief A type of the sockets used by the DHCP server.
+ SocketType socket_type_;
+
+ /// @brief A boolean value which reflects current re-detect setting
+ bool re_detect_;
+
+ /// @brief A boolean value indicates that Kea must successfully bind all socket services on init
+ bool service_socket_require_all_;
+
+ /// @brief An interval between attempts to retry the socket service binding.
+ uint32_t service_sockets_retry_wait_time_;
+
+ /// @brief A maximum number of attempts to bind the service sockets.
+ uint32_t service_sockets_max_retries_;
+
+ /// @brief Indicates how outbound interface is selected for relayed traffic.
+ OutboundIface outbound_iface_;
+
+ /// @brief Used to manage socket reconnection.
+ util::ReconnectCtlPtr reconnect_ctl_;
+};
+
+/// @brief A pointer to the @c CfgIface .
+typedef boost::shared_ptr<CfgIface> CfgIfacePtr;
+
+/// @brief A pointer to the const @c CfgIface.
+typedef boost::shared_ptr<const CfgIface> ConstCfgIfacePtr;
+
+}
+}
+
+#endif // CFG_IFACE_H
diff --git a/src/lib/dhcpsrv/cfg_mac_source.cc b/src/lib/dhcpsrv/cfg_mac_source.cc
new file mode 100644
index 0000000..7a0db26
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_mac_source.cc
@@ -0,0 +1,89 @@
+// Copyright (C) 2014-2015,2017 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 <dhcpsrv/cfg_mac_source.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/hwaddr.h>
+
+using namespace isc::data;
+
+namespace {
+
+using namespace isc::dhcp;
+
+struct {
+ const char * name;
+ uint32_t type;
+} sources[] = {
+ { "any", HWAddr::HWADDR_SOURCE_ANY },
+ { "raw", HWAddr::HWADDR_SOURCE_RAW },
+ { "duid", HWAddr::HWADDR_SOURCE_DUID },
+ { "ipv6-link-local", HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL },
+ { "client-link-addr-option", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
+ { "rfc6939", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
+ { "remote-id", HWAddr::HWADDR_SOURCE_REMOTE_ID },
+ { "rfc4649", HWAddr::HWADDR_SOURCE_REMOTE_ID },
+ { "subscriber-id", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
+ { "rfc4580", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
+ { "docsis-cmts", HWAddr::HWADDR_SOURCE_DOCSIS_CMTS },
+ { "docsis-modem", HWAddr::HWADDR_SOURCE_DOCSIS_MODEM }
+};
+
+};
+
+namespace isc {
+namespace dhcp {
+
+CfgMACSource::CfgMACSource() {
+
+ // By default, use any hardware source that is available.
+ mac_sources_.push_back(HWAddr::HWADDR_SOURCE_ANY);
+}
+
+uint32_t CfgMACSource::MACSourceFromText(const std::string& name) {
+ for (unsigned i = 0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
+ if (name.compare(sources[i].name) == 0) {
+ return (sources[i].type);
+ }
+ }
+
+ isc_throw(BadValue, "Can't convert '" << name << "' to any known MAC source.");
+}
+
+void CfgMACSource::add(uint32_t source) {
+ for (CfgMACSources::const_iterator it = mac_sources_.begin();
+ it != mac_sources_.end(); ++it) {
+ if (*it == source) {
+ isc_throw(InvalidParameter, "mac-source parameter " << source
+ << "' specified twice.");
+ }
+ }
+ mac_sources_.push_back(source);
+}
+
+ElementPtr CfgMACSource::toElement() const {
+ ElementPtr result = Element::createList();
+ for (CfgMACSources::const_iterator source = mac_sources_.cbegin();
+ source != mac_sources_.cend(); ++source) {
+ std::string name;
+ for (unsigned i = 0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
+ if (sources[i].type == *source) {
+ name = sources[i].name;
+ break;
+ }
+ }
+ if (name.empty()) {
+ isc_throw(ToElementError, "invalid MAC source: " << *source);
+ }
+ result->add(Element::create(name));
+ }
+ // @todo check if the list is empty (including a new unit test)
+ return (result);
+}
+
+};
+};
diff --git a/src/lib/dhcpsrv/cfg_mac_source.h b/src/lib/dhcpsrv/cfg_mac_source.h
new file mode 100644
index 0000000..87c673d
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_mac_source.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2014-2015,2017 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 CFG_MAC_SOURCE_H
+#define CFG_MAC_SOURCE_H
+
+#include <cc/cfg_to_element.h>
+#include <stdint.h>
+#include <vector>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Container for defined MAC/hardware address sources
+typedef std::vector<uint32_t> CfgMACSources;
+
+/// @brief Wrapper class that holds MAC/hardware address sources
+///
+/// It's a simple wrapper around a vector of uint32_t, with each entry
+/// holding one MAC source.
+class CfgMACSource : public isc::data::CfgToElement {
+
+ public:
+ /// @brief Default constructor.
+ ///
+ /// Sets source to 'any'.
+ CfgMACSource();
+
+ /// @brief Attempts to convert known hardware address sources to uint32_t
+ ///
+ /// Supported strings are: \li any => 0xffffffff
+ /// \li raw => 0x00000001
+ /// \li duid => 0x00000002
+ /// \li ipv6-link-local 0x00000004
+ /// \li client-link-addr-option, rfc6939 => 0x00000008
+ /// \li remote-id, rfc4649 => 0x00000010
+ /// \li subscriber-id, rfc4580 => 0x00000020
+ /// \li docsis => 0x00000040
+ ///
+ /// For specific constants, see @ref isc::dhcp::HWAddr class.
+ ///
+ /// @throw BadValue if specified string is unknown
+ /// @return bitmask version of a given method
+ static uint32_t MACSourceFromText(const std::string& name);
+
+
+ /// @brief Adds additional MAC/hardware address acquisition.
+ ///
+ /// @param source MAC source (see constants in Pkt::HWADDR_SOURCE_*)
+ ///
+ /// Specified source is being added to the mac_sources_ array.
+ /// @throw InvalidParameter if such a source is already defined.
+ void add(uint32_t source);
+
+ /// @brief Provides access to the configure MAC/Hardware address sources.
+ ///
+ /// @note The const reference returned is only valid as long as the
+ /// object that returned it.
+ const CfgMACSources& get() const {
+ return mac_sources_;
+ }
+
+ /// @brief Removes any configured MAC/Hardware address sources.
+ void clear() {
+ mac_sources_.clear();
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ protected:
+ /// @brief Actual MAC sources storage
+ CfgMACSources mac_sources_;
+
+};
+
+};
+};
+
+#endif
diff --git a/src/lib/dhcpsrv/cfg_multi_threading.cc b/src/lib/dhcpsrv/cfg_multi_threading.cc
new file mode 100644
index 0000000..9968974
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_multi_threading.cc
@@ -0,0 +1,52 @@
+// Copyright (C) 2020-2023 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 <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cfg_multi_threading.h>
+#include <util/multi_threading_mgr.h>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+CfgMultiThreading::apply(ConstElementPtr value) {
+ // Note the default values are set by extract, not here!
+ bool enabled = false;
+ uint32_t thread_count = 0;
+ uint32_t queue_size = 0;
+ CfgMultiThreading::extract(value, enabled, thread_count, queue_size);
+ MultiThreadingMgr::instance().apply(enabled, thread_count, queue_size);
+}
+
+void
+CfgMultiThreading::extract(ConstElementPtr value, bool& enabled,
+ uint32_t& thread_count, uint32_t& queue_size) {
+ enabled = true;
+ thread_count = 0;
+ queue_size = 0;
+ if (value) {
+ if (value->get("enable-multi-threading")) {
+ enabled = SimpleParser::getBoolean(value, "enable-multi-threading");
+ }
+
+ if (value->get("thread-pool-size")) {
+ thread_count = SimpleParser::getInteger(value, "thread-pool-size");
+ }
+
+ if (value->get("packet-queue-size")) {
+ queue_size = SimpleParser::getInteger(value, "packet-queue-size");
+ }
+ }
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/cfg_multi_threading.h b/src/lib/dhcpsrv/cfg_multi_threading.h
new file mode 100644
index 0000000..37f7897
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_multi_threading.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2020-2023 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 CFG_MULTI_THREADING_H
+#define CFG_MULTI_THREADING_H
+
+#include <cc/data.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class to apply multi threading configurations
+class CfgMultiThreading {
+public:
+
+ /// @brief apply multi threading configuration
+ ///
+ /// @param value The multi-threading configuration
+ static void apply(data::ConstElementPtr value);
+
+ /// @brief Extract multi-threading parameters from a given configuration.
+ ///
+ /// If the configuration does not contain the enable parameter,
+ /// multi-threading is disabled. This is very useful as a default value for
+ /// unit tests, so that they are kept simple, and that they can enable
+ /// multi-threading on their own if it is in their scope to test MT.
+ ///
+ /// @param[in] value The multi-threading configuration
+ /// @param[out] enabled The enabled flag
+ /// @param[out] thread_count The thread count
+ /// @param[out] queue_size The queue size
+ static void extract(data::ConstElementPtr value, bool& enabled,
+ uint32_t& thread_count, uint32_t& queue_size);
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // CFG_MULTI_THREADING_H
diff --git a/src/lib/dhcpsrv/cfg_option.cc b/src/lib/dhcpsrv/cfg_option.cc
new file mode 100644
index 0000000..e738023
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_option.cc
@@ -0,0 +1,555 @@
+// Copyright (C) 2014-2023 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 <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_space.h>
+#include <util/encode/hex.h>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/make_shared.hpp>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+OptionDescriptorPtr
+OptionDescriptor::create(const OptionPtr& opt, bool persist, bool cancel,
+ const std::string& formatted_value,
+ ConstElementPtr user_context) {
+ return (boost::make_shared<OptionDescriptor>(opt, persist, cancel,
+ formatted_value,
+ user_context));
+}
+
+OptionDescriptorPtr
+OptionDescriptor::create(bool persist, bool cancel) {
+ return (boost::make_shared<OptionDescriptor>(persist, cancel));
+}
+
+OptionDescriptorPtr
+OptionDescriptor::create(const OptionDescriptor& desc) {
+ return (boost::make_shared<OptionDescriptor>(desc));
+}
+
+bool
+OptionDescriptor::equals(const OptionDescriptor& other) const {
+ return ((persistent_ == other.persistent_) &&
+ (cancelled_ == other.cancelled_) &&
+ (formatted_value_ == other.formatted_value_) &&
+ (space_name_ == other.space_name_) &&
+ option_->equals(other.option_));
+}
+
+CfgOption::CfgOption()
+ : encapsulated_(false) {
+}
+
+bool
+CfgOption::empty() const {
+ return (options_.empty() && vendor_options_.empty());
+}
+
+bool
+CfgOption::equals(const CfgOption& other) const {
+ return (options_.equals(other.options_) &&
+ vendor_options_.equals(other.vendor_options_));
+}
+
+void
+CfgOption::add(const OptionPtr& option,
+ const bool persistent,
+ const bool cancelled,
+ const std::string& option_space,
+ const uint64_t id) {
+ OptionDescriptor desc(option, persistent, cancelled);
+ if (id > 0) {
+ desc.setId(id);
+ }
+ add(desc, option_space);
+}
+
+void
+CfgOption::add(const OptionDescriptor& desc, const std::string& option_space) {
+ if (!desc.option_) {
+ isc_throw(isc::BadValue, "option being configured must not be NULL");
+
+ } else if (!OptionSpace::validateName(option_space)) {
+ isc_throw(isc::BadValue, "invalid option space name: '"
+ << option_space << "'");
+ }
+
+ const uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
+ if (vendor_id) {
+ vendor_options_.addItem(desc, vendor_id);
+ } else {
+ options_.addItem(desc, option_space);
+ }
+}
+
+void
+CfgOption::replace(const OptionDescriptor& desc, const std::string& option_space) {
+ if (!desc.option_) {
+ isc_throw(isc::BadValue, "option being replaced must not be NULL");
+ }
+
+ // Check for presence of options.
+ OptionContainerPtr options = getAll(option_space);
+ if (!options) {
+ isc_throw(isc::BadValue, "option space " << option_space
+ << " does not exist");
+ }
+
+ // Find the option we want to replace.
+ OptionContainerTypeIndex& idx = options->get<1>();
+ auto const& od_itr = idx.find(desc.option_->getType());
+ if (od_itr == idx.end()) {
+ isc_throw(isc::BadValue, "cannot replace option: "
+ << option_space << ":" << desc.option_->getType()
+ << ", it does not exist");
+ }
+
+ idx.replace(od_itr, desc);
+}
+
+std::list<std::string>
+CfgOption::getVendorIdsSpaceNames() const {
+ std::list<uint32_t> ids = getVendorIds();
+ std::list<std::string> names;
+ for (auto const& id : ids) {
+ std::ostringstream s;
+ // Vendor space name is constructed as "vendor-XYZ" where XYZ is an
+ // uint32_t value, without leading zeros.
+ s << "vendor-" << id;
+ names.push_back(s.str());
+ }
+ return (names);
+}
+
+void
+CfgOption::merge(CfgOptionDefPtr cfg_def, CfgOption& other) {
+ // First we merge our options into other.
+ // This adds my options that are not
+ // in other, to other (i.e we skip over
+ // duplicates).
+ mergeTo(other);
+
+ // Create option instances based on the given definitions.
+ other.createOptions(cfg_def);
+
+ // Next we copy "other" on top of ourself.
+ other.copyTo(*this);
+
+ // If we copied new options we may need to populate the
+ // sub-options into the upper level options. The server
+ // expects that the top-level options have suitable
+ // suboptions appended.
+ encapsulate();
+}
+
+void
+CfgOption::createOptions(CfgOptionDefPtr cfg_def) {
+ // Iterate over all the option descriptors in
+ // all the spaces and instantiate the options
+ // based on the given definitions.
+ for (auto space : getOptionSpaceNames()) {
+ for (auto opt_desc : *(getAll(space))) {
+ if (createDescriptorOption(cfg_def, space, opt_desc)) {
+ // Option was recreated, let's replace the descriptor.
+ replace(opt_desc, space);
+ }
+ }
+ }
+}
+
+bool
+CfgOption::createDescriptorOption(CfgOptionDefPtr cfg_def, const std::string& space,
+ OptionDescriptor& opt_desc) {
+ if (!opt_desc.option_) {
+ isc_throw(BadValue,
+ "validateCreateOption: descriptor has no option instance");
+ }
+
+ Option::Universe universe = opt_desc.option_->getUniverse();
+ uint16_t code = opt_desc.option_->getType();
+
+ // Find the option's defintion, if it has one.
+ // First, check for a standard definition.
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
+
+ // If there is no standard definition but the option is vendor specific,
+ // we should search the definition within the vendor option space.
+ if (!def && (space != DHCP4_OPTION_SPACE) && (space != DHCP6_OPTION_SPACE)) {
+ uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
+ if (vendor_id > 0) {
+ def = LibDHCP::getVendorOptionDef(universe, vendor_id, code);
+ }
+ }
+
+ // Still haven't found the definition, so look for custom
+ // definition in the given set of configured definitions
+ if (!def) {
+ def = cfg_def->get(space, code);
+ }
+
+ // Finish with a last resort option definition.
+ if (!def) {
+ def = LibDHCP::getLastResortOptionDef(space, code);
+ }
+
+ std::string& formatted_value = opt_desc.formatted_value_;
+ if (!def) {
+ if (!formatted_value.empty()) {
+ isc_throw(InvalidOperation, "option: " << space << "." << code
+ << " has a formatted value: '" << formatted_value
+ << "' but no option definition");
+ }
+
+ // If there's no definition and no formatted string, we'll
+ // settle for the generic option already in the descriptor.
+ // Indicate no-change by returning false.
+ return (false);
+ }
+
+ try {
+ // Definition found. Let's replace the generic option in
+ // the descriptor with one created based on definition's factory.
+ if (formatted_value.empty()) {
+ // No formatted value, use data stored in the generic option.
+ opt_desc.option_ = def->optionFactory(universe, code, opt_desc.option_->getData());
+ } else {
+ // Spit the value specified in comma separated values format.
+ std::vector<std::string> split_vec;
+ boost::split(split_vec, formatted_value, boost::is_any_of(","));
+ opt_desc.option_ = def->optionFactory(universe, code, split_vec);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidOperation, "could not create option: " << space << "." << code
+ << " from data specified, reason: " << ex.what());
+ }
+
+ // Indicate we replaced the definition.
+ return(true);
+}
+
+void
+CfgOption::mergeTo(CfgOption& other) const {
+ // Merge non-vendor options.
+ mergeInternal(options_, other.options_);
+ // Merge vendor options.
+ mergeInternal(vendor_options_, other.vendor_options_);
+}
+
+void
+CfgOption::copyTo(CfgOption& other) const {
+ // Remove any existing data in the destination.
+ other.options_.clearItems();
+ other.vendor_options_.clearItems();
+ mergeTo(other);
+}
+
+void
+CfgOption::encapsulate() {
+ // Append sub-options to the top level "dhcp4" option space.
+ encapsulateInternal(DHCP4_OPTION_SPACE);
+ // Append sub-options to the top level "dhcp6" option space.
+ encapsulateInternal(DHCP6_OPTION_SPACE);
+ encapsulated_ = true;
+}
+
+void
+CfgOption::encapsulateInternal(const std::string& option_space) {
+ // Get all options for the particular option space.
+ OptionContainerPtr options = getAll(option_space);
+ // For each option in the option space we will append sub-options
+ // from the option spaces they encapsulate.
+ for (auto const& opt : *options) {
+ encapsulateInternal(opt.option_);
+ }
+}
+
+void
+CfgOption::encapsulateInternal(const OptionPtr& option) {
+ // Get encapsulated option space for the option.
+ const std::string& encap_space = option->getEncapsulatedSpace();
+ // Empty value means that no option space is encapsulated.
+ if (!encap_space.empty()) {
+ if (encap_space == DHCP4_OPTION_SPACE || encap_space == DHCP6_OPTION_SPACE) {
+ return;
+ }
+ // Retrieve all options from the encapsulated option space.
+ OptionContainerPtr encap_options = getAll(encap_space);
+ for (auto const& encap_opt : *encap_options) {
+ if (option.get() == encap_opt.option_.get()) {
+ // Avoid recursion by not adding options to themselves.
+ continue;
+ }
+
+ // Add sub-option if there isn't one added already.
+ if (!option->getOption(encap_opt.option_->getType())) {
+ option->addOption(encap_opt.option_);
+ }
+ encapsulateInternal(encap_opt.option_);
+ }
+ }
+}
+
+template <typename Selector>
+void
+CfgOption::mergeInternal(const OptionSpaceContainer<OptionContainer,
+ OptionDescriptor, Selector>& src_container,
+ OptionSpaceContainer<OptionContainer,
+ OptionDescriptor, Selector>& dest_container) const {
+ // Get all option spaces used in source container.
+ std::list<Selector> selectors = src_container.getOptionSpaceNames();
+
+ // For each space in the source container retrieve the actual options and
+ // match them with the options held in the destination container under
+ // the same space.
+ for (auto const& it : selectors) {
+ // Get all options in the destination container for the particular
+ // option space.
+ OptionContainerPtr dest_all = dest_container.getItems(it);
+ OptionContainerPtr src_all = src_container.getItems(it);
+ // For each option under this option space check if there is a
+ // corresponding option in the destination container. If not,
+ // add one.
+ for (auto const& src_opt : *src_all) {
+ const OptionContainerTypeIndex& idx = dest_all->get<1>();
+ const OptionContainerTypeRange& range =
+ idx.equal_range(src_opt.option_->getType());
+ // If there is no such option in the destination container,
+ // add one.
+ if (std::distance(range.first, range.second) == 0) {
+ dest_container.addItem(OptionDescriptor(src_opt), it);
+ }
+ }
+ }
+}
+
+OptionContainerPtr
+CfgOption::getAll(const std::string& option_space) const {
+ return (options_.getItems(option_space));
+}
+
+OptionContainerPtr
+CfgOption::getAll(const uint32_t vendor_id) const {
+ return (vendor_options_.getItems(vendor_id));
+}
+
+OptionContainerPtr
+CfgOption::getAllCombined(const std::string& option_space) const {
+ auto vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
+ if (vendor_id > 0) {
+ return (getAll(vendor_id));
+ }
+ return (getAll(option_space));
+}
+
+size_t
+CfgOption::del(const std::string& option_space, const uint16_t option_code) {
+ // Check for presence of options.
+ OptionContainerPtr options = getAll(option_space);
+ if (!options || options->empty()) {
+ // There are no options, so there is nothing to do.
+ return (0);
+ }
+
+ // If this is not top level option we may also need to delete the
+ // option instance from options encapsulating the particular option
+ // space.
+ if ((option_space != DHCP4_OPTION_SPACE) &&
+ (option_space != DHCP6_OPTION_SPACE)) {
+ // For each option space name iterate over the existing options.
+ auto option_space_names = getOptionSpaceNames();
+ for (auto option_space_from_list : option_space_names) {
+ // Get all options within the particular option space.
+ auto options_in_space = getAll(option_space_from_list);
+ for (auto option_it = options_in_space->begin();
+ option_it != options_in_space->end();
+ ++option_it) {
+
+ // Check if the option encapsulates our option space and
+ // it does, try to delete our option.
+ if (option_it->option_ &&
+ (option_it->option_->getEncapsulatedSpace() == option_space)) {
+ option_it->option_->delOption(option_code);
+ }
+ }
+ }
+ }
+
+ auto& idx = options->get<1>();
+ return (idx.erase(option_code));
+}
+
+size_t
+CfgOption::del(const uint32_t vendor_id, const uint16_t option_code) {
+ // Check for presence of options.
+ OptionContainerPtr vendor_options = getAll(vendor_id);
+ if (!vendor_options || vendor_options->empty()) {
+ // There are no options, so there is nothing to do.
+ return (0);
+ }
+
+ auto& idx = vendor_options->get<1>();
+ return (idx.erase(option_code));
+}
+
+size_t
+CfgOption::del(const uint64_t id) {
+ // Hierarchical nature of the options configuration requires that
+ // we go over all options and decapsulate them before removing
+ // any of them. Let's walk over the existing option spaces.
+ for (auto space_name : getOptionSpaceNames()) {
+ // Get all options for the option space.
+ auto options = getAll(space_name);
+ for (auto option_it = options->begin(); option_it != options->end();
+ ++option_it) {
+ if (!option_it->option_) {
+ continue;
+ }
+
+ // For each option within the option space we need to dereference
+ // any existing sub options.
+ auto sub_options = option_it->option_->getOptions();
+ for (auto sub = sub_options.begin(); sub != sub_options.end();
+ ++sub) {
+ // Dereference sub option.
+ option_it->option_->delOption(sub->second->getType());
+ }
+ }
+ }
+
+ // Now that we got rid of dependencies between the instances of the options
+ // we can delete all options having a specified id.
+ size_t num_deleted = options_.deleteItems(id) + vendor_options_.deleteItems(id);
+
+ // Let's encapsulate those options that remain in the configuration.
+ encapsulate();
+
+ // Return the number of deleted options.
+ return (num_deleted);
+}
+
+ElementPtr
+CfgOption::toElement() const {
+ return (toElementWithMetadata(false));
+}
+
+ElementPtr
+CfgOption::toElementWithMetadata(const bool include_metadata) const {
+ // option-data value is a list of maps
+ ElementPtr result = Element::createList();
+ // Iterate first on options using space names
+ const std::list<std::string>& names = options_.getOptionSpaceNames();
+ for (auto const& name : names) {
+ OptionContainerPtr opts = getAll(name);
+ for (auto const& opt : *opts) {
+ // Get and fill the map for this option
+ ElementPtr map = Element::createMap();
+ // Set user context
+ opt.contextToElement(map);
+ // Set space from parent iterator
+ map->set("space", Element::create(name));
+ // Set the code
+ uint16_t code = opt.option_->getType();
+ map->set("code", Element::create(code));
+ // Set the name (always for standard options else when asked for)
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(name, code);
+ if (!def) {
+ def = LibDHCP::getRuntimeOptionDef(name, code);
+ }
+ if (!def) {
+ def = LibDHCP::getLastResortOptionDef(name, code);
+ }
+ if (def) {
+ map->set("name", Element::create(def->getName()));
+ }
+ // Set the data item
+ if (!opt.formatted_value_.empty()) {
+ map->set("csv-format", Element::create(true));
+ map->set("data", Element::create(opt.formatted_value_));
+ } else {
+ std::vector<uint8_t> bin = opt.option_->toBinary();
+ if (!opt.cancelled_ || !bin.empty()) {
+ map->set("csv-format", Element::create(false));
+ std::string repr = util::encode::encodeHex(bin);
+ map->set("data", Element::create(repr));
+ }
+ }
+ // Set the persistency flag
+ map->set("always-send", Element::create(opt.persistent_));
+ // Set the cancelled flag.
+ map->set("never-send", Element::create(opt.cancelled_));
+ // Include metadata if requested.
+ if (include_metadata) {
+ map->set("metadata", opt.getMetadata());
+ }
+
+ // Push on the list
+ result->add(map);
+ }
+ }
+ // Iterate first on vendor_options using vendor ids
+ const std::list<uint32_t>& ids = vendor_options_.getOptionSpaceNames();
+ for (auto const& id : ids) {
+ OptionContainerPtr opts = getAll(id);
+ for (auto const& opt : *opts) {
+ // Get and fill the map for this option
+ ElementPtr map = Element::createMap();
+ // Set user context
+ opt.contextToElement(map);
+ // Set space from parent iterator
+ std::ostringstream oss;
+ oss << "vendor-" << id;
+ map->set("space", Element::create(oss.str()));
+ // Set the code
+ uint16_t code = opt.option_->getType();
+ map->set("code", Element::create(code));
+ // Set the name
+ Option::Universe universe = opt.option_->getUniverse();
+ OptionDefinitionPtr def =
+ LibDHCP::getVendorOptionDef(universe, id, code);
+ if (!def) {
+ // vendor-XXX space is in oss
+ def = LibDHCP::getRuntimeOptionDef(oss.str(), code);
+ }
+ if (def) {
+ map->set("name", Element::create(def->getName()));
+ }
+ // Set the data item
+ if (!opt.formatted_value_.empty()) {
+ map->set("csv-format", Element::create(true));
+ map->set("data", Element::create(opt.formatted_value_));
+ } else {
+ std::vector<uint8_t> bin = opt.option_->toBinary();
+ if (!opt.cancelled_ || !bin.empty()) {
+ map->set("csv-format", Element::create(false));
+ std::string repr = util::encode::encodeHex(bin);
+ map->set("data", Element::create(repr));
+ }
+ }
+ // Set the persistency flag
+ map->set("always-send", Element::create(opt.persistent_));
+ // Set the cancellation flag
+ map->set("never-send", Element::create(opt.cancelled_));
+ // Push on the list
+ result->add(map);
+ }
+ }
+ return (result);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h
new file mode 100644
index 0000000..a0c6d0b
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_option.h
@@ -0,0 +1,816 @@
+// Copyright (C) 2014-2023 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 CFG_OPTION_H
+#define CFG_OPTION_H
+
+#include <dhcp/option.h>
+#include <dhcp/option_space_container.h>
+#include <cc/cfg_to_element.h>
+#include <cc/stamped_element.h>
+#include <cc/user_context.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <dhcpsrv/key_from_key.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <list>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+class OptionDescriptor;
+
+/// A pointer to option descriptor.
+typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
+
+/// A list of option descriptors.
+typedef std::vector<OptionDescriptor> OptionDescriptorList;
+
+/// @brief Option descriptor.
+///
+/// Option descriptor holds instance of an option and additional information
+/// for this option. This information comprises whether this option is sent
+/// to DHCP client only on request (persistent = false) or always
+/// (persistent = true), or must never send (cancelled = true).
+class OptionDescriptor : public data::StampedElement, public data::UserContext {
+public:
+ /// @brief Option instance.
+ OptionPtr option_;
+
+ /// @brief Persistence flag.
+ ///
+ /// If true, option is always sent to the client. If false, option is
+ /// sent to the client when requested using ORO or PRL option.
+ bool persistent_;
+
+ /// @brief Cancelled flag.
+ ///
+ /// If true, option is never sent to the client. If false, option is
+ /// sent when it should.
+ /// @note: When true the action of this flag is final i.e. it can't be
+ /// overridden at a more specific level and has precedence over persist.
+ bool cancelled_;
+
+ /// @brief Option value in textual (CSV) format.
+ ///
+ /// This field is used to convey option value in human readable format,
+ /// the same as used to specify option value in the server configuration.
+ /// This value is optional and can be held in the host reservations
+ /// database instead of the binary format.
+ ///
+ /// Note that this value is carried in the option descriptor, rather than
+ /// @c Option instance because it is a server specific value (same as
+ /// persistence flag).
+ ///
+ /// An example of the formatted value is: "2001:db8:1::1, 23, some text"
+ /// for the option which carries IPv6 address, a number and a text.
+ std::string formatted_value_;
+
+ /// @brief Option space name.
+ ///
+ /// Options are associated with option spaces. Typically, such association
+ /// is made when the option is stored in the @c OptionContainer. However,
+ /// in some cases it is also required to associate option with the particular
+ /// option space outside of the container. In particular, when the option
+ /// is fetched from a database. The database configuration backend will
+ /// set option space upon return of the option. In other cases this value
+ /// won't be set.
+ std::string space_name_;
+
+ /// @brief Constructor.
+ ///
+ /// @param opt option instance.
+ /// @param persist if true, option is always sent.
+ /// @param cancel if true, option is never sent.
+ /// @param formatted_value option value in the textual format (optional).
+ /// @param user_context user context (optional).
+ OptionDescriptor(const OptionPtr& opt, bool persist, bool cancel,
+ const std::string& formatted_value = "",
+ data::ConstElementPtr user_context = data::ConstElementPtr())
+ : data::StampedElement(), option_(opt), persistent_(persist),
+ cancelled_(cancel), formatted_value_(formatted_value),
+ space_name_() {
+ setContext(user_context);
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param persist if true option is always sent.
+ /// @param cancel if true, option is never sent.
+ OptionDescriptor(bool persist, bool cancel)
+ : data::StampedElement(), option_(OptionPtr()), persistent_(persist),
+ cancelled_(cancel), formatted_value_(), space_name_() {};
+
+ /// @brief Copy constructor.
+ ///
+ /// @param desc option descriptor to be copied.
+ OptionDescriptor(const OptionDescriptor& desc)
+ : data::StampedElement(desc),
+ option_(desc.option_),
+ persistent_(desc.persistent_),
+ cancelled_(desc.cancelled_),
+ formatted_value_(desc.formatted_value_),
+ space_name_(desc.space_name_) {
+ setContext(desc.getContext());
+ };
+
+ /// @brief Assignment operator.
+ ///
+ /// @param other option descriptor to be assigned from.
+ OptionDescriptor& operator=(const OptionDescriptor& other) {
+ if (this != &other) {
+ // Not self-assignment.
+ data::StampedElement::operator=(other);
+ option_ = other.option_;
+ persistent_ = other.persistent_;
+ cancelled_ = other.cancelled_;
+ formatted_value_ = other.formatted_value_;
+ space_name_ = other.space_name_;
+ setContext(other.getContext());
+ }
+ return (*this);
+ }
+
+ /// @brief Factory function creating an instance of the @c OptionDescriptor.
+ ///
+ /// @param opt option instance.
+ /// @param persist if true, option is always sent.
+ /// @param cancel if true, option is never sent.
+ /// @param formatted_value option value in the textual format (optional).
+ /// @param user_context user context (optional).
+ ///
+ /// @return Pointer to the @c OptionDescriptor instance.
+ static OptionDescriptorPtr create(const OptionPtr& opt,
+ bool persist,
+ bool cancel,
+ const std::string& formatted_value = "",
+ data::ConstElementPtr user_context =
+ data::ConstElementPtr());
+
+ /// @brief Factory function creating an instance of the @c OptionDescriptor.
+ ///
+ /// @param persist if true option is always sent.
+ /// @param cancel if true, option is never sent.
+ ///
+ /// @return Pointer to the @c OptionDescriptor instance.
+ static OptionDescriptorPtr create(bool persist, bool cancel);
+
+ /// @brief Factory function creating an instance of the @c OptionDescriptor.
+ ///
+ /// @param desc option descriptor to be copied.
+ ///
+ /// @return Pointer to the @c OptionDescriptor instance.
+ static OptionDescriptorPtr create(const OptionDescriptor& desc);
+
+ /// @brief Checks if the one descriptor is equal to another.
+ ///
+ /// @param other Other option descriptor to compare to.
+ ///
+ /// @return true if descriptors equal, false otherwise.
+ bool equals(const OptionDescriptor& other) const;
+
+ /// @brief Equality operator.
+ ///
+ /// @param other Other option descriptor to compare to.
+ ///
+ /// @return true if descriptors equal, false otherwise.
+ bool operator==(const OptionDescriptor& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other Other option descriptor to compare to.
+ ///
+ /// @return true if descriptors unequal, false otherwise.
+ bool operator!=(const OptionDescriptor& other) const {
+ return (!equals(other));
+ }
+};
+
+/// @brief Multi index container for DHCP option descriptors.
+///
+/// This container comprises three indexes to access option
+/// descriptors:
+/// - sequenced index: used to access elements in the order they
+/// have been added to the container,
+/// - option type index: used to search option descriptors containing
+/// options with specific option code (aka option type).
+/// - persistency flag index: used to search option descriptors with
+/// 'persistent' flag set to true.
+///
+/// This container is the equivalent of four separate STL containers:
+/// - std::list of all options,
+/// - std::multimap of options with option code used as a multimap key,
+/// - std::multimap of option descriptors with option persistency flag
+/// used as a multimap key.
+/// - std::multimap of option descriptors with option cancellation flag
+/// used as a multimap key.
+/// The major advantage of this container over 4 separate STL containers
+/// is automatic synchronization of all indexes when elements are added,
+/// removed or modified in the container. With separate containers,
+/// the synchronization would have to be guaranteed by the Subnet class
+/// code. This would increase code complexity and presumably it would
+/// be much harder to add new search criteria (indexes).
+///
+/// @todo we may want to search for options using option spaces when
+/// they are implemented.
+///
+/// @see http://www.boost.org/doc/libs/1_51_0/libs/multi_index/doc/index.html
+typedef boost::multi_index_container<
+ // Container comprises elements of OptionDescriptor type.
+ OptionDescriptor,
+ // Here we start enumerating various indexes.
+ boost::multi_index::indexed_by<
+ // Sequenced index allows accessing elements in the same way
+ // as elements in std::list.
+ // Sequenced is an index #0.
+ boost::multi_index::sequenced<>,
+ // Start definition of index #1.
+ boost::multi_index::hashed_non_unique<
+ // KeyFromKeyExtractor is the index key extractor that allows
+ // accessing option type being held by the OptionPtr through
+ // OptionDescriptor structure.
+ KeyFromKeyExtractor<
+ // Use option type as the index key. The type is held
+ // in OptionPtr object so we have to call Option::getType
+ // to retrieve this key for each element.
+ boost::multi_index::const_mem_fun<
+ Option,
+ uint16_t,
+ &Option::getType
+ >,
+ // Indicate that OptionPtr is a member of
+ // OptionDescriptor structure.
+ boost::multi_index::member<
+ OptionDescriptor,
+ OptionPtr,
+ &OptionDescriptor::option_
+ >
+ >
+ >,
+ // Start definition of index #2.
+ // Use 'persistent' struct member as a key.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::member<
+ OptionDescriptor,
+ bool,
+ &OptionDescriptor::persistent_
+ >
+ >,
+ // Start definition of index #3.
+ // Use BaseStampedElement::getModificationTime as a key.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::const_mem_fun<
+ data::BaseStampedElement,
+ boost::posix_time::ptime,
+ &data::BaseStampedElement::getModificationTime
+ >
+ >,
+
+ // Start definition of index #4.
+ // Use BaseStampedElement::getId as a key.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<OptionIdIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement, uint64_t,
+ &data::BaseStampedElement::getId>
+ >,
+ // Start definition of index #5.
+ // Use 'cancelled' struct member as a key.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::member<
+ OptionDescriptor,
+ bool,
+ &OptionDescriptor::cancelled_
+ >
+ >
+ >
+> OptionContainer;
+
+/// Pointer to the OptionContainer object.
+typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
+/// Type of the index #1 - option type.
+typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
+/// Pair of iterators to represent the range of options having the
+/// same option type value. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<OptionContainerTypeIndex::const_iterator,
+ OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
+/// Type of the index #2 - option persistency flag.
+typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
+/// Pair of iterators to represent the range of options having the
+/// same persistency flag. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<OptionContainerPersistIndex::const_iterator,
+ OptionContainerPersistIndex::const_iterator> OptionContainerPersistRange;
+/// Type of the index #5 - option cancellation flag.
+typedef OptionContainer::nth_index<5>::type OptionContainerCancelIndex;
+/// Pair of iterators to represent the range of options having the
+/// same cancellation flag. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<OptionContainerCancelIndex::const_iterator,
+ OptionContainerCancelIndex::const_iterator> OptionContainerCancelRange;
+
+/// @brief Represents option data configuration for the DHCP server.
+///
+/// This class holds a collection of options to be sent to a DHCP client.
+/// Options are grouped by the option space or vendor identifier (for
+/// vendor options).
+///
+/// The server configuration allows for specifying two distinct collections
+/// of options: global options and per-subnet options in which some options
+/// may overlap.
+///
+/// The collection of global options specify options being sent to the client
+/// belonging to any subnets, i.e. global options are "inherited" by all
+/// subnets.
+///
+/// The per-subnet options are configured for a particular subnet and are sent
+/// to clients which belong to this subnet. The values of the options specified
+/// for a particular subnet override the values of the global options.
+///
+/// This class represents a single collection of options (either global or
+/// per-subnet). Each subnet holds its own object of the @c CfgOption type. The
+/// @c CfgMgr holds a @c CfgOption object representing global options.
+///
+/// Note that having a separate copy of the @c CfgOption to represent global
+/// options is useful when the client requests stateless configuration from
+/// the DHCP server and no subnet is selected for this client. This client
+/// will only receive global options.
+class CfgOption : public isc::data::CfgToElement {
+public:
+
+ /// @brief default constructor
+ CfgOption();
+
+ /// @brief Indicates the object is empty
+ ///
+ /// @return true when the object is empty
+ bool empty() const;
+
+ /// @name Methods and operators used for comparing objects.
+ ///
+ //@{
+ /// @brief Check if configuration is equal to other configuration.
+ ///
+ /// @param other An object holding configuration to compare to.
+ ///
+ /// @return true if configurations are equal, false otherwise.
+ bool equals(const CfgOption& other) const;
+
+ /// @brief Equality operator.
+ ///
+ /// @param other An object holding configuration to compare to.
+ ///
+ /// @return true if configurations are equal, false otherwise.
+ bool operator==(const CfgOption& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other An object holding configuration to compare to.
+ ///
+ /// @return true if configurations are unequal, false otherwise.
+ bool operator!=(const CfgOption& other) const {
+ return (!equals(other));
+ }
+
+ //@}
+
+ /// @brief Adds instance of the option to the configuration.
+ ///
+ /// There are two types of options which may be passed to this method:
+ /// - vendor options
+ /// - non-vendor options
+ ///
+ /// The non-vendor options are grouped by the name of the option space
+ /// (specified in textual format). The vendor options are grouped by the
+ /// vendor identifier, which is a 32-bit unsigned integer value.
+ ///
+ /// In order to add new vendor option to the list the option space name
+ /// (last argument of this method) should be specified as "vendor-X" where
+ /// "X" is a 32-bit unsigned integer, e.g. "vendor-1234". Options for which
+ /// the @c option_space argument doesn't follow this format are added as
+ /// non-vendor options.
+ ///
+ /// @param option Pointer to the option being added.
+ /// @param persistent Boolean value which specifies if the option should
+ /// be sent to the client regardless if requested (true), or nor (false)
+ /// @param cancelled Boolean value which specifies if the option must
+ /// never be sent to the client.
+ /// @param option_space Option space name.
+ /// @param id Optional database id to be associated with the option.
+ ///
+ /// @throw isc::BadValue if the option space is invalid.
+ void add(const OptionPtr& option, const bool persistent,
+ const bool cancelled, const std::string& option_space,
+ const uint64_t id = 0);
+
+ /// @brief A variant of the @ref CfgOption::add method which takes option
+ /// descriptor as an argument.
+ ///
+ /// @param desc Option descriptor holding option instance and other
+ /// parameters pertaining to the option.
+ /// @param option_space Option space name.
+ ///
+ /// @throw isc::BadValue if the option space is invalid.
+ void add(const OptionDescriptor& desc, const std::string& option_space);
+
+ /// @brief Replaces the instance of an option within this collection
+ ///
+ /// This method locates the option within the given space and replaces
+ /// it with a copy of the given descriptor. This effectively updates
+ /// the contents without altering the container indexing.
+ ///
+ /// @param desc Option descriptor holding option instance and other
+ /// parameters pertaining to the option.
+ /// @param option_space Option space name.
+ ///
+ /// @throw isc::BadValue if the descriptor's option instance is null,
+ /// if space is invalid, or if the option does not already exist
+ /// in the given space.
+ void replace(const OptionDescriptor& desc, const std::string& option_space);
+
+ /// @brief Merges another option configuration into this one.
+ ///
+ /// This method calls @c mergeTo() to add this configuration's
+ /// options into @c other (skipping any duplicates). Next it calls
+ /// @c createDescriptorOption() for each option descriptor in the
+ /// merged set. This (re)-creates each descriptor's option based on
+ /// the merged set of opt definitions. Finally, it calls
+ /// @c copyTo() to overwrite this configuration's options with
+ /// the merged set in @c other.
+ ///
+ /// @warning The merge operation will affect the @c other configuration.
+ /// Therefore, the caller must not rely on the data held in the @c other
+ /// object after the call to @c merge. Also, the data held in @c other must
+ /// not be modified after the call to @c merge because it may affect the
+ /// merged configuration.
+ ///
+ /// @param cfg_def set of of user-defined option definitions to use
+ /// when merging.
+ /// @param other option configuration to merge in.
+ void merge(CfgOptionDefPtr cfg_def, CfgOption& other);
+
+ /// @brief Re-create the option in each descriptor based on given definitions
+ ///
+ /// Invokes @c createDescriptorOption() on each option descriptor in
+ /// each option space, passing in the given dictionary of option
+ /// definitions. If the descriptor's option is re-created, then the
+ /// descriptor is updated by calling @c replace().
+ ///
+ /// @param cfg_def set of of user-defined option definitions to use
+ /// when creating option instances.
+ void createOptions(CfgOptionDefPtr cfg_def);
+
+ /// @brief Creates an option descriptor's option based on a set of option defs
+ ///
+ /// This function's primary use is to create definition specific options for
+ /// option descriptors fetched from a configuration backend, as part of a
+ /// configuration merge.
+ ///
+ /// Given an OptionDescriptor whose option_ member contains a generic option
+ /// (i.e has a code and/or data), this function will attempt to find a matching
+ /// definition and then use that definition's factory to create an option
+ /// instance specific to that definition. It will then replace the descriptor's
+ /// generic option with the specific option.
+ ///
+ /// Three sources of definitions are searched, in the following order:
+ ///
+ /// 1. Standard option definitions (@c LIBDHCP::getOptionDef))
+ /// 2. Vendor option definitions (@c LIBDHCP::getVendorOptionDef))
+ /// 3. User specified definitions passed in via cfg_def parameter.
+ ///
+ /// The code will use the first matching definition found. It then applies
+ /// the following rules:
+ ///
+ /// -# If no definition is found but the descriptor conveys a non-empty
+ /// formatted value, throw an error.
+ /// -# If not definition is found and there is no formatted value, return
+ /// This leaves intact the generic option in the descriptor.
+ /// -# If a definition is found and there is no formatted value, pass the
+ /// descriptor's generic option's data into the definition's factory. Replace
+ /// the descriptor's option with the newly created option.
+ /// -# If a definition is found and there is a formatted value, split
+ /// the value into vector of values and pass that into the definition's
+ /// factory. Replace the descriptor's option with the newly created option.
+ ///
+ /// @param cfg_def the user specified definitions to use
+ /// @param space the option space name of the option
+ /// @param opt_desc OptionDescriptor describing the option.
+ ///
+ /// @return True if the descriptor's option instance was replaced.
+ /// @throw InvalidOperation if the descriptor conveys a formatted value and
+ /// there is no definition matching the option code in the given space, or
+ /// if the definition factory invocation fails.
+ static bool createDescriptorOption(CfgOptionDefPtr cfg_def, const std::string& space,
+ OptionDescriptor& opt_desc);
+
+ /// @brief Merges this configuration to another configuration.
+ ///
+ /// This method iterates over the configuration items held in this
+ /// configuration and copies them to the configuration specified
+ /// as a parameter. If an item exists in the destination it is not
+ /// copied.
+ ///
+ /// @param [out] other Configuration object to merge to.
+ void mergeTo(CfgOption& other) const;
+
+ /// @brief Copies this configuration to another configuration.
+ ///
+ /// This method copies options configuration to another object.
+ ///
+ /// @param [out] other An object to copy the configuration to.
+ void copyTo(CfgOption& other) const;
+
+ /// @brief Appends encapsulated options to top-level options.
+ ///
+ /// This method iterates over the top-level options (from "dhcp4"
+ /// and "dhcp6" option space) and checks which option spaces these
+ /// options encapsulate. For each encapsulated option space, the
+ /// options from this option space are appended to top-level options.
+ void encapsulate();
+
+ /// @brief Checks if options have been encapsulated.
+ ///
+ /// @return true if options have been encapsulated, false otherwise.
+ bool isEncapsulated() const {
+ return (encapsulated_);
+ }
+
+ /// @brief Returns all options for the specified option space.
+ ///
+ /// This method will not return vendor options, i.e. having option space
+ /// name in the format of "vendor-X" where X is 32-bit unsigned integer.
+ /// See @c getAll(uint32_t) for vendor options.
+ ///
+ /// @param option_space Name of the option space.
+ ///
+ /// @return Pointer to the container holding returned options. This
+ /// container is empty if no options have been found.
+ OptionContainerPtr getAll(const std::string& option_space) const;
+
+ /// @brief Returns vendor options for the specified vendor id.
+ ///
+ /// @param vendor_id Vendor id for which options are to be returned.
+ ///
+ /// @return Pointer to the container holding returned options. This
+ /// container is empty if no options have been found.
+ OptionContainerPtr getAll(const uint32_t vendor_id) const;
+
+ /// @brief Returns all non-vendor or vendor options for the specified
+ /// option space.
+ ///
+ /// It combines the output of the @c getAll function variants. When
+ /// option space has the format of "vendor-X", it retrieves the vendor
+ /// options by vendor id, where X must be a 32-bit unsigned integer.
+ /// Otherwise, it fetches non-vendor options.
+ ///
+ /// @param option_space Name of the option space.
+ /// @return Pointer to the container holding returned options. This
+ /// container is empty if no options have been found.
+ OptionContainerPtr getAllCombined(const std::string& option_space) const;
+
+ /// @brief Returns option for the specified key and option code.
+ ///
+ /// The key should be a string, in which case it specifies an option space
+ /// name, or an uint32_t value, in which case it specifies a vendor
+ /// identifier.
+ ///
+ /// @note If there are multiple options with the same key, only one will
+ /// be returned. No indication will be given of the presence of others,
+ /// and the instance returned is not determinable. So please use
+ /// the next method when multiple instances of the option are expected.
+ ///
+ /// @param key Option space name or vendor identifier.
+ /// @param option_code Code of the option to be returned.
+ /// @tparam Selector one of: @c std::string or @c uint32_t
+ ///
+ /// @return Descriptor of the option. If option hasn't been found, the
+ /// descriptor holds null option.
+ template<typename Selector>
+ OptionDescriptor get(const Selector& key,
+ const uint16_t option_code) const {
+
+ // Check for presence of options.
+ OptionContainerPtr options = getAll(key);
+ if (!options || options->empty()) {
+ return (OptionDescriptor(false, false));
+ }
+
+ // Some options present, locate the one we are interested in.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ OptionContainerTypeIndex::const_iterator od_itr = idx.find(option_code);
+ if (od_itr == idx.end()) {
+ return (OptionDescriptor(false, false));
+ }
+
+ return (*od_itr);
+ }
+
+ /// @brief Returns options for the specified key and option code.
+ ///
+ /// The key should be a string, in which case it specifies an option space
+ /// name, or an uint32_t value, in which case it specifies a vendor
+ /// identifier.
+ ///
+ /// @param key Option space name or vendor identifier.
+ /// @param option_code Code of the option to be returned.
+ /// @tparam Selector one of: @c std::string or @c uint32_t
+ ///
+ /// @return List of Descriptors of the option.
+ template<typename Selector>
+ OptionDescriptorList getList(const Selector& key,
+ const uint16_t option_code) const {
+
+ OptionDescriptorList list;
+ // Check for presence of options.
+ OptionContainerPtr options = getAll(key);
+ if (!options || options->empty()) {
+ return (list);
+ }
+
+ // Some options present, locate the one we are interested in.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ OptionContainerTypeRange range = idx.equal_range(option_code);
+ // This code copies descriptors and can be optimized not doing this.
+ for (OptionContainerTypeIndex::const_iterator od_itr = range.first;
+ od_itr != range.second; ++od_itr) {
+ list.push_back(*od_itr);
+ }
+
+ return (list);
+ }
+
+ /// @brief Deletes option for the specified option space and option code.
+ ///
+ /// If the option is encapsulated within some non top level option space,
+ /// it is also deleted from all option instances encapsulating this
+ /// option space.
+ ///
+ /// @param option_space Option space name.
+ /// @param option_code Code of the option to be returned.
+ ///
+ /// @return Number of deleted options.
+ size_t del(const std::string& option_space, const uint16_t option_code);
+
+ /// @brief Deletes vendor option for the specified vendor id.
+ ///
+ /// @param vendor_id Vendor identifier.
+ /// @param option_code Option code.
+ ///
+ /// @return Number of deleted options.
+ size_t del(const uint32_t vendor_id, const uint16_t option_code);
+
+ /// @brief Deletes all options having a given database id.
+ ///
+ /// Note that there are cases when there will be multiple options
+ /// having the same id (typically id of 0). When configuration backend
+ /// is in use it sets the unique ids from the database. In cases when
+ /// the configuration backend is not used, the ids default to 0.
+ /// Passing the id of 0 would result in deleting all options that were
+ /// not added via the database.
+ ///
+ /// Both regular and vendor specific options are deleted with this
+ /// method.
+ ///
+ /// This method internally calls @c encapsulate() after deleting
+ /// options having the given id.
+ ///
+ /// @param id Identifier of the options to be deleted.
+ ///
+ /// @return Number of deleted options. Note that if a single option
+ /// instance is encapsulated by multiple options it adds 1 to the
+ /// number of deleted options even though the same instance is
+ /// deleted from multiple higher level options.
+ size_t del(const uint64_t id);
+
+ /// @brief Returns a list of configured option space names.
+ ///
+ /// The returned option space names exclude vendor option spaces,
+ /// such as "vendor-1234". These are returned by the
+ /// @ref getVendorIdsSpaceNames.
+ ///
+ /// @return List comprising option space names.
+ std::list<std::string> getOptionSpaceNames() const {
+ return (options_.getOptionSpaceNames());
+ }
+
+ /// @brief Returns a list of all configured vendor identifiers.
+ std::list<uint32_t> getVendorIds() const {
+ return (vendor_options_.getOptionSpaceNames());
+ }
+
+ /// @brief Returns a list of option space names for configured vendor ids.
+ ///
+ /// For each vendor-id the option space name returned is constructed
+ /// as "vendor-XYZ" where XYZ is a @c uint32_t value without leading
+ /// zeros.
+ ///
+ /// @return List comprising option space names for vendor options.
+ std::list<std::string> getVendorIdsSpaceNames() const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Unparse a configuration object with optionally including
+ /// the metadata.
+ ///
+ /// @param include_metadata boolean value indicating if the metadata
+ /// should be included (if true) or not (if false).
+ ///
+ /// @return A pointer to the unparsed configuration.
+ isc::data::ElementPtr
+ toElementWithMetadata(const bool include_metadata) const;
+
+private:
+
+ /// @brief Appends encapsulated options to the options in an option space.
+ ///
+ /// This method appends sub-options to the options belonging to the
+ /// particular option space. For example: if the option space "foo"
+ /// is specified, this function will go over all options belonging to
+ /// "foo" and will check which option spaces they encapsulate. For each
+ /// such option it will retrieve options for these option spaces and append
+ /// as sub-options to options belonging to "foo".
+ ///
+ /// @param option_space Name of the option space containing option to
+ /// which encapsulated options are appended.
+ void encapsulateInternal(const std::string& option_space);
+
+ /// @brief Appends encapsulated options from the option space encapsulated
+ /// by the specified option.
+ ///
+ /// This method will go over all options belonging to the encapsulated space
+ /// and will check which option spaces they encapsulate recursively,
+ /// adding these options to the current option
+ ///
+ /// @param option which encapsulated options.
+ void encapsulateInternal(const OptionPtr& option);
+
+ /// @brief Merges data from two option containers.
+ ///
+ /// This method merges options from one option container to another
+ /// option container. This function is templated because containers
+ /// may use different type of selectors. For non-vendor options
+ /// the selector is of the @c std::string type, for vendor options
+ /// the selector is of the @c uint32_t type.
+ ///
+ /// @param src_container Reference to a container from which the data
+ /// will be merged.
+ /// @param [out] dest_container Reference to a container to which the
+ /// data will be merged.
+ /// @tparam Type of the selector: @c std::string or @c uint32_t.
+ template <typename Selector>
+ void mergeInternal(const OptionSpaceContainer<OptionContainer,
+ OptionDescriptor, Selector>& src_container,
+ OptionSpaceContainer<OptionContainer,
+ OptionDescriptor, Selector>& dest_container) const;
+
+ /// @brief A flag indicating if options have been encapsulated.
+ bool encapsulated_;
+
+ /// @brief Type of the container holding options grouped by option space.
+ typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
+ std::string> OptionSpaceCollection;
+ /// @brief Container holding options grouped by option space.
+ OptionSpaceCollection options_;
+
+ /// @brief Type of the container holding options grouped by vendor id.
+ typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
+ uint32_t> VendorOptionSpaceCollection;
+ /// @brief Container holding options grouped by vendor id.
+ VendorOptionSpaceCollection vendor_options_;
+};
+
+/// @name Pointers to the @c CfgOption objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgOption> CfgOptionPtr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgOption> ConstCfgOptionPtr;
+
+/// @brief Const pointer list.
+typedef std::list<ConstCfgOptionPtr> CfgOptionList;
+
+//@}
+
+}
+}
+
+#endif // CFG_OPTION_H
diff --git a/src/lib/dhcpsrv/cfg_option_def.cc b/src/lib/dhcpsrv/cfg_option_def.cc
new file mode 100644
index 0000000..02de922
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_option_def.cc
@@ -0,0 +1,261 @@
+// Copyright (C) 2014-2021 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 <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <sstream>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void
+CfgOptionDef::copyTo(CfgOptionDef& new_config) const {
+ // Remove any existing option definitions from the destination.
+ new_config.option_definitions_.clearItems();
+ const std::list<std::string>& names =
+ option_definitions_.getOptionSpaceNames();
+ for (std::list<std::string>::const_iterator name = names.begin();
+ name != names.end(); ++name) {
+ OptionDefContainerPtr defs = getAll(*name);
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ OptionDefinitionPtr new_def =
+ OptionDefinitionPtr(new OptionDefinition(**def));
+ new_config.add(new_def);
+ }
+ }
+}
+
+bool
+CfgOptionDef::equals(const CfgOptionDef& other) const {
+ // Get our option space names.
+ const std::list<std::string>& names = option_definitions_.getOptionSpaceNames();
+ // Get option space names held by the other object.
+ const std::list<std::string>&
+ other_names = other.option_definitions_.getOptionSpaceNames();
+ // Compare that sizes are the same. If they hold different number of
+ // option space names the objects are not equal.
+ if (names.size() != other_names.size()) {
+ return (false);
+ }
+ // Iterate over all option space names and get the definitions for each
+ // of them.
+ for (std::list<std::string>::const_iterator name = names.begin();
+ name != names.end(); ++name) {
+ // Get all definitions.
+ OptionDefContainerPtr defs = getAll(*name);
+ OptionDefContainerPtr other_defs = other.getAll(*name);
+ // Compare sizes. If they hold different number of definitions,
+ // they are unequal.
+ if (defs->size() != defs->size()) {
+ return (false);
+ }
+ // For each option definition, try to find one in the other object.
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ OptionDefinitionPtr
+ other_def = other.get(*name, (*def)->getCode());
+ // Actually compare them.
+ if (!other_def || (*other_def != **def)) {
+ return (false);
+ }
+ }
+ }
+
+ // All checks passed.
+ return (true);
+}
+
+void
+CfgOptionDef::add(const OptionDefinitionPtr& def) {
+ // Option definition being added must be a valid pointer.
+ if (!def) {
+ isc_throw(MalformedOptionDefinition,
+ "option definition must not be NULL");
+ }
+ const std::string& option_space = def->getOptionSpaceName();
+
+ // Must not duplicate an option definition.
+ if (get(option_space, def->getCode())) {
+ isc_throw(DuplicateOptionDefinition, "option definition with code '"
+ << def->getCode() << "' already exists in option"
+ " space '" << option_space << "'");
+ } else if (get(option_space, def->getName())) {
+ isc_throw(DuplicateOptionDefinition, "option definition with name '"
+ << def->getName() << "' already exists in option"
+ " space '" << option_space << "'");
+
+ // Must not override standard option definition.
+ } else if (LibDHCP::getOptionDef(option_space, def->getCode())) {
+ isc_throw(BadValue, "unable to override definition of option '"
+ << def->getCode() << "' in standard option space '"
+ << option_space << "'");
+ } else if (LibDHCP::getOptionDef(option_space, def->getName())) {
+ isc_throw(BadValue, "unable to override definition of option '"
+ << def->getName() << "' in standard option space '"
+ << option_space << "'");
+ }
+ // Add the definition.
+ option_definitions_.addItem(def);
+}
+
+OptionDefContainerPtr
+CfgOptionDef::getAll(const std::string& option_space) const {
+ /// @todo Does option space require any validation here?
+ return (option_definitions_.getItems(option_space));
+}
+
+OptionDefinitionPtr
+CfgOptionDef::get(const std::string& option_space,
+ const uint16_t option_code) const {
+ // Get the pointer to collection of the option definitions that belong
+ // to the particular option space.
+ OptionDefContainerPtr defs = getAll(option_space);
+ // If there are any option definitions for this option space, get the
+ // one that has the specified option code.
+ if (defs && !defs->empty()) {
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+ // If there is more than one definition matching the option code,
+ // return the first one. In fact, it shouldn't happen that we have
+ // more than one because we check for duplicates when we add them.
+ if (std::distance(range.first, range.second) > 0) {
+ return (*range.first);
+ }
+ }
+ // Nothing found. Return NULL pointer.
+ return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
+CfgOptionDef::get(const std::string& option_space,
+ const std::string& option_name) const {
+ // Get the pointer to collection of the option definitions that belong
+ // to the particular option space.
+ OptionDefContainerPtr defs = getAll(option_space);
+ // If there are any option definitions for this option space, get the
+ // one that has the specified option name.
+ if (defs && !defs->empty()) {
+ const OptionDefContainerNameIndex& idx = defs->get<2>();
+ const OptionDefContainerNameRange& range = idx.equal_range(option_name);
+ // If there is more than one definition matching the option name,
+ // return the first one. In fact, it shouldn't happen that we have
+ // more than one because we check for duplicates when we add them.
+ if (std::distance(range.first, range.second) > 0) {
+ return (*range.first);
+ }
+ }
+ // Nothing found. Return NULL pointer.
+ return (OptionDefinitionPtr());
+}
+
+uint64_t
+CfgOptionDef::del(const uint64_t id) {
+ return (option_definitions_.deleteItems(id));
+}
+
+ElementPtr
+CfgOptionDef::toElement() const {
+ return (toElementWithMetadata(false));
+}
+
+ElementPtr
+CfgOptionDef::toElementWithMetadata(const bool include_metadata) const {
+ // option-defs value is a list of maps
+ ElementPtr result = Element::createList();
+ // Iterate through the container by names and definitions
+ const std::list<std::string>& names =
+ option_definitions_.getOptionSpaceNames();
+ for (std::list<std::string>::const_iterator name = names.begin();
+ name != names.end(); ++name) {
+ OptionDefContainerPtr defs = getAll(*name);
+ for (OptionDefContainer::const_iterator def = defs->begin();
+ def != defs->end(); ++def) {
+ // Get and fill the map for this definition
+ ElementPtr map = Element::createMap();
+ // Set user context
+ (*def)->contextToElement(map);
+ // Set space from parent iterator
+ map->set("space", Element::create(*name));
+ // Set required items: name, code and type
+ map->set("name", Element::create((*def)->getName()));
+ map->set("code", Element::create((*def)->getCode()));
+ std::string data_type =
+ OptionDataTypeUtil::getDataTypeName((*def)->getType());
+ map->set("type", Element::create(data_type));
+ // Set the array type
+ bool array_type = (*def)->getArrayType();
+ map->set("array", Element::create(array_type));
+ // Set the encapsulate space
+ std::string encapsulates = (*def)->getEncapsulatedSpace();
+ map->set("encapsulate", Element::create(encapsulates));
+ // Set the record field types
+ OptionDefinition::RecordFieldsCollection fields =
+ (*def)->getRecordFields();
+ if (!fields.empty()) {
+ std::ostringstream oss;
+ for (OptionDefinition::RecordFieldsCollection::const_iterator
+ field = fields.begin();
+ field != fields.end(); ++field) {
+ if (field != fields.begin()) {
+ oss << ", ";
+ }
+ oss << OptionDataTypeUtil::getDataTypeName(*field);
+ }
+ map->set("record-types", Element::create(oss.str()));
+ } else {
+ map->set("record-types", Element::create(std::string()));
+ }
+
+ // Include metadata if requested.
+ if (include_metadata) {
+ map->set("metadata", (*def)->getMetadata());
+ }
+
+ // Push on the list
+ result->add(map);
+ }
+ }
+ return (result);
+}
+
+void
+CfgOptionDef::merge(CfgOptionDef& other) {
+ // The definitions in "other" are presumed to be valid and
+ // not in conflict with standard definitions.
+ if (other.getContainer().getOptionSpaceNames().empty()) {
+ // Nothing to merge, don't waste cycles.
+ return;
+ }
+
+ // Iterate over this config's definitions in each space.
+ // If either a definition's name or code already exist in
+ // that space in "other", skip it. Otherwise, add it to "other".
+ for (auto space : option_definitions_.getOptionSpaceNames()) {
+ for (auto my_def : *(getAll(space))) {
+ if ((other.get(space, my_def->getName())) ||
+ (other.get(space, my_def->getCode()))) {
+ // Already in "other" so skip it.
+ continue;
+ }
+
+ // Not in "other" so add it.
+ other.add(my_def);
+ }
+ }
+
+ // Replace the current definitions with the merged set.
+ other.copyTo(*this);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_option_def.h b/src/lib/dhcpsrv/cfg_option_def.h
new file mode 100644
index 0000000..3aa4e9f
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_option_def.h
@@ -0,0 +1,196 @@
+// Copyright (C) 2014-2021 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 CFG_OPTION_DEF_H
+#define CFG_OPTION_DEF_H
+
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space_container.h>
+#include <cc/cfg_to_element.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents option definitions used by the DHCP server.
+///
+/// This class provides methods to add and retrieve option definitions
+/// specified by the administrator for the DHCP server. Option definitions
+/// specify formats of the options. This class doesn't hold information
+/// about the data being carried by the options.
+///
+/// Option definitions are grouped by option spaces. The option space is
+/// identified by the unique name which is specified as a string. The
+/// following names: "dhcp4" and "dhcp6" are reserved, though. They are
+/// names of option spaces used for standard top-level DHCPv4 and DHCPv6
+/// options respectively.
+class CfgOptionDef : public isc::data::CfgToElement {
+public:
+
+ /// @brief Copies this configuration to a new configuration.
+ ///
+ /// This method copies the option definitions stores in the configuration
+ /// to an object passed as parameter. There are no shared objects or
+ /// pointers between the original object and a copy.
+ ///
+ /// @param [out] new_config An object to which the configuration will be
+ /// copied.
+ void copyTo(CfgOptionDef& new_config) const;
+
+ /// @name Methods and operators used for comparing objects.
+ ///
+ //@{
+ /// @brief Check if configuration is equal to other configuration.
+ ///
+ /// @param other An object holding configuration to compare to.
+ ///
+ /// @return true if configurations are equal, false otherwise.
+ bool equals(const CfgOptionDef& other) const;
+
+ /// @brief Equality operator.
+ ///
+ /// @param other An object holding configuration to compare to.
+ ///
+ /// @return true if configurations are equal, false otherwise.
+ bool operator==(const CfgOptionDef& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other An object holding configuration to compare to.
+ ///
+ /// @return true if configurations are unequal, false otherwise.
+ bool operator!=(const CfgOptionDef& other) const {
+ return (!equals(other));
+ }
+
+ //@}
+
+ /// @brief Add new option definition.
+ ///
+ /// @param def option definition to be added.
+ ///
+ /// @throw isc::dhcp::DuplicateOptionDefinition when the particular
+ /// option definition already exists.
+ /// @throw isc::dhcp::MalformedOptionDefinition when the pointer to
+ /// an option definition is NULL.
+ /// @throw isc::BadValue when the option space name is empty or
+ /// when trying to override the standard option (in dhcp4 or dhcp6
+ /// option space).
+ void add(const OptionDefinitionPtr& def);
+
+ /// @brief Return option definitions for particular option space.
+ ///
+ /// @param option_space option space.
+ ///
+ /// @return Pointer to the collection of option definitions for
+ /// the particular option space. The option collection is empty
+ /// if no option exists for the option space specified.
+ OptionDefContainerPtr getAll(const std::string& option_space) const;
+
+ /// @brief Return option definition for a particular option space and code.
+ ///
+ /// @param option_space option space.
+ /// @param option_code option code.
+ ///
+ /// @return An option definition or NULL pointer if option definition
+ /// has not been found.
+ OptionDefinitionPtr get(const std::string& option_space,
+ const uint16_t option_code) const;
+
+ /// @brief Return option definition for the particular option space and name.
+ ///
+ /// @param option_space option space.
+ /// @param option_name option name.
+ ///
+ /// @return An option definition or NULL pointer if option definition
+ /// has not been found.
+ OptionDefinitionPtr get(const std::string& option_space,
+ const std::string& option_name) const;
+
+ /// @brief Deletes all option definitions having a given database id.
+ ///
+ /// Note that there are cases when there will be multiple option
+ /// definitions having the same id (typically id of 0). When
+ /// configuration backend is in use it sets the unique ids from the
+ /// database. In cases when the configuration backend is not used,
+ /// the ids default to 0. Passing the id of 0 would result in
+ /// deleting all option definitions that were not added via the
+ /// database.
+ ///
+ /// @param id Identifier of the option definitions to be deleted.
+ ///
+ /// @return Number of deleted option definitions.
+ uint64_t del(const uint64_t id);
+
+ /// @brief Returns reference to container holding option definitions.
+ const OptionDefSpaceContainer& getContainer() const {
+ return (option_definitions_);
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Unparse a configuration object with optionally including
+ /// the metadata.
+ ///
+ /// @param include_metadata boolean value indicating if the metadata
+ /// should be included (if true) or not (if false).
+ ///
+ /// @return A pointer to the unparsed configuration.
+ isc::data::ElementPtr
+ toElementWithMetadata(const bool include_metadata) const;
+
+ /// @brief Merges specified option definitions from a configuration
+ /// into this configuration.
+ ///
+ /// This method merges the option definitions from the @c other
+ /// configuration into this configuration. The merged set of
+ /// definitions is created as follows:
+ ///
+ /// Iterator over the definitions in each name space in this configuration:
+ /// If either the definition's name or code are defined in @c other
+ /// then skip over the definition otherwise add it to @c other.
+ ///
+ /// Replace this configuration's definitions with the definitions
+ /// in @c other using @c copyTo().
+ ///
+ /// @param other option definitions to merge in.
+ ///
+ /// @warning The merge operation affects @c other.
+ /// Therefore, the caller must not rely on the data held in the @c other
+ /// object after the call to @c merge. Also, the data held in @c other must
+ /// not be modified after the call to @c merge because it may affect the
+ /// merged configuration.
+ void merge(CfgOptionDef& other);
+
+private:
+
+ /// @brief A collection of option definitions.
+ ///
+ /// The option definitions stored in this container can be accessed
+ /// using the option space name they belong to.
+ OptionDefSpaceContainer option_definitions_;
+
+};
+
+/// @name Pointers to the @c CfgOptionDef objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgOptionDef> CfgOptionDefPtr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgOptionDef> ConstCfgOptionDefPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_OPTION_DEF_H
diff --git a/src/lib/dhcpsrv/cfg_rsoo.cc b/src/lib/dhcpsrv/cfg_rsoo.cc
new file mode 100644
index 0000000..30f02e6
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_rsoo.cc
@@ -0,0 +1,54 @@
+// Copyright (C) 2015,2017 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 <dhcp/dhcp6.h>
+#include <dhcpsrv/cfg_rsoo.h>
+#include <boost/lexical_cast.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+CfgRSOO::CfgRSOO()
+ : rsoo_options_() {
+ rsoo_options_.insert(D6O_ERP_LOCAL_DOMAIN_NAME);
+}
+
+void
+CfgRSOO::clear() {
+ rsoo_options_.clear();
+}
+
+bool
+CfgRSOO::enabled(const uint16_t code) const {
+ return (rsoo_options_.find(code) != rsoo_options_.end());
+}
+
+void
+CfgRSOO::enable(const uint16_t code) {
+ if (rsoo_options_.find(code) == rsoo_options_.end()) {
+ // If there's no such code added yet, let's add it
+ rsoo_options_.insert(code);
+ }
+}
+
+ElementPtr
+CfgRSOO::toElement() const {
+ ElementPtr result = Element::createList();
+ // We can use LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, *opt) too...
+ for (std::set<uint16_t>::const_iterator opt = rsoo_options_.cbegin();
+ opt != rsoo_options_.cend(); ++opt) {
+ const std::string& code = boost::lexical_cast<std::string>(*opt);
+ result->add(Element::create(code));
+ }
+ return (result);
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/cfg_rsoo.h b/src/lib/dhcpsrv/cfg_rsoo.h
new file mode 100644
index 0000000..01979e5
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_rsoo.h
@@ -0,0 +1,81 @@
+// Copyright (C) 2015,2017 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 CFG_RSOO_H
+#define CFG_RSOO_H
+
+#include <cc/cfg_to_element.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents configuration of the RSOO options for the DHCP server.
+///
+/// This class holds the set of RSOO-enabled options (see RFC6422). The list
+/// of RSOO-enabled options is maintained by IANA and currently the option
+/// 65 is officially RSSO-enabled. The list may be extended in the future
+/// and this class allows for specifying any future RSOO-enabled options.
+/// The administrator may also use existing options as RSOO-enabled.
+class CfgRSOO : public isc::data::CfgToElement {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// It adds the default (officially) RSOO-enabled options:
+ /// - OPTION_ERP_LOCAL_DOMAIN_NAME
+ CfgRSOO();
+
+ /// @brief Removes designation of all options as RSOO_enabled.
+ ///
+ /// This method removes all designations of all options as being RSOO-enabled.
+ void clear();
+
+ /// @brief Returns whether specific option code is RSOO-enabled.
+ ///
+ /// @param code Option code to check
+ /// @return true, if it is allowed in Relay-Supplied Options option
+ bool enabled(const uint16_t code) const;
+
+ /// @brief Marks specified option code as RSOO-enabled.
+ ///
+ /// @param code option to be enabled in RSOO
+ void enable(const uint16_t code);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief Contains a set of options that are allowed in RSOO option
+ ///
+ /// RSOO stands for Relay-Supplied Options option. This is an option that
+ /// is inserted by the relay agent with the intention that the server will
+ /// echo those options back to the client. Only those options marked as
+ /// RSOO-enabled may appear in the RSOO. Currently only option 65 is marked
+ /// as such, but more options may be added in the future. See RFC6422 for details.
+ std::set<uint16_t> rsoo_options_;
+
+};
+
+/// @name Pointers to the @c CfgRSOO objects.
+//@{
+/// @brief Pointer to the Non-const object.
+typedef boost::shared_ptr<CfgRSOO> CfgRSOOPtr;
+
+/// @brief Pointer to the const object.
+typedef boost::shared_ptr<const CfgRSOO> ConstCfgRSOOPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_RSOO_H
diff --git a/src/lib/dhcpsrv/cfg_shared_networks.cc b/src/lib/dhcpsrv/cfg_shared_networks.cc
new file mode 100644
index 0000000..75d7059
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_shared_networks.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2017-2019 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 <dhcpsrv/cfg_shared_networks.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+bool
+CfgSharedNetworks4::hasNetworkWithServerId(const IOAddress& server_id) const {
+ const auto& index = networks_.get<SharedNetworkServerIdIndexTag>();
+ auto network_it = index.find(server_id);
+ return (network_it != index.cend());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_shared_networks.h b/src/lib/dhcpsrv/cfg_shared_networks.h
new file mode 100644
index 0000000..a8de2f2
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_shared_networks.h
@@ -0,0 +1,243 @@
+// Copyright (C) 2017-2019 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 CFG_SHARED_NETWORKS_H
+#define CFG_SHARED_NETWORKS_H
+
+#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/shared_network.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This class holds configuration of shared networks.
+///
+/// This is a generic class implementing basic functions such as shared network
+/// addition, removal and retrieval. It also dumps configuration in the JSON
+/// format.
+///
+/// There are specializations of this class implemented as
+/// @ref CfgSharedNetworks4 and @ref CfgSharedNetworks6 for IPv4 and IPv6 cases
+/// repspectively.
+///
+/// @tparam Type of the pointer to a shared network, i.e. @ref SharedNetwork4Ptr
+/// or @ref SharedNetwork6Ptr.
+template<typename SharedNetworkPtrType, typename SharedNetworkCollection>
+class CfgSharedNetworks : public data::CfgToElement {
+public:
+ /// @brief Returns pointer to all configured shared networks.
+ const SharedNetworkCollection* getAll() const {
+ return (&networks_);
+ }
+
+ /// @brief Adds new shared network to the configuration.
+ ///
+ /// @param network Pointer to a network
+ ///
+ /// @throw isc::BadValue when name is a duplicate of existing network's
+ /// name.
+ void add(const SharedNetworkPtrType& network) {
+ if (getByName(network->getName())) {
+ isc_throw(BadValue, "duplicate network '" << network->getName() <<
+ "' found in the configuration");
+ }
+
+ static_cast<void>(networks_.push_back(network));
+ }
+
+ /// @brief Deletes shared network from the configuration.
+ ///
+ /// @param name Name of the network to be deleted.
+ ///
+ /// @throw isc::BadValue if the network can't be found.
+ void del(const std::string& name) {
+ auto& index = networks_.template get<SharedNetworkNameIndexTag>();
+ auto shared_network = index.find(name);
+ if (shared_network != index.end()) {
+ // Delete all subnets from the network
+ (*shared_network)->delAll();
+
+ // Then delete the network from the networks list.
+ index.erase(shared_network);
+ } else {
+ isc_throw(BadValue, "unable to delete non-existing network '"
+ << name << "' from shared networks configuration");
+ }
+ }
+
+ /// @brief Deletes shared networks from the configuration by id.
+ ///
+ /// Note that there are cases when there will be multiple shared
+ /// networks having the same id (typically id of 0). When configuration
+ /// backend is in use it sets the unique ids from the database.
+ /// In cases when the configuration backend is not used, the ids
+ /// default to 0. Passing the id of 0 would result in deleting all
+ /// shared networks that were not added via the database.
+ ///
+ /// @param id Identifier of the shared networks to be deleted.
+ ///
+ /// @return Number of deleted shared networks.
+ uint64_t del(const uint64_t id) {
+ auto& index = networks_.template get<SharedNetworkIdIndexTag>();
+ auto sn_range = index.equal_range(id);
+
+ // For each shared network found, dereference the subnets belonging
+ // to it.
+ for (auto it = sn_range.first; it != sn_range.second; ++it) {
+ (*it)->delAll();
+ }
+
+ // Remove the shared networks.
+ return (static_cast<uint64_t>(index.erase(id)));
+ }
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// @param name Name of the network to be retrieved.
+ ///
+ /// @return Pointer to the shared network or null pointer if the network
+ /// is not found.
+ SharedNetworkPtrType getByName(const std::string& name) const {
+ const auto& index = networks_.template get<SharedNetworkNameIndexTag>();
+ auto shared_network = index.find(name);
+ if (shared_network != index.cend()) {
+ return (*shared_network);
+ }
+ return (SharedNetworkPtrType());
+ }
+
+ /// @brief Unparses shared networks configuration.
+ ///
+ /// @return Element object representing a list of shared networks held
+ /// within configuration. The networks are sorted by their names.
+ virtual data::ElementPtr toElement() const {
+ data::ElementPtr list = data::Element::createList();
+
+ // Insert shared networks sorted by their names into the list.
+ const auto& index = networks_.template get<SharedNetworkNameIndexTag>();
+ for (auto shared_network = index.begin(); shared_network != index.end();
+ ++shared_network) {
+ list->add((*shared_network)->toElement());
+ }
+ return (list);
+ }
+
+ /// @brief Merges specified shared network configuration into this
+ /// configuration.
+ ///
+ /// This method merges networks from the @c other configuration into this
+ /// configuration. The general rule is that existing networks are replaced
+ /// by the networks from @c other.
+ ///
+ /// For each network in @c other, do the following:
+ ///
+ /// - Any associated subnets are removed. Shared networks retrieved from
+ /// config backends, do not carry their associated subnets (if any) with
+ /// them. (Subnet assignments are maintained by subnet merges).
+ /// - If a shared network of the same name already exists in this
+ /// configuration:
+ /// - All of its associated subnets are moved to the "other" network.
+ /// - The existing network is removed from this configuration.
+ /// - The "other" network's option instances are created.
+ /// - The "other" network is added to this configuration.
+ ///
+ /// @warning The merge operation may affect the @c other configuration.
+ /// Therefore, the caller must not rely on the data held in the @c other
+ /// object after the call to @c merge. Also, the data held in @c other must
+ /// not be modified after the call to @c merge because it may affect the
+ /// merged configuration.
+ ///
+ /// @param cfg_def set of of user-defined option definitions to use
+ /// when creating option instances.
+ /// @param other the shared network configuration to be merged into this
+ /// configuration.
+ void merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks& other) {
+ auto& index = networks_.template get<SharedNetworkNameIndexTag>();
+
+ // Iterate over the subnets to be merged. They will replace the existing
+ // subnets with the same id. All new subnets will be inserted into this
+ // configuration.
+ auto other_networks = other.getAll();
+ for (auto other_network = other_networks->begin();
+ other_network != other_networks->end(); ++other_network) {
+
+ // In theory we should drop subnet assignments from "other". The
+ // idea being those that come from the CB should not have subnets_
+ // populated. We will quietly throw them away, just in case.
+ (*other_network)->delAll();
+
+ // Check if the other network exists in this config.
+ auto existing_network = index.find((*other_network)->getName());
+ if (existing_network != index.end()) {
+
+ // Somehow the same instance is in both, skip it.
+ if (*existing_network == *other_network) {
+ continue;
+ }
+
+ // Network exists, which means we're updating it.
+ // First we need to move its subnets to the new
+ // version of the network.
+ const auto subnets = (*existing_network)->getAllSubnets();
+
+ auto copy_subnets(*subnets);
+ for (auto subnet = copy_subnets.cbegin(); subnet != copy_subnets.cend(); ++subnet) {
+ (*existing_network)->del((*subnet)->getID());
+ (*other_network)->add(*subnet);
+ }
+
+ // Now we discard the existing copy of the network.
+ index.erase(existing_network);
+ }
+
+ // Create the network's options based on the given definitions.
+ (*other_network)->getCfgOption()->createOptions(cfg_def);
+
+ // Add the new/updated nework.
+ static_cast<void>(networks_.push_back(*other_network));
+ }
+ }
+
+protected:
+
+ /// @brief Multi index container holding shared networks.
+ SharedNetworkCollection networks_;
+};
+
+/// @brief Represents configuration of IPv4 shared networks.
+class CfgSharedNetworks4 : public CfgSharedNetworks<SharedNetwork4Ptr,
+ SharedNetwork4Collection> {
+public:
+ /// @brief Checks if specified server identifier has been specified for
+ /// any network.
+ ///
+ /// @param server_id Server identifier.
+ ///
+ /// @return true if there is a network with a specified server identifier.
+ bool hasNetworkWithServerId(const asiolink::IOAddress& server_id) const;
+};
+
+/// @brief Pointer to the configuration of IPv4 shared networks.
+typedef boost::shared_ptr<CfgSharedNetworks4> CfgSharedNetworks4Ptr;
+
+/// @brief Represents configuration of IPv6 shared networks.
+class CfgSharedNetworks6 : public CfgSharedNetworks<SharedNetwork6Ptr,
+ SharedNetwork6Collection> {
+};
+
+/// @brief Pointer to the configuration of IPv6 shared networks.
+typedef boost::shared_ptr<CfgSharedNetworks6> CfgSharedNetworks6Ptr;
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CFG_SHARED_NETWORKS_H
diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc
new file mode 100644
index 0000000..fe4967a
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_subnets4.cc
@@ -0,0 +1,649 @@
+// Copyright (C) 2014-2023 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 <dhcp/iface_mgr.h>
+#include <dhcp/option_custom.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet_id.h>
+#include <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <stats/stats_mgr.h>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void
+CfgSubnets4::add(const Subnet4Ptr& subnet) {
+ if (getBySubnetId(subnet->getID())) {
+ isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv4 subnet '"
+ << subnet->getID() << "' is already in use");
+
+ } else if (getByPrefix(subnet->toText())) {
+ /// @todo: Check that this new subnet does not cross boundaries of any
+ /// other already defined subnet.
+ isc_throw(isc::dhcp::DuplicateSubnetID, "subnet with the prefix of '"
+ << subnet->toText() << "' already exists");
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
+ .arg(subnet->toText());
+ static_cast<void>(subnets_.insert(subnet));
+}
+
+Subnet4Ptr
+CfgSubnets4::replace(const Subnet4Ptr& subnet) {
+ // Get the subnet with the same ID.
+ const SubnetID& subnet_id = subnet->getID();
+ auto& index = subnets_.template get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ isc_throw(BadValue, "There is no IPv4 subnet with ID " <<subnet_id);
+ }
+ Subnet4Ptr old = *subnet_it;
+ bool ret = index.replace(subnet_it, subnet);
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_UPDATE_SUBNET4)
+ .arg(subnet_id).arg(ret);
+ if (ret) {
+ return (old);
+ } else {
+ return (Subnet4Ptr());
+ }
+}
+
+void
+CfgSubnets4::del(const ConstSubnet4Ptr& subnet) {
+ del(subnet->getID());
+}
+
+void
+CfgSubnets4::del(const SubnetID& subnet_id) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ isc_throw(BadValue, "no subnet with ID of '" << subnet_id
+ << "' found");
+ }
+
+ Subnet4Ptr subnet = *subnet_it;
+
+ index.erase(subnet_it);
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DEL_SUBNET4)
+ .arg(subnet->toText());
+}
+
+void
+CfgSubnets4::merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks4Ptr networks,
+ CfgSubnets4& other) {
+ auto& index_id = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto& index_prefix = subnets_.get<SubnetPrefixIndexTag>();
+
+ // Iterate over the subnets to be merged. They will replace the existing
+ // subnets with the same id. All new subnets will be inserted into the
+ // configuration into which we're merging.
+ auto const& other_subnets = other.getAll();
+ for (auto const& other_subnet : (*other_subnets)) {
+
+ // Check if there is a subnet with the same ID.
+ auto subnet_id_it = index_id.find(other_subnet->getID());
+ if (subnet_id_it != index_id.end()) {
+
+ // Subnet found.
+ auto existing_subnet = *subnet_id_it;
+
+ // If the existing subnet and other subnet
+ // are the same instance skip it.
+ if (existing_subnet == other_subnet) {
+ continue;
+ }
+
+ // Updating the prefix can lead to problems... e.g. pools
+ // and reservations going outside range.
+ // @todo: check prefix change.
+
+ // We're going to replace the existing subnet with the other
+ // version. If it belongs to a shared network, we need
+ // remove it from that network.
+ SharedNetwork4Ptr network;
+ existing_subnet->getSharedNetwork(network);
+ if (network) {
+ network->del(existing_subnet->getID());
+ }
+
+ // Now we remove the existing subnet.
+ index_id.erase(subnet_id_it);
+ }
+
+ // Check if there is a subnet with the same prefix.
+ auto subnet_prefix_it = index_prefix.find(other_subnet->toText());
+ if (subnet_prefix_it != index_prefix.end()) {
+
+ // Subnet found.
+ auto existing_subnet = *subnet_prefix_it;
+
+ // Updating the id can lead to problems... e.g. reservation
+ // for the previous subnet ID.
+ // @todo: check reservations
+
+ // We're going to replace the existing subnet with the other
+ // version. If it belongs to a shared network, we need
+ // remove it from that network.
+ SharedNetwork4Ptr network;
+ existing_subnet->getSharedNetwork(network);
+ if (network) {
+ network->del(existing_subnet->getID());
+ }
+
+ // Now we remove the existing subnet.
+ index_prefix.erase(subnet_prefix_it);
+ }
+
+ // Create the subnet's options based on the given definitions.
+ other_subnet->getCfgOption()->createOptions(cfg_def);
+
+ // Create the options for pool based on the given definitions.
+ for (const auto& pool : other_subnet->getPoolsWritable(Lease::TYPE_V4)) {
+ pool->getCfgOption()->createOptions(cfg_def);
+ }
+
+ // Add the "other" subnet to the our collection of subnets.
+ static_cast<void>(subnets_.insert(other_subnet));
+
+ // If it belongs to a shared network, find the network and
+ // add the subnet to it
+ std::string network_name = other_subnet->getSharedNetworkName();
+ if (!network_name.empty()) {
+ SharedNetwork4Ptr network = networks->getByName(network_name);
+ if (network) {
+ network->add(other_subnet);
+ } else {
+ // This implies the shared-network collection we were given
+ // is out of sync with the subnets we were given.
+ isc_throw(InvalidOperation, "Cannot assign subnet ID of "
+ << other_subnet->getID()
+ << " to shared network: " << network_name
+ << ", network does not exist");
+ }
+ }
+ // Instantiate the configured allocator and its state.
+ other_subnet->createAllocators();
+ }
+}
+
+ConstSubnet4Ptr
+CfgSubnets4::getBySubnetId(const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
+}
+
+ConstSubnet4Ptr
+CfgSubnets4::getByPrefix(const std::string& subnet_text) const {
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_text);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
+}
+
+bool
+CfgSubnets4::hasSubnetWithServerId(const asiolink::IOAddress& server_id) const {
+ const auto& index = subnets_.get<SubnetServerIdIndexTag>();
+ auto subnet_it = index.find(server_id);
+ return (subnet_it != index.cend());
+}
+
+SubnetSelector
+CfgSubnets4::initSelector(const Pkt4Ptr& query) {
+ SubnetSelector selector;
+ selector.ciaddr_ = query->getCiaddr();
+ selector.giaddr_ = query->getGiaddr();
+ selector.local_address_ = query->getLocalAddr();
+ selector.remote_address_ = query->getRemoteAddr();
+ selector.client_classes_ = query->classes_;
+ selector.iface_name_ = query->getIface();
+
+ // If the link-selection sub-option is present, extract its value.
+ // "The link-selection sub-option is used by any DHCP relay agent
+ // that desires to specify a subnet/link for a DHCP client request
+ // that it is relaying but needs the subnet/link specification to
+ // be different from the IP address the DHCP server should use
+ // when communicating with the relay agent." (RFC 3527)
+ //
+ // Try first Relay Agent Link Selection sub-option
+ OptionPtr rai = query->getOption(DHO_DHCP_AGENT_OPTIONS);
+ if (rai) {
+ OptionCustomPtr rai_custom =
+ boost::dynamic_pointer_cast<OptionCustom>(rai);
+ if (rai_custom) {
+ // If Relay Agent Information Link Selection is ignored in the
+ // configuration, skip returning the related subnet selector here,
+ // and move on to normal subnet selection.
+ bool ignore_link_sel = CfgMgr::instance().getCurrentCfg()->
+ getIgnoreRAILinkSelection();
+ if (!ignore_link_sel) {
+ OptionPtr link_select =
+ rai_custom->getOption(RAI_OPTION_LINK_SELECTION);
+ if (link_select) {
+ OptionBuffer link_select_buf = link_select->getData();
+ if (link_select_buf.size() == sizeof(uint32_t)) {
+ selector.option_select_ =
+ IOAddress::fromBytes(AF_INET, &link_select_buf[0]);
+ return (selector);
+ }
+ }
+ }
+ }
+ }
+ // The query does not include a RAI option or that option does
+ // not contain the link-selection sub-option. Try subnet-selection
+ // option.
+ OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
+ if (sbnsel) {
+ OptionCustomPtr oc =
+ boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
+ if (oc) {
+ selector.option_select_ = oc->readAddress();
+ }
+ }
+ return (selector);
+}
+
+Subnet4Ptr
+CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
+ for (auto const& subnet : subnets_) {
+ Cfg4o6& cfg4o6 = subnet->get4o6();
+
+ // Is this an 4o6 subnet at all?
+ if (!cfg4o6.enabled()) {
+ continue; // No? Let's try the next one.
+ }
+
+ // First match criteria: check if we have a prefix/len defined.
+ std::pair<asiolink::IOAddress, uint8_t> pref = cfg4o6.getSubnet4o6();
+ if (!pref.first.isV6Zero()) {
+
+ // Let's check if the IPv6 address is in range
+ IOAddress first = firstAddrInPrefix(pref.first, pref.second);
+ IOAddress last = lastAddrInPrefix(pref.first, pref.second);
+ if ((first <= selector.remote_address_) &&
+ (selector.remote_address_ <= last)) {
+ return (subnet);
+ }
+ }
+
+ // Second match criteria: check if the interface-id matches
+ if (cfg4o6.getInterfaceId() && selector.interface_id_ &&
+ cfg4o6.getInterfaceId()->equals(selector.interface_id_)) {
+ return (subnet);
+ }
+
+ // Third match criteria: check if the interface name matches
+ if (!cfg4o6.getIface4o6().empty() && !selector.iface_name_.empty()
+ && cfg4o6.getIface4o6() == selector.iface_name_) {
+ return (subnet);
+ }
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_SUBNET4O6_SELECT_FAILED);
+
+ // Ok, wasn't able to find any matching subnet.
+ return (Subnet4Ptr());
+}
+
+Subnet4Ptr
+CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
+ // First use RAI link select sub-option or subnet select option
+ if (!selector.option_select_.isV4Zero()) {
+ return (selectSubnet(selector.option_select_,
+ selector.client_classes_));
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET4_SELECT_NO_RAI_OPTIONS);
+ }
+
+ // If relayed message has been received, try to match the giaddr with the
+ // relay address specified for a subnet and/or shared network. It is also
+ // possible that the relay address will not match with any of the relay
+ // addresses across all subnets, but we need to verify that for all subnets
+ // before we can try to use the giaddr to match with the subnet prefix.
+ if (!selector.giaddr_.isV4Zero()) {
+ for (auto const& subnet : subnets_) {
+
+ // If relay information is specified for this subnet, it must match.
+ // Otherwise, we ignore this subnet.
+ if (subnet->hasRelays()) {
+ if (!subnet->hasRelayAddress(selector.giaddr_)) {
+ continue;
+ }
+ } else {
+ // Relay information is not specified on the subnet level,
+ // so let's try matching on the shared network level.
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (!network || !(network->hasRelayAddress(selector.giaddr_))) {
+ continue;
+ }
+ }
+
+ // If a subnet meets the client class criteria return it.
+ if (subnet->clientSupported(selector.client_classes_)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET4_RELAY)
+ .arg(subnet->toText())
+ .arg(selector.giaddr_.toText());
+ return (subnet);
+ }
+ }
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_NO_MATCH)
+ .arg(selector.giaddr_.toText());
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET4_SELECT_NO_RELAY_ADDRESS);
+ }
+
+ // If we got to this point it means that we were not able to match the
+ // giaddr with any of the addresses specified for subnets. Let's determine
+ // what address from the client's packet to use to match with the
+ // subnets' prefixes.
+
+ IOAddress address = IOAddress::IPV4_ZERO_ADDRESS();
+ // If there is a giaddr, use it for subnet selection.
+ if (!selector.giaddr_.isV4Zero()) {
+ address = selector.giaddr_;
+
+ // If it is a Renew or Rebind, use the ciaddr.
+ } else if (!selector.ciaddr_.isV4Zero() &&
+ !selector.local_address_.isV4Bcast()) {
+ address = selector.ciaddr_;
+
+ // If ciaddr is not specified, use the source address.
+ } else if (!selector.remote_address_.isV4Zero() &&
+ !selector.local_address_.isV4Bcast()) {
+ address = selector.remote_address_;
+
+ // If local interface name is known, use the local address on this
+ // interface.
+ } else if (!selector.iface_name_.empty()) {
+ IfacePtr iface = IfaceMgr::instance().getIface(selector.iface_name_);
+ // This should never happen in the real life. Hence we throw an
+ // exception.
+ if (iface == NULL) {
+ isc_throw(isc::BadValue, "interface " << selector.iface_name_
+ << " doesn't exist and therefore it is impossible"
+ " to find a suitable subnet for its IPv4 address");
+ }
+
+ // Attempt to select subnet based on the interface name.
+ Subnet4Ptr subnet = selectSubnet(selector.iface_name_,
+ selector.client_classes_);
+
+ // If it matches - great. If not, we'll try to use a different
+ // selection criteria below.
+ if (subnet) {
+ return (subnet);
+ } else {
+ // Let's try to get an address from the local interface and
+ // try to match it to defined subnet.
+ iface->getAddress4(address);
+ }
+ }
+
+ // Unable to find a suitable address to use for subnet selection.
+ if (address.isV4Zero()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET4_SELECT_NO_USABLE_ADDRESS);
+
+ return (Subnet4Ptr());
+ }
+
+ // We have identified an address in the client's packet that can be
+ // used for subnet selection. Match this packet with the subnets.
+ return (selectSubnet(address, selector.client_classes_));
+}
+
+Subnet4Ptr
+CfgSubnets4::selectSubnet(const std::string& iface,
+ const ClientClasses& client_classes) const {
+ for (auto const& subnet : subnets_) {
+ Subnet4Ptr subnet_selected;
+
+ // First, try subnet specific interface name.
+ if (!subnet->getIface(Network4::Inheritance::NONE).empty()) {
+ if (subnet->getIface(Network4::Inheritance::NONE) == iface) {
+ subnet_selected = subnet;
+ }
+
+ } else {
+ // Interface not specified for a subnet, so let's try if
+ // we can match with shared network specific setting of
+ // the interface.
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network &&
+ (network->getIface(Network4::Inheritance::NONE) == iface)) {
+ subnet_selected = subnet;
+ }
+ }
+
+ if (subnet_selected) {
+ // If a subnet meets the client class criteria return it.
+ if (subnet_selected->clientSupported(client_classes)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET4_IFACE)
+ .arg(subnet->toText())
+ .arg(iface);
+ return (subnet_selected);
+ }
+ }
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_NO_MATCH)
+ .arg(iface);
+
+ // Failed to find a subnet.
+ return (Subnet4Ptr());
+}
+
+Subnet4Ptr
+CfgSubnets4::getSubnet(const SubnetID id) const {
+ /// @todo: Once this code is migrated to multi-index container, use
+ /// an index rather than full scan.
+ for (auto const& subnet : subnets_) {
+ if (subnet->getID() == id) {
+ return (subnet);
+ }
+ }
+ return (Subnet4Ptr());
+}
+
+Subnet4Ptr
+CfgSubnets4::selectSubnet(const IOAddress& address,
+ const ClientClasses& client_classes) const {
+ for (auto const& subnet : subnets_) {
+
+ // Address is in range for the subnet prefix, so return it.
+ if (!subnet->inRange(address)) {
+ continue;
+ }
+
+ // If a subnet meets the client class criteria return it.
+ if (subnet->clientSupported(client_classes)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET4_ADDR)
+ .arg(subnet->toText())
+ .arg(address.toText());
+ return (subnet);
+ }
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_NO_MATCH)
+ .arg(address.toText());
+
+ // Failed to find a subnet.
+ return (Subnet4Ptr());
+}
+
+SubnetIDSet
+CfgSubnets4::getLinks(const IOAddress& link_addr, uint8_t& link_len) const {
+ SubnetIDSet links;
+ bool link_len_set = false;
+ for (auto const& subnet : subnets_) {
+ if (!subnet->inRange(link_addr)) {
+ continue;
+ }
+ uint8_t plen = subnet->get().second;
+ if (!link_len_set || (plen < link_len)) {
+ link_len_set = true;
+ link_len = plen;
+ }
+ links.insert(subnet->getID());
+ }
+ return (links);
+}
+
+void
+CfgSubnets4::removeStatistics() {
+ using namespace isc::stats;
+
+ // For each v4 subnet currently configured, remove the statistic.
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ for (auto const& subnet4 : subnets_) {
+ SubnetID subnet_id = subnet4->getID();
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "total-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "assigned-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+
+ for (const auto& pool : subnet4->getPools(Lease::TYPE_V4)) {
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "total-addresses")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-addresses")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "cumulative-assigned-addresses")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "declined-addresses")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-declined-addresses")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-leases")));
+ }
+ }
+}
+
+void
+CfgSubnets4::updateStatistics() {
+ using namespace isc::stats;
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ for (auto const& subnet4 : subnets_) {
+ SubnetID subnet_id = subnet4->getID();
+
+ stats_mgr.setValue(StatsMgr::
+ generateName("subnet", subnet_id, "total-addresses"),
+ int64_t(subnet4->getPoolCapacity(Lease::TYPE_V4)));
+ const std::string& name(StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-addresses"));
+ if (!stats_mgr.getObservation(name)) {
+ stats_mgr.setValue(name, static_cast<int64_t>(0));
+ }
+
+ const std::string& name_reuses(StatsMgr::generateName("subnet", subnet_id,
+ "v4-lease-reuses"));
+ if (!stats_mgr.getObservation(name_reuses)) {
+ stats_mgr.setValue(name_reuses, int64_t(0));
+ }
+
+ const std::string& name_conflicts(StatsMgr::generateName("subnet", subnet_id,
+ "v4-reservation-conflicts"));
+ if (!stats_mgr.getObservation(name_conflicts)) {
+ stats_mgr.setValue(name_conflicts, static_cast<int64_t>(0));
+ }
+
+ for (const auto& pool : subnet4->getPools(Lease::TYPE_V4)) {
+ const std::string& name_total(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "total-addresses")));
+ if (!stats_mgr.getObservation(name_total)) {
+ stats_mgr.setValue(name_total, static_cast<int64_t>(pool->getCapacity()));
+ } else {
+ stats_mgr.addValue(name_total, static_cast<int64_t>(pool->getCapacity()));
+ }
+
+ const std::string& name_ca(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "cumulative-assigned-addresses")));
+ if (!stats_mgr.getObservation(name_ca)) {
+ stats_mgr.setValue(name_ca, static_cast<int64_t>(0));
+ }
+ }
+ }
+
+ // Only recount the stats if we have subnets.
+ if (subnets_.begin() != subnets_.end()) {
+ LeaseMgrFactory::instance().recountLeaseStats4();
+ }
+}
+
+void
+CfgSubnets4::initAllocatorsAfterConfigure() {
+ for (auto subnet : subnets_) {
+ subnet->initAllocatorsAfterConfigure();
+ }
+}
+
+void
+CfgSubnets4::clear() {
+ subnets_.clear();
+}
+
+ElementPtr
+CfgSubnets4::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate subnets
+ for (auto const& subnet : subnets_) {
+ result->add(subnet->toElement());
+ }
+ return (result);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_subnets4.h b/src/lib/dhcpsrv/cfg_subnets4.h
new file mode 100644
index 0000000..84367bb
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_subnets4.h
@@ -0,0 +1,361 @@
+// Copyright (C) 2014-2023 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 CFG_SUBNETS4_H
+#define CFG_SUBNETS4_H
+
+#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <dhcp/pkt4.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds subnets configured for the DHCPv4 server.
+///
+/// This class holds a collection of subnets configured for the DHCPv4 server.
+/// It allows for retrieving a subnet for the particular client using various
+/// parameters extracted from the DHCPv4 message. These parameters must be
+/// assigned to the appropriate members of the @c CfgSubnets4::Selector
+/// structure.
+///
+/// See @c CfgSubnets4::selectSubnet documentation for more details on how the
+/// subnet is selected for the client.
+class CfgSubnets4 : public isc::data::CfgToElement {
+public:
+
+ /// @brief Adds new subnet to the configuration.
+ ///
+ /// @param subnet Pointer to the subnet being added.
+ ///
+ /// @throw isc::DuplicateSubnetID If the subnet id for the new subnet
+ /// duplicates id of an existing subnet.
+ void add(const Subnet4Ptr& subnet);
+
+ /// @brief Replaces subnet in the configuration.
+ ///
+ /// This method replaces a subnet by another subnet with the same ID.
+ /// The prefix should be the same too.
+ ///
+ /// @param subnet Pointer to the subnet being updated.
+ /// @throw BadValue if the subnet to update does not exit.
+ /// @return Pointer to the replaced subnet or NULL if it failed.
+ Subnet4Ptr replace(const Subnet4Ptr& subnet);
+
+ /// @brief Removes subnet from the configuration.
+ ///
+ /// @param subnet Pointer to the subnet to be removed.
+ ///
+ /// @throw isc::BadValue if such subnet doesn't exist.
+ void del(const ConstSubnet4Ptr& subnet);
+
+ /// @brief Removes subnet from the configuration.
+ ///
+ /// @param subnet_id Identifier of the subnet to be removed.
+ ///
+ /// @throw isc::BadValue if such subnet doesn't exist.
+ void del(const SubnetID& subnet_id);
+
+ /// @brief Merges specified subnet configuration into this configuration.
+ ///
+ /// This method merges subnets from the @c other configuration into this
+ /// configuration. The general rule is that existing subnets are replaced
+ /// by the subnets from @c other. If there is no corresponding subnet in
+ /// this configuration the subnet from @c other configuration is inserted.
+ ///
+ /// The complexity of the merge process stems from the associations between
+ /// the subnets and shared networks. It is assumed that subnets in @c other
+ /// are the authority on their shared network assignments. It is also
+ /// assumed that @ networks is the list of shared networks that should be
+ /// used in making assignments. The general concept is that the overarching
+ /// merge process will first merge shared networks and then pass that list
+ /// of networks into this method. Subnets from @c other are then merged
+ /// into this configuration as follows:
+ ///
+ /// For each subnet in @c other:
+ ///
+ /// - If a subnet of the same ID already exists in this configuration:
+ /// -# If it belongs to a shared network, remove it from that network
+ /// -# Remove the subnet from this configuration and discard it
+ ///
+ /// - Create the subnet's option instance, as well as any options
+ /// that belong to any of the subnet's pools.
+ /// - Add the subnet from @c other to this configuration.
+ /// - If that subnet is associated to shared network, find that network
+ /// in @ networks and add that subnet to it.
+ ///
+ /// @warning The merge operation affects the @c other configuration.
+ /// Therefore, the caller must not rely on the data held in the @c other
+ /// object after the call to @c merge. Also, the data held in @c other must
+ /// not be modified after the call to @c merge because it may affect the
+ /// merged configuration.
+ ///
+ /// @param cfg_def set of user-defined option definitions to use
+ /// when creating option instances.
+ /// @param networks collection of shared networks that to which assignments
+ /// should be added. In other words, the list of shared networks that belong
+ /// to the same SrvConfig instance we are merging into.
+ /// @param other the subnet configuration to be merged into this
+ /// configuration.
+ void merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks4Ptr networks,
+ CfgSubnets4& other);
+
+ /// @brief Returns pointer to the collection of all IPv4 subnets.
+ ///
+ /// This is used in a hook (subnet4_select), where the hook is able
+ /// to choose a different subnet. Server code has to offer a list
+ /// of possible choices (i.e. all subnets).
+ ///
+ /// @return A pointer to const Subnet4 collection
+ const Subnet4Collection* getAll() const {
+ return (&subnets_);
+ }
+
+ /// @brief Returns const pointer to a subnet identified by the specified
+ /// subnet identifier.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configuration is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Pointer to the @c Subnet4 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet4Ptr getBySubnetId(const SubnetID& subnet_id) const;
+
+ /// @brief Returns const pointer to a subnet which matches the specified
+ /// prefix in the canonical form.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configuration is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_prefix Subnet prefix, e.g. 10.2.3.0/24
+ ///
+ /// @return Pointer to the @c Subnet4 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet4Ptr getByPrefix(const std::string& subnet_prefix) const;
+
+ /// @brief Checks if specified server identifier has been specified for
+ /// any subnet.
+ ///
+ /// @param server_id Server identifier.
+ ///
+ /// @return true if there is a subnet with a specified server identifier.
+ bool hasSubnetWithServerId(const asiolink::IOAddress& server_id) const;
+
+ /// @brief Build selector from a client's message.
+ ///
+ /// @note: code moved from server.
+ ///
+ /// @param query client's message.
+ /// @return filled selector.
+ static SubnetSelector initSelector(const Pkt4Ptr& query);
+
+ /// @brief Returns a pointer to the selected subnet.
+ ///
+ /// This method tries to retrieve the subnet for the client using various
+ /// parameters extracted from the client's message using the following
+ /// logic.
+ ///
+ /// First when link select suboption of relay agent information option
+ /// or subnet select option in this order exists the address is used
+ ///
+ /// If the giaddr value is set in the selector it means that the client's
+ /// message was relayed. The subnet configuration allows for setting the
+ /// relay address for each subnet to indicate that the subnet must be
+ /// assigned when the packet was transmitted over the particular relay.
+ /// This method first tries to match the giaddr with the relay addresses
+ /// specified for all subnets. If the relay address for the subnet is equal
+ /// to the address of the relay through which the message was transmitted,
+ /// the particular subnet is returned.
+ ///
+ /// If the giaddr is not matched with any of the relay addresses in any
+ /// subnet or the message was not relayed, the method will need to try to
+ /// match one of the addresses in the client's message with the prefixes
+ /// of the existing subnets. Depending whether it is a relayed message,
+ /// message from the renewing client or a new allocation, the server will
+ /// pick one of the following addresses for this matching:
+ /// - giaddr - for relayed message
+ /// - ciaddr - for renewing or rebinding client
+ /// - source address - for the renewing client which didn't provide ciaddr
+ /// - address on the local server's interface if this is a new allocation
+ /// requested by the directly connected client
+ ///
+ /// If the address matches with a subnet, the subnet is returned.
+ ///
+ /// @todo This method requires performance improvement! It currently
+ /// iterates over all existing subnets (possibly a couple of times)
+ /// to find the one which fulfills the search criteria. The subnet storage
+ /// is implemented as a simple STL vector which precludes fast searches
+ /// using specific keys. Hence, full scan is required. To improve the
+ /// search performance a different container type is required, e.g.
+ /// multi-index container, or something of a similar functionality.
+ ///
+ /// @param selector Const reference to the selector structure which holds
+ /// various information extracted from the client's packet which are used
+ /// to find appropriate subnet.
+ ///
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ /// @throw isc::BadValue if the values in the subnet selector are invalid
+ /// or they are insufficient to select a subnet.
+ Subnet4Ptr selectSubnet(const SubnetSelector& selector) const;
+
+ /// @brief Returns subnet with specified subnet-id value
+ ///
+ /// Warning: this method uses full scan. Its use is not recommended for
+ /// packet processing.
+ /// Please use @ref getBySubnetId instead when possible.
+ ///
+ /// @return Subnet (or NULL)
+ Subnet4Ptr getSubnet(const SubnetID id) const;
+
+ /// @brief Returns a pointer to a subnet if provided address is in its range.
+ ///
+ /// This method returns a pointer to the subnet if the address passed in
+ /// parameter is in range with this subnet. This is mainly used for unit
+ /// testing. This method is also called by the
+ /// @c selectSubnet(SubnetSelector).
+ ///
+ /// @todo This method requires performance improvement! It currently
+ /// iterates over all existing subnets to find the one which fulfills
+ /// the search criteria. The subnet storage is implemented as a simple
+ /// STL vector which precludes fast searches using specific keys.
+ /// Hence, full scan is required. To improve the search performance a
+ /// different container type is required, e.g. multi-index container,
+ /// or something of a similar functionality.
+ ///
+ /// @param address Address for which the subnet is searched.
+ /// @param client_classes Optional parameter specifying the classes that
+ /// the client belongs to.
+ ///
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ Subnet4Ptr selectSubnet(const asiolink::IOAddress& address,
+ const ClientClasses& client_classes
+ = ClientClasses()) const;
+
+ /// @brief Returns a pointer to a subnet if provided interface name matches.
+ ///
+ /// This method returns a pointer to the subnet if the interface name passed
+ /// in parameter iface matches that of a subnet. This is mainly used for matching
+ /// local incoming traffic, even when the addresses on local interfaces do
+ /// not match a subnet definition. This method is also called by the
+ /// @c selectSubnet(SubnetSelector).
+ ///
+ /// @todo This method requires performance improvement! It currently
+ /// iterates over all existing subnets to find the one which fulfills
+ /// the search criteria. The subnet storage is implemented as a simple
+ /// STL vector which precludes fast searches using specific keys.
+ /// Hence, full scan is required. To improve the search performance a
+ /// different container type is required, e.g. multi-index container,
+ /// or something of a similar functionality.
+ ///
+ /// @param iface name of the interface to be matched.
+ /// @param client_classes Optional parameter specifying the classes that
+ /// the client belongs to.
+ ///
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ Subnet4Ptr selectSubnet(const std::string& iface,
+ const ClientClasses& client_classes) const;
+
+ /// @brief Attempts to do subnet selection based on DHCP4o6 information
+ ///
+ /// The algorithm implemented is as follows:
+ ///
+ /// - First: try to match IPv6 subnet (4o6-subnet parameter) with the
+ /// remote IPv6 address of the incoming packet
+ /// - Second: try to match interface-id (4o6-interface-id parameter)
+ /// with the interface-id option in the incoming 4o6 packet
+ /// - Third: try to match interface-name (4o6-interface parameter)
+ /// with the name of the interface the incoming 4o6 packet was
+ /// received over.
+ ///
+ /// @todo: Add additional selection criteria. See
+ /// https://gitlab.isc.org/isc-projects/kea/wikis/designs/dhcpv4o6-design for details.
+ ///
+ /// @param selector Const reference to the selector structure which holds
+ /// various information extracted from the client's packet which are used
+ /// to find appropriate subnet.
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ Subnet4Ptr
+ selectSubnet4o6(const SubnetSelector& selector) const;
+
+ /// @brief Convert a link address into a link set.
+ ///
+ /// Given a link address this returns the ordered list aka set of id
+ /// of subnets the address belongs to. It also sets the minimum link
+ /// length when there is at least one subnet.
+ ///
+ /// @param link_addr The link address.
+ /// @param[out] link_len The minimum link length.
+ /// @return The set of subnet ids the link address belongs to.
+ SubnetIDSet getLinks(const asiolink::IOAddress& link_addr,
+ uint8_t& link_len) const;
+
+ /// @brief Updates statistics.
+ ///
+ /// This method updates statistics that are affected by the newly committed
+ /// configuration. In particular, it updates the number of available addresses
+ /// in each subnet. Other statistics may be added in the future. In general,
+ /// these are statistics that are dependent only on configuration, so they are
+ /// not expected to change until the next reconfiguration event.
+ void updateStatistics();
+
+ /// @brief Removes statistics.
+ ///
+ /// During commitment of a new configuration, we need to get rid of the old
+ /// statistics for the old configuration. In particular, we need to remove
+ /// anything related to subnets, as there may be fewer subnets in the new
+ /// configuration and also subnet-ids may change.
+ void removeStatistics();
+
+ /// @brief Calls @c initAllocatorsAfterConfigure for each subnet.
+ void initAllocatorsAfterConfigure();
+
+ /// @brief Clears all subnets from the configuration.
+ void clear();
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief A container for IPv4 subnets.
+ Subnet4Collection subnets_;
+
+};
+
+/// @name Pointer to the @c CfgSubnets4 objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgSubnets4> CfgSubnets4Ptr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgSubnets4> ConstCfgSubnets4Ptr;
+
+//@}
+
+}
+}
+
+#endif // CFG_SUBNETS4_H
diff --git a/src/lib/dhcpsrv/cfg_subnets6.cc b/src/lib/dhcpsrv/cfg_subnets6.cc
new file mode 100644
index 0000000..93565ac
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_subnets6.cc
@@ -0,0 +1,595 @@
+// Copyright (C) 2014-2023 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 <dhcp/dhcp6.h>
+#include <dhcp/option_custom.h>
+#include <asiolink/addr_utilities.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet_id.h>
+#include <stats/stats_mgr.h>
+#include <boost/foreach.hpp>
+#include <string.h>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+void
+CfgSubnets6::add(const Subnet6Ptr& subnet) {
+ if (getBySubnetId(subnet->getID())) {
+ isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv6 subnet '"
+ << subnet->getID() << "' is already in use");
+
+ } else if (getByPrefix(subnet->toText())) {
+ /// @todo: Check that this new subnet does not cross boundaries of any
+ /// other already defined subnet.
+ isc_throw(isc::dhcp::DuplicateSubnetID, "subnet with the prefix of '"
+ << subnet->toText() << "' already exists");
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+ .arg(subnet->toText());
+ static_cast<void>(subnets_.insert(subnet));
+}
+
+Subnet6Ptr
+CfgSubnets6::replace(const Subnet6Ptr& subnet) {
+ // Get the subnet with the same ID.
+ const SubnetID& subnet_id = subnet->getID();
+ auto& index = subnets_.template get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ isc_throw(BadValue, "There is no IPv6 subnet with ID " << subnet_id);
+ }
+ Subnet6Ptr old = *subnet_it;
+ bool ret = index.replace(subnet_it, subnet);
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_UPDATE_SUBNET6)
+ .arg(subnet_id).arg(ret);
+ if (ret) {
+ return (old);
+ } else {
+ return (Subnet6Ptr());
+ }
+}
+
+void
+CfgSubnets6::del(const ConstSubnet6Ptr& subnet) {
+ del(subnet->getID());
+}
+
+void
+CfgSubnets6::del(const SubnetID& subnet_id) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ isc_throw(BadValue, "no subnet with ID of '" << subnet_id
+ << "' found");
+ }
+
+ Subnet6Ptr subnet = *subnet_it;
+
+ index.erase(subnet_it);
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DEL_SUBNET6)
+ .arg(subnet->toText());
+}
+
+void
+CfgSubnets6::merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks6Ptr networks,
+ CfgSubnets6& other) {
+ auto& index_id = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto& index_prefix = subnets_.get<SubnetPrefixIndexTag>();
+
+ // Iterate over the subnets to be merged. They will replace the existing
+ // subnets with the same id. All new subnets will be inserted into the
+ // configuration into which we're merging.
+ auto const& other_subnets = other.getAll();
+ for (auto const& other_subnet : *other_subnets) {
+
+ // Check if there is a subnet with the same ID.
+ auto subnet_it = index_id.find(other_subnet->getID());
+ if (subnet_it != index_id.end()) {
+
+ // Subnet found.
+ auto existing_subnet = *subnet_it;
+
+ // If the existing subnet and other subnet
+ // are the same instance skip it.
+ if (existing_subnet == other_subnet) {
+ continue;
+ }
+
+ // We're going to replace the existing subnet with the other
+ // version. If it belongs to a shared network, we need
+ // remove it from that network.
+ SharedNetwork6Ptr network;
+ existing_subnet->getSharedNetwork(network);
+ if (network) {
+ network->del(existing_subnet->getID());
+ }
+
+ // Now we remove the existing subnet.
+ index_id.erase(subnet_it);
+ }
+
+ // Check if there is a subnet with the same prefix.
+ auto subnet_prefix_it = index_prefix.find(other_subnet->toText());
+ if (subnet_prefix_it != index_prefix.end()) {
+
+ // Subnet found.
+ auto existing_subnet = *subnet_prefix_it;
+
+ // Updating the id can lead to problems... e.g. reservation
+ // for the previous subnet ID.
+ // @todo: check reservations
+
+ // We're going to replace the existing subnet with the other
+ // version. If it belongs to a shared network, we need
+ // remove it from that network.
+ SharedNetwork6Ptr network;
+ existing_subnet->getSharedNetwork(network);
+ if (network) {
+ network->del(existing_subnet->getID());
+ }
+
+ // Now we remove the existing subnet.
+ index_prefix.erase(subnet_prefix_it);
+ }
+
+ // Create the subnet's options based on the given definitions.
+ other_subnet->getCfgOption()->createOptions(cfg_def);
+
+ // Create the options for pool based on the given definitions.
+ for (const auto& pool : other_subnet->getPoolsWritable(Lease::TYPE_NA)) {
+ pool->getCfgOption()->createOptions(cfg_def);
+ }
+
+ // Create the options for pd pool based on the given definitions.
+ for (const auto& pool : other_subnet->getPoolsWritable(Lease::TYPE_PD)) {
+ pool->getCfgOption()->createOptions(cfg_def);
+ }
+
+ // Add the "other" subnet to the our collection of subnets.
+ static_cast<void>(subnets_.insert(other_subnet));
+
+ // If it belongs to a shared network, find the network and
+ // add the subnet to it
+ std::string network_name = other_subnet->getSharedNetworkName();
+ if (!network_name.empty()) {
+ SharedNetwork6Ptr network = networks->getByName(network_name);
+ if (network) {
+ network->add(other_subnet);
+ } else {
+ // This implies the shared-network collection we were given
+ // is out of sync with the subnets we were given.
+ isc_throw(InvalidOperation, "Cannot assign subnet ID of "
+ << other_subnet->getID()
+ << " to shared network: " << network_name
+ << ", network does not exist");
+ }
+ }
+ // Instantiate the configured allocators and their states.
+ other_subnet->createAllocators();
+ }
+}
+
+ConstSubnet6Ptr
+CfgSubnets6::getBySubnetId(const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet6Ptr());
+}
+
+ConstSubnet6Ptr
+CfgSubnets6::getByPrefix(const std::string& subnet_text) const {
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_text);
+ return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet6Ptr());
+}
+
+SubnetSelector
+CfgSubnets6::initSelector(const Pkt6Ptr& query) {
+ // Initialize subnet selector with the values used to select the subnet.
+ SubnetSelector selector;
+ selector.iface_name_ = query->getIface();
+ selector.remote_address_ = query->getRemoteAddr();
+ selector.first_relay_linkaddr_ = IOAddress("::");
+ selector.client_classes_ = query->classes_;
+
+ // Initialize fields specific to relayed messages.
+ if (!query->relay_info_.empty()) {
+ BOOST_REVERSE_FOREACH(Pkt6::RelayInfo relay, query->relay_info_) {
+ if (!relay.linkaddr_.isV6Zero() &&
+ !relay.linkaddr_.isV6LinkLocal()) {
+ selector.first_relay_linkaddr_ = relay.linkaddr_;
+ break;
+ }
+ }
+ selector.interface_id_ =
+ query->getAnyRelayOption(D6O_INTERFACE_ID,
+ Pkt6::RELAY_GET_FIRST);
+ }
+
+ return (selector);
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const SubnetSelector& selector) const {
+ Subnet6Ptr subnet;
+
+ // If relay agent link address is set to zero it means that we're dealing
+ // with a directly connected client.
+ if (selector.first_relay_linkaddr_ == IOAddress("::")) {
+ // If interface name is known try to match it with interface names
+ // specified for configured subnets.
+ if (!selector.iface_name_.empty()) {
+ subnet = selectSubnet(selector.iface_name_,
+ selector.client_classes_);
+ }
+
+ // If interface name didn't match, try the client's address.
+ if (!subnet && selector.remote_address_ != IOAddress("::")) {
+ subnet = selectSubnet(selector.remote_address_,
+ selector.client_classes_);
+ }
+
+ // If relay agent link address is set, we're dealing with a relayed message.
+ } else {
+ // Find the subnet using the Interface Id option, if present.
+ subnet = selectSubnet(selector.interface_id_, selector.client_classes_);
+
+ // If Interface ID option could not be matched for any subnet, try
+ // the relay agent link address.
+ if (!subnet) {
+ subnet = selectSubnet(selector.first_relay_linkaddr_,
+ selector.client_classes_,
+ true);
+ }
+ }
+
+ // Return subnet found, or NULL if not found.
+ return (subnet);
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const asiolink::IOAddress& address,
+ const ClientClasses& client_classes,
+ const bool is_relay_address) const {
+ // If the specified address is a relay address we first need to match
+ // it with the relay addresses specified for all subnets.
+ if (is_relay_address) {
+ for (auto const& subnet : subnets_) {
+
+ // If the specified address matches a relay address, return this
+ // subnet.
+ if (subnet->hasRelays()) {
+ if (!subnet->hasRelayAddress(address)) {
+ continue;
+ }
+
+ } else {
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
+ if (!network || !network->hasRelayAddress(address)) {
+ continue;
+ }
+ }
+
+ if (subnet->clientSupported(client_classes)) {
+ // The relay address is matching the one specified for a subnet
+ // or its shared network.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_RELAY)
+ .arg(subnet->toText()).arg(address.toText());
+ return (subnet);
+ }
+ }
+ }
+
+ // No success so far. Check if the specified address is in range
+ // with any subnet.
+ for (auto const& subnet : subnets_) {
+ if (subnet->inRange(address) && subnet->clientSupported(client_classes)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET6)
+ .arg(subnet->toText()).arg(address.toText());
+ return (subnet);
+ }
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET6_SELECT_BY_ADDRESS_NO_MATCH)
+ .arg(address.toText());
+
+ // Nothing found.
+ return (Subnet6Ptr());
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const std::string& iface_name,
+ const ClientClasses& client_classes) const {
+ // If empty interface specified, we can't select subnet by interface.
+ if (!iface_name.empty()) {
+ for (auto const& subnet : subnets_) {
+
+ // If interface name matches with the one specified for the subnet
+ // and the client is not rejected based on the classification,
+ // return the subnet.
+ if ((subnet->getIface() == iface_name) &&
+ subnet->clientSupported(client_classes)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_IFACE)
+ .arg(subnet->toText()).arg(iface_name);
+ return (subnet);
+ }
+ }
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_NO_MATCH)
+ .arg(iface_name);
+
+ // No subnet found for this interface name.
+ return (Subnet6Ptr());
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const OptionPtr& interface_id,
+ const ClientClasses& client_classes) const {
+ // We can only select subnet using an interface id, if the interface
+ // id is known.
+ if (interface_id) {
+ for (auto const& subnet : subnets_) {
+
+ // If interface id matches for the subnet and the subnet is not
+ // rejected based on the classification.
+ if (subnet->getInterfaceId() &&
+ subnet->getInterfaceId()->equals(interface_id) &&
+ subnet->clientSupported(client_classes)) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
+ .arg(subnet->toText());
+ return (subnet);
+ }
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_ID_NO_MATCH)
+ .arg(interface_id->toText());
+ }
+
+ // No subnet found.
+ return (Subnet6Ptr());
+}
+
+Subnet6Ptr
+CfgSubnets6::getSubnet(const SubnetID id) const {
+ /// @todo: Once this code is migrated to multi-index container, use
+ /// an index rather than full scan.
+ for (auto const& subnet : subnets_) {
+ if (subnet->getID() == id) {
+ return (subnet);
+ }
+ }
+ return (Subnet6Ptr());
+}
+
+SubnetIDSet
+CfgSubnets6::getLinks(const IOAddress& link_addr, uint8_t& link_len) const {
+ SubnetIDSet links;
+ bool link_len_set = false;
+ for (auto const& subnet : subnets_) {
+ if (!subnet->inRange(link_addr)) {
+ continue;
+ }
+ uint8_t plen = subnet->get().second;
+ if (!link_len_set || (plen < link_len)) {
+ link_len_set = true;
+ link_len = plen;
+ }
+ links.insert(subnet->getID());
+ }
+ return (links);
+}
+
+void
+CfgSubnets6::removeStatistics() {
+ using namespace isc::stats;
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ // For each v6 subnet currently configured, remove the statistics.
+ for (auto const& subnet6 : subnets_) {
+ SubnetID subnet_id = subnet6->getID();
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "total-nas"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "assigned-nas"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-nas"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "total-pds"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "assigned-pds"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-pds"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+
+ for (const auto& pool : subnet6->getPools(Lease::TYPE_NA)) {
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "total-nas")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-nas")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "cumulative-assigned-nas")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "declined-addresses")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-declined-addresses")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-leases")));
+ }
+
+ for (const auto& pool : subnet6->getPools(Lease::TYPE_PD)) {
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "total-pds")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "assigned-pds")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "cumulative-assigned-pds")));
+
+ stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "reclaimed-leases")));
+ }
+ }
+}
+
+void
+CfgSubnets6::updateStatistics() {
+ using namespace isc::stats;
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ // For each v6 subnet currently configured, calculate totals
+ for (auto const& subnet6 : subnets_) {
+ SubnetID subnet_id = subnet6->getID();
+
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "total-nas"),
+ subnet6->getPoolCapacity(Lease::TYPE_NA));
+
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "total-pds"),
+ subnet6->getPoolCapacity(Lease::TYPE_PD));
+
+ const std::string& name_nas(StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-nas"));
+ if (!stats_mgr.getObservation(name_nas)) {
+ stats_mgr.setValue(name_nas, static_cast<int64_t>(0));
+ }
+
+ const std::string& name_pds(StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-pds"));
+ if (!stats_mgr.getObservation(name_pds)) {
+ stats_mgr.setValue(name_pds, static_cast<int64_t>(0));
+ }
+
+ string const& name_ia_na_reuses(StatsMgr::generateName("subnet", subnet_id,
+ "v6-ia-na-lease-reuses"));
+ if (!stats_mgr.getObservation(name_ia_na_reuses)) {
+ stats_mgr.setValue(name_ia_na_reuses, int64_t(0));
+ }
+
+ string const& name_ia_pd_reuses(StatsMgr::generateName("subnet", subnet_id,
+ "v6-ia-pd-lease-reuses"));
+ if (!stats_mgr.getObservation(name_ia_pd_reuses)) {
+ stats_mgr.setValue(name_ia_pd_reuses, int64_t(0));
+ }
+
+ for (const auto& pool : subnet6->getPools(Lease::TYPE_NA)) {
+ const std::string& name_total_nas(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "total-nas")));
+ if (!stats_mgr.getObservation(name_total_nas)) {
+ stats_mgr.setValue(name_total_nas, pool->getCapacity());
+ } else {
+ stats_mgr.addValue(name_total_nas, pool->getCapacity());
+ }
+
+ const std::string& name_ca_nas(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "cumulative-assigned-nas")));
+ if (!stats_mgr.getObservation(name_ca_nas)) {
+ stats_mgr.setValue(name_ca_nas, static_cast<int64_t>(0));
+ }
+ }
+
+ for (const auto& pool : subnet6->getPools(Lease::TYPE_PD)) {
+ const std::string& name_total_pds(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "total-pds")));
+ if (!stats_mgr.getObservation(name_total_pds)) {
+ stats_mgr.setValue(name_total_pds, pool->getCapacity());
+ } else {
+ stats_mgr.addValue(name_total_pds, pool->getCapacity());
+ }
+
+ const std::string& name_ca_pds(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "cumulative-assigned-pds")));
+ if (!stats_mgr.getObservation(name_ca_pds)) {
+ stats_mgr.setValue(name_ca_pds, static_cast<int64_t>(0));
+ }
+ }
+ }
+
+ // Only recount the stats if we have subnets.
+ if (subnets_.begin() != subnets_.end()) {
+ LeaseMgrFactory::instance().recountLeaseStats6();
+ }
+}
+
+void
+CfgSubnets6::initAllocatorsAfterConfigure() {
+ for (auto subnet : subnets_) {
+ subnet->initAllocatorsAfterConfigure();
+ }
+}
+
+void
+CfgSubnets6::clear() {
+ subnets_.clear();
+}
+
+ElementPtr
+CfgSubnets6::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate subnets
+ for (auto const& subnet : subnets_) {
+ result->add(subnet->toElement());
+ }
+ return (result);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/cfg_subnets6.h b/src/lib/dhcpsrv/cfg_subnets6.h
new file mode 100644
index 0000000..b21c808
--- /dev/null
+++ b/src/lib/dhcpsrv/cfg_subnets6.h
@@ -0,0 +1,359 @@
+// Copyright (C) 2014-2023 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 CFG_SUBNETS6_H
+#define CFG_SUBNETS6_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt6.h>
+#include <cc/cfg_to_element.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <util/optional.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds subnets configured for the DHCPv6 server.
+///
+/// This class holds a collection of subnets configured for the DHCPv6 server.
+/// It allows for retrieving a subnet for the particular client using various
+/// parameters extracted from the DHCPv6 message. These parameters must be
+/// assigned to the appropriate members of the @c SubnetSelector structure.
+///
+/// See @c CfgSubnets6::selectSubnet documentation for more details on how the subnet
+/// is selected for the client.
+class CfgSubnets6 : public isc::data::CfgToElement {
+public:
+
+ /// @brief Adds new subnet to the configuration.
+ ///
+ /// @param subnet Pointer to the subnet being added.
+ ///
+ /// @throw isc::DuplicateSubnetID If the subnet id for the new subnet
+ /// duplicates id of an existing subnet.
+ void add(const Subnet6Ptr& subnet);
+
+ /// @brief Replaces subnet in the configuration.
+ ///
+ /// This method replaces a subnet by another subnet with the same ID.
+ /// The prefix should be the same too.
+ ///
+ /// @param subnet Pointer to the subnet being updated.
+ /// @throw BadValue if the subnet to update does not exit.
+ /// @return Pointer to the replaced subnet or NULL if it failed.
+ Subnet6Ptr replace(const Subnet6Ptr& subnet);
+
+ /// @brief Removes subnet from the configuration.
+ ///
+ /// @param subnet Pointer to the subnet to be removed.
+ ///
+ /// @throw isc::BadValue if such subnet doesn't exist.
+ void del(const ConstSubnet6Ptr& subnet);
+
+ /// @brief Removes subnet from the configuration.
+ ///
+ /// @param subnet_id Identifier of the subnet to be removed.
+ ///
+ /// @throw isc::BadValue if such subnet doesn't exist.
+ void del(const SubnetID& subnet_id);
+
+ /// @brief Merges specified subnet configuration into this configuration.
+ ///
+ /// This method merges subnets from the @c other configuration into this
+ /// configuration. The general rule is that existing subnets are replaced
+ /// by the subnets from @c other. If there is no corresponding subnet in
+ /// this configuration the subnet from @c other configuration is inserted.
+ ///
+ /// The complexity of the merge process stems from the associations between
+ /// the subnets and shared networks. It is assumed that subnets in @c other
+ /// are the authority on their shared network assignments. It is also
+ /// assumed that @ networks is the list of shared networks that should be
+ /// used in making assignments. The general concept is that the overarching
+ /// merge process will first merge shared networks and then pass that list
+ /// of networks into this method. Subnets from @c other are then merged
+ /// into this configuration as follows:
+ ///
+ /// For each subnet in @c other:
+ ///
+ /// - If a subnet of the same ID already exists in this configuration:
+ /// -# If it belongs to a shared network, remove it from that network
+ /// -# Remove the subnet from this configuration and discard it
+ ///
+ /// - Create the subnet's option instance, as well as any options
+ /// that belong to any of the subnet's pools.
+ /// - Add the subnet from @c other to this configuration.
+ /// - If that subnet is associated to shared network, find that network
+ /// in @ networks and add that subnet to it.
+ ///
+ /// @warning The merge operation affects the @c other configuration.
+ /// Therefore, the caller must not rely on the data held in the @c other
+ /// object after the call to @c merge. Also, the data held in @c other must
+ /// not be modified after the call to @c merge because it may affect the
+ /// merged configuration.
+ ///
+ /// @param cfg_def set of user-defined option definitions to use
+ /// when creating option instances.
+ /// @param networks collection of shared networks that to which assignments
+ /// should be added. In other words, the list of shared networks that belong
+ /// to the same SrvConfig instance we are merging into.
+ /// @param other the subnet configuration to be merged into this
+ /// configuration.
+ void merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks6Ptr networks,
+ CfgSubnets6& other);
+
+ /// @brief Returns pointer to the collection of all IPv6 subnets.
+ ///
+ /// This is used in a hook (subnet6_select), where the hook is able
+ /// to choose a different subnet. Server code has to offer a list
+ /// of possible choices (i.e. all subnets).
+ ///
+ /// @return A pointer to const Subnet6 collection
+ const Subnet6Collection* getAll() const {
+ return (&subnets_);
+ }
+
+ /// @brief Returns const pointer to a subnet identified by the specified
+ /// subnet identifier.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configuration is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Pointer to the @c Subnet6 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet6Ptr getBySubnetId(const SubnetID& subnet_id) const;
+
+ /// @brief Returns const pointer to a subnet which matches the specified
+ /// prefix in the canonical form.
+ ///
+ /// The const pointer is returned by this method to prevent a caller from
+ /// modifying the subnet configuration. Modifications to subnet configuration
+ /// is dangerous and must be done carefully. The subnets' configuration is
+ /// held in the multi index container and any modifications to the subnet
+ /// id or subnet prefix must trigger re-indexing of multi index container.
+ /// There is no possibility to enforce this when the non-const pointer is
+ /// returned.
+ ///
+ /// @param subnet_prefix Subnet prefix, e.g. 2001:db8:1::/64
+ ///
+ /// @return Pointer to the @c Subnet6 object or null pointer if such
+ /// subnet doesn't exist.
+ ConstSubnet6Ptr getByPrefix(const std::string& subnet_prefix) const;
+
+ /// @brief Build selector from a client's message.
+ ///
+ /// @note: code moved from server.
+ ///
+ /// @param query client's message.
+ /// @return filled selector.
+ static SubnetSelector initSelector(const Pkt6Ptr& query);
+
+ /// @brief Selects a subnet using parameters specified in the selector.
+ ///
+ /// This method tries to retrieve the subnet for the client using various
+ /// parameters extracted from the client's message using the following
+ /// logic.
+ ///
+ /// If the relay agent link address is set to zero it is assumed that
+ /// the subnet is selected for the directly connected client.
+ /// In this case it is checked if there is any subnet associated with the
+ /// interface over which the message has been received. If there is no
+ /// subnet explicitly associated with this interface the client's address
+ /// will be used to check if the address is in range with any of the
+ /// subnets.
+ ///
+ /// If the message was relayed it is possible that the relay agent has
+ /// appended an Interface ID option. If this option is present, the method
+ /// will check if it matches with any explicitly specified interface id
+ /// for any subnet. If it does, the subnet is returned. Otherwise, the
+ /// relay agents link address is used to select the subnet. In this case,
+ /// the method will first check if this link address is explicitly
+ /// associated with any subnet. If not, it is checked if the link address
+ /// is in range with any of the subnets.
+ ///
+ /// @todo This method requires performance improvement! It currently
+ /// iterates over all existing subnets (possibly a couple of times)
+ /// to find the one which fulfills the search criteria. The subnet storage
+ /// is implemented as a simple STL vector which precludes fast searches
+ /// using specific keys. Hence, full scan is required. To improve the
+ /// search performance a different container type is required, e.g.
+ /// multi-index container, or something of a similar functionality.
+ ///
+ /// @param selector Const reference to the selector structure which holds
+ /// various information extracted from the client's packet which are used
+ /// to find appropriate subnet.
+ ///
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ Subnet6Ptr selectSubnet(const SubnetSelector& selector) const;
+
+ /// @brief Returns subnet with specified subnet-id value
+ ///
+ /// Warning: this method uses full scan. Its use is not recommended for
+ /// packet processing.
+ /// Please use @ref getBySubnetId instead when possible.
+ ///
+ /// @return Subnet (or NULL)
+ Subnet6Ptr getSubnet(const SubnetID id) const;
+
+ /// @brief Selects the subnet using a specified address.
+ ///
+ /// This method searches for the subnet using the specified address. If
+ /// the specified address is a link address on the relay agent (which is
+ /// indicated by the 3rd argument) the method will first try to match the
+ /// specified address with the relay addresses explicitly specified for
+ /// existing subnets. If no match is found, the method will check if the
+ /// address is in range with any of the subnets.
+ ///
+ /// If the address is not a relay agent link address (@c is_relay_address
+ /// is set to false), the method will simply check if the address is in
+ /// range with any of the subnets.
+ ///
+ /// @note This method is mainly to be used in unit tests, which often
+ /// require sanity-checking if the subnet exists for the particular
+ /// address. For other purposes the @c selectSubnet(SubnetSelector) should
+ /// rather be used instead.
+ ///
+ /// @todo This method requires performance improvement! It currently
+ /// iterates over all existing subnets (possibly a couple of times)
+ /// to find the one which fulfills the search criteria. The subnet storage
+ /// is implemented as a simple STL vector which precludes fast searches
+ /// using specific keys. Hence, full scan is required. To improve the
+ /// search performance a different container type is required, e.g.
+ /// multi-index container, or something of a similar functionality.
+ ///
+ /// @param address Address for which the subnet is searched.
+ /// @param client_classes Optional parameter specifying the classes that
+ /// the client belongs to.
+ /// @param is_relay_address Specifies if the provided address is an
+ /// address of the relay agent (true) or not (false).
+ ///
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ Subnet6Ptr
+ selectSubnet(const asiolink::IOAddress& address,
+ const ClientClasses& client_classes = ClientClasses(),
+ const bool is_relay_address = false) const;
+
+ /// @brief Convert a link address into a link set.
+ ///
+ /// Given a link address this returns the ordered list aka set of id
+ /// of subnets the address belongs to. It also sets the minimum link
+ /// length when there is at least one subnet.
+ ///
+ /// @param link_addr The link address.
+ /// @param[out] link_len The minimum link length.
+ /// @return The set of subnet ids the link address belongs to.
+ SubnetIDSet getLinks(const asiolink::IOAddress& link_addr,
+ uint8_t& link_len) const;
+
+ /// @brief Updates statistics.
+ ///
+ /// This method updates statistics that are affected by the newly committed
+ /// configuration. In particular, it updates the number of available addresses
+ /// and prefixes in each subnet. Other statistics may be added in the future. In
+ /// general, these are statistics that are dependent only on configuration, so
+ /// they are not expected to change until the next reconfiguration event.
+ void updateStatistics();
+
+ /// @brief Removes statistics.
+ ///
+ /// During commitment of a new configuration, we need to get rid of the old
+ /// statistics for the old configuration. In particular, we need to remove
+ /// anything related to subnets, as there may be fewer subnets in the new
+ /// configuration and also subnet-ids may change.
+ void removeStatistics();
+
+ /// @brief Calls @c initAllocatorsAfterConfigure for each subnet.
+ void initAllocatorsAfterConfigure();
+
+ /// @brief Clears all subnets from the configuration.
+ void clear();
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief Selects a subnet using the interface name.
+ ///
+ /// This method searches for the subnet using the name of the interface.
+ /// If any of the subnets is explicitly associated with the interface
+ /// name, the subnet is returned.
+ ///
+ /// @todo This method requires performance improvement! It currently
+ /// iterates over all existing subnets to find the one which fulfills
+ /// the search criteria. The subnet storage is implemented as a
+ /// simple STL vector which precludes fast searches using specific
+ /// keys. Hence, full scan is required. To improve the search
+ /// performance a different container type is required, e.g.
+ /// multi-index container, or something of a similar functionality.
+ ///
+ /// @param iface_name Interface name.
+ /// @param client_classes Optional parameter specifying the classes that
+ /// the client belongs to.
+ ///
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ Subnet6Ptr
+ selectSubnet(const std::string& iface_name,
+ const ClientClasses& client_classes) const;
+
+ /// @brief Selects a subnet using Interface ID option.
+ ///
+ /// This method searches for the subnet using the Interface ID option
+ /// inserted by the relay agent to the message from a client. If any
+ /// of the subnets is explicitly associated with that interface id, the
+ /// subnet is returned.
+ ///
+ /// @todo This method requires performance improvement! It currently
+ /// iterates over all existing subnets to find the one which fulfills
+ /// the search criteria. The subnet storage is implemented as a
+ /// simple STL vector which precludes fast searches using specific
+ /// keys. Hence, full scan is required. To improve the search
+ /// performance a different container type is required, e.g.
+ /// multi-index container, or something of a similar functionality.
+ ///
+ /// @param interface_id An instance of the Interface ID option received
+ /// from the client.
+ /// @param client_classes Optional parameter specifying the classes that
+ /// the client belongs to.
+ ///
+ /// @return Pointer to the selected subnet or NULL if no subnet found.
+ Subnet6Ptr
+ selectSubnet(const OptionPtr& interface_id,
+ const ClientClasses& client_classes) const;
+
+ /// @brief A container for IPv6 subnets.
+ Subnet6Collection subnets_;
+
+};
+
+/// @name Pointer to the @c CfgSubnets6 objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgSubnets6> CfgSubnets6Ptr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgSubnets6> ConstCfgSubnets6Ptr;
+
+//@}
+
+}
+}
+
+#endif // CFG_SUBNETS6_H
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
new file mode 100644
index 0000000..c532404
--- /dev/null
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -0,0 +1,233 @@
+// Copyright (C) 2012-2021 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 <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+const size_t CfgMgr::CONFIG_LIST_SIZE = 10;
+
+CfgMgr&
+CfgMgr::instance() {
+ static CfgMgr cfg_mgr;
+ return (cfg_mgr);
+}
+
+Optional<std::string>
+CfgMgr::getDataDir() const {
+ return (datadir_);
+}
+
+void
+CfgMgr::setDataDir(const std::string& datadir, bool unspecified) {
+ datadir_ = Optional<std::string>(datadir, unspecified);
+}
+
+void
+CfgMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+ ensureCurrentAllocated();
+ // Note that D2ClientMgr::setD2Config() actually attempts to apply the
+ // configuration by stopping its sender and opening a new one and so
+ // forth per the new configuration.
+ d2_client_mgr_.setD2ClientConfig(new_config);
+
+ // Manager will throw if the set fails, if it succeeds
+ // we'll update our SrvConfig, configuration_, with the D2ClientConfig
+ // used. This is largely bookkeeping in case we ever want to compare
+ // configuration_ to another SrvConfig.
+ configuration_->setD2ClientConfig(new_config);
+}
+
+bool
+CfgMgr::ddnsEnabled() {
+ return (d2_client_mgr_.ddnsEnabled());
+}
+
+const D2ClientConfigPtr&
+CfgMgr::getD2ClientConfig() const {
+ return (d2_client_mgr_.getD2ClientConfig());
+}
+
+D2ClientMgr&
+CfgMgr::getD2ClientMgr() {
+ return (d2_client_mgr_);
+}
+
+void
+CfgMgr::ensureCurrentAllocated() {
+ if (!configuration_ || configs_.empty()) {
+ configuration_.reset(new SrvConfig());
+ configs_.push_back(configuration_);
+ }
+}
+
+void
+CfgMgr::clear() {
+ if (configuration_) {
+ configuration_->removeStatistics();
+ }
+ configs_.clear();
+ external_configs_.clear();
+ D2ClientConfigPtr d2_default_conf(new D2ClientConfig());
+ setD2ClientConfig(d2_default_conf);
+}
+
+void
+CfgMgr::commit() {
+ ensureCurrentAllocated();
+
+ // First we need to remove statistics. The new configuration can have fewer
+ // subnets. Also, it may change subnet-ids. So we need to remove them all
+ // and add it back.
+ configuration_->removeStatistics();
+
+ if (!configs_.back()->sequenceEquals(*configuration_)) {
+ configuration_ = configs_.back();
+ // Keep track of the maximum size of the configs history. Before adding
+ // new element, we have to remove the oldest one.
+ if (configs_.size() > CONFIG_LIST_SIZE) {
+ SrvConfigList::iterator it = configs_.begin();
+ std::advance(it, configs_.size() - CONFIG_LIST_SIZE);
+ configs_.erase(configs_.begin(), it);
+ }
+ }
+
+ // Set the last commit timestamp.
+ auto now = boost::posix_time::second_clock::universal_time();
+ configuration_->setLastCommitTime(now);
+
+ // Now we need to set the statistics back.
+ configuration_->updateStatistics();
+
+ configuration_->configureLowerLevelLibraries();
+}
+
+void
+CfgMgr::rollback() {
+ ensureCurrentAllocated();
+ if (!configuration_->sequenceEquals(*configs_.back())) {
+ configs_.pop_back();
+ }
+}
+
+void
+CfgMgr::revert(const size_t index) {
+ ensureCurrentAllocated();
+ if (index == 0) {
+ isc_throw(isc::OutOfRange, "invalid commit index 0 when reverting"
+ " to an old configuration");
+ } else if (index > configs_.size() - 1) {
+ isc_throw(isc::OutOfRange, "unable to revert to commit index '"
+ << index << "', only '" << configs_.size() - 1
+ << "' previous commits available");
+ }
+
+ // Let's rollback an existing configuration to make sure that the last
+ // configuration on the list is the current one. Note that all remaining
+ // operations in this function should be exception free so there shouldn't
+ // be a problem that the revert operation fails and the staging
+ // configuration is destroyed by this rollback.
+ rollback();
+
+ // Get the iterator to the current configuration and then advance to the
+ // desired one.
+ SrvConfigList::const_reverse_iterator it = configs_.rbegin();
+ std::advance(it, index);
+
+ // Copy the desired configuration to the new staging configuration. The
+ // staging configuration is re-created here because we rolled back earlier
+ // in this function.
+ (*it)->copy(*getStagingCfg());
+
+ // Make the staging configuration a current one.
+ commit();
+}
+
+SrvConfigPtr
+CfgMgr::getCurrentCfg() {
+ ensureCurrentAllocated();
+ return (configuration_);
+}
+
+SrvConfigPtr
+CfgMgr::getStagingCfg() {
+ ensureCurrentAllocated();
+ if (configuration_->sequenceEquals(*configs_.back())) {
+ uint32_t sequence = configuration_->getSequence();
+ configs_.push_back(SrvConfigPtr(new SrvConfig(++sequence)));
+ }
+ return (configs_.back());
+}
+
+SrvConfigPtr
+CfgMgr::createExternalCfg() {
+ uint32_t seq = 0;
+
+ if (!external_configs_.empty()) {
+ seq = external_configs_.rbegin()->second->getSequence() + 1;
+ }
+
+ SrvConfigPtr srv_config(new SrvConfig(seq));
+ external_configs_[seq] = srv_config;
+ return (srv_config);
+}
+
+void
+CfgMgr::mergeIntoStagingCfg(const uint32_t seq) {
+ mergeIntoCfg(getStagingCfg(), seq);
+}
+
+void
+CfgMgr::mergeIntoCurrentCfg(const uint32_t seq) {
+ try {
+ // First we need to remove statistics.
+ getCurrentCfg()->removeStatistics();
+ mergeIntoCfg(getCurrentCfg(), seq);
+
+ } catch (...) {
+ // Make sure the statistics is updated even if the merge failed.
+ getCurrentCfg()->updateStatistics();
+ throw;
+ }
+ getCurrentCfg()->updateStatistics();
+}
+
+void
+CfgMgr::mergeIntoCfg(const SrvConfigPtr& target_config, const uint32_t seq) {
+ auto source_config = external_configs_.find(seq);
+ if (source_config != external_configs_.end()) {
+ target_config->merge(*source_config->second);
+ external_configs_.erase(source_config);
+
+ } else {
+ isc_throw(BadValue, "the external configuration with the sequence number "
+ "of " << seq << " was not found");
+ }
+}
+
+CfgMgr::CfgMgr()
+ : datadir_(DHCP_DATA_DIR, true), d2_client_mgr_(), family_(AF_INET) {
+ // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
+ // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
+ // See AM_CPPFLAGS definition in Makefile.am
+}
+
+CfgMgr::~CfgMgr() {
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
new file mode 100644
index 0000000..d34b1c5
--- /dev/null
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -0,0 +1,357 @@
+// Copyright (C) 2012-2022 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 CFGMGR_H
+#define CFGMGR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcp/option_space.h>
+#include <dhcp/classify.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/srv_config.h>
+#include <util/buffer.h>
+#include <util/optional.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+#include <list>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when the same interface has been specified twice.
+///
+/// In particular, this exception is thrown when adding interface to the set
+/// of interfaces on which server is supposed to listen.
+class DuplicateListeningIface : public Exception {
+public:
+ DuplicateListeningIface(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Configuration Manager
+///
+/// This singleton class holds the whole configuration for DHCPv4 and DHCPv6
+/// servers.
+///
+/// Below is a sketch of configuration inheritance.
+/// Let's investigate the following configuration:
+///
+/// @code
+/// preferred-lifetime 500;
+/// valid-lifetime 1000;
+/// subnet6 2001:db8:1::/48 {
+/// pool6 2001::db8:1::1 - 2001::db8:1::ff;
+/// };
+/// subnet6 2001:db8:2::/48 {
+/// valid-lifetime 2000;
+/// pool6 2001::db8:2::1 - 2001::db8:2::ff;
+/// };
+/// @endcode
+///
+/// Parameters defined in a global scope are applicable to everything until
+/// they are overwritten in a smaller scope, in this case subnet6.
+/// In the example above, the first subnet6 has preferred lifetime of 500s
+/// and a valid lifetime of 1000s. The second subnet has preferred lifetime
+/// of 500s, but valid lifetime of 2000s.
+///
+/// Parameter inheritance is implemented in dedicated classes. See
+/// @ref isc::dhcp::SimpleParser4::deriveParameters and
+/// @ref isc::dhcp::SimpleParser6::deriveParameters.
+class CfgMgr : public boost::noncopyable {
+public:
+
+ /// @brief A number of configurations held by @c CfgMgr.
+ ///
+ /// @todo Make it configurable.
+ static const size_t CONFIG_LIST_SIZE;
+
+ /// @brief returns a single instance of Configuration Manager
+ ///
+ /// CfgMgr is a singleton and this method is the only way of
+ /// accessing it.
+ static CfgMgr& instance();
+
+ /// @brief returns path do the data directory
+ ///
+ /// This method returns a path to writable directory that DHCP servers
+ /// can store data in.
+ /// @return data directory
+ util::Optional<std::string> getDataDir() const;
+
+ /// @brief Sets new data directory.
+ ///
+ /// @param datadir New data directory.
+ /// @param unspecified Initial state. Default is "unspecified".
+ void setDataDir(const std::string& datadir, bool unspecified = true);
+
+ /// @brief Updates the DHCP-DDNS client configuration to the given value.
+ ///
+ /// Passes the new configuration to the D2ClientMgr instance,
+ /// d2_client_mgr_, which will attempt to apply the new configuration
+ /// by shutting down its sender and opening a new connection per the new
+ /// configuration (see @c D2ClientMgr::setD2ClientConfig()).
+ ///
+ /// @param new_config pointer to the new client configuration.
+ ///
+ /// @throw Underlying method(s) will throw D2ClientError if given an empty
+ /// pointer.
+ void setD2ClientConfig(D2ClientConfigPtr& new_config);
+
+ /// @brief Convenience method for checking if DHCP-DDNS updates are enabled.
+ ///
+ /// @return True if the D2 configuration is enabled.
+ bool ddnsEnabled();
+
+ /// @brief Fetches the DHCP-DDNS configuration pointer.
+ ///
+ /// @return a reference to the current configuration pointer.
+ const D2ClientConfigPtr& getD2ClientConfig() const;
+
+ /// @brief Fetches the DHCP-DDNS manager.
+ ///
+ /// @return a reference to the DHCP-DDNS manager.
+ D2ClientMgr& getD2ClientMgr();
+
+ /// @name Methods managing the collection of configurations.
+ ///
+ /// The following methods manage the process of preparing a configuration
+ /// without affecting a currently used configuration and then committing
+ /// the configuration to replace current configuration atomically.
+ /// They also allow for keeping a history of previous configurations so
+ /// as the @c CfgMgr can revert to the historical configuration when
+ /// required.
+ ///
+ /// @todo Migrate all configuration parameters to use the model supported
+ /// by these functions.
+ ///
+ /// @todo Make the size of the configurations history configurable.
+ ///
+ //@{
+
+ /// @brief Removes current, staging and all previous configurations.
+ ///
+ /// This function removes all configurations, including current,
+ /// staging and external configurations. It creates a new current
+ /// configuration with default settings.
+ ///
+ /// This function is exception safe.
+ void clear();
+
+ /// @brief Commits the staging configuration.
+ ///
+ /// The staging configuration becomes current configuration when this
+ /// function is called. It removes the oldest configuration held in the
+ /// history so as the size of the list of configuration does not exceed
+ /// the @c CONFIG_LIST_SIZE.
+ ///
+ /// This function is exception safe.
+ void commit();
+
+ /// @brief Removes staging configuration.
+ ///
+ /// This function should be called when there is a staging configuration
+ /// (likely created in the previous configuration attempt) but the entire
+ /// new configuration should be created. It removes the existing staging
+ /// configuration and the next call to @c CfgMgr::getStagingCfg will return a
+ /// fresh (default) configuration.
+ ///
+ /// This function is exception safe.
+ void rollback();
+
+ /// @brief Reverts to one of the previous configurations.
+ ///
+ /// This function reverts to selected previous configuration. The previous
+ /// configuration is entirely copied to a new @c SrvConfig instance. This
+ /// new instance has a unique sequence id (sequence id is not copied). The
+ /// previous configuration (being copied) is not modified by this operation.
+ ///
+ /// The configuration to be copied is identified by the index value which
+ /// is the distance between the current (most recent) and desired
+ /// configuration. If the index is out of range an exception is thrown.
+ ///
+ /// @warning Revert operation will rollback any changes to the staging
+ /// configuration (if it exists).
+ ///
+ /// @warning This function requires that the entire previous configuration
+ /// is copied to the new configuration object. This is not working for
+ /// some of the complex configuration objects, e.g. subnets. Hence, the
+ /// "revert" operation is not really usable at this point.
+ ///
+ /// @param index A distance from the current configuration to the
+ /// past configuration to be reverted. The minimal value is 1 which points
+ /// to the nearest configuration.
+ ///
+ /// @throw isc::OutOfRange if the specified index is out of range.
+ void revert(const size_t index);
+
+ /// @brief Returns a pointer to the current configuration.
+ ///
+ /// This function returns pointer to the current configuration. If the
+ /// current configuration is not set it will create a default configuration
+ /// and return it.
+ ///
+ /// In the previous Kea releases this method used to return a const pointer
+ /// to the current configuration to ensure that it is not accidentally
+ /// modified while the server is running. This has been changed in Kea 1.3
+ /// release and now this function returns a non-const pointer. The reason
+ /// is that there are certain use cases when current configuration must
+ /// be modified without going through a full cycle of server
+ /// reconfiguration, e.g. add a subnet to the current configuration as
+ /// a result of receiving a command over control API. In such case the
+ /// performance of processing such command is critical and rebuilding the
+ /// whole configuration just for this small configuration change is out
+ /// of question.
+ ///
+ /// Nevertheless, such configuration updates should always be made with
+ /// caution and one has to make sure that the configuration data integrity
+ /// is preserved.
+ ///
+ /// @return Non-null pointer to the current configuration.
+ SrvConfigPtr getCurrentCfg();
+
+ /// @brief Returns a pointer to the staging configuration.
+ ///
+ /// The staging configuration is used by the configuration parsers to
+ /// create new configuration. The staging configuration doesn't affect the
+ /// server's operation until it is committed. The staging configuration
+ /// is a non-const object which can be modified by the caller.
+ ///
+ /// Multiple consecutive calls to this function return the same object
+ /// which can be modified from various places of the code (e.g. various
+ /// configuration parsers).
+ ///
+ /// @return non-null pointer to the staging configuration.
+ SrvConfigPtr getStagingCfg();
+
+ /// @brief Creates an external configuration and returns pointer to it.
+ ///
+ /// External configurations are those that come from other sources than
+ /// from the configuration file, e.g. a database or a command. They
+ /// are created aside and merged into the staging or current configuration.
+ /// External configurations are accessed by their sequence numbers. The
+ /// sequence numbers are autogenerated when the external configuration
+ /// instance is created.
+ ///
+ /// @return non-null pointer to created external configuration.
+ SrvConfigPtr createExternalCfg();
+
+ /// @brief Merges external configuration with the given sequence number
+ /// into the staging configuration.
+ ///
+ /// After the merge, the source configuration is discarded from the
+ /// @c CfgMgr as it should not be used anymore.
+ ///
+ /// @param seq Source configuration sequence number.
+ ///
+ /// @throw BadValue if the external configuration with the given sequence
+ /// number doesn't exist.
+ void mergeIntoStagingCfg(const uint32_t seq);
+
+ /// @brief Merges external configuration with the given sequence number
+ /// into the current configuration.
+ ///
+ /// After the merge, the source configuration is discarded from the
+ /// @c CfgMgr as it should not be used anymore.
+ ///
+ /// @param seq Source configuration sequence number.
+ ///
+ /// @throw BadValue if the external configuration with the given sequence
+ /// number doesn't exist.
+ void mergeIntoCurrentCfg(const uint32_t seq);
+
+ //@}
+
+ /// @brief Sets address family (AF_INET or AF_INET6)
+ void setFamily(uint16_t family) {
+ family_ = family == AF_INET ? AF_INET : AF_INET6;
+ }
+
+ /// @brief Returns address family.
+ uint16_t getFamily() const {
+ return (family_);
+ }
+
+ //@}
+
+protected:
+
+ /// @brief Protected constructor.
+ ///
+ /// This constructor is protected for 2 reasons. First, it forbids any
+ /// instantiations of this class (CfgMgr is a singleton). Second, it
+ /// allows derived class to instantiate it. That is useful for testing
+ /// purposes.
+ CfgMgr();
+
+ /// @brief virtual destructor
+ virtual ~CfgMgr();
+
+private:
+
+ /// @brief Checks if current configuration is created and creates it if needed.
+ ///
+ /// This private method is called to ensure that the current configuration
+ /// is created. If current configuration is not set, it creates the
+ /// default current configuration.
+ void ensureCurrentAllocated();
+
+
+ /// @brief Merges external configuration with the given sequence number
+ /// into the specified configuration.
+ ///
+ /// @param target_config Pointer to the configuration into which the
+ /// external configuration should be merged.
+ /// @param seq Source configuration sequence number.
+ void mergeIntoCfg(const SrvConfigPtr& taget_config, const uint32_t seq);
+
+ /// @brief directory where data files (e.g. server-id) are stored
+ util::Optional<std::string> datadir_;
+
+ /// @brief Manages the DHCP-DDNS client and its configuration.
+ D2ClientMgr d2_client_mgr_;
+
+ /// @brief Server configuration
+ ///
+ /// This is a structure that will hold all configuration.
+ /// @todo: migrate all other parameters to that structure.
+ SrvConfigPtr configuration_;
+
+ /// @name Configuration List.
+ ///
+ //@{
+ /// @brief Server configuration list type.
+ typedef std::list<SrvConfigPtr> SrvConfigList;
+
+ /// @brief Container holding all previous and current configurations.
+ SrvConfigList configs_;
+ //@}
+
+ /// @name Map of external configurations.
+ ///
+ //@{
+ /// @brief Server configuration map type.
+ typedef std::map<uint32_t, SrvConfigPtr> SrvConfigMap;
+
+ /// @brief Map of external configurations with sequence numbers used
+ /// as keys.
+ SrvConfigMap external_configs_;
+ //@}
+
+ /// @brief Address family.
+ uint16_t family_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CFGMGR_H
diff --git a/src/lib/dhcpsrv/client_class_def.cc b/src/lib/dhcpsrv/client_class_def.cc
new file mode 100644
index 0000000..465d94f
--- /dev/null
+++ b/src/lib/dhcpsrv/client_class_def.cc
@@ -0,0 +1,649 @@
+// Copyright (C) 2015-2023 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 <eval/dependency.h>
+#include <eval/evaluate.h>
+#include <eval/eval_log.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <boost/foreach.hpp>
+
+#include <queue>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+//********** ClientClassDef ******************//
+
+ClientClassDef::ClientClassDef(const std::string& name,
+ const ExpressionPtr& match_expr,
+ const CfgOptionPtr& cfg_option)
+ : UserContext(), CfgToElement(), StampedElement(), name_(name),
+ match_expr_(match_expr), required_(false), depend_on_known_(false),
+ cfg_option_(cfg_option), next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ valid_(), preferred_() {
+
+ // Name can't be blank
+ if (name_.empty()) {
+ isc_throw(BadValue, "Client Class name cannot be blank");
+ }
+
+ // We permit an empty expression for now. This will likely be useful
+ // for automatic classes such as vendor class.
+ // For classes without options, make sure we have an empty collection
+ if (!cfg_option_) {
+ cfg_option_.reset(new CfgOption());
+ }
+}
+
+ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
+ : UserContext(rhs), CfgToElement(rhs), StampedElement(rhs), name_(rhs.name_),
+ match_expr_(ExpressionPtr()), test_(rhs.test_), required_(rhs.required_),
+ depend_on_known_(rhs.depend_on_known_), cfg_option_(new CfgOption()),
+ next_server_(rhs.next_server_), sname_(rhs.sname_),
+ filename_(rhs.filename_), valid_(rhs.valid_), preferred_(rhs.preferred_),
+ offer_lft_(rhs.offer_lft_) {
+
+ if (rhs.match_expr_) {
+ match_expr_.reset(new Expression());
+ *match_expr_ = *(rhs.match_expr_);
+ }
+
+ if (rhs.cfg_option_def_) {
+ cfg_option_def_.reset(new CfgOptionDef());
+ rhs.cfg_option_def_->copyTo(*cfg_option_def_);
+ }
+
+ if (rhs.cfg_option_) {
+ rhs.cfg_option_->copyTo(*cfg_option_);
+ }
+}
+
+ClientClassDef::~ClientClassDef() {
+}
+
+std::string
+ClientClassDef::getName() const {
+ return (name_);
+}
+
+void
+ClientClassDef::setName(const std::string& name) {
+ name_ = name;
+}
+
+const ExpressionPtr&
+ClientClassDef::getMatchExpr() const {
+ return (match_expr_);
+}
+
+void
+ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) {
+ match_expr_ = match_expr;
+}
+
+std::string
+ClientClassDef::getTest() const {
+ return (test_);
+}
+
+void
+ClientClassDef::setTest(const std::string& test) {
+ test_ = test;
+}
+
+bool
+ClientClassDef::getRequired() const {
+ return (required_);
+}
+
+void
+ClientClassDef::setRequired(bool required) {
+ required_ = required;
+}
+
+bool
+ClientClassDef::getDependOnKnown() const {
+ return (depend_on_known_);
+}
+
+void
+ClientClassDef::setDependOnKnown(bool depend_on_known) {
+ depend_on_known_ = depend_on_known;
+}
+
+const CfgOptionDefPtr&
+ClientClassDef::getCfgOptionDef() const {
+ return (cfg_option_def_);
+}
+
+void
+ClientClassDef::setCfgOptionDef(const CfgOptionDefPtr& cfg_option_def) {
+ cfg_option_def_ = cfg_option_def;
+}
+
+const CfgOptionPtr&
+ClientClassDef::getCfgOption() const {
+ return (cfg_option_);
+}
+
+void
+ClientClassDef::setCfgOption(const CfgOptionPtr& cfg_option) {
+ cfg_option_ = cfg_option;
+}
+
+void
+ClientClassDef::test(PktPtr pkt, const ExpressionPtr& expr_ptr) {
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *pkt);
+ if (status) {
+ LOG_INFO(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(status);
+ // Matching: add the class
+ pkt->addClass(getName());
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, EVAL_RESULT)
+ .arg(getName())
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg("get exception?");
+ }
+}
+
+const std::string TemplateClientClassDef::SPAWN_CLASS_PREFIX("SPAWN_");
+
+TemplateClientClassDef::TemplateClientClassDef(const std::string& name,
+ const ExpressionPtr& match_expr,
+ const CfgOptionPtr& options) :
+ ClientClassDef(name, match_expr, options) {
+}
+
+void
+TemplateClientClassDef::test(PktPtr pkt, const ExpressionPtr& expr_ptr) {
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ std::string subclass = evaluateString(*expr_ptr, *pkt);
+ if (!subclass.empty()) {
+ LOG_INFO(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(subclass);
+ // Matching: add the subclass
+ std::string value(TemplateClientClassDef::SPAWN_CLASS_PREFIX);
+ value += getName();
+ value += "_";
+ value += subclass;
+ pkt->addSubClass(getName(), value);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(dhcpsrv_logger, EVAL_RESULT)
+ .arg(getName())
+ .arg("get exception?");
+ }
+}
+
+bool
+ClientClassDef::dependOnClass(const std::string& name) const {
+ return (isc::dhcp::dependOnClass(match_expr_, name));
+}
+
+bool
+ClientClassDef::equals(const ClientClassDef& other) const {
+ return ((name_ == other.name_) &&
+ ((!match_expr_ && !other.match_expr_) ||
+ (match_expr_ && other.match_expr_ &&
+ (*match_expr_ == *(other.match_expr_)))) &&
+ ((!cfg_option_ && !other.cfg_option_) ||
+ (cfg_option_ && other.cfg_option_ &&
+ (*cfg_option_ == *other.cfg_option_))) &&
+ ((!cfg_option_def_ && !other.cfg_option_def_) ||
+ (cfg_option_def_ && other.cfg_option_def_ &&
+ (*cfg_option_def_ == *other.cfg_option_def_))) &&
+ (required_ == other.required_) &&
+ (depend_on_known_ == other.depend_on_known_) &&
+ (next_server_ == other.next_server_) &&
+ (sname_ == other.sname_) &&
+ (filename_ == other.filename_));
+}
+
+ElementPtr
+ClientClassDef::toElement() const {
+ uint16_t family = CfgMgr::instance().getFamily();
+ ElementPtr result = Element::createMap();
+ // Set user-context
+ contextToElement(result);
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set original match expression (empty string won't parse)
+ if (!test_.empty()) {
+ result->set("test", Element::create(test_));
+ }
+ // Set only-if-required
+ if (required_) {
+ result->set("only-if-required", Element::create(required_));
+ }
+ // Set option-def (used only by DHCPv4)
+ if (cfg_option_def_ && (family == AF_INET)) {
+ result->set("option-def", cfg_option_def_->toElement());
+ }
+ // Set option-data
+ result->set("option-data", cfg_option_->toElement());
+
+ if (family == AF_INET) {
+ // V4 only
+ // Set next-server
+ result->set("next-server", Element::create(next_server_.toText()));
+ // Set server-hostname
+ result->set("server-hostname", Element::create(sname_));
+ // Set boot-file-name
+ result->set("boot-file-name", Element::create(filename_));
+
+ // Set offer-lifetime
+ if (!offer_lft_.unspecified()) {
+ result->set("offer-lifetime",
+ Element::create(static_cast<long long>(offer_lft_.get())));
+ }
+ } else {
+ // V6 only
+ // Set preferred-lifetime
+ if (!preferred_.unspecified()) {
+ result->set("preferred-lifetime",
+ Element::create(static_cast<long long>(preferred_.get())));
+ }
+
+ if (preferred_.getMin() < preferred_.get()) {
+ result->set("min-preferred-lifetime",
+ Element::create(static_cast<long long>(preferred_.getMin())));
+ }
+
+ if (preferred_.getMax() > preferred_.get()) {
+ result->set("max-preferred-lifetime",
+ Element::create(static_cast<long long>(preferred_.getMax())));
+ }
+ }
+
+ // Set valid-lifetime
+ if (!valid_.unspecified()) {
+ result->set("valid-lifetime",
+ Element::create(static_cast<long long>(valid_.get())));
+
+ if (valid_.getMin() < valid_.get()) {
+ result->set("min-valid-lifetime",
+ Element::create(static_cast<long long>(valid_.getMin())));
+ }
+
+ if (valid_.getMax() > valid_.get()) {
+ result->set("max-valid-lifetime",
+ Element::create(static_cast<long long>(valid_.getMax())));
+ }
+ }
+
+ return (result);
+}
+
+ElementPtr
+TemplateClientClassDef::toElement() const {
+ auto const& result = ClientClassDef::toElement();
+ auto const& test = result->get("test");
+ if (test) {
+ result->set("template-test", test);
+ result->remove("test");
+ } else {
+ result->set("template-test", Element::create(""));
+ }
+ return (result);
+}
+
+std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
+ os << "ClientClassDef:" << x.getName();
+ return (os);
+}
+
+//********** ClientClassDictionary ******************//
+
+ClientClassDictionary::ClientClassDictionary()
+ : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
+}
+
+ClientClassDictionary::ClientClassDictionary(const ClientClassDictionary& rhs)
+ : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
+ BOOST_FOREACH(ClientClassDefPtr cclass, *(rhs.list_)) {
+ ClientClassDefPtr copy(new ClientClassDef(*cclass));
+ addClass(copy);
+ }
+}
+
+ClientClassDictionary::~ClientClassDictionary() {
+}
+
+void
+ClientClassDictionary::addClass(const std::string& name,
+ const ExpressionPtr& match_expr,
+ const std::string& test,
+ bool required,
+ bool depend_on_known,
+ const CfgOptionPtr& cfg_option,
+ CfgOptionDefPtr cfg_option_def,
+ ConstElementPtr user_context,
+ asiolink::IOAddress next_server,
+ const std::string& sname,
+ const std::string& filename,
+ const util::Triplet<uint32_t>& valid,
+ const util::Triplet<uint32_t>& preferred,
+ bool is_template,
+ const util::Optional<uint32_t>& offer_lft) {
+ ClientClassDefPtr cclass;
+ if (is_template) {
+ cclass.reset(new TemplateClientClassDef(name, match_expr, cfg_option));
+ } else {
+ cclass.reset(new ClientClassDef(name, match_expr, cfg_option));
+ }
+ cclass->setTest(test);
+ cclass->setRequired(required);
+ cclass->setDependOnKnown(depend_on_known);
+ cclass->setCfgOptionDef(cfg_option_def);
+ cclass->setContext(user_context),
+ cclass->setNextServer(next_server);
+ cclass->setSname(sname);
+ cclass->setFilename(filename);
+ cclass->setValid(valid);
+ cclass->setPreferred(preferred);
+ cclass->setOfferLft(offer_lft);
+ addClass(cclass);
+}
+
+void
+ClientClassDictionary::addClass(ClientClassDefPtr& class_def) {
+ if (!class_def) {
+ isc_throw(BadValue, "ClientClassDictionary::addClass "
+ " - class definition cannot be null");
+ }
+
+ if (findClass(class_def->getName())) {
+ isc_throw(DuplicateClientClassDef, "Client Class: "
+ << class_def->getName() << " has already been defined");
+ }
+
+ list_->push_back(class_def);
+ (*map_)[class_def->getName()] = class_def;
+}
+
+ClientClassDefPtr
+ClientClassDictionary::findClass(const std::string& name) const {
+ ClientClassDefMap::iterator it = map_->find(name);
+ if (it != map_->end()) {
+ return (*it).second;
+ }
+
+ return (ClientClassDefPtr());
+}
+
+void
+ClientClassDictionary::removeClass(const std::string& name) {
+ for (ClientClassDefList::iterator this_class = list_->begin();
+ this_class != list_->end(); ++this_class) {
+ if ((*this_class)->getName() == name) {
+ list_->erase(this_class);
+ break;
+ }
+ }
+ map_->erase(name);
+}
+
+void
+ClientClassDictionary::removeClass(const uint64_t id) {
+ // Class id equal to 0 means it wasn't set.
+ if (id == 0) {
+ return;
+ }
+ for (ClientClassDefList::iterator this_class = list_->begin();
+ this_class != list_->end(); ++this_class) {
+ if ((*this_class)->getId() == id) {
+ map_->erase((*this_class)->getName());
+ list_->erase(this_class);
+ break;
+ }
+ }
+}
+
+const ClientClassDefListPtr&
+ClientClassDictionary::getClasses() const {
+ return (list_);
+}
+
+bool
+ClientClassDictionary::empty() const {
+ return (list_->empty());
+}
+
+bool
+ClientClassDictionary::dependOnClass(const std::string& name,
+ std::string& dependent_class) const {
+ // Skip previous classes as they should not depend on name.
+ bool found = false;
+ for (ClientClassDefList::iterator this_class = list_->begin();
+ this_class != list_->end(); ++this_class) {
+ if (found) {
+ if ((*this_class)->dependOnClass(name)) {
+ dependent_class = (*this_class)->getName();
+ return (true);
+ }
+ } else {
+ if ((*this_class)->getName() == name) {
+ found = true;
+ }
+ }
+ }
+ return (false);
+}
+
+bool
+ClientClassDictionary::equals(const ClientClassDictionary& other) const {
+ if (list_->size() != other.list_->size()) {
+ return (false);
+ }
+
+ ClientClassDefList::const_iterator this_class = list_->cbegin();
+ ClientClassDefList::const_iterator other_class = other.list_->cbegin();
+ while (this_class != list_->cend() &&
+ other_class != other.list_->cend()) {
+ if (!*this_class || !*other_class ||
+ **this_class != **other_class) {
+ return false;
+ }
+
+ ++this_class;
+ ++other_class;
+ }
+
+ return (true);
+}
+
+void
+ClientClassDictionary::initMatchExpr(uint16_t family) {
+ std::queue<ExpressionPtr> expressions;
+ for (auto c : *list_) {
+ if (!c->getTest().empty()) {
+ ExpressionPtr match_expr = boost::make_shared<Expression>();
+ ExpressionParser parser;
+ EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL;
+ if (dynamic_cast<TemplateClientClassDef*>(c.get())) {
+ parser_type = EvalContext::PARSER_STRING;
+ }
+ parser.parse(match_expr, Element::create(c->getTest()), family,
+ EvalContext::acceptAll, parser_type);
+ expressions.push(match_expr);
+ }
+ }
+ // All expressions successfully initialized. Let's set them for the
+ // client classes in the dictionary.
+ for (auto c : *list_) {
+ if (!c->getTest().empty()) {
+ c->setMatchExpr(expressions.front());
+ expressions.pop();
+ }
+ }
+}
+
+void
+ClientClassDictionary::createOptions(const CfgOptionDefPtr& external_defs) {
+ for (auto c : *list_) {
+ // If the class has no options, skip it.
+ CfgOptionPtr class_options = c->getCfgOption();
+ if (!class_options || class_options->empty()) {
+ continue;
+ }
+
+ // If the class has no option definitions, use the set
+ // of definitions we were given as is to create its
+ // options.
+ if (!c->getCfgOptionDef()) {
+ class_options->createOptions(external_defs);
+ } else {
+ // Class has its own option definitions, we need a
+ // composite set of definitions to recreate its options.
+ // We make copies of both sets of definitions, then merge
+ // the external defs copy into the class defs copy.
+ // We do this because merging actually effects both sets
+ // of definitions and we cannot alter either set.
+ // Seed the composite set with the class's definitions.
+ CfgOptionDefPtr composite_defs(new CfgOptionDef());
+ c->getCfgOptionDef()->copyTo(*composite_defs);
+
+ // Make a copy of the external definitions and
+ // merge those into the composite set. This should give
+ // us a set of options with class definitions taking
+ // precedence.
+ CfgOptionDefPtr external_defs_copy(new CfgOptionDef());
+ external_defs->copyTo(*external_defs_copy);
+ composite_defs->merge(*external_defs_copy);
+
+ // Now create the class options using the composite
+ // set of definitions.
+ class_options->createOptions(composite_defs);
+ }
+ }
+}
+
+ElementPtr
+ClientClassDictionary::toElement() const {
+ ElementPtr result = Element::createList();
+ // Iterate on the map
+ for (ClientClassDefList::const_iterator this_class = list_->begin();
+ this_class != list_->cend(); ++this_class) {
+ result->add((*this_class)->toElement());
+ }
+ return (result);
+}
+
+ClientClassDictionary&
+ClientClassDictionary::operator=(const ClientClassDictionary& rhs) {
+ if (this != &rhs) {
+ list_->clear();
+ map_->clear();
+ for (auto cclass : *(rhs.list_)) {
+ ClientClassDefPtr copy(new ClientClassDef(*cclass));
+ addClass(copy);
+ }
+ }
+ return (*this);
+}
+
+/// @brief List of classes for which test expressions cannot be defined.
+std::list<std::string>
+builtinNames = {
+ // DROP is not in this list because it is special but not built-in.
+ // In fact DROP is set from an expression as callouts can drop
+ // directly the incoming packet. The expression must not depend on
+ // KNOWN/UNKNOWN which are set far after the drop point.
+ // SKIP_DDNS, used by DDNS-tuning is also omitted from this list
+ // so users may assign it a test expression.
+ "ALL", "KNOWN", "UNKNOWN", "BOOTP"
+};
+
+/// @brief The prefixes used to check if a class is BuiltIn class.
+///
+/// The 'SPAWN_' prefix is not added to this list to permit other template
+/// classes to associate the packet to regular classes which use this prefix in
+/// their name. This guarantees that regular classes are never treated as
+/// built-in classes.
+std::list<std::string>
+builtinPrefixes = {
+ "VENDOR_CLASS_", "HA_", "AFTER_", "EXTERNAL_"
+};
+
+bool
+isClientClassBuiltIn(const ClientClass& client_class) {
+ for (std::list<std::string>::const_iterator bn = builtinNames.cbegin();
+ bn != builtinNames.cend(); ++bn) {
+ if (client_class == *bn) {
+ return true;
+ }
+ }
+
+ for (std::list<std::string>::const_iterator bt = builtinPrefixes.cbegin();
+ bt != builtinPrefixes.cend(); ++bt) {
+ if (client_class.size() <= bt->size()) {
+ continue;
+ }
+ auto mis = std::mismatch(bt->cbegin(), bt->cend(), client_class.cbegin());
+ if (mis.first == bt->cend()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+isClientClassDefined(ClientClassDictionaryPtr& class_dictionary,
+ bool& depend_on_known,
+ const ClientClass& client_class) {
+ // First check built-in classes
+ if (isClientClassBuiltIn(client_class)) {
+ // Check direct dependency on [UN]KNOWN
+ if ((client_class == "KNOWN") || (client_class == "UNKNOWN")) {
+ depend_on_known = true;
+ }
+ return (true);
+ }
+
+ // Second check already defined, i.e. in the dictionary
+ ClientClassDefPtr def = class_dictionary->findClass(client_class);
+ if (def) {
+ // Check indirect dependency on [UN]KNOWN
+ if (def->getDependOnKnown()) {
+ depend_on_known = true;
+ }
+ return (true);
+ }
+
+ // Not defined...
+ return (false);
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/client_class_def.h b/src/lib/dhcpsrv/client_class_def.h
new file mode 100644
index 0000000..0cede45
--- /dev/null
+++ b/src/lib/dhcpsrv/client_class_def.h
@@ -0,0 +1,543 @@
+// Copyright (C) 2015-2023 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 CLIENT_CLASS_DEF_H
+#define CLIENT_CLASS_DEF_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/stamped_element.h>
+#include <cc/user_context.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <eval/token.h>
+#include <exceptions/exceptions.h>
+#include <util/triplet.h>
+#include <util/optional.h>
+
+#include <string>
+#include <unordered_map>
+#include <list>
+#include <vector>
+
+/// @file client_class_def.h
+///
+/// @brief Defines classes for storing client class definitions
+///
+/// The file defines the class, ClientClassDef, which houses the
+/// information for single client class such as the class name, the
+/// logical expression used to identify members of the class, and options
+/// that may be attributed to class members.
+///
+/// In addition it defines a container class, ClientClassDictionary, which
+/// is houses class definitions keyed by class name.
+///
+namespace isc {
+namespace dhcp {
+
+/// @brief Error that occurs when an attempt is made to add a duplicate class
+/// to a class dictionary.
+class DuplicateClientClassDef : public isc::Exception {
+public:
+ DuplicateClientClassDef(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Embodies a single client class definition
+class ClientClassDef : public data::UserContext,
+ public data::CfgToElement,
+ public data::StampedElement {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name Name to assign to this class
+ /// @param match_expr Expression the class will use to determine membership
+ /// @param options Collection of options members should be given
+ ClientClassDef(const std::string& name, const ExpressionPtr& match_expr,
+ const CfgOptionPtr& options = CfgOptionPtr());
+
+ /// Copy constructor
+ ClientClassDef(const ClientClassDef& rhs);
+
+ /// @brief Destructor
+ virtual ~ClientClassDef();
+
+ /// @brief Fetches the class's name
+ std::string getName() const;
+
+ /// @brief Sets the class's name
+ ///
+ /// @param name the name to assign the class
+ void setName(const std::string& name);
+
+ /// @brief Fetches the class's match expression
+ const ExpressionPtr& getMatchExpr() const;
+
+ /// @brief Sets the class's match expression
+ ///
+ /// @param match_expr the expression to assign the class
+ void setMatchExpr(const ExpressionPtr& match_expr);
+
+ /// @brief Fetches the class's original match expression
+ std::string getTest() const;
+
+ /// @brief Sets the class's original match expression
+ ///
+ /// @param test the original expression to assign the class
+ void setTest(const std::string& test);
+
+ /// @brief Fetches the only if required flag
+ bool getRequired() const;
+
+ /// @brief Sets the only if required flag
+ ///
+ /// @param required the value of the only if required flag
+ void setRequired(bool required);
+
+ /// @brief Fetches the depend on known flag aka use host flag
+ bool getDependOnKnown() const;
+
+ /// @brief Sets the depend on known flag aka use host flag
+ ///
+ /// @param depend_on_known the value of the depend on known flag
+ void setDependOnKnown(bool depend_on_known);
+
+ /// @brief Fetches the class's option definitions
+ const CfgOptionDefPtr& getCfgOptionDef() const;
+
+ /// @brief Sets the class's option definition collection
+ ///
+ /// @param cfg_option_def the option definitions to assign the class
+ void setCfgOptionDef(const CfgOptionDefPtr& cfg_option_def);
+
+ /// @brief Fetches the class's option collection
+ const CfgOptionPtr& getCfgOption() const;
+
+ /// @brief Sets the class's option collection
+ ///
+ /// @param cfg_option the option collection to assign the class
+ void setCfgOption(const CfgOptionPtr& cfg_option);
+
+ /// @brief Checks direct dependency.
+ ///
+ /// @param name The client class name.
+ ///
+ /// @return true if the definition depends on the class name, false if not.
+ bool dependOnClass(const std::string& name) const;
+
+ /// @brief Compares two @c ClientClassDef objects for equality.
+ ///
+ /// @param other Other client class definition to compare to.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool equals(const ClientClassDef& other) const;
+
+ /// @brief Equality operator.
+ ///
+ /// @param other Other client class definition to compare to.
+ ///
+ /// @return true if the definitions equal, false otherwise.
+ bool operator==(const ClientClassDef& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other Other client class definition to compare to.
+ ///
+ /// @return true if the definitions are not equal, false otherwise.
+ bool operator!=(const ClientClassDef& other) const {
+ return (!(equals(other)));
+ }
+
+ /// @brief Provides a convenient text representation of the class
+ friend std::ostream& operator<<(std::ostream& os, const ClientClassDef& x);
+
+ /// @brief returns next-server value
+ /// @return next-server value
+ const asiolink::IOAddress& getNextServer() const {
+ return (next_server_);
+ }
+
+ /// @brief sets the next-server value
+ ///
+ /// @param addr the value to be set
+ void setNextServer(const asiolink::IOAddress& addr) {
+ next_server_ = addr;
+ }
+
+ /// @brief sets the server-name value
+ ///
+ /// @param sname the value to be set
+ void setSname(const std::string& sname) {
+ sname_ = sname;
+ }
+
+ /// @brief returns server-hostname value
+ /// @return the vector that contains server-hostname (may be empty if not defined)
+ const std::string& getSname() const {
+ return (sname_);
+ }
+
+ /// @brief sets the boot-file-name value
+ ///
+ /// @param filename the value to be set
+ void setFilename(const std::string& filename) {
+ filename_ = filename;
+ }
+
+ /// @brief returns boot-file-name value
+ /// @return the vector that contains boot-file-name (may be empty if not defined)
+ const std::string& getFilename() const {
+ return (filename_);
+ }
+
+ /// @brief Return valid-lifetime value
+ ///
+ /// @return a triplet containing the valid lifetime.
+ util::Triplet<uint32_t> getValid() const {
+ return (valid_);
+ }
+
+ /// @brief Sets new valid lifetime
+ ///
+ /// @param valid New valid lifetime in seconds.
+ void setValid(const util::Triplet<uint32_t>& valid) {
+ valid_ = valid;
+ }
+
+ /// @brief Return preferred-lifetime value
+ ///
+ /// @return a triplet containing the preferred lifetime.
+ util::Triplet<uint32_t> getPreferred() const {
+ return (preferred_);
+ }
+
+ /// @brief Sets new preferred lifetime
+ ///
+ /// @param preferred New valid lifetime in seconds.
+ void setPreferred(const util::Triplet<uint32_t>& preferred) {
+ preferred_ = preferred;
+ }
+
+ /// @brief Sets offer lifetime for the class.
+ ///
+ /// @param offer_lft the offer lifetime assigned to the class (may be empty if not defined)
+ void setOfferLft(const util::Optional<uint32_t>& offer_lft) {
+ offer_lft_ = offer_lft;
+ }
+
+ /// @brief Returns offer lifetime for the class.
+ ///
+ /// @return offer lifetime value
+ util::Optional<uint32_t> getOfferLft() const {
+ return (offer_lft_);
+ }
+
+ /// @brief Test method which checks if the packet belongs to the class
+ ///
+ /// If the packet belongs to the class, the class is added to the packet.
+ ///
+ /// @param pkt The packet checked if it belongs to the class.
+ virtual void test(PktPtr pkt, const ExpressionPtr& expr_ptr);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief Unique text identifier by which this class is known.
+ std::string name_;
+
+ /// @brief The logical expression which determines membership in
+ /// this class.
+ ExpressionPtr match_expr_;
+
+ /// @brief The original expression which determines membership in
+ /// this class.
+ std::string test_;
+
+ /// @brief The only-if-required flag: when false (the default) membership
+ /// is determined during classification so is available for instance for
+ /// subnet selection. When true, membership is evaluated only when required
+ /// and is usable only for option configuration.
+ bool required_;
+
+ /// @brief The depend on known aka use host flag: when false (the default),
+ /// the required flag is false and the class has a match expression
+ /// the expression is evaluated in the first pass. When true and the
+ /// two other conditions stand the expression is evaluated later when
+ /// the host reservation membership was determined.
+ /// This flag is set to true during the match expression parsing if
+ /// direct or indirect dependency on the builtin [UN]KNOWN classes is
+ /// detected.
+ bool depend_on_known_;
+
+ /// @brief The option definition configuration for this class
+ CfgOptionDefPtr cfg_option_def_;
+
+ /// @brief The option data configuration for this class
+ CfgOptionPtr cfg_option_;
+
+ /// @brief Next server field
+ /// If set by the next-server parameter, this value will be set
+ /// in the siaddr field of the DHCPv4 packet.
+ asiolink::IOAddress next_server_;
+
+ /// @brief server-hostname
+ /// If set by the server-hostname parameter, this value will be
+ /// set in the sname field of the DHCPv4 packet.
+ /// This can be up to 64 octets long.
+ std::string sname_;
+
+ /// @brief boot-file-name
+ /// If set by the boot-file-name parameter, this value will be
+ /// set in the file field of the DHCPv4 packet.
+ /// This can be up to 128 octets long.
+ std::string filename_;
+
+ /// @brief a Triplet (min/default/max) holding allowed valid lifetime values
+ util::Triplet<uint32_t> valid_;
+
+ /// @brief a Triplet (min/default/max) holding allowed preferred lifetime values
+ util::Triplet<uint32_t> preferred_;
+
+ /// @brief offer lifetime for this class (V4 only).
+ util::Optional<uint32_t> offer_lft_;
+};
+
+class TemplateClientClassDef : public ClientClassDef {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name Name to assign to this class
+ /// @param match_expr Expression the class will use to determine membership
+ /// @param options Collection of options members should be given
+ TemplateClientClassDef(const std::string& name, const ExpressionPtr& match_expr,
+ const CfgOptionPtr& options = CfgOptionPtr());
+
+ /// @brief Test method which checks if the packet belongs to the class
+ ///
+ /// If the packet belongs to the class, the class is added to the packet.
+ ///
+ /// @param pkt The packet checked if it belongs to the class.
+ virtual void test(PktPtr pkt, const ExpressionPtr& expr_ptr) override;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const override;
+
+ /// @brief This is a prefix added to the spawned class name
+ ///
+ /// If incoming packet is associated with the template class, the name of
+ /// generated spawned class is prepended with this prefix.
+ /// For example, a packet that associates with the template class "FOO" by
+ /// evaluating the template class expression to BAR will cause the packet to
+ /// be assigned to class SPAWN_FOO_BAR.
+ static const std::string SPAWN_CLASS_PREFIX;
+};
+
+/// @brief a pointer to an ClientClassDef
+typedef boost::shared_ptr<ClientClassDef> ClientClassDefPtr;
+
+/// @brief Defines a map of ClientClassDef's, keyed by the class name.
+typedef std::unordered_map<std::string, ClientClassDefPtr> ClientClassDefMap;
+
+/// @brief Defines a pointer to a ClientClassDefMap
+typedef boost::shared_ptr<ClientClassDefMap> ClientClassDefMapPtr;
+
+/// @brief Defines a list of ClientClassDefPtr's, using insert order.
+typedef std::vector<ClientClassDefPtr> ClientClassDefList;
+
+/// @brief Defines a pointer to a ClientClassDefList
+typedef boost::shared_ptr<ClientClassDefList> ClientClassDefListPtr;
+
+/// @brief Maintains a list of ClientClassDef's
+class ClientClassDictionary : public isc::data::CfgToElement {
+
+public:
+ /// @brief Constructor
+ ClientClassDictionary();
+
+ ClientClassDictionary(const ClientClassDictionary& rhs);
+
+ /// @brief Destructor
+ ~ClientClassDictionary();
+
+ /// @brief Adds a new class to the list
+ ///
+ /// @param name Name to assign to this class
+ /// @param match_expr Expression the class will use to determine membership
+ /// @param test Original version of match_expr
+ /// @param required Original value of the only if required flag
+ /// @param depend_on_known Using host so will be evaluated later
+ /// @param options Collection of options members should be given
+ /// @param defs Option definitions (optional)
+ /// @param user_context User context (optional)
+ /// @param next_server next-server value for this class (optional)
+ /// @param sname server-name value for this class (optional)
+ /// @param filename boot-file-name value for this class (optional)
+ /// @param valid valid-lifetime triplet (optional)
+ /// @param preferred preferred-lifetime triplet (optional)
+ ///
+ /// @throw DuplicateClientClassDef if class already exists within the
+ /// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for
+ /// others.
+ void addClass(const std::string& name, const ExpressionPtr& match_expr,
+ const std::string& test, bool required, bool depend_on_known,
+ const CfgOptionPtr& options,
+ CfgOptionDefPtr defs = CfgOptionDefPtr(),
+ isc::data::ConstElementPtr user_context = isc::data::ConstElementPtr(),
+ asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
+ const std::string& sname = std::string(),
+ const std::string& filename = std::string(),
+ const util::Triplet<uint32_t>& valid = util::Triplet<uint32_t>(),
+ const util::Triplet<uint32_t>& preferred = util::Triplet<uint32_t>(),
+ bool is_template = false,
+ const util::Optional<uint32_t>& offer_lft = util::Optional<uint32_t>());
+
+ /// @brief Adds a new class to the list
+ ///
+ /// @param class_def pointer to class definition to add
+ ///
+ /// @throw DuplicateClientClassDef if class already exists within the
+ /// dictionary, BadValue if the pointer is empty.
+ void addClass(ClientClassDefPtr& class_def);
+
+ /// @brief Fetches the class definition for a given class name
+ ///
+ /// @param name the name of the desired class
+ ///
+ /// @return ClientClassDefPtr to the desired class if found, or
+ /// an empty pointer if not.
+ ClientClassDefPtr findClass(const std::string& name) const;
+
+ /// @brief Removes a given class definition from the dictionary
+ ///
+ /// Removes the class definition from the map if it exists, otherwise
+ /// no harm, no foul.
+ ///
+ /// @param name the name of the class to remove
+ void removeClass(const std::string& name);
+
+ /// @brief Removes a client class by id.
+ ///
+ /// @param id class id.
+ void removeClass(const uint64_t id);
+
+ /// @brief Fetches the dictionary's list of classes
+ ///
+ /// @return ClientClassDefListPtr to the list of classes
+ const ClientClassDefListPtr& getClasses() const;
+
+ /// @brief Checks if the class dictionary is empty.
+ ///
+ /// @return true if there are no classes, false otherwise.
+ bool empty() const;
+
+ /// @brief Checks direct dependency.
+ ///
+ /// @param name The client class name.
+ /// @param [out] dependent_class Reference to a variable where the
+ /// name of the first class depending on the checked class is set.
+ ///
+ /// @return true if a definition depends on the class name, false if none.
+ bool dependOnClass(const std::string& name, std::string& dependent_class) const;
+
+ /// @brief Compares two @c ClientClassDictionary objects for equality.
+ ///
+ /// @param other Other client class definition to compare to.
+ ///
+ /// @return true if descriptors equal, false otherwise.
+ bool equals(const ClientClassDictionary& other) const;
+
+ /// @brief Iterates over the classes in the dictionary and ensures that
+ /// that match expressions are initialized.
+ ///
+ /// @param family Class universe, e.g. AF_INET or AF_INET6.
+ void initMatchExpr(uint16_t family);
+
+ /// @brief Iterates over the classes in the dictionary and recreates
+ /// the options.
+ ///
+ /// @param cfg_option_def set of option definitions to use.
+ void createOptions(const CfgOptionDefPtr& cfg_option_def);
+
+ /// @brief Equality operator.
+ ///
+ /// @param other Other client class dictionary to compare to.
+ ///
+ /// @return true if the dictionaries are equal, false otherwise.
+ bool operator==(const ClientClassDictionary& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other Other client class dictionary to compare to.
+ ///
+ /// @return true if the dictionaries are not equal, false otherwise.
+ bool operator!=(const ClientClassDictionary& other) const {
+ return (!equals(other));
+ }
+
+ /// @brief Copy assignment operator.
+ ///
+ /// @param rhs Client class dictionary to be copied from.
+ /// @return Instance copy.
+ ClientClassDictionary& operator=(const ClientClassDictionary& rhs);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief Map of the class definitions
+ ClientClassDefMapPtr map_;
+
+ /// @brief List of the class definitions
+ ClientClassDefListPtr list_;
+};
+
+/// @brief Defines a pointer to a ClientClassDictionary
+typedef boost::shared_ptr<ClientClassDictionary> ClientClassDictionaryPtr;
+
+/// @brief List of built-in client class names.
+/// i.e. ALL, KNOWN, UNKNOWN and BOOTP but not DROP.
+extern std::list<std::string> builtinNames;
+
+/// @brief List of built-in client class prefixes
+/// i.e. VENDOR_CLASS_, HA_, AFTER_ and EXTERNAL_.
+extern std::list<std::string> builtinPrefixes;
+
+/// @brief Check if a client class name is builtin.
+///
+/// @param client_class A client class name to look for.
+/// @return true if built-in, false if not.
+bool isClientClassBuiltIn(const ClientClass& client_class);
+
+/// @brief Check if a client class name is already defined,
+/// i.e. is built-in or in the dictionary,
+///
+/// The reference to depend on known flag is set to true if the class
+/// is KNOWN or UNKNOWN (direct dependency) or has this flag set
+/// (indirect dependency).
+///
+/// @param class_dictionary A class dictionary where to look for.
+/// @param depend_on_known A reference to depend on known flag.
+/// @param client_class A client class name to look for.
+/// @return true if defined or built-in, false if not.
+bool isClientClassDefined(ClientClassDictionaryPtr& class_dictionary,
+ bool& depend_on_known,
+ const ClientClass& client_class);
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CLIENT_CLASS_DEF_H
diff --git a/src/lib/dhcpsrv/config_backend_dhcp4.h b/src/lib/dhcpsrv/config_backend_dhcp4.h
new file mode 100644
index 0000000..a235a4b
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_dhcp4.h
@@ -0,0 +1,691 @@
+// Copyright (C) 2018-2021 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 CONFIG_BACKEND_DHCP4_H
+#define CONFIG_BACKEND_DHCP4_H
+
+#include <cc/stamped_value.h>
+#include <config_backend/base_config_backend.h>
+#include <database/audit_entry.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/ptime.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Interface implemented by DHCPv4 configuration backends.
+///
+/// All POSIX times specified in the methods belonging to this
+/// class must be local times.
+///
+/// Below, we describe the general rules of using the server selectors
+/// when creating, updating, fetching and deleting the configuration
+/// elements from the backends. The detailed information can be found
+/// in the descriptions of the individual methods. The backend
+/// implementations must not be in conflict with the rules described
+/// here but may sometimes lack some functionality and not support
+/// some of the server selectors for some API calls. In such cases
+/// the backend's documentation should be clear about these cases
+/// and document the exceptions thrown when unsupported selector is
+/// used for a given method.
+///
+/// The @c ServerSelector class defines 5 types of selectors:
+/// - ANY: server tag/id is not a part of the database query, i.e. the
+/// object in the database is identified by some unique property,
+/// e.g. subnet identifier, shared network name etc.
+///
+/// - UNASSIGNED: query pertains to the objects in the database which
+/// are associated with no particular server (including the logical
+/// server "all"). Objects associated with any server are never
+/// selected.
+///
+/// - ALL: query pertains only to the objects in the database which are
+/// associated with the logical server "all". Those objects are shared
+/// between all servers using the database. This server selector never
+/// returns objects explicitly associated with the particular servers
+/// defined by the user.
+///
+/// - ONE: query pertains to the objects used by one particular server.
+/// The server uses both the objects explicitly associated with it and
+/// and the objects associated with the logical server "all". Therefore
+/// the result returned for this server selector combines configuration
+/// elements associated with this server and with "all" servers. In case
+/// if there are two instances of the configuration information, one
+/// associated with "all" servers and one associated with the server,
+/// the information associated with the server takes precedence.
+/// When using this selector to delete objects from the database, the
+/// deletion pertains only to the objects associated with the given
+/// server tag. It doesn't delete the objects associated with "all"
+/// servers.
+///
+/// - MULTIPLE: query pertains to the objects used by multiple servers
+/// listed in the selector. It allows for querying for a list of
+/// objects associated with multiple servers and/or logical server
+/// "all".
+///
+/// There are limitations imposed on the API calls what server selectors
+/// are allowed for them. Configuration Backend implementations must not
+/// be in conflict with those limitations. In particular, the implementation
+/// must not permit for server selectors which are not allowed here.
+/// However, the backend implementation may be more restrictive and not
+/// allow some of the server selectors for some API calls. This should,
+/// however, be properly documented.
+class ConfigBackendDHCPv4 : public cb::BaseConfigBackend {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~ConfigBackendDHCPv4() { }
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const = 0;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const = 0;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getAllSubnets4(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// retrieved subnets should belongs to.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const = 0;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getModifiedSubnets4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork4Ptr
+ getSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) const = 0;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getAllSharedNetworks4(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getModifiedSharedNetworks4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const = 0;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs4(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Option code.
+ /// @param space Option space.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const = 0;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions4(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves options modified after specified time.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions4(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter or null if parameter doesn't
+ /// exist.
+ virtual data::StampedValuePtr
+ getGlobalParameter4(const db::ServerSelector& selector,
+ const std::string& name) const = 0;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of global parameters.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters4(const db::ServerSelector& selector) const = 0;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters4(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass4(const db::ServerSelector& selector, const std::string& name) const = 0;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses4(const db::ServerSelector& selector) const = 0;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses4(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const = 0;
+
+ /// @brief Retrieves all servers.
+ ///
+ /// This method returns the list of servers excluding the logical server
+ /// 'all'.
+ ///
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers4() const = 0;
+
+ /// @brief Retrieves a server.
+ ///
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer4(const data::ServerTag& server_tag) const = 0;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet4(const db::ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) = 0;
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork4(const db::ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) = 0;
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef4(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) = 0;
+
+ /// @brief Creates or updates global option.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates global parameter.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter4(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value) = 0;
+
+ /// @brief Creates or updates DHCPv4 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass4(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) = 0;
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer4(const db::ServerPtr& server) = 0;
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) = 0;
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id) = 0;
+
+ /// @brief Deletes all subnets.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE.
+ /// Not allowed server selectors: ANY, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets4(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// deleted subnets should belongs to.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) = 0;
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) = 0;
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE.
+ /// Not allowed server selectors: ANY, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks4(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes option definition.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs4(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes global option.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes pool level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes global parameter.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name) = 0;
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters4(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes DHCPv4 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name) = 0;
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses4(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer4(const data::ServerTag& server_tag) = 0;
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers4() = 0;
+};
+
+/// @brief Shared pointer to the @c ConfigBackendDHCPv4 instance.
+typedef boost::shared_ptr<ConfigBackendDHCPv4> ConfigBackendDHCPv4Ptr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CONFIG_BACKEND_DHCP4_H
diff --git a/src/lib/dhcpsrv/config_backend_dhcp4_mgr.cc b/src/lib/dhcpsrv/config_backend_dhcp4_mgr.cc
new file mode 100644
index 0000000..587cbc8
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_dhcp4_mgr.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2018-2019 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 <dhcpsrv/config_backend_dhcp4_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+boost::scoped_ptr<ConfigBackendDHCPv4Mgr>&
+ConfigBackendDHCPv4Mgr::getConfigBackendDHCPv4MgrPtr() {
+ static boost::scoped_ptr<ConfigBackendDHCPv4Mgr> cb_dhcp4_mgr;
+ return (cb_dhcp4_mgr);
+}
+
+void
+ConfigBackendDHCPv4Mgr::create() {
+ getConfigBackendDHCPv4MgrPtr().reset(new ConfigBackendDHCPv4Mgr());
+}
+
+void
+ConfigBackendDHCPv4Mgr::destroy() {
+ getConfigBackendDHCPv4MgrPtr().reset();
+}
+
+ConfigBackendDHCPv4Mgr&
+ConfigBackendDHCPv4Mgr::instance() {
+ boost::scoped_ptr<ConfigBackendDHCPv4Mgr>& cb_dhcp4_mgr = getConfigBackendDHCPv4MgrPtr();
+ if (!cb_dhcp4_mgr) {
+ create();
+ }
+ return (*cb_dhcp4_mgr);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/config_backend_dhcp4_mgr.h b/src/lib/dhcpsrv/config_backend_dhcp4_mgr.h
new file mode 100644
index 0000000..53073c1
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_dhcp4_mgr.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2018 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 CONFIG_BACKEND_DHCP4_MGR_H
+#define CONFIG_BACKEND_DHCP4_MGR_H
+
+#include <config_backend/base_config_backend_mgr.h>
+#include <dhcpsrv/config_backend_pool_dhcp4.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Configuration Backend Manager for DHPCv4 servers.
+///
+/// Implements the "manager" class which holds information about the
+/// supported and configured backends and provides access to those
+/// backends. This is similar to @c HostMgr and @c LeaseMgr singletons
+/// being used by the DHCP servers.
+///
+/// It is implemented as a singleton that can be accessed from any place
+/// within the server code. This includes server configuration, data
+/// fetching during normal server operation and data management, including
+/// processing of control commands implemented within hooks libraries.
+///
+/// Unlike @c HostMgr, the it does not directly expose the API to fetch and
+/// manipulate the data in the database. This is done via, the Configuration
+/// Backend Pool, see @c ConfigBackendPoolDHCPv4 for details.
+class ConfigBackendDHCPv4Mgr : public cb::BaseConfigBackendMgr<ConfigBackendPoolDHCPv4>,
+ public boost::noncopyable {
+public:
+ /// @brief Creates new instance of the @c ConfigBackendDHCPv4Mgr.
+ ///
+ /// If an instance of the @c ConfigBackendDHCPv4Mgr already exists,
+ /// it will be replaced by the new instance. Thus, all factories
+ /// will be unregistered and config databases will be dropped.
+ static void create();
+
+ /// @brief Destroys the instance of the @c ConfigBackendDHCPv4Mgr.
+ ///
+ /// If an instance of the @c ConfigBackendDHCPv4Mgr exists,
+ /// it will be destroyed. Thus, all factories will be unregistered
+ /// and config databases will be dropped.
+ static void destroy();
+
+ /// @brief Returns a sole instance of the @c ConfigBackendDHCPv4Mgr.
+ ///
+ /// This method should be used to retrieve an instance of the @c ConfigBackendDHCPv4Mgr
+ /// to be used to gather/manage config backends. It returns an instance
+ /// of the @c ConfigBackendDHCPv4Mgr created by the @c create method. If
+ /// the instance doesn't exist yet, it is created using the @c create method
+ /// with the an empty set of configuration databases.
+ static ConfigBackendDHCPv4Mgr& instance();
+
+private:
+ /// @brief Private default constructor.
+ ConfigBackendDHCPv4Mgr() {}
+
+ /// @brief Returns a pointer to the currently used instance of the
+ /// @c ConfigBackendDHCPv4Mgr.
+ static boost::scoped_ptr<ConfigBackendDHCPv4Mgr>& getConfigBackendDHCPv4MgrPtr();
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CONFIG_BACKEND_DHCP4_MGR_H
diff --git a/src/lib/dhcpsrv/config_backend_dhcp6.h b/src/lib/dhcpsrv/config_backend_dhcp6.h
new file mode 100644
index 0000000..37a7e7f
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_dhcp6.h
@@ -0,0 +1,729 @@
+// Copyright (C) 2019-2021 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 CONFIG_BACKEND_DHCP6_H
+#define CONFIG_BACKEND_DHCP6_H
+
+#include <cc/server_tag.h>
+#include <cc/stamped_value.h>
+#include <config_backend/base_config_backend.h>
+#include <database/audit_entry.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/ptime.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Interface implemented by DHCPv6 configuration backends.
+///
+/// All POSIX times specified in the methods belonging to this
+/// class must be local times.
+///
+/// Below, we describe the general rules of using the server selectors
+/// when creating, updating, fetching and deleting the configuration
+/// elements from the backends. The detailed information can be found
+/// in the descriptions of the individual methods. The backend
+/// implementations must not be in conflict with the rules described
+/// here but may sometimes lack some functionality and not support
+/// some of the server selectors for some API calls. In such cases
+/// the backend's documentation should be clear about these cases
+/// and document the exceptions thrown when unsupported selector is
+/// used for a given method.
+///
+/// The @c ServerSelector class defines 5 types of selectors:
+/// - ANY: server tag/id is not a part of the database query, i.e. the
+/// object in the database is identified by some unique property,
+/// e.g. subnet identifier, shared network name etc.
+///
+/// - UNASSIGNED: query pertains to the objects in the database which
+/// are associated with no particular server (including the logical
+/// server "all"). Objects associated with any server are never
+/// selected.
+///
+/// - ALL: query pertains only to the objects in the database which are
+/// associated with the logical server "all". Those objects are shared
+/// between all servers using the database. This server selector never
+/// returns objects explicitly associated with the particular servers
+/// defined by the user.
+///
+/// - ONE: query pertains to the objects used by one particular server.
+/// The server uses both the objects explicitly associated with it and
+/// and the objects associated with the logical server "all". Therefore
+/// the result returned for this server selector combines configuration
+/// elements associated with this server and with "all" servers. In case
+/// if there are two instances of the configuration information, one
+/// associated with "all" servers and one associated with the server,
+/// the information associated with the server takes precedence.
+/// When using this selector to delete objects from the database, the
+/// deletion pertains only to the objects associated with the given
+/// server tag. It doesn't delete the objects associated with "all"
+/// servers.
+///
+/// - MULTIPLE: query pertains to the objects used by multiple servers
+/// listed in the selector. It allows for querying for a list of
+/// objects associated with multiple servers and/or logical server
+/// "all".
+///
+/// There are limitations imposed on the API calls what server selectors
+/// are allowed for them. Configuration Backend implementations must not
+/// be in conflict with those limitations. In particular, the implementation
+/// must not permit for server selectors which are not allowed here.
+/// However, the backend implementation may be more restrictive and not
+/// allow some of the server selectors for some API calls. This should,
+/// however, be properly documented.
+class ConfigBackendDHCPv6 : public cb::BaseConfigBackend {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~ConfigBackendDHCPv6() { }
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const = 0;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const = 0;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getAllSubnets6(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// retrieved subnets should belongs to.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const = 0;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getModifiedSubnets6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork6Ptr
+ getSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) const = 0;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getAllSharedNetworks6(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getModifiedSharedNetworks6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const = 0;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs6(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Option code.
+ /// @param space Option space.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const = 0;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions6(const db::ServerSelector& server_selector) const = 0;
+
+ /// @brief Retrieves options modified after specified time.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions6(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter or null if parameter doesn't
+ /// exist.
+ virtual data::StampedValuePtr
+ getGlobalParameter6(const db::ServerSelector& selector,
+ const std::string& name) const = 0;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of global parameters.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters6(const db::ServerSelector& selector) const = 0;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// Allowed server selectors: ALL, ONE, MULTIPLE.
+ /// Not allowed server selectors: ANY, UNASSIGNED.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters6(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass6(const db::ServerSelector& selector, const std::string& name) const = 0;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses6(const db::ServerSelector& selector) const = 0;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses6(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const = 0;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const = 0;
+
+ /// @brief Retrieves all servers.
+ ///
+ /// This method returns the list of servers excluding the logical server
+ /// 'all'.
+ ///
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers6() const = 0;
+
+ /// @brief Retrieves a server.
+ ///
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer6(const data::ServerTag& server_tag) const = 0;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet6(const db::ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) = 0;
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ /// Not allowed server selector: ANY.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork6(const db::ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) = 0;
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef6(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) = 0;
+
+ /// @brief Creates or updates global option.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates prefix delegation pool level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the prefix
+ /// delegation pool to which the option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the prefix delegation
+ /// pool to which the option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const OptionDescriptorPtr& option) = 0;
+
+ /// @brief Creates or updates global parameter.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter6(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value) = 0;
+
+ /// @brief Creates or updates a client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass6(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) = 0;
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer6(const db::ServerPtr& server) = 0;
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) = 0;
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id) = 0;
+
+ /// @brief Deletes all subnets.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE.
+ /// Not allowed server selectors: ANY, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets6(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// deleted subnets should belongs to.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) = 0;
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// Allowed server selectors: ANY, UNASSIGNED, ALL, ONE.
+ /// Not allowed server selector: MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) = 0;
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// Allowed server selectors: UNASSIGNED, ALL, ONE.
+ /// Not allowed server selectors: ANY, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks6(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes option definition.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs6(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes global option.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes pool level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes prefix delegation pool level option.
+ ///
+ /// Allowed server selector: ANY.
+ /// Not allowed server selectors: UNASSIGNED, ALL, ONE, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the prefix
+ /// delegation pool to which the deleted option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the prefix delegation
+ /// pool to which the deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const uint16_t code,
+ const std::string& space) = 0;
+
+ /// @brief Deletes global parameter.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name) = 0;
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters6(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes a client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass6(const db::ServerSelector& server_selector,
+ const std::string& name) = 0;
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses6(const db::ServerSelector& server_selector) = 0;
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer6(const data::ServerTag& server_tag) = 0;
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers6() = 0;
+};
+
+/// @brief Shared pointer to the @c ConfigBackendDHCPv6 instance.
+typedef boost::shared_ptr<ConfigBackendDHCPv6> ConfigBackendDHCPv6Ptr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CONFIG_BACKEND_DHCP6_H
diff --git a/src/lib/dhcpsrv/config_backend_dhcp6_mgr.cc b/src/lib/dhcpsrv/config_backend_dhcp6_mgr.cc
new file mode 100644
index 0000000..9fc5231
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_dhcp6_mgr.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2019 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 <dhcpsrv/config_backend_dhcp6_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+boost::scoped_ptr<ConfigBackendDHCPv6Mgr>&
+ConfigBackendDHCPv6Mgr::getConfigBackendDHCPv6MgrPtr() {
+ static boost::scoped_ptr<ConfigBackendDHCPv6Mgr> cb_dhcp6_mgr;
+ return (cb_dhcp6_mgr);
+}
+
+void
+ConfigBackendDHCPv6Mgr::create() {
+ getConfigBackendDHCPv6MgrPtr().reset(new ConfigBackendDHCPv6Mgr());
+}
+
+void
+ConfigBackendDHCPv6Mgr::destroy() {
+ getConfigBackendDHCPv6MgrPtr().reset();
+}
+
+ConfigBackendDHCPv6Mgr&
+ConfigBackendDHCPv6Mgr::instance() {
+ boost::scoped_ptr<ConfigBackendDHCPv6Mgr>& cb_dhcp6_mgr = getConfigBackendDHCPv6MgrPtr();
+ if (!cb_dhcp6_mgr) {
+ create();
+ }
+ return (*cb_dhcp6_mgr);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/config_backend_dhcp6_mgr.h b/src/lib/dhcpsrv/config_backend_dhcp6_mgr.h
new file mode 100644
index 0000000..3c1062b
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_dhcp6_mgr.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2019 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 CONFIG_BACKEND_DHCP6_MGR_H
+#define CONFIG_BACKEND_DHCP6_MGR_H
+
+#include <config_backend/base_config_backend_mgr.h>
+#include <dhcpsrv/config_backend_pool_dhcp6.h>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Configuration Backend Manager for DHPCv6 servers.
+///
+/// Implements the "manager" class which holds information about the
+/// supported and configured backends and provides access to those
+/// backends. This is similar to @c HostMgr and @c LeaseMgr singletons
+/// being used by the DHCP servers.
+///
+/// It is implemented as a singleton that can be accessed from any place
+/// within the server code. This includes server configuration, data
+/// fetching during normal server operation and data management, including
+/// processing of control commands implemented within hooks libraries.
+///
+/// Unlike @c HostMgr, the it does not directly expose the API to fetch and
+/// manipulate the data in the database. This is done via, the Configuration
+/// Backend Pool, see @c ConfigBackendPoolDHCPv6 for details.
+class ConfigBackendDHCPv6Mgr : public cb::BaseConfigBackendMgr<ConfigBackendPoolDHCPv6>,
+ public boost::noncopyable {
+public:
+ /// @brief Creates new instance of the @c ConfigBackendDHCPv6Mgr.
+ ///
+ /// If an instance of the @c ConfigBackendDHCPv6Mgr already exists,
+ /// it will be replaced by the new instance. Thus, all factories
+ /// will be unregistered and config databases will be dropped.
+ static void create();
+
+ /// @brief Destroys the instance of the @c ConfigBackendDHCPv6Mgr.
+ ///
+ /// If an instance of the @c ConfigBackendDHCPv6Mgr exists,
+ /// it will be destroyed. Thus, all factories will be unregistered
+ /// and config databases will be dropped.
+ static void destroy();
+
+ /// @brief Returns a sole instance of the @c ConfigBackendDHCPv6Mgr.
+ ///
+ /// This method should be used to retrieve an instance of the @c ConfigBackendDHCPv6Mgr
+ /// to be used to gather/manage config backends. It returns an instance
+ /// of the @c ConfigBackendDHCPv6Mgr created by the @c create method. If
+ /// the instance doesn't exist yet, it is created using the @c create method
+ /// with the an empty set of configuration databases.
+ static ConfigBackendDHCPv6Mgr& instance();
+
+private:
+ /// @brief Private default constructor.
+ ConfigBackendDHCPv6Mgr() {}
+
+ /// @brief Returns a pointer to the currently used instance of the
+ /// @c ConfigBackendDHCPv6Mgr.
+ static boost::scoped_ptr<ConfigBackendDHCPv6Mgr>& getConfigBackendDHCPv6MgrPtr();
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CONFIG_BACKEND_DHCP6_MGR_H
diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc b/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc
new file mode 100644
index 0000000..1eaa273
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_pool_dhcp4.cc
@@ -0,0 +1,524 @@
+// Copyright (C) 2018-2021 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 <dhcpsrv/config_backend_pool_dhcp4.h>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+
+Subnet4Ptr
+ConfigBackendPoolDHCPv4::getSubnet4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& subnet_prefix) const {
+ Subnet4Ptr subnet;
+ getPropertyPtrConst<Subnet4Ptr, const std::string&>
+ (&ConfigBackendDHCPv4::getSubnet4, backend_selector, server_selector,
+ subnet, subnet_prefix);
+ return (subnet);
+}
+
+Subnet4Ptr
+ConfigBackendPoolDHCPv4::getSubnet4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id) const {
+ Subnet4Ptr subnet;
+ getPropertyPtrConst<Subnet4Ptr, const SubnetID&>
+ (&ConfigBackendDHCPv4::getSubnet4, backend_selector, server_selector,
+ subnet, subnet_id);
+ return (subnet);
+}
+
+Subnet4Collection
+ConfigBackendPoolDHCPv4::getAllSubnets4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ Subnet4Collection subnets;
+ getAllPropertiesConst<Subnet4Collection>
+ (&ConfigBackendDHCPv4::getAllSubnets4, backend_selector, server_selector,
+ subnets);
+ return (subnets);
+}
+
+Subnet4Collection
+ConfigBackendPoolDHCPv4::getModifiedSubnets4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ Subnet4Collection subnets;
+ getMultiplePropertiesConst<Subnet4Collection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv4::getModifiedSubnets4, backend_selector, server_selector,
+ subnets, modification_time);
+ return (subnets);
+}
+
+Subnet4Collection
+ConfigBackendPoolDHCPv4::getSharedNetworkSubnets4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const {
+ Subnet4Collection subnets;
+ getMultiplePropertiesConst<Subnet4Collection, const std::string&>
+ (&ConfigBackendDHCPv4::getSharedNetworkSubnets4, backend_selector, server_selector,
+ subnets, shared_network_name);
+ return (subnets);
+}
+
+SharedNetwork4Ptr
+ConfigBackendPoolDHCPv4::getSharedNetwork4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) const {
+ SharedNetwork4Ptr shared_network;
+ getPropertyPtrConst<SharedNetwork4Ptr, const std::string&>
+ (&ConfigBackendDHCPv4::getSharedNetwork4, backend_selector, server_selector,
+ shared_network, name);
+ return (shared_network);
+}
+
+SharedNetwork4Collection
+ConfigBackendPoolDHCPv4::getAllSharedNetworks4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ SharedNetwork4Collection shared_networks;
+ getAllPropertiesConst<SharedNetwork4Collection>
+ (&ConfigBackendDHCPv4::getAllSharedNetworks4, backend_selector, server_selector,
+ shared_networks);
+ return (shared_networks);
+}
+
+SharedNetwork4Collection
+ConfigBackendPoolDHCPv4::
+getModifiedSharedNetworks4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ SharedNetwork4Collection shared_networks;
+ getMultiplePropertiesConst<SharedNetwork4Collection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv4::getModifiedSharedNetworks4, backend_selector, server_selector,
+ shared_networks, modification_time);
+ return (shared_networks);
+}
+
+OptionDefinitionPtr
+ConfigBackendPoolDHCPv4::getOptionDef4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ OptionDefinitionPtr option_def;
+ getPropertyPtrConst<OptionDefinitionPtr, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv4::getOptionDef4, backend_selector, server_selector,
+ option_def, code, space);
+ return (option_def);
+}
+
+OptionDefContainer
+ConfigBackendPoolDHCPv4::getAllOptionDefs4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ OptionDefContainer option_defs;
+ getAllPropertiesConst<OptionDefContainer>
+ (&ConfigBackendDHCPv4::getAllOptionDefs4, backend_selector, server_selector,
+ option_defs);
+ return (option_defs);
+}
+
+OptionDefContainer
+ConfigBackendPoolDHCPv4::getModifiedOptionDefs4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ OptionDefContainer option_defs;
+ getMultiplePropertiesConst<OptionDefContainer, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv4::getModifiedOptionDefs4, backend_selector, server_selector,
+ option_defs, modification_time);
+ return (option_defs);
+}
+
+OptionDescriptorPtr
+ConfigBackendPoolDHCPv4::getOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ OptionDescriptorPtr option;
+ getPropertyPtrConst<OptionDescriptorPtr, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv4::getOption4, backend_selector, server_selector,
+ option, code, space);
+ return (option);
+}
+
+OptionContainer
+ConfigBackendPoolDHCPv4::getAllOptions4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ OptionContainer options;
+ getAllPropertiesConst<OptionContainer>
+ (&ConfigBackendDHCPv4::getAllOptions4, backend_selector, server_selector,
+ options);
+ return (options);
+}
+
+OptionContainer
+ConfigBackendPoolDHCPv4::getModifiedOptions4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ OptionContainer options;
+ getMultiplePropertiesConst<OptionContainer, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv4::getModifiedOptions4, backend_selector, server_selector,
+ options, modification_time);
+ return (options);
+}
+
+StampedValuePtr
+ConfigBackendPoolDHCPv4::getGlobalParameter4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) const {
+ StampedValuePtr parameter;
+ getPropertyPtrConst<StampedValuePtr, const std::string&>
+ (&ConfigBackendDHCPv4::getGlobalParameter4, backend_selector,
+ server_selector, parameter, name);
+ return (parameter);
+}
+
+StampedValueCollection
+ConfigBackendPoolDHCPv4::getAllGlobalParameters4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ StampedValueCollection parameters;
+ getAllPropertiesConst<StampedValueCollection>
+ (&ConfigBackendDHCPv4::getAllGlobalParameters4, backend_selector,
+ server_selector, parameters);
+ return (parameters);
+}
+
+StampedValueCollection
+ConfigBackendPoolDHCPv4::
+getModifiedGlobalParameters4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ StampedValueCollection parameters;
+ getMultiplePropertiesConst<StampedValueCollection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv4::getModifiedGlobalParameters4, backend_selector,
+ server_selector, parameters, modification_time);
+ return (parameters);
+}
+
+ClientClassDefPtr
+ConfigBackendPoolDHCPv4::getClientClass4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) const {
+ ClientClassDefPtr client_class;
+ getPropertyPtrConst<ClientClassDefPtr, const std::string&>
+ (&ConfigBackendDHCPv4::getClientClass4, backend_selector, server_selector,
+ client_class, name);
+ return (client_class);
+}
+
+ClientClassDictionary
+ConfigBackendPoolDHCPv4::getAllClientClasses4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ ClientClassDictionary client_classes;
+ getAllPropertiesConst<ClientClassDictionary>
+ (&ConfigBackendDHCPv4::getAllClientClasses4, backend_selector, server_selector,
+ client_classes);
+ return (client_classes);
+
+}
+
+ClientClassDictionary
+ConfigBackendPoolDHCPv4::getModifiedClientClasses4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ ClientClassDictionary client_classes;
+ getMultiplePropertiesConst<ClientClassDictionary, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv4::getModifiedClientClasses4, backend_selector, server_selector,
+ client_classes, modification_time);
+ return (client_classes);
+}
+
+AuditEntryCollection
+ConfigBackendPoolDHCPv4::
+getRecentAuditEntries(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const {
+ AuditEntryCollection audit_entries;
+ getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv4::getRecentAuditEntries, backend_selector,
+ server_selector, audit_entries, modification_time, modification_id);
+ return (audit_entries);
+}
+
+ServerCollection
+ConfigBackendPoolDHCPv4::getAllServers4(const BackendSelector& backend_selector) const {
+ ServerCollection servers;
+ getAllBackendPropertiesConst<ServerCollection>
+ (&ConfigBackendDHCPv4::getAllServers4, backend_selector, servers);
+ return (servers);
+}
+
+ServerPtr
+ConfigBackendPoolDHCPv4::getServer4(const BackendSelector& backend_selector,
+ const ServerTag& server_tag) const {
+ ServerPtr server;
+ getBackendPropertyPtrConst<ServerPtr, const ServerTag&>
+ (&ConfigBackendDHCPv4::getServer4, backend_selector, server,
+ server_tag);
+ return (server);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateSubnet4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ createUpdateDeleteProperty<void, const Subnet4Ptr&>
+ (&ConfigBackendDHCPv4::createUpdateSubnet4, backend_selector,
+ server_selector, subnet);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateSharedNetwork4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) {
+ createUpdateDeleteProperty<void, const SharedNetwork4Ptr&>
+ (&ConfigBackendDHCPv4::createUpdateSharedNetwork4, backend_selector,
+ server_selector, shared_network);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateOptionDef4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) {
+ createUpdateDeleteProperty<void, const OptionDefinitionPtr&>
+ (&ConfigBackendDHCPv4::createUpdateOptionDef4, backend_selector,
+ server_selector, option_def);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
+ server_selector, option);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const std::string&, const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
+ server_selector, shared_network_name, option);
+}
+
+
+void
+ConfigBackendPoolDHCPv4::createUpdateOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const SubnetID&, const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
+ server_selector, subnet_id, option);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const IOAddress& pool_start_address,
+ const IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const IOAddress&, const IOAddress&,
+ const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv4::createUpdateOption4, backend_selector,
+ server_selector, pool_start_address, pool_end_address, option);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateGlobalParameter4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const StampedValuePtr& value) {
+ createUpdateDeleteProperty<void, const StampedValuePtr&>
+ (&ConfigBackendDHCPv4::createUpdateGlobalParameter4, backend_selector,
+ server_selector, value);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateClientClass4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) {
+ createUpdateDeleteProperty<void, const ClientClassDefPtr&, const std::string&>
+ (&ConfigBackendDHCPv4::createUpdateClientClass4, backend_selector,
+ server_selector, client_class, follow_class_name);
+}
+
+void
+ConfigBackendPoolDHCPv4::createUpdateServer4(const BackendSelector& backend_selector,
+ const ServerPtr& server) {
+ createUpdateDeleteBackendProperty<void, const ServerPtr&>
+ (&ConfigBackendDHCPv4::createUpdateServer4, backend_selector,
+ server);
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteSubnet4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& subnet_prefix) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteSubnet4, backend_selector, server_selector,
+ subnet_prefix));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteSubnet4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id) {
+ return (createUpdateDeleteProperty<uint64_t, const SubnetID&>
+ (&ConfigBackendDHCPv4::deleteSubnet4, backend_selector, server_selector,
+ subnet_id));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteAllSubnets4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv4::deleteAllSubnets4, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteSharedNetworkSubnets4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteSharedNetworkSubnets4, backend_selector, server_selector,
+ shared_network_name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteSharedNetwork4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteSharedNetwork4, backend_selector,
+ server_selector, name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteAllSharedNetworks4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv4::deleteAllSharedNetworks4, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteOptionDef4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteOptionDef4, backend_selector,
+ server_selector, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteAllOptionDefs4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv4::deleteAllOptionDefs4, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector,
+ code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&, uint16_t,
+ const std::string&>
+ (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector,
+ shared_network_name, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, const SubnetID&, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector,
+ subnet_id, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteOption4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, const IOAddress&, const IOAddress&,
+ uint16_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteOption4, backend_selector, server_selector,
+ pool_start_address, pool_end_address, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteGlobalParameter4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteGlobalParameter4, backend_selector,
+ server_selector, name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteAllGlobalParameters4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv4::deleteAllGlobalParameters4, backend_selector,
+ server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteClientClass4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv4::deleteClientClass4, backend_selector,
+ server_selector, name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteAllClientClasses4(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv4::deleteAllClientClasses4, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteServer4(const BackendSelector& backend_selector,
+ const ServerTag& server_tag) {
+ return (createUpdateDeleteBackendProperty<uint64_t>
+ (&ConfigBackendDHCPv4::deleteServer4, backend_selector,
+ server_tag));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv4::deleteAllServers4(const BackendSelector& backend_selector) {
+ return (createUpdateDeleteBackendProperty<uint64_t>
+ (&ConfigBackendDHCPv4::deleteAllServers4, backend_selector));
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp4.h b/src/lib/dhcpsrv/config_backend_pool_dhcp4.h
new file mode 100644
index 0000000..1810d5f
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_pool_dhcp4.h
@@ -0,0 +1,619 @@
+// Copyright (C) 2018-2022 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 CONFIG_BACKEND_POOL_DHCP4_H
+#define CONFIG_BACKEND_POOL_DHCP4_H
+
+#include <cc/stamped_value.h>
+#include <config_backend/base_config_backend_pool.h>
+#include <database/backend_selector.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp4.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <boost/date_time/posix_time/ptime.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implementation of the Configuration Backend Pool for DHCPv4.
+///
+/// All POSIX times specified in the methods belonging to this
+/// class must be local times.
+class ConfigBackendPoolDHCPv4 : public cb::BaseConfigBackendPool<ConfigBackendDHCPv4> {
+public:
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) const;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getAllSubnets4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getModifiedSubnets4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// @note: Returning a Subnet4Collection instead of a
+ /// Subnet4SimpleCollection can be considered as overkilling
+ /// but makes this code far simpler.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be retrieved.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getSharedNetworkSubnets4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork4Ptr
+ getSharedNetwork4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getAllSharedNetworks4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getModifiedSharedNetworks4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Option code.
+ /// @param space Option space.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter.
+ virtual data::StampedValuePtr
+ getGlobalParameter4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief Retrieves all servers from the particular backend.
+ ///
+ /// This method returns the list of servers excluding the logical server
+ /// 'all'.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers4(const db::BackendSelector& backend_selector) const;
+
+ /// @brief Retrieves a server from the particular backend.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer4(const db::BackendSelector& backend_selector,
+ const data::ServerTag& server_tag) const;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const Subnet4Ptr& subnet);
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network);
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def);
+
+ /// @brief Creates or updates global option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates global string parameter.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value);
+
+ /// @brief Creates or updates DHCPv4 client class.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name);
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer4(const db::BackendSelector& backend_selector,
+ const db::ServerPtr& server);
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix);
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id);
+
+ /// @brief Deletes all subnets.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name);
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteSharedNetwork4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes option definition.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes global option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code, const std::string& space);
+
+ /// @brief Deletes pool level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes global parameter.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes DHCPv4 client class.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses4(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer4(const db::BackendSelector& backend_selector,
+ const data::ServerTag& server_tag);
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers4(const db::BackendSelector& backend_selector);
+};
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CONFIG_BACKEND_POOL_DHCP4_H
diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp6.cc b/src/lib/dhcpsrv/config_backend_pool_dhcp6.cc
new file mode 100644
index 0000000..05513e1
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_pool_dhcp6.cc
@@ -0,0 +1,549 @@
+// Copyright (C) 2019-2021 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 <dhcpsrv/config_backend_pool_dhcp6.h>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+
+Subnet6Ptr
+ConfigBackendPoolDHCPv6::getSubnet6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& subnet_prefix) const {
+ Subnet6Ptr subnet;
+ getPropertyPtrConst<Subnet6Ptr, const std::string&>
+ (&ConfigBackendDHCPv6::getSubnet6, backend_selector, server_selector,
+ subnet, subnet_prefix);
+ return (subnet);
+}
+
+Subnet6Ptr
+ConfigBackendPoolDHCPv6::getSubnet6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id) const {
+ Subnet6Ptr subnet;
+ getPropertyPtrConst<Subnet6Ptr, const SubnetID&>
+ (&ConfigBackendDHCPv6::getSubnet6, backend_selector, server_selector,
+ subnet, subnet_id);
+ return (subnet);
+}
+
+Subnet6Collection
+ConfigBackendPoolDHCPv6::getAllSubnets6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ Subnet6Collection subnets;
+ getAllPropertiesConst<Subnet6Collection>
+ (&ConfigBackendDHCPv6::getAllSubnets6, backend_selector, server_selector,
+ subnets);
+ return (subnets);
+}
+
+Subnet6Collection
+ConfigBackendPoolDHCPv6::getModifiedSubnets6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ Subnet6Collection subnets;
+ getMultiplePropertiesConst<Subnet6Collection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv6::getModifiedSubnets6, backend_selector, server_selector,
+ subnets, modification_time);
+ return (subnets);
+}
+
+Subnet6Collection
+ConfigBackendPoolDHCPv6::getSharedNetworkSubnets6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const {
+ Subnet6Collection subnets;
+ getMultiplePropertiesConst<Subnet6Collection, const std::string&>
+ (&ConfigBackendDHCPv6::getSharedNetworkSubnets6, backend_selector, server_selector,
+ subnets, shared_network_name);
+ return (subnets);
+}
+
+SharedNetwork6Ptr
+ConfigBackendPoolDHCPv6::getSharedNetwork6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) const {
+ SharedNetwork6Ptr shared_network;
+ getPropertyPtrConst<SharedNetwork6Ptr, const std::string&>
+ (&ConfigBackendDHCPv6::getSharedNetwork6, backend_selector, server_selector,
+ shared_network, name);
+ return (shared_network);
+}
+
+SharedNetwork6Collection
+ConfigBackendPoolDHCPv6::getAllSharedNetworks6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ SharedNetwork6Collection shared_networks;
+ getAllPropertiesConst<SharedNetwork6Collection>
+ (&ConfigBackendDHCPv6::getAllSharedNetworks6, backend_selector, server_selector,
+ shared_networks);
+ return (shared_networks);
+}
+
+SharedNetwork6Collection
+ConfigBackendPoolDHCPv6::
+getModifiedSharedNetworks6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ SharedNetwork6Collection shared_networks;
+ getMultiplePropertiesConst<SharedNetwork6Collection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv6::getModifiedSharedNetworks6, backend_selector, server_selector,
+ shared_networks, modification_time);
+ return (shared_networks);
+}
+
+OptionDefinitionPtr
+ConfigBackendPoolDHCPv6::getOptionDef6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ OptionDefinitionPtr option_def;
+ getPropertyPtrConst<OptionDefinitionPtr, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv6::getOptionDef6, backend_selector, server_selector,
+ option_def, code, space);
+ return (option_def);
+}
+
+OptionDefContainer
+ConfigBackendPoolDHCPv6::getAllOptionDefs6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ OptionDefContainer option_defs;
+ getAllPropertiesConst<OptionDefContainer>
+ (&ConfigBackendDHCPv6::getAllOptionDefs6, backend_selector, server_selector,
+ option_defs);
+ return (option_defs);
+}
+
+OptionDefContainer
+ConfigBackendPoolDHCPv6::getModifiedOptionDefs6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ OptionDefContainer option_defs;
+ getMultiplePropertiesConst<OptionDefContainer, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv6::getModifiedOptionDefs6, backend_selector, server_selector,
+ option_defs, modification_time);
+ return (option_defs);
+}
+
+OptionDescriptorPtr
+ConfigBackendPoolDHCPv6::getOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ OptionDescriptorPtr option;
+ getPropertyPtrConst<OptionDescriptorPtr, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv6::getOption6, backend_selector, server_selector,
+ option, code, space);
+ return (option);
+}
+
+OptionContainer
+ConfigBackendPoolDHCPv6::getAllOptions6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ OptionContainer options;
+ getAllPropertiesConst<OptionContainer>
+ (&ConfigBackendDHCPv6::getAllOptions6, backend_selector, server_selector,
+ options);
+ return (options);
+}
+
+OptionContainer
+ConfigBackendPoolDHCPv6::getModifiedOptions6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ OptionContainer options;
+ getMultiplePropertiesConst<OptionContainer, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv6::getModifiedOptions6, backend_selector, server_selector,
+ options, modification_time);
+ return (options);
+}
+
+StampedValuePtr
+ConfigBackendPoolDHCPv6::getGlobalParameter6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) const {
+ StampedValuePtr parameter;
+ getPropertyPtrConst<StampedValuePtr, const std::string&>
+ (&ConfigBackendDHCPv6::getGlobalParameter6, backend_selector,
+ server_selector, parameter, name);
+ return (parameter);
+}
+
+StampedValueCollection
+ConfigBackendPoolDHCPv6::getAllGlobalParameters6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ StampedValueCollection parameters;
+ getAllPropertiesConst<StampedValueCollection>
+ (&ConfigBackendDHCPv6::getAllGlobalParameters6, backend_selector,
+ server_selector, parameters);
+ return (parameters);
+}
+
+StampedValueCollection
+ConfigBackendPoolDHCPv6::
+getModifiedGlobalParameters6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ StampedValueCollection parameters;
+ getMultiplePropertiesConst<StampedValueCollection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv6::getModifiedGlobalParameters6, backend_selector,
+ server_selector, parameters, modification_time);
+ return (parameters);
+}
+
+ClientClassDefPtr
+ConfigBackendPoolDHCPv6::getClientClass6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) const {
+ ClientClassDefPtr client_class;
+ getPropertyPtrConst<ClientClassDefPtr, const std::string&>
+ (&ConfigBackendDHCPv6::getClientClass6, backend_selector, server_selector,
+ client_class, name);
+ return (client_class);
+}
+
+ClientClassDictionary
+ConfigBackendPoolDHCPv6::getAllClientClasses6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) const {
+ ClientClassDictionary client_classes;
+ getAllPropertiesConst<ClientClassDictionary>
+ (&ConfigBackendDHCPv6::getAllClientClasses6, backend_selector, server_selector,
+ client_classes);
+ return (client_classes);
+
+}
+
+ClientClassDictionary
+ConfigBackendPoolDHCPv6::getModifiedClientClasses6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ ClientClassDictionary client_classes;
+ getMultiplePropertiesConst<ClientClassDictionary, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv6::getModifiedClientClasses6, backend_selector, server_selector,
+ client_classes, modification_time);
+ return (client_classes);
+}
+
+AuditEntryCollection
+ConfigBackendPoolDHCPv6::
+getRecentAuditEntries(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const {
+ AuditEntryCollection audit_entries;
+ getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
+ (&ConfigBackendDHCPv6::getRecentAuditEntries, backend_selector,
+ server_selector, audit_entries, modification_time, modification_id);
+ return (audit_entries);
+}
+
+ServerCollection
+ConfigBackendPoolDHCPv6::getAllServers6(const BackendSelector& backend_selector) const {
+ ServerCollection servers;
+ getAllBackendPropertiesConst<ServerCollection>
+ (&ConfigBackendDHCPv6::getAllServers6, backend_selector, servers);
+ return (servers);
+}
+
+ServerPtr
+ConfigBackendPoolDHCPv6::getServer6(const BackendSelector& backend_selector,
+ const ServerTag& server_tag) const {
+ ServerPtr server;
+ getBackendPropertyPtrConst<ServerPtr, const ServerTag&>
+ (&ConfigBackendDHCPv6::getServer6, backend_selector, server,
+ server_tag);
+ return (server);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateSubnet6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ createUpdateDeleteProperty<void, const Subnet6Ptr&>
+ (&ConfigBackendDHCPv6::createUpdateSubnet6, backend_selector,
+ server_selector, subnet);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateSharedNetwork6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) {
+ createUpdateDeleteProperty<void, const SharedNetwork6Ptr&>
+ (&ConfigBackendDHCPv6::createUpdateSharedNetwork6, backend_selector,
+ server_selector, shared_network);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateOptionDef6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) {
+ createUpdateDeleteProperty<void, const OptionDefinitionPtr&>
+ (&ConfigBackendDHCPv6::createUpdateOptionDef6, backend_selector,
+ server_selector, option_def);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv6::createUpdateOption6, backend_selector,
+ server_selector, option);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const std::string&, const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv6::createUpdateOption6, backend_selector,
+ server_selector, shared_network_name, option);
+}
+
+
+void
+ConfigBackendPoolDHCPv6::createUpdateOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const SubnetID&, const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv6::createUpdateOption6, backend_selector,
+ server_selector, subnet_id, option);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const IOAddress& pool_start_address,
+ const IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const IOAddress&, const IOAddress&,
+ const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv6::createUpdateOption6, backend_selector,
+ server_selector, pool_start_address, pool_end_address, option);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const OptionDescriptorPtr& option) {
+ createUpdateDeleteProperty<void, const IOAddress&, uint8_t,
+ const OptionDescriptorPtr&>
+ (&ConfigBackendDHCPv6::createUpdateOption6, backend_selector,
+ server_selector, pd_pool_prefix, pd_pool_prefix_length, option);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateGlobalParameter6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const StampedValuePtr& value) {
+ createUpdateDeleteProperty<void, const StampedValuePtr&>
+ (&ConfigBackendDHCPv6::createUpdateGlobalParameter6, backend_selector,
+ server_selector, value);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateClientClass6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) {
+ createUpdateDeleteProperty<void, const ClientClassDefPtr&, const std::string&>
+ (&ConfigBackendDHCPv6::createUpdateClientClass6, backend_selector,
+ server_selector, client_class, follow_class_name);
+}
+
+void
+ConfigBackendPoolDHCPv6::createUpdateServer6(const BackendSelector& backend_selector,
+ const ServerPtr& server) {
+ createUpdateDeleteBackendProperty<void, const ServerPtr&>
+ (&ConfigBackendDHCPv6::createUpdateServer6, backend_selector,
+ server);
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteSubnet6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& subnet_prefix) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteSubnet6, backend_selector, server_selector,
+ subnet_prefix));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteSubnet6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id) {
+ return (createUpdateDeleteProperty<uint64_t, const SubnetID&>
+ (&ConfigBackendDHCPv6::deleteSubnet6, backend_selector, server_selector,
+ subnet_id));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteAllSubnets6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv6::deleteAllSubnets6, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteSharedNetworkSubnets6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteSharedNetworkSubnets6, backend_selector, server_selector,
+ shared_network_name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteSharedNetwork6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteSharedNetwork6, backend_selector,
+ server_selector, name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteAllSharedNetworks6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv6::deleteAllSharedNetworks6, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteOptionDef6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteOptionDef6, backend_selector,
+ server_selector, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteAllOptionDefs6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv6::deleteAllOptionDefs6, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteOption6, backend_selector, server_selector,
+ code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&, uint16_t,
+ const std::string&>
+ (&ConfigBackendDHCPv6::deleteOption6, backend_selector, server_selector,
+ shared_network_name, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, const SubnetID&, uint16_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteOption6, backend_selector, server_selector,
+ subnet_id, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, const IOAddress&, const IOAddress&,
+ uint16_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteOption6, backend_selector, server_selector,
+ pool_start_address, pool_end_address, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteOption6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const uint16_t code,
+ const std::string& space) {
+ return (createUpdateDeleteProperty<uint64_t, const IOAddress&, uint8_t,
+ uint16_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteOption6, backend_selector, server_selector,
+ pd_pool_prefix, pd_pool_prefix_length, code, space));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteGlobalParameter6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteGlobalParameter6, backend_selector,
+ server_selector, name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteAllGlobalParameters6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv6::deleteAllGlobalParameters6, backend_selector,
+ server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteClientClass6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& name) {
+ return (createUpdateDeleteProperty<uint64_t, const std::string&>
+ (&ConfigBackendDHCPv6::deleteClientClass6, backend_selector,
+ server_selector, name));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteAllClientClasses6(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector) {
+ return (createUpdateDeleteProperty<uint64_t>
+ (&ConfigBackendDHCPv6::deleteAllClientClasses6, backend_selector, server_selector));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteServer6(const BackendSelector& backend_selector,
+ const ServerTag& server_tag) {
+ return (createUpdateDeleteBackendProperty<uint64_t>
+ (&ConfigBackendDHCPv6::deleteServer6, backend_selector,
+ server_tag));
+}
+
+uint64_t
+ConfigBackendPoolDHCPv6::deleteAllServers6(const BackendSelector& backend_selector) {
+ return (createUpdateDeleteBackendProperty<uint64_t>
+ (&ConfigBackendDHCPv6::deleteAllServers6, backend_selector));
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/config_backend_pool_dhcp6.h b/src/lib/dhcpsrv/config_backend_pool_dhcp6.h
new file mode 100644
index 0000000..c866000
--- /dev/null
+++ b/src/lib/dhcpsrv/config_backend_pool_dhcp6.h
@@ -0,0 +1,653 @@
+// Copyright (C) 2019-2022 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 CONFIG_BACKEND_POOL_DHCP6_H
+#define CONFIG_BACKEND_POOL_DHCP6_H
+
+#include <cc/server_tag.h>
+#include <cc/stamped_value.h>
+#include <config_backend/base_config_backend_pool.h>
+#include <database/backend_selector.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp6.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <boost/date_time/posix_time/ptime.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implementation of the Configuration Backend Pool for DHCPv6.
+///
+/// All POSIX times specified in the methods belonging to this
+/// class must be local times.
+class ConfigBackendPoolDHCPv6 : public cb::BaseConfigBackendPool<ConfigBackendDHCPv6> {
+public:
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) const;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getAllSubnets6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getModifiedSubnets6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// @note: Returning a Subnet6Collection instead of a
+ /// Subnet6SimpleCollection can be considered as overkilling
+ /// but makes this code far simpler.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be retrieved.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getSharedNetworkSubnets6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork6Ptr
+ getSharedNetwork6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getAllSharedNetworks6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getModifiedSharedNetworks6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Option code.
+ /// @param space Option space.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter.
+ virtual data::StampedValuePtr
+ getGlobalParameter6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief Retrieves all servers from the particular backend.
+ ///
+ /// This method returns the list of servers excluding the logical server
+ /// 'all'.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers6(const db::BackendSelector& backend_selector) const;
+
+ /// @brief Retrieves a server from the particular backend.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer6(const db::BackendSelector& backend_selector,
+ const data::ServerTag& server_tag) const;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const Subnet6Ptr& subnet);
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network);
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def);
+
+ /// @brief Creates or updates global option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates prefix delegation pool level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the prefix
+ /// delegation pool to which the option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the prefix
+ /// delegation pool to which the option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates global string parameter.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value);
+
+ /// @brief Creates or updates DHCPv6 client class.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name);
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer6(const db::BackendSelector& backend_selector,
+ const db::ServerPtr& server);
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix);
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id);
+
+ /// @brief Deletes all subnets.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name);
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteSharedNetwork6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes option definition.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes global option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code, const std::string& space);
+
+ /// @brief Deletes pool level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes prefix delegation pool level option.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the prefix
+ /// delegation pool to which the deleted option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the prefix delegation
+ /// pool to which the deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes global parameter.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes DHCPv6 client class.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses6(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer6(const db::BackendSelector& backend_selector,
+ const data::ServerTag& server_tag);
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers6(const db::BackendSelector& backend_selector);
+};
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CONFIG_BACKEND_POOL_DHCP6_H
diff --git a/src/lib/dhcpsrv/csv_lease_file4.cc b/src/lib/dhcpsrv/csv_lease_file4.cc
new file mode 100644
index 0000000..169ed7b
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file4.cc
@@ -0,0 +1,278 @@
+// Copyright (C) 2014-2023 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 <dhcpsrv/csv_lease_file4.h>
+#include <ctime>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+CSVLeaseFile4::CSVLeaseFile4(const std::string& filename)
+ : VersionedCSVFile(filename) {
+ initColumns();
+}
+
+void
+CSVLeaseFile4::open(const bool seek_to_end) {
+ // Call the base class to open the file
+ VersionedCSVFile::open(seek_to_end);
+
+ // and clear any statistics we may have
+ clearStatistics();
+}
+
+void
+CSVLeaseFile4::append(const Lease4& lease) {
+ // Bump the number of write attempts
+ ++writes_;
+
+ CSVRow row(getColumnCount());
+ row.writeAt(getColumnIndex("address"), lease.addr_.toText());
+
+ if (((!lease.hwaddr_) || lease.hwaddr_->hwaddr_.empty()) &&
+ ((!lease.client_id_) || (lease.client_id_->getClientId().empty())) &&
+ (lease.state_ != Lease::STATE_DECLINED)) {
+ // Bump the error counter
+ ++write_errs_;
+
+ isc_throw(BadValue, "Lease4: " << lease.addr_.toText() << ", state: "
+ << Lease::basicStatesToText(lease.state_)
+ << " has neither hardware address or client id");
+
+ }
+
+ // Hardware addr may be unset (NULL).
+ if (lease.hwaddr_) {
+ row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false));
+ }
+
+ // Client id may be unset (NULL).
+ if (lease.client_id_) {
+ row.writeAt(getColumnIndex("client_id"), lease.client_id_->toText());
+ }
+
+ row.writeAt(getColumnIndex("valid_lifetime"), lease.valid_lft_);
+ row.writeAt(getColumnIndex("expire"), static_cast<uint64_t>(lease.cltt_) + lease.valid_lft_);
+ row.writeAt(getColumnIndex("subnet_id"), lease.subnet_id_);
+ row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
+ row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
+ row.writeAtEscaped(getColumnIndex("hostname"), lease.hostname_);
+ row.writeAt(getColumnIndex("state"), lease.state_);
+ // User context is optional.
+ if (lease.getContext()) {
+ row.writeAtEscaped(getColumnIndex("user_context"), lease.getContext()->str());
+ }
+ row.writeAt(getColumnIndex("pool_id"), lease.pool_id_);
+ try {
+ VersionedCSVFile::append(row);
+ } catch (const std::exception&) {
+ // Catch any errors so we can bump the error counter than rethrow it
+ ++write_errs_;
+ throw;
+ }
+
+ // Bump the number of leases written
+ ++write_leases_;
+}
+
+bool
+CSVLeaseFile4::next(Lease4Ptr& lease) {
+ // Bump the number of read attempts
+ ++reads_;
+
+ // Read the CSV row and try to create a lease from the values read.
+ // This may easily result in exception. We don't want this function
+ // to throw exceptions, so we catch them all and rather return the
+ // false value.
+ try {
+ // Get the row of CSV values.
+ CSVRow row;
+ VersionedCSVFile::next(row);
+ // The empty row signals EOF.
+ if (row == CSVFile::EMPTY_ROW()) {
+ lease.reset();
+ return (true);
+ }
+
+ // Get the lease address.
+ IOAddress addr(readAddress(row));
+
+ // Get client id. It is possible that the client id is empty and the
+ // returned pointer is NULL. This is ok, but if the client id is NULL,
+ // we need to be careful to not use the NULL pointer.
+ ClientIdPtr client_id = readClientId(row);
+ std::vector<uint8_t> client_id_vec;
+ if (client_id) {
+ client_id_vec = client_id->getClientId();
+ }
+ size_t client_id_len = client_id_vec.size();
+
+ // Get the HW address. It should never be empty and the readHWAddr checks
+ // that.
+ HWAddr hwaddr = readHWAddr(row);
+ uint32_t state = readState(row);
+
+ if ((hwaddr.hwaddr_.empty()) && (client_id_vec.empty()) &&
+ (state != Lease::STATE_DECLINED)) {
+ isc_throw(BadValue, "Lease4: " << addr.toText() << ", state: "
+ << Lease::basicStatesToText(state)
+ << " has neither hardware address or client id");
+ }
+
+ // Get the user context (can be NULL).
+ ConstElementPtr ctx = readContext(row);
+
+ lease.reset(new Lease4(addr,
+ HWAddrPtr(new HWAddr(hwaddr)),
+ client_id_vec.empty() ? NULL : &client_id_vec[0],
+ client_id_len,
+ readValid(row),
+ readCltt(row),
+ readSubnetID(row),
+ readFqdnFwd(row),
+ readFqdnRev(row),
+ readHostname(row)));
+
+ lease->state_ = state;
+
+ if (ctx) {
+ lease->setContext(ctx);
+ }
+
+ lease->pool_id_ = readPoolID(row);
+ } catch (const std::exception& ex) {
+ // bump the read error count
+ ++read_errs_;
+
+ // The lease might have been created, so let's set it back to NULL to
+ // signal that lease hasn't been parsed.
+ lease.reset();
+ setReadMsg(ex.what());
+ return (false);
+ }
+
+ // bump the number of leases read
+ ++read_leases_;
+
+ return (true);
+}
+
+void
+CSVLeaseFile4::initColumns() {
+ addColumn("address", "1.0");
+ addColumn("hwaddr", "1.0");
+ addColumn("client_id", "1.0");
+ addColumn("valid_lifetime", "1.0");
+ addColumn("expire", "1.0");
+ addColumn("subnet_id", "1.0");
+ addColumn("fqdn_fwd", "1.0");
+ addColumn("fqdn_rev", "1.0");
+ addColumn("hostname", "1.0");
+ addColumn("state", "2.0", "0");
+ addColumn("user_context", "2.1");
+ addColumn("pool_id", "3.0", "0");
+
+ // Any file with less than hostname is invalid
+ setMinimumValidColumns("hostname");
+}
+
+IOAddress
+CSVLeaseFile4::readAddress(const CSVRow& row) {
+ IOAddress address(row.readAt(getColumnIndex("address")));
+ return (address);
+}
+
+HWAddr
+CSVLeaseFile4::readHWAddr(const CSVRow& row) {
+ HWAddr hwaddr = HWAddr::fromText(row.readAt(getColumnIndex("hwaddr")));
+ return (hwaddr);
+}
+
+ClientIdPtr
+CSVLeaseFile4::readClientId(const CSVRow& row) {
+ std::string client_id = row.readAt(getColumnIndex("client_id"));
+ // NULL client ids are allowed in DHCPv4.
+ if (client_id.empty()) {
+ return (ClientIdPtr());
+ }
+ ClientIdPtr cid = ClientId::fromText(client_id);
+ return (cid);
+}
+
+uint32_t
+CSVLeaseFile4::readValid(const CSVRow& row) {
+ uint32_t valid =
+ row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime"));
+ return (valid);
+}
+
+time_t
+CSVLeaseFile4::readCltt(const CSVRow& row) {
+ time_t cltt =
+ static_cast<time_t>(row.readAndConvertAt<uint64_t>(getColumnIndex("expire"))
+ - readValid(row));
+ return (cltt);
+}
+
+SubnetID
+CSVLeaseFile4::readSubnetID(const CSVRow& row) {
+ SubnetID subnet_id =
+ row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id"));
+ return (subnet_id);
+}
+
+uint32_t
+CSVLeaseFile4::readPoolID(const CSVRow& row) {
+ uint32_t pool_id =
+ row.readAndConvertAt<uint32_t>(getColumnIndex("pool_id"));
+ return (pool_id);
+}
+
+bool
+CSVLeaseFile4::readFqdnFwd(const CSVRow& row) {
+ bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd"));
+ return (fqdn_fwd);
+}
+
+bool
+CSVLeaseFile4::readFqdnRev(const CSVRow& row) {
+ bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev"));
+ return (fqdn_rev);
+}
+
+std::string
+CSVLeaseFile4::readHostname(const CSVRow& row) {
+ std::string hostname = row.readAtEscaped(getColumnIndex("hostname"));
+ return (hostname);
+}
+
+uint32_t
+CSVLeaseFile4::readState(const util::CSVRow& row) {
+ uint32_t state = row.readAndConvertAt<uint32_t>(getColumnIndex("state"));
+ return (state);
+}
+
+ConstElementPtr
+CSVLeaseFile4::readContext(const util::CSVRow& row) {
+ std::string user_context = row.readAtEscaped(getColumnIndex("user_context"));
+ if (user_context.empty()) {
+ return (ConstElementPtr());
+ }
+ ConstElementPtr ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(isc::BadValue, "user context '" << user_context
+ << "' is not a JSON map");
+ }
+ return (ctx);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/csv_lease_file4.h b/src/lib/dhcpsrv/csv_lease_file4.h
new file mode 100644
index 0000000..5e5bd4e
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file4.h
@@ -0,0 +1,175 @@
+// Copyright (C) 2014-2023 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 CSV_LEASE_FILE4_H
+#define CSV_LEASE_FILE4_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/lease_file_stats.h>
+#include <util/versioned_csv_file.h>
+#include <stdint.h>
+#include <string>
+#include <time.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Provides methods to access CSV file with DHCPv4 leases.
+///
+/// This class contains methods customized to read and write DHCPv4 leases from
+/// and to the CSV file. It expects that the CSV file being parsed contains a
+/// set of columns with well known names (initialized in the class constructor).
+///
+/// @todo This class doesn't validate the lease values read from the file.
+/// The @c Lease4 is a structure that should be itself responsible for this
+/// validation. However, the @c next function may need to be updated to use the
+/// validation capability of @c Lease4.
+class CSVLeaseFile4 : public isc::util::VersionedCSVFile, public LeaseFileStats {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes columns of the lease file.
+ ///
+ /// @param filename Name of the lease file.
+ CSVLeaseFile4(const std::string& filename);
+
+ /// @brief Opens a lease file.
+ ///
+ /// This function calls the base class open to do the
+ /// work of opening a file. It is used to clear any
+ /// statistics associated with any previous use of the file
+ /// While it doesn't throw any exceptions of its own
+ /// the base class may do so.
+ virtual void open(const bool seek_to_end = false);
+
+ /// @brief Appends the lease record to the CSV file.
+ ///
+ /// This function doesn't throw exceptions itself. In theory, exceptions
+ /// are possible when the index of the indexes of the values being written
+ /// to the file are invalid. However, this would have been a programming
+ /// error.
+ ///
+ /// @param lease Structure representing a DHCPv4 lease.
+ /// @throw BadValue if the lease has no hardware address, no client id and
+ /// is not in STATE_DECLINED.
+ void append(const Lease4& lease);
+
+ /// @brief Reads next lease from the CSV file.
+ ///
+ /// If this function hits an error during lease read, it sets the error
+ /// message using @c CSVFile::setReadMsg and returns false. The error
+ /// string may be read using @c CSVFile::getReadMsg.
+ ///
+ /// Treats rows without a hardware address or a client id when their
+ /// state is not STATE_DECLINED as an error.
+ ///
+ /// This function is exception safe.
+ ///
+ /// @param [out] lease Pointer to the lease read from CSV file or
+ /// NULL pointer if lease hasn't been read.
+ ///
+ /// @return Boolean value indicating that the new lease has been
+ /// read from the CSV file (if true), or that the error has occurred
+ /// (false).
+ ///
+ /// @todo Make sure that the values read from the file are correct.
+ /// The appropriate @c Lease4 validation mechanism should be used.
+ bool next(Lease4Ptr& lease);
+
+private:
+
+ /// @brief Initializes columns of the CSV file holding leases.
+ ///
+ /// This function initializes the following columns:
+ /// - address
+ /// - hwaddr
+ /// - client_id
+ /// - valid_lifetime
+ /// - expire
+ /// - subnet_id
+ /// - fqdn_fwd
+ /// - fqdn_rev
+ /// - hostname
+ /// - state
+ /// - user_context
+ /// - pool_id
+ void initColumns();
+
+ ///
+ /// @name Methods which read specific lease fields from the CSV row.
+ ///
+ //@{
+ ///
+ /// @brief Reads lease address from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ asiolink::IOAddress readAddress(const util::CSVRow& row);
+
+ /// @brief Reads HW address from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ HWAddr readHWAddr(const util::CSVRow& row);
+
+ /// @brief Reads client identifier from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ ClientIdPtr readClientId(const util::CSVRow& row);
+
+ /// @brief Reads valid lifetime from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readValid(const util::CSVRow& row);
+
+ /// @brief Reads cltt value from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ time_t readCltt(const util::CSVRow& row);
+
+ /// @brief Reads subnet id from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ SubnetID readSubnetID(const util::CSVRow& row);
+
+ /// @brief Reads pool id from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readPoolID(const util::CSVRow& row);
+
+ /// @brief Reads the FQDN forward flag from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ bool readFqdnFwd(const util::CSVRow& row);
+
+ /// @brief Reads the FQDN reverse flag from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ bool readFqdnRev(const util::CSVRow& row);
+
+ /// @brief Reads hostname from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ std::string readHostname(const util::CSVRow& row);
+
+ /// @brief Reads lease state from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readState(const util::CSVRow& row);
+
+ /// @brief Reads lease user context from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ data::ConstElementPtr readContext(const util::CSVRow& row);
+ //@}
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CSV_LEASE_FILE4_H
diff --git a/src/lib/dhcpsrv/csv_lease_file6.cc b/src/lib/dhcpsrv/csv_lease_file6.cc
new file mode 100644
index 0000000..22608cb
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file6.cc
@@ -0,0 +1,333 @@
+// Copyright (C) 2014-2023 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 <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/csv_lease_file6.h>
+
+#include <ctime>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+CSVLeaseFile6::CSVLeaseFile6(const std::string& filename)
+ : VersionedCSVFile(filename) {
+ initColumns();
+}
+
+void
+CSVLeaseFile6::open(const bool seek_to_end) {
+ // Call the base class to open the file
+ VersionedCSVFile::open(seek_to_end);
+
+ // and clear any statistics we may have
+ clearStatistics();
+}
+
+void
+CSVLeaseFile6::append(const Lease6& lease) {
+ // Bump the number of write attempts
+ ++writes_;
+
+ if (((!(lease.duid_)) || (*(lease.duid_) == DUID::EMPTY())) &&
+ (lease.state_ != Lease::STATE_DECLINED)) {
+ ++write_errs_;
+ isc_throw(BadValue, "Lease6: " << lease.addr_.toText() << ", state: "
+ << Lease::basicStatesToText(lease.state_) << ", has no DUID");
+ }
+
+ CSVRow row(getColumnCount());
+ row.writeAt(getColumnIndex("address"), lease.addr_.toText());
+ row.writeAt(getColumnIndex("duid"), lease.duid_->toText());
+ row.writeAt(getColumnIndex("valid_lifetime"), lease.valid_lft_);
+ row.writeAt(getColumnIndex("expire"), static_cast<uint64_t>(lease.cltt_) + lease.valid_lft_);
+ row.writeAt(getColumnIndex("subnet_id"), lease.subnet_id_);
+ row.writeAt(getColumnIndex("pref_lifetime"), lease.preferred_lft_);
+ row.writeAt(getColumnIndex("lease_type"), lease.type_);
+ row.writeAt(getColumnIndex("iaid"), lease.iaid_);
+ row.writeAt(getColumnIndex("prefix_len"),
+ static_cast<int>(lease.prefixlen_));
+ row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
+ row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
+ row.writeAtEscaped(getColumnIndex("hostname"), lease.hostname_);
+ // We may not have hardware information.
+ if (lease.hwaddr_) {
+ row.writeAt(getColumnIndex("hwaddr"), lease.hwaddr_->toText(false));
+ row.writeAt(getColumnIndex("hwtype"), lease.hwaddr_->htype_);
+ row.writeAt(getColumnIndex("hwaddr_source"), lease.hwaddr_->source_);
+ }
+ row.writeAt(getColumnIndex("state"), lease.state_);
+ // User context is optional.
+ if (lease.getContext()) {
+ row.writeAtEscaped(getColumnIndex("user_context"), lease.getContext()->str());
+ }
+ row.writeAt(getColumnIndex("pool_id"), lease.pool_id_);
+ try {
+ VersionedCSVFile::append(row);
+ } catch (const std::exception&) {
+ // Catch any errors so we can bump the error counter than rethrow it
+ ++write_errs_;
+ throw;
+ }
+
+ // Bump the number of leases written
+ ++write_leases_;
+}
+
+bool
+CSVLeaseFile6::next(Lease6Ptr& lease) {
+ // Bump the number of read attempts
+ ++reads_;
+
+ // Read the CSV row and try to create a lease from the values read.
+ // This may easily result in exception. We don't want this function
+ // to throw exceptions, so we catch them all and rather return the
+ // false value.
+ try {
+ // Get the row of CSV values.
+ CSVRow row;
+ VersionedCSVFile::next(row);
+ // The empty row signals EOF.
+ if (row == CSVFile::EMPTY_ROW()) {
+ lease.reset();
+ return (true);
+ }
+
+ Lease::Type type = readType(row);
+ uint8_t prefixlen = 128;
+ if (type == Lease::TYPE_PD) {
+ prefixlen = readPrefixLen(row);
+ }
+
+ lease.reset(new Lease6(type, readAddress(row), readDUID(row),
+ readIAID(row), readPreferred(row),
+ readValid(row),
+ readSubnetID(row),
+ readHWAddr(row),
+ prefixlen));
+
+ lease->cltt_ = readCltt(row);
+ lease->fqdn_fwd_ = readFqdnFwd(row);
+ lease->fqdn_rev_ = readFqdnRev(row);
+ lease->hostname_ = readHostname(row);
+ lease->state_ = readState(row);
+
+ if ((*lease->duid_ == DUID::EMPTY())
+ && lease->state_ != Lease::STATE_DECLINED) {
+ isc_throw(isc::BadValue,
+ "The Empty DUID is only valid for declined leases");
+ }
+
+ ConstElementPtr ctx = readContext(row);
+ if (ctx) {
+ lease->setContext(ctx);
+ }
+
+ lease->pool_id_ = readPoolID(row);
+ } catch (const std::exception& ex) {
+ // bump the read error count
+ ++read_errs_;
+
+ // The lease might have been created, so let's set it back to NULL to
+ // signal that lease hasn't been parsed.
+ lease.reset();
+ setReadMsg(ex.what());
+ return (false);
+ }
+
+ // bump the number of leases read
+ ++read_leases_;
+
+ return (true);
+}
+
+void
+CSVLeaseFile6::initColumns() {
+ addColumn("address", "1.0");
+ addColumn("duid", "1.0");
+ addColumn("valid_lifetime", "1.0");
+ addColumn("expire", "1.0");
+ addColumn("subnet_id", "1.0");
+ addColumn("pref_lifetime", "1.0");
+ addColumn("lease_type", "1.0");
+ addColumn("iaid", "1.0");
+ addColumn("prefix_len", "1.0");
+ addColumn("fqdn_fwd", "1.0");
+ addColumn("fqdn_rev", "1.0");
+ addColumn("hostname", "1.0");
+ addColumn("hwaddr", "2.0");
+ addColumn("state", "3.0", "0" /* == STATE_DEFAULT */);
+ addColumn("user_context", "3.1");
+ // Default not added for hwtype and hwaddr_source, because they depend on
+ // hwaddr having value. When a CSV lease having a hwaddr is upgraded to 4.0,
+ // hwtype will have value "1" meaning HTYPE_ETHER and
+ // hwaddr_source will have value "0" meaning HWADDR_SOURCE_UNKNOWN.
+ addColumn("hwtype", "4.0");
+ addColumn("hwaddr_source", "4.0");
+ addColumn("pool_id", "5.0", "0");
+
+ // Any file with less than hostname is invalid
+ setMinimumValidColumns("hostname");
+}
+
+Lease::Type
+CSVLeaseFile6::readType(const CSVRow& row) {
+ return (static_cast<Lease::Type>
+ (row.readAndConvertAt<int>(getColumnIndex("lease_type"))));
+}
+
+IOAddress
+CSVLeaseFile6::readAddress(const CSVRow& row) {
+ IOAddress address(row.readAt(getColumnIndex("address")));
+ return (address);
+}
+
+DuidPtr
+CSVLeaseFile6::readDUID(const util::CSVRow& row) {
+ DuidPtr duid(new DUID(DUID::fromText(row.readAt(getColumnIndex("duid")))));
+ return (duid);
+}
+
+uint32_t
+CSVLeaseFile6::readIAID(const CSVRow& row) {
+ uint32_t iaid = row.readAndConvertAt<uint32_t>(getColumnIndex("iaid"));
+ return (iaid);
+}
+
+uint32_t
+CSVLeaseFile6::readPreferred(const CSVRow& row) {
+ uint32_t pref =
+ row.readAndConvertAt<uint32_t>(getColumnIndex("pref_lifetime"));
+ return (pref);
+}
+
+uint32_t
+CSVLeaseFile6::readValid(const CSVRow& row) {
+ uint32_t valid =
+ row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime"));
+ return (valid);
+}
+
+uint32_t
+CSVLeaseFile6::readCltt(const CSVRow& row) {
+ time_t cltt =
+ static_cast<time_t>(row.readAndConvertAt<uint64_t>(getColumnIndex("expire"))
+ - readValid(row));
+ return (cltt);
+}
+
+SubnetID
+CSVLeaseFile6::readSubnetID(const CSVRow& row) {
+ SubnetID subnet_id =
+ row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id"));
+ return (subnet_id);
+}
+
+uint32_t
+CSVLeaseFile6::readPoolID(const CSVRow& row) {
+ uint32_t pool_id =
+ row.readAndConvertAt<uint32_t>(getColumnIndex("pool_id"));
+ return (pool_id);
+}
+
+uint8_t
+CSVLeaseFile6::readPrefixLen(const CSVRow& row) {
+ int prefixlen = row.readAndConvertAt<int>(getColumnIndex("prefix_len"));
+ return (static_cast<uint8_t>(prefixlen));
+}
+
+bool
+CSVLeaseFile6::readFqdnFwd(const CSVRow& row) {
+ bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd"));
+ return (fqdn_fwd);
+}
+
+bool
+CSVLeaseFile6::readFqdnRev(const CSVRow& row) {
+ bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev"));
+ return (fqdn_rev);
+}
+
+std::string
+CSVLeaseFile6::readHostname(const CSVRow& row) {
+ std::string hostname = row.readAtEscaped(getColumnIndex("hostname"));
+ return (hostname);
+}
+
+HWAddrPtr
+CSVLeaseFile6::readHWAddr(const CSVRow& row) {
+
+ try {
+ uint16_t const hwtype(readHWType(row).valueOr(HTYPE_ETHER));
+ HWAddr hwaddr(
+ HWAddr::fromText(row.readAt(getColumnIndex("hwaddr")), hwtype));
+ if (hwaddr.hwaddr_.empty()) {
+ return (HWAddrPtr());
+ }
+ hwaddr.source_ =
+ readHWAddrSource(row).valueOr(HWAddr::HWADDR_SOURCE_UNKNOWN);
+
+ /// @todo: HWAddr returns an object, not a pointer. Without HWAddr
+ /// refactoring, at least one copy is unavoidable.
+
+ // Let's return a pointer to new freshly created copy.
+ return (HWAddrPtr(new HWAddr(hwaddr)));
+
+ } catch (const std::exception& ex) {
+ // That's worse. There was something in the file, but its conversion
+ // to HWAddr failed. Let's log it on warning and carry on.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_READ_HWADDR_FAIL)
+ .arg(ex.what());
+
+ return (HWAddrPtr());
+ }
+}
+
+uint32_t
+CSVLeaseFile6::readState(const util::CSVRow& row) {
+ uint32_t state = row.readAndConvertAt<uint32_t>(getColumnIndex("state"));
+ return (state);
+}
+
+ConstElementPtr
+CSVLeaseFile6::readContext(const util::CSVRow& row) {
+ std::string user_context = row.readAtEscaped(getColumnIndex("user_context"));
+ if (user_context.empty()) {
+ return (ConstElementPtr());
+ }
+ ConstElementPtr ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(isc::BadValue, "user context '" << user_context
+ << "' is not a JSON map");
+ }
+ return (ctx);
+}
+
+Optional<uint16_t>
+CSVLeaseFile6::readHWType(const CSVRow& row) {
+ size_t const index(getColumnIndex("hwtype"));
+ if (row.readAt(index).empty()) {
+ return Optional<uint16_t>();
+ }
+ return row.readAndConvertAt<uint16_t>(index);
+}
+
+Optional<uint32_t>
+CSVLeaseFile6::readHWAddrSource(const CSVRow& row) {
+ size_t const index(getColumnIndex("hwaddr_source"));
+ if (row.readAt(index).empty()) {
+ return Optional<uint16_t>();
+ }
+ return row.readAndConvertAt<uint32_t>(index);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/csv_lease_file6.h b/src/lib/dhcpsrv/csv_lease_file6.h
new file mode 100644
index 0000000..26fa55e
--- /dev/null
+++ b/src/lib/dhcpsrv/csv_lease_file6.h
@@ -0,0 +1,217 @@
+// Copyright (C) 2014-2023 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 CSV_LEASE_FILE6_H
+#define CSV_LEASE_FILE6_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/lease_file_stats.h>
+#include <util/optional.h>
+#include <util/versioned_csv_file.h>
+
+#include <stdint.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Provides methods to access CSV file with DHCPv6 leases.
+///
+/// This class contains methods customized to read and write DHCPv6 leases from
+/// and to the CSV file. It expects that the CSV file being parsed contains a
+/// set of columns with well known names (initialized in the class constructor).
+///
+/// @todo This class doesn't validate the lease values read from the file.
+/// The @c Lease6 is a structure that should be itself responsible for this
+/// validation. However, the @c next function may need to be updated to use the
+/// validation capability of @c Lease6.
+class CSVLeaseFile6 : public isc::util::VersionedCSVFile, public LeaseFileStats {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes columns of the lease file.
+ ///
+ /// @param filename Name of the lease file.
+ CSVLeaseFile6(const std::string& filename);
+
+ /// @brief Opens a lease file.
+ ///
+ /// This function calls the base class open to do the
+ /// work of opening a file. It is used to clear any
+ /// statistics associated with any previous use of the file
+ /// While it doesn't throw any exceptions of its own
+ /// the base class may do so.
+ virtual void open(const bool seek_to_end = false);
+
+ /// @brief Appends the lease record to the CSV file.
+ ///
+ /// This function doesn't throw exceptions itself. In theory, exceptions
+ /// are possible when the index of the indexes of the values being written
+ /// to the file are invalid. However, this would have been a programming
+ /// error.
+ ///
+ /// @param lease Structure representing a DHCPv6 lease.
+ /// @throw BadValue if the lease to be written has an empty DUID and is
+ /// whose state is not STATE_DECLINED.
+ void append(const Lease6& lease);
+
+ /// @brief Reads next lease from the CSV file.
+ ///
+ /// If this function hits an error during lease read, it sets the error
+ /// message using @c CSVFile::setReadMsg and returns false. The error
+ /// string may be read using @c CSVFile::getReadMsg.
+ ///
+ /// This function is exception safe.
+ ///
+ /// @param [out] lease Pointer to the lease read from CSV file or
+ /// NULL pointer if lease hasn't been read.
+ ///
+ /// @return Boolean value indicating that the new lease has been
+ /// read from the CSV file (if true), or that the error has occurred
+ /// (false).
+ ///
+ /// @todo Make sure that the values read from the file are correct.
+ /// The appropriate @c Lease6 validation mechanism should be used.
+ bool next(Lease6Ptr& lease);
+
+private:
+
+ /// @brief Initializes columns of the CSV file holding leases.
+ ///
+ /// This function initializes the following columns:
+ /// - address
+ /// - duid
+ /// - valid_lifetime
+ /// - expire
+ /// - subnet_id
+ /// - pref_lifetime
+ /// - lease_type
+ /// - iaid
+ /// - prefix_len
+ /// - fqdn_fwd
+ /// - fqdn_rev
+ /// - hostname
+ /// - hwaddr
+ /// - state
+ /// - user_context
+ /// - hwtype
+ /// - hwaddr_source
+ /// - pool_id
+ void initColumns();
+
+ ///
+ /// @name Methods which read specific lease fields from the CSV row.
+ ///
+ //@{
+ ///
+ /// @brief Reads lease type from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ Lease::Type readType(const util::CSVRow& row);
+
+ /// @brief Reads lease address from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ asiolink::IOAddress readAddress(const util::CSVRow& row);
+
+ /// @brief Reads DUID from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ DuidPtr readDUID(const util::CSVRow& row);
+
+ /// @brief Reads IAID from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readIAID(const util::CSVRow& row);
+
+ /// @brief Reads preferred lifetime from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readPreferred(const util::CSVRow& row);
+
+ /// @brief Reads valid lifetime from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readValid(const util::CSVRow& row);
+
+ /// @brief Reads cltt value from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readCltt(const util::CSVRow& row);
+
+ /// @brief Reads subnet id from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ SubnetID readSubnetID(const util::CSVRow& row);
+
+ /// @brief Reads pool id from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readPoolID(const util::CSVRow& row);
+
+ /// @brief Reads prefix length from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint8_t readPrefixLen(const util::CSVRow& row);
+
+ /// @brief Reads the FQDN forward flag from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ bool readFqdnFwd(const util::CSVRow& row);
+
+ /// @brief Reads the FQDN reverse flag from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ bool readFqdnRev(const util::CSVRow& row);
+
+ /// @brief Reads hostname from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ std::string readHostname(const util::CSVRow& row);
+
+ /// @brief Reads HW address from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ /// @return pointer to the HWAddr structure that was read
+ HWAddrPtr readHWAddr(const util::CSVRow& row);
+
+ /// @brief Reads lease state from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ uint32_t readState(const util::CSVRow& row);
+
+ /// @brief Reads lease user context from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information.
+ data::ConstElementPtr readContext(const util::CSVRow& row);
+
+ /// @brief Reads hardware address type from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information
+ ///
+ /// @return the integer value of the hardware address type that was read
+ /// or an unspecified Optional if it is not specified in the CSV
+ isc::util::Optional<uint16_t> readHWType(const util::CSVRow& row);
+
+ /// @brief Reads hardware address source from the CSV file row.
+ ///
+ /// @param row CSV file row holding lease information
+ ///
+ /// @return the integer value of the hardware address source that was read
+ /// or an unspecified Optional if it is not specified in the CSV
+ isc::util::Optional<uint32_t> readHWAddrSource(const util::CSVRow& row);
+ //@}
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CSV_LEASE_FILE6_H
diff --git a/src/lib/dhcpsrv/d2_client_cfg.cc b/src/lib/dhcpsrv/d2_client_cfg.cc
new file mode 100644
index 0000000..78d7ffd
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_cfg.cc
@@ -0,0 +1,222 @@
+// Copyright (C) 2013-2020 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 <dhcp_ddns/ncr_udp.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <string>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+/// These values need to match those used in D2ClientConfigParser::SimpleDefaults
+const char* D2ClientConfig::DFT_SERVER_IP = "127.0.0.1";
+const size_t D2ClientConfig::DFT_SERVER_PORT = 53001;
+const char* D2ClientConfig::DFT_V4_SENDER_IP = "0.0.0.0";
+const char* D2ClientConfig::DFT_V6_SENDER_IP = "::";
+const size_t D2ClientConfig::DFT_SENDER_PORT = 0;
+const size_t D2ClientConfig::DFT_MAX_QUEUE_SIZE = 1024;
+const char* D2ClientConfig::DFT_NCR_PROTOCOL = "UDP";
+const char* D2ClientConfig::DFT_NCR_FORMAT = "JSON";
+const bool D2ClientConfig::DFT_OVERRIDE_NO_UPDATE = false;
+const bool D2ClientConfig::DFT_OVERRIDE_CLIENT_UPDATE = false;
+const char* D2ClientConfig::DFT_REPLACE_CLIENT_NAME_MODE = "NEVER";
+const char* D2ClientConfig::DFT_GENERATED_PREFIX = "myhost";
+const char* D2ClientConfig::DFT_HOSTNAME_CHAR_SET = "";
+const char* D2ClientConfig::DFT_HOSTNAME_CHAR_REPLACEMENT = "";
+
+D2ClientConfig::ReplaceClientNameMode
+D2ClientConfig::stringToReplaceClientNameMode(const std::string& mode_str) {
+ if (mode_str == "never") {
+ return (D2ClientConfig::RCM_NEVER);
+ }
+
+ if (mode_str == "always") {
+ return (D2ClientConfig::RCM_ALWAYS);
+ }
+
+ if (mode_str == "when-present") {
+ return (D2ClientConfig::RCM_WHEN_PRESENT);
+ }
+
+ if (mode_str == "when-not-present") {
+ return (D2ClientConfig::RCM_WHEN_NOT_PRESENT);
+ }
+
+ isc_throw(BadValue,
+ "Invalid ReplaceClientNameMode: " << mode_str);
+}
+
+std::string
+D2ClientConfig::replaceClientNameModeToString(const ReplaceClientNameMode& mode) {
+ switch (mode) {
+ case D2ClientConfig::RCM_NEVER:
+ return ("never");
+ case D2ClientConfig::RCM_ALWAYS:
+ return ("always");
+ case D2ClientConfig::RCM_WHEN_PRESENT:
+ return ("when-present");
+ case D2ClientConfig::RCM_WHEN_NOT_PRESENT:
+ return ("when-not-present");
+ default:
+ break;
+ }
+
+ std::ostringstream stream;
+ stream << "unknown(" << mode << ")";
+ return (stream.str());
+}
+
+D2ClientConfig::D2ClientConfig(const bool enable_updates,
+ const isc::asiolink::IOAddress& server_ip,
+ const size_t server_port,
+ const isc::asiolink::IOAddress& sender_ip,
+ const size_t sender_port,
+ const size_t max_queue_size,
+ const dhcp_ddns::
+ NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::
+ NameChangeFormat& ncr_format)
+ : enable_updates_(enable_updates),
+ server_ip_(server_ip),
+ server_port_(server_port),
+ sender_ip_(sender_ip),
+ sender_port_(sender_port),
+ max_queue_size_(max_queue_size),
+ ncr_protocol_(ncr_protocol),
+ ncr_format_(ncr_format) {
+ validateContents();
+}
+
+D2ClientConfig::D2ClientConfig()
+ : enable_updates_(false),
+ server_ip_(isc::asiolink::IOAddress(DFT_SERVER_IP)),
+ server_port_(DFT_SERVER_PORT),
+ sender_ip_(isc::asiolink::IOAddress(DFT_V4_SENDER_IP)),
+ sender_port_(DFT_SENDER_PORT),
+ max_queue_size_(DFT_MAX_QUEUE_SIZE),
+ ncr_protocol_(dhcp_ddns::stringToNcrProtocol(DFT_NCR_PROTOCOL)),
+ ncr_format_(dhcp_ddns::stringToNcrFormat(DFT_NCR_FORMAT)) {
+ validateContents();
+}
+
+D2ClientConfig::~D2ClientConfig(){};
+
+void
+D2ClientConfig::enableUpdates(bool enable) {
+ enable_updates_ = enable;
+}
+
+void
+D2ClientConfig::validateContents() {
+ if (ncr_format_ != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2ClientError, "D2ClientConfig: NCR Format: "
+ << dhcp_ddns::ncrFormatToString(ncr_format_)
+ << " is not yet supported");
+ }
+
+ if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol: "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+ << " is not yet supported");
+ }
+
+ if (sender_ip_.getFamily() != server_ip_.getFamily()) {
+ isc_throw(D2ClientError, "D2ClientConfig: address family mismatch: "
+ << "server-ip: " << server_ip_.toText()
+ << " is: " << (server_ip_.isV4() ? "IPv4" : "IPv6")
+ << " while sender-ip: " << sender_ip_.toText()
+ << " is: " << (sender_ip_.isV4() ? "IPv4" : "IPv6"));
+ }
+
+ if (server_ip_ == sender_ip_ && server_port_ == sender_port_) {
+ isc_throw(D2ClientError, "D2ClientConfig: server and sender cannot"
+ " share the exact same IP address/port: "
+ << server_ip_.toText() << "/" << server_port_);
+ }
+
+ /// @todo perhaps more validation we should do yet?
+ /// Are there any invalid combinations of options we need to test against?
+}
+
+bool
+D2ClientConfig::operator == (const D2ClientConfig& other) const {
+ return ((enable_updates_ == other.enable_updates_) &&
+ (server_ip_ == other.server_ip_) &&
+ (server_port_ == other.server_port_) &&
+ (sender_ip_ == other.sender_ip_) &&
+ (sender_port_ == other.sender_port_) &&
+ (max_queue_size_ == other.max_queue_size_) &&
+ (ncr_protocol_ == other.ncr_protocol_) &&
+ (ncr_format_ == other.ncr_format_));
+}
+
+bool
+D2ClientConfig::operator != (const D2ClientConfig& other) const {
+ return (!(*this == other));
+}
+
+std::string
+D2ClientConfig::toText() const {
+ std::ostringstream stream;
+
+ stream << "enable_updates: " << (enable_updates_ ? "yes" : "no");
+ if (enable_updates_) {
+ stream << ", server-ip: " << server_ip_.toText()
+ << ", server-port: " << server_port_
+ << ", sender-ip: " << sender_ip_.toText()
+ << ", sender-port: " << sender_port_
+ << ", max-queue-size: " << max_queue_size_
+ << ", ncr-protocol: " << ncrProtocolToString(ncr_protocol_)
+ << ", ncr-format: " << ncrFormatToString(ncr_format_);
+ }
+
+
+ return (stream.str());
+}
+
+ElementPtr
+D2ClientConfig::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user context
+ contextToElement(result);
+ // Set enable-updates
+ result->set("enable-updates", Element::create(enable_updates_));
+ // Set server-ip
+ result->set("server-ip", Element::create(server_ip_.toText()));
+ // Set server-port
+ result->set("server-port", Element::create(static_cast<long long>(server_port_)));
+ // Set sender-ip
+ result->set("sender-ip", Element::create(sender_ip_.toText()));
+ // Set sender-port
+ result->set("sender-port", Element::create(static_cast<long long>(sender_port_)));
+ // Set max-queue-size
+ result->set("max-queue-size", Element::create(static_cast<long long>(max_queue_size_)));
+ // Set ncr-protocol
+ result->set("ncr-protocol", Element::create(dhcp_ddns::ncrProtocolToString(ncr_protocol_)));
+ // Set ncr-format
+ result->set("ncr-format", Element::create(dhcp_ddns::ncrFormatToString(ncr_format_)));
+ // Set override-no-update
+ return (result);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config) {
+ os << config.toText();
+ return (os);
+}
+
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/d2_client_cfg.h b/src/lib/dhcpsrv/d2_client_cfg.h
new file mode 100644
index 0000000..da068fb
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_cfg.h
@@ -0,0 +1,241 @@
+// Copyright (C) 2013-2022 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 D2_CLIENT_CFG_H
+#define D2_CLIENT_CFG_H
+
+/// @file d2_client_cfg.h Defines the D2ClientConfig class.
+/// This file defines the classes Kea uses to manage configuration needed to
+/// act as a client of the kea-dhcp-ddns module (aka D2).
+///
+
+#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <dhcpsrv/cfg_globals.h>
+#include <exceptions/exceptions.h>
+#include <util/optional.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if an error occurs while configuring
+/// the D2 DHCP DDNS client.
+class D2ClientError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ D2ClientError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Callback function for @c D2ClientConfig that retrieves globally
+/// configured parameters.
+typedef std::function<ConstCfgGlobalsPtr()> FetchNetworkGlobalsFn;
+
+
+/// @brief Acts as a storage vault for D2 client configuration
+///
+/// A simple container class for storing and retrieving the configuration
+/// parameters associated with DHCP-DDNS and acting as a client of D2.
+/// Instances of this class may be constructed through configuration parsing.
+///
+class D2ClientConfig : public data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @brief Default configuration constants.
+ static const char* DFT_SERVER_IP;
+ static const size_t DFT_SERVER_PORT;
+ static const char* DFT_V4_SENDER_IP;
+ static const char* DFT_V6_SENDER_IP;
+ static const size_t DFT_SENDER_PORT;
+ static const size_t DFT_MAX_QUEUE_SIZE;
+ static const char* DFT_NCR_PROTOCOL;
+ static const char* DFT_NCR_FORMAT;
+ static const bool DFT_OVERRIDE_NO_UPDATE;
+ static const bool DFT_OVERRIDE_CLIENT_UPDATE;
+ static const char* DFT_REPLACE_CLIENT_NAME_MODE;
+ static const char* DFT_GENERATED_PREFIX;
+ static const char* DFT_HOSTNAME_CHAR_SET;
+ static const char* DFT_HOSTNAME_CHAR_REPLACEMENT;
+
+ /// @brief Defines the client name replacement modes.
+ enum ReplaceClientNameMode {
+ RCM_NEVER,
+ RCM_ALWAYS,
+ RCM_WHEN_PRESENT,
+ RCM_WHEN_NOT_PRESENT
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param enable_updates Enables DHCP-DDNS updates
+ /// @param server_ip IP address of the kea-dhcp-ddns server (IPv4 or IPv6)
+ /// @param server_port IP port of the kea-dhcp-ddns server
+ /// @param sender_ip IP address of the kea-dhcp-ddns server (IPv4 or IPv6)
+ /// @param sender_port IP port of the kea-dhcp-ddns server
+ /// @param max_queue_size maximum NCRs allowed in sender's queue
+ /// @param ncr_protocol Socket protocol to use with kea-dhcp-ddns
+ /// Currently only UDP is supported.
+ /// @param ncr_format Format of the kea-dhcp-ddns requests.
+ /// Currently only JSON format is supported.
+ /// @c enable_updates is mandatory, other parameters are optional.
+ ///
+ /// @throw D2ClientError if given an invalid protocol or format.
+ D2ClientConfig(const bool enable_updates,
+ const isc::asiolink::IOAddress& server_ip,
+ const size_t server_port,
+ const isc::asiolink::IOAddress& sender_ip,
+ const size_t sender_port,
+ const size_t max_queue_size,
+ const dhcp_ddns::NameChangeProtocol& ncr_protocol,
+ const dhcp_ddns::NameChangeFormat& ncr_format);
+
+ /// @brief Default constructor
+ /// The default constructor creates an instance that has updates disabled.
+ D2ClientConfig();
+
+ /// @brief Destructor
+ virtual ~D2ClientConfig();
+
+ /// @brief Return whether or not DHCP-DDNS updating is enabled.
+ bool getEnableUpdates() const {
+ return(enable_updates_);
+ }
+
+ /// @brief Return the IP address of kea-dhcp-ddns (IPv4 or IPv6).
+ const isc::asiolink::IOAddress& getServerIp() const {
+ return(server_ip_);
+ }
+
+ /// @brief Return the IP port of kea-dhcp-ddns.
+ size_t getServerPort() const {
+ return(server_port_);
+ }
+
+ /// @brief Return the IP address client should use to send
+ const isc::asiolink::IOAddress& getSenderIp() const {
+ return(sender_ip_);
+ }
+
+ /// @brief Return the IP port client should use to send
+ size_t getSenderPort() const {
+ return(sender_port_);
+ }
+
+ /// @brief Return Maximum sender queue size
+ size_t getMaxQueueSize() const {
+ return(max_queue_size_);
+ }
+
+ /// @brief Return the socket protocol to use with kea-dhcp-ddns.
+ const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
+ return(ncr_protocol_);
+ }
+
+ /// @brief Return the kea-dhcp-ddns request format.
+ const dhcp_ddns::NameChangeFormat& getNcrFormat() const {
+ return(ncr_format_);
+ }
+
+ /// @brief Compares two D2ClientConfigs for equality
+ bool operator == (const D2ClientConfig& other) const;
+
+ /// @brief Compares two D2ClientConfigs for inequality
+ bool operator != (const D2ClientConfig& other) const;
+
+ /// @brief Generates a string representation of the class contents.
+ std::string toText() const;
+
+ /// @brief Sets enable-updates flag to the given value.
+ ///
+ /// This is the only value that may be altered outside the constructor
+ /// as it may be desirable to toggle it off and on when dealing with
+ /// D2 IO errors.
+ ///
+ /// @param enable boolean value to assign to the enable-updates flag
+ void enableUpdates(bool enable);
+
+ /// @brief Converts labels to ReplaceClientNameMode enum values.
+ ///
+ /// @param mode_str text to convert to an enum.
+ /// Valid string values: "never", "always", "when-present",
+ /// "when-not-present" (case-insensitive)
+ ///
+ /// @return NameChangeFormat value which maps to the given string.
+ ///
+ /// @throw isc::BadValue if given a string value which does not map to an
+ /// enum value.
+ static ReplaceClientNameMode stringToReplaceClientNameMode(const std::string& mode_str);
+
+ /// @brief Converts NameChangeFormat enums to text labels.
+ ///
+ /// @param mode enum value to convert to label
+ ///
+ /// @return std:string containing the text label if the value is valid, or
+ /// "unknown" if not.
+ static std::string replaceClientNameModeToString(const ReplaceClientNameMode& mode);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Validates member values.
+ ///
+ /// Method is used by the constructor to validate member contents.
+ ///
+ /// @throw D2ClientError if given an invalid protocol or format.
+ virtual void validateContents();
+
+private:
+ /// @brief Indicates whether or not DHCP DDNS updating is enabled.
+ bool enable_updates_;
+
+ /// @brief IP address of the kea-dhcp-ddns server (IPv4 or IPv6).
+ isc::asiolink::IOAddress server_ip_;
+
+ /// @brief IP port of the kea-dhcp-ddns server.
+ size_t server_port_;
+
+ /// @brief IP address on which the client should send
+ isc::asiolink::IOAddress sender_ip_;
+
+ /// @brief IP port on which the client should send
+ size_t sender_port_;
+
+ /// @brief Maximum number of NCRs allowed to queue waiting to send
+ size_t max_queue_size_;
+
+ /// @brief The socket protocol to use with kea-dhcp-ddns.
+ /// Currently only UDP is supported.
+ dhcp_ddns::NameChangeProtocol ncr_protocol_;
+
+ /// @brief Format of the kea-dhcp-ddns requests.
+ /// Currently only JSON format is supported.
+ dhcp_ddns::NameChangeFormat ncr_format_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const D2ClientConfig& config);
+
+/// @brief Defines a pointer for D2ClientConfig instances.
+typedef boost::shared_ptr<D2ClientConfig> D2ClientConfigPtr;
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/lib/dhcpsrv/d2_client_mgr.cc b/src/lib/dhcpsrv/d2_client_mgr.cc
new file mode 100644
index 0000000..807f728
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_mgr.cc
@@ -0,0 +1,423 @@
+// Copyright (C) 2014-2021 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 <dhcp/iface_mgr.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <functional>
+#include <string>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+D2ClientMgr::D2ClientMgr() : d2_client_config_(new D2ClientConfig()),
+ name_change_sender_(), private_io_service_(),
+ registered_select_fd_(util::WatchSocket::SOCKET_NOT_VALID) {
+ // Default constructor initializes with a disabled configuration.
+}
+
+D2ClientMgr::~D2ClientMgr(){
+ stopSender();
+}
+
+void
+D2ClientMgr::suspendUpdates() {
+ if (ddnsEnabled()) {
+ /// @todo For now we will disable updates and stop sending.
+ /// This at least provides a means to shut it off if there are errors.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES);
+ d2_client_config_->enableUpdates(false);
+ if (name_change_sender_) {
+ stopSender();
+ }
+ }
+}
+
+void
+D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
+ if (!new_config) {
+ isc_throw(D2ClientError,
+ "D2ClientMgr cannot set DHCP-DDNS configuration to NULL.");
+ }
+
+ // Don't do anything unless configuration values are actually different.
+ if (*d2_client_config_ != *new_config) {
+ // Make sure we stop sending first.
+ stopSender();
+ if (!new_config->getEnableUpdates()) {
+ // Updating has been turned off.
+ // Destroy current sender (any queued requests are tossed).
+ name_change_sender_.reset();
+ } else {
+ dhcp_ddns::NameChangeSenderPtr new_sender;
+ switch (new_config->getNcrProtocol()) {
+ case dhcp_ddns::NCR_UDP: {
+ // Instantiate a new sender.
+ new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
+ new_config->getSenderIp(),
+ new_config->getSenderPort(),
+ new_config->getServerIp(),
+ new_config->getServerPort(),
+ new_config->getNcrFormat(),
+ *this,
+ new_config->getMaxQueueSize()));
+ break;
+ }
+ default:
+ // In theory you can't get here.
+ isc_throw(D2ClientError, "Invalid sender Protocol: "
+ << new_config->getNcrProtocol());
+ break;
+ }
+
+ // Transfer queued requests from previous sender to the new one.
+ /// @todo - Should we consider anything queued to be wrong?
+ /// If only server values changed content might still be right but
+ /// if content values changed (e.g. suffix or an override flag)
+ /// then the queued contents might now be invalid. There is
+ /// no way to regenerate them if they are wrong.
+ if (name_change_sender_) {
+ new_sender->assumeQueue(*name_change_sender_);
+ }
+
+ // Replace the old sender with the new one.
+ name_change_sender_ = new_sender;
+ }
+ }
+
+ // Update the configuration.
+ d2_client_config_ = new_config;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_CFG_DHCP_DDNS)
+ .arg(!ddnsEnabled() ? "DHCP-DDNS updates disabled" :
+ "DHCP_DDNS updates enabled");
+}
+
+bool
+D2ClientMgr::ddnsEnabled() {
+ return (d2_client_config_->getEnableUpdates());
+}
+
+const D2ClientConfigPtr&
+D2ClientMgr::getD2ClientConfig() const {
+ return (d2_client_config_);
+}
+
+void
+D2ClientMgr::analyzeFqdn(const bool client_s, const bool client_n,
+ bool& server_s, bool& server_n,
+ const DdnsParams& ddns_params) const {
+ // Per RFC 4702 & 4704, the client N and S flags allow the client to
+ // request one of three options:
+ //
+ // N flag S flag Option
+ // ------------------------------------------------------------------
+ // 0 0 client wants to do forward updates (section 3.2)
+ // 0 1 client wants server to do forward updates (section 3.3)
+ // 1 0 client wants no one to do updates (section 3.4)
+ // 1 1 invalid combination
+ // (Note section numbers cited are for 4702, for 4704 see 5.1, 5.2, and 5.3)
+ //
+ // Make a bit mask from the client's flags and use it to set the response
+ // flags accordingly.
+ const uint8_t mask = ((client_n ? 2 : 0) + (client_s ? 1 : 0));
+
+ switch (mask) {
+ case 0:
+ if (!ddns_params.getEnableUpdates()) {
+ server_s = false;
+ server_n = true;
+ } else {
+ // If updates are enabled and we are overriding client delegation
+ // then S flag should be true. N-flag should be false.
+ server_s = ddns_params.getOverrideClientUpdate();
+ server_n = false;
+ }
+ break;
+
+ case 1:
+ server_s = ddns_params.getEnableUpdates();
+ server_n = !server_s;
+ break;
+
+ case 2:
+ // If updates are enabled and we are overriding "no updates" then
+ // S flag should be true.
+ server_s = (ddns_params.getEnableUpdates() &&
+ ddns_params.getOverrideNoUpdate());
+ server_n = !server_s;
+ break;
+
+ default:
+ // RFCs declare this an invalid combination.
+ isc_throw(isc::BadValue,
+ "Invalid client FQDN - N and S cannot both be 1");
+ break;
+ }
+}
+
+std::string
+D2ClientMgr::generateFqdn(const asiolink::IOAddress& address,
+ const DdnsParams& ddns_params,
+ const bool trailing_dot) const {
+ std::string hostname = address.toText();
+ std::replace(hostname.begin(), hostname.end(),
+ (address.isV4() ? '.' : ':'), '-');
+
+ std::ostringstream gen_name;
+ gen_name << ddns_params.getGeneratedPrefix() << "-" << hostname;
+ return (qualifyName(gen_name.str(), ddns_params, trailing_dot));
+}
+
+
+std::string
+D2ClientMgr::qualifyName(const std::string& partial_name,
+ const DdnsParams& ddns_params,
+ const bool trailing_dot) const {
+ std::ostringstream gen_name;
+
+ gen_name << partial_name;
+ std::string suffix = ddns_params.getQualifyingSuffix();
+ bool suffix_present = true;
+ if (!suffix.empty()) {
+ std::string str = gen_name.str();
+ auto suffix_rit = suffix.rbegin();
+ if (*suffix_rit == '.') {
+ ++suffix_rit;
+ }
+
+ auto gen_rit = str.rbegin();
+ if (*gen_rit == '.') {
+ ++gen_rit;
+ }
+
+ while (suffix_rit != suffix.rend()) {
+ if ((gen_rit == str.rend()) || (*suffix_rit != *gen_rit)) {
+ // They don't match.
+ suffix_present = false;
+ break;
+ }
+
+ ++suffix_rit;
+ ++gen_rit;
+ }
+
+ // Catch the case where name has suffix embedded.
+ // input: foo.barexample.com suffix: example.com
+ if ((suffix_present) && (suffix_rit == suffix.rend())) {
+ if ((gen_rit != str.rend()) && (*gen_rit != '.')) {
+ suffix_present = false;
+ }
+ }
+
+ if (!suffix_present) {
+ size_t len = str.length();
+ if ((len > 0) && (str[len - 1] != '.')) {
+ gen_name << ".";
+ }
+
+ gen_name << suffix;
+ }
+ }
+
+ std::string str = gen_name.str();
+ size_t len = str.length();
+
+ if (trailing_dot) {
+ // If trailing dot should be added but there is no trailing dot,
+ // append it.
+ if ((len > 0) && (str[len - 1] != '.')) {
+ gen_name << ".";
+ }
+
+ } else {
+ // If the trailing dot should not be appended but it is present,
+ // remove it.
+ if ((len > 0) && (str[len - 1] == '.')) {
+ gen_name.str(str.substr(0,len-1));
+ }
+
+ }
+
+ return (gen_name.str());
+}
+
+void
+D2ClientMgr::startSender(D2ClientErrorHandler error_handler) {
+ if (amSending()) {
+ return;
+ }
+
+ // Create a our own service instance when we are not being multiplexed
+ // into an external service..
+ private_io_service_.reset(new asiolink::IOService());
+ startSender(error_handler, *private_io_service_);
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STARTED)
+ .arg(d2_client_config_->toText());
+}
+
+void
+D2ClientMgr::startSender(D2ClientErrorHandler error_handler,
+ isc::asiolink::IOService& io_service) {
+ if (amSending()) {
+ return;
+ }
+
+ if (!name_change_sender_) {
+ isc_throw(D2ClientError, "D2ClientMgr::startSender sender is null");
+ }
+
+ if (!error_handler) {
+ isc_throw(D2ClientError, "D2ClientMgr::startSender handler is null");
+ }
+
+ // Set the error handler.
+ client_error_handler_ = error_handler;
+
+ // Start the sender on the given service.
+ name_change_sender_->startSending(io_service);
+
+ // Register sender's select-fd with IfaceMgr.
+ // We need to remember the fd that is registered so we can unregister later.
+ // IO error handling in the sender may alter its select-fd.
+ registered_select_fd_ = name_change_sender_->getSelectFd();
+ IfaceMgr::instance().addExternalSocket(registered_select_fd_,
+ std::bind(&D2ClientMgr::runReadyIO,
+ this));
+}
+
+bool
+D2ClientMgr::amSending() const {
+ return (name_change_sender_ && name_change_sender_->amSending());
+}
+
+void
+D2ClientMgr::stopSender() {
+ /// Unregister sender's select-fd.
+ if (registered_select_fd_ != util::WatchSocket::SOCKET_NOT_VALID) {
+ IfaceMgr::instance().deleteExternalSocket(registered_select_fd_);
+ registered_select_fd_ = util::WatchSocket::SOCKET_NOT_VALID;
+ }
+
+ // If its not null, call stop.
+ if (amSending()) {
+ name_change_sender_->stopSending();
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_SENDER_STOPPED);
+ }
+}
+
+void
+D2ClientMgr::sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr) {
+ if (!amSending()) {
+ // This is programmatic error so bust them for it.
+ isc_throw(D2ClientError, "D2ClientMgr::sendRequest not in send mode");
+ }
+
+ try {
+ name_change_sender_->sendRequest(ncr);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_NCR_REJECTED)
+ .arg(ex.what()).arg((ncr ? ncr->toText() : " NULL "));
+ invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR, ncr);
+ }
+}
+
+void
+D2ClientMgr::invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
+ Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ // Handler is mandatory to enter send mode but test it just to be safe.
+ if (!client_error_handler_) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_HANDLER_NULL);
+ } else {
+ // Handler is not supposed to throw, but catch just in case.
+ try {
+ (client_error_handler_)(result, ncr);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION)
+ .arg(ex.what());
+ }
+ }
+}
+
+size_t
+D2ClientMgr::getQueueSize() const {
+ if (!name_change_sender_) {
+ isc_throw(D2ClientError, "D2ClientMgr::getQueueSize sender is null");
+ }
+
+ return(name_change_sender_->getQueueSize());
+}
+
+size_t
+D2ClientMgr::getQueueMaxSize() const {
+ if (!name_change_sender_) {
+ isc_throw(D2ClientError, "D2ClientMgr::getQueueMaxSize sender is null");
+ }
+
+ return(name_change_sender_->getQueueMaxSize());
+}
+
+
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2ClientMgr::peekAt(const size_t index) const {
+ if (!name_change_sender_) {
+ isc_throw(D2ClientError, "D2ClientMgr::peekAt sender is null");
+ }
+
+ return (name_change_sender_->peekAt(index));
+}
+
+void
+D2ClientMgr::clearQueue() {
+ if (!name_change_sender_) {
+ isc_throw(D2ClientError, "D2ClientMgr::clearQueue sender is null");
+ }
+
+ name_change_sender_->clearSendQueue();
+}
+
+void
+D2ClientMgr::operator()(const dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ if (result == dhcp_ddns::NameChangeSender::SUCCESS) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_DHCP_DDNS_NCR_SENT).arg(ncr->toText());
+ } else {
+ invokeClientErrorHandler(result, ncr);
+ }
+}
+
+int
+D2ClientMgr::getSelectFd() {
+ if (!amSending()) {
+ isc_throw (D2ClientError, "D2ClientMgr::getSelectFd "
+ " not in send mode");
+ }
+
+ return (name_change_sender_->getSelectFd());
+}
+
+void
+D2ClientMgr::runReadyIO() {
+ if (!name_change_sender_) {
+ // This should never happen.
+ isc_throw(D2ClientError, "D2ClientMgr::runReadyIO"
+ " name_change_sender is null");
+ }
+
+ name_change_sender_->runReadyIO();
+}
+
+}; // namespace dhcp
+
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/d2_client_mgr.h b/src/lib/dhcpsrv/d2_client_mgr.h
new file mode 100644
index 0000000..7b282c5
--- /dev/null
+++ b/src/lib/dhcpsrv/d2_client_mgr.h
@@ -0,0 +1,530 @@
+// Copyright (C) 2014-2022 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 D2_CLIENT_MGR_H
+#define D2_CLIENT_MGR_H
+
+/// @file d2_client_mgr.h Defines the D2ClientMgr class.
+/// This file defines the class Kea uses to act as a client of the
+/// kea-dhcp-ddns module (aka D2).
+///
+#include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <dhcpsrv/srv_config.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Defines the type for D2 IO error handler.
+/// This callback is invoked when a send to kea-dhcp-ddns completes with a
+/// failed status. This provides the application layer (Kea) with a means to
+/// handle the error appropriately.
+///
+/// @param result Result code of the send operation.
+/// @param ncr NameChangeRequest which failed to send.
+///
+/// @note Handlers are expected not to throw. In the event a handler does
+/// throw invoking code logs the exception and then swallows it.
+typedef
+std::function<void(const dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr)> D2ClientErrorHandler;
+
+/// @brief D2ClientMgr isolates Kea from the details of being a D2 client.
+///
+/// Provides services for managing the current dhcp-ddns configuration and
+/// as well as communications with kea-dhcp-ddns. Regarding configuration it
+/// provides services to store, update, and access the current dhcp-ddns
+/// configuration. As for kea-dhcp-ddns communications, D2ClientMgr creates
+/// maintains a NameChangeSender appropriate to the current configuration and
+/// provides services to start, stop, and post NCRs to the sender. Additionally
+/// there are methods to examine the queue of requests currently waiting for
+/// transmission.
+///
+/// The manager also provides the mechanics to integrate the ASIO-based IO
+/// used by the NCR IPC with the select-driven IO used by Kea. Senders expose
+/// a file descriptor, the "select-fd" that can monitored for read-readiness
+/// with the select() function (or variants). D2ClientMgr provides a method,
+/// runReadyIO(), that will instructs the sender to process the next ready
+/// ready IO handler on the sender's IOservice. Track# 3315 extended
+/// Kea's IfaceMgr to support the registration of multiple external sockets
+/// with callbacks that are then monitored with IO readiness via select().
+/// D2ClientMgr registers the sender's select-fd and runReadyIO() with
+/// IfaceMgr when entering the send mode and unregisters it when exiting send
+/// mode.
+///
+/// To place the manager in send mode, the calling layer must supply an error
+/// handler and optionally an IOService instance. The error handler is invoked
+/// if a send completes with a failed status. This provides the calling layer
+/// an opportunity act upon the error.
+///
+/// If the caller supplies an IOService, that service will be used to process
+/// the sender's IO. If not supplied, D2ClientMgr pass a private IOService
+/// into the sender. Using a private service isolates the sender's IO from
+/// any other services.
+///
+class D2ClientMgr : public dhcp_ddns::NameChangeSender::RequestSendHandler,
+ boost::noncopyable {
+public:
+ /// @brief Constructor
+ ///
+ /// Default constructor which constructs an instance which has DHCP-DDNS
+ /// updates disabled.
+ D2ClientMgr();
+
+ /// @brief Destructor.
+ ~D2ClientMgr();
+
+ /// @brief Updates the DHCP-DDNS client configuration to the given value.
+ ///
+ /// @param new_config pointer to the new client configuration.
+ /// @throw D2ClientError if passed an empty pointer.
+ void setD2ClientConfig(D2ClientConfigPtr& new_config);
+
+ /// @brief Convenience method for checking if DHCP-DDNS is enabled.
+ ///
+ /// @return True if the D2 configuration is enabled.
+ bool ddnsEnabled();
+
+ /// @brief Fetches the DHCP-DDNS configuration pointer.
+ ///
+ /// @return a reference to the current configuration pointer.
+ const D2ClientConfigPtr& getD2ClientConfig() const;
+
+ /// @brief Determines server flags based on configuration and client flags.
+ ///
+ /// This method uses input values for the client's FQDN S and N flags, in
+ /// conjunction with the configuration parameters updates-enabled, override-
+ /// no-updates, and override-client-updates to determine the values that
+ /// should be used for the server's FQDN S and N flags.
+ /// The logic in this method is based upon RFCs 4702 and 4704, and is
+ /// shown in the following truth table:
+ ///
+ /// @code
+ ///
+ /// When Updates are enabled:
+ ///
+ /// ON = Override No Updates, OC = Override Client Updates
+ ///
+ /// | Client |-------- Server Response Flags ------------|
+ /// | Flags | ON=F,OC=F | ON=F,OC=T | ON=T,OC=F | ON=T,OC=T |
+ /// | N-S | N-S-O | N-S-O | N-S-O | N-S-O |
+ /// ----------------------------------------------------------
+ /// | 0-0 | 0-0-0 | 0-1-1 | 0-0-0 | 0-1-1 |
+ /// | 0-1 | 0-1-0 | 0-1-0 | 0-1-0 | 0-1-0 |
+ /// | 1-0 | 1-0-0 | 1-0-0 | 0-1-1 | 0-1-1 |
+ ///
+ /// One can then use the server response flags to know when forward and
+ /// reverse updates should be performed:
+ ///
+ /// - Forward updates should be done when the Server S-Flag is true.
+ /// - Reverse updates should be done when the Server N-Flag is false.
+ ///
+ /// When Updates are disabled:
+ ///
+ /// | Client | Server |
+ /// | N-S | N-S-O |
+ /// --------------------
+ /// | 0-0 | 1-0-0 |
+ /// | 0-1 | 1-0-1 |
+ /// | 1-0 | 1-0-0 |
+ ///
+ /// @endcode
+ ///
+ /// @param client_s S Flag from the client's FQDN
+ /// @param client_n N Flag from the client's FQDN
+ /// @param server_s [out] S Flag for the server's FQDN
+ /// @param server_n [out] N Flag for the server's FQDN
+ /// @param ddns_params DDNS behavioral configuration parameters
+ ///
+ /// @throw isc::BadValue if client_s and client_n are both 1 as this is
+ /// an invalid combination per RFCs.
+ void analyzeFqdn(const bool client_s, const bool client_n, bool& server_s,
+ bool& server_n, const DdnsParams& ddns_params) const;
+
+ /// @brief Builds a FQDN based on the configuration and given IP address.
+ ///
+ /// Using the current values for generated-prefix, qualifying-suffix and
+ /// an IP address, this method constructs a fully qualified domain name.
+ /// It supports both IPv4 and IPv6 addresses. The format of the name
+ /// is as follows:
+ ///
+ /// <generated-prefix>-<ip address>.<qualifying-suffix>.
+ ///
+ /// <ip-address> is the result of IOAddress.toText() with the delimiters
+ /// ('.' for IPv4 or ':' for IPv6) replaced with a hyphen, '-'.
+ ///
+ /// @param address IP address from which to derive the name (IPv4 or IPv6)
+ /// @param ddns_params DDNS behavioral configuration parameters
+ /// @param trailing_dot A boolean value which indicates whether trailing
+ /// dot should be appended (if true) or not (false).
+ ///
+ /// @return std::string containing the generated name.
+ std::string generateFqdn(const asiolink::IOAddress& address,
+ const DdnsParams& ddns_params,
+ const bool trailing_dot = true) const;
+
+ /// @brief Adds a qualifying suffix to a given domain name
+ ///
+ /// Constructs a FQDN based on the configured qualifying-suffix and
+ /// a partial domain name as follows:
+ ///
+ /// <partial_name>.<qualifying-suffix>.
+ ///
+ /// Note that the qualifying suffix will only be appended if the
+ /// input name does not already end with that suffix.
+ ///
+ /// @param partial_name domain name to qualify
+ /// @param ddns_params DDNS behavioral configuration parameters
+ /// @param trailing_dot A boolean value which when true guarantees the
+ /// result will end with a "." and when false that the result will not
+ /// end with a "." Note that this rule is applied even if the qualifying
+ /// suffix itself is empty (i.e. "").
+ ///
+ /// @return std::string containing the qualified name.
+ std::string qualifyName(const std::string& partial_name,
+ const DdnsParams& ddns_params,
+ const bool trailing_dot) const;
+
+ /// @brief Set server FQDN flags based on configuration and a given FQDN
+ ///
+ /// Templated wrapper around the analyzeFqdn() allowing that method to
+ /// be used for either IPv4 or IPv6 processing. This methods resets all
+ /// of the flags in the response to zero and then sets the S,N, and O
+ /// flags. Any other flags are the responsibility of the invoking layer.
+ ///
+ /// @param fqdn FQDN option from which to read client (inbound) flags
+ /// @param fqdn_resp FQDN option to update with the server (outbound) flags
+ /// @param ddns_params DDNS behavioral configuration parameters
+ /// @tparam T FQDN Option class containing the FQDN data such as
+ /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+ template <class T>
+ void adjustFqdnFlags(const T& fqdn, T& fqdn_resp,
+ const DdnsParams& ddns_params);
+
+ /// @brief Get directional update flags based on server FQDN flags
+ ///
+ /// Templated convenience method which determines whether forward and
+ /// reverse updates should be performed based on a server response version
+ /// of the FQDN flags. The logic is straight forward and currently not
+ /// dependent upon configuration specific values:
+ ///
+ /// * forward will be true if S_FLAG is true
+ /// * reverse will be true if N_FLAG is false
+ ///
+ /// @param fqdn_resp FQDN option from which to read server (outbound) flags
+ /// @param [out] forward bool value will be set to true if forward updates
+ /// should be done, false if not.
+ /// @param [out] reverse bool value will be set to true if reverse updates
+ /// should be done, false if not.
+ /// @tparam T FQDN Option class containing the FQDN data such as
+ /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+ template <class T>
+ void getUpdateDirections(const T& fqdn_resp, bool& forward, bool& reverse);
+
+ /// @brief Set server FQDN name based on configuration and a given FQDN
+ ///
+ /// Templated method which adjusts the domain name value and type in
+ /// a server FQDN from a client (inbound) FQDN and the current
+ /// configuration. The logic is as follows:
+ ///
+ /// If replace-client-name is true or the supplied name is empty, the
+ /// server FQDN is set to ""/PARTIAL.
+ ///
+ /// If replace-client-name is false and the supplied name is a partial
+ /// name the server FQDN is set to the supplied name qualified by
+ /// appending the qualifying-suffix.
+ ///
+ /// If replace-client-name is false and the supplied name is a fully
+ /// qualified name, set the server FQDN to the supplied name.
+ ///
+ /// If hostname-char-set is not empty, the inbound name will be
+ /// sanitized. This is done by iterating over the domain name labels,
+ /// sanitizing each individually, and then concatenating them into a
+ /// new sanitized name. It is done this way to guard against the case
+ /// where the hostname-char-set does not protect dots from replacement.
+ ///
+ /// @param fqdn FQDN option from which to get client (inbound) name
+ /// @param fqdn_resp FQDN option to update with the adjusted name
+ /// @param ddns_params DDNS behavioral configuration parameters
+ /// @tparam T FQDN Option class containing the FQDN data such as
+ /// dhcp::Option4ClientFqdn or dhcp::Option6ClientFqdn
+ template <class T>
+ void adjustDomainName(const T& fqdn, T& fqdn_resp,
+ const DdnsParams& ddns_params);
+
+ /// @brief Enables sending NameChangeRequests to kea-dhcp-ddns
+ ///
+ /// Places the NameChangeSender into send mode. This instructs the
+ /// sender to begin dequeuing and transmitting requests and to accept
+ /// additional requests via the sendRequest() method.
+ ///
+ /// @param error_handler application level error handler to cope with
+ /// sends that complete with a failed status. A valid function must be
+ /// supplied as the manager cannot know how an application should deal
+ /// with send failures.
+ /// @param io_service IOService to be used for sender IO event processing
+ /// @warning It is up to the invoking layer to ensure the io_service
+ /// instance used outlives the D2ClientMgr send mode. When the send mode
+ /// is exited, either explicitly by calling stopSender() or implicitly
+ /// through D2ClientMgr destruction, any ASIO objects such as sockets or
+ /// timers will be closed and released. If the io_service goes out of scope
+ /// first this behavior could be unpredictable.
+ ///
+ /// @throw D2ClientError if sender instance is null. Underlying layer
+ /// may throw NCRSenderExceptions exceptions.
+ void startSender(D2ClientErrorHandler error_handler,
+ isc::asiolink::IOService& io_service);
+
+ /// @brief Enables sending NameChangeRequests to kea-dhcp-ddns
+ ///
+ /// Places the NameChangeSender into send mode. This instructs the
+ /// sender to begin dequeuing and transmitting requests and to accept
+ /// additional requests via the sendRequest() method. The manager
+ /// will create a new, private instance of an IOService for the sender
+ /// to use for IO event processing.
+ ///
+ /// @param error_handler application level error handler to cope with
+ /// sends that complete with a failed status. A valid function must be
+ /// supplied as the manager cannot know how an application should deal
+ /// with send failures.
+ ///
+ /// @throw D2ClientError if sender instance is null. Underlying layer
+ /// may throw NCRSenderExceptions exceptions.
+ void startSender(D2ClientErrorHandler error_handler);
+
+ /// @brief Returns true if the sender is in send mode, false otherwise.
+ ///
+ /// A true value indicates that the sender is present and in accepting
+ /// messages for transmission, false otherwise.
+ bool amSending() const;
+
+ /// @brief Disables sending NameChangeRequests to kea-dhcp-ddns
+ ///
+ /// Takes the NameChangeSender out of send mode. The sender will stop
+ /// transmitting requests, though any queued requests remain queued.
+ /// Attempts to queue additional requests via sendRequest will fail.
+ ///
+ /// @throw D2ClientError if sender instance is null. Underlying layer
+ /// may throw NCRSenderExceptions exceptions.
+ void stopSender();
+
+ /// @brief Send the given NameChangeRequests to kea-dhcp-ddns
+ ///
+ /// Passes NameChangeRequests to the NCR sender for transmission to
+ /// kea-dhcp-ddns. If the sender rejects the message, the client's error
+ /// handler will be invoked. The most likely cause for rejection is
+ /// the senders' queue has reached maximum capacity.
+ ///
+ /// @param ncr NameChangeRequest to send
+ ///
+ /// @throw D2ClientError if sender instance is null or not in send
+ /// mode. Either of these represents a programmatic error.
+ void sendRequest(dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Calls the client's error handler.
+ ///
+ /// Calls the error handler method set by startSender() when an
+ /// error occurs attempting to send a method. If the error handler
+ /// throws an exception it will be caught and logged.
+ ///
+ /// @param result contains that send outcome status.
+ /// @param ncr is a pointer to the NameChangeRequest that was attempted.
+ ///
+ /// This method is exception safe.
+ void invokeClientErrorHandler(const dhcp_ddns::NameChangeSender::
+ Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Returns the number of NCRs queued for transmission.
+ size_t getQueueSize() const;
+
+ /// @brief Returns the maximum number of NCRs allowed in the queue.
+ size_t getQueueMaxSize() const;
+
+ /// @brief Returns the nth NCR queued for transmission.
+ ///
+ /// Note that the entry is not removed from the queue.
+ /// @param index the index of the entry in the queue to fetch.
+ /// Valid values are 0 (front of the queue) to (queue size - 1).
+ /// @note This method is for test purposes only.
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw D2ClientError if sender instance is null. Underlying layer
+ /// may throw NCRSenderExceptions exceptions.
+ const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
+
+ /// @brief Removes all NCRs queued for transmission.
+ ///
+ /// @throw D2ClientError if sender instance is null. Underlying layer
+ /// may throw NCRSenderExceptions exceptions.
+ void clearQueue();
+
+ /// @brief Processes sender IO events
+ ///
+ /// Serves as callback registered for the sender's select-fd with IfaceMgr.
+ /// It instructs the sender to execute the next ready IO handler.
+ /// It provides an instance method that can be bound via std::bind, as
+ /// NameChangeSender is abstract.
+ void runReadyIO();
+
+ /// @brief Suspends sending requests.
+ ///
+ /// This method is intended to be used when IO errors occur. It toggles
+ /// the enable-updates configuration flag to off, and takes the sender
+ /// out of send mode. Messages in the sender's queue will remain in the
+ /// queue.
+ /// @todo This logic may change in NameChangeSender is altered allow
+ /// queuing while stopped. Currently when a sender is not in send mode
+ /// it will not accept additional messages.
+ void suspendUpdates();
+
+protected:
+ /// @brief Function operator implementing the NCR sender callback.
+ ///
+ /// This method is invoked each time the NameChangeSender completes
+ /// an asynchronous send.
+ ///
+ /// @param result contains that send outcome status.
+ /// @param ncr is a pointer to the NameChangeRequest that was
+ /// delivered (or attempted).
+ ///
+ /// @throw This method MUST NOT throw.
+ virtual void operator ()(const dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Fetches the sender's select-fd.
+ ///
+ /// The select-fd may be used with select() or poll(). If the sender has
+ /// IO waiting to process, the fd will evaluate as !EWOULDBLOCK.
+ /// @note This is only exposed for testing purposes.
+ ///
+ /// @return The sender's select-fd
+ ///
+ /// @throw D2ClientError if the sender does not exist or is not in send
+ /// mode.
+ int getSelectFd();
+
+ /// @brief Fetches the select-fd that is currently registered.
+ ///
+ /// @return The currently registered select-fd or
+ /// util::WatchSocket::SOCKET_NOT_VALID.
+ ///
+ /// @note This is only exposed for testing purposes.
+ int getRegisteredSelectFd();
+
+private:
+ /// @brief Container class for DHCP-DDNS configuration parameters.
+ D2ClientConfigPtr d2_client_config_;
+
+ /// @brief Pointer to the current interface to DHCP-DDNS.
+ dhcp_ddns::NameChangeSenderPtr name_change_sender_;
+
+ /// @brief Private IOService to use if calling layer doesn't wish to
+ /// supply one.
+ boost::shared_ptr<asiolink::IOService> private_io_service_;
+
+ /// @brief Application supplied error handler invoked when a send
+ /// completes with a failed status.
+ D2ClientErrorHandler client_error_handler_;
+
+ /// @brief Remembers the select-fd registered with IfaceMgr.
+ int registered_select_fd_;
+};
+
+template <class T>
+void
+D2ClientMgr::adjustFqdnFlags(const T& fqdn, T& fqdn_resp, const DdnsParams& ddns_params) {
+ bool server_s = false;
+ bool server_n = false;
+ analyzeFqdn(fqdn.getFlag(T::FLAG_S), fqdn.getFlag(T::FLAG_N),
+ server_s, server_n, ddns_params);
+
+ // Reset the flags to zero to avoid triggering N and S both 1 check.
+ fqdn_resp.resetFlags();
+
+ // Set S and N flags.
+ fqdn_resp.setFlag(T::FLAG_S, server_s);
+ fqdn_resp.setFlag(T::FLAG_N, server_n);
+
+ // Set O flag true if server S overrides client S.
+ fqdn_resp.setFlag(T::FLAG_O, (fqdn.getFlag(T::FLAG_S) != server_s));
+}
+
+template <class T>
+void
+D2ClientMgr::getUpdateDirections(const T& fqdn_resp,
+ bool& forward, bool& reverse) {
+ forward = fqdn_resp.getFlag(T::FLAG_S);
+ reverse = !(fqdn_resp.getFlag(T::FLAG_N));
+}
+
+template <class T>
+void
+D2ClientMgr::adjustDomainName(const T& fqdn, T& fqdn_resp, const DdnsParams& ddns_params) {
+ // If we're configured to replace it or the supplied name is blank
+ // set the response name to blank.
+ D2ClientConfig::ReplaceClientNameMode mode = ddns_params.getReplaceClientNameMode();
+ if ((mode == D2ClientConfig::RCM_ALWAYS || mode == D2ClientConfig::RCM_WHEN_PRESENT) ||
+ fqdn.getDomainName().empty()) {
+ fqdn_resp.setDomainName("", T::PARTIAL);
+ } else {
+ // Sanitize the name the client sent us, if we're configured to do so.
+ std::string client_name = fqdn.getDomainName();
+
+ isc::util::str::StringSanitizerPtr sanitizer = ddns_params.getHostnameSanitizer();
+ if (sanitizer) {
+ // We need the raw text form, so we can replace escaped chars
+ dns::Name tmp(client_name);
+ std::string raw_name = tmp.toRawText();
+
+ // We do not know if the sanitizer's regexp preserves dots, so
+ // we'll scrub it label by label. Yeah, lucky us.
+ // Using boost::split is simpler than using dns::Name::split() as
+ // that returns Names which have trailing dots etc.
+ std::vector<std::string> labels;
+ boost::algorithm::split(labels, raw_name, boost::is_any_of("."));
+ std::stringstream ss;
+ for (auto label = labels.begin(); label != labels.end(); ++label ) {
+ if (label != labels.begin()) {
+ ss << ".";
+ }
+
+ ss << sanitizer->scrub(*label);
+ }
+
+ client_name = ss.str();
+ }
+
+ // If the supplied name is partial, qualify it by adding the suffix.
+ if (fqdn.getDomainNameType() == T::PARTIAL) {
+ fqdn_resp.setDomainName(qualifyName(client_name, ddns_params, true), T::FULL);
+ } else {
+ fqdn_resp.setDomainName(client_name, T::FULL);
+ }
+ }
+}
+
+
+/// @brief Defines a pointer for D2ClientMgr instances.
+typedef boost::shared_ptr<D2ClientMgr> D2ClientMgrPtr;
+
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
new file mode 100644
index 0000000..0cc54f0
--- /dev/null
+++ b/src/lib/dhcpsrv/database_backends.dox
@@ -0,0 +1,217 @@
+// Copyright (C) 2012-2022 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/.
+
+/**
+ @page dhcpDatabaseBackends DHCP Database Back-Ends
+
+ All DHCP lease data is stored in some form of database, the interface
+ to this being through the Lease Manager.
+
+ All backend classes such as isc::dhcp::MySqlLeaseMgr are derived from
+ the abstract isc::dhcp::LeaseMgr class. This provides methods to
+ create, retrieve, modify and delete leases in the database.
+
+ There are currently three available Lease Managers, Memfile, MySQL and
+ PostgreSQL:
+
+ - Memfile is an in-memory lease database which can be configured to persist
+ its content to disk in a flat-file. Support for the Memfile database
+ backend is built into Kea DHCP.
+
+ - The MySQL lease manager uses the freely available MySQL as its backend
+ database. This is not included in Kea DHCP by default:
+ the \--with-mysql switch must be supplied to "configure" for support
+ to be compiled into the software.
+
+ - The PostgreSQL lease manager uses the freely available PostgreSQL as its
+ backend database. This is not included in Kea DHCP by default:
+ the \--with-pgsql switch must be supplied to "configure" for
+ support to be compiled into the software.
+
+ @section dhcpdb-instantiation Instantiation of Lease Managers
+
+ A lease manager is instantiated through the @c LeaseMgrFactory class. This
+ has three methods:
+
+ - isc::dhcp::LeaseMgrFactory::create - Creates a singleton Lease
+ Manager of the appropriate type.
+ - isc::dhcp::LeaseMgrFactory::instance - Returns a reference to the
+ the instance of the Lease Manager.
+ - isc::dhcp::LeaseMgrFactory::destroy - Destroys the singleton lease manager.
+
+ The selection of the Lease Manager (and thus the backend database) is
+ controlled by the connection string passed to
+ isc::dhcp::LeaseMgrFactory::create. This is a set of "keyword=value" pairs
+ (no embedded spaces), each pair separated by a space from the others, e.g.
+
+ \code
+ type=mysql user=keatest password=keatest name=keatest host=localhost
+ \endcode
+
+ The following keywords are used for all backends:
+
+ - <b>type</b> - specifies the type of database backend. The following values
+ for the type keyword are supported:
+ - <B>memfile</b> - In-memory database.
+ - <b>mysql</b> - Use MySQL as the database. Must be enabled at compilation
+ time.
+ - <b>postgresql</b> - Use PostgreSQL as the database. Must be enabled
+ at compilation time.
+
+ The following sections list the database-specific keywords:
+
+ @subsection dhcpdb-keywords-mysql MySQL connection string keywords
+
+ - <b>host</b> - host on which the selected database is running. If not
+ supplied, "localhost" is assumed.
+ - <b>name</b> - name of the MySQL database to access. There is no default -
+ this must always be supplied.
+ - <b>password</b> - password for the selected user ID (see below). If not
+ specified, no password is used.
+ - <b>user</b> - database user ID under which the database is accessed. If not
+ specified, no user ID is used - the database is assumed to be open.
+
+ For details, see @ref isc::db::MySqlConnection::openDatabase().
+
+ @subsection dhcpdb-keywords-pgsql PostgreSQL connection string keywords
+
+ - <b>host</b> - host on which the selected database is running. If not
+ supplied, "localhost" is assumed.
+ - <b>name</b> - name of the PostgreSQL database to access. There is no
+ default - this must always be supplied.
+ - <b>password</b> - password for the selected user ID (see below). If not
+ specified, no password is used.
+ - <b>user</b> - database user ID under which the database is accessed. If not
+ specified, no user ID is used - the database is assumed to be open.
+
+ For details, see @ref isc::db::PgSqlConnection::openDatabase().
+
+ @subsection infinite-valid-lifetime Infinite Valid Lifetime
+
+ The @c isc::dhcp::Lease class uses cltt (client last transmission time)
+ and valid lifetime, backend lease uses expire and valid lifetime.
+ These quantities are bound by the equation:
+ @code
+ expire = cltt + valid_lifetime
+ @endcode
+
+ But when expire is a 32 bit date and valid lifetime is the infinity
+ special value (0xffffffff) this overflows so for MySQL and PostgreSQL
+ backends this becomes:
+ @code
+ expire = cltt + valid_lifetime if valid_lifetime != 0xffffffff
+ expire = cltt if valid_lifetime == 0xffffffff
+ @endcode
+
+ @section dhcpdb-host Host Backends
+
+ Host backends (known also as host data sources) are similar to lease
+ backends with a few differences:
+
+ - host backends are optional (so it is allowed to have none) because
+ the first source of host reservations is the server configuration,
+ others are alternate backends.
+
+ - there may be more than one host backend. In such a case for lookups
+ returning a collection all results are appended, for lookups returning
+ at most one entry the first found is returned. Add operation is submitted
+ to all alternate backends which can ignore it, add the entry or throw
+ if the new entry conflicts with an already existing one. Delete
+ operations are submitted in sequence to all alternate backends until
+ one finds the entry, deletes it and returns true.
+
+ - the first alternate backend can be a cache (host cache hook library
+ is a premium feature) which avoids to lookup slow databases.
+ For subnet ID and identifier negative caching is optionally supported.
+
+ - host backends which do not support host collection (as host cache
+ and RADIUS) must return an empty collection (so not contributing
+ to the final result) from all methods returning collections.
+ Of course the core code must not use these methods with these backends
+ but there are some callers outside the server core code, e.g. the
+ host commands hook library.
+
+ @subsection dhcpdb-caching Caching
+
+ Some of these considerations apply to lease backends too but only
+ the host caching was analyzed and implemented.
+
+ Caching divides into two parts, positive and negative caching, and
+ its support is implemented at two places, a cache backend and inside
+ the host manager, i.e. the entity calling backends in sequence
+ providing the result of lookups to allocation engines.
+
+ The idea of positive caching is simple: when a value not in the
+ cache in returned by a database, this value is added to the cache
+ so the next time it will be available without calling and waiting
+ for the database.
+
+ This cannot be extended to lookups returning a collection because
+ they are supposed to collect and append results from all backends.
+ If you replace append by merge you avoid duplicate items in the
+ result but still get no benefit from caching. So in general a cache
+ backend should simply return nothing for these lookups.
+
+ Add (or any operation which can fail) has to wait that all backends
+ are called and possibly one fails before the new entry being cached.
+ Del is simpler: the cache backend processes it but always returns
+ false so the backend holding it if any is called.
+
+ Negative caching consists into adding fake entries indicating that
+ a particular host does not exists. As no host constructor allows
+ a host object without an identifier or with an empty identifier,
+ negative caching applies only to by identifier lookups. This is
+ no a problem because out-of-pools provides a clearer and simpler
+ to implement performance benefit than by address negative caching.
+ Note that by identifier negative caching can be critical for
+ performance because the non-existence is the worst case for lookups.
+
+ Negative cache entries should be easily identified (current
+ implementation uses the negative_ flag member in @c host class)
+ so all lookups returning at most one entry can (in fact have to)
+ return a null pointer when they get a negative cache entry.
+ Note this is for all such lookups, not only by identifier lookups,
+ to allow to negative cached entries with any value, for instance
+ with a IP address.
+
+ There is no direct and simple way to support negative caching
+ for collection lookups so again cache backends should return nothing
+ for these lookups which have not to filter out negative cached entries
+ from result.
+
+ Negative caching can be performed by the host manager: when a by
+ identifier lookup returns a null pointer, a fake entry with lookup
+ parameters and the negative cache mark is inserted into the cache.
+ Note this leads to negative cache entries without IP reservations,
+ this property should not be used because it limits negative cache
+ addition to only be performed by the host manager.
+
+@section dhcpDatabaseBackendsMTConsiderations Multi-Threading Consideration for DHCP Database Backends
+
+Lease and host database backends including the memfile for leases are Kea
+thread safe (i.e. are thread safe when the multi-threading mode is true).
+This extends to legal / forensic log backends but not to config
+backends which is used only for configuration by the main thread with
+packet processing threads stopped so has no thread safety
+requirements.
+
+There are exceptions:
+
+ - memfile constructor (including loading of leases from files) is not
+ thread safe.
+
+ - lfc handling in memfile is not thread safe: instead it is required
+ to be called from the main thread.
+
+ - wipe lease methods are either not thread safe or not implemented.
+
+Note for statistics queries it does not make sense to call them with
+running packet processing threads so they have no thread safety guarantees.
+
+Note too that the memfile backend is not inter-process safe so must be kept
+private to the Kea server using it.
+
+ */
diff --git a/src/lib/dhcpsrv/db_type.h b/src/lib/dhcpsrv/db_type.h
new file mode 100644
index 0000000..dc22fcd
--- /dev/null
+++ b/src/lib/dhcpsrv/db_type.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2018 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 DB_TYPE_H
+#define DB_TYPE_H
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Specifies the database type.
+enum class DBType {
+ LEASE_DB = 1,
+ HOSTS_DB = 2
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.cc b/src/lib/dhcpsrv/dhcp4o6_ipc.cc
new file mode 100644
index 0000000..4cfe046
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp4o6_ipc.cc
@@ -0,0 +1,288 @@
+// Copyright (C) 2015-2023 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 <dhcp/dhcp6.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+
+#include <boost/pointer_cast.hpp>
+
+#include <errno.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+Dhcp4o6IpcBase::Dhcp4o6IpcBase() : port_(0), socket_fd_(-1) {}
+
+Dhcp4o6IpcBase::~Dhcp4o6IpcBase() {
+ close();
+}
+
+int Dhcp4o6IpcBase::open(uint16_t port, EndpointType endpoint_type) {
+ // Don't check if the value is greater than 65534 as it is done
+ // by callers before they cast the value to 16 bits.
+
+ if (port == port_) {
+ // No change: nothing to do
+ return (socket_fd_);
+ }
+
+ // Open socket
+ int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (sock < 0) {
+ isc_throw(Dhcp4o6IpcError, "Failed to create DHCP4o6 socket.");
+ }
+
+ // Set no blocking
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
+ ::close(sock);
+ isc_throw(Dhcp4o6IpcError,
+ "Failed to set O_NONBLOCK on DHCP4o6 socket.");
+ }
+
+ // Bind to the local address
+ struct sockaddr_in6 local6;
+ memset(&local6, 0, sizeof(local6));
+ local6.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ local6.sin6_len = sizeof(local6);
+#endif
+ if (endpoint_type == ENDPOINT_TYPE_V6) {
+ local6.sin6_port = htons(port);
+ } else {
+ local6.sin6_port = htons(port + 1);
+ }
+ // We'll connect to the loopback address so bind to it too.
+ local6.sin6_addr.s6_addr[15] = 1;
+ if (::bind(sock, (struct sockaddr *)&local6, sizeof(local6)) < 0) {
+ ::close(sock);
+ isc_throw(Dhcp4o6IpcError, "Failed to bind DHCP4o6 socket.");
+ }
+
+ // Connect to the remote address
+ struct sockaddr_in6 remote6;
+ memset(&remote6, 0, sizeof(remote6));
+ remote6.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+ remote6.sin6_len = sizeof(remote6);
+#endif
+ if (endpoint_type == ENDPOINT_TYPE_V6) {
+ remote6.sin6_port = htons(port + 1);
+ } else {
+ remote6.sin6_port = htons(port);
+ }
+ // At least OpenBSD requires the remote address to not be left
+ // unspecified, so we set it to the loopback address.
+ remote6.sin6_addr.s6_addr[15] = 1;
+ if (connect(sock, reinterpret_cast<const struct sockaddr*>(&remote6),
+ sizeof(remote6)) < 0) {
+ ::close(sock);
+ isc_throw(Dhcp4o6IpcError, "Failed to connect DHCP4o6 socket.");
+ }
+
+ if (socket_fd_ != -1) {
+ if (dup2(sock, socket_fd_) == -1) {
+ ::close(sock);
+ isc_throw(Dhcp4o6IpcError, "Failed to duplicate DHCP4o6 socket.");
+ }
+ if (sock != socket_fd_) {
+ ::close(sock);
+ sock = socket_fd_;
+ }
+ }
+
+ // Success
+ port_ = port;
+ socket_fd_ = sock;
+ return (socket_fd_);
+}
+
+void Dhcp4o6IpcBase::close() {
+ port_ = 0;
+ if (socket_fd_ != -1) {
+ IfaceMgr::instance().deleteExternalSocket(socket_fd_);
+ ::close(socket_fd_);
+ socket_fd_ = -1;
+ }
+}
+
+Pkt6Ptr Dhcp4o6IpcBase::receive() {
+ uint8_t buf[65536];
+ ssize_t cc = recv(socket_fd_, buf, sizeof(buf), 0);
+ if (cc < 0) {
+ isc_throw(Dhcp4o6IpcError, "Failed to receive on DHCP4o6 socket.");
+ }
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(buf, cc));
+ pkt->updateTimestamp();
+
+ // Get interface name and remote address
+ pkt->unpack();
+
+ // Vendor option is initially NULL. If we find the instance of the vendor
+ // option with the ISC enterprise id this pointer will point to it.
+ OptionVendorPtr option_vendor;
+
+ // Get all vendor option and look for the one with the ISC enterprise id.
+ OptionCollection vendor_options = pkt->getOptions(D6O_VENDOR_OPTS);
+ for (OptionCollection::const_iterator opt = vendor_options.begin();
+ opt != vendor_options.end(); ++opt) {
+ option_vendor = boost::dynamic_pointer_cast<OptionVendor>(opt->second);
+ if (option_vendor) {
+ if (option_vendor->getVendorId() == ENTERPRISE_ID_ISC) {
+ break;
+ }
+ option_vendor.reset();
+ }
+ }
+
+ // Vendor option must exist.
+ if (!option_vendor) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
+ .arg("no ISC vendor option");
+ isc_throw(Dhcp4o6IpcError, "malformed packet (no ISC vendor option)");
+ }
+
+ // The option carrying interface name is required.
+ OptionStringPtr ifname = boost::dynamic_pointer_cast<
+ OptionString>(option_vendor->getOption(ISC_V6_4O6_INTERFACE));
+ if (!ifname) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
+ .arg("no interface suboption");
+ isc_throw(Dhcp4o6IpcError,
+ "malformed packet (interface suboption missing "
+ "or has incorrect type)");
+ }
+
+ // Check if this interface is present in the system.
+ IfacePtr iface = IfaceMgr::instance().getIface(ifname->getValue());
+ if (!iface) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
+ .arg("can't get interface " + ifname->getValue());
+ isc_throw(Dhcp4o6IpcError,
+ "malformed packet (unknown interface "
+ + ifname->getValue() + ")");
+ }
+
+ // Get the option holding source IPv6 address.
+ OptionCustomPtr srcs = boost::dynamic_pointer_cast<
+ OptionCustom>(option_vendor->getOption(ISC_V6_4O6_SRC_ADDRESS));
+ if (!srcs) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
+ .arg("no source address suboption");
+ isc_throw(Dhcp4o6IpcError,
+ "malformed packet (source address suboption missing "
+ "or has incorrect type)");
+ }
+
+ // Get the option holding source port.
+ OptionUint16Ptr sport = boost::dynamic_pointer_cast<
+ OptionUint16>(option_vendor->getOption(ISC_V6_4O6_SRC_PORT));
+ if (!sport) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
+ .arg("no source port suboption");
+ isc_throw(Dhcp4o6IpcError,
+ "malformed packet (source port suboption missing "
+ "or has incorrect type)");
+ }
+
+ // Update the packet.
+ pkt->setRemoteAddr(srcs->readAddress());
+ pkt->setRemotePort(sport->getValue());
+ pkt->setIface(iface->getName());
+ pkt->setIndex(iface->getIndex());
+
+ // Remove options that have been added by the IPC sender.
+ static_cast<void>(option_vendor->delOption(ISC_V6_4O6_INTERFACE));
+ static_cast<void>(option_vendor->delOption(ISC_V6_4O6_SRC_ADDRESS));
+ static_cast<void>(option_vendor->delOption(ISC_V6_4O6_SRC_PORT));
+
+ // If there are no more options, the IPC sender has probably created the
+ // vendor option, in which case we should remove it here.
+ if (option_vendor->getOptions().empty()) {
+ static_cast<void>(pkt->delOption(D6O_VENDOR_OPTS));
+ }
+
+ return (pkt);
+}
+
+void Dhcp4o6IpcBase::send(const Pkt6Ptr& pkt) {
+ // This shouldn't happen, i.e. send() shouldn't be called if there is
+ // no message.
+ if (!pkt) {
+ isc_throw(Dhcp4o6IpcError, "DHCP4o6 message must not be NULL while"
+ " trying to send it over the IPC");
+ }
+
+ // Disabled: nowhere to send
+ if (socket_fd_ == -1) {
+ isc_throw(Dhcp4o6IpcError, "unable to send DHCP4o6 message because"
+ " IPC socket is closed");
+ }
+
+ // Check if vendor option exists.
+ // Vendor option is initially NULL. If we find the instance of the vendor
+ // option with the ISC enterprise id this pointer will point to it.
+ OptionVendorPtr option_vendor;
+
+ // Get all vendor option and look for the one with the ISC enterprise id.
+ OptionCollection vendor_options = pkt->getOptions(D6O_VENDOR_OPTS);
+ for (OptionCollection::const_iterator opt = vendor_options.begin();
+ opt != vendor_options.end(); ++opt) {
+ option_vendor = boost::dynamic_pointer_cast<OptionVendor>(opt->second);
+ if (option_vendor) {
+ if (option_vendor->getVendorId() == ENTERPRISE_ID_ISC) {
+ break;
+ }
+ option_vendor.reset();
+ }
+ }
+
+ // If vendor option doesn't exist or its enterprise id is not ISC's
+ // enterprise id, let's create it.
+ if (!option_vendor) {
+ option_vendor.reset(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+ pkt->addOption(option_vendor);
+ }
+
+ // Push interface name and source address in it
+ option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+ ISC_V6_4O6_INTERFACE,
+ pkt->getIface())));
+ option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+ pkt->getRemoteAddr())));
+ option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
+ ISC_V6_4O6_SRC_PORT,
+ pkt->getRemotePort())));
+ // Get packet content
+ OutputBuffer& buf = pkt->getBuffer();
+ buf.clear();
+ pkt->pack();
+
+ // Try to send the message.
+ if (::send(socket_fd_, buf.getData(), buf.getLength(), 0) < 0) {
+ isc_throw(Dhcp4o6IpcError,
+ "failed to send DHCP4o6 message over the IPC: "
+ << strerror(errno));
+ }
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.h b/src/lib/dhcpsrv/dhcp4o6_ipc.h
new file mode 100644
index 0000000..d67f2d2
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp4o6_ipc.h
@@ -0,0 +1,131 @@
+// Copyright (C) 2015-2017 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 DHCP4O6_IPC_H
+#define DHCP4O6_IPC_H
+
+/// @file dhcp4o6_ipc.h Defines the Dhcp4o6IpcBase class.
+/// This file defines the class Kea uses as a base for
+/// DHCPv4-over-DHCPv6 communication between servers.
+///
+
+#include <exceptions/exceptions.h>
+#include <dhcp/pkt6.h>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when error occurs as a result of use of IPC.
+class Dhcp4o6IpcError : public Exception {
+public:
+ Dhcp4o6IpcError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief This class implements the communication between the DHCPv4
+/// and DHCPv6 servers to allow for transmission of the DHCPv4 query
+/// and DHCPv4 response messages.
+///
+/// When the DHCPv6 server receives the DHCPv4 query message it needs
+/// to forward it to the DHCPv4 server for processing. The DHCPv4
+/// server processes the message and answers with the DHCPv4 response
+/// message to the DHCPv6 server. The server forwards it back to the
+/// client. This class implements the communication between the DHCPv4
+/// and DHCPv6 servers to allow for transmission of the DHCPv4 query
+/// and DHCPv6 response messages.
+///
+/// This class creates a socket (when @c open is called) and binds it
+/// to a port, depending on the configuration. The port number is
+/// explicitly specified in the server configuration. This explicit
+/// port value is used directly on the DHCPv6 server side. The DHCPv4
+/// server uses the port specified + 1.
+///
+/// The DHCPv4 and DHCPv6 servers use distinct instances of classes derived
+/// from this base class. Each of these instances is used to send and
+/// receive messages sent by the other server.
+///
+/// In order to make address allocation decisions, the DHCPv4 server
+/// requires information about the interface and the source address of
+/// the original DHCPv4 query message sent by the client. This
+/// information is known by the DHCPv6 server and needs to be conveyed
+/// to the DHCPv4 server. The IPC conveys it in the
+/// @c ISC_V6_4O6_INTERFACE, @c ISC_V6_4O6_SRC_ADDRESS and @c
+/// ISC_V6_4O6_SRC_PORT options within the Vendor Specific Information
+/// option, with ISC enterprise id. These options are added by the IPC
+/// sender and removed by the IPC receiver.
+class Dhcp4o6IpcBase : public boost::noncopyable {
+public:
+
+ /// @brief Endpoint type: DHCPv4 or DHCPv6 server.
+ enum EndpointType {
+ ENDPOINT_TYPE_V4 = 4,
+ ENDPOINT_TYPE_V6 = 6
+ };
+
+protected:
+ /// @brief Constructor
+ ///
+ /// Default constructor
+ Dhcp4o6IpcBase();
+
+ /// @brief Destructor.
+ virtual ~Dhcp4o6IpcBase();
+
+ /// @brief Open communication socket (from base class).
+ ///
+ /// @param port Port number to use. The socket is bound to this port
+ /// if the endpoint type is DHCPv6 server, otherwise the port + 1
+ /// value is used.
+ /// @param endpoint_type Endpoint type (DHCPv4 or DHCPv6 server).
+ ///
+ /// @return New socket descriptor.
+ /// @throw isc::dhcp::Dhcp4o6IpcError on system call errors.
+ int open(uint16_t port, EndpointType endpoint_type);
+
+public:
+
+ /// @brief Open communication socket (for derived classes).
+ virtual void open() = 0;
+
+ /// @brief Close communication socket.
+ void close();
+
+ /// @brief Receive message over IPC.
+ ///
+ /// @return a pointer to a DHCPv6 message with interface and remote
+ /// address set from the IPC message
+ /// @throw isc::dhcp::Dhcp4o6IpcError on system call error or
+ /// malformed packets.
+ Pkt6Ptr receive();
+
+ /// @brief Send message over IPC.
+ ///
+ /// The IPC uses @c ISC_V6_4O6_INTERFACE, @c ISC_V6_4O6_SRC_ADDRESS
+ /// and @c ISC_V6_4O6_SRC_PORT options conveyed within the Vendor
+ /// Specific Information option, with ISC enterprise id, to communicate
+ /// the client remote address and the interface on which the DHCPv4 query
+ /// was received. These options will be removed by the receiver.
+ ///
+ /// @param pkt Pointer to a DHCPv6 message with interface and remote
+ /// address.
+ /// @throw isc::dhcp::Dhcp4o6IpcError.
+ void send(const Pkt6Ptr& pkt);
+
+protected:
+
+ /// @brief Port number configured for IPC communication.
+ uint16_t port_;
+
+ /// @brief Socket descriptor.
+ int socket_fd_;
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
diff --git a/src/lib/dhcpsrv/dhcpsrv_exceptions.h b/src/lib/dhcpsrv/dhcpsrv_exceptions.h
new file mode 100644
index 0000000..e18aa51
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_exceptions.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 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 DHCPSRV_EXCEPTIONS_H
+#define DHCPSRV_EXCEPTIONS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Attempt to update lease that was not there
+class NoSuchLease : public Exception {
+public:
+ NoSuchLease(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // DHCPSRV_EXCEPTIONS_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.cc b/src/lib/dhcpsrv/dhcpsrv_log.cc
new file mode 100644
index 0000000..ef0d578
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2012-2018 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/.
+
+/// Defines the logger used by the NSAS
+
+#include <config.h>
+
+#include "dhcpsrv/dhcpsrv_log.h"
+
+namespace isc {
+namespace dhcp {
+
+extern const int DHCPSRV_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int DHCPSRV_DBG_RESULTS = isc::log::DBGLVL_TRACE_BASIC_DATA;
+extern const int DHCPSRV_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
+extern const int DHCPSRV_DBG_TRACE_DETAIL_DATA =
+ isc::log::DBGLVL_TRACE_DETAIL_DATA;
+extern const int DHCPSRV_DBG_HOOKS = isc::log::DBGLVL_TRACE_BASIC;
+
+isc::log::Logger dhcpsrv_logger("dhcpsrv");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
new file mode 100644
index 0000000..95fa701
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2011-2018 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 DHCPSRV_LOG_H
+#define DHCPSRV_LOG_H
+
+#include <dhcpsrv/dhcpsrv_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief DHCP server library logging levels
+///
+/// Defines the levels used to output debug messages in the DHCP server
+/// library. Note that higher numbers equate to more verbose (and detailed)
+/// output.
+
+/// @brief Traces normal operations
+///
+/// E.g. sending a query to the database etc.
+extern const int DHCPSRV_DBG_TRACE;
+
+/// @brief Records the results of the lookups
+///
+/// Using the example of tracing queries from the backend database, this will
+/// just record the summary results.
+extern const int DHCPSRV_DBG_RESULTS;
+
+/// @brief Additional information
+///
+/// Record detailed tracing. This is generally reserved for tracing access to
+/// the lease database.
+extern const int DHCPSRV_DBG_TRACE_DETAIL;
+
+/// @brief Additional information
+///
+/// Record detailed (and verbose) data on the server.
+extern const int DHCPSRV_DBG_TRACE_DETAIL_DATA;
+
+// Trace hook related operations
+extern const int DHCPSRV_DBG_HOOKS;
+
+///@}
+
+
+/// \brief DHCP server library Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger dhcpsrv_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOG_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.cc b/src/lib/dhcpsrv/dhcpsrv_messages.cc
new file mode 100644
index 0000000..07df920
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.cc
@@ -0,0 +1,569 @@
+// File created from ../../../src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ADD_IFACE = "DHCPSRV_CFGMGR_ADD_IFACE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ADD_SUBNET4 = "DHCPSRV_CFGMGR_ADD_SUBNET4";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ADD_SUBNET6 = "DHCPSRV_CFGMGR_ADD_SUBNET6";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE = "DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CFG_DHCP_DDNS = "DHCPSRV_CFGMGR_CFG_DHCP_DDNS";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES = "DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG4_MERGED = "DHCPSRV_CFGMGR_CONFIG4_MERGED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG6_MERGED = "DHCPSRV_CFGMGR_CONFIG6_MERGED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIGURE_SERVERID = "DHCPSRV_CFGMGR_CONFIGURE_SERVERID";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DDNS_PARAMETER_IGNORED = "DHCPSRV_CFGMGR_DDNS_PARAMETER_IGNORED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DDNS_PARAMETER_MOVED = "DHCPSRV_CFGMGR_DDNS_PARAMETER_MOVED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET4 = "DHCPSRV_CFGMGR_DEL_SUBNET4";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET6 = "DHCPSRV_CFGMGR_DEL_SUBNET6";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES = "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE = "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES = "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE = "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_IPV4_RESERVATIONS_NON_UNIQUE_IGNORED = "DHCPSRV_CFGMGR_IPV4_RESERVATIONS_NON_UNIQUE_IGNORED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_IPV6_RESERVATIONS_NON_UNIQUE_IGNORED = "DHCPSRV_CFGMGR_IPV6_RESERVATIONS_NON_UNIQUE_IGNORED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_POSSIBLE = "DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_POSSIBLE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NEW_SUBNET4 = "DHCPSRV_CFGMGR_NEW_SUBNET4";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NEW_SUBNET6 = "DHCPSRV_CFGMGR_NEW_SUBNET6";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NO_SUBNET4 = "DHCPSRV_CFGMGR_NO_SUBNET4";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NO_SUBNET6 = "DHCPSRV_CFGMGR_NO_SUBNET6";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ONLY_SUBNET4 = "DHCPSRV_CFGMGR_ONLY_SUBNET4";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ONLY_SUBNET6 = "DHCPSRV_CFGMGR_ONLY_SUBNET6";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_OPTION_DUPLICATE = "DHCPSRV_CFGMGR_OPTION_DUPLICATE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED = "DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_RENEW_GTR_REBIND = "DHCPSRV_CFGMGR_RENEW_GTR_REBIND";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED = "DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT = "DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT = "DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4 = "DHCPSRV_CFGMGR_SUBNET4";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_ADDR = "DHCPSRV_CFGMGR_SUBNET4_ADDR";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_IFACE = "DHCPSRV_CFGMGR_SUBNET4_IFACE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_RELAY = "DHCPSRV_CFGMGR_SUBNET4_RELAY";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6 = "DHCPSRV_CFGMGR_SUBNET6";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6_IFACE = "DHCPSRV_CFGMGR_SUBNET6_IFACE";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6_IFACE_ID = "DHCPSRV_CFGMGR_SUBNET6_IFACE_ID";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6_RELAY = "DHCPSRV_CFGMGR_SUBNET6_RELAY";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL = "DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_UPDATE_SUBNET4 = "DHCPSRV_CFGMGR_UPDATE_SUBNET4";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_UPDATE_SUBNET6 = "DHCPSRV_CFGMGR_UPDATE_SUBNET6";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ADDRESS = "DHCPSRV_CFGMGR_USE_ADDRESS";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ALLOCATOR = "DHCPSRV_CFGMGR_USE_ALLOCATOR";
+extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_UNICAST = "DHCPSRV_CFGMGR_USE_UNICAST";
+extern const isc::log::MessageID DHCPSRV_CLOSE_DB = "DHCPSRV_CLOSE_DB";
+extern const isc::log::MessageID DHCPSRV_CONFIGURED_SUBNET_WITHOUT_ID = "DHCPSRV_CONFIGURED_SUBNET_WITHOUT_ID";
+extern const isc::log::MessageID DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL = "DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL";
+extern const isc::log::MessageID DHCPSRV_DEPRECATED = "DHCPSRV_DEPRECATED";
+extern const isc::log::MessageID DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET = "DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET";
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION = "DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION";
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_HANDLER_NULL = "DHCPSRV_DHCP_DDNS_HANDLER_NULL";
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_NCR_REJECTED = "DHCPSRV_DHCP_DDNS_NCR_REJECTED";
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_NCR_SENT = "DHCPSRV_DHCP_DDNS_NCR_SENT";
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_SENDER_STARTED = "DHCPSRV_DHCP_DDNS_SENDER_STARTED";
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_SENDER_STOPPED = "DHCPSRV_DHCP_DDNS_SENDER_STOPPED";
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES = "DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES";
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE4_RECOVER_SKIP = "DHCPSRV_HOOK_LEASE4_RECOVER_SKIP";
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE4_RENEW_SKIP = "DHCPSRV_HOOK_LEASE4_RENEW_SKIP";
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE4_SELECT_SKIP = "DHCPSRV_HOOK_LEASE4_SELECT_SKIP";
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE6_EXTEND_SKIP = "DHCPSRV_HOOK_LEASE6_EXTEND_SKIP";
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE6_RECOVER_SKIP = "DHCPSRV_HOOK_LEASE6_RECOVER_SKIP";
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE6_SELECT_SKIP = "DHCPSRV_HOOK_LEASE6_SELECT_SKIP";
+extern const isc::log::MessageID DHCPSRV_INVALID_ACCESS = "DHCPSRV_INVALID_ACCESS";
+extern const isc::log::MessageID DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL = "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL";
+extern const isc::log::MessageID DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED = "DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED";
+extern const isc::log::MessageID DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL = "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL";
+extern const isc::log::MessageID DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED = "DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED";
+extern const isc::log::MessageID DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION = "DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION";
+extern const isc::log::MessageID DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION = "DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION";
+extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FAIL = "DHCPSRV_LEASE_SANITY_FAIL";
+extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FAIL_DISCARD = "DHCPSRV_LEASE_SANITY_FAIL_DISCARD";
+extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FIXED = "DHCPSRV_LEASE_SANITY_FIXED";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_ADD_ADDR4 = "DHCPSRV_MEMFILE_ADD_ADDR4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_ADD_ADDR6 = "DHCPSRV_MEMFILE_ADD_ADDR6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BEGIN_BUILD_EXTENDED_INFO_TABLES6 = "DHCPSRV_MEMFILE_BEGIN_BUILD_EXTENDED_INFO_TABLES6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BEGIN_EXTRACT_EXTENDED_INFO4 = "DHCPSRV_MEMFILE_BEGIN_EXTRACT_EXTENDED_INFO4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BEGIN_TRANSACTION = "DHCPSRV_MEMFILE_BEGIN_TRANSACTION";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6 = "DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6_ERROR = "DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6_ERROR";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_COMMIT = "DHCPSRV_MEMFILE_COMMIT";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES = "DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DB = "DHCPSRV_MEMFILE_DB";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_ADDR = "DHCPSRV_MEMFILE_DELETE_ADDR";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4 = "DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6 = "DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START = "DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4 = "DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4_ERROR = "DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4_ERROR";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET4 = "DHCPSRV_MEMFILE_GET4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET6 = "DHCPSRV_MEMFILE_GET6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET6_DUID = "DHCPSRV_MEMFILE_GET6_DUID";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_ADDR4 = "DHCPSRV_MEMFILE_GET_ADDR4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_ADDR6 = "DHCPSRV_MEMFILE_GET_ADDR6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_CLIENTID = "DHCPSRV_MEMFILE_GET_CLIENTID";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_EXPIRED4 = "DHCPSRV_MEMFILE_GET_EXPIRED4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_EXPIRED6 = "DHCPSRV_MEMFILE_GET_EXPIRED6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_HOSTNAME4 = "DHCPSRV_MEMFILE_GET_HOSTNAME4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_HOSTNAME6 = "DHCPSRV_MEMFILE_GET_HOSTNAME6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_HWADDR = "DHCPSRV_MEMFILE_GET_HWADDR";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_IAID_DUID = "DHCPSRV_MEMFILE_GET_IAID_DUID";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID = "DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_LINKADDR6 = "DHCPSRV_MEMFILE_GET_LINKADDR6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_PAGE4 = "DHCPSRV_MEMFILE_GET_PAGE4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_PAGE6 = "DHCPSRV_MEMFILE_GET_PAGE6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_RELAYID4 = "DHCPSRV_MEMFILE_GET_RELAYID4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_RELAYID6 = "DHCPSRV_MEMFILE_GET_RELAYID6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_REMOTEID4 = "DHCPSRV_MEMFILE_GET_REMOTEID4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_REMOTEID6 = "DHCPSRV_MEMFILE_GET_REMOTEID6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID4 = "DHCPSRV_MEMFILE_GET_SUBID4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID6 = "DHCPSRV_MEMFILE_GET_SUBID6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID_CLIENTID = "DHCPSRV_MEMFILE_GET_SUBID_CLIENTID";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID_HWADDR = "DHCPSRV_MEMFILE_GET_SUBID_HWADDR";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_VERSION = "DHCPSRV_MEMFILE_GET_VERSION";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LEASE_FILE_LOAD = "DHCPSRV_MEMFILE_LEASE_FILE_LOAD";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LEASE_LOAD = "DHCPSRV_MEMFILE_LEASE_LOAD";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR = "DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_EXECUTE = "DHCPSRV_MEMFILE_LFC_EXECUTE";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL = "DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL = "DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_SETUP = "DHCPSRV_MEMFILE_LFC_SETUP";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_SPAWN_FAIL = "DHCPSRV_MEMFILE_LFC_SPAWN_FAIL";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_START = "DHCPSRV_MEMFILE_LFC_START";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_UNREGISTER_TIMER_FAILED = "DHCPSRV_MEMFILE_LFC_UNREGISTER_TIMER_FAILED";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_NEEDS_DOWNGRADING = "DHCPSRV_MEMFILE_NEEDS_DOWNGRADING";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_NEEDS_UPGRADING = "DHCPSRV_MEMFILE_NEEDS_UPGRADING";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_NO_STORAGE = "DHCPSRV_MEMFILE_NO_STORAGE";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_READ_HWADDR_FAIL = "DHCPSRV_MEMFILE_READ_HWADDR_FAIL";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_ROLLBACK = "DHCPSRV_MEMFILE_ROLLBACK";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_UPDATE_ADDR4 = "DHCPSRV_MEMFILE_UPDATE_ADDR4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_UPDATE_ADDR6 = "DHCPSRV_MEMFILE_UPDATE_ADDR6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES4 = "DHCPSRV_MEMFILE_WIPE_LEASES4";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED = "DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES6 = "DHCPSRV_MEMFILE_WIPE_LEASES6";
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED = "DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED";
+extern const isc::log::MessageID DHCPSRV_MT_DISABLED_QUEUE_CONTROL = "DHCPSRV_MT_DISABLED_QUEUE_CONTROL";
+extern const isc::log::MessageID DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE = "DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE";
+extern const isc::log::MessageID DHCPSRV_MYSQL_ADD_ADDR4 = "DHCPSRV_MYSQL_ADD_ADDR4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_ADD_ADDR6 = "DHCPSRV_MYSQL_ADD_ADDR6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_BEGIN_TRANSACTION = "DHCPSRV_MYSQL_BEGIN_TRANSACTION";
+extern const isc::log::MessageID DHCPSRV_MYSQL_COMMIT = "DHCPSRV_MYSQL_COMMIT";
+extern const isc::log::MessageID DHCPSRV_MYSQL_DB = "DHCPSRV_MYSQL_DB";
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETED_EXPIRED_RECLAIMED = "DHCPSRV_MYSQL_DELETED_EXPIRED_RECLAIMED";
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETE_ADDR = "DHCPSRV_MYSQL_DELETE_ADDR";
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED4 = "DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED6 = "DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_FATAL_ERROR = "DHCPSRV_MYSQL_FATAL_ERROR";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET4 = "DHCPSRV_MYSQL_GET4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET6 = "DHCPSRV_MYSQL_GET6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_ADDR4 = "DHCPSRV_MYSQL_GET_ADDR4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_ADDR6 = "DHCPSRV_MYSQL_GET_ADDR6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_CLIENTID = "DHCPSRV_MYSQL_GET_CLIENTID";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_DUID = "DHCPSRV_MYSQL_GET_DUID";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_EXPIRED4 = "DHCPSRV_MYSQL_GET_EXPIRED4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_EXPIRED6 = "DHCPSRV_MYSQL_GET_EXPIRED6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_HOSTNAME4 = "DHCPSRV_MYSQL_GET_HOSTNAME4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_HOSTNAME6 = "DHCPSRV_MYSQL_GET_HOSTNAME6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_HWADDR = "DHCPSRV_MYSQL_GET_HWADDR";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_IAID_DUID = "DHCPSRV_MYSQL_GET_IAID_DUID";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_IAID_SUBID_DUID = "DHCPSRV_MYSQL_GET_IAID_SUBID_DUID";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_LINKADDR6 = "DHCPSRV_MYSQL_GET_LINKADDR6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_PAGE4 = "DHCPSRV_MYSQL_GET_PAGE4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_PAGE6 = "DHCPSRV_MYSQL_GET_PAGE6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_RELAYID4 = "DHCPSRV_MYSQL_GET_RELAYID4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_REMOTEID4 = "DHCPSRV_MYSQL_GET_REMOTEID4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID4 = "DHCPSRV_MYSQL_GET_SUBID4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID6 = "DHCPSRV_MYSQL_GET_SUBID6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID_CLIENTID = "DHCPSRV_MYSQL_GET_SUBID_CLIENTID";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID_HWADDR = "DHCPSRV_MYSQL_GET_SUBID_HWADDR";
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_VERSION = "DHCPSRV_MYSQL_GET_VERSION";
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB = "DHCPSRV_MYSQL_HOST_DB";
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_GET_VERSION = "DHCPSRV_MYSQL_HOST_DB_GET_VERSION";
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_READONLY = "DHCPSRV_MYSQL_HOST_DB_READONLY";
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED = "DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED";
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE = "DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE";
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_RECONNECT_FAILED = "DHCPSRV_MYSQL_HOST_DB_RECONNECT_FAILED";
+extern const isc::log::MessageID DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED = "DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED";
+extern const isc::log::MessageID DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE = "DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE";
+extern const isc::log::MessageID DHCPSRV_MYSQL_LEASE_DB_RECONNECT_FAILED = "DHCPSRV_MYSQL_LEASE_DB_RECONNECT_FAILED";
+extern const isc::log::MessageID DHCPSRV_MYSQL_NEGATIVE_LEASES_STAT = "DHCPSRV_MYSQL_NEGATIVE_LEASES_STAT";
+extern const isc::log::MessageID DHCPSRV_MYSQL_NO_TLS = "DHCPSRV_MYSQL_NO_TLS";
+extern const isc::log::MessageID DHCPSRV_MYSQL_ROLLBACK = "DHCPSRV_MYSQL_ROLLBACK";
+extern const isc::log::MessageID DHCPSRV_MYSQL_START_TRANSACTION = "DHCPSRV_MYSQL_START_TRANSACTION";
+extern const isc::log::MessageID DHCPSRV_MYSQL_TLS_CIPHER = "DHCPSRV_MYSQL_TLS_CIPHER";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPDATE_ADDR4 = "DHCPSRV_MYSQL_UPDATE_ADDR4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPDATE_ADDR6 = "DHCPSRV_MYSQL_UPDATE_ADDR6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6 = "DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_ERROR = "DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_ERROR";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_PAGE = "DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_PAGE";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4 = "DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_ERROR = "DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_ERROR";
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_PAGE = "DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_PAGE";
+extern const isc::log::MessageID DHCPSRV_NOTYPE_DB = "DHCPSRV_NOTYPE_DB";
+extern const isc::log::MessageID DHCPSRV_NO_SOCKETS_OPEN = "DHCPSRV_NO_SOCKETS_OPEN";
+extern const isc::log::MessageID DHCPSRV_OPEN_SOCKET_FAIL = "DHCPSRV_OPEN_SOCKET_FAIL";
+extern const isc::log::MessageID DHCPSRV_PGSQL_ADD_ADDR4 = "DHCPSRV_PGSQL_ADD_ADDR4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_ADD_ADDR6 = "DHCPSRV_PGSQL_ADD_ADDR6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_BEGIN_TRANSACTION = "DHCPSRV_PGSQL_BEGIN_TRANSACTION";
+extern const isc::log::MessageID DHCPSRV_PGSQL_COMMIT = "DHCPSRV_PGSQL_COMMIT";
+extern const isc::log::MessageID DHCPSRV_PGSQL_DB = "DHCPSRV_PGSQL_DB";
+extern const isc::log::MessageID DHCPSRV_PGSQL_DEALLOC_ERROR = "DHCPSRV_PGSQL_DEALLOC_ERROR";
+extern const isc::log::MessageID DHCPSRV_PGSQL_DELETE_ADDR = "DHCPSRV_PGSQL_DELETE_ADDR";
+extern const isc::log::MessageID DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED4 = "DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED6 = "DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_FATAL_ERROR = "DHCPSRV_PGSQL_FATAL_ERROR";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET4 = "DHCPSRV_PGSQL_GET4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET6 = "DHCPSRV_PGSQL_GET6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_ADDR4 = "DHCPSRV_PGSQL_GET_ADDR4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_ADDR6 = "DHCPSRV_PGSQL_GET_ADDR6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_CLIENTID = "DHCPSRV_PGSQL_GET_CLIENTID";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_DUID = "DHCPSRV_PGSQL_GET_DUID";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_EXPIRED4 = "DHCPSRV_PGSQL_GET_EXPIRED4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_EXPIRED6 = "DHCPSRV_PGSQL_GET_EXPIRED6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_HOSTNAME4 = "DHCPSRV_PGSQL_GET_HOSTNAME4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_HOSTNAME6 = "DHCPSRV_PGSQL_GET_HOSTNAME6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_HWADDR = "DHCPSRV_PGSQL_GET_HWADDR";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_IAID_DUID = "DHCPSRV_PGSQL_GET_IAID_DUID";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_IAID_SUBID_DUID = "DHCPSRV_PGSQL_GET_IAID_SUBID_DUID";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_LINKADDR6 = "DHCPSRV_PGSQL_GET_LINKADDR6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_PAGE4 = "DHCPSRV_PGSQL_GET_PAGE4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_PAGE6 = "DHCPSRV_PGSQL_GET_PAGE6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_RELAYID4 = "DHCPSRV_PGSQL_GET_RELAYID4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_REMOTEID4 = "DHCPSRV_PGSQL_GET_REMOTEID4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID4 = "DHCPSRV_PGSQL_GET_SUBID4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID6 = "DHCPSRV_PGSQL_GET_SUBID6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID_CLIENTID = "DHCPSRV_PGSQL_GET_SUBID_CLIENTID";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID_HWADDR = "DHCPSRV_PGSQL_GET_SUBID_HWADDR";
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_VERSION = "DHCPSRV_PGSQL_GET_VERSION";
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB = "DHCPSRV_PGSQL_HOST_DB";
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_GET_VERSION = "DHCPSRV_PGSQL_HOST_DB_GET_VERSION";
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_READONLY = "DHCPSRV_PGSQL_HOST_DB_READONLY";
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED = "DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED";
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE = "DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE";
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED = "DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED";
+extern const isc::log::MessageID DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED = "DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED";
+extern const isc::log::MessageID DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE = "DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE";
+extern const isc::log::MessageID DHCPSRV_PGSQL_LEASE_DB_RECONNECT_FAILED = "DHCPSRV_PGSQL_LEASE_DB_RECONNECT_FAILED";
+extern const isc::log::MessageID DHCPSRV_PGSQL_NEGATIVE_LEASES_STAT = "DHCPSRV_PGSQL_NEGATIVE_LEASES_STAT";
+extern const isc::log::MessageID DHCPSRV_PGSQL_NO_TLS_SUPPORT = "DHCPSRV_PGSQL_NO_TLS_SUPPORT";
+extern const isc::log::MessageID DHCPSRV_PGSQL_ROLLBACK = "DHCPSRV_PGSQL_ROLLBACK";
+extern const isc::log::MessageID DHCPSRV_PGSQL_START_TRANSACTION = "DHCPSRV_PGSQL_START_TRANSACTION";
+extern const isc::log::MessageID DHCPSRV_PGSQL_TLS_SUPPORT = "DHCPSRV_PGSQL_TLS_SUPPORT";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPDATE_ADDR4 = "DHCPSRV_PGSQL_UPDATE_ADDR4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPDATE_ADDR6 = "DHCPSRV_PGSQL_UPDATE_ADDR6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6 = "DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_ERROR = "DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_ERROR";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_PAGE = "DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_PAGE";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4 = "DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_ERROR = "DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_ERROR";
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_PAGE = "DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_PAGE";
+extern const isc::log::MessageID DHCPSRV_QUEUE_NCR = "DHCPSRV_QUEUE_NCR";
+extern const isc::log::MessageID DHCPSRV_QUEUE_NCR_FAILED = "DHCPSRV_QUEUE_NCR_FAILED";
+extern const isc::log::MessageID DHCPSRV_QUEUE_NCR_SKIP = "DHCPSRV_QUEUE_NCR_SKIP";
+extern const isc::log::MessageID DHCPSRV_SUBNET4O6_SELECT_FAILED = "DHCPSRV_SUBNET4O6_SELECT_FAILED";
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_NO_MATCH = "DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_NO_MATCH";
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_NO_MATCH = "DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_NO_MATCH";
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_NO_MATCH = "DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_NO_MATCH";
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_NO_RAI_OPTIONS = "DHCPSRV_SUBNET4_SELECT_NO_RAI_OPTIONS";
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_NO_RELAY_ADDRESS = "DHCPSRV_SUBNET4_SELECT_NO_RELAY_ADDRESS";
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_NO_USABLE_ADDRESS = "DHCPSRV_SUBNET4_SELECT_NO_USABLE_ADDRESS";
+extern const isc::log::MessageID DHCPSRV_SUBNET6_SELECT_BY_ADDRESS_NO_MATCH = "DHCPSRV_SUBNET6_SELECT_BY_ADDRESS_NO_MATCH";
+extern const isc::log::MessageID DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_ID_NO_MATCH = "DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_ID_NO_MATCH";
+extern const isc::log::MessageID DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_NO_MATCH = "DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_NO_MATCH";
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_CALLBACK_FAILED = "DHCPSRV_TIMERMGR_CALLBACK_FAILED";
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_REGISTER_TIMER = "DHCPSRV_TIMERMGR_REGISTER_TIMER";
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION = "DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION";
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_START_TIMER = "DHCPSRV_TIMERMGR_START_TIMER";
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_STOP_TIMER = "DHCPSRV_TIMERMGR_STOP_TIMER";
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS = "DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS";
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_UNREGISTER_TIMER = "DHCPSRV_TIMERMGR_UNREGISTER_TIMER";
+extern const isc::log::MessageID DHCPSRV_UNEXPECTED_NAME = "DHCPSRV_UNEXPECTED_NAME";
+extern const isc::log::MessageID DHCPSRV_UNKNOWN_DB = "DHCPSRV_UNKNOWN_DB";
+
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DHCPSRV_CFGMGR_ADD_IFACE", "listening on interface %1",
+ "DHCPSRV_CFGMGR_ADD_SUBNET4", "adding subnet %1",
+ "DHCPSRV_CFGMGR_ADD_SUBNET6", "adding subnet %1",
+ "DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE", "enabling listening on all interfaces",
+ "DHCPSRV_CFGMGR_CFG_DHCP_DDNS", "Setting DHCP-DDNS configuration to: %1",
+ "DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES", "stop listening on all interfaces",
+ "DHCPSRV_CFGMGR_CONFIG4_MERGED", "Configuration backend data has been merged.",
+ "DHCPSRV_CFGMGR_CONFIG6_MERGED", "Configuration backend data has been merged.",
+ "DHCPSRV_CFGMGR_CONFIGURE_SERVERID", "server configuration includes specification of a server identifier",
+ "DHCPSRV_CFGMGR_DDNS_PARAMETER_IGNORED", "dhcp-ddns:%1 is deprecated, using existing global:%2",
+ "DHCPSRV_CFGMGR_DDNS_PARAMETER_MOVED", "dhcp-ddns:%1 is deprecated, moving it to global:%2",
+ "DHCPSRV_CFGMGR_DEL_SUBNET4", "IPv4 subnet %1 removed",
+ "DHCPSRV_CFGMGR_DEL_SUBNET6", "IPv6 subnet %1 removed",
+ "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES", "populating free address leases for the FLQ allocator in subnet %1; it can take a while!",
+ "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE", "populated %1 free address leases for the FLQ allocator in subnet %2 in %3",
+ "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES", "populating free prefix leases for the FLQ allocator in subnet %1; it can take a while!",
+ "DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE", "populated %1 free prefix leases for the FLQ allocator in subnet %2 completed in %3",
+ "DHCPSRV_CFGMGR_IPV4_RESERVATIONS_NON_UNIQUE_IGNORED", "ignoring \"ip-reservations-unique\" setting because at least one of the host database backends does not support non-unique IP reservations in a subnet",
+ "DHCPSRV_CFGMGR_IPV6_RESERVATIONS_NON_UNIQUE_IGNORED", "ignoring \"ip-reservations-unique\" setting because at least one of the host database backends does not support non unique IP reservations in a subnet",
+ "DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_POSSIBLE", "setting \"ip-reservations-unique\" from false to true poses a risk that some host backends may still contain multiple reservations for the same IP address",
+ "DHCPSRV_CFGMGR_NEW_SUBNET4", "a new subnet has been added to configuration: %1",
+ "DHCPSRV_CFGMGR_NEW_SUBNET6", "a new subnet has been added to configuration: %1",
+ "DHCPSRV_CFGMGR_NO_SUBNET4", "no suitable subnet is defined for address hint %1",
+ "DHCPSRV_CFGMGR_NO_SUBNET6", "no suitable subnet is defined for address hint %1",
+ "DHCPSRV_CFGMGR_ONLY_SUBNET4", "retrieved subnet %1 for address hint %2",
+ "DHCPSRV_CFGMGR_ONLY_SUBNET6", "retrieved subnet %1 for address hint %2",
+ "DHCPSRV_CFGMGR_OPTION_DUPLICATE", "multiple options with the code: %1 added to the subnet: %2",
+ "DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED", "\"relay\" uses \"ip-address\", which has been deprecated, please use \"ip-addresses\": %1",
+ "DHCPSRV_CFGMGR_RENEW_GTR_REBIND", "in %1, the value of renew-timer %2 is greater than the value of rebind-timer %3, ignoring renew-timer",
+ "DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED", "use of raw sockets is unsupported on this OS, UDP sockets will be used",
+ "DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT", "\"dhcp-socket-type\" not specified , using default socket type %1",
+ "DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT", "using socket type %1",
+ "DHCPSRV_CFGMGR_SUBNET4", "retrieved subnet %1 for address hint %2",
+ "DHCPSRV_CFGMGR_SUBNET4_ADDR", "selected subnet %1 for packet received by matching address %2",
+ "DHCPSRV_CFGMGR_SUBNET4_IFACE", "selected subnet %1 for packet received over interface %2",
+ "DHCPSRV_CFGMGR_SUBNET4_RELAY", "selected subnet %1, because of matching relay addr %2",
+ "DHCPSRV_CFGMGR_SUBNET6", "retrieved subnet %1 for address hint %2",
+ "DHCPSRV_CFGMGR_SUBNET6_IFACE", "selected subnet %1 for packet received over interface %2",
+ "DHCPSRV_CFGMGR_SUBNET6_IFACE_ID", "selected subnet %1 (interface-id match) for incoming packet",
+ "DHCPSRV_CFGMGR_SUBNET6_RELAY", "selected subnet %1, because of matching relay addr %2",
+ "DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL", "specified link local address %1 for unicast traffic on interface %2",
+ "DHCPSRV_CFGMGR_UPDATE_SUBNET4", "updating subnet %1 (result %2)",
+ "DHCPSRV_CFGMGR_UPDATE_SUBNET6", "updating subnet %1 (result %2)",
+ "DHCPSRV_CFGMGR_USE_ADDRESS", "listening on address %1, on interface %2",
+ "DHCPSRV_CFGMGR_USE_ALLOCATOR", "using the %1 allocator for %2 leases in subnet %3",
+ "DHCPSRV_CFGMGR_USE_UNICAST", "listening on unicast address %1, on interface %2",
+ "DHCPSRV_CLOSE_DB", "closing currently open %1 database",
+ "DHCPSRV_CONFIGURED_SUBNET_WITHOUT_ID", "a subnet was configured without an id: %1",
+ "DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL", "ddns-ttl-percent %1 of lease lifetime %2 is too small, ignoring it",
+ "DHCPSRV_DEPRECATED", "This configuration is using a deprecated feature: %1",
+ "DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET", "received bad DHCPv4o6 packet: %1",
+ "DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION", "error handler for DHCP_DDNS IO generated an expected exception: %1",
+ "DHCPSRV_DHCP_DDNS_HANDLER_NULL", "error handler for DHCP_DDNS IO is not set.",
+ "DHCPSRV_DHCP_DDNS_NCR_REJECTED", "NameChangeRequest rejected by the sender: %1, ncr: %2",
+ "DHCPSRV_DHCP_DDNS_NCR_SENT", "NameChangeRequest sent to kea-dhcp-ddns: %1",
+ "DHCPSRV_DHCP_DDNS_SENDER_STARTED", "NameChangeRequest sender has been started: %1",
+ "DHCPSRV_DHCP_DDNS_SENDER_STOPPED", "NameChangeRequest sender has been stopped.",
+ "DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES", "DHCP_DDNS updates are being suspended.",
+ "DHCPSRV_HOOK_LEASE4_RECOVER_SKIP", "DHCPv4 lease %1 was not recovered from the declined state because a callout set the skip status.",
+ "DHCPSRV_HOOK_LEASE4_RENEW_SKIP", "DHCPv4 lease was not renewed because a callout set the skip flag.",
+ "DHCPSRV_HOOK_LEASE4_SELECT_SKIP", "Lease4 creation was skipped, because of callout skip flag.",
+ "DHCPSRV_HOOK_LEASE6_EXTEND_SKIP", "DHCPv6 lease lifetime was not extended because a callout set the skip flag for message %1",
+ "DHCPSRV_HOOK_LEASE6_RECOVER_SKIP", "DHCPv6 lease %1 was not recovered from declined state because a callout set the skip status.",
+ "DHCPSRV_HOOK_LEASE6_SELECT_SKIP", "Lease6 (non-temporary) creation was skipped, because of callout skip flag.",
+ "DHCPSRV_INVALID_ACCESS", "invalid database access string: %1",
+ "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL", "extended info for lease %1 failed checks (%2)",
+ "DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED", "extended info for lease %1 was upgraded",
+ "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL", "extended info for lease %1 failed checks (%2)",
+ "DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED", "extended info for lease %1 was upgraded",
+ "DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION", "exception occurred in a lease manager callback for callback type %1, subnet id %2, and lease %3: %4",
+ "DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION", "unknown exception occurred in a lease manager callback for callback type %1, subnet id %2, and lease %3",
+ "DHCPSRV_LEASE_SANITY_FAIL", "The lease %1 with subnet-id %2 failed subnet-id checks (%3).",
+ "DHCPSRV_LEASE_SANITY_FAIL_DISCARD", "The lease %1 with subnet-id %2 failed subnet-id checks (%3) and was dropped.",
+ "DHCPSRV_LEASE_SANITY_FIXED", "The lease %1 with subnet-id %2 failed subnet-id checks, but was corrected to subnet-id %3.",
+ "DHCPSRV_MEMFILE_ADD_ADDR4", "adding IPv4 lease with address %1",
+ "DHCPSRV_MEMFILE_ADD_ADDR6", "adding IPv6 lease with address %1",
+ "DHCPSRV_MEMFILE_BEGIN_BUILD_EXTENDED_INFO_TABLES6", "building extended info tables with %1 sanity check level%2, tables %3",
+ "DHCPSRV_MEMFILE_BEGIN_EXTRACT_EXTENDED_INFO4", "extract extended info with %1 sanity check level%2",
+ "DHCPSRV_MEMFILE_BEGIN_TRANSACTION", "committing to memory file database",
+ "DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6", "building extended info tables saw %1 leases, extended info sanity checks modified %2 / updated %3 leases and %4 leases were entered into tables",
+ "DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6_ERROR", "building extended info tables got an exception on the lease for %1: %2",
+ "DHCPSRV_MEMFILE_COMMIT", "committing to memory file database",
+ "DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES", "running LFC now to convert lease files to the current schema: %1.%2",
+ "DHCPSRV_MEMFILE_DB", "opening memory file lease database: %1",
+ "DHCPSRV_MEMFILE_DELETE_ADDR", "deleting lease for address %1",
+ "DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4", "deleting reclaimed IPv4 leases that expired more than %1 seconds ago",
+ "DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6", "deleting reclaimed IPv6 leases that expired more than %1 seconds ago",
+ "DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START", "starting deletion of %1 expired-reclaimed leases",
+ "DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4", "extracting extended info saw %1 leases, extended info sanity checks modified %2 / updated %3 leases and %4 leases have relay or remote id",
+ "DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4_ERROR", "extracting extended info got an exception on the lease for %1: %2",
+ "DHCPSRV_MEMFILE_GET4", "obtaining all IPv4 leases",
+ "DHCPSRV_MEMFILE_GET6", "obtaining all IPv6 leases",
+ "DHCPSRV_MEMFILE_GET6_DUID", "obtaining IPv6 leases for DUID %1",
+ "DHCPSRV_MEMFILE_GET_ADDR4", "obtaining IPv4 lease for address %1",
+ "DHCPSRV_MEMFILE_GET_ADDR6", "obtaining IPv6 lease for address %1 and lease type %2",
+ "DHCPSRV_MEMFILE_GET_CLIENTID", "obtaining IPv4 leases for client ID %1",
+ "DHCPSRV_MEMFILE_GET_EXPIRED4", "obtaining maximum %1 of expired IPv4 leases",
+ "DHCPSRV_MEMFILE_GET_EXPIRED6", "obtaining maximum %1 of expired IPv6 leases",
+ "DHCPSRV_MEMFILE_GET_HOSTNAME4", "obtaining IPv4 leases for hostname %1",
+ "DHCPSRV_MEMFILE_GET_HOSTNAME6", "obtaining IPv6 leases for hostname %1",
+ "DHCPSRV_MEMFILE_GET_HWADDR", "obtaining IPv4 leases for hardware address %1",
+ "DHCPSRV_MEMFILE_GET_IAID_DUID", "obtaining IPv6 leases for IAID %1 and DUID %2 and lease type %3",
+ "DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID", "obtaining IPv6 leases for IAID %1, Subnet ID %2, DUID %3 and lease type %4",
+ "DHCPSRV_MEMFILE_GET_LINKADDR6", "obtaining at most %1 IPv6 leases starting from address %2 with link %3/%4",
+ "DHCPSRV_MEMFILE_GET_PAGE4", "obtaining at most %1 IPv4 leases starting from address %2",
+ "DHCPSRV_MEMFILE_GET_PAGE6", "obtaining at most %1 IPv6 leases starting from address %2",
+ "DHCPSRV_MEMFILE_GET_RELAYID4", "obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5",
+ "DHCPSRV_MEMFILE_GET_RELAYID6", "obtaining at most %1 IPv6 leases starting from address %2 with relay id %3 and link %4/%5",
+ "DHCPSRV_MEMFILE_GET_REMOTEID4", "obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5",
+ "DHCPSRV_MEMFILE_GET_REMOTEID6", "obtaining at most %1 IPv6 leases starting from address %2 with remote id %3 and link %4/%5",
+ "DHCPSRV_MEMFILE_GET_SUBID4", "obtaining IPv4 leases for subnet ID %1",
+ "DHCPSRV_MEMFILE_GET_SUBID6", "obtaining IPv6 leases for subnet ID %1",
+ "DHCPSRV_MEMFILE_GET_SUBID_CLIENTID", "obtaining IPv4 lease for subnet ID %1 and client ID %2",
+ "DHCPSRV_MEMFILE_GET_SUBID_HWADDR", "obtaining IPv4 lease for subnet ID %1 and hardware address %2",
+ "DHCPSRV_MEMFILE_GET_VERSION", "obtaining schema version information",
+ "DHCPSRV_MEMFILE_LEASE_FILE_LOAD", "loading leases from file %1",
+ "DHCPSRV_MEMFILE_LEASE_LOAD", "loading lease %1",
+ "DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR", "discarding row %1, error: %2",
+ "DHCPSRV_MEMFILE_LFC_EXECUTE", "executing Lease File Cleanup using: %1",
+ "DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL", "failed to rename the current lease file %1 to %2, reason: %3",
+ "DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL", "failed to reopen lease file %1 after preparing input file for lease file cleanup, reason: %2, new leases will not persist!",
+ "DHCPSRV_MEMFILE_LFC_SETUP", "setting up the Lease File Cleanup interval to %1 sec",
+ "DHCPSRV_MEMFILE_LFC_SPAWN_FAIL", "lease file cleanup failed to run because kea-lfc process couldn't be spawned",
+ "DHCPSRV_MEMFILE_LFC_START", "starting Lease File Cleanup",
+ "DHCPSRV_MEMFILE_LFC_UNREGISTER_TIMER_FAILED", "failed to unregister timer 'memfile-lfc': %1",
+ "DHCPSRV_MEMFILE_NEEDS_DOWNGRADING", "version of lease file: %1 schema is later than version %2",
+ "DHCPSRV_MEMFILE_NEEDS_UPGRADING", "version of lease file: %1 schema is earlier than version %2",
+ "DHCPSRV_MEMFILE_NO_STORAGE", "running in non-persistent mode, leases will be lost after restart",
+ "DHCPSRV_MEMFILE_READ_HWADDR_FAIL", "failed to read hardware address from lease file: %1",
+ "DHCPSRV_MEMFILE_ROLLBACK", "rolling back memory file database",
+ "DHCPSRV_MEMFILE_UPDATE_ADDR4", "updating IPv4 lease for address %1",
+ "DHCPSRV_MEMFILE_UPDATE_ADDR6", "updating IPv6 lease for address %1",
+ "DHCPSRV_MEMFILE_WIPE_LEASES4", "removing all IPv4 leases from subnet %1",
+ "DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED", "removing all IPv4 leases from subnet %1 finished, removed %2 leases",
+ "DHCPSRV_MEMFILE_WIPE_LEASES6", "removing all IPv6 leases from subnet %1",
+ "DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED", "removing all IPv6 leases from subnet %1 finished, removed %2 leases",
+ "DHCPSRV_MT_DISABLED_QUEUE_CONTROL", "disabling dhcp queue control when multi-threading is enabled.",
+ "DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE", "current configuration will result in opening multiple broadcast capable sockets on some interfaces and some DHCP messages may be duplicated",
+ "DHCPSRV_MYSQL_ADD_ADDR4", "adding IPv4 lease with address %1",
+ "DHCPSRV_MYSQL_ADD_ADDR6", "adding IPv6 lease with address %1, lease type %2",
+ "DHCPSRV_MYSQL_BEGIN_TRANSACTION", "committing to MySQL database",
+ "DHCPSRV_MYSQL_COMMIT", "committing to MySQL database",
+ "DHCPSRV_MYSQL_DB", "opening MySQL lease database: %1",
+ "DHCPSRV_MYSQL_DELETED_EXPIRED_RECLAIMED", "deleted %1 reclaimed leases from the database",
+ "DHCPSRV_MYSQL_DELETE_ADDR", "deleting lease for address %1",
+ "DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED4", "deleting reclaimed IPv4 leases that expired more than %1 seconds ago",
+ "DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED6", "deleting reclaimed IPv6 leases that expired more than %1 seconds ago",
+ "DHCPSRV_MYSQL_FATAL_ERROR", "Unrecoverable MySQL error occurred: %1 for <%2>, reason: %3 (error code: %4).",
+ "DHCPSRV_MYSQL_GET4", "obtaining all IPv4 leases",
+ "DHCPSRV_MYSQL_GET6", "obtaining all IPv6 leases",
+ "DHCPSRV_MYSQL_GET_ADDR4", "obtaining IPv4 lease for address %1",
+ "DHCPSRV_MYSQL_GET_ADDR6", "obtaining IPv6 lease for address %1, lease type %2",
+ "DHCPSRV_MYSQL_GET_CLIENTID", "obtaining IPv4 leases for client ID %1",
+ "DHCPSRV_MYSQL_GET_DUID", "obtaining IPv6 lease for duid %1,",
+ "DHCPSRV_MYSQL_GET_EXPIRED4", "obtaining maximum %1 of expired IPv4 leases",
+ "DHCPSRV_MYSQL_GET_EXPIRED6", "obtaining maximum %1 of expired IPv6 leases",
+ "DHCPSRV_MYSQL_GET_HOSTNAME4", "obtaining IPv4 leases for hostname %1",
+ "DHCPSRV_MYSQL_GET_HOSTNAME6", "obtaining IPv6 leases for hostname %1",
+ "DHCPSRV_MYSQL_GET_HWADDR", "obtaining IPv4 leases for hardware address %1",
+ "DHCPSRV_MYSQL_GET_IAID_DUID", "obtaining IPv6 leases for IAID %1, DUID %2, lease type %3",
+ "DHCPSRV_MYSQL_GET_IAID_SUBID_DUID", "obtaining IPv6 leases for IAID %1, Subnet ID %2, DUID %3, lease type %4",
+ "DHCPSRV_MYSQL_GET_LINKADDR6", "obtaining at most %1 IPv6 leases starting from address %2 with link %3/%4",
+ "DHCPSRV_MYSQL_GET_PAGE4", "obtaining at most %1 IPv4 leases starting from address %2",
+ "DHCPSRV_MYSQL_GET_PAGE6", "obtaining at most %1 IPv6 leases starting from address %2",
+ "DHCPSRV_MYSQL_GET_RELAYID4", "obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5",
+ "DHCPSRV_MYSQL_GET_REMOTEID4", "obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5",
+ "DHCPSRV_MYSQL_GET_SUBID4", "obtaining IPv4 leases for subnet ID %1",
+ "DHCPSRV_MYSQL_GET_SUBID6", "obtaining IPv6 leases for subnet ID %1",
+ "DHCPSRV_MYSQL_GET_SUBID_CLIENTID", "obtaining IPv4 lease for subnet ID %1 and client ID %2",
+ "DHCPSRV_MYSQL_GET_SUBID_HWADDR", "obtaining IPv4 lease for subnet ID %1 and hardware address %2",
+ "DHCPSRV_MYSQL_GET_VERSION", "obtaining schema version information",
+ "DHCPSRV_MYSQL_HOST_DB", "opening MySQL hosts database: %1",
+ "DHCPSRV_MYSQL_HOST_DB_GET_VERSION", "obtaining schema version information for the MySQL hosts database",
+ "DHCPSRV_MYSQL_HOST_DB_READONLY", "MySQL host database opened for read access only",
+ "DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED", "database reconnect failed: %1",
+ "DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE", "scheduling attempt %1 of %2 in %3 milliseconds",
+ "DHCPSRV_MYSQL_HOST_DB_RECONNECT_FAILED", "maximum number of database reconnect attempts: %1, has been exhausted without success",
+ "DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED", "database reconnect failed: %1",
+ "DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE", "scheduling attempt %1 of %2 in %3 milliseconds",
+ "DHCPSRV_MYSQL_LEASE_DB_RECONNECT_FAILED", "maximum number of database reconnect attempts: %1, has been exhausted without success",
+ "DHCPSRV_MYSQL_NEGATIVE_LEASES_STAT", "recount of leases returned a negative value",
+ "DHCPSRV_MYSQL_NO_TLS", "TLS was required but is not used",
+ "DHCPSRV_MYSQL_ROLLBACK", "rolling back MySQL database",
+ "DHCPSRV_MYSQL_START_TRANSACTION", "starting new MySQL transaction",
+ "DHCPSRV_MYSQL_TLS_CIPHER", "TLS cipher: %1",
+ "DHCPSRV_MYSQL_UPDATE_ADDR4", "updating IPv4 lease for address %1",
+ "DHCPSRV_MYSQL_UPDATE_ADDR6", "updating IPv6 lease for address %1, lease type %2",
+ "DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6", "upgrading IPv6 leases done in %1 pages with %2 updated leases",
+ "DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_ERROR", "upgrading binary address for IPv6 lease at %1 failed with %2",
+ "DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_PAGE", "upgrading IPv6 lease binary addresses at page %1 starting at %2 (updated %3)",
+ "DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4", "upgrading IPv4 leases done in %1 pages with %2 updated leases",
+ "DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_ERROR", "upgrading extending info for IPv4 lease at %1 failed with %2",
+ "DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_PAGE", "upgrading IPv4 lease extended info at page %1 starting at %2 (updated %3)",
+ "DHCPSRV_NOTYPE_DB", "no 'type' keyword to determine database backend: %1",
+ "DHCPSRV_NO_SOCKETS_OPEN", "no interface configured to listen to DHCP traffic",
+ "DHCPSRV_OPEN_SOCKET_FAIL", "failed to open socket: %1",
+ "DHCPSRV_PGSQL_ADD_ADDR4", "adding IPv4 lease with address %1",
+ "DHCPSRV_PGSQL_ADD_ADDR6", "adding IPv6 lease with address %1, lease type %2",
+ "DHCPSRV_PGSQL_BEGIN_TRANSACTION", "committing to PostgreSQL database",
+ "DHCPSRV_PGSQL_COMMIT", "committing to PostgreSQL database",
+ "DHCPSRV_PGSQL_DB", "opening PostgreSQL lease database: %1",
+ "DHCPSRV_PGSQL_DEALLOC_ERROR", "An error occurred deallocating SQL statements while closing the PostgreSQL lease database: %1",
+ "DHCPSRV_PGSQL_DELETE_ADDR", "deleting lease for address %1",
+ "DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED4", "deleting reclaimed IPv4 leases that expired more than %1 seconds ago",
+ "DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED6", "deleting reclaimed IPv6 leases that expired more than %1 seconds ago",
+ "DHCPSRV_PGSQL_FATAL_ERROR", "Unrecoverable PostgreSQL error occurred: Statement: <%1>, reason: %2 (error code: %3).",
+ "DHCPSRV_PGSQL_GET4", "obtaining all IPv4 leases",
+ "DHCPSRV_PGSQL_GET6", "obtaining all IPv6 leases",
+ "DHCPSRV_PGSQL_GET_ADDR4", "obtaining IPv4 lease for address %1",
+ "DHCPSRV_PGSQL_GET_ADDR6", "obtaining IPv6 lease for address %1 (lease type %2)",
+ "DHCPSRV_PGSQL_GET_CLIENTID", "obtaining IPv4 leases for client ID %1",
+ "DHCPSRV_PGSQL_GET_DUID", "obtaining IPv6 leases for DUID %1,",
+ "DHCPSRV_PGSQL_GET_EXPIRED4", "obtaining maximum %1 of expired IPv4 leases",
+ "DHCPSRV_PGSQL_GET_EXPIRED6", "obtaining maximum %1 of expired IPv6 leases",
+ "DHCPSRV_PGSQL_GET_HOSTNAME4", "obtaining IPv4 leases for hostname %1",
+ "DHCPSRV_PGSQL_GET_HOSTNAME6", "obtaining IPv6 leases for hostname %1",
+ "DHCPSRV_PGSQL_GET_HWADDR", "obtaining IPv4 leases for hardware address %1",
+ "DHCPSRV_PGSQL_GET_IAID_DUID", "obtaining IPv4 leases for IAID %1 and DUID %2, lease type %3",
+ "DHCPSRV_PGSQL_GET_IAID_SUBID_DUID", "obtaining IPv4 leases for IAID %1, Subnet ID %2, DUID %3, and lease type %4",
+ "DHCPSRV_PGSQL_GET_LINKADDR6", "obtaining at most %1 IPv6 leases starting from address %2 with link %3/%4",
+ "DHCPSRV_PGSQL_GET_PAGE4", "obtaining at most %1 IPv4 leases starting from address %2",
+ "DHCPSRV_PGSQL_GET_PAGE6", "obtaining at most %1 IPv6 leases starting from address %2",
+ "DHCPSRV_PGSQL_GET_RELAYID4", "obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5",
+ "DHCPSRV_PGSQL_GET_REMOTEID4", "obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5",
+ "DHCPSRV_PGSQL_GET_SUBID4", "obtaining IPv4 leases for subnet ID %1",
+ "DHCPSRV_PGSQL_GET_SUBID6", "obtaining IPv6 leases for subnet ID %1",
+ "DHCPSRV_PGSQL_GET_SUBID_CLIENTID", "obtaining IPv4 lease for subnet ID %1 and client ID %2",
+ "DHCPSRV_PGSQL_GET_SUBID_HWADDR", "obtaining IPv4 lease for subnet ID %1 and hardware address %2",
+ "DHCPSRV_PGSQL_GET_VERSION", "obtaining schema version information",
+ "DHCPSRV_PGSQL_HOST_DB", "opening PostgreSQL hosts database: %1",
+ "DHCPSRV_PGSQL_HOST_DB_GET_VERSION", "obtaining schema version information for the PostgreSQL hosts database",
+ "DHCPSRV_PGSQL_HOST_DB_READONLY", "PostgreSQL host database opened for read access only",
+ "DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED", "database reconnect failed: %1",
+ "DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE", "scheduling attempt %1 of %2 in %3 milliseconds",
+ "DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED", "maximum number of database reconnect attempts: %1, has been exhausted without success",
+ "DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED", "database reconnect failed: %1",
+ "DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE", "scheduling attempt %1 of %2 in %3 milliseconds",
+ "DHCPSRV_PGSQL_LEASE_DB_RECONNECT_FAILED", "maximum number of database reconnect attempts: %1, has been exhausted without success",
+ "DHCPSRV_PGSQL_NEGATIVE_LEASES_STAT", "recount of leases returned a negative value",
+ "DHCPSRV_PGSQL_NO_TLS_SUPPORT", "Attempt to configure TLS (unsupported for PostgreSQL): %1",
+ "DHCPSRV_PGSQL_ROLLBACK", "rolling back PostgreSQL database",
+ "DHCPSRV_PGSQL_START_TRANSACTION", "starting a new PostgreSQL transaction",
+ "DHCPSRV_PGSQL_TLS_SUPPORT", "Attempt to configure TLS: %1",
+ "DHCPSRV_PGSQL_UPDATE_ADDR4", "updating IPv4 lease for address %1",
+ "DHCPSRV_PGSQL_UPDATE_ADDR6", "updating IPv6 lease for address %1, lease type %2",
+ "DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6", "upgrading IPv6 leases done in %1 pages with %2 updated leases",
+ "DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_ERROR", "upgrading binary address for IPv6 lease at %1 failed with %2",
+ "DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_PAGE", "upgrading IPv6 lease binary addresses at page %1 starting at %2 (updated %3)",
+ "DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4", "upgrading IPv4 leases done in %1 pages with %2 updated leases",
+ "DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_ERROR", "upgrading extending info for IPv4 lease at %1 failed with %2",
+ "DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_PAGE", "upgrading IPv4 lease extended info at page %1 starting at %2 (updated %3)",
+ "DHCPSRV_QUEUE_NCR", "%1: Name change request to %2 DNS entry queued: %3",
+ "DHCPSRV_QUEUE_NCR_FAILED", "%1: queuing %2 name change request failed for lease %3: %4",
+ "DHCPSRV_QUEUE_NCR_SKIP", "%1: skip queuing name change request for lease: %2",
+ "DHCPSRV_SUBNET4O6_SELECT_FAILED", "Failed to select any subnet for the DHCPv4o6 packet",
+ "DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_NO_MATCH", "No subnet matches address: %1",
+ "DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_NO_MATCH", "No subnet matches interface: %1",
+ "DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_NO_MATCH", "No subnet matches relay address: %1",
+ "DHCPSRV_SUBNET4_SELECT_NO_RAI_OPTIONS", "No RAI options found to use for subnet selection.",
+ "DHCPSRV_SUBNET4_SELECT_NO_RELAY_ADDRESS", "Relay address (giaddr) in client packet is empty.",
+ "DHCPSRV_SUBNET4_SELECT_NO_USABLE_ADDRESS", "No subnet selected because no suitable address to use for subnet selection was found.",
+ "DHCPSRV_SUBNET6_SELECT_BY_ADDRESS_NO_MATCH", "No subnet matches address: %1",
+ "DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_ID_NO_MATCH", "No subnet matches interface id: %1",
+ "DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_NO_MATCH", "No subnet matches interface: %1",
+ "DHCPSRV_TIMERMGR_CALLBACK_FAILED", "running handler for timer %1 caused exception: %2",
+ "DHCPSRV_TIMERMGR_REGISTER_TIMER", "registering timer: %1, using interval: %2 ms",
+ "DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION", "running operation for timer: %1",
+ "DHCPSRV_TIMERMGR_START_TIMER", "starting timer: %1",
+ "DHCPSRV_TIMERMGR_STOP_TIMER", "stopping timer: %1",
+ "DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS", "unregistering all timers",
+ "DHCPSRV_TIMERMGR_UNREGISTER_TIMER", "unregistering timer: %1",
+ "DHCPSRV_UNEXPECTED_NAME", "database access parameters passed through '%1', expected 'lease-database'",
+ "DHCPSRV_UNKNOWN_DB", "unknown database type: %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.h b/src/lib/dhcpsrv/dhcpsrv_messages.h
new file mode 100644
index 0000000..0f4f634
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.h
@@ -0,0 +1,288 @@
+// File created from ../../../src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+#ifndef DHCPSRV_MESSAGES_H
+#define DHCPSRV_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ADD_IFACE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ADD_SUBNET4;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ADD_SUBNET6;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CFG_DHCP_DDNS;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG4_MERGED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG6_MERGED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIGURE_SERVERID;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DDNS_PARAMETER_IGNORED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DDNS_PARAMETER_MOVED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET4;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET6;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_IPV4_RESERVATIONS_NON_UNIQUE_IGNORED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_IPV6_RESERVATIONS_NON_UNIQUE_IGNORED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_POSSIBLE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NEW_SUBNET4;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NEW_SUBNET6;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NO_SUBNET4;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_NO_SUBNET6;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ONLY_SUBNET4;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_ONLY_SUBNET6;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_OPTION_DUPLICATE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_RENEW_GTR_REBIND;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_ADDR;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_IFACE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET4_RELAY;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6_IFACE;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6_IFACE_ID;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_SUBNET6_RELAY;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_UPDATE_SUBNET4;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_UPDATE_SUBNET6;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ADDRESS;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_ALLOCATOR;
+extern const isc::log::MessageID DHCPSRV_CFGMGR_USE_UNICAST;
+extern const isc::log::MessageID DHCPSRV_CLOSE_DB;
+extern const isc::log::MessageID DHCPSRV_CONFIGURED_SUBNET_WITHOUT_ID;
+extern const isc::log::MessageID DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL;
+extern const isc::log::MessageID DHCPSRV_DEPRECATED;
+extern const isc::log::MessageID DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET;
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION;
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_HANDLER_NULL;
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_NCR_REJECTED;
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_NCR_SENT;
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_SENDER_STARTED;
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_SENDER_STOPPED;
+extern const isc::log::MessageID DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES;
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE4_RECOVER_SKIP;
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE4_RENEW_SKIP;
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE4_SELECT_SKIP;
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE6_EXTEND_SKIP;
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE6_RECOVER_SKIP;
+extern const isc::log::MessageID DHCPSRV_HOOK_LEASE6_SELECT_SKIP;
+extern const isc::log::MessageID DHCPSRV_INVALID_ACCESS;
+extern const isc::log::MessageID DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL;
+extern const isc::log::MessageID DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED;
+extern const isc::log::MessageID DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL;
+extern const isc::log::MessageID DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED;
+extern const isc::log::MessageID DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION;
+extern const isc::log::MessageID DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION;
+extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FAIL;
+extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FAIL_DISCARD;
+extern const isc::log::MessageID DHCPSRV_LEASE_SANITY_FIXED;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_ADD_ADDR4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_ADD_ADDR6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BEGIN_BUILD_EXTENDED_INFO_TABLES6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BEGIN_EXTRACT_EXTENDED_INFO4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BEGIN_TRANSACTION;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6_ERROR;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_COMMIT;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DB;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_ADDR;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4_ERROR;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET6_DUID;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_ADDR4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_ADDR6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_CLIENTID;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_EXPIRED4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_EXPIRED6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_HOSTNAME4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_HOSTNAME6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_HWADDR;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_IAID_DUID;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_LINKADDR6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_PAGE4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_PAGE6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_RELAYID4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_RELAYID6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_REMOTEID4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_REMOTEID6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID_CLIENTID;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_SUBID_HWADDR;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_GET_VERSION;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LEASE_FILE_LOAD;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LEASE_LOAD;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_EXECUTE;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_SETUP;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_SPAWN_FAIL;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_START;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_LFC_UNREGISTER_TIMER_FAILED;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_NEEDS_DOWNGRADING;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_NEEDS_UPGRADING;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_NO_STORAGE;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_READ_HWADDR_FAIL;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_ROLLBACK;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_UPDATE_ADDR4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_UPDATE_ADDR6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES4;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES6;
+extern const isc::log::MessageID DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED;
+extern const isc::log::MessageID DHCPSRV_MT_DISABLED_QUEUE_CONTROL;
+extern const isc::log::MessageID DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE;
+extern const isc::log::MessageID DHCPSRV_MYSQL_ADD_ADDR4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_ADD_ADDR6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_BEGIN_TRANSACTION;
+extern const isc::log::MessageID DHCPSRV_MYSQL_COMMIT;
+extern const isc::log::MessageID DHCPSRV_MYSQL_DB;
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETED_EXPIRED_RECLAIMED;
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETE_ADDR;
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_FATAL_ERROR;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_ADDR4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_ADDR6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_CLIENTID;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_DUID;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_EXPIRED4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_EXPIRED6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_HOSTNAME4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_HOSTNAME6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_HWADDR;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_IAID_DUID;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_IAID_SUBID_DUID;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_LINKADDR6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_PAGE4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_PAGE6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_RELAYID4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_REMOTEID4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID_CLIENTID;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_SUBID_HWADDR;
+extern const isc::log::MessageID DHCPSRV_MYSQL_GET_VERSION;
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB;
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_GET_VERSION;
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_READONLY;
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED;
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE;
+extern const isc::log::MessageID DHCPSRV_MYSQL_HOST_DB_RECONNECT_FAILED;
+extern const isc::log::MessageID DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED;
+extern const isc::log::MessageID DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE;
+extern const isc::log::MessageID DHCPSRV_MYSQL_LEASE_DB_RECONNECT_FAILED;
+extern const isc::log::MessageID DHCPSRV_MYSQL_NEGATIVE_LEASES_STAT;
+extern const isc::log::MessageID DHCPSRV_MYSQL_NO_TLS;
+extern const isc::log::MessageID DHCPSRV_MYSQL_ROLLBACK;
+extern const isc::log::MessageID DHCPSRV_MYSQL_START_TRANSACTION;
+extern const isc::log::MessageID DHCPSRV_MYSQL_TLS_CIPHER;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPDATE_ADDR4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPDATE_ADDR6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_ERROR;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_PAGE;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_ERROR;
+extern const isc::log::MessageID DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_PAGE;
+extern const isc::log::MessageID DHCPSRV_NOTYPE_DB;
+extern const isc::log::MessageID DHCPSRV_NO_SOCKETS_OPEN;
+extern const isc::log::MessageID DHCPSRV_OPEN_SOCKET_FAIL;
+extern const isc::log::MessageID DHCPSRV_PGSQL_ADD_ADDR4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_ADD_ADDR6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_BEGIN_TRANSACTION;
+extern const isc::log::MessageID DHCPSRV_PGSQL_COMMIT;
+extern const isc::log::MessageID DHCPSRV_PGSQL_DB;
+extern const isc::log::MessageID DHCPSRV_PGSQL_DEALLOC_ERROR;
+extern const isc::log::MessageID DHCPSRV_PGSQL_DELETE_ADDR;
+extern const isc::log::MessageID DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_FATAL_ERROR;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_ADDR4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_ADDR6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_CLIENTID;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_DUID;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_EXPIRED4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_EXPIRED6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_HOSTNAME4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_HOSTNAME6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_HWADDR;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_IAID_DUID;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_IAID_SUBID_DUID;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_LINKADDR6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_PAGE4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_PAGE6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_RELAYID4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_REMOTEID4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID_CLIENTID;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_SUBID_HWADDR;
+extern const isc::log::MessageID DHCPSRV_PGSQL_GET_VERSION;
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB;
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_GET_VERSION;
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_READONLY;
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED;
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE;
+extern const isc::log::MessageID DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED;
+extern const isc::log::MessageID DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED;
+extern const isc::log::MessageID DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE;
+extern const isc::log::MessageID DHCPSRV_PGSQL_LEASE_DB_RECONNECT_FAILED;
+extern const isc::log::MessageID DHCPSRV_PGSQL_NEGATIVE_LEASES_STAT;
+extern const isc::log::MessageID DHCPSRV_PGSQL_NO_TLS_SUPPORT;
+extern const isc::log::MessageID DHCPSRV_PGSQL_ROLLBACK;
+extern const isc::log::MessageID DHCPSRV_PGSQL_START_TRANSACTION;
+extern const isc::log::MessageID DHCPSRV_PGSQL_TLS_SUPPORT;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPDATE_ADDR4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPDATE_ADDR6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_ERROR;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_PAGE;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_ERROR;
+extern const isc::log::MessageID DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_PAGE;
+extern const isc::log::MessageID DHCPSRV_QUEUE_NCR;
+extern const isc::log::MessageID DHCPSRV_QUEUE_NCR_FAILED;
+extern const isc::log::MessageID DHCPSRV_QUEUE_NCR_SKIP;
+extern const isc::log::MessageID DHCPSRV_SUBNET4O6_SELECT_FAILED;
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_NO_MATCH;
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_NO_MATCH;
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_NO_MATCH;
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_NO_RAI_OPTIONS;
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_NO_RELAY_ADDRESS;
+extern const isc::log::MessageID DHCPSRV_SUBNET4_SELECT_NO_USABLE_ADDRESS;
+extern const isc::log::MessageID DHCPSRV_SUBNET6_SELECT_BY_ADDRESS_NO_MATCH;
+extern const isc::log::MessageID DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_ID_NO_MATCH;
+extern const isc::log::MessageID DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_NO_MATCH;
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_CALLBACK_FAILED;
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_REGISTER_TIMER;
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION;
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_START_TIMER;
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_STOP_TIMER;
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS;
+extern const isc::log::MessageID DHCPSRV_TIMERMGR_UNREGISTER_TIMER;
+extern const isc::log::MessageID DHCPSRV_UNEXPECTED_NAME;
+extern const isc::log::MessageID DHCPSRV_UNKNOWN_DB;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_MESSAGES_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
new file mode 100644
index 0000000..f82f8cc
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -0,0 +1,1401 @@
+# Copyright (C) 2012-2023 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/.
+
+$NAMESPACE isc::dhcp
+
+% DHCPSRV_CFGMGR_ADD_IFACE listening on interface %1
+An info message issued when a new interface is being added to the collection of
+interfaces on which the server listens to DHCP messages.
+
+% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv4 subnet to its database.
+
+% DHCPSRV_CFGMGR_ADD_SUBNET6 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv6 subnet to its database.
+
+% DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE enabling listening on all interfaces
+A debug message issued when the server is being configured to listen on all
+interfaces.
+
+% DHCPSRV_CFGMGR_CFG_DHCP_DDNS Setting DHCP-DDNS configuration to: %1
+A debug message issued when the server's DHCP-DDNS settings are changed.
+
+% DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
+A debug message issued when configuration manager clears the internal list
+of active interfaces. This doesn't prevent the server from listening to
+the DHCP traffic through open sockets, but will rather be used by Interface
+Manager to select active interfaces when sockets are re-opened.
+
+% DHCPSRV_CFGMGR_CONFIG4_MERGED Configuration backend data has been merged.
+This is an informational message emitted when the DHCPv4 server has
+successfully merged configuration data retrieved from its configuration
+backends into the current configuration.
+
+% DHCPSRV_CFGMGR_CONFIG6_MERGED Configuration backend data has been merged.
+This is an informational message emitted when the DHCPv6 server has
+successfully merged configuration data retrieved from its configuration
+backends into the current configuration.
+
+% DHCPSRV_CFGMGR_CONFIGURE_SERVERID server configuration includes specification of a server identifier
+This warning message is issued when the server specified configuration of
+a server identifier. If this new configuration overrides an existing
+server identifier, this will affect existing bindings of the clients.
+Clients will use old server identifier when they renew their bindings.
+The server will not respond to those renews, and the clients will
+eventually transition to rebinding state. The server should reassign
+existing bindings and the clients will subsequently use new server
+identifier. It is recommended to not modify the server identifier, unless
+there is a good reason for it, to avoid increased number of renewals and
+a need for rebinding (increase of multicast traffic, which may be received
+by multiple servers).
+
+% DHCPSRV_CFGMGR_DDNS_PARAMETER_IGNORED dhcp-ddns:%1 is deprecated, using existing global:%2
+This is an informational message issued during configuration parsing when
+the server detects that a deprecated parameter has been specified in the
+"dhcp-ddns" element which conflicts with its corresponding global parameter.
+When this occurs the server simply ignores the value from dhcp-ddns.
+The log message shows be the deprecated and the supported parameter names.
+Note the configuration change only affects the in-memory configuration.
+Modify the configuration to comply with the supported parameters.
+
+% DHCPSRV_CFGMGR_DDNS_PARAMETER_MOVED dhcp-ddns:%1 is deprecated, moving it to global:%2
+This is an informational message issued during configuration parsing when
+the server detects that a deprecated parameter has been specified in the
+"dhcp-ddns" element for which no corresponding global value exists. When
+this occurs, the server removes the parameter from dhcp-ddns and inserts the
+parameter into the global scope. The log message shows the deprecated
+and the supported parameter names. Note the configuration change only affects
+the in-memory configuration. Modify the configuration to comply with
+the supported parameters.
+
+% DHCPSRV_CFGMGR_DEL_SUBNET4 IPv4 subnet %1 removed
+This debug message is issued when a subnet is successfully removed from the
+server configuration. The argument identifies the removed subnet.
+
+% DHCPSRV_CFGMGR_DEL_SUBNET6 IPv6 subnet %1 removed
+This debug message is issued when a subnet is successfully removed from the
+server configuration. The argument identifies the removed subnet.
+
+% DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES populating free address leases for the FLQ allocator in subnet %1; it can take a while!
+This informational message is issued when the server begins building a queue
+of free address leases for the given subnet. It can take a considerable amount
+of time, depending on the size of the address pools.
+
+% DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE populated %1 free address leases for the FLQ allocator in subnet %2 in %3
+This informational message is issued when the server ends building a queue
+of free address leases for a given subnet. The first argument logs the
+number of free leases, the second argument logs the subnet, and the third
+argument logs a duration.
+
+% DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES populating free prefix leases for the FLQ allocator in subnet %1; it can take a while!
+This informational message is issued when the server begins building a queue
+of free leases for the given subnet. It can take a considerable amount of
+time, depending on the size of the delegated prefix pools.
+
+% DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE populated %1 free prefix leases for the FLQ allocator in subnet %2 completed in %3
+This informational message is issued when the server ends building a queue
+of free prefix leases for a given subnet. The first argument logs the
+number of free leases, the second argument logs the subnet, and the third
+argument logs a duration.
+
+% DHCPSRV_CFGMGR_IPV4_RESERVATIONS_NON_UNIQUE_IGNORED ignoring "ip-reservations-unique" setting because at least one of the host database backends does not support non-unique IP reservations in a subnet
+This warning message is issued when the server failed to use the new setting
+of the ip-reservations-unique global parameter configured via the configuration
+backend. Some host database backends used apparently do not support specifying
+several reservations for the same IP address in a subnet. The administrator
+should either stop using the backend that does not support this setting or set
+the value of the ip-reservations-unique to true to resolve the configuration
+issue.
+
+% DHCPSRV_CFGMGR_IPV6_RESERVATIONS_NON_UNIQUE_IGNORED ignoring "ip-reservations-unique" setting because at least one of the host database backends does not support non unique IP reservations in a subnet
+This warning message is issued when the server failed to use the new setting
+of the ip-reservations-unique global parameter configured via the configuration
+backend. Some host database backends used apparently do not support specifying
+several reservations for the same IP address or delegated prefix in a subnet.
+The administrator should either stop using the backend that does not support
+this setting or set the value of the ip-reservations-unique to true to resolve
+the configuration issue.
+
+% DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_POSSIBLE setting "ip-reservations-unique" from false to true poses a risk that some host backends may still contain multiple reservations for the same IP address
+This warning message is issued when the DHCP server is configured to not allow
+multiple reservations for the same IP address. However, the host database
+backends may still contain multiple reservations for the same IP addresses
+causing problems with lease allocation for certain addresses. Please ensure
+that all such duplicates are removed.
+
+% DHCPSRV_CFGMGR_NEW_SUBNET4 a new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv4 subnet.
+
+% DHCPSRV_CFGMGR_NEW_SUBNET6 a new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified subnet.
+
+% DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv4 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_NO_SUBNET6 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv6 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
+This warning message is issued on an attempt to configure multiple options with the
+same option code for the particular subnet. Adding multiple options is uncommon
+for DHCPv6, but it is not prohibited.
+
+% DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED "relay" uses "ip-address", which has been deprecated, please use "ip-addresses": %1
+This is debug message issued when the "relay" element being parse
+contains "ip-address" rather than its replacement, "ip-addresses".
+The server will still honor the value but users are encouraged to
+move to the new list parameter.
+
+% DHCPSRV_CFGMGR_RENEW_GTR_REBIND in %1, the value of renew-timer %2 is greater than the value of rebind-timer %3, ignoring renew-timer
+A warning message that indicates the configured renew-timer is greater
+than the configured rebind-timer. The server will ignore the renew
+timer value and send the rebind timer value only. This is considered
+a non-fatal configuration error.
+
+% DHCPSRV_CFGMGR_SOCKET_RAW_UNSUPPORTED use of raw sockets is unsupported on this OS, UDP sockets will be used
+This warning message is logged when the user specified that the
+DHCPv4 server should use the raw sockets to receive the DHCP
+messages and respond to the clients, but the use of raw sockets
+is not supported on the particular environment. The raw sockets
+are useful when the server must respond to the directly connected
+clients which don't have an address yet. If the raw sockets are
+not supported by Kea on the particular platform, Kea will fall
+back to use of the IP/UDP sockets. The responses to
+the directly connected clients will be broadcast. The responses
+to relayed clients will be unicast as usual.
+
+% DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT "dhcp-socket-type" not specified , using default socket type %1
+This informational message is logged when the administrator hasn't
+specified the "dhcp-socket-type" parameter in configuration for interfaces.
+In such case, the default socket type will be used.
+
+% DHCPSRV_CFGMGR_SOCKET_TYPE_SELECT using socket type %1
+This informational message is logged when the DHCPv4 server selects the
+socket type to be used for all sockets that will be opened on the
+interfaces. Typically, the socket type is specified by the server
+administrator. If the socket type hasn't been specified, the raw
+socket will be selected. If the raw socket has been selected but
+Kea doesn't support the use of raw sockets on the particular
+OS, it will use an UDP socket instead.
+
+% DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET4_ADDR selected subnet %1 for packet received by matching address %2
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv4 subnet for a received packet. This particular
+subnet was selected, because an IPv4 address was matched which belonged to that
+subnet.
+
+% DHCPSRV_CFGMGR_SUBNET4_IFACE selected subnet %1 for packet received over interface %2
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv4 subnet for a packet received over
+the given interface. This particular subnet was selected, because it
+was specified as being directly reachable over the given interface. (see
+'interface' parameter in the subnet4 definition).
+
+% DHCPSRV_CFGMGR_SUBNET4_RELAY selected subnet %1, because of matching relay addr %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet, because detected relay agent address
+matches value specified for this subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6_IFACE selected subnet %1 for packet received over interface %2
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv6 subnet for a packet received over
+given interface. This particular subnet was selected, because it
+was specified as being directly reachable over given interface. (see
+'interface' parameter in the subnet6 definition).
+
+% DHCPSRV_CFGMGR_SUBNET6_IFACE_ID selected subnet %1 (interface-id match) for incoming packet
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv6 subnet for a received packet. This particular
+subnet was selected, because value of interface-id option matched what was
+configured in the server's interface-id option for that selected subnet6.
+(see 'interface-id' parameter in the subnet6 definition).
+
+% DHCPSRV_CFGMGR_SUBNET6_RELAY selected subnet %1, because of matching relay addr %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet, because detected relay agent address
+matches value specified for this subnet.
+
+% DHCPSRV_CFGMGR_UNICAST_LINK_LOCAL specified link local address %1 for unicast traffic on interface %2
+This warning message is logged when user specified a link-local address to
+receive unicast traffic. The warning message is issued because it is an
+uncommon use.
+
+% DHCPSRV_CFGMGR_UPDATE_SUBNET4 updating subnet %1 (result %2)
+A debug message reported when the DHCP configuration manager is updating the
+specified IPv4 subnet in its current configuration. Subnet ID and result
+(expected to be true) are displayed.
+
+% DHCPSRV_CFGMGR_UPDATE_SUBNET6 updating subnet %1 (result %2)
+A debug message reported when the DHCP configuration manager is replacing the
+specified IPv6 subnet in its current configuration. Subnet ID and result
+(expected to be true) are displayed.
+
+% DHCPSRV_CFGMGR_USE_ADDRESS listening on address %1, on interface %2
+A message issued when the server is configured to listen on the explicitly specified
+IP address on the given interface.
+
+% DHCPSRV_CFGMGR_USE_ALLOCATOR using the %1 allocator for %2 leases in subnet %3
+A message issued when the configuration manager starts using a given allocator
+for a subnet.
+
+% DHCPSRV_CFGMGR_USE_UNICAST listening on unicast address %1, on interface %2
+An info message issued when configuring the DHCP server to listen on the unicast
+address on the specific interface.
+
+% DHCPSRV_CLOSE_DB closing currently open %1 database
+This is a debug message, issued when the DHCP server closes the currently
+open lease database. It is issued at program shutdown and whenever
+the database access parameters are changed: in the latter case, the
+server closes the currently open database, and opens a database using
+the new parameters.
+
+% DHCPSRV_CONFIGURED_SUBNET_WITHOUT_ID a subnet was configured without an id: %1
+A warning message issued when a subnet was configured with a zero or without
+an id, causing the server to auto-generate it. Using auto-generated subnet
+ids is now deprecated. Each configured subnet should have an explicit subnet id
+specified with the "id" entry. The sole argument of this warning message contains
+a subnet prefix.
+
+% DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL ddns-ttl-percent %1 of lease lifetime %2 is too small, ignoring it
+A debug message issued when the DDNS TTL value calculated using the
+ddns-ttl-percent is zero. Kea will ignore the value and calculate
+the DDNS TTL as though ddsn-ttl-percent were not specified. The
+value of ddns-ttl-percent and the lease lifetime are shown in
+the message details.
+
+% DHCPSRV_DEPRECATED This configuration is using a deprecated feature: %1
+This warning is printed every time a deprecated feature (identified by the parameter) is
+used. A deprecated feature is functional now, but there will be a future Kea release
+where it will be completely removed. If you see this message it's not a reason for panic,
+but you should consider your long term strategy to eventually stop using the deprecated
+feature.
+
+% DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET received bad DHCPv4o6 packet: %1
+A bad DHCPv4o6 packet was received.
+
+% DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION error handler for DHCP_DDNS IO generated an expected exception: %1
+This is an error message that occurs when an attempt to send a request to
+kea-dhcp-ddns fails there registered error handler threw an uncaught exception.
+This is a programmatic error which should not occur. By convention, the error
+handler should not propagate exceptions. Please report this error.
+
+% DHCPSRV_DHCP_DDNS_HANDLER_NULL error handler for DHCP_DDNS IO is not set.
+This is an error message that occurs when an attempt to send a request to
+kea-dhcp-ddns fails and there is no registered error handler. This is a
+programmatic error which should never occur and should be reported.
+
+% DHCPSRV_DHCP_DDNS_NCR_REJECTED NameChangeRequest rejected by the sender: %1, ncr: %2
+This is an error message indicating that NameChangeSender used to deliver DDNS
+update requests to kea-dhcp-ddns rejected the request. This most likely cause
+is the sender's queue has reached maximum capacity. This would imply that
+requests are being generated faster than they can be delivered.
+
+% DHCPSRV_DHCP_DDNS_NCR_SENT NameChangeRequest sent to kea-dhcp-ddns: %1
+A debug message issued when a NameChangeRequest has been successfully sent to
+kea-dhcp-ddns.
+
+% DHCPSRV_DHCP_DDNS_SENDER_STARTED NameChangeRequest sender has been started: %1
+An informational message issued when a communication with kea-dhcp-ddns has
+been successfully started.
+
+% DHCPSRV_DHCP_DDNS_SENDER_STOPPED NameChangeRequest sender has been stopped.
+An informational message issued when a communication with kea-dhcp-ddns has
+been stopped. This normally occurs during reconfiguration and as part of normal
+shutdown. It may occur if kea-dhcp-ddns communications break down.
+
+% DHCPSRV_DHCP_DDNS_SUSPEND_UPDATES DHCP_DDNS updates are being suspended.
+This is a warning message indicating the DHCP_DDNS updates have been turned
+off. This should only occur if IO errors communicating with kea-dhcp-ddns
+have been experienced. Any such errors should have preceding entries in the
+log with details. No further attempts to communicate with kea-dhcp-ddns will
+be made without intervention.
+
+% DHCPSRV_HOOK_LEASE4_RECOVER_SKIP DHCPv4 lease %1 was not recovered from the declined state because a callout set the skip status.
+This debug message is printed when a callout installed on lease4_recover
+hook point set the next step status to SKIP. For this particular hook point, this
+indicates that the server should not recover the lease from declined state.
+The server will leave the lease as it is, in the declined state. The
+server will attempt to recover it the next time decline recovery procedure
+takes place.
+
+% DHCPSRV_HOOK_LEASE4_RENEW_SKIP DHCPv4 lease was not renewed because a callout set the skip flag.
+This debug message is printed when a callout installed on lease4_renew
+hook point set the skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to not renew a lease. The
+server will use existing lease as it is, without extending its lifetime.
+
+% DHCPSRV_HOOK_LEASE4_SELECT_SKIP Lease4 creation was skipped, because of callout skip flag.
+This debug message is printed when a callout installed on lease4_select
+hook point sets the skip flag. It means that the server was told that
+no lease4 should be assigned. The server will not put that lease in its
+database and the client will get a NAK packet.
+
+% DHCPSRV_HOOK_LEASE6_EXTEND_SKIP DHCPv6 lease lifetime was not extended because a callout set the skip flag for message %1
+This debug message is printed when a callout installed on lease6_renew
+or the lease6_rebind hook point set the skip flag. For this particular hook
+point, the setting of the flag by a callout instructs the server to not
+extend the lifetime for a lease. If the client requested renewal of multiple
+leases (by sending multiple IA options), the server will skip the renewal
+of the one in question and will proceed with other renewals as usual.
+
+% DHCPSRV_HOOK_LEASE6_RECOVER_SKIP DHCPv6 lease %1 was not recovered from declined state because a callout set the skip status.
+This debug message is printed when a callout installed on lease6_recover
+hook point set the next step status to SKIP. For this particular hook point, this
+indicates that the server should not recover the lease from declined state.
+The server will leave the lease as it is, in the declined state. The
+server will attempt to recover it the next time decline recovery procedure
+takes place.
+
+% DHCPSRV_HOOK_LEASE6_SELECT_SKIP Lease6 (non-temporary) creation was skipped, because of callout skip flag.
+This debug message is printed when a callout installed on lease6_select
+hook point sets the skip flag. It means that the server was told that
+no lease6 should be assigned. The server will not put that lease in its
+database and the client will get a NoAddrsAvail for that IA_NA option.
+
+% DHCPSRV_INVALID_ACCESS invalid database access string: %1
+This is logged when an attempt has been made to parse a database access string
+and the attempt ended in error. The access string in question - which
+should be of the form 'keyword=value keyword=value...' is included in
+the message.
+
+% DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL extended info for lease %1 failed checks (%2)
+This error message is printed when a lease extended info failed to
+pass sanity checks. The detail of the found problem was displayed and
+the extended info deleted from the lease user context.
+
+% DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED extended info for lease %1 was upgraded
+This debug message is printed when a lease extended info was upgraded.
+
+% DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL extended info for lease %1 failed checks (%2)
+This error message is printed when a lease extended info failed to
+pass sanity checks. The detail of the found problem was displayed and
+the extended info deleted from the lease user context.
+
+% DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED extended info for lease %1 was upgraded
+This debug message is printed when a lease extended info was upgraded.
+
+% DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION exception occurred in a lease manager callback for callback type %1, subnet id %2, and lease %3: %4
+This warning message is printed when one of the callback functions registered
+in the lease manager causes an error. The callback functions can serve
+different purposes and they likely log the detailed error messages. This
+error message possibly indicates an unhandled error. The first argument
+indicates a callback type. The second argument prints the subnet id.
+The third argument prints the lease for which the error has occurred.
+The last argument prints the error text.
+
+% DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION unknown exception occurred in a lease manager callback for callback type %1, subnet id %2, and lease %3
+This warning message is printed when one of the callback functions registered
+in the lease manager causes an unknown error. The callback functions can serve
+different purposes and they likely log the detailed error messages. This
+error message possibly indicates an unhandled error. The first argument
+indicates a callback type. The second argument prints the subnet id.
+The third argument prints the lease for which the error has occurred.
+This log message variant contains no error text because it is triggered
+by an unknown exception.
+
+% DHCPSRV_LEASE_SANITY_FAIL The lease %1 with subnet-id %2 failed subnet-id checks (%3).
+This warning message is printed when the lease being loaded does not match the
+configuration. Due to lease-checks value, the lease will be loaded, but
+it will most likely be unused by Kea, as there is no subnet that matches
+the IP address associated with the lease.
+
+% DHCPSRV_LEASE_SANITY_FAIL_DISCARD The lease %1 with subnet-id %2 failed subnet-id checks (%3) and was dropped.
+This warning message is printed when a lease was loaded, but Kea was told
+(by setting lease-checks parameter) to discard leases with inconsistent
+data. The lease was discarded, because either there is no subnet configured
+with matching subnet-id or the address of the lease does not belong to the
+subnet.
+
+% DHCPSRV_LEASE_SANITY_FIXED The lease %1 with subnet-id %2 failed subnet-id checks, but was corrected to subnet-id %3.
+This informational message is printed when a lease was loaded, but had
+incorrect subnet-id value. The lease-checks parameter was set to a value
+that told Kea to try to correct the problem. There is a matching subnet,
+so Kea updated subnet-id and loaded the lease successfully.
+
+% DHCPSRV_MEMFILE_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_BEGIN_BUILD_EXTENDED_INFO_TABLES6 building extended info tables with %1 sanity check level%2, tables %3
+A debug message issued when the server is building extended info tables.
+The extended info sanity check level, update in file when requested
+and the fact tables are enabled or disabled are displayed.
+
+% DHCPSRV_MEMFILE_BEGIN_EXTRACT_EXTENDED_INFO4 extract extended info with %1 sanity check level%2
+A debug message issued when the server is extracting extended info.
+The extended info sanity check level and update in file when requested
+are displayed.
+
+% DHCPSRV_MEMFILE_BEGIN_TRANSACTION committing to memory file database
+The code has issued a begin transaction call. For the memory file database
+this is a no-op.
+
+% DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6 building extended info tables saw %1 leases, extended info sanity checks modified %2 / updated %3 leases and %4 leases were entered into tables
+Extended info tables build was finished. Some statistics are displayed, the
+updated in database is returned to the command interface.
+
+% DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6_ERROR building extended info tables got an exception on the lease for %1: %2
+An error message issued when the server is building extended info tables and
+receives an exception processing a lease.
+
+% DHCPSRV_MEMFILE_COMMIT committing to memory file database
+The code has issued a commit call. For the memory file database this is
+a no-op.
+
+% DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES running LFC now to convert lease files to the current schema: %1.%2
+A warning message issued when the server has detected lease files that need
+to be either upgraded or downgraded to match the server's schema, and that
+the server is automatically running the LFC process to perform the conversion.
+This should only occur the first time the server is launched following a Kea
+installation upgrade (or downgrade).
+
+% DHCPSRV_MEMFILE_DB opening memory file lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a memory file lease database. The parameters of
+the connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MEMFILE_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease
+for the specified address from the memory file database for the specified
+address.
+
+% DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4 deleting reclaimed IPv4 leases that expired more than %1 seconds ago
+A debug message issued when the server is removing reclaimed DHCPv4
+leases which have expired longer than a specified period of time.
+The argument is the amount of time Kea waits after a reclaimed
+lease expires before considering its removal.
+
+% DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6 deleting reclaimed IPv6 leases that expired more than %1 seconds ago
+A debug message issued when the server is removing reclaimed DHCPv6
+leases which have expired longer than a specified period of time.
+The argument is the amount of time Kea waits after a reclaimed
+lease expires before considering its removal.
+
+% DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START starting deletion of %1 expired-reclaimed leases
+A debug message issued when the server has found expired-reclaimed
+leases to be removed. The number of leases to be removed is logged
+in the message.
+
+% DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4 extracting extended info saw %1 leases, extended info sanity checks modified %2 / updated %3 leases and %4 leases have relay or remote id
+Extended info extraction was finished. Some statistics are displayed, the
+updated in database is returned to the command interface.
+
+% DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4_ERROR extracting extended info got an exception on the lease for %1: %2
+A debug message issued when the server is extracting extended info and
+receives an exception processing a lease.
+
+% DHCPSRV_MEMFILE_GET4 obtaining all IPv4 leases
+A debug message issued when the server is attempting to obtain all IPv4
+leases from the memory file database.
+
+% DHCPSRV_MEMFILE_GET6 obtaining all IPv6 leases
+A debug message issued when the server is attempting to obtain all IPv6
+leases from the memory file database.
+
+% DHCPSRV_MEMFILE_GET6_DUID obtaining IPv6 leases for DUID %1
+A debug message issued when the server is attempting to obtain IPv6
+leases from the memory file database for the DUID.
+
+% DHCPSRV_MEMFILE_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1 and lease type %2
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+client identification.
+
+% DHCPSRV_MEMFILE_GET_EXPIRED4 obtaining maximum %1 of expired IPv4 leases
+A debug message issued when the server is attempting to obtain expired
+IPv4 leases to reclaim them. The maximum number of leases to be retrieved
+is logged in the message.
+
+% DHCPSRV_MEMFILE_GET_EXPIRED6 obtaining maximum %1 of expired IPv6 leases
+A debug message issued when the server is attempting to obtain expired
+IPv6 leases to reclaim them. The maximum number of leases to be retrieved
+is logged in the message.
+
+% DHCPSRV_MEMFILE_GET_HOSTNAME4 obtaining IPv4 leases for hostname %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+hostname.
+
+% DHCPSRV_MEMFILE_GET_HOSTNAME6 obtaining IPv6 leases for hostname %1
+A debug message issued when the server is attempting to obtain a set of
+IPv6 leases from the memory file database for a client with the specified
+hostname.
+
+% DHCPSRV_MEMFILE_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+hardware address.
+
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv6 leases for IAID %1 and DUID %2 and lease type %3
+A debug message issued when the server is attempting to obtain a set of IPv6
+leases from the memory file database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv6 leases for IAID %1, Subnet ID %2, DUID %3 and lease type %4
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_LINKADDR6 obtaining at most %1 IPv6 leases starting from address %2 with link %3/%4
+A debug message issued when the server is attempting to obtain a page of
+IPv6 leases beginning with the specified address within a link.
+
+% DHCPSRV_MEMFILE_GET_PAGE4 obtaining at most %1 IPv4 leases starting from address %2
+A debug message issued when the server is attempting to obtain a page
+of leases beginning with the specified address.
+
+% DHCPSRV_MEMFILE_GET_PAGE6 obtaining at most %1 IPv6 leases starting from address %2
+A debug message issued when the server is attempting to obtain a page
+of leases beginning with the specified address.
+
+% DHCPSRV_MEMFILE_GET_RELAYID4 obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a relay id and client
+transaction time between start and end dates.
+
+% DHCPSRV_MEMFILE_GET_RELAYID6 obtaining at most %1 IPv6 leases starting from address %2 with relay id %3 and link %4/%5
+A debug message issued when the server is attempting to obtain a page of
+IPv6 leases beginning with the specified address with a relay id and a link.
+
+% DHCPSRV_MEMFILE_GET_REMOTEID4 obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a remote id and
+client transaction time between start and end dates.
+
+% DHCPSRV_MEMFILE_GET_REMOTEID6 obtaining at most %1 IPv6 leases starting from address %2 with remote id %3 and link %4/%5
+A debug message issued when the server is attempting to obtain a page of
+IPv6 leases beginning with the specified address with a remote id and a link.
+
+% DHCPSRV_MEMFILE_GET_SUBID4 obtaining IPv4 leases for subnet ID %1
+A debug message issued when the server is attempting to obtain all IPv4
+leases for a given subnet identifier from the memory file database.
+
+% DHCPSRV_MEMFILE_GET_SUBID6 obtaining IPv6 leases for subnet ID %1
+A debug message issued when the server is attempting to obtain all IPv6
+leases for a given subnet identifier from the memory file database.
+
+% DHCPSRV_MEMFILE_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and client ID.
+
+% DHCPSRV_MEMFILE_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and hardware address.
+
+% DHCPSRV_MEMFILE_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the memory file database.
+
+% DHCPSRV_MEMFILE_LEASE_FILE_LOAD loading leases from file %1
+An info message issued when the server is about to start reading DHCP leases
+from the lease file. All leases currently held in the memory will be
+replaced by those read from the file.
+
+% DHCPSRV_MEMFILE_LEASE_LOAD loading lease %1
+A debug message issued when DHCP lease is being loaded from the file to memory.
+
+% DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR discarding row %1, error: %2
+An error message issued if the DHCP lease being loaded from the given row of
+the lease file fails. The log message should contain the specific reason the
+row was discarded. The server continues loading the remaining data.
+This may indicate a corrupt lease file.
+
+% DHCPSRV_MEMFILE_LFC_EXECUTE executing Lease File Cleanup using: %1
+An informational message issued when the memfile lease database backend
+starts a new process to perform Lease File Cleanup.
+
+% DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL failed to rename the current lease file %1 to %2, reason: %3
+An error message logged when the memfile lease database backend fails to
+move the current lease file to a new file on which the cleanup should
+be performed. This effectively means that the lease file cleanup
+does not take place.
+
+% DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL failed to reopen lease file %1 after preparing input file for lease file cleanup, reason: %2, new leases will not persist!
+An error message logged when the memfile lease database backend
+failed to re-open or re-create the lease file after renaming the
+lease file for lease file cleanup. The server continues to
+operate but leases do not persist to disk.
+
+% DHCPSRV_MEMFILE_LFC_SETUP setting up the Lease File Cleanup interval to %1 sec
+An informational message logged when the memfile lease database backend
+configures the LFC to be executed periodically. The argument holds the
+interval in seconds in which the LFC will be executed.
+
+% DHCPSRV_MEMFILE_LFC_SPAWN_FAIL lease file cleanup failed to run because kea-lfc process couldn't be spawned
+This error message is logged when the Kea server fails to run kea-lfc,
+the program that cleans up the lease file. The server will try again the
+next time a lease file cleanup is scheduled. Although this message should
+not appear and the reason why it did investigated, the occasional failure
+to start the lease file cleanup will not impact operations. Should the
+failure persist however, the size of the lease file will increase without bound.
+
+% DHCPSRV_MEMFILE_LFC_START starting Lease File Cleanup
+An informational message issued when the Memfile lease database backend
+starts the periodic Lease File Cleanup.
+
+% DHCPSRV_MEMFILE_LFC_UNREGISTER_TIMER_FAILED failed to unregister timer 'memfile-lfc': %1
+This debug message is logged when Memfile backend fails to unregister
+timer used for lease file cleanup scheduling. There are several reasons
+why this could occur, although the most likely cause is that the system
+is being shut down and some other component has unregistered the timer.
+The message includes the reason for this error.
+
+% DHCPSRV_MEMFILE_NEEDS_DOWNGRADING version of lease file: %1 schema is later than version %2
+A warning message issued when the schema of the lease file loaded by the server
+is newer than the memfile schema of the server. The server converts the lease
+data from newer schemas to its schema as it is read, therefore the lease
+information in use by the server will be correct. Note though, that any data
+stored in newer schema fields will be dropped. What remains is for the
+file itself to be rewritten using the current schema.
+
+% DHCPSRV_MEMFILE_NEEDS_UPGRADING version of lease file: %1 schema is earlier than version %2
+A warning message issued when the schema of the lease file loaded by the server
+pre-dates the memfile schema of the server. Note that the server converts the
+lease data from older schemas to the current schema as it is read, therefore
+the lease information in use by the server will be correct. What remains is
+for the file itself to be rewritten using the current schema.
+
+% DHCPSRV_MEMFILE_NO_STORAGE running in non-persistent mode, leases will be lost after restart
+A warning message issued when writes of leases to disk have been disabled
+in the configuration. This mode is useful for some kinds of performance
+testing but should not be enabled in normal circumstances. Non-persistence
+mode is enabled when 'persist4=no persist6=no' parameters are specified
+in the database access string.
+
+% DHCPSRV_MEMFILE_READ_HWADDR_FAIL failed to read hardware address from lease file: %1
+A warning message issued when read attempt of the hardware address stored in
+a disk file failed. The parameter should provide the exact nature of the failure.
+The database read will continue, but that particular lease will no longer
+have hardware address associated with it.
+
+% DHCPSRV_MEMFILE_ROLLBACK rolling back memory file database
+The code has issued a rollback call. For the memory file database this is
+a no-op.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_WIPE_LEASES4 removing all IPv4 leases from subnet %1
+This informational message is printed when removal of all leases from
+specified IPv4 subnet is commencing. This is a result of receiving administrative
+command.
+
+% DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED removing all IPv4 leases from subnet %1 finished, removed %2 leases
+This informational message is printed when removal of all leases from
+a specified IPv4 subnet has finished. The number of removed leases is
+printed.
+
+% DHCPSRV_MEMFILE_WIPE_LEASES6 removing all IPv6 leases from subnet %1
+This informational message is printed when removal of all leases from
+specified IPv6 subnet is commencing. This is a result of receiving administrative
+command.
+
+% DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED removing all IPv6 leases from subnet %1 finished, removed %2 leases
+This informational message is printed when removal of all leases from
+a specified IPv6 subnet has finished. The number of removed leases is
+printed.
+
+% DHCPSRV_MT_DISABLED_QUEUE_CONTROL disabling dhcp queue control when multi-threading is enabled.
+This warning message is issued when dhcp queue control is disabled automatically
+if multi-threading is enabled. These two options are incompatible and can not
+both be enabled at the same time.
+
+% DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE current configuration will result in opening multiple broadcast capable sockets on some interfaces and some DHCP messages may be duplicated
+A warning message issued when the current configuration indicates that multiple
+sockets, capable of receiving broadcast traffic, will be opened on some of the
+interfaces. It must be noted that this may lead to receiving and processing
+the same DHCP message multiple times, as it will be received by each socket
+individually.
+
+% DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_ADD_ADDR6 adding IPv6 lease with address %1, lease type %2
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_BEGIN_TRANSACTION committing to MySQL database
+The code has issued a begin transaction call.
+
+% DHCPSRV_MYSQL_COMMIT committing to MySQL database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the MySQL settings,
+the commit may not include a write to disk.
+
+% DHCPSRV_MYSQL_DB opening MySQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a MySQL lease database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MYSQL_DELETED_EXPIRED_RECLAIMED deleted %1 reclaimed leases from the database
+A debug message issued when the server has removed a number of reclaimed
+leases from the database. The number of removed leases is included in the
+message.
+
+% DHCPSRV_MYSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED4 deleting reclaimed IPv4 leases that expired more than %1 seconds ago
+A debug message issued when the server is removing reclaimed DHCPv4
+leases which have expired longer than a specified period of time.
+The argument is the amount of time Kea waits after a reclaimed
+lease expires before considering its removal.
+
+% DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED6 deleting reclaimed IPv6 leases that expired more than %1 seconds ago
+A debug message issued when the server is removing reclaimed DHCPv6
+leases which have expired longer than a specified period of time.
+The argument is the amount of time Kea waits after a reclaimed
+lease expires before considering its removal.
+
+% DHCPSRV_MYSQL_FATAL_ERROR Unrecoverable MySQL error occurred: %1 for <%2>, reason: %3 (error code: %4).
+An error message indicating that communication with the MySQL database server
+has been lost. If automatic recovery has been enabled, then the server will
+attempt to recover the connectivity. If not the server will exit with a
+non-zero exit code. The cause of such an error is most likely a network issue
+or the MySQL server has gone down.
+
+% DHCPSRV_MYSQL_GET4 obtaining all IPv4 leases
+A debug message issued when the server is attempting to obtain all IPv4
+leases from the MySQL database.
+
+% DHCPSRV_MYSQL_GET6 obtaining all IPv6 leases
+A debug message issued when the server is attempting to obtain all IPv6
+leases from the MySQL database.
+
+% DHCPSRV_MYSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR6 obtaining IPv6 lease for address %1, lease type %2
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+client identification.
+
+% DHCPSRV_MYSQL_GET_DUID obtaining IPv6 lease for duid %1,
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for the specified duid.
+
+% DHCPSRV_MYSQL_GET_EXPIRED4 obtaining maximum %1 of expired IPv4 leases
+A debug message issued when the server is attempting to obtain expired
+IPv4 leases to reclaim them. The maximum number of leases to be retrieved
+is logged in the message.
+
+% DHCPSRV_MYSQL_GET_EXPIRED6 obtaining maximum %1 of expired IPv6 leases
+A debug message issued when the server is attempting to obtain expired
+IPv6 leases to reclaim them. The maximum number of leases to be retrieved
+is logged in the message.
+
+% DHCPSRV_MYSQL_GET_HOSTNAME4 obtaining IPv4 leases for hostname %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+hostname.
+
+% DHCPSRV_MYSQL_GET_HOSTNAME6 obtaining IPv6 leases for hostname %1
+A debug message issued when the server is attempting to obtain a set
+of IPv6 leases from the MySQL database for a client with the specified
+hostname.
+
+% DHCPSRV_MYSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_MYSQL_GET_IAID_DUID obtaining IPv6 leases for IAID %1, DUID %2, lease type %3
+A debug message issued when the server is attempting to obtain a set of IPv6
+leases from the MySQL database for a client with the specified IAID (Identity
+Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_IAID_SUBID_DUID obtaining IPv6 leases for IAID %1, Subnet ID %2, DUID %3, lease type %4
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_LINKADDR6 obtaining at most %1 IPv6 leases starting from address %2 with link %3/%4
+A debug message issued when the server is attempting to obtain a page of
+IPv6 leases beginning with the specified address within a link.
+
+% DHCPSRV_MYSQL_GET_PAGE4 obtaining at most %1 IPv4 leases starting from address %2
+A debug message issued when the server is attempting to obtain a page
+of leases beginning with the specified address.
+
+% DHCPSRV_MYSQL_GET_PAGE6 obtaining at most %1 IPv6 leases starting from address %2
+A debug message issued when the server is attempting to obtain a page
+of leases beginning with the specified address.
+
+% DHCPSRV_MYSQL_GET_RELAYID4 obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a relay id and client
+transaction time between start and end dates.
+
+% DHCPSRV_MYSQL_GET_REMOTEID4 obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a remote id and client
+transaction time between start and end dates.
+
+% DHCPSRV_MYSQL_GET_SUBID4 obtaining IPv4 leases for subnet ID %1
+A debug message issued when the server is attempting to obtain all IPv4
+leases for a given subnet identifier from the MySQL database.
+
+% DHCPSRV_MYSQL_GET_SUBID6 obtaining IPv6 leases for subnet ID %1
+A debug message issued when the server is attempting to obtain all IPv6
+leases for a given subnet identifier from the MySQL database.
+
+% DHCPSRV_MYSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_MYSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_MYSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the MySQL database.
+
+% DHCPSRV_MYSQL_HOST_DB opening MySQL hosts database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a MySQL hosts database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MYSQL_HOST_DB_GET_VERSION obtaining schema version information for the MySQL hosts database
+A debug message issued when the server is about to obtain schema version
+information from the MySQL hosts database.
+
+% DHCPSRV_MYSQL_HOST_DB_READONLY MySQL host database opened for read access only
+This informational message is issued when the user has configured the MySQL
+database in read-only mode. Kea will not be able to insert or modify
+host reservations but will be able to retrieve existing ones and
+assign them to the clients communicating with the server.
+
+% DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED database reconnect failed: %1
+An error message issued when an attempt to reconnect has failed.
+
+% DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE scheduling attempt %1 of %2 in %3 milliseconds
+An info message issued when the server is scheduling the next attempt to reconnect
+to the database. This occurs when the server has lost database connectivity and
+is attempting to reconnect automatically.
+
+% DHCPSRV_MYSQL_HOST_DB_RECONNECT_FAILED maximum number of database reconnect attempts: %1, has been exhausted without success
+An error message issued when the server failed to reconnect. Loss of connectivity
+is typically a network or database server issue.
+
+% DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED database reconnect failed: %1
+An error message issued when an attempt to reconnect has failed.
+
+% DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE scheduling attempt %1 of %2 in %3 milliseconds
+An info message issued when the server is scheduling the next attempt to reconnect
+to the database. This occurs when the server has lost database connectivity and
+is attempting to reconnect automatically.
+
+% DHCPSRV_MYSQL_LEASE_DB_RECONNECT_FAILED maximum number of database reconnect attempts: %1, has been exhausted without success
+An error message issued when the server failed to reconnect. Loss of connectivity
+is typically a network or database server issue.
+
+% DHCPSRV_MYSQL_NEGATIVE_LEASES_STAT recount of leases returned a negative value
+This warning message is issued when the recount of leases using counters
+in the MySQL database returned a negative value. This shows a problem
+which can be fixed only by an offline direct recount on the database.
+This message is issued only once.
+
+% DHCPSRV_MYSQL_NO_TLS TLS was required but is not used
+This error message is issued when TLS for the connection was required but
+TLS is not used.
+
+% DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_MYSQL_START_TRANSACTION starting new MySQL transaction
+A debug message issued when a new MySQL transaction is being started.
+This message is typically not issued when inserting data into a
+single table because the server doesn't explicitly start
+transactions in this case. This message is issued when data is
+inserted into multiple tables with multiple INSERT statements
+and there may be a need to rollback the whole transaction if
+any of these INSERT statements fail.
+
+% DHCPSRV_MYSQL_TLS_CIPHER TLS cipher: %1
+A debug message issued when a new MySQL connected is created with TLS.
+The TLS cipher name is logged.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR6 updating IPv6 lease for address %1, lease type %2
+A debug message issued when the server is attempting to update IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6 upgrading IPv6 leases done in %1 pages with %2 updated leases
+The server upgraded binary addresses. The number of pages and the
+final count of updated leases are displayed.
+
+% DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_ERROR upgrading binary address for IPv6 lease at %1 failed with %2
+An error message issued when the server failed to upgrade a binary address.
+The address of the lease and the error message are displayed.
+
+% DHCPSRV_MYSQL_UPGRADE_BINARY_ADDRESS6_PAGE upgrading IPv6 lease binary addresses at page %1 starting at %2 (updated %3)
+A debug message issued when the server upgrades IPv6 lease binary addresses.
+The page number and started address, and the count of already updated leases
+are displayed.
+
+% DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4 upgrading IPv4 leases done in %1 pages with %2 updated leases
+The server upgraded extended info. The number of pages and the final count of
+updated leases are displayed.
+
+% DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_ERROR upgrading extending info for IPv4 lease at %1 failed with %2
+A debug message issued when the server failed to upgrade an extended info.
+The address of the lease and the error message are displayed.
+
+% DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_PAGE upgrading IPv4 lease extended info at page %1 starting at %2 (updated %3)
+A debug message issued when the server upgrades IPv4 lease extended info.
+The page number and started address, and the count of already updated leases
+are displayed.
+
+% DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
+This is an error message, logged when an attempt has been made to access
+a database backend, but where no 'type' keyword has been included in
+the access string. The access string (less any passwords) is included
+in the message.
+
+% DHCPSRV_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when the current server configuration specifies
+no interfaces that the server should listen on, or when the specified interfaces are not
+configured to receive the traffic.
+
+% DHCPSRV_OPEN_SOCKET_FAIL failed to open socket: %1
+A warning message issued when IfaceMgr fails to open and bind a socket.
+The reason for the failure is appended as an argument of the log message.
+
+% DHCPSRV_PGSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the PostgreSQL backend database.
+
+% DHCPSRV_PGSQL_ADD_ADDR6 adding IPv6 lease with address %1, lease type %2
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the PostgreSQL backend database.
+
+% DHCPSRV_PGSQL_BEGIN_TRANSACTION committing to PostgreSQL database
+The code has issued a begin transaction call.
+
+% DHCPSRV_PGSQL_COMMIT committing to PostgreSQL database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the PostgreSQL settings,
+the commit may not include a write to disk.
+
+% DHCPSRV_PGSQL_DB opening PostgreSQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a PostgreSQL lease database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_PGSQL_DEALLOC_ERROR An error occurred deallocating SQL statements while closing the PostgreSQL lease database: %1
+This is an error message issued when a DHCP server (either V4 or V6) experienced
+and error freeing database SQL resources as part of closing its connection to
+the PostgreSQL database. The connection is closed as part of normal server
+shutdown. This error is most likely a programmatic issue that is highly
+unlikely to occur or negatively impact server operation.
+
+% DHCPSRV_PGSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED4 deleting reclaimed IPv4 leases that expired more than %1 seconds ago
+A debug message issued when the server is removing reclaimed DHCPv4
+leases which have expired longer than a specified period of time.
+The argument is the amount of time Kea waits after a reclaimed
+lease expires before considering its removal.
+
+% DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED6 deleting reclaimed IPv6 leases that expired more than %1 seconds ago
+A debug message issued when the server is removing reclaimed DHCPv6
+leases which have expired longer than a specified period of time.
+The argument is the amount of time Kea waits after a reclaimed
+lease expires before considering its removal.
+
+% DHCPSRV_PGSQL_FATAL_ERROR Unrecoverable PostgreSQL error occurred: Statement: <%1>, reason: %2 (error code: %3).
+An error message indicating that communication with the PostgreSQL database server
+has been lost. If automatic recovery has been enabled, then the server will
+attempt to recover the connectivity. If not the server will exit with a
+non-zero exit code. The cause of such an error is most likely a network issue
+or the PostgreSQL server has gone down.
+
+% DHCPSRV_PGSQL_GET4 obtaining all IPv4 leases
+A debug message issued when the server is attempting to obtain all IPv4
+leases from the PostgreSQL database.
+
+% DHCPSRV_PGSQL_GET6 obtaining all IPv6 leases
+A debug message issued when the server is attempting to obtain all IPv6
+leases from the PostgreSQL database.
+
+% DHCPSRV_PGSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_GET_ADDR6 obtaining IPv6 lease for address %1 (lease type %2)
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the PostgreSQL database for a client with the specified
+client identification.
+
+% DHCPSRV_PGSQL_GET_DUID obtaining IPv6 leases for DUID %1,
+A debug message issued when the server is attempting to obtain a set of IPv6
+leases from the PostgreSQL database for a client with the specified DUID (DHCP Unique Identifier).
+
+% DHCPSRV_PGSQL_GET_EXPIRED4 obtaining maximum %1 of expired IPv4 leases
+A debug message issued when the server is attempting to obtain expired
+IPv4 leases to reclaim them. The maximum number of leases to be retrieved
+is logged in the message.
+
+% DHCPSRV_PGSQL_GET_EXPIRED6 obtaining maximum %1 of expired IPv6 leases
+A debug message issued when the server is attempting to obtain expired
+IPv6 leases to reclaim them. The maximum number of leases to be retrieved
+is logged in the message.
+
+% DHCPSRV_PGSQL_GET_HOSTNAME4 obtaining IPv4 leases for hostname %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the PostgreSQL database for a client with the specified
+hostname.
+
+% DHCPSRV_PGSQL_GET_HOSTNAME6 obtaining IPv6 leases for hostname %1
+A debug message issued when the server is attempting to obtain a set
+of IPv6 leases from the PostgreSQL database for a client with the specified
+hostname.
+
+% DHCPSRV_PGSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the PostgreSQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_PGSQL_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2, lease type %3
+A debug message issued when the server is attempting to obtain a set of IPv6
+leases from the PostgreSQL database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_PGSQL_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2, DUID %3, and lease type %4
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the PostgreSQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_PGSQL_GET_LINKADDR6 obtaining at most %1 IPv6 leases starting from address %2 with link %3/%4
+A debug message issued when the server is attempting to obtain a page of
+IPv6 leases beginning with the specified address within a link.
+
+% DHCPSRV_PGSQL_GET_PAGE4 obtaining at most %1 IPv4 leases starting from address %2
+A debug message issued when the server is attempting to obtain a page
+of leases beginning with the specified address.
+
+% DHCPSRV_PGSQL_GET_PAGE6 obtaining at most %1 IPv6 leases starting from address %2
+A debug message issued when the server is attempting to obtain a page
+of leases beginning with the specified address.
+
+% DHCPSRV_PGSQL_GET_RELAYID4 obtaining at most %1 IPv4 leases starting from address %2 with relay id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a relay id and client
+transaction time between start and end dates.
+
+% DHCPSRV_PGSQL_GET_REMOTEID4 obtaining at most %1 IPv4 leases starting from address %2 with remote id %3 and cltt between %4 and %5
+A debug message issued when the server is attempting to obtain a page of
+IPv4 leases beginning with the specified address with a remote id and client
+transaction time between start and end dates.
+
+% DHCPSRV_PGSQL_GET_SUBID4 obtaining IPv4 leases for subnet ID %1
+A debug message issued when the server is attempting to obtain all IPv4
+leases for a given subnet identifier from the PostgreSQL database.
+
+% DHCPSRV_PGSQL_GET_SUBID6 obtaining IPv6 leases for subnet ID %1
+A debug message issued when the server is attempting to obtain all IPv6
+leases for a given subnet identifier from the PostgreSQL database.
+
+% DHCPSRV_PGSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the PostgreSQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_PGSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the PostgreSQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_PGSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the PostgreSQL database.
+
+% DHCPSRV_PGSQL_HOST_DB opening PostgreSQL hosts database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a PostgreSQL hosts database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_PGSQL_HOST_DB_GET_VERSION obtaining schema version information for the PostgreSQL hosts database
+A debug message issued when the server is about to obtain schema version
+information from the PostgreSQL hosts database.
+
+% DHCPSRV_PGSQL_HOST_DB_READONLY PostgreSQL host database opened for read access only
+This informational message is issued when the user has configured the PostgreSQL
+database in read-only mode. Kea will not be able to insert or modify
+host reservations but will be able to retrieve existing ones and
+assign them to the clients communicating with the server.
+
+% DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED database reconnect failed: %1
+An error message issued when an attempt to reconnect has failed.
+
+% DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE scheduling attempt %1 of %2 in %3 milliseconds
+An info message issued when the server is scheduling the next attempt to reconnect
+to the database. This occurs when the server has lost database connectivity and
+is attempting to reconnect automatically.
+
+% DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED maximum number of database reconnect attempts: %1, has been exhausted without success
+An error message issued when the server failed to reconnect. Loss of connectivity
+is typically a network or database server issue.
+
+% DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED database reconnect failed: %1
+An error message issued when an attempt to reconnect has failed.
+
+% DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE scheduling attempt %1 of %2 in %3 milliseconds
+An info message issued when the server is scheduling the next attempt to reconnect
+to the database. This occurs when the server has lost database connectivity and
+is attempting to reconnect automatically.
+
+% DHCPSRV_PGSQL_LEASE_DB_RECONNECT_FAILED maximum number of database reconnect attempts: %1, has been exhausted without success
+An error message issued when the server failed to reconnect. Loss of connectivity
+is typically a network or database server issue.
+
+% DHCPSRV_PGSQL_NEGATIVE_LEASES_STAT recount of leases returned a negative value
+This warning message is issued when the recount of leases using counters
+in the PostgreSQL database returned a negative value. This shows a problem
+which can be fixed only by an offline direct recount on the database.
+This message is issued only once.
+
+% DHCPSRV_PGSQL_NO_TLS_SUPPORT Attempt to configure TLS (unsupported for PostgreSQL): %1
+This error message is printed when TLS support was required in the Kea
+configuration: Kea was built with this feature disabled for PostgreSQL.
+The parameters of the connection are logged.
+
+% DHCPSRV_PGSQL_ROLLBACK rolling back PostgreSQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_PGSQL_START_TRANSACTION starting a new PostgreSQL transaction
+A debug message issued when a new PostgreSQL transaction is being started.
+This message is typically not issued when inserting data into a
+single table because the server doesn't explicitly start
+transactions in this case. This message is issued when data is
+inserted into multiple tables with multiple INSERT statements
+and there may be a need to rollback the whole transaction if
+any of these INSERT statements fail.
+
+% DHCPSRV_PGSQL_TLS_SUPPORT Attempt to configure TLS: %1
+This informational message is printed when TLS support was required in
+the Kea configuration: The TLS support in PostgreSQL will be initialized but
+its configuration is fully managed outside the C API.
+The parameters of the connection are logged.
+
+% DHCPSRV_PGSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_UPDATE_ADDR6 updating IPv6 lease for address %1, lease type %2
+A debug message issued when the server is attempting to update IPv6
+lease from the PostgreSQL database for the specified address.
+
+% DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6 upgrading IPv6 leases done in %1 pages with %2 updated leases
+The server upgraded binary addresses. The number of pages and the
+final count of updated leases are displayed.
+
+% DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_ERROR upgrading binary address for IPv6 lease at %1 failed with %2
+An error message issued when the server failed to upgrade a binary address.
+The address of the lease and the error message are displayed.
+
+% DHCPSRV_PGSQL_UPGRADE_BINARY_ADDRESS6_PAGE upgrading IPv6 lease binary addresses at page %1 starting at %2 (updated %3)
+A debug message issued when the server upgrades IPv6 lease binary addresses.
+The page number and started address, and the count of already updated leases
+are displayed.
+
+% DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4 upgrading IPv4 leases done in %1 pages with %2 updated leases
+The server upgraded extended info. The number of pages and the final count of
+updated leases are displayed.
+
+% DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_ERROR upgrading extending info for IPv4 lease at %1 failed with %2
+A debug message issued when the server failed to upgrade an extended info.
+The address of the lease and the error message are displayed.
+
+% DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_PAGE upgrading IPv4 lease extended info at page %1 starting at %2 (updated %3)
+A debug message issued when the server upgrades IPv4 lease extended info.
+The page number and started address, and the count of already updated leases
+are displayed.
+
+% DHCPSRV_QUEUE_NCR %1: Name change request to %2 DNS entry queued: %3
+A debug message which is logged when the NameChangeRequest to add or remove
+a DNS entries for a particular lease has been queued. The first argument
+includes the client identification information. The second argument
+indicates whether the DNS entry is to be added or removed. The third
+argument carries the details of the NameChangeRequest.
+
+% DHCPSRV_QUEUE_NCR_FAILED %1: queuing %2 name change request failed for lease %3: %4
+This error message is logged when sending a NameChangeRequest
+to DHCP DDNS failed. The first argument includes the client identification
+information. The second argument indicates whether the DNS entry is to be
+added or removed. The third argument specifies the leased address. The
+last argument provides the reason for failure.
+
+% DHCPSRV_QUEUE_NCR_SKIP %1: skip queuing name change request for lease: %2
+This debug message is issued when the server decides to not queue the name
+change request because the lease doesn't include the FQDN, the forward and
+reverse update is disabled for this lease or the DNS updates are disabled
+in the configuration. The first argument includes the client identification
+information. The second argument includes the leased address.
+
+% DHCPSRV_SUBNET4O6_SELECT_FAILED Failed to select any subnet for the DHCPv4o6 packet
+A debug message issued when the server was unable to select any subnet for the
+DHCPv4o6 packet.
+
+% DHCPSRV_SUBNET4_SELECT_BY_ADDRESS_NO_MATCH No subnet matches address: %1
+A debug message issued when the server was unable to select a subnet using
+the specified address.
+
+% DHCPSRV_SUBNET4_SELECT_BY_INTERFACE_NO_MATCH No subnet matches interface: %1
+A debug message issued when the server was unable to select a subnet using
+the specified interface name.
+
+% DHCPSRV_SUBNET4_SELECT_BY_RELAY_ADDRESS_NO_MATCH No subnet matches relay address: %1
+A debug message issued when the server was unable to select a subnet using
+the specified relay address.
+
+% DHCPSRV_SUBNET4_SELECT_NO_RAI_OPTIONS No RAI options found to use for subnet selection.
+A debug message issued by the server when the client query does not include RAI
+options suitable for use with subnet selection.
+
+% DHCPSRV_SUBNET4_SELECT_NO_RELAY_ADDRESS Relay address (giaddr) in client packet is empty.
+A debug message issued when no relay address was specified to use for subnet
+selection.
+
+% DHCPSRV_SUBNET4_SELECT_NO_USABLE_ADDRESS No subnet selected because no suitable address to use for subnet selection was found.
+A debug message issued when the server was find a suitable address to use for
+subnet selection.
+
+% DHCPSRV_SUBNET6_SELECT_BY_ADDRESS_NO_MATCH No subnet matches address: %1
+A debug message issued when the server was unable to select a subnet using
+the specified address.
+
+% DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_ID_NO_MATCH No subnet matches interface id: %1
+A debug message issued when the server was unable to select a subnet using
+the specified interface id.
+
+% DHCPSRV_SUBNET6_SELECT_BY_INTERFACE_NO_MATCH No subnet matches interface: %1
+A debug message issued when the server was unable to select a subnet using
+the specified interface name.
+
+% DHCPSRV_TIMERMGR_CALLBACK_FAILED running handler for timer %1 caused exception: %2
+This error message is emitted when the timer elapsed and the
+operation associated with this timer has thrown an exception.
+The timer name and the reason for exception is logged.
+
+% DHCPSRV_TIMERMGR_REGISTER_TIMER registering timer: %1, using interval: %2 ms
+A debug message issued when the new interval timer is registered in
+the Timer Manager. This timer will have a callback function
+associated with it, and this function will be executed according
+to the interval specified. The unique name of the timer and the
+interval at which the callback function will be executed is
+included in the message.
+
+% DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION running operation for timer: %1
+A debug message issued when the Timer Manager is about to
+run a periodic operation associated with the given timer.
+An example of such operation is a periodic cleanup of
+expired leases. The name of the timer is included in the
+message.
+
+% DHCPSRV_TIMERMGR_START_TIMER starting timer: %1
+A debug message issued when the registered interval timer is
+being started. If this operation is successful the timer will
+periodically execute the operation associated with it. The
+name of the started timer is included in the message.
+
+% DHCPSRV_TIMERMGR_STOP_TIMER stopping timer: %1
+A debug message issued when the registered interval timer is
+being stopped. The timer remains registered and can be restarted
+if necessary. The name of the timer is included in the message.
+
+% DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS unregistering all timers
+A debug message issued when all registered interval timers are
+being unregistered from the Timer Manager.
+
+% DHCPSRV_TIMERMGR_UNREGISTER_TIMER unregistering timer: %1
+A debug message issued when one of the registered interval timers
+is unregistered from the Timer Manager. The name of the timer is
+included in the message.
+
+% DHCPSRV_UNEXPECTED_NAME database access parameters passed through '%1', expected 'lease-database'
+The parameters for access the lease database were passed to the server through
+the named configuration parameter, but the code was expecting them to be
+passed via the parameter named "lease-database". If the database opens
+successfully, there is no impact on server operation. However, as this does
+indicate an error in the source code, please submit a bug report.
+
+% DHCPSRV_UNKNOWN_DB unknown database type: %1
+The database access string specified a database type (given in the
+message) that is unknown to the software. This is a configuration error.
diff --git a/src/lib/dhcpsrv/flq_allocation_state.cc b/src/lib/dhcpsrv/flq_allocation_state.cc
new file mode 100644
index 0000000..816759a
--- /dev/null
+++ b/src/lib/dhcpsrv/flq_allocation_state.cc
@@ -0,0 +1,87 @@
+// Copyright (C) 2023 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 <dhcpsrv/flq_allocation_state.h>
+#include <boost/make_shared.hpp>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+PoolFreeLeaseQueueAllocationStatePtr
+PoolFreeLeaseQueueAllocationState::create(const PoolPtr& pool) {
+ return (boost::make_shared<PoolFreeLeaseQueueAllocationState>(pool->getType()));
+}
+
+PoolFreeLeaseQueueAllocationState::PoolFreeLeaseQueueAllocationState(Lease::Type type)
+ : AllocationState(), free_lease4_queue_(), free_lease6_queue_() {
+ if (type == Lease::TYPE_V4) {
+ free_lease4_queue_ = boost::make_shared<FreeLeaseQueue<uint32_t>>();
+ } else {
+ free_lease6_queue_ = boost::make_shared<FreeLeaseQueue<IOAddress>>();
+ }
+}
+
+bool
+PoolFreeLeaseQueueAllocationState::exhausted() const {
+ return ((free_lease4_queue_ && free_lease4_queue_->empty()) ||
+ (free_lease6_queue_ && free_lease6_queue_->empty()));
+}
+
+void
+PoolFreeLeaseQueueAllocationState::addFreeLease(const asiolink::IOAddress& address) {
+ if (free_lease4_queue_) {
+ free_lease4_queue_->push_back(address.toUint32());
+ } else {
+ free_lease6_queue_->push_back(address);
+ }
+}
+
+void
+PoolFreeLeaseQueueAllocationState::deleteFreeLease(const asiolink::IOAddress& address) {
+ if (free_lease4_queue_) {
+ auto& idx = free_lease4_queue_->get<1>();
+ idx.erase(address.toUint32());
+ } else {
+ auto& idx = free_lease6_queue_->get<1>();
+ idx.erase(address);
+ }
+}
+
+IOAddress
+PoolFreeLeaseQueueAllocationState::offerFreeLease() {
+ if (free_lease4_queue_) {
+ if (free_lease4_queue_->empty()) {
+ return (IOAddress::IPV4_ZERO_ADDRESS());
+ }
+ uint32_t lease = free_lease4_queue_->front();
+ free_lease4_queue_->pop_front();
+ free_lease4_queue_->push_back(lease);
+ return (IOAddress(lease));
+ }
+
+ if (free_lease6_queue_->empty()) {
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ IOAddress lease = free_lease6_queue_->front();
+ free_lease6_queue_->pop_front();
+ free_lease6_queue_->push_back(lease);
+ return (lease);
+}
+
+size_t
+PoolFreeLeaseQueueAllocationState::getFreeLeaseCount() const {
+ if (free_lease4_queue_) {
+ return (free_lease4_queue_->size());
+ }
+ return (free_lease6_queue_->size());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
diff --git a/src/lib/dhcpsrv/flq_allocation_state.h b/src/lib/dhcpsrv/flq_allocation_state.h
new file mode 100644
index 0000000..194edcf
--- /dev/null
+++ b/src/lib/dhcpsrv/flq_allocation_state.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 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 FLQ_ALLOCATION_STATE_H
+#define FLQ_ALLOCATION_STATE_H
+
+#include <dhcpsrv/allocation_state.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of the @c PoolFreeLeaseQueueAllocationState.
+class PoolFreeLeaseQueueAllocationState;
+
+/// @brief Type of the pointer to the @c PoolFreeLeaseQueueAllocationState.
+typedef boost::shared_ptr<PoolFreeLeaseQueueAllocationState> PoolFreeLeaseQueueAllocationStatePtr;
+
+/// @brief Pool allocation state used by the FLQ allocator.
+class PoolFreeLeaseQueueAllocationState : public AllocationState {
+public:
+
+ /// @brief Factory function creating the state instance from a pool.
+ ///
+ /// @param pool instance of the pool for which the allocation state
+ /// should be instantiated.
+ /// @return new allocation state instance.
+ static PoolFreeLeaseQueueAllocationStatePtr create(const PoolPtr& pool);
+
+ /// @brief Constructor.
+ ///
+ /// Instantiates the allocation state for the specified lease type.
+ ///
+ /// @param type lease type.
+ PoolFreeLeaseQueueAllocationState(Lease::Type type);
+
+ /// @brief Checks if the pool has run out of free leases.
+ ///
+ /// @return true if the pool has no free leases, false otherwise.
+ bool exhausted() const;
+
+ /// @brief Adds a free lease to the queue.
+ ///
+ /// @param address lease address.
+ void addFreeLease(const asiolink::IOAddress& address);
+
+ /// @brief Deletes free lease from the queue.
+ ///
+ /// @param address lease address.
+ void deleteFreeLease(const asiolink::IOAddress& address);
+
+ /// @brief Returns next available lease.
+ ///
+ /// @return next free lease address or IPv4/IPv6 zero address when
+ /// there are no free leases.
+ asiolink::IOAddress offerFreeLease();
+
+ /// @brief Returns the current number of free leases in the queue.
+ ///
+ /// @return the number of free leases in the queue.
+ size_t getFreeLeaseCount() const;
+
+private:
+
+ /// @brief A multi-index container holding free leases.
+ ///
+ /// When it is used as a storage for IPv4 leases, the @c AddressType
+ /// should be @c uint32_t. For IPv6 leases, it should be @c IOAddress.
+ /// Note that using the @c uint32_t for the IPv4 case significantly reduces
+ /// the amount of memory occupied by the container by removing the overhead
+ /// of holding the entire IOAddress instance.
+ template<typename AddressType>
+ using FreeLeaseQueue = boost::multi_index_container<
+ AddressType,
+ boost::multi_index::indexed_by<
+ boost::multi_index::sequenced<>,
+ boost::multi_index::hashed_unique<
+ boost::multi_index::identity<AddressType>
+ >
+ >
+ >;
+
+ /// @brief A multi-index container holding free IPv4 leases.
+ typedef boost::shared_ptr<FreeLeaseQueue<uint32_t>> FreeLease4QueuePtr;
+
+ /// @brief A multi-index container holding free IPv6 leases.
+ typedef boost::shared_ptr<FreeLeaseQueue<asiolink::IOAddress>> FreeLease6QueuePtr;
+
+ /// @brief An instance of the multi-index container holding
+ /// free IPv4 leases.
+ FreeLease4QueuePtr free_lease4_queue_;
+
+ /// @brief An instance of the multi-index container holding
+ /// free IPv6 leases.
+ FreeLease6QueuePtr free_lease6_queue_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // FLQ_ALLOCATION_STATE_H
diff --git a/src/lib/dhcpsrv/flq_allocator.cc b/src/lib/dhcpsrv/flq_allocator.cc
new file mode 100644
index 0000000..8da982f
--- /dev/null
+++ b/src/lib/dhcpsrv/flq_allocator.cc
@@ -0,0 +1,361 @@
+// Copyright (C) 2023 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 <asiolink/addr_utilities.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/ip_range_permutation.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <util/stopwatch.h>
+#include <unordered_set>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+/// @brief An owner string used in the callbacks installed in
+/// the lease manager.
+const string FLQ_OWNER = "flq";
+}
+
+namespace isc {
+namespace dhcp {
+
+FreeLeaseQueueAllocator::FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
+ : Allocator(type, subnet), generator_() {
+ random_device rd;
+ generator_.seed(rd());
+}
+
+IOAddress
+FreeLeaseQueueAllocator::pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr&,
+ const IOAddress&) {
+ auto subnet = subnet_.lock();
+ auto const& pools = subnet->getPools(pool_type_);
+ if (pools.empty()) {
+ // No pools, no allocation.
+ return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Let's first iterate over the pools and identify the ones that
+ // meet client class criteria and are not exhausted.
+ std::vector<uint64_t> available;
+ for (auto i = 0; i < pools.size(); ++i) {
+ // Check if the pool is allowed for the client's classes.
+ if (pools[i]->clientSupported(client_classes)) {
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pools[i]);
+ if (!pool_state->exhausted()) {
+ // There are still available addresses in this pool.
+ available.push_back(i);
+ }
+ }
+ }
+ if (available.empty()) {
+ // No pool meets the client class criteria or all are exhausted.
+ return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Get a random pool from the available ones.
+ auto const& pool = pools[available[getRandomNumber(available.size() - 1)]];
+
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pool);
+
+ // The pool should still offer some leases.
+ auto free_lease = pool_state->offerFreeLease();
+ // It shouldn't happen, but let's be safe.
+ if (!free_lease.isV4Zero() && !free_lease.isV6Zero()) {
+ return (free_lease);
+ }
+ // No address available.
+ return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+IOAddress
+FreeLeaseQueueAllocator::pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool6,
+ const IdentifierBaseTypePtr&,
+ PrefixLenMatchType prefix_length_match,
+ const IOAddress&,
+ uint8_t hint_prefix_length) {
+ auto subnet = subnet_.lock();
+ auto const& pools = subnet->getPools(pool_type_);
+ if (pools.empty()) {
+ // No pool, no allocation.
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Let's first iterate over the pools and identify the ones that
+ // meet client class criteria and are not exhausted.
+ std::vector<uint64_t> available;
+ for (auto i = 0; i < pools.size(); ++i) {
+ // Check if the pool is allowed for the client's classes.
+ if (pools[i]->clientSupported(client_classes)) {
+ if (!Allocator::isValidPrefixPool(prefix_length_match, pools[i],
+ hint_prefix_length)) {
+ continue;
+ }
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pools[i]);
+ if (!pool_state->exhausted()) {
+ // There are still available prefixes in this pool.
+ available.push_back(i);
+ }
+ }
+ }
+ if (available.empty()) {
+ // No pool meets the client class criteria or all are exhausted.
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+ }
+ // Get a random pool from the available ones.
+ auto const& pool = pools[available[getRandomNumber(available.size() - 1)]];
+ pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ if (!pool6) {
+ // Something is gravely wrong here
+ isc_throw(Unexpected, "Wrong type of pool: "
+ << (pool)->toText()
+ << " is not Pool6");
+ }
+ // Get or create the pool state.
+ auto pool_state = getPoolState(pool);
+ // The pool should still offer some leases.
+ auto free_lease = pool_state->offerFreeLease();
+ // It shouldn't happen, but let's be safe.
+ if (!free_lease.isV6Zero()) {
+ return (free_lease);
+ }
+ // No prefix available.
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+void
+FreeLeaseQueueAllocator::initAfterConfigureInternal() {
+ auto subnet = subnet_.lock();
+ auto const& pools = subnet->getPools(pool_type_);
+ if (pools.empty()) {
+ // If there are no pools there is nothing to do.
+ return;
+ }
+ Lease4Collection leases4;
+ Lease6Collection leases6;
+ switch (pool_type_) {
+ case Lease::TYPE_V4:
+ leases4 = LeaseMgrFactory::instance().getLeases4(subnet->getID());
+ populateFreeAddressLeases(leases4, pools);
+ break;
+ case Lease::TYPE_NA:
+ case Lease::TYPE_TA:
+ leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
+ populateFreeAddressLeases(leases6, pools);
+ break;
+ case Lease::TYPE_PD:
+ leases6 = LeaseMgrFactory::instance().getLeases6(subnet->getID());
+ populateFreePrefixDelegationLeases(leases6, pools);
+ break;
+ default:
+ ;
+ }
+ // Install the callbacks for lease add, update and delete in the interface manager.
+ // These callbacks will ensure that we have up-to-date free lease queue.
+ auto& lease_mgr = LeaseMgrFactory::instance();
+ lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+ std::bind(&FreeLeaseQueueAllocator::addLeaseCallback, this,
+ std::placeholders::_1));
+ lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+ std::bind(&FreeLeaseQueueAllocator::updateLeaseCallback, this,
+ std::placeholders::_1));
+ lease_mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, FLQ_OWNER, subnet->getID(), pool_type_,
+ std::bind(&FreeLeaseQueueAllocator::deleteLeaseCallback, this,
+ std::placeholders::_1));
+}
+
+template<typename LeaseCollectionType>
+void
+FreeLeaseQueueAllocator::populateFreeAddressLeases(const LeaseCollectionType& leases,
+ const PoolCollection& pools) {
+ auto subnet = subnet_.lock();
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES)
+ .arg(subnet->toText());
+
+ Stopwatch stopwatch;
+
+ // Let's iterate over the lease queue and index them with the
+ // unordered_set. Also, elminate the expired leases and those
+ // in the expired-reclaimed state.
+ unordered_set<IOAddress, IOAddress::Hash> leased_addresses;
+ for (auto lease : leases) {
+ if ((lease->getType() == pool_type_) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
+ leased_addresses.insert(lease->addr_);
+ }
+ }
+ // For each pool, check if the address is in the leases list.
+ size_t free_lease_count = 0;
+ for (auto pool : pools) {
+ // Create the pool permutation so the resulting lease queue is no
+ // particular order.
+ IPRangePermutation perm(AddressRange(pool->getFirstAddress(), pool->getLastAddress()));
+ auto pool_state = getPoolState(pool);
+ auto done = false;
+ while (!done) {
+ auto address = perm.next(done);
+ if (address.isV4Zero() || address.isV6Zero()) {
+ continue;
+ }
+ if (leased_addresses.count(address) == 0) {
+ // No lease for this address, so add it to the free leases queue.
+ pool_state->addFreeLease(address);
+ }
+ }
+ free_lease_count += pool_state->getFreeLeaseCount();
+ }
+
+ stopwatch.stop();
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_ADDRESS_LEASES_DONE)
+ .arg(free_lease_count)
+ .arg(subnet->toText())
+ .arg(stopwatch.logFormatLastDuration());
+}
+
+void
+FreeLeaseQueueAllocator::populateFreePrefixDelegationLeases(const Lease6Collection& leases,
+ const PoolCollection& pools) {
+ auto subnet = subnet_.lock();
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES)
+ .arg(subnet->toText());
+
+ Stopwatch stopwatch;
+
+ // Let's iterate over the lease queue and index them with the
+ // unordered_set. Also, elminate the expired leases and those
+ // in the expired-reclaimed state.
+ unordered_set<IOAddress, IOAddress::Hash> leased_prefixes;
+ for (auto lease : leases) {
+ if ((lease->getType() == Lease::TYPE_PD) && (!lease->expired()) && (!lease->stateExpiredReclaimed())) {
+ leased_prefixes.insert(lease->addr_);
+ }
+ }
+ // For each pool, check if the prefix is in the leases list.
+ size_t free_lease_count = 0;
+ for (auto pool : pools) {
+ auto pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+ if (!pool6) {
+ continue;
+ }
+ // Create the pool permutation so the resulting lease queue is no
+ // particular order.
+ IPRangePermutation perm(PrefixRange(pool->getFirstAddress(),
+ pool->getLastAddress(),
+ pool6->getLength()));
+ auto pool_state = getPoolState(pool);
+ auto done = false;
+ while (!done) {
+ auto prefix = perm.next(done);
+ if (prefix.isV4Zero() || prefix.isV6Zero()) {
+ continue;
+ }
+ if (leased_prefixes.count(prefix) == 0) {
+ // No lease for this prefix, so add it to the free leases queue.
+ pool_state->addFreeLease(prefix);
+ }
+ }
+ free_lease_count += pool_state->getFreeLeaseCount();
+ }
+
+ stopwatch.stop();
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_FLQ_POPULATE_FREE_PREFIX_LEASES_DONE)
+ .arg(free_lease_count)
+ .arg(subnet->toText())
+ .arg(stopwatch.logFormatLastDuration());
+}
+
+PoolFreeLeaseQueueAllocationStatePtr
+FreeLeaseQueueAllocator::getPoolState(const PoolPtr& pool) const {
+ if (!pool->getAllocationState()) {
+ pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
+ }
+ return (boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState()));
+}
+
+PoolPtr
+FreeLeaseQueueAllocator::getLeasePool(const LeasePtr& lease) const {
+ auto subnet = subnet_.lock();
+ if (!subnet) {
+ return (PoolPtr());
+ }
+ auto pool = subnet->getPool(pool_type_, lease->addr_, false);
+ return (pool);
+}
+
+void
+FreeLeaseQueueAllocator::addLeaseCallback(LeasePtr lease) {
+ MultiThreadingLock lock(mutex_);
+ addLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::addLeaseCallbackInternal(LeasePtr lease) {
+ if (lease->expired()) {
+ return;
+ }
+ auto pool = getLeasePool(lease);
+ if (!pool) {
+ return;
+ }
+ getPoolState(pool)->deleteFreeLease(lease->addr_);
+}
+
+void
+FreeLeaseQueueAllocator::updateLeaseCallback(LeasePtr lease) {
+ MultiThreadingLock lock(mutex_);
+ updateLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::updateLeaseCallbackInternal(LeasePtr lease) {
+ auto pool = getLeasePool(lease);
+ if (!pool) {
+ return;
+ }
+ auto pool_state = getPoolState(pool);
+ if (lease->stateExpiredReclaimed() || (lease->expired())) {
+ pool_state->addFreeLease(lease->addr_);
+ } else {
+ pool_state->deleteFreeLease(lease->addr_);
+ }
+}
+
+void
+FreeLeaseQueueAllocator::deleteLeaseCallback(LeasePtr lease) {
+ MultiThreadingLock lock(mutex_);
+ deleteLeaseCallbackInternal(lease);
+}
+
+void
+FreeLeaseQueueAllocator::deleteLeaseCallbackInternal(LeasePtr lease) {
+ auto pool = getLeasePool(lease);
+ if (!pool) {
+ return;
+ }
+ getPoolState(pool)->addFreeLease(lease->addr_);
+}
+
+uint64_t
+FreeLeaseQueueAllocator::getRandomNumber(uint64_t limit) {
+ // Take the short path if there is only one number to randomize from.
+ if (limit == 0) {
+ return (0);
+ }
+ std::uniform_int_distribution<uint64_t> dist(0, limit);
+ return (dist(generator_));
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/flq_allocator.h b/src/lib/dhcpsrv/flq_allocator.h
new file mode 100644
index 0000000..026ccc3
--- /dev/null
+++ b/src/lib/dhcpsrv/flq_allocator.h
@@ -0,0 +1,202 @@
+// Copyright (C) 2023 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 FLQ_ALLOCATOR_H
+#define FLQ_ALLOCATOR_H
+
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief An allocator maintaining a queue of free leases.
+///
+/// This allocator populates the queue of free leases during the initialization.
+/// It also installs the callbacks in the @c LeaseMgr, to track the subsequent
+/// lease changes. It allows for maintaining the running queue of free leases.
+///
+/// The allocator offers the leases from the queue, minimizing the number of
+/// the allocation engine's attempts to check if some other client is using
+/// the offered lease. Ideally, only one check should suffice. However, when
+/// several servers share a lease database, the collisions may occur because
+/// the servers don't observe each others' allocations.
+///
+/// This allocator should only be used for reasonably small pools due to the
+/// overhead to populate the free leases. A reasonably small pool is an IPv4
+/// pool (including /8) and the prefix delegation pools with similar capacity.
+/// This allocator is not suitable for a typical IPv6 address pool (e.g., /64).
+/// An attempt to populate free leases for such a giant pool would freeze the
+/// server and likely exhaust its memory.
+///
+/// Free leases are populated in a random order.
+class FreeLeaseQueueAllocator : public Allocator {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param type specifies the type of allocated leases.
+ /// @param subnet weak pointer to the subnet owning the allocator.
+ FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr& subnet);
+
+ /// @brief Returns the allocator type string.
+ ///
+ /// @return flq string.
+ virtual std::string getType() const {
+ return ("flq");
+ }
+
+private:
+
+ /// @brief Performs allocator initialization after server's reconfiguration.
+ ///
+ /// The allocator installs the callbacks in the lease manager to keep track of
+ /// the lease allocations and maintain the free leases queue.
+ virtual void initAfterConfigureInternal();
+
+ /// @brief Populates the queue of free addresses (IPv4 and IPv6).
+ ///
+ /// It adds each address in the subnet pools that does not exist in the
+ /// list of leases to the free leases queue. The addresses are added
+ /// in a random order.
+ ///
+ /// @param lease collection of leases in the database for a subnet.
+ /// @param pools collection of pools in the subnet.
+ /// @tparam LeaseCollectionType Type of the lease collection returned from the
+ /// database (i.e., @c Lease4Collection or @c Lease6Collection).
+ template<typename LeaseCollectionType>
+ void populateFreeAddressLeases(const LeaseCollectionType& leases, const PoolCollection& pools);
+
+ /// @brief Populates the queue of free delegated prefixes.
+ ///
+ /// It adds each delegated prefix in the subnet pools that does not exist in the
+ /// list of leases to the free leases queue. The delegated prefixes are added
+ /// in a random order.
+ ///
+ /// @param lease collection of delegated prefixes in the database for a subnet.
+ /// @param pools collection of prefix delegation pools in the subnet.
+ void populateFreePrefixDelegationLeases(const Lease6Collection& leases, const PoolCollection& pools);
+
+ /// @brief Returns next available address from the queue.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickAddress.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param duid client DUID (ignored).
+ /// @param hint client hint (ignored).
+ ///
+ /// @return next offered address.
+ virtual asiolink::IOAddress pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr& duid,
+ const asiolink::IOAddress& hint);
+
+ /// @brief Returns next available delegated prefix from the queue.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickPrefix.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param pool the selected pool satisfying all required conditions.
+ /// @param duid Client's DUID.
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length
+ /// @param hint Client's hint.
+ /// @param hint_prefix_length the hint prefix length that the client
+ /// provided. The 0 value means that there is no hint and that any
+ /// pool will suffice.
+ ///
+ /// @return the next prefix.
+ virtual isc::asiolink::IOAddress
+ pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool,
+ const IdentifierBaseTypePtr& duid,
+ PrefixLenMatchType prefix_length_match,
+ const isc::asiolink::IOAddress& hint,
+ uint8_t hint_prefix_length);
+
+ /// @brief Convenience function returning pool allocation state instance.
+ ///
+ /// It creates a new pool state instance and assigns it to the pool
+ /// if it hasn't been initialized.
+ ///
+ /// @param pool pool instance.
+ /// @return allocation state instance for the pool.
+ PoolFreeLeaseQueueAllocationStatePtr getPoolState(const PoolPtr& pool) const;
+
+ /// @brief Returns a pool in the subnet the lease belongs to.
+ ///
+ /// This function is used in the interface manager callbacks to find
+ /// a pool for a lease modified in the database.
+ ///
+ /// @param lease lease instance for which the pool should be returned.
+ /// @return A pool found for a lease or null pointer if such a pool does
+ /// not exist.
+ PoolPtr getLeasePool(const LeasePtr& lease) const;
+
+ /// @brief Thread safe callback for adding a lease.
+ ///
+ /// Removes the lease from the free lease queue.
+ ///
+ /// @param lease added lease.
+ void addLeaseCallback(LeasePtr lease);
+
+ /// @brief Thread unsafe callback for adding a lease.
+ ///
+ /// Removes the lease from the free lease queue.
+ ///
+ /// @param lease added lease.
+ void addLeaseCallbackInternal(LeasePtr lease);
+
+ /// @brief Thread safe callback for updating a lease.
+ ///
+ /// If the lease is reclaimed in this update it is added to the
+ /// free lease queue. If the lease is valid after the update,
+ /// the lease is removed from the free lease queue, if exists.
+ ///
+ /// @param lease updated lease.
+ void updateLeaseCallback(LeasePtr lease);
+
+ /// @brief Thread unsafe callback for updating a lease.
+ ///
+ /// If the lease is reclaimed in this update it is added to the
+ /// free lease queue. If the lease is valid after the update,
+ /// the lease is removed from the free lease queue, if exists.
+ ///
+ /// @param lease updated lease.
+ void updateLeaseCallbackInternal(LeasePtr lease);
+
+ /// @brief Thread safe callback for deleting a lease.
+ ///
+ /// Adds the lease to the free lease queue.
+ ///
+ /// @param lease deleted lease.
+ void deleteLeaseCallback(LeasePtr lease);
+
+ /// @brief Thread unsafe callback for updating a lease.
+ ///
+ /// Adds the lease to the free lease queue.
+ ///
+ /// @param lease deleted lease.
+ void deleteLeaseCallbackInternal(LeasePtr lease);
+
+ /// @brief Convenience function returning a random number.
+ ///
+ /// It is used internally by the @c pickAddressInternal and @c pickPrefixInternal
+ /// functions to select a random pool.
+ ///
+ /// @param limit upper bound of the range.
+ /// @returns random number between 0 and limit.
+ uint64_t getRandomNumber(uint64_t limit);
+
+ /// @brief Random generator used by this class.
+ std::mt19937 generator_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // FLQ_ALLOCATOR_H
diff --git a/src/lib/dhcpsrv/fuzz.cc b/src/lib/dhcpsrv/fuzz.cc
new file mode 100644
index 0000000..a62d7a6
--- /dev/null
+++ b/src/lib/dhcpsrv/fuzz.cc
@@ -0,0 +1,209 @@
+// Copyright (C) 2016-2019 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>
+
+#ifdef ENABLE_AFL
+
+#ifndef __AFL_LOOP
+#error To use American Fuzzy Lop you have to set CXX to afl-clang-fast++
+#endif
+
+#include <dhcp/dhcp6.h>
+#include <dhcpsrv/fuzz.h>
+#include <dhcpsrv/fuzz_log.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <ctime>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace std;
+
+// Constants defined in the Fuzz class definition.
+constexpr size_t Fuzz::BUFFER_SIZE;
+constexpr size_t Fuzz::MAX_SEND_SIZE;
+constexpr long Fuzz::MAX_LOOP_COUNT;
+
+// Constructor
+Fuzz::Fuzz(int ipversion, uint16_t port) :
+ loop_max_(MAX_LOOP_COUNT), sockaddr_len_(0), sockaddr_ptr_(nullptr),
+ sockfd_(-1) {
+
+ try {
+ stringstream reason; // Used to construct exception messages
+
+ // Get the values of the environment variables used to control the
+ // fuzzing.
+
+ // Specfies the interface to be used to pass packets from AFL to Kea.
+ const char* interface = getenv("KEA_AFL_INTERFACE");
+ if (! interface) {
+ isc_throw(FuzzInitFail, "no fuzzing interface has been set");
+ }
+
+ // The address on the interface to be used.
+ const char* address = getenv("KEA_AFL_ADDRESS");
+ if (address == 0) {
+ isc_throw(FuzzInitFail, "no fuzzing address has been set");
+ }
+
+ // Number of Kea packet-read loops before Kea exits and AFL starts a
+ // new instance. This is optional: the default is set by the constant
+ // MAX_LOOP_COUNT.
+ const char *loop_max_ptr = getenv("KEA_AFL_LOOP_MAX");
+ if (loop_max_ptr != 0) {
+ try {
+ loop_max_ = boost::lexical_cast<long>(loop_max_ptr);
+ } catch (const boost::bad_lexical_cast&) {
+ reason << "cannot convert loop count " << loop_max_ptr
+ << " to an integer";
+ isc_throw(FuzzInitFail, reason.str());
+ }
+
+ if (loop_max_ <= 0) {
+ reason << "KEA_AFL_LOOP_MAX is " << loop_max_ << ". "
+ << "It must be an integer greater than zero.";
+ isc_throw(FuzzInitFail, reason.str());
+ }
+ }
+
+ // Set up address structures used to route the packets from AFL to Kea.
+ createAddressStructures(ipversion, interface, address, port);
+
+ // Create the socket through which packets read from stdin will be sent
+ // to the port on which Kea is listening. This is closed in the
+ // destructor.
+ sockfd_ = socket((ipversion == 4) ? AF_INET : AF_INET6, SOCK_DGRAM, 0);
+ if (sockfd_ < 0) {
+ LOG_FATAL(fuzz_logger, FUZZ_SOCKET_CREATE_FAIL)
+ .arg(strerror(errno));
+ return;
+ }
+
+ LOG_INFO(fuzz_logger, FUZZ_INIT_COMPLETE).arg(interface).arg(address)
+ .arg(port).arg(loop_max_);
+
+ } catch (const FuzzInitFail& e) {
+ // AFL tends to make it difficult to find out what exactly has failed:
+ // make sure that the error is logged.
+ LOG_FATAL(fuzz_logger, FUZZ_INIT_FAIL).arg(e.what());
+ throw;
+ }
+}
+
+// Destructor
+Fuzz::~Fuzz() {
+ static_cast<void>(close(sockfd_));
+}
+
+// Set up address structures.
+void
+Fuzz::createAddressStructures(int ipversion, const char* interface,
+ const char* address, uint16_t port) {
+ stringstream reason; // Used in error messages
+
+ // Set up the appropriate data structure depending on the address given.
+ if ((ipversion == 6) && (strstr(address, ":") != NULL)) {
+ // Expecting IPv6 and the address contains a colon, so assume it is an
+ // an IPv6 address.
+ memset(&servaddr6_, 0, sizeof (servaddr6_));
+
+ servaddr6_.sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, address, &servaddr6_.sin6_addr) != 1) {
+ reason << "inet_pton() failed: can't convert "
+ << address << " to an IPv6 address" << endl;
+ isc_throw(FuzzInitFail, reason.str());
+ }
+ servaddr6_.sin6_port = htons(port);
+
+ // Interface ID is needed for IPv6 address structures.
+ servaddr6_.sin6_scope_id = if_nametoindex(interface);
+ if (servaddr6_.sin6_scope_id == 0) {
+ reason << "error retrieving interface ID for "
+ << interface << ": " << strerror(errno);
+ isc_throw(FuzzInitFail, reason.str());
+ }
+
+ sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr6_);
+ sockaddr_len_ = sizeof(servaddr6_);
+
+ } else if ((ipversion == 4) && (strstr(address, ".") != NULL)) {
+ // Expecting an IPv4 address and it contains a dot, so assume it is.
+ // This check is done after the IPv6 check, as it is possible for an
+ // IPv4 address to be embedded in an IPv6 one.
+ memset(&servaddr4_, 0, sizeof(servaddr4_));
+
+ servaddr4_.sin_family = AF_INET;
+ if (inet_pton(AF_INET, address, &servaddr4_.sin_addr) != 1) {
+ reason << "inet_pton() failed: can't convert "
+ << address << " to an IPv6 address" << endl;
+ isc_throw(FuzzInitFail, reason.str());
+ }
+ servaddr4_.sin_port = htons(port);
+
+ sockaddr_ptr_ = reinterpret_cast<sockaddr*>(&servaddr4_);
+ sockaddr_len_ = sizeof(servaddr4_);
+
+ } else {
+ reason << "Expected IP version (" << ipversion << ") is not "
+ << "4 or 6, or the given address " << address << " does not "
+ << "match the IP version expected";
+ isc_throw(FuzzInitFail, reason.str());
+ }
+
+}
+
+
+// This is the main fuzzing function. It receives data from fuzzing engine over
+// stdin and then sends it to the configured UDP socket.
+void
+Fuzz::transfer(void) const {
+
+ // Read from stdin. Just return if nothing is read (or there is an error)
+ // and hope that this does not cause a hang.
+ char buf[BUFFER_SIZE];
+ ssize_t length = read(0, buf, sizeof(buf));
+
+ // Save the errno in case there was an error because if debugging is
+ // enabled, the following LOG_DEBUG call may destroy its value.
+ int errnum = errno;
+ LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_DATA_READ).arg(length);
+
+ if (length > 0) {
+ // Now send the data to the UDP port on which Kea is listening.
+ // Send the data to the main Kea thread. Limit the size of the
+ // packets that can be sent.
+ size_t send_len = (length < MAX_SEND_SIZE) ? length : MAX_SEND_SIZE;
+ ssize_t sent = sendto(sockfd_, buf, send_len, 0, sockaddr_ptr_,
+ sockaddr_len_);
+ if (sent > 0) {
+ LOG_DEBUG(fuzz_logger, FUZZ_DBG_TRACE_DETAIL, FUZZ_SEND).arg(sent);
+ } else if (sent != length) {
+ LOG_WARN(fuzz_logger, FUZZ_SHORT_SEND).arg(length).arg(sent);
+ } else {
+ LOG_ERROR(fuzz_logger, FUZZ_SEND_ERROR).arg(strerror(errno));
+ }
+ } else {
+ // Read did not get any bytes. A zero-length read (EOF) may have been
+ // generated by AFL, so don't log that. But otherwise log an error.
+ if (length != 0) {
+ LOG_ERROR(fuzz_logger, FUZZ_READ_FAIL).arg(strerror(errnum));
+ }
+ }
+
+}
+
+#endif // ENABLE_AFL
diff --git a/src/lib/dhcpsrv/fuzz.h b/src/lib/dhcpsrv/fuzz.h
new file mode 100644
index 0000000..c71e6c5
--- /dev/null
+++ b/src/lib/dhcpsrv/fuzz.h
@@ -0,0 +1,144 @@
+// Copyright (C) 2016-2019 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 FUZZ_H
+#define FUZZ_H
+
+#ifdef ENABLE_AFL
+
+#include <exceptions/exceptions.h>
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <string>
+#include <thread>
+
+namespace isc {
+
+
+/// @brief AFL Fuzzing
+///
+/// Persistent-mode AFL fuzzing has the AFL fuzzer send packets of data to
+/// stdin of the program being tested. The program processes the data and
+/// signals to AFL that it is complete.
+///
+/// To reduce the code changes required, the scheme adopted for Kea is that
+/// the AFL data read from stdin is written to an address/port on which Kea
+/// is listening. Kea then reads the data from that port and processes it
+/// in the usual way.
+///
+/// The Fuzz class handles the transfer of data between AFL and Kea. After
+/// suitable initialization, its transfer() method is called in the main
+/// processing loop, right before Kea waits for input. The method handles the
+/// read from stdin and the write to the selected address port.
+
+class Fuzz {
+public:
+ /// @brief size of the buffer used to transfer data between AFL and Kea.
+ ///
+ /// This is much larger than the data that will be sent to Kea (so AFL
+ /// data may be trimmed). However, it does allow for AFL to send quite
+ /// large packets without resulting in AFL synchronization problems because
+ /// Kea has not read all the data sent.
+ static constexpr size_t BUFFER_SIZE = 256 * 1024;
+
+ /// @brief maximum size of packets fuzzing thread will send to Kea
+ ///
+ /// This is below the maximum size of data that we will allow Kea to put
+ /// into a single UDP datagram so as to avoid any "data too big" errors
+ /// when trying to send it to the port on which Kea listens.
+ static constexpr size_t MAX_SEND_SIZE = 64000;
+
+ /// @brief Number of packets Kea will process before shutting down.
+ ///
+ /// After the shutdown, AFL will restart it. This safety switch is here for
+ /// eliminating cases where Kea goes into a weird state and stops
+ /// processing packets properly. This can be overridden by setting the
+ /// environment variable KEA_AFL_LOOP_MAX.
+ static constexpr long MAX_LOOP_COUNT = 1000;
+
+
+ /// @brief Constructor
+ ///
+ /// Sets up data structures to access the address/port being used to
+ /// transfer data from AFL to Kea.
+ ///
+ /// @param ipversion Either 4 or 6 depending on what IP version the
+ /// server responds to.
+ /// @param port Port on which the server is listening, and hence the
+ /// port to which the fuzzer will send input from AFL.
+ Fuzz(int ipversion, uint16_t port);
+
+ /// @brief Destructor
+ ///
+ /// Closes the socket used for transferring data from stdin to the selected
+ /// interface.
+ ~Fuzz();
+
+ /// @brief Transfer Data
+ ///
+ /// Called immediately prior to Kea reading data, this reads stdin (where
+ /// AFL will have sent the packet being tested) and copies the data to the
+ /// interface on which Kea is listening.
+ void transfer(void) const;
+
+ /// @brief Return Max Loop Count
+ ///
+ /// Returns the maximum number of loops (i.e. AFL packets processed) before
+ /// Kea exits. This is the value of the environment variable
+ /// FUZZ_AFL_LOOP_MAX, or the class constant MAX_LOOP_COUNT if that is not
+ /// defined.
+ ///
+ /// @return Maximum loop count
+ long maxLoopCount() const {
+ return loop_max_;
+ }
+
+private:
+ /// @brief Create address structures
+ ///
+ /// Create the address structures describing the address/port on whick Kea
+ /// is listening for packets from AFL.
+ ///
+ /// @param ipversion Either 4 or 6 depending on which IP version address
+ /// is expected.
+ /// @param interface Interface through which the fuzzer is sending packets
+ /// to Kea.
+ /// @param address Address on the interface that will be used.
+ /// @param port Port to be used.
+ ///
+ /// @throws FuzzInitFail Thrown if the address is not in the expected
+ /// format.
+ void createAddressStructures(int ipversion, const char* interface,
+ const char* address, uint16_t port);
+
+ // Other member variables.
+ long loop_max_; //< Maximum number of loop iterations
+ size_t sockaddr_len_; //< Length of the structure
+ struct sockaddr* sockaddr_ptr_; //< Pointer to structure used
+ struct sockaddr_in servaddr4_; //< IPv6 address information
+ struct sockaddr_in6 servaddr6_; //< IPv6 address information
+ int sockfd_; //< Socket used to transfer data
+};
+
+
+/// @brief Exception thrown if fuzzing initialization fails.
+class FuzzInitFail : public Exception {
+public:
+ FuzzInitFail(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+}
+
+#endif // ENABLE_AFL
+
+#endif // FUZZ_H
diff --git a/src/lib/dhcpsrv/fuzz_log.cc b/src/lib/dhcpsrv/fuzz_log.cc
new file mode 100644
index 0000000..53b860a
--- /dev/null
+++ b/src/lib/dhcpsrv/fuzz_log.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2015-2019 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/.
+
+/// @brief Defines the logger used by the @c isc::dhcp::HostMgr
+
+#include <config.h>
+
+#include "dhcpsrv/fuzz_log.h"
+
+namespace isc {
+namespace dhcp {
+
+extern const int FUZZ_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int FUZZ_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
+
+isc::log::Logger fuzz_logger("fuzz");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/fuzz_log.h b/src/lib/dhcpsrv/fuzz_log.h
new file mode 100644
index 0000000..3c0a627
--- /dev/null
+++ b/src/lib/dhcpsrv/fuzz_log.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2015-2019 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 FUZZ_LOG_H
+#define FUZZ_LOG_H
+
+#include <dhcpsrv/fuzz_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief Logging levels for fuzzing output
+///
+/// Defines the levels used to output debug messages during fuzzing.
+
+/// @brief Traces normal operations
+///
+/// An example of the normal operation is a report of a packet being received
+/// from the fuzzer.
+extern const int FUZZ_DBG_TRACE;
+
+/// @brief Record detailed traces
+///
+/// Messages logged at this level will log detailed tracing information.
+extern const int FUZZ_DBG_TRACE_DETAIL;
+
+///@}
+
+/// @brief Logger for the @c HostMgr and the code it calls.
+///
+/// Define the logger used to log messages in @c HostMgr and the code it
+/// calls to manage host reservations.
+extern isc::log::Logger fuzz_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // FUZZ_LOG_H
diff --git a/src/lib/dhcpsrv/fuzz_messages.cc b/src/lib/dhcpsrv/fuzz_messages.cc
new file mode 100644
index 0000000..651f2e2
--- /dev/null
+++ b/src/lib/dhcpsrv/fuzz_messages.cc
@@ -0,0 +1,39 @@
+// File created from ../../../src/lib/dhcpsrv/fuzz_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID FUZZ_DATA_READ = "FUZZ_DATA_READ";
+extern const isc::log::MessageID FUZZ_INIT_COMPLETE = "FUZZ_INIT_COMPLETE";
+extern const isc::log::MessageID FUZZ_INIT_FAIL = "FUZZ_INIT_FAIL";
+extern const isc::log::MessageID FUZZ_READ_FAIL = "FUZZ_READ_FAIL";
+extern const isc::log::MessageID FUZZ_SEND = "FUZZ_SEND";
+extern const isc::log::MessageID FUZZ_SEND_ERROR = "FUZZ_SEND_ERROR";
+extern const isc::log::MessageID FUZZ_SHORT_SEND = "FUZZ_SHORT_SEND";
+extern const isc::log::MessageID FUZZ_SOCKET_CREATE_FAIL = "FUZZ_SOCKET_CREATE_FAIL";
+
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "FUZZ_DATA_READ", "read %1 byte(s) from AFL via stdin",
+ "FUZZ_INIT_COMPLETE", "fuzz initialization complete: interface %1, address %2, port %3, max loops %4",
+ "FUZZ_INIT_FAIL", "fuzz initialization failure, reason: %1",
+ "FUZZ_READ_FAIL", "error reading input from fuzzer: %1",
+ "FUZZ_SEND", "sent %1 byte(s) to the socket connected to the Kea interface",
+ "FUZZ_SEND_ERROR", "failed to send data to Kea input socket: %1",
+ "FUZZ_SHORT_SEND", "expected to send %d bytes to Kea input socket but only sent %2",
+ "FUZZ_SOCKET_CREATE_FAIL", "failed to crease socket for use by fuzzing thread: %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/dhcpsrv/fuzz_messages.h b/src/lib/dhcpsrv/fuzz_messages.h
new file mode 100644
index 0000000..c9f9e12
--- /dev/null
+++ b/src/lib/dhcpsrv/fuzz_messages.h
@@ -0,0 +1,23 @@
+// File created from ../../../src/lib/dhcpsrv/fuzz_messages.mes
+
+#ifndef FUZZ_MESSAGES_H
+#define FUZZ_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID FUZZ_DATA_READ;
+extern const isc::log::MessageID FUZZ_INIT_COMPLETE;
+extern const isc::log::MessageID FUZZ_INIT_FAIL;
+extern const isc::log::MessageID FUZZ_READ_FAIL;
+extern const isc::log::MessageID FUZZ_SEND;
+extern const isc::log::MessageID FUZZ_SEND_ERROR;
+extern const isc::log::MessageID FUZZ_SHORT_SEND;
+extern const isc::log::MessageID FUZZ_SOCKET_CREATE_FAIL;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // FUZZ_MESSAGES_H
diff --git a/src/lib/dhcpsrv/fuzz_messages.mes b/src/lib/dhcpsrv/fuzz_messages.mes
new file mode 100644
index 0000000..f4f4383
--- /dev/null
+++ b/src/lib/dhcpsrv/fuzz_messages.mes
@@ -0,0 +1,49 @@
+# Copyright (C) 2015-2019 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/.
+
+$NAMESPACE isc::dhcp
+
+% FUZZ_DATA_READ read %1 byte(s) from AFL via stdin
+A debug message output to indicate how much data has been received from
+the fuzzer via stdin
+
+% FUZZ_INIT_COMPLETE fuzz initialization complete: interface %1, address %2, port %3, max loops %4
+An informational message output when the fuzzing initialization function has
+completed successfully. The parameters listed are those which must be/can be
+set via environment variables.
+
+% FUZZ_INIT_FAIL fuzz initialization failure, reason: %1
+An error message reported if the fuzzing initialization failed. The reason
+for the failure is given in the message.
+
+% FUZZ_READ_FAIL error reading input from fuzzer: %1
+This error is reported if the read of data from the fuzzer (which is
+received over stdin) fails, or if a read returns zero bytes. If this
+occurs, the thread will sleep for a short period before retrying the read.
+The message includes the reason for the failure.
+
+% FUZZ_SEND sent %1 byte(s) to the socket connected to the Kea interface
+A debug message stating that the sendto() call in the main fuzzing function
+has successfully completed and reporting the number of bytes sent. This
+call sends data received from AFL to the port on which Kea is listening.
+
+% FUZZ_SEND_ERROR failed to send data to Kea input socket: %1
+This error will be reported if the sendto() call in the fuzzing thread (which
+sends data received from AFL to the socket on which Kea is listening) fails.
+The reason for the failure is given in the message. The fuzzing code will
+attempt to continue from this, but it may cause the fuzzing process to fail.
+
+% FUZZ_SHORT_SEND expected to send %d bytes to Kea input socket but only sent %2
+A warning message that is output if the sendto() call (used to send data
+from the fuzzing thread to the main Kea processing) did not send as much
+data as that read from AFL. This may indicate a problem in the underlying
+communications between the fuzzing thread and the main Kea processing.
+
+% FUZZ_SOCKET_CREATE_FAIL failed to crease socket for use by fuzzing thread: %1
+An error message output when the fuzzing code has failed to create a socket
+through which is will copy data received on stdin from the AFL fuzzer to
+the port on which Kea is listening. The program will most likely hang if
+this occurs.
diff --git a/src/lib/dhcpsrv/host.cc b/src/lib/dhcpsrv/host.cc
new file mode 100644
index 0000000..30ac476
--- /dev/null
+++ b/src/lib/dhcpsrv/host.cc
@@ -0,0 +1,724 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <cryptolink/crypto_rng.h>
+#include <dhcp/pkt4.h>
+#include <dhcpsrv/host.h>
+#include <exceptions/exceptions.h>
+
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+AuthKey::AuthKey(const std::vector<uint8_t>& key) {
+ setAuthKey(key);
+}
+
+AuthKey::AuthKey(const std::string& key) {
+ setAuthKey(key);
+}
+
+AuthKey::AuthKey() {
+ authKey_ = AuthKey::getRandomKeyString();
+}
+
+std::vector<uint8_t>
+AuthKey::getRandomKeyString() {
+ return (isc::cryptolink::random(AUTH_KEY_LEN));
+}
+
+std::string
+AuthKey::toText() const {
+ if (authKey_.empty()) {
+ return ("");
+ }
+ return (util::encode::encodeHex(authKey_));
+}
+
+void
+AuthKey::setAuthKey(const std::vector<uint8_t>& key) {
+ authKey_ = key;
+ if (authKey_.size() > AUTH_KEY_LEN) {
+ authKey_.resize(AUTH_KEY_LEN);
+ }
+}
+
+void
+AuthKey::setAuthKey(const std::string& key) {
+ if (key.empty()) {
+ authKey_.clear();
+ return;
+ }
+ try {
+ std::vector<uint8_t> bin;
+ util::encode::decodeHex(key, bin);
+ setAuthKey(bin);
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "bad auth key: " << ex.what());
+ }
+}
+
+bool
+AuthKey::operator==(const AuthKey& other) const {
+ return (authKey_ == other.authKey_);
+}
+
+bool
+AuthKey::operator!=(const AuthKey& other) const {
+ return (authKey_ != other.authKey_);
+}
+
+IPv6Resrv::IPv6Resrv(const Type& type,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len)
+ : type_(type), prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
+ // Validate and set the actual values.
+ set(type, prefix, prefix_len);
+}
+
+void
+IPv6Resrv::set(const Type& type, const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) {
+ if (!prefix.isV6() || prefix.isV6Multicast()) {
+ isc_throw(isc::BadValue, "invalid prefix '" << prefix
+ << "' for new IPv6 reservation");
+
+ } else if (prefix_len > 128) {
+ isc_throw(isc::BadValue, "invalid prefix length '"
+ << static_cast<int>(prefix_len)
+ << "' for new IPv6 reservation");
+
+ } else if ((type == TYPE_NA) && (prefix_len != 128)) {
+ isc_throw(isc::BadValue, "invalid prefix length '"
+ << static_cast<int>(prefix_len)
+ << "' for reserved IPv6 address, expected 128");
+ }
+
+ type_ = type;
+ prefix_ = prefix;
+ prefix_len_ = prefix_len;
+}
+
+std::string
+IPv6Resrv::toText() const {
+ std::ostringstream s;
+ s << prefix_;
+ // For PD, append prefix length.
+ if (getType() == TYPE_PD) {
+ s << "/" << static_cast<int>(prefix_len_);
+ }
+ return (s.str());
+}
+
+bool
+IPv6Resrv::operator==(const IPv6Resrv& other) const {
+ return (type_ == other.type_ &&
+ prefix_ == other.prefix_ &&
+ prefix_len_ == other.prefix_len_);
+}
+
+bool
+IPv6Resrv::operator!=(const IPv6Resrv& other) const {
+ return (!operator==(other));
+}
+
+Host::Host(const uint8_t* identifier, const size_t identifier_len,
+ const IdentifierType& identifier_type,
+ const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+ const asiolink::IOAddress& ipv4_reservation,
+ const std::string& hostname,
+ const std::string& dhcp4_client_classes,
+ const std::string& dhcp6_client_classes,
+ const asiolink::IOAddress& next_server,
+ const std::string& server_host_name,
+ const std::string& boot_file_name,
+ const AuthKey& auth_key)
+
+ : identifier_type_(identifier_type),
+ identifier_value_(), ipv4_subnet_id_(ipv4_subnet_id),
+ ipv6_subnet_id_(ipv6_subnet_id),
+ ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
+ dhcp6_client_classes_(dhcp6_client_classes),
+ next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ server_host_name_(server_host_name), boot_file_name_(boot_file_name),
+ host_id_(0), cfg_option4_(new CfgOption()),
+ cfg_option6_(new CfgOption()), negative_(false),
+ key_(auth_key) {
+
+ // Initialize host identifier.
+ setIdentifier(identifier, identifier_len, identifier_type);
+
+ if (!ipv4_reservation.isV4Zero()) {
+ // Validate and set IPv4 address reservation.
+ setIPv4Reservation(ipv4_reservation);
+ }
+
+ if (!next_server.isV4Zero()) {
+ // Validate and set next server address.
+ setNextServer(next_server);
+ }
+}
+
+Host::Host(const std::string& identifier, const std::string& identifier_name,
+ const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+ const asiolink::IOAddress& ipv4_reservation,
+ const std::string& hostname,
+ const std::string& dhcp4_client_classes,
+ const std::string& dhcp6_client_classes,
+ const asiolink::IOAddress& next_server,
+ const std::string& server_host_name,
+ const std::string& boot_file_name,
+ const AuthKey& auth_key)
+ : identifier_type_(IDENT_HWADDR),
+ identifier_value_(), ipv4_subnet_id_(ipv4_subnet_id),
+ ipv6_subnet_id_(ipv6_subnet_id),
+ ipv4_reservation_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
+ dhcp6_client_classes_(dhcp6_client_classes),
+ next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
+ server_host_name_(server_host_name), boot_file_name_(boot_file_name),
+ host_id_(0), cfg_option4_(new CfgOption()),
+ cfg_option6_(new CfgOption()), negative_(false),
+ key_(auth_key) {
+
+ // Initialize host identifier.
+ setIdentifier(identifier, identifier_name);
+
+ if (!ipv4_reservation.isV4Zero()) {
+ // Validate and set IPv4 address reservation.
+ setIPv4Reservation(ipv4_reservation);
+ }
+
+ if (!next_server.isV4Zero()) {
+ // Validate and set next server address.
+ setNextServer(next_server);
+ }
+}
+
+size_t
+Host::getIdentifierMaxLength(const IdentifierType& type) {
+ switch (type) {
+ case IDENT_HWADDR:
+ return (HWAddr::MAX_HWADDR_LEN);
+ case IDENT_DUID:
+ return (DUID::MAX_DUID_LEN);
+ case IDENT_CLIENT_ID:
+ return (ClientId::MAX_CLIENT_ID_LEN);
+ default:
+ // In fact it is backend dependent but for compatibility we take
+ // the lowest value.
+ return (128);
+ }
+}
+
+const std::vector<uint8_t>&
+Host::getIdentifier() const {
+ return (identifier_value_);
+}
+
+Host::IdentifierType
+Host::getIdentifierType() const {
+ return (identifier_type_);
+}
+
+Host::IdentifierType
+Host::getIdentifierType(const std::string& identifier_name) {
+ if (identifier_name == "hw-address") {
+ return (IDENT_HWADDR);
+
+ } else if (identifier_name == "duid") {
+ return (IDENT_DUID);
+
+ } else if (identifier_name == "circuit-id") {
+ return (IDENT_CIRCUIT_ID);
+
+ } else if (identifier_name == "client-id") {
+ return (IDENT_CLIENT_ID);
+ } else if (identifier_name == "flex-id") {
+ return (IDENT_FLEX);
+ } else {
+ isc_throw(isc::BadValue, "invalid client identifier type '"
+ << identifier_name << "'");
+ }
+}
+
+HWAddrPtr
+Host::getHWAddress() const {
+ return ((identifier_type_ == IDENT_HWADDR) ?
+ HWAddrPtr(new HWAddr(identifier_value_, HTYPE_ETHER)) : HWAddrPtr());
+}
+
+DuidPtr
+Host::getDuid() const {
+ return ((identifier_type_ == IDENT_DUID) ?
+ DuidPtr(new DUID(identifier_value_)) : DuidPtr());
+}
+
+
+std::string
+Host::getIdentifierAsText() const {
+ return (getIdentifierAsText(identifier_type_, &identifier_value_[0],
+ identifier_value_.size()));
+}
+
+std::string
+Host::getIdentifierAsText(const IdentifierType& type, const uint8_t* value,
+ const size_t length) {
+ // Convert identifier into <type>=<value> form.
+ std::ostringstream s;
+ switch (type) {
+ case IDENT_HWADDR:
+ s << "hwaddr";
+ break;
+ case IDENT_DUID:
+ s << "duid";
+ break;
+ case IDENT_CIRCUIT_ID:
+ s << "circuit-id";
+ break;
+ case IDENT_CLIENT_ID:
+ s << "client-id";
+ break;
+ case IDENT_FLEX:
+ s << "flex-id";
+ break;
+ default:
+ // This should never happen actually, unless we add new identifier
+ // and forget to add a case for it above.
+ s << "(invalid-type)";
+ }
+ std::vector<uint8_t> vec(value, value + length);
+ s << "=" << (length > 0 ? util::encode::encodeHex(vec) : "(null)");
+ return (s.str());
+}
+
+std::string
+Host::getIdentifierName(const IdentifierType& type) {
+ switch (type) {
+ case Host::IDENT_HWADDR:
+ return ("hw-address");
+
+ case Host::IDENT_DUID:
+ return ("duid");
+
+ case Host::IDENT_CIRCUIT_ID:
+ return ("circuit-id");
+
+ case Host::IDENT_CLIENT_ID:
+ return ("client-id");
+
+ case Host::IDENT_FLEX:
+ return ("flex-id");
+
+ default:
+ ;
+ }
+ return ("(unknown)");
+}
+
+
+void
+Host::setIdentifier(const uint8_t* identifier, const size_t len,
+ const IdentifierType& type) {
+ if (len < 1) {
+ isc_throw(BadValue, "invalid client identifier length 0");
+ } else if (len > getIdentifierMaxLength(type)) {
+ isc_throw(BadValue, "too long client identifier type "
+ << getIdentifierName(type)
+ << " length " << len);
+ }
+
+ identifier_type_ = type;
+ identifier_value_.assign(identifier, identifier + len);
+}
+
+void
+Host::setIdentifier(const std::string& identifier, const std::string& name) {
+ // Empty identifier is not allowed.
+ if (identifier.empty()) {
+ isc_throw(isc::BadValue, "empty host identifier used");
+ }
+
+ // Set identifier type.
+ identifier_type_ = getIdentifierType(name);
+
+ // Identifier value can either be specified as string of hexadecimal
+ // digits or a string in quotes. The latter is copied to a vector excluding
+ // quote characters.
+
+ // Try to convert the values in quotes into a vector of ASCII codes.
+ // If the identifier lacks opening and closing quote, this will return
+ // an empty value, in which case we'll try to decode it as a string of
+ // hexadecimal digits.
+ bool too_long = false;
+ try {
+ std::vector<uint8_t> binary = util::str::quotedStringToBinary(identifier);
+ if (binary.empty()) {
+ util::str::decodeFormattedHexString(identifier, binary);
+ }
+
+ size_t len = binary.size();
+ if (len > getIdentifierMaxLength(identifier_type_)) {
+ // Message does not matter as it will be replaced below...
+ too_long = true;
+ isc_throw(BadValue, "too long client identifier type " << name
+ << " length " << len);
+ }
+
+ // Successfully decoded the identifier, so let's use it.
+ identifier_value_.swap(binary);
+
+ } catch (...) {
+ // The string doesn't match any known pattern, so we have to
+ // report an error at this point.
+ if (too_long) {
+ throw;
+ }
+ isc_throw(isc::BadValue, "invalid host identifier value '"
+ << identifier << "'");
+ }
+}
+
+void
+Host::setIdentifierType(const IdentifierType& type) {
+ identifier_type_ = type;
+}
+
+void
+Host::setIPv4Reservation(const asiolink::IOAddress& address) {
+ if (!address.isV4()) {
+ isc_throw(isc::BadValue, "address '" << address << "' is not a valid"
+ " IPv4 address");
+ } else if (address.isV4Zero() || address.isV4Bcast()) {
+ isc_throw(isc::BadValue, "must not make reservation for the '"
+ << address << "' address");
+ }
+ ipv4_reservation_ = address;
+}
+
+void
+Host::removeIPv4Reservation() {
+ ipv4_reservation_ = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+}
+
+void
+Host::addReservation(const IPv6Resrv& reservation) {
+ // Check if it is not duplicating existing reservation.
+ if (hasReservation(reservation)) {
+ isc_throw(isc::InvalidOperation, "failed on attempt to add a duplicated"
+ " host reservation for " << reservation.toText());
+ }
+ // Add it.
+ ipv6_reservations_.insert(IPv6ResrvTuple(reservation.getType(),
+ reservation));
+}
+
+IPv6ResrvRange
+Host::getIPv6Reservations(const IPv6Resrv::Type& type) const {
+ return (ipv6_reservations_.equal_range(type));
+}
+
+IPv6ResrvRange
+Host::getIPv6Reservations() const {
+ return (IPv6ResrvRange(ipv6_reservations_.begin(),
+ ipv6_reservations_.end()));
+}
+
+bool
+Host::hasIPv6Reservation() const {
+ return (!ipv6_reservations_.empty());
+}
+
+bool
+Host::hasReservation(const IPv6Resrv& reservation) const {
+ IPv6ResrvRange reservations = getIPv6Reservations(reservation.getType());
+ if (std::distance(reservations.first, reservations.second) > 0) {
+ for (IPv6ResrvIterator it = reservations.first;
+ it != reservations.second; ++it) {
+ if (it->second == reservation) {
+ return (true);
+ }
+ }
+ }
+
+ // No matching reservations found.
+ return (false);
+}
+
+void
+Host::addClientClass4(const std::string& class_name) {
+ addClientClassInternal(dhcp4_client_classes_, class_name);
+}
+
+
+void
+Host::addClientClass6(const std::string& class_name) {
+ addClientClassInternal(dhcp6_client_classes_, class_name);
+}
+
+void
+Host::addClientClassInternal(ClientClasses& classes,
+ const std::string& class_name) {
+ std::string trimmed = util::str::trim(class_name);
+ if (!trimmed.empty()) {
+ classes.insert(ClientClass(trimmed));
+ }
+}
+
+void
+Host::setNextServer(const asiolink::IOAddress& next_server) {
+ if (!next_server.isV4()) {
+ isc_throw(isc::BadValue, "next server address '" << next_server
+ << "' is not a valid IPv4 address");
+ } else if (next_server.isV4Bcast()) {
+ isc_throw(isc::BadValue, "invalid next server address '"
+ << next_server << "'");
+ }
+
+ next_server_ = next_server;
+}
+
+void
+Host::setServerHostname(const std::string& server_host_name) {
+ if (server_host_name.size() > Pkt4::MAX_SNAME_LEN - 1) {
+ isc_throw(isc::BadValue, "server hostname length must not exceed "
+ << (Pkt4::MAX_SNAME_LEN - 1));
+ }
+ server_host_name_ = server_host_name;
+}
+
+void
+Host::setBootFileName(const std::string& boot_file_name) {
+ if (boot_file_name.size() > Pkt4::MAX_FILE_LEN - 1) {
+ isc_throw(isc::BadValue, "boot file length must not exceed "
+ << (Pkt4::MAX_FILE_LEN - 1));
+ }
+ boot_file_name_ = boot_file_name;
+}
+
+ElementPtr
+Host::toElement4() const {
+
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ // Set the user context
+ contextToElement(map);
+ // Set the identifier
+ Host::IdentifierType id_type = getIdentifierType();
+ if (id_type == Host::IDENT_HWADDR) {
+ HWAddrPtr hwaddr = getHWAddress();
+ map->set("hw-address", Element::create(hwaddr->toText(false)));
+ } else if (id_type == Host::IDENT_DUID) {
+ DuidPtr duid = getDuid();
+ map->set("duid", Element::create(duid->toText()));
+ } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string circuit_id = util::encode::encodeHex(bin);
+ map->set("circuit-id", Element::create(circuit_id));
+ } else if (id_type == Host::IDENT_CLIENT_ID) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string client_id = util::encode::encodeHex(bin);
+ map->set("client-id", Element::create(client_id));
+ } else if (id_type == Host::IDENT_FLEX) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string flex = util::encode::encodeHex(bin);
+ map->set("flex-id", Element::create(flex));
+ } else {
+ isc_throw(ToElementError, "invalid identifier type: " << id_type);
+ }
+ // Set the reservation (if not 0.0.0.0 which may not be re-read)
+ const IOAddress& address = getIPv4Reservation();
+ if (!address.isV4Zero()) {
+ map->set("ip-address", Element::create(address.toText()));
+ }
+ // Set the hostname
+ const std::string& hostname = getHostname();
+ map->set("hostname", Element::create(hostname));
+ // Set next-server
+ const IOAddress& next_server = getNextServer();
+ map->set("next-server", Element::create(next_server.toText()));
+ // Set server-hostname
+ const std::string& server_hostname = getServerHostname();
+ map->set("server-hostname", Element::create(server_hostname));
+ // Set boot-file-name
+ const std::string& boot_file_name = getBootFileName();
+ map->set("boot-file-name", Element::create(boot_file_name));
+ // Set client-classes
+ const ClientClasses& cclasses = getClientClasses4();
+ ElementPtr classes = Element::createList();
+ for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+ cclass != cclasses.cend(); ++cclass) {
+ classes->add(Element::create(*cclass));
+ }
+ map->set("client-classes", classes);
+ // Set option-data
+ ConstCfgOptionPtr opts = getCfgOption4();
+ map->set("option-data", opts->toElement());
+
+ return (map);
+}
+
+ElementPtr
+Host::toElement6() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ // Set the user context
+ contextToElement(map);
+ // Set the identifier
+ Host::IdentifierType id_type = getIdentifierType();
+ if (id_type == Host::IDENT_HWADDR) {
+ HWAddrPtr hwaddr = getHWAddress();
+ map->set("hw-address", Element::create(hwaddr->toText(false)));
+ } else if (id_type == Host::IDENT_DUID) {
+ DuidPtr duid = getDuid();
+ map->set("duid", Element::create(duid->toText()));
+ } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+ isc_throw(ToElementError, "unexpected circuit-id DUID type");
+ } else if (id_type == Host::IDENT_CLIENT_ID) {
+ isc_throw(ToElementError, "unexpected client-id DUID type");
+ } else if (id_type == Host::IDENT_FLEX) {
+ const std::vector<uint8_t>& bin = getIdentifier();
+ std::string flex = util::encode::encodeHex(bin);
+ map->set("flex-id", Element::create(flex));
+ } else {
+ isc_throw(ToElementError, "invalid DUID type: " << id_type);
+ }
+ // Set reservations (ip-addresses)
+ IPv6ResrvRange na_resv = getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ElementPtr resvs = Element::createList();
+ for (IPv6ResrvIterator resv = na_resv.first;
+ resv != na_resv.second; ++resv) {
+ resvs->add(Element::create(resv->second.toText()));
+ }
+ map->set("ip-addresses", resvs);
+ // Set reservations (prefixes)
+ IPv6ResrvRange pd_resv = getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ resvs = Element::createList();
+ for (IPv6ResrvIterator resv = pd_resv.first;
+ resv != pd_resv.second; ++resv) {
+ resvs->add(Element::create(resv->second.toText()));
+ }
+ map->set("prefixes", resvs);
+ // Set the hostname
+ const std::string& hostname = getHostname();
+ map->set("hostname", Element::create(hostname));
+ // Set client-classes
+ const ClientClasses& cclasses = getClientClasses6();
+ ElementPtr classes = Element::createList();
+ for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+ cclass != cclasses.cend(); ++cclass) {
+ classes->add(Element::create(*cclass));
+ }
+ map->set("client-classes", classes);
+
+ // Set option-data
+ ConstCfgOptionPtr opts = getCfgOption6();
+ map->set("option-data", opts->toElement());
+
+ // Set auth key
+ //@todo: uncomment once storing in configuration file is enabled
+ //map->set("auth-key", Element::create(getKey().toText()));
+
+ return (map);
+}
+
+void
+Host::encapsulateOptions() const {
+ if (!cfg_option4_->isEncapsulated()) {
+ cfg_option4_->encapsulate();
+ }
+ if (!cfg_option6_->isEncapsulated()) {
+ cfg_option6_->encapsulate();
+ }
+}
+
+std::string
+Host::toText() const {
+ std::ostringstream s;
+
+ // Add HW address or DUID.
+ s << getIdentifierAsText();
+
+ // Add IPv4 subnet id if exists.
+ if (ipv4_subnet_id_ != SUBNET_ID_UNUSED) {
+ s << " ipv4_subnet_id=" << ipv4_subnet_id_;
+ }
+
+ // Add IPv6 subnet id if exists.
+ if (ipv6_subnet_id_ != SUBNET_ID_UNUSED) {
+ s << " ipv6_subnet_id=" << ipv6_subnet_id_;
+ }
+
+ // Add hostname.
+ s << " hostname=" << (hostname_.empty() ? "(empty)" : hostname_);
+
+ // Add IPv4 reservation.
+ s << " ipv4_reservation=" << (ipv4_reservation_.isV4Zero() ? "(no)" :
+ ipv4_reservation_.toText());
+
+ // Add next server.
+ s << " siaddr=" << (next_server_.isV4Zero() ? "(no)" :
+ next_server_.toText());
+
+ // Add server host name.
+ s << " sname=" << (server_host_name_.empty() ? "(empty)" : server_host_name_);
+
+ // Add boot file name.
+ s << " file=" << (boot_file_name_.empty() ? "(empty)" : boot_file_name_);
+
+ s << " key=" << (key_.toText().empty() ? "(empty)" : key_.toText());
+
+ if (ipv6_reservations_.empty()) {
+ s << " ipv6_reservations=(none)";
+
+ } else {
+ // Add all IPv6 reservations.
+ for (IPv6ResrvIterator resrv = ipv6_reservations_.begin();
+ resrv != ipv6_reservations_.end(); ++resrv) {
+ s << " ipv6_reservation"
+ << std::distance(ipv6_reservations_.begin(), resrv)
+ << "=" << resrv->second.toText();
+ }
+ }
+
+ // Add DHCPv4 client classes.
+ for (ClientClasses::const_iterator cclass = dhcp4_client_classes_.cbegin();
+ cclass != dhcp4_client_classes_.cend(); ++cclass) {
+ s << " dhcp4_class"
+ << std::distance(dhcp4_client_classes_.cbegin(), cclass)
+ << "=" << *cclass;
+ }
+
+ // Add DHCPv6 client classes.
+ for (ClientClasses::const_iterator cclass = dhcp6_client_classes_.cbegin();
+ cclass != dhcp6_client_classes_.cend(); ++cclass) {
+ s << " dhcp6_class"
+ << std::distance(dhcp6_client_classes_.cbegin(), cclass)
+ << "=" << *cclass;
+ }
+
+ // Add negative cached.
+ if (negative_) {
+ s << " negative cached";
+ }
+
+ return (s.str());
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/host.h b/src/lib/dhcpsrv/host.h
new file mode 100644
index 0000000..fd9eb75
--- /dev/null
+++ b/src/lib/dhcpsrv/host.h
@@ -0,0 +1,821 @@
+// Copyright (C) 2014-2023 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 HOST_H
+#define HOST_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <cc/user_context.h>
+#include <dhcp/classify.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/subnet_id.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/algorithm/string.hpp>
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Maximum size of an IPv6 address represented as a text string.
+///
+/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
+/// colon separators.
+const size_t ADDRESS6_TEXT_MAX_LEN = 39;
+
+/// @brief Maximum length of classes stored in a dhcp4/6_client_classes
+/// columns.
+const size_t CLIENT_CLASSES_MAX_LEN = 255;
+
+/// @brief Maximum length of the hostname stored in DNS.
+///
+/// This length is restricted by the length of the domain-name carried
+/// in the Client FQDN %Option (see RFC4702 and RFC4704).
+const size_t HOSTNAME_MAX_LEN = 255;
+
+/// @brief Maximum length of option value.
+const size_t OPTION_VALUE_MAX_LEN = 4096;
+
+/// @brief Maximum length of option value specified in textual format.
+const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
+
+/// @brief Maximum length of option space name.
+const size_t OPTION_SPACE_MAX_LEN = 128;
+
+/// @brief Maximum length of user context.
+const size_t USER_CONTEXT_MAX_LEN = 8192;
+
+/// @brief Maximum length of the server hostname.
+const size_t SERVER_HOSTNAME_MAX_LEN = 64;
+
+/// @brief Maximum length of the boot file name.
+const size_t BOOT_FILE_NAME_MAX_LEN = 128;
+
+/// @brief Maximum length of authentication keys - 128 bits.
+const uint8_t AUTH_KEY_LEN = 16;
+
+/// @brief Maximum length of authentication keys (coded in hexadecimal).
+const size_t TEXT_AUTH_KEY_LEN = AUTH_KEY_LEN * 2;
+
+/// @brief HostID (used only when storing in MySQL or PostgreSQL backends)
+typedef uint64_t HostID;
+
+/// @brief Authentication keys.
+///
+/// This class represents authentication keys to be used for
+/// calculating HMAC in the authentication field of the reconfigure message.
+class AuthKey {
+public:
+ /// @brief Constructor.
+ ///
+ /// Constructor for assigning auth keys in host reservation.
+ /// Ensures the key length is not greater than 16 bytes.
+ /// @param key auth key in binary to be stored.
+ AuthKey(const std::vector<uint8_t>& key);
+
+ /// @brief Constructor.
+ ///
+ /// Constructor for assigning auth keys in host reservation.
+ /// Ensures the key length is not greater than AUTH_KEY_LEN (16) bytes
+ /// so TEXT_AUTH_KEY_LEN (32) hexadecimal digits.
+ /// See @c setKey for constraints on its input format.
+ ///
+ /// @param key auth key in hexadecimal to be stored.
+ AuthKey(const std::string& key);
+
+ /// @brief Constructor.
+ ///
+ /// Constructor for generating auth keys, with no argument.
+ /// shall use the internal function for generationg random keys.
+ AuthKey();
+
+ // @brief Get random string.
+ ///
+ /// Random string is generated by default will be used for
+ /// the keys to be used for signing Reconfigure Message.
+ /// @return Random binary string of 16 bytes.
+ static std::vector<uint8_t> getRandomKeyString();
+
+ /// @brief Set auth key value from binary.
+ ///
+ /// Set the key value.
+ // If the size is greater than 16 bytes, we resize to 16 bytes.
+ /// Doesn't throw an exception.
+ /// @param key auth key in binary to be stored
+ void setAuthKey(const std::vector<uint8_t>& key);
+
+ /// @brief Set auth key value from hexadecimal.
+ ///
+ /// Set the key value.
+ /// If the size is greater than 16 bytes, we resize to 16 bytes.
+ /// @param key auth key in hexadecimal to be stored.
+ /// @throw BadValue if the string is not a valid hexadecimal encoding,
+ /// for instance has a not hexadecimal or odd number of digits.
+ void setAuthKey(const std::string& key);
+
+ /// @brief Return auth key.
+ ///
+ /// @return auth key in binary.
+ const std::vector<uint8_t>& getAuthKey() const {
+ return authKey_;
+ }
+
+ /// @brief Return text format for keys.
+ ///
+ /// @return auth key as a string of hexadecimal digits.
+ std::string toText() const;
+
+ ///
+ /// @brief Equality operator.
+ ///
+ /// equality operator to compare two AuthKey objects.
+ /// @param other Authkey to be compared against.
+ bool operator==(const AuthKey& other) const;
+
+ /// @brief Inequality operator.
+ ///
+ /// inequality operator to compare two AuthKey objects.
+ /// @param other Authkey to be compared against.
+ bool operator!=(const AuthKey& other) const;
+
+private:
+ std::vector<uint8_t> authKey_;
+};
+
+/// @brief IPv6 reservation for a host.
+///
+/// This class represents a reservation for a host of a single IPv6
+/// address or prefix (in @c Host object).
+///
+/// The class holds the address and prefix length, a value of 128
+/// for the latter implying that the reservation is for a single
+/// IPv6 address.
+class IPv6Resrv {
+public:
+
+ /// @brief Type of the reservation.
+ ///
+ /// Currently supported types are NA and PD.
+ enum Type {
+ TYPE_NA,
+ TYPE_PD
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Creates a reservation from the IPv6 address and prefix length
+ /// value. If the prefix length is not specified, the default value
+ /// of 128 is used. This value indicates that the reservation is made
+ /// for an IPv6 address.
+ ///
+ /// @param type Reservation type: NA or PD.
+ /// @param prefix Address or prefix to be reserved.
+ /// @param prefix_len Prefix length.
+ ///
+ /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
+ /// multicast address or the prefix length is greater than 128.
+ IPv6Resrv(const Type& type,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len = 128);
+
+ /// @brief Returns prefix for the reservation.
+ const asiolink::IOAddress& getPrefix() const {
+ return (prefix_);
+ }
+
+ /// @brief Returns prefix length.
+ uint8_t getPrefixLen() const {
+ return (prefix_len_);
+ }
+
+ /// @brief Returns reservation type.
+ ///
+ /// The type of reservation is determined using a prefix length.
+ ///
+ /// @return NA for prefix length equal to 128, PD otherwise.
+ Type getType() const {
+ return (type_);
+ }
+
+ /// @brief Sets a new prefix and prefix length.
+ ///
+ /// @param type Reservation type: NA or PD.
+ /// @param prefix New prefix.
+ /// @param prefix_len New prefix length.
+ ///
+ /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
+ /// multicast address or the prefix length is greater than 128.
+ void set(const Type& type, const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len);
+
+ /// @brief Returns information about the reservation in the textual format.
+ std::string toText() const;
+
+ /// @brief Equality operator.
+ ///
+ /// @param other Reservation to compare to.
+ bool operator==(const IPv6Resrv& other) const;
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other Reservation to compare to.
+ bool operator!=(const IPv6Resrv& other) const;
+
+private:
+
+ Type type_; ///< Reservation type.
+ asiolink::IOAddress prefix_; ///< Prefix
+ uint8_t prefix_len_; ///< Prefix length.
+};
+
+/// @brief Collection of IPv6 reservations for the host.
+typedef std::multimap<IPv6Resrv::Type, IPv6Resrv> IPv6ResrvCollection;
+typedef IPv6ResrvCollection::const_iterator IPv6ResrvIterator;
+typedef std::pair<IPv6Resrv::Type, IPv6Resrv> IPv6ResrvTuple;
+typedef std::pair<IPv6ResrvIterator, IPv6ResrvIterator> IPv6ResrvRange;
+
+/// @brief Represents a device with IPv4 and/or IPv6 reservations.
+///
+/// This class represents a network device which can be identified
+/// by a unique property, such as MAC address on the interface or
+/// client identifier (DUID), and for which some resources are statically
+/// assigned:
+/// - IPv4 address which the device obtains when it contacts a DHCPv4 server
+/// - IPv6 address(es) which the device obtains when it contacts a DHCPv6
+/// server
+/// - IPv6 prefix(es) obtained when the device contacts the DHCPv6 server
+/// and requests allocation of prefixes using prefix delegation mechanism
+/// - hostname which is used for dynamic DNS updates for both DHCPv4 and
+/// DHCPv6 exchanges.
+/// - client classes which the client is associated with
+/// - DHCP options specifically configured for the device
+///
+/// Note, that "host" in this context has a different meaning from
+/// host construed as device attached to a network with (possibly) multiple
+/// interfaces. For the MAC address based reservations, each interface on a
+/// network device maps to a single @c Host object as each @c Host object
+/// contains at most one MAC address. So, it is possible that a single
+/// device is associated with multiple distinct @c Host objects if the
+/// device has multiple interfaces. Under normal circumstances, a non-mobile
+/// dual stack device using one interface should be represented by a single
+/// @c Host object.
+///
+/// A DHCPv6 DUID is common for all interfaces on a device. Therefore, for
+/// DUID based reservations a @c Host object may represent a network device with
+/// multiple interfaces. However, since @c Host objects are grouped by
+/// subnets to which device's interfaces are connected a single instance of
+/// @c Host object usually defines reservations for a single interface.
+///
+/// The @c Host object combines reservations for both IPv4 and IPv6 resources
+/// to allow for correlation of the information about the dual stack devices
+/// using DHCPv4 and DHCPv6 respectively. For example: both the DHCPv4 and
+/// DHCPv6 servers may use the same database for storing host reservations, so
+/// the information about the DHCPv4 reservations are available for the
+/// DHCPv6 server and vice versa. Also, this approach allows for reserving
+/// common resources such as host name for DHCPv4 and DHCPv6 clients.
+///
+/// This class also holds pointers to specific DHCP options reserved
+/// for a host. Options instances are held in @c CfgOption objects.
+/// There are two @c CfgOption objects in this class, one holding
+/// DHCPv4 options, another one holding DHCPv6 options.
+///
+/// @todo This class offers basic functionality for storing host information.
+/// It will need to be extended to allow for the following operations:
+/// - remove and replace IPv6 reservations
+/// - remove and replace client classes
+/// - disable IPv4 reservation without a need to set it to the 0.0.0.0 address
+/// Note that the last three operations are mainly required for managing
+/// host reservations which will be implemented later.
+class Host : public data::UserContext {
+public:
+
+ /// @brief Type of the host identifier.
+ ///
+ /// Currently supported identifiers are:
+ /// - hardware address (DHCPv4 and DHCPv6) (identifier name: "hw-address"),
+ /// - DUID (DHCPv4 and DHCPv6) (identifier name: "duid"),
+ /// - circuit identifier (DHCPv4) (identifier name: "circuit-id"),
+ /// - client identifier (DHCPv4) (identifier name: "client-id")
+ enum IdentifierType {
+ IDENT_HWADDR,
+ IDENT_DUID,
+ IDENT_CIRCUIT_ID,
+ IDENT_CLIENT_ID,
+ IDENT_FLEX, ///< Flexible host identifier.
+ };
+
+ /// @brief Constant pointing to the last identifier of the
+ /// @ref IdentifierType enumeration.
+ static const IdentifierType LAST_IDENTIFIER_TYPE = IDENT_FLEX;
+
+ /// @brief Constructor.
+ ///
+ /// Creates a @c Host object using an identifier in a binary format. This
+ /// is most useful in cases where the identifier is obtained from the
+ /// database. The constructor will create an instance of the @c HWAddr
+ /// or @c DUID object depending on the identifier type.
+ ///
+ /// @param identifier Pointer to the binary value holding an identifier.
+ /// @param identifier_len Length of the identifier.
+ /// @param identifier_type Type of the identifier (hardware address or
+ /// DUID).
+ /// @param ipv4_subnet_id Identifier of the IPv4 subnet to which the host
+ /// is connected.
+ /// @param ipv6_subnet_id Identifier of the IPv6 subnet to which the host
+ /// is connected.
+ /// @param ipv4_reservation An IPv4 address reserved for the client. If
+ /// this address is set to 0, there is no reservation.
+ /// @param hostname Hostname to be allocated to both DHCPv4 and DHCPv6
+ /// clients. This is empty string if hostname is not allocated.
+ /// @param dhcp4_client_classes A string holding DHCPv4 client class names
+ /// separated by commas. The names get trimmed by this constructor.
+ /// @param dhcp6_client_classes A string holding DHCPv6 client class names
+ /// separated by commas. The names get trimmed by this constructor.
+ /// @param next_server IPv4 address of next server (siaddr).
+ /// @param server_host_name Server host name (a.k.a. sname).
+ /// @param boot_file_name Boot file name (a.k.a. file).
+ /// @param auth_key Authentication key.
+ ///
+ /// @throw BadValue if the provided values are invalid. In particular,
+ /// if the identifier is invalid.
+ Host(const uint8_t* identifier, const size_t identifier_len,
+ const IdentifierType& identifier_type,
+ const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+ const asiolink::IOAddress& ipv4_reservation,
+ const std::string& hostname = "",
+ const std::string& dhcp4_client_classes = "",
+ const std::string& dhcp6_client_classes = "",
+ const asiolink::IOAddress& next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS(),
+ const std::string& server_host_name = "",
+ const std::string& boot_file_name = "",
+ const AuthKey& auth_key = AuthKey(""));
+
+ /// @brief Constructor.
+ ///
+ /// Creates @c Host object using an identifier in a textual format. This
+ /// is useful in cases when the reservation is specified in the server
+ /// configuration file. Identifiers can be specified in the following
+ /// formats:
+ /// - "yy:yy:yy:yy:yy:yy"
+ /// - "yyyyyyyyyy",
+ /// - "0xyyyyyyyyyy",
+ /// - "'some identifier'".
+ /// where y is a hexadecimal digit.
+ ///
+ /// Note that it is possible to use textual representation, e.g. 'some identifier',
+ /// which is converted to a vector of ASCII codes representing characters in a
+ /// given string, excluding quotes. This is useful in cases when specific
+ /// identifiers, e.g. circuit-id are manually assigned user friendly values.
+ ///
+ /// @param identifier Identifier in the textual format. The expected formats
+ /// for the hardware address and other identifiers are provided above.
+ /// @param identifier_name One of the supported identifiers in the text form as
+ /// described for @ref IdentifierType.
+ /// @param ipv4_subnet_id Identifier of the IPv4 subnet to which the host
+ /// is connected.
+ /// @param ipv6_subnet_id Identifier of the IPv6 subnet to which the host
+ /// is connected.
+ /// @param ipv4_reservation An IPv4 address reserved for the client. If
+ /// this address is set to 0, there is no reservation.
+ /// @param hostname Hostname to be allocated to both DHCPv4 and DHCPv6
+ /// clients. This is empty string if hostname is not allocated.
+ /// @param dhcp4_client_classes A string holding DHCPv4 client class names
+ /// separated by commas. The names get trimmed by this constructor.
+ /// @param dhcp6_client_classes A string holding DHCPv6 client class names
+ /// separated by commas. The names get trimmed by this constructor.
+ /// @param next_server IPv4 address of next server (siaddr).
+ /// @param server_host_name Server host name (a.k.a. sname).
+ /// @param boot_file_name Boot file name (a.k.a. file).
+ /// @param auth_key Authentication key.
+ ///
+ /// @throw BadValue if the provided values are invalid. In particular,
+ /// if the identifier is invalid.
+ Host(const std::string& identifier, const std::string& identifier_name,
+ const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+ const asiolink::IOAddress& ipv4_reservation,
+ const std::string& hostname = "",
+ const std::string& dhcp4_client_classes = "",
+ const std::string& dhcp6_client_classes = "",
+ const asiolink::IOAddress& next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS(),
+ const std::string& server_host_name = "",
+ const std::string& boot_file_name = "",
+ const AuthKey& auth_key = AuthKey(""));
+
+ /// @brief Get maximum identifier length.
+ ///
+ /// This method returns the maximum identifier length.
+ ///
+ /// @param type Identifier type.
+ static size_t getIdentifierMaxLength(const IdentifierType& type);
+
+ /// @brief Replaces currently used identifier with a new identifier.
+ ///
+ /// This method sets a new identifier type and value for a host.
+ /// This method is called by the @c Host constructor.
+ ///
+ /// @param identifier Pointer to a buffer holding an identifier.
+ /// @param len Length of the identifier that the @c identifier points to.
+ /// @param type Identifier type.
+ ///
+ /// @throw BadValue if the identifier is invalid.
+ void setIdentifier(const uint8_t* identifier, const size_t len,
+ const IdentifierType& type);
+
+ /// @brief Replaces currently used identifier with a new identifier.
+ ///
+ /// This method sets a new identifier type and value for a host.
+ /// This method is called by the @c Host constructor.
+ ///
+ /// @param identifier Reference to a new identifier in the textual format.
+ /// @param name One of the supported identifiers in the text form as
+ /// described for @ref IdentifierType.
+ ///
+ /// @throw BadValue if the identifier is invalid.
+ void setIdentifier(const std::string& identifier, const std::string& name);
+
+ /// @brief Returns hardware address for which the reservations are made.
+ ///
+ /// @return Pointer to the @c HWAddr structure or null if the reservation
+ /// is not associated with a hardware address.
+ HWAddrPtr getHWAddress() const;
+
+ /// @brief Returns DUID for which the reservations are made.
+ ///
+ /// @return Pointer to the @c DUID structure or null if the reservation
+ /// is not associated with a DUID.
+ DuidPtr getDuid() const;
+
+ /// @brief Returns the identifier in a binary form.
+ ///
+ /// @return const reference to a vector<uint8_t> holding an identifier
+ /// value.
+ const std::vector<uint8_t>& getIdentifier() const;
+
+ /// @brief Returns the identifier type.
+ ///
+ IdentifierType getIdentifierType() const;
+
+ /// @brief Converts identifier name to identifier type.
+ ///
+ /// @param identifier_name Identifier name.
+ /// @return Identifier type.
+ static IdentifierType getIdentifierType(const std::string& identifier_name);
+
+ /// @brief Returns host identifier in a textual form.
+ ///
+ /// @return Identifier in the form of type=value.
+ std::string getIdentifierAsText() const;
+
+ /// @brief Returns name of the identifier of a specified type.
+ static std::string getIdentifierName(const IdentifierType& type);
+
+ /// @brief Returns host identifier in textual form.
+ ///
+ /// @param type Identifier type.
+ /// @param value Pointer to a buffer holding identifier.
+ /// @param length Length of the identifier.
+ /// @return Identifier in the form of type=value.
+ static std::string getIdentifierAsText(const IdentifierType& type,
+ const uint8_t* value,
+ const size_t length);
+
+ /// @brief Sets new IPv4 subnet identifier.
+ ///
+ /// @param ipv4_subnet_id New subnet identifier.
+ void setIPv4SubnetID(const SubnetID ipv4_subnet_id) {
+ ipv4_subnet_id_ = ipv4_subnet_id;
+ }
+
+ /// @brief Sets new IPv6 subnet identifier.
+ ///
+ /// @param ipv6_subnet_id New subnet identifier.
+ void setIPv6SubnetID(const SubnetID ipv6_subnet_id) {
+ ipv6_subnet_id_ = ipv6_subnet_id;
+ }
+
+ /// @brief Returns subnet identifier for IPv4 reservation.
+ SubnetID getIPv4SubnetID() const {
+ return (ipv4_subnet_id_);
+ }
+
+ /// @brief Returns subnet identifier for IPv6 reservations.
+ SubnetID getIPv6SubnetID() const {
+ return (ipv6_subnet_id_);
+ }
+
+ /// @brief Sets new IPv4 reservation.
+ ///
+ /// The new reservation removes a previous reservation.
+ ///
+ /// @param address Address to be reserved for the client.
+ ///
+ /// @throw isc::BadValue if the provided address is not an IPv4 address,
+ /// is a 0 address or broadcast address.
+ void setIPv4Reservation(const asiolink::IOAddress& address);
+
+ /// @brief Removes the IPv4 reservation.
+ ///
+ /// Sets the IPv4 reserved address to 0.
+ void removeIPv4Reservation();
+
+ /// @brief Returns reserved IPv4 address.
+ ///
+ /// @return IPv4 address or 0.0.0.0 if no IPv4 reservation specified.
+ const asiolink::IOAddress& getIPv4Reservation() const {
+ return (ipv4_reservation_);
+ }
+
+ /// @brief Adds new IPv6 reservation.
+ ///
+ /// @param reservation New IPv6 reservation to be appended.
+ void addReservation(const IPv6Resrv& reservation);
+
+ /// @brief Returns IPv6 reservations of a specified type.
+ ///
+ /// @param type Type of the reservations to be returned (NA or PD).
+ ///
+ /// @return A range of iterators pointing to the reservations of
+ /// the specified type.
+ IPv6ResrvRange getIPv6Reservations(const IPv6Resrv::Type& type) const;
+
+ /// @brief Returns all IPv6 reservations.
+ ///
+ /// @return A range of iterators pointing to the reservations of
+ /// the specified type.
+ IPv6ResrvRange getIPv6Reservations() const;
+
+ /// @brief Checks if there is at least one IPv6 reservation for this host.
+ ///
+ /// @return true if there is a reservation for the host, false otherwise.
+ bool hasIPv6Reservation() const;
+
+ /// @brief Checks if specified IPv6 reservation exists for the host.
+ ///
+ /// @param reservation A reservation to be checked for the host.
+ ///
+ /// @return true if the reservation already exists for the host, false
+ /// otherwise.
+ bool hasReservation(const IPv6Resrv& reservation) const;
+
+ /// @brief Sets new hostname.
+ ///
+ /// @param hostname New hostname.
+ void setHostname(const std::string& hostname) {
+ hostname_ = hostname;
+ }
+
+ /// @brief Returns reserved hostname.
+ const std::string& getHostname() const {
+ return (hostname_);
+ }
+
+ /// @brief Returns reserved hostname in lower case.
+ std::string getLowerHostname() const {
+ return (boost::algorithm::to_lower_copy(hostname_));
+ }
+
+ /// @brief Adds new client class for DHCPv4.
+ ///
+ /// @param class_name Class name.
+ void addClientClass4(const std::string& class_name);
+
+ /// @brief Returns classes which DHCPv4 client is associated with.
+ const ClientClasses& getClientClasses4() const {
+ return (dhcp4_client_classes_);
+ }
+
+ /// @brief Adds new client class for DHCPv6.
+ ///
+ /// @param class_name Class name.
+ void addClientClass6(const std::string& class_name);
+
+ /// @brief Returns classes which DHCPv6 client is associated with.
+ const ClientClasses& getClientClasses6() const {
+ return (dhcp6_client_classes_);
+ }
+
+ /// @brief Sets new value for next server field (siaddr).
+ ///
+ /// @param next_server New address of a next server.
+ ///
+ /// @throw isc::BadValue if the provided address is not an IPv4 address,
+ /// is broadcast address.
+ void setNextServer(const asiolink::IOAddress& next_server);
+
+ /// @brief Returns value of next server field (siaddr).
+ const asiolink::IOAddress& getNextServer() const {
+ return (next_server_);
+ }
+
+ /// @brief Sets new value for server hostname (sname).
+ ///
+ /// @param server_host_name New value for server hostname.
+ ///
+ /// @throw BadValue if hostname is longer than 63 bytes.
+ void setServerHostname(const std::string& server_host_name);
+
+ /// @brief Returns value of server hostname (sname).
+ const std::string& getServerHostname() const {
+ return (server_host_name_);
+ }
+
+ /// @brief Sets new value for boot file name (file).
+ ///
+ /// @param boot_file_name New value of boot file name.
+ ///
+ /// @throw BadValue if boot file name is longer than 128 bytes.
+ void setBootFileName(const std::string& boot_file_name);
+
+ /// @brief Returns value of boot file name (file).
+ const std::string& getBootFileName() const {
+ return (boot_file_name_);
+ }
+
+ /// @brief Returns pointer to the DHCPv4 option data configuration for
+ /// this host.
+ ///
+ /// Returned pointer can be used to add, remove and update options
+ /// reserved for a host.
+ CfgOptionPtr getCfgOption4() {
+ return (cfg_option4_);
+ }
+
+ /// @brief Returns const pointer to the DHCPv4 option data configuration for
+ /// this host.
+ ConstCfgOptionPtr getCfgOption4() const {
+ return (cfg_option4_);
+ }
+
+ /// @brief Returns pointer to the DHCPv6 option data configuration for
+ /// this host.
+ ///
+ /// Returned pointer can be used to add, remove and update options
+ /// reserved for a host.
+ CfgOptionPtr getCfgOption6() {
+ return (cfg_option6_);
+ }
+
+ /// @brief Returns const pointer to the DHCPv6 option data configuration for
+ /// this host.
+ ConstCfgOptionPtr getCfgOption6() const {
+ return (cfg_option6_);
+ }
+
+ /// @brief Encapsulates host-specific options with their suboptions.
+ ///
+ /// This function must be called before the server returns host-specific
+ /// DHCP options to the client.
+ void encapsulateOptions() const;
+
+ /// @brief Returns information about the host in the textual format.
+ std::string toText() const;
+
+ /// @brief Sets Host ID (primary key in MySQL and PostgreSQL backends)
+ /// @param id HostId value
+ void setHostId(HostID id) {
+ host_id_ = id;
+ }
+
+ /// @brief Returns Host ID (primary key in MySQL and PostgreSQL backends)
+ /// @return id HostId value (or 0 if not set)
+ HostID getHostId() const {
+ return (host_id_);
+ }
+
+ /// @brief Sets the negative cached flag.
+ ///
+ /// @param negative sets whether this is a negative cached host,
+ /// i.e. a fake host in the cache which indicates non-existence
+ /// and avoids to lookup in a slow backend.
+ void setNegative(bool negative) {
+ negative_ = negative;
+ }
+
+ /// @brief Return the negative cache flag value.
+ /// When true standard lookup methods return null host pointer instead.
+ bool getNegative() const {
+ return (negative_);
+ }
+
+ /// @brief Unparses (converts to Element representation) IPv4 host
+ ///
+ /// @return Element representation of the host
+ isc::data::ElementPtr toElement4() const;
+
+ /// @brief Unparses (converts to Element representation) IPv6 host
+ ///
+ /// @return Element representation of the host
+ isc::data::ElementPtr toElement6() const;
+
+ /// @brief sets key.
+ ///
+ /// Keys are used for signing the Reconfigure Message.
+ void setKey(const AuthKey& key) {
+ key_ = key;
+ }
+
+ /// @brief Returns the key.
+ ///
+ /// Keys are used for signing the Reconfigure Message.
+ AuthKey getKey() const {
+ return(key_);
+ }
+
+protected:
+
+ /// @brief Set the identifier type.
+ ///
+ /// @note for test only!
+ ///
+ /// @param type Identifier type.
+ void setIdentifierType(const IdentifierType& type);
+
+private:
+
+ /// @brief Adds new client class for DHCPv4 or DHCPv6.
+ ///
+ /// This method is called internally by the @c addClientClass4 and
+ /// @c addClientClass6 functions. It adds the class of the specified name
+ /// to the supplied class set. The class names are trimmed before they are
+ /// added. Empty class names are ignored.
+ ///
+ /// @param [out] classes Set of classes to which the new class should be
+ /// inserted.
+ /// @param class_name Class name.
+ void addClientClassInternal(ClientClasses& classes,
+ const std::string& class_name);
+
+ /// @brief Identifier type.
+ IdentifierType identifier_type_;
+ /// @brief Vector holding identifier value.
+ std::vector<uint8_t> identifier_value_;
+ /// @brief Subnet identifier for the DHCPv4 client.
+ SubnetID ipv4_subnet_id_;
+ /// @brief Subnet identifier for the DHCPv6 client.
+ SubnetID ipv6_subnet_id_;
+ /// @brief Reserved IPv4 address.
+ asiolink::IOAddress ipv4_reservation_;
+ /// @brief Collection of IPv6 reservations for the host.
+ IPv6ResrvCollection ipv6_reservations_;
+ /// @brief Name reserved for the host.
+ std::string hostname_;
+ /// @brief Collection of classes associated with a DHCPv4 client.
+ ClientClasses dhcp4_client_classes_;
+ /// @brief Collection of classes associated with a DHCPv6 client.
+ ClientClasses dhcp6_client_classes_;
+ /// @brief Next server (a.k.a. siaddr, carried in DHCPv4 message).
+ asiolink::IOAddress next_server_;
+ /// @brief Server host name (a.k.a. sname, carried in DHCPv4 message).
+ std::string server_host_name_;
+ /// @brief Boot file name (a.k.a. file, carried in DHCPv4 message)
+ std::string boot_file_name_;
+
+ /// @brief HostID (a unique identifier assigned when the host is stored in
+ /// MySQL or PostgreSQL backends)
+ uint64_t host_id_;
+
+ /// @brief Pointer to the DHCPv4 option data configuration for this host.
+ CfgOptionPtr cfg_option4_;
+ /// @brief Pointer to the DHCPv6 option data configuration for this host.
+ CfgOptionPtr cfg_option6_;
+
+ /// @brief Negative cached flag.
+ ///
+ /// This flag determines whether this object is a negative cache, i.e.
+ /// we queried other backends for specific host and there was no
+ /// entry for it.
+ bool negative_;
+
+ /// @brief key for authentication .
+ ///
+ /// key is a 16 byte value to be used in the authentication field.
+ /// Server replies will contain the below key in authentication field
+ /// as specified in the RFC 8415. While sending reconfigure message
+ /// authentication field shall contain MD5 hash computed using this key.
+ AuthKey key_;
+};
+
+/// @brief Pointer to the @c Host object.
+typedef boost::shared_ptr<Host> HostPtr;
+
+/// @brief Const pointer to the @c Host object.
+typedef boost::shared_ptr<const Host> ConstHostPtr;
+
+/// @brief Collection of the const Host objects.
+typedef std::vector<ConstHostPtr> ConstHostCollection;
+
+/// @brief Collection of the @c Host objects.
+typedef std::vector<HostPtr> HostCollection;
+
+}
+}
+
+#endif // HOST_H
diff --git a/src/lib/dhcpsrv/host_container.h b/src/lib/dhcpsrv/host_container.h
new file mode 100644
index 0000000..40bc193
--- /dev/null
+++ b/src/lib/dhcpsrv/host_container.h
@@ -0,0 +1,315 @@
+// Copyright (C) 2014-2023 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 HOST_CONTAINER_H
+#define HOST_CONTAINER_H
+
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet_id.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Multi-index container holding host reservations.
+///
+/// This container holds a collection of @c Host objects which can be retrieved
+/// using various parameters. The typical use of this container is to search for
+/// all @c Host objects which are identified by a specified identifier, i.e.
+/// HW address or DUID.
+///
+/// @todo This container will be extended to search for @c Host objects
+/// associated with a specific IPv4 address or IPv6 prefix/length.
+///
+/// @see http://www.boost.org/doc/libs/1_56_0/libs/multi_index/doc/index.html
+typedef boost::multi_index_container<
+ // This container stores pointers to Host objects.
+ HostPtr,
+ // Start specification of indexes here.
+ boost::multi_index::indexed_by<
+ // First index is used to search for the host using one of the
+ // identifiers, i.e. HW address or DUID. The elements of this
+ // index are non-unique because there may be multiple reservations
+ // for the same host belonging to a different subnets.
+ boost::multi_index::ordered_non_unique<
+ // The index comprises actual identifier (HW address or DUID) in
+ // a binary form and a type of the identifier which indicates
+ // that it is HW address or DUID.
+ boost::multi_index::composite_key<
+ // Composite key uses members of the Host class.
+ Host,
+ // Binary identifier is retrieved from the Host class using
+ // a getIdentifier method.
+ boost::multi_index::const_mem_fun<
+ Host, const std::vector<uint8_t>&,
+ &Host::getIdentifier
+ >,
+ // Identifier type is retrieved from the Host class using
+ // a getIdentifierType method.
+ boost::multi_index::const_mem_fun<
+ Host, Host::IdentifierType,
+ &Host::getIdentifierType
+ >
+ >
+ >,
+
+ // Second index is used to search for the host using reserved IPv4
+ // address.
+ boost::multi_index::ordered_non_unique<
+ // Index using values returned by the @c Host::getIPv4Reservation.
+ boost::multi_index::const_mem_fun<Host, const asiolink::IOAddress&,
+ &Host::getIPv4Reservation>
+ >,
+
+ // Third index is used to search for the host using IPv4 subnet id
+ boost::multi_index::ordered_non_unique<
+ // Index using values returned by the @c Host::getIPv4SubnetID
+ boost::multi_index::const_mem_fun<Host, SubnetID,
+ &Host::getIPv4SubnetID>
+ >,
+
+ // Forth index is used to search for the host using IPv6 subnet id
+ boost::multi_index::ordered_non_unique<
+ // Index using values returned by the @c Host::getIPv6SubnetID
+ boost::multi_index::const_mem_fun<Host, SubnetID,
+ &Host::getIPv6SubnetID>
+ >,
+
+ // Fifth index is used to search by increasing host id
+ boost::multi_index::ordered_unique<
+ // Index using values returned by the @c Host::getHostId
+ boost::multi_index::const_mem_fun<Host, uint64_t,
+ &Host::getHostId>
+ >,
+
+ // Sixth index is used to search for the host using hostname
+ // (case-sensitive compare so the key is in lower case).
+ boost::multi_index::ordered_non_unique<
+ // Index using values returned by the @c Host::getLowerHostname
+ boost::multi_index::const_mem_fun<Host, std::string,
+ &Host::getLowerHostname>
+ >
+ >
+> HostContainer;
+
+/// @brief First index type in the @c HostContainer.
+///
+/// This index allows for searching for @c Host objects using an
+/// identifier + identifier type tuple.
+typedef HostContainer::nth_index<0>::type HostContainerIndex0;
+
+/// @brief Results range returned using the @c HostContainerIndex0.
+typedef std::pair<HostContainerIndex0::iterator,
+ HostContainerIndex0::iterator> HostContainerIndex0Range;
+
+/// @brief Second index type in the @c HostContainer.
+///
+/// This index allows for searching for @c Host objects using a
+/// reserved IPv4 address.
+typedef HostContainer::nth_index<1>::type HostContainerIndex1;
+
+/// @brief Results range returned using the @c HostContainerIndex1.
+typedef std::pair<HostContainerIndex1::iterator,
+ HostContainerIndex1::iterator> HostContainerIndex1Range;
+
+/// @brief Third index type in the @c HostContainer.
+///
+/// This index allows for searching for @c Host objects using a
+/// IPv4 subnet id.
+typedef HostContainer::nth_index<2>::type HostContainerIndex2;
+
+/// @brief Results range returned using the @c HostContainerIndex2.
+typedef std::pair<HostContainerIndex2::iterator,
+ HostContainerIndex2::iterator> HostContainerIndex2Range;
+
+/// @brief Forth index type in the @c HostContainer.
+///
+/// This index allows for searching for @c Host objects using a
+/// IPv6 subnet id.
+typedef HostContainer::nth_index<3>::type HostContainerIndex3;
+
+/// @brief Results range returned using the @c HostContainerIndex3.
+typedef std::pair<HostContainerIndex3::iterator,
+ HostContainerIndex3::iterator> HostContainerIndex3Range;
+
+/// @brief Fifth index type in the @c HostContainer.
+///
+/// This index allows for searching for @c Host objects using a host id.
+typedef HostContainer::nth_index<4>::type HostContainerIndex4;
+
+/// @brief Results range returned using the @c HostContainerIndex4.
+typedef std::pair<HostContainerIndex4::iterator,
+ HostContainerIndex4::iterator> HostContainerIndex4Range;
+
+/// @brief Sixth index type in the @c HostContainer.
+///
+/// This index allows for searching for @c Host objects using a hostname.
+typedef HostContainer::nth_index<5>::type HostContainerIndex5;
+
+/// @brief Defines one entry for the Host Container for v6 hosts
+///
+/// It's essentially a pair of (IPv6 reservation, Host pointer).
+/// This structure is used as an intermediate structure in HostContainer6.
+/// For a single host that has reservations for X addresses or prefixes, there
+/// will be X HostResrv6Tuple structures.
+struct HostResrv6Tuple {
+
+ /// @brief Default constructor.
+ ///
+ /// @param resrv IPv6 address/prefix reservation
+ /// @param host pointer to the host object
+ HostResrv6Tuple(const IPv6Resrv& resrv, const HostPtr& host)
+ : resrv_(resrv), host_(host), subnet_id_(host ? host->getIPv6SubnetID() : SUBNET_ID_GLOBAL) {
+ }
+
+ /// @brief Address or prefix reservation.
+ const IPv6Resrv resrv_;
+
+ /// @brief Pointer to the host object.
+ HostPtr host_;
+
+ /// @brief Value of the IPv6 Subnet-id
+ const SubnetID subnet_id_;
+
+ /// @brief Key extractor used in the second composite key
+ const asiolink::IOAddress& getPrefix() const {
+ return (resrv_.getPrefix());
+ }
+
+ /// @brief Key extractor used in the fourth composite key
+ HostID getHostId() const {
+ return (host_->getHostId());
+ }
+};
+
+/// @brief Multi-index container holding IPv6 reservations.
+///
+/// This container holds HostResrv6Tuples, i.e. pairs of (IPv6Resrv, HostPtr)
+/// pieces of information. This is needed for efficiently finding a host
+/// for a given IPv6 address or prefix.
+typedef boost::multi_index_container<
+
+ // This containers stores (IPv6Resrv, HostPtr) tuples
+ HostResrv6Tuple,
+
+ // Start specification of indexes here.
+ boost::multi_index::indexed_by<
+
+ // First index is used to search by an address.
+ boost::multi_index::ordered_non_unique<
+
+ // Address is extracted by calling IPv6Resrv::getPrefix()
+ // and it will return an IOAddress object.
+ boost::multi_index::const_mem_fun<
+ HostResrv6Tuple, const asiolink::IOAddress&, &HostResrv6Tuple::getPrefix>
+ >,
+
+ // Second index is used to search by (subnet_id, address) pair.
+ // This is
+ boost::multi_index::ordered_non_unique<
+
+ /// This is a composite key. It uses two keys: subnet-id and
+ /// IPv6 address reservation.
+ boost::multi_index::composite_key<
+
+ // Composite key uses members of the HostResrv6Tuple class.
+ HostResrv6Tuple,
+
+ // First key extractor. Gets subnet-id as a member of the
+ // HostResrv6Tuple structure.
+ boost::multi_index::member<HostResrv6Tuple, const SubnetID,
+ &HostResrv6Tuple::subnet_id_>,
+
+ // Second key extractor. Address is extracted by calling
+ // IPv6Resrv::getPrefix() and it will return an IOAddress object.
+ boost::multi_index::const_mem_fun<
+ HostResrv6Tuple, const asiolink::IOAddress&,
+ &HostResrv6Tuple::getPrefix
+ >
+ >
+ >,
+
+ // Third index is used to search for the host using IPv6 subnet id
+ boost::multi_index::ordered_non_unique<
+ // Index using values returned by the @c Host::getIPv6SubnetID
+ boost::multi_index::member<HostResrv6Tuple, const SubnetID,
+ &HostResrv6Tuple::subnet_id_>
+ >,
+
+ // Fourth index is used to search by increasing host id
+ boost::multi_index::ordered_non_unique<
+ // Index using values returned by the @c Host::getHostId
+ boost::multi_index::const_mem_fun<HostResrv6Tuple, uint64_t,
+ &HostResrv6Tuple::getHostId>
+ >,
+
+ // Fifth index is used to search by the reserved address.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::const_mem_fun<
+ HostResrv6Tuple, const asiolink::IOAddress&,
+ &HostResrv6Tuple::getPrefix
+ >
+ >
+ >
+> HostContainer6;
+
+/// @brief First index type in the @c HostContainer6.
+///
+/// This index allows for searching for @c Host objects using an
+/// address.
+typedef HostContainer6::nth_index<0>::type HostContainer6Index0;
+
+/// @brief Results range returned using the @c HostContainer6Index0.
+typedef std::pair<HostContainer6Index0::iterator,
+ HostContainer6Index0::iterator> HostContainer6Index0Range;
+
+/// @brief Second index type in the @c HostContainer6.
+///
+/// This index allows for searching for @c Host objects using a
+/// reserved (SubnetID, IPv6 address) tuple.
+typedef HostContainer6::nth_index<1>::type HostContainer6Index1;
+
+/// @brief Results range returned using the @c HostContainer6Index1.
+typedef std::pair<HostContainer6Index1::iterator,
+ HostContainer6Index1::iterator> HostContainer6Index1Range;
+
+/// @brief Third index type in the @c HostContainer6.
+///
+/// This index allows for searching for @c Host objects using a
+/// IPv6 subnet id.
+typedef HostContainer6::nth_index<2>::type HostContainer6Index2;
+
+/// @brief Results range returned using the @c HostContainer6Index2.
+typedef std::pair<HostContainer6Index2::iterator,
+ HostContainer6Index2::iterator> HostContainer6Index2Range;
+
+/// @brief Fourth index type in the @c HostContainer6.
+///
+/// This index allows for searching for @c Host objects using a host id.
+typedef HostContainer6::nth_index<3>::type HostContainer6Index3;
+
+/// @brief Results range returned using the @c HostContainer6Index3.
+typedef std::pair<HostContainer6Index3::iterator,
+ HostContainer6Index3::iterator> HostContainer6Index3Range;
+
+/// @brief Fifth index type in the @c HostContainer6.
+///
+/// This index allows for searching for @c Host objects using an IP address.
+typedef HostContainer6::nth_index<4>::type HostContainer6Index4;
+
+/// @brief Results range returned using the @c HostContainer6Index4.
+typedef std::pair<HostContainer6Index4::iterator,
+ HostContainer6Index4::iterator> HostContainer6Index4Range;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // HOST_CONTAINER_H
diff --git a/src/lib/dhcpsrv/host_data_source_factory.cc b/src/lib/dhcpsrv/host_data_source_factory.cc
new file mode 100644
index 0000000..fa27d08
--- /dev/null
+++ b/src/lib/dhcpsrv/host_data_source_factory.cc
@@ -0,0 +1,238 @@
+// Copyright (C) 2015-2023 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 <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/hosts_log.h>
+#include <log/logger_support.h>
+
+#ifdef HAVE_MYSQL
+#include <dhcpsrv/mysql_host_data_source.h>
+#endif
+
+#ifdef HAVE_PGSQL
+#include <dhcpsrv/pgsql_host_data_source.h>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <sstream>
+#include <utility>
+
+using namespace isc::db;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+map<string, HostDataSourceFactory::Factory> HostDataSourceFactory::map_;
+
+void
+HostDataSourceFactory::add(HostDataSourceList& sources,
+ const string& dbaccess) {
+ // Parse the access string and create a redacted string for logging.
+ DatabaseConnection::ParameterMap parameters =
+ DatabaseConnection::parse(dbaccess);
+
+ // Get the database type and open the corresponding database
+ DatabaseConnection::ParameterMap::iterator it = parameters.find("type");
+ if (it == parameters.end()) {
+ isc_throw(InvalidParameter, "Host database configuration does not "
+ "contain the 'type' keyword");
+ }
+
+ string db_type = it->second;
+ auto index = map_.find(db_type);
+
+ // No match?
+ if (index == map_.end()) {
+ if ((db_type == "mysql") ||
+ (db_type == "postgresql")) {
+ string with = (db_type == "postgresql" ? "pgsql" : db_type);
+ isc_throw(InvalidType, "The type of host backend: '" << db_type
+ << "' is not compiled in. Did you forget to use --with-"
+ << with << " during compilation?");
+ }
+ isc_throw(InvalidType, "The type of host backend: '" <<
+ db_type << "' is not supported");
+ }
+
+ // Call the factory and push the pointer on sources.
+ sources.push_back(index->second(parameters));
+
+ // Check the factory did not return NULL.
+ if (!sources.back()) {
+ sources.pop_back();
+ isc_throw(Unexpected, "Hosts database " << db_type <<
+ " factory returned NULL");
+ }
+}
+
+bool
+HostDataSourceFactory::del(HostDataSourceList& sources,
+ const string& db_type) {
+ for (auto it = sources.begin(); it != sources.end(); ++it) {
+ if ((*it)->getType() != db_type) {
+ continue;
+ }
+ LOG_DEBUG(hosts_logger, DHCPSRV_DBG_TRACE, HOSTS_CFG_CLOSE_HOST_DATA_SOURCE)
+ .arg(db_type);
+ sources.erase(it);
+ return (true);
+ }
+ return (false);
+}
+
+bool
+HostDataSourceFactory::del(HostDataSourceList& sources,
+ const string& db_type,
+ const string& dbaccess,
+ bool if_unusable) {
+ DatabaseConnection::ParameterMap parameters =
+ DatabaseConnection::parse(dbaccess);
+ bool deleted = false;
+ if (if_unusable) {
+ deleted = true;
+ }
+
+ for (auto it = sources.begin(); it != sources.end(); ++it) {
+ if ((*it)->getType() != db_type || (*it)->getParameters() != parameters) {
+ continue;
+ }
+ if (if_unusable && (!(*it)->isUnusable())) {
+ deleted = false;
+ continue;
+ }
+ LOG_DEBUG(hosts_logger, DHCPSRV_DBG_TRACE, HOSTS_CFG_CLOSE_HOST_DATA_SOURCE)
+ .arg((*it)->getType());
+ sources.erase(it);
+ return (true);
+ }
+ return (deleted);
+}
+
+bool
+HostDataSourceFactory::registerFactory(const string& db_type,
+ const Factory& factory,
+ bool no_log) {
+ if (map_.count(db_type)) {
+ return (false);
+ }
+ map_.insert(pair<string, Factory>(db_type, factory));
+
+ // We are dealing here with static logger initialization fiasco.
+ // registerFactory may be called from constructors of static global
+ // objects for built in backends. The logging is not initialized yet,
+ // so the LOG_DEBUG would throw.
+ if (!no_log) {
+ LOG_DEBUG(hosts_logger, DHCPSRV_DBG_TRACE, HOSTS_BACKEND_REGISTER)
+ .arg(db_type);
+ }
+ return (true);
+}
+
+bool
+HostDataSourceFactory::deregisterFactory(const string& db_type, bool no_log) {
+ auto index = map_.find(db_type);
+ if (index != map_.end()) {
+ map_.erase(index);
+ if (!no_log) {
+ LOG_DEBUG(hosts_logger, DHCPSRV_DBG_TRACE,
+ HOSTS_BACKEND_DEREGISTER)
+ .arg(db_type);
+ }
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+bool
+HostDataSourceFactory::registeredFactory(const std::string& db_type) {
+ auto index = map_.find(db_type);
+ return (index != map_.end());
+}
+
+void
+HostDataSourceFactory::printRegistered() {
+ std::stringstream txt;
+
+ for (auto x : map_) {
+ txt << x.first << " ";
+ }
+
+ LOG_INFO(hosts_logger, HOSTS_BACKENDS_REGISTERED).arg(txt.str());
+}
+
+} // namespace dhcp
+} // namespace isc
+
+//
+// Register database backends
+//
+
+using namespace isc::dhcp;
+
+namespace {
+
+#ifdef HAVE_MYSQL
+struct MySqlHostDataSourceInit {
+ // Constructor registers
+ MySqlHostDataSourceInit() {
+ HostDataSourceFactory::registerFactory("mysql", factory, true);
+ }
+
+ // Destructor deregisters
+ ~MySqlHostDataSourceInit() {
+ HostDataSourceFactory::deregisterFactory("mysql", true);
+ }
+
+ // Factory class method
+ static HostDataSourcePtr
+ factory(const DatabaseConnection::ParameterMap& parameters) {
+ LOG_INFO(hosts_logger, DHCPSRV_MYSQL_HOST_DB)
+ .arg(DatabaseConnection::redactedAccessString(parameters));
+ return (HostDataSourcePtr(new MySqlHostDataSource(parameters)));
+ }
+};
+
+// Database backend will be registered at object initialization
+MySqlHostDataSourceInit mysql_init_;
+#endif
+
+#ifdef HAVE_PGSQL
+struct PgSqlHostDataSourceInit {
+ // Constructor registers
+ PgSqlHostDataSourceInit() {
+ HostDataSourceFactory::registerFactory("postgresql", factory, true);
+ }
+
+ // Destructor deregisters
+ ~PgSqlHostDataSourceInit() {
+ HostDataSourceFactory::deregisterFactory("postgresql", true);
+ }
+
+ // Factory class method
+ static HostDataSourcePtr
+ factory(const DatabaseConnection::ParameterMap& parameters) {
+ LOG_INFO(hosts_logger, DHCPSRV_PGSQL_HOST_DB)
+ .arg(DatabaseConnection::redactedAccessString(parameters));
+ return (HostDataSourcePtr(new PgSqlHostDataSource(parameters)));
+ }
+};
+
+// Database backend will be registered at object initialization
+PgSqlHostDataSourceInit pgsql_init_;
+#endif
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/host_data_source_factory.h b/src/lib/dhcpsrv/host_data_source_factory.h
new file mode 100644
index 0000000..c4e8336
--- /dev/null
+++ b/src/lib/dhcpsrv/host_data_source_factory.h
@@ -0,0 +1,149 @@
+// Copyright (C) 2015-2023 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 HOST_DATA_SOURCE_FACTORY_H
+#define HOST_DATA_SOURCE_FACTORY_H
+
+#include <database/database_connection.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <exceptions/exceptions.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+#include <map>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief No host data source instance exception
+///
+/// Thrown if an attempt is made to get a reference to the current
+/// host data source instance and none is currently available.
+class NoHostDataSourceManager : public Exception {
+public:
+ NoHostDataSourceManager(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Host Data Source Factory
+///
+/// This class comprises nothing but static methods used to create a host data
+/// source object. It analyzes the database information passed to the creation
+/// function and instantiates an appropriate host data source object based on
+/// the type requested.
+///
+/// Strictly speaking these functions could be stand-alone functions. However,
+/// it is convenient to encapsulate them in a class for naming purposes.
+
+class HostDataSourceFactory {
+public:
+ /// @brief Create and add an instance of a host data source.
+ ///
+ /// Each database backend has its own host data source type. This static
+ /// method adds an object of the appropriate type to a list of
+ /// host data sources.
+ ///
+ /// dbaccess is a generic way of passing parameters. Parameters are passed
+ /// in the "name=value" format, separated by spaces. The data MUST include
+ /// a keyword/value pair of the form "type=dbtype" giving the database
+ /// type, e.q. "mysql" or "sqlite3".
+ ///
+ /// @param sources host data source list (new backend will be added here)
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
+ ///
+ /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
+ /// keyword.
+ /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
+ /// identify a supported backend.
+ static void add(HostDataSourceList& sources, const std::string& dbaccess);
+
+ /// @brief Delete a host data source.
+ ///
+ /// Delete the first instance of a host data source of the given type.
+ /// This should have the effect of closing the database connection.
+ ///
+ /// @param sources host data source list.
+ /// @param db_type database backend type.
+ /// @return true when found and removed, false when not found.
+ static bool del(HostDataSourceList& sources, const std::string& db_type);
+
+ /// @brief Delete a host data source.
+ ///
+ /// Delete the first instance of a host data source which matches specific
+ /// parameters.
+ /// This should have the effect of closing the database connection.
+ ///
+ /// @param sources host data source list.
+ /// @param db_type database backend type.
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
+ /// @param if_unusable flag which indicates if the host data source should
+ /// be deleted only if it is unusable.
+ /// @return false when not removed because it is not found or because it is
+ /// still usable (if_unusable is true), true otherwise.
+ static bool del(HostDataSourceList& sources, const std::string& db_type,
+ const std::string& dbaccess, bool if_unusable = true);
+
+ /// @brief Type of host data source factory
+ ///
+ /// A factory takes a parameter map and returns a pointer to a host
+ /// data source. In case of failure it must throw and not return NULL.
+ typedef std::function<HostDataSourcePtr (const db::DatabaseConnection::ParameterMap&)> Factory;
+
+ /// @brief Register a host data source factory
+ ///
+ /// Associate the factory to a database type in the map.
+ /// The no_log is to avoid logging before the logger is initialized
+ /// as when called at global object initialization.
+ ///
+ /// @param db_type database type
+ /// @param factory host data source factory
+ /// @param no_log do not log (default false)
+ /// @return true if the factory was successfully added to the map, false
+ /// if it already exists.
+ static bool registerFactory(const std::string& db_type,
+ const Factory& factory, bool no_log = false);
+
+ /// @brief Deregister a host data source factory
+ ///
+ /// Disassociate the factory to a database type in the map.
+ /// The no_log is to avoid logging during global object deinitialization.
+ ///
+ /// @param db_type database type
+ /// @param no_log do not log (default false)
+ /// @return true if the factory was successfully removed from the map,
+ /// false if it was not found.
+ static bool deregisterFactory(const std::string& db_type,
+ bool no_log = false);
+
+ /// @brief Check if a host data source factory was registered
+ ///
+ /// @param db_type database type
+ /// @return true if a factory was registered for db_type, false if not.
+ static bool registeredFactory(const std::string& db_type);
+
+ /// @brief Prints out all registered backends.
+ ///
+ /// We need a dedicated method for this, because we sometimes can't log
+ /// the backend type when doing early initialization for backends
+ /// initialized statically.
+ static void printRegistered();
+
+private:
+ /// @brief Factory map
+ static std::map<std::string, Factory> map_;
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif
diff --git a/src/lib/dhcpsrv/host_mgr.cc b/src/lib/dhcpsrv/host_mgr.cc
new file mode 100644
index 0000000..3771fd3
--- /dev/null
+++ b/src/lib/dhcpsrv/host_mgr.cc
@@ -0,0 +1,985 @@
+// Copyright (C) 2014-2023 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 <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/host_data_source_factory.h>
+
+namespace {
+
+/// @brief Convenience function returning a pointer to the hosts configuration
+/// for editing.
+///
+/// This function is called by the @c HostMgr methods requiring access to the
+/// host reservations specified in the DHCP server configuration.
+///
+/// @return A pointer to the non-const hosts reservation configuration.
+isc::dhcp::CfgHostsPtr getCfgHostsForEdit() {
+ return (isc::dhcp::CfgMgr::instance().getCurrentCfg()->getCfgHosts());
+}
+
+/// @brief Convenience function returning a pointer to the hosts configuration.
+///
+/// This function is called by the @c HostMgr methods requiring access to the
+/// host reservations specified in the DHCP server configuration.
+///
+/// @return A pointer to the const hosts reservation configuration.
+isc::dhcp::ConstCfgHostsPtr getCfgHosts() {
+ return (getCfgHostsForEdit());
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+using namespace isc::asiolink;
+using namespace isc::db;
+
+IOServicePtr HostMgr::io_service_ = IOServicePtr();
+
+boost::scoped_ptr<HostMgr>&
+HostMgr::getHostMgrPtr() {
+ static boost::scoped_ptr<HostMgr> host_mgr_ptr;
+ return (host_mgr_ptr);
+}
+
+void
+HostMgr::create() {
+ getHostMgrPtr().reset(new HostMgr());
+}
+
+void
+HostMgr::addBackend(const std::string& access) {
+ HostDataSourceFactory::add(getHostMgrPtr()->alternate_sources_, access);
+}
+
+bool
+HostMgr::delBackend(const std::string& db_type) {
+ if (getHostMgrPtr()->cache_ptr_ &&
+ getHostMgrPtr()->cache_ptr_->getType() == db_type) {
+ getHostMgrPtr()->cache_ptr_.reset();
+ }
+ return (HostDataSourceFactory::del(getHostMgrPtr()->alternate_sources_,
+ db_type));
+}
+
+bool
+HostMgr::delBackend(const std::string& db_type, const std::string& access,
+ bool if_unusable) {
+ return (HostDataSourceFactory::del(getHostMgrPtr()->alternate_sources_,
+ db_type, access, if_unusable));
+}
+
+void
+HostMgr::delAllBackends() {
+ getHostMgrPtr()->alternate_sources_.clear();
+}
+
+HostDataSourcePtr
+HostMgr::getHostDataSource() const {
+ if (alternate_sources_.empty()) {
+ return (HostDataSourcePtr());
+ }
+ return (alternate_sources_[0]);
+}
+
+bool
+HostMgr::checkCacheBackend(bool logging) {
+ if (getHostMgrPtr()->cache_ptr_) {
+ return (true);
+ }
+ HostDataSourceList& sources = getHostMgrPtr()->alternate_sources_;
+ if (sources.empty()) {
+ return (false);
+ }
+ CacheHostDataSourcePtr cache_ptr =
+ boost::dynamic_pointer_cast<CacheHostDataSource>(sources[0]);
+ if (cache_ptr) {
+ getHostMgrPtr()->cache_ptr_ = cache_ptr;
+ if (logging) {
+ LOG_INFO(hosts_logger, HOSTS_CFG_CACHE_HOST_DATA_SOURCE)
+ .arg(cache_ptr->getType());
+ }
+ return (true);
+ }
+ return (false);
+}
+
+HostMgr&
+HostMgr::instance() {
+ boost::scoped_ptr<HostMgr>& host_mgr_ptr = getHostMgrPtr();
+ if (!host_mgr_ptr) {
+ create();
+ }
+ return (*host_mgr_ptr);
+}
+
+ConstHostCollection
+HostMgr::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAll(identifier_type, identifier_begin,
+ identifier_len);
+ }
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus =
+ source->getAll(identifier_type, identifier_begin, identifier_len);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ return getAll(identifier_type, identifier_begin, identifier_len,
+ HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAll4(const SubnetID& subnet_id, const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAll4(subnet_id);
+ }
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus = source->getAll4(subnet_id);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll4(const SubnetID& subnet_id) const {
+ return getAll4(subnet_id, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAll6(const SubnetID& subnet_id, const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAll6(subnet_id);
+ }
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus = source->getAll6(subnet_id);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll6(const SubnetID& subnet_id) const {
+ return getAll6(subnet_id, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAllbyHostname(const std::string& hostname, const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAllbyHostname(hostname);
+ }
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus = source->getAllbyHostname(hostname);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAllbyHostname(const std::string& hostname) const {
+ return getAllbyHostname(hostname, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id,
+ const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAllbyHostname4(hostname, subnet_id);
+ }
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus = source->getAllbyHostname4(hostname,
+ subnet_id);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ return getAllbyHostname4(hostname, subnet_id, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id,
+ const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAllbyHostname6(hostname, subnet_id);
+ }
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus = source->getAllbyHostname6(hostname,
+ subnet_id);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ return getAllbyHostname6(hostname, subnet_id, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Return empty if (and only if) sources are exhausted.
+ if (source_index > alternate_sources_.size()) {
+ return (ConstHostCollection());
+ }
+
+ ConstHostCollection hosts;
+ // Source index 0 means config file.
+ if (source_index == 0) {
+ hosts = getCfgHosts()->
+ getPage4(subnet_id, source_index, lower_host_id, page_size);
+ } else {
+ hosts = alternate_sources_[source_index - 1]->
+ getPage4(subnet_id, source_index, lower_host_id, page_size);
+ }
+
+ // When got something return it.
+ if (!hosts.empty()) {
+ return (hosts);
+ }
+
+ // Nothing from this source: try the next one.
+ // Note the recursion is limited to the number of sources in all cases.
+ ++source_index;
+ return (getPage4(subnet_id, source_index, 0UL, page_size));
+}
+
+ConstHostCollection
+HostMgr::getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Return empty if (and only if) sources are exhausted.
+ if (source_index > alternate_sources_.size()) {
+ return (ConstHostCollection());
+ }
+
+ ConstHostCollection hosts;
+ // Source index 0 means config file.
+ if (source_index == 0) {
+ hosts = getCfgHosts()->
+ getPage6(subnet_id, source_index, lower_host_id, page_size);
+ } else {
+ hosts = alternate_sources_[source_index - 1]->
+ getPage6(subnet_id, source_index, lower_host_id, page_size);
+ }
+
+ // When got something return it.
+ if (!hosts.empty()) {
+ return (hosts);
+ }
+
+ // Nothing from this source: try the next one.
+ // Note the recursion is limited to the number of sources in all cases.
+ ++source_index;
+ return (getPage6(subnet_id, source_index, 0UL, page_size));
+}
+
+ConstHostCollection
+HostMgr::getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Return empty if (and only if) sources are exhausted.
+ if (source_index > alternate_sources_.size()) {
+ return (ConstHostCollection());
+ }
+
+ ConstHostCollection hosts;
+ // Source index 0 means config file.
+ if (source_index == 0) {
+ hosts = getCfgHosts()->
+ getPage4(source_index, lower_host_id, page_size);
+ } else {
+ hosts = alternate_sources_[source_index - 1]->
+ getPage4(source_index, lower_host_id, page_size);
+ }
+
+ // When got something return it.
+ if (!hosts.empty()) {
+ return (hosts);
+ }
+
+ // Nothing from this source: try the next one.
+ // Note the recursion is limited to the number of sources in all cases.
+ ++source_index;
+ return (getPage4(source_index, 0UL, page_size));
+}
+
+ConstHostCollection
+HostMgr::getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Return empty if (and only if) sources are exhausted.
+ if (source_index > alternate_sources_.size()) {
+ return (ConstHostCollection());
+ }
+
+ ConstHostCollection hosts;
+ // Source index 0 means config file.
+ if (source_index == 0) {
+ hosts = getCfgHosts()->
+ getPage6(source_index, lower_host_id, page_size);
+ } else {
+ hosts = alternate_sources_[source_index - 1]->
+ getPage6(source_index, lower_host_id, page_size);
+ }
+
+ // When got something return it.
+ if (!hosts.empty()) {
+ return (hosts);
+ }
+
+ // Nothing from this source: try the next one.
+ // Note the recursion is limited to the number of sources in all cases.
+ ++source_index;
+ return (getPage6(source_index, 0UL, page_size));
+}
+
+ConstHostCollection
+HostMgr::getAll4(const IOAddress& address, const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAll4(address);
+ }
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus = source->getAll4(address);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll4(const IOAddress& address) const {
+ return getAll4(address, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostPtr
+HostMgr::get4Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const {
+ ConstHostPtr host;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ host = getCfgHosts()->get4(subnet_id, identifier_type,
+ identifier_begin, identifier_len);
+ }
+
+ // Found it in the config file, there are no backends configured, or
+ // querying them is disabled?
+ // Then we're done here.
+ if (host || alternate_sources_.empty() || !(target & HostMgrOperationTarget::ALTERNATE_SOURCES)) {
+ return (host);
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+
+ // Try to find a host in each configured backend. We return as soon
+ // as we find first hit.
+ for (auto source : alternate_sources_) {
+ host = source->get4(subnet_id, identifier_type,
+ identifier_begin, identifier_len);
+
+ if (host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type,
+ identifier_begin,
+ identifier_len))
+ .arg(source->getType())
+ .arg(host->toText());
+
+ if (source != cache_ptr_) {
+ cache(host);
+ }
+ return (host);
+ }
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+HostMgr::get4Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ return get4Any(subnet_id, identifier_type, identifier_begin, identifier_len,
+ HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const {
+ ConstHostPtr host = get4Any(subnet_id, identifier_type,
+ identifier_begin, identifier_len, target);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ } else if (!host && negative_caching_) {
+ cacheNegative(subnet_id, SubnetID(SUBNET_ID_UNUSED),
+ identifier_type, identifier_begin, identifier_len);
+ }
+ return (host);
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ return get4(subnet_id, identifier_type, identifier_begin, identifier_len,
+ HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const {
+ ConstHostPtr host;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ host = getCfgHosts()->get4(subnet_id, address);
+ }
+
+ if (host || alternate_sources_.empty() || !(target & HostMgrOperationTarget::ALTERNATE_SOURCES)) {
+ return (host);
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4)
+ .arg(subnet_id)
+ .arg(address.toText());
+ for (auto source : alternate_sources_) {
+ host = source->get4(subnet_id, address);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ }
+ if (host && source != cache_ptr_) {
+ cache(host);
+ }
+ if (host) {
+ return (host);
+ }
+ }
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ return get4(subnet_id, address, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAll4(subnet_id, address);
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4)
+ .arg(subnet_id)
+ .arg(address.toText());
+
+ for (auto source : alternate_sources_) {
+ auto hosts_plus = source->getAll4(subnet_id, address);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ return getAll4(subnet_id, address, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostPtr
+HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len,
+ const HostMgrOperationTarget target) const {
+ ConstHostPtr host;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ host = getCfgHosts()->get6(prefix, prefix_len);
+ }
+ if (host || alternate_sources_.empty() || !(target & HostMgrOperationTarget::ALTERNATE_SOURCES)) {
+ return (host);
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE, HOSTS_MGR_ALTERNATE_GET6_PREFIX)
+ .arg(prefix.toText())
+ .arg(static_cast<int>(prefix_len));
+ for (auto source : alternate_sources_) {
+ host = source->get6(prefix, prefix_len);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ }
+ if (host && source != cache_ptr_) {
+ cache(host);
+ }
+ if (host) {
+ return (host);
+ }
+ }
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
+ return get6(prefix, prefix_len, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostPtr
+HostMgr::get6Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const {
+ ConstHostPtr host;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ host = getCfgHosts()->get6(subnet_id, identifier_type,
+ identifier_begin, identifier_len);
+ }
+ if (host || alternate_sources_.empty() || !(target & HostMgrOperationTarget::ALTERNATE_SOURCES)) {
+ return (host);
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+
+ for (auto source : alternate_sources_) {
+ host = source->get6(subnet_id, identifier_type,
+ identifier_begin, identifier_len);
+
+ if (host) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type,
+ identifier_begin,
+ identifier_len))
+ .arg(source->getType())
+ .arg(host->toText());
+
+ if (source != cache_ptr_) {
+ cache(host);
+ }
+ return (host);
+ }
+ }
+
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_RESULTS,
+ HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL)
+ .arg(subnet_id)
+ .arg(Host::getIdentifierAsText(identifier_type, identifier_begin,
+ identifier_len));
+
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+HostMgr::get6Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ return get6Any(subnet_id, identifier_type, identifier_begin, identifier_len,
+ HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const {
+ ConstHostPtr host = get6Any(subnet_id, identifier_type,
+ identifier_begin, identifier_len, target);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ } else if (!host && negative_caching_) {
+ cacheNegative(SubnetID(SUBNET_ID_UNUSED), subnet_id,
+ identifier_type, identifier_begin, identifier_len);
+ }
+ return (host);
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ return get6(subnet_id, identifier_type, identifier_begin, identifier_len,
+ HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr,
+ const HostMgrOperationTarget target) const {
+ ConstHostPtr host;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ host = getCfgHosts()->get6(subnet_id, addr);
+ }
+ if (host || alternate_sources_.empty() || !(target & HostMgrOperationTarget::ALTERNATE_SOURCES)) {
+ return (host);
+ }
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6)
+ .arg(subnet_id)
+ .arg(addr.toText());
+ for (auto source : alternate_sources_) {
+ host = source->get6(subnet_id, addr);
+ if (host && host->getNegative()) {
+ return (ConstHostPtr());
+ }
+ if (host && source != cache_ptr_) {
+ cache(host);
+ }
+ if (host) {
+ return (host);
+ }
+ }
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr) const {
+ return get6(subnet_id, addr, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAll6(subnet_id, address);
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ LOG_DEBUG(hosts_logger, HOSTS_DBG_TRACE,
+ HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6)
+ .arg(subnet_id)
+ .arg(address.toText());
+
+ for (auto source : alternate_sources_) {
+ auto hosts_plus = source->getAll6(subnet_id, address);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ return getAll6(subnet_id, address, HostMgrOperationTarget::ALL_SOURCES);
+}
+
+ConstHostCollection
+HostMgr::getAll6(const IOAddress& address) const {
+ return (getAll6(address, HostMgrOperationTarget::ALL_SOURCES));
+}
+
+ConstHostCollection
+HostMgr::getAll6(const IOAddress& address, const HostMgrOperationTarget target) const {
+ ConstHostCollection hosts;
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ hosts = getCfgHosts()->getAll6(address);
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ for (auto source : alternate_sources_) {
+ ConstHostCollection hosts_plus = source->getAll6(address);
+ hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+ }
+ }
+
+ return (hosts);
+}
+
+void
+HostMgr::add(const HostPtr& host, const HostMgrOperationTarget target) {
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ getCfgHostsForEdit()->add(host);
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ // Don't throw if all targets were selected.
+ if (alternate_sources_.empty() && !(target & HostMgrOperationTarget::PRIMARY_SOURCE)) {
+ isc_throw(NoHostDataSourceManager, "Unable to add new host because there is "
+ "no hosts-database configured.");
+
+ }
+
+ for (auto source : alternate_sources_) {
+ source->add(host);
+ }
+ }
+
+ // If no backend throws the host should be cached.
+ if (cache_ptr_) {
+ cache(host);
+ }
+}
+
+void
+HostMgr::add(const HostPtr& host) {
+ return add(host, HostMgrOperationTarget::ALTERNATE_SOURCES);
+}
+
+bool
+HostMgr::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr,
+ const HostMgrOperationTarget target) {
+ size_t erased = false;
+
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ erased = getCfgHostsForEdit()->del(subnet_id, addr);
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ // Don't throw if all targets were selected.
+ if (alternate_sources_.empty() && !(target & HostMgrOperationTarget::PRIMARY_SOURCE)) {
+ isc_throw(NoHostDataSourceManager, "Unable to delete a host because there is "
+ "no hosts-database configured.");
+ }
+
+ for (auto source : alternate_sources_) {
+ bool alternate_erased = source->del(subnet_id, addr);
+ erased = alternate_erased || erased;
+ }
+ }
+
+ return (erased);
+}
+
+bool
+HostMgr::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
+ return del(subnet_id, addr, HostMgrOperationTarget::ALTERNATE_SOURCES);
+}
+
+bool
+HostMgr::del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len,
+ const HostMgrOperationTarget target) {
+ bool success = false;
+
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ if (getCfgHostsForEdit()->del4(subnet_id, identifier_type,
+ identifier_begin, identifier_len)) {
+ success = true;
+ }
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ // Don't throw if all targets were selected.
+ if (alternate_sources_.empty() && !(target & HostMgrOperationTarget::PRIMARY_SOURCE)) {
+ isc_throw(NoHostDataSourceManager, "Unable to delete a host because there is "
+ "no hosts-database configured.");
+ }
+
+ for (auto source : alternate_sources_) {
+ if (source->del4(subnet_id, identifier_type,
+ identifier_begin, identifier_len)) {
+ success = true;
+ }
+ }
+ }
+ return (success);
+}
+
+bool
+HostMgr::del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) {
+ return del4(subnet_id, identifier_type, identifier_begin, identifier_len,
+ HostMgrOperationTarget::ALTERNATE_SOURCES);
+}
+
+bool
+HostMgr::del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len,
+ const HostMgrOperationTarget target) {
+ bool success = false;
+
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ if (getCfgHostsForEdit()->del6(subnet_id, identifier_type,
+ identifier_begin, identifier_len)) {
+ success = true;
+ }
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ // Don't throw if all targets were selected.
+ if (alternate_sources_.empty() && !(target & HostMgrOperationTarget::PRIMARY_SOURCE)) {
+ isc_throw(NoHostDataSourceManager, "Unable to delete a host because there is "
+ "no hosts-database configured.");
+ }
+
+ for (auto source : alternate_sources_) {
+ if (source->del6(subnet_id, identifier_type,
+ identifier_begin, identifier_len)) {
+ success = true;
+ }
+ }
+ }
+ return (success);
+}
+
+bool
+HostMgr::del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) {
+ return del6(subnet_id, identifier_type, identifier_begin, identifier_len,
+ HostMgrOperationTarget::ALTERNATE_SOURCES);
+}
+
+void
+HostMgr::update(HostPtr const& host, const HostMgrOperationTarget target) {
+ if (target & HostMgrOperationTarget::PRIMARY_SOURCE) {
+ getCfgHostsForEdit()->update(host);
+ }
+
+ if (target & HostMgrOperationTarget::ALTERNATE_SOURCES) {
+ // Don't throw if all targets were selected.
+ if (alternate_sources_.empty() && !(target & HostMgrOperationTarget::PRIMARY_SOURCE)) {
+ isc_throw(NoHostDataSourceManager,
+ "Unable to update existing host because there is no hosts-database configured.");
+ }
+
+ for (HostDataSourcePtr const& source : alternate_sources_) {
+ source->update(host);
+ }
+ }
+
+ // If no backend throws the host should be cached.
+ if (cache_ptr_) {
+ cache(host);
+ }
+}
+
+void
+HostMgr::update(HostPtr const& host) {
+ update(host, HostMgrOperationTarget::ALTERNATE_SOURCES);
+}
+
+void
+HostMgr::cache(ConstHostPtr host) const {
+ if (cache_ptr_) {
+ // Need a real host.
+ if (!host || host->getNegative()) {
+ return;
+ }
+ // Replace any existing value.
+ // Don't check the result as it does not matter?
+ cache_ptr_->insert(host, true);
+ }
+}
+
+void
+HostMgr::cacheNegative(const SubnetID& ipv4_subnet_id,
+ const SubnetID& ipv6_subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ if (cache_ptr_ && negative_caching_) {
+ HostPtr host(new Host(identifier_begin, identifier_len,
+ identifier_type,
+ ipv4_subnet_id, ipv6_subnet_id,
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host->setNegative(true);
+ // Don't replace any existing value.
+ // nor matter if it fails.
+ cache_ptr_->insert(host, false);
+ }
+}
+
+bool
+HostMgr::setIPReservationsUnique(const bool unique) {
+ // Iterate over the alternate sources first, because they may include those
+ // for which the new setting is not supported.
+ for (auto source : alternate_sources_) {
+ if (!source->setIPReservationsUnique(unique)) {
+ // One of the sources does not support this new mode of operation.
+ // Let's log a warning and back off the changes to the default
+ // setting which should always be supported.
+ ip_reservations_unique_ = true;
+ LOG_WARN(hosts_logger, HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED)
+ .arg(source->getType());
+ for (auto source : alternate_sources_) {
+ source->setIPReservationsUnique(true);
+ }
+ return (false);
+ }
+ }
+ // Successfully configured the HostMgr to use the new setting.
+ // Remember this setting so we can return it via the
+ // getIPReservationsUnique.
+ ip_reservations_unique_ = unique;
+ return (true);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/host_mgr.h b/src/lib/dhcpsrv/host_mgr.h
new file mode 100644
index 0000000..f6e6501
--- /dev/null
+++ b/src/lib/dhcpsrv/host_mgr.h
@@ -0,0 +1,912 @@
+// Copyright (C) 2014-2023 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 HOST_MGR_H
+#define HOST_MGR_H
+
+#include <asiolink/io_service.h>
+#include <database/database_connection.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/cache_host_data_source.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet_id.h>
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <string>
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+// Enum flags to define a target of the host manager functions.
+enum HostMgrOperationTarget {
+ // The operation target not specified. Consider nothing.
+ UNSPECIFIED_SOURCE = 0,
+ // Consider only the CfgHosts instance.
+ PRIMARY_SOURCE = 1, // 1 << 1
+ // Consider only the alternate sources.
+ ALTERNATE_SOURCES = 2, // 1 << 2
+ // Consider both CfgInstance and alternate sources.
+ ALL_SOURCES = 3 // PRIMARY_SOURCE | ALTERNATE_SOURCES
+};
+
+/// @brief Host Manager.
+///
+/// This is a singleton class which provides access to multiple sources of
+/// information about static host reservations. These sources are also referred
+/// to as host data sources. Each source derives (directly or indirectly) from
+/// the @c BaseHostDataSource.
+///
+/// The @c HostMgr is a central point for providing information about the host
+/// reservations. Internally, it relays the queries (calls to the appropriate
+/// methods declared in the @c BaseHostDataSource) to the data sources it is
+/// connected to. The @c HostMgr is always connected to the server's
+/// configuration, accessible through the @c CfgHosts object in the @c CfgMgr.
+/// The @c CfgHosts object holds all reservations specified in the DHCP server
+/// configuration file. If a particular reservation is not found in the
+/// @c CfgHosts object, the @c HostMgr will try to find it using alternate
+/// host data storages. An alternate host data storage is usually a database
+/// (e.g. SQL database), accessible through a dedicated host data source
+/// object (a.k.a. database backend). This datasource is responsible for
+/// managing the connection with the database and forming appropriate queries
+/// to retrieve (or update) the information about the reservations.
+///
+/// The use of alternate host data sources is optional and usually requires
+/// additional configuration to be specified by the server administrator.
+/// For example, for the SQL database the user's credentials, database address,
+/// and database name are required. The @c HostMgr passes these parameters
+/// to an appropriate datasource which is responsible for opening a connection
+/// and maintaining it.
+///
+/// It is possible to switch to different alternate data sources or disable
+/// the use of alternate datasources, e.g. as a result of server's
+/// reconfiguration. However, the use of the primary host data source (i.e.
+/// reservations specified in the configuration file) can't be disabled.
+class HostMgr : public boost::noncopyable, public BaseHostDataSource {
+public:
+
+ /// @brief Creates new instance of the @c HostMgr.
+ ///
+ /// If an instance of the @c HostMgr already exists, it will be replaced
+ /// by the new instance. Thus, any instances of alternate host data
+ /// sources will be dropped.
+ ///
+ static void create();
+
+ /// @brief Add an alternate host backend (aka host data source).
+ ///
+ /// @param access Host backend access parameters for the alternate
+ /// host backend. It holds "keyword=value" pairs, separated by spaces.
+ ///
+ /// The supported values are specific to the alternate backend in use.
+ /// However, the "type" parameter will be common and it will specify which
+ /// backend is to be used. Currently, no parameters are supported
+ /// and the parameter is ignored.
+ static void addBackend(const std::string& access);
+
+ /// @brief Delete an alternate host backend (aka host data source).
+ ///
+ /// @param db_type database backend type.
+ /// @return true when found and removed, false when not found.
+ static bool delBackend(const std::string& db_type);
+
+ /// @brief Delete an alternate host backend (aka host data source).
+ ///
+ /// @param db_type database backend type.
+ /// @param access Host backend access parameters for the alternate
+ /// host backend. It holds "keyword=value" pairs, separated by spaces.
+ /// @param if_unusable flag which indicates if the host data source should
+ /// be deleted only if it is unusable.
+ /// @return false when not removed because it is not found or because it is
+ /// still usable (if_unusable is true), true otherwise.
+ static bool delBackend(const std::string& db_type,
+ const std::string& access,
+ bool if_unusable = false);
+
+ /// @brief Delete all alternate backends.
+ static void delAllBackends();
+
+ /// @brief Check for the cache host backend.
+ ///
+ /// Checks if the first host backend implements
+ /// the cache abstract class and sets cache_ptr_.
+ ///
+ /// @param logging When true (not the default) emit an informational log.
+ /// @return true if the first host backend is a cache.
+ static bool checkCacheBackend(bool logging = false);
+
+ /// @brief Returns a sole instance of the @c HostMgr.
+ ///
+ /// This method should be used to retrieve an instance of the @c HostMgr
+ /// to be used to gather/manage host reservations. It returns an instance
+ /// of the @c HostMgr created by the @c create method. If such instance
+ /// doesn't exist yet, it is created using the @c create method with the
+ /// default value of the data access string, which configures the host
+ /// manager to not use the alternate host data source.
+ static HostMgr& instance();
+
+ /// @brief Return all hosts connected to any subnet for which reservations
+ /// have been made using a specified identifier.
+ ///
+ /// This method returns all @c Host objects representing reservations for
+ /// a specified identifier as documented in the
+ /// @c BaseHostDataSource::getAll.
+ ///
+ /// It retrieves reservations from both primary and alternate host data
+ /// source as a single collection of @c Host objects, i.e. if matching
+ /// reservations are in both sources, all of them are returned. The
+ /// reservations from the primary data source are placed before the
+ /// reservations from the alternate source.
+ ///
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAll compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects representing reservations
+ /// in a specified subnet as documented in the
+ /// @c BaseHostDataSource::getAll4
+ ///
+ /// It retrieves reservations from both primary and alternate host data
+ /// source as a single collection of @c Host objects, i.e. if matching
+ /// reservations are in both sources, all of them are returned. The
+ /// reservations from the primary data source are placed before the
+ /// reservations from the alternate source.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAll4(const SubnetID& subnet_id, const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAll4 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects representing reservations
+ /// in a specified subnet as documented in the
+ /// @c BaseHostDataSource::getAll6
+ ///
+ /// It retrieves reservations from both primary and alternate host data
+ /// source as a single collection of @c Host objects, i.e. if matching
+ /// reservations are in both sources, all of them are returned. The
+ /// reservations from the primary data source are placed before the
+ /// reservations from the alternate source.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAll6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAllbyHostname(const std::string& hostname,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAllbyHostname compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAllbyHostname(const std::string& hostname) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAllbyHostname4 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAllbyHostname6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Returns range of hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns a page of @c Host objects representing
+ /// reservations in a specified subnet as documented in the
+ /// @c BaseHostDataSource::getPage4
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of hosts by specifying zero index and id
+ /// as the beginning of the range.
+ /// - Index and last id of the returned range should be used as
+ /// starting index and id for the next page in the subsequent call.
+ /// - All returned hosts are from the same source so if the number of
+ /// hosts returned is lower than the page size, it does not indicate
+ /// that the last page has been retrieved.
+ /// - If there are no hosts returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns a page of @c Host objects representing
+ /// reservations in a specified subnet as documented in the
+ /// @c BaseHostDataSource::getPage6
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of hosts by specifying zero index and id
+ /// as the beginning of the range.
+ /// - Index and last id of the returned range should be used as
+ /// starting index and id for the next page in the subsequent call.
+ /// - All returned hosts are from the same source so if the number of
+ /// hosts returned is lower than the page size, it does not indicate
+ /// that the last page has been retrieved.
+ /// - If there are no hosts returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects representing
+ /// reservations as documented in the @c BaseHostDataSource::getPage4
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of hosts by specifying zero index and id
+ /// as the beginning of the range.
+ /// - Index and last id of the returned range should be used as
+ /// starting index and id for the next page in the subsequent call.
+ /// - All returned hosts are from the same source so if the number of
+ /// hosts returned is lower than the page size, it does not indicate
+ /// that the last page has been retrieved.
+ /// - If there are no hosts returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects representing
+ /// reservations as documented in the @c BaseHostDataSource::getPage6
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of hosts by specifying zero index and id
+ /// as the beginning of the range.
+ /// - Index and last id of the returned range should be used as
+ /// starting index and id for the next page in the subsequent call.
+ /// - All returned hosts are from the same source so if the number of
+ /// hosts returned is lower than the page size, it does not indicate
+ /// that the last page has been retrieved.
+ /// - If there are no hosts returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Host collection (may be empty).
+ virtual ConstHostCollection
+ getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected to
+ /// different subnets.
+ ///
+ /// If matching reservations are both in the primary and the alternate
+ /// data source, all of them are returned. The reservations from the
+ /// primary data source are placed before the reservations from the
+ /// alternate source.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAll4(const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAll4 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAll4(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns any host connected to the IPv4 subnet.
+ ///
+ /// This method returns a single reservation for a particular host as
+ /// documented in the @c BaseHostDataSource::get4 even when the
+ /// reservation is marked as from negative caching. This allows to
+ /// monitor negative caching.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ ConstHostPtr
+ get4Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::get4Any compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostPtr
+ get4Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// This method returns a single reservation for a particular host as
+ /// documented in the @c BaseHostDataSource::get4.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ ConstHostPtr
+ get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::get4 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// This method returns a single reservation for the particular host
+ /// (identified by the HW address or DUID) as documented in the
+ /// @c BaseHostDataSource::get4.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Const @c Host object using a specified IPv4 address.
+ ConstHostPtr
+ get4(const SubnetID& subnet_id, const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::get4 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv4 subnet and having
+ /// a reservation for a specified address.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv4 address within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv4 reservation. In that case, the number of hosts returned
+ /// by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv4 address in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv4 address is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv4 address is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAll4 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns any host connected to the IPv6 subnet.
+ ///
+ /// This method returns a host connected to the IPv6 subnet as described
+ /// in the @c BaseHostDataSource::get6 even when the
+ /// reservation is marked as from negative caching. This allows to
+ /// monitor negative caching.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ ConstHostPtr
+ get6Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::get6Any compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostPtr
+ get6Any(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// This method returns a host connected to the IPv6 subnet as described
+ /// in the @c BaseHostDataSource::get6.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ ConstHostPtr
+ get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::get6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// This method returns a host using specified IPv6 prefix, as described
+ /// in the @c BaseHostDataSource::get6.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Const @c Host object using a specified IPv6 prefix.
+ ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::get6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+ /// @brief Returns a host from specific subnet and reserved address.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Const @c host object that has a reservation for specified address.
+ ConstHostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& addr,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::get6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& addr) const;
+
+ /// @brief Returns all hosts connected to the IPv6 subnet and having
+ /// a reservation for a specified address or delegated prefix (lease).
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix. In that case, the number of hosts
+ /// returned by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief The @c HostMgr::getAll6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief The @c HostMgr::getAll6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on all host sources.
+ virtual ConstHostCollection
+ getAll6(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv6 address/prefix.
+ ///
+ /// This method may return multiple @c Host objects if they are connected to
+ /// different subnets or if there are multiple hosts with the same IPv6 address/prefix.
+ ///
+ /// If matching reservations are both in the primary and the alternate
+ /// data source, all of them are returned. The reservations from the
+ /// primary data source are placed before the reservations from the
+ /// alternate source.
+ ///
+ /// @param address IPv6 address for which the @c Host object is searched.
+ /// @param target The host data source being a target of the operation.
+ ///
+ /// @return Collection of const @c Host objects.
+ ConstHostCollection
+ getAll6(const asiolink::IOAddress& address,
+ const HostMgrOperationTarget target) const;
+
+ /// @brief Adds a new host to the alternate data source.
+ ///
+ /// This method will throw an exception if no alternate data source is
+ /// in use.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ /// @param target The host data source being a target of the operation.
+ void add(const HostPtr& host, const HostMgrOperationTarget target);
+
+ /// @brief The @c HostMgr::add compatible with @c BaseHostDataSource
+ /// interfaces. Operates on alternate host sources only.
+ virtual void add(const HostPtr& host);
+
+ /// @brief Attempts to delete hosts by address.
+ ///
+ /// It deletes hosts from the first alternate source in which at least
+ /// one matching host is found. In unlikely case that the hosts having
+ /// the same IP address exist in other alternate sources, the hosts
+ /// from these other sources are not deleted.
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @param target The host data source being a target of the operation.
+ /// @return true if deletion was successful, false otherwise.
+ bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr,
+ const HostMgrOperationTarget target);
+
+ /// @brief The @c HostMgr::del compatible with @c BaseHostDataSource
+ /// interfaces. Operates on alternate host sources only.
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier,
+ /// identifier-type, operation-target)
+ ///
+ /// This method supports v4 only.
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param target The host data source being a target of the operation.
+ /// @return true if deletion was successful, false otherwise.
+ bool
+ del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len,
+ const HostMgrOperationTarget target);
+
+ /// @brief The @c HostMgr::del4 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on alternate host sources only.
+ virtual bool
+ del4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier,
+ /// identifier-type, operation-target)
+ ///
+ /// This method supports v6 only.
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param target The host data source being a target of the operation.
+ /// @return true if deletion was successful, false otherwise.
+ bool
+ del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len,
+ const HostMgrOperationTarget target);
+
+ /// @brief The @c HostMgr::del6 compatible with @c BaseHostDataSource
+ /// interfaces. Operates on alternate host sources only.
+ virtual bool
+ del6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Implements @ref BaseHostDataSource::update() for alternate sources.
+ ///
+ /// Attempts to update an existing host entry.
+ ///
+ /// @param host the host up to date with the requested changes
+ /// @param target The host data source being a target of the operation.
+ void update(HostPtr const& host, const HostMgrOperationTarget target);
+
+ /// @brief The @c HostMgr::update with default operation target. Operates
+ /// on alternate host sources only.
+ void update(HostPtr const& host);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("host_mgr"));
+ }
+
+ /// @brief Returns the host data source list.
+ ///
+ /// @return reference to the host data source list.
+ HostDataSourceList& getHostDataSourceList() {
+ return (alternate_sources_);
+ }
+
+ /// @brief Returns the first host data source.
+ ///
+ /// May return NULL if the host data source list is empty.
+ /// @return pointer to the first host data source (or NULL).
+ HostDataSourcePtr getHostDataSource() const;
+
+ /// @brief Returns the negative caching flag.
+ ///
+ /// @return the negative caching flag.
+ bool getNegativeCaching() const {
+ return (negative_caching_);
+ }
+
+ /// @brief Sets the negative caching flag.
+ ///
+ void setNegativeCaching(bool negative_caching) {
+ negative_caching_ = negative_caching;
+ }
+
+ /// @brief Returns the disable single query flag.
+ ///
+ /// @return the disable single query flag.
+ bool getDisableSingleQuery() const {
+ return (disable_single_query_);
+ }
+
+ /// @brief Sets the disable single query flag.
+ ///
+ void setDisableSingleQuery(bool disable_single_query) {
+ disable_single_query_ = disable_single_query;
+ }
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// Calling this function on @c HostMgr causes the manager to attempt to
+ /// set this flag on all backends in use.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return true if the new setting was accepted by the backend or false
+ /// otherwise.
+ virtual bool setIPReservationsUnique(const bool unique);
+
+ /// @brief Returns the boolean flag indicating if the IP reservations
+ /// must be unique or can be non-unique.
+ ///
+ /// @return true if IP reservations must be unique or false if IP
+ /// reservations can be non-unique.
+ bool getIPReservationsUnique() const {
+ return (ip_reservations_unique_);
+ }
+
+ /// @brief Sets IO service to be used by the Host Manager.
+ ///
+ /// @param io_service IOService object, used for all ASIO operations.
+ static void setIOService(const isc::asiolink::IOServicePtr& io_service) {
+ io_service_ = io_service;
+ }
+
+ /// @brief Returns pointer to the IO service.
+ static isc::asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+protected:
+
+ /// @brief The negative caching flag.
+ ///
+ /// When true and the first backend is a cache
+ /// negative answers are inserted in the cache.
+ /// This works for get[46] for a subnet and an identifier.
+ bool negative_caching_;
+
+ /// @brief The disable single query flag.
+ ///
+ /// When true prevent the use of lookup methods returning a collection
+ /// aka single queries when methods returning a host object are usable
+ /// instead.
+ bool disable_single_query_;
+
+ /// @brief Cache an answer.
+ ///
+ /// @param host Pointer to the missed host.
+ virtual void cache(ConstHostPtr host) const;
+
+ /// @brief Cache a negative answer.
+ ///
+ /// @param ipv4_subnet_id Identifier of the IPv4 subnet.
+ /// @param ipv6_subnet_id Identifier of the IPv6 subnet.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of the Identifier.
+ /// @param identifier_len Identifier length.
+ virtual void cacheNegative(const SubnetID& ipv4_subnet_id,
+ const SubnetID& ipv6_subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+private:
+
+ /// @brief Indicates if backends are running in the mode in which IP
+ /// reservations must be unique (true) or non-unique (false).
+ ///
+ /// This flag is set to false only after calling @c setIPReservationsUnique
+ /// with the @c unique value set to false and after this setting was
+ /// successfully applied to all backends.
+ bool ip_reservations_unique_;
+
+ /// @brief Private default constructor.
+ HostMgr() : negative_caching_(false), disable_single_query_(false),
+ ip_reservations_unique_(true) { }
+
+ /// @brief List of alternate host data sources.
+ HostDataSourceList alternate_sources_;
+
+ /// @brief Pointer to the cache.
+ CacheHostDataSourcePtr cache_ptr_;
+
+ /// @brief Returns a pointer to the currently used instance of the
+ /// @c HostMgr.
+ static boost::scoped_ptr<HostMgr>& getHostMgrPtr();
+
+ /// The IOService object, used for all ASIO operations.
+ static isc::asiolink::IOServicePtr io_service_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // HOST_MGR_H
diff --git a/src/lib/dhcpsrv/hosts_log.cc b/src/lib/dhcpsrv/hosts_log.cc
new file mode 100644
index 0000000..e5602dd
--- /dev/null
+++ b/src/lib/dhcpsrv/hosts_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2015-2018 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/.
+
+/// @brief Defines the logger used by the @c isc::dhcp::HostMgr
+
+#include <config.h>
+
+#include "dhcpsrv/hosts_log.h"
+
+namespace isc {
+namespace dhcp {
+
+extern const int HOSTS_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int HOSTS_DBG_RESULTS = isc::log::DBGLVL_TRACE_BASIC_DATA;
+extern const int HOSTS_DBG_TRACE_DETAIL = isc::log::DBGLVL_TRACE_DETAIL;
+extern const int HOSTS_DBG_TRACE_DETAIL_DATA =
+ isc::log::DBGLVL_TRACE_DETAIL_DATA;
+
+isc::log::Logger hosts_logger("hosts");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/hosts_log.h b/src/lib/dhcpsrv/hosts_log.h
new file mode 100644
index 0000000..4d1dc3d
--- /dev/null
+++ b/src/lib/dhcpsrv/hosts_log.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2015-2018 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 HOSTS_LOG_H
+#define HOSTS_LOG_H
+
+#include <dhcpsrv/hosts_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief Logging levels for the host reservations management.
+///
+/// Defines the levels used to output debug messages during the host
+/// reservations management, i.e. retrieving and adding host reservations.
+/// Note that higher numbers equate to more verbose(and detailed) output.
+
+/// @brief Traces normal operations
+///
+/// An example of the normal operation is the call to one of the functions
+/// which retrieve the reservations or add new reservation.
+extern const int HOSTS_DBG_TRACE;
+
+/// @brief Records the results of the lookups
+///
+/// Messages logged at this level will typically contain summary of the
+/// data retrieved.
+extern const int HOSTS_DBG_RESULTS;
+
+/// @brief Record detailed traces
+///
+/// Messages logged at this level will log detailed tracing information.
+extern const int HOSTS_DBG_TRACE_DETAIL;
+
+/// @brief Records detailed results of lookups.
+///
+/// Messages logged at this level will contain detailed results.
+extern const int HOSTS_DBG_TRACE_DETAIL_DATA;
+
+///@}
+
+/// @brief Logger for the @c HostMgr and the code it calls.
+///
+/// Define the logger used to log messages in @c HostMgr and the code it
+/// calls to manage host reservations.
+extern isc::log::Logger hosts_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // HOSTS_LOG_H
diff --git a/src/lib/dhcpsrv/hosts_messages.cc b/src/lib/dhcpsrv/hosts_messages.cc
new file mode 100644
index 0000000..4b5b75f
--- /dev/null
+++ b/src/lib/dhcpsrv/hosts_messages.cc
@@ -0,0 +1,159 @@
+// File created from ../../../src/lib/dhcpsrv/hosts_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID HOSTS_BACKENDS_REGISTERED = "HOSTS_BACKENDS_REGISTERED";
+extern const isc::log::MessageID HOSTS_BACKEND_DEREGISTER = "HOSTS_BACKEND_DEREGISTER";
+extern const isc::log::MessageID HOSTS_BACKEND_REGISTER = "HOSTS_BACKEND_REGISTER";
+extern const isc::log::MessageID HOSTS_CFG_ADD_HOST = "HOSTS_CFG_ADD_HOST";
+extern const isc::log::MessageID HOSTS_CFG_CACHE_HOST_DATA_SOURCE = "HOSTS_CFG_CACHE_HOST_DATA_SOURCE";
+extern const isc::log::MessageID HOSTS_CFG_CLOSE_HOST_DATA_SOURCE = "HOSTS_CFG_CLOSE_HOST_DATA_SOURCE";
+extern const isc::log::MessageID HOSTS_CFG_DEL = "HOSTS_CFG_DEL";
+extern const isc::log::MessageID HOSTS_CFG_DEL4 = "HOSTS_CFG_DEL4";
+extern const isc::log::MessageID HOSTS_CFG_DEL6 = "HOSTS_CFG_DEL6";
+extern const isc::log::MessageID HOSTS_CFG_DEL_ALL_SUBNET4 = "HOSTS_CFG_DEL_ALL_SUBNET4";
+extern const isc::log::MessageID HOSTS_CFG_DEL_ALL_SUBNET6 = "HOSTS_CFG_DEL_ALL_SUBNET6";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL = "HOSTS_CFG_GET_ALL";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS4 = "HOSTS_CFG_GET_ALL_ADDRESS4";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS4_COUNT = "HOSTS_CFG_GET_ALL_ADDRESS4_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS4_HOST = "HOSTS_CFG_GET_ALL_ADDRESS4_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS6 = "HOSTS_CFG_GET_ALL_ADDRESS6";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS6_COUNT = "HOSTS_CFG_GET_ALL_ADDRESS6_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS6_HOST = "HOSTS_CFG_GET_ALL_ADDRESS6_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_COUNT = "HOSTS_CFG_GET_ALL_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOST = "HOSTS_CFG_GET_ALL_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME = "HOSTS_CFG_GET_ALL_HOSTNAME";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_COUNT = "HOSTS_CFG_GET_ALL_HOSTNAME_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_HOST = "HOSTS_CFG_GET_ALL_HOSTNAME_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4 = "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_COUNT = "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_HOST = "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6 = "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_COUNT = "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_HOST = "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_IDENTIFIER = "HOSTS_CFG_GET_ALL_IDENTIFIER";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT = "HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_IDENTIFIER_HOST = "HOSTS_CFG_GET_ALL_IDENTIFIER_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4 = "HOSTS_CFG_GET_ALL_SUBNET_ID4";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT = "HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST = "HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6 = "HOSTS_CFG_GET_ALL_SUBNET_ID6";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT = "HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST = "HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4 = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6 = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT";
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST = "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_PREFIX = "HOSTS_CFG_GET_ONE_PREFIX";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_PREFIX_HOST = "HOSTS_CFG_GET_ONE_PREFIX_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_PREFIX_NULL = "HOSTS_CFG_GET_ONE_PREFIX_NULL";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4 = "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST = "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL = "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6 = "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST = "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL = "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER = "HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST = "HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST";
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL = "HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4 = "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER = "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST = "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL = "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_PREFIX = "HOSTS_MGR_ALTERNATE_GET6_PREFIX";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6 = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL = "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4 = "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4";
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6 = "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6";
+extern const isc::log::MessageID HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED = "HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED";
+
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "HOSTS_BACKENDS_REGISTERED", "the following host backend types are available: %1",
+ "HOSTS_BACKEND_DEREGISTER", "deregistered host backend type: %1",
+ "HOSTS_BACKEND_REGISTER", "registered host backend type: %1",
+ "HOSTS_CFG_ADD_HOST", "add the host for reservations: %1",
+ "HOSTS_CFG_CACHE_HOST_DATA_SOURCE", "get host cache data source: %1",
+ "HOSTS_CFG_CLOSE_HOST_DATA_SOURCE", "Closing host data source: %1",
+ "HOSTS_CFG_DEL", "deleted %1 host(s) having %2 IPv6 reservation(s) for subnet id %3 and address %4",
+ "HOSTS_CFG_DEL4", "deleted %1 host(s) for subnet id %2 and identifier %3",
+ "HOSTS_CFG_DEL6", "deleted %1 host(s) having %2 IPv6 reservation(s) for subnet id %3 and identifier %4",
+ "HOSTS_CFG_DEL_ALL_SUBNET4", "deleted all %1 host(s) for subnet id %2",
+ "HOSTS_CFG_DEL_ALL_SUBNET6", "deleted all %1 host(s) having %2 IPv6 reservation(s) for subnet id %3",
+ "HOSTS_CFG_GET_ALL", "get all hosts with reservations",
+ "HOSTS_CFG_GET_ALL_ADDRESS4", "get all hosts with reservations for IPv4 address %1",
+ "HOSTS_CFG_GET_ALL_ADDRESS4_COUNT", "using address %1, found %2 host(s)",
+ "HOSTS_CFG_GET_ALL_ADDRESS4_HOST", "using address %1 found host: %2",
+ "HOSTS_CFG_GET_ALL_ADDRESS6", "get all hosts with reservations for IPv6 address %1",
+ "HOSTS_CFG_GET_ALL_ADDRESS6_COUNT", "using address %1, found %2 host(s)",
+ "HOSTS_CFG_GET_ALL_ADDRESS6_HOST", "using address %1 found host: %2",
+ "HOSTS_CFG_GET_ALL_COUNT", "found %1 host(s)",
+ "HOSTS_CFG_GET_ALL_HOST", "found host: %1",
+ "HOSTS_CFG_GET_ALL_HOSTNAME", "get all hosts with reservations for hostname %1",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_COUNT", "using hostname %1, found %2 host(s)",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_HOST", "using hostname %1, found host: %2",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4", "get all hosts with reservations for hostname %1 and IPv4 subnet %2",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_COUNT", "using hostname %1 and IPv4 subnet %2, found %3 host(s)",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_HOST", "using hostname %1 and IPv4 subnet %2, found host: %3",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6", "get all hosts with reservations for hostname %1 and IPv6 subnet %2",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_COUNT", "using hostname %1 and IPv6 subnet %2, found %3 host(s)",
+ "HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_HOST", "using hostname %1 and IPv6 subnet %2, found host: %3",
+ "HOSTS_CFG_GET_ALL_IDENTIFIER", "get all hosts with reservations using identifier: %1",
+ "HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT", "using identifier %1, found %2 host(s)",
+ "HOSTS_CFG_GET_ALL_IDENTIFIER_HOST", "using identifier: %1, found host: %2",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID4", "get all hosts with reservations for IPv4 subnet %1",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT", "using IPv4 subnet %1, found %2 host(s)",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST", "using IPv4 subnet %1, found host: %2",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID6", "get all hosts with reservations for IPv6 subnet %1",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT", "using IPv6 subnet %1, found %2 host(s)",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST", "using IPv6 subnet %1, found host: %2",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4", "get all hosts with reservations for subnet id %1 and IPv4 address %2",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT", "using IPv4 subnet %1 and IPv4 address %2, found %3 host(s)",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST", "using IPv4 subnet %1 and IPv4 address %2, found host: %3",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6", "get all hosts with reservations for subnet id %1 and IPv6 address %2",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT", "using subnet id %1 and address %2, found %3 host(s)",
+ "HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST", "using subnet id %1 and address %2, found host: %3",
+ "HOSTS_CFG_GET_ONE_PREFIX", "get one host with reservation for prefix %1/%2",
+ "HOSTS_CFG_GET_ONE_PREFIX_HOST", "using prefix %1/%2, found host: %3",
+ "HOSTS_CFG_GET_ONE_PREFIX_NULL", "host not found using prefix %1/%2",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4", "get one host with reservation for subnet id %1 and IPv4 address %2",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST", "using subnet id %1 and address %2, found host: %3",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL", "host not found using subnet id %1 and address %2",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6", "get one host with reservation for subnet id %1 and having IPv6 address %2",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST", "using subnet id %1 and address %2, found host: %3",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL", "host not found using subnet id %1 and address %2",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER", "get one host with %1 reservation for subnet id %2, identified by %3",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST", "using subnet id %1 and identifier %2, found host: %3",
+ "HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL", "host not found using subnet id %1 and identifier %2",
+ "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4", "trying alternate sources for host using subnet id %1 and address %2",
+ "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER", "get one host with IPv4 reservation for subnet id %1, identified by %2",
+ "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST", "using subnet id %1 and identifier %2, found in %3 host: %4",
+ "HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL", "host not found using subnet id %1 and identifier %2",
+ "HOSTS_MGR_ALTERNATE_GET6_PREFIX", "trying alternate sources for host using prefix %1/%2",
+ "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6", "trying alternate sources for host using subnet id %1 and IPv6 address %2",
+ "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER", "get one host with IPv6 reservation for subnet id %1, identified by %2",
+ "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST", "using subnet id %1 and identifier %2, found in %3 host: %4",
+ "HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL", "host not found using subnet id %1 and identifier %2",
+ "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4", "trying alternate sources for hosts using subnet id %1 and address %2",
+ "HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6", "trying alternate sources for hosts using subnet id %1 and address %2",
+ "HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED", "host data source %1 does not support the mode in which IP reservations are non-unique",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/dhcpsrv/hosts_messages.h b/src/lib/dhcpsrv/hosts_messages.h
new file mode 100644
index 0000000..f40666e
--- /dev/null
+++ b/src/lib/dhcpsrv/hosts_messages.h
@@ -0,0 +1,83 @@
+// File created from ../../../src/lib/dhcpsrv/hosts_messages.mes
+
+#ifndef HOSTS_MESSAGES_H
+#define HOSTS_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID HOSTS_BACKENDS_REGISTERED;
+extern const isc::log::MessageID HOSTS_BACKEND_DEREGISTER;
+extern const isc::log::MessageID HOSTS_BACKEND_REGISTER;
+extern const isc::log::MessageID HOSTS_CFG_ADD_HOST;
+extern const isc::log::MessageID HOSTS_CFG_CACHE_HOST_DATA_SOURCE;
+extern const isc::log::MessageID HOSTS_CFG_CLOSE_HOST_DATA_SOURCE;
+extern const isc::log::MessageID HOSTS_CFG_DEL;
+extern const isc::log::MessageID HOSTS_CFG_DEL4;
+extern const isc::log::MessageID HOSTS_CFG_DEL6;
+extern const isc::log::MessageID HOSTS_CFG_DEL_ALL_SUBNET4;
+extern const isc::log::MessageID HOSTS_CFG_DEL_ALL_SUBNET6;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS4;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS4_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS4_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS6;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS6_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_ADDRESS6_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_IDENTIFIER;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_IDENTIFIER_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT;
+extern const isc::log::MessageID HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_PREFIX;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_PREFIX_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_PREFIX_NULL;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST;
+extern const isc::log::MessageID HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_PREFIX;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4;
+extern const isc::log::MessageID HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6;
+extern const isc::log::MessageID HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // HOSTS_MESSAGES_H
diff --git a/src/lib/dhcpsrv/hosts_messages.mes b/src/lib/dhcpsrv/hosts_messages.mes
new file mode 100644
index 0000000..fb18cde
--- /dev/null
+++ b/src/lib/dhcpsrv/hosts_messages.mes
@@ -0,0 +1,340 @@
+# Copyright (C) 2015-2023 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/.
+
+$NAMESPACE isc::dhcp
+
+% HOSTS_BACKENDS_REGISTERED the following host backend types are available: %1
+This informational message lists all possible host backends that could
+be used in hosts-database[s].
+
+% HOSTS_BACKEND_DEREGISTER deregistered host backend type: %1
+This debug message is issued when a backend factory was deregistered.
+It is no longer possible to use host backend of this type.
+
+% HOSTS_BACKEND_REGISTER registered host backend type: %1
+This debug message is issued when a backend factory was successfully
+registered. It is now possible to use host backend of this type.
+
+% HOSTS_CFG_ADD_HOST add the host for reservations: %1
+This debug message is issued when new host (with reservations) is added to
+the server's configuration. The argument describes the host and its
+reservations in detail.
+
+% HOSTS_CFG_CACHE_HOST_DATA_SOURCE get host cache data source: %1
+This informational message is issued when a host cache data source is
+detected by the host manager.
+
+% HOSTS_CFG_CLOSE_HOST_DATA_SOURCE Closing host data source: %1
+This is a normal message being printed when the server closes host data
+source connection.
+
+% HOSTS_CFG_DEL deleted %1 host(s) having %2 IPv6 reservation(s) for subnet id %3 and address %4
+This debug message is issued when reservations are deleted for the specified
+subnet and address. The first argument specifies how many hosts have been
+deleted. The second argument specifies how many reservations have been deleted.
+The third argument is the subnet identifier. The fourth argument is the IP
+address.
+
+% HOSTS_CFG_DEL4 deleted %1 host(s) for subnet id %2 and identifier %3
+This debug message is issued when IPv4 reservations are deleted for the specified
+subnet and identifier. The first argument specifies how many hosts have been
+deleted. The second argument is the subnet identifier. The third argument is
+the identifier.
+
+% HOSTS_CFG_DEL6 deleted %1 host(s) having %2 IPv6 reservation(s) for subnet id %3 and identifier %4
+This debug message is issued when IPv6 reservations are deleted for the
+specified subnet and identifier. The first argument specifies how many hosts
+have been deleted. The second argument specifies how many reservations have
+been deleted. The third argument is the subnet identifier. The fourth argument
+is the identifier.
+
+% HOSTS_CFG_DEL_ALL_SUBNET4 deleted all %1 host(s) for subnet id %2
+This debug message is issued when all IPv4 reservations are deleted for
+the specified subnet. The first argument specifies how many reservations
+have been deleted. The second argument is the subnet identifier.
+
+% HOSTS_CFG_DEL_ALL_SUBNET6 deleted all %1 host(s) having %2 IPv6 reservation(s) for subnet id %3
+This debug message is issued when all IPv6 reservations are deleted for
+the specified subnet. The first argument specifies how many hosts
+have been deleted. The second argument specifies how many IPv6
+(addresses and prefixes) reservations have been deleted. The third argument is
+the subnet identifier.
+
+% HOSTS_CFG_GET_ALL get all hosts with reservations
+This debug message is issued when starting to retrieve all hosts.
+
+% HOSTS_CFG_GET_ALL_ADDRESS4 get all hosts with reservations for IPv4 address %1
+This debug message is issued when starting to retrieve all hosts, holding the
+reservation for the specific IPv4 address, from the configuration. The
+argument specifies the IPv4 address used to search the hosts.
+
+% HOSTS_CFG_GET_ALL_ADDRESS4_COUNT using address %1, found %2 host(s)
+This debug message logs the number of hosts found using the specified
+IPv4 address. The arguments specify the IPv4 address used and the number
+of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_ADDRESS4_HOST using address %1 found host: %2
+This debug message is issued when found host with the reservation
+for the specified IPv4 address. The arguments specify the IPv4 address
+and the detailed description of the host found.
+
+% HOSTS_CFG_GET_ALL_ADDRESS6 get all hosts with reservations for IPv6 address %1
+This debug message is issued when starting to retrieve all hosts, holding the
+reservation for the specific IPv6 address, from the configuration.
+The argument specifies the IPv6 address used to search the hosts.
+
+% HOSTS_CFG_GET_ALL_ADDRESS6_COUNT using address %1, found %2 host(s)
+This debug message logs the number of hosts found using the specified
+IPv6 address. The arguments specify the IPv6 address used and the number
+of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_ADDRESS6_HOST using address %1 found host: %2
+This debug message is issued when found host with the reservation
+for the specified IPv6 address. The arguments specify the IPv6 address
+and the detailed description of the host found.
+
+% HOSTS_CFG_GET_ALL_COUNT found %1 host(s)
+This debug message include the details of the host found. The argument
+specifies the number of hosts found.
+
+% HOSTS_CFG_GET_ALL_HOST found host: %1
+This debug message includes the details of the host found. The argument
+specifies found host details.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME get all hosts with reservations for hostname %1
+This debug message is issued when starting to retrieve all hosts with
+the specific hostname. The argument specifies hostname.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_COUNT using hostname %1, found %2 host(s)
+This debug message include the details of the host found using the
+hostname. The arguments specify hostname and the number of hosts found
+respectively.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_HOST using hostname %1, found host: %2
+This debug message includes the details of the host found using the hostname.
+The arguments specify hostname and found host details respectively.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4 get all hosts with reservations for hostname %1 and IPv4 subnet %2
+This debug message is issued when starting to retrieve all hosts with
+the specific hostname connected to the specific DHCPv4 subnet. The argument
+specifies hostname and subnet id.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_COUNT using hostname %1 and IPv4 subnet %2, found %3 host(s)
+This debug message include the details of the host found using the
+hostname and the DHCPv4 subnet id. The arguments specify hostname,
+subnet id and the number of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID4_HOST using hostname %1 and IPv4 subnet %2, found host: %3
+This debug message includes the details of the host found using the
+hostname and the DHCPv4 subnet id. The arguments specify hostname,
+subnet id and found host details respectively.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6 get all hosts with reservations for hostname %1 and IPv6 subnet %2
+This debug message is issued when starting to retrieve all hosts with
+the specific hostname connected to the specific DHCPv6 subnet. The argument
+specifies hostname and subnet id.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_COUNT using hostname %1 and IPv6 subnet %2, found %3 host(s)
+This debug message include the details of the host found using the
+hostname and the DHCPv6 subnet id. The arguments specify hostname,
+subnet id and the number of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_HOSTNAME_SUBNET_ID6_HOST using hostname %1 and IPv6 subnet %2, found host: %3
+This debug message includes the details of the host found using the
+hostname and the DHCPv6 subnet id. The arguments specify hostname,
+subnet id and found host details respectively.
+
+% HOSTS_CFG_GET_ALL_IDENTIFIER get all hosts with reservations using identifier: %1
+This debug message is issued when starting to retrieve reservations for all hosts
+identified by HW address or DUID. The argument holds both the identifier
+type and the value.
+
+% HOSTS_CFG_GET_ALL_IDENTIFIER_COUNT using identifier %1, found %2 host(s)
+This debug message logs the number of hosts found using the specified
+identifier. The arguments specify the identifier used and the number
+of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_IDENTIFIER_HOST using identifier: %1, found host: %2
+This debug message is issued when found host identified by the specific
+identifier. The arguments specify the identifier and the detailed
+description of the host found.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID4 get all hosts with reservations for IPv4 subnet %1
+This debug message is issued when starting to retrieve all hosts connected to
+the specific DHCPv4 subnet. The argument specifies subnet id.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID4_COUNT using IPv4 subnet %1, found %2 host(s)
+This debug message include the details of the host found using the DHCPv4
+subnet id. The arguments specify subnet id and the number of hosts found
+respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID4_HOST using IPv4 subnet %1, found host: %2
+This debug message includes the details of the host found using the DHCPv4
+subnet id. The arguments specify subnet id and found host details respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID6 get all hosts with reservations for IPv6 subnet %1
+This debug message is issued when starting to retrieve all hosts connected to
+the specific DHCPv6 subnet. The argument specifies subnet id.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID6_COUNT using IPv6 subnet %1, found %2 host(s)
+This debug message include the details of the host found using the DHCPv6
+subnet id. The arguments specify subnet id and the number of hosts found
+respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID6_HOST using IPv6 subnet %1, found host: %2
+This debug message includes the details of the host found using the DHCPv6
+subnet id. The arguments specify subnet id and found host details respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4 get all hosts with reservations for subnet id %1 and IPv4 address %2
+This debug message is issued when starting to retrieve all hosts having
+the reservation for the given IPv4 address within the given subnet. The
+first argument specifies subnet identifier. The second argument specifies
+the IPv4 address for which the reservation is to be returned.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_COUNT using IPv4 subnet %1 and IPv4 address %2, found %3 host(s)
+This debug message logs the number of hosts found having the reservation
+for the specified IPv4 address within the specified subnet. The first
+argument specifies the subnet identifier. The second argument specifies
+the reserved IPv4 address. The third argument specifies the number of
+hosts found.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS4_HOST using IPv4 subnet %1 and IPv4 address %2, found host: %3
+This debug message is issued when found host having the reservation for
+the specified IPv4 address in the specified subnet. The first argument
+specifies the subnet identifier. The second argument specifies the reserved
+IPv4 address. The third argument specifies host details.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6 get all hosts with reservations for subnet id %1 and IPv6 address %2
+This debug message is issued when starting to retrieve all hosts connected to
+the specific subnet and having the specific IPv6 address reserved.
+The arguments specify subnet id and IPv6 address respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_COUNT using subnet id %1 and address %2, found %3 host(s)
+This debug message include the details of the host found using the
+subnet id and address. The arguments specify subnet id, address and
+the number of hosts found respectively.
+
+% HOSTS_CFG_GET_ALL_SUBNET_ID_ADDRESS6_HOST using subnet id %1 and address %2, found host: %3
+This debug message includes the details of the host found using the
+subnet id and address. The arguments specify subnet id, address and
+the number of hosts found respectively.
+found host details respectively.
+
+% HOSTS_CFG_GET_ONE_PREFIX get one host with reservation for prefix %1/%2
+This debug message is issued when starting to retrieve a host having a
+reservation for a specified prefix. The arguments specify a prefix and
+prefix length.
+
+% HOSTS_CFG_GET_ONE_PREFIX_HOST using prefix %1/%2, found host: %3
+This debug message includes the details of the host found using the
+specific prefix/prefix length. The arguments specify prefix, prefix
+length and host details respectively.
+
+% HOSTS_CFG_GET_ONE_PREFIX_NULL host not found using prefix %1/%2
+This debug message is issued when no host was found for a specified
+prefix and prefix length.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4 get one host with reservation for subnet id %1 and IPv4 address %2
+This debug message is issued when starting to retrieve a host connected to the
+specific subnet and having the specific IPv4 address reserved. The
+arguments specify subnet id and IPv4 address respectively.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_HOST using subnet id %1 and address %2, found host: %3
+This debug message logs the details of the host found using the
+subnet id and IPv4 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS4_NULL host not found using subnet id %1 and address %2
+This debug message is issued when no host was found for the specified
+subnet id and IPv4 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6 get one host with reservation for subnet id %1 and having IPv6 address %2
+This debug message is issued when starting to retrieve a host connected to the
+specific subnet and having the specific IPv6 address reserved. The
+arguments specify subnet id and IPv6 address respectively.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_HOST using subnet id %1 and address %2, found host: %3
+This debug message logs the details of the host found using the
+subnet id and IPv6 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_ADDRESS6_NULL host not found using subnet id %1 and address %2
+This debug message is issued when no host was found using the specified
+subnet if and IPv6 address.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER get one host with %1 reservation for subnet id %2, identified by %3
+This debug message is issued when starting to retrieve a host holding
+IPv4 or IPv6 reservations, which is connected to a specific subnet and
+is identified by a specific unique identifier. The first argument
+identifies if the IPv4 or IPv6 reservation is desired.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_HOST using subnet id %1 and identifier %2, found host: %3
+This debug message includes the details of a host found using a
+subnet id and specific host identifier.
+
+% HOSTS_CFG_GET_ONE_SUBNET_ID_IDENTIFIER_NULL host not found using subnet id %1 and identifier %2
+This debug message is issued when no host was found using the specified
+subnet id and host identifier.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_ADDRESS4 trying alternate sources for host using subnet id %1 and address %2
+This debug message is issued when the Host Manager doesn't find the
+host connected to the specific subnet and having the reservation for
+the specific IPv4 address, and it is starting to search for this host
+in alternate host data sources.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER get one host with IPv4 reservation for subnet id %1, identified by %2
+This debug message is issued when starting to retrieve a host holding
+IPv4 reservation, which is connected to a specific subnet and
+is identified by a specific unique identifier.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_HOST using subnet id %1 and identifier %2, found in %3 host: %4
+This debug message includes the details of a host returned by an
+alternate hosts data source using a subnet id and specific host
+identifier.
+
+% HOSTS_MGR_ALTERNATE_GET4_SUBNET_ID_IDENTIFIER_NULL host not found using subnet id %1 and identifier %2
+This debug message is issued when no host was found using the specified
+subnet id and host identifier.
+
+% HOSTS_MGR_ALTERNATE_GET6_PREFIX trying alternate sources for host using prefix %1/%2
+This debug message is issued when the Host Manager doesn't find the
+host connected to the specific subnet and having the reservation for
+the specified prefix, and it is starting to search for this host in
+alternate host data sources.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_ADDRESS6 trying alternate sources for host using subnet id %1 and IPv6 address %2
+This debug message is issued when the Host Manager doesn't find the
+host connected to the specific subnet and having the reservation for
+the specified IPv6 address, and it is starting to search for this
+host in alternate host data sources.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER get one host with IPv6 reservation for subnet id %1, identified by %2
+This debug message is issued when starting to retrieve a host holding
+IPv4 reservation, which is connected to a specific subnet and
+is identified by a specific unique identifier.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_HOST using subnet id %1 and identifier %2, found in %3 host: %4
+This debug message includes the details of a host returned by an
+alternate host data source using a subnet id and specific host
+identifier.
+
+% HOSTS_MGR_ALTERNATE_GET6_SUBNET_ID_IDENTIFIER_NULL host not found using subnet id %1 and identifier %2
+This debug message is issued when no host was found using the specified
+subnet id and host identifier.
+
+% HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS4 trying alternate sources for hosts using subnet id %1 and address %2
+This debug message is issued when the Host Manager is starting to search
+for hosts in alternate host data sources by subnet ID and IPv4 address.
+
+% HOSTS_MGR_ALTERNATE_GET_ALL_SUBNET_ID_ADDRESS6 trying alternate sources for hosts using subnet id %1 and address %2
+This debug message is issued when the Host Manager is starting to search
+for hosts in alternate host data sources by subnet ID and IPv6 address.
+
+% HOSTS_MGR_NON_UNIQUE_IP_UNSUPPORTED host data source %1 does not support the mode in which IP reservations are non-unique
+This warning message is issued when an administrator attempted to configure the
+server to allow multiple host reservations for the same IP address or prefix.
+Some host database backends may not support this mode of operation. In this
+case the administrator should stop using these backends or fall back to the
+default setting which requires that IP addresses are unique within a subnet.
+This setting is guaranteed to work for MySQL and Postgres host backends.
diff --git a/src/lib/dhcpsrv/images/pgsql_host_data_source.svg b/src/lib/dhcpsrv/images/pgsql_host_data_source.svg
new file mode 100644
index 0000000..944715d
--- /dev/null
+++ b/src/lib/dhcpsrv/images/pgsql_host_data_source.svg
@@ -0,0 +1,419 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="1211" height="918" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="144" y="237" width="2" height="212" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="16" y="447" width="130" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="13" y="234" width="131" height="213" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="79" y="246">PgSqlHostDataSource</text>
+ <line stroke="black" stroke-opacity="1" x1="13" y1="247" x2="144" y2="247" />
+ <line stroke="black" stroke-opacity="1" x1="13" y1="253" x2="144" y2="253" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="265">PgSqlHostDataSource()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="277">~PgSqlHostDataSource()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="289">getAll()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="301">getAll()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="313">getAll4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="325">get4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="337">get4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="349">get4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="361">get6()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="373">get6()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="385">get6()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="397">add()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="409">getType()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="421">getName()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="433">getDescription()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="16" y="445">getVersion()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="704" y="24" width="2" height="208" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="560" y="230" width="146" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="557" y="21" width="147" height="209" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="631" y="33">PgSqlExchange</text>
+ <line stroke="black" stroke-opacity="1" x1="557" y1="34" x2="704" y2="34" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="560" y="46">columns_</text>
+ <line stroke="black" stroke-opacity="1" x1="557" y1="47" x2="704" y2="47" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="560" y="59">PgSqlExchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="560" y="71">~PgSqlExchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="83">convertToDatabaseTime()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="95">convertToDatabaseTime()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="107">convertFromDatabaseTime()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="119">getRawColumnValue()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="131">getColumnLabel()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="143">getColumnValue()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="155">getColumnValue()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="167">getColumnValue()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="179">getIPv6Value()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="191">isColumnNull()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="203">getColumnValue()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="215">convertFromBytea()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="560" y="227">dumpRow()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="698" y="643" width="2" height="172" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="578" y="813" width="122" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="575" y="640" width="123" height="173" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="637" y="652">OptionProcessor</text>
+ <line stroke="black" stroke-opacity="1" x1="575" y1="653" x2="698" y2="653" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="665">universe_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="677">start_column_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="689">option_id_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="701">code_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="713">value_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="725">formatted_value_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="737">space_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="749">persistent_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="761">most_recent_option_id_</text>
+ <line stroke="black" stroke-opacity="1" x1="575" y1="762" x2="698" y2="762" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="774">OptionProcessor()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="786">clear()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="798">retrieveOption()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="578" y="810">setColumnNames()</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="90" y="475">impl_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="87" y="638">conn_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="76" y="831">conn_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="230" y="581">host_exchange_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="215" y="714">host_ipv6_exchange_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="210" y="755">host_ipv46_exchange_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="282" y="155">host_ipv6_reservation_exchange_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="391" y="425">host_option_exchange_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="513" y="563">opt_proc4_</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="513" y="601">opt_proc6_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="155" y="486" width="2" height="128" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="7" y="612" width="150" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="4" y="483" width="151" height="129" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="80" y="495">PgSqlHostDataSourceImpl</text>
+ <line stroke="black" stroke-opacity="1" x1="4" y1="496" x2="155" y2="496" />
+ <line stroke="black" stroke-opacity="1" x1="4" y1="502" x2="155" y2="502" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="514">PgSqlHostDataSourceImpl()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="526">~PgSqlHostDataSourceImpl()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="538">addStatement()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="550">addResv()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="562">addOption()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="574">addOptions()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="586">getHostCollection()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="598">getHost()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="7" y="610">getVersion()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="80" y1="482" x2="86" y2="476" />
+ <line stroke="black" stroke-opacity="1" x1="80" y1="482" x2="74" y2="476" />
+ <line stroke="black" stroke-opacity="1" x1="80" y1="450" x2="80" y2="482" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="126" y="650" width="2" height="160" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="12" y="808" width="116" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="9" y="647" width="117" height="161" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="68" y="659">PgSqlConnection</text>
+ <line stroke="black" stroke-opacity="1" x1="9" y1="660" x2="126" y2="660" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="12" y="672">DUPLICATE_KEY</text>
+ <line stroke="black" stroke-opacity="1" x1="9" y1="673" x2="126" y2="673" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="685">PgSqlConnection()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="697">~PgSqlConnection()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="709">prepareStatement()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="721">openDatabase()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="733">startTransaction()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="745">commit()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="757">rollback()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="769">compareError()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="781">checkStatementError()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="793">operator PGconn*()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="12" y="805">operator bool()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="497" y="548" width="2" height="76" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="321" y="622" width="178" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="318" y="545" width="179" height="77" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="408" y="557">PgSqlHostWithOptionsExchange</text>
+ <line stroke="black" stroke-opacity="1" x1="318" y1="558" x2="497" y2="558" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="321" y="570">OPTION_COLUMNS</text>
+ <line stroke="black" stroke-opacity="1" x1="318" y1="571" x2="497" y2="571" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="321" y="583">PgSqlHostWithOptionsExchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="321" y="595">clear()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="321" y="607">processRowData()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="321" y="619">getRequiredColumnsNum()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="78" y1="646" x2="83" y2="639" />
+ <line stroke="black" stroke-opacity="1" x1="78" y1="646" x2="71" y2="640" />
+ <line stroke="black" stroke-opacity="1" x1="77" y1="615" x2="78" y2="646" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="77,615 83,620 77,626 71,621" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="317" y1="583" x2="311" y2="576" />
+ <line stroke="black" stroke-opacity="1" x1="317" y1="583" x2="310" y2="588" />
+ <line stroke="black" stroke-opacity="1" x1="158" y1="582" x2="317" y2="583" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="158,582 164,576 169,582 163,588" />
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="198" y="596">&lt;&lt;:shared_ptr&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="118" y="841" width="2" height="64" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="14" y="903" width="106" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="11" y="838" width="107" height="65" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="65" y="850">PgSqlTransaction</text>
+ <line stroke="black" stroke-opacity="1" x1="11" y1="851" x2="118" y2="851" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="863">committed_</text>
+ <line stroke="black" stroke-opacity="1" x1="11" y1="864" x2="118" y2="864" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="876">PgSqlTransaction()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="888">~PgSqlTransaction()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="14" y="900">commit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="478" y="679" width="2" height="160" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="332" y="837" width="148" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="329" y="676" width="149" height="161" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="404" y="688">PgSqlHostIPv6Exchange</text>
+ <line stroke="black" stroke-opacity="1" x1="329" y1="689" x2="478" y2="689" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="332" y="701">RESERVATION_COLUMNS</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="713">reservation_id_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="725">address_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="737">prefix_len_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="749">type_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="761">iaid_index_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="773">most_recent_reservation_id_</text>
+ <line stroke="black" stroke-opacity="1" x1="329" y1="774" x2="478" y2="774" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="786">PgSqlHostIPv6Exchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="798">clear()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="810">getReservationId()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="822">retrieveReservation()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="332" y="834">processRowData()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="724" y="272" width="2" height="232" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="552" y="502" width="174" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="549" y="269" width="175" height="233" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="637" y="281">PgSqlHostExchange</text>
+ <line stroke="black" stroke-opacity="1" x1="549" y1="282" x2="724" y2="282" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="294">HOST_ID_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="306">DHCP_IDENTIFIER_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="318">DHCP_IDENTIFIER_TYPE_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="330">DHCP4_SUBNET_ID_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="342">DHCP6_SUBNET_ID_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="354">IPV4_ADDRESS_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="366">HOSTNAME_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="378">DHCP4_CLIENT_CLASSES_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="390">DHCP6_CLIENT_CLASSES_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="552" y="402">HOST_COLUMNS</text>
+ <line stroke="black" stroke-opacity="1" x1="549" y1="403" x2="724" y2="403" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="415">PgSqlHostExchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="427">~PgSqlHostExchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="439">clear()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="451">findAvailColumn()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="463">getHostId()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="475">createBindForSend()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="487">processRowData()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="552" y="499">retrieveHost()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="66" y1="811" x2="60" y2="817" />
+ <line stroke="black" stroke-opacity="1" x1="66" y1="811" x2="72" y2="817" />
+ <line stroke="black" stroke-opacity="1" x1="66" y1="837" x2="66" y2="811" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="406" y1="675" x2="406" y2="630" />
+ <line stroke="black" stroke-opacity="1" x1="407" y1="625" x2="400" y2="630" />
+ <line stroke="black" stroke-opacity="1" x1="407" y1="625" x2="412" y2="631" />
+ <line stroke="black" stroke-opacity="1" x1="400" y1="630" x2="412" y2="631" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="148" y1="615" x2="148" y2="720" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="148,615 154,621 148,627 142,621" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="328" y1="720" x2="322" y2="714" />
+ <line stroke="black" stroke-opacity="1" x1="328" y1="720" x2="322" y2="726" />
+ <line stroke="black" stroke-opacity="1" x1="148" y1="720" x2="328" y2="720" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="136" y1="615" x2="136" y2="758" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="136,615 142,621 136,627 130,621" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="328" y1="758" x2="322" y2="752" />
+ <line stroke="black" stroke-opacity="1" x1="328" y1="758" x2="322" y2="764" />
+ <line stroke="black" stroke-opacity="1" x1="136" y1="758" x2="328" y2="758" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="631" y1="268" x2="631" y2="239" />
+ <line stroke="black" stroke-opacity="1" x1="631" y1="233" x2="625" y2="239" />
+ <line stroke="black" stroke-opacity="1" x1="631" y1="233" x2="637" y2="239" />
+ <line stroke="black" stroke-opacity="1" x1="625" y1="239" x2="637" y2="239" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="409" y1="544" x2="409" y2="487" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="409" y1="487" x2="542" y2="487" />
+ <line stroke="black" stroke-opacity="1" x1="548" y1="487" x2="542" y2="481" />
+ <line stroke="black" stroke-opacity="1" x1="548" y1="487" x2="542" y2="493" />
+ <line stroke="black" stroke-opacity="1" x1="542" y1="481" x2="542" y2="493" />
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="155" y="731">&lt;&lt;:shared_ptr&gt;&gt;</text>
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="145" y="774">&lt;&lt;:shared_ptr&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="689" y="562" width="2" height="49" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="580" y="609" width="111" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="577" y="559" width="112" height="50" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="633" y="571">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="633" y="584">OptionProcessorPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="577" y1="585" x2="689" y2="585" />
+ <line stroke="black" stroke-opacity="1" x1="577" y1="591" x2="689" y2="591" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="359" y="83" width="2" height="52" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="187" y="133" width="174" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="184" y="80" width="175" height="53" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="272" y="92">PgSqlIPv6ReservationExchange</text>
+ <line stroke="black" stroke-opacity="1" x1="184" y1="93" x2="359" y2="93" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="187" y="105">RESRV_COLUMNS</text>
+ <line stroke="black" stroke-opacity="1" x1="184" y1="106" x2="359" y2="106" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="187" y="118">PgSqlIPv6ReservationExchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="187" y="130">createBindForSend()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="634" y1="639" x2="640" y2="633" />
+ <line stroke="black" stroke-opacity="1" x1="634" y1="639" x2="628" y2="633" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="634" y1="612" x2="634" y2="639" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="576" y1="582" x2="512" y2="582" />
+<ellipse fill="none" stroke="black" stroke-width="1" stroke-opacity="1" cx="506" cy="582" rx="5" ry="5" />
+ <line stroke="black" stroke-opacity="1" x1="501" y1="582" x2="511" y2="582" />
+ <line stroke="black" stroke-opacity="1" x1="506" y1="577" x2="506" y2="587" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="576" y1="567" x2="569" y2="561" />
+ <line stroke="black" stroke-opacity="1" x1="576" y1="567" x2="570" y2="573" />
+ <line stroke="black" stroke-opacity="1" x1="500" y1="568" x2="576" y2="567" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="500,568 505,561 511,567 506,573" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="576" y1="605" x2="570" y2="598" />
+ <line stroke="black" stroke-opacity="1" x1="576" y1="605" x2="569" y2="610" />
+ <line stroke="black" stroke-opacity="1" x1="500" y1="603" x2="576" y2="605" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="500,603 506,597 511,603 505,609" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="273" y1="79" x2="273" y2="55" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="273" y1="55" x2="550" y2="55" />
+ <line stroke="black" stroke-opacity="1" x1="556" y1="55" x2="550" y2="49" />
+ <line stroke="black" stroke-opacity="1" x1="556" y1="55" x2="550" y2="61" />
+ <line stroke="black" stroke-opacity="1" x1="550" y1="49" x2="550" y2="61" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="158" y1="493" x2="273" y2="493" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="158,493 164,487 170,493 164,499" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="273" y1="136" x2="267" y2="142" />
+ <line stroke="black" stroke-opacity="1" x1="273" y1="136" x2="279" y2="142" />
+ <line stroke="black" stroke-opacity="1" x1="273" y1="493" x2="273" y2="136" />
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="181" y="489">&lt;&lt;:shared_ptr&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="461" y="194" width="2" height="208" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="311" y="400" width="152" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="308" y="191" width="153" height="209" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="385" y="203">PgSqlOptionExchange</text>
+ <line stroke="black" stroke-opacity="1" x1="308" y1="204" x2="461" y2="204" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="216">OPTION_ID_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="228">CODE_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="240">VALUE_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="252">FORMATTED_VALUE_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="264">SPACE_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="276">PERSISTENT_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="288">DHCP_CLIENT_CLASS_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="300">DHCP_SUBNET_ID_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="312">HOST_ID_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="324">SCOPE_ID_COL</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="311" y="336">OPTION_COLUMNS</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="348">value_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="360">value_len_</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="372">option_</text>
+ <line stroke="black" stroke-opacity="1" x1="308" y1="373" x2="461" y2="373" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="385">PgSqlOptionExchange()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="311" y="397">createBindForSend()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="386" y1="190" x2="386" y2="168" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="386" y1="168" x2="550" y2="168" />
+ <line stroke="black" stroke-opacity="1" x1="556" y1="168" x2="550" y2="162" />
+ <line stroke="black" stroke-opacity="1" x1="556" y1="168" x2="550" y2="174" />
+ <line stroke="black" stroke-opacity="1" x1="550" y1="162" x2="550" y2="174" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="158" y1="509" x2="383" y2="509" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="158,509 164,503 170,509 164,515" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="383" y1="403" x2="377" y2="409" />
+ <line stroke="black" stroke-opacity="1" x1="383" y1="403" x2="389" y2="409" />
+ <line stroke="black" stroke-opacity="1" x1="383" y1="509" x2="383" y2="403" />
+</g>
+<g>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="250" y="522">&lt;&lt;:shared_ptr&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="137" y="22" width="2" height="188" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="15" y="208" width="124" height="2" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="12" y="19" width="125" height="189" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="75" y="31">BaseHostDataSource</text>
+ <line stroke="black" stroke-opacity="1" x1="12" y1="32" x2="137" y2="32" />
+ <line stroke="black" stroke-opacity="1" x1="12" y1="38" x2="137" y2="38" />
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="15" y="50">~BaseHostDataSource()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="62">getAll()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="74">getAll()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="86">getAll4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="98">get4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="110">get4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="122">get4()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="134">get6()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="146">get6()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="158">get6()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="170">add()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="15" y="182">getType()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="15" y="194">commit()</text>
+ <text font-family=".Helvetica Neue DeskInterface" font-size="11" fill="#000000" xml:space="preserve" x="15" y="206">rollback()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="74" y1="233" x2="74" y2="217" />
+ <line stroke="black" stroke-opacity="1" x1="74" y1="211" x2="68" y2="217" />
+ <line stroke="black" stroke-opacity="1" x1="74" y1="211" x2="80" y2="217" />
+ <line stroke="black" stroke-opacity="1" x1="68" y1="217" x2="80" y2="217" />
+</g>
+</svg>
diff --git a/src/lib/dhcpsrv/ip_range.cc b/src/lib/dhcpsrv/ip_range.cc
new file mode 100644
index 0000000..4630e15
--- /dev/null
+++ b/src/lib/dhcpsrv/ip_range.cc
@@ -0,0 +1,74 @@
+// Copyright (C) 2020-2023 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 <asiolink/addr_utilities.h>
+#include <asiolink/io_address.h>
+#include <dhcpsrv/ip_range.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+AddressRange::AddressRange(const IOAddress& start, const IOAddress& end)
+ : start_(start), end_(end) {
+ // Two IPv4 or two IPv6 addresses are expected as range boundaries.
+ if (start_.getFamily() != end_.getFamily()) {
+ isc_throw(BadValue, "address range boundaries must have the same type: " << start_
+ << ":" << end_);
+ }
+ // The start must be lower or equal the end.
+ if (end_ < start_) {
+ isc_throw(BadValue, "invalid address range boundaries " << start_ << ":" << end_);
+ }
+}
+
+PrefixRange::PrefixRange(const asiolink::IOAddress& prefix, const uint8_t length, const uint8_t delegated)
+ : start_(prefix), end_(IOAddress::IPV6_ZERO_ADDRESS()), prefix_length_(length),
+ delegated_length_(delegated) {
+ if (!start_.isV6()) {
+ isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
+ << start_ << " was specified");
+ }
+ if (delegated_length_ < prefix_length_) {
+ isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
+ << " must not be lower than prefix length " << static_cast<int>(length));
+ }
+ if ((prefix_length_ > 128) || (delegated_length_ > 128)) {
+ isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
+ << " and prefix length " << static_cast<int>(length)
+ << " must not be greater than 128");
+ }
+ // Now calculate the last prefix in the range.
+ end_ = lastAddrInPrefix(prefix, length);
+}
+
+PrefixRange::PrefixRange(const asiolink::IOAddress& start, const asiolink::IOAddress& end,
+ const uint8_t delegated)
+ : start_(start), end_(end), prefix_length_(prefixLengthFromRange(start, end)),
+ delegated_length_(delegated) {
+ if (!start_.isV6() || !end_.isV6()) {
+ isc_throw(BadValue, "IPv6 prefix required for prefix delegation range but "
+ << start_ << ":" << end_ << " was specified");
+ }
+ // The start must be lower or equal the end.
+ if (end_ < start_) {
+ isc_throw(BadValue, "invalid address range boundaries " << start_ << ":" << end_);
+ }
+ if (prefix_length_ > 128) {
+ isc_throw(BadValue, "the " << start_ << ":" << end_
+ << " does not constitute a valid prefix delegation range");
+ }
+ if (delegated_length_ > 128) {
+ isc_throw(BadValue, "delegated length " << static_cast<int>(delegated_length_)
+ << " must not be greater than 128");
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/ip_range.h b/src/lib/dhcpsrv/ip_range.h
new file mode 100644
index 0000000..b511ca7
--- /dev/null
+++ b/src/lib/dhcpsrv/ip_range.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2020 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 IP_RANGE_H
+#define IP_RANGE_H
+
+#include <asiolink/io_address.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Structure representing IP address range.
+struct AddressRange {
+ /// IP address denoting the start of the address range.
+ asiolink::IOAddress start_;
+ /// IP address denoting the end of the address range.
+ asiolink::IOAddress end_;
+
+ /// @brief Constructor.
+ ///
+ /// @param start beginning of the address range.
+ /// @param end end of the address range.
+ /// @throw BadValue if the @c start is greater than the end or
+ /// specified boundaries do not belong to the same family.
+ AddressRange(const asiolink::IOAddress& start, const asiolink::IOAddress& end);
+};
+
+/// @brief Structure representing delegated prefix range.
+struct PrefixRange {
+ /// IP address denoting the start of the prefix range.
+ asiolink::IOAddress start_;
+ /// IP address denoting the first address within the last prefix
+ /// in the prefix range.
+ asiolink::IOAddress end_;
+ /// Prefix length.
+ uint8_t prefix_length_;
+ /// Delegated prefix length.
+ uint8_t delegated_length_;
+
+ /// @brief Constructor.
+ ///
+ /// @param prefix prefix from which prefixes are delegated.
+ /// @param length length of the prefix from which prefixes are delegated.
+ /// @param delegated delegated prefix length.
+ /// @throw BadValue if the values provided to the constructor are invalid,
+ /// e.g. it is not IPv6 prefix, delegated length is lower than prefix length
+ /// etc.
+ PrefixRange(const asiolink::IOAddress& prefix, const uint8_t length, const uint8_t delegated);
+
+ /// @brief Constructor.
+ ///
+ /// @param start beginning of the prefix range.
+ /// @param end end of the prefix range.
+ /// @param delegated delegated prefix length.
+ /// @throw BadValue if the values provided to the constructor are invalid,
+ /// e.g. it is not IPv6 prefix.
+ PrefixRange(const asiolink::IOAddress& start, const asiolink::IOAddress& end,
+ const uint8_t delegated);
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // IP_RANGE_H
diff --git a/src/lib/dhcpsrv/ip_range_permutation.cc b/src/lib/dhcpsrv/ip_range_permutation.cc
new file mode 100644
index 0000000..d3520b8
--- /dev/null
+++ b/src/lib/dhcpsrv/ip_range_permutation.cc
@@ -0,0 +1,114 @@
+// Copyright (C) 2020-2023 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 <asiolink/addr_utilities.h>
+#include <dhcpsrv/ip_range_permutation.h>
+
+#include <iostream>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+IPRangePermutation::IPRangePermutation(const AddressRange& range)
+ : range_start_(range.start_), step_(1), cursor_(addrsInRange(range_start_, range.end_) - 1),
+ initial_cursor_(cursor_), state_(), done_(false), generator_() {
+ std::random_device rd;
+ generator_.seed(rd());
+}
+
+IPRangePermutation::IPRangePermutation(const PrefixRange& range)
+ : range_start_(range.start_), step_(isc::util::uint128_t(1) << (128 - range.delegated_length_)),
+ cursor_(prefixesInRange(range.prefix_length_, range.delegated_length_) - 1),
+ initial_cursor_(cursor_), state_(), done_(false), generator_() {
+ std::random_device rd;
+ generator_.seed(rd());
+}
+
+IOAddress
+IPRangePermutation::next(bool& done) {
+ // If we're done iterating over the pool let's return zero address and
+ // set the user supplied done flag to true.
+ if (done_) {
+ done = true;
+ return (range_start_.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+ }
+
+ // If there is one address left, return this address.
+ if (cursor_ == 0) {
+ done = done_ = true;
+ return (state_.at(0));
+ }
+
+ // We're not done.
+ done = false;
+
+ // The cursor indicates where we're in the range starting from its end. The
+ // addresses between the cursor and the end of the range have been already
+ // returned by this function. Therefore we focus on the remaining cursor-1
+ // addresses. Let's get random address from this sub-range.
+ uint64_t max_limit = std::numeric_limits<uint64_t>::max();
+ if ((cursor_ - 1) < isc::util::int128_t(max_limit)) {
+ max_limit = static_cast<uint64_t>(cursor_ - 1);
+ }
+ std::uniform_int_distribution<uint64_t> dist(0, max_limit);
+ auto next_loc = dist(generator_);
+
+ IOAddress next_loc_address = IOAddress::IPV4_ZERO_ADDRESS();
+
+ // Check whether this address exists in our map or not. If it exists
+ // it means it was swapped with some other address in previous calls to
+ // this function.
+ auto next_loc_existing = state_.find(next_loc);
+ if (next_loc_existing != state_.end()) {
+ // Address exists, so let's record it.
+ next_loc_address = next_loc_existing->second;
+ } else {
+ // Address does not exist on this position. We infer this address from
+ // its position by advancing the range start by position. For example,
+ // if the range is 192.0.2.1-192.0.2.10 and the picked random position is
+ // 5, the address we get is 192.0.2.6. This random address will be later
+ // returned to the caller.
+ next_loc_address = offsetAddress(range_start_, next_loc * step_);
+ }
+
+ // Let's get the address at cursor position in the same way.
+ IOAddress cursor_address = IOAddress::IPV4_ZERO_ADDRESS();
+ auto cursor_existing = state_.find(cursor_);
+ if (cursor_existing != state_.end()) {
+ cursor_address = cursor_existing->second;
+ } else {
+ cursor_address = offsetAddress(range_start_, cursor_ * step_);
+ }
+
+ // Now we swap them.... in fact we don't swap because as an optimization
+ // we don't record the addresses we returned by this function. We merely
+ // replace the address at random position with the address from cursor
+ // position. This address will be returned in the future if we get back
+ // to this position as a result of randomization.
+ if (next_loc_existing == state_.end()) {
+ state_.insert(std::make_pair(next_loc, cursor_address));
+ } else {
+ state_.at(next_loc) = cursor_address;
+ }
+ // Move the cursor one position backwards.
+ --cursor_;
+
+ // Return the address from the random position.
+ return (next_loc_address);
+}
+
+void
+IPRangePermutation::reset() {
+ state_.clear();
+ cursor_ = initial_cursor_;
+ done_ = false;
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/ip_range_permutation.h b/src/lib/dhcpsrv/ip_range_permutation.h
new file mode 100644
index 0000000..ca7a5ae
--- /dev/null
+++ b/src/lib/dhcpsrv/ip_range_permutation.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2020-2023 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 IP_RANGE_PERMUTATION_H
+#define IP_RANGE_PERMUTATION_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/ip_range.h>
+#include <util/bigints.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <random>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Random IP address/prefix permutation based on Fisher-Yates shuffle.
+///
+/// This class is used to shuffle IP addresses or delegated prefixes within
+/// the specified range. It is following the Fisher-Yates shuffle algorithm
+/// described in https://en.wikipedia.org/wiki/Fisher–Yates_shuffle.
+///
+/// The original algorithm is modified to keep the minimal information about
+/// the current state of the permutation and relies on the caller to collect
+/// and store the next available value. In other words, the generated and
+/// already returned random values are not stored by this class.
+///
+/// The class assumes that initially the IP addresses or delegated prefixes
+/// in the specified range are in increasing order. Suppose we're dealing with
+/// the following address range: 192.0.2.1-192.0.2.5. Therefore our addresses
+/// are initially ordered like this: a[0]=192.0.2.1, a[1]=192.0.2.2 ...,
+/// a[4]=192.0.2.5. The algorithm starts from the end of that range, i.e. i=4,
+/// so a[i]=192.0.2.5. A random value from the range of [0..i-1] is picked,
+/// i.e. a value from the range of [0..3]. Let's say it is 1. This value initially
+/// corresponds to the address a[1]=192.0.2.2. In the original algorithm the
+/// value of a[1] is swapped with a[4], yelding the following partial permutation:
+/// 192.0.2.1, 192.0.2.5, 192.0.2.3, 192.0.2.4, 192.0.2.2. In our case, we simply
+/// return the value of 192.0.2.2 to the caller and remember that
+/// a[1]=192.0.2.5. At this point we don't store the values of a[0], a[2] and
+/// a[3] because the corresponding IP addresses can be calculated from the
+/// range start and their index in the permutation. The value of a[1] must be
+/// stored because it has been swapped with a[4] and can't be calculated from
+/// the position index.
+///
+/// In the next step, the current index i (cursor value) is decreased by one.
+/// It now has the value of 3. Again, a random index is picked from the range
+/// of [0..3]. Note that it can be the same or different index than selected
+/// in the previous step. Let's assume it is 0. This corresponds to the address
+/// of 192.0.2.1. This address will be returned to the caller. The value of
+/// a[3]=192.0.2.4 is moved to a[0]. This yelds the following permutation:
+/// 192.0.2.4, 192.0.2.5, 192.0.2.3, 192.0.2.1, 192.0.2.2. However, we only
+/// remember a[0] and a[1]. The a[3] can be still computed from the range
+/// start and the position. The other two have been already returned to the
+/// caller so we forget them.
+///
+/// This algorithm guarantees that all IP addresses or delegated prefixes
+/// belonging to the given range are returned and no duplicates are returned.
+/// The addresses or delegated prefixes are returned in a random order.
+///
+/// @todo Methods of this class should be called in thread safe context. Otherwise
+/// they should be made thread safe.
+class IPRangePermutation {
+public:
+
+ /// @brief Constructor for address ranges.
+ ///
+ /// @param range address range for which the permutation will be generated.
+ IPRangePermutation(const AddressRange& range);
+
+ /// @brief Constructor for prefix ranges.
+ ///
+ /// @param range range of delegated prefixes for which the permutation will
+ /// be generated.
+ IPRangePermutation(const PrefixRange& range);
+
+ /// @brief Checks if the range has been exhausted.
+ ///
+ /// @return false if the algorithm went over all addresses or prefixes in
+ /// the range, true otherwise.
+ bool exhausted() const {
+ return (done_);
+ }
+
+ /// @brief Returns next random address or prefix from the permutation.
+ ///
+ /// This method returns all addresses or prefixes belonging to the specified
+ /// range in random order. For the first number of calls equal to the size of
+ /// the range it guarantees to return a non-zero IP address from that range
+ /// without duplicates.
+ ///
+ /// @param [out] done this parameter is set to true if no more addresses
+ /// or prefixes can be returned for this permutation.
+ /// @return next available IP address or prefix. It returns IPv4 zero or IPv6
+ /// zero address after this method walked over all available IP addresses or
+ /// prefixes in the range.
+ asiolink::IOAddress next(bool& done);
+
+ /// @brief Resets the permutation state.
+ ///
+ /// It effectively causes the permutation to start over the process of
+ /// serving addresses. Any previously returned addresses can be returned
+ /// again after calling this function.
+ void reset();
+
+private:
+
+ /// Beginning of the range.
+ asiolink::IOAddress range_start_;
+
+ /// Distance between two neighboring addresses or delegated prefixes,
+ /// i.e. 1 for address range and delegated prefix size for delegated
+ /// prefixes.
+ isc::util::uint128_t step_;
+
+ /// Keeps the position of the next address or prefix to be swapped with
+ /// a randomly picked address or prefix from the range of 0..cursor-1. The
+ /// cursor value is decreased every time a new IP address or prefix
+ /// is returned.
+ isc::util::uint128_t cursor_;
+
+ /// Keeps the initial cursor position for @c reset function.
+ isc::util::uint128_t initial_cursor_;
+
+ /// Keeps the current permutation state. The state associates the
+ /// swapped IP addresses or delegated prefixes with their positions in
+ /// the permutation.
+ std::map<isc::util::uint128_t, asiolink::IOAddress> state_;
+
+ /// Indicates if the addresses or delegated prefixes are exhausted.
+ bool done_;
+
+ /// Random generator.
+ std::mt19937 generator_;
+};
+
+/// @brief Pointer to the @c IPRangePermutation.
+typedef boost::shared_ptr<IPRangePermutation> IPRangePermutationPtr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // IP_RANGE_PERMUTATION_H
diff --git a/src/lib/dhcpsrv/iterative_allocation_state.cc b/src/lib/dhcpsrv/iterative_allocation_state.cc
new file mode 100644
index 0000000..badf3f8
--- /dev/null
+++ b/src/lib/dhcpsrv/iterative_allocation_state.cc
@@ -0,0 +1,56 @@
+// Copyright (C) 2022-2023 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 <asiolink/addr_utilities.h>
+#include <dhcpsrv/iterative_allocation_state.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/make_shared.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+SubnetIterativeAllocationStatePtr
+SubnetIterativeAllocationState::create(const SubnetPtr& subnet) {
+ auto subnet_prefix = subnet->get();
+ return (boost::make_shared<SubnetIterativeAllocationState>(subnet_prefix.first, subnet_prefix.second));
+}
+
+SubnetIterativeAllocationState::SubnetIterativeAllocationState(const IOAddress& prefix,
+ const uint8_t prefix_length)
+ : SubnetAllocationState(),
+ last_allocated_(lastAddrInPrefix(prefix, prefix_length)) {
+}
+
+IOAddress
+SubnetIterativeAllocationState::getLastAllocated() const {
+ MultiThreadingLock lock(*mutex_);
+ return (last_allocated_);
+}
+
+void
+SubnetIterativeAllocationState::setLastAllocated(const IOAddress& address) {
+ MultiThreadingLock lock(*mutex_);
+ last_allocated_ = address;
+ // Update the timestamp of the last allocation.
+ setCurrentAllocatedTimeInternal();
+}
+
+PoolIterativeAllocationStatePtr
+PoolIterativeAllocationState::create(const PoolPtr& pool) {
+ return (boost::make_shared<PoolIterativeAllocationState>(pool->getFirstAddress()));
+}
+
+PoolIterativeAllocationState::PoolIterativeAllocationState(const IOAddress& first)
+ : last_allocated_(first), last_allocated_valid_(false) {
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/iterative_allocation_state.h b/src/lib/dhcpsrv/iterative_allocation_state.h
new file mode 100644
index 0000000..25d9d7a
--- /dev/null
+++ b/src/lib/dhcpsrv/iterative_allocation_state.h
@@ -0,0 +1,138 @@
+// Copyright (C) 2022-2023 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 ITERATIVE_ALLOCATION_STATE_H
+#define ITERATIVE_ALLOCATION_STATE_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+#include <map>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of the @c SubnetIterativeAllocationState.
+class SubnetIterativeAllocationState;
+
+/// @brief Type of the pointer to the @c SubnetIterativeAllocationState.
+typedef boost::shared_ptr<SubnetIterativeAllocationState> SubnetIterativeAllocationStatePtr;
+
+/// @brief Subnet allocation state used by the iterative allocator.
+///
+/// It extends the base class with the mechanism to store the last
+/// allocated address or delegated prefix. The iterative allocator
+/// uses this information to pick the next address or delegated
+/// prefix on the next allocation request.
+class SubnetIterativeAllocationState : public SubnetAllocationState {
+public:
+
+ /// @brief Factory function creating the state instance from subnet.
+ ///
+ /// @param subnet instance of the subnet for which the allocation
+ /// state should be instantiated.
+ /// @return new allocation state instance.
+ static SubnetIterativeAllocationStatePtr create(const SubnetPtr& subnet);
+
+ /// @brief Constructor.
+ ///
+ /// @param prefix subnet prefix.
+ /// @param prefix_length subnet prefix length.
+ SubnetIterativeAllocationState(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_length);
+
+ /// @brief Returns last allocated address or prefix.
+ ///
+ /// @return last allocated address or prefix.
+ asiolink::IOAddress getLastAllocated() const;
+
+ /// @brief Sets last allocated address or prefix.
+ ///
+ /// @param address an address or prefix last allocated.
+ void setLastAllocated(const asiolink::IOAddress& address);
+
+private:
+
+ /// @brief Last allocated address or delegated prefix.
+ ///
+ /// This is the last allocated address or delegated prefix that was
+ /// previously allocated from the particular subnet. It should be
+ /// noted that although the value is usually correct, there are
+ /// cases when it is invalid, e.g. after removing a pool,
+ /// restarting or changing allocation algorithms. For that purpose
+ /// it should be only considered a help that should not be fully
+ /// trusted.
+ asiolink::IOAddress last_allocated_;
+};
+
+/// @brief Forward declaration of the @c PoolIterativeAllocationState.
+class PoolIterativeAllocationState;
+
+/// @brief Type of the pointer to the @c PoolIterativeAllocationState.
+typedef boost::shared_ptr<PoolIterativeAllocationState> PoolIterativeAllocationStatePtr;
+
+/// @brief Pool allocation state used by the iterative allocator.
+///
+/// It extends the base class with the information about the last allocated
+/// address in the pool.
+class PoolIterativeAllocationState : public AllocationState {
+public:
+
+ /// @brief Factory function creating the state instance from pool.
+ ///
+ /// @param pool instance of the pool for which the allocation state
+ /// should be instantiated.
+ /// @return new allocation state instance.
+ static PoolIterativeAllocationStatePtr create(const PoolPtr& pool);
+
+ /// @brief Constructor.
+ ///
+ /// @param first first address in the pool.
+ PoolIterativeAllocationState(const asiolink::IOAddress& first);
+
+ /// @brief Returns the last address that was tried from this pool
+ ///
+ /// @return address or prefix that was last tried from this pool
+ isc::asiolink::IOAddress getLastAllocated() const {
+ return (last_allocated_);
+ }
+
+ /// @brief Checks if the last address is valid.
+ ///
+ /// @return true if the last address is valid, false otherwise.
+ bool isLastAllocatedValid() const {
+ return last_allocated_valid_;
+ }
+
+ /// @brief Sets the last address that was tried from this pool.
+ ///
+ /// @param address address or prefix to that was tried last.
+ void setLastAllocated(const asiolink::IOAddress& address) {
+ last_allocated_ = address;
+ last_allocated_valid_ = true;
+ }
+
+ /// @brief Resets the last address to invalid.
+ void resetLastAllocated() {
+ last_allocated_valid_ = false;
+ }
+
+private:
+
+ /// @brief Last allocated address or prefix.
+ isc::asiolink::IOAddress last_allocated_;
+
+ /// @brief Last allocated address status.
+ bool last_allocated_valid_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // ITERATIVE_ALLOCATION_STATE_H
diff --git a/src/lib/dhcpsrv/iterative_allocator.cc b/src/lib/dhcpsrv/iterative_allocator.cc
new file mode 100644
index 0000000..6ec2b8c
--- /dev/null
+++ b/src/lib/dhcpsrv/iterative_allocator.cc
@@ -0,0 +1,370 @@
+// Copyright (C) 2022-2023 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 <dhcpsrv/iterative_allocator.h>
+#include <exceptions/exceptions.h>
+#include <boost/pointer_cast.hpp>
+#include <cstring>
+
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+IterativeAllocator::IterativeAllocator(Lease::Type type,
+ const WeakSubnetPtr& subnet)
+ : Allocator(type, subnet) {
+}
+
+isc::asiolink::IOAddress
+IterativeAllocator::increasePrefix(const IOAddress& prefix,
+ const uint8_t prefix_len) {
+ if (!prefix.isV6()) {
+ isc_throw(BadValue, "Prefix operations are for IPv6 only (attempted to "
+ "increase prefix " << prefix << ")");
+ }
+
+ // Get a buffer holding an address.
+ const std::vector<uint8_t>& vec = prefix.toBytes();
+
+ if (prefix_len < 1 || prefix_len > 128) {
+ isc_throw(BadValue, "Cannot increase prefix: invalid prefix length: "
+ << prefix_len);
+ }
+
+ uint8_t n_bytes = (prefix_len - 1)/8;
+ uint8_t n_bits = 8 - (prefix_len - n_bytes*8);
+ uint8_t mask = 1 << n_bits;
+
+ // Explanation: n_bytes specifies number of full bytes that are in-prefix.
+ // They can also be used as an offset for the first byte that is not in
+ // prefix. n_bits specifies number of bits on the last byte that is
+ // (often partially) in prefix. For example for a /125 prefix, the values
+ // are 15 and 3, respectively. Mask is a bitmask that has the least
+ // significant bit from the prefix set.
+
+ uint8_t packed[V6ADDRESS_LEN];
+
+ // Copy the address. It must be V6, but we already checked that.
+ memcpy(packed, &vec[0], V6ADDRESS_LEN);
+
+ // Can we safely increase only the last byte in prefix without overflow?
+ if (packed[n_bytes] + uint16_t(mask) < 256u) {
+ packed[n_bytes] += mask;
+ return (IOAddress::fromBytes(AF_INET6, packed));
+ }
+
+ // Overflow (done on uint8_t, but the sum is greater than 255)
+ packed[n_bytes] += mask;
+
+ // Deal with the overflow. Start increasing the least significant byte
+ for (int i = n_bytes - 1; i >= 0; --i) {
+ ++packed[i];
+ // If we haven't overflowed (0xff->0x0) the next byte, then we are done
+ if (packed[i] != 0) {
+ break;
+ }
+ }
+
+ return (IOAddress::fromBytes(AF_INET6, packed));
+}
+
+IOAddress
+IterativeAllocator::increaseAddress(const IOAddress& address,
+ bool prefix,
+ const uint8_t prefix_len) {
+ if (!prefix) {
+ return (IOAddress::increase(address));
+ } else {
+ return (increasePrefix(address, prefix_len));
+ }
+}
+
+IOAddress
+IterativeAllocator::pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr&,
+ const IOAddress&) {
+ // Let's get the last allocated address. It is usually set correctly,
+ // but there are times when it won't be (like after removing a pool or
+ // perhaps restarting the server).
+ IOAddress last = getSubnetState()->getLastAllocated();
+ bool valid = true;
+ bool retrying = false;
+
+ const auto& pools = subnet_.lock()->getPools(pool_type_);
+
+ if (pools.empty()) {
+ isc_throw(AllocFailed, "No pools defined in selected subnet");
+ }
+
+ // first we need to find a pool the last address belongs to.
+ PoolCollection::const_iterator it;
+ PoolCollection::const_iterator first = pools.end();
+ PoolPtr first_pool;
+ for (it = pools.begin(); it != pools.end(); ++it) {
+ if (!(*it)->clientSupported(client_classes)) {
+ continue;
+ }
+ if (first == pools.end()) {
+ first = it;
+ }
+ if ((*it)->inRange(last)) {
+ break;
+ }
+ }
+
+ // Caller checked this cannot happen
+ if (first == pools.end()) {
+ isc_throw(AllocFailed, "No allowed pools defined in selected subnet");
+ }
+
+ // last one was bogus for one of several reasons:
+ // - we just booted up and that's the first address we're allocating
+ // - a subnet was removed or other reconfiguration just completed
+ // - perhaps allocation algorithm was changed
+ // - last pool does not allow this client
+ if (it == pools.end()) {
+ it = first;
+ }
+
+ for (;;) {
+ // Trying next pool
+ if (retrying) {
+ for (; it != pools.end(); ++it) {
+ if ((*it)->clientSupported(client_classes)) {
+ break;
+ }
+ }
+ if (it == pools.end()) {
+ // Really out of luck today. That was the last pool.
+ break;
+ }
+ }
+
+ last = getPoolState(*it)->getLastAllocated();
+ valid = getPoolState(*it)->isLastAllocatedValid();
+ if (!valid && (last == (*it)->getFirstAddress())) {
+ // Pool was (re)initialized
+ getPoolState(*it)->setLastAllocated(last);
+ getSubnetState()->setLastAllocated(last);
+ return (last);
+ }
+ // still can be bogus
+ if (valid && !(*it)->inRange(last)) {
+ valid = false;
+ getPoolState(*it)->resetLastAllocated();
+ getPoolState(*it)->setLastAllocated((*it)->getFirstAddress());
+ }
+
+ if (valid) {
+ IOAddress next = increaseAddress(last, false, 0);
+ if ((*it)->inRange(next)) {
+ // the next one is in the pool as well, so we haven't hit
+ // pool boundary yet
+ getPoolState(*it)->setLastAllocated(next);
+ getSubnetState()->setLastAllocated(next);
+ return (next);
+ }
+
+ getPoolState(*it)->resetLastAllocated();
+ }
+ // We hit pool boundary, let's try to jump to the next pool and try again
+ ++it;
+ retrying = true;
+ }
+
+ // Let's rewind to the beginning.
+ for (it = first; it != pools.end(); ++it) {
+ if ((*it)->clientSupported(client_classes)) {
+ getPoolState(*it)->setLastAllocated((*it)->getFirstAddress());
+ getPoolState(*it)->resetLastAllocated();
+ }
+ }
+
+ // ok to access first element directly. We checked that pools is non-empty
+ last = getPoolState(*first)->getLastAllocated();
+ getPoolState(*first)->setLastAllocated(last);
+ getSubnetState()->setLastAllocated(last);
+ return (last);
+}
+
+IOAddress
+IterativeAllocator::pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool6,
+ const IdentifierBaseTypePtr&,
+ PrefixLenMatchType prefix_length_match,
+ const IOAddress&,
+ uint8_t hint_prefix_length) {
+ uint8_t prefix_len = 0;
+
+ // Let's get the last allocated prefix. It is usually set correctly,
+ // but there are times when it won't be (like after removing a pool or
+ // perhaps restarting the server).
+ IOAddress last = getSubnetState()->getLastAllocated();
+ bool valid = true;
+ bool retrying = false;
+
+ const auto& pools = subnet_.lock()->getPools(pool_type_);
+
+ if (pools.empty()) {
+ isc_throw(AllocFailed, "No pools defined in selected subnet");
+ }
+
+ // first we need to find a pool the last prefix belongs to.
+ PoolCollection::const_iterator it;
+ PoolCollection::const_iterator first = pools.end();
+ PoolPtr first_pool;
+ for (it = pools.begin(); it != pools.end(); ++it) {
+ if (!(*it)->clientSupported(client_classes)) {
+ continue;
+ }
+ if (!Allocator::isValidPrefixPool(prefix_length_match, *it,
+ hint_prefix_length)) {
+ continue;
+ }
+ if (first == pools.end()) {
+ first = it;
+ }
+ if ((*it)->inRange(last)) {
+ break;
+ }
+ }
+
+ // Caller checked this cannot happen
+ if (first == pools.end()) {
+ isc_throw(AllocFailed, "No allowed pools defined in selected subnet");
+ }
+
+ // last one was bogus for one of several reasons:
+ // - we just booted up and that's the first prefix we're allocating
+ // - a subnet was removed or other reconfiguration just completed
+ // - perhaps allocation algorithm was changed
+ // - last pool does not allow this client
+ if (it == pools.end()) {
+ it = first;
+ }
+
+ for (;;) {
+ // Trying next pool
+ if (retrying) {
+ for (; it != pools.end(); ++it) {
+ if ((*it)->clientSupported(client_classes)) {
+ if (!Allocator::isValidPrefixPool(prefix_length_match, *it,
+ hint_prefix_length)) {
+ continue;
+ }
+ break;
+ }
+ }
+ if (it == pools.end()) {
+ // Really out of luck today. That was the last pool.
+ break;
+ }
+ }
+
+ last = getPoolState(*it)->getLastAllocated();
+ valid = getPoolState(*it)->isLastAllocatedValid();
+ if (!valid && (last == (*it)->getFirstAddress())) {
+ // Pool was (re)initialized
+ getPoolState(*it)->setLastAllocated(last);
+ getSubnetState()->setLastAllocated(last);
+
+ pool6 = boost::dynamic_pointer_cast<Pool6>(*it);
+
+ if (!pool6) {
+ // Something is gravely wrong here
+ isc_throw(Unexpected, "Wrong type of pool: "
+ << (*it)->toText()
+ << " is not Pool6");
+ }
+
+ return (last);
+ }
+ // still can be bogus
+ if (valid && !(*it)->inRange(last)) {
+ valid = false;
+ getPoolState(*it)->resetLastAllocated();
+ getPoolState(*it)->setLastAllocated((*it)->getFirstAddress());
+ }
+
+ if (valid) {
+ // Ok, we have a pool that the last prefix belonged to, let's use it.
+ pool6 = boost::dynamic_pointer_cast<Pool6>(*it);
+ if (!pool6) {
+ // Something is gravely wrong here
+ isc_throw(Unexpected, "Wrong type of pool: "
+ << (*it)->toText()
+ << " is not Pool6");
+ }
+
+ // Get the prefix length
+ prefix_len = pool6->getLength();
+
+ IOAddress next = increaseAddress(last, true, prefix_len);
+ if ((*it)->inRange(next)) {
+ // the next one is in the pool as well, so we haven't hit
+ // pool boundary yet
+ getPoolState(*it)->setLastAllocated(next);
+ getSubnetState()->setLastAllocated(next);
+ return (next);
+ }
+
+ getPoolState(*it)->resetLastAllocated();
+ }
+ // We hit pool boundary, let's try to jump to the next pool and try again
+ ++it;
+ retrying = true;
+ }
+
+ // Let's rewind to the beginning.
+ for (it = first; it != pools.end(); ++it) {
+ if ((*it)->clientSupported(client_classes)) {
+ if (!Allocator::isValidPrefixPool(prefix_length_match, *it,
+ hint_prefix_length)) {
+ continue;
+ }
+ getPoolState(*it)->setLastAllocated((*it)->getFirstAddress());
+ getPoolState(*it)->resetLastAllocated();
+ }
+ }
+
+ // ok to access first element directly. We checked that pools is non-empty
+ last = getPoolState(*first)->getLastAllocated();
+ getPoolState(*first)->setLastAllocated(last);
+ getSubnetState()->setLastAllocated(last);
+
+ pool6 = boost::dynamic_pointer_cast<Pool6>(*first);
+ if (!pool6) {
+ // Something is gravely wrong here
+ isc_throw(Unexpected, "Wrong type of pool: "
+ << (*it)->toText()
+ << " is not Pool6");
+ }
+ return (last);
+}
+
+SubnetIterativeAllocationStatePtr
+IterativeAllocator::getSubnetState() const {
+ auto subnet = subnet_.lock();
+ if (!subnet->getAllocationState(pool_type_)) {
+ subnet->setAllocationState(pool_type_, SubnetIterativeAllocationState::create(subnet));
+ }
+ return (boost::dynamic_pointer_cast<SubnetIterativeAllocationState>(subnet->getAllocationState(pool_type_)));
+}
+
+PoolIterativeAllocationStatePtr
+IterativeAllocator::getPoolState(const PoolPtr& pool) const {
+ if (!pool->getAllocationState()) {
+ pool->setAllocationState(PoolIterativeAllocationState::create(pool));
+ }
+ return (boost::dynamic_pointer_cast<PoolIterativeAllocationState>(pool->getAllocationState()));
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/iterative_allocator.h b/src/lib/dhcpsrv/iterative_allocator.h
new file mode 100644
index 0000000..ef08090
--- /dev/null
+++ b/src/lib/dhcpsrv/iterative_allocator.h
@@ -0,0 +1,129 @@
+// Copyright (C) 2022-2023 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 ITERATIVE_ALLOCATOR_H
+#define ITERATIVE_ALLOCATOR_H
+
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/iterative_allocation_state.h>
+#include <dhcpsrv/lease.h>
+
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Address/prefix allocator that iterates over all addresses.
+///
+/// This class implements an iterative algorithm that returns all addresses in
+/// a pool iteratively, one after another. Once the last address is reached,
+/// it starts allocating from the beginning of the first pool (i.e. it loops
+/// over).
+class IterativeAllocator : public Allocator {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param type specifies the type of allocated leases.
+ /// @param subnet weak pointer to the subnet owning the allocator.
+ IterativeAllocator(Lease::Type type, const WeakSubnetPtr& subnet);
+
+ /// @brief Returns the allocator type string.
+ ///
+ /// @return iterative string.
+ virtual std::string getType() const {
+ return ("iterative");
+ }
+
+private:
+
+ /// @brief Returns the next address from the pools in the subnet.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickAddress.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param duid client DUID (ignored).
+ /// @param hint client hint (ignored).
+ ///
+ /// @return next offered address.
+ virtual asiolink::IOAddress pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr& duid,
+ const asiolink::IOAddress& hint);
+
+ /// @brief Picks a delegated prefix.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickPrefix.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param pool the selected pool satisfying all required conditions.
+ /// @param duid Client's DUID.
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length.
+ /// @param hint Client's hint.
+ /// @param hint_prefix_length the hint prefix length that the client
+ /// provided. The 0 value means that there is no hint and that any
+ /// pool will suffice.
+ ///
+ /// @return the next prefix.
+ virtual asiolink::IOAddress pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool,
+ const IdentifierBaseTypePtr& duid,
+ PrefixLenMatchType prefix_length_match,
+ const asiolink::IOAddress& hint,
+ uint8_t hint_prefix_length);
+
+ /// @brief Convenience function returning subnet allocation state instance.
+ ///
+ /// It creates a new subnet state instance and assigns it to the subnet
+ /// if it hasn't been initialized.
+ ///
+ /// @return allocation state instance for the subnet.
+ SubnetIterativeAllocationStatePtr getSubnetState() const;
+
+ /// @brief Convenience function returning pool allocation state instance.
+ ///
+ /// It creates a new pool state instance and assigns it to the pool
+ /// if it hasn't been initialized.
+ ///
+ /// @param pool pool instance.
+ /// @return allocation state instance for the pool.
+ PoolIterativeAllocationStatePtr getPoolState(const PoolPtr& pool) const;
+
+protected:
+
+ /// @brief Returns the next prefix.
+ ///
+ /// This method works for IPv6 addresses only. It increases the
+ /// specified prefix by a given prefix_len. For example, 2001:db8::
+ /// increased by prefix length /32 will become 2001:db9::. This method
+ /// is used to iterate over IPv6 prefix pools
+ ///
+ /// @param prefix prefix to be increased.
+ /// @param prefix_len length of the prefix to be increased.
+ ///
+ /// @return next prefix.
+ static asiolink::IOAddress increasePrefix(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len);
+
+ /// @brief Returns the next address or prefix.
+ ///
+ /// This method works for IPv4 addresses, IPv6 addresses and
+ /// IPv6 prefixes.
+ ///
+ /// @param address address or prefix to be increased
+ /// @param prefix true when the previous argument is a prefix.
+ /// @param prefix_len length of the prefix.
+ ///
+ /// @return result address or prefix
+ static asiolink::IOAddress increaseAddress(const asiolink::IOAddress& address,
+ bool prefix,
+ const uint8_t prefix_len);
+};
+
+} // namespace dhcp
+} // end of namespace isc
+
+#endif // ITERATIVE_ALLOCATOR_H
diff --git a/src/lib/dhcpsrv/key_from_key.h b/src/lib/dhcpsrv/key_from_key.h
new file mode 100644
index 0000000..ce8a92e
--- /dev/null
+++ b/src/lib/dhcpsrv/key_from_key.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2013-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/.
+
+#ifndef KEY_FROM_KEY_H
+#define KEY_FROM_KEY_H
+
+#include <functional>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class which cascades two key extractors.
+///
+/// The key extractor (a.k.a. key extraction class) is used by the
+/// key-based indices to obtain the indexing keys from the elements of
+/// a multi_index_container. The standard key extractors can be used
+/// to retrieve indexing key values by accessing members or methods
+/// exposed by the elements (objects or structures) stored in a
+/// multi_index_container. For example, if a container holds objects
+/// of type A, then the public members of object A or its accessors can
+/// be used by the standard extractor classes such as "member" or
+/// "const_mem_fun" respectively. Assume more complex scenario, where
+/// multi_index_container holds objects of a type A, object A exposes
+/// its public member B, which in turn exposes the accessor function
+/// returning object C. One may want to use the value C (e.g. integer)
+/// to index objects A in the container. This can't be solved by using
+/// standard key extractors because object C is nested in B and thus
+/// it is not directly accessible from A. However, it is possible
+/// to specify two distinct key extractors, one used to extract value
+/// C from B, another one to extract value B from A. These two extractors
+/// can be then wrapped by another key extractor which can be used
+/// to obtain index key C from object A. This key extractor is implemented
+/// as a functor class. The functor calls functors specified as
+/// template parameters to retrieve the index value from the cascaded
+/// structure.
+///
+/// @tparam KeyExtractor1 extractor used to extract the key value from
+/// the object containing it.
+/// @tparam KeyExtractor2 extractor used to extract the nested object
+/// containing a key.
+template<typename KeyExtractor1, typename KeyExtractor2>
+class KeyFromKeyExtractor {
+public:
+ typedef typename KeyExtractor1::result_type result_type;
+
+ /// @brief Constructor.
+ KeyFromKeyExtractor()
+ : key1_(KeyExtractor1()), key2_(KeyExtractor2()) { };
+
+ /// @brief Extract key value from the object hierarchy.
+ ///
+ /// @param arg the key value.
+ ///
+ /// @tparam key value type.
+ template<typename T>
+ result_type operator() (T& arg) const {
+ return (key1_(key2_(arg)));
+ }
+private:
+ /// Key Extractor used to extract the key value from the
+ /// object containing it.
+ KeyExtractor1 key1_;
+ /// Key Extractor used to extract the nested object
+ /// containing a key.
+ KeyExtractor2 key2_;
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // KEY_FROM_KEY_H
diff --git a/src/lib/dhcpsrv/lease.cc b/src/lib/dhcpsrv/lease.cc
new file mode 100644
index 0000000..9dad408
--- /dev/null
+++ b/src/lib/dhcpsrv/lease.cc
@@ -0,0 +1,750 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <dhcpsrv/lease.h>
+#include <util/pointer_util.h>
+#include <util/strutil.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <sstream>
+#include <iostream>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+const uint32_t Lease::STATE_DEFAULT = 0x0;
+const uint32_t Lease::STATE_DECLINED = 0x1;
+const uint32_t Lease::STATE_EXPIRED_RECLAIMED = 0x2;
+
+std::string
+Lease::lifetimeToText(uint32_t lifetime) {
+ ostringstream repr;
+ if (lifetime == INFINITY_LFT) {
+ repr << "infinity";
+ } else {
+ repr << lifetime;
+ }
+ return repr.str();
+}
+
+Lease::Lease(const isc::asiolink::IOAddress& addr,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt,
+ const bool fqdn_fwd, const bool fqdn_rev,
+ const std::string& hostname, const HWAddrPtr& hwaddr)
+ : addr_(addr), valid_lft_(valid_lft), current_valid_lft_(valid_lft),
+ reuseable_valid_lft_(0),
+ cltt_(cltt), current_cltt_(cltt), subnet_id_(subnet_id), pool_id_(0),
+ hostname_(boost::algorithm::to_lower_copy(hostname)), fqdn_fwd_(fqdn_fwd),
+ fqdn_rev_(fqdn_rev), hwaddr_(hwaddr), state_(STATE_DEFAULT) {
+}
+
+std::string
+Lease::typeToText(Lease::Type type) {
+ switch (type) {
+ case Lease::TYPE_V4:
+ return string("V4");
+ case Lease::TYPE_NA:
+ return string("IA_NA");
+ case Lease::TYPE_TA:
+ return string("IA_TA");
+ case Lease::TYPE_PD:
+ return string("IA_PD");
+ break;
+ default: {
+ stringstream tmp;
+ tmp << "unknown (" << type << ")";
+ return (tmp.str());
+ }
+ }
+}
+
+Lease::Type
+Lease::textToType(const std::string& text) {
+ if (text == "V4") {
+ return (TYPE_V4);
+
+ } else if (text == "IA_NA") {
+ return (TYPE_NA);
+
+ } else if (text == "IA_TA") {
+ return (TYPE_TA);
+
+ } else if (text == "IA_PD") {
+ return (TYPE_PD);
+ }
+
+ isc_throw(BadValue, "unsupported lease type " << text);
+}
+
+std::string
+Lease::basicStatesToText(const uint32_t state) {
+ switch (state) {
+ case STATE_DEFAULT:
+ return ("default");
+ case STATE_DECLINED:
+ return ("declined");
+ case STATE_EXPIRED_RECLAIMED:
+ return ("expired-reclaimed");
+ default:
+ // The default case will be handled further on
+ ;
+ }
+ std::ostringstream s;
+ s << "unknown (" << state << ")";
+ return s.str();
+}
+
+bool
+Lease::expired() const {
+ return ((valid_lft_ != INFINITY_LFT) && (getExpirationTime() < time(NULL)));
+}
+
+bool
+Lease::stateExpiredReclaimed() const {
+ return (state_ == STATE_EXPIRED_RECLAIMED);
+}
+
+bool
+Lease::stateDeclined() const {
+ return (state_ == STATE_DECLINED);
+}
+
+int64_t
+Lease::getExpirationTime() const {
+ return (static_cast<int64_t>(cltt_) + valid_lft_);
+}
+
+bool
+Lease::hasIdenticalFqdn(const Lease& other) const {
+ return (boost::algorithm::iequals(hostname_, other.hostname_) &&
+ fqdn_fwd_ == other.fqdn_fwd_ &&
+ fqdn_rev_ == other.fqdn_rev_);
+}
+
+void
+Lease::fromElementCommon(const LeasePtr& lease, const data::ConstElementPtr& element) {
+ if (!element) {
+ isc_throw(BadValue, "parsed lease data is null");
+ }
+
+ if (element->getType() != Element::map) {
+ isc_throw(BadValue, "parsed lease data is not a JSON map");
+ }
+
+ if (!lease) {
+ isc_throw(Unexpected, "pointer to parsed lease is null");
+ }
+
+ // IP address.
+ ConstElementPtr ip_address = element->get("ip-address");
+ if (!ip_address || (ip_address->getType() != Element::string)) {
+ isc_throw(BadValue, "ip-address not present in the parsed lease"
+ " or it is not a string");
+ }
+
+ boost::scoped_ptr<asiolink::IOAddress> io_address;
+ try {
+ io_address.reset(new asiolink::IOAddress(ip_address->stringValue()));
+
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "invalid IP address " << ip_address->stringValue()
+ << " in the parsed lease");
+ }
+
+ lease->addr_ = *io_address;
+
+ // Subnet identifier.
+ ConstElementPtr subnet_id = element->get("subnet-id");
+ if (!subnet_id || (subnet_id->getType() != Element::integer)) {
+ isc_throw(BadValue, "subnet-id not present in the parsed lease"
+ " or it is not an integer");
+ }
+
+ if (subnet_id->intValue() <= 0) {
+ isc_throw(BadValue, "subnet-id " << subnet_id->intValue() << " is not"
+ << " a positive integer");
+ } else if (subnet_id->intValue() > numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "subnet-id " << subnet_id->intValue() << " is not"
+ << " a 32 bit unsigned integer");
+ }
+
+ lease->subnet_id_ = SubnetID(subnet_id->intValue());
+
+ // Pool identifier.
+ ConstElementPtr pool_id = element->get("pool-id");
+ if (pool_id) {
+ if (pool_id->getType() != Element::integer) {
+ isc_throw(BadValue, "pool-id is not an integer");
+ }
+
+ if (pool_id->intValue() <= 0) {
+ isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not"
+ << " a positive integer");
+ } else if (pool_id->intValue() > numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not"
+ << " a 32 bit unsigned integer");
+ }
+
+ lease->pool_id_ = pool_id->intValue();
+ }
+
+ // Hardware address.
+ ConstElementPtr hw_address = element->get("hw-address");
+ if (hw_address) {
+ if (hw_address->getType() != Element::string) {
+ isc_throw(BadValue, "hw-address is not a string in the parsed lease");
+
+ }
+
+ try {
+ HWAddr parsed_hw_address = HWAddr::fromText(hw_address->stringValue());
+ lease->hwaddr_.reset(new HWAddr(parsed_hw_address.hwaddr_, HTYPE_ETHER));
+
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "invalid hardware address "
+ << hw_address->stringValue() << " in the parsed lease");
+ }
+ }
+
+ // cltt
+ ConstElementPtr cltt = element->get("cltt");
+ if (!cltt || (cltt->getType() != Element::integer)) {
+ isc_throw(BadValue, "cltt is not present in the parsed lease"
+ " or it is not an integer");
+ }
+
+ if (cltt->intValue() <= 0) {
+ isc_throw(BadValue, "cltt " << cltt->intValue() << " is not a"
+ " positive integer in the parsed lease");
+ }
+
+ lease->cltt_ = static_cast<time_t>(cltt->intValue());
+
+ // valid lifetime
+ ConstElementPtr valid_lifetime = element->get("valid-lft");
+ if (!valid_lifetime || (valid_lifetime->getType() != Element::integer)) {
+ isc_throw(BadValue, "valid-lft is not present in the parsed lease"
+ " or it is not an integer");
+ }
+
+ if (valid_lifetime->intValue() < 0) {
+ isc_throw(BadValue, "valid-lft " << valid_lifetime->intValue()
+ << " is negative in the parsed lease");
+ }
+
+ lease->valid_lft_ = valid_lifetime->intValue();
+
+ // fqdn-fwd
+ ConstElementPtr fqdn_fwd = element->get("fqdn-fwd");
+ if (!fqdn_fwd || fqdn_fwd->getType() != Element::boolean) {
+ isc_throw(BadValue, "fqdn-fwd is not present in the parsed lease"
+ " or it is not a boolean value");
+ }
+
+ lease->fqdn_fwd_ = fqdn_fwd->boolValue();
+
+ // fqdn-fwd
+ ConstElementPtr fqdn_rev = element->get("fqdn-rev");
+ if (!fqdn_rev || (fqdn_rev->getType() != Element::boolean)) {
+ isc_throw(BadValue, "fqdn-rev is not present in the parsed lease"
+ " or it is not a boolean value");
+ }
+
+ lease->fqdn_rev_ = fqdn_rev->boolValue();
+
+ // hostname
+ ConstElementPtr hostname = element->get("hostname");
+ if (!hostname || (hostname->getType() != Element::string)) {
+ isc_throw(BadValue, "hostname is not present in the parsed lease"
+ " or it is not a string value");
+ }
+
+ lease->hostname_ = hostname->stringValue();
+ boost::algorithm::to_lower(lease->hostname_);
+
+ // state
+ ConstElementPtr state = element->get("state");
+ if (!state || (state->getType() != Element::integer)) {
+ isc_throw(BadValue, "state is not present in the parsed lease"
+ " or it is not an integer");
+ }
+
+ if ((state->intValue() < 0) || (state->intValue() > Lease::STATE_EXPIRED_RECLAIMED)) {
+ isc_throw(BadValue, "state " << state->intValue()
+ << " must be in range [0.."
+ << Lease::STATE_EXPIRED_RECLAIMED << "]");
+ }
+
+ lease->state_ = state->intValue();
+
+ // user context
+ ConstElementPtr ctx = element->get("user-context");
+ if (ctx) {
+ if (ctx->getType() != Element::map) {
+ isc_throw(BadValue, "user context is not a map");
+ }
+ lease->setContext(ctx);
+ }
+
+ lease->updateCurrentExpirationTime();
+}
+
+void
+Lease::updateCurrentExpirationTime() {
+ Lease::syncCurrentExpirationTime(*this, *this);
+}
+
+void
+Lease::syncCurrentExpirationTime(const Lease& from, Lease& to) {
+ to.current_cltt_ = from.cltt_;
+ to.current_valid_lft_ = from.valid_lft_;
+}
+
+Lease4::Lease4(const isc::asiolink::IOAddress& address,
+ const HWAddrPtr& hw_address,
+ const ClientIdPtr& client_id,
+ const uint32_t valid_lifetime,
+ const time_t cltt,
+ const SubnetID subnet_id,
+ const bool fqdn_fwd,
+ const bool fqdn_rev,
+ const std::string& hostname)
+ : Lease(address, valid_lifetime, subnet_id, cltt, fqdn_fwd,
+ fqdn_rev, hostname, hw_address),
+ client_id_(client_id), remote_id_(), relay_id_() {
+}
+
+Lease4::Lease4() : Lease(0, 0, 0, 0, false, false, "", HWAddrPtr()) {
+}
+
+std::string
+Lease4::statesToText(const uint32_t state) {
+ return (Lease::basicStatesToText(state));
+}
+
+const std::vector<uint8_t>&
+Lease4::getClientIdVector() const {
+ if (!client_id_) {
+ static std::vector<uint8_t> empty_vec;
+ return (empty_vec);
+ }
+
+ return (client_id_->getClientId());
+}
+
+const std::vector<uint8_t>&
+Lease::getHWAddrVector() const {
+ if (!hwaddr_) {
+ static std::vector<uint8_t> empty_vec;
+ return (empty_vec);
+ }
+ return (hwaddr_->hwaddr_);
+}
+
+bool
+Lease4::belongsToClient(const HWAddrPtr& hw_address,
+ const ClientIdPtr& client_id) const {
+ // If client id matches, lease matches.
+ if (equalValues(client_id, client_id_)) {
+ return (true);
+
+ } else if (!client_id || !client_id_) {
+ // If client id is unspecified, use HW address.
+ if (equalValues(hw_address, hwaddr_)) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+void
+Lease4::decline(uint32_t probation_period) {
+ hwaddr_.reset(new HWAddr());
+ client_id_.reset();
+ cltt_ = time(NULL);
+ hostname_ = string("");
+ fqdn_fwd_ = false;
+ fqdn_rev_ = false;
+ state_ = STATE_DECLINED;
+ valid_lft_ = probation_period;
+}
+
+isc::data::ElementPtr
+Lease4::toElement() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ contextToElement(map);
+ map->set("ip-address", Element::create(addr_.toText()));
+ map->set("subnet-id", Element::create(static_cast<long int>(subnet_id_)));
+ if (pool_id_) {
+ map->set("pool-id", Element::create(static_cast<long int>(pool_id_)));
+ }
+ map->set("hw-address", Element::create(hwaddr_->toText(false)));
+
+ if (client_id_) {
+ map->set("client-id", Element::create(client_id_->toText()));
+ }
+
+ map->set("cltt", Element::create(cltt_));
+ map->set("valid-lft", Element::create(static_cast<long int>(valid_lft_)));
+
+ map->set("fqdn-fwd", Element::create(fqdn_fwd_));
+ map->set("fqdn-rev", Element::create(fqdn_rev_));
+ map->set("hostname", Element::create(hostname_));
+
+ map->set("state", Element::create(static_cast<int>(state_)));
+
+ return (map);
+}
+
+Lease4Ptr
+Lease4::fromElement(const ConstElementPtr& element) {
+ Lease4Ptr lease(new Lease4());
+
+ // Extract common lease properties into the lease.
+ fromElementCommon(boost::dynamic_pointer_cast<Lease>(lease), element);
+
+ // Validate ip-address, which must be an IPv4 address.
+ if (!lease->addr_.isV4()) {
+ isc_throw(BadValue, "address " << lease->addr_ << " it not an IPv4 address");
+ }
+
+ // Make sure the hw-addres is present.
+ if (!lease->hwaddr_) {
+ isc_throw(BadValue, "hw-address not present in the parsed lease");
+ }
+
+ // Client identifier is IPv4 specific.
+ ConstElementPtr client_id = element->get("client-id");
+ if (client_id) {
+ if (client_id->getType() != Element::string) {
+ isc_throw(BadValue, "client identifier is not a string in the"
+ " parsed lease");
+ }
+
+ try {
+ lease->client_id_ = ClientId::fromText(client_id->stringValue());
+
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "invalid client identifier "
+ << client_id->stringValue() << " in the parsed lease");
+ }
+ }
+
+ return (lease);
+}
+
+Lease6::Lease6(Lease::Type type, const isc::asiolink::IOAddress& addr,
+ DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
+ SubnetID subnet_id, const HWAddrPtr& hwaddr, uint8_t prefixlen)
+ : Lease(addr, valid, subnet_id, 0/*cltt*/, false, false, "", hwaddr),
+ type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+ preferred_lft_(preferred), reuseable_preferred_lft_(0),
+ extended_info_action_(ExtendedInfoAction::ACTION_IGNORE) {
+ if (!duid) {
+ isc_throw(BadValue, "DUID is mandatory for an IPv6 lease");
+ }
+
+ if (prefixlen != 128) {
+ if (type != Lease::TYPE_PD) {
+ isc_throw(BadValue, "prefixlen must be 128 for non prefix type");
+ }
+ }
+
+ cltt_ = time(NULL);
+ current_cltt_ = cltt_;
+}
+
+Lease6::Lease6(Lease::Type type, const isc::asiolink::IOAddress& addr,
+ DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
+ SubnetID subnet_id, const bool fqdn_fwd, const bool fqdn_rev,
+ const std::string& hostname, const HWAddrPtr& hwaddr,
+ uint8_t prefixlen)
+ : Lease(addr, valid, subnet_id, 0/*cltt*/,
+ fqdn_fwd, fqdn_rev, hostname, hwaddr),
+ type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+ preferred_lft_(preferred), reuseable_preferred_lft_(0),
+ extended_info_action_(ExtendedInfoAction::ACTION_IGNORE) {
+
+ if (!duid) {
+ isc_throw(BadValue, "DUID is mandatory for an IPv6 lease");
+ }
+
+ if (prefixlen != 128) {
+ if (type != Lease::TYPE_PD) {
+ isc_throw(BadValue, "prefixlen must be 128 for non prefix type");
+ }
+ }
+
+ cltt_ = time(NULL);
+ current_cltt_ = cltt_;
+}
+
+Lease6::Lease6()
+ : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, false, false, "",
+ HWAddrPtr()), type_(TYPE_NA), prefixlen_(0), iaid_(0),
+ duid_(DuidPtr()), preferred_lft_(0), reuseable_preferred_lft_(0),
+ extended_info_action_(ExtendedInfoAction::ACTION_IGNORE) {
+}
+
+std::string
+Lease6::statesToText(const uint32_t state) {
+ return (Lease::basicStatesToText(state));
+}
+
+const std::vector<uint8_t>&
+Lease6::getDuidVector() const {
+ if (!duid_) {
+ static std::vector<uint8_t> empty_vec;
+ return (empty_vec);
+ }
+
+ return (duid_->getDuid());
+}
+
+void
+Lease6::decline(uint32_t probation_period) {
+ hwaddr_.reset();
+ duid_.reset(new DUID(DUID::EMPTY()));
+ preferred_lft_ = 0;
+ valid_lft_ = probation_period;
+ cltt_ = time(NULL);
+ hostname_ = string("");
+ fqdn_fwd_ = false;
+ fqdn_rev_ = false;
+ state_ = STATE_DECLINED;
+}
+
+std::string
+Lease6::toText() const {
+ ostringstream stream;
+
+ /// @todo: print out DUID
+ stream << "Type: " << typeToText(type_) << "("
+ << static_cast<int>(type_) << ")\n"
+ << "Address: " << addr_ << "\n"
+ << "Prefix length: " << static_cast<int>(prefixlen_) << "\n"
+ << "IAID: " << iaid_ << "\n"
+ << "Pref life: " << lifetimeToText(preferred_lft_) << "\n"
+ << "Valid life: " << lifetimeToText(valid_lft_) << "\n"
+ << "Cltt: " << cltt_ << "\n"
+ << "DUID: " << (duid_?duid_->toText():"(none)") << "\n"
+ << "Hardware addr: " << (hwaddr_?hwaddr_->toText(false):"(none)") << "\n"
+ << "Subnet ID: " << subnet_id_ << "\n"
+ << "Pool ID: " << pool_id_ << "\n"
+ << "State: " << statesToText(state_) << "\n";
+
+ if (getContext()) {
+ stream << "User context: " << getContext()->str() << "\n";
+ }
+
+ return (stream.str());
+}
+
+std::string
+Lease4::toText() const {
+ ostringstream stream;
+
+ stream << "Address: " << addr_ << "\n"
+ << "Valid life: " << lifetimeToText(valid_lft_) << "\n"
+ << "Cltt: " << cltt_ << "\n"
+ << "Hardware addr: " << (hwaddr_ ? hwaddr_->toText(false) : "(none)") << "\n"
+ << "Client id: " << (client_id_ ? client_id_->toText() : "(none)") << "\n"
+ << "Subnet ID: " << subnet_id_ << "\n"
+ << "Pool ID: " << pool_id_ << "\n"
+ << "State: " << statesToText(state_) << "\n"
+ << "Relay ID: " << (relay_id_.empty() ? "(none)" :
+ str::dumpAsHex(&relay_id_[0], relay_id_.size())) << "\n"
+ << "Remote ID: " << (remote_id_.empty() ? "(none)" :
+ str::dumpAsHex(&remote_id_[0], remote_id_.size())) << "\n";
+
+ if (getContext()) {
+ stream << "User context: " << getContext()->str() << "\n";
+ }
+
+ return (stream.str());
+}
+
+bool
+Lease4::operator==(const Lease4& other) const {
+ return (nullOrEqualValues(hwaddr_, other.hwaddr_) &&
+ nullOrEqualValues(client_id_, other.client_id_) &&
+ addr_ == other.addr_ &&
+ subnet_id_ == other.subnet_id_ &&
+ pool_id_ == other.pool_id_ &&
+ valid_lft_ == other.valid_lft_ &&
+ current_valid_lft_ == other.current_valid_lft_ &&
+ reuseable_valid_lft_ == other.reuseable_valid_lft_ &&
+ cltt_ == other.cltt_ &&
+ current_cltt_ == other.current_cltt_ &&
+ hostname_ == other.hostname_ &&
+ fqdn_fwd_ == other.fqdn_fwd_ &&
+ fqdn_rev_ == other.fqdn_rev_ &&
+ state_ == other.state_ &&
+ nullOrEqualValues(getContext(), other.getContext()));
+}
+
+bool
+Lease6::operator==(const Lease6& other) const {
+ return (nullOrEqualValues(duid_, other.duid_) &&
+ nullOrEqualValues(hwaddr_, other.hwaddr_) &&
+ addr_ == other.addr_ &&
+ type_ == other.type_ &&
+ prefixlen_ == other.prefixlen_ &&
+ iaid_ == other.iaid_ &&
+ preferred_lft_ == other.preferred_lft_ &&
+ reuseable_preferred_lft_ == other.reuseable_preferred_lft_ &&
+ valid_lft_ == other.valid_lft_ &&
+ current_valid_lft_ == other.current_valid_lft_ &&
+ reuseable_valid_lft_ == other.reuseable_valid_lft_ &&
+ cltt_ == other.cltt_ &&
+ current_cltt_ == other.current_cltt_ &&
+ subnet_id_ == other.subnet_id_ &&
+ pool_id_ == other.pool_id_ &&
+ hostname_ == other.hostname_ &&
+ fqdn_fwd_ == other.fqdn_fwd_ &&
+ fqdn_rev_ == other.fqdn_rev_ &&
+ state_ == other.state_ &&
+ nullOrEqualValues(getContext(), other.getContext()));
+}
+
+isc::data::ElementPtr
+Lease6::toElement() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+ contextToElement(map);
+ map->set("ip-address", Element::create(addr_.toText()));
+ map->set("type", Element::create(typeToText(type_)));
+ if (type_ == Lease::TYPE_PD) {
+ map->set("prefix-len", Element::create(prefixlen_));
+ }
+ map->set("iaid", Element::create(static_cast<long int>(iaid_)));
+ map->set("duid", Element::create(duid_->toText()));
+ map->set("subnet-id", Element::create(static_cast<long int>(subnet_id_)));
+ if (pool_id_) {
+ map->set("pool-id", Element::create(static_cast<long int>(pool_id_)));
+ }
+
+ map->set("cltt", Element::create(cltt_));
+ map->set("preferred-lft", Element::create(static_cast<long int>(preferred_lft_)));
+ map->set("valid-lft", Element::create(static_cast<long int>(valid_lft_)));
+
+ map->set("fqdn-fwd", Element::create(fqdn_fwd_));
+ map->set("fqdn-rev", Element::create(fqdn_rev_));
+ map->set("hostname", Element::create(hostname_));
+
+ if (hwaddr_) {
+ map->set("hw-address", Element::create(hwaddr_->toText(false)));
+ }
+
+ map->set("state", Element::create(static_cast<long int>(state_)));
+
+ return (map);
+}
+
+Lease6Ptr
+Lease6::fromElement(const data::ConstElementPtr& element) {
+ Lease6Ptr lease(new Lease6());
+
+ // Extract common lease properties into the lease.
+ fromElementCommon(boost::dynamic_pointer_cast<Lease>(lease), element);
+
+ // Validate ip-address, which must be an IPv6 address.
+ if (!lease->addr_.isV6()) {
+ isc_throw(BadValue, "address " << lease->addr_ << " it not an IPv6 address");
+ }
+
+ // lease type
+ ConstElementPtr lease_type = element->get("type");
+ if (!lease_type || (lease_type->getType() != Element::string)) {
+ isc_throw(BadValue, "type is not present in the parsed lease"
+ " or it is not a string value");
+ }
+
+ lease->type_ = textToType(lease_type->stringValue());
+
+ // prefix length
+ if (lease->type_ != Lease::TYPE_PD) {
+ lease->prefixlen_ = 128;
+ } else {
+ ConstElementPtr prefix_len = element->get("prefix-len");
+ if (!prefix_len || (prefix_len->getType() != Element::integer)) {
+ isc_throw(BadValue, "prefix-len is not present in the parsed lease"
+ " or it is not an integer");
+ }
+
+ if ((prefix_len->intValue() < 1) || (prefix_len->intValue() > 128)) {
+ isc_throw(BadValue, "prefix-len " << prefix_len->intValue()
+ << " must be in range of [1..128]");
+ }
+
+ lease->prefixlen_ = static_cast<uint8_t>(prefix_len->intValue());
+ }
+
+ // IAID
+ ConstElementPtr iaid = element->get("iaid");
+ if (!iaid || (iaid->getType() != Element::integer)) {
+ isc_throw(BadValue, "iaid is not present in the parsed lease"
+ " or it is not an integer");
+ }
+
+ if (iaid->intValue() < 0) {
+ isc_throw(BadValue, "iaid " << iaid->intValue() << " must not be negative");
+ }
+
+ lease->iaid_ = static_cast<uint32_t>(iaid->intValue());
+
+ // DUID
+ ConstElementPtr duid = element->get("duid");
+ if (!duid || (duid->getType() != Element::string)) {
+ isc_throw(BadValue, "duid not present in the parsed lease"
+ " or it is not a string");
+ }
+
+ try {
+ DUID parsed_duid = DUID::fromText(duid->stringValue());
+ lease->duid_.reset(new DUID(parsed_duid.getDuid()));
+
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "invalid DUID "
+ << duid->stringValue() << " in the parsed lease");
+ }
+
+ // preferred lifetime
+ ConstElementPtr preferred_lft = element->get("preferred-lft");
+ if (!preferred_lft || (preferred_lft->getType() != Element::integer)) {
+ isc_throw(BadValue, "preferred-lft is not present in the parsed lease"
+ " or is not an integer");
+ }
+
+ if (preferred_lft->intValue() < 0) {
+ isc_throw(BadValue, "preferred-lft " << preferred_lft->intValue()
+ << " must not be negative");
+ }
+
+ lease->preferred_lft_ = static_cast<uint32_t>(preferred_lft->intValue());
+
+ return (lease);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Lease& lease) {
+ os << lease.toText();
+ return (os);
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/lease.h b/src/lib/dhcpsrv/lease.h
new file mode 100644
index 0000000..f84302d
--- /dev/null
+++ b/src/lib/dhcpsrv/lease.h
@@ -0,0 +1,713 @@
+// Copyright (C) 2013-2023 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 LEASE_H
+#define LEASE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <cc/user_context.h>
+#include <cc/cfg_to_element.h>
+#include <util/dhcp_space.h>
+
+namespace isc {
+namespace dhcp {
+
+struct Lease;
+
+/// @brief Pointer to the lease object.
+typedef boost::shared_ptr<Lease> LeasePtr;
+
+/// @brief a common structure for IPv4 and IPv6 leases
+///
+/// This structure holds all information that is common between IPv4 and IPv6
+/// leases.
+struct Lease : public isc::data::UserContext, public isc::data::CfgToElement {
+
+ /// @brief Infinity (means static, i.e. never expire)
+ static const uint32_t INFINITY_LFT = 0xffffffff;
+
+ /// @brief Print lifetime
+ ///
+ /// This converts a lifetime to a string taking into account the
+ /// infinity special value.
+ ///
+ /// @param lifetime lifetime to print
+ /// @return a string representing the finite value or "infinity"
+ static std::string lifetimeToText(uint32_t lifetime);
+
+ /// @brief Type of lease or pool
+ typedef enum {
+ TYPE_NA = 0, ///< the lease contains non-temporary IPv6 address
+ TYPE_TA = 1, ///< the lease contains temporary IPv6 address
+ TYPE_PD = 2, ///< the lease contains IPv6 prefix (for prefix delegation)
+ TYPE_V4 = 3 ///< IPv4 lease
+ } Type;
+
+ /// @brief returns text representation of a lease type
+ /// @param type lease or pool type to be converted
+ /// @return text description
+ static std::string typeToText(Type type);
+
+ /// @brief Converts type name to the actual type.
+ ///
+ /// @param text lease type as text.
+ /// @return converted type.
+ /// @throw BadValue if the text contains unsupported value.
+ static Type textToType(const std::string& text);
+
+ /// @name Common lease states constants.
+ //@{
+ ///
+ /// @brief A lease in the default state.
+ static const uint32_t STATE_DEFAULT;
+
+ /// @brief Declined lease.
+ static const uint32_t STATE_DECLINED;
+
+ /// @brief Expired and reclaimed lease.
+ static const uint32_t STATE_EXPIRED_RECLAIMED;
+
+ /// @brief Returns name(s) of the basic lease state(s).
+ ///
+ /// @param state A numeric value holding a state information.
+ /// Some states may be composite, i.e. the single state value
+ /// maps to multiple logical states of the lease.
+ ///
+ /// @return Comma separated list of state names.
+ static std::string basicStatesToText(const uint32_t state);
+
+ /// @brief Constructor
+ ///
+ /// @param addr IP address
+ /// @param valid_lft Lifetime of the lease
+ /// @param subnet_id Subnet identification
+ /// @param cltt Client last transmission time
+ /// @param fqdn_fwd If true, forward DNS update is performed for a lease.
+ /// @param fqdn_rev If true, reverse DNS update is performed for a lease.
+ /// @param hostname FQDN of the client which gets the lease.
+ /// @param hwaddr Hardware/MAC address
+ ///
+ /// @note When creating a new Lease object, current_cltt_ matches cltt_ and
+ /// current_valid_lft_ matches valid_lft_. Any update operation that changes
+ /// cltt_ or valid_lft_ in the database must also update the current_cltt_
+ /// and current_valid_lft_ after the database response so that additional
+ /// operations can be performed on the same object. Failing to do so will
+ /// result in the new actions to be rejected by the database.
+ Lease(const isc::asiolink::IOAddress& addr,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt,
+ const bool fqdn_fwd, const bool fqdn_rev,
+ const std::string& hostname,
+ const HWAddrPtr& hwaddr);
+
+ /// @brief Destructor
+ virtual ~Lease() {}
+
+ /// @brief Returns Lease type
+ ///
+ /// One of normal address, temporary address, or prefix, or V4
+ virtual Lease::Type getType() const = 0;
+
+ /// @brief IPv4 ot IPv6 address
+ ///
+ /// IPv4, IPv6 address or, in the case of a prefix delegation, the prefix.
+ isc::asiolink::IOAddress addr_;
+
+ /// @brief Valid lifetime
+ ///
+ /// Expressed as number of seconds since cltt.
+ uint32_t valid_lft_;
+
+ /// @brief Current valid lifetime
+ ///
+ /// Expressed as number of seconds since cltt before update.
+ uint32_t current_valid_lft_;
+
+ /// @brief Remaining valid lifetime
+ ///
+ /// Expressed as number of seconds since current time, also
+ /// valid lifetime - age where age is old cltt - new cltt.
+ /// The value 0 is used for the "cannot be reused" condition.
+ uint32_t reuseable_valid_lft_;
+
+ /// @brief Client last transmission time
+ ///
+ /// Specifies a timestamp giving the time when the last transmission from a
+ /// client was received.
+ time_t cltt_;
+
+ /// @brief Current client last transmission time
+ ///
+ /// Specifies a timestamp giving the time when the last transmission from a
+ /// client was received before update.
+ time_t current_cltt_;
+
+ /// @brief Subnet identifier
+ ///
+ /// Specifies the identification of the subnet to which the lease belongs.
+ SubnetID subnet_id_;
+
+ /// @brief The pool id
+ ///
+ /// Specifies the identification of the pool from a subnet to which the lease belongs.
+ uint32_t pool_id_;
+
+ /// @brief Client hostname
+ ///
+ /// This field is in lower case and may be empty.
+ std::string hostname_;
+
+ /// @brief Forward zone updated?
+ ///
+ /// Set true if the DNS AAAA record for this lease has been updated.
+ bool fqdn_fwd_;
+
+ /// @brief Reverse zone updated?
+ ///
+ /// Set true if the DNS PTR record for this lease has been updated.
+ bool fqdn_rev_;
+
+ /// @brief Client's MAC/hardware address
+ ///
+ /// This information may not be available in certain cases.
+ HWAddrPtr hwaddr_;
+
+ /// @brief Holds the lease state(s).
+ ///
+ /// This is the field that holds the lease state(s). Typically, a
+ /// lease remains in a single states. However, it is possible to
+ /// define a value for state which indicates that the lease remains
+ /// in multiple logical states.
+ ///
+ /// The defined states are represented by the "STATE_*" constants
+ /// belonging to this class.
+ uint32_t state_;
+
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const = 0;
+
+ /// @brief returns true if the lease is expired
+ /// @return true if the lease is expired
+ bool expired() const;
+
+ /// @brief Indicates if the lease is in the "expired-reclaimed" state.
+ ///
+ /// @return true if the lease is in the "expired-reclaimed" state, false
+ /// otherwise.
+ bool stateExpiredReclaimed() const;
+
+ /// @brief Indicates if the lease is in the "declined" state.
+ ///
+ /// @return true if the lease is in the "declined" state, false otherwise.
+ bool stateDeclined() const;
+
+ /// @brief Returns true if the other lease has equal FQDN data.
+ ///
+ /// The comparison of the hostname is case insensitive.
+ ///
+ /// @param other Lease which FQDN data is to be compared with our lease.
+ ///
+ /// @return Boolean value which indicates whether FQDN data of the other
+ /// lease is equal to the FQDN data of our lease (true) or not (false).
+ bool hasIdenticalFqdn(const Lease& other) const;
+
+ /// @brief Returns raw (as vector) hardware address
+ ///
+ /// This method is needed in multi-index container as key extractor.
+ /// The const reference is only valid as long as the object that returned it.
+ /// In the unlikely case when Lease4 does not have a hardware address,
+ /// the function will return an empty vector.
+ ///
+ /// @return const reference to the hardware address
+ const std::vector<uint8_t>& getHWAddrVector() const;
+
+ /// @brief Returns lease expiration time.
+ ///
+ /// The lease expiration time is a sum of a client last transmission time
+ /// and valid lifetime.
+ int64_t getExpirationTime() const;
+
+ /// @brief Sets lease to DECLINED state.
+ ///
+ /// All client identifying parameters will be stripped off (HWaddr,
+ /// client_id, hostname), cltt will be set to current time and
+ /// valid_lft to parameter specified as probation period.
+ /// Note that This method only sets fields in the structure.
+ /// It is caller's responsibility to clean up DDNS, bump up stats,
+ /// log, call hooks ets.
+ ///
+ /// @param probation_period lease lifetime will be set to this value
+ virtual void decline(uint32_t probation_period) = 0;
+
+ /// Avoid a clang spurious error
+ using isc::data::CfgToElement::toElement;
+
+ /// Sync lease current expiration time with new value from another lease,
+ /// so that additional operations can be done without performing extra read
+ /// from the database.
+ ///
+ /// @note The lease current expiration time is represented by the
+ /// @ref current_cltt_ and @ref current_valid_lft_ and the new value by
+ /// @ref cltt_ and @ref valid_lft_
+ ///
+ /// @param from The lease with latest value of expiration time.
+ /// @param [out] to The lease that needs to be updated.
+ static void syncCurrentExpirationTime(const Lease& from, Lease& to);
+
+ /// Update lease current expiration time with new value,
+ /// so that additional operations can be done without performing extra read
+ /// from the database.
+ ///
+ /// @note The lease current expiration time is represented by the
+ /// @ref current_cltt_ and @ref current_valid_lft_ and the new value by
+ /// @ref cltt_ and @ref valid_lft_
+ void updateCurrentExpirationTime();
+
+protected:
+
+ /// @brief Sets common (for v4 and v6) properties of the lease object.
+ ///
+ /// This method is called by the @c fromElement methods of the @c Lease
+ /// class derivations.
+ ///
+ /// @param [out] lease pointer to the lease object for which common
+ /// properties should be set.
+ /// @param element pointer to the data element object to be parsed.
+ static void fromElementCommon(const LeasePtr& lease,
+ const data::ConstElementPtr& element);
+
+};
+
+struct Lease4;
+
+/// @brief Pointer to a Lease4 structure.
+typedef boost::shared_ptr<Lease4> Lease4Ptr;
+
+/// @brief Structure that holds a lease for IPv4 address
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease4 : public Lease {
+
+ /// @brief Client identifier
+ ///
+ /// @todo Should this be a pointer to a client ID or the ID itself?
+ /// Compare with the DUID in the Lease6 structure.
+ ClientIdPtr client_id_;
+
+ /// @brief Constructor
+ ///
+ /// @param addr IPv4 address.
+ /// @param hwaddr A pointer to HWAddr structure
+ /// @param clientid Client identification buffer
+ /// @param clientid_len Length of client identification buffer
+ /// @param valid_lft Lifetime of the lease
+ /// @param cltt Client last transmission time
+ /// @param subnet_id Subnet identification
+ /// @param fqdn_fwd If true, forward DNS update is performed for a lease.
+ /// @param fqdn_rev If true, reverse DNS update is performed for a lease.
+ /// @param hostname FQDN of the client which gets the lease.
+ Lease4(const isc::asiolink::IOAddress& addr, const HWAddrPtr& hwaddr,
+ const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
+ time_t cltt, uint32_t subnet_id,
+ const bool fqdn_fwd = false, const bool fqdn_rev = false,
+ const std::string& hostname = "")
+ : Lease(addr, valid_lft, subnet_id, cltt, fqdn_fwd, fqdn_rev,
+ hostname, hwaddr) {
+ if (clientid_len) {
+ client_id_.reset(new ClientId(clientid, clientid_len));
+ }
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param address IPv4 address.
+ /// @param hw_address Pointer to client's HW address.
+ /// @param client_id pointer to the client id structure.
+ /// @param valid_lifetime Valid lifetime value.
+ /// @param cltt Timestamp when the lease is acquired, renewed.
+ /// @param subnet_id Subnet identifier.
+ /// @param fqdn_fwd Forward DNS update performed.
+ /// @param fqdn_rev Reverse DNS update performed.
+ /// @param hostname Client's name for the DNS update..
+ Lease4(const isc::asiolink::IOAddress& address,
+ const HWAddrPtr& hw_address,
+ const ClientIdPtr& client_id,
+ const uint32_t valid_lifetime,
+ const time_t cltt,
+ const SubnetID subnet_id,
+ const bool fqdn_fwd = false,
+ const bool fqdn_rev = false,
+ const std::string& hostname = "");
+
+
+ /// @brief Default constructor
+ ///
+ /// Initialize fields that don't have a default constructor.
+ Lease4();
+
+ /// @brief Returns Lease type
+ ///
+ /// Since @c Lease does not define a member for lease type, we implement this
+ /// so we don't store the same value in a billion v4 lease instances.
+ ///
+ /// @return Lease::TYPE_V4
+ virtual Lease::Type getType() const {
+ return (Lease::TYPE_V4);
+ }
+
+ /// @brief Returns name of the lease states specific to DHCPv4.
+ ///
+ /// @todo Currently it simply returns common states for DHCPv4 and DHCPv6.
+ /// This method will have to be extended to handle DHCPv4 specific states
+ /// when they are defined.
+ ///
+ /// @param state Numeric value holding lease states.
+ /// @return Comma separated list of lease state names.
+ static std::string statesToText(const uint32_t state);
+
+ /// @brief Returns a client identifier.
+ ///
+ /// @warning Since the function returns the reference to a vector (not a
+ /// copy), the returned object should be used with caution because it will
+ /// remain valid only for the period of time when an object which returned
+ /// it exists.
+ ///
+ /// @return A reference to a vector holding client identifier,
+ /// or an empty vector if client identifier is NULL.
+ const std::vector<uint8_t>& getClientIdVector() const;
+
+ /// @brief Check if the lease belongs to the client with the given
+ /// identifiers.
+ ///
+ /// This method checks if the lease belongs to the client using the
+ /// specified HW address and/or client identifier. Note that any of the
+ /// pointers passed to this method may be set to null, in which case
+ /// they are treated as unspecified and are not used for matching the
+ /// client with the lease.
+ ///
+ /// According to the DHCPv4 specifications, the client identifier takes
+ /// precedence over the HW address when identifying the lease for the
+ /// client on the server side. In particular, the RFC4361 introduces the
+ /// use of DUID for DHCPv4 which should be a stable identifier for the
+ /// client. The use of stable identifier allows for the correlation of the
+ /// DHCPv4 and DHCPv6 clients in the dual stack networks. It also allows
+ /// for allocating the same lease to the client which hardware (and thus
+ /// MAC address) has changed.
+ ///
+ /// By default, Kea respects the precedence of the client identifier over
+ /// MAC address and when this method finds the match of the client
+ /// identifier with the client identifier stored in the lease, it will
+ /// treat the lease as the lease of this client, even when the HW
+ /// address doesn't match.
+ ///
+ /// The HW address is used for matching the client with the lease only
+ /// when the lease is not associated with any client identifier (client
+ /// identifier for the lease is null) or when the client identifier
+ /// parameter passed to this method is null. This facilitates the following
+ /// cases:
+ /// - client didn't generate client identifier and is only using the chaddr
+ /// field to identify itself.
+ /// - server's administrator configured the server to NOT match client
+ /// identifiers, the client obtained the new lease, and the administrator
+ /// reconfigured the server to match the client identifiers. The client
+ /// is trying to renew its lease and both the client identifier and HW
+ /// address is used for matching the lease which doesn't have the record
+ /// of the client identifier.
+ /// - client obtained the lease using the HW address and client identifier,
+ /// the server's administrator configured the server to NOT match the
+ /// client identifiers, and the client returns to renew the lease. This
+ /// time, the lease has a record of both client identifier and the HW
+ /// address but only the HW address is used for matching the client to
+ /// the lease.
+ ///
+ /// Note that the typical case when the server's administrator may want to
+ /// disable matching the client identifier passed in the client's message
+ /// is when the client is performing multi-stage boot. In such case, the
+ /// client identifiers may change on various stages of the boot, but the
+ /// HW address will remain stable. The server's administrator prefers
+ /// using the HW address for client identification in this case.
+ ///
+ /// It may also be useful to disable matching client identifiers to
+ /// mitigate the problem of broken client implementations which generate
+ /// new client identifiers every time they connect to the network.
+ ///
+ /// @param hw_address Pointer to the HW address of the client.
+ /// @param client_id Pointer to the client identifier structure.
+ ///
+ /// @return true if the lease belongs to the client using the specified
+ /// hardware address and/or client identifier.
+ bool belongsToClient(const HWAddrPtr& hw_address,
+ const ClientIdPtr& client_id) const;
+
+ /// @brief Compare two leases for equality
+ ///
+ /// @param other lease6 object with which to compare
+ bool operator==(const Lease4& other) const;
+
+ /// @brief Compare two leases for inequality
+ ///
+ /// @param other lease6 object with which to compare
+ bool operator!=(const Lease4& other) const {
+ return (!operator==(other));
+ }
+
+ /// @brief Convert lease to printable form
+ ///
+ /// @return Textual representation of lease data
+ virtual std::string toText() const;
+
+ /// @brief Sets IPv4 lease to declined state.
+ ///
+ /// See @ref Lease::decline for detailed description.
+ ///
+ /// @param probation_period valid lifetime will be set to this value
+ void decline(uint32_t probation_period);
+
+ /// @brief Return the JSON representation of a lease
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Returns pointer to the IPv4 lease created from JSON
+ /// representation.
+ ///
+ /// @param element pointer to the data element object to be parsed.
+ /// @return Pointer to the created lease.
+ static Lease4Ptr fromElement(const data::ConstElementPtr& element);
+
+ /// @todo: Add DHCPv4 failover related fields here
+
+ /// @brief Remote identifier for Bulk Lease Query
+ std::vector<uint8_t> remote_id_;
+
+ /// @brief Relay identifier for Bulk Lease Query
+ std::vector<uint8_t> relay_id_;
+};
+
+/// @brief A collection of IPv4 leases.
+typedef std::vector<Lease4Ptr> Lease4Collection;
+
+/// @brief A shared pointer to the collection of IPv4 leases.
+typedef boost::shared_ptr<Lease4Collection> Lease4CollectionPtr;
+
+struct Lease6;
+
+/// @brief Pointer to a Lease6 structure.
+typedef boost::shared_ptr<Lease6> Lease6Ptr;
+
+/// @brief Structure that holds a lease for IPv6 address and/or prefix
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease6 : public Lease {
+
+ /// @brief Lease type
+ ///
+ /// One of normal address, temporary address, or prefix.
+ Lease::Type type_;
+
+ /// @brief IPv6 prefix length
+ ///
+ /// This is used only for prefix delegations and is ignored otherwise.
+ uint8_t prefixlen_;
+
+ /// @brief Identity Association Identifier (IAID)
+ ///
+ /// DHCPv6 stores all addresses and prefixes in IA containers (IA_NA,
+ /// IA_TA, IA_PD). All containers may appear more than once in a message.
+ /// To differentiate between them, the IAID field is present
+ uint32_t iaid_;
+
+ /// @brief Client identifier
+ DuidPtr duid_;
+
+ /// @brief Preferred lifetime
+ ///
+ /// This parameter specifies the preferred lifetime since the lease was
+ /// assigned or renewed (cltt), expressed in seconds.
+ uint32_t preferred_lft_;
+
+ /// @brief Remaining preferred lifetime
+ ///
+ /// Expressed as number of seconds since current time, also
+ /// preferred lifetime - age where age is old cltt - new cltt.
+ /// This parameter is used only when reuseable_valid_lft_ is not zero,
+ /// i.e. when the lease can be reused.
+ uint32_t reuseable_preferred_lft_;
+
+ /// @brief Action on extended info tables.
+ typedef enum {
+ ACTION_IGNORE, ///< ignore extended info,
+ ACTION_DELETE, ///< delete reference to the lease
+ ACTION_UPDATE ///< update extended info tables.
+ } ExtendedInfoAction;
+
+ /// @brief Record the action on extended info tables in the lease.
+ ExtendedInfoAction extended_info_action_;
+
+ /// @todo: Add DHCPv6 failover related fields here
+
+ /// @brief Constructor
+ /// @param type Lease type.
+ /// @param addr Assigned address.
+ /// @param duid A pointer to an object representing DUID.
+ /// @param iaid IAID.
+ /// @param preferred Preferred lifetime.
+ /// @param valid Valid lifetime.
+ /// @param subnet_id A Subnet identifier.
+ /// @param hwaddr hardware/MAC address (optional)
+ /// @param prefixlen An address prefix length (optional, defaults to 128)
+ Lease6(Lease::Type type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
+ uint32_t iaid, uint32_t preferred, uint32_t valid,
+ SubnetID subnet_id, const HWAddrPtr& hwaddr = HWAddrPtr(),
+ uint8_t prefixlen = 128);
+
+ /// @brief Constructor, including FQDN data.
+ ///
+ /// @param type Lease type.
+ /// @param addr Assigned address.
+ /// @param duid A pointer to an object representing DUID.
+ /// @param iaid IAID.
+ /// @param preferred Preferred lifetime.
+ /// @param valid Valid lifetime.
+ /// @param subnet_id A Subnet identifier.
+ /// @param fqdn_fwd If true, forward DNS update is performed for a lease.
+ /// @param fqdn_rev If true, reverse DNS update is performed for a lease.
+ /// @param hostname FQDN of the client which gets the lease.
+ /// @param hwaddr hardware address (MAC), may be NULL
+ /// @param prefixlen An address prefix length (optional, defaults to 128)
+ Lease6(Lease::Type type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
+ uint32_t iaid, uint32_t preferred, uint32_t valid,
+ SubnetID subnet_id, const bool fqdn_fwd,
+ const bool fqdn_rev, const std::string& hostname,
+ const HWAddrPtr& hwaddr = HWAddrPtr(), uint8_t prefixlen = 128);
+
+ /// @brief Constructor
+ ///
+ /// Initialize fields that don't have a default constructor.
+ Lease6();
+
+ /// @brief Returns Lease type
+ ///
+ /// Since @c Lease does not define a member for lease type, we implement this
+ /// so code that only has LeasePtr can see what it has.
+ ///
+ /// @return Type of lease
+ virtual Lease::Type getType() const {
+ return (type_);
+ }
+
+ /// @brief Returns name of the lease states specific to DHCPv6.
+ ///
+ /// @todo Currently it simply returns common states for DHCPv4 and DHCPv6.
+ /// This method will have to be extended to handle DHCPv6 specific states
+ /// when they are defined.
+ ///
+ /// @param state Numeric value holding lease states.
+ /// @return Comma separated list of lease state names.
+ static std::string statesToText(const uint32_t state);
+
+ /// @brief Returns a reference to a vector representing a DUID.
+ ///
+ /// @warning Since the function returns the reference to a vector (not a
+ /// copy), the returned object should be used with caution because it will
+ /// remain valid only for the period of time when an object which returned
+ /// it exists.
+ ///
+ /// @return A reference to a vector holding a DUID.
+ const std::vector<uint8_t>& getDuidVector() const;
+
+ /// @brief Sets IPv6 lease to declined state.
+ ///
+ /// See @ref Lease::decline for detailed description.
+ ///
+ /// @param probation_period valid lifetime will be set to this value
+ void decline(uint32_t probation_period);
+
+ /// @brief Compare two leases for equality
+ ///
+ /// @param other lease6 object with which to compare
+ bool operator==(const Lease6& other) const;
+
+ /// @brief Compare two leases for inequality
+ ///
+ /// @param other lease6 object with which to compare
+ bool operator!=(const Lease6& other) const {
+ return (!operator==(other));
+ }
+
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const;
+
+ /// @brief Return the JSON representation of a lease
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Returns pointer to the IPv6 lease created from JSON
+ /// representation.
+ ///
+ /// @param element pointer to the data element object to be parsed.
+ /// @return Pointer to the created lease.
+ static Lease6Ptr fromElement(const data::ConstElementPtr& element);
+};
+
+/// @brief Pointer to a const Lease6 structure.
+typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
+
+/// @brief A collection of IPv6 leases.
+typedef std::vector<Lease6Ptr> Lease6Collection;
+
+/// @brief A shared pointer to the collection of IPv6 leases.
+typedef boost::shared_ptr<Lease6Collection> Lease6CollectionPtr;
+
+/// @brief Stream output operator.
+///
+/// Dumps the output of Lease::toText to the given stream.
+/// @param os output stream to which the output is
+/// @param lease reference to Lease object to dump
+/// @return a reference to the output stream parameter
+std::ostream&
+operator<<(std::ostream& os, const Lease& lease);
+
+/// @brief adapters for linking templates to qualified names
+/// @{
+namespace {
+
+template <isc::util::DhcpSpace D>
+struct AdapterLease {};
+
+template <>
+struct AdapterLease<isc::util::DHCPv4> {
+ using type = Lease4;
+};
+
+template <>
+struct AdapterLease<isc::util::DHCPv6> {
+ using type = Lease6;
+};
+
+} // namespace
+
+template <isc::util::DhcpSpace D>
+using LeaseT = typename AdapterLease<D>::type;
+
+template <isc::util::DhcpSpace D>
+using LeaseTPtr = boost::shared_ptr<LeaseT<D>>;
+/// @}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // LEASE_H
diff --git a/src/lib/dhcpsrv/lease_file_loader.h b/src/lib/dhcpsrv/lease_file_loader.h
new file mode 100644
index 0000000..b4f724a
--- /dev/null
+++ b/src/lib/dhcpsrv/lease_file_loader.h
@@ -0,0 +1,246 @@
+// Copyright (C) 2015-2019 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 LEASE_FILE_LOADER_H
+#define LEASE_FILE_LOADER_H
+
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <util/versioned_csv_file.h>
+#include <dhcpsrv/sanity_checker.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class to manage bulk of leases in the lease files.
+///
+/// This class exposes methods which allow for bulk loading leases from
+/// the lease file and dumping the leases held in memory into the
+/// lease file. There are two major use cases for this class:
+/// - load leases by the DHCP server when the server starts up or
+/// reloads configuration,
+/// - an application performing a lease file cleanup rewrites the whole
+/// lease file to remove the redundant lease entries.
+///
+/// In the former case, this class is used by the @c MemFile_LeaseMgr.
+/// In the latter case, this class is used by the standalone application
+/// which reads the whole lease file into memory (storage) and then
+/// dumps the leases held in the storage to another file.
+///
+/// The methods in this class are templated so as they can be used both
+/// with the @c Lease4Storage and @c Lease6Storage to process the DHCPv4
+/// and DHCPv6 leases respectively.
+///
+class LeaseFileLoader {
+public:
+
+ /// @brief Load leases from the lease file into the specified storage.
+ ///
+ /// This method iterates over the entries in the lease file in the
+ /// CSV format, creates @c Lease4 or @c Lease6 objects and inserts
+ /// them into the storage to which reference is specified as an
+ /// argument. If there are multiple entries for the particular lease
+ /// in the lease file the entries further in the lease file override
+ /// the previous entries.
+ ///
+ /// If the method finds the entry with the valid lifetime of 0 it
+ /// means that the particular lease was released and the method
+ /// removes an existing lease from the container.
+ ///
+ /// @param lease_file A reference to the @c CSVLeaseFile4 or
+ /// @c CSVLeaseFile6 object representing the lease file. The file
+ /// doesn't need to be open because the method re-opens the file.
+ /// @param storage A reference to the container to which leases
+ /// should be inserted.
+ /// @param max_errors Maximum number of corrupted leases in the
+ /// lease file. The method will skip corrupted leases but after
+ /// exceeding the specified number of errors it will throw an
+ /// exception. A value of 0 (default) disables the limit check.
+ /// @param close_file_on_exit A boolean flag which indicates if
+ /// the file should be closed after it has been successfully parsed.
+ /// One case when the file is not opened is when the server starts
+ /// up, reads the leases in the file and then leaves the file open
+ /// for writing future lease updates.
+ /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
+ /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
+ /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
+ ///
+ /// @throw isc::util::CSVFileError when the maximum number of errors
+ /// has been exceeded.
+ template<typename LeaseObjectType, typename LeaseFileType,
+ typename StorageType>
+ static void load(LeaseFileType& lease_file, StorageType& storage,
+ const uint32_t max_errors = 0,
+ const bool close_file_on_exit = true) {
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASE_FILE_LOAD)
+ .arg(lease_file.getFilename());
+
+ // Reopen the file, as we don't know whether the file is open
+ // and we also don't know its current state.
+ lease_file.close();
+ lease_file.open();
+
+ // Create lease sanity checker if checking is enabled.
+ boost::scoped_ptr<SanityChecker> lease_checker;
+ if (SanityChecker::leaseCheckingEnabled(false)) {
+ // Since lease file is loaded during the configuration,
+ // we have to use staging config, rather than current
+ // config for this (false = staging).
+ lease_checker.reset(new SanityChecker());
+ }
+
+ boost::shared_ptr<LeaseObjectType> lease;
+ // Track the number of corrupted leases.
+ uint32_t errcnt = 0;
+ while (true) {
+ // Unable to parse the lease.
+ if (!lease_file.next(lease)) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LEASE_LOAD_ROW_ERROR)
+ .arg(lease_file.getReads())
+ .arg(lease_file.getReadMsg());
+
+ // A value of 0 indicates that we don't return
+ // until the whole file is parsed, even if errors occur.
+ // Otherwise, check if we have exceeded the maximum number
+ // of errors and throw an exception if we have.
+ if (max_errors && (++errcnt > max_errors)) {
+ // If we break parsing the CSV file because of too many
+ // errors, it doesn't make sense to keep the file open.
+ // This is because the caller wouldn't know where we
+ // stopped parsing and where the internal file pointer
+ // is. So, there are probably no cases when the caller
+ // would continue to use the open file.
+ lease_file.close();
+ isc_throw(util::CSVFileError, "exceeded maximum number of"
+ " failures " << max_errors << " to read a lease"
+ " from the lease file "
+ << lease_file.getFilename());
+ }
+ // Skip the corrupted lease.
+ continue;
+ }
+
+ // Lease was found and we successfully parsed it.
+ if (lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA,
+ DHCPSRV_MEMFILE_LEASE_LOAD)
+ .arg(lease->toText());
+
+ if (lease_checker) {
+ // If the lease is insane the checker will reset the lease pointer.
+ // As lease file is loaded during the configuration, we have
+ // to use staging config, rather than current config for this
+ // (false = staging).
+ lease_checker->checkLease(lease, false);
+ if (!lease) {
+ continue;
+ }
+ }
+
+ // Check if this lease exists.
+ typename StorageType::iterator lease_it =
+ storage.find(lease->addr_);
+ // The lease doesn't exist yet. Insert the lease if
+ // it has a positive valid lifetime.
+ if (lease_it == storage.end()) {
+ if (lease->valid_lft_ > 0) {
+ storage.insert(lease);
+ }
+ } else {
+ // The lease exists. If the new entry has a valid
+ // lifetime of 0 it is an indication to remove the
+ // existing entry. Otherwise, we update the lease.
+ if (lease->valid_lft_ == 0) {
+ storage.erase(lease_it);
+
+ } else {
+ // Use replace to re-index leases on update.
+ storage.replace(lease_it, lease);
+ }
+ }
+
+ } else {
+ // Being here means that we hit the end of file.
+ break;
+
+ }
+ }
+
+ if (lease_file.needsConversion()) {
+ LOG_WARN(dhcpsrv_logger,
+ (lease_file.getInputSchemaState()
+ == util::VersionedCSVFile::NEEDS_UPGRADE
+ ? DHCPSRV_MEMFILE_NEEDS_UPGRADING
+ : DHCPSRV_MEMFILE_NEEDS_DOWNGRADING))
+ .arg(lease_file.getFilename())
+ .arg(lease_file.getSchemaVersion());
+ }
+
+ if (close_file_on_exit) {
+ lease_file.close();
+ }
+ }
+
+ /// @brief Write leases from the storage into a lease file
+ ///
+ /// This method iterates over the @c Lease4 or @c Lease6 object in the
+ /// storage specified in the arguments and writes them to the file
+ /// specified in the arguments.
+ ///
+ /// This method writes all entries in the storage to the file, it does
+ /// not perform any checks for expiration or duplication.
+ ///
+ /// The order in which the entries will be written to the file depends
+ /// on the first index in the multi-index container. Currently that
+ /// is the v4 or v6 IP address and they are written from lowest to highest.
+ ///
+ /// Before writing the method will close the file if it is open
+ /// and reopen it for writing. After completion it will close
+ /// the file.
+ ///
+ /// @param lease_file A reference to the @c CSVLeaseFile4 or
+ /// @c CSVLeaseFile6 object representing the lease file. The file
+ /// doesn't need to be open because the method re-opens the file.
+ /// @param storage A reference to the container from which leases
+ /// should be written.
+ ///
+ /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
+ /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
+ /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
+ template<typename LeaseObjectType, typename LeaseFileType,
+ typename StorageType>
+ static void write(LeaseFileType& lease_file, const StorageType& storage) {
+ // Reopen the file, as we don't know whether the file is open
+ // and we also don't know its current state.
+ lease_file.close();
+ lease_file.open();
+
+ // Iterate over the storage area writing out the leases
+ for (typename StorageType::const_iterator lease = storage.begin();
+ lease != storage.end();
+ ++lease) {
+ try {
+ lease_file.append(**lease);
+ } catch (const isc::Exception&) {
+ // Close the file
+ lease_file.close();
+ throw;
+ }
+ }
+
+ // Close the file
+ lease_file.close();
+ }
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // LEASE_FILE_LOADER_H
diff --git a/src/lib/dhcpsrv/lease_file_stats.h b/src/lib/dhcpsrv/lease_file_stats.h
new file mode 100644
index 0000000..6a46480
--- /dev/null
+++ b/src/lib/dhcpsrv/lease_file_stats.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2015-2017 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 LEASE_FILE_STATS_H
+#define LEASE_FILE_STATS_H
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Provides statistics for leases.
+///
+/// This class provides a common space for statistics that we wish
+/// to keep about leases. Currently this is for use with lease files
+/// but it may be expanded in the future.
+class LeaseFileStats {
+public:
+ /// @brief Constructor
+ ///
+ /// Initializes the stats variables to zeros
+ LeaseFileStats() {
+ clearStatistics();
+ }
+
+ /// @brief Destructor
+ ~LeaseFileStats() {
+ }
+
+ /// @brief Gets the number of attempts to read a lease
+ uint32_t getReads() const {
+ return (reads_);
+ }
+
+ /// @brief Gets the number of leases read
+ uint32_t getReadLeases() const {
+ return (read_leases_);
+ }
+
+ /// @brief Gets the number of errors when reading leases
+ uint32_t getReadErrs() const {
+ return (read_errs_);
+ }
+
+ /// @brief Gets the number of attempts to write a lease
+ uint32_t getWrites() const {
+ return (writes_);
+ }
+
+ /// @brief Gets the number of leases written
+ uint32_t getWriteLeases() const {
+ return (write_leases_);
+ }
+
+ /// @brief Gets the number of errors when writing leases
+ uint32_t getWriteErrs() const {
+ return (write_errs_);
+ }
+
+ /// @brief Clears the statistics
+ void clearStatistics() {
+ reads_ = 0;
+ read_leases_ = 0;
+ read_errs_ = 0;
+ writes_ = 0;
+ write_leases_ = 0;
+ write_errs_ = 0;
+ }
+
+protected:
+ /// @brief Number of attempts to read a lease
+ uint32_t reads_;
+
+ /// @brief Number of leases read
+ uint32_t read_leases_;
+
+ /// @brief Number of errors when reading
+ uint32_t read_errs_;
+
+ /// @brief Number of attempts to write a lease
+ uint32_t writes_;
+
+ /// @brief Number of lease written
+ uint32_t write_leases_;
+
+ /// @brief Number of errors when writing
+ uint32_t write_errs_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // LEASE_FILE_STATS_H
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
new file mode 100644
index 0000000..2b588b6
--- /dev/null
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -0,0 +1,1270 @@
+// Copyright (C) 2012-2023 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 <dhcp/libdhcp++.h>
+#include <dhcp/option_custom.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <exceptions/exceptions.h>
+#include <stats/stats_mgr.h>
+#include <util/encode/hex.h>
+
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <time.h>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+IOServicePtr LeaseMgr::io_service_ = IOServicePtr();
+
+LeasePageSize::LeasePageSize(const size_t page_size)
+ : page_size_(page_size) {
+
+ if (page_size_ == 0) {
+ isc_throw(OutOfRange, "page size of retrieved leases must not be 0");
+ }
+
+ if (page_size_ > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(OutOfRange, "page size of retrieved leases must not be greater than "
+ << std::numeric_limits<uint32_t>::max());
+ }
+}
+
+Lease6Ptr
+LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const {
+ Lease6Collection col = getLeases6(type, duid, iaid, subnet_id);
+
+ if (col.size() > 1) {
+ isc_throw(MultipleRecords, "More than one lease found for type "
+ << static_cast<int>(type) << ", duid "
+ << duid.toText() << ", iaid " << iaid
+ << " and subnet-id " << subnet_id);
+ }
+ if (col.empty()) {
+ return (Lease6Ptr());
+ }
+ return (*col.begin());
+}
+
+void
+LeaseMgr::recountLeaseStats4() {
+ using namespace stats;
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+
+ LeaseStatsQueryPtr query = startLeaseStatsQuery4();
+ if (!query) {
+ /// NULL means not backend does not support recounting.
+ return;
+ }
+
+ // Zero out the global stats.
+ // Cumulative counters ("reclaimed-declined-addresses", "reclaimed-leases",
+ // "cumulative-assigned-addresses") never get zeroed.
+ int64_t zero = 0;
+ stats_mgr.setValue("declined-addresses", zero);
+
+ // Create if it does not exit reclaimed declined leases global stats.
+ if (!stats_mgr.getObservation("reclaimed-declined-addresses")) {
+ stats_mgr.setValue("reclaimed-declined-addresses", zero);
+ }
+
+ // Create if it does not exit reclaimed leases global stats.
+ if (!stats_mgr.getObservation("reclaimed-leases")) {
+ stats_mgr.setValue("reclaimed-leases", zero);
+ }
+
+ // Create if it does not exit cumulative global stats.
+ if (!stats_mgr.getObservation("cumulative-assigned-addresses")) {
+ stats_mgr.setValue("cumulative-assigned-addresses", zero);
+ }
+
+ // Clear subnet level stats. This ensures we don't end up with corner
+ // cases that leave stale values in place.
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+
+ for (Subnet4Collection::const_iterator subnet = subnets->begin();
+ subnet != subnets->end(); ++subnet) {
+ SubnetID subnet_id = (*subnet)->getID();
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "assigned-addresses"),
+ zero);
+
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"),
+ zero);
+
+ const std::string name_rec_dec(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ if (!stats_mgr.getObservation(name_rec_dec)) {
+ stats_mgr.setValue(name_rec_dec, zero);
+ }
+
+ const std::string name_rec(StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ if (!stats_mgr.getObservation(name_rec)) {
+ stats_mgr.setValue(name_rec, zero);
+ }
+
+ for (const auto& pool : (*subnet)->getPools(Lease::TYPE_V4)) {
+ const std::string name_aa(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-addresses")));
+ if (!stats_mgr.getObservation(name_aa)) {
+ stats_mgr.setValue(name_aa, zero);
+ }
+
+ const std::string& name_da(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "declined-addresses")));
+ if (!stats_mgr.getObservation(name_da)) {
+ stats_mgr.setValue(name_da, zero);
+ }
+
+ const std::string& name_rec_dec(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-declined-addresses")));
+ if (!stats_mgr.getObservation(name_rec_dec)) {
+ stats_mgr.setValue(name_rec_dec, zero);
+ }
+
+ const std::string& name_rec(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-leases")));
+ if (!stats_mgr.getObservation(name_rec)) {
+ stats_mgr.setValue(name_rec, zero);
+ }
+ }
+ }
+
+ // Get counts per state per subnet. Iterate over the result set
+ // updating the subnet and global values.
+ LeaseStatsRow row;
+ while (query->getNextRow(row)) {
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ // Add to subnet level value.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ "assigned-addresses"),
+ row.state_count_);
+ } else if (row.lease_state_ == Lease::STATE_DECLINED) {
+ // Set subnet level value.
+ stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ "declined-addresses"),
+ row.state_count_);
+
+ // Add to the global value.
+ stats_mgr.addValue("declined-addresses", row.state_count_);
+
+ // Add to subnet level value.
+ // Declined leases also count as assigned.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ "assigned-addresses"),
+ row.state_count_);
+ }
+ }
+
+ query = startPoolLeaseStatsQuery4();
+ if (!query) {
+ /// NULL means not backend does not support recounting.
+ return;
+ }
+
+ // Get counts per state per subnet and pool. Iterate over the result set
+ // updating the subnet and pool and global values.
+ while (query->getNextRow(row)) {
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ // Add to subnet and pool level value.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ StatsMgr::generateName("pool", row.pool_id_,
+ "assigned-addresses")),
+ row.state_count_);
+ } else if (row.lease_state_ == Lease::STATE_DECLINED) {
+ // Set subnet and pool level value.
+ stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ StatsMgr::generateName("pool", row.pool_id_,
+ "declined-addresses")),
+ row.state_count_);
+
+ // Add to subnet and pool level value.
+ // Declined leases also count as assigned.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ StatsMgr::generateName("pool", row.pool_id_,
+ "assigned-addresses")),
+ row.state_count_);
+ }
+ }
+}
+
+LeaseStatsQuery::LeaseStatsQuery(const SelectMode& select_mode)
+ : first_subnet_id_(0), last_subnet_id_(0), select_mode_(select_mode) {
+ if (select_mode != ALL_SUBNETS && select_mode != ALL_SUBNET_POOLS) {
+ isc_throw(BadValue, "LeaseStatsQuery: mode must be either ALL_SUBNETS or ALL_SUBNET_POOLS");
+ }
+}
+
+LeaseStatsQuery::LeaseStatsQuery(const SubnetID& subnet_id)
+ : first_subnet_id_(subnet_id), last_subnet_id_(0),
+ select_mode_(SINGLE_SUBNET) {
+
+ if (first_subnet_id_ == 0) {
+ isc_throw(BadValue, "LeaseStatsQuery: subnet_id_ must be > 0");
+ }
+}
+
+LeaseStatsQuery::LeaseStatsQuery(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id)
+ : first_subnet_id_(first_subnet_id), last_subnet_id_(last_subnet_id),
+ select_mode_(SUBNET_RANGE) {
+
+ if (first_subnet_id_ == 0) {
+ isc_throw(BadValue, "LeaseStatsQuery: first_subnet_id_ must be > 0");
+ }
+
+ if (last_subnet_id_ == 0) {
+ isc_throw(BadValue, "LeaseStatsQuery: last_subnet_id_ must be > 0");
+ }
+
+ if (last_subnet_id_ <= first_subnet_id_) {
+ isc_throw(BadValue,
+ "LeaseStatsQuery: last_subnet_id_must be > first_subnet_id_");
+ }
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startLeaseStatsQuery4() {
+ return(LeaseStatsQueryPtr());
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startPoolLeaseStatsQuery4() {
+ return(LeaseStatsQueryPtr());
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startSubnetLeaseStatsQuery4(const SubnetID& /* subnet_id */) {
+ return(LeaseStatsQueryPtr());
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startSubnetRangeLeaseStatsQuery4(const SubnetID& /* first_subnet_id */,
+ const SubnetID& /* last_subnet_id */) {
+ return(LeaseStatsQueryPtr());
+}
+
+bool
+LeaseStatsQuery::getNextRow(LeaseStatsRow& /*row*/) {
+ return (false);
+}
+
+void
+LeaseMgr::recountLeaseStats6() {
+ using namespace stats;
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+
+ LeaseStatsQueryPtr query = startLeaseStatsQuery6();
+ if (!query) {
+ /// NULL means not backend does not support recounting.
+ return;
+ }
+
+ // Zero out the global stats.
+ // Cumulative counters ("reclaimed-declined-addresses", "reclaimed-leases",
+ // "cumulative-assigned-nas", "cumulative-assigned-pds") never get zeroed.
+ int64_t zero = 0;
+ stats_mgr.setValue("declined-addresses", zero);
+
+ if (!stats_mgr.getObservation("reclaimed-declined-addresses")) {
+ stats_mgr.setValue("reclaimed-declined-addresses", zero);
+ }
+
+ if (!stats_mgr.getObservation("reclaimed-leases")) {
+ stats_mgr.setValue("reclaimed-leases", zero);
+ }
+
+ // Create if it does not exit cumulative nas global stats.
+ if (!stats_mgr.getObservation("cumulative-assigned-nas")) {
+ stats_mgr.setValue("cumulative-assigned-nas", zero);
+ }
+
+ // Create if it does not exit cumulative pds global stats.
+ if (!stats_mgr.getObservation("cumulative-assigned-pds")) {
+ stats_mgr.setValue("cumulative-assigned-pds", zero);
+ }
+
+ // Clear subnet level stats. This ensures we don't end up with corner
+ // cases that leave stale values in place.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+
+ for (Subnet6Collection::const_iterator subnet = subnets->begin();
+ subnet != subnets->end(); ++subnet) {
+ SubnetID subnet_id = (*subnet)->getID();
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "assigned-nas"),
+ zero);
+
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "assigned-pds"),
+ zero);
+
+ stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"),
+ zero);
+
+ if (!stats_mgr.getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"))) {
+ stats_mgr.setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"),
+ zero);
+ }
+
+ if (!stats_mgr.getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"))) {
+ stats_mgr.setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"),
+ zero);
+ }
+
+ for (const auto& pool : (*subnet)->getPools(Lease::TYPE_NA)) {
+ const std::string& name_anas(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "assigned-nas")));
+ if (!stats_mgr.getObservation(name_anas)) {
+ stats_mgr.setValue(name_anas, zero);
+ }
+
+ const std::string& name_da(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "declined-addresses")));
+ if (!stats_mgr.getObservation(name_da)) {
+ stats_mgr.setValue(name_da, zero);
+ }
+
+ const std::string name_rec_dec(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-declined-addresses")));
+ if (!stats_mgr.getObservation(name_rec_dec)) {
+ stats_mgr.setValue(name_rec_dec, zero);
+ }
+
+ const std::string& name_rec(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", pool->getID(),
+ "reclaimed-leases")));
+ if (!stats_mgr.getObservation(name_rec)) {
+ stats_mgr.setValue(name_rec, zero);
+ }
+ }
+
+ for (const auto& pool : (*subnet)->getPools(Lease::TYPE_PD)) {
+ const std::string& name_apds(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "assigned-pds")));
+ if (!stats_mgr.getObservation(name_apds)) {
+ stats_mgr.setValue(name_apds, zero);
+ }
+
+ const std::string& name_rec(StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", pool->getID(),
+ "reclaimed-leases")));
+ if (!stats_mgr.getObservation(name_rec)) {
+ stats_mgr.setValue(name_rec, zero);
+ }
+ }
+ }
+
+ // Get counts per state per subnet. Iterate over the result set
+ // updating the subnet and global values.
+ LeaseStatsRow row;
+ while (query->getNextRow(row)) {
+ switch(row.lease_type_) {
+ case Lease::TYPE_NA:
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ // Add to subnet level value.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ "assigned-nas"),
+ row.state_count_);
+ } else if (row.lease_state_ == Lease::STATE_DECLINED) {
+ // Set subnet level value.
+ stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ "declined-addresses"),
+ row.state_count_);
+
+ // Add to the global value.
+ stats_mgr.addValue("declined-addresses", row.state_count_);
+
+ // Add to subnet level value.
+ // Declined leases also count as assigned.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ "assigned-nas"),
+ row.state_count_);
+ }
+ break;
+
+ case Lease::TYPE_PD:
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ // Set subnet level value.
+ stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ "assigned-pds"),
+ row.state_count_);
+ }
+ break;
+
+ default:
+ // We don't support TYPE_TAs yet
+ break;
+ }
+ }
+
+ query = startPoolLeaseStatsQuery6();
+ if (!query) {
+ /// NULL means not backend does not support recounting.
+ return;
+ }
+
+ // Get counts per state per subnet and pool. Iterate over the result set
+ // updating the subnet and pool and global values.
+ while (query->getNextRow(row)) {
+ switch(row.lease_type_) {
+ case Lease::TYPE_NA:
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ // Add to subnet and pool level value.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ StatsMgr::generateName("pool", row.pool_id_,
+ "assigned-nas")),
+ row.state_count_);
+ } else if (row.lease_state_ == Lease::STATE_DECLINED) {
+ // Set subnet and pool level value.
+ stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ StatsMgr::generateName("pool", row.pool_id_,
+ "declined-addresses")),
+ row.state_count_);
+
+ // Add to subnet and pool level value.
+ // Declined leases also count as assigned.
+ stats_mgr.addValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ StatsMgr::generateName("pool", row.pool_id_,
+ "assigned-nas")),
+ row.state_count_);
+ }
+ break;
+
+ case Lease::TYPE_PD:
+ if (row.lease_state_ == Lease::STATE_DEFAULT) {
+ // Set subnet and pool level value.
+ stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+ StatsMgr::generateName("pd-pool", row.pool_id_,
+ "assigned-pds")),
+ row.state_count_);
+ }
+ break;
+
+ default:
+ // We don't support TYPE_TAs yet
+ break;
+ }
+ }
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startLeaseStatsQuery6() {
+ return(LeaseStatsQueryPtr());
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startPoolLeaseStatsQuery6() {
+ return(LeaseStatsQueryPtr());
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startSubnetLeaseStatsQuery6(const SubnetID& /* subnet_id */) {
+ return(LeaseStatsQueryPtr());
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startSubnetRangeLeaseStatsQuery6(const SubnetID& /* first_subnet_id */,
+ const SubnetID& /* last_subnet_id */) {
+ return(LeaseStatsQueryPtr());
+}
+
+std::string
+LeaseMgr::getDBVersion() {
+ isc_throw(NotImplemented, "LeaseMgr::getDBVersion() called");
+}
+
+void
+LeaseMgr::setExtendedInfoTablesEnabled(const DatabaseConnection::ParameterMap& parameters) {
+ std::string extended_info_tables;
+ try {
+ extended_info_tables = parameters.at("extended-info-tables");
+ } catch (const exception&) {
+ extended_info_tables = "false";
+ }
+ // If extended_info_tables is 'true' we will enable them.
+ if (extended_info_tables == "true") {
+ setExtendedInfoTablesEnabled(true);
+ }
+}
+
+bool
+LeaseMgr::upgradeLease4ExtendedInfo(const Lease4Ptr& lease,
+ CfgConsistency::ExtendedInfoSanity check) {
+ static OptionDefinitionPtr rai_def;
+
+ bool changed = false;
+ if (!lease) {
+ return (changed);
+ }
+
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) {
+ return (changed);
+ }
+
+ ConstElementPtr user_context = lease->getContext();
+ if (!user_context) {
+ return (changed);
+ }
+
+ if (!rai_def) {
+ rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ }
+
+ if (!rai_def) {
+ // The definition is set when libdhcp++ is loaded so it is impossible
+ // to not be able to get it... so should not happen!
+ isc_throw(Unexpected, "can't find RAI option definition?!");
+ }
+
+ ConstElementPtr isc;
+ ConstElementPtr extended_info;
+ ElementPtr mutable_user_context;
+ ElementPtr mutable_isc;
+ string verifying = "";
+ bool removed_extended_info = false;
+
+ try {
+ verifying = "user context";
+ if (user_context->getType() != Element::map) {
+ isc_throw(BadValue, "user context is not a map");
+ }
+ if (user_context->empty()) {
+ changed = true;
+ lease->setContext(ConstElementPtr());
+ return (changed);
+ }
+
+ verifying = "isc";
+ isc = user_context->get("ISC");
+ if (!isc) {
+ return (changed);
+ }
+ mutable_user_context =
+ boost::const_pointer_cast<Element>(user_context);
+ if (!mutable_user_context) {
+ // Should not happen...
+ mutable_user_context = copy(user_context, 0);
+ lease->setContext(mutable_user_context);
+ }
+
+ if (isc->getType() != Element::map) {
+ isc_throw(BadValue, "ISC entry is not a map");
+ }
+ if (isc->empty()) {
+ changed = true;
+ mutable_user_context->remove("ISC");
+ if (mutable_user_context->empty()) {
+ lease->setContext(ConstElementPtr());
+ }
+ return (changed);
+ }
+
+ verifying = "relay-agent-info";
+ extended_info = isc->get("relay-agent-info");
+ if (!extended_info) {
+ return (changed);
+ }
+ mutable_isc = boost::const_pointer_cast<Element>(isc);
+ if (!mutable_isc) {
+ // Should not happen...
+ mutable_isc = copy(isc, 0);
+ mutable_user_context->set("ISC", mutable_isc);
+ }
+
+ if (extended_info->getType() == Element::string) {
+ // Upgrade
+ changed = true;
+ ElementPtr upgraded = Element::createMap();
+ upgraded->set("sub-options", extended_info);
+ mutable_isc->set("relay-agent-info", upgraded);
+
+ // Try to decode sub-options.
+ verifying = "rai";
+ string rai_hex = extended_info->stringValue();
+ vector<uint8_t> rai_data;
+ str::decodeFormattedHexString(rai_hex, rai_data);
+ OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4, rai_data));
+ if (!rai) {
+ isc_throw(BadValue, "can't create RAI option");
+ }
+
+ OptionPtr remote_id = rai->getOption(RAI_OPTION_REMOTE_ID);
+ if (remote_id) {
+ vector<uint8_t> bytes = remote_id->toBinary(false);
+ if (bytes.size() > 0) {
+ upgraded->set("remote-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+
+ OptionPtr relay_id = rai->getOption(RAI_OPTION_RELAY_ID);
+ if (relay_id) {
+ vector<uint8_t> bytes = relay_id->toBinary(false);
+ if (bytes.size() > 0) {
+ upgraded->set("relay-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED)
+ .arg(lease->addr_.toText());
+ return (changed);
+ } else if (extended_info->getType() != Element::map) {
+ mutable_isc->remove("relay-agent-info");
+ removed_extended_info = true;
+ isc_throw(BadValue, "relay-agent-info is not a map or a string");
+ }
+
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_FIX) {
+ return (changed);
+ }
+
+ // Try to decode sub-options.
+ ConstElementPtr sub_options = extended_info->get("sub-options");
+ if (sub_options) {
+ verifying = "sub-options";
+ if (sub_options->getType() != Element::string) {
+ mutable_isc->remove("relay-agent-info");
+ removed_extended_info = true;
+ isc_throw(BadValue, "sub-options is not a string");
+ }
+ string rai_hex = sub_options->stringValue();
+ vector<uint8_t> rai_data;
+ str::decodeFormattedHexString(rai_hex, rai_data);
+ }
+
+ ConstElementPtr remote_id = extended_info->get("remote-id");
+ if (remote_id) {
+ verifying = "remote-id";
+ if (remote_id->getType() != Element::string) {
+ mutable_isc->remove("relay-agent-info");
+ removed_extended_info = true;
+ isc_throw(BadValue, "remote-id is not a string");
+ }
+ string remote_id_hex = remote_id->stringValue();
+ vector<uint8_t> remote_id_data;
+ encode::decodeHex(remote_id_hex, remote_id_data);
+ if (remote_id_data.empty()) {
+ mutable_isc->remove("relay-agent-info");
+ removed_extended_info = true;
+ isc_throw(BadValue, "remote-id is empty");
+ }
+ }
+
+ ConstElementPtr relay_id = extended_info->get("relay-id");
+ if (relay_id) {
+ verifying = "relay-id";
+ if (relay_id->getType() != Element::string) {
+ mutable_isc->remove("relay-agent-info");
+ removed_extended_info = true;
+ isc_throw(BadValue, "relay-id is not a string");
+ }
+ string relay_id_hex = relay_id->stringValue();
+ vector<uint8_t> relay_id_data;
+ encode::decodeHex(relay_id_hex, relay_id_data);
+ if (relay_id_data.empty()) {
+ mutable_isc->remove("relay-agent-info");
+ removed_extended_info = true;
+ isc_throw(BadValue, "relay-id is empty");
+ }
+ }
+
+ if (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC) {
+ return (changed);
+ }
+
+ verifying = "relay-agent-info";
+ for (auto elem : extended_info->mapValue()) {
+ if ((elem.first != "sub-options") &&
+ (elem.first != "remote-id") &&
+ (elem.first != "relay-id") &&
+ (elem.first != "comment")) {
+ mutable_isc->remove("relay-agent-info");
+ removed_extended_info = true;
+ isc_throw(BadValue, "spurious '" << elem.first <<
+ "' entry in relay-agent-info");
+ }
+ }
+
+ return (changed);
+ } catch (const exception& ex) {
+ ostringstream err;
+ err << "in " << verifying << " a problem was found: " << ex.what();
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(err.str());
+
+ changed = true;
+ if (verifying == "user context") {
+ lease->setContext(ConstElementPtr());
+ } else if (verifying == "isc") {
+ mutable_user_context->remove("ISC");
+ if (mutable_user_context->empty()) {
+ lease->setContext(ConstElementPtr());
+ }
+ } else {
+ if (!removed_extended_info) {
+ mutable_isc->remove("relay-agent-info");
+ }
+ if (mutable_isc->empty()) {
+ mutable_user_context->remove("ISC");
+ if (mutable_user_context->empty()) {
+ lease->setContext(ConstElementPtr());
+ }
+ }
+ }
+ return (changed);
+ }
+}
+
+bool
+LeaseMgr::upgradeLease6ExtendedInfo(const Lease6Ptr& lease,
+ CfgConsistency::ExtendedInfoSanity check) {
+ bool changed = false;
+ if (!lease) {
+ return (changed);
+ }
+
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) {
+ return (changed);
+ }
+
+ ConstElementPtr user_context = lease->getContext();
+ if (!user_context) {
+ return (changed);
+ }
+
+ ConstElementPtr isc;
+ ConstElementPtr relay_info;
+ ElementPtr mutable_user_context;
+ ElementPtr mutable_isc;
+ string verifying = "";
+ bool removed_relay_info = false;
+ bool upgraded = false;
+ bool have_both = false;
+ int i = -1;
+
+ try {
+ verifying = "user context";
+ if (user_context->getType() != Element::map) {
+ isc_throw(BadValue, "user context is not a map");
+ }
+ if (user_context->empty()) {
+ changed = true;
+ lease->setContext(ConstElementPtr());
+ return (changed);
+ }
+
+ verifying = "isc";
+ isc = user_context->get("ISC");
+ if (!isc) {
+ return (changed);
+ }
+ mutable_user_context =
+ boost::const_pointer_cast<Element>(user_context);
+ if (!mutable_user_context) {
+ // Should not happen...
+ mutable_user_context = copy(user_context, 0);
+ lease->setContext(mutable_user_context);
+ }
+
+ if (isc->getType() != Element::map) {
+ isc_throw(BadValue, "ISC entry is not a map");
+ }
+ if (isc->empty()) {
+ changed = true;
+ mutable_user_context->remove("ISC");
+ if (mutable_user_context->empty()) {
+ lease->setContext(ConstElementPtr());
+ }
+ return (changed);
+ }
+ mutable_isc = boost::const_pointer_cast<Element>(isc);
+ if (!mutable_isc) {
+ // Should not happen...
+ mutable_isc = copy(isc, 0);
+ mutable_user_context->set("ISC", mutable_isc);
+ }
+
+ relay_info = mutable_isc->get("relays");
+ if (relay_info && isc->contains("relay-info")) {
+ changed = true;
+ mutable_isc->remove("relays");
+ have_both = true;
+ relay_info.reset();
+ }
+ if (relay_info) {
+ // Upgrade
+ changed = true;
+ upgraded = true;
+ verifying = "relays";
+ mutable_isc->set("relay-info", relay_info);
+ mutable_isc->remove("relays");
+
+ if (relay_info->getType() != Element::list) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relays is not a list");
+ }
+ if (relay_info->empty()) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relays is empty");
+ }
+
+ verifying = "relay";
+ for (i = 0; i < relay_info->size(); ++i) {
+ ElementPtr relay = relay_info->getNonConst(i);
+ if (!relay) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "null relay#" << i);
+ }
+ if (relay->getType() != Element::map) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relay#" << i << " is not a map");
+ }
+
+ // Try to decode options.
+ ConstElementPtr options = relay->get("options");
+ if (!options) {
+ continue;
+ }
+
+ verifying = "options";
+ if (options->getType() != Element::string) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "options is not a string");
+ }
+ string options_hex = options->stringValue();
+ vector<uint8_t> options_data;
+ str::decodeFormattedHexString(options_hex, options_data);
+ OptionCollection opts;
+ LibDHCP::unpackOptions6(options_data, DHCP6_OPTION_SPACE, opts);
+
+ auto remote_id_it = opts.find(D6O_REMOTE_ID);
+ if (remote_id_it != opts.end()) {
+ OptionPtr remote_id = remote_id_it->second;
+ if (remote_id) {
+ vector<uint8_t> bytes = remote_id->toBinary(false);
+ if (bytes.size() > 0) {
+ relay->set("remote-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+ }
+
+ auto relay_id_it = opts.find(D6O_RELAY_ID);
+ if (relay_id_it != opts.end()) {
+ OptionPtr relay_id = relay_id_it->second;
+ if (relay_id) {
+ vector<uint8_t> bytes = relay_id->toBinary(false);
+ if (bytes.size() > 0) {
+ relay->set("relay-id",
+ Element::create(encode::encodeHex(bytes)));
+ }
+ }
+ }
+ }
+ }
+
+ verifying = (upgraded ? "relays" : "relay-info");
+ i = -1;
+ relay_info = mutable_isc->get("relay-info");
+ if (!relay_info) {
+ return (changed);
+ }
+ if (!upgraded && (relay_info->getType() != Element::list)) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relay-info is not a list");
+ }
+ if (!upgraded && relay_info->empty()) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relay-info is empty");
+ }
+
+ verifying = "relay";
+ for (i = 0; i < relay_info->size(); ++i) {
+ ElementPtr relay = relay_info->getNonConst(i);
+ if (!upgraded && !relay) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "null relay#" << i);
+ }
+ if (!upgraded && (relay->getType() != Element::map)) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relay#" << i << " is not a map");
+ }
+
+ ConstElementPtr options = relay->get("options");
+ if (!upgraded && options) {
+ // Try to decode options.
+ verifying = "options";
+ if (options->getType() != Element::string) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "options is not a string");
+ }
+ string options_hex = options->stringValue();
+ vector<uint8_t> options_data;
+ str::decodeFormattedHexString(options_hex, options_data);
+ OptionCollection opts;
+ LibDHCP::unpackOptions6(options_data, DHCP6_OPTION_SPACE, opts);
+ }
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_FIX) {
+ continue;
+ }
+
+ verifying = "link";
+ ConstElementPtr link_addr = relay->get("link");
+ if (!link_addr) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "no link");
+ }
+ if (link_addr->getType() != Element::string) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "link is not a string");
+ }
+ IOAddress laddr(link_addr->stringValue());
+ if (!laddr.isV6()) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "link is not an IPv6 address");
+ }
+
+ ConstElementPtr remote_id = relay->get("remote-id");
+ if (!upgraded && remote_id) {
+ verifying = "remote-id";
+ if (remote_id->getType() != Element::string) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "remote-id is not a string");
+ }
+ string remote_id_hex = remote_id->stringValue();
+ vector<uint8_t> remote_id_data;
+ encode::decodeHex(remote_id_hex, remote_id_data);
+ if (remote_id_data.empty()) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "remote-id is empty");
+ }
+ }
+
+ ConstElementPtr relay_id = relay->get("relay-id");
+ if (!upgraded && relay_id) {
+ verifying = "relay-id";
+ if (relay_id->getType() != Element::string) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relay-id is not a string");
+ }
+ string relay_id_hex = relay_id->stringValue();
+ vector<uint8_t> relay_id_data;
+ encode::decodeHex(relay_id_hex, relay_id_data);
+ if (relay_id_data.empty()) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "relay-id is empty");
+ }
+ }
+
+ if (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC) {
+ continue;
+ }
+
+ verifying = "peer";
+ ConstElementPtr peer_addr = relay->get("peer");
+ if (!peer_addr) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "no peer");
+ }
+ if (peer_addr->getType() != Element::string) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "peer is not a string");
+ }
+ IOAddress paddr(peer_addr->stringValue());
+ if (!paddr.isV6()) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "peer is not an IPv6 address");
+ }
+
+ verifying = "hop";
+ ConstElementPtr hop = relay->get("hop");
+ if (!hop) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "no hop");
+ }
+ if (hop->getType() != Element::integer) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "hop is not an integer");
+ }
+
+ verifying = (upgraded ? "relays" : "relay-info");
+ for (auto elem : relay->mapValue()) {
+ if ((elem.first != "hop") &&
+ (elem.first != "link") &&
+ (elem.first != "peer") &&
+ (elem.first != "options") &&
+ (elem.first != "remote-id") &&
+ (elem.first != "relay-id") &&
+ (elem.first != "comment")) {
+ mutable_isc->remove("relay-info");
+ removed_relay_info = true;
+ isc_throw(BadValue, "spurious '" << elem.first << "' entry");
+ }
+ }
+ }
+
+ if (upgraded) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED)
+ .arg(lease->addr_.toText());
+ }
+
+ return (changed);
+ } catch (const exception& ex) {
+ ostringstream err;
+ err << "in " << verifying;
+ if (i >= 0) {
+ err << " [relay#" << i << "]";
+ }
+ err << " a problem was found: " << ex.what();
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(err.str());
+
+ changed = true;
+ have_both = !have_both;
+ if (verifying == "user context") {
+ lease->setContext(ConstElementPtr());
+ } else if (verifying == "isc") {
+ mutable_user_context->remove("ISC");
+ if (mutable_user_context->empty()) {
+ lease->setContext(ConstElementPtr());
+ }
+ } else {
+ if (!removed_relay_info) {
+ mutable_isc->remove("relay-info");
+ }
+ if (mutable_isc->empty()) {
+ mutable_user_context->remove("ISC");
+ if (mutable_user_context->empty()) {
+ lease->setContext(ConstElementPtr());
+ }
+ }
+ }
+ return (changed);
+ }
+}
+
+void
+LeaseMgr::extractLease4ExtendedInfo(const Lease4Ptr& lease,
+ bool ignore_errors) {
+ if (!lease) {
+ return;
+ }
+
+ ConstElementPtr user_context = lease->getContext();
+ if (!user_context) {
+ return;
+ }
+ if (user_context->getType() != Element::map) {
+ if (ignore_errors) {
+ return;
+ }
+ isc_throw(BadValue, "user context is not a map");
+ }
+ if (user_context->empty()) {
+ return;
+ }
+
+ ConstElementPtr isc = user_context->get("ISC");
+ if (!isc) {
+ return;
+ }
+ if (isc->getType() != Element::map) {
+ if (ignore_errors) {
+ return;
+ }
+ isc_throw(BadValue, "ISC entry is not a map");
+ }
+ if (isc->empty()) {
+ return;
+ }
+
+ ConstElementPtr extended_info = isc->get("relay-agent-info");
+ if (!extended_info) {
+ return;
+ }
+ if (extended_info->getType() != Element::map) {
+ if (ignore_errors) {
+ return;
+ }
+ isc_throw(BadValue, "relay-agent-info is not a map");
+ }
+ if (extended_info->empty()) {
+ return;
+ }
+
+ ConstElementPtr relay_id = extended_info->get("relay-id");
+ if (relay_id) {
+ if (relay_id->getType() == Element::string) {
+ vector<uint8_t> bytes;
+ try {
+ encode::decodeHex(relay_id->stringValue(), bytes);
+ } catch (...) {
+ // Decode failed
+ if (!ignore_errors) {
+ throw;
+ }
+ }
+ lease->relay_id_ = bytes;
+ } else if (!ignore_errors) {
+ isc_throw(BadValue, "relay-id entry is not a string");
+ }
+ }
+
+ ConstElementPtr remote_id = extended_info->get("remote-id");
+ if (remote_id) {
+ if (remote_id->getType() == Element::string) {
+ vector<uint8_t> bytes;
+ try {
+ encode::decodeHex(remote_id->stringValue(), bytes);
+ } catch (...) {
+ // Decode failed
+ if (!ignore_errors) {
+ throw;
+ }
+ }
+ lease->remote_id_ = bytes;
+ } else if (!ignore_errors) {
+ isc_throw(BadValue, "remote-id entry is not a string");
+ }
+ }
+}
+
+bool
+LeaseMgr::addExtendedInfo6(const Lease6Ptr& lease) {
+
+ bool added = false;
+ if (!lease) {
+ return (added);
+ }
+
+ ConstElementPtr user_context = lease->getContext();
+ if (!user_context || (user_context->getType() != Element::map) ||
+ user_context->empty()) {
+ return (added);
+ }
+
+ ConstElementPtr isc = user_context->get("ISC");
+ if (!isc || (isc->getType() != Element::map) || isc->empty()) {
+ return (added);
+ }
+
+ ConstElementPtr relay_info = isc->get("relay-info");
+ if (!relay_info || (relay_info->getType() != Element::list) ||
+ relay_info->empty()) {
+ return (added);
+ }
+
+ for (int i = 0; i < relay_info->size(); ++i) {
+ ConstElementPtr relay = relay_info->get(i);
+ if (!relay || (relay->getType() != Element::map) || relay->empty()) {
+ continue;
+ }
+ try {
+ ConstElementPtr relay_id = relay->get("relay-id");
+ if (relay_id) {
+ string relay_id_hex = relay_id->stringValue();
+ vector<uint8_t> relay_id_data;
+ encode::decodeHex(relay_id_hex, relay_id_data);
+ if (relay_id_data.empty()) {
+ continue;
+ }
+ addRelayId6(lease->addr_, relay_id_data);
+ added = true;
+ }
+
+ ConstElementPtr remote_id = relay->get("remote-id");
+ if (remote_id) {
+ string remote_id_hex = remote_id->stringValue();
+ vector<uint8_t> remote_id_data;
+ encode::decodeHex(remote_id_hex, remote_id_data);
+ if (remote_id_data.empty()) {
+ continue;
+ }
+ addRemoteId6(lease->addr_, remote_id_data);
+ added = true;
+ }
+ } catch (const exception&) {
+ continue;
+ }
+ }
+ return (added);
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
new file mode 100644
index 0000000..4b5ea99
--- /dev/null
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -0,0 +1,1103 @@
+// Copyright (C) 2012-2023 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 LEASE_MGR_H
+#define LEASE_MGR_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <dhcp/duid.h>
+#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+/// @file lease_mgr.h
+/// @brief An abstract API for lease database
+///
+/// This file contains declarations of Lease4, Lease6 and LeaseMgr classes.
+/// They are essential components of the interface to any database backend.
+/// Each concrete database backend (e.g. MySQL) will define a class derived
+/// from LeaseMgr class.
+namespace isc {
+namespace dhcp {
+
+/// @brief Pair containing major and minor versions
+typedef std::pair<uint32_t, uint32_t> VersionPair;
+
+/// @brief Wraps value holding size of the page with leases.
+class LeasePageSize {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param page_size page size value.
+ /// @throw OutOfRange if page size is 0 or greater than uint32_t numeric
+ /// limit.
+ explicit LeasePageSize(const size_t page_size);
+
+ const size_t page_size_; ///< Holds page size.
+};
+
+/// @brief Contains a single row of lease statistical data
+///
+/// The contents of the row consist of a subnet ID, a lease
+/// type, a lease state, and the number of leases in that state
+/// for that type for that subnet ID.
+struct LeaseStatsRow {
+ /// @brief Default constructor
+ LeaseStatsRow() :
+ subnet_id_(0), pool_id_(0), lease_type_(Lease::TYPE_NA),
+ lease_state_(Lease::STATE_DEFAULT), state_count_(0) {
+ }
+
+ /// @brief Constructor
+ ///
+ /// Constructor which defaults the type to TYPE_NA.
+ ///
+ /// @param subnet_id The subnet id to which this data applies
+ /// @param lease_state The lease state counted
+ /// @param state_count The count of leases in the lease state
+ /// @param pool_id The pool id to which this data applies or 0 if it is not
+ /// used
+ LeaseStatsRow(const SubnetID& subnet_id, const uint32_t lease_state,
+ const int64_t state_count, uint32_t pool_id = 0)
+ : subnet_id_(subnet_id), pool_id_(pool_id), lease_type_(Lease::TYPE_NA),
+ lease_state_(lease_state), state_count_(state_count) {
+ }
+
+ /// @brief Constructor
+ ///
+ /// @param subnet_id The subnet id to which this data applies
+ /// @param lease_type The lease type for this state count
+ /// @param lease_state The lease state counted
+ /// @param state_count The count of leases in the lease state
+ /// @param pool_id The pool id to which this data applies or 0 if it is not
+ /// used
+ LeaseStatsRow(const SubnetID& subnet_id, const Lease::Type& lease_type,
+ const uint32_t lease_state, const int64_t state_count,
+ uint32_t pool_id = 0)
+ : subnet_id_(subnet_id), pool_id_(pool_id), lease_type_(lease_type),
+ lease_state_(lease_state), state_count_(state_count) {
+ }
+
+ /// @brief Less-than operator
+ bool operator<(const LeaseStatsRow &rhs) const {
+ if (subnet_id_ < rhs.subnet_id_) {
+ return (true);
+ }
+
+ if (subnet_id_ == rhs.subnet_id_ &&
+ pool_id_ < rhs.pool_id_) {
+ return (true);
+ }
+
+ if (subnet_id_ == rhs.subnet_id_ &&
+ pool_id_ == rhs.pool_id_ &&
+ lease_type_ < rhs.lease_type_) {
+ return (true);
+ }
+
+ if (subnet_id_ == rhs.subnet_id_ &&
+ pool_id_ == rhs.pool_id_ &&
+ lease_type_ == rhs.lease_type_ &&
+ lease_state_ < rhs.lease_state_) {
+ return (true);
+ }
+
+ return (false);
+ }
+
+ /// @brief The subnet ID to which this data applies
+ SubnetID subnet_id_;
+
+ /// @brief The pool ID to which this data applies
+ uint32_t pool_id_;
+
+ /// @brief The lease_type to which the count applies
+ Lease::Type lease_type_;
+
+ /// @brief The lease_state to which the count applies
+ uint32_t lease_state_;
+
+ /// @brief state_count The count of leases in the lease state
+ int64_t state_count_;
+};
+
+/// @brief Base class for fulfilling a statistical lease data query
+///
+/// LeaseMgr derivations implement this class such that it provides
+/// up to date statistical lease data organized as rows of LeaseStatsRow
+/// instances. The rows must be accessible in ascending order by subnet id.
+class LeaseStatsQuery {
+public:
+ /// @brief Defines the types of selection criteria supported
+ typedef enum {
+ ALL_SUBNETS,
+ SINGLE_SUBNET,
+ SUBNET_RANGE,
+ ALL_SUBNET_POOLS
+ } SelectMode;
+
+ /// @brief Constructor to query statistics for all subnets
+ ///
+ /// The query created will return statistics for all subnets
+ ///
+ /// @param select_mode The selection criteria which is either ALL_SUBNETS or
+ /// ALL_SUBNET_POOLS
+ LeaseStatsQuery(const SelectMode& select_mode = ALL_SUBNETS);
+
+ /// @brief Constructor to query for a single subnet's stats
+ ///
+ /// The query created will return statistics for a single subnet
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @throw BadValue if subnet_id given is 0.
+ LeaseStatsQuery(const SubnetID& subnet_id);
+
+ /// @brief Constructor to query for the stats for a range of subnets
+ ///
+ /// The query created will return statistics for the inclusive range of
+ /// subnets described by first and last subnet IDs.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @throw BadValue if either value given is 0 or if last <= first.
+ LeaseStatsQuery(const SubnetID& first_subnet_id, const SubnetID& last_subnet_id);
+
+ /// @brief virtual destructor
+ virtual ~LeaseStatsQuery() {};
+
+ /// @brief Executes the query
+ ///
+ /// This method should conduct whatever steps are required to
+ /// calculate the lease statistical data by examining the
+ /// lease data and making that results available row by row.
+ virtual void start() {};
+
+ /// @brief Fetches the next row of data
+ ///
+ /// @param[out] row Storage into which the row is fetched
+ ///
+ /// @return True if a row was fetched, false if there are no
+ /// more rows.
+ virtual bool getNextRow(LeaseStatsRow& row);
+
+ /// @brief Returns the value of first subnet ID specified (or zero)
+ SubnetID getFirstSubnetID() const {
+ return (first_subnet_id_);
+ };
+
+ /// @brief Returns the value of last subnet ID specified (or zero)
+ SubnetID getLastSubnetID() const {
+ return (last_subnet_id_);
+ };
+
+ /// @brief Returns the selection criteria mode
+ /// The value returned is based upon the constructor variant used
+ /// and it indicates which query variant will be executed.
+ SelectMode getSelectMode() const {
+ return (select_mode_);
+ };
+
+protected:
+ /// @brief First (or only) subnet_id in the selection criteria
+ SubnetID first_subnet_id_;
+
+ /// @brief Last subnet_id in the selection criteria when a range is given
+ SubnetID last_subnet_id_;
+
+private:
+ /// @brief Indicates the type of selection criteria specified
+ SelectMode select_mode_;
+};
+
+/// @brief Defines a pointer to a LeaseStatsQuery.
+typedef boost::shared_ptr<LeaseStatsQuery> LeaseStatsQueryPtr;
+
+/// @brief Defines a pointer to a LeaseStatsRow.
+typedef boost::shared_ptr<LeaseStatsRow> LeaseStatsRowPtr;
+
+/// @brief Abstract Lease Manager
+///
+/// This is an abstract API for lease database backends. It provides unified
+/// interface to all backends. As this is an abstract class, it should not
+/// be used directly, but rather specialized derived class should be used
+/// instead.
+///
+/// This class throws no exceptions. However, methods in concrete
+/// implementations of this class may throw exceptions: see the documentation
+/// of those classes for details.
+class LeaseMgr {
+public:
+ /// @brief Constructor
+ ///
+ LeaseMgr() : extended_info_tables_enabled_(false)
+ {}
+
+ /// @brief Destructor
+ virtual ~LeaseMgr()
+ {}
+
+ /// @brief Class method to return extended version info
+ /// This class method must be redeclared and redefined in derived classes
+ static std::string getDBVersion();
+
+ /// @brief Adds an IPv4 lease.
+ ///
+ /// The lease may be modified due to sanity checks setting (see
+ /// LeaseSanityChecks in CfgConsistency) before being inserted. For
+ /// performance reasons, the sanity checks do not make a copy, but rather
+ /// modify lease in place if needed.
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not (because a lease
+ /// with the same address was already there or failed sanity checks)
+ virtual bool addLease(const Lease4Ptr& lease) = 0;
+
+ /// @brief Adds an IPv6 lease.
+ ///
+ /// The lease may be modified due to sanity checks setting (see
+ /// LeaseSanityChecks in CfgConsistency) before being inserted. For
+ /// performance reasons, the sanity checks do not make a copy, but rather
+ /// modify lease in place if needed.
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not (because a lease
+ /// with the same address was already there or failed sanity checks)
+ virtual bool addLease(const Lease6Ptr& lease) = 0;
+
+ /// @brief Returns an IPv4 lease for specified IPv4 address
+ ///
+ /// This method return a lease that is associated with a given address.
+ /// For other query types (by hardware addr, by client-id) there can be
+ /// several leases in different subnets (e.g. for mobile clients that
+ /// got address in different subnets). However, for a single address
+ /// there can be only one lease, so this method returns a pointer to
+ /// a single lease, not a container of leases.
+ ///
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const = 0;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address.
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param hwaddr hardware address of the client
+ ///
+ /// @return lease collection
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const = 0;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address
+ /// and a subnet
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method will either return a single lease or NULL.
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
+ SubnetID subnet_id) const = 0;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param clientid client identifier
+ ///
+ /// @return lease collection
+ virtual Lease4Collection getLease4(const ClientId& clientid) const = 0;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// There can be at most one lease for a given client-id in a single
+ /// pool, so this method will either return a single lease or NULL.
+ ///
+ /// @param clientid client identifier
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const ClientId& clientid,
+ SubnetID subnet_id) const = 0;
+
+ /// @brief Returns all IPv4 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(SubnetID subnet_id) const = 0;
+
+ /// @brief Returns all IPv4 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(const std::string& hostname) const = 0;
+
+ /// @brief Returns all IPv4 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4() const = 0;
+
+ /// @brief Returns range of IPv4 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv4 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv4 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection
+ getLeases4(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const = 0;
+
+ /// @brief Returns existing IPv6 lease for a given IPv6 address.
+ ///
+ /// For a given address, we assume that there will be only one lease.
+ /// The assumption here is that there will not be site or link-local
+ /// addresses used, so there is no way of having address duplication.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ virtual Lease6Ptr getLease6(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const = 0;
+
+ /// @brief Returns existing IPv6 leases for a given DUID+IA combination
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ ///
+ /// @return Lease collection (may be empty if no lease is found)
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid) const = 0;
+
+ /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+ ///
+ /// There may be more than one address, temp. address or prefix
+ /// for specified duid/iaid/subnet-id tuple.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param subnet_id subnet id of the subnet the lease belongs to
+ ///
+ /// @return Lease collection (may be empty if no lease is found)
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const = 0;
+
+ /// @brief returns zero or one IPv6 lease for a given duid+iaid+subnet_id
+ ///
+ /// This function is mostly intended to be used in unit-tests during the
+ /// transition from single to multi address per IA. It may also be used
+ /// in other cases where at most one lease is expected in the database.
+ ///
+ /// It is a wrapper around getLeases6(), which returns a collection of
+ /// leases. That collection can be converted into a single pointer if
+ /// there are no leases (NULL pointer) or one lease (use that single lease).
+ /// If there are more leases in the collection, the function will
+ /// throw MultipleRecords exception.
+ ///
+ /// Note: This method is not virtual on purpose. It is common for all
+ /// backends.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param subnet_id subnet id of the subnet the lease belongs to
+ ///
+ /// @throw MultipleRecords if there is more than one lease matching
+ ///
+ /// @return Lease pointer (or NULL if none is found)
+ Lease6Ptr getLease6(Lease::Type type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const;
+
+ /// @brief Returns all IPv6 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(SubnetID subnet_id) const = 0;
+
+ /// @brief Returns all IPv6 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(const std::string& hostname) const = 0;
+
+ /// @brief Returns all IPv6 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6() const = 0;
+
+ /// @brief Returns collection of leases for matching DUID
+ ///
+ /// @return Lease collection
+ /// (may be empty if no IPv6 lease found for the DUID).
+ virtual Lease6Collection getLeases6(const DUID& duid) const = 0;
+
+ /// @brief Returns range of IPv6 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv6 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv6 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection
+ getLeases6(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const = 0;
+
+ /// @brief Returns a collection of expired DHCPv4 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+ const size_t max_leases) const = 0;
+
+ /// @brief Returns a collection of expired DHCPv6 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+ const size_t max_leases) const = 0;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// If no such lease is present, an exception will be thrown.
+ virtual void updateLease4(const Lease4Ptr& lease4) = 0;
+
+ /// @brief Updates IPv6 lease.
+ ///
+ /// @param lease6 The lease to be updated.
+ virtual void updateLease6(const Lease6Ptr& lease6) = 0;
+
+ /// @brief Deletes an IPv4 lease.
+ ///
+ /// @param lease IPv4 lease to be deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ virtual bool deleteLease(const Lease4Ptr& lease) = 0;
+
+ /// @brief Deletes an IPv6 lease.
+ ///
+ /// @param lease IPv6 lease to be deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual bool deleteLease(const Lease6Ptr& lease) = 0;
+
+ /// @brief Deletes all expired and reclaimed DHCPv4 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs) = 0;
+
+ /// @brief Deletes all expired and reclaimed DHCPv6 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs) = 0;
+
+ /// @brief Recalculates per-subnet and global stats for IPv4 leases
+ ///
+ /// This method recalculates the following statistics:
+ /// per-subnet:
+ /// - assigned-addresses
+ /// - declined-addresses
+ /// global:
+ /// - declined-addresses
+ ///
+ /// It invokes the virtual method, startLeaseStatsQuery4(), which
+ /// returns an instance of an LeaseStatsQuery. The query
+ /// query contains a "result set" where each row is an LeaseStatRow
+ /// that contains a subnet id, a lease type (currently always TYPE_NA),
+ /// a lease state, and the number of leases of that type, in that state
+ /// and is ordered by subnet id. The method iterates over the
+ /// result set rows, setting the appropriate statistic per subnet and
+ /// adding to the appropriate global statistic.
+ void recountLeaseStats4();
+
+ /// @brief Creates and runs the IPv4 lease stats query for all subnets
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv4 lease statistical data for all subnets.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery4();
+
+ /// @brief Creates and runs the IPv4 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv4 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery4();
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv4 lease statistical data for a single
+ /// subnet. Each row of the result set is an LeaseStatRow.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery4(const SubnetID& subnet_id);
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv4 lease statistical data for an inclusive
+ /// range of subnets. Each row of the result set is an LeaseStatRow which
+ /// ordered ascending by subnet ID.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id);
+
+ /// @brief Recalculates per-subnet and global stats for IPv6 leases
+ ///
+ /// This method recalculates the following statistics:
+ /// per-subnet:
+ /// - assigned-nas
+ /// - declined-addresses
+ /// - assigned-pds
+ /// global:
+ /// - assigned-nas
+ /// - declined-addresses
+ /// - assigned-pds
+ ///
+ /// It invokes the virtual method, startLeaseStatsQuery6(), which
+ /// returns an instance of an LeaseStatsQuery. The query contains
+ /// a "result set" where each row is an LeaseStatRow that contains
+ /// a subnet id, a lease type, a lease state, and the number of leases
+ /// of that type, in that state and is ordered by subnet id. The method
+ /// iterates over the result set rows, setting the appropriate statistic
+ /// per subnet and adding to the appropriate global statistic.
+ void recountLeaseStats6();
+
+ /// @brief Creates and runs the IPv6 lease stats query for all subnets
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv6 lease statistical data for all subnets.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery6();
+
+ /// @brief Creates and runs the IPv6 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv6 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery6();
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv6 lease statistical data for a single
+ /// subnet. Each row of the result set is an LeaseStatRow.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery6(const SubnetID& subnet_id);
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv6 lease statistical data for an inclusive
+ /// range of subnets. Each row of the result set is an LeaseStatRow which
+ /// ordered ascending by subnet ID.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id);
+
+ /// @brief Virtual method which removes specified leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet (or 0 for all subnets)
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id) = 0;
+
+ /// @brief Virtual method which removes specified leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet (or 0 for all subnets)
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id) = 0;
+
+ /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+ /// Abstract method.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+ /// "subnet": { "id": 1, "address-limit": 2 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string checkLimits4(isc::data::ConstElementPtr const& user_context) const = 0;
+
+ /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+ /// Abstract method.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+ /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string checkLimits6(isc::data::ConstElementPtr const& user_context) const = 0;
+
+ /// @brief Checks if JSON support is enabled in the database.
+ /// Abstract method.
+ ///
+ /// @return true if there is JSON support, false otherwise
+ virtual bool isJsonSupported() const = 0;
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const = 0;
+
+ /// @brief Returns backend name.
+ ///
+ /// If the backend is a database, this is the name of the database or the
+ /// file. Otherwise it is just the same as the type.
+ ///
+ /// @return Name of the backend.
+ virtual std::string getName() const = 0;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const = 0;
+
+ /// @brief Returns backend version.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @todo: We will need to implement 3 version functions eventually:
+ /// A. abstract API version
+ /// B. backend version
+ /// C. database version (stored in the database scheme)
+ ///
+ /// and then check that:
+ /// B>=A and B=C (it is ok to have newer backend, as it should be backward
+ /// compatible)
+ /// Also if B>C, some database upgrade procedure may be triggered
+ virtual VersionPair getVersion() const = 0;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void commit() = 0;
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void rollback() = 0;
+
+ /// @brief Sets IO service to be used by the Lease Manager.
+ ///
+ /// @param io_service IOService object, used for all ASIO operations.
+ static void setIOService(const isc::asiolink::IOServicePtr& io_service) {
+ io_service_ = io_service;
+ }
+
+ /// @brief Returns pointer to the IO service.
+ static isc::asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ // -- The following are memfile only, but defined in the base LeaseMgr for convenience. --
+
+ /// @brief Returns the class lease count for a given class and lease type.
+ ///
+ /// @param client_class client class for which the count is desired
+ /// @param ltype type of lease for which the count is desired. Defaults to
+ /// Lease::TYPE_V4.
+ ///
+ /// @return number of leases
+ virtual size_t getClassLeaseCount(const ClientClass& client_class,
+ const Lease::Type& ltype = Lease::TYPE_V4) const = 0;
+
+ /// @brief Recount the leases per class for V4 leases.
+ virtual void recountClassLeases4() = 0;
+
+ /// @brief Recount the leases per class for V6 leases.
+ virtual void recountClassLeases6() = 0;
+
+ /// @brief Clears the class-lease count map.
+ virtual void clearClassLeaseCounts() = 0;
+
+ /// The following queries are used to fulfill Bulk Lease Query
+ /// queries. They rely on relay data contained in lease's
+ /// user-context when the extended-store-info flag is enabled.
+
+ /// @brief Upgrade a V4 lease user context to the new extended info entry.
+ ///
+ /// In details:
+ /// - perform sanity checks according to check level.
+ /// - change the "ISC" / "relay-agent-info" to a map.
+ /// - move the "relay-agent-info" string to the "sub-options" entry of
+ /// the map.
+ /// - decode remote-id and relay-id from the RAI option content and
+ /// add the raw value in hexadecimal in "remote-id" and/or "relay-id"
+ /// entries of the map.
+ ///
+ /// @param lease Pointer to the lease to be updated.
+ /// @param check Sanity/consistency check level.
+ /// @return True if the lease user context was updated, false otherwise.
+ static bool
+ upgradeLease4ExtendedInfo(const Lease4Ptr& lease,
+ CfgConsistency::ExtendedInfoSanity check =
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ /// @brief Upgrade a V6 lease user context to the new extended info entry.
+ ///
+ /// In details:
+ /// - perform sanity checks according to check level.
+ /// - change the "ISC" / "relays" list entry to "relay-info".
+ /// - decode remote-id and relay-id from each relay options and
+ /// add the raw value in hexadecimal in "remote-id" and/or "relay-id"
+ /// in the relay item of the list.
+ ///
+ /// @param lease Pointer to the lease to be updated.
+ /// @param check Sanity/consistency check level.
+ /// @return True if the lease user context was updated, false otherwise.
+ static bool
+ upgradeLease6ExtendedInfo(const Lease6Ptr& lease,
+ CfgConsistency::ExtendedInfoSanity check =
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ /// @brief Extract relay and remote identifiers from the extended info.
+ ///
+ /// @param lease Pointer to the lease to be updated.
+ /// @param ignore_errors When true (the default) ignore errors,
+ /// when false throw an exception.
+ static void extractLease4ExtendedInfo(const Lease4Ptr& lease,
+ bool ignore_errors = true);
+
+ /// @brief Returns existing IPv4 leases with a given relay-id.
+ ///
+ /// @param relay_id RAI Relay-ID sub-option value for relay_id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRelayId(const OptionBuffer& relay_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) = 0;
+
+ /// @brief Returns existing IPv4 leases with a given remote-id.
+ ///
+ /// @param remote_id RAI Remote-ID sub-option value for remote-id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included. Defaults to zero.
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included. Defaults to zero.
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) = 0;
+
+ /// @brief Returns existing IPv6 leases with a given relay-id.
+ ///
+ /// @param relay_id DUID for relay_id of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRelayId(const DUID& relay_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) = 0;
+
+ /// @brief Returns existing IPv6 leases with a given remote-id.
+ ///
+ /// @param remote_id remote-id option data of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) = 0;
+
+ /// @brief Returns existing IPv6 leases with on a given link.
+ ///
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByLink(const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) = 0;
+
+ /// @brief Write V4 leases to a file.
+ ///
+ /// @param filename File name to write leases.
+ virtual void writeLeases4(const std::string& filename) = 0;
+
+ /// @brief Write V6 leases to a file.
+ ///
+ /// @param filename File name to write leases.
+ virtual void writeLeases6(const std::string& filename) = 0;
+
+ /// @brief Upgrade extended info (v4).
+ ///
+ /// On SQL backends for all leases with a not null user context.
+ /// - sanitize the user context
+ /// - update relay and remote ids
+ /// - when the lease was modified update it in the database
+ /// On memfile backend a similar action is done when the database is
+ /// loaded from the file. This function implements the new BLQ hook
+ /// command named "extended-info4-upgrade".
+ ///
+ /// @param page_size The page size used for retrieval.
+ /// @return The number of updates in the database.
+ virtual size_t upgradeExtendedInfo4(const LeasePageSize& page_size) = 0;
+
+ /// @brief Returns the setting indicating if lease extended info tables
+ /// are enabled.
+ ///
+ /// @return true if lease extended info tables are enabled or false
+ /// if they are disabled.
+ bool getExtendedInfoTablesEnabled() const {
+ return (extended_info_tables_enabled_);
+ }
+
+ /// @brief Build extended info v6 tables.
+ ///
+ /// @param update Update extended info in database.
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config.
+ /// @return The number of updates in the database or 0.
+ virtual size_t buildExtendedInfoTables6(bool update, bool current) = 0;
+
+protected:
+
+ /// Extended information / Bulk Lease Query shared interface.
+
+ /// @brief Modifies the setting whether the lease extended info tables
+ /// are enabled.
+ ///
+ /// @note This method is virtual so backend doing specific action
+ /// on value changes can intercept it by redefining it.
+ ///
+ /// @param enabled new setting.
+ virtual void setExtendedInfoTablesEnabled(const bool enabled) {
+ extended_info_tables_enabled_ = enabled;
+ }
+
+ /// @brief Decode parameters to set whether the lease extended info tables
+ /// are enabled.
+ ///
+ /// @note: common code in constructors.
+ ///
+ /// @param parameters The parameter map.
+ virtual void setExtendedInfoTablesEnabled(const db::DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Extract extended info from a lease6 and add it into tables.
+ ///
+ /// @param lease IPv6 lease to process.
+ /// @return true if something was added, false otherwise.
+ virtual bool addExtendedInfo6(const Lease6Ptr& lease);
+
+ /// @brief Delete lease6 extended info from tables.
+ ///
+ /// @param addr The address of the lease.
+ virtual void deleteExtendedInfo6(const isc::asiolink::IOAddress& addr) = 0;
+
+ /// @brief Add lease6 extended info into by-relay-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param relay_id The relay id from the relay header options.
+ virtual void addRelayId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& relay_id) = 0;
+
+ /// @brief Add lease6 extended info into by-remote-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param remote_id The remote id from the relay header options.
+ virtual void addRemoteId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& remote_id) = 0;
+
+private:
+ /// The IOService object, used for all ASIO operations.
+ static isc::asiolink::IOServicePtr io_service_;
+
+ /// @brief Holds the setting whether the lease extended info tables
+ /// are enabled or disabled. The default is disabled.
+ bool extended_info_tables_enabled_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // LEASE_MGR_H
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc
new file mode 100644
index 0000000..e633916
--- /dev/null
+++ b/src/lib/dhcpsrv/lease_mgr_factory.cc
@@ -0,0 +1,138 @@
+// Copyright (C) 2012-2023 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 <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+#ifdef HAVE_MYSQL
+#include <dhcpsrv/mysql_lease_mgr.h>
+#endif
+#ifdef HAVE_PGSQL
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <sstream>
+#include <utility>
+
+using namespace isc::db;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+boost::scoped_ptr<TrackingLeaseMgr>&
+LeaseMgrFactory::getLeaseMgrPtr() {
+ static boost::scoped_ptr<TrackingLeaseMgr> lease_mgr_ptr;
+ return (lease_mgr_ptr);
+}
+
+void
+LeaseMgrFactory::create(const std::string& dbaccess) {
+ const std::string type = "type";
+
+ // Parse the access string and create a redacted string for logging.
+ DatabaseConnection::ParameterMap parameters = DatabaseConnection::parse(dbaccess);
+ std::string redacted = DatabaseConnection::redactedAccessString(parameters);
+
+ // Is "type" present?
+ if (parameters.find(type) == parameters.end()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
+ isc_throw(InvalidParameter, "Database configuration parameters do not "
+ "contain the 'type' keyword");
+ }
+
+
+ // Yes, check what it is.
+ if (parameters[type] == string("mysql")) {
+#ifdef HAVE_MYSQL
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_DB).arg(redacted);
+ getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
+ return;
+#else
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg("mysql");
+ isc_throw(InvalidType, "The Kea server has not been compiled with "
+ "support for database type: mysql");
+#endif
+ }
+
+ if (parameters[type] == string("postgresql")) {
+#ifdef HAVE_PGSQL
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_DB).arg(redacted);
+ getLeaseMgrPtr().reset(new PgSqlLeaseMgr(parameters));
+ return;
+#else
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg("postgresql");
+ isc_throw(InvalidType, "The Kea server has not been compiled with "
+ "support for database type: postgresql");
+#endif
+ }
+ if (parameters[type] == string("memfile")) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_DB).arg(redacted);
+ getLeaseMgrPtr().reset(new Memfile_LeaseMgr(parameters));
+ return;
+ }
+
+ // Get here on no match
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+ isc_throw(InvalidType, "Database access parameter 'type' does "
+ "not specify a supported database backend: " << parameters[type]);
+}
+
+void
+LeaseMgrFactory::destroy() {
+ // Destroy current lease manager. This is a no-op if no lease manager
+ // is available.
+ if (getLeaseMgrPtr()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CLOSE_DB)
+ .arg(getLeaseMgrPtr()->getType());
+ }
+ getLeaseMgrPtr().reset();
+}
+
+void
+LeaseMgrFactory::recreate(const std::string& dbaccess, bool preserve_callbacks) {
+ TrackingLeaseMgr::CallbackContainerPtr callbacks;
+ // Preserve the callbacks if needed.
+ if (preserve_callbacks && haveInstance()) {
+ callbacks = instance().callbacks_;
+ }
+
+ // Re-create the manager.
+ destroy();
+ create(dbaccess);
+
+ if (callbacks) {
+ // Copy the callbacks to the new instance. It should be fast
+ // because we merely copy the pointer.
+ instance().callbacks_ = callbacks;
+ }
+}
+
+bool
+LeaseMgrFactory::haveInstance() {
+ return (getLeaseMgrPtr().get());
+}
+
+TrackingLeaseMgr&
+LeaseMgrFactory::instance() {
+ TrackingLeaseMgr* lmptr = getLeaseMgrPtr().get();
+ if (lmptr == NULL) {
+ isc_throw(NoLeaseManager, "no current lease manager is available");
+ }
+ return (*lmptr);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h
new file mode 100644
index 0000000..69fef85
--- /dev/null
+++ b/src/lib/dhcpsrv/lease_mgr_factory.h
@@ -0,0 +1,119 @@
+// Copyright (C) 2012-2023 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 LEASE_MGR_FACTORY_H
+#define LEASE_MGR_FACTORY_H
+
+#include <database/database_connection.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+
+/// @brief No lease manager exception
+///
+/// Thrown if an attempt is made to get a reference to the current lease
+/// manager and none is currently available.
+class NoLeaseManager : public Exception {
+public:
+ NoLeaseManager(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Lease Manager Factory
+///
+/// This class comprises nothing but static methods used to create a lease
+/// manager. It analyzes the database information passed to the creation
+/// function and instantiates an appropriate lease manager based on the type
+/// requested.
+///
+/// Strictly speaking these functions could be stand-alone functions. However,
+/// it is convenient to encapsulate them in a class for naming purposes.
+///
+/// @todo: Will need to develop some form of registration mechanism for
+/// user-supplied backends (so that there is no need to modify the code).
+class LeaseMgrFactory {
+public:
+ /// @brief Create an instance of a lease manager.
+ ///
+ /// Each database backend has its own lease manager type. This static
+ /// method sets the "current" lease manager to be a manager of the
+ /// appropriate type. The actual lease manager is returned by the
+ /// "instance" method.
+ ///
+ /// @note When called, the current lease manager is <b>always</b> destroyed
+ /// and a new one created - even if the parameters are the same.
+ ///
+ /// dbaccess is a generic way of passing parameters. Parameters are passed
+ /// in the "name=value" format, separated by spaces. The data MUST include
+ /// a keyword/value pair of the form "type=dbtype" giving the database
+ /// type, e.q. "mysql" or "sqlite3".
+ ///
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
+ ///
+ /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
+ /// keyword.
+ /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
+ /// identify a supported backend.
+ static void create(const std::string& dbaccess);
+
+ /// @brief Destroy lease manager
+ ///
+ /// Destroys the current lease manager object. This should have the effect
+ /// of closing the database connection. The method is a no-op if no
+ /// lease manager is available.
+ static void destroy();
+
+ /// @brief Recreate an instance of a lease manager with optionally
+ /// preserving registered callbacks.
+ ///
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
+ /// @param preserve_callbacks a boolean flag indicating if all registered
+ /// @c TrackingLeaseMgr callbacks should be copied to the new
+ /// instance.
+ static void recreate(const std::string& dbaccess,
+ bool preserve_callbacks = true);
+
+ /// @brief Return current lease manager
+ ///
+ /// Returns an instance of the "current" lease manager. An exception
+ /// will be thrown if none is available.
+ ///
+ /// @throw isc::dhcp::NoLeaseManager No lease manager is available: use
+ /// create() to create one before calling this method.
+ static TrackingLeaseMgr& instance();
+
+ /// @brief Indicates if the lease manager has been instantiated.
+ ///
+ /// @return True if the lease manager instance exists, false otherwise.
+ static bool haveInstance();
+
+private:
+ /// @brief Hold pointer to lease manager
+ ///
+ /// Holds a pointer to the singleton lease manager. The singleton
+ /// is encapsulated in this method to avoid a "static initialization
+ /// fiasco" if defined in an external static variable.
+ static boost::scoped_ptr<TrackingLeaseMgr>& getLeaseMgrPtr();
+
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // LEASE_MGR_FACTORY_H
diff --git a/src/lib/dhcpsrv/libdhcpsrv.dox b/src/lib/dhcpsrv/libdhcpsrv.dox
new file mode 100644
index 0000000..803517d
--- /dev/null
+++ b/src/lib/dhcpsrv/libdhcpsrv.dox
@@ -0,0 +1,528 @@
+// Copyright (C) 2012-2023 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/.
+
+/**
+ @page libdhcpsrv libkea-dhcpsrv - Server DHCP Library
+
+This library contains code used for the DHCPv4 and DHCPv6 servers' operations,
+including the "Lease Manager" that manages information about leases and the
+"Configuration Manager" that stores the servers' configuration etc.
+The code here is server specific. For generic (useful to the server,
+client, relay and other tools like perfdhcp) code, please see
+\ref libdhcp.
+
+This library contains several crucial elements for the operation of the DHCP server:
+
+- isc::dhcp::CfgGlobals - global scalar (i.e. not list or map) parameters.
+- isc::dhcp::LeaseMgr - lease manager is the name for the database backend that stores
+ leases.
+- isc::dhcp::CfgMgr - configuration manager that holds DHCP specific
+ configuration information (subnets, pools, options, timer values etc.) in
+ easy to use format.
+- isc::dhcp::AllocEngine - allocation engine that handles new requests and allocates new
+ leases.
+- isc::dhcp::HostMgr - manager for static reservations (a.k.a. host reservations).
+- isc::dhcp::D2ClientMgr - DHCP-DDNS (D2) client manager which is responsible for
+ the communication between the DHCP server and the D2 component.
+- isc::dhcp::Dhcp4o6IpcBase - common part (base class) of DHCPv4-over-DHCPv6
+ inter server communication (aka IPC).
+
+@section cfgglobals Global Parameters
+
+The global parameters handle direct (vs using a search in a name to
+value table) access to global scalar (i.e. not list or map) parameter values.
+
+This is related to the procedure to add a new global scalar parameter to
+the DHCPv4 or DHCPv6 (DHCPvX below) server implementation:
+
+- update the src/bin/dhcpX/dhcpX_lexer.ll to add the new token
+- update the src/bin/dhcpX/dhcpX_parser.yy to add the new syntax
+- update the src/bin/dhcpX/json_config_parser.cc to add the new parameter
+ in the global parameter big if statement
+- update the src/lib/dhcpsrv/parsers/simple_parserX.cc file to add the new
+ parameter in the GLOBALX_PARAMETERS keyword list and eventually in the
+ GLOBALX_DEFAULTS list
+- update the cfg_globals.h and cfg_globals.cc files, note that specific to
+ v4 or v6 parameters are after no specific
+- if the parameter exists for shared networks, subnets, etc,
+ the corresponding tables must be updated in simple parser files
+
+Note there is nothing to update for a global parameter in the configuration
+backend: no new column in database schemas, no code in hooks. Of course
+this does not apply to parameters which exist at not global level too.
+
+@section leasemgr Lease Manager
+
+LeaseMgr provides a common, unified abstract API for all database backends. All
+backends are derived from the base class isc::dhcp::LeaseMgr. Currently Kea
+supports three backends, implemented in the following classes:
+
+- isc::dhcp::Memfile_LeaseMgr - stores leases in a CSV file,
+- isc::dhcp::MySqlLeaseMgr - stores leases in a MySQL database
+- isc::dhcp::PgSqlLeaseMgr - stores leases in a PostgreSQL database
+
+@section cfgmgr Configuration Manager
+
+Configuration Manager (\ref isc::dhcp::CfgMgr) is a singleton object which
+holds configuration information necessary for the operation of Kea daemons.
+A complete collection of information for the daemon is stored in the
+\ref isc::dhcp::SrvConfig object. Internally, the Configuration Manager
+holds a list of \ref isc::dhcp::SrvConfig objects, from which one
+is marked as "current configuration".
+
+When the server starts up or is being reconfigured a new
+\ref isc::dhcp::SrvConfig object, referred to as "staging configuration",
+is created. The staging configuration is held at the tip of the list of
+configurations. The object can be accessed by calling the
+\ref isc::dhcp::CfgMgr::getStagingCfg. This object can be accessed
+from different stages of the configuration parsing and modified as needed.
+Modifications of the staging configuration do not affect the current
+configuration. The staging configuration is unused until the
+\ref isc::dhcp::CfgMgr::commit function is called. This exception safe method
+marks the staging object as "current configuration". The const pointer to the
+current configuration can be accessed by calling a
+\ref isc::dhcp::CfgMgr::getCurrentCfg.
+
+The staging configuration can be discarded at any time before it is committed
+by calling the \ref isc::dhcp::CfgMgr::rollback. This removes the
+\ref isc::dhcp::SrvConfig object from the Configuration Manager. When
+the \ref isc::dhcp::CfgMgr::getStagingCfg is called again a fresh/default
+\ref isc::dhcp::SrvConfig object is returned.
+
+The Configuration Manager stores previous configurations, i.e. configurations
+which occurred prior to the most current configuration. This is currently
+unused (except for unit tests) by the daemons, but in the future this
+mechanism can be used to trigger a rollover of the server configuration
+to a last good configuration that the administrator prefers.
+
+The previous configurations are identified by the value which specifies a
+distance between the current configuration and the previous
+configuration. For example: the value of 1 identifies an immediate
+predecessor of the current configuration, the value of 2 identifies the
+one that occurred before it etc.
+
+All configuration classes are derived from the abstract base class
+\ref isc::data::CfgToElement and define the toElement virtual method
+which returns a \ref isc::data::ConstElementPtr which must be
+parsed into the same object, i.e. fulfill this property:
+@code
+for all valid C: parse(parse(C)->toElement()) == parse(C)
+@endcode
+
+
+@section hostmgr Host Manager
+
+Host Manager implemented by the \ref isc::dhcp::HostMgr is a singleton object
+which provides means to retrieve resources statically assigned to the DHCP
+clients, such as IP addresses, prefixes or hostnames. The statically assigned
+resources are called reservations (or host reservations) and they are
+represented in the code by the \ref isc::dhcp::Host class.
+
+The reservations can be specified in the configuration file or in some
+other storage (typically in a database). A dedicated object, called
+host data source, is needed to retrieve the host reservations from the
+database. This object must implement the \ref isc::dhcp::BaseHostDataSource
+interface and its implementation is specific to the type of storage
+holding the reservations. For example, the host data source managing
+host reservations in the MySQL database is required to establish
+connection to the MySQL database and issue specific queries. A factory
+method creating an instance of a base host data source object must be
+registered (at global object initialization for built-in backends,
+dynamically for backends loaded at run-time). See host_data_source_factory.cc
+for example code that registers MySQL and PostgreSQL. Note, that this instance
+is created as "alternate host data source" as opposed to the primary data
+source which returns host reservations specified in the configuration file.
+The primary data source is implemented internally in the
+\ref isc::dhcp::HostMgr and uses the configuration data structures held by
+the \ref isc::dhcp::CfgMgr to retrieve the reservations. In general, the
+\ref isc::dhcp::HostMgr first searches for the reservations using the
+primary data source and falls back to the use of alternate data source
+when nothing has been found. For those methods which are meant to return
+multiple reservations (e.g. find all reservations for the particular
+client), the \ref isc::dhcp::HostMgr will use both primary and alternate
+data source (if present) and concatenate results.
+
+For more information about the \ref isc::dhcp::HostMgr please refer to its
+documentation.
+
+@subsection postgreSQLHostMgr PostgreSQL Host Reservation Management
+
+Storing and retrieving host reservations within a PostgreSQL schema is
+provided by the class, \ref isc::dhcp::PgSqlHostDataSource, a derivation of
+\ref isc::dhcp::BaseHostDataSource and is depicted in the following
+class diagram:
+
+@image html pgsql_host_data_source.svg "PgSqlHostDataSource Class Diagram"
+
+@section optionsConfig Options Configuration Information
+
+The \ref isc::dhcp::CfgOption object holds a collection of options being
+sent to the client. Since each subnet comes with a distinct set of
+options, every \ref isc::dhcp::Subnet object holds its own copy of the
+\ref isc::dhcp::CfgOption object with specific options.
+
+The DHCP server also allows for configuration of "global" options
+which are shared by all subnets. The rule here is that if a particular
+option appears in the global options set and the subnet specific options
+set, the subnet specific option takes precedence. The global options
+configuration is held in the dedicated instance of the
+\ref isc::dhcp::CfgOption class. This instance is owned by the
+\ref isc::dhcp::SrvConfig class.
+
+When the new configuration is parsed, the global options are merged into
+the \ref isc::dhcp::CfgOption instances for all subnets. This is
+causing some overhead during the reconfiguration of the server but on
+the other hand it avoids the lookup of options in two places (among
+subnet specific options and global options) during each packet
+processing.
+
+One of the benefits of keeping a separate set of global options is
+that there may be cases when the server administrator doesn't specify
+any subnet configuration and only wants global options to be used.
+This is the case, when the DHCP server is used for stateless
+configuration, i.e. client's are not allocated an address or prefix,
+and only stateless configuration is handed out.
+
+@section allocengine Allocation Engine
+
+The Allocation Engine (\ref isc::dhcp::AllocEngine) is one of the core Kea modules.
+It uses the data from the received client DHCP messages to find an available
+lease and allocate it to the client. Finding a free lease is very complex
+because the engine has to consider the client's hints, whether or not the
+client has a static reservation, classes associated with the client, and
+many more. If the engine cannot allocate a lease indicated in the hint and
+the client has no static reservations, it has to find an available lease
+in the configured address or prefix delegation pools. It is the responsibility
+of the allocator (\ref isc::dhcp::Allocator).
+
+Allocators are implemented in C++ classes with a well-defined interface. Each
+allocator uses a different algorithm for selecting a lease from the configured
+pools:
+
+- Iterative - it iterates over all resources (addresses or prefixes) in
+available pools, one by one. The advantages of this approach are: speed
+(typically it only needs to increase address just one), the guarantee to cover
+all addresses and predictability. This allocator behaves reasonably good in
+case of nearing depletion. Even when pools are almost completely allocated, it
+still will be able to allocate outstanding leases efficiently. Predictability
+can also be considered a serious flaw in some environments, as prediction of the
+next address is trivial and can be leveraged by an attacker. Another drawback of
+this allocator is that it does not attempt to give the same address to returning
+clients (clients that released or expired their leases and are requesting a new
+lease will likely get a different lease). This allocator is not suitable for
+temporary addresses, which must be randomized. This allocator is implemented
+in \ref isc::dhcp::IterativeAllocator.
+
+- Random - an allocator generating IP permutations within configured address pool
+using the Fisher-Yates shuffle algorithm. The allocator picks the leases from
+the permutation, so the leases are offered in random order. The permutation
+holds a state indicating which leases have already been offered to avoid
+returning the same lease multiple times. The advantage of the random allocator
+is that it makes the attacks based on address prediction more difficult. On
+the other hand, this allocator consumes more memory than the iterative allocator
+to hold the permutation state. This allocator is implemented
+in \ref isc::dhcp::RandomAllocator.
+
+The following allocators are not implemented in Kea but can be considered in
+the future:
+
+- FLQ (Free Lease Queue) - it is a specialized allocator optimizing lease
+selection when the server has nearly depleted pools. The iterative and
+random allocators can be slow in this case because they are unaware of
+which leases have already been taken. The FLQ allocator tracks lease
+allocations and avoids offering already allocated leases. It must maintain
+the list of available leases and offer them from this list. When the lease
+is allocated, it must be removed from the list. Populating the list of
+available leases is performed during the server startup or reconfiguration,
+possibly impacting the server's startup and reconfiguration time.
+
+- Hashed - ISC-DHCP uses hash of the client-id or DUID to determine, which
+address is tried first. If that address is not available, the result is hashed
+again. That procedure is repeated until available address is found or there
+are no more addresses left. The benefit of that approach is that it provides
+a relative lease stability, so returning old clients are likely to get the same
+address again. The drawbacks are increased computation cost, as each iteration
+requires use of a hashing function. That is especially difficult when the
+pools are almost depleted. It also may be difficult to guarantee that the
+repeated hashing will iterate over all available addresses in all pools. Flawed
+hash algorithm can go into cycles that iterate over only part of the addresses.
+It is difficult to detect such issues as only some initial seed (client-id
+or DUID) values may trigger short cycles.
+
+@subsection allocEngineTypes Different lease types support
+
+Allocation Engine has been extended to support different types of leases. Four
+types are supported: TYPE_V4 (IPv4 addresses), TYPE_NA (normal IPv6 addresses),
+TYPE_TA (temporary IPv6 addresses) and TYPE_PD (delegated prefixes). Support for
+TYPE_TA is partial. Some routines are able to handle it, while other are
+not. The major missing piece is the RandomAllocator, so there is no way to randomly
+generate an address. This defeats the purpose of using temporary addresses for now.
+
+@subsection allocEnginePD Prefix Delegation support in AllocEngine
+
+The Allocation Engine supports allocation of the IPv6 addresses and prefixes.
+For a prefix pool, the iterative allocator "walks over"
+every available pool. It is similar to how it iterates over address pool,
+but instead of increasing address by just one, it walks over the whole delegated
+prefix length in one step. This is implemented in
+isc::dhcp::AllocEngine::IterativeAllocator::increasePrefix(). Functionally the
+increaseAddress(addr) call is equivalent to increasePrefix(addr, 128)
+(increasing by a /128 prefix, i.e. a single address). However, both methods are
+kept, because increaseAddress() is faster and this is a routine that may be
+called many hundred thousands times per second.
+
+@subsection allocEngineDHCPv4HostReservation Host Reservation support
+
+The Allocation Engine supports allocation of statically assigned addresses
+to the DHCPv4 clients, a.k.a. Host Reservation.
+
+When the server receives a DHCPDISCOVER or DHCPREQUEST from the client it
+calls \ref isc::dhcp::AllocEngine::allocateLease4 to obtain the suitable lease
+for the client. If the Allocation Engine determines that the particular client
+has a reservation it will try to allocate a reserved address for it. If the
+client requested allocation or renewal of a different address, the Allocation
+Engine will respond with a NULL lease to indicate that the address
+desired by the client could not be assigned. The DHCP server should send
+a DHCPNAK to the client and the client should fall back to the DHCP
+server discovery. When the client sends DHCPDISCOVER, the Allocation
+Engine offers the reserved address and the client should request the
+offered address in subsequent DHCPREQUEST messages.
+
+There are cases when the Allocation Engine is unable to assign the
+reserved address for the client. This includes the situations when
+the address had been previously reserved for another client or the
+address had been assigned out of the dynamic address pool. Such address
+may still remain in use of the client which obtained it first and the
+Allocation Engine must not assign it to the client for which it is
+reserved until the client using this address releases or the server
+assigns a different address for it.
+
+In order to resolve this conflict the Allocation Engine will refuse to
+renew the lease for the client using the address not reserved for it.
+This client should fall back to the 4-way exchange and the Allocation
+Engine will assign a different address. As a result, the reserved
+address will be freed for the use of the client for which the reservation
+was made. The client will be offered/allocated a reserved address
+the next time it retries sending a DHCPDISCOVER/DHCPREQUEST message to
+the server.
+
+@subsection allocEngineReuse Allocation Engine Cache
+
+The allocation engine provides a cache-like feature: when a suitable
+lease already exists for a client if its age is small enough compared
+to the valid lifetime (threshold parameter) and below a configured maximum
+(max age parameter) the lease can be reused. A reusable lease is marked
+by a not zero reuseable_valid_lft_ value.
+
+@section timerManager Timer Manager
+
+The @c isc::dhcp::TimerMgr is a singleton class used throughout the
+server process to register and unregister timers triggering periodic
+tasks such as lease file cleanup, reclamation of expired leases etc.
+
+The Timer Manger is using ASIO deadline timers (wrapped in
+@c isc::asiolink::IntervalTimer class) to execute tasks according to
+the configured periods. Therefore, the server process must provide the
+Timer Manager with the pointer to the @c isc::asiolink::IOService which
+the server is using to run asynchronous tasks.
+
+Current implementation of the DHCP servers uses synchronous calls to
+@c select() function to check if any transmission has been received
+on any socket. This poses a problem with running asynchronous calls
+via @c IOService in the main server loop because the @c select()
+blocks for a specified amount of time while asynchronous calls
+are not triggered. In the future we should migrate from the synchronous
+@c select() calls into asynchronous calls using ASIO. Currently,
+we mitigate the problem by lowering the @c select() timeout to 1s,
+and polling @c IOService for "ready" timers (handlers) after
+@c select() returns. This may cause delays of "ready" handlers
+execution by around 1s. However, this is acceptable for the current
+applications of the periodic timers.
+
+@section leaseReclamationRoutine Leases Reclamation Routine
+
+Lease reclamation is the process in which the expired lease becomes
+available for re-assignment to the same or another client. When the
+server reclaims the lease it executes the callouts registered for the
+"lease4_expire" and "lease6_expire" hook points, performs the DNS update
+to remove any DNS records associated with the expired lease, and finally
+marks a lease as reclaimed in the lease database. The lease may be
+marked as reclaimed by setting its state to @c Lease::STATE_EXPIRED_RECLAIMED
+or by being removed from the database.
+
+Reclamation is performed periodically for a bulk of expired
+leases in the lease reclamation routine. The lease reclamation routines
+for both DHCP servers are implemented in the @c isc::dhcp::AllocEngine:
+- @c isc::dhcp::AllocEngine::reclaimExpiredLeases4 (DHCPv4)
+- @c isc::dhcp::AllocEngine::reclaimExpiredLeases6 (DHCPv6)
+
+Note that besides the reclamation of the leases, these methods also
+update the relevant statistics, i.e. decrease the number of assigned
+leases and increase the number of reclaimed leases.
+
+The reclamation routines are executed periodically according to
+the server configuration (see the documentation for the
+"expired-leases-processing" configuration map). Internally, they are
+registered as callback functions in the @c isc::dhcp::TimerMgr
+(see @ref timerManager for the details), during the servers' startup
+or reconfiguration.
+
+Execution of the reclamation routine may take a relatively
+long period of time. It depends on the complexity of the callouts,
+whether the DNS update is required for leases, and the type of the
+lease database used. While the reclamation routine is
+executed, the server will not process any DHCP messages to avoid
+race conditions being a result of concurrent access to the lease
+database to allocate and reclaim leases. To make sure that the
+server remains responsive, it is possible to limit the number of
+leases being processed by the leases reclamation routine and/or
+limit the time for the reclamation routine to process
+leases. Both limits are specified in the respective arguments
+passed to the lease reclamation routines.
+
+As mentioned above, reclaimed leases may be marked as such, by
+updating their state to @c Lease::STATE_EXPIRED_RECLAIMED or by
+being removed. This behavior is controlled by the boolean parameter
+passed to the reclamation routine. The first approach is desired
+when the server should provide "lease affinity", i.e. ability to
+re-assign the same lease to the returning client. By only
+updating the lease state, the server preserves association of the
+lease with a particular client. When that client returns the
+server may assign the same lease to the client, assuming that this
+lease is still available. The lease is removed during the
+reclamation when the lease affinity is not required and it is
+preferred to not keep redundant information (about expired
+leases) in the lease database.
+
+If the reclaimed leases are not removed, they are held in the
+database for a specified amount of time after their expiration.
+Each reclaimed lease is removed when this time elapses for it.
+The @c isc::dhcp::LeaseMgr::deleteExpiredReclaimedLeases4 and
+@c isc::dhcp::LeaseMgr::deleteExpiredReclaimedLeases6 are used
+to remove those leases for which the specified amount of time
+since expiration elapsed. These methods are executed periodically
+by the DHCP servers using the dedicated timers registered in the
+@c isc::dhcp::TimerMgr.
+
+@section subnetSelect Subnet Selection
+
+An important service offered by this library is the subnet selection
+from a query packet.
+
+@subsection dhcp4SubnetSelect DHCPv4 Subnet Selection
+
+Selectors (i.e., members of @c SubnetSelector class) are:
+- incoming interface name
+- gateway address - giaddr field
+- client address - ciaddr field
+- local address
+- remote address
+- option select - from the first Relay Agent Link Selection suboption or
+ from a Subnet Selection option
+- client classes - used to reject a matching rule and try next rules
+
+First use the option select, next if the message was relayed (not undefined
+gateway address) use the gateway address as a subnet relay address.
+
+If a subnet was not already selected choose an address between:
+
+- if the gateway address is not undefined the gateway address
+- if the client address is not undefined and the local address not the
+ broadcast address (i.e., renew or rebind) the client address
+- if the remote address is not undefined and the local address not the
+ broadcast address (i.e., renew or rebind) the remote address
+- at this point try the interface name as a subnet interface
+- if the interface name does not select a subnet choose the interface address
+ (last resort)
+
+Match the chosen address in a subnet address range.
+
+@subsection dhcp4o6SubnetSelect DHCPv4-over-DHCPv6 Subnet Selection
+
+Selectors (i.e., members of @c SubnetSelector class) are:
+- incoming interface name
+- gateway address - giaddr field (should be always undefined)
+- client address - ciaddr field
+- local address - set to the interface IPv4 address
+- remote address - IPv6 address
+- option select - from a Subnet Selection option
+- first relay link address - (IPv6) undefined or the first relay link
+ address which is not undefined or link local (i.e., usable)
+- interface ID - (IPv6) when a relay message includes an interface ID
+ relay option
+- client classes - used to reject a matching rule and try next rules
+
+Check if DHCPv4-over-DHCPv6 is enabled for this particular subnet and
+continue with the next subnet is if it is not.
+
+First the remote address is matched in a subnet IPv6 range, second
+the interface ID if it is set is matched, third the interface name.
+
+These rules are applied for each subnet configuration so if two
+subnets match the first one is returned (vs. the first matching rule).
+
+@todo better DHCPv4-over-DHCPv6 selection, e.g., handle relayed
+messages and return best (vs first) match.
+
+@subsection dhcp6SubnetSelection DHCPv6 Subnet Selection
+
+Selectors (i.e., members of @c SubnetSelector class) are:
+- incoming interface name
+- remote address
+- first relay link address - undefined or the first relay link address which
+ is not undefined or link local (i.e., usable)
+- interface ID - when a relay message includes an interface ID relay option
+- client classes - used to reject a matching rule and try next rules
+
+If the first relay link address is undefined the client is directly connected:
+the interface name is matched and if it does not select a subnet the remote
+address is matched in a subnet address range.
+
+If the first relay link address is not undefined the query was relayed:
+the interface ID is tried and if it does not select a subnet the first
+relay address is matched as a subnet relay address.
+
+@section dhcp4o6Ipc DHCPv4-over-DHCPv6 Inter Process Communication
+
+DHCPv4-over-DHCPv6 support is implemented using cooperating
+DHCPv6 and DHCPv6 servers. Servers communicate over a pair of
+local UDP sockets using consecutive ports. The common part of
+the Inter Process Communication (IPC) is provided by the base class
+@c isc::dhcp::Dhcp4o6IpcBase.
+
+The receiving interface name and remote IPv6 address meta information
+are conveyed within a Vendor Specific Information option with the ISC
+enterprise ID carrying interface and remote address suboptions.
+\ref isc::dhcp::Dhcp4o6IpcBase::send adds them,
+\ref isc::dhcp::Dhcp4o6IpcBase::receive decodes and removes them.
+
+Also see \ref dhcpv4o6Dhcp4 and \ref dhcpv4o6Dhcp6 for details on how IPC
+is used by DHCPv4 and DHCPv6 components.
+
+@todo
+
+DHCPv4-over-DHCPv6 which are relayed by a DHCPv6 relay are not yet supported.
+
+@section libdhcpsrvMTConsiderations Multi-Threading Consideration for Server DHCP Library
+
+Note that for backends specific consideration is in @ref
+dhcpDatabaseBackendsMTConsiderations.
+
+Below Kea thread safe means thread safe when the multi-threading mode is
+true (when it is false packets are processed by the main thread).
+
+By default this library is not thread safe, in particular all classes used
+for configuration are not thread safe. Exceptions are:
+
+ - allocation engine allocator is Kea thread safe.
+
+ - resource handler is thread safe.
+
+ - last allocated members of subnets are Kea thread safe.
+
+ - timer manager functions are Kea thread safe.
+
+*/
diff --git a/src/lib/dhcpsrv/memfile_lease_limits.cc b/src/lib/dhcpsrv/memfile_lease_limits.cc
new file mode 100644
index 0000000..f972eb6
--- /dev/null
+++ b/src/lib/dhcpsrv/memfile_lease_limits.cc
@@ -0,0 +1,161 @@
+// Copyright (C) 2022 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 <dhcpsrv/memfile_lease_limits.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+size_t
+ClassLeaseCounter::getClassCount(const ClientClass& client_class,
+ const Lease::Type& ltype) const {
+ const ClassCountMap& leases_by_class = getConstCountMap(ltype);
+ auto it = leases_by_class.find(client_class);
+ if (it == leases_by_class.end()) {
+ return (0);
+ }
+
+ // Return the lease count for the class.
+ return(it->second);
+}
+
+void
+ClassLeaseCounter::setClassCount(const ClientClass& client_class, size_t count,
+ const Lease::Type& ltype) {
+ ClassCountMap& leases_by_class = getCountMap(ltype);
+ leases_by_class[client_class] = count;
+}
+
+void
+ClassLeaseCounter::adjustClassCount(const ClientClass& client_class, int offset,
+ const Lease::Type& ltype) {
+ ClassCountMap& leases_by_class = getCountMap(ltype);
+ auto it = leases_by_class.find(client_class);
+ if (it == leases_by_class.end()) {
+ // Not there yet, add it.
+ leases_by_class[client_class] = offset < 0 ? 0 : offset;
+ } else {
+ size_t new_count = it->second + offset;
+ if (offset < 0 && (new_count > it->second)) {
+ // We rolled over, set it zero. We should probably log this?
+ it->second = 0;
+ } else {
+ it->second = new_count;
+ }
+ }
+}
+
+
+ConstElementPtr
+ClassLeaseCounter::getLeaseClientClasses(LeasePtr lease) {
+ if (!lease) {
+ isc_throw(BadValue, "getLeaseClientCLasses - lease cannot be empty");
+ }
+
+ ConstElementPtr classes;
+ auto ctx = lease->getContext();
+ try {
+ if (ctx) {
+ classes = ctx->find("ISC/client-classes");
+ if (classes && classes->getType() != Element::list) {
+ isc_throw(BadValue, "client-classes is not a list");
+ }
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "getLeaseClientClasses - invalid context: "
+ << data::prettyPrint(ctx) << ", " << ex.what());
+ }
+
+ return (classes);
+}
+
+void
+ClassLeaseCounter::adjustClassCounts(ConstElementPtr classes, int offset,
+ const Lease::Type& ltype) {
+ if (!classes) {
+ return;
+ }
+
+ for (int i = 0; i < classes->size(); ++i) {
+ std::string class_name = classes->get(i)->stringValue();
+ adjustClassCount(class_name, offset, ltype);
+ }
+}
+
+void
+ClassLeaseCounter::addLease(LeasePtr lease) {
+ if (!lease) {
+ isc_throw(BadValue, "addLease - lease cannot be empty");
+ }
+
+ ConstElementPtr classes = getLeaseClientClasses(lease);
+ if (!classes) {
+ return; // Lease limits isn't loaded.
+ }
+
+ // Add the new lease to its classes.
+ if (lease->state_ == Lease::STATE_DEFAULT) {
+ adjustClassCounts(classes, 1, lease->getType());
+ }
+}
+
+void
+ClassLeaseCounter::updateLease(LeasePtr new_lease, LeasePtr old_lease) {
+ // Sanity checks.
+ if (!new_lease) {
+ isc_throw(BadValue, "updateLease - new_lease cannot be empty");
+ }
+
+ if (!old_lease) {
+ isc_throw(BadValue, "updateLease - old_lease cannot be empty");
+ }
+
+ ConstElementPtr new_classes = getLeaseClientClasses(new_lease);
+ uint32_t new_state = new_lease->state_;
+
+ ConstElementPtr old_classes = getLeaseClientClasses(old_lease);
+ uint32_t old_state = old_lease->state_;
+
+ // Did we change states or classes?
+ // Note we do not worry about lease type changes, because it makes no
+ // business sense to repurpose a lease as a different type. Other than
+ // some unit tests unrelated to this it never occurs in the code.
+ if ((old_state != new_state) || (old_classes != new_classes)) {
+ // Old classes are moving out of a counted state.
+ if (old_state == Lease::STATE_DEFAULT) {
+ adjustClassCounts(old_classes, -1, old_lease->getType());
+ }
+
+ // New classes are moving into a counted state.
+ if (new_state == Lease::STATE_DEFAULT) {
+ adjustClassCounts(new_classes, 1, new_lease->getType());
+ }
+ }
+}
+
+void
+ClassLeaseCounter::removeLease(LeasePtr lease) {
+ if (!lease) {
+ isc_throw(BadValue, "removeLease - lease cannot be empty");
+ }
+
+ ConstElementPtr classes = getLeaseClientClasses(lease);
+ if (!classes) {
+ return; // Lease limits isn't loaded.
+ }
+
+ // Remove the new lease to its classes.
+ if (lease->state_ == Lease::STATE_DEFAULT) {
+ adjustClassCounts(classes, -1, lease->getType());
+ }
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/memfile_lease_limits.h b/src/lib/dhcpsrv/memfile_lease_limits.h
new file mode 100644
index 0000000..79f62ad
--- /dev/null
+++ b/src/lib/dhcpsrv/memfile_lease_limits.h
@@ -0,0 +1,162 @@
+// Copyright (C) 2022 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 MEMFILE_LEASE_LIMITS_H
+#define MEMFILE_LEASE_LIMITS_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/classify.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet_id.h>
+#include <stats/stats_mgr.h>
+
+#include <unordered_map>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Container that maintains counts of leases per class and lease type.
+///
+/// For methods on this class which accept the lease type as a parameter the value
+/// is one of the following Lease::TYPE_V4, Lease::TYPE_NA, Lease::TYPE_PD. Kea
+/// does not support Lease::TYPE_TA leases and this value is treated as invalid.
+/// Note that for simplicity and performance an instance of container is assumed
+/// to be used either only for DHCPv4 processing or DHCPv6 processing but not both
+/// concurrently.
+class ClassLeaseCounter {
+public:
+ /// @brief Defines ClassCountMap as an unordered map of counts.
+ typedef std::unordered_map<ClientClass, size_t> ClassCountMap;
+
+ /// @brief Constructor
+ ClassLeaseCounter() = default;
+
+ /// @brief Destructor
+ ~ClassLeaseCounter() = default;
+
+ /// @brief Fetches the lease count for the given class and lease type.
+ ///
+ /// @param client_class class for which the count is desired
+ /// @param ltype lease type for which the count is desired, defaults to
+ /// Lease::TYPE_V4
+ ///
+ /// @return Number of leases for the class and lease type. If there is no
+ /// entry found for the class and lease type, a value of zero is returned.
+ size_t getClassCount(const ClientClass& client_class,
+ const Lease::Type& ltype = Lease::TYPE_V4) const;
+
+ /// @brief Sets the lease count for the given class and lease type to a value.
+ ///
+ /// @param client_class class for which the count is desired
+ /// @param count new count value for the class and lease type.
+ /// @param ltype lease type for which the count is desired, defaults
+ /// to Lease::TYPE_V4
+ void setClassCount(const ClientClass& client_class, size_t count,
+ const Lease::Type& ltype = Lease::TYPE_V4);
+
+ /// @brief Adjust the count for a given class and lease type by a signed offset.
+ ///
+ /// If no entry exists in container for the class and type, a new one is created.
+ ///
+ /// @param client_class client class to adjust
+ /// @param offset signed amount to add to the current count
+ /// @param ltype lease type for which the count is desired, defaults
+ /// to Lease::TYPE_V4
+ void adjustClassCount(const ClientClass& client_class, int offset,
+ const Lease::Type& ltype = Lease::TYPE_V4);
+
+ /// @brief Adjust the count for a list of classes for a lease type by
+ /// a signed offset.
+ ///
+ /// @param classes list of classes to adjust
+ /// @param offset signed amount to add the current count
+ /// @param ltype lease type for which the count is desired, defaults
+ /// to Lease::TYPE_V4
+ void adjustClassCounts(data::ConstElementPtr classes, int offset,
+ const Lease::Type& ltype = Lease::TYPE_V4);
+
+ /// @brief Increment the counts for all of a lease's classes by one
+ ///
+ /// Function is intended to be whenever a new lease is being added.
+ ///
+ /// @param lease lease whose classes are to be incremented
+ void addLease(LeasePtr lease);
+
+ /// @brief Adjust class lease counts given a new and old version of a lease
+ ///
+ /// Function is intended to be used whenever an existing lease is being updated.
+ /// If, upon comparison of the two leases, either the lease states or the class
+ /// lists are different then:
+ ///
+ /// -# Decrement the counts for the old class list if the old lease state
+ /// is Lease::STATE_DEFAULT
+ /// -# Increment the counts for the new class list if the new lease state
+ /// is Lease::STATE_DEFAULT
+ ///
+ /// @param new_lease new version of the lease
+ /// @param old_lease old version of the lease
+ void updateLease(LeasePtr new_lease, LeasePtr old_lease);
+
+ /// @brief Decrement the counts for all of a lease's classes by one
+ ///
+ /// Function is intended to be whenever an existing lease is being deleted.
+ ///
+ /// @param lease lease whose classes are to be decremented
+ void removeLease(LeasePtr lease);
+
+ /// @brief Remove all entries.
+ void clear() {
+ addresses_by_class_.clear();
+ pds_by_class_.clear();
+ }
+
+ /// @brief Get the number of entries for a given lease type
+ ///
+ /// @param ltype type of lease for which the number of entries is desired,
+ /// defaults to Lease::TYPE_V4
+ ///
+ /// @return Number of entries for the lease type
+ size_t size(const Lease::Type& ltype = Lease::TYPE_V4) const {
+ return (getConstCountMap(ltype).size());
+ }
+
+ /// @brief Fetches the list of classes from the lease's user-context
+ ///
+ /// @param lease lease from which to fetch classes
+ /// @return ElementPtr to an Element::List containing the client classes or an
+ /// empty List.
+ static data::ConstElementPtr getLeaseClientClasses(LeasePtr lease);
+
+private:
+ /// @brief Fetches the map used to count the given lease type.
+ ///
+ /// @param ltype type of lease of for which the map is desired,
+ /// defaults to Lease::TYPE_V4
+ ///
+ /// @return Reference to the map for the lease type
+ ClassCountMap& getCountMap(const Lease::Type& ltype = Lease::TYPE_V4) {
+ return (ltype == Lease::TYPE_PD ? pds_by_class_ : addresses_by_class_);
+ }
+
+ const ClassCountMap& getConstCountMap(const Lease::Type& ltype = Lease::TYPE_V4) const {
+ return (ltype == Lease::TYPE_PD ? pds_by_class_ : addresses_by_class_);
+ }
+
+ /// @brief Contains counts for classes for addresses. This map is used
+ /// to house either Lease::TYPE_V4 when used for V4 or Lease::TYPE_NA
+ /// when used for V6.
+ ClassCountMap addresses_by_class_;
+
+ /// @brief Contains counts for classes for Lease::TYPE_PD. Applicable
+ /// only for V6 use.
+ ClassCountMap pds_by_class_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // MEMFILE_LEASE_LIMITS_H
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
new file mode 100644
index 0000000..b31617d
--- /dev/null
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -0,0 +1,3575 @@
+// Copyright (C) 2012-2023 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 <asiolink/addr_utilities.h>
+#include <database/database_connection.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_exceptions.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_file_loader.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+#include <stats/stats_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <util/pid_file.h>
+
+#include <cstdio>
+#include <cstring>
+#include <errno.h>
+#include <iostream>
+#include <limits>
+#include <sstream>
+
+namespace {
+
+/// @brief A name of the environmental variable specifying the kea-lfc
+/// program location.
+///
+/// This variable can be set by tests to point to the location of the
+/// kea-lfc program within a build directory. If this variable is not
+/// set, the backend will use the location of the kea-lfc in the
+/// Kea installation directory.
+const char* KEA_LFC_EXECUTABLE_ENV_NAME = "KEA_LFC_EXECUTABLE";
+
+} // namespace
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::util;
+using namespace isc::stats;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents a configuration for Lease File Cleanup.
+///
+/// This class is solely used by the @c Memfile_LeaseMgr as a configuration
+/// information storage for %Lease File Cleanup. Internally, it creates
+/// the interval timer and assigns a callback function (pointer to which is
+/// passed in the constructor), which will be called at the specified
+/// intervals to perform the cleanup. It is also responsible for creating
+/// and maintaining the object which is used to spawn the new process which
+/// executes the @c kea-lfc program.
+///
+/// This functionality is enclosed in a separate class so as the implementation
+/// details are not exposed in the @c Memfile_LeaseMgr header file and
+/// to maintain a single place with the LFC configuration, instead of multiple
+/// members and functions scattered in the @c Memfile_LeaseMgr class.
+class LFCSetup {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Assigns a pointer to the function triggered to perform the cleanup.
+ /// This pointer should point to the appropriate method of the
+ /// @c Memfile_LeaseMgr class.
+ ///
+ /// @param callback A pointer to the callback function.
+ LFCSetup(asiolink::IntervalTimer::Callback callback);
+
+ /// @brief Destructor.
+ ///
+ /// Unregisters LFC timer.
+ ~LFCSetup();
+
+ /// @brief Sets the new configuration for the %Lease File Cleanup.
+ ///
+ /// @param lfc_interval An interval in seconds at which the cleanup should
+ /// be performed.
+ /// @param lease_file4 A pointer to the DHCPv4 lease file to be cleaned up
+ /// or null. If this is null, the @c lease_file6 must be non-null.
+ /// @param lease_file6 A pointer to the DHCPv6 lease file to be cleaned up
+ /// or null. If this is null, the @c lease_file4 must be non-null.
+ /// @param run_once_now A flag that causes LFC to be invoked immediately,
+ /// regardless of the value of lfc_interval. This is primarily used to
+ /// cause lease file schema upgrades upon startup.
+ void setup(const uint32_t lfc_interval,
+ const boost::shared_ptr<CSVLeaseFile4>& lease_file4,
+ const boost::shared_ptr<CSVLeaseFile6>& lease_file6,
+ bool run_once_now = false);
+
+ /// @brief Spawns a new process.
+ void execute();
+
+ /// @brief Checks if the lease file cleanup is in progress.
+ ///
+ /// @return true if the lease file cleanup is being executed.
+ bool isRunning() const;
+
+ /// @brief Returns exit code of the last completed cleanup.
+ int getExitStatus() const;
+
+private:
+
+ /// @brief A pointer to the @c ProcessSpawn object used to execute
+ /// the LFC.
+ boost::scoped_ptr<ProcessSpawn> process_;
+
+ /// @brief A pointer to the callback function executed by the timer.
+ asiolink::IntervalTimer::Callback callback_;
+
+ /// @brief A PID of the last executed LFC process.
+ pid_t pid_;
+
+ /// @brief Pointer to the timer manager.
+ ///
+ /// We have to hold this pointer here to make sure that the timer
+ /// manager is not destroyed before the lease manager.
+ TimerMgrPtr timer_mgr_;
+};
+
+LFCSetup::LFCSetup(asiolink::IntervalTimer::Callback callback)
+ : process_(), callback_(callback), pid_(0),
+ timer_mgr_(TimerMgr::instance()) {
+}
+
+LFCSetup::~LFCSetup() {
+ try {
+ // Remove the timer. This will throw an exception if the timer does not
+ // exist. There are several possible reasons for this:
+ // a) It hasn't been registered (although if the LFC Setup instance
+ // exists it means that the timer must have been registered or that
+ // such registration has been attempted).
+ // b) The registration may fail if the duplicate timer exists or if the
+ // TimerMgr's worker thread is running but if this happens it is a
+ // programming error.
+ // c) The program is shutting down and the timer has been removed by
+ // another component.
+ timer_mgr_->unregisterTimer("memfile-lfc");
+
+ } catch (const std::exception& ex) {
+ // We don't want exceptions being thrown from the destructor so we just
+ // log a message here. The message is logged at debug severity as
+ // we don't want an error message output during shutdown.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_MEMFILE_LFC_UNREGISTER_TIMER_FAILED).arg(ex.what());
+ }
+}
+
+void
+LFCSetup::setup(const uint32_t lfc_interval,
+ const boost::shared_ptr<CSVLeaseFile4>& lease_file4,
+ const boost::shared_ptr<CSVLeaseFile6>& lease_file6,
+ bool run_once_now) {
+
+ // If to nothing to do, punt
+ if (lfc_interval == 0 && !run_once_now) {
+ return;
+ }
+
+ // Start preparing the command line for kea-lfc.
+ std::string executable;
+ char* c_executable = getenv(KEA_LFC_EXECUTABLE_ENV_NAME);
+ if (!c_executable) {
+ executable = KEA_LFC_EXECUTABLE;
+ } else {
+ executable = c_executable;
+ }
+
+ // Gather the base file name.
+ std::string lease_file = lease_file4 ? lease_file4->getFilename() :
+ lease_file6->getFilename();
+
+ // Create the other names by appending suffixes to the base name.
+ ProcessArgs args;
+ // Universe: v4 or v6.
+ args.push_back(lease_file4 ? "-4" : "-6");
+
+ // Previous file.
+ args.push_back("-x");
+ args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
+ Memfile_LeaseMgr::FILE_PREVIOUS));
+ // Input file.
+ args.push_back("-i");
+ args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
+ Memfile_LeaseMgr::FILE_INPUT));
+ // Output file.
+ args.push_back("-o");
+ args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
+ Memfile_LeaseMgr::FILE_OUTPUT));
+ // Finish file.
+ args.push_back("-f");
+ args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
+ Memfile_LeaseMgr::FILE_FINISH));
+ // PID file.
+ args.push_back("-p");
+ args.push_back(Memfile_LeaseMgr::appendSuffix(lease_file,
+ Memfile_LeaseMgr::FILE_PID));
+
+ // The configuration file is currently unused.
+ args.push_back("-c");
+ args.push_back("ignored-path");
+
+ // Create the process (do not start it yet).
+ process_.reset(new ProcessSpawn(LeaseMgr::getIOService(), executable, args));
+
+ // If we've been told to run it once now, invoke the callback directly.
+ if (run_once_now) {
+ callback_();
+ }
+
+ // If it's supposed to run periodically, setup that now.
+ if (lfc_interval > 0) {
+ // Set the timer to call callback function periodically.
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SETUP).arg(lfc_interval);
+
+ // Multiple the lfc_interval value by 1000 as this value specifies
+ // a timeout in seconds, whereas the setup() method expects the
+ // timeout in milliseconds.
+ timer_mgr_->registerTimer("memfile-lfc", callback_, lfc_interval * 1000,
+ asiolink::IntervalTimer::REPEATING);
+ timer_mgr_->setup("memfile-lfc");
+ }
+}
+
+void
+LFCSetup::execute() {
+ try {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_EXECUTE)
+ .arg(process_->getCommandLine());
+ pid_ = process_->spawn();
+
+ } catch (const ProcessSpawnError&) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_SPAWN_FAIL);
+ }
+}
+
+bool
+LFCSetup::isRunning() const {
+ return (process_ && process_->isRunning(pid_));
+}
+
+int
+LFCSetup::getExitStatus() const {
+ if (!process_) {
+ isc_throw(InvalidOperation, "unable to obtain LFC process exit code: "
+ " the process is null");
+ }
+ return (process_->getExitStatus(pid_));
+}
+
+
+/// @brief Base Memfile derivation of the statistical lease data query
+///
+/// This class provides the functionality such as results storage and row
+/// fetching common to fulfilling the statistical lease data query.
+///
+class MemfileLeaseStatsQuery : public LeaseStatsQuery {
+public:
+ /// @brief Constructor for all subnets query
+ ///
+ /// @param select_mode The selection criteria which is either ALL_SUBNETS or
+ /// ALL_SUBNET_POOLS
+ MemfileLeaseStatsQuery(const SelectMode& select_mode = ALL_SUBNETS)
+ : LeaseStatsQuery(select_mode), rows_(0), next_pos_(rows_.end()) {
+ };
+
+ /// @brief Constructor for single subnet query
+ ///
+ /// @param subnet_id ID of the desired subnet
+ MemfileLeaseStatsQuery(const SubnetID& subnet_id)
+ : LeaseStatsQuery(subnet_id), rows_(0), next_pos_(rows_.end()) {
+ };
+
+ /// @brief Constructor for subnet range query
+ ///
+ /// @param first_subnet_id ID of the first subnet in the desired range
+ /// @param last_subnet_id ID of the last subnet in the desired range
+ MemfileLeaseStatsQuery(const SubnetID& first_subnet_id, const SubnetID& last_subnet_id)
+ : LeaseStatsQuery(first_subnet_id, last_subnet_id), rows_(0), next_pos_(rows_.end()) {
+ };
+
+ /// @brief Destructor
+ virtual ~MemfileLeaseStatsQuery() {};
+
+ /// @brief Fetches the next row in the result set
+ ///
+ /// Once the internal result set has been populated by invoking the
+ /// the start() method, this method is used to iterate over the
+ /// result set rows. Once the last row has been fetched, subsequent
+ /// calls will return false.
+ /// @param row Storage for the fetched row
+ ///
+ /// @return True if the fetch succeeded, false if there are no more
+ /// rows to fetch.
+ virtual bool getNextRow(LeaseStatsRow& row) {
+ if (next_pos_ == rows_.end()) {
+ return (false);
+ }
+
+ row = *next_pos_;
+ ++next_pos_;
+ return (true);
+ }
+
+ /// @brief Returns the number of rows in the result set
+ int getRowCount() const {
+ return (rows_.size());
+ }
+
+protected:
+ /// @brief A vector containing the "result set"
+ std::vector<LeaseStatsRow> rows_;
+
+ /// @brief An iterator for accessing the next row within the result set
+ std::vector<LeaseStatsRow>::iterator next_pos_;
+};
+
+/// @brief Memfile derivation of the IPv4 statistical lease data query
+///
+/// This class is used to recalculate IPv4 lease statistics for Memfile
+/// lease storage. It does so by iterating over the given storage,
+/// accumulating counts of leases in each of the monitored lease states
+/// for each subnet and storing these counts in an internal collection.
+/// The populated result set will contain one entry per monitored state
+/// per subnet.
+///
+class MemfileLeaseStatsQuery4 : public MemfileLeaseStatsQuery {
+public:
+ /// @brief Constructor for an all subnets query
+ ///
+ /// @param storage4 A pointer to the v4 lease storage to be counted
+ /// @param select_mode The selection criteria which is either ALL_SUBNETS or
+ /// ALL_SUBNET_POOLS
+ MemfileLeaseStatsQuery4(Lease4Storage& storage4,
+ const SelectMode& select_mode = ALL_SUBNETS)
+ : MemfileLeaseStatsQuery(select_mode), storage4_(storage4) {
+ };
+
+ /// @brief Constructor for a single subnet query
+ ///
+ /// @param storage4 A pointer to the v4 lease storage to be counted
+ /// @param subnet_id ID of the desired subnet
+ MemfileLeaseStatsQuery4(Lease4Storage& storage4, const SubnetID& subnet_id)
+ : MemfileLeaseStatsQuery(subnet_id), storage4_(storage4) {
+ };
+
+ /// @brief Constructor for a subnet range query
+ ///
+ /// @param storage4 A pointer to the v4 lease storage to be counted
+ /// @param first_subnet_id ID of the first subnet in the desired range
+ /// @param last_subnet_id ID of the last subnet in the desired range
+ MemfileLeaseStatsQuery4(Lease4Storage& storage4, const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id)
+ : MemfileLeaseStatsQuery(first_subnet_id, last_subnet_id), storage4_(storage4) {
+ };
+
+ /// @brief Destructor
+ virtual ~MemfileLeaseStatsQuery4() {};
+
+ /// @brief Creates the IPv4 lease statistical data result set
+ ///
+ /// The result set is populated by iterating over the IPv4 leases in
+ /// storage, in ascending order by subnet id or by subnet id and pool id,
+ /// accumulating the lease state counts per subnet or per subnet and pool.
+ /// At the completion of all entries for a given subnet or pool, the counts
+ /// are used to create LeaseStatsRow instances which are appended to an
+ /// internal vector. The process results in a vector containing one entry
+ /// per state per subnet or per subnet and pool.
+ ///
+ /// Currently the states counted are:
+ ///
+ /// - Lease::STATE_DEFAULT (i.e. assigned)
+ /// - Lease::STATE_DECLINED
+ void start() {
+ switch (getSelectMode()) {
+ case ALL_SUBNETS:
+ case SINGLE_SUBNET:
+ case SUBNET_RANGE:
+ startSubnets();
+ break;
+
+ case ALL_SUBNET_POOLS:
+ startSubnetPools();
+ break;
+ }
+ }
+
+private:
+ /// @brief Creates the IPv4 lease statistical data result set
+ ///
+ /// The result set is populated by iterating over the IPv4 leases in
+ /// storage, in ascending order by subnet id, accumulating the lease state
+ /// counts per subnet.
+ /// At the completion of all entries for a given subnet, the counts are
+ /// used to create LeaseStatsRow instances which are appended to an
+ /// internal vector. The process results in a vector containing one entry
+ /// per state per subnet.
+ ///
+ /// Currently the states counted are:
+ ///
+ /// - Lease::STATE_DEFAULT (i.e. assigned)
+ /// - Lease::STATE_DECLINED
+ void startSubnets() {
+ const Lease4StorageSubnetIdIndex& idx
+ = storage4_.get<SubnetIdIndexTag>();
+
+ // Set lower and upper bounds based on select mode
+ Lease4StorageSubnetIdIndex::const_iterator lower;
+ Lease4StorageSubnetIdIndex::const_iterator upper;
+
+ switch (getSelectMode()) {
+ case ALL_SUBNETS:
+ lower = idx.begin();
+ upper = idx.end();
+ break;
+
+ case SINGLE_SUBNET:
+ lower = idx.lower_bound(getFirstSubnetID());
+ upper = idx.upper_bound(getFirstSubnetID());
+ break;
+
+ case SUBNET_RANGE:
+ lower = idx.lower_bound(getFirstSubnetID());
+ upper = idx.upper_bound(getLastSubnetID());
+ break;
+
+ default:
+ return;
+ }
+
+ // Return an empty set if there are no rows.
+ if (lower == upper) {
+ return;
+ }
+
+ // Iterate over the leases in order by subnet, accumulating per
+ // subnet counts for each state of interest. As we finish each
+ // subnet, add the appropriate rows to our result set.
+ SubnetID cur_id = 0;
+ int64_t assigned = 0;
+ int64_t declined = 0;
+ for (Lease4StorageSubnetIdIndex::const_iterator lease = lower;
+ lease != upper; ++lease) {
+ // If we've hit the next subnet, add rows for the current subnet
+ // and wipe the accumulators
+ if ((*lease)->subnet_id_ != cur_id) {
+ if (cur_id > 0) {
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id,
+ Lease::STATE_DEFAULT,
+ assigned));
+ assigned = 0;
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id,
+ Lease::STATE_DECLINED,
+ declined));
+ declined = 0;
+ }
+ }
+
+ // Update current subnet id
+ cur_id = (*lease)->subnet_id_;
+ }
+
+ // Bump the appropriate accumulator
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ ++assigned;
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ ++declined;
+ }
+ }
+
+ // Make the rows for last subnet
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DEFAULT,
+ assigned));
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DECLINED,
+ declined));
+ }
+
+ // Reset the next row position back to the beginning of the rows.
+ next_pos_ = rows_.begin();
+ }
+
+ /// @brief Creates the IPv4 lease statistical data result set
+ ///
+ /// The result set is populated by iterating over the IPv4 leases in
+ /// storage, in ascending order by subnet id and pool id, accumulating the
+ /// lease state counts per subnet and pool.
+ /// At the completion of all entries for a given subnet or pool, the counts
+ /// are used to create LeaseStatsRow instances which are appended to an
+ /// internal vector. The process results in a vector containing one entry
+ /// per state per subnet and pool.
+ ///
+ /// Currently the states counted are:
+ ///
+ /// - Lease::STATE_DEFAULT (i.e. assigned)
+ /// - Lease::STATE_DECLINED
+ void startSubnetPools() {
+ const Lease4StorageSubnetIdPoolIdIndex& idx
+ = storage4_.get<SubnetIdPoolIdIndexTag>();
+
+ // Set lower and upper bounds based on select mode
+ Lease4StorageSubnetIdPoolIdIndex::const_iterator lower;
+ Lease4StorageSubnetIdPoolIdIndex::const_iterator upper;
+ switch (getSelectMode()) {
+ case ALL_SUBNET_POOLS:
+ lower = idx.begin();
+ upper = idx.end();
+ break;
+
+ default:
+ return;
+ }
+
+ // Return an empty set if there are no rows.
+ if (lower == upper) {
+ return;
+ }
+
+ // Iterate over the leases in order by subnet and pool, accumulating per
+ // subnet and pool counts for each state of interest. As we finish each
+ // subnet or pool, add the appropriate rows to our result set.
+ SubnetID cur_id = 0;
+ uint32_t cur_pool_id = 0;
+ int64_t assigned = 0;
+ int64_t declined = 0;
+ for (Lease4StorageSubnetIdPoolIdIndex::const_iterator lease = lower;
+ lease != upper; ++lease) {
+ // If we've hit the next pool, add rows for the current subnet and
+ // pool and wipe the accumulators
+ if ((*lease)->pool_id_ != cur_pool_id) {
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id,
+ Lease::STATE_DEFAULT,
+ assigned, cur_pool_id));
+ assigned = 0;
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id,
+ Lease::STATE_DECLINED,
+ declined, cur_pool_id));
+ declined = 0;
+ }
+
+ // Update current pool id
+ cur_pool_id = (*lease)->pool_id_;
+ }
+
+ // If we've hit the next subnet, add rows for the current subnet
+ // and wipe the accumulators
+ if ((*lease)->subnet_id_ != cur_id) {
+ if (cur_id > 0) {
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id,
+ Lease::STATE_DEFAULT,
+ assigned, cur_pool_id));
+ assigned = 0;
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id,
+ Lease::STATE_DECLINED,
+ declined, cur_pool_id));
+ declined = 0;
+ }
+ }
+
+ // Update current subnet id
+ cur_id = (*lease)->subnet_id_;
+
+ // Reset pool id
+ cur_pool_id = 0;
+ }
+
+ // Bump the appropriate accumulator
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ ++assigned;
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ ++declined;
+ }
+ }
+
+ // Make the rows for last subnet
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DEFAULT,
+ assigned, cur_pool_id));
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DECLINED,
+ declined, cur_pool_id));
+ }
+
+ // Reset the next row position back to the beginning of the rows.
+ next_pos_ = rows_.begin();
+ }
+
+ /// @brief The Memfile storage containing the IPv4 leases to analyze
+ Lease4Storage& storage4_;
+};
+
+
+/// @brief Memfile derivation of the IPv6 statistical lease data query
+///
+/// This class is used to recalculate IPv6 lease statistics for Memfile
+/// lease storage. It does so by iterating over the given storage,
+/// accumulating counts of leases in each of the monitored lease states
+/// for each subnet and storing these counts in an internal collection.
+/// The populated result set will contain one entry per monitored state
+/// per subnet.
+///
+class MemfileLeaseStatsQuery6 : public MemfileLeaseStatsQuery {
+public:
+ /// @brief Constructor
+ ///
+ /// @param storage6 A pointer to the v6 lease storage to be counted
+ /// @param select_mode The selection criteria which is either ALL_SUBNETS or
+ /// ALL_SUBNET_POOLS
+ MemfileLeaseStatsQuery6(Lease6Storage& storage6,
+ const SelectMode& select_mode = ALL_SUBNETS)
+ : MemfileLeaseStatsQuery(select_mode), storage6_(storage6) {
+ };
+
+ /// @brief Constructor for a single subnet query
+ ///
+ /// @param storage6 A pointer to the v6 lease storage to be counted
+ /// @param subnet_id ID of the desired subnet
+ MemfileLeaseStatsQuery6(Lease6Storage& storage6, const SubnetID& subnet_id)
+ : MemfileLeaseStatsQuery(subnet_id), storage6_(storage6) {
+ };
+
+ /// @brief Constructor for a subnet range query
+ ///
+ /// @param storage6 A pointer to the v6 lease storage to be counted
+ /// @param first_subnet_id ID of the first subnet in the desired range
+ /// @param last_subnet_id ID of the last subnet in the desired range
+ MemfileLeaseStatsQuery6(Lease6Storage& storage6, const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id)
+ : MemfileLeaseStatsQuery(first_subnet_id, last_subnet_id), storage6_(storage6) {
+ };
+
+ /// @brief Destructor
+ virtual ~MemfileLeaseStatsQuery6() {};
+
+ /// @brief Creates the IPv6 lease statistical data result set
+ ///
+ /// The result set is populated by iterating over the IPv6 leases in
+ /// storage, in ascending order by subnet id or by subnet id and pool id,
+ /// accumulating the lease state counts per subnet or per subnet and pool.
+ /// At the completion of all entries for a given subnet or pool, the counts
+ /// are used to create LeaseStatsRow instances which are appended to an
+ /// internal vector. The process results in a vector containing one entry
+ /// per state per subnet or per subnet and pool.
+ ///
+ /// Currently the states counted are:
+ ///
+ /// - Lease::STATE_DEFAULT (i.e. assigned)
+ /// - Lease::STATE_DECLINED
+ void start() {
+ switch (getSelectMode()) {
+ case ALL_SUBNETS:
+ case SINGLE_SUBNET:
+ case SUBNET_RANGE:
+ startSubnets();
+ break;
+
+ case ALL_SUBNET_POOLS:
+ startSubnetPools();
+ break;
+ }
+ }
+
+private:
+ /// @brief Creates the IPv6 lease statistical data result set
+ ///
+ /// The result set is populated by iterating over the IPv6 leases in
+ /// storage, in ascending order by subnet id, accumulating the lease state
+ /// counts per subnet.
+ /// At the completion of all entries for a given subnet, the counts are
+ /// used to create LeaseStatsRow instances which are appended to an
+ /// internal vector. The process results in a vector containing one entry
+ /// per state per subnet.
+ ///
+ /// Currently the states counted are:
+ ///
+ /// - Lease::STATE_DEFAULT (i.e. assigned)
+ /// - Lease::STATE_DECLINED
+ virtual void startSubnets() {
+ const Lease6StorageSubnetIdIndex& idx
+ = storage6_.get<SubnetIdIndexTag>();
+
+ // Set lower and upper bounds based on select mode
+ Lease6StorageSubnetIdIndex::const_iterator lower;
+ Lease6StorageSubnetIdIndex::const_iterator upper;
+ switch (getSelectMode()) {
+ case ALL_SUBNETS:
+ lower = idx.begin();
+ upper = idx.end();
+ break;
+
+ case SINGLE_SUBNET:
+ lower = idx.lower_bound(getFirstSubnetID());
+ upper = idx.upper_bound(getFirstSubnetID());
+ break;
+
+ case SUBNET_RANGE:
+ lower = idx.lower_bound(getFirstSubnetID());
+ upper = idx.upper_bound(getLastSubnetID());
+ break;
+
+ default:
+ return;
+ }
+
+ // Return an empty set if there are no rows.
+ if (lower == upper) {
+ return;
+ }
+
+ // Iterate over the leases in order by subnet, accumulating per
+ // subnet counts for each state of interest. As we finish each
+ // subnet, add the appropriate rows to our result set.
+ SubnetID cur_id = 0;
+ int64_t assigned = 0;
+ int64_t declined = 0;
+ int64_t assigned_pds = 0;
+ for (Lease6StorageSubnetIdIndex::const_iterator lease = lower;
+ lease != upper; ++lease) {
+ // If we've hit the next subnet, add rows for the current subnet
+ // and wipe the accumulators
+ if ((*lease)->subnet_id_ != cur_id) {
+ if (cur_id > 0) {
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DEFAULT,
+ assigned));
+ assigned = 0;
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DECLINED,
+ declined));
+ declined = 0;
+ }
+
+ if (assigned_pds > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD,
+ Lease::STATE_DEFAULT,
+ assigned_pds));
+ assigned_pds = 0;
+ }
+ }
+
+ // Update current subnet id
+ cur_id = (*lease)->subnet_id_;
+ }
+
+ // Bump the appropriate accumulator
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ switch((*lease)->type_) {
+ case Lease::TYPE_NA:
+ ++assigned;
+ break;
+ case Lease::TYPE_PD:
+ ++assigned_pds;
+ break;
+ default:
+ break;
+ }
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ // In theory only NAs can be declined
+ if (((*lease)->type_) == Lease::TYPE_NA) {
+ ++declined;
+ }
+ }
+ }
+
+ // Make the rows for last subnet, unless there were no rows
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DEFAULT, assigned));
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DECLINED, declined));
+ }
+
+ if (assigned_pds > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD,
+ Lease::STATE_DEFAULT, assigned_pds));
+ }
+
+ // Set the next row position to the beginning of the rows.
+ next_pos_ = rows_.begin();
+ }
+
+ /// @brief Creates the IPv6 lease statistical data result set
+ ///
+ /// The result set is populated by iterating over the IPv6 leases in
+ /// storage, in ascending order by subnet id and pool id, accumulating the
+ /// lease state counts per subnet and pool.
+ /// At the completion of all entries for a given subnet or pool, the counts
+ /// are used to create LeaseStatsRow instances which are appended to an
+ /// internal vector. The process results in a vector containing one entry
+ /// per state per subnet and pool.
+ ///
+ /// Currently the states counted are:
+ ///
+ /// - Lease::STATE_DEFAULT (i.e. assigned)
+ /// - Lease::STATE_DECLINED
+ virtual void startSubnetPools() {
+ const Lease6StorageSubnetIdPoolIdIndex& idx
+ = storage6_.get<SubnetIdPoolIdIndexTag>();
+
+ // Set lower and upper bounds based on select mode
+ Lease6StorageSubnetIdPoolIdIndex::const_iterator lower;
+ Lease6StorageSubnetIdPoolIdIndex::const_iterator upper;
+ switch (getSelectMode()) {
+ case ALL_SUBNET_POOLS:
+ lower = idx.begin();
+ upper = idx.end();
+ break;
+
+ default:
+ return;
+ }
+
+ // Return an empty set if there are no rows.
+ if (lower == upper) {
+ return;
+ }
+
+ // Iterate over the leases in order by subnet, accumulating per
+ // subnet counts for each state of interest. As we finish each
+ // subnet, add the appropriate rows to our result set.
+ SubnetID cur_id = 0;
+ uint32_t cur_pool_id = 0;
+ int64_t assigned = 0;
+ int64_t declined = 0;
+ int64_t assigned_pds = 0;
+ for (Lease6StorageSubnetIdPoolIdIndex::const_iterator lease = lower;
+ lease != upper; ++lease) {
+ // If we've hit the next pool, add rows for the current subnet and
+ // pool and wipe the accumulators
+ if ((*lease)->pool_id_ != cur_pool_id) {
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DEFAULT,
+ assigned, cur_pool_id));
+ assigned = 0;
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DECLINED,
+ declined, cur_pool_id));
+ declined = 0;
+ }
+
+ if (assigned_pds > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD,
+ Lease::STATE_DEFAULT,
+ assigned_pds, cur_pool_id));
+ assigned_pds = 0;
+ }
+
+ // Update current pool id
+ cur_pool_id = (*lease)->pool_id_;
+ }
+
+ // If we've hit the next subnet, add rows for the current subnet
+ // and wipe the accumulators
+ if ((*lease)->subnet_id_ != cur_id) {
+ if (cur_id > 0) {
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DEFAULT,
+ assigned, cur_pool_id));
+ assigned = 0;
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DECLINED,
+ declined, cur_pool_id));
+ declined = 0;
+ }
+
+ if (assigned_pds > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD,
+ Lease::STATE_DEFAULT,
+ assigned_pds, cur_pool_id));
+ assigned_pds = 0;
+ }
+ }
+
+ // Update current subnet id
+ cur_id = (*lease)->subnet_id_;
+
+ // Reset pool id
+ cur_pool_id = 0;
+ }
+
+ // Bump the appropriate accumulator
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ switch((*lease)->type_) {
+ case Lease::TYPE_NA:
+ ++assigned;
+ break;
+ case Lease::TYPE_PD:
+ ++assigned_pds;
+ break;
+ default:
+ break;
+ }
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ // In theory only NAs can be declined
+ if (((*lease)->type_) == Lease::TYPE_NA) {
+ ++declined;
+ }
+ }
+ }
+
+ // Make the rows for last subnet, unless there were no rows
+ if (assigned > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DEFAULT, assigned,
+ cur_pool_id));
+ }
+
+ if (declined > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+ Lease::STATE_DECLINED, declined,
+ cur_pool_id));
+ }
+
+ if (assigned_pds > 0) {
+ rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD,
+ Lease::STATE_DEFAULT, assigned_pds,
+ cur_pool_id));
+ }
+
+ // Set the next row position to the beginning of the rows.
+ next_pos_ = rows_.begin();
+ }
+
+ /// @brief The Memfile storage containing the IPv6 leases to analyze
+ Lease6Storage& storage6_;
+};
+
+// Explicit definition of class static constants. Values are given in the
+// declaration so they're not needed here.
+const int Memfile_LeaseMgr::MAJOR_VERSION_V4;
+const int Memfile_LeaseMgr::MINOR_VERSION_V4;
+const int Memfile_LeaseMgr::MAJOR_VERSION_V6;
+const int Memfile_LeaseMgr::MINOR_VERSION_V6;
+
+Memfile_LeaseMgr::Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+ : TrackingLeaseMgr(), lfc_setup_(), conn_(parameters), mutex_(new std::mutex) {
+ bool conversion_needed = false;
+
+ // Check if the extended info tables are enabled.
+ setExtendedInfoTablesEnabled(parameters);
+
+ // Check the universe and use v4 file or v6 file.
+ std::string universe = conn_.getParameter("universe");
+ if (universe == "4") {
+ std::string file4 = initLeaseFilePath(V4);
+ if (!file4.empty()) {
+ conversion_needed = loadLeasesFromFiles<Lease4,
+ CSVLeaseFile4>(file4,
+ lease_file4_,
+ storage4_);
+ static_cast<void>(extractExtendedInfo4(false, false));
+ }
+ } else {
+ std::string file6 = initLeaseFilePath(V6);
+ if (!file6.empty()) {
+ conversion_needed = loadLeasesFromFiles<Lease6,
+ CSVLeaseFile6>(file6,
+ lease_file6_,
+ storage6_);
+ static_cast<void>(buildExtendedInfoTables6Internal(false, false));
+ }
+ }
+
+ // If lease persistence have been disabled for both v4 and v6,
+ // issue a warning. It is ok not to write leases to disk when
+ // doing testing, but it should not be done in normal server
+ // operation.
+ if (!persistLeases(V4) && !persistLeases(V6)) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_NO_STORAGE);
+ } else {
+ if (conversion_needed) {
+ auto const& version(getVersion());
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MEMFILE_CONVERTING_LEASE_FILES)
+ .arg(version.first).arg(version.second);
+ }
+ lfcSetup(conversion_needed);
+ }
+}
+
+Memfile_LeaseMgr::~Memfile_LeaseMgr() {
+ if (lease_file4_) {
+ lease_file4_->close();
+ lease_file4_.reset();
+ }
+ if (lease_file6_) {
+ lease_file6_->close();
+ lease_file6_.reset();
+ }
+}
+
+std::string
+Memfile_LeaseMgr::getDBVersion(Universe const& u) {
+ std::stringstream tmp;
+ tmp << "Memfile backend ";
+ if (u == V4) {
+ tmp << MAJOR_VERSION_V4 << "." << MINOR_VERSION_V4;
+ } else if (u == V6) {
+ tmp << MAJOR_VERSION_V6 << "." << MINOR_VERSION_V6;
+ }
+ return tmp.str();
+}
+
+bool
+Memfile_LeaseMgr::addLeaseInternal(const Lease4Ptr& lease) {
+ if (getLease4Internal(lease->addr_)) {
+ // there is a lease with specified address already
+ return (false);
+ }
+
+ // Try to write a lease to disk first. If this fails, the lease will
+ // not be inserted to the memory and the disk and in-memory data will
+ // remain consistent.
+ if (persistLeases(V4)) {
+ lease_file4_->append(*lease);
+ }
+
+ storage4_.insert(lease);
+
+ // Update lease current expiration time (allows update between the creation
+ // of the Lease up to the point of insertion in the database).
+ lease->updateCurrentExpirationTime();
+
+ // Increment class lease counters.
+ class_lease_counter_.addLease(lease);
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackAddLease(lease);
+ }
+
+ return (true);
+}
+
+bool
+Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (addLeaseInternal(lease));
+ } else {
+ return (addLeaseInternal(lease));
+ }
+}
+
+bool
+Memfile_LeaseMgr::addLeaseInternal(const Lease6Ptr& lease) {
+ if (getLease6Internal(lease->type_, lease->addr_)) {
+ // there is a lease with specified address already
+ return (false);
+ }
+
+ // Try to write a lease to disk first. If this fails, the lease will
+ // not be inserted to the memory and the disk and in-memory data will
+ // remain consistent.
+ if (persistLeases(V6)) {
+ lease_file6_->append(*lease);
+ }
+
+ lease->extended_info_action_ = Lease6::ACTION_IGNORE;
+ storage6_.insert(lease);
+
+ // Update lease current expiration time (allows update between the creation
+ // of the Lease up to the point of insertion in the database).
+ lease->updateCurrentExpirationTime();
+
+ // Increment class lease counters.
+ class_lease_counter_.addLease(lease);
+
+ if (getExtendedInfoTablesEnabled()) {
+ static_cast<void>(addExtendedInfo6(lease));
+ }
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackAddLease(lease);
+ }
+
+ return (true);
+}
+
+bool
+Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (addLeaseInternal(lease));
+ } else {
+ return (addLeaseInternal(lease));
+ }
+}
+
+Lease4Ptr
+Memfile_LeaseMgr::getLease4Internal(const isc::asiolink::IOAddress& addr) const {
+ const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
+ Lease4StorageAddressIndex::iterator l = idx.find(addr);
+ if (l == idx.end()) {
+ return (Lease4Ptr());
+ } else {
+ return (Lease4Ptr(new Lease4(**l)));
+ }
+}
+
+Lease4Ptr
+Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLease4Internal(addr));
+ } else {
+ return (getLease4Internal(addr));
+ }
+}
+
+void
+Memfile_LeaseMgr::getLease4Internal(const HWAddr& hwaddr,
+ Lease4Collection& collection) const {
+ // Using composite index by 'hw address' and 'subnet id'. It is
+ // ok to use it for searching by the 'hw address' only.
+ const Lease4StorageHWAddressSubnetIdIndex& idx =
+ storage4_.get<HWAddressSubnetIdIndexTag>();
+ std::pair<Lease4StorageHWAddressSubnetIdIndex::const_iterator,
+ Lease4StorageHWAddressSubnetIdIndex::const_iterator> l
+ = idx.equal_range(boost::make_tuple(hwaddr.hwaddr_));
+
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
+
+ Lease4Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLease4Internal(hwaddr, collection);
+ } else {
+ getLease4Internal(hwaddr, collection);
+ }
+
+ return (collection);
+}
+
+Lease4Ptr
+Memfile_LeaseMgr::getLease4Internal(const HWAddr& hwaddr,
+ SubnetID subnet_id) const {
+ // Get the index by HW Address and Subnet Identifier.
+ const Lease4StorageHWAddressSubnetIdIndex& idx =
+ storage4_.get<HWAddressSubnetIdIndexTag>();
+ // Try to find the lease using HWAddr and subnet id.
+ Lease4StorageHWAddressSubnetIdIndex::const_iterator lease =
+ idx.find(boost::make_tuple(hwaddr.hwaddr_, subnet_id));
+ // Lease was not found. Return empty pointer to the caller.
+ if (lease == idx.end()) {
+ return (Lease4Ptr());
+ }
+
+ // Lease was found. Return it to the caller.
+ return (Lease4Ptr(new Lease4(**lease)));
+}
+
+Lease4Ptr
+Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
+ .arg(hwaddr.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLease4Internal(hwaddr, subnet_id));
+ } else {
+ return (getLease4Internal(hwaddr, subnet_id));
+ }
+}
+
+void
+Memfile_LeaseMgr::getLease4Internal(const ClientId& client_id,
+ Lease4Collection& collection) const {
+ // Using composite index by 'client id' and 'subnet id'. It is ok
+ // to use it to search by 'client id' only.
+ const Lease4StorageClientIdSubnetIdIndex& idx =
+ storage4_.get<ClientIdSubnetIdIndexTag>();
+ std::pair<Lease4StorageClientIdSubnetIdIndex::const_iterator,
+ Lease4StorageClientIdSubnetIdIndex::const_iterator> l
+ = idx.equal_range(boost::make_tuple(client_id.getClientId()));
+
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLease4(const ClientId& client_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_CLIENTID).arg(client_id.toText());
+
+ Lease4Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLease4Internal(client_id, collection);
+ } else {
+ getLease4Internal(client_id, collection);
+ }
+
+ return (collection);
+}
+
+Lease4Ptr
+Memfile_LeaseMgr::getLease4Internal(const ClientId& client_id,
+ SubnetID subnet_id) const {
+ // Get the index by client and subnet id.
+ const Lease4StorageClientIdSubnetIdIndex& idx =
+ storage4_.get<ClientIdSubnetIdIndexTag>();
+ // Try to get the lease using client id and subnet id.
+ Lease4StorageClientIdSubnetIdIndex::const_iterator lease =
+ idx.find(boost::make_tuple(client_id.getClientId(), subnet_id));
+ // Lease was not found. Return empty pointer to the caller.
+ if (lease == idx.end()) {
+ return (Lease4Ptr());
+ }
+ // Lease was found. Return it to the caller.
+ return (Lease4Ptr(new Lease4(**lease)));
+}
+
+Lease4Ptr
+Memfile_LeaseMgr::getLease4(const ClientId& client_id,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
+ .arg(client_id.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLease4Internal(client_id, subnet_id));
+ } else {
+ return (getLease4Internal(client_id, subnet_id));
+ }
+}
+
+void
+Memfile_LeaseMgr::getLeases4Internal(SubnetID subnet_id,
+ Lease4Collection& collection) const {
+ const Lease4StorageSubnetIdIndex& idx = storage4_.get<SubnetIdIndexTag>();
+ std::pair<Lease4StorageSubnetIdIndex::const_iterator,
+ Lease4StorageSubnetIdIndex::const_iterator> l =
+ idx.equal_range(subnet_id);
+
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4(SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_SUBID4)
+ .arg(subnet_id);
+
+ Lease4Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases4Internal(subnet_id, collection);
+ } else {
+ getLeases4Internal(subnet_id, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases4Internal(const std::string& hostname,
+ Lease4Collection& collection) const {
+ const Lease4StorageHostnameIndex& idx = storage4_.get<HostnameIndexTag>();
+ std::pair<Lease4StorageHostnameIndex::const_iterator,
+ Lease4StorageHostnameIndex::const_iterator> l =
+ idx.equal_range(hostname);
+
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4(const std::string& hostname) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_HOSTNAME4)
+ .arg(hostname);
+
+ Lease4Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases4Internal(hostname, collection);
+ } else {
+ getLeases4Internal(hostname, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases4Internal(Lease4Collection& collection) const {
+ for (auto lease = storage4_.begin(); lease != storage4_.end(); ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET4);
+
+ Lease4Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases4Internal(collection);
+ } else {
+ getLeases4Internal(collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases4Internal(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ Lease4Collection& collection) const {
+ const Lease4StorageAddressIndex& idx = storage4_.get<AddressIndexTag>();
+ Lease4StorageAddressIndex::const_iterator lb = idx.lower_bound(lower_bound_address);
+
+ // Exclude the lower bound address specified by the caller.
+ if ((lb != idx.end()) && ((*lb)->addr_ == lower_bound_address)) {
+ ++lb;
+ }
+
+ // Return all other leases being within the page size.
+ for (auto lease = lb;
+ (lease != idx.end()) && (std::distance(lb, lease) < page_size.page_size_);
+ ++lease) {
+ collection.push_back(Lease4Ptr(new Lease4(**lease)));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const {
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_PAGE4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText());
+
+ Lease4Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases4Internal(lower_bound_address, page_size, collection);
+ } else {
+ getLeases4Internal(lower_bound_address, page_size, collection);
+ }
+
+ return (collection);
+}
+
+Lease6Ptr
+Memfile_LeaseMgr::getLease6Internal(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const {
+ Lease6Storage::iterator l = storage6_.find(addr);
+ if (l == storage6_.end() || !(*l) || ((*l)->type_ != type)) {
+ return (Lease6Ptr());
+ } else {
+ return (Lease6Ptr(new Lease6(**l)));
+ }
+}
+
+Lease6Ptr
+Memfile_LeaseMgr::getAnyLease6Internal(const isc::asiolink::IOAddress& addr) const {
+ Lease6Storage::iterator l = storage6_.find(addr);
+ if (l == storage6_.end() || !(*l)) {
+ return (Lease6Ptr());
+ } else {
+ return (Lease6Ptr(new Lease6(**l)));
+ }
+}
+
+Lease6Ptr
+Memfile_LeaseMgr::getLease6(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR6)
+ .arg(addr.toText())
+ .arg(Lease::typeToText(type));
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLease6Internal(type, addr));
+ } else {
+ return (getLease6Internal(type, addr));
+ }
+}
+
+void
+Memfile_LeaseMgr::getLeases6Internal(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid,
+ Lease6Collection& collection) const {
+ // Get the index by DUID, IAID, lease type.
+ const Lease6StorageDuidIaidTypeIndex& idx = storage6_.get<DuidIaidTypeIndexTag>();
+ // Try to get the lease using the DUID, IAID and lease type.
+ std::pair<Lease6StorageDuidIaidTypeIndex::const_iterator,
+ Lease6StorageDuidIaidTypeIndex::const_iterator> l =
+ idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
+
+ for (Lease6StorageDuidIaidTypeIndex::const_iterator lease =
+ l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_DUID)
+ .arg(iaid)
+ .arg(duid.toText())
+ .arg(Lease::typeToText(type));
+
+ Lease6Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases6Internal(type, duid, iaid, collection);
+ } else {
+ getLeases6Internal(type, duid, iaid, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases6Internal(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid,
+ SubnetID subnet_id,
+ Lease6Collection& collection) const {
+ // Get the index by DUID, IAID, lease type.
+ const Lease6StorageDuidIaidTypeIndex& idx = storage6_.get<DuidIaidTypeIndexTag>();
+ // Try to get the lease using the DUID, IAID and lease type.
+ std::pair<Lease6StorageDuidIaidTypeIndex::const_iterator,
+ Lease6StorageDuidIaidTypeIndex::const_iterator> l =
+ idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
+
+ for (Lease6StorageDuidIaidTypeIndex::const_iterator lease =
+ l.first; lease != l.second; ++lease) {
+ // Filter out the leases which subnet id doesn't match.
+ if ((*lease)->subnet_id_ == subnet_id) {
+ collection.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
+ .arg(iaid)
+ .arg(subnet_id)
+ .arg(duid.toText())
+ .arg(Lease::typeToText(type));
+
+ Lease6Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases6Internal(type, duid, iaid, subnet_id, collection);
+ } else {
+ getLeases6Internal(type, duid, iaid, subnet_id, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases6Internal(SubnetID subnet_id,
+ Lease6Collection& collection) const {
+ const Lease6StorageSubnetIdIndex& idx = storage6_.get<SubnetIdIndexTag>();
+ std::pair<Lease6StorageSubnetIdIndex::const_iterator,
+ Lease6StorageSubnetIdIndex::const_iterator> l =
+ idx.equal_range(subnet_id);
+
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6(SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_SUBID6)
+ .arg(subnet_id);
+
+ Lease6Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases6Internal(subnet_id, collection);
+ } else {
+ getLeases6Internal(subnet_id, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases6Internal(const std::string& hostname,
+ Lease6Collection& collection) const {
+ const Lease6StorageHostnameIndex& idx = storage6_.get<HostnameIndexTag>();
+ std::pair<Lease6StorageHostnameIndex::const_iterator,
+ Lease6StorageHostnameIndex::const_iterator> l =
+ idx.equal_range(hostname);
+
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6(const std::string& hostname) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_HOSTNAME6)
+ .arg(hostname);
+
+ Lease6Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases6Internal(hostname, collection);
+ } else {
+ getLeases6Internal(hostname, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases6Internal(Lease6Collection& collection) const {
+ for (auto lease = storage6_.begin(); lease != storage6_.end(); ++lease) {
+ collection.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET6);
+
+ Lease6Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases6Internal(collection);
+ } else {
+ getLeases6Internal(collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases6Internal(const DUID& duid,
+ Lease6Collection& collection) const {
+ const Lease6StorageDuidIndex& idx = storage6_.get<DuidIndexTag>();
+ std::pair<Lease6StorageDuidIndex::const_iterator,
+ Lease6StorageDuidIndex::const_iterator> l =
+ idx.equal_range(duid.getDuid());
+
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ collection.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6(const DUID& duid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET6_DUID)
+ .arg(duid.toText());
+
+ Lease6Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases6Internal(duid, collection);
+ } else {
+ getLeases6Internal(duid, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getLeases6Internal(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ Lease6Collection& collection) const {
+ const Lease6StorageAddressIndex& idx = storage6_.get<AddressIndexTag>();
+ Lease6StorageAddressIndex::const_iterator lb = idx.lower_bound(lower_bound_address);
+
+ // Exclude the lower bound address specified by the caller.
+ if ((lb != idx.end()) && ((*lb)->addr_ == lower_bound_address)) {
+ ++lb;
+ }
+
+ // Return all other leases being within the page size.
+ for (auto lease = lb;
+ (lease != idx.end()) && (std::distance(lb, lease) < page_size.page_size_);
+ ++lease) {
+ collection.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const {
+ // Expecting IPv6 address.
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_PAGE6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText());
+
+ Lease6Collection collection;
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getLeases6Internal(lower_bound_address, page_size, collection);
+ } else {
+ getLeases6Internal(lower_bound_address, page_size, collection);
+ }
+
+ return (collection);
+}
+
+void
+Memfile_LeaseMgr::getExpiredLeases4Internal(Lease4Collection& expired_leases,
+ const size_t max_leases) const {
+ // Obtain the index which segragates leases by state and time.
+ const Lease4StorageExpirationIndex& index = storage4_.get<ExpirationIndexTag>();
+
+ // Retrieve leases which are not reclaimed and which haven't expired. The
+ // 'less-than' operator will be used for both components of the index. So,
+ // for the 'state' 'false' is less than 'true'. Also the leases with
+ // expiration time lower than current time will be returned.
+ Lease4StorageExpirationIndex::const_iterator ub =
+ index.upper_bound(boost::make_tuple(false, time(0)));
+
+ // Copy only the number of leases indicated by the max_leases parameter.
+ for (Lease4StorageExpirationIndex::const_iterator lease = index.begin();
+ (lease != ub) && ((max_leases == 0) || (std::distance(index.begin(), lease) <
+ max_leases));
+ ++lease) {
+ expired_leases.push_back(Lease4Ptr(new Lease4(**lease)));
+ }
+}
+
+void
+Memfile_LeaseMgr::getExpiredLeases4(Lease4Collection& expired_leases,
+ const size_t max_leases) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_EXPIRED4)
+ .arg(max_leases);
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getExpiredLeases4Internal(expired_leases, max_leases);
+ } else {
+ getExpiredLeases4Internal(expired_leases, max_leases);
+ }
+}
+
+void
+Memfile_LeaseMgr::getExpiredLeases6Internal(Lease6Collection& expired_leases,
+ const size_t max_leases) const {
+ // Obtain the index which segragates leases by state and time.
+ const Lease6StorageExpirationIndex& index = storage6_.get<ExpirationIndexTag>();
+
+ // Retrieve leases which are not reclaimed and which haven't expired. The
+ // 'less-than' operator will be used for both components of the index. So,
+ // for the 'state' 'false' is less than 'true'. Also the leases with
+ // expiration time lower than current time will be returned.
+ Lease6StorageExpirationIndex::const_iterator ub =
+ index.upper_bound(boost::make_tuple(false, time(0)));
+
+ // Copy only the number of leases indicated by the max_leases parameter.
+ for (Lease6StorageExpirationIndex::const_iterator lease = index.begin();
+ (lease != ub) && ((max_leases == 0) || (std::distance(index.begin(), lease) <
+ max_leases));
+ ++lease) {
+ expired_leases.push_back(Lease6Ptr(new Lease6(**lease)));
+ }
+}
+
+void
+Memfile_LeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases,
+ const size_t max_leases) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_GET_EXPIRED6)
+ .arg(max_leases);
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ getExpiredLeases6Internal(expired_leases, max_leases);
+ } else {
+ getExpiredLeases6Internal(expired_leases, max_leases);
+ }
+}
+
+void
+Memfile_LeaseMgr::updateLease4Internal(const Lease4Ptr& lease) {
+ // Obtain 'by address' index.
+ Lease4StorageAddressIndex& index = storage4_.get<AddressIndexTag>();
+
+ bool persist = persistLeases(V4);
+
+ // Lease must exist if it is to be updated.
+ Lease4StorageAddressIndex::const_iterator lease_it = index.find(lease->addr_);
+ if (lease_it == index.end()) {
+ isc_throw(NoSuchLease, "failed to update the lease with address "
+ << lease->addr_ << " - no such lease");
+ } else if ((!persist) && (((*lease_it)->cltt_ != lease->current_cltt_) ||
+ ((*lease_it)->valid_lft_ != lease->current_valid_lft_))) {
+ // For test purpose only: check that the lease has not changed in
+ // the database.
+ isc_throw(NoSuchLease, "failed to update the lease with address "
+ << lease->addr_ << " - lease has changed in database");
+ }
+
+ // Try to write a lease to disk first. If this fails, the lease will
+ // not be inserted to the memory and the disk and in-memory data will
+ // remain consistent.
+ if (persist) {
+ lease_file4_->append(*lease);
+ }
+
+ // Update lease current expiration time.
+ lease->updateCurrentExpirationTime();
+
+ // Save a copy of the old lease as lease_it will point to the new
+ // one after the replacement.
+ Lease4Ptr old_lease = *lease_it;
+
+ // Use replace() to re-index leases.
+ index.replace(lease_it, Lease4Ptr(new Lease4(*lease)));
+
+ // Adjust class lease counters.
+ class_lease_counter_.updateLease(lease, old_lease);
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackUpdateLease(lease);
+ }
+}
+
+void
+Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ updateLease4Internal(lease);
+ } else {
+ updateLease4Internal(lease);
+ }
+}
+
+void
+Memfile_LeaseMgr::updateLease6Internal(const Lease6Ptr& lease) {
+ // Obtain 'by address' index.
+ Lease6StorageAddressIndex& index = storage6_.get<AddressIndexTag>();
+
+ bool persist = persistLeases(V6);
+
+ // Get the recorded action and reset it.
+ Lease6::ExtendedInfoAction recorded_action = lease->extended_info_action_;
+ lease->extended_info_action_ = Lease6::ACTION_IGNORE;
+
+ // Lease must exist if it is to be updated.
+ Lease6StorageAddressIndex::const_iterator lease_it = index.find(lease->addr_);
+ if (lease_it == index.end()) {
+ isc_throw(NoSuchLease, "failed to update the lease with address "
+ << lease->addr_ << " - no such lease");
+ } else if ((!persist) && (((*lease_it)->cltt_ != lease->current_cltt_) ||
+ ((*lease_it)->valid_lft_ != lease->current_valid_lft_))) {
+ // For test purpose only: check that the lease has not changed in
+ // the database.
+ isc_throw(NoSuchLease, "failed to update the lease with address "
+ << lease->addr_ << " - lease has changed in database");
+ }
+
+ // Try to write a lease to disk first. If this fails, the lease will
+ // not be inserted to the memory and the disk and in-memory data will
+ // remain consistent.
+ if (persist) {
+ lease_file6_->append(*lease);
+ }
+
+ // Update lease current expiration time.
+ lease->updateCurrentExpirationTime();
+
+ // Save a copy of the old lease as lease_it will point to the new
+ // one after the replacement.
+ Lease6Ptr old_lease = *lease_it;
+
+ // Use replace() to re-index leases.
+ index.replace(lease_it, Lease6Ptr(new Lease6(*lease)));
+
+ // Adjust class lease counters.
+ class_lease_counter_.updateLease(lease, old_lease);
+
+ // Update extended info tables.
+ if (getExtendedInfoTablesEnabled()) {
+ switch (recorded_action) {
+ case Lease6::ACTION_IGNORE:
+ break;
+
+ case Lease6::ACTION_DELETE:
+ deleteExtendedInfo6(lease->addr_);
+ break;
+
+ case Lease6::ACTION_UPDATE:
+ deleteExtendedInfo6(lease->addr_);
+ static_cast<void>(addExtendedInfo6(lease));
+ break;
+ }
+ }
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackUpdateLease(lease);
+ }
+}
+
+void
+Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ updateLease6Internal(lease);
+ } else {
+ updateLease6Internal(lease);
+ }
+}
+
+bool
+Memfile_LeaseMgr::deleteLeaseInternal(const Lease4Ptr& lease) {
+ const isc::asiolink::IOAddress& addr = lease->addr_;
+ Lease4Storage::iterator l = storage4_.find(addr);
+ if (l == storage4_.end()) {
+ // No such lease
+ return (false);
+ } else {
+ if (persistLeases(V4)) {
+ // Copy the lease. The valid lifetime needs to be modified and
+ // we don't modify the original lease.
+ Lease4 lease_copy = **l;
+ // Setting valid lifetime to 0 means that lease is being
+ // removed.
+ lease_copy.valid_lft_ = 0;
+ lease_file4_->append(lease_copy);
+ } else {
+ // For test purpose only: check that the lease has not changed in
+ // the database.
+ if (((*l)->cltt_ != lease->current_cltt_) ||
+ ((*l)->valid_lft_ != lease->current_valid_lft_)) {
+ return false;
+ }
+ }
+
+ storage4_.erase(l);
+
+ // Decrement class lease counters.
+ class_lease_counter_.removeLease(lease);
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackDeleteLease(lease);
+ }
+
+ return (true);
+ }
+}
+
+bool
+Memfile_LeaseMgr::deleteLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_ADDR).arg(lease->addr_.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (deleteLeaseInternal(lease));
+ } else {
+ return (deleteLeaseInternal(lease));
+ }
+}
+
+bool
+Memfile_LeaseMgr::deleteLeaseInternal(const Lease6Ptr& lease) {
+ lease->extended_info_action_ = Lease6::ACTION_IGNORE;
+
+ const isc::asiolink::IOAddress& addr = lease->addr_;
+ Lease6Storage::iterator l = storage6_.find(addr);
+ if (l == storage6_.end()) {
+ // No such lease
+ return (false);
+ } else {
+ if (persistLeases(V6)) {
+ // Copy the lease. The lifetimes need to be modified and we
+ // don't modify the original lease.
+ Lease6 lease_copy = **l;
+ // Setting lifetimes to 0 means that lease is being removed.
+ lease_copy.valid_lft_ = 0;
+ lease_copy.preferred_lft_ = 0;
+ lease_file6_->append(lease_copy);
+ } else {
+ // For test purpose only: check that the lease has not changed in
+ // the database.
+ if (((*l)->cltt_ != lease->current_cltt_) ||
+ ((*l)->valid_lft_ != lease->current_valid_lft_)) {
+ return false;
+ }
+ }
+
+ storage6_.erase(l);
+
+ // Decrement class lease counters.
+ class_lease_counter_.removeLease(lease);
+
+ // Delete references from extended info tables.
+ if (getExtendedInfoTablesEnabled()) {
+ deleteExtendedInfo6(lease->addr_);
+ }
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackDeleteLease(lease);
+ }
+
+ return (true);
+ }
+}
+
+bool
+Memfile_LeaseMgr::deleteLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_ADDR).arg(lease->addr_.toText());
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (deleteLeaseInternal(lease));
+ } else {
+ return (deleteLeaseInternal(lease));
+ }
+}
+
+uint64_t
+Memfile_LeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED4)
+ .arg(secs);
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (deleteExpiredReclaimedLeases<
+ Lease4StorageExpirationIndex, Lease4
+ >(secs, V4, storage4_, lease_file4_));
+ } else {
+ return (deleteExpiredReclaimedLeases<
+ Lease4StorageExpirationIndex, Lease4
+ >(secs, V4, storage4_, lease_file4_));
+ }
+}
+
+uint64_t
+Memfile_LeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED6)
+ .arg(secs);
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (deleteExpiredReclaimedLeases<
+ Lease6StorageExpirationIndex, Lease6
+ >(secs, V6, storage6_, lease_file6_));
+ } else {
+ return (deleteExpiredReclaimedLeases<
+ Lease6StorageExpirationIndex, Lease6
+ >(secs, V6, storage6_, lease_file6_));
+ }
+}
+
+template<typename IndexType, typename LeaseType, typename StorageType,
+ typename LeaseFileType>
+uint64_t
+Memfile_LeaseMgr::deleteExpiredReclaimedLeases(const uint32_t secs,
+ const Universe& universe,
+ StorageType& storage,
+ LeaseFileType& lease_file) {
+ // Obtain the index which segragates leases by state and time.
+ IndexType& index = storage.template get<ExpirationIndexTag>();
+
+ // This returns the first element which is greater than the specified
+ // tuple (true, time(0) - secs). However, the range between the
+ // beginning of the index and returned element also includes all the
+ // elements for which the first value is false (lease state is NOT
+ // reclaimed), because false < true. All elements between the
+ // beginning of the index and the element returned, for which the
+ // first value is true, represent the reclaimed leases which should
+ // be deleted, because their expiration time + secs has occurred earlier
+ // than current time.
+ typename IndexType::const_iterator upper_limit =
+ index.upper_bound(boost::make_tuple(true, time(0) - secs));
+
+ // Now, we have to exclude all elements of the index which represent
+ // leases in the state other than reclaimed - with the first value
+ // in the index equal to false. Note that elements in the index are
+ // ordered from the lower to the higher ones. So, all elements with
+ // the first value of false are placed before the elements with the
+ // value of true. Hence, we have to find the first element which
+ // contains value of true. The time value is the lowest possible.
+ typename IndexType::const_iterator lower_limit =
+ index.upper_bound(boost::make_tuple(true, std::numeric_limits<int64_t>::min()));
+
+ // If there are some elements in this range, delete them.
+ uint64_t num_leases = static_cast<uint64_t>(std::distance(lower_limit, upper_limit));
+ if (num_leases > 0) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_EXPIRED_RECLAIMED_START)
+ .arg(num_leases);
+
+ // If lease persistence is enabled, we also have to mark leases
+ // as deleted in the lease file. We do this by setting the
+ // lifetime to 0.
+ if (persistLeases(universe)) {
+ for (typename IndexType::const_iterator lease = lower_limit;
+ lease != upper_limit; ++lease) {
+ // Copy lease to not affect the lease in the container.
+ LeaseType lease_copy(**lease);
+ // Set the valid lifetime to 0 to indicate the removal
+ // of the lease.
+ lease_copy.valid_lft_ = 0;
+ lease_file->append(lease_copy);
+ }
+ }
+
+ // Delete references from extended info tables.
+ if (getExtendedInfoTablesEnabled()) {
+ // Swap if and for when v4 will be implemented.
+ if (universe == V6) {
+ for (typename IndexType::const_iterator lease = lower_limit;
+ lease != upper_limit; ++lease) {
+ deleteExtendedInfo6((*lease)->addr_);
+ }
+ }
+ }
+
+ // Erase leases from memory.
+ index.erase(lower_limit, upper_limit);
+
+ }
+ // Return number of leases deleted.
+ return (num_leases);
+}
+
+std::string
+Memfile_LeaseMgr::getDescription() const {
+ return (std::string("In memory database with leases stored in a CSV file."));
+}
+
+std::pair<uint32_t, uint32_t>
+Memfile_LeaseMgr::getVersion() const {
+ std::string const& universe(conn_.getParameter("universe"));
+ if (universe == "4") {
+ return std::make_pair(MAJOR_VERSION_V4, MINOR_VERSION_V4);
+ } else if (universe == "6") {
+ return std::make_pair(MAJOR_VERSION_V6, MINOR_VERSION_V6);
+ }
+ isc_throw(BadValue, "cannot determine version for universe " << universe);
+}
+
+void
+Memfile_LeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
+}
+
+void
+Memfile_LeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ROLLBACK);
+}
+
+std::string
+Memfile_LeaseMgr::appendSuffix(const std::string& file_name,
+ const LFCFileType& file_type) {
+ std::string name(file_name);
+ switch (file_type) {
+ case FILE_INPUT:
+ name += ".1";
+ break;
+ case FILE_PREVIOUS:
+ name += ".2";
+ break;
+ case FILE_OUTPUT:
+ name += ".output";
+ break;
+ case FILE_FINISH:
+ name += ".completed";
+ break;
+ case FILE_PID:
+ name += ".pid";
+ break;
+ default:
+ // Do not append any suffix for the FILE_CURRENT.
+ ;
+ }
+
+ return (name);
+}
+
+std::string
+Memfile_LeaseMgr::getDefaultLeaseFilePath(Universe u) const {
+ std::ostringstream s;
+ s << CfgMgr::instance().getDataDir() << "/kea-leases";
+ s << (u == V4 ? "4" : "6");
+ s << ".csv";
+ return (s.str());
+}
+
+std::string
+Memfile_LeaseMgr::getLeaseFilePath(Universe u) const {
+ if (u == V4) {
+ return (lease_file4_ ? lease_file4_->getFilename() : "");
+ }
+
+ return (lease_file6_ ? lease_file6_->getFilename() : "");
+}
+
+bool
+Memfile_LeaseMgr::persistLeases(Universe u) const {
+ // Currently, if the lease file IO is not created, it means that writes to
+ // disk have been explicitly disabled by the administrator. At some point,
+ // there may be a dedicated ON/OFF flag implemented to control this.
+ if (u == V4 && lease_file4_) {
+ return (true);
+ }
+
+ return (u == V6 && lease_file6_);
+}
+
+std::string
+Memfile_LeaseMgr::initLeaseFilePath(Universe u) {
+ std::string persist_val;
+ try {
+ persist_val = conn_.getParameter("persist");
+ } catch (const Exception&) {
+ // If parameter persist hasn't been specified, we use a default value
+ // 'yes'.
+ persist_val = "true";
+ }
+ // If persist_val is 'false' we will not store leases to disk, so let's
+ // return empty file name.
+ if (persist_val == "false") {
+ return ("");
+
+ } else if (persist_val != "true") {
+ isc_throw(isc::BadValue, "invalid value 'persist="
+ << persist_val << "'");
+ }
+
+ std::string lease_file;
+ try {
+ lease_file = conn_.getParameter("name");
+ } catch (const Exception&) {
+ lease_file = getDefaultLeaseFilePath(u);
+ }
+ return (lease_file);
+}
+
+template<typename LeaseObjectType, typename LeaseFileType, typename StorageType>
+bool
+Memfile_LeaseMgr::loadLeasesFromFiles(const std::string& filename,
+ boost::shared_ptr<LeaseFileType>& lease_file,
+ StorageType& storage) {
+ // Check if the instance of the LFC is running right now. If it is
+ // running, we refuse to load leases as the LFC may be writing to the
+ // lease files right now. When the user retries server configuration
+ // it should go through.
+ /// @todo Consider applying a timeout for an LFC and retry when this
+ /// timeout elapses.
+ PIDFile pid_file(appendSuffix(filename, FILE_PID));
+ if (pid_file.check()) {
+ isc_throw(DbOpenError, "unable to load leases from files while the "
+ "lease file cleanup is in progress");
+ }
+
+ storage.clear();
+
+ std::string max_row_errors_str = "0";
+ try {
+ max_row_errors_str = conn_.getParameter("max-row-errors");
+ } catch (const std::exception&) {
+ // Ignore and default to 0.
+ }
+
+ int64_t max_row_errors64;
+ try {
+ max_row_errors64 = boost::lexical_cast<int64_t>(max_row_errors_str);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(isc::BadValue, "invalid value of the max-row-errors "
+ << max_row_errors_str << " specified");
+ }
+ if ((max_row_errors64 < 0) ||
+ (max_row_errors64 > std::numeric_limits<uint32_t>::max())) {
+ isc_throw(isc::BadValue, "invalid value of the max-row-errors "
+ << max_row_errors_str << " specified");
+ }
+ uint32_t max_row_errors = static_cast<uint32_t>(max_row_errors64);
+
+ // Load the leasefile.completed, if exists.
+ bool conversion_needed = false;
+ lease_file.reset(new LeaseFileType(std::string(filename + ".completed")));
+ if (lease_file->exists()) {
+ LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+ max_row_errors);
+ conversion_needed = conversion_needed || lease_file->needsConversion();
+ } else {
+ // If the leasefile.completed doesn't exist, let's load the leases
+ // from leasefile.2 and leasefile.1, if they exist.
+ lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_PREVIOUS)));
+ if (lease_file->exists()) {
+ LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+ max_row_errors);
+ conversion_needed = conversion_needed || lease_file->needsConversion();
+ }
+
+ lease_file.reset(new LeaseFileType(appendSuffix(filename, FILE_INPUT)));
+ if (lease_file->exists()) {
+ LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+ max_row_errors);
+ conversion_needed = conversion_needed || lease_file->needsConversion();
+ }
+ }
+
+ // Always load leases from the primary lease file. If the lease file
+ // doesn't exist it will be created by the LeaseFileLoader. Note
+ // that the false value passed as the last parameter to load
+ // function causes the function to leave the file open after
+ // it is parsed. This file will be used by the backend to record
+ // future lease updates.
+ lease_file.reset(new LeaseFileType(filename));
+ LeaseFileLoader::load<LeaseObjectType>(*lease_file, storage,
+ max_row_errors, false);
+ conversion_needed = conversion_needed || lease_file->needsConversion();
+
+ return (conversion_needed);
+}
+
+
+bool
+Memfile_LeaseMgr::isLFCRunning() const {
+ return (lfc_setup_->isRunning());
+}
+
+int
+Memfile_LeaseMgr::getLFCExitStatus() const {
+ return (lfc_setup_->getExitStatus());
+}
+
+void
+Memfile_LeaseMgr::lfcCallback() {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_START);
+
+ // Check if we're in the v4 or v6 space and use the appropriate file.
+ if (lease_file4_) {
+ MultiThreadingCriticalSection cs;
+ lfcExecute(lease_file4_);
+ } else if (lease_file6_) {
+ MultiThreadingCriticalSection cs;
+ lfcExecute(lease_file6_);
+ }
+}
+
+void
+Memfile_LeaseMgr::lfcSetup(bool conversion_needed) {
+ std::string lfc_interval_str = "3600";
+ try {
+ lfc_interval_str = conn_.getParameter("lfc-interval");
+ } catch (const std::exception&) {
+ // Ignore and default to 3600.
+ }
+
+ uint32_t lfc_interval = 0;
+ try {
+ lfc_interval = boost::lexical_cast<uint32_t>(lfc_interval_str);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(isc::BadValue, "invalid value of the lfc-interval "
+ << lfc_interval_str << " specified");
+ }
+
+ if (lfc_interval > 0 || conversion_needed) {
+ lfc_setup_.reset(new LFCSetup(std::bind(&Memfile_LeaseMgr::lfcCallback, this)));
+ lfc_setup_->setup(lfc_interval, lease_file4_, lease_file6_, conversion_needed);
+ }
+}
+
+template<typename LeaseFileType>
+void
+Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file) {
+ bool do_lfc = true;
+
+ // Check the status of the LFC instance.
+ // If the finish file exists or the copy of the lease file exists it
+ // is an indication that another LFC instance may be in progress or
+ // may be stalled. In that case we don't want to rotate the current
+ // lease file to avoid overriding the contents of the existing file.
+ CSVFile lease_file_finish(appendSuffix(lease_file->getFilename(), FILE_FINISH));
+ CSVFile lease_file_copy(appendSuffix(lease_file->getFilename(), FILE_INPUT));
+ if (!lease_file_finish.exists() && !lease_file_copy.exists()) {
+ // Close the current file so as we can move it to the copy file.
+ lease_file->close();
+ // Move the current file to the copy file. Remember the result
+ // because we don't want to run LFC if the rename failed.
+ do_lfc = (rename(lease_file->getFilename().c_str(),
+ lease_file_copy.getFilename().c_str()) == 0);
+
+ if (!do_lfc) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_LEASE_FILE_RENAME_FAIL)
+ .arg(lease_file->getFilename())
+ .arg(lease_file_copy.getFilename())
+ .arg(strerror(errno));
+ }
+
+ // Regardless if we successfully moved the current file or not,
+ // we need to re-open the current file for the server to write
+ // new lease updates. If the file has been successfully moved,
+ // this will result in creation of the new file. Otherwise,
+ // an existing file will be opened.
+ try {
+ lease_file->open(true);
+
+ } catch (const CSVFileError& ex) {
+ // If we're unable to open the lease file this is a serious
+ // error because the server will not be able to persist
+ // leases.
+ /// @todo We need to better address this error. It should
+ /// trigger an alarm (once we have a monitoring system in
+ /// place) so as an administrator can correct it. In
+ /// practice it should be very rare that this happens and
+ /// is most likely related to a human error, e.g. changing
+ /// file permissions.
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MEMFILE_LFC_LEASE_FILE_REOPEN_FAIL)
+ .arg(lease_file->getFilename())
+ .arg(ex.what());
+ // Reset the pointer to the file so as the backend doesn't
+ // try to write leases to disk.
+ lease_file.reset();
+ do_lfc = false;
+ }
+ }
+ // Once the files have been rotated, or untouched if another LFC had
+ // not finished, a new process is started.
+ if (do_lfc) {
+ lfc_setup_->execute();
+ }
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startLeaseStatsQuery4() {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery4(storage4_));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startPoolLeaseStatsQuery4() {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery4(storage4_, LeaseStatsQuery::ALL_SUBNET_POOLS));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery4(storage4_, subnet_id));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery4(storage4_, first_subnet_id,
+ last_subnet_id));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startLeaseStatsQuery6() {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery6(storage6_));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startPoolLeaseStatsQuery6() {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery6(storage6_, LeaseStatsQuery::ALL_SUBNET_POOLS));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery6(storage6_, subnet_id));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) {
+ LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery6(storage6_, first_subnet_id,
+ last_subnet_id));
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ query->start();
+ } else {
+ query->start();
+ }
+
+ return(query);
+}
+
+size_t
+Memfile_LeaseMgr::wipeLeases4(const SubnetID& subnet_id) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES4)
+ .arg(subnet_id);
+
+ // Get the index by DUID, IAID, lease type.
+ const Lease4StorageSubnetIdIndex& idx = storage4_.get<SubnetIdIndexTag>();
+
+ // Try to get the lease using the DUID, IAID and lease type.
+ std::pair<Lease4StorageSubnetIdIndex::const_iterator,
+ Lease4StorageSubnetIdIndex::const_iterator> l =
+ idx.equal_range(subnet_id);
+
+ // Let's collect all leases.
+ Lease4Collection leases;
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ leases.push_back(*lease);
+ }
+
+ size_t num = leases.size();
+ for (auto l = leases.begin(); l != leases.end(); ++l) {
+ deleteLease(*l);
+ }
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES4_FINISHED)
+ .arg(subnet_id).arg(num);
+
+ return (num);
+}
+
+size_t
+Memfile_LeaseMgr::wipeLeases6(const SubnetID& subnet_id) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES6)
+ .arg(subnet_id);
+
+ // Get the index by DUID, IAID, lease type.
+ const Lease6StorageSubnetIdIndex& idx = storage6_.get<SubnetIdIndexTag>();
+
+ // Try to get the lease using the DUID, IAID and lease type.
+ std::pair<Lease6StorageSubnetIdIndex::const_iterator,
+ Lease6StorageSubnetIdIndex::const_iterator> l =
+ idx.equal_range(subnet_id);
+
+ // Let's collect all leases.
+ Lease6Collection leases;
+ for (auto lease = l.first; lease != l.second; ++lease) {
+ leases.push_back(*lease);
+ }
+
+ size_t num = leases.size();
+ for (auto l = leases.begin(); l != leases.end(); ++l) {
+ deleteLease(*l);
+ }
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_WIPE_LEASES6_FINISHED)
+ .arg(subnet_id).arg(num);
+
+ return (num);
+}
+
+void
+Memfile_LeaseMgr::recountClassLeases4() {
+ class_lease_counter_.clear();
+ for (auto lease = storage4_.begin(); lease != storage4_.end(); ++lease) {
+ // Bump the appropriate accumulator
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ class_lease_counter_.addLease(*lease);
+ }
+ }
+}
+
+void
+Memfile_LeaseMgr::recountClassLeases6() {
+ class_lease_counter_.clear();
+ for (auto lease = storage6_.begin(); lease != storage6_.end(); ++lease) {
+ // Bump the appropriate accumulator
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ class_lease_counter_.addLease(*lease);
+ }
+ }
+}
+
+size_t
+Memfile_LeaseMgr::getClassLeaseCount(const ClientClass& client_class,
+ const Lease::Type& ltype /* = Lease::TYPE_V4*/) const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return(class_lease_counter_.getClassCount(client_class, ltype));
+ } else {
+ return(class_lease_counter_.getClassCount(client_class, ltype));
+ }
+}
+
+void
+Memfile_LeaseMgr::clearClassLeaseCounts() {
+ return(class_lease_counter_.clear());
+}
+
+std::string
+Memfile_LeaseMgr::checkLimits4(isc::data::ConstElementPtr const& user_context) const {
+ if (!user_context) {
+ return ("");
+ }
+
+ ConstElementPtr limits = user_context->find("ISC/limits");
+ if (!limits) {
+ return ("");
+ }
+
+ // Iterate of the 'client-classes' list in 'limits'. For each class that specifies
+ // an "address-limit", check its value against the class's lease count.
+ ConstElementPtr classes = limits->get("client-classes");
+ if (classes) {
+ for (int i = 0; i < classes->size(); ++i) {
+ ConstElementPtr class_elem = classes->get(i);
+ // Get class name.
+ ConstElementPtr name_elem = class_elem->get("name");
+ if (!name_elem) {
+ isc_throw(BadValue, "checkLimits4 - client-class.name is missing: "
+ << prettyPrint(limits));
+ }
+
+ std::string name = name_elem->stringValue();
+
+ // Now look for an address-limit
+ size_t limit;
+ if (!getLeaseLimit(class_elem, Lease::TYPE_V4, limit)) {
+ // No limit, go to the next class.
+ continue;
+ }
+
+ // If the limit is > 0 look up the class lease count. Limit of 0 always
+ // denies the lease.
+ size_t lease_count = 0;
+ if (limit) {
+ lease_count = getClassLeaseCount(name);
+ }
+
+ // If we're over the limit, return the error, no need to evaluate any others.
+ if (lease_count >= limit) {
+ std::ostringstream ss;
+ ss << "address limit " << limit << " for client class \""
+ << name << "\", current lease count " << lease_count;
+ return (ss.str());
+ }
+ }
+ }
+
+ // If there were class limits we passed them, now look for a subnet limit.
+ ConstElementPtr subnet_elem = limits->get("subnet");
+ if (subnet_elem) {
+ // Get the subnet id.
+ ConstElementPtr id_elem = subnet_elem->get("id");
+ if (!id_elem) {
+ isc_throw(BadValue, "checkLimits4 - subnet.id is missing: "
+ << prettyPrint(limits));
+ }
+
+ SubnetID subnet_id = id_elem->intValue();
+
+ // Now look for an address-limit.
+ size_t limit;
+ if (getLeaseLimit(subnet_elem, Lease::TYPE_V4, limit)) {
+ // If the limit is > 0 look up the subnet lease count. Limit of 0 always
+ // denies the lease.
+ int64_t lease_count = 0;
+ if (limit) {
+ lease_count = getSubnetStat(subnet_id, "assigned-addresses");
+ }
+
+ // If we're over the limit, return the error.
+ if (lease_count >= limit) {
+ std::ostringstream ss;
+ ss << "address limit " << limit << " for subnet ID " << subnet_id
+ << ", current lease count " << lease_count;
+ return (ss.str());
+ }
+ }
+ }
+
+ // No limits exceeded!
+ return ("");
+}
+
+std::string
+Memfile_LeaseMgr::checkLimits6(isc::data::ConstElementPtr const& user_context) const {
+ if (!user_context) {
+ return ("");
+ }
+
+ ConstElementPtr limits = user_context->find("ISC/limits");
+ if (!limits) {
+ return ("");
+ }
+
+ // Iterate over the 'client-classes' list in 'limits'. For each class that specifies
+ // limit (either "address-limit" or "prefix-limit", check its value against the appropriate
+ // class lease count.
+ ConstElementPtr classes = limits->get("client-classes");
+ if (classes) {
+ for (int i = 0; i < classes->size(); ++i) {
+ ConstElementPtr class_elem = classes->get(i);
+ // Get class name.
+ ConstElementPtr name_elem = class_elem->get("name");
+ if (!name_elem) {
+ isc_throw(BadValue, "checkLimits6 - client-class.name is missing: "
+ << prettyPrint(limits));
+ }
+
+ std::string name = name_elem->stringValue();
+
+ // Now look for either address-limit or a prefix=limit.
+ size_t limit = 0;
+ Lease::Type ltype = Lease::TYPE_NA;
+ if (!getLeaseLimit(class_elem, ltype, limit)) {
+ ltype = Lease::TYPE_PD;
+ if (!getLeaseLimit(class_elem, ltype, limit)) {
+ // No limits for this class, skip to the next.
+ continue;
+ }
+ }
+
+ // If the limit is > 0 look up the class lease count. Limit of 0 always
+ // denies the lease.
+ size_t lease_count = 0;
+ if (limit) {
+ lease_count = getClassLeaseCount(name, ltype);
+ }
+
+ // If we're over the limit, return the error, no need to evaluate any others.
+ if (lease_count >= limit) {
+ std::ostringstream ss;
+ ss << (ltype == Lease::TYPE_NA ? "address" : "prefix")
+ << " limit " << limit << " for client class \""
+ << name << "\", current lease count " << lease_count;
+ return (ss.str());
+ }
+ }
+ }
+
+ // If there were class limits we passed them, now look for a subnet limit.
+ ConstElementPtr subnet_elem = limits->get("subnet");
+ if (subnet_elem) {
+ // Get the subnet id.
+ ConstElementPtr id_elem = subnet_elem->get("id");
+ if (!id_elem) {
+ isc_throw(BadValue, "checkLimits6 - subnet.id is missing: "
+ << prettyPrint(limits));
+ }
+
+ SubnetID subnet_id = id_elem->intValue();
+
+ // Now look for either address-limit or a prefix=limit.
+ size_t limit = 0;
+ Lease::Type ltype = Lease::TYPE_NA;
+ if (!getLeaseLimit(subnet_elem, ltype, limit)) {
+ ltype = Lease::TYPE_PD;
+ if (!getLeaseLimit(subnet_elem, ltype, limit)) {
+ // No limits for the subnet so none exceeded!
+ return ("");
+ }
+ }
+
+ // If the limit is > 0 look up the class lease count. Limit of 0 always
+ // denies the lease.
+ int64_t lease_count = 0;
+ if (limit) {
+ lease_count = getSubnetStat(subnet_id, (ltype == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds"));
+ }
+
+ // If we're over the limit, return the error.
+ if (lease_count >= limit) {
+ std::ostringstream ss;
+ ss << (ltype == Lease::TYPE_NA ? "address" : "prefix")
+ << " limit " << limit << " for subnet ID " << subnet_id
+ << ", current lease count " << lease_count;
+ return (ss.str());
+ }
+ }
+
+ // No limits exceeded!
+ return ("");
+}
+
+bool
+Memfile_LeaseMgr::isJsonSupported() const {
+ return true;
+}
+
+int64_t
+Memfile_LeaseMgr::getSubnetStat(const SubnetID& subnet_id, const std::string& stat_label) const {
+ /// @todo This could be simplified if StatsMgr provided a mechanism to
+ /// return the most recent sample as an InterSample.
+ std::string stat_name = StatsMgr::generateName("subnet", subnet_id, stat_label);
+ ConstElementPtr stat = StatsMgr::instance().get(stat_name);
+ ConstElementPtr samples = stat->get(stat_name);
+ if (samples && samples->size()) {
+ auto sample = samples->get(0);
+ if (sample->size()) {
+ auto count_elem = sample->get(0);
+ return (count_elem->intValue());
+ }
+ }
+
+ return (0);
+}
+
+bool
+Memfile_LeaseMgr::getLeaseLimit(ConstElementPtr parent, Lease::Type ltype, size_t& limit) const {
+ ConstElementPtr limit_elem = parent->get(ltype == Lease::TYPE_PD ?
+ "prefix-limit" : "address-limit");
+ if (limit_elem) {
+ limit = limit_elem->intValue();
+ return (true);
+ }
+
+ return (false);
+}
+
+namespace {
+
+std::string
+idToText(const OptionBuffer& id) {
+ std::stringstream tmp;
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = id.begin();
+ it != id.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0')
+ << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
+} // anonymous namespace
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4ByRelayId(const OptionBuffer& relay_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time /* = 0 */,
+ const time_t& qry_end_time /* = 0 */) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_RELAYID4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(idToText(relay_id))
+ .arg(qry_start_time)
+ .arg(qry_end_time);
+
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ // Catch 2038 bug with 32 bit time_t.
+ if ((qry_start_time < 0) || (qry_end_time < 0)) {
+ isc_throw(BadValue, "negative time value");
+ }
+
+ // Start time must be before end time.
+ if ((qry_start_time > 0) && (qry_end_time > 0) &&
+ (qry_start_time > qry_end_time)) {
+ isc_throw(BadValue, "start time must be before end time");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLeases4ByRelayIdInternal(relay_id,
+ lower_bound_address,
+ page_size,
+ qry_start_time,
+ qry_end_time));
+ } else {
+ return (getLeases4ByRelayIdInternal(relay_id,
+ lower_bound_address,
+ page_size,
+ qry_start_time,
+ qry_end_time));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4ByRelayIdInternal(const OptionBuffer& relay_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time,
+ const time_t& qry_end_time) {
+ Lease4Collection collection;
+ const Lease4StorageRelayIdIndex& idx = storage4_.get<RelayIdIndexTag>();
+ Lease4StorageRelayIdIndex::const_iterator lb =
+ idx.lower_bound(boost::make_tuple(relay_id, lower_bound_address));
+ // Return all convenient leases being within the page size.
+ IOAddress last_addr = lower_bound_address;
+ for (; lb != idx.end(); ++lb) {
+ if ((*lb)->addr_ == last_addr) {
+ // Already seen: skip it.
+ continue;
+ }
+ if ((*lb)->relay_id_ != relay_id) {
+ // Gone after the relay id index.
+ break;
+ }
+ last_addr = (*lb)->addr_;
+ if ((qry_start_time > 0) && ((*lb)->cltt_ < qry_start_time)) {
+ // Too old.
+ continue;
+ }
+ if ((qry_end_time > 0) && ((*lb)->cltt_ > qry_end_time)) {
+ // Too young.
+ continue;
+ }
+ collection.push_back(Lease4Ptr(new Lease4(**lb)));
+ if (collection.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+ return (collection);
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4ByRemoteId(const OptionBuffer& remote_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time /* = 0 */,
+ const time_t& qry_end_time /* = 0 */) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_REMOTEID4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(idToText(remote_id))
+ .arg(qry_start_time)
+ .arg(qry_end_time);
+
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ // Catch 2038 bug with 32 bit time_t.
+ if ((qry_start_time < 0) || (qry_end_time < 0)) {
+ isc_throw(BadValue, "negative time value");
+ }
+
+ // Start time must be before end time.
+ if ((qry_start_time > 0) && (qry_end_time > 0) &&
+ (qry_start_time > qry_end_time)) {
+ isc_throw(BadValue, "start time must be before end time");
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLeases4ByRemoteIdInternal(remote_id,
+ lower_bound_address,
+ page_size,
+ qry_start_time,
+ qry_end_time));
+ } else {
+ return (getLeases4ByRemoteIdInternal(remote_id,
+ lower_bound_address,
+ page_size,
+ qry_start_time,
+ qry_end_time));
+ }
+}
+
+Lease4Collection
+Memfile_LeaseMgr::getLeases4ByRemoteIdInternal(const OptionBuffer& remote_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time,
+ const time_t& qry_end_time) {
+ Lease4Collection collection;
+ std::map<IOAddress, Lease4Ptr> sorted;
+ const Lease4StorageRemoteIdIndex& idx = storage4_.get<RemoteIdIndexTag>();
+ Lease4StorageRemoteIdRange er = idx.equal_range(remote_id);
+ // Store all convenient leases being within the page size.
+ for (auto it = er.first; it != er.second; ++it) {
+ const IOAddress& addr = (*it)->addr_;
+ if (addr <= lower_bound_address) {
+ // Not greater than lower_bound_address.
+ continue;
+ }
+ if ((qry_start_time > 0) && ((*it)->cltt_ < qry_start_time)) {
+ // Too old.
+ continue;
+ }
+ if ((qry_end_time > 0) && ((*it)->cltt_ > qry_end_time)) {
+ // Too young.
+ continue;
+ }
+ sorted[addr] = *it;
+ }
+
+ // Return all leases being within the page size.
+ for (auto it : sorted) {
+ collection.push_back(Lease4Ptr(new Lease4(*it.second)));
+ if (collection.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+ return (collection);
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6ByRelayId(const DUID& relay_id,
+ const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_RELAYID6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(relay_id.toText())
+ .arg(link_addr.toText())
+ .arg(static_cast<unsigned>(link_len));
+
+ // Expecting IPv6 valid prefix and address.
+ if (!link_addr.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << link_addr);
+ }
+ if (link_len > 128) {
+ isc_throw(OutOfRange, "invalid IPv6 prefix length "
+ << static_cast<unsigned>(link_len));
+ }
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLeases6ByRelayIdInternal(relay_id,
+ link_addr,
+ link_len,
+ lower_bound_address,
+ page_size));
+ } else {
+ return (getLeases6ByRelayIdInternal(relay_id,
+ link_addr,
+ link_len,
+ lower_bound_address,
+ page_size));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6ByRelayIdInternal(const DUID& relay_id,
+ const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ const std::vector<uint8_t>& relay_id_data = relay_id.getDuid();
+ Lease6Collection collection;
+ const RelayIdIndex& idx = relay_id6_.get<RelayIdIndexTag>();
+ if (!link_len) {
+ RelayIdIndex::const_iterator lb =
+ idx.lower_bound(boost::make_tuple(relay_id_data,
+ lower_bound_address));
+
+ // Return all leases being within the page size.
+ IOAddress last_addr = lower_bound_address;
+ for (; lb != idx.end(); ++lb) {
+ if ((*lb)->lease_addr_ == last_addr) {
+ // Already seen: skip it.
+ continue;
+ }
+ if ((*lb)->id_ != relay_id_data) {
+ // Gone after the relay id index.
+ break;
+ }
+ last_addr = (*lb)->lease_addr_;
+ Lease6Ptr lease = getAnyLease6Internal(last_addr);
+ if (lease) {
+ collection.push_back(lease);
+ if (collection.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+ }
+ } else {
+ const IOAddress& first_addr = firstAddrInPrefix(link_addr, link_len);
+ const IOAddress& last_addr = lastAddrInPrefix(link_addr, link_len);
+ const IOAddress& start_addr =
+ (lower_bound_address < first_addr ? first_addr : lower_bound_address);
+ RelayIdIndex::const_iterator lb =
+ idx.lower_bound(boost::make_tuple(relay_id_data, start_addr));
+ RelayIdIndex::const_iterator ub =
+ idx.upper_bound(boost::make_tuple(relay_id_data, last_addr));
+
+ // Return all leases being within the page size.
+ IOAddress last_seen_addr = lower_bound_address;
+ for (auto it = lb; it != ub; ++it) {
+ if ((*it)->lease_addr_ == last_seen_addr) {
+ // Already seen: skip it.
+ continue;
+ }
+ last_seen_addr = (*it)->lease_addr_;
+ Lease6Ptr lease = getAnyLease6Internal(last_seen_addr);
+ if (lease) {
+ collection.push_back(lease);
+ if (collection.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+ }
+ }
+ return (collection);
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6ByRemoteId(const OptionBuffer& remote_id,
+ const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_REMOTEID6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(idToText(remote_id))
+ .arg(link_addr.toText())
+ .arg(static_cast<unsigned>(link_len));
+
+ // Expecting IPv6 valid prefix and address.
+ if (!link_addr.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << link_addr);
+ }
+ if (link_len > 128) {
+ isc_throw(OutOfRange, "invalid IPv6 prefix length "
+ << static_cast<unsigned>(link_len));
+ }
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLeases6ByRemoteIdInternal(remote_id,
+ link_addr,
+ link_len,
+ lower_bound_address,
+ page_size));
+ } else {
+ return (getLeases6ByRemoteIdInternal(remote_id,
+ link_addr,
+ link_len,
+ lower_bound_address,
+ page_size));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6ByRemoteIdInternal(const OptionBuffer& remote_id,
+ const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ Lease6Collection collection;
+ std::set<IOAddress> sorted;
+ const RemoteIdIndex& idx = remote_id6_.get<RemoteIdIndexTag>();
+ RemoteIdIndexRange er = idx.equal_range(remote_id);
+ if (!link_len) {
+ // Store all addresses greater than lower_bound_address.
+ for (auto it = er.first; it != er.second; ++it) {
+ const IOAddress& addr = (*it)->lease_addr_;
+ if (addr <= lower_bound_address) {
+ continue;
+ }
+ static_cast<void>(sorted.insert(addr));
+ }
+
+ // Return all leases being within the page size.
+ for (const IOAddress& addr : sorted) {
+ Lease6Ptr lease = getAnyLease6Internal(addr);
+ if (lease) {
+ collection.push_back(lease);
+ if (collection.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+ }
+ } else {
+ const IOAddress& first_addr = firstAddrInPrefix(link_addr, link_len);
+ const IOAddress& last_addr = lastAddrInPrefix(link_addr, link_len);
+
+ // Store all addresses greater than lower_bound_address in the link.
+ for (auto it = er.first; it != er.second; ++it) {
+ const IOAddress& addr = (*it)->lease_addr_;
+ if (addr <= lower_bound_address) {
+ continue;
+ }
+ if ((addr < first_addr) || (last_addr < addr)) {
+ continue;
+ }
+ static_cast<void>(sorted.insert(addr));
+ }
+
+ // Return all leases being within the page size.
+ for (const IOAddress& addr : sorted) {
+ Lease6Ptr lease = getAnyLease6Internal(addr);
+ if (lease) {
+ collection.push_back(lease);
+ if (collection.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+ }
+ }
+ return (collection);
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6ByLink(const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_LINKADDR6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(link_addr.toText())
+ .arg(static_cast<unsigned>(link_len));
+
+ // Expecting IPv6 valid prefix and address.
+ if (!link_addr.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << link_addr);
+ }
+ if ((link_len == 0) || (link_len > 128)) {
+ isc_throw(OutOfRange, "invalid IPv6 prefix length "
+ << static_cast<unsigned>(link_len));
+ }
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (getLeases6ByLinkInternal(link_addr,
+ link_len,
+ lower_bound_address,
+ page_size));
+ } else {
+ return (getLeases6ByLinkInternal(link_addr,
+ link_len,
+ lower_bound_address,
+ page_size));
+ }
+}
+
+Lease6Collection
+Memfile_LeaseMgr::getLeases6ByLinkInternal(const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ const IOAddress& first_addr = firstAddrInPrefix(link_addr, link_len);
+ const IOAddress& last_addr = lastAddrInPrefix(link_addr, link_len);
+ const IOAddress& start_addr =
+ (lower_bound_address < first_addr ? first_addr : lower_bound_address);
+ Lease6Collection collection;
+ const Lease6StorageAddressIndex& idx = storage6_.get<AddressIndexTag>();
+ Lease6StorageAddressIndex::const_iterator lb = idx.lower_bound(start_addr);
+ Lease6StorageAddressIndex::const_iterator eb = idx.upper_bound(last_addr);
+
+ // Return all leases being within the page size.
+ IOAddress last_seen_addr = lower_bound_address;
+ for (auto it = lb; it != eb; ++it) {
+ if ((*it)->addr_ == last_seen_addr) {
+ // Already seen: skip it.
+ continue;
+ }
+ last_seen_addr = (*it)->addr_;
+ Lease6Ptr lease = getAnyLease6Internal(last_seen_addr);
+ if (lease) {
+ collection.push_back(lease);
+ if (collection.size() >= page_size.page_size_) {
+ break;
+ }
+ }
+ }
+ return (collection);
+}
+
+size_t
+Memfile_LeaseMgr::extractExtendedInfo4(bool update, bool current) {
+ CfgConsistencyPtr cfg;
+ if (current) {
+ cfg = CfgMgr::instance().getCurrentCfg()->getConsistency();
+ } else {
+ cfg = CfgMgr::instance().getStagingCfg()->getConsistency();
+ }
+ if (!cfg) {
+ isc_throw(Unexpected, "the " << (current ? "current" : "staging")
+ << " consistency configuration is null");
+ }
+ auto check = cfg->getExtendedInfoSanityCheck();
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_MEMFILE_BEGIN_EXTRACT_EXTENDED_INFO4)
+ .arg(CfgConsistency::sanityCheckToText(check))
+ .arg(update ? " updating in file" : "");
+
+ size_t leases = 0;
+ size_t modified = 0;
+ size_t updated = 0;
+ size_t processed = 0;
+ auto& index = storage4_.get<AddressIndexTag>();
+ auto lease_it = index.begin();
+ auto next_it = index.end();
+
+ for (; lease_it != index.end(); lease_it = next_it) {
+ next_it = std::next(lease_it);
+ Lease4Ptr lease = *lease_it;
+ ++leases;
+ try {
+ if (upgradeLease4ExtendedInfo(lease, check)) {
+ ++modified;
+ if (update && persistLeases(V4)) {
+ lease_file4_->append(*lease);
+ ++updated;
+ }
+ }
+ // Work on a copy as the multi-index requires fields used
+ // as indexes to be read-only.
+ Lease4Ptr copy(new Lease4(*lease));
+ extractLease4ExtendedInfo(copy, false);
+ if (!copy->relay_id_.empty() || !copy->remote_id_.empty()) {
+ index.replace(lease_it, copy);
+ ++processed;
+ }
+ } catch (const std::exception& ex) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4_ERROR)
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ }
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_EXTRACT_EXTENDED_INFO4)
+ .arg(leases)
+ .arg(modified)
+ .arg(updated)
+ .arg(processed);
+
+ return (updated);
+}
+
+size_t
+Memfile_LeaseMgr::upgradeExtendedInfo4(const LeasePageSize& /* page_size */) {
+ return (0);
+}
+
+size_t
+Memfile_LeaseMgr::buildExtendedInfoTables6Internal(bool update, bool current) {
+ CfgConsistencyPtr cfg;
+ if (current) {
+ cfg = CfgMgr::instance().getCurrentCfg()->getConsistency();
+ } else {
+ cfg = CfgMgr::instance().getStagingCfg()->getConsistency();
+ }
+ if (!cfg) {
+ isc_throw(Unexpected, "the " << (current ? "current" : "staging")
+ << " consistency configuration is null");
+ }
+ auto check = cfg->getExtendedInfoSanityCheck();
+ bool enabled = getExtendedInfoTablesEnabled();
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_MEMFILE_BEGIN_BUILD_EXTENDED_INFO_TABLES6)
+ .arg(CfgConsistency::sanityCheckToText(check))
+ .arg(update ? " updating in file" : "")
+ .arg(enabled ? "enabled" : "disabled");
+
+ // Clear tables when enabled.
+ if (enabled) {
+ relay_id6_.clear();
+ remote_id6_.clear();
+ }
+
+ size_t leases = 0;
+ size_t modified = 0;
+ size_t updated = 0;
+ size_t processed = 0;
+
+ for (auto lease : storage6_) {
+ ++leases;
+ try {
+ if (upgradeLease6ExtendedInfo(lease, check)) {
+ ++modified;
+ if (update && persistLeases(V6)) {
+ lease_file6_->append(*lease);
+ ++updated;
+ }
+ }
+ if (enabled && addExtendedInfo6(lease)) {
+ ++processed;
+ }
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger,
+ DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6_ERROR)
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ }
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MEMFILE_BUILD_EXTENDED_INFO_TABLES6)
+ .arg(leases)
+ .arg(modified)
+ .arg(updated)
+ .arg(processed);
+
+ return (updated);
+}
+
+size_t
+Memfile_LeaseMgr::buildExtendedInfoTables6(bool update, bool current) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (buildExtendedInfoTables6Internal(update, current));
+ } else {
+ return (buildExtendedInfoTables6Internal(update, current));
+ }
+}
+
+void
+Memfile_LeaseMgr::deleteExtendedInfo6(const IOAddress& addr) {
+ LeaseAddressRelayIdIndex& relay_id_idx =
+ relay_id6_.get<LeaseAddressIndexTag>();
+ static_cast<void>(relay_id_idx.erase(addr));
+ LeaseAddressRemoteIdIndex& remote_id_idx =
+ remote_id6_.get<LeaseAddressIndexTag>();
+ static_cast<void>(remote_id_idx.erase(addr));
+}
+
+void
+Memfile_LeaseMgr::addRelayId6(const IOAddress& lease_addr,
+ const std::vector<uint8_t>& relay_id) {
+ Lease6ExtendedInfoPtr ex_info;
+ ex_info.reset(new Lease6ExtendedInfo(lease_addr, relay_id));
+ relay_id6_.insert(ex_info);
+}
+
+void
+Memfile_LeaseMgr::addRemoteId6(const IOAddress& lease_addr,
+ const std::vector<uint8_t>& remote_id) {
+ Lease6ExtendedInfoPtr ex_info;
+ ex_info.reset(new Lease6ExtendedInfo(lease_addr, remote_id));
+ remote_id6_.insert(ex_info);
+}
+
+void
+Memfile_LeaseMgr::writeLeases4(const std::string& filename) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ writeLeases4Internal(filename);
+ } else {
+ writeLeases4Internal(filename);
+ }
+}
+
+void
+Memfile_LeaseMgr::writeLeases4Internal(const std::string& filename) {
+ bool overwrite = (lease_file4_ && lease_file4_->getFilename() == filename);
+ try {
+ if (overwrite) {
+ lease_file4_->close();
+ }
+ std::ostringstream old;
+ old << filename << ".bak" << getpid();
+ ::rename(filename.c_str(), old.str().c_str());
+ CSVLeaseFile4 backup(filename);
+ backup.open();
+ for (const auto& lease : storage4_) {
+ backup.append(*lease);
+ }
+ backup.close();
+ if (overwrite) {
+ lease_file4_->open(true);
+ }
+ } catch (const std::exception&) {
+ if (overwrite) {
+ lease_file4_->open(true);
+ }
+ throw;
+ }
+}
+
+void
+Memfile_LeaseMgr::writeLeases6(const std::string& filename) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ writeLeases6Internal(filename);
+ } else {
+ writeLeases6Internal(filename);
+ }
+}
+
+void
+Memfile_LeaseMgr::writeLeases6Internal(const std::string& filename) {
+ bool overwrite = (lease_file6_ && lease_file6_->getFilename() == filename);
+ try {
+ if (overwrite) {
+ lease_file6_->close();
+ }
+ std::ostringstream old;
+ old << filename << ".bak" << getpid();
+ ::rename(filename.c_str(), old.str().c_str());
+ CSVLeaseFile6 backup(filename);
+ backup.open();
+ for (const auto& lease : storage6_) {
+ backup.append(*lease);
+ }
+ backup.close();
+ if (overwrite) {
+ lease_file6_->open(true);
+ }
+ } catch (const std::exception&) {
+ if (overwrite) {
+ lease_file6_->open(true);
+ }
+ throw;
+ }
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
new file mode 100644
index 0000000..b83264c
--- /dev/null
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -0,0 +1,1582 @@
+// Copyright (C) 2012-2023 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 MEMFILE_LEASE_MGR_H
+#define MEMFILE_LEASE_MGR_H
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/process_spawn.h>
+#include <database/database_connection.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/memfile_lease_limits.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <mutex>
+
+namespace isc {
+namespace dhcp {
+
+class LFCSetup;
+
+/// @brief Concrete implementation of a lease database backend using flat file.
+///
+/// This class implements a lease database backend using CSV files to store
+/// DHCPv4 and DHCPv6 leases on disk. The format of the files is determined
+/// by the @c CSVLeaseFile4 and @c CSVLeaseFile6 classes.
+///
+/// In order to obtain good performance, the backend stores leases
+/// incrementally, i.e. updates to leases are appended at the end of the lease
+/// file. To record the deletion of a lease, the lease record is appended to
+/// the lease file with the valid lifetime set to 0. However, this may result
+/// in a significant growth of the lease file size over time, because the lease
+/// file will contain many entries for each lease. In order to mitigate this
+/// problem, the backend implements the Lease File Cleanup mechanism which is
+/// described on the Kea wiki:
+/// https://gitlab.isc.org/isc-projects/kea/wikis/designs/Lease-File-Cleanup-design.
+///
+/// The backend installs an @c asiolink::IntervalTimer to periodically execute
+/// the @c Memfile_LeaseMgr::lfcCallback. This callback function controls
+/// the startup of the background process which removes redundant information
+/// from the lease file(s).
+///
+/// When the backend is starting up, it reads leases from the lease file (one
+/// by one) and adds them to the in-memory container as follows:
+/// - if the lease record being parsed identifies a lease which is not present
+/// in the container, and the lease has valid lifetime greater than 0,
+/// the lease is added to the container,
+/// - if the lease record being parsed identifies a lease which is present in
+/// the container, and the valid lifetime of the lease record being parsed is
+/// greater than 0, the lease in the container is updated
+/// - if the lease record being parsed has valid lifetime equal to 0, and the
+/// corresponding lease exists in the container, the lease is removed from
+/// the container.
+///
+/// After the container holding leases is initialized, each subsequent update,
+/// removal or addition of the lease is appended to the lease file
+/// synchronously.
+///
+/// Originally, the Memfile backend didn't write leases to disk. This was
+/// particularly useful for testing server performance in non-disk bound
+/// conditions. In order to preserve this capability, the new parameter
+/// "persist=true|false" has been introduced in the database access string.
+/// For example, database access string: "type=memfile persist=true"
+/// enables writes of leases to a disk.
+///
+/// The lease file locations can be specified with the "name=[path]"
+/// parameter in the database access string. The [path] is the
+/// absolute path to the file (including file name). If this parameter
+/// is not specified, the default location in the installation
+/// directory is used: <install-dir>/var/lib/kea/kea-leases4.csv and
+/// <install-dir>/var/lib/kea/kea-leases6.csv.
+class Memfile_LeaseMgr : public TrackingLeaseMgr {
+public:
+
+ /// @defgroup v4 memfile backend versions
+ ///
+ /// Version history:
+ /// 1.0 - initial version (released in Kea 0.9)
+ /// 2.0 - hwaddr column added (released in Kea 0.9.1)
+ /// 2.1 - user context column added (released in Kea 1.4.0)
+ /// 3.0 - pool_id column added (released in Kea 2.3.8)
+ ///
+ /// @{
+ /// @brief the major version of the v4 memfile backend
+ static const int MAJOR_VERSION_V4 = 3;
+
+ /// @brief the minor version of the v4 memfile backend
+ static const int MINOR_VERSION_V4 = 0;
+ /// @}
+
+ /// @defgroup v6 memfile backend versions
+ ///
+ /// Version history:
+ /// 1.0 - initial version (released in Kea 0.9)
+ /// 2.0 - hwaddr column added (released in Kea 0.9.1)
+ /// 3.0 - state column added (released in Kea 0.9.2)
+ /// 3.1 - user context column added (released in Kea 1.4.0)
+ /// 4.0 - hwtype,hwaddr_source columns added (released in Kea 2.1.2)
+ /// 5.0 - pool_id column added (released in Kea 2.3.8)
+ ///
+ /// @{
+ /// @brief the major version of the v6 memfile backend
+ static const int MAJOR_VERSION_V6 = 5;
+
+ /// @brief the minor version of the v6 memfile backend
+ static const int MINOR_VERSION_V6 = 0;
+ /// @}
+
+
+ /// @brief Specifies universe (V4, V6)
+ ///
+ /// This enumeration is used by various functions in Memfile %Lease Manager,
+ /// to identify the lease type referred to. In particular, it is used by
+ /// functions operating on the lease files to distinguish between lease
+ /// files for DHCPv4 and DHCPv6.
+ enum Universe {
+ V4,
+ V6
+ };
+
+ /// @name Methods implementing the API of the lease database backend.
+ /// The following methods are implementing the API of the
+ /// @c LeaseMgr to manage leases.
+ //@{
+
+ /// @brief The sole lease manager constructor
+ ///
+ /// This method:
+ /// - Initializes the new instance based on the parameters given
+ /// - Loads (or creates) the appropriate lease file(s)
+ /// - Initiates the periodic scheduling of the LFC (if enabled)
+ ///
+ /// If any of the files loaded require conversion to the current schema
+ /// (upgrade or downgrade), @c lfcSetup() will be invoked with its
+ /// @c run_once_now parameter set to true. This causes lfcSetup() to
+ /// invoke the LFC process immediately regardless of whether LFC is
+ /// enabled. This ensures that any files which need conversion are
+ /// converted automatically.
+ ///
+ /// dbconfig is a generic way of passing parameters. Parameters
+ /// are passed in the "name=value" format, separated by spaces.
+ /// Values may be enclosed in double quotes, if needed.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ Memfile_LeaseMgr(const db::DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Destructor (closes file)
+ virtual ~Memfile_LeaseMgr();
+
+ /// @brief Local version of getDBVersion() class method
+ static std::string getDBVersion(Universe const& u);
+
+ /// @brief Adds an IPv4 lease.
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not
+ virtual bool addLease(const Lease4Ptr& lease) override;
+
+ /// @brief Adds an IPv6 lease.
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not
+ virtual bool addLease(const Lease6Ptr& lease) override;
+
+ /// @brief Returns existing IPv4 lease for specified IPv4 address.
+ ///
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
+ /// @param addr An address of the searched lease.
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const override;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address.
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param hwaddr hardware address of the client
+ ///
+ /// @return lease collection
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const override;
+
+ /// @brief Returns existing IPv4 lease for specified hardware address
+ /// and a subnet
+ ///
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ SubnetID subnet_id) const override;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// @param client_id client identifier
+ ///
+ /// @return lease collection
+ virtual Lease4Collection getLease4(const ClientId& client_id) const override;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param clientid client identifier
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const ClientId& clientid,
+ SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv4 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv4 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(const std::string& hostname) const override;
+
+ /// @brief Returns all IPv4 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4() const override;
+
+ /// @brief Returns range of IPv4 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv4 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv4 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection
+ getLeases4(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const override;
+
+ /// @brief Returns existing IPv6 lease for a given IPv6 address.
+ ///
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param addr An address of the searched lease.
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease6Ptr getLease6(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const override;
+
+ /// @brief Returns existing IPv6 lease for a given DUID + IA + lease type
+ /// combination
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection getLeases6(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid) const override;
+
+ /// @brief Returns existing IPv6 lease for a given DUID + IA + subnet-id +
+ /// lease type combination.
+ ///
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param subnet_id identifier of the subnet the lease must belong to
+ ///
+ /// @return lease collection (may be empty if no lease is found)
+ virtual Lease6Collection getLeases6(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid,
+ SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv6 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv6 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(const std::string& hostname) const override;
+
+ /// @brief Returns all IPv6 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6() const override;
+
+ /// @brief Returns IPv6 leases for the DUID.
+ ///
+ /// @param duid client DUID
+ virtual Lease6Collection getLeases6(const DUID& duid) const override;
+
+ /// @brief Returns range of IPv6 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv6 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv6 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection
+ getLeases6(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const override;
+
+ /// @brief Returns a collection of expired DHCPv4 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+ const size_t max_leases) const override;
+
+ /// @brief Returns a collection of expired DHCPv6 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+ const size_t max_leases) const override;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// @warning This function does not validate the pointer to the lease.
+ /// It is caller's responsibility to pass the valid pointer.
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// @throw NoSuchLease if there is no such lease to be updated.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the update
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and UPDATE with different expiration time.
+ virtual void updateLease4(const Lease4Ptr& lease4) override;
+
+ /// @brief Updates IPv6 lease.
+ ///
+ /// @warning This function does not validate the pointer to the lease.
+ /// It is caller's responsibility to pass the valid pointer.
+ ///
+ /// @param lease6 The lease to be updated.
+ ///
+ /// @throw NoSuchLease if there is no such lease to be updated.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the update
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and UPDATE with different expiration time.
+ virtual void updateLease6(const Lease6Ptr& lease6) override;
+
+ /// @brief Deletes an IPv4 lease.
+ ///
+ /// @param lease IPv4 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the deletion
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and DELETE with different expiration time.
+ virtual bool deleteLease(const Lease4Ptr& lease) override;
+
+ /// @brief Deletes an IPv6 lease.
+ ///
+ /// @param lease IPv6 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the deletion
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and DELETE with different expiration time.
+ virtual bool deleteLease(const Lease6Ptr& lease) override;
+
+ /// @brief Deletes all expired-reclaimed DHCPv4 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs) override;
+
+ /// @brief Deletes all expired-reclaimed DHCPv6 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs) override;
+
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ ///
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id) override;
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ ///
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id) override;
+
+ /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+ /// Memfile implementation.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+ /// "subnet": { "id": 1, "address-limit": 2 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string
+ checkLimits4(isc::data::ConstElementPtr const& user_context) const override;
+
+ /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+ /// Memfile implementation.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+ /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string
+ checkLimits6(isc::data::ConstElementPtr const& user_context) const override;
+
+ /// @brief Checks if JSON support is enabled in the database.
+ /// Memfile implementation assumes JSON support is always enabled.
+ ///
+ /// @return true if there is JSON support, false otherwise
+ virtual bool isJsonSupported() const override;
+
+private:
+
+ /// @name Internal methods called while holding the mutex in multi threading
+ /// mode.
+ ///@{
+
+ /// @brief Adds an IPv4 lease,
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not
+ bool addLeaseInternal(const Lease4Ptr& lease);
+
+ /// @brief Adds an IPv6 lease.
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not
+ bool addLeaseInternal(const Lease6Ptr& lease);
+
+ /// @brief Returns existing IPv4 lease for specified IPv4 address.
+ ///
+ /// @param addr An address of the searched lease.
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ Lease4Ptr getLease4Internal(const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief Gets existing IPv4 leases for specified hardware address.
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param collection lease collection
+ void getLease4Internal(const isc::dhcp::HWAddr& hwaddr,
+ Lease4Collection& collection) const;
+
+ /// @brief Returns existing IPv4 lease for specified hardware address
+ /// and a subnet
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ Lease4Ptr getLease4Internal(const HWAddr& hwaddr,
+ SubnetID subnet_id) const;
+
+ /// @brief Gets existing IPv4 lease for specified client-id
+ ///
+ /// @param client_id client identifier
+ /// @param collection lease collection
+ void getLease4Internal(const ClientId& client_id,
+ Lease4Collection& collection) const;
+
+ /// @brief Returns IPv4 lease for specified client-id/hwaddr/subnet-id tuple
+ ///
+ /// @param clientid client identifier
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ Lease4Ptr getLease4Internal(const ClientId& clientid,
+ const HWAddr& hwaddr,
+ SubnetID subnet_id) const;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// @param clientid client identifier
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ Lease4Ptr getLease4Internal(const ClientId& clientid,
+ SubnetID subnet_id) const;
+
+ /// @brief Gets all IPv4 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param collection lease collection
+ void getLeases4Internal(SubnetID subnet_id,
+ Lease4Collection& collection) const;
+
+ /// @brief Returns all IPv4 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ /// @param collection lease collection
+ void getLeases4Internal(const std::string& hostname,
+ Lease4Collection& collection) const;
+
+ /// @brief Gets all IPv4 leases.
+ ///
+ /// @param collection lease collection
+ void getLeases4Internal(Lease4Collection& collection) const;
+
+ /// @brief Returns range of IPv4 leases using paging.
+ ///
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param collection lease collection
+ void getLeases4Internal(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ Lease4Collection& collection) const;
+
+ /// @brief Returns existing IPv6 lease for a given IPv6 address and type.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param addr An address of the searched lease.
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ Lease6Ptr getLease6Internal(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief Returns existing IPv6 lease of any type for a given IPv6 address.
+ ///
+ /// @param addr An address of the searched lease.
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ Lease6Ptr getAnyLease6Internal(const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief Returns existing IPv6 lease for a given DUID + IA + lease type
+ /// combination
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param collection lease collection
+ void getLeases6Internal(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid,
+ Lease6Collection& collection) const;
+
+ /// @brief Returns existing IPv6 lease for a given DUID + IA + subnet-id +
+ /// lease type combination.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param subnet_id identifier of the subnet the lease must belong to
+ /// @param collection lease collection
+ void getLeases6Internal(Lease::Type type,
+ const DUID& duid,
+ uint32_t iaid,
+ SubnetID subnet_id,
+ Lease6Collection& collection) const;
+
+ /// @brief Returns all IPv6 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param collection lease collection
+ void getLeases6Internal(SubnetID subnet_id,
+ Lease6Collection& collection) const;
+
+ /// @brief Returns all IPv6 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ /// @param collection lease collection
+ void getLeases6Internal(const std::string& hostname,
+ Lease6Collection& collection) const;
+
+ /// @brief Returns all IPv6 leases.
+ ///
+ /// @param collection lease collection
+ void getLeases6Internal(Lease6Collection& collection) const;
+
+ /// @brief Returns IPv6 leases for the DUID.
+ ///
+ /// @param duid client DUID
+ /// @param collection lease collection
+ void getLeases6Internal(const DUID& duid,
+ Lease6Collection& collection) const;
+
+ /// @brief Returns range of IPv6 leases using paging.
+ ///
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param collection lease collection
+ void getLeases6Internal(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ Lease6Collection& collection) const;
+
+ /// @brief Returns a collection of expired DHCPv4 leases.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ void getExpiredLeases4Internal(Lease4Collection& expired_leases,
+ const size_t max_leases) const;
+
+ /// @brief Returns a collection of expired DHCPv6 leases.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ void getExpiredLeases6Internal(Lease6Collection& expired_leases,
+ const size_t max_leases) const;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// @throw NoSuchLease if there is no such lease to be updated.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the update
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and UPDATE with different expiration time.
+ void updateLease4Internal(const Lease4Ptr& lease4);
+
+ /// @brief Updates IPv6 lease.
+ ///
+ /// @param lease6 The lease to be updated.
+ ///
+ /// @throw NoSuchLease if there is no such lease to be updated.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the update
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and UPDATE with different expiration time.
+ void updateLease6Internal(const Lease6Ptr& lease6);
+
+ /// @brief Deletes an IPv4 lease.
+ ///
+ /// @param lease IPv4 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the deletion
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and DELETE with different expiration time.
+ bool deleteLeaseInternal(const Lease4Ptr& addr);
+
+ /// @brief Deletes an IPv6 lease.
+ ///
+ /// @param lease IPv6 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note For test purposes only, when persistence is disabled, the deletion
+ /// of the lease is performed only if the value matches the one received on
+ /// the SELECT query, effectively enforcing no update on the lease between
+ /// SELECT and DELETE with different expiration time.
+ bool deleteLeaseInternal(const Lease6Ptr& addr);
+
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// @param subnet_id identifier of the subnet
+ ///
+ /// @return The number of deleted leases
+ size_t wipeLeases4Internal(const SubnetID& subnet_id);
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// @param subnet_id identifier of the subnet
+ ///
+ /// @return The number of deleted leases
+ size_t wipeLeases6Internal(const SubnetID& subnet_id);
+ ///@}
+
+ /// @brief Deletes all expired-reclaimed leases.
+ ///
+ /// This private method is called by both of the public methods:
+ /// @c deleteExpiredReclaimedLeases4 and
+ /// @c deleteExpiredReclaimedLeases6 to remove all expired
+ /// reclaimed DHCPv4 or DHCPv6 leases respectively.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ /// @param universe V4 or V6.
+ /// @param storage Reference to the container where leases are held.
+ /// Some expired-reclaimed leases will be removed from this container.
+ /// @param lease_file Reference to a DHCPv4 or DHCPv6 lease file
+ /// instance where leases should be marked as deleted.
+ ///
+ /// @return Number of leases deleted.
+ ///
+ /// @tparam IndexType Index type to be used to search for the
+ /// expired-reclaimed leases, i.e.
+ /// @c Lease4StorageExpirationIndex or @c Lease6StorageExpirationIndex.
+ /// @tparam LeaseType Lease type, i.e. @c Lease4 or @c Lease6.
+ /// @tparam StorageType Type of storage where leases are held, i.e.
+ /// @c Lease4Storage or @c Lease6Storage.
+ /// @tparam LeaseFileType Type of the lease file, i.e. DHCPv4 or
+ /// DHCPv6 lease file type.
+ template<typename IndexType, typename LeaseType, typename StorageType,
+ typename LeaseFileType>
+ uint64_t deleteExpiredReclaimedLeases(const uint32_t secs,
+ const Universe& universe,
+ StorageType& storage,
+ LeaseFileType& lease_file);
+
+ /// @brief Fetches the most recent value for a subnet statistic
+ ///
+ /// @param subnet_id subnet id of the subnet for which the stat is desired
+ /// @param stat_label name of the statistic desired (e.g. "assigned-addresses")
+ ///
+ /// @return Value of the statistic or zero if there are no entries found.
+ int64_t getSubnetStat(const SubnetID& subnet_id, const std::string& stat_label) const;
+
+ /// @brief Fetches the integer value of lease limit element from a parent element based
+ /// on Lease::Type.
+ ///
+ /// @param parent parent element (e.g. "client-class" or "subnet") in which to look for
+ /// the limit
+ /// @param ltype Lease::Type of the limit for which to look (one of Lease::TYPE_V4,
+ /// Lease::TYPE_NA, or Lease::TYPE_PD)
+ /// @param[out] limit contains the value of the limit if found
+ ///
+ /// @return bool true if a limit for the lease type was found, false otherwise.
+ bool getLeaseLimit(data::ConstElementPtr parent, Lease::Type ltype, size_t& limit) const;
+
+public:
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend.
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const override {
+ return (std::string("memfile"));
+ }
+
+ /// @brief Returns backend name.
+ ///
+ /// For now, memfile can only store data in memory.
+ ///
+ /// @return Name of the backend.
+ virtual std::string getName() const override {
+ return ("memory");
+ }
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const override;
+
+ /// @brief Returns backend version.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const override;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void commit() override;
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ virtual void rollback() override;
+
+ //@}
+
+ /// @name Public type and method used to determine file names for LFC.
+ //@{
+
+ /// @brief Types of the lease files used by the %Lease File Cleanup.
+ ///
+ /// This enumeration is used by a method which appends the appropriate
+ /// suffix to the lease file name.
+ enum LFCFileType {
+ FILE_CURRENT, ///< %Lease File
+ FILE_INPUT, ///< %Lease File Copy
+ FILE_PREVIOUS, ///< Previous %Lease File
+ FILE_OUTPUT, ///< LFC Output File
+ FILE_FINISH, ///< LFC Finish File
+ FILE_PID ///< PID File
+ };
+
+ /// @brief Appends appropriate suffix to the file name.
+ ///
+ /// The suffix is selected using the LFC file type specified as a
+ /// parameter. Each file type uses a unique suffix or no suffix:
+ /// - Current File: no suffix
+ /// - %Lease File Copy or Input File: ".1"
+ /// - Previous File: ".2"
+ /// - LFC Output File: ".output"
+ /// - LFC Finish File: ".completed"
+ /// - LFC PID File: ".pid"
+ ///
+ /// See
+ /// https://gitlab.isc.org/isc-projects/kea/wikis/designs/Lease-File-Cleanup-design
+ /// for details.
+ ///
+ /// @param file_name A base file name to which suffix is appended.
+ /// @param file_type An LFC file type.
+ /// @return A lease file name with a suffix appended.
+ static std::string appendSuffix(const std::string& file_name,
+ const LFCFileType& file_type);
+ //@}
+
+
+ /// @name Miscellaneous public convenience methods.
+ /// The following methods allow for retrieving useful information
+ /// about the state of the backend.
+ //@{
+
+ /// @brief Returns default path to the lease file.
+ ///
+ /// @param u Universe (V4 or V6).
+ std::string getDefaultLeaseFilePath(Universe u) const;
+
+ /// @brief Returns an absolute path to the lease file.
+ ///
+ /// @param u Universe (V4 or V6).
+ ///
+ /// @return Absolute path to the lease file or empty string if no lease
+ /// file is used.
+ std::string getLeaseFilePath(Universe u) const;
+
+ /// @brief Specifies whether or not leases are written to disk.
+ ///
+ /// It is possible that leases for DHCPv4 are written to disk whereas leases
+ /// for DHCPv6 are not; or vice versa. The argument of the method specifies
+ /// the type of lease in that respect.
+ ///
+ /// @param u Universe (V4 or V6).
+ ///
+ /// @return true if leases are written to lease file; if false is
+ /// returned, leases will be held in memory and will be lost upon
+ /// server shut down.
+ bool persistLeases(Universe u) const;
+
+ //@}
+
+private:
+
+ /// @brief Initialize the location of the lease file.
+ ///
+ /// This method uses the parameters passed as a map to the constructor to
+ /// initialize the location of the lease file. If the lease file is not
+ /// specified, the method will use the default location for the universe
+ /// (v4 or v6) selected. If the location is specified in the map as empty
+ /// or the "persist" parameter is set to "no" it will set the empty
+ /// location, which implies that leases belonging to the specified universe
+ /// will not be written to disk.
+ ///
+ /// @param u Universe (v4 or v6)
+ ///
+ /// @return The location of the lease file that should be assigned to the
+ /// lease_file4_ or lease_file6_, depending on the universe specified as an
+ /// argument to this function.
+ std::string initLeaseFilePath(Universe u);
+
+ /// @brief Load leases from the persistent storage.
+ ///
+ /// This method loads DHCPv4 or DHCPv6 leases from lease files in the
+ /// following order:
+ /// - If the <filename>.completed doesn't exist:
+ /// - leases from the <filename>.2
+ /// - leases from the <filename>.1
+ /// - leases from the <filename>
+ /// - else
+ /// - leases from the <filename>.completed
+ /// - leases from the <filename>
+ ///
+ /// If any of the files doesn't exist the method proceeds to reading
+ /// leases from the subsequent file. If the <filename> doesn't exist
+ /// it is created.
+ ///
+ /// When the method successfully reads leases from the files, it leaves
+ /// the file <filename> open and its internal pointer is set to the
+ /// end of file. The server will append lease entries to this file as
+ /// a result of processing new messages from the clients.
+ ///
+ /// The <filename>.2, <filename>.1 and <filename>.completed are the
+ /// products of the lease file cleanups (LFC).
+ /// See:
+ /// https://gitlab.isc.org/isc-projects/kea/wikis/designs/Lease-File-Cleanup-design
+ /// for details.
+ ///
+ /// @note: When the server starts up or is reconfigured it will try to
+ /// read leases from the lease files using this method. It is possible
+ /// that the %Lease File Cleanup is performed upon the lease files to
+ /// be read by this method. This may result in conflicts between the
+ /// server process and the LFC. To prevent it, the method checks if the
+ /// instance of the @c kea-lfc is running (using the PID file) before it
+ /// tries to load leases from the lease files. If it finds that there
+ /// is an LFC in progress, it throws an exception which will result
+ /// in the server refuse to start or reconfigure. When the administrator
+ /// retries starting up or reconfiguring the server it will most likely
+ /// be successful as the LFC should be complete by that time.
+ ///
+ /// @todo Consider implementing delaying the lease files loading when
+ /// the LFC is in progress by the specified amount of time.
+ ///
+ /// @param filename Name of the lease file.
+ /// @param lease_file An object representing a lease file to which
+ /// the server will store lease updates.
+ /// @param storage A storage for leases read from the lease file.
+ /// @tparam LeaseObjectType @c Lease4 or @c Lease6.
+ /// @tparam LeaseFileType @c CSVLeaseFile4 or @c CSVLeaseFile6.
+ /// @tparam StorageType @c Lease4Storage or @c Lease6Storage.
+ ///
+ /// @return Returns true if any of the files loaded need conversion from
+ /// an older or newer schema.
+ ///
+ /// @throw CSVFileError when parsing any of the lease files fails.
+ /// @throw DbOpenError when it is found that the LFC is in progress.
+ template<typename LeaseObjectType, typename LeaseFileType,
+ typename StorageType>
+ bool loadLeasesFromFiles(const std::string& filename,
+ boost::shared_ptr<LeaseFileType>& lease_file,
+ StorageType& storage);
+
+ /// @brief stores IPv4 leases
+ Lease4Storage storage4_;
+
+ /// @brief stores IPv6 leases
+ Lease6Storage storage6_;
+
+protected:
+
+ /// @brief stores IPv6 by-relay-id cross-reference table
+ Lease6ExtendedInfoRelayIdTable relay_id6_;
+
+ /// @brief stores IPv6 by-remote-id cross-reference table
+ Lease6ExtendedInfoRemoteIdTable remote_id6_;
+
+ /// @brief Holds the pointer to the DHCPv4 lease file IO.
+ boost::shared_ptr<CSVLeaseFile4> lease_file4_;
+
+ /// @brief Holds the pointer to the DHCPv6 lease file IO.
+ boost::shared_ptr<CSVLeaseFile6> lease_file6_;
+
+public:
+
+ /// @name Public methods to retrieve information about the LFC process state.
+ /// These methods are meant to be used by unit tests to retrieve the
+ /// state of the spawned LFC process before validating the result of
+ /// the lease file cleanup.
+ //@{
+
+ /// @brief Checks if the process performing lease file cleanup is running.
+ ///
+ /// @return true if the process performing lease file cleanup is running.
+ bool isLFCRunning() const;
+
+ /// @brief Returns the status code returned by the last executed
+ /// LFC process.
+ int getLFCExitStatus() const;
+ //@}
+
+ /// @brief Creates and runs the IPv4 lease stats query
+ ///
+ /// It creates an instance of a MemfileLeaseStatsQuery4 for an all subnets
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @return The populated query as a pointer to an LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery4() override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv4 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery4() override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MemfileLeaseStatsQuery4 for a single subnet
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MemfileLeaseStatsQuery4 for a subnet range
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) override;
+
+ /// @brief Creates and runs the IPv6 lease stats query
+ ///
+ /// It creates an instance of a MemfileLeaseStatsQuery6 and then
+ /// invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @return The populated query as a pointer to an LeaseStatsQuery.
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery6() override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv6 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery6() override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MemfileLeaseStatsQuery6 for a single subnet
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MemfileLeaseStatsQuery6 for a subnet range
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) override;
+
+ /// @name Protected methods used for %Lease File Cleanup.
+ /// The following methods are protected so as they can be accessed and
+ /// tested by unit tests.
+ //@{
+
+protected:
+
+ /// @brief A callback function triggering %Lease File Cleanup (LFC).
+ ///
+ /// This method is executed periodically to start the lease file cleanup.
+ /// It checks whether the file is a DHCPv4 lease file or DHCPv6 lease file
+ /// and executes the @c Memfile_LeaseMgr::lfcExecute private method
+ /// with the appropriate parameters.
+ ///
+ /// This method is virtual so as it can be overridden and customized in
+ /// the unit tests. In particular, the unit test which checks that the
+ /// callback function has been executed would override this function
+ /// to increase the execution counter each time it is executed.
+ virtual void lfcCallback();
+ //@}
+
+ /// @name Private methods and members used for %Lease File Cleanup.
+ //@{
+
+private:
+
+ /// @brief Setup the periodic %Lease File Cleanup.
+ ///
+ /// This method checks if the @c lfc-interval configuration parameter
+ /// is set to a non-zero value and sets up the interval timer to
+ /// perform the %Lease File Cleanup periodically. It also prepares the
+ /// path and arguments for the @c kea-lfc application which will be
+ /// executed to perform the cleanup. By default the backend will use
+ /// the path to the kea-lfc in the Kea installation directory. If
+ /// the unit tests need to override this path (with the path in the
+ /// Kea build directory, the @c KEA_LFC_EXECUTABLE environmental
+ /// variable should be set to hold an absolute path to the kea-lfc
+ /// executable.
+ /// @param conversion_needed flag that indicates input lease file(s) are
+ /// schema do not match the current schema (older or newer), and need
+ /// conversion. This value is passed through to LFCSetup::setup() via its
+ /// run_once_now parameter.
+ void lfcSetup(bool conversion_needed = false);
+
+ /// @brief Performs a lease file cleanup for DHCPv4 or DHCPv6.
+ ///
+ /// This method performs all the actions necessary to prepare for the
+ /// execution of the LFC and if these actions are successful, it executes
+ /// the @c kea-lfc application as a background process to process (cleanup)
+ /// the lease files.
+ ///
+ /// For the design and the terminology used in this description refer to
+ /// the https://gitlab.isc.org/isc-projects/kea/wikis/designs/Lease-File-Cleanup-design.
+ ///
+ /// If the method finds that the %Lease File Copy exists it simply runs
+ /// the @c kea-lfc application.
+ ///
+ /// If the %Lease File Copy doesn't exist it moves the Current %Lease File
+ /// to Lease File Copy, and then recreates the Current Lease File without
+ /// any lease entries. If the file has been successfully moved, it runs
+ /// the @c kea-lfc application.
+ ///
+ /// @param lease_file A pointer to the object representing the Current
+ /// %Lease File (DHCPv4 or DHCPv6 lease file).
+ ///
+ /// @tparam LeaseFileType One of @c CSVLeaseFile4 or @c CSVLeaseFile6.
+ template<typename LeaseFileType>
+ void lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file);
+
+ /// @brief A pointer to the Lease File Cleanup configuration.
+ boost::scoped_ptr<LFCSetup> lfc_setup_;
+
+ /// @brief Parameters storage
+ ///
+ /// DatabaseConnection object is used only for storing, accessing and
+ /// printing parameter map.
+ db::DatabaseConnection conn_;
+
+ //@}
+
+ /// @brief Manager mutex
+ boost::scoped_ptr<std::mutex> mutex_;
+
+ /// @brief Class lease counts container
+ ClassLeaseCounter class_lease_counter_;
+
+public:
+ /// @brief Returns the class lease count for a given class and lease type.
+ ///
+ /// @param client_class client class for which the count is desired
+ /// @param ltype type of lease for which the count is desired. Defaults to
+ /// Lease::TYPE_V4.
+ ///
+ /// @return number of leases
+ virtual size_t getClassLeaseCount(const ClientClass& client_class,
+ const Lease::Type& ltype = Lease::TYPE_V4) const override;
+
+ /// @brief Recount the leases per class for V4 leases.
+ ///
+ /// Clears the current class-lease count map and then iterates
+ /// over all, retabulating counts based on class lists in each lease
+ /// user-context.
+ ///
+ /// Must be called from a thread-safe context.
+ virtual void recountClassLeases4() override;
+
+ /// @brief Recount the leases per class for V6 leases.
+ ///
+ /// Clears the current class-lease count map and then iterates
+ /// over all, retabulating counts based on class lists in each lease
+ /// user-context.
+ ///
+ /// Must be called from a thread-safe context.
+ virtual void recountClassLeases6() override;
+
+ /// @brief Clears the class-lease count map.
+ ///
+ /// Must be called from a thread-safe context.
+ virtual void clearClassLeaseCounts() override;
+
+ /// The following queries are used to fulfill Bulk Lease Query
+ /// queries. They rely on relay data contained in lease's
+ /// user-context when the extended-store-info flag is enabled.
+
+ /// @brief Returns existing IPv4 leases with a given relay-id.
+ ///
+ /// @param relay_id RAI Relay-ID sub-option value for relay_id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRelayId(const OptionBuffer& relay_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) override;
+
+ /// @brief Returns existing IPv4 leases with a given remote-id.
+ ///
+ /// @param remote_id RAI Remote-ID sub-option value for remote-id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included. Defaults to zero.
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included. Defaults to zero.
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) override;
+
+ /// @brief Returns existing IPv6 leases with a given relay-id.
+ ///
+ /// @param relay_id DUID for relay_id of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRelayId(const DUID& relay_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Returns existing IPv6 leases with a given remote-id.
+ ///
+ /// @param remote_id remote-id option data of interest
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Returns existing IPv6 leases with on a given link.
+ ///
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByLink(const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Extract extended info for v4 leases.
+ ///
+ /// For v4 relay and remote identifiers are stored inside leases vs.
+ /// tables for v6.
+ ///
+ /// @param update Update extended info in database.
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config.
+ /// @return The number of updates in the database or 0.
+ size_t extractExtendedInfo4(bool update, bool current);
+
+ /// @brief Build extended info v6 tables.
+ ///
+ /// @param update Update extended info in database.
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config.
+ /// @return The number of updates in the database or 0.
+ virtual size_t buildExtendedInfoTables6(bool update, bool current) override;
+
+private:
+
+ /// @brief Returns existing IPv4 leases with a given relay-id.
+ ///
+ /// @param relay_id RAI Relay-ID sub-option value for relay_id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included
+ ///
+ /// @return collection of IPv4 leases
+ Lease4Collection
+ getLeases4ByRelayIdInternal(const OptionBuffer& relay_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time,
+ const time_t& qry_end_time);
+
+ /// @brief Returns existing IPv4 leases with a given remote-id.
+ ///
+ /// @param remote_id RAI Remote-ID sub-option value for remote-id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included. Defaults to zero.
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included. Defaults to zero.
+ ///
+ /// @return collection of IPv4 leases
+ Lease4Collection
+ getLeases4ByRemoteIdInternal(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time,
+ const time_t& qry_end_time);
+
+ /// @brief Returns existing IPv6 leases with a given relay-id.
+ ///
+ /// @param relay_id DUID for relay_id of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ Lease6Collection
+ getLeases6ByRelayIdInternal(const DUID& relay_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size);
+
+ /// @brief Returns existing IPv6 leases with a given remote-id.
+ ///
+ /// @param remote_id remote-id option data of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ Lease6Collection
+ getLeases6ByRemoteIdInternal(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size);
+
+ /// @brief Returns existing IPv6 leases with on a given link.
+ ///
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ Lease6Collection
+ getLeases6ByLinkInternal(const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size);
+
+ /// @brief Build extended info v6 tables.
+ ///
+ /// @param update Update extended info in database.
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config.
+ /// @return The number of updates in the database or 0.
+ size_t buildExtendedInfoTables6Internal(bool update, bool current);
+
+public:
+
+ /// @brief Write V4 leases to a file.
+ ///
+ /// @param filename File name to write leases.
+ virtual void writeLeases4(const std::string& filename) override;
+
+ /// @brief Write V6 leases to a file.
+ ///
+ /// @param filename File name to write leases.
+ virtual void writeLeases6(const std::string& filename) override;
+
+ /// @brief Upgrade extended info (v4).
+ ///
+ /// @param page_size The page size used for retrieval.
+ /// @return Always return 0 as this function is a noop for not SQL backends.
+ virtual size_t upgradeExtendedInfo4(const LeasePageSize& page_size) override;
+
+protected:
+
+ /// Extended information / Bulk Lease Query shared interface.
+
+ /// @brief Delete lease6 extended info from tables.
+ ///
+ /// @param addr The address of the lease.
+ virtual void deleteExtendedInfo6(const isc::asiolink::IOAddress& addr) override;
+
+ /// @brief Add lease6 extended info into by-relay-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param relay_id The relay id from the relay header options.
+ virtual void addRelayId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& relay_id) override;
+
+ /// @brief Add lease6 extended info into by-remote-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param remote_id The remote id from the relay header options.
+ virtual void addRemoteId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& remote_id) override;
+
+private:
+ /// @brief Write V4 leases to a file.
+ ///
+ /// @param filename File name to write leases.
+ /// Must be called from a thread-safe context.
+ virtual void writeLeases4Internal(const std::string& filename);
+
+ /// @brief Write V6 leases to a file.
+ ///
+ /// @param filename File name to write leases.
+ /// Must be called from a thread-safe context.
+ virtual void writeLeases6Internal(const std::string& filename);
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // MEMFILE_LEASE_MGR_H
diff --git a/src/lib/dhcpsrv/memfile_lease_storage.h b/src/lib/dhcpsrv/memfile_lease_storage.h
new file mode 100644
index 0000000..174dff4
--- /dev/null
+++ b/src/lib/dhcpsrv/memfile_lease_storage.h
@@ -0,0 +1,513 @@
+// Copyright (C) 2015-2023 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 MEMFILE_LEASE_STORAGE_H
+#define MEMFILE_LEASE_STORAGE_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet_id.h>
+
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+
+#include <functional>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Tag for indexes by address.
+struct AddressIndexTag { };
+
+/// @brief Tag for indexes by DUID, IAID, lease type tuple.
+struct DuidIaidTypeIndexTag { };
+
+/// @brief Tag for indexes by expiration time.
+struct ExpirationIndexTag { };
+
+/// @brief Tag for indexes by HW address, subnet-id tuple.
+struct HWAddressSubnetIdIndexTag { };
+
+/// @brief Tag for indexes by client-id, subnet-id tuple.
+struct ClientIdSubnetIdIndexTag { };
+
+/// @brief Tag for indexes by subnet-id.
+struct SubnetIdIndexTag { };
+
+/// @brief Tag for indexes by subnet-id and pool-id.
+struct SubnetIdPoolIdIndexTag { };
+
+/// @brief Tag for index using DUID.
+struct DuidIndexTag { };
+
+/// @brief Tag for index using hostname.
+struct HostnameIndexTag { };
+
+/// @brief Tag for index using remote-id.
+struct RemoteIdIndexTag { };
+
+/// @brief Tag for index using relay-id.
+struct RelayIdIndexTag { };
+
+/// @name Multi index containers holding DHCPv4 and DHCPv6 leases.
+///
+//@{
+
+/// @brief A multi index container holding DHCPv6 leases.
+///
+/// The leases in the container may be accessed using different indexes:
+/// - using an IPv6 address,
+/// - using a composite index: DUID, IAID and lease type.
+/// - using a composite index: boolean flag indicating if the state is
+/// "expired-reclaimed" and expiration time.
+/// - using subnet ID.
+/// - using hostname.
+///
+/// Indexes can be accessed using the index number (from 0 to 5) or a
+/// name tag. It is recommended to use the tags to access indexes as
+/// they do not depend on the order of indexes in the container.
+typedef boost::multi_index_container<
+ // It holds pointers to Lease6 objects.
+ Lease6Ptr,
+ boost::multi_index::indexed_by<
+ // Specification of the first index starts here.
+ // This index sorts leases by IPv6 addresses represented as
+ // IOAddress objects.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<AddressIndexTag>,
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+ >,
+
+ // Specification of the second index starts here.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<DuidIaidTypeIndexTag>,
+ // This is a composite index that will be used to search for
+ // the lease using three attributes: DUID, IAID and lease type.
+ boost::multi_index::composite_key<
+ Lease6,
+ // The DUID can be retrieved from the Lease6 object using
+ // a getDuidVector const function.
+ boost::multi_index::const_mem_fun<Lease6, const std::vector<uint8_t>&,
+ &Lease6::getDuidVector>,
+ // The two other ingredients of this index are IAID and
+ // lease type.
+ boost::multi_index::member<Lease6, uint32_t, &Lease6::iaid_>,
+ boost::multi_index::member<Lease6, Lease::Type, &Lease6::type_>
+ >
+ >,
+
+ // Specification of the third index starts here.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<ExpirationIndexTag>,
+ // This is a composite index that is used to search for
+ // the expired leases. Depending on the value of the first component
+ // of the search key, the reclaimed or not reclaimed leases can
+ // be searched.
+ boost::multi_index::composite_key<
+ Lease6,
+ // The boolean value specifying if lease is reclaimed or not.
+ boost::multi_index::const_mem_fun<Lease, bool,
+ &Lease::stateExpiredReclaimed>,
+ // Lease expiration time.
+ boost::multi_index::const_mem_fun<Lease, int64_t,
+ &Lease::getExpirationTime>
+ >
+ >,
+
+ // Specification of the fourth index starts here.
+ // This index sorts leases by SubnetID.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetIdIndexTag>,
+ boost::multi_index::member<Lease, isc::dhcp::SubnetID,
+ &Lease::subnet_id_>
+ >,
+
+ // Specification of the fifth index starts here
+ // This index is used to retrieve leases for matching duid.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<DuidIndexTag>,
+ boost::multi_index::const_mem_fun<Lease6,
+ const std::vector<uint8_t>&,
+ &Lease6::getDuidVector>
+ >,
+
+ // Specification of the sixth index starts here
+ // This index is used to retrieve leases for matching hostname.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<HostnameIndexTag>,
+ boost::multi_index::member<Lease, std::string, &Lease::hostname_>
+ >,
+
+ // Specification of the seventh index starts here.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetIdPoolIdIndexTag>,
+ // This is a composite index that combines two attributes of the
+ // Lease6 object: subnet id and pool id.
+ boost::multi_index::composite_key<
+ Lease6,
+ // The subnet id is held in the subnet_id_ member of Lease6
+ // class. Note that the subnet_id_ is defined in the base
+ // class (Lease) so we have to point to this class rather
+ // than derived class: Lease6.
+ boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>,
+ // The pool id is held in the pool_id_ member of Lease6
+ // class. Note that the pool_id_ is defined in the base
+ // class (Lease) so we have to point to this class rather
+ // than derived class: Lease6.
+ boost::multi_index::member<Lease, uint32_t, &Lease::pool_id_>
+ >
+ >
+ >
+> Lease6Storage; // Specify the type name of this container.
+
+/// @brief A multi index container holding DHCPv4 leases.
+///
+/// The leases in the container may be accessed using different indexes:
+/// - IPv4 address,
+/// - composite index: hardware address and subnet id,
+/// - composite index: client id and subnet id,
+/// - using a composite index: boolean flag indicating if the state is
+/// "expired-reclaimed" and expiration time.
+/// - using subnet id.
+/// - using hostname.
+/// - using remote id.
+/// - using a composite index:
+///
+/// Indexes can be accessed using the index number (from 0 to 5) or a
+/// name tag. It is recommended to use the tags to access indexes as
+/// they do not depend on the order of indexes in the container.
+typedef boost::multi_index_container<
+ // It holds pointers to Lease4 objects.
+ Lease4Ptr,
+ // Specification of search indexes starts here.
+ boost::multi_index::indexed_by<
+ // Specification of the first index starts here.
+ // This index sorts leases by IPv4 addresses represented as
+ // IOAddress objects.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<AddressIndexTag>,
+ // The IPv4 address are held in addr_ members that belong to
+ // Lease class.
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+ >,
+
+ // Specification of the second index starts here.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<HWAddressSubnetIdIndexTag>,
+ // This is a composite index that combines two attributes of the
+ // Lease4 object: hardware address and subnet id.
+ boost::multi_index::composite_key<
+ Lease4,
+ // The hardware address is held in the hwaddr_ member of the
+ // Lease4 object, which is a HWAddr object. Boost does not
+ // provide a key extractor for getting a member of a member,
+ // so we need a simple method for that.
+ boost::multi_index::const_mem_fun<Lease, const std::vector<uint8_t>&,
+ &Lease::getHWAddrVector>,
+ // The subnet id is held in the subnet_id_ member of Lease4
+ // class. Note that the subnet_id_ is defined in the base
+ // class (Lease) so we have to point to this class rather
+ // than derived class: Lease4.
+ boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
+ >
+ >,
+
+ // Specification of the third index starts here.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<ClientIdSubnetIdIndexTag>,
+ // This is a composite index that uses two values to search for a
+ // lease: client id and subnet id.
+ boost::multi_index::composite_key<
+ Lease4,
+ // The client id can be retrieved from the Lease4 object by
+ // calling getClientIdVector const function.
+ boost::multi_index::const_mem_fun<Lease4, const std::vector<uint8_t>&,
+ &Lease4::getClientIdVector>,
+ // The subnet id is accessed through the subnet_id_ member.
+ boost::multi_index::member<Lease, uint32_t, &Lease::subnet_id_>
+ >
+ >,
+
+ // Specification of the fourth index starts here.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<ExpirationIndexTag>,
+ // This is a composite index that will be used to search for
+ // the expired leases. Depending on the value of the first component
+ // of the search key, the reclaimed or not reclaimed leases will can
+ // be searched.
+ boost::multi_index::composite_key<
+ Lease4,
+ // The boolean value specifying if lease is reclaimed or not.
+ boost::multi_index::const_mem_fun<Lease, bool,
+ &Lease::stateExpiredReclaimed>,
+ // Lease expiration time.
+ boost::multi_index::const_mem_fun<Lease, int64_t,
+ &Lease::getExpirationTime>
+ >
+ >,
+
+ // Specification of the fifth index starts here.
+ // This index sorts leases by SubnetID.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetIdIndexTag>,
+ boost::multi_index::member<Lease, isc::dhcp::SubnetID, &Lease::subnet_id_>
+ >,
+
+ // Specification of the sixth index starts here.
+ // This index is used to retrieve leases for matching hostname.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<HostnameIndexTag>,
+ boost::multi_index::member<Lease, std::string, &Lease::hostname_>
+ >,
+
+ // Specification of the seventh index starts here.
+ // This index is used to retrieve leases for matching remote id
+ // for Bulk Lease Query.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<RemoteIdIndexTag>,
+ boost::multi_index::member<Lease4,
+ std::vector<uint8_t>,
+ &Lease4::remote_id_>
+ >,
+
+ // Specification of the eighth index starts here.
+ // This index is used to retrieve leases for matching relay id
+ // for Bulk Lease Query.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<RelayIdIndexTag>,
+ boost::multi_index::composite_key<
+ Lease4,
+ // Relay id.
+ boost::multi_index::member<Lease4,
+ std::vector<uint8_t>,
+ &Lease4::relay_id_>,
+ // Address.
+ boost::multi_index::member<Lease,
+ isc::asiolink::IOAddress,
+ &Lease::addr_>
+ >
+ >,
+
+ // Specification of the ninth index starts here.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetIdPoolIdIndexTag>,
+ // This is a composite index that combines two attributes of the
+ // Lease4 object: subnet id and pool id.
+ boost::multi_index::composite_key<
+ Lease4,
+ // The subnet id is held in the subnet_id_ member of Lease4
+ // class. Note that the subnet_id_ is defined in the base
+ // class (Lease) so we have to point to this class rather
+ // than derived class: Lease4.
+ boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>,
+ // The pool id is held in the pool_id_ member of Lease4
+ // class. Note that the pool_id_ is defined in the base
+ // class (Lease) so we have to point to this class rather
+ // than derived class: Lease4.
+ boost::multi_index::member<Lease, uint32_t, &Lease::pool_id_>
+ >
+ >
+ >
+> Lease4Storage; // Specify the type name for this container.
+
+//@}
+
+/// @name Indexes used by the multi index containers
+///
+//@{
+
+/// @brief DHCPv6 lease storage index by address.
+typedef Lease6Storage::index<AddressIndexTag>::type Lease6StorageAddressIndex;
+
+/// @brief DHCPv6 lease storage index by DUID, IAID, lease type.
+typedef Lease6Storage::index<DuidIaidTypeIndexTag>::type Lease6StorageDuidIaidTypeIndex;
+
+/// @brief DHCPv6 lease storage index by expiration time.
+typedef Lease6Storage::index<ExpirationIndexTag>::type Lease6StorageExpirationIndex;
+
+/// @brief DHCPv6 lease storage index by subnet-id.
+typedef Lease6Storage::index<SubnetIdIndexTag>::type Lease6StorageSubnetIdIndex;
+
+/// @brief DHCPv6 lease storage index subnet-id and pool-id.
+typedef Lease6Storage::index<SubnetIdPoolIdIndexTag>::type Lease6StorageSubnetIdPoolIdIndex;
+
+/// @brief DHCPv6 lease storage index by DUID.
+typedef Lease6Storage::index<DuidIndexTag>::type Lease6StorageDuidIndex;
+
+/// @brief DHCPv6 lease storage index by hostname.
+typedef Lease6Storage::index<HostnameIndexTag>::type Lease6StorageHostnameIndex;
+
+/// @brief DHCPv4 lease storage index by address.
+typedef Lease4Storage::index<AddressIndexTag>::type Lease4StorageAddressIndex;
+
+/// @brief DHCPv4 lease storage index by expiration time.
+typedef Lease4Storage::index<ExpirationIndexTag>::type Lease4StorageExpirationIndex;
+
+/// @brief DHCPv4 lease storage index by HW address and subnet-id.
+typedef Lease4Storage::index<HWAddressSubnetIdIndexTag>::type
+Lease4StorageHWAddressSubnetIdIndex;
+
+/// @brief DHCPv4 lease storage index by client-id and subnet-id.
+typedef Lease4Storage::index<ClientIdSubnetIdIndexTag>::type
+Lease4StorageClientIdSubnetIdIndex;
+
+/// @brief DHCPv4 lease storage index subnet-id.
+typedef Lease4Storage::index<SubnetIdIndexTag>::type Lease4StorageSubnetIdIndex;
+
+/// @brief DHCPv4 lease storage index subnet-id and pool-id.
+typedef Lease4Storage::index<SubnetIdPoolIdIndexTag>::type Lease4StorageSubnetIdPoolIdIndex;
+
+/// @brief DHCPv4 lease storage index by hostname.
+typedef Lease4Storage::index<HostnameIndexTag>::type Lease4StorageHostnameIndex;
+
+/// @brief DHCPv4 lease storage index by remote-id.
+typedef Lease4Storage::index<RemoteIdIndexTag>::type Lease4StorageRemoteIdIndex;
+
+/// @brief DHCPv4 lease storage range by remote-id.
+typedef std::pair<Lease4StorageRemoteIdIndex::const_iterator,
+ Lease4StorageRemoteIdIndex::const_iterator> Lease4StorageRemoteIdRange;
+
+/// @brief DHCPv4 lease storage index by relay-id.
+typedef Lease4Storage::index<RelayIdIndexTag>::type Lease4StorageRelayIdIndex;
+
+//@}
+
+/// @name Multi index containers holding DHCPv6 lease extended informations
+/// for Bulk Lease Query.
+//@{
+
+/// @brief Lease6 extended informations for Bulk Lease Query.
+class Lease6ExtendedInfo {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param lease_addr Lease address.
+ /// @param id Identifier.
+ Lease6ExtendedInfo(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& id)
+ : lease_addr_(lease_addr), id_(id) {
+ }
+
+ /// @brief Lease address.
+ isc::asiolink::IOAddress lease_addr_;
+
+ /// @brief Remote or relay opaque identifier.
+ std::vector<uint8_t> id_;
+};
+
+/// @brief Pointer to a Lease6ExtendedInfo object.
+typedef boost::shared_ptr<Lease6ExtendedInfo> Lease6ExtendedInfoPtr;
+
+/// @brief Tag for indexes by lease address.
+struct LeaseAddressIndexTag { };
+
+/// @brief A multi index container holding lease6 extended info for by relay id.
+///
+/// The lease6 extended info may be accessed using different indexes:
+/// - using relay id, and lease address for getting lower bounds.
+/// - using lease address for deletes.
+///
+/// The choice of binary trees was governed by the fact a large number of
+/// clients can be behind a relay.
+///
+/// Indexes can be accessed using the index number (from 0 to 5) or a
+/// name tag. It is recommended to use the tags to access indexes as
+/// they do not depend on the order of indexes in the container.
+typedef boost::multi_index_container<
+ // It holds pointers to lease6 extended info.
+ Lease6ExtendedInfoPtr,
+ boost::multi_index::indexed_by<
+ // First index is by relay id and lease address.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<RelayIdIndexTag>,
+ boost::multi_index::composite_key<
+ Lease6ExtendedInfo,
+ boost::multi_index::member<Lease6ExtendedInfo,
+ std::vector<uint8_t>,
+ &Lease6ExtendedInfo::id_>,
+ boost::multi_index::member<Lease6ExtendedInfo,
+ isc::asiolink::IOAddress,
+ &Lease6ExtendedInfo::lease_addr_>
+ >
+ >,
+
+ // Last index is by lease address.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<LeaseAddressIndexTag>,
+ boost::multi_index::member<Lease6ExtendedInfo,
+ isc::asiolink::IOAddress,
+ &Lease6ExtendedInfo::lease_addr_>
+ >
+ >
+> Lease6ExtendedInfoRelayIdTable;
+
+/// @brief A multi index container holding lease6 extended info for by remote id.
+///
+/// The lease6 extended info may be accessed using different indexes:
+/// - using remote id.
+/// - using lease address for deletes.
+///
+/// The internal layout of remote id is not used. The choice of hash tables
+/// was governed by the fact a small number of clients should share the same
+/// remote id.
+///
+/// Indexes can be accessed using the index number (from 0 to 5) or a
+/// name tag. It is recommended to use the tags to access indexes as
+/// they do not depend on the order of indexes in the container.
+typedef boost::multi_index_container<
+ // It holds pointers to lease6 extended info.
+ Lease6ExtendedInfoPtr,
+ boost::multi_index::indexed_by<
+ // First index is by remote id.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<RemoteIdIndexTag>,
+ boost::multi_index::member<Lease6ExtendedInfo,
+ std::vector<uint8_t>,
+ &Lease6ExtendedInfo::id_>
+ >,
+
+ // Last index is by lease address.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<LeaseAddressIndexTag>,
+ boost::multi_index::member<Lease6ExtendedInfo,
+ isc::asiolink::IOAddress,
+ &Lease6ExtendedInfo::lease_addr_>
+ >
+ >
+> Lease6ExtendedInfoRemoteIdTable;
+
+/// @brief Lease6 extended information by relay id index.
+typedef Lease6ExtendedInfoRelayIdTable::index<RelayIdIndexTag>::type
+ RelayIdIndex;
+
+/// @brief Lease6 extended information by lease address index of by relay id table.
+typedef Lease6ExtendedInfoRelayIdTable::index<LeaseAddressIndexTag>::type
+ LeaseAddressRelayIdIndex;
+
+/// @brief Lease6 extended information by remote id index.
+typedef Lease6ExtendedInfoRemoteIdTable::index<RemoteIdIndexTag>::type
+ RemoteIdIndex;
+
+/// @brief Lease6 extended information by remote id range.
+typedef std::pair<RemoteIdIndex::const_iterator, RemoteIdIndex::const_iterator>
+ RemoteIdIndexRange;
+
+/// @brief Lease6 extended information by lease address index of by remote id table.
+typedef Lease6ExtendedInfoRemoteIdTable::index<LeaseAddressIndexTag>::type
+ LeaseAddressRemoteIdIndex;
+
+//@}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // MEMFILE_LEASE_STORAGE_H
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.cc b/src/lib/dhcpsrv/mysql_host_data_source.cc
new file mode 100644
index 0000000..b070ec8
--- /dev/null
+++ b/src/lib/dhcpsrv/mysql_host_data_source.cc
@@ -0,0 +1,4141 @@
+// Copyright (C) 2015-2023 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 <asiolink/io_service.h>
+#include <database/db_exceptions.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/mysql_host_data_source.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <util/buffer.h>
+#include <util/multi_threading_mgr.h>
+#include <util/optional.h>
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/array.hpp>
+#include <boost/pointer_cast.hpp>
+#include <boost/static_assert.hpp>
+
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <stdint.h>
+
+#include <mutex>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+/// @brief Numeric value representing last supported identifier.
+///
+/// This value is used to validate whether the identifier type stored in
+/// a database is within bounds. of supported identifiers.
+const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
+
+/// @brief This class provides mechanisms for sending and retrieving
+/// information from the 'hosts' table.
+///
+/// This class is used to insert and retrieve entries from the 'hosts' table.
+/// The queries used with this class do not retrieve IPv6 reservations or
+/// options associated with a host to minimize impact on performance. Other
+/// classes derived from @ref MySqlHostExchange should be used to retrieve
+/// information about IPv6 reservations and options.
+///
+/// Database schema contains several unique indexes to guard against adding
+/// multiple hosts for the same client identifier in a single subnet and for
+/// adding multiple hosts with a reservation for the same IPv4 address in a
+/// single subnet. The exceptions that have to be taken into account are
+/// listed below:
+/// - zero or null IPv4 address indicates that there is no reservation for the
+/// IPv4 address for the host,
+/// - zero or null subnet identifier (either IPv4 or IPv6) indicates that
+/// this subnet identifier must be ignored. Specifically, this is the case
+/// when host reservation is for the DHCPv4 server, the IPv6 subnet id should
+/// be ignored. Conversely, when host reservation is created for the DHCPv6 server,
+/// the IPv4 subnet id should be ignored.
+///
+/// To exclude those special case values from the unique indexes, the MySQL
+/// backend relies on the property of the unique indexes in MySQL, i.e. null
+/// values are excluded from unique indexes. That means that there might be
+/// multiple null values in a given column on which unique index is applied.
+/// Therefore, the MySQL backend converts subnet identifiers and IPv4 addresses
+/// of 0 to null before inserting a host to the database.
+class MySqlHostExchange {
+private:
+ /// @brief Column numbers for each column in the hosts table.
+ /// These are used for both retrieving data and for looking up
+ /// column labels for logging. Note that their numeric order
+ /// MUST match that of the column order in the hosts table.
+ //@{
+ static const size_t HOST_ID_COL = 0;
+ static const size_t DHCP_IDENTIFIER_COL = 1;
+ static const size_t DHCP_IDENTIFIER_TYPE_COL = 2;
+ static const size_t DHCP4_SUBNET_ID_COL = 3;
+ static const size_t DHCP6_SUBNET_ID_COL = 4;
+ static const size_t IPV4_ADDRESS_COL = 5;
+ static const size_t HOSTNAME_COL = 6;
+ static const size_t DHCP4_CLIENT_CLASSES_COL = 7;
+ static const size_t DHCP6_CLIENT_CLASSES_COL = 8;
+ static const size_t USER_CONTEXT_COL = 9;
+ static const size_t DHCP4_NEXT_SERVER_COL = 10;
+ static const size_t DHCP4_SERVER_HOSTNAME_COL = 11;
+ static const size_t DHCP4_BOOT_FILE_NAME_COL = 12;
+ static const size_t AUTH_KEY_COL = 13;
+ //@}
+ /// @brief Number of columns returned for SELECT queries sent by this class.
+ static const size_t HOST_COLUMNS = 14;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param additional_columns_num This value is set by the derived classes
+ /// to indicate how many additional columns will be returned by SELECT
+ /// queries performed by the derived class. This constructor will allocate
+ /// resources for these columns, e.g. binding table, error indicators.
+ MySqlHostExchange(const size_t additional_columns_num = 0)
+ : columns_num_(HOST_COLUMNS + additional_columns_num),
+ bind_(columns_num_), columns_(columns_num_),
+ error_(columns_num_, MLM_FALSE), host_id_(0),
+ dhcp_identifier_length_(0), dhcp_identifier_type_(0),
+ dhcp4_subnet_id_(SUBNET_ID_UNUSED),
+ dhcp6_subnet_id_(SUBNET_ID_UNUSED), ipv4_address_(0),
+ hostname_length_(0), dhcp4_client_classes_length_(0),
+ dhcp6_client_classes_length_(0),
+ user_context_length_(0),
+ dhcp4_next_server_(0),
+ dhcp4_server_hostname_length_(0),
+ dhcp4_boot_file_name_length_(0),
+ auth_key_length_(0),
+ dhcp4_subnet_id_null_(MLM_FALSE),
+ dhcp6_subnet_id_null_(MLM_FALSE),
+ ipv4_address_null_(MLM_FALSE), hostname_null_(MLM_FALSE),
+ dhcp4_client_classes_null_(MLM_FALSE),
+ dhcp6_client_classes_null_(MLM_FALSE),
+ user_context_null_(MLM_FALSE),
+ dhcp4_next_server_null_(MLM_FALSE),
+ dhcp4_server_hostname_null_(MLM_FALSE),
+ dhcp4_boot_file_name_null_(MLM_FALSE),
+ auth_key_null_(MLM_FALSE) {
+
+ // Fill arrays with 0 so as they don't include any garbage.
+ memset(dhcp_identifier_buffer_, 0, sizeof(dhcp_identifier_buffer_));
+ memset(hostname_, 0, sizeof(hostname_));
+ memset(dhcp4_client_classes_, 0, sizeof(dhcp4_client_classes_));
+ memset(dhcp6_client_classes_, 0, sizeof(dhcp6_client_classes_));
+ memset(user_context_, 0, sizeof(user_context_));
+ memset(dhcp4_server_hostname_, 0, sizeof(dhcp4_server_hostname_));
+ memset(dhcp4_boot_file_name_, 0, sizeof(dhcp4_boot_file_name_));
+ memset(auth_key_, 0, sizeof(auth_key_));
+
+ // Set the column names for use by this class. This only comprises
+ // names used by the MySqlHostExchange class. Derived classes will
+ // need to set names for the columns they use.
+ columns_[HOST_ID_COL] = "host_id";
+ columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
+ columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
+ columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
+ columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
+ columns_[IPV4_ADDRESS_COL] = "ipv4_address";
+ columns_[HOSTNAME_COL] = "hostname";
+ columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
+ columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
+ columns_[USER_CONTEXT_COL] = "user_context";
+ columns_[DHCP4_NEXT_SERVER_COL] = "dhcp4_next_server";
+ columns_[DHCP4_SERVER_HOSTNAME_COL] = "dhcp4_server_hostname";
+ columns_[DHCP4_BOOT_FILE_NAME_COL] = "dhcp4_boot_file_name";
+ columns_[AUTH_KEY_COL] = "auth_key";
+
+ BOOST_STATIC_ASSERT(13 < HOST_COLUMNS);
+ };
+
+ /// @brief Virtual destructor.
+ virtual ~MySqlHostExchange() {
+ }
+
+ /// @brief Returns index of the first uninitialized column name.
+ ///
+ /// This method is called by the derived classes to determine which
+ /// column indexes are available for the derived classes within a
+ /// binding array, error array and column names. This method
+ /// determines the first available index by searching the first
+ /// empty value within the columns_ vector. Previously we relied on
+ /// the fixed values set for each class, but this was hard to maintain
+ /// when new columns were added to the SELECT queries. It required
+ /// modifying indexes in all derived classes.
+ ///
+ /// Derived classes must call this method in their constructors and
+ /// use returned value as an index for the first column used by the
+ /// derived class and increment this value for each subsequent column.
+ size_t findAvailColumn() const {
+ std::vector<std::string>::const_iterator empty_column =
+ std::find(columns_.begin(), columns_.end(), std::string());
+ return (std::distance(columns_.begin(), empty_column));
+ }
+
+ /// @brief Returns value of host id.
+ ///
+ /// This method is used by derived classes.
+ uint64_t getHostId() const {
+ return (host_id_);
+ };
+
+ /// @brief Set error indicators
+ ///
+ /// Sets the error indicator for each of the MYSQL_BIND elements. It points
+ /// the "error" field within an element of the input array to the
+ /// corresponding element of the passed error array.
+ ///
+ /// @param bind Array of BIND elements
+ /// @param error Array of error elements. If there is an error in getting
+ /// data associated with one of the "bind" elements, the
+ /// corresponding element in the error array is set to MLM_TRUE.
+ static void setErrorIndicators(std::vector<MYSQL_BIND>& bind,
+ std::vector<my_bools>& error) {
+ for (size_t i = 0; i < error.size(); ++i) {
+ error[i] = MLM_FALSE;
+ bind[i].error = reinterpret_cast<my_bool*>(&error[i]);
+ }
+ };
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @param error Array of error elements. An element is set to MLM_TRUE
+ /// if the corresponding column in the database is the source of
+ /// the error.
+ /// @param names Array of column names, the same size as the error array.
+ /// @param count Size of each of the arrays.
+ static std::string getColumnsInError(std::vector<my_bools>& error,
+ const std::vector<std::string>& names) {
+ std::string result = "";
+
+ // Accumulate list of column names
+ for (size_t i = 0; i < names.size(); ++i) {
+ if (error[i] == MLM_TRUE) {
+ if (!result.empty()) {
+ result += ", ";
+ }
+ result += names[i];
+ }
+ }
+
+ if (result.empty()) {
+ result = "(None)";
+ }
+
+ return (result);
+ };
+
+ /// @brief Create MYSQL_BIND objects for Host Pointer
+ ///
+ /// Fills in the MYSQL_BIND array for sending data stored in the Host object
+ /// to the database.
+ ///
+ /// @param host Host object to be added to the database.
+ /// None of the fields in the host reservation are modified -
+ /// the host data is only read.
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
+ ///
+ /// @return Vector of MySQL BIND objects representing the data to be added.
+ std::vector<MYSQL_BIND> createBindForSend(const HostPtr& host, const bool unique_ip) {
+ // Store host object to ensure it remains valid.
+ host_ = host;
+
+ // Initialize prior to constructing the array of MYSQL_BIND structures.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach.
+ memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
+
+ // Set up the structures for the various components of the host structure.
+
+ try {
+ // host_id : INT UNSIGNED NOT NULL
+ // The host_id is auto_incremented by MySQL database,
+ // so we need to pass the NULL value
+ host_id_ = 0;
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[0].is_unsigned = MLM_TRUE;
+
+ // dhcp_identifier : VARBINARY(255) NOT NULL
+ dhcp_identifier_length_ = host->getIdentifier().size();
+ memcpy(static_cast<void*>(dhcp_identifier_buffer_),
+ &(host->getIdentifier())[0],
+ host->getIdentifier().size());
+
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = dhcp_identifier_buffer_;
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+
+ // dhcp_identifier_type : TINYINT NOT NULL
+ dhcp_identifier_type_ = static_cast<uint8_t>(host->getIdentifierType());
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ bind_[2].is_unsigned = MLM_TRUE;
+
+ // dhcp4_subnet_id : INT UNSIGNED NULL
+ // Can't take an address of intermediate object, so let's store it
+ // in dhcp4_subnet_id_
+ dhcp4_subnet_id_ = host->getIPv4SubnetID();
+ dhcp4_subnet_id_null_ = host->getIPv4SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
+ bind_[3].is_unsigned = MLM_TRUE;
+ bind_[3].is_null = &dhcp4_subnet_id_null_;
+
+ // dhcp6_subnet_id : INT UNSIGNED NULL
+ // Can't take an address of intermediate object, so let's store it
+ // in dhcp6_subnet_id_
+ dhcp6_subnet_id_ = host->getIPv6SubnetID();
+ dhcp6_subnet_id_null_ = host->getIPv6SubnetID() == SUBNET_ID_UNUSED ? MLM_TRUE : MLM_FALSE;
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+ bind_[4].is_null = &dhcp6_subnet_id_null_;
+
+ // ipv4_address : INT UNSIGNED NULL
+ // The address in the Host structure is an IOAddress object. Convert
+ // this to an integer for storage.
+ ipv4_address_ = host->getIPv4Reservation().toUint32();
+ ipv4_address_null_ = ipv4_address_ == 0 ? MLM_TRUE : MLM_FALSE;
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ bind_[5].is_null = &ipv4_address_null_;
+
+ // hostname : VARCHAR(255) NULL
+ strncpy(hostname_, host->getHostname().c_str(), HOSTNAME_MAX_LEN - 1);
+ hostname_length_ = host->getHostname().length();
+ bind_[6].buffer_type = MYSQL_TYPE_STRING;
+ bind_[6].buffer = reinterpret_cast<char*>(hostname_);
+ bind_[6].buffer_length = hostname_length_;
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ bind_[7].buffer_type = MYSQL_TYPE_STRING;
+ // Override default separator to not include space after comma.
+ string classes4_txt = host->getClientClasses4().toText(",");
+ strncpy(dhcp4_client_classes_, classes4_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
+ bind_[7].buffer = dhcp4_client_classes_;
+ bind_[7].buffer_length = classes4_txt.length();
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ // Override default separator to not include space after comma.
+ string classes6_txt = host->getClientClasses6().toText(",");
+ strncpy(dhcp6_client_classes_, classes6_txt.c_str(), CLIENT_CLASSES_MAX_LEN - 1);
+ bind_[8].buffer = dhcp6_client_classes_;
+ bind_[8].buffer_length = classes6_txt.length();
+
+ // user_context : TEXT NULL
+ ConstElementPtr ctx = host->getContext();
+ if (ctx) {
+ bind_[9].buffer_type = MYSQL_TYPE_STRING;
+ string ctx_txt = ctx->str();
+ strncpy(user_context_, ctx_txt.c_str(), USER_CONTEXT_MAX_LEN - 1);
+ bind_[9].buffer = user_context_;
+ bind_[9].buffer_length = ctx_txt.length();
+ } else {
+ bind_[9].buffer_type = MYSQL_TYPE_NULL;
+ }
+
+ // ipv4_address : INT UNSIGNED NULL
+ // The address in the Host structure is an IOAddress object. Convert
+ // this to an integer for storage.
+ dhcp4_next_server_ = host->getNextServer().toUint32();
+ bind_[10].buffer_type = MYSQL_TYPE_LONG;
+ bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
+ bind_[10].is_unsigned = MLM_TRUE;
+ // bind_[10].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // dhcp4_server_hostname
+ bind_[11].buffer_type = MYSQL_TYPE_STRING;
+ std::string server_hostname = host->getServerHostname();
+ strncpy(dhcp4_server_hostname_, server_hostname.c_str(),
+ SERVER_HOSTNAME_MAX_LEN - 1);
+ bind_[11].buffer = dhcp4_server_hostname_;
+ bind_[11].buffer_length = server_hostname.length();
+
+ // dhcp4_boot_file_name
+ bind_[12].buffer_type = MYSQL_TYPE_STRING;
+ std::string boot_file_name = host->getBootFileName();
+ strncpy(dhcp4_boot_file_name_, boot_file_name.c_str(),
+ BOOT_FILE_NAME_MAX_LEN - 1);
+ bind_[12].buffer = dhcp4_boot_file_name_;
+ bind_[12].buffer_length = boot_file_name.length();
+
+ // auth key
+ bind_[13].buffer_type = MYSQL_TYPE_STRING;
+ std::string auth_key = host->getKey().toText();
+ std::strncpy(auth_key_, auth_key.c_str(), TEXT_AUTH_KEY_LEN - 1);
+ auth_key_null_ = auth_key.empty() ? MLM_TRUE : MLM_FALSE;
+ bind_[13].buffer = auth_key_;
+ bind_[13].buffer_length = auth_key.length();
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from Host: "
+ << host->getHostname() << ", reason: " << ex.what());
+ }
+
+ // Add the data to the vector.
+ std::vector<MYSQL_BIND> vec(bind_.begin(), bind_.begin() + HOST_COLUMNS);
+
+ // When checking whether the IP is unique we need to bind the IPv4 address
+ // at the end of the query as it has additional binding for the IPv4
+ // address.
+ if (unique_ip) {
+ vec.push_back(bind_[5]); // ipv4_address
+ vec.push_back(bind_[3]); // subnet_id
+ }
+ return (vec);
+ };
+
+ /// @brief Create BIND array to receive Host data.
+ ///
+ /// Creates a MYSQL_BIND array to receive Host data from the database.
+ /// After data is successfully received, @ref retrieveHost can be called
+ /// to retrieve the Host object.
+ ///
+ /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
+ virtual std::vector<MYSQL_BIND> createBindForReceive() {
+ // Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ // This also takes care of setting bind_[X].is_null to MLM_FALSE.
+ memset(&bind_[0], 0, sizeof(MYSQL_BIND) * bind_.size());
+
+ // host_id : INT UNSIGNED NOT NULL
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[0].is_unsigned = MLM_TRUE;
+
+ // dhcp_identifier : VARBINARY(255) NOT NULL
+ dhcp_identifier_length_ = sizeof(dhcp_identifier_buffer_);
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(dhcp_identifier_buffer_);
+ bind_[1].buffer_length = dhcp_identifier_length_;
+ bind_[1].length = &dhcp_identifier_length_;
+
+ // dhcp_identifier_type : TINYINT NOT NULL
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&dhcp_identifier_type_);
+ bind_[2].is_unsigned = MLM_TRUE;
+
+ // dhcp4_subnet_id : INT UNSIGNED NULL
+ dhcp4_subnet_id_null_ = MLM_FALSE;
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&dhcp4_subnet_id_);
+ bind_[3].is_unsigned = MLM_TRUE;
+ bind_[3].is_null = &dhcp4_subnet_id_null_;
+
+ // dhcp6_subnet_id : INT UNSIGNED NULL
+ dhcp6_subnet_id_null_ = MLM_FALSE;
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&dhcp6_subnet_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+ bind_[4].is_null = &dhcp6_subnet_id_null_;
+
+ // ipv4_address : INT UNSIGNED NULL
+ ipv4_address_null_ = MLM_FALSE;
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&ipv4_address_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ bind_[5].is_null = &ipv4_address_null_;
+
+ // hostname : VARCHAR(255) NULL
+ hostname_null_ = MLM_FALSE;
+ hostname_length_ = sizeof(hostname_);
+ bind_[6].buffer_type = MYSQL_TYPE_STRING;
+ bind_[6].buffer = reinterpret_cast<char*>(hostname_);
+ bind_[6].buffer_length = hostname_length_;
+ bind_[6].length = &hostname_length_;
+ bind_[6].is_null = &hostname_null_;
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ dhcp4_client_classes_null_ = MLM_FALSE;
+ dhcp4_client_classes_length_ = sizeof(dhcp4_client_classes_);
+ bind_[7].buffer_type = MYSQL_TYPE_STRING;
+ bind_[7].buffer = reinterpret_cast<char*>(dhcp4_client_classes_);
+ bind_[7].buffer_length = dhcp4_client_classes_length_;
+ bind_[7].length = &dhcp4_client_classes_length_;
+ bind_[7].is_null = &dhcp4_client_classes_null_;
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ dhcp6_client_classes_null_ = MLM_FALSE;
+ dhcp6_client_classes_length_ = sizeof(dhcp6_client_classes_);
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ bind_[8].buffer = reinterpret_cast<char*>(dhcp6_client_classes_);
+ bind_[8].buffer_length = dhcp6_client_classes_length_;
+ bind_[8].length = &dhcp6_client_classes_length_;
+ bind_[8].is_null = &dhcp6_client_classes_null_;
+
+ // user_context : TEXT NULL
+ user_context_null_ = MLM_FALSE;
+ user_context_length_ = sizeof(user_context_);
+ bind_[9].buffer_type = MYSQL_TYPE_STRING;
+ bind_[9].buffer = reinterpret_cast<char*>(user_context_);
+ bind_[9].buffer_length = user_context_length_;
+ bind_[9].length = &user_context_length_;
+ bind_[9].is_null = &user_context_null_;
+
+ // dhcp4_next_server
+ dhcp4_next_server_null_ = MLM_FALSE;
+ bind_[10].buffer_type = MYSQL_TYPE_LONG;
+ bind_[10].buffer = reinterpret_cast<char*>(&dhcp4_next_server_);
+ bind_[10].is_unsigned = MLM_TRUE;
+ bind_[10].is_null = &dhcp4_next_server_null_;
+
+ // dhcp4_server_hostname
+ dhcp4_server_hostname_null_ = MLM_FALSE;
+ dhcp4_server_hostname_length_ = sizeof(dhcp4_server_hostname_);
+ bind_[11].buffer_type = MYSQL_TYPE_STRING;
+ bind_[11].buffer = reinterpret_cast<char*>(dhcp4_server_hostname_);
+ bind_[11].buffer_length = dhcp4_server_hostname_length_;
+ bind_[11].length = &dhcp4_server_hostname_length_;
+ bind_[11].is_null = &dhcp4_server_hostname_null_;
+
+ // dhcp4_boot_file_name
+ dhcp4_boot_file_name_null_ = MLM_FALSE;
+ dhcp4_boot_file_name_length_ = sizeof(dhcp4_boot_file_name_);
+ bind_[12].buffer_type = MYSQL_TYPE_STRING;
+ bind_[12].buffer = reinterpret_cast<char*>(dhcp4_boot_file_name_);
+ bind_[12].buffer_length = dhcp4_boot_file_name_length_;
+ bind_[12].length = &dhcp4_boot_file_name_length_;
+ bind_[12].is_null = &dhcp4_boot_file_name_null_;
+
+ // auth_key_
+ auth_key_null_ = MLM_FALSE;
+ auth_key_length_ = sizeof(auth_key_);
+ bind_[13].buffer_type = MYSQL_TYPE_STRING;
+ bind_[13].buffer = reinterpret_cast<char*>(auth_key_);
+ bind_[13].buffer_length = auth_key_length_;
+ bind_[13].length = &auth_key_length_;
+ bind_[13].is_null = &auth_key_null_;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_);
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (bind_);
+ };
+
+ /// @brief Copy received data into Host object
+ ///
+ /// This function copies information about the host into a newly created
+ /// @ref Host object. This method is called after @ref createBindForReceive.
+ /// has been used.
+ ///
+ /// @return Host Pointer to a @ref HostPtr object holding a pointer to the
+ /// @ref Host object returned.
+ HostPtr retrieveHost() {
+ // Check if the identifier stored in the database is correct.
+ if (dhcp_identifier_type_ > MAX_IDENTIFIER_TYPE) {
+ isc_throw(BadValue, "invalid dhcp identifier type returned: "
+ << static_cast<int>(dhcp_identifier_type_));
+ }
+ // Set the dhcp identifier type in a variable of the appropriate
+ // data type.
+ Host::IdentifierType type =
+ static_cast<Host::IdentifierType>(dhcp_identifier_type_);
+
+ // Set DHCPv4 subnet ID to the value returned. If NULL returned,
+ // set to 0.
+ SubnetID ipv4_subnet_id(SUBNET_ID_UNUSED);
+ if (dhcp4_subnet_id_null_ == MLM_FALSE) {
+ ipv4_subnet_id = static_cast<SubnetID>(dhcp4_subnet_id_);
+ }
+
+ // Set DHCPv6 subnet ID to the value returned. If NULL returned,
+ // set to 0.
+ SubnetID ipv6_subnet_id(SUBNET_ID_UNUSED);
+ if (dhcp6_subnet_id_null_ == MLM_FALSE) {
+ ipv6_subnet_id = static_cast<SubnetID>(dhcp6_subnet_id_);
+ }
+
+ // Set IPv4 address reservation if it was given, if not, set IPv4 zero
+ // address
+ asiolink::IOAddress ipv4_reservation = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+ if (ipv4_address_null_ == MLM_FALSE) {
+ ipv4_reservation = asiolink::IOAddress(ipv4_address_);
+ }
+
+ // Set hostname if non NULL value returned. Otherwise, leave an
+ // empty string.
+ std::string hostname;
+ if (hostname_null_ == MLM_FALSE) {
+ hostname = std::string(hostname_, hostname_length_);
+ }
+
+ // Set DHCPv4 client classes if non NULL value returned.
+ std::string dhcp4_client_classes;
+ if (dhcp4_client_classes_null_ == MLM_FALSE) {
+ dhcp4_client_classes = std::string(dhcp4_client_classes_,
+ dhcp4_client_classes_length_);
+ }
+
+ // Set DHCPv6 client classes if non NULL value returned.
+ std::string dhcp6_client_classes;
+ if (dhcp6_client_classes_null_ == MLM_FALSE) {
+ dhcp6_client_classes = std::string(dhcp6_client_classes_,
+ dhcp6_client_classes_length_);
+ }
+
+ // Convert user_context to string as well.
+ std::string user_context;
+ if (user_context_null_ == MLM_FALSE) {
+ user_context_[user_context_length_] = '\0';
+ user_context.assign(user_context_);
+ }
+
+ // Set next server value (siaddr) if non NULL value returned.
+ asiolink::IOAddress next_server = asiolink::IOAddress::IPV4_ZERO_ADDRESS();
+ if (dhcp4_next_server_null_ == MLM_FALSE) {
+ next_server = asiolink::IOAddress(dhcp4_next_server_);
+ }
+
+ // Set server hostname (sname) if non NULL value returned.
+ std::string dhcp4_server_hostname;
+ if (dhcp4_server_hostname_null_ == MLM_FALSE) {
+ dhcp4_server_hostname = std::string(dhcp4_server_hostname_,
+ dhcp4_server_hostname_length_);
+ }
+
+ // Set boot file name (file) if non NULL value returned.
+ std::string dhcp4_boot_file_name;
+ if (dhcp4_boot_file_name_null_ == MLM_FALSE) {
+ dhcp4_boot_file_name = std::string(dhcp4_boot_file_name_,
+ dhcp4_boot_file_name_length_);
+ }
+
+ // Set the auth key if a non empty array is retrieved
+ std::string auth_key;
+ if (auth_key_null_ == MLM_FALSE) {
+ auth_key = std::string(auth_key_, auth_key_length_);
+ }
+
+ // Create and return Host object from the data gathered.
+ HostPtr h(new Host(dhcp_identifier_buffer_, dhcp_identifier_length_,
+ type, ipv4_subnet_id, ipv6_subnet_id, ipv4_reservation,
+ hostname, dhcp4_client_classes, dhcp6_client_classes,
+ next_server, dhcp4_server_hostname,
+ dhcp4_boot_file_name, AuthKey(auth_key)));
+ h->setHostId(host_id_);
+
+ // Set the user context if there is one.
+ if (!user_context.empty()) {
+ try {
+ ConstElementPtr ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is not a JSON map");
+ }
+ h->setContext(ctx);
+ } catch (const isc::data::JSONError& ex) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is invalid JSON: " << ex.what());
+ }
+ }
+
+ return (h);
+ };
+
+ /// @brief Processes one row of data fetched from a database.
+ ///
+ /// The processed data must contain host id, which uniquely identifies a
+ /// host. This method creates a host and inserts it to the hosts collection
+ /// only if the last inserted host has a different host id. This prevents
+ /// adding duplicated hosts to the collection, assuming that processed
+ /// rows are primarily ordered by host id column.
+ ///
+ /// This method must be overridden in the derived classes to also
+ /// retrieve IPv6 reservations and DHCP options associated with a host.
+ ///
+ /// @param [out] hosts Collection of hosts to which a new host created
+ /// from the processed data should be inserted.
+ virtual void processFetchedData(ConstHostCollection& hosts) {
+ HostPtr host;
+ // Add new host only if there are no hosts yet or the host id of the
+ // most recently added host is different than the host id of the
+ // currently processed host.
+ if (hosts.empty() || (hosts.back()->getHostId() != getHostId())) {
+ // Create Host object from the fetched data and append it to the
+ // collection.
+ host = retrieveHost();
+ hosts.push_back(host);
+ }
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @return Comma-separated list of columns in error, or the string
+ /// "(None)".
+ std::string getErrorColumns() {
+ return (getColumnsInError(error_, columns_));
+ };
+
+protected:
+
+ /// Number of columns returned in queries.
+ size_t columns_num_;
+
+ /// Vector of MySQL bindings.
+ std::vector<MYSQL_BIND> bind_;
+
+ /// Column names.
+ std::vector<std::string> columns_;
+
+ /// Error array.
+ std::vector<my_bools> error_;
+
+ /// Pointer to Host object holding information to be inserted into
+ /// Hosts table.
+ HostPtr host_;
+
+private:
+
+ /// Host identifier (primary key in Hosts table).
+ uint64_t host_id_;
+
+ /// Buffer holding client's identifier (e.g. DUID, HW address)
+ /// in the binary format.
+ uint8_t dhcp_identifier_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+
+ /// Length of a data in the dhcp_identifier_buffer_.
+ unsigned long dhcp_identifier_length_;
+
+ /// Type of the identifier in the dhcp_identifier_buffer_. This
+ /// value corresponds to the @ref Host::IdentifierType value.
+ uint8_t dhcp_identifier_type_;
+
+ /// DHCPv4 subnet identifier.
+ uint32_t dhcp4_subnet_id_;
+
+ /// DHCPv6 subnet identifier.
+ uint32_t dhcp6_subnet_id_;
+
+ /// Reserved IPv4 address.
+ uint32_t ipv4_address_;
+
+ /// Name reserved for the host.
+ char hostname_[HOSTNAME_MAX_LEN];
+
+ /// Hostname length.
+ unsigned long hostname_length_;
+
+ /// A string holding comma separated list of DHCPv4 client classes.
+ char dhcp4_client_classes_[CLIENT_CLASSES_MAX_LEN];
+
+ /// A length of the string holding comma separated list of DHCPv4
+ /// client classes.
+ unsigned long dhcp4_client_classes_length_;
+
+ /// A string holding comma separated list of DHCPv6 client classes.
+ char dhcp6_client_classes_[CLIENT_CLASSES_MAX_LEN];
+
+ /// A length of the string holding comma separated list of DHCPv6
+ /// client classes.
+ unsigned long dhcp6_client_classes_length_;
+
+ /// @brief Buffer holding textual user context.
+ char user_context_[USER_CONTEXT_MAX_LEN];
+
+ /// @brief User context length.
+ unsigned long user_context_length_;
+
+ /// Next server address (siaddr).
+ uint32_t dhcp4_next_server_;
+
+ /// Server hostname (sname).
+ char dhcp4_server_hostname_[SERVER_HOSTNAME_MAX_LEN];
+
+ /// A length of the string holding server hostname.
+ unsigned long dhcp4_server_hostname_length_;
+
+ /// Boot file name (file).
+ char dhcp4_boot_file_name_[BOOT_FILE_NAME_MAX_LEN];
+
+ /// A length of the string holding boot file name.
+ unsigned long dhcp4_boot_file_name_length_;
+
+ /// Authentication keys
+ char auth_key_[TEXT_AUTH_KEY_LEN];
+
+ /// The length of the string for holding keys
+ unsigned long auth_key_length_;
+
+ /// @name Boolean values indicating if values of specific columns in
+ /// the database are NULL.
+ //@{
+ /// Boolean flag indicating if the value of the DHCPv4 subnet is NULL.
+ my_bool dhcp4_subnet_id_null_;
+
+ /// Boolean flag indicating if the value of the DHCPv6 subnet is NULL.
+ my_bool dhcp6_subnet_id_null_;
+
+ /// Boolean flag indicating if the value of IPv4 reservation is NULL.
+ my_bool ipv4_address_null_;
+
+ /// Boolean flag indicating if the value if hostname is NULL.
+ my_bool hostname_null_;
+
+ /// Boolean flag indicating if the value of DHCPv4 client classes is
+ /// NULL.
+ my_bool dhcp4_client_classes_null_;
+
+ /// Boolean flag indicating if the value of DHCPv6 client classes is
+ /// NULL.
+ my_bool dhcp6_client_classes_null_;
+
+ /// @brief Boolean flag indicating if the value of user context is NULL.
+ my_bool user_context_null_;
+
+ /// Boolean flag indicating if the value of next server is NULL.
+ my_bool dhcp4_next_server_null_;
+
+ /// Boolean flag indicating if the value of server hostname is NULL.
+ my_bool dhcp4_server_hostname_null_;
+
+ /// Boolean flag indicating if the value of boot file name is NULL.
+ my_bool dhcp4_boot_file_name_null_;
+
+ /// Boolean flag indicating if the value of string is NULL.
+ my_bool auth_key_null_;
+
+ //@}
+};
+
+/// @brief Extends base exchange class with ability to retrieve DHCP options
+/// from the 'dhcp4_options' and 'dhcp6_options' tables.
+///
+/// This class provides means to retrieve both DHCPv4 and DHCPv6 options
+/// along with the host information. It is not used to retrieve IPv6
+/// reservations. The following types of queries are supported:
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
+class MySqlHostWithOptionsExchange : public MySqlHostExchange {
+private:
+
+ /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
+ static const size_t OPTION_COLUMNS = 8;
+
+ /// @brief Receives DHCPv4 or DHCPv6 options information from the
+ /// dhcp4_options or dhcp6_options tables respectively.
+ ///
+ /// The MySqlHostWithOptionsExchange class holds two respective instances
+ /// of this class, one for receiving DHCPv4 options, one for receiving
+ /// DHCPv6 options.
+ ///
+ /// The following are the basic functions of this class:
+ /// - bind class members to specific columns in MySQL binding tables,
+ /// - set DHCP options specific column names,
+ /// - create instances of options retrieved from the database.
+ ///
+ /// The reason for isolating those functions in a separate C++ class is
+ /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
+ class OptionProcessor {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param universe V4 or V6. The type of the options' instances
+ /// created by this class depends on this parameter.
+ /// @param start_column Index of the first column to be used by this
+ /// class.
+ OptionProcessor(const Option::Universe& universe,
+ const size_t start_column)
+ : universe_(universe), start_column_(start_column), option_id_(0),
+ code_(0), value_length_(0), formatted_value_length_(0),
+ space_length_(0), persistent_(false), cancelled_(false),
+ user_context_length_(0),
+ option_id_null_(MLM_FALSE), code_null_(MLM_FALSE),
+ value_null_(MLM_FALSE), formatted_value_null_(MLM_FALSE),
+ space_null_(MLM_FALSE), user_context_null_(MLM_FALSE),
+ option_id_index_(start_column), code_index_(start_column_ + 1),
+ value_index_(start_column_ + 2),
+ formatted_value_index_(start_column_ + 3),
+ space_index_(start_column_ + 4),
+ persistent_index_(start_column_ + 5),
+ cancelled_index_(start_column_ + 6),
+ user_context_index_(start_column_ + 7),
+ most_recent_option_id_(0) {
+
+ memset(value_, 0, sizeof(value_));
+ memset(formatted_value_, 0, sizeof(formatted_value_));
+ memset(space_, 0, sizeof(space_));
+ memset(user_context_, 0, sizeof(user_context_));
+ }
+
+ /// @brief Returns identifier of the currently processed option.
+ uint64_t getOptionId() const {
+ if (option_id_null_ == MLM_FALSE) {
+ return (option_id_);
+ }
+ return (0);
+ }
+
+ /// @brief Creates instance of the currently processed option.
+ ///
+ /// This method detects if the currently processed option is a new
+ /// instance. It makes it determination by comparing the identifier
+ /// of the currently processed option, with the most recently processed
+ /// option. If the current value is greater than the id of the recently
+ /// processed option it is assumed that the processed row holds new
+ /// option information. In such case the option instance is created and
+ /// inserted into the configuration passed as argument.
+ ///
+ /// @param cfg Pointer to the configuration object into which new
+ /// option instances should be inserted.
+ void retrieveOption(const CfgOptionPtr& cfg) {
+ // option_id may be NULL if dhcp4_options or dhcp6_options table
+ // doesn't contain any options for the particular host. Also, the
+ // current option id must be greater than id if the most recent
+ // option because options are ordered by option id. Otherwise
+ // we assume that this is already processed option.
+ if ((option_id_null_ == MLM_TRUE) ||
+ (most_recent_option_id_ >= option_id_)) {
+ return;
+ }
+
+ // Remember current option id as the most recent processed one. We
+ // will be comparing it with option ids in subsequent rows.
+ most_recent_option_id_ = option_id_;
+
+ // Convert it to string object for easier comparison.
+ std::string space;
+ if (space_null_ == MLM_FALSE) {
+ // Typically, the string values returned by the database are not
+ // NULL terminated.
+ space_[space_length_] = '\0';
+ space.assign(space_);
+ }
+
+ // If empty or null space provided, use a default top level space.
+ if (space.empty()) {
+ space = (universe_ == Option::V4 ?
+ DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
+ }
+
+ // Convert formatted_value to string as well.
+ std::string formatted_value;
+ if (formatted_value_null_ == MLM_FALSE) {
+ formatted_value_[formatted_value_length_] = '\0';
+ formatted_value.assign(formatted_value_);
+ }
+
+ // Convert user_context to string as well.
+ std::string user_context;
+ if (user_context_null_ == MLM_FALSE) {
+ user_context_[user_context_length_] = '\0';
+ user_context.assign(user_context_);
+ }
+
+ // Options are held in a binary or textual format in the database.
+ // This is similar to having an option specified in a server
+ // configuration file. Such option is converted to appropriate C++
+ // class, using option definition. Thus, we need to find the
+ // option definition for this option code and option space.
+
+ // Check if this is a standard option.
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code_);
+
+ // Otherwise, we may check if this an option encapsulated within the
+ // vendor space.
+ if (!def && (space != DHCP4_OPTION_SPACE) &&
+ (space != DHCP6_OPTION_SPACE)) {
+ uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
+ if (vendor_id > 0) {
+ def = LibDHCP::getVendorOptionDef(universe_, vendor_id, code_);
+ }
+ }
+
+ // In all other cases, we use runtime option definitions, which
+ // should be also registered within the libdhcp++.
+ if (!def) {
+ def = LibDHCP::getRuntimeOptionDef(space, code_);
+ }
+
+ // Finish with a last resort option definition.
+ if (!def) {
+ def = LibDHCP::getLastResortOptionDef(space, code_);
+ }
+
+ OptionPtr option;
+
+ // If no definition found, we use generic option type.
+ if (!def) {
+ // We have to pay attention if the value is NULL. If it is,
+ // we must create an empty option instance. We can't rely on
+ // the value_length_ because it may contain garbage for the
+ // null values. Thus we check explicitly whether or not the
+ // NULL flag is set.
+ if (value_null_ == MLM_FALSE) {
+ OptionBuffer buf(value_, value_ + value_length_);
+ option.reset(new Option(universe_, code_, buf.begin(),
+ buf.end()));
+ } else {
+ option.reset(new Option(universe_, code_));
+ }
+ } else {
+ // The option value may be specified in textual or binary format
+ // in the database. If formatted_value is empty, the binary
+ // format is used. Depending on the format we use a different
+ // variant of the optionFactory function.
+ if (formatted_value.empty()) {
+ OptionBuffer buf(value_, value_ + value_length_);
+ // Again, check if the value is null before submitting the
+ // buffer to the factory function.
+ option = def->optionFactory(universe_, code_, buf.begin(),
+ value_null_ == MLM_FALSE ? buf.end() :
+ buf.begin());
+ } else {
+ // Spit the value specified in comma separated values
+ // format.
+ std::vector<std::string> split_vec;
+ boost::split(split_vec, formatted_value, boost::is_any_of(","));
+ option = def->optionFactory(universe_, code_, split_vec);
+ }
+ }
+
+ OptionDescriptor desc(option, persistent_, cancelled_,
+ formatted_value);
+
+ // Set the user context if there is one into the option descriptor.
+ if (!user_context.empty()) {
+ try {
+ ConstElementPtr ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is no a JSON map");
+ }
+ desc.setContext(ctx);
+ } catch (const isc::data::JSONError& ex) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is invalid JSON: " << ex.what());
+ }
+ }
+ cfg->add(desc, space);
+ }
+
+ /// @brief Specify column names.
+ ///
+ /// @param [out] columns Reference to a vector holding names of option
+ /// specific columns.
+ void setColumnNames(std::vector<std::string>& columns) {
+ columns[option_id_index_] = "option_id";
+ columns[code_index_] = "code";
+ columns[value_index_] = "value";
+ columns[formatted_value_index_] = "formatted_value";
+ columns[space_index_] = "space";
+ columns[persistent_index_] = "persistent";
+ columns[cancelled_index_] = "cancelled";
+ columns[user_context_index_] = "user_context";
+ }
+
+ /// @brief Initialize binding table fields for options.
+ ///
+ /// Resets most_recent_option_id_ to 0 and other exchange members to
+ /// default values.
+ ///
+ /// @param [out] bind Binding table.
+ void setBindFields(std::vector<MYSQL_BIND>& bind) {
+ // This method is called just before making a new query, so we
+ // reset the most_recent_option_id_ and other exchange members to
+ // start over with options processing.
+ most_recent_option_id_ = 0;
+
+ option_id_ = 0;
+ code_ = 0;
+ persistent_ = false;
+ cancelled_ = false;
+ option_id_null_ = MLM_FALSE;
+ code_null_ = MLM_FALSE;
+ value_null_ = MLM_FALSE;
+ formatted_value_null_ = MLM_FALSE;
+ space_null_ = MLM_FALSE;
+ user_context_null_ = MLM_FALSE;
+
+ memset(value_, 0, sizeof(value_));
+ memset(formatted_value_, 0, sizeof(formatted_value_));
+ memset(space_, 0, sizeof(space_));
+ memset(user_context_, 0, sizeof(user_context_));
+
+ // option_id : INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ bind[option_id_index_].buffer_type = MYSQL_TYPE_LONG;
+ bind[option_id_index_].buffer = reinterpret_cast<char*>(&option_id_);
+ bind[option_id_index_].is_unsigned = MLM_TRUE;
+ bind[option_id_index_].is_null = &option_id_null_;
+
+ // code : TINYINT OR SHORT UNSIGNED NOT NULL
+ bind[code_index_].buffer_type = MYSQL_TYPE_SHORT;
+ bind[code_index_].buffer = reinterpret_cast<char*>(&code_);
+ bind[code_index_].is_unsigned = MLM_TRUE;
+ bind[code_index_].is_null = &code_null_;
+
+ // value : BLOB NULL
+ value_length_ = sizeof(value_);
+ bind[value_index_].buffer_type = MYSQL_TYPE_BLOB;
+ bind[value_index_].buffer = reinterpret_cast<char*>(value_);
+ bind[value_index_].buffer_length = value_length_;
+ bind[value_index_].length = &value_length_;
+ bind[value_index_].is_null = &value_null_;
+
+ // formatted_value : TEXT NULL
+ formatted_value_length_ = sizeof(formatted_value_);
+ bind[formatted_value_index_].buffer_type = MYSQL_TYPE_STRING;
+ bind[formatted_value_index_].buffer = reinterpret_cast<char*>(formatted_value_);
+ bind[formatted_value_index_].buffer_length = formatted_value_length_;
+ bind[formatted_value_index_].length = &formatted_value_length_;
+ bind[formatted_value_index_].is_null = &formatted_value_null_;
+
+ // space : VARCHAR(128) NULL
+ space_length_ = sizeof(space_);
+ bind[space_index_].buffer_type = MYSQL_TYPE_STRING;
+ bind[space_index_].buffer = reinterpret_cast<char*>(space_);
+ bind[space_index_].buffer_length = space_length_;
+ bind[space_index_].length = &space_length_;
+ bind[space_index_].is_null = &space_null_;
+
+ // persistent : TINYINT(1) NOT NULL DEFAULT 0
+ bind[persistent_index_].buffer_type = MYSQL_TYPE_TINY;
+ bind[persistent_index_].buffer = reinterpret_cast<char*>(&persistent_);
+ bind[persistent_index_].is_unsigned = MLM_TRUE;
+
+ // cancelled : TINYINT(1) NOT NULL DEFAULT 0
+ bind[cancelled_index_].buffer_type = MYSQL_TYPE_TINY;
+ bind[cancelled_index_].buffer = reinterpret_cast<char*>(&cancelled_);
+ bind[cancelled_index_].is_unsigned = MLM_TRUE;
+
+ // user_context : TEXT NULL
+ user_context_length_ = sizeof(user_context_);
+ bind[user_context_index_].buffer_type = MYSQL_TYPE_STRING;
+ bind[user_context_index_].buffer = reinterpret_cast<char*>(user_context_);
+ bind[user_context_index_].buffer_length = user_context_length_;
+ bind[user_context_index_].length = &user_context_length_;
+ bind[user_context_index_].is_null = &user_context_null_;
+ }
+
+ private:
+
+ /// @brief Universe: V4 or V6.
+ Option::Universe universe_;
+
+ /// @brief Index of first column used by this class.
+ size_t start_column_;
+
+ /// @brief Option id.
+ uint32_t option_id_;
+
+ /// @brief Option code.
+ uint16_t code_;
+
+ /// @brief Buffer holding binary value of an option.
+ uint8_t value_[OPTION_VALUE_MAX_LEN];
+
+ /// @brief Option value length.
+ unsigned long value_length_;
+
+ /// @brief Buffer holding textual value of an option.
+ char formatted_value_[OPTION_FORMATTED_VALUE_MAX_LEN];
+
+ /// @brief Formatted option value length.
+ unsigned long formatted_value_length_;
+
+ /// @brief Buffer holding option space name.
+ char space_[OPTION_SPACE_MAX_LEN];
+
+ /// @brief Option space length.
+ unsigned long space_length_;
+
+ /// @brief Flag indicating if option is always sent or only if
+ /// requested.
+ bool persistent_;
+
+ /// @brief Flag indicating if option must be never sent.
+ bool cancelled_;
+
+ /// @brief Buffer holding textual user context of an option.
+ char user_context_[USER_CONTEXT_MAX_LEN];
+
+ /// @brief User context length.
+ unsigned long user_context_length_;
+
+ /// @name Boolean values indicating if values of specific columns in
+ /// the database are NULL.
+ //@{
+ /// @brief Boolean flag indicating if the DHCPv4 option id is NULL.
+ my_bool option_id_null_;
+
+ /// @brief Boolean flag indicating if the DHCPv4 option code is NULL.
+ my_bool code_null_;
+
+ /// @brief Boolean flag indicating if the DHCPv4 option value is NULL.
+ my_bool value_null_;
+
+ /// @brief Boolean flag indicating if the DHCPv4 formatted option value
+ /// is NULL.
+ my_bool formatted_value_null_;
+
+ /// @brief Boolean flag indicating if the DHCPv4 option space is NULL.
+ my_bool space_null_;
+
+ /// @brief Boolean flag indicating if the DHCPv4 option user context is NULL.
+ my_bool user_context_null_;
+ //@}
+
+ /// @name Indexes of the specific columns
+ //@{
+ /// @brief Option id
+ size_t option_id_index_;
+
+ /// @brief Code
+ size_t code_index_;
+
+ /// @brief Value
+ size_t value_index_;
+
+ /// @brief Formatted value
+ size_t formatted_value_index_;
+
+ /// @brief Space
+ size_t space_index_;
+
+ /// @brief Persistent
+ size_t persistent_index_;
+
+ /// @brief Cancelled
+ size_t cancelled_index_;
+ //@}
+
+ /// @brief User context
+ size_t user_context_index_;
+
+ /// @brief Option id for last processed row.
+ uint32_t most_recent_option_id_;
+ };
+
+ /// @brief Pointer to the @ref OptionProcessor class.
+ typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
+
+public:
+
+ /// @brief DHCP option types to be fetched from the database.
+ ///
+ /// Supported types are:
+ /// - Only DHCPv4 options,
+ /// - Only DHCPv6 options,
+ /// - Both DHCPv4 and DHCPv6 options.
+ enum FetchedOptions {
+ DHCP4_ONLY,
+ DHCP6_ONLY,
+ DHCP4_AND_DHCP6
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
+ /// be fetched from the database for a host.
+ /// @param additional_columns_num Number of additional columns for which
+ /// resources should be allocated, e.g. binding table, column names etc.
+ /// This parameter should be set to a non zero value by derived classes to
+ /// allocate resources for the columns supported by derived classes.
+ MySqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
+ const size_t additional_columns_num = 0)
+ : MySqlHostExchange(getRequiredColumnsNum(fetched_options)
+ + additional_columns_num),
+ opt_proc4_(), opt_proc6_() {
+
+ // Create option processor for DHCPv4 options, if required.
+ if ((fetched_options == DHCP4_ONLY) ||
+ (fetched_options == DHCP4_AND_DHCP6)) {
+ opt_proc4_.reset(new OptionProcessor(Option::V4,
+ findAvailColumn()));
+ opt_proc4_->setColumnNames(columns_);
+ }
+
+ // Create option processor for DHCPv6 options, if required.
+ if ((fetched_options == DHCP6_ONLY) ||
+ (fetched_options == DHCP4_AND_DHCP6)) {
+ opt_proc6_.reset(new OptionProcessor(Option::V6,
+ findAvailColumn()));
+ opt_proc6_->setColumnNames(columns_);
+ }
+ }
+
+ /// @brief Processes the current row.
+ ///
+ /// The processed row includes both host information and DHCP option
+ /// information. Because used SELECT query use LEFT JOIN clause, the
+ /// some rows contain duplicated host or options entries. This method
+ /// detects duplicated information and discards such entries.
+ ///
+ /// @param [out] hosts Container holding parsed hosts and options.
+ virtual void processFetchedData(ConstHostCollection& hosts) {
+ // Holds pointer to the previously parsed host.
+ HostPtr most_recent_host;
+ if (!hosts.empty()) {
+ // Const cast is not very elegant way to deal with it, but
+ // there is a good reason to use it here. This method is called
+ // to build a collection of const hosts to be returned to the
+ // caller. If we wanted to use non-const collection we'd need
+ // to copy the whole collection before returning it, which has
+ // performance implications. Alternatively, we could store the
+ // most recently added host in a class member but this would
+ // make the code less readable.
+ most_recent_host = boost::const_pointer_cast<Host>(hosts.back());
+ }
+
+ // If no host has been parsed yet or we're at the row holding next
+ // host, we create a new host object and put it at the end of the
+ // list.
+ if (!most_recent_host || (most_recent_host->getHostId() < getHostId())) {
+ HostPtr host = retrieveHost();
+ hosts.push_back(host);
+ most_recent_host = host;
+ }
+
+ // Parse DHCPv4 options if required to do so.
+ if (opt_proc4_) {
+ CfgOptionPtr cfg = most_recent_host->getCfgOption4();
+ opt_proc4_->retrieveOption(cfg);
+ }
+
+ // Parse DHCPv6 options if required to do so.
+ if (opt_proc6_) {
+ CfgOptionPtr cfg = most_recent_host->getCfgOption6();
+ opt_proc6_->retrieveOption(cfg);
+ }
+ }
+
+ /// @brief Bind variables for receiving option data.
+ ///
+ /// @return Vector of MYSQL_BIND object representing data to be retrieved.
+ virtual std::vector<MYSQL_BIND> createBindForReceive() {
+ // The following call sets bind_ values between 0 and 8.
+ static_cast<void>(MySqlHostExchange::createBindForReceive());
+
+ // Bind variables for DHCPv4 options.
+ if (opt_proc4_) {
+ opt_proc4_->setBindFields(bind_);
+ }
+
+ // Bind variables for DHCPv6 options.
+ if (opt_proc6_) {
+ opt_proc6_->setBindFields(bind_);
+ }
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_);
+
+ return (bind_);
+ };
+
+private:
+
+ /// @brief Returns a number of columns required to retrieve option data.
+ ///
+ /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
+ /// DHCPv6 a different number of columns is required in the binding array.
+ /// This method returns the number of required columns, according to the
+ /// value of @c fetched_columns passed in the constructor.
+ ///
+ /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
+ /// both types of options should be retrieved.
+ ///
+ /// @return Number of required columns.
+ static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
+ return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
+ OPTION_COLUMNS);
+ }
+
+ /// @brief Pointer to DHCPv4 options processor.
+ ///
+ /// If this object is NULL, the DHCPv4 options are not fetched.
+ OptionProcessorPtr opt_proc4_;
+
+ /// @brief Pointer to DHCPv6 options processor.
+ ///
+ /// If this object is NULL, the DHCPv6 options are not fetched.
+ OptionProcessorPtr opt_proc6_;
+};
+
+/// @brief This class provides mechanisms for sending and retrieving
+/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
+///
+/// This class extends the @ref MySqlHostWithOptionsExchange class with the
+/// mechanisms to retrieve IPv6 reservations. This class is used in situations
+/// when it is desired to retrieve DHCPv6 specific information about the host
+/// (DHCPv6 options and reservations), or entire information about the host
+/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
+/// queries used with this class:
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
+/// LEFT JOIN ipv6_reservations ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
+class MySqlHostIPv6Exchange : public MySqlHostWithOptionsExchange {
+private:
+
+ /// @brief Number of columns holding IPv6 reservation information.
+ static const size_t RESERVATION_COLUMNS = 5;
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Apart from initializing the base class data structures it also
+ /// initializes values representing IPv6 reservation information.
+ MySqlHostIPv6Exchange(const FetchedOptions& fetched_options)
+ : MySqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
+ reservation_id_(0),
+ reserv_type_(0), reserv_type_null_(MLM_FALSE),
+ ipv6_address_buffer_len_(0), prefix_len_(0), iaid_(0),
+ reservation_id_index_(findAvailColumn()),
+ address_index_(reservation_id_index_ + 1),
+ prefix_len_index_(reservation_id_index_ + 2),
+ type_index_(reservation_id_index_ + 3),
+ iaid_index_(reservation_id_index_ + 4),
+ most_recent_reservation_id_(0) {
+
+ memset(ipv6_address_buffer_, 0, sizeof(ipv6_address_buffer_));
+
+ // Provide names of additional columns returned by the queries.
+ columns_[reservation_id_index_] = "reservation_id";
+ columns_[address_index_] = "address";
+ columns_[prefix_len_index_] = "prefix_len";
+ columns_[type_index_] = "type";
+ columns_[iaid_index_] = "dhcp6_iaid";
+ }
+
+ /// @brief Returns last fetched reservation id.
+ ///
+ /// @return Reservation id or 0 if no reservation data is fetched.
+ uint32_t getReservationId() const {
+ if (reserv_type_null_ == MLM_FALSE) {
+ return (reservation_id_);
+ }
+ return (0);
+ };
+
+ /// @brief Creates IPv6 reservation from the data contained in the
+ /// currently processed row.
+ ///
+ /// Called after the MYSQL_BIND array created by createBindForReceive().
+ ///
+ /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
+ IPv6Resrv retrieveReservation() {
+ // Set the IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
+ IPv6Resrv::Type type = IPv6Resrv::TYPE_NA;
+
+ switch (reserv_type_) {
+ case 0:
+ type = IPv6Resrv::TYPE_NA;
+ break;
+
+ case 2:
+ type = IPv6Resrv::TYPE_PD;
+ break;
+
+ default:
+ isc_throw(BadValue,
+ "invalid IPv6 reservation type returned: "
+ << static_cast<int>(reserv_type_)
+ << ". Only 0 or 2 are allowed.");
+ }
+
+ IOAddress addr6 = IOAddress::fromBytes(AF_INET6, ipv6_address_buffer_);
+ IPv6Resrv r(type, addr6, prefix_len_);
+ return (r);
+ };
+
+ /// @brief Processes one row of data fetched from a database.
+ ///
+ /// The processed data must contain host id, which uniquely identifies a
+ /// host. This method creates a host and inserts it to the hosts collection
+ /// only if the last inserted host has a different host id. This prevents
+ /// adding duplicated hosts to the collection, assuming that processed
+ /// rows are primarily ordered by host id column.
+ ///
+ /// Depending on the value of the @c fetched_options specified in the
+ /// constructor, this method also parses options returned as a result
+ /// of SELECT queries.
+ ///
+ /// For any returned row which contains IPv6 reservation information it
+ /// checks if the reservation is not a duplicate of previously parsed
+ /// reservation and appends the IPv6Resrv object into the host object
+ /// if the parsed row contains new reservation information.
+ ///
+ /// @param [out] hosts Collection of hosts to which a new host created
+ /// from the processed data should be inserted.
+ virtual void processFetchedData(ConstHostCollection& hosts) {
+
+ // Call parent class to fetch host information and options.
+ MySqlHostWithOptionsExchange::processFetchedData(hosts);
+
+ if (getReservationId() == 0) {
+ return;
+ }
+
+ if (hosts.empty()) {
+ isc_throw(Unexpected, "no host information while retrieving"
+ " IPv6 reservation");
+ }
+ HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
+
+ // If we're dealing with a new reservation, let's add it to the
+ // host.
+ if (getReservationId() > most_recent_reservation_id_) {
+ most_recent_reservation_id_ = getReservationId();
+
+ if (most_recent_reservation_id_ > 0) {
+ host->addReservation(retrieveReservation());
+ }
+ }
+ }
+
+ /// @brief Create BIND array to receive Host data with IPv6 reservations.
+ ///
+ /// Creates a MYSQL_BIND array to receive Host data from the database.
+ /// After data is successfully received, @ref processedFetchedData is
+ /// called for each returned row to build collection of @ref Host
+ /// objects with associated IPv6 reservations.
+ ///
+ /// @return Vector of MYSQL_BIND objects representing data to be retrieved.
+ virtual std::vector<MYSQL_BIND> createBindForReceive() {
+ // Reset most recent reservation id value because we're now making
+ // a new SELECT query.
+ most_recent_reservation_id_ = 0;
+
+ // Bind values supported by parent classes.
+ static_cast<void>(MySqlHostWithOptionsExchange::createBindForReceive());
+
+ // reservation_id : INT UNSIGNED NOT NULL AUTO_INCREMENT
+ bind_[reservation_id_index_].buffer_type = MYSQL_TYPE_LONG;
+ bind_[reservation_id_index_].buffer = reinterpret_cast<char*>(&reservation_id_);
+ bind_[reservation_id_index_].is_unsigned = MLM_TRUE;
+
+ // IPv6 address/prefix BINARY(16)
+ ipv6_address_buffer_len_ = isc::asiolink::V6ADDRESS_LEN;
+ bind_[address_index_].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[address_index_].buffer = reinterpret_cast<char*>(ipv6_address_buffer_);
+ bind_[address_index_].buffer_length = ipv6_address_buffer_len_;
+ bind_[address_index_].length = &ipv6_address_buffer_len_;
+
+ // prefix_len : TINYINT
+ bind_[prefix_len_index_].buffer_type = MYSQL_TYPE_TINY;
+ bind_[prefix_len_index_].buffer = reinterpret_cast<char*>(&prefix_len_);
+ bind_[prefix_len_index_].is_unsigned = MLM_TRUE;
+
+ // (reservation) type : TINYINT
+ reserv_type_null_ = MLM_FALSE;
+ bind_[type_index_].buffer_type = MYSQL_TYPE_TINY;
+ bind_[type_index_].buffer = reinterpret_cast<char*>(&reserv_type_);
+ bind_[type_index_].is_unsigned = MLM_TRUE;
+ bind_[type_index_].is_null = &reserv_type_null_;
+
+ // dhcp6_iaid INT UNSIGNED
+ bind_[iaid_index_].buffer_type = MYSQL_TYPE_LONG;
+ bind_[iaid_index_].buffer = reinterpret_cast<char*>(&iaid_);
+ bind_[iaid_index_].is_unsigned = MLM_TRUE;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_);
+
+ return (bind_);
+ };
+
+private:
+
+ /// @brief IPv6 reservation id.
+ uint32_t reservation_id_;
+
+ /// @brief IPv6 reservation type.
+ uint8_t reserv_type_;
+
+ /// @brief Boolean flag indicating if reservation type field is null.
+ ///
+ /// This flag is used by the class to determine if the returned row
+ /// contains IPv6 reservation information.
+ my_bool reserv_type_null_;
+
+ /// @brief Buffer holding IPv6 address/prefix in textual format.
+ uint8_t ipv6_address_buffer_[isc::asiolink::V6ADDRESS_LEN];
+
+ /// @brief Length of the textual address representation.
+ unsigned long ipv6_address_buffer_len_;
+
+ /// @brief Length of the prefix (128 for addresses)
+ uint8_t prefix_len_;
+
+ /// @brief IAID.
+ uint32_t iaid_;
+
+ /// @name Indexes of columns holding information about IPv6 reservations.
+ //@{
+ /// @brief Index of reservation_id column.
+ size_t reservation_id_index_;
+
+ /// @brief Index of address column.
+ size_t address_index_;
+
+ /// @brief Index of prefix_len column.
+ size_t prefix_len_index_;
+
+ /// @brief Index of type column.
+ size_t type_index_;
+
+ /// @brief Index of IAID column.
+ size_t iaid_index_;
+
+ //@}
+
+ /// @brief Reservation id for last processed row.
+ uint32_t most_recent_reservation_id_;
+};
+
+/// @brief This class is used for storing IPv6 reservations in a MySQL database.
+///
+/// This class is only used to insert IPv6 reservations into the
+/// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
+/// retrieve IPv6 reservation the @ref MySqlHostIPv6Exchange class should be
+/// used instead.
+///
+/// When a new IPv6 reservation is inserted into the database, an appropriate
+/// host must be defined in the hosts table. An attempt to insert IPv6
+/// reservation for non-existing host will result in failure.
+class MySqlIPv6ReservationExchange {
+private:
+
+ /// @brief Set number of columns for ipv6_reservation table.
+ static const size_t RESRV_COLUMNS = 6;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initialize class members representing a single IPv6 reservation.
+ MySqlIPv6ReservationExchange()
+ : host_id_(0), prefix_len_(0), type_(0),
+ iaid_(0), resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
+
+ // Reset error table.
+ std::fill(&error_[0], &error_[RESRV_COLUMNS], MLM_FALSE);
+
+ // Set the column names (for error messages)
+ columns_[0] = "host_id";
+ columns_[1] = "address";
+ columns_[2] = "prefix_len";
+ columns_[3] = "type";
+ columns_[4] = "dhcp6_iaid";
+
+ BOOST_STATIC_ASSERT(4 < RESRV_COLUMNS);
+ }
+
+ /// @brief Create MYSQL_BIND objects for IPv6 Reservation.
+ ///
+ /// Fills in the MYSQL_BIND array for sending data in the IPv6 Reservation
+ /// object to the database.
+ ///
+ /// @param resv An object representing IPv6 reservation which will be
+ /// sent to the database.
+ /// None of the fields in the reservation are modified -
+ /// the reservation data is only read.
+ /// @param id ID of a host owning this reservation
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
+ ///
+ /// @return Vector of MySQL BIND objects representing the data to be added.
+ std::vector<MYSQL_BIND> createBindForSend(const IPv6Resrv& resv,
+ const HostID& id,
+ const bool unique_ip) {
+
+ // Store the values to ensure they remain valid.
+ resv_ = resv;
+ host_id_ = id;
+
+ // Initialize prior to constructing the array of MYSQL_BIND structures.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ memset(bind_, 0, sizeof(bind_));
+
+ // Set up the structures for the various components of the host structure.
+
+ try {
+ addr6_ = resv.getPrefix().toBytes();
+ if (addr6_.size() != isc::asiolink::V6ADDRESS_LEN) {
+ isc_throw(DbOperationError, "createBindForSend() - prefix is not "
+ << isc::asiolink::V6ADDRESS_LEN << " bytes long");
+ }
+
+ addr6_length_ = isc::asiolink::V6ADDRESS_LEN;
+ bind_[0].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[0].buffer = reinterpret_cast<char*>(&addr6_[0]);
+ bind_[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
+ bind_[0].length = &addr6_length_;
+
+ // prefix_len tinyint
+ prefix_len_ = resv.getPrefixLen();
+ bind_[1].buffer_type = MYSQL_TYPE_TINY;
+ bind_[1].buffer = reinterpret_cast<char*>(&prefix_len_);
+ bind_[1].is_unsigned = MLM_TRUE;
+
+ // type tinyint
+ // See lease6_types for values (0 = IA_NA, 1 = IA_TA, 2 = IA_PD)
+ type_ = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
+ bind_[2].buffer_type = MYSQL_TYPE_TINY;
+ bind_[2].buffer = reinterpret_cast<char*>(&type_);
+ bind_[2].is_unsigned = MLM_TRUE;
+
+ // dhcp6_iaid INT UNSIGNED
+ /// @todo: We don't support iaid in the IPv6Resrv yet.
+ iaid_ = 0;
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&iaid_);
+ bind_[3].is_unsigned = MLM_TRUE;
+
+ // host_id INT UNSIGNED NOT NULL
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from IPv6 Reservation: "
+ << resv_.toText() << ", reason: " << ex.what());
+ }
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ // RESRV_COLUMNS -1 as we do not set reservation_id.
+ std::vector<MYSQL_BIND> vec(&bind_[0], &bind_[RESRV_COLUMNS-1]);
+
+ // When checking whether the IP is unique we need to bind the IPv6 address
+ // and prefix length at the end of the query as it has additional binding
+ // for the IPv6 address and prefix length.
+ if (unique_ip) {
+ vec.push_back(bind_[0]); // address
+ vec.push_back(bind_[1]); // prefix_len
+ }
+
+ return (vec);
+ }
+
+private:
+
+ /// @brief Host unique identifier.
+ uint64_t host_id_;
+
+ /// @brief Length of the prefix (128 for addresses).
+ uint8_t prefix_len_;
+
+ /// @brief Reservation type.
+ uint8_t type_;
+
+ /// @brief IAID.
+ uint8_t iaid_;
+
+ /// @brief Object holding reservation being sent to the database.
+ IPv6Resrv resv_;
+
+ /// @brief Array of MySQL bindings.
+ MYSQL_BIND bind_[RESRV_COLUMNS];
+
+ /// @brief Array of strings holding columns names.
+ std::string columns_[RESRV_COLUMNS];
+
+ /// @brief Array of boolean values indicating if error occurred
+ /// for respective columns.
+ my_bool error_[RESRV_COLUMNS];
+
+ /// @brief Binary address data.
+ std::vector<uint8_t> addr6_;
+
+ /// @brief Binary address length.
+ unsigned long addr6_length_;
+};
+
+/// @brief This class is used for inserting options into a database.
+///
+/// This class supports inserting both DHCPv4 and DHCPv6 options.
+class MySqlOptionExchange {
+private:
+
+ static const size_t OPTION_ID_COL = 0;
+ static const size_t CODE_COL = 1;
+ static const size_t VALUE_COL = 2;
+ static const size_t FORMATTED_VALUE_COL = 3;
+ static const size_t SPACE_COL = 4;
+ static const size_t PERSISTENT_COL = 5;
+ static const size_t CANCELLED_COL = 6;
+ static const size_t USER_CONTEXT_COL = 7;
+ static const size_t DHCP_SUBNET_ID_COL = 8;
+ static const size_t HOST_ID_COL = 9;
+ /// @brief Number of columns in the option tables holding bindable values.
+ static const size_t OPTION_COLUMNS = 10;
+
+public:
+
+ /// @brief Constructor.
+ MySqlOptionExchange()
+ : type_(0), value_len_(0), formatted_value_len_(0), space_(),
+ space_len_(0), persistent_(false), cancelled_(false),
+ user_context_(), user_context_len_(0), subnet_id_(SUBNET_ID_UNUSED),
+ host_id_(0), option_() {
+
+ BOOST_STATIC_ASSERT(10 <= OPTION_COLUMNS);
+ }
+
+ /// @brief Creates binding array to insert option data into database.
+ ///
+ /// @return Vector of MYSQL_BIND object representing an option.
+ std::vector<MYSQL_BIND> createBindForSend(const OptionDescriptor& opt_desc,
+ const std::string& opt_space,
+ const Optional<SubnetID>& subnet_id,
+ const HostID& host_id) {
+
+ // Hold pointer to the option to make sure it remains valid until
+ // we complete a query.
+ option_ = opt_desc.option_;
+
+ memset(bind_, 0, sizeof(bind_));
+
+ try {
+ // option_id: INT UNSIGNED NOT NULL
+ // The option_id is auto_incremented, so we need to pass the NULL
+ // value.
+ bind_[0].buffer_type = MYSQL_TYPE_NULL;
+
+ // code: SMALLINT UNSIGNED NOT NULL
+ type_ = option_->getType();
+ bind_[1].buffer_type = MYSQL_TYPE_SHORT;
+ bind_[1].buffer = reinterpret_cast<char*>(&type_);
+ bind_[1].is_unsigned = MLM_TRUE;
+
+ // value: BLOB NULL
+ if (opt_desc.formatted_value_.empty() &&
+ (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
+ // The formatted_value is empty and the option value is
+ // non-empty so we need to prepare on-wire format for the
+ // option and store it in the database as a blob.
+ OutputBuffer buf(opt_desc.option_->len());
+ opt_desc.option_->pack(buf);
+ const char* buf_ptr = static_cast<const char*>(buf.getData());
+ value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
+ buf_ptr + buf.getLength());
+ value_len_ = value_.size();
+ bind_[2].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[2].buffer = &value_[0];
+ bind_[2].buffer_length = value_len_;
+ bind_[2].length = &value_len_;
+
+ } else {
+ // No value or formatted_value specified. In this case, the
+ // value blob is NULL.
+ value_.clear();
+ bind_[2].buffer_type = MYSQL_TYPE_NULL;
+ }
+
+ // formatted_value: TEXT NULL,
+ if (!opt_desc.formatted_value_.empty()) {
+ formatted_value_len_ = opt_desc.formatted_value_.size();
+ bind_[3].buffer_type = MYSQL_TYPE_STRING;
+ bind_[3].buffer = const_cast<char*>(opt_desc.formatted_value_.c_str());
+ bind_[3].buffer_length = formatted_value_len_;
+ bind_[3].length = &formatted_value_len_;
+
+ } else {
+ bind_[3].buffer_type = MYSQL_TYPE_NULL;
+ }
+
+ // space: VARCHAR(128) NULL
+ space_ = opt_space;
+ space_len_ = space_.size();
+ bind_[4].buffer_type = MYSQL_TYPE_STRING;
+ bind_[4].buffer = const_cast<char*>(space_.c_str());
+ bind_[4].buffer_length = space_len_;
+ bind_[4].length = &space_len_;
+
+ // persistent: TINYINT(1) NOT NULL DEFAULT 0
+ persistent_ = opt_desc.persistent_;
+ bind_[5].buffer_type = MYSQL_TYPE_TINY;
+ bind_[5].buffer = reinterpret_cast<char*>(&persistent_);
+ bind_[5].is_unsigned = MLM_TRUE;
+
+ // cancelled: TINYINT(1) NOT NULL DEFAULT 0
+ cancelled_ = opt_desc.cancelled_;
+ bind_[6].buffer_type = MYSQL_TYPE_TINY;
+ bind_[6].buffer = reinterpret_cast<char*>(&cancelled_);
+ bind_[6].is_unsigned = MLM_TRUE;
+
+ // user_context: TEST NULL,
+ ConstElementPtr ctx = opt_desc.getContext();
+ if (ctx) {
+ user_context_ = ctx->str();
+ user_context_len_ = user_context_.size();
+ bind_[7].buffer_type = MYSQL_TYPE_STRING;
+ bind_[7].buffer = const_cast<char*>(user_context_.c_str());
+ bind_[7].buffer_length = user_context_len_;
+ bind_[7].length = &user_context_len_;
+ } else {
+ bind_[7].buffer_type = MYSQL_TYPE_NULL;
+ }
+
+ // dhcp4_subnet_id: INT UNSIGNED NULL
+ if (!subnet_id.unspecified()) {
+ subnet_id_ = subnet_id;
+ bind_[8].buffer_type = MYSQL_TYPE_LONG;
+ bind_[8].buffer = reinterpret_cast<char*>(subnet_id_);
+ bind_[8].is_unsigned = MLM_TRUE;
+
+ } else {
+ bind_[8].buffer_type = MYSQL_TYPE_NULL;
+ }
+
+ // host_id: INT UNSIGNED NOT NULL
+ host_id_ = host_id;
+ bind_[9].buffer_type = MYSQL_TYPE_LONG;
+ bind_[9].buffer = reinterpret_cast<char*>(&host_id_);
+ bind_[9].is_unsigned = MLM_TRUE;
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array for inserting DHCP "
+ "option: " << option_->toText() << ", reason: "
+ << ex.what());
+ }
+
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[OPTION_COLUMNS]));
+ }
+
+private:
+
+ /// @brief Option type.
+ uint16_t type_;
+
+ /// @brief Option value as binary.
+ std::vector<uint8_t> value_;
+
+ /// @brief Option value length.
+ unsigned long value_len_;
+
+ /// @brief Formatted option value length.
+ unsigned long formatted_value_len_;
+
+ /// @brief Option space name.
+ std::string space_;
+
+ /// @brief Option space name length.
+ unsigned long space_len_;
+
+ /// @brief Boolean flag indicating if the option is always returned to
+ /// a client or only when requested.
+ bool persistent_;
+
+ /// @brief Boolean flag indicating if the option must be never returned
+ /// to a client.
+ bool cancelled_;
+
+ /// @brief User context.
+ std::string user_context_;
+
+ /// @brief User context length.
+ unsigned long user_context_len_;
+
+ /// @brief Subnet identifier.
+ uint32_t subnet_id_;
+
+ /// @brief Host identifier.
+ uint32_t host_id_;
+
+ /// @brief Pointer to currently parsed option.
+ OptionPtr option_;
+
+ /// @brief Array of MYSQL_BIND elements representing inserted data.
+ MYSQL_BIND bind_[OPTION_COLUMNS];
+};
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief MySQL Host Context
+///
+/// This class stores the thread context for the manager pool.
+/// The class is needed by all get/update/delete functions which must use one
+/// or more exchanges to perform database operations.
+/// Each context provides a set of such exchanges for each thread.
+/// The context instances are lazy initialized by the requesting thread by using
+/// the manager's createContext function and are destroyed when the manager's
+/// pool instance is destroyed.
+class MySqlHostContext {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param parameters See MySqlHostMgr constructor.
+ /// @param io_service_accessor The IOService accessor function.
+ /// @param db_reconnect_callback The connection recovery callback.
+ MySqlHostContext(const DatabaseConnection::ParameterMap& parameters,
+ IOServiceAccessorPtr io_service_accessor,
+ db::DbCallback db_reconnect_callback);
+
+ /// The exchange objects are used for transfer of data to/from the database.
+ /// They are pointed-to objects as the contents may change in "const" calls,
+ /// while the rest of this object does not. (At alternative would be to
+ /// declare them as "mutable".)
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to retrieve hosts and DHCPv4 options.
+ boost::shared_ptr<MySqlHostWithOptionsExchange> host_ipv4_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
+ boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv6_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
+ /// IPv6 reservations using a single query.
+ boost::shared_ptr<MySqlHostIPv6Exchange> host_ipv46_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to insert new IPv6 reservation.
+ boost::shared_ptr<MySqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
+ /// or dhcp6_options table.
+ boost::shared_ptr<MySqlOptionExchange> host_option_exchange_;
+
+ /// @brief MySQL connection
+ MySqlConnection conn_;
+
+ /// @brief Indicates if the database is opened in read only mode.
+ bool is_readonly_;
+};
+
+/// @brief MySQL Host Context Pool
+///
+/// This class provides a pool of contexts.
+/// The manager will use this class to handle available contexts.
+/// There is only one ContextPool per manager per back-end, which is created
+/// and destroyed by the respective manager factory class.
+class MySqlHostContextPool {
+public:
+
+ /// @brief The vector of available contexts.
+ std::vector<MySqlHostContextPtr> pool_;
+
+ /// @brief The mutex to protect pool access.
+ std::mutex mutex_;
+};
+
+/// @brief Type of pointers to context pools.
+typedef boost::shared_ptr<MySqlHostContextPool> MySqlHostContextPoolPtr;
+
+/// @brief Implementation of the @ref MySqlHostDataSource.
+class MySqlHostDataSourceImpl {
+public:
+
+ /// @brief Statement Tags
+ ///
+ /// The contents of the enum are indexes into the list of SQL statements.
+ /// It is assumed that the order is such that the indices of statements
+ /// reading the database are less than those of statements modifying the
+ /// database.
+ /// @note: please add new statements doing read only operations before
+ /// the WRITE_STMTS_BEGIN position.
+ enum StatementIndex {
+ GET_HOST_DHCPID, // Gets hosts by host identifier
+ GET_HOST_ADDR, // Gets hosts by IPv4 address
+ GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
+ GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
+ GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
+ GET_HOST_PREFIX, // Gets host by IPv6 prefix
+ GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
+ GET_HOST_ADDR6, // Gets hosts by IPv6 address/prefix
+ GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
+ GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
+ GET_HOST_HOSTNAME, // Gets hosts by hostname
+ GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
+ GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
+ GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
+ GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
+ GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
+ GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
+ INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
+ INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
+ INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
+ INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
+ INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
+ INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
+ DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
+ DEL_HOST_ADDR6, // Delete v6 host (subnet-id, addr6)
+ DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
+ DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
+ NUM_STATEMENTS // Number of statements
+ };
+
+ /// @brief Index of first statement performing write to the database.
+ ///
+ /// This value is used to mark border line between queries and other
+ /// statements and statements performing write operation on the database,
+ /// such as INSERT, DELETE, UPDATE.
+ static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_NON_UNIQUE_IP;
+
+ /// @brief Constructor.
+ ///
+ /// This constructor opens database connection and initializes prepared
+ /// statements used in the queries.
+ MySqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Destructor.
+ ~MySqlHostDataSourceImpl();
+
+ /// @brief Attempts to reconnect the server to the host DB backend manager.
+ ///
+ /// This is a self-rescheduling function that attempts to reconnect to the
+ /// server's host DB backends after connectivity to one or more have been
+ /// lost. Upon entry it will attempt to reconnect via
+ /// @ref HostDataSourceFactory::add.
+ /// If this is successful, DHCP servicing is re-enabled and server returns
+ /// to normal operation.
+ ///
+ /// If reconnection fails and the maximum number of retries has not been
+ /// exhausted, it will schedule a call to itself to occur at the
+ /// configured retry interval. DHCP service remains disabled.
+ ///
+ /// If the maximum number of retries has been exhausted an error is logged
+ /// and the server shuts down.
+ ///
+ /// This function is passed to the connection recovery mechanism. It will be
+ /// invoked when a connection loss is detected.
+ ///
+ /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters.
+ /// @return true if connection has been recovered, false otherwise.
+ static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief Create a new context.
+ ///
+ /// The database is opened with all the SQL commands pre-compiled.
+ ///
+ /// @return A new (never null) context.
+ ///
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ MySqlHostContextPtr createContext() const;
+
+ /// @brief Returns backend version.
+ ///
+ /// The method is called by the constructor before opening the database
+ /// to verify that the schema version is correct.
+ ///
+ /// @return Version number stored in the database, as a pair of unsigned
+ /// integers. "first" is the major version number, "second" the
+ /// minor number.
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database
+ /// has failed.
+ std::pair<uint32_t, uint32_t> getVersion() const;
+
+ /// @brief Executes statements which inserts a row into one of the tables.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param bind Vector of MYSQL_BIND objects to be used when making the
+ /// query.
+ ///
+ /// @throw isc::db::DuplicateEntry Database throws duplicate entry error
+ void addStatement(MySqlHostContextPtr& ctx,
+ MySqlHostDataSourceImpl::StatementIndex stindex,
+ std::vector<MYSQL_BIND>& bind);
+
+ /// @brief Executes statements that delete records.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param bind Vector of MYSQL_BIND objects to be used when making the
+ /// query.
+ ///
+ /// @return true if any records were deleted, false otherwise
+ bool delStatement(MySqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind);
+
+ /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
+ ///
+ /// @param ctx Context
+ /// @param resv IPv6 Reservation to be added
+ /// @param id ID of a host owning this reservation
+ void addResv(MySqlHostContextPtr& ctx,
+ const IPv6Resrv& resv,
+ const HostID& id);
+
+ /// @brief Inserts a single DHCP option into the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param opt_desc Option descriptor holding information about an option
+ /// to be inserted into the database.
+ /// @param opt_space Option space name.
+ /// @param subnet_id Subnet identifier.
+ /// @param host_id Host identifier.
+ void addOption(MySqlHostContextPtr& ctx,
+ const MySqlHostDataSourceImpl::StatementIndex& stindex,
+ const OptionDescriptor& opt_desc,
+ const std::string& opt_space,
+ const Optional<SubnetID>& subnet_id,
+ const HostID& host_id);
+
+ /// @brief Inserts multiple options into the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param options_cfg An object holding a collection of options to be
+ /// inserted into the database.
+ /// @param host_id Host identifier retrieved using @c mysql_insert_id.
+ void addOptions(MySqlHostContextPtr& ctx,
+ const StatementIndex& stindex,
+ const ConstCfgOptionPtr& options_cfg,
+ const uint64_t host_id);
+
+ /// @brief Check Error and Throw Exception
+ ///
+ /// This method invokes @ref db::MySqlConnection::checkError.
+ ///
+ /// @param ctx Context
+ /// @param status Status code: non-zero implies an error
+ /// @param index Index of statement that caused the error
+ /// @param what High-level description of the error
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ void checkError(MySqlHostContextPtr& ctx,
+ const int status,
+ const StatementIndex index,
+ const char* what) const;
+
+ /// @brief Creates collection of @ref Host objects with associated
+ /// information such as IPv6 reservations and/or DHCP options.
+ ///
+ /// This method performs a query which returns host information from
+ /// the 'hosts' table. The query may also use LEFT JOIN clause to
+ /// retrieve information from other tables, e.g. ipv6_reservations,
+ /// dhcp4_options and dhcp6_options.
+ /// Whether IPv6 reservations and/or options are assigned to the
+ /// @ref Host objects depends on the type of the exchange object.
+ ///
+ /// @param ctx Context
+ /// @param stindex Statement index.
+ /// @param bind Pointer to an array of MySQL bindings.
+ /// @param exchange Pointer to the exchange object used for the
+ /// particular query.
+ /// @param [out] result Reference to the collection of hosts returned.
+ /// @param single A boolean value indicating if a single host is
+ /// expected to be returned, or multiple hosts.
+ void getHostCollection(MySqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ boost::shared_ptr<MySqlHostExchange> exchange,
+ ConstHostCollection& result,
+ bool single) const;
+
+ /// @brief Retrieves a host by subnet and client's unique identifier.
+ ///
+ /// This method is used by both MySqlHostDataSource::get4 and
+ /// MySqlHOstDataSource::get6 methods.
+ ///
+ /// @param ctx Context
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param stindex Statement index.
+ /// @param exchange Pointer to the exchange object used for the
+ /// particular query.
+ ///
+ /// @return Pointer to const instance of Host or null pointer if
+ /// no host found.
+ ConstHostPtr getHost(MySqlHostContextPtr& ctx,
+ const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ StatementIndex stindex,
+ boost::shared_ptr<MySqlHostExchange> exchange) const;
+
+ /// @brief Throws exception if database is read only.
+ ///
+ /// This method should be called by the methods which write to the
+ /// database. If the backend is operating in read-only mode this
+ /// method will throw exception.
+ ///
+ /// @param ctx Context
+ ///
+ /// @throw DbReadOnly if backend is operating in read only mode.
+ void checkReadOnly(MySqlHostContextPtr& ctx) const;
+
+ /// @brief The parameters
+ DatabaseConnection::ParameterMap parameters_;
+
+ /// @brief Holds the setting whether the IP reservations must be unique or
+ /// may be non-unique.
+ bool ip_reservations_unique_;
+
+ /// @brief The pool of contexts
+ MySqlHostContextPoolPtr pool_;
+
+ /// @brief Indicates if there is at least one connection that can no longer
+ /// be used for normal operations.
+ bool unusable_;
+
+ /// @brief Timer name used to register database reconnect timer.
+ std::string timer_name_;
+};
+
+namespace {
+
+/// @brief Array of tagged statements.
+typedef boost::array<TaggedStatement, MySqlHostDataSourceImpl::NUM_STATEMENTS>
+TaggedStatementArray;
+
+/// @brief Prepared MySQL statements used by the backend to insert and
+/// retrieve hosts from the database.
+TaggedStatementArray tagged_statements = { {
+ // Retrieves host information, IPv6 reservations and both DHCPv4 and
+ // DHCPv6 options associated with the host. The LEFT JOIN clause is used
+ // to retrieve information from 4 different tables using a single query.
+ // Hence, this query returns multiple rows for a single host.
+ {MySqlHostDataSourceImpl::GET_HOST_DHCPID,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
+ "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
+ "h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
+ "o4.persistent, o4.cancelled, o4.user_context, "
+ "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
+ "o6.persistent, o6.cancelled, o6.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o4 "
+ "ON h.host_id = o4.host_id "
+ "LEFT JOIN dhcp6_options AS o6 "
+ "ON h.host_id = o6.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE dhcp_identifier = ? AND dhcp_identifier_type = ? "
+ "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
+
+ // Retrieves host information along with the DHCPv4 options associated with
+ // it. Left joining the dhcp4_options table results in multiple rows being
+ // returned for the same host. The host is retrieved by IPv4 address.
+ {MySqlHostDataSourceImpl::GET_HOST_ADDR,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o "
+ "ON h.host_id = o.host_id "
+ "WHERE ipv4_address = ? "
+ "ORDER BY h.host_id, o.option_id"},
+
+ // Retrieves host information and DHCPv4 options using subnet identifier
+ // and client's identifier. Left joining the dhcp4_options table results in
+ // multiple rows being returned for the same host.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o "
+ "ON h.host_id = o.host_id "
+ "WHERE h.dhcp4_subnet_id = ? AND h.dhcp_identifier_type = ? "
+ "AND h.dhcp_identifier = ? "
+ "ORDER BY h.host_id, o.option_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host. The number of rows returned is a multiplication
+ // of number of IPv6 reservations and DHCPv6 options.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.dhcp6_subnet_id = ? AND h.dhcp_identifier_type = ? "
+ "AND h.dhcp_identifier = ? "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Retrieves host information and DHCPv4 options for the host using subnet
+ // identifier and IPv4 reservation. Left joining the dhcp4_options table
+ // results in multiple rows being returned for the host. The number of
+ // rows depends on the number of options defined for the host.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o "
+ "ON h.host_id = o.host_id "
+ "WHERE h.dhcp4_subnet_id = ? AND h.ipv4_address = ? "
+ "ORDER BY h.host_id, o.option_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using prefix and prefix length. This query
+ // returns host information for a single host. However, multiple rows
+ // are returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {MySqlHostDataSourceImpl::GET_HOST_PREFIX,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context,"
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.host_id = "
+ "( SELECT host_id FROM ipv6_reservations "
+ "WHERE address = ? AND prefix_len = ? ) "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using IPv6 subnet id and prefix. This query
+ // returns host information for a single host. However, multiple rows
+ // are returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.dhcp6_subnet_id = ? AND h.host_id IN "
+ "(SELECT host_id FROM ipv6_reservations "
+ "WHERE address = ?) "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using IPv6 address/prefix. This query
+ // may return host information for one or more host reservations. Even
+ // if only one host is found, multiple rows
+ // are returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {MySqlHostDataSourceImpl::GET_HOST_ADDR6,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.host_id IN "
+ "(SELECT host_id FROM ipv6_reservations "
+ "WHERE address = ?) "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Retrieves host information along with the DHCPv4 options associated with
+ // it. Left joining the dhcp4_options table results in multiple rows being
+ // returned for the same host. Hosts are retrieved by IPv4 subnet id.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID4,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o "
+ "ON h.host_id = o.host_id "
+ "WHERE h.dhcp4_subnet_id = ? "
+ "ORDER BY h.host_id, o.option_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host. The number of rows returned is a multiplication
+ // of number of IPv6 reservations and DHCPv6 options. Hosts are retrieved
+ // by IPv6 subnet id.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID6,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.dhcp6_subnet_id = ? "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Retrieves host information, IPv6 reservations and both DHCPv4 and
+ // DHCPv6 options associated with the host. The LEFT JOIN clause is used
+ // to retrieve information from 4 different tables using a single query.
+ // Hence, this query returns multiple rows for a single host.
+ {MySqlHostDataSourceImpl::GET_HOST_HOSTNAME,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
+ "h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
+ "h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
+ "o4.persistent, o4.cancelled, o4.user_context, "
+ "o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
+ "o6.persistent, o6.cancelled, o6.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o4 "
+ "ON h.host_id = o4.host_id "
+ "LEFT JOIN dhcp6_options AS o6 "
+ "ON h.host_id = o6.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.hostname = ? "
+ "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"},
+
+ // Retrieves host information and DHCPv4 options using hostname and
+ // subnet identifier. Left joining the dhcp4_options table results in
+ // multiple rows being returned for the same host.
+ {MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o "
+ "ON h.host_id = o.host_id "
+ "WHERE h.hostname = ? AND h.dhcp4_subnet_id = ? "
+ "ORDER BY h.host_id, o.option_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // using hostname and subnet identifier. The number of rows returned
+ // is a multiplication of number of IPv6 reservations and DHCPv6 options.
+ {MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.hostname = ? AND h.dhcp6_subnet_id = ? "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Retrieves host information along with the DHCPv4 options associated with
+ // it. Left joining the dhcp4_options table results in multiple rows being
+ // returned for the same host. Hosts are retrieved by IPv4 subnet id
+ // and with a host id greater than the start one.
+ // The number of hosts returned is lower or equal to the limit.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context "
+ "FROM ( SELECT * FROM hosts AS h "
+ "WHERE h.dhcp4_subnet_id = ? AND h.host_id > ? "
+ "ORDER BY h.host_id "
+ "LIMIT ? ) AS h "
+ "LEFT JOIN dhcp4_options AS o "
+ "ON h.host_id = o.host_id "
+ "ORDER BY h.host_id, o.option_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host. The number of rows returned is a multiplication
+ // of number of IPv6 reservations and DHCPv6 options. Hosts are retrieved
+ // by IPv6 subnet id and with a host id greater than the start one.
+ // The number of hosts returned is lower or equal to the limit.
+ {MySqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM ( SELECT * FROM hosts AS h "
+ "WHERE h.dhcp6_subnet_id = ? AND h.host_id > ? "
+ "ORDER BY h.host_id "
+ "LIMIT ? ) AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Retrieves host information along with the DHCPv4 options associated with
+ // it. Left joining the dhcp4_options table results in multiple rows being
+ // returned for the same host. Hosts are retrieved with a host id greater
+ // than the start one.
+ // The number of hosts returned is lower or equal to the limit.
+ {MySqlHostDataSourceImpl::GET_HOST_PAGE4,
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ "h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context "
+ "FROM ( SELECT * FROM hosts AS h "
+ "WHERE h.host_id > ? "
+ "ORDER BY h.host_id "
+ "LIMIT ? ) AS h "
+ "LEFT JOIN dhcp4_options AS o "
+ "ON h.host_id = o.host_id "
+ "ORDER BY h.host_id, o.option_id"},
+
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host. The number of rows returned is a multiplication
+ // of number of IPv6 reservations and DHCPv6 options. Hosts are retrieved
+ // with a host id greater than the start one.
+ // The number of hosts returned is lower or equal to the limit.
+ {MySqlHostDataSourceImpl::GET_HOST_PAGE6,
+ "SELECT h.host_id, h.dhcp_identifier, "
+ "h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ "h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ "h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ "h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ "h.dhcp4_boot_file_name, h.auth_key, "
+ "o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ "o.persistent, o.cancelled, o.user_context, "
+ "r.reservation_id, r.address, r.prefix_len, r.type, "
+ "r.dhcp6_iaid "
+ "FROM ( SELECT * FROM hosts AS h "
+ "WHERE h.host_id > ? "
+ "ORDER BY h.host_id "
+ "LIMIT ? ) AS h "
+ "LEFT JOIN dhcp6_options AS o "
+ "ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"},
+
+ // Inserts a host into the 'hosts' table without checking that there is
+ // a reservation for the IP address.
+ {MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
+ "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes, "
+ "user_context, dhcp4_next_server, "
+ "dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+
+ // Inserts a host into the 'hosts' table with checking that reserved IP
+ // address is unique. The innermost query checks if there is at least
+ // one host for the given IP/subnet combination. For checking whether
+ // hosts exists or not it doesn't matter if we select actual columns,
+ // thus SELECT 1 was used as an optimization to avoid selecting data
+ // that will be ignored anyway. If the host does not exist the new
+ // host is inserted. DUAL is a special MySQL table from which we can
+ // select the values to be inserted. If the host with the given IP
+ // address already exists the new host won't be inserted. The caller
+ // can check the number of affected rows to detect that there was
+ // a duplicate host in the database.
+ {MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP,
+ "INSERT INTO hosts(host_id, dhcp_identifier, dhcp_identifier_type, "
+ "dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ "dhcp4_client_classes, dhcp6_client_classes, "
+ "user_context, dhcp4_next_server, "
+ "dhcp4_server_hostname, dhcp4_boot_file_name, auth_key) "
+ "SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM DUAL "
+ "WHERE NOT EXISTS ("
+ "SELECT 1 FROM hosts "
+ "WHERE ipv4_address = ? AND dhcp4_subnet_id = ? "
+ "LIMIT 1"
+ ")"},
+
+ // Inserts a single IPv6 reservation into 'reservations' table without
+ // checking that the inserted reservation is unique.
+ {MySqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE,
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ "dhcp6_iaid, host_id) "
+ "VALUES (?, ?, ?, ?, ?)"},
+
+ // Inserts a single IPv6 reservation into 'reservations' table with
+ // checking that the inserted reservation is unique.
+ {MySqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE,
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ "dhcp6_iaid, host_id) "
+ "SELECT ?, ?, ?, ?, ? FROM DUAL "
+ "WHERE NOT EXISTS ("
+ "SELECT 1 FROM ipv6_reservations "
+ "WHERE address = ? AND prefix_len = ? "
+ "LIMIT 1"
+ ")"},
+
+ // Inserts a single DHCPv4 option into 'dhcp4_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
+ "INSERT INTO dhcp4_options(option_id, code, value, formatted_value, space, "
+ "persistent, cancelled, user_context, dhcp4_subnet_id, host_id, scope_id) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+
+ // Inserts a single DHCPv6 option into 'dhcp6_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
+ "INSERT INTO dhcp6_options(option_id, code, value, formatted_value, space, "
+ "persistent, cancelled, user_context, dhcp6_subnet_id, host_id, scope_id) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 3)"},
+
+ // Delete IPv4 reservations by subnet id and reserved address.
+ {MySqlHostDataSourceImpl::DEL_HOST_ADDR4,
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND ipv4_address = ?"},
+
+ // Delete IPv6 reservations by subnet id and reserved address/prefix.
+ {MySqlHostDataSourceImpl::DEL_HOST_ADDR6,
+ "DELETE h FROM hosts AS h "
+ "INNER JOIN ipv6_reservations AS r "
+ "ON h.host_id = r.host_id "
+ "WHERE h.dhcp6_subnet_id = ? AND r.address = ?"},
+
+ // Delete a single IPv4 reservation by subnet id and identifier.
+ {MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = ? AND dhcp_identifier_type=? "
+ "AND dhcp_identifier = ?"},
+
+ // Delete a single IPv6 reservation by subnet id and identifier.
+ {MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
+ "DELETE FROM hosts WHERE dhcp6_subnet_id = ? AND dhcp_identifier_type=? "
+ "AND dhcp_identifier = ?"}
+ }
+};
+
+} // namespace
+
+// MySqlHostContext Constructor
+
+MySqlHostContext::MySqlHostContext(const DatabaseConnection::ParameterMap& parameters,
+ IOServiceAccessorPtr io_service_accessor,
+ db::DbCallback db_reconnect_callback)
+ : conn_(parameters, io_service_accessor, db_reconnect_callback),
+ is_readonly_(true) {
+}
+
+// MySqlHostContextAlloc Constructor and Destructor
+
+MySqlHostDataSource::MySqlHostContextAlloc::MySqlHostContextAlloc(
+ MySqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ {
+ // we need to protect the whole pool_ operation, hence extra scope {}
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (!mgr_.pool_->pool_.empty()) {
+ ctx_ = mgr_.pool_->pool_.back();
+ mgr_.pool_->pool_.pop_back();
+ }
+ }
+ if (!ctx_) {
+ ctx_ = mgr_.createContext();
+ }
+ } else {
+ // single-threaded
+ if (mgr_.pool_->pool_.empty()) {
+ isc_throw(Unexpected, "No available MySQL host context?!");
+ }
+ ctx_ = mgr_.pool_->pool_.back();
+ }
+}
+
+MySqlHostDataSource::MySqlHostContextAlloc::~MySqlHostContextAlloc() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ mgr_.pool_->pool_.push_back(ctx_);
+ if (ctx_->conn_.isUnusable()) {
+ mgr_.unusable_ = true;
+ }
+ } else if (ctx_->conn_.isUnusable()) {
+ mgr_.unusable_ = true;
+ }
+}
+
+MySqlHostDataSourceImpl::MySqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters)
+ : parameters_(parameters), ip_reservations_unique_(true), unusable_(false),
+ timer_name_("") {
+
+ // Create unique timer name per instance.
+ timer_name_ = "MySqlHostMgr[";
+ timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+ timer_name_ += "]DbReconnectTimer";
+
+ // Validate the schema version first.
+ std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
+ MYSQL_SCHEMA_VERSION_MINOR);
+ std::pair<uint32_t, uint32_t> db_version = getVersion();
+ if (code_version != db_version) {
+ isc_throw(DbOpenError,
+ "MySQL schema version mismatch: need version: "
+ << code_version.first << "." << code_version.second
+ << " found version: " << db_version.first << "."
+ << db_version.second);
+ }
+
+ // Create an initial context.
+ pool_.reset(new MySqlHostContextPool());
+ pool_->pool_.push_back(createContext());
+}
+
+// Create context.
+
+MySqlHostContextPtr
+MySqlHostDataSourceImpl::createContext() const {
+ MySqlHostContextPtr ctx(new MySqlHostContext(parameters_,
+ IOServiceAccessorPtr(new IOServiceAccessor(&HostMgr::getIOService)),
+ &MySqlHostDataSourceImpl::dbReconnect));
+
+ // Open the database.
+ ctx->conn_.openDatabase();
+
+ // Check if we have TLS when we required it.
+ if (ctx->conn_.getTls()) {
+ std::string cipher = ctx->conn_.getTlsCipher();
+ if (cipher.empty()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_NO_TLS);
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_MYSQL_TLS_CIPHER)
+ .arg(cipher);
+ }
+ }
+
+ // Prepare query statements. Those are will be only used to retrieve
+ // information from the database, so they can be used even if the
+ // database is read only for the current user.
+ ctx->conn_.prepareStatements(tagged_statements.begin(),
+ tagged_statements.begin() + WRITE_STMTS_BEGIN);
+
+ // Check if the backend is explicitly configured to operate with
+ // read only access to the database.
+ ctx->is_readonly_ = ctx->conn_.configuredReadOnly();
+
+ // If we are using read-write mode for the database we also prepare
+ // statements for INSERTS etc.
+ if (!ctx->is_readonly_) {
+ // Prepare statements for writing to the database, e.g. INSERT.
+ ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
+ tagged_statements.end());
+ } else {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_READONLY);
+ }
+
+ // Create the exchange objects for use in exchanging data between the
+ // program and the database.
+ ctx->host_ipv4_exchange_.reset(new MySqlHostWithOptionsExchange(MySqlHostWithOptionsExchange::DHCP4_ONLY));
+ ctx->host_ipv6_exchange_.reset(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP6_ONLY));
+ ctx->host_ipv46_exchange_.reset(new MySqlHostIPv6Exchange(MySqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
+ ctx->host_ipv6_reservation_exchange_.reset(new MySqlIPv6ReservationExchange());
+ ctx->host_option_exchange_.reset(new MySqlOptionExchange());
+
+ // Create ReconnectCtl for this connection.
+ ctx->conn_.makeReconnectCtl(timer_name_);
+
+ return (ctx);
+}
+
+MySqlHostDataSourceImpl::~MySqlHostDataSourceImpl() {
+}
+
+bool
+MySqlHostDataSourceImpl::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
+ MultiThreadingCriticalSection cs;
+
+ // Invoke application layer connection lost callback.
+ if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+
+ bool reopened = false;
+
+ const std::string timer_name = db_reconnect_ctl->timerName();
+
+ // At least one connection was lost.
+ try {
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
+ std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
+ for (std::string& hds : host_db_access_list) {
+ auto parameters = DatabaseConnection::parse(hds);
+ if (HostMgr::delBackend("mysql", hds, true)) {
+ HostMgr::addBackend(hds);
+ }
+ }
+ reopened = true;
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED)
+ .arg(ex.what());
+ }
+
+ if (reopened) {
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection recovered callback.
+ if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+ } else {
+ if (!db_reconnect_ctl->checkRetries()) {
+ // We're out of retries, log it and initiate shutdown.
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_RECONNECT_FAILED)
+ .arg(db_reconnect_ctl->maxRetries());
+
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection failed callback.
+ DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
+ return (false);
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE)
+ .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
+ .arg(db_reconnect_ctl->maxRetries())
+ .arg(db_reconnect_ctl->retryInterval());
+
+ // Start the timer.
+ if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->registerTimer(timer_name,
+ std::bind(&MySqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
+ db_reconnect_ctl->retryInterval(),
+ asiolink::IntervalTimer::ONE_SHOT);
+ }
+ TimerMgr::instance()->setup(timer_name);
+ }
+
+ return (true);
+}
+
+std::pair<uint32_t, uint32_t>
+MySqlHostDataSourceImpl::getVersion() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_HOST_DB_GET_VERSION);
+
+ return (MySqlConnection::getVersion(parameters_));
+}
+
+void
+MySqlHostDataSourceImpl::addStatement(MySqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ std::vector<MYSQL_BIND>& bind) {
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], &bind[0]);
+ checkError(ctx, status, stindex, "unable to bind parameters");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(ctx->conn_.statements_[stindex]);
+
+ if (status != 0) {
+ // Failure: check for the special case of duplicate entry.
+ if (mysql_errno(ctx->conn_.mysql_) == ER_DUP_ENTRY) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+ checkError(ctx, status, stindex, "unable to execute");
+ }
+
+ // If the number of rows inserted is 0 it means that the query detected
+ // an attempt to insert duplicated data for which there is no unique
+ // index in the database. Unique indexes are not created in the database
+ // when it may be sometimes allowed to insert duplicated records per
+ // server's configuration.
+ my_ulonglong numrows = mysql_stmt_affected_rows(ctx->conn_.statements_[stindex]);
+ if (numrows == 0) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+}
+
+bool
+MySqlHostDataSourceImpl::delStatement(MySqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind) {
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], &bind[0]);
+ checkError(ctx, status, stindex, "unable to bind parameters");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(ctx->conn_.statements_[stindex]);
+
+ if (status != 0) {
+ checkError(ctx, status, stindex, "unable to execute");
+ }
+
+ // Let's check how many hosts were deleted.
+ my_ulonglong numrows = mysql_stmt_affected_rows(ctx->conn_.statements_[stindex]);
+
+ return (numrows != 0);
+}
+
+void
+MySqlHostDataSourceImpl::addResv(MySqlHostContextPtr& ctx,
+ const IPv6Resrv& resv,
+ const HostID& id) {
+ std::vector<MYSQL_BIND> bind = ctx->host_ipv6_reservation_exchange_->
+ createBindForSend(resv, id, ip_reservations_unique_);
+
+ addStatement(ctx, ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE, bind);
+}
+
+void
+MySqlHostDataSourceImpl::addOption(MySqlHostContextPtr& ctx,
+ const StatementIndex& stindex,
+ const OptionDescriptor& opt_desc,
+ const std::string& opt_space,
+ const Optional<SubnetID>& subnet_id,
+ const HostID& id) {
+ std::vector<MYSQL_BIND> bind = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, subnet_id, id);
+
+ addStatement(ctx, stindex, bind);
+}
+
+void
+MySqlHostDataSourceImpl::addOptions(MySqlHostContextPtr& ctx,
+ const StatementIndex& stindex,
+ const ConstCfgOptionPtr& options_cfg,
+ const uint64_t host_id) {
+ // Get option space names and vendor space names and combine them within a
+ // single list.
+ std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
+ std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
+ option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
+ vendor_spaces.end());
+
+ // For each option space retrieve all options and insert them into the
+ // database.
+ for (auto space = option_spaces.begin(); space != option_spaces.end(); ++space) {
+ OptionContainerPtr options = options_cfg->getAllCombined(*space);
+ if (options && !options->empty()) {
+ for (auto opt = options->begin(); opt != options->end(); ++opt) {
+ addOption(ctx, stindex, *opt, *space, Optional<SubnetID>(), host_id);
+ }
+ }
+ }
+}
+
+void
+MySqlHostDataSourceImpl::checkError(MySqlHostContextPtr& ctx,
+ const int status,
+ const StatementIndex index,
+ const char* what) const {
+ ctx->conn_.checkError(status, index, what);
+}
+
+void
+MySqlHostDataSourceImpl::getHostCollection(MySqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ boost::shared_ptr<MySqlHostExchange> exchange,
+ ConstHostCollection& result,
+ bool single) const {
+
+ // Bind the selection parameters to the statement
+ int status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], bind);
+ checkError(ctx, status, stindex, "unable to bind WHERE clause parameter");
+
+ // Set up the MYSQL_BIND array for the data being returned and bind it to
+ // the statement.
+ std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
+ status = mysql_stmt_bind_result(ctx->conn_.statements_[stindex], &outbind[0]);
+ checkError(ctx, status, stindex, "unable to bind SELECT clause parameters");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(ctx->conn_.statements_[stindex]);
+ checkError(ctx, status, stindex, "unable to execute");
+
+ // Ensure that all the lease information is retrieved in one go to avoid
+ // overhead of going back and forth between client and server.
+ status = mysql_stmt_store_result(ctx->conn_.statements_[stindex]);
+ checkError(ctx, status, stindex, "unable to set up for storing all results");
+
+ // Set up the fetch "release" object to release resources associated
+ // with the call to mysql_stmt_fetch when this method exits, then
+ // retrieve the data. mysql_stmt_fetch return value equal to 0 represents
+ // successful data fetch.
+ MySqlFreeResult fetch_release(ctx->conn_.statements_[stindex]);
+ while ((status = mysql_stmt_fetch(ctx->conn_.statements_[stindex])) ==
+ MLM_MYSQL_FETCH_SUCCESS) {
+ try {
+ exchange->processFetchedData(result);
+
+ } catch (const isc::BadValue& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ ctx->conn_.text_statements_[stindex] << ">");
+ }
+
+ if (single && (result.size() > 1)) {
+ isc_throw(MultipleRecords, "multiple records were found in the "
+ "database where only one was expected for query "
+ << ctx->conn_.text_statements_[stindex]);
+ }
+ }
+
+ // How did the fetch end?
+ // If mysql_stmt_fetch return value is equal to 1 an error occurred.
+ if (status == MLM_MYSQL_FETCH_FAILURE) {
+ // Error - unable to fetch results
+ checkError(ctx, status, stindex, "unable to fetch results");
+
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, ctx->conn_.text_statements_[stindex]
+ << " returned truncated data: columns affected are "
+ << exchange->getErrorColumns());
+ }
+}
+
+ConstHostPtr
+MySqlHostDataSourceImpl::getHost(MySqlHostContextPtr& ctx,
+ const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ StatementIndex stindex,
+ boost::shared_ptr<MySqlHostExchange> exchange) const {
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Identifier value.
+ std::vector<char> identifier_vec(identifier_begin,
+ identifier_begin + identifier_len);
+ unsigned long length = identifier_vec.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = &identifier_vec[0];
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+
+ // Identifier type.
+ char identifier_type_copy = static_cast<char>(identifier_type);
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ getHostCollection(ctx, stindex, inbind, exchange, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+void
+MySqlHostDataSourceImpl::checkReadOnly(MySqlHostContextPtr& ctx) const {
+ if (ctx->is_readonly_) {
+ isc_throw(ReadOnlyDb, "MySQL host database backend is configured to"
+ " operate in read only mode");
+ }
+}
+
+MySqlHostDataSource::MySqlHostDataSource(const DatabaseConnection::ParameterMap& parameters)
+ : impl_(new MySqlHostDataSourceImpl(parameters)) {
+}
+
+MySqlHostDataSource::~MySqlHostDataSource() {
+}
+
+DatabaseConnection::ParameterMap
+MySqlHostDataSource::getParameters() const {
+ return (impl_->parameters_);
+}
+
+void
+MySqlHostDataSource::add(const HostPtr& host) {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ // Initiate MySQL transaction as we will have to make multiple queries
+ // to insert host information into multiple tables. If that fails on
+ // any stage, the transaction will be rolled back by the destructor of
+ // the MySqlTransaction class.
+ MySqlTransaction transaction(ctx->conn_);
+
+ // If we're configured to check that an IP reservation within a given subnet
+ // is unique, the IP reservation exists and the subnet is actually set
+ // we will be using a special query that checks for uniqueness. Otherwise,
+ // we will use a regular insert statement.
+ bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
+ && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
+
+ // Create the MYSQL_BIND array for the host
+ std::vector<MYSQL_BIND> bind = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
+
+ // ... and insert the host.
+ impl_->addStatement(ctx, unique_ip ? MySqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
+ MySqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP, bind);
+
+ // Gets the last inserted hosts id
+ uint64_t host_id = mysql_insert_id(ctx->conn_.mysql_);
+
+ // Insert DHCPv4 options.
+ ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
+ if (cfg_option4) {
+ impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
+ cfg_option4, host_id);
+ }
+
+ // Insert DHCPv6 options.
+ ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
+ if (cfg_option6) {
+ impl_->addOptions(ctx, MySqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
+ cfg_option6, host_id);
+ }
+
+ // Insert IPv6 reservations.
+ IPv6ResrvRange v6resv = host->getIPv6Reservations();
+ if (std::distance(v6resv.first, v6resv.second) > 0) {
+ for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
+ ++resv) {
+ impl_->addResv(ctx, resv->second, host_id);
+ }
+ }
+
+ // Everything went fine, so explicitly commit the transaction.
+ transaction.commit();
+}
+
+bool
+MySqlHostDataSource::del(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr) {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+
+ uint32_t subnet = subnet_id;
+ memset(inbind, 0, sizeof(inbind));
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // v4
+ if (addr.isV4()) {
+ uint32_t addr4 = addr.toUint32();
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR4, inbind));
+ }
+
+ // v6
+ std::vector<uint8_t>addr6 = addr.toBytes();
+ if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
+ isc_throw(DbOperationError, "del() - address is not "
+ << isc::asiolink::V6ADDRESS_LEN << " bytes long");
+ }
+
+ unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[1].length = &addr6_length;
+
+ return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_ADDR6, inbind));
+}
+
+bool
+MySqlHostDataSource::del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+
+ // subnet-id
+ memset(inbind, 0, sizeof(inbind));
+ uint32_t subnet = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // identifier type
+ char identifier_type_copy = static_cast<char>(identifier_type);
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // identifier value
+ std::vector<char> identifier_vec(identifier_begin,
+ identifier_begin + identifier_len);
+ unsigned long length = identifier_vec.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = &identifier_vec[0];
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+
+ ConstHostCollection collection;
+ return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_SUBID4_ID, inbind));
+}
+
+bool
+MySqlHostDataSource::del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+
+ // subnet-id
+ memset(inbind, 0, sizeof(inbind));
+ uint32_t subnet = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // identifier type
+ char identifier_type_copy = static_cast<char>(identifier_type);
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&identifier_type_copy);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // identifier value
+ std::vector<char> identifier_vec(identifier_begin,
+ identifier_begin + identifier_len);
+ unsigned long length = identifier_vec.size();
+ inbind[2].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[2].buffer = &identifier_vec[0];
+ inbind[2].buffer_length = length;
+ inbind[2].length = &length;
+
+ ConstHostCollection collection;
+ return (impl_->delStatement(ctx, MySqlHostDataSourceImpl::DEL_HOST_SUBID6_ID, inbind));
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Identifier type.
+ char identifier_type_copy = static_cast<char>(identifier_type);
+ inbind[1].buffer = &identifier_type_copy;
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Identifier value.
+ std::vector<char> identifier_vec(identifier_begin,
+ identifier_begin + identifier_len);
+ unsigned long int length = identifier_vec.size();
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = &identifier_vec[0];
+ inbind[0].buffer_length = length;
+ inbind[0].length = &length;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_DHCPID, inbind,
+ ctx->host_ipv46_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll4(const SubnetID& subnet_id) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+ uint32_t subnet = subnet_id;
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4, inbind,
+ ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll6(const SubnetID& subnet_id) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+ uint32_t subnet = subnet_id;
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6, inbind,
+ ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Hostname
+ char hostname_[HOSTNAME_MAX_LEN];
+ strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
+ unsigned long length = hostname.length();
+ inbind[0].buffer_type = MYSQL_TYPE_STRING;
+ inbind[0].buffer = reinterpret_cast<char*>(hostname_);
+ inbind[0].buffer_length = length;
+ inbind[0].length = &length;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME, inbind,
+ ctx->host_ipv46_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Hostname
+ char hostname_[HOSTNAME_MAX_LEN];
+ strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
+ unsigned long length = hostname.length();
+ inbind[0].buffer_type = MYSQL_TYPE_STRING;
+ inbind[0].buffer = reinterpret_cast<char*>(hostname_);
+ inbind[0].buffer_length = length;
+ inbind[0].length = &length;
+
+ // Subnet ID
+ uint32_t subnet = subnet_id;
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4, inbind,
+ ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Hostname
+ char hostname_[HOSTNAME_MAX_LEN];
+ strncpy(hostname_, hostname.c_str(), HOSTNAME_MAX_LEN - 1);
+ unsigned long length = hostname.length();
+ inbind[0].buffer_type = MYSQL_TYPE_STRING;
+ inbind[0].buffer = reinterpret_cast<char*>(hostname_);
+ inbind[0].buffer_length = length;
+ inbind[0].length = &length;
+
+ // Subnet ID
+ uint32_t subnet = subnet_id;
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6, inbind,
+ ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getPage4(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind subnet id
+ uint32_t subnet = subnet_id;
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Bind lower host id
+ uint32_t host_id = lower_host_id;
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&host_id);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Bind page size value
+ uint32_t page_size_data = page_size.page_size_;
+ inbind[2].buffer_type = MYSQL_TYPE_LONG;
+ inbind[2].buffer = reinterpret_cast<char*>(&page_size_data);
+ inbind[2].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE, inbind,
+ ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getPage6(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind subnet id
+ uint32_t subnet = subnet_id;
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Bind lower host id
+ uint32_t host_id = lower_host_id;
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&host_id);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Bind page size value
+ uint32_t page_size_data = page_size.page_size_;
+ inbind[2].buffer_type = MYSQL_TYPE_LONG;
+ inbind[2].buffer = reinterpret_cast<char*>(&page_size_data);
+ inbind[2].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE, inbind,
+ ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getPage4(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind lower host id
+ uint32_t host_id = lower_host_id;
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&host_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Bind page size value
+ uint32_t page_size_data = page_size.page_size_;
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&page_size_data);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PAGE4, inbind,
+ ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getPage6(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind lower host id
+ uint32_t host_id = lower_host_id;
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&host_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Bind page size value
+ uint32_t page_size_data = page_size.page_size_;
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&page_size_data);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PAGE6, inbind,
+ ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t addr4 = address.toUint32();
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_ADDR, inbind,
+ ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
+ MySqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
+ ctx->host_ipv4_exchange_));
+}
+
+ConstHostPtr
+MySqlHostDataSource::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ if (!address.isV4()) {
+ isc_throw(BadValue, "MySqlHostDataSource::get4(id, address): "
+ "wrong address type, address supplied is an IPv6 address");
+ }
+
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ uint32_t subnet = subnet_id;
+ memset(inbind, 0, sizeof(inbind));
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ uint32_t addr4 = address.toUint32();
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, inbind,
+ ctx->host_ipv4_exchange_, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ if (!address.isV4()) {
+ isc_throw(BadValue, "MySqlHostDataSource::getAll4(id, address): "
+ "wrong address type, address supplied is an IPv6 address");
+ }
+
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ uint32_t subnet = subnet_id;
+ memset(inbind, 0, sizeof(inbind));
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ uint32_t addr4 = address.toUint32();
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID_ADDR, inbind,
+ ctx->host_ipv4_exchange_, collection, false);
+ return (collection);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
+ MySqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
+ ctx->host_ipv6_exchange_));
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ if (!prefix.isV6()) {
+ isc_throw(BadValue, "MySqlHostDataSource::get6(prefix, prefix_len): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ std::vector<uint8_t>addr6 = prefix.toBytes();
+ if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
+ isc_throw(DbOperationError, "get6() - prefix is not "
+ << isc::asiolink::V6ADDRESS_LEN << " bytes long");
+ }
+
+ unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[0].length = &addr6_length;
+
+ uint8_t tmp = prefix_len;
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&tmp);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_PREFIX, inbind,
+ ctx->host_ipv6_exchange_, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostPtr
+MySqlHostDataSource::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ if (!address.isV6()) {
+ isc_throw(BadValue, "MySqlHostDataSource::get6(id, address): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ std::vector<uint8_t>addr6 = address.toBytes();
+ if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
+ isc_throw(DbOperationError, "get6() - address is not "
+ << isc::asiolink::V6ADDRESS_LEN << " bytes long");
+ }
+
+ unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[1].length = &addr6_length;
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, inbind,
+ ctx->host_ipv6_exchange_, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ if (!address.isV6()) {
+ isc_throw(BadValue, "MySqlHostDataSource::getAll6(id, address): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t subnet_buffer = static_cast<uint32_t>(subnet_id);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet_buffer);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ std::vector<uint8_t>addr6 = address.toBytes();
+ if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
+ isc_throw(DbOperationError, "getAll6() - address is not "
+ << isc::asiolink::V6ADDRESS_LEN << " bytes long");
+ }
+
+ unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[1].buffer_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[1].length = &addr6_length;
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR, inbind,
+ ctx->host_ipv6_exchange_, collection, false);
+ return (collection);
+}
+
+ConstHostCollection
+MySqlHostDataSource::getAll6(const IOAddress& address) const {
+ if (!address.isV6()) {
+ isc_throw(BadValue, "MySqlHostDataSource::getAll6(address): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ std::vector<uint8_t>addr6 = address.toBytes();
+ if (addr6.size() != isc::asiolink::V6ADDRESS_LEN) {
+ isc_throw(DbOperationError, "getAll6() - address is not "
+ << isc::asiolink::V6ADDRESS_LEN << " bytes long");
+ }
+
+ unsigned long addr6_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[0].buffer_length = isc::asiolink::V6ADDRESS_LEN;
+ inbind[0].length = &addr6_length;
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, MySqlHostDataSourceImpl::GET_HOST_ADDR6, inbind,
+ ctx->host_ipv6_exchange_, collection, false);
+ return (collection);
+}
+
+void
+MySqlHostDataSource::update(HostPtr const& host) {
+ // Get a context.
+ MySqlHostContextAlloc const context(*impl_);
+ MySqlHostContextPtr ctx(context.ctx_);
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ // Initiate MySQL transaction as we will have to make multiple queries
+ // to update host information into multiple tables. If that fails on
+ // any stage, the transaction will be rolled back by the destructor of
+ // the MySqlTransaction class.
+ MySqlTransaction transaction(ctx->conn_);
+
+ // As much as having dedicated prepared statements for updating tables would be consistent with
+ // the implementation of other commands, it's difficult if not impossible to cover all cases for
+ // updating the host to exactly as is described in the command, which may involve inserts and
+ // deletes alongside updates. So let's delete and add. The delete cascades into all tables. The
+ // add explicitly adds into all tables.
+ BaseHostDataSource::update(host);
+
+ // Everything went fine, so explicitly commit the transaction.
+ transaction.commit();
+}
+
+// Miscellaneous database methods.
+
+std::string
+MySqlHostDataSource::getName() const {
+ std::string name = "";
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ try {
+ name = ctx->conn_.getParameter("name");
+ } catch (...) {
+ // Return an empty name
+ }
+ return (name);
+}
+
+std::string
+MySqlHostDataSource::getDescription() const {
+ return (std::string("Host data source that stores host information"
+ "in MySQL database"));
+}
+
+std::pair<uint32_t, uint32_t>
+MySqlHostDataSource::getVersion() const {
+ return(impl_->getVersion());
+}
+
+void
+MySqlHostDataSource::commit() {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+ if (ctx->conn_.isTransactionStarted()) {
+ ctx->conn_.commit();
+ }
+}
+
+void
+MySqlHostDataSource::rollback() {
+ // Get a context
+ MySqlHostContextAlloc get_context(*impl_);
+ MySqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+ if (ctx->conn_.isTransactionStarted()) {
+ ctx->conn_.rollback();
+ }
+}
+
+bool
+MySqlHostDataSource::setIPReservationsUnique(const bool unique) {
+ impl_->ip_reservations_unique_ = unique;
+ return (true);
+}
+
+bool
+MySqlHostDataSource::isUnusable() {
+ return (impl_->unusable_);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/mysql_host_data_source.h b/src/lib/dhcpsrv/mysql_host_data_source.h
new file mode 100644
index 0000000..bf99472
--- /dev/null
+++ b/src/lib/dhcpsrv/mysql_host_data_source.h
@@ -0,0 +1,536 @@
+// Copyright (C) 2015-2023 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 MYSQL_HOST_DATA_SOURCE_H
+#define MYSQL_HOST_DATA_SOURCE_H
+
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <mysql/mysql_connection.h>
+
+#include <stdint.h>
+
+#include <utility>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration to the implementation of the @ref MySqlHostDataSource.
+class MySqlHostDataSourceImpl;
+
+/// @brief Type of pointers to MySqlHostDataSourceImpl.
+typedef boost::shared_ptr<MySqlHostDataSourceImpl> MySqlHostDataSourceImplPtr;
+
+/// Forward declaration for the thread context for the manager pool.
+class MySqlHostContext;
+
+/// @brief Type of pointers to contexts.
+typedef boost::shared_ptr<MySqlHostContext> MySqlHostContextPtr;
+
+/// @brief MySQL Host Data Source
+///
+/// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
+/// the MySQL database. Use of this backend presupposes that a MySQL database
+/// is available and that the Kea schema has been created within it.
+class MySqlHostDataSource : public BaseHostDataSource {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Uses the following keywords in the parameters passed to it to
+ /// connect to the database:
+ /// - name - Name of the database to which to connect (mandatory)
+ /// - host - Host to which to connect (optional, defaults to "localhost")
+ /// - user - Username under which to connect (optional)
+ /// - password - Password for "user" on the database (optional)
+ ///
+ /// If the database is successfully opened, the version number in the
+ /// schema_version table will be checked against hard-coded value in
+ /// the implementation file.
+ ///
+ /// Finally, all the SQL commands are pre-compiled.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+ /// @throw isc::db::DbOpenError Error opening the database or the
+ /// schema version is invalid.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ MySqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Virtual destructor.
+ ///
+ /// Releases prepared MySQL statements used by the backend.
+ virtual ~MySqlHostDataSource();
+
+ /// @brief Return backend parameters
+ ///
+ /// Returns the backend parameters
+ ///
+ /// @return Parameters of the backend.
+ virtual isc::db::DatabaseConnection::ParameterMap getParameters() const;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// The implementations of this method should guard against duplicate
+ /// reservations for the same host, where possible. For example, when the
+ /// reservation for the same HW address and subnet id is added twice, the
+ /// addHost method should throw an DuplicateEntry exception. Note, that
+ /// usually it is impossible to guard against adding duplicated host, where
+ /// one instance is identified by HW address, another one by DUID.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ virtual void add(const HostPtr& host);
+
+ /// @brief Attempts to delete hosts by (subnet-id, address)
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier type, identifier)
+ ///
+ /// This method supports v4 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier type, identifier)
+ ///
+ /// This method supports v6 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len);
+
+ /// @brief Return all hosts connected to any subnet for which reservations
+ /// have been made using a specified identifier.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for a specified identifier. This method may return multiple hosts
+ /// because a particular client may have reservations in multiple subnets.
+ ///
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns all @ref Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id subnet identifier to filter by
+ ///
+ /// @return Collection of const @ref Host objects.
+ virtual ConstHostCollection getAll4(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns all @ref Host objects which represent reservations
+ /// in a specified subnet.
+ ///
+ /// @param subnet_id subnet identifier to filter by
+ ///
+ /// @return Collection of const @ref Host objects.
+ virtual ConstHostCollection getAll6(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname.
+ ///
+ /// MySQL uses the case-insensitive hosts_by_hostname index on hostname.
+ ///
+ /// @param hostname The lower case hostname.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAllbyHostname(const std::string& hostname) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const;
+
+ /// @brief Returns range of hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations in a specified subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method returns a page of @c Host objects which represent
+ /// reservations.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAll4(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// One of the use cases for this method is to detect collisions between
+ /// dynamically allocated addresses and reserved addresses. When the new
+ /// address is assigned to a client, the allocation mechanism should check
+ /// if this address is not reserved for some other host and do not allocate
+ /// this address if reservation is present.
+ ///
+ /// Implementations of this method should guard against invalid addresses,
+ /// such as IPv6 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Const @c Host object using a specified IPv4 address.
+ virtual ConstHostPtr get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv4 subnet and having
+ /// a reservation for a specified address.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv4 address within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv4 reservation. In that case, the number of hosts returned
+ /// by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv4 address in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv4 address is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv4 address is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Const @c Host object using a specified IPv6 prefix.
+ virtual ConstHostPtr get6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address or prefix.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Const @c Host object using a specified IPv6 address/prefix.
+ virtual ConstHostPtr get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv6 subnet and having
+ /// a reservation for a specified address or delegated prefix (lease).
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix. In that case, the number of hosts
+ /// returned by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts having a reservation for a specified
+ /// address or delegated prefix (lease) in all subnets.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host per subnet.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAll6(const asiolink::IOAddress& address) const;
+
+ /// @brief Implements @ref BaseHostDataSource::update() for MySQL.
+ ///
+ /// Attempts to update an existing host entry.
+ ///
+ /// @param host the host up to date with the requested changes
+ void update(HostPtr const& host);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("mysql"));
+ }
+
+ /// @brief Returns backend name.
+ ///
+ /// Each backend have specific name.
+ ///
+ /// @return "mysql".
+ virtual std::string getName() const;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const;
+
+ /// @brief Returns backend version.
+ ///
+ /// @return Version number stored in the database, as a pair of unsigned
+ /// integers. "first" is the major version number, "second" the
+ /// minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database
+ /// has failed.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations.
+ virtual void commit();
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations.
+ virtual void rollback();
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique within the subnet or can be non-unique.
+ /// @return always true because this backend supports both the case when
+ /// the addresses must be unique and when they may be non-unique.
+ virtual bool setIPReservationsUnique(const bool unique);
+
+ /// @brief Flag which indicates if the host manager has at least one
+ /// unusable connection.
+ ///
+ /// @return true if there is at least one unusable connection, false
+ /// otherwise
+ virtual bool isUnusable();
+
+ /// @brief Context RAII Allocator.
+ class MySqlHostContextAlloc {
+ public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor takes a context of the pool if one is available
+ /// or creates a new one.
+ ///
+ /// @param mgr A parent instance
+ MySqlHostContextAlloc(MySqlHostDataSourceImpl& mgr);
+
+ /// @brief Destructor
+ ///
+ /// This destructor puts back the context in the pool.
+ ~MySqlHostContextAlloc();
+
+ /// @brief The context
+ MySqlHostContextPtr ctx_;
+
+ private:
+ /// @brief The manager
+ MySqlHostDataSourceImpl& mgr_;
+ };
+
+private:
+ /// @brief Pointer to the implementation of the @ref MySqlHostDataSource.
+ MySqlHostDataSourceImplPtr impl_;
+};
+
+}
+}
+
+#endif // MYSQL_HOST_DATA_SOURCE_H
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
new file mode 100644
index 0000000..9319800
--- /dev/null
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -0,0 +1,4236 @@
+// Copyright (C) 2012-2023 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 <asiolink/addr_utilities.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/mysql_lease_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <mysql/mysql_connection.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/array.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/static_assert.hpp>
+#include <mysqld_error.h>
+
+#include <iostream>
+#include <iomanip>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <time.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::util;
+using namespace std;
+
+/// @file
+///
+/// This file holds the implementation of the Lease Manager using MySQL. The
+/// implementation uses MySQL's C API, as it comes as standard with the MySQL
+/// client libraries.
+///
+/// In general, each of the database access methods corresponds to one SQL
+/// statement. To avoid the overhead of parsing a statement every time it is
+/// used, when the database is opened "prepared statements" are created -
+/// essentially doing the SQL parsing up front. Every time a method is used
+/// to access data, the corresponding prepared statement is referenced. Each
+/// prepared statement contains a set of placeholders for data, each
+/// placeholder being for:
+///
+/// - data being added to the database (as in adding or updating a lease)
+/// - data being retrieved from the database (as in getting lease information)
+/// - selection criteria used to determine which records to update/retrieve.
+///
+/// All such data is associated with the prepared statement using an array of
+/// MYSQL_BIND structures. Each element in the array corresponds to one
+/// parameter in the prepared statement - the first element in the array is
+/// associated with the first parameter, the second element with the second
+/// parameter etc.
+///
+/// Within this file, the setting up of the MYSQL_BIND arrays for data being
+/// passed to and retrieved from the database is handled in the
+/// isc::dhcp::MySqlLease4Exchange and isc::dhcp::MySqlLease6Exchange classes.
+/// The classes also hold intermediate variables required for exchanging some
+/// of the data.
+///
+/// With these exchange objects in place, many of the methods follow similar
+/// logic:
+/// - Set up the MYSQL_BIND array for data being transferred to/from the
+/// database. For data being transferred to the database, some of the
+/// data is extracted from the lease to intermediate variables, whilst
+/// in other cases the MYSQL_BIND arrays point to the data in the lease.
+/// - Set up the MYSQL_BIND array for the data selection parameters.
+/// - Bind these arrays to the prepared statement.
+/// - Execute the statement.
+/// - If there is output, copy the data from the bound variables to the output
+/// lease object.
+
+namespace {
+
+/// @brief Maximum length of the hostname stored in DNS.
+///
+/// This length is restricted by the length of the domain-name carried
+/// in the Client FQDN %Option (see RFC4702 and RFC4704).
+const size_t HOSTNAME_MAX_LEN = 255;
+
+/// @brief Maximum size of an IPv6 address represented as a text string.
+///
+/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
+/// colon separators.
+const size_t ADDRESS6_TEXT_MAX_LEN = 39;
+
+/// @brief Maximum length of user context.
+const size_t USER_CONTEXT_MAX_LEN = 8192;
+
+/// @brief Maximum length of the text returned by the limit checking functions.
+const size_t LIMITS_TEXT_MAX_LEN = 512;
+
+boost::array<TaggedStatement, MySqlLeaseMgr::NUM_STATEMENTS>
+tagged_statements = { {
+ {MySqlLeaseMgr::DELETE_LEASE4,
+ "DELETE FROM lease4 WHERE address = ? AND expire = ?"},
+ {MySqlLeaseMgr::DELETE_LEASE4_STATE_EXPIRED,
+ "DELETE FROM lease4 "
+ "WHERE state = ? AND expire < ?"},
+ {MySqlLeaseMgr::DELETE_LEASE6,
+ "DELETE FROM lease6 WHERE address = ? AND expire = ?"},
+ {MySqlLeaseMgr::DELETE_LEASE6_STATE_EXPIRED,
+ "DELETE FROM lease6 "
+ "WHERE state = ? AND expire < ?"},
+ {MySqlLeaseMgr::GET_LEASE4,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4"},
+ {MySqlLeaseMgr::GET_LEASE4_ADDR,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE address = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_CLIENTID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE client_id = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE client_id = ? AND subnet_id = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_HWADDR,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE hwaddr = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_HWADDR_SUBID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE hwaddr = ? AND subnet_id = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_PAGE,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE address > ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_UCTX_PAGE,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE address > ? AND user_context IS NOT NULL "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_SUBID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE subnet_id = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_HOSTNAME,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE hostname = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_EXPIRE,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE state != ? "
+ "AND valid_lifetime != 4294967295 "
+ "AND expire < ? "
+ "ORDER BY expire ASC "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_RELAYID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = ? and address > ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_RELAYID_QST,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = ? and address > ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " >= ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_RELAYID_QSET,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = ? and address > ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " >= ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " <= ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_RELAYID_QET,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = ? and address > ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " <= ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_REMOTEID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = ? and address > ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_REMOTEID_QST,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = ? and address > ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " >= ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_REMOTEID_QSET,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = ? and address > ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " >= ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " <= ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE4_REMOTEID_QET,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = ? and address > ? "
+ " and UNIX_TIMESTAMP(expire) - IF"
+ "(valid_lifetime = 4294967295, 0, valid_lifetime)"
+ " <= ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE6,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6"},
+ {MySqlLeaseMgr::GET_LEASE6_ADDR,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address = ? AND lease_type = ?"},
+ {MySqlLeaseMgr::GET_LEASE6_DUID_IAID,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE duid = ? AND iaid = ? AND lease_type = ?"},
+ {MySqlLeaseMgr::GET_LEASE6_DUID_IAID_SUBID,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE duid = ? AND iaid = ? AND subnet_id = ? "
+ "AND lease_type = ?"},
+ {MySqlLeaseMgr::GET_LEASE6_PAGE,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address > ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE6_UCTX_PAGE,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address > ? AND user_context IS NOT NULL "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE6_SUBID,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE subnet_id = ?"},
+ {MySqlLeaseMgr::GET_LEASE6_DUID,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE duid = ?"},
+ {MySqlLeaseMgr::GET_LEASE6_HOSTNAME,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE hostname = ?"},
+ {MySqlLeaseMgr::GET_LEASE6_EXPIRE,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE state != ? "
+ "AND valid_lifetime != 4294967295 "
+ "AND expire < ? "
+ "ORDER BY expire ASC "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::GET_LEASE6_LINK,
+ "SELECT address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address BETWEEN ? AND ? "
+ "ORDER BY address "
+ "LIMIT ?"},
+ {MySqlLeaseMgr::INSERT_LEASE4,
+ "INSERT INTO lease4(address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+ {MySqlLeaseMgr::INSERT_LEASE6,
+ "INSERT INTO lease6(address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+ {MySqlLeaseMgr::UPDATE_LEASE4,
+ "UPDATE lease4 SET address = ?, hwaddr = ?, "
+ "client_id = ?, valid_lifetime = ?, expire = ?, "
+ "subnet_id = ?, fqdn_fwd = ?, fqdn_rev = ?, "
+ "hostname = ?, "
+ "state = ?, user_context = ?, "
+ "relay_id = ?, remote_id = ?, pool_id = ? "
+ "WHERE address = ? AND expire = ?"},
+ {MySqlLeaseMgr::UPDATE_LEASE6,
+ "UPDATE lease6 SET address = ?, duid = ?, "
+ "valid_lifetime = ?, expire = ?, subnet_id = ?, "
+ "pref_lifetime = ?, lease_type = ?, iaid = ?, "
+ "prefix_len = ?, fqdn_fwd = ?, fqdn_rev = ?, "
+ "hostname = ?, hwaddr = ?, hwtype = ?, hwaddr_source = ?, "
+ "state = ?, user_context = ?, pool_id = ? "
+ "WHERE address = ? AND expire = ?"},
+ {MySqlLeaseMgr::ALL_LEASE4_STATS,
+ "SELECT subnet_id, state, leases as state_count "
+ "FROM lease4_stat ORDER BY subnet_id, state"},
+ {MySqlLeaseMgr::SUBNET_LEASE4_STATS,
+ "SELECT subnet_id, state, leases as state_count "
+ "FROM lease4_stat "
+ "WHERE subnet_id = ? "
+ "ORDER BY state"},
+ {MySqlLeaseMgr::SUBNET_RANGE_LEASE4_STATS,
+ "SELECT subnet_id, state, leases as state_count "
+ "FROM lease4_stat "
+ "WHERE subnet_id >= ? and subnet_id <= ? "
+ "ORDER BY subnet_id, state"},
+ {MySqlLeaseMgr::ALL_POOL_LEASE4_STATS,
+ "SELECT subnet_id, pool_id, state, leases as state_count "
+ "FROM lease4_pool_stat ORDER BY subnet_id, pool_id, state"},
+ {MySqlLeaseMgr::ALL_LEASE6_STATS,
+ "SELECT subnet_id, lease_type, state, leases as state_count "
+ "FROM lease6_stat ORDER BY subnet_id, lease_type, state"},
+ {MySqlLeaseMgr::SUBNET_LEASE6_STATS,
+ "SELECT subnet_id, lease_type, state, leases as state_count "
+ "FROM lease6_stat "
+ "WHERE subnet_id = ? "
+ "ORDER BY lease_type, state"},
+ {MySqlLeaseMgr::SUBNET_RANGE_LEASE6_STATS,
+ "SELECT subnet_id, lease_type, state, leases as state_count "
+ "FROM lease6_stat "
+ "WHERE subnet_id >= ? and subnet_id <= ? "
+ "ORDER BY subnet_id, lease_type, state"},
+ {MySqlLeaseMgr::ALL_POOL_LEASE6_STATS,
+ "SELECT subnet_id, pool_id, lease_type, state, leases as state_count "
+ "FROM lease6_pool_stat ORDER BY subnet_id, pool_id, lease_type, state"},
+ {MySqlLeaseMgr::CHECK_LEASE4_LIMITS,
+ "SELECT checkLease4Limits(?)"},
+ {MySqlLeaseMgr::CHECK_LEASE6_LIMITS,
+ "SELECT checkLease6Limits(?)"},
+ {MySqlLeaseMgr::IS_JSON_SUPPORTED,
+ "SELECT isJsonSupported()"},
+ {MySqlLeaseMgr::GET_LEASE4_COUNT_BY_CLASS,
+ "SELECT leases "
+ "FROM lease4_stat_by_client_class "
+ "WHERE client_class = ?"},
+ {MySqlLeaseMgr::GET_LEASE6_COUNT_BY_CLASS,
+ "SELECT leases "
+ "FROM lease6_stat_by_client_class "
+ "WHERE client_class = ? AND lease_type = ?"},
+} }; // tagged_statements
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Common MySQL and Lease Data Methods
+///
+/// The MySqlLease4Exchange and MySqlLease6Exchange classes provide the
+/// functionality to set up binding information between variables in the
+/// program and data extracted from the database. This class is the common
+/// base to both of them, containing some common methods.
+
+class MySqlLeaseExchange {
+public:
+
+ /// @brief Set error indicators
+ ///
+ /// Sets the error indicator for each of the MYSQL_BIND elements. It points
+ /// the "error" field within an element of the input array to the
+ /// corresponding element of the passed error array.
+ ///
+ /// @param bind Array of BIND elements
+ /// @param error Array of error elements. If there is an error in getting
+ /// data associated with one of the "bind" elements, the
+ /// corresponding element in the error array is set to MLM_TRUE.
+ /// @param count Size of each of the arrays.
+ static void setErrorIndicators(MYSQL_BIND* bind, my_bool* error,
+ size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ error[i] = MLM_FALSE;
+ bind[i].error = reinterpret_cast<my_bool*>(&error[i]);
+ }
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @param error Array of error elements. An element is set to MLM_TRUE
+ /// if the corresponding column in the database is the source of
+ /// the error.
+ /// @param names Array of column names, the same size as the error array.
+ /// @param count Size of each of the arrays.
+ static std::string getColumnsInError(my_bool* error, std::string* names,
+ size_t count) {
+ std::string result = "";
+
+ // Accumulate list of column names
+ for (size_t i = 0; i < count; ++i) {
+ if (error[i] == MLM_TRUE) {
+ if (!result.empty()) {
+ result += ", ";
+ }
+ result += names[i];
+ }
+ }
+
+ if (result.empty()) {
+ result = "(None)";
+ }
+
+ return (result);
+ }
+};
+
+/// @brief Exchange MySQL and Lease4 Data
+///
+/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
+/// describe the parameters in the prepared statements. Where information is
+/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
+/// structure is identical. This class handles the creation of that array.
+///
+/// Owing to the MySQL API, the process requires some intermediate variables
+/// to hold things like data length etc. This object holds those variables.
+///
+/// @note There are no unit tests for this class. It is tested indirectly
+/// in all MySqlLeaseMgr::xxx4() calls where it is used.
+
+class MySqlLease4Exchange : public MySqlLeaseExchange {
+ /// These are used for both retrieving data and for looking up
+ /// column labels for logging. Note that their numeric order
+ /// MUST match that of the column order in the Lease4 table.
+ //@{
+ static const size_t ADDRESS_COL = 0;
+ static const size_t HWADDR_COL = 1;
+ static const size_t CLIENT_ID_COL = 2;
+ static const size_t VALID_LIFETIME_COL = 3;
+ static const size_t EXPIRE_COL = 4;
+ static const size_t SUBNET_ID_COL = 5;
+ static const size_t FQDN_FWD_COL = 6;
+ static const size_t FQDN_REV_COL = 7;
+ static const size_t HOSTNAME_COL = 8;
+ static const size_t STATE_COL = 9;
+ static const size_t USER_CONTEXT_COL = 10;
+ static const size_t RELAY_ID_COL = 11;
+ static const size_t REMOTE_ID_COL = 12;
+ static const size_t POOL_ID_COL = 13;
+ //@}
+ /// @brief Number of columns in the table holding DHCPv4 leases.
+ static const size_t LEASE_COLUMNS = 14;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// The initialization of the variables here is only to satisfy cppcheck -
+ /// all variables are initialized/set in the methods before they are used.
+ MySqlLease4Exchange() : addr4_(0), hwaddr_length_(0), hwaddr_null_(MLM_FALSE),
+ client_id_length_(0), client_id_null_(MLM_FALSE),
+ subnet_id_(0), pool_id_(0), valid_lifetime_(0),
+ fqdn_fwd_(false), fqdn_rev_(false), hostname_length_(0),
+ state_(0), user_context_length_(0),
+ user_context_null_(MLM_FALSE), relay_id_length_(0),
+ relay_id_null_(MLM_FALSE), remote_id_length_(0),
+ remote_id_null_(MLM_FALSE) {
+ memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
+ memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
+ memset(hostname_buffer_, 0, sizeof(hostname_buffer_));
+ memset(user_context_, 0, sizeof(user_context_));
+ memset(relay_id_buffer_, 0, sizeof(relay_id_buffer_));
+ memset(remote_id_buffer_, 0, sizeof(remote_id_buffer_));
+ std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
+
+ // Set the column names (for error messages)
+ columns_[ADDRESS_COL] = "address";
+ columns_[HWADDR_COL] = "hwaddr";
+ columns_[CLIENT_ID_COL] = "client_id";
+ columns_[VALID_LIFETIME_COL] = "valid_lifetime";
+ columns_[EXPIRE_COL] = "expire";
+ columns_[SUBNET_ID_COL] = "subnet_id";
+ columns_[FQDN_FWD_COL] = "fqdn_fwd";
+ columns_[FQDN_REV_COL] = "fqdn_rev";
+ columns_[HOSTNAME_COL] = "hostname";
+ columns_[STATE_COL] = "state";
+ columns_[USER_CONTEXT_COL] = "user_context";
+ columns_[RELAY_ID_COL] = "relay_id";
+ columns_[REMOTE_ID_COL] = "remote_id";
+ columns_[POOL_ID_COL] = "pool_id";
+ BOOST_STATIC_ASSERT(13 < LEASE_COLUMNS);
+ }
+
+ /// @brief Create MYSQL_BIND objects for Lease4 Pointer
+ ///
+ /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
+ /// the database.
+ ///
+ /// @param lease Lease object to be added to the database. None of the
+ /// fields in the lease are modified - the lease data is only read.
+ ///
+ /// @return Vector of MySQL BIND objects representing the data to be added.
+ std::vector<MYSQL_BIND> createBindForSend(const Lease4Ptr& lease) {
+
+ // Store lease object to ensure it remains valid.
+ lease_ = lease;
+
+ // Initialize prior to constructing the array of MYSQL_BIND structures.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ memset(bind_, 0, sizeof(bind_));
+
+ // Set up the structures for the various components of the lease4
+ // structure.
+
+ try {
+ // address: uint32_t
+ // The address in the Lease structure is an IOAddress object. Convert
+ // this to an integer for storage.
+ addr4_ = lease_->addr_.toUint32();
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
+ bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hwaddr: varbinary(20) - hardware/MAC address
+ HWAddrPtr hwaddr = lease_->hwaddr_;
+ if (hwaddr) {
+ hwaddr_ = hwaddr->hwaddr_;
+ hwaddr_length_ = hwaddr->hwaddr_.size();
+
+ // Make sure that the buffer has at least length of 1, even if
+ // empty HW address is passed. This is required by some of the
+ // MySQL connectors that the buffer is set to non-null value.
+ // Otherwise, null value would be inserted into the database,
+ // rather than empty string.
+ if (hwaddr_.empty()) {
+ hwaddr_.resize(1);
+ }
+
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(&(hwaddr_[0]));
+ bind_[1].buffer_length = hwaddr_length_;
+ bind_[1].length = &hwaddr_length_;
+ } else {
+ bind_[1].buffer_type = MYSQL_TYPE_NULL;
+ // According to http://dev.mysql.com/doc/refman/5.5/en/
+ // c-api-prepared-statement-data-structures.html, the other
+ // fields doesn't matter if type is set to MYSQL_TYPE_NULL,
+ // but let's set them to some sane values in case earlier versions
+ // didn't have that assumption.
+ hwaddr_null_ = MLM_TRUE;
+ bind_[1].buffer = NULL;
+ bind_[1].is_null = &hwaddr_null_;
+ }
+
+ // client_id: varbinary(255)
+ if (lease_->client_id_) {
+ client_id_ = lease_->client_id_->getClientId();
+ client_id_length_ = client_id_.size();
+
+ // Make sure that the buffer has at least length of 1, even if
+ // empty client id is passed. This is required by some of the
+ // MySQL connectors that the buffer is set to non-null value.
+ // Otherwise, null value would be inserted into the database,
+ // rather than empty string.
+ if (client_id_.empty()) {
+ client_id_.resize(1);
+ }
+
+ bind_[2].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[2].buffer = reinterpret_cast<char*>(&client_id_[0]);
+ bind_[2].buffer_length = client_id_length_;
+ bind_[2].length = &client_id_length_;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+ } else {
+ bind_[2].buffer_type = MYSQL_TYPE_NULL;
+ // According to http://dev.mysql.com/doc/refman/5.5/en/
+ // c-api-prepared-statement-data-structures.html, the other
+ // fields doesn't matter if type is set to MYSQL_TYPE_NULL,
+ // but let's set them to some sane values in case earlier versions
+ // didn't have that assumption.
+ client_id_null_ = MLM_TRUE;
+ bind_[2].buffer = NULL;
+ bind_[2].is_null = &client_id_null_;
+ }
+
+ // valid lifetime: unsigned int
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
+ bind_[3].is_unsigned = MLM_TRUE;
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // expire: timestamp
+ // The lease structure holds the client last transmission time (cltt_)
+ // For convenience for external tools, this is converted to lease
+ // expiry time (expire). The relationship is given by:
+ //
+ // expire = cltt_ + valid_lft_
+ // Avoid overflow with infinite valid lifetime by using
+ // expire = cltt_ when valid_lft_ = 0xffffffff
+ uint32_t valid_lft = lease_->valid_lft_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertToDatabaseTime(lease_->cltt_, valid_lft,
+ expire_);
+ bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ bind_[4].buffer = reinterpret_cast<char*>(&expire_);
+ bind_[4].buffer_length = sizeof(expire_);
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // subnet_id: unsigned int
+ // Can use lease_->subnet_id_ directly as it is of type uint32_t.
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_fwd: boolean
+ bind_[6].buffer_type = MYSQL_TYPE_TINY;
+ bind_[6].buffer = reinterpret_cast<char*>(&lease_->fqdn_fwd_);
+ bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_rev: boolean
+ bind_[7].buffer_type = MYSQL_TYPE_TINY;
+ bind_[7].buffer = reinterpret_cast<char*>(&lease_->fqdn_rev_);
+ bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hostname: varchar(255)
+ // Note that previously we used MYSQL_TYPE_VARCHAR instead of
+ // MYSQL_TYPE_STRING. However, that caused 'buffer type not supported'
+ // errors on some systems running MariaDB.
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ bind_[8].buffer = const_cast<char*>(lease_->hostname_.c_str());
+ bind_[8].buffer_length = lease_->hostname_.length();
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // state: uint32_t
+ bind_[9].buffer_type = MYSQL_TYPE_LONG;
+ bind_[9].buffer = reinterpret_cast<char*>(&lease_->state_);
+ bind_[9].is_unsigned = MLM_TRUE;
+ // bind_[9].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // user_context: text
+ ConstElementPtr ctx = lease->getContext();
+ if (ctx) {
+ bind_[10].buffer_type = MYSQL_TYPE_STRING;
+ std::string ctx_txt = ctx->str();
+ strncpy(user_context_, ctx_txt.c_str(), USER_CONTEXT_MAX_LEN - 1);
+ bind_[10].buffer = user_context_;
+ bind_[10].buffer_length = ctx_txt.length();
+ // bind_[10].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+ } else {
+ bind_[10].buffer_type = MYSQL_TYPE_NULL;
+ }
+
+ // relay_id: varbinary(255)
+ relay_id_ = lease_->relay_id_;
+ if (!relay_id_.empty()) {
+ bind_[11].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[11].buffer = reinterpret_cast<char*>(&relay_id_[0]);
+ relay_id_length_ = relay_id_.size();
+ bind_[11].buffer_length = relay_id_length_;
+ bind_[11].length = &relay_id_length_;
+ } else {
+ bind_[11].buffer_type = MYSQL_TYPE_NULL;
+ relay_id_null_ = MLM_TRUE;
+ bind_[11].buffer = NULL;
+ bind_[11].is_null = &relay_id_null_;
+ }
+
+ // remote_id: varbinary(255)
+ remote_id_ = lease_->remote_id_;
+ if (!remote_id_.empty()) {
+ bind_[12].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[12].buffer = reinterpret_cast<char*>(&remote_id_[0]);
+ remote_id_length_ = remote_id_.size();
+ bind_[12].buffer_length = remote_id_length_;
+ bind_[12].length = &remote_id_length_;
+ } else {
+ bind_[12].buffer_type = MYSQL_TYPE_NULL;
+ remote_id_null_ = MLM_TRUE;
+ bind_[12].buffer = NULL;
+ bind_[12].is_null = &remote_id_null_;
+ }
+
+ // pool_id: unsigned int
+ // Can use lease_->pool_id_ directly as it is of type uint32_t.
+ bind_[13].buffer_type = MYSQL_TYPE_LONG;
+ bind_[13].buffer = reinterpret_cast<char*>(&lease_->pool_id_);
+ bind_[13].is_unsigned = MLM_TRUE;
+ // bind_[13].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(13 < LEASE_COLUMNS);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from Lease4: "
+ << lease_->addr_.toText() << ", reason: " << ex.what());
+ }
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
+ }
+
+ /// @brief Create BIND array to receive data
+ ///
+ /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
+ /// After data is successfully received, getLeaseData() can be used to copy
+ /// it to a Lease6 object.
+ std::vector<MYSQL_BIND> createBindForReceive() {
+
+ // Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ memset(bind_, 0, sizeof(bind_));
+
+ // address: uint32_t
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
+ bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hwaddr: varbinary(20)
+ hwaddr_length_ = sizeof(hwaddr_buffer_);
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(hwaddr_buffer_);
+ bind_[1].buffer_length = hwaddr_length_;
+ bind_[1].length = &hwaddr_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // client_id: varbinary(255)
+ client_id_length_ = sizeof(client_id_buffer_);
+ bind_[2].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[2].buffer = reinterpret_cast<char*>(client_id_buffer_);
+ bind_[2].buffer_length = client_id_length_;
+ bind_[2].length = &client_id_length_;
+ bind_[2].is_null = &client_id_null_;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // valid lifetime: unsigned int
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&valid_lifetime_);
+ bind_[3].is_unsigned = MLM_TRUE;
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // expire: timestamp
+ bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ bind_[4].buffer = reinterpret_cast<char*>(&expire_);
+ bind_[4].buffer_length = sizeof(expire_);
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // subnet_id: unsigned int
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&subnet_id_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_fwd: boolean
+ bind_[6].buffer_type = MYSQL_TYPE_TINY;
+ bind_[6].buffer = reinterpret_cast<char*>(&fqdn_fwd_);
+ bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_rev: boolean
+ bind_[7].buffer_type = MYSQL_TYPE_TINY;
+ bind_[7].buffer = reinterpret_cast<char*>(&fqdn_rev_);
+ bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hostname: varchar(255)
+ // Note that previously we used MYSQL_TYPE_VARCHAR instead of
+ // MYSQL_TYPE_STRING. However, that caused 'buffer type not supported'
+ // errors on some systems running MariaDB.
+ hostname_length_ = sizeof(hostname_buffer_);
+ bind_[8].buffer_type = MYSQL_TYPE_STRING;
+ bind_[8].buffer = reinterpret_cast<char*>(hostname_buffer_);
+ bind_[8].buffer_length = hostname_length_;
+ bind_[8].length = &hostname_length_;
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // state: uint32_t
+ bind_[9].buffer_type = MYSQL_TYPE_LONG;
+ bind_[9].buffer = reinterpret_cast<char*>(&state_);
+ bind_[9].is_unsigned = MLM_TRUE;
+ // bind_[9].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // user_context: text
+ user_context_null_ = MLM_FALSE;
+ user_context_length_ = sizeof(user_context_);
+ bind_[10].buffer_type = MYSQL_TYPE_STRING;
+ bind_[10].buffer = reinterpret_cast<char*>(user_context_);
+ bind_[10].buffer_length = user_context_length_;
+ bind_[10].length = &user_context_length_;
+ bind_[10].is_null = &user_context_null_;
+
+ // relay_id: varbinary(255)
+ relay_id_length_ = sizeof(relay_id_buffer_);
+ bind_[11].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[11].buffer = reinterpret_cast<char*>(relay_id_buffer_);
+ bind_[11].buffer_length = relay_id_length_;
+ bind_[11].length = &relay_id_length_;
+ bind_[11].is_null = &relay_id_null_;
+
+ // remote_id: varbinary(255)
+ remote_id_length_ = sizeof(remote_id_buffer_);
+ bind_[12].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[12].buffer = reinterpret_cast<char*>(remote_id_buffer_);
+ bind_[12].buffer_length = remote_id_length_;
+ bind_[12].length = &remote_id_length_;
+ bind_[12].is_null = &remote_id_null_;
+
+ // pool_id: unsigned int
+ bind_[13].buffer_type = MYSQL_TYPE_LONG;
+ bind_[13].buffer = reinterpret_cast<char*>(&pool_id_);
+ bind_[13].is_unsigned = MLM_TRUE;
+ // bind_[13].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(13 < LEASE_COLUMNS);
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
+ }
+
+ /// @brief Copy Received Data into Lease4 Object
+ ///
+ /// Called after the MYSQL_BIND array created by createBindForReceive()
+ /// has been used, this copies data from the internal member variables
+ /// into a Lease4 object.
+ ///
+ /// @return Lease4Ptr Pointer to a Lease6 object holding the relevant
+ /// data.
+ Lease4Ptr getLeaseData() {
+ // Convert times received from the database to times for the lease
+ // structure. See the expire code of createBindForSend for
+ // the infinite valid lifetime special case.
+ time_t cltt = 0;
+ // Recover from overflow
+ uint32_t valid_lft = valid_lifetime_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertFromDatabaseTime(expire_, valid_lft, cltt);
+
+ if (client_id_null_ == MLM_TRUE) {
+ // There's no client-id, so we pass client-id_length_ set to 0
+ client_id_length_ = 0;
+ }
+
+ // Hostname is passed to Lease4 as a string object. We have to create
+ // it from the buffer holding hostname and the buffer length.
+ std::string hostname(hostname_buffer_,
+ hostname_buffer_ + hostname_length_);
+
+ // Set hardware address if it was set
+ HWAddrPtr hwaddr;
+ if (hwaddr_null_ == MLM_FALSE) {
+ hwaddr.reset(new HWAddr(hwaddr_buffer_, hwaddr_length_, HTYPE_ETHER));
+ }
+
+ // Convert user_context to string as well.
+ std::string user_context;
+ if (user_context_null_ == MLM_FALSE) {
+ user_context_[user_context_length_] = '\0';
+ user_context.assign(user_context_);
+ }
+
+ // Set the user context if there is one.
+ ConstElementPtr ctx;
+ if (!user_context.empty()) {
+ ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is not a JSON map");
+ }
+ }
+
+ Lease4Ptr lease(boost::make_shared<Lease4>(addr4_, hwaddr,
+ client_id_buffer_,
+ client_id_length_,
+ valid_lifetime_, cltt,
+ subnet_id_, fqdn_fwd_,
+ fqdn_rev_, hostname));
+
+ // Set state.
+ lease->state_ = state_;
+
+ if (ctx) {
+ lease->setContext(ctx);
+ }
+
+ // Set relay id if it was set.
+ if (relay_id_null_ == MLM_FALSE) {
+ lease->relay_id_.assign(relay_id_buffer_,
+ relay_id_buffer_ + relay_id_length_);
+ }
+
+ // Set remote id if it was set.
+ if (remote_id_null_ == MLM_FALSE) {
+ lease->remote_id_.assign(remote_id_buffer_,
+ remote_id_buffer_ + remote_id_length_);
+ }
+
+ // Set pool ID
+ lease->pool_id_ = pool_id_;
+
+ return (lease);
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @return Comma-separated list of columns in error, or the string
+ /// "(None)".
+ std::string getErrorColumns() {
+ return (getColumnsInError(error_, columns_, LEASE_COLUMNS));
+ }
+
+private:
+
+ // Note: All array lengths are equal to the corresponding variable in the
+ // schema.
+ // Note: Arrays are declared fixed length for speed of creation
+ uint32_t addr4_; ///< IPv4 address
+ MYSQL_BIND bind_[LEASE_COLUMNS]; ///< Bind array
+ std::string columns_[LEASE_COLUMNS]; ///< Column names
+ my_bool error_[LEASE_COLUMNS]; ///< Error array
+ Lease4Ptr lease_; ///< Pointer to lease object
+ std::vector<uint8_t> hwaddr_; ///< Hardware address
+ uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN]; ///< Hardware address buffer
+ unsigned long hwaddr_length_; ///< Length of Hardware address
+ my_bool hwaddr_null_; ///< Used when Hardware address is null
+ std::vector<uint8_t> client_id_; ///< Client identification
+ uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; ///< Client ID buffer
+ unsigned long client_id_length_; ///< Client ID address length
+ my_bool client_id_null_; ///< Used when Client ID is null
+ MYSQL_TIME expire_; ///< Lease expire time
+ uint32_t subnet_id_; ///< Subnet identification
+ uint32_t pool_id_; ///< Pool identification
+ uint32_t valid_lifetime_; ///< Lease time
+ my_bool fqdn_fwd_; ///< Has forward DNS update been performed
+ my_bool fqdn_rev_; ///< Has reverse DNS update been performed
+ char hostname_buffer_[HOSTNAME_MAX_LEN]; ///< Client hostname
+ unsigned long hostname_length_; ///< Length of client hostname
+ uint32_t state_; ///< Lease state
+ char user_context_[USER_CONTEXT_MAX_LEN]; ///< User context
+ unsigned long user_context_length_; ///< Length of user context
+ my_bool user_context_null_; ///< Used when user context is null
+ std::vector<uint8_t> relay_id_; ///< Relay id
+ uint8_t relay_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; ///< Relay id buffer
+ unsigned long relay_id_length_; ///< Relay id length
+ my_bool relay_id_null_; ///< Used when Relay id is null
+ std::vector<uint8_t> remote_id_; ///< Remote id
+ uint8_t remote_id_buffer_[ClientId::MAX_CLIENT_ID_LEN]; ///< Remote id buffer
+ unsigned long remote_id_length_; ///< Remote id length
+ my_bool remote_id_null_; ///< Used when Remote id is null
+};
+
+/// @brief Exchange MySQL and Lease6 Data
+///
+/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
+/// describe the parameters in the prepared statements. Where information is
+/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
+/// structure is identical. This class handles the creation of that array.
+///
+/// Owing to the MySQL API, the process requires some intermediate variables
+/// to hold things like data length etc. This object holds those variables.
+///
+/// @note There are no unit tests for this class. It is tested indirectly
+/// in all MySqlLeaseMgr::xxx6() calls where it is used.
+
+class MySqlLease6Exchange : public MySqlLeaseExchange {
+ /// @brief Column numbers for each column in the Lease6 table.
+ /// These are used for both retrieving data and for looking up
+ /// column labels for logging. Note that their numeric order
+ /// MUST match that of the column order in the Lease6 table.
+ //@{
+ static const size_t ADDRESS_COL = 0;
+ static const size_t DUID_COL = 1;
+ static const size_t VALID_LIFETIME_COL = 2;
+ static const size_t EXPIRE_COL = 3;
+ static const size_t SUBNET_ID_COL = 4;
+ static const size_t PREF_LIFETIME_COL = 5;
+ static const size_t LEASE_TYPE_COL = 6;
+ static const size_t IAID_COL = 7;
+ static const size_t PREFIX_LEN_COL = 8;
+ static const size_t FQDN_FWD_COL = 9;
+ static const size_t FQDN_REV_COL = 10;
+ static const size_t HOSTNAME_COL = 11;
+ static const size_t HWADDR_COL = 12;
+ static const size_t HWTYPE_COL = 13;
+ static const size_t HWADDR_SOURCE_COL = 14;
+ static const size_t STATE_COL = 15;
+ static const size_t USER_CONTEXT_COL = 16;
+ static const size_t POOL_ID_COL = 17;
+ //@}
+ /// @brief Number of columns in the table holding DHCPv6 leases.
+ static const size_t LEASE_COLUMNS = 18;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// The initialization of the variables here is only to satisfy cppcheck -
+ /// all variables are initialized/set in the methods before they are used.
+ MySqlLease6Exchange() : addr6_length_(16), hwaddr_length_(0),
+ hwaddr_null_(MLM_FALSE), duid_length_(0),
+ iaid_(0), lease_type_(0), prefix_len_(0),
+ pref_lifetime_(0), subnet_id_(0), pool_id_(0),
+ valid_lifetime_(0), fqdn_fwd_(false), fqdn_rev_(false),
+ hostname_length_(0), hwtype_(0), hwaddr_source_(0),
+ state_(0), user_context_length_(0),
+ user_context_null_(MLM_FALSE) {
+ memset(addr6_buffer_, 0, sizeof(addr6_buffer_));
+ memset(duid_buffer_, 0, sizeof(duid_buffer_));
+ memset(hostname_buffer_, 0, sizeof(hostname_buffer_));
+ memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
+ memset(user_context_, 0, sizeof(user_context_));
+ std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
+
+ // Set the column names (for error messages)
+ columns_[ADDRESS_COL] = "address";
+ columns_[DUID_COL] = "duid";
+ columns_[VALID_LIFETIME_COL] = "valid_lifetime";
+ columns_[EXPIRE_COL] = "expire";
+ columns_[SUBNET_ID_COL] = "subnet_id";
+ columns_[PREF_LIFETIME_COL] = "pref_lifetime";
+ columns_[LEASE_TYPE_COL] = "lease_type";
+ columns_[IAID_COL] = "iaid";
+ columns_[PREFIX_LEN_COL] = "prefix_len";
+ columns_[FQDN_FWD_COL] = "fqdn_fwd";
+ columns_[FQDN_REV_COL] = "fqdn_rev";
+ columns_[HOSTNAME_COL] = "hostname";
+ columns_[HWADDR_COL] = "hwaddr";
+ columns_[HWTYPE_COL] = "hwtype";
+ columns_[HWADDR_SOURCE_COL] = "hwaddr_source";
+ columns_[STATE_COL] = "state";
+ columns_[USER_CONTEXT_COL] = "user_context";
+ columns_[POOL_ID_COL] = "pool_id";
+ BOOST_STATIC_ASSERT(17 < LEASE_COLUMNS);
+ }
+
+ /// @brief Create MYSQL_BIND objects for Lease6 Pointer
+ ///
+ /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
+ /// the database.
+ ///
+ /// @param lease Lease object to be added to the database.
+ ///
+ /// @return Vector of MySQL BIND objects representing the data to be added.
+ std::vector<MYSQL_BIND> createBindForSend(const Lease6Ptr& lease) {
+ // Store lease object to ensure it remains valid.
+ lease_ = lease;
+
+ // Ensure bind_ array clear for constructing the MYSQL_BIND structures
+ // for this lease.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ memset(bind_, 0, sizeof(bind_));
+
+ try {
+ // address: binary(16)
+ addr6_ = lease->addr_.toBytes();
+ if (addr6_.size() != 16) {
+ isc_throw(DbOperationError, "lease6 address is not 16 bytes long");
+ }
+
+ addr6_length_ = 16;
+ bind_[0].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[0].buffer = reinterpret_cast<char*>(&addr6_[0]);
+ bind_[0].buffer_length = 16;
+ bind_[0].length = &addr6_length_;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // duid: varchar(130)
+ if (!lease_->duid_) {
+ isc_throw(DbOperationError, "lease6 for address " << lease->addr_.toText()
+ << " is missing mandatory client-id.");
+ }
+ duid_ = lease_->duid_->getDuid();
+ duid_length_ = duid_.size();
+
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(&(duid_[0]));
+ bind_[1].buffer_length = duid_length_;
+ bind_[1].length = &duid_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // valid lifetime: unsigned int
+ bind_[2].buffer_type = MYSQL_TYPE_LONG;
+ bind_[2].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
+ bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // expire: timestamp
+ // The lease structure holds the client last transmission time (cltt_)
+ // For convenience for external tools, this is converted to lease
+ // expiry time (expire). The relationship is given by:
+ //
+ // expire = cltt_ + valid_lft_
+ // Avoid overflow with infinite valid lifetime by using
+ // expire = cltt_ when valid_lft_ = 0xffffffff
+ uint32_t valid_lft = lease_->valid_lft_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertToDatabaseTime(lease_->cltt_, valid_lft,
+ expire_);
+ bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ bind_[3].buffer = reinterpret_cast<char*>(&expire_);
+ bind_[3].buffer_length = sizeof(expire_);
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // subnet_id: unsigned int
+ // Can use lease_->subnet_id_ directly as it is of type uint32_t.
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // pref_lifetime: unsigned int
+ // Can use lease_->preferred_lft_ directly as it is of type uint32_t.
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&lease_->preferred_lft_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // lease_type: tinyint
+ // Must convert to uint8_t as lease_->type_ is a LeaseType variable.
+ lease_type_ = lease_->type_;
+ bind_[6].buffer_type = MYSQL_TYPE_TINY;
+ bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
+ bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // iaid: unsigned int
+ // Can use lease_->iaid_ directly as it is of type uint32_t.
+ bind_[7].buffer_type = MYSQL_TYPE_LONG;
+ bind_[7].buffer = reinterpret_cast<char*>(&lease_->iaid_);
+ bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // prefix_len: unsigned tinyint
+ // Can use lease_->prefixlen_ directly as it is uint32_t.
+ bind_[8].buffer_type = MYSQL_TYPE_TINY;
+ bind_[8].buffer = reinterpret_cast<char*>(&lease_->prefixlen_);
+ bind_[8].is_unsigned = MLM_TRUE;
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_fwd: boolean
+ bind_[9].buffer_type = MYSQL_TYPE_TINY;
+ bind_[9].buffer = reinterpret_cast<char*>(&lease_->fqdn_fwd_);
+ bind_[9].is_unsigned = MLM_TRUE;
+ // bind_[9].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_rev: boolean
+ bind_[10].buffer_type = MYSQL_TYPE_TINY;
+ bind_[10].buffer = reinterpret_cast<char*>(&lease_->fqdn_rev_);
+ bind_[10].is_unsigned = MLM_TRUE;
+ // bind_[10].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hostname: varchar(255)
+ bind_[11].buffer_type = MYSQL_TYPE_STRING;
+ bind_[11].buffer = const_cast<char*>(lease_->hostname_.c_str());
+ bind_[11].buffer_length = lease_->hostname_.length();
+ // bind_[11].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hwaddr: varbinary(20) - hardware/MAC address
+ HWAddrPtr hwaddr = lease_->hwaddr_;
+ if (hwaddr) {
+ hwaddr_ = hwaddr->hwaddr_;
+ hwaddr_length_ = hwaddr->hwaddr_.size();
+
+ // Make sure that the buffer has at least length of 1, even if
+ // empty HW address is passed. This is required by some of the
+ // MySQL connectors that the buffer is set to non-null value.
+ // Otherwise, null value would be inserted into the database,
+ // rather than empty string.
+ if (hwaddr_.empty()) {
+ hwaddr_.resize(1);
+ }
+
+ bind_[12].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[12].buffer = reinterpret_cast<char*>(&(hwaddr_[0]));
+ bind_[12].buffer_length = hwaddr_length_;
+ bind_[12].length = &hwaddr_length_;
+ } else {
+ bind_[12].buffer_type = MYSQL_TYPE_NULL;
+ // According to http://dev.mysql.com/doc/refman/5.5/en/
+ // c-api-prepared-statement-data-structures.html, the other
+ // fields doesn't matter if type is set to MYSQL_TYPE_NULL,
+ // but let's set them to some sane values in case earlier versions
+ // didn't have that assumption.
+ hwaddr_null_ = MLM_TRUE;
+ bind_[12].buffer = NULL;
+ bind_[12].is_null = &hwaddr_null_;
+ }
+
+ // hardware type: unsigned short int (16 bits)
+ if (hwaddr) {
+ hwtype_ = lease->hwaddr_->htype_;
+ bind_[13].buffer_type = MYSQL_TYPE_SHORT;
+ bind_[13].buffer = reinterpret_cast<char*>(&hwtype_);
+ bind_[13].is_unsigned = MLM_TRUE;
+ } else {
+ hwtype_ = 0;
+ bind_[13].buffer_type = MYSQL_TYPE_NULL;
+ // According to http://dev.mysql.com/doc/refman/5.5/en/
+ // c-api-prepared-statement-data-structures.html, the other
+ // fields doesn't matter if type is set to MYSQL_TYPE_NULL,
+ // but let's set them to some sane values in case earlier versions
+ // didn't have that assumption.
+ hwaddr_null_ = MLM_TRUE;
+ bind_[13].buffer = NULL;
+ bind_[13].is_null = &hwaddr_null_;
+ }
+
+ // hardware source: unsigned int (32 bits)
+ if (hwaddr) {
+ hwaddr_source_ = lease->hwaddr_->source_;
+ bind_[14].buffer_type = MYSQL_TYPE_LONG;
+ bind_[14].buffer = reinterpret_cast<char*>(&hwaddr_source_);
+ bind_[14].is_unsigned = MLM_TRUE;
+ } else {
+ hwaddr_source_ = 0;
+ bind_[14].buffer_type = MYSQL_TYPE_NULL;
+ // According to http://dev.mysql.com/doc/refman/5.5/en/
+ // c-api-prepared-statement-data-structures.html, the other
+ // fields doesn't matter if type is set to MYSQL_TYPE_NULL,
+ // but let's set them to some sane values in case earlier versions
+ // didn't have that assumption.
+ hwaddr_null_ = MLM_TRUE;
+ bind_[14].buffer = NULL;
+ bind_[14].is_null = &hwaddr_null_;
+ }
+
+ // state: uint32_t
+ bind_[15].buffer_type = MYSQL_TYPE_LONG;
+ bind_[15].buffer = reinterpret_cast<char*>(&lease_->state_);
+ bind_[15].is_unsigned = MLM_TRUE;
+ // bind_[15].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // user_context: text
+ ConstElementPtr ctx = lease->getContext();
+ if (ctx) {
+ bind_[16].buffer_type = MYSQL_TYPE_STRING;
+ std::string ctx_txt = ctx->str();
+ strncpy(user_context_, ctx_txt.c_str(), USER_CONTEXT_MAX_LEN - 1);
+ bind_[16].buffer = user_context_;
+ bind_[16].buffer_length = ctx_txt.length();
+ // bind_[16].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+ } else {
+ bind_[16].buffer_type = MYSQL_TYPE_NULL;
+ }
+
+ // pool_id: unsigned int
+ // Can use lease_->pool_id_ directly as it is of type uint32_t.
+ bind_[17].buffer_type = MYSQL_TYPE_LONG;
+ bind_[17].buffer = reinterpret_cast<char*>(&lease_->pool_id_);
+ bind_[17].is_unsigned = MLM_TRUE;
+ // bind_[17].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(17 < LEASE_COLUMNS);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from Lease6: "
+ << lease_->addr_.toText() << ", reason: " << ex.what());
+ }
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
+ }
+
+ /// @brief Create BIND array to receive data
+ ///
+ /// Creates a MYSQL_BIND array to receive Lease6 data from the database.
+ /// After data is successfully received, getLeaseData() is used to copy
+ /// it to a Lease6 object.
+ ///
+ /// @return Vector of MySQL BIND objects passed to the MySQL data retrieval
+ /// functions.
+ std::vector<MYSQL_BIND> createBindForReceive() {
+
+ // Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
+ memset(bind_, 0, sizeof(bind_));
+
+ // address: binary(16)
+ addr6_length_ = 16;
+ bind_[0].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[0].buffer = reinterpret_cast<char*>(addr6_buffer_);
+ bind_[0].buffer_length = addr6_length_;
+ bind_[0].length = &addr6_length_;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // duid: varbinary(130)
+ duid_length_ = sizeof(duid_buffer_);
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(duid_buffer_);
+ bind_[1].buffer_length = duid_length_;
+ bind_[1].length = &duid_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // valid lifetime: unsigned int
+ bind_[2].buffer_type = MYSQL_TYPE_LONG;
+ bind_[2].buffer = reinterpret_cast<char*>(&valid_lifetime_);
+ bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // expire: timestamp
+ bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ bind_[3].buffer = reinterpret_cast<char*>(&expire_);
+ bind_[3].buffer_length = sizeof(expire_);
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // subnet_id: unsigned int
+ bind_[4].buffer_type = MYSQL_TYPE_LONG;
+ bind_[4].buffer = reinterpret_cast<char*>(&subnet_id_);
+ bind_[4].is_unsigned = MLM_TRUE;
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // pref_lifetime: unsigned int
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&pref_lifetime_);
+ bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // lease_type: tinyint
+ bind_[6].buffer_type = MYSQL_TYPE_TINY;
+ bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
+ bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // iaid: unsigned int
+ bind_[7].buffer_type = MYSQL_TYPE_LONG;
+ bind_[7].buffer = reinterpret_cast<char*>(&iaid_);
+ bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // prefix_len: unsigned tinyint
+ bind_[8].buffer_type = MYSQL_TYPE_TINY;
+ bind_[8].buffer = reinterpret_cast<char*>(&prefix_len_);
+ bind_[8].is_unsigned = MLM_TRUE;
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_fwd: boolean
+ bind_[9].buffer_type = MYSQL_TYPE_TINY;
+ bind_[9].buffer = reinterpret_cast<char*>(&fqdn_fwd_);
+ bind_[9].is_unsigned = MLM_TRUE;
+ // bind_[9].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // fqdn_rev: boolean
+ bind_[10].buffer_type = MYSQL_TYPE_TINY;
+ bind_[10].buffer = reinterpret_cast<char*>(&fqdn_rev_);
+ bind_[10].is_unsigned = MLM_TRUE;
+ // bind_[10].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hostname: varchar(255)
+ hostname_length_ = sizeof(hostname_buffer_);
+ bind_[11].buffer_type = MYSQL_TYPE_STRING;
+ bind_[11].buffer = reinterpret_cast<char*>(hostname_buffer_);
+ bind_[11].buffer_length = hostname_length_;
+ bind_[11].length = &hostname_length_;
+ // bind_[11].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // hwaddr: varbinary(20)
+ hwaddr_null_ = MLM_FALSE;
+ hwaddr_length_ = sizeof(hwaddr_buffer_);
+ bind_[12].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[12].buffer = reinterpret_cast<char*>(hwaddr_buffer_);
+ bind_[12].buffer_length = hwaddr_length_;
+ bind_[12].length = &hwaddr_length_;
+ bind_[12].is_null = &hwaddr_null_;
+
+ // hardware type: unsigned short int (16 bits)
+ bind_[13].buffer_type = MYSQL_TYPE_SHORT;
+ bind_[13].buffer = reinterpret_cast<char*>(&hwtype_);
+ bind_[13].is_unsigned = MLM_TRUE;
+
+ // hardware source: unsigned int (32 bits)
+ bind_[14].buffer_type = MYSQL_TYPE_LONG;
+ bind_[14].buffer = reinterpret_cast<char*>(&hwaddr_source_);
+ bind_[14].is_unsigned = MLM_TRUE;
+
+ // state: uint32_t
+ bind_[15].buffer_type = MYSQL_TYPE_LONG;
+ bind_[15].buffer = reinterpret_cast<char*>(&state_);
+ bind_[15].is_unsigned = MLM_TRUE;
+ // bind_[15].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // user_context: text
+ user_context_null_ = MLM_FALSE;
+ user_context_length_ = sizeof(user_context_);
+ bind_[16].buffer_type = MYSQL_TYPE_STRING;
+ bind_[16].buffer = reinterpret_cast<char*>(user_context_);
+ bind_[16].buffer_length = user_context_length_;
+ bind_[16].length = &user_context_length_;
+ bind_[16].is_null = &user_context_null_;
+
+ // pool_id: unsigned int
+ bind_[17].buffer_type = MYSQL_TYPE_LONG;
+ bind_[17].buffer = reinterpret_cast<char*>(&pool_id_);
+ bind_[17].is_unsigned = MLM_TRUE;
+ // bind_[17].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(17 < LEASE_COLUMNS);
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
+ }
+
+ /// @brief Copy Received Data into Lease6 Object
+ ///
+ /// Called after the MYSQL_BIND array created by createBindForReceive()
+ /// has been used, this copies data from the internal member variables
+ /// into a Lease6 object.
+ ///
+ /// @return Lease6Ptr Pointer to a Lease6 object holding the relevant
+ /// data.
+ ///
+ /// @throw isc::BadValue Unable to convert Lease Type value in database
+ Lease6Ptr getLeaseData() {
+ // Convert lease from network-order bytes to IOAddress.
+ IOAddress addr = IOAddress::fromBytes(AF_INET6, addr6_buffer_);
+ std::string address = addr.toText();
+
+ // Set the lease type in a variable of the appropriate data type, which
+ // has been initialized with an arbitrary (but valid) value.
+ Lease::Type type = Lease::TYPE_NA;
+ switch (lease_type_) {
+ case Lease::TYPE_NA:
+ type = Lease::TYPE_NA;
+ break;
+
+ case Lease::TYPE_TA:
+ type = Lease::TYPE_TA;
+ break;
+
+ case Lease::TYPE_PD:
+ type = Lease::TYPE_PD;
+ break;
+
+ default:
+ isc_throw(BadValue, "invalid lease type returned (" <<
+ static_cast<int>(lease_type_) << ") for lease with "
+ << "address " << address << ". Only 0, 1, or 2 are "
+ << "allowed.");
+ }
+
+ if (type != Lease::TYPE_PD) {
+ prefix_len_ = 128;
+ }
+
+ // Set up DUID,
+ DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_));
+
+ // Hostname is passed to Lease6 as a string object, so we have to
+ // create it from the hostname buffer and length.
+ std::string hostname(hostname_buffer_,
+ hostname_buffer_ + hostname_length_);
+
+ // Set hardware address if it was set
+ HWAddrPtr hwaddr;
+ if (hwaddr_null_ == MLM_FALSE) {
+ hwaddr.reset(new HWAddr(hwaddr_buffer_, hwaddr_length_, hwtype_));
+ hwaddr->source_ = hwaddr_source_;
+ }
+
+ // Convert user_context to string as well.
+ std::string user_context;
+ if (user_context_null_ == MLM_FALSE) {
+ user_context_[user_context_length_] = '\0';
+ user_context.assign(user_context_);
+ }
+
+ // Set the user context if there is one.
+ ConstElementPtr ctx;
+ if (!user_context.empty()) {
+ ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is not a JSON map");
+ }
+ }
+
+ // Create the lease and set the cltt (after converting from the
+ // expire time retrieved from the database).
+ Lease6Ptr result(boost::make_shared<Lease6>(type, addr, duid_ptr, iaid_,
+ pref_lifetime_,
+ valid_lifetime_, subnet_id_,
+ fqdn_fwd_, fqdn_rev_,
+ hostname, hwaddr,
+ prefix_len_));
+ time_t cltt = 0;
+ // Recover from overflow (see expire code of createBindForSend).
+ uint32_t valid_lft = valid_lifetime_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertFromDatabaseTime(expire_, valid_lft, cltt);
+ // Update cltt_ and current_cltt_ explicitly.
+ result->cltt_ = cltt;
+ result->current_cltt_ = cltt;
+
+ // Set state.
+ result->state_ = state_;
+
+ if (ctx) {
+ result->setContext(ctx);
+ }
+
+ // Set pool ID.
+ result->pool_id_ = pool_id_;
+
+ return (result);
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @return Comma-separated list of columns in error, or the string
+ /// "(None)".
+ std::string getErrorColumns() {
+ return (getColumnsInError(error_, columns_, LEASE_COLUMNS));
+ }
+
+private:
+
+ // Note: All array lengths are equal to the corresponding variable in the
+ // schema.
+ // Note: arrays are declared fixed length for speed of creation
+ std::vector<uint8_t> addr6_; ///< Binary address
+ uint8_t addr6_buffer_[16]; ///< Binary address buffer
+ unsigned long addr6_length_; ///< Binary address length
+ MYSQL_BIND bind_[LEASE_COLUMNS]; ///< Bind array
+ std::string columns_[LEASE_COLUMNS]; ///< Column names
+ my_bool error_[LEASE_COLUMNS]; ///< Error array
+ Lease6Ptr lease_; ///< Pointer to lease object
+ std::vector<uint8_t> hwaddr_; ///< Hardware address
+ uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN]; ///< Hardware address buffer
+ unsigned long hwaddr_length_; ///< Length of Hardware address
+ my_bool hwaddr_null_; ///< Used when Hardware address is null
+ std::vector<uint8_t> duid_; ///< DUID
+ uint8_t duid_buffer_[DUID::MAX_DUID_LEN]; ///< DUID buffer
+ unsigned long duid_length_; ///< Length of DUID
+ MYSQL_TIME expire_; ///< Lease expire time
+ uint32_t iaid_; ///< Identity association ID
+ uint8_t lease_type_; ///< Lease type
+ uint8_t prefix_len_; ///< Prefix length
+ uint32_t pref_lifetime_; ///< Preferred lifetime
+ uint32_t subnet_id_; ///< Subnet identification
+ uint32_t pool_id_; ///< Pool identification
+ uint32_t valid_lifetime_; ///< Lease time
+ my_bool fqdn_fwd_; ///< Has forward DNS update been performed
+ my_bool fqdn_rev_; ///< Has reverse DNS update been performed
+ char hostname_buffer_[HOSTNAME_MAX_LEN]; ///< Client hostname
+ unsigned long hostname_length_; ///< Length of client hostname
+ uint16_t hwtype_; ///< Hardware type
+ uint32_t hwaddr_source_; ///< Source of the hardware address
+ uint32_t state_; ///< Lease state
+ char user_context_[USER_CONTEXT_MAX_LEN]; ///< User context
+ unsigned long user_context_length_; ///< Length of user context
+ my_bool user_context_null_; ///< Used when user context is null
+};
+
+/// @brief MySql derivation of the statistical lease data query
+///
+/// This class is used to recalculate lease statistics for MySQL
+/// lease storage. It does so by executing a query which returns a result
+/// containing one row per monitored state per lease type per
+/// subnet, ordered by subnet id in ascending order.
+
+class MySqlLeaseStatsQuery : public LeaseStatsQuery {
+public:
+
+ /// @brief Constructor to query for all subnets' stats
+ ///
+ /// The query created will return statistics for all subnets
+ ///
+ /// @param conn An open connection to the database housing the lease data
+ /// @param statement_index Index of the query's prepared statement
+ /// @param fetch_type Indicates if query supplies lease type
+ /// @param fetch_pool Indicates if query requires pool data
+ /// @throw if statement index is invalid.
+ MySqlLeaseStatsQuery(MySqlConnection& conn, const size_t statement_index,
+ const bool fetch_type, const bool fetch_pool = false)
+ : conn_(conn), statement_index_(statement_index), statement_(NULL),
+ fetch_type_(fetch_type), fetch_pool_(fetch_pool),
+ // Set the number of columns in the bind array based on fetch_type
+ // This is the number of columns expected in the result set
+ bind_(fetch_type_ ? (fetch_pool_ ? 5 : 4) : (fetch_pool_ ? 4 : 3)),
+ subnet_id_(0), pool_id_(0), lease_type_(Lease::TYPE_NA),
+ state_(Lease::STATE_DEFAULT), state_count_(0) {
+ validateStatement();
+ }
+
+ /// @brief Constructor to query for a single subnet's stats
+ ///
+ /// The query created will return statistics for a single subnet
+ ///
+ /// @param conn An open connection to the database housing the lease data
+ /// @param statement_index Index of the query's prepared statement
+ /// @param fetch_type Indicates if query supplies lease type
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @throw BadValue if subnet_id given is 0 or if statement index is invalid.
+ MySqlLeaseStatsQuery(MySqlConnection& conn, const size_t statement_index,
+ const bool fetch_type, const SubnetID& subnet_id)
+ : LeaseStatsQuery(subnet_id), conn_(conn), statement_index_(statement_index),
+ statement_(NULL), fetch_type_(fetch_type), fetch_pool_(false),
+ // Set the number of columns in the bind array based on fetch_type
+ // This is the number of columns expected in the result set
+ bind_(fetch_type_ ? 4 : 3), subnet_id_(0), pool_id_(0),
+ lease_type_(Lease::TYPE_NA), state_(Lease::STATE_DEFAULT),
+ state_count_(0) {
+ validateStatement();
+ }
+
+ /// @brief Constructor to query for the stats for a range of subnets
+ ///
+ /// The query created will return statistics for the inclusive range of
+ /// subnets described by first and last subnet IDs.
+ ///
+ /// @param conn An open connection to the database housing the lease data
+ /// @param statement_index Index of the query's prepared statement
+ /// @param fetch_type Indicates if query supplies lease type
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @throw BadValue if either subnet ID is 0 or if last <= first or
+ /// if statement index is invalid.
+ MySqlLeaseStatsQuery(MySqlConnection& conn, const size_t statement_index,
+ const bool fetch_type, const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id)
+ : LeaseStatsQuery(first_subnet_id, last_subnet_id), conn_(conn),
+ statement_index_(statement_index), statement_(NULL),
+ fetch_type_(fetch_type), fetch_pool_(false),
+ // Set the number of columns in the bind array based on fetch_type
+ // This is the number of columns expected in the result set
+ bind_(fetch_type_ ? 4 : 3), subnet_id_(0), pool_id_(0),
+ lease_type_(Lease::TYPE_NA), state_(Lease::STATE_DEFAULT),
+ state_count_(0) {
+ validateStatement();
+ }
+
+ /// @brief Destructor
+ virtual ~MySqlLeaseStatsQuery() {
+ (void) mysql_stmt_free_result(statement_);
+ }
+
+ /// @brief Creates the IPv4 lease statistical data result set
+ ///
+ /// The result set is populated by executing a SQL query against the
+ /// lease(4/6) table which sums the leases per lease state per lease
+ /// type (v6 only) per subnet id. This method binds the statement to
+ /// the output bind array and then executes the statement, and fetches
+ /// entire result set.
+ void start() {
+ // Set up where clause inputs if needed.
+ if (getSelectMode() != ALL_SUBNETS && getSelectMode() != ALL_SUBNET_POOLS) {
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Add first_subnet_id used by both single and range.
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&first_subnet_id_);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Add last_subnet_id for range.
+ if (getSelectMode() == SUBNET_RANGE) {
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&last_subnet_id_);
+ inbind[1].is_unsigned = MLM_TRUE;
+ }
+
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(statement_, &inbind[0]);
+ conn_.checkError(status, statement_index_, "unable to bind parameters");
+ }
+
+ int col = 0;
+ // subnet_id: unsigned int
+ bind_[col].buffer_type = MYSQL_TYPE_LONG;
+ bind_[col].buffer = reinterpret_cast<char*>(&subnet_id_);
+ bind_[col].is_unsigned = MLM_TRUE;
+ ++col;
+
+ // Fetch the pool id if we were told to do so.
+ if (fetch_pool_) {
+ // pool id: uint32_t
+ bind_[col].buffer_type = MYSQL_TYPE_LONG;
+ bind_[col].buffer = reinterpret_cast<char*>(&pool_id_);
+ bind_[col].is_unsigned = MLM_TRUE;
+ ++col;
+ }
+
+ // Fetch the lease type if we were told to do so.
+ if (fetch_type_) {
+ // lease type: uint32_t
+ bind_[col].buffer_type = MYSQL_TYPE_LONG;
+ bind_[col].buffer = reinterpret_cast<char*>(&lease_type_);
+ bind_[col].is_unsigned = MLM_TRUE;
+ ++col;
+ } else {
+ fetch_type_ = Lease::TYPE_NA;
+ }
+
+ // state: uint32_t
+ bind_[col].buffer_type = MYSQL_TYPE_LONG;
+ bind_[col].buffer = reinterpret_cast<char*>(&state_);
+ bind_[col].is_unsigned = MLM_TRUE;
+ ++col;
+
+ // state_count_: int64_t
+ bind_[col].buffer_type = MYSQL_TYPE_LONGLONG;
+ bind_[col].buffer = reinterpret_cast<char*>(&state_count_);
+ //bind_[col].is_unsigned = MLM_FALSE;
+
+ // Set up the MYSQL_BIND array for the data being returned
+ // and bind it to the statement.
+ int status = mysql_stmt_bind_result(statement_, &bind_[0]);
+ conn_.checkError(status, statement_index_, "outbound binding failed");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(statement_);
+ conn_.checkError(status, statement_index_, "unable to execute");
+
+ // Ensure that all the lease information is retrieved in one go to avoid
+ // overhead of going back and forth between client and server.
+ status = mysql_stmt_store_result(statement_);
+ conn_.checkError(status, statement_index_, "results storage failed");
+ }
+
+ /// @brief Fetches the next row in the result set
+ ///
+ /// Once the internal result set has been populated by invoking the
+ /// the start() method, this method is used to iterate over the
+ /// result set rows. Once the last row has been fetched, subsequent
+ /// calls will return false.
+ ///
+ /// Checks against negative values for the state count and logs once
+ /// a warning message. Unfortunately not getting the message is not
+ /// a proof that detailed counters are correct.
+ ///
+ /// @param row Storage for the fetched row
+ ///
+ /// @return True if the fetch succeeded, false if there are no more
+ /// rows to fetch.
+ bool getNextRow(LeaseStatsRow& row) {
+ bool have_row = false;
+ int status = mysql_stmt_fetch(statement_);
+ if (status == MLM_MYSQL_FETCH_SUCCESS) {
+ row.subnet_id_ = static_cast<SubnetID>(subnet_id_);
+ row.pool_id_ = pool_id_;
+ row.lease_type_ = static_cast<Lease::Type>(lease_type_);
+ row.lease_state_ = state_;
+ if (state_count_ >= 0) {
+ row.state_count_ = state_count_;
+ } else {
+ row.state_count_ = 0;
+ if (!negative_count_) {
+ negative_count_ = true;
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MYSQL_NEGATIVE_LEASES_STAT);
+ }
+ }
+ have_row = true;
+ } else if (status != MYSQL_NO_DATA) {
+ conn_.checkError(status, statement_index_, "getNextRow failed");
+ }
+
+ return (have_row);
+ }
+
+private:
+
+ /// @brief Validate the statement index passed to the constructor
+ /// Safely fetch the statement from the connection based on statement index
+ /// @throw BadValue if statement index is out of range
+ void validateStatement() {
+ if (statement_index_ >= MySqlLeaseMgr::NUM_STATEMENTS) {
+ isc_throw(BadValue, "MySqlLeaseStatsQuery"
+ " - invalid statement index" << statement_index_);
+ }
+
+ statement_ = conn_.statements_[statement_index_];
+ }
+
+ /// @brief Database connection to use to execute the query
+ MySqlConnection& conn_;
+
+ /// @brief Index of the query's prepared statement
+ size_t statement_index_;
+
+ /// @brief The query's prepared statement
+ MYSQL_STMT *statement_;
+
+ /// @brief Indicates if query supplies lease type
+ bool fetch_type_;
+
+ /// @brief Indicates if query requires pool data
+ bool fetch_pool_;
+
+ /// @brief Bind array used to store the query result set;
+ std::vector<MYSQL_BIND> bind_;
+
+ /// @brief Receives subnet ID when fetching a row
+ uint32_t subnet_id_;
+
+ /// @brief Receives pool ID when fetching a row
+ uint32_t pool_id_;
+
+ /// @brief Receives the lease type when fetching a row
+ uint32_t lease_type_;
+
+ /// @brief Receives the lease state when fetching a row
+ uint32_t state_;
+
+ /// @brief Receives the state count when fetching a row
+ int64_t state_count_;
+
+ /// @brief Received negative state count showing a problem
+ static bool negative_count_;
+};
+
+// Initialize negative state count flag to false.
+bool MySqlLeaseStatsQuery::negative_count_ = false;
+
+// MySqlLeaseContext Constructor
+
+MySqlLeaseContext::MySqlLeaseContext(const DatabaseConnection::ParameterMap& parameters,
+ IOServiceAccessorPtr io_service_accessor,
+ DbCallback db_reconnect_callback)
+ : conn_(parameters, io_service_accessor, db_reconnect_callback) {
+}
+
+// MySqlLeaseContextAlloc Constructor and Destructor
+
+MySqlLeaseMgr::MySqlLeaseContextAlloc::MySqlLeaseContextAlloc(
+ const MySqlLeaseMgr& mgr) : ctx_(), mgr_(mgr) {
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ {
+ // we need to protect the whole pool_ operation, hence extra scope {}
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (!mgr_.pool_->pool_.empty()) {
+ ctx_ = mgr_.pool_->pool_.back();
+ mgr_.pool_->pool_.pop_back();
+ }
+ }
+ if (!ctx_) {
+ ctx_ = mgr_.createContext();
+ }
+ } else {
+ // single-threaded
+ if (mgr_.pool_->pool_.empty()) {
+ isc_throw(Unexpected, "No available MySQL lease context?!");
+ }
+ ctx_ = mgr_.pool_->pool_.back();
+ }
+}
+
+MySqlLeaseMgr::MySqlLeaseContextAlloc::~MySqlLeaseContextAlloc() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ mgr_.pool_->pool_.push_back(ctx_);
+ }
+ // If running in single-threaded mode, there's nothing to do here.
+}
+
+// MySqlLeaseTrackingContextAlloc Constructor and Destructor
+
+MySqlLeaseMgr::MySqlLeaseTrackingContextAlloc::MySqlLeaseTrackingContextAlloc(
+ MySqlLeaseMgr& mgr, const LeasePtr& lease) : ctx_(), mgr_(mgr), lease_(lease) {
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ {
+ // we need to protect the whole pool_ operation, hence extra scope {}
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (mgr_.hasCallbacks() && !mgr_.tryLock(lease)) {
+ isc_throw(DbOperationError, "unable to lock the lease " << lease->addr_);
+ }
+ if (!mgr_.pool_->pool_.empty()) {
+ ctx_ = mgr_.pool_->pool_.back();
+ mgr_.pool_->pool_.pop_back();
+ }
+ }
+ if (!ctx_) {
+ ctx_ = mgr_.createContext();
+ }
+ } else {
+ // single-threaded
+ if (mgr_.pool_->pool_.empty()) {
+ isc_throw(Unexpected, "No available MySQL lease context?!");
+ }
+ ctx_ = mgr_.pool_->pool_.back();
+ }
+}
+
+MySqlLeaseMgr::MySqlLeaseTrackingContextAlloc::~MySqlLeaseTrackingContextAlloc() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (mgr_.hasCallbacks()) {
+ mgr_.unlock(lease_);
+ }
+ mgr_.pool_->pool_.push_back(ctx_);
+ }
+ // If running in single-threaded mode, there's nothing to do here.
+}
+
+void
+MySqlLeaseMgr::setExtendedInfoTablesEnabled(const db::DatabaseConnection::ParameterMap& /* parameters */) {
+ isc_throw(isc::NotImplemented, "extended info tables are not yet supported by mysql");
+}
+
+// MySqlLeaseMgr Constructor and Destructor
+
+MySqlLeaseMgr::MySqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+ : TrackingLeaseMgr(), parameters_(parameters), timer_name_("") {
+
+ // Check if the extended info tables are enabled.
+ LeaseMgr::setExtendedInfoTablesEnabled(parameters);
+
+ // Create unique timer name per instance.
+ timer_name_ = "MySqlLeaseMgr[";
+ timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+ timer_name_ += "]DbReconnectTimer";
+
+ // Validate schema version first.
+ std::pair<uint32_t, uint32_t> code_version(MYSQL_SCHEMA_VERSION_MAJOR,
+ MYSQL_SCHEMA_VERSION_MINOR);
+ std::pair<uint32_t, uint32_t> db_version = getVersion();
+ if (code_version != db_version) {
+ isc_throw(DbOpenError,
+ "MySQL schema version mismatch: need version: "
+ << code_version.first << "." << code_version.second
+ << " found version: " << db_version.first << "."
+ << db_version.second);
+ }
+
+ // Create an initial context.
+ pool_.reset(new MySqlLeaseContextPool());
+ pool_->pool_.push_back(createContext());
+}
+
+MySqlLeaseMgr::~MySqlLeaseMgr() {
+}
+
+bool
+MySqlLeaseMgr::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
+ MultiThreadingCriticalSection cs;
+
+ // Invoke application layer connection lost callback.
+ if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+
+ bool reopened = false;
+
+ const std::string timer_name = db_reconnect_ctl->timerName();
+
+ // At least one connection was lost.
+ try {
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
+ LeaseMgrFactory::recreate(cfg_db->getLeaseDbAccessString());
+ reopened = true;
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED)
+ .arg(ex.what());
+ }
+
+ if (reopened) {
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection recovered callback.
+ if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+ } else {
+ if (!db_reconnect_ctl->checkRetries()) {
+ // We're out of retries, log it and initiate shutdown.
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_LEASE_DB_RECONNECT_FAILED)
+ .arg(db_reconnect_ctl->maxRetries());
+
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection failed callback.
+ DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
+ return (false);
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE)
+ .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
+ .arg(db_reconnect_ctl->maxRetries())
+ .arg(db_reconnect_ctl->retryInterval());
+
+ // Start the timer.
+ if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->registerTimer(timer_name,
+ std::bind(&MySqlLeaseMgr::dbReconnect, db_reconnect_ctl),
+ db_reconnect_ctl->retryInterval(),
+ asiolink::IntervalTimer::ONE_SHOT);
+ }
+ TimerMgr::instance()->setup(timer_name);
+ }
+
+ return (true);
+}
+
+// Create context.
+
+MySqlLeaseContextPtr
+MySqlLeaseMgr::createContext() const {
+ MySqlLeaseContextPtr ctx(new MySqlLeaseContext(parameters_,
+ IOServiceAccessorPtr(new IOServiceAccessor(&LeaseMgr::getIOService)),
+ &MySqlLeaseMgr::dbReconnect));
+
+ // Open the database.
+ ctx->conn_.openDatabase();
+
+ // Check if we have TLS when we required it.
+ if (ctx->conn_.getTls()) {
+ std::string cipher = ctx->conn_.getTlsCipher();
+ if (cipher.empty()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_MYSQL_NO_TLS);
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_MYSQL_TLS_CIPHER)
+ .arg(cipher);
+ }
+ }
+
+ // Prepare all statements likely to be used.
+ ctx->conn_.prepareStatements(tagged_statements.begin(),
+ tagged_statements.end());
+
+ // Create the exchange objects for use in exchanging data between the
+ // program and the database.
+ ctx->exchange4_.reset(new MySqlLease4Exchange());
+ ctx->exchange6_.reset(new MySqlLease6Exchange());
+
+ // Create ReconnectCtl for this connection.
+ ctx->conn_.makeReconnectCtl(timer_name_);
+
+ return (ctx);
+}
+
+std::string
+MySqlLeaseMgr::getDBVersion() {
+ std::stringstream tmp;
+ tmp << "MySQL backend " << MYSQL_SCHEMA_VERSION_MAJOR;
+ tmp << "." << MYSQL_SCHEMA_VERSION_MINOR;
+ tmp << ", library " << mysql_get_client_info();
+ return (tmp.str());
+}
+
+// Add leases to the database. The two public methods accept a lease object
+// (either V4 of V6), bind the contents to the appropriate prepared
+// statement, then call common code to execute the statement.
+
+bool
+MySqlLeaseMgr::addLeaseCommon(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ std::vector<MYSQL_BIND>& bind) {
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], &bind[0]);
+ checkError(ctx, status, stindex, "unable to bind parameters");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(ctx->conn_.statements_[stindex]);
+ if (status != 0) {
+
+ // Failure: check for the special case of duplicate entry. If this is
+ // the case, we return false to indicate that the row was not added.
+ // Otherwise we throw an exception.
+ if (mysql_errno(ctx->conn_.mysql_) == ER_DUP_ENTRY) {
+ return (false);
+ }
+ checkError(ctx, status, stindex, "unable to execute");
+ }
+
+ // Insert succeeded
+ return (true);
+}
+
+bool
+MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ADD_ADDR4)
+ .arg(lease->addr_.toText());
+
+ // Get a context
+ MySqlLeaseTrackingContextAlloc get_context(*this, lease);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create the MYSQL_BIND array for the lease
+ std::vector<MYSQL_BIND> bind = ctx->exchange4_->createBindForSend(lease);
+
+ // ... and drop to common code.
+ auto result = addLeaseCommon(ctx, INSERT_LEASE4, bind);
+
+ // Update lease current expiration time (allows update between the creation
+ // of the Lease up to the point of insertion in the database).
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackAddLease(lease);
+ }
+
+ return (result);
+}
+
+bool
+MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ADD_ADDR6)
+ .arg(lease->addr_.toText())
+ .arg(lease->type_);
+
+ // Get a context
+ MySqlLeaseTrackingContextAlloc get_context(*this, lease);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create the MYSQL_BIND array for the lease
+ std::vector<MYSQL_BIND> bind = ctx->exchange6_->createBindForSend(lease);
+
+ // ... and drop to common code.
+ auto result = addLeaseCommon(ctx, INSERT_LEASE6, bind);
+
+ // Update lease current expiration time (allows update between the creation
+ // of the Lease up to the point of insertion in the database).
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackAddLease(lease);
+ }
+
+ return (result);
+}
+
+// Extraction of leases from the database.
+//
+// All getLease() methods ultimately call getLeaseCollection(). This
+// binds the input parameters passed to it with the appropriate prepared
+// statement and executes the statement. It then gets the results from the
+// database. getlease() methods that expect a single result back call it
+// with the "single" parameter set true: this causes an exception to be
+// generated if multiple records can be retrieved from the result set. (Such
+// an occurrence either indicates corruption in the database, or that an
+// assumption that a query can only return a single record is incorrect.)
+// Methods that require a collection of records have "single" set to the
+// default value of false. The logic is the same for both Lease4 and Lease6
+// objects, so the code is templated.
+//
+// Methods that require a collection of objects access this method through
+// two interface methods (also called getLeaseCollection()). These are
+// short enough as to be defined in the header file: all they do is to supply
+// the appropriate MySqlLeaseXExchange object depending on the type of the
+// LeaseCollection objects passed to them.
+//
+// Methods that require a single object to be returned access the method
+// through two interface methods (called getLease()). As well as supplying
+// the appropriate exchange object, they convert between lease collection
+// holding zero or one leases into an appropriate Lease object.
+
+template <typename Exchange, typename LeaseCollection>
+void
+MySqlLeaseMgr::getLeaseCollection(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ Exchange& exchange,
+ LeaseCollection& result,
+ bool single) const {
+ int status;
+
+ if (bind) {
+ // Bind the selection parameters to the statement
+ status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], bind);
+ checkError(ctx, status, stindex, "unable to bind WHERE clause parameter");
+ }
+
+ // Set up the MYSQL_BIND array for the data being returned and bind it to
+ // the statement.
+ std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
+ status = mysql_stmt_bind_result(ctx->conn_.statements_[stindex], &outbind[0]);
+ checkError(ctx, status, stindex, "unable to bind SELECT clause parameters");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(ctx->conn_.statements_[stindex]);
+ checkError(ctx, status, stindex, "unable to execute");
+
+ // Ensure that all the lease information is retrieved in one go to avoid
+ // overhead of going back and forth between client and server.
+ status = mysql_stmt_store_result(ctx->conn_.statements_[stindex]);
+ checkError(ctx, status, stindex, "unable to set up for storing all results");
+
+ // Set up the fetch "release" object to release resources associated
+ // with the call to mysql_stmt_fetch when this method exits, then
+ // retrieve the data.
+ MySqlFreeResult fetch_release(ctx->conn_.statements_[stindex]);
+ int count = 0;
+ while ((status = mysql_stmt_fetch(ctx->conn_.statements_[stindex])) == 0) {
+ try {
+ result.push_back(exchange->getLeaseData());
+
+ } catch (const isc::BadValue& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ ctx->conn_.text_statements_[stindex] << ">");
+ }
+
+ if (single && (++count > 1)) {
+ isc_throw(MultipleRecords, "multiple records were found in the "
+ "database where only one was expected for query "
+ << ctx->conn_.text_statements_[stindex]);
+ }
+ }
+
+ // How did the fetch end?
+ if (status == 1) {
+ // Error - unable to fetch results
+ checkError(ctx, status, stindex, "unable to fetch results");
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, ctx->conn_.text_statements_[stindex]
+ << " returned truncated data: columns affected are "
+ << exchange->getErrorColumns());
+ }
+}
+
+void
+MySqlLeaseMgr::getLease(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex, MYSQL_BIND* bind,
+ Lease4Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" parameter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease4Collection collection;
+ getLeaseCollection(ctx, stindex, bind, ctx->exchange4_, collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+}
+
+void
+MySqlLeaseMgr::getLease(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex, MYSQL_BIND* bind,
+ Lease6Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" parameter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease6Collection collection;
+ getLeaseCollection(ctx, stindex, bind, ctx->exchange6_, collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+}
+
+// Basic lease access methods. Obtain leases from the database using various
+// criteria.
+
+Lease4Ptr
+MySqlLeaseMgr::getLease4(const IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_ADDR4)
+ .arg(addr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t addr4 = addr.toUint32();
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Get the data
+ Lease4Ptr result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE4_ADDR, inbind, result);
+
+ return (result);
+}
+
+Lease4Collection
+MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_HWADDR)
+ .arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+
+ // If the data happens to be empty, we have to create a 1 byte dummy
+ // buffer and pass it to the binding.
+ uint8_t single_byte_data = 0;
+
+ // As "buffer" is "char*" - even though the data is being read - we need
+ // to cast away the "const"ness as well as reinterpreting the data as
+ // a "char*". (We could avoid the "const_cast" by copying the data to a
+ // local variable, but as the data is only being read, this introduces
+ // an unnecessary copy).
+ uint8_t* data = !hwaddr.hwaddr_.empty() ? const_cast<uint8_t*>(&hwaddr.hwaddr_[0])
+ : &single_byte_data;
+
+ inbind[0].buffer = reinterpret_cast<char*>(data);
+ inbind[0].buffer_length = hwaddr_length;
+ inbind[0].length = &hwaddr_length;
+
+ // Get the data
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_HWADDR, inbind, result);
+
+ return (result);
+}
+
+Lease4Ptr
+MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id)
+ .arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+
+ // If the data happens to be empty, we have to create a 1 byte dummy
+ // buffer and pass it to the binding.
+ std::vector<uint8_t> single_byte_vec(1);
+
+ // As "buffer" is "char*" - even though the data is being read - we need
+ // to cast away the "const"ness as well as reinterpreting the data as
+ // a "char*". (We could avoid the "const_cast" by copying the data to a
+ // local variable, but as the data is only being read, this introduces
+ // an unnecessary copy).
+ uint8_t* data = !hwaddr.hwaddr_.empty() ? const_cast<uint8_t*>(&hwaddr.hwaddr_[0])
+ : &single_byte_vec[0];
+
+ inbind[0].buffer = reinterpret_cast<char*>(data);
+ inbind[0].buffer_length = hwaddr_length;
+ inbind[0].length = &hwaddr_length;
+
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&subnet_id);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Get the data
+ Lease4Ptr result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE4_HWADDR_SUBID, inbind, result);
+
+ return (result);
+}
+
+Lease4Collection
+MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_CLIENTID)
+ .arg(clientid.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+
+ std::vector<uint8_t> client_data = clientid.getClientId();
+ unsigned long client_data_length = client_data.size();
+
+ // If the data happens to be empty, we have to create a 1 byte dummy
+ // buffer and pass it to the binding.
+ if (client_data.empty()) {
+ client_data.resize(1);
+ }
+
+ inbind[0].buffer = reinterpret_cast<char*>(&client_data[0]);
+ inbind[0].buffer_length = client_data_length;
+ inbind[0].length = &client_data_length;
+
+ // Get the data
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_CLIENTID, inbind, result);
+
+ return (result);
+}
+
+Lease4Ptr
+MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_SUBID_CLIENTID)
+ .arg(subnet_id)
+ .arg(clientid.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+
+ std::vector<uint8_t> client_data = clientid.getClientId();
+ unsigned long client_data_length = client_data.size();
+
+ // If the data happens to be empty, we have to create a 1 byte dummy
+ // buffer and pass it to the binding.
+ if (client_data.empty()) {
+ client_data.resize(1);
+ }
+
+ inbind[0].buffer = reinterpret_cast<char*>(&client_data[0]);
+ inbind[0].buffer_length = client_data_length;
+ inbind[0].length = &client_data_length;
+
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&subnet_id);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Get the data
+ Lease4Ptr result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE4_CLIENTID_SUBID, inbind, result);
+
+ return (result);
+}
+
+Lease4Collection
+MySqlLeaseMgr::getLeases4(SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_SUBID4)
+ .arg(subnet_id);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Subnet ID
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // ... and get the data
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_SUBID, inbind, result);
+
+ return (result);
+}
+
+Lease4Collection
+MySqlLeaseMgr::getLeases4(const std::string& hostname) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_HOSTNAME4)
+ .arg(hostname);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Hostname
+ inbind[0].buffer_type = MYSQL_TYPE_STRING;
+ inbind[0].buffer = const_cast<char*>(hostname.c_str());
+ inbind[0].buffer_length = hostname.length();
+
+ // ... and get the data
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_HOSTNAME, inbind, result);
+
+ return (result);
+}
+
+Lease4Collection
+MySqlLeaseMgr::getLeases4() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET4);
+
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4, 0, result);
+
+ return (result);
+}
+
+Lease4Collection
+MySqlLeaseMgr::getLeases4(const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const {
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_PAGE4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText());
+
+ // Prepare WHERE clause
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind lower bound address
+ uint32_t lb_address_data = lower_bound_address.toUint32();
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&lb_address_data);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Bind page size value
+ uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&ps);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Get the leases
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_PAGE, inbind, result);
+
+ return (result);
+}
+
+Lease6Ptr
+MySqlLeaseMgr::getLease6(Lease::Type lease_type,
+ const IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_ADDR6)
+ .arg(addr.toText())
+ .arg(lease_type);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // address: binary(16)
+ std::vector<uint8_t>addr6 = addr.toBytes();
+ if (addr6.size() != 16) {
+ isc_throw(DbOperationError, "lease6 address is not 16 bytes long");
+ }
+
+ unsigned long addr6_length = 16;
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[0].buffer_length = 16;
+ inbind[0].length = &addr6_length;
+
+ // LEASE_TYPE
+ inbind[1].buffer_type = MYSQL_TYPE_TINY;
+ inbind[1].buffer = reinterpret_cast<char*>(&lease_type);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ Lease6Ptr result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE6_ADDR, inbind, result);
+
+ return (result);
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid,
+ uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_IAID_DUID)
+ .arg(iaid)
+ .arg(duid.toText())
+ .arg(lease_type);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ // In the following statement, the DUID is being read. However, the
+ // MySQL C interface does not use "const", so the "buffer" element
+ // is declared as "char*" instead of "const char*". To resolve this,
+ // the "const" is discarded before the uint8_t* is cast to char*.
+ //
+ // Note that the const_cast could be avoided by copying the DUID to
+ // a writable buffer and storing the address of that in the "buffer"
+ // element. However, this introduces a copy operation (with additional
+ // overhead) purely to get round the structures introduced by design of
+ // the MySQL interface (which uses the area pointed to by "buffer" as
+ // input when specifying query parameters and as output when retrieving
+ // data). For that reason, "const_cast" has been used.
+ const vector<uint8_t>& duid_vector = duid.getDuid();
+ unsigned long duid_length = duid_vector.size();
+
+ // Make sure that the buffer has at least length of 1, even if
+ // empty client id is passed. This is required by some of the
+ // MySQL connectors that the buffer is set to non-null value.
+ // Otherwise, null value would be inserted into the database,
+ // rather than empty string.
+ uint8_t single_byte_data = 0;
+ uint8_t* data = !duid_vector.empty() ? const_cast<uint8_t*>(&duid_vector[0])
+ : &single_byte_data;
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(data);
+ inbind[0].buffer_length = duid_length;
+ inbind[0].length = &duid_length;
+
+ // IAID
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&iaid);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // LEASE_TYPE
+ inbind[2].buffer_type = MYSQL_TYPE_TINY;
+ inbind[2].buffer = reinterpret_cast<char*>(&lease_type);
+ inbind[2].is_unsigned = MLM_TRUE;
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_DUID_IAID, inbind, result);
+
+ return (result);
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid)
+ .arg(subnet_id)
+ .arg(duid.toText())
+ .arg(lease_type);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[4];
+ memset(inbind, 0, sizeof(inbind));
+
+ // See the earlier description of the use of "const_cast" when accessing
+ // the DUID for an explanation of the reason.
+ const vector<uint8_t>& duid_vector = duid.getDuid();
+ unsigned long duid_length = duid_vector.size();
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(
+ const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[0].buffer_length = duid_length;
+ inbind[0].length = &duid_length;
+
+ // IAID
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&iaid);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Subnet ID
+ inbind[2].buffer_type = MYSQL_TYPE_LONG;
+ inbind[2].buffer = reinterpret_cast<char*>(&subnet_id);
+ inbind[2].is_unsigned = MLM_TRUE;
+
+ // LEASE_TYPE
+ inbind[3].buffer_type = MYSQL_TYPE_TINY;
+ inbind[3].buffer = reinterpret_cast<char*>(&lease_type);
+ inbind[3].is_unsigned = MLM_TRUE;
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_DUID_IAID_SUBID, inbind, result);
+
+ return (result);
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6(SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_SUBID6)
+ .arg(subnet_id);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Subnet ID
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&subnet_id);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_SUBID, inbind, result);
+
+ return (result);
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET6);
+
+ Lease6Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6, 0, result);
+
+ return (result);
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6(const DUID& duid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_DUID)
+ .arg(duid.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ const vector<uint8_t>& duid_vector = duid.getDuid();
+ unsigned long duid_length = duid_vector.size();
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(
+ const_cast<uint8_t*>(&duid_vector[0]));
+ inbind[0].buffer_length = duid_length;
+ inbind[0].length = &duid_length;
+
+ Lease6Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_DUID, inbind, result);
+
+ return result;
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6(const std::string& hostname) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_HOSTNAME6)
+ .arg(hostname);
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Hostname
+ inbind[0].buffer_type = MYSQL_TYPE_STRING;
+ inbind[0].buffer = const_cast<char*>(hostname.c_str());
+ inbind[0].buffer_length = hostname.length();
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_HOSTNAME, inbind, result);
+
+ return (result);
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6(const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const {
+ // Expecting IPv6 address.
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_PAGE6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText());
+
+ // Prepare WHERE clause
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind lower bound address
+ std::vector<uint8_t>lb_addr = lower_bound_address.toBytes();
+ if (lb_addr.size() != 16) {
+ isc_throw(DbOperationError, "getLeases6() - lower bound address is not 16 bytes long");
+ }
+
+ unsigned long lb_addr_length = 16;
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&lb_addr[0]);
+ inbind[0].buffer_length = 16;
+ inbind[0].length = &lb_addr_length;
+
+ // Bind page size value
+ uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&ps);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Get the leases
+ Lease6Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_PAGE, inbind, result);
+
+ return (result);
+}
+
+void
+MySqlLeaseMgr::getExpiredLeases4(Lease4Collection& expired_leases,
+ const size_t max_leases) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_EXPIRED4)
+ .arg(max_leases);
+ getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE4_EXPIRE);
+}
+
+void
+MySqlLeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases,
+ const size_t max_leases) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_EXPIRED6)
+ .arg(max_leases);
+ getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE6_EXPIRE);
+}
+
+template<typename LeaseCollection>
+void
+MySqlLeaseMgr::getExpiredLeasesCommon(LeaseCollection& expired_leases,
+ const size_t max_leases,
+ StatementIndex statement_index) const {
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Exclude reclaimed leases.
+ uint32_t state = static_cast<uint32_t>(Lease::STATE_EXPIRED_RECLAIMED);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&state);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Expiration timestamp.
+ MYSQL_TIME expire_time;
+ MySqlConnection::convertToDatabaseTime(time(0), expire_time);
+ inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
+ inbind[1].buffer_length = sizeof(expire_time);
+
+ // If the number of leases is 0, we will return all leases. This is
+ // achieved by setting the limit to a very high value.
+ uint32_t limit = max_leases > 0 ? static_cast<uint32_t>(max_leases) :
+ std::numeric_limits<uint32_t>::max();
+ inbind[2].buffer_type = MYSQL_TYPE_LONG;
+ inbind[2].buffer = reinterpret_cast<char*>(&limit);
+ inbind[2].is_unsigned = MLM_TRUE;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Get the data
+ getLeaseCollection(ctx, statement_index, inbind, expired_leases);
+}
+
+// Update lease methods. These comprise common code that handles the actual
+// update, and type-specific methods that set up the parameters for the prepared
+// statement depending on the type of lease.
+
+template <typename LeasePtr>
+void
+MySqlLeaseMgr::updateLeaseCommon(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ const LeasePtr& lease) {
+
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], bind);
+ checkError(ctx, status, stindex, "unable to bind parameters");
+
+ // Execute
+ status = MysqlExecuteStatement(ctx->conn_.statements_[stindex]);
+ checkError(ctx, status, stindex, "unable to execute");
+
+ // See how many rows were affected. The statement should only update a
+ // single row.
+ int affected_rows = mysql_stmt_affected_rows(ctx->conn_.statements_[stindex]);
+
+ // Check success case first as it is the most likely outcome.
+ if (affected_rows == 1) {
+ return;
+ }
+
+ // If no rows affected, lease doesn't exist.
+ if (affected_rows == 0) {
+ isc_throw(NoSuchLease, "unable to update lease for address " <<
+ lease->addr_.toText() << " as it does not exist");
+ }
+
+ // Should not happen - primary key constraint should only have selected
+ // one row.
+ isc_throw(DbOperationError, "apparently updated more than one lease "
+ "that had the address " << lease->addr_.toText());
+}
+
+void
+MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ const StatementIndex stindex = UPDATE_LEASE4;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_UPDATE_ADDR4)
+ .arg(lease->addr_.toText());
+
+ // Get a context
+ MySqlLeaseTrackingContextAlloc get_context(*this, lease);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create the MYSQL_BIND array for the data being updated
+ std::vector<MYSQL_BIND> bind = ctx->exchange4_->createBindForSend(lease);
+
+ // Set up the WHERE clause and append it to the MYSQL_BIND array
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t addr4 = lease->addr_.toUint32();
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ bind.push_back(inbind[0]);
+
+ // See the expire code of createBindForSend for the
+ // infinite valid lifetime special case.
+ MYSQL_TIME expire;
+ uint32_t valid_lft = lease->current_valid_lft_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertToDatabaseTime(lease->current_cltt_, valid_lft,
+ expire);
+ inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ inbind[1].buffer = reinterpret_cast<char*>(&expire);
+ inbind[1].buffer_length = sizeof(expire);
+
+ bind.push_back(inbind[1]);
+
+ // Drop to common update code
+ updateLeaseCommon(ctx, stindex, &bind[0], lease);
+
+ // Update lease current expiration time.
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackUpdateLease(lease);
+ }
+}
+
+void
+MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ const StatementIndex stindex = UPDATE_LEASE6;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_UPDATE_ADDR6)
+ .arg(lease->addr_.toText())
+ .arg(lease->type_);
+
+ // Get a context
+ MySqlLeaseTrackingContextAlloc get_context(*this, lease);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create the MYSQL_BIND array for the data being updated
+ std::vector<MYSQL_BIND> bind = ctx->exchange6_->createBindForSend(lease);
+
+ // Set up the WHERE clause and append it to the MYSQL_BIND array
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind the where clause address parameter.
+ std::vector<uint8_t>addr6 = lease->addr_.toBytes();
+ if (addr6.size() != 16) {
+ isc_throw(DbOperationError, "updateLease6() - address is not 16 bytes long");
+ }
+
+ unsigned long addr6_length = 16;
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[0].buffer_length = 16;
+ inbind[0].length = &addr6_length;
+
+ bind.push_back(inbind[0]);
+
+ // See the expire code of createBindForSend for the
+ // infinite valid lifetime special case.
+ MYSQL_TIME expire;
+ uint32_t valid_lft = lease->current_valid_lft_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertToDatabaseTime(lease->current_cltt_, valid_lft,
+ expire);
+ inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ inbind[1].buffer = reinterpret_cast<char*>(&expire);
+ inbind[1].buffer_length = sizeof(expire);
+
+ bind.push_back(inbind[1]);
+
+ // Drop to common update code
+ updateLeaseCommon(ctx, stindex, &bind[0], lease);
+
+ // Update lease current expiration time.
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackUpdateLease(lease);
+ }
+}
+
+// Delete lease methods. Similar to other groups of methods, these comprise
+// a per-type method that sets up the relevant MYSQL_BIND array (in this
+// case, a single method for both V4 and V6 addresses) and a common method that
+// handles the common processing.
+
+uint64_t
+MySqlLeaseMgr::deleteLeaseCommon(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind) {
+ // Bind the input parameters to the statement
+ int status = mysql_stmt_bind_param(ctx->conn_.statements_[stindex], bind);
+ checkError(ctx, status, stindex, "unable to bind WHERE clause parameter");
+
+ // Execute
+ status = MysqlExecuteStatement(ctx->conn_.statements_[stindex]);
+ checkError(ctx, status, stindex, "unable to execute");
+
+ // See how many rows were affected. Note that the statement may delete
+ // multiple rows.
+ return (static_cast<uint64_t>(mysql_stmt_affected_rows(ctx->conn_.statements_[stindex])));
+}
+
+bool
+MySqlLeaseMgr::deleteLease(const Lease4Ptr& lease) {
+ const IOAddress& addr = lease->addr_;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_DELETE_ADDR)
+ .arg(addr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t addr4 = addr.toUint32();
+
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // See the expire code of createBindForSend for the
+ // infinite valid lifetime special case.
+ MYSQL_TIME expire;
+ uint32_t valid_lft = lease->current_valid_lft_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertToDatabaseTime(lease->current_cltt_, valid_lft,
+ expire);
+ inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ inbind[1].buffer = reinterpret_cast<char*>(&expire);
+ inbind[1].buffer_length = sizeof(expire);
+
+ // Get a context
+ MySqlLeaseTrackingContextAlloc get_context(*this, lease);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ auto affected_rows = deleteLeaseCommon(ctx, DELETE_LEASE4, inbind);
+
+ // Check success case first as it is the most likely outcome.
+ if (affected_rows == 1) {
+ if (hasCallbacks()) {
+ trackDeleteLease(lease);
+ }
+ return (true);
+ }
+
+ // If no rows affected, lease doesn't exist.
+ if (affected_rows == 0) {
+ return (false);
+ }
+
+ // Should not happen - primary key constraint should only have selected
+ // one row.
+ isc_throw(DbOperationError, "apparently deleted more than one lease "
+ "that had the address " << lease->addr_.toText());
+}
+
+bool
+MySqlLeaseMgr::deleteLease(const Lease6Ptr& lease) {
+ const IOAddress& addr = lease->addr_;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_DELETE_ADDR)
+ .arg(addr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind the where clause address parameter.
+ std::vector<uint8_t>addr6 = addr.toBytes();
+ if (addr6.size() != 16) {
+ isc_throw(DbOperationError, "deleteLease6() - address is not 16 bytes long");
+ }
+
+ unsigned long addr6_length = 16;
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr6[0]);
+ inbind[0].buffer_length = 16;
+ inbind[0].length = &addr6_length;
+
+ // See the expire code of createBindForSend for the
+ // infinite valid lifetime special case.
+ MYSQL_TIME expire;
+ uint32_t valid_lft = lease->current_valid_lft_;
+ if (valid_lft == Lease::INFINITY_LFT) {
+ valid_lft = 0;
+ }
+ MySqlConnection::convertToDatabaseTime(lease->current_cltt_, valid_lft,
+ expire);
+ inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ inbind[1].buffer = reinterpret_cast<char*>(&expire);
+ inbind[1].buffer_length = sizeof(expire);
+
+ // Get a context
+ MySqlLeaseTrackingContextAlloc get_context(*this, lease);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ auto affected_rows = deleteLeaseCommon(ctx, DELETE_LEASE6, inbind);
+
+ // Check success case first as it is the most likely outcome.
+ if (affected_rows == 1) {
+ if (hasCallbacks()) {
+ trackDeleteLease(lease);
+ }
+ return (true);
+ }
+
+ // If no rows affected, lease doesn't exist.
+ if (affected_rows == 0) {
+ return (false);
+ }
+
+ // Should not happen - primary key constraint should only have selected
+ // one row.
+ isc_throw(DbOperationError, "apparently deleted more than one lease "
+ "that had the address " << lease->addr_.toText());
+}
+
+uint64_t
+MySqlLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED4)
+ .arg(secs);
+ return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE4_STATE_EXPIRED));
+}
+
+uint64_t
+MySqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_DELETE_EXPIRED_RECLAIMED6)
+ .arg(secs);
+ return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE6_STATE_EXPIRED));
+}
+
+uint64_t
+MySqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
+ StatementIndex statement_index) {
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // State is reclaimed.
+ uint32_t state = static_cast<uint32_t>(Lease::STATE_EXPIRED_RECLAIMED);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&state);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Expiration timestamp.
+ MYSQL_TIME expire_time;
+ MySqlConnection::convertToDatabaseTime(time(0) - static_cast<time_t>(secs), expire_time);
+ inbind[1].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ inbind[1].buffer = reinterpret_cast<char*>(&expire_time);
+ inbind[1].buffer_length = sizeof(expire_time);
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Get the number of deleted leases and log it.
+ uint64_t deleted_leases = deleteLeaseCommon(ctx, statement_index, inbind);
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_DELETED_EXPIRED_RECLAIMED)
+ .arg(deleted_leases);
+
+ return (deleted_leases);
+}
+
+string
+MySqlLeaseMgr::checkLimits(ConstElementPtr const& user_context, StatementIndex const stindex) const {
+ // No user context means no limits means allocation allowed means empty string.
+ if (!user_context) {
+ return string();
+ }
+
+ // Get a context.
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create bindings.
+ MySqlBindingCollection in_bindings({
+ MySqlBinding::createString(user_context->str())
+ });
+ MySqlBindingCollection out_bindings({
+ MySqlBinding::createString(LIMITS_TEXT_MAX_LEN)
+ });
+
+ // Execute the select.
+ std::string limit_text;
+ ctx->conn_.selectQuery(stindex, in_bindings, out_bindings,
+ [&limit_text] (MySqlBindingCollection const& result) {
+ limit_text = result[0]->getString();
+ });
+
+ return limit_text;
+}
+
+string
+MySqlLeaseMgr::checkLimits4(ConstElementPtr const& user_context) const {
+ return checkLimits(user_context, CHECK_LEASE4_LIMITS);
+}
+
+string
+MySqlLeaseMgr::checkLimits6(ConstElementPtr const& user_context) const {
+ return checkLimits(user_context, CHECK_LEASE6_LIMITS);
+}
+
+bool
+MySqlLeaseMgr::isJsonSupported() const {
+ // Get a context.
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create bindings.
+ MySqlBindingCollection in_bindings;
+ MySqlBindingCollection out_bindings({
+ MySqlBinding::createBool()
+ });
+
+ // Execute the select.
+ bool json_supported(false);
+ ctx->conn_.selectQuery(IS_JSON_SUPPORTED, in_bindings, out_bindings,
+ [&json_supported] (MySqlBindingCollection const& result) {
+ json_supported = result[0]->getBool();
+ });
+
+ return json_supported;
+}
+
+size_t
+MySqlLeaseMgr::getClassLeaseCount(const ClientClass& client_class,
+ const Lease::Type& ltype /* = Lease::TYPE_V4*/) const {
+ // Get a context.
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create bindings.
+ MySqlBindingCollection in_bindings({
+ MySqlBinding::createString(client_class)
+ });
+ if (ltype != Lease::TYPE_V4) {
+ in_bindings.push_back(MySqlBinding::createInteger<uint8_t>(ltype));
+ }
+ MySqlBindingCollection out_bindings({
+ MySqlBinding::createInteger<int64_t>()
+ });
+
+ // Execute the select.
+ StatementIndex const stindex(ltype == Lease::TYPE_V4 ? GET_LEASE4_COUNT_BY_CLASS :
+ GET_LEASE6_COUNT_BY_CLASS);
+ size_t count(0);
+ ctx->conn_.selectQuery(stindex, in_bindings, out_bindings,
+ [&count] (MySqlBindingCollection const& result) {
+ count = result[0]->getInteger<int64_t>();
+ });
+
+ return count;
+}
+
+void
+MySqlLeaseMgr::recountClassLeases4() {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::recountClassLeases4() not implemented");
+}
+
+void
+MySqlLeaseMgr::recountClassLeases6() {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::recountClassLeases6() not implemented");
+}
+
+void
+MySqlLeaseMgr::clearClassLeaseCounts() {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::clearClassLeaseCounts() not implemented");
+}
+
+void
+MySqlLeaseMgr::writeLeases4(const std::string&) {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::writeLeases4() not implemented");
+}
+
+void
+MySqlLeaseMgr::writeLeases6(const std::string&) {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::writeLeases6() not implemented");
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startLeaseStatsQuery4() {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ ALL_LEASE4_STATS,
+ false));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startPoolLeaseStatsQuery4() {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ ALL_POOL_LEASE4_STATS,
+ false, true));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ SUBNET_LEASE4_STATS,
+ false,
+ subnet_id));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ SUBNET_RANGE_LEASE4_STATS,
+ false,
+ first_subnet_id,
+ last_subnet_id));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startLeaseStatsQuery6() {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ ALL_LEASE6_STATS,
+ true));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startPoolLeaseStatsQuery6() {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ ALL_POOL_LEASE6_STATS,
+ true, true));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ SUBNET_LEASE6_STATS,
+ true,
+ subnet_id));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(ctx->conn_,
+ SUBNET_RANGE_LEASE6_STATS,
+ true,
+ first_subnet_id,
+ last_subnet_id));
+ query->start();
+ return(query);
+}
+
+size_t
+MySqlLeaseMgr::wipeLeases4(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases4 is not implemented for MySQL backend");
+}
+
+size_t
+MySqlLeaseMgr::wipeLeases6(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases6 is not implemented for MySQL backend");
+}
+
+// Miscellaneous database methods.
+
+std::string
+MySqlLeaseMgr::getName() const {
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ std::string name = "";
+ try {
+ name = ctx->conn_.getParameter("name");
+ } catch (...) {
+ // Return an empty name
+ }
+ return (name);
+}
+
+std::string
+MySqlLeaseMgr::getDescription() const {
+ return (std::string("MySQL Database"));
+}
+
+std::pair<uint32_t, uint32_t>
+MySqlLeaseMgr::getVersion() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_GET_VERSION);
+
+ return (MySqlConnection::getVersion(parameters_));
+}
+
+void
+MySqlLeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
+}
+
+void
+MySqlLeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
+}
+
+void
+MySqlLeaseMgr::checkError(MySqlLeaseContextPtr& ctx,
+ int status, StatementIndex index,
+ const char* what) const {
+ ctx->conn_.checkError(status, index, what);
+}
+
+void
+MySqlLeaseMgr::deleteExtendedInfo6(const IOAddress& /* addr */) {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::deleteExtendedInfo6 not implemented");
+}
+
+void
+MySqlLeaseMgr::addRelayId6(const IOAddress& /* lease_addr */,
+ const vector<uint8_t>& /* relay_id */) {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::addRelayId6 not implemented");
+}
+
+void
+MySqlLeaseMgr::addRemoteId6(const IOAddress& /* lease_addr */,
+ const vector<uint8_t>& /* remote_id */) {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::addRemoteId6 not implemented");
+}
+
+namespace {
+
+std::string
+idToText(const OptionBuffer& id) {
+ std::stringstream tmp;
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = id.begin();
+ it != id.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0')
+ << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
+} // anonymous namespace
+
+Lease4Collection
+MySqlLeaseMgr::getLeases4ByRelayId(const OptionBuffer& relay_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time /* = 0 */,
+ const time_t& qry_end_time /* = 0 */) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_RELAYID4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(idToText(relay_id))
+ .arg(qry_start_time)
+ .arg(qry_end_time);
+
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ // Catch 2038 bug with 32 bit time_t.
+ if ((qry_start_time < 0) || (qry_end_time < 0)) {
+ isc_throw(BadValue, "negative time value");
+ }
+
+ bool have_qst = (qry_start_time > 0);
+ bool have_qet = (qry_end_time > 0);
+
+ // Start time must be before end time.
+ if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+ isc_throw(BadValue, "start time must be before end time");
+ }
+
+ // Prepare WHERE clause
+ size_t bindings = 3;
+ if (have_qst) {
+ ++bindings;
+ }
+ if (have_qet) {
+ ++bindings;
+ }
+ MYSQL_BIND inbind[bindings];
+ memset(inbind, 0, sizeof(inbind));
+
+ std::vector<uint8_t> relay_id_data = relay_id;
+ unsigned long relay_id_length = relay_id.size();
+
+ // If the relay id happens to be empty, we have to create a
+ // 1 byte dummy buffer and pass it to the binding.
+ if (relay_id_data.empty()) {
+ relay_id_data.resize(1);
+ }
+
+ // Bind relay id
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&relay_id_data[0]);
+ inbind[0].buffer_length = relay_id_length;
+ inbind[0].length = &relay_id_length;
+
+ // Bind lower bound address
+ uint32_t lb_address_data = lower_bound_address.toUint32();
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&lb_address_data);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ size_t index = 2;
+ // Bind query start time.
+ uint32_t start_time = static_cast<uint32_t>(qry_start_time);
+ if (have_qst) {
+ inbind[index].buffer_type = MYSQL_TYPE_LONG;
+ inbind[index].buffer = reinterpret_cast<char*>(&start_time);
+ inbind[index].is_unsigned = MLM_TRUE;
+ ++index;
+ }
+
+ // Bind query end time.
+ uint32_t end_time = static_cast<uint32_t>(qry_end_time);
+ if (have_qet) {
+ inbind[index].buffer_type = MYSQL_TYPE_LONG;
+ inbind[index].buffer = reinterpret_cast<char*>(&end_time);
+ inbind[index].is_unsigned = MLM_TRUE;
+ ++index;
+ }
+
+ // Bind page size value
+ uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+ inbind[index].buffer_type = MYSQL_TYPE_LONG;
+ inbind[index].buffer = reinterpret_cast<char*>(&ps);
+ inbind[index].is_unsigned = MLM_TRUE;
+
+ StatementIndex stindex = GET_LEASE4_RELAYID;
+ if (have_qst && !have_qet) {
+ stindex = GET_LEASE4_RELAYID_QST;
+ } else if (have_qst && have_qet) {
+ stindex = GET_LEASE4_RELAYID_QSET;
+ } else if (!have_qst && have_qet) {
+ stindex = GET_LEASE4_RELAYID_QET;
+ }
+
+ // Get the leases
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, stindex, inbind, result);
+
+ return (result);
+}
+
+Lease4Collection
+MySqlLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& remote_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time /* = 0 */,
+ const time_t& qry_end_time /* = 0 */) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_REMOTEID4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(idToText(remote_id))
+ .arg(qry_start_time)
+ .arg(qry_end_time);
+
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ // Catch 2038 bug with 32 bit time_t.
+ if ((qry_start_time < 0) || (qry_end_time < 0)) {
+ isc_throw(BadValue, "negative time value");
+ }
+
+ bool have_qst = (qry_start_time > 0);
+ bool have_qet = (qry_end_time > 0);
+
+ // Start time must be before end time.
+ if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+ isc_throw(BadValue, "start time must be before end time");
+ }
+
+ // Prepare WHERE clause
+ size_t bindings = 3;
+ if (have_qst) {
+ ++bindings;
+ }
+ if (have_qet) {
+ ++bindings;
+ }
+ MYSQL_BIND inbind[bindings];
+ memset(inbind, 0, sizeof(inbind));
+
+ std::vector<uint8_t> remote_id_data = remote_id;
+ unsigned long remote_id_length = remote_id.size();
+
+ // If the remote id happens to be empty, we have to create a
+ // 1 byte dummy buffer and pass it to the binding.
+ if (remote_id_data.empty()) {
+ remote_id_data.resize(1);
+ }
+
+ // Bind remote id
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&remote_id_data[0]);
+ inbind[0].buffer_length = remote_id_length;
+ inbind[0].length = &remote_id_length;
+
+ // Bind lower bound address
+ uint32_t lb_address_data = lower_bound_address.toUint32();
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&lb_address_data);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ size_t index = 2;
+ // Bind query start time.
+ uint32_t start_time = static_cast<uint32_t>(qry_start_time);
+ if (have_qst) {
+ inbind[index].buffer_type = MYSQL_TYPE_LONG;
+ inbind[index].buffer = reinterpret_cast<char*>(&start_time);
+ inbind[index].is_unsigned = MLM_TRUE;
+ ++index;
+ }
+
+ // Bind query end time.
+ uint32_t end_time = static_cast<uint32_t>(qry_end_time);
+ if (have_qet) {
+ inbind[index].buffer_type = MYSQL_TYPE_LONG;
+ inbind[index].buffer = reinterpret_cast<char*>(&end_time);
+ inbind[index].is_unsigned = MLM_TRUE;
+ ++index;
+ }
+
+ // Bind page size value
+ uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+ inbind[index].buffer_type = MYSQL_TYPE_LONG;
+ inbind[index].buffer = reinterpret_cast<char*>(&ps);
+ inbind[index].is_unsigned = MLM_TRUE;
+
+ StatementIndex stindex = GET_LEASE4_REMOTEID;
+ if (have_qst && !have_qet) {
+ stindex = GET_LEASE4_REMOTEID_QST;
+ } else if (have_qst && have_qet) {
+ stindex = GET_LEASE4_REMOTEID_QSET;
+ } else if (!have_qst && have_qet) {
+ stindex = GET_LEASE4_REMOTEID_QET;
+ }
+
+ // Get the leases
+ Lease4Collection result;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, stindex, inbind, result);
+
+ return (result);
+}
+
+size_t
+MySqlLeaseMgr::upgradeExtendedInfo4(const LeasePageSize& page_size) {
+ auto check = CfgMgr::instance().getCurrentCfg()->
+ getConsistency()->getExtendedInfoSanityCheck();
+
+ size_t pages = 0;
+ size_t updated = 0;
+ IOAddress start_addr = IOAddress::IPV4_ZERO_ADDRESS();
+ for (;;) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_PAGE)
+ .arg(pages)
+ .arg(start_addr.toText())
+ .arg(updated);
+
+ // Prepare WHERE clause.
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind start address.
+ uint32_t start_addr_data = start_addr.toUint32();
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&start_addr_data);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Bind page size value.
+ uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&ps);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ Lease4Collection leases;
+
+ // Get a context.
+ {
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_UCTX_PAGE, inbind, leases);
+ }
+
+ if (leases.empty()) {
+ // Done.
+ break;
+ }
+
+ ++pages;
+ start_addr = leases.back()->addr_;
+ for (auto lease : leases) {
+ ConstElementPtr previous_user_context = lease->getContext();
+ vector<uint8_t> previous_relay_id = lease->relay_id_;
+ vector<uint8_t> previous_remote_id = lease->remote_id_;
+ if (!previous_user_context &&
+ previous_relay_id.empty() &&
+ previous_remote_id.empty()) {
+ continue;
+ }
+ bool modified = upgradeLease4ExtendedInfo(lease, check);
+ try {
+ lease->relay_id_.clear();
+ lease->remote_id_.clear();
+ extractLease4ExtendedInfo(lease, false);
+ if (modified ||
+ (previous_relay_id != lease->relay_id_) ||
+ (previous_remote_id != lease->remote_id_)) {
+ updateLease4(lease);
+ ++updated;
+ }
+ } catch (const NoSuchLease&) {
+ // The lease was modified in parallel:
+ // as its extended info was processed just ignore.
+ continue;
+ } catch (const std::exception& ex) {
+ // Something when wrong, for instance extract failed.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4_ERROR)
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ }
+ }
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_MYSQL_UPGRADE_EXTENDED_INFO4)
+ .arg(pages)
+ .arg(updated);
+
+ return (updated);
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6ByRelayId(const DUID& /* relay_id */,
+ const IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::getLeases6ByRelayId not implemented");
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6ByRemoteId(const OptionBuffer& /* remote_id */,
+ const IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size*/) {
+ isc_throw(NotImplemented, "MySqlLeaseMgr::getLeases6ByRemoteId not implemented");
+}
+
+Lease6Collection
+MySqlLeaseMgr::getLeases6ByLink(const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_LINKADDR6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(link_addr.toText())
+ .arg(static_cast<unsigned>(link_len));
+
+ // Expecting IPv6 valid prefix and address.
+ if (!link_addr.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << link_addr);
+ }
+ if ((link_len == 0) || (link_len > 128)) {
+ isc_throw(OutOfRange, "invalid IPv6 prefix length "
+ << static_cast<unsigned>(link_len));
+ }
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ Lease6Collection result;
+ const IOAddress& first_addr = firstAddrInPrefix(link_addr, link_len);
+ const IOAddress& last_addr = lastAddrInPrefix(link_addr, link_len);
+ IOAddress start_addr = lower_bound_address;
+ if (lower_bound_address < first_addr) {
+ start_addr = first_addr;
+ } else if (last_addr <= lower_bound_address) {
+ // Range was already done.
+ return (result);
+ } else {
+ // The lower bound address is from the last call so skip it.
+ start_addr = IOAddress::increase(lower_bound_address);
+ }
+
+ // Prepare WHERE clause
+ MYSQL_BIND inbind[3];
+ memset(inbind, 0, sizeof(inbind));
+
+ // Bind start address
+ std::vector<uint8_t> start_addr_data = start_addr.toBytes();
+ if (start_addr_data.size() != 16) {
+ isc_throw(DbOperationError, "start address is not 16 bytes long");
+ }
+ unsigned long start_addr_size = 16;
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&start_addr_data[0]);
+ inbind[0].buffer_length = 16;
+ inbind[0].length = &start_addr_size;
+
+ // Bind last address
+ std::vector<uint8_t> last_addr_data = last_addr.toBytes();
+ if (last_addr_data.size() != 16) {
+ isc_throw(DbOperationError, "last address is not 16 bytes long");
+ }
+ unsigned long last_addr_size = 16;
+ inbind[1].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[1].buffer = reinterpret_cast<char*>(&last_addr_data[0]);
+ inbind[1].buffer_length = 16;
+ inbind[1].length = &last_addr_size;
+
+ // Bind page size value
+ uint32_t ps = static_cast<uint32_t>(page_size.page_size_);
+ inbind[2].buffer_type = MYSQL_TYPE_LONG;
+ inbind[2].buffer = reinterpret_cast<char*>(&ps);
+ inbind[2].is_unsigned = MLM_TRUE;
+
+ // Get a context
+ MySqlLeaseContextAlloc get_context(*this);
+ MySqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Get the leases
+ getLeaseCollection(ctx, GET_LEASE6_LINK, inbind, result);
+
+ return (result);
+}
+
+size_t
+MySqlLeaseMgr::buildExtendedInfoTables6(bool /* update */, bool /* current */) {
+ isc_throw(isc::NotImplemented,
+ "MySqlLeaseMgr::buildExtendedInfoTables6 not implemented");
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
new file mode 100644
index 0000000..c7cf421
--- /dev/null
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -0,0 +1,1293 @@
+// Copyright (C) 2012-2023 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 MYSQL_LEASE_MGR_H
+#define MYSQL_LEASE_MGR_H
+
+#include <asiolink/io_service.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_exceptions.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <mysql/mysql_connection.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/utility.hpp>
+#include <mysql.h>
+
+#include <time.h>
+#include <vector>
+#include <mutex>
+
+namespace isc {
+namespace dhcp {
+
+// Forward declaration of the Lease exchange objects. These classes are defined
+// in the .cc file.
+class MySqlLease4Exchange;
+class MySqlLease6Exchange;
+
+/// @brief MySQL Lease Context
+///
+/// This class stores the thread context for the manager pool.
+/// The class is needed by all get/update/delete functions which must use one
+/// or more exchanges to perform database operations.
+/// Each context provides a set of such exchanges for each thread.
+/// The context instances are lazy initialized by the requesting thread by using
+/// the manager's createContext function and are destroyed when the manager's
+/// pool instance is destroyed.
+class MySqlLeaseContext {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param parameters See MySqlLeaseMgr constructor.
+ /// @param io_service_accessor The IOService accessor function.
+ /// @param db_reconnect_callback The connection recovery callback.
+ MySqlLeaseContext(const db::DatabaseConnection::ParameterMap& parameters,
+ db::IOServiceAccessorPtr io_service_accessor,
+ db::DbCallback db_reconnect_callback);
+
+ /// The exchange objects are used for transfer of data to/from the database.
+ /// They are pointed-to objects as the contents may change in "const" calls,
+ /// while the rest of this object does not. (At alternative would be to
+ /// declare them as "mutable".)
+ boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
+ boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
+
+ /// @brief MySQL connection
+ db::MySqlConnection conn_;
+};
+
+/// @brief Type of pointers to contexts.
+typedef boost::shared_ptr<MySqlLeaseContext> MySqlLeaseContextPtr;
+
+/// @brief MySQL Lease Context Pool
+///
+/// This class provides a pool of contexts.
+/// The manager will use this class to handle available contexts.
+/// There is only one ContextPool per manager per back-end, which is created
+/// and destroyed by the respective manager factory class.
+class MySqlLeaseContextPool {
+public:
+
+ /// @brief The vector of available contexts.
+ std::vector<MySqlLeaseContextPtr> pool_;
+
+ /// @brief The mutex to protect pool access.
+ std::mutex mutex_;
+};
+
+/// @brief Type of pointers to context pools.
+typedef boost::shared_ptr<MySqlLeaseContextPool> MySqlLeaseContextPoolPtr;
+
+/// @brief MySQL Lease Manager
+///
+/// This class provides the \ref isc::dhcp::LeaseMgr interface to the MySQL
+/// database. Use of this backend presupposes that a MySQL database is
+/// available and that the Kea schema has been created within it.
+
+class MySqlLeaseMgr : public TrackingLeaseMgr {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Uses the following keywords in the parameters passed to it to
+ /// connect to the database:
+ /// - name - Name of the database to which to connect (mandatory)
+ /// - host - Host to which to connect (optional, defaults to "localhost")
+ /// - user - Username under which to connect (optional)
+ /// - password - Password for "user" on the database (optional)
+ ///
+ /// Check the schema version and create an initial context.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+ /// @throw isc::db::DbOpenError Error opening the database or the schema
+ /// version is incorrect.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ MySqlLeaseMgr(const db::DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Destructor (closes database)
+ virtual ~MySqlLeaseMgr();
+
+ /// @brief Create a new context.
+ ///
+ /// The database is opened with all the SQL commands pre-compiled.
+ ///
+ /// @return A new (never null) context.
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ MySqlLeaseContextPtr createContext() const;
+
+ /// @brief Attempts to reconnect the server to the lease DB backend manager.
+ ///
+ /// This is a self-rescheduling function that attempts to reconnect to the
+ /// server's lease DB backends after connectivity to one or more have been
+ /// lost. Upon entry it will attempt to reconnect via
+ /// @ref LeaseMgrFactory::create.
+ /// If this is successful, DHCP servicing is re-enabled and server returns
+ /// to normal operation.
+ ///
+ /// If reconnection fails and the maximum number of retries has not been
+ /// exhausted, it will schedule a call to itself to occur at the
+ /// configured retry interval. DHCP service remains disabled.
+ ///
+ /// If the maximum number of retries has been exhausted an error is logged
+ /// and the server shuts down.
+ ///
+ /// This function is passed to the connection recovery mechanism. It will be
+ /// invoked when a connection loss is detected.
+ ///
+ /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters.
+ /// @return true if connection has been recovered, false otherwise.
+ static bool dbReconnect(util::ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief Local version of getDBVersion() class method
+ static std::string getDBVersion();
+
+ /// @brief Adds an IPv4 lease
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not (because a lease
+ /// with the same address was already there).
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual bool addLease(const Lease4Ptr& lease) override;
+
+ /// @brief Adds an IPv6 lease
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not (because a lease
+ /// with the same address was already there).
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual bool addLease(const Lease6Ptr& lease) override;
+
+ /// @brief Returns an IPv4 lease for specified IPv4 address
+ ///
+ /// This method return a lease that is associated with a given address.
+ /// For other query types (by hardware addr, by Client ID) there can be
+ /// several leases in different subnets (e.g. for mobile clients that
+ /// got address in different subnets). However, for a single address
+ /// there can be only one lease, so this method returns a pointer to
+ /// a single lease, not a container of leases.
+ ///
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const override;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address.
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param hwaddr hardware address of the client
+ ///
+ /// @return lease collection
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const override;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address
+ /// and a subnet
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
+ SubnetID subnet_id) const override;
+
+ /// @brief Returns existing IPv4 leases for specified client-id
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param clientid client identifier
+ ///
+ /// @return lease collection
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Collection getLease4(const ClientId& clientid) const override;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param clientid client identifier
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Ptr getLease4(const ClientId& clientid,
+ SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv4 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv4 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(const std::string& hostname) const override;
+
+ /// @brief Returns all IPv4 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4() const override;
+
+ /// @brief Returns range of IPv4 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv4 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv4 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection
+ getLeases4(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const override;
+
+ /// @brief Returns existing IPv6 lease for a given IPv6 address.
+ ///
+ /// For a given address, we assume that there will be only one lease.
+ /// The assumption here is that there will not be site or link-local
+ /// addresses used, so there is no way of having address duplication.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease6Ptr getLease6(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const override;
+
+ /// @brief Returns existing IPv6 leases for a given DUID+IA combination
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid) const override;
+
+ /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param subnet_id subnet id of the subnet the lease belongs to
+ ///
+ /// @return lease collection (may be empty if no lease is found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv6 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv6 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(const std::string& hostname) const override;
+
+ /// @brief Returns all IPv6 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6() const override;
+
+ /// @brief Returns all IPv6 leases for the DUID.
+ ///
+ /// @todo: implement an optimised of the query using index.
+ /// @return Lease collection (may be empty if no IPv6 lease found)
+ /// for the DUID.
+ virtual Lease6Collection getLeases6(const DUID& duid) const override;
+
+ /// @brief Returns range of IPv6 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv6 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv6 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection
+ getLeases6(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const override;
+
+ /// @brief Returns a collection of expired DHCPv4 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+ const size_t max_leases) const override;
+
+ /// @brief Returns a collection of expired DHCPv6 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+ const size_t max_leases) const override;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// Updates the record of the lease in the database (as identified by the
+ /// address) with the data in the passed lease object.
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
+ /// exist.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The UPDATE query uses WHERE expire = ? to update the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and UPDATE with
+ /// different expiration time.
+ virtual void updateLease4(const Lease4Ptr& lease4) override;
+
+ /// @brief Updates IPv6 lease.
+ ///
+ /// Updates the record of the lease in the database (as identified by the
+ /// address) with the data in the passed lease object.
+ ///
+ /// @param lease6 The lease to be updated.
+ ///
+ /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
+ /// exist.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The UPDATE query uses WHERE expire = ? to update the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and UPDATE with
+ /// different expiration time.
+ virtual void updateLease6(const Lease6Ptr& lease6) override;
+
+ /// @brief Deletes an IPv4 lease.
+ ///
+ /// @param lease IPv4 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The DELETE query uses WHERE expire = ? to delete the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and DELETE with
+ /// different expiration time.
+ virtual bool deleteLease(const Lease4Ptr& lease) override;
+
+ /// @brief Deletes an IPv6 lease.
+ ///
+ /// @param lease IPv6 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The DELETE query uses WHERE expire = ? to delete the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and DELETE with
+ /// different expiration time.
+ virtual bool deleteLease(const Lease6Ptr& lease) override;
+
+ /// @brief Deletes all expired-reclaimed DHCPv4 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs) override;
+
+ /// @brief Deletes all expired-reclaimed DHCPv6 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs) override;
+
+ /// @brief Creates and runs the IPv4 lease stats query
+ ///
+ /// It creates an instance of a MySqlLeaseStatsQuery4 and then
+ /// invokes its start method, which fetches its statistical data
+ /// result set by executing the ALL_LEASE_STATS4 query.
+ /// The query object is then returned.
+ ///
+ /// @return The populated query as a pointer to an LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery4() override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv4 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery4() override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MySqlLeaseStatsQuery4 for a single subnet
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MySqlLeaseStatsQuery4 for a subnet range
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) override;
+
+ /// @brief Creates and runs the IPv6 lease stats query
+ ///
+ /// It creates an instance of a MySqlLeaseStatsQuery6 and then
+ /// invokes its start method, which fetches its statistical data
+ /// result set by executing the ALL_LEASE_STATS6 query.
+ /// The query object is then returned.
+ ///
+ /// @return The populated query as a pointer to an LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery6() override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv6 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery6() override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MySqlLeaseStatsQuery6 for a single subnet
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a MySqlLeaseStatsQuery6 for a subnet range
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) override;
+
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id) override;
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id) override;
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const override {
+ return (std::string("mysql"));
+ }
+
+ /// @brief Returns backend name.
+ ///
+ /// Each backend have specific name.
+ ///
+ /// @return Name of the backend.
+ virtual std::string getName() const override;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const override;
+
+ /// @brief Returns backend version.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const override;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// MySQL supports transactions but this manager does not use them.
+ virtual void commit() override;
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// MySQL supports transactions but this manager does not use them.
+ virtual void rollback() override;
+
+ /// @brief Statement Tags
+ ///
+ /// The contents of the enum are indexes into the list of compiled SQL
+ /// statements
+ enum StatementIndex {
+ DELETE_LEASE4, // Delete from lease4 by address
+ DELETE_LEASE4_STATE_EXPIRED, // Delete expired lease4 in a given state
+ DELETE_LEASE6, // Delete from lease6 by address
+ DELETE_LEASE6_STATE_EXPIRED, // Delete expired lease6 in a given state
+ GET_LEASE4, // Get all IPv4 leases
+ GET_LEASE4_ADDR, // Get lease4 by address
+ GET_LEASE4_CLIENTID, // Get lease4 by client ID
+ GET_LEASE4_CLIENTID_SUBID, // Get lease4 by client ID & subnet ID
+ GET_LEASE4_HWADDR, // Get lease4 by HW address
+ GET_LEASE4_HWADDR_SUBID, // Get lease4 by HW address & subnet ID
+ GET_LEASE4_PAGE, // Get page of leases beginning with an address
+ GET_LEASE4_UCTX_PAGE, // Get page of leases with user context
+ GET_LEASE4_SUBID, // Get IPv4 leases by subnet ID
+ GET_LEASE4_HOSTNAME, // Get IPv4 leases by hostname
+ GET_LEASE4_EXPIRE, // Get lease4 by expiration.
+ GET_LEASE4_RELAYID, // Get page of lease by relay ID.
+ GET_LEASE4_RELAYID_QST, // Get page of leases by relay ID and query start time.
+ GET_LEASE4_RELAYID_QSET, // Get page of leases by relay ID and query start and end times.
+ GET_LEASE4_RELAYID_QET, // Get page of leases by relay ID and query end time.
+ GET_LEASE4_REMOTEID, // Get page of lease by remote ID.
+ GET_LEASE4_REMOTEID_QST, // Get page of leases by remote ID and query start time.
+ GET_LEASE4_REMOTEID_QSET, // Get page of leases by remote ID and query start and end times.
+ GET_LEASE4_REMOTEID_QET, // Get page of leases by remote ID and query end time.
+ GET_LEASE6, // Get all IPv6 leases
+ GET_LEASE6_ADDR, // Get lease6 by address
+ GET_LEASE6_DUID_IAID, // Get lease6 by DUID and IAID
+ GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and subnet ID
+ GET_LEASE6_PAGE, // Get page of leases beginning with an address
+ GET_LEASE6_UCTX_PAGE, // Get page of leases with user context
+ GET_LEASE6_SUBID, // Get IPv6 leases by subnet ID
+ GET_LEASE6_DUID, // Get IPv6 leases by DUID
+ GET_LEASE6_HOSTNAME, // Get IPv6 leases by hostname
+ GET_LEASE6_EXPIRE, // Get lease6 by expiration.
+ GET_LEASE6_LINK, // Get page of lease6 by link
+ INSERT_LEASE4, // Add entry to lease4 table
+ INSERT_LEASE6, // Add entry to lease6 table
+ UPDATE_LEASE4, // Update a Lease4 entry
+ UPDATE_LEASE6, // Update a Lease6 entry
+ ALL_LEASE4_STATS, // Fetches IPv4 lease statistics
+ SUBNET_LEASE4_STATS, // Fetched IPv4 lease stats for a single subnet.
+ SUBNET_RANGE_LEASE4_STATS, // Fetched IPv4 lease stats for a subnet range.
+ ALL_POOL_LEASE4_STATS, // Fetches IPv4 lease pool statistics
+ ALL_LEASE6_STATS, // Fetches IPv6 lease statistics
+ SUBNET_LEASE6_STATS, // Fetched IPv6 lease stats for a single subnet.
+ SUBNET_RANGE_LEASE6_STATS, // Fetched IPv6 lease stats for a subnet range.
+ ALL_POOL_LEASE6_STATS, // Fetches IPv6 lease pool statistics
+ CHECK_LEASE4_LIMITS, // Check if allocated IPv4 leases are inside the set limits.
+ CHECK_LEASE6_LIMITS, // Check if allocated IPv6 leases are inside the set limits.
+ IS_JSON_SUPPORTED, // Checks if JSON support is enabled in the database.
+ GET_LEASE4_COUNT_BY_CLASS, // Fetches the IPv4 lease count for a given class.
+ GET_LEASE6_COUNT_BY_CLASS, // Fetches the IPv6 lease count for given class and lease type.
+ NUM_STATEMENTS // Number of statements
+ };
+
+private:
+
+ /// @brief Add Lease Common Code
+ ///
+ /// This method performs the common actions for both flavours (V4 and V6)
+ /// of the addLease method. It binds the contents of the lease object to
+ /// the prepared statement and adds it to the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array that has been created for the type
+ /// of lease in question.
+ ///
+ /// @return true if the lease was added, false if it was not added because
+ /// a lease with that address already exists in the database.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ bool addLeaseCommon(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+ /// @brief Get Lease Collection Common Code
+ ///
+ /// This method performs the common actions for obtaining multiple leases
+ /// from the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param exchange Exchange object to use
+ /// @param result Returned collection of leases. Note that any leases in
+ /// the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ /// @param single If true, only a single data item is to be retrieved.
+ /// If more than one is present, a MultipleRecords exception will
+ /// be thrown.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::db::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ template <typename Exchange, typename LeaseCollection>
+ void getLeaseCollection(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ Exchange& exchange, LeaseCollection& result,
+ bool single = false) const;
+
+ /// @brief Get Lease4 Collection
+ ///
+ /// Gets a collection of Lease4 objects. This is just an interface to
+ /// the get lease collection common code.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param result LeaseCollection object returned. Note that any leases in
+ /// the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::db::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getLeaseCollection(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ Lease4Collection& result) const {
+ getLeaseCollection(ctx, stindex, bind, ctx->exchange4_, result);
+ }
+
+ /// @brief Get Lease6 Collection
+ ///
+ /// Gets a collection of Lease6 objects. This is just an interface to
+ /// the get lease collection common code.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param result LeaseCollection object returned. Note that any existing
+ /// data in the collection is erased first.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::db::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getLeaseCollection(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ Lease6Collection& result) const {
+ getLeaseCollection(ctx, stindex, bind, ctx->exchange6_, result);
+ }
+
+ /// @brief Get Lease4 Common Code
+ ///
+ /// This method performs the common actions for the various getLease4()
+ /// methods. It acts as an interface to the getLeaseCollection() method,
+ /// but retrieving only a single lease.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param result Lease4 object returned
+ void getLease(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ Lease4Ptr& result) const;
+
+ /// @brief Get Lease6 Common Code
+ ///
+ /// This method performs the common actions for the various getLease6()
+ /// methods. It acts as an interface to the getLeaseCollection() method,
+ /// but retrieving only a single lease.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param result Lease6 object returned
+ void getLease(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ Lease6Ptr& result) const;
+
+ /// @brief Get expired leases common code.
+ ///
+ /// This method retrieves expired and not reclaimed leases from the
+ /// lease database. The returned leases are ordered by the expiration
+ /// time. The maximum number of leases to be returned is specified
+ /// as an argument.
+ ///
+ /// @param [out] expired_leases Reference to the container where the
+ /// retrieved leases are put.
+ /// @param max_leases Maximum number of leases to be returned.
+ /// @param statement_index One of the @c GET_LEASE4_EXPIRE or
+ /// @c GET_LEASE6_EXPIRE.
+ ///
+ /// @tparam One of the @c Lease4Collection or @c Lease6Collection.
+ template<typename LeaseCollection>
+ void getExpiredLeasesCommon(LeaseCollection& expired_leases,
+ const size_t max_leases,
+ StatementIndex statement_index) const;
+
+ /// @brief Update lease common code
+ ///
+ /// Holds the common code for updating a lease. It binds the parameters
+ /// to the prepared statement, executes it, then checks how many rows
+ /// were affected.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of prepared statement to be executed
+ /// @param bind Array of MYSQL_BIND objects representing the parameters.
+ /// (Note that the number is determined by the number of parameters
+ /// in the statement.)
+ /// @param lease Pointer to the lease object whose record is being updated.
+ ///
+ /// @throw NoSuchLease Could not update a lease because no lease matches
+ /// the address given.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ template <typename LeasePtr>
+ void updateLeaseCommon(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind,
+ const LeasePtr& lease);
+
+ /// @brief Delete lease common code
+ ///
+ /// Holds the common code for deleting a lease. It binds the parameters
+ /// to the prepared statement, executes the statement and checks to
+ /// see how many rows were deleted.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of prepared statement to be executed
+ /// @param bind Array of MYSQL_BIND objects representing the parameters.
+ /// (Note that the number is determined by the number of parameters
+ /// in the statement.)
+ ///
+ /// @return Number of deleted leases.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ uint64_t deleteLeaseCommon(MySqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ MYSQL_BIND* bind);
+
+ /// @brief Delete expired-reclaimed leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ /// @param statement_index One of the @c DELETE_LEASE4_STATE_EXPIRED or
+ /// @c DELETE_LEASE6_STATE_EXPIRED.
+ ///
+ /// @return Number of leases deleted.
+ uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
+ StatementIndex statement_index);
+
+ /// @brief Checks if the lease limits set in the given user context are exceeded.
+ /// Contains common logic used by @ref checkLimits4 and @ref checkLimits6.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+ /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ std::string
+ checkLimits(isc::data::ConstElementPtr const& user_context, StatementIndex const stindex) const;
+
+ /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+ /// MySQL implementation.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+ /// "subnet": { "id": 1, "address-limit": 2 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string
+ checkLimits4(isc::data::ConstElementPtr const& user_context) const override;
+
+ /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+ /// MySQL implementation.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+ /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string
+ checkLimits6(isc::data::ConstElementPtr const& user_context) const override;
+
+ /// @brief Checks if JSON support is enabled in the database.
+ /// MySQL implementation.
+ ///
+ /// @return true if there is JSON support, false otherwise
+ virtual bool isJsonSupported() const override;
+
+ /// @brief Returns the class lease count for a given class and lease type.
+ ///
+ /// @param client_class client class for which the count is desired
+ /// @param ltype type of lease for which the count is desired. Defaults to
+ /// Lease::TYPE_V4.
+ ///
+ /// @return number of leases
+ virtual size_t getClassLeaseCount(const ClientClass& client_class,
+ const Lease::Type& ltype = Lease::TYPE_V4) const override;
+
+ /// @brief Recount the leases per class for V4 leases.
+ virtual void recountClassLeases4() override;
+
+ /// @brief Recount the leases per class for V6 leases.
+ virtual void recountClassLeases6() override;
+
+ /// @brief Clears the class-lease count map.
+ virtual void clearClassLeaseCounts() override;
+
+ /// @brief Write V4 leases to a file.
+ virtual void writeLeases4(const std::string& /*filename*/) override;
+
+ /// @brief Write V6 leases to a file.
+ virtual void writeLeases6(const std::string& /*filename*/) override;
+
+ /// @brief Check Error and Throw Exception
+ ///
+ /// This method invokes @ref MySqlConnection::checkError.
+ ///
+ /// @param ctx Context
+ /// @param status Status code: non-zero implies an error
+ /// @param index Index of statement that caused the error
+ /// @param what High-level description of the error
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ void checkError(MySqlLeaseContextPtr& ctx,
+ int status, StatementIndex index,
+ const char* what) const;
+
+ /// The following queries are used to fulfill Bulk Lease Query
+ /// queries. They rely on relay data contained in lease's
+ /// user-context when the extended-store-info flag is enabled.
+
+ /// @brief Returns existing IPv4 leases with a given relay-id.
+ ///
+ /// @param relay_id RAI Relay-ID sub-option value for relay_id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRelayId(const OptionBuffer& relay_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) override;
+
+ /// @brief Returns existing IPv4 leases with a given remote-id.
+ ///
+ /// @param remote_id RAI Remote-ID sub-option value for remote-id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included. Defaults to zero.
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included. Defaults to zero.
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) override;
+
+ /// @brief Returns existing IPv6 leases with a given relay-id.
+ ///
+ /// @param relay_id DUID for relay_id of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRelayId(const DUID& relay_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Returns existing IPv6 leases with a given remote-id.
+ ///
+ /// @param remote_id remote-id option data of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Returns existing IPv6 leases with on a given link.
+ ///
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByLink(const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Upgrade extended info (v4).
+ ///
+ /// For all leases with a not null user context.
+ /// - sanitize the user context
+ /// - update relay and remote ids
+ /// - when the lease was modified update it in the database
+ /// This function implements the new BLQ hook command named
+ /// "extended-info4-upgrade".
+ ///
+ /// @param page_size The page size used for retrieval.
+ /// @return The number of updates in the database.
+ virtual size_t upgradeExtendedInfo4(const LeasePageSize& page_size) override;
+
+ /// @brief Build extended info v6 tables.
+ ///
+ /// @param update Update extended info in database.
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config.
+ /// @return The number of updates in the database or 0.
+ virtual size_t buildExtendedInfoTables6(bool update, bool current) override;
+
+ /// @brief Context RAII allocator.
+ class MySqlLeaseContextAlloc {
+ public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor takes a context of the pool if one is available
+ /// or creates a new one.
+ ///
+ /// @param mgr A parent instance
+ MySqlLeaseContextAlloc(const MySqlLeaseMgr& mgr);
+
+ /// @brief Destructor
+ ///
+ /// This destructor puts back the context in the pool.
+ ~MySqlLeaseContextAlloc();
+
+ /// @brief The context
+ MySqlLeaseContextPtr ctx_;
+
+ private:
+
+ /// @brief The manager
+ const MySqlLeaseMgr& mgr_;
+ };
+
+ /// @brief Context RAII allocator for lease tracking.
+ ///
+ /// This context should be used in the non-const calls that
+ /// may trigger callbacks for lease tracking.
+ class MySqlLeaseTrackingContextAlloc {
+ public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor takes a context of the pool if one is available
+ /// or creates a new one.
+ ///
+ /// @param mgr A parent instance
+ /// @param lease allocated or deallocated lease instance.
+ MySqlLeaseTrackingContextAlloc(MySqlLeaseMgr& mgr, const LeasePtr& lease);
+
+ /// @brief Destructor
+ ///
+ /// This destructor puts back the context in the pool.
+ ~MySqlLeaseTrackingContextAlloc();
+
+ /// @brief The context
+ MySqlLeaseContextPtr ctx_;
+
+ private:
+
+ /// @brief The manager
+ MySqlLeaseMgr& mgr_;
+
+ /// @brief Tracked lease instance.
+ LeasePtr lease_;
+ };
+
+protected:
+
+ /// Extended information / Bulk Lease Query shared interface.
+
+ /// @brief Modifies the setting whether the lease extended info tables
+ /// are enabled.
+ ///
+ /// Transient redefine to refuse the enable setting.
+ /// @param enabled new setting.
+ virtual void setExtendedInfoTablesEnabled(const bool enabled) override {
+ if (enabled) {
+ isc_throw(isc::NotImplemented,
+ "extended info tables are not yet supported by mysql");
+ }
+ }
+
+ /// @brief Decode parameters to set whether the lease extended info tables
+ /// are enabled.
+ ///
+ /// @note: common code in constructors.
+ ///
+ /// @param parameters The parameter map.
+ virtual void setExtendedInfoTablesEnabled(const db::DatabaseConnection::ParameterMap& parameters) override;
+
+ /// @brief Delete lease6 extended info from tables.
+ ///
+ /// @param addr The address of the lease.
+ virtual void deleteExtendedInfo6(const isc::asiolink::IOAddress& addr) override;
+
+ /// @brief Add lease6 extended info into by-relay-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param relay_id The relay id from the relay header options.
+ virtual void addRelayId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& relay_id) override;
+
+ /// @brief Add lease6 extended info into by-remote-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param remote_id The remote id from the relay header options.
+ virtual void addRemoteId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& remote_id) override;
+
+private:
+
+ // Members
+
+ /// @brief The parameters
+ db::DatabaseConnection::ParameterMap parameters_;
+
+ /// @brief The pool of contexts
+ MySqlLeaseContextPoolPtr pool_;
+
+ /// @brief Timer name used to register database reconnect timer.
+ std::string timer_name_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // MYSQL_LEASE_MGR_H
diff --git a/src/lib/dhcpsrv/ncr_generator.cc b/src/lib/dhcpsrv/ncr_generator.cc
new file mode 100644
index 0000000..df1033d
--- /dev/null
+++ b/src/lib/dhcpsrv/ncr_generator.cc
@@ -0,0 +1,154 @@
+// Copyright (C) 2015-2023 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 <dhcp/option_data_types.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <stdint.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Sends name change request to D2 using lease information.
+///
+/// This method is exception safe.
+///
+/// @param chg_type type of change to create CHG_ADD or CHG_REMOVE
+/// @param lease Pointer to a lease for which NCR should be sent.
+/// @param identifier Identifier to be used to generate DHCID for
+/// the DNS update. For DHCPv4 it will be hardware address or client
+/// identifier. For DHCPv6 it will be a DUID.
+/// @param label Client identification information in the textual format.
+/// This is used for logging purposes.
+/// @param subnet subnet to which the lease belongs.
+///
+/// @tparam LeasePtrType Pointer to a lease.
+/// @tparam IdentifierType HW Address, Client Identifier or DUID.
+template<typename LeasePtrType, typename IdentifierType>
+void queueNCRCommon(const NameChangeType& chg_type, const LeasePtrType& lease,
+ const IdentifierType& identifier, const std::string& label,
+ NetworkPtr subnet) {
+ // Check if there is a need for update.
+ if (lease->hostname_.empty() || (!lease->fqdn_fwd_ && !lease->fqdn_rev_)
+ || !CfgMgr::instance().getD2ClientMgr().ddnsEnabled()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_QUEUE_NCR_SKIP)
+ .arg(label)
+ .arg(lease->addr_.toText());
+
+ return;
+ }
+
+ bool use_conflict_resolution = true;
+ util::Optional<double> ddns_ttl_percent;
+ if (subnet) {
+ use_conflict_resolution = subnet->getDdnsUseConflictResolution();
+ ddns_ttl_percent = subnet->getDdnsTtlPercent();
+ }
+
+ try {
+ // Create DHCID
+ std::vector<uint8_t> hostname_wire;
+ OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
+ D2Dhcid dhcid = D2Dhcid(identifier, hostname_wire);
+
+ // Calculate the TTL based on lease life time.
+ uint32_t ttl = calculateDdnsTtl(lease->valid_lft_, ddns_ttl_percent);
+
+ // Create name change request.
+ NameChangeRequestPtr ncr
+ (new NameChangeRequest(chg_type, lease->fqdn_fwd_, lease->fqdn_rev_,
+ lease->hostname_, lease->addr_.toText(),
+ dhcid, lease->cltt_ + ttl,
+ ttl, use_conflict_resolution));
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA, DHCPSRV_QUEUE_NCR)
+ .arg(label)
+ .arg(chg_type == CHG_ADD ? "add" : "remove")
+ .arg(ncr->toText());
+
+ // Send name change request.
+ CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_QUEUE_NCR_FAILED)
+ .arg(label)
+ .arg(chg_type == CHG_ADD ? "add" : "remove")
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ }
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+void queueNCR(const NameChangeType& chg_type, const Lease4Ptr& lease) {
+ if (lease) {
+ // Figure out from the lease's subnet if we should use conflict resolution.
+ // If there's no subnet, something hinky is going on so we'll set it true.
+ Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()
+ ->getCfgSubnets4()->getSubnet(lease->subnet_id_);
+
+ // Client id takes precedence over HW address.
+ if (lease->client_id_) {
+ queueNCRCommon(chg_type, lease, lease->client_id_->getClientId(),
+ Pkt4::makeLabel(lease->hwaddr_, lease->client_id_), subnet);
+ } else {
+ // Client id is not specified for the lease. Use HW address
+ // instead.
+ queueNCRCommon(chg_type, lease, lease->hwaddr_,
+ Pkt4::makeLabel(lease->hwaddr_, lease->client_id_), subnet);
+ }
+ }
+}
+
+void queueNCR(const NameChangeType& chg_type, const Lease6Ptr& lease) {
+ // DUID is required to generate NCR.
+ if (lease && (lease->type_ != Lease::TYPE_PD) && lease->duid_) {
+ // Figure out from the lease's subnet if we should use conflict resolution.
+ // If there's no subnet, something hinky is going on so we'll set it true.
+ Subnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()
+ ->getCfgSubnets6()->getSubnet(lease->subnet_id_);
+ queueNCRCommon(chg_type, lease, *(lease->duid_),
+ Pkt6::makeLabel(lease->duid_, lease->hwaddr_), subnet);
+ }
+}
+
+uint32_t calculateDdnsTtl(uint32_t lease_lft, const util::Optional<double>& ddns_ttl_percent) {
+ // If we have a configured percentage use it to calculate TTL.
+ if (!ddns_ttl_percent.unspecified() && (ddns_ttl_percent.get() > 0.0)) {
+ uint32_t new_lft = static_cast<uint32_t>(round(ddns_ttl_percent.get() * lease_lft));
+ if (new_lft > 0) {
+ return (new_lft);
+ } else {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL_DATA,
+ DHCPSRV_DDNS_TTL_PERCENT_TOO_SMALL)
+ .arg(ddns_ttl_percent.get())
+ .arg(lease_lft);
+ }
+ }
+
+ // Per RFC 4702 DDNS RR TTL should be given by:
+ // ((lease life time / 3) < 10 minutes) ? 10 minutes : (lease life time / 3)
+ if (lease_lft < 1800) {
+ return (600);
+ }
+
+ return (lease_lft / 3);
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/ncr_generator.h b/src/lib/dhcpsrv/ncr_generator.h
new file mode 100644
index 0000000..2011912
--- /dev/null
+++ b/src/lib/dhcpsrv/ncr_generator.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2015-2023 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 NCR_GENERATOR_H
+#define NCR_GENERATOR_H
+
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/lease.h>
+#include <util/optional.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Creates name change request from the DHCPv4 lease.
+///
+/// This function creates name change request from the information contained
+/// in the DHCPv4 lease. If the client identifier is present in the lease,
+/// this identifier is used to compute the DHCID, otherwise the HW address
+/// is used.
+///
+/// This function is exception safe. On failure, it logs an error.
+///
+/// @param chg_type Type of the name change request
+/// @param lease Pointer to the lease.
+void queueNCR(const dhcp_ddns::NameChangeType& chg_type, const Lease4Ptr& lease);
+
+/// @brief Creates name change request from the DHCPv6 lease.
+///
+/// This function creates name change request from the information contained
+/// in the DHCPv6 lease. The DUID is used to compute the DHCID for the name
+/// change request.
+///
+/// This function will skip sending the NCR if the lease type is a delegated
+/// prefix.
+///
+/// This function is exception safe. On failure, it logs an error.
+///
+/// @param chg_type Type of the name change request
+/// @param lease Pointer to the lease.
+void queueNCR(const dhcp_ddns::NameChangeType& chg_type, const Lease6Ptr& lease);
+
+/// @brief Calculates TTL for a DNS resource record based on lease life time.
+///
+/// If the parameter, ddns_ttl_percent is greater than zero, it is used to calculate
+/// the TTL directly:
+///
+/// TTL = (lease life time * ddns-ttl-percent)
+///
+/// Otherwise it is calculated as per RFC 4702 Section 5:
+///
+/// TTL = ((lease life time / 3) < 10 minutes) ? 10 minutes : (lease life time / 3)
+///
+/// @param lease_life_time valid life time of the lease
+/// @param ddns_ttl_percent optional percentage to use in calculation
+///
+/// @return the calculated TTL.
+uint32_t calculateDdnsTtl(uint32_t lease_life_time,
+ const util::Optional<double>& ddns_ttl_percent
+ = util::Optional<double>());
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // NCR_GENERATOR_H
diff --git a/src/lib/dhcpsrv/network.cc b/src/lib/dhcpsrv/network.cc
new file mode 100644
index 0000000..30532d8
--- /dev/null
+++ b/src/lib/dhcpsrv/network.cc
@@ -0,0 +1,389 @@
+// Copyright (C) 2017-2023 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 <dhcp/dhcp4.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/network.h>
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+Network::RelayInfo::addAddress(const asiolink::IOAddress& addr) {
+ if (containsAddress(addr)) {
+ isc_throw (BadValue, "RelayInfo already contains address: "
+ << addr.toText());
+ }
+
+ addresses_.push_back(addr);
+}
+
+bool
+Network::RelayInfo::hasAddresses() const {
+ return (!addresses_.empty());
+}
+
+bool
+Network::RelayInfo::containsAddress(const asiolink::IOAddress& addr) const {
+ auto const& index = addresses_.get<IOAddressListSetTag>();
+ return (index.find(addr) != index.end());
+}
+
+const IOAddressList&
+Network::RelayInfo::getAddresses() const {
+ return (addresses_);
+}
+
+void
+Network::addRelayAddress(const asiolink::IOAddress& addr) {
+ relay_.addAddress(addr);
+}
+
+bool
+Network::hasRelays() const {
+ return (relay_.hasAddresses());
+}
+
+bool
+Network::hasRelayAddress(const asiolink::IOAddress& addr) const {
+ return (relay_.containsAddress(addr));
+}
+
+const IOAddressList&
+Network::getRelayAddresses() const {
+ return (relay_.getAddresses());
+}
+
+bool
+Network::clientSupported(const isc::dhcp::ClientClasses& classes) const {
+ if (client_class_.empty()) {
+ // There is no class defined for this network, so we do
+ // support everyone.
+ return (true);
+ }
+
+ return (classes.contains(client_class_));
+}
+
+void
+Network::allowClientClass(const isc::dhcp::ClientClass& class_name) {
+ client_class_ = class_name;
+}
+
+void
+Network::requireClientClass(const isc::dhcp::ClientClass& class_name) {
+ if (!required_classes_.contains(class_name)) {
+ required_classes_.insert(class_name);
+ }
+}
+
+const ClientClasses&
+Network::getRequiredClasses() const {
+ return (required_classes_);
+}
+
+Optional<IOAddress>
+Network::getGlobalProperty(Optional<IOAddress> property,
+ const int global_index,
+ const int /*min_index*/,
+ const int /*max_index*/) const {
+ if ((global_index >= 0) && fetch_globals_fn_) {
+ ConstCfgGlobalsPtr globals = fetch_globals_fn_();
+ if (globals) {
+ ConstElementPtr global_param = globals->get(global_index);
+ if (global_param) {
+ std::string global_str = global_param->stringValue();
+ if (!global_str.empty()) {
+ return (IOAddress(global_str));
+ }
+ }
+ }
+ }
+ return (property);
+}
+
+ElementPtr
+Network::toElement() const {
+ ElementPtr map = Element::createMap();
+
+ // Set user-context
+ contextToElement(map);
+
+ // Set interface
+ if (!iface_name_.unspecified()) {
+ map->set("interface", Element::create(iface_name_.get()));
+ }
+
+ ElementPtr relay_map = Element::createMap();
+ ElementPtr address_list = Element::createList();
+ const IOAddressList addresses = getRelayAddresses();
+ for (auto address = addresses.begin(); address != addresses.end(); ++address) {
+ address_list->add(Element::create((*address).toText()));
+ }
+
+ relay_map->set("ip-addresses", address_list);
+ map->set("relay", relay_map);
+
+ // Set client-class
+ if (!client_class_.unspecified()) {
+ map->set("client-class", Element::create(client_class_.get()));
+ }
+
+ // Set require-client-classes
+ const ClientClasses& classes = getRequiredClasses();
+ if (!classes.empty()) {
+ ElementPtr class_list = Element::createList();
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
+ class_list->add(Element::create(*it));
+ }
+ map->set("require-client-classes", class_list);
+ }
+
+ // T1, T2, and Valid are optional for SharedNetworks, and
+ // T1 and T2 are optional for Subnet4 thus we will only
+ // output them if they are marked as specified.
+ if (!t1_.unspecified()) {
+ map->set("renew-timer",
+ Element::create(static_cast<long long>(t1_.get())));
+ }
+
+ // Set rebind-timer
+ if (!t2_.unspecified()) {
+ map->set("rebind-timer",
+ Element::create(static_cast<long long>(t2_.get())));
+ }
+
+ // Set valid-lifetime
+ if (!valid_.unspecified()) {
+ map->set("valid-lifetime",
+ Element::create(static_cast<long long>(valid_.get())));
+ map->set("min-valid-lifetime",
+ Element::create(static_cast<long long>(valid_.getMin())));
+ map->set("max-valid-lifetime",
+ Element::create(static_cast<long long>(valid_.getMax())));
+ }
+
+ // Set reservations-global
+ if (!reservations_global_.unspecified()) {
+ map->set("reservations-global",
+ Element::create(reservations_global_.get()));
+ }
+
+ // Set reservations-in-subnet
+ if (!reservations_in_subnet_.unspecified()) {
+ map->set("reservations-in-subnet",
+ Element::create(reservations_in_subnet_.get()));
+ }
+
+ // Set reservations-out-of-pool
+ if (!reservations_out_of_pool_.unspecified()) {
+ map->set("reservations-out-of-pool",
+ Element::create(reservations_out_of_pool_.get()));
+ }
+
+ // Set options
+ ConstCfgOptionPtr opts = getCfgOption();
+ map->set("option-data", opts->toElement());
+
+ // Output calculate-tee-times and percentages if calculation is enabled.
+ if (!calculate_tee_times_.unspecified()) {
+ map->set("calculate-tee-times", Element::create(calculate_tee_times_));
+ }
+
+ if (!t1_percent_.unspecified()) {
+ map->set("t1-percent", Element::create(t1_percent_));
+ }
+
+ if (!t2_percent_.unspecified()) {
+ map->set("t2-percent", Element::create(t2_percent_));
+ }
+
+ if (!ddns_send_updates_.unspecified()) {
+ map->set("ddns-send-updates", Element::create(ddns_send_updates_));
+ }
+
+ if (!ddns_override_no_update_.unspecified()) {
+ map->set("ddns-override-no-update", Element::create(ddns_override_no_update_));
+ }
+
+ if (!ddns_override_client_update_.unspecified()) {
+ map->set("ddns-override-client-update", Element::create(ddns_override_client_update_));
+ }
+
+ if (!ddns_replace_client_name_mode_.unspecified()) {
+ map->set("ddns-replace-client-name",
+ Element::create(D2ClientConfig::
+ replaceClientNameModeToString(ddns_replace_client_name_mode_)));
+ }
+
+ if (!ddns_generated_prefix_.unspecified()) {
+ map->set("ddns-generated-prefix", Element::create(ddns_generated_prefix_));
+ }
+
+ if (!ddns_qualifying_suffix_.unspecified()) {
+ map->set("ddns-qualifying-suffix", Element::create(ddns_qualifying_suffix_));
+ }
+
+ if (!ddns_ttl_percent_.unspecified()) {
+ map->set("ddns-ttl-percent", Element::create(ddns_ttl_percent_));
+ }
+
+ if (!hostname_char_set_.unspecified()) {
+ map->set("hostname-char-set", Element::create(hostname_char_set_));
+ }
+
+ if (!hostname_char_replacement_.unspecified()) {
+ map->set("hostname-char-replacement", Element::create(hostname_char_replacement_));
+ }
+
+ if (!store_extended_info_.unspecified()) {
+ map->set("store-extended-info", Element::create(store_extended_info_));
+ }
+
+ if (!cache_threshold_.unspecified()) {
+ map->set("cache-threshold", Element::create(cache_threshold_));
+ }
+
+ if (!cache_max_age_.unspecified()) {
+ map->set("cache-max-age",
+ Element::create(static_cast<long long>(cache_max_age_)));
+ }
+
+ if (!ddns_update_on_renew_.unspecified()) {
+ map->set("ddns-update-on-renew", Element::create(ddns_update_on_renew_));
+ }
+
+ if (!ddns_use_conflict_resolution_.unspecified()) {
+ map->set("ddns-use-conflict-resolution", Element::create(ddns_use_conflict_resolution_));
+ }
+
+ if (!allocator_type_.unspecified()) {
+ map->set("allocator", Element::create(allocator_type_));
+ }
+
+ return (map);
+}
+
+void
+Network4::setSiaddr(const Optional<IOAddress>& siaddr) {
+ if (!siaddr.get().isV4()) {
+ isc_throw(BadValue, "Can't set siaddr to non-IPv4 address "
+ << siaddr);
+ }
+ siaddr_ = siaddr;
+}
+
+void
+Network4::setSname(const Optional<std::string>& sname) {
+ sname_ = sname;
+}
+
+void
+Network4::setFilename(const Optional<std::string>& filename) {
+ filename_ = filename;
+}
+
+ElementPtr
+Network4::toElement() const {
+ ElementPtr map = Network::toElement();
+
+ // Set match-client-id
+ if (!match_client_id_.unspecified()) {
+ map->set("match-client-id", Element::create(match_client_id_.get()));
+ }
+
+ // Set authoritative
+ if (!authoritative_.unspecified()) {
+ map->set("authoritative", Element::create(authoritative_.get()));
+ }
+
+ // Set next-server
+ if (!siaddr_.unspecified()) {
+ map->set("next-server", Element::create(siaddr_.get().toText()));
+ }
+
+ // Set server-hostname
+ if (!sname_.unspecified()) {
+ map->set("server-hostname", Element::create(sname_.get()));
+ }
+
+ // Set boot-file-name
+ if (!filename_.unspecified()) {
+ map->set("boot-file-name",Element::create(filename_.get()));
+ }
+
+ // Set offer-lifetime
+ if (!offer_lft_.unspecified()) {
+ map->set("offer-lifetime",Element::create(offer_lft_.get()));
+ }
+
+ return (map);
+}
+
+IOAddress
+Network4::getServerId() const {
+ try {
+ OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
+ (cfg_option_->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
+ if (opt_server_id) {
+ return (opt_server_id->readAddress());
+ }
+ } catch (const std::exception&) {
+ // Ignore any exceptions and simply return empty buffer.
+ }
+
+ return (IOAddress::IPV4_ZERO_ADDRESS());
+}
+
+ElementPtr
+Network6::toElement() const {
+ ElementPtr map = Network::toElement();
+
+ // Set preferred-lifetime
+ if (!preferred_.unspecified()) {
+ map->set("preferred-lifetime",
+ Element::create(static_cast<long long>(preferred_.get())));
+ map->set("min-preferred-lifetime",
+ Element::create(static_cast<long long>(preferred_.getMin())));
+ map->set("max-preferred-lifetime",
+ Element::create(static_cast<long long>(preferred_.getMax())));
+ }
+
+ // Set interface-id
+ if (interface_id_) {
+ std::vector<uint8_t> bin = interface_id_->getData();
+ std::string ifid;
+ ifid.resize(bin.size());
+ if (!bin.empty()) {
+ std::memcpy(&ifid[0], &bin[0], bin.size());
+ }
+ map->set("interface-id", Element::create(ifid));
+ }
+
+ // Set rapid-commit
+ if (!rapid_commit_.unspecified()) {
+ map->set("rapid-commit", Element::create(rapid_commit_.get()));
+ }
+
+ // Set pd-allocator
+ if (!pd_allocator_type_.unspecified()) {
+ map->set("pd-allocator", Element::create(pd_allocator_type_));
+ }
+
+ return (map);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h
new file mode 100644
index 0000000..9d2a899
--- /dev/null
+++ b/src/lib/dhcpsrv/network.h
@@ -0,0 +1,1559 @@
+// Copyright (C) 2017-2023 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 NETWORK_H
+#define NETWORK_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <cc/element_value.h>
+#include <cc/stamped_element.h>
+#include <cc/user_context.h>
+#include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_globals.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfg_4o6.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <util/triplet.h>
+#include <util/optional.h>
+
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/identity.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <cstdint>
+#include <functional>
+#include <string>
+
+/// @brief Template to ignore unused arguments.
+namespace {
+template <typename... Args>
+inline void unused(Args const& ...) {}
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Tag for the list of IO addresses as a list.
+struct IOAddressListListTag { };
+
+/// @brief Tag for the list of IO addresses as a set.
+struct IOAddressListSetTag { };
+
+/// @brief List of IO addresses
+typedef boost::multi_index_container<
+ // Multi index container holds IO addresses.
+ asiolink::IOAddress,
+ // Indexes.
+ boost::multi_index::indexed_by<
+ // First and default index allows for in order iteration.
+ boost::multi_index::sequenced<
+ boost::multi_index::tag<IOAddressListListTag>
+ >,
+ // Second index allows for checking existence.
+ boost::multi_index::hashed_unique<
+ boost::multi_index::tag<IOAddressListSetTag>,
+ boost::multi_index::identity<asiolink::IOAddress>
+ >
+ >
+> IOAddressList;
+
+// @brief Forward declaration of the Network class.
+class Network;
+
+/// @brief Pointer to the @ref Network object.
+typedef boost::shared_ptr<Network> NetworkPtr;
+
+/// @brief Weak pointer to the @ref Network object.
+typedef boost::weak_ptr<Network> WeakNetworkPtr;
+
+/// @brief Callback function for @c Network that retrieves globally
+/// configured parameters.
+typedef std::function<ConstCfgGlobalsPtr()> FetchNetworkGlobalsFn;
+
+/// @brief Common interface representing a network to which the DHCP clients
+/// are connected.
+///
+/// The most common type of network, in Kea's terminology, is a subnet. The
+/// @ref Subnet derives from this class. Another types of objects implementing
+/// this interface are @ref SharedNetwork4 and @ref SharedNetwork6 objects.
+/// They group multiple subnets together to provide means for
+/// extending available address pools (a single client may obtain IP
+/// address from any of the pools belonging to subnets in the shared
+/// network), or for selecting a subnet on a given link, depending on the
+/// class of the client (e.g. cable network case: different subnet is
+/// selected for cable modems, different one for routers).
+///
+/// The subnets and shared networks share many data structures, e.g. DHCP
+/// options, local interface name, address manipulation methods. Both subnets
+/// and shared networks derive from this class to provide the common
+/// functionality.
+///
+/// The DHCP server configuration is complex because many parameters may
+/// be specified at different levels of hierarchy. The lower level values,
+/// e.g. subnet specific values, take precedence over upper level values,
+/// e.g. shared network specific ones. For historical reasons, the DHCP
+/// servers expect that the appropriate values are inherited from the
+/// upper configuration levels to the lower configuration levels upon
+/// the reconfiguration. For example: if a user didn't specify
+/// valid-lifetime for a subnet, calling @c Subnet4::getValid() should
+/// result in returning a global value of valid-lifetime. In the early
+/// Kea days it was achieved by the configuration parsers which would
+/// explicitly assign the global valid lifetime to the @c Subnet4
+/// instances for which the subnet specific value was not provided. This
+/// approach has a major benefit that it is fast. However, it makes
+/// the subnets tightly dependent on the global values (and shared
+/// network specific values). Modification of the global value must
+/// result in modification of this value in all subnets for which
+/// there is no explicit value provided. This issue became a serious
+/// road block during the implementation of the Configuration Backend.
+///
+/// The @c Network object has been modified to address the problem of
+/// inheritance of global, shared network specific and subnet specific
+/// parameters in a generic way, at the same time minimizing the need to
+/// change the existing server logic.
+///
+/// The @c Network object now holds the pointer to the "parent" @c Network
+/// object. Thus subnets which belong to a shared network will have
+/// that shared network as its parent. Stand-alone subnets, will have
+/// no parent.
+///
+/// The general idea is that the accessor functions of the network
+/// will first check if the accessed value is specified or not (that
+/// is handled by @c util::Optional object). If the value is specified
+/// it is returned. Otherwise, the object will check if there is a
+/// parent object it belongs to and will call the appropriate method
+/// of that object. If the value is present it is returned. Otherwise
+/// the global value is returned.
+///
+/// Accessing global values from the @c Network object is troublesome.
+/// There is no uniform way to access those values. For example, the
+/// given network may be in a staging or current configuration and
+/// it really has no means to know in which of the two it belongs.
+/// In fact, an attempt to pass the pointer to the @c SrvConfig object
+/// would cause a circular dependency between the @c Network and the
+/// @c SrvConfig. Even if it was possible and the @c Network had
+/// access to the specific @c SrvConfig instance, it doesn't handle
+/// the cases when the @c SrvConfig instance was modified.
+///
+/// To deal with the problem of accessing the global parameters in a
+/// flexible manner, we elected to use an optional callback function
+/// which can be associated with the @c Network object. This callback
+/// implements the logic to retrieve global parameters and return them
+/// in a well known form, so as the @c Network accessors can use them.
+class Network : public virtual isc::data::StampedElement,
+ public virtual isc::data::UserContext,
+ public isc::data::CfgToElement {
+public:
+ /// @brief Holds optional information about relay.
+ ///
+ /// In some cases it is beneficial to have additional information about
+ /// a relay configured in the subnet. For now, the structure holds only
+ /// IP addresses, but there may potentially be additional parameters added
+ /// later, e.g. relay interface-id or relay-id.
+ class RelayInfo {
+ public:
+
+ /// @brief Adds an address to the list of addresses
+ ///
+ /// @param addr address to add
+ /// @throw BadValue if the address is already in the list
+ void addAddress(const asiolink::IOAddress& addr);
+
+ /// @brief Returns const reference to the list of addresses
+ ///
+ /// @return const reference to the list of addresses
+ const IOAddressList& getAddresses() const;
+
+ /// @brief Indicates whether or not the address list has entries
+ ///
+ /// @return True if the address list is not empty
+ bool hasAddresses() const;
+
+ /// @brief Checks the address list for the given address
+ ///
+ /// @return True if the address is found in the address list
+ bool containsAddress(const asiolink::IOAddress& addr) const;
+
+ private:
+ /// @brief List of relay IP addresses
+ IOAddressList addresses_;
+ };
+
+ /// @brief Inheritance "mode" used when fetching an optional @c Network
+ /// parameter.
+ ///
+ /// The following modes are currently supported:
+ /// - NONE: no inheritance is used, the network specific value is returned
+ /// regardless if it is specified or not.
+ /// - PARENT_NETWORK: parent network specific value is returned or unspecified
+ /// if the parent network doesn't exist.
+ /// - GLOBAL: global specific value is returned.
+ /// - ALL: inheritance is used on all levels: network specific value takes
+ /// precedence over parent specific value over the global value.
+ enum class Inheritance {
+ NONE,
+ PARENT_NETWORK,
+ GLOBAL,
+ ALL
+ };
+
+ /// Pointer to the RelayInfo structure
+ typedef boost::shared_ptr<Network::RelayInfo> RelayInfoPtr;
+
+ /// @brief Constructor.
+ Network()
+ : iface_name_(), client_class_(), t1_(), t2_(), valid_(),
+ reservations_global_(false, true), reservations_in_subnet_(true, true),
+ reservations_out_of_pool_(false, true), cfg_option_(new CfgOption()),
+ calculate_tee_times_(), t1_percent_(), t2_percent_(),
+ ddns_send_updates_(), ddns_override_no_update_(), ddns_override_client_update_(),
+ ddns_replace_client_name_mode_(), ddns_generated_prefix_(), ddns_qualifying_suffix_(),
+ hostname_char_set_(), hostname_char_replacement_(), store_extended_info_(),
+ cache_threshold_(), cache_max_age_(), ddns_update_on_renew_(),
+ ddns_use_conflict_resolution_(), ddns_ttl_percent_(), allocator_type_(),
+ default_allocator_type_() {
+ }
+
+ /// @brief Virtual destructor.
+ ///
+ /// Does nothing at the moment.
+ virtual ~Network() { };
+
+ /// @brief Sets the optional callback function used to fetch globally
+ /// configured parameters.
+ ///
+ /// @param fetch_globals_fn Pointer to the function.
+ void setFetchGlobalsFn(FetchNetworkGlobalsFn fetch_globals_fn) {
+ fetch_globals_fn_ = fetch_globals_fn;
+ }
+
+ /// @brief Checks if the network is associated with a function used to
+ /// fetch globally configured parameters.
+ ///
+ /// @return true if it is associated, false otherwise.
+ bool hasFetchGlobalsFn() const {
+ return (static_cast<bool>(fetch_globals_fn_));
+ }
+
+ /// @brief Sets local name of the interface for which this network is
+ /// selected.
+ ///
+ /// If the interface is specified, the server will use the network
+ /// associated with this local interface to allocate IP addresses and
+ /// other resources to a client.
+ /// Empty values are translated into unspecified.
+ ///
+ /// @param iface_name Interface name.
+ void setIface(const util::Optional<std::string>& iface_name) {
+ if (iface_name.empty()) {
+ iface_name_ = util::Optional<std::string>("", true);
+ } else {
+ iface_name_ = iface_name;
+ }
+ }
+
+ /// @brief Returns name of the local interface for which this network is
+ /// selected.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return Interface name as optional text.
+ util::Optional<std::string>
+ getIface(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getIface, iface_name_,
+ inheritance));
+ };
+
+ /// @brief Sets information about relay
+ ///
+ /// In some situations where there are shared subnets (i.e. two different
+ /// subnets are available on the same physical link), there is only one
+ /// relay that handles incoming requests from clients. In such a case,
+ /// the usual subnet selection criteria based on relay belonging to the
+ /// subnet being selected are no longer sufficient and we need to explicitly
+ /// specify a relay. One notable example of such uncommon, but valid
+ /// scenario is a cable network, where there is only one CMTS (one relay),
+ /// but there are 2 distinct subnets behind it: one for cable modems
+ /// and another one for CPEs and other user equipment behind modems.
+ /// From manageability perspective, it is essential that modems get addresses
+ /// from different subnet, so users won't tinker with their modems.
+ ///
+ /// Setting this parameter is not needed in most deployments.
+ /// This structure holds IP address only for now, but it is expected to
+ /// be extended in the future.
+ ///
+ /// @param relay structure that contains relay information
+ void setRelayInfo(const RelayInfo& relay) {
+ relay_ = relay;
+ }
+
+ /// @brief Returns const reference to relay information
+ ///
+ /// @note The returned reference is only valid as long as the object
+ /// returned it is valid.
+ ///
+ /// @return const reference to the relay information
+ const RelayInfo& getRelayInfo() const {
+ return (relay_);
+ }
+
+ /// @brief Adds an address to the list addresses in the network's relay info
+ ///
+ /// @param addr address of the relay
+ /// @throw BadValue if the address is already in the list
+ void addRelayAddress(const asiolink::IOAddress& addr);
+
+ /// @brief Returns the list of relay addresses from the network's relay info
+ ///
+ /// @return const reference to the list of addresses
+ const IOAddressList& getRelayAddresses() const;
+
+ /// @brief Indicates if network's relay info has relay addresses
+ ///
+ /// @return True the relay list is not empty, false otherwise
+ bool hasRelays() const;
+
+ /// @brief Tests if the network's relay info contains the given address
+ ///
+ /// @param address address to search for in the relay list
+ /// @return True if a relay with the given address is found, false otherwise
+ bool hasRelayAddress(const asiolink::IOAddress& address) const;
+
+ /// @brief Checks whether this network supports client that belongs to
+ /// specified classes.
+ ///
+ /// This method checks whether a client that belongs to given classes can
+ /// use this network. For example, if this class is reserved for client
+ /// class "foo" and the client belongs to classes "foo", "bar" and "baz",
+ /// it is supported. On the other hand, client belonging to classes
+ /// "foobar" and "zyxxy" is not supported.
+ ///
+ /// @note: changed the planned white and black lists idea to a simple
+ /// client class name.
+ ///
+ /// @param client_classes list of all classes the client belongs to
+ /// @return true if client can be supported, false otherwise
+ virtual bool
+ clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
+
+ /// @brief Sets the supported class to class class_name
+ ///
+ /// @param class_name client class to be supported by this network
+ void allowClientClass(const isc::dhcp::ClientClass& class_name);
+
+ /// @brief Adds class class_name to classes required to be evaluated.
+ ///
+ /// @param class_name client class required to be evaluated
+ void requireClientClass(const isc::dhcp::ClientClass& class_name);
+
+ /// @brief Returns classes which are required to be evaluated
+ const ClientClasses& getRequiredClasses() const;
+
+ /// @brief returns the client class
+ ///
+ /// @note The returned reference is only valid as long as the object
+ /// returned it is valid.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return client class @ref client_class_
+ util::Optional<ClientClass>
+ getClientClass(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getClientClass, client_class_,
+ inheritance));
+ }
+
+ /// @brief Return valid-lifetime for addresses in that prefix
+ ///
+ /// @param inheritance inheritance mode to be used.
+ isc::util::Triplet<uint32_t> getValid(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getValid, valid_, inheritance,
+ CfgGlobals::VALID_LIFETIME,
+ CfgGlobals::MIN_VALID_LIFETIME,
+ CfgGlobals::MAX_VALID_LIFETIME));
+ }
+
+ /// @brief Sets new valid lifetime for a network.
+ ///
+ /// @param valid New valid lifetime in seconds.
+ void setValid(const isc::util::Triplet<uint32_t>& valid) {
+ valid_ = valid;
+ }
+
+ /// @brief Returns T1 (renew timer), expressed in seconds
+ ///
+ /// @param inheritance inheritance mode to be used.
+ isc::util::Triplet<uint32_t> getT1(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getT1, t1_, inheritance,
+ CfgGlobals::RENEW_TIMER));
+ }
+
+ /// @brief Sets new renew timer for a network.
+ ///
+ /// @param t1 New renew timer value in seconds.
+ void setT1(const isc::util::Triplet<uint32_t>& t1) {
+ t1_ = t1;
+ }
+
+ /// @brief Returns T2 (rebind timer), expressed in seconds
+ ///
+ /// @param inheritance inheritance mode to be used.
+ isc::util::Triplet<uint32_t> getT2(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getT2, t2_, inheritance,
+ CfgGlobals::REBIND_TIMER));
+ }
+
+ /// @brief Sets new rebind timer for a network.
+ ///
+ /// @param t2 New rebind timer value in seconds.
+ void setT2(const isc::util::Triplet<uint32_t>& t2) {
+ t2_ = t2;
+ }
+
+ /// @brief Returns whether global reservations should be fetched.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getReservationsGlobal(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getReservationsGlobal,
+ reservations_global_,
+ inheritance,
+ CfgGlobals::RESERVATIONS_GLOBAL));
+ }
+
+ /// @brief Sets whether global reservations should be fetched.
+ ///
+ /// @param reservations_global new value of enabled/disabled.
+ void setReservationsGlobal(const util::Optional<bool>& reservations_global) {
+ reservations_global_ = reservations_global;
+ }
+
+ /// @brief Returns whether subnet reservations should be fetched.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getReservationsInSubnet(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getReservationsInSubnet,
+ reservations_in_subnet_,
+ inheritance,
+ CfgGlobals::RESERVATIONS_IN_SUBNET));
+ }
+
+ /// @brief Sets whether subnet reservations should be fetched.
+ ///
+ /// @param reservations_in_subnet new value of enabled/disabled.
+ void setReservationsInSubnet(const util::Optional<bool>& reservations_in_subnet) {
+ reservations_in_subnet_ = reservations_in_subnet;
+ }
+
+ /// @brief Returns whether only out-of-pool reservations are allowed.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getReservationsOutOfPool(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getReservationsOutOfPool,
+ reservations_out_of_pool_,
+ inheritance,
+ CfgGlobals::RESERVATIONS_OUT_OF_POOL));
+ }
+
+ /// @brief Sets whether only out-of-pool reservations are allowed.
+ ///
+ /// @param reservations_out_of_pool new value of enabled/disabled.
+ void setReservationsOutOfPool(const util::Optional<bool>& reservations_out_of_pool) {
+ reservations_out_of_pool_ = reservations_out_of_pool;
+ }
+
+ /// @brief Returns pointer to the option data configuration for this network.
+ CfgOptionPtr getCfgOption() {
+ return (cfg_option_);
+ }
+
+ /// @brief Returns const pointer to the option data configuration for this
+ /// network.
+ ConstCfgOptionPtr getCfgOption() const {
+ return (cfg_option_);
+ }
+
+ /// @brief Returns whether or not T1/T2 calculation is enabled.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getCalculateTeeTimes(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getCalculateTeeTimes,
+ calculate_tee_times_,
+ inheritance,
+ CfgGlobals::CALCULATE_TEE_TIMES));
+ }
+
+ /// @brief Sets whether or not T1/T2 calculation is enabled.
+ ///
+ /// @param calculate_tee_times new value of enabled/disabled.
+ void setCalculateTeeTimes(const util::Optional<bool>& calculate_tee_times) {
+ calculate_tee_times_ = calculate_tee_times;
+ }
+
+ /// @brief Returns percentage to use when calculating the T1 (renew timer).
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<double>
+ getT1Percent(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getT1Percent, t1_percent_,
+ inheritance, CfgGlobals::T1_PERCENT));
+ }
+
+ /// @brief Sets new percentage for calculating T1 (renew timer).
+ ///
+ /// @param t1_percent New percentage to use.
+ void setT1Percent(const util::Optional<double>& t1_percent) {
+ t1_percent_ = t1_percent;
+ }
+
+ /// @brief Returns percentage to use when calculating the T2 (rebind timer).
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<double>
+ getT2Percent(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getT2Percent, t2_percent_,
+ inheritance, CfgGlobals::T2_PERCENT));
+ }
+
+ /// @brief Sets new percentage for calculating T2 (rebind timer).
+ ///
+ /// @param t2_percent New percentage to use.
+ void setT2Percent(const util::Optional<double>& t2_percent) {
+ t2_percent_ = t2_percent;
+ }
+
+ /// @brief Returns ddns-send-updates
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getDdnsSendUpdates(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsSendUpdates,
+ ddns_send_updates_, inheritance,
+ CfgGlobals::DDNS_SEND_UPDATES));
+ }
+
+ /// @brief Sets new ddns-send-updates
+ ///
+ /// @param ddns_send_updates New value to use.
+ void setDdnsSendUpdates(const util::Optional<bool>& ddns_send_updates) {
+ ddns_send_updates_ = ddns_send_updates;
+ }
+
+ /// @brief Returns ddns-override-no-update
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getDdnsOverrideNoUpdate(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsOverrideNoUpdate,
+ ddns_override_no_update_, inheritance,
+ CfgGlobals::DDNS_OVERRIDE_NO_UPDATE));
+ }
+
+ /// @brief Sets new ddns-override-no-update
+ ///
+ /// @param ddns_override_no_update New value to use.
+ void setDdnsOverrideNoUpdate(const util::Optional<bool>& ddns_override_no_update) {
+ ddns_override_no_update_ = ddns_override_no_update;
+ }
+
+ /// @brief Returns ddns-override-client-update
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getDdnsOverrideClientUpdate(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsOverrideClientUpdate,
+ ddns_override_client_update_, inheritance,
+ CfgGlobals::DDNS_OVERRIDE_CLIENT_UPDATE));
+ }
+
+ /// @brief Sets new ddns-override-client-update
+ ///
+ /// @param ddns_override_client_update New value to use.
+ void setDdnsOverrideClientUpdate(const util::Optional<bool>&
+ ddns_override_client_update) {
+ ddns_override_client_update_ = ddns_override_client_update;
+ }
+
+ /// @brief Returns ddns-replace-client-name-mode
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<D2ClientConfig::ReplaceClientNameMode>
+ getDdnsReplaceClientNameMode(const Inheritance& inheritance = Inheritance::ALL) const {
+ // Inheritance for ddns-replace-client-name is a little different than for other
+ // parameters. The value at the global level is given as a string.
+ // Thus we call getProperty here without a global name to check if it
+ // is specified on network level only.
+ const util::Optional<D2ClientConfig::ReplaceClientNameMode>& mode =
+ getProperty<Network>(&Network::getDdnsReplaceClientNameMode,
+ ddns_replace_client_name_mode_, inheritance);
+
+ // If it is not specified at network level we need this special
+ // case code to convert the global string value to an enum.
+ if (mode.unspecified() && (inheritance != Inheritance::NONE) &&
+ (inheritance != Inheritance::PARENT_NETWORK)) {
+ // Get global mode.
+ util::Optional<std::string> mode_label;
+ mode_label = getGlobalProperty(mode_label,
+ CfgGlobals::DDNS_REPLACE_CLIENT_NAME);
+ if (!mode_label.unspecified()) {
+ try {
+ // If the mode is globally configured, convert it to an enum.
+ return (D2ClientConfig::stringToReplaceClientNameMode(mode_label.get()));
+ } catch (...) {
+ // This should not really happen because the configuration
+ // parser should have already verified the globally configured
+ // mode. However, we want to be 100% sure that this
+ // method doesn't throw. Let's just return unspecified.
+ return (mode);
+ }
+ }
+ }
+ return (mode);
+ }
+
+ /// @brief Sets new ddns-replace-client-name-mode
+ ///
+ /// @param ddns_replace_client_name_mode New value to use.
+ void
+ setDdnsReplaceClientNameMode(const util::Optional<D2ClientConfig::ReplaceClientNameMode>&
+ ddns_replace_client_name_mode) {
+ ddns_replace_client_name_mode_ = ddns_replace_client_name_mode;
+ }
+
+ /// @brief Returns ddns-generated-prefix
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<std::string>
+ getDdnsGeneratedPrefix(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsGeneratedPrefix,
+ ddns_generated_prefix_, inheritance,
+ CfgGlobals::DDNS_GENERATED_PREFIX));
+ }
+
+ /// @brief Sets new ddns-generated-prefix
+ ///
+ /// @param ddns_generated_prefix New value to use.
+ void setDdnsGeneratedPrefix(const util::Optional<std::string>& ddns_generated_prefix) {
+ ddns_generated_prefix_ = ddns_generated_prefix;
+ }
+
+ /// @brief Returns ddns-qualifying-suffix
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<std::string>
+ getDdnsQualifyingSuffix(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsQualifyingSuffix,
+ ddns_qualifying_suffix_, inheritance,
+ CfgGlobals::DDNS_QUALIFYING_SUFFIX));
+ }
+
+ /// @brief Sets new ddns-qualifying-suffix
+ ///
+ /// @param ddns_qualifying_suffix New value to use.
+ void setDdnsQualifyingSuffix(const util::Optional<std::string>& ddns_qualifying_suffix) {
+ ddns_qualifying_suffix_ = ddns_qualifying_suffix;
+ }
+
+ /// @brief Returns ddns-ttl-percent
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<double>
+ getDdnsTtlPercent(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsTtlPercent,
+ ddns_ttl_percent_, inheritance,
+ CfgGlobals::DDNS_TTL_PERCENT));
+ }
+
+ /// @brief Sets new ddns-ttl-percent
+ ///
+ /// @param ddns_ttl_percent New value to use.
+ void setDdnsTtlPercent(const util::Optional<double>& ddns_ttl_percent) {
+ ddns_ttl_percent_ = ddns_ttl_percent;
+ }
+
+ /// @brief Return the char set regexp used to sanitize client hostnames.
+ util::Optional<std::string>
+ getHostnameCharSet(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getHostnameCharSet,
+ hostname_char_set_, inheritance,
+ CfgGlobals::HOSTNAME_CHAR_SET));
+ }
+
+ /// @brief Sets new hostname-char-set
+ ///
+ /// @param hostname_char_set New value to use.
+ void setHostnameCharSet(const util::Optional<std::string>& hostname_char_set) {
+ hostname_char_set_ = hostname_char_set;
+ }
+
+ /// @brief Return the invalid char replacement used to sanitize client hostnames.
+ util::Optional<std::string>
+ getHostnameCharReplacement(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getHostnameCharReplacement,
+ hostname_char_replacement_, inheritance,
+ CfgGlobals::HOSTNAME_CHAR_REPLACEMENT));
+ }
+
+ /// @brief Sets new hostname-char-replacement
+ ///
+ /// @param hostname_char_replacement New value to use.
+ void setHostnameCharReplacement(const util::Optional<std::string>&
+ hostname_char_replacement) {
+ hostname_char_replacement_ = hostname_char_replacement;
+ }
+
+ /// @brief Returns store-extended-info
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getStoreExtendedInfo(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getStoreExtendedInfo,
+ store_extended_info_, inheritance,
+ CfgGlobals::STORE_EXTENDED_INFO));
+ }
+
+ /// @brief Sets new store-extended-info
+ ///
+ /// @param store_extended_info New value to use.
+ void setStoreExtendedInfo(const util::Optional<bool>& store_extended_info) {
+ store_extended_info_ = store_extended_info;
+ }
+
+ /// @brief Returns percentage to use as cache threshold.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<double>
+ getCacheThreshold(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getCacheThreshold,
+ cache_threshold_, inheritance,
+ CfgGlobals::CACHE_THRESHOLD));
+ }
+
+ /// @brief Sets cache threshold for a network.
+ ///
+ /// @param cache_threshold New cache threshold percentage to use.
+ void setCacheThreshold(const util::Optional<double>& cache_threshold) {
+ cache_threshold_ = cache_threshold;
+ }
+
+ /// @brief Returns value in seconds to use as cache maximum age.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<uint32_t>
+ getCacheMaxAge(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getCacheMaxAge, cache_max_age_,
+ inheritance, CfgGlobals::CACHE_MAX_AGE));
+ }
+
+ /// @brief Sets cache max for a network.
+ ///
+ /// @param cache_max_age New cache maximum value in seconds to use.
+ void setCacheMaxAge(const util::Optional<uint32_t>& cache_max_age) {
+ cache_max_age_ = cache_max_age;
+ }
+
+ /// @brief Returns ddns-update-on-renew
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getDdnsUpdateOnRenew(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsUpdateOnRenew,
+ ddns_update_on_renew_, inheritance,
+ CfgGlobals::DDNS_UPDATE_ON_RENEW));
+ }
+
+ /// @brief Sets new ddns-update-on-renew
+ ///
+ /// @param ddns_update_on_renew New value to use.
+ void setDdnsUpdateOnRenew(const util::Optional<bool>& ddns_update_on_renew) {
+ ddns_update_on_renew_ = ddns_update_on_renew;
+ }
+
+ /// @brief Returns ddns-use-conflict-resolution
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<bool>
+ getDdnsUseConflictResolution(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDdnsUseConflictResolution,
+ ddns_use_conflict_resolution_,
+ inheritance,
+ CfgGlobals::DDNS_USE_CONFLICT_RESOLUTION));
+ }
+
+ /// @brief Sets new ddns-use-conflict-resolution
+ ///
+ /// @param ddns_use_conflict_resolution New value to use.
+ void setDdnsUseConflictResolution(const util::Optional<bool>& ddns_use_conflict_resolution) {
+ ddns_use_conflict_resolution_ = ddns_use_conflict_resolution;
+ }
+
+ /// @brief Returns allocator type.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<std::string>
+ getAllocatorType(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getAllocatorType,
+ allocator_type_,
+ inheritance,
+ CfgGlobals::ALLOCATOR));
+ }
+
+ /// @brief Sets new allocator type.
+ ///
+ /// It doesn't set the actual allocator instance. It merely remembers the
+ /// value specified in the configuration, so it can be output in the
+ /// @c toElement call.
+ ///
+ /// @param allocator_type new allocator type to use.
+ void setAllocatorType(const util::Optional<std::string>& allocator_type) {
+ allocator_type_ = allocator_type;
+ }
+
+ /// @brief Returns a default allocator type.
+ ///
+ /// This allocator type is used when the allocator type is neither specified
+ /// at the shared network nor subnet level.
+ ///
+ /// @return an allocator type as a string.
+ util::Optional<std::string>
+ getDefaultAllocatorType(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network>(&Network::getDefaultAllocatorType,
+ default_allocator_type_,
+ inheritance));
+ }
+
+ /// @brief Sets a defalt allocator type.
+ ///
+ /// @param allocator_type a new default allocator type.
+ void setDefaultAllocatorType(const std::string& allocator_type) {
+ default_allocator_type_ = allocator_type;
+ }
+
+ /// @brief Unparses network object.
+ ///
+ /// @return A pointer to unparsed network configuration.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief Generates an identifying label for logging.
+ ///
+ /// @return string containing the label
+ virtual std::string getLabel() const {
+ return ("base-network");
+ }
+
+protected:
+
+ /// @brief Gets the optional callback function used to fetch globally
+ /// configured parameters.
+ ///
+ /// @return Pointer to the function.
+ FetchNetworkGlobalsFn getFetchGlobalsFn() const {
+ return (fetch_globals_fn_);
+ }
+
+ /// @brief Returns a value of global configuration parameter with
+ /// a given index.
+ ///
+ /// If the @c ferch_globals_fn_ function is non-null, this method will
+ /// invoke this function to retrieve a global value having the given
+ /// index. Typically, this method is invoked by @c getProperty when
+ /// network specific value of the parameter is not found. In some cases
+ /// it may be called by other methods. One such example is the
+ /// @c getDdnsReplaceClientNameMode which needs to call @c getGlobalProperty
+ /// explicitly to convert the global replace client name mode value from
+ /// a string to an enum.
+ ///
+ /// @tparam ReturnType Type of the returned value, e.g.
+ /// @c Optional<std::string>.
+ ///
+ /// @param property Value to be returned when it is specified or when
+ /// no global value is found.
+ /// @param global_index Index of the global parameter which value should
+ /// be returned
+ /// @param min_index Index of the min global parameter which value should
+ /// be returned for triplets
+ /// @param max_index Index of the max global parameter which value should
+ /// be returned for triplets
+ ///
+ /// @return Optional value fetched from the global level or the value
+ /// of @c property.
+ template<typename ReturnType>
+ ReturnType getGlobalProperty(ReturnType property,
+ const int global_index,
+ const int min_index = -1,
+ const int max_index = -1) const {
+ unused(min_index, max_index);
+ if ((global_index >= 0) && fetch_globals_fn_) {
+ ConstCfgGlobalsPtr globals = fetch_globals_fn_();
+ if (globals) {
+ data::ConstElementPtr global_param = globals->get(global_index);
+ if (global_param) {
+ // If there is a global parameter, convert it to the
+ // optional value of the given type and return.
+ return (data::ElementValue<typename ReturnType::ValueType>()(global_param));
+ }
+ }
+ }
+ return (property);
+ }
+
+ /// @brief The @c getGlobalProperty specialization for isc::util::Triplet<T>.
+ ///
+ /// @note: use overloading vs specialization because full specialization
+ /// is not allowed in this scope.
+ ///
+ /// @tparam NumType Type of the encapsulated value(s).
+ ///
+ /// @param property Value to be returned when it is specified or when
+ /// no global value is found.
+ /// @param global_index Index of the global parameter which value should
+ /// be returned
+ /// @param min_index Index of the min global parameter which value should
+ /// be returned for triplets
+ /// @param max_index Index of the max global parameter which value should
+ /// be returned for triplets
+ ///
+ /// @return Optional value fetched from the global level or the value
+ /// of @c property.
+ template<typename NumType>
+ isc::util::Triplet<NumType> getGlobalProperty(isc::util::Triplet<NumType> property,
+ const int global_index,
+ const int min_index = -1,
+ const int max_index = -1) const {
+
+ if ((global_index >= 0) && fetch_globals_fn_) {
+ ConstCfgGlobalsPtr globals = fetch_globals_fn_();
+ if (globals) {
+ data::ConstElementPtr param = globals->get(global_index);
+ if (param) {
+ NumType def_value = static_cast<NumType>(param->intValue());
+ if ((min_index < 0) || (max_index < 0)) {
+ return (def_value);
+ } else {
+ NumType min_value = def_value;
+ NumType max_value = def_value;
+ data::ConstElementPtr min_param = globals->get(min_index);
+ if (min_param) {
+ min_value = static_cast<NumType>(min_param->intValue());
+ }
+ data::ConstElementPtr max_param = globals->get(max_index);
+ if (max_param) {
+ max_value = static_cast<NumType>(max_param->intValue());
+ }
+ return (isc::util::Triplet<NumType>(min_value, def_value, max_value));
+ }
+ }
+ }
+ }
+ return (property);
+ }
+
+ /// @brief The @c getGlobalProperty specialization for Optional<IOAddress>.
+ ///
+ /// This does two things:
+ /// - uses the string value of the parameter
+ /// - falls back when the value is empty
+ ///
+ /// @note: use overloading vs specialization because full specialization
+ /// is not allowed in this scope.
+ ///
+ /// @param property Value to be returned when it is specified or when
+ /// no global value is found.
+ /// @param global_index Index of the global parameter which value should
+ /// be returned
+ /// @param min_index Index of the min global parameter which value should
+ /// be returned for triplets
+ /// @param max_index Index of the max global parameter which value should
+ /// be returned for triplets
+ ///
+ /// @return Optional value fetched from the global level or the value
+ /// of @c property.
+ util::Optional<asiolink::IOAddress>
+ getGlobalProperty(util::Optional<asiolink::IOAddress> property,
+ const int global_index,
+ const int min_index = -1,
+ const int max_index = -1) const;
+
+ /// @brief Returns a value associated with a network using inheritance.
+ ///
+ /// This template method provides a generic mechanism to retrieve a
+ /// network parameter using inheritance. It is called from public
+ /// accessor methods which return an @c OptionalValue or @c isc::util::Triplet.
+ ///
+ /// @tparam BaseType Type of this instance, e.g. @c Network, @c Network4
+ /// etc, which exposes a method to be called.
+ /// @tparam ReturnType Type of the returned value, e.g.
+ /// @c Optional<std::string>.
+ ///
+ /// @param MethodPointer Pointer to the method of the base class which
+ /// should be called on the parent network instance (typically on
+ /// @c SharedNetwork4 or @c SharedNetwork6) to fetch the parent specific
+ /// value if the value is unspecified for this instance.
+ /// @param property Value to be returned when it is specified or when
+ /// no explicit value is specified on upper inheritance levels.
+ /// @param inheritance inheritance mode to be used.
+ /// @param global_index Optional index of the global parameter which value
+ /// should be returned if the given parameter is not specified on network
+ /// level. This value is empty by default, which indicates that the
+ /// global value for the given parameter is not supported and shouldn't
+ /// be fetched.
+ /// @param min_index Index of the min global parameter which value should
+ /// be returned for triplets
+ /// @param max_index Index of the max global parameter which value should
+ /// be returned for triplets
+ ///
+ /// @return Optional value fetched from this instance level, parent
+ /// network level or global level
+ template<typename BaseType, typename ReturnType>
+ ReturnType getProperty(ReturnType(BaseType::*MethodPointer)(const Inheritance&) const,
+ ReturnType property,
+ const Inheritance& inheritance,
+ const int global_index = -1,
+ const int min_index = -1,
+ const int max_index = -1) const {
+
+ // If no inheritance is to be used, return the value for this
+ // network regardless if it is specified or not.
+ if (inheritance == Inheritance::NONE) {
+ return (property);
+
+ } else if (inheritance == Inheritance::PARENT_NETWORK) {
+ ReturnType parent_property;
+
+ // Check if this instance has a parent network.
+ auto parent = boost::dynamic_pointer_cast<BaseType>(parent_network_.lock());
+ if (parent) {
+ parent_property = ((*parent).*MethodPointer)(Network::Inheritance::NONE);
+ }
+ return (parent_property);
+
+ // If global value requested, return it.
+ } else if (inheritance == Inheritance::GLOBAL) {
+ return (getGlobalProperty(ReturnType(), global_index, min_index, max_index));
+ }
+
+ // We use inheritance and the value is not specified on the network level.
+ // Hence, we need to get the parent network specific value or global value.
+ if (property.unspecified()) {
+ // Check if this instance has a parent network.
+ auto parent = boost::dynamic_pointer_cast<BaseType>(parent_network_.lock());
+ // If the parent network exists, let's fetch the parent specific
+ // value.
+ if (parent) {
+ // We're using inheritance so ask for the parent specific network
+ // and return it only if it is specified.
+ auto parent_property = ((*parent).*MethodPointer)(inheritance);
+ if (!parent_property.unspecified()) {
+ return (parent_property);
+ }
+ }
+
+ // The value is not specified on network level. If the value
+ // can be specified on global level and there is a callback
+ // that returns the global values, try to find this parameter
+ // at the global scope.
+ return (getGlobalProperty(property, global_index, min_index, max_index));
+ }
+
+ // We haven't found the value at any level, so return the unspecified.
+ return (property);
+ }
+
+ /// @brief Returns option pointer associated with a network using inheritance.
+ ///
+ /// This template method provides a generic mechanism to retrieve a
+ /// network parameter using inheritance. It is called from public
+ /// accessor methods which return an @c OptionPtr.
+ ///
+ /// @tparam BaseType Type of this instance, e.g. @c Network, @c Network4
+ /// etc, which exposes a method to be called.
+ ///
+ /// @param MethodPointer Pointer to the method of the base class which
+ /// should be called on the parent network instance (typically on
+ /// @c SharedNetwork4 or @c SharedNetwork6) to fetch the parent specific
+ /// value if the value is unspecified for this instance.
+ /// @param property the value to return when inheritance mode is NONE, or
+ /// when the mode is PARENT_NETWORK and the property has not been specified
+ /// by a parent network.
+ /// @param inheritance inheritance mode to be used.
+ ///
+ /// @return Option pointer fetched from this instance level or parent
+ /// network level.
+ template<typename BaseType>
+ OptionPtr
+ getOptionProperty(OptionPtr(BaseType::*MethodPointer)(const Inheritance& inheritance) const,
+ OptionPtr property,
+ const Inheritance& inheritance) const {
+ if (inheritance == Network::Inheritance::NONE) {
+ return (property);
+
+ } else if (inheritance == Network::Inheritance::PARENT_NETWORK) {
+ OptionPtr parent_property;
+ // Check if this instance has a parent network.
+ auto parent = boost::dynamic_pointer_cast<BaseType>(parent_network_.lock());
+ // If the parent network exists, let's fetch the parent specific
+ // value.
+ if (parent) {
+ parent_property = ((*parent).*MethodPointer)(Network::Inheritance::NONE);
+ }
+ return (parent_property);
+
+ } else if (inheritance == Network::Inheritance::GLOBAL) {
+ return (OptionPtr());
+ }
+
+ // We use inheritance and the value is not specified on the network level.
+ // Hence, we need to get the parent network specific value.
+ if (!property) {
+ // Check if this instance has a parent network.
+ auto parent = boost::dynamic_pointer_cast<BaseType>(parent_network_.lock());
+ if (parent) {
+ // We're using inheritance so ask for the parent specific network
+ // and return it only if it is specified.
+ OptionPtr parent_property = (((*parent).*MethodPointer)(inheritance));
+ if (parent_property) {
+ return (parent_property);
+ }
+ }
+ }
+
+ // We haven't found the value at any level, so return the unspecified.
+ return (property);
+ }
+
+ /// @brief Holds interface name for which this network is selected.
+ util::Optional<std::string> iface_name_;
+
+ /// @brief Relay information
+ ///
+ /// See @ref RelayInfo for detailed description.
+ RelayInfo relay_;
+
+ /// @brief Optional definition of a client class
+ ///
+ /// If defined, only clients belonging to that class will be allowed to use
+ /// this particular network. The default value for this is an empty string,
+ /// which means that any client is allowed, regardless of its class.
+ util::Optional<ClientClass> client_class_;
+
+ /// @brief Required classes
+ ///
+ /// If the network is selected these classes will be added to the
+ /// incoming packet and their evaluation will be required.
+ ClientClasses required_classes_;
+
+ /// @brief a isc::util::Triplet (min/default/max) holding allowed renew timer values
+ isc::util::Triplet<uint32_t> t1_;
+
+ /// @brief a isc::util::Triplet (min/default/max) holding allowed rebind timer values
+ isc::util::Triplet<uint32_t> t2_;
+
+ /// @brief a isc::util::Triplet (min/default/max) holding allowed valid lifetime values
+ isc::util::Triplet<uint32_t> valid_;
+
+ /// @brief Enables global reservations.
+ util::Optional<bool> reservations_global_;
+
+ /// @brief Enables subnet reservations.
+ util::Optional<bool> reservations_in_subnet_;
+
+ /// @brief Enables out-of-pool reservations optimization.
+ ///
+ /// When true only out-of-pool reservations are allowed. This allows
+ /// AllocEngine to skip reservation checks when dealing with addresses
+ /// that are in pool.
+ util::Optional<bool> reservations_out_of_pool_;
+
+ /// @brief Pointer to the option data configuration for this subnet.
+ CfgOptionPtr cfg_option_;
+
+ /// @brief Enables calculation of T1 and T2 timers
+ util::Optional<bool> calculate_tee_times_;
+
+ /// @brief Percentage of the lease lifetime to use when calculating T1 timer
+ util::Optional<double> t1_percent_;
+
+ /// @brief Percentage of the lease lifetime to use when calculating T2 timer
+ util::Optional<double> t2_percent_;
+
+ /// @brief Should Kea perform DNS updates. Used to provide scoped enabling
+ /// and disabling of updates.
+ util::Optional<bool> ddns_send_updates_;
+
+ /// @brief Should Kea perform updates, even if client requested no updates.
+ /// Overrides the client request for no updates via the N flag.
+ util::Optional<bool> ddns_override_no_update_;
+
+ /// @brief Should Kea perform updates, even if client requested delegation.
+ util::Optional<bool> ddns_override_client_update_;
+
+ /// @brief How Kea should handle the domain-name supplied by the client.
+ util::Optional<D2ClientConfig::ReplaceClientNameMode> ddns_replace_client_name_mode_;
+
+ /// @brief Prefix Kea should use when generating domain-names.
+ util::Optional<std::string> ddns_generated_prefix_;
+
+ /// @brief Suffix Kea should use when to qualify partial domain-names.
+ util::Optional<std::string> ddns_qualifying_suffix_;
+
+ /// @brief Regular expression describing invalid characters for client
+ /// hostnames.
+ util::Optional<std::string> hostname_char_set_;
+
+ /// @brief A string to replace invalid characters when scrubbing hostnames.
+ /// Meaningful only if hostname_char_set_ is not empty.
+ util::Optional<std::string> hostname_char_replacement_;
+
+ /// @brief Should Kea store additional client query data (e.g. relay-agent-info)
+ /// on the lease.
+ util::Optional<bool> store_extended_info_;
+
+ /// @brief Percentage of the lease lifetime to use as cache threshold.
+ util::Optional<double> cache_threshold_;
+
+ /// @brief Value in seconds to use as cache maximal age.
+ util::Optional<uint32_t> cache_max_age_;
+
+ /// @brief Should Kea perform updates when leases are extended
+ util::Optional<bool> ddns_update_on_renew_;
+
+ /// @brief Used to to tell kea-dhcp-ddns whether or not to use conflict resolution.
+ util::Optional<bool> ddns_use_conflict_resolution_;
+
+ /// @brief Percentage of the lease lifetime to use for DNS TTL.
+ util::Optional<double> ddns_ttl_percent_;
+
+ /// @brief Allocator used for IP address allocations.
+ util::Optional<std::string> allocator_type_;
+
+ /// @brief Default allocator type.
+ ///
+ /// This value is not configurable by the user. It is used by the configuration
+ /// backend internally.
+ util::Optional<std::string> default_allocator_type_;
+
+ /// @brief Pointer to another network that this network belongs to.
+ ///
+ /// The most common case is that this instance is a subnet which belongs
+ /// to a shared network and the @c parent_network_ points to the shared
+ /// network object. If the network instance (subnet) doesn't belong to
+ /// a shared network this pointer is null.
+ WeakNetworkPtr parent_network_;
+
+ /// @brief Pointer to the optional callback used to fetch globally
+ /// configured parameters inherited to the @c Network object.
+ FetchNetworkGlobalsFn fetch_globals_fn_;
+};
+
+/// @brief Specialization of the @ref Network object for DHCPv4 case.
+class Network4 : public virtual Network {
+public:
+
+ /// @brief Constructor.
+ Network4()
+ : Network(), match_client_id_(true, true), authoritative_(),
+ siaddr_(), sname_(), filename_(), offer_lft_() {
+ }
+
+ /// @brief Returns the flag indicating if the client identifiers should
+ /// be used to identify the client's lease.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return true if client identifiers should be used, false otherwise.
+ util::Optional<bool>
+ getMatchClientId(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network4>(&Network4::getMatchClientId,
+ match_client_id_,
+ inheritance,
+ CfgGlobals::MATCH_CLIENT_ID));
+ }
+
+ /// @brief Sets the flag indicating if the client identifier should be
+ /// used to identify the client's lease.
+ ///
+ /// @param match If this value is true, the client identifiers are not
+ /// used for lease lookup.
+ void setMatchClientId(const util::Optional<bool>& match) {
+ match_client_id_ = match;
+ }
+
+ /// @brief Returns the flag indicating if requests for unknown IP addresses
+ /// should be rejected with DHCPNAK instead of ignored.
+ ///
+ /// @param inheritance inheritance mode to be used.w
+ /// @return true if requests for unknown IP addresses should be rejected,
+ /// false otherwise.
+ util::Optional<bool>
+ getAuthoritative(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network4>(&Network4::getAuthoritative,
+ authoritative_, inheritance,
+ CfgGlobals::AUTHORITATIVE));
+ }
+
+ /// @brief Sets the flag indicating if requests for unknown IP addresses
+ /// should be rejected with DHCPNAK instead of ignored.
+ ///
+ /// @param authoritative If this value is true, the requests for unknown IP
+ /// addresses will be rejected with DHCPNAK messages
+ void setAuthoritative(const util::Optional<bool>& authoritative) {
+ authoritative_ = authoritative;
+ }
+
+ /// @brief Sets siaddr for the network.
+ ///
+ /// Will be used for siaddr field (the next server) that typically is used
+ /// as TFTP server. If not specified, the default value of 0.0.0.0 is
+ /// used.
+ void setSiaddr(const util::Optional<asiolink::IOAddress>& siaddr);
+
+ /// @brief Returns siaddr for this network.
+ ///
+ /// @return siaddr value
+ util::Optional<asiolink::IOAddress>
+ getSiaddr(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network4>(&Network4::getSiaddr, siaddr_,
+ inheritance, CfgGlobals::NEXT_SERVER));
+ }
+
+ /// @brief Sets server hostname for the network.
+ ///
+ /// Will be used for server hostname field (may be empty if not defined)
+ void setSname(const util::Optional<std::string>& sname);
+
+ /// @brief Returns server hostname for this network.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return server hostname value
+ util::Optional<std::string>
+ getSname(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network4>(&Network4::getSname, sname_,
+ inheritance,
+ CfgGlobals::SERVER_HOSTNAME));
+ }
+
+ /// @brief Sets boot file name for the network.
+ ///
+ /// Will be used for boot file name (may be empty if not defined)
+ void setFilename(const util::Optional<std::string>& filename);
+
+ /// @brief Returns boot file name for this subnet
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return boot file name value
+ util::Optional<std::string>
+ getFilename(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network4>(&Network4::getFilename, filename_,
+ inheritance,
+ CfgGlobals::BOOT_FILE_NAME));
+ }
+
+ /// @brief Sets offer lifetime for the network.
+ ///
+ /// @param offer_lft the offer lifetime assigned to the class (may be empty if not defined)
+ void setOfferLft(const util::Optional<uint32_t>& offer_lft) {
+ offer_lft_ = offer_lft;
+ }
+
+ /// @brief Returns offer lifetime for the network
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return offer lifetime value
+ util::Optional<uint32_t>
+ getOfferLft(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network4>(&Network4::getOfferLft, offer_lft_,
+ inheritance,
+ CfgGlobals::OFFER_LIFETIME));
+ }
+
+ /// @brief Unparses network object.
+ ///
+ /// @return A pointer to unparsed network configuration.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief Returns binary representation of the dhcp-server-identifier option (54).
+ ///
+ /// @return Server identifier option as IPv4 address. Zero IPv4 address
+ /// indicates that server identifier hasn't been specified.
+ virtual asiolink::IOAddress getServerId() const;
+
+private:
+
+ /// @brief Should server use client identifiers for client lease
+ /// lookup.
+ util::Optional<bool> match_client_id_;
+
+ /// @brief Should requests for unknown IP addresses be rejected.
+ util::Optional<bool> authoritative_;
+
+ /// @brief siaddr value for this network
+ util::Optional<asiolink::IOAddress> siaddr_;
+
+ /// @brief server hostname for this network
+ util::Optional<std::string> sname_;
+
+ /// @brief boot file name for this network
+ util::Optional<std::string> filename_;
+
+ /// @brief offer lifetime for this network
+ util::Optional<uint32_t> offer_lft_;
+};
+
+/// @brief Pointer to the @ref Network4 object.
+typedef boost::shared_ptr<Network4> Network4Ptr;
+
+class Network6;
+
+/// @brief Pointer to the @ref Network6 object.
+typedef boost::shared_ptr<Network6> Network6Ptr;
+
+/// @brief Specialization of the @ref Network object for DHCPv6 case.
+class Network6 : public virtual Network {
+public:
+
+ /// @brief Constructor.
+ Network6()
+ : Network(), preferred_(), interface_id_(), rapid_commit_(),
+ default_pd_allocator_type_(){
+ }
+
+ /// @brief Returns preferred lifetime (in seconds)
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return a triplet with preferred lifetime
+ isc::util::Triplet<uint32_t>
+ getPreferred(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network6>(&Network6::getPreferred, preferred_,
+ inheritance,
+ CfgGlobals::PREFERRED_LIFETIME,
+ CfgGlobals::MIN_PREFERRED_LIFETIME,
+ CfgGlobals::MAX_PREFERRED_LIFETIME));
+ }
+
+ /// @brief Sets new preferred lifetime for a network.
+ ///
+ /// @param preferred New preferred lifetime in seconds.
+ void setPreferred(const isc::util::Triplet<uint32_t>& preferred) {
+ preferred_ = preferred;
+ }
+
+ /// @brief Returns interface-id value (if specified)
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return interface-id option (if defined)
+ OptionPtr getInterfaceId(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getOptionProperty<Network6>(&Network6::getInterfaceId, interface_id_,
+ inheritance));
+ }
+
+ /// @brief sets interface-id option (if defined)
+ ///
+ /// @param ifaceid pointer to interface-id option
+ void setInterfaceId(const OptionPtr& ifaceid) {
+ interface_id_ = ifaceid;
+ }
+
+ /// @brief Returns boolean value indicating that the Rapid Commit option
+ /// is supported or unsupported for the subnet.
+ ///
+ /// @note This parameter does not exist at the global level.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ /// @return true if the Rapid Commit option is supported, false otherwise.
+ util::Optional<bool>
+ getRapidCommit(const Inheritance& inheritance = Inheritance::ALL) const {
+
+ return (getProperty<Network6>(&Network6::getRapidCommit, rapid_commit_,
+ inheritance));
+ }
+
+ /// @brief Enables or disables Rapid Commit option support for the subnet.
+ ///
+ /// @param rapid_commit A boolean value indicating that the Rapid Commit
+ /// option support is enabled (if true), or disabled (if false).
+ void setRapidCommit(const util::Optional<bool>& rapid_commit) {
+ rapid_commit_ = rapid_commit;
+ };
+
+ /// @brief Returns allocator type for prefix delegation.
+ ///
+ /// @param inheritance inheritance mode to be used.
+ util::Optional<std::string>
+ getPdAllocatorType(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network6>(&Network6::getPdAllocatorType,
+ pd_allocator_type_,
+ inheritance,
+ CfgGlobals::PD_ALLOCATOR));
+ }
+
+ /// @brief Sets new allocator type for prefix delegation.
+ ///
+ /// It doesn't set the actual allocator instance. It merely remembers the
+ /// value specified in the configuration, so it can be output in the
+ /// @c toElement call.
+ ///
+ /// @param allocator_type new allocator type to use.
+ void setPdAllocatorType(const util::Optional<std::string>& allocator_type) {
+ pd_allocator_type_ = allocator_type;
+ }
+
+ /// @brief Returns a default allocator type for prefix delegation.
+ ///
+ /// This allocator type is used when the allocator type is neither specified
+ /// at the shared network nor subnet level.
+ ///
+ /// @return an allocator type as a string.
+ util::Optional<std::string>
+ getDefaultPdAllocatorType(const Inheritance& inheritance = Inheritance::ALL) const {
+ return (getProperty<Network6>(&Network6::getDefaultPdAllocatorType,
+ default_pd_allocator_type_,
+ inheritance));
+ }
+
+ /// @brief Sets a defalt allocator type for prefix delegation.
+ ///
+ /// @param allocator_type a new default allocator type.
+ void setDefaultPdAllocatorType(const std::string& allocator_type) {
+ default_pd_allocator_type_ = allocator_type;
+ }
+
+ /// @brief Unparses network object.
+ ///
+ /// @return A pointer to unparsed network configuration.
+ virtual data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief a triplet with preferred lifetime (in seconds)
+ isc::util::Triplet<uint32_t> preferred_;
+
+ /// @brief specifies optional interface-id
+ OptionPtr interface_id_;
+
+ /// @brief A flag indicating if Rapid Commit option is supported
+ /// for this network.
+ ///
+ /// It's default value is false, which indicates that the Rapid
+ /// Commit is disabled for the subnet.
+ util::Optional<bool> rapid_commit_;
+
+ /// @brief Allocator used for prefix delegation.
+ util::Optional<std::string> pd_allocator_type_;
+
+ // @brief Default allocator type for prefix delegation.
+ util::Optional<std::string> default_pd_allocator_type_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // NETWORK_H
diff --git a/src/lib/dhcpsrv/network_state.cc b/src/lib/dhcpsrv/network_state.cc
new file mode 100644
index 0000000..14e4fe7
--- /dev/null
+++ b/src/lib/dhcpsrv/network_state.cc
@@ -0,0 +1,332 @@
+// Copyright (C) 2017-2021 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 <dhcpsrv/network_state.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <functional>
+#include <string>
+
+using namespace isc::util;
+
+namespace {
+
+/// @brief Name of the timer used by the @c NetworkState class.
+const std::string NETWORK_STATE_TIMER_NAME_USER_CMD = "network-state-timer-user-cmd";
+const std::string NETWORK_STATE_TIMER_NAME_HA_CMD = "network-state-timer-ha-cmd";
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implementation of the @c NetworkState class.
+class NetworkStateImpl : public boost::enable_shared_from_this<NetworkStateImpl> {
+public:
+
+ /// @brief Constructor.
+ NetworkStateImpl(const NetworkState::ServerType& server_type)
+ : server_type_(server_type), globally_disabled_(false),
+ disabled_subnets_(), disabled_networks_(),
+ timer_mgr_(TimerMgr::instance()), disabled_by_user_command_(false),
+ disabled_by_ha_command_(false), disabled_by_db_connection_(0) {
+ }
+
+ /// @brief Destructor.
+ ~NetworkStateImpl() {
+ destroyTimer(NetworkState::Origin::USER_COMMAND);
+ destroyTimer(NetworkState::Origin::HA_COMMAND);
+ }
+
+ /// @brief Sets appropriate disabled or enabled DHCP service state for the
+ /// respective origin.
+ ///
+ /// @note If any of the user commands, HA internal commands or connection
+ /// recovery processes disable the dhcp service, the service will remain
+ /// disabled until all flags are cleared.
+ /// In the case of the connection recovery a reference count is used
+ /// internally, so that all connections must be restored before enabling
+ /// the network state.
+ ///
+ /// @param disable The value of the flag used to perform the transition.
+ /// @param origin The origin of the state transition.
+ void setDisableService(const bool disable,
+ const NetworkState::Origin& origin) {
+ if (disable) {
+ // Disable the service for any flag.
+ globally_disabled_ = true;
+ switch (origin) {
+ case NetworkState::Origin::USER_COMMAND:
+ disabled_by_user_command_ = true;
+ break;
+ case NetworkState::Origin::HA_COMMAND:
+ disabled_by_ha_command_ = true;
+ break;
+ case NetworkState::Origin::DB_CONNECTION:
+ ++disabled_by_db_connection_;
+ break;
+ default:
+ isc_throw(NotImplemented, "origin value not handled when "
+ "disabling the network state");
+ break;
+ }
+ } else {
+ switch (origin) {
+ case NetworkState::Origin::USER_COMMAND:
+ disabled_by_user_command_ = false;
+ break;
+ case NetworkState::Origin::HA_COMMAND:
+ disabled_by_ha_command_ = false;
+ break;
+ case NetworkState::Origin::DB_CONNECTION:
+ // Never go below 0 (using unsigned type).
+ // This should never happen anyway.
+ if (disabled_by_db_connection_) {
+ --disabled_by_db_connection_;
+ }
+ break;
+ default:
+ isc_throw(NotImplemented, "origin value not handled when "
+ "enabling the network state");
+ break;
+ }
+ // Enable the service only if all flags have been cleared.
+ if (!disabled_by_user_command_ && !disabled_by_ha_command_ &&
+ disabled_by_db_connection_ == 0) {
+ globally_disabled_ = false;
+ }
+ }
+ }
+
+ /// @brief Reset internal counters for a specific origin.
+ ///
+ /// @note The dhcp service will remain disabled until all flags are cleared.
+ ///
+ /// @param origin The origin of the state transition.
+ void reset(const NetworkState::Origin& origin) {
+ switch (origin) {
+ case NetworkState::Origin::USER_COMMAND:
+ disabled_by_user_command_ = false;
+ break;
+ case NetworkState::Origin::HA_COMMAND:
+ disabled_by_ha_command_ = false;
+ break;
+ case NetworkState::Origin::DB_CONNECTION:
+ disabled_by_db_connection_ = 0;
+ break;
+ default:
+ isc_throw(NotImplemented, "origin value not handled when "
+ "resetting the network state");
+ break;
+ }
+ // Enable the service only if all flags have been cleared.
+ if (!disabled_by_user_command_ && !disabled_by_ha_command_ &&
+ disabled_by_db_connection_ == 0) {
+ globally_disabled_ = false;
+ }
+ }
+
+ /// @brief Enables DHCP service globally and per scopes.
+ ///
+ /// If delayed enabling DHCP service has been scheduled, it cancels it.
+ ///
+ /// @param origin The origin of the state transition.
+ void enableAll(const NetworkState::Origin& origin) {
+ setDisableService(false, origin);
+
+ /// @todo Enable service for all subnets and networks here.
+
+ destroyTimer(origin);
+ }
+
+ /// @brief Creates a timer counting the time when @c enableAll should be
+ /// automatically called.
+ ///
+ /// If the timer has been already scheduled, it is destroyed and replaced
+ /// with a new timer.
+ ///
+ /// @param seconds Number of seconds to elapse before the @c enableAll is
+ /// called.
+ /// @param origin The origin of the state transition.
+ void createTimer(const unsigned int seconds,
+ const NetworkState::Origin& origin) {
+ destroyTimer(origin);
+ std::string timer_name = NETWORK_STATE_TIMER_NAME_USER_CMD;
+ switch (origin) {
+ case NetworkState::Origin::USER_COMMAND:
+ timer_name = NETWORK_STATE_TIMER_NAME_USER_CMD;
+ break;
+ case NetworkState::Origin::HA_COMMAND:
+ timer_name = NETWORK_STATE_TIMER_NAME_HA_CMD;
+ break;
+ case NetworkState::Origin::DB_CONNECTION:
+ isc_throw(BadValue, "DB connection does not support delayed enable");
+ break;
+ default:
+ isc_throw(NotImplemented, "origin value not handled when creating "
+ "a timer for delayed enable");
+ break;
+ }
+ timer_mgr_->registerTimer(timer_name,
+ std::bind(&NetworkStateImpl::enableAll,
+ shared_from_this(), origin),
+ seconds * 1000,
+ asiolink::IntervalTimer::ONE_SHOT);
+ timer_mgr_->setup(timer_name);
+ }
+
+ /// @brief Destroys a timer if present.
+ ///
+ /// @param origin The origin of the state transition.
+ void destroyTimer(const NetworkState::Origin& origin) {
+ std::string timer_name = NETWORK_STATE_TIMER_NAME_USER_CMD;
+ switch (origin) {
+ case NetworkState::Origin::USER_COMMAND:
+ timer_name = NETWORK_STATE_TIMER_NAME_USER_CMD;
+ break;
+ case NetworkState::Origin::HA_COMMAND:
+ timer_name = NETWORK_STATE_TIMER_NAME_HA_CMD;
+ break;
+ case NetworkState::Origin::DB_CONNECTION:
+ return;
+ default:
+ isc_throw(NotImplemented, "origin value not handled when creating "
+ "a timer for delayed enable");
+ break;
+ }
+ if (timer_mgr_->isTimerRegistered(timer_name)) {
+ timer_mgr_->unregisterTimer(timer_name);
+ }
+ }
+
+ /// @brief Server type.
+ NetworkState::ServerType server_type_;
+
+ /// @brief A flag indicating if DHCP service is globally disabled.
+ bool globally_disabled_;
+
+ /// @brief A list of subnets for which the DHCP service has been disabled.
+ NetworkState::Subnets disabled_subnets_;
+
+ /// @brief A list of networks for which the DHCP service has been disabled.
+ NetworkState::Networks disabled_networks_;
+
+ /// @brief A pointer to the common timer manager.
+ ///
+ /// This pointer is held here to make sure that the timer manager is not
+ /// destroyed before an instance of this class is destroyed.
+ TimerMgrPtr timer_mgr_;
+
+ /// @brief Flag which indicates the state has been disabled by an user
+ /// command.
+ bool disabled_by_user_command_;
+
+ /// @brief Flag which indicates the state has been disabled by the HA
+ /// command.
+ bool disabled_by_ha_command_;
+
+ /// @brief Flag which indicates the state has been disabled by a DB
+ /// connection loss.
+ uint32_t disabled_by_db_connection_;
+};
+
+NetworkState::NetworkState(const NetworkState::ServerType& server_type)
+ : impl_(new NetworkStateImpl(server_type)), mutex_(new std::mutex()) {
+}
+
+void
+NetworkState::disableService(const Origin& origin) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(*mutex_);
+ impl_->setDisableService(true, origin);
+ } else {
+ impl_->setDisableService(true, origin);
+ }
+}
+
+void
+NetworkState::enableService(const Origin& origin) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(*mutex_);
+ impl_->setDisableService(false, origin);
+ } else {
+ impl_->setDisableService(false, origin);
+ }
+}
+
+void
+NetworkState::reset(const NetworkState::Origin& origin) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(*mutex_);
+ impl_->reset(origin);
+ } else {
+ impl_->reset(origin);
+ }
+}
+
+void
+NetworkState::enableAll(const NetworkState::Origin& origin) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(*mutex_);
+ impl_->enableAll(origin);
+ } else {
+ impl_->enableAll(origin);
+ }
+}
+
+void
+NetworkState::delayedEnableAll(const unsigned int seconds,
+ const NetworkState::Origin& origin) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(*mutex_);
+ impl_->createTimer(seconds, origin);
+ } else {
+ impl_->createTimer(seconds, origin);
+ }
+}
+
+bool
+NetworkState::isServiceEnabled() const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(*mutex_);
+ return (!impl_->globally_disabled_);
+ } else {
+ return (!impl_->globally_disabled_);
+ }
+}
+
+bool
+NetworkState::isDelayedEnableAll() const {
+ return (TimerMgr::instance()->isTimerRegistered(NETWORK_STATE_TIMER_NAME_USER_CMD) ||
+ TimerMgr::instance()->isTimerRegistered(NETWORK_STATE_TIMER_NAME_HA_CMD));
+}
+
+void
+NetworkState::selectiveDisable(const NetworkState::Subnets&) {
+ isc_throw(NotImplemented, "selectiveDisableService is not implemented");
+}
+
+void
+NetworkState::selectiveDisable(const NetworkState::Networks&) {
+ isc_throw(NotImplemented, "selectiveDisableService is not implemented");
+}
+
+void
+NetworkState::selectiveEnable(const NetworkState::Subnets&) {
+ isc_throw(NotImplemented, "selectiveEnableService is not implemented");
+}
+
+void
+NetworkState::selectiveEnable(const NetworkState::Networks&) {
+ isc_throw(NotImplemented, "selectiveEnableService is not implemented");
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/network_state.h b/src/lib/dhcpsrv/network_state.h
new file mode 100644
index 0000000..def52e6
--- /dev/null
+++ b/src/lib/dhcpsrv/network_state.h
@@ -0,0 +1,209 @@
+// Copyright (C) 2017-2021 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 NETWORK_STATE_H
+#define NETWORK_STATE_H
+
+#include <cc/data.h>
+#include <dhcpsrv/subnet_id.h>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <set>
+#include <mutex>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+class NetworkStateImpl;
+
+/// @brief Holds information about DHCP service enabling status.
+///
+/// When the DHCP server receives a command to disable DHCP service entirely
+/// or for specific networks, this has to be recorded to allow for re-enabling
+/// DHCP service for these networks as a result of receiving a command from
+/// the administrator or when the timeout for re-enabling the service occurs.
+/// Currently there are two types of command originating either from user or
+/// HA internal mechanism.
+/// The global state can also be altered by the DB recovery mechanism which
+/// disables the service on connection loss and re-enables it after the
+/// connection is restored. Because the server supports recovery for multiple
+/// connections, this is implemented using an internal counter.
+/// Combining all the origins of the alteration of the network state, the
+/// behavior is:
+/// a) the network state is disabled if any of the originators explicitly set
+/// the disabled flag.
+/// b) the network state is restored only if all originators explicitly clear
+/// the disabled flag.
+/// In the future, it will be possible to specify "disabled" parameter for
+/// a subnet (or network) in the configuration file to indicate that this subnet
+/// should be excluded from the service. When a command is subsequently sent to
+/// temporarily disable a service for some other subnets for a specified amount
+/// of time, only these subnets should be re-enabled when the time elapses. This
+/// class fulfills this requirement by recording the subnets disabled with a command
+/// and re-enabling them when required. The subnets specified as "disabled" in
+/// the configuration file should remain disabled until explicitly enabled with a
+/// control command.
+///
+/// This class also allows for disabling the DHCP service globally. In this case
+/// the server drops all received packets.
+///
+/// The "dhcp-disable" and "dhcp-enable" commands are used for globally disabling
+/// and enabling the DHCP service. The "dhcp-disable-scopes" and "dhcp-enable-scopes"
+/// commands are used to disable and enable DHCP service for subnets and networks.
+/// In case of the "dhcp-disable" and "dhcp-disable-scopes" commands, it is possible
+/// to specify "max-period" parameter which provides a timeout, after which the
+/// settings are reverted (service is re-enabled globally and/or for specific
+/// scopes).
+///
+/// Disabling DHCP service with a timeout is useful to guard against issues when
+/// the controlling client dies after disabling the DHCP service on the server,
+/// e.g. failover peers may instruct each other to disable the DHCP service while
+/// database synchronization takes place. If the peer subsequently dies, the
+/// surviving server must re-enable DHCP on its own.
+///
+/// @todo This class currently supports only the case of globally disabling
+/// the DHCP service. Disabling per network/subnet will be added later.
+class NetworkState {
+public:
+
+ /// @brief DHCP server type.
+ enum ServerType {
+ DHCPv4,
+ DHCPv6
+ };
+
+ /// @brief Origin of the network state transition.
+ ///
+ /// The enumeration indicates the originator of the state transition of the
+ /// network state: either user command, HA internal command or DB connection
+ /// recovery mechanism.
+ enum class Origin {
+ /// @brief The network state is being altered by a user command.
+ USER_COMMAND,
+ /// @brief The network state is being altered by a HA internal command.
+ HA_COMMAND,
+ /// @brief The network state is being altered by the DB connection
+ /// recovery mechanics.
+ DB_CONNECTION
+ };
+
+ /// @brief Type of the container holding collection of subnet identifiers.
+ typedef std::set<SubnetID> Subnets;
+
+ /// @brief Type of the container holding collection of shared network names.
+ typedef std::set<std::string> Networks;
+
+ /// @brief Constructor.
+ NetworkState(const ServerType& server_type);
+
+ /// @brief Disable the DHCP service state for respective transition origin.
+ ///
+ /// @note If any of the user commands, HA internal commands or connection
+ /// recovery processes disable the dhcp service, the service will remain
+ /// disabled until all flags are cleared.
+ ///
+ /// @param origin The origin of the state transition.
+ void disableService(const NetworkState::Origin& origin);
+
+ /// @brief Enable the DHCP service state for respective transition origin.
+ ///
+ /// @note If any of the user commands, HA internal commands or connection
+ /// recovery processes disable the dhcp service, the service will remain
+ /// disabled until all flags are cleared.
+ ///
+ /// @param origin The origin of the state transition.
+ void enableService(const NetworkState::Origin& origin);
+
+ /// @brief Reset internal counters.
+ ///
+ /// Reset internal counters for a specific 'origin' after the server has
+ /// been reconfigured or all the connections have been restored.
+ ///
+ /// @param type The origin for which the state flags need to be reset.
+ void reset(const NetworkState::Origin& type);
+
+ /// @brief Enables DHCP service globally and for scopes which have been
+ /// disabled as a result of control command.
+ ///
+ /// @param origin The origin of the state transition.
+ void enableAll(const NetworkState::Origin& origin);
+
+ /// @brief Schedules enabling DHCP service in the future.
+ ///
+ /// @param seconds Number of seconds after which the service should be enabled
+ /// unless @c enableAll is enabled before that time.
+ /// @param origin The origin of the state transition.
+ void delayedEnableAll(const unsigned int seconds,
+ const NetworkState::Origin& origin);
+
+ /// @brief Checks if the DHCP service is globally enabled.
+ ///
+ /// @return true if the service is globally enabled, false otherwise.
+ bool isServiceEnabled() const;
+
+ /// @brief Checks if delayed enabling of DHCP services is scheduled.
+ ///
+ /// It indicates that the timer is present which counts the time until
+ /// @c enableAll function will be called automatically.
+ ///
+ /// @return true if delayed enabling of the DHCP service is scheduled,
+ /// false otherwise.
+ bool isDelayedEnableAll() const;
+
+ /// @name Selective disabling/enabling DHCP service per scopes
+ //@{
+
+ /// @brief Disable DHCP service for selected subnets.
+ ///
+ /// @param subnets Collection of subnet identifiers for which the service
+ /// should be disabled.
+ ///
+ /// @throw isc::NotImplemented
+ void selectiveDisable(const NetworkState::Subnets& subnets);
+
+ /// @brief Disable DHCP service for selected networks.
+ ///
+ /// @param networks Collection of shared network names for which the service
+ /// should be disabled.
+ ///
+ /// @throw isc::NotImplemented
+ void selectiveDisable(const NetworkState::Networks& networks);
+
+ /// @brief Enable DHCP service for selected subnets.
+ ///
+ /// @param subnets Collection of subnet identifiers for which the service
+ /// should be disabled.
+ ///
+ /// @throw isc::NotImplemented
+ void selectiveEnable(const NetworkState::Subnets& subnets);
+
+ /// @brief Enable DHCP service for selected networks.
+ ///
+ /// @param networks Collection of shared network names for which the service
+ /// should be enabled.
+ ///
+ /// @throw isc::NotImplemented
+ void selectiveEnable(const NetworkState::Networks& networks);
+
+ //@}
+
+private:
+
+ /// @brief Pointer to the @c NetworkState implementation.
+ boost::shared_ptr<NetworkStateImpl> impl_;
+
+ /// @brief The mutex used to protect internal state.
+ const boost::scoped_ptr<std::mutex> mutex_;
+};
+
+/// @brief Pointer to the @c NetworkState object.
+typedef boost::shared_ptr<NetworkState> NetworkStatePtr;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // NETWORK_STATE_H
diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.cc b/src/lib/dhcpsrv/parsers/base_network_parser.cc
new file mode 100644
index 0000000..d5a21b2
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/base_network_parser.cc
@@ -0,0 +1,324 @@
+// Copyright (C) 2019-2023 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/triplet.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/base_network_parser.h>
+#include <util/optional.h>
+#include <util/strutil.h>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+BaseNetworkParser::moveReservationMode(ElementPtr config) {
+ if (!config->contains("reservation-mode")) {
+ return;
+ }
+ if (config->contains("reservations-global") ||
+ config->contains("reservations-in-subnet") ||
+ config->contains("reservations-out-of-pool")) {
+ isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'"
+ " and one of 'reservations-global', 'reservations-in-subnet'"
+ " or 'reservations-out-of-pool' parameters");
+ }
+ std::string hr_mode = getString(config, "reservation-mode");
+ if ((hr_mode == "disabled") || (hr_mode == "off")) {
+ config->set("reservations-global", Element::create(false));
+ config->set("reservations-in-subnet", Element::create(false));
+ } else if (hr_mode == "out-of-pool") {
+ config->set("reservations-global", Element::create(false));
+ config->set("reservations-in-subnet", Element::create(true));
+ config->set("reservations-out-of-pool", Element::create(true));
+ } else if (hr_mode == "global") {
+ config->set("reservations-global", Element::create(true));
+ config->set("reservations-in-subnet", Element::create(false));
+ } else if (hr_mode == "all") {
+ config->set("reservations-global", Element::create(false));
+ config->set("reservations-in-subnet", Element::create(true));
+ config->set("reservations-out-of-pool", Element::create(false));
+ } else {
+ isc_throw(DhcpConfigError, "invalid reservation-mode parameter: '"
+ << hr_mode << "' ("
+ << getPosition("reservation-mode", config) << ")");
+ }
+ config->remove("reservation-mode");
+}
+
+void
+BaseNetworkParser::moveReservationMode(CfgGlobalsPtr config) {
+ if (!config->get(CfgGlobals::RESERVATION_MODE)) {
+ return;
+ }
+ if (config->get(CfgGlobals::RESERVATIONS_GLOBAL) ||
+ config->get(CfgGlobals::RESERVATIONS_IN_SUBNET) ||
+ config->get(CfgGlobals::RESERVATIONS_OUT_OF_POOL)) {
+ isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'"
+ " and one of 'reservations-global', 'reservations-in-subnet'"
+ " or 'reservations-out-of-pool' parameters");
+ }
+ std::string hr_mode = config->get(CfgGlobals::RESERVATION_MODE)->stringValue();
+ if ((hr_mode == "disabled") || (hr_mode == "off")) {
+ config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(false));
+ config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(false));
+ } else if (hr_mode == "out-of-pool") {
+ config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(false));
+ config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(true));
+ config->set(CfgGlobals::RESERVATIONS_OUT_OF_POOL, Element::create(true));
+ } else if (hr_mode == "global") {
+ config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(true));
+ config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(false));
+ } else if (hr_mode == "all") {
+ config->set(CfgGlobals::RESERVATIONS_GLOBAL, Element::create(false));
+ config->set(CfgGlobals::RESERVATIONS_IN_SUBNET, Element::create(true));
+ config->set("reservations-out-of-pool", Element::create(false));
+ } else {
+ isc_throw(DhcpConfigError, "invalid reservation-mode parameter: '"
+ << hr_mode << "' ("
+ << config->get(CfgGlobals::RESERVATION_MODE)->getPosition()
+ << ")");
+ }
+ config->set(CfgGlobals::RESERVATION_MODE, ConstElementPtr());
+}
+
+void
+BaseNetworkParser::parseCommon(const ConstElementPtr& network_data,
+ NetworkPtr& network) {
+ bool has_renew = network_data->contains("renew-timer");
+ bool has_rebind = network_data->contains("rebind-timer");
+ int64_t renew = -1;
+ int64_t rebind = -1;
+
+ if (has_renew) {
+ renew = getInteger(network_data, "renew-timer");
+ if (renew < 0) {
+ isc_throw(DhcpConfigError, "the value of renew-timer ("
+ << renew << ") must be a positive number");
+ }
+ network->setT1(renew);
+ }
+
+ if (has_rebind) {
+ rebind = getInteger(network_data, "rebind-timer");
+ if (rebind < 0) {
+ isc_throw(DhcpConfigError, "the value of rebind-timer ("
+ << rebind << ") must be a positive number");
+ }
+ network->setT2(rebind);
+ }
+
+ if (has_renew && has_rebind && (renew > rebind)) {
+ // The renew-timer value is too large and server logic
+ // later on will end up not sending it. Warn the user but
+ // allow the configuration to pass.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_RENEW_GTR_REBIND)
+ .arg(network->getLabel())
+ .arg(renew)
+ .arg(rebind);
+ }
+
+ network->setValid(parseIntTriplet(network_data, "valid-lifetime"));
+
+ if (network_data->contains("store-extended-info")) {
+ network->setStoreExtendedInfo(getBoolean(network_data,
+ "store-extended-info"));
+ }
+
+ if (network_data->contains("reservations-global")) {
+ network->setReservationsGlobal(getBoolean(network_data,
+ "reservations-global"));
+ }
+
+ if (network_data->contains("reservations-in-subnet")) {
+ network->setReservationsInSubnet(getBoolean(network_data,
+ "reservations-in-subnet"));
+ }
+
+ if (network_data->contains("reservations-out-of-pool")) {
+ network->setReservationsOutOfPool(getBoolean(network_data,
+ "reservations-out-of-pool"));
+ }
+}
+
+void
+BaseNetworkParser::parseTeePercents(const ConstElementPtr& network_data,
+ NetworkPtr& network) {
+ bool calculate_tee_times = network->getCalculateTeeTimes();
+ if (network_data->contains("calculate-tee-times")) {
+ calculate_tee_times = getBoolean(network_data, "calculate-tee-times");
+ network->setCalculateTeeTimes(calculate_tee_times);
+ }
+
+ Optional<double> t2_percent;
+ if (network_data->contains("t2-percent")) {
+ t2_percent = getDouble(network_data, "t2-percent");
+ }
+
+ Optional<double> t1_percent;
+ if (network_data->contains("t1-percent")) {
+ t1_percent = getDouble(network_data, "t1-percent");
+ }
+ if (calculate_tee_times) {
+ if (!t2_percent.unspecified() && ((t2_percent.get() <= 0.0) ||
+ (t2_percent.get() >= 1.0))) {
+ isc_throw(DhcpConfigError, "t2-percent: " << t2_percent.get()
+ << " is invalid, it must be greater than 0.0 and less than 1.0");
+ }
+
+ if (!t1_percent.unspecified() && ((t1_percent.get() <= 0.0) ||
+ (t1_percent.get() >= 1.0))) {
+ isc_throw(DhcpConfigError, "t1-percent: " << t1_percent.get()
+ << " is invalid it must be greater than 0.0 and less than 1.0");
+ }
+
+ if (!t1_percent.unspecified() && !t2_percent.unspecified() &&
+ (t1_percent.get() >= t2_percent.get())) {
+ isc_throw(DhcpConfigError, "t1-percent: " << t1_percent.get()
+ << " is invalid, it must be less than t2-percent: "
+ << t2_percent.get());
+ }
+ }
+
+ network->setT2Percent(t2_percent);
+ network->setT1Percent(t1_percent);
+}
+
+void
+BaseNetworkParser::parseCacheParams(const ConstElementPtr& network_data,
+ NetworkPtr& network) {
+ if (network_data->contains("cache-threshold")) {
+ double cache_threshold = getDouble(network_data, "cache-threshold");
+ if ((cache_threshold <= 0.0) || (cache_threshold >= 1.0)) {
+ isc_throw(DhcpConfigError, "cache-threshold: " << cache_threshold
+ << " is invalid, it must be greater than 0.0 and less than 1.0");
+ }
+ network->setCacheThreshold(cache_threshold);
+ }
+
+ if (network_data->contains("cache-max-age")) {
+ network->setCacheMaxAge(getInteger(network_data, "cache-max-age"));
+ }
+}
+
+void
+BaseNetworkParser::parseDdnsParams(const data::ConstElementPtr& network_data,
+ NetworkPtr& network) {
+
+ if (network_data->contains("ddns-send-updates")) {
+ network->setDdnsSendUpdates(getBoolean(network_data, "ddns-send-updates"));
+ }
+
+ if (network_data->contains("ddns-override-no-update")) {
+ network->setDdnsOverrideNoUpdate(getBoolean(network_data, "ddns-override-no-update"));
+ }
+
+ if (network_data->contains("ddns-override-client-update")) {
+ network->setDdnsOverrideClientUpdate(getBoolean(network_data, "ddns-override-client-update"));
+ }
+
+ if (network_data->contains("ddns-replace-client-name")) {
+ network->setDdnsReplaceClientNameMode(getAndConvert<D2ClientConfig::ReplaceClientNameMode,
+ D2ClientConfig::stringToReplaceClientNameMode>
+ (network_data, "ddns-replace-client-name",
+ "ReplaceClientName mode"));
+ }
+
+ if (network_data->contains("ddns-generated-prefix")) {
+ network->setDdnsGeneratedPrefix(getString(network_data, "ddns-generated-prefix"));
+ }
+
+ if (network_data->contains("ddns-qualifying-suffix")) {
+ network->setDdnsQualifyingSuffix(getString(network_data, "ddns-qualifying-suffix"));
+ }
+
+ std::string hostname_char_set;
+ if (network_data->contains("hostname-char-set")) {
+ hostname_char_set = getString(network_data, "hostname-char-set");
+ network->setHostnameCharSet(hostname_char_set);
+ }
+
+ std::string hostname_char_replacement;
+ if (network_data->contains("hostname-char-replacement")) {
+ hostname_char_replacement = getString(network_data, "hostname-char-replacement");
+ network->setHostnameCharReplacement(hostname_char_replacement);
+ }
+
+ // We need to validate sanitizer values here so we can detect problems and
+ // cause a configuration. We don't retain the compilation because it's not
+ // something we can inherit.
+ if (!hostname_char_set.empty()) {
+ try {
+ str::StringSanitizerPtr sanitizer(new str::StringSanitizer(hostname_char_set,
+ hostname_char_replacement));
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "hostname-char-set '" << hostname_char_set
+ << "' is not a valid regular expression");
+ }
+ }
+
+ if (network_data->contains("ddns-update-on-renew")) {
+ network->setDdnsUpdateOnRenew(getBoolean(network_data, "ddns-update-on-renew"));
+ }
+
+ if (network_data->contains("ddns-use-conflict-resolution")) {
+ network->setDdnsUseConflictResolution(getBoolean(network_data, "ddns-use-conflict-resolution"));
+ }
+
+ if (network_data->contains("ddns-ttl-percent")) {
+ network->setDdnsTtlPercent(getDouble(network_data, "ddns-ttl-percent"));
+ }
+}
+
+void
+BaseNetworkParser::parseAllocatorParams(const data::ConstElementPtr& network_data,
+ NetworkPtr& network) {
+ if (network_data->contains("allocator")) {
+ auto allocator_type = getString(network_data, "allocator");
+ if ((allocator_type != "iterative") && (allocator_type != "random") &&
+ (allocator_type != "flq")) {
+ // Unsupported allocator type used.
+ isc_throw(DhcpConfigError, "supported allocators are: iterative, random and flq");
+ }
+ network->setAllocatorType(allocator_type);
+ }
+}
+
+void
+BaseNetworkParser::parsePdAllocatorParams(const data::ConstElementPtr& network_data,
+ Network6Ptr& network) {
+ if (network_data->contains("pd-allocator")) {
+ auto allocator_type = getString(network_data, "pd-allocator");
+ if ((allocator_type != "iterative") && (allocator_type != "random") &&
+ (allocator_type != "flq")) {
+ // Unsupported allocator type used.
+ isc_throw(DhcpConfigError, "supported allocators are: iterative, random and flq");
+ }
+ network->setPdAllocatorType(allocator_type);
+ }
+}
+
+void
+BaseNetworkParser::parseOfferLft(const data::ConstElementPtr& network_data,
+ Network4Ptr& network) {
+ if (network_data->contains("offer-lifetime")) {
+ auto value = getInteger(network_data, "offer-lifetime");
+ if (value < 0) {
+ isc_throw(DhcpConfigError, "the value of offer-lifetime '"
+ << value << "' must be a positive number ("
+ << getPosition("offer-lifetime", network_data) << ")");
+ }
+
+ network->setOfferLft(value);
+ }
+}
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.h b/src/lib/dhcpsrv/parsers/base_network_parser.h
new file mode 100644
index 0000000..19a1e1b
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/base_network_parser.h
@@ -0,0 +1,151 @@
+// Copyright (C) 2019-2023 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 BASE_NETWORK_PARSER_H
+#define BASE_NETWORK_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/cfg_globals.h>
+#include <dhcpsrv/network.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Common configuration parser for shared networks
+/// and subnets.
+class BaseNetworkParser : public data::SimpleParser {
+public:
+
+ /// @brief Moves deprecated reservation-mode parameter to
+ /// new reservations flags.
+ ///
+ /// @param config [in/out] configuration to alter.
+ /// @throw DhcpConfigError on error e.g. when both reservation-mode
+ /// and a flag are specified.
+ static void moveReservationMode(isc::data::ElementPtr config);
+
+ /// @brief Moves deprecated reservation-mode parameter to
+ /// new reservations flags.
+ ///
+ /// @param config [in/out] global parameters to alter.
+ /// @throw DhcpConfigError on error e.g. when both reservation-mode
+ /// and a flag are specified.
+ static void moveReservationMode(CfgGlobalsPtr config);
+
+protected:
+
+ /// @brief Parses common parameters
+ ///
+ /// The parsed parameters are:
+ /// - renew-timer,
+ /// - rebind-timer,
+ /// - valid-lifetime,
+ /// - store-extended-info
+ /// - reservations-global
+ /// - reservations-in-subnet
+ /// - reservations-out-of-pool
+ ///
+ /// @param network_data Data element holding shared network
+ /// configuration to be parsed.
+ /// @param [out] network Pointer to a network in which parsed data is
+ /// to be stored.
+ void parseCommon(const data::ConstElementPtr& network_data,
+ NetworkPtr& network);
+
+ /// @brief Parses parameters related to "percent" timers settings.
+ ///
+ /// The parsed parameters are:
+ /// - calculate-tee-times,
+ /// - t1-percent,
+ /// - t2-percent.
+ ///
+ /// @param network_data Data element holding network configuration
+ /// to be parsed.
+ /// @param [out] network Pointer to a network in which parsed data is
+ /// to be stored.
+ ///
+ /// @throw DhcpConfigError if configuration of these parameters is
+ /// invalid.
+ void parseTeePercents(const data::ConstElementPtr& network_data,
+ NetworkPtr& network);
+
+ /// @brief Parses parameters related to lease cache settings.
+ ///
+ /// The parsed parameters are:
+ /// - cache-threshold,
+ /// - cache-max-age.
+ ///
+ /// @param network_data Data element holding network configuration
+ /// to be parsed.
+ /// @param [out] network Pointer to a network in which parsed data is
+ /// to be stored.
+ ///
+ /// @throw DhcpConfigError if configuration of these parameters is
+ /// invalid.
+ void parseCacheParams(const data::ConstElementPtr& network_data,
+ NetworkPtr& network);
+
+ /// @brief Parses parameters pertaining to DDNS behavior.
+ ///
+ /// The parsed parameters are:
+ /// - ddns-send-updates
+ /// - ddns-override-no-update
+ /// - ddns-override-client-update
+ /// - ddns-replace-client-name
+ /// - ddns-generated-prefix
+ /// - ddns-qualifying-suffix
+ /// - ddns-use-conflict-resolution
+ /// - ddns-update-on-renew
+ /// - ddns-ttl-percent
+ ///
+ /// @param network_data Data element holding shared network
+ /// configuration to be parsed.
+ /// @param [out] network Pointer to a network in which parsed data is
+ /// to be stored.
+ void parseDdnsParams(const data::ConstElementPtr& network_data,
+ NetworkPtr& network);
+
+ /// @brief Parses parameters pertaining to allocator selection.
+ ///
+ /// The parsed parameters are:
+ /// - allocator
+ ///
+ /// @param network_data Data element holding shared network
+ /// configuration to be parsed.
+ /// @param [out] network Pointer to a network in which parsed data is
+ /// to be stored.
+ void parseAllocatorParams(const data::ConstElementPtr& network_data,
+ NetworkPtr& network);
+
+ /// @brief Parses parameters pertaining to prefix delegation allocator
+ /// selection.
+ ///
+ /// The parsed parameters are:
+ /// - pd-allocator
+ ///
+ /// @param network_data Data element holding shared network
+ /// configuration to be parsed.
+ /// @param [out] network Pointer to a network in which parsed data is
+ /// to be stored.
+ void parsePdAllocatorParams(const data::ConstElementPtr& network_data,
+ Network6Ptr& network);
+
+ /// @brief Parses offer-lifetime parameter (v4 only)
+ ///
+ /// @param network_data Data element holding shared network
+ /// configuration to be parsed.
+ /// @param [out] network Pointer to the v4 network in which parsed data is
+ /// to be stored.
+ /// @throw DhcpConfigError if the value is less than 0.
+ void parseOfferLft(const data::ConstElementPtr& network_data,
+ Network4Ptr& network);
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
new file mode 100644
index 0000000..291632b
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
@@ -0,0 +1,358 @@
+// Copyright (C) 2015-2023 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 <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <eval/eval_context.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <algorithm>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace std;
+
+/// @file client_class_def_parser.cc
+///
+/// @brief Method implementations for client class definition parsing
+
+namespace isc {
+namespace dhcp {
+
+// ********************** ExpressionParser ****************************
+
+void
+ExpressionParser::parse(ExpressionPtr& expression,
+ ConstElementPtr expression_cfg,
+ uint16_t family,
+ EvalContext::CheckDefined check_defined,
+ EvalContext::ParserType parser_type) {
+ if (expression_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "expression ["
+ << expression_cfg->str() << "] must be a string, at ("
+ << expression_cfg->getPosition() << ")");
+ }
+
+ // Get the expression's text via getValue() as the text returned
+ // by str() enclosed in quotes.
+ std::string value;
+ expression_cfg->getValue(value);
+
+ if (parser_type == EvalContext::PARSER_STRING && value.empty()) {
+ isc_throw(DhcpConfigError, "expression can not be empty at ("
+ << expression_cfg->getPosition() << ")");
+ }
+
+ try {
+ EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6,
+ check_defined);
+ eval_ctx.parseString(value, parser_type);
+ expression.reset(new Expression());
+ *expression = eval_ctx.expression;
+ } catch (const std::exception& ex) {
+ // Append position if there is a failure.
+ isc_throw(DhcpConfigError,
+ "expression: [" << value
+ << "] error: " << ex.what() << " at ("
+ << expression_cfg->getPosition() << ")");
+ }
+}
+
+// ********************** ClientClassDefParser ****************************
+
+void
+ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
+ ConstElementPtr class_def_cfg,
+ uint16_t family,
+ bool append_error_position,
+ bool check_dependencies) {
+ // name is now mandatory, so let's deal with it first.
+ std::string name = getString(class_def_cfg, "name");
+ if (name.empty()) {
+ isc_throw(DhcpConfigError,
+ "not empty parameter 'name' is required "
+ << getPosition("name", class_def_cfg) << ")");
+ }
+
+ EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL;
+
+ // Let's try to parse the template-test expression
+ bool is_template = false;
+
+ // Parse matching expression
+ ExpressionPtr match_expr;
+ ConstElementPtr test_cfg = class_def_cfg->get("test");
+ ConstElementPtr template_test_cfg = class_def_cfg->get("template-test");
+ if (test_cfg && template_test_cfg) {
+ isc_throw(DhcpConfigError, "can not use both 'test' and 'template-test' ("
+ << test_cfg->getPosition() << ") and ("
+ << template_test_cfg->getPosition() << ")");
+ }
+ std::string test;
+ bool depend_on_known = false;
+ EvalContext::CheckDefined check_defined = EvalContext::acceptAll;
+ if (template_test_cfg) {
+ test_cfg = template_test_cfg;
+ parser_type = EvalContext::PARSER_STRING;
+ is_template = true;
+ } else {
+ check_defined = [&class_dictionary, &depend_on_known, check_dependencies](const ClientClass& cclass) {
+ return (!check_dependencies || isClientClassDefined(class_dictionary, depend_on_known, cclass));
+ };
+ }
+
+ if (test_cfg) {
+ ExpressionParser parser;
+ parser.parse(match_expr, test_cfg, family, check_defined, parser_type);
+ test = test_cfg->stringValue();
+ }
+
+ // Parse option def
+ CfgOptionDefPtr defs(new CfgOptionDef());
+ ConstElementPtr option_defs = class_def_cfg->get("option-def");
+ if (option_defs) {
+ // Apply defaults
+ SimpleParser::setListDefaults(option_defs,
+ family == AF_INET ?
+ SimpleParser4::OPTION4_DEF_DEFAULTS :
+ SimpleParser6::OPTION6_DEF_DEFAULTS);
+
+ OptionDefParser parser(family);
+ BOOST_FOREACH(ConstElementPtr option_def, option_defs->listValue()) {
+ OptionDefinitionPtr def = parser.parse(option_def);
+
+ // Verify if the definition is for an option which is in a deferred
+ // processing list.
+ if (!LibDHCP::shouldDeferOptionUnpack(def->getOptionSpaceName(),
+ def->getCode())) {
+ isc_throw(DhcpConfigError,
+ "Not allowed option definition for code '"
+ << def->getCode() << "' in space '"
+ << def->getOptionSpaceName() << "' at ("
+ << option_def->getPosition() << ")");
+ }
+ try {
+ defs->add(def);
+ } catch (const std::exception& ex) {
+ // Sanity check: it should never happen
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << option_def->getPosition() << ")");
+ }
+ }
+ }
+
+ // Parse option data
+ CfgOptionPtr options(new CfgOption());
+ ConstElementPtr option_data = class_def_cfg->get("option-data");
+ if (option_data) {
+ auto opts_parser = createOptionDataListParser(family, defs);
+ opts_parser->parse(options, option_data);
+ }
+
+ // Parse user context
+ ConstElementPtr user_context = class_def_cfg->get("user-context");
+ if (user_context) {
+ if (user_context->getType() != Element::map) {
+ isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+ << user_context->getPosition() << ")");
+ }
+ }
+
+ // Let's try to parse the only-if-required flag
+ bool required = false;
+ if (class_def_cfg->contains("only-if-required")) {
+ required = getBoolean(class_def_cfg, "only-if-required");
+ }
+
+ // Let's try to parse the next-server field
+ IOAddress next_server("0.0.0.0");
+ if (class_def_cfg->contains("next-server")) {
+ std::string next_server_txt = getString(class_def_cfg, "next-server");
+ try {
+ next_server = IOAddress(next_server_txt);
+ } catch (const IOError& ex) {
+ isc_throw(DhcpConfigError,
+ "Invalid next-server value specified: '"
+ << next_server_txt << "' ("
+ << getPosition("next-server", class_def_cfg) << ")");
+ }
+
+ if (next_server.getFamily() != AF_INET) {
+ isc_throw(DhcpConfigError, "Invalid next-server value: '"
+ << next_server_txt << "', must be IPv4 address ("
+ << getPosition("next-server", class_def_cfg) << ")");
+ }
+
+ if (next_server.isV4Bcast()) {
+ isc_throw(DhcpConfigError, "Invalid next-server value: '"
+ << next_server_txt << "', must not be a broadcast ("
+ << getPosition("next-server", class_def_cfg) << ")");
+ }
+ }
+
+ // Let's try to parse server-hostname
+ std::string sname;
+ if (class_def_cfg->contains("server-hostname")) {
+ sname = getString(class_def_cfg, "server-hostname");
+
+ if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
+ isc_throw(DhcpConfigError, "server-hostname must be at most "
+ << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
+ << sname.length() << " ("
+ << getPosition("server-hostname", class_def_cfg) << ")");
+ }
+ }
+
+ // Let's try to parse boot-file-name
+ std::string filename;
+ if (class_def_cfg->contains("boot-file-name")) {
+ filename = getString(class_def_cfg, "boot-file-name");
+
+ if (filename.length() > Pkt4::MAX_FILE_LEN) {
+ isc_throw(DhcpConfigError, "boot-file-name must be at most "
+ << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
+ << filename.length() << " ("
+ << getPosition("boot-file-name", class_def_cfg) << ")");
+ }
+ }
+
+ Optional<uint32_t> offer_lft;
+ if (class_def_cfg->contains("offer-lifetime")) {
+ auto value = getInteger(class_def_cfg, "offer-lifetime");
+ if (value < 0) {
+ isc_throw(DhcpConfigError, "the value of offer-lifetime '"
+ << value << "' must be a positive number ("
+ << getPosition("offer-lifetime", class_def_cfg) << ")");
+ }
+
+ offer_lft = value;
+ }
+
+ // Parse valid lifetime triplet.
+ Triplet<uint32_t> valid_lft = parseIntTriplet(class_def_cfg, "valid-lifetime");
+
+ Triplet<uint32_t> preferred_lft;
+ if (family != AF_INET) {
+ // Parse preferred lifetime triplet.
+ preferred_lft = parseIntTriplet(class_def_cfg, "preferred-lifetime");
+ }
+
+ // Sanity checks on built-in classes
+ for (auto bn : builtinNames) {
+ if (name == bn) {
+ if (required) {
+ isc_throw(DhcpConfigError, "built-in class '" << name
+ << "' only-if-required flag must be false");
+ }
+ if (!test.empty()) {
+ isc_throw(DhcpConfigError, "built-in class '" << name
+ << "' test expression must be empty");
+ }
+ }
+ }
+
+ // Sanity checks on DROP
+ if (name == "DROP") {
+ if (required) {
+ isc_throw(DhcpConfigError, "special class '" << name
+ << "' only-if-required flag must be false");
+ }
+ // depend_on_known is now allowed
+ }
+
+ // Add the client class definition
+ try {
+ class_dictionary->addClass(name, match_expr, test, required,
+ depend_on_known, options, defs,
+ user_context, next_server, sname, filename,
+ valid_lft, preferred_lft, is_template, offer_lft);
+ } catch (const std::exception& ex) {
+ std::ostringstream s;
+ s << "Can't add class: " << ex.what();
+ // Append position of the error in JSON string if required.
+ if (append_error_position) {
+ s << " (" << class_def_cfg->getPosition() << ")";
+ }
+ isc_throw(DhcpConfigError, s.str());
+ }
+}
+
+void
+ClientClassDefParser::checkParametersSupported(const ConstElementPtr& class_def_cfg,
+ const uint16_t family) {
+ // Make sure that the client class definition is stored in a map.
+ if (!class_def_cfg || (class_def_cfg->getType() != Element::map)) {
+ isc_throw(DhcpConfigError, "client class definition is not a map");
+ }
+
+ // Common v4 and v6 parameters supported for the client class.
+ static std::set<std::string> supported_params = { "name",
+ "test",
+ "option-data",
+ "user-context",
+ "only-if-required",
+ "valid-lifetime",
+ "min-valid-lifetime",
+ "max-valid-lifetime",
+ "template-test"};
+
+ // The v4 client class supports additional parameters.
+ static std::set<std::string> supported_params_v4 = { "option-def",
+ "next-server",
+ "server-hostname",
+ "boot-file-name" };
+
+ // The v6 client class supports additional parameters.
+ static std::set<std::string> supported_params_v6 = { "preferred-lifetime",
+ "min-preferred-lifetime",
+ "max-preferred-lifetime" };
+
+ // Iterate over the specified parameters and check if they are all supported.
+ for (auto name_value_pair : class_def_cfg->mapValue()) {
+ if ((supported_params.count(name_value_pair.first) > 0) ||
+ ((family == AF_INET) && (supported_params_v4.count(name_value_pair.first) > 0)) ||
+ ((family != AF_INET) && (supported_params_v6.count(name_value_pair.first) > 0))) {
+ continue;
+ } else {
+ isc_throw(DhcpConfigError, "unsupported client class parameter '"
+ << name_value_pair.first << "'");
+ }
+ }
+}
+
+boost::shared_ptr<OptionDataListParser>
+ClientClassDefParser::createOptionDataListParser(const uint16_t address_family,
+ CfgOptionDefPtr cfg_option_def) const {
+ auto parser = boost::make_shared<OptionDataListParser>(address_family, cfg_option_def);
+ return (parser);
+}
+
+// ****************** ClientClassDefListParser ************************
+
+ClientClassDictionaryPtr
+ClientClassDefListParser::parse(ConstElementPtr client_class_def_list,
+ uint16_t family, bool check_dependencies) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ BOOST_FOREACH(ConstElementPtr client_class_def,
+ client_class_def_list->listValue()) {
+ ClientClassDefParser parser;
+ parser.parse(dictionary, client_class_def, family, true, check_dependencies);
+ }
+ return (dictionary);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.h b/src/lib/dhcpsrv/parsers/client_class_def_parser.h
new file mode 100644
index 0000000..96e73e7
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.h
@@ -0,0 +1,175 @@
+// Copyright (C) 2015-2022 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 CLIENT_CLASS_DEF_PARSER_H
+#define CLIENT_CLASS_DEF_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <eval/eval_context.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <functional>
+#include <list>
+
+/// @file client_class_def_parser.h
+///
+/// @brief Parsers for client class definitions
+///
+/// These parsers are used to parse lists of client class definitions
+/// into a ClientClassDictionary of ClientClassDef instances. Each
+/// ClientClassDef consists of (at least) a name, an expression, option-def
+/// and option-data. Currently only a not empty name is required.
+///
+/// There parsers defined are:
+///
+/// ClientClassDefListParser - creates a ClientClassDictionary from a list
+/// of element maps, where each map contains the entries that specify a
+/// single class. The names of the classes in the are expected to be
+/// unique. Attempting to define a duplicate class will result in an
+/// DhcpConfigError throw. At the end the dictionary is stored by the CfgMgr.
+///
+/// ClientClassDefParser - creates a ClientClassDefinition from an element
+/// map. The elements are as follows:
+///
+/// -# "name" - a string containing the name of the class
+///
+/// -# "test" - a string containing the logical expression used to determine
+/// membership in the class. This is passed into the eval parser.
+///
+/// -# "option-def" - a list which defines the options which processing
+/// is deferred. This element is optional and parsed using the @ref
+/// isc::dhcp::OptionDefParser. A check is done to verify definitions
+/// are only for deferred processing option (DHCPv4 43 and 224-254).
+///
+/// -# "option-data" - a list which defines the options that should be
+/// assigned to remembers of the class. This element is optional and parsed
+/// using the @ref isc::dhcp::OptionDataListParser.
+///
+/// ExpressionParser - creates an eval::Expression from a string element,
+/// using the Eval Parser.
+///
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a logical expression
+///
+/// This parser creates an instance of an Expression from a string. The
+/// string is passed to the Eval Parser and the resultant Expression is
+/// stored into the ExpressionPtr reference passed into the constructor.
+class ExpressionParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Parses an expression configuration element into an Expression
+ ///
+ /// @param expression variable in which to store the new expression
+ /// @param expression_cfg the configuration entry to be parsed.
+ /// @param family the address family of the expression.
+ /// @param check_defined a closure to check if a client class is defined.
+ /// @param parser_type the expected type of the evaluated expression.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void parse(ExpressionPtr& expression,
+ isc::data::ConstElementPtr expression_cfg,
+ uint16_t family,
+ isc::eval::EvalContext::CheckDefined check_defined = isc::eval::EvalContext::acceptAll,
+ isc::eval::EvalContext::ParserType parser_type = isc::eval::EvalContext::PARSER_BOOL);
+};
+
+/// @brief Parser for a single client class definition.
+///
+/// This parser creates an instance of a client class definition.
+class ClientClassDefParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~ClientClassDefParser() {
+ }
+
+ /// @brief Parses an entry that describes single client class definition.
+ ///
+ /// Attempts to add the new class directly into the given dictionary.
+ /// This done here to detect duplicate classes prior to commit().
+ /// @param class_dictionary dictionary into which the class should be added
+ /// @param client_class_def a configuration entry to be parsed.
+ /// @param family the address family of the client class.
+ /// @param append_error_position Boolean flag indicating if position
+ /// of the parsed string within parsed JSON should be appended. The
+ /// default setting is to append it, but it is typically set to false
+ /// when this parser is used by hooks libraries.
+ /// @param check_dependencies indicates if the parser should evaluate an
+ /// expression to see if the referenced client classes exist.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void parse(ClientClassDictionaryPtr& class_dictionary,
+ isc::data::ConstElementPtr client_class_def,
+ uint16_t family,
+ bool append_error_position = true,
+ bool check_dependencies = true);
+
+ /// @brief Iterates over class parameters and checks if they are supported.
+ ///
+ /// This method should be called by hooks libraries which do not use Bison
+ /// to validate class syntax prior to parsing the client class information.
+ ///
+ /// @param class_def_cfg class configuration entry.
+ /// @param family the address family of the client class.
+ ///
+ /// @throw DhcpConfigError if any of the parameters is not supported.
+ void checkParametersSupported(const isc::data::ConstElementPtr& class_def_cfg,
+ const uint16_t family);
+
+protected:
+
+ /// @brief Returns an instance of the @c OptionDataListParser to
+ /// be used in parsing the option-data structure.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for option data.
+ ///
+ /// @param address_family @c AF_INET (for DHCPv4) or @c AF_INET6 (for DHCPv6).
+ /// @param cfg_option_def structure holding option definitions.
+ ///
+ /// @return an instance of the @c OptionDataListParser.
+ virtual boost::shared_ptr<OptionDataListParser>
+ createOptionDataListParser(const uint16_t address_family,
+ CfgOptionDefPtr cfg_option_def) const;
+};
+
+/// @brief Defines a pointer to a ClientClassDefParser
+typedef boost::shared_ptr<ClientClassDefParser> ClientClassDefParserPtr;
+
+/// @brief Parser for a list of client class definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// client classes and creates ClientClassDef instances for each.
+/// When the parsing successfully completes, the collection of
+/// created definitions is given to the CfgMgr.
+class ClientClassDefListParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries, creates instances
+ /// of client class definitions and tries to adds them to the
+ /// local dictionary. At the end the dictionary is returned.
+ ///
+ /// @param class_def_list pointer to an element that holds entries
+ /// for client class definitions.
+ /// @param family the address family of the client class definitions.
+ /// @param check_dependencies indicates if the parser should evaluate an
+ /// expression to see if the referenced client classes exist.
+ /// @return a pointer to the filled dictionary
+ /// @throw DhcpConfigError if configuration parsing fails.
+ ClientClassDictionaryPtr
+ parse(isc::data::ConstElementPtr class_def_list, uint16_t family,
+ bool check_dependencies = true);
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // CLIENT_CLASS_DEF_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
new file mode 100644
index 0000000..384369c
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
@@ -0,0 +1,1676 @@
+// Copyright (C) 2013-2023 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 <dhcp/iface_mgr.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcpsrv/cfg_mac_source.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+#include <iomanip>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+// ******************** MACSourcesListConfigParser *************************
+
+void
+MACSourcesListConfigParser::parse(CfgMACSource& mac_sources, ConstElementPtr value) {
+ uint32_t source = 0;
+ size_t cnt = 0;
+
+ // By default, there's only one source defined: ANY.
+ // If user specified anything, we need to get rid of that default.
+ mac_sources.clear();
+
+ BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) {
+ std::string source_str = source_elem->stringValue();
+ try {
+ source = CfgMACSource::MACSourceFromText(source_str);
+ mac_sources.add(source);
+ ++cnt;
+ } catch (const InvalidParameter& ex) {
+ isc_throw(DhcpConfigError, "The mac-sources value '" << source_str
+ << "' was specified twice (" << value->getPosition() << ")");
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to convert '"
+ << source_str << "' to any recognized MAC source:"
+ << ex.what() << " (" << value->getPosition() << ")");
+ }
+ }
+
+ if (!cnt) {
+ isc_throw(DhcpConfigError, "If specified, MAC Sources cannot be empty");
+ }
+}
+
+// ******************** ControlSocketParser *************************
+void ControlSocketParser::parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value) {
+ if (!value) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError, "Logic error: specified control-socket is null");
+ }
+
+ if (value->getType() != Element::map) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError, "Specified control-socket is expected to be a map"
+ ", i.e. a structure defined within { }");
+ }
+ srv_cfg.setControlSocketInfo(value);
+}
+
+// ******************************** OptionDefParser ****************************
+
+OptionDefParser::OptionDefParser(const uint16_t address_family)
+ : address_family_(address_family) {
+}
+
+OptionDefinitionPtr
+OptionDefParser::parse(ConstElementPtr option_def) {
+
+ // Check parameters.
+ if (address_family_ == AF_INET) {
+ checkKeywords(SimpleParser4::OPTION4_DEF_PARAMETERS, option_def);
+ } else {
+ checkKeywords(SimpleParser6::OPTION6_DEF_PARAMETERS, option_def);
+ }
+
+ // Get mandatory parameters.
+ std::string name = getString(option_def, "name");
+ int64_t code64 = getInteger(option_def, "code");
+ std::string type = getString(option_def, "type");
+
+ // Get optional parameters. Whoever called this parser, should have
+ // called SimpleParser::setDefaults first.
+ bool array_type = getBoolean(option_def, "array");
+ std::string record_types = getString(option_def, "record-types");
+ std::string space = getString(option_def, "space");
+ std::string encapsulates = getString(option_def, "encapsulate");
+ ConstElementPtr user_context = option_def->get("user-context");
+
+ // Check code value.
+ if (code64 < 0) {
+ isc_throw(DhcpConfigError, "option code must not be negative "
+ "(" << getPosition("code", option_def) << ")");
+ } else if (address_family_ == AF_INET &&
+ code64 > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code64
+ << "', it must not be greater than '"
+ << static_cast<int>(std::numeric_limits<uint8_t>::max())
+ << "' (" << getPosition("code", option_def) << ")");
+ } else if (address_family_ == AF_INET6 &&
+ code64 > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code64
+ << "', it must not be greater than '"
+ << std::numeric_limits<uint16_t>::max()
+ << "' (" << getPosition("code", option_def) << ")");
+ }
+ uint32_t code = static_cast<uint32_t>(code64);
+
+ // Validate space name.
+ if (!OptionSpace::validateName(space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "' ("
+ << getPosition("space", option_def) << ")");
+ }
+
+ // Protect against definition of options 0 (PAD) or 255 (END)
+ // in (and only in) the dhcp4 space.
+ if (space == DHCP4_OPTION_SPACE) {
+ if (code == DHO_PAD) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved for PAD ("
+ << getPosition("code", option_def) << ")");
+ } else if (code == DHO_END) {
+ isc_throw(DhcpConfigError, "invalid option code '255': "
+ << "reserved for END ("
+ << getPosition("code", option_def) << ")");
+ }
+ }
+
+ // For dhcp6 space the value 0 is reserved.
+ if (space == DHCP6_OPTION_SPACE) {
+ if (code == 0) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved value ("
+ << getPosition("code", option_def) << ")");
+ }
+ }
+
+ // Create option definition.
+ OptionDefinitionPtr def;
+ // We need to check if user has set encapsulated option space
+ // name. If so, different constructor will be used.
+ if (!encapsulates.empty()) {
+ // Arrays can't be used together with sub-options.
+ if (array_type) {
+ isc_throw(DhcpConfigError, "option '" << space << "."
+ << name << "', comprising an array of data"
+ << " fields may not encapsulate any option space ("
+ << option_def->getPosition() << ")");
+
+ } else if (encapsulates == space) {
+ isc_throw(DhcpConfigError, "option must not encapsulate"
+ << " an option space it belongs to: '"
+ << space << "." << name << "' is set to"
+ << " encapsulate '" << space << "' ("
+ << option_def->getPosition() << ")");
+
+ } else {
+ def.reset(new OptionDefinition(name, code, space, type,
+ encapsulates.c_str()));
+ }
+
+ } else {
+ def.reset(new OptionDefinition(name, code, space, type, array_type));
+
+ }
+
+ if (user_context) {
+ def->setContext(user_context);
+ }
+
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what() << " ("
+ << getPosition("record-types", option_def) << ")");
+ }
+ }
+
+ // Validate the definition.
+ try {
+ def->validate();
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what()
+ << " (" << option_def->getPosition() << ")");
+ }
+
+ // Option definition has been created successfully.
+ return (def);
+}
+
+// ******************************** OptionDefListParser ************************
+
+OptionDefListParser::OptionDefListParser(const uint16_t address_family)
+ : address_family_(address_family) {
+}
+
+void
+OptionDefListParser::parse(CfgOptionDefPtr storage, ConstElementPtr option_def_list) {
+ if (!option_def_list) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL ("
+ << option_def_list->getPosition() << ")");
+ }
+
+ OptionDefParser parser(address_family_);
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ OptionDefinitionPtr def = parser.parse(option_def);
+ try {
+ storage->add(def);
+ } catch (const std::exception& ex) {
+ // Append position if there is a failure.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << option_def->getPosition() << ")");
+ }
+ }
+
+ // All definitions have been prepared. Put them as runtime options into
+ // the libdhcp++.
+ LibDHCP::setRuntimeOptionDefs(storage->getContainer());
+}
+
+//****************************** RelayInfoParser ********************************
+RelayInfoParser::RelayInfoParser(const Option::Universe& family)
+ : family_(family) {
+};
+
+void
+RelayInfoParser::parse(const isc::dhcp::Network::RelayInfoPtr& relay_info,
+ ConstElementPtr relay_elem) {
+
+ if (relay_elem->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "relay must be a map");
+ }
+
+ ConstElementPtr address = relay_elem->get("ip-address");
+ ConstElementPtr addresses = relay_elem->get("ip-addresses");
+
+ if (address && addresses) {
+ isc_throw(DhcpConfigError,
+ "specify either ip-address or ip-addresses, not both");
+ }
+
+ if (!address && !addresses) {
+ isc_throw(DhcpConfigError, "ip-addresses is required");
+ }
+
+ // Create our resultant RelayInfo structure
+ *relay_info = isc::dhcp::Network::RelayInfo();
+
+ if (address) {
+ addAddress("ip-address", getString(relay_elem, "ip-address"),
+ relay_elem, relay_info);
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_CFGMGR_RELAY_IP_ADDRESS_DEPRECATED)
+ .arg(getPosition("ip-address", relay_elem));
+ return;
+ }
+
+ if (addresses->getType() != Element::list) {
+ isc_throw(DhcpConfigError, "ip-addresses must be a list "
+ "(" << getPosition("ip-addresses", relay_elem) << ")");
+ }
+
+ BOOST_FOREACH(ConstElementPtr address_element, addresses->listValue()) {
+ addAddress("ip-addresses", address_element->stringValue(),
+ relay_elem, relay_info);
+ }
+}
+
+void
+RelayInfoParser::addAddress(const std::string& name,
+ const std::string& address_str,
+ ConstElementPtr relay_elem,
+ const isc::dhcp::Network::RelayInfoPtr& relay_info) {
+ boost::scoped_ptr<isc::asiolink::IOAddress> ip;
+ try {
+ ip.reset(new isc::asiolink::IOAddress(address_str));
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "address " << address_str
+ << " is not a valid: "
+ << (family_ == Option::V4 ? "IPv4" : "IPv6")
+ << "address"
+ << " (" << getPosition(name, relay_elem) << ")");
+ }
+
+ // Check if the address family matches.
+ if ((ip->isV4() && family_ != Option::V4) ||
+ (ip->isV6() && family_ != Option::V6) ) {
+ isc_throw(DhcpConfigError, "address " << address_str
+ << " is not a: "
+ << (family_ == Option::V4 ? "IPv4" : "IPv6")
+ << "address"
+ << " (" << getPosition(name, relay_elem) << ")");
+ }
+
+ try {
+ relay_info->addAddress(*ip);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "cannot add address: " << address_str
+ << " to relay info: " << ex.what()
+ << " (" << getPosition(name, relay_elem) << ")");
+ }
+}
+
+//****************************** PoolParser ********************************
+
+void
+PoolParser::parse(PoolStoragePtr pools,
+ ConstElementPtr pool_structure,
+ const uint16_t address_family,
+ bool encapsulate_options) {
+
+ if (address_family == AF_INET) {
+ checkKeywords(SimpleParser4::POOL4_PARAMETERS, pool_structure);
+ } else {
+ checkKeywords(SimpleParser6::POOL6_PARAMETERS, pool_structure);
+ }
+
+ ConstElementPtr text_pool = pool_structure->get("pool");
+
+ if (!text_pool) {
+ isc_throw(DhcpConfigError, "Mandatory 'pool' entry missing in "
+ "definition: (" << pool_structure->getPosition() << ")");
+ }
+
+ // That should be a single pool representation. It should contain
+ // text is form prefix/len or first - last. Note that spaces
+ // are allowed
+ string txt = text_pool->stringValue();
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ PoolPtr pool;
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+ if (pos != string::npos) {
+ isc::asiolink::IOAddress addr("::");
+ uint8_t len = 0;
+ try {
+ addr = isc::asiolink::IOAddress(txt.substr(0, pos));
+
+ // start with the first character after /
+ string prefix_len = txt.substr(pos + 1);
+
+ // It is lexical cast to int and then downcast to uint8_t.
+ // Direct cast to uint8_t (which is really an unsigned char)
+ // will result in interpreting the first digit as output
+ // value and throwing exception if length is written on two
+ // digits (because there are extra characters left over).
+
+ // No checks for values over 128. Range correctness will
+ // be checked in Pool4 constructor, here we only check
+ // the representation fits in an uint8_t as this can't
+ // be done by a direct lexical cast as explained...
+ int val_len = boost::lexical_cast<int>(prefix_len);
+ if ((val_len < std::numeric_limits<uint8_t>::min()) ||
+ (val_len > std::numeric_limits<uint8_t>::max())) {
+ // This exception will be handled 4 line later!
+ isc_throw(OutOfRange, "");
+ }
+ len = static_cast<uint8_t>(val_len);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << txt << " ("
+ << text_pool->getPosition() << ")");
+ }
+
+ try {
+ pool = poolMaker(addr, len);
+ pools->push_back(pool);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to create pool defined by: "
+ << txt << " (" << text_pool->getPosition() << ")");
+ }
+
+ } else {
+ isc::asiolink::IOAddress min("::");
+ isc::asiolink::IOAddress max("::");
+
+ // Is this min-max notation?
+ pos = txt.find("-");
+ if (pos != string::npos) {
+ // using min-max notation
+ try {
+ min = isc::asiolink::IOAddress(txt.substr(0, pos));
+ max = isc::asiolink::IOAddress(txt.substr(pos + 1));
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << txt << " ("
+ << text_pool->getPosition() << ")");
+ }
+
+ try {
+ pool = poolMaker(min, max);
+ pools->push_back(pool);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to create pool defined by: "
+ << txt << " (" << text_pool->getPosition() << ")");
+ }
+ }
+ }
+
+ if (!pool) {
+ isc_throw(DhcpConfigError, "invalid pool definition: "
+ << text_pool->stringValue() <<
+ ". There are two acceptable formats <min address-max address>"
+ " or <prefix/len> ("
+ << text_pool->getPosition() << ")");
+ }
+
+ // If there is a pool-id, store it.
+ ConstElementPtr pool_id = pool_structure->get("pool-id");
+ if (pool_id) {
+ if (pool_id->intValue() <= 0) {
+ isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not"
+ << " a positive integer greater than 0");
+ } else if (pool_id->intValue() > numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "pool-id " << pool_id->intValue() << " is not"
+ << " a 32 bit unsigned integer");
+ }
+
+ pool->setID(pool_id->intValue());
+ }
+
+ // If there's user-context specified, store it.
+ ConstElementPtr user_context = pool_structure->get("user-context");
+ if (user_context) {
+ // The grammar accepts only maps but still check it.
+ if (user_context->getType() != Element::map) {
+ isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+ << user_context->getPosition() << ")");
+ }
+ pool->setContext(user_context);
+ }
+
+ // Parser pool specific options.
+ ConstElementPtr option_data = pool_structure->get("option-data");
+ if (option_data) {
+ try {
+ CfgOptionPtr cfg = pool->getCfgOption();
+ auto option_parser = createOptionDataListParser(address_family);
+ option_parser->parse(cfg, option_data, encapsulate_options);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::dhcp::DhcpConfigError, ex.what()
+ << " (" << option_data->getPosition() << ")");
+ }
+ }
+
+ // Client-class.
+ ConstElementPtr client_class = pool_structure->get("client-class");
+ if (client_class) {
+ string cclass = client_class->stringValue();
+ if (!cclass.empty()) {
+ pool->allowClientClass(cclass);
+ }
+ }
+
+ // Try setting up required client classes.
+ ConstElementPtr class_list = pool_structure->get("require-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool->requireClientClass((*cclass)->stringValue());
+ }
+ }
+}
+
+boost::shared_ptr<OptionDataListParser>
+PoolParser::createOptionDataListParser(const uint16_t address_family) const {
+ auto parser = boost::make_shared<OptionDataListParser>(address_family);
+ return (parser);
+}
+
+//****************************** Pool4Parser *************************
+
+PoolPtr
+Pool4Parser::poolMaker (IOAddress &addr, uint32_t len, int32_t) {
+ return (PoolPtr(new Pool4(addr, len)));
+}
+
+PoolPtr
+Pool4Parser::poolMaker (IOAddress &min, IOAddress &max, int32_t) {
+ return (PoolPtr(new Pool4(min, max)));
+}
+
+//****************************** Pools4ListParser *************************
+
+void
+Pools4ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list,
+ bool encapsulate_options) {
+ BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) {
+ auto parser = createPoolConfigParser();
+ parser->parse(pools, pool, AF_INET, encapsulate_options);
+ }
+}
+
+boost::shared_ptr<PoolParser>
+Pools4ListParser::createPoolConfigParser() const {
+ auto parser = boost::make_shared<Pool4Parser>();
+ return (parser);
+}
+
+//****************************** SubnetConfigParser *************************
+
+SubnetConfigParser::SubnetConfigParser(uint16_t family, bool check_iface)
+ : pools_(new PoolStorage()),
+ address_family_(family),
+ check_iface_(check_iface) {
+ relay_info_.reset(new isc::dhcp::Network::RelayInfo());
+}
+
+SubnetPtr
+SubnetConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) {
+
+ ConstElementPtr relay_params = subnet->get("relay");
+ if (relay_params) {
+ Option::Universe u = (address_family_ == AF_INET) ? Option::V4 : Option::V6;
+ RelayInfoParser parser(u);
+ parser.parse(relay_info_, relay_params);
+ }
+
+ // Create a subnet.
+ try {
+ createSubnet(subnet);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError,
+ "subnet configuration failed: " << ex.what());
+ }
+
+ // We create subnet first and then parse the options straight into the subnet's
+ // CfgOption structure. Previously, we first parsed the options and then copied
+ // them into the CfgOption after creating the subnet but it had two issues. First,
+ // it cost performance. Second, copying options reset the isEncapsulated() flag.
+ // If the options have been encapsulated we want to preserve the flag to ensure
+ // they are not encapsulated several times.
+ ConstElementPtr options_params = subnet->get("option-data");
+ if (options_params) {
+ auto opt_parser = createOptionDataListParser();
+ opt_parser->parse(subnet_->getCfgOption(), options_params, encapsulate_options);
+ }
+
+ return (subnet_);
+}
+
+void
+SubnetConfigParser::createSubnet(ConstElementPtr params) {
+ std::string subnet_txt;
+ try {
+ subnet_txt = getString(params, "subnet");
+ } catch (const DhcpConfigError &) {
+ // rethrow with precise error
+ isc_throw(DhcpConfigError,
+ "mandatory 'subnet' parameter is missing for a subnet being"
+ " configured (" << params->getPosition() << ")");
+ }
+
+ // Remove any spaces or tabs.
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ ConstElementPtr elem = params->get("subnet");
+ isc_throw(DhcpConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << subnet_txt
+ << " (" << elem->getPosition() << ")");
+ }
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+
+ // Now parse out the prefix length.
+ unsigned int len;
+ try {
+ len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ } catch (const boost::bad_lexical_cast&) {
+ ConstElementPtr elem = params->get("subnet");
+ isc_throw(DhcpConfigError, "prefix length: '" <<
+ subnet_txt.substr(pos+1) << "' is not an integer ("
+ << elem->getPosition() << ")");
+ }
+
+ // Sanity check the prefix length
+ if ((addr.isV6() && len > 128) ||
+ (addr.isV4() && len > 32)) {
+ ConstElementPtr elem = params->get("subnet");
+ isc_throw(BadValue,
+ "Invalid prefix length specified for subnet: " << len
+ << " (" << elem->getPosition() << ")");
+ }
+
+ // Call the subclass's method to instantiate the subnet
+ initSubnet(params, addr, len);
+
+ // Add pools to it.
+ for (const auto& pool : *pools_) {
+ try {
+ subnet_->addPool(pool);
+ } catch (const BadValue& ex) {
+ // addPool() can throw BadValue if the pool is overlapping or
+ // is out of bounds for the subnet.
+ isc_throw(DhcpConfigError,
+ ex.what() << " (" << params->getPosition() << ")");
+ }
+ }
+ // If there's user-context specified, store it.
+ ConstElementPtr user_context = params->get("user-context");
+ if (user_context) {
+ // The grammar accepts only maps but still check it.
+ if (user_context->getType() != Element::map) {
+ isc_throw(isc::dhcp::DhcpConfigError, "User context has to be a map ("
+ << user_context->getPosition() << ")");
+ }
+ subnet_->setContext(user_context);
+ }
+
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a subnet we need to set a callback function for each
+ // subnet to allow for fetching global parameters.
+ subnet_->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
+ });
+}
+
+boost::shared_ptr<OptionDataListParser>
+SubnetConfigParser::createOptionDataListParser() const {
+ auto parser = boost::make_shared<OptionDataListParser>(address_family_);
+ return (parser);
+}
+
+//****************************** Subnet4ConfigParser *************************
+
+Subnet4ConfigParser::Subnet4ConfigParser(bool check_iface)
+ : SubnetConfigParser(AF_INET, check_iface) {
+}
+
+Subnet4Ptr
+Subnet4ConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) {
+ // Check parameters.
+ checkKeywords(SimpleParser4::SUBNET4_PARAMETERS, subnet);
+
+ /// Parse Pools first.
+ ConstElementPtr pools = subnet->get("pools");
+ if (pools) {
+ auto parser = createPoolsListParser();
+ parser->parse(pools_, pools, encapsulate_options);
+ }
+
+ SubnetPtr generic = SubnetConfigParser::parse(subnet, encapsulate_options);
+
+ if (!generic) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError,
+ "Failed to create an IPv4 subnet (" <<
+ subnet->getPosition() << ")");
+ }
+
+ Subnet4Ptr sn4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_);
+ if (!sn4ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid Subnet4 cast in Subnet4ConfigParser::parse");
+ }
+
+ // Set relay information if it was parsed
+ if (relay_info_) {
+ sn4ptr->setRelayInfo(*relay_info_);
+ }
+
+ // Parse Host Reservations for this subnet if any.
+ ConstElementPtr reservations = subnet->get("reservations");
+ if (reservations) {
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ parser.parse(subnet_->getID(), reservations, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ validateResv(sn4ptr, *h);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }
+
+ // Parse allocator specification.
+ auto network4 = boost::dynamic_pointer_cast<Network>(sn4ptr);
+ parseAllocatorParams(subnet, network4);
+
+ // Instantiate the allocator.
+ sn4ptr->createAllocators();
+
+ return (sn4ptr);
+}
+
+void
+Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len) {
+ // Subnet ID is optional. If it is not supplied the value of 0 is used,
+ // which means autogenerate. The value was inserted earlier by calling
+ // SimpleParser4::setAllDefaults.
+ int64_t subnet_id_max = static_cast<int64_t>(SUBNET_ID_MAX);
+ SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id", 0,
+ subnet_id_max));
+
+ auto subnet4 = Subnet4::create(addr, len, Triplet<uint32_t>(),
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ subnet_id);
+ subnet_ = subnet4;
+
+ // Move from reservation mode to new reservations flags.
+ ElementPtr mutable_params;
+ mutable_params = boost::const_pointer_cast<Element>(params);
+ // @todo add warning
+ BaseNetworkParser::moveReservationMode(mutable_params);
+
+ // Parse parameters common to all Network derivations.
+ NetworkPtr network = boost::dynamic_pointer_cast<Network>(subnet4);
+ parseCommon(mutable_params, network);
+
+ std::ostringstream output;
+ output << addr << "/" << static_cast<int>(len) << " with params: ";
+
+ bool has_renew = !subnet4->getT1().unspecified();
+ bool has_rebind = !subnet4->getT2().unspecified();
+ int64_t renew = -1;
+ int64_t rebind = -1;
+
+ // t1 and t2 are optional may be not specified.
+ if (has_renew) {
+ renew = subnet4->getT1().get();
+ output << "t1=" << renew << ", ";
+ }
+ if (has_rebind) {
+ rebind = subnet4->getT2().get();
+ output << "t2=" << rebind << ", ";
+ }
+
+ if (!subnet4->getValid().unspecified()) {
+ output << "valid-lifetime=" << subnet4->getValid().get();
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET4).arg(output.str());
+
+ // Set the match-client-id value for the subnet.
+ if (params->contains("match-client-id")) {
+ bool match_client_id = getBoolean(params, "match-client-id");
+ subnet4->setMatchClientId(match_client_id);
+ }
+
+ // Set the authoritative value for the subnet.
+ if (params->contains("authoritative")) {
+ bool authoritative = getBoolean(params, "authoritative");
+ subnet4->setAuthoritative(authoritative);
+ }
+
+ // Set next-server. The default value is 0.0.0.0. Nevertheless, the
+ // user could have messed that up by specifying incorrect value.
+ // To avoid using 0.0.0.0, user can specify "".
+ if (params->contains("next-server")) {
+ string next_server;
+ try {
+ next_server = getString(params, "next-server");
+ if (!next_server.empty()) {
+ subnet4->setSiaddr(IOAddress(next_server));
+ }
+ } catch (...) {
+ ConstElementPtr next = params->get("next-server");
+ string pos;
+ if (next) {
+ pos = next->getPosition().str();
+ } else {
+ pos = params->getPosition().str();
+ }
+ isc_throw(DhcpConfigError, "invalid parameter next-server : "
+ << next_server << "(" << pos << ")");
+ }
+ }
+
+ // Set server-hostname.
+ if (params->contains("server-hostname")) {
+ std::string sname = getString(params, "server-hostname");
+ if (!sname.empty()) {
+ if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
+ ConstElementPtr error = params->get("server-hostname");
+ isc_throw(DhcpConfigError, "server-hostname must be at most "
+ << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
+ << sname.length() << " ("
+ << error->getPosition() << ")");
+ }
+ subnet4->setSname(sname);
+ }
+ }
+
+ // Set boot-file-name.
+ if (params->contains("boot-file-name")) {
+ std::string filename =getString(params, "boot-file-name");
+ if (!filename.empty()) {
+ if (filename.length() > Pkt4::MAX_FILE_LEN) {
+ ConstElementPtr error = params->get("boot-file-name");
+ isc_throw(DhcpConfigError, "boot-file-name must be at most "
+ << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
+ << filename.length() << " ("
+ << error->getPosition() << ")");
+ }
+ subnet4->setFilename(filename);
+ }
+ }
+
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+ if (params->contains("interface")) {
+ std::string iface = getString(params, "interface");
+ if (!iface.empty()) {
+ if (check_iface_ && !IfaceMgr::instance().getIface(iface)) {
+ ConstElementPtr error = params->get("interface");
+ isc_throw(DhcpConfigError, "Specified network interface name " << iface
+ << " for subnet " << subnet4->toText()
+ << " is not present in the system ("
+ << error->getPosition() << ")");
+ }
+
+ subnet4->setIface(iface);
+ }
+ }
+
+ // Try setting up client class.
+ if (params->contains("client-class")) {
+ string client_class = getString(params, "client-class");
+ if (!client_class.empty()) {
+ subnet4->allowClientClass(client_class);
+ }
+ }
+
+ // Try setting up required client classes.
+ ConstElementPtr class_list = params->get("require-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet4->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
+ // 4o6 specific parameter: 4o6-interface.
+ if (params->contains("4o6-interface")) {
+ string iface4o6 = getString(params, "4o6-interface");
+ if (!iface4o6.empty()) {
+ subnet4->get4o6().setIface4o6(iface4o6);
+ subnet4->get4o6().enabled(true);
+ }
+ }
+
+ // 4o6 specific parameter: 4o6-subnet.
+ if (params->contains("4o6-subnet")) {
+ string subnet4o6 = getString(params, "4o6-subnet");
+ if (!subnet4o6.empty()) {
+ size_t slash = subnet4o6.find("/");
+ if (slash == std::string::npos) {
+ isc_throw(DhcpConfigError, "Missing / in the 4o6-subnet parameter:"
+ << subnet4o6 << ", expected format: prefix6/length");
+ }
+ string prefix = subnet4o6.substr(0, slash);
+ string lenstr = subnet4o6.substr(slash + 1);
+
+ uint8_t len = 128;
+ try {
+ len = boost::lexical_cast<unsigned int>(lenstr.c_str());
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(DhcpConfigError, "Invalid prefix length specified in "
+ "4o6-subnet parameter: " << subnet4o6 << ", expected 0..128 value");
+ }
+ subnet4->get4o6().setSubnet4o6(IOAddress(prefix), len);
+ subnet4->get4o6().enabled(true);
+ }
+ }
+
+ // Try 4o6 specific parameter: 4o6-interface-id
+ if (params->contains("4o6-interface-id")) {
+ std::string ifaceid = getString(params, "4o6-interface-id");
+ if (!ifaceid.empty()) {
+ OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet4->get4o6().setInterfaceId(opt);
+ subnet4->get4o6().enabled(true);
+ }
+ }
+
+ /// client-class processing is now generic and handled in the common
+ /// code (see isc::data::SubnetConfigParser::createSubnet)
+
+ // Here globally defined options were merged to the subnet specific
+ // options but this is no longer the case (they have a different
+ // and not consecutive priority).
+
+ // Parse t1-percent and t2-percent
+ parseTeePercents(params, network);
+
+ // Parse DDNS parameters
+ parseDdnsParams(params, network);
+
+ // Parse lease cache parameters
+ parseCacheParams(params, network);
+
+ // Set the offer_lft value for the subnet.
+ if (params->contains("offer-lifetime")) {
+ uint32_t offer_lft = getInteger(params, "offer-lifetime");
+ subnet4->setOfferLft(offer_lft);
+ }
+
+ // Parse offer-lifetime parameter.
+ Network4Ptr network4 = boost::dynamic_pointer_cast<Network4>(subnet4);
+ parseOfferLft(params, network4);
+
+}
+
+void
+Subnet4ConfigParser::validateResv(const Subnet4Ptr& subnet, ConstHostPtr host) {
+ const IOAddress& address = host->getIPv4Reservation();
+ if (!address.isV4Zero() && !subnet->inRange(address)) {
+ isc_throw(DhcpConfigError, "specified reservation '" << address
+ << "' is not within the IPv4 subnet '"
+ << subnet->toText() << "'");
+ }
+}
+
+boost::shared_ptr<PoolsListParser>
+Subnet4ConfigParser::createPoolsListParser() const {
+ auto parser = boost::make_shared<Pools4ListParser>();
+ return (parser);
+}
+
+//**************************** Subnets4ListConfigParser **********************
+
+Subnets4ListConfigParser::Subnets4ListConfigParser(bool check_iface)
+ : check_iface_(check_iface) {
+}
+
+size_t
+Subnets4ListConfigParser::parse(SrvConfigPtr cfg,
+ ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet4Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+ if (subnet) {
+
+ // Adding a subnet to the Configuration Manager may fail if the
+ // subnet id is invalid (duplicate). Thus, we catch exceptions
+ // here to append a position in the configuration string.
+ try {
+ cfg->getCfgSubnets4()->add(subnet);
+ cnt++;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ }
+ return (cnt);
+}
+
+size_t
+Subnets4ListConfigParser::parse(Subnet4Collection& subnets,
+ data::ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet4Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+ if (subnet) {
+ try {
+ auto ret = subnets.insert(subnet);
+ if (!ret.second) {
+ isc_throw(Unexpected,
+ "can't store subnet because of conflict");
+ }
+ ++cnt;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ }
+ return (cnt);
+}
+
+boost::shared_ptr<Subnet4ConfigParser>
+Subnets4ListConfigParser::createSubnetConfigParser() const {
+ auto parser = boost::make_shared<Subnet4ConfigParser>(check_iface_);
+ return (parser);
+}
+
+//**************************** Pool6Parser *********************************
+
+PoolPtr
+Pool6Parser::poolMaker(IOAddress &addr, uint32_t len, int32_t ptype)
+{
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type>
+ (ptype), addr, len)));
+}
+
+PoolPtr
+Pool6Parser::poolMaker(IOAddress &min, IOAddress &max, int32_t ptype)
+{
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Lease::Type>
+ (ptype), min, max)));
+}
+
+
+//**************************** Pool6ListParser ***************************
+
+void
+Pools6ListParser::parse(PoolStoragePtr pools, ConstElementPtr pools_list,
+ bool encapsulate_options) {
+ BOOST_FOREACH(ConstElementPtr pool, pools_list->listValue()) {
+ auto parser = createPoolConfigParser();
+ parser->parse(pools, pool, AF_INET6, encapsulate_options);
+ }
+}
+
+boost::shared_ptr<PoolParser>
+Pools6ListParser::createPoolConfigParser() const {
+ auto parser = boost::make_shared<Pool6Parser>();
+ return (parser);
+}
+
+//**************************** PdPoolParser ******************************
+
+PdPoolParser::PdPoolParser() {
+}
+
+void
+PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_,
+ bool encapsulate_options) {
+ checkKeywords(SimpleParser6::PD_POOL6_PARAMETERS, pd_pool_);
+
+ std::string addr_str = getString(pd_pool_, "prefix");
+
+ uint8_t prefix_len = getUint8(pd_pool_, "prefix-len");
+
+ uint8_t delegated_len = getUint8(pd_pool_, "delegated-len");
+
+ std::string excluded_prefix_str = "::";
+ if (pd_pool_->contains("excluded-prefix")) {
+ excluded_prefix_str = getString(pd_pool_, "excluded-prefix");
+ }
+
+ uint8_t excluded_prefix_len = 0;
+ if (pd_pool_->contains("excluded-prefix-len")) {
+ excluded_prefix_len = getUint8(pd_pool_, "excluded-prefix-len");
+ }
+
+ ConstElementPtr user_context = pd_pool_->get("user-context");
+ if (user_context) {
+ user_context_ = user_context;
+ }
+
+ ConstElementPtr client_class = pd_pool_->get("client-class");
+ if (client_class) {
+ client_class_ = client_class;
+ }
+
+ ConstElementPtr class_list = pd_pool_->get("require-client-classes");
+
+ // Check the pool parameters. It will throw an exception if any
+ // of the required parameters are invalid.
+ try {
+ // Attempt to construct the local pool.
+ pool_.reset(new Pool6(IOAddress(addr_str),
+ prefix_len,
+ delegated_len,
+ IOAddress(excluded_prefix_str),
+ excluded_prefix_len));
+ } catch (const std::exception& ex) {
+ // Some parameters don't exist or are invalid. Since we are not
+ // aware whether they don't exist or are invalid, let's append
+ // the position of the pool map element.
+ isc_throw(isc::dhcp::DhcpConfigError, ex.what()
+ << " (" << pd_pool_->getPosition() << ")");
+ }
+
+ // We create subnet first and then parse the options straight into the subnet's
+ // CfgOption structure. Previously, we first parsed the options and then copied
+ // them into the CfgOption after creating the subnet but it had two issues. First,
+ // it cost performance. Second, copying options reset the isEncapsulated() flag.
+ // If the options have been encapsulated we want to preserve the flag to ensure
+ // they are not encapsulated several times.
+ ConstElementPtr option_data = pd_pool_->get("option-data");
+ if (option_data) {
+ auto opts_parser = createOptionDataListParser();
+ opts_parser->parse(pool_->getCfgOption(), option_data, encapsulate_options);
+ }
+
+ if (user_context_) {
+ pool_->setContext(user_context_);
+ }
+
+ if (client_class_) {
+ string cclass = client_class_->stringValue();
+ if (!cclass.empty()) {
+ pool_->allowClientClass(cclass);
+ }
+ }
+
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool_->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
+ // Add the local pool to the external storage ptr.
+ pools->push_back(pool_);
+}
+
+boost::shared_ptr<OptionDataListParser>
+PdPoolParser::createOptionDataListParser() const {
+ auto parser = boost::make_shared<OptionDataListParser>(AF_INET6);
+ return (parser);
+}
+
+//**************************** PdPoolsListParser ************************
+
+void
+PdPoolsListParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_list) {
+ // Loop through the list of pd pools.
+ BOOST_FOREACH(ConstElementPtr pd_pool, pd_pool_list->listValue()) {
+ auto parser = createPdPoolConfigParser();
+ parser->parse(pools, pd_pool);
+ }
+}
+
+boost::shared_ptr<PdPoolParser>
+PdPoolsListParser::createPdPoolConfigParser() const {
+ auto parser = boost::make_shared<PdPoolParser>();
+ return (parser);
+}
+
+//**************************** Subnet6ConfigParser ***********************
+
+Subnet6ConfigParser::Subnet6ConfigParser(bool check_iface)
+ : SubnetConfigParser(AF_INET6, check_iface) {
+}
+
+Subnet6Ptr
+Subnet6ConfigParser::parse(ConstElementPtr subnet, bool encapsulate_options) {
+ // Check parameters.
+ checkKeywords(SimpleParser6::SUBNET6_PARAMETERS, subnet);
+
+ /// Parse all pools first.
+ ConstElementPtr pools = subnet->get("pools");
+ if (pools) {
+ auto parser = createPoolsListParser();
+ parser->parse(pools_, pools, encapsulate_options);
+ }
+ ConstElementPtr pd_pools = subnet->get("pd-pools");
+ if (pd_pools) {
+ auto parser = createPdPoolsListParser();
+ parser->parse(pools_, pd_pools);
+ }
+
+ SubnetPtr generic = SubnetConfigParser::parse(subnet, encapsulate_options);
+
+ if (!generic) {
+ // Sanity check: not supposed to fail.
+ isc_throw(DhcpConfigError,
+ "Failed to create an IPv6 subnet (" <<
+ subnet->getPosition() << ")");
+ }
+
+ Subnet6Ptr sn6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_);
+ if (!sn6ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid Subnet6 cast in Subnet6ConfigParser::parse");
+ }
+
+ // Set relay information if it was provided
+ if (relay_info_) {
+ sn6ptr->setRelayInfo(*relay_info_);
+ }
+
+ // Parse Host Reservations for this subnet if any.
+ ConstElementPtr reservations = subnet->get("reservations");
+ if (reservations) {
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ parser.parse(subnet_->getID(), reservations, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ validateResvs(sn6ptr, *h);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }
+
+ // Parse allocator specification.
+ auto network = boost::dynamic_pointer_cast<Network>(sn6ptr);
+ parseAllocatorParams(subnet, network);
+
+ // Parse pd-allocator specification.
+ auto network6 = boost::dynamic_pointer_cast<Network6>(sn6ptr);
+ parsePdAllocatorParams(subnet, network6);
+
+ // Instantiate the allocators.
+ sn6ptr->createAllocators();
+
+ return (sn6ptr);
+}
+
+// Unused?
+void
+Subnet6ConfigParser::duplicateOptionWarning(uint32_t code,
+ asiolink::IOAddress& addr) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_OPTION_DUPLICATE)
+ .arg(code).arg(addr.toText());
+}
+
+void
+Subnet6ConfigParser::initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len) {
+ // Subnet ID is optional. If it is not supplied the value of 0 is used,
+ // which means autogenerate. The value was inserted earlier by calling
+ // SimpleParser6::setAllDefaults.
+ int64_t subnet_id_max = static_cast<int64_t>(SUBNET_ID_MAX);
+ SubnetID subnet_id = static_cast<SubnetID>(getInteger(params, "id", 0,
+ subnet_id_max));
+
+ // We want to log whether rapid-commit is enabled, so we get this
+ // before the actual subnet creation.
+ Optional<bool> rapid_commit;
+ if (params->contains("rapid-commit")) {
+ rapid_commit = getBoolean(params, "rapid-commit");
+ }
+
+ // Parse preferred lifetime as it is not parsed by the common function.
+ Triplet<uint32_t> pref = parseIntTriplet(params, "preferred-lifetime");
+
+ // Create a new subnet.
+ auto subnet6 = Subnet6::create(addr, len, Triplet<uint32_t>(),
+ Triplet<uint32_t>(),
+ pref,
+ Triplet<uint32_t>(),
+ subnet_id);
+ subnet_ = subnet6;
+
+ // Move from reservation mode to new reservations flags.
+ ElementPtr mutable_params;
+ mutable_params = boost::const_pointer_cast<Element>(params);
+ // @todo add warning
+ BaseNetworkParser::moveReservationMode(mutable_params);
+
+ // Parse parameters common to all Network derivations.
+ NetworkPtr network = boost::dynamic_pointer_cast<Network>(subnet_);
+ parseCommon(mutable_params, network);
+
+ // Enable or disable Rapid Commit option support for the subnet.
+ if (!rapid_commit.unspecified()) {
+ subnet6->setRapidCommit(rapid_commit);
+ }
+
+ std::ostringstream output;
+ output << addr << "/" << static_cast<int>(len) << " with params: ";
+ // t1 and t2 are optional may be not specified.
+
+ bool has_renew = !subnet6->getT1().unspecified();
+ bool has_rebind = !subnet6->getT2().unspecified();
+ int64_t renew = -1;
+ int64_t rebind = -1;
+
+ if (has_renew) {
+ renew = subnet6->getT1().get();
+ output << "t1=" << renew << ", ";
+ }
+ if (has_rebind) {
+ rebind = subnet6->getT2().get();
+ output << "t2=" << rebind << ", ";
+ }
+
+ if (!subnet6->getPreferred().unspecified()) {
+ output << "preferred-lifetime=" << subnet6->getPreferred().get() << ", ";
+ }
+ if (!subnet6->getValid().unspecified()) {
+ output << "valid-lifetime=" << subnet6->getValid().get();
+ }
+ if (!subnet6->getRapidCommit().unspecified()) {
+ output << ", rapid-commit is "
+ << boolalpha << subnet6->getRapidCommit().get();
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_NEW_SUBNET6).arg(output.str());
+
+ // Get interface-id option content. For now we support string
+ // representation only
+ Optional<std::string> ifaceid;
+ if (params->contains("interface-id")) {
+ ifaceid = getString(params, "interface-id");
+ }
+
+ Optional<std::string> iface;
+ if (params->contains("interface")) {
+ iface = getString(params, "interface");
+ }
+
+ // Specifying both interface for locally reachable subnets and
+ // interface id for relays is mutually exclusive. Need to test for
+ // this condition.
+ if (!ifaceid.unspecified() && !iface.unspecified() && !ifaceid.empty() &&
+ !iface.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: interface (defined for locally reachable "
+ "subnets) and interface-id (defined for subnets reachable"
+ " via relays) cannot be defined at the same time for "
+ "subnet " << addr << "/" << (int)len << "("
+ << params->getPosition() << ")");
+ }
+
+ // Configure interface-id for remote interfaces, if defined
+ if (!ifaceid.unspecified() && !ifaceid.empty()) {
+ std::string ifaceid_value = ifaceid.get();
+ OptionBuffer tmp(ifaceid_value.begin(), ifaceid_value.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet6->setInterfaceId(opt);
+ }
+
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+ if (!iface.unspecified() && !iface.empty()) {
+ if (check_iface_ && !IfaceMgr::instance().getIface(iface)) {
+ ConstElementPtr error = params->get("interface");
+ isc_throw(DhcpConfigError, "Specified network interface name " << iface
+ << " for subnet " << subnet6->toText()
+ << " is not present in the system ("
+ << error->getPosition() << ")");
+ }
+
+ subnet6->setIface(iface);
+ }
+
+ // Try setting up client class.
+ if (params->contains("client-class")) {
+ string client_class = getString(params, "client-class");
+ if (!client_class.empty()) {
+ subnet6->allowClientClass(client_class);
+ }
+ }
+
+ if (params->contains("require-client-classes")) {
+ // Try setting up required client classes.
+ ConstElementPtr class_list = params->get("require-client-classes");
+ if (class_list) {
+ const std::vector<data::ElementPtr>& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet6->requireClientClass((*cclass)->stringValue());
+ }
+ }
+ }
+
+ /// client-class processing is now generic and handled in the common
+ /// code (see isc::data::SubnetConfigParser::createSubnet)
+
+ // Parse t1-percent and t2-percent
+ parseTeePercents(params, network);
+
+ // Parse DDNS parameters
+ parseDdnsParams(params, network);
+
+ // Parse lease cache parameters
+ parseCacheParams(params, network);
+}
+
+void
+Subnet6ConfigParser::validateResvs(const Subnet6Ptr& subnet, ConstHostPtr host) {
+ IPv6ResrvRange range = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ for (auto it = range.first; it != range.second; ++it) {
+ const IOAddress& address = it->second.getPrefix();
+ if (!subnet->inRange(address)) {
+ isc_throw(DhcpConfigError, "specified reservation '" << address
+ << "' is not within the IPv6 subnet '"
+ << subnet->toText() << "'");
+ }
+ }
+}
+
+boost::shared_ptr<PoolsListParser>
+Subnet6ConfigParser::createPoolsListParser() const {
+ auto parser = boost::make_shared<Pools6ListParser>();
+ return (parser);
+}
+
+boost::shared_ptr<PdPoolsListParser>
+Subnet6ConfigParser::createPdPoolsListParser() const {
+ auto parser = boost::make_shared<PdPoolsListParser>();
+ return (parser);
+}
+
+//**************************** Subnet6ListConfigParser ********************
+
+Subnets6ListConfigParser::Subnets6ListConfigParser(bool check_iface)
+ : check_iface_(check_iface) {
+}
+
+size_t
+Subnets6ListConfigParser::parse(SrvConfigPtr cfg,
+ ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet6Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+
+ // Adding a subnet to the Configuration Manager may fail if the
+ // subnet id is invalid (duplicate). Thus, we catch exceptions
+ // here to append a position in the configuration string.
+ try {
+ cfg->getCfgSubnets6()->add(subnet);
+ cnt++;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ return (cnt);
+}
+
+size_t
+Subnets6ListConfigParser::parse(Subnet6Collection& subnets,
+ ConstElementPtr subnets_list,
+ bool encapsulate_options) {
+ size_t cnt = 0;
+ BOOST_FOREACH(ConstElementPtr subnet_json, subnets_list->listValue()) {
+
+ auto parser = createSubnetConfigParser();
+ Subnet6Ptr subnet = parser->parse(subnet_json, encapsulate_options);
+ if (subnet) {
+ try {
+ auto ret = subnets.insert(subnet);
+ if (!ret.second) {
+ isc_throw(Unexpected,
+ "can't store subnet because of conflict");
+ }
+ ++cnt;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << subnet_json->getPosition() << ")");
+ }
+ }
+ }
+ return (cnt);
+}
+
+boost::shared_ptr<Subnet6ConfigParser>
+Subnets6ListConfigParser::createSubnetConfigParser() const {
+ auto parser = boost::make_shared<Subnet6ConfigParser>(check_iface_);
+ return (parser);
+}
+
+//**************************** D2ClientConfigParser **********************
+
+dhcp_ddns::NameChangeProtocol
+D2ClientConfigParser::getProtocol(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<dhcp_ddns::NameChangeProtocol,
+ dhcp_ddns::stringToNcrProtocol>
+ (scope, name, "NameChangeRequest protocol"));
+}
+
+dhcp_ddns::NameChangeFormat
+D2ClientConfigParser::getFormat(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<dhcp_ddns::NameChangeFormat,
+ dhcp_ddns::stringToNcrFormat>
+ (scope, name, "NameChangeRequest format"));
+}
+
+D2ClientConfig::ReplaceClientNameMode
+D2ClientConfigParser::getMode(ConstElementPtr scope,
+ const std::string& name) {
+ return (getAndConvert<D2ClientConfig::ReplaceClientNameMode,
+ D2ClientConfig::stringToReplaceClientNameMode>
+ (scope, name, "ReplaceClientName mode"));
+}
+
+D2ClientConfigPtr
+D2ClientConfigParser::parse(isc::data::ConstElementPtr client_config) {
+ D2ClientConfigPtr new_config;
+
+ // Get all parameters that are needed to create the D2ClientConfig.
+ bool enable_updates = getBoolean(client_config, "enable-updates");
+
+ IOAddress server_ip = getAddress(client_config, "server-ip");
+
+ uint32_t server_port = getUint32(client_config, "server-port");
+
+ std::string sender_ip_str = getString(client_config, "sender-ip");
+
+ uint32_t sender_port = getUint32(client_config, "sender-port");
+
+ uint32_t max_queue_size = getUint32(client_config, "max-queue-size");
+
+ dhcp_ddns::NameChangeProtocol ncr_protocol =
+ getProtocol(client_config, "ncr-protocol");
+
+ dhcp_ddns::NameChangeFormat ncr_format =
+ getFormat(client_config, "ncr-format");
+
+ IOAddress sender_ip(0);
+ if (sender_ip_str.empty()) {
+ // The default sender IP depends on the server IP family
+ sender_ip = (server_ip.isV4() ? IOAddress::IPV4_ZERO_ADDRESS() :
+ IOAddress::IPV6_ZERO_ADDRESS());
+ } else {
+ try {
+ sender_ip = IOAddress(sender_ip_str);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "invalid address (" << sender_ip_str
+ << ") specified for parameter 'sender-ip' ("
+ << getPosition("sender-ip", client_config) << ")");
+ }
+ }
+
+ // Now we check for logical errors. This repeats what is done in
+ // D2ClientConfig::validate(), but doing it here permits us to
+ // emit meaningful parameter position info in the error.
+ if (ncr_format != dhcp_ddns::FMT_JSON) {
+ isc_throw(D2ClientError, "D2ClientConfig error: NCR Format: "
+ << dhcp_ddns::ncrFormatToString(ncr_format)
+ << " is not supported. ("
+ << getPosition("ncr-format", client_config) << ")");
+ }
+
+ if (ncr_protocol != dhcp_ddns::NCR_UDP) {
+ isc_throw(D2ClientError, "D2ClientConfig error: NCR Protocol: "
+ << dhcp_ddns::ncrProtocolToString(ncr_protocol)
+ << " is not supported. ("
+ << getPosition("ncr-protocol", client_config) << ")");
+ }
+
+ if (sender_ip.getFamily() != server_ip.getFamily()) {
+ isc_throw(D2ClientError,
+ "D2ClientConfig error: address family mismatch: "
+ << "server-ip: " << server_ip.toText()
+ << " is: " << (server_ip.isV4() ? "IPv4" : "IPv6")
+ << " while sender-ip: " << sender_ip.toText()
+ << " is: " << (sender_ip.isV4() ? "IPv4" : "IPv6")
+ << " (" << getPosition("sender-ip", client_config) << ")");
+ }
+
+ if (server_ip == sender_ip && server_port == sender_port) {
+ isc_throw(D2ClientError,
+ "D2ClientConfig error: server and sender cannot"
+ " share the exact same IP address/port: "
+ << server_ip.toText() << "/" << server_port
+ << " (" << getPosition("sender-ip", client_config) << ")");
+ }
+
+ try {
+ // Attempt to create the new client config.
+ new_config.reset(new D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ sender_ip,
+ sender_port,
+ max_queue_size,
+ ncr_protocol,
+ ncr_format));
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << client_config->getPosition() << ")");
+ }
+
+ // Add user context
+ ConstElementPtr user_context = client_config->get("user-context");
+ if (user_context) {
+ new_config->setContext(user_context);
+ }
+
+ return (new_config);
+}
+
+/// @brief This table defines default values for D2 client configuration
+const SimpleDefaults D2ClientConfigParser::D2_CLIENT_CONFIG_DEFAULTS = {
+ // enable-updates is unconditionally required
+ { "server-ip", Element::string, "127.0.0.1" },
+ { "server-port", Element::integer, "53001" },
+ // default sender-ip depends on server-ip family, so we leave default blank
+ // parser knows to use the appropriate ZERO address based on server-ip
+ { "sender-ip", Element::string, "" },
+ { "sender-port", Element::integer, "0" },
+ { "max-queue-size", Element::integer, "1024" },
+ { "ncr-protocol", Element::string, "UDP" },
+ { "ncr-format", Element::string, "JSON" }
+};
+
+size_t
+D2ClientConfigParser::setAllDefaults(isc::data::ConstElementPtr d2_config) {
+ ElementPtr mutable_d2 = boost::const_pointer_cast<Element>(d2_config);
+ return (SimpleParser::setDefaults(mutable_d2, D2_CLIENT_CONFIG_DEFAULTS));
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h
new file mode 100644
index 0000000..b416539
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h
@@ -0,0 +1,1079 @@
+// Copyright (C) 2013-2023 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 DHCP_PARSERS_H
+#define DHCP_PARSERS_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space_container.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <dhcpsrv/cfg_mac_source.h>
+#include <dhcpsrv/srv_config.h>
+#include <dhcpsrv/parsers/base_network_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <cc/simple_parser.h>
+#include <exceptions/exceptions.h>
+#include <util/optional.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<OptionContainer, OptionDescriptor,
+ std::string> OptionStorage;
+/// @brief Shared pointer to option storage.
+typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
+
+/// @brief A template class that stores named elements of a given data type.
+///
+/// This template class is provides data value storage for configuration
+/// parameters of a given data type. The values are stored by parameter name
+/// and as instances of type "ValueType". Each value held in the storage has
+/// a corresponding position within a configuration string (file) specified
+/// as a: file name, line number and position within the line. The position
+/// information is used for logging when the particular configuration value
+/// causes a configuration error.
+///
+/// @tparam ValueType is the data type of the elements to store.
+template<typename ValueType>
+class ValueStorage {
+public:
+ /// @brief Stores the parameter, its value and the position in the
+ /// store.
+ ///
+ /// If the parameter does not exist in the store, then it will be added,
+ /// otherwise its data value and the position will be updated with the
+ /// given values.
+ ///
+ /// @param name is the name of the parameter to store.
+ /// @param value is the data value to store.
+ /// @param position is the position of the data element within a
+ /// configuration string (file).
+ void setParam(const std::string& name, const ValueType& value,
+ const data::Element::Position& position) {
+ values_[name] = value;
+ positions_[name] = position;
+ }
+
+ /// @brief Returns the data value for the given parameter.
+ ///
+ /// Finds and returns the data value for the given parameter.
+ /// @param name is the name of the parameter for which the data
+ /// value is desired.
+ ///
+ /// @return The parameter's data value of type @c ValueType.
+ /// @throw DhcpConfigError if the parameter is not found.
+ ValueType getParam(const std::string& name) const {
+ typename std::map<std::string, ValueType>::const_iterator param
+ = values_.find(name);
+
+ if (param == values_.end()) {
+ isc_throw(DhcpConfigError, "Missing parameter '"
+ << name << "'");
+ }
+
+ return (param->second);
+ }
+
+ /// @brief Returns position of the data element in the configuration string.
+ ///
+ /// The returned object comprises file name, line number and the position
+ /// within the particular line of the configuration string where the data
+ /// element holding a particular value is located.
+ ///
+ /// @param name is the name of the parameter which position is desired.
+ /// @param parent Pointer to a data element which position should be
+ /// returned when position of the specified parameter is not found.
+ ///
+ /// @return Position of the data element or the position holding empty
+ /// file name and two zeros if the position hasn't been specified for the
+ /// particular value.
+ const data::Element::Position&
+ getPosition(const std::string& name, const data::ConstElementPtr parent =
+ data::ConstElementPtr()) const {
+ typename std::map<std::string, data::Element::Position>::const_iterator
+ pos = positions_.find(name);
+ if (pos == positions_.end()) {
+ return (parent ? parent->getPosition() :
+ data::Element::ZERO_POSITION());
+ }
+
+ return (pos->second);
+ }
+
+ /// @brief Returns the data value for an optional parameter.
+ ///
+ /// Finds and returns the data value for the given parameter or
+ /// a supplied default value if it is not found.
+ ///
+ /// @param name is the name of the parameter for which the data
+ /// value is desired.
+ /// @param default_value value to use the default
+ ///
+ /// @return The parameter's data value of type @c ValueType.
+ ValueType getOptionalParam(const std::string& name,
+ const ValueType& default_value) const {
+ typename std::map<std::string, ValueType>::const_iterator param
+ = values_.find(name);
+
+ if (param == values_.end()) {
+ return (default_value);
+ }
+
+ return (param->second);
+ }
+
+ /// @brief Remove the parameter from the store.
+ ///
+ /// Deletes the entry for the given parameter from the store if it
+ /// exists.
+ ///
+ /// @param name is the name of the parameter to delete.
+ void delParam(const std::string& name) {
+ values_.erase(name);
+ positions_.erase(name);
+ }
+
+ /// @brief Deletes all of the entries from the store.
+ ///
+ void clear() {
+ values_.clear();
+ positions_.clear();
+ }
+
+private:
+ /// @brief An std::map of the data values, keyed by parameter names.
+ std::map<std::string, ValueType> values_;
+
+ /// @brief An std::map holding positions of the data elements in the
+ /// configuration, which values are held in @c values_.
+ ///
+ /// The position is used for logging, when the particular value
+ /// causes a configuration error.
+ std::map<std::string, data::Element::Position> positions_;
+
+};
+
+/// @brief Combination of parameter name and configuration contents
+typedef std::pair<std::string, isc::data::ConstElementPtr> ConfigPair;
+
+/// @brief a collection of elements that store uint32 values
+typedef ValueStorage<uint32_t> Uint32Storage;
+typedef boost::shared_ptr<Uint32Storage> Uint32StoragePtr;
+
+/// @brief a collection of elements that store string values
+typedef ValueStorage<std::string> StringStorage;
+typedef boost::shared_ptr<StringStorage> StringStoragePtr;
+
+/// @brief Storage for parsed boolean values.
+typedef ValueStorage<bool> BooleanStorage;
+typedef boost::shared_ptr<BooleanStorage> BooleanStoragePtr;
+
+/// @brief parser for MAC/hardware acquisition sources
+///
+/// This parser handles Dhcp6/mac-sources entry.
+/// It contains a list of MAC/hardware acquisition source, i.e. methods how
+/// MAC address can possibly by obtained in DHCPv6. For a currently supported
+/// methods, see @ref isc::dhcp::Pkt::getMAC.
+class MACSourcesListConfigParser : public isc::data::SimpleParser {
+public:
+ /// @brief parses parameters value
+ ///
+ /// Parses configuration entry (list of sources) and adds each element
+ /// to the sources list.
+ ///
+ /// @param value pointer to the content of parsed values
+ /// @param mac_sources parsed sources will be stored here
+ void parse(CfgMACSource& mac_sources, isc::data::ConstElementPtr value);
+};
+
+/// @brief Parser for the control-socket structure
+///
+/// It does not parse anything, simply stores the element in
+/// the staging config.
+class ControlSocketParser : public isc::data::SimpleParser {
+public:
+ /// @brief "Parses" control-socket structure
+ ///
+ /// Since the SrvConfig structure takes the socket definition
+ /// as ConstElementPtr, there's really nothing to parse here.
+ /// It only does basic sanity checks and throws DhcpConfigError
+ /// if the value is null or is not a map.
+ ///
+ /// @param srv_cfg parsed values will be stored here
+ /// @param value pointer to the content of parsed values
+ void parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value);
+};
+
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser : public isc::data::SimpleParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param address_family Address family: @c AF_INET or @c AF_INET6.
+ OptionDefParser(const uint16_t address_family);
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ /// @return option definition of the parsed structure.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ OptionDefinitionPtr parse(isc::data::ConstElementPtr option_def);
+
+private:
+ /// @brief Address family: @c AF_INET or @c AF_INET6.
+ uint16_t address_family_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : public isc::data::SimpleParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param address_family Address family: @c AF_INET or @c AF_INET6.
+ OptionDefListParser(const uint16_t address_family);
+
+ /// @brief Parses a list of option definitions, create them and store in cfg
+ ///
+ /// This method iterates over def_list, which is a JSON list of option definitions,
+ /// then creates corresponding option definitions and store them in the
+ /// configuration structure.
+ ///
+ /// @param def_list JSON list describing option definitions
+ /// @param cfg parsed option definitions will be stored here
+ void parse(CfgOptionDefPtr cfg, isc::data::ConstElementPtr def_list);
+
+private:
+ /// @brief Address family: @c AF_INET or @c AF_INET6.
+ uint16_t address_family_;
+};
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<PoolPtr> PoolStorage;
+typedef boost::shared_ptr<PoolStorage> PoolStoragePtr;
+
+/// @brief parser for a single pool definition
+///
+/// This abstract parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[X]/pools[X] structure.
+class PoolParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief destructor.
+ virtual ~PoolParser() {
+ }
+
+ /// @brief parses the actual structure
+ ///
+ /// This method parses the actual list of interfaces.
+ /// No validation is done at this stage, everything is interpreted as
+ /// interface name.
+ /// @param pools is the storage in which to store the parsed pool
+ /// @param pool_structure a single entry on a list of pools
+ /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6).
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ virtual void parse(PoolStoragePtr pools,
+ isc::data::ConstElementPtr pool_structure,
+ const uint16_t address_family,
+ bool encapsulate_options = true);
+
+protected:
+ /// @brief Creates a Pool object given a IPv4 prefix and the prefix length.
+ ///
+ /// @param addr is the IP prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ptype is the type of pool to create.
+ /// @return returns a PoolPtr to the new Pool object.
+ virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len,
+ int32_t ptype = 0) = 0;
+
+ /// @brief Creates a Pool object given starting and ending IP addresses.
+ ///
+ /// @param min is the first IP address in the pool.
+ /// @param max is the last IP address in the pool.
+ /// @param ptype is the type of pool to create (not used by all derivations)
+ /// @return returns a PoolPtr to the new Pool object.
+ virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min,
+ isc::asiolink::IOAddress &max,
+ int32_t ptype = 0) = 0;
+
+ /// @brief Returns an instance of the @c OptionDataListParser to
+ /// be used in parsing the option-data structure.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for option data.
+ ///
+ /// @param address_family AF_INET (for DHCPv4) or AF_INET6 (for DHCPv6).
+ ///
+ /// @return an instance of the @c OptionDataListParser.
+ virtual boost::shared_ptr<OptionDataListParser>
+ createOptionDataListParser(const uint16_t address_family) const;
+};
+
+/// @brief Parser for IPv4 pool definitions.
+///
+/// This is the IPv4 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv4 pools. Pool4 objects are created and stored in chosen
+/// PoolStorage container.
+///
+/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
+class Pool4Parser : public PoolParser {
+protected:
+ /// @brief Creates a Pool4 object given a IPv4 prefix and the prefix length.
+ ///
+ /// @param addr is the IPv4 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &addr, uint32_t len,
+ int32_t ignored);
+
+ /// @brief Creates a Pool4 object given starting and ending IPv4 addresses.
+ ///
+ /// @param min is the first IPv4 address in the pool.
+ /// @param max is the last IPv4 address in the pool.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &min, asiolink::IOAddress &max,
+ int32_t ignored);
+};
+
+/// @brief Parser for a list of pools
+///
+/// This parser parses a list pools. Each element on that list gets its own
+/// parser, created with poolParserMaker() method. That method must be specified
+/// for each protocol family (v4 or v6) separately.
+class PoolsListParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief destructor.
+ virtual ~PoolsListParser() {
+ }
+
+ /// @brief parses the actual structure
+ ///
+ /// This method parses the actual list of pools.
+ ///
+ /// @param pools is the storage in which to store the parsed pools.
+ /// @param pools_list a list of pool structures
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ virtual void parse(PoolStoragePtr pools,
+ isc::data::ConstElementPtr pools_list,
+ bool encapsulate_options) = 0;
+
+protected:
+
+ /// @brief Returns an instance of the @c PoolParser to be used in
+ /// parsing the address pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c PoolParser.
+ virtual boost::shared_ptr<PoolParser> createPoolConfigParser() const = 0;
+};
+
+/// @brief Specialization of the pool list parser for DHCPv4
+class Pools4ListParser : public PoolsListParser {
+public:
+
+ /// @brief parses the actual structure
+ ///
+ /// This method parses the actual list of pools.
+ ///
+ /// @param pools storage container in which to store the parsed pool.
+ /// @param pools_list a list of pool structures
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Returns an instance of the @c Pool4Parser to be used in
+ /// parsing the address pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c Pool4Parser.
+ virtual boost::shared_ptr<PoolParser> createPoolConfigParser() const;
+};
+
+/// @brief parser for additional relay information
+///
+/// This concrete parser handles RelayInfo structure definitions.
+/// So far that structure holds only relay IP (v4 or v6) address, but it
+/// is expected that the number of parameters will increase over time.
+///
+/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[x]/relay parameters.
+class RelayInfoParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief constructor
+ /// @param family specifies protocol family (IPv4 or IPv6)
+ explicit RelayInfoParser(const isc::dhcp::Option::Universe& family);
+
+ /// @brief parses the actual relay parameters
+ ///
+ /// The elements currently supported are:
+ /// -# ip-address
+ /// -# ip-addresses
+ ///
+ /// Note that ip-address and ip-addresses are mutually exclusive, with
+ /// former being deprecated. The use of ip-address will cause an debug
+ /// log to be emitted, reminded users to switch.
+ ///
+ /// @param relay_info configuration will be stored here
+ /// @param relay_elem Element tree containing the relay and its members
+ /// @throw isc::dhcp::DhcpConfigError if both or neither of ip-address
+ /// and ip-addresses are specified.
+ void parse(const isc::dhcp::Network::RelayInfoPtr& relay_info,
+ isc::data::ConstElementPtr relay_elem);
+
+ /// @brief Attempts to add an IP address to list of relay addresses
+ ///
+ /// @param name name of the element supplying the address string, (either
+ /// "ip-address" or "ip-addresses")
+ /// @param address_str string form of the IP address to add
+ /// @param relay_elem parent relay element (needed for position info)
+ /// @param relay_info RelayInfo to which the address should be added
+ /// @throw isc::dhcp::DhcpConfigError if the address string is not a valid
+ /// IP address, is an address of the wrong family, or is already in the
+ /// relay address list
+ void addAddress(const std::string& name, const std::string& address_str,
+ isc::data::ConstElementPtr relay_elem,
+ const isc::dhcp::Network::RelayInfoPtr& relay_info);
+private:
+
+ /// Protocol family (IPv4 or IPv6)
+ Option::Universe family_;
+};
+
+/// @brief this class parses a single subnet
+///
+/// There are dedicated @ref Subnet4ConfigParser and @ref Subnet6ConfigParser
+/// classes. They provide specialized parse() methods that return Subnet4Ptr
+/// or Subnet6Ptr.
+///
+/// This class parses the whole subnet definition. This class attempts to
+/// unify the code between v4 and v6 as much as possible. As a result, the flow
+/// is somewhat complex and it looks as follows:
+///
+/// ------- Base class
+/// /
+/// | /----- Derived class
+/// 1. * SubnetXConfigParser::parse() is called.
+/// 2. * SubnetConfigParser::parse() is called.
+/// 3. * SubnetConfigParser::createSubnet() is called.
+/// 4. * SubnetXConfigParser::initSubnet() is called (Subnet4 or Subnet6 is
+/// instantiated here and family specific parameters are set)
+/// 5. Control returns to createSubnet() (step 3) and common parameters
+/// are set.
+class SubnetConfigParser : public BaseNetworkParser {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param family address family: @c AF_INET or @c AF_INET6
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ explicit SubnetConfigParser(uint16_t family, bool check_iface = true);
+
+ /// @brief virtual destructor (does nothing)
+ virtual ~SubnetConfigParser() { }
+
+protected:
+ /// @brief parses a subnet description and returns Subnet{4,6} structure
+ ///
+ /// This method is called from specialized (Subnet4ConfigParser or
+ /// Subnet6ConfigParser) classes.
+ ///
+ /// @param subnet pointer to the content of subnet definition
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @return a pointer to newly created subnet
+ ///
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
+ SubnetPtr parse(isc::data::ConstElementPtr subnet,
+ bool encapsulate_options);
+
+ /// @brief Instantiates the subnet based on a given IP prefix and prefix
+ /// length.
+ ///
+ /// @param params configuration parameters for that subnet
+ /// @param addr is the IP prefix of the subnet.
+ /// @param len is the prefix length
+ virtual void initSubnet(isc::data::ConstElementPtr params,
+ isc::asiolink::IOAddress addr, uint8_t len) = 0;
+
+protected:
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @param data Element map that describes the subnet
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing
+ /// failed.
+ void createSubnet(isc::data::ConstElementPtr data);
+
+ /// @brief Returns an instance of the @c OptionDataListParser to
+ /// be used in parsing the option-data structure.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for option data.
+ ///
+ /// @return an instance of the @c OptionDataListParser.
+ virtual boost::shared_ptr<OptionDataListParser> createOptionDataListParser() const;
+
+ /// @brief Returns an instance of the @c PoolsListParser to be used
+ /// in parsing the address pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c PoolsListParser.
+ virtual boost::shared_ptr<PoolsListParser>
+ createPoolsListParser() const = 0;
+
+ /// Storage for pools belonging to this subnet.
+ PoolStoragePtr pools_;
+
+ /// Pointer to the created subnet object.
+ isc::dhcp::SubnetPtr subnet_;
+
+ /// @brief Address family: @c AF_INET or @c AF_INET6
+ uint16_t address_family_;
+
+ /// Pointer to relay information
+ isc::dhcp::Network::RelayInfoPtr relay_info_;
+
+ /// Check if the specified interface exists in the system.
+ bool check_iface_;
+};
+
+/// @anchor Subnet4ConfigParser
+/// @brief This class parses a single IPv4 subnet.
+///
+/// This is the IPv4 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet4ConfigParser : public SubnetConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// stores global scope parameters, options, option definitions.
+ ///
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ Subnet4ConfigParser(bool check_iface = true);
+
+ /// @brief Parses a single IPv4 subnet configuration and adds to the
+ /// Configuration Manager.
+ ///
+ /// @param subnet A new subnet being configured.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @return a pointer to created Subnet4 object
+ Subnet4Ptr parse(data::ConstElementPtr subnet,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Instantiates the IPv4 Subnet based on a given IPv4 address
+ /// and prefix length.
+ ///
+ /// @param params Data structure describing a subnet.
+ /// @param addr is IPv4 address of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(data::ConstElementPtr params,
+ asiolink::IOAddress addr, uint8_t len);
+
+ /// @brief Verifies the host reservation address is in the subnet range
+ ///
+ /// @param subnet pointer to the subnet
+ /// @param host pointer to the host reservation
+ /// @throw DhcpConfigError when the address is not in the subnet range.
+ void validateResv(const Subnet4Ptr& subnet, ConstHostPtr host);
+
+ /// @brief Returns an instance of the @c Pools4ListParser to be used
+ /// in parsing the address pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c Pools4ListParser.
+ virtual boost::shared_ptr<PoolsListParser>
+ createPoolsListParser() const;
+};
+
+/// @brief this class parses list of DHCP4 subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet4
+/// definitions. It iterates over all entries and creates Subnet4ConfigParser
+/// for each entry.
+class Subnets4ListConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ Subnets4ListConfigParser(bool check_iface = true);
+
+ /// @brief Virtual destructor.
+ virtual ~Subnets4ListConfigParser() {
+ }
+
+ /// @brief parses contents of the list
+ ///
+ /// Iterates over all entries on the list, parses its content
+ /// (by instantiating Subnet6ConfigParser) and adds to specified
+ /// configuration.
+ ///
+ /// @param cfg Pointer to server configuration.
+ /// @param subnets_list pointer to a list of IPv4 subnets
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @return number of subnets created
+ size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list,
+ bool encapsulate_options = true);
+
+ /// @brief Parses contents of the subnet4 list.
+ ///
+ /// @param [out] subnets Container where parsed subnets will be stored.
+ /// @param subnets_list pointer to a list of IPv4 subnets
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @return Number of subnets created.
+ size_t parse(Subnet4Collection& subnets,
+ data::ConstElementPtr subnets_list,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Returns an instance of the @c Subnet4ConfigParser to be
+ /// used in parsing the subnets.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the subnets.
+ ///
+ /// @return an instance of the @c Subnet4ConfigParser.
+ virtual boost::shared_ptr<Subnet4ConfigParser> createSubnetConfigParser() const;
+
+ /// Check if the specified interface exists in the system.
+ bool check_iface_;
+};
+
+/// @brief Parser for IPv6 pool definitions.
+///
+/// This is the IPv6 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv6 pools. Pool6 objects are created and stored in chosen
+/// PoolStorage container.
+///
+/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
+class Pool6Parser : public PoolParser {
+protected:
+ /// @brief Creates a Pool6 object given a IPv6 prefix and the prefix length.
+ ///
+ /// @param addr is the IPv6 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ptype is the type of IPv6 pool (Pool::PoolType). Note this is
+ /// passed in as an int32_t and cast to PoolType to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &addr, uint32_t len, int32_t ptype);
+
+ /// @brief Creates a Pool6 object given starting and ending IPv6 addresses.
+ ///
+ /// @param min is the first IPv6 address in the pool.
+ /// @param max is the last IPv6 address in the pool.
+ /// @param ptype is the type of IPv6 pool (Pool::PoolType). Note this is
+ /// passed in as an int32_t and cast to PoolType to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (asiolink::IOAddress &min, asiolink::IOAddress &max,
+ int32_t ptype);
+};
+
+/// @brief Specialization of the pool list parser for DHCPv6
+class Pools6ListParser : public PoolsListParser {
+public:
+
+ /// @brief parses the actual structure
+ ///
+ /// This method parses the actual list of pools.
+ ///
+ /// @param pools storage container in which to store the parsed pool.
+ /// @param pools_list a list of pool structures
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pools_list,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Returns an instance of the @c Pool6Parser to be used in
+ /// parsing the address pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c Pool6Parser.
+ virtual boost::shared_ptr<PoolParser> createPoolConfigParser() const;
+};
+
+/// @brief Parser for IPv6 prefix delegation definitions.
+///
+/// This class handles prefix delegation pool definitions for IPv6 subnets
+/// Pool6 objects are created and stored in the given PoolStorage container.
+///
+/// PdPool definitions currently support three elements: prefix, prefix-len,
+/// and delegated-len, as shown in the example JSON text below:
+///
+/// @code
+///
+/// {
+/// "prefix": "2001:db8:1::",
+/// "prefix-len": 64,
+/// "delegated-len": 128
+/// }
+/// @endcode
+///
+class PdPoolParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ PdPoolParser();
+
+ /// @brief Virtual destructor.
+ virtual ~PdPoolParser() {
+ }
+
+ /// @brief Builds a prefix delegation pool from the given configuration
+ ///
+ /// This function parses configuration entries and creates an instance
+ /// of a dhcp::Pool6 configured for prefix delegation.
+ ///
+ /// @param pools storage container in which to store the parsed pool.
+ /// @param pd_pool_ pointer to an element that holds configuration entries
+ /// that define a prefix delegation pool.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ ///
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Returns an instance of the @c OptionDataListParser to
+ /// be used in parsing the option-data structure.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for option data.
+ ///
+ /// @return an instance of the @c OptionDataListParser.
+ virtual boost::shared_ptr<OptionDataListParser>
+ createOptionDataListParser() const;
+
+ /// Pointer to the created pool object.
+ isc::dhcp::Pool6Ptr pool_;
+
+ /// @brief User context (optional, may be null)
+ ///
+ /// User context is arbitrary user data, to be used by hooks.
+ isc::data::ConstElementPtr user_context_;
+
+ /// @brief Client class (a client has to belong to to use this pd-pool)
+ ///
+ /// If null, everyone is allowed.
+ isc::data::ConstElementPtr client_class_;
+};
+
+/// @brief Parser for a list of prefix delegation pools.
+///
+/// This parser iterates over a list of prefix delegation pool entries and
+/// creates pool instances for each one. If the parsing is successful, the
+/// collection of pools is committed to the provided storage.
+class PdPoolsListParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~PdPoolsListParser() {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of prefix delegation pools .
+ ///
+ /// @param pools is the pool storage in which to store the parsed
+ /// @param pd_pool_list pointer to an element that holds entries
+ /// that define a prefix delegation pool.
+ ///
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void parse(PoolStoragePtr pools, data::ConstElementPtr pd_pool_list);
+
+protected:
+
+ /// @brief Returns an instance of the @c PdPoolParser to be used in
+ /// parsing the prefix delegation pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c PdPool6Parser.
+ virtual boost::shared_ptr<PdPoolParser>
+ createPdPoolConfigParser() const;
+};
+
+/// @anchor Subnet6ConfigParser
+/// @brief This class parses a single IPv6 subnet.
+///
+/// This is the IPv6 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet6ConfigParser : public SubnetConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// stores global scope parameters, options, option definitions.
+ ///
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ Subnet6ConfigParser(bool check_iface = true);
+
+ /// @brief Parses a single IPv6 subnet configuration and adds to the
+ /// Configuration Manager.
+ ///
+ /// @param subnet A new subnet being configured.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @return a pointer to created Subnet6 object
+ Subnet6Ptr parse(data::ConstElementPtr subnet,
+ bool encapsulate_options = true);
+
+protected:
+ /// @brief Issues a DHCP6 server specific warning regarding duplicate subnet
+ /// options.
+ ///
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo A means to know the correct logger and perhaps a common
+ /// message would allow this message to be emitted by the base class.
+ virtual void duplicateOptionWarning(uint32_t code,
+ asiolink::IOAddress& addr);
+
+ /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address
+ /// and prefix length.
+ ///
+ /// @param params Data structure describing a subnet.
+ /// @param addr is IPv6 prefix of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(isc::data::ConstElementPtr params,
+ isc::asiolink::IOAddress addr, uint8_t len);
+
+ /// @brief Verifies host reservation addresses are in the subnet range
+ ///
+ /// @param subnet pointer to the subnet
+ /// @param host pointer to the host reservation
+ /// @throw DhcpConfigError when an address is not in the subnet range.
+ void validateResvs(const Subnet6Ptr& subnet, ConstHostPtr host);
+
+ /// @brief Returns an instance of the @c Pools6ListParser to be used
+ /// in parsing the address pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c Pools6ListParser.
+ virtual boost::shared_ptr<PoolsListParser>
+ createPoolsListParser() const;
+
+ /// @brief Returns an instance of the @c PdPools6ListParser to be used
+ /// in parsing the prefix delegation pools.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the pools.
+ ///
+ /// @return an instance of the @c PdPools6ListParser.
+ virtual boost::shared_ptr<PdPoolsListParser>
+ createPdPoolsListParser() const;
+};
+
+
+/// @brief this class parses a list of DHCP6 subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet6
+/// definitions. It iterates over all entries and creates Subnet6ConfigParser
+/// for each entry.
+class Subnets6ListConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ Subnets6ListConfigParser(bool check_iface = true);
+
+ /// @brief Virtual destructor.
+ virtual ~Subnets6ListConfigParser() {
+ }
+
+ /// @brief parses contents of the list
+ ///
+ /// Iterates over all entries on the list, parses its content
+ /// (by instantiating Subnet6ConfigParser) and adds to specified
+ /// configuration.
+ ///
+ /// @param cfg configuration (parsed subnets will be stored here)
+ /// @param subnets_list pointer to a list of IPv6 subnets
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ /// @throw DhcpConfigError if CfgMgr rejects the subnet (e.g. subnet-id is a duplicate)
+ size_t parse(SrvConfigPtr cfg, data::ConstElementPtr subnets_list,
+ bool encapsulate_options = true);
+
+ /// @brief Parses contents of the subnet6 list.
+ ///
+ /// @param [out] subnets Container where parsed subnets will be stored.
+ /// @param subnets_list pointer to a list of IPv6 subnets
+ /// @return Number of subnets created.
+ size_t parse(Subnet6Collection& subnets,
+ data::ConstElementPtr subnets_list,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Returns an instance of the @c Subnet6ConfigParser to be
+ /// used in parsing the subnets.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the subnets.
+ ///
+ /// @return an instance of the @c Subnet6ConfigParser.
+ virtual boost::shared_ptr<Subnet6ConfigParser> createSubnetConfigParser() const;
+
+ /// Check if the specified interface exists in the system.
+ bool check_iface_;
+};
+
+/// @brief Parser for D2ClientConfig
+///
+/// This class parses the configuration element "dhcp-ddns" common to the
+/// config files for both dhcp4 and dhcp6. It creates an instance of a
+/// D2ClientConfig.
+class D2ClientConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Parses a given dhcp-ddns element into D2ClientConfig.
+ ///
+ /// @param d2_client_cfg is the "dhcp-ddns" configuration to parse
+ ///
+ /// The elements currently supported are (see isc::dhcp::D2ClientConfig
+ /// for details on each):
+ /// -# enable-updates
+ /// -# server-ip
+ /// -# server-port
+ /// -# sender-ip
+ /// -# sender-port
+ /// -# max-queue-size
+ /// -# ncr-protocol
+ /// -# ncr-format
+ ///
+ /// @return returns a pointer to newly created D2ClientConfig.
+ D2ClientConfigPtr parse(isc::data::ConstElementPtr d2_client_cfg);
+
+ /// @brief Defaults for the D2 client configuration.
+ static const isc::data::SimpleDefaults D2_CLIENT_CONFIG_DEFAULTS;
+
+ /// @brief Sets all defaults for D2 client configuration.
+ ///
+ /// This method sets defaults value. It must not be called
+ /// before the short cut disabled updates condition was checked.
+ ///
+ /// @param d2_config d2 client configuration (will be const cast
+ // to ElementPtr)
+ /// @return number of parameters inserted
+ static size_t setAllDefaults(isc::data::ConstElementPtr d2_config);
+
+private:
+
+ /// @brief Returns a value converted to NameChangeProtocol
+ ///
+ /// Instantiation of getAndConvert() to NameChangeProtocol
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a NameChangeProtocol value
+ dhcp_ddns::NameChangeProtocol
+ getProtocol(isc::data::ConstElementPtr scope, const std::string& name);
+
+ /// @brief Returns a value converted to NameChangeFormat
+ ///
+ /// Instantiation of getAndConvert() to NameChangeFormat
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a NameChangeFormat value
+ dhcp_ddns::NameChangeFormat
+ getFormat(isc::data::ConstElementPtr scope, const std::string& name);
+
+ /// @brief Returns a value converted to ReplaceClientNameMode
+ ///
+ /// Instantiation of getAndConvert() to ReplaceClientNameMode
+ ///
+ /// @param scope specified parameter will be extracted from this scope
+ /// @param name name of the parameter
+ /// @return a NameChangeFormat value
+ D2ClientConfig::ReplaceClientNameMode
+ getMode(isc::data::ConstElementPtr scope, const std::string& name);
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // DHCP_PARSERS_H
diff --git a/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.cc b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.cc
new file mode 100644
index 0000000..e3bca0f
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.cc
@@ -0,0 +1,61 @@
+// Copyright (C) 2015-2023 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 <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/foreach.hpp>
+#include <string>
+#include <sys/types.h>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+ElementPtr
+DHCPQueueControlParser::parse(const ConstElementPtr& control_elem,
+ bool multi_threading_enabled) {
+ // All we really do here is verify that it is a map that
+ // contains at least queue-type. All other content depends
+ // on the packet queue implementation of that type.
+ if (control_elem->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "dhcp-queue-control must be a map");
+ }
+
+ // enable-queue is mandatory.
+ bool enable_queue = getBoolean(control_elem, "enable-queue");
+
+ if (enable_queue) {
+ ConstElementPtr elem = control_elem->get("queue-type");
+ if (!elem) {
+ isc_throw(DhcpConfigError, "when queue is enabled, queue-type is required");
+ } else {
+ if (elem->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "queue-type must be a string");
+ }
+ }
+ }
+
+ // Return a copy of it.
+ ElementPtr result = data::copy(control_elem);
+
+ // Currently not compatible with multi-threading.
+ if (multi_threading_enabled) {
+ // Silently disable it.
+ result->set("enable-queue", Element::create(false));
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_MT_DISABLED_QUEUE_CONTROL);
+ }
+
+ return (result);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.h b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.h
new file mode 100644
index 0000000..d732b2e
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/dhcp_queue_control_parser.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2018-2023 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 DHCP_QUEUE_CONTROL_PARSER_H
+#define DHCP_QUEUE_CONTROL_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for the configuration of DHCP packet queue controls
+///
+/// This parser parses the "dhcp-queue-control" parameter which holds the
+/// the configurable parameters that tailor DHCP packet queue behavior.
+/// In order to provide wide latitude to packet queue implementators,
+/// 'dhcp-queue-control' is mostly treated as a map of arbitrary values.
+/// There is only mandatory value, 'enable-queue', which enables/disables
+/// DHCP packet queueing. If this value is true, then the content must
+/// also include a value for 'queue-type'. Beyond these values, the
+/// map may contain any combination of valid JSON elements.
+///
+/// Unlike most other parsers, this parser primarily serves to validate
+/// the aforementioned rules, and rather than instantiate an object as
+/// a result, it simply returns a copy original map of elements.
+///
+/// This parser is used in both DHCPv4 and DHCPv6. Derived parsers
+/// are not needed.
+class DHCPQueueControlParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Constructor.
+ DHCPQueueControlParser(){};
+
+ /// @brief Parses content of the "dhcp-queue-control".
+ ///
+ /// @param control_elem MapElement containing the queue control values to
+ /// parse.
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled.
+ ///
+ /// @return A copy of the of the input MapElement.
+ ///
+ /// @throw DhcpConfigError if any of the values are invalid.
+ data::ElementPtr parse(const isc::data::ConstElementPtr& control_elem,
+ bool multi_threading_enabled);
+
+private:
+};
+
+}
+} // end of namespace isc
+
+#endif // DHCP_QUEUE_CONTROL_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.cc b/src/lib/dhcpsrv/parsers/duid_config_parser.cc
new file mode 100644
index 0000000..937a542
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/duid_config_parser.cc
@@ -0,0 +1,95 @@
+// Copyright (C) 2015-2023 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 <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void
+DUIDConfigParser::parse(const CfgDUIDPtr& cfg,
+ isc::data::ConstElementPtr duid_configuration) {
+ if (!cfg) {
+ // Sanity check
+ isc_throw(DhcpConfigError, "Must provide valid pointer to cfg when parsing duid");
+ }
+
+ std::string param;
+ try {
+ param = "type";
+ std::string duid_type = getString(duid_configuration, "type");
+ // Map DUID type represented as text into numeric value.
+ DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN;
+ if (duid_type == "LLT") {
+ numeric_type = DUID::DUID_LLT;
+ } else if (duid_type == "EN") {
+ numeric_type = DUID::DUID_EN;
+ } else if (duid_type == "LL") {
+ numeric_type = DUID::DUID_LL;
+ } else {
+ isc_throw(BadValue, "unsupported DUID type '"
+ << duid_type << "'. Expected: LLT, EN or LL");
+ }
+
+ cfg->setType(static_cast<DUID::DUIDType>(numeric_type));
+
+ param = "identifier";
+ if (duid_configuration->contains(param)) {
+ cfg->setIdentifier(getString(duid_configuration, param));
+ }
+
+ param = "htype";
+ if (duid_configuration->contains(param)) {
+ cfg->setHType(getUint16(duid_configuration, param));
+ }
+
+ param = "time";
+ if (duid_configuration->contains(param)) {
+ cfg->setTime(getUint32(duid_configuration, param));
+ }
+
+ param = "enterprise-id";
+ if (duid_configuration->contains(param)) {
+ cfg->setEnterpriseId(getUint32(duid_configuration, param));
+ }
+
+ param = "persist";
+ if (duid_configuration->contains(param)) {
+ cfg->setPersist(getBoolean(duid_configuration, param));
+ }
+
+ param = "user-context";
+ ConstElementPtr user_context = duid_configuration->get("user-context");
+ if (user_context) {
+ cfg->setContext(user_context);
+ }
+ } catch (const DhcpConfigError&) {
+ throw;
+ } catch (const std::exception& ex) {
+ // Append position.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << getPosition(param, duid_configuration) << ")");
+ }
+
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIGURE_SERVERID);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/duid_config_parser.h b/src/lib/dhcpsrv/parsers/duid_config_parser.h
new file mode 100644
index 0000000..8780031
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/duid_config_parser.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2015,2017 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 DUID_CONFIG_PARSER_H
+#define DUID_CONFIG_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for server DUID configuration.
+///
+/// This parser currently supports the following DUID types:
+/// - DUID-LLT,
+/// - DUID-EN
+/// - DUID-LL
+///
+/// @todo Add support for DUID-UUID in the parser.
+class DUIDConfigParser : public isc::data::SimpleParser {
+public:
+ /// @brief Parses DUID configuration.
+ ///
+ /// @param cfg parsed DUID configuration will be stored here
+ /// @param duid_configuration Data element holding a map representing
+ /// DUID configuration.
+ ///
+ /// @throw DhcpConfigError If the configuration is invalid.
+ void parse(const CfgDUIDPtr& cfg, isc::data::ConstElementPtr duid_configuration);
+};
+
+}
+} // end of namespace isc
+
+#endif // DUID_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.cc b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc
new file mode 100644
index 0000000..3bbe333
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.cc
@@ -0,0 +1,69 @@
+// Copyright (C) 2015-2019 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 <cc/data.h>
+#include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/expiration_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void
+ExpirationConfigParser::parse(ConstElementPtr expiration_config) {
+ CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
+
+ std::string param;
+
+ try {
+ param = "reclaim-timer-wait-time";
+ if (expiration_config->contains(param)) {
+ cfg->setReclaimTimerWaitTime(getInteger(expiration_config, param));
+ }
+
+ param = "flush-reclaimed-timer-wait-time";
+ if (expiration_config->contains(param)) {
+ cfg->setFlushReclaimedTimerWaitTime(getInteger(expiration_config,
+ param));
+ }
+
+ param = "hold-reclaimed-time";
+ if (expiration_config->contains(param)) {
+ cfg->setHoldReclaimedTime(getInteger(expiration_config, param));
+ }
+
+ param = "max-reclaim-leases";
+ if (expiration_config->contains(param)) {
+ cfg->setMaxReclaimLeases(getInteger(expiration_config, param));
+ }
+
+ param = "max-reclaim-time";
+ if (expiration_config->contains(param)) {
+ cfg->setMaxReclaimTime(getInteger(expiration_config, param));
+ }
+
+ param = "unwarned-reclaim-cycles";
+ if (expiration_config->contains(param)) {
+ cfg->setUnwarnedReclaimCycles(
+ getInteger(expiration_config, param));
+ }
+ } catch (const DhcpConfigError&) {
+ throw;
+ } catch (const std::exception& ex) {
+ // Append position of the configuration parameter to the error message.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << getPosition(param, expiration_config) << ")");
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/expiration_config_parser.h b/src/lib/dhcpsrv/parsers/expiration_config_parser.h
new file mode 100644
index 0000000..44ba77d
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/expiration_config_parser.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2015,2017 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 EXPIRATION_CONFIG_PARSER_H
+#define EXPIRATION_CONFIG_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+
+namespace isc {
+namespace dhcp {
+
+
+/// @brief Parser for the configuration parameters pertaining to the
+/// processing of expired leases.
+///
+/// This parser iterates over parameters stored in the map and tries to
+/// set the appropriate values in the @c CfgExpiration object of the
+/// Configuration Manager.
+///
+/// Currently supported parameters are:
+/// - reclaim-timer-wait-time,
+/// - flush-reclaimed-timer-wait-time,
+/// - hold-reclaimed-time,
+/// - max-reclaim-leases,
+/// - max-reclaim-time,
+/// - unwarned-reclaim-cycles.
+///
+/// These parameters are optional and the default values are used for
+/// those that aren't specified.
+///
+/// The parser checks if the values of the specified parameters are within
+/// the allowed ranges and throws exception if they aren't. Each parameter
+/// has a corresponding maximum value defined in the @c CfgExpiration class.
+/// None of them may be negative.
+class ExpirationConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Destructor.
+ virtual ~ExpirationConfigParser() { }
+
+ /// @brief Parses parameters in the JSON map, pertaining to the processing
+ /// of the expired leases.
+ ///
+ /// @param expiration_config pointer to the content of parsed values
+ ///
+ /// @throw DhcpConfigError if unknown parameter specified or the
+ /// parameter contains invalid value..
+ void parse(isc::data::ConstElementPtr expiration_config);
+
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // EXPIRATION_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.cc b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
new file mode 100644
index 0000000..751ca09
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.cc
@@ -0,0 +1,457 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <algorithm>
+#include <sys/socket.h>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Returns set of the supported parameters for DHCPv4.
+///
+/// This function returns the set of supported parameters for
+/// host reservation in DHCPv4.
+///
+/// @param identifiers_only Indicates if the function should only
+/// return supported host identifiers (if true) or all supported
+/// parameters (if false).
+const std::set<std::string>&
+getSupportedParams4(const bool identifiers_only = false) {
+ // Holds set of host identifiers.
+ static std::set<std::string> identifiers_set;
+ // Holds set of all supported parameters, including identifiers.
+ static std::set<std::string> params_set;
+ // If this is first execution of this function, we need
+ // to initialize the set.
+ if (identifiers_set.empty()) {
+ identifiers_set.insert("hw-address");
+ identifiers_set.insert("duid");
+ identifiers_set.insert("circuit-id");
+ identifiers_set.insert("client-id");
+ identifiers_set.insert("flex-id");
+ }
+ // Copy identifiers and add all other parameters.
+ if (params_set.empty()) {
+ params_set = identifiers_set;
+ params_set.insert("hostname");
+ params_set.insert("ip-address");
+ params_set.insert("option-data");
+ params_set.insert("next-server");
+ params_set.insert("server-hostname");
+ params_set.insert("boot-file-name");
+ params_set.insert("client-classes");
+ params_set.insert("user-context");
+ }
+ return (identifiers_only ? identifiers_set : params_set);
+}
+
+/// @brief Returns set of the supported parameters for DHCPv6.
+///
+/// This function returns the set of supported parameters for
+/// host reservation in DHCPv6.
+///
+/// @param identifiers_only Indicates if the function should only
+/// return supported host identifiers (if true) or all supported
+/// parameters (if false).
+const std::set<std::string>&
+getSupportedParams6(const bool identifiers_only = false) {
+ // Holds set of host identifiers.
+ static std::set<std::string> identifiers_set;
+ // Holds set of all supported parameters, including identifiers.
+ static std::set<std::string> params_set;
+ // If this is first execution of this function, we need
+ // to initialize the set.
+ if (identifiers_set.empty()) {
+ identifiers_set.insert("hw-address");
+ identifiers_set.insert("duid");
+ identifiers_set.insert("flex-id");
+ }
+ // Copy identifiers and add all other parameters.
+ if (params_set.empty()) {
+ params_set = identifiers_set;
+ params_set.insert("hostname");
+ params_set.insert("ip-addresses");
+ params_set.insert("prefixes");
+ params_set.insert("option-data");
+ params_set.insert("client-classes");
+ params_set.insert("user-context");
+ }
+ return (identifiers_only ? identifiers_set : params_set);
+}
+
+}
+
+namespace isc {
+namespace dhcp {
+
+HostPtr
+HostReservationParser::parse(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data,
+ bool encapsulate_options) {
+ return (parseInternal(subnet_id, reservation_data, encapsulate_options));
+}
+
+HostPtr
+HostReservationParser::parseInternal(const SubnetID&,
+ isc::data::ConstElementPtr reservation_data,
+ bool) {
+ std::string identifier;
+ std::string identifier_name;
+ std::string hostname;
+ ConstElementPtr user_context;
+ HostPtr host;
+
+ try {
+ // Gather those parameters that are common for both IPv4 and IPv6
+ // reservations.
+ BOOST_FOREACH(auto element, reservation_data->mapValue()) {
+ // Check if we support this parameter.
+ if (!isSupportedParameter(element.first)) {
+ isc_throw(DhcpConfigError, "unsupported configuration"
+ " parameter '" << element.first << "'");
+ }
+
+ if (isIdentifierParameter(element.first)) {
+ if (!identifier.empty()) {
+ isc_throw(DhcpConfigError, "the '" << element.first
+ << "' and '" << identifier_name
+ << "' are mutually exclusive");
+ }
+ identifier = element.second->stringValue();
+ identifier_name = element.first;
+
+ } else if (element.first == "hostname") {
+ hostname = element.second->stringValue();
+ } else if (element.first == "user-context") {
+ user_context = element.second;
+ }
+ }
+
+ // Host identifier is a must.
+ if (identifier_name.empty()) {
+ // If there is no identifier specified, we have to display an
+ // error message and include the information what identifiers
+ // are supported.
+ std::ostringstream s;
+ BOOST_FOREACH(std::string param_name, getSupportedParameters(true)) {
+ if (s.tellp() != std::streampos(0)) {
+ s << ", ";
+ }
+ s << param_name;
+ }
+ isc_throw(DhcpConfigError, "one of the supported identifiers must"
+ " be specified for host reservation: "
+ << s.str());
+
+ }
+
+ // Create a host object from the basic parameters we already parsed.
+ host.reset(new Host(identifier, identifier_name, SUBNET_ID_UNUSED,
+ SUBNET_ID_UNUSED, IOAddress("0.0.0.0"), hostname));
+
+ // Add user context
+ if (user_context) {
+ host->setContext(user_context);
+ }
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << reservation_data->getPosition() << ")");
+ }
+
+ return (host);
+}
+
+bool
+HostReservationParser::isIdentifierParameter(const std::string& param_name) const {
+ return (getSupportedParameters(true).count(param_name) > 0);
+}
+
+bool
+HostReservationParser::isSupportedParameter(const std::string& param_name) const {
+ return (getSupportedParameters(false).count(param_name) > 0);
+}
+
+HostPtr
+HostReservationParser4::parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data,
+ bool encapsulate_options) {
+ HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data,
+ encapsulate_options);
+
+ host->setIPv4SubnetID(subnet_id);
+
+ BOOST_FOREACH(auto element, reservation_data->mapValue()) {
+ // For 'option-data' element we will use another parser which
+ // already returns errors with position appended, so don't
+ // surround it with try-catch.
+ if (element.first == "option-data") {
+ CfgOptionPtr cfg_option = host->getCfgOption4();
+
+ // This parser is converted to SimpleParser already. It
+ // parses the Element structure immediately, there's no need
+ // to go through build/commit phases.
+ OptionDataListParser parser(AF_INET);
+ parser.parse(cfg_option, element.second, encapsulate_options);
+
+ // Everything else should be surrounded with try-catch to append
+ // position.
+ } else {
+ try {
+ if (element.first == "ip-address") {
+ host->setIPv4Reservation(IOAddress(element.second->
+ stringValue()));
+ } else if (element.first == "next-server") {
+ host->setNextServer(IOAddress(element.second->stringValue()));
+
+ } else if (element.first == "server-hostname") {
+ host->setServerHostname(element.second->stringValue());
+
+ } else if (element.first == "boot-file-name") {
+ host->setBootFileName(element.second->stringValue());
+
+ } else if (element.first == "client-classes") {
+ BOOST_FOREACH(ConstElementPtr class_element,
+ element.second->listValue()) {
+ host->addClientClass4(class_element->stringValue());
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << element.second->getPosition() << ")");
+ }
+ }
+ }
+
+ return (host);
+}
+
+const std::set<std::string>&
+HostReservationParser4::getSupportedParameters(const bool identifiers_only) const {
+ return (getSupportedParams4(identifiers_only));
+}
+
+HostPtr
+HostReservationParser6::parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data,
+ bool encapsulate_options) {
+ HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data,
+ encapsulate_options);
+
+ host->setIPv6SubnetID(subnet_id);
+
+ BOOST_FOREACH(auto element, reservation_data->mapValue()) {
+ // Parse option values. Note that the configuration option parser
+ // returns errors with position information appended, so there is no
+ // need to surround it with try-clause (and rethrow with position
+ // appended).
+ if (element.first == "option-data") {
+ CfgOptionPtr cfg_option = host->getCfgOption6();
+
+ // This parser is converted to SimpleParser already. It
+ // parses the Element structure immediately, there's no need
+ // to go through build/commit phases.
+ OptionDataListParser parser(AF_INET6);
+ parser.parse(cfg_option, element.second, encapsulate_options);
+
+ } else if (element.first == "ip-addresses" || element.first == "prefixes") {
+ BOOST_FOREACH(ConstElementPtr prefix_element,
+ element.second->listValue()) {
+ try {
+ // For the IPv6 address the prefix length is 128 and the
+ // value specified in the list is a reserved address.
+ IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
+ std::string prefix = prefix_element->stringValue();
+ uint8_t prefix_len = 128;
+
+ // If we're dealing with prefixes, instead of addresses,
+ // we will have to extract the prefix length from the value
+ // specified in the following format: 2001:db8:2000::/64.
+ if (element.first == "prefixes") {
+ // The slash is mandatory for prefixes. If there is no
+ // slash, return an error.
+ size_t len_pos = prefix.find('/');
+ if (len_pos == std::string::npos) {
+ isc_throw(DhcpConfigError, "prefix reservation"
+ " requires prefix length be specified"
+ " in '" << prefix << "'");
+
+ // If there is nothing after the slash, we should also
+ // report an error.
+ } else if (len_pos >= prefix.length() - 1) {
+ isc_throw(DhcpConfigError, "prefix '" << prefix
+ << "' requires length after '/'");
+
+ }
+
+ // Convert the prefix length from the string to the
+ // number. Note, that we don't use the uint8_t type
+ // as the lexical cast would expect a character, e.g.
+ // 'a', instead of prefix length, e.g. '64'.
+ try {
+ prefix_len = boost::lexical_cast<unsigned int>(prefix.substr(len_pos + 1));
+
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(DhcpConfigError, "prefix length value '"
+ << prefix.substr(len_pos + 1)
+ << "' is invalid");
+ }
+
+ if ((prefix_len == 0) || (prefix_len > 128)) {
+ isc_throw(OutOfRange,
+ "'prefix-len' value must be in range of [1..128]");
+ }
+
+ // Remove the slash character and the prefix length
+ // from the parsed value.
+ prefix.erase(len_pos);
+
+ // Finally, set the reservation type.
+ resrv_type = IPv6Resrv::TYPE_PD;
+
+ if (prefix_len != 128) {
+ IOAddress addr(prefix);
+ IOAddress first_address = firstAddrInPrefix(addr, prefix_len);
+ if (first_address != addr) {
+ isc_throw(BadValue, "Prefix address: " << addr
+ << " exceeds prefix/prefix-len pair: " << first_address
+ << "/" << static_cast<uint32_t>(prefix_len));
+ }
+ }
+ }
+
+ // Create a reservation for an address or prefix.
+ host->addReservation(IPv6Resrv(resrv_type,
+ IOAddress(prefix),
+ prefix_len));
+
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << prefix_element->getPosition() << ")");
+ }
+ }
+
+ } else if (element.first == "client-classes") {
+ try {
+ BOOST_FOREACH(ConstElementPtr class_element,
+ element.second->listValue()) {
+ host->addClientClass6(class_element->stringValue());
+ }
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << element.second->getPosition() << ")");
+ }
+ }
+ }
+
+ return (host);
+}
+
+const std::set<std::string>&
+HostReservationParser6::getSupportedParameters(const bool identifiers_only) const {
+ return (getSupportedParams6(identifiers_only));
+}
+
+HostReservationIdsParser::HostReservationIdsParser()
+ : staging_cfg_() {
+}
+
+void
+HostReservationIdsParser::parse(isc::data::ConstElementPtr ids_list) {
+ parseInternal(ids_list);
+}
+
+void
+HostReservationIdsParser::parseInternal(isc::data::ConstElementPtr ids_list) {
+ // Remove existing identifier types.
+ staging_cfg_->clearIdentifierTypes();
+
+ BOOST_FOREACH(ConstElementPtr element, ids_list->listValue()) {
+ std::string id_name = element->stringValue();
+ try {
+ if (id_name != "auto") {
+ if (!isSupportedIdentifier(id_name)) {
+ isc_throw(isc::BadValue, "unsupported identifier '"
+ << id_name << "'");
+ }
+ staging_cfg_->addIdentifierType(id_name);
+
+ } else {
+ // 'auto' is mutually exclusive with other values. If there
+ // are any values in the configuration already it means that
+ // some other values have already been specified.
+ if (!staging_cfg_->getIdentifierTypes().empty()) {
+ isc_throw(isc::BadValue, "if 'auto' keyword is used,"
+ " no other values can be specified within '"
+ "host-reservation-identifiers' list");
+ }
+ // Iterate over all identifier types and for those supported
+ // in a given context (DHCPv4 or DHCPv6) add the identifier type
+ // to the configuration.
+ for (unsigned int i = 0;
+ i <= static_cast<unsigned int>(Host::LAST_IDENTIFIER_TYPE);
+ ++i) {
+ std::string supported_id_name =
+ Host::getIdentifierName(static_cast<Host::IdentifierType>(i));
+ if (isSupportedIdentifier(supported_id_name)) {
+ staging_cfg_->addIdentifierType(supported_id_name);
+ }
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << element->getPosition() << ")");
+ }
+ }
+
+ // The parsed list must not be empty.
+ if (staging_cfg_->getIdentifierTypes().empty()) {
+ isc_throw(DhcpConfigError, "'host-reservation-identifiers' list must not"
+ " be empty (" << ids_list->getPosition() << ")");
+ }
+
+}
+
+HostReservationIdsParser4::HostReservationIdsParser4()
+ : HostReservationIdsParser() {
+ staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations4();
+}
+
+bool
+HostReservationIdsParser4::isSupportedIdentifier(const std::string& id_name) const {
+ return (getSupportedParams4(true).count(id_name) > 0);
+}
+
+HostReservationIdsParser6::HostReservationIdsParser6()
+ : HostReservationIdsParser() {
+ staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations6();
+}
+
+bool
+HostReservationIdsParser6::isSupportedIdentifier(const std::string& id_name) const {
+ return (getSupportedParams6(true).count(id_name) > 0);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/host_reservation_parser.h b/src/lib/dhcpsrv/parsers/host_reservation_parser.h
new file mode 100644
index 0000000..2c2f260
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/host_reservation_parser.h
@@ -0,0 +1,243 @@
+// Copyright (C) 2014-2023 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 HOST_RESERVATION_PARSER_H
+#define HOST_RESERVATION_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/host.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a single host reservation entry.
+class HostReservationParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Destructor.
+ virtual ~HostReservationParser() { }
+
+ /// @brief Parses a single entry for host reservation.
+ ///
+ /// @param subnet_id Identifier of the subnet that the host is
+ /// connected to.
+ /// @param reservation_data Data element holding map with a host
+ /// reservation configuration.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ ///
+ /// @return Pointer to the object representing parsed host.
+ /// @throw DhcpConfigError If the configuration is invalid.
+ virtual HostPtr
+ parse(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data,
+ bool encapsulate_options = true) final;
+
+protected:
+
+ /// @brief Parses a single entry for host reservation.
+ ///
+ /// This method is called by @ref parse and it can be overridden in the
+ /// derived classes to provide class specific parsing logic.
+ ///
+ /// @param subnet_id Identifier of the subnet that the host is
+ /// connected to.
+ /// @param reservation_data Data element holding map with a host
+ /// reservation configuration.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ ///
+ /// @return Pointer to the object representing parsed host.
+ /// @throw DhcpConfigError If the configuration is invalid.
+ virtual HostPtr parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data,
+ bool encapsulate_options);
+
+ /// @brief Checks if the specified parameter is a host identifier.
+ ///
+ /// @param param_name Parameter name.
+ ///
+ /// @return true if the parameter specifies host identifier, false
+ /// otherwise.
+ virtual bool isIdentifierParameter(const std::string& param_name) const;
+
+ /// @brief Checks if the specified parameter is supported by the parser.
+ ///
+ /// @param param_name Parameter name.
+ ///
+ /// @return true if the parameter is supported, false otherwise.
+ virtual bool isSupportedParameter(const std::string& param_name) const;
+
+ /// @brief Returns set of the supported parameters.
+ ///
+ /// @param identifiers_only Indicates if the function should only
+ /// return supported host identifiers (if true) or all supported
+ /// parameters (if false).
+ ///
+ /// @return Set of supported parameter names.
+ virtual const std::set<std::string>&
+ getSupportedParameters(const bool identifiers_only) const = 0;
+};
+
+/// @brief Parser for a single host reservation for DHCPv4.
+class HostReservationParser4 : public HostReservationParser {
+protected:
+
+ /// @brief Parses a single host reservation for DHCPv4.
+ ///
+ /// @param subnet_id Identifier of the subnet that the host is
+ /// connected to.
+ /// @param reservation_data Data element holding map with a host
+ /// reservation configuration.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ ///
+ /// @return Pointer to the object representing parsed host.
+ /// @throw DhcpConfigError If the configuration is invalid.
+ virtual HostPtr parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data,
+ bool encapsulate_options);
+
+ /// @brief Returns set of the supported parameters for DHCPv4.
+ ///
+ /// @param identifiers_only Indicates if the function should only
+ /// return supported host identifiers (if true) or all supported
+ /// parameters (if false).
+ ///
+ /// @return Set of supported parameter names.
+ virtual const std::set<std::string>&
+ getSupportedParameters(const bool identifiers_only) const;
+};
+
+/// @brief Parser for a single host reservation for DHCPv6.
+class HostReservationParser6 : public HostReservationParser {
+protected:
+
+ /// @brief Parses a single host reservation for DHCPv6.
+ ///
+ /// @param subnet_id Identifier of the subnet that the host is
+ /// connected to.
+ /// @param reservation_data Data element holding map with a host
+ /// reservation configuration.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ ///
+ /// @return Pointer to the object representing parsed host.
+ /// @throw DhcpConfigError If the configuration is invalid.
+ virtual HostPtr parseInternal(const SubnetID& subnet_id,
+ isc::data::ConstElementPtr reservation_data,
+ bool encapsulate_options);
+
+ /// @brief Returns set of the supported parameters for DHCPv6.
+ ///
+ /// @param identifiers_only Indicates if the function should only
+ /// return supported host identifiers (if true) or all supported
+ /// parameters (if false).
+ ///
+ /// @return Set of supported parameter names.
+ virtual const std::set<std::string>&
+ getSupportedParameters(const bool identifiers_only) const;
+
+};
+
+/// @brief Parser for a list of host identifiers.
+///
+/// This is a parent parser class for parsing "host-reservation-identifiers"
+/// global configuration parameter. The DHCPv4 and DHCPv6 specific parsers
+/// derive from this class.
+class HostReservationIdsParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Constructor.
+ HostReservationIdsParser();
+
+ /// @brief Destructor.
+ virtual ~HostReservationIdsParser() { }
+
+ /// @brief Parses a list of host identifiers.
+ ///
+ /// @param ids_list Data element pointing to an ordered list of host
+ /// identifier names.
+ ///
+ /// @throw DhcpConfigError If specified configuration is invalid.
+ void parse(isc::data::ConstElementPtr ids_list);
+
+protected:
+
+ /// @brief Parses a list of host identifiers.
+ ///
+ /// This method is called by @ref parse and it can be overridden in the
+ /// derived classes to provide class specific parsing logic.
+ ///
+ /// @param ids_list Data element pointing to an ordered list of host
+ /// identifier names.
+ ///
+ /// @throw DhcpConfigError If specified configuration is invalid.
+ virtual void parseInternal(isc::data::ConstElementPtr ids_list);
+
+ /// @brief Checks if specified identifier name is supported in the
+ /// context of the parser.
+ ///
+ /// This is abstract method which must be implemented in the derived
+ /// parser classes for DHCPv4 and DHCPv6.
+ ///
+ /// @param id_name Identifier name.
+ /// @return true if the specified identifier is supported, false
+ /// otherwise.
+ virtual bool isSupportedIdentifier(const std::string& id_name) const = 0;
+
+ /// @brief Pointer to the object holding configuration.
+ CfgHostOperationsPtr staging_cfg_;
+
+};
+
+/// @brief Parser for a list of host identifiers for DHCPv4.
+class HostReservationIdsParser4 : public HostReservationIdsParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes staging configuration pointer to the one used for DHCPv4
+ /// configuration.
+ HostReservationIdsParser4();
+
+protected:
+
+ /// @brief Checks if specified identifier name is supported for DHCPv4.
+ ///
+ /// @param id_name Identifier name.
+ /// @return true if the specified identifier is supported, false
+ /// otherwise.
+ virtual bool isSupportedIdentifier(const std::string& id_name) const;
+
+};
+
+/// @brief Parser for a list of host identifiers for DHCPv6.
+class HostReservationIdsParser6 : public HostReservationIdsParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes staging configuration pointer to the one used for DHCPv6
+ /// configuration.
+ HostReservationIdsParser6();
+
+protected:
+
+ /// @brief Checks if specified identifier name is supported for DHCPv6.
+ ///
+ /// @param id_name Identifier name.
+ /// @return true if the specified identifier is supported, false
+ /// otherwise.
+ virtual bool isSupportedIdentifier(const std::string& id_name) const;
+};
+
+
+}
+} // end of namespace isc
+
+#endif // HOST_RESERVATION_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h b/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h
new file mode 100644
index 0000000..9f6ce2f
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/host_reservations_list_parser.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2014-2017 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 HOST_RESERVATIONS_LIST_PARSER_H
+#define HOST_RESERVATIONS_LIST_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet_id.h>
+#include <boost/foreach.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a list of host reservations for a subnet.
+///
+/// @tparam HostReservationParserType Host reservation parser to be used to
+/// parse individual reservations: @c HostReservationParser4 or
+/// @c HostReservationParser6.
+template<typename HostReservationParserType>
+class HostReservationsListParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Parses a list of host reservation entries for a subnet.
+ ///
+ /// @param subnet_id Identifier of the subnet to which the reservations
+ /// belong.
+ /// @param hr_list Data element holding a list of host reservations.
+ /// Each host reservation is described by a map object.
+ /// @param [out] hosts_list Hosts representing parsed reservations are stored
+ /// in this list.
+ ///
+ /// @throw DhcpConfigError If the configuration if any of the reservations
+ /// is invalid.
+ void parse(const SubnetID& subnet_id, isc::data::ConstElementPtr hr_list,
+ HostCollection& hosts_list) {
+ HostCollection hosts;
+ BOOST_FOREACH(data::ConstElementPtr reservation, hr_list->listValue()) {
+ HostReservationParserType parser;
+ hosts.push_back(parser.parse(subnet_id, reservation));
+ }
+ hosts_list.swap(hosts);
+ }
+};
+
+}
+}
+
+#endif // HOST_RESERVATIONS_LIST_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc
new file mode 100644
index 0000000..c375b40
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/ifaces_config_parser.cc
@@ -0,0 +1,130 @@
+// Copyright (C) 2015-2023 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 <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <boost/foreach.hpp>
+#include <string>
+#include <sys/types.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void
+IfacesConfigParser::parseInterfacesList(const CfgIfacePtr& cfg_iface,
+ ConstElementPtr ifaces_list) {
+ BOOST_FOREACH(ConstElementPtr iface, ifaces_list->listValue()) {
+ std::string iface_name = iface->stringValue();
+ try {
+ cfg_iface->use(protocol_, iface_name);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "Failed to select interface: "
+ << ex.what() << " (" << iface->getPosition() << ")");
+ }
+ }
+}
+
+IfacesConfigParser::IfacesConfigParser(const uint16_t protocol, bool test_mode)
+ : protocol_(protocol), test_mode_(test_mode) {
+}
+
+void
+IfacesConfigParser::parse(const CfgIfacePtr& cfg,
+ const isc::data::ConstElementPtr& ifaces_config) {
+
+ // Check for re-detect before calling parseInterfacesList()
+ bool re_detect = getBoolean(ifaces_config, "re-detect");
+ cfg->setReDetect(re_detect);
+ if (re_detect && !test_mode_) {
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().detectIfaces();
+ }
+
+ bool socket_type_specified = false;
+ BOOST_FOREACH(ConfigPair element, ifaces_config->mapValue()) {
+ try {
+ if (element.first == "re-detect") {
+ continue;
+ }
+
+ if (element.first == "interfaces") {
+ parseInterfacesList(cfg, element.second);
+ continue;
+ }
+
+ if (element.first == "dhcp-socket-type") {
+ if (protocol_ == AF_INET) {
+ cfg->useSocketType(AF_INET, element.second->stringValue());
+ socket_type_specified = true;
+ continue;
+ } else {
+ isc_throw(DhcpConfigError,
+ "dhcp-socket-type is not supported in DHCPv6");
+ }
+ }
+
+ if (element.first == "outbound-interface") {
+ if (protocol_ == AF_INET) {
+ CfgIface::OutboundIface type =
+ CfgIface::textToOutboundIface(element.second->stringValue());
+ cfg->setOutboundIface(type);
+ continue;
+ } else {
+ isc_throw(DhcpConfigError,
+ "outbound-interface is not supported in DHCPv6");
+ }
+ }
+
+ if (element.first == "service-sockets-require-all") {
+ cfg->setServiceSocketsRequireAll(element.second->boolValue());
+ continue;
+ }
+
+ if (element.first == "service-sockets-retry-wait-time") {
+ cfg->setServiceSocketsRetryWaitTime(static_cast<uint32_t>(element.second->intValue()));
+ continue;
+ }
+
+ if (element.first == "service-sockets-max-retries") {
+ cfg->setServiceSocketsMaxRetries(static_cast<uint32_t>(element.second->intValue()));
+ continue;
+ }
+
+ if (element.first == "user-context") {
+ cfg->setContext(element.second);
+ continue;
+ }
+
+ // This should never happen as the input produced by the parser
+ // see (src/bin/dhcpX/dhcpX_parser.yy) should not produce any
+ // other parameter, so this case is only to catch bugs in
+ // the parser.
+ isc_throw(DhcpConfigError, "unsupported parameter '"
+ << element.first << "'");
+ } catch (const std::exception& ex) {
+ // Append line number where the error occurred.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << element.second->getPosition() << ")");
+ }
+ }
+
+ // User hasn't specified the socket type. Log that we are using
+ // the default type. Log it only if this is DHCPv4. (DHCPv6 does not use
+ // raw sockets).
+ if (!socket_type_specified && (protocol_ == AF_INET) ) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_SOCKET_TYPE_DEFAULT)
+ .arg(cfg->socketTypeToText());
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/ifaces_config_parser.h b/src/lib/dhcpsrv/parsers/ifaces_config_parser.h
new file mode 100644
index 0000000..9eb25cb
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/ifaces_config_parser.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2015-2020 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 IFACES_CONFIG_PARSER_H
+#define IFACES_CONFIG_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for the configuration of interfaces.
+///
+/// This parser parses the "interfaces-config" parameter which holds the
+/// full configuration of the DHCP server with respect to the use of
+/// interfaces, DHCP traffic sockets and alike.
+///
+/// This parser is used in both DHCPv4 and DHCPv6. Derived parsers
+/// are not needed.
+class IfacesConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// In test mode only the configuration is checked. In particular
+ /// sockets are not opened or closed.
+ ///
+ /// @param protocol AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
+ /// @param test_mode True if in test mode, False if not.
+ IfacesConfigParser(const uint16_t protocol, bool test_mode);
+
+ /// @brief Parses content of the "interfaces-config".
+ ///
+ /// @param config parsed structures will be stored here
+ /// @param values pointer to the content of parsed values
+ ///
+ /// @throw DhcpConfigError if the interface names and/or addresses
+ /// are invalid.
+ void parse(const CfgIfacePtr& config, const isc::data::ConstElementPtr& values);
+
+private:
+ /// @brief parses interfaces-list structure
+ ///
+ /// This method goes through all the interfaces-specified in
+ /// 'interfaces-list' and enabled them in the specified configuration
+ /// structure
+ ///
+ /// @param cfg_iface parsed interfaces will be specified here
+ /// @param ifaces_list interfaces-list to be parsed
+ /// @throw DhcpConfigError if the interface names are invalid.
+ void parseInterfacesList(const CfgIfacePtr& cfg_iface,
+ isc::data::ConstElementPtr ifaces_list);
+
+ /// @brief AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
+ int protocol_;
+
+ /// @brief Test mode.
+ bool test_mode_;
+};
+
+}
+} // end of namespace isc
+
+#endif // IFACES_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/multi_threading_config_parser.cc b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.cc
new file mode 100644
index 0000000..68f6c41
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.cc
@@ -0,0 +1,73 @@
+// Copyright (C) 2020-2023 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 <cc/data.h>
+#include <dhcpsrv/srv_config.h>
+#include <dhcpsrv/parsers/multi_threading_config_parser.h>
+#include <util/multi_threading_mgr.h>
+
+#include <limits>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+MultiThreadingConfigParser::parse(SrvConfig& srv_cfg,
+ const ConstElementPtr& value) {
+ if (!value) {
+ return;
+ }
+ if (value->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "multi-threading is supposed to be a map");
+ }
+
+ // enable-multi-threading is mandatory
+ getBoolean(value, "enable-multi-threading");
+
+ // thread-pool-size is not mandatory
+ if (value->get("thread-pool-size")) {
+ auto thread_pool_size = getInteger(value, "thread-pool-size");
+ uint32_t max_size = std::numeric_limits<uint16_t>::max();
+ if (thread_pool_size < 0) {
+ isc_throw(DhcpConfigError,
+ "thread pool size code must not be negative ("
+ << getPosition("thread-pool-size", value) << ")");
+ }
+ if (thread_pool_size > max_size) {
+ isc_throw(DhcpConfigError, "invalid thread pool size '"
+ << thread_pool_size << "', it must not be greater than '"
+ << max_size << "' ("
+ << getPosition("thread-pool-size", value) << ")");
+ }
+ }
+
+ // packet-queue-size is not mandatory
+ if (value->get("packet-queue-size")) {
+ auto packet_queue_size = getInteger(value, "packet-queue-size");
+ uint32_t max_size = std::numeric_limits<uint16_t>::max();
+ if (packet_queue_size < 0) {
+ isc_throw(DhcpConfigError,
+ "packet queue size code must not be negative ("
+ << getPosition("packet-queue-size", value) << ")");
+ }
+ if (packet_queue_size > max_size) {
+ isc_throw(DhcpConfigError, "invalid packet queue size '"
+ << packet_queue_size << "', it must not be greater than '"
+ << max_size << "' ("
+ << getPosition("packet-queue-size", value) << ")");
+ }
+ }
+
+ srv_cfg.setDHCPMultiThreading(value);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/parsers/multi_threading_config_parser.h b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.h
new file mode 100644
index 0000000..d099a05
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/multi_threading_config_parser.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 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 MULTI_THREADING_CONFIG_PARSER_H
+#define MULTI_THREADING_CONFIG_PARSER_H
+
+#include <cc/simple_parser.h>
+#include <dhcpsrv/srv_config.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Simple parser for multi-threading structure
+class MultiThreadingConfigParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief parses JSON structure.
+ ///
+ /// This function stores the 'multi-threading' settings in the server
+ /// configuration and updates the MT mode so that is can be checked when
+ /// parsing 'hooks-libraries'.
+ ///
+ /// @param srv_cfg parsed value will be stored here.
+ /// @param value a JSON map that contains multi-threading parameters.
+ void parse(SrvConfig& srv_cfg, const isc::data::ConstElementPtr& value);
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif /* MULTI_THREADING_CONFIG_PARSER_H */
diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.cc b/src/lib/dhcpsrv/parsers/option_data_parser.cc
new file mode 100644
index 0000000..6a321cd
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/option_data_parser.cc
@@ -0,0 +1,479 @@
+// Copyright (C) 2017-2023 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 <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <boost/foreach.hpp>
+#include <boost/make_shared.hpp>
+#include <limits>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+// **************************** OptionDataParser *************************
+
+OptionDataParser::OptionDataParser(const uint16_t address_family,
+ CfgOptionDefPtr cfg_option_def)
+ : address_family_(address_family), cfg_option_def_(cfg_option_def) {
+}
+
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::parse(isc::data::ConstElementPtr single_option) {
+
+ // Check parameters.
+ if (address_family_ == AF_INET) {
+ checkKeywords(SimpleParser4::OPTION4_PARAMETERS, single_option);
+ } else {
+ checkKeywords(SimpleParser6::OPTION6_PARAMETERS, single_option);
+ }
+
+ // Try to create the option instance.
+ std::pair<OptionDescriptor, std::string> opt = createOption(single_option);
+
+ if (!opt.first.option_) {
+ // Should never happen (@todo: update message)
+ isc_throw(isc::InvalidOperation,
+ "parser logic error: no option has been configured and"
+ " thus there is nothing to commit. Has build() been called?");
+ }
+
+ return (opt);
+}
+
+Optional<uint32_t>
+OptionDataParser::extractCode(ConstElementPtr parent) const {
+ uint32_t code;
+ try {
+ code = getInteger(parent, "code");
+
+ } catch (const std::exception&) {
+ // The code parameter was not found. Return an unspecified
+ // value.
+ return (Optional<uint32_t>());
+ }
+
+ if (address_family_ == AF_INET &&
+ code > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code
+ << "', it must not be greater than '"
+ << static_cast<int>(std::numeric_limits<uint8_t>::max())
+ << "' (" << getPosition("code", parent)
+ << ")");
+
+ } else if (address_family_ == AF_INET6 &&
+ code > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << code
+ << "', it must not exceed '"
+ << std::numeric_limits<uint16_t>::max()
+ << "' (" << getPosition("code", parent)
+ << ")");
+
+ }
+
+ return (Optional<uint32_t>(code));
+}
+
+Optional<std::string>
+OptionDataParser::extractName(ConstElementPtr parent) const {
+ std::string name;
+ try {
+ name = getString(parent, "name");
+
+ } catch (...) {
+ return (Optional<std::string>());
+ }
+
+ if (name.find(" ") != std::string::npos) {
+ isc_throw(DhcpConfigError, "invalid option name '" << name
+ << "', space character is not allowed ("
+ << getPosition("name", parent) << ")");
+ }
+
+ return (Optional<std::string>(name));
+}
+
+Optional<std::string>
+OptionDataParser::extractData(ConstElementPtr parent) const {
+ std::string data;
+ try {
+ data = getString(parent, "data");
+
+ } catch (...) {
+ // The "data" parameter was not found. Return an empty value.
+ return (Optional<std::string>());
+ }
+
+ return (Optional<std::string>(data));
+}
+
+Optional<bool>
+OptionDataParser::extractCSVFormat(ConstElementPtr parent) const {
+ bool csv_format = true;
+ try {
+ csv_format = getBoolean(parent, "csv-format");
+
+ } catch (...) {
+ return (Optional<bool>());
+ }
+
+ return (Optional<bool>(csv_format));
+}
+
+std::string
+OptionDataParser::extractSpace(ConstElementPtr parent) const {
+ std::string space = address_family_ == AF_INET ?
+ DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
+ try {
+ space = getString(parent, "space");
+
+ } catch (...) {
+ return (space);
+ }
+
+ try {
+ if (!OptionSpace::validateName(space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ }
+
+ if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) {
+ isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
+ << "' option space name is reserved for DHCPv4 server");
+
+ } else if ((space == DHCP6_OPTION_SPACE) &&
+ (address_family_ == AF_INET)) {
+ isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
+ << "' option space name is reserved for DHCPv6 server");
+ }
+
+ } catch (const std::exception& ex) {
+ // Append position of the option space parameter.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << getPosition("space", parent) << ")");
+ }
+
+ return (space);
+}
+
+Optional<bool>
+OptionDataParser::extractPersistent(ConstElementPtr parent) const {
+ bool persist = false;
+ try {
+ persist = getBoolean(parent, "always-send");
+
+ } catch (...) {
+ return (Optional<bool>());
+ }
+
+ return (Optional<bool>(persist));
+}
+
+Optional<bool>
+OptionDataParser::extractCancelled(ConstElementPtr parent) const {
+ bool cancel = false;
+ try {
+ cancel = getBoolean(parent, "never-send");
+
+ } catch (...) {
+ return (Optional<bool>());
+ }
+
+ return (Optional<bool>(cancel));
+}
+
+OptionDefinitionPtr
+OptionDataParser::findOptionDefinition(const std::string& option_space,
+ const Optional<uint32_t>& option_code,
+ const Optional<std::string>& option_name) const {
+ OptionDefinitionPtr def;
+ if (cfg_option_def_) {
+ // Check if the definition was given in the constructor
+ if (option_code.unspecified()) {
+ def = cfg_option_def_->get(option_space, option_name);
+ } else {
+ def = cfg_option_def_->get(option_space, option_code);
+ }
+ }
+
+ if (!def) {
+ // Check if this is a standard option.
+ if (option_code.unspecified()) {
+ def = LibDHCP::getOptionDef(option_space, option_name);
+ } else {
+ def = LibDHCP::getOptionDef(option_space, option_code);
+ }
+ }
+
+ if (!def) {
+ // Check if this is a vendor-option. If it is, get vendor-specific
+ // definition.
+ uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(option_space);
+ if (vendor_id) {
+ const Option::Universe u = address_family_ == AF_INET ?
+ Option::V4 : Option::V6;
+ if (option_code.unspecified()) {
+ def = LibDHCP::getVendorOptionDef(u, vendor_id, option_name);
+ } else {
+ def = LibDHCP::getVendorOptionDef(u, vendor_id, option_code);
+ }
+ }
+ }
+
+ if (!def) {
+ // Check if this is an option specified by a user. We used to
+ // check that in the staging configuration, but when the configuration
+ // changes are caused by a command the staging configuration doesn't
+ // exist. What is always available is the container holding runtime
+ // option definitions in LibDHCP. It holds option definitions from
+ // the staging configuration in case of the full reconfiguration or
+ // the definitions from the current configuration in case there is
+ // no staging configuration (after configuration commit). In other
+ // words, runtime options are always the ones that we need here.
+ if (option_code.unspecified()) {
+ def = LibDHCP::getRuntimeOptionDef(option_space, option_name);
+ } else {
+ def = LibDHCP::getRuntimeOptionDef(option_space, option_code);
+ }
+ }
+
+ if (!def) {
+ // Finish by last resort definitions.
+ if (option_code.unspecified()) {
+ def = LibDHCP::getLastResortOptionDef(option_space, option_name);
+ } else {
+ def = LibDHCP::getLastResortOptionDef(option_space, option_code);
+ }
+ }
+
+ return (def);
+}
+
+std::pair<OptionDescriptor, std::string>
+OptionDataParser::createOption(ConstElementPtr option_data) {
+ const Option::Universe universe = address_family_ == AF_INET ?
+ Option::V4 : Option::V6;
+
+ Optional<uint32_t> code_param = extractCode(option_data);
+ Optional<std::string> name_param = extractName(option_data);
+ Optional<bool> csv_format_param = extractCSVFormat(option_data);
+ Optional<bool> persist_param = extractPersistent(option_data);
+ Optional<bool> cancel_param = extractCancelled(option_data);
+ Optional<std::string> data_param = extractData(option_data);
+ std::string space_param = extractSpace(option_data);
+ ConstElementPtr user_context = option_data->get("user-context");
+
+ // Require that option code or option name is specified.
+ if (code_param.unspecified() && name_param.unspecified()) {
+ isc_throw(DhcpConfigError, "option data configuration requires one of"
+ " 'code' or 'name' parameters to be specified"
+ << " (" << option_data->getPosition() << ")");
+ }
+
+ // Try to find a corresponding option definition using option code or
+ // option name.
+ OptionDefinitionPtr def = findOptionDefinition(space_param, code_param, name_param);
+
+ // If there is no definition, the user must not explicitly enable the
+ // use of csv-format.
+ if (!def) {
+ // If explicitly requested that the CSV format is to be used,
+ // the option definition is a must.
+ if (!csv_format_param.unspecified() && csv_format_param) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << space_param << "." << name_param
+ << "' having code '" << code_param
+ << "' does not exist ("
+ << getPosition("name", option_data)
+ << ")");
+
+ // If there is no option definition and the option code is not specified
+ // we have no means to find the option code.
+ } else if (!name_param.unspecified() && code_param.unspecified()) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << space_param << "." << name_param
+ << "' does not exist ("
+ << getPosition("name", option_data)
+ << ")");
+ }
+ } else {
+ // Option name is specified it should match the name in the definition.
+ if (!name_param.unspecified() && (def->getName() != name_param.get())) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << name_param << "' does not match the "
+ << "option definition: '" << space_param
+ << "." << def->getName() << "' ("
+ << getPosition("name", option_data)
+ << ")");
+ }
+ }
+
+ // No data and cancelled is a supported special case.
+ if (!cancel_param.unspecified() && cancel_param &&
+ data_param.unspecified()) {
+ uint16_t code;
+ if (def) {
+ code = def->getCode();
+ } else {
+ code = static_cast<uint16_t>(code_param);
+ }
+ OptionPtr option(new Option(universe, code));
+ bool persistent = !persist_param.unspecified() && persist_param;
+ OptionDescriptor desc(option, persistent, true, "", user_context);
+ return (make_pair(desc, space_param));
+ }
+
+ // Transform string of hexadecimal digits into binary format.
+ std::vector<uint8_t> binary;
+ std::vector<std::string> data_tokens;
+
+ // If the definition is available and csv-format hasn't been explicitly
+ // disabled, we will parse the data as comma separated values.
+ if (def && (csv_format_param.unspecified() || csv_format_param)) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ // It is the only usage of the escape option: this allows
+ // to embed commas in individual values and to return
+ // for instance a string value with embedded commas.
+ data_tokens = isc::util::str::tokens(data_param, ",", true);
+
+ } else {
+ // Try to convert the values in quotes into a vector of ASCII codes.
+ // If the identifier lacks opening and closing quote, this will return
+ // an empty value, in which case we'll try to decode it as a string of
+ // hexadecimal digits.
+ try {
+ binary = util::str::quotedStringToBinary(data_param);
+ if (binary.empty()) {
+ util::str::decodeFormattedHexString(data_param, binary);
+ }
+ } catch (...) {
+ isc_throw(DhcpConfigError, "option data is not a valid"
+ << " string of hexadecimal digits: " << data_param
+ << " ("
+ << getPosition("data", option_data)
+ << ")");
+ }
+ }
+
+ OptionDescriptor desc(false, false);
+
+ if (!def) {
+ // @todo We have a limited set of option definitions initialized at
+ // the moment. In the future we want to initialize option definitions
+ // for all options. Consequently an error will be issued if an option
+ // definition does not exist for a particular option code. For now it is
+ // ok to create generic option if definition does not exist.
+ OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param),
+ binary));
+
+ desc.option_ = option;
+ desc.persistent_ = !persist_param.unspecified() && persist_param;
+ desc.cancelled_ = !cancel_param.unspecified() && cancel_param;
+ } else {
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
+ try {
+ bool use_csv = csv_format_param.unspecified() || csv_format_param;
+ OptionPtr option = use_csv ?
+ def->optionFactory(universe, def->getCode(), data_tokens) :
+ def->optionFactory(universe, def->getCode(), binary);
+ desc.option_ = option;
+ desc.persistent_ = !persist_param.unspecified() && persist_param;
+ desc.cancelled_ = !cancel_param.unspecified() && cancel_param;
+ if (use_csv) {
+ desc.formatted_value_ = data_param;
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << space_param
+ << ", code: " << def->getCode() << "): "
+ << ex.what() << " ("
+ << getPosition("data", option_data)
+ << ")");
+ }
+ }
+
+ // Check PAD and END in (and only in) dhcp4 space.
+ if (space_param == DHCP4_OPTION_SPACE) {
+ if (desc.option_->getType() == DHO_PAD) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved for PAD ("
+ << option_data->getPosition() << ")");
+ } else if (desc.option_->getType() == DHO_END) {
+ isc_throw(DhcpConfigError, "invalid option code '255': "
+ << "reserved for END ("
+ << option_data->getPosition() << ")");
+ }
+ }
+
+ // For dhcp6 space the value 0 is reserved.
+ if (space_param == DHCP6_OPTION_SPACE) {
+ if (desc.option_->getType() == 0) {
+ isc_throw(DhcpConfigError, "invalid option code '0': "
+ << "reserved value ("
+ << option_data->getPosition() << ")");
+ }
+ }
+
+
+ // Add user context
+ if (user_context) {
+ desc.setContext(user_context);
+ }
+
+ // All went good, so we can set the option space name.
+ return (make_pair(desc, space_param));
+}
+
+// **************************** OptionDataListParser *************************
+OptionDataListParser::OptionDataListParser(//const std::string&,
+ //const CfgOptionPtr& cfg,
+ const uint16_t address_family,
+ CfgOptionDefPtr cfg_option_def)
+ : address_family_(address_family), cfg_option_def_(cfg_option_def) {
+}
+
+
+void OptionDataListParser::parse(const CfgOptionPtr& cfg,
+ isc::data::ConstElementPtr option_data_list,
+ bool encapsulate) {
+ auto option_parser = createOptionDataParser();
+ BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) {
+ std::pair<OptionDescriptor, std::string> option =
+ option_parser->parse(data);
+ // Use the option description to keep the formatted value
+ cfg->add(option.first, option.second);
+ if (encapsulate) {
+ cfg->encapsulate();
+ }
+ }
+}
+
+boost::shared_ptr<OptionDataParser>
+OptionDataListParser::createOptionDataParser() const {
+ auto parser = boost::make_shared<OptionDataParser>(address_family_, cfg_option_def_);
+ return (parser);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/option_data_parser.h b/src/lib/dhcpsrv/parsers/option_data_parser.h
new file mode 100644
index 0000000..f6b44b3
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/option_data_parser.h
@@ -0,0 +1,240 @@
+// Copyright (C) 2017-2023 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 OPTION_DATA_PARSER_H
+#define OPTION_DATA_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <boost/shared_ptr.hpp>
+#include <util/optional.h>
+#include <cstdint>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public isc::data::SimpleParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param address_family Address family: @c AF_INET or @c AF_INET6.
+ /// @param cfg_option_def Config option definitions (optional)
+ OptionDataParser(const uint16_t address_family,
+ CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr());
+
+ /// @brief Virtual destructor.
+ virtual ~OptionDataParser() {
+ }
+
+ /// @brief Parses ElementPtr containing option definition
+ ///
+ /// This method parses ElementPtr containing the option definition,
+ /// instantiates the option for it and then returns a pair
+ /// of option descriptor (that holds that new option) and
+ /// a string that specifies the option space.
+ ///
+ /// Note: ElementPtr is expected to contain all fields. If your
+ /// ElementPtr does not have them, please use
+ /// @ref isc::data::SimpleParser::setDefaults to fill the missing fields
+ /// with default values.
+ ///
+ /// @param single_option ElementPtr containing option definition
+ /// @return Option object wrapped in option description and an option
+ /// space
+ std::pair<OptionDescriptor, std::string>
+ parse(isc::data::ConstElementPtr single_option);
+
+protected:
+
+ /// @brief Finds an option definition within an option space
+ ///
+ /// Given an option space and an option code, find the corresponding
+ /// option definition within the option definition storage.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param option_code option code to be used to find the option
+ /// definition, if the option name is unspecified.
+ /// @param option_name option name to be used to lookup the option
+ /// definition.
+ ///
+ /// @return OptionDefinitionPtr of the option definition or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ virtual OptionDefinitionPtr
+ findOptionDefinition(const std::string& option_space,
+ const util::Optional<uint32_t>& option_code,
+ const util::Optional<std::string>& option_name) const;
+
+ /// @brief Create option instance.
+ ///
+ /// Creates an instance of an option and adds it to the provided
+ /// options storage. If the option data parsed by createOption function
+ /// is invalid or insufficient this function emits an exception.
+ ///
+ /// If the option data is given as a string containing a hexadecimal
+ /// literal, then it is converted into binary format. These literals
+ /// may contain upper and lower case digits. They may be octets
+ /// delimited by colons or spaces (octets may be 1 or 2 digits)
+ /// If not delimited octets then they must be a continuous string of
+ /// digits with or without a "0x" prefix. Examples:
+ ///
+ /// -# ab:cd:ef - colon delimited
+ /// -# ab cd ef - space delimited
+ /// -# 0xabcdef - 0x prefixed (no delimiters)
+ /// -# abcdef - no prefix or delimiters
+ ///
+ /// A leading zero is assumed for odd number of digits
+ /// in an octet or continuous string.
+ ///
+ /// @param option_data An element holding data for a single option being
+ /// created.
+ ///
+ /// @return created option descriptor
+ ///
+ /// @throw DhcpConfigError if parameters provided in the configuration
+ /// are invalid.
+ std::pair<OptionDescriptor, std::string>
+ createOption(isc::data::ConstElementPtr option_data);
+
+ /// @brief Retrieves parsed option code as an optional value.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ ///
+ /// @return Option code, possibly unspecified.
+ /// @throw DhcpConfigError if option code is invalid.
+ util::Optional<uint32_t>
+ extractCode(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves parsed option name as an optional value.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ ///
+ /// @return Option name, possibly unspecified.
+ /// @throw DhcpConfigError if option name is invalid.
+ util::Optional<std::string>
+ extractName(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves csv-format parameter as an optional value.
+ ///
+ /// @return Value of the csv-format parameter, possibly unspecified.
+ util::Optional<bool> extractCSVFormat(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves option data as a string.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ /// @return Option data as a string. It will return empty string if
+ /// option data is unspecified.
+ util::Optional<std::string>
+ extractData(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves option space name.
+ ///
+ /// If option space name is not specified in the configuration the
+ /// 'dhcp4' or 'dhcp6' option space name is returned, depending on
+ /// the universe specified in the parser context.
+ ///
+ /// @param parent A data element holding full option data configuration.
+ ///
+ /// @return Option space name.
+ std::string extractSpace(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves persistent/always-send parameter as an optional value.
+ ///
+ /// @return Value of the persistent parameter, possibly unspecified.
+ util::Optional<bool> extractPersistent(data::ConstElementPtr parent) const;
+
+ /// @brief Retrieves cancelled/never-send parameter as an optional value.
+ ///
+ /// @return Value of the cancelled parameter, possibly unspecified.
+ util::Optional<bool> extractCancelled(data::ConstElementPtr parent) const;
+
+ /// @brief Address family: @c AF_INET or @c AF_INET6.
+ uint16_t address_family_;
+
+ /// @brief Config option definitions
+ CfgOptionDefPtr cfg_option_def_;
+};
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public isc::data::SimpleParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param address_family Address family: @c AF_INET or AF_INET6
+ /// @param cfg_option_def Config option definitions (optional)
+ OptionDataListParser(const uint16_t address_family,
+ CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr());
+
+ /// @brief Virtual destructor.
+ virtual ~OptionDataListParser() {
+ }
+
+ /// @brief Parses a list of options, instantiates them and stores in cfg
+ ///
+ /// This method expects to get a list of options in option_data_list,
+ /// iterates over them, creates option objects, wraps them with
+ /// option descriptor and stores in specified cfg.
+ ///
+ /// @param cfg created options will be stored here
+ /// @param option_data_list configuration that describes the options
+ /// @param encapsulate a boolean value indicating whether or not the
+ /// parser should encapsulate options with suboptions. The default
+ /// value is true (encapsulate).
+ void parse(const CfgOptionPtr& cfg,
+ isc::data::ConstElementPtr option_data_list,
+ bool encapsulate = true);
+protected:
+
+ /// @brief Returns an instance of the @c OptionDataListParser to
+ /// be used in parsing options.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for option data.
+ ///
+ /// @return an instance of the @c OptionDataListParser.
+ virtual boost::shared_ptr<OptionDataParser> createOptionDataParser() const;
+
+ /// @brief Address family: @c AF_INET or @c AF_INET6
+ uint16_t address_family_;
+
+ /// @brief Config option definitions
+ CfgOptionDefPtr cfg_option_def_;
+};
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // OPTION_DATA_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/sanity_checks_parser.cc b/src/lib/dhcpsrv/parsers/sanity_checks_parser.cc
new file mode 100644
index 0000000..e7c0b20
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/sanity_checks_parser.cc
@@ -0,0 +1,80 @@
+// Copyright (C) 2018-2022 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 <dhcpsrv/parsers/sanity_checks_parser.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <cc/data.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void
+SanityChecksParser::parse(SrvConfig& cfg, const ConstElementPtr& sanity_checks) {
+
+ if (!sanity_checks) {
+ return;
+ }
+ if (sanity_checks->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "sanity-checks is supposed to be a map");
+ }
+
+ // Subnet-id lease checker.
+ ConstElementPtr checks = sanity_checks->get("lease-checks");
+ if (checks) {
+ if (checks->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "lease-checks must be a string");
+ }
+ std::string lc = checks->stringValue();
+ CfgConsistency::LeaseSanity check;
+ if (lc == "none") {
+ check = CfgConsistency::LEASE_CHECK_NONE;
+ } else if (lc == "warn") {
+ check = CfgConsistency::LEASE_CHECK_WARN;
+ } else if (lc == "fix") {
+ check = CfgConsistency::LEASE_CHECK_FIX;
+ } else if (lc == "fix-del") {
+ check = CfgConsistency::LEASE_CHECK_FIX_DEL;
+ } else if (lc == "del") {
+ check = CfgConsistency::LEASE_CHECK_DEL;
+ } else {
+ isc_throw(DhcpConfigError, "Unsupported lease-checks value: " << lc
+ << ", supported values are: none, warn, fix, fix-del, del");
+ }
+ cfg.getConsistency()->setLeaseSanityCheck(check);
+ }
+
+ // Extended info lease checker.
+ checks = sanity_checks->get("extended-info-checks");
+ if (checks) {
+ if (checks->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "extended-info-checks must be a string");
+ }
+ std::string exc = checks->stringValue();
+ CfgConsistency::ExtendedInfoSanity check;
+ if (exc == "none") {
+ check = CfgConsistency::EXTENDED_INFO_CHECK_NONE;
+ } else if (exc == "fix") {
+ check = CfgConsistency::EXTENDED_INFO_CHECK_FIX;
+ } else if (exc == "strict") {
+ check = CfgConsistency::EXTENDED_INFO_CHECK_STRICT;
+ } else if (exc == "pedantic") {
+ check = CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC;
+ } else {
+ isc_throw(DhcpConfigError,
+ "Unsupported extended-info-checks value: " << exc
+ << ", supported values are: none, fix, strict, pedantic");
+ }
+ cfg.getConsistency()->setExtendedInfoSanityCheck(check);
+ }
+
+ // Additional sanity check fields will come in later here.
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/parsers/sanity_checks_parser.h b/src/lib/dhcpsrv/parsers/sanity_checks_parser.h
new file mode 100644
index 0000000..660d612
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/sanity_checks_parser.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2018-2022 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 SANITY_CHECKS_PARSER_H
+#define SANITY_CHECKS_PARSER_H
+
+#include <cc/simple_parser.h>
+#include <dhcpsrv/srv_config.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Simple parser for sanity-checks structure
+///
+/// Currently parses only one parameter:
+/// - lease-checks. Allowed values: none, warn, fix, fix-del, del
+class SanityChecksParser : public isc::data::SimpleParser {
+ public:
+ /// @brief parses JSON structure
+ ///
+ /// @param srv_cfg parsed value will be stored here
+ /// @param value a JSON map that contains lease-checks parameter.
+ void parse(SrvConfig& srv_cfg, const isc::data::ConstElementPtr& value);
+};
+
+}
+}
+
+#endif /* SANITY_CHECKS_PARSER_H */
diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc
new file mode 100644
index 0000000..ef9cc1d
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc
@@ -0,0 +1,429 @@
+// Copyright (C) 2017-2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/shared_network_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcpsrv/shared_network.h>
+#include <boost/make_shared.hpp>
+#include <boost/pointer_cast.hpp>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+SharedNetwork4Parser::SharedNetwork4Parser(bool check_iface)
+ : check_iface_(check_iface) {
+}
+
+SharedNetwork4Ptr
+SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data,
+ bool encapsulate_options) {
+ SharedNetwork4Ptr shared_network;
+ try {
+
+ // Check parameters.
+ checkKeywords(SimpleParser4::SHARED_NETWORK4_PARAMETERS,
+ shared_network_data);
+
+ // Make sure that the network name has been specified. The name is required
+ // to create a SharedNetwork4 object.
+ std::string name = getString(shared_network_data, "name");
+ shared_network.reset(new SharedNetwork4(name));
+
+ // Move from reservation mode to new reservations flags.
+ ElementPtr mutable_params;
+ mutable_params = boost::const_pointer_cast<Element>(shared_network_data);
+ BaseNetworkParser::moveReservationMode(mutable_params);
+
+ // Parse parameters common to all Network derivations.
+ NetworkPtr network = boost::dynamic_pointer_cast<Network>(shared_network);
+ parseCommon(mutable_params, network);
+
+ // interface is an optional parameter
+ if (shared_network_data->contains("interface")) {
+ std::string iface = getString(shared_network_data, "interface");
+ if (!iface.empty()) {
+ if (check_iface_ && !IfaceMgr::instance().getIface(iface)) {
+ ConstElementPtr error =
+ shared_network_data->get("interface");
+ isc_throw(DhcpConfigError,
+ "Specified network interface name " << iface
+ << " for shared network " << name
+ << " is not present in the system ("
+ << error->getPosition() << ")");
+ }
+ shared_network->setIface(iface);
+ }
+ }
+
+ if (shared_network_data->contains("option-data")) {
+ auto json = shared_network_data->get("option-data");
+ // Create parser instance for option-data.
+ CfgOptionPtr cfg_option = shared_network->getCfgOption();
+ auto parser = createOptionDataListParser();
+ parser->parse(cfg_option, json, encapsulate_options);
+ }
+
+ if (shared_network_data->contains("subnet4")) {
+ auto json = shared_network_data->get("subnet4");
+
+ // Create parser instance of subnet4.
+ auto parser = createSubnetsListParser();
+ Subnet4Collection subnets;
+ parser->parse(subnets, json);
+
+ // Add all returned subnets into shared network.
+ for (auto subnet = subnets.cbegin(); subnet != subnets.cend();
+ ++subnet) {
+ shared_network->add(*subnet);
+ }
+ }
+
+ if (shared_network_data->contains("match-client-id")) {
+ shared_network->setMatchClientId(getBoolean(shared_network_data,
+ "match-client-id"));
+ }
+
+ if (shared_network_data->contains("authoritative")) {
+ shared_network->setAuthoritative(getBoolean(shared_network_data,
+ "authoritative"));
+ }
+
+ // Set next-server
+ if (shared_network_data->contains("next-server")) {
+ std::string next_server;
+ try {
+ next_server = getString(shared_network_data, "next-server");
+ if (!next_server.empty()) {
+ shared_network->setSiaddr(IOAddress(next_server));
+ }
+ } catch (...) {
+ ConstElementPtr next = shared_network_data->get("next-server");
+ std::string pos;
+ if (next) {
+ pos = next->getPosition().str();
+ } else {
+ pos = shared_network_data->getPosition().str();
+ }
+ isc_throw(DhcpConfigError, "invalid parameter next-server : "
+ << next_server << "(" << pos << ")");
+ }
+ }
+
+ // Set server-hostname.
+ if (shared_network_data->contains("server-hostname")) {
+ std::string sname = getString(shared_network_data, "server-hostname");
+ if (!sname.empty()) {
+ if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
+ ConstElementPtr error = shared_network_data->get("server-hostname");
+ isc_throw(DhcpConfigError, "server-hostname must be at most "
+ << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
+ << sname.length() << " ("
+ << error->getPosition() << ")");
+ }
+ shared_network->setSname(sname);
+ }
+ }
+
+ // Set boot-file-name.
+ if (shared_network_data->contains("boot-file-name")) {
+ std::string filename = getString(shared_network_data, "boot-file-name");
+ if (!filename.empty()) {
+ if (filename.length() > Pkt4::MAX_FILE_LEN) {
+ ConstElementPtr error = shared_network_data->get("boot-file-name");
+ isc_throw(DhcpConfigError, "boot-file-name must be at most "
+ << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
+ << filename.length() << " ("
+ << error->getPosition() << ")");
+ }
+ shared_network->setFilename(filename);
+ }
+ }
+
+ if (shared_network_data->contains("client-class")) {
+ std::string client_class = getString(shared_network_data, "client-class");
+ if (!client_class.empty()) {
+ shared_network->allowClientClass(client_class);
+ }
+ }
+
+ ConstElementPtr user_context = shared_network_data->get("user-context");
+ if (user_context) {
+ shared_network->setContext(user_context);
+ }
+
+ if (shared_network_data->contains("require-client-classes")) {
+ const std::vector<data::ElementPtr>& class_list =
+ shared_network_data->get("require-client-classes")->listValue();
+ for (auto cclass = class_list.cbegin();
+ cclass != class_list.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ shared_network->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
+ if (shared_network_data->contains("relay")) {
+ auto relay_parms = shared_network_data->get("relay");
+ if (relay_parms) {
+ RelayInfoParser parser(Option::V4);
+ Network::RelayInfoPtr relay_info(new Network::RelayInfo());
+ parser.parse(relay_info, relay_parms);
+ shared_network->setRelayInfo(*relay_info);
+ }
+ }
+
+ parseTeePercents(shared_network_data, network);
+
+ // Parse DDNS parameters
+ parseDdnsParams(shared_network_data, network);
+
+ // Parse lease cache parameters
+ parseCacheParams(shared_network_data, network);
+
+ // Parse allocator params.
+ parseAllocatorParams(shared_network_data, network);
+
+ // Parse offer-lifetime parameter.
+ Network4Ptr network4 = boost::dynamic_pointer_cast<Network4>(shared_network);
+ parseOfferLft(shared_network_data, network4);
+
+ } catch (const DhcpConfigError&) {
+ // Position was already added
+ throw;
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << shared_network_data->getPosition() << ")");
+ }
+
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a shared network we need to set a callback function
+ // for each shared network to allow for fetching global parameters.
+ shared_network->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
+ });
+
+ return (shared_network);
+}
+
+boost::shared_ptr<OptionDataListParser>
+SharedNetwork4Parser::createOptionDataListParser() const {
+ auto parser = boost::make_shared<OptionDataListParser>(AF_INET);
+ return (parser);
+}
+
+boost::shared_ptr<Subnets4ListConfigParser>
+SharedNetwork4Parser::createSubnetsListParser() const {
+ auto parser = boost::make_shared<Subnets4ListConfigParser>(check_iface_);
+ return (parser);
+}
+
+SharedNetwork6Parser::SharedNetwork6Parser(bool check_iface)
+ : check_iface_(check_iface) {
+}
+
+SharedNetwork6Ptr
+SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data,
+ bool encapsulate_options) {
+ SharedNetwork6Ptr shared_network;
+ std::string name;
+ try {
+ // Check parameters.
+ checkKeywords(SimpleParser6::SHARED_NETWORK6_PARAMETERS,
+ shared_network_data);
+
+ // Make sure that the network name has been specified. The name is required
+ // to create a SharedNetwork6 object.
+ std::string name = getString(shared_network_data, "name");
+ shared_network.reset(new SharedNetwork6(name));
+
+ // Move from reservation mode to new reservations flags.
+ ElementPtr mutable_params;
+ mutable_params = boost::const_pointer_cast<Element>(shared_network_data);
+ BaseNetworkParser::moveReservationMode(mutable_params);
+
+ // Parse parameters common to all Network derivations.
+ NetworkPtr network = boost::dynamic_pointer_cast<Network>(shared_network);
+ parseCommon(mutable_params, network);
+
+ // preferred-lifetime
+ shared_network->setPreferred(parseIntTriplet(shared_network_data,
+ "preferred-lifetime"));
+
+ // Get interface-id option content. For now we support string
+ // representation only
+ Optional<std::string> ifaceid;
+ if (shared_network_data->contains("interface-id")) {
+ ifaceid = getString(shared_network_data, "interface-id");
+ }
+
+ // Interface is an optional parameter
+ Optional<std::string> iface;
+ if (shared_network_data->contains("interface")) {
+ iface = getString(shared_network_data, "interface");
+ }
+
+ // Specifying both interface for locally reachable subnets and
+ // interface id for relays is mutually exclusive. Need to test for
+ // this condition.
+ if (!ifaceid.unspecified() && !iface.unspecified() && !ifaceid.empty() &&
+ !iface.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: interface (defined for locally reachable "
+ "subnets) and interface-id (defined for subnets reachable"
+ " via relays) cannot be defined at the same time for "
+ "shared network " << name << "("
+ << shared_network_data->getPosition() << ")");
+ }
+
+ // Configure interface-id for remote interfaces, if defined
+ if (!ifaceid.unspecified() && !ifaceid.empty()) {
+ std::string ifaceid_value = ifaceid.get();
+ OptionBuffer tmp(ifaceid_value.begin(), ifaceid_value.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ shared_network->setInterfaceId(opt);
+ }
+
+ // Set interface name. If it is defined, then subnets are available
+ // directly over specified network interface.
+ if (!iface.unspecified() && !iface.empty()) {
+ if (check_iface_ && !IfaceMgr::instance().getIface(iface)) {
+ ConstElementPtr error = shared_network_data->get("interface");
+ isc_throw(DhcpConfigError,
+ "Specified network interface name " << iface
+ << " for shared network " << name
+ << " is not present in the system ("
+ << error->getPosition() << ")");
+ }
+ shared_network->setIface(iface);
+ }
+
+ if (shared_network_data->contains("rapid-commit")) {
+ shared_network->setRapidCommit(getBoolean(shared_network_data,
+ "rapid-commit"));
+ }
+
+ if (shared_network_data->contains("option-data")) {
+ auto json = shared_network_data->get("option-data");
+ // Create parser instance for option-data.
+ CfgOptionPtr cfg_option = shared_network->getCfgOption();
+ auto parser = createOptionDataListParser();
+ parser->parse(cfg_option, json, encapsulate_options);
+ }
+
+ if (shared_network_data->contains("client-class")) {
+ std::string client_class = getString(shared_network_data, "client-class");
+ if (!client_class.empty()) {
+ shared_network->allowClientClass(client_class);
+ }
+ }
+
+ ConstElementPtr user_context = shared_network_data->get("user-context");
+ if (user_context) {
+ shared_network->setContext(user_context);
+ }
+
+ if (shared_network_data->contains("require-client-classes")) {
+ const std::vector<data::ElementPtr>& class_list =
+ shared_network_data->get("require-client-classes")->listValue();
+ for (auto cclass = class_list.cbegin();
+ cclass != class_list.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ shared_network->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
+ if (shared_network_data->contains("subnet6")) {
+ auto json = shared_network_data->get("subnet6");
+
+ // Create parser instance of subnet6.
+ auto parser = createSubnetsListParser();
+ Subnet6Collection subnets;
+ parser->parse(subnets, json);
+
+ // Add all returned subnets into shared network.
+ for (auto subnet = subnets.cbegin(); subnet != subnets.cend();
+ ++subnet) {
+ shared_network->add(*subnet);
+ }
+ }
+
+ if (shared_network_data->contains("relay")) {
+ auto relay_parms = shared_network_data->get("relay");
+ if (relay_parms) {
+ RelayInfoParser parser(Option::V6);
+ Network::RelayInfoPtr relay_info(new Network::RelayInfo());
+ parser.parse(relay_info, relay_parms);
+ shared_network->setRelayInfo(*relay_info);
+ }
+ }
+
+ parseTeePercents(shared_network_data, network);
+
+ // Parse DDNS parameters
+ parseDdnsParams(shared_network_data, network);
+
+ // Parse lease cache parameters
+ parseCacheParams(shared_network_data, network);
+
+ // Parse allocator params.
+ parseAllocatorParams(shared_network_data, network);
+ if (network->getAllocatorType() == "flq") {
+ isc_throw(BadValue, "Free Lease Queue allocator is not supported for IPv6 address pools");
+ }
+
+ // Parse prefix delegation allocator params.
+ auto network6 = boost::dynamic_pointer_cast<Network6>(shared_network);
+ parsePdAllocatorParams(shared_network_data, network6);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << shared_network_data->getPosition() << ")");
+ }
+
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a shared network we need to set a callback function
+ // for each shared network which can be used to fetch global parameters.
+ shared_network->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals());
+ });
+
+ return (shared_network);
+}
+
+boost::shared_ptr<OptionDataListParser>
+SharedNetwork6Parser::createOptionDataListParser() const {
+ auto parser = boost::make_shared<OptionDataListParser>(AF_INET6);
+ return (parser);
+}
+
+boost::shared_ptr<Subnets6ListConfigParser>
+SharedNetwork6Parser::createSubnetsListParser() const {
+ auto parser = boost::make_shared<Subnets6ListConfigParser>(check_iface_);
+ return (parser);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.h b/src/lib/dhcpsrv/parsers/shared_network_parser.h
new file mode 100644
index 0000000..b7c78fa
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/shared_network_parser.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2017-2023 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 SHARED_SUBNET_PARSER_H
+#define SHARED_SUBNET_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/parsers/base_network_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements parser for IPv4 shared networks.
+class SharedNetwork4Parser : public BaseNetworkParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ SharedNetwork4Parser(bool check_iface = true);
+
+ /// @brief Virtual destructor.
+ virtual ~SharedNetwork4Parser() {
+ }
+
+ /// @brief Parses shared configuration information for IPv4 shared network.
+ ///
+ /// @param shared_network_data Data element holding shared network
+ /// configuration to be parsed.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ ///
+ /// @return Pointer to an object representing shared network.
+ /// @throw DhcpConfigError when shared network configuration is invalid.
+ SharedNetwork4Ptr
+ parse(const data::ConstElementPtr& shared_network_data,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Returns an instance of the @c OptionDataListParser to
+ /// be used in parsing the option-data structure.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for option data.
+ ///
+ /// @return an instance of the @c OptionDataListParser(AF_INET).
+ virtual boost::shared_ptr<OptionDataListParser> createOptionDataListParser() const;
+
+ /// @brief Returns an instance of the @c Subnets4ListConfigParser
+ /// to be used for parsing the subnets within the shared network.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the subnets.
+ ///
+ /// @return an instance of the @c Subnets4ListConfigParser.
+ virtual boost::shared_ptr<Subnets4ListConfigParser> createSubnetsListParser() const;
+
+ /// Check if the specified interface exists in the system.
+ bool check_iface_;
+};
+
+/// @brief Implements parser for IPv6 shared networks.
+class SharedNetwork6Parser : public BaseNetworkParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ SharedNetwork6Parser(bool check_iface = true);
+
+ /// @brief Virtual destructor.
+ virtual ~SharedNetwork6Parser() {
+ }
+
+ /// @brief Parses shared configuration information for IPv6 shared network.
+ ///
+ /// @param shared_network_data Data element holding shared network
+ /// configuration to be parsed.
+ /// @param encapsulate_options a boolean parameter indicating if the
+ /// parsed options should be encapsulated with suboptions.
+ ///
+ /// @return Pointer to an object representing shared network.
+ /// @throw DhcpConfigError when shared network configuration is invalid.
+ SharedNetwork6Ptr
+ parse(const data::ConstElementPtr& shared_network_data,
+ bool encapsulate_options = true);
+
+protected:
+
+ /// @brief Returns an instance of the @c OptionDataListParser to
+ /// be used in parsing the option-data structure.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for option data.
+ ///
+ /// @return an instance of the @c OptionDataListParser(AF_INET6).
+ virtual boost::shared_ptr<OptionDataListParser> createOptionDataListParser() const;
+
+ /// @brief Returns an instance of the @c Subnets6ListConfigParser
+ /// to be used for parsing the subnets within the shared network.
+ ///
+ /// This function can be overridden in the child classes to supply
+ /// a custom parser for the subnets.
+ ///
+ /// @return an instance of the @c Subnets6ListConfigParser.
+ virtual boost::shared_ptr<Subnets6ListConfigParser> createSubnetsListParser() const;
+
+ /// Check if the specified interface exists in the system.
+ bool check_iface_;
+};
+
+} // enf of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // SHARED_SUBNET_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h b/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h
new file mode 100644
index 0000000..5f49c8b
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/shared_networks_list_parser.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2017-2019 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 SHARED_NETWORKS_LIST_PARSER_H
+#define SHARED_NETWORKS_LIST_PARSER_H
+
+#include <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <cc/simple_parser.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/parsers/shared_network_parser.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a list of shared networks.
+///
+/// This is a generic parser for a list of IPv4 or IPv6 shared networks.
+///
+/// @tparam SharedNetworkParserType Type of the parser to be used for
+/// parsing shared network, i.e. @ref SharedNetwork4Parser or
+/// @ref SharedNetwork6Parser.
+template<typename SharedNetworkParserType>
+class SharedNetworksListParser : public data::SimpleParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param check_iface Check if the specified interface exists in
+ /// the system.
+ SharedNetworksListParser(bool check_iface = true)
+ : check_iface_(check_iface) {
+ }
+
+ /// @brief Parses a list of shared networks.
+ ///
+ /// @tparam CfgSharedNetworksTypePtr Type of the configuration structure
+ /// into which the result will be stored, i.e. @ref CfgSharedNetworks4
+ /// or @ref CfgSharedNetworks6.
+ /// @param [out] cfg Shared networks configuration structure into which
+ /// the data should be parsed.
+ /// @param shared_networks_list_data List element holding a list of
+ /// shared networks.
+ ///
+ /// @throw DhcpConfigError when error has occurred, e.g. when networks
+ /// with duplicated names have been specified.
+ template<typename CfgSharedNetworksTypePtr>
+ void parse(CfgSharedNetworksTypePtr& cfg,
+ const data::ConstElementPtr& shared_networks_list_data) {
+ try {
+ // Get the C++ vector holding networks.
+ const std::vector<data::ElementPtr>& networks_list =
+ shared_networks_list_data->listValue();
+ // Iterate over all networks and do the parsing.
+ for (auto network_element = networks_list.cbegin();
+ network_element != networks_list.cend(); ++network_element) {
+ SharedNetworkParserType parser(check_iface_);
+ auto network = parser.parse(*network_element);
+ cfg->add(network);
+ }
+ } catch (const DhcpConfigError&) {
+ // Such exceptions are emitted by the lower level parsers and
+ // errors should already include element's positions. So, we
+ // simply rethrow.
+ throw;
+
+ } catch (const std::exception& ex) {
+ // Other exceptions don't include positions of the elements, so
+ // we should append one.
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << shared_networks_list_data->getPosition() << ")");
+ }
+ }
+
+protected:
+ /// Check if the specified interface exists in the system.
+ bool check_iface_;
+};
+
+/// @brief Type of the shared networks list parser for IPv4.
+typedef SharedNetworksListParser<SharedNetwork4Parser> SharedNetworks4ListParser;
+
+/// @brief Type of the shared networks list parser for IPv6.
+typedef SharedNetworksListParser<SharedNetwork6Parser> SharedNetworks6ListParser;
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // SHARED_NETWORKS_LIST_PARSER_H
diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.cc b/src/lib/dhcpsrv/parsers/simple_parser4.cc
new file mode 100644
index 0000000..f9349ef
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser4.cc
@@ -0,0 +1,548 @@
+// Copyright (C) 2016-2023 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 <dhcpsrv/parsers/simple_parser4.h>
+#include <cc/data.h>
+#include <boost/foreach.hpp>
+#include <iostream>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+/// @brief This sets of arrays define the default values and
+/// values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file simple_parser4.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines all global parameters in DHCPv4.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows global_param rule in bison grammar.
+const SimpleKeywords SimpleParser4::GLOBAL4_PARAMETERS = {
+ { "valid-lifetime", Element::integer },
+ { "min-valid-lifetime", Element::integer },
+ { "max-valid-lifetime", Element::integer },
+ { "renew-timer", Element::integer },
+ { "rebind-timer", Element::integer },
+ { "decline-probation-period", Element::integer },
+ { "subnet4", Element::list },
+ { "shared-networks", Element::list },
+ { "interfaces-config", Element::map },
+ { "lease-database", Element::map },
+ { "hosts-database", Element::map },
+ { "hosts-databases", Element::list },
+ { "host-reservation-identifiers", Element::list },
+ { "client-classes", Element::list },
+ { "option-def", Element::list },
+ { "option-data", Element::list },
+ { "hooks-libraries", Element::list },
+ { "expired-leases-processing", Element::map },
+ { "dhcp4o6-port", Element::integer },
+ { "control-socket", Element::map },
+ { "dhcp-queue-control", Element::map },
+ { "dhcp-ddns", Element::map },
+ { "echo-client-id", Element::boolean },
+ { "match-client-id", Element::boolean },
+ { "authoritative", Element::boolean },
+ { "next-server", Element::string },
+ { "server-hostname", Element::string },
+ { "boot-file-name", Element::string },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "sanity-checks", Element::map },
+ { "reservations", Element::list },
+ { "config-control", Element::map },
+ { "server-tag", Element::string },
+ { "reservation-mode", Element::string },
+ { "reservations-global", Element::boolean },
+ { "reservations-in-subnet", Element::boolean },
+ { "reservations-out-of-pool", Element::boolean },
+ { "calculate-tee-times", Element::boolean },
+ { "t1-percent", Element::real },
+ { "t2-percent", Element::real },
+ { "loggers", Element::list },
+ { "hostname-char-set", Element::string },
+ { "hostname-char-replacement", Element::string },
+ { "ddns-send-updates", Element::boolean },
+ { "ddns-override-no-update", Element::boolean },
+ { "ddns-override-client-update", Element::boolean },
+ { "ddns-replace-client-name", Element::string },
+ { "ddns-generated-prefix", Element::string },
+ { "ddns-qualifying-suffix", Element::string },
+ { "store-extended-info", Element::boolean },
+ { "statistic-default-sample-count", Element::integer },
+ { "statistic-default-sample-age", Element::integer },
+ { "multi-threading", Element::map },
+ { "cache-threshold", Element::real },
+ { "cache-max-age", Element::integer },
+ { "early-global-reservations-lookup", Element::boolean },
+ { "ip-reservations-unique", Element::boolean },
+ { "reservations-lookup-first", Element::boolean },
+ { "ddns-update-on-renew", Element::boolean },
+ { "ddns-use-conflict-resolution", Element::boolean },
+ { "compatibility", Element::map },
+ { "parked-packet-limit", Element::integer },
+ { "allocator", Element::string },
+ { "offer-lifetime", Element::integer },
+ { "ddns-ttl-percent", Element::real },
+};
+
+/// @brief This table defines default global values for DHCPv4
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in Dhcp4) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = {
+ { "valid-lifetime", Element::integer, "7200" },
+ { "decline-probation-period", Element::integer, "86400" }, // 24h
+ { "dhcp4o6-port", Element::integer, "0" },
+ { "echo-client-id", Element::boolean, "true" },
+ { "match-client-id", Element::boolean, "true" },
+ { "authoritative", Element::boolean, "false" },
+ { "next-server", Element::string, "0.0.0.0" },
+ { "server-hostname", Element::string, "" },
+ { "boot-file-name", Element::string, "" },
+ { "server-tag", Element::string, "" },
+ { "reservations-global", Element::boolean, "false" },
+ { "reservations-in-subnet", Element::boolean, "true" },
+ { "reservations-out-of-pool", Element::boolean, "false" },
+ { "calculate-tee-times", Element::boolean, "false" },
+ { "t1-percent", Element::real, ".50" },
+ { "t2-percent", Element::real, ".875" },
+ { "ddns-send-updates", Element::boolean, "true" },
+ { "ddns-override-no-update", Element::boolean, "false" },
+ { "ddns-override-client-update", Element::boolean, "false" },
+ { "ddns-replace-client-name", Element::string, "never" },
+ { "ddns-generated-prefix", Element::string, "myhost" },
+ { "ddns-qualifying-suffix", Element::string, "" },
+ { "hostname-char-set", Element::string, "[^A-Za-z0-9.-]" },
+ { "hostname-char-replacement", Element::string, "" },
+ { "store-extended-info", Element::boolean, "false" },
+ { "statistic-default-sample-count", Element::integer, "20" },
+ { "statistic-default-sample-age", Element::integer, "0" },
+ { "early-global-reservations-lookup", Element::boolean, "false" },
+ { "ip-reservations-unique", Element::boolean, "true" },
+ { "reservations-lookup-first", Element::boolean, "false" },
+ { "ddns-update-on-renew", Element::boolean, "false" },
+ { "ddns-use-conflict-resolution", Element::boolean, "true" },
+ { "parked-packet-limit", Element::integer, "256" },
+ { "allocator", Element::string, "iterative" },
+};
+
+/// @brief This table defines all option definition parameters.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows option_def_param rules in bison grammar.
+const SimpleKeywords SimpleParser4::OPTION4_DEF_PARAMETERS = {
+ { "name", Element::string },
+ { "code", Element::integer },
+ { "type", Element::string },
+ { "record-types", Element::string },
+ { "space", Element::string },
+ { "encapsulate", Element::string },
+ { "array", Element::boolean, },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map }
+};
+
+/// @brief This table defines default values for option definitions in DHCPv4.
+///
+/// Dhcp4 may contain an array called option-def that enumerates new option
+/// definitions. This array lists default values for those option definitions.
+const SimpleDefaults SimpleParser4::OPTION4_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp4"}, // DHCP4_OPTION_SPACE
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// @brief This table defines all option parameters.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows option_param rules in bison grammar.
+const SimpleKeywords SimpleParser4::OPTION4_PARAMETERS = {
+ { "name", Element::string },
+ { "data", Element::string },
+ { "code", Element::integer },
+ { "space", Element::string },
+ { "csv-format", Element::boolean },
+ { "always-send", Element::boolean },
+ { "never-send", Element::boolean },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map }
+};
+
+/// @brief This table defines default values for options in DHCPv4.
+///
+/// Dhcp4 usually contains option values (option-data) defined in global,
+/// subnet, class or host reservations scopes. This array lists default values
+/// for those option-data declarations.
+const SimpleDefaults SimpleParser4::OPTION4_DEFAULTS = {
+ { "space", Element::string, "dhcp4"}, // DHCP4_OPTION_SPACE
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean, "false"},
+ { "never-send", Element::boolean, "false"}
+};
+
+/// @brief This table defines all subnet parameters for DHCPv4.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows subnet4_param rule in bison grammar.
+const SimpleKeywords SimpleParser4::SUBNET4_PARAMETERS = {
+ { "valid-lifetime", Element::integer },
+ { "min-valid-lifetime", Element::integer },
+ { "max-valid-lifetime", Element::integer },
+ { "renew-timer", Element::integer },
+ { "rebind-timer", Element::integer },
+ { "option-data", Element::list },
+ { "pools", Element::list },
+ { "subnet", Element::string },
+ { "interface", Element::string },
+ { "id", Element::integer },
+ { "client-class", Element::string },
+ { "require-client-classes", Element::list },
+ { "reservations", Element::list },
+ { "reservation-mode", Element::string },
+ { "reservations-global", Element::boolean },
+ { "reservations-in-subnet", Element::boolean },
+ { "reservations-out-of-pool", Element::boolean },
+ { "relay", Element::map },
+ { "match-client-id", Element::boolean },
+ { "authoritative", Element::boolean },
+ { "next-server", Element::string },
+ { "server-hostname", Element::string },
+ { "boot-file-name", Element::string },
+ { "4o6-interface", Element::string },
+ { "4o6-interface-id", Element::string },
+ { "4o6-subnet", Element::string },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "calculate-tee-times", Element::boolean },
+ { "t1-percent", Element::real },
+ { "t2-percent", Element::real },
+ { "ddns-send-updates", Element::boolean },
+ { "ddns-override-no-update", Element::boolean },
+ { "ddns-override-client-update", Element::boolean },
+ { "ddns-replace-client-name", Element::string },
+ { "ddns-generated-prefix", Element::string },
+ { "ddns-qualifying-suffix", Element::string },
+ { "hostname-char-set", Element::string },
+ { "hostname-char-replacement", Element::string },
+ { "store-extended-info", Element::boolean },
+ { "metadata", Element::map },
+ { "cache-threshold", Element::real },
+ { "cache-max-age", Element::integer },
+ { "ddns-update-on-renew", Element::boolean },
+ { "ddns-use-conflict-resolution", Element::boolean },
+ { "allocator", Element::string },
+ { "offer-lifetime", Element::integer },
+ { "ddns-ttl-percent", Element::real },
+};
+
+/// @brief This table defines default values for each IPv4 subnet.
+///
+/// Note: When updating this array, please also update SHARED_SUBNET4_DEFAULTS
+/// below. In most cases, those two should be kept in sync, except cases
+/// where a parameter can be derived from shared-networks, but is not
+/// defined on global level. Currently there are two such parameters:
+/// interface and reservation-mode
+const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = {
+ { "id", Element::integer, "0" }, // 0 means autogenerate
+ { "interface", Element::string, "" },
+ { "client-class", Element::string, "" },
+ { "4o6-interface", Element::string, "" },
+ { "4o6-interface-id", Element::string, "" },
+ { "4o6-subnet", Element::string, "" },
+};
+
+/// @brief This table defines default values for each IPv4 subnet that is
+/// part of a shared network
+///
+/// This is mostly the same as @ref SUBNET4_DEFAULTS, except two parameters
+/// that can be derived from shared-network, but cannot from global scope.
+/// Those are: interface and reservation-mode.
+const SimpleDefaults SimpleParser4::SHARED_SUBNET4_DEFAULTS = {
+ { "id", Element::integer, "0" }, // 0 means autogenerate
+ { "4o6-interface", Element::string, "" },
+ { "4o6-interface-id", Element::string, "" },
+ { "4o6-subnet", Element::string, "" },
+};
+
+/// @brief This table defines default values for each IPv4 shared network.
+const SimpleDefaults SimpleParser4::SHARED_NETWORK4_DEFAULTS = {
+ { "client-class", Element::string, "" },
+ { "interface", Element::string, "" }
+};
+
+/// @brief List of parameters that can be inherited to subnet4 scope.
+///
+/// Some parameters may be defined on both global (directly in Dhcp4) and
+/// subnet (Dhcp4/subnet4/...) scope. If not defined in the subnet scope,
+/// the value is being inherited (derived) from the global scope. This
+/// array lists all of such parameters.
+///
+/// This list is also used for inheriting from global to shared networks
+/// and from shared networks to subnets within it.
+const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = {
+ "rebind-timer",
+ "relay",
+ "renew-timer",
+ "valid-lifetime",
+ "min-valid-lifetime",
+ "max-valid-lifetime",
+ "calculate-tee-times",
+ "t1-percent",
+ "t2-percent",
+ "store-extended-info",
+ "cache-threshold",
+ "cache-max-age",
+ "allocator",
+ "offer-lifetime",
+};
+
+/// @brief This table defines all pool parameters.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows pool_param rules in bison grammar.
+const SimpleKeywords SimpleParser4::POOL4_PARAMETERS = {
+ { "pool", Element::string },
+ { "pool-id", Element::integer },
+ { "option-data", Element::list },
+ { "client-class", Element::string },
+ { "require-client-classes", Element::list },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map }
+};
+
+/// @brief This table defines all shared network parameters for DHCPv4.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows shared_network_param rule in bison grammar.
+const SimpleKeywords SimpleParser4::SHARED_NETWORK4_PARAMETERS = {
+ { "name", Element::string },
+ { "subnet4", Element::list },
+ { "interface", Element::string },
+ { "renew-timer", Element::integer },
+ { "rebind-timer", Element::integer },
+ { "option-data", Element::list },
+ { "match-client-id", Element::boolean },
+ { "authoritative", Element::boolean },
+ { "next-server", Element::string },
+ { "server-hostname", Element::string },
+ { "boot-file-name", Element::string },
+ { "relay", Element::map },
+ { "reservation-mode", Element::string },
+ { "reservations-global", Element::boolean },
+ { "reservations-in-subnet", Element::boolean },
+ { "reservations-out-of-pool", Element::boolean },
+ { "client-class", Element::string },
+ { "require-client-classes", Element::list },
+ { "valid-lifetime", Element::integer },
+ { "min-valid-lifetime", Element::integer },
+ { "max-valid-lifetime", Element::integer },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "calculate-tee-times", Element::boolean },
+ { "t1-percent", Element::real },
+ { "t2-percent", Element::real },
+ { "ddns-send-updates", Element::boolean },
+ { "ddns-override-no-update", Element::boolean },
+ { "ddns-override-client-update", Element::boolean },
+ { "ddns-replace-client-name", Element::string },
+ { "ddns-generated-prefix", Element::string },
+ { "ddns-qualifying-suffix", Element::string },
+ { "hostname-char-set", Element::string },
+ { "hostname-char-replacement", Element::string },
+ { "store-extended-info", Element::boolean },
+ { "metadata", Element::map },
+ { "cache-threshold", Element::real },
+ { "cache-max-age", Element::integer },
+ { "ddns-update-on-renew", Element::boolean },
+ { "ddns-use-conflict-resolution", Element::boolean },
+ { "allocator", Element::string },
+ { "offer-lifetime", Element::integer },
+ { "ddns-ttl-percent", Element::real },
+};
+
+/// @brief This table defines default values for interfaces for DHCPv4.
+const SimpleDefaults SimpleParser4::IFACE4_DEFAULTS = {
+ { "re-detect", Element::boolean, "true" }
+};
+
+/// @brief This table defines default values for dhcp-queue-control in DHCPv4.
+const SimpleDefaults SimpleParser4::DHCP_QUEUE_CONTROL4_DEFAULTS = {
+ { "enable-queue", Element::boolean, "false"},
+ { "queue-type", Element::string, "kea-ring4"},
+ { "capacity", Element::integer, "64"}
+};
+
+/// @brief This table defines default values for multi-threading in DHCPv4.
+const SimpleDefaults SimpleParser4::DHCP_MULTI_THREADING4_DEFAULTS = {
+ { "enable-multi-threading", Element::boolean, "true" },
+ { "thread-pool-size", Element::integer, "0" },
+ { "packet-queue-size", Element::integer, "64" }
+};
+
+/// @brief This defines default values for sanity checking for DHCPv4.
+const SimpleDefaults SimpleParser4::SANITY_CHECKS4_DEFAULTS = {
+ { "lease-checks", Element::string, "warn" }
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t SimpleParser4::setAllDefaults(ElementPtr global) {
+ size_t cnt = 0;
+
+ // Set global defaults first.
+ cnt = setDefaults(global, GLOBAL4_DEFAULTS);
+
+ // Now set option definition defaults for each specified option definition
+ ConstElementPtr option_defs = global->get("option-def");
+ if (option_defs) {
+ BOOST_FOREACH(ElementPtr option_def, option_defs->listValue()) {
+ cnt += SimpleParser::setDefaults(option_def, OPTION4_DEF_DEFAULTS);
+ }
+ }
+
+ // Set the defaults for option data
+ ConstElementPtr options = global->get("option-data");
+ if (options) {
+ cnt += setListDefaults(options, OPTION4_DEFAULTS);
+ }
+
+ // Now set the defaults for defined subnets
+ ConstElementPtr subnets = global->get("subnet4");
+ if (subnets) {
+ cnt += setListDefaults(subnets, SUBNET4_DEFAULTS);
+ }
+
+ // Set the defaults for interfaces config
+ ConstElementPtr ifaces_cfg = global->get("interfaces-config");
+ if (ifaces_cfg) {
+ ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(ifaces_cfg);
+ cnt += setDefaults(mutable_cfg, IFACE4_DEFAULTS);
+ }
+
+ // Set defaults for shared networks
+ ConstElementPtr shared = global->get("shared-networks");
+ if (shared) {
+ BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+
+ cnt += setDefaults(net, SHARED_NETWORK4_DEFAULTS);
+
+ ConstElementPtr subs = net->get("subnet4");
+ if (subs) {
+ cnt += setListDefaults(subs, SHARED_SUBNET4_DEFAULTS);
+ }
+ }
+ }
+
+ // Set the defaults for dhcp-queue-control. If the element isn't
+ // there we'll add it.
+ ConstElementPtr queue_control = global->get("dhcp-queue-control");
+ ElementPtr mutable_cfg;
+ if (queue_control) {
+ mutable_cfg = boost::const_pointer_cast<Element>(queue_control);
+ } else {
+ mutable_cfg = Element::createMap();
+ global->set("dhcp-queue-control", mutable_cfg);
+ }
+
+ cnt += setDefaults(mutable_cfg, DHCP_QUEUE_CONTROL4_DEFAULTS);
+
+ // Set the defaults for multi-threading. If the element isn't there
+ // we'll add it.
+ ConstElementPtr multi_threading = global->get("multi-threading");
+ if (multi_threading) {
+ mutable_cfg = boost::const_pointer_cast<Element>(multi_threading);
+ } else {
+ mutable_cfg = Element::createMap();
+ global->set("multi-threading", mutable_cfg);
+ }
+
+ cnt += setDefaults(mutable_cfg, DHCP_MULTI_THREADING4_DEFAULTS);
+
+ // Set the defaults for sanity-checks. If the element isn't
+ // there we'll add it.
+ ConstElementPtr sanity_checks = global->get("sanity-checks");
+ if (sanity_checks) {
+ mutable_cfg = boost::const_pointer_cast<Element>(sanity_checks);
+ } else {
+ mutable_cfg = Element::createMap();
+ global->set("sanity-checks", mutable_cfg);
+ }
+
+ cnt += setDefaults(mutable_cfg, SANITY_CHECKS4_DEFAULTS);
+
+ return (cnt);
+}
+
+size_t SimpleParser4::deriveParameters(ElementPtr global) {
+ size_t cnt = 0;
+
+ // Now derive global parameters into subnets.
+ ConstElementPtr subnets = global->get("subnet4");
+ if (subnets) {
+ BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+ cnt += SimpleParser::deriveParams(global, single_subnet,
+ INHERIT_TO_SUBNET4);
+ }
+ }
+
+ // Deriving parameters for shared networks is a bit more involved.
+ // First, the shared-network level derives from global, and then
+ // subnets within derive from it.
+ ConstElementPtr shared = global->get("shared-networks");
+ if (shared) {
+ BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+ // First try to inherit the parameters from shared network,
+ // if defined there.
+ // Then try to inherit them from global.
+ cnt += SimpleParser::deriveParams(global, net,
+ INHERIT_TO_SUBNET4);
+
+ // Now we need to go thrugh all the subnets in this net.
+ subnets = net->get("subnet4");
+ if (subnets) {
+ BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+ cnt += SimpleParser::deriveParams(net, single_subnet,
+ INHERIT_TO_SUBNET4);
+ }
+ }
+ }
+ }
+
+ return (cnt);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/parsers/simple_parser4.h b/src/lib/dhcpsrv/parsers/simple_parser4.h
new file mode 100644
index 0000000..e209ab4
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser4.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2016-2020 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 SIMPLE_PARSER4_H
+#define SIMPLE_PARSER4_H
+
+#include <cc/simple_parser.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief SimpleParser specialized for DHCPv4
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to DHCPv4 parser.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file simple_parser4.cc
+class SimpleParser4 : public isc::data::SimpleParser {
+public:
+
+ /// @brief Sets all defaults for DHCPv4 configuration
+ ///
+ /// This method sets global, option data and option definitions defaults.
+ ///
+ /// @param global scope to be filled in with defaults.
+ /// @return number of default values added
+ static size_t setAllDefaults(isc::data::ElementPtr global);
+
+ /// @brief Derives (inherits) all parameters from global to more specific scopes.
+ ///
+ /// This method currently does the following:
+ /// - derives global parameters to subnets (lifetimes for now)
+ /// @param global scope to be modified if needed (subnet4 will be extracted)
+ /// @return number of default values derived
+ static size_t deriveParameters(isc::data::ElementPtr global);
+
+ // see simple_parser4.cc for comments for those parameters
+ static const isc::data::SimpleKeywords GLOBAL4_PARAMETERS;
+ static const isc::data::SimpleDefaults GLOBAL4_DEFAULTS;
+
+ static const isc::data::SimpleKeywords OPTION4_DEF_PARAMETERS;
+ static const isc::data::SimpleDefaults OPTION4_DEF_DEFAULTS;
+
+ static const isc::data::SimpleKeywords OPTION4_PARAMETERS;
+ static const isc::data::SimpleDefaults OPTION4_DEFAULTS;
+
+ static const isc::data::SimpleKeywords SUBNET4_PARAMETERS;
+ static const isc::data::SimpleDefaults SUBNET4_DEFAULTS;
+ static const isc::data::SimpleDefaults SHARED_SUBNET4_DEFAULTS;
+ static const isc::data::ParamsList INHERIT_TO_SUBNET4;
+
+ static const isc::data::SimpleKeywords POOL4_PARAMETERS;
+
+ static const isc::data::SimpleKeywords SHARED_NETWORK4_PARAMETERS;
+ static const isc::data::SimpleDefaults SHARED_NETWORK4_DEFAULTS;
+
+ static const isc::data::SimpleDefaults IFACE4_DEFAULTS;
+ static const isc::data::SimpleDefaults DHCP_QUEUE_CONTROL4_DEFAULTS;
+ static const isc::data::SimpleDefaults DHCP_MULTI_THREADING4_DEFAULTS;
+ static const isc::data::SimpleDefaults SANITY_CHECKS4_DEFAULTS;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.cc b/src/lib/dhcpsrv/parsers/simple_parser6.cc
new file mode 100644
index 0000000..f7484dd
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser6.cc
@@ -0,0 +1,564 @@
+// Copyright (C) 2016-2023 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 <cc/data.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
+
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+/// @brief This sets of arrays define the default values and
+/// values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file simple_parser6.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines all global parameters in DHCPv6.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows global_param rule in bison grammar.
+const SimpleKeywords SimpleParser6::GLOBAL6_PARAMETERS = {
+ { "data-directory", Element::string },
+ { "preferred-lifetime", Element::integer },
+ { "min-preferred-lifetime", Element::integer },
+ { "max-preferred-lifetime", Element::integer },
+ { "valid-lifetime", Element::integer },
+ { "min-valid-lifetime", Element::integer },
+ { "max-valid-lifetime", Element::integer },
+ { "renew-timer", Element::integer },
+ { "rebind-timer", Element::integer },
+ { "decline-probation-period", Element::integer },
+ { "subnet6", Element::list },
+ { "shared-networks", Element::list },
+ { "interfaces-config", Element::map },
+ { "lease-database", Element::map },
+ { "hosts-database", Element::map },
+ { "hosts-databases", Element::list },
+ { "mac-sources", Element::list },
+ { "relay-supplied-options", Element::list },
+ { "host-reservation-identifiers", Element::list },
+ { "client-classes", Element::list },
+ { "option-def", Element::list },
+ { "option-data", Element::list },
+ { "hooks-libraries", Element::list },
+ { "expired-leases-processing", Element::map },
+ { "server-id", Element::map },
+ { "dhcp4o6-port", Element::integer },
+ { "control-socket", Element::map },
+ { "dhcp-queue-control", Element::map },
+ { "dhcp-ddns", Element::map },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "sanity-checks", Element::map },
+ { "reservations", Element::list },
+ { "config-control", Element::map },
+ { "server-tag", Element::string },
+ { "reservation-mode", Element::string },
+ { "reservations-global", Element::boolean },
+ { "reservations-in-subnet", Element::boolean },
+ { "reservations-out-of-pool", Element::boolean },
+ { "calculate-tee-times", Element::boolean },
+ { "t1-percent", Element::real },
+ { "t2-percent", Element::real },
+ { "loggers", Element::list },
+ { "hostname-char-set", Element::string },
+ { "hostname-char-replacement", Element::string },
+ { "ddns-send-updates", Element::boolean },
+ { "ddns-override-no-update", Element::boolean },
+ { "ddns-override-client-update", Element::boolean },
+ { "ddns-replace-client-name", Element::string },
+ { "ddns-generated-prefix", Element::string },
+ { "ddns-qualifying-suffix", Element::string },
+ { "store-extended-info", Element::boolean },
+ { "statistic-default-sample-count", Element::integer },
+ { "statistic-default-sample-age", Element::integer },
+ { "multi-threading", Element::map },
+ { "cache-threshold", Element::real },
+ { "cache-max-age", Element::integer },
+ { "early-global-reservations-lookup", Element::boolean },
+ { "ip-reservations-unique", Element::boolean },
+ { "reservations-lookup-first", Element::boolean },
+ { "ddns-update-on-renew", Element::boolean },
+ { "ddns-use-conflict-resolution", Element::boolean },
+ { "compatibility", Element::map },
+ { "parked-packet-limit", Element::integer },
+ { "allocator", Element::string },
+ { "pd-allocator", Element::string },
+ { "ddns-ttl-percent", Element::real },
+};
+
+/// @brief This table defines default global values for DHCPv6
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in Dhcp6) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults SimpleParser6::GLOBAL6_DEFAULTS = {
+ // preferred-lifetime is unspecified and defaults to 0.625 of valid-lifetime.
+ { "valid-lifetime", Element::integer, "7200" },
+ { "decline-probation-period", Element::integer, "86400" }, // 24h
+ { "dhcp4o6-port", Element::integer, "0" },
+ { "server-tag", Element::string, "" },
+ { "reservations-global", Element::boolean, "false" },
+ { "reservations-in-subnet", Element::boolean, "true" },
+ { "reservations-out-of-pool", Element::boolean, "false" },
+ { "calculate-tee-times", Element::boolean, "true" },
+ { "t1-percent", Element::real, ".50" },
+ { "t2-percent", Element::real, ".80" },
+ { "ddns-send-updates", Element::boolean, "true" },
+ { "ddns-override-no-update", Element::boolean, "false" },
+ { "ddns-override-client-update", Element::boolean, "false" },
+ { "ddns-replace-client-name", Element::string, "never" },
+ { "ddns-generated-prefix", Element::string, "myhost" },
+ { "ddns-qualifying-suffix", Element::string, "" },
+ { "hostname-char-set", Element::string, "[^A-Za-z0-9.-]" },
+ { "hostname-char-replacement", Element::string, "" },
+ { "store-extended-info", Element::boolean, "false" },
+ { "statistic-default-sample-count", Element::integer, "20" },
+ { "statistic-default-sample-age", Element::integer, "0" },
+ { "early-global-reservations-lookup", Element::boolean, "false" },
+ { "ip-reservations-unique", Element::boolean, "true" },
+ { "reservations-lookup-first", Element::boolean, "false" },
+ { "ddns-update-on-renew", Element::boolean, "false" },
+ { "ddns-use-conflict-resolution", Element::boolean, "true" },
+ { "parked-packet-limit", Element::integer, "256" },
+ { "allocator", Element::string, "iterative" },
+ { "pd-allocator", Element::string, "iterative" },
+};
+
+/// @brief This table defines all option definition parameters.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows option_def_param rules in bison grammar.
+const SimpleKeywords SimpleParser6::OPTION6_DEF_PARAMETERS = {
+ { "name", Element::string },
+ { "code", Element::integer },
+ { "type", Element::string },
+ { "record-types", Element::string },
+ { "space", Element::string },
+ { "encapsulate", Element::string },
+ { "array", Element::boolean, },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map }
+};
+
+/// @brief This table defines default values for option definitions in DHCPv6.
+///
+/// Dhcp6 may contain an array called option-def that enumerates new option
+/// definitions. This array lists default values for those option definitions.
+const SimpleDefaults SimpleParser6::OPTION6_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp6"}, // DHCP6_OPTION_SPACE
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// @brief This table defines all option parameters.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows option_param rules in bison grammar.
+const SimpleKeywords SimpleParser6::OPTION6_PARAMETERS = {
+ { "name", Element::string },
+ { "data", Element::string },
+ { "code", Element::integer },
+ { "space", Element::string },
+ { "csv-format", Element::boolean },
+ { "always-send", Element::boolean },
+ { "never-send", Element::boolean },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map }
+};
+
+/// @brief This table defines default values for options in DHCPv6.
+///
+/// Dhcp6 usually contains option values (option-data) defined in global,
+/// subnet, class or host reservations scopes. This array lists default values
+/// for those option-data declarations.
+const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = {
+ { "space", Element::string, "dhcp6"}, // DHCP6_OPTION_SPACE
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean, "false"},
+ { "never-send", Element::boolean, "false"}
+};
+
+/// @brief This table defines all subnet parameters for DHCPv6.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows subnet6_param rule in bison grammar.
+const SimpleKeywords SimpleParser6::SUBNET6_PARAMETERS = {
+ { "preferred-lifetime", Element::integer },
+ { "min-preferred-lifetime", Element::integer },
+ { "max-preferred-lifetime", Element::integer },
+ { "valid-lifetime", Element::integer },
+ { "min-valid-lifetime", Element::integer },
+ { "max-valid-lifetime", Element::integer },
+ { "renew-timer", Element::integer },
+ { "rebind-timer", Element::integer },
+ { "option-data", Element::list },
+ { "pools", Element::list },
+ { "pd-pools", Element::list },
+ { "subnet", Element::string },
+ { "interface", Element::string },
+ { "interface-id", Element::string },
+ { "id", Element::integer },
+ { "rapid-commit", Element::boolean },
+ { "client-class", Element::string },
+ { "require-client-classes", Element::list },
+ { "reservations", Element::list },
+ { "reservation-mode", Element::string },
+ { "reservations-global", Element::boolean },
+ { "reservations-in-subnet", Element::boolean },
+ { "reservations-out-of-pool", Element::boolean },
+ { "relay", Element::map },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "calculate-tee-times", Element::boolean },
+ { "t1-percent", Element::real },
+ { "t2-percent", Element::real },
+ { "ddns-send-updates", Element::boolean },
+ { "ddns-override-no-update", Element::boolean },
+ { "ddns-override-client-update", Element::boolean },
+ { "ddns-replace-client-name", Element::string },
+ { "ddns-generated-prefix", Element::string },
+ { "ddns-qualifying-suffix", Element::string },
+ { "hostname-char-set", Element::string },
+ { "hostname-char-replacement", Element::string },
+ { "store-extended-info", Element::boolean },
+ { "metadata", Element::map },
+ { "cache-threshold", Element::real },
+ { "cache-max-age", Element::integer },
+ { "ddns-update-on-renew", Element::boolean },
+ { "ddns-use-conflict-resolution", Element::boolean },
+ { "allocator", Element::string },
+ { "pd-allocator", Element::string },
+ { "ddns-ttl-percent", Element::real },
+};
+
+/// @brief This table defines default values for each IPv6 subnet.
+///
+/// Note: When updating this array, please also update SHARED_SUBNET6_DEFAULTS
+/// below. In most cases, those two should be kept in sync, except cases
+/// where a parameter can be derived from shared-networks, but is not
+/// defined on global level.
+const SimpleDefaults SimpleParser6::SUBNET6_DEFAULTS = {
+ { "id", Element::integer, "0" }, // 0 means autogenerate
+ { "interface", Element::string, "" },
+ { "client-class", Element::string, "" },
+ { "rapid-commit", Element::boolean, "false" }, // rapid-commit disabled by default
+ { "interface-id", Element::string, "" }
+};
+
+/// @brief This table defines default values for each IPv6 subnet that is
+/// part of a shared network
+///
+/// This is mostly the same as @ref SUBNET6_DEFAULTS, except the parameters
+/// that can be derived from shared-network, but cannot from global scope.
+const SimpleDefaults SimpleParser6::SHARED_SUBNET6_DEFAULTS = {
+ { "id", Element::integer, "0" } // 0 means autogenerate
+};
+
+/// @brief This table defines default values for each IPv6 shared network.
+const SimpleDefaults SimpleParser6::SHARED_NETWORK6_DEFAULTS = {
+ { "client-class", Element::string, "" },
+ { "interface", Element::string, "" },
+ { "interface-id", Element::string, "" },
+ { "rapid-commit", Element::boolean, "false" } // rapid-commit disabled by default
+};
+
+/// @brief List of parameters that can be inherited from the global to subnet6 scope.
+///
+/// Some parameters may be defined on both global (directly in Dhcp6) and
+/// subnet (Dhcp6/subnet6/...) scope. If not defined in the subnet scope,
+/// the value is being inherited (derived) from the global scope. This
+/// array lists all of such parameters.
+///
+/// This list is also used for inheriting from global to shared networks
+/// and from shared networks to subnets within it.
+const ParamsList SimpleParser6::INHERIT_TO_SUBNET6 = {
+ "preferred-lifetime",
+ "min-preferred-lifetime",
+ "max-preferred-lifetime",
+ "rebind-timer",
+ "relay",
+ "renew-timer",
+ "valid-lifetime",
+ "min-valid-lifetime",
+ "max-valid-lifetime",
+ "calculate-tee-times",
+ "t1-percent",
+ "t2-percent",
+ "store-extended-info",
+ "cache-threshold",
+ "cache-max-age",
+ "allocator",
+ "pd-allocator"
+};
+
+/// @brief This table defines all pool parameters.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows pool_param rules in bison grammar.
+const SimpleKeywords SimpleParser6::POOL6_PARAMETERS = {
+ { "pool", Element::string },
+ { "pool-id", Element::integer },
+ { "option-data", Element::list },
+ { "client-class", Element::string },
+ { "require-client-classes", Element::list },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map }
+};
+
+/// @brief This table defines all prefix delegation pool parameters.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows pd_pool_param rules in bison grammar.
+const SimpleKeywords SimpleParser6::PD_POOL6_PARAMETERS = {
+ { "prefix", Element::string },
+ { "prefix-len", Element::integer },
+ { "delegated-len", Element::integer },
+ { "pool-id", Element::integer },
+ { "option-data", Element::list },
+ { "client-class", Element::string },
+ { "require-client-classes", Element::list },
+ { "excluded-prefix", Element::string },
+ { "excluded-prefix-len", Element::integer },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "metadata", Element::map }
+};
+
+/// @brief This table defines all shared network parameters for DHCPv6.
+///
+/// Boolean, integer, real and string types are for scalar parameters,
+/// list and map types for entries.
+/// Order follows shared_network_param rule in bison grammar.
+const SimpleKeywords SimpleParser6::SHARED_NETWORK6_PARAMETERS = {
+ { "name", Element::string },
+ { "subnet6", Element::list },
+ { "interface", Element::string },
+ { "interface-id", Element::string },
+ { "renew-timer", Element::integer },
+ { "rebind-timer", Element::integer },
+ { "option-data", Element::list },
+ { "relay", Element::map },
+ { "reservation-mode", Element::string },
+ { "reservations-global", Element::boolean },
+ { "reservations-in-subnet", Element::boolean },
+ { "reservations-out-of-pool", Element::boolean },
+ { "client-class", Element::string },
+ { "require-client-classes", Element::list },
+ { "preferred-lifetime", Element::integer },
+ { "min-preferred-lifetime", Element::integer },
+ { "max-preferred-lifetime", Element::integer },
+ { "rapid-commit", Element::boolean },
+ { "valid-lifetime", Element::integer },
+ { "min-valid-lifetime", Element::integer },
+ { "max-valid-lifetime", Element::integer },
+ { "user-context", Element::map },
+ { "comment", Element::string },
+ { "calculate-tee-times", Element::boolean },
+ { "t1-percent", Element::real },
+ { "t2-percent", Element::real },
+ { "ddns-send-updates", Element::boolean },
+ { "ddns-override-no-update", Element::boolean },
+ { "ddns-override-client-update", Element::boolean },
+ { "ddns-replace-client-name", Element::string },
+ { "ddns-generated-prefix", Element::string },
+ { "ddns-qualifying-suffix", Element::string },
+ { "hostname-char-set", Element::string },
+ { "hostname-char-replacement", Element::string },
+ { "store-extended-info", Element::boolean },
+ { "metadata", Element::map },
+ { "cache-threshold", Element::real },
+ { "cache-max-age", Element::integer },
+ { "ddns-update-on-renew", Element::boolean },
+ { "ddns-use-conflict-resolution", Element::boolean },
+ { "allocator", Element::string },
+ { "pd-allocator", Element::string },
+ { "ddns-ttl-percent", Element::real },
+};
+
+/// @brief This table defines default values for interfaces for DHCPv6.
+const SimpleDefaults SimpleParser6::IFACE6_DEFAULTS = {
+ { "re-detect", Element::boolean, "true" }
+};
+
+/// @brief This table defines default values for dhcp-queue-control in DHCPv6.
+const SimpleDefaults SimpleParser6::DHCP_QUEUE_CONTROL6_DEFAULTS = {
+ { "enable-queue", Element::boolean, "false"},
+ { "queue-type", Element::string, "kea-ring6"},
+ { "capacity", Element::integer, "64"}
+};
+
+/// @brief This table defines default values for multi-threading in DHCPv6.
+const SimpleDefaults SimpleParser6::DHCP_MULTI_THREADING6_DEFAULTS = {
+ { "enable-multi-threading", Element::boolean, "true" },
+ { "thread-pool-size", Element::integer, "0" },
+ { "packet-queue-size", Element::integer, "64" }
+};
+
+/// @brief This defines default values for sanity checking for DHCPv6.
+const SimpleDefaults SimpleParser6::SANITY_CHECKS6_DEFAULTS = {
+ { "lease-checks", Element::string, "warn" }
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t SimpleParser6::setAllDefaults(ElementPtr global) {
+ size_t cnt = 0;
+
+ // Set global defaults first.
+ cnt = setDefaults(global, GLOBAL6_DEFAULTS);
+
+ // Now set the defaults for each specified option definition
+ ConstElementPtr option_defs = global->get("option-def");
+ if (option_defs) {
+ BOOST_FOREACH(ElementPtr option_def, option_defs->listValue()) {
+ cnt += SimpleParser::setDefaults(option_def, OPTION6_DEF_DEFAULTS);
+ }
+ }
+
+ // Set the defaults for option data
+ ConstElementPtr options = global->get("option-data");
+ if (options) {
+ BOOST_FOREACH(ElementPtr single_option, options->listValue()) {
+ cnt += SimpleParser::setDefaults(single_option, OPTION6_DEFAULTS);
+ }
+ }
+
+ // Now set the defaults for defined subnets
+ ConstElementPtr subnets = global->get("subnet6");
+ if (subnets) {
+ cnt += setListDefaults(subnets, SUBNET6_DEFAULTS);
+ }
+
+ // Set the defaults for interfaces config
+ ConstElementPtr ifaces_cfg = global->get("interfaces-config");
+ if (ifaces_cfg) {
+ ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(ifaces_cfg);
+ cnt += setDefaults(mutable_cfg, IFACE6_DEFAULTS);
+ }
+
+ // Set defaults for shared networks
+ ConstElementPtr shared = global->get("shared-networks");
+ if (shared) {
+ BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+
+ cnt += setDefaults(net, SHARED_NETWORK6_DEFAULTS);
+
+ ConstElementPtr subs = net->get("subnet6");
+ if (subs) {
+ cnt += setListDefaults(subs, SHARED_SUBNET6_DEFAULTS);
+ }
+ }
+ }
+
+ // Set the defaults for dhcp-queue-control. If the element isn't there
+ // we'll add it.
+ ConstElementPtr queue_control = global->get("dhcp-queue-control");
+ ElementPtr mutable_cfg;
+ if (queue_control) {
+ mutable_cfg = boost::const_pointer_cast<Element>(queue_control);
+ } else {
+ mutable_cfg = Element::createMap();
+ global->set("dhcp-queue-control", mutable_cfg);
+ }
+
+ cnt += setDefaults(mutable_cfg, DHCP_QUEUE_CONTROL6_DEFAULTS);
+
+ // Set the defaults for multi-threading. If the element isn't there
+ // we'll add it.
+ ConstElementPtr multi_threading = global->get("multi-threading");
+ if (multi_threading) {
+ mutable_cfg = boost::const_pointer_cast<Element>(multi_threading);
+ } else {
+ mutable_cfg = Element::createMap();
+ global->set("multi-threading", mutable_cfg);
+ }
+
+ cnt += setDefaults(mutable_cfg, DHCP_MULTI_THREADING6_DEFAULTS);
+
+ // Set the defaults for sanity-checks. If the element isn't
+ // there we'll add it.
+ ConstElementPtr sanity_checks = global->get("sanity-checks");
+ if (sanity_checks) {
+ mutable_cfg = boost::const_pointer_cast<Element>(sanity_checks);
+ } else {
+ mutable_cfg = Element::createMap();
+ global->set("sanity-checks", mutable_cfg);
+ }
+
+ cnt += setDefaults(mutable_cfg, SANITY_CHECKS6_DEFAULTS);
+
+ return (cnt);
+}
+
+size_t SimpleParser6::deriveParameters(ElementPtr global) {
+ size_t cnt = 0;
+
+ // Now derive global parameters into subnets.
+ ConstElementPtr subnets = global->get("subnet6");
+ if (subnets) {
+ BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+ cnt += SimpleParser::deriveParams(global, single_subnet,
+ INHERIT_TO_SUBNET6);
+ }
+ }
+
+ // Deriving parameters for shared networks is a bit more involved.
+ // First, the shared-network level derives from global, and then
+ // subnets within derive from it.
+ ConstElementPtr shared = global->get("shared-networks");
+ if (shared) {
+ BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+ // First try to inherit the parameters from shared network,
+ // if defined there.
+ // Then try to inherit them from global.
+ cnt += SimpleParser::deriveParams(global, net,
+ INHERIT_TO_SUBNET6);
+
+ // Now we need to go thrugh all the subnets in this net.
+ subnets = net->get("subnet6");
+ if (subnets) {
+ BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+ cnt += SimpleParser::deriveParams(net, single_subnet,
+ INHERIT_TO_SUBNET6);
+ }
+ }
+ }
+ }
+
+ return (cnt);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/parsers/simple_parser6.h b/src/lib/dhcpsrv/parsers/simple_parser6.h
new file mode 100644
index 0000000..8eae3bf
--- /dev/null
+++ b/src/lib/dhcpsrv/parsers/simple_parser6.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2016-2020 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 SIMPLE_PARSER6_H
+#define SIMPLE_PARSER6_H
+
+#include <cc/simple_parser.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief SimpleParser specialized for DHCPv6
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to DHCPv6 parser.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file simple_parser6.cc
+class SimpleParser6 : public isc::data::SimpleParser {
+public:
+
+ /// @brief Sets all defaults for DHCPv6 configuration
+ ///
+ /// This method sets global, option data and option definitions defaults.
+ ///
+ /// @param global scope to be filled in with defaults.
+ /// @return number of default values added
+ static size_t setAllDefaults(isc::data::ElementPtr global);
+
+ /// @brief Derives (inherits) all parameters from global to more specific scopes.
+ ///
+ /// This method currently does the following:
+ /// - derives global parameters to subnets (lifetimes for now)
+ /// @param global scope to be modified if needed (subnet6 will be extracted)
+ /// @return number of default values derived
+ static size_t deriveParameters(isc::data::ElementPtr global);
+
+ // see simple_parser6.cc for comments for those parameters
+ static const isc::data::SimpleKeywords GLOBAL6_PARAMETERS;
+ static const isc::data::SimpleDefaults GLOBAL6_DEFAULTS;
+
+ static const isc::data::SimpleKeywords OPTION6_DEF_PARAMETERS;
+ static const isc::data::SimpleDefaults OPTION6_DEF_DEFAULTS;
+
+ static const isc::data::SimpleKeywords OPTION6_PARAMETERS;
+ static const isc::data::SimpleDefaults OPTION6_DEFAULTS;
+
+ static const isc::data::SimpleKeywords SUBNET6_PARAMETERS;
+ static const isc::data::SimpleDefaults SUBNET6_DEFAULTS;
+ static const isc::data::SimpleDefaults SHARED_SUBNET6_DEFAULTS;
+ static const isc::data::ParamsList INHERIT_TO_SUBNET6;
+
+ static const isc::data::SimpleKeywords POOL6_PARAMETERS;
+ static const isc::data::SimpleKeywords PD_POOL6_PARAMETERS;
+
+ static const isc::data::SimpleKeywords SHARED_NETWORK6_PARAMETERS;
+ static const isc::data::SimpleDefaults SHARED_NETWORK6_DEFAULTS;
+
+ static const isc::data::SimpleDefaults IFACE6_DEFAULTS;
+ static const isc::data::SimpleDefaults DHCP_QUEUE_CONTROL6_DEFAULTS;
+ static const isc::data::SimpleDefaults DHCP_MULTI_THREADING6_DEFAULTS;
+ static const isc::data::SimpleDefaults SANITY_CHECKS6_DEFAULTS;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.cc b/src/lib/dhcpsrv/pgsql_host_data_source.cc
new file mode 100644
index 0000000..b9cea84
--- /dev/null
+++ b/src/lib/dhcpsrv/pgsql_host_data_source.cc
@@ -0,0 +1,3339 @@
+// Copyright (C) 2016-2023 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 <asiolink/io_service.h>
+#include <database/db_exceptions.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/pgsql_host_data_source.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <util/buffer.h>
+#include <util/multi_threading_mgr.h>
+#include <util/optional.h>
+
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/array.hpp>
+#include <boost/pointer_cast.hpp>
+#include <boost/static_assert.hpp>
+
+#include <stdint.h>
+
+#include <mutex>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+/// @brief Maximum length of option value.
+/// The maximum size of the raw option data that may be read from the
+/// database.
+const size_t OPTION_VALUE_MAX_LEN = 4096;
+
+/// @brief Numeric value representing last supported identifier.
+///
+/// This value is used to validate whether the identifier type stored in
+/// a database is within bounds. of supported identifiers.
+const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
+
+/// @brief Maximum length of DHCP identifier value.
+const size_t DHCP_IDENTIFIER_MAX_LEN = ClientId::MAX_CLIENT_ID_LEN;
+
+/// @brief This class provides mechanisms for sending and retrieving
+/// information from the 'hosts' table.
+///
+/// This class is used to insert and retrieve entries from the 'hosts' table.
+/// The queries used with this class do not retrieve IPv6 reservations or
+/// options associated with a host to minimize impact on performance. Other
+/// classes derived from @ref PgSqlHostExchange should be used to retrieve
+/// information about IPv6 reservations and options.
+///
+/// Database schema contains several unique indexes to guard against adding
+/// multiple hosts for the same client identifier in a single subnet and for
+/// adding multiple hosts with a reservation for the same IPv4 address in a
+/// single subnet. The exceptions that have to be taken into account are
+/// listed below:
+/// - zero or null IPv4 address indicates that there is no reservation for the
+/// IPv4 address for the host,
+/// - null subnet identifier (either IPv4 or IPv6) indicates that
+/// this subnet identifier must be ignored. Specifically, this is the case
+/// when host reservation is created for the DHCPv4 server, the IPv6 subnet id
+/// should be ignored. Conversely, when host reservation is created for the
+/// DHCPv6 server, the IPv4 subnet id should be ignored.
+/// NOTE! Zero is the "global" subnet id as Kea 1.5.0
+///
+/// To exclude those special case values, the Postgres backend uses partial
+/// indexes, i.e. the only values that are included in the index are those that
+/// are non-zero and non-null.
+class PgSqlHostExchange : public PgSqlExchange {
+private:
+
+ /// @brief Column numbers for each column in the hosts table.
+ /// These are used for both retrieving data and for looking up
+ /// column labels for logging. Note that their numeric order
+ /// MUST match that of the column order in the hosts table.
+ static const size_t HOST_ID_COL = 0;
+ static const size_t DHCP_IDENTIFIER_COL = 1;
+ static const size_t DHCP_IDENTIFIER_TYPE_COL = 2;
+ static const size_t DHCP4_SUBNET_ID_COL = 3;
+ static const size_t DHCP6_SUBNET_ID_COL = 4;
+ static const size_t IPV4_ADDRESS_COL = 5;
+ static const size_t HOSTNAME_COL = 6;
+ static const size_t DHCP4_CLIENT_CLASSES_COL = 7;
+ static const size_t DHCP6_CLIENT_CLASSES_COL = 8;
+ static const size_t USER_CONTEXT_COL = 9;
+ static const size_t DHCP4_NEXT_SERVER_COL = 10;
+ static const size_t DHCP4_SERVER_HOSTNAME_COL = 11;
+ static const size_t DHCP4_BOOT_FILE_NAME_COL = 12;
+ static const size_t AUTH_KEY_COL = 13;
+ /// @brief Number of columns returned for SELECT queries sent by this class.
+ static const size_t HOST_COLUMNS = 14;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param additional_columns_num This value is set by the derived classes
+ /// to indicate how many additional columns will be returned by SELECT
+ /// queries performed by the derived class. This constructor will allocate
+ /// resources for these columns, e.g. binding table, error indicators.
+ PgSqlHostExchange(const size_t additional_columns_num = 0)
+ : PgSqlExchange(HOST_COLUMNS + additional_columns_num) {
+ // Set the column names for use by this class. This only comprises
+ // names used by the PgSqlHostExchange class. Derived classes will
+ // need to set names for the columns they use. Currently these are
+ // only used for logging purposes.
+ columns_[HOST_ID_COL] = "host_id";
+ columns_[DHCP_IDENTIFIER_COL] = "dhcp_identifier";
+ columns_[DHCP_IDENTIFIER_TYPE_COL] = "dhcp_identifier_type";
+ columns_[DHCP4_SUBNET_ID_COL] = "dhcp4_subnet_id";
+ columns_[DHCP6_SUBNET_ID_COL] = "dhcp6_subnet_id";
+ columns_[IPV4_ADDRESS_COL] = "ipv4_address";
+ columns_[HOSTNAME_COL] = "hostname";
+ columns_[DHCP4_CLIENT_CLASSES_COL] = "dhcp4_client_classes";
+ columns_[DHCP6_CLIENT_CLASSES_COL] = "dhcp6_client_classes";
+ columns_[USER_CONTEXT_COL] = "user_context";
+ columns_[DHCP4_NEXT_SERVER_COL] = "dhcp4_next_server";
+ columns_[DHCP4_SERVER_HOSTNAME_COL] = "dhcp4_server_hostname";
+ columns_[DHCP4_BOOT_FILE_NAME_COL] = "dhcp4_boot_file_name";
+ columns_[AUTH_KEY_COL] = "auth_key";
+
+ BOOST_STATIC_ASSERT(12 < HOST_COLUMNS);
+ };
+
+ /// @brief Virtual destructor.
+ virtual ~PgSqlHostExchange() {
+ }
+
+ /// @brief Reinitializes state information
+ ///
+ /// This function should be called in between statement executions.
+ /// Deriving classes should invoke this method as well as be reset
+ /// all of their own stateful values.
+ virtual void clear() {
+ host_.reset();
+ };
+
+ /// @brief Returns index of the first uninitialized column name.
+ ///
+ /// This method is called by the derived classes to determine which
+ /// column indexes are available for the derived classes within a
+ /// binding array, error array and column names. This method
+ /// determines the first available index by searching the first
+ /// empty value within the columns_ vector. Previously we relied on
+ /// the fixed values set for each class, but this was hard to maintain
+ /// when new columns were added to the SELECT queries. It required
+ /// modifying indexes in all derived classes.
+ ///
+ /// Derived classes must call this method in their constructors and
+ /// use returned value as an index for the first column used by the
+ /// derived class and increment this value for each subsequent column.
+ size_t findAvailColumn() const {
+ std::vector<std::string>::const_iterator empty_column =
+ std::find(columns_.begin(), columns_.end(), std::string());
+ return (std::distance(columns_.begin(), empty_column));
+ }
+
+ /// @brief Returns value of host id in the given row.
+ ///
+ /// This method is used to "look ahead" at the host_id in a row
+ /// without having to call retrieveHost()
+ HostID getHostId(const PgSqlResult& r, int row) {
+ HostID host_id;
+ getColumnValue(r, row, HOST_ID_COL, host_id);
+ return (host_id);
+ }
+
+ /// @brief Populate a bind array from a host
+ ///
+ /// Constructs a PsqlBindArray for sending data stored in a Host object
+ /// to the database.
+ ///
+ /// @param host Host object to be added to the database.
+ /// None of the fields in the host reservation are modified -
+ /// the host data is only read.
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
+ ///
+ /// @return pointer to newly constructed bind_array containing the
+ /// bound values extracted from host
+ ///
+ /// @throw DbOperationError if bind_array cannot be populated.
+ PsqlBindArrayPtr createBindForSend(const HostPtr& host, const bool unique_ip) {
+ if (!host) {
+ isc_throw(BadValue, "createBindForSend:: host object is NULL");
+ }
+
+ // Store the host to ensure bound values remain in scope
+ host_ = host;
+
+ // Bind the host data to the array
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+ try {
+ // host_id : is auto_incremented skip it
+
+ // dhcp_identifier : BYTEA NOT NULL
+ bind_array->add(host->getIdentifier());
+
+ // dhcp_identifier_type : SMALLINT NOT NULL
+ bind_array->add(host->getIdentifierType());
+
+ // dhcp4_subnet_id : INT NULL
+ if (host->getIPv4SubnetID() == SUBNET_ID_UNUSED) {
+ bind_array->addNull();
+ } else {
+ bind_array->add(host->getIPv4SubnetID());
+ }
+
+ // dhcp6_subnet_id : INT NULL
+ if (host->getIPv6SubnetID() == SUBNET_ID_UNUSED) {
+ bind_array->addNull();
+ } else {
+ bind_array->add(host->getIPv6SubnetID());
+ }
+
+ // ipv4_address : BIGINT NULL
+ bind_array->add((host->getIPv4Reservation()));
+
+ // hostname : VARCHAR(255) NULL
+ bind_array->add(host->getHostname());
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ // Override default separator to not include space after comma.
+ bind_array->addTempString(host->getClientClasses4().toText(","));
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ bind_array->addTempString(host->getClientClasses6().toText(","));
+
+ // user_context: TEXT NULL
+ ConstElementPtr ctx = host->getContext();
+ if (ctx) {
+ std::string user_context_ = ctx->str();
+ bind_array->addTempString(user_context_);
+ } else {
+ bind_array->addNull();
+ }
+
+ // dhcp4_next_server : BIGINT NULL
+ bind_array->add((host->getNextServer()));
+
+ // dhcp4_server_hostname : VARCHAR(64)
+ bind_array->add(host->getServerHostname());
+
+ // dhcp4_boot_file_name : VARCHAR(128)
+ bind_array->add(host->getBootFileName());
+
+ // add auth keys
+ std::string key = host->getKey().toText();
+ if (key.empty()) {
+ bind_array->addNull();
+ } else {
+ bind_array->addTempString(key);
+ }
+
+ // When checking whether the IP is unique we need to bind the IPv4 address
+ // at the end of the query as it has additional binding for the IPv4
+ // address.
+ if (unique_ip) {
+ bind_array->add(host->getIPv4Reservation()); // ipv4_address
+ bind_array->add(host->getIPv4SubnetID()); // subnet_id
+ }
+
+ } catch (const std::exception& ex) {
+ host_.reset();
+ isc_throw(DbOperationError,
+ "Could not create bind array from Host: "
+ << host->getHostname() << ", reason: " << ex.what());
+ }
+
+ return (bind_array);
+ };
+
+ /// @brief Processes one row of data fetched from a database.
+ ///
+ /// The processed data must contain host id, which uniquely identifies a
+ /// host. This method creates a host and inserts it to the hosts collection
+ /// only if the last inserted host has a different host id. This prevents
+ /// adding duplicated hosts to the collection, assuming that processed
+ /// rows are primarily ordered by host id column.
+ ///
+ /// This method must be overridden in the derived classes to also
+ /// retrieve IPv6 reservations and DHCP options associated with a host.
+ ///
+ /// @param [out] hosts Collection of hosts to which a new host created
+ /// from the processed data should be inserted.
+ virtual void processRowData(ConstHostCollection& hosts,
+ const PgSqlResult& r, int row) {
+ // Peek at the host id , so we can skip it if we already have it
+ // This lets us avoid constructing a copy of host for each
+ // of its sub-rows (options, etc...)
+ HostID row_host_id = getHostId(r, row);
+
+ // Add new host only if there are no hosts or the host id of the
+ // most recently added host is different than the host id of the
+ // currently processed host.
+ if (hosts.empty() || row_host_id != hosts.back()->getHostId()) {
+ HostPtr host = retrieveHost(r, row, row_host_id);
+ hosts.push_back(host);
+ }
+ }
+
+ /// @brief Creates a Host object from a given row in a result set.
+ ///
+ /// @param r result set containing one or more rows from the hosts table
+ /// @param row index within the result set of the row to process
+ /// @param peeked_host_id if the caller has peeked ahead at the row's
+ /// host_id, it can be passed in here to avoid fetching it from the row
+ /// a second time.
+ ///
+ /// @return HostPtr to the newly created Host object
+ ///
+ /// @throw DbOperationError if the host cannot be created.
+ HostPtr retrieveHost(const PgSqlResult& r, int row,
+ const HostID& peeked_host_id = 0) {
+
+ // If the caller peeked ahead at the host_id use that, otherwise
+ // read it from the row.
+ HostID host_id = (peeked_host_id ? peeked_host_id : getHostId(r,row));
+
+ // dhcp_identifier : BYTEA NOT NULL
+ uint8_t identifier_value[DHCP_IDENTIFIER_MAX_LEN];
+ size_t identifier_len;
+ convertFromBytea(r, row, DHCP_IDENTIFIER_COL, identifier_value,
+ sizeof(identifier_value), identifier_len);
+
+ // dhcp_identifier_type : SMALLINT NOT NULL
+ uint8_t type;
+ getColumnValue(r, row, DHCP_IDENTIFIER_TYPE_COL, type);
+ if (type > MAX_IDENTIFIER_TYPE) {
+ isc_throw(BadValue, "invalid dhcp identifier type returned: "
+ << static_cast<int>(type));
+ }
+
+ Host::IdentifierType identifier_type =
+ static_cast<Host::IdentifierType>(type);
+
+ // dhcp4_subnet_id : INT NULL
+ uint32_t subnet_id(SUBNET_ID_UNUSED);
+ if (!isColumnNull(r, row, DHCP4_SUBNET_ID_COL)) {
+ getColumnValue(r, row, DHCP4_SUBNET_ID_COL, subnet_id);
+ }
+ SubnetID dhcp4_subnet_id = static_cast<SubnetID>(subnet_id);
+
+ // dhcp6_subnet_id : INT NULL
+ subnet_id = SUBNET_ID_UNUSED;
+ if (!isColumnNull(r, row, DHCP6_SUBNET_ID_COL)) {
+ getColumnValue(r, row, DHCP6_SUBNET_ID_COL, subnet_id);
+ }
+ SubnetID dhcp6_subnet_id = static_cast<SubnetID>(subnet_id);
+
+ // ipv4_address : BIGINT NULL
+ uint32_t addr4(0);
+ if (!isColumnNull(r, row, IPV4_ADDRESS_COL)) {
+ getColumnValue(r, row, IPV4_ADDRESS_COL, addr4);
+ }
+ isc::asiolink::IOAddress ipv4_reservation(addr4);
+
+ // hostname : VARCHAR(255) NULL
+ std::string hostname;
+ if (!isColumnNull(r, row, HOSTNAME_COL)) {
+ getColumnValue(r, row, HOSTNAME_COL, hostname);
+ }
+
+ // dhcp4_client_classes : VARCHAR(255) NULL
+ std::string dhcp4_client_classes;
+ if (!isColumnNull(r, row, DHCP4_CLIENT_CLASSES_COL)) {
+ getColumnValue(r, row, DHCP4_CLIENT_CLASSES_COL, dhcp4_client_classes);
+ }
+
+ // dhcp6_client_classes : VARCHAR(255) NULL
+ std::string dhcp6_client_classes;
+ if (!isColumnNull(r, row, DHCP6_CLIENT_CLASSES_COL)) {
+ getColumnValue(r, row, DHCP6_CLIENT_CLASSES_COL, dhcp6_client_classes);
+ }
+
+ // user_context: TEXT
+ std::string user_context;
+ if (!isColumnNull(r, row, USER_CONTEXT_COL)) {
+ getColumnValue(r, row, USER_CONTEXT_COL, user_context);
+ }
+
+ // dhcp4_next_server : BIGINT NULL
+ uint32_t dhcp4_next_server_as_uint32(0);
+ if (!isColumnNull(r, row, DHCP4_NEXT_SERVER_COL)) {
+ getColumnValue(r, row, DHCP4_NEXT_SERVER_COL, dhcp4_next_server_as_uint32);
+ }
+ isc::asiolink::IOAddress dhcp4_next_server(dhcp4_next_server_as_uint32);
+
+ // dhcp4_server_hostname : VARCHAR(64)
+ std::string dhcp4_server_hostname;
+ if (!isColumnNull(r, row, DHCP4_SERVER_HOSTNAME_COL)) {
+ getColumnValue(r, row, DHCP4_SERVER_HOSTNAME_COL, dhcp4_server_hostname);
+ }
+
+ // dhcp4_boot_file_name : VARCHAR(128)
+ std::string dhcp4_boot_file_name;
+ if (!isColumnNull(r, row, DHCP4_BOOT_FILE_NAME_COL)) {
+ getColumnValue(r, row, DHCP4_BOOT_FILE_NAME_COL, dhcp4_boot_file_name);
+ }
+
+ // auth_key : VARCHAR(16)
+ std::string auth_key;
+ if (!isColumnNull(r, row, AUTH_KEY_COL)) {
+ getColumnValue(r, row, AUTH_KEY_COL, auth_key);
+ }
+
+ // Finally, attempt to create the new host.
+ HostPtr host;
+ try {
+ host.reset(new Host(identifier_value, identifier_len,
+ identifier_type, dhcp4_subnet_id,
+ dhcp6_subnet_id, ipv4_reservation, hostname,
+ dhcp4_client_classes, dhcp6_client_classes,
+ dhcp4_next_server, dhcp4_server_hostname,
+ dhcp4_boot_file_name, AuthKey(auth_key)));
+
+ // Set the user context if there is one.
+ if (!user_context.empty()) {
+ try {
+ ConstElementPtr ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is not a JSON map");
+ }
+ host->setContext(ctx);
+ } catch (const isc::data::JSONError& ex) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is invalid JSON: " << ex.what());
+ }
+ }
+
+ host->setHostId(host_id);
+ } catch (const isc::Exception& ex) {
+ isc_throw(DbOperationError, "Could not create host: " << ex.what());
+ }
+
+ return(host);
+ };
+
+protected:
+ /// Pointer to Host object holding information to be inserted into
+ /// Hosts table. This is used to retain scope.
+ HostPtr host_;
+};
+
+/// @brief Extends base exchange class with ability to retrieve DHCP options
+/// from the 'dhcp4_options' and 'dhcp6_options' tables.
+///
+/// This class provides means to retrieve both DHCPv4 and DHCPv6 options
+/// along with the host information. It is not used to retrieve IPv6
+/// reservations. The following types of queries are supported:
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options ...
+class PgSqlHostWithOptionsExchange : public PgSqlHostExchange {
+private:
+
+ /// @brief Number of columns holding DHCPv4 or DHCPv6 option information.
+ static const size_t OPTION_COLUMNS = 8;
+
+ /// @brief Receives DHCPv4 or DHCPv6 options information from the
+ /// dhcp4_options or dhcp6_options tables respectively.
+ ///
+ /// The PgSqlHostWithOptionsExchange class holds two respective instances
+ /// of this class, one for receiving DHCPv4 options, one for receiving
+ /// DHCPv6 options.
+ ///
+ /// The following are the basic functions of this class:
+ /// - bind class members to specific columns in PgSQL binding tables,
+ /// - set DHCP options specific column names,
+ /// - create instances of options retrieved from the database.
+ ///
+ /// The reason for isolating those functions in a separate C++ class is
+ /// to prevent code duplication for handling DHCPv4 and DHCPv6 options.
+ class OptionProcessor {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param universe V4 or V6. The type of the options' instances
+ /// created by this class depends on this parameter.
+ /// @param start_column Index of the first column to be used by this
+ /// class.
+ OptionProcessor(const Option::Universe& universe,
+ const size_t start_column)
+ : universe_(universe), start_column_(start_column),
+ option_id_index_(start_column), code_index_(start_column_ + 1),
+ value_index_(start_column_ + 2),
+ formatted_value_index_(start_column_ + 3),
+ space_index_(start_column_ + 4),
+ persistent_index_(start_column_ + 5),
+ cancelled_index_(start_column_ + 6),
+ user_context_index_(start_column_ + 7),
+ most_recent_option_id_(0) {
+ }
+
+ /// @brief Reinitializes state information
+ ///
+ /// This function should be called prior to processing a fetched
+ /// set of options.
+ void clear() {
+ most_recent_option_id_ = 0;
+ }
+
+ /// @brief Creates instance of the currently processed option.
+ ///
+ /// This method detects if the currently processed option is a new
+ /// instance. It makes its determination by comparing the identifier
+ /// of the currently processed option, with the most recently processed
+ /// option. If the current value is greater than the id of the recently
+ /// processed option it is assumed that the processed row holds new
+ /// option information. In such case the option instance is created and
+ /// inserted into the configuration passed as argument.
+ ///
+ /// This logic is necessary to deal with result sets made from multiple
+ /// left joins which contain duplicated data. For instance queries
+ /// returning both v4 and v6 options for a host would generate result
+ /// sets similar to this:
+ /// @code
+ ///
+ /// row 0: host-1 v4-opt-1 v6-opt-1
+ /// row 1: host-1 v4-opt-1 v6-opt-2
+ /// row 2: host-1 v4-opt-1 v6-opt-3
+ /// row 4: host-1 v4-opt-2 v6-opt-1
+ /// row 5: host-1 v4-opt-2 v6-opt-2
+ /// row 6: host-1 v4-opt-2 v6-opt-3
+ /// row 7: host-2 v4-opt-1 v6-opt-1
+ /// row 8: host-2 v4-opt-2 v6-opt-1
+ /// :
+ /// @endcode
+ ///
+ /// @param cfg Pointer to the configuration object into which new
+ /// option instances should be inserted.
+ /// @param r result set containing one or more rows from a dhcp
+ /// options table.
+ /// @param row index within the result set of the row to process
+ void retrieveOption(const CfgOptionPtr& cfg, const PgSqlResult& r,
+ int row) {
+ // If the option id on this row is NULL, then there's no
+ // option of this type (4/6) on this row to fetch, so bail.
+ if (PgSqlExchange::isColumnNull(r, row, option_id_index_)) {
+ return;
+ }
+
+ // option_id: INT
+ uint64_t option_id;
+ PgSqlExchange::getColumnValue(r, row, option_id_index_, option_id);
+
+ // The row option id must be greater than id if the most recent
+ // option because they are ordered by option id. Otherwise
+ // we assume that we have already processed this option.
+ if (most_recent_option_id_ >= option_id) {
+ return;
+ }
+
+ // Remember current option id as the most recent processed one. We
+ // will be comparing it with option ids in subsequent rows.
+ most_recent_option_id_ = option_id;
+
+ // code: SMALLINT NOT NULL
+ uint16_t code;
+ PgSqlExchange::getColumnValue(r, row, code_index_, code);
+
+ // value: BYTEA
+ uint8_t value[OPTION_VALUE_MAX_LEN];
+ size_t value_len(0);
+ if (!isColumnNull(r, row, value_index_)) {
+ PgSqlExchange::convertFromBytea(r, row, value_index_, value,
+ sizeof(value), value_len);
+ }
+
+ // formatted_value: TEXT
+ std::string formatted_value;
+ if (!isColumnNull(r, row, formatted_value_index_)) {
+ PgSqlExchange::getColumnValue(r, row, formatted_value_index_,
+ formatted_value);
+ }
+
+ // space: VARCHAR(128)
+ std::string space;
+ if (!isColumnNull(r, row, space_index_)) {
+ PgSqlExchange::getColumnValue(r, row, space_index_, space);
+ }
+
+ // If empty or null space provided, use a default top level space.
+ if (space.empty()) {
+ space = (universe_ == Option::V4 ?
+ DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE);
+ }
+
+ // persistent: BOOL default false
+ bool persistent;
+ PgSqlExchange::getColumnValue(r, row, persistent_index_,
+ persistent);
+
+ // cancelled: BOOL default false
+ bool cancelled;
+ PgSqlExchange::getColumnValue(r, row, cancelled_index_,
+ cancelled);
+
+ // user_context: TEXT
+ std::string user_context;
+ if (!isColumnNull(r, row, user_context_index_)) {
+ PgSqlExchange::getColumnValue(r, row, user_context_index_,
+ user_context);
+ }
+
+ // Options are held in a binary or textual format in the database.
+ // This is similar to having an option specified in a server
+ // configuration file. Such option is converted to appropriate C++
+ // class, using option definition. Thus, we need to find the
+ // option definition for this option code and option space.
+
+ // If the option space is a standard DHCPv4 or DHCPv6 option space,
+ // this is most likely a standard option, for which we have a
+ // definition created within libdhcp++.
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(space, code);
+
+ // Otherwise, we may check if this an option encapsulated within the
+ // vendor space.
+ if (!def && (space != DHCP4_OPTION_SPACE) &&
+ (space != DHCP6_OPTION_SPACE)) {
+ uint32_t vendor_id = LibDHCP::optionSpaceToVendorId(space);
+ if (vendor_id > 0) {
+ def = LibDHCP::getVendorOptionDef(universe_, vendor_id,
+ code);
+ }
+ }
+
+ // In all other cases, we use runtime option definitions, which
+ // should be also registered within the libdhcp++.
+ if (!def) {
+ def = LibDHCP::getRuntimeOptionDef(space, code);
+ }
+
+ // Finish with a last resort option definition.
+ if (!def) {
+ def = LibDHCP::getLastResortOptionDef(space, code);
+ }
+
+ OptionPtr option;
+
+ if (!def) {
+ // If no definition found, we use generic option type.
+ OptionBuffer buf(value, value + value_len);
+ option.reset(new Option(universe_, code, buf.begin(),
+ buf.end()));
+ } else {
+ // The option value may be specified in textual or binary format
+ // in the database. If formatted_value is empty, the binary
+ // format is used. Depending on the format we use a different
+ // variant of the optionFactory function.
+ if (formatted_value.empty()) {
+ OptionBuffer buf(value, value + value_len);
+ option = def->optionFactory(universe_, code, buf.begin(),
+ buf.end());
+ } else {
+ // Spit the value specified in comma separated values
+ // format.
+ std::vector<std::string> split_vec;
+ boost::split(split_vec, formatted_value,
+ boost::is_any_of(","));
+ option = def->optionFactory(universe_, code, split_vec);
+ }
+ }
+
+ OptionDescriptor desc(option, persistent, cancelled,
+ formatted_value);
+
+ // Set the user context if there is one into the option descriptor.
+ if (!user_context.empty()) {
+ try {
+ ConstElementPtr ctx = Element::fromJSON(user_context);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is no a JSON map");
+ }
+ desc.setContext(ctx);
+ } catch (const isc::data::JSONError& ex) {
+ isc_throw(BadValue, "user context '" << user_context
+ << "' is invalid JSON: " << ex.what());
+ }
+ }
+
+ cfg->add(desc, space);
+ }
+
+ /// @brief Specify column names.
+ ///
+ /// @param [out] columns Reference to a vector holding names of option
+ /// specific columns.
+ void setColumnNames(std::vector<std::string>& columns) {
+ columns[option_id_index_] = "option_id";
+ columns[code_index_] = "code";
+ columns[value_index_] = "value";
+ columns[formatted_value_index_] = "formatted_value";
+ columns[space_index_] = "space";
+ columns[persistent_index_] = "persistent";
+ columns[cancelled_index_] = "cancelled";
+ columns[user_context_index_] = "user_context";
+ }
+
+ private:
+ /// @brief Universe: V4 or V6.
+ Option::Universe universe_;
+
+ /// @brief Index of first column used by this class.
+ size_t start_column_;
+
+ //@}
+
+ /// @name Indexes of the specific columns
+ //@{
+ /// @brief Option id
+ size_t option_id_index_;
+
+ /// @brief Code
+ size_t code_index_;
+
+ /// @brief Value
+ size_t value_index_;
+
+ /// @brief Formatted value
+ size_t formatted_value_index_;
+
+ /// @brief Space
+ size_t space_index_;
+
+ /// @brief Persistent
+ size_t persistent_index_;
+
+ /// @brief Cancelled
+ size_t cancelled_index_;
+ //@}
+
+ /// @brief User context
+ size_t user_context_index_;
+
+ /// @brief Option id for last processed row.
+ uint64_t most_recent_option_id_;
+ };
+
+ /// @brief Pointer to the @ref OptionProcessor class.
+ typedef boost::shared_ptr<OptionProcessor> OptionProcessorPtr;
+
+public:
+
+ /// @brief DHCP option types to be fetched from the database.
+ ///
+ /// Supported types are:
+ /// - Only DHCPv4 options,
+ /// - Only DHCPv6 options,
+ /// - Both DHCPv4 and DHCPv6 options.
+ enum FetchedOptions {
+ DHCP4_ONLY,
+ DHCP6_ONLY,
+ DHCP4_AND_DHCP6
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param fetched_options Specifies if DHCPv4, DHCPv6 or both should
+ /// be fetched from the database for a host.
+ /// @param additional_columns_num Number of additional columns for which
+ /// resources should be allocated, e.g. binding table, column names etc.
+ /// This parameter should be set to a non zero value by derived classes to
+ /// allocate resources for the columns supported by derived classes.
+ PgSqlHostWithOptionsExchange(const FetchedOptions& fetched_options,
+ const size_t additional_columns_num = 0)
+ : PgSqlHostExchange(getRequiredColumnsNum(fetched_options)
+ + additional_columns_num),
+ opt_proc4_(), opt_proc6_() {
+
+ // Create option processor for DHCPv4 options, if required.
+ if ((fetched_options == DHCP4_ONLY) ||
+ (fetched_options == DHCP4_AND_DHCP6)) {
+ opt_proc4_.reset(new OptionProcessor(Option::V4,
+ findAvailColumn()));
+ opt_proc4_->setColumnNames(columns_);
+ }
+
+ // Create option processor for DHCPv6 options, if required.
+ if ((fetched_options == DHCP6_ONLY) ||
+ (fetched_options == DHCP4_AND_DHCP6)) {
+ opt_proc6_.reset(new OptionProcessor(Option::V6,
+ findAvailColumn()));
+ opt_proc6_->setColumnNames(columns_);
+ }
+ }
+
+ /// @brief Clears state information
+ ///
+ /// This function should be called in between statement executions.
+ /// Deriving classes should invoke this method as well as be reset
+ /// all of their own stateful values.
+ virtual void clear() {
+ PgSqlHostExchange::clear();
+ if (opt_proc4_) {
+ opt_proc4_->clear();
+ }
+
+ if (opt_proc6_) {
+ opt_proc6_->clear();
+ }
+ }
+
+ /// @brief Processes the current row.
+ ///
+ /// The fetched row includes both host information and DHCP option
+ /// information. Because the SELECT queries use one or more LEFT JOIN
+ /// clauses, the result set may contain duplicated host or options
+ /// entries. This method detects duplicated information and discards such
+ /// entries.
+ ///
+ /// @param [out] hosts Container holding parsed hosts and options.
+ virtual void processRowData(ConstHostCollection& hosts,
+ const PgSqlResult& r, int row) {
+ HostPtr current_host;
+ if (hosts.empty()) {
+ // Must be the first one, fetch it.
+ current_host = retrieveHost(r, row);
+ hosts.push_back(current_host);
+ } else {
+ // Peek at the host id so we can skip it if we already have
+ // this host. This lets us avoid retrieving the host needlessly
+ // for each of its sub-rows (options, etc...).
+ HostID row_host_id = getHostId(r, row);
+ current_host = boost::const_pointer_cast<Host>(hosts.back());
+
+ // if the row's host id is greater than the one we've been
+ // working on we're starting a new host, so fetch it.
+ if (row_host_id > current_host->getHostId()) {
+ current_host = retrieveHost(r, row, row_host_id);
+ hosts.push_back(current_host);
+ }
+ }
+
+ // Parse DHCPv4 options if required to do so.
+ if (opt_proc4_) {
+ CfgOptionPtr cfg = current_host->getCfgOption4();
+ opt_proc4_->retrieveOption(cfg, r, row);
+ }
+
+ // Parse DHCPv6 options if required to do so.
+ if (opt_proc6_) {
+ CfgOptionPtr cfg = current_host->getCfgOption6();
+ opt_proc6_->retrieveOption(cfg, r, row);
+ }
+ }
+
+private:
+
+ /// @brief Returns a number of columns required to retrieve option data.
+ ///
+ /// Depending if we need DHCPv4/DHCPv6 options only, or both DHCPv4 and
+ /// DHCPv6 a different number of columns is required in the binding array.
+ /// This method returns the number of required columns, according to the
+ /// value of @c fetched_columns passed in the constructor.
+ ///
+ /// @param fetched_columns A value which specifies whether DHCPv4, DHCPv6 or
+ /// both types of options should be retrieved.
+ ///
+ /// @return Number of required columns.
+ static size_t getRequiredColumnsNum(const FetchedOptions& fetched_options) {
+ return (fetched_options == DHCP4_AND_DHCP6 ? 2 * OPTION_COLUMNS :
+ OPTION_COLUMNS);
+ }
+
+ /// @brief Pointer to DHCPv4 options processor.
+ ///
+ /// If this object is NULL, the DHCPv4 options are not fetched.
+ OptionProcessorPtr opt_proc4_;
+
+ /// @brief Pointer to DHCPv6 options processor.
+ ///
+ /// If this object is NULL, the DHCPv6 options are not fetched.
+ OptionProcessorPtr opt_proc6_;
+};
+
+/// @brief This class provides mechanisms for sending and retrieving
+/// host information, DHCPv4 options, DHCPv6 options and IPv6 reservations.
+///
+/// This class extends the @ref PgSqlHostWithOptionsExchange class with the
+/// mechanisms to retrieve IPv6 reservations. This class is used in situations
+/// when it is desired to retrieve DHCPv6 specific information about the host
+/// (DHCPv6 options and reservations), or entire information about the host
+/// (DHCPv4 options, DHCPv6 options and reservations). The following are the
+/// queries used with this class:
+/// - SELECT ? FROM hosts LEFT JOIN dhcp4_options LEFT JOIN dhcp6_options
+/// LEFT JOIN ipv6_reservations ...
+/// - SELECT ? FROM hosts LEFT JOIN dhcp6_options LEFT JOIN ipv6_reservations ..
+class PgSqlHostIPv6Exchange : public PgSqlHostWithOptionsExchange {
+private:
+
+ /// @brief Number of columns holding IPv6 reservation information.
+ static const size_t RESERVATION_COLUMNS = 5;
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Apart from initializing the base class data structures it also
+ /// initializes values representing IPv6 reservation information.
+ PgSqlHostIPv6Exchange(const FetchedOptions& fetched_options)
+ : PgSqlHostWithOptionsExchange(fetched_options, RESERVATION_COLUMNS),
+ reservation_id_index_(findAvailColumn()),
+ address_index_(reservation_id_index_ + 1),
+ prefix_len_index_(reservation_id_index_ + 2),
+ type_index_(reservation_id_index_ + 3),
+ iaid_index_(reservation_id_index_ + 4),
+ most_recent_reservation_id_(0) {
+
+ // Provide names of additional columns returned by the queries.
+ columns_[reservation_id_index_] = "reservation_id";
+ columns_[address_index_] = "address";
+ columns_[prefix_len_index_] = "prefix_len";
+ columns_[type_index_] = "type";
+ columns_[iaid_index_] = "dhcp6_iaid";
+
+ BOOST_STATIC_ASSERT(4 < RESERVATION_COLUMNS);
+ }
+
+ /// @brief Reinitializes state information
+ ///
+ /// This function should be called in between statement executions.
+ /// Deriving classes should invoke this method as well as be reset
+ /// all of their own stateful values.
+ void clear() {
+ PgSqlHostWithOptionsExchange::clear();
+ most_recent_reservation_id_ = 0;
+ }
+
+ /// @brief Returns reservation id from the row.
+ ///
+ /// @return Reservation id or 0 if no reservation data is fetched.
+ uint64_t getReservationId(const PgSqlResult& r, int row) const {
+ uint64_t resv_id = 0;
+ if (!isColumnNull(r, row, reservation_id_index_)) {
+ getColumnValue(r, row, reservation_id_index_, resv_id);
+ }
+
+ return (resv_id);
+ };
+
+ /// @brief Creates IPv6 reservation from the data contained in the
+ /// currently processed row.
+ ///
+ /// @return IPv6Resrv object (containing IPv6 address or prefix reservation)
+ IPv6Resrv retrieveReservation(const PgSqlResult& r, int row) {
+
+ // type: SMALLINT NOT NULL
+ uint16_t tmp;
+ getColumnValue(r, row, type_index_, tmp);
+
+ // Convert it to IPv6 Reservation type (0 = IA_NA, 2 = IA_PD)
+ IPv6Resrv::Type resv_type;
+ switch (tmp) {
+ case 0:
+ resv_type = IPv6Resrv::TYPE_NA;
+ break;
+
+ case 2:
+ resv_type = IPv6Resrv::TYPE_PD;
+ break;
+
+ default:
+ isc_throw(BadValue,
+ "invalid IPv6 reservation type returned: "
+ << tmp << ". Only 0 or 2 are allowed.");
+ }
+
+ // address VARCHAR(39) NOT NULL
+ isc::asiolink::IOAddress address(getIPv6Value(r, row, address_index_));
+
+ // prefix_len: SMALLINT NOT NULL
+ uint16_t prefix_len;
+ getColumnValue(r, row, prefix_len_index_, prefix_len);
+
+ // @todo once we support populating iaid
+ // iaid: INT
+ // int iaid;
+ // getColumnValue(r, row, iaid_index_, iaid);
+
+ // Create the reservation.
+ IPv6Resrv reservation(resv_type, IOAddress(address), prefix_len);
+ return (reservation);
+ };
+
+ /// @brief Processes one row of data fetched from a database.
+ ///
+ /// The processed data must contain host id, which uniquely identifies a
+ /// host. This method creates a host and inserts it to the hosts collection
+ /// only if the last inserted host has a different host id. This prevents
+ /// adding duplicated hosts to the collection, assuming that processed
+ /// rows are primarily ordered by host id column.
+ ///
+ /// Depending on the value of the @c fetched_options specified in the
+ /// constructor, this method also parses options returned as a result
+ /// of SELECT queries.
+ ///
+ /// For any returned row which contains IPv6 reservation information it
+ /// checks if the reservation is not a duplicate of previously parsed
+ /// reservation and appends the IPv6Resrv object into the host object
+ /// if the parsed row contains new reservation information.
+ ///
+ /// @param [out] hosts Collection of hosts to which a new host created
+ /// from the processed data should be inserted.
+ /// @param r result set containing one or more rows of fetched data
+ /// @param row index within the result set of the row to process
+ virtual void processRowData(ConstHostCollection& hosts,
+ const PgSqlResult& r, int row) {
+ // Call parent class to fetch host information and options.
+ PgSqlHostWithOptionsExchange::processRowData(hosts, r, row);
+
+ // Shouldn't happen but just in case
+ if (hosts.empty()) {
+ isc_throw(Unexpected, "no host information while retrieving"
+ " IPv6 reservation");
+ }
+
+ // If we have reservation id we haven't seen yet, retrieve the
+ // the reservation, adding it to the current host
+ uint64_t reservation_id = getReservationId(r, row);
+ if (reservation_id && (reservation_id > most_recent_reservation_id_)) {
+ HostPtr host = boost::const_pointer_cast<Host>(hosts.back());
+ host->addReservation(retrieveReservation(r, row));
+ most_recent_reservation_id_ = reservation_id;
+ }
+ }
+
+private:
+ /// @name Indexes of columns holding information about IPv6 reservations.
+ //@{
+ /// @brief Index of reservation_id column.
+ size_t reservation_id_index_;
+
+ /// @brief Index of address column.
+ size_t address_index_;
+
+ /// @brief Index of prefix_len column.
+ size_t prefix_len_index_;
+
+ /// @brief Index of type column.
+ size_t type_index_;
+
+ /// @brief Index of IAID column.
+ size_t iaid_index_;
+
+ //@}
+
+ /// @brief Reservation id for last processed row.
+ uint64_t most_recent_reservation_id_;
+};
+
+/// @brief This class is used for storing IPv6 reservations in a PgSQL database.
+///
+/// This class is only used to insert IPv6 reservations into the
+/// ipv6_reservations table. It is not used to retrieve IPv6 reservations. To
+/// retrieve IPv6 reservation the @ref PgSqlIPv6HostExchange class should be
+/// used instead.
+///
+/// When a new IPv6 reservation is inserted into the database, an appropriate
+/// host must be defined in the hosts table. An attempt to insert IPv6
+/// reservation for non-existing host will result in failure.
+class PgSqlIPv6ReservationExchange : public PgSqlExchange {
+private:
+
+ /// @brief Set number of columns for ipv6_reservation table.
+ static const size_t RESRV_COLUMNS = 6;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initialize class members representing a single IPv6 reservation.
+ PgSqlIPv6ReservationExchange()
+ : PgSqlExchange(RESRV_COLUMNS),
+ resv_(IPv6Resrv::TYPE_NA, asiolink::IOAddress("::"), 128) {
+ // Set the column names (for error messages)
+ columns_[0] = "host_id";
+ columns_[1] = "address";
+ columns_[2] = "prefix_len";
+ columns_[3] = "type";
+ columns_[4] = "dhcp6_iaid";
+
+ BOOST_STATIC_ASSERT(5 < RESRV_COLUMNS);
+ }
+
+ /// @brief Populate a bind array representing an IPv6 reservation
+ ///
+ /// Constructs a PsqlBindArray for an IPv6 reservation to the database.
+ ///
+ /// @param resv The IPv6 reservation to be added to the database.
+ /// None of the fields in the reservation are modified -
+ /// @param host_id ID of the host to which this reservation belongs.
+ /// @param unique_ip boolean value indicating if multiple reservations for the
+ /// same IP address are allowed (false) or not (true).
+ ///
+ /// @return pointer to newly constructed bind_array containing the
+ /// bound values extracted the IPv6 reservation
+ ///
+ /// @throw DbOperationError if bind_array cannot be populated.
+ PsqlBindArrayPtr createBindForSend(const IPv6Resrv& resv,
+ const HostID& host_id,
+ const bool unique_ip) {
+ // Store the values to ensure they remain valid.
+ // Technically we don't need this, as currently all the values
+ // are converted to strings and stored by the bind array.
+ resv_ = resv;
+
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ try {
+ // address VARCHAR(39) NOT NULL
+ bind_array->add(resv.getPrefix());
+
+ // prefix_len: SMALLINT NOT NULL
+ bind_array->add(resv.getPrefixLen());
+
+ // type: SMALLINT NOT NULL
+ // See lease6_types table for values (0 = IA_NA, 2 = IA_PD)
+ uint16_t type = resv.getType() == IPv6Resrv::TYPE_NA ? 0 : 2;
+ bind_array->add(type);
+
+ // dhcp6_iaid: INT UNSIGNED
+ /// @todo: We don't support iaid in the IPv6Resrv yet.
+ bind_array->addNull();
+
+ // host_id: BIGINT NOT NULL
+ bind_array->add(host_id);
+
+ // When checking whether the IP is unique we need to bind the IPv6 address
+ // and prefix length at the end of the query as it has additional binding
+ // for the IPv6 address and prefix length.
+ if (unique_ip) {
+ bind_array->add(resv.getPrefix()); // address
+ bind_array->add(resv.getPrefixLen()); // prefix_len
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from IPv6 Reservation: "
+ << resv_.toText() << ", reason: " << ex.what());
+ }
+
+ return (bind_array);
+ }
+
+private:
+ /// @brief Object holding reservation being sent to the database.
+ IPv6Resrv resv_;
+};
+
+/// @brief This class is used for inserting options into a database.
+///
+/// This class supports inserting both DHCPv4 and DHCPv6 options.
+class PgSqlOptionExchange : public PgSqlExchange {
+private:
+
+ static const size_t OPTION_ID_COL = 0;
+ static const size_t CODE_COL = 1;
+ static const size_t VALUE_COL = 2;
+ static const size_t FORMATTED_VALUE_COL = 3;
+ static const size_t SPACE_COL = 4;
+ static const size_t PERSISTENT_COL = 5;
+ static const size_t CANCELLED_COL = 6;
+ static const size_t USER_CONTEXT_COL = 7;
+ static const size_t DHCP_SUBNET_ID_COL = 8;
+ static const size_t HOST_ID_COL = 9;
+ /// @brief Number of columns in the option tables holding bindable values.
+ static const size_t OPTION_COLUMNS = 10;
+
+public:
+
+ /// @brief Constructor.
+ PgSqlOptionExchange()
+ : PgSqlExchange(OPTION_COLUMNS), value_(),
+ value_len_(0), option_() {
+ columns_[OPTION_ID_COL] = "option_id";
+ columns_[CODE_COL] = "code";
+ columns_[VALUE_COL] = "value";
+ columns_[FORMATTED_VALUE_COL] = "formatted_value";
+ columns_[SPACE_COL] = "space";
+ columns_[PERSISTENT_COL] = "persistent";
+ columns_[CANCELLED_COL] = "cancelled";
+ columns_[USER_CONTEXT_COL] = "user_context";
+ columns_[DHCP_SUBNET_ID_COL] = "dhcp_subnet_id";
+ columns_[HOST_ID_COL] = "host_id";
+
+ BOOST_STATIC_ASSERT(10 <= OPTION_COLUMNS);
+ }
+
+ /// @brief Creates binding array to insert option data into database.
+ ///
+ /// @param opt_desc option descriptor of the option to write
+ /// @param opt_space name of the option space to which the option belongs
+ /// @param host_id host id of the host to which the option belongs
+ ///
+ /// @return pointer to newly constructed bind_array containing the
+ /// bound values extracted from host
+ PsqlBindArrayPtr createBindForSend(const OptionDescriptor& opt_desc,
+ const std::string& opt_space,
+ const HostID& host_id) {
+ // Hold pointer to the option to make sure it remains valid until
+ // we complete a query.
+ option_ = opt_desc.option_;
+
+ // Create the bind-array
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ try {
+ // option_id: is auto_incremented so skip it
+
+ // code: SMALLINT UNSIGNED NOT NULL
+ bind_array->add(option_->getType());
+
+ // value: BYTEA NULL
+ if (opt_desc.formatted_value_.empty() &&
+ (opt_desc.option_->len() > opt_desc.option_->getHeaderLen())) {
+ // The formatted_value is empty and the option value is
+ // non-empty so we need to prepare on-wire format for the
+ // option and store it in the database as a BYTEA.
+ OutputBuffer buf(opt_desc.option_->len());
+ opt_desc.option_->pack(buf);
+ const char* buf_ptr = static_cast<const char*>(buf.getData());
+ value_.assign(buf_ptr + opt_desc.option_->getHeaderLen(),
+ buf_ptr + buf.getLength());
+ value_len_ = value_.size();
+ bind_array->add(value_);
+ } else {
+ // No value or formatted_value specified. In this case, the
+ // value BYTEA should be NULL.
+ bind_array->addNull(PsqlBindArray::BINARY_FMT);
+ }
+
+ // formatted_value: TEXT NULL,
+ if (!opt_desc.formatted_value_.empty()) {
+ bind_array->addTempString(opt_desc.formatted_value_);
+ } else {
+ bind_array->addNull();
+ }
+
+ // space: VARCHAR(128) NULL
+ if (!opt_space.empty()) {
+ bind_array->addTempString(opt_space);
+ } else {
+ bind_array->addNull();
+ }
+
+ // persistent: BOOLEAN DEFAULT false
+ bind_array->add(opt_desc.persistent_);
+
+ // cancelled: BOOLEAN DEFAULT false
+ bind_array->add(opt_desc.cancelled_);
+
+ // user_context: TEXT NULL,
+ ConstElementPtr ctx = opt_desc.getContext();
+ if (ctx) {
+ std::string user_context_ = ctx->str();
+ bind_array->addTempString(user_context_);
+ } else {
+ bind_array->addNull();
+ }
+
+ // host_id: INT NULL
+ if (!host_id) {
+ isc_throw(BadValue, "host_id cannot be null");
+ }
+ bind_array->add(host_id);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array for inserting DHCP "
+ "host option: " << option_->toText() << ", reason: "
+ << ex.what());
+ }
+
+ return (bind_array);
+ }
+
+private:
+
+ /// @brief Option value as binary.
+ std::vector<uint8_t> value_;
+
+ /// @brief Option value length.
+ size_t value_len_;
+
+ /// @brief Pointer to currently parsed option.
+ OptionPtr option_;
+};
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief PostgreSQL Host Context
+///
+/// This class stores the thread context for the manager pool.
+/// The class is needed by all get/update/delete functions which must use one
+/// or more exchanges to perform database operations.
+/// Each context provides a set of such exchanges for each thread.
+/// The context instances are lazy initialized by the requesting thread by using
+/// the manager's createContext function and are destroyed when the manager's
+/// pool instance is destroyed.
+class PgSqlHostContext {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param parameters See PgSqlHostMgr constructor.
+ /// @param io_service_accessor The IOService accessor function.
+ /// @param db_reconnect_callback The connection recovery callback.
+ PgSqlHostContext(const DatabaseConnection::ParameterMap& parameters,
+ IOServiceAccessorPtr io_service_accessor,
+ db::DbCallback db_reconnect_callback);
+
+ /// The exchange objects are used for transfer of data to/from the database.
+ /// They are pointed-to objects as the contents may change in "const" calls,
+ /// while the rest of this object does not. (At alternative would be to
+ /// declare them as "mutable".)
+
+ /// @brief Pointer to the object representing an exchange which
+ /// can be used to retrieve hosts and DHCPv4 options.
+ boost::shared_ptr<PgSqlHostWithOptionsExchange> host_ipv4_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to retrieve hosts, DHCPv6 options and IPv6 reservations.
+ boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv6_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to retrieve hosts, DHCPv4 and DHCPv6 options, and
+ /// IPv6 reservations using a single query.
+ boost::shared_ptr<PgSqlHostIPv6Exchange> host_ipv46_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to insert new IPv6 reservation.
+ boost::shared_ptr<PgSqlIPv6ReservationExchange> host_ipv6_reservation_exchange_;
+
+ /// @brief Pointer to an object representing an exchange which can
+ /// be used to insert DHCPv4 or DHCPv6 option into dhcp4_options
+ /// or dhcp6_options table.
+ boost::shared_ptr<PgSqlOptionExchange> host_option_exchange_;
+
+ /// @brief PostgreSQL connection
+ PgSqlConnection conn_;
+
+ /// @brief Indicates if the database is opened in read only mode.
+ bool is_readonly_;
+};
+
+/// @brief PostgreSQL Host Context Pool
+///
+/// This class provides a pool of contexts.
+/// The manager will use this class to handle available contexts.
+/// There is only one ContextPool per manager per back-end, which is created
+/// and destroyed by the respective manager factory class.
+class PgSqlHostContextPool {
+public:
+
+ /// @brief The vector of available contexts.
+ std::vector<PgSqlHostContextPtr> pool_;
+
+ /// @brief The mutex to protect pool access.
+ std::mutex mutex_;
+};
+
+/// @brief Type of pointers to context pools.
+typedef boost::shared_ptr<PgSqlHostContextPool> PgSqlHostContextPoolPtr;
+
+/// @brief Implementation of the @ref PgSqlHostDataSource.
+class PgSqlHostDataSourceImpl {
+public:
+
+ /// @brief Statement Tags
+ ///
+ /// The contents of the enum are indexes into the list of SQL statements.
+ /// It is assumed that the order is such that the indices of statements
+ /// reading the database are less than those of statements modifying the
+ /// database.
+ /// @note: please add new statements doing read only operations before
+ /// the WRITE_STMTS_BEGIN position.
+ enum StatementIndex {
+ GET_HOST_DHCPID, // Gets hosts by host identifier
+ GET_HOST_ADDR, // Gets hosts by IPv4 address
+ GET_HOST_SUBID4_DHCPID, // Gets host by IPv4 SubnetID, HW address/DUID
+ GET_HOST_SUBID6_DHCPID, // Gets host by IPv6 SubnetID, HW address/DUID
+ GET_HOST_SUBID_ADDR, // Gets host by IPv4 SubnetID and IPv4 address
+ GET_HOST_PREFIX, // Gets host by IPv6 prefix
+ GET_HOST_SUBID6_ADDR, // Gets host by IPv6 SubnetID and IPv6 prefix
+ GET_HOST_ADDR6, // Gets hosts by IPv6 address/prefix
+ GET_HOST_SUBID4, // Gets hosts by IPv4 SubnetID
+ GET_HOST_SUBID6, // Gets hosts by IPv6 SubnetID
+ GET_HOST_HOSTNAME, // Gets hosts by hostname
+ GET_HOST_HOSTNAME_SUBID4, // Gets hosts by hostname and IPv4 SubnetID
+ GET_HOST_HOSTNAME_SUBID6, // Gets hosts by hostname and IPv6 SubnetID
+ GET_HOST_SUBID4_PAGE, // Gets hosts by IPv4 SubnetID beginning by HID
+ GET_HOST_SUBID6_PAGE, // Gets hosts by IPv6 SubnetID beginning by HID
+ GET_HOST_PAGE4, // Gets v4 hosts beginning by HID
+ GET_HOST_PAGE6, // Gets v6 hosts beginning by HID
+ INSERT_HOST_NON_UNIQUE_IP, // Insert new host to collection with allowing IP duplicates
+ INSERT_HOST_UNIQUE_IP, // Insert new host to collection with checking for IP duplicates
+ INSERT_V6_RESRV_NON_UNIQUE,// Insert v6 reservation without checking that it is unique
+ INSERT_V6_RESRV_UNIQUE, // Insert v6 reservation with checking that it is unique
+ INSERT_V4_HOST_OPTION, // Insert DHCPv4 option
+ INSERT_V6_HOST_OPTION, // Insert DHCPv6 option
+ DEL_HOST_ADDR4, // Delete v4 host (subnet-id, addr4)
+ DEL_HOST_ADDR6, // Delete v6 host (subnet-id, addr6)
+ DEL_HOST_SUBID4_ID, // Delete v4 host (subnet-id, ident.type, identifier)
+ DEL_HOST_SUBID6_ID, // Delete v6 host (subnet-id, ident.type, identifier)
+ NUM_STATEMENTS // Number of statements
+ };
+
+ /// @brief Index of first statement performing write to the database.
+ ///
+ /// This value is used to mark border line between queries and other
+ /// statements and statements performing write operation on the database,
+ /// such as INSERT, DELETE, UPDATE.
+ static const StatementIndex WRITE_STMTS_BEGIN = INSERT_HOST_NON_UNIQUE_IP;
+
+ /// @brief Constructor.
+ ///
+ /// This constructor opens database connection and initializes prepared
+ /// statements used in the queries.
+ PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Destructor.
+ ~PgSqlHostDataSourceImpl();
+
+ /// @brief Attempts to reconnect the server to the host DB backend manager.
+ ///
+ /// This is a self-rescheduling function that attempts to reconnect to the
+ /// server's host DB backends after connectivity to one or more have been
+ /// lost. Upon entry it will attempt to reconnect via
+ /// @ref HostDataSourceFactory::add.
+ /// If this is successful, DHCP servicing is re-enabled and server returns
+ /// to normal operation.
+ ///
+ /// If reconnection fails and the maximum number of retries has not been
+ /// exhausted, it will schedule a call to itself to occur at the
+ /// configured retry interval. DHCP service remains disabled.
+ ///
+ /// If the maximum number of retries has been exhausted an error is logged
+ /// and the server shuts down.
+ ///
+ /// This function is passed to the connection recovery mechanism. It will be
+ /// invoked when a connection loss is detected.
+ ///
+ /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters.
+ /// @return true if connection has been recovered, false otherwise.
+ static bool dbReconnect(ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief Create a new context.
+ ///
+ /// The database is opened with all the SQL commands pre-compiled.
+ ///
+ /// @return A new (never null) context.
+ ///
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ PgSqlHostContextPtr createContext() const;
+
+ /// @brief Executes statements which insert a row into one of the tables.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param bind Vector of PgsqlBindArray objects to be used for the query
+ /// @param return_last_id flag indicating whether or not the insert
+ /// returns the primary key of from the row inserted via " RETURNING
+ /// <primary key> as pid" clause on the INSERT statement. The RETURNING
+ /// clause causes the INSERT to return a result set that should consist
+ /// of a single row with one column, the value of the primary key.
+ /// Defaults to false.
+ ///
+ /// @return 0 if return_last_id is false, otherwise it returns the
+ /// the value in the result set in the first col of the first row.
+ ///
+ /// @throw isc::db::DuplicateEntry Database throws duplicate entry error
+ uint64_t addStatement(PgSqlHostContextPtr& ctx,
+ PgSqlHostDataSourceImpl::StatementIndex stindex,
+ PsqlBindArrayPtr& bind,
+ const bool return_last_id = false);
+
+ /// @brief Executes statements that delete records.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param bind pointer to PsqlBindArray objects to be used for the query
+ ///
+ /// @return true if any records were deleted, false otherwise
+ bool delStatement(PgSqlHostContextPtr& ctx,
+ PgSqlHostDataSourceImpl::StatementIndex stindex,
+ PsqlBindArrayPtr& bind);
+
+ /// @brief Inserts IPv6 Reservation into ipv6_reservation table.
+ ///
+ /// @param ctx Context
+ /// @param resv IPv6 Reservation to be added
+ /// @param id ID of a host owning this reservation
+ void addResv(PgSqlHostContextPtr& ctx,
+ const IPv6Resrv& resv,
+ const HostID& id);
+
+ /// @brief Inserts a single DHCP option into the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param opt_desc Option descriptor holding information about an option
+ /// to be inserted into the database.
+ /// @param opt_space Option space name.
+ /// @param subnet_id Subnet identifier.
+ /// @param host_id Host identifier.
+ void addOption(PgSqlHostContextPtr& ctx,
+ const PgSqlHostDataSourceImpl::StatementIndex& stindex,
+ const OptionDescriptor& opt_desc,
+ const std::string& opt_space,
+ const Optional<SubnetID>& subnet_id,
+ const HostID& host_id);
+
+ /// @brief Inserts multiple options into the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of a statement being executed.
+ /// @param options_cfg An object holding a collection of options to be
+ /// inserted into the database.
+ /// @param host_id Host identifier retrieved using getColumnValue
+ /// in addStatement method
+ void addOptions(PgSqlHostContextPtr& ctx,
+ const StatementIndex& stindex,
+ const ConstCfgOptionPtr& options_cfg,
+ const uint64_t host_id);
+
+ /// @brief Creates collection of @ref Host objects with associated
+ /// information such as IPv6 reservations and/or DHCP options.
+ ///
+ /// This method performs a query which returns host information from
+ /// the 'hosts' table. The query may also use LEFT JOIN clause to
+ /// retrieve information from other tables, e.g. ipv6_reservations,
+ /// dhcp4_options and dhcp6_options.
+ /// Whether IPv6 reservations and/or options are assigned to the
+ /// @ref Host objects depends on the type of the exchange object.
+ ///
+ /// @param ctx Context
+ /// @param stindex Statement index.
+ /// @param bind Pointer to an array of PgSQL bindings.
+ /// @param exchange Pointer to the exchange object used for the
+ /// particular query.
+ /// @param [out] result Reference to the collection of hosts returned.
+ /// @param single A boolean value indicating if a single host is
+ /// expected to be returned, or multiple hosts.
+ void getHostCollection(PgSqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArrayPtr bind,
+ boost::shared_ptr<PgSqlHostExchange> exchange,
+ ConstHostCollection& result,
+ bool single) const;
+
+ /// @brief Retrieves a host by subnet and client's unique identifier.
+ ///
+ /// This method is used by both PgSqlHostDataSource::get4 and
+ /// PgSqlHostDataSource::get6 methods.
+ ///
+ /// @param ctx Context
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @param stindex Statement index.
+ /// @param exchange Pointer to the exchange object used for the
+ /// particular query.
+ ///
+ /// @return Pointer to const instance of Host or null pointer if
+ /// no host found.
+ ConstHostPtr getHost(PgSqlHostContextPtr& ctx,
+ const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ StatementIndex stindex,
+ boost::shared_ptr<PgSqlHostExchange> exchange) const;
+
+ /// @brief Throws exception if database is read only.
+ ///
+ /// This method should be called by the methods which write to the
+ /// database. If the backend is operating in read-only mode this
+ /// method will throw exception.
+ ///
+ /// @param ctx Context
+ ///
+ /// @throw DbReadOnly if backend is operating in read only mode.
+ void checkReadOnly(PgSqlHostContextPtr& ctx) const;
+
+ /// @brief Returns PostgreSQL schema version of the open database
+ ///
+ /// @return Version number stored in the database, as a pair of unsigned
+ /// integers. "first" is the major version number, "second" the
+ /// minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database
+ /// has failed.
+ std::pair<uint32_t, uint32_t> getVersion() const;
+
+ /// @brief The parameters
+ DatabaseConnection::ParameterMap parameters_;
+
+ /// @brief Holds the setting whether the IP reservations must be unique or
+ /// may be non-unique.
+ bool ip_reservations_unique_;
+
+ /// @brief The pool of contexts
+ PgSqlHostContextPoolPtr pool_;
+
+ /// @brief Indicates if there is at least one connection that can no longer
+ /// be used for normal operations.
+ bool unusable_;
+
+ /// @brief Timer name used to register database reconnect timer.
+ std::string timer_name_;
+};
+
+namespace {
+
+/// @brief Array of tagged statements.
+typedef boost::array<PgSqlTaggedStatement, PgSqlHostDataSourceImpl::NUM_STATEMENTS>
+TaggedStatementArray;
+
+/// @brief Prepared PosgreSQL statements used by the backend to insert and
+/// retrieve reservation data from the database.
+TaggedStatementArray tagged_statements = { {
+ // PgSqlHostDataSourceImpl::GET_HOST_DHCPID
+ // Retrieves host information, IPv6 reservations and both DHCPv4 and
+ // DHCPv6 options associated with the host. The LEFT JOIN clause is used
+ // to retrieve information from 4 different tables using a single query.
+ // Hence, this query returns multiple rows for a single host.
+ {2,
+ { OID_BYTEA, OID_INT2 },
+ "get_host_dhcpid",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
+ " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
+ " h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
+ " o4.persistent, o4.cancelled, o4.user_context, "
+ " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
+ " o6.persistent, o6.cancelled, o6.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
+ "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE dhcp_identifier = $1 AND dhcp_identifier_type = $2 "
+ "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_ADDR
+ // Retrieves host information along with the DHCPv4 options associated with
+ // it. Left joining the dhcp4_options table results in multiple rows being
+ // returned for the same host. The host is retrieved by IPv4 address.
+ {1,
+ { OID_INT8 },
+ "get_host_addr",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+ "WHERE ipv4_address = $1 "
+ "ORDER BY h.host_id, o.option_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID
+ // Retrieves host information and DHCPv4 options using subnet identifier
+ // and client's identifier. Left joining the dhcp4_options table results in
+ // multiple rows being returned for the same host.
+ {3,
+ { OID_INT8, OID_INT2, OID_BYTEA },
+ "get_host_subid4_dhcpid",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+ "WHERE h.dhcp4_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
+ " AND h.dhcp_identifier = $3 "
+ "ORDER BY h.host_id, o.option_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host. The number of rows returned is a multiplication
+ // of number of IPv6 reservations and DHCPv6 options.
+ {3,
+ { OID_INT8, OID_INT2, OID_BYTEA },
+ "get_host_subid6_dhcpid",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE h.dhcp6_subnet_id = $1 AND h.dhcp_identifier_type = $2 "
+ " AND h.dhcp_identifier = $3 "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR
+ // Retrieves host information and DHCPv4 options for the host using subnet
+ // identifier and IPv4 reservation. Left joining the dhcp4_options table
+ // results in multiple rows being returned for the host. The number of
+ // rows depends on the number of options defined for the host.
+ {2,
+ { OID_INT8, OID_INT8 },
+ "get_host_subid_addr",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+ "WHERE h.dhcp4_subnet_id = $1 AND h.ipv4_address = $2 "
+ "ORDER BY h.host_id, o.option_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_PREFIX
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using prefix and prefix length. This query
+ // returns host information for a single host. However, multiple rows
+ // are returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {2,
+ { OID_VARCHAR, OID_INT2 },
+ "get_host_prefix",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, "
+ " r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE h.host_id = "
+ " (SELECT host_id FROM ipv6_reservations "
+ " WHERE address = cast($1 as inet) AND prefix_len = $2) "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using IPv6 subnet id and prefix. This query
+ // returns host information for a single host. However, multiple rows
+ // are returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {2,
+ { OID_INT8, OID_VARCHAR },
+ "get_host_subid6_addr",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, "
+ " r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE h.dhcp6_subnet_id = $1 AND h.host_id IN "
+ " (SELECT host_id FROM ipv6_reservations "
+ " WHERE address = cast($2 as inet)) "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_ADDR6
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using IPv6 address/prefix. This query
+ // may return host information for one or more host reservations. Even
+ // if only one host is found, multiple rows
+ // are returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {1,
+ { OID_VARCHAR },
+ "get_host_addr6",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, r.address, r.prefix_len, r.type, "
+ " r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE h.host_id IN "
+ " (SELECT host_id FROM ipv6_reservations "
+ " WHERE address = cast($1 as inet)) "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID4
+ //
+ // Retrieves host information for all hosts in a subnet, along with the
+ // DHCPv4 options associated with it. Left joining the dhcp4_options table
+ // results in multiple rows being returned for the same host. The hosts are
+ // retrieved by subnet id.
+ {1,
+ { OID_INT8 },
+ "get_host_subid4",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+ "WHERE h.dhcp4_subnet_id = $1 "
+ "ORDER BY h.host_id, o.option_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID6
+ //
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with all hosts using the IPv6 subnet id. This query returns
+ // host information for many hosts. However, multiple rows are
+ // returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options for each host in a subnet. There
+ // are usually many hosts in a subnet. The amount of returned data may
+ // be huge.
+ {1,
+ { OID_INT8 },
+ "get_host_subid6",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE h.dhcp6_subnet_id = $1 "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME
+ // Retrieves host information, IPv6 reservations and both DHCPv4 and
+ // DHCPv6 options associated with all hosts using the hostname.
+ // The LEFT JOIN clause is used to retrieve information from 4 different
+ // tables using a single query. Hence, this query returns multiple rows
+ // for a single host.
+ {1,
+ { OID_VARCHAR },
+ "get_host_hostname",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, "
+ " h.hostname, h.dhcp4_client_classes, h.dhcp6_client_classes, "
+ " h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o4.option_id, o4.code, o4.value, o4.formatted_value, o4.space, "
+ " o4.persistent, o4.cancelled, o4.user_context, "
+ " o6.option_id, o6.code, o6.value, o6.formatted_value, o6.space, "
+ " o6.persistent, o6.cancelled, o6.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o4 ON h.host_id = o4.host_id "
+ "LEFT JOIN dhcp6_options AS o6 ON h.host_id = o6.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE lower(h.hostname) = $1 "
+ "ORDER BY h.host_id, o4.option_id, o6.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4
+ // Retrieves host information for all hosts with a hostname in a subnet,
+ // along with the DHCPv4 options associated with it. Left joining
+ // the dhcp4_options table results in multiple rows being returned for
+ // the same host.
+ {2,
+ { OID_VARCHAR, OID_INT8 },
+ "get_host_hostname_subid4",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+ "WHERE lower(h.hostname) = $1 AND h.dhcp4_subnet_id = $2 "
+ "ORDER BY h.host_id, o.option_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with all hosts using the hostname and the IPv6 subnet id.
+ // This query returns host information for many hosts. However, multiple
+ // rows are returned due to left joining IPv6 reservations and DHCPv6
+ // options. The number of rows returned is multiplication of number of
+ // existing IPv6 reservations and DHCPv6 options for each host in a subnet.
+ {2,
+ { OID_VARCHAR, OID_INT8 },
+ "get_host_hostname_subid6",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, r.dhcp6_iaid "
+ "FROM hosts AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "WHERE lower(h.hostname) = $1 AND h.dhcp6_subnet_id = $2 "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE
+ // Retrieves host information along with the DHCPv4 options associated with
+ // it. Left joining the dhcp4_options table results in multiple rows being
+ // returned for the same host. The hosts are retrieved by subnet id,
+ // starting from specified host id. Specified number of hosts is returned.
+ {3,
+ { OID_INT8, OID_INT8, OID_INT8 },
+ "get_host_subid4_page",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context "
+ "FROM ( SELECT * FROM hosts AS h "
+ " WHERE h.dhcp4_subnet_id = $1 AND h.host_id > $2 "
+ " ORDER BY h.host_id "
+ " LIMIT $3 ) AS h "
+ "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+ "ORDER BY h.host_id, o.option_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using IPv6 subnet id. This query returns
+ // host information for a single host. However, multiple rows are
+ // returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {3,
+ { OID_INT8, OID_INT8, OID_INT8 },
+ "get_host_subid6_page",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, r.dhcp6_iaid "
+ "FROM ( SELECT * FROM hosts AS h "
+ " WHERE h.dhcp6_subnet_id = $1 AND h.host_id > $2 "
+ " ORDER BY h.host_id "
+ " LIMIT $3 ) AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_PAGE4
+ // Retrieves host information along with the DHCPv4 options associated with
+ // it. Left joining the dhcp4_options table results in multiple rows being
+ // returned for the same host. The hosts are retrieved starting from
+ // specified host id. Specified number of hosts is returned.
+ {2,
+ { OID_INT8, OID_INT8 },
+ "get_host_page4",
+ "SELECT h.host_id, h.dhcp_identifier, h.dhcp_identifier_type, "
+ " h.dhcp4_subnet_id, h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context "
+ "FROM ( SELECT * FROM hosts AS h "
+ " WHERE h.host_id > $1 "
+ " ORDER BY h.host_id "
+ " LIMIT $2 ) AS h "
+ "LEFT JOIN dhcp4_options AS o ON h.host_id = o.host_id "
+ "ORDER BY h.host_id, o.option_id"
+ },
+
+ // PgSqlHostDataSourceImpl::GET_HOST_PAGE6
+ // Retrieves host information, IPv6 reservations and DHCPv6 options
+ // associated with a host using IPv6 subnet id. This query returns
+ // host information for a single host. However, multiple rows are
+ // returned due to left joining IPv6 reservations and DHCPv6 options.
+ // The number of rows returned is multiplication of number of existing
+ // IPv6 reservations and DHCPv6 options.
+ {2,
+ { OID_INT8, OID_INT8 },
+ "get_host_page6",
+ "SELECT h.host_id, h.dhcp_identifier, "
+ " h.dhcp_identifier_type, h.dhcp4_subnet_id, "
+ " h.dhcp6_subnet_id, h.ipv4_address, h.hostname, "
+ " h.dhcp4_client_classes, h.dhcp6_client_classes, h.user_context, "
+ " h.dhcp4_next_server, h.dhcp4_server_hostname, "
+ " h.dhcp4_boot_file_name, h.auth_key, "
+ " o.option_id, o.code, o.value, o.formatted_value, o.space, "
+ " o.persistent, o.cancelled, o.user_context, "
+ " r.reservation_id, host(r.address), r.prefix_len, r.type, r.dhcp6_iaid "
+ "FROM ( SELECT * FROM hosts AS h "
+ " WHERE h.host_id > $1 "
+ " ORDER BY h.host_id "
+ " LIMIT $2 ) AS h "
+ "LEFT JOIN dhcp6_options AS o ON h.host_id = o.host_id "
+ "LEFT JOIN ipv6_reservations AS r ON h.host_id = r.host_id "
+ "ORDER BY h.host_id, o.option_id, r.reservation_id"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP
+ // Inserts a host into the 'hosts' table without checking that there is
+ // a reservation for the IP address.
+ {13,
+ { OID_BYTEA, OID_INT2,
+ OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
+ OID_VARCHAR, OID_VARCHAR, OID_TEXT,
+ OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR},
+ "insert_host_non_unique_ip",
+ "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
+ " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ " dhcp4_client_classes, dhcp6_client_classes, user_context, "
+ " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
+ "VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13 ) "
+ "RETURNING host_id"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP
+ // Inserts a host into the 'hosts' table with checking that reserved IP
+ // address is unique. The innermost query checks if there is at least
+ // one host for the given IP/subnet combination. For checking whether
+ // hosts exists or not it doesn't matter if we select actual columns,
+ // thus SELECT 1 was used as an optimization to avoid selecting data
+ // that will be ignored anyway. If it does not exist the new host is
+ // inserted. If the host with the given IP address already exists the
+ // new host won't be inserted. The caller can check the number of
+ // affected rows to detect that there was a duplicate host in the
+ // database. Returns the inserted host id.
+ {15,
+ { OID_BYTEA, OID_INT2,
+ OID_INT8, OID_INT8, OID_INT8, OID_VARCHAR,
+ OID_VARCHAR, OID_VARCHAR, OID_TEXT,
+ OID_INT8, OID_VARCHAR, OID_VARCHAR, OID_VARCHAR,
+ OID_INT8, OID_INT8},
+ "insert_host_unique_ip",
+ "INSERT INTO hosts(dhcp_identifier, dhcp_identifier_type, "
+ " dhcp4_subnet_id, dhcp6_subnet_id, ipv4_address, hostname, "
+ " dhcp4_client_classes, dhcp6_client_classes, user_context, "
+ " dhcp4_next_server, dhcp4_server_hostname, dhcp4_boot_file_name, auth_key)"
+ " SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13"
+ " WHERE NOT EXISTS ("
+ " SELECT 1 FROM hosts WHERE ipv4_address = $14 AND dhcp4_subnet_id = $15"
+ " LIMIT 1"
+ " ) "
+ "RETURNING host_id"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_NON_UNIQUE
+ // Inserts a single IPv6 reservation into 'reservations' table without
+ // checking that the inserted reservation is unique.
+ {5,
+ { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4 },
+ "insert_v6_resrv_non_unique",
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ " dhcp6_iaid, host_id) "
+ "VALUES (cast($1 as inet), $2, $3, $4, $5)"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_V6_RESRV_UNIQUE
+ // Inserts a single IPv6 reservation into 'reservations' table with
+ // checking that the inserted reservation is unique.
+ {7,
+ { OID_VARCHAR, OID_INT2, OID_INT4, OID_INT4, OID_INT4, OID_VARCHAR, OID_INT2 },
+ "insert_v6_resrv_unique",
+ "INSERT INTO ipv6_reservations(address, prefix_len, type, "
+ " dhcp6_iaid, host_id) "
+ "SELECT cast($1 as inet), $2, $3, $4, $5 "
+ " WHERE NOT EXISTS ("
+ " SELECT 1 FROM ipv6_reservations"
+ " WHERE address = cast($6 as inet) AND prefix_len = $7"
+ " LIMIT 1"
+ " )"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION
+ // Inserts a single DHCPv4 option into 'dhcp4_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {8,
+ { OID_INT2, OID_BYTEA, OID_TEXT,
+ OID_VARCHAR, OID_BOOL, OID_BOOL, OID_TEXT, OID_INT8 },
+ "insert_v4_host_option",
+ "INSERT INTO dhcp4_options(code, value, formatted_value, space, "
+ " persistent, cancelled, user_context, host_id, scope_id) "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 3)"
+ },
+
+ // PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION
+ // Inserts a single DHCPv6 option into 'dhcp6_options' table.
+ // Using fixed scope_id = 3, which associates an option with host.
+ {8,
+ { OID_INT2, OID_BYTEA, OID_TEXT,
+ OID_VARCHAR, OID_BOOL, OID_BOOL, OID_TEXT, OID_INT8 },
+ "insert_v6_host_option",
+ "INSERT INTO dhcp6_options(code, value, formatted_value, space, "
+ " persistent, cancelled, user_context, host_id, scope_id) "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 3)"
+ },
+
+ // PgSqlHostDataSourceImpl::DEL_HOST_ADDR4
+ // Deletes a v4 host that matches (subnet-id, addr4)
+ {2,
+ { OID_INT8, OID_INT8 },
+ "del_host_addr4",
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 AND ipv4_address = $2"
+ },
+
+ // PgSqlHostDataSourceImpl::DEL_HOST_ADDR6
+ // Deletes a v6 host that matches (subnet-id, addr6)
+ {2,
+ { OID_INT8, OID_VARCHAR },
+ "del_host_addr6",
+ "DELETE FROM hosts USING ipv6_reservations "
+ " WHERE dhcp6_subnet_id = $1 AND ipv6_reservations.address = cast($2 as inet)"
+ },
+
+ // PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID
+ // Deletes a v4 host that matches (subnet4-id, identifier-type, identifier)
+ {3,
+ { OID_INT8, OID_INT2, OID_BYTEA },
+ "del_host_subid4_id",
+ "DELETE FROM hosts WHERE dhcp4_subnet_id = $1 "
+ "AND dhcp_identifier_type = $2 "
+ "AND dhcp_identifier = $3"
+ },
+
+ // PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID
+ // Deletes a v6 host that matches (subnet6-id, identifier-type, identifier)
+ {3,
+ { OID_INT8, OID_INT2, OID_BYTEA },
+ "del_host_subid6_id",
+ "DELETE FROM hosts WHERE dhcp6_subnet_id = $1 "
+ "AND dhcp_identifier_type = $2 "
+ "AND dhcp_identifier = $3"
+ }
+}
+};
+
+} // namespace
+
+// PgSqlHostContext Constructor
+
+PgSqlHostContext::PgSqlHostContext(const DatabaseConnection::ParameterMap& parameters,
+ IOServiceAccessorPtr io_service_accessor,
+ db::DbCallback db_reconnect_callback)
+ : conn_(parameters, io_service_accessor, db_reconnect_callback),
+ is_readonly_(true) {
+}
+
+// PgSqlHostContextAlloc Constructor and Destructor
+
+PgSqlHostDataSource::PgSqlHostContextAlloc::PgSqlHostContextAlloc(
+ PgSqlHostDataSourceImpl& mgr) : ctx_(), mgr_(mgr) {
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ {
+ // we need to protect the whole pool_ operation, hence extra scope {}
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (!mgr_.pool_->pool_.empty()) {
+ ctx_ = mgr_.pool_->pool_.back();
+ mgr_.pool_->pool_.pop_back();
+ }
+ }
+ if (!ctx_) {
+ ctx_ = mgr_.createContext();
+ }
+ } else {
+ // single-threaded
+ if (mgr_.pool_->pool_.empty()) {
+ isc_throw(Unexpected, "No available PostgreSQL host context?!");
+ }
+ ctx_ = mgr_.pool_->pool_.back();
+ }
+}
+
+PgSqlHostDataSource::PgSqlHostContextAlloc::~PgSqlHostContextAlloc() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ mgr_.pool_->pool_.push_back(ctx_);
+ if (ctx_->conn_.isUnusable()) {
+ mgr_.unusable_ = true;
+ }
+ } else if (ctx_->conn_.isUnusable()) {
+ mgr_.unusable_ = true;
+ }
+}
+
+PgSqlHostDataSourceImpl::PgSqlHostDataSourceImpl(const DatabaseConnection::ParameterMap& parameters)
+ : parameters_(parameters), ip_reservations_unique_(true), unusable_(false),
+ timer_name_("") {
+
+ // Create unique timer name per instance.
+ timer_name_ = "PgSqlHostMgr[";
+ timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+ timer_name_ += "]DbReconnectTimer";
+
+ // Check TLS support.
+ size_t tls(0);
+ tls += parameters.count("trust-anchor");
+ tls += parameters.count("cert-file");
+ tls += parameters.count("key-file");
+ tls += parameters.count("cipher-list");
+#ifdef HAVE_PGSQL_SSL
+ if ((tls > 0) && !PgSqlConnection::warned_about_tls) {
+ PgSqlConnection::warned_about_tls = true;
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_TLS_SUPPORT)
+ .arg(DatabaseConnection::redactedAccessString(parameters_));
+ PQinitSSL(1);
+ }
+#else
+ if (tls > 0) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_NO_TLS_SUPPORT)
+ .arg(DatabaseConnection::redactedAccessString(parameters_));
+ isc_throw(DbOpenError, "Attempt to configure TLS for PostgreSQL "
+ << "backend (built with this feature disabled)");
+ }
+#endif
+
+ // Validate the schema version first.
+ std::pair<uint32_t, uint32_t> code_version(PGSQL_SCHEMA_VERSION_MAJOR,
+ PGSQL_SCHEMA_VERSION_MINOR);
+ std::pair<uint32_t, uint32_t> db_version = getVersion();
+ if (code_version != db_version) {
+ isc_throw(DbOpenError,
+ "PostgreSQL schema version mismatch: need version: "
+ << code_version.first << "." << code_version.second
+ << " found version: " << db_version.first << "."
+ << db_version.second);
+ }
+
+ // Create an initial context.
+ pool_.reset(new PgSqlHostContextPool());
+ pool_->pool_.push_back(createContext());
+}
+
+// Create context.
+
+PgSqlHostContextPtr
+PgSqlHostDataSourceImpl::createContext() const {
+ PgSqlHostContextPtr ctx(new PgSqlHostContext(parameters_,
+ IOServiceAccessorPtr(new IOServiceAccessor(&HostMgr::getIOService)),
+ &PgSqlHostDataSourceImpl::dbReconnect));
+
+ // Open the database.
+ ctx->conn_.openDatabase();
+
+ // Now prepare the SQL statements.
+ ctx->conn_.prepareStatements(tagged_statements.begin(),
+ tagged_statements.begin() + WRITE_STMTS_BEGIN);
+
+ // Check if the backend is explicitly configured to operate with
+ // read only access to the database.
+ ctx->is_readonly_ = ctx->conn_.configuredReadOnly();
+
+ // If we are using read-write mode for the database we also prepare
+ // statements for INSERTS etc.
+ if (!ctx->is_readonly_) {
+ ctx->conn_.prepareStatements(tagged_statements.begin() + WRITE_STMTS_BEGIN,
+ tagged_statements.end());
+ } else {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_READONLY);
+ }
+
+ ctx->host_ipv4_exchange_.reset(new PgSqlHostWithOptionsExchange(PgSqlHostWithOptionsExchange::DHCP4_ONLY));
+ ctx->host_ipv6_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP6_ONLY));
+ ctx->host_ipv46_exchange_.reset(new PgSqlHostIPv6Exchange(PgSqlHostWithOptionsExchange::DHCP4_AND_DHCP6));
+ ctx->host_ipv6_reservation_exchange_.reset(new PgSqlIPv6ReservationExchange());
+ ctx->host_option_exchange_.reset(new PgSqlOptionExchange());
+
+ // Create ReconnectCtl for this connection.
+ ctx->conn_.makeReconnectCtl(timer_name_);
+
+ return (ctx);
+}
+
+PgSqlHostDataSourceImpl::~PgSqlHostDataSourceImpl() {
+}
+
+bool
+PgSqlHostDataSourceImpl::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
+ MultiThreadingCriticalSection cs;
+
+ // Invoke application layer connection lost callback.
+ if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+
+ bool reopened = false;
+
+ const std::string timer_name = db_reconnect_ctl->timerName();
+
+ // At least one connection was lost.
+ try {
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
+ std::list<std::string> host_db_access_list = cfg_db->getHostDbAccessStringList();
+ for (std::string& hds : host_db_access_list) {
+ auto parameters = DatabaseConnection::parse(hds);
+ if (HostMgr::delBackend("postgresql", hds, true)) {
+ HostMgr::addBackend(hds);
+ }
+ }
+ reopened = true;
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_FAILED)
+ .arg(ex.what());
+ }
+
+ if (reopened) {
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection recovered callback.
+ if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+ } else {
+ if (!db_reconnect_ctl->checkRetries()) {
+ // We're out of retries, log it and initiate shutdown.
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_FAILED)
+ .arg(db_reconnect_ctl->maxRetries());
+
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection failed callback.
+ DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
+ return (false);
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_HOST_DB_RECONNECT_ATTEMPT_SCHEDULE)
+ .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
+ .arg(db_reconnect_ctl->maxRetries())
+ .arg(db_reconnect_ctl->retryInterval());
+
+ // Start the timer.
+ if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->registerTimer(timer_name,
+ std::bind(&PgSqlHostDataSourceImpl::dbReconnect, db_reconnect_ctl),
+ db_reconnect_ctl->retryInterval(),
+ asiolink::IntervalTimer::ONE_SHOT);
+ }
+ TimerMgr::instance()->setup(timer_name);
+ }
+
+ return (true);
+}
+
+uint64_t
+PgSqlHostDataSourceImpl::addStatement(PgSqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArrayPtr& bind_array,
+ const bool return_last_id) {
+ uint64_t last_id = 0;
+ PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array->values_[0],
+ &bind_array->lengths_[0],
+ &bind_array->formats_[0], 0));
+
+ int s = PQresultStatus(r);
+
+ if (s != PGRES_COMMAND_OK) {
+ // Failure: check for the special case of duplicate entry.
+ if (ctx->conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+
+ // Connection determines if the error is fatal or not, and
+ // throws the appropriate exception
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+ }
+
+ // Get the number of affected rows.
+ char* rows_affected = PQcmdTuples(r);
+ if (!rows_affected) {
+ isc_throw(DbOperationError,
+ "Could not retrieve the number of affected rows.");
+ }
+
+ // If the number of rows inserted is 0 it means that the query detected
+ // an attempt to insert duplicated data for which there is no unique
+ // index in the database. Unique indexes are not created in the database
+ // when it may be sometimes allowed to insert duplicated records per
+ // server's configuration.
+ if (rows_affected[0] == '0') {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+
+ if (return_last_id) {
+ PgSqlExchange::getColumnValue(r, 0, 0, last_id);
+ }
+
+ return (last_id);
+}
+
+bool
+PgSqlHostDataSourceImpl::delStatement(PgSqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArrayPtr& bind_array) {
+ PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array->values_[0],
+ &bind_array->lengths_[0],
+ &bind_array->formats_[0], 0));
+
+ int s = PQresultStatus(r);
+
+ if (s != PGRES_COMMAND_OK) {
+ // Connection determines if the error is fatal or not, and
+ // throws the appropriate exception
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+ }
+
+ // Now check how many rows (hosts) were deleted. This should be either
+ // "0" or "1".
+ char* rows_deleted = PQcmdTuples(r);
+ if (!rows_deleted) {
+ isc_throw(DbOperationError,
+ "Could not retrieve the number of deleted rows.");
+ }
+ return (rows_deleted[0] != '0');
+}
+
+void
+PgSqlHostDataSourceImpl::addResv(PgSqlHostContextPtr& ctx,
+ const IPv6Resrv& resv,
+ const HostID& id) {
+ PsqlBindArrayPtr bind_array = ctx->host_ipv6_reservation_exchange_->
+ createBindForSend(resv, id, ip_reservations_unique_);
+
+ addStatement(ctx,
+ ip_reservations_unique_ ? INSERT_V6_RESRV_UNIQUE : INSERT_V6_RESRV_NON_UNIQUE,
+ bind_array);
+}
+
+void
+PgSqlHostDataSourceImpl::addOption(PgSqlHostContextPtr& ctx,
+ const StatementIndex& stindex,
+ const OptionDescriptor& opt_desc,
+ const std::string& opt_space,
+ const Optional<SubnetID>&,
+ const HostID& id) {
+ PsqlBindArrayPtr bind_array = ctx->host_option_exchange_->createBindForSend(opt_desc, opt_space, id);
+
+ addStatement(ctx, stindex, bind_array);
+}
+
+void
+PgSqlHostDataSourceImpl::addOptions(PgSqlHostContextPtr& ctx,
+ const StatementIndex& stindex,
+ const ConstCfgOptionPtr& options_cfg,
+ const uint64_t host_id) {
+ // Get option space names and vendor space names and combine them within a
+ // single list.
+ std::list<std::string> option_spaces = options_cfg->getOptionSpaceNames();
+ std::list<std::string> vendor_spaces = options_cfg->getVendorIdsSpaceNames();
+ option_spaces.insert(option_spaces.end(), vendor_spaces.begin(),
+ vendor_spaces.end());
+
+ // For each option space retrieve all options and insert them into the
+ // database.
+ for (auto space = option_spaces.begin(); space != option_spaces.end(); ++space) {
+ OptionContainerPtr options = options_cfg->getAllCombined(*space);
+ if (options && !options->empty()) {
+ for (auto opt = options->begin(); opt != options->end(); ++opt) {
+ addOption(ctx, stindex, *opt, *space, Optional<SubnetID>(), host_id);
+ }
+ }
+ }
+}
+
+void
+PgSqlHostDataSourceImpl::getHostCollection(PgSqlHostContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArrayPtr bind_array,
+ boost::shared_ptr<PgSqlHostExchange> exchange,
+ ConstHostCollection& result,
+ bool single) const {
+
+ exchange->clear();
+ PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array->values_[0],
+ &bind_array->lengths_[0],
+ &bind_array->formats_[0], 0));
+
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+
+ int rows = r.getRows();
+ for (int row = 0; row < rows; ++row) {
+ exchange->processRowData(result, r, row);
+
+ if (single && result.size() > 1) {
+ isc_throw(MultipleRecords, "multiple records were found in the "
+ "database where only one was expected for query "
+ << tagged_statements[stindex].name);
+ }
+ }
+}
+
+ConstHostPtr
+PgSqlHostDataSourceImpl::getHost(PgSqlHostContextPtr& ctx,
+ const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len,
+ StatementIndex stindex,
+ boost::shared_ptr<PgSqlHostExchange> exchange) const {
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id.
+ bind_array->add(subnet_id);
+
+ // Add the Identifier type.
+ bind_array->add(static_cast<uint8_t>(identifier_type));
+
+ // Add the identifier value.
+ bind_array->add(identifier_begin, identifier_len);
+
+ ConstHostCollection collection;
+ getHostCollection(ctx, stindex, bind_array, exchange, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+std::pair<uint32_t, uint32_t>
+PgSqlHostDataSourceImpl::getVersion() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_HOST_DB_GET_VERSION);
+ return (PgSqlConnection::getVersion(parameters_));
+}
+
+void
+PgSqlHostDataSourceImpl::checkReadOnly(PgSqlHostContextPtr& ctx) const {
+ if (ctx->is_readonly_) {
+ isc_throw(ReadOnlyDb, "PostgreSQL host database backend is configured"
+ " to operate in read only mode");
+ }
+}
+
+/*********** PgSqlHostDataSource *********************/
+
+PgSqlHostDataSource::PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters)
+ : impl_(new PgSqlHostDataSourceImpl(parameters)) {
+}
+
+PgSqlHostDataSource::~PgSqlHostDataSource() {
+}
+
+DatabaseConnection::ParameterMap
+PgSqlHostDataSource::getParameters() const {
+ return (impl_->parameters_);
+}
+
+void
+PgSqlHostDataSource::add(const HostPtr& host) {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ // Initiate PostgreSQL transaction as we will have to make multiple queries
+ // to insert host information into multiple tables. If that fails on
+ // any stage, the transaction will be rolled back by the destructor of
+ // the PgSqlTransaction class.
+ PgSqlTransaction transaction(ctx->conn_);
+
+ // If we're configured to check that an IP reservation within a given subnet
+ // is unique, the IP reservation exists and the subnet is actually set
+ // we will be using a special query that checks for uniqueness. Otherwise,
+ // we will use a regular insert statement.
+ bool unique_ip = impl_->ip_reservations_unique_ && !host->getIPv4Reservation().isV4Zero()
+ && host->getIPv4SubnetID() != SUBNET_ID_UNUSED;
+
+ // Create the PgSQL Bind array for the host
+ PsqlBindArrayPtr bind_array = ctx->host_ipv4_exchange_->createBindForSend(host, unique_ip);
+
+ // ... and insert the host.
+ uint32_t host_id = impl_->addStatement(ctx,
+ unique_ip ? PgSqlHostDataSourceImpl::INSERT_HOST_UNIQUE_IP :
+ PgSqlHostDataSourceImpl::INSERT_HOST_NON_UNIQUE_IP,
+ bind_array, true);
+
+ // Insert DHCPv4 options.
+ ConstCfgOptionPtr cfg_option4 = host->getCfgOption4();
+ if (cfg_option4) {
+ impl_->addOptions(ctx, PgSqlHostDataSourceImpl::INSERT_V4_HOST_OPTION,
+ cfg_option4, host_id);
+ }
+
+ // Insert DHCPv6 options.
+ ConstCfgOptionPtr cfg_option6 = host->getCfgOption6();
+ if (cfg_option6) {
+ impl_->addOptions(ctx, PgSqlHostDataSourceImpl::INSERT_V6_HOST_OPTION,
+ cfg_option6, host_id);
+ }
+
+ // Insert IPv6 reservations.
+ IPv6ResrvRange v6resv = host->getIPv6Reservations();
+ if (std::distance(v6resv.first, v6resv.second) > 0) {
+ for (IPv6ResrvIterator resv = v6resv.first; resv != v6resv.second;
+ ++resv) {
+ impl_->addResv(ctx, resv->second, host_id);
+ }
+ }
+
+ // Everything went fine, so explicitly commit the transaction.
+ transaction.commit();
+}
+
+bool
+PgSqlHostDataSource::del(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr) {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+ bind_array->add(subnet_id);
+
+ // v4
+ if (addr.isV4()) {
+ bind_array->add(addr);
+ return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR4,
+ bind_array));
+ }
+
+ // v6
+ bind_array->addTempString(addr.toText());
+
+ return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_ADDR6,
+ bind_array));
+}
+
+bool
+PgSqlHostDataSource::del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Subnet-id
+ bind_array->add(subnet_id);
+
+ // identifier-type
+ bind_array->add(static_cast<uint8_t>(identifier_type));
+
+ // identifier
+ bind_array->add(identifier_begin, identifier_len);
+
+ return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID4_ID,
+ bind_array));
+}
+
+bool
+PgSqlHostDataSource::del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Subnet-id
+ bind_array->add(subnet_id);
+
+ // identifier-type
+ bind_array->add(static_cast<uint8_t>(identifier_type));
+
+ // identifier
+ bind_array->add(identifier_begin, identifier_len);
+
+ return (impl_->delStatement(ctx, PgSqlHostDataSourceImpl::DEL_HOST_SUBID6_ID,
+ bind_array));
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Identifier value.
+ bind_array->add(identifier_begin, identifier_len);
+
+ // Identifier type.
+ bind_array->add(static_cast<uint8_t>(identifier_type));
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_DHCPID,
+ bind_array, ctx->host_ipv46_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll4(const SubnetID& subnet_id) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id.
+ bind_array->add(subnet_id);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4,
+ bind_array, ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll6(const SubnetID& subnet_id) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id.
+ bind_array->add(subnet_id);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6,
+ bind_array, ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAllbyHostname(const std::string& hostname) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the hostname.
+ bind_array->add(hostname);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME,
+ bind_array, ctx->host_ipv46_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the hostname.
+ bind_array->add(hostname);
+
+ // Add the subnet id.
+ bind_array->add(subnet_id);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID4,
+ bind_array, ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the hostname.
+ bind_array->add(hostname);
+
+ // Add the subnet id.
+ bind_array->add(subnet_id);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_HOSTNAME_SUBID6,
+ bind_array, ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getPage4(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id.
+ bind_array->add(subnet_id);
+
+ // Add the lower bound host id.
+ bind_array->add(lower_host_id);
+
+ // Add the page size value.
+ string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array->add(page_size_data);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID4_PAGE,
+ bind_array, ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getPage6(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id.
+ bind_array->add(subnet_id);
+
+ // Add the lower bound host id.
+ bind_array->add(lower_host_id);
+
+ // Add the page size value.
+ string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array->add(page_size_data);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_PAGE,
+ bind_array, ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getPage4(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the lower bound host id.
+ bind_array->add(lower_host_id);
+
+ // Add the page size value.
+ string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array->add(page_size_data);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE4,
+ bind_array, ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getPage6(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the lower bound host id.
+ bind_array->add(lower_host_id);
+
+ // Add the page size value.
+ string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array->add(page_size_data);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PAGE6,
+ bind_array, ctx->host_ipv6_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll4(const asiolink::IOAddress& address) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // v4 Reservation address
+ bind_array->add(address);
+
+ ConstHostCollection result;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_ADDR,
+ bind_array, ctx->host_ipv4_exchange_, result, false);
+
+ return (result);
+}
+
+ConstHostPtr
+PgSqlHostDataSource::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
+ PgSqlHostDataSourceImpl::GET_HOST_SUBID4_DHCPID,
+ ctx->host_ipv4_exchange_));
+}
+
+ConstHostPtr
+PgSqlHostDataSource::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ if (!address.isV4()) {
+ isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
+ " wrong address type, address supplied is an IPv6 address");
+ }
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id
+ bind_array->add(subnet_id);
+
+ // Add the address
+ bind_array->add(address);
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
+ bind_array, ctx->host_ipv4_exchange_, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ if (!address.isV4()) {
+ isc_throw(BadValue, "PgSqlHostDataSource::get4(id, address) - "
+ " wrong address type, address supplied is an IPv6 address");
+ }
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id
+ bind_array->add(subnet_id);
+
+ // Add the address
+ bind_array->add(address);
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID_ADDR,
+ bind_array, ctx->host_ipv4_exchange_, collection, false);
+ return (collection);
+}
+
+ConstHostPtr
+PgSqlHostDataSource::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ return (impl_->getHost(ctx, subnet_id, identifier_type, identifier_begin, identifier_len,
+ PgSqlHostDataSourceImpl::GET_HOST_SUBID6_DHCPID,
+ ctx->host_ipv6_exchange_));
+}
+
+ConstHostPtr
+PgSqlHostDataSource::get6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const {
+ if (!prefix.isV6()) {
+ isc_throw(BadValue, "PgSqlHostDataSource::get6(prefix, prefix_len): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the prefix
+ bind_array->add(prefix);
+
+ // Add the prefix length
+ bind_array->add(prefix_len);
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_PREFIX,
+ bind_array, ctx->host_ipv6_exchange_, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostPtr
+PgSqlHostDataSource::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ if (!address.isV6()) {
+ isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id
+ bind_array->add(subnet_id);
+
+ // Add the prefix
+ bind_array->add(address);
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
+ bind_array, ctx->host_ipv6_exchange_, collection, true);
+
+ // Return single record if present, else clear the host.
+ ConstHostPtr result;
+ if (!collection.empty()) {
+ result = *collection.begin();
+ }
+
+ return (result);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ if (!address.isV6()) {
+ isc_throw(BadValue, "PgSqlHostDataSource::get6(id, address): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the subnet id
+ bind_array->add(subnet_id);
+
+ // Add the prefix
+ bind_array->add(address);
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_SUBID6_ADDR,
+ bind_array, ctx->host_ipv6_exchange_, collection, false);
+ return (collection);
+}
+
+ConstHostCollection
+PgSqlHostDataSource::getAll6(const IOAddress& address) const {
+ if (!address.isV6()) {
+ isc_throw(BadValue, "PgSqlHostDataSource::get6(address): "
+ "wrong address type, address supplied is an IPv4 address");
+ }
+
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // Set up the WHERE clause value
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Add the prefix
+ bind_array->add(address);
+
+ ConstHostCollection collection;
+ impl_->getHostCollection(ctx, PgSqlHostDataSourceImpl::GET_HOST_ADDR6,
+ bind_array, ctx->host_ipv6_exchange_, collection, false);
+ return (collection);
+}
+
+void
+PgSqlHostDataSource::update(HostPtr const& host) {
+ // Get a context.
+ PgSqlHostContextAlloc const context(*impl_);
+ PgSqlHostContextPtr ctx(context.ctx_);
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+
+ // Initiate PostgreSQL transaction as we will have to make multiple queries
+ // to update host information into multiple tables. If that fails on
+ // any stage, the transaction will be rolled back by the destructor of
+ // the PgSqlTransaction class.
+ PgSqlTransaction transaction(ctx->conn_);
+
+ // As much as having dedicated prepared statements for updating tables would be consistent with
+ // the implementation of other commands, it's difficult if not impossible to cover all cases for
+ // updating the host to exactly as is described in the command, which may involve inserts and
+ // deletes alongside updates. So let's delete and add. The delete cascades into all tables. The
+ // add explicitly adds into all tables.
+ BaseHostDataSource::update(host);
+
+ // Everything went fine, so explicitly commit the transaction.
+ transaction.commit();
+}
+
+// Miscellaneous database methods.
+
+std::string
+PgSqlHostDataSource::getName() const {
+ std::string name = "";
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ try {
+ name = ctx->conn_.getParameter("name");
+ } catch (...) {
+ // Return an empty name
+ }
+ return (name);
+}
+
+std::string
+PgSqlHostDataSource::getDescription() const {
+ return (std::string("Host data source that stores host information"
+ "in PostgreSQL database"));
+}
+
+std::pair<uint32_t, uint32_t>
+PgSqlHostDataSource::getVersion() const {
+ return(impl_->getVersion());
+}
+
+void
+PgSqlHostDataSource::commit() {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+ ctx->conn_.commit();
+}
+
+void
+PgSqlHostDataSource::rollback() {
+ // Get a context
+ PgSqlHostContextAlloc get_context(*impl_);
+ PgSqlHostContextPtr ctx = get_context.ctx_;
+
+ // If operating in read-only mode, throw exception.
+ impl_->checkReadOnly(ctx);
+ ctx->conn_.rollback();
+}
+
+bool
+PgSqlHostDataSource::setIPReservationsUnique(const bool unique) {
+ impl_->ip_reservations_unique_ = unique;
+ return (true);
+}
+
+bool
+PgSqlHostDataSource::isUnusable() {
+ return (impl_->unusable_);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/pgsql_host_data_source.h b/src/lib/dhcpsrv/pgsql_host_data_source.h
new file mode 100644
index 0000000..51b4856
--- /dev/null
+++ b/src/lib/dhcpsrv/pgsql_host_data_source.h
@@ -0,0 +1,588 @@
+// Copyright (C) 2016-2023 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 PGSQL_HOST_DATA_SOURCE_H
+#define PGSQL_HOST_DATA_SOURCE_H
+
+#include <database/database_connection.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+
+namespace isc {
+namespace dhcp {
+
+/// Forward declaration to the implementation of the @ref PgSqlHostDataSource.
+class PgSqlHostDataSourceImpl;
+
+/// @brief Type of pointers to PgSqlHostDataSourceImpl.
+typedef boost::shared_ptr<PgSqlHostDataSourceImpl> PgSqlHostDataSourceImplPtr;
+
+/// Forward declaration for the thread context for the manager pool.
+class PgSqlHostContext;
+
+/// @brief Type of pointers to contexts.
+typedef boost::shared_ptr<PgSqlHostContext> PgSqlHostContextPtr;
+
+/// @brief PostgreSQL Host Data Source
+///
+/// This class implements the @ref isc::dhcp::BaseHostDataSource interface to
+/// the PostgreSQL database. Use of this backend presupposes that a PostgreSQL
+/// database is available and that the Kea schema has been created within it.
+///
+/// Reservations are uniquely identified by identifier type and value.
+/// The currently supported values are defined in @ref Host::IdentifierType
+/// as well as in host_identifier_table:
+///
+/// - IDENT_HWADDR
+/// - IDENT_DUID
+/// - IDENT_CIRCUIT_ID
+/// - IDENT_CLIENT_ID
+///
+class PgSqlHostDataSource : public BaseHostDataSource {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Uses the following keywords in the parameters passed to it to
+ /// connect to the database:
+ /// - name - Name of the database to which to connect (mandatory)
+ /// - host - Host to which to connect (optional, defaults to "localhost")
+ /// - user - Username under which to connect (optional)
+ /// - password - Password for "user" on the database (optional)
+ ///
+ /// If the database is successfully opened, the version number in the
+ /// schema_version table will be checked against hard-coded value in
+ /// the implementation file.
+ ///
+ /// Finally, all the SQL commands are pre-compiled.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::db::NoDatabaseName Mandatory database name not given
+ /// @throw isc::db::DbOpenError Error opening the database
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ PgSqlHostDataSource(const db::DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Virtual destructor.
+ ///
+ /// Frees database resources and closes the database connection through
+ /// the destruction of member impl_.
+ virtual ~PgSqlHostDataSource();
+
+ /// @brief Return backend parameters
+ ///
+ /// Returns the backend parameters
+ ///
+ /// @return Parameters of the backend.
+ virtual isc::db::DatabaseConnection::ParameterMap getParameters() const;
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// The method will insert the given host and all of its children (v4
+ /// options, v6 options, and v6 reservations) into the database. It
+ /// relies on constraints defined as part of the PostgreSQL schema to
+ /// defend against duplicate entries and to ensure referential
+ /// integrity.
+ ///
+ /// Violation of any of these constraints for a host will result in a
+ /// DuplicateEntry exception:
+ ///
+ /// -# IPV4_ADDRESS and DHCP4_SUBNET_ID combination must be unique
+ /// -# IPV6 ADDRESS and PREFIX_LEN combination must be unique
+ /// -# DHCP ID, DHCP ID TYPE, and DHCP4_SUBNET_ID combination must be unique
+ /// -# DHCP ID, DHCP ID TYPE, and DHCP6_SUBNET_ID combination must be unique
+ ///
+ /// In addition, violating the following referential constraints will
+ /// a DbOperationError exception:
+ ///
+ /// -# DHCP ID TYPE must be defined in the HOST_IDENTIFIER_TYPE table
+ /// -# For DHCP4 Options:
+ /// -# HOST_ID must exist with HOSTS
+ /// -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
+ /// -# For DHCP6 Options:
+ /// -# HOST_ID must exist with HOSTS
+ /// -# SCOPE_ID must be defined in DHCP_OPTION_SCOPE
+ /// -# For IPV6 Reservations:
+ /// -# HOST_ID must exist with HOSTS
+ /// -# Address and Prefix Length must be unique (DuplicateEntry)
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ /// @throw DuplicateEntry or DbOperationError dependent on the constraint
+ /// violation
+ virtual void add(const HostPtr& host);
+
+ /// @brief Attempts to delete hosts by (subnet-id, address)
+ ///
+ /// This method supports both v4 and v6.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet4-id, identifier type, identifier)
+ ///
+ /// This method supports v4 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet6-id, identifier type, identifier)
+ ///
+ /// This method supports v6 hosts only.
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len);
+
+ /// @brief Return all hosts connected to any subnet for which reservations
+ /// have been made using a specified identifier.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for a specified identifier. This method may return multiple hosts
+ /// because a particular client may have reservations in multiple subnets.
+ ///
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// This method returns all @ref Host objects which represent reservations
+ /// in a specified subnet. Global reservations are returned for the
+ /// subnet id 0.
+ ///
+ /// @param subnet_id subnet identifier to filter by
+ ///
+ /// @return Collection of const @ref Host objects.
+ virtual ConstHostCollection getAll4(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// This method returns all @ref Host objects which represent reservations
+ /// in a specified subnet. Global reservations are returned for the
+ /// subnet id 0.
+ ///
+ /// @param subnet_id subnet identifier to filter by
+ ///
+ /// @return Collection of const @ref Host objects.
+ virtual ConstHostCollection getAll6(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname.
+ ///
+ /// PostgreSQL uses the hosts_by_hostname index on LOWER(hostname).
+ ///
+ /// @param hostname The lower case hostname.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAllbyHostname(const std::string& hostname) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const;
+
+ /// @brief Returns range of hosts in a DHCPv4 subnet.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts in a DHCPv6 subnet.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of const @c Host objects (may be empty).
+ virtual ConstHostCollection getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAll4(const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// One of the use cases for this method is to detect collisions between
+ /// dynamically allocated addresses and reserved addresses. When the new
+ /// address is assigned to a client, the allocation mechanism should check
+ /// if this address is not reserved for some other host and do not allocate
+ /// this address if reservation is present.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Const @c Host object using a specified IPv4 address.
+ /// @throw BadValue is given an IPv6 address
+ virtual ConstHostPtr get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv4 subnet and having
+ /// a reservation for a specified address.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv4 address within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv4 reservation. In that case, the number of hosts returned
+ /// by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv4 address in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv4 address is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv4 address is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Const @c Host object using a specified IPv6 prefix.
+ virtual ConstHostPtr get6(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address or prefix.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Const @c Host object using a specified IPv6 address/prefix.
+ virtual ConstHostPtr get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv6 subnet and having
+ /// a reservation for a specified address or delegated prefix (lease).
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix. In that case, the number of hosts
+ /// returned by this function is 0 or 1.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts having a reservation for a specified
+ /// address or delegated prefix (lease) in all subnets.
+ ///
+ /// In most cases it is desired that there is at most one reservation
+ /// for a given IPv6 lease within a subnet. In a default configuration,
+ /// the backend does not allow for inserting more than one host with
+ /// the same IPv6 address or prefix.
+ ///
+ /// If the backend is configured to allow multiple hosts with reservations
+ /// for the same IPv6 lease in the given subnet, this method can return
+ /// more than one host per subnet.
+ ///
+ /// The typical use case when a single IPv6 lease is reserved for multiple
+ /// hosts is when these hosts represent different interfaces of the same
+ /// machine and each interface comes with a different MAC address. In that
+ /// case, the same IPv6 lease is assigned regardless of which interface is
+ /// used by the DHCP client to communicate with the server.
+ ///
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection getAll6(const asiolink::IOAddress& address) const;
+
+ /// @brief Implements @ref BaseHostDataSource::update() for PostgreSQL.
+ ///
+ /// Attempts to update an existing host entry.
+ ///
+ /// @param host the host up to date with the requested changes
+ void update(HostPtr const& host);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of database as the string "postgresql". This is
+ /// same value as used for configuration purposes.
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return (std::string("postgresql"));
+ }
+
+ /// @brief Returns the name of the open database
+ ///
+ /// @return String containing the name of the database
+ virtual std::string getName() const;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const;
+
+ /// @brief Returns backend version.
+ ///
+ /// The method is called by the constructor after opening the database
+ /// but prior to preparing SQL statements, to verify that the schema version
+ /// is correct. Thus it must not rely on a pre-prepared statement or
+ /// formal statement execution error checking.
+ ///
+ /// @return Version number stored in the database, as a pair of unsigned
+ /// integers. "first" is the major version number, "second" the
+ /// minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database
+ /// has failed.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations.
+ virtual void commit();
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations.
+ virtual void rollback();
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address/subnet does not exist. In some cases it may be
+ /// required to allow non-unique IP reservations, e.g. in the case when a
+ /// host has several interfaces and independently of which interface is used
+ /// by this host to communicate with the DHCP server the same IP address
+ /// should be assigned. In this case the @c unique value should be set to
+ /// false to disable the checks for uniqueness on the backend side.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique within the subnet or can be non-unique.
+ /// @return always true because this backend supports both the case when
+ /// the addresses must be unique and when they may be non-unique.
+ virtual bool setIPReservationsUnique(const bool unique);
+
+ /// @brief Flag which indicates if the host manager has at least one
+ /// unusable connection.
+ ///
+ /// @return true if there is at least one unusable connection, false
+ /// otherwise
+ virtual bool isUnusable();
+
+ /// @brief Context RAII Allocator.
+ class PgSqlHostContextAlloc {
+ public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor takes a context of the pool if one is available
+ /// or creates a new one.
+ ///
+ /// @param mgr A parent instance
+ PgSqlHostContextAlloc(PgSqlHostDataSourceImpl& mgr);
+
+ /// @brief Destructor
+ ///
+ /// This destructor puts back the context in the pool.
+ ~PgSqlHostContextAlloc();
+
+ /// @brief The context
+ PgSqlHostContextPtr ctx_;
+
+ private:
+ /// @brief The manager
+ PgSqlHostDataSourceImpl& mgr_;
+ };
+
+private:
+ /// @brief Pointer to the implementation of the @ref PgSqlHostDataSource.
+ PgSqlHostDataSourceImplPtr impl_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // PGSQL_HOST_DATA_SOURCE_H
diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.cc b/src/lib/dhcpsrv/pgsql_lease_mgr.cc
new file mode 100644
index 0000000..28c4109
--- /dev/null
+++ b/src/lib/dhcpsrv/pgsql_lease_mgr.cc
@@ -0,0 +1,3302 @@
+// Copyright (C) 2014-2023 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 <asiolink/addr_utilities.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/dhcpsrv_exceptions.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/static_assert.hpp>
+
+#include <iomanip>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <time.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief Catalog of all the SQL statements currently supported. Note
+/// that the order columns appear in statement body must match the order they
+/// that the occur in the table. This does not apply to the where clause.
+PgSqlTaggedStatement tagged_statements[] = {
+ // DELETE_LEASE4
+ { 2, { OID_INT8, OID_TIMESTAMP },
+ "delete_lease4",
+ "DELETE FROM lease4 WHERE address = $1 AND expire = $2" },
+
+ // DELETE_LEASE4_STATE_EXPIRED
+ { 2, { OID_INT8, OID_TIMESTAMP },
+ "delete_lease4_state_expired",
+ "DELETE FROM lease4 "
+ "WHERE state = $1 AND expire < $2" },
+
+ // DELETE_LEASE6
+ { 2, { OID_VARCHAR, OID_TIMESTAMP },
+ "delete_lease6",
+ "DELETE FROM lease6 WHERE address = cast($1 as inet) AND expire = $2"},
+
+ // DELETE_LEASE6_STATE_EXPIRED
+ { 2, { OID_INT8, OID_TIMESTAMP },
+ "delete_lease6_state_expired",
+ "DELETE FROM lease6 "
+ "WHERE state = $1 AND expire < $2" },
+
+ // GET_LEASE4
+ { 0, { OID_NONE },
+ "get_lease4",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4" },
+
+ // GET_LEASE4_ADDR
+ { 1, { OID_INT8 },
+ "get_lease4_addr",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE address = $1" },
+
+ // GET_LEASE4_CLIENTID
+ { 1, { OID_BYTEA },
+ "get_lease4_clientid",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE client_id = $1" },
+
+ // GET_LEASE4_CLIENTID_SUBID
+ { 2, { OID_BYTEA, OID_INT8 },
+ "get_lease4_clientid_subid",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE client_id = $1 AND subnet_id = $2" },
+
+ // GET_LEASE4_HWADDR
+ { 1, { OID_BYTEA },
+ "get_lease4_hwaddr",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE hwaddr = $1" },
+
+ // GET_LEASE4_HWADDR_SUBID
+ { 2, { OID_BYTEA, OID_INT8 },
+ "get_lease4_hwaddr_subid",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE hwaddr = $1 AND subnet_id = $2" },
+
+ // GET_LEASE4_PAGE
+ { 2, { OID_INT8, OID_INT8 },
+ "get_lease4_page",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE address > $1 "
+ "ORDER BY address "
+ "LIMIT $2" },
+
+ // GET_LEASE4_UCTX_PAGE
+ { 2, { OID_INT8, OID_INT8 },
+ "get_lease4_uctx_page",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE address > $1 AND user_context IS NOT NULL "
+ "ORDER BY address "
+ "LIMIT $2" },
+
+ // GET_LEASE4_SUBID
+ { 1, { OID_INT8 },
+ "get_lease4_subid",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE subnet_id = $1" },
+
+ // GET_LEASE4_HOSTNAME
+ { 1, { OID_VARCHAR },
+ "get_lease4_hostname",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE lower(hostname) = $1" },
+
+ // GET_LEASE4_EXPIRE
+ { 3, { OID_INT8, OID_TIMESTAMP, OID_INT8 },
+ "get_lease4_expire",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE state != $1 AND valid_lifetime != 4294967295 AND expire < $2 "
+ "ORDER BY expire "
+ "LIMIT $3" },
+
+ // GET_LEASE4_RELAYID
+ { 3, { OID_BYTEA, OID_INT8, OID_INT8 },
+ "get_lease4_relayid",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = $1 and address > $2 "
+ "ORDER BY address "
+ "LIMIT $3" },
+
+ // GET_LEASE4_RELAYID_QST
+ { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+ "get_lease4_relayid_qst",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = $1 and address > $2 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) >= $3 "
+ "ORDER BY address "
+ "LIMIT $4" },
+
+ // GET_LEASE4_RELAYID_QSET
+ { 5, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8, OID_INT8 },
+ "get_lease4_relayid_qset",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = $1 and address > $2 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) >= $3 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) <= $4 "
+ "ORDER BY address "
+ "LIMIT $5" },
+
+ // GET_LEASE4_RELAYID_QET
+ { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+ "get_lease4_relayid_qet",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE relay_id = $1 and address > $2 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) <= $3 "
+ "ORDER BY address "
+ "LIMIT $4" },
+
+ // GET_LEASE4_REMOTEID
+ { 3, { OID_BYTEA, OID_INT8, OID_INT8 },
+ "get_lease4_remoteid",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = $1 and address > $2 "
+ "ORDER BY address "
+ "LIMIT $3" },
+
+ // GET_LEASE4_REMOTEID_QST
+ { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+ "get_lease4_remoteid_qst",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = $1 and address > $2 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) >= $3 "
+ "ORDER BY address "
+ "LIMIT $4" },
+
+ // GET_LEASE4_REMOTEID_QSET
+ { 5, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8, OID_INT8 },
+ "get_lease4_remoteid_qset",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = $1 and address > $2 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) >= $3 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) <= $4 "
+ "ORDER BY address "
+ "LIMIT $5" },
+
+ // GET_LEASE4_REMOTEID_QET
+ { 4, { OID_BYTEA, OID_INT8, OID_INT8, OID_INT8 },
+ "get_lease4_remoteid_qet",
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, extract(epoch from expire)::bigint, subnet_id, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id "
+ "FROM lease4 "
+ "WHERE remote_id = $1 and address > $2 "
+ "and EXTRACT(EPOCH FROM expire) - (CASE valid_lifetime WHEN 4294967295 "
+ "THEN 0 ELSE valid_lifetime END) <= $3 "
+ "ORDER BY address "
+ "LIMIT $4" },
+
+ // GET_LEASE6
+ { 0, { OID_NONE },
+ "get_lease6",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "ORDER BY address "},
+
+ // GET_LEASE6_ADDR
+ { 2, { OID_VARCHAR, OID_INT2 },
+ "get_lease6_addr",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address = cast($1 as inet) AND lease_type = $2"},
+
+ // GET_LEASE6_DUID_IAID
+ { 3, { OID_BYTEA, OID_INT8, OID_INT2 },
+ "get_lease6_duid_iaid",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE duid = $1 AND iaid = $2 AND lease_type = $3" },
+
+ // GET_LEASE6_DUID_IAID_SUBID
+ { 4, { OID_INT2, OID_BYTEA, OID_INT8, OID_INT8 },
+ "get_lease6_duid_iaid_subid",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE lease_type = $1 "
+ "AND duid = $2 AND iaid = $3 AND subnet_id = $4" },
+
+ // GET_LEASE6_PAGE
+ { 2, { OID_VARCHAR, OID_INT8 },
+ "get_lease6_page",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address > cast($1 as inet) "
+ "ORDER BY address "
+ "LIMIT $2"},
+
+ // GET_LEASE6_UCTX_PAGE
+ { 2, { OID_VARCHAR, OID_INT8 },
+ "get_lease6_uctx_page",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address > cast($1 as inet) AND user_context IS NOT NULL "
+ "ORDER BY address "
+ "LIMIT $2" },
+
+ // GET_LEASE6_SUBID
+ { 1, { OID_INT8 },
+ "get_lease6_subid",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE subnet_id = $1" },
+
+ // GET_LEASE6_DUID
+ { 1, { OID_BYTEA },
+ "get_lease6_duid",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE duid = $1" },
+
+ // GET_LEASE6_HOSTNAME
+ { 1, { OID_VARCHAR },
+ "get_lease6_hostname",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE lower(hostname) = $1" },
+
+ // GET_LEASE6_EXPIRE
+ { 3, { OID_INT8, OID_TIMESTAMP, OID_INT8 },
+ "get_lease6_expire",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, "
+ "fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE state != $1 AND valid_lifetime != 4294967295 AND expire < $2 "
+ "ORDER BY expire "
+ "LIMIT $3" },
+
+ // GET_LEASE6_LINK
+ { 3, { OID_VARCHAR, OID_VARCHAR, OID_INT8 },
+ "get_lease6_link",
+ "SELECT host(address), duid, valid_lifetime, "
+ "extract(epoch from expire)::bigint, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id "
+ "FROM lease6 "
+ "WHERE address BETWEEN cast($1 as inet) and cast($2 as inet) "
+ "ORDER BY address "
+ "LIMIT $3" },
+
+ // INSERT_LEASE4
+ { 14, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
+ OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT, OID_BYTEA,
+ OID_BYTEA, OID_INT8 },
+ "insert_lease4",
+ "INSERT INTO lease4(address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id, fqdn_fwd, fqdn_rev, hostname, "
+ "state, user_context, relay_id, remote_id, pool_id) "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)" },
+
+ // INSERT_LEASE6
+ { 18, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
+ OID_INT8, OID_INT2, OID_INT8, OID_INT2, OID_BOOL, OID_BOOL,
+ OID_VARCHAR, OID_BYTEA, OID_INT2, OID_INT2, OID_INT8, OID_TEXT,
+ OID_INT8},
+ "insert_lease6",
+ "INSERT INTO lease6(address, duid, valid_lifetime, "
+ "expire, subnet_id, pref_lifetime, "
+ "lease_type, iaid, prefix_len, fqdn_fwd, fqdn_rev, hostname, "
+ "hwaddr, hwtype, hwaddr_source, "
+ "state, user_context, pool_id) "
+ "VALUES (cast($1 as inet), $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)" },
+
+ // UPDATE_LEASE4
+ { 16, { OID_INT8, OID_BYTEA, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8,
+ OID_BOOL, OID_BOOL, OID_VARCHAR, OID_INT8, OID_TEXT, OID_BYTEA,
+ OID_BYTEA, OID_INT8, OID_INT8, OID_TIMESTAMP },
+ "update_lease4",
+ "UPDATE lease4 SET address = $1, hwaddr = $2, "
+ "client_id = $3, valid_lifetime = $4, expire = $5, "
+ "subnet_id = $6, fqdn_fwd = $7, fqdn_rev = $8, hostname = $9, "
+ "state = $10, user_context = $11, relay_id = $12, remote_id = $13, pool_id = $14 "
+ "WHERE address = $15 AND expire = $16" },
+
+ // UPDATE_LEASE6
+ { 20, { OID_VARCHAR, OID_BYTEA, OID_INT8, OID_TIMESTAMP, OID_INT8, OID_INT8,
+ OID_INT2, OID_INT8, OID_INT2, OID_BOOL, OID_BOOL, OID_VARCHAR,
+ OID_BYTEA, OID_INT2, OID_INT2,
+ OID_INT8, OID_TEXT, OID_INT8, OID_VARCHAR, OID_TIMESTAMP },
+ "update_lease6",
+ "UPDATE lease6 SET address = cast($1 as inet), duid = $2, "
+ "valid_lifetime = $3, expire = $4, subnet_id = $5, "
+ "pref_lifetime = $6, lease_type = $7, iaid = $8, "
+ "prefix_len = $9, fqdn_fwd = $10, fqdn_rev = $11, hostname = $12, "
+ "hwaddr = $13, hwtype = $14, hwaddr_source = $15, "
+ "state = $16, user_context = $17, pool_id = $18 "
+ "WHERE address = cast($19 as inet) AND expire = $20" },
+
+ // ALL_LEASE4_STATS
+ { 0, { OID_NONE },
+ "all_lease4_stats",
+ "SELECT subnet_id, state, leases as state_count"
+ " FROM lease4_stat ORDER BY subnet_id, state" },
+
+ // SUBNET_LEASE4_STATS
+ { 1, { OID_INT8 },
+ "subnet_lease4_stats",
+ "SELECT subnet_id, state, leases as state_count"
+ " FROM lease4_stat "
+ " WHERE subnet_id = $1 "
+ " ORDER BY state" },
+
+ // SUBNET_RANGE_LEASE4_STATS
+ { 2, { OID_INT8, OID_INT8 },
+ "subnet_range_lease4_stats",
+ "SELECT subnet_id, state, leases as state_count"
+ " FROM lease4_stat "
+ " WHERE subnet_id >= $1 and subnet_id <= $2 "
+ " ORDER BY subnet_id, state" },
+
+ // ALL_POOL_LEASE4_STATS
+ { 0, { OID_NONE },
+ "all_pool_lease4_stats",
+ "SELECT subnet_id, pool_id, state, leases as state_count"
+ " FROM lease4_pool_stat ORDER BY subnet_id, pool_id, state" },
+
+ // ALL_LEASE6_STATS,
+ { 0, { OID_NONE },
+ "all_lease6_stats",
+ "SELECT subnet_id, lease_type, state, leases as state_count"
+ " FROM lease6_stat ORDER BY subnet_id, lease_type, state" },
+
+ // SUBNET_LEASE6_STATS
+ { 1, { OID_INT8 },
+ "subnet_lease6_stats",
+ "SELECT subnet_id, lease_type, state, leases as state_count"
+ " FROM lease6_stat "
+ " WHERE subnet_id = $1 "
+ " ORDER BY lease_type, state" },
+
+ // SUBNET_RANGE_LEASE6_STATS
+ { 2, { OID_INT8, OID_INT8 },
+ "subnet_range_lease6_stats",
+ "SELECT subnet_id, lease_type, state, leases as state_count"
+ " FROM lease6_stat "
+ " WHERE subnet_id >= $1 and subnet_id <= $2 "
+ " ORDER BY subnet_id, lease_type, state" },
+
+ // ALL_POOL_LEASE6_STATS,
+ { 0, { OID_NONE },
+ "all_pool_lease6_stats",
+ "SELECT subnet_id, pool_id, lease_type, state, leases as state_count"
+ " FROM lease6_pool_stat ORDER BY subnet_id, pool_id, lease_type, state" },
+
+ // CHECK_LEASE4_LIMITS
+ { 1, { OID_TEXT },
+ "check_lease4_limits",
+ "SELECT checkLease4Limits($1)" },
+
+ // CHECK_LEASE6_LIMITS
+ { 1, { OID_TEXT },
+ "check_lease6_limits",
+ "SELECT checkLease6Limits($1)" },
+
+ // IS_JSON_SUPPORTED
+ { 0, { OID_NONE },
+ "is_json_supported",
+ "SELECT isJsonSupported()" },
+
+ // GET_LEASE4_COUNT_BY_CLASS
+ { 1, { OID_VARCHAR },
+ "get_lease4_count_by_class",
+ "SELECT leases "
+ "FROM lease4_stat_by_client_class "
+ "WHERE client_class = $1" },
+
+ // GET_LEASE6_COUNT_BY_CLASS
+ { 2, { OID_VARCHAR, OID_INT2 },
+ "get_lease6_count_by_class",
+ "SELECT leases "
+ "FROM lease6_stat_by_client_class "
+ "WHERE client_class = $1 AND lease_type = $2" },
+
+ // End of list sentinel
+ { 0, { 0 }, NULL, NULL }
+};
+
+} // namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Base class for marshalling leases to and from PostgreSQL.
+///
+/// Provides the common functionality to set up binding information between
+/// lease objects in the program and their database representation in the
+/// database.
+class PgSqlLeaseExchange : public PgSqlExchange {
+public:
+
+ PgSqlLeaseExchange()
+ : addr_str_(""), hwaddr_length_(0), hwaddr_(hwaddr_length_),
+ valid_lifetime_(0), valid_lifetime_str_(""), expire_(0),
+ expire_str_(""), subnet_id_(0), subnet_id_str_(""), pool_id_(0),
+ pool_id_str_(""), cltt_(0), fqdn_fwd_(false), fqdn_rev_(false),
+ hostname_(""), state_str_(""), user_context_(""), addr_bin_(16) {
+ }
+
+ virtual ~PgSqlLeaseExchange() {}
+
+protected:
+
+ /// @brief Common Instance members used for binding and conversion
+ //@{
+ std::string addr_str_;
+ size_t hwaddr_length_;
+ std::vector<uint8_t> hwaddr_;
+ uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN];
+ uint32_t valid_lifetime_;
+ std::string valid_lifetime_str_;
+ time_t expire_;
+ std::string expire_str_;
+ uint32_t subnet_id_;
+ std::string subnet_id_str_;
+ uint32_t pool_id_;
+ std::string pool_id_str_;
+ time_t cltt_;
+ bool fqdn_fwd_;
+ bool fqdn_rev_;
+ std::string hostname_;
+ std::string state_str_;
+ std::string user_context_;
+ std::vector<uint8_t> addr_bin_;
+ //@}
+};
+
+/// @brief Supports exchanging IPv4 leases with PostgreSQL.
+class PgSqlLease4Exchange : public PgSqlLeaseExchange {
+private:
+
+ /// @brief Column numbers for each column in the Lease4 table.
+ /// These are used for both retrieving data and for looking up
+ /// column labels for logging. Note that their numeric order
+ /// MUST match that of the column order in the Lease4 table.
+ //@{
+ static const size_t ADDRESS_COL = 0;
+ static const size_t HWADDR_COL = 1;
+ static const size_t CLIENT_ID_COL = 2;
+ static const size_t VALID_LIFETIME_COL = 3;
+ static const size_t EXPIRE_COL = 4;
+ static const size_t SUBNET_ID_COL = 5;
+ static const size_t FQDN_FWD_COL = 6;
+ static const size_t FQDN_REV_COL = 7;
+ static const size_t HOSTNAME_COL = 8;
+ static const size_t STATE_COL = 9;
+ static const size_t USER_CONTEXT_COL = 10;
+ static const size_t RELAY_ID_COL = 11;
+ static const size_t REMOTE_ID_COL = 12;
+ static const size_t POOL_ID_COL = 13;
+ //@}
+ /// @brief Number of columns in the table holding DHCPv4 leases.
+ static const size_t LEASE_COLUMNS = 14;
+
+public:
+
+ /// @brief Constructor
+ PgSqlLease4Exchange()
+ : lease_(), addr4_(0), client_id_length_(0),
+ relay_id_length_(0), remote_id_length_(0) {
+
+ BOOST_STATIC_ASSERT(13 < LEASE_COLUMNS);
+
+ memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
+ memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
+ memset(relay_id_buffer_, 0, sizeof(relay_id_buffer_));
+ memset(remote_id_buffer_, 0, sizeof(remote_id_buffer_));
+
+ // Set the column names (for error messages)
+ columns_.push_back("address");
+ columns_.push_back("hwaddr");
+ columns_.push_back("client_id");
+ columns_.push_back("valid_lifetime");
+ columns_.push_back("expire");
+ columns_.push_back("subnet_id");
+ columns_.push_back("fqdn_fwd");
+ columns_.push_back("fqdn_rev");
+ columns_.push_back("hostname");
+ columns_.push_back("state");
+ columns_.push_back("user_context");
+ columns_.push_back("relay_id");
+ columns_.push_back("remote_id");
+ columns_.push_back("pool_id");
+ }
+
+ /// @brief Creates the bind array for sending Lease4 data to the database.
+ ///
+ /// Converts each Lease4 member into the appropriate form and adds it
+ /// to the bind array. Note that the array additions must occur in the
+ /// order the columns are specified in the SQL statement. By convention
+ /// all columns in the table are explicitly listed in the SQL statement(s)
+ /// in the same order as they occur in the table.
+ ///
+ /// @param lease Lease4 object that is to be written to the database
+ /// @param[out] bind_array array to populate with the lease data values
+ ///
+ /// @throw DbOperationError if bind_array cannot be populated.
+ void createBindForSend(const Lease4Ptr& lease, PsqlBindArray& bind_array) {
+ if (!lease) {
+ isc_throw(BadValue, "createBindForSend:: Lease4 object is NULL");
+ }
+
+ // Store lease object to ensure it remains valid.
+ lease_ = lease;
+
+ try {
+ addr_str_ = boost::lexical_cast<std::string>(lease->addr_.toUint32());
+ bind_array.add(addr_str_);
+
+ if (lease->hwaddr_ && !lease->hwaddr_->hwaddr_.empty()) {
+ // PostgreSql does not provide MAX on variable length types
+ // so we have to enforce it ourselves.
+ if (lease->hwaddr_->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
+ isc_throw(DbOperationError, "Hardware address length : "
+ << lease_->hwaddr_->hwaddr_.size()
+ << " exceeds maximum allowed of: "
+ << HWAddr::MAX_HWADDR_LEN);
+ }
+ bind_array.add(lease->hwaddr_->hwaddr_);
+ } else {
+ bind_array.add("");
+ }
+
+ if (lease->client_id_) {
+ bind_array.add(lease->client_id_->getClientId());
+ } else {
+ bind_array.add("");
+ }
+
+ valid_lifetime_str_ = boost::lexical_cast<std::string>(lease->valid_lft_);
+ bind_array.add(valid_lifetime_str_);
+
+ // The lease structure holds the client last transmission time (cltt_)
+ // For convenience for external tools, this is converted to lease
+ // expiry time (expire). The relationship is given by:
+ // expire = cltt_ + valid_lft_
+ // Avoid overflow with infinite valid lifetime by using
+ // expire = cltt_ when valid_lft_ = 0xffffffff
+ if (lease_->valid_lft_ == Lease::INFINITY_LFT) {
+ expire_str_ = convertToDatabaseTime(lease->cltt_, 0);
+ } else {
+ expire_str_ = convertToDatabaseTime(lease->cltt_,
+ lease_->valid_lft_);
+ }
+ bind_array.add(expire_str_);
+
+ subnet_id_str_ = boost::lexical_cast<std::string>(lease->subnet_id_);
+ bind_array.add(subnet_id_str_);
+
+ bind_array.add(lease->fqdn_fwd_);
+
+ bind_array.add(lease->fqdn_rev_);
+
+ bind_array.add(lease->hostname_);
+
+ state_str_ = boost::lexical_cast<std::string>(lease->state_);
+ bind_array.add(state_str_);
+
+ ConstElementPtr ctx = lease->getContext();
+ if (ctx) {
+ user_context_ = ctx->str();
+ } else {
+ user_context_ = "";
+ }
+ bind_array.add(user_context_);
+
+ if (!lease->relay_id_.empty()) {
+ bind_array.add(lease->relay_id_);
+ } else {
+ bind_array.addNull();
+ }
+
+ if (!lease->remote_id_.empty()) {
+ bind_array.add(lease->remote_id_);
+ } else {
+ bind_array.addNull();
+ }
+
+ pool_id_str_ = boost::lexical_cast<std::string>(lease->pool_id_);
+ bind_array.add(pool_id_str_);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from Lease4: "
+ << lease_->addr_.toText() << ", reason: " << ex.what());
+ }
+ }
+
+ /// @brief Creates a Lease4 object from a given row in a result set.
+ ///
+ /// @param r result set containing one or rows from the Lease4 table
+ /// @param row row number within the result set from to create the Lease4
+ /// object.
+ ///
+ /// @return Lease4Ptr to the newly created Lease4 object
+ /// @throw DbOperationError if the lease cannot be created.
+ Lease4Ptr convertFromDatabase(const PgSqlResult& r, int row) {
+ try {
+ getColumnValue(r, row, ADDRESS_COL, addr4_);
+
+ convertFromBytea(r, row, HWADDR_COL, hwaddr_buffer_,
+ sizeof(hwaddr_buffer_), hwaddr_length_);
+
+ convertFromBytea(r, row, CLIENT_ID_COL, client_id_buffer_,
+ sizeof(client_id_buffer_), client_id_length_);
+
+ getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_);
+
+ expire_ = convertFromDatabaseTime(getRawColumnValue(r, row,
+ EXPIRE_COL));
+
+ getColumnValue(r, row, SUBNET_ID_COL, subnet_id_);
+
+ // Recover from overflow (see createBindForSend)
+ if (valid_lifetime_ == Lease::INFINITY_LFT) {
+ cltt_ = expire_;
+ } else {
+ cltt_ = expire_ - valid_lifetime_;
+ }
+
+ getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_);
+
+ getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_);
+
+ hostname_ = getRawColumnValue(r, row, HOSTNAME_COL);
+
+ uint32_t state;
+ getColumnValue(r, row, STATE_COL, state);
+
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_buffer_, hwaddr_length_,
+ HTYPE_ETHER));
+
+ user_context_ = getRawColumnValue(r, row, USER_CONTEXT_COL);
+ ConstElementPtr ctx;
+ if (!user_context_.empty()) {
+ ctx = Element::fromJSON(user_context_);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context_
+ << "' is not a JSON map");
+ }
+ }
+
+ convertFromBytea(r, row, RELAY_ID_COL, relay_id_buffer_,
+ sizeof(relay_id_buffer_), relay_id_length_);
+
+ convertFromBytea(r, row, REMOTE_ID_COL, remote_id_buffer_,
+ sizeof(remote_id_buffer_), remote_id_length_);
+
+ getColumnValue(r, row, POOL_ID_COL, pool_id_);
+
+ Lease4Ptr result(boost::make_shared<Lease4>(addr4_, hwaddr,
+ client_id_buffer_,
+ client_id_length_,
+ valid_lifetime_, cltt_,
+ subnet_id_, fqdn_fwd_,
+ fqdn_rev_, hostname_));
+
+ result->state_ = state;
+
+ if (ctx) {
+ result->setContext(ctx);
+ }
+
+ if (relay_id_length_) {
+ result->relay_id_.assign(relay_id_buffer_,
+ relay_id_buffer_ + relay_id_length_);
+ }
+
+ if (remote_id_length_) {
+ result->remote_id_.assign(remote_id_buffer_,
+ remote_id_buffer_ + remote_id_length_);
+ }
+
+ result->pool_id_ = pool_id_;
+
+ return (result);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not convert data to Lease4, reason: "
+ << ex.what());
+ }
+ }
+
+private:
+
+ /// @brief Lease4 object currently being sent to the database.
+ /// Storing this value ensures that it remains in scope while any bindings
+ /// that refer to its contents are in use.
+ Lease4Ptr lease_;
+
+ /// @brief Lease4 specific members for binding and conversion.
+ uint32_t addr4_;
+ size_t client_id_length_;
+ uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+ size_t relay_id_length_;
+ uint8_t relay_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+ size_t remote_id_length_;
+ uint8_t remote_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+};
+
+/// @brief Supports exchanging IPv6 leases with PostgreSQL.
+class PgSqlLease6Exchange : public PgSqlLeaseExchange {
+private:
+
+ /// @brief Column numbers for each column in the Lease6 table.
+ /// These are used for both retrieving data and for looking up
+ /// column labels for logging. Note that their numeric order
+ /// MUST match that of the column order in the Lease6 table.
+ //@{
+ static const size_t ADDRESS_COL = 0;
+ static const size_t DUID_COL = 1;
+ static const size_t VALID_LIFETIME_COL = 2;
+ static const size_t EXPIRE_COL = 3;
+ static const size_t SUBNET_ID_COL = 4;
+ static const size_t PREF_LIFETIME_COL = 5;
+ static const size_t LEASE_TYPE_COL = 6;
+ static const size_t IAID_COL = 7;
+ static const size_t PREFIX_LEN_COL = 8;
+ static const size_t FQDN_FWD_COL = 9;
+ static const size_t FQDN_REV_COL = 10;
+ static const size_t HOSTNAME_COL = 11;
+ static const size_t HWADDR_COL = 12;
+ static const size_t HWTYPE_COL = 13;
+ static const size_t HWADDR_SOURCE_COL = 14;
+ static const size_t STATE_COL = 15;
+ static const size_t USER_CONTEXT_COL = 16;
+ static const size_t POOL_ID_COL = 17;
+ //@}
+ /// @brief Number of columns in the table holding DHCPv6 leases.
+ static const size_t LEASE_COLUMNS = 18;
+
+public:
+
+ /// @brief Union for marshalling IAID into and out of the database
+ /// IAID is defined in the RFC as 4 octets, which Kea code handles as
+ /// a uint32_t. Postgresql however, offers only signed integer types
+ /// of sizes 2, 4, and 8 bytes (SMALLINT, INT, and BIGINT respectively).
+ /// IAID is used in several indexes so rather than use the BIGINT, we
+ /// use this union to safely move the value into and out of an INT column.
+ union Uiaid {
+ /// @brief Constructor
+ /// @param val unsigned 32 bit value for the IAID.
+ Uiaid(uint32_t val) : uval_(val) {};
+
+ /// @brief Constructor
+ /// @param val signed 32 bit value for the IAID.
+ Uiaid(int32_t val) : ival_(val) {};
+
+ /// @brief Return a string representing the signed 32-bit value.
+ std::string dbInputString() {
+ return (boost::lexical_cast<std::string>(ival_));
+ };
+
+ uint32_t uval_;
+ int32_t ival_;
+ };
+
+ PgSqlLease6Exchange()
+ : lease_(), duid_length_(0), duid_(duid_length_), iaid_u_(0),
+ iaid_str_(""), lease_type_(Lease6::TYPE_NA), lease_type_str_(""),
+ prefix_len_(0), prefix_len_str_(""), pref_lifetime_(0),
+ preferred_lifetime_str_(""), hwtype_(0), hwtype_str_(""),
+ hwaddr_source_(0), hwaddr_source_str_("") {
+
+ BOOST_STATIC_ASSERT(17 < LEASE_COLUMNS);
+
+ memset(duid_buffer_, 0, sizeof(duid_buffer_));
+
+ // Set the column names (for error messages)
+ columns_.push_back("address");
+ columns_.push_back("duid");
+ columns_.push_back("valid_lifetime");
+ columns_.push_back("expire");
+ columns_.push_back("subnet_id");
+ columns_.push_back("pref_lifetime");
+ columns_.push_back("lease_type");
+ columns_.push_back("iaid");
+ columns_.push_back("prefix_len");
+ columns_.push_back("fqdn_fwd");
+ columns_.push_back("fqdn_rev");
+ columns_.push_back("hostname");
+ columns_.push_back("hwaddr");
+ columns_.push_back("hwtype");
+ columns_.push_back("hwaddr_source");
+ columns_.push_back("state");
+ columns_.push_back("user_context");
+ columns_.push_back("pool_id");
+ }
+
+ /// @brief Creates the bind array for sending Lease6 data to the database.
+ ///
+ /// Converts each Lease6 member into the appropriate form and adds it
+ /// to the bind array. Note that the array additions must occur in the
+ /// order the columns are specified in the SQL statement. By convention
+ /// all columns in the table are explicitly listed in the SQL statement(s)
+ /// in the same order as they occur in the table.
+ ///
+ /// @param lease Lease6 object that is to be written to the database
+ /// @param[out] bind_array array to populate with the lease data values
+ ///
+ /// @throw DbOperationError if bind_array cannot be populated.
+ void createBindForSend(const Lease6Ptr& lease, PsqlBindArray& bind_array) {
+ if (!lease) {
+ isc_throw(BadValue, "createBindForSend:: Lease6 object is NULL");
+ }
+
+ // Store lease object to ensure it remains valid.
+ lease_ = lease;
+ try {
+ addr_str_ = lease_->addr_.toText();
+ bind_array.add(addr_str_);
+
+ if (lease_->duid_) {
+ bind_array.add(lease_->duid_->getDuid());
+ } else {
+ isc_throw (BadValue, "IPv6 Lease cannot have a null DUID");
+ }
+
+ valid_lifetime_str_ = boost::lexical_cast<std::string>(lease->valid_lft_);
+ bind_array.add(valid_lifetime_str_);
+
+ // The lease structure holds the client last transmission time (cltt_)
+ // For convenience for external tools, this is converted to lease
+ // expiry time (expire). The relationship is given by:
+ // expire = cltt_ + valid_lft_
+ // Avoid overflow with infinite valid lifetime by using
+ // expire = cltt_ when valid_lft_ = 0xffffffff
+ if (lease_->valid_lft_ == Lease::INFINITY_LFT) {
+ expire_str_ = convertToDatabaseTime(lease->cltt_, 0);
+ } else {
+ expire_str_ = convertToDatabaseTime(lease->cltt_,
+ lease_->valid_lft_);
+ }
+ bind_array.add(expire_str_);
+
+ subnet_id_str_ = boost::lexical_cast<std::string>(lease->subnet_id_);
+ bind_array.add(subnet_id_str_);
+
+ preferred_lifetime_str_ = boost::lexical_cast<std::string>(lease_->preferred_lft_);
+ bind_array.add(preferred_lifetime_str_);
+
+ lease_type_str_ = boost::lexical_cast<std::string>(lease_->type_);
+ bind_array.add(lease_type_str_);
+
+ // The iaid is stored as an INT in lease6 table, so we must
+ // lexically cast from an integer version to avoid out of range
+ // exception failure upon insert.
+ iaid_u_.uval_ = lease_->iaid_;
+ iaid_str_ = iaid_u_.dbInputString();
+ bind_array.add(iaid_str_);
+
+ prefix_len_str_ = boost::lexical_cast<std::string>
+ (static_cast<unsigned int>(lease_->prefixlen_));
+ bind_array.add(prefix_len_str_);
+
+ bind_array.add(lease->fqdn_fwd_);
+
+ bind_array.add(lease->fqdn_rev_);
+
+ bind_array.add(lease->hostname_);
+
+ if (lease->hwaddr_ && !lease->hwaddr_->hwaddr_.empty()) {
+ // PostgreSql does not provide MAX on variable length types
+ // so we have to enforce it ourselves.
+ if (lease->hwaddr_->hwaddr_.size() > HWAddr::MAX_HWADDR_LEN) {
+ isc_throw(DbOperationError, "Hardware address length : "
+ << lease_->hwaddr_->hwaddr_.size()
+ << " exceeds maximum allowed of: "
+ << HWAddr::MAX_HWADDR_LEN);
+ }
+ bind_array.add(lease->hwaddr_->hwaddr_);
+ } else {
+ bind_array.add("");
+ }
+
+ if (lease->hwaddr_) {
+ hwtype_str_ = boost::lexical_cast<std::string>
+ (static_cast<unsigned int>(lease_->hwaddr_->htype_));
+ hwaddr_source_str_ = boost::lexical_cast<std::string>
+ (static_cast<unsigned int>(lease_->hwaddr_->source_));
+ } else {
+ hwtype_str_ = boost::lexical_cast<std::string>
+ (static_cast<unsigned int>(HTYPE_UNDEFINED));
+ hwaddr_source_str_ = boost::lexical_cast<std::string>
+ (static_cast<unsigned int>(HWAddr::HWADDR_SOURCE_UNKNOWN));
+ }
+
+ bind_array.add(hwtype_str_);
+
+ bind_array.add(hwaddr_source_str_);
+
+ state_str_ = boost::lexical_cast<std::string>(lease->state_);
+ bind_array.add(state_str_);
+
+ ConstElementPtr ctx = lease->getContext();
+ if (ctx) {
+ user_context_ = ctx->str();
+ } else {
+ user_context_ = "";
+ }
+ bind_array.add(user_context_);
+
+ pool_id_str_ = boost::lexical_cast<std::string>(lease->pool_id_);
+ bind_array.add(pool_id_str_);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not create bind array from Lease6: "
+ << lease_->addr_.toText() << ", reason: " << ex.what());
+ }
+ }
+
+ /// @brief Creates a Lease6 object from a given row in a result set.
+ ///
+ /// @param r result set containing one or rows from the Lease6 table
+ /// @param row row number within the result set from to create the Lease6
+ /// object.
+ ///
+ /// @return Lease6Ptr to the newly created Lease4 object
+ /// @throw DbOperationError if the lease cannot be created.
+ Lease6Ptr convertFromDatabase(const PgSqlResult& r, int row) {
+ try {
+
+ /// @todo In theory, an administrator could tweak lease
+ /// information in the database. In this case, some of the
+ /// values could be set to NULL. This is less likely than
+ /// in case of host reservations, but we may consider if
+ /// retrieved values should be checked for being NULL to
+ /// prevent cryptic errors during conversions from NULL
+ /// to actual values.
+
+ IOAddress addr(getIPv6Value(r, row, ADDRESS_COL));
+
+ convertFromBytea(r, row, DUID_COL, duid_buffer_, sizeof(duid_buffer_), duid_length_);
+ DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_));
+
+ getColumnValue(r, row, VALID_LIFETIME_COL, valid_lifetime_);
+
+ expire_ = convertFromDatabaseTime(getRawColumnValue(r, row,
+ EXPIRE_COL));
+
+ // Recover from overflow (see createBindForSend)
+ if (valid_lifetime_ == Lease::INFINITY_LFT) {
+ cltt_ = expire_;
+ } else {
+ cltt_ = expire_ - valid_lifetime_;
+ }
+
+ getColumnValue(r, row, SUBNET_ID_COL, subnet_id_);
+
+ getColumnValue(r, row, PREF_LIFETIME_COL, pref_lifetime_);
+
+ getLeaseTypeColumnValue(r, row, LEASE_TYPE_COL, lease_type_);
+
+ getColumnValue(r, row, IAID_COL, iaid_u_.ival_);
+
+ getColumnValue(r, row, PREFIX_LEN_COL, prefix_len_);
+
+ getColumnValue(r, row, FQDN_FWD_COL, fqdn_fwd_);
+
+ getColumnValue(r, row, FQDN_REV_COL, fqdn_rev_);
+
+ hostname_ = getRawColumnValue(r, row, HOSTNAME_COL);
+
+ convertFromBytea(r, row, HWADDR_COL, hwaddr_buffer_,
+ sizeof(hwaddr_buffer_), hwaddr_length_);
+
+ getColumnValue(r, row, HWTYPE_COL, hwtype_);
+
+ getColumnValue(r, row, HWADDR_SOURCE_COL, hwaddr_source_);
+
+ HWAddrPtr hwaddr;
+
+ if (hwaddr_length_) {
+ hwaddr.reset(new HWAddr(hwaddr_buffer_, hwaddr_length_,
+ hwtype_));
+
+ hwaddr->source_ = hwaddr_source_;
+ }
+
+ uint32_t state;
+ getColumnValue(r, row, STATE_COL, state);
+
+ user_context_ = getRawColumnValue(r, row, USER_CONTEXT_COL);
+ ConstElementPtr ctx;
+ if (!user_context_.empty()) {
+ ctx = Element::fromJSON(user_context_);
+ if (!ctx || (ctx->getType() != Element::map)) {
+ isc_throw(BadValue, "user context '" << user_context_
+ << "' is not a JSON map");
+ }
+ }
+
+ getColumnValue(r, row, POOL_ID_COL, pool_id_);
+
+ if (lease_type_ != Lease::TYPE_PD) {
+ prefix_len_ = 128;
+ }
+
+ Lease6Ptr result(boost::make_shared<Lease6>(lease_type_, addr,
+ duid_ptr,
+ iaid_u_.uval_,
+ pref_lifetime_,
+ valid_lifetime_,
+ subnet_id_, fqdn_fwd_,
+ fqdn_rev_, hostname_,
+ hwaddr, prefix_len_));
+ // Update cltt_ and current_cltt_ explicitly.
+ result->cltt_ = cltt_;
+ result->current_cltt_ = cltt_;
+
+ result->state_ = state;
+
+ if (ctx) {
+ result->setContext(ctx);
+ }
+
+ result->pool_id_ = pool_id_;
+
+ return (result);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError,
+ "Could not convert data to Lease6, reason: "
+ << ex.what());
+ }
+ }
+
+ /// @brief Fetches an integer text column as a Lease6::Type
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value parameter to receive the converted value
+ ///
+ /// Note we depart from overloading getColumnValue to avoid ambiguity
+ /// with base class methods for integers.
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ void getLeaseTypeColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, Lease6::Type& value) const {
+ uint32_t raw_value = 0;
+ getColumnValue(r, row, col, raw_value);
+ switch (raw_value) {
+ case Lease6::TYPE_NA:
+ case Lease6::TYPE_TA:
+ case Lease6::TYPE_PD:
+ value = static_cast<Lease6::Type>(raw_value);
+ break;
+
+ default:
+ isc_throw(DbOperationError, "Invalid lease type: " << raw_value
+ << " for: " << getColumnLabel(r, col) << " row:" << row);
+ }
+ }
+
+private:
+ /// @brief Lease6 object currently being sent to the database.
+ /// Storing this value ensures that it remains in scope while any bindings
+ /// that refer to its contents are in use.
+ Lease6Ptr lease_;
+
+ /// @brief Lease6 specific members for binding and conversion.
+ //@{
+ size_t duid_length_;
+ std::vector<uint8_t> duid_;
+ uint8_t duid_buffer_[DUID::MAX_DUID_LEN];
+ union Uiaid iaid_u_;
+ std::string iaid_str_;
+ Lease6::Type lease_type_;
+ std::string lease_type_str_;
+ uint8_t prefix_len_;
+ std::string prefix_len_str_;
+ uint32_t pref_lifetime_;
+ std::string preferred_lifetime_str_;
+ uint32_t hwtype_;
+ std::string hwtype_str_;
+ uint32_t hwaddr_source_;
+ std::string hwaddr_source_str_;
+ //@}
+};
+
+/// @brief Base PgSql derivation of the statistical lease data query
+///
+/// This class provides the functionality such as results storage and row
+/// fetching common to fulfilling the statistical lease data query.
+///
+class PgSqlLeaseStatsQuery : public LeaseStatsQuery {
+public:
+
+ /// @brief Constructor to query for all subnets' stats
+ ///
+ /// The query created will return statistics for all subnets
+ ///
+ /// @param conn A open connection to the database housing the lease data
+ /// @param statement The lease data SQL prepared statement to execute
+ /// @param fetch_type Indicates whether or not lease_type should be
+ /// fetched from the result set
+ /// @param fetch_pool Indicates if query requires pool data
+ PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement,
+ const bool fetch_type, const bool fetch_pool = false)
+ : conn_(conn), statement_(statement), result_set_(), next_row_(0),
+ fetch_type_(fetch_type), fetch_pool_(fetch_pool) {
+ }
+
+ /// @brief Constructor to query for a single subnet's stats
+ ///
+ /// The query created will return statistics for a single subnet
+ ///
+ /// @param conn A open connection to the database housing the lease data
+ /// @param statement The lease data SQL prepared statement to execute
+ /// @param fetch_type Indicates if query supplies lease type
+ /// @param subnet_id id of the subnet for which stats are desired
+ PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement,
+ const bool fetch_type, const SubnetID& subnet_id)
+ : LeaseStatsQuery(subnet_id), conn_(conn), statement_(statement), result_set_(),
+ next_row_(0), fetch_type_(fetch_type), fetch_pool_(false) {
+ }
+
+ /// @brief Constructor to query for the stats for a range of subnets
+ ///
+ /// The query created will return statistics for the inclusive range of
+ /// subnets described by first and last subnet IDs.
+ ///
+ /// @param conn A open connection to the database housing the lease data
+ /// @param statement The lease data SQL prepared statement to execute
+ /// @param fetch_type Indicates if query supplies lease type
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement,
+ const bool fetch_type, const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id)
+ : LeaseStatsQuery(first_subnet_id, last_subnet_id), conn_(conn), statement_(statement),
+ result_set_(), next_row_(0), fetch_type_(fetch_type), fetch_pool_(false) {
+ }
+
+ /// @brief Destructor
+ virtual ~PgSqlLeaseStatsQuery() {};
+
+ /// @brief Creates the lease statistical data result set
+ ///
+ /// The result set is populated by executing a prepared SQL query
+ /// against the database fetches the lease count per lease state per
+ /// (per least type - v6 only) per subnet id.
+ ///
+ /// Depending upon the selection mode, the query will have either no
+ /// parameters (for all subnets), a subnet id for a single subnet, or
+ /// a first and last subnet id for a subnet range.
+ void start() {
+
+ if (getSelectMode() == ALL_SUBNETS || getSelectMode() == ALL_SUBNET_POOLS) {
+ // Run the query with no where clause parameters.
+ result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name,
+ 0, 0, 0, 0, 0)));
+ } else {
+ // Set up the WHERE clause values
+ PsqlBindArray parms;
+
+ // Add first_subnet_id used by both single and range.
+ parms.addTempString(boost::lexical_cast<std::string>(getFirstSubnetID()));
+
+ // Add last_subnet_id for range.
+ if (getSelectMode() == SUBNET_RANGE) {
+ // Add last_subnet_id used by range.
+ parms.addTempString(boost::lexical_cast<std::string>(getLastSubnetID()));
+ }
+
+ // Run the query with where clause parameters.
+ result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name,
+ parms.size(), &parms.values_[0],
+ &parms.lengths_[0], &parms.formats_[0], 0)));
+ }
+
+ conn_.checkStatementError(*result_set_, statement_);
+ }
+
+ /// @brief Fetches the next row in the result set
+ ///
+ /// Once the internal result set has been populated by invoking the
+ /// the start() method, this method is used to iterate over the
+ /// result set rows. Once the last row has been fetched, subsequent
+ /// calls will return false.
+ ///
+ /// Checks against negative values for the state count and logs once
+ /// a warning message.
+ ///
+ /// @param row Storage for the fetched row
+ ///
+ /// @return True if the fetch succeeded, false if there are no more
+ /// rows to fetch.
+ bool getNextRow(LeaseStatsRow& row) {
+ // If we're past the end, punt.
+ if (next_row_ >= result_set_->getRows()) {
+ return (false);
+ }
+
+ // Fetch the subnet id.
+ uint32_t col = 0;
+ uint32_t subnet_id;
+ PgSqlExchange::getColumnValue(*result_set_, next_row_, col, subnet_id);
+ row.subnet_id_ = static_cast<SubnetID>(subnet_id);
+ ++col;
+
+ // Fetch the pool id if we were told to do so.
+ if (fetch_pool_) {
+ PgSqlExchange::getColumnValue(*result_set_, next_row_, col,
+ row.pool_id_);
+ ++col;
+ }
+
+ // Fetch the lease type if we were told to do so.
+ if (fetch_type_) {
+ uint32_t lease_type;
+ PgSqlExchange::getColumnValue(*result_set_, next_row_, col,
+ lease_type);
+ row.lease_type_ = static_cast<Lease::Type>(lease_type);
+ ++col;
+ } else {
+ row.lease_type_ = Lease::TYPE_NA;
+ }
+
+ // Fetch the lease state.
+ PgSqlExchange::getColumnValue(*result_set_, next_row_, col,
+ row.lease_state_);
+ ++col;
+
+ // Fetch the state count.
+ PgSqlExchange::getColumnValue(*result_set_, next_row_, col,
+ row.state_count_);
+
+ // Protect against negative state count.a
+ if (row.state_count_ < 0) {
+ row.state_count_ = 0;
+ if (!negative_count_) {
+ negative_count_ = true;
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_PGSQL_NEGATIVE_LEASES_STAT);
+ }
+ }
+
+ // Point to the next row.
+ ++next_row_;
+ return (true);
+ }
+
+protected:
+
+ /// @brief Database connection to use to execute the query
+ PgSqlConnection& conn_;
+
+ /// @brief The query's prepared statement
+ PgSqlTaggedStatement& statement_;
+
+ /// @brief The result set returned by Postgres.
+ boost::shared_ptr<PgSqlResult> result_set_;
+
+ /// @brief Index of the next row to fetch
+ uint32_t next_row_;
+
+ /// @brief Indicates if query supplies lease type
+ bool fetch_type_;
+
+ /// @brief Indicates if query requires pool data
+ bool fetch_pool_;
+
+ /// @brief Received negative state count showing a problem
+ static bool negative_count_;
+};
+
+// Initialize negative state count flag to false.
+bool PgSqlLeaseStatsQuery::negative_count_ = false;
+
+// PgSqlLeaseContext Constructor
+
+PgSqlLeaseContext::PgSqlLeaseContext(const DatabaseConnection::ParameterMap& parameters,
+ IOServiceAccessorPtr io_service_accessor,
+ DbCallback db_reconnect_callback)
+ : conn_(parameters, io_service_accessor, db_reconnect_callback) {
+}
+
+// PgSqlLeaseContextAlloc Constructor and Destructor
+
+PgSqlLeaseMgr::PgSqlLeaseContextAlloc::PgSqlLeaseContextAlloc(
+ const PgSqlLeaseMgr& mgr) : ctx_(), mgr_(mgr) {
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ {
+ // we need to protect the whole pool_ operation, hence extra scope {}
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (!mgr_.pool_->pool_.empty()) {
+ ctx_ = mgr_.pool_->pool_.back();
+ mgr_.pool_->pool_.pop_back();
+ }
+ }
+ if (!ctx_) {
+ ctx_ = mgr_.createContext();
+ }
+ } else {
+ // single-threaded
+ if (mgr_.pool_->pool_.empty()) {
+ isc_throw(Unexpected, "No available PostgreSQL lease context?!");
+ }
+ ctx_ = mgr_.pool_->pool_.back();
+ }
+}
+
+PgSqlLeaseMgr::PgSqlLeaseContextAlloc::~PgSqlLeaseContextAlloc() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ mgr_.pool_->pool_.push_back(ctx_);
+ }
+ // If running in single-threaded mode, there's nothing to do here.
+}
+
+// PgSqlLeaseTrackingContextAlloc Constructor and Destructor
+
+PgSqlLeaseMgr::PgSqlLeaseTrackingContextAlloc::PgSqlLeaseTrackingContextAlloc(
+ PgSqlLeaseMgr& mgr, const LeasePtr& lease) : ctx_(), mgr_(mgr), lease_(lease) {
+
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ {
+ // we need to protect the whole pool_ operation, hence extra scope {}
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (mgr_.hasCallbacks() && !mgr_.tryLock(lease)) {
+ isc_throw(DbOperationError, "unable to lock the lease " << lease->addr_);
+ }
+ if (!mgr_.pool_->pool_.empty()) {
+ ctx_ = mgr_.pool_->pool_.back();
+ mgr_.pool_->pool_.pop_back();
+ }
+ }
+ if (!ctx_) {
+ ctx_ = mgr_.createContext();
+ }
+ } else {
+ // single-threaded
+ if (mgr_.pool_->pool_.empty()) {
+ isc_throw(Unexpected, "No available PostgreSQL lease context?!");
+ }
+ ctx_ = mgr_.pool_->pool_.back();
+ }
+}
+
+PgSqlLeaseMgr::PgSqlLeaseTrackingContextAlloc::~PgSqlLeaseTrackingContextAlloc() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ // multi-threaded
+ lock_guard<mutex> lock(mgr_.pool_->mutex_);
+ if (mgr_.hasCallbacks()) {
+ mgr_.unlock(lease_);
+ }
+ mgr_.pool_->pool_.push_back(ctx_);
+ }
+ // If running in single-threaded mode, there's nothing to do here.
+}
+
+void
+PgSqlLeaseMgr::setExtendedInfoTablesEnabled(const db::DatabaseConnection::ParameterMap& /* parameters */) {
+ isc_throw(isc::NotImplemented, "extended info tables are not yet supported by mysql");
+}
+
+// PgSqlLeaseMgr Constructor and Destructor
+
+PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+ : TrackingLeaseMgr(), parameters_(parameters), timer_name_("") {
+
+ // Check if the extended info tables are enabled.
+ LeaseMgr::setExtendedInfoTablesEnabled(parameters);
+
+ // Create unique timer name per instance.
+ timer_name_ = "PgSqlLeaseMgr[";
+ timer_name_ += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+ timer_name_ += "]DbReconnectTimer";
+
+ // Check TLS support.
+ size_t tls(0);
+ tls += parameters.count("trust-anchor");
+ tls += parameters.count("cert-file");
+ tls += parameters.count("key-file");
+ tls += parameters.count("cipher-list");
+#ifdef HAVE_PGSQL_SSL
+ if ((tls > 0) && !PgSqlConnection::warned_about_tls) {
+ PgSqlConnection::warned_about_tls = true;
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_TLS_SUPPORT)
+ .arg(DatabaseConnection::redactedAccessString(parameters_));
+ PQinitSSL(1);
+ }
+#else
+ if (tls > 0) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_NO_TLS_SUPPORT)
+ .arg(DatabaseConnection::redactedAccessString(parameters_));
+ isc_throw(DbOpenError, "Attempt to configure TLS for PostgreSQL "
+ << "backend (built with this feature disabled)");
+ }
+#endif
+
+ // Validate schema version first.
+ std::pair<uint32_t, uint32_t> code_version(PGSQL_SCHEMA_VERSION_MAJOR,
+ PGSQL_SCHEMA_VERSION_MINOR);
+ std::pair<uint32_t, uint32_t> db_version = getVersion();
+ if (code_version != db_version) {
+ isc_throw(DbOpenError,
+ "PostgreSQL schema version mismatch: need version: "
+ << code_version.first << "." << code_version.second
+ << " found version: " << db_version.first << "."
+ << db_version.second);
+ }
+
+ // Create an initial context.
+ pool_.reset(new PgSqlLeaseContextPool());
+ pool_->pool_.push_back(createContext());
+}
+
+PgSqlLeaseMgr::~PgSqlLeaseMgr() {
+}
+
+bool
+PgSqlLeaseMgr::dbReconnect(ReconnectCtlPtr db_reconnect_ctl) {
+ MultiThreadingCriticalSection cs;
+
+ // Invoke application layer connection lost callback.
+ if (!DatabaseConnection::invokeDbLostCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+
+ bool reopened = false;
+
+ const std::string timer_name = db_reconnect_ctl->timerName();
+
+ // At least one connection was lost.
+ try {
+ CfgDbAccessPtr cfg_db = CfgMgr::instance().getCurrentCfg()->getCfgDbAccess();
+ LeaseMgrFactory::recreate(cfg_db->getLeaseDbAccessString());
+ reopened = true;
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_FAILED)
+ .arg(ex.what());
+ }
+
+ if (reopened) {
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection recovered callback.
+ if (!DatabaseConnection::invokeDbRecoveredCallback(db_reconnect_ctl)) {
+ return (false);
+ }
+ } else {
+ if (!db_reconnect_ctl->checkRetries()) {
+ // We're out of retries, log it and initiate shutdown.
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_LEASE_DB_RECONNECT_FAILED)
+ .arg(db_reconnect_ctl->maxRetries());
+
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ // Invoke application layer connection failed callback.
+ DatabaseConnection::invokeDbFailedCallback(db_reconnect_ctl);
+ return (false);
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_LEASE_DB_RECONNECT_ATTEMPT_SCHEDULE)
+ .arg(db_reconnect_ctl->maxRetries() - db_reconnect_ctl->retriesLeft() + 1)
+ .arg(db_reconnect_ctl->maxRetries())
+ .arg(db_reconnect_ctl->retryInterval());
+
+ // Start the timer.
+ if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->registerTimer(timer_name,
+ std::bind(&PgSqlLeaseMgr::dbReconnect, db_reconnect_ctl),
+ db_reconnect_ctl->retryInterval(),
+ asiolink::IntervalTimer::ONE_SHOT);
+ }
+ TimerMgr::instance()->setup(timer_name);
+ }
+
+ return (true);
+}
+
+// Create context.
+
+PgSqlLeaseContextPtr
+PgSqlLeaseMgr::createContext() const {
+ PgSqlLeaseContextPtr ctx(new PgSqlLeaseContext(parameters_,
+ IOServiceAccessorPtr(new IOServiceAccessor(&LeaseMgr::getIOService)),
+ &PgSqlLeaseMgr::dbReconnect));
+
+ // Open the database.
+ ctx->conn_.openDatabase();
+
+ // Now prepare the SQL statements.
+ uint32_t i = 0;
+ for (; tagged_statements[i].text != NULL; ++i) {
+ ctx->conn_.prepareStatement(tagged_statements[i]);
+ }
+
+ // Just in case somebody foo-barred things
+ if (i != NUM_STATEMENTS) {
+ isc_throw(DbOpenError, "Number of statements prepared: " << i
+ << " does not match expected count:" << NUM_STATEMENTS);
+ }
+
+ // Create the exchange objects for use in exchanging data between the
+ // program and the database.
+ ctx->exchange4_.reset(new PgSqlLease4Exchange());
+ ctx->exchange6_.reset(new PgSqlLease6Exchange());
+
+ // Create ReconnectCtl for this connection.
+ ctx->conn_.makeReconnectCtl(timer_name_);
+
+ return (ctx);
+}
+
+std::string
+PgSqlLeaseMgr::getDBVersion() {
+ std::stringstream tmp;
+ tmp << "PostgreSQL backend " << PGSQL_SCHEMA_VERSION_MAJOR;
+ tmp << "." << PGSQL_SCHEMA_VERSION_MINOR;
+ tmp << ", library " << PQlibVersion();
+ return (tmp.str());
+}
+
+bool
+PgSqlLeaseMgr::addLeaseCommon(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArray& bind_array) {
+ PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array.values_[0],
+ &bind_array.lengths_[0],
+ &bind_array.formats_[0], 0));
+
+ int s = PQresultStatus(r);
+
+ if (s != PGRES_COMMAND_OK) {
+ // Failure: check for the special case of duplicate entry. If this is
+ // the case, we return false to indicate that the row was not added.
+ // Otherwise we throw an exception.
+ if (ctx->conn_.compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
+ return (false);
+ }
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+ }
+
+ return (true);
+}
+
+bool
+PgSqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ADD_ADDR4)
+ .arg(lease->addr_.toText());
+
+ // Get a context
+ PgSqlLeaseTrackingContextAlloc get_context(*this, lease);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ PsqlBindArray bind_array;
+ ctx->exchange4_->createBindForSend(lease, bind_array);
+ auto result = addLeaseCommon(ctx, INSERT_LEASE4, bind_array);
+
+ // Update lease current expiration time (allows update between the creation
+ // of the Lease up to the point of insertion in the database).
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackAddLease(lease);
+ }
+
+ return (result);
+}
+
+bool
+PgSqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ADD_ADDR6)
+ .arg(lease->addr_.toText())
+ .arg(lease->type_);
+
+ // Get a context
+ PgSqlLeaseTrackingContextAlloc get_context(*this, lease);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ PsqlBindArray bind_array;
+ ctx->exchange6_->createBindForSend(lease, bind_array);
+
+ auto result = addLeaseCommon(ctx, INSERT_LEASE6, bind_array);
+
+ // Update lease current expiration time (allows update between the creation
+ // of the Lease up to the point of insertion in the database).
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackAddLease(lease);
+ }
+
+ return (result);
+}
+
+template <typename Exchange, typename LeaseCollection>
+void
+PgSqlLeaseMgr::getLeaseCollection(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArray& bind_array,
+ Exchange& exchange,
+ LeaseCollection& result,
+ bool single) const {
+ const int n = tagged_statements[stindex].nbparams;
+ PgSqlResult r(PQexecPrepared(ctx->conn_,
+ tagged_statements[stindex].name, n,
+ n > 0 ? &bind_array.values_[0] : NULL,
+ n > 0 ? &bind_array.lengths_[0] : NULL,
+ n > 0 ? &bind_array.formats_[0] : NULL, 0));
+
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+
+ int rows = PQntuples(r);
+ if (single && rows > 1) {
+ isc_throw(MultipleRecords, "multiple records were found in the "
+ "database where only one was expected for query "
+ << tagged_statements[stindex].name);
+ }
+
+ for(int i = 0; i < rows; ++i) {
+ result.push_back(exchange->convertFromDatabase(r, i));
+ }
+}
+
+void
+PgSqlLeaseMgr::getLease(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex, PsqlBindArray& bind_array,
+ Lease4Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" parameter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease4Collection collection;
+ getLeaseCollection(ctx, stindex, bind_array, ctx->exchange4_,
+ collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+}
+
+void
+PgSqlLeaseMgr::getLease(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex, PsqlBindArray& bind_array,
+ Lease6Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" parameter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease6Collection collection;
+ getLeaseCollection(ctx, stindex, bind_array, ctx->exchange6_,
+ collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+}
+
+Lease4Ptr
+PgSqlLeaseMgr::getLease4(const IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_ADDR4)
+ .arg(addr.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // LEASE ADDRESS
+ std::string addr_str = boost::lexical_cast<std::string>(addr.toUint32());
+ bind_array.add(addr_str);
+
+ // Get the data
+ Lease4Ptr result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE4_ADDR, bind_array, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_HWADDR)
+ .arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // HWADDR
+ if (!hwaddr.hwaddr_.empty()) {
+ bind_array.add(hwaddr.hwaddr_);
+ } else {
+ bind_array.add("");
+ }
+
+ // Get the data
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_HWADDR, bind_array, result);
+
+ return (result);
+}
+
+Lease4Ptr
+PgSqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id)
+ .arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // HWADDR
+ if (!hwaddr.hwaddr_.empty()) {
+ bind_array.add(hwaddr.hwaddr_);
+ } else {
+ bind_array.add("");
+ }
+
+ // SUBNET_ID
+ std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
+ bind_array.add(subnet_id_str);
+
+ // Get the data
+ Lease4Ptr result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE4_HWADDR_SUBID, bind_array, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_CLIENTID)
+ .arg(clientid.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // CLIENT_ID
+ bind_array.add(clientid.getClientId());
+
+ // Get the data
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_CLIENTID, bind_array, result);
+
+ return (result);
+}
+
+Lease4Ptr
+PgSqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_SUBID_CLIENTID)
+ .arg(subnet_id)
+ .arg(clientid.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // CLIENT_ID
+ bind_array.add(clientid.getClientId());
+
+ // SUBNET_ID
+ std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
+ bind_array.add(subnet_id_str);
+
+ // Get the data
+ Lease4Ptr result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE4_CLIENTID_SUBID, bind_array, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLeases4(SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_SUBID4)
+ .arg(subnet_id);
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // SUBNET_ID
+ std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
+ bind_array.add(subnet_id_str);
+
+ // ... and get the data
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_SUBID, bind_array, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLeases4(const std::string& hostname) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_HOSTNAME4)
+ .arg(hostname);
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // Hostname
+ bind_array.add(hostname);
+
+ // ... and get the data
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_HOSTNAME, bind_array, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLeases4() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET4);
+
+ // Provide empty binding array because our query has no parameters in
+ // WHERE clause.
+ PsqlBindArray bind_array;
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4, bind_array, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLeases4(const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const {
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_PAGE4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText());
+
+ // Prepare WHERE clause
+ PsqlBindArray bind_array;
+
+ // Bind lower bound address
+ std::string lb_address_data = boost::lexical_cast<std::string>(lower_bound_address.toUint32());
+ bind_array.add(lb_address_data);
+
+ // Bind page size value
+ std::string page_size_data = boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array.add(page_size_data);
+
+ // Get the leases
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_PAGE, bind_array, result);
+
+ return (result);
+}
+
+Lease6Ptr
+PgSqlLeaseMgr::getLease6(Lease::Type lease_type,
+ const IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_ADDR6)
+ .arg(addr.toText())
+ .arg(lease_type);
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // LEASE ADDRESS
+ std::string addr_str = addr.toText();
+ bind_array.add(addr_str);
+
+ // LEASE_TYPE
+ std::string type_str_ = boost::lexical_cast<std::string>(lease_type);
+ bind_array.add(type_str_);
+
+ // ... and get the data
+ Lease6Ptr result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLease(ctx, GET_LEASE6_ADDR, bind_array, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid,
+ uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_IAID_DUID)
+ .arg(iaid)
+ .arg(duid.toText())
+ .arg(lease_type);
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // DUID
+ bind_array.add(duid.getDuid());
+
+ // IAID
+ std::string iaid_str = PgSqlLease6Exchange::Uiaid(iaid).dbInputString();
+ bind_array.add(iaid_str);
+
+ // LEASE_TYPE
+ std::string lease_type_str = boost::lexical_cast<std::string>(lease_type);
+ bind_array.add(lease_type_str);
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_DUID_IAID, bind_array, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(Lease::Type lease_type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid)
+ .arg(subnet_id)
+ .arg(duid.toText())
+ .arg(lease_type);
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // LEASE_TYPE
+ std::string lease_type_str = boost::lexical_cast<std::string>(lease_type);
+ bind_array.add(lease_type_str);
+
+ // DUID
+ bind_array.add(duid.getDuid());
+
+ // IAID
+ std::string iaid_str = PgSqlLease6Exchange::Uiaid(iaid).dbInputString();
+ bind_array.add(iaid_str);
+
+ // SUBNET ID
+ std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
+ bind_array.add(subnet_id_str);
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_DUID_IAID_SUBID, bind_array, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_SUBID6)
+ .arg(subnet_id);
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // SUBNET_ID
+ std::string subnet_id_str = boost::lexical_cast<std::string>(subnet_id);
+ bind_array.add(subnet_id_str);
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_SUBID, bind_array, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(const DUID& duid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_DUID)
+ .arg(duid.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // DUID
+ bind_array.add(duid.getDuid());
+ Lease6Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // query to fetch the data
+ getLeaseCollection(ctx, GET_LEASE6_DUID, bind_array, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(const std::string& hostname) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_HOSTNAME6)
+ .arg(hostname);
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ // Hostname
+ bind_array.add(hostname);
+
+ // ... and get the data
+ Lease6Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_HOSTNAME, bind_array, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET6);
+
+ // Provide empty binding array because our query has no parameters in
+ // WHERE clause.
+ PsqlBindArray bind_array;
+ Lease6Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6, bind_array, result);
+
+ return (result);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6(const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const {
+ // Expecting IPv6 address.
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_PAGE6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText());
+
+ // Prepare WHERE clause
+ PsqlBindArray bind_array;
+
+ // Bind lower bound address
+ std::string lb_address_data = lower_bound_address.toText();
+ bind_array.add(lb_address_data);
+
+ // Bind page size value
+ std::string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array.add(page_size_data);
+
+ // Get the leases
+ Lease6Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE6_PAGE, bind_array, result);
+
+ return (result);
+}
+
+void
+PgSqlLeaseMgr::getExpiredLeases4(Lease4Collection& expired_leases,
+ const size_t max_leases) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_EXPIRED4)
+ .arg(max_leases);
+ getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE4_EXPIRE);
+}
+
+void
+PgSqlLeaseMgr::getExpiredLeases6(Lease6Collection& expired_leases,
+ const size_t max_leases) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_EXPIRED6)
+ .arg(max_leases);
+ getExpiredLeasesCommon(expired_leases, max_leases, GET_LEASE6_EXPIRE);
+}
+
+template<typename LeaseCollection>
+void
+PgSqlLeaseMgr::getExpiredLeasesCommon(LeaseCollection& expired_leases,
+ const size_t max_leases,
+ StatementIndex statement_index) const {
+ PsqlBindArray bind_array;
+
+ // Exclude reclaimed leases.
+ std::string state_str = boost::lexical_cast<std::string>(Lease::STATE_EXPIRED_RECLAIMED);
+ bind_array.add(state_str);
+
+ // Expiration timestamp.
+ std::string timestamp_str = PgSqlLeaseExchange::convertToDatabaseTime(time(0));
+ bind_array.add(timestamp_str);
+
+ // If the number of leases is 0, we will return all leases. This is
+ // achieved by setting the limit to a very high value.
+ uint32_t limit = max_leases > 0 ? static_cast<uint32_t>(max_leases) :
+ std::numeric_limits<uint32_t>::max();
+ std::string limit_str = boost::lexical_cast<std::string>(limit);
+ bind_array.add(limit_str);
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Retrieve leases from the database.
+ getLeaseCollection(ctx, statement_index, bind_array, expired_leases);
+}
+
+template<typename LeasePtr>
+void
+PgSqlLeaseMgr::updateLeaseCommon(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArray& bind_array,
+ const LeasePtr& lease) {
+ PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array.values_[0],
+ &bind_array.lengths_[0],
+ &bind_array.formats_[0], 0));
+
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+
+ int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
+
+ // Check success case first as it is the most likely outcome.
+ if (affected_rows == 1) {
+ return;
+ }
+
+ // If no rows affected, lease doesn't exist.
+ if (affected_rows == 0) {
+ isc_throw(NoSuchLease, "unable to update lease for address " <<
+ lease->addr_.toText() << " as it does not exist");
+ }
+
+ // Should not happen - primary key constraint should only have selected
+ // one row.
+ isc_throw(DbOperationError, "apparently updated more than one lease "
+ "that had the address " << lease->addr_.toText());
+}
+
+void
+PgSqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ const StatementIndex stindex = UPDATE_LEASE4;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_UPDATE_ADDR4)
+ .arg(lease->addr_.toText());
+
+ // Get a context
+ PgSqlLeaseTrackingContextAlloc get_context(*this, lease);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create the BIND array for the data being updated
+ PsqlBindArray bind_array;
+ ctx->exchange4_->createBindForSend(lease, bind_array);
+
+ // Set up the WHERE clause and append it to the SQL_BIND array
+ std::string addr4_str = boost::lexical_cast<std::string>(lease->addr_.toUint32());
+ bind_array.add(addr4_str);
+
+ std::string expire_str;
+ // Avoid overflow (see createBindForSend)
+ if (lease->current_valid_lft_ == Lease::INFINITY_LFT) {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0);
+ } else {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_,
+ lease->current_valid_lft_);
+ }
+ bind_array.add(expire_str);
+
+ // Drop to common update code
+ updateLeaseCommon(ctx, stindex, bind_array, lease);
+
+ // Update lease current expiration time.
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackUpdateLease(lease);
+ }
+}
+
+void
+PgSqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ const StatementIndex stindex = UPDATE_LEASE6;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_UPDATE_ADDR6)
+ .arg(lease->addr_.toText())
+ .arg(lease->type_);
+
+ // Get a context
+ PgSqlLeaseTrackingContextAlloc get_context(*this, lease);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Create the BIND array for the data being updated
+ PsqlBindArray bind_array;
+ ctx->exchange6_->createBindForSend(lease, bind_array);
+
+ // Set up the WHERE clause and append it to the BIND array
+ std::string addr_str = lease->addr_.toText();
+ bind_array.add(addr_str);
+
+ std::string expire_str;
+ // Avoid overflow (see createBindForSend)
+ if (lease->current_valid_lft_ == Lease::INFINITY_LFT) {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0);
+ } else {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_,
+ lease->current_valid_lft_);
+ }
+ bind_array.add(expire_str);
+
+ // Drop to common update code
+ updateLeaseCommon(ctx, stindex, bind_array, lease);
+
+ // Update lease current expiration time.
+ lease->updateCurrentExpirationTime();
+
+ // Run installed callbacks.
+ if (hasCallbacks()) {
+ trackUpdateLease(lease);
+ }
+}
+
+uint64_t
+PgSqlLeaseMgr::deleteLeaseCommon(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ PsqlBindArray& bind_array) {
+ PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array.values_[0],
+ &bind_array.lengths_[0],
+ &bind_array.formats_[0], 0));
+
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+ int affected_rows = boost::lexical_cast<int>(PQcmdTuples(r));
+
+ return (affected_rows);
+}
+
+bool
+PgSqlLeaseMgr::deleteLease(const Lease4Ptr& lease) {
+ const IOAddress& addr = lease->addr_;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_DELETE_ADDR)
+ .arg(addr.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ std::string addr4_str = boost::lexical_cast<std::string>(addr.toUint32());
+ bind_array.add(addr4_str);
+
+ std::string expire_str;
+ // Avoid overflow (see createBindForSend)
+ if (lease->current_valid_lft_ == Lease::INFINITY_LFT) {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0);
+ } else {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_,
+ lease->current_valid_lft_);
+ }
+ bind_array.add(expire_str);
+
+ // Get a context
+ PgSqlLeaseTrackingContextAlloc get_context(*this, lease);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ auto affected_rows = deleteLeaseCommon(ctx, DELETE_LEASE4, bind_array);
+
+ // Check success case first as it is the most likely outcome.
+ if (affected_rows == 1) {
+ if (hasCallbacks()) {
+ trackDeleteLease(lease);
+ }
+ return (true);
+ }
+
+ // If no rows affected, lease doesn't exist.
+ if (affected_rows == 0) {
+ return (false);
+ }
+
+ // Should not happen - primary key constraint should only have selected
+ // one row.
+ isc_throw(DbOperationError, "apparently deleted more than one lease "
+ "that had the address " << lease->addr_.toText());
+}
+
+bool
+PgSqlLeaseMgr::deleteLease(const Lease6Ptr& lease) {
+ const IOAddress& addr = lease->addr_;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_DELETE_ADDR)
+ .arg(addr.toText());
+
+ // Set up the WHERE clause value
+ PsqlBindArray bind_array;
+
+ std::string addr6_str = addr.toText();
+ bind_array.add(addr6_str);
+
+ std::string expire_str;
+ // Avoid overflow (see createBindForSend)
+ if (lease->current_valid_lft_ == Lease::INFINITY_LFT) {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_, 0);
+ } else {
+ expire_str = PgSqlLeaseExchange::convertToDatabaseTime(lease->current_cltt_,
+ lease->current_valid_lft_);
+ }
+ bind_array.add(expire_str);
+
+ // Get a context
+ PgSqlLeaseTrackingContextAlloc get_context(*this, lease);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ auto affected_rows = deleteLeaseCommon(ctx, DELETE_LEASE6, bind_array);
+
+ // Check success case first as it is the most likely outcome.
+ if (affected_rows == 1) {
+ if (hasCallbacks()) {
+ trackDeleteLease(lease);
+ }
+ return (true);
+ }
+
+ // If no rows affected, lease doesn't exist.
+ if (affected_rows == 0) {
+ return (false);
+ }
+
+ // Should not happen - primary key constraint should only have selected
+ // one row.
+ isc_throw(DbOperationError, "apparently deleted more than one lease "
+ "that had the address " << lease->addr_.toText());
+}
+
+uint64_t
+PgSqlLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t secs) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED4)
+ .arg(secs);
+ return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE4_STATE_EXPIRED));
+}
+
+uint64_t
+PgSqlLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t secs) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_DELETE_EXPIRED_RECLAIMED6)
+ .arg(secs);
+ return (deleteExpiredReclaimedLeasesCommon(secs, DELETE_LEASE6_STATE_EXPIRED));
+}
+
+uint64_t
+PgSqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
+ StatementIndex statement_index) {
+ PsqlBindArray bind_array;
+
+ // State is reclaimed.
+ std::string state_str = boost::lexical_cast<std::string>(Lease::STATE_EXPIRED_RECLAIMED);
+ bind_array.add(state_str);
+
+ // Expiration timestamp.
+ std::string expiration_str = PgSqlLeaseExchange::convertToDatabaseTime(time(0) -
+ static_cast<time_t>(secs));
+ bind_array.add(expiration_str);
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Delete leases.
+ return (deleteLeaseCommon(ctx, statement_index, bind_array));
+}
+
+string
+PgSqlLeaseMgr::checkLimits(ConstElementPtr const& user_context, StatementIndex const stindex) const {
+ // No user context means no limits means allocation allowed means empty string.
+ if (!user_context) {
+ return string();
+ }
+
+ // Get a context.
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx(get_context.ctx_);
+
+ // Create bindings.
+ PsqlBindArray bind_array;
+ std::string const user_context_str(user_context->str());
+ bind_array.add(user_context_str);
+
+ // Execute the select.
+ PgSqlResult r(PQexecPrepared(ctx->conn_,
+ tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array.values_[0],
+ &bind_array.lengths_[0],
+ &bind_array.formats_[0], 0));
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+
+ std::string limits;
+ PgSqlExchange::getColumnValue(r, 0, 0, limits);
+ return limits;
+}
+
+string
+PgSqlLeaseMgr::checkLimits4(ConstElementPtr const& user_context) const {
+ return checkLimits(user_context, CHECK_LEASE4_LIMITS);
+}
+
+string
+PgSqlLeaseMgr::checkLimits6(ConstElementPtr const& user_context) const {
+ return checkLimits(user_context, CHECK_LEASE6_LIMITS);
+}
+
+bool
+PgSqlLeaseMgr::isJsonSupported() const {
+ // Get a context.
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx(get_context.ctx_);
+
+ // Execute the select.
+ StatementIndex const stindex(IS_JSON_SUPPORTED);
+ PgSqlResult r(PQexecPrepared(ctx->conn_, tagged_statements[stindex].name,
+ 0, 0, 0, 0, 0));
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+
+ bool json_supported;
+ PgSqlExchange::getColumnValue(r, 0, 0, json_supported);
+ return json_supported;
+}
+
+size_t
+PgSqlLeaseMgr::getClassLeaseCount(const ClientClass& client_class,
+ const Lease::Type& ltype /* = Lease::TYPE_V4*/) const {
+ // Get a context.
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx(get_context.ctx_);
+
+ // Create bindings.
+ PsqlBindArray bind_array;
+ bind_array.add(client_class);
+ if (ltype != Lease::TYPE_V4) {
+ bind_array.add(ltype);
+ }
+
+ // Execute the select.
+ StatementIndex const stindex(ltype == Lease::TYPE_V4 ? GET_LEASE4_COUNT_BY_CLASS :
+ GET_LEASE6_COUNT_BY_CLASS);
+ PgSqlResult r(PQexecPrepared(ctx->conn_,
+ tagged_statements[stindex].name,
+ tagged_statements[stindex].nbparams,
+ &bind_array.values_[0],
+ &bind_array.lengths_[0],
+ &bind_array.formats_[0], 0));
+ ctx->conn_.checkStatementError(r, tagged_statements[stindex]);
+
+ int rows = PQntuples(r);
+ if (rows == 0) {
+ // No entries means 0 leases.
+ return 0;
+ }
+
+ size_t count;
+ PgSqlExchange::getColumnValue(r, 0, 0, count);
+ return count;
+}
+
+void
+PgSqlLeaseMgr::recountClassLeases4() {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::recountClassLeases4() not implemented");
+}
+
+void
+PgSqlLeaseMgr::recountClassLeases6() {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::recountClassLeases6() not implemented");
+}
+
+void
+PgSqlLeaseMgr::clearClassLeaseCounts() {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::clearClassLeaseCounts() not implemented");
+}
+
+void
+PgSqlLeaseMgr::writeLeases4(const std::string&) {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::writeLeases4() not implemented");
+}
+
+void
+PgSqlLeaseMgr::writeLeases6(const std::string&) {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::writeLeases6() not implemented");
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startLeaseStatsQuery4() {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[ALL_LEASE4_STATS],
+ false));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startPoolLeaseStatsQuery4() {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[ALL_POOL_LEASE4_STATS],
+ false, true));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[SUBNET_LEASE4_STATS],
+ false,
+ subnet_id));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[SUBNET_RANGE_LEASE4_STATS],
+ false,
+ first_subnet_id,
+ last_subnet_id));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startLeaseStatsQuery6() {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[ALL_LEASE6_STATS],
+ true));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startPoolLeaseStatsQuery6() {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[ALL_POOL_LEASE6_STATS],
+ true, true));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[SUBNET_LEASE6_STATS],
+ true,
+ subnet_id));
+ query->start();
+ return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ LeaseStatsQueryPtr query(new PgSqlLeaseStatsQuery(ctx->conn_,
+ tagged_statements[SUBNET_RANGE_LEASE6_STATS],
+ true,
+ first_subnet_id,
+ last_subnet_id));
+ query->start();
+ return(query);
+}
+
+size_t
+PgSqlLeaseMgr::wipeLeases4(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases4 is not implemented for PostgreSQL backend");
+}
+
+size_t
+PgSqlLeaseMgr::wipeLeases6(const SubnetID& /*subnet_id*/) {
+ isc_throw(NotImplemented, "wipeLeases6 is not implemented for PostgreSQL backend");
+}
+
+std::string
+PgSqlLeaseMgr::getName() const {
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ std::string name = "";
+ try {
+ name = ctx->conn_.getParameter("name");
+ } catch (...) {
+ // Return an empty name
+ }
+ return (name);
+}
+
+std::string
+PgSqlLeaseMgr::getDescription() const {
+ return (std::string("PostgreSQL Database"));
+}
+
+std::pair<uint32_t, uint32_t>
+PgSqlLeaseMgr::getVersion() const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_GET_VERSION);
+
+ return (PgSqlConnection::getVersion(parameters_));
+}
+
+void
+PgSqlLeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);
+}
+
+void
+PgSqlLeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ROLLBACK);
+}
+
+void
+PgSqlLeaseMgr::deleteExtendedInfo6(const IOAddress& /* addr */) {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::deleteExtendedInfo6 not implemented");
+}
+
+void
+PgSqlLeaseMgr::addRelayId6(const IOAddress& /* lease_addr */,
+ const vector<uint8_t>& /* relay_id */) {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::addRelayId6 not implemented");
+}
+
+void
+PgSqlLeaseMgr::addRemoteId6(const IOAddress& /* lease_addr */,
+ const vector<uint8_t>& /* remote_id */) {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::addRemoteId6 not implemented");
+}
+
+namespace {
+
+std::string
+idToText(const OptionBuffer& id) {
+ std::stringstream tmp;
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = id.begin();
+ it != id.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0')
+ << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
+} // anonymous namespace
+
+Lease4Collection
+PgSqlLeaseMgr::getLeases4ByRelayId(const OptionBuffer& relay_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time /* = 0 */,
+ const time_t& qry_end_time /* = 0 */) {
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ // Catch 2038 bug with 32 bit time_t.
+ if ((qry_start_time < 0) || (qry_end_time < 0)) {
+ isc_throw(BadValue, "negative time value");
+ }
+
+ bool have_qst = (qry_start_time > 0);
+ bool have_qet = (qry_end_time > 0);
+
+ // Start time must be before end time.
+ if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+ isc_throw(BadValue, "start time must be before end time");
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_RELAYID4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(idToText(relay_id))
+ .arg(qry_start_time)
+ .arg(qry_end_time);
+
+ // Prepare WHERE clause
+ PsqlBindArray bind_array;
+
+ // Bind relay id
+ if (!relay_id.empty()) {
+ bind_array.add(relay_id);
+ } else {
+ bind_array.add("");
+ }
+
+ // Bind lower bound address
+ std::string lb_address_data =
+ boost::lexical_cast<std::string>(lower_bound_address.toUint32());
+ bind_array.add(lb_address_data);
+
+ // Bind query start time.
+ std::string start_time_str;
+ if (have_qst) {
+ start_time_str = boost::lexical_cast<std::string>(qry_start_time);
+ bind_array.add(start_time_str);
+ }
+
+ // Bind query end time.
+ std::string end_time_str;
+ if (have_qet) {
+ end_time_str = boost::lexical_cast<std::string>(qry_end_time);
+ bind_array.add(end_time_str);
+ }
+
+ // Bind page size value
+ std::string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array.add(page_size_data);
+
+ StatementIndex stindex = GET_LEASE4_RELAYID;
+ if (have_qst && !have_qet) {
+ stindex = GET_LEASE4_RELAYID_QST;
+ } else if (have_qst && have_qet) {
+ stindex = GET_LEASE4_RELAYID_QSET;
+ } else if (!have_qst && have_qet) {
+ stindex = GET_LEASE4_RELAYID_QET;
+ }
+
+ // Get the leases
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, stindex, bind_array, result);
+
+ return (result);
+}
+
+Lease4Collection
+PgSqlLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& remote_id,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time /* = 0 */,
+ const time_t& qry_end_time /* = 0 */) {
+ // Expecting IPv4 address.
+ if (!lower_bound_address.isV4()) {
+ isc_throw(InvalidAddressFamily, "expected IPv4 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ // Catch 2038 bug with 32 bit time_t.
+ if ((qry_start_time < 0) || (qry_end_time < 0)) {
+ isc_throw(BadValue, "negative time value");
+ }
+
+ bool have_qst = (qry_start_time > 0);
+ bool have_qet = (qry_end_time > 0);
+
+ // Start time must be before end time.
+ if (have_qst && have_qet && (qry_start_time > qry_end_time)) {
+ isc_throw(BadValue, "start time must be before end time");
+ }
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_REMOTEID4)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(idToText(remote_id))
+ .arg(qry_start_time)
+ .arg(qry_end_time);
+
+ // Prepare WHERE clause
+ PsqlBindArray bind_array;
+
+ // Bind remote id
+ if (!remote_id.empty()) {
+ bind_array.add(remote_id);
+ } else {
+ bind_array.add("");
+ }
+
+ // Bind lower bound address
+ std::string lb_address_data =
+ boost::lexical_cast<std::string>(lower_bound_address.toUint32());
+ bind_array.add(lb_address_data);
+
+ // Bind query start time.
+ std::string start_time_str;
+ if (have_qst) {
+ start_time_str = boost::lexical_cast<std::string>(qry_start_time);
+ bind_array.add(start_time_str);
+ }
+
+ // Bind query end time.
+ std::string end_time_str;
+ if (have_qet) {
+ end_time_str = boost::lexical_cast<std::string>(qry_end_time);
+ bind_array.add(end_time_str);
+ }
+
+ // Bind page size value
+ std::string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array.add(page_size_data);
+
+ StatementIndex stindex = GET_LEASE4_REMOTEID;
+ if (have_qst && !have_qet) {
+ stindex = GET_LEASE4_REMOTEID_QST;
+ } else if (have_qst && have_qet) {
+ stindex = GET_LEASE4_REMOTEID_QSET;
+ } else if (!have_qst && have_qet) {
+ stindex = GET_LEASE4_REMOTEID_QET;
+ }
+
+ // Get the leases
+ Lease4Collection result;
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, stindex, bind_array, result);
+
+ return (result);
+}
+
+size_t
+PgSqlLeaseMgr::upgradeExtendedInfo4(const LeasePageSize& page_size) {
+ auto check = CfgMgr::instance().getCurrentCfg()->
+ getConsistency()->getExtendedInfoSanityCheck();
+
+ size_t pages = 0;
+ size_t updated = 0;
+ IOAddress start_addr = IOAddress::IPV4_ZERO_ADDRESS();
+ for (;;) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_PAGE)
+ .arg(pages)
+ .arg(start_addr.toText())
+ .arg(updated);
+
+ // Prepare WHERE clause.
+ PsqlBindArray bind_array;
+
+ // Bind start address.
+ uint32_t start_addr_data = start_addr.toUint32();
+ bind_array.add(start_addr_data);
+
+ // Bind page size value.
+ std::string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array.add(page_size_data);
+
+ Lease4Collection leases;
+
+ // Get a context.
+ {
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ getLeaseCollection(ctx, GET_LEASE4_UCTX_PAGE, bind_array, leases);
+ }
+
+ if (leases.empty()) {
+ // Done.
+ break;
+ }
+
+ ++pages;
+ start_addr = leases.back()->addr_;
+ for (auto lease : leases) {
+ ConstElementPtr previous_user_context = lease->getContext();
+ vector<uint8_t> previous_relay_id = lease->relay_id_;
+ vector<uint8_t> previous_remote_id = lease->remote_id_;
+ if (!previous_user_context &&
+ previous_relay_id.empty() &&
+ previous_remote_id.empty()) {
+ continue;
+ }
+ bool modified = upgradeLease4ExtendedInfo(lease, check);
+ try {
+ lease->relay_id_.clear();
+ lease->remote_id_.clear();
+ extractLease4ExtendedInfo(lease, false);
+ if (modified ||
+ (previous_relay_id != lease->relay_id_) ||
+ (previous_remote_id != lease->remote_id_)) {
+ updateLease4(lease);
+ ++updated;
+ }
+ } catch (const NoSuchLease&) {
+ // The lease was modified in parallel:
+ // as its extended info was processed just ignore.
+ continue;
+ } catch (const std::exception& ex) {
+ // Something when wrong, for instance extract failed.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4_ERROR)
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ }
+ }
+ }
+
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_PGSQL_UPGRADE_EXTENDED_INFO4)
+ .arg(pages)
+ .arg(updated);
+
+ return (updated);
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6ByRelayId(const DUID& /* relay_id */,
+ const IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::getLeases6ByRelayId not implemented");
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6ByRemoteId(const OptionBuffer& /* remote_id */,
+ const IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size*/) {
+ isc_throw(NotImplemented, "PgSqlLeaseMgr::getLeases6ByRemoteId not implemented");
+}
+
+Lease6Collection
+PgSqlLeaseMgr::getLeases6ByLink(const IOAddress& link_addr,
+ uint8_t link_len,
+ const IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_PGSQL_GET_LINKADDR6)
+ .arg(page_size.page_size_)
+ .arg(lower_bound_address.toText())
+ .arg(link_addr.toText())
+ .arg(static_cast<unsigned>(link_len));
+
+ // Expecting IPv6 valid prefix and address.
+ if (!link_addr.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << link_addr);
+ }
+
+ if ((link_len == 0) || (link_len > 128)) {
+ isc_throw(OutOfRange, "invalid IPv6 prefix length "
+ << static_cast<unsigned>(link_len));
+ }
+
+ if (!lower_bound_address.isV6()) {
+ isc_throw(InvalidAddressFamily, "expected IPv6 address while "
+ "retrieving leases from the lease database, got "
+ << lower_bound_address);
+ }
+
+ Lease6Collection result;
+ const IOAddress& first_addr = firstAddrInPrefix(link_addr, link_len);
+ const IOAddress& last_addr = lastAddrInPrefix(link_addr, link_len);
+ IOAddress start_addr = lower_bound_address;
+ if (lower_bound_address < first_addr) {
+ start_addr = first_addr;
+ } else if (last_addr <= lower_bound_address) {
+ // Range was already done.
+ return (result);
+ } else {
+ // The lower bound address is from the last call so skip it.
+ start_addr = IOAddress::increase(lower_bound_address);
+ }
+
+ // Prepare WHERE clause
+ PsqlBindArray bind_array;
+
+ // Bind start address
+ std::string start_addr_str = start_addr.toText();
+ bind_array.add(start_addr_str);
+
+ // Bind last address
+ std::string last_addr_str = last_addr.toText();
+ bind_array.add(last_addr_str);
+
+ // Bind page size value
+ std::string page_size_data =
+ boost::lexical_cast<std::string>(page_size.page_size_);
+ bind_array.add(page_size_data);
+
+ // Get a context
+ PgSqlLeaseContextAlloc get_context(*this);
+ PgSqlLeaseContextPtr ctx = get_context.ctx_;
+
+ // Get the leases
+ getLeaseCollection(ctx, GET_LEASE6_LINK, bind_array, result);
+
+ return (result);
+}
+
+size_t
+PgSqlLeaseMgr::buildExtendedInfoTables6(bool /* update */, bool /* current */) {
+ isc_throw(isc::NotImplemented,
+ "PgSqlLeaseMgr::buildExtendedInfoTables6 not implemented");
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/pgsql_lease_mgr.h b/src/lib/dhcpsrv/pgsql_lease_mgr.h
new file mode 100644
index 0000000..8b60e42
--- /dev/null
+++ b/src/lib/dhcpsrv/pgsql_lease_mgr.h
@@ -0,0 +1,1253 @@
+// Copyright (C) 2013-2023 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 PGSQL_LEASE_MGR_H
+#define PGSQL_LEASE_MGR_H
+
+#include <asiolink/io_service.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_exceptions.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/utility.hpp>
+
+#include <vector>
+#include <mutex>
+
+namespace isc {
+namespace dhcp {
+
+// Forward declaration of the Lease exchange objects. These classes are defined
+// in the .cc file.
+class PgSqlLease4Exchange;
+class PgSqlLease6Exchange;
+
+/// @brief PostgreSQL Lease Context
+///
+/// This class stores the thread context for the manager pool.
+/// The class is needed by all get/update/delete functions which must use one
+/// or more exchanges to perform database operations.
+/// Each context provides a set of such exchanges for each thread.
+/// The context instances are lazy initialized by the requesting thread by using
+/// the manager's createContext function and are destroyed when the manager's
+/// pool instance is destroyed.
+class PgSqlLeaseContext {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param parameters See PgSqlLeaseMgr constructor.
+ /// @param io_service_accessor The IOService accessor function.
+ /// @param db_reconnect_callback The connection recovery callback.
+ PgSqlLeaseContext(const db::DatabaseConnection::ParameterMap& parameters,
+ db::IOServiceAccessorPtr io_service_accessor,
+ db::DbCallback db_reconnect_callback);
+
+ /// The exchange objects are used for transfer of data to/from the database.
+ /// They are pointed-to objects as the contents may change in "const" calls,
+ /// while the rest of this object does not. (At alternative would be to
+ /// declare them as "mutable".)
+ boost::scoped_ptr<PgSqlLease4Exchange> exchange4_; ///< Exchange object
+ boost::scoped_ptr<PgSqlLease6Exchange> exchange6_; ///< Exchange object
+
+ /// @brief PostgreSQL connection
+ db::PgSqlConnection conn_;
+};
+
+/// @brief Type of pointers to contexts.
+typedef boost::shared_ptr<PgSqlLeaseContext> PgSqlLeaseContextPtr;
+
+/// @brief PostgreSQL Lease Context Pool
+///
+/// This class provides a pool of contexts.
+/// The manager will use this class to handle available contexts.
+/// There is only one ContextPool per manager per back-end, which is created
+/// and destroyed by the respective manager factory class.
+class PgSqlLeaseContextPool {
+public:
+
+ /// @brief The vector of available contexts.
+ std::vector<PgSqlLeaseContextPtr> pool_;
+
+ /// @brief The mutex to protect pool access.
+ std::mutex mutex_;
+};
+
+/// @brief Type of pointers to context pools.
+typedef boost::shared_ptr<PgSqlLeaseContextPool> PgSqlLeaseContextPoolPtr;
+
+/// @brief PostgreSQL Lease Manager
+///
+/// This class provides the \ref isc::dhcp::LeaseMgr interface to the PostgreSQL
+/// database. Use of this backend presupposes that a PostgreSQL database is
+/// available and that the Kea schema has been created within it.
+
+class PgSqlLeaseMgr : public TrackingLeaseMgr {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Uses the following keywords in the parameters passed to it to
+ /// connect to the database:
+ /// - name - Name of the database to which to connect (mandatory)
+ /// - host - Host to which to connect (optional, defaults to "localhost")
+ /// - user - Username under which to connect (optional)
+ /// - password - Password for "user" on the database (optional)
+ ///
+ /// Check the schema version and create an initial context.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given
+ /// @throw isc::db::DbOpenError Error opening the database or the schema
+ /// version is incorrect.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ PgSqlLeaseMgr(const db::DatabaseConnection::ParameterMap& parameters);
+
+ /// @brief Destructor (closes database)
+ virtual ~PgSqlLeaseMgr();
+
+ /// @brief Create a new context.
+ ///
+ /// The database is opened with all the SQL commands pre-compiled.
+ ///
+ /// @return A new (never null) context.
+ /// @throw isc::dhcp::NoDatabaseName Mandatory database name not given.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ PgSqlLeaseContextPtr createContext() const;
+
+ /// @brief Attempts to reconnect the server to the lease DB backend manager.
+ ///
+ /// This is a self-rescheduling function that attempts to reconnect to the
+ /// server's lease DB backends after connectivity to one or more have been
+ /// lost. Upon entry it will attempt to reconnect via
+ /// @ref LeaseMgrFactory::create.
+ /// If this is successful, DHCP servicing is re-enabled and server returns
+ /// to normal operation.
+ ///
+ /// If reconnection fails and the maximum number of retries has not been
+ /// exhausted, it will schedule a call to itself to occur at the
+ /// configured retry interval. DHCP service remains disabled.
+ ///
+ /// If the maximum number of retries has been exhausted an error is logged
+ /// and the server shuts down.
+ ///
+ /// This function is passed to the connection recovery mechanism. It will be
+ /// invoked when a connection loss is detected.
+ ///
+ /// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
+ /// configured reconnect parameters.
+ /// @return true if connection has been recovered, false otherwise.
+ static bool dbReconnect(util::ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief Local version of getDBVersion() class method
+ static std::string getDBVersion();
+
+ /// @brief Adds an IPv4 lease
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not (because a lease
+ /// with the same address was already there).
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual bool addLease(const Lease4Ptr& lease) override;
+
+ /// @brief Adds an IPv6 lease
+ ///
+ /// @param lease lease to be added
+ ///
+ /// @result true if the lease was added, false if not (because a lease
+ /// with the same address was already there).
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual bool addLease(const Lease6Ptr& lease) override;
+
+ /// @brief Returns an IPv4 lease for specified IPv4 address
+ ///
+ /// This method return a lease that is associated with a given address.
+ /// For other query types (by hardware addr, by Client ID) there can be
+ /// several leases in different subnets (e.g. for mobile clients that
+ /// got address in different subnets). However, for a single address
+ /// there can be only one lease, so this method returns a pointer to
+ /// a single lease, not a container of leases.
+ ///
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const override;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address.
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param hwaddr hardware address of the client
+ ///
+ /// @return lease collection
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const override;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address
+ /// and a subnet
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
+ SubnetID subnet_id) const override;
+
+ /// @brief Returns existing IPv4 leases for specified client-id
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param clientid client identifier
+ ///
+ /// @return lease collection
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Collection getLease4(const ClientId& clientid) const override;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param clientid client identifier
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Ptr getLease4(const ClientId& clientid,
+ SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv4 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv4 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(const std::string& hostname) const override;
+
+ /// @brief Returns all IPv4 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4() const override;
+
+ /// @brief Returns range of IPv4 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv4 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv4 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection
+ getLeases4(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const override;
+
+ /// @brief Returns existing IPv6 lease for a given IPv6 address.
+ ///
+ /// For a given address, we assume that there will be only one lease.
+ /// The assumption here is that there will not be site or link-local
+ /// addresses used, so there is no way of having address duplication.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease6Ptr getLease6(Lease::Type type,
+ const isc::asiolink::IOAddress& addr) const override;
+
+ /// @brief Returns existing IPv6 leases for a given DUID+IA combination
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid) const override;
+
+ /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+ ///
+ /// @param type specifies lease type: (NA, TA or PD)
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param subnet_id subnet id of the subnet the lease belongs to
+ ///
+ /// @return lease collection (may be empty if no lease is found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
+ uint32_t iaid, SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv6 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(SubnetID subnet_id) const override;
+
+ /// @brief Returns all IPv6 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(const std::string& hostname) const override;
+
+ /// @brief Returns all IPv6 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6() const override;
+
+ /// @brief Returns all IPv6 leases for the DUID.
+ ///
+ /// @todo: implement an optimised of the query using index.
+ /// @return Lease collection (may be empty if no IPv6 lease found)
+ /// for the DUID.
+ virtual Lease6Collection getLeases6(const DUID& duid) const override;
+
+ /// @brief Returns range of IPv6 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv6 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv6 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv6 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection
+ getLeases6(const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) const override;
+
+ /// @brief Returns a collection of expired DHCPv4 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases4(Lease4Collection& expired_leases,
+ const size_t max_leases) const override;
+
+ /// @brief Returns a collection of expired DHCPv6 leases.
+ ///
+ /// This method returns at most @c max_leases expired leases. The leases
+ /// returned haven't been reclaimed, i.e. the database query must exclude
+ /// reclaimed leases from the results returned.
+ ///
+ /// @param [out] expired_leases A container to which expired leases returned
+ /// by the database backend are added.
+ /// @param max_leases A maximum number of leases to be returned. If this
+ /// value is set to 0, all expired (but not reclaimed) leases are returned.
+ virtual void getExpiredLeases6(Lease6Collection& expired_leases,
+ const size_t max_leases) const override;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// Updates the record of the lease in the database (as identified by the
+ /// address) with the data in the passed lease object.
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
+ /// exist.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The UPDATE query uses WHERE expire = ? to update the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and UPDATE with
+ /// different expiration time.
+ virtual void updateLease4(const Lease4Ptr& lease4) override;
+
+ /// @brief Updates IPv6 lease.
+ ///
+ /// Updates the record of the lease in the database (as identified by the
+ /// address) with the data in the passed lease object.
+ ///
+ /// @param lease6 The lease to be updated.
+ ///
+ /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
+ /// exist.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The UPDATE query uses WHERE expire = ? to update the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and UPDATE with
+ /// different expiration time.
+ virtual void updateLease6(const Lease6Ptr& lease6) override;
+
+ /// @brief Deletes an IPv4 lease.
+ ///
+ /// @param lease IPv4 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The DELETE query uses WHERE expire = ? to delete the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and DELETE with
+ /// different expiration time.
+ virtual bool deleteLease(const Lease4Ptr& lease) override;
+
+ /// @brief Deletes an IPv6 lease.
+ ///
+ /// @param lease IPv6 lease being deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ ///
+ /// @note The current_cltt_ and current_valid_lft_ are used to maximize the
+ /// chance that only one thread or process performs an update or delete
+ /// operation on the lease by matching these values with the expiration time
+ /// data in the database.
+ /// @note The DELETE query uses WHERE expire = ? to delete the lease only if
+ /// the value matches the one received on the SELECT query, effectively
+ /// enforcing no update on the lease between SELECT and DELETE with
+ /// different expiration time.
+ virtual bool deleteLease(const Lease6Ptr& lease) override;
+
+ /// @brief Deletes all expired-reclaimed DHCPv4 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t secs) override;
+
+ /// @brief Deletes all expired-reclaimed DHCPv6 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ ///
+ /// @return Number of leases deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs) override;
+
+ /// @brief Creates and runs the IPv4 lease stats query
+ ///
+ /// It creates an instance of a PgSqlLeaseStatsQuery4 and then
+ /// invokes its start method, which fetches its statistical data
+ /// result set by executing the ALL_LEASE_STATS4 query.
+ /// The query object is then returned.
+ ///
+ /// @return The populated query as a pointer to an LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery4() override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv4 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery4() override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a PgSqlLeaseStatsQuery4 for a single subnet
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery4(const SubnetID& subnet_id) override;
+
+ /// @brief Creates and runs the IPv4 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a PgSqlLeaseStatsQuery4 for a subnet range
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery4(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) override;
+
+ /// @brief Creates and runs the IPv6 lease stats query
+ ///
+ /// It creates an instance of a PgSqlLeaseStatsQuery and then
+ /// invokes its start method, which fetches its statistical data
+ /// result set by executing the ALL_LEASE_STATS6 query.
+ /// The query object is then returned.
+ ///
+ /// @return The populated query as a pointer to an LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startLeaseStatsQuery6() override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for all subnets and
+ /// pools
+ ///
+ /// LeaseMgr derivations implement this method such that it creates and
+ /// returns an instance of an LeaseStatsQuery whose result set has been
+ /// populated with up to date IPv6 lease statistical data for all subnets
+ /// and pools.
+ /// Each row of the result set is an LeaseStatRow which ordered ascending
+ /// by subnet ID and pool ID.
+ ///
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startPoolLeaseStatsQuery6() override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a PgSqlLeaseStatsQuery6 for a single subnet
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param subnet_id id of the subnet for which stats are desired
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetLeaseStatsQuery6(const SubnetID& subnet_id) override;
+
+ /// @brief Creates and runs the IPv6 lease stats query for a single subnet
+ ///
+ /// It creates an instance of a PgSqlLeaseStatsQuery6 for a subnet range
+ /// query and then invokes its start method in which the query constructs its
+ /// statistical data result set. The query object is then returned.
+ ///
+ /// @param first_subnet_id first subnet in the range of subnets
+ /// @param last_subnet_id last subnet in the range of subnets
+ /// @return A populated LeaseStatsQuery
+ virtual LeaseStatsQueryPtr startSubnetRangeLeaseStatsQuery6(const SubnetID& first_subnet_id,
+ const SubnetID& last_subnet_id) override;
+
+ /// @brief Removes specified IPv4 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases4(const SubnetID& subnet_id) override;
+
+ /// @brief Removed specified IPv6 leases.
+ ///
+ /// This rather dangerous method is able to remove all leases from specified
+ /// subnet.
+ ///
+ /// @todo: Not implemented yet.
+ ///
+ /// @param subnet_id identifier of the subnet
+ /// @return number of leases removed.
+ virtual size_t wipeLeases6(const SubnetID& subnet_id) override;
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const override {
+ return (std::string("postgresql"));
+ }
+
+ /// @brief Returns backend name.
+ ///
+ /// Each backend have specific name.
+ ///
+ /// @return Name of the backend.
+ virtual std::string getName() const override;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const override;
+
+ /// @brief Returns backend version.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const override;
+
+ /// @brief Commit Transactions
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// PostgreSQL supports transactions but this manager does not use them.
+ virtual void commit() override;
+
+ /// @brief Rollback Transactions
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// PostgreSQL supports transactions but this manager does not use them.
+ virtual void rollback() override;
+
+ /// @brief Statement Tags
+ ///
+ /// The contents of the enum are indexes into the list of compiled SQL
+ /// statements
+ enum StatementIndex {
+ DELETE_LEASE4, // Delete from lease4 by address
+ DELETE_LEASE4_STATE_EXPIRED, // Delete expired lease4 in a given state
+ DELETE_LEASE6, // Delete from lease6 by address
+ DELETE_LEASE6_STATE_EXPIRED, // Delete expired lease6 in a given state
+ GET_LEASE4, // Get all IPv4 leases
+ GET_LEASE4_ADDR, // Get lease4 by address
+ GET_LEASE4_CLIENTID, // Get lease4 by client ID
+ GET_LEASE4_CLIENTID_SUBID, // Get lease4 by client ID & subnet ID
+ GET_LEASE4_HWADDR, // Get lease4 by HW address
+ GET_LEASE4_HWADDR_SUBID, // Get lease4 by HW address & subnet ID
+ GET_LEASE4_PAGE, // Get page of leases beginning with an address
+ GET_LEASE4_UCTX_PAGE, // Get page of leases with user context
+ GET_LEASE4_SUBID, // Get IPv4 leases by subnet ID
+ GET_LEASE4_HOSTNAME, // Get IPv4 leases by hostname
+ GET_LEASE4_EXPIRE, // Get lease4 by expiration.
+ GET_LEASE4_RELAYID, // Get page of lease by relay ID.
+ GET_LEASE4_RELAYID_QST, // Get page of leases by relay ID and query start time.
+ GET_LEASE4_RELAYID_QSET, // Get page of leases by relay ID and query start and end times.
+ GET_LEASE4_RELAYID_QET, // Get page of leases by relay ID and query end time.
+ GET_LEASE4_REMOTEID, // Get page of lease by remote ID.
+ GET_LEASE4_REMOTEID_QST, // Get page of leases by remote ID and query start time.
+ GET_LEASE4_REMOTEID_QSET, // Get page of leases by remote ID and query start and end times.
+ GET_LEASE4_REMOTEID_QET, // Get page of leases by remote ID and query end time.
+ GET_LEASE6, // Get all IPv6 leases
+ GET_LEASE6_ADDR, // Get lease6 by address
+ GET_LEASE6_DUID_IAID, // Get lease6 by DUID and IAID
+ GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and subnet ID
+ GET_LEASE6_PAGE, // Get page of leases beginning with an address
+ GET_LEASE6_UCTX_PAGE, // Get page of leases with user context
+ GET_LEASE6_SUBID, // Get IPv6 leases by subnet ID
+ GET_LEASE6_DUID, // Get IPv6 leases by DUID
+ GET_LEASE6_HOSTNAME, // Get IPv6 leases by hostname
+ GET_LEASE6_EXPIRE, // Get lease6 by expiration.
+ GET_LEASE6_LINK, // Get page of lease6 by link
+ INSERT_LEASE4, // Add entry to lease4 table
+ INSERT_LEASE6, // Add entry to lease6 table
+ UPDATE_LEASE4, // Update a Lease4 entry
+ UPDATE_LEASE6, // Update a Lease6 entry
+ ALL_LEASE4_STATS, // Fetches IPv4 lease statistics
+ SUBNET_LEASE4_STATS, // Fetched IPv4 lease stats for a single subnet.
+ SUBNET_RANGE_LEASE4_STATS, // Fetched IPv4 lease stats for a subnet range.
+ ALL_POOL_LEASE4_STATS, // Fetches IPv4 lease pool statistics
+ ALL_LEASE6_STATS, // Fetches IPv6 lease statistics
+ SUBNET_LEASE6_STATS, // Fetched IPv6 lease stats for a single subnet.
+ SUBNET_RANGE_LEASE6_STATS, // Fetched IPv6 lease stats for a subnet range.
+ ALL_POOL_LEASE6_STATS, // Fetches IPv6 lease pool statistics
+ CHECK_LEASE4_LIMITS, // Check if allocated IPv4 leases are inside the set limits.
+ CHECK_LEASE6_LIMITS, // Check if allocated IPv6 leases are inside the set limits.
+ IS_JSON_SUPPORTED, // Checks if JSON support is enabled in the database.
+ GET_LEASE4_COUNT_BY_CLASS, // Fetches the IPv4 lease count for a given class.
+ GET_LEASE6_COUNT_BY_CLASS, // Fetches the IPv6 lease count for given class and lease type.
+ NUM_STATEMENTS // Number of statements
+ };
+
+private:
+
+ /// @brief Add Lease Common Code
+ ///
+ /// This method performs the common actions for both flavours (V4 and V6)
+ /// of the addLease method. It binds the contents of the lease object to
+ /// the prepared statement and adds it to the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind_array array that has been created for the type
+ /// of lease in question.
+ ///
+ /// @return true if the lease was added, false if it was not added because
+ /// a lease with that address already exists in the database.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ bool addLeaseCommon(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array);
+
+ /// @brief Get Lease Collection Common Code
+ ///
+ /// This method performs the common actions for obtaining multiple leases
+ /// from the database.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind_array array for input parameters
+ /// @param exchange Exchange object to use
+ /// @param result Returned collection of leases. Note that any leases in
+ /// the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ /// @param single If true, only a single data item is to be retrieved.
+ /// If more than one is present, a MultipleRecords exception will
+ /// be thrown.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::db::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ template <typename Exchange, typename LeaseCollection>
+ void getLeaseCollection(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array,
+ Exchange& exchange, LeaseCollection& result,
+ bool single = false) const;
+
+ /// @brief Get Lease4 Collection
+ ///
+ /// Gets a collection of Lease4 objects. This is just an interface to
+ /// the get lease collection common code.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind_array array for input parameters
+ /// @param result LeaseCollection object returned. Note that any leases in
+ /// the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::db::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getLeaseCollection(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array,
+ Lease4Collection& result) const {
+ getLeaseCollection(ctx, stindex, bind_array, ctx->exchange4_, result);
+ }
+
+ /// @brief Get Lease6 Collection
+ ///
+ /// Gets a collection of Lease6 objects. This is just an interface to
+ /// the get lease collection common code.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind_array array for input parameters
+ /// @param result LeaseCollection object returned. Note that any existing
+ /// data in the collection is erased first.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::db::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getLeaseCollection(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array,
+ Lease6Collection& result) const {
+ getLeaseCollection(ctx, stindex, bind_array, ctx->exchange6_, result);
+ }
+
+ /// @brief Get Lease4 Common Code
+ ///
+ /// This method performs the common actions for the various getLease4()
+ /// methods. It acts as an interface to the getLeaseCollection() method,
+ /// but retrieving only a single lease.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind_array array for input parameters
+ /// @param result Lease4 object returned
+ void getLease(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array,
+ Lease4Ptr& result) const;
+
+ /// @brief Get Lease6 Common Code
+ ///
+ /// This method performs the common actions for the various getLease6()
+ /// methods. It acts as an interface to the getLeaseCollection() method,
+ /// but retrieving only a single lease.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of statement being executed
+ /// @param bind_array array for input parameters
+ /// @param result Lease6 object returned
+ void getLease(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array,
+ Lease6Ptr& result) const;
+
+ /// @brief Get expired leases common code.
+ ///
+ /// This method retrieves expired and not reclaimed leases from the
+ /// lease database. The returned leases are ordered by the expiration
+ /// time. The maximum number of leases to be returned is specified
+ /// as an argument.
+ ///
+ /// @param [out] expired_leases Reference to the container where the
+ /// retrieved leases are put.
+ /// @param max_leases Maximum number of leases to be returned.
+ /// @param statement_index One of the @c GET_LEASE4_EXPIRE or
+ /// @c GET_LEASE6_EXPIRE.
+ ///
+ /// @tparam One of the @c Lease4Collection or @c Lease6Collection.
+ template<typename LeaseCollection>
+ void getExpiredLeasesCommon(LeaseCollection& expired_leases,
+ const size_t max_leases,
+ StatementIndex statement_index) const;
+
+ /// @brief Update lease common code
+ ///
+ /// Holds the common code for updating a lease. It binds the parameters
+ /// to the prepared statement, executes it, then checks how many rows
+ /// were affected.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of prepared statement to be executed
+ /// @param bind_array Array containing lease values and where clause
+ /// parameters for the update.
+ /// @param lease Pointer to the lease object whose record is being updated.
+ ///
+ /// @throw NoSuchLease Could not update a lease because no lease matches
+ /// the address given.
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ template <typename LeasePtr>
+ void updateLeaseCommon(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array,
+ const LeasePtr& lease);
+
+ /// @brief Delete lease common code
+ ///
+ /// Holds the common code for deleting a lease. It binds the parameters
+ /// to the prepared statement, executes the statement and checks to
+ /// see how many rows were deleted.
+ ///
+ /// @param ctx Context
+ /// @param stindex Index of prepared statement to be executed
+ /// @param bind_array Array containing lease values and where clause
+ /// parameters for the delete
+ ///
+ /// @return Number of deleted leases.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ uint64_t deleteLeaseCommon(PgSqlLeaseContextPtr& ctx,
+ StatementIndex stindex,
+ db::PsqlBindArray& bind_array);
+
+ /// @brief Delete expired-reclaimed leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ /// @param statement_index One of the @c DELETE_LEASE4_STATE_EXPIRED or
+ /// @c DELETE_LEASE6_STATE_EXPIRED.
+ ///
+ /// @return Number of leases deleted.
+ uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
+ StatementIndex statement_index);
+
+ /// @brief Checks if the lease limits set in the given user context are exceeded.
+ /// Contains common logic used by @ref checkLimits4 and @ref checkLimits6.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+ /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ std::string
+ checkLimits(isc::data::ConstElementPtr const& user_context, StatementIndex const stindex) const;
+
+ /// @brief Checks if the IPv4 lease limits set in the given user context are exceeded.
+ /// PostgreSQL implementation.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2 } ],
+ /// "subnet": { "id": 1, "address-limit": 2 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string
+ checkLimits4(isc::data::ConstElementPtr const& user_context) const override;
+
+ /// @brief Checks if the IPv6 lease limits set in the given user context are exceeded.
+ /// MySQL implementation.
+ ///
+ /// @param user_context all or part of the lease's user context which, for the intents and
+ /// purposes of lease limiting should have the following format
+ /// (not all nodes are mandatory and values are given only as examples):
+ /// { "ISC": { "limits": { "client-classes": [ { "name": "foo", "address-limit": 2, "prefix-limit": 1 } ],
+ /// "subnet": { "id": 1, "address-limit": 2, "prefix-limit": 1 } } } }
+ ///
+ /// @return a string describing a limit that is being exceeded, or an empty
+ /// string if no limits are exceeded
+ virtual std::string
+ checkLimits6(isc::data::ConstElementPtr const& user_context) const override;
+
+ /// @brief Checks if JSON support is enabled in the database.
+ /// PostgreSQL implementation.
+ ///
+ /// @return true if there is JSON support, false otherwise
+ virtual bool isJsonSupported() const override;
+
+ /// @brief Returns the class lease count for a given class and lease type.
+ ///
+ /// @param client_class client class for which the count is desired
+ /// @param ltype type of lease for which the count is desired. Defaults to
+ /// Lease::TYPE_V4.
+ ///
+ /// @return number of leases
+ virtual size_t getClassLeaseCount(const ClientClass& client_class,
+ const Lease::Type& ltype = Lease::TYPE_V4) const override;
+
+ /// @brief Recount the leases per class for V4 leases.
+ virtual void recountClassLeases4() override;
+
+ /// @brief Recount the leases per class for V6 leases.
+ virtual void recountClassLeases6() override;
+
+ /// @brief Clears the class-lease count map.
+ virtual void clearClassLeaseCounts() override;
+
+ /// The following queries are used to fulfill Bulk Lease Query
+ /// queries. They rely on relay data contained in lease's
+ /// user-context when the extended-store-info flag is enabled.
+
+ /// @brief Returns existing IPv4 leases with a given relay-id.
+ ///
+ /// @param relay_id RAI Relay-ID sub-option value for relay_id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRelayId(const OptionBuffer& relay_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) override;
+
+ /// @brief Returns existing IPv4 leases with a given remote-id.
+ ///
+ /// @param remote_id RAI Remote-ID sub-option value for remote-id of interest
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ /// @param qry_start_time when not zero, only leases whose CLTT is greater than
+ /// or equal to this value will be included. Defaults to zero.
+ /// @param qry_end_time when not zero, only leases whose CLTT is less than
+ /// or equal to this value will be included. Defaults to zero.
+ ///
+ /// @return collection of IPv4 leases
+ virtual Lease4Collection
+ getLeases4ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size,
+ const time_t& qry_start_time = 0,
+ const time_t& qry_end_time = 0) override;
+
+ /// @brief Returns existing IPv6 leases with a given relay-id.
+ ///
+ /// @param relay_id DUID for relay_id of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRelayId(const DUID& relay_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Returns existing IPv6 leases with a given remote-id.
+ ///
+ /// @param remote_id remote-id option data of interest.
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByRemoteId(const OptionBuffer& remote_id,
+ const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Returns existing IPv6 leases with on a given link.
+ ///
+ /// @param link_addr limit results to leases on this link (prefix).
+ /// @param link_len limit results to leases on this link (length).
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return collection of IPv6 leases
+ virtual Lease6Collection
+ getLeases6ByLink(const asiolink::IOAddress& link_addr,
+ uint8_t link_len,
+ const asiolink::IOAddress& lower_bound_address,
+ const LeasePageSize& page_size) override;
+
+ /// @brief Upgrade extended info (v4).
+ ///
+ /// For all leases with a not null user context.
+ /// - sanitize the user context
+ /// - update relay and remote ids
+ /// - when the lease was modified update it in the database
+ /// This function implements the new BLQ hook command named
+ /// "extended-info4-upgrade".
+ ///
+ /// @param page_size The page size used for retrieval.
+ /// @return The number of updates in the database.
+ virtual size_t upgradeExtendedInfo4(const LeasePageSize& page_size) override;
+
+ /// @brief Build extended info v6 tables.
+ ///
+ /// @param update Update extended info in database.
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config.
+ /// @return The number of updates in the database or 0.
+ virtual size_t buildExtendedInfoTables6(bool update, bool current) override;
+
+ /// @brief Write V4 leases to a file.
+ virtual void writeLeases4(const std::string& /*filename*/) override;
+
+ /// @brief Write V6 leases to a file.
+ virtual void writeLeases6(const std::string& /*filename*/) override;
+
+ /// @brief Context RAII allocator.
+ class PgSqlLeaseContextAlloc {
+ public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor takes a context of the pool if one is available
+ /// or creates a new one.
+ ///
+ /// @param mgr A parent instance
+ PgSqlLeaseContextAlloc(const PgSqlLeaseMgr& mgr);
+
+ /// @brief Destructor
+ ///
+ /// This destructor puts back the context in the pool.
+ ~PgSqlLeaseContextAlloc();
+
+ /// @brief The context
+ PgSqlLeaseContextPtr ctx_;
+
+ private:
+
+ /// @brief The manager
+ const PgSqlLeaseMgr& mgr_;
+ };
+
+ /// @brief Context RAII allocator for lease tracking.
+ ///
+ /// This context should be used in the non-const calls that
+ /// may trigger callbacks for lease tracking.
+ class PgSqlLeaseTrackingContextAlloc {
+ public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor takes a context of the pool if one is available
+ /// or creates a new one.
+ ///
+ /// @param mgr A parent instance
+ /// @param lease allocated or deallocated lease instance.
+ PgSqlLeaseTrackingContextAlloc(PgSqlLeaseMgr& mgr, const LeasePtr& lease);
+
+ /// @brief Destructor
+ ///
+ /// This destructor puts back the context in the pool.
+ ~PgSqlLeaseTrackingContextAlloc();
+
+ /// @brief The context
+ PgSqlLeaseContextPtr ctx_;
+
+ private:
+
+ /// @brief The manager
+ PgSqlLeaseMgr& mgr_;
+
+ /// @brief Tracked lease instance.
+ LeasePtr lease_;
+ };
+
+protected:
+
+ /// Extended information / Bulk Lease Query shared interface.
+
+ /// @brief Modifies the setting whether the lease extended info tables
+ /// are enabled.
+ ///
+ /// Transient redefine to refuse the enable setting.
+ /// @param enabled new setting.
+ virtual void setExtendedInfoTablesEnabled(const bool enabled) override {
+ if (enabled) {
+ isc_throw(isc::NotImplemented,
+ "extended info tables are not yet supported by postgresql");
+ }
+ }
+
+ /// @brief Decode parameters to set whether the lease extended info tables
+ /// are enabled.
+ ///
+ /// @note: common code in constructors.
+ ///
+ /// @param parameters The parameter map.
+ virtual void setExtendedInfoTablesEnabled(const db::DatabaseConnection::ParameterMap& parameters) override;
+
+ /// @brief Delete lease6 extended info from tables.
+ ///
+ /// @param addr The address of the lease.
+ virtual void deleteExtendedInfo6(const isc::asiolink::IOAddress& addr) override;
+
+ /// @brief Add lease6 extended info into by-relay-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param relay_id The relay id from the relay header options.
+ virtual void addRelayId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& relay_id) override;
+
+ /// @brief Add lease6 extended info into by-remote-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param remote_id The remote id from the relay header options.
+ virtual void addRemoteId6(const isc::asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& remote_id) override;
+
+private:
+
+ // Members
+
+ /// @brief The parameters
+ db::DatabaseConnection::ParameterMap parameters_;
+
+ /// @brief The pool of contexts
+ PgSqlLeaseContextPoolPtr pool_;
+
+ /// @brief Timer name used to register database reconnect timer.
+ std::string timer_name_;
+};
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // PGSQL_LEASE_MGR_H
diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc
new file mode 100644
index 0000000..2a47333
--- /dev/null
+++ b/src/lib/dhcpsrv/pool.cc
@@ -0,0 +1,438 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <dhcpsrv/pool.h>
+#include <boost/make_shared.hpp>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+Pool::Pool(Lease::Type type, const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last)
+ : id_(0), first_(first), last_(last), type_(type), capacity_(0),
+ cfg_option_(new CfgOption()), client_class_("") {
+}
+
+bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
+ return (first_ <= addr && addr <= last_);
+}
+
+bool Pool::clientSupported(const ClientClasses& classes) const {
+ return (client_class_.empty() || classes.contains(client_class_));
+}
+
+void Pool::allowClientClass(const ClientClass& class_name) {
+ client_class_ = class_name;
+}
+
+std::string
+Pool::toText() const {
+ std::stringstream tmp;
+ tmp << "type=" << Lease::typeToText(type_) << ", " << first_
+ << "-" << last_;
+ return (tmp.str());
+}
+
+Pool4::Pool4(const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last)
+ : Pool(Lease::TYPE_V4, first, last) {
+ // check if specified address boundaries are sane
+ if (!first.isV4() || !last.isV4()) {
+ isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
+ }
+
+ if (last < first) {
+ isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
+ }
+
+ // This is IPv4 pool, which only has one type. We can calculate
+ // the number of theoretically possible leases in it. As there's 2^32
+ // possible IPv4 addresses, we'll be able to accurately store that
+ // info.
+ capacity_ = addrsInRange(first, last);
+}
+
+Pool4::Pool4(const isc::asiolink::IOAddress& prefix, uint8_t prefix_len)
+ : Pool(Lease::TYPE_V4, prefix, IOAddress("0.0.0.0")) {
+
+ // check if the prefix is sane
+ if (!prefix.isV4()) {
+ isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
+ }
+
+ // check if the prefix length is sane
+ if (prefix_len == 0 || prefix_len > 32) {
+ isc_throw(BadValue, "Invalid prefix length");
+ }
+
+ IOAddress first_address = firstAddrInPrefix(prefix, prefix_len);
+ if (first_address != prefix) {
+ isc_throw(BadValue, "Invalid Pool4 address boundaries: " << prefix
+ << " is not the first address in prefix: " << first_address
+ << "/" << static_cast<uint32_t>(prefix_len));
+ }
+
+ // Let's now calculate the last address in defined pool
+ last_ = lastAddrInPrefix(prefix, prefix_len);
+
+ // This is IPv4 pool, which only has one type. We can calculate
+ // the number of theoretically possible leases in it. As there's 2^32
+ // possible IPv4 addresses, we'll be able to accurately store that
+ // info.
+ capacity_ = addrsInRange(prefix, last_);
+}
+
+Pool4Ptr
+Pool4::create(const IOAddress& first, const IOAddress& last) {
+ return (boost::make_shared<Pool4>(first, last));
+}
+
+Pool4Ptr
+Pool4::create(const IOAddress& prefix, uint8_t prefix_len) {
+ return (boost::make_shared<Pool4>(prefix, prefix_len));
+}
+
+data::ElementPtr
+Pool::toElement() const {
+ // Prepare the map
+ ElementPtr map = Element::createMap();
+
+ // Set user-context
+ contextToElement(map);
+
+ // Set pool options
+ ConstCfgOptionPtr opts = getCfgOption();
+ map->set("option-data", opts->toElement());
+
+ // Set client-class
+ const ClientClass& cclass = getClientClass();
+ if (!cclass.empty()) {
+ map->set("client-class", Element::create(cclass));
+ }
+
+ // Set require-client-classes
+ const ClientClasses& classes = getRequiredClasses();
+ if (!classes.empty()) {
+ ElementPtr class_list = Element::createList();
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
+ class_list->add(Element::create(*it));
+ }
+ map->set("require-client-classes", class_list);
+ }
+
+ if (id_) {
+ map->set("pool-id", Element::create(static_cast<long long>(id_)));
+ }
+
+ return (map);
+}
+
+data::ElementPtr
+Pool4::toElement() const {
+ // Prepare the map
+ ElementPtr map = Pool::toElement();
+
+ // Set pool
+ const IOAddress& first = getFirstAddress();
+ const IOAddress& last = getLastAddress();
+ std::string range = first.toText() + "-" + last.toText();
+
+ // Try to output a prefix (vs a range)
+ int prefix_len = prefixLengthFromRange(first, last);
+ if (prefix_len >= 0) {
+ std::ostringstream oss;
+ oss << first.toText() << "/" << prefix_len;
+ range = oss.str();
+ }
+
+ map->set("pool", Element::create(range));
+ return (map);
+}
+
+Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last)
+ : Pool(type, first, last), prefix_len_(128), pd_exclude_option_() {
+
+ // check if specified address boundaries are sane
+ if (!first.isV6() || !last.isV6()) {
+ isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
+ }
+
+ if ((type != Lease::TYPE_NA) && (type != Lease::TYPE_TA) &&
+ (type != Lease::TYPE_PD)) {
+ isc_throw(BadValue, "Invalid Pool6 type: " << static_cast<int>(type)
+ << ", must be TYPE_IA, TYPE_TA or TYPE_PD");
+ }
+
+ if (last < first) {
+ isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
+ // This check is a bit strict. If we decide that it is too strict,
+ // we need to comment it and uncomment lines below.
+ // On one hand, letting the user specify 2001::f - 2001::1 is nice, but
+ // on the other hand, 2001::1 may be a typo and the user really meant
+ // 2001::1:0 (or 1 followed by some hex digit), so a at least a warning
+ // would be useful.
+
+ // first_ = last;
+ // last_ = first;
+ }
+
+ // TYPE_PD is not supported by this constructor. first-last style
+ // parameters are for IA and TA only. There is another dedicated
+ // constructor for that (it uses prefix/length)
+ if ((type != Lease::TYPE_NA) && (type != Lease::TYPE_TA)) {
+ isc_throw(BadValue, "Invalid Pool6 type specified: "
+ << static_cast<int>(type));
+ }
+
+ // Let's calculate the theoretical number of leases in this pool.
+ // If the pool is extremely large (i.e. contains more than 2^64 addresses,
+ // we'll just cap it at max value of uint64_t).
+ capacity_ = addrsInRange(first, last);
+}
+
+Pool6::Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
+ const uint8_t prefix_len, const uint8_t delegated_len /* = 128 */)
+ : Pool(type, prefix, IOAddress::IPV6_ZERO_ADDRESS()),
+ prefix_len_(delegated_len), pd_exclude_option_() {
+
+ init(type, prefix, prefix_len, delegated_len,
+ IOAddress::IPV6_ZERO_ADDRESS(), 0);
+}
+
+Pool6::Pool6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len)
+ : Pool(Lease::TYPE_PD, prefix, IOAddress::IPV6_ZERO_ADDRESS()),
+ prefix_len_(delegated_len), pd_exclude_option_() {
+
+ init(Lease::TYPE_PD, prefix, prefix_len, delegated_len, excluded_prefix,
+ excluded_prefix_len);
+
+ // The excluded prefix can only be specified using this constructor.
+ // Therefore, the initialization of the excluded prefix is takes place
+ // here, rather than in the init(...) function.
+ if (!excluded_prefix.isV6()) {
+ isc_throw(BadValue, "excluded prefix must be an IPv6 prefix");
+ }
+
+ // An "unspecified" prefix should have both value and length equal to 0.
+ if ((excluded_prefix.isV6Zero() && (excluded_prefix_len != 0)) ||
+ (!excluded_prefix.isV6Zero() && (excluded_prefix_len == 0))) {
+ isc_throw(BadValue, "invalid excluded prefix "
+ << excluded_prefix << "/"
+ << static_cast<unsigned>(excluded_prefix_len));
+ }
+
+ // If excluded prefix has been specified.
+ if (!excluded_prefix.isV6Zero() && (excluded_prefix_len != 0)) {
+ // Excluded prefix length must not be greater than 128.
+ if (excluded_prefix_len > 128) {
+ isc_throw(BadValue, "excluded prefix length "
+ << static_cast<unsigned>(excluded_prefix_len)
+ << " must not be greater than 128");
+ }
+
+ // Excluded prefix must be a sub-prefix of a delegated prefix. First
+ // check the prefix length as it is less involved.
+ if (excluded_prefix_len <= prefix_len_) {
+ isc_throw(BadValue, "excluded prefix length "
+ << static_cast<unsigned>(excluded_prefix_len)
+ << " must be longer than the delegated prefix length "
+ << static_cast<unsigned>(prefix_len_));
+ }
+
+ /// @todo Check that the prefixes actually match. Theoretically, a
+ /// user could specify a prefix which sets insignificant bits. We should
+ /// clear insignificant bits based on the prefix length but this
+ /// should be considered a part of the IOAddress class, perhaps and
+ /// requires a bit of work (mainly in terms of testing).
+ }
+}
+
+Pool6Ptr
+Pool6::create(Lease::Type type, const IOAddress& first, const IOAddress& last) {
+ return (boost::make_shared<Pool6>(type, first, last));
+}
+
+Pool6Ptr
+Pool6::create(Lease::Type type, const IOAddress& prefix,
+ uint8_t prefix_len, uint8_t delegated_len) {
+ return (boost::make_shared<Pool6>(type, prefix, prefix_len, delegated_len));
+}
+
+Pool6Ptr
+Pool6::create(const IOAddress& prefix, const uint8_t prefix_len,
+ const uint8_t delegated_len, const IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len) {
+ return (boost::make_shared<Pool6>(prefix, prefix_len,
+ delegated_len, excluded_prefix,
+ excluded_prefix_len));
+}
+
+void
+Pool6::init(const Lease::Type& type,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len) {
+ // Check if the prefix is sane
+ if (!prefix.isV6()) {
+ isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
+ }
+
+ // Check if the prefix length is sane
+ if (prefix_len == 0 || prefix_len > 128) {
+ isc_throw(BadValue, "Invalid prefix length: "
+ << static_cast<unsigned>(prefix_len));
+ }
+
+ if (prefix_len > delegated_len) {
+ isc_throw(BadValue, "Delegated length ("
+ << static_cast<int>(delegated_len)
+ << ") must be longer than or equal to prefix length ("
+ << static_cast<int>(prefix_len) << ")");
+ }
+
+ if ((type != Lease::TYPE_PD) && (delegated_len != 128)) {
+ isc_throw(BadValue, "For NA or TA pools, delegated prefix length must"
+ << " be 128.");
+ }
+
+ // excluded_prefix_len == 0 means there's no excluded prefix at all.
+ if (excluded_prefix_len && (excluded_prefix_len <= delegated_len)) {
+ isc_throw(BadValue, "Excluded prefix ("
+ << static_cast<int>(excluded_prefix_len)
+ << ") must be longer than the delegated prefix length ("
+ << static_cast<int>(delegated_len) << ")");
+ }
+
+ if (prefix_len != 128) {
+ IOAddress first_address = firstAddrInPrefix(prefix, prefix_len);
+ if (first_address != prefix) {
+ isc_throw(BadValue, "Invalid Pool6 address boundaries: " << prefix
+ << " is not the first address in prefix: " << first_address
+ << "/" << static_cast<uint32_t>(prefix_len));
+ }
+ }
+
+ /// @todo: We should probably implement checks against weird addresses
+ /// here, like ::, starting with fe80, starting with ff etc. .
+
+ // Let's now calculate the last address in defined pool
+ last_ = lastAddrInPrefix(prefix, prefix_len);
+
+ // Let's calculate the theoretical number of leases in this pool.
+ // For addresses, we could use addrsInRange(prefix, last_), but it's
+ // much faster to do calculations on prefix lengths.
+ capacity_ = prefixesInRange(prefix_len, delegated_len);
+
+ // If user specified an excluded prefix, create an option that will
+ // be sent to clients obtaining prefixes from this pool.
+ if (excluded_prefix_len > 0) {
+ pd_exclude_option_.reset(new Option6PDExclude(prefix, delegated_len,
+ excluded_prefix,
+ excluded_prefix_len));
+ }
+}
+
+data::ElementPtr
+Pool6::toElement() const {
+ // Prepare the map
+ ElementPtr map = Pool::toElement();
+
+ switch (getType()) {
+ case Lease::TYPE_NA: {
+ const IOAddress& first = getFirstAddress();
+ const IOAddress& last = getLastAddress();
+ std::string range = first.toText() + "-" + last.toText();
+
+ // Try to output a prefix (vs a range)
+ int prefix_len = prefixLengthFromRange(first, last);
+ if (prefix_len >= 0) {
+ std::ostringstream oss;
+ oss << first.toText() << "/" << prefix_len;
+ range = oss.str();
+ }
+
+ map->set("pool", Element::create(range));
+ break;
+ }
+ case Lease::TYPE_PD: {
+ // Set prefix
+ const IOAddress& prefix = getFirstAddress();
+ map->set("prefix", Element::create(prefix.toText()));
+
+ // Set prefix-len (get it from min - max)
+ const IOAddress& last = getLastAddress();
+ int prefix_len = prefixLengthFromRange(prefix, last);
+ if (prefix_len < 0) {
+ // The pool is bad: give up
+ isc_throw(ToElementError, "invalid prefix range "
+ << prefix.toText() << "-" << last.toText());
+ }
+ map->set("prefix-len", Element::create(prefix_len));
+
+ // Set delegated-len
+ uint8_t len = getLength();
+ map->set("delegated-len", Element::create(static_cast<int>(len)));
+
+ // Set excluded prefix
+ const Option6PDExcludePtr& xopt = getPrefixExcludeOption();
+ if (xopt) {
+ const IOAddress& xprefix = xopt->getExcludedPrefix(prefix, len);
+ map->set("excluded-prefix", Element::create(xprefix.toText()));
+
+ uint8_t xlen = xopt->getExcludedPrefixLength();
+ map->set("excluded-prefix-len",
+ Element::create(static_cast<int>(xlen)));
+ }
+ // Let's not insert empty excluded-prefix values. If we ever
+ // decide to insert it after all, here's the code to do it:
+ // else {
+ // map->set("excluded-prefix",
+ // Element::create(std::string("::")));
+ // map->set("excluded-prefix-len", Element::create(0));
+ /// }
+
+ break;
+ }
+ default:
+ isc_throw(ToElementError, "Lease type: " << getType()
+ << ", unsupported for Pool6");
+ break;
+ }
+
+ return (map);
+}
+
+std::string
+Pool6::toText() const {
+ std::ostringstream s;
+ s << "type=" << Lease::typeToText(type_) << ", " << first_
+ << "-" << last_ << ", delegated_len="
+ << static_cast<unsigned>(prefix_len_);
+
+ if (pd_exclude_option_) {
+ s << ", excluded_prefix_len="
+ << static_cast<unsigned>(pd_exclude_option_->getExcludedPrefixLength());
+ }
+ return (s.str());
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
new file mode 100644
index 0000000..0015bb1
--- /dev/null
+++ b/src/lib/dhcpsrv/pool.h
@@ -0,0 +1,491 @@
+// Copyright (C) 2012-2023 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 POOL_H
+#define POOL_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <cc/user_context.h>
+#include <dhcp/classify.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcpsrv/allocation_state.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/ip_range_permutation.h>
+#include <util/bigints.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief base class for Pool4 and Pool6
+///
+/// Stores information about pool of IPv4 or IPv6 addresses.
+/// That is a basic component of a configuration.
+class Pool : public isc::data::UserContext, public isc::data::CfgToElement {
+
+public:
+ /// @note:
+ /// PoolType enum was removed. Please use Lease::Type instead
+
+ /// @brief Returns Pool-id
+ ///
+ /// Pool-id is an unique value that can be used to identify a pool within a
+ /// subnet or shared network.
+ ///
+ /// @return pool-id value
+ uint64_t getID() const {
+ return (id_);
+ }
+
+ /// @brief Sets Pool-id
+ ///
+ /// Pool-id is an unique value that can be used to identify a pool within a
+ /// subnet or shared network.
+ ///
+ /// @param id value to be set
+ void setID(const uint64_t id) {
+ id_ = id;
+ }
+
+ /// @brief Returns the first address in a pool.
+ ///
+ /// @return first address in a pool
+ const isc::asiolink::IOAddress& getFirstAddress() const {
+ return (first_);
+ }
+
+ /// @brief Returns the last address in a pool.
+ /// @return last address in a pool
+ const isc::asiolink::IOAddress& getLastAddress() const {
+ return (last_);
+ }
+
+ /// @brief Checks if a given address is in the range.
+ ///
+ /// @return true, if the address is in pool
+ bool inRange(const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief Returns pool type (v4, v6 non-temporary, v6 temp, v6 prefix)
+ /// @return returns pool type
+ Lease::Type getType() const {
+ return (type_);
+ }
+
+ /// @brief returns textual representation of the pool
+ ///
+ /// @return textual representation
+ virtual std::string toText() const;
+
+ /// @brief virtual destructor
+ ///
+ /// We need Pool to be a polymorphic class, so we could dynamic cast
+ /// from PoolPtr to Pool6Ptr if we need to. A class becomes polymorphic,
+ /// when there is at least one virtual method.
+ virtual ~Pool() = default;
+
+ /// @brief Returns the number of all leases in this pool.
+ ///
+ /// Note that this is the upper bound, assuming that no leases are used
+ /// and there are no host reservations. This is just a theoretical calculation.
+ /// @return number of possible leases in this pool
+ isc::util::uint128_t getCapacity() const {
+ return (capacity_);
+ }
+
+ /// @brief Returns pointer to the option data configuration for this pool.
+ CfgOptionPtr getCfgOption() {
+ return (cfg_option_);
+ }
+
+ /// @brief Returns const pointer to the option data configuration for
+ /// this pool.
+ ConstCfgOptionPtr getCfgOption() const {
+ return (cfg_option_);
+ }
+
+ /// @brief Checks whether this pool supports client that belongs to
+ /// specified classes.
+ ///
+ /// @todo: currently doing the same as network which needs improving.
+ ///
+ /// @param client_classes list of all classes the client belongs to
+ /// @return true if client can be supported, false otherwise
+ bool clientSupported(const ClientClasses& client_classes) const;
+
+ /// @brief Sets the supported class to class class_name
+ ///
+ /// @param class_name client class to be supported by this pool
+ void allowClientClass(const ClientClass& class_name);
+
+ /// @brief returns the client class
+ ///
+ /// @note The returned reference is only valid as long as the object
+ /// returned is valid.
+ ///
+ /// @return client class @ref client_class_
+ const ClientClass& getClientClass() const {
+ return (client_class_);
+ }
+
+ /// @brief Adds class class_name to classes required to be evaluated
+ ///
+ /// @param class_name client class required to be evaluated
+ void requireClientClass(const ClientClass& class_name) {
+ if (!required_classes_.contains(class_name)) {
+ required_classes_.insert(class_name);
+ }
+ }
+
+ /// @brief Returns classes which are required to be evaluated
+ const ClientClasses& getRequiredClasses() const {
+ return (required_classes_);
+ }
+
+ /// @brief Returns pool-specific allocation state.
+ ///
+ /// The actual type of the state depends on the allocator type.
+ ///
+ /// @return allocation state.
+ AllocationStatePtr getAllocationState() const {
+ return (allocation_state_);
+ }
+
+ /// @brief Sets pool-specific allocation state.
+ ///
+ /// @param allocation_state allocation state instance.
+ void setAllocationState(const AllocationStatePtr& allocation_state) {
+ allocation_state_ = allocation_state;
+ }
+
+ /// @brief Unparse a pool object.
+ ///
+ /// @return A pointer to unparsed pool configuration.
+ virtual data::ElementPtr toElement() const;
+
+protected:
+
+ /// @brief protected constructor
+ ///
+ /// This constructor is protected to prevent anyone from instantiating
+ /// Pool class directly. Instances of Pool4 and Pool6 should be created
+ /// instead.
+ ///
+ /// @param type type of lease that will be served from this pool
+ /// @param first first address of a range
+ /// @param last last address of a range
+ Pool(Lease::Type type,
+ const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last);
+
+ /// @brief pool-id
+ ///
+ /// This id is an unique value that can be used to identify a pool within a
+ /// subnet or shared network.
+ uint64_t id_;
+
+ /// @brief The first address in a pool
+ isc::asiolink::IOAddress first_;
+
+ /// @brief The last address in a pool
+ isc::asiolink::IOAddress last_;
+
+ /// @brief defines a lease type that will be served from this pool
+ Lease::Type type_;
+
+ /// @brief Stores number of possible leases.
+ ///
+ /// This could be calculated on the fly, but the calculations are somewhat
+ /// involved, so it is more efficient to calculate it once and just store
+ /// the result. Note that for very large pools, the number is capped at
+ /// max value of uint64_t.
+ isc::util::uint128_t capacity_;
+
+ /// @brief Pointer to the option data configuration for this pool.
+ CfgOptionPtr cfg_option_;
+
+ /// @brief Optional definition of a client class
+ ///
+ /// @ref Network::client_class_
+ ClientClass client_class_;
+
+ /// @brief Required classes
+ ///
+ /// @ref isc::dhcp::Network::required_classes_
+ ClientClasses required_classes_;
+
+ /// @brief Pointer to the user context (may be NULL)
+ data::ConstElementPtr user_context_;
+
+ /// @brief Holds pool-specific allocation state.
+ AllocationStatePtr allocation_state_;
+};
+
+class Pool4;
+
+/// @brief a pointer an IPv4 Pool
+typedef boost::shared_ptr<Pool4> Pool4Ptr;
+
+/// @brief Pool information for IPv4 addresses
+///
+/// It holds information about pool4, i.e. a range of IPv4 address space that
+/// is configured for DHCP allocation.
+class Pool4 : public Pool {
+public:
+ /// @brief the constructor for Pool4 "min-max" style definition
+ ///
+ /// @param first the first address in a pool
+ /// @param last the last address in a pool
+ Pool4(const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last);
+
+ /// @brief the constructor for Pool4 "prefix/len" style definition
+ ///
+ /// @param prefix specifies prefix of the pool
+ /// @param prefix_len specifies length of the prefix of the pool
+ Pool4(const isc::asiolink::IOAddress& prefix,
+ uint8_t prefix_len);
+
+ /// @brief Factory function for creating an instance of the @c Pool4.
+ ///
+ /// This function should be used to create an instance of the pool
+ /// within a hooks library in cases when the library may be unloaded
+ /// before the object is destroyed. This ensures that the ownership
+ /// of the object by the Kea process is retained.
+ ///
+ /// @param first the first address in a pool
+ /// @param last the last address in a pool
+ ///
+ /// @return Pointer to the @c Pool4 instance.
+ static Pool4Ptr create(const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last);
+
+ /// @brief Factory function for creating an instance of the @c Pool4.
+ ///
+ /// This function should be used to create an instance of the pool
+ /// within a hooks library in cases when the library may be unloaded
+ /// before the object is destroyed. This ensures that the ownership
+ /// of the object by the Kea process is retained.
+ ///
+ /// @param prefix specifies prefix of the pool.
+ /// @param prefix_len specifies length of the prefix of the pool.
+ ///
+ /// @return Pointer to the @c Pool4 instance.
+ static Pool4Ptr create(const isc::asiolink::IOAddress& prefix,
+ uint8_t prefix_len);
+
+ /// @brief Unparse a Pool4 object.
+ ///
+ /// @return A pointer to unparsed Pool4 configuration.
+ virtual data::ElementPtr toElement() const;
+};
+
+class Pool6;
+
+/// @brief a pointer an IPv6 Pool
+typedef boost::shared_ptr<Pool6> Pool6Ptr;
+
+/// @brief Pool information for IPv6 addresses and prefixes
+///
+/// It holds information about pool6, i.e. a range of IPv6 address space that
+/// is configured for DHCP allocation.
+class Pool6 : public Pool {
+public:
+
+ /// @brief the constructor for Pool6 "min-max" style definition
+ ///
+ /// @throw BadValue if PD is define (PD can be only prefix/len)
+ ///
+ /// @param type type of the pool (IA or TA)
+ /// @param first the first address in a pool
+ /// @param last the last address in a pool
+ Pool6(Lease::Type type, const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last);
+
+ /// @brief the constructor for Pool6 "prefix/len" style definition
+ ///
+ /// For addressed, this is just a prefix/len definition. For prefixes,
+ /// there is one extra additional parameter delegated_len. It specifies
+ /// a size of delegated prefixes that the pool will be split into. For
+ /// example pool 2001:db8::/56, delegated_len=64 means that there is a
+ /// pool 2001:db8::/56. It will be split into 256 prefixes of length /64,
+ /// e.g. 2001:db8:0:1::/64, 2001:db8:0:2::/64 etc.
+ ///
+ /// Naming convention:
+ /// A smaller prefix length yields a shorter prefix which describes a larger
+ /// set of addresses. A larger length yields a longer prefix which describes
+ /// a smaller set of addresses.
+ ///
+ /// Obviously, prefix_len must define shorter or equal prefix length than
+ /// delegated_len, so prefix_len <= delegated_len. Note that it is slightly
+ /// confusing: bigger (larger) prefix actually has smaller prefix length,
+ /// e.g. /56 is a bigger prefix than /64, but has shorter (smaller) prefix
+ /// length.
+ ///
+ /// @throw BadValue if delegated_len is defined for non-PD types or
+ /// when delegated_len < prefix_len
+ ///
+ /// @param type type of the pool (IA, TA or PD)
+ /// @param prefix specifies prefix of the pool
+ /// @param prefix_len specifies prefix length of the pool
+ /// @param delegated_len specifies length of the delegated prefixes
+ Pool6(Lease::Type type, const isc::asiolink::IOAddress& prefix,
+ uint8_t prefix_len, uint8_t delegated_len = 128);
+
+ /// @brief Constructor for DHCPv6 prefix pool with an excluded prefix.
+ ///
+ /// If @c excluded_prefix is equal to '::' and the @c excluded_prefix_len
+ /// is equal to 0, the excluded prefix is assumed to be unspecified for
+ /// the pool. In this case, the server will not send the Prefix Exclude
+ /// option to a client.
+ ///
+ /// @param prefix specified a prefix of the pool.
+ /// @param prefix_len specifies prefix length of the pool.
+ /// @param delegated_len specifies length of the delegated prefixes.
+ /// @param excluded_prefix specifies an excluded prefix as per RFC6603.
+ /// @param excluded_prefix_len specifies length of an excluded prefix.
+ Pool6(const asiolink::IOAddress& prefix, const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len);
+
+ /// @brief Factory function for creating an instance of the @c Pool6.
+ ///
+ /// This function should be used to create an instance of the pool
+ /// within a hooks library in cases when the library may be unloaded
+ /// before the object is destroyed. This ensures that the ownership
+ /// of the object by the Kea process is retained.
+ ///
+ /// @param type type of the pool (IA or TA)
+ /// @param first the first address in a pool
+ /// @param last the last address in a pool
+ ///
+ /// @return Pointer to the @c Pool6 instance.
+ static Pool6Ptr create(Lease::Type type,
+ const isc::asiolink::IOAddress& first,
+ const isc::asiolink::IOAddress& last);
+
+ /// @brief Factory function for creating an instance of the @c Pool6.
+ ///
+ /// This function should be used to create an instance of the pool
+ /// within a hooks library in cases when the library may be unloaded
+ /// before the object is destroyed. This ensures that the ownership
+ /// of the object by the Kea process is retained.
+ ///
+ /// @param type type of the pool (IA, TA or PD)
+ /// @param prefix specifies prefix of the pool
+ /// @param prefix_len specifies prefix length of the pool
+ /// @param delegated_len specifies length of the delegated prefixes
+ ///
+ /// @return Pointer to the @c Pool6 instance.
+ static Pool6Ptr create(Lease::Type type,
+ const isc::asiolink::IOAddress& prefix,
+ uint8_t prefix_len,
+ uint8_t delegated_len = 128);
+
+ /// @brief Factory function for creating an instance of the @c Pool6.
+ ///
+ /// If @c excluded_prefix is equal to '::' and the @c excluded_prefix_len
+ /// is equal to 0, the excluded prefix is assumed to be unspecified for
+ /// the pool. In this case, the server will not send the Prefix Exclude
+ /// option to a client.
+ ///
+ /// @param prefix specifies a prefix of the pool.
+ /// @param prefix_len specifies prefix length of the pool.
+ /// @param delegated_len specifies length of the delegated prefixes.
+ /// @param excluded_prefix specifies an excluded prefix as per RFC6603.
+ /// @param excluded_prefix_len specifies length of an excluded prefix.
+ ///
+ /// @return Pointer to the @c Pool6 instance.
+ static Pool6Ptr create(const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len);
+
+ /// @brief returns pool type
+ ///
+ /// @return pool type
+ Lease::Type getType() const {
+ return (type_);
+ }
+
+ /// @brief returns delegated prefix length
+ ///
+ /// This may be useful for "prefix/len" style definition for
+ /// addresses, but is mostly useful for prefix pools.
+ /// @return prefix length (1-128)
+ uint8_t getLength() const {
+ return (prefix_len_);
+ }
+
+ /// @brief Returns instance of the pool specific Prefix Exclude option.
+ ///
+ /// @return An instance of the Prefix Exclude option (RFC 6603) or NULL
+ /// if such option hasn't been specified for the pool.
+ Option6PDExcludePtr getPrefixExcludeOption() const {
+ return (pd_exclude_option_);
+ }
+
+ /// @brief Unparse a Pool6 object.
+ ///
+ /// @return A pointer to unparsed Pool6 configuration.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief returns textual representation of the pool
+ ///
+ /// @return textual representation
+ virtual std::string toText() const;
+
+private:
+
+ /// @brief Generic method initializing a DHCPv6 pool.
+ ///
+ /// This method should be called by the constructors to initialize
+ /// DHCPv6 pools.
+ ///
+ /// @param Lease/pool type.
+ /// @param prefix An address or delegated prefix (depending on the
+ /// pool type specified as @c type).
+ /// @param prefix_len Prefix length. If a pool is an address pool,
+ /// this value should be set to 128.
+ /// @param delegated_len Length of the delegated prefixes. If a pool
+ /// is an address pool, this value should be set to 128.
+ /// @param excluded_prefix An excluded prefix as per RFC6603. This
+ /// value should only be specified for prefix pools. The value of
+ /// '::' means "unspecified".
+ /// @param excluded_prefix_len Length of the excluded prefix. This
+ /// is only specified for prefix pools. The value of 0 should be
+ /// used when @c excluded_prefix is not specified.
+ void init(const Lease::Type& type,
+ const asiolink::IOAddress& prefix,
+ const uint8_t prefix_len,
+ const uint8_t delegated_len,
+ const asiolink::IOAddress& excluded_prefix,
+ const uint8_t excluded_prefix_len);
+
+ /// @brief Defines prefix length (for TYPE_PD only)
+ uint8_t prefix_len_;
+
+ /// @brief A pointer to the Prefix Exclude option (RFC 6603).
+ Option6PDExcludePtr pd_exclude_option_;
+
+};
+
+/// @brief a pointer to either IPv4 or IPv6 Pool
+typedef boost::shared_ptr<Pool> PoolPtr;
+
+/// @brief a container for either IPv4 or IPv6 Pools
+typedef std::vector<PoolPtr> PoolCollection;
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // POOL_H
diff --git a/src/lib/dhcpsrv/random_allocation_state.cc b/src/lib/dhcpsrv/random_allocation_state.cc
new file mode 100644
index 0000000..5d0fd0b
--- /dev/null
+++ b/src/lib/dhcpsrv/random_allocation_state.cc
@@ -0,0 +1,40 @@
+// Copyright (C) 2022-2023 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 <dhcpsrv/random_allocation_state.h>
+#include <boost/make_shared.hpp>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+PoolRandomAllocationStatePtr
+PoolRandomAllocationState::create(const PoolPtr& pool) {
+ if (pool->getType() == Lease::TYPE_PD) {
+ // Pool classes ensure that the proper type is used for
+ // the IPv6 specific lease types, so we can just cast
+ // to the Pool6 pointer.
+ auto pd_pool = boost::dynamic_pointer_cast<Pool6>(pool);
+ return (boost::make_shared<PoolRandomAllocationState>(pd_pool->getFirstAddress(),
+ pd_pool->getLastAddress(),
+ pd_pool->getLength()));
+ }
+ return (boost::make_shared<PoolRandomAllocationState>(pool->getFirstAddress(), pool->getLastAddress()));
+}
+
+PoolRandomAllocationState::PoolRandomAllocationState(const IOAddress& first, const IOAddress& last)
+ : permutation_(new IPRangePermutation(AddressRange(first, last))) {
+}
+
+PoolRandomAllocationState::PoolRandomAllocationState(const IOAddress& first, const IOAddress& last,
+ const uint8_t delegated)
+ : permutation_(new IPRangePermutation(PrefixRange(first, last, delegated))) {
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/random_allocation_state.h b/src/lib/dhcpsrv/random_allocation_state.h
new file mode 100644
index 0000000..dfc060b
--- /dev/null
+++ b/src/lib/dhcpsrv/random_allocation_state.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2022-2023 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 RANDOM_ALLOCATION_STATE_H
+#define RANDOM_ALLOCATION_STATE_H
+
+#include <dhcpsrv/allocation_state.h>
+#include <dhcpsrv/ip_range_permutation.h>
+#include <dhcpsrv/pool.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of the @c PoolRandomAllocationState.
+class PoolRandomAllocationState;
+
+/// @brief Type of the pointer to the @c PoolRandomAllocationState.
+typedef boost::shared_ptr<PoolRandomAllocationState> PoolRandomAllocationStatePtr;
+
+/// @brief Pool allocation state used by the random allocator.
+///
+/// It extends the base class with the mechanism that maintains
+/// an address or delegated prefix pool permutation. The
+/// permutation serves random, non-repeating leases.
+class PoolRandomAllocationState : public AllocationState {
+public:
+
+ /// @brief Factory function creating the state instance from pool.
+ ///
+ /// @param pool instance of the pool for which the allocation state
+ /// should be instantiated.
+ /// @return new allocation state instance.
+ static PoolRandomAllocationStatePtr create(const PoolPtr& pool);
+
+ /// @brief Constructor from an IP address pool.
+ ///
+ /// @param first first address in the pool.
+ /// @param last last address in the pool.
+ PoolRandomAllocationState(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& last);
+
+ /// @brief Constructor from a delegated prefix pool.
+ ///
+ /// @param first first address in the pool.
+ /// @param last last prefix in the pool.
+ /// @param delegated delegated prefix length.
+ PoolRandomAllocationState(const asiolink::IOAddress& first,
+ const asiolink::IOAddress& last,
+ const uint8_t delegated);
+
+ /// @brief Returns a pointer to the permutation of addresses
+ /// or delegated prefixes.
+ ///
+ /// @return permutation instance.
+ IPRangePermutationPtr getPermutation() const {
+ return (permutation_);
+ }
+
+private:
+
+ /// @brief Permutation instance for the pool.
+ IPRangePermutationPtr permutation_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // RANDOM_ALLOCATION_STATE_H
diff --git a/src/lib/dhcpsrv/random_allocator.cc b/src/lib/dhcpsrv/random_allocator.cc
new file mode 100644
index 0000000..4d4643b
--- /dev/null
+++ b/src/lib/dhcpsrv/random_allocator.cc
@@ -0,0 +1,180 @@
+// Copyright (C) 2022-2023 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 <dhcpsrv/random_allocator.h>
+#include <dhcpsrv/subnet.h>
+#include <algorithm>
+#include <random>
+
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+RandomAllocator::RandomAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
+ : Allocator(type, subnet), generator_() {
+ random_device rd;
+ generator_.seed(rd());
+}
+
+IOAddress
+RandomAllocator::pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr&,
+ const IOAddress&) {
+ auto subnet = subnet_.lock();
+ const auto& pools = subnet->getPools(pool_type_);
+
+ // Let's first iterate over the pools and identify the ones that
+ // meet client class criteria. Then, segregate these pools into
+ // the ones that still have available addresses and exhausted
+ // ones.
+ std::vector<uint64_t> available;
+ std::vector<uint64_t> exhausted;
+ for (auto i = 0; i < pools.size(); ++i) {
+ // Check if the pool is allowed for the client's classes.
+ if (pools[i]->clientSupported(client_classes)) {
+ // Get or create the pool state.
+ auto state = getPoolState(pools[i]);
+ if (state->getPermutation()->exhausted()) {
+ // Pool is exhausted. It means that all addresses from
+ // this pool have been offered. It doesn't mean that
+ // leases are allocated for all these addresses. It
+ // only means that all have been picked from the pool.
+ exhausted.push_back(i);
+ } else {
+ // There are still available addresses in this pool. It
+ // means that not all of them have been offered.
+ available.push_back(i);
+ }
+ }
+ }
+ // Find a suitable pool.
+ PoolPtr pool;
+ if (!available.empty()) {
+ // There are pools with available addresses. Let's randomly
+ // pick one of these pools and get next available address.
+ pool = pools[available[getRandomNumber(available.size() - 1)]];
+
+ } else if (!exhausted.empty()) {
+ // All pools have been exhausted. We will start offering the same
+ // addresses from these pools. We need to reset the permutations
+ // of the exhausted pools.
+ for (auto e : exhausted) {
+ getPoolState(pools[e])->getPermutation()->reset();
+ }
+ // Get random pool from those we just reset.
+ pool = pools[exhausted[getRandomNumber(exhausted.size() - 1)]];
+ }
+
+ // If pool has been found, let's get next address.
+ if (pool) {
+ auto done = false;
+ return (getPoolState(pool)->getPermutation()->next(done));
+ }
+
+ // No pool available. There are no pools or client classes do
+ // not match.
+ return (pool_type_ == Lease::TYPE_V4 ? IOAddress::IPV4_ZERO_ADDRESS() : IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+IOAddress
+RandomAllocator::pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool6,
+ const IdentifierBaseTypePtr&,
+ PrefixLenMatchType prefix_length_match,
+ const IOAddress&,
+ uint8_t hint_prefix_length) {
+ auto subnet = subnet_.lock();
+ const auto& pools = subnet->getPools(pool_type_);
+
+ // Let's first iterate over the pools and identify the ones that
+ // meet client class criteria. Then, segragate these pools into
+ // the ones that still have available addresses and exhausted
+ // ones.
+ std::vector<uint64_t> available;
+ std::vector<uint64_t> exhausted;
+ for (auto i = 0; i < pools.size(); ++i) {
+ // Check if the pool is allowed for the client's classes.
+ if (pools[i]->clientSupported(client_classes)) {
+ if (!Allocator::isValidPrefixPool(prefix_length_match, pools[i],
+ hint_prefix_length)) {
+ continue;
+ }
+ // Get or create the pool state.
+ auto state = getPoolState(pools[i]);
+ if (state->getPermutation()->exhausted()) {
+ // Pool is exhausted. It means that all prefixes from
+ // this pool have been offered. It doesn't mean that
+ // leases are allocated for all these prefixes. It
+ // only means that all have been picked from the pool.
+ exhausted.push_back(i);
+ } else {
+ // There are still available prefixes in this pool. It
+ // means that not all of them have been offered.
+ available.push_back(i);
+ }
+ }
+ }
+ // Find a suitable pool.
+ PoolPtr pool;
+ if (!available.empty()) {
+ // There are pools with available prefixes. Let's randomly
+ // pick one of these pools and get next available prefix.
+ pool = pools[available[getRandomNumber(available.size() - 1)]];
+
+ } else if (!exhausted.empty()) {
+ // All pools have been exhausted. We will start offering the same
+ // prefixes from these pools. We need to reset the permutations
+ // of the exhausted pools.
+ for (auto e : exhausted) {
+ getPoolState(pools[e])->getPermutation()->reset();
+ }
+ // Get random pool from those we just reset.
+ pool = pools[exhausted[getRandomNumber(exhausted.size() - 1)]];
+ }
+
+ // If pool has been found, let's get next prefix.
+ if (pool) {
+ auto done = false;
+ pool6 = boost::dynamic_pointer_cast<Pool6>(pool);
+
+ if (!pool6) {
+ // Something is gravely wrong here
+ isc_throw(Unexpected, "Wrong type of pool: "
+ << (pool)->toText()
+ << " is not Pool6");
+ }
+ return (getPoolState(pool)->getPermutation()->next(done));
+ }
+
+ // No pool available. There are no pools or client classes do
+ // not match.
+ return (IOAddress::IPV6_ZERO_ADDRESS());
+}
+
+PoolRandomAllocationStatePtr
+RandomAllocator::getPoolState(const PoolPtr& pool) const {
+ if (!pool->getAllocationState()) {
+ pool->setAllocationState(PoolRandomAllocationState::create(pool));
+ }
+ return (boost::dynamic_pointer_cast<PoolRandomAllocationState>(pool->getAllocationState()));
+}
+
+uint64_t
+RandomAllocator::getRandomNumber(uint64_t limit) {
+ // Take the short path if there is only one number to randomize from.
+ if (limit == 0) {
+ return 0;
+ }
+ std::uniform_int_distribution<uint64_t> dist(0, limit);
+ return (dist(generator_));
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/random_allocator.h b/src/lib/dhcpsrv/random_allocator.h
new file mode 100644
index 0000000..759005e
--- /dev/null
+++ b/src/lib/dhcpsrv/random_allocator.h
@@ -0,0 +1,108 @@
+// Copyright (C) 2022-2023 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 RANDOM_ALLOCATOR_H
+#define RANDOM_ALLOCATOR_H
+
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/random_allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <cstdint>
+#include <random>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief An allocator offering addresses in a random order.
+///
+/// This allocator uses @c IPRangePermutation to select random
+/// addresses or delegated prefixes from the pools. It guarantees
+/// that all offered addresses are unique (do not repeat).
+///
+/// The allocator also randomly picks pools to ensure that the
+/// leases are offered uniformly from the entire subnet rather than
+/// from the same pool until it exhausts. When all pools exhaust,
+/// the allocator resets their permutations and begins offering
+/// leases from these pools again.
+class RandomAllocator : public Allocator {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param type specifies the type of allocated leases.
+ /// @param subnet weak pointer to the subnet owning the allocator.
+ RandomAllocator(Lease::Type type, const WeakSubnetPtr& subnet);
+
+ /// @brief Returns the allocator type string.
+ ///
+ /// @return random string.
+ virtual std::string getType() const {
+ return ("random");
+ }
+
+private:
+
+ /// @brief Returns a random address from the pools in the subnet.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickAddress.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param duid client DUID (ignored).
+ /// @param hint client hint (ignored).
+ ///
+ /// @return next offered address.
+ virtual asiolink::IOAddress pickAddressInternal(const ClientClasses& client_classes,
+ const IdentifierBaseTypePtr& duid,
+ const asiolink::IOAddress& hint);
+
+ /// @brief Picks a delegated prefix.
+ ///
+ /// Internal thread-unsafe implementation of the @c pickPrefix.
+ ///
+ /// @param client_classes list of classes client belongs to.
+ /// @param pool the selected pool satisfying all required conditions.
+ /// @param duid Client's DUID.
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length
+ /// @param hint Client's hint.
+ /// @param hint_prefix_length the hint prefix length that the client
+ /// provided. The 0 value means that there is no hint and that any
+ /// pool will suffice.
+ ///
+ /// @return the next prefix.
+ virtual asiolink::IOAddress pickPrefixInternal(const ClientClasses& client_classes,
+ Pool6Ptr& pool,
+ const IdentifierBaseTypePtr& duid,
+ PrefixLenMatchType prefix_length_match,
+ const asiolink::IOAddress& hint,
+ uint8_t hint_prefix_length);
+
+ /// @brief Convenience function returning pool allocation state instance.
+ ///
+ /// It creates a new pool state instance and assigns it to the pool
+ /// if it hasn't been initialized.
+ ///
+ /// @param pool pool instance.
+ /// @return allocation state instance for the pool.
+ PoolRandomAllocationStatePtr getPoolState(const PoolPtr& pool) const;
+
+ /// @brief Convenience function returning a random number.
+ ///
+ /// It is used internally by the @c pickAddressInternal function to
+ /// select a random pool.
+ ///
+ /// @param limit upper bound of the range.
+ /// @returns random number between 0 and limit.
+ uint64_t getRandomNumber(uint64_t limit);
+
+ /// @brief Random generator used by this class.
+ std::mt19937 generator_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // RANDOM_ALLOCATOR_H
diff --git a/src/lib/dhcpsrv/resource_handler.cc b/src/lib/dhcpsrv/resource_handler.cc
new file mode 100644
index 0000000..9ae2ee5
--- /dev/null
+++ b/src/lib/dhcpsrv/resource_handler.cc
@@ -0,0 +1,98 @@
+// Copyright (C) 2020 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 <dhcpsrv/resource_handler.h>
+#include <exceptions/exceptions.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+mutex ResourceHandler::mutex_;
+
+ResourceHandler::ResourceContainer ResourceHandler::resources_;
+
+ResourceHandler::ResourceHandler() : owned_() {
+}
+
+ResourceHandler::~ResourceHandler() {
+ lock_guard<mutex> lock_(mutex_);
+ for (auto res : owned_) {
+ unLockInternal(res->type_, res->addr_);
+ }
+ owned_.clear();
+}
+
+ResourceHandler::ResourcePtr
+ResourceHandler::lookup(Lease::Type type, const asiolink::IOAddress& addr) {
+ auto key = boost::make_tuple(type, addr.toBytes());
+ auto it = resources_.find(key);
+ if (it == resources_.end()) {
+ return (ResourcePtr());
+ }
+ return (*it);
+}
+
+void
+ResourceHandler::lock(Lease::Type type, const asiolink::IOAddress& addr) {
+ ResourcePtr res(new Resource(type, addr));
+ // Assume insert will never fail so not checking its result.
+ resources_.insert(res);
+ owned_.insert(res);
+}
+
+void
+ResourceHandler::unLockInternal(Lease::Type type,
+ const asiolink::IOAddress& addr) {
+ auto key = boost::make_tuple(type, addr.toBytes());
+ auto it = resources_.find(key);
+ if (it == resources_.end()) {
+ return;
+ }
+ resources_.erase(it);
+}
+
+bool
+ResourceHandler::tryLock(Lease::Type type, const asiolink::IOAddress& addr) {
+ ResourcePtr holder;
+ // Try to acquire the lock and return the holder when it failed.
+ lock_guard<mutex> lock_(mutex_);
+ holder = lookup(type, addr);
+ if (holder) {
+ return (false);
+ }
+ lock(type, addr);
+ return (true);
+}
+
+bool
+ResourceHandler::isLocked(Lease::Type type, const asiolink::IOAddress& addr) {
+ auto key = boost::make_tuple(type, addr.toBytes());
+ lock_guard<mutex> lock_(mutex_);
+ auto it = owned_.find(key);
+ return (it != owned_.end());
+}
+
+void
+ResourceHandler::unLock(Lease::Type type, const asiolink::IOAddress& addr) {
+ auto key = boost::make_tuple(type, addr.toBytes());
+ lock_guard<mutex> lock_(mutex_);
+ auto it = owned_.find(key);
+ if (it == owned_.end()) {
+ isc_throw(NotFound, "does not own " << Lease::typeToText(type)
+ << " " << addr.toText());
+ }
+ unLockInternal(type, addr);
+ owned_.erase(it);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/resource_handler.h b/src/lib/dhcpsrv/resource_handler.h
new file mode 100644
index 0000000..e03f3c1
--- /dev/null
+++ b/src/lib/dhcpsrv/resource_handler.h
@@ -0,0 +1,221 @@
+// Copyright (C) 2020-2021 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 RESOURCE_HANDLER_H
+#define RESOURCE_HANDLER_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/lease.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <mutex>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Thrown by lock users when a resource lock cannot be obtained.
+class ResourceBusy : public Exception {
+public:
+ ResourceBusy(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Resource race avoidance RAII handler.
+class ResourceHandler : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ResourceHandler();
+
+ /// @brief Destructor.
+ ///
+ /// Releases owned resources.
+ virtual ~ResourceHandler();
+
+ /// @brief Tries to acquires a resource.
+ ///
+ /// Lookup the resource, if not found insert the resource
+ /// in the resource container and return true, if found return false
+ ///
+ /// @param type Type of the resource, member of @c Lease::Type enum.
+ /// @param addr The address or prefix aka the resource.
+ /// @return true if the resource was acquired, false if the resource is
+ /// busy i.e. owned by a handler.
+ bool tryLock(Lease::Type type, const asiolink::IOAddress& addr);
+
+ /// @brief Checks if a resource is owned by this handler.
+ ///
+ /// @param type Type of the resource, member of @c Lease::Type enum.
+ /// @param addr The address or prefix aka the resource.
+ /// @return true if this handler owns the resource, false otherwise.
+ bool isLocked(Lease::Type type, const asiolink::IOAddress& addr);
+
+ /// @brief Releases a resource.
+ ///
+ /// Remove the resource from the resource container.
+ ///
+ /// @param type Type of the resource, member of @c Lease::Type enum.
+ /// @param addr The address or prefix aka the resource.
+ /// @throw when we do not own the resource.
+ void unLock(Lease::Type type, const asiolink::IOAddress& addr);
+
+private:
+
+ /// Type definitions.
+ //@{
+
+ /// @brief Structure representing a resource.
+ struct Resource {
+
+ /// @brief Constructor.
+ ///
+ /// @param addr The address or prefix aka the resource..
+ Resource(Lease::Type type, const asiolink::IOAddress& addr)
+ : type_(type), addr_(addr) {
+ }
+
+ /// @brief The type.
+ Lease::Type type_;
+
+ /// @brief The resource.
+ asiolink::IOAddress addr_;
+
+ /// @brief The key extractor.
+ std::vector<uint8_t> toBytes() const {
+ return (addr_.toBytes());
+ }
+ };
+
+ /// @brief The type of shared pointers to resources.
+ typedef boost::shared_ptr<Resource> ResourcePtr;
+
+ /// @brief The type of the resource container.
+ typedef boost::multi_index_container<
+
+ // This container stores pointers to resource objects.
+ ResourcePtr,
+
+ // Start specification of indexes here.
+ boost::multi_index::indexed_by<
+
+ // First index is used to search by type and address.
+ boost::multi_index::hashed_unique<
+ boost::multi_index::composite_key<
+ Resource,
+ // Lease type.
+ boost::multi_index::member<
+ Resource, Lease::Type, &Resource::type_
+ >,
+ // Address bytes.
+ boost::multi_index::const_mem_fun<
+ Resource, std::vector<uint8_t>, &Resource::toBytes
+ >
+ >
+ >
+ >
+ > ResourceContainer;
+
+ //@}
+
+ /// Class members.
+ //@{
+
+ /// @brief The resource container.
+ static ResourceContainer resources_;
+
+ /// @brief Mutex to protect the resource container.
+ static std::mutex mutex_;
+
+ /// @brief Lookup a resource.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param type Type of the resource, member of @c Lease::Type enum.
+ /// @param addr The address or prefix aka the resource.
+ /// @return The busy resource or null.
+ static ResourcePtr
+ lookup(Lease::Type type, const asiolink::IOAddress& addr);
+
+ //@}
+
+ /// Instance members.
+ //@{
+
+ /// @brief Acquire a resource.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// @param type Type of the resource, member of @c Lease::Type enum.
+ /// @param addr The address or prefix aka the resource.
+ void lock(Lease::Type type, const asiolink::IOAddress& addr);
+
+ /// @brief Release a resource.
+ ///
+ /// The mutex must be held by the caller.
+ ///
+ /// Remove the resource from the resource container.
+ ///
+ /// @param type Type of the resource, member of @c Lease::Type enum.
+ /// @param addr The address or prefix aka the resource.
+ void unLockInternal(Lease::Type type, const asiolink::IOAddress& addr);
+
+ /// @brief List of resources this handler owns.
+ ResourceContainer owned_;
+
+ //@}
+};
+
+/// @brief Resource race avoidance RAII handler for DHCPv4.
+class ResourceHandler4 : public ResourceHandler {
+public:
+
+ /// @brief Destructor.
+ ///
+ /// Releases owned resources.
+ virtual ~ResourceHandler4() { }
+
+ /// @brief Tries to acquires a resource.
+ ///
+ /// Lookup the resource, if not found insert the resource
+ /// in the resource container and return true, if found return false
+ ///
+ /// @param addr The address aka the resource.
+ /// @return true if the resource was acquired, false if the resource is
+ /// busy i.e. owned by a handler.
+ bool tryLock4(const asiolink::IOAddress& addr) {
+ return (tryLock(Lease::TYPE_V4, addr));
+ }
+
+ /// @brief Checks if a resource is owned by this handler.
+ ///
+ /// @param addr The address aka the resource.
+ /// @return true if this handler owns the resource, false otherwise.
+ bool isLocked4(const asiolink::IOAddress& addr) {
+ return (isLocked(Lease::TYPE_V4, addr));
+ }
+
+ /// @brief Releases a resource.
+ ///
+ /// Remove the resource from the resource container.
+ ///
+ /// @param addr The address aka the resource.
+ /// @throw when we do not own the resource.
+ void unLock4(const asiolink::IOAddress& addr) {
+ unLock(Lease::TYPE_V4, addr);
+ }
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif // RESOURCE_HANDLER_H
diff --git a/src/lib/dhcpsrv/sanity_checker.cc b/src/lib/dhcpsrv/sanity_checker.cc
new file mode 100644
index 0000000..e2df498
--- /dev/null
+++ b/src/lib/dhcpsrv/sanity_checker.cc
@@ -0,0 +1,187 @@
+// Copyright (C) 2018-2022 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 <dhcpsrv/sanity_checker.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+bool SanityChecker::leaseCheckingEnabled(bool current) {
+ SrvConfigPtr cfg;
+ if (current) {
+ cfg = CfgMgr::instance().getCurrentCfg();
+ } else {
+ cfg = CfgMgr::instance().getStagingCfg();
+ }
+
+ if (cfg) {
+ CfgConsistencyPtr sanity = cfg->getConsistency();
+ return (sanity && (sanity->getLeaseSanityCheck() != CfgConsistency::LEASE_CHECK_NONE));
+ }
+
+ return (false);
+}
+
+void SanityChecker::checkLease(Lease4Ptr& lease, bool current) {
+ SrvConfigPtr cfg;
+ if (current) {
+ cfg = CfgMgr::instance().getCurrentCfg();
+ } else {
+ cfg = CfgMgr::instance().getStagingCfg();
+ }
+
+ CfgConsistencyPtr sanity = cfg->getConsistency();
+ if (sanity->getLeaseSanityCheck() == CfgConsistency::LEASE_CHECK_NONE) {
+ // No sense going farther.
+ return;
+ }
+
+ CfgSubnets4Ptr subnets = cfg->getCfgSubnets4();
+ checkLeaseInternal(lease, sanity, subnets);
+}
+
+void SanityChecker::checkLease(Lease6Ptr& lease, bool current) {
+ // We only check IA_NAs currently.
+ if (lease->type_ != Lease::TYPE_NA) {
+ return;
+ }
+
+ SrvConfigPtr cfg;
+ if (current) {
+ cfg = CfgMgr::instance().getCurrentCfg();
+ } else {
+ cfg = CfgMgr::instance().getStagingCfg();
+ }
+ CfgConsistencyPtr sanity = cfg->getConsistency();
+ if (sanity->getLeaseSanityCheck() == CfgConsistency::LEASE_CHECK_NONE) {
+ // No sense going farther.
+ return;
+ }
+
+ CfgSubnets6Ptr subnets = cfg->getCfgSubnets6();
+ checkLeaseInternal(lease, sanity, subnets);
+}
+
+template<typename LeasePtrType, typename SubnetsType>
+void SanityChecker::checkLeaseInternal(LeasePtrType& lease, const CfgConsistencyPtr& checks,
+ const SubnetsType& subnets) {
+
+ auto subnet = subnets->getBySubnetId(lease->subnet_id_);
+ if (subnet && subnet->inRange(lease->addr_)) {
+
+ // If the subnet is defined and the address is in range, we're good.
+
+ return;
+ }
+
+ // Ok, if we got here, that means that either we did not find a subnet
+ // of found it, but it wasn't the right subnet.
+ SubnetID id = findSubnetId(lease, subnets);
+
+ // Prepare a message in the case the check fails.
+ std::ostringstream msg;
+ if (id != 0) {
+ msg << "the lease should have subnet-id " << id;
+ } else {
+ msg << "the lease IP address did not belong to a configured subnet";
+ }
+
+ switch (checks->getLeaseSanityCheck()) {
+ case CfgConsistency::LEASE_CHECK_WARN:
+ if (lease->subnet_id_ != id) {
+ // Print a warning, but return the lease as is.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_LEASE_SANITY_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(lease->subnet_id_)
+ .arg(msg.str());
+ }
+ break;
+
+ case CfgConsistency::LEASE_CHECK_FIX:
+ if (lease->subnet_id_ != id) {
+
+ // If there is a better subnet, use it.
+ if (id != 0) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_LEASE_SANITY_FIXED)
+ .arg(lease->addr_.toText())
+ .arg(lease->subnet_id_)
+ .arg(id);
+ lease->subnet_id_ = id;
+ } else {
+ // If not, return the lease as is.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_LEASE_SANITY_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(lease->subnet_id_)
+ .arg(msg.str());
+ }
+ }
+ break;
+
+ case CfgConsistency::LEASE_CHECK_FIX_DEL:
+ if (lease->subnet_id_ != id) {
+
+ // If there is a better subnet, use it.
+ if (id != 0) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_LEASE_SANITY_FIXED)
+ .arg(lease->addr_.toText())
+ .arg(lease->subnet_id_)
+ .arg(id);
+ lease->subnet_id_ = id;
+ break;
+ } else {
+ // If not, delete the lease.
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_LEASE_SANITY_FAIL_DISCARD)
+ .arg(lease->addr_.toText())
+ .arg(lease->subnet_id_)
+ .arg(msg.str());
+ lease.reset();
+ }
+
+ }
+ break;
+
+ case CfgConsistency::LEASE_CHECK_DEL:
+ if (lease->subnet_id_ != id) {
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_LEASE_SANITY_FAIL_DISCARD)
+ .arg(lease->addr_.toText())
+ .arg(lease->subnet_id_)
+ .arg(msg.str());
+ lease.reset();
+ }
+ break;
+
+ default:
+ // Shouldn't get here but some compilers and analyzers
+ // complain. We'll we treat it as NONE and return the
+ // lease as-is.
+ break;
+
+ }
+
+ // Additional checks may be implemented in the future here.
+
+ /// @todo: add a check if the address is within specified dynamic pool
+ /// if not, check if the address is reserved.
+}
+
+template<typename LeaseType, typename SubnetsType>
+SubnetID SanityChecker::findSubnetId(const LeaseType& lease, const SubnetsType& subnets) {
+ auto subnet = subnets->selectSubnet(lease->addr_);
+ if (!subnet) {
+ return (0);
+ }
+
+ return (subnet->getID());
+}
+
+}
+}
diff --git a/src/lib/dhcpsrv/sanity_checker.h b/src/lib/dhcpsrv/sanity_checker.h
new file mode 100644
index 0000000..7412add
--- /dev/null
+++ b/src/lib/dhcpsrv/sanity_checker.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2018-2022 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 SANITY_CHECKER_H
+#define SANITY_CHECKER_H
+
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/cfg_consistency.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Code used to conduct various sanity checks. Currently used for leases.
+///
+/// This class is expected to be used as a simple interface sanity checker for
+/// various run-time and configuration elements. Currently is provides sanity
+/// checking and correction for subnet-id parameter in leases.
+///
+/// @note: the extended info checker for leases is in the lease manager.
+class SanityChecker {
+public:
+
+ /// @brief Sanity checks and possibly corrects an IPv4 lease
+ ///
+ /// Depending on the sanity-checks/lease-checks parameter value (see
+ /// @ref CfgConsistency for details), this code may print a warning,
+ /// correct subnet-id or discard the lease.
+ ///
+ /// @param lease Lease to be sanity-checked
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config
+ void checkLease(Lease4Ptr& lease, bool current = true);
+
+ /// @brief Sanity checks and possibly corrects an IPv6 lease
+ ///
+ /// Depending on the sanity-checks/lease-checks parameter value (see
+ /// @ref CfgConsistency for details), this code may print a warning,
+ /// correct subnet-id or discard the lease. Note that if the lease
+ /// type is TYPE_PD, it simply returns.
+ ///
+ /// @param lease Lease to be sanity-checked
+ /// @param current specify whether to use current (true) or staging
+ /// (false) config
+ void checkLease(Lease6Ptr& lease, bool current = true);
+
+ /// @brief Indicates the specified configuration enables lease sanity checking.
+ ///
+ /// @param current specifies whether to use current (true) or staging(false)
+ /// server configuration
+ /// @return true if the sanity-checks/lease-checks parameter value (see
+ /// @ref CfgConsistency for details) is not CfgConsistency::LEASE_CHECK_NONE.
+ static bool leaseCheckingEnabled(bool current = true);
+
+ private:
+
+ /// @brief Internal implementation for checkLease command
+ ///
+ /// @tparam LeaseType type of the lease (Lease4Ptr or Lease6Ptr)
+ /// @tparam SubnetsType type of the subnets container (CfgSubnets4Ptr or
+ /// CfgSubnets6Ptr)
+ /// @param lease a lease to be checked/corrected
+ /// @param checks a pointer to CfgConsistency structure (type of checks
+ /// specified here)
+ /// @param subnets configuration structure with subnets
+ template<typename LeaseType, typename SubnetsType>
+ void checkLeaseInternal(LeaseType& lease, const CfgConsistencyPtr& checks,
+ const SubnetsType& subnets);
+
+ /// @brief Internal method for finding appropriate subnet-id
+ ///
+ /// @tparam LeaseType type of the lease (Lease4Ptr or Lease6Ptr)
+ /// @tparam SubnetsType type of the subnets container (CfgSubnets4Ptr or
+ /// CfgSubnets6Ptr)
+ /// @param lease a lease to be checked/corrected
+ /// @param subnets configuration structure with subnets
+ template<typename LeaseType, typename SubnetsType>
+ SubnetID findSubnetId(const LeaseType& lease, const SubnetsType& subnets);
+};
+
+}
+}
+
+#endif /* SANITY_CHECKER_H */
diff --git a/src/lib/dhcpsrv/shared_network.cc b/src/lib/dhcpsrv/shared_network.cc
new file mode 100644
index 0000000..2307dfe
--- /dev/null
+++ b/src/lib/dhcpsrv/shared_network.cc
@@ -0,0 +1,531 @@
+// Copyright (C) 2017-2023 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 <dhcpsrv/shared_network.h>
+#include <boost/make_shared.hpp>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Implements common functionality for SharedNetwork4 and
+/// SharedNetwork6 classes.
+///
+/// It provides mechanisms to add, remove and find subnets within shared
+/// networks. It also provides means to walk over the subnets within a
+/// shared network.
+class Impl {
+public:
+
+ /// @brief Adds a subnet to a shared network.
+ ///
+ /// This is a generic method for adding a new subnet to a shared network.
+ ///
+ /// @param [out] subnets Container holding subnets for this shared network.
+ /// @param subnet Pointer to a subnet being added to this shared network.
+ ///
+ /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
+ /// or @ref Subnet6Ptr.
+ /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+ /// @ref Subnet4SimpleCollection or @ref Subnet6SimpleCollection.
+ ///
+ /// @throw isc::BadValue if subnet is null.
+ /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id
+ /// already exists in this shared network.
+ /// @throw InvalidOperation if a subnet is already associated with some
+ /// shared network.
+ template<typename SubnetPtrType, typename SubnetCollectionType>
+ static void add(SubnetCollectionType& subnets, const SubnetPtrType& subnet) {
+ // Subnet must be non-null.
+ if (!subnet) {
+ isc_throw(BadValue, "null pointer specified when adding a subnet"
+ " to a shared network");
+ }
+
+ // Check if a subnet with this id already exists.
+ if (getSubnet<SubnetPtrType>(subnets, subnet->getID())) {
+ isc_throw(DuplicateSubnetID, "attempted to add subnet with a"
+ " duplicated subnet identifier " << subnet->getID());
+ } else if (getSubnet<SubnetPtrType>(subnets, subnet->toText())) {
+ isc_throw(DuplicateSubnetID, "attempted to add subnet with a"
+ " duplicated subnet prefix " << subnet->toText());
+ }
+
+ // Check if the subnet is already associated with some network.
+ NetworkPtr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ isc_throw(InvalidOperation, "subnet " << subnet->getID()
+ << " being added to a shared network"
+ " already belongs to a shared network");
+ }
+
+ // Add a subnet to the collection of subnets for this shared network.
+ static_cast<void>(subnets.insert(subnet));
+ }
+
+ /// @brief Replaces IPv4 subnet in a shared network.
+ ///
+ /// This generic method replaces a subnet by another subnet
+ /// with the same ID in a shared network.
+ /// The prefix should be the same too.
+ ///
+ /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
+ /// or @ref Subnet6Ptr.
+ /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+ /// @ref Subnet4SimpleCollection or @ref Subnet6SimpleCollection.
+ ///
+ /// @param [out] subnets Container holding subnets for this shared network.
+ /// @param subnet Pointer to a subnet replacing the subnet with the same ID
+ /// in this shared network.
+ ///
+ /// @throw isc::BadValue if subnet is null.
+ /// @throw InvalidOperation if a subnet is already associated with some
+ /// shared network.
+ ////
+ /// @return true if the operation succeeded, false otherwise.
+ template<typename SubnetPtrType, typename SubnetCollectionType>
+ static bool replace(SubnetCollectionType& subnets,
+ const SubnetPtrType& subnet) {
+
+ // Check if the new subnet is already associated with some network.
+ NetworkPtr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ isc_throw(InvalidOperation, "subnet " << subnet->getID()
+ << " being replaced in a shared network"
+ " already belongs to a shared network");
+ }
+
+ // Get the subnet with the same ID.
+ const SubnetID& subnet_id = subnet->getID();
+ auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ // Nothing to replace: return false to get the whole operation
+ // to be rollbacked.
+ return (false);
+ }
+
+ // Replace it.
+ return (index.replace(subnet_it, subnet));
+ }
+
+ /// @brief Removes a subnet from the shared network.
+ ///
+ /// @param [out] subnets Container holding subnets for this shared network.
+ /// @param subnet_id Identifier of a subnet to be removed.
+ ///
+ /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+ /// @ref Subnet4SimpleCollection or @ref Subnet6SimpleCollection.
+ ///
+ /// @return Erased subnet.
+ /// @throw BadValue if a subnet with specified identifier doesn't exist.
+ template<typename SubnetPtrType, typename SubnetCollectionType>
+ static SubnetPtrType del(SubnetCollectionType& subnets,
+ const SubnetID& subnet_id) {
+ auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ isc_throw(BadValue, "unable to delete subnet " << subnet_id
+ << " from shared network. Subnet doesn't belong"
+ " to this shared network");
+ }
+ auto subnet = *subnet_it;
+ index.erase(subnet_it);
+ return (subnet);
+ }
+
+ /// @brief Returns a subnet belonging to this network for a given subnet id.
+ ///
+ /// @param subnets Container holding subnets for this shared network.
+ /// @param subnet_id Identifier of a subnet being retrieved.
+ ///
+ /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
+ /// or @ref Subnet6Ptr.
+ /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+ /// @ref Subnet4SimpleCollection or @ref Subnet6SimpleCollection.
+ ///
+ /// @return Pointer to the subnet or null if the subnet doesn't exist.
+ template<typename SubnetPtrType, typename SubnetCollectionType>
+ static SubnetPtrType getSubnet(const SubnetCollectionType& subnets,
+ const SubnetID& subnet_id) {
+ const auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it != index.cend()) {
+ return (*subnet_it);
+ }
+
+ // Subnet not found.
+ return (SubnetPtrType());
+ }
+
+ /// @brief Returns a subnet belonging to this network for a given subnet
+ /// prefix.
+ ///
+ /// @param subnets Container holding subnets for this shared network.
+ /// @param subnet_prefix Prefix of a subnet being retrieved.
+ ///
+ /// @tparam SubnetPtrType Type of a pointer to a subnet, i.e. Subnet4Ptr
+ /// or @ref Subnet6Ptr.
+ /// @tparam SubnetCollectionType Type of a container holding subnets, i.e.
+ /// @ref Subnet4SimpleCollection or @ref Subnet6SimpleCollection.
+ ///
+ /// @return Pointer to the subnet or null if the subnet doesn't exist.
+ template<typename SubnetPtrType, typename SubnetCollectionType>
+ static SubnetPtrType getSubnet(const SubnetCollectionType& subnets,
+ const std::string& subnet_prefix) {
+ const auto& index = subnets.template get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it != index.cend()) {
+ return (*subnet_it);
+ }
+
+ // Subnet not found.
+ return (SubnetPtrType());
+ }
+
+ /// @brief Retrieves next available subnet within shared network.
+ ///
+ /// This method returns next available subnet within a shared network.
+ /// The subnets are ordered and retrieved using random access index
+ /// (first in/first out). The next subnet means next in turn after
+ /// the current subnet, which is specified as an argument. A caller
+ /// can iterate over all subnets starting from any of the subnets
+ /// belonging to a shared network. This subnet is called here as
+ /// a first subnet and is also specified as a method argument. When the
+ /// method detects that the next available subnet is a first subnet, it
+ /// returns a null pointer to indicate that there are no more subnets
+ /// available.
+ ///
+ /// The typical use case for this method is to allow DHCP server's
+ /// allocation engine to walk over the available subnets within a shared
+ /// network, starting from a subnet that has been selected during the
+ /// "subnet selection" processing step. In some cases the allocation
+ /// engine is unable to allocate resources from a selected subnet due
+ /// to client classification restrictions or address shortage within
+ /// its pools. It then uses this mechanism to move to another subnet
+ /// belonging to the same shared network.
+ ///
+ /// @param subnets Container holding subnets belonging to this shared
+ /// network.
+ /// @param first_subnet Pointer to a subnet from which the caller is
+ /// iterating over subnets within shared network. This is typically a
+ /// subnet selected during "subnet selection" step.
+ /// @param current_subnet Pointer to a subnet for which next subnet is
+ /// to be found.
+ ///
+ /// @tparam SubnetPtrType Type of the pointer to a subnet, i.e.
+ /// @ref Subnet4Ptr or @ref Subnet6Ptr.
+ /// @tparam SubnetCollectionType Type of the container holding subnets, i.e.
+ /// @ref Subnet4SimpleCollection or @ref Subnet6SimpleCollection.
+ ///
+ /// @return Pointer to next subnet or null pointer if no more subnets found.
+ ///
+ /// @throw isc::BadValue if invalid arguments specified, e.g. unable to
+ /// find first or current subnet within the container.
+ template<typename SubnetPtrType, typename SubnetCollectionType>
+ static SubnetPtrType getNextSubnet(const SubnetCollectionType& subnets,
+ const SubnetPtrType& first_subnet,
+ const SubnetID& current_subnet) {
+ // It is ok to have a shared network without any subnets, but in this
+ // case there is nothing else we can return but null pointer.
+ if (subnets.empty()) {
+ return (SubnetPtrType());
+ }
+
+ // Need to retrieve an iterator to the current subnet first. The
+ // subnet must exist in this container, thus we throw if the iterator
+ // is not found.
+ const auto& index = subnets.template get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(current_subnet);
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "no such subnet " << current_subnet
+ << " within shared network");
+ }
+
+ // Step to a next subnet.
+ if (++subnet_it == subnets.cend()) {
+ // If we reached the end of the container, start over from the
+ // beginning.
+ subnet_it = subnets.cbegin();
+ }
+
+ // Check if we have made a full circle. If we did, return a null pointer
+ // to indicate that there are no more subnets.
+ if ((*subnet_it)->getID() == first_subnet->getID()) {
+ return (SubnetPtrType());
+ }
+
+ // Got the next subnet, so return it.
+ return (*subnet_it);
+ }
+
+ /// @brief Attempts to find a subnet which is more likely to include available
+ /// leases than selected subnet.
+ ///
+ /// When allocating unreserved leases from a shared network it is important to
+ /// remember from which subnet within the shared network we have been recently
+ /// handing out leases. The allocation engine can use that information to start
+ /// trying allocation of the leases from that subnet rather than from the default
+ /// subnet selected for this client. Starting from the default subnet causes a
+ /// risk of having to walk over many subnets with exhausted address pools before
+ /// getting to the subnet with available leases. This method attempts to find
+ /// such subnet by inspecting "last allocation" timestamps. The one with most
+ /// recent timestamp is selected.
+ ///
+ /// The preferred subnet must also fulfil the condition of equal client class
+ /// with the @c selected_subnet.
+ ///
+ /// @tparam SubnetPtrType Type of the pointer to a subnet, i.e.
+ /// @ref Subnet4Ptr or @ref Subnet6Ptr.
+ /// @tparam SubnetCollectionType Type of the container holding subnets, i.e.
+ /// @ref Subnet4SimpleCollection or @ref Subnet6SimpleCollection.
+ /// @param subnets Container holding subnets belonging to this shared
+ /// network.
+ /// @param selected_subnet Pointer to a currently selected subnet.
+ /// @param lease_type Type of the lease for which preferred subnet should be
+ /// returned.
+ ///
+ /// @return Pointer to a preferred subnet. It may be the same as @c selected_subnet
+ /// if no better subnet was found.
+ template<typename SubnetPtrType, typename SubnetCollectionType>
+ static SubnetPtrType getPreferredSubnet(const SubnetCollectionType& subnets,
+ const SubnetPtrType& selected_subnet,
+ const Lease::Type& lease_type) {
+
+ auto preferred_subnet = selected_subnet;
+ for (auto s = subnets.begin(); s != subnets.end(); ++s) {
+ // It doesn't make sense to check the subnet against itself.
+ if (preferred_subnet == (*s)) {
+ continue;
+ }
+ if ((*s)->getClientClass().get() != selected_subnet->getClientClass().get()) {
+ continue;
+ }
+ auto current_subnet_state = (*s)->getAllocationState(lease_type);
+ if (!current_subnet_state) {
+ continue;
+ }
+ auto preferred_subnet_state = preferred_subnet->getAllocationState(lease_type);
+ if (!preferred_subnet_state) {
+ continue;
+ }
+ // The currently checked subnet has more recent time than the
+ // currently preferred subnet. Update the preferred subnet
+ // instance.
+ if (current_subnet_state->getLastAllocatedTime() >
+ preferred_subnet_state->getLastAllocatedTime()) {
+ preferred_subnet = (*s);
+ }
+ }
+ return (preferred_subnet);
+ }
+};
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+SharedNetwork4Ptr
+SharedNetwork4::create(const std::string& name) {
+ return (boost::make_shared<SharedNetwork4>(name));
+}
+
+void
+SharedNetwork4::add(const Subnet4Ptr& subnet) {
+ Impl::add(subnets_, subnet);
+ // Associate the subnet with this network.
+ subnet->setSharedNetwork(shared_from_this());
+ subnet->setSharedNetworkName(name_);
+}
+
+bool
+SharedNetwork4::replace(const Subnet4Ptr& subnet) {
+ // Subnet must be non-null.
+ if (!subnet) {
+ isc_throw(BadValue, "null pointer specified when adding a subnet"
+ " to a shared network");
+ }
+ const Subnet4Ptr& old = getSubnet(subnet->getID());
+ bool ret = Impl::replace(subnets_, subnet);
+ if (ret) {
+ // Associate the subnet with this network.
+ subnet->setSharedNetwork(shared_from_this());
+ subnet->setSharedNetworkName(name_);
+ // Deassociate the previous subnet.
+ old->setSharedNetwork(NetworkPtr());
+ old->setSharedNetworkName("");
+ }
+ return (ret);
+}
+
+void
+SharedNetwork4::del(const SubnetID& subnet_id) {
+ Subnet4Ptr subnet = Impl::del<Subnet4Ptr>(subnets_, subnet_id);
+ subnet->setSharedNetwork(NetworkPtr());
+ subnet->setSharedNetworkName("");
+}
+
+void
+SharedNetwork4::delAll() {
+ for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
+ (*subnet)->setSharedNetwork(NetworkPtr());
+ (*subnet)->setSharedNetworkName("");
+ }
+ subnets_.clear();
+}
+
+Subnet4Ptr
+SharedNetwork4::getSubnet(const SubnetID& subnet_id) const {
+ return (Impl::getSubnet<Subnet4Ptr>(subnets_, subnet_id));
+}
+
+Subnet4Ptr
+SharedNetwork4::getSubnet(const std::string& subnet_prefix) const {
+ return (Impl::getSubnet<Subnet4Ptr>(subnets_, subnet_prefix));
+}
+
+Subnet4Ptr
+SharedNetwork4::getNextSubnet(const Subnet4Ptr& first_subnet,
+ const SubnetID& current_subnet) const {
+ return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
+}
+
+Subnet4Ptr
+SharedNetwork4::getPreferredSubnet(const Subnet4Ptr& selected_subnet) const {
+ return (Impl::getPreferredSubnet<Subnet4Ptr>(subnets_, selected_subnet,
+ Lease::TYPE_V4));
+}
+
+bool
+SharedNetwork4::subnetsIncludeMatchClientId(const Subnet4Ptr& first_subnet,
+ const ClientClasses& client_classes) {
+ for (Subnet4Ptr subnet = first_subnet; subnet;
+ subnet = subnet->getNextSubnet(first_subnet, client_classes)) {
+ if (subnet->getMatchClientId()) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+ElementPtr
+SharedNetwork4::toElement() const {
+ ElementPtr map = Network4::toElement();
+
+ // Set shared network name.
+ if (!name_.empty()) {
+ map->set("name", Element::create(name_));
+ }
+
+ ElementPtr subnet4 = Element::createList();
+ for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
+ subnet4->add((*subnet)->toElement());
+ }
+
+ map->set("subnet4", subnet4);
+
+ return (map);
+}
+
+SharedNetwork6Ptr
+SharedNetwork6::create(const std::string& name) {
+ return (boost::make_shared<SharedNetwork6>(name));
+}
+
+void
+SharedNetwork6::add(const Subnet6Ptr& subnet) {
+ Impl::add(subnets_, subnet);
+ // Associate the subnet with this network.
+ subnet->setSharedNetwork(shared_from_this());
+ subnet->setSharedNetworkName(name_);
+}
+
+bool
+SharedNetwork6::replace(const Subnet6Ptr& subnet) {
+ // Subnet must be non-null.
+ if (!subnet) {
+ isc_throw(BadValue, "null pointer specified when adding a subnet"
+ " to a shared network");
+ }
+ const Subnet6Ptr& old = getSubnet(subnet->getID());
+ bool ret = Impl::replace(subnets_, subnet);
+ if (ret) {
+ // Associate the subnet with this network.
+ subnet->setSharedNetwork(shared_from_this());
+ subnet->setSharedNetworkName(name_);
+ // Deassociate the previous subnet.
+ old->setSharedNetwork(NetworkPtr());
+ old->setSharedNetworkName("");
+ }
+ return (ret);
+}
+
+void
+SharedNetwork6::del(const SubnetID& subnet_id) {
+ Subnet6Ptr subnet = Impl::del<Subnet6Ptr>(subnets_, subnet_id);
+ subnet->setSharedNetwork(NetworkPtr());
+ subnet->setSharedNetworkName("");
+}
+
+void
+SharedNetwork6::delAll() {
+ for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
+ (*subnet)->setSharedNetwork(NetworkPtr());
+ }
+ subnets_.clear();
+}
+
+Subnet6Ptr
+SharedNetwork6::getSubnet(const SubnetID& subnet_id) const {
+ return (Impl::getSubnet<Subnet6Ptr>(subnets_, subnet_id));
+}
+
+Subnet6Ptr
+SharedNetwork6::getSubnet(const std::string& subnet_prefix) const {
+ return (Impl::getSubnet<Subnet6Ptr>(subnets_, subnet_prefix));
+}
+
+Subnet6Ptr
+SharedNetwork6::getNextSubnet(const Subnet6Ptr& first_subnet,
+ const SubnetID& current_subnet) const {
+ return (Impl::getNextSubnet(subnets_, first_subnet, current_subnet));
+}
+
+Subnet6Ptr
+SharedNetwork6::getPreferredSubnet(const Subnet6Ptr& selected_subnet,
+ const Lease::Type& lease_type) const {
+ return (Impl::getPreferredSubnet(subnets_, selected_subnet, lease_type));
+}
+
+ElementPtr
+SharedNetwork6::toElement() const {
+ ElementPtr map = Network6::toElement();
+
+ // Set shared network name.
+ if (!name_.empty()) {
+ map->set("name", Element::create(name_));
+ }
+
+ ElementPtr subnet6 = Element::createList();
+ for (auto subnet = subnets_.cbegin(); subnet != subnets_.cend(); ++subnet) {
+ subnet6->add((*subnet)->toElement());
+ }
+
+ map->set("subnet6", subnet6);
+
+ return (map);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/shared_network.h b/src/lib/dhcpsrv/shared_network.h
new file mode 100644
index 0000000..da95baa
--- /dev/null
+++ b/src/lib/dhcpsrv/shared_network.h
@@ -0,0 +1,502 @@
+// Copyright (C) 2017-2023 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 SHARED_NETWORK_H
+#define SHARED_NETWORK_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A tag for accessing random access index.
+struct SharedNetworkRandomAccessIndexTag { };
+
+/// @brief A tag for accessing index by id.
+struct SharedNetworkIdIndexTag { };
+
+/// @brief A tag for accessing index by shared network name.
+struct SharedNetworkNameIndexTag { };
+
+/// @brief A tag for accessing index by server identifier.
+struct SharedNetworkServerIdIndexTag { };
+
+/// @brief Tag for the index for searching by shared network modification
+/// time.
+struct SharedNetworkModificationTimeIndexTag { };
+
+class SharedNetwork4;
+
+/// @brief Pointer to @ref SharedNetwork4 object.
+typedef boost::shared_ptr<SharedNetwork4> SharedNetwork4Ptr;
+
+/// @brief Shared network holding IPv4 subnets.
+///
+/// Specialization of the @ref Network4 class for IPv4 shared networks.
+class SharedNetwork4 : public virtual Network4,
+ public boost::enable_shared_from_this<SharedNetwork4> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets name of the shared network.
+ ///
+ /// @param name Name of the shared network.
+ explicit SharedNetwork4(const std::string& name)
+ : name_(name), subnets_() {
+ }
+
+ /// @brief Factory function creating an instance of the @c SharedNetwork4.
+ ///
+ /// This function should be used to create an instance of the shared
+ /// network within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param name Name of the shared network.
+ ///
+ /// @return Pointer to the @c SharedNetwork4 instance.
+ static SharedNetwork4Ptr create(const std::string& name);
+
+ /// @brief Returns a name of the shared network.
+ std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Sets new name for the shared network.
+ ///
+ /// @param name New name for the shared network.
+ void setName(const std::string& name) {
+ name_ = name;
+ }
+
+ /// @brief Adds IPv4 subnet to a shared network.
+ ///
+ /// @param subnet Pointer to a subnet being added to this shared network.
+ ///
+ /// @throw isc::BadValue if subnet is null.
+ /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id
+ /// already exists in this shared network.
+ /// @throw InvalidOperation if a subnet is already associated with some
+ /// shared network.
+ void add(const Subnet4Ptr& subnet);
+
+ /// @brief Replaces IPv4 subnet in a shared network.
+ ///
+ /// This method replaces a subnet by another subnet with the same ID.
+ /// The prefix should be the same too.
+ ///
+ /// @param subnet Pointer to a subnet replacing the subnet with the same ID
+ /// in this shared network.
+ /// @throw isc::BadValue if subnet is null.
+ /// @throw InvalidOperation if a subnet is already associated with some
+ /// shared network.
+ /// @return true if the operation succeeded, false otherwise.
+ bool replace(const Subnet4Ptr& subnet);
+
+ /// @brief Removes subnet from a shared network.
+ ///
+ /// @param subnet_id Identifier of a subnet to be removed.
+ ///
+ /// @throw BadValue When specified subnet doesn't exist.
+ void del(const SubnetID& subnet_id);
+
+ /// @brief Removes all subnets from a shared network.
+ void delAll();
+
+ /// @brief Returns a pointer to the collection of subnets within this
+ /// shared network.
+ const Subnet4SimpleCollection* getAllSubnets() const {
+ return (&subnets_);
+ }
+
+ /// @brief Returns a subnet for a specified subnet id.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Shared pointer to a subnet using this id or null pointer
+ /// if such subnet doesn't exist within shared network.
+ Subnet4Ptr getSubnet(const SubnetID& subnet_id) const;
+
+ /// @brief Returns a subnet for a specified subnet prefix.
+ ///
+ /// @param subnet_prefix Subnet prefix.
+ ///
+ /// @return Shared pointer to a subnet using this prefix or null pointer
+ /// if such subnet doesn't exist within shared network.
+ Subnet4Ptr getSubnet(const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves next available IPv4 subnet within shared network.
+ ///
+ /// See documentation for @ref SharedNetwork4::getNextSubnet.
+ ///
+ /// @param first_subnet Pointer to a subnet from which the caller is
+ /// iterating over subnets within shared network. This is typically a
+ /// subnet selected during "subnet selection" step.
+ /// @param current_subnet Identifier of a subnet for which next subnet is
+ /// to be found.
+ ///
+ /// @return Pointer to next subnet or null pointer if no more subnets found.
+ ///
+ /// @throw isc::BadValue if invalid arguments specified, e.g. unable to
+ /// find first or current subnet within shared network.
+ Subnet4Ptr getNextSubnet(const Subnet4Ptr& first_subnet,
+ const SubnetID& current_subnet) const;
+
+ /// @brief Attempts to find a subnet which is more likely to include available
+ /// leases than selected subnet.
+ ///
+ /// When allocating unreserved leases from a shared network it is important to
+ /// remember from which subnet within the shared network we have been recently
+ /// handing out leases. The allocation engine can use that information to start
+ /// trying allocation of the leases from that subnet rather than from the default
+ /// subnet selected for this client. Starting from the default subnet causes a
+ /// risk of having to walk over many subnets with exhausted address pools before
+ /// getting to the subnet with available leases. This method attempts to find
+ /// such subnet by inspecting "last allocation" timestamps. The one with most
+ /// recent timestamp is selected.
+ ///
+ /// The preferred subnet must also fulfil the condition of equal client classes
+ /// with the @c selected_subnet.
+ ///
+ /// @todo Need extensions to this logic when we support more than one client
+ /// class for a subnet.
+ ///
+ /// @param selected_subnet Pointer to a currently selected subnet.
+ ///
+ /// @return Pointer to a preferred subnet. It may be the same as @c selected_subnet
+ /// if no better subnet was found.
+ Subnet4Ptr getPreferredSubnet(const Subnet4Ptr& selected_subnet) const;
+
+ /// @brief Checks if the shared network includes a subnet with
+ /// the match client ID flag set to true.
+ ///
+ /// @param first_subnet Pointer to the subnet from which iteration starts.
+ /// @param client_classes List of classes that the client belongs to.
+ /// @return true if the shared network includes at least one subnet
+ /// guarded by a given class with the match client ID flag set to true.
+ /// False otherwise.
+ static
+ bool subnetsIncludeMatchClientId(const Subnet4Ptr& first_subnet,
+ const ClientClasses& client_classes);
+
+ /// @brief Unparses shared network object.
+ ///
+ /// @return A pointer to unparsed shared network configuration.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief Generates an identifying label for logging.
+ ///
+ /// @return string containing the label
+ virtual std::string getLabel() const {
+ std::stringstream ss;
+ ss << "shared-network " << name_;
+ return (ss.str());
+ }
+
+private:
+
+ /// @brief Holds a name of a shared network.
+ std::string name_;
+
+ /// @brief Collection of IPv4 subnets within shared network.
+ Subnet4SimpleCollection subnets_;
+};
+
+/// @brief Multi index container holding shared networks.
+///
+/// This is multi index container can hold pointers to @ref SharedNetwork4
+/// objects. It provides indexes for shared network lookups using properties
+/// such as shared network's name.
+typedef boost::multi_index_container<
+ // Multi index container holds pointers to the shared networks.
+ SharedNetwork4Ptr,
+ boost::multi_index::indexed_by<
+ // First is the random access index allowing for accessing objects
+ // just like we'd do with vector.
+ boost::multi_index::random_access<
+ boost::multi_index::tag<SharedNetworkRandomAccessIndexTag>
+ >,
+ // Second index allows for access by shared network id.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<SharedNetworkIdIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement, uint64_t,
+ &data::BaseStampedElement::getId>
+ >,
+ // Third index allows for access by shared network's name.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SharedNetworkNameIndexTag>,
+ boost::multi_index::const_mem_fun<SharedNetwork4, std::string,
+ &SharedNetwork4::getName>
+ >,
+ // Fourth index allows for access by server identifier specified for the
+ // network.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SharedNetworkServerIdIndexTag>,
+ boost::multi_index::const_mem_fun<Network4, asiolink::IOAddress,
+ &Network4::getServerId>
+ >,
+ // Fifth index allows for searching using subnet modification time.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SharedNetworkModificationTimeIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement,
+ boost::posix_time::ptime,
+ &data::BaseStampedElement::getModificationTime>
+ >
+ >
+> SharedNetwork4Collection;
+
+class SharedNetwork6;
+
+/// @brief Pointer to @ref SharedNetwork6 object.
+typedef boost::shared_ptr<SharedNetwork6> SharedNetwork6Ptr;
+
+/// @brief Shared network holding IPv6 subnets.
+///
+/// Specialization of the @ref Network6 class for IPv6 shared networks.
+class SharedNetwork6 : public virtual Network6,
+ public boost::enable_shared_from_this<SharedNetwork6> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets name of the shared network.
+ explicit SharedNetwork6(const std::string& name)
+ : name_(name), subnets_() {
+ }
+
+ /// @brief Factory function creating an instance of the @c SharedNetwork6.
+ ///
+ /// This function should be used to create an instance of the shared
+ /// network within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// @param name Name of the shared network.
+ ///
+ /// @return Pointer to the @c SharedNetwork6 instance.
+ static SharedNetwork6Ptr create(const std::string& name);
+
+ /// @brief Returns a name of the shared network.
+ std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Sets new name for the shared network.
+ ///
+ /// @param name New name for the shared network.
+ void setName(const std::string& name) {
+ name_ = name;
+ }
+
+ /// @brief Adds IPv6 subnet to a shared network.
+ ///
+ /// @param subnet Pointer to a subnet being added to this shared network.
+ ///
+ /// @throw isc::BadValue if subnet is null.
+ /// @throw isc::DuplicateSubnetID if a subnet with the given subnet id
+ /// already exists in this shared network.
+ /// @throw InvalidOperation if a subnet is already associated with some
+ /// shared network.
+ void add(const Subnet6Ptr& subnet);
+
+ /// @brief Replaces IPv6 subnet in a shared network.
+ ///
+ /// This method replaces a subnet by another subnet with the same ID.
+ /// The prefix should be the same too.
+ ///
+ /// @param subnet Pointer to a subnet replacing the subnet with the same ID
+ /// in this shared network.
+ /// @throw isc::BadValue if subnet is null.
+ /// @throw InvalidOperation if a subnet is already associated with some
+ /// shared network.
+ /// @return true if the operation succeeded, false otherwise.
+ bool replace(const Subnet6Ptr& subnet);
+
+ /// @brief Removes subnet from a shared network.
+ ///
+ /// @param subnet_id Identifier of a subnet to be removed.
+ ///
+ /// @throw BadValue When specified subnet doesn't exist.
+ void del(const SubnetID& subnet_id);
+
+ /// @brief Removes all subnets from a shared network.
+ void delAll();
+
+ /// @brief Returns a pointer to the collection of subnets within this
+ /// shared network.
+ const Subnet6SimpleCollection* getAllSubnets() const {
+ return (&subnets_);
+ }
+
+ /// @brief Returns a subnet for a specified subnet id.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Shared pointer to a subnet using this id or null pointer
+ /// if such subnet doesn't exist within shared network.
+ Subnet6Ptr getSubnet(const SubnetID& subnet_id) const;
+
+ /// @brief Returns a subnet for a specified subnet prefix.
+ ///
+ /// @param subnet_prefix Subnet prefix.
+ ///
+ /// @return Shared pointer to a subnet using this prefix or null pointer
+ /// if such subnet doesn't exist within shared network.
+ Subnet6Ptr getSubnet(const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves next available IPv6 subnet within shared network.
+ ///
+ /// See documentation for @ref SharedNetwork6::getNextSubnet.
+ ///
+ /// @param first_subnet Pointer to a subnet from which the caller is
+ /// iterating over subnets within shared network. This is typically a
+ /// subnet selected during "subnet selection" step.
+ /// @param current_subnet Identifier of a subnet for which next subnet is
+ /// to be found.
+ ///
+ /// @return Pointer to next subnet or null pointer if no more subnets found.
+ ///
+ /// @throw isc::BadValue if invalid arguments specified, e.g. unable to
+ /// find first or current subnet within shared network.
+ Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet,
+ const SubnetID& current_subnet) const;
+
+ /// @brief Attempts to find a subnet which is more likely to include available
+ /// leases than selected subnet.
+ ///
+ /// When allocating unreserved leases from a shared network it is important to
+ /// remember from which subnet within the shared network we have been recently
+ /// handing out leases. The allocation engine can use that information to start
+ /// trying allocation of the leases from that subnet rather than from the default
+ /// subnet selected for this client. Starting from the default subnet causes a
+ /// risk of having to walk over many subnets with exhausted address pools before
+ /// getting to the subnet with available leases. This method attempts to find
+ /// such subnet by inspecting "last allocation" timestamps. The one with most
+ /// recent timestamp is selected.
+ ///
+ /// The preferred subnet must also fulfil the condition of equal client classes
+ /// with the @c selected_subnet.
+ ///
+ /// @param selected_subnet Pointer to a currently selected subnet.
+ /// @param lease_type Type of the lease for which preferred subnet should be
+ /// returned.
+ ///
+ /// @return Pointer to a preferred subnet. It may be the same as @c selected_subnet
+ /// if no better subnet was found.
+ Subnet6Ptr getPreferredSubnet(const Subnet6Ptr& selected_subnet,
+ const Lease::Type& lease_type) const;
+
+ /// @brief Unparses shared network object.
+ ///
+ /// @return A pointer to unparsed shared network configuration.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief Generates an identifying label for logging.
+ ///
+ /// @return string containing the label
+ virtual std::string getLabel() const {
+ std::stringstream ss;
+ ss << "shared-network " << name_;
+ return (ss.str());
+ }
+
+private:
+
+ /// @brief Holds a name of a shared network.
+ std::string name_;
+
+ /// @brief Collection of IPv6 subnets within shared network.
+ Subnet6SimpleCollection subnets_;
+};
+
+/// @brief Multi index container holding shared networks.
+///
+/// This is multi index container can hold pointers to @ref SharedNetwork6
+/// objects. It provides indexes for shared network lookups using properties
+/// such as shared network's name.
+typedef boost::multi_index_container<
+ // Multi index container holds pointers to the shared networks.
+ SharedNetwork6Ptr,
+ boost::multi_index::indexed_by<
+ // First is the random access index allowing for accessing objects
+ // just like we'd do with vector.
+ boost::multi_index::random_access<
+ boost::multi_index::tag<SharedNetworkRandomAccessIndexTag>
+ >,
+ // Second index allows for access by shared network id.
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::tag<SharedNetworkIdIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement, uint64_t,
+ &data::BaseStampedElement::getId>
+ >,
+ // Third index allows for access by shared network's name.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SharedNetworkNameIndexTag>,
+ boost::multi_index::const_mem_fun<SharedNetwork6, std::string,
+ &SharedNetwork6::getName>
+ >,
+ // Fourth index allows for searching using subnet modification time.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SharedNetworkModificationTimeIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement,
+ boost::posix_time::ptime,
+ &data::BaseStampedElement::getModificationTime>
+ >
+ >
+> SharedNetwork6Collection;
+
+/// @brief A class containing static convenience methods to fetch the shared
+/// networks from the containers.
+///
+/// @tparam ReturnPtrType Type of the returned object, i.e. @c SharedNetwork4Ptr
+/// or @c SharedNetwork6Ptr.
+/// @tparam CollectionType One of the @c SharedNetwork4Collection or
+/// @c SharedNetwork6Collection.
+template<typename ReturnPtrType, typename CollectionType>
+class SharedNetworkFetcher {
+public:
+
+ /// @brief Fetches shared network by name.
+ ///
+ /// @param collection Const reference to the collection from which the shared
+ /// network is to be fetched.
+ /// @param name Name of the shared network to be fetched.
+ /// @return Pointer to the fetched shared network or null if no such shared
+ /// network could be found.
+ static ReturnPtrType get(const CollectionType& collection, const std::string& name) {
+ auto& index = collection.template get<SharedNetworkNameIndexTag>();
+ auto sn = index.find(name);
+ if (sn != index.end()) {
+ return (*sn);
+ }
+ // No network found. Return null pointer.
+ return (ReturnPtrType());
+ }
+};
+
+/// @brief Type of the @c SharedNetworkFetcher used for IPv4.
+using SharedNetworkFetcher4 = SharedNetworkFetcher<SharedNetwork4Ptr, SharedNetwork4Collection>;
+
+/// @brief Type of the @c SharedNetworkFetcher used for IPv6.
+using SharedNetworkFetcher6 = SharedNetworkFetcher<SharedNetwork6Ptr, SharedNetwork6Collection>;
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // SHARED_NETWORK_H
diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc
new file mode 100644
index 0000000..3d2dfce
--- /dev/null
+++ b/src/lib/dhcpsrv/srv_config.cc
@@ -0,0 +1,1044 @@
+// Copyright (C) 2014-2023 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 <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/base_network_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/srv_config.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <process/logging_info.h>
+#include <log/logger_manager.h>
+#include <log/logger_specification.h>
+#include <dhcp/pkt.h> // Needed for HWADDR_SOURCE_*
+#include <stats/stats_mgr.h>
+#include <util/strutil.h>
+
+#include <boost/make_shared.hpp>
+
+#include <list>
+#include <sstream>
+
+using namespace isc::log;
+using namespace isc::data;
+using namespace isc::process;
+
+namespace isc {
+namespace dhcp {
+
+SrvConfig::SrvConfig()
+ : sequence_(0), cfg_iface_(new CfgIface()),
+ cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
+ cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
+ cfg_shared_networks4_(new CfgSharedNetworks4()),
+ cfg_shared_networks6_(new CfgSharedNetworks6()),
+ cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
+ cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
+ cfg_db_access_(new CfgDbAccess()),
+ cfg_host_operations4_(CfgHostOperations::createConfig4()),
+ cfg_host_operations6_(CfgHostOperations::createConfig6()),
+ class_dictionary_(new ClientClassDictionary()),
+ decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0),
+ d2_client_config_(new D2ClientConfig()),
+ configured_globals_(new CfgGlobals()), cfg_consist_(new CfgConsistency()),
+ lenient_option_parsing_(false), ignore_dhcp_server_identifier_(false),
+ ignore_rai_link_selection_(false), exclude_first_last_24_(false),
+ reservations_lookup_first_(false) {
+}
+
+SrvConfig::SrvConfig(const uint32_t sequence)
+ : sequence_(sequence), cfg_iface_(new CfgIface()),
+ cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
+ cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
+ cfg_shared_networks4_(new CfgSharedNetworks4()),
+ cfg_shared_networks6_(new CfgSharedNetworks6()),
+ cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
+ cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
+ cfg_db_access_(new CfgDbAccess()),
+ cfg_host_operations4_(CfgHostOperations::createConfig4()),
+ cfg_host_operations6_(CfgHostOperations::createConfig6()),
+ class_dictionary_(new ClientClassDictionary()),
+ decline_timer_(0), echo_v4_client_id_(true), dhcp4o6_port_(0),
+ d2_client_config_(new D2ClientConfig()),
+ configured_globals_(new CfgGlobals()), cfg_consist_(new CfgConsistency()),
+ lenient_option_parsing_(false), ignore_dhcp_server_identifier_(false),
+ ignore_rai_link_selection_(false), exclude_first_last_24_(false),
+ reservations_lookup_first_(false) {
+}
+
+std::string
+SrvConfig::getConfigSummary(const uint32_t selection) const {
+ std::ostringstream s;
+ size_t subnets_num;
+ if ((selection & CFGSEL_SUBNET4) == CFGSEL_SUBNET4) {
+ subnets_num = getCfgSubnets4()->getAll()->size();
+ if (subnets_num > 0) {
+ s << "added IPv4 subnets: " << subnets_num;
+ } else {
+ s << "no IPv4 subnets!";
+ }
+ s << "; ";
+ }
+
+ if ((selection & CFGSEL_SUBNET6) == CFGSEL_SUBNET6) {
+ subnets_num = getCfgSubnets6()->getAll()->size();
+ if (subnets_num > 0) {
+ s << "added IPv6 subnets: " << subnets_num;
+ } else {
+ s << "no IPv6 subnets!";
+ }
+ s << "; ";
+ }
+
+ if ((selection & CFGSEL_DDNS) == CFGSEL_DDNS) {
+ bool ddns_enabled = getD2ClientConfig()->getEnableUpdates();
+ s << "DDNS: " << (ddns_enabled ? "enabled" : "disabled") << "; ";
+ }
+
+ if (s.tellp() == static_cast<std::streampos>(0)) {
+ s << "no config details available";
+ }
+
+ std::string summary = s.str();
+ size_t last_separator_pos = summary.find_last_of(";");
+ if (last_separator_pos == summary.length() - 2) {
+ summary.erase(last_separator_pos);
+ }
+ return (summary);
+}
+
+bool
+SrvConfig::sequenceEquals(const SrvConfig& other) {
+ return (getSequence() == other.getSequence());
+}
+
+void
+SrvConfig::copy(SrvConfig& new_config) const {
+ ConfigBase::copy(new_config);
+
+ // Replace interface configuration.
+ new_config.cfg_iface_.reset(new CfgIface(*cfg_iface_));
+ // Replace option definitions.
+ cfg_option_def_->copyTo(*new_config.cfg_option_def_);
+ cfg_option_->copyTo(*new_config.cfg_option_);
+ // Replace the client class dictionary
+ new_config.class_dictionary_.reset(new ClientClassDictionary(*class_dictionary_));
+ // Replace the D2 client configuration
+ new_config.setD2ClientConfig(getD2ClientConfig());
+ // Replace configured hooks libraries.
+ new_config.hooks_config_.clear();
+ using namespace isc::hooks;
+ for (HookLibsCollection::const_iterator it = hooks_config_.get().begin();
+ it != hooks_config_.get().end(); ++it) {
+ new_config.hooks_config_.add(it->first, it->second);
+ }
+}
+
+bool
+SrvConfig::equals(const SrvConfig& other) const {
+
+ // Checks common elements: logging & config control
+ if (!ConfigBase::equals(other)) {
+ return (false);
+ }
+
+ // Common information is equal between objects, so check other values.
+ if ((*cfg_iface_ != *other.cfg_iface_) ||
+ (*cfg_option_def_ != *other.cfg_option_def_) ||
+ (*cfg_option_ != *other.cfg_option_) ||
+ (*class_dictionary_ != *other.class_dictionary_) ||
+ (*d2_client_config_ != *other.d2_client_config_)) {
+ return (false);
+ }
+ // Now only configured hooks libraries can differ.
+ // If number of configured hooks libraries are different, then
+ // configurations aren't equal.
+ if (hooks_config_.get().size() != other.hooks_config_.get().size()) {
+ return (false);
+ }
+ // Pass through all configured hooks libraries.
+ return (hooks_config_.equal(other.hooks_config_));
+}
+
+void
+SrvConfig::merge(ConfigBase& other) {
+ ConfigBase::merge(other);
+ try {
+ SrvConfig& other_srv_config = dynamic_cast<SrvConfig&>(other);
+ // We merge objects in order of dependency (real or theoretical).
+ // First we merge the common stuff.
+
+ // Merge globals.
+ mergeGlobals(other_srv_config);
+
+ // Merge option defs. We need to do this next so we
+ // pass these into subsequent merges so option instances
+ // at each level can be created based on the merged
+ // definitions.
+ cfg_option_def_->merge((*other_srv_config.getCfgOptionDef()));
+
+ // Merge options.
+ cfg_option_->merge(cfg_option_def_, (*other_srv_config.getCfgOption()));
+
+ if (!other_srv_config.getClientClassDictionary()->empty()) {
+ // Client classes are complicated because they are ordered and may
+ // depend on each other. Merging two lists of classes with preserving
+ // the order would be very involved and could result in errors. Thus,
+ // we simply replace the current list of classes with a new list.
+ setClientClassDictionary(boost::make_shared
+ <ClientClassDictionary>(*other_srv_config.getClientClassDictionary()));
+ }
+
+ if (CfgMgr::instance().getFamily() == AF_INET) {
+ merge4(other_srv_config);
+ } else {
+ merge6(other_srv_config);
+ }
+ } catch (const std::bad_cast&) {
+ isc_throw(InvalidOperation, "internal server error: must use derivation"
+ " of the SrvConfig as an argument of the call to"
+ " SrvConfig::merge()");
+ }
+}
+
+void
+SrvConfig::merge4(SrvConfig& other) {
+ // Merge shared networks.
+ cfg_shared_networks4_->merge(cfg_option_def_, *(other.getCfgSharedNetworks4()));
+
+ // Merge subnets.
+ cfg_subnets4_->merge(cfg_option_def_, getCfgSharedNetworks4(),
+ *(other.getCfgSubnets4()));
+
+ /// @todo merge other parts of the configuration here.
+}
+
+void
+SrvConfig::merge6(SrvConfig& other) {
+ // Merge shared networks.
+ cfg_shared_networks6_->merge(cfg_option_def_, *(other.getCfgSharedNetworks6()));
+
+ // Merge subnets.
+ cfg_subnets6_->merge(cfg_option_def_, getCfgSharedNetworks6(),
+ *(other.getCfgSubnets6()));
+
+ /// @todo merge other parts of the configuration here.
+}
+
+void
+SrvConfig::mergeGlobals(SrvConfig& other) {
+ auto config_set = getConfiguredGlobals();
+ // If the deprecated reservation-mode is found in database, overwrite other
+ // reservation flags so there is no conflict when merging to new flags.
+ if (other.getConfiguredGlobal(CfgGlobals::RESERVATION_MODE)) {
+ config_set->set(CfgGlobals::RESERVATIONS_GLOBAL, ConstElementPtr());
+ config_set->set(CfgGlobals::RESERVATIONS_IN_SUBNET, ConstElementPtr());
+ config_set->set(CfgGlobals::RESERVATIONS_OUT_OF_POOL, ConstElementPtr());
+ }
+ // Iterate over the "other" globals, adding/overwriting them into
+ // this config's list of globals.
+ for (auto other_global : other.getConfiguredGlobals()->valuesMap()) {
+ addConfiguredGlobal(other_global.first, other_global.second);
+ }
+
+ // Merge the reservation-mode to new reservation flags.
+ BaseNetworkParser::moveReservationMode(config_set);
+
+ // A handful of values are stored as members in SrvConfig. So we'll
+ // iterate over the merged globals, setting appropriate members.
+ for (auto merged_global : getConfiguredGlobals()->valuesMap()) {
+ std::string name = merged_global.first;
+ ConstElementPtr element = merged_global.second;
+ try {
+ if (name == "decline-probation-period") {
+ setDeclinePeriod(element->intValue());
+ } else if (name == "echo-client-id") {
+ // echo-client-id is v4 only, but we'll let upstream
+ // worry about that.
+ setEchoClientId(element->boolValue());
+ } else if (name == "dhcp4o6-port") {
+ setDhcp4o6Port(element->intValue());
+ } else if (name == "server-tag") {
+ setServerTag(element->stringValue());
+ } else if (name == "ip-reservations-unique") {
+ setIPReservationsUnique(element->boolValue());
+ } else if (name == "reservations-lookup-first") {
+ setReservationsLookupFirst(element->boolValue());
+ }
+ } catch(const std::exception& ex) {
+ isc_throw (BadValue, "Invalid value:" << element->str()
+ << " explicit global:" << name);
+ }
+ }
+}
+
+void
+SrvConfig::removeStatistics() {
+ // Removes statistics for v4 and v6 subnets
+ getCfgSubnets4()->removeStatistics();
+
+ getCfgSubnets6()->removeStatistics();
+}
+
+void
+SrvConfig::updateStatistics() {
+ // Update default sample limits.
+ stats::StatsMgr& stats_mgr = stats::StatsMgr::instance();
+ ConstElementPtr samples =
+ getConfiguredGlobal("statistic-default-sample-count");
+ uint32_t max_samples = 0;
+ if (samples) {
+ max_samples = samples->intValue();
+ stats_mgr.setMaxSampleCountDefault(max_samples);
+ if (max_samples != 0) {
+ stats_mgr.setMaxSampleCountAll(max_samples);
+ }
+ }
+ ConstElementPtr duration =
+ getConfiguredGlobal("statistic-default-sample-age");
+ if (duration) {
+ int64_t time_duration = duration->intValue();
+ auto max_age = std::chrono::seconds(time_duration);
+ stats_mgr.setMaxSampleAgeDefault(max_age);
+ if (max_samples == 0) {
+ stats_mgr.setMaxSampleAgeAll(max_age);
+ }
+ }
+
+ // Updating subnet statistics involves updating lease statistics, which
+ // is done by the LeaseMgr. Since servers with subnets, must have a
+ // LeaseMgr, we do not bother updating subnet stats for servers without
+ // a lease manager, such as D2. @todo We should probably examine why
+ // "SrvConfig" is being used by D2.
+ if (LeaseMgrFactory::haveInstance()) {
+ // Updates statistics for v4 and v6 subnets
+ getCfgSubnets4()->updateStatistics();
+
+ getCfgSubnets6()->updateStatistics();
+ }
+}
+
+void
+SrvConfig::applyDefaultsConfiguredGlobals(const SimpleDefaults& defaults) {
+ // Code from SimpleParser::setDefaults
+ // This is the position representing a default value. As the values
+ // we're inserting here are not present in whatever the config file
+ // came from, we need to make sure it's clearly labeled as default.
+ const Element::Position pos("<default-value>", 0, 0);
+
+ // Let's go over all parameters we have defaults for.
+ for (auto def_value : defaults) {
+
+ // Try if such a parameter is there. If it is, let's
+ // skip it, because user knows best *cough*.
+ ConstElementPtr x = getConfiguredGlobal(def_value.name_);
+ if (x) {
+ // There is such a value already, skip it.
+ continue;
+ }
+
+ // There isn't such a value defined, let's create the default
+ // value...
+ switch (def_value.type_) {
+ case Element::string: {
+ x.reset(new StringElement(def_value.value_, pos));
+ break;
+ }
+ case Element::integer: {
+ try {
+ int int_value = boost::lexical_cast<int>(def_value.value_);
+ x.reset(new IntElement(int_value, pos));
+ }
+ catch (const std::exception& ex) {
+ isc_throw(BadValue,
+ "Internal error. Integer value expected for: "
+ << def_value.name_ << ", value is: "
+ << def_value.value_ );
+ }
+
+ break;
+ }
+ case Element::boolean: {
+ bool bool_value;
+ if (def_value.value_ == std::string("true")) {
+ bool_value = true;
+ } else if (def_value.value_ == std::string("false")) {
+ bool_value = false;
+ } else {
+ isc_throw(BadValue,
+ "Internal error. Boolean value for "
+ << def_value.name_ << " specified as "
+ << def_value.value_ << ", expected true or false");
+ }
+ x.reset(new BoolElement(bool_value, pos));
+ break;
+ }
+ case Element::real: {
+ double dbl_value = boost::lexical_cast<double>(def_value.value_);
+ x.reset(new DoubleElement(dbl_value, pos));
+ break;
+ }
+ default:
+ // No default values for null, list or map
+ isc_throw(BadValue,
+ "Internal error. Incorrect default value type for "
+ << def_value.name_);
+ }
+ addConfiguredGlobal(def_value.name_, x);
+ }
+}
+
+void
+SrvConfig::extractConfiguredGlobals(isc::data::ConstElementPtr config) {
+ if (config->getType() != Element::map) {
+ isc_throw(BadValue, "extractConfiguredGlobals must be given a map element");
+ }
+
+ const std::map<std::string, ConstElementPtr>& values = config->mapValue();
+ for (auto value = values.begin(); value != values.end(); ++value) {
+ if (value->second->getType() != Element::list &&
+ value->second->getType() != Element::map) {
+ addConfiguredGlobal(value->first, value->second);
+ }
+ }
+}
+
+void
+SrvConfig::sanityChecksLifetime(const std::string& name) const {
+ // Initialize as some compilers complain otherwise.
+ uint32_t value = 0;
+ ConstElementPtr has_value = getConfiguredGlobal(name);
+ if (has_value) {
+ value = has_value->intValue();
+ }
+
+ uint32_t min_value = 0;
+ ConstElementPtr has_min = getConfiguredGlobal("min-" + name);
+ if (has_min) {
+ min_value = has_min->intValue();
+ }
+
+ uint32_t max_value = 0;
+ ConstElementPtr has_max = getConfiguredGlobal("max-" + name);
+ if (has_max) {
+ max_value = has_max->intValue();
+ }
+
+ if (!has_value && !has_min && !has_max) {
+ return;
+ }
+ if (has_value) {
+ if (!has_min && !has_max) {
+ // default only.
+ return;
+ } else if (!has_min) {
+ // default and max.
+ min_value = value;
+ } else if (!has_max) {
+ // default and min.
+ max_value = value;
+ }
+ } else if (has_min) {
+ if (!has_max) {
+ // min only.
+ return;
+ } else {
+ // min and max.
+ isc_throw(BadValue, "have min-" << name << " and max-"
+ << name << " but no " << name << " (default)");
+ }
+ } else {
+ // max only.
+ return;
+ }
+
+ // Check that min <= max.
+ if (min_value > max_value) {
+ if (has_min && has_max) {
+ isc_throw(BadValue, "the value of min-" << name << " ("
+ << min_value << ") is not less than max-" << name << " ("
+ << max_value << ")");
+ } else if (has_min) {
+ // Only min and default so min > default.
+ isc_throw(BadValue, "the value of min-" << name << " ("
+ << min_value << ") is not less than (default) " << name
+ << " (" << value << ")");
+ } else {
+ // Only default and max so default > max.
+ isc_throw(BadValue, "the value of (default) " << name
+ << " (" << value << ") is not less than max-" << name
+ << " (" << max_value << ")");
+ }
+ }
+
+ // Check that value is between min and max.
+ if ((value < min_value) || (value > max_value)) {
+ isc_throw(BadValue, "the value of (default) " << name << " ("
+ << value << ") is not between min-" << name << " ("
+ << min_value << ") and max-" << name << " ("
+ << max_value << ")");
+ }
+}
+
+void
+SrvConfig::sanityChecksLifetime(const SrvConfig& target_config,
+ const std::string& name) const {
+ // Three cases:
+ // - the external/source config has the parameter: use it.
+ // - only the target config has the parameter: use this one.
+ // - no config has the parameter.
+ uint32_t value = 0;
+ ConstElementPtr has_value = getConfiguredGlobal(name);
+ bool new_value = true;
+ if (!has_value) {
+ has_value = target_config.getConfiguredGlobal(name);
+ new_value = false;
+ }
+ if (has_value) {
+ value = has_value->intValue();
+ }
+
+ uint32_t min_value = 0;
+ ConstElementPtr has_min = getConfiguredGlobal("min-" + name);
+ bool new_min = true;
+ if (!has_min) {
+ has_min = target_config.getConfiguredGlobal("min-" + name);
+ new_min = false;
+ }
+ if (has_min) {
+ min_value = has_min->intValue();
+ }
+
+ uint32_t max_value = 0;
+ ConstElementPtr has_max = getConfiguredGlobal("max-" + name);
+ bool new_max = true;
+ if (!has_max) {
+ has_max = target_config.getConfiguredGlobal("max-" + name);
+ new_max = false;
+ }
+ if (has_max) {
+ max_value = has_max->intValue();
+ }
+
+ if (!has_value && !has_min && !has_max) {
+ return;
+ }
+ if (has_value) {
+ if (!has_min && !has_max) {
+ // default only.
+ return;
+ } else if (!has_min) {
+ // default and max.
+ min_value = value;
+ } else if (!has_max) {
+ // default and min.
+ max_value = value;
+ }
+ } else if (has_min) {
+ if (!has_max) {
+ // min only.
+ return;
+ } else {
+ // min and max.
+ isc_throw(BadValue, "have min-" << name << " and max-"
+ << name << " but no " << name << " (default)");
+ }
+ } else {
+ // max only.
+ return;
+ }
+
+ // Check that min <= max.
+ if (min_value > max_value) {
+ if (has_min && has_max) {
+ std::string from_min = (new_min ? "new" : "previous");
+ std::string from_max = (new_max ? "new" : "previous");
+ isc_throw(BadValue, "the value of " << from_min
+ << " min-" << name << " ("
+ << min_value << ") is not less than "
+ << from_max << " max-" << name
+ << " (" << max_value << ")");
+ } else if (has_min) {
+ // Only min and default so min > default.
+ std::string from_min = (new_min ? "new" : "previous");
+ std::string from_value = (new_value ? "new" : "previous");
+ isc_throw(BadValue, "the value of " << from_min
+ << " min-" << name << " ("
+ << min_value << ") is not less than " << from_value
+ << " (default) " << name
+ << " (" << value << ")");
+ } else {
+ // Only default and max so default > max.
+ std::string from_max = (new_max ? "new" : "previous");
+ std::string from_value = (new_value ? "new" : "previous");
+ isc_throw(BadValue, "the value of " << from_value
+ << " (default) " << name
+ << " (" << value << ") is not less than " << from_max
+ << " max-" << name << " (" << max_value << ")");
+ }
+ }
+
+ // Check that value is between min and max.
+ if ((value < min_value) || (value > max_value)) {
+ std::string from_value = (new_value ? "new" : "previous");
+ std::string from_min = (new_min ? "new" : "previous");
+ std::string from_max = (new_max ? "new" : "previous");
+ isc_throw(BadValue, "the value of " << from_value
+ <<" (default) " << name << " ("
+ << value << ") is not between " << from_min
+ << " min-" << name << " (" << min_value
+ << ") and " << from_max << " max-"
+ << name << " (" << max_value << ")");
+ }
+}
+
+ElementPtr
+SrvConfig::toElement() const {
+ // Top level map
+ ElementPtr result = Element::createMap();
+
+ // Get family for the configuration manager
+ uint16_t family = CfgMgr::instance().getFamily();
+
+ // DhcpX global map initialized from configured globals
+ ElementPtr dhcp = configured_globals_->toElement();
+
+ auto loggers_info = getLoggingInfo();
+ // Was in the Logging global map.
+ if (!loggers_info.empty()) {
+ // Set loggers list
+ ElementPtr loggers = Element::createList();
+ for (LoggingInfoStorage::const_iterator logger =
+ loggers_info.cbegin();
+ logger != loggers_info.cend(); ++logger) {
+ loggers->add(logger->toElement());
+ }
+ dhcp->set("loggers", loggers);
+ }
+
+ // Set user-context
+ contextToElement(dhcp);
+
+ // Set data directory if DHCPv6 and specified.
+ if (family == AF_INET6) {
+ const util::Optional<std::string>& datadir =
+ CfgMgr::instance().getDataDir();
+ if (!datadir.unspecified()) {
+ dhcp->set("data-directory", Element::create(datadir));
+ }
+ }
+
+ // Set compatibility flags.
+ ElementPtr compatibility = Element::createMap();
+ if (getLenientOptionParsing()) {
+ compatibility->set("lenient-option-parsing", Element::create(true));
+ }
+ if (getIgnoreServerIdentifier()) {
+ compatibility->set("ignore-dhcp-server-identifier", Element::create(true));
+ }
+ if (getIgnoreRAILinkSelection()) {
+ compatibility->set("ignore-rai-link-selection", Element::create(true));
+ }
+ if (getExcludeFirstLast24()) {
+ compatibility->set("exclude-first-last-24", Element::create(true));
+ }
+ if (compatibility->size() > 0) {
+ dhcp->set("compatibility", compatibility);
+ }
+
+ // Set decline-probation-period
+ dhcp->set("decline-probation-period",
+ Element::create(static_cast<long long>(decline_timer_)));
+ // Set echo-client-id (DHCPv4)
+ if (family == AF_INET) {
+ dhcp->set("echo-client-id", Element::create(echo_v4_client_id_));
+ }
+ // Set dhcp4o6-port
+ dhcp->set("dhcp4o6-port",
+ Element::create(static_cast<int>(dhcp4o6_port_)));
+ // Set dhcp-ddns
+ dhcp->set("dhcp-ddns", d2_client_config_->toElement());
+ // Set interfaces-config
+ dhcp->set("interfaces-config", cfg_iface_->toElement());
+ // Set option-def
+ dhcp->set("option-def", cfg_option_def_->toElement());
+ // Set option-data
+ dhcp->set("option-data", cfg_option_->toElement());
+
+ // Set subnets and shared networks.
+
+ // We have two problems to solve:
+ // - a subnet is unparsed once:
+ // * if it is a plain subnet in the global subnet list
+ // * if it is a member of a shared network in the shared network
+ // subnet list
+ // - unparsed subnets must be kept to add host reservations in them.
+ // Of course this can be done only when subnets are unparsed.
+
+ // The list of all unparsed subnets
+ std::vector<ElementPtr> sn_list;
+
+ if (family == AF_INET) {
+ // Get plain subnets
+ ElementPtr plain_subnets = Element::createList();
+ const Subnet4Collection* subnets = cfg_subnets4_->getAll();
+ for (Subnet4Collection::const_iterator subnet = subnets->cbegin();
+ subnet != subnets->cend(); ++subnet) {
+ // Skip subnets which are in a shared-network
+ SharedNetwork4Ptr network;
+ (*subnet)->getSharedNetwork(network);
+ if (network) {
+ continue;
+ }
+ ElementPtr subnet_cfg = (*subnet)->toElement();
+ sn_list.push_back(subnet_cfg);
+ plain_subnets->add(subnet_cfg);
+ }
+ dhcp->set("subnet4", plain_subnets);
+
+ // Get shared networks
+ ElementPtr shared_networks = cfg_shared_networks4_->toElement();
+ dhcp->set("shared-networks", shared_networks);
+
+ // Get subnets in shared network subnet lists
+ const std::vector<ElementPtr> networks = shared_networks->listValue();
+ for (auto network = networks.cbegin();
+ network != networks.cend(); ++network) {
+ const std::vector<ElementPtr> sh_list =
+ (*network)->get("subnet4")->listValue();
+ for (auto subnet = sh_list.cbegin();
+ subnet != sh_list.cend(); ++subnet) {
+ sn_list.push_back(*subnet);
+ }
+ }
+
+ } else {
+ // Get plain subnets
+ ElementPtr plain_subnets = Element::createList();
+ const Subnet6Collection* subnets = cfg_subnets6_->getAll();
+ for (Subnet6Collection::const_iterator subnet = subnets->cbegin();
+ subnet != subnets->cend(); ++subnet) {
+ // Skip subnets which are in a shared-network
+ SharedNetwork6Ptr network;
+ (*subnet)->getSharedNetwork(network);
+ if (network) {
+ continue;
+ }
+ ElementPtr subnet_cfg = (*subnet)->toElement();
+ sn_list.push_back(subnet_cfg);
+ plain_subnets->add(subnet_cfg);
+ }
+ dhcp->set("subnet6", plain_subnets);
+
+ // Get shared networks
+ ElementPtr shared_networks = cfg_shared_networks6_->toElement();
+ dhcp->set("shared-networks", shared_networks);
+
+ // Get subnets in shared network subnet lists
+ const std::vector<ElementPtr> networks = shared_networks->listValue();
+ for (auto network = networks.cbegin();
+ network != networks.cend(); ++network) {
+ const std::vector<ElementPtr> sh_list =
+ (*network)->get("subnet6")->listValue();
+ for (auto subnet = sh_list.cbegin();
+ subnet != sh_list.cend(); ++subnet) {
+ sn_list.push_back(*subnet);
+ }
+ }
+ }
+
+ // Host reservations
+ CfgHostsList resv_list;
+ resv_list.internalize(cfg_hosts_->toElement());
+
+ // Insert global reservations
+ ConstElementPtr global_resvs = resv_list.get(SUBNET_ID_GLOBAL);
+ if (global_resvs->size() > 0) {
+ dhcp->set("reservations", global_resvs);
+ }
+
+ // Insert subnet reservations
+ for (std::vector<ElementPtr>::const_iterator subnet = sn_list.cbegin();
+ subnet != sn_list.cend(); ++subnet) {
+ ConstElementPtr id = (*subnet)->get("id");
+ if (isNull(id)) {
+ isc_throw(ToElementError, "subnet has no id");
+ }
+ SubnetID subnet_id = id->intValue();
+ ConstElementPtr resvs = resv_list.get(subnet_id);
+ (*subnet)->set("reservations", resvs);
+ }
+
+ // Set expired-leases-processing
+ ConstElementPtr expired = cfg_expiration_->toElement();
+ dhcp->set("expired-leases-processing", expired);
+ if (family == AF_INET6) {
+ // Set server-id (DHCPv6)
+ dhcp->set("server-id", cfg_duid_->toElement());
+
+ // Set relay-supplied-options (DHCPv6)
+ dhcp->set("relay-supplied-options", cfg_rsoo_->toElement());
+ }
+ // Set lease-database
+ CfgLeaseDbAccess lease_db(*cfg_db_access_);
+ dhcp->set("lease-database", lease_db.toElement());
+ // Set hosts-databases
+ CfgHostDbAccess host_db(*cfg_db_access_);
+ ConstElementPtr hosts_databases = host_db.toElement();
+ if (hosts_databases->size() > 0) {
+ dhcp->set("hosts-databases", hosts_databases);
+ }
+ // Set host-reservation-identifiers
+ ConstElementPtr host_ids;
+ if (family == AF_INET) {
+ host_ids = cfg_host_operations4_->toElement();
+ } else {
+ host_ids = cfg_host_operations6_->toElement();
+ }
+ dhcp->set("host-reservation-identifiers", host_ids);
+ // Set mac-sources (DHCPv6)
+ if (family == AF_INET6) {
+ dhcp->set("mac-sources", cfg_mac_source_.toElement());
+ }
+ // Set control-socket (skip if null as empty is not legal)
+ if (!isNull(control_socket_)) {
+ dhcp->set("control-socket", UserContext::toElement(control_socket_));
+ }
+ // Set client-classes
+ ConstElementPtr client_classes = class_dictionary_->toElement();
+ /// @todo accept empty list
+ if (!client_classes->empty()) {
+ dhcp->set("client-classes", client_classes);
+ }
+ // Set hooks-libraries
+ ConstElementPtr hooks_libs = hooks_config_.toElement();
+ dhcp->set("hooks-libraries", hooks_libs);
+ // Set DhcpX
+ result->set(family == AF_INET ? "Dhcp4" : "Dhcp6", dhcp);
+
+ ConstElementPtr cfg_consist = cfg_consist_->toElement();
+ dhcp->set("sanity-checks", cfg_consist);
+
+ // Set config-control (if it exists)
+ ConstConfigControlInfoPtr info = getConfigControlInfo();
+ if (info) {
+ ConstElementPtr info_elem = info->toElement();
+ dhcp->set("config-control", info_elem);
+ }
+
+ // Set dhcp-packet-control (if it exists)
+ data::ConstElementPtr dhcp_queue_control = getDHCPQueueControl();
+ if (dhcp_queue_control) {
+ dhcp->set("dhcp-queue-control", dhcp_queue_control);
+ }
+
+ // Set multi-threading (if it exists)
+ data::ConstElementPtr dhcp_multi_threading = getDHCPMultiThreading();
+ if (dhcp_multi_threading) {
+ dhcp->set("multi-threading", dhcp_multi_threading);
+ }
+
+ return (result);
+}
+
+DdnsParamsPtr
+SrvConfig::getDdnsParams(const Subnet4Ptr& subnet) const {
+ return (DdnsParamsPtr(new DdnsParams(subnet,
+ getD2ClientConfig()->getEnableUpdates())));
+}
+
+DdnsParamsPtr
+SrvConfig::getDdnsParams(const Subnet6Ptr& subnet) const {
+ return(DdnsParamsPtr(new DdnsParams(subnet,
+ getD2ClientConfig()->getEnableUpdates())));
+}
+
+void
+SrvConfig::moveDdnsParams(isc::data::ElementPtr srv_elem) {
+ if (!srv_elem || (srv_elem->getType() != Element::map)) {
+ isc_throw(BadValue, "moveDdnsParams server config must be given a map element");
+ }
+
+ if (!srv_elem->contains("dhcp-ddns")) {
+ /* nothing to do */
+ return;
+ }
+
+ ElementPtr d2_elem = boost::const_pointer_cast<Element>(srv_elem->get("dhcp-ddns"));
+ if (!d2_elem || (d2_elem->getType() != Element::map)) {
+ isc_throw(BadValue, "moveDdnsParams dhcp-ddns is not a map");
+ }
+
+ struct Param {
+ std::string from_name;
+ std::string to_name;
+ };
+
+ std::vector<Param> params {
+ { "override-no-update", "ddns-override-no-update" },
+ { "override-client-update", "ddns-override-client-update" },
+ { "replace-client-name", "ddns-replace-client-name" },
+ { "generated-prefix", "ddns-generated-prefix" },
+ { "qualifying-suffix", "ddns-qualifying-suffix" },
+ { "hostname-char-set", "hostname-char-set" },
+ { "hostname-char-replacement", "hostname-char-replacement" }
+ };
+
+ for (auto param : params) {
+ if (d2_elem->contains(param.from_name)) {
+ if (!srv_elem->contains(param.to_name)) {
+ // No global value for it already, so let's add it.
+ srv_elem->set(param.to_name, d2_elem->get(param.from_name));
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_DDNS_PARAMETER_MOVED)
+ .arg(param.from_name).arg(param.to_name);
+ } else {
+ // Already a global value, we'll use it and ignore this one.
+ LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_DDNS_PARAMETER_IGNORED)
+ .arg(param.from_name).arg(param.to_name);
+ }
+
+ // Now remove it from d2_data, so D2ClientCfg won't complain.
+ d2_elem->remove(param.from_name);
+ }
+ }
+}
+
+void
+SrvConfig::setIPReservationsUnique(const bool unique) {
+ if (!getCfgDbAccess()->getIPReservationsUnique() && unique) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_IP_RESERVATIONS_UNIQUE_DUPLICATES_POSSIBLE);
+ }
+ getCfgHosts()->setIPReservationsUnique(unique);
+ getCfgDbAccess()->setIPReservationsUnique(unique);
+}
+
+void
+SrvConfig::configureLowerLevelLibraries() const {
+ Option::lenient_parsing_ = lenient_option_parsing_;
+}
+
+bool
+DdnsParams::getEnableUpdates() const {
+ if (!subnet_) {
+ return (false);
+ }
+
+ return (d2_client_enabled_ && subnet_->getDdnsSendUpdates().get());
+}
+
+bool
+DdnsParams::getOverrideNoUpdate() const {
+ if (!subnet_) {
+ return (false);
+ }
+
+ return (subnet_->getDdnsOverrideNoUpdate().get());
+}
+
+bool DdnsParams::getOverrideClientUpdate() const {
+ if (!subnet_) {
+ return (false);
+ }
+
+ return (subnet_->getDdnsOverrideClientUpdate().get());
+}
+
+D2ClientConfig::ReplaceClientNameMode
+DdnsParams::getReplaceClientNameMode() const {
+ if (!subnet_) {
+ return (D2ClientConfig::RCM_NEVER);
+ }
+
+ return (subnet_->getDdnsReplaceClientNameMode().get());
+}
+
+std::string
+DdnsParams::getGeneratedPrefix() const {
+ if (!subnet_) {
+ return ("");
+ }
+
+ return (subnet_->getDdnsGeneratedPrefix().get());
+}
+
+std::string
+DdnsParams::getQualifyingSuffix() const {
+ if (!subnet_) {
+ return ("");
+ }
+
+ return (subnet_->getDdnsQualifyingSuffix().get());
+}
+
+std::string
+DdnsParams::getHostnameCharSet() const {
+ if (!subnet_) {
+ return ("");
+ }
+
+ return (subnet_->getHostnameCharSet().get());
+}
+
+std::string
+DdnsParams::getHostnameCharReplacement() const {
+ if (!subnet_) {
+ return ("");
+ }
+
+ return (subnet_->getHostnameCharReplacement().get());
+}
+
+util::str::StringSanitizerPtr
+DdnsParams::getHostnameSanitizer() const {
+ util::str::StringSanitizerPtr sanitizer;
+ if (subnet_) {
+ std::string char_set = getHostnameCharSet();
+ if (!char_set.empty()) {
+ try {
+ sanitizer.reset(new util::str::StringSanitizer(char_set,
+ getHostnameCharReplacement()));
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "hostname_char_set_: '" << char_set <<
+ "' is not a valid regular expression");
+ }
+ }
+ }
+
+ return (sanitizer);
+}
+
+bool
+DdnsParams::getUpdateOnRenew() const {
+ if (!subnet_) {
+ return (false);
+ }
+
+ return (subnet_->getDdnsUpdateOnRenew().get());
+}
+
+bool
+DdnsParams::getUseConflictResolution() const {
+ if (!subnet_) {
+ return (true);
+ }
+
+ return (subnet_->getDdnsUseConflictResolution().get());
+}
+
+util::Optional<double>
+DdnsParams::getTtlPercent() const {
+ if (!subnet_) {
+ return (util::Optional<double>());
+ }
+
+ return (subnet_->getDdnsTtlPercent());
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h
new file mode 100644
index 0000000..c3d1b57
--- /dev/null
+++ b/src/lib/dhcpsrv/srv_config.h
@@ -0,0 +1,1243 @@
+// Copyright (C) 2014-2023 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 DHCPSRV_CONFIG_H
+#define DHCPSRV_CONFIG_H
+
+#include <cc/cfg_to_element.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/cfg_globals.h>
+#include <dhcpsrv/cfg_host_operations.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <dhcpsrv/cfg_rsoo.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/cfg_mac_source.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/d2_client_cfg.h>
+#include <process/config_base.h>
+#include <hooks/hooks_config.h>
+#include <cc/data.h>
+#include <cc/user_context.h>
+#include <cc/simple_parser.h>
+#include <util/optional.h>
+#include <util/strutil.h>
+
+#include <boost/shared_ptr.hpp>
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+class CfgMgr;
+
+/// @brief Convenience container for conveying DDNS behavioral parameters
+/// It is intended to be created per Packet exchange using the selected
+/// subnet passed into functions that require them
+class DdnsParams {
+public:
+ /// @brief Default constructor
+ DdnsParams() : subnet_(), d2_client_enabled_(false) {};
+
+ /// @brief Constructor for DHPCv4 subnets
+ ///
+ /// @param subnet Pointer to Subnet4 instance to use for fetching
+ /// parameter values (typically this is the selected subnet).
+ /// @param d2_client_enabled flag which indicates whether or not
+ /// D2Client is enabled (typically the value should come from
+ /// global D2Client configuration).
+ DdnsParams(const Subnet4Ptr& subnet, bool d2_client_enabled)
+ : subnet_(boost::dynamic_pointer_cast<Subnet>(subnet)),
+ d2_client_enabled_(d2_client_enabled) {}
+
+ /// @brief Constructor for DHPCv6 subnets
+ ///
+ /// @param subnet Pointer to Subnet6 instance to use for fetching
+ /// parameter values (typically this is the selected subnet).
+ /// @param d2_client_enabled flag which indicates whether or not
+ /// D2Client is enabled (typically the value should come from
+ /// global D2Client configuration).
+ DdnsParams(const Subnet6Ptr& subnet, bool d2_client_enabled)
+ : subnet_(boost::dynamic_pointer_cast<Subnet>(subnet)),
+ d2_client_enabled_(d2_client_enabled) {}
+
+ /// @brief Returns whether or not DHCP DDNS updating is enabled.
+ /// The value is the logical AND of d2_client_enabled_ and
+ /// the value returned by subnet_'s getDdnsSendUpdates().
+ ///
+ /// @return True if updates are enabled, false otherwise or if
+ /// subnet_ is empty.
+ bool getEnableUpdates() const;
+
+ /// @brief Returns whether or not Kea should perform updates, even if
+ /// client requested no updates.
+ ///
+ /// @return The value from the subnet_ or false if subnet_ is empty.
+ bool getOverrideNoUpdate() const;
+
+ /// @brief Returns whether or not Kea should perform updates, even if
+ /// client requested delegation.
+ ///
+ /// @return The value from the subnet_ or false if subnet_ is empty.
+ bool getOverrideClientUpdate() const;
+
+ /// @brief Returns how Kea should handle the domain-name supplied by
+ /// the client.
+ ///
+ /// @return The value from the subnet_ or RCM_NEVER if subnet_ is empty.
+ D2ClientConfig::ReplaceClientNameMode getReplaceClientNameMode() const;
+
+ /// @brief Returns the Prefix Kea should use when generating domain-names.
+ ///
+ /// @return The value from the subnet_ or an empty string if subnet_ is empty.
+ std::string getGeneratedPrefix() const;
+
+ /// @brief Returns the suffix Kea should use when to qualify partial
+ /// domain-names.
+ ///
+ /// @return The value from the subnet_ or an empty string if subnet_ is empty.
+ std::string getQualifyingSuffix() const;
+
+ /// @brief Returns the regular expression describing invalid characters
+ /// for client hostnames. If empty, host name scrubbing should not be done.
+ ///
+ /// @return The value from the subnet_ or an empty string if subnet_ is empty.
+ std::string getHostnameCharSet() const;
+
+ /// @brief Returns the string to replace invalid characters when scrubbing
+ /// hostnames. Meaningful only if hostname_char_set_ is not empty.
+ ///
+ /// @return The value from the subnet_ or an empty string if subnet_ is empty.
+ std::string getHostnameCharReplacement() const;
+
+ /// @brief Returns a regular expression string sanitizer
+ ///
+ /// If the value returned by getHostnameCharSet() is not empty, then it is
+ /// used in conjunction the value returned by getHostnameCharReplacment()
+ /// (which may be empty) to create and return a StringSanitizer instance.
+ /// Otherwise it will return an empty pointer.
+ ///
+ /// @return pointer to the StringSanitizer instance or an empty pointer
+ /// @throw BadValue if the compilation fails.
+ isc::util::str::StringSanitizerPtr getHostnameSanitizer() const;
+
+ /// @brief Returns whether or not DNS should be updated when leases renew.
+ ///
+ /// If this is true, DNS should always be updated when leases are
+ /// extended (i.e. renewed/rebound) even if the DNS information
+ /// has not changed.
+ ///
+ /// @return True if updates should always be performed.
+ bool getUpdateOnRenew() const;
+
+ /// @brief Returns whether or not keah-dhcp-ddns should use conflict resolution
+ ///
+ /// This value is communicated to D2 via the NCR. When true, D2 should follow
+ /// follow conflict resolution steps described in RFC 4703. If not, it should
+ /// simple add or remove entries.
+ ///
+ /// @return True if conflict resolution should be used.
+ bool getUseConflictResolution() const;
+
+ /// @brief Returns percent of lease lifetime to use for TTL
+ ///
+ /// This value, if greater than zero, is used to calculate the lease lifetime
+ /// passed to D2 in the NCR. Otherwise the value is calculated per RFC 4702.
+ ///
+ /// @return TTL percent as an Optional.
+ util::Optional<double> getTtlPercent() const;
+
+ /// @brief Returns the subnet-id of the subnet associated with these parameters
+ ///
+ /// @return value of subnet-id (or 0 if no subnet is associated)
+ SubnetID getSubnetId() const {
+ if (subnet_) {
+ return (subnet_->getID());
+ } else {
+ return (0);
+ }
+ }
+
+private:
+ /// @brief Subnet from which values should be fetched.
+ SubnetPtr subnet_;
+
+ /// @brief Flag indicating whether or not the D2Client is enabled.
+ bool d2_client_enabled_;
+};
+
+/// @brief Defines a pointer for DdnsParams instances.
+typedef boost::shared_ptr<DdnsParams> DdnsParamsPtr;
+
+/// @brief Specifies current DHCP configuration
+///
+/// @todo Migrate all other configuration parameters from cfgmgr.h here
+class SrvConfig : public process::ConfigBase {
+public:
+ /// @name Constants for selection of parameters returned by @c getConfigSummary
+ ///
+ //@{
+ /// Nothing selected
+ static const uint32_t CFGSEL_NONE = 0x00000000;
+ /// Number of IPv4 subnets
+ static const uint32_t CFGSEL_SUBNET4 = 0x00000001;
+ /// Number of IPv6 subnets
+ static const uint32_t CFGSEL_SUBNET6 = 0x00000002;
+ /// Number of enabled ifaces
+ static const uint32_t CFGSEL_IFACE4 = 0x00000004;
+ /// Number of v6 ifaces
+ static const uint32_t CFGSEL_IFACE6 = 0x00000008;
+ /// DDNS enabled/disabled
+ static const uint32_t CFGSEL_DDNS = 0x00000010;
+ /// Number of all subnets
+ static const uint32_t CFGSEL_SUBNET = 0x00000003;
+ /// Configured globals
+ static const uint32_t CFGSEL_GLOBALS = 0x00000020;
+ /// Config control info
+ static const uint32_t CFGSEL_CFG_CTL = 0x00000040;
+ /// IPv4 related config
+ static const uint32_t CFGSEL_ALL4 = 0x00000035;
+ /// IPv6 related config
+ static const uint32_t CFGSEL_ALL6 = 0x0000003A;
+ /// Whole config
+ static const uint32_t CFGSEL_ALL = 0xFFFFFFFF;
+ //@}
+
+ /// @brief Default constructor.
+ ///
+ /// This constructor sets configuration sequence number to 0.
+ SrvConfig();
+
+ /// @brief Constructor.
+ ///
+ /// Sets arbitrary configuration sequence number.
+ ///
+ /// @param sequence The configuration sequence.
+ SrvConfig(const uint32_t sequence);
+
+ /// @brief Returns summary of the configuration in the textual format.
+ ///
+ /// This method returns the brief text describing the current configuration.
+ /// It may be used for logging purposes, e.g. when the new configuration is
+ /// committed to notify a user about the changes in configuration.
+ ///
+ /// @todo Currently this method uses @c CfgMgr accessors to get the
+ /// configuration parameters. Once these parameters are migrated from the
+ /// @c CfgMgr this method will have to be modified accordingly.
+ ///
+ /// @todo Implement reporting a summary of interfaces being used for
+ /// receiving and sending DHCP messages. This will be implemented with
+ /// ticket #3512.
+ ///
+ /// @param selection Is a bitfield which describes the parts of the
+ /// configuration to be returned.
+ ///
+ /// @return Summary of the configuration in the textual format.
+ std::string getConfigSummary(const uint32_t selection) const;
+
+ /// @brief Returns configuration sequence number.
+ ///
+ /// @return The configuration sequence number.
+ uint32_t getSequence() const {
+ return (sequence_);
+ }
+
+ /// @brief Compares configuration sequence with other sequence.
+ ///
+ /// This method compares sequence numbers of two configurations for
+ /// equality. The sequence numbers are meant to be unique, so if
+ /// they are equal it means that they point to the same configuration.
+ ///
+ /// @param other Configuration which sequence number should be
+ /// compared with the sequence number of this configuration.
+ ///
+ /// @return true if sequence numbers are equal.
+ bool sequenceEquals(const SrvConfig& other);
+
+ /// @brief Returns non-const pointer to interface configuration.
+ ///
+ /// This function returns a non-const pointer to the interface
+ /// configuration.
+ ///
+ /// @return Object representing configuration of interfaces.
+ CfgIfacePtr getCfgIface() {
+ return (cfg_iface_);
+ }
+
+ /// @brief Returns const pointer to interface configuration.
+ ///
+ /// This function returns a const pointer to the interface
+ /// configuration.
+ ///
+ /// @return Object representing configuration of interfaces.
+ ConstCfgIfacePtr getCfgIface() const {
+ return (cfg_iface_);
+ }
+
+ /// @brief Return pointer to non-const object representing user-defined
+ /// option definitions.
+ ///
+ /// This function returns a pointer to the object which represents the
+ /// user defined option definitions grouped by option space name.
+ ///
+ /// @return Pointer to an object holding option definitions.
+ CfgOptionDefPtr getCfgOptionDef() {
+ return (cfg_option_def_);
+ }
+
+ /// @brief Returns pointer to the const object representing user-defined
+ /// option definitions.
+ ///
+ /// This function returns a pointer to the object which represents the
+ /// user defined option definitions grouped by option space name.
+ ///
+ /// @return Pointer to an object holding option definitions.
+ ConstCfgOptionDefPtr getCfgOptionDef() const {
+ return (cfg_option_def_);
+ }
+
+ /// @brief Returns pointer to the non-const object holding options.
+ ///
+ /// This method returns a pointer to the object which holds instances
+ /// of the options to be returned to the clients belonging to any subnet.
+ ///
+ /// @return Pointer to the object holding options.
+ CfgOptionPtr getCfgOption() {
+ return (cfg_option_);
+ }
+
+ /// @brief Returns pointer to the const object holding options.
+ ///
+ /// This method returns a pointer to the object which holds instances
+ /// of the options to be returned to the clients belonging to any subnet.
+ ///
+ /// @return Pointer to the object holding options.
+ const ConstCfgOptionPtr getCfgOption() const {
+ return (cfg_option_);
+ }
+
+ /// @brief Returns pointer to non-const object holding subnets configuration
+ /// for DHCPv4.
+ ///
+ /// @return Pointer to the object holding subnets configuration for DHCPv4.
+ CfgSubnets4Ptr getCfgSubnets4() {
+ return (cfg_subnets4_);
+ }
+
+ /// @brief Returns pointer to non-const object holding configuration of
+ /// shared networks in DHCPv4;
+ ///
+ /// @return Pointer to the object holding shared networks configuration
+ /// for DHCPv4.
+ CfgSharedNetworks4Ptr getCfgSharedNetworks4() const {
+ return (cfg_shared_networks4_);
+ }
+
+ /// @brief Returns pointer to non-const object holding configuration of
+ /// shared networks in DHCPv6.
+ ///
+ /// @return Pointer to the object holding shared networks configuration
+ /// for DHCPv6.
+ CfgSharedNetworks6Ptr getCfgSharedNetworks6() const {
+ return (cfg_shared_networks6_);
+ }
+
+ /// @brief Returns pointer to const object holding subnets configuration for
+ /// DHCPv4.
+ ///
+ /// @return Pointer to the object holding subnets configuration for DHCPv4.
+ ConstCfgSubnets4Ptr getCfgSubnets4() const {
+ return (cfg_subnets4_);
+ }
+
+ /// @brief Returns pointer to non-const object holding subnets configuration
+ /// for DHCPv6.
+ ///
+ /// @return Pointer to the object holding subnets configuration for DHCPv6.
+ CfgSubnets6Ptr getCfgSubnets6() {
+ return (cfg_subnets6_);
+ }
+
+ /// @brief Returns pointer to const object holding subnets configuration for
+ /// DHCPv6.
+ ///
+ /// @return Pointer to the object holding subnets configuration for DHCPv6.
+ ConstCfgSubnets6Ptr getCfgSubnets6() const {
+ return (cfg_subnets6_);
+ }
+
+ /// @brief Returns pointer to the non-const objects representing host
+ /// reservations for different IPv4 and IPv6 subnets.
+ ///
+ /// @return Pointer to the non-const object holding host reservations.
+ CfgHostsPtr getCfgHosts() {
+ return (cfg_hosts_);
+ }
+
+ /// @brief Returns pointer to the const objects representing host
+ /// reservations for different IPv4 and IPv6 subnets.
+ ///
+ /// @return Pointer to the const object holding host reservations.
+ ConstCfgHostsPtr getCfgHosts() const {
+ return (cfg_hosts_);
+ }
+
+ /// @brief Returns pointer to the non-const object representing
+ /// set of RSOO-enabled options.
+ ///
+ /// @return Pointer to the non-const object holding RSOO-enabled
+ /// options.
+ CfgRSOOPtr getCfgRSOO() {
+ return (cfg_rsoo_);
+ }
+
+ /// @brief Returns pointer to the const object representing set
+ /// of RSOO-enabled options.
+ ///
+ /// @return Pointer to the const object holding RSOO-enabled options.
+ ConstCfgRSOOPtr getCfgRSOO() const {
+ return (cfg_rsoo_);
+ }
+
+ /// @brief Returns pointer to the object holding configuration pertaining
+ /// to processing expired leases.
+ ///
+ /// @return Pointer to the object holding configuration pertaining to
+ /// processing expired leases.
+ CfgExpirationPtr getCfgExpiration() {
+ return (cfg_expiration_);
+ }
+
+ /// @brief Returns pointer to the const object holding configuration
+ /// pertaining to processing expired leases.
+ ///
+ /// @return Pointer to the const object holding configuration pertaining to
+ /// processing expired leases.
+ ConstCfgExpirationPtr getCfgExpiration() const {
+ return (cfg_expiration_);
+ }
+
+ /// @brief Returns pointer to the object holding configuration of the
+ /// server identifier.
+ ///
+ /// @return Pointer to the object holding configuration of the server
+ /// identifier.
+ CfgDUIDPtr getCfgDUID() {
+ return (cfg_duid_);
+ }
+
+ /// @brief Returns const pointer to the object holding configuration
+ /// of the server identifier.
+ ///
+ /// @return Const pointer to the object holding configuration of the server
+ /// identifier.
+ ConstCfgDUIDPtr getCfgDUID() const {
+ return (cfg_duid_);
+ }
+
+ /// @brief Returns pointer to the object holding configuration of the
+ /// lease and host database connection parameters.
+ ///
+ /// @return Pointer to the object holding configuration of the lease and
+ /// host database connection parameters.
+ CfgDbAccessPtr getCfgDbAccess() {
+ return (cfg_db_access_);
+ }
+
+ /// @brief Returns const pointer to the object holding configuration of
+ /// the lease and host database connection parameters.
+ ///
+ /// @return Const pointer to the object holding configuration of the lease
+ /// and host database connection parameters.
+ ConstCfgDbAccessPtr getCfgDbAccess() const {
+ return (cfg_db_access_);
+ }
+
+ /// @brief Returns pointer to the object holding general configuration
+ /// for host reservations in DHCPv4.
+ ///
+ /// @return Pointer to the object holding general configuration for host
+ /// reservations in DHCPv4.
+ CfgHostOperationsPtr getCfgHostOperations4() {
+ return (cfg_host_operations4_);
+ }
+
+ /// @brief Returns const pointer to the object holding general configuration
+ /// for host reservations in DHCPv4.
+ ///
+ /// @return Const pointer to the object holding general configuration for
+ /// host reservations in DHCPv4.
+ ConstCfgHostOperationsPtr getCfgHostOperations4() const {
+ return (cfg_host_operations4_);
+ }
+
+ /// @brief Returns pointer to the object holding general configuration
+ /// for host reservations in DHCPv6.
+ ///
+ /// @return Pointer to the object holding general configuration for host
+ /// reservations in DHCPv6.
+ CfgHostOperationsPtr getCfgHostOperations6() {
+ return (cfg_host_operations6_);
+ }
+
+ /// @brief Returns const pointer to the object holding general configuration
+ /// for host reservations in DHCPv6.
+ ///
+ /// @return Const pointer to the object holding general configuration for
+ /// host reservations in DHCPv6.
+ ConstCfgHostOperationsPtr getCfgHostOperations6() const {
+ return (cfg_host_operations6_);
+ }
+
+ /// @brief Returns non-const pointer to object holding sanity checks flags
+ ///
+ /// @return Pointer to object holding sanity checks flags
+ CfgConsistencyPtr getConsistency() {
+ return (cfg_consist_);
+ }
+
+ /// @brief Returns const pointer to object holding sanity checks flags
+ ///
+ /// @return Const pointer to object holding sanity checks flags
+ ConstCfgConsistencyPtr getConsistency() const {
+ return (cfg_consist_);
+ }
+
+ //@}
+
+ /// @brief Returns non-const reference to an array that stores
+ /// MAC/hardware address sources.
+ ///
+ /// @return non-const reference to MAC/hardware address sources
+ CfgMACSource& getMACSources() {
+ return (cfg_mac_source_);
+ }
+
+ /// @brief Returns const reference to an array that stores
+ /// MAC/hardware address sources.
+ ///
+ /// @return const reference to MAC/hardware address sources
+ const CfgMACSource& getMACSources() const {
+ return (cfg_mac_source_);
+ }
+
+ /// @brief Returns information about control socket
+ ///
+ /// @return pointer to the Element that holds control-socket map
+ const isc::data::ConstElementPtr getControlSocketInfo() const {
+ return (control_socket_);
+ }
+
+ /// @brief Sets information about the control socket
+ ///
+ /// @param control_socket Element that holds control-socket map
+ void setControlSocketInfo(const isc::data::ConstElementPtr& control_socket) {
+ control_socket_ = control_socket;
+ }
+
+ /// @brief Returns DHCP queue control information
+ ///
+ /// @return pointer to the DHCP queue control information
+ const isc::data::ConstElementPtr getDHCPQueueControl() const {
+ return (dhcp_queue_control_);
+ }
+
+ /// @brief Sets information about the dhcp queue control
+ ///
+ /// @param dhcp_queue_control new dhcp queue control information
+ void setDHCPQueueControl(const isc::data::ConstElementPtr dhcp_queue_control) {
+ dhcp_queue_control_ = dhcp_queue_control;
+ }
+
+ /// @brief Returns DHCP multi threading information
+ ///
+ /// @return pointer to the DHCP multi threading information
+ const isc::data::ConstElementPtr getDHCPMultiThreading() const {
+ return (dhcp_multi_threading_);
+ }
+
+ /// @brief Sets information about the dhcp multi threading
+ ///
+ /// @param dhcp_multi_threading new dhcp multi threading information
+ void setDHCPMultiThreading(const isc::data::ConstElementPtr dhcp_multi_threading) {
+ dhcp_multi_threading_ = dhcp_multi_threading;
+ }
+
+ /// @brief Returns pointer to the dictionary of global client
+ /// class definitions
+ ///
+ /// @return Pointer to the dictionary of global client class definitions
+ ClientClassDictionaryPtr getClientClassDictionary() {
+ return (class_dictionary_);
+ }
+
+ /// @brief Returns pointer to const dictionary of global client
+ /// class definitions
+ ///
+ /// @return Pointer to const dictionary of global client class definitions
+ const ClientClassDictionaryPtr getClientClassDictionary() const {
+ return (class_dictionary_);
+ }
+
+ /// @brief Sets the client class dictionary
+ ///
+ /// @param dictionary pointer to the new class dictionary
+ void setClientClassDictionary(const ClientClassDictionaryPtr& dictionary) {
+ class_dictionary_ = dictionary;
+ }
+
+ /// @brief Returns non-const reference to configured hooks libraries.
+ ///
+ /// @return non-const reference to configured hooks libraries.
+ isc::hooks::HooksConfig& getHooksConfig() {
+ return (hooks_config_);
+ }
+
+ /// @brief Returns const reference to configured hooks libraries.
+ ///
+ /// @return const reference to configured hooks libraries.
+ const isc::hooks::HooksConfig& getHooksConfig() const {
+ return (hooks_config_);
+ }
+
+ /// @brief Fetches the DDNS parameters for a given DHCPv4 subnet.
+ ///
+ /// Creates a DdnsParams structure which retain and thereafter
+ /// use the given subnet to fetch DDNS behavioral parameters.
+ /// The values are fetched with the inheritance scope mode
+ /// of Network::ALL.
+ ///
+ /// @param subnet DHCPv4 Subnet for which DDNS parameters are desired.
+ /// @return pointer to DddnParams instance
+ DdnsParamsPtr getDdnsParams(const Subnet4Ptr& subnet) const;
+
+ /// @brief Fetches the DDNS parameters for a given DHCPv6 subnet.
+ ///
+ /// Creates a DdnsParams structure which retain and thereafter
+ /// use the given subnet to fetch DDNS behavioral parameters.
+ /// The values are fetched with the inheritance scope mode
+ /// of Network::ALL.
+ ///
+ /// @param subnet DHCPv6 Subnet for which DDNS parameters are desired.
+ /// @return pointer to DddnParams instance
+ DdnsParamsPtr getDdnsParams(const Subnet6Ptr& subnet) const;
+
+ /// @brief Copies the current configuration to a new configuration.
+ ///
+ /// This method copies the parameters stored in the configuration to
+ /// an object passed as parameter. The configuration sequence is not
+ /// copied.
+ ///
+ /// @warning Some of the configuration objects are not copied at
+ /// this point, e.g. subnets. This is because they contain quite complex
+ /// data structures and they make use of pointers, so in many cases
+ /// the default copy constructors can't be used. Implementing this
+ /// requires quite a lot of time so this is left as is for now.
+ /// The lack of ability to copy the entire configuration makes
+ /// revert function of the @c CfgMgr unusable.
+ ///
+ /// @param [out] new_config An object to which the configuration will
+ /// be copied.
+ void copy(SrvConfig& new_config) const;
+
+ /// @name Methods and operators used to compare configurations.
+ ///
+ //@{
+ ///
+ /// @brief Compares two objects for equality.
+ ///
+ /// It ignores the configuration sequence number when checking for
+ /// equality of objects.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if two objects are equal, false otherwise.
+ bool equals(const SrvConfig& other) const;
+
+ /// @brief Compares two objects for inequality.
+ ///
+ /// It ignores the configuration sequence number when checking for
+ /// inequality of objects.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if two objects are not equal, false otherwise.
+ bool nequals(const SrvConfig& other) const {
+ return (!equals(other));
+ }
+
+ /// @brief Equality operator.
+ ///
+ /// It ignores the configuration sequence number when checking for
+ /// equality of objects.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if two objects are equal, false otherwise.
+ bool operator==(const SrvConfig& other) const {
+ return (equals(other));
+ }
+
+ /// @brief other An object to be compared with this object.
+ ///
+ /// It ignores the configuration sequence number when checking for
+ /// inequality of objects.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if two objects are not equal, false otherwise.
+ bool operator!=(const SrvConfig& other) const {
+ return (nequals(other));
+ }
+
+ //@}
+
+ /// @brief Merges the configuration specified as a parameter into
+ /// this configuration.
+ ///
+ /// This method is used when two or more configurations held in the
+ /// @c SrvConfig objects need to be combined into a single configuration.
+ /// Specifically, when the configuration backend is used, then part of
+ /// the server configuration comes from the configuration file and
+ /// stored in the staging configuration. The other part of the
+ /// configuration comes from the database. The configuration fetched
+ /// from the database is stored in a separate @c SrvConfig instance
+ /// and then merged into the staging configuration prior to committing
+ /// it.
+ ///
+ /// The merging strategy depends on the underlying data being merged.
+ /// For example: subnets are merged using the algorithm implemented
+ /// in the @c CfgSubnets4. Other data structures are merged using the
+ /// algorithms implemented in their respective configuration
+ /// containers.
+ ///
+ /// The general rule is that the configuration data from the @c other
+ /// object replaces configuration data held in this object instance.
+ /// The data that do not overlap between the two objects is simply
+ /// inserted into this configuration.
+ ///
+ /// Due to the nature of the client classes, i.e. they are ordered and
+ /// depend on each other, individual classes are not merged. Instead,
+ /// the new list of classes entirely replaces the existing list. It
+ /// implies that client classes should not be defined in a config
+ /// file if there are classes defined in the config backend for this
+ /// server.
+ ///
+ /// @warning The call to @c merge may modify the data in the @c other
+ /// object. Therefore, the caller must not rely on the data held
+ /// in the @c other object after the call to @c merge. Also, the
+ /// data held in @c other must not be modified after the call to
+ /// @c merge because it may affect the merged configuration.
+ ///
+ /// The @c other parameter must be a @c SrvConfig or its derivation.
+ ///
+ /// This method calls either @c merge4 or @c merge6 based on
+ ///
+ /// Currently, the following parts of the configuration are merged:
+ /// - globals
+ /// - option definitions
+ /// - options
+ /// - client classes
+ /// - via @c merge4 or @c merge6 depending on @c CfgMgr::family_:
+ /// - shared networks
+ /// - subnets
+ ///
+ /// @todo Add support for merging other configuration elements.
+ ///
+ /// @param other An object holding the configuration to be merged
+ /// into this configuration.
+ virtual void merge(ConfigBase& other);
+
+ /// @brief Updates statistics.
+ ///
+ /// This method calls appropriate methods in child objects that update
+ /// related statistics. See @ref CfgSubnets4::updateStatistics and
+ /// @ref CfgSubnets6::updateStatistics for details.
+ void updateStatistics();
+
+ /// @brief Removes statistics.
+ ///
+ /// This method calls appropriate methods in child objects that remove
+ /// related statistics. See @ref CfgSubnets4::removeStatistics and
+ /// @ref CfgSubnets6::removeStatistics for details.
+ void removeStatistics();
+
+ /// @brief Sets decline probation-period
+ ///
+ /// Probation-period is the timer, expressed, in seconds, that specifies how
+ /// long a lease is unavailable after reported as declined.
+ ///
+ /// @param decline_timer number of seconds after declined lease is restored
+ void setDeclinePeriod(const uint32_t decline_timer) {
+ decline_timer_ = decline_timer;
+ }
+
+ /// @brief Returns probation-period
+ ///
+ /// See @ref setDeclinePeriod for brief discussion.
+ ///
+ /// @return value of probation-period, expressed in seconds
+ uint32_t getDeclinePeriod() const {
+ return (decline_timer_);
+ }
+
+ /// @brief Sets whether server should send back client-id in DHCPv4
+ ///
+ /// This is a compatibility flag. The default (true) is compliant with
+ /// RFC6842. False is for backward compatibility.
+ ///
+ /// @param echo should the client-id be sent or not
+ void setEchoClientId(const bool echo) {
+ echo_v4_client_id_ = echo;
+ }
+
+ /// @brief Returns whether server should send back client-id in DHCPv4.
+ ///
+ /// @return true if client-id should be returned, false otherwise.
+ bool getEchoClientId() const {
+ return (echo_v4_client_id_);
+ }
+
+ /// @brief Sets DHCP4o6 IPC port
+ ///
+ /// DHCPv4-over-DHCPv6 uses a UDP socket for inter-server communication,
+ /// this socket is bound and connected to this port and port + 1
+ ///
+ /// @param port port and port + 1 to use
+ void setDhcp4o6Port(uint16_t port) {
+ dhcp4o6_port_ = port;
+ }
+
+ /// @brief Returns DHCP4o6 IPC port
+ ///
+ /// See @ref setDhcp4o6Port for brief discussion.
+ ///
+ /// @return value of DHCP4o6 IPC port
+ uint16_t getDhcp4o6Port() const {
+ return (dhcp4o6_port_);
+ }
+
+ /// @brief Returns pointer to the D2 client configuration
+ ///
+ /// @return Pointer to the D2 client configuration
+ D2ClientConfigPtr getD2ClientConfig() {
+ return (d2_client_config_);
+ }
+
+ /// @brief Returns pointer to const D2 client configuration
+ ///
+ /// @return Pointer to const D2 client configuration
+ const D2ClientConfigPtr getD2ClientConfig() const {
+ return (d2_client_config_);
+ }
+
+ /// @brief Sets the D2 client configuration
+ ///
+ /// @param d2_client_config pointer to the new D2 client configuration
+ void setD2ClientConfig(const D2ClientConfigPtr& d2_client_config) {
+ d2_client_config_ = d2_client_config;
+ }
+
+ /// @brief Returns non-const pointer to configured global parameters.
+ ///
+ /// This function returns a non-const pointer to the configured
+ /// global parameters.
+ ///
+ /// @return Object representing configured global parameters.
+ CfgGlobalsPtr getConfiguredGlobals() {
+ return (configured_globals_);
+ }
+
+ /// @brief Returns const pointer to configured global parameters.
+ ///
+ /// This function returns a const pointer to the configured
+ /// global parameters.
+ ///
+ /// @return Object representing configured global parameters.
+ ConstCfgGlobalsPtr getConfiguredGlobals() const {
+ return (configured_globals_);
+ }
+
+ /// @brief Returns pointer to a given configured global parameter
+ ///
+ /// @param name Name of the parameter to fetch.
+ /// @return Pointer to the parameter if it exists, otherwise an
+ /// empty pointer.
+ isc::data::ConstElementPtr getConfiguredGlobal(std::string name) const {
+ return (configured_globals_->get(name));
+ }
+
+ /// @brief Returns pointer to a given configured global parameter
+ ///
+ /// @param index Index of the parameter to fetch.
+ /// @return Pointer to the parameter if it exists, otherwise an
+ /// empty pointer.
+ isc::data::ConstElementPtr getConfiguredGlobal(int index) const {
+ return (configured_globals_->get(index));
+ }
+
+ /// @brief Removes all configured global parameters.
+ ///
+ /// @note This removes the default values too so either
+ /// @c applyDefaultsConfiguredGlobals and @c mergeGlobals,
+ /// or @c isc::data::SimpleParser::setDefaults and
+ /// @c extractConfiguredGlobals should be called after.
+ void clearConfiguredGlobals() {
+ configured_globals_->clear();
+ }
+
+ /// @brief Applies defaults to global parameters.
+ ///
+ /// @param defaults vector of (name, type, value) defaults to apply.
+ void applyDefaultsConfiguredGlobals(const isc::data::SimpleDefaults& defaults);
+
+ /// @brief Saves scalar elements from the global scope of a configuration.
+ void extractConfiguredGlobals(isc::data::ConstElementPtr config);
+
+ /// @brief Adds a parameter to the collection configured globals
+ ///
+ /// @param name std::string name of the global to add.
+ /// @param value ElementPtr containing the value of the global.
+ void addConfiguredGlobal(const std::string& name, isc::data::ConstElementPtr value) {
+ configured_globals_->set(name, value);
+ }
+
+ /// @brief Conducts sanity checks on global lifetime parameters.
+ ///
+ /// @param name Base name of the lifetime parameter.
+ void sanityChecksLifetime(const std::string& name) const;
+
+ /// @brief Conducts sanity checks on global lifetime parameters
+ /// before merge from the external configuration to the target one.
+ ///
+ /// @param target_config Target configuration.
+ /// @param name Base name of the lifetime parameter.
+ void sanityChecksLifetime(const SrvConfig& target_config,
+ const std::string& name) const;
+
+ /// @brief Moves deprecated parameters from dhcp-ddns element to global element
+ ///
+ /// Given a server configuration element map, the following parameters are moved
+ /// from dhcp-ddns to top-level (i.e. global) element if they do not already
+ /// exist there:
+ ///
+ /// @code
+ /// From dhcp-ddns: To (global):
+ /// ------------------------------------------------------
+ /// override-no-update ddns-override-no-update
+ /// override-client-update ddns-override-client-update
+ /// replace-client-name ddns-replace-client-name
+ /// generated-prefix ddns-generated-prefix
+ /// qualifying-suffix ddns-qualifying-suffix
+ /// hostname-char-set hostname-char-set
+ /// hostname-char-replacement hostname-char-replacement
+ /// @endcode
+ ///
+ /// Note that the whether or not the deprecated parameters are added
+ /// to the global element, they are always removed from the dhcp-ddns
+ /// element.
+ ///
+ /// @param srv_elem server top level map to alter
+ static void moveDdnsParams(isc::data::ElementPtr srv_elem);
+
+ /// @brief Configures the server to allow or disallow specifying multiple
+ /// hosts with the same IP address/subnet.
+ ///
+ /// This setting is applied in @c CfgDbAccess and @c CfgHosts. This function
+ /// should be called when the server is being configured using the configuration
+ /// file, config-set command or via the configuration backend.
+ ///
+ /// @param unique Boolean value indicating if it is allowed (when false)
+ /// or disallowed to specify multiple hosts with the same IP reservation.
+ void setIPReservationsUnique(const bool unique);
+
+ /// @brief Sets whether the server does host reservations lookup before lease
+ /// lookup.
+ ///
+ /// @param first Boolean value indicating if host reservations lookup should
+ /// be performed before lease lookup.
+ void setReservationsLookupFirst(const bool first) {
+ reservations_lookup_first_ = first;
+ }
+
+ /// @brief Returns whether the server does host reservations lookup before
+ /// lease lookup.
+ ///
+ /// @return Boolean value indicating if host reservations lookup should be
+ /// performed before lease lookup.
+ bool getReservationsLookupFirst() const {
+ return (reservations_lookup_first_);
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Set lenient option parsing compatibility flag.
+ ///
+ /// @param value the boolean value to be set when configuring lenient option
+ /// parsing
+ void setLenientOptionParsing(bool const value) {
+ lenient_option_parsing_ = value;
+ }
+
+ /// @brief Get lenient option parsing compatibility flag.
+ ///
+ /// @return the configured value for lenient option parsing
+ bool getLenientOptionParsing() const {
+ return lenient_option_parsing_;
+ }
+
+ /// @brief Set ignore DHCP Server Identifier compatibility flag.
+ ///
+ /// @param value the boolean value to be set when configuring DHCP
+ /// Server Identifier usage preferences.
+ void setIgnoreServerIdentifier(bool const value) {
+ ignore_dhcp_server_identifier_ = value;
+ }
+
+ /// @brief Get ignore DHCP Server Identifier compatibility flag.
+ ///
+ /// @return the configured value for DHCP Server Identifier usage preferences.
+ bool getIgnoreServerIdentifier() const {
+ return (ignore_dhcp_server_identifier_);
+ }
+
+ /// @brief Set ignore RAI Link Selection compatibility flag.
+ ///
+ /// @param value the boolean value to be set when configuring RAI Link
+ /// Selection usage preferences
+ void setIgnoreRAILinkSelection(bool const value) {
+ ignore_rai_link_selection_ = value;
+ }
+
+ /// @brief Get ignore RAI Link Selection compatibility flag.
+ ///
+ /// @return the configured value for RAI Link Selection usage preferences
+ bool getIgnoreRAILinkSelection() const {
+ return ignore_rai_link_selection_;
+ }
+
+ /// @brief Set exclude .0 and .255 addresses in subnets bigger than /24 flag.
+ ///
+ /// @param value the boolean value to be set when excluding .0 .255 from
+ /// subnets bigger than /24.
+ void setExcludeFirstLast24(bool const value) {
+ exclude_first_last_24_ = value;
+ }
+
+ /// @brief Get exclude .0 and .255 addresses in subnets bigger than /24 flag.
+ ///
+ /// @return the configured value for exclude .0 and .255 flag.
+ bool getExcludeFirstLast24() const {
+ return exclude_first_last_24_;
+ }
+
+ /// @brief Convenience method to propagate configuration parameters through
+ /// inversion of control.
+ ///
+ /// To be used as a last resort when CfgMgr::instance().getCurrentCfg()
+ /// can't be easily called from where the configuration parameter is used,
+ /// usually because that particular library is lower in the dependency tree.
+ /// Happens on configuration commit.
+ void configureLowerLevelLibraries() const;
+
+private:
+
+ /// @brief Merges the DHCPv4 configuration specified as a parameter into
+ /// this configuration.
+ ///
+ /// This is called by @c merge() to handle v4 specifics, such as
+ /// networks and subnets.
+ ///
+ /// @param other An object holding the configuration to be merged
+ /// into this configuration.
+ void merge4(SrvConfig& other);
+
+ /// @brief Merges the DHCPv6 configuration specified as a parameter into
+ /// this configuration.
+ ///
+ /// This is called by @c merge() to handle v4 specifics, such as
+ /// networks and subnets.
+ ///
+ /// @param other An object holding the configuration to be merged
+ /// into this configuration.
+ void merge6(SrvConfig& other);
+
+ /// @brief Merges the globals specified in the given configuration
+ /// into this configuration.
+ ///
+ /// Configurable global values may be specified either via JSON
+ /// configuration (e.g. "echo-client-id":true) or as global parameters
+ /// within a configuration back end. Regardless of the source, these
+ /// values once provided, are stored in @c SrvConfig::configured_globals_.
+ /// Any such value that does not have an explicit specification should be
+ /// considered "unspecified" at the global scope.
+ ///
+ /// This function adds the configured globals from the "other" config
+ /// into this config's configured globals. If a value already exists
+ /// in this config, it will be overwritten with the value from the
+ /// "other" config.
+ ///
+ /// It then iterates over this merged list of globals, setting
+ /// any of the corresponding SrvConfig members that map to a
+ /// a configurable parameter (e.g. c@ SrvConfig::echo_client_id_,
+ /// @c SrvConfig::server_tag_).
+ ///
+ /// @param other An object holding the configuration to be merged
+ /// into this configuration.
+ void mergeGlobals(SrvConfig& other);
+
+ /// @brief Sequence number identifying the configuration.
+ uint32_t sequence_;
+
+ /// @brief Interface configuration.
+ ///
+ /// Used to select interfaces on which the DHCP server will listen to
+ /// queries.
+ CfgIfacePtr cfg_iface_;
+
+ /// @brief Pointer to option definitions configuration.
+ ///
+ /// This object holds the user-defined option definitions grouped
+ /// by option space name.
+ CfgOptionDefPtr cfg_option_def_;
+
+ /// @brief Pointer to options (data) configuration.
+ ///
+ /// This object holds the instances of the options to be sent to clients
+ /// connected to any subnet.
+ CfgOptionPtr cfg_option_;
+
+ /// @brief Pointer to subnets configuration for IPv4.
+ CfgSubnets4Ptr cfg_subnets4_;
+
+ /// @brief Pointer to subnets configuration for IPv6.
+ CfgSubnets6Ptr cfg_subnets6_;
+
+ /// @brief Pointer to IPv4 shared networks configuration.
+ CfgSharedNetworks4Ptr cfg_shared_networks4_;
+
+ /// @brief Pointer to IPv4 shared networks configuration.
+ CfgSharedNetworks6Ptr cfg_shared_networks6_;
+
+ /// @brief Pointer to the configuration for hosts reservation.
+ ///
+ /// This object holds a list of @c Host objects representing host
+ /// reservations for different IPv4 and IPv6 subnets.
+ CfgHostsPtr cfg_hosts_;
+
+ /// @brief A list of configured MAC sources.
+ CfgMACSource cfg_mac_source_;
+
+ /// @brief Pointer to the configuration for RSOO-enabled options.
+ ///
+ /// This object holds a set of RSOO-enabled options. See
+ /// RFC 6422 for the definition of the RSOO-enabled option.
+ CfgRSOOPtr cfg_rsoo_;
+
+ /// @brief Pointer to the configuration pertaining to processing of
+ /// expired leases.
+ CfgExpirationPtr cfg_expiration_;
+
+ /// @brief Pointer to the configuration of the server identifier.
+ CfgDUIDPtr cfg_duid_;
+
+ /// @brief Pointer to the configuration of the lease and host database
+ /// connection parameters.
+ CfgDbAccessPtr cfg_db_access_;
+
+ /// @brief Pointer to the general configuration for host reservations in
+ /// DHCPv4.
+ CfgHostOperationsPtr cfg_host_operations4_;
+
+ /// @brief Pointer to the general configuration for host reservations in
+ /// DHCPv6.
+ CfgHostOperationsPtr cfg_host_operations6_;
+
+ /// @brief Pointer to the control-socket information
+ isc::data::ConstElementPtr control_socket_;
+
+ /// @brief Pointer to the dhcp-queue-control information
+ isc::data::ConstElementPtr dhcp_queue_control_;
+
+ /// @brief Pointer to the multi-threading information
+ isc::data::ConstElementPtr dhcp_multi_threading_;
+
+ /// @brief Pointer to the dictionary of global client class definitions
+ ClientClassDictionaryPtr class_dictionary_;
+
+ /// @brief Configured hooks libraries.
+ isc::hooks::HooksConfig hooks_config_;
+
+ /// @brief Decline Period time
+ ///
+ /// This timer specifies decline probation period, the time after a declined
+ /// lease is recovered back to available state. Expressed in seconds.
+ uint32_t decline_timer_;
+
+ /// @brief Indicates whether v4 server should send back client-id
+ bool echo_v4_client_id_;
+
+ /// @brief DHCP4o6 IPC port
+ ///
+ /// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication,
+ /// this socket is bound and connected to this port and port + 1
+ uint16_t dhcp4o6_port_;
+
+ /// @brief Stores D2 client configuration
+ D2ClientConfigPtr d2_client_config_;
+
+ /// @brief Stores the global parameters specified via configuration
+ CfgGlobalsPtr configured_globals_;
+
+ /// @brief Pointer to the configuration consistency settings
+ CfgConsistencyPtr cfg_consist_;
+
+ /// @name Compatibility flags
+ ///
+ //@{
+ /// @brief Indicates whether lenient option parsing is enabled
+ bool lenient_option_parsing_;
+ /// @brief Indicates whether DHCP server identifier option will be ignored
+ bool ignore_dhcp_server_identifier_;
+ /// @brief Indicates whether RAI link-selection suboptions will be ignored
+ bool ignore_rai_link_selection_;
+ /// @brief Indicates whether exclude .0 .255 from subnets bigger than /24.
+ bool exclude_first_last_24_;
+ //@}
+
+ /// @brief Flag which indicates if the server should do host reservations
+ /// lookup before lease lookup. This parameter has effect only when
+ /// multi-threading is disabled. If multi-threading is enabled, host
+ /// reservations lookup is always performed first.
+ /// It default to false when multi-threading is disabled.
+ bool reservations_lookup_first_;
+};
+
+/// @name Pointers to the @c SrvConfig object.
+///
+//@{
+/// @brief Non-const pointer to the @c SrvConfig.
+typedef boost::shared_ptr<SrvConfig> SrvConfigPtr;
+
+/// @brief Const pointer to the @c SrvConfig.
+typedef boost::shared_ptr<const SrvConfig> ConstSrvConfigPtr;
+//@}
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_CONFIG_H
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
new file mode 100644
index 0000000..2e480d4
--- /dev/null
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -0,0 +1,952 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <asiolink/addr_utilities.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/iterative_allocation_state.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/random_allocation_state.h>
+#include <dhcpsrv/random_allocator.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/make_shared.hpp>
+
+#include <algorithm>
+#include <limits>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+using namespace std;
+
+namespace {
+
+/// @brief Function used in calls to std::upper_bound to check
+/// if the specified prefix is lower than the first address a pool.
+///
+/// @return true if prefix is lower than the first address in the pool.
+bool
+prefixLessThanFirstAddress(const IOAddress& prefix,
+ const PoolPtr& pool) {
+ return (prefix < pool->getFirstAddress());
+}
+
+/// @brief Function used in calls to std::sort to compare first
+/// prefixes of the two pools.
+///
+/// @param pool1 First pool.
+/// @param pool2 Second pool.
+///
+/// @return true if first prefix of the first pool is smaller than
+/// the first address of the second pool.
+bool
+comparePoolFirstAddress(const PoolPtr& pool1,
+ const PoolPtr& pool2) {
+ return (pool1->getFirstAddress() < pool2->getFirstAddress());
+}
+
+}
+
+namespace isc {
+namespace dhcp {
+
+// This is an initial value of subnet-id. See comments in subnet.h for details.
+SubnetID Subnet::static_id_ = 1;
+
+Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
+ const SubnetID id)
+ : id_(id == 0 ? generateNextID() : id), prefix_(prefix),
+ prefix_len_(len),
+ shared_network_name_() {
+ if ((id == 0) && (id_ == 1)) {
+ // Emit a warning on the first auto-numbered subnet.
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_CONFIGURED_SUBNET_WITHOUT_ID)
+ .arg(toText());
+ }
+ if ((prefix.isV6() && len > 128) ||
+ (prefix.isV4() && len > 32)) {
+ isc_throw(BadValue,
+ "Invalid prefix length specified for subnet: " << len);
+ }
+}
+
+bool
+Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
+ IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
+ IOAddress last = lastAddrInPrefix(prefix_, prefix_len_);
+
+ return ((first <= addr) && (addr <= last));
+}
+
+std::string
+Subnet::toText() const {
+ std::stringstream tmp;
+ tmp << prefix_ << "/" << static_cast<unsigned int>(prefix_len_);
+ return (tmp.str());
+}
+
+uint128_t
+Subnet::getPoolCapacity(Lease::Type type) const {
+ switch (type) {
+ case Lease::TYPE_V4:
+ case Lease::TYPE_NA:
+ return sumPoolCapacity(pools_);
+ case Lease::TYPE_TA:
+ return sumPoolCapacity(pools_ta_);
+ case Lease::TYPE_PD:
+ return sumPoolCapacity(pools_pd_);
+ default:
+ isc_throw(BadValue, "Unsupported pool type: "
+ << static_cast<int>(type));
+ }
+}
+
+uint128_t
+Subnet::getPoolCapacity(Lease::Type type,
+ const ClientClasses& client_classes) const {
+ switch (type) {
+ case Lease::TYPE_V4:
+ case Lease::TYPE_NA:
+ return sumPoolCapacity(pools_, client_classes);
+ case Lease::TYPE_TA:
+ return sumPoolCapacity(pools_ta_, client_classes);
+ case Lease::TYPE_PD:
+ return sumPoolCapacity(pools_pd_, client_classes);
+ default:
+ isc_throw(BadValue, "Unsupported pool type: "
+ << static_cast<int>(type));
+ }
+}
+
+uint128_t
+Subnet::getPoolCapacity(Lease::Type type,
+ const ClientClasses& client_classes,
+ Allocator::PrefixLenMatchType prefix_length_match,
+ uint8_t hint_prefix_length) const {
+ switch (type) {
+ case Lease::TYPE_V4:
+ case Lease::TYPE_NA:
+ return sumPoolCapacity(pools_, client_classes);
+ case Lease::TYPE_TA:
+ return sumPoolCapacity(pools_ta_, client_classes);
+ case Lease::TYPE_PD:
+ return sumPoolCapacity(pools_pd_, client_classes, prefix_length_match,
+ hint_prefix_length);
+ default:
+ isc_throw(BadValue, "Unsupported pool type: "
+ << static_cast<int>(type));
+ }
+}
+
+uint128_t
+Subnet::sumPoolCapacity(const PoolCollection& pools) const {
+ uint128_t sum(0);
+ for (auto const& p : pools) {
+ uint128_t const c(p->getCapacity());
+
+ // Check if we can add it. If sum + c > UINT128_MAX, then we would have
+ // overflown if we tried to add it.
+ if (c > numeric_limits<uint128_t>::max() - sum) {
+ return (numeric_limits<uint128_t>::max());
+ }
+
+ sum += c;
+ }
+
+ return (sum);
+}
+
+uint128_t
+Subnet::sumPoolCapacity(const PoolCollection& pools,
+ const ClientClasses& client_classes) const {
+ uint128_t sum(0);
+ for (auto const& p : pools) {
+ if (!p->clientSupported(client_classes)) {
+ continue;
+ }
+
+ uint128_t const c(p->getCapacity());
+
+ // Check if we can add it. If sum + c > UINT128_MAX, then we would have
+ // overflown if we tried to add it.
+ if (c > numeric_limits<uint128_t>::max() - sum) {
+ return (numeric_limits<uint128_t>::max());
+ }
+
+ sum += c;
+ }
+
+ return (sum);
+}
+
+uint128_t
+Subnet::sumPoolCapacity(const PoolCollection& pools,
+ const ClientClasses& client_classes,
+ Allocator::PrefixLenMatchType prefix_length_match,
+ uint8_t hint_prefix_length) const {
+ uint128_t sum(0);
+ for (auto const& p : pools) {
+ if (!p->clientSupported(client_classes)) {
+ continue;
+ }
+
+ if (!Allocator::isValidPrefixPool(prefix_length_match, p,
+ hint_prefix_length)) {
+ continue;
+ }
+
+ uint128_t const c(p->getCapacity());
+
+ // Check if we can add it. If sum + c > UINT128_MAX, then we would have
+ // overflown if we tried to add it.
+ if (c > numeric_limits<uint128_t>::max() - sum) {
+ return (numeric_limits<uint128_t>::max());
+ }
+
+ sum += c;
+ }
+
+ return (sum);
+}
+
+std::pair<IOAddress, uint8_t>
+Subnet::parsePrefixCommon(const std::string& prefix) {
+ auto pos = prefix.find('/');
+ if ((pos == std::string::npos) ||
+ (pos == prefix.size() - 1) ||
+ (pos == 0)) {
+ isc_throw(BadValue, "unable to parse invalid prefix " << prefix);
+ }
+
+ try {
+ IOAddress address(prefix.substr(0, pos));
+ int length = boost::lexical_cast<int>(prefix.substr(pos + 1));
+ return (std::make_pair(address, static_cast<int>(length)));
+
+ } catch (...) {
+ isc_throw(BadValue, "unable to parse invalid prefix " << prefix);
+ }
+}
+
+
+void Subnet4::checkType(Lease::Type type) const {
+ if (type != Lease::TYPE_V4) {
+ isc_throw(BadValue, "Only TYPE_V4 is allowed for Subnet4");
+ }
+}
+
+Subnet4::Subnet4(const IOAddress& prefix, uint8_t length,
+ const Triplet<uint32_t>& t1,
+ const Triplet<uint32_t>& t2,
+ const Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id)
+ : Subnet(prefix, length, id), Network4() {
+ if (!prefix.isV4()) {
+ isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
+ << " specified in subnet4");
+ }
+ // Timers.
+ setT1(t1);
+ setT2(t2);
+ setValid(valid_lifetime);
+}
+
+Subnet4Ptr
+Subnet4::create(const IOAddress& prefix, uint8_t length,
+ const Triplet<uint32_t>& t1,
+ const Triplet<uint32_t>& t2,
+ const Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id) {
+ Subnet4Ptr subnet = boost::make_shared<Subnet4>
+ (prefix, length, t1, t2, valid_lifetime, id);
+ subnet->setAllocator(Lease::TYPE_V4,
+ boost::make_shared<IterativeAllocator>
+ (Lease::TYPE_V4, subnet));
+ subnet->setAllocationState(Lease::TYPE_V4,
+ SubnetIterativeAllocationState::create(subnet));
+
+ return (subnet);
+}
+
+Subnet4Ptr
+Subnet4::getNextSubnet(const Subnet4Ptr& first_subnet) const {
+ SharedNetwork4Ptr network;
+ getSharedNetwork(network);
+ if (network) {
+ return (network->getNextSubnet(first_subnet, getID()));
+ }
+
+ return (Subnet4Ptr());
+}
+
+Subnet4Ptr
+Subnet4::getNextSubnet(const Subnet4Ptr& first_subnet,
+ const ClientClasses& client_classes) const {
+ SharedNetwork4Ptr network;
+ getSharedNetwork(network);
+ // We can only get next subnet if shared network has been defined for
+ // the current subnet.
+ if (network) {
+ Subnet4Ptr subnet;
+ do {
+ // Use subnet identifier of this subnet if this is the first
+ // time we're calling getNextSubnet. Otherwise, use the
+ // subnet id of the previously returned subnet.
+ SubnetID subnet_id = subnet ? subnet->getID() : getID();
+ subnet = network->getNextSubnet(first_subnet, subnet_id);
+ // If client classes match the subnet, return it. Otherwise,
+ // try another subnet.
+ if (subnet && subnet->clientSupported(client_classes)) {
+ return (subnet);
+ }
+ } while (subnet);
+ }
+
+ // No subnet found.
+ return (Subnet4Ptr());
+}
+
+
+bool
+Subnet4::clientSupported(const isc::dhcp::ClientClasses& client_classes) const {
+ NetworkPtr network;
+ getSharedNetwork(network);
+ if (network && !network->clientSupported(client_classes)) {
+ return (false);
+ }
+
+ return (Network4::clientSupported(client_classes));
+}
+
+const PoolCollection& Subnet::getPools(Lease::Type type) const {
+ // check if the type is valid (and throw if it isn't)
+ checkType(type);
+
+ switch (type) {
+ case Lease::TYPE_V4:
+ case Lease::TYPE_NA:
+ return (pools_);
+ case Lease::TYPE_TA:
+ return (pools_ta_);
+ case Lease::TYPE_PD:
+ return (pools_pd_);
+ default:
+ isc_throw(BadValue, "Unsupported pool type: "
+ << static_cast<int>(type));
+ }
+}
+
+PoolCollection& Subnet::getPoolsWritable(Lease::Type type) {
+ // check if the type is valid (and throw if it isn't)
+ checkType(type);
+
+ switch (type) {
+ case Lease::TYPE_V4:
+ case Lease::TYPE_NA:
+ return (pools_);
+ case Lease::TYPE_TA:
+ return (pools_ta_);
+ case Lease::TYPE_PD:
+ return (pools_pd_);
+ default:
+ isc_throw(BadValue, "Invalid pool type specified: "
+ << static_cast<int>(type));
+ }
+}
+
+AllocatorPtr
+Subnet::getAllocator(Lease::Type type) const {
+ auto alloc = allocators_.find(type);
+
+ if (alloc == allocators_.end()) {
+ isc_throw(BadValue, "no allocator initialized for pool type "
+ << Lease::typeToText(type));
+ }
+ return (alloc->second);
+}
+
+void
+Subnet::setAllocator(Lease::Type type, const AllocatorPtr& allocator) {
+ allocators_[type] = allocator;
+}
+
+SubnetAllocationStatePtr
+Subnet::getAllocationState(Lease::Type type) const {
+ auto state = allocation_states_.find(type);
+
+ if (state == allocation_states_.end()) {
+ isc_throw(BadValue, "no allocation state initialized for pool type "
+ << Lease::typeToText(type));
+ }
+ return (state->second);
+}
+
+void
+Subnet::setAllocationState(Lease::Type type, const SubnetAllocationStatePtr& allocation_state) {
+ allocation_states_[type] = allocation_state;
+}
+
+const PoolPtr Subnet::getPool(Lease::Type type, const isc::asiolink::IOAddress& hint,
+ bool anypool /* true */) const {
+ // check if the type is valid (and throw if it isn't)
+ checkType(type);
+
+ const auto& pools = getPools(type);
+
+ PoolPtr candidate;
+
+ if (!pools.empty()) {
+ // Pools are sorted by their first prefixes. For example: 2001::,
+ // 2001::db8::, 3000:: etc. If our hint is 2001:db8:5:: we want to
+ // find the pool with the longest matching prefix, so: 2001:db8::,
+ // rather than 2001::. upper_bound returns the first pool with a prefix
+ // that is greater than 2001:db8:5::, i.e. 3000::. To find the longest
+ // matching prefix we use decrement operator to go back by one item.
+ // If returned iterator points to begin it means that prefixes in all
+ // pools are greater than out prefix, and thus there is no match.
+ auto ub =
+ std::upper_bound(pools.begin(), pools.end(), hint,
+ prefixLessThanFirstAddress);
+
+ if (ub != pools.begin()) {
+ --ub;
+ if ((*ub)->inRange(hint)) {
+ candidate = *ub;
+ }
+ }
+
+ // If we don't find anything better, then let's just use the first pool
+ if (!candidate && anypool) {
+ candidate = *pools.begin();
+ }
+ }
+
+ // Return a pool or NULL if no match found.
+ return (candidate);
+}
+
+void
+Subnet::initAllocatorsAfterConfigure() {
+ for (auto allocator : allocators_) {
+ allocator.second->initAfterConfigure();
+ }
+}
+
+const PoolPtr Subnet::getPool(Lease::Type type,
+ const ClientClasses& client_classes,
+ const isc::asiolink::IOAddress& hint) const {
+ // check if the type is valid (and throw if it isn't)
+ checkType(type);
+
+ const auto& pools = getPools(type);
+
+ PoolPtr candidate;
+
+ if (!pools.empty()) {
+ auto ub =
+ std::upper_bound(pools.begin(), pools.end(), hint,
+ prefixLessThanFirstAddress);
+
+ if (ub != pools.begin()) {
+ --ub;
+ if ((*ub)->inRange(hint) &&
+ (*ub)->clientSupported(client_classes)) {
+ candidate = *ub;
+ }
+ }
+ }
+
+ // Return a pool or NULL if no match found.
+ return (candidate);
+}
+
+void
+Subnet::addPool(const PoolPtr& pool) {
+ // check if the type is valid (and throw if it isn't)
+ checkType(pool->getType());
+
+ // Check that the pool is in range with a subnet only if this is
+ // not a pool of IPv6 prefixes. The IPv6 prefixes delegated for
+ // the particular subnet don't need to match the prefix of the
+ // subnet.
+ if (pool->getType() != Lease::TYPE_PD) {
+ if (!inRange(pool->getFirstAddress()) || !inRange(pool->getLastAddress())) {
+ isc_throw(BadValue, "a pool of type "
+ << Lease::typeToText(pool->getType())
+ << ", with the following address range: "
+ << pool->getFirstAddress() << "-"
+ << pool->getLastAddress() << " does not match"
+ << " the prefix of a subnet: "
+ << prefix_ << "/" << static_cast<int>(prefix_len_)
+ << " to which it is being added");
+
+ }
+ }
+
+ bool overlaps = false;
+ if (pool->getType() == Lease::TYPE_V4) {
+ overlaps = poolOverlaps(Lease::TYPE_V4, pool);
+
+ } else {
+ overlaps =
+ poolOverlaps(Lease::TYPE_NA, pool) ||
+ poolOverlaps(Lease::TYPE_PD, pool) ||
+ poolOverlaps(Lease::TYPE_TA, pool);
+ }
+
+ if (overlaps) {
+ isc_throw(BadValue,"a pool of type "
+ << Lease::typeToText(pool->getType())
+ << ", with the following address range: "
+ << pool->getFirstAddress() << "-"
+ << pool->getLastAddress() << " overlaps with "
+ "an existing pool in the subnet: "
+ << prefix_ << "/" << static_cast<int>(prefix_len_)
+ << " to which it is being added");
+ }
+
+ PoolCollection& pools_writable = getPoolsWritable(pool->getType());
+
+ // Add the pool to the appropriate pools collection
+ pools_writable.push_back(pool);
+
+ // Sort pools by first address.
+ std::sort(pools_writable.begin(), pools_writable.end(),
+ comparePoolFirstAddress);
+}
+
+void
+Subnet::delPools(Lease::Type type) {
+ getPoolsWritable(type).clear();
+}
+
+bool
+Subnet::inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const {
+
+ // Let's start with checking if it even belongs to that subnet.
+ if ((type != Lease::TYPE_PD) && !inRange(addr)) {
+ return (false);
+ }
+
+ const auto& pools = getPools(type);
+ for (const auto& pool : pools) {
+ if (pool->inRange(addr)) {
+ return (true);
+ }
+ }
+ // There's no pool that address belongs to
+ return (false);
+}
+
+bool
+Subnet::inPool(Lease::Type type,
+ const isc::asiolink::IOAddress& addr,
+ const ClientClasses& client_classes) const {
+
+ // Let's start with checking if it even belongs to that subnet.
+ if ((type != Lease::TYPE_PD) && !inRange(addr)) {
+ return (false);
+ }
+
+ const auto& pools = getPools(type);
+ for (const auto& pool : pools) {
+ if (!pool->clientSupported(client_classes)) {
+ continue;
+ }
+ if (pool->inRange(addr)) {
+ return (true);
+ }
+ }
+ // There's no pool that address belongs to
+ return (false);
+}
+
+bool
+Subnet::poolOverlaps(const Lease::Type& pool_type, const PoolPtr& pool) const {
+ const auto& pools = getPools(pool_type);
+
+ // If no pools, we don't overlap. Nothing to do.
+ if (pools.empty()) {
+ return (false);
+ }
+
+ // We're going to insert a new pool, likely between two existing pools.
+ // So we're going to end up with the following case:
+ // |<---- pool1 ---->| |<-------- pool2 ------>| |<-- pool3 -->|
+ // F1 L1 F2 L2 F3 L3
+ // where pool1 and pool3 are existing pools, pool2 is a pool being
+ // inserted and "F"/"L" mark first and last address in the pools
+ // respectively. So the following conditions must be fulfilled:
+ // F2 > L1 and L2 < F3. Obviously, for any pool: F < L.
+
+ // Search for pool3. We use F2 and upper_bound to find the F3 (upper_bound
+ // returns first pool in the sorted container which first address is
+ // greater than F2). prefixLessThanPoolAddress with the first argument
+ // set to "true" is the custom comparison function for upper_bound, which
+ // compares F2 with the first addresses of the existing pools.
+ const auto pool3_it =
+ std::upper_bound(pools.begin(), pools.end(), pool->getFirstAddress(),
+ prefixLessThanFirstAddress);
+
+ // The upper_bound function returns a first pool which first address is
+ // greater than the address F2. However, it is also possible that there is a
+ // pool which first address is equal to F2. Such pool is also in conflict
+ // with a new pool. If the returned value is pools.begin() it means that all
+ // pools have greater first address than F2, thus none of the pools can have
+ // first address equal to F2. Otherwise, we'd need to check them for
+ // equality. However any pool has first address <= last address, so checking
+ // that the new pool first address is greater than the pool before pool3
+ // last address is enough. We now have to find the pool1. This pool should
+ // be right before the pool3 if there is any pool before pool3.
+ if (pool3_it != pools.begin()) {
+ PoolPtr pool1 = *(pool3_it - 1);
+ // F2 must be greater than L1, otherwise pools will overlap.
+ if (pool->getFirstAddress() <= pool1->getLastAddress()) {
+ return (true);
+ }
+ }
+
+ // If returned value is unequal pools.end() it means that there is a pool3,
+ // with F3 > F2.
+ if (pool3_it != pools.end()) {
+ // Let's store the pointer to this pool.
+ PoolPtr pool3 = *pool3_it;
+ // F3 must be greater than L2, otherwise pools will overlap.
+ if (pool3->getFirstAddress() <= pool->getLastAddress()) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+Subnet6::Subnet6(const IOAddress& prefix, uint8_t length,
+ const Triplet<uint32_t>& t1,
+ const Triplet<uint32_t>& t2,
+ const Triplet<uint32_t>& preferred_lifetime,
+ const Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id)
+ : Subnet(prefix, length, id), Network6() {
+ if (!prefix.isV6()) {
+ isc_throw(BadValue, "Non IPv6 prefix " << prefix
+ << " specified in subnet6");
+ }
+
+ // Timers.
+ setT1(t1);
+ setT2(t2);
+ setPreferred(preferred_lifetime);
+ setValid(valid_lifetime);
+}
+
+Subnet6Ptr
+Subnet6::create(const IOAddress& prefix, uint8_t length,
+ const Triplet<uint32_t>& t1,
+ const Triplet<uint32_t>& t2,
+ const Triplet<uint32_t>& preferred_lifetime,
+ const Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id) {
+ Subnet6Ptr subnet = boost::make_shared<Subnet6>
+ (prefix, length, t1, t2, preferred_lifetime, valid_lifetime, id);
+ // IA_NA
+ subnet->setAllocator(Lease::TYPE_NA,
+ boost::make_shared<IterativeAllocator>
+ (Lease::TYPE_NA, subnet));
+ subnet->setAllocationState(Lease::TYPE_NA,
+ SubnetIterativeAllocationState::create(subnet));
+ // IA_TA
+ subnet->setAllocator(Lease::TYPE_TA,
+ boost::make_shared<IterativeAllocator>
+ (Lease::TYPE_TA, subnet));
+ subnet->setAllocationState(Lease::TYPE_TA,
+ SubnetIterativeAllocationState::create(subnet));
+ // IA_PD
+ subnet->setAllocator(Lease::TYPE_PD,
+ boost::make_shared<IterativeAllocator>
+ (Lease::TYPE_PD, subnet));
+ subnet->setAllocationState(Lease::TYPE_PD,
+ SubnetIterativeAllocationState::create(subnet));
+ return (subnet);
+}
+
+void Subnet6::checkType(Lease::Type type) const {
+ if ((type != Lease::TYPE_NA) && (type != Lease::TYPE_TA) && (type != Lease::TYPE_PD)) {
+ isc_throw(BadValue, "Invalid Pool type: " << Lease::typeToText(type)
+ << "(" << static_cast<int>(type)
+ << "), must be TYPE_NA, TYPE_TA or TYPE_PD for Subnet6");
+ }
+}
+
+Subnet6Ptr
+Subnet6::getNextSubnet(const Subnet6Ptr& first_subnet) const {
+ SharedNetwork6Ptr network;
+ getSharedNetwork(network);
+ if (network) {
+ return (network->getNextSubnet(first_subnet, getID()));
+ }
+
+ return (Subnet6Ptr());
+}
+
+Subnet6Ptr
+Subnet6::getNextSubnet(const Subnet6Ptr& first_subnet,
+ const ClientClasses& client_classes) const {
+ SharedNetwork6Ptr network;
+ getSharedNetwork(network);
+ // We can only get next subnet if shared network has been defined for
+ // the current subnet.
+ if (network) {
+ Subnet6Ptr subnet;
+ do {
+ // Use subnet identifier of this subnet if this is the first
+ // time we're calling getNextSubnet. Otherwise, use the
+ // subnet id of the previously returned subnet.
+ SubnetID subnet_id = subnet ? subnet->getID() : getID();
+ subnet = network->getNextSubnet(first_subnet, subnet_id);
+ // If client classes match the subnet, return it. Otherwise,
+ // try another subnet.
+ if (subnet && subnet->clientSupported(client_classes)) {
+ return (subnet);
+ }
+ } while (subnet);
+ }
+
+ // No subnet found.
+ return (Subnet6Ptr());
+}
+
+bool
+Subnet6::clientSupported(const isc::dhcp::ClientClasses& client_classes) const {
+ NetworkPtr network;
+ getSharedNetwork(network);
+ if (network && !network->clientSupported(client_classes)) {
+ return (false);
+ }
+
+ return (Network6::clientSupported(client_classes));
+}
+
+data::ElementPtr
+Subnet::toElement() const {
+ ElementPtr map = Element::createMap();
+
+ // Add user-context
+ contextToElement(map);
+
+ // Set subnet id
+ SubnetID id = getID();
+ map->set("id", Element::create(static_cast<long long>(id)));
+
+ // Set subnet
+ map->set("subnet", Element::create(toText()));
+
+ return (map);
+}
+
+void
+Subnet4::createAllocators() {
+ auto allocator_type = getAllocatorType();
+ if (allocator_type.empty()) {
+ allocator_type = getDefaultAllocatorType();
+ }
+ if (allocator_type == "random") {
+ setAllocator(Lease::TYPE_V4,
+ boost::make_shared<RandomAllocator>
+ (Lease::TYPE_V4, shared_from_this()));
+ setAllocationState(Lease::TYPE_V4, SubnetAllocationStatePtr());
+
+ for (auto pool : pools_) {
+ pool->setAllocationState(PoolRandomAllocationState::create(pool));
+ }
+
+ } else if (allocator_type == "flq") {
+ setAllocator(Lease::TYPE_V4,
+ boost::make_shared<FreeLeaseQueueAllocator>
+ (Lease::TYPE_V4, shared_from_this()));
+ setAllocationState(Lease::TYPE_V4, SubnetAllocationStatePtr());
+
+ for (auto pool : pools_) {
+ pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
+ }
+
+ } else {
+ setAllocator(Lease::TYPE_V4,
+ boost::make_shared<IterativeAllocator>
+ (Lease::TYPE_V4, shared_from_this()));
+ setAllocationState(Lease::TYPE_V4,
+ SubnetIterativeAllocationState::create(shared_from_this()));
+
+ for (auto pool : pools_) {
+ pool->setAllocationState(PoolIterativeAllocationState::create(pool));
+ }
+ }
+}
+
+data::ElementPtr
+Subnet4::toElement() const {
+ // Prepare the map
+ ElementPtr map = Subnet::toElement();
+ ElementPtr network_map = Network4::toElement();
+
+ merge(map, network_map);
+
+ // Set DHCP4o6
+ const Cfg4o6& d4o6 = get4o6();
+ isc::data::merge(map, d4o6.toElement());
+
+ // Set pools
+ const auto& pools = getPools(Lease::TYPE_V4);
+ ElementPtr pool_list = Element::createList();
+ for (const auto& pool : pools) {
+ // Add the formatted pool to the list
+ pool_list->add(pool->toElement());
+ }
+ map->set("pools", pool_list);
+
+ return (map);
+}
+
+std::pair<IOAddress, uint8_t>
+Subnet4::parsePrefix(const std::string& prefix) {
+ std::pair<IOAddress, uint8_t> parsed = Subnet::parsePrefixCommon(prefix);
+ if (!parsed.first.isV4() || parsed.first.isV4Zero() ||
+ (parsed.second > 32) || (parsed.second == 0)) {
+ isc_throw(BadValue, "unable to parse invalid IPv4 prefix " << prefix);
+ }
+ return (parsed);
+}
+
+void
+Subnet6::createAllocators() {
+ auto allocator_type = getAllocatorType();
+ if (allocator_type.empty()) {
+ allocator_type = getDefaultAllocatorType();
+ }
+ if (allocator_type == "random") {
+ setAllocator(Lease::TYPE_NA,
+ boost::make_shared<RandomAllocator>
+ (Lease::TYPE_NA, shared_from_this()));
+ setAllocator(Lease::TYPE_TA,
+ boost::make_shared<RandomAllocator>
+ (Lease::TYPE_TA, shared_from_this()));
+ setAllocationState(Lease::TYPE_NA, SubnetAllocationStatePtr());
+ setAllocationState(Lease::TYPE_TA, SubnetAllocationStatePtr());
+
+ } else if (allocator_type == "flq") {
+ isc_throw(BadValue, "Free Lease Queue allocator is not supported for IPv6 address pools");
+
+ } else {
+ setAllocator(Lease::TYPE_NA,
+ boost::make_shared<IterativeAllocator>
+ (Lease::TYPE_NA, shared_from_this()));
+ setAllocationState(Lease::TYPE_NA, SubnetIterativeAllocationState::create(shared_from_this()));
+ setAllocationState(Lease::TYPE_TA, SubnetIterativeAllocationState::create(shared_from_this()));
+ }
+
+ auto pd_allocator_type = getPdAllocatorType();
+ if (pd_allocator_type.empty()) {
+ pd_allocator_type = getDefaultPdAllocatorType();
+ }
+ // Repeat the same for the delegated prefix allocator.
+ if (pd_allocator_type == "random") {
+ setAllocator(Lease::TYPE_PD,
+ boost::make_shared<RandomAllocator>
+ (Lease::TYPE_PD, shared_from_this()));
+ setAllocationState(Lease::TYPE_PD, SubnetAllocationStatePtr());
+
+ } else if (pd_allocator_type == "flq") {
+ setAllocator(Lease::TYPE_PD,
+ boost::make_shared<FreeLeaseQueueAllocator>
+ (Lease::TYPE_PD, shared_from_this()));
+ setAllocationState(Lease::TYPE_PD, SubnetAllocationStatePtr());
+
+ } else {
+ setAllocator(Lease::TYPE_PD,
+ boost::make_shared<IterativeAllocator>
+ (Lease::TYPE_PD, shared_from_this()));
+ setAllocationState(Lease::TYPE_PD, SubnetIterativeAllocationState::create(shared_from_this()));
+ }
+ // Create allocation states for NA pools.
+ for (auto pool : pools_) {
+ if (allocator_type == "random") {
+ pool->setAllocationState(PoolRandomAllocationState::create(pool));
+ } else {
+ pool->setAllocationState(PoolIterativeAllocationState::create(pool));
+ }
+ }
+ // Create allocation states for TA pools.
+ for (auto pool : pools_ta_) {
+ if (allocator_type == "random") {
+ pool->setAllocationState(PoolRandomAllocationState::create(pool));
+ } else {
+ pool->setAllocationState(PoolIterativeAllocationState::create(pool));
+ }
+ }
+ // Create allocation states for PD pools.
+ for (auto pool : pools_pd_) {
+ if (pd_allocator_type == "random") {
+ pool->setAllocationState(PoolRandomAllocationState::create(pool));
+ } else if (pd_allocator_type == "flq") {
+ pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool));
+ } else {
+ pool->setAllocationState(PoolIterativeAllocationState::create(pool));
+ }
+ }
+}
+
+data::ElementPtr
+Subnet6::toElement() const {
+ // Prepare the map
+ ElementPtr map = Subnet::toElement();
+ ElementPtr network_map = Network6::toElement();
+
+ merge(map, network_map);
+
+ // Set pools
+ const auto& pools = getPools(Lease::TYPE_NA);
+ ElementPtr pool_list = Element::createList();
+ for (const auto& pool : pools) {
+ // Add the formatted pool to the list
+ pool_list->add(pool->toElement());
+ }
+ map->set("pools", pool_list);
+
+ // Set pd-pools
+ const auto& pdpools = getPools(Lease::TYPE_PD);
+ ElementPtr pdpool_list = Element::createList();
+ for (const auto& pool : pdpools) {
+ // Add the formatted pool to the list
+ pdpool_list->add(pool->toElement());
+ }
+ map->set("pd-pools", pdpool_list);
+
+ return (map);
+}
+
+std::pair<IOAddress, uint8_t>
+Subnet6::parsePrefix(const std::string& prefix) {
+ std::pair<IOAddress, uint8_t> parsed = Subnet::parsePrefixCommon(prefix);
+ if (!parsed.first.isV6() || parsed.first.isV6Zero() ||
+ (parsed.second > 128) || (parsed.second == 0)) {
+ isc_throw(BadValue, "unable to parse invalid IPv6 prefix " << prefix);
+ }
+ return (parsed);
+}
+
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
new file mode 100644
index 0000000..54e18d3
--- /dev/null
+++ b/src/lib/dhcpsrv/subnet.h
@@ -0,0 +1,1042 @@
+// Copyright (C) 2012-2023 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 SUBNET_H
+#define SUBNET_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <cc/user_context.h>
+#include <dhcp/option_space_container.h>
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet_id.h>
+#include <util/bigints.h>
+#include <util/dhcp_space.h>
+#include <util/triplet.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/random_access_index.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/pointer_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <cstdint>
+#include <map>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+class Subnet : public virtual Network {
+public:
+
+ /// @brief checks if specified address is in range.
+ ///
+ /// @param addr this address will be checked if it is included in a specific
+ /// range
+ /// @return true if address is in range, false otherwise
+ bool inRange(const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief checks if the specified address is in pools.
+ ///
+ /// Note the difference between inRange() and inPool() for addresses
+ /// (i.e. *not* prefixes). For a given subnet (e.g. 2001::/64) there
+ /// may be one or more pools defined that may or may not cover
+ /// entire subnet, e.g. pool 2001::1-2001::10). inPool() returning
+ /// true implies inRange(), but the reverse implication is not
+ /// always true. For the given example, 2001::1234:abcd would return
+ /// true for inRange(), but false for inPool() check.
+ ///
+ /// @param type type of pools to iterate over
+ /// @param addr this address will be checked if it belongs to any pools in
+ /// that subnet
+ /// @return true if the address is in any of the pools
+ bool inPool(Lease::Type type, const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief checks if the specified address is in allowed pools.
+ ///
+ /// This takes also into account client classes
+ ///
+ /// @param type type of pools to iterate over
+ /// @param addr this address will be checked if it belongs to any pools in
+ /// that subnet
+ /// @param client_classes client class list which must be allowed
+ /// @return true if the address is in any of the allowed pools
+ bool inPool(Lease::Type type,
+ const isc::asiolink::IOAddress& addr,
+ const ClientClasses& client_classes) const;
+
+ /// @brief Returns unique ID for that subnet.
+ ///
+ /// @return unique ID for that subnet
+ SubnetID getID() const {
+ return (id_);
+ }
+
+ /// @brief Returns subnet parameters (prefix and prefix length).
+ ///
+ /// @return (prefix, prefix length) pair
+ std::pair<isc::asiolink::IOAddress, uint8_t> get() const {
+ return (std::make_pair(prefix_, prefix_len_));
+ }
+
+ /// @brief Adds a new pool for the subnet.
+ ///
+ /// This method checks that the address range represented by the pool
+ /// matches the subnet prefix, if the pool type is different than
+ /// IA_PD. The prefixes from the IA_PD pools don't need to match the
+ /// prefix from the subnet from which they are handed out to the
+ /// requesting router because the requesting router may use the
+ /// delegated prefixes in different networks (using different subnets).
+ ///
+ /// A DHCPv4 pool being added must not overlap with any existing DHCPv4
+ /// pool. A DHCPv6 pool being added must not overlap with any existing
+ /// DHCPv6 pool.
+ ///
+ /// Pools held within a subnet are sorted by first pool address/prefix
+ /// from the lowest to the highest.
+ ///
+ /// @param pool pool to be added
+ ///
+ /// @throw isc::BadValue if the pool type is invalid, in the case of address
+ /// pools if the address range of the pool does not match the subnet prefix,
+ /// or if the pool overlaps with an existing pool within the subnet.
+ void addPool(const PoolPtr& pool);
+
+ /// @brief Deletes all pools of specified type.
+ ///
+ /// This method is used for testing purposes only
+ ///
+ /// @param type type of pools to be deleted
+ void delPools(Lease::Type type);
+
+ /// @brief Returns a pool that specified address belongs to.
+ ///
+ /// This method uses binary search to retrieve the pool. Thus, the number
+ /// of comparisons performed by this method is logarithmic in the number
+ /// of pools belonging to a subnet.
+ ///
+ /// If there is no pool that the address belongs to (hint is invalid), other
+ /// pool of specified type will be returned.
+ ///
+ /// With anypool set to true, this means give me a pool, preferably
+ /// the one that addr belongs to. With anypool set to false, it means
+ /// give me a pool that addr belongs to (or NULL if here is no such pool)
+ ///
+ /// @param type pool type that the pool is looked for
+ /// @param addr address that the returned pool should cover (optional)
+ /// @param anypool other pool may be returned as well, not only the one
+ /// that addr belongs to
+ /// @return found pool (or NULL)
+ const PoolPtr getPool(Lease::Type type, const isc::asiolink::IOAddress& addr,
+ bool anypool = true) const;
+
+ /// @brief Returns a pool that specified address belongs to with classes.
+ ///
+ /// Variant using only pools allowing given classes.
+ ///
+ /// @param type pool type that the pool is looked for
+ /// @param client_classes client class list which must be allowed
+ /// @param addr address that the returned pool should cover (optional)
+ const PoolPtr getPool(Lease::Type type,
+ const ClientClasses& client_classes,
+ const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief Returns a pool without any address specified.
+ ///
+ /// @param type pool type that the pool is looked for
+ /// @return returns one of the pools defined
+ PoolPtr getAnyPool(Lease::Type type) {
+ return (getPool(type, default_pool()));
+ }
+
+ /// @brief Returns the default address that will be used for pool selection.
+ ///
+ /// It must be implemented in derived classes (should return :: for Subnet6
+ /// and 0.0.0.0 for Subnet4).
+ virtual isc::asiolink::IOAddress default_pool() const = 0;
+
+ /// @brief Returns all pools (const variant).
+ ///
+ /// The reference is only valid as long as the object that returned it.
+ ///
+ /// @param type lease type to be set
+ /// @return a collection of all pools
+ const PoolCollection& getPools(Lease::Type type) const;
+
+ /// @brief Returns the number of possible leases for specified lease type.
+ ///
+ /// @param type type of the lease
+ isc::util::uint128_t getPoolCapacity(Lease::Type type) const;
+
+ /// @brief Returns the number of possible leases for specified lease type
+ /// allowed for a client which belongs to classes.
+ ///
+ /// @param type type of the lease
+ /// @param client_classes list of classes the client belongs to
+ /// @return number of leases matching lease type and classes
+ isc::util::uint128_t getPoolCapacity(Lease::Type type,
+ const ClientClasses& client_classes) const;
+
+ /// @brief Returns the number of possible leases for specified lease type
+ /// allowed for a client which belongs to classes and matching selection
+ /// criteria relative to provided hint prefix length.
+ ///
+ /// @param type type of the lease
+ /// @param client_classes list of classes the client belongs to
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length
+ /// @param hint_prefix_length the hint prefix length that the client
+ /// provided
+ /// @return number of leases matching lease type and classes
+ isc::util::uint128_t getPoolCapacity(Lease::Type type,
+ const ClientClasses& client_classes,
+ Allocator::PrefixLenMatchType prefix_length_match,
+ uint8_t hint_prefix_length) const;
+
+ /// @brief Returns textual representation of the subnet (e.g.
+ /// "2001:db8::/64").
+ ///
+ /// @return textual representation
+ virtual std::string toText() const;
+
+ /// @brief Resets subnet-id counter to its initial value (1).
+ ///
+ /// This should be called during reconfiguration, before any new
+ /// subnet objects are created. It will ensure that the subnet_id will
+ /// be consistent between reconfigures.
+ static void resetSubnetID() {
+ static_id_ = 1;
+ }
+
+ /// @brief Retrieves pointer to a shared network associated with a subnet.
+ ///
+ /// By implementing it as a template function we overcome a need to
+ /// include shared_network.h header file to specify return type explicitly.
+ /// The header can't be included because it would cause circular dependency
+ /// between subnet.h and shared_network.h.
+ ///
+ /// This method uses an argument to hold a return value to allow the compiler
+ /// to infer the return type without a need to call this function with an
+ /// explicit return type as template argument.
+ ///
+ /// @param [out] shared_network Pointer to the shared network where returned
+ /// value should be assigned.
+ ///
+ /// @tparam Type of the shared network, i.e. @ref SharedNetwork4 or a
+ /// @ref SharedNetwork6.
+ template<typename SharedNetworkPtrType>
+ void getSharedNetwork(SharedNetworkPtrType& shared_network) const {
+ shared_network = boost::dynamic_pointer_cast<
+ typename SharedNetworkPtrType::element_type>(parent_network_.lock());
+ }
+
+ /// @brief Assigns shared network to a subnet.
+ ///
+ /// This method replaces any shared network associated with a subnet with
+ /// a new shared network.
+ ///
+ /// @param shared_network Pointer to a new shared network to be associated
+ /// with the subnet.
+ void setSharedNetwork(const NetworkPtr& shared_network) {
+ parent_network_ = shared_network;
+ }
+
+ /// @brief Returns shared network name.
+ ///
+ /// @return shared network name
+ std::string getSharedNetworkName() const {
+ return (shared_network_name_);
+ }
+
+ /// @brief Sets new shared network name.
+ ///
+ /// In certain cases the subnet must be associated with the shared network
+ /// but the shared network object is not available. In particular, subnets
+ /// are returned from the configuration database with only names of the
+ /// shared networks. The actual shared networks must be fetched from the
+ /// database using a separate query. In order to not loose associations
+ /// of subnets with shared networks, the configuration backends will use
+ /// this method to store the shared network names. The servers will later
+ /// use those names to associate subnets with shared network instances.
+ ///
+ /// @param shared_network_name New shared network name.
+ void setSharedNetworkName(const std::string& shared_network_name) {
+ shared_network_name_ = shared_network_name;
+ }
+
+ /// @brief Returns all pools (non-const variant).
+ ///
+ /// The reference is only valid as long as the object that returned it.
+ ///
+ /// @param type lease type to be set
+ /// @return a collection of all pools
+ PoolCollection& getPoolsWritable(Lease::Type type);
+
+ /// @brief Returns lease allocator instance.
+ ///
+ /// An allocator is responsible for selecting leases from the subnet's
+ /// pools. Each subnet has one allocator common for all pools belonging
+ /// to the subnet. The allocation engine uses this function to get the
+ /// current subnet allocator and uses it to select and offer an address.
+ ///
+ /// @param type lease type for which the allocator instance should be
+ /// returned.
+ /// @return Allocator instance.
+ AllocatorPtr getAllocator(Lease::Type type) const;
+
+ /// @brief Sets new allocator instance.
+ ///
+ /// If the server is configured to use a different allocator for the
+ /// subnet, it can set the current allocator with this function.
+ ///
+ /// @param type lease type for which the allocator is set.
+ /// @param allocator new allocator instance.
+ void setAllocator(Lease::Type type, const AllocatorPtr& allocator);
+
+ /// @brief Returns subnet-specific allocation state.
+ ///
+ /// The actual type of the state depends on the allocator type.
+ ///
+ /// @param type lease type for which the allocation state is returned.
+ /// @return allocation state.
+ SubnetAllocationStatePtr getAllocationState(Lease::Type type) const;
+
+ /// @brief Sets subnet-specific allocation state.
+ ///
+ /// @param type lease type for which the allocation state is set.
+ /// @param allocation_state allocation state instance.
+ void setAllocationState(Lease::Type type, const SubnetAllocationStatePtr& allocation_state);
+
+ /// @brief Instantiates the allocators and their states.
+ ///
+ /// It determines the types of the allocators to create using the list of
+ /// the allocator types specified with the @c Network::setAllocatorType method.
+ ///
+ /// This function is called from the subnet parsers and after fetching
+ /// the subnet configuration from a configuration backend.
+ virtual void createAllocators() = 0;
+
+ /// @brief Calls @c initAfterConfigure for each allocator.
+ void initAllocatorsAfterConfigure();
+
+protected:
+
+ /// @brief Protected constructor.
+ //
+ /// By making the constructor protected, we make sure that no one will
+ /// ever instantiate that class. Subnet4 and Subnet6 should be used instead.
+ ///
+ /// This constructor assigns a new subnet-id (see @ref generateNextID).
+ /// This subnet-id has unique value that is strictly monotonously increasing
+ /// for each subnet, until it is explicitly reset back to 1 during
+ /// reconfiguration process.
+ ///
+ /// @param prefix subnet prefix
+ /// @param len prefix length for the subnet
+ /// @param id arbitrary subnet id, value of 0 triggers autogeneration
+ /// of subnet id
+ Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
+ const SubnetID id);
+
+ /// @brief virtual destructor.
+ ///
+ /// A virtual destructor is needed because other classes
+ /// derive from this class.
+ virtual ~Subnet() { };
+
+ /// @brief keeps the subnet-id value.
+ ///
+ /// It is incremented every time a new Subnet object is created. It is reset
+ /// (@ref resetSubnetID) every time reconfiguration
+ /// occurs.
+ ///
+ /// Static value initialized in subnet.cc.
+ static SubnetID static_id_;
+
+ /// @brief returns the next unique Subnet-ID.
+ ///
+ /// This method generates and returns the next unique subnet-id.
+ /// It is a strictly monotonously increasing value (1,2,3,...) for
+ /// each new Subnet object created. It can be explicitly reset
+ /// back to 1 during reconfiguration (@ref resetSubnetID).
+ ///
+ /// @return the next unique Subnet-ID
+ static SubnetID generateNextID() {
+ if (static_id_ == SUBNET_ID_MAX) {
+ resetSubnetID();
+ }
+
+ return (static_id_++);
+ }
+
+ /// @brief Checks if used pool type is valid.
+ ///
+ /// Allowed type for Subnet4 is Pool::TYPE_V4.
+ /// Allowed types for Subnet6 are Pool::TYPE_{IA,TA,PD}.
+ /// This method is implemented in derived classes.
+ ///
+ /// @param type type to be checked
+ /// @throw BadValue if invalid value is used
+ virtual void checkType(Lease::Type type) const = 0;
+
+ /// @brief Returns a sum of possible leases in all pools.
+ ///
+ /// @param pools list of pools
+ /// @return sum of possible leases
+ isc::util::uint128_t sumPoolCapacity(const PoolCollection& pools) const;
+
+ /// @brief Returns a sum of possible leases in all pools allowing classes.
+ ///
+ /// @param pools list of pools
+ /// @param client_classes list of classes
+ /// @return sum of possible/allowed leases
+ isc::util::uint128_t sumPoolCapacity(const PoolCollection& pools,
+ const ClientClasses& client_classes) const;
+
+ /// @brief Returns a sum of possible leases in all pools allowing classes
+ /// and matching selection criteria relative to provided hint prefix length.
+ ///
+ /// @note This function should be called only for PD pools.
+ ///
+ /// @param pools list of pools
+ /// @param client_classes list of classes
+ /// @param prefix_length_match type which indicates the selection criteria
+ /// for the pools relative to the provided hint prefix length
+ /// @param hint_prefix_length the hint prefix length that the client
+ /// provided
+ /// @return sum of possible/allowed leases
+ isc::util::uint128_t sumPoolCapacity(const PoolCollection& pools,
+ const ClientClasses& client_classes,
+ Allocator::PrefixLenMatchType prefix_length_match,
+ uint8_t hint_prefix_length) const;
+
+ /// @brief Checks if the specified pool overlaps with an existing pool.
+ ///
+ /// @param pool_type Pool type.
+ /// @param pool Pointer to a pool for which the method should check if
+ /// it overlaps with any existing pool within this subnet.
+ ///
+ /// @return true if pool overlaps with an existing pool of a specified
+ /// type, false otherwise
+ bool poolOverlaps(const Lease::Type& pool_type, const PoolPtr& pool) const;
+
+ /// @brief Unparse a subnet object.
+ ///
+ /// @return A pointer to unparsed subnet configuration.
+ virtual data::ElementPtr toElement() const;
+
+ virtual std::string getLabel() const {
+ std::stringstream ss;
+ ss << "subnet-id " << id_;
+ return (ss.str());
+ }
+
+ /// @brief Converts subnet prefix to a pair of prefix/length pair.
+ ///
+ /// IPv4 and IPv6 specific conversion functions should apply extra checks
+ /// on the returned values, i.e. whether length is in range and the IP
+ /// address has a valid type.
+ ///
+ /// @param prefix Prefix to be parsed.
+ /// @throw BadValue if provided prefix is not valid.
+ static std::pair<asiolink::IOAddress, uint8_t>
+ parsePrefixCommon(const std::string& prefix);
+
+ /// @brief subnet-id
+ ///
+ /// Subnet-id is a unique value that can be used to find or identify
+ /// a Subnet4 or Subnet6.
+ SubnetID id_;
+
+ /// @brief collection of IPv4 or non-temporary IPv6 pools in that subnet.
+ PoolCollection pools_;
+
+ /// @brief collection of IPv6 temporary address pools in that subnet.
+ PoolCollection pools_ta_;
+
+ /// @brief collection of IPv6 prefix pools in that subnet.
+ PoolCollection pools_pd_;
+
+ /// @brief a prefix of the subnet.
+ isc::asiolink::IOAddress prefix_;
+
+ /// @brief a prefix length of the subnet.
+ uint8_t prefix_len_;
+
+ /// @brief Shared network name.
+ std::string shared_network_name_;
+
+ /// @brief Lease allocators used by the subnet.
+ std::map<Lease::Type, AllocatorPtr> allocators_;
+
+ /// @brief Holds subnet-specific allocation state.
+ std::map<Lease::Type, SubnetAllocationStatePtr> allocation_states_;
+};
+
+/// @brief A generic pointer to either Subnet4 or Subnet6 object
+typedef boost::shared_ptr<Subnet> SubnetPtr;
+
+
+class Subnet4;
+
+/// @brief A const pointer to a @c Subnet4 object.
+typedef boost::shared_ptr<const Subnet4> ConstSubnet4Ptr;
+
+/// @brief A pointer to a @c Subnet4 object.
+typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
+
+/// @brief A configuration holder for IPv4 subnet.
+///
+/// This class represents an IPv4 subnet.
+/// @note Subnet and Network use virtual inheritance to avoid
+/// a diamond issue with UserContext
+class Subnet4 : public Subnet, public Network4, public boost::enable_shared_from_this<Subnet4> {
+public:
+
+ /// @brief Constructor with all parameters.
+ ///
+ /// This constructor calls Subnet::Subnet, where subnet-id is generated.
+ ///
+ /// @param prefix Subnet4 prefix
+ /// @param length prefix length
+ /// @param t1 renewal timer (in seconds)
+ /// @param t2 rebind timer (in seconds)
+ /// @param valid_lifetime preferred lifetime of leases (in seconds)
+ /// @param id arbitrary subnet id, no default value
+ Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
+ const util::Triplet<uint32_t>& t1,
+ const util::Triplet<uint32_t>& t2,
+ const util::Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id);
+
+ /// @brief Factory function creating an instance of the @c Subnet4.
+ ///
+ /// This function should be used to create an instance of the subnet
+ /// object within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// It associates the subnet with the default, iterative, allocator.
+ /// Therefore, using this function should be preferred over the
+ /// constructor whenever the subnet needs a default allocator.
+ ///
+ /// @param prefix Subnet4 prefix
+ /// @param length prefix length
+ /// @param t1 renewal timer (in seconds)
+ /// @param t2 rebind timer (in seconds)
+ /// @param valid_lifetime preferred lifetime of leases (in seconds)
+ /// @param id arbitrary subnet id, no default value
+ ///
+ /// @return Pointer to the @c Subnet4 instance.
+ static Subnet4Ptr
+ create(const isc::asiolink::IOAddress& prefix, uint8_t length,
+ const util::Triplet<uint32_t>& t1,
+ const util::Triplet<uint32_t>& t2,
+ const util::Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id);
+
+ /// @brief Returns next subnet within shared network.
+ ///
+ /// If the current subnet doesn't belong to any shared network or if
+ /// the next subnet is the same as first subnet (specified in the
+ /// argument) a NULL pointer is returned.
+ ///
+ /// @param first_subnet Pointer to the subnet from which iterations have
+ /// started.
+ ///
+ /// @return Pointer to the next subnet or NULL pointer if the next subnet
+ /// is the first subnet or if the current subnet doesn't belong to a
+ /// shared network.
+ Subnet4Ptr getNextSubnet(const Subnet4Ptr& first_subnet) const;
+
+ /// @brief Returns next subnet within shared network that matches
+ /// client classes.
+ ///
+ /// @param first_subnet Pointer to the subnet from which iterations have
+ /// started.
+ /// @param client_classes List of classes that the client belongs to.
+ /// The subnets not matching the classes aren't returned by this
+ /// method.
+ ///
+ /// @return Pointer to the next subnet or NULL pointer if the next subnet
+ /// is the first subnet or if the current subnet doesn't belong to a
+ /// shared network.
+ Subnet4Ptr getNextSubnet(const Subnet4Ptr& first_subnet,
+ const ClientClasses& client_classes) const;
+
+ /// @brief Checks whether this subnet and parent shared network supports
+ /// the client that belongs to specified classes.
+ ///
+ /// This method extends the @ref Network::clientSupported method with
+ /// additional checks whether shared network owning this class supports
+ /// the client belonging to specified classes. If the class doesn't
+ /// belong to a shared network this method only checks if the subnet
+ /// supports specified classes.
+ ///
+ /// @param client_classes List of classes the client belongs to.
+ /// @return true if client can be supported, false otherwise.
+ virtual bool
+ clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
+
+ /// @brief Returns DHCP4o6 configuration parameters.
+ ///
+ /// This structure is always available. If the 4o6 is not enabled, its
+ /// enabled_ field will be set to false.
+ Cfg4o6& get4o6() {
+ return (dhcp4o6_);
+ }
+
+ /// @brief Returns const DHCP4o6 configuration parameters.
+ ///
+ /// This structure is always available. If the 4o6 is not enabled, its
+ /// enabled_ field will be set to false.
+ const Cfg4o6& get4o6() const {
+ return (dhcp4o6_);
+ }
+
+ /// @brief Unparse a subnet object.
+ ///
+ /// @return A pointer to unparsed subnet configuration.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief Instantiates the allocator and its state.
+ ///
+ /// It uses the type of the allocator specified with the
+ /// @c Network::setAllocatorType method.
+ ///
+ /// This function is called from the subnet parsers and after fetching
+ /// the subnet configuration from a configuration backend.
+ virtual void createAllocators();
+
+ /// @brief Converts subnet prefix to a pair of prefix/length pair.
+ ///
+ /// @param prefix Prefix to be parsed.
+ /// @throw BadValue if provided invalid IPv4 prefix.
+ static std::pair<asiolink::IOAddress, uint8_t>
+ parsePrefix(const std::string& prefix);
+
+private:
+
+ /// @brief Deleted copy constructor.
+ Subnet4(const Subnet4&) = delete;
+
+ /// @brief Deleted assignment operator.
+ Subnet4& operator=(const Subnet4&) = delete;
+
+ /// @brief Returns default address for pool selection.
+ ///
+ /// @return ANY IPv4 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("0.0.0.0"));
+ }
+
+ /// @brief Checks if used pool type is valid.
+ ///
+ /// Allowed type for Subnet4 is Pool::TYPE_V4.
+ ///
+ /// @param type type to be checked
+ /// @throw BadValue if invalid value is used
+ virtual void checkType(Lease::Type type) const;
+
+ /// @brief All the information related to DHCP4o6
+ Cfg4o6 dhcp4o6_;
+};
+
+class Subnet6;
+
+/// @brief A const pointer to a @c Subnet6 object.
+typedef boost::shared_ptr<const Subnet6> ConstSubnet6Ptr;
+
+/// @brief A pointer to a Subnet6 object
+typedef boost::shared_ptr<Subnet6> Subnet6Ptr;
+
+/// @brief A configuration holder for IPv6 subnet.
+///
+/// This class represents an IPv6 subnet.
+/// @note Subnet and Network use virtual inheritance to avoid
+/// a diamond issue with UserContext
+class Subnet6 : public Subnet, public Network6, public boost::enable_shared_from_this<Subnet6> {
+public:
+
+ /// @brief Constructor with all parameters.
+ ///
+ /// This constructor calls Subnet::Subnet, where subnet-id is generated.
+ ///
+ /// @param prefix Subnet6 prefix
+ /// @param length prefix length
+ /// @param t1 renewal timer (in seconds)
+ /// @param t2 rebind timer (in seconds)
+ /// @param preferred_lifetime preferred lifetime of leases (in seconds)
+ /// @param valid_lifetime preferred lifetime of leases (in seconds)
+ /// @param id arbitrary subnet id, no default value
+ /// autogeneration of subnet id
+ Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
+ const util::Triplet<uint32_t>& t1,
+ const util::Triplet<uint32_t>& t2,
+ const util::Triplet<uint32_t>& preferred_lifetime,
+ const util::Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id);
+
+ /// @brief Factory function creating an instance of the @c Subnet4.
+ ///
+ /// This function should be used to create an instance of the subnet
+ /// object within a hooks library in cases when the library may be
+ /// unloaded before the object is destroyed. This ensures that the
+ /// ownership of the object by the Kea process is retained.
+ ///
+ /// It associates the subnet with the default, iterative, allocator.
+ /// Therefore, using this function should be preferred over the
+ /// constructor whenever the subnet needs a default allocator.
+ ///
+ /// @param prefix Subnet6 prefix
+ /// @param length prefix length
+ /// @param t1 renewal timer (in seconds)
+ /// @param t2 rebind timer (in seconds)
+ /// @param preferred_lifetime preferred lifetime of leases (in seconds)
+ /// @param valid_lifetime preferred lifetime of leases (in seconds)
+ /// @param id arbitrary subnet id, no default value
+ ///
+ /// @return Pointer to the @c Subnet6 instance.
+ static Subnet6Ptr
+ create(const isc::asiolink::IOAddress& prefix, uint8_t length,
+ const util::Triplet<uint32_t>& t1,
+ const util::Triplet<uint32_t>& t2,
+ const util::Triplet<uint32_t>& preferred_lifetime,
+ const util::Triplet<uint32_t>& valid_lifetime,
+ const SubnetID id);
+
+ /// @brief Returns next subnet within shared network.
+ ///
+ /// If the current subnet doesn't belong to any shared network or if
+ /// the next subnet is the same as first subnet (specified in the
+ /// arguments) a NULL pointer is returned.
+ ///
+ /// @param first_subnet Pointer to the subnet from which iterations have
+ /// started.
+ ///
+ /// @return Pointer to the next subnet or NULL pointer if the next subnet
+ /// is the first subnet or if the current subnet doesn't belong to a
+ /// shared network.
+ Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet) const;
+
+ /// @brief Returns next subnet within shared network that matches
+ /// client classes.
+ ///
+ /// @param first_subnet Pointer to the subnet from which iterations have
+ /// started.
+ /// @param client_classes List of classes that the client belongs to.
+ /// The subnets not matching the classes aren't returned by this
+ /// method.
+ ///
+ /// @return Pointer to the next subnet or NULL pointer if the next subnet
+ /// is the first subnet or if the current subnet doesn't belong to a
+ /// shared network.
+ Subnet6Ptr getNextSubnet(const Subnet6Ptr& first_subnet,
+ const ClientClasses& client_classes) const;
+
+ /// @brief Checks whether this subnet and parent shared network supports
+ /// the client that belongs to specified classes.
+ ///
+ /// This method extends the @ref Network::clientSupported method with
+ /// additional checks whether shared network owning this class supports
+ /// the client belonging to specified classes. If the class doesn't
+ /// belong to a shared network this method only checks if the subnet
+ /// supports specified classes.
+ ///
+ /// @param client_classes List of classes the client belongs to.
+ /// @return true if client can be supported, false otherwise.
+ virtual bool
+ clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
+
+ /// @brief Instantiates the allocators and their states.
+ ///
+ /// It determines the types of the allocators to create using the list of
+ /// the allocator types specified with the @c Network::setAllocatorType method.
+ ///
+ /// This function is called from the subnet parsers and after fetching
+ /// the subnet configuration from a configuration backend.
+ virtual void createAllocators();
+
+ /// @brief Unparse a subnet object.
+ ///
+ /// @return A pointer to unparsed subnet configuration.
+ virtual data::ElementPtr toElement() const;
+
+ /// @brief Converts subnet prefix to a pair of prefix/length pair.
+ ///
+ /// @param prefix Prefix to be parsed.
+ /// @throw BadValue if provided invalid IPv4 prefix.
+ static std::pair<asiolink::IOAddress, uint8_t>
+ parsePrefix(const std::string& prefix);
+
+private:
+
+ /// @brief Deleted copy constructor.
+ Subnet6(const Subnet6&) = delete;
+
+ /// @brief Deleted assignment operator.
+ Subnet6& operator=(const Subnet6&) = delete;
+
+ /// @brief Returns default address for pool selection
+ ///
+ /// @return ANY IPv6 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("::"));
+ }
+
+ /// @brief Checks if used pool type is valid
+ ///
+ /// allowed types for Subnet6 are Pool::TYPE_{IA,TA,PD}.
+ ///
+ /// @param type type to be checked
+ /// @throw BadValue if invalid value is used
+ virtual void checkType(Lease::Type type) const;
+
+};
+
+/// @name Definition of the multi index container holding subnet information
+///
+//@{
+
+/// @brief Tag for the index for searching by subnet identifier.
+struct SubnetSubnetIdIndexTag { };
+
+/// @brief Tag for the index for searching by subnet prefix.
+struct SubnetPrefixIndexTag { };
+
+/// @brief Tag for the index for searching by server identifier.
+struct SubnetServerIdIndexTag { };
+
+/// @brief Tag for the index for searching by subnet modification time.
+struct SubnetModificationTimeIndexTag { };
+
+/// @brief A simple collection of @c Subnet4 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by subnet identifier and subnet prefix.
+///
+/// The random access index is used for direct iteration over the collection.
+typedef boost::multi_index_container<
+ // Multi index container holds pointers to the subnets.
+ Subnet4Ptr,
+ // The following holds all indexes.
+ boost::multi_index::indexed_by<
+ // First index allows for searching using subnet identifier.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetSubnetIdIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+ >,
+ // Second index allows for searching using an output from toText function.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetPrefixIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+ >
+ >
+> Subnet4SimpleCollection;
+
+/// @brief A collection of @c Subnet4 objects.
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
+///
+/// This multi index container can hold pointers to @ref Subnet4
+/// objects representing subnets. It provides indexes for subnet lookups
+/// using subnet properties such as: subnet identifier,
+/// subnet prefix or server identifier specified for a subnet. It also
+/// provides a random access index which allows for using the container
+/// like a vector.
+///
+/// The random access index is used by the DHCP servers which perform
+/// a full scan on subnets to find the one that matches some specific
+/// criteria for subnet selection.
+///
+/// The remaining indexes are used for searching for a specific subnet
+/// as a result of receiving a command over the control API, e.g.
+/// when 'subnet-get' command is received.
+///
+/// @todo We should consider optimizing subnet selection by leveraging
+/// the indexing capabilities of this container, e.g. searching for
+/// a subnet by interface name, relay address etc.
+typedef boost::multi_index_container<
+ // Multi index container holds pointers to the subnets.
+ Subnet4Ptr,
+ // The following holds all indexes.
+ boost::multi_index::indexed_by<
+ // First index allows for searching using subnet identifier.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetSubnetIdIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+ >,
+ // Second index allows for searching using an output from toText function.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetPrefixIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+ >,
+
+ // Third index allows for searching using an output from getServerId.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetServerIdIndexTag>,
+ boost::multi_index::const_mem_fun<Network4, asiolink::IOAddress,
+ &Network4::getServerId>
+ >,
+
+ // Forth index allows for searching using subnet modification time.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetModificationTimeIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement,
+ boost::posix_time::ptime,
+ &data::BaseStampedElement::getModificationTime>
+ >
+ >
+> Subnet4Collection;
+
+/// @brief A simple collection of @c Subnet6 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by subnet identifier and subnet prefix.
+///
+/// The random access index is used for direct iteration over the collection.
+typedef boost::multi_index_container<
+ // Multi index container holds pointers to the subnets.
+ Subnet6Ptr,
+ // The following holds all indexes.
+ boost::multi_index::indexed_by<
+ // First index allows for searching using subnet identifier.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetSubnetIdIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+ >,
+ // Second index allows for searching using an output from toText function.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetPrefixIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+ >
+ >
+> Subnet6SimpleCollection;
+
+/// @brief A collection of @c Subnet6 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
+///
+/// This multi index container can hold pointers to @ref Subnet6 objects
+/// representing subnets. It provides indexes for subnet lookups using
+/// subnet properties such as: subnet identifier or subnet prefix. It
+/// also provides a random access index which allows for using the
+/// container like a vector.
+///
+/// The random access index is used by the DHCP servers which perform
+/// a full scan on subnets to find the one that matches some specific
+/// criteria for subnet selection.
+///
+/// The remaining indexes are used for searching for a specific subnet
+/// as a result of receiving a command over the control API, e.g.
+/// when 'subnet-get' command is received.
+///
+/// @todo We should consider optimizing subnet selection by leveraging
+/// the indexing capabilities of this container, e.g. searching for
+/// a subnet by interface name, relay address etc.
+typedef boost::multi_index_container<
+ // Multi index container holds pointers to the subnets.
+ Subnet6Ptr,
+ // The following holds all indexes.
+ boost::multi_index::indexed_by<
+ // First index allows for searching using subnet identifier.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetSubnetIdIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+ >,
+ // Second index allows for searching using an output from toText function.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetPrefixIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+ >,
+ // Third index allows for searching using subnet modification time.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetModificationTimeIndexTag>,
+ boost::multi_index::const_mem_fun<data::BaseStampedElement,
+ boost::posix_time::ptime,
+ &data::BaseStampedElement::getModificationTime>
+ >
+ >
+> Subnet6Collection;
+
+/// @brief A class containing static convenience methods to fetch the subnets
+/// from the containers.
+///
+/// @tparam ReturnPtrType Type of the returned object, i.e. @c Subnet4Ptr
+/// or @c Subnet6Ptr.
+/// @tparam CollectionType One of the @c Subnet4SimpleCollection,
+/// @c Subnet4Collection, @c Subnet6SimpleCollection or @c Subnet6Collection.
+template<typename ReturnPtrType, typename CollectionType>
+class SubnetFetcher {
+public:
+
+ /// @brief Fetches subnets by id.
+ ///
+ /// @param collection Const reference to the collection from which the
+ /// subnet is to be fetched.
+ /// @param subnet_id Id of the subnet to be fetched.
+ /// @return Pointer to the fetched subnet or null if no such subnet
+ /// could be found.
+ static ReturnPtrType get(const CollectionType& collection,
+ const SubnetID& subnet_id) {
+ auto& index = collection.template get<SubnetSubnetIdIndexTag>();
+ auto s = index.find(subnet_id);
+ if (s != index.end()) {
+ return (*s);
+ }
+ // No subnet found. Return null pointer.
+ return (ReturnPtrType());
+ }
+};
+
+/// @brief Type of the @c SubnetFetcher used for IPv4.
+using SubnetFetcher4 = SubnetFetcher<Subnet4Ptr, Subnet4Collection>;
+
+/// @brief Type of the @c SubnetFetcher used for IPv6.
+using SubnetFetcher6 = SubnetFetcher<Subnet6Ptr, Subnet6Collection>;
+//@}
+
+/// @brief adapters for linking templates to qualified names
+/// @{
+namespace {
+
+template <isc::util::DhcpSpace D>
+struct AdapterSubnet {};
+
+template <>
+struct AdapterSubnet<isc::util::DHCPv4> {
+ using type = Subnet4;
+};
+
+template <>
+struct AdapterSubnet<isc::util::DHCPv6> {
+ using type = Subnet6;
+};
+
+} // namespace
+
+template <isc::util::DhcpSpace D>
+using SubnetT = typename AdapterSubnet<D>::type;
+
+template <isc::util::DhcpSpace D>
+using SubnetTPtr = boost::shared_ptr<SubnetT<D>>;
+/// @}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // SUBNET_H
diff --git a/src/lib/dhcpsrv/subnet_id.h b/src/lib/dhcpsrv/subnet_id.h
new file mode 100644
index 0000000..aff581b
--- /dev/null
+++ b/src/lib/dhcpsrv/subnet_id.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2014-2022 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 SUBNET_ID_H
+#define SUBNET_ID_H
+
+#include <exceptions/exceptions.h>
+#include <stdint.h>
+#include <typeinfo>
+#include <limits>
+#include <set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Defines unique IPv4 or IPv6 subnet identifier.
+///
+/// Each subnet for which the DHCP service has been configured is identified
+/// by the unique value called subnet id. Right now it is represented as
+/// a simple unsigned integer. In the future it may be extended to more complex
+/// type.
+typedef uint32_t SubnetID;
+
+/// @brief Special value is used for storing/recognizing global host reservations.
+static const SubnetID SUBNET_ID_GLOBAL = 0;
+/// @brief The largest valid value for auto-generated subnet IDs.
+static const SubnetID SUBNET_ID_MAX = std::numeric_limits<uint32_t>::max()-1;
+/// @brief Special value used to signify that a SubnetID is "not set"
+static const SubnetID SUBNET_ID_UNUSED = std::numeric_limits<uint32_t>::max();
+
+/// @brief Exception thrown upon attempt to add subnet with an ID that belongs
+/// to the subnet that already exists.
+class DuplicateSubnetID : public Exception {
+public:
+ DuplicateSubnetID(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Ordered list aka set of subnetIDs.
+typedef std::set<dhcp::SubnetID> SubnetIDSet;
+
+}
+}
+
+#endif // SUBNET_ID_H
diff --git a/src/lib/dhcpsrv/subnet_selector.h b/src/lib/dhcpsrv/subnet_selector.h
new file mode 100644
index 0000000..559fac5
--- /dev/null
+++ b/src/lib/dhcpsrv/subnet_selector.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2014-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/.
+
+#ifndef SUBNET_SELECTOR_H
+#define SUBNET_SELECTOR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Subnet selector used to specify parameters used to select a subnet.
+///
+/// This structure holds various parameters extracted from a packet sent
+/// by a DHCP client used to select the subnet for the client. This selector
+/// is common for IPv4 and IPv6 subnets.
+struct SubnetSelector {
+ /// @name DHCPv4 specific parameters.
+ //@{
+ /// @brief ciaddr from the client's message.
+ asiolink::IOAddress ciaddr_;
+ /// @brief giaddr from the client's message.
+ asiolink::IOAddress giaddr_;
+ /// @brief RAI link select or subnet select option
+ asiolink::IOAddress option_select_;
+ //@}
+
+ /// @name DHCPv6 specific parameters.
+ //@{
+ /// @brief Interface id option.
+ OptionPtr interface_id_;
+ /// @brief First relay link address.
+ asiolink::IOAddress first_relay_linkaddr_;
+ //@}
+
+ /// @brief Address on which the message was received.
+ asiolink::IOAddress local_address_;
+ /// @brief Source address of the message.
+ asiolink::IOAddress remote_address_;
+ /// @brief Classes that the client belongs to.
+ ClientClasses client_classes_;
+ /// @brief Name of the interface on which the message was received.
+ std::string iface_name_;
+
+ /// @brief Specifies if the packet is DHCP4o6
+ bool dhcp4o6_;
+
+ /// @brief Default constructor.
+ ///
+ /// Sets the default values for the @c Selector.
+ SubnetSelector()
+ : ciaddr_(asiolink::IOAddress("0.0.0.0")),
+ giaddr_(asiolink::IOAddress("0.0.0.0")),
+ option_select_(asiolink::IOAddress("0.0.0.0")),
+ interface_id_(),
+ first_relay_linkaddr_(asiolink::IOAddress("::")),
+ local_address_(asiolink::IOAddress("0.0.0.0")),
+ remote_address_(asiolink::IOAddress("0.0.0.0")),
+ client_classes_(), iface_name_(std::string()),
+ dhcp4o6_(false) {
+ }
+};
+
+
+}
+}
+
+#endif // SUBNET_SELECTOR_H
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
new file mode 100644
index 0000000..a73d0f3
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -0,0 +1,197 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
+AM_CPPFLAGS += -DDHCP_DATA_DIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\"
+AM_CPPFLAGS += -DKEA_LFC_BUILD_DIR=\"$(abs_top_builddir)/src/bin/lfc\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+DISTCLEANFILES = test_libraries.h
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+# Build shared libraries for testing. The libtool way to create a shared library
+# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS
+# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html).
+# Use of these switches will guarantee that the .so files are created in the
+# .libs folder and they can be dlopened.
+# Note that the shared libraries with callouts should not be used together with
+# the --enable-static-link option. With this option, the bind10 libraries are
+# statically linked with the program and if the callout invokes the methods
+# which belong to these libraries, the library with the callout will get its
+# own copy of the static objects (e.g. logger, ServerHooks) and that will lead
+# to unexpected errors. For this reason, the --enable-static-link option is
+# ignored for unit tests built here.
+
+noinst_LTLIBRARIES = libco1.la libco2.la libco3.la
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+
+libco1_la_SOURCES = callout_library.cc
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco1_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+libco2_la_SOURCES = callout_library.cc
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+libco3_la_SOURCES = callout_params_library.cc
+libco3_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco3_la_CPPFLAGS = $(AM_CPPFLAGS)
+libco3_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+TESTS += libdhcpsrv_unittests
+
+libdhcpsrv_unittests_SOURCES = run_unittests.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine_utils.cc alloc_engine_utils.h
+libdhcpsrv_unittests_SOURCES += alloc_engine_expiration_unittest.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine4_unittest.cc
+libdhcpsrv_unittests_SOURCES += alloc_engine6_unittest.cc
+libdhcpsrv_unittests_SOURCES += allocation_state_unittest.cc
+libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
+libdhcpsrv_unittests_SOURCES += cb_ctl_dhcp_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_db_access_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_duid_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_expiration_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_host_operations_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_mac_source_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_multi_threading_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_rsoo_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_shared_networks4_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_shared_networks6_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += client_class_def_unittest.cc
+libdhcpsrv_unittests_SOURCES += client_class_def_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
+libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
+libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
+libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
+libdhcpsrv_unittests_SOURCES += dhcp_queue_control_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += dhcp4o6_ipc_unittest.cc
+libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += flq_allocation_state_unittest.cc
+libdhcpsrv_unittests_SOURCES += flq_allocator_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_cache_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_data_source_factory_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_reservation_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_reservations_list_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += ifaces_config_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += ip_range_unittest.cc
+libdhcpsrv_unittests_SOURCES += ip_range_permutation_unittest.cc
+libdhcpsrv_unittests_SOURCES += iterative_allocation_state_unittest.cc
+libdhcpsrv_unittests_SOURCES += iterative_allocator_unittest.cc
+libdhcpsrv_unittests_SOURCES += lease_file_loader_unittest.cc
+libdhcpsrv_unittests_SOURCES += lease_unittest.cc
+libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
+libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += generic_lease_mgr_unittest.cc generic_lease_mgr_unittest.h
+libdhcpsrv_unittests_SOURCES += memfile_lease_extended_info_unittest.cc
+libdhcpsrv_unittests_SOURCES += memfile_lease_limits_unittest.cc
+libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += multi_threading_config_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
+libdhcpsrv_unittests_SOURCES += ncr_generator_unittest.cc
+if HAVE_MYSQL
+libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += mysql_lease_extended_info_unittest.cc
+libdhcpsrv_unittests_SOURCES += mysql_host_data_source_unittest.cc
+endif
+if HAVE_PGSQL
+libdhcpsrv_unittests_SOURCES += pgsql_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += pgsql_lease_extended_info_unittest.cc
+libdhcpsrv_unittests_SOURCES += pgsql_host_data_source_unittest.cc
+endif
+libdhcpsrv_unittests_SOURCES += pool_unittest.cc
+libdhcpsrv_unittests_SOURCES += random_allocation_state_unittest.cc
+libdhcpsrv_unittests_SOURCES += random_allocator_unittest.cc
+libdhcpsrv_unittests_SOURCES += resource_handler_unittest.cc
+libdhcpsrv_unittests_SOURCES += sanity_checks_unittest.cc
+libdhcpsrv_unittests_SOURCES += shared_network_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += shared_network_unittest.cc
+libdhcpsrv_unittests_SOURCES += shared_networks_list_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += srv_config_unittest.cc
+libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
+libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h
+libdhcpsrv_unittests_SOURCES += timer_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += tracking_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += network_state_unittest.cc
+libdhcpsrv_unittests_SOURCES += network_unittest.cc
+
+libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+if HAVE_MYSQL
+libdhcpsrv_unittests_CPPFLAGS += $(MYSQL_CPPFLAGS)
+endif
+if HAVE_PGSQL
+libdhcpsrv_unittests_CPPFLAGS += $(PGSQL_CPPFLAGS)
+endif
+
+libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+libdhcpsrv_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+if HAVE_MYSQL
+libdhcpsrv_unittests_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+libdhcpsrv_unittests_LDFLAGS += $(PGSQL_LIBS)
+endif
+
+libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+
+if HAVE_PGSQL
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+endif
+
+if HAVE_MYSQL
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+endif
+
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libdhcpsrv_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+libdhcpsrv_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dhcpsrv/tests/Makefile.in b/src/lib/dhcpsrv/tests/Makefile.in
new file mode 100644
index 0000000..dc4b2ea
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/Makefile.in
@@ -0,0 +1,2795 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libdhcpsrv_unittests
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_2 = \
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@ mysql_lease_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@ mysql_lease_extended_info_unittest.cc \
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@ mysql_host_data_source_unittest.cc
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_3 = \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ pgsql_lease_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ pgsql_lease_extended_info_unittest.cc \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ pgsql_host_data_source_unittest.cc
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_4 = $(MYSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_5 = $(PGSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_6 = $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_7 = $(PGSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_8 = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_9 = $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la \
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@ $(top_builddir)/src/lib/mysql/libkea-mysql.la
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/dhcpsrv/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = test_libraries.h
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = libdhcpsrv_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libco1_la_LIBADD =
+am__libco1_la_SOURCES_DIST = callout_library.cc
+@HAVE_GTEST_TRUE@am_libco1_la_OBJECTS = libco1_la-callout_library.lo
+libco1_la_OBJECTS = $(am_libco1_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 =
+libco1_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libco1_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libco1_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libco1_la_rpath =
+libco2_la_LIBADD =
+am__libco2_la_SOURCES_DIST = callout_library.cc
+@HAVE_GTEST_TRUE@am_libco2_la_OBJECTS = libco2_la-callout_library.lo
+libco2_la_OBJECTS = $(am_libco2_la_OBJECTS)
+libco2_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libco2_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libco2_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libco2_la_rpath =
+libco3_la_LIBADD =
+am__libco3_la_SOURCES_DIST = callout_params_library.cc
+@HAVE_GTEST_TRUE@am_libco3_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libco3_la-callout_params_library.lo
+libco3_la_OBJECTS = $(am_libco3_la_OBJECTS)
+libco3_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libco3_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libco3_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libco3_la_rpath =
+am__libdhcpsrv_unittests_SOURCES_DIST = run_unittests.cc \
+ alloc_engine_utils.cc alloc_engine_utils.h \
+ alloc_engine_expiration_unittest.cc \
+ alloc_engine_hooks_unittest.cc alloc_engine4_unittest.cc \
+ alloc_engine6_unittest.cc allocation_state_unittest.cc \
+ callout_handle_store_unittest.cc cb_ctl_dhcp_unittest.cc \
+ cfg_db_access_unittest.cc cfg_duid_unittest.cc \
+ cfg_expiration_unittest.cc cfg_host_operations_unittest.cc \
+ cfg_hosts_unittest.cc cfg_iface_unittest.cc \
+ cfg_mac_source_unittest.cc cfg_multi_threading_unittest.cc \
+ cfg_option_unittest.cc cfg_option_def_unittest.cc \
+ cfg_rsoo_unittest.cc cfg_shared_networks4_unittest.cc \
+ cfg_shared_networks6_unittest.cc cfg_subnets4_unittest.cc \
+ cfg_subnets6_unittest.cc cfgmgr_unittest.cc \
+ client_class_def_unittest.cc \
+ client_class_def_parser_unittest.cc \
+ csv_lease_file4_unittest.cc csv_lease_file6_unittest.cc \
+ d2_client_unittest.cc d2_udp_unittest.cc \
+ dhcp_queue_control_parser_unittest.cc dhcp4o6_ipc_unittest.cc \
+ duid_config_parser_unittest.cc \
+ expiration_config_parser_unittest.cc \
+ flq_allocation_state_unittest.cc flq_allocator_unittest.cc \
+ host_cache_unittest.cc host_data_source_factory_unittest.cc \
+ host_mgr_unittest.cc host_unittest.cc \
+ host_reservation_parser_unittest.cc \
+ host_reservations_list_parser_unittest.cc \
+ ifaces_config_parser_unittest.cc ip_range_unittest.cc \
+ ip_range_permutation_unittest.cc \
+ iterative_allocation_state_unittest.cc \
+ iterative_allocator_unittest.cc lease_file_loader_unittest.cc \
+ lease_unittest.cc lease_mgr_factory_unittest.cc \
+ lease_mgr_unittest.cc generic_lease_mgr_unittest.cc \
+ generic_lease_mgr_unittest.h \
+ memfile_lease_extended_info_unittest.cc \
+ memfile_lease_limits_unittest.cc memfile_lease_mgr_unittest.cc \
+ multi_threading_config_parser_unittest.cc \
+ dhcp_parsers_unittest.cc ncr_generator_unittest.cc \
+ mysql_lease_mgr_unittest.cc \
+ mysql_lease_extended_info_unittest.cc \
+ mysql_host_data_source_unittest.cc pgsql_lease_mgr_unittest.cc \
+ pgsql_lease_extended_info_unittest.cc \
+ pgsql_host_data_source_unittest.cc pool_unittest.cc \
+ random_allocation_state_unittest.cc \
+ random_allocator_unittest.cc resource_handler_unittest.cc \
+ sanity_checks_unittest.cc shared_network_parser_unittest.cc \
+ shared_network_unittest.cc \
+ shared_networks_list_parser_unittest.cc srv_config_unittest.cc \
+ subnet_unittest.cc test_get_callout_handle.cc \
+ test_get_callout_handle.h timer_mgr_unittest.cc \
+ tracking_lease_mgr_unittest.cc network_state_unittest.cc \
+ network_unittest.cc
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__objects_1 = libdhcpsrv_unittests-mysql_lease_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@ libdhcpsrv_unittests-mysql_lease_extended_info_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@ libdhcpsrv_unittests-mysql_host_data_source_unittest.$(OBJEXT)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__objects_2 = libdhcpsrv_unittests-pgsql_lease_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@ libdhcpsrv_unittests-pgsql_host_data_source_unittest.$(OBJEXT)
+@HAVE_GTEST_TRUE@am_libdhcpsrv_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-alloc_engine_utils.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-alloc_engine_expiration_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-alloc_engine_hooks_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-alloc_engine4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-alloc_engine6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-allocation_state_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-callout_handle_store_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cb_ctl_dhcp_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_db_access_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_duid_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_expiration_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_host_operations_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_hosts_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_iface_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_mac_source_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_multi_threading_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_option_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_option_def_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_rsoo_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_shared_networks4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_shared_networks6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_subnets4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfg_subnets6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-cfgmgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-client_class_def_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-client_class_def_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-csv_lease_file4_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-csv_lease_file6_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-d2_client_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-d2_udp_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-dhcp4o6_ipc_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-duid_config_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-expiration_config_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-flq_allocation_state_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-flq_allocator_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-host_cache_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-host_data_source_factory_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-host_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-host_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-host_reservation_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-host_reservations_list_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-ifaces_config_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-ip_range_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-ip_range_permutation_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-iterative_allocation_state_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-iterative_allocator_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-lease_file_loader_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-lease_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-lease_mgr_factory_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-lease_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-generic_lease_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-memfile_lease_extended_info_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-memfile_lease_limits_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-memfile_lease_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-multi_threading_config_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-dhcp_parsers_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-ncr_generator_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-pool_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-random_allocation_state_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-random_allocator_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-resource_handler_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-sanity_checks_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-shared_network_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-shared_network_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-shared_networks_list_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-srv_config_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-subnet_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-test_get_callout_handle.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-timer_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-tracking_lease_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-network_state_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libdhcpsrv_unittests-network_unittest.$(OBJEXT)
+libdhcpsrv_unittests_OBJECTS = $(am_libdhcpsrv_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libdhcpsrv_unittests_DEPENDENCIES = $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(am__append_8) $(am__append_9) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/testutils/libdatabasetest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+libdhcpsrv_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libdhcpsrv_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)/libco1_la-callout_library.Plo \
+ ./$(DEPDIR)/libco2_la-callout_library.Plo \
+ ./$(DEPDIR)/libco3_la-callout_params_library.Plo \
+ ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-host_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-network_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Po \
+ ./$(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.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 = $(libco1_la_SOURCES) $(libco2_la_SOURCES) \
+ $(libco3_la_SOURCES) $(libdhcpsrv_unittests_SOURCES)
+DIST_SOURCES = $(am__libco1_la_SOURCES_DIST) \
+ $(am__libco2_la_SOURCES_DIST) $(am__libco3_la_SOURCES_DIST) \
+ $(am__libdhcpsrv_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)`
+ETAGS = etags
+CTAGS = ctags
+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 $(srcdir)/test_libraries.h.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\" \
+ -DDHCP_DATA_DIR=\"$(abs_top_builddir)/src/lib/dhcpsrv/tests\" \
+ -DKEA_LFC_BUILD_DIR=\"$(abs_top_builddir)/src/bin/lfc\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+DISTCLEANFILES = test_libraries.h
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+# Build shared libraries for testing. The libtool way to create a shared library
+# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS
+# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html).
+# Use of these switches will guarantee that the .so files are created in the
+# .libs folder and they can be dlopened.
+# Note that the shared libraries with callouts should not be used together with
+# the --enable-static-link option. With this option, the bind10 libraries are
+# statically linked with the program and if the callout invokes the methods
+# which belong to these libraries, the library with the callout will get its
+# own copy of the static objects (e.g. logger, ServerHooks) and that will lead
+# to unexpected errors. For this reason, the --enable-static-link option is
+# ignored for unit tests built here.
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libco1.la libco2.la libco3.la
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+@HAVE_GTEST_TRUE@libco1_la_SOURCES = callout_library.cc
+@HAVE_GTEST_TRUE@libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libco1_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+@HAVE_GTEST_TRUE@libco2_la_SOURCES = callout_library.cc
+@HAVE_GTEST_TRUE@libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+@HAVE_GTEST_TRUE@libco3_la_SOURCES = callout_params_library.cc
+@HAVE_GTEST_TRUE@libco3_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libco3_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libco3_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+@HAVE_GTEST_TRUE@libdhcpsrv_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ alloc_engine_utils.cc alloc_engine_utils.h \
+@HAVE_GTEST_TRUE@ alloc_engine_expiration_unittest.cc \
+@HAVE_GTEST_TRUE@ alloc_engine_hooks_unittest.cc \
+@HAVE_GTEST_TRUE@ alloc_engine4_unittest.cc \
+@HAVE_GTEST_TRUE@ alloc_engine6_unittest.cc \
+@HAVE_GTEST_TRUE@ allocation_state_unittest.cc \
+@HAVE_GTEST_TRUE@ callout_handle_store_unittest.cc \
+@HAVE_GTEST_TRUE@ cb_ctl_dhcp_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_db_access_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_duid_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_expiration_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_host_operations_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_hosts_unittest.cc cfg_iface_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_mac_source_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_multi_threading_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_option_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_option_def_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_rsoo_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_shared_networks4_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_shared_networks6_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_subnets4_unittest.cc \
+@HAVE_GTEST_TRUE@ cfg_subnets6_unittest.cc cfgmgr_unittest.cc \
+@HAVE_GTEST_TRUE@ client_class_def_unittest.cc \
+@HAVE_GTEST_TRUE@ client_class_def_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ csv_lease_file4_unittest.cc \
+@HAVE_GTEST_TRUE@ csv_lease_file6_unittest.cc \
+@HAVE_GTEST_TRUE@ d2_client_unittest.cc d2_udp_unittest.cc \
+@HAVE_GTEST_TRUE@ dhcp_queue_control_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ dhcp4o6_ipc_unittest.cc \
+@HAVE_GTEST_TRUE@ duid_config_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ expiration_config_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ flq_allocation_state_unittest.cc \
+@HAVE_GTEST_TRUE@ flq_allocator_unittest.cc \
+@HAVE_GTEST_TRUE@ host_cache_unittest.cc \
+@HAVE_GTEST_TRUE@ host_data_source_factory_unittest.cc \
+@HAVE_GTEST_TRUE@ host_mgr_unittest.cc host_unittest.cc \
+@HAVE_GTEST_TRUE@ host_reservation_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ host_reservations_list_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ ifaces_config_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ ip_range_unittest.cc \
+@HAVE_GTEST_TRUE@ ip_range_permutation_unittest.cc \
+@HAVE_GTEST_TRUE@ iterative_allocation_state_unittest.cc \
+@HAVE_GTEST_TRUE@ iterative_allocator_unittest.cc \
+@HAVE_GTEST_TRUE@ lease_file_loader_unittest.cc \
+@HAVE_GTEST_TRUE@ lease_unittest.cc \
+@HAVE_GTEST_TRUE@ lease_mgr_factory_unittest.cc \
+@HAVE_GTEST_TRUE@ lease_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_lease_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_lease_mgr_unittest.h \
+@HAVE_GTEST_TRUE@ memfile_lease_extended_info_unittest.cc \
+@HAVE_GTEST_TRUE@ memfile_lease_limits_unittest.cc \
+@HAVE_GTEST_TRUE@ memfile_lease_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ multi_threading_config_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ dhcp_parsers_unittest.cc \
+@HAVE_GTEST_TRUE@ ncr_generator_unittest.cc $(am__append_2) \
+@HAVE_GTEST_TRUE@ $(am__append_3) pool_unittest.cc \
+@HAVE_GTEST_TRUE@ random_allocation_state_unittest.cc \
+@HAVE_GTEST_TRUE@ random_allocator_unittest.cc \
+@HAVE_GTEST_TRUE@ resource_handler_unittest.cc \
+@HAVE_GTEST_TRUE@ sanity_checks_unittest.cc \
+@HAVE_GTEST_TRUE@ shared_network_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ shared_network_unittest.cc \
+@HAVE_GTEST_TRUE@ shared_networks_list_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ srv_config_unittest.cc subnet_unittest.cc \
+@HAVE_GTEST_TRUE@ test_get_callout_handle.cc \
+@HAVE_GTEST_TRUE@ test_get_callout_handle.h \
+@HAVE_GTEST_TRUE@ timer_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ tracking_lease_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ network_state_unittest.cc network_unittest.cc
+@HAVE_GTEST_TRUE@libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) \
+@HAVE_GTEST_TRUE@ $(GTEST_INCLUDES) $(am__append_4) \
+@HAVE_GTEST_TRUE@ $(am__append_5)
+@HAVE_GTEST_TRUE@libdhcpsrv_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdhcpsrv_unittests_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(am__append_6) $(am__append_7)
+@HAVE_GTEST_TRUE@libdhcpsrv_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(am__append_8) $(am__append_9) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/testutils/libdatabasetest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_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/dhcpsrv/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcpsrv/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):
+test_libraries.h: $(top_builddir)/config.status $(srcdir)/test_libraries.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libco1.la: $(libco1_la_OBJECTS) $(libco1_la_DEPENDENCIES) $(EXTRA_libco1_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libco1_la_LINK) $(am_libco1_la_rpath) $(libco1_la_OBJECTS) $(libco1_la_LIBADD) $(LIBS)
+
+libco2.la: $(libco2_la_OBJECTS) $(libco2_la_DEPENDENCIES) $(EXTRA_libco2_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libco2_la_LINK) $(am_libco2_la_rpath) $(libco2_la_OBJECTS) $(libco2_la_LIBADD) $(LIBS)
+
+libco3.la: $(libco3_la_OBJECTS) $(libco3_la_DEPENDENCIES) $(EXTRA_libco3_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libco3_la_LINK) $(am_libco3_la_rpath) $(libco3_la_OBJECTS) $(libco3_la_LIBADD) $(LIBS)
+
+libdhcpsrv_unittests$(EXEEXT): $(libdhcpsrv_unittests_OBJECTS) $(libdhcpsrv_unittests_DEPENDENCIES) $(EXTRA_libdhcpsrv_unittests_DEPENDENCIES)
+ @rm -f libdhcpsrv_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libdhcpsrv_unittests_LINK) $(libdhcpsrv_unittests_OBJECTS) $(libdhcpsrv_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libco1_la-callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libco2_la-callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libco3_la-callout_params_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-host_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-network_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.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 $@ $<
+
+libco1_la-callout_library.lo: callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco1_la_CPPFLAGS) $(CPPFLAGS) $(libco1_la_CXXFLAGS) $(CXXFLAGS) -MT libco1_la-callout_library.lo -MD -MP -MF $(DEPDIR)/libco1_la-callout_library.Tpo -c -o libco1_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libco1_la-callout_library.Tpo $(DEPDIR)/libco1_la-callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_library.cc' object='libco1_la-callout_library.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) $(libco1_la_CPPFLAGS) $(CPPFLAGS) $(libco1_la_CXXFLAGS) $(CXXFLAGS) -c -o libco1_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+
+libco2_la-callout_library.lo: callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco2_la_CPPFLAGS) $(CPPFLAGS) $(libco2_la_CXXFLAGS) $(CXXFLAGS) -MT libco2_la-callout_library.lo -MD -MP -MF $(DEPDIR)/libco2_la-callout_library.Tpo -c -o libco2_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libco2_la-callout_library.Tpo $(DEPDIR)/libco2_la-callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_library.cc' object='libco2_la-callout_library.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) $(libco2_la_CPPFLAGS) $(CPPFLAGS) $(libco2_la_CXXFLAGS) $(CXXFLAGS) -c -o libco2_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+
+libco3_la-callout_params_library.lo: callout_params_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libco3_la_CPPFLAGS) $(CPPFLAGS) $(libco3_la_CXXFLAGS) $(CXXFLAGS) -MT libco3_la-callout_params_library.lo -MD -MP -MF $(DEPDIR)/libco3_la-callout_params_library.Tpo -c -o libco3_la-callout_params_library.lo `test -f 'callout_params_library.cc' || echo '$(srcdir)/'`callout_params_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libco3_la-callout_params_library.Tpo $(DEPDIR)/libco3_la-callout_params_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_params_library.cc' object='libco3_la-callout_params_library.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) $(libco3_la_CPPFLAGS) $(CPPFLAGS) $(libco3_la_CXXFLAGS) $(CXXFLAGS) -c -o libco3_la-callout_params_library.lo `test -f 'callout_params_library.cc' || echo '$(srcdir)/'`callout_params_library.cc
+
+libdhcpsrv_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-run_unittests.Tpo -c -o libdhcpsrv_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-run_unittests.Tpo $(DEPDIR)/libdhcpsrv_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcpsrv_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libdhcpsrv_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-run_unittests.Tpo -c -o libdhcpsrv_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)/libdhcpsrv_unittests-run_unittests.Tpo $(DEPDIR)/libdhcpsrv_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libdhcpsrv_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libdhcpsrv_unittests-alloc_engine_utils.o: alloc_engine_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine_utils.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Tpo -c -o libdhcpsrv_unittests-alloc_engine_utils.o `test -f 'alloc_engine_utils.cc' || echo '$(srcdir)/'`alloc_engine_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_utils.cc' object='libdhcpsrv_unittests-alloc_engine_utils.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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine_utils.o `test -f 'alloc_engine_utils.cc' || echo '$(srcdir)/'`alloc_engine_utils.cc
+
+libdhcpsrv_unittests-alloc_engine_utils.obj: alloc_engine_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine_utils.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Tpo -c -o libdhcpsrv_unittests-alloc_engine_utils.obj `if test -f 'alloc_engine_utils.cc'; then $(CYGPATH_W) 'alloc_engine_utils.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine_utils.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_utils.cc' object='libdhcpsrv_unittests-alloc_engine_utils.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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine_utils.obj `if test -f 'alloc_engine_utils.cc'; then $(CYGPATH_W) 'alloc_engine_utils.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine_utils.cc'; fi`
+
+libdhcpsrv_unittests-alloc_engine_expiration_unittest.o: alloc_engine_expiration_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine_expiration_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine_expiration_unittest.o `test -f 'alloc_engine_expiration_unittest.cc' || echo '$(srcdir)/'`alloc_engine_expiration_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_expiration_unittest.cc' object='libdhcpsrv_unittests-alloc_engine_expiration_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine_expiration_unittest.o `test -f 'alloc_engine_expiration_unittest.cc' || echo '$(srcdir)/'`alloc_engine_expiration_unittest.cc
+
+libdhcpsrv_unittests-alloc_engine_expiration_unittest.obj: alloc_engine_expiration_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine_expiration_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine_expiration_unittest.obj `if test -f 'alloc_engine_expiration_unittest.cc'; then $(CYGPATH_W) 'alloc_engine_expiration_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine_expiration_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_expiration_unittest.cc' object='libdhcpsrv_unittests-alloc_engine_expiration_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine_expiration_unittest.obj `if test -f 'alloc_engine_expiration_unittest.cc'; then $(CYGPATH_W) 'alloc_engine_expiration_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine_expiration_unittest.cc'; fi`
+
+libdhcpsrv_unittests-alloc_engine_hooks_unittest.o: alloc_engine_hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine_hooks_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine_hooks_unittest.o `test -f 'alloc_engine_hooks_unittest.cc' || echo '$(srcdir)/'`alloc_engine_hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_hooks_unittest.cc' object='libdhcpsrv_unittests-alloc_engine_hooks_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine_hooks_unittest.o `test -f 'alloc_engine_hooks_unittest.cc' || echo '$(srcdir)/'`alloc_engine_hooks_unittest.cc
+
+libdhcpsrv_unittests-alloc_engine_hooks_unittest.obj: alloc_engine_hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine_hooks_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine_hooks_unittest.obj `if test -f 'alloc_engine_hooks_unittest.cc'; then $(CYGPATH_W) 'alloc_engine_hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine_hooks_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine_hooks_unittest.cc' object='libdhcpsrv_unittests-alloc_engine_hooks_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine_hooks_unittest.obj `if test -f 'alloc_engine_hooks_unittest.cc'; then $(CYGPATH_W) 'alloc_engine_hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine_hooks_unittest.cc'; fi`
+
+libdhcpsrv_unittests-alloc_engine4_unittest.o: alloc_engine4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine4_unittest.o `test -f 'alloc_engine4_unittest.cc' || echo '$(srcdir)/'`alloc_engine4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine4_unittest.cc' object='libdhcpsrv_unittests-alloc_engine4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine4_unittest.o `test -f 'alloc_engine4_unittest.cc' || echo '$(srcdir)/'`alloc_engine4_unittest.cc
+
+libdhcpsrv_unittests-alloc_engine4_unittest.obj: alloc_engine4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine4_unittest.obj `if test -f 'alloc_engine4_unittest.cc'; then $(CYGPATH_W) 'alloc_engine4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine4_unittest.cc' object='libdhcpsrv_unittests-alloc_engine4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine4_unittest.obj `if test -f 'alloc_engine4_unittest.cc'; then $(CYGPATH_W) 'alloc_engine4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine4_unittest.cc'; fi`
+
+libdhcpsrv_unittests-alloc_engine6_unittest.o: alloc_engine6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine6_unittest.o `test -f 'alloc_engine6_unittest.cc' || echo '$(srcdir)/'`alloc_engine6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine6_unittest.cc' object='libdhcpsrv_unittests-alloc_engine6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine6_unittest.o `test -f 'alloc_engine6_unittest.cc' || echo '$(srcdir)/'`alloc_engine6_unittest.cc
+
+libdhcpsrv_unittests-alloc_engine6_unittest.obj: alloc_engine6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-alloc_engine6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Tpo -c -o libdhcpsrv_unittests-alloc_engine6_unittest.obj `if test -f 'alloc_engine6_unittest.cc'; then $(CYGPATH_W) 'alloc_engine6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='alloc_engine6_unittest.cc' object='libdhcpsrv_unittests-alloc_engine6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-alloc_engine6_unittest.obj `if test -f 'alloc_engine6_unittest.cc'; then $(CYGPATH_W) 'alloc_engine6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/alloc_engine6_unittest.cc'; fi`
+
+libdhcpsrv_unittests-allocation_state_unittest.o: allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-allocation_state_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-allocation_state_unittest.o `test -f 'allocation_state_unittest.cc' || echo '$(srcdir)/'`allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='allocation_state_unittest.cc' object='libdhcpsrv_unittests-allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-allocation_state_unittest.o `test -f 'allocation_state_unittest.cc' || echo '$(srcdir)/'`allocation_state_unittest.cc
+
+libdhcpsrv_unittests-allocation_state_unittest.obj: allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-allocation_state_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-allocation_state_unittest.obj `if test -f 'allocation_state_unittest.cc'; then $(CYGPATH_W) 'allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/allocation_state_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='allocation_state_unittest.cc' object='libdhcpsrv_unittests-allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-allocation_state_unittest.obj `if test -f 'allocation_state_unittest.cc'; then $(CYGPATH_W) 'allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/allocation_state_unittest.cc'; fi`
+
+libdhcpsrv_unittests-callout_handle_store_unittest.o: callout_handle_store_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-callout_handle_store_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Tpo -c -o libdhcpsrv_unittests-callout_handle_store_unittest.o `test -f 'callout_handle_store_unittest.cc' || echo '$(srcdir)/'`callout_handle_store_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_store_unittest.cc' object='libdhcpsrv_unittests-callout_handle_store_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-callout_handle_store_unittest.o `test -f 'callout_handle_store_unittest.cc' || echo '$(srcdir)/'`callout_handle_store_unittest.cc
+
+libdhcpsrv_unittests-callout_handle_store_unittest.obj: callout_handle_store_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-callout_handle_store_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Tpo -c -o libdhcpsrv_unittests-callout_handle_store_unittest.obj `if test -f 'callout_handle_store_unittest.cc'; then $(CYGPATH_W) 'callout_handle_store_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_store_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_store_unittest.cc' object='libdhcpsrv_unittests-callout_handle_store_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-callout_handle_store_unittest.obj `if test -f 'callout_handle_store_unittest.cc'; then $(CYGPATH_W) 'callout_handle_store_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_store_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cb_ctl_dhcp_unittest.o: cb_ctl_dhcp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cb_ctl_dhcp_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Tpo -c -o libdhcpsrv_unittests-cb_ctl_dhcp_unittest.o `test -f 'cb_ctl_dhcp_unittest.cc' || echo '$(srcdir)/'`cb_ctl_dhcp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cb_ctl_dhcp_unittest.cc' object='libdhcpsrv_unittests-cb_ctl_dhcp_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cb_ctl_dhcp_unittest.o `test -f 'cb_ctl_dhcp_unittest.cc' || echo '$(srcdir)/'`cb_ctl_dhcp_unittest.cc
+
+libdhcpsrv_unittests-cb_ctl_dhcp_unittest.obj: cb_ctl_dhcp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cb_ctl_dhcp_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Tpo -c -o libdhcpsrv_unittests-cb_ctl_dhcp_unittest.obj `if test -f 'cb_ctl_dhcp_unittest.cc'; then $(CYGPATH_W) 'cb_ctl_dhcp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cb_ctl_dhcp_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cb_ctl_dhcp_unittest.cc' object='libdhcpsrv_unittests-cb_ctl_dhcp_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cb_ctl_dhcp_unittest.obj `if test -f 'cb_ctl_dhcp_unittest.cc'; then $(CYGPATH_W) 'cb_ctl_dhcp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cb_ctl_dhcp_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_db_access_unittest.o: cfg_db_access_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_db_access_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_db_access_unittest.o `test -f 'cfg_db_access_unittest.cc' || echo '$(srcdir)/'`cfg_db_access_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_db_access_unittest.cc' object='libdhcpsrv_unittests-cfg_db_access_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_db_access_unittest.o `test -f 'cfg_db_access_unittest.cc' || echo '$(srcdir)/'`cfg_db_access_unittest.cc
+
+libdhcpsrv_unittests-cfg_db_access_unittest.obj: cfg_db_access_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_db_access_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_db_access_unittest.obj `if test -f 'cfg_db_access_unittest.cc'; then $(CYGPATH_W) 'cfg_db_access_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_db_access_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_db_access_unittest.cc' object='libdhcpsrv_unittests-cfg_db_access_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_db_access_unittest.obj `if test -f 'cfg_db_access_unittest.cc'; then $(CYGPATH_W) 'cfg_db_access_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_db_access_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_duid_unittest.o: cfg_duid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_duid_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_duid_unittest.o `test -f 'cfg_duid_unittest.cc' || echo '$(srcdir)/'`cfg_duid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_duid_unittest.cc' object='libdhcpsrv_unittests-cfg_duid_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_duid_unittest.o `test -f 'cfg_duid_unittest.cc' || echo '$(srcdir)/'`cfg_duid_unittest.cc
+
+libdhcpsrv_unittests-cfg_duid_unittest.obj: cfg_duid_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_duid_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_duid_unittest.obj `if test -f 'cfg_duid_unittest.cc'; then $(CYGPATH_W) 'cfg_duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_duid_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_duid_unittest.cc' object='libdhcpsrv_unittests-cfg_duid_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_duid_unittest.obj `if test -f 'cfg_duid_unittest.cc'; then $(CYGPATH_W) 'cfg_duid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_duid_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_expiration_unittest.o: cfg_expiration_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_expiration_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_expiration_unittest.o `test -f 'cfg_expiration_unittest.cc' || echo '$(srcdir)/'`cfg_expiration_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_expiration_unittest.cc' object='libdhcpsrv_unittests-cfg_expiration_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_expiration_unittest.o `test -f 'cfg_expiration_unittest.cc' || echo '$(srcdir)/'`cfg_expiration_unittest.cc
+
+libdhcpsrv_unittests-cfg_expiration_unittest.obj: cfg_expiration_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_expiration_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_expiration_unittest.obj `if test -f 'cfg_expiration_unittest.cc'; then $(CYGPATH_W) 'cfg_expiration_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_expiration_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_expiration_unittest.cc' object='libdhcpsrv_unittests-cfg_expiration_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_expiration_unittest.obj `if test -f 'cfg_expiration_unittest.cc'; then $(CYGPATH_W) 'cfg_expiration_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_expiration_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_host_operations_unittest.o: cfg_host_operations_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_host_operations_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_host_operations_unittest.o `test -f 'cfg_host_operations_unittest.cc' || echo '$(srcdir)/'`cfg_host_operations_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_host_operations_unittest.cc' object='libdhcpsrv_unittests-cfg_host_operations_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_host_operations_unittest.o `test -f 'cfg_host_operations_unittest.cc' || echo '$(srcdir)/'`cfg_host_operations_unittest.cc
+
+libdhcpsrv_unittests-cfg_host_operations_unittest.obj: cfg_host_operations_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_host_operations_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_host_operations_unittest.obj `if test -f 'cfg_host_operations_unittest.cc'; then $(CYGPATH_W) 'cfg_host_operations_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_host_operations_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_host_operations_unittest.cc' object='libdhcpsrv_unittests-cfg_host_operations_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_host_operations_unittest.obj `if test -f 'cfg_host_operations_unittest.cc'; then $(CYGPATH_W) 'cfg_host_operations_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_host_operations_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_hosts_unittest.o: cfg_hosts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_hosts_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_hosts_unittest.o `test -f 'cfg_hosts_unittest.cc' || echo '$(srcdir)/'`cfg_hosts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_hosts_unittest.cc' object='libdhcpsrv_unittests-cfg_hosts_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_hosts_unittest.o `test -f 'cfg_hosts_unittest.cc' || echo '$(srcdir)/'`cfg_hosts_unittest.cc
+
+libdhcpsrv_unittests-cfg_hosts_unittest.obj: cfg_hosts_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_hosts_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_hosts_unittest.obj `if test -f 'cfg_hosts_unittest.cc'; then $(CYGPATH_W) 'cfg_hosts_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_hosts_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_hosts_unittest.cc' object='libdhcpsrv_unittests-cfg_hosts_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_hosts_unittest.obj `if test -f 'cfg_hosts_unittest.cc'; then $(CYGPATH_W) 'cfg_hosts_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_hosts_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_iface_unittest.o: cfg_iface_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_iface_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_iface_unittest.o `test -f 'cfg_iface_unittest.cc' || echo '$(srcdir)/'`cfg_iface_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_iface_unittest.cc' object='libdhcpsrv_unittests-cfg_iface_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_iface_unittest.o `test -f 'cfg_iface_unittest.cc' || echo '$(srcdir)/'`cfg_iface_unittest.cc
+
+libdhcpsrv_unittests-cfg_iface_unittest.obj: cfg_iface_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_iface_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_iface_unittest.obj `if test -f 'cfg_iface_unittest.cc'; then $(CYGPATH_W) 'cfg_iface_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_iface_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_iface_unittest.cc' object='libdhcpsrv_unittests-cfg_iface_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_iface_unittest.obj `if test -f 'cfg_iface_unittest.cc'; then $(CYGPATH_W) 'cfg_iface_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_iface_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_mac_source_unittest.o: cfg_mac_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_mac_source_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_mac_source_unittest.o `test -f 'cfg_mac_source_unittest.cc' || echo '$(srcdir)/'`cfg_mac_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_mac_source_unittest.cc' object='libdhcpsrv_unittests-cfg_mac_source_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_mac_source_unittest.o `test -f 'cfg_mac_source_unittest.cc' || echo '$(srcdir)/'`cfg_mac_source_unittest.cc
+
+libdhcpsrv_unittests-cfg_mac_source_unittest.obj: cfg_mac_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_mac_source_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_mac_source_unittest.obj `if test -f 'cfg_mac_source_unittest.cc'; then $(CYGPATH_W) 'cfg_mac_source_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_mac_source_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_mac_source_unittest.cc' object='libdhcpsrv_unittests-cfg_mac_source_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_mac_source_unittest.obj `if test -f 'cfg_mac_source_unittest.cc'; then $(CYGPATH_W) 'cfg_mac_source_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_mac_source_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_multi_threading_unittest.o: cfg_multi_threading_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_multi_threading_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_multi_threading_unittest.o `test -f 'cfg_multi_threading_unittest.cc' || echo '$(srcdir)/'`cfg_multi_threading_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_multi_threading_unittest.cc' object='libdhcpsrv_unittests-cfg_multi_threading_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_multi_threading_unittest.o `test -f 'cfg_multi_threading_unittest.cc' || echo '$(srcdir)/'`cfg_multi_threading_unittest.cc
+
+libdhcpsrv_unittests-cfg_multi_threading_unittest.obj: cfg_multi_threading_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_multi_threading_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_multi_threading_unittest.obj `if test -f 'cfg_multi_threading_unittest.cc'; then $(CYGPATH_W) 'cfg_multi_threading_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_multi_threading_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_multi_threading_unittest.cc' object='libdhcpsrv_unittests-cfg_multi_threading_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_multi_threading_unittest.obj `if test -f 'cfg_multi_threading_unittest.cc'; then $(CYGPATH_W) 'cfg_multi_threading_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_multi_threading_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_option_unittest.o: cfg_option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_option_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_option_unittest.o `test -f 'cfg_option_unittest.cc' || echo '$(srcdir)/'`cfg_option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_option_unittest.cc' object='libdhcpsrv_unittests-cfg_option_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_option_unittest.o `test -f 'cfg_option_unittest.cc' || echo '$(srcdir)/'`cfg_option_unittest.cc
+
+libdhcpsrv_unittests-cfg_option_unittest.obj: cfg_option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_option_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_option_unittest.obj `if test -f 'cfg_option_unittest.cc'; then $(CYGPATH_W) 'cfg_option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_option_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_option_unittest.cc' object='libdhcpsrv_unittests-cfg_option_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_option_unittest.obj `if test -f 'cfg_option_unittest.cc'; then $(CYGPATH_W) 'cfg_option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_option_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_option_def_unittest.o: cfg_option_def_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_option_def_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_option_def_unittest.o `test -f 'cfg_option_def_unittest.cc' || echo '$(srcdir)/'`cfg_option_def_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_option_def_unittest.cc' object='libdhcpsrv_unittests-cfg_option_def_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_option_def_unittest.o `test -f 'cfg_option_def_unittest.cc' || echo '$(srcdir)/'`cfg_option_def_unittest.cc
+
+libdhcpsrv_unittests-cfg_option_def_unittest.obj: cfg_option_def_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_option_def_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_option_def_unittest.obj `if test -f 'cfg_option_def_unittest.cc'; then $(CYGPATH_W) 'cfg_option_def_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_option_def_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_option_def_unittest.cc' object='libdhcpsrv_unittests-cfg_option_def_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_option_def_unittest.obj `if test -f 'cfg_option_def_unittest.cc'; then $(CYGPATH_W) 'cfg_option_def_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_option_def_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_rsoo_unittest.o: cfg_rsoo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_rsoo_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_rsoo_unittest.o `test -f 'cfg_rsoo_unittest.cc' || echo '$(srcdir)/'`cfg_rsoo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_rsoo_unittest.cc' object='libdhcpsrv_unittests-cfg_rsoo_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_rsoo_unittest.o `test -f 'cfg_rsoo_unittest.cc' || echo '$(srcdir)/'`cfg_rsoo_unittest.cc
+
+libdhcpsrv_unittests-cfg_rsoo_unittest.obj: cfg_rsoo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_rsoo_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_rsoo_unittest.obj `if test -f 'cfg_rsoo_unittest.cc'; then $(CYGPATH_W) 'cfg_rsoo_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_rsoo_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_rsoo_unittest.cc' object='libdhcpsrv_unittests-cfg_rsoo_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_rsoo_unittest.obj `if test -f 'cfg_rsoo_unittest.cc'; then $(CYGPATH_W) 'cfg_rsoo_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_rsoo_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_shared_networks4_unittest.o: cfg_shared_networks4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_shared_networks4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_shared_networks4_unittest.o `test -f 'cfg_shared_networks4_unittest.cc' || echo '$(srcdir)/'`cfg_shared_networks4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_shared_networks4_unittest.cc' object='libdhcpsrv_unittests-cfg_shared_networks4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_shared_networks4_unittest.o `test -f 'cfg_shared_networks4_unittest.cc' || echo '$(srcdir)/'`cfg_shared_networks4_unittest.cc
+
+libdhcpsrv_unittests-cfg_shared_networks4_unittest.obj: cfg_shared_networks4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_shared_networks4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_shared_networks4_unittest.obj `if test -f 'cfg_shared_networks4_unittest.cc'; then $(CYGPATH_W) 'cfg_shared_networks4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_shared_networks4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_shared_networks4_unittest.cc' object='libdhcpsrv_unittests-cfg_shared_networks4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_shared_networks4_unittest.obj `if test -f 'cfg_shared_networks4_unittest.cc'; then $(CYGPATH_W) 'cfg_shared_networks4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_shared_networks4_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_shared_networks6_unittest.o: cfg_shared_networks6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_shared_networks6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_shared_networks6_unittest.o `test -f 'cfg_shared_networks6_unittest.cc' || echo '$(srcdir)/'`cfg_shared_networks6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_shared_networks6_unittest.cc' object='libdhcpsrv_unittests-cfg_shared_networks6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_shared_networks6_unittest.o `test -f 'cfg_shared_networks6_unittest.cc' || echo '$(srcdir)/'`cfg_shared_networks6_unittest.cc
+
+libdhcpsrv_unittests-cfg_shared_networks6_unittest.obj: cfg_shared_networks6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_shared_networks6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_shared_networks6_unittest.obj `if test -f 'cfg_shared_networks6_unittest.cc'; then $(CYGPATH_W) 'cfg_shared_networks6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_shared_networks6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_shared_networks6_unittest.cc' object='libdhcpsrv_unittests-cfg_shared_networks6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_shared_networks6_unittest.obj `if test -f 'cfg_shared_networks6_unittest.cc'; then $(CYGPATH_W) 'cfg_shared_networks6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_shared_networks6_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_subnets4_unittest.o: cfg_subnets4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_subnets4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_subnets4_unittest.o `test -f 'cfg_subnets4_unittest.cc' || echo '$(srcdir)/'`cfg_subnets4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_subnets4_unittest.cc' object='libdhcpsrv_unittests-cfg_subnets4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_subnets4_unittest.o `test -f 'cfg_subnets4_unittest.cc' || echo '$(srcdir)/'`cfg_subnets4_unittest.cc
+
+libdhcpsrv_unittests-cfg_subnets4_unittest.obj: cfg_subnets4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_subnets4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_subnets4_unittest.obj `if test -f 'cfg_subnets4_unittest.cc'; then $(CYGPATH_W) 'cfg_subnets4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_subnets4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_subnets4_unittest.cc' object='libdhcpsrv_unittests-cfg_subnets4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_subnets4_unittest.obj `if test -f 'cfg_subnets4_unittest.cc'; then $(CYGPATH_W) 'cfg_subnets4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_subnets4_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfg_subnets6_unittest.o: cfg_subnets6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_subnets6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_subnets6_unittest.o `test -f 'cfg_subnets6_unittest.cc' || echo '$(srcdir)/'`cfg_subnets6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_subnets6_unittest.cc' object='libdhcpsrv_unittests-cfg_subnets6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_subnets6_unittest.o `test -f 'cfg_subnets6_unittest.cc' || echo '$(srcdir)/'`cfg_subnets6_unittest.cc
+
+libdhcpsrv_unittests-cfg_subnets6_unittest.obj: cfg_subnets6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfg_subnets6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Tpo -c -o libdhcpsrv_unittests-cfg_subnets6_unittest.obj `if test -f 'cfg_subnets6_unittest.cc'; then $(CYGPATH_W) 'cfg_subnets6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_subnets6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfg_subnets6_unittest.cc' object='libdhcpsrv_unittests-cfg_subnets6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfg_subnets6_unittest.obj `if test -f 'cfg_subnets6_unittest.cc'; then $(CYGPATH_W) 'cfg_subnets6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfg_subnets6_unittest.cc'; fi`
+
+libdhcpsrv_unittests-cfgmgr_unittest.o: cfgmgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfgmgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Tpo -c -o libdhcpsrv_unittests-cfgmgr_unittest.o `test -f 'cfgmgr_unittest.cc' || echo '$(srcdir)/'`cfgmgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfgmgr_unittest.cc' object='libdhcpsrv_unittests-cfgmgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfgmgr_unittest.o `test -f 'cfgmgr_unittest.cc' || echo '$(srcdir)/'`cfgmgr_unittest.cc
+
+libdhcpsrv_unittests-cfgmgr_unittest.obj: cfgmgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-cfgmgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Tpo -c -o libdhcpsrv_unittests-cfgmgr_unittest.obj `if test -f 'cfgmgr_unittest.cc'; then $(CYGPATH_W) 'cfgmgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfgmgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cfgmgr_unittest.cc' object='libdhcpsrv_unittests-cfgmgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-cfgmgr_unittest.obj `if test -f 'cfgmgr_unittest.cc'; then $(CYGPATH_W) 'cfgmgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/cfgmgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-client_class_def_unittest.o: client_class_def_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-client_class_def_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Tpo -c -o libdhcpsrv_unittests-client_class_def_unittest.o `test -f 'client_class_def_unittest.cc' || echo '$(srcdir)/'`client_class_def_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_class_def_unittest.cc' object='libdhcpsrv_unittests-client_class_def_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-client_class_def_unittest.o `test -f 'client_class_def_unittest.cc' || echo '$(srcdir)/'`client_class_def_unittest.cc
+
+libdhcpsrv_unittests-client_class_def_unittest.obj: client_class_def_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-client_class_def_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Tpo -c -o libdhcpsrv_unittests-client_class_def_unittest.obj `if test -f 'client_class_def_unittest.cc'; then $(CYGPATH_W) 'client_class_def_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/client_class_def_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_class_def_unittest.cc' object='libdhcpsrv_unittests-client_class_def_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-client_class_def_unittest.obj `if test -f 'client_class_def_unittest.cc'; then $(CYGPATH_W) 'client_class_def_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/client_class_def_unittest.cc'; fi`
+
+libdhcpsrv_unittests-client_class_def_parser_unittest.o: client_class_def_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-client_class_def_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Tpo -c -o libdhcpsrv_unittests-client_class_def_parser_unittest.o `test -f 'client_class_def_parser_unittest.cc' || echo '$(srcdir)/'`client_class_def_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_class_def_parser_unittest.cc' object='libdhcpsrv_unittests-client_class_def_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-client_class_def_parser_unittest.o `test -f 'client_class_def_parser_unittest.cc' || echo '$(srcdir)/'`client_class_def_parser_unittest.cc
+
+libdhcpsrv_unittests-client_class_def_parser_unittest.obj: client_class_def_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-client_class_def_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Tpo -c -o libdhcpsrv_unittests-client_class_def_parser_unittest.obj `if test -f 'client_class_def_parser_unittest.cc'; then $(CYGPATH_W) 'client_class_def_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/client_class_def_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_class_def_parser_unittest.cc' object='libdhcpsrv_unittests-client_class_def_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-client_class_def_parser_unittest.obj `if test -f 'client_class_def_parser_unittest.cc'; then $(CYGPATH_W) 'client_class_def_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/client_class_def_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-csv_lease_file4_unittest.o: csv_lease_file4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-csv_lease_file4_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Tpo -c -o libdhcpsrv_unittests-csv_lease_file4_unittest.o `test -f 'csv_lease_file4_unittest.cc' || echo '$(srcdir)/'`csv_lease_file4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_lease_file4_unittest.cc' object='libdhcpsrv_unittests-csv_lease_file4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-csv_lease_file4_unittest.o `test -f 'csv_lease_file4_unittest.cc' || echo '$(srcdir)/'`csv_lease_file4_unittest.cc
+
+libdhcpsrv_unittests-csv_lease_file4_unittest.obj: csv_lease_file4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-csv_lease_file4_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Tpo -c -o libdhcpsrv_unittests-csv_lease_file4_unittest.obj `if test -f 'csv_lease_file4_unittest.cc'; then $(CYGPATH_W) 'csv_lease_file4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_lease_file4_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_lease_file4_unittest.cc' object='libdhcpsrv_unittests-csv_lease_file4_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-csv_lease_file4_unittest.obj `if test -f 'csv_lease_file4_unittest.cc'; then $(CYGPATH_W) 'csv_lease_file4_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_lease_file4_unittest.cc'; fi`
+
+libdhcpsrv_unittests-csv_lease_file6_unittest.o: csv_lease_file6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-csv_lease_file6_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Tpo -c -o libdhcpsrv_unittests-csv_lease_file6_unittest.o `test -f 'csv_lease_file6_unittest.cc' || echo '$(srcdir)/'`csv_lease_file6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_lease_file6_unittest.cc' object='libdhcpsrv_unittests-csv_lease_file6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-csv_lease_file6_unittest.o `test -f 'csv_lease_file6_unittest.cc' || echo '$(srcdir)/'`csv_lease_file6_unittest.cc
+
+libdhcpsrv_unittests-csv_lease_file6_unittest.obj: csv_lease_file6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-csv_lease_file6_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Tpo -c -o libdhcpsrv_unittests-csv_lease_file6_unittest.obj `if test -f 'csv_lease_file6_unittest.cc'; then $(CYGPATH_W) 'csv_lease_file6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_lease_file6_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_lease_file6_unittest.cc' object='libdhcpsrv_unittests-csv_lease_file6_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-csv_lease_file6_unittest.obj `if test -f 'csv_lease_file6_unittest.cc'; then $(CYGPATH_W) 'csv_lease_file6_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_lease_file6_unittest.cc'; fi`
+
+libdhcpsrv_unittests-d2_client_unittest.o: d2_client_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-d2_client_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Tpo -c -o libdhcpsrv_unittests-d2_client_unittest.o `test -f 'd2_client_unittest.cc' || echo '$(srcdir)/'`d2_client_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_client_unittest.cc' object='libdhcpsrv_unittests-d2_client_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-d2_client_unittest.o `test -f 'd2_client_unittest.cc' || echo '$(srcdir)/'`d2_client_unittest.cc
+
+libdhcpsrv_unittests-d2_client_unittest.obj: d2_client_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-d2_client_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Tpo -c -o libdhcpsrv_unittests-d2_client_unittest.obj `if test -f 'd2_client_unittest.cc'; then $(CYGPATH_W) 'd2_client_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_client_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_client_unittest.cc' object='libdhcpsrv_unittests-d2_client_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-d2_client_unittest.obj `if test -f 'd2_client_unittest.cc'; then $(CYGPATH_W) 'd2_client_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_client_unittest.cc'; fi`
+
+libdhcpsrv_unittests-d2_udp_unittest.o: d2_udp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-d2_udp_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Tpo -c -o libdhcpsrv_unittests-d2_udp_unittest.o `test -f 'd2_udp_unittest.cc' || echo '$(srcdir)/'`d2_udp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_udp_unittest.cc' object='libdhcpsrv_unittests-d2_udp_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-d2_udp_unittest.o `test -f 'd2_udp_unittest.cc' || echo '$(srcdir)/'`d2_udp_unittest.cc
+
+libdhcpsrv_unittests-d2_udp_unittest.obj: d2_udp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-d2_udp_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Tpo -c -o libdhcpsrv_unittests-d2_udp_unittest.obj `if test -f 'd2_udp_unittest.cc'; then $(CYGPATH_W) 'd2_udp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_udp_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_udp_unittest.cc' object='libdhcpsrv_unittests-d2_udp_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-d2_udp_unittest.obj `if test -f 'd2_udp_unittest.cc'; then $(CYGPATH_W) 'd2_udp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_udp_unittest.cc'; fi`
+
+libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.o: dhcp_queue_control_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Tpo -c -o libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.o `test -f 'dhcp_queue_control_parser_unittest.cc' || echo '$(srcdir)/'`dhcp_queue_control_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_queue_control_parser_unittest.cc' object='libdhcpsrv_unittests-dhcp_queue_control_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.o `test -f 'dhcp_queue_control_parser_unittest.cc' || echo '$(srcdir)/'`dhcp_queue_control_parser_unittest.cc
+
+libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.obj: dhcp_queue_control_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Tpo -c -o libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.obj `if test -f 'dhcp_queue_control_parser_unittest.cc'; then $(CYGPATH_W) 'dhcp_queue_control_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_queue_control_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_queue_control_parser_unittest.cc' object='libdhcpsrv_unittests-dhcp_queue_control_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.obj `if test -f 'dhcp_queue_control_parser_unittest.cc'; then $(CYGPATH_W) 'dhcp_queue_control_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_queue_control_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-dhcp4o6_ipc_unittest.o: dhcp4o6_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-dhcp4o6_ipc_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Tpo -c -o libdhcpsrv_unittests-dhcp4o6_ipc_unittest.o `test -f 'dhcp4o6_ipc_unittest.cc' || echo '$(srcdir)/'`dhcp4o6_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4o6_ipc_unittest.cc' object='libdhcpsrv_unittests-dhcp4o6_ipc_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-dhcp4o6_ipc_unittest.o `test -f 'dhcp4o6_ipc_unittest.cc' || echo '$(srcdir)/'`dhcp4o6_ipc_unittest.cc
+
+libdhcpsrv_unittests-dhcp4o6_ipc_unittest.obj: dhcp4o6_ipc_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-dhcp4o6_ipc_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Tpo -c -o libdhcpsrv_unittests-dhcp4o6_ipc_unittest.obj `if test -f 'dhcp4o6_ipc_unittest.cc'; then $(CYGPATH_W) 'dhcp4o6_ipc_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4o6_ipc_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4o6_ipc_unittest.cc' object='libdhcpsrv_unittests-dhcp4o6_ipc_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-dhcp4o6_ipc_unittest.obj `if test -f 'dhcp4o6_ipc_unittest.cc'; then $(CYGPATH_W) 'dhcp4o6_ipc_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp4o6_ipc_unittest.cc'; fi`
+
+libdhcpsrv_unittests-duid_config_parser_unittest.o: duid_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-duid_config_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-duid_config_parser_unittest.o `test -f 'duid_config_parser_unittest.cc' || echo '$(srcdir)/'`duid_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_config_parser_unittest.cc' object='libdhcpsrv_unittests-duid_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-duid_config_parser_unittest.o `test -f 'duid_config_parser_unittest.cc' || echo '$(srcdir)/'`duid_config_parser_unittest.cc
+
+libdhcpsrv_unittests-duid_config_parser_unittest.obj: duid_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-duid_config_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-duid_config_parser_unittest.obj `if test -f 'duid_config_parser_unittest.cc'; then $(CYGPATH_W) 'duid_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_config_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='duid_config_parser_unittest.cc' object='libdhcpsrv_unittests-duid_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-duid_config_parser_unittest.obj `if test -f 'duid_config_parser_unittest.cc'; then $(CYGPATH_W) 'duid_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/duid_config_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-expiration_config_parser_unittest.o: expiration_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-expiration_config_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-expiration_config_parser_unittest.o `test -f 'expiration_config_parser_unittest.cc' || echo '$(srcdir)/'`expiration_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='expiration_config_parser_unittest.cc' object='libdhcpsrv_unittests-expiration_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-expiration_config_parser_unittest.o `test -f 'expiration_config_parser_unittest.cc' || echo '$(srcdir)/'`expiration_config_parser_unittest.cc
+
+libdhcpsrv_unittests-expiration_config_parser_unittest.obj: expiration_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-expiration_config_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-expiration_config_parser_unittest.obj `if test -f 'expiration_config_parser_unittest.cc'; then $(CYGPATH_W) 'expiration_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/expiration_config_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='expiration_config_parser_unittest.cc' object='libdhcpsrv_unittests-expiration_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-expiration_config_parser_unittest.obj `if test -f 'expiration_config_parser_unittest.cc'; then $(CYGPATH_W) 'expiration_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/expiration_config_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-flq_allocation_state_unittest.o: flq_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-flq_allocation_state_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-flq_allocation_state_unittest.o `test -f 'flq_allocation_state_unittest.cc' || echo '$(srcdir)/'`flq_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='flq_allocation_state_unittest.cc' object='libdhcpsrv_unittests-flq_allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-flq_allocation_state_unittest.o `test -f 'flq_allocation_state_unittest.cc' || echo '$(srcdir)/'`flq_allocation_state_unittest.cc
+
+libdhcpsrv_unittests-flq_allocation_state_unittest.obj: flq_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-flq_allocation_state_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-flq_allocation_state_unittest.obj `if test -f 'flq_allocation_state_unittest.cc'; then $(CYGPATH_W) 'flq_allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/flq_allocation_state_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='flq_allocation_state_unittest.cc' object='libdhcpsrv_unittests-flq_allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-flq_allocation_state_unittest.obj `if test -f 'flq_allocation_state_unittest.cc'; then $(CYGPATH_W) 'flq_allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/flq_allocation_state_unittest.cc'; fi`
+
+libdhcpsrv_unittests-flq_allocator_unittest.o: flq_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-flq_allocator_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Tpo -c -o libdhcpsrv_unittests-flq_allocator_unittest.o `test -f 'flq_allocator_unittest.cc' || echo '$(srcdir)/'`flq_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='flq_allocator_unittest.cc' object='libdhcpsrv_unittests-flq_allocator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-flq_allocator_unittest.o `test -f 'flq_allocator_unittest.cc' || echo '$(srcdir)/'`flq_allocator_unittest.cc
+
+libdhcpsrv_unittests-flq_allocator_unittest.obj: flq_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-flq_allocator_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Tpo -c -o libdhcpsrv_unittests-flq_allocator_unittest.obj `if test -f 'flq_allocator_unittest.cc'; then $(CYGPATH_W) 'flq_allocator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/flq_allocator_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='flq_allocator_unittest.cc' object='libdhcpsrv_unittests-flq_allocator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-flq_allocator_unittest.obj `if test -f 'flq_allocator_unittest.cc'; then $(CYGPATH_W) 'flq_allocator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/flq_allocator_unittest.cc'; fi`
+
+libdhcpsrv_unittests-host_cache_unittest.o: host_cache_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_cache_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Tpo -c -o libdhcpsrv_unittests-host_cache_unittest.o `test -f 'host_cache_unittest.cc' || echo '$(srcdir)/'`host_cache_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_cache_unittest.cc' object='libdhcpsrv_unittests-host_cache_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_cache_unittest.o `test -f 'host_cache_unittest.cc' || echo '$(srcdir)/'`host_cache_unittest.cc
+
+libdhcpsrv_unittests-host_cache_unittest.obj: host_cache_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_cache_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Tpo -c -o libdhcpsrv_unittests-host_cache_unittest.obj `if test -f 'host_cache_unittest.cc'; then $(CYGPATH_W) 'host_cache_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_cache_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_cache_unittest.cc' object='libdhcpsrv_unittests-host_cache_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_cache_unittest.obj `if test -f 'host_cache_unittest.cc'; then $(CYGPATH_W) 'host_cache_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_cache_unittest.cc'; fi`
+
+libdhcpsrv_unittests-host_data_source_factory_unittest.o: host_data_source_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_data_source_factory_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Tpo -c -o libdhcpsrv_unittests-host_data_source_factory_unittest.o `test -f 'host_data_source_factory_unittest.cc' || echo '$(srcdir)/'`host_data_source_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_data_source_factory_unittest.cc' object='libdhcpsrv_unittests-host_data_source_factory_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_data_source_factory_unittest.o `test -f 'host_data_source_factory_unittest.cc' || echo '$(srcdir)/'`host_data_source_factory_unittest.cc
+
+libdhcpsrv_unittests-host_data_source_factory_unittest.obj: host_data_source_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_data_source_factory_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Tpo -c -o libdhcpsrv_unittests-host_data_source_factory_unittest.obj `if test -f 'host_data_source_factory_unittest.cc'; then $(CYGPATH_W) 'host_data_source_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_data_source_factory_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_data_source_factory_unittest.cc' object='libdhcpsrv_unittests-host_data_source_factory_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_data_source_factory_unittest.obj `if test -f 'host_data_source_factory_unittest.cc'; then $(CYGPATH_W) 'host_data_source_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_data_source_factory_unittest.cc'; fi`
+
+libdhcpsrv_unittests-host_mgr_unittest.o: host_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-host_mgr_unittest.o `test -f 'host_mgr_unittest.cc' || echo '$(srcdir)/'`host_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_mgr_unittest.cc' object='libdhcpsrv_unittests-host_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_mgr_unittest.o `test -f 'host_mgr_unittest.cc' || echo '$(srcdir)/'`host_mgr_unittest.cc
+
+libdhcpsrv_unittests-host_mgr_unittest.obj: host_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-host_mgr_unittest.obj `if test -f 'host_mgr_unittest.cc'; then $(CYGPATH_W) 'host_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_mgr_unittest.cc' object='libdhcpsrv_unittests-host_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_mgr_unittest.obj `if test -f 'host_mgr_unittest.cc'; then $(CYGPATH_W) 'host_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-host_unittest.o: host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_unittest.Tpo -c -o libdhcpsrv_unittests-host_unittest.o `test -f 'host_unittest.cc' || echo '$(srcdir)/'`host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_unittest.cc' object='libdhcpsrv_unittests-host_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_unittest.o `test -f 'host_unittest.cc' || echo '$(srcdir)/'`host_unittest.cc
+
+libdhcpsrv_unittests-host_unittest.obj: host_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_unittest.Tpo -c -o libdhcpsrv_unittests-host_unittest.obj `if test -f 'host_unittest.cc'; then $(CYGPATH_W) 'host_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_unittest.cc' object='libdhcpsrv_unittests-host_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_unittest.obj `if test -f 'host_unittest.cc'; then $(CYGPATH_W) 'host_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_unittest.cc'; fi`
+
+libdhcpsrv_unittests-host_reservation_parser_unittest.o: host_reservation_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_reservation_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Tpo -c -o libdhcpsrv_unittests-host_reservation_parser_unittest.o `test -f 'host_reservation_parser_unittest.cc' || echo '$(srcdir)/'`host_reservation_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_reservation_parser_unittest.cc' object='libdhcpsrv_unittests-host_reservation_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_reservation_parser_unittest.o `test -f 'host_reservation_parser_unittest.cc' || echo '$(srcdir)/'`host_reservation_parser_unittest.cc
+
+libdhcpsrv_unittests-host_reservation_parser_unittest.obj: host_reservation_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_reservation_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Tpo -c -o libdhcpsrv_unittests-host_reservation_parser_unittest.obj `if test -f 'host_reservation_parser_unittest.cc'; then $(CYGPATH_W) 'host_reservation_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_reservation_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_reservation_parser_unittest.cc' object='libdhcpsrv_unittests-host_reservation_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_reservation_parser_unittest.obj `if test -f 'host_reservation_parser_unittest.cc'; then $(CYGPATH_W) 'host_reservation_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_reservation_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-host_reservations_list_parser_unittest.o: host_reservations_list_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_reservations_list_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Tpo -c -o libdhcpsrv_unittests-host_reservations_list_parser_unittest.o `test -f 'host_reservations_list_parser_unittest.cc' || echo '$(srcdir)/'`host_reservations_list_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_reservations_list_parser_unittest.cc' object='libdhcpsrv_unittests-host_reservations_list_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_reservations_list_parser_unittest.o `test -f 'host_reservations_list_parser_unittest.cc' || echo '$(srcdir)/'`host_reservations_list_parser_unittest.cc
+
+libdhcpsrv_unittests-host_reservations_list_parser_unittest.obj: host_reservations_list_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-host_reservations_list_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Tpo -c -o libdhcpsrv_unittests-host_reservations_list_parser_unittest.obj `if test -f 'host_reservations_list_parser_unittest.cc'; then $(CYGPATH_W) 'host_reservations_list_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_reservations_list_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_reservations_list_parser_unittest.cc' object='libdhcpsrv_unittests-host_reservations_list_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-host_reservations_list_parser_unittest.obj `if test -f 'host_reservations_list_parser_unittest.cc'; then $(CYGPATH_W) 'host_reservations_list_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/host_reservations_list_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-ifaces_config_parser_unittest.o: ifaces_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ifaces_config_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-ifaces_config_parser_unittest.o `test -f 'ifaces_config_parser_unittest.cc' || echo '$(srcdir)/'`ifaces_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ifaces_config_parser_unittest.cc' object='libdhcpsrv_unittests-ifaces_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ifaces_config_parser_unittest.o `test -f 'ifaces_config_parser_unittest.cc' || echo '$(srcdir)/'`ifaces_config_parser_unittest.cc
+
+libdhcpsrv_unittests-ifaces_config_parser_unittest.obj: ifaces_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ifaces_config_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-ifaces_config_parser_unittest.obj `if test -f 'ifaces_config_parser_unittest.cc'; then $(CYGPATH_W) 'ifaces_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ifaces_config_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ifaces_config_parser_unittest.cc' object='libdhcpsrv_unittests-ifaces_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ifaces_config_parser_unittest.obj `if test -f 'ifaces_config_parser_unittest.cc'; then $(CYGPATH_W) 'ifaces_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ifaces_config_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-ip_range_unittest.o: ip_range_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ip_range_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Tpo -c -o libdhcpsrv_unittests-ip_range_unittest.o `test -f 'ip_range_unittest.cc' || echo '$(srcdir)/'`ip_range_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_range_unittest.cc' object='libdhcpsrv_unittests-ip_range_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ip_range_unittest.o `test -f 'ip_range_unittest.cc' || echo '$(srcdir)/'`ip_range_unittest.cc
+
+libdhcpsrv_unittests-ip_range_unittest.obj: ip_range_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ip_range_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Tpo -c -o libdhcpsrv_unittests-ip_range_unittest.obj `if test -f 'ip_range_unittest.cc'; then $(CYGPATH_W) 'ip_range_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ip_range_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_range_unittest.cc' object='libdhcpsrv_unittests-ip_range_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ip_range_unittest.obj `if test -f 'ip_range_unittest.cc'; then $(CYGPATH_W) 'ip_range_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ip_range_unittest.cc'; fi`
+
+libdhcpsrv_unittests-ip_range_permutation_unittest.o: ip_range_permutation_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ip_range_permutation_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Tpo -c -o libdhcpsrv_unittests-ip_range_permutation_unittest.o `test -f 'ip_range_permutation_unittest.cc' || echo '$(srcdir)/'`ip_range_permutation_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_range_permutation_unittest.cc' object='libdhcpsrv_unittests-ip_range_permutation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ip_range_permutation_unittest.o `test -f 'ip_range_permutation_unittest.cc' || echo '$(srcdir)/'`ip_range_permutation_unittest.cc
+
+libdhcpsrv_unittests-ip_range_permutation_unittest.obj: ip_range_permutation_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ip_range_permutation_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Tpo -c -o libdhcpsrv_unittests-ip_range_permutation_unittest.obj `if test -f 'ip_range_permutation_unittest.cc'; then $(CYGPATH_W) 'ip_range_permutation_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ip_range_permutation_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ip_range_permutation_unittest.cc' object='libdhcpsrv_unittests-ip_range_permutation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ip_range_permutation_unittest.obj `if test -f 'ip_range_permutation_unittest.cc'; then $(CYGPATH_W) 'ip_range_permutation_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ip_range_permutation_unittest.cc'; fi`
+
+libdhcpsrv_unittests-iterative_allocation_state_unittest.o: iterative_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-iterative_allocation_state_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-iterative_allocation_state_unittest.o `test -f 'iterative_allocation_state_unittest.cc' || echo '$(srcdir)/'`iterative_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iterative_allocation_state_unittest.cc' object='libdhcpsrv_unittests-iterative_allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-iterative_allocation_state_unittest.o `test -f 'iterative_allocation_state_unittest.cc' || echo '$(srcdir)/'`iterative_allocation_state_unittest.cc
+
+libdhcpsrv_unittests-iterative_allocation_state_unittest.obj: iterative_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-iterative_allocation_state_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-iterative_allocation_state_unittest.obj `if test -f 'iterative_allocation_state_unittest.cc'; then $(CYGPATH_W) 'iterative_allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iterative_allocation_state_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iterative_allocation_state_unittest.cc' object='libdhcpsrv_unittests-iterative_allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-iterative_allocation_state_unittest.obj `if test -f 'iterative_allocation_state_unittest.cc'; then $(CYGPATH_W) 'iterative_allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iterative_allocation_state_unittest.cc'; fi`
+
+libdhcpsrv_unittests-iterative_allocator_unittest.o: iterative_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-iterative_allocator_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Tpo -c -o libdhcpsrv_unittests-iterative_allocator_unittest.o `test -f 'iterative_allocator_unittest.cc' || echo '$(srcdir)/'`iterative_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iterative_allocator_unittest.cc' object='libdhcpsrv_unittests-iterative_allocator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-iterative_allocator_unittest.o `test -f 'iterative_allocator_unittest.cc' || echo '$(srcdir)/'`iterative_allocator_unittest.cc
+
+libdhcpsrv_unittests-iterative_allocator_unittest.obj: iterative_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-iterative_allocator_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Tpo -c -o libdhcpsrv_unittests-iterative_allocator_unittest.obj `if test -f 'iterative_allocator_unittest.cc'; then $(CYGPATH_W) 'iterative_allocator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iterative_allocator_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='iterative_allocator_unittest.cc' object='libdhcpsrv_unittests-iterative_allocator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-iterative_allocator_unittest.obj `if test -f 'iterative_allocator_unittest.cc'; then $(CYGPATH_W) 'iterative_allocator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/iterative_allocator_unittest.cc'; fi`
+
+libdhcpsrv_unittests-lease_file_loader_unittest.o: lease_file_loader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_file_loader_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Tpo -c -o libdhcpsrv_unittests-lease_file_loader_unittest.o `test -f 'lease_file_loader_unittest.cc' || echo '$(srcdir)/'`lease_file_loader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_file_loader_unittest.cc' object='libdhcpsrv_unittests-lease_file_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_file_loader_unittest.o `test -f 'lease_file_loader_unittest.cc' || echo '$(srcdir)/'`lease_file_loader_unittest.cc
+
+libdhcpsrv_unittests-lease_file_loader_unittest.obj: lease_file_loader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_file_loader_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Tpo -c -o libdhcpsrv_unittests-lease_file_loader_unittest.obj `if test -f 'lease_file_loader_unittest.cc'; then $(CYGPATH_W) 'lease_file_loader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_file_loader_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_file_loader_unittest.cc' object='libdhcpsrv_unittests-lease_file_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_file_loader_unittest.obj `if test -f 'lease_file_loader_unittest.cc'; then $(CYGPATH_W) 'lease_file_loader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_file_loader_unittest.cc'; fi`
+
+libdhcpsrv_unittests-lease_unittest.o: lease_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Tpo -c -o libdhcpsrv_unittests-lease_unittest.o `test -f 'lease_unittest.cc' || echo '$(srcdir)/'`lease_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_unittest.cc' object='libdhcpsrv_unittests-lease_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_unittest.o `test -f 'lease_unittest.cc' || echo '$(srcdir)/'`lease_unittest.cc
+
+libdhcpsrv_unittests-lease_unittest.obj: lease_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Tpo -c -o libdhcpsrv_unittests-lease_unittest.obj `if test -f 'lease_unittest.cc'; then $(CYGPATH_W) 'lease_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_unittest.cc' object='libdhcpsrv_unittests-lease_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_unittest.obj `if test -f 'lease_unittest.cc'; then $(CYGPATH_W) 'lease_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_unittest.cc'; fi`
+
+libdhcpsrv_unittests-lease_mgr_factory_unittest.o: lease_mgr_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_mgr_factory_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Tpo -c -o libdhcpsrv_unittests-lease_mgr_factory_unittest.o `test -f 'lease_mgr_factory_unittest.cc' || echo '$(srcdir)/'`lease_mgr_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_mgr_factory_unittest.cc' object='libdhcpsrv_unittests-lease_mgr_factory_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_mgr_factory_unittest.o `test -f 'lease_mgr_factory_unittest.cc' || echo '$(srcdir)/'`lease_mgr_factory_unittest.cc
+
+libdhcpsrv_unittests-lease_mgr_factory_unittest.obj: lease_mgr_factory_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_mgr_factory_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Tpo -c -o libdhcpsrv_unittests-lease_mgr_factory_unittest.obj `if test -f 'lease_mgr_factory_unittest.cc'; then $(CYGPATH_W) 'lease_mgr_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_mgr_factory_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_mgr_factory_unittest.cc' object='libdhcpsrv_unittests-lease_mgr_factory_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_mgr_factory_unittest.obj `if test -f 'lease_mgr_factory_unittest.cc'; then $(CYGPATH_W) 'lease_mgr_factory_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_mgr_factory_unittest.cc'; fi`
+
+libdhcpsrv_unittests-lease_mgr_unittest.o: lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-lease_mgr_unittest.o `test -f 'lease_mgr_unittest.cc' || echo '$(srcdir)/'`lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_mgr_unittest.cc' object='libdhcpsrv_unittests-lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_mgr_unittest.o `test -f 'lease_mgr_unittest.cc' || echo '$(srcdir)/'`lease_mgr_unittest.cc
+
+libdhcpsrv_unittests-lease_mgr_unittest.obj: lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-lease_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-lease_mgr_unittest.obj `if test -f 'lease_mgr_unittest.cc'; then $(CYGPATH_W) 'lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_mgr_unittest.cc' object='libdhcpsrv_unittests-lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-lease_mgr_unittest.obj `if test -f 'lease_mgr_unittest.cc'; then $(CYGPATH_W) 'lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/lease_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-generic_lease_mgr_unittest.o: generic_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-generic_lease_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-generic_lease_mgr_unittest.o `test -f 'generic_lease_mgr_unittest.cc' || echo '$(srcdir)/'`generic_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-generic_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-generic_lease_mgr_unittest.o `test -f 'generic_lease_mgr_unittest.cc' || echo '$(srcdir)/'`generic_lease_mgr_unittest.cc
+
+libdhcpsrv_unittests-generic_lease_mgr_unittest.obj: generic_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-generic_lease_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-generic_lease_mgr_unittest.obj `if test -f 'generic_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'generic_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/generic_lease_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-generic_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-generic_lease_mgr_unittest.obj `if test -f 'generic_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'generic_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/generic_lease_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-memfile_lease_extended_info_unittest.o: memfile_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-memfile_lease_extended_info_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Tpo -c -o libdhcpsrv_unittests-memfile_lease_extended_info_unittest.o `test -f 'memfile_lease_extended_info_unittest.cc' || echo '$(srcdir)/'`memfile_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_extended_info_unittest.cc' object='libdhcpsrv_unittests-memfile_lease_extended_info_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-memfile_lease_extended_info_unittest.o `test -f 'memfile_lease_extended_info_unittest.cc' || echo '$(srcdir)/'`memfile_lease_extended_info_unittest.cc
+
+libdhcpsrv_unittests-memfile_lease_extended_info_unittest.obj: memfile_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-memfile_lease_extended_info_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Tpo -c -o libdhcpsrv_unittests-memfile_lease_extended_info_unittest.obj `if test -f 'memfile_lease_extended_info_unittest.cc'; then $(CYGPATH_W) 'memfile_lease_extended_info_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memfile_lease_extended_info_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_extended_info_unittest.cc' object='libdhcpsrv_unittests-memfile_lease_extended_info_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-memfile_lease_extended_info_unittest.obj `if test -f 'memfile_lease_extended_info_unittest.cc'; then $(CYGPATH_W) 'memfile_lease_extended_info_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memfile_lease_extended_info_unittest.cc'; fi`
+
+libdhcpsrv_unittests-memfile_lease_limits_unittest.o: memfile_lease_limits_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-memfile_lease_limits_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Tpo -c -o libdhcpsrv_unittests-memfile_lease_limits_unittest.o `test -f 'memfile_lease_limits_unittest.cc' || echo '$(srcdir)/'`memfile_lease_limits_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_limits_unittest.cc' object='libdhcpsrv_unittests-memfile_lease_limits_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-memfile_lease_limits_unittest.o `test -f 'memfile_lease_limits_unittest.cc' || echo '$(srcdir)/'`memfile_lease_limits_unittest.cc
+
+libdhcpsrv_unittests-memfile_lease_limits_unittest.obj: memfile_lease_limits_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-memfile_lease_limits_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Tpo -c -o libdhcpsrv_unittests-memfile_lease_limits_unittest.obj `if test -f 'memfile_lease_limits_unittest.cc'; then $(CYGPATH_W) 'memfile_lease_limits_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memfile_lease_limits_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_limits_unittest.cc' object='libdhcpsrv_unittests-memfile_lease_limits_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-memfile_lease_limits_unittest.obj `if test -f 'memfile_lease_limits_unittest.cc'; then $(CYGPATH_W) 'memfile_lease_limits_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memfile_lease_limits_unittest.cc'; fi`
+
+libdhcpsrv_unittests-memfile_lease_mgr_unittest.o: memfile_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-memfile_lease_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-memfile_lease_mgr_unittest.o `test -f 'memfile_lease_mgr_unittest.cc' || echo '$(srcdir)/'`memfile_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-memfile_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-memfile_lease_mgr_unittest.o `test -f 'memfile_lease_mgr_unittest.cc' || echo '$(srcdir)/'`memfile_lease_mgr_unittest.cc
+
+libdhcpsrv_unittests-memfile_lease_mgr_unittest.obj: memfile_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-memfile_lease_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-memfile_lease_mgr_unittest.obj `if test -f 'memfile_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'memfile_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memfile_lease_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memfile_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-memfile_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-memfile_lease_mgr_unittest.obj `if test -f 'memfile_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'memfile_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memfile_lease_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-multi_threading_config_parser_unittest.o: multi_threading_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-multi_threading_config_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-multi_threading_config_parser_unittest.o `test -f 'multi_threading_config_parser_unittest.cc' || echo '$(srcdir)/'`multi_threading_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='multi_threading_config_parser_unittest.cc' object='libdhcpsrv_unittests-multi_threading_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-multi_threading_config_parser_unittest.o `test -f 'multi_threading_config_parser_unittest.cc' || echo '$(srcdir)/'`multi_threading_config_parser_unittest.cc
+
+libdhcpsrv_unittests-multi_threading_config_parser_unittest.obj: multi_threading_config_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-multi_threading_config_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Tpo -c -o libdhcpsrv_unittests-multi_threading_config_parser_unittest.obj `if test -f 'multi_threading_config_parser_unittest.cc'; then $(CYGPATH_W) 'multi_threading_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/multi_threading_config_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='multi_threading_config_parser_unittest.cc' object='libdhcpsrv_unittests-multi_threading_config_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-multi_threading_config_parser_unittest.obj `if test -f 'multi_threading_config_parser_unittest.cc'; then $(CYGPATH_W) 'multi_threading_config_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/multi_threading_config_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-dhcp_parsers_unittest.o: dhcp_parsers_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-dhcp_parsers_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Tpo -c -o libdhcpsrv_unittests-dhcp_parsers_unittest.o `test -f 'dhcp_parsers_unittest.cc' || echo '$(srcdir)/'`dhcp_parsers_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_parsers_unittest.cc' object='libdhcpsrv_unittests-dhcp_parsers_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-dhcp_parsers_unittest.o `test -f 'dhcp_parsers_unittest.cc' || echo '$(srcdir)/'`dhcp_parsers_unittest.cc
+
+libdhcpsrv_unittests-dhcp_parsers_unittest.obj: dhcp_parsers_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-dhcp_parsers_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Tpo -c -o libdhcpsrv_unittests-dhcp_parsers_unittest.obj `if test -f 'dhcp_parsers_unittest.cc'; then $(CYGPATH_W) 'dhcp_parsers_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_parsers_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_parsers_unittest.cc' object='libdhcpsrv_unittests-dhcp_parsers_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-dhcp_parsers_unittest.obj `if test -f 'dhcp_parsers_unittest.cc'; then $(CYGPATH_W) 'dhcp_parsers_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_parsers_unittest.cc'; fi`
+
+libdhcpsrv_unittests-ncr_generator_unittest.o: ncr_generator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ncr_generator_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Tpo -c -o libdhcpsrv_unittests-ncr_generator_unittest.o `test -f 'ncr_generator_unittest.cc' || echo '$(srcdir)/'`ncr_generator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_generator_unittest.cc' object='libdhcpsrv_unittests-ncr_generator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ncr_generator_unittest.o `test -f 'ncr_generator_unittest.cc' || echo '$(srcdir)/'`ncr_generator_unittest.cc
+
+libdhcpsrv_unittests-ncr_generator_unittest.obj: ncr_generator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-ncr_generator_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Tpo -c -o libdhcpsrv_unittests-ncr_generator_unittest.obj `if test -f 'ncr_generator_unittest.cc'; then $(CYGPATH_W) 'ncr_generator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_generator_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='ncr_generator_unittest.cc' object='libdhcpsrv_unittests-ncr_generator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-ncr_generator_unittest.obj `if test -f 'ncr_generator_unittest.cc'; then $(CYGPATH_W) 'ncr_generator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/ncr_generator_unittest.cc'; fi`
+
+libdhcpsrv_unittests-mysql_lease_mgr_unittest.o: mysql_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-mysql_lease_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-mysql_lease_mgr_unittest.o `test -f 'mysql_lease_mgr_unittest.cc' || echo '$(srcdir)/'`mysql_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-mysql_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-mysql_lease_mgr_unittest.o `test -f 'mysql_lease_mgr_unittest.cc' || echo '$(srcdir)/'`mysql_lease_mgr_unittest.cc
+
+libdhcpsrv_unittests-mysql_lease_mgr_unittest.obj: mysql_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-mysql_lease_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-mysql_lease_mgr_unittest.obj `if test -f 'mysql_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'mysql_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_lease_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-mysql_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-mysql_lease_mgr_unittest.obj `if test -f 'mysql_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'mysql_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_lease_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-mysql_lease_extended_info_unittest.o: mysql_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-mysql_lease_extended_info_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Tpo -c -o libdhcpsrv_unittests-mysql_lease_extended_info_unittest.o `test -f 'mysql_lease_extended_info_unittest.cc' || echo '$(srcdir)/'`mysql_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_lease_extended_info_unittest.cc' object='libdhcpsrv_unittests-mysql_lease_extended_info_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-mysql_lease_extended_info_unittest.o `test -f 'mysql_lease_extended_info_unittest.cc' || echo '$(srcdir)/'`mysql_lease_extended_info_unittest.cc
+
+libdhcpsrv_unittests-mysql_lease_extended_info_unittest.obj: mysql_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-mysql_lease_extended_info_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Tpo -c -o libdhcpsrv_unittests-mysql_lease_extended_info_unittest.obj `if test -f 'mysql_lease_extended_info_unittest.cc'; then $(CYGPATH_W) 'mysql_lease_extended_info_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_lease_extended_info_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_lease_extended_info_unittest.cc' object='libdhcpsrv_unittests-mysql_lease_extended_info_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-mysql_lease_extended_info_unittest.obj `if test -f 'mysql_lease_extended_info_unittest.cc'; then $(CYGPATH_W) 'mysql_lease_extended_info_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_lease_extended_info_unittest.cc'; fi`
+
+libdhcpsrv_unittests-mysql_host_data_source_unittest.o: mysql_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-mysql_host_data_source_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Tpo -c -o libdhcpsrv_unittests-mysql_host_data_source_unittest.o `test -f 'mysql_host_data_source_unittest.cc' || echo '$(srcdir)/'`mysql_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_host_data_source_unittest.cc' object='libdhcpsrv_unittests-mysql_host_data_source_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-mysql_host_data_source_unittest.o `test -f 'mysql_host_data_source_unittest.cc' || echo '$(srcdir)/'`mysql_host_data_source_unittest.cc
+
+libdhcpsrv_unittests-mysql_host_data_source_unittest.obj: mysql_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-mysql_host_data_source_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Tpo -c -o libdhcpsrv_unittests-mysql_host_data_source_unittest.obj `if test -f 'mysql_host_data_source_unittest.cc'; then $(CYGPATH_W) 'mysql_host_data_source_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_host_data_source_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_host_data_source_unittest.cc' object='libdhcpsrv_unittests-mysql_host_data_source_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-mysql_host_data_source_unittest.obj `if test -f 'mysql_host_data_source_unittest.cc'; then $(CYGPATH_W) 'mysql_host_data_source_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_host_data_source_unittest.cc'; fi`
+
+libdhcpsrv_unittests-pgsql_lease_mgr_unittest.o: pgsql_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pgsql_lease_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-pgsql_lease_mgr_unittest.o `test -f 'pgsql_lease_mgr_unittest.cc' || echo '$(srcdir)/'`pgsql_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-pgsql_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pgsql_lease_mgr_unittest.o `test -f 'pgsql_lease_mgr_unittest.cc' || echo '$(srcdir)/'`pgsql_lease_mgr_unittest.cc
+
+libdhcpsrv_unittests-pgsql_lease_mgr_unittest.obj: pgsql_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pgsql_lease_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-pgsql_lease_mgr_unittest.obj `if test -f 'pgsql_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'pgsql_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_lease_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-pgsql_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pgsql_lease_mgr_unittest.obj `if test -f 'pgsql_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'pgsql_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_lease_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.o: pgsql_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Tpo -c -o libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.o `test -f 'pgsql_lease_extended_info_unittest.cc' || echo '$(srcdir)/'`pgsql_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_lease_extended_info_unittest.cc' object='libdhcpsrv_unittests-pgsql_lease_extended_info_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.o `test -f 'pgsql_lease_extended_info_unittest.cc' || echo '$(srcdir)/'`pgsql_lease_extended_info_unittest.cc
+
+libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.obj: pgsql_lease_extended_info_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Tpo -c -o libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.obj `if test -f 'pgsql_lease_extended_info_unittest.cc'; then $(CYGPATH_W) 'pgsql_lease_extended_info_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_lease_extended_info_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_lease_extended_info_unittest.cc' object='libdhcpsrv_unittests-pgsql_lease_extended_info_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.obj `if test -f 'pgsql_lease_extended_info_unittest.cc'; then $(CYGPATH_W) 'pgsql_lease_extended_info_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_lease_extended_info_unittest.cc'; fi`
+
+libdhcpsrv_unittests-pgsql_host_data_source_unittest.o: pgsql_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pgsql_host_data_source_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Tpo -c -o libdhcpsrv_unittests-pgsql_host_data_source_unittest.o `test -f 'pgsql_host_data_source_unittest.cc' || echo '$(srcdir)/'`pgsql_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_host_data_source_unittest.cc' object='libdhcpsrv_unittests-pgsql_host_data_source_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pgsql_host_data_source_unittest.o `test -f 'pgsql_host_data_source_unittest.cc' || echo '$(srcdir)/'`pgsql_host_data_source_unittest.cc
+
+libdhcpsrv_unittests-pgsql_host_data_source_unittest.obj: pgsql_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pgsql_host_data_source_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Tpo -c -o libdhcpsrv_unittests-pgsql_host_data_source_unittest.obj `if test -f 'pgsql_host_data_source_unittest.cc'; then $(CYGPATH_W) 'pgsql_host_data_source_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_host_data_source_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_host_data_source_unittest.cc' object='libdhcpsrv_unittests-pgsql_host_data_source_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pgsql_host_data_source_unittest.obj `if test -f 'pgsql_host_data_source_unittest.cc'; then $(CYGPATH_W) 'pgsql_host_data_source_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_host_data_source_unittest.cc'; fi`
+
+libdhcpsrv_unittests-pool_unittest.o: pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pool_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Tpo -c -o libdhcpsrv_unittests-pool_unittest.o `test -f 'pool_unittest.cc' || echo '$(srcdir)/'`pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pool_unittest.cc' object='libdhcpsrv_unittests-pool_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pool_unittest.o `test -f 'pool_unittest.cc' || echo '$(srcdir)/'`pool_unittest.cc
+
+libdhcpsrv_unittests-pool_unittest.obj: pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-pool_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Tpo -c -o libdhcpsrv_unittests-pool_unittest.obj `if test -f 'pool_unittest.cc'; then $(CYGPATH_W) 'pool_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pool_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pool_unittest.cc' object='libdhcpsrv_unittests-pool_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-pool_unittest.obj `if test -f 'pool_unittest.cc'; then $(CYGPATH_W) 'pool_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pool_unittest.cc'; fi`
+
+libdhcpsrv_unittests-random_allocation_state_unittest.o: random_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-random_allocation_state_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-random_allocation_state_unittest.o `test -f 'random_allocation_state_unittest.cc' || echo '$(srcdir)/'`random_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_allocation_state_unittest.cc' object='libdhcpsrv_unittests-random_allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-random_allocation_state_unittest.o `test -f 'random_allocation_state_unittest.cc' || echo '$(srcdir)/'`random_allocation_state_unittest.cc
+
+libdhcpsrv_unittests-random_allocation_state_unittest.obj: random_allocation_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-random_allocation_state_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Tpo -c -o libdhcpsrv_unittests-random_allocation_state_unittest.obj `if test -f 'random_allocation_state_unittest.cc'; then $(CYGPATH_W) 'random_allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/random_allocation_state_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_allocation_state_unittest.cc' object='libdhcpsrv_unittests-random_allocation_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-random_allocation_state_unittest.obj `if test -f 'random_allocation_state_unittest.cc'; then $(CYGPATH_W) 'random_allocation_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/random_allocation_state_unittest.cc'; fi`
+
+libdhcpsrv_unittests-random_allocator_unittest.o: random_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-random_allocator_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Tpo -c -o libdhcpsrv_unittests-random_allocator_unittest.o `test -f 'random_allocator_unittest.cc' || echo '$(srcdir)/'`random_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_allocator_unittest.cc' object='libdhcpsrv_unittests-random_allocator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-random_allocator_unittest.o `test -f 'random_allocator_unittest.cc' || echo '$(srcdir)/'`random_allocator_unittest.cc
+
+libdhcpsrv_unittests-random_allocator_unittest.obj: random_allocator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-random_allocator_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Tpo -c -o libdhcpsrv_unittests-random_allocator_unittest.obj `if test -f 'random_allocator_unittest.cc'; then $(CYGPATH_W) 'random_allocator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/random_allocator_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='random_allocator_unittest.cc' object='libdhcpsrv_unittests-random_allocator_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-random_allocator_unittest.obj `if test -f 'random_allocator_unittest.cc'; then $(CYGPATH_W) 'random_allocator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/random_allocator_unittest.cc'; fi`
+
+libdhcpsrv_unittests-resource_handler_unittest.o: resource_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-resource_handler_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Tpo -c -o libdhcpsrv_unittests-resource_handler_unittest.o `test -f 'resource_handler_unittest.cc' || echo '$(srcdir)/'`resource_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='resource_handler_unittest.cc' object='libdhcpsrv_unittests-resource_handler_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-resource_handler_unittest.o `test -f 'resource_handler_unittest.cc' || echo '$(srcdir)/'`resource_handler_unittest.cc
+
+libdhcpsrv_unittests-resource_handler_unittest.obj: resource_handler_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-resource_handler_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Tpo -c -o libdhcpsrv_unittests-resource_handler_unittest.obj `if test -f 'resource_handler_unittest.cc'; then $(CYGPATH_W) 'resource_handler_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/resource_handler_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='resource_handler_unittest.cc' object='libdhcpsrv_unittests-resource_handler_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-resource_handler_unittest.obj `if test -f 'resource_handler_unittest.cc'; then $(CYGPATH_W) 'resource_handler_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/resource_handler_unittest.cc'; fi`
+
+libdhcpsrv_unittests-sanity_checks_unittest.o: sanity_checks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-sanity_checks_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Tpo -c -o libdhcpsrv_unittests-sanity_checks_unittest.o `test -f 'sanity_checks_unittest.cc' || echo '$(srcdir)/'`sanity_checks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sanity_checks_unittest.cc' object='libdhcpsrv_unittests-sanity_checks_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-sanity_checks_unittest.o `test -f 'sanity_checks_unittest.cc' || echo '$(srcdir)/'`sanity_checks_unittest.cc
+
+libdhcpsrv_unittests-sanity_checks_unittest.obj: sanity_checks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-sanity_checks_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Tpo -c -o libdhcpsrv_unittests-sanity_checks_unittest.obj `if test -f 'sanity_checks_unittest.cc'; then $(CYGPATH_W) 'sanity_checks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/sanity_checks_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sanity_checks_unittest.cc' object='libdhcpsrv_unittests-sanity_checks_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-sanity_checks_unittest.obj `if test -f 'sanity_checks_unittest.cc'; then $(CYGPATH_W) 'sanity_checks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/sanity_checks_unittest.cc'; fi`
+
+libdhcpsrv_unittests-shared_network_parser_unittest.o: shared_network_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-shared_network_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Tpo -c -o libdhcpsrv_unittests-shared_network_parser_unittest.o `test -f 'shared_network_parser_unittest.cc' || echo '$(srcdir)/'`shared_network_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_parser_unittest.cc' object='libdhcpsrv_unittests-shared_network_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-shared_network_parser_unittest.o `test -f 'shared_network_parser_unittest.cc' || echo '$(srcdir)/'`shared_network_parser_unittest.cc
+
+libdhcpsrv_unittests-shared_network_parser_unittest.obj: shared_network_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-shared_network_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Tpo -c -o libdhcpsrv_unittests-shared_network_parser_unittest.obj `if test -f 'shared_network_parser_unittest.cc'; then $(CYGPATH_W) 'shared_network_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_network_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_parser_unittest.cc' object='libdhcpsrv_unittests-shared_network_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-shared_network_parser_unittest.obj `if test -f 'shared_network_parser_unittest.cc'; then $(CYGPATH_W) 'shared_network_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_network_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-shared_network_unittest.o: shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-shared_network_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Tpo -c -o libdhcpsrv_unittests-shared_network_unittest.o `test -f 'shared_network_unittest.cc' || echo '$(srcdir)/'`shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_unittest.cc' object='libdhcpsrv_unittests-shared_network_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-shared_network_unittest.o `test -f 'shared_network_unittest.cc' || echo '$(srcdir)/'`shared_network_unittest.cc
+
+libdhcpsrv_unittests-shared_network_unittest.obj: shared_network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-shared_network_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Tpo -c -o libdhcpsrv_unittests-shared_network_unittest.obj `if test -f 'shared_network_unittest.cc'; then $(CYGPATH_W) 'shared_network_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_network_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_network_unittest.cc' object='libdhcpsrv_unittests-shared_network_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-shared_network_unittest.obj `if test -f 'shared_network_unittest.cc'; then $(CYGPATH_W) 'shared_network_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_network_unittest.cc'; fi`
+
+libdhcpsrv_unittests-shared_networks_list_parser_unittest.o: shared_networks_list_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-shared_networks_list_parser_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Tpo -c -o libdhcpsrv_unittests-shared_networks_list_parser_unittest.o `test -f 'shared_networks_list_parser_unittest.cc' || echo '$(srcdir)/'`shared_networks_list_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_networks_list_parser_unittest.cc' object='libdhcpsrv_unittests-shared_networks_list_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-shared_networks_list_parser_unittest.o `test -f 'shared_networks_list_parser_unittest.cc' || echo '$(srcdir)/'`shared_networks_list_parser_unittest.cc
+
+libdhcpsrv_unittests-shared_networks_list_parser_unittest.obj: shared_networks_list_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-shared_networks_list_parser_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Tpo -c -o libdhcpsrv_unittests-shared_networks_list_parser_unittest.obj `if test -f 'shared_networks_list_parser_unittest.cc'; then $(CYGPATH_W) 'shared_networks_list_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_networks_list_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='shared_networks_list_parser_unittest.cc' object='libdhcpsrv_unittests-shared_networks_list_parser_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-shared_networks_list_parser_unittest.obj `if test -f 'shared_networks_list_parser_unittest.cc'; then $(CYGPATH_W) 'shared_networks_list_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/shared_networks_list_parser_unittest.cc'; fi`
+
+libdhcpsrv_unittests-srv_config_unittest.o: srv_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-srv_config_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Tpo -c -o libdhcpsrv_unittests-srv_config_unittest.o `test -f 'srv_config_unittest.cc' || echo '$(srcdir)/'`srv_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='srv_config_unittest.cc' object='libdhcpsrv_unittests-srv_config_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-srv_config_unittest.o `test -f 'srv_config_unittest.cc' || echo '$(srcdir)/'`srv_config_unittest.cc
+
+libdhcpsrv_unittests-srv_config_unittest.obj: srv_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-srv_config_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Tpo -c -o libdhcpsrv_unittests-srv_config_unittest.obj `if test -f 'srv_config_unittest.cc'; then $(CYGPATH_W) 'srv_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/srv_config_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='srv_config_unittest.cc' object='libdhcpsrv_unittests-srv_config_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-srv_config_unittest.obj `if test -f 'srv_config_unittest.cc'; then $(CYGPATH_W) 'srv_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/srv_config_unittest.cc'; fi`
+
+libdhcpsrv_unittests-subnet_unittest.o: subnet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-subnet_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Tpo -c -o libdhcpsrv_unittests-subnet_unittest.o `test -f 'subnet_unittest.cc' || echo '$(srcdir)/'`subnet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='subnet_unittest.cc' object='libdhcpsrv_unittests-subnet_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-subnet_unittest.o `test -f 'subnet_unittest.cc' || echo '$(srcdir)/'`subnet_unittest.cc
+
+libdhcpsrv_unittests-subnet_unittest.obj: subnet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-subnet_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Tpo -c -o libdhcpsrv_unittests-subnet_unittest.obj `if test -f 'subnet_unittest.cc'; then $(CYGPATH_W) 'subnet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/subnet_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='subnet_unittest.cc' object='libdhcpsrv_unittests-subnet_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-subnet_unittest.obj `if test -f 'subnet_unittest.cc'; then $(CYGPATH_W) 'subnet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/subnet_unittest.cc'; fi`
+
+libdhcpsrv_unittests-test_get_callout_handle.o: test_get_callout_handle.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-test_get_callout_handle.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Tpo -c -o libdhcpsrv_unittests-test_get_callout_handle.o `test -f 'test_get_callout_handle.cc' || echo '$(srcdir)/'`test_get_callout_handle.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Tpo $(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_get_callout_handle.cc' object='libdhcpsrv_unittests-test_get_callout_handle.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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-test_get_callout_handle.o `test -f 'test_get_callout_handle.cc' || echo '$(srcdir)/'`test_get_callout_handle.cc
+
+libdhcpsrv_unittests-test_get_callout_handle.obj: test_get_callout_handle.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-test_get_callout_handle.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Tpo -c -o libdhcpsrv_unittests-test_get_callout_handle.obj `if test -f 'test_get_callout_handle.cc'; then $(CYGPATH_W) 'test_get_callout_handle.cc'; else $(CYGPATH_W) '$(srcdir)/test_get_callout_handle.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Tpo $(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_get_callout_handle.cc' object='libdhcpsrv_unittests-test_get_callout_handle.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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-test_get_callout_handle.obj `if test -f 'test_get_callout_handle.cc'; then $(CYGPATH_W) 'test_get_callout_handle.cc'; else $(CYGPATH_W) '$(srcdir)/test_get_callout_handle.cc'; fi`
+
+libdhcpsrv_unittests-timer_mgr_unittest.o: timer_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-timer_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-timer_mgr_unittest.o `test -f 'timer_mgr_unittest.cc' || echo '$(srcdir)/'`timer_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='timer_mgr_unittest.cc' object='libdhcpsrv_unittests-timer_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-timer_mgr_unittest.o `test -f 'timer_mgr_unittest.cc' || echo '$(srcdir)/'`timer_mgr_unittest.cc
+
+libdhcpsrv_unittests-timer_mgr_unittest.obj: timer_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-timer_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-timer_mgr_unittest.obj `if test -f 'timer_mgr_unittest.cc'; then $(CYGPATH_W) 'timer_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/timer_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='timer_mgr_unittest.cc' object='libdhcpsrv_unittests-timer_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-timer_mgr_unittest.obj `if test -f 'timer_mgr_unittest.cc'; then $(CYGPATH_W) 'timer_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/timer_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-tracking_lease_mgr_unittest.o: tracking_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-tracking_lease_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-tracking_lease_mgr_unittest.o `test -f 'tracking_lease_mgr_unittest.cc' || echo '$(srcdir)/'`tracking_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tracking_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-tracking_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-tracking_lease_mgr_unittest.o `test -f 'tracking_lease_mgr_unittest.cc' || echo '$(srcdir)/'`tracking_lease_mgr_unittest.cc
+
+libdhcpsrv_unittests-tracking_lease_mgr_unittest.obj: tracking_lease_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-tracking_lease_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.Tpo -c -o libdhcpsrv_unittests-tracking_lease_mgr_unittest.obj `if test -f 'tracking_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'tracking_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tracking_lease_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tracking_lease_mgr_unittest.cc' object='libdhcpsrv_unittests-tracking_lease_mgr_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-tracking_lease_mgr_unittest.obj `if test -f 'tracking_lease_mgr_unittest.cc'; then $(CYGPATH_W) 'tracking_lease_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tracking_lease_mgr_unittest.cc'; fi`
+
+libdhcpsrv_unittests-network_state_unittest.o: network_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-network_state_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Tpo -c -o libdhcpsrv_unittests-network_state_unittest.o `test -f 'network_state_unittest.cc' || echo '$(srcdir)/'`network_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='network_state_unittest.cc' object='libdhcpsrv_unittests-network_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-network_state_unittest.o `test -f 'network_state_unittest.cc' || echo '$(srcdir)/'`network_state_unittest.cc
+
+libdhcpsrv_unittests-network_state_unittest.obj: network_state_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-network_state_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Tpo -c -o libdhcpsrv_unittests-network_state_unittest.obj `if test -f 'network_state_unittest.cc'; then $(CYGPATH_W) 'network_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/network_state_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='network_state_unittest.cc' object='libdhcpsrv_unittests-network_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-network_state_unittest.obj `if test -f 'network_state_unittest.cc'; then $(CYGPATH_W) 'network_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/network_state_unittest.cc'; fi`
+
+libdhcpsrv_unittests-network_unittest.o: network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-network_unittest.o -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-network_unittest.Tpo -c -o libdhcpsrv_unittests-network_unittest.o `test -f 'network_unittest.cc' || echo '$(srcdir)/'`network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-network_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='network_unittest.cc' object='libdhcpsrv_unittests-network_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-network_unittest.o `test -f 'network_unittest.cc' || echo '$(srcdir)/'`network_unittest.cc
+
+libdhcpsrv_unittests-network_unittest.obj: network_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrv_unittests-network_unittest.obj -MD -MP -MF $(DEPDIR)/libdhcpsrv_unittests-network_unittest.Tpo -c -o libdhcpsrv_unittests-network_unittest.obj `if test -f 'network_unittest.cc'; then $(CYGPATH_W) 'network_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/network_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrv_unittests-network_unittest.Tpo $(DEPDIR)/libdhcpsrv_unittests-network_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='network_unittest.cc' object='libdhcpsrv_unittests-network_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) $(libdhcpsrv_unittests_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrv_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrv_unittests-network_unittest.obj `if test -f 'network_unittest.cc'; then $(CYGPATH_W) 'network_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/network_unittest.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) $(LTLIBRARIES)
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libco1_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libco2_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libco3_la-callout_params_library.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-network_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.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)/libco1_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libco2_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libco3_la-callout_params_library.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_expiration_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_hooks_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-alloc_engine_utils.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-callout_handle_store_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cb_ctl_dhcp_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_db_access_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_duid_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_expiration_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_host_operations_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_hosts_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_iface_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_mac_source_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_multi_threading_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_def_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_option_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_rsoo_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_shared_networks6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfg_subnets6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-cfgmgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-client_class_def_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file4_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-csv_lease_file6_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-d2_client_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-d2_udp_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-dhcp4o6_ipc_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-dhcp_parsers_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-dhcp_queue_control_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-duid_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-expiration_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-flq_allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-flq_allocator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-generic_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_cache_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_data_source_factory_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_reservation_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_reservations_list_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-host_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ifaces_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ip_range_permutation_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ip_range_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-iterative_allocator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_file_loader_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_factory_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-lease_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_extended_info_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_limits_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-memfile_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-multi_threading_config_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-mysql_host_data_source_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_extended_info_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-mysql_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-ncr_generator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-network_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-network_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_host_data_source_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_extended_info_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pgsql_lease_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-pool_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-random_allocation_state_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-random_allocator_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-resource_handler_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-sanity_checks_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-shared_network_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-shared_network_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-shared_networks_list_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-srv_config_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-subnet_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-test_get_callout_handle.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-timer_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/libdhcpsrv_unittests-tracking_lease_mgr_unittest.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-noinstLTLIBRARIES 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/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
new file mode 100644
index 0000000..327a036
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
@@ -0,0 +1,5988 @@
+// Copyright (C) 2015-2023 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 <dhcp/pkt4.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <eval/eval_context.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#if defined HAVE_MYSQL
+#include <mysql/testutils/mysql_schema.h>
+#endif
+
+#if defined HAVE_PGSQL
+#include <pgsql/testutils/pgsql_schema.h>
+#endif
+
+#include <boost/pointer_cast.hpp>
+
+using namespace std;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::stats;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// This test checks if the v4 Allocation Engine can be instantiated, parses
+// parameters string and allocators are created.
+TEST_F(AllocEngine4Test, constructor) {
+ boost::scoped_ptr<AllocEngine> x;
+
+ // Create V4 (ipv6=false) Allocation Engine that will try at most
+ // 100 attempts to pick up a lease
+ ASSERT_NO_THROW(x.reset(new AllocEngine(100)));
+}
+
+// This test checks if two simple IPv4 allocations succeed and that the
+// statistics is properly updated. Prior to the second allocation it
+// resets the pointer to the last allocated address within the address
+// pool. This causes the engine to walk over the already allocated
+// address and then pick the first available address for the second
+// allocation. Because the allocation engine checks the callouts next
+// step status after each attempt to allocate an address, this test
+// also sets this status to non-default value prior to the second
+// allocation attempt, to make sure that this unexpected status will
+// not interfere with the allocation.
+TEST_F(AllocEngine4Test, simpleAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Assigned addresses should be zero.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Assigned addresses should have incremented.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+
+ // Second allocation starts here.
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr2, IOAddress("0.0.0.0"),
+ false, true, "anotherhost.example.com.",
+ false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Set the next step to non-default value to verify that it doesn't
+ // affect the allocation.
+ ctx2.callout_handle_ = HooksManager::createCalloutHandle();
+ ctx2.callout_handle_->setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ // Set the last allocated to the beginning of the pool. The allocation
+ // engine should detect that the first address is already allocated and
+ // assign the first available one.
+ auto allocation_state = boost::dynamic_pointer_cast<PoolIterativeAllocationState>(pool_->getAllocationState());
+ allocation_state->resetLastAllocated();
+
+ lease = engine->allocateLease4(ctx2);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx2.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Assigned addresses should have incremented.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 2, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+}
+
+// This test checks that simple allocation uses the default valid lifetime.
+TEST_F(AllocEngine4Test, defaultAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check the valid lifetime has the default.
+ EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks that simple allocation uses the specified valid lifetime.
+TEST_F(AllocEngine4Test, hintAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Specify the valid lifetime we want.
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 4));
+ ctx.query_->addOption(opt);
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check the valid lifetime has the wanted value.
+ EXPECT_EQ(opt->getValue(), lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks that simple allocation uses the min valid lifetime.
+TEST_F(AllocEngine4Test, minAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(2, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Specify the valid lifetime we want, as it is lower than the min value
+ // we'll get this min value instead.
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 1));
+ ctx.query_->addOption(opt);
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check the valid lifetime has the wanted value.
+ EXPECT_EQ(subnet_->getValid().getMin(), lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks that simple allocation uses the max valid lifetime.
+TEST_F(AllocEngine4Test, maxAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Specify the valid lifetime we want, as it is greater than the max value
+ // we'll get this max value instead.
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 6));
+ ctx.query_->addOption(opt);
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check the valid lifetime has the wanted value.
+ EXPECT_EQ(subnet_->getValid().getMax(), lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks that simple allocation handles BOOTP queries.
+TEST_F(AllocEngine4Test, bootpAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if the fake allocation (for DHCPDISCOVER) can succeed
+TEST_F(AllocEngine4Test, fakeAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Assigned addresses should be zero.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, true,
+ "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is NOT in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_FALSE(from_mgr);
+
+ // Assigned addresses should still be zero.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+}
+
+// This test checks if the allocation with a hint that is valid (in range,
+// in pool and free) can succeed
+TEST_F(AllocEngine4Test, allocWithValidHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.105"), true, true,
+ "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We have allocated the new lease, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // We should get what we asked for
+ EXPECT_EQ(lease->addr_.toText(), "192.0.2.105");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if the allocation with a hint that is in range,
+// in pool, but is currently used can succeed
+TEST_F(AllocEngine4Test, allocWithUsedHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2,
+ clientid2, sizeof(clientid2), 1, now, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Another client comes in and request an address that is in pool, but
+ // unfortunately it is used already. The same address must not be allocated
+ // twice.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.106"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // New lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Allocated address must be different
+ EXPECT_NE(used->addr_, lease->addr_);
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_NE("192.0.2.106", lease->addr_.toText());
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // The lease should not be in the LeaseMgr because it was a failed allocation.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_FALSE(from_mgr);
+}
+
+// This test checks if an allocation with a hint that is out of the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngine4Test, allocBogusHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Client would like to get a 10.1.1.1 lease, which does not belong to any
+ // supported lease. Allocation engine should ignore it and carry on
+ // with the normal allocation
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("10.1.1.1"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We have allocated a new lease, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_NE("10.1.1.1", lease->addr_.toText());
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is not in the LeaseMgr as it is a fake allocation.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ EXPECT_FALSE(from_mgr);
+}
+
+// This test checks that NULL values are handled properly
+TEST_F(AllocEngine4Test, allocateLease4Nulls) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Allocations without subnet are not allowed
+ AllocEngine::ClientContext4 ctx1(Subnet4Ptr(), clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine->allocateLease4(ctx1);
+
+ ASSERT_FALSE(lease);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Allocations without HW address are not allowed
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, HWAddrPtr(),
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx2);
+ ASSERT_FALSE(lease);
+ ASSERT_FALSE(ctx2.old_lease_);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Allocations without client-id are allowed
+ clientid_.reset();
+ AllocEngine::ClientContext4 ctx3(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx3.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx3);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+ // New lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx3.old_lease_);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if a returning client can renew an
+// an existing lease and assigned-leases increments accordingly
+TEST_F(AllocEngine4Test, simpleRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease);
+ checkLease4(lease);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // We should have incremented assigned-addresses
+ EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease2);
+ checkLease4(lease2);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Should NOT have bumped assigned-addresses
+ EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+}
+
+// This test checks simple renewal uses the default valid lifetime.
+TEST_F(AllocEngine4Test, defaultRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease);
+ checkLease4(lease);
+
+ // Check the valid lifetime has the default.
+ EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease2);
+ checkLease4(lease2);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Check the renewed valid lifetime has the default.
+ EXPECT_EQ(subnet_->getValid(), lease2->valid_lft_);
+}
+
+// This test checks simple renewal uses the specified valid lifetime.
+TEST_F(AllocEngine4Test, hintRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Specify the valid lifetime we want.
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 4));
+ ctx.query_->addOption(opt);
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease);
+ checkLease4(lease);
+
+ // Check the valid lifetime has the wanted value.
+ EXPECT_EQ(opt->getValue(), lease->valid_lft_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease2);
+ checkLease4(lease2);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Check the renewed valid lifetime has the wanted value.
+ EXPECT_EQ(opt->getValue(), lease2->valid_lft_);
+}
+
+// This test checks simple renewal uses the min valid lifetime.
+TEST_F(AllocEngine4Test, minRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(2, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Specify the valid lifetime we want, as it is lower than the min value
+ // we'll get this min value instead.
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 1));
+ ctx.query_->addOption(opt);
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease);
+ checkLease4(lease);
+
+ // Check the valid lifetime has the min value.
+ EXPECT_EQ(subnet_->getValid().getMin(), lease->valid_lft_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease2);
+ checkLease4(lease2);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Check the renewed valid lifetime has the min value.
+ EXPECT_EQ(subnet_->getValid().getMin(), lease2->valid_lft_);
+}
+
+// This test checks simple renewal uses the max valid lifetime.
+TEST_F(AllocEngine4Test, maxRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Specify the valid lifetime we want, as it is greater than the max value
+ // we'll get this max value instead.
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 6));
+ ctx.query_->addOption(opt);
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease);
+ checkLease4(lease);
+
+ // Check the valid lifetime has the max value.
+ EXPECT_EQ(subnet_->getValid().getMax(), lease->valid_lft_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease and it's sane
+ ASSERT_TRUE(lease2);
+ checkLease4(lease2);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Check the renewed valid lifetime has the max value.
+ EXPECT_EQ(subnet_->getValid().getMax(), lease2->valid_lft_);
+}
+
+// This test checks simple renewal handles BOOTP queries.
+TEST_F(AllocEngine4Test, bootpRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease.
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease.
+ ASSERT_TRUE(lease2);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease2->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease2->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease2->addr_));
+ ASSERT_TRUE(lease2->client_id_);
+ EXPECT_TRUE(*lease2->client_id_ == *clientid_);
+ ASSERT_TRUE(lease2->hwaddr_);
+ EXPECT_TRUE(*lease2->hwaddr_ == *hwaddr_);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Check the renewed valid lifetime has the max value.
+ EXPECT_EQ(infinity_lft, lease2->valid_lft_);
+}
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine4Test, smallPool4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Get rid of the default subnet configuration.
+ cfg_mgr.clear();
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+
+ // We have allocated new lease, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine4Test, outOfAddresses4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Get rid of the default test configuration.
+ cfg_mgr.clear();
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2,
+ sizeof(clientid2), 501, now,
+ subnet_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // There is just a single address in the pool and allocated it to someone
+ // else, so the allocation should fail
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+ ASSERT_FALSE(lease2);
+ ASSERT_FALSE(ctx.old_lease_);
+
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+}
+
+/// @brief This test class is dedicated to testing shared networks
+///
+/// It uses one common configuration:
+/// 1 shared network with 2 subnets:
+/// - 192.0.2.0/24 subnet with a small pool of single address: 192.0.2.17
+/// - 10.1.2.0/24 subnet with pool with 96 addresses.
+class SharedNetworkAlloc4Test : public AllocEngine4Test {
+public:
+
+ /// @brief Initializes configuration (2 subnets, 1 shared network)
+ SharedNetworkAlloc4Test() : engine_(0) {
+ // Create two subnets, each with a single address pool. The first subnet
+ // has only one address in its address pool to make it easier to simulate
+ // address exhaustion.
+ subnet1_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ subnet2_ = Subnet4::create(IOAddress("10.1.2.0"), 24, 1, 2, 3, SubnetID(20));
+ pool1_.reset(new Pool4(IOAddress("192.0.2.17"), IOAddress("192.0.2.17")));
+ pool2_.reset(new Pool4(IOAddress("10.1.2.5"), IOAddress("10.1.2.100")));
+
+ subnet1_->addPool(pool1_);
+ subnet2_->addPool(pool2_);
+
+ // Both subnets belong to the same network so they can be used
+ // interchangeably.
+ network_.reset(new SharedNetwork4("test_network"));
+ network_->add(subnet1_);
+ network_->add(subnet2_);
+
+ std::vector<uint8_t> hwaddr_vec = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ hwaddr2_.reset(new HWAddr(hwaddr_vec, HTYPE_ETHER));
+ }
+
+ /// @brief Inserts a new lease for specified address
+ ///
+ /// Creates a new lease for specified address and subnet-id and inserts
+ /// it into database. This is not particularly fancy method, it is used
+ /// just to mark existing addresses as used. It uses hwaddr2_ to allocate
+ /// the lease.
+ ///
+ /// @param addr text representation of the address
+ /// @param subnet_id ID of the subnet
+ /// @param return pointer to the lease
+ Lease4Ptr
+ insertLease(std::string addr, SubnetID subnet_id) {
+ Lease4Ptr lease(new Lease4(IOAddress(addr), hwaddr2_, ClientIdPtr(),
+ 501, time(NULL), subnet_id));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ if (!LeaseMgrFactory::instance().addLease(lease)) {
+ ADD_FAILURE() << "Attempt to add a lease for IP " << addr
+ << " in subnet " << subnet_id << " failed";
+ }
+
+ return (lease);
+ }
+
+ /// Convenience pointers to configuration elements. These are initialized
+ /// in the constructor and are used throughout the tests.
+ AllocEngine engine_;
+ Subnet4Ptr subnet1_;
+ Subnet4Ptr subnet2_;
+ Pool4Ptr pool1_;
+ Pool4Ptr pool2_;
+ SharedNetwork4Ptr network_;
+
+ HWAddrPtr hwaddr2_; // Note there's hwaddr_ already defined in base class.
+};
+
+// This test verifies that the server can offer an address from a
+// subnet and the introduction of shared network doesn't break anything here.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkSimple) {
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+
+ // The allocation engine should have assigned an address from the first
+ // subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+ // Make sure the lease is not in the lease mgr (this is only
+ // discover).
+ ASSERT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+}
+
+// This test verifies that the server will pick a second subnet out of two
+// shared subnets if there is a hint for the second subnet.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkHint) {
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress("10.1.2.25"),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+
+ // The allocation engine should have assigned an address from the second
+ // subnet, because that's what the hint requested.
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+}
+
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetwork) {
+ // Create a lease for a single address in the first address pool. The
+ // pool is now exhausted.
+ Lease4Ptr lease = insertLease("192.0.2.17", subnet1_->getID());
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease2 = engine_.allocateLease4(ctx);
+ // The allocation engine should have assigned an address from the second
+ // subnet. We could guess that this is 10.1.2.5, being the first address
+ // in the address pool, but to make the test more generic, we merely
+ // verify that the address is in the given address pool.
+ ASSERT_TRUE(lease2);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease2->addr_));
+
+ // The client should also be offered a lease when it specifies a hint
+ // that doesn't match the subnet from which the lease is offered. The
+ // engine should check alternative subnets to match the hint to
+ // a subnet. The requested lease is available, so it should be offered.
+ ctx.subnet_ = subnet1_;
+ ctx.requested_address_ = IOAddress("10.1.2.25");
+ lease2 = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("10.1.2.25", lease2->addr_.toText());
+
+ // The returning client (the one that has a lease) should also be able
+ // to renew its lease regardless of a subnet it begins with. So, it has
+ // an address assigned from subnet1, but we use subnet2 as a selected
+ // subnet.
+ AllocEngine::ClientContext4 ctx2(subnet2_, ClientIdPtr(), hwaddr2_,
+ IOAddress("0.0.0.0"), false, false,
+ "host.example.com.", true);
+ ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease2 = engine_.allocateLease4(ctx2);
+ // The existing lease should be returned.
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("192.0.2.17", lease2->addr_.toText());
+}
+
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkClassification) {
+
+ // Try to offer address from subnet1. There is one address available
+ // so it should be offered.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Apply restrictions on the subnet1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ subnet1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the subnet1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should offer an address from subnet2 that belongs
+ // to the same shared network.
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Create reservation for the client in subnet1. Because this subnet is
+ // not allowed for the client the client should still be offered a
+ // lease from subnet2.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet1_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.17")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+ AllocEngine::findReservation(ctx);
+
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the subnet1.
+ ctx.query_->addClass(ClientClass("cable-modem"));
+
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+}
+
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet requires another class.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkPoolClassification) {
+
+ // Try to offer address from subnet1. There is one address available
+ // so it should be offered.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Apply restrictions on the pool1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ pool1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the pool1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should offer an address from subnet2 that belongs
+ // to the same shared network.
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the pool1.
+ ctx.query_->addClass(ClientClass("cable-modem"));
+
+ // Restrict access to pool2 for this client, to make sure that the
+ // server doesn't accidentally get an address from this pool.
+ pool2_->allowClientClass("telephone");
+
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+}
+
+// Test that global reservations within shared network take precedence over the
+// existing leases regardless in which subnet belonging to a shared network
+// reservations belong.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkReservationsGlobal) {
+
+ EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery());
+
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress("10.1.2.105")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet1_->setReservationsGlobal(true);
+ subnet1_->setReservationsInSubnet(true);
+ subnet2_->setReservationsGlobal(true);
+ subnet2_->setReservationsInSubnet(true);
+
+ // Start allocation from subnet1. The engine should determine that the
+ // client has global reservations within subnet2 and should rather
+ // assign reserved addresses.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+
+ // Let's create a lease for the client to make sure the lease is not
+ // renewed but a reserved lease is offered.
+ Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(),
+ 501, time(NULL), subnet1_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2));
+ ctx.subnet_ = subnet1_;
+ ctx.hosts_.clear();
+ AllocEngine::findReservation(ctx);
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+}
+
+
+// Test that reservations within shared network take precedence over the
+// existing leases regardless in which subnet belonging to a shared network
+// reservations belong.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkReservations) {
+
+ EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery());
+
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet2_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("10.1.2.105")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Start allocation from subnet1. The engine should determine that the
+ // client has reservations in subnet2 and should rather assign reserved
+ // addresses.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+
+ // Let's create a lease for the client in subnet1 to make sure the lease
+ // is not renewed but a reserved lease is offered.
+ Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(),
+ 501, time(NULL), subnet1_->getID()));
+ lease2->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2));
+ ctx.subnet_ = subnet1_;
+ ctx.hosts_.clear();
+ AllocEngine::findReservation(ctx);
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+
+ // Let's create a lease for the client in subnet2 to make sure the lease
+ // is not renewed but a reserved lease is offered.
+ Lease4Ptr lease3(new Lease4(IOAddress("10.1.2.55"), hwaddr_, ClientIdPtr(),
+ 501, time(NULL), subnet2_->getID()));
+ lease3->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease3));
+ ctx.subnet_ = subnet1_;
+ ctx.hosts_.clear();
+ AllocEngine::findReservation(ctx);
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+}
+
+
+// Test that reservations within shared network take precedence over the
+// existing leases regardless in which subnet belonging to a shared network
+// reservations belong. Host lookups returning a collection are disabled.
+// As it is only an optimization the behavior (so the test) must stay
+// unchanged.
+TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkReservationsNoColl) {
+
+ // Disable host lookups returning a collection.
+ ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery());
+ HostMgr::instance().setDisableSingleQuery(true);
+
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet2_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("10.1.2.105")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet1_->setReservationsGlobal(true);
+ subnet1_->setReservationsInSubnet(true);
+ subnet2_->setReservationsGlobal(true);
+ subnet2_->setReservationsInSubnet(true);
+
+ // Start allocation from subnet1. The engine should determine that the
+ // client has reservations in subnet2 and should rather assign reserved
+ // addresses.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+
+ // Let's create a lease for the client to make sure the lease is not
+ // renewed but a reserved lease is offered.
+ Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(),
+ 501, time(NULL), subnet1_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2));
+ ctx.subnet_ = subnet1_;
+ ctx.hosts_.clear();
+ AllocEngine::findReservation(ctx);
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+}
+
+// This test verifies that the server can offer an address from a shared
+// subnet if there's at least 1 address left there, but will not offer
+// anything if both subnets are completely full.
+TEST_F(SharedNetworkAlloc4Test, runningOut) {
+
+ // Allocate everything in subnet1
+ insertLease("192.0.2.17", subnet1_->getID());
+
+ // Allocate everything, except one address in subnet2.
+ for (int i = 5; i < 100; i++) {
+ stringstream tmp;
+ tmp << "10.1.2." << i;
+ insertLease(tmp.str(), subnet2_->getID());
+ }
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ EXPECT_TRUE(lease);
+
+ // Now allocate the last address. Now both subnets are exhausted.
+ insertLease("10.1.2.100", subnet2_->getID());
+
+ // Ok, we're out. We should not get anything now.
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_FALSE(lease);
+
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail", subnet1_->getID()));
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail-shared-network", subnet1_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet1_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet1_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet1_->getID()));
+}
+
+// This test verifies that the server can offer an address from a
+// subnet and the introduction of shared network doesn't break anything here.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkSimple) {
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+
+ // The allocation engine should have assigned an address from the first
+ // subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+ // Make sure the lease is in the lease mgr.
+ ASSERT_TRUE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+}
+
+// This test verifies that the server can allocate an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetwork) {
+
+ // Create a lease for a single address in the first address pool. The
+ // pool is now exhausted.
+ Lease4Ptr lease = insertLease("192.0.2.17", subnet1_->getID());
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease2 = engine_.allocateLease4(ctx);
+ // The allocation engine should have assigned an address from the second
+ // subnet. We could guess that this is 10.1.2.5, being the first address
+ // in the address pool, but to make the test more generic, we merely
+ // verify that the address is in the given address pool.
+ ASSERT_TRUE(lease2);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease2->addr_));
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease2));
+
+ // The client should also be assigned a lease when it specifies a hint
+ // that doesn't match the subnet from which the lease is offered. The
+ // engine should check alternative subnets to match the hint to
+ // a subnet. The requested lease is available, so it should be offered.
+ ctx.subnet_ = subnet1_;
+ ctx.requested_address_ = IOAddress("10.1.2.25");
+ lease2 = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("10.1.2.25", lease2->addr_.toText());
+
+ // The returning client (the one that has a lease) should also be able
+ // to renew its lease regardless of a subnet it begins with. So, it has
+ // an address assigned from subnet1, but we use subnet2 as a selected
+ // subnet.
+ AllocEngine::ClientContext4 ctx2(subnet2_, ClientIdPtr(), hwaddr2_,
+ IOAddress("0.0.0.0"), false, false,
+ "host.example.com.", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease2 = engine_.allocateLease4(ctx2);
+ // The existing lease should be returned.
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("192.0.2.17", lease2->addr_.toText());
+}
+
+// This test verifies that the server can assign an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkClassification) {
+ // Try to offer address from subnet1. There is one address available
+ // so it should be offered.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Remove the lease so as we can start over.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Apply restrictions on the subnet1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ subnet1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the subnet1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should assign an address from subnet2 that belongs
+ // to the same shared network.
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Remove the lease so as we can start over.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the subnet1.
+ ctx.query_->addClass(ClientClass("cable-modem"));
+
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Let's now remove the client from the cable-modem class and try
+ // to renew the address. The engine should determine that the
+ // client doesn't have access to the subnet1 pools anymore and
+ // assign an address from unrestricted subnet.
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+}
+
+// This test verifies that the server can assign an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet requires another class.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkPoolClassification) {
+ // Try to offer address from subnet1. There is one address available
+ // so it should be offered.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Remove the lease so as we can start over.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Apply restrictions on the pool1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ pool1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the pool1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should assign an address from subnet2 that belongs
+ // to the same shared network.
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Remove the lease so as we can start over.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the pool1.
+ ctx.query_->addClass(ClientClass("cable-modem"));
+
+ // Restrict access to pool2 for this client, to make sure that the
+ // server doesn't accidentally get an address from this pool.
+ pool2_->allowClientClass("telephone");
+
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Let's now remove the client from the cable-modem class and try
+ // to renew the address. The engine should determine that the
+ // client doesn't have access to the pool1 anymore and
+ // assign an address from another pool.
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ ctx.query_->addClass(ClientClass("telephone"));
+ ctx.subnet_ = subnet1_;
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_));
+}
+
+// Test that reservations within shared network take precedence over the
+// existing leases regardless in which subnet belonging to a shared network
+// reservations belong (DHCPREQUEST case).
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkReservations) {
+
+ EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery());
+
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet2_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("10.1.2.105")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Start allocation from subnet1. The engine should determine that the
+ // client has reservations in subnet2 and should rather assign reserved
+ // addresses.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+
+ // Remove the lease for another test below.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Let's create a lease for the client to make sure the lease is not
+ // renewed but a reserved lease is allocated again.
+ Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(),
+ 501, time(NULL), subnet1_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2));
+ ctx.subnet_ = subnet1_;
+ ctx.hosts_.clear();
+ AllocEngine::findReservation(ctx);
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+}
+
+// Test that reservations within shared network take precedence over the
+// existing leases regardless in which subnet belonging to a shared network
+// reservations belong (DHCPREQUEST case). Host lookups returning a collection
+// are disabled. As it is only an optimization the behavior (so the test)
+// must stay unchanged.
+TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkReservationsNoColl) {
+
+ // Disable host lookups returning a collection.
+ ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery());
+ HostMgr::instance().setDisableSingleQuery(true);
+
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet2_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("10.1.2.105")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Start allocation from subnet1. The engine should determine that the
+ // client has reservations in subnet2 and should rather assign reserved
+ // addresses.
+ AllocEngine::ClientContext4
+ ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+
+ // Remove the lease for another test below.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Let's create a lease for the client to make sure the lease is not
+ // renewed but a reserved lease is allocated again.
+ Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(),
+ 501, time(NULL), subnet1_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2));
+ ctx.subnet_ = subnet1_;
+ ctx.hosts_.clear();
+ AllocEngine::findReservation(ctx);
+ lease = engine_.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.1.2.105", lease->addr_.toText());
+ EXPECT_EQ(lease->subnet_id_, subnet2_->getID());
+}
+
+// This test checks if an expired lease can be reused in DHCPDISCOVER (fake
+// allocation)
+TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Get rid of the default test configuration.
+ cfg_mgr.clear();
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2),
+ 495, now, subnet_->getID()));
+ // Copy the lease, so as it can be compared with the old lease returned
+ // by the allocation engine.
+ Lease4 original_lease(*lease);
+
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // CASE 1: Asking for any address
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx1);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // We are reusing expired lease, the old (expired) instance should be
+ // returned. The returned instance should be the same as the original
+ // lease.
+ ASSERT_TRUE(ctx1.old_lease_);
+ EXPECT_TRUE(original_lease == *ctx1.old_lease_);
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease4(lease);
+
+ // CASE 2: Asking specifically for this address
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress(addr), false, false,
+ "", true);
+ ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx2);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // We are updating expired lease. The copy of the old lease should be
+ // returned and it should be equal to the original lease.
+ ASSERT_TRUE(ctx2.old_lease_);
+ EXPECT_TRUE(*ctx2.old_lease_ == original_lease);
+}
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.105");
+
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2),
+ 495, now, subnet_->getID()));
+ // Make a copy of the lease, so as we can compare that with the old lease
+ // instance returned by the allocation engine.
+ Lease4 original_lease(*lease);
+
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress(addr), false, false,
+ "host.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx);
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // The allocation engine should return a copy of the old lease. This
+ // lease should be equal to the original lease.
+ ASSERT_TRUE(ctx.old_lease_);
+ EXPECT_TRUE(*ctx.old_lease_ == original_lease);
+
+ // Check that the stats declined stats were modified correctly. Note, because
+ // added the lease directly, assigned-leases never bumped to one, so when we
+ // reclaim it gets decremented to -1, then on assignment back to 0.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1, subnet_->getID()));
+}
+
+// This test checks if an expired declined lease can be reused when responding
+// to DHCPDISCOVER (fake allocation)
+TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4) {
+
+ AllocEnginePtr engine(new AllocEngine(0));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Now create a declined lease, decline it and rewind its cltt, so it
+ // is expired.
+ Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10);
+
+ // CASE 1: Ask for any address
+ Lease4Ptr assigned;
+ testReuseLease4(engine, declined, "0.0.0.0", true, SHOULD_PASS, assigned);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(assigned);
+ EXPECT_EQ(addr, assigned->addr_);
+
+ // CASE 2: Asking specifically for this address
+ testReuseLease4(engine, declined, "192.0.2.15", true, SHOULD_PASS, assigned);
+
+ // Check that we get it again
+ ASSERT_TRUE(assigned);
+ EXPECT_EQ(addr, assigned->addr_);
+}
+
+// This test checks if statistics are not updated when expired declined lease
+// is reused when responding to DHCPDISCOVER (fake allocation)
+TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4Stats) {
+
+ // Now prepare for DISCOVER processing
+ AllocEnginePtr engine(new AllocEngine(0));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ cfg_mgr.commit(); // so we will recalc stats
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ // Now create a declined lease, decline it and rewind its cltt, so it
+ // is expired.
+ Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10);
+
+ // Ask for any address. There's only one address in the pool, so it doesn't
+ // matter much.
+ Lease4Ptr assigned;
+ testReuseLease4(engine, declined, "0.0.0.0", true, SHOULD_PASS, assigned);
+
+ // Check that the stats declined stats were not modified
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
+}
+
+// This test checks if an expired declined lease can be reused when responding
+// to REQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseDeclinedLease4) {
+
+ AllocEnginePtr engine(new AllocEngine(0));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Now create a declined lease, decline it and rewind its cltt, so it
+ // is expired.
+ Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10);
+
+ // Asking specifically for this address
+ Lease4Ptr assigned;
+ testReuseLease4(engine, declined, "192.0.2.15", false, SHOULD_PASS, assigned);
+ // Check that we got it.
+ ASSERT_TRUE(assigned);
+ EXPECT_EQ(addr, assigned->addr_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(assigned, from_mgr);
+}
+
+// This test checks if statistics are not updated when expired declined lease
+// is reused when responding to DHCPREQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseDeclinedLease4Stats) {
+
+ AllocEnginePtr engine(new AllocEngine(0));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+ cfg_mgr.commit();
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ // Now create a declined lease, decline it and rewind its cltt, so it
+ // is expired.
+ Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10);
+
+ // Asking specifically for this address
+ Lease4Ptr assigned;
+ testReuseLease4(engine, declined, "192.0.2.15", false, SHOULD_PASS, assigned);
+ // Check that we got it.
+ ASSERT_TRUE(assigned);
+
+ // Check that the stats are correct. Note that assigned-addresses does
+ // not get decremented when a lease is declined, ergo not incremented
+ // when it is reused. Declined address stats will be -1 since
+ // lease was created as declined which does not increment the stat.
+ EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1, subnet_->getID()));
+}
+
+// This test checks that the Allocation Engine correctly identifies the
+// existing client's lease in the lease database, using the client
+// identifier and HW address.
+TEST_F(AllocEngine4Test, identifyClientLease) {
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, clientid_,
+ 100, time(NULL), subnet_->getID()));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "", true);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ Lease4Ptr identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText());
+
+ ctx.hwaddr_ = hwaddr2_;
+ ctx.clientid_ = clientid_;
+ identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText());
+
+ ctx.hwaddr_ = hwaddr_;
+ ctx.clientid_ = clientid2_;
+ identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_NE(identified_lease->addr_.toText(), "192.0.2.101");
+
+ ctx.hwaddr_ = hwaddr_;
+ ctx.clientid_.reset();
+ identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText());
+
+ ctx.hwaddr_ = hwaddr2_;
+ ctx.clientid_.reset();
+ identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_NE(identified_lease->addr_.toText(), "192.0.2.101");
+
+ lease->client_id_.reset();
+ ASSERT_NO_THROW(LeaseMgrFactory::instance().updateLease4(lease));
+
+ ctx.hwaddr_ = hwaddr_;
+ ctx.clientid_ = clientid_;
+ identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText());
+
+ ctx.hwaddr_ = hwaddr_;
+ ctx.clientid_.reset();
+ identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText());
+
+ ctx.hwaddr_ = hwaddr2_;
+ ctx.clientid_ = clientid_;
+ identified_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(identified_lease);
+ EXPECT_NE(identified_lease->addr_.toText(), "192.0.2.101");
+}
+
+// This test checks that when the client requests the address which belongs
+// to another client, the allocation engine returns NULL (for the
+// DHCPREQUEST case) or a lease for the address which belongs to this
+// client (DHCPDISCOVER case).
+TEST_F(AllocEngine4Test, requestOtherClientLease) {
+ // Create the first lease.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ // Create the second lease. Note that we use the same client id here and
+ // we expect that the allocation engine will figure out that the hardware
+ // address is different.
+ Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.102"), hwaddr2_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ // Add leases for both clients to the Lease Manager.
+ LeaseMgrFactory::instance().addLease(lease);
+ LeaseMgrFactory::instance().addLease(lease2);
+
+ AllocEngine engine(0);
+
+ // First client requests the lease which belongs to the second client.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.102"),
+ false, false, "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr new_lease = engine.allocateLease4(ctx);
+ // Allocation engine should return NULL.
+ ASSERT_FALSE(new_lease);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Now simulate the DHCPDISCOVER case when the provided address is
+ // treated as a hint. The engine should return a lease for a
+ // different address than requested.
+ ctx.fake_allocation_ = true;
+ new_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(new_lease);
+ EXPECT_EQ("192.0.2.101", new_lease->addr_.toText());
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPREQUEST without requested IP Address, nor ciaddr.
+// - Client is allocated a reserved address.
+//
+// Note that client must normally include a requested IP address or ciaddr
+// in its message. But, we still want to provision clients that don't do that.
+// The server simply picks reserved address or any other available one if there
+// is no reservation.
+TEST_F(AllocEngine4Test, reservedAddressNoHint) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ // Try to allocate a lease without specifying a hint. This is actually
+ // incorrect behavior of the client to not send an address it wants to
+ // obtain but the server should handle this gracefully.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // Make sure that the lease has been committed to the lease database.
+ // And that the committed lease is equal to the one returned.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(lease, from_mgr);
+
+ // Initially, there was no lease for this client, so the returned old
+ // lease should be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks behavior of the allocation engine in the following scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPDISCOVER without requested IP Address.
+// - Server returns DHCPOFFER with the reserved address.
+TEST_F(AllocEngine4Test, reservedAddressNoHintFakeAllocation) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+
+ ASSERT_TRUE(lease);
+ // The allocation engine should return a reserved address.
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // This is a "fake" allocation so the returned lease should not be committed
+ // to the lease database.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPREQUEST with a requested IP address
+// - Server returns DHCPNAK when requested IP address is different than
+// the reserved address. Note that the allocation engine returns NULL
+// to indicate to the server that it should send DHCPNAK.
+// - Server allocates a reserved address to the client when the client requests
+// this address using requested IP address option.
+TEST_F(AllocEngine4Test, reservedAddressHint) {
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.234"), false, false,
+ "", false);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr lease = engine.allocateLease4(ctx1);
+
+ // The client requested a different address than reserved, so
+ // the allocation engine should return NULL lease. When the server
+ // receives a NULL lease for the client, it will send a DHCPNAK.
+ ASSERT_FALSE(lease);
+ ASSERT_FALSE(ctx1.old_lease_);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Now, request a correct address. The client should obtain it.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.123"), false, false,
+ "", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx2);
+ lease = engine.allocateLease4(ctx2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // Make sure that the lease has been committed to the lease database.
+ // And that the committed lease is equal to the one returned.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(lease, from_mgr);
+
+ ASSERT_FALSE(ctx2.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a reservation.
+// - Client sends DHCPDISCOVER with a requested IP address as a hint.
+// - Server offers a reserved address, even though it is different than the
+// requested address.
+TEST_F(AllocEngine4Test, reservedAddressHintFakeAllocation) {
+ // Create a reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ // Query the allocation engine for the lease to be assigned to the client
+ // and specify a hint being a different address than the reserved one.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.234"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+
+ ASSERT_TRUE(lease);
+ // Allocation engine should return reserved address.
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // This is a "fake" allocation so the returned lease should not be committed
+ // to the lease database.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a lease for the address from the dynamic pool in the database.
+// - Client has a reservation for a different address than the one for which
+// the client has a lease.
+// - Client sends DHCPREQUEST, asking for the reserved address (as it has been
+// offered to it when it sent DHCPDISCOVER).
+// - Server allocates a reserved address and removes the lease for the address
+// previously allocated to the client.
+TEST_F(AllocEngine4Test, reservedAddressExistingLease) {
+ // Create the reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for the client with a different address than the reserved
+ // one.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Request allocation of the reserved address.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.123"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
+
+ ASSERT_TRUE(allocated_lease);
+ // The engine should have allocated the reserved address.
+ EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+ // Make sure that the lease has been committed to the lease database.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(allocated_lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(allocated_lease, from_mgr);
+
+ // The previous lease should have been replaced by a new one. The previous
+ // lease should be returned by the allocation engine to the caller.
+ ASSERT_TRUE(ctx.old_lease_);
+ EXPECT_EQ("192.0.2.101", ctx.old_lease_->addr_.toText());
+ detailCompareLease(ctx.old_lease_, lease);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client A has a lease in the database.
+// - Client B has a reservation for the address in use by client A.
+// - Client B sends a DHCPREQUEST requesting the allocation of the reserved
+// lease (in use by client A).
+// - Server determines that the reserved address is in use by a different client
+// and returns DHCPNAK to client B.
+TEST_F(AllocEngine4Test, reservedAddressHijacked) {
+ // Create host reservation for the client B.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Allocate a lease for the client A for the same address as reserved
+ // for the client B.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0,
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Try to allocate the reserved lease to client B.
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.123"), false, false,
+ "", false);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
+ // The lease is allocated to someone else, so the allocation should not
+ // succeed.
+ ASSERT_FALSE(allocated_lease);
+ ASSERT_FALSE(ctx1.old_lease_);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Make sure that the allocation engine didn't modify the lease of the
+ // client A.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(lease, from_mgr);
+
+ // Try doing the same thing, but this time do not request any specific
+ // address. It should have the same effect.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx2);
+ allocated_lease = engine.allocateLease4(ctx2);
+ ASSERT_FALSE(allocated_lease);
+ ASSERT_FALSE(ctx2.old_lease_);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client A has a lease in the database.
+// - Client B has a reservation for the address in use by client A.
+// - Client B sends a DHCPDISCOVER.
+// - Server determines that the reserved address is in use by a different client
+// so it offers an address from the dynamic pool.
+TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) {
+ // Create a reservation for the client B.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for the client A.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0,
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Query allocation engine for the lease to be allocated to the client B.
+ // The allocation engine is not able to allocate the lease to the client
+ // B, because the address is in use by client A.
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.123"), false, false,
+ "", true);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
+
+ // The allocation engine should return a lease but for a different address
+ // than requested because this address is in use.
+ ASSERT_TRUE(allocated_lease);
+ ASSERT_FALSE(ctx1.old_lease_);
+ EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.123");
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, allocated_lease->addr_));
+
+ // Do the same test. But, this time do not specify any address to be
+ // allocated.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx2);
+ allocated_lease = engine.allocateLease4(ctx2);
+
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.123");
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, allocated_lease->addr_));
+ ASSERT_FALSE(ctx2.old_lease_);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a reservation.
+// - Client has a lease in the database for a different address than reserved.
+// - Client sends a DHCPREQUEST and asks for a different address than reserved,
+// and different than it has in a database.
+// - Server doesn't allocate the reserved address to the client because the
+// client asked for the different address.
+//
+// Note that in this case the client should get the DHCPNAK and should fall back
+// to the DHCPDISCOVER.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseInvalidHint) {
+ // Create a reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for the client for a different address than reserved.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, ClientIdPtr(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Try to allocate a lease and specify a different address than reserved
+ // and different from the one that client is currently using.
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.102"), false, false,
+ "", false);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
+ ASSERT_FALSE(allocated_lease);
+ ASSERT_FALSE(ctx1.old_lease_);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Repeat the test, but this time ask for the address that the client
+ // has allocated.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.101"), false, false,
+ "", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx2);
+ allocated_lease = engine.allocateLease4(ctx2);
+ // The client has reservation so the server wants to allocate a
+ // reserved address and doesn't want to renew the address that the
+ // client is currently using. This is equivalent of the case when
+ // the client tries to renew the lease but there is a new reservation
+ // for this client. The server doesn't allow for the renewal and
+ // responds with DHCPNAK to force the client to return to the
+ // DHCP server discovery.
+ ASSERT_FALSE(allocated_lease);
+ ASSERT_FALSE(ctx2.old_lease_);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a lease in the database.
+// - Client has a reservation for a different address than the one for which it
+// has a lease.
+// - Client sends a DHCPDISCOVER and asks for a different address than reserved
+// and different from which it has a lease for.
+// - Server ignores the client's hint and offers a reserved address.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) {
+ // Create a reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for a different address than reserved.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Try to allocate a lease and use a completely different address
+ // as a hint.
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.102"), false, false,
+ "", true);
+ ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
+
+ // Server should offer a lease for a reserved address.
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+ // The lease should not be allocated until the client sends a DHCPREQUEST.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_));
+
+ // Old lease should contain the currently used lease.
+ ASSERT_TRUE(ctx1.old_lease_);
+ EXPECT_EQ("192.0.2.101", ctx1.old_lease_->addr_.toText());
+
+ // Repeat the test but this time ask for the address for which the
+ // client has a lease.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.101"), false, false,
+ "", true);
+ ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ AllocEngine::findReservation(ctx2);
+ allocated_lease = engine.allocateLease4(ctx2);
+
+ // The server should offer the lease, but not for the address that
+ // the client requested. The server should offer a reserved address.
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+ // Old lease should contain the currently used lease.
+ ASSERT_TRUE(ctx2.old_lease_);
+ EXPECT_EQ("192.0.2.101", ctx2.old_lease_->addr_.toText());
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a reservation.
+// - Client has a lease for a different address than reserved.
+// - Client sends a DHCPREQUEST to allocate a lease.
+// - The server determines that the client has a reservation for the
+// different address than it is currently using and should assign
+// a reserved address and remove the previous lease.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHint) {
+ // Create a reservation.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for a different address than reserved.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Try to allocate a lease with providing no hint.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
+
+ // The reserved address should be allocated.
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+ // The previous lease should be removed.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ // Make sure that the allocated lease is committed in the lease database.
+ Lease4Ptr from_mgr =
+ LeaseMgrFactory::instance().getLease4(allocated_lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(allocated_lease, from_mgr);
+
+ // Old lease should be returned.
+ ASSERT_TRUE(ctx.old_lease_);
+ detailCompareLease(lease, ctx.old_lease_);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client has a reservation.
+// - Client has a lease for a different address than reserved.
+// - Client sends a DHCPDISCOVER with no hint.
+// - Server determines that there is a reservation for the client and that
+// the reserved address should be offered when the client sends a
+// DHCPDISCOVER.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHintFakeAllocation) {
+ // Create a reservation.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for a different address than reserved.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Query the allocation engine for the lease to be allocated for the
+ // client.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
+
+ // The server should offer the reserved address.
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText());
+
+ // The lease should not be committed to the lease database until the
+ // client sends a DHCPREQUEST.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_));
+
+ // The old lease should reflect what is in the database.
+ ASSERT_TRUE(ctx.old_lease_);
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks that the behavior of the allocation engine in the following
+// scenario:
+// - Client A has a lease for the address.
+// - Client B has a reservation for the same address that the Client A is using.
+// - Client B requests allocation of the reserved address.
+// - Server returns DHCPNAK to the client to indicate that the requested address
+// can't be allocated.
+// - Client A renews the lease.
+// - Server determines that the lease that the Client A is trying to renew
+// is for the address reserved for Client B. Therefore, the server returns
+// DHCPNAK to force the client to return to the server discovery.
+// - The Client A sends DHCPDISCOVER.
+// - The server offers an address to the Client A, which is different than
+// the address reserved for Client B.
+// - The Client A requests allocation of the offered address.
+// - The server allocates the new address to Client A.
+// - The Client B sends DHCPDISCOVER to the server.
+// - The server offers a reserved address to the Client B.
+// - The Client B requests the offered address.
+// - The server allocates the reserved address to the Client B.
+TEST_F(AllocEngine4Test, reservedAddressConflictResolution) {
+ // Create a reservation for client B.
+ HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr2_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.101")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for Client A.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(0);
+
+ // Client B sends a DHCPREQUEST to allocate a reserved lease. The
+ // allocation engine can't allocate a reserved lease for this client
+ // because this specific address is in use by the Client A.
+ AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr2_,
+ IOAddress("192.0.2.101"), false, false,
+ "", false);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr offered_lease = engine.allocateLease4(ctx1);
+ ASSERT_FALSE(offered_lease);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Client A tries to renew the lease. The renewal should fail because
+ // server detects that Client A doesn't have reservation for this
+ // address.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.101"), false, false,
+ "", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx2);
+ ASSERT_FALSE(engine.allocateLease4(ctx2));
+ ASSERT_FALSE(ctx2.old_lease_);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Client A returns to DHCPDISCOVER and should be offered a lease.
+ // The offered lease address must be different than the one the
+ // Client B has reservation for.
+ AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.101"), false, false,
+ "", true);
+ ctx3.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx3);
+ offered_lease = engine.allocateLease4(ctx3);
+ ASSERT_TRUE(offered_lease);
+ EXPECT_NE(offered_lease->addr_.toText(), "192.0.2.101");
+
+ // Client A tries to acquire the lease. It should succeed. At this point
+ // the previous lease should be released and become available for the
+ // Client B.
+ AllocEngine::ClientContext4 ctx4(subnet_, clientid_, hwaddr_,
+ offered_lease->addr_, false, false,
+ "", false);
+ ctx4.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx4);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx4);
+
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.101");
+
+ // Client B tries to get the lease again. It should be offered
+ // a reserved lease.
+ AllocEngine::ClientContext4 ctx5(subnet_, ClientIdPtr(), hwaddr2_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx5.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx5);
+ offered_lease = engine.allocateLease4(ctx5);
+
+ ASSERT_TRUE(offered_lease);
+ EXPECT_EQ("192.0.2.101", offered_lease->addr_.toText());
+
+ // Client B requests allocation of the lease and it should succeed.
+ AllocEngine::ClientContext4 ctx6(subnet_, ClientIdPtr(), hwaddr2_,
+ offered_lease->addr_, false, false,
+ "", false);
+ ctx6.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ allocated_lease = engine.allocateLease4(ctx6);
+
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_EQ("192.0.2.101", allocated_lease->addr_.toText());
+}
+
+// This test checks that the address is not assigned from the dynamic
+// pool if it has been reserved for another client.
+TEST_F(AllocEngine4Test, reservedAddressVsDynamicPool) {
+ // Create a reservation for the client.
+ HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.100")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ // Different client tries to allocate a lease. Note, that we're using
+ // an iterative allocator which would pick the first address from the
+ // dynamic pool, i.e. 192.0.2.100. This address is reserved so we expect
+ // that a different address will be allocated.
+ AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
+
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.100");
+
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(allocated_lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ detailCompareLease(allocated_lease, from_mgr);
+}
+
+// This test checks that the client requesting an address which is
+// reserved for another client will get no lease or a different
+// address will be assigned if the client is sending a DHCPDISCOVER.
+TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) {
+ // Create a reservation for the client.
+ HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.100")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ // Different client is requesting this address.
+ AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("192.0.2.100"), false, false,
+ "", false);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
+
+ // The client should get no lease (DHCPNAK).
+ ASSERT_FALSE(allocated_lease);
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // The same client should get a different lease than requested if
+ // if is sending a DHCPDISCOVER (fake allocation is true).
+ AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("192.0.2.100"), false, false,
+ "", true);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx2);
+ allocated_lease = engine.allocateLease4(ctx2);
+
+ ASSERT_TRUE(allocated_lease);
+ // Make sure the lease obtained is for a different address.
+ EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.100");
+}
+
+// This test checks that the allocation engine refuses to allocate an
+// address when the pool is exhausted, and the only available
+// address is reserved for a different client.
+TEST_F(AllocEngine4Test, reservedAddressShortPool) {
+ AllocEngine engine(0);
+
+ // Create short pool with only one address.
+ initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100"));
+ // Reserve the address for a different client.
+ HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr2_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.100")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Allocation engine should determine that the available address is
+ // reserved for someone else and not allocate it.
+ AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx1);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx1);
+
+ ASSERT_FALSE(allocated_lease);
+
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network"));
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes"));
+
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID()));
+
+ // Now, let's remove the reservation.
+ initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100"));
+ CfgMgr::instance().commit();
+
+ // Address should be successfully allocated.
+ AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx2);
+ allocated_lease = engine.allocateLease4(ctx2);
+
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_EQ("192.0.2.100", allocated_lease->addr_.toText());
+}
+
+// This test checks that the AllocEngine allocates an address from the
+// dynamic pool if the client's reservation is made for a hostname but
+// not for an address.
+TEST_F(AllocEngine4Test, reservedHostname) {
+ AllocEngine engine(0);
+
+ // Create a reservation for a hostname. Address is set to 0 which
+ // indicates that there is no reservation.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(),
+ "foo.example.org"));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Try to allocate a lease.
+ AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("192.0.2.109"), false, false,
+ "foo.example.org", true);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(allocated_lease);
+ ASSERT_FALSE(allocated_lease->addr_.isV4Zero());
+ ASSERT_EQ("192.0.2.109", allocated_lease->addr_.toText());
+
+ ctx.requested_address_ = allocated_lease->addr_;
+ ctx.fake_allocation_ = false;
+ allocated_lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(allocated_lease);
+ EXPECT_EQ("192.0.2.109", allocated_lease->addr_.toText());
+}
+
+// This test checks that the AllocEngine::findReservation method finds
+// and returns host reservation for the DHCPv4 client using the data from
+// the client context. If the host reservation can't be found, it sets
+// the value of NULL in the host_ field of the client context.
+TEST_F(AllocEngine4Test, findReservation) {
+ // Create the instance of the allocation engine.
+ AllocEngine engine(0);
+
+ // Context is required to call the AllocEngine::findReservation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_);
+ ctx.addHostIdentifier(Host::IDENT_DUID, clientid_->getClientId());
+
+ // There is no reservation in the database so no host should be returned.
+ ASSERT_NO_THROW(engine.findReservation(ctx));
+ EXPECT_FALSE(ctx.currentHost());
+
+ // Create a reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.100")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // This time the reservation should be returned.
+ ctx.hosts_.clear();
+ ASSERT_NO_THROW(engine.findReservation(ctx));
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // It shouldn't be returned when reservations-in-subnet is disabled.
+ subnet_->setReservationsInSubnet(false);
+ ctx.hosts_.clear();
+ ASSERT_NO_THROW(engine.findReservation(ctx));
+ EXPECT_FALSE(ctx.currentHost());
+
+ // Check the reservations-in-subnet and reservations-out-of-pool flags.
+ subnet_->setReservationsInSubnet(true);
+ subnet_->setReservationsOutOfPool(true);
+ ctx.hosts_.clear();
+ ASSERT_NO_THROW(engine.findReservation(ctx));
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // This time use the client identifier to search for the host.
+ host.reset(new Host(&clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ Host::IDENT_DUID, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.101")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ ctx.hosts_.clear();
+ ASSERT_NO_THROW(engine.findReservation(ctx));
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // Remove the subnet. Subnet id is required to find host reservations, so
+ // if it is set to NULL, no reservation should be returned
+ ctx.subnet_.reset();
+ ctx.hosts_.clear();
+ ASSERT_NO_THROW(engine.findReservation(ctx));
+ EXPECT_FALSE(ctx.currentHost());
+
+ // The same if there is a mismatch of the subnet id between the reservation
+ // and the context.
+ ctx.subnet_ = subnet_;
+ host.reset(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID() + 1,
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.100")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ ctx.hosts_.clear();
+ ASSERT_NO_THROW(engine.findReservation(ctx));
+ EXPECT_FALSE(ctx.currentHost());
+}
+
+// This test checks if the simple IPv4 allocation can succeed and that
+// statistic for allocated addresses is increased appropriately.
+TEST_F(AllocEngine4Test, simpleAlloc4Stats) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Let's pretend 100 addresses were allocated already
+ string name = StatsMgr::generateName("subnet", subnet_->getID(),
+ "assigned-addresses");
+ StatsMgr::instance().addValue(name, static_cast<int64_t>(100));
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // The statistic should be there and it should be increased by 1 (to 101).
+ ObservationPtr stat = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(stat);
+ EXPECT_EQ(101, stat->getInteger().first);
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+}
+
+// This test checks if the fake allocation (for DHCPDISCOVER) can succeed
+// and that it doesn't increase allocated-addresses statistic.
+TEST_F(AllocEngine4Test, fakeAlloc4Stat) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, true,
+ "host.example.com.", true);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Let's pretend 100 addresses were allocated already
+ string name = StatsMgr::generateName("subnet", subnet_->getID(),
+ "assigned-addresses");
+ StatsMgr::instance().addValue(name, static_cast<int64_t>(100));
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // The statistic should be there and it should not be increased
+ // (should be still equal to 100).
+ ObservationPtr stat = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(stat);
+ EXPECT_EQ(100, stat->getInteger().first);
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+}
+
+// This test checks that the allocated-addresses statistic is decreased when
+// the client has a lease and a reservation for a different address is
+// available.
+TEST_F(AllocEngine4Test, reservedAddressExistingLeaseStat) {
+ // Create the reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ // Create a lease for the client with a different address than the reserved
+ // one.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_,
+ &clientid_->getClientId()[0],
+ clientid_->getClientId().size(),
+ 100, time(NULL), subnet_->getID(),
+ false, false, ""));
+ LeaseMgrFactory::instance().addLease(lease);
+
+ AllocEngine engine(100);
+
+ // Let's pretend 100 addresses were allocated already
+ string name = StatsMgr::generateName("subnet", subnet_->getID(),
+ "assigned-addresses");
+ StatsMgr::instance().addValue(name, static_cast<int64_t>(100));
+ int64_t cumulative = getStatistics("cumulative-assigned-addresses",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses");
+
+ // Request allocation of the reserved address.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.123"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+
+ Lease4Ptr allocated_lease = engine.allocateLease4(ctx);
+
+ ASSERT_TRUE(allocated_lease);
+
+ // The statistic should be still at 100. Note that it was decreased
+ // (because old lease was removed), but also increased (because the
+ // new lease was immediately allocated).
+ ObservationPtr stat = StatsMgr::instance().getObservation(name);
+ ASSERT_TRUE(stat);
+ EXPECT_EQ(100, stat->getInteger().first);
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative));
+
+ // Lets' double check that the actual allocation took place.
+ EXPECT_FALSE(ctx.fake_allocation_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation outside of the subnet.
+// - Client sends DISCOVER
+// - Client is allocated an address within the subnet.
+// - Lease is not added to the lease database
+TEST_F(AllocEngine4Test, globalReservationReservedNonMatchingAddressDiscover) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress("192.0.77.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate an address in the subnet
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+
+ // This is a "fake" allocation so the returned lease should not be committed
+ // to the lease database.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation matching the current subnet.
+// - Client sends DISCOVER
+// - Client is allocated the reserved address.
+// - Lease is not added to the lease database
+TEST_F(AllocEngine4Test, globalReservationReservedMatchingAddressDiscover) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.10")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate the reserved address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.10", lease->addr_.toText());
+
+ // This is a "fake" allocation so the returned lease should not be committed
+ // to the lease database.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation outside the current subnet.
+// - Client sends REQUEST
+// - Client is allocated a dynamic address.
+// - Lease is added to the lease database
+TEST_F(AllocEngine4Test, globalReservationReservedNonMatchingAddressRequest) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress("192.0.77.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate the reserved address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ ASSERT_NE("192.0.77.123", lease->addr_.toText());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation matching the current subnet.
+// - Client sends REQUEST
+// - Client is allocated the reserved address.
+// - Lease is added to the lease database
+TEST_F(AllocEngine4Test, globalReservationReservedMatchingAddressRequest) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.10")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate the reserved address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.10", lease->addr_.toText());
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation.
+// - Client sends DISCOVER
+// - Client is allocated a dynamic address from matched subnet
+// - Lease is not added to the lease database
+TEST_F(AllocEngine4Test, globalReservationDynamicDiscover) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(),
+ "foo.example.org"));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate a dynamic address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.100", lease->addr_.toText());
+
+ // This is a "fake" allocation so the returned lease should not be committed
+ // to the lease database.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global reservation.
+// - Client sends REQUEST
+// - Client is allocated a dynamic address from matched subnet
+// - Lease is added to the lease database
+TEST_F(AllocEngine4Test, globalReservationDynamicRequest) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(),
+ "foo.example.org"));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate a dynamic address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.100", lease->addr_.toText());
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a subnet reservation.
+// - Client sends DISCOVER
+// - Client is allocated the reserved address.
+// - Lease is not added to the lease database
+TEST_F(AllocEngine4Test, mixedReservationReservedAddressDiscover) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123"),
+ "foo.example.org"));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate the reserved address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // This is a "fake" allocation so the returned lease should not be committed
+ // to the lease database.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a subnet reservation.
+// - Client sends REQUEST
+// - Client is allocated the reserved address.
+// - Lease is added to the lease database
+TEST_F(AllocEngine4Test, mixedReservationReservedAddressRequest) {
+ // Create reservation for the client.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123"),
+ "foo.example.org"));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate the reserved address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global and a subnet reservation.
+// - Client sends DISCOVER
+// - Client is allocated the reserved address.
+// - Lease is not added to the lease database
+TEST_F(AllocEngine4Test, bothReservationReservedAddressDiscover) {
+ // Create reservations for the client.
+ HostPtr ghost(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress("192.0.77.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123"),
+ "foo.example.org"));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate the reserved address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // This is a "fake" allocation so the returned lease should not be committed
+ // to the lease database.
+ EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_));
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has a global and a subnet reservation.
+// - Client sends REQUEST
+// - Client is allocated the reserved address.
+// - Lease is added to the lease database
+TEST_F(AllocEngine4Test, bothReservationReservedAddressRequest) {
+ // Create reservations for the client.
+ HostPtr ghost(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_GLOBAL,
+ SUBNET_ID_UNUSED, IOAddress("192.0.77.123")));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.123"),
+ "foo.example.org"));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(0);
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Query allocation engine for the lease to be assigned to this
+ // client without specifying the address to be assigned.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Look up the host.
+ AllocEngine::findReservation(ctx);
+
+ // We should have the correct current host
+ EXPECT_TRUE(ctx.currentHost());
+ EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname());
+ EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation());
+
+ // We should allocate the reserved address.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.123", lease->addr_.toText());
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Client had no lease in the database, so the old lease returned should
+ // be NULL.
+ ASSERT_FALSE(ctx.old_lease_);
+}
+
+// Exercises AllocEnginer4Test::updateExtendedInfo4() through various
+// permutations of client packet content.
+TEST_F(AllocEngine4Test, updateExtendedInfo4) {
+
+ // Structure that defines a test scenario.
+ struct Scenario {
+ std::string description_; // test description
+ std::string orig_context_json_; // user context the lease begins with
+ std::string rai_data_; // RAI option the client packet contains
+ std::string exp_context_json_; // expected user context on the lease
+ bool exp_ret; // expected returned value
+ };
+
+ // Test scenarios.
+ std::vector<Scenario> scenarios {
+ {
+ "no context, no rai",
+ "",
+ "",
+ "",
+ false
+ },
+ {
+ "some original context, no rai",
+ "{\"foo\": 123}",
+ "",
+ "{\"foo\": 123}",
+ false
+ },
+ {
+ "no original context, rai",
+ "",
+ "0x52060104aabbccdd",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ true
+ },
+ {
+ "no original context, rai, remote and relay ids",
+ "",
+ "0x520a02030102030c03aabbcc",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\","
+ " \"relay-id\": \"AABBCC\" } } }",
+ true
+ },
+ {
+ "some original context, rai",
+ "{\"foo\": 123, \"ISC\":{\"bar\": 456}}",
+ "0x52060104aabbccdd",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" }, \"bar\": 456 }, \"foo\": 123 }",
+ true
+ },
+ {
+ "bad original context, rai",
+ "[\"foo\"]",
+ "0x52060104aabbccdd",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ true
+ },
+ {
+ "some original context with bad isc entry, rai",
+ "{\"foo\": 123, \"ISC\":[\"bar\"]}",
+ "0x52060104aabbccdd",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } }, \"foo\": 123 }",
+ true
+ },
+ {
+ "some original context, rai, remote and relay ids",
+ "{\"foo\": 123, \"ISC\":{ \"bar\": 456}}",
+ "0x520a02030102030c03aabbcc",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\","
+ " \"relay-id\": \"AABBCC\" }, \"bar\": 456 }, \"foo\": 123 }",
+ true
+ },
+ {
+ "original rai context, no rai",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ "",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ false
+ },
+ {
+ "original rai context, different rai",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ "0x52060104ddeeffaa",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104DDEEFFAA\" } } }",
+ true
+ },
+ {
+ "original rai context, different rai, remote and relay ids",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ "0x520a02030102030c03aabbcc",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\","
+ " \"relay-id\": \"AABBCC\" } } }",
+ true
+ }};
+
+ // Create the allocation engine, context and lease.
+ NakedAllocEngine engine(0);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "", true);
+
+ // All scenarios require storage to be enabled.
+ ctx.subnet_->setStoreExtendedInfo(true);
+
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("192.0.2.100", lease->addr_.toText());
+
+ // Verify that the lease begins with no user context.
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_FALSE(user_context);
+
+ // Iterate over the test scenarios.
+ ElementPtr orig_context;
+ ElementPtr exp_context;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ // Create the original user context from JSON.
+ if (scenario.orig_context_json_.empty()) {
+ orig_context.reset();
+ } else {
+ ASSERT_NO_THROW(orig_context = Element::fromJSON(scenario.orig_context_json_))
+ << "invalid orig_context_json_, test is broken";
+ }
+
+ // Create the expected user context from JSON.
+ if (scenario.exp_context_json_.empty()) {
+ exp_context.reset();
+ } else {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_))
+ << "invalid exp_context_json_, test is broken";
+ }
+
+ // Initialize lease's user context.
+ lease->setContext(orig_context);
+ if (!orig_context) {
+ ASSERT_FALSE(lease->getContext());
+ } else {
+ ASSERT_TRUE(lease->getContext());
+ ASSERT_TRUE(orig_context->equals(*(lease->getContext())));
+ }
+
+ // Create the client packet and the add RAI option (if one).
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ if (!scenario.rai_data_.empty()) {
+ std::vector<uint8_t> opt_data;
+ ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data))
+ << "scenario.rai_data_ is invalid, test is broken";
+ OptionDefinitionPtr rai_def =
+ LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_AGENT_OPTIONS);
+ ASSERT_TRUE(rai_def) << "could not get RAI definition, test is broken";
+ ASSERT_GT(opt_data.size(), 2);
+ ASSERT_EQ(DHO_DHCP_AGENT_OPTIONS, opt_data[0]);
+ ASSERT_EQ(opt_data[1] + 2, opt_data.size());
+ std::vector<uint8_t> rai_data(opt_data.cbegin() + 2,
+ opt_data.cend());
+ OptionCustomPtr rai;
+ ASSERT_NO_THROW(rai.reset(new OptionCustom(*rai_def, Option::V4,
+ rai_data)))
+ << "could not create rai option, test is broken";
+ ctx.query_->addOption(rai);
+ }
+
+ // Call AllocEngine::updateLease4ExtendeInfo().
+ bool ret = false;
+ ASSERT_NO_THROW_LOG(ret = engine.callUpdateLease4ExtendedInfo(lease, ctx));
+ ASSERT_EQ(scenario.exp_ret, ret);
+
+ // Verify the lease has the expected user context content.
+ if (!exp_context) {
+ ASSERT_FALSE(lease->getContext());
+ } else {
+ ASSERT_TRUE(lease->getContext());
+ ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
+ << "expected: " << *(exp_context) << std::endl
+ << " actual: " << *(lease->getContext()) << std::endl;
+ }
+ }
+}
+
+// Verifies that the extended data (e.g. RAI option for now) is
+// added to a V4 lease when leases are created and/or renewed,
+// when store-extended-info is true.
+TEST_F(AllocEngine4Test, storeExtendedInfoEnabled4) {
+
+ // Structure that defines a test scenario.
+ struct Scenario {
+ std::string description_; // test description
+ std::vector<uint8_t> mac_; // MAC address
+ std::string rai_data_; // RAI option the client packet contains
+ std::string exp_context_json_; // expected user context on the lease
+ std::string exp_address_; // expected lease address
+ };
+
+ std::vector<uint8_t> mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01 };
+ std::string mac1_addr = "192.0.2.100";
+
+ std::vector<uint8_t> mac2 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x02 };
+ std::string mac2_addr = "192.0.2.101";
+
+ // Test scenarios.
+ std::vector<Scenario> scenarios {
+ {
+ "create client one without rai",
+ mac1,
+ "",
+ "",
+ mac1_addr
+ },
+ {
+ "renew client one without rai",
+ {},
+ "",
+ "",
+ mac1_addr
+ },
+ {
+ "create client two with rai",
+ mac2,
+ "0x52050104a1b1c1d1",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x52050104A1B1C1D1\" } } }",
+ mac2_addr
+ },
+ {
+ "renew client two without rai",
+ {},
+ "",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x52050104A1B1C1D1\" } } }",
+ mac2_addr
+ }};
+
+ // Create the allocation engine, context and lease.
+ NakedAllocEngine engine(0);
+
+ // All of the scenarios require storage to be enabled.
+ subnet_->setStoreExtendedInfo(true);
+
+ AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "", false);
+
+ // Iterate over the test scenarios.
+ Lease4Ptr lease;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ ElementPtr exp_context;
+ // Create the expected user context from JSON.
+ if (!scenario.exp_context_json_.empty()) {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_))
+ << "invalid exp_context_json_, test is broken";
+ }
+
+ // If we have a MAC address this scenario is for a new client.
+ if (!scenario.mac_.empty()) {
+ std::cout << "setting mac address" << std::endl;
+ ASSERT_NO_THROW(ctx.hwaddr_.reset(new HWAddr(scenario.mac_, HTYPE_ETHER)))
+ << "invalid MAC address, test is broken";
+ }
+
+ // Create the client packet and the add RAI option (if one).
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ if (!scenario.rai_data_.empty()) {
+ std::vector<uint8_t> opt_data;
+ ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data))
+ << "scenario.rai_data_ is invalid, test is broken";
+ OptionPtr rai;
+ ASSERT_NO_THROW(rai.reset(new Option(Option::V4, 0x52, opt_data)))
+ << "could not create rai option, test is broken";
+
+ ctx.query_->addOption(rai);
+ }
+
+ // Create or renew the lease.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(scenario.exp_address_, lease->addr_.toText());
+
+ // Verify the lease has the expected user context content.
+ if (!exp_context) {
+ ASSERT_FALSE(lease->getContext());
+ } else {
+ ASSERT_TRUE(lease->getContext());
+ ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
+ << "expected: " << *(exp_context) << std::endl
+ << " actual: " << *(lease->getContext()) << std::endl;
+ }
+ }
+}
+
+// Verifies that the extended data (e.g. RAI option for now) is
+// not added to a V4 lease when leases are created and/or renewed,
+// when store-extended-info is false.
+TEST_F(AllocEngine4Test, storeExtendedInfoDisabled4) {
+
+ // Structure that defines a test scenario.
+ struct Scenario {
+ std::string description_; // test description
+ std::vector<uint8_t> mac_; // MAC address
+ std::string rai_data_; // RAI option the client packet contains
+ std::string exp_address_; // expected lease address
+ };
+
+ std::vector<uint8_t> mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01 };
+ std::string mac1_addr = "192.0.2.100";
+
+ std::vector<uint8_t> mac2 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x02 };
+ std::string mac2_addr = "192.0.2.101";
+
+ // Test scenarios.
+ std::vector<Scenario> scenarios {
+ {
+ "create client one without rai",
+ mac1,
+ "",
+ mac1_addr
+ },
+ {
+ "renew client one without rai",
+ {},
+ "",
+ mac1_addr
+ },
+ {
+ "create client two with rai",
+ mac2,
+ "0x52050104a1b1c1d1",
+ mac2_addr
+ },
+ {
+ "renew client two with rai",
+ {},
+ "0x52050104a1b1c1d1",
+ mac2_addr
+ }};
+
+ // Create the allocation engine, context and lease.
+ NakedAllocEngine engine(0);
+
+ // All of the scenarios require storage to be disabled.
+ subnet_->setStoreExtendedInfo(false);
+
+ AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ false, false, "", false);
+
+ Lease4Ptr lease;
+
+ // Iterate over the test scenarios.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ // If we have a MAC address this scenario is for a new client.
+ if (!scenario.mac_.empty()) {
+ std::cout << "setting mac address" << std::endl;
+ ASSERT_NO_THROW(ctx.hwaddr_.reset(new HWAddr(scenario.mac_, HTYPE_ETHER)))
+ << "invalid MAC address, test is broken";
+ }
+
+ // Create the client packet and the add RAI option (if one).
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ if (!scenario.rai_data_.empty()) {
+ std::vector<uint8_t> opt_data;
+ ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data))
+ << "scenario.rai_data_ is invalid, test is broken";
+ OptionPtr rai;
+ ASSERT_NO_THROW(rai.reset(new Option(Option::V4, 0x52, opt_data)))
+ << "could not create rai option, test is broken";
+
+ ctx.query_->addOption(rai);
+ }
+
+ // Create or renew the lease.
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(scenario.exp_address_, lease->addr_.toText());
+
+ // Verify the lease does not have user context content.
+ ASSERT_FALSE(lease->getContext());
+ }
+}
+
+// This test checks if a lease can be reused in DHCPDISCOVER (fake allocation)
+// using cache threshold.
+TEST_F(AllocEngine4Test, discoverCacheThreshold4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for fake allocation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "", true);
+
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(valid - age, lease->reuseable_valid_lft_);
+
+ // Check other lease parameters.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+}
+
+// This test checks if a lease can be reused in DHCPREQUEST (real allocation)
+// using cache threshold.
+TEST_F(AllocEngine4Test, requestCacheThreshold4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease4Ptr original_lease(new Lease4(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for real allocation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "", false);
+
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(valid - age, lease->reuseable_valid_lft_);
+
+ // Check other lease parameters.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the lease was not updated in the database.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+/// We proved that there is no different from the "cache" feature between
+/// discovers and request at the exception of the lease database update.
+
+// This test checks if a lease can be reused in DHCPDISCOVER (fake allocation)
+// using cache max age.
+TEST_F(AllocEngine4Test, discoverCacheMaxAge4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the max age to 200.
+ subnet_->setCacheMaxAge(200);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for fake allocation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "", true);
+
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(valid - age, lease->reuseable_valid_lft_);
+
+ // Check other lease parameters.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+}
+
+// This test checks if a lease can be reused in DHCPREQUEST (real allocation)
+// using both cache threshold and max age.
+TEST_F(AllocEngine4Test, requestCacheBoth4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ // Set the max age to 200.
+ subnet_->setCacheMaxAge(200);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease4Ptr original_lease(new Lease4(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for real allocation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "", false);
+
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(valid - age, lease->reuseable_valid_lft_);
+
+ // Check other lease parameters.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the lease was not updated in the database.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// This test checks if a lease can't be reused in DHCPDISCOVER (fake allocation)
+// using too small cache threshold.
+TEST_F(AllocEngine4Test, discoverCacheBadThreshold4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the threshold to 10%.
+ subnet_->setCacheThreshold(.10);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for fake allocation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "", true);
+
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+}
+
+// This test checks if a lease can't be reused in DHCPREQUEST (real allocation)
+// using too small cache max age.
+TEST_F(AllocEngine4Test, requestCacheBadMaxAge4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ // Set the max age to 50.
+ subnet_->setCacheMaxAge(50);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+
+ // Create a context for real allocation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "", false);
+
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if a lease can't be reused in DHCPDISCOVER (fake allocation)
+// when the valid lifetime was reduced.
+TEST_F(AllocEngine4Test, discoverCacheReducedValid4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 200.
+ subnet_->setValid(200);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ uint32_t valid = 500; // Used a value greater than subnet_->getValid().
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for fake allocation.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "", true);
+
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+}
+
+// This test checks if a lease can't be reused in DHCPREQUEST (real allocation)
+// when DDNS parameter changed.
+TEST_F(AllocEngine4Test, requestCacheFwdDDNS4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the max age to 200.
+ subnet_->setCacheMaxAge(200);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+
+ // Create a context for real allocation with fwd_dns_update changed.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ true, false, "", false);
+
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if a lease can't be reused in DHCPDISCOVER (fake allocation)
+// when DDNS parameter changed.
+TEST_F(AllocEngine4Test, discoverCacheRevDDNS4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ // Set the max age to 200.
+ subnet_->setCacheMaxAge(200);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID()));
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for fake allocation with rev_dns_update changed.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, true, "", true);
+
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+}
+
+// This test checks if a lease can't be reused in DHCPREQUEST (real allocation)
+// when hostname changed.
+TEST_F(AllocEngine4Test, requestCacheHostname4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 500.
+ uint32_t valid = 500;
+ subnet_->setValid(valid);
+
+ // Set the max age to 200.
+ subnet_->setCacheMaxAge(200);
+
+ IOAddress addr("192.0.2.105");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
+ valid, now, subnet_->getID(),
+ false, false, "foo"));
+ ASSERT_FALSE(lease->expired());
+
+ // Create a context for real allocation with fwd_dns_update changed.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr,
+ false, false, "bar", false);
+
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ lease = engine->allocateLease4(ctx);
+ // Check that we got that single lease.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+ EXPECT_EQ("bar", lease->hostname_);
+
+ // Check the lease was updated in the database.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Verifies that AllocEngine::getValidLft(ctx4) returns the appropriate
+// lifetime value based on the context content.
+TEST_F(AllocEngine4Test, getValidLft4) {
+ AllocEngine engine(0);
+
+ // Let's make three classes, two with valid-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+
+ ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
+ Triplet<uint32_t> valid_one(50, 100, 150);
+ class_def->setValid(valid_one);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("valid_two", ExpressionPtr()));
+ Triplet<uint32_t>valid_two(200, 250, 300);
+ class_def->setValid(valid_two);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("valid_unspec", ExpressionPtr()));
+ dictionary->addClass(class_def);
+
+ // Commit our class changes.
+ CfgMgr::instance().commit();
+
+ // Update the subnet's triplet to something more useful.
+ subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
+
+ // Describes a test scenario.
+ struct Scenario {
+ std::string desc_; // descriptive text for logging
+ std::vector<std::string> classes_; // class list of assigned classes
+ uint32_t requested_lft_; // use as option 51 is > 0
+ uint32_t exp_valid_; // expected lifetime
+ };
+
+ // Scenarios to test.
+ std::vector<Scenario> scenarios = {
+ {
+ "BOOTP",
+ { "BOOTP" },
+ 0,
+ Lease::INFINITY_LFT
+ },
+ {
+ "no classes, no option",
+ {},
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "no classes, option",
+ {},
+ subnet_->getValid().getMin() + 50,
+ subnet_->getValid().getMin() + 50
+ },
+ {
+ "no classes, option too small",
+ {},
+ subnet_->getValid().getMin() - 50,
+ subnet_->getValid().getMin()
+ },
+ {
+ "no classes, option too big",
+ {},
+ subnet_->getValid().getMax() + 50,
+ subnet_->getValid().getMax()
+ },
+ {
+ "class unspecified, no option",
+ { "valid_unspec" },
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "from last class, no option",
+ { "valid_unspec", "valid_one" },
+ 0,
+ valid_one.get()
+ },
+ {
+ "from first class, no option",
+ { "valid_two", "valid_one" },
+ 0,
+ valid_two.get()
+ },
+ {
+ "class plus option",
+ { "valid_one" },
+ valid_one.getMin() + 25,
+ valid_one.getMin() + 25
+ },
+ {
+ "class plus option too small",
+ { "valid_one" },
+ valid_one.getMin() - 25,
+ valid_one.getMin()
+ },
+ {
+ "class plus option too big",
+ { "valid_one" },
+ valid_one.getMax() + 25,
+ valid_one.getMax()
+ }
+ };
+
+ // Iterate over the scenarios and verify the correct outcome.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ // Create a context;
+ AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ ctx.query_->addClass(class_name);
+ }
+
+ // Add client option (if one)
+ if (scenario.requested_lft_) {
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME,
+ scenario.requested_lft_));
+ ctx.query_->addOption(opt);
+ }
+
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
+ }
+ }
+}
+
+// Verifies that AllocEngine::getValidLft(ctx4) returns the appropriate
+// lifetime value based on the context content.
+TEST_F(AllocEngine4Test, getTemplateClassValidLft4) {
+ AllocEngine engine(0);
+
+ // Let's make three classes, two with valid-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ExpressionPtr match_expr;
+ ExpressionParser parser;
+
+ ElementPtr test_cfg = Element::create("'valid_one_value'");
+ parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr));
+ Triplet<uint32_t> valid_one(50, 100, 150);
+ class_def->setValid(valid_one);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_two_value'");
+ parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_two", match_expr));
+ Triplet<uint32_t>valid_two(200, 250, 300);
+ class_def->setValid(valid_two);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_unspec_value'");
+ parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr));
+ dictionary->addClass(class_def);
+
+ // Commit our class changes.
+ CfgMgr::instance().commit();
+
+ // Update the subnet's triplet to something more useful.
+ subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
+
+ // Describes a test scenario.
+ struct Scenario {
+ std::string desc_; // descriptive text for logging
+ std::vector<std::string> classes_; // class list of assigned classes
+ uint32_t requested_lft_; // use as option 51 is > 0
+ uint32_t exp_valid_; // expected lifetime
+ };
+
+ // Scenarios to test.
+ std::vector<Scenario> scenarios = {
+ {
+ "BOOTP",
+ { "BOOTP" },
+ 0,
+ Lease::INFINITY_LFT
+ },
+ {
+ "no classes, no option",
+ {},
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "no classes, option",
+ {},
+ subnet_->getValid().getMin() + 50,
+ subnet_->getValid().getMin() + 50
+ },
+ {
+ "no classes, option too small",
+ {},
+ subnet_->getValid().getMin() - 50,
+ subnet_->getValid().getMin()
+ },
+ {
+ "no classes, option too big",
+ {},
+ subnet_->getValid().getMax() + 50,
+ subnet_->getValid().getMax()
+ },
+ {
+ "class unspecified, no option",
+ { "valid_unspec" },
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "from last class, no option",
+ { "valid_unspec", "valid_one" },
+ 0,
+ valid_one.get()
+ },
+ {
+ "from first class, no option",
+ { "valid_two", "valid_one" },
+ 0,
+ valid_two.get()
+ },
+ {
+ "class plus option",
+ { "valid_one" },
+ valid_one.getMin() + 25,
+ valid_one.getMin() + 25
+ },
+ {
+ "class plus option too small",
+ { "valid_one" },
+ valid_one.getMin() - 25,
+ valid_one.getMin()
+ },
+ {
+ "class plus option too big",
+ { "valid_one" },
+ valid_one.getMax() + 25,
+ valid_one.getMax()
+ }
+ };
+
+ // Iterate over the scenarios and verify the correct outcome.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ // Create a context;
+ AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", false);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ if (class_name == "BOOTP") {
+ ctx.query_->addClass(class_name);
+ } else {
+ string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX);
+ subclass += class_name;
+ subclass += "_value";
+ ctx.query_->addSubClass(class_name, subclass);
+ }
+ }
+
+ // Add client option (if one)
+ if (scenario.requested_lft_) {
+ OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME,
+ scenario.requested_lft_));
+ ctx.query_->addOption(opt);
+ }
+
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
+ }
+ }
+}
+
+// This test checks that deleteRelease handles BOOTP leases.
+TEST_F(AllocEngine4Test, bootpDelete) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now delete it.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(lease));
+ EXPECT_TRUE(deleted);
+ from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ EXPECT_FALSE(from_mgr);
+}
+
+// This test verifies that all addresses in a pool can be allocated.
+TEST_F(AllocEngine4Test, fullPool) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a larger subnet with a /24 pool.
+ subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to false.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(false);
+ cfg_mgr.commit();
+
+ size_t cnt = 0;
+ vector<bool> found(256, false);
+ while (true) {
+ vector<uint8_t> duid = clientid_->getClientId();
+ duid[6] = cnt >> 8;
+ duid[7] = cnt & 0xff;
+ ClientIdPtr clientid(new ClientId(duid));
+ HWAddrPtr hwaddr(new HWAddr(*hwaddr_));
+ hwaddr->hwaddr_[4] = cnt >> 8;
+ hwaddr->hwaddr_[5] = cnt & 0xff;
+ AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr,
+ IOAddress("0.0.0.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ if (!lease) {
+ break;
+ }
+ const vector<uint8_t>& addr = lease->addr_.toBytes();
+ ASSERT_EQ(4, addr.size());
+ EXPECT_EQ(10, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(1, addr[2]);
+ EXPECT_FALSE(found[addr[3]]);
+ found[addr[3]] = true;
+ ++cnt;
+ // Catch unbound loop.
+ ASSERT_LT(cnt, 1000);
+ }
+ EXPECT_EQ(256, cnt);
+ EXPECT_EQ(found, vector<bool>(256, true));
+}
+
+// This test verifies that all addresses in a subnet can be allocated.
+TEST_F(AllocEngine4Test, fullSubnet24) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a larger subnet with a /24 pool.
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to false.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(false);
+ cfg_mgr.commit();
+
+ size_t cnt = 0;
+ vector<bool> found(256, false);
+ while (true) {
+ vector<uint8_t> duid = clientid_->getClientId();
+ duid[6] = cnt >> 8;
+ duid[7] = cnt & 0xff;
+ ClientIdPtr clientid(new ClientId(duid));
+ HWAddrPtr hwaddr(new HWAddr(*hwaddr_));
+ hwaddr->hwaddr_[4] = cnt >> 8;
+ hwaddr->hwaddr_[5] = cnt & 0xff;
+ AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr,
+ IOAddress("0.0.0.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ if (!lease) {
+ break;
+ }
+ const vector<uint8_t>& addr = lease->addr_.toBytes();
+ ASSERT_EQ(4, addr.size());
+ EXPECT_EQ(192, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(2, addr[2]);
+ EXPECT_FALSE(found[addr[3]]);
+ found[addr[3]] = true;
+ ++cnt;
+ // Catch unbound loop.
+ ASSERT_LT(cnt, 1000);
+ }
+ EXPECT_EQ(256, cnt);
+ EXPECT_EQ(found, vector<bool>(256, true));
+}
+
+// This test verifies that not all addresses in a pool can be allocated when
+// exclude-first-last is true.
+TEST_F(AllocEngine4Test, excludeFirstLast) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a larger subnet with a /24 pool.
+ subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to true.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true);
+ cfg_mgr.commit();
+
+ size_t cnt = 0;
+ vector<bool> found(256, false);
+ while (true) {
+ vector<uint8_t> duid = clientid_->getClientId();
+ duid[6] = cnt >> 8;
+ duid[7] = cnt & 0xff;
+ ClientIdPtr clientid(new ClientId(duid));
+ HWAddrPtr hwaddr(new HWAddr(*hwaddr_));
+ hwaddr->hwaddr_[4] = cnt >> 8;
+ hwaddr->hwaddr_[5] = cnt & 0xff;
+ AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr,
+ IOAddress("0.0.0.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ if (!lease) {
+ break;
+ }
+ const vector<uint8_t>& addr = lease->addr_.toBytes();
+ ASSERT_EQ(4, addr.size());
+ EXPECT_EQ(10, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(1, addr[2]);
+ EXPECT_FALSE(found[addr[3]]);
+ found[addr[3]] = true;
+ ++cnt;
+ // Catch unbound loop.
+ ASSERT_LT(cnt, 1000);
+ }
+ EXPECT_EQ(254, cnt);
+ vector<bool> expected(256, true);
+ expected[0] = false;
+ expected[255] = false;
+ EXPECT_EQ(expected, found);
+}
+
+// This test verifies that not all addresses in a subnet can be allocated when
+// exclude-first-last is true.
+TEST_F(AllocEngine4Test, excludeFirstLast24) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a larger subnet with a /24 pool.
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to true.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true);
+ cfg_mgr.commit();
+
+ size_t cnt = 0;
+ vector<bool> found(256, false);
+ while (true) {
+ vector<uint8_t> duid = clientid_->getClientId();
+ duid[6] = cnt >> 8;
+ duid[7] = cnt & 0xff;
+ ClientIdPtr clientid(new ClientId(duid));
+ HWAddrPtr hwaddr(new HWAddr(*hwaddr_));
+ hwaddr->hwaddr_[4] = cnt >> 8;
+ hwaddr->hwaddr_[5] = cnt & 0xff;
+ AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr,
+ IOAddress("0.0.0.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ if (!lease) {
+ break;
+ }
+ const vector<uint8_t>& addr = lease->addr_.toBytes();
+ ASSERT_EQ(4, addr.size());
+ EXPECT_EQ(192, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(2, addr[2]);
+ EXPECT_FALSE(found[addr[3]]);
+ found[addr[3]] = true;
+ ++cnt;
+ // Catch unbound loop.
+ ASSERT_LT(cnt, 1000);
+ }
+ EXPECT_EQ(254, cnt);
+ vector<bool> expected(256, true);
+ expected[0] = false;
+ expected[255] = false;
+ EXPECT_EQ(expected, found);
+}
+
+// This test verifies that all addresses in a subnet can be allocated when
+// exclude-first-last is true but the prefix length is greater than 24.
+TEST_F(AllocEngine4Test, excludeFirst25) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a smaller subnet with a /25 pool.
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 25, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.127")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to true.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true);
+ cfg_mgr.commit();
+
+ size_t cnt = 0;
+ vector<bool> found(128, false);
+ while (true) {
+ vector<uint8_t> duid = clientid_->getClientId();
+ duid[6] = cnt >> 8;
+ duid[7] = cnt & 0xff;
+ ClientIdPtr clientid(new ClientId(duid));
+ HWAddrPtr hwaddr(new HWAddr(*hwaddr_));
+ hwaddr->hwaddr_[4] = cnt >> 8;
+ hwaddr->hwaddr_[5] = cnt & 0xff;
+ AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr,
+ IOAddress("0.0.0.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ if (!lease) {
+ break;
+ }
+ const vector<uint8_t>& addr = lease->addr_.toBytes();
+ ASSERT_EQ(4, addr.size());
+ EXPECT_EQ(192, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(2, addr[2]);
+ ASSERT_GT(128, addr[3]);
+ EXPECT_FALSE(found[addr[3]]);
+ found[addr[3]] = true;
+ ++cnt;
+ // Catch unbound loop.
+ ASSERT_LT(cnt, 1000);
+ }
+ EXPECT_EQ(128, cnt);
+ EXPECT_EQ(found, vector<bool>(128, true));
+}
+
+// This test verifies that all addresses in a subnet can be allocated when
+// exclude-first-last is true but the prefix length is greater than 24.
+TEST_F(AllocEngine4Test, excludeLast25) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a smaller subnet with a /25 pool.
+ subnet_ = Subnet4::create(IOAddress("192.0.2.128"), 25, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.128"),
+ IOAddress("192.0.2.255")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to true.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true);
+ cfg_mgr.commit();
+
+ size_t cnt = 0;
+ vector<bool> found(128, false);
+ while (true) {
+ vector<uint8_t> duid = clientid_->getClientId();
+ duid[6] = cnt >> 8;
+ duid[7] = cnt & 0xff;
+ ClientIdPtr clientid(new ClientId(duid));
+ HWAddrPtr hwaddr(new HWAddr(*hwaddr_));
+ hwaddr->hwaddr_[4] = cnt >> 8;
+ hwaddr->hwaddr_[5] = cnt & 0xff;
+ AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr,
+ IOAddress("0.0.0.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ if (!lease) {
+ break;
+ }
+ const vector<uint8_t>& addr = lease->addr_.toBytes();
+ ASSERT_EQ(4, addr.size());
+ EXPECT_EQ(192, addr[0]);
+ EXPECT_EQ(0, addr[1]);
+ EXPECT_EQ(2, addr[2]);
+ ASSERT_LE(128, addr[3]);
+ EXPECT_FALSE(found[addr[3] - 128]);
+ found[addr[3] - 128] = true;
+ ++cnt;
+ // Catch unbound loop.
+ ASSERT_LT(cnt, 1000);
+ }
+ EXPECT_EQ(128, cnt);
+ EXPECT_EQ(found, vector<bool>(128, true));
+}
+
+// This test verifies that an excluded address can be allocated when requested
+// and exclude-first-last is true.
+TEST_F(AllocEngine4Test, excludeFirstLastRequested) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a larger subnet with a /24 pool.
+ subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to true.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true);
+ cfg_mgr.commit();
+
+ // Request the first address.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("10.0.1.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.0.1.0", lease->addr_.toText());
+
+ // Request the last address.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid2_, hwaddr2_,
+ IOAddress("10.0.1.255"),
+ false, false, "bar.foo", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 2345));
+ lease = engine->allocateLease4(ctx2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.0.1.255", lease->addr_.toText());
+}
+
+// This test verifies that an excluded address can be allocated when reserved
+// and exclude-first-last is true.
+TEST_F(AllocEngine4Test, excludeFirstLastReserver) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ // Get rid of the default subnet configuration.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear();
+
+ // Configure a larger subnet with a /24 pool.
+ subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255")));
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Set exclude-first-last to true.
+ cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true);
+
+ // Add reservations.
+ HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("10.0.1.0")));
+ cfg_mgr.getStagingCfg()->getCfgHosts()->add(host);
+ HostPtr host2(new Host(&hwaddr2_->hwaddr_[0], hwaddr2_->hwaddr_.size(),
+ Host::IDENT_HWADDR, subnet_->getID(),
+ SUBNET_ID_UNUSED, IOAddress("10.0.1.255")));
+ cfg_mgr.getStagingCfg()->getCfgHosts()->add(host2);
+ subnet_->setReservationsInSubnet(true);
+ subnet_->setReservationsOutOfPool(false);
+ cfg_mgr.commit();
+
+ // Request the first address.
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "foo.bar", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ AllocEngine::findReservation(ctx);
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.0.1.0", lease->addr_.toText());
+
+ // Request the last address.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid2_, hwaddr2_,
+ IOAddress("0.0.0.0"),
+ false, false, "bar.foo", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 2345));
+ AllocEngine::findReservation(ctx2);
+ lease = engine->allocateLease4(ctx2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("10.0.1.255", lease->addr_.toText());
+}
+
+#ifdef HAVE_MYSQL
+/// @brief Extension of the fixture class to use the MySQL backend.
+class MySqlAllocEngine4Test : public AllocEngine4Test {
+public:
+ /// @brief Constructor.
+ MySqlAllocEngine4Test() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createMySQLSchema();
+ factory_.create(db::test::validMySQLConnectionString());
+ }
+
+ /// @brief Destructor.
+ ~MySqlAllocEngine4Test() {
+ // If data wipe enabled, delete transient data otherwise destroy
+ // the schema.
+ db::test::destroyMySQLSchema();
+ LeaseMgrFactory::destroy();
+ }
+};
+
+// This test checks that simple allocation handles BOOTP queries.
+TEST_F(MySqlAllocEngine4Test, bootpAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ // The MySQL database does not keep the hwtype for DHCPv4 leases.
+ from_mgr->hwaddr_->htype_ = HTYPE_FDDI;
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks simple renewal handles BOOTP queries.
+TEST_F(MySqlAllocEngine4Test, bootpRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease.
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease.
+ ASSERT_TRUE(lease2);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease2->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease2->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease2->addr_));
+ ASSERT_TRUE(lease2->client_id_);
+ EXPECT_TRUE(*lease2->client_id_ == *clientid_);
+ ASSERT_TRUE(lease2->hwaddr_);
+ EXPECT_TRUE(*lease2->hwaddr_ == *hwaddr_);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Check the renewed valid lifetime has the max value.
+ EXPECT_EQ(infinity_lft, lease2->valid_lft_);
+}
+
+// This test checks that deleteRelease handles BOOTP leases.
+TEST_F(MySqlAllocEngine4Test, bootpDelete) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now delete it.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(lease));
+ EXPECT_TRUE(deleted);
+ from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ EXPECT_FALSE(from_mgr);
+}
+#endif
+
+#ifdef HAVE_PGSQL
+/// @brief Extension of the fixture class to use the PostgreSql backend.
+class PgSqlAllocEngine4Test : public AllocEngine4Test {
+public:
+ /// @brief Constructor.
+ PgSqlAllocEngine4Test() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createPgSQLSchema();
+ factory_.create(db::test::validPgSQLConnectionString());
+ }
+
+ /// @brief Destructor.
+ ~PgSqlAllocEngine4Test() {
+ // If data wipe enabled, delete transient data otherwise destroy
+ // the schema.
+ db::test::destroyPgSQLSchema();
+ LeaseMgrFactory::destroy();
+ }
+};
+
+// This test checks that simple allocation handles BOOTP queries.
+TEST_F(PgSqlAllocEngine4Test, bootpAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ // The PostgreSql database does not keep the hwtype for DHCPv4 leases.
+ from_mgr->hwaddr_->htype_ = HTYPE_FDDI;
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks simple renewal handles BOOTP queries.
+TEST_F(PgSqlAllocEngine4Test, bootpRenew4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease.
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Do it again, this should amount to the renew of an existing lease
+ Lease4Ptr lease2 = engine->allocateLease4(ctx);
+
+ // Check that we got a lease.
+ ASSERT_TRUE(lease2);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease2->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease2->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease2->addr_));
+ ASSERT_TRUE(lease2->client_id_);
+ EXPECT_TRUE(*lease2->client_id_ == *clientid_);
+ ASSERT_TRUE(lease2->hwaddr_);
+ EXPECT_TRUE(*lease2->hwaddr_ == *hwaddr_);
+
+ // Lease already existed, so old_lease should be set.
+ EXPECT_TRUE(ctx.old_lease_);
+
+ // Check the renewed valid lifetime has the max value.
+ EXPECT_EQ(infinity_lft, lease2->valid_lft_);
+}
+
+// This test checks that deleteRelease handles BOOTP leases.
+TEST_F(PgSqlAllocEngine4Test, bootpDelete) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ subnet_->setValid(Triplet<uint32_t>(1, 3, 5));
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ // Make the query a BOOTP one.
+ ctx.query_->addClass("BOOTP");
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+ // The new lease has been allocated, so the old lease should not exist.
+ ASSERT_FALSE(ctx.old_lease_);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Check that is belongs to the right subnet and client.
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+
+ // Check the valid lifetime is infinite.
+ uint32_t infinity_lft = Lease::INFINITY_LFT;
+ EXPECT_EQ(infinity_lft, lease->valid_lft_);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now delete it.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(lease));
+ EXPECT_TRUE(deleted);
+ from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ EXPECT_FALSE(from_mgr);
+}
+#endif
+
+// Verifies that offer_lft is non-zero, that an offered lease is stored
+// in the lease database.
+TEST_F(AllocEngine4Test, discoverOfferLft) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Get rid of the default test configuration.
+ cfg_mgr.clear();
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+
+ // Set subnet's offer-lifetime to a non-zero, positive value.
+ uint32_t offer_lft = (subnet_->getValid() / 3);
+ subnet_->setOfferLft(offer_lft);
+ ASSERT_EQ(offer_lft, subnet_->getOfferLft().get());
+
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Ask for any address
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), true, true,
+ "one", true);
+ ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Check that we got that single lease
+ Lease4Ptr lease = engine->allocateLease4(ctx1);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(offer_lft, lease->valid_lft_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+
+ // Check that the lease has been stored by LeaseMgr.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(offer_lft, from_mgr->valid_lft_);
+ EXPECT_FALSE(from_mgr->fqdn_fwd_);
+ EXPECT_FALSE(from_mgr->fqdn_rev_);
+
+ // Try to discover an address for a second client.
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+
+ // Ask for any address.
+ AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr2,
+ IOAddress("0.0.0.0"), true, true,
+ "two", true);
+ ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1235));
+
+ // Verify that we did not get a lease.
+ lease = engine->allocateLease4(ctx2);
+ ASSERT_FALSE(lease);
+
+ // Original client now does a DHCPREQUEST. Make sure we get the
+ // previously offered lease with the proper valid lifetime.
+ AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), true, true,
+ "one", false);
+ ctx3.query_.reset(new Pkt4(DHCPREQUEST, 1236));
+
+ lease = engine->allocateLease4(ctx3);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(subnet_->getValid(), from_mgr->valid_lft_);
+}
+
+// Verifies that when offer_lft is non-zero, that an existing lease is
+// whose remaining life is larger than offer_lft, is offered as is.
+TEST_F(AllocEngine4Test, discoverOfferLftUseExistingLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Get rid of the default test configuration.
+ cfg_mgr.clear();
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 300, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+
+ // Set subnet's offer-lifetime to a non-zero, positive value.
+ uint32_t offer_lft = 100;
+ subnet_->setOfferLft(offer_lft);
+ ASSERT_EQ(offer_lft, subnet_->getOfferLft().get());
+
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ time_t now = time(NULL) - 5; // Allocated 5 seconds ago
+ Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, 300, now,
+ subnet_->getID(), true, true, "somehost"));
+
+ // Copy the lease, so as it can be compared with the old lease returned
+ // by the allocation engine.
+ Lease4 original_lease(*lease);
+
+ // Lease was assigned 5 seconds ago, its valid lifetime is 300, its
+ // remaining lifetime is still larger than offer_lft.
+ ASSERT_FALSE(lease->expired());
+ ASSERT_EQ(300, lease->valid_lft_);
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // CASE 1: Asking for any address
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx1);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(300, lease->valid_lft_);
+
+ // We are reusing the existing lease, the old instance should be
+ // returned. The returned instance should be the same as the original
+ // lease.
+ ASSERT_TRUE(ctx1.old_lease_);
+ EXPECT_TRUE(original_lease == *ctx1.old_lease_);
+
+ // Check that the lease has not been modified by LeaseMgr.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_TRUE(original_lease == *from_mgr);
+}
+
+// Verifies that when offer_lft is non-zero, that an expired lease can
+// be reclaimed an offered correctly.
+TEST_F(AllocEngine4Test, discoverOfferLftReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(0)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Get rid of the default test configuration.
+ cfg_mgr.clear();
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+
+ // Set subnet's offer-lifetime to a non-zero, positive value.
+ uint32_t offer_lft = (subnet_->getValid() / 3);
+ subnet_->setOfferLft(offer_lft);
+ ASSERT_EQ(offer_lft, subnet_->getOfferLft().get());
+
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe };
+ HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER));
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2),
+ 495, now, subnet_->getID()));
+ // Copy the lease, so as it can be compared with the old lease returned
+ // by the allocation engine.
+ Lease4 original_lease(*lease);
+
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // CASE 1: Asking for any address
+ AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ lease = engine->allocateLease4(ctx1);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(offer_lft, lease->valid_lft_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+
+ // We are reusing expired lease, the old (expired) instance should be
+ // returned. The returned instance should be the same as the original
+ // lease.
+ ASSERT_TRUE(ctx1.old_lease_);
+ EXPECT_TRUE(original_lease == *ctx1.old_lease_);
+
+ // Check that the lease has been stored by LeaseMgr.
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(offer_lft, from_mgr->valid_lft_);
+ EXPECT_FALSE(from_mgr->fqdn_fwd_);
+ EXPECT_FALSE(from_mgr->fqdn_rev_);
+
+ // Client now does a DHCPREQUEST. Make sure we get the
+ // previously offered lease with the proper valid lifetime.
+ AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), true, true,
+ "one", false);
+ ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1235));
+
+ lease = engine->allocateLease4(ctx2);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(subnet_->getValid(), from_mgr->valid_lft_);
+}
+
+// Verifies that AllocEngine::getOfferLft(ctx4) returns the appropriate
+// lifetime value based on the context content.
+TEST_F(AllocEngine4Test, getOfferLft4) {
+ AllocEngine engine(0);
+
+ // Let's make three classes, two with offer-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+
+ ClientClassDefPtr class_def(new ClientClassDef("offer_lft_one", ExpressionPtr()));
+ Optional<uint32_t> offer_lft_one(100);
+ class_def->setOfferLft(offer_lft_one);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("offer_lft_two", ExpressionPtr()));
+ Optional<uint32_t>offer_lft_two(200);
+ class_def->setOfferLft(offer_lft_two);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("offer_lft_zero", ExpressionPtr()));
+ Optional<uint32_t>offer_lft_zero(0);
+ class_def->setOfferLft(offer_lft_zero);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("offer_lft_unspec", ExpressionPtr()));
+ dictionary->addClass(class_def);
+
+ // Commit our class changes.
+ CfgMgr::instance().commit();
+
+ // Update the subnet's triplet to something more useful.
+ subnet_->setOfferLft(Optional<uint32_t>(300));
+
+ // Describes a test scenario.
+ struct Scenario {
+ std::string desc_; // descriptive text for logging
+ std::vector<std::string> classes_; // class list of assigned classes
+ uint32_t exp_valid_lft_; // expected lease lifetime
+ bool exp_allocate_; // true if lease should be allocated
+ };
+
+ bool exp_allocate = true;
+
+ // Scenarios to test.
+ std::vector<Scenario> scenarios = {
+ {
+ "BOOTP",
+ { "BOOTP" },
+ Lease::INFINITY_LFT,
+ !exp_allocate,
+ },
+ {
+ "no classes",
+ {},
+ subnet_->getOfferLft(),
+ exp_allocate
+ },
+ {
+ "class unspecified",
+ { "offer_lft_unspec" },
+ subnet_->getOfferLft(),
+ exp_allocate
+ },
+ {
+ "from last class",
+ { "offer_lft_unspec", "offer_lft_one" },
+ offer_lft_one.get(),
+ exp_allocate
+ },
+ {
+ "from first class",
+ { "offer_lft_two", "offer_lft_one" },
+ offer_lft_two.get(),
+ exp_allocate
+ },
+ {
+ // Using class value of zero should override non-zero set at
+ // subnet level, lease should have actual valid lft
+ "zero from class",
+ { "offer_lft_zero" },
+ subnet_->getValid(),
+ !exp_allocate
+ }
+ };
+
+ // Iterate over the scenarios and verify the correct outcome.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ // Create a context;
+ AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"), false, false,
+ "", true);
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ ctx.query_->addClass(class_name);
+ }
+
+ Lease4Ptr lease = engine.allocateLease4(ctx);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_lft_);
+
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ if (!scenario.exp_allocate_) {
+ ASSERT_FALSE(from_mgr);
+ } else {
+ ASSERT_TRUE(from_mgr);
+ EXPECT_EQ(from_mgr->valid_lft_, scenario.exp_valid_lft_);
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(from_mgr));
+ }
+ }
+ }
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
new file mode 100644
index 0000000..68a0fd4
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
@@ -0,0 +1,5967 @@
+// Copyright (C) 2015-2023 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 <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <dhcpsrv/allocator.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <eval/eval_context.h>
+#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <boost/pointer_cast.hpp>
+
+using namespace std;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+using namespace isc::stats;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// Convenience values to improve readibility.
+const bool IN_SUBNET = true;
+const bool IN_POOL = true;
+
+// Test convenience method adding hints to IA context.
+TEST(ClientContext6Test, addHint) {
+ AllocEngine::ClientContext6 ctx;
+ ctx.currentIA().addHint(IOAddress("2001:db8:1::1"));
+ ctx.currentIA().addHint(IOAddress("3000:1::"), 64);
+ ctx.currentIA().addHint(IOAddress("3001:2::"), 64, 100, 200);
+
+ ASSERT_EQ(3, ctx.currentIA().hints_.size());
+ EXPECT_EQ("2001:db8:1::1", ctx.currentIA().hints_[0].getAddress().toText());
+ EXPECT_EQ("3000:1::", ctx.currentIA().hints_[1].getAddress().toText());
+ EXPECT_EQ("3001:2::", ctx.currentIA().hints_[2].getAddress().toText());
+ EXPECT_EQ(100, ctx.currentIA().hints_[2].getPreferred());
+ EXPECT_EQ(200, ctx.currentIA().hints_[2].getValid());
+}
+
+// Test convenience method adding allocated prefixes and addresses to
+// a context.
+TEST(ClientContext6Test, addAllocatedResource) {
+ AllocEngine::ClientContext6 ctx;
+ ctx.addAllocatedResource(IOAddress("2001:db8:1::1"));
+ ctx.addAllocatedResource(IOAddress("3000:1::"), 64);
+
+ ASSERT_EQ(2, ctx.allocated_resources_.size());
+ EXPECT_TRUE(ctx.isAllocated(IOAddress("2001:db8:1::1")));
+ EXPECT_TRUE(ctx.isAllocated(IOAddress("3000:1::"), 64));
+}
+
+// This test checks if the v6 Allocation Engine can be instantiated, parses
+// parameters string and allocators are created.
+TEST_F(AllocEngine6Test, constructor) {
+ boost::scoped_ptr<AllocEngine> x;
+
+ ASSERT_NO_THROW(x.reset(new AllocEngine(100)));
+}
+
+// This test checks if two simple IPv6 allocations succeed and that the
+// statistics is properly updated. Prior to the second allocation it
+// resets the pointer to the last allocated address within the address
+// pool. This causes the engine to walk over the already allocated
+// address and then pick the first available address for the second
+// allocation. Because the allocation engine checks the callouts next
+// step status after each attempt to allocate an address, this test
+// also sets this status to non-default value prior to the second
+// allocation attempt, to make sure that this unexpected status will
+// not interfere with the allocation.
+TEST_F(AllocEngine6Test, simpleAlloc6) {
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ simpleAlloc6Test(pool_, IOAddress("::"), false);
+
+ // We should have bumped the assigned counter by 1
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // Reset last allocated address to check that the other client will
+ // be refused the already allocated address and will get the one
+ // available.
+ auto allocation_state = boost::dynamic_pointer_cast<PoolIterativeAllocationState>(pool_->getAllocationState());
+ allocation_state->resetLastAllocated();
+
+ // Simulate another client. This client should be assigned a different
+ // address.
+ DuidPtr duid(new DUID(std::vector<uint8_t>(8, 0x84)));
+ simpleAlloc6Test(pool_, duid, IOAddress("::"), false);
+
+ // We should have bumped the assigned counter by 2
+ EXPECT_TRUE(testStatistics("assigned-nas", 2, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// This test checks that simple allocation uses default lifetimes.
+TEST_F(AllocEngine6Test, defaultAlloc6) {
+ simpleAlloc6Test(pool_, IOAddress("::"), 0, 0, 300, 400);
+}
+
+// This test checks that simple allocation uses specified lifetimes.
+TEST_F(AllocEngine6Test, hintAlloc6) {
+ simpleAlloc6Test(pd_pool_, IOAddress("::"), 301, 399, 301, 399);
+}
+
+// This test checks that simple allocation uses min lifetimes.
+TEST_F(AllocEngine6Test, minAlloc6) {
+ simpleAlloc6Test(pool_, IOAddress("::"), 100, 200, 200, 300);
+}
+
+// This test checks that simple allocation uses max lifetimes.
+TEST_F(AllocEngine6Test, maxAlloc6) {
+ simpleAlloc6Test(pd_pool_, IOAddress("::"), 500, 600, 400, 500);
+}
+
+// This test checks if the simple PD allocation (REQUEST) can succeed
+// and the stats counter is properly bumped by 1
+TEST_F(AllocEngine6Test, pdSimpleAlloc6) {
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned prefixes.
+ int64_t cumulative = getStatistics("cumulative-assigned-pds",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-pds");
+
+ simpleAlloc6Test(pd_pool_, IOAddress("::"), false);
+
+ // We should have bumped the assigned counter by 1
+ EXPECT_TRUE(testStatistics("assigned-pds", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-pds",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-pds", glbl_cumulative));
+}
+
+// This test checks if the fake allocation (for SOLICIT) can succeed
+// and the stats counter isn't bumped
+TEST_F(AllocEngine6Test, fakeAlloc6) {
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ simpleAlloc6Test(pool_, IOAddress("::"), true);
+
+ // We should not have bumped the assigned counter.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// This test checks if the fake PD allocation (for SOLICIT) can succeed
+// and the stats counter isn't bumped
+TEST_F(AllocEngine6Test, pdFakeAlloc6) {
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned prefixes.
+ int64_t cumulative = getStatistics("cumulative-assigned-pds",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-pds");
+
+ simpleAlloc6Test(pd_pool_, IOAddress("::"), true);
+
+ // We should not have bumped the assigned counter
+ EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-pds",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-pds", glbl_cumulative));
+}
+
+// This test checks if the allocation with a hint that is valid (in range,
+// in pool and free) can succeed
+TEST_F(AllocEngine6Test, allocWithValidHint6) {
+
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::15"),
+ false);
+ ASSERT_TRUE(lease);
+
+ // We should get what we asked for
+ EXPECT_EQ("2001:db8:1::15", lease->addr_.toText());
+}
+
+// This test checks if the address allocation with a hint that is in range,
+// in pool, but is currently used, can succeed
+TEST_F(AllocEngine6Test, allocWithUsedHint6) {
+ allocWithUsedHintTest(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1f"), // allocate this as used
+ IOAddress("2001:db8:1::1f"), // request this addr
+ 128);
+}
+
+// This test checks if the PD allocation with a hint that is in range,
+// in pool, but is currently used, can succeed
+TEST_F(AllocEngine6Test, pdAllocWithUsedHint6) {
+ allocWithUsedHintTest(Lease::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), // allocate this prefix as used
+ IOAddress("2001:db8:1:2::"), // request this prefix
+ 80);
+}
+
+// This test checks if the allocation with a hint that is out the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngine6Test, allocBogusHint6) {
+
+ allocBogusHint6(Lease::TYPE_NA, IOAddress("3000::abc"), 128);
+}
+
+// This test checks if the allocation with a hint that is out the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngine6Test, pdAllocBogusHint6) {
+
+ allocBogusHint6(Lease::TYPE_PD, IOAddress("3000::abc"), 80);
+}
+
+// This test checks that NULL values are handled properly
+TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Allocations without subnet are not allowed
+ Lease6Ptr lease;
+ AllocEngine::ClientContext6 ctx1(Subnet6Ptr(), duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx1.currentIA().iaid_ = iaid_;
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));
+ ASSERT_FALSE(lease);
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
+
+ // Allocations without DUID are not allowed either
+ AllocEngine::ClientContext6 ctx2(subnet_, DuidPtr(), false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+ ASSERT_FALSE(lease);
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
+}
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine6Test, smallPool6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+
+ // Create a subnet with a pool that has one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+
+ // Initialize FQDN for a lease.
+ initFqdn("myhost.example.com", true, true);
+
+ Lease6Ptr lease;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, fqdn_fwd_, fqdn_rev_,
+ hostname_, false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx.currentIA().iaid_ = iaid_;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("2001:db8:1::ad", lease->addr_.toText());
+
+ // Do all checks on the lease
+ checkLease6(duid_, lease, Lease::TYPE_NA, 128);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // This is a new lease allocation. The old lease corresponding to a newly
+ // allocated lease should be NULL.
+ ASSERT_TRUE(ctx.currentIA().old_leases_.empty());
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine6Test, outOfAddresses6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // There is just a single address in the pool and allocated it to someone
+ // else, so the allocation should fail
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease2;
+ EXPECT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_FALSE(lease2);
+
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
+}
+
+// This test checks if an expired lease can be reused in SOLICIT (fake allocation)
+TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+
+ // Create one subnet with a pool holding one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+
+ // Initialize FQDN data for the lease.
+ initFqdn("myhost.example.com", true, true);
+
+ // Verify the all of relevant stats are zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // CASE 1: Asking for any address
+ AllocEngine::ClientContext6 ctx1(subnet_, duid_, fqdn_fwd_, fqdn_rev_, hostname_,
+ true, Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ ctx1.currentIA().iaid_ = iaid_;
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease6(duid_, lease, Lease::TYPE_NA, 128);
+
+ // CASE 2: Asking specifically for this address
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Verify the none of relevant stats were altered.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+}
+
+// This test checks if an expired lease can be reused using default lifetimes.
+TEST_F(AllocEngine6Test, defaultReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+
+ // Create one subnet with a pool holding one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // Initialize FQDN data for the lease.
+ initFqdn("myhost.example.com", true, true);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // Asking specifically for this address with zero lifetimes
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().addHint(addr, 128, 0, 0);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check lifetimes: defaults are expected.
+ EXPECT_EQ(300, lease->preferred_lft_);
+ EXPECT_EQ(400, lease->valid_lft_);
+}
+
+// This test checks if an expired lease can be reused using specified lifetimes.
+TEST_F(AllocEngine6Test, hintReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+
+ // Create one subnet with a pool holding one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // Initialize FQDN data for the lease.
+ initFqdn("myhost.example.com", true, true);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // Asking specifically for this address with zero lifetimes
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().addHint(addr, 128, 299, 401);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check lifetimes: specified values are expected.
+ EXPECT_EQ(299, lease->preferred_lft_);
+ EXPECT_EQ(401, lease->valid_lft_);
+}
+
+// This test checks if an expired lease can be reused using min lifetimes.
+TEST_F(AllocEngine6Test, minReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+
+ // Create one subnet with a pool holding one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // Initialize FQDN data for the lease.
+ initFqdn("myhost.example.com", true, true);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // Asking specifically for this address with zero lifetimes
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().addHint(addr, 128, 100, 200);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check lifetimes: min values are expected.
+ EXPECT_EQ(200, lease->preferred_lft_);
+ EXPECT_EQ(300, lease->valid_lft_);
+}
+
+// This test checks if an expired lease can be reused using max lifetimes.
+TEST_F(AllocEngine6Test, maxReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+
+ // Create one subnet with a pool holding one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // Initialize FQDN data for the lease.
+ initFqdn("myhost.example.com", true, true);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // Asking specifically for this address with zero lifetimes
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().addHint(addr, 128, 500, 600);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check lifetimes: max values are expected.
+ EXPECT_EQ(400, lease->preferred_lft_);
+ EXPECT_EQ(500, lease->valid_lft_);
+}
+
+// This test checks if an expired lease can be reused using class lifetimes.
+// Verifies getLifetimes() is invoked by v6 resuseExpiredLease().
+TEST_F(AllocEngine6Test, classReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+
+ // Let's make a classes with valid-lifetime and add it to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+
+ ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
+ Triplet<uint32_t> valid_one(600, 700, 800);
+ class_def->setValid(valid_one);
+ dictionary->addClass(class_def);
+
+ // Create one subnet with a pool holding one address.
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+ subnet_->setPreferred(Triplet<uint32_t>(100, 200, 300));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // Initialize FQDN data for the lease.
+ initFqdn("myhost.example.com", true, true);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // Asking specifically for this address with zero lifetimes
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().addHint(addr, 128, 0, 0);
+
+ // Add the class name to the context.
+ ctx2.query_->addClass("valid_one");
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check lifetimes: specified values are expected.
+ EXPECT_EQ(200, lease->preferred_lft_);
+ EXPECT_EQ(700, lease->valid_lft_);
+}
+
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ cfg_mgr.commit();
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's create an expired lease
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+
+ const SubnetID other_subnetid = 999;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
+ 501, 502, other_subnetid, HWAddrPtr()));
+ int64_t other_cumulative =
+ getStatistics("cumulative-assigned-nas", other_subnetid);
+
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ // This reactivated lease should have updated FQDN data.
+ EXPECT_TRUE(lease->hostname_.empty());
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+
+ // Check that the old lease has been returned.
+ Lease6Ptr old_lease = expectOneLease(ctx.currentIA().old_leases_);
+ ASSERT_TRUE(old_lease);
+
+ // It should at least have the same IPv6 address.
+ EXPECT_EQ(lease->addr_, old_lease->addr_);
+ // Check that it carries not updated FQDN data.
+ EXPECT_EQ("myhost.example.com.", old_lease->hostname_);
+ EXPECT_TRUE(old_lease->fqdn_fwd_);
+ EXPECT_TRUE(old_lease->fqdn_rev_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Verify the stats got adjusted correctly
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("assigned-nas", -1, other_subnetid));
+ EXPECT_FALSE(testStatistics("cumulative-assigned-nas",
+ other_cumulative, other_subnetid, false));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 1, other_subnetid));
+}
+
+// Checks if the lease lifetime is extended when the client sends the
+// Request.
+TEST_F(AllocEngine6Test, requestExtendLeaseLifetime) {
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Client should receive a lease.
+ Lease6Ptr new_lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
+ ASSERT_TRUE(new_lease);
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(new_lease->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Checks if the lease lifetime is extended when the client sends the
+// Request and the client has a reservation for the lease.
+TEST_F(AllocEngine6Test, requestExtendLeaseLifetimeForReservation) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1c"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Client should receive a lease.
+ Lease6Ptr new_lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
+ ASSERT_TRUE(new_lease);
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(new_lease->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Checks if the lease lifetime is extended when the client sends the
+// Renew.
+TEST_F(AllocEngine6Test, renewExtendLeaseLifetime) {
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Checks that a renewed lease uses default lifetimes.
+TEST_F(AllocEngine6Test, defaultRenewLeaseLifetime) {
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
+ 128, 0, 0));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+
+ // Checks that default values are used for lifetimes.
+ EXPECT_EQ(300, renewed[0]->preferred_lft_);
+ EXPECT_EQ(400, renewed[0]->valid_lft_);
+}
+
+// Checks that a renewed lease uses specified lifetimes.
+TEST_F(AllocEngine6Test, hintRenewLeaseLifetime) {
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
+ 128, 301, 399));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+
+ // Checks that specified values are used for lifetimes.
+ EXPECT_EQ(301, renewed[0]->preferred_lft_);
+ EXPECT_EQ(399, renewed[0]->valid_lft_);
+}
+
+// Checks that a renewed lease uses min lifetimes.
+TEST_F(AllocEngine6Test, minRenewLeaseLifetime) {
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
+ 128, 100, 200));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+
+ // Checks that min values are used for lifetimes.
+ EXPECT_EQ(200, renewed[0]->preferred_lft_);
+ EXPECT_EQ(300, renewed[0]->valid_lft_);
+}
+
+// Checks that a renewed lease uses max lifetimes.
+TEST_F(AllocEngine6Test, maxRenewLeaseLifetime) {
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
+ 128, 500, 600));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+
+ // Checks that max values are used for lifetimes.
+ EXPECT_EQ(400, renewed[0]->preferred_lft_);
+ EXPECT_EQ(500, renewed[0]->valid_lft_);
+}
+
+// Verifies renewal uses class life times via getLifetimes() in invoked by
+// extendLease6().
+TEST_F(AllocEngine6Test, renewClassLeaseLifetime) {
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));
+
+ Lease::Type type = Lease::TYPE_NA;
+
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "",
+ false, query);
+ ctx.currentIA().hints_ = hints;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().type_ = type;
+
+ // Add a client class with both valid and preferred lifetime values.
+ ClientClassDefPtr class_def(new ClientClassDef("lft_class", ExpressionPtr()));
+ Triplet<uint32_t> valid_lft(600, 700, 800);
+ class_def->setValid(valid_lft);
+ Triplet<uint32_t> preferred_lft(650, 750, 850);
+ class_def->setPreferred(preferred_lft);
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary()->addClass(class_def);
+ ctx.query_->addClass(class_def->getName());
+
+
+ findReservation(engine, ctx);
+ Lease6Collection renewed = engine.renewLeases6(ctx);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+
+ // Verify life times came from the class. Preferred should get adjusted.
+ EXPECT_EQ(renewed[0]->preferred_lft_, 437);
+ EXPECT_EQ(renewed[0]->valid_lft_, 700);
+}
+
+// Renew and the client has a reservation for the lease.
+TEST_F(AllocEngine6Test, renewExtendLeaseLifetimeForReservation) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::15"), 128);
+
+ // Create a lease for the client.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
+ duid_, iaid_, 300, 400,
+ subnet_->getID(), HWAddrPtr()));
+
+ // Allocated 200 seconds ago - half of the lifetime.
+ time_t lease_cltt = time(NULL) - 200;
+ lease->cltt_ = lease_cltt;
+
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ AllocEngine engine(100);
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease_cltt)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// --- v6 host reservation ---
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that a DHCPv6 client can, but doesn't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitNoHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressInPoolRequestNoHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should have been incremented by one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitValidHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should not have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressInPoolRequestValidHint) {
+ // Create reservation for the client This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does matches reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitMatchingHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should not have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressInPoolRequestMatchingHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // Assigned count should have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (out-of-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an out-of-pool reservation.
+// - Client sends SOLICIT without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitNoHint) {
+ // Create reservation for the client. This is out-of-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true, false);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // Assigned count should not have been incremented.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (out-of-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an out-of-pool reservation.
+// - Client sends REQUEST without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestNoHint) {
+ // Create reservation for the client. This is out-of-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false, false);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitValidHint) {
+ // Create reservation for the client. This is out-of-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true, false);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (out-of-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an out-of-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestValidHint) {
+ // Create reservation for the client. This is out-of-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false, false);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (out-of-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an out-of-pool reservation.
+// - Client sends SOLICIT with a hint that does matches reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitMatchingHint) {
+ // Create reservation for the client. This is out-of-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true, false);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a client gets the address reserved (out-of-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an out-of-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestMatchingHint) {
+ // Create reservation for the client. This is out-of-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false, false);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());
+
+ // We should not have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// In the following situation:
+// - client is assigned an address A
+// - HR is made for *this* client to get B
+// - client tries to get address A:
+// Check that his existing lease for lease A is removed
+// Check that he is assigned a new lease for B
+// - verify that the number of assigned address behaves as expected
+TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedThis) {
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Client gets an address
+ Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
+ ASSERT_TRUE(lease1);
+
+ // We should have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // Just check that if the client requests again, it will get the same
+ // address.
+ Lease6Ptr lease2 = simpleAlloc6Test(pool_, lease1->addr_, false);
+ ASSERT_TRUE(lease2);
+ detailCompareLease(lease1, lease2);
+
+ // We should not have bumped the address counter again
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // Now admin creates a reservation for this client. This is in-pool
+ // reservation, as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ // Just check that this time the client will get.
+ Lease6Ptr lease3 = simpleAlloc6Test(pool_, lease1->addr_, false);
+ ASSERT_TRUE(lease3);
+
+ // Check that previous lease was not used anymore.
+ EXPECT_NE(lease1->addr_.toText(), lease3->addr_.toText());
+
+ // Check that the reserved address was indeed assigned.
+ EXPECT_EQ("2001:db8:1::1c", lease3->addr_.toText());
+
+ // Check that the old lease is gone.
+ Lease6Ptr old = LeaseMgrFactory::instance().getLease6(lease1->type_,
+ lease1->addr_);
+ EXPECT_FALSE(old);
+
+ // Check that the reserved lease is in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease1->type_,
+ IOAddress("2001:db8:1::1c"));
+
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease3, from_mgr);
+
+ // Lastly check to see that the address counter is still 1, we should have
+ // have decremented it on the implied release and incremented it on the reserved
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// In the following situation:
+// - client X is assigned an address A
+// - HR is made for client Y (*other* client) to get A
+// - client X tries to get address A:
+// Check that his existing lease for lease A is removed
+// Check that he is assigned a new lease
+TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedOther) {
+ AllocEngine engine(100);
+
+ // Assigned count should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Client gets an address
+ Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
+ ASSERT_TRUE(lease1);
+
+ // We should have bumped the address counter
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // Just check that if the client requests again, it will get the same
+ // address.
+ Lease6Ptr lease2 = simpleAlloc6Test(pool_, lease1->addr_, false);
+ ASSERT_TRUE(lease2);
+ detailCompareLease(lease1, lease2);
+
+ // We should not have bumped the address counter again
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // Now admin creates a reservation for this client. Let's use the
+ // address client X just received. Let's generate a host, but don't add it
+ // to the HostMgr yet.
+ HostPtr host = createHost6(false, IPv6Resrv::TYPE_NA, lease1->addr_, 128);
+
+ // We need to tweak reservation id: use a different DUID for client Y
+ vector<uint8_t> other_duid(8, 0x45);
+ host->setIdentifier(&other_duid[0], other_duid.size(), Host::IDENT_DUID);
+
+ // Ok, now add it to the HostMgr
+ addHost(host);
+
+ // Just check that this time the client will get a different lease.
+ Lease6Ptr lease3 = simpleAlloc6Test(pool_, lease1->addr_, false);
+ ASSERT_TRUE(lease3);
+
+ // Check that previous lease was not used anymore.
+ EXPECT_NE(lease1->addr_.toText(), lease3->addr_.toText());
+
+ // Check that the old lease is gone.
+ Lease6Ptr old = LeaseMgrFactory::instance().getLease6(lease1->type_,
+ lease1->addr_);
+ EXPECT_FALSE(old);
+
+ // Check that the reserved lease is in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease1->type_,
+ lease3->addr_);
+
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease3, from_mgr);
+
+ // Lastly check to see that the address counter is still 1 we should have
+ // have decremented it on the implied release and incremented it on the reserved
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks that a reserved address for client A is not assigned when
+// other clients are requesting addresses. The scenario is as follows:
+// we use a regular pool with 17 addresses in it. One of them is
+// reserved for client A. Now we try to allocate addresses for 30 clients
+// (A is not one of them). The first 16 attempts should succeed. Then
+// we run out of addresses and remaining 14 clients will get nothing.
+// Finally, we check that client A still can get his reserved address.
+TEST_F(AllocEngine6Test, reservedAddress) {
+ AllocEngine engine(100);
+
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::12"), 128);
+
+ // Let's generate 30 DUIDs, each of them 16 bytes long
+ vector<DuidPtr> clients;
+ for (int i = 0; i < 30; i++) {
+ vector<uint8_t> data(16, i);
+ clients.push_back(DuidPtr(new DUID(data)));
+ }
+
+ // The default pool is 2001:db8:1::10 to 2001:db8:1::20. There's 17
+ // addresses in it. One of them is reserved, so this means that we should
+ // get 16 successes and 14 (30-16) failures.
+ int success = 0;
+ int failure = 0;
+ for (int i = 0; i < 30; i++) {
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, clients[i], false, false, "",
+ false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ findReservation(engine, ctx);
+ Lease6Collection leases = engine.allocateLeases6(ctx);
+ if (leases.empty()) {
+ failure++;
+ std::cout << "Alloc for client " << (int)i << " failed." << std::endl;
+ EXPECT_EQ(failure, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(failure, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(failure, getStatistics("v6-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(failure, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
+ } else {
+ success++;
+ std::cout << "Alloc for client " << (int)i << " succeeded:"
+ << leases[0]->addr_.toText() << std::endl;
+
+ // The assigned addresses must not match the one reserved.
+ EXPECT_NE("2001:db8:1::12", leases[0]->addr_.toText());
+ }
+ }
+
+ EXPECT_EQ(16, success);
+ EXPECT_EQ(14, failure);
+
+ // We're now pretty sure that any clients other than the reserved address
+ // will not get any service. Now let's check if the client that has the
+ // address reserved, will get it (despite the pool being depleted).
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx.currentIA().iaid_ = iaid_;
+
+ findReservation(engine, ctx);
+ Lease6Collection leases = engine.allocateLeases6(ctx);
+ ASSERT_EQ(1, leases.size());
+ EXPECT_EQ("2001:db8:1::12", leases[0]->addr_.toText());
+}
+
+// Checks if the allocateLeases throws exceptions for invalid input data.
+TEST_F(AllocEngine6Test, allocateLeasesInvalidData) {
+ AllocEngine engine(100);
+
+ // That looks like a valid context.
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Collection leases;
+
+ // Let's break it!
+ ctx.subnet_.reset();
+
+ // Subnet is required for allocation, so we should get no leases.
+ EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
+ ASSERT_TRUE(leases.empty());
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
+
+ // Let's fix this and break it in a different way.
+ ctx.subnet_ = subnet_;
+ ctx.duid_.reset();
+
+ // We must know who we're allocating for. No duid = no service.
+ EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
+ ASSERT_TRUE(leases.empty());
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
+}
+
+// Checks whether an address can be renewed (simple case, no reservation tricks)
+TEST_F(AllocEngine6Test, addressRenewal) {
+ AllocEngine engine(100);
+
+ // Assigned count should zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ Lease6Collection leases;
+
+ leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
+ ASSERT_EQ(1, leases.size());
+
+ // Assigned count should be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));
+
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // Check that the lease was indeed renewed and hasn't changed
+ // (i.e. the same address, preferred and valid lifetimes)
+
+ /// @todo: use leaseCompare, but ignore cltt_
+ EXPECT_EQ(leases[0]->addr_, renewed[0]->addr_);
+ EXPECT_EQ(leases[0]->type_, renewed[0]->type_);
+ EXPECT_EQ(leases[0]->preferred_lft_, renewed[0]->preferred_lft_);
+ EXPECT_EQ(leases[0]->valid_lft_, renewed[0]->valid_lft_);
+
+ // Assigned count should still be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks whether an address can be renewed (in-pool reservation)
+TEST_F(AllocEngine6Test, reservedAddressRenewal) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Assigned count should zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ Lease6Collection leases;
+
+ leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
+ ASSERT_EQ(1, leases.size());
+ ASSERT_EQ("2001:db8:1::1c", leases[0]->addr_.toText());
+
+ // Assigned count should be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));
+
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+ ASSERT_EQ("2001:db8:1::1c", leases[0]->addr_.toText());
+
+ // Assigned count should still be one.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+}
+
+// Checks whether a single host can have more than one reservation.
+//
+/// @todo: as of #3677, this does not work. When processing solicit with two
+/// IA_NAs and two reservations, there currently no way to indicate that
+/// the first reservation should be used for the first IA and the second
+/// reservation for the second IA. This works for Requests and Renews, though.
+/// In both of those messages, when processing of the first IA is complete,
+/// we have a lease in the database. Based on that, when processing the second
+/// IA we can detect that the first reserved address is in use already and
+/// use the second reservation.
+TEST_F(AllocEngine6Test, DISABLED_reserved2AddressesSolicit) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ // Two addresses are reserved: 2001:db8:1::babe and 2001:db8:1::cafe
+ HostPtr host = createHost6(true, IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::babe"), 128);
+
+ IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"), 128);
+ host->addReservation(resv2);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(100);
+
+ AllocEngine::ClientContext6 ctx1(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ ctx1.currentIA().iaid_ = iaid_;
+ ctx1.currentIA().type_ = pool_->getType();
+
+ Lease6Collection leases1;
+ findReservation(engine, ctx1);
+ EXPECT_NO_THROW(leases1 = engine.allocateLeases6(ctx1));
+ ASSERT_EQ(1, leases1.size());
+ EXPECT_EQ("2001:db8:1::babe", leases1[0]->addr_.toText());
+
+ // Double check that repeating the same duid/type/iaid will end up with
+ // the same address.
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().type_ = pool_->getType();
+
+ Lease6Collection leases2;
+ findReservation(engine, ctx2);
+ EXPECT_NO_THROW(leases2 = engine.allocateLeases6(ctx2));
+ EXPECT_EQ(1, leases2.size());
+ EXPECT_EQ("2001:db8:1::babe", leases2[0]->addr_.toText());
+
+ // Ok, now the tricky part. Request allocation for the same duid and type, but
+ // different iaid. The second address should be assigned.
+ AllocEngine::ClientContext6 ctx3(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ ctx3.currentIA().iaid_ = iaid_ + 1;
+ ctx3.currentIA().type_ = pool_->getType();
+
+ Lease6Collection leases3;
+ findReservation(engine, ctx3);
+ EXPECT_NO_THROW(leases3 = engine.allocateLeases6(ctx3));
+ ASSERT_EQ(1, leases3.size());
+ EXPECT_EQ("2001:db8:1::cafe", leases3[0]->addr_.toText());
+}
+
+// Checks whether a single host can have more than one reservation.
+TEST_F(AllocEngine6Test, reserved2Addresses) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ // Two addresses are reserved: 2001:db8:1::babe and 2001:db8:1::cafe
+ HostPtr host = createHost6(true, IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::babe"), 128);
+
+ IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"), 128);
+ host->addReservation(resv2);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ AllocEngine engine(100);
+
+ AllocEngine::ClientContext6 ctx1(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx1.currentIA().iaid_ = iaid_;
+ ctx1.currentIA().type_ = pool_->getType();
+
+ Lease6Collection leases1;
+ findReservation(engine, ctx1);
+ EXPECT_NO_THROW(leases1 = engine.allocateLeases6(ctx1));
+ ASSERT_EQ(1, leases1.size());
+ EXPECT_EQ("2001:db8:1::babe", leases1[0]->addr_.toText());
+
+ // Double check that repeating the same duid/type/iaid will end up with
+ // the same address.
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.currentIA().type_ = pool_->getType();
+
+ Lease6Collection leases2;
+ findReservation(engine, ctx2);
+ EXPECT_NO_THROW(leases2 = engine.allocateLeases6(ctx2));
+ EXPECT_EQ(1, leases2.size());
+ EXPECT_EQ("2001:db8:1::babe", leases2[0]->addr_.toText());
+
+ // Ok, now the tricky part. Request allocation for the same duid and type, but
+ // different iaid. The second address should be assigned.
+ AllocEngine::ClientContext6 ctx3(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ctx3.currentIA().iaid_ = iaid_ + 1;
+ ctx3.currentIA().type_ = pool_->getType();
+
+ Lease6Collection leases3;
+ findReservation(engine, ctx3);
+ EXPECT_NO_THROW(leases3 = engine.allocateLeases6(ctx3));
+ ASSERT_EQ(1, leases3.size());
+ EXPECT_EQ("2001:db8:1::cafe", leases3[0]->addr_.toText());
+}
+
+// Checks whether address can change during renew (if there is a new
+// reservation for this client)
+TEST_F(AllocEngine6Test, reservedAddressRenewChange) {
+
+ AllocEngine engine(100);
+
+ Lease6Collection leases;
+
+ leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
+ ASSERT_EQ(1, leases.size());
+ ASSERT_NE("2001:db8:1::1c", leases[0]->addr_.toText());
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));
+
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);
+
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+ ASSERT_EQ("2001:db8:1::1c", renewed[0]->addr_.toText());
+}
+
+// Checks whether address can change during renew (if there is a new
+// reservation for this address for another client)
+TEST_F(AllocEngine6Test, reservedAddressRenewReserved) {
+
+ AllocEngine engine(100);
+
+ Lease6Collection leases;
+
+ leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
+ ASSERT_EQ(1, leases.size());
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));
+
+ // Create reservation for this address, but for another client.
+ // This is in-pool reservation, as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ HostPtr host = createHost6(false, IPv6Resrv::TYPE_NA, leases[0]->addr_, 128);
+
+ // We need to tweak reservation id: use a different DUID for client Y
+ vector<uint8_t> other_duid(8, 0x45);
+ host->setIdentifier(&other_duid[0], other_duid.size(), Host::IDENT_DUID);
+ // Ok, now add it to the HostMgr
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // Check that we no longer have the reserved address.
+ ASSERT_NE(leases[0]->addr_.toText(), renewed[0]->addr_.toText());
+
+ // Check that the lease for the now reserved address is no longer in
+ // the lease database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ leases[0]->addr_);
+ EXPECT_FALSE(from_mgr);
+}
+
+/// @todo: The following methods are tested indirectly by allocateLeases6()
+/// tests, but could use more direct testing:
+/// - AllocEngine::allocateUnreservedLeases6
+/// - AllocEngine::allocateReservedLeases6
+/// - AllocEngine::removeNonmatchingReservedLeases6
+/// - AllocEngine::removeLeases
+/// - AllocEngine::removeNonreservedLeases6
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolSolicitNoHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+ IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST without any hints.
+// - Client is allocated a reserved address.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolRequestNoHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+ IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends SOLICIT with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client can, but don't have to send any hints in its
+// Solicit message.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolSolicitValidHint) {
+ // Create reservation for the client. This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+ IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// Checks that a client gets the address reserved (in-pool case)
+// This test checks the behavior of the allocation engine in the following
+// scenario:
+// - Client has no lease in the database.
+// - Client has an in-pool reservation.
+// - Client sends REQUEST with a hint that does not match reservation
+// - Client is allocated a reserved address, not the hint.
+//
+// Note that DHCPv6 client must send an address in REQUEST that the server
+// offered in Advertise. Nevertheless, the client may ignore this requirement.
+TEST_F(AllocEngine6Test, reservedAddressByMacInPoolRequestValidHint) {
+ // Create reservation for the client This is in-pool reservation,
+ // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
+ createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
+ IOAddress("2001:db8:1::1c"), 128);
+
+ AllocEngine engine(100);
+
+ // Let's pretend the client sends hint 2001:db8:1::10.
+ Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false);
+ ASSERT_TRUE(lease);
+
+ // The hint should be ignored and the reserved address should be assigned
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+}
+
+// This test checks that the allocation engine can delegate the long prefix.
+// The pool with prefix of 64 and with long delegated prefix has a very
+// high capacity. The number of attempts that the allocation engine makes
+// to allocate the prefix for high capacity pools is equal to the capacity
+// value. This test verifies that the prefix can be allocated in that
+// case.
+TEST_F(AllocEngine6Test, largePdPool) {
+ AllocEngine engine(0);
+
+ // Remove the default PD pool.
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Configure the PD pool with the prefix length of /80 and the delegated
+ // length /96.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
+ subnet_->addPool(pool);
+
+ // We should have got exactly one lease.
+ Lease6Collection leases = allocateTest(engine, pool, IOAddress("::"),
+ false, true);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks that the allocation engine can pick a pool which has smaller
+// delegated prefix length than the hint.
+TEST_F(AllocEngine6Test, largePdPoolPreferLower) {
+ AllocEngine engine(0);
+
+ // Remove the default PD pool.
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Configure the PD pool with the prefix length of /80 and the delegated
+ // length /96.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
+ subnet_->addPool(pool);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
+ subnet_->addPool(pool2);
+
+ // We should have got exactly one lease.
+ // Even though the hint is from the first pool, the second pool is preferred.
+ Lease6Collection leases = allocateTest(engine, pool2, IOAddress("2001:db8:1:2::"),
+ false, true, 92);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks that the allocation engine can pick a pool which has smaller
+// delegated prefix length than the hint. However the already present lease in
+// the database is used and the hint delegated length is ignored.
+TEST_F(AllocEngine6Test, largePdPoolPreferExistingInsteadOfLower) {
+ AllocEngine engine(0);
+
+ // Remove the default PD pool.
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Configure the PD pool with the prefix length of /80 and the delegated
+ // length /96.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
+ subnet_->addPool(pool);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
+ subnet_->addPool(pool2);
+
+ // Let's create a lease and put it in the LeaseMgr
+ // Even if the prefix length in the hint does not match, the allocation
+ // engine should use the existing lease.
+ Lease6Ptr used(new Lease6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ duid_, iaid_, 300, 400, subnet_->getID(), HWAddrPtr(), 96));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // We should have got exactly one lease.
+ Lease6Collection leases = allocateTest(engine, pool, IOAddress("2001:db8:1:3::"),
+ false, true, 92);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks that the allocation engine can pick a pool which has exact
+// delegated prefix length as the hint.
+TEST_F(AllocEngine6Test, largePdPoolPreferEqual) {
+ AllocEngine engine(0);
+
+ // Remove the default PD pool.
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Configure the PD pool with the prefix length of /80 and the delegated
+ // length /96.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
+ subnet_->addPool(pool);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
+ subnet_->addPool(pool2);
+
+ // We should have got exactly one lease.
+ // Even though the hint is from the first pool, the second pool is preferred.
+ Lease6Collection leases = allocateTest(engine, pool2, IOAddress("2001:db8:1:2::"),
+ false, true, 80);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks that the allocation engine can pick a pool which has exact
+// delegated prefix length as the hint. However the already present lease in
+// the database is used and the hint delegated length is ignored.
+TEST_F(AllocEngine6Test, largePdPoolPreferExistingInsteadOfEqual) {
+ AllocEngine engine(0);
+
+ // Remove the default PD pool.
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Configure the PD pool with the prefix length of /80 and the delegated
+ // length /96.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
+ subnet_->addPool(pool);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
+ subnet_->addPool(pool2);
+
+ // Let's create a lease and put it in the LeaseMgr
+ // Even if the prefix length in the hint does not match, the allocation
+ // engine should use the existing lease.
+ Lease6Ptr used(new Lease6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ duid_, iaid_, 300, 400, subnet_->getID(), HWAddrPtr(), 96));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // We should have got exactly one lease.
+ Lease6Collection leases = allocateTest(engine, pool, IOAddress("2001:db8:1:3::"),
+ false, true, 80);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks that the allocation engine can pick a pool which has greater
+// delegated prefix length than the hint.
+TEST_F(AllocEngine6Test, largePdPoolPreferHigher) {
+ AllocEngine engine(0);
+
+ // Remove the default PD pool.
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Configure the PD pool with the prefix length of /80 and the delegated
+ // length /96.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
+ subnet_->addPool(pool);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
+ subnet_->addPool(pool2);
+
+ // We should have got exactly one lease.
+ // Even though the first pool also matches the condition, because of the hint,
+ // the second pool is preferred.
+ Lease6Collection leases = allocateTest(engine, pool2, IOAddress("2001:db8:1:3::"),
+ false, true, 64);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks that the allocation engine can pick a pool which has greater
+// delegated prefix length than the hint. However the already present lease in
+// the database is used and the hint delegated length is ignored.
+TEST_F(AllocEngine6Test, largePdPoolPreferExistingInsteadOfHigher) {
+ AllocEngine engine(0);
+
+ // Remove the default PD pool.
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Configure the PD pool with the prefix length of /80 and the delegated
+ // length /96.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
+ subnet_->addPool(pool);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
+ subnet_->addPool(pool2);
+
+ // Let's create a lease and put it in the LeaseMgr
+ // Even if the prefix length in the hint does not match, the allocation
+ // engine should use the existing lease.
+ Lease6Ptr used(new Lease6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
+ duid_, iaid_, 300, 400, subnet_->getID(), HWAddrPtr(), 96));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // We should have got exactly one lease.
+ Lease6Collection leases = allocateTest(engine, pool, IOAddress("2001:db8:1:3::"),
+ false, true, 64);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks that the allocation engine can delegate addresses
+// from ridiculously large pool. The configuration provides 2^80 or
+// 1208925819614629174706176 addresses. We used to have a bug that would
+// confuse the allocation engine if the number of available addresses
+// was larger than 2^32.
+TEST_F(AllocEngine6Test, largePoolOver32bits) {
+ AllocEngine engine(0);
+
+ // Configure 2001:db8::/32 subnet
+ subnet_ = Subnet6::create(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(10));
+
+ // Configure the NA pool of /48. So there are 2^80 addresses there. Make
+ // sure that we still can handle cases where number of available addresses
+ // is over max_uint64
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 48));
+ subnet_->addPool(pool);
+
+ // We should have got exactly one lease.
+ Lease6Collection leases = allocateTest(engine, pool, IOAddress("::"),
+ false, true);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test verifies that it is possible to override the number of allocation
+// attempts made by the allocation engine for a single lease.
+TEST_F(AllocEngine6Test, largeAllocationAttemptsOverride) {
+ // Remove the default NA pools.
+ subnet_->delPools(Lease::TYPE_NA);
+ subnet_->delPools(Lease::TYPE_PD);
+
+ // Add exactly one pool with many addresses.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 56));
+ subnet_->addPool(pool);
+
+ // Allocate 5 addresses from the pool configured.
+ for (int i = 0; i < 5; ++i) {
+ DuidPtr duid = DuidPtr(new DUID(vector<uint8_t>(12,
+ static_cast<uint8_t>(i))));
+ // Get the unique IAID.
+ const uint32_t iaid = 3568 + i;
+
+ // Construct the unique address from the pool.
+ std::ostringstream address;
+ address << "2001:db8:1::";
+ address << i;
+
+ // Allocate the lease.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress(address.str()),
+ duid, iaid, 501, 502, subnet_->getID(), HWAddrPtr()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+ }
+
+ // Try to use the allocation engine to allocate the lease. The iterative
+ // allocator will pick the addresses already allocated until it finds the
+ // available address. Since, we have restricted the number of attempts the
+ // allocation should fail.
+ AllocEngine engine(3);
+ Lease6Collection leases = allocateTest(engine, pool_, IOAddress("::"),
+ false, true);
+ ASSERT_TRUE(leases.empty());
+
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
+
+ // This time, lets allow more attempts, and expect that the allocation will
+ // be successful.
+ AllocEngine engine2(6);
+ leases = allocateTest(engine2, pool_, IOAddress("::"), false, true);
+ ASSERT_EQ(1, leases.size());
+}
+
+// This test checks if an expired declined lease can be reused in SOLICIT (fake allocation)
+TEST_F(AllocEngine6Test, solicitReuseDeclinedLease6) {
+
+ AllocEnginePtr engine(new AllocEngine(100));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ // Create one subnet with a pool holding one address.
+ string addr_txt("2001:db8:1::ad");
+ IOAddress addr(addr_txt);
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+
+ // Use information that is different than what we'll request
+ Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);
+ ASSERT_TRUE(declined->expired());
+
+ // CASE 1: Asking for any address
+ Lease6Ptr assigned;
+ testReuseLease6(engine, declined, "::", true, SHOULD_PASS, assigned);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(assigned);
+ EXPECT_EQ(addr, assigned->addr_);
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease6(duid_, assigned, Lease::TYPE_NA, 128);
+
+ // CASE 2: Asking specifically for this address
+ testReuseLease6(engine, declined, addr_txt, true, SHOULD_PASS, assigned);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(assigned);
+ EXPECT_EQ(addr, assigned->addr_);
+}
+
+// This test checks if an expired declined lease can be reused when responding
+// to REQUEST (actual allocation)
+TEST_F(AllocEngine6Test, requestReuseDeclinedLease6) {
+
+ AllocEnginePtr engine(new AllocEngine(100));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ string addr_txt("2001:db8::7");
+ IOAddress addr(addr_txt);
+ initSubnet(IOAddress("2001:db8::"), addr, addr);
+
+ // Now create a declined lease, decline it and rewind its cltt, so it
+ // is expired.
+ Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);
+
+ // Asking specifically for this address
+ Lease6Ptr assigned;
+ testReuseLease6(engine, declined, addr_txt, false, SHOULD_PASS, assigned);
+ // Check that we got it.
+ ASSERT_TRUE(assigned);
+ EXPECT_EQ(addr, assigned->addr_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(assigned, from_mgr);
+}
+
+// This test checks if statistics are not updated when expired declined lease
+// is reused when responding to SOLICIT (fake allocation)
+TEST_F(AllocEngine6Test, solicitReuseDeclinedLease6Stats) {
+
+ // Now prepare for SOLICIT processing
+ AllocEnginePtr engine(new AllocEngine(100));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ string addr_txt("2001:db8:1::1");
+ IOAddress addr(addr_txt);
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+
+ // Stats should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Now create a declined lease, decline it and rewind its cltt, so it
+ // is expired.
+ Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);
+
+ // Ask for any address. There's only one address in the pool, so it doesn't
+ // matter much.
+ Lease6Ptr assigned;
+ testReuseLease6(engine, declined, "::", true, SHOULD_PASS, assigned);
+
+ // Check that the stats were not modified
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
+}
+
+// This test checks if statistics are updated when expired declined lease
+// is reused when responding to REQUEST (actual allocation)
+TEST_F(AllocEngine6Test, requestReuseDeclinedLease6Stats) {
+
+ // Prepare for REQUEST processing.
+ AllocEnginePtr engine(new AllocEngine(100));
+ ASSERT_TRUE(engine);
+
+ // Now prepare a configuration with single address pool.
+ string addr_txt("2001:db8::1");
+ IOAddress addr(addr_txt);
+ initSubnet(IOAddress("2001:db8::"), addr, addr);
+
+ // Stats should be zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
+ EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Now create a declined lease, decline it and rewind its cltt, so it
+ // is expired.
+ Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);
+
+ // Ask for any address. There's only one address in the pool, so it doesn't
+ // matter much.
+ Lease6Ptr assigned;
+ testReuseLease6(engine, declined, "::", false, SHOULD_PASS, assigned);
+
+ // Check that the stats were modified as expected.
+ // assigned-nas should NOT get incremented. Currently we do not adjust assigned
+ // counts when we declines
+ // declined-addresses will -1, as the artificial creation of declined lease
+ // doesn't increment it from zero. reclaimed-declined-addresses will be 1
+ // because the leases are implicitly reclaimed before they can be assigned.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1));
+ EXPECT_TRUE(testStatistics("declined-addresses", -1, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1, subnet_->getID()));
+}
+
+// This test checks if an expired-reclaimed lease can be reused by
+// a returning client via REQUEST, rather than renew/rebind. This
+// would be typical of cable modem clients which do not retain lease
+// data across reboots.
+TEST_F(AllocEngine6Test, reuseReclaimedExpiredViaRequest) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.clear(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ cfg_mgr.commit();
+
+ // Verify relevant stats are zero.
+ EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Let's create an expired lease
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Verify that the lease state is indeed expired-reclaimed
+ EXPECT_EQ(lease->state_, Lease::STATE_EXPIRED_RECLAIMED);
+
+ // Same client comes along and issues a request
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that he got the original lease back.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
+ addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+
+ // Verify that the lease state has been set back to the default.
+ EXPECT_EQ(lease->state_, Lease::STATE_DEFAULT);
+
+ // Verify assigned-nas got bumped. Reclaimed stats should still
+ // be zero as we artificially marked it reclaimed.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
+}
+
+/// @brief This test class is dedicated to testing shared networks
+///
+/// It uses one common configuration:
+/// 1 shared network with 2 subnets:
+/// - 2001:db8:1::/56 subnet with a small pool of single address
+/// - 2001:db8:1::/56 subnet with pool with 64K addresses.
+class SharedNetworkAlloc6Test : public AllocEngine6Test {
+public:
+ SharedNetworkAlloc6Test() : engine_(0) {
+ subnet1_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
+ subnet2_ = Subnet6::create(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4, SubnetID(20));
+ pool1_.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::1")));
+ pool2_.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
+ IOAddress("2001:db8:2::FF")));
+ subnet1_->addPool(pool1_);
+ subnet2_->addPool(pool2_);
+
+ // Both subnets belong to the same network so they can be used
+ // interchangeably.
+ network_.reset(new SharedNetwork6("test_network"));
+ network_->add(subnet1_);
+ network_->add(subnet2_);
+ }
+
+ Lease6Ptr
+ insertLease(std::string addr, SubnetID subnet_id) {
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress(addr), duid_, iaid_,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ if (!LeaseMgrFactory::instance().addLease(lease)) {
+ ADD_FAILURE() << "Failed to add a lease for address " << addr
+ << " in subnet with subnet-id " << subnet_id;
+ return (Lease6Ptr());
+ }
+ return (lease);
+ }
+
+ /// Convenience pointers to configuration elements. These are initialized
+ /// in the constructor and are used throughout the tests.
+ AllocEngine engine_;
+ Subnet6Ptr subnet1_;
+ Subnet6Ptr subnet2_;
+ Pool6Ptr pool1_;
+ Pool6Ptr pool2_;
+ SharedNetwork6Ptr network_;
+};
+
+// This test verifies that the server can offer an address from a
+// subnet and the introduction of shared network doesn't break anything here.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkSimple) {
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet1_->inRange(lease->addr_));
+}
+
+// This test verifies that the server can pick a subnet from shared subnets
+// based on hints.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkHint) {
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. There's a hint that points to the subnet2. The
+ // shared network mechanism should be able to pick the second subnet
+ // based on it.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(IOAddress("2001:db8:2::12"));
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+
+ // The second subnet should be selected.
+ ASSERT_TRUE(subnet2_->inRange(lease->addr_));
+}
+
+// This test verifies that the client is offered an address from an
+// alternative subnet within shared network when the address pool is
+// exhausted in the first address pool.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkOutOfAddresses) {
+
+ // Create a lease for a single address in the first address pool. The
+ // pool is now exhausted.
+ DuidPtr other_duid(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ other_duid, other_iaid, 501, 502,
+ subnet1_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet1, which address space
+ // is exhausted. We expect the allocation engine to find another subnet
+ // within the same shared network and offer an address from there.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease2;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ ASSERT_TRUE(subnet2_->inRange(lease2->addr_));
+
+ // The client having a lease should be offered this lease, even if
+ // the client starts allocation from the second subnet. The code should
+ // determine that the client has a lease in subnet1 and use this subnet
+ // instead.
+ AllocEngine::ClientContext6 ctx2(subnet2_, other_duid, false, false, "",
+ true, query);
+ ctx2.currentIA().iaid_ = other_iaid;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease2);
+ ASSERT_EQ("2001:db8:1::1", lease2->addr_.toText());
+
+ // Delete the lease in the first subnet.
+ ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));
+
+ // Now, try requesting this address by providing a hint. The engine
+ // should try to honor the hint even though we start from the subnet2.
+ ctx.subnet_ = subnet2_;
+ ctx.currentIA().addHint(IOAddress("2001:db8:1::1"));
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ ASSERT_TRUE(subnet1_->inRange(lease2->addr_));
+}
+
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet is exhausted.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkClassification) {
+ // Try to offer address from subnet1. There is an address available so
+ // it should be offered.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet1_->inRange(lease->addr_));
+
+ // Apply restrictions on the subnet1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ subnet1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the subnet1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should offer an address from subnet2 that belongs
+ // to the same shared network.
+ AllocEngine::ClientContext6 ctx2(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.query_ = query;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet2_->inRange(lease->addr_));
+
+ AllocEngine::ClientContext6 ctx3(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx3.currentIA().iaid_ = iaid_;
+ ctx3.query_ = query;
+
+ // Create host reservation in the first subnet for this client. The
+ // allocation engine should not assign reserved address to the client
+ // because client classification doesn't allow that.
+ subnet_ = subnet1_;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"), 128);
+ AllocEngine::findReservation(ctx3);
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet2_->inRange(lease->addr_));
+
+ AllocEngine::ClientContext6 ctx4(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx4.currentIA().iaid_ = iaid_;
+ ctx4.query_ = query;
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the subnet1_.
+ ctx4.query_->addClass(ClientClass("cable-modem"));
+
+ AllocEngine::findReservation(ctx4);
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
+}
+
+// This test verifies that the server can offer an address from a
+// different subnet than orginally selected, when the address pool in
+// the first subnet requires another class.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkPoolClassification) {
+ // Try to offer address from subnet1. There is an address available so
+ // it should be offered.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet1_->inRange(lease->addr_));
+
+ // Apply restrictions on the pool1. This should be only assigned
+ // to clients belonging to cable-modem class.
+ pool1_->allowClientClass("cable-modem");
+
+ // The allocation engine should determine that the pool1 is not
+ // available for the client not belonging to the cable-modem class.
+ // Instead, it should offer an address from subnet2 that belongs
+ // to the same shared network.
+ AllocEngine::ClientContext6 ctx2(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx2.currentIA().iaid_ = iaid_;
+ ctx2.query_ = query;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease);
+ ASSERT_TRUE(subnet2_->inRange(lease->addr_));
+
+ AllocEngine::ClientContext6 ctx3(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx3.currentIA().iaid_ = iaid_;
+ ctx3.query_ = query;
+
+ AllocEngine::ClientContext6 ctx4(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx4.currentIA().iaid_ = iaid_;
+ ctx4.query_ = query;
+
+ // Assign cable-modem class and try again. This time, we should
+ // offer an address from the pool1_.
+ ctx4.query_->addClass(ClientClass("cable-modem"));
+
+ // Restrict access to pool2 for this client, to make sure that the
+ // server doesn't accidentally get a lease from this pool.
+ pool2_->allowClientClass("telephone");
+
+ AllocEngine::findReservation(ctx4);
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
+}
+
+// This test verifies that the client is offered a reserved address
+// even if this address belongs to another subnet within the same
+// shared network.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkReservations) {
+ EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery());
+
+ // Create reservation for the client in the second subnet.
+ subnet_ = subnet2_;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);
+
+ // Start allocation from subnet1_. The engine should determine that the
+ // client has reservations in subnet2_ and should rather assign reserved
+ // addresses.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Find reservations for this subnet/shared network.
+ AllocEngine::findReservation(ctx);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
+}
+
+// This test verifies that the client is offered a reserved address
+// even if this address belongs to another subnet within the same
+// shared network. Host lookups returning a collection are disabled.
+// As it is only an optimization the behavior (so the test) must stay
+// unchanged.
+TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkReservationsNoColl) {
+ // Disable host lookups returning a collection.
+ ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery());
+ HostMgr::instance().setDisableSingleQuery(true);
+
+ // Create reservation for the client in the second subnet.
+ subnet_ = subnet2_;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);
+
+ // Start allocation from subnet1_. The engine should determine that the
+ // client has reservations in subnet2_ and should rather assign reserved
+ // addresses.
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Find reservations for this subnet/shared network.
+ AllocEngine::findReservation(ctx);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
+}
+
+// This test verifies that the client is allocated a reserved address
+// even if this address belongs to another subnet within the same
+// shared network.
+TEST_F(SharedNetworkAlloc6Test, requestSharedNetworkReservations) {
+ EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery());
+
+ // Create reservation for the client in the second subnet.
+ subnet_ = subnet2_;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);
+
+ // Start allocation from subnet1_. The engine should determine that the
+ // client has reservations in subnet2_ and should rather assign reserved
+ // addresses.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Find reservations for this subnet/shared network.
+ AllocEngine::findReservation(ctx);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
+}
+
+// This test verifies that the client is allocated a reserved address
+// even if this address belongs to another subnet within the same
+// shared network. Host lookups returning a collection are disabled.
+// As it is only an optimization the behavior (so the test) must stay
+// unchanged.
+TEST_F(SharedNetworkAlloc6Test, requestSharedNetworkReservationsNoColl) {
+ // Disable host lookups returning a collection.
+ ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery());
+ HostMgr::instance().setDisableSingleQuery(true);
+
+ // Create reservation for the client in the second subnet.
+ subnet_ = subnet2_;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);
+
+ // Start allocation from subnet1_. The engine should determine that the
+ // client has reservations in subnet2_ and should rather assign reserved
+ // addresses.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Find reservations for this subnet/shared network.
+ AllocEngine::findReservation(ctx);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
+}
+
+// This test verifies that client is assigned an existing lease from a
+// shared network, regardless of the default subnet. It also verifies that
+// the client is assigned a reserved address from a shared network which
+// replaces existing lease within this shared network.
+TEST_F(SharedNetworkAlloc6Test, requestSharedNetworkExistingLeases) {
+ // Get the cumulative count of assigned addresses.
+ int64_t cumulative = getStatistics("cumulative-assigned-nas",
+ subnet2_->getID());
+ int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");
+
+ // Create a lease in subnet 2 for this client. The lease is in expired
+ // reclaimed state initially to allow for checking whether the lease
+ // gets renewed.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1"),
+ duid_, iaid_, 501, 502,
+ subnet2_->getID(), HWAddrPtr()));
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet 1 initially, but the
+ // allocation engine should determine that there are existing leases
+ // in subnet 2 and renew those.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Check that we have been allocated the existing lease.
+ Lease6Ptr lease2;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("2001:db8:2::1", lease2->addr_.toText());
+
+ // Statistics should be bumped when the lease is re-assigned.
+ EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet2_->getID()));
+ cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
+ cumulative, subnet2_->getID()));
+ glbl_cumulative += 1;
+ EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
+
+ // Another interesting case is when there is a reservation in a different
+ // subnet than the one from which the ease has been assigned.
+ subnet_ = subnet1_;
+ createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"), 128);
+
+ // The reserved lease should take precedence.
+ ctx.subnet_ = subnet1_;
+ ctx.currentIA().iaid_ = iaid_;
+ AllocEngine::findReservation(ctx);
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("2001:db8:1::1", lease2->addr_.toText());
+
+ // The previous lease should have been removed.
+ ASSERT_EQ(1, ctx.currentIA().old_leases_.size());
+ EXPECT_EQ("2001:db8:2::1", ctx.currentIA().old_leases_[0]->addr_.toText());
+}
+
+// This test verifies that the server can offer an address from a shared
+// subnet if there's at least 1 address left there, but will not offer
+// anything if both subnets are completely full.
+TEST_F(SharedNetworkAlloc6Test, requestRunningOut) {
+
+ // Allocate everything in subnet1
+ insertLease("2001:db8:1::1", subnet1_->getID());
+
+ // Allocate everything, except one address in subnet2.
+ for (int i = 0; i < 255; i++) {
+ stringstream tmp;
+ tmp << "2001:db8:2::" << hex << i;
+ insertLease(tmp.str(), subnet2_->getID());
+ }
+
+ // Create context which will be used to try to allocate leases from the
+ // shared network. The context points to subnet 1 initially, but the
+ // allocation engine should determine that there are existing leases
+ // in subnet 2 and renew those.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx1(subnet1_, duid_, false, false, "", false,
+ query);
+ ctx1.currentIA().iaid_ = iaid_;
+
+ // Check that we have been allocated the existing lease (there's only
+ // one lease left, so we know exactly which one will be given out.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx1)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:2::ff", lease->addr_.toText());
+
+ // Ok, now try for another client. We should be completely full.
+ DuidPtr other_duid(new DUID(vector<uint8_t>(12, 0xff)));
+ AllocEngine::ClientContext6 ctx2(subnet2_, other_duid, false, false, "", false,
+ query);
+ Lease6Collection leases = engine_.allocateLeases6(ctx2);
+
+ // Bugger off, we're full!
+ ASSERT_TRUE(leases.empty());
+
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail"));
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail-shared-network"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));
+
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail", subnet2_->getID()));
+ EXPECT_EQ(1, getStatistics("v6-allocation-fail-shared-network", subnet2_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet2_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet2_->getID()));
+ EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet2_->getID()));
+}
+
+// Verifies that client with a hostname reservation can
+// 1. Get a dynamic lease
+// 2. Renew the same lease via REQUEST (calls allocateLease6)
+// 3. Renew the same lease via RENEW/REBIND (calls renewLeases6)
+// renew a dynamic lease from their selected subnet.
+TEST_F(AllocEngine6Test, hostDynamicAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("host1");
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsInSubnet(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("host1", current->getHostname());
+
+ // Check that we have been allocated a dynamic address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::10", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::10"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "host1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ Lease6Ptr renewed_lease = renewed[0];
+ EXPECT_EQ("2001:db8:1::10", renewed_lease->addr_.toText());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed_lease->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+
+ // Now let's verify that if the client returns via SARR, they get the same
+ // lease and the cltt gets extended.
+
+ // First, we're going to rollback the clock again so we can verify the
+ // allocation updates the expiry.
+ ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(renewed_lease));
+
+ // Create a new context which will be used to try to allocate leases
+ query.reset(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", false, query);
+ ctx2.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx2);
+
+ // Make sure we found our host.
+ current = ctx2.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("host1", current->getHostname());
+
+ // Check that we have been allocated the original dynamic address.
+ Lease6Ptr lease2;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("2001:db8:1::10", lease2->addr_.toText());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(lease2->cltt_, renewed_lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a global hostname reservation can:
+// 1. Get a dynamic lease
+// 2. Renew the same lease via REQUEST (calls allocateLease6)
+// 3. Renew the same lease via RENEW/REBIND (calls renewLeases6)
+TEST_F(AllocEngine6Test, globalHostDynamicAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated a dynamic address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::10", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::10"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ ASSERT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+
+ // Now let's verify that if the client returns via SARR, they get the same
+ // lease. Create a new context which will be used to try to allocate leases
+
+ // First, we're going to rollback the clock again so we can verify the
+ // allocation updates the expiry.
+ Lease6Ptr renewed_lease = renewed[0];
+ ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(renewed_lease));
+
+ query.reset(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", false, query);
+ ctx2.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx2);
+
+ // Make sure we found our host.
+ current = ctx2.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated a dynamic address.
+ Lease6Ptr lease2;
+ ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx2)));
+ ASSERT_TRUE(lease2);
+ EXPECT_EQ("2001:db8:1::10", lease2->addr_.toText());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(lease2->cltt_, renewed_lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a globally reserved address that is
+// outside the selected subnet is given a dynamic address instead.
+TEST_F(AllocEngine6Test, globalHostReservedAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated a dynamic address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_NE("3001::1", lease->addr_.toText());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_))
+ << " address not in range: " << lease->addr_.toText();
+
+ ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress(lease->addr_.toText()), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a globally reserved address that is
+// inside the selected subnet is given that address.
+TEST_F(AllocEngine6Test, globalHostReservedMatchingAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("2001:db8:1::a"), 128);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::a", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ //--lease->cltt_;
+ ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2008:db8:1::a"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, !IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a global prefix reservation can get and
+// renew a lease for an arbitrary prefix.
+TEST_F(AllocEngine6Test, globalHostReservedPrefix) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("3001::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("3001::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, !IN_SUBNET, !IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a global prefix reservation can get and
+// renew a lease for an arbitrary prefix even if using a wrong hint prefix
+// length.
+TEST_F(AllocEngine6Test, globalHostReservedPrefixDifferentPrefixLen) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("ghost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ // Using a different prefix length in the hint should have no effect
+ ctx.currentIA().addHint(asiolink::IOAddress("3001::"), 32);
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("ghost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("3001::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("3001::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "ghost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, !IN_SUBNET, !IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a subnet address reservation can get and
+// renew a lease for an address in the subnet.
+TEST_F(AllocEngine6Test, mixedHostReservedAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("mhost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("2001:db8:1::1c"), 128);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+ subnet_->setReservationsOutOfPool(false);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("mhost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::1c"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "mhost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a subnet prefix reservation can get and
+// renew a lease for a prefix in the subnet even if using a wrong hint prefix
+// length.
+TEST_F(AllocEngine6Test, mixedHostReservedPrefix) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("mhost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ // Using a different prefix length in the hint should have no effect
+ ctx.currentIA().addHint(asiolink::IOAddress("2001:db8:1:2::"), 32);
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("mhost1", current->getHostname());
+
+ // Check that we have been allocated the fixed prefix.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "mhost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a subnet prefix reservation can get and
+// renew a lease for a prefix in the subnet.
+TEST_F(AllocEngine6Test, mixedHostReservedPrefixDifferentPrefixLen) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("mhost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("mhost1", current->getHostname());
+
+ // Check that we have been allocated the fixed prefix.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "mhost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a subnet and a global address reservation
+// can get and renew a lease for an address in the subnet.
+TEST_F(AllocEngine6Test, bothHostReservedAddress) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr ghost(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ ghost->setHostname("ghost1");
+ IPv6Resrv gresv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128);
+ ghost->addReservation(gresv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("mhost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("2001:db8:1::1c"), 128);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+ subnet_->setReservationsOutOfPool(false);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("mhost1", current->getHostname());
+
+ // Check that we have been allocated the fixed address.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::1c"), 128));
+
+ // Set test fixture hostname_ to the expected value. This gets checked in
+ // renewTest.
+ hostname_ = "mhost1";
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a subnet and a global prefix reservation
+// can get and renew a lease for a prefix in the subnet.
+TEST_F(AllocEngine6Test, bothHostReservedPrefix) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr ghost(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ ghost->setHostname("ghost1");
+ IPv6Resrv gresv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
+ ghost->addReservation(gresv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("mhost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("mhost1", current->getHostname());
+
+ // Check that we have been allocated the fixed prefix.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "mhost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+// Verifies that client with a subnet and a global prefix reservation
+// can get and renew a lease for a prefix in the subnet even if using a wrong
+// hint prefix length.
+TEST_F(AllocEngine6Test, bothHostReservedPrefixDifferentPrefixLen) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ HostPtr ghost(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+ asiolink::IOAddress("0.0.0.0")));
+ ghost->setHostname("ghost1");
+ IPv6Resrv gresv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
+ ghost->addReservation(gresv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);
+
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+ Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ host->setHostname("mhost1");
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
+ host->addReservation(resv);
+
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+
+ subnet_->setReservationsGlobal(true);
+ subnet_->setReservationsInSubnet(true);
+
+ // Create context which will be used to try to allocate leases
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ // Using a different prefix length in the hint should have no effect
+ ctx.currentIA().addHint(asiolink::IOAddress("2001:db8:1:2::"), 32);
+
+ // Look up the reservation.
+ findReservation(*engine, ctx);
+ // Make sure we found our host.
+ ConstHostPtr current = ctx.currentHost();
+ ASSERT_TRUE(current);
+ ASSERT_EQ("mhost1", current->getHostname());
+
+ // Check that we have been allocated the fixed prefix.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());
+
+ // We're going to rollback the clock a little so we can verify a renewal.
+ --lease->cltt_;
+ EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));
+
+ // This is what the client will send in his renew message.
+ AllocEngine::HintContainer hints;
+ hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));
+
+ // Set test fixture hostname_ to the expected value. This gets checked via
+ // renewTest.
+ hostname_ = "mhost1";
+
+ // We need a PD pool to fake renew_test
+ Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));
+
+ // Client should receive a lease.
+ Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
+ ASSERT_EQ(1, renewed.size());
+
+ // And the lease lifetime should be extended.
+ EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
+ << "Lease lifetime was not extended, but it should";
+}
+
+/// @brief Test fixture class for testing storage of extended lease data.
+/// It primarily creates several configuration items common to the
+/// extended info tests.
+class AllocEngine6ExtendedInfoTest : public AllocEngine6Test {
+public:
+ /// @brief Constructor
+ AllocEngine6ExtendedInfoTest()
+ : engine_(100),
+ duid1_(), duid2_(), duid3_(), relay1_(), relay2_(), relay3_(),
+ duid1_addr_("::"), duid2_addr_("::") {
+ duid1_.reset(new DUID(std::vector<uint8_t>(8, 0x84)));
+ duid2_.reset(new DUID(std::vector<uint8_t>(8, 0x74)));
+ duid3_.reset(new DUID(std::vector<uint8_t>(8, 0x64)));
+
+ relay1_.msg_type_ = DHCPV6_RELAY_FORW;
+ relay1_.hop_count_ = 33;
+ relay1_.linkaddr_ = IOAddress("2001:db8::1");
+ relay1_.peeraddr_ = IOAddress("2001:db8::2");
+ relay1_.relay_msg_len_ = 0;
+
+ uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ vector<uint8_t> relay_data(relay_opt_data,
+ relay_opt_data + sizeof(relay_opt_data));
+ OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));
+
+ relay1_.options_.insert(make_pair(optRelay1->getType(), optRelay1));
+
+ relay2_.msg_type_ = DHCPV6_RELAY_FORW;
+ relay2_.hop_count_ = 77;
+ relay2_.linkaddr_ = IOAddress("2001:db8::3");
+ relay2_.peeraddr_ = IOAddress("2001:db8::4");
+ relay2_.relay_msg_len_ = 0;
+
+ relay3_.msg_type_ = DHCPV6_RELAY_FORW;
+ relay3_.hop_count_ = 100;
+ relay3_.linkaddr_ = IOAddress("2001:db8::5");
+ relay3_.peeraddr_ = IOAddress("2001:db8::6");
+ relay1_.relay_msg_len_ = 0;
+
+ vector<uint8_t> remote_id_data({1, 2, 3, 4, 5, 6});
+ OptionPtr remote_id(new Option(Option::V6, D6O_REMOTE_ID, remote_id_data));
+ relay3_.options_.insert(make_pair(remote_id->getType(), remote_id));
+
+ OptionPtr relay_id(new Option(Option::V6, D6O_RELAY_ID, duid3_->getDuid()));
+ relay3_.options_.insert(make_pair(relay_id->getType(), relay_id));
+
+ duid1_addr_ = IOAddress("2001:db8:1::10");
+ duid2_addr_ = IOAddress("2001:db8:1::11");
+
+ // Create the allocation engine, context and lease.
+ NakedAllocEngine engine(100);
+ }
+
+ /// Configuration elements. These are initialized in the constructor
+ /// and are used throughout the tests.
+ NakedAllocEngine engine_;
+ DuidPtr duid1_;
+ DuidPtr duid2_;
+ DuidPtr duid3_;
+ Pkt6::RelayInfo relay1_;
+ Pkt6::RelayInfo relay2_;
+ Pkt6::RelayInfo relay3_;
+ IOAddress duid1_addr_;
+ IOAddress duid2_addr_;
+};
+
+// Exercises AllocEnginer6Test::updateExtendedInfo6() through various
+// permutations of client packet content.
+TEST_F(AllocEngine6ExtendedInfoTest, updateExtendedInfo6) {
+ // Structure that defines a test scenario.
+ struct Scenario {
+ std::string description_; // test description
+ std::string orig_context_json_; // user context the lease begins with
+ std::vector<Pkt6::RelayInfo> relays_; // vector of relays from pkt
+ std::string exp_context_json_; // expected user context on the lease
+ bool exp_ret; // expected returned value
+ };
+
+ // Test scenarios.
+ std::vector<Scenario> scenarios {
+ {
+ "no context, no relay",
+ "",
+ {},
+ "",
+ false
+ },
+ {
+ "some original context, no relay",
+ "{\"foo\": 123}",
+ {},
+ "{\"foo\": 123}",
+ false
+ },
+ {
+ "no original context, one relay",
+ "",
+ { relay1_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
+ true
+ },
+ {
+ "no original context, one relay with remote and relay ids",
+ "",
+ { relay3_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100, \"link\": \"2001:db8::5\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\","
+ " \"peer\": \"2001:db8::6\" } ] } }",
+ true
+ },
+ {
+ "some original context, one relay",
+ "{\"foo\": 123, \"ISC\": {\"bar\": 456}}",
+ { relay1_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ],"
+ "\"bar\": 456 }, \"foo\": 123 }",
+ true
+ },
+ {
+ "bad original context, one relay",
+ "[\"foo\"]",
+ { relay1_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
+ true
+ },
+ {
+ "some original context, one relay",
+ "{\"foo\": 123, \"ISC\":[\"bar\"]}",
+ { relay1_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] },"
+ " \"foo\": 123 }",
+ true
+ },
+ {
+ "some original context, one relay with remote and relay ids",
+ "{\"foo\": 123}",
+ { relay3_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100, \"link\": \"2001:db8::5\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\","
+ " \"peer\": \"2001:db8::6\" } ] }, \"foo\": 123 }",
+ true
+ },
+ {
+ "no original context, two relay-info",
+ "",
+ { relay1_, relay2_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
+ " {\"hop\": 77, \"link\": \"2001:db8::3\", \"peer\": \"2001:db8::4\" } ] } }",
+ true
+ },
+ {
+ "no original context, two relay-info, second with remote and relay ids",
+ "",
+ { relay1_, relay3_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
+ " { \"hop\": 100, \"link\": \"2001:db8::5\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\","
+ " \"peer\": \"2001:db8::6\" } ] } }",
+ true
+ },
+ {
+ "original relay context, no relay",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
+ {},
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
+ false
+ },
+ {
+ "original relay context, different relay",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
+ { relay2_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 77, \"link\": \"2001:db8::3\","
+ " \"peer\": \"2001:db8::4\" } ] } }",
+ true
+ },
+ {
+ "original relay context, different relay with remote and relay ids",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
+ { relay3_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100, \"link\": \"2001:db8::5\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\","
+ " \"peer\": \"2001:db8::6\" } ] } }",
+ true
+ }
+ };
+
+ // Allocate a lease.
+ Lease6Ptr lease;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+
+ // All scenarios require storage to be enabled.
+ ctx.subnet_->setStoreExtendedInfo(true);
+
+ // Verify that the lease begins with no user context.
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_FALSE(user_context);
+
+ // Iterate over the test scenarios.
+ ElementPtr orig_context;
+ ElementPtr exp_context;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ // Create the original user context from JSON.
+ if (scenario.orig_context_json_.empty()) {
+ orig_context.reset();
+ } else {
+ ASSERT_NO_THROW(orig_context = Element::fromJSON(scenario.orig_context_json_))
+ << "invalid orig_context_json_, test is broken";
+ }
+
+ // Create the expected user context from JSON.
+ if (scenario.exp_context_json_.empty()) {
+ exp_context.reset();
+ } else {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_))
+ << "invalid exp_context_json_, test is broken";
+ }
+
+ // Initialize lease's user context.
+ lease->setContext(orig_context);
+ if (!orig_context) {
+ ASSERT_FALSE(lease->getContext());
+ } else {
+ ASSERT_TRUE(lease->getContext());
+ ASSERT_TRUE(orig_context->equals(*(lease->getContext())));
+ }
+
+ // Set the client packet relay vector from the scenario.
+ ctx.query_->relay_info_ = scenario.relays_;
+
+ // Call AllocEngine::updateLease6ExtendeInfo().
+ ASSERT_NO_THROW_LOG(engine_.callUpdateLease6ExtendedInfo(lease, ctx));
+ bool ret = (lease->extended_info_action_ == Lease6::ACTION_UPDATE);
+ // Reset the lease action.
+ lease->extended_info_action_ = Lease6::ACTION_IGNORE;
+ ASSERT_EQ(scenario.exp_ret, ret);
+
+ // Verify the lease has the expected user context content.
+ if (!exp_context) {
+ ASSERT_FALSE(lease->getContext());
+ } else {
+ ASSERT_TRUE(lease->getContext());
+ ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
+ << "expected: " << *(exp_context) << std::endl
+ << " actual: " << *(lease->getContext()) << std::endl;
+ }
+ }
+}
+
+// Verifies that the extended data (RelayInfos for now) is
+// added to a V6 lease when leases are created and/or renewed,
+// when store-extended-info is true.
+TEST_F(AllocEngine6ExtendedInfoTest, storeExtendedInfoEnabled6) {
+ // Structure that defines a test scenario.
+ struct Scenario {
+ std::string description_; // test description
+ DuidPtr duid_; // client DUID
+ std::vector<Pkt6::RelayInfo> relays_; // vector of relays from pkt
+ std::string exp_context_json_; // expected user context on the lease
+ IOAddress exp_address_; // expected lease address
+ };
+
+ // Test scenarios.
+ std::vector<Scenario> scenarios {
+ {
+ "create client one without relays",
+ duid1_,
+ {},
+ "",
+ duid1_addr_
+ },
+ {
+ "renew client one without relays",
+ DuidPtr(),
+ {},
+ "",
+ duid1_addr_
+ },
+ {
+ "create client two with relays",
+ duid2_,
+ { relay1_, relay2_ },
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
+ " { \"hop\": 77, \"link\": \"2001:db8::3\", \"peer\": \"2001:db8::4\" } ] } }",
+ duid2_addr_
+ },
+ {
+ "renew client two without rai",
+ DuidPtr(),
+ {},
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
+ " { \"hop\": 77, \"link\": \"2001:db8::3\", \"peer\": \"2001:db8::4\" } ] } }",
+ duid2_addr_
+ }};
+
+ // All of the scenarios require storage to be enabled.
+ subnet_->setStoreExtendedInfo(true);
+
+ // Iterate over the test scenarios.
+ DuidPtr current_duid;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ ElementPtr exp_context;
+ // Create the expected user context from JSON.
+ if (!scenario.exp_context_json_.empty()) {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_))
+ << "invalid exp_context_json_, test is broken";
+ }
+
+ Pkt6Ptr pkt;
+ if (scenario.duid_) {
+ current_duid = scenario.duid_;
+ pkt.reset(new Pkt6(DHCPV6_REQUEST, 1234));
+ } else {
+ pkt.reset(new Pkt6(DHCPV6_RENEW, 1234));
+ }
+
+ // Set packet relay vector from the scenario.
+ pkt->relay_info_ = scenario.relays_;
+
+ // Create the context;
+ AllocEngine::ClientContext6 ctx(subnet_, current_duid, false, false, "", false, pkt);
+
+ // Create or renew the lease.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ(scenario.exp_address_, lease->addr_);
+
+ // Verify the lease has the expected user context content.
+ if (!exp_context) {
+ ASSERT_FALSE(lease->getContext());
+ } else {
+ ASSERT_TRUE(lease->getContext());
+ ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
+ << "expected: " << *(exp_context) << std::endl
+ << " actual: " << *(lease->getContext()) << std::endl;
+ }
+ }
+}
+
+// Verifies that the extended data (RelayInfos for now) is
+// not added to a V6 lease when leases are created and/or renewed,
+// when store-extended-info is false.
+TEST_F(AllocEngine6ExtendedInfoTest, storeExtendedInfoDisabled6) {
+ // Structure that defines a test scenario.
+ struct Scenario {
+ std::string description_; // test description
+ DuidPtr duid_; // client DUID
+ std::vector<Pkt6::RelayInfo> relays_; // vector of relays from pkt
+ IOAddress exp_address_; // expected lease address
+ };
+
+ // Test scenarios.
+ std::vector<Scenario> scenarios {
+ {
+ "create client one without relays",
+ duid1_,
+ {},
+ duid1_addr_
+ },
+ {
+ "renew client one without relays",
+ DuidPtr(),
+ {},
+ duid1_addr_
+ },
+ {
+ "create client two with relays",
+ duid2_,
+ { relay1_, relay2_ },
+ duid2_addr_
+ },
+ {
+ "renew client two with relays",
+ DuidPtr(),
+ { relay1_, relay2_ },
+ duid2_addr_
+ }
+ };
+
+ // All of the scenarios require storage to be disabled.
+ subnet_->setStoreExtendedInfo(false);
+
+ // Iterate over the test scenarios.
+ DuidPtr current_duid;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ Pkt6Ptr pkt;
+ if (scenario.duid_) {
+ current_duid = scenario.duid_;
+ pkt.reset(new Pkt6(DHCPV6_REQUEST, 1234));
+ } else {
+ pkt.reset(new Pkt6(DHCPV6_RENEW, 1234));
+ }
+
+ // Set packet relay vector from the scenario.
+ pkt->relay_info_ = scenario.relays_;
+
+ // Create the context;
+ AllocEngine::ClientContext6 ctx(subnet_, current_duid, false, false, "", false, pkt);
+
+ // Create or renew the lease.
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ(scenario.exp_address_, lease->addr_);
+
+ // Verify the lease had no user context content.
+ ASSERT_FALSE(lease->getContext());
+ }
+}
+
+// Verifies that the extended data (RelayInfos for now) is
+// added to a V6 lease when an expired lease is reused and
+// store-extended-info is true. We don't bother testing the
+// disabled case as this is tested thoroughly elsewhere.
+TEST_F(AllocEngine6ExtendedInfoTest, reuseExpiredLease6) {
+ // Create one subnet with a pool holding one address.
+ IOAddress addr("2001:db8:1::ad");
+ initSubnet(IOAddress("2001:db8:1::"), addr, addr);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ // Create an expired lease for duid1_.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid1_, 1234,
+ 501, 502, subnet_->getID(), HWAddrPtr()));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // Asking specifically for this address with zero lifetimes
+ AllocEngine::ClientContext6 ctx(subnet_, duid2_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 5678)));
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr, 128, 0, 0);
+
+ // Add a relay to the packet relay vector.
+ ctx.query_->relay_info_.push_back(relay1_);
+
+ // Enable extended info storage.
+ subnet_->setStoreExtendedInfo(true);
+
+ // Reuse the expired lease.
+ EXPECT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+
+ // Now let's verify that the extended info is in the user-context.
+ ASSERT_TRUE(lease->getContext());
+ std::string exp_content_json =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
+ " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }";
+ ConstElementPtr exp_context;
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(exp_content_json))
+ << "invalid exp_context_json_, test is broken";
+ ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
+ << "expected: " << *(exp_context) << std::endl
+ << " actual: " << *(lease->getContext()) << std::endl;
+}
+
+// Checks whether fake allocation does not use the cache feature.
+TEST_F(AllocEngine6Test, solicitNoCache) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease6Ptr original_lease(new Lease6(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for fake allocation..
+ Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was not updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// Checks whether a lease can be reused (request) using cache threshold.
+TEST_F(AllocEngine6Test, requestCacheThreshold6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 33%.
+ subnet_->setCacheThreshold(.33);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease6Ptr original_lease(new Lease6(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
+ EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);
+
+ // Check other lease parameters.
+ EXPECT_TRUE(*lease->duid_ == *duid_);
+ EXPECT_TRUE(ctx.isAllocated(addr));
+
+ // Check the lease was not updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// Checks whether a lease can be reused (renew) using cache threshold.
+TEST_F(AllocEngine6Test, renewCacheThreshold6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease6Ptr original_lease(new Lease6(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
+ EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);
+
+ // Check other lease parameters.
+ EXPECT_TRUE(*lease->duid_ == *duid_);
+ EXPECT_TRUE(ctx.isAllocated(prefix, prefixlen));
+
+ // Check the lease was not updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// Checks whether a lease can be reused (request) using cache max age.
+TEST_F(AllocEngine6Test, requestCacheMaxAge6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the max age to 150.
+ subnet_->setCacheMaxAge(150);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease6Ptr original_lease(new Lease6(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
+ EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);
+
+ // Check other lease parameters.
+ EXPECT_TRUE(*lease->duid_ == *duid_);
+ EXPECT_TRUE(ctx.isAllocated(addr));
+
+ // Check the lease was not updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// Checks whether a lease can be reused (renew) using cache max age.
+TEST_F(AllocEngine6Test, renewCacheMaxAge6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the max age to 150.
+ subnet_->setCacheMaxAge(150);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease6Ptr original_lease(new Lease6(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
+ EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);
+
+ // Check other lease parameters.
+ EXPECT_TRUE(*lease->duid_ == *duid_);
+ EXPECT_TRUE(ctx.isAllocated(prefix, prefixlen));
+
+ // Check the lease was not updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// Checks whether a lease can be reused (request) using both cache threshold
+// and max age.
+TEST_F(AllocEngine6Test, requestCacheBoth6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ // Set the max age to 150.
+ subnet_->setCacheMaxAge(150);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease6Ptr original_lease(new Lease6(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
+ EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);
+
+ // Check other lease parameters.
+ EXPECT_TRUE(*lease->duid_ == *duid_);
+ EXPECT_TRUE(ctx.isAllocated(addr));
+
+ // Check the lease was not updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// Checks whether a lease can be reused (renew) using both cache threshold
+// and max age.
+TEST_F(AllocEngine6Test, renewCacheBoth6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ // Set the max age to 150.
+ subnet_->setCacheMaxAge(150);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ // Copy the lease, so as it can be compared with.
+ Lease6Ptr original_lease(new Lease6(*lease));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was reused.
+ time_t age = lease->cltt_ - now;
+ EXPECT_GE(age, 100);
+ EXPECT_LE(age, 110);
+ EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
+ EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);
+
+ // Check other lease parameters.
+ EXPECT_TRUE(*lease->duid_ == *duid_);
+ EXPECT_TRUE(ctx.isAllocated(prefix, prefixlen));
+
+ // Check the lease was not updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(original_lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (request) using too small
+// cache threshold.
+TEST_F(AllocEngine6Test, requestCacheBadThreshold6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 10%.
+ subnet_->setCacheThreshold(.1);
+
+ // Set the max age to 150.
+ subnet_->setCacheMaxAge(150);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) using too small
+// cache threshold.
+TEST_F(AllocEngine6Test, renewCacheBadThreshold6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 10%.
+ subnet_->setCacheThreshold(.1);
+
+ // Set the max age to 150.
+ subnet_->setCacheMaxAge(150);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (request) using too small
+// cache max age.
+TEST_F(AllocEngine6Test, requestCacheBadMaxAge6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ // Set the max age to 50.
+ subnet_->setCacheMaxAge(50);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) using too small
+// cache max age.
+TEST_F(AllocEngine6Test, renewCacheBadMaxAge6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ // Set the max age to 50.
+ subnet_->setCacheMaxAge(50);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) when the valid
+// lifetime was reduced.
+// This works only when the lifetime is recomputed.
+TEST_F(AllocEngine6Test, renewCacheReducedValid6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set valid lifetime to 200.
+ subnet_->setValid(200);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) when the preferred
+// lifetime was reduced.
+// This works only when the lifetime is recomputed.
+TEST_F(AllocEngine6Test, renewCacheReducedPreferred6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set preferred lifetime to 100.
+ subnet_->setPreferred(100);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (request) when DDNS parameter changed.
+TEST_F(AllocEngine6Test, requestCacheFwdDDNS6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, true, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) when DDNS parameter changed.
+TEST_F(AllocEngine6Test, renewCacheFwdDDNS6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, true, false, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (request) when DDNS parameter changed.
+TEST_F(AllocEngine6Test, requestCacheRevDDNS6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID()));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, true, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) when DDNS parameter changed.
+TEST_F(AllocEngine6Test, renewCacheRevDDNS6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, true, "", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (request) when hostname changed.
+TEST_F(AllocEngine6Test, requestCacheHostname6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress addr("2001:db8:1::15");
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ false, false, "foo"));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for request.
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "bar", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(addr);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr, lease->addr_);
+ EXPECT_EQ(128, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+ EXPECT_EQ("bar", lease->hostname_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) when hostname changed.
+TEST_F(AllocEngine6Test, renewCacheHostname6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Set the threshold to 25%.
+ subnet_->setCacheThreshold(.25);
+
+ IOAddress prefix("2001:db8:1:2::");
+ uint8_t prefixlen = 80;
+ time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+ 300, 400, subnet_->getID(),
+ false, false, "foo",
+ HWAddrPtr(), prefixlen));
+ lease->cltt_ = now;
+ ASSERT_FALSE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Create a context for renew.
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "bar", false,
+ query);
+ ctx.currentIA().type_ = Lease::TYPE_PD;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(prefix, prefixlen);
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(prefix, lease->addr_);
+ EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+ // The lease was not reused.
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+ EXPECT_EQ("bar", lease->hostname_);
+
+ // Check the lease was updated in the database.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ detailCompareLease(lease, from_mgr);
+}
+
+// Verifies that AllocEngine::getLifetimes6() returns the appropriate
+// valid lifetime value based on the context content.
+TEST_F(AllocEngine6Test, getValidLifetime) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Let's make three classes, two with valid-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+
+ ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
+ Triplet<uint32_t> valid_one(50, 100, 150);
+ class_def->setValid(valid_one);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("valid_two", ExpressionPtr()));
+ Triplet<uint32_t>valid_two(200, 250, 300);
+ class_def->setValid(valid_two);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("valid_unspec", ExpressionPtr()));
+ dictionary->addClass(class_def);
+
+ // Commit our class changes.
+ CfgMgr::instance().commit();
+
+ // Update the subnet's triplet to something more useful.
+ subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
+
+ // Describes a test scenario.
+ struct Scenario {
+ std::string desc_; // descriptive text for logging
+ std::vector<std::string> classes_; // class list of assigned classes
+ uint32_t requested_lft_; // use as option 51 is > 0
+ uint32_t exp_valid_; // expected lifetime
+ };
+
+ // Scenarios to test.
+ std::vector<Scenario> scenarios = {
+ {
+ "no classes, no hint",
+ {},
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "no classes, hint",
+ {},
+ subnet_->getValid().getMin() + 50,
+ subnet_->getValid().getMin() + 50
+ },
+ {
+ "no classes, hint too small",
+ {},
+ subnet_->getValid().getMin() - 50,
+ subnet_->getValid().getMin()
+ },
+ {
+ "no classes, hint too big",
+ {},
+ subnet_->getValid().getMax() + 50,
+ subnet_->getValid().getMax()
+ },
+ {
+ "class unspecified, no hint",
+ { "valid_unspec" },
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "from last class, no hint",
+ { "valid_unspec", "valid_one" },
+ 0,
+ valid_one.get()
+ },
+ {
+ "from first class, no hint",
+ { "valid_two", "valid_one" },
+ 0,
+ valid_two.get()
+ },
+ {
+ "class plus hint",
+ { "valid_one" },
+ valid_one.getMin() + 25,
+ valid_one.getMin() + 25
+ },
+ {
+ "class plus hint too small",
+ { "valid_one" },
+ valid_one.getMin() - 25,
+ valid_one.getMin()
+ },
+ {
+ "class plus hint too big",
+ { "valid_one" },
+ valid_one.getMax() + 25,
+ valid_one.getMax()
+ }
+ };
+
+ // Iterate over the scenarios and verify the correct outcome.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ // Create a context;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ ctx.query_->addClass(class_name);
+ }
+
+ // Add hint
+ ctx.currentIA().iaid_ = iaid_;
+
+ // prefix, prefixlen, preferred, valid
+ ctx.currentIA().addHint(IOAddress("::"), 128, 0, scenario.requested_lft_);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
+ }
+ }
+}
+
+// Verifies that AllocEngine::getLifetimes6() returns the appropriate
+// valid lifetime value based on the context content.
+TEST_F(AllocEngine6Test, getTemplateClassValidLifetime) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Let's make three classes, two with valid-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ExpressionPtr match_expr;
+ ExpressionParser parser;
+
+ ElementPtr test_cfg = Element::create("'valid_one_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr));
+ Triplet<uint32_t> valid_one(50, 100, 150);
+ class_def->setValid(valid_one);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_two_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_two", match_expr));
+ Triplet<uint32_t>valid_two(200, 250, 300);
+ class_def->setValid(valid_two);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'valid_unspec_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr));
+ dictionary->addClass(class_def);
+
+ // Commit our class changes.
+ CfgMgr::instance().commit();
+
+ // Update the subnet's triplet to something more useful.
+ subnet_->setValid(Triplet<uint32_t>(500, 1000, 1500));
+
+ // Describes a test scenario.
+ struct Scenario {
+ std::string desc_; // descriptive text for logging
+ std::vector<std::string> classes_; // class list of assigned classes
+ uint32_t requested_lft_; // use as option 51 is > 0
+ uint32_t exp_valid_; // expected lifetime
+ };
+
+ // Scenarios to test.
+ std::vector<Scenario> scenarios = {
+ {
+ "no classes, no hint",
+ {},
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "no classes, hint",
+ {},
+ subnet_->getValid().getMin() + 50,
+ subnet_->getValid().getMin() + 50
+ },
+ {
+ "no classes, hint too small",
+ {},
+ subnet_->getValid().getMin() - 50,
+ subnet_->getValid().getMin()
+ },
+ {
+ "no classes, hint too big",
+ {},
+ subnet_->getValid().getMax() + 50,
+ subnet_->getValid().getMax()
+ },
+ {
+ "class unspecified, no hint",
+ { "valid_unspec" },
+ 0,
+ subnet_->getValid()
+ },
+ {
+ "from last class, no hint",
+ { "valid_unspec", "valid_one" },
+ 0,
+ valid_one.get()
+ },
+ {
+ "from first class, no hint",
+ { "valid_two", "valid_one" },
+ 0,
+ valid_two.get()
+ },
+ {
+ "class plus hint",
+ { "valid_one" },
+ valid_one.getMin() + 25,
+ valid_one.getMin() + 25
+ },
+ {
+ "class plus hint too small",
+ { "valid_one" },
+ valid_one.getMin() - 25,
+ valid_one.getMin()
+ },
+ {
+ "class plus hint too big",
+ { "valid_one" },
+ valid_one.getMax() + 25,
+ valid_one.getMax()
+ }
+ };
+
+ // Iterate over the scenarios and verify the correct outcome.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ // Create a context;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ ctx.query_->addClass(class_name);
+ }
+
+ // Add hint
+ ctx.currentIA().iaid_ = iaid_;
+
+ // prefix, prefixlen, preferred, valid
+ ctx.currentIA().addHint(IOAddress("::"), 128, 0, scenario.requested_lft_);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
+ }
+ }
+}
+
+// Verifies that AllocEngine::getLifetimes6() returns the appropriate
+// preferred lifetime value based on the context content.
+TEST_F(AllocEngine6Test, getPreferredLifetime) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Let's make three classes, two with preferred-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+
+ ClientClassDefPtr class_def(new ClientClassDef("preferred_one", ExpressionPtr()));
+ Triplet<uint32_t> preferred_one(50, 100, 150);
+ class_def->setPreferred(preferred_one);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("preferred_two", ExpressionPtr()));
+ Triplet<uint32_t>preferred_two(200, 250, 300);
+ class_def->setPreferred(preferred_two);
+ dictionary->addClass(class_def);
+
+ class_def.reset(new ClientClassDef("preferred_unspec", ExpressionPtr()));
+ dictionary->addClass(class_def);
+
+ // Commit our class changes.
+ CfgMgr::instance().commit();
+
+ // Ensure subnet valid-lft is 400.
+ subnet_->setValid(Triplet<uint32_t>(400, 400, 400));
+
+ // Convenience Triplet values.
+ Triplet<uint32_t> unspecified;
+ Triplet<uint32_t> specified(Triplet<uint32_t>(301, 350, 450));
+
+ // Describes a test scenario.
+ struct Scenario {
+ std::string desc_; // descriptive text for logging
+ std::vector<std::string> classes_; // class list of assigned classes
+ Triplet<uint32_t> subnet_pref_; // subnet's preferred lifetime triplet.
+ uint32_t requested_lft_; // use as option 51 is > 0
+ uint32_t exp_preferred_; // expected lifetime
+ };
+
+ // Scenarios to test.
+ std::vector<Scenario> scenarios = {
+ {
+ "no classes, no hint, subnet specified",
+ {},
+ specified,
+ 0,
+ specified
+ },
+ {
+ "no classes, no hint, subnet unspecified",
+ {},
+ unspecified,
+ 0,
+ (subnet_->getValid() * 5 / 8)
+ },
+ {
+ "no classes, hint",
+ {},
+ specified,
+ subnet_->getPreferred().getMin() + 50,
+ subnet_->getPreferred().getMin() + 50
+ },
+ {
+ "no classes, hint too small",
+ {},
+ specified,
+ specified.getMin() - 50,
+ specified.getMin()
+ },
+ {
+ "no classes, hint too big",
+ {},
+ specified,
+ specified.getMax() + 50,
+ (subnet_->getValid() * 5 / 8)
+ },
+ {
+ "class unspecified, no hint",
+ { "preferred_unspec" },
+ specified,
+ 0,
+ specified
+ },
+ {
+ "from last class, no hint",
+ { "preferred_unspec", "preferred_one" },
+ specified,
+ 0,
+ preferred_one.get()
+ },
+ {
+ "from first class, no hint",
+ { "preferred_two", "preferred_one" },
+ specified,
+ 0,
+ preferred_two.get()
+ },
+ {
+ "class plus hint",
+ { "preferred_one" },
+ specified,
+ preferred_one.getMin() + 25,
+ preferred_one.getMin() + 25
+ },
+ {
+ "class plus hint too small",
+ { "preferred_one" },
+ specified,
+ preferred_one.getMin() - 25,
+ preferred_one.getMin()
+ },
+ {
+ "class plus hint too big",
+ { "preferred_one" },
+ specified,
+ preferred_one.getMax() + 25,
+ preferred_one.getMax()
+ }
+ };
+
+ // Iterate over the scenarios and verify the correct outcome.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ // Set the subnet's preferred-lifetime triplet.
+ subnet_->setPreferred(scenario.subnet_pref_);
+
+ // Create a context;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX);
+ subclass += class_name;
+ subclass += "_value";
+ ctx.query_->addSubClass(class_name, subclass);
+ }
+
+ // Add hint
+ ctx.currentIA().iaid_ = iaid_;
+
+ // prefix, prefixlen, preferred, valid
+ ctx.currentIA().addHint(IOAddress("::"), 128, scenario.requested_lft_, 0);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->preferred_lft_, scenario.exp_preferred_);
+ }
+ }
+}
+
+// Verifies that AllocEngine::getLifetimes6() returns the appropriate
+// preferred lifetime value based on the context content.
+TEST_F(AllocEngine6Test, getTemplateClassPreferredLifetime) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Let's make three classes, two with preferred-lifetime and one without,
+ // and add them to the dictionary.
+ ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();
+ ExpressionPtr match_expr;
+ ExpressionParser parser;
+
+ ElementPtr test_cfg = Element::create("'preferred_one_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ ClientClassDefPtr class_def(new TemplateClientClassDef("preferred_one", match_expr));
+ Triplet<uint32_t> preferred_one(50, 100, 150);
+ class_def->setPreferred(preferred_one);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'preferred_two_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("preferred_two", match_expr));
+ Triplet<uint32_t>preferred_two(200, 250, 300);
+ class_def->setPreferred(preferred_two);
+ dictionary->addClass(class_def);
+
+ test_cfg = Element::create("'preferred_unspec_value'");
+ parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING);
+
+ class_def.reset(new TemplateClientClassDef("preferred_unspec", match_expr));
+ dictionary->addClass(class_def);
+
+ // Commit our class changes.
+ CfgMgr::instance().commit();
+
+ // Update the subnet's triplet to something more useful. Note max is
+ // intentionally larger than valid-lft.
+ subnet_->setPreferred(Triplet<uint32_t>(301, 350, 450));
+
+ // Describes a test scenario.
+ struct Scenario {
+ std::string desc_; // descriptive text for logging
+ std::vector<std::string> classes_; // class list of assigned classes
+ uint32_t requested_lft_; // use as option 51 is > 0
+ uint32_t exp_preferred_; // expected lifetime
+ };
+
+ // Scenarios to test.
+ std::vector<Scenario> scenarios = {
+ {
+ "no classes, no hint",
+ {},
+ 0,
+ subnet_->getPreferred()
+ },
+ {
+ "no classes, hint",
+ {},
+ subnet_->getPreferred().getMin() + 50,
+ subnet_->getPreferred().getMin() + 50
+ },
+ {
+ "no classes, hint too small",
+ {},
+ subnet_->getPreferred().getMin() - 50,
+ subnet_->getPreferred().getMin()
+ },
+ {
+ "no classes, hint too big",
+ {},
+ subnet_->getPreferred().getMax() + 50,
+ (subnet_->getValid() * 5 / 8)
+ },
+ {
+ "class unspecified, no hint",
+ { "preferred_unspec" },
+ 0,
+ subnet_->getPreferred()
+ },
+ {
+ "from last class, no hint",
+ { "preferred_unspec", "preferred_one" },
+ 0,
+ preferred_one.get()
+ },
+ {
+ "from first class, no hint",
+ { "preferred_two", "preferred_one" },
+ 0,
+ preferred_two.get()
+ },
+ {
+ "class plus hint",
+ { "preferred_one" },
+ preferred_one.getMin() + 25,
+ preferred_one.getMin() + 25
+ },
+ {
+ "class plus hint too small",
+ { "preferred_one" },
+ preferred_one.getMin() - 25,
+ preferred_one.getMin()
+ },
+ {
+ "class plus hint too big",
+ { "preferred_one" },
+ preferred_one.getMax() + 25,
+ preferred_one.getMax()
+ }
+ };
+
+ // Iterate over the scenarios and verify the correct outcome.
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ // Create a context;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
+ Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
+ // Add client classes (if any)
+ for (auto class_name : scenario.classes_) {
+ string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX);
+ subclass += class_name;
+ subclass += "_value";
+ ctx.query_->addSubClass(class_name, subclass);
+ }
+
+ // Add hint
+ ctx.currentIA().iaid_ = iaid_;
+
+ // prefix, prefixlen, preferred, valid
+ ctx.currentIA().addHint(IOAddress("::"), 128, scenario.requested_lft_, 0);
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->preferred_lft_, scenario.exp_preferred_);
+ }
+ }
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc
new file mode 100644
index 0000000..6e3a7cb
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/alloc_engine_expiration_unittest.cc
@@ -0,0 +1,2329 @@
+// Copyright (C) 2015-2023 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 <dhcp/duid.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <hooks/hooks_manager.h>
+#include <stats/stats_mgr.h>
+#include <gtest/gtest.h>
+#include <boost/static_assert.hpp>
+#include <functional>
+#include <iomanip>
+#include <sstream>
+#include <time.h>
+#include <unistd.h>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::dhcp_ddns;
+using namespace isc::hooks;
+using namespace isc::stats;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Number of leases to be initialized for each test.
+///
+/// This value is expected by some of the tests to be multiple
+/// of 10.
+const unsigned int TEST_LEASES_NUM = 100;
+
+/// @brief Structure wrapping a lower limit within the collection
+/// of leases.
+///
+/// We're using this structure rather than a size_t value directly
+/// to make API of the test fixture class more readable, i.e. the
+/// struct name indicates the purpose of the value being held.
+struct LowerBound {
+ /// @brief Constructor.
+ ///
+ /// @param lower_bound Integer value wrapped by the structure.
+ explicit LowerBound(const size_t lower_bound)
+ : lower_bound_(lower_bound) { };
+
+ /// @brief Operator returning the size_t value wrapped.
+ operator size_t() const {
+ return (lower_bound_);
+ }
+
+ /// @brief Value wrapped in the structure.
+ size_t lower_bound_;
+};
+
+/// @brief Structure wrapping an upper limit within the collection
+/// of leases.
+///
+/// We're using this structure rather than a size_t value directly
+/// to make API of the test fixture class more readable, i.e. the
+/// struct name indicates the purpose of the value being held.
+struct UpperBound {
+ /// @brief Constructor.
+ ///
+ /// @param lower_bound Integer value wrapped by the structure.
+ explicit UpperBound(const size_t upper_bound)
+ : upper_bound_(upper_bound) { };
+
+ /// @brief Operator returning the size_t value wrapped.
+ operator size_t() const {
+ return (upper_bound_);
+ }
+
+ /// @brief Value wrapped in the structure.
+ size_t upper_bound_;
+};
+
+/// @brief List holding addresses for executed callouts.
+std::list<IOAddress> callouts_;
+
+/// @brief Callout argument name for expired lease.
+std::string callout_argument_name("lease4");
+
+/// @brief Base test fixture class for the lease reclamation routines in the
+/// @c AllocEngine.
+///
+/// This class implements infrastructure for testing leases reclamation
+/// routines. The lease reclamation routine has the following
+/// characteristic:
+/// - it processes multiple leases,
+/// - leases are processed in certain order,
+/// - number of processed leases may be limited by the parameters,
+/// - maximum duration of the lease reclamation routine may be limited,
+/// - reclaimed leases may be marked as reclaimed or deleted,
+/// - DNS records for some of the leases must be removed when the lease
+/// is reclaimed and DNS updates are enabled,
+/// - hooks must be invoked (if installed) for each reclaimed lease
+/// - statistics must be updated to increase the number of reclaimed
+/// leases and decrease the number of allocated leases
+///
+/// The typical test requires many leases to be initialized and stored
+/// in the lease database for the test. The test fixture class creates
+/// these leases upon construction. It is possible to modify these
+/// leases to test various properties of the lease reclamation routine
+/// as listed above. For example: some of the leases may be marked
+/// as expired or hostname may be cleared for some of the leases to
+/// check that DNS updates are not generated for them.
+///
+/// The tests are built around the
+/// @c ExpirationAllocEngineTest::testLeases methods. These methods
+/// verify that the certain operations have been performed by the
+/// lease reclamation routine on selected leases. The leases for which
+/// certain conditions should be met are selected using the "index
+/// algorithms". Various index algorithms are implemented in the
+/// test fixture class as static functions and the algorithm is
+/// selected by passing function pointer to the @c testLeases method.
+///
+/// Examples of index algorithms are:
+/// - evenLeaseIndex(index) - picks even index numbers,
+/// - oddLeaseIndex(index) - picks odd index numbers,
+/// - allLeasesIndexes(index) - picks all index number.
+///
+/// For example, the test may use the @c evenLeaseIndex algorithm
+/// to mark leases with even indexes as expired and then test whether
+/// leases with even indexes have been successfully reclaimed.
+///
+/// The "lease algorithm" verifies if the given lease fulfills the
+/// specific conditions after reclamation. These are the examples of
+/// the lease algorithms:
+/// - leaseExists - lease still exists in the database,
+/// - leaseDoesntExist - lease removed from the database,
+/// - leaseReclaimed - lease exists but has reclaimed status,
+/// - leaseNotReclaimed - lease exists and is not in the reclaimed status,
+/// - leaseDeclined - lease exists and is in declined state,
+/// - dnsUpdateGeneratedForLease - DNS updates generated for lease,
+/// - dnsUpdateNotGeneratedForLease - DNS updates not generated for lease
+///
+/// The combination of index algorithm and lease algorithm allows for
+/// verifying that the whole sets of leases in the lease database fulfill
+/// certain conditions. For example, it is possible to verify that
+/// after lease reclamation leases with even indexes have state set to
+/// "expired-reclaimed".
+///
+/// See @c ExpirationAllocEngineTest::testLeases for further details.
+///
+/// @todo These tests should be extended to cover the following cases:
+/// - declined leases - declined leases expire and should be removed
+/// from the lease database by the lease reclamation routine. See
+/// ticket #3976.
+template<typename LeasePtrType>
+class ExpirationAllocEngineTest : public ::testing::Test {
+public:
+
+ /// @brief Type definition for the lease algorithm.
+ typedef std::function<bool (const LeasePtrType)> LeaseAlgorithmFun;
+ /// @brief type definition for the lease index algorithm.
+ typedef std::function<bool (const size_t)> IndexAlgorithmFun;
+
+ /// @brief Constructor.
+ ///
+ /// Clears configuration, creates new lease manager and allocation engine
+ /// instances.
+ ExpirationAllocEngineTest(const std::string& lease_mgr_params) {
+ // Clear configuration.
+ CfgMgr::instance().clear();
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(cfg);
+
+ // Remove all statistics.
+ StatsMgr::instance().resetAll();
+
+ // Set the 'reclaimed-leases' statistics to '0'. This statistics
+ // is used by some tests to verify that the leases reclamation
+ // routine has been called.
+ StatsMgr::instance().setValue("reclaimed-leases",
+ static_cast<int64_t>(0));
+
+ // Create lease manager.
+ LeaseMgrFactory::create(lease_mgr_params);
+
+ // Create allocation engine instance.
+ engine_.reset(new AllocEngine(100));
+ }
+
+ /// @brief Destructor
+ ///
+ /// Stops D2 client (if running), clears configuration and removes
+ /// an instance of the lease manager.
+ virtual ~ExpirationAllocEngineTest() {
+ // Stop D2 client if running and remove all queued name change
+ // requests.
+ D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ if (mgr.amSending()) {
+ mgr.stopSender();
+ mgr.clearQueue();
+ }
+
+ // Clear configuration.
+ CfgMgr::instance().clear();
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(cfg);
+
+ // Remove all statistics.
+ StatsMgr::instance().resetAll();
+
+ // Kill lease manager.
+ LeaseMgrFactory::destroy();
+
+ // Remove callouts executed.
+ callouts_.clear();
+
+ // Unload libraries.
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture dtor) unloadLibraries failed" << endl;
+ }
+ }
+
+ /// @brief Starts D2 client.
+ void enableDDNS() const {
+ // Start DDNS and assign no-op error handler.
+ D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ cfg->enableUpdates(true);
+ mgr.setD2ClientConfig(cfg);
+ mgr.startSender(std::bind(&ExpirationAllocEngineTest::d2ErrorHandler, ph::_1, ph::_2));
+ }
+
+ /// @brief No-op error handler for the D2 client.
+ static void d2ErrorHandler(const dhcp_ddns::NameChangeSender::Result,
+ dhcp_ddns::NameChangeRequestPtr&) {
+ }
+
+ /// @brief Marks a lease as expired.
+ ///
+ /// @param lease_index Lease index. Must be between 0 and
+ /// @c TEST_LEASES_NUM.
+ /// @param secs Offset of the expiration time since now. For example
+ /// a value of 2 would set the lease expiration time to 2 seconds ago.
+ void expire(const uint16_t lease_index, const time_t secs) {
+ ASSERT_GT(leases_.size(), lease_index);
+ // We set the expiration time indirectly by modifying the cltt value.
+ leases_[lease_index]->cltt_ = time(NULL) - secs -
+ leases_[lease_index]->valid_lft_;
+ ASSERT_NO_THROW(updateLease(lease_index));
+ }
+
+ /// @brief Changes the owner of a lease.
+ ///
+ /// This method changes the owner of the lease. It must be implemented in
+ /// the derived classes to update the unique identifier(s) in the lease to
+ /// point to a different client.
+ ///
+ /// @param lease_index Lease index. Must be between 0 and
+ /// @c TEST_LEASES_NUM.
+ virtual void transferOwnership(const uint16_t lease_index) = 0;
+
+ /// @brief Marks lease as expired-reclaimed.
+ ///
+ /// @param lease_index Lease index. Must be between 0 and
+ /// @c TEST_LEASES_NUM.
+ /// @param secs Offset of the expiration time since now. For example
+ /// a value of 2 would set the lease expiration time to 2 seconds ago.
+ void reclaim(const uint16_t lease_index, const time_t secs) {
+ ASSERT_GT(leases_.size(), lease_index);
+ leases_[lease_index]->cltt_ = time(NULL) - secs -
+ leases_[lease_index]->valid_lft_;
+ leases_[lease_index]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ ASSERT_NO_THROW(updateLease(lease_index));
+ }
+
+ /// @brief Declines specified lease
+ ///
+ /// Sets specified lease to declined state and sets its probation-period.
+ /// @param lease_index Index of the lease.
+ /// @param probation_time value of probation period to be set (in seconds)
+ void decline(const uint16_t lease_index, const time_t probation_time) {
+ ASSERT_GT(leases_.size(), lease_index);
+ leases_[lease_index]->decline(probation_time);
+ ASSERT_NO_THROW(updateLease(lease_index));
+ }
+
+ /// @brief Updates lease in the lease database.
+ ///
+ /// @param lease_index Index of the lease.
+ virtual void updateLease(const unsigned int lease_index) = 0;
+
+ /// @brief Retrieves lease from the database.
+ ///
+ /// @param lease_index Index of the lease.
+ virtual LeasePtrType getLease(const unsigned int lease_index) const = 0;
+
+ /// @brief Sets subnet id for a lease.
+ ///
+ /// It also updates statistics of assigned leases in the stats manager.
+ ///
+ /// @param lease_index Lease index.
+ /// @param id New subnet id.
+ virtual void setSubnetId(const uint16_t lease_index, const SubnetID& id) = 0;
+
+ /// @brief Wrapper method running lease reclamation routine.
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in seconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ virtual void reclaimExpiredLeases(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease) = 0;
+
+ /// @brief Wrapper method for removing expired-reclaimed leases.
+ ///
+ /// @param secs The minimum amount of time, expressed in seconds,
+ /// for the lease to be left in the "expired-reclaimed" state
+ /// before it can be removed.
+ virtual void deleteExpiredReclaimedLeases(const uint32_t secs) = 0;
+
+ /// @brief Test selected leases using the specified algorithms.
+ ///
+ /// This function picks leases from the range of 0 thru
+ /// @c TEST_LEASES_NUM and selects the ones to be verified using the
+ /// specified index algorithm. Selected leases are tested using
+ /// the specified lease algorithm.
+ ///
+ /// @param lease_algorithm Pointer to the lease algorithm function.
+ /// @param index_algorithm Pointer to the index algorithm function.
+ bool testLeases(const LeaseAlgorithmFun& lease_algorithm,
+ const IndexAlgorithmFun& index_algorithm) const {
+ // No limits are specified, so test all leases in the range of
+ // 0 .. TEST_LEASES_NUM.
+ return (testLeases(lease_algorithm, index_algorithm, LowerBound(0),
+ UpperBound(TEST_LEASES_NUM)));
+ }
+
+
+ /// @brief Test selected leases using the specified algorithms.
+ ///
+ /// This function picks leases from the range of @c lower_bound
+ /// thru @c upper_bound and selects the ones to be verified using the
+ /// specified index algorithm. Selected leases are tested using the
+ /// specified lease algorithm.
+ ///
+ /// @param lease_algorithm Pointer to the lease algorithm function.
+ /// @param index_algorithm Pointer to the index algorithm function.
+ /// @param lower_bound First index in the range.
+ /// @param upper_bound Last + 1 index in the range.
+ bool testLeases(const LeaseAlgorithmFun& lease_algorithm,
+ const IndexAlgorithmFun& index_algorithm,
+ const LowerBound& lower_bound,
+ const UpperBound& upper_bound) const {
+ // Select leases between the lower_bound and upper_bound.
+ for (size_t i = lower_bound; i < upper_bound; ++i) {
+ // Get the lease from the lease database.
+ LeasePtrType lease = getLease(i);
+ // index_algorithm(i) checks if the lease should be checked.
+ // If so, check if the lease_algorithm indicates that the
+ // lease fulfills a given condition, e.g. is present in the
+ // database. If not, return false.
+ if (index_algorithm(i) && !lease_algorithm(lease)) {
+ return (false);
+ }
+ }
+ // All leases checked, so return true.
+ return (true);
+ }
+
+ /// @brief Index algorithm selecting even indexes.
+ ///
+ /// @param index Lease index.
+ /// @return true if index is an even number.
+ static bool evenLeaseIndex(const size_t index) {
+ return (index % 2 == 0);
+ }
+
+ /// @brief Index algorithm selecting odd indexes.
+ ///
+ /// @param index Lease index.
+ /// @return true if index is an odd number.
+ static bool oddLeaseIndex(const size_t index) {
+ return (!evenLeaseIndex(index));
+ }
+
+ /// @brief Index algorithm selecting all indexes.
+ ///
+ /// @param index Lease index.
+ /// @return true if the index is in the range of [0 .. TEST_LEASES_NUM).
+ static bool allLeaseIndexes(const size_t index) {
+ return (index < TEST_LEASES_NUM);
+ }
+
+ /// @brief Lease algorithm checking if lease exists.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if lease pointer is non-null.
+ static bool leaseExists(const LeasePtrType& lease) {
+ return (static_cast<bool>(lease));
+ }
+
+ /// @brief Lease algorithm checking if lease doesn't exist.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if lease pointer is null.
+ static bool leaseDoesntExist(const LeasePtrType& lease) {
+ return (static_cast<bool>(!lease));
+ }
+
+ /// @brief Lease algorithm checking if lease state is expired-reclaimed.
+ ///
+ /// This algorithm also checks that the FQDN information has been removed
+ /// from the lease.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if lease state is "expired-reclaimed" and the FQDN
+ /// information has been removed from the lease.
+ static bool leaseReclaimed(const LeasePtrType& lease) {
+ return (lease && lease->stateExpiredReclaimed() &&
+ lease->hostname_.empty() && !lease->fqdn_fwd_ &&
+ !lease->fqdn_rev_ && !lease->getContext());
+ }
+
+ /// @brief Lease algorithm checking if lease state is Declined.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if lease state is "declined".
+ static bool leaseDeclined(const LeasePtrType& lease) {
+ return (lease && lease->stateDeclined());
+ }
+
+ /// @brief Lease algorithm checking if lease state is not
+ /// expired-reclaimed.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if lease state is not "expired-reclaimed".
+ static bool leaseNotReclaimed(const LeasePtrType& lease) {
+ return (lease && !lease->stateExpiredReclaimed());
+ }
+
+ /// @brief Lease algorithm checking if removal name change request
+ /// has been generated for lease.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if NCR has been generated for the lease.
+ static bool dnsUpdateGeneratedForLease(const LeasePtrType& lease) {
+ try {
+ return (static_cast<bool>(getNCRForLease(lease)));
+
+ } catch (...) {
+ // If error occurred, treat it as no match.
+ return (false);
+ }
+ }
+
+ /// @brief Lease algorithm checking if removal name change request
+ /// hasn't been generated for lease.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if NCR has not been generated for the lease.
+ static bool dnsUpdateNotGeneratedForLease(const LeasePtrType& lease) {
+ try {
+ // Iterate over the generated name change requests and try
+ // to find the match with our lease (using IP address). If
+ D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ for (size_t i = 0; i < mgr.getQueueSize(); ++i) {
+ const NameChangeRequestPtr& ncr = mgr.peekAt(i);
+ // If match found, we treat it as if the test fails
+ // because we expected no NCR.
+ if (ncr->getIpAddress() == lease->addr_.toText()) {
+ return (false);
+ }
+ }
+ } catch (...) {
+ return (false);
+ }
+
+ // No match found, so we're good.
+ return (true);
+ }
+
+ /// @brief Lease algorithm checking if callout has been executed for
+ /// the expired lease.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if callout has been executed for the lease.
+ static bool leaseCalloutExecuted(const LeasePtrType& lease) {
+ return (std::find(callouts_.begin(), callouts_.end(), lease->addr_) !=
+ callouts_.end());
+ }
+
+ /// @brief Lease algorithm checking if callout hasn't been executed for
+ /// the expired lease.
+ ///
+ /// @param lease Pointer to lease.
+ /// @return true if callout hasn't been executed for the lease.
+ static bool leaseCalloutNotExecuted(const LeasePtrType& lease) {
+ return (!leaseCalloutExecuted(lease));
+ }
+
+ /// @brief Implements "lease{4,6}_expire" callout.
+ ///
+ /// @param callout_handle Callout handle.
+ /// @return Zero.
+ static int leaseExpireCallout(CalloutHandle& callout_handle) {
+ LeasePtrType lease;
+ callout_handle.getArgument(callout_argument_name, lease);
+ bool remove_lease = true;
+ callout_handle.getArgument("remove_lease", remove_lease);
+
+ // Check if the remove_lease is set to false and assume that the callout
+ // has been successfully executed if it is. This is mainly to test
+ // that the lease reclamation routine sets this value at all.
+ if (!remove_lease) {
+ callouts_.push_back(lease->addr_);
+ }
+
+ return (0);
+ }
+
+ /// @brief Implements "lease{4,6}_expire callout returning skip flag.
+ ///
+ /// @param callout_handle Callout handle.
+ /// @return Zero.
+ static int leaseExpireWithSkipCallout(CalloutHandle& callout_handle) {
+ leaseExpireCallout(callout_handle);
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
+ /// @brief Implements "lease{4,6}_expire callout, which lasts at least
+ /// 40ms.
+ ///
+ /// This callout is useful to test scenarios where the reclamation of the
+ /// lease needs to take a known amount of time. If the callout is installed
+ /// it will take at least 40ms for each lease. It is then possible to calculate
+ /// the approximate time that the reclamation of all leases would take and
+ /// test that the timeouts for the leases' reclamation work as expected.
+ ///
+ /// The value of 40ms is relatively high, but it has been selected to
+ /// mitigate the problems with usleep on some virtual machines. On those
+ /// machines the wakeup from usleep may take significant amount of time,
+ /// i.e. usually around 10ms. Thus, the sleep time should be considerably
+ /// higher than this delay.
+ ///
+ /// @param callout_handle Callout handle.
+ /// @return Zero.
+ static int leaseExpireWithDelayCallout(CalloutHandle& callout_handle) {
+ leaseExpireCallout(callout_handle);
+ // Delay the return from the callout by 40ms.
+ usleep(40000);
+
+ return (0);
+ }
+
+ /// @brief Returns removal name change request from the D2 client queue.
+ ///
+ /// @param lease Pointer to the lease to be matched with NCR.
+ ///
+ /// @return null pointer if no match found.
+ static NameChangeRequestPtr getNCRForLease(const LeasePtrType& lease) {
+ // Iterate over the generated name change requests and try
+ // to find the match with our lease (using IP address). If
+ D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+ for (size_t i = 0; i < mgr.getQueueSize(); ++i) {
+ const NameChangeRequestPtr& ncr = mgr.peekAt(i);
+ // If match found, return true.
+ if ((ncr->getIpAddress() == lease->addr_.toText()) &&
+ (ncr->getChangeType() == CHG_REMOVE)) {
+ return (ncr);
+ }
+ }
+ return (NameChangeRequestPtr());
+ }
+
+ /// @brief Returns index of the lease from the address.
+ ///
+ /// This method assumes that leases are ordered from the smallest to
+ /// the highest address, e.g. 10.0.0.0, 10.0.0.1, 10.0.0.2 etc. The
+ /// last two bytes can be used to extract index.
+ ///
+ /// @param address Address.
+ ///
+ /// @return index
+ static uint16_t getLeaseIndexFromAddress(const IOAddress& address) {
+ std::vector<uint8_t> bytes = address.toBytes();
+ std::vector<uint8_t>::reverse_iterator bytes_it = bytes.rbegin();
+ uint16_t index = static_cast<uint16_t>(*bytes_it) |
+ (static_cast<uint16_t>(*(bytes_it + 1)) << 8);
+ return (index);
+ }
+
+ /// @brief Generates hostname for lease index.
+ ///
+ /// Generates hostname in the form of "hostXXXX.example.org", where
+ /// XXXX is a lease index.
+ ///
+ /// @param index Lease index.
+ ///
+ /// @return Generated hostname.
+ static std::string generateHostnameForLeaseIndex(const uint16_t index) {
+ std::ostringstream hostname_s;
+ hostname_s << "host" << std::setw(4) << std::setfill('0')
+ << index << ".example.org";
+ return (hostname_s.str());
+ }
+
+ /// @brief Test that leases can be reclaimed without being removed.
+ void testReclaimExpiredLeasesUpdateState() {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Mark leases with even indexes as expired.
+ if (evenLeaseIndex(i)) {
+ // The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+ }
+
+ // Run leases reclamation routine on all leases. This should result
+ // in setting "expired-reclaimed" state for all leases with even
+ // indexes.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, false));
+
+ // Leases with even indexes should be marked as reclaimed.
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex));
+ // Leases with odd indexes shouldn't be marked as reclaimed.
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
+ }
+
+ /// @brief Test that the leases may be reclaimed by being deleted.
+ void testReclaimExpiredLeasesDelete() {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Mark leases with even indexes as expired.
+ if (evenLeaseIndex(i)) {
+ // The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+ }
+
+ // Run leases reclamation routine on all leases. This should result
+ // in removal of all leases with even indexes.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, true));
+
+ // Leases with odd indexes should be retained and their state
+ // shouldn't be "expired-reclaimed".
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
+ // Leases with even indexes should have been removed.
+ EXPECT_TRUE(testLeases(&leaseDoesntExist, &evenLeaseIndex));
+ }
+
+ /// @brief Test that it is possible to specify the limit for the number
+ /// of reclaimed leases.
+ void testReclaimExpiredLeasesLimit() {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Mark all leases as expired. The higher the index the less
+ // expired the lease.
+ expire(i, 1000 - i);
+ }
+
+ // We will be performing lease reclamation on lease groups of 10.
+ // Hence, it is convenient if the number of test leases is a
+ // multiple of 10.
+ const size_t reclamation_group_size = 10;
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
+
+ // Leases will be reclaimed in groups of 10.
+ for (unsigned int i = reclamation_group_size; i < TEST_LEASES_NUM;
+ i += reclamation_group_size) {
+
+ // Reclaim 10 most expired leases out of TEST_LEASES_NUM. Since
+ // leases are ordered from the most expired to the least expired
+ // this should reclaim leases between 0 and 9, then 10 and 19 etc.
+ ASSERT_NO_THROW(reclaimExpiredLeases(reclamation_group_size,
+ 0, false));
+
+ // Check that leases having all indexes between 0 and 9, 19, 29 etc.
+ // have been reclaimed.
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &allLeaseIndexes,
+ LowerBound(0), UpperBound(i)))
+ << "check failed for i = " << i;
+
+ // Check that all remaining leases haven't been reclaimed.
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes,
+ LowerBound(i), UpperBound(TEST_LEASES_NUM)))
+ << "check failed for i = " << i;
+ }
+ }
+
+ /// @brief Test that DNS updates are generated for the leases for which
+ /// the DNS records exist.
+ void testReclaimExpiredLeasesWithDDNS() {
+ // DNS must be started for the D2 client to accept NCRs.
+ ASSERT_NO_THROW(enableDDNS());
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Expire all leases with even indexes.
+ if (evenLeaseIndex(i)) {
+ // The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+ }
+
+ // Reclaim all expired leases.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, false));
+
+ // Leases with odd indexes shouldn't be reclaimed.
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
+ // Leases with even indexes should be reclaimed.
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex));
+ // DNS updates (removal NCRs) should be generated for leases with even
+ // indexes.
+ EXPECT_TRUE(testLeases(&dnsUpdateGeneratedForLease, &evenLeaseIndex));
+ // DNS updates (removal NCRs) shouldn't be generated for leases with
+ // odd indexes.
+ EXPECT_TRUE(testLeases(&dnsUpdateNotGeneratedForLease, &oddLeaseIndex));
+ }
+
+ /// @brief Test that DNS updates are only generated for the reclaimed
+ /// leases (not for all leases with hostname stored).
+ void testReclaimExpiredLeasesWithDDNSAndLimit() {
+ // DNS must be started for the D2 client to accept NCRs.
+ ASSERT_NO_THROW(enableDDNS());
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Expire only leases with even indexes.
+ if (evenLeaseIndex(i)) {
+ // The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+ }
+
+ const size_t reclamation_group_size = 10;
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM % reclamation_group_size == 0);
+
+ // Leases will be reclaimed in groups of 10
+ for (unsigned int i = 10; i < TEST_LEASES_NUM; i += reclamation_group_size) {
+ // Reclaim 10 most expired leases. Note that the leases with the
+ // higher index are more expired. For example, if the
+ // TEST_LEASES_NUM is equal to 100, the most expired lease will
+ // be 98, then 96, 94 etc.
+ ASSERT_NO_THROW(reclaimExpiredLeases(reclamation_group_size, 0,
+ false));
+
+ // After the first iteration the lower bound is 80, because there
+ // will be 10 the most expired leases in this group: 80, 82, 84,
+ // 86, 88, 90, 92, 94, 96, 98. For subsequent iterations
+ // accordingly.
+ int reclaimed_lower_bound = TEST_LEASES_NUM - 2 * i;
+ // At some point the lower bound will hit the negative value, which
+ // must be corrected to 0.
+ if (reclaimed_lower_bound < 0) {
+ reclaimed_lower_bound = 0;
+ }
+
+ // Leases between the lower bound calculated above and the upper
+ // bound of all leases, and having even indexes should have been
+ // reclaimed.
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex,
+ LowerBound(reclaimed_lower_bound),
+ UpperBound(TEST_LEASES_NUM)))
+ << "check failed for i = " << i;
+
+ // For the same leases we should have generated DNS updates
+ // (removal NCRs).
+ EXPECT_TRUE(testLeases(&dnsUpdateGeneratedForLease, &evenLeaseIndex,
+ LowerBound(reclaimed_lower_bound),
+ UpperBound(TEST_LEASES_NUM)))
+ << "check failed for i = " << i;
+
+ // Leases with odd indexes (falling between the reclaimed ones)
+ // shouldn't have been reclaimed, because they are not expired.
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex,
+ LowerBound(reclaimed_lower_bound),
+ UpperBound(TEST_LEASES_NUM)))
+ << "check failed for i = " << i;
+
+ EXPECT_TRUE(testLeases(&dnsUpdateNotGeneratedForLease,
+ &oddLeaseIndex,
+ LowerBound(reclaimed_lower_bound),
+ UpperBound(TEST_LEASES_NUM)))
+ << "check failed for i = " << i;
+
+
+ // At early stages of iterations, there should be continuous
+ // group of leases (expired and not expired) which haven't been
+ // reclaimed.
+ if (reclaimed_lower_bound > 0) {
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes,
+ LowerBound(0),
+ UpperBound(reclaimed_lower_bound)))
+ << "check failed for i = " << i;
+
+ EXPECT_TRUE(testLeases(&dnsUpdateNotGeneratedForLease,
+ &oddLeaseIndex,
+ LowerBound(0),
+ UpperBound(reclaimed_lower_bound)));
+ }
+ }
+ }
+
+ /// @brief This test verifies that reclamation routine continues if the
+ /// DNS update has failed for some leases.
+ void testReclaimExpiredLeasesInvalidHostname() {
+ // DNS must be started for the D2 client to accept NCRs.
+ ASSERT_NO_THROW(enableDDNS());
+
+ for (size_t i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Generate invalid hostname for every other lease.
+ if (evenLeaseIndex(i)) {
+ // Hostname with two consecutive dots is invalid and may result
+ // in exception if the reclamation routine doesn't protect
+ // against such exceptions.
+ std::ostringstream hostname_s;
+ hostname_s << "invalid-host" << i << "..example.com";
+ leases_[i]->hostname_ = hostname_s.str();
+ ASSERT_NO_THROW(updateLease(i));
+ }
+ // Every lease is expired.
+ expire(i, 10 + i);
+ }
+
+ // Although we know that some hostnames are broken we don't want the
+ // reclamation process to break when it finds a broken record.
+ // It should rather continue to process other leases.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, false));
+
+ // All leases should have been reclaimed. Broken DNS entry doesn't
+ // warrant that we don't reclaim the lease.
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &allLeaseIndexes));
+ // The routine should not generate DNS updates for the leases with
+ // broken hostname.
+ EXPECT_TRUE(testLeases(&dnsUpdateNotGeneratedForLease,
+ &evenLeaseIndex));
+ // But it should generate DNS updates for the leases with the correct
+ // hostname.
+ EXPECT_TRUE(testLeases(&dnsUpdateGeneratedForLease, &oddLeaseIndex));
+ }
+
+ /// @brief This test verifies that callouts are executed for each expired
+ /// lease when installed.
+ void testReclaimExpiredLeasesHooks() {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ if (evenLeaseIndex(i)) {
+ expire(i, 1000 - i);
+ }
+ }
+
+ HookLibsCollection libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install a callout: lease4_expire or lease6_expire.
+ std::ostringstream callout_name;
+ callout_name << callout_argument_name << "_expire";
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ callout_name.str(), leaseExpireCallout));
+
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, false));
+
+ // Callouts should be executed for leases with even indexes and these
+ // leases should be reclaimed.
+ EXPECT_TRUE(testLeases(&leaseCalloutExecuted, &evenLeaseIndex));
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex));
+ // Callouts should not be executed for leases with odd indexes and these
+ // leases should not be reclaimed.
+ EXPECT_TRUE(testLeases(&leaseCalloutNotExecuted, &oddLeaseIndex));
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &oddLeaseIndex));
+ }
+
+ /// @brief This test verifies that callouts are executed for each expired
+ /// lease and that the lease is not reclaimed when skip flag is set.
+ void testReclaimExpiredLeasesHooksWithSkip() {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ if (evenLeaseIndex(i)) {
+ expire(i, 1000 - i);
+ }
+ }
+
+ HookLibsCollection libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install a callout: lease4_expire or lease6_expire.
+ std::ostringstream callout_name;
+ callout_name << callout_argument_name << "_expire";
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ callout_name.str(), leaseExpireWithSkipCallout));
+
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, false));
+
+ // Callouts should have been executed for leases with even indexes.
+ EXPECT_TRUE(testLeases(&leaseCalloutExecuted, &evenLeaseIndex));
+ // Callouts should not be executed for leases with odd indexes.
+ EXPECT_TRUE(testLeases(&leaseCalloutNotExecuted, &oddLeaseIndex));
+ // Leases shouldn't be reclaimed because the callout sets the
+ // skip flag for each of them.
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes));
+ }
+
+ /// @brief This test verifies that it is possible to set the timeout for
+ /// the execution of the lease reclamation routine.
+ void testReclaimExpiredLeasesTimeout(const uint16_t timeout) {
+ // Leases are segregated from the most expired to the least expired.
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ expire(i, 2000 - i);
+ }
+
+ HookLibsCollection libraries;
+ HooksManager::loadLibraries(libraries);
+
+ // Install a callout: lease4_expire or lease6_expire. Each callout
+ // takes at least 40ms to run (it uses usleep).
+ std::ostringstream callout_name;
+ callout_name << callout_argument_name << "_expire";
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ callout_name.str(), leaseExpireWithDelayCallout));
+
+ // Reclaim leases with timeout.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, timeout, false));
+
+ // We reclaimed at most (timeout / 40ms) leases.
+ const uint16_t theoretical_reclaimed = static_cast<uint16_t>(timeout / 40);
+
+ // The actual number of leases reclaimed is likely to be lower than
+ // the theoretical number. For low theoretical number the adjusted
+ // number is always 1. For higher number, it will be 10 less than the
+ // theoretical number.
+ const uint16_t adjusted_reclaimed = (theoretical_reclaimed > 10 ?
+ theoretical_reclaimed - 10 : 1);
+
+ EXPECT_TRUE(testLeases(&leaseCalloutExecuted, &allLeaseIndexes,
+ LowerBound(0), UpperBound(adjusted_reclaimed)));
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &allLeaseIndexes,
+ LowerBound(0), UpperBound(adjusted_reclaimed)));
+ EXPECT_TRUE(testLeases(&leaseCalloutNotExecuted, &allLeaseIndexes,
+ LowerBound(theoretical_reclaimed + 1),
+ UpperBound(TEST_LEASES_NUM)));
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes,
+ LowerBound(theoretical_reclaimed + 1),
+ UpperBound(TEST_LEASES_NUM)));
+ }
+
+ /// @brief This test verifies that expired-reclaimed leases are removed
+ /// from the lease database.
+ void testDeleteExpiredReclaimedLeases() {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Mark leases with even indexes as expired.
+ if (evenLeaseIndex(i)) {
+ // The higher the index, the more expired the lease.
+ reclaim(i, 10 + i);
+ }
+ }
+
+ // Run leases reclamation routine on all leases. This should result
+ // in removal of all leases with even indexes.
+ ASSERT_NO_THROW(deleteExpiredReclaimedLeases(10));
+
+ // Leases with odd indexes shouldn't be removed from the database.
+ EXPECT_TRUE(testLeases(&leaseExists, &oddLeaseIndex));
+ // Leases with even indexes should have been removed.
+ EXPECT_TRUE(testLeases(&leaseDoesntExist, &evenLeaseIndex));
+ }
+
+ /// @brief Test that declined expired leases can be removed.
+ ///
+ /// This method allows controlling remove_leases parameter when calling
+ /// @ref AllocEngine::reclaimExpiredLeases4 or
+ /// @ref AllocEngine::reclaimExpiredLeases6. This should not matter, as
+ /// the address affinity doesn't make sense for declined leases (they don't
+ /// have any useful information in them anymore), so AllocEngine should
+ /// remove them all the time.
+ ///
+ /// @param remove see description above
+ void testReclaimDeclined(bool remove) {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+
+ // Mark leases with even indexes as expired.
+ if (evenLeaseIndex(i)) {
+
+ // Mark lease as declined with 100 seconds of probation-period
+ // (i.e. lease is supposed to be off limits for 100 seconds)
+ decline(i, 100);
+
+ // The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+ }
+
+ // Run leases reclamation routine on all leases. This should result
+ // in removing all leases with status = declined, i.e. all
+ // even leases should be gone.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, remove));
+
+ // Leases with even indexes should not exist in the DB
+ EXPECT_TRUE(testLeases(&leaseDoesntExist, &evenLeaseIndex));
+ }
+
+ /// @brief Test that appropriate statistics are updated when
+ /// declined expired leases are processed by AllocEngine.
+ ///
+ /// This method works for both v4 and v6. Just make sure the correct
+ /// statistic name is passed. This is the name of the assigned addresses,
+ /// that is expected to be decreased once the reclamation procedure
+ /// is complete.
+ ///
+ /// @param stat_name name of the statistic for assigned addresses statistic
+ /// ("assigned-addresses" for both v4 and "assigned-nas" for v6)
+ void testReclaimDeclinedStats(const std::string& stat_name) {
+
+ // Leases by default all belong to subnet_id_ = 1. Let's count the
+ // number of declined leases.
+ int subnet1_cnt = 0;
+ int subnet2_cnt = 0;
+
+ // Let's move all leases to declined,expired state.
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+
+ // Move the lease to declined state
+ decline(i, 100);
+
+ // And expire it, so it will be reclaimed
+ expire(i, 10 + 1);
+
+ // Move every other lease to subnet-id = 2.
+ if (evenLeaseIndex(i)) {
+ subnet1_cnt++;
+ } else {
+ subnet2_cnt++;
+ setSubnetId(i, 2);
+ }
+ }
+
+ StatsMgr& stats_mgr = StatsMgr::instance();
+
+ // Let's set the global statistic. Values are arbitrary and can
+ // be used to easily detect whether a given stat was decreased or
+ // increased. They are sufficiently high compared to number of leases
+ // to avoid any chances of going into negative.
+ stats_mgr.setValue("declined-addresses", static_cast<int64_t>(1000));
+
+ // Let's set global the counter for reclaimed declined addresses.
+ stats_mgr.setValue("reclaimed-declined-addresses",
+ static_cast<int64_t>(2000));
+
+ // And those subnet specific as well
+ stats_mgr.setValue(stats_mgr.generateName("subnet", 1,
+ stat_name), int64_t(1000));
+ stats_mgr.setValue(stats_mgr.generateName("subnet", 2,
+ stat_name), int64_t(2000));
+
+ stats_mgr.setValue(stats_mgr.generateName("subnet", 1,
+ "reclaimed-declined-addresses"), int64_t(10000));
+ stats_mgr.setValue(stats_mgr.generateName("subnet", 2,
+ "reclaimed-declined-addresses"), int64_t(20000));
+
+ stats_mgr.setValue(stats_mgr.generateName("subnet", 1,
+ "declined-addresses"), int64_t(100));
+ stats_mgr.setValue(stats_mgr.generateName("subnet", 2,
+ "declined-addresses"), int64_t(200));
+
+ // Run leases reclamation routine on all leases. This should result
+ // in removal of all leases with even indexes.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, true));
+
+ // Declined-addresses should be decreased from its initial value (1000)
+ // for both recovered addresses from subnet1 and subnet2.
+ testStatistics("declined-addresses", 1000 - subnet1_cnt - subnet2_cnt);
+
+ // The code should bump up global counter for reclaimed declined
+ // addresses.
+ testStatistics("reclaimed-declined-addresses", 2000 + subnet1_cnt + subnet2_cnt);
+
+ // subnet[X].assigned-addresses should go down. Between the time
+ // of DHCPDECLINE(v4)/DECLINE(v6) reception and declined expired lease
+ // reclamation, we count this address as assigned-addresses. We decrease
+ // assigned-addresses(v4)/assigned-nas(v6) when we reclaim the lease,
+ // not when the packet is received. For explanation, see Duplicate
+ // Addresses (DHCPDECLINE support) (v4) or Duplicate Addresses (DECLINE
+ // support) sections in the User's Guide or a comment in
+ // Dhcpv4Srv::declineLease or Dhcpv6Srv::declineLease.
+ testStatistics(stat_name, 1000 - subnet1_cnt, 1);
+ testStatistics(stat_name, 2000 - subnet2_cnt, 2);
+
+ testStatistics("declined-addresses", 100 - subnet1_cnt, 1);
+ testStatistics("declined-addresses", 200 - subnet2_cnt, 2);
+
+ // subnet[X].reclaimed-declined-addresses should go up in each subnet
+ testStatistics("reclaimed-declined-addresses", 10000 + subnet1_cnt, 1);
+ testStatistics("reclaimed-declined-addresses", 20000 + subnet1_cnt, 2);
+ }
+
+ /// @brief Collection of leases created at construction time.
+ std::vector<LeasePtrType> leases_;
+
+ /// @brief Allocation engine instance used for tests.
+ AllocEnginePtr engine_;
+};
+
+
+
+/// @brief Specialization of the @c ExpirationAllocEngineTest class to test
+/// reclamation of the IPv6 leases.
+class ExpirationAllocEngine6Test : public ExpirationAllocEngineTest<Lease6Ptr> {
+public:
+
+ /// @brief Class constructor.
+ ///
+ /// This constructor initializes @c TEST_LEASES_NUM leases and
+ /// stores them in the lease manager.
+ ExpirationAllocEngine6Test();
+
+ /// @brief Virtual destructor.
+ ///
+ /// Clears up static fields that may be modified by hooks.
+ virtual ~ExpirationAllocEngine6Test() {
+ callout_lease_.reset();
+ callout_name_ = string("");
+ }
+
+ /// @brief Creates collection of leases for a test.
+ ///
+ /// It is called internally at the construction time.
+ void createLeases();
+
+ /// @brief Updates lease in the lease database.
+ ///
+ /// @param lease_index Index of the lease.
+ virtual void updateLease(const unsigned int lease_index) {
+ LeaseMgrFactory::instance().updateLease6(leases_[lease_index]);
+ }
+
+ /// @brief Changes the owner of a lease.
+ ///
+ /// This method changes the owner of the lease by modifying the DUID.
+ ///
+ /// @param lease_index Lease index. Must be between 0 and
+ /// @c TEST_LEASES_NUM.
+ virtual void transferOwnership(const uint16_t lease_index);
+
+ /// @brief Sets subnet id for a lease.
+ ///
+ /// It also updates statistics of assigned leases in the stats manager.
+ ///
+ /// @param lease_index Lease index.
+ /// @param id New subnet id.
+ virtual void setSubnetId(const uint16_t lease_index, const SubnetID& id);
+
+ /// @brief Sets type of a lease.
+ ///
+ /// It also updates statistics of assigned leases in the stats manager.
+ ///
+ /// @param lease_index Lease index.
+ /// @param lease_type Lease type.
+ void setLeaseType(const uint16_t lease_index, const Lease6::Type& lease_type);
+
+ /// @brief Retrieves lease from the database.
+ ///
+ /// @param lease_index Index of the lease.
+ virtual Lease6Ptr getLease(const unsigned int lease_index) const {
+ return (LeaseMgrFactory::instance().getLease6(leases_[lease_index]->type_,
+ leases_[lease_index]->addr_));
+ }
+
+ /// @brief Wrapper method running lease reclamation routine.
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in seconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ virtual void reclaimExpiredLeases(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease) {
+ engine_->reclaimExpiredLeases6(max_leases, timeout, remove_lease);
+ }
+
+ /// @brief Wrapper method for removing expired-reclaimed leases.
+ ///
+ /// @param secs The minimum amount of time, expressed in seconds,
+ /// for the lease to be left in the "expired-reclaimed" state
+ /// before it can be removed.
+ virtual void deleteExpiredReclaimedLeases(const uint32_t secs) {
+ engine_->deleteExpiredReclaimedLeases6(secs);
+ }
+
+ /// @brief Test that statistics is updated when leases are reclaimed.
+ void testReclaimExpiredLeasesStats();
+
+ /// @brief Test that expired leases are reclaimed before they are allocated.
+ ///
+ /// @param msg_type DHCPv6 message type.
+ /// @param use_reclaimed Boolean parameter indicating if the leases
+ /// stored in the lease database should be marked as 'expired-reclaimed'
+ /// or 'expired'. This allows to test whether the allocation engine can
+ /// determine that the lease has been reclaimed already and not reclaim
+ /// it the second time.
+ void testReclaimReusedLeases(const uint16_t msg_type, const bool use_reclaimed);
+
+ /// @brief Callout for lease6_recover
+ ///
+ /// This callout stores passed parameter into static fields.
+ ///
+ /// @param callout_handle will be provided by hooks framework
+ /// @return always 0
+ static int lease6RecoverCallout(CalloutHandle& callout_handle) {
+ callout_name_ = "lease6_recover";
+
+ callout_handle.getArgument("lease6", callout_lease_);
+
+ return (0);
+ }
+
+ /// @brief Callout for lease6_recover that sets status to SKIP
+ ///
+ /// This callout stores passed parameter into static fields.
+ ///
+ /// @param callout_handle will be provided by hooks framework
+ /// @return always 0
+ static int lease6RecoverSkipCallout(CalloutHandle& callout_handle) {
+ // Set the next step status to SKIP
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (lease6RecoverCallout(callout_handle));
+ }
+
+ /// @brief Test install a hook callout, recovers declined leases
+ ///
+ /// This test: declines, then expires half of the leases, then
+ /// installs a callout on lease6_recover hook, then reclaims
+ /// expired leases and checks that:
+ /// - the callout was indeed called
+ /// - the parameter (lease6) was indeed passed as expected
+ /// - checks that the leases are removed (skip=false) or
+ /// - checks that the leases are still there (skip=true)
+ /// @param skip should the callout set the next step status to skip?
+ void
+ testReclaimDeclinedHook(bool skip);
+
+ /// The following parameters will be written by a callout
+ static std::string callout_name_; ///< Stores callout name
+ static Lease6Ptr callout_lease_; ///< Stores callout parameter
+};
+
+std::string ExpirationAllocEngine6Test::callout_name_;
+Lease6Ptr ExpirationAllocEngine6Test::callout_lease_;
+
+ExpirationAllocEngine6Test::ExpirationAllocEngine6Test()
+ : ExpirationAllocEngineTest<Lease6Ptr>("type=memfile universe=6 persist=false") {
+ createLeases();
+ callout_argument_name = "lease6";
+
+ // Let's clear any garbage previous test may have left in static fields.
+ callout_name_ = string("");
+ callout_lease_.reset();
+}
+
+void
+ExpirationAllocEngine6Test::createLeases() {
+ // Create TEST_LEASES_NUM leases.
+ for (uint16_t i = 0; i < TEST_LEASES_NUM; ++i) {
+ // DUID
+ std::ostringstream duid_s;
+ duid_s << "01020304050607" << std::setw(4) << std::setfill('0') << i;
+ DuidPtr duid(new DUID(DUID::fromText(duid_s.str()).getDuid()));
+
+ // Address.
+ std::ostringstream address_s;
+ address_s << "2001:db8:1::" << std::setw(4) << std::setfill('0') << i;
+ IOAddress address(address_s.str());
+
+ // Create lease.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, address, duid, 1, 50, 60,
+ SubnetID(1), true, true,
+ generateHostnameForLeaseIndex(i)));
+ ElementPtr user_context = Element::createMap();
+ user_context->set("index", Element::create(static_cast<int>(i)));
+ lease->setContext(user_context);
+ leases_.push_back(lease);
+ // Copy the lease before adding it to the lease manager. We want to
+ // make sure that modifications to the leases held in the leases_
+ // container doesn't affect the leases in the lease manager.
+ Lease6Ptr tmp(new Lease6(*lease));
+ LeaseMgrFactory::instance().addLease(tmp);
+
+ // Note in the statistics that this lease has been added.
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ std::string stat_name =
+ lease->type_ == Lease::TYPE_NA ? "assigned-nas" : "assigned-pds";
+ stats_mgr.addValue(stats_mgr.generateName("subnet", lease->subnet_id_, stat_name),
+ int64_t(1));
+ }
+}
+
+void
+ExpirationAllocEngine6Test::transferOwnership(const uint16_t lease_index) {
+ ASSERT_GT(leases_.size(), lease_index);
+ std::vector<uint8_t> bytes = leases_[lease_index]->duid_->getDuid();
+ if (bytes.size() > 1) {
+ if (++bytes[0] == 0) {
+ ++bytes[1];
+ }
+ }
+
+ leases_[lease_index]->duid_.reset(new DUID(bytes));
+}
+
+void
+ExpirationAllocEngine6Test::setSubnetId(const uint16_t lease_index, const SubnetID& id) {
+ ASSERT_GT(leases_.size(), lease_index);
+ if (leases_[lease_index]->subnet_id_ != id) {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ std::string stats_name = (leases_[lease_index]->type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds");
+ stats_mgr.addValue(stats_mgr.generateName("subnet", id, stats_name),
+ int64_t(1));
+ stats_mgr.addValue(stats_mgr.generateName("subnet",
+ leases_[lease_index]->subnet_id_,
+ stats_name),
+ int64_t(-1));
+ leases_[lease_index]->subnet_id_ = id;
+ ASSERT_NO_THROW(updateLease(lease_index));
+ }
+}
+
+void
+ExpirationAllocEngine6Test::setLeaseType(const uint16_t lease_index,
+ const Lease6::Type& lease_type) {
+ ASSERT_GT(leases_.size(), lease_index);
+ if (leases_[lease_index]->type_ != lease_type) {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ std::string stats_name = (lease_type == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds");
+ stats_mgr.addValue(stats_mgr.generateName("subnet",
+ leases_[lease_index]->subnet_id_,
+ stats_name),
+ int64_t(1));
+ stats_name = (leases_[lease_index]->type_ == Lease::TYPE_NA ?
+ "assigned-nas" : "assigned-pds");
+ stats_mgr.addValue(stats_mgr.generateName("subnet",
+ leases_[lease_index]->subnet_id_,
+ stats_name),
+ int64_t(-1));
+ leases_[lease_index]->type_ = lease_type;
+ ASSERT_NO_THROW(updateLease(lease_index));
+ }
+}
+
+
+void
+ExpirationAllocEngine6Test::testReclaimExpiredLeasesStats() {
+ // This test requires that the number of leases is an even number.
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Mark all leases as expired. The higher the index the less
+ // expired the lease.
+ expire(i, 1000 - i);
+ // Modify subnet ids and lease types for some leases.
+ if (evenLeaseIndex(i)) {
+ setSubnetId(i, SubnetID(2));
+ setLeaseType(i, Lease::TYPE_PD);
+ }
+ }
+
+ // Leases will be reclaimed in groups of 8.
+ const size_t reclamation_group_size = 8;
+ for (unsigned int i = reclamation_group_size; i < TEST_LEASES_NUM;
+ i += reclamation_group_size) {
+
+ // Reclaim 8 most expired leases out of TEST_LEASES_NUM.
+ ASSERT_NO_THROW(reclaimExpiredLeases(reclamation_group_size,
+ 0, false));
+
+ // Number of reclaimed leases should increase as we loop.
+ EXPECT_TRUE(testStatistics("reclaimed-leases", i));
+ // Make sure that the number of reclaimed leases is also distributed
+ // across two subnets.
+ EXPECT_TRUE(testStatistics("reclaimed-leases", i / 2, 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", i / 2, 2));
+ // Number of assigned leases should decrease as we reclaim them.
+ EXPECT_TRUE(testStatistics("assigned-nas",
+ (TEST_LEASES_NUM - i) / 2, 1));
+ EXPECT_TRUE(testStatistics("assigned-pds",
+ (TEST_LEASES_NUM - i) / 2, 2));
+ }
+}
+
+void
+ExpirationAllocEngine6Test::testReclaimReusedLeases(const uint16_t msg_type,
+ const bool use_reclaimed) {
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM < 1000);
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Depending on the parameter, mark leases 'expired-reclaimed' or
+ // simply 'expired'.
+ if (use_reclaimed) {
+ reclaim(i, 1000 - i);
+
+ } else {
+ // Mark all leases as expired.
+ expire(i, 1000 - i);
+ }
+
+ // For the Renew case, we don't change the ownership of leases. We
+ // will let the lease owners renew them. For other cases, we modify
+ // the DUIDs to simulate reuse of expired leases.
+ if (msg_type != DHCPV6_RENEW) {
+ transferOwnership(i);
+ }
+ }
+
+ // Create subnet and the pool. This is required by the allocation process.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 50, 60,
+ SubnetID(1)));
+ ASSERT_NO_THROW(subnet->addPool(Pool6Ptr(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::"),
+ IOAddress("2001:db8:1::FFFF")))));
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Build the context.
+ AllocEngine::ClientContext6 ctx(subnet, leases_[i]->duid_,
+ false, false,
+ leases_[i]->hostname_,
+ msg_type == DHCPV6_SOLICIT,
+ Pkt6Ptr(new Pkt6(msg_type, 0x1234)));
+ ctx.currentIA().iaid_ = 1;
+ ctx.currentIA().addHint(leases_[i]->addr_);
+
+ // Depending on the message type, we will call a different function.
+ if (msg_type == DHCPV6_RENEW) {
+ ASSERT_NO_THROW(engine_->renewLeases6(ctx));
+
+ } else {
+ ASSERT_NO_THROW(engine_->allocateLeases6(ctx));
+ }
+ }
+
+ // The Solicit should not trigger leases reclamation. The Renew and
+ // Request must trigger leases reclamation unless the lease is
+ // initially reclaimed.
+ if (use_reclaimed || (msg_type == DHCPV6_SOLICIT)) {
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+ } else {
+ EXPECT_TRUE(testStatistics("reclaimed-leases", TEST_LEASES_NUM));
+ EXPECT_TRUE(testStatistics("assigned-nas", TEST_LEASES_NUM, subnet->getID()));
+ // Leases should have been updated in the lease database and their
+ // state should not be 'expired-reclaimed' anymore.
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes));
+ }
+
+}
+
+void
+ExpirationAllocEngine6Test::testReclaimDeclinedHook(bool skip) {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+
+ // Mark leases with even indexes as expired.
+ if (evenLeaseIndex(i)) {
+
+ // Mark lease as declined with 100 seconds of probation-period
+ // (i.e. lease is supposed to be off limits for 100 seconds)
+ decline(i, 100);
+
+ // The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+ }
+
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_recover",
+ skip ? lease6RecoverSkipCallout : lease6RecoverCallout));
+
+ // Run leases reclamation routine on all leases.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, true));
+
+ // Make sure that the callout really was called. It was supposed to modify
+ // the callout_name_ and store the lease in callout_lease_
+ EXPECT_EQ("lease6_recover", callout_name_);
+ EXPECT_TRUE(callout_lease_);
+
+ // Leases with even indexes should not exist in the DB
+ if (skip) {
+ // Skip status should have prevented removing the lease.
+ EXPECT_TRUE(testLeases(&leaseExists, &evenLeaseIndex));
+ } else {
+ // The hook hasn't modified next step status. The lease should be gone.
+ EXPECT_TRUE(testLeases(&leaseDoesntExist, &evenLeaseIndex));
+ }
+};
+
+// This test verifies that the leases can be reclaimed without being removed
+// from the database. In such case, the leases' state is set to
+// "expired-reclaimed".
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeases6UpdateState) {
+ testReclaimExpiredLeasesUpdateState();
+}
+
+// This test verifies that the reclaimed leases are deleted when requested.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesDelete) {
+ testReclaimExpiredLeasesDelete();
+}
+
+// This test verifies that it is possible to specify the limit for the
+// number of reclaimed leases.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesLimit) {
+ testReclaimExpiredLeasesLimit();
+}
+
+// This test verifies that DNS updates are generated for the leases
+// for which the DNS records exist.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesWithDDNS) {
+ testReclaimExpiredLeasesWithDDNS();
+}
+
+// This test verifies that it is DNS updates are generated only for the
+// reclaimed expired leases. In this case we limit the number of leases
+// reclaimed during a single call to reclamation routine.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesWithDDNSAndLimit) {
+ testReclaimExpiredLeasesWithDDNSAndLimit();
+}
+
+// This test verifies that if some leases have invalid hostnames, the
+// lease reclamation routine continues with reclamation of leases anyway.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesInvalidHostname) {
+ testReclaimExpiredLeasesInvalidHostname();
+}
+
+// This test verifies that statistics is correctly updated when the leases
+// are reclaimed.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesStats) {
+ testReclaimExpiredLeasesStats();
+}
+
+// This test verifies that callouts are executed for each expired lease.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesHooks) {
+ testReclaimExpiredLeasesHooks();
+}
+
+// This test verifies that callouts are executed for each expired lease
+// and that the lease is not reclaimed when the skip flag is set.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesHooksWithSkip) {
+ testReclaimExpiredLeasesHooksWithSkip();
+}
+
+// This test verifies that it is possible to set the timeout for the
+// execution of the lease reclamation routine.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesTimeout) {
+ // This test needs at least 40 leases to make sense.
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 40);
+ // Run with timeout of 1.2s.
+ testReclaimExpiredLeasesTimeout(1200);
+}
+
+// This test verifies that at least one lease is reclaimed if the timeout
+// for the lease reclamation routine is shorter than the time needed for
+// the reclamation of a single lease. This prevents the situation when
+// very short timeout (perhaps misconfigured) effectively precludes leases
+// reclamation.
+TEST_F(ExpirationAllocEngine6Test, reclaimExpiredLeasesShortTimeout) {
+ // We will most likely reclaim just one lease, so 5 is more than enough.
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 5);
+ // Reclaim leases with the 1ms timeout.
+ testReclaimExpiredLeasesTimeout(1);
+}
+
+// This test verifies that expired-reclaimed leases are removed from the
+// lease database.
+TEST_F(ExpirationAllocEngine6Test, deleteExpiredReclaimedLeases) {
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 10);
+ testDeleteExpiredReclaimedLeases();
+}
+
+/// This test verifies that @ref AllocEngine::reclaimExpiredLeases6 properly
+/// handles declined leases that have expired in case when it is told to
+/// remove leases.}
+TEST_F(ExpirationAllocEngine6Test, reclaimDeclined1) {
+ testReclaimDeclined(true);
+}
+
+/// This test verifies that @ref AllocEngine::reclaimExpiredLeases6 properly
+/// handles declined leases that have expired in case when it is told to
+/// not remove leases. This flag should not matter and declined expired
+/// leases should always be removed.
+TEST_F(ExpirationAllocEngine6Test, reclaimDeclined2) {
+ testReclaimDeclined(false);
+}
+
+/// This test verifies that statistics are modified correctly after
+/// reclaim expired leases is called.
+TEST_F(ExpirationAllocEngine6Test, reclaimDeclinedStats) {
+ testReclaimDeclinedStats("assigned-nas");
+}
+
+// This test verifies that expired leases are reclaimed before they are
+// allocated to another client sending a Request message.
+TEST_F(ExpirationAllocEngine6Test, reclaimReusedLeases) {
+ testReclaimReusedLeases(DHCPV6_REQUEST, false);
+}
+
+// This test verifies that allocation engine detects that the expired
+// lease has been reclaimed already when it reuses this lease.
+TEST_F(ExpirationAllocEngine6Test, reclaimReusedLeasesAlreadyReclaimed) {
+ testReclaimReusedLeases(DHCPV6_REQUEST, true);
+}
+
+// This test verifies that expired leases are reclaimed before they
+// are renewed.
+TEST_F(ExpirationAllocEngine6Test, reclaimRenewedLeases) {
+ testReclaimReusedLeases(DHCPV6_RENEW, false);
+}
+
+// This test verifies that allocation engine detects that the expired
+// lease has been reclaimed already when it renews the lease.
+TEST_F(ExpirationAllocEngine6Test, reclaimRenewedLeasesAlreadyReclaimed) {
+ testReclaimReusedLeases(DHCPV6_RENEW, true);
+}
+
+// This test verifies that the expired leases are not reclaimed when the
+// Solicit message is being processed.
+TEST_F(ExpirationAllocEngine6Test, reclaimReusedLeasesSolicit) {
+ testReclaimReusedLeases(DHCPV6_SOLICIT, false);
+}
+
+// This test verifies that the 'expired-reclaimed' leases are not reclaimed
+// again when the Solicit message is being processed.
+TEST_F(ExpirationAllocEngine6Test, reclaimReusedLeasesSolicitAlreadyReclaimed) {
+ testReclaimReusedLeases(DHCPV6_SOLICIT, true);
+}
+
+// This test verifies if the hooks installed on lease6_recover are called
+// when the lease expires.
+TEST_F(ExpirationAllocEngine6Test, reclaimDeclinedHook1) {
+ testReclaimDeclinedHook(false); // false = don't use skip callout
+}
+
+// This test verifies if the hooks installed on lease6_recover are called
+// when the lease expires and that the next step status set to SKIP
+// causes the recovery to not be conducted.
+TEST_F(ExpirationAllocEngine6Test, reclaimDeclinedHook2) {
+ testReclaimDeclinedHook(true); // true = use skip callout
+}
+
+// *******************************************************
+//
+// DHCPv4 lease reclamation routine tests start here!
+//
+// *******************************************************
+
+/// @brief Specialization of the @c ExpirationAllocEngineTest class to test
+/// reclamation of the IPv4 leases.
+class ExpirationAllocEngine4Test : public ExpirationAllocEngineTest<Lease4Ptr> {
+public:
+
+ /// @brief Class constructor.
+ ///
+ /// This constructor initializes @c TEST_LEASES_NUM leases and
+ /// stores them in the lease manager.
+ ExpirationAllocEngine4Test();
+
+ /// @brief Virtual destructor.
+ ///
+ /// Clears up static fields that may be modified by hooks.
+ virtual ~ExpirationAllocEngine4Test() {
+ callout_lease_.reset();
+ callout_name_ = string("");
+ }
+
+ /// @brief Creates collection of leases for a test.
+ ///
+ /// It is called internally at the construction time.
+ void createLeases();
+
+ /// @brief Generates unique client identifier from lease index.
+ ///
+ /// @param index lease index.
+ void setUniqueClientId(const uint16_t index);
+
+ /// @brief Updates lease in the lease database.
+ ///
+ /// @param lease_index Index of the lease.
+ virtual void updateLease(const unsigned int lease_index) {
+ LeaseMgrFactory::instance().updateLease4(leases_[lease_index]);
+ }
+
+ /// @brief Changes the owner of a lease.
+ ///
+ /// This method changes the owner of the lease by updating the client
+ /// identifier (if present) or HW address.
+ ///
+ /// @param lease_index Lease index. Must be between 0 and
+ /// @c TEST_LEASES_NUM.
+ virtual void transferOwnership(const uint16_t lease_index);
+
+ /// @brief Retrieves lease from the database.
+ ///
+ /// @param lease_index Index of the lease.
+ virtual Lease4Ptr getLease(const unsigned int lease_index) const {
+ return (LeaseMgrFactory::instance().getLease4(leases_[lease_index]->addr_));
+ }
+
+ /// @brief Sets subnet id for a lease.
+ ///
+ /// It also updates statistics of assigned leases in the stats manager.
+ ///
+ /// @param lease_index Lease index.
+ /// @param id New subnet id.
+ virtual void setSubnetId(const uint16_t lease_index, const SubnetID& id);
+
+ /// @brief Wrapper method running lease reclamation routine.
+ ///
+ /// @param max_leases Maximum number of leases to be reclaimed.
+ /// @param timeout Maximum amount of time that the reclamation routine
+ /// may be processing expired leases, expressed in seconds.
+ /// @param remove_lease A boolean value indicating if the lease should
+ /// be removed when it is reclaimed (if true) or it should be left in the
+ /// database in the "expired-reclaimed" state (if false).
+ virtual void reclaimExpiredLeases(const size_t max_leases,
+ const uint16_t timeout,
+ const bool remove_lease) {
+ engine_->reclaimExpiredLeases4(max_leases, timeout, remove_lease);
+ }
+
+ /// @brief Wrapper method for removing expired-reclaimed leases.
+ ///
+ /// @param secs The minimum amount of time, expressed in seconds,
+ /// for the lease to be left in the "expired-reclaimed" state
+ /// before it can be removed.
+ virtual void deleteExpiredReclaimedLeases(const uint32_t secs) {
+ engine_->deleteExpiredReclaimedLeases4(secs);
+ }
+
+ /// @brief Lease algorithm checking if NCR has been generated from client
+ /// identifier.
+ ///
+ /// @param lease Pointer to the lease for which the NCR needs to be checked.
+ static bool dnsUpdateGeneratedFromClientId(const Lease4Ptr& lease);
+
+ /// @brief Lease algorithm checking if NCR has been generated from
+ /// HW address.
+ static bool dnsUpdateGeneratedFromHWAddress(const Lease4Ptr& lease);
+
+ /// @brief Test that DNS updates are properly generated when the
+ /// reclaimed leases contain client identifier.
+ void testReclaimExpiredLeasesWithDDNSAndClientId();
+
+ /// @brief Test that statistics is updated when leases are reclaimed..
+ void testReclaimExpiredLeasesStats();
+
+ /// @brief Test that the lease is reclaimed before it is renewed or
+ /// reused.
+ ///
+ /// @param msg_type DHCPv4 message type, i.e. DHCPDISCOVER or DHCPREQUEST.
+ /// @param client_renews A boolean value which indicates if the test should
+ /// simulate renewals of leases (if true) or reusing expired leases which
+ /// belong to different clients (if false).
+ /// @param use_reclaimed Boolean parameter indicating if the leases being
+ /// reused should initially be reclaimed.
+ void testReclaimReusedLeases(const uint8_t msg_type, const bool client_renews,
+ const bool use_reclaimed);
+
+ /// @brief Callout for lease4_recover
+ ///
+ /// This callout stores passed parameter into static fields.
+ ///
+ /// @param callout_handle will be provided by hooks framework
+ /// @return always 0
+ static int lease4RecoverCallout(CalloutHandle& callout_handle) {
+ callout_name_ = "lease4_recover";
+
+ callout_handle.getArgument("lease4", callout_lease_);
+
+ return (0);
+ }
+
+ /// @brief Callout for lease4_recover that sets status to SKIP
+ ///
+ /// This callout stores passed parameter into static fields.
+ ///
+ /// @param callout_handle will be provided by hooks framework
+ /// @return always 0
+ static int lease4RecoverSkipCallout(CalloutHandle& callout_handle) {
+ // Set the next step status to SKIP
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (lease4RecoverCallout(callout_handle));
+ }
+
+ /// @brief Test install a hook callout, recovers declined leases
+ ///
+ /// This test: declines, then expires half of the leases, then
+ /// installs a callout on lease4_recover hook, then reclaims
+ /// expired leases and checks that:
+ /// - the callout was indeed called
+ /// - the parameter (lease4) was indeed passed as expected
+ /// - checks that the leases are removed (skip=false) or
+ /// - checks that the leases are still there (skip=true)
+ /// @param skip should the callout set the next step status to skip?
+ void
+ testReclaimDeclinedHook(bool skip);
+
+ /// The following parameters will be written by a callout
+ static std::string callout_name_; ///< Stores callout name
+ static Lease4Ptr callout_lease_; ///< Stores callout parameter
+};
+
+std::string ExpirationAllocEngine4Test::callout_name_;
+Lease4Ptr ExpirationAllocEngine4Test::callout_lease_;
+
+ExpirationAllocEngine4Test::ExpirationAllocEngine4Test()
+ : ExpirationAllocEngineTest<Lease4Ptr>("type=memfile universe=4 persist=false") {
+ createLeases();
+ callout_argument_name = "lease4";
+
+ // Let's clear any garbage previous test may have left in static fields.
+ callout_name_ = string("");
+ callout_lease_.reset();
+}
+
+void
+ExpirationAllocEngine4Test::createLeases() {
+ // Create TEST_LEASES_NUM leases.
+ for (uint16_t i = 0; i < TEST_LEASES_NUM; ++i) {
+ // HW address
+ std::ostringstream hwaddr_s;
+ hwaddr_s << "01:02:03:04:" << std::setw(2) << std::setfill('0')
+ << (i >> 8) << ":" << std::setw(2) << std::setfill('0')
+ << (i & 0x00FF);
+ HWAddrPtr hwaddr(new HWAddr(HWAddr::fromText(hwaddr_s.str(),
+ HTYPE_ETHER)));
+
+ // Address.
+ std::ostringstream address_s;
+ address_s << "10.0." << (i >> 8) << "." << (i & 0x00FF);
+ IOAddress address(address_s.str());
+
+ // Create lease.
+ Lease4Ptr lease(new Lease4(address, hwaddr, ClientIdPtr(), 60,
+ time(NULL), SubnetID(1), true, true,
+ generateHostnameForLeaseIndex(i)));
+ ElementPtr user_context = Element::createMap();
+ user_context->set("index", Element::create(static_cast<int>(i)));
+ lease->setContext(user_context);
+ leases_.push_back(lease);
+ // Copy the lease before adding it to the lease manager. We want to
+ // make sure that modifications to the leases held in the leases_
+ // container doesn't affect the leases in the lease manager.
+ Lease4Ptr tmp(new Lease4(*lease));
+ LeaseMgrFactory::instance().addLease(tmp);
+
+ // Note in the statistics that this lease has been added.
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ std::string stat_name = "assigned-addresses";
+ stats_mgr.addValue(stats_mgr.generateName("subnet", lease->subnet_id_, stat_name),
+ int64_t(1));
+ }
+}
+
+void
+ExpirationAllocEngine4Test::setUniqueClientId(const uint16_t index) {
+ std::ostringstream clientid_s;
+ clientid_s << "AA:BB:" << std::setw(2) << std::setfill('0')
+ << (index >> 8) << ":" << std::setw(2) << std::setfill('0')
+ << (index & 0x00FF);
+ ClientIdPtr client_id(ClientId::fromText(clientid_s.str()));
+ leases_[index]->client_id_ = client_id;
+ LeaseMgrFactory::instance().updateLease4(leases_[index]);
+}
+
+void
+ExpirationAllocEngine4Test::setSubnetId(const uint16_t lease_index, const SubnetID& id) {
+ ASSERT_GT(leases_.size(), lease_index);
+ if (leases_[lease_index]->subnet_id_ != id) {
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ stats_mgr.addValue(stats_mgr.generateName("subnet", id, "assigned-addresses"),
+ int64_t(1));
+ stats_mgr.addValue(stats_mgr.generateName("subnet",
+ leases_[lease_index]->subnet_id_,
+ "assigned-addresses"),
+ int64_t(-1));
+ leases_[lease_index]->subnet_id_ = id;
+ ASSERT_NO_THROW(updateLease(lease_index));
+ }
+}
+
+void
+ExpirationAllocEngine4Test::transferOwnership(const uint16_t lease_index) {
+ ASSERT_GT(leases_.size(), lease_index);
+ std::vector<uint8_t> bytes;
+ if (leases_[lease_index]->client_id_) {
+ bytes = leases_[lease_index]->client_id_->getClientId();
+ } else {
+ bytes = leases_[lease_index]->hwaddr_->hwaddr_;
+ }
+
+ if (!bytes.empty()) {
+ if (++bytes[0] == 0) {
+ ++bytes[1];
+ }
+ }
+
+ if (leases_[lease_index]->client_id_) {
+ leases_[lease_index]->client_id_.reset(new ClientId(bytes));
+ } else {
+ leases_[lease_index]->hwaddr_.reset(new HWAddr(bytes, HTYPE_ETHER));
+ }
+}
+
+
+bool
+ExpirationAllocEngine4Test::dnsUpdateGeneratedFromClientId(const Lease4Ptr& lease) {
+ try {
+ NameChangeRequestPtr ncr = getNCRForLease(lease);
+ if (ncr) {
+ if (lease->client_id_) {
+ // Generate hostname for this lease. Note that the lease
+ // in the database doesn't have the hostname because it
+ // has been removed by the lease reclamation routine.
+ std::string hostname = generateHostnameForLeaseIndex(
+ getLeaseIndexFromAddress(lease->addr_));
+
+ // Get DHCID from NCR.
+ const D2Dhcid& dhcid = ncr->getDhcid();
+ // Generate reference DHCID to compare with the one from
+ // the NCR.
+ std::vector<uint8_t> fqdn_wire;
+ OptionDataTypeUtil::writeFqdn(hostname, fqdn_wire, true);
+ D2Dhcid clientid_dhcid(lease->client_id_->getClientId(),
+ fqdn_wire);
+ // Return true if they match.
+ return (dhcid == clientid_dhcid);
+ }
+ }
+
+ } catch (...) {
+ // If error occurred, treat it as no match.
+ return (false);
+ }
+
+ // All leases checked - no match.
+ return (false);
+}
+
+bool
+ExpirationAllocEngine4Test::dnsUpdateGeneratedFromHWAddress(const Lease4Ptr& lease) {
+ try {
+ NameChangeRequestPtr ncr = getNCRForLease(lease);
+ if (ncr) {
+ if (lease->hwaddr_) {
+ // Generate hostname for this lease. Note that the lease
+ // in the database doesn't have the hostname because it
+ // has been removed by the lease reclamation routine.
+ std::string hostname = generateHostnameForLeaseIndex(
+ getLeaseIndexFromAddress(lease->addr_));
+
+ // Get DHCID from NCR.
+ const D2Dhcid& dhcid = ncr->getDhcid();
+ // Generate reference DHCID to compare with the one from
+ // the NCR.
+ std::vector<uint8_t> fqdn_wire;
+ OptionDataTypeUtil::writeFqdn(hostname, fqdn_wire, true);
+ D2Dhcid hwaddr_dhcid(lease->hwaddr_, fqdn_wire);
+ // Return true if they match.
+ return (dhcid == hwaddr_dhcid);
+ }
+ }
+
+ } catch (...) {
+ // If error occurred, treat it as no match.
+ return (false);
+ }
+
+ // All leases checked - no match.
+ return (false);
+}
+
+void
+ExpirationAllocEngine4Test::testReclaimExpiredLeasesWithDDNSAndClientId() {
+ // DNS must be started for the D2 client to accept NCRs.
+ ASSERT_NO_THROW(enableDDNS());
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Set client identifiers for leases with even indexes only.
+ if (evenLeaseIndex(i)) {
+ setUniqueClientId(i);
+ }
+ // Expire all leases. The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+
+ // Reclaim all expired leases.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, false));
+
+ // Leases with even indexes should be reclaimed.
+ EXPECT_TRUE(testLeases(&leaseReclaimed, &evenLeaseIndex));
+ // DNS updates (removal NCRs) should be generated for all leases.
+ EXPECT_TRUE(testLeases(&dnsUpdateGeneratedForLease, &allLeaseIndexes));
+ // Leases with even indexes include client identifiers so the DHCID should
+ // be generated from the client identifiers.
+ EXPECT_TRUE(testLeases(&dnsUpdateGeneratedFromClientId, &evenLeaseIndex));
+ // Leases with odd indexes do not include client identifiers so their
+ // DHCID should be generated from the HW address.
+ EXPECT_TRUE(testLeases(&dnsUpdateGeneratedFromHWAddress, &oddLeaseIndex));
+}
+
+void
+ExpirationAllocEngine4Test::testReclaimExpiredLeasesStats() {
+ // This test requires that the number of leases is an even number.
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM % 2 == 0);
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Mark all leases as expired. The higher the index the less
+ // expired the lease.
+ expire(i, 1000 - i);
+ // Modify subnet ids of some leases.
+ if (evenLeaseIndex(i)) {
+ setSubnetId(i, 2);
+ }
+ }
+
+ // Leases will be reclaimed in groups of 8.
+ const size_t reclamation_group_size = 8;
+ for (unsigned int i = reclamation_group_size; i < TEST_LEASES_NUM;
+ i += reclamation_group_size) {
+
+ // Reclaim 8 most expired leases out of TEST_LEASES_NUM.
+ ASSERT_NO_THROW(reclaimExpiredLeases(reclamation_group_size,
+ 0, false));
+
+ // Number of reclaimed leases should increase as we loop.
+ EXPECT_TRUE(testStatistics("reclaimed-leases", i));
+ // Make sure that the number of reclaimed leases is also distributed
+ // across two subnets.
+ EXPECT_TRUE(testStatistics("reclaimed-leases", i / 2, 1));
+ EXPECT_TRUE(testStatistics("reclaimed-leases", i / 2, 2));
+ // Number of assigned leases should decrease as we reclaim them.
+ EXPECT_TRUE(testStatistics("assigned-addresses",
+ (TEST_LEASES_NUM - i) / 2, 1));
+ EXPECT_TRUE(testStatistics("assigned-addresses",
+ (TEST_LEASES_NUM - i) / 2, 2));
+ }
+}
+
+void
+ExpirationAllocEngine4Test::testReclaimReusedLeases(const uint8_t msg_type,
+ const bool client_renews,
+ const bool use_reclaimed) {
+ // Let's restrict the number of leases.
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM < 1000);
+
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Depending on the parameter, mark leases 'expired-reclaimed' or
+ // simply 'expired'.
+ if (use_reclaimed) {
+ reclaim(i, 1000 - i);
+
+ } else {
+ // Mark all leases as expired.
+ expire(i, 1000 - i);
+ }
+
+ // Check if we're simulating renewals or reusing leases. If this is
+ // about reusing leases, we should be using different MAC addresses
+ // or client identifiers for the leases than those stored presently
+ // in the database.
+ if (!client_renews) {
+ // This function modifies the MAC address or the client identifier
+ // of the test lease to make sure it doesn't match the one we
+ // have in the database.
+ transferOwnership(i);
+ }
+ }
+
+ // The call to AllocEngine::allocateLease4 requires the subnet selection.
+ // The pool must be present within a subnet for the allocation engine to
+ // hand out address from.
+ Subnet4Ptr subnet(new Subnet4(IOAddress("10.0.0.0"), 16, 10, 20, 60, SubnetID(1)));
+ ASSERT_NO_THROW(subnet->addPool(Pool4Ptr(new Pool4(IOAddress("10.0.0.0"),
+ IOAddress("10.0.255.255")))));
+
+ // Re-allocate leases (reuse or renew).
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+ // Build the context.
+ AllocEngine::ClientContext4 ctx(subnet, leases_[i]->client_id_,
+ leases_[i]->hwaddr_,
+ leases_[i]->addr_, false, false,
+ leases_[i]->hostname_,
+ msg_type == DHCPDISCOVER);
+ // Query is needed for logging purposes.
+ ctx.query_.reset(new Pkt4(msg_type, 0x1234));
+
+ // Re-allocate a lease. Note that the iterative will pick addresses
+ // starting from the beginning of the pool. This matches exactly
+ // the set of addresses we have allocated and stored in the database.
+ // Since all leases are marked expired the allocation engine will
+ // reuse them or renew them as appropriate.
+ ASSERT_NO_THROW(engine_->allocateLease4(ctx));
+ }
+
+ // If DHCPDISCOVER is being processed, the leases should not be reclaimed.
+ // Also, the leases should not be reclaimed if they are already in the
+ // 'expired-reclaimed' state.
+ if (use_reclaimed || (msg_type == DHCPDISCOVER)) {
+ EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
+
+ } else if (msg_type == DHCPREQUEST) {
+ // Re-allocation of expired leases should result in reclamations.
+ EXPECT_TRUE(testStatistics("reclaimed-leases", TEST_LEASES_NUM));
+ EXPECT_TRUE(testStatistics("assigned-addresses", TEST_LEASES_NUM, subnet->getID()));
+ // Leases should have been updated in the lease database and their
+ // state should not be 'expired-reclaimed' anymore.
+ EXPECT_TRUE(testLeases(&leaseNotReclaimed, &allLeaseIndexes));
+ }
+}
+
+void
+ExpirationAllocEngine4Test::testReclaimDeclinedHook(bool skip) {
+ for (unsigned int i = 0; i < TEST_LEASES_NUM; ++i) {
+
+ // Mark leases with even indexes as expired.
+ if (evenLeaseIndex(i)) {
+
+ // Mark lease as declined with 100 seconds of probation-period
+ // (i.e. lease is supposed to be off limits for 100 seconds)
+ decline(i, 100);
+
+ // The higher the index, the more expired the lease.
+ expire(i, 10 + i);
+ }
+ }
+
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_recover",
+ skip ? lease4RecoverSkipCallout : lease4RecoverCallout));
+
+ // Run leases reclamation routine on all leases.
+ ASSERT_NO_THROW(reclaimExpiredLeases(0, 0, true));
+
+ // Make sure that the callout really was called. It was supposed to modify
+ // the callout_name_ and store the lease in callout_lease_
+ EXPECT_EQ("lease4_recover", callout_name_);
+ EXPECT_TRUE(callout_lease_);
+
+ // Leases with even indexes should not exist in the DB
+ if (skip) {
+ // Skip status should have prevented removing the lease.
+ EXPECT_TRUE(testLeases(&leaseExists, &evenLeaseIndex));
+ } else {
+ // The hook hasn't modified next step status. The lease should be gone.
+ EXPECT_TRUE(testLeases(&leaseDoesntExist, &evenLeaseIndex));
+ }
+};
+
+// This test verifies that the leases can be reclaimed without being removed
+// from the database. In such case, the leases' state is set to
+// "expired-reclaimed".
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesUpdateState) {
+ testReclaimExpiredLeasesUpdateState();
+}
+
+// This test verifies that the reclaimed leases are deleted when requested.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesDelete) {
+ testReclaimExpiredLeasesDelete();
+}
+
+// This test verifies that it is possible to specify the limit for the
+// number of reclaimed leases.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesLimit) {
+ testReclaimExpiredLeasesLimit();
+}
+
+// This test verifies that DNS updates are generated for the leases
+// for which the DNS records exist.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesWithDDNS) {
+ testReclaimExpiredLeasesWithDDNS();
+}
+
+// This test verifies that it is DNS updates are generated only for the
+// reclaimed expired leases. In this case we limit the number of leases
+// reclaimed during a single call to reclamation routine.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesWithDDNSAndLimit) {
+ testReclaimExpiredLeasesWithDDNSAndLimit();
+}
+
+// This test verifies that if some leases have invalid hostnames, the
+// lease reclamation routine continues with reclamation of leases anyway.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesInvalidHostname) {
+ testReclaimExpiredLeasesInvalidHostname();
+}
+
+// This test verifies that DNS updates are properly generated when the
+// client id is used as a primary identifier in the lease.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesWithDDNSAndClientId) {
+ testReclaimExpiredLeasesWithDDNSAndClientId();
+}
+
+// This test verifies that statistics is correctly updated when the leases
+// are reclaimed.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesStats) {
+ testReclaimExpiredLeasesStats();
+}
+
+// This test verifies that callouts are executed for each expired lease.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesHooks) {
+ testReclaimExpiredLeasesHooks();
+}
+
+// This test verifies that callouts are executed for each expired lease
+// and that the lease is not reclaimed when the skip flag is set.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesHooksWithSkip) {
+ testReclaimExpiredLeasesHooksWithSkip();
+}
+
+// This test verifies that it is possible to set the timeout for the
+// execution of the lease reclamation routine.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesTimeout) {
+ // This test needs at least 40 leases to make sense.
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 40);
+ // Run with timeout of 1.2s.
+ testReclaimExpiredLeasesTimeout(1200);
+}
+
+// This test verifies that at least one lease is reclaimed if the timeout
+// for the lease reclamation routine is shorter than the time needed for
+// the reclamation of a single lease. This prevents the situation when
+// very short timeout (perhaps misconfigured) effectively precludes leases
+// reclamation.
+TEST_F(ExpirationAllocEngine4Test, reclaimExpiredLeasesShortTimeout) {
+ // We will most likely reclaim just one lease, so 5 is more than enough.
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 5);
+ // Reclaim leases with the 1ms timeout.
+ testReclaimExpiredLeasesTimeout(1);
+}
+
+// This test verifies that expired-reclaimed leases are removed from the
+// lease database.
+TEST_F(ExpirationAllocEngine4Test, deleteExpiredReclaimedLeases) {
+ BOOST_STATIC_ASSERT(TEST_LEASES_NUM >= 10);
+ testDeleteExpiredReclaimedLeases();
+}
+
+/// This test verifies that @ref AllocEngine::reclaimExpiredLeases4 properly
+/// handles declined leases that have expired in case when it is told to
+/// remove leases.
+TEST_F(ExpirationAllocEngine4Test, reclaimDeclined1) {
+ testReclaimDeclined(true);
+}
+
+/// This test verifies that @ref AllocEngine::reclaimExpiredLeases4 properly
+/// handles declined leases that have expired in case when it is told to
+/// not remove leases. This flag should not matter and declined expired
+/// leases should always be removed.
+TEST_F(ExpirationAllocEngine4Test, reclaimDeclined2) {
+ testReclaimDeclined(false);
+}
+
+/// This test verifies that statistics are modified correctly after
+/// reclaim expired leases is called.
+TEST_F(ExpirationAllocEngine4Test, reclaimDeclinedStats) {
+ testReclaimDeclinedStats("assigned-addresses");
+}
+
+// This test verifies that the lease is reclaimed before it is reused.
+TEST_F(ExpirationAllocEngine4Test, reclaimReusedLeases) {
+ // First false value indicates that the leases will be reused.
+ // Second false value indicates that the lease will not be
+ // initially reclaimed.
+ testReclaimReusedLeases(DHCPREQUEST, false, false);
+}
+
+// This test verifies that the lease is not reclaimed when it is
+// reused and if its state indicates that it has been already reclaimed.
+TEST_F(ExpirationAllocEngine4Test, reclaimReusedLeasesAlreadyReclaimed) {
+ // false value indicates that the leases will be reused
+ // true value indicates that the lease will be initially reclaimed.
+ testReclaimReusedLeases(DHCPREQUEST, false, true);
+}
+
+// This test verifies that the expired lease is reclaimed before it
+// is renewed.
+TEST_F(ExpirationAllocEngine4Test, reclaimRenewedLeases) {
+ // true value indicates that the leases will be renewed.
+ // false value indicates that the lease will not be initially
+ // reclaimed.
+ testReclaimReusedLeases(DHCPREQUEST, true, false);
+}
+
+// This test verifies that the lease is not reclaimed upon renewal
+// if its state indicates that it has been already reclaimed.
+TEST_F(ExpirationAllocEngine4Test, reclaimRenewedLeasesAlreadyReclaimed) {
+ // First true value indicates that the leases will be renewed.
+ // Second true value indicates that the lease will be initially
+ // reclaimed.
+ testReclaimReusedLeases(DHCPREQUEST, true, true);
+}
+
+// This test verifies that the reused lease is not reclaimed when the
+// processed message is a DHCPDISCOVER.
+TEST_F(ExpirationAllocEngine4Test, reclaimReusedLeasesDiscover) {
+ testReclaimReusedLeases(DHCPDISCOVER, false, false);
+}
+
+// This test verifies that the lease being in the 'expired-reclaimed'
+// state is not reclaimed again when processing the DHCPDISCOVER
+// message.
+TEST_F(ExpirationAllocEngine4Test, reclaimRenewedLeasesDiscoverAlreadyReclaimed) {
+ testReclaimReusedLeases(DHCPDISCOVER, false, true);
+}
+
+// This test verifies if the hooks installed on lease4_recover are called
+// when the lease expires.
+TEST_F(ExpirationAllocEngine4Test, reclaimDeclinedHook1) {
+ testReclaimDeclinedHook(false); // false = don't use skip callout
+}
+
+// This test verifies if the hooks installed on lease4_recover are called
+// when the lease expires and that the next step status set to SKIP
+// causes the recovery to not be conducted.
+TEST_F(ExpirationAllocEngine4Test, reclaimDeclinedHook2) {
+ testReclaimDeclinedHook(true); // true = use skip callout
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc
new file mode 100644
index 0000000..99ea605
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/alloc_engine_hooks_unittest.cc
@@ -0,0 +1,658 @@
+// Copyright (C) 2015-2023 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 <dhcpsrv/tests/alloc_engine_utils.h>
+#include <dhcpsrv/testutils/test_utils.h>
+
+#include <hooks/server_hooks.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
+
+#include <iostream>
+
+using namespace std;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief helper class used in Hooks testing in AllocEngine6
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+class HookAllocEngine6Test : public AllocEngine6Test {
+public:
+ HookAllocEngine6Test() {
+ resetCalloutBuffers();
+ }
+
+ virtual ~HookAllocEngine6Test() {
+ resetCalloutBuffers();
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "lease6_select");
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture dtor) unloadLibraries failed" << endl;
+ }
+ }
+
+ /// @brief clears out buffers, so callouts can store received arguments
+ void resetCalloutBuffers() {
+ callback_name_ = string("");
+ callback_subnet6_.reset();
+ callback_fake_allocation_ = false;
+ callback_lease6_.reset();
+ callback_argument_names_.clear();
+ callback_addr_original_ = IOAddress("::");
+ callback_addr_updated_ = IOAddress("::");
+ callback_qry_pkt6_.reset();
+ callback_qry_options_copy_ = false;
+ callback_skip_ = 0;
+ }
+
+ /// @brief Checks if the state of the callout handle associated with a query
+ /// was reset after the callout invocation.
+ ///
+ /// The check includes verification if the status was set to 'continue' and
+ /// that all arguments were deleted.
+ ///
+ /// @param query pointer to the query which callout handle is associated
+ /// with.
+ void checkCalloutHandleReset(const Pkt6Ptr& query) {
+ CalloutHandlePtr callout_handle = query->getCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getArgumentNames().empty());
+ }
+
+ /// callback that stores received callout name and received values
+ static int
+ lease6_select_callout(CalloutHandle& callout_handle) {
+
+ callback_name_ = string("lease6_select");
+
+ callout_handle.getArgument("query6", callback_qry_pkt6_);
+ callout_handle.getArgument("subnet6", callback_subnet6_);
+ callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+
+ callback_addr_original_ = callback_lease6_->addr_;
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt6_) {
+ callback_qry_options_copy_ =
+ callback_qry_pkt6_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// callback that overrides the lease with different values
+ static int
+ lease6_select_different_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease6_select_callout(callout_handle);
+
+ // Now we need to tweak the least a bit
+ Lease6Ptr lease;
+ callout_handle.getArgument("lease6", lease);
+ callback_addr_updated_ = addr_override_;
+ lease->addr_ = callback_addr_updated_;
+ lease->preferred_lft_ = pref_override_;
+ lease->valid_lft_ = valid_override_;
+
+ return (0);
+ }
+
+ /// callback that return next step skip status
+ static int
+ lease6_select_skip_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease6_select_callout(callout_handle);
+
+ // Count the call
+ callback_skip_++;
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
+ // Values to be used in callout to override lease6 content
+ static const IOAddress addr_override_;
+ static const uint32_t pref_override_;
+ static const uint32_t valid_override_;
+
+ // Callback will store original and overridden values here
+ static IOAddress callback_addr_original_;
+ static IOAddress callback_addr_updated_;
+
+ // Buffers (callback will store received values here)
+ static string callback_name_;
+ static Subnet6Ptr callback_subnet6_;
+ static Lease6Ptr callback_lease6_;
+ static bool callback_fake_allocation_;
+ static vector<string> callback_argument_names_;
+ static Pkt6Ptr callback_qry_pkt6_;
+ static bool callback_qry_options_copy_;
+
+ // Counter for next step skip (should be not retried)
+ static unsigned callback_skip_;
+};
+
+// For some reason initialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine6Test::addr_override_("2001:db8::abcd");
+const uint32_t HookAllocEngine6Test::pref_override_ = 8000;
+const uint32_t HookAllocEngine6Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine6Test::callback_addr_original_("::");
+IOAddress HookAllocEngine6Test::callback_addr_updated_("::");
+
+string HookAllocEngine6Test::callback_name_;
+Subnet6Ptr HookAllocEngine6Test::callback_subnet6_;
+Lease6Ptr HookAllocEngine6Test::callback_lease6_;
+bool HookAllocEngine6Test::callback_fake_allocation_;
+vector<string> HookAllocEngine6Test::callback_argument_names_;
+Pkt6Ptr HookAllocEngine6Test::callback_qry_pkt6_;
+bool HookAllocEngine6Test::callback_qry_options_copy_;
+
+unsigned HookAllocEngine6Test::callback_skip_;
+
+// This test checks if the lease6_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine6Test, lease6_select) {
+
+ // Note: The following order is working as expected:
+ // 1. create AllocEngine (that register hook points)
+ // 2. call loadLibraries()
+ //
+ // This order, however, causes segfault in HooksManager
+ // 1. call loadLibraries()
+ // 2. create AllocEngine (that register hook points)
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ ASSERT_NO_THROW(HooksManager::loadLibraries(libraries));
+
+ // Install lease6_select
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_select", lease6_select_callout));
+
+ Lease6Ptr lease;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)),
+ HooksManager::createCalloutHandle());
+ ctx.currentIA().iaid_ = iaid_;
+
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease6(duid_, lease, Lease::TYPE_NA, 128);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check that callouts were indeed called
+ EXPECT_EQ("lease6_select", callback_name_);
+
+ // Check that query6 argument was set correctly
+ ASSERT_TRUE(callback_qry_pkt6_);
+ EXPECT_EQ(callback_qry_pkt6_.get(), ctx.query_.get());
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ ASSERT_TRUE(callback_lease6_);
+ detailCompareLease(callback_lease6_, from_mgr);
+
+ ASSERT_TRUE(callback_subnet6_);
+ EXPECT_EQ(subnet_->toText(), callback_subnet6_->toText());
+
+ EXPECT_FALSE(callback_fake_allocation_);
+
+ // Check if all expected parameters are reported. The order needs to be
+ // alphabetical to match the order returned by
+ // CallbackHandle::getArgumentNames()
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("fake_allocation");
+ expected_argument_names.push_back("lease6");
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("subnet6");
+
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(ctx.query_);
+}
+
+// This test checks if lease6_select callout is able to override the values
+// in a lease6.
+TEST_F(HookAllocEngine6Test, change_lease6_select) {
+
+ // Make sure that the overridden values are different than the ones from
+ // subnet originally used to create the lease
+ ASSERT_NE(pref_override_, subnet_->getPreferred());
+ ASSERT_NE(valid_override_, subnet_->getValid());
+ ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ ASSERT_NO_THROW(HooksManager::loadLibraries(libraries));
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_select", lease6_select_different_callout));
+
+ // Call allocateLeases6. Callouts should be triggered here.
+ Lease6Ptr lease;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)),
+ HooksManager::createCalloutHandle());
+ ctx.currentIA().iaid_ = iaid_;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // See if the values overridden by callout are there
+ EXPECT_TRUE(lease->addr_.equals(addr_override_));
+ EXPECT_EQ(pref_override_, lease->preferred_lft_);
+ EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+ // Now check if the lease is in the database
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check if values in the database are overridden
+ EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+ EXPECT_EQ(pref_override_, from_mgr->preferred_lft_);
+ EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(ctx.query_);
+}
+
+// This test checks if lease6_select callout can set the status to next
+// step skip without the engine to retry.
+TEST_F(HookAllocEngine6Test, skip_lease6_select) {
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ ASSERT_NO_THROW(HooksManager::loadLibraries(libraries));
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_select", lease6_select_skip_callout));
+
+ // Call allocateLeases6. Callouts should be triggered here.
+ Lease6Ptr lease;
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)),
+ HooksManager::createCalloutHandle());
+ ctx.currentIA().iaid_ = iaid_;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+ // Check that we got no lease
+ EXPECT_FALSE(lease);
+
+ // Check no retry was attempted
+ EXPECT_EQ(1, callback_skip_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(ctx.query_);
+}
+
+/// @brief helper class used in Hooks testing in AllocEngine4
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+///
+/// Note: lease4_renew callout is tested from DHCPv4 server.
+/// See HooksDhcpv4SrvTest.basic_lease4_renew in
+/// src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+class HookAllocEngine4Test : public AllocEngine4Test {
+public:
+ HookAllocEngine4Test() {
+ // The default context is not used in these tests.
+ ctx_.callout_handle_.reset();
+ resetCalloutBuffers();
+ }
+
+ virtual ~HookAllocEngine4Test() {
+ resetCalloutBuffers();
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "lease4_select");
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture dtor) unloadLibraries failed" << endl;
+ }
+ }
+
+ /// @brief clears out buffers, so callouts can store received arguments
+ void resetCalloutBuffers() {
+ callback_name_ = string("");
+ callback_subnet4_.reset();
+ callback_fake_allocation_ = false;
+ callback_lease4_.reset();
+ callback_argument_names_.clear();
+ callback_addr_original_ = IOAddress("::");
+ callback_addr_updated_ = IOAddress("::");
+ callback_qry_pkt4_.reset();
+ callback_qry_options_copy_ = false;
+ callback_skip_ = 0;
+ }
+
+ /// @brief Checks if the state of the callout handle associated with a query
+ /// was reset after the callout invocation.
+ ///
+ /// The check includes verification if the status was set to 'continue' and
+ /// that all arguments were deleted.
+ ///
+ /// @param query pointer to the query which callout handle is associated
+ /// with.
+ void checkCalloutHandleReset(const Pkt4Ptr& query) {
+ CalloutHandlePtr callout_handle = query->getCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle->getStatus());
+ EXPECT_TRUE(callout_handle->getArgumentNames().empty());
+ }
+
+ /// callback that stores received callout name and received values
+ static int
+ lease4_select_callout(CalloutHandle& callout_handle) {
+
+ callback_name_ = string("lease4_select");
+
+ callout_handle.getArgument("query4", callback_qry_pkt4_);
+ callout_handle.getArgument("subnet4", callback_subnet4_);
+ callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+ callout_handle.getArgument("lease4", callback_lease4_);
+
+ callback_addr_original_ = callback_lease4_->addr_;
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+
+ if (callback_qry_pkt4_) {
+ callback_qry_options_copy_ =
+ callback_qry_pkt4_->isCopyRetrievedOptions();
+ }
+
+ return (0);
+ }
+
+ /// callback that overrides the lease with different values
+ static int
+ lease4_select_different_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease4_select_callout(callout_handle);
+
+ // Now we need to tweak the least a bit
+ Lease4Ptr lease;
+ callout_handle.getArgument("lease4", lease);
+ callback_addr_updated_ = addr_override_;
+ lease->addr_ = callback_addr_updated_;
+ lease->valid_lft_ = valid_override_;
+
+ return (0);
+ }
+
+ /// callback that return next step skip status
+ static int
+ lease4_select_skip_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease4_select_callout(callout_handle);
+
+ // Count the call
+ callback_skip_++;
+
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ return (0);
+ }
+
+ // Values to be used in callout to override lease4 content
+ static const IOAddress addr_override_;
+ static const uint32_t valid_override_;
+
+ // Callback will store original and overridden values here
+ static IOAddress callback_addr_original_;
+ static IOAddress callback_addr_updated_;
+
+ // Buffers (callback will store received values here)
+ static string callback_name_;
+ static Subnet4Ptr callback_subnet4_;
+ static Lease4Ptr callback_lease4_;
+ static bool callback_fake_allocation_;
+ static vector<string> callback_argument_names_;
+ static Pkt4Ptr callback_qry_pkt4_;
+ static bool callback_qry_options_copy_;
+
+ // Counter for next step skip (should be not retried)
+ static unsigned callback_skip_;
+};
+
+// For some reason initialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine4Test::addr_override_("192.0.3.1");
+const uint32_t HookAllocEngine4Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine4Test::callback_addr_original_("::");
+IOAddress HookAllocEngine4Test::callback_addr_updated_("::");
+
+string HookAllocEngine4Test::callback_name_;
+Subnet4Ptr HookAllocEngine4Test::callback_subnet4_;
+Lease4Ptr HookAllocEngine4Test::callback_lease4_;
+bool HookAllocEngine4Test::callback_fake_allocation_;
+vector<string> HookAllocEngine4Test::callback_argument_names_;
+Pkt4Ptr HookAllocEngine4Test::callback_qry_pkt4_;
+bool HookAllocEngine4Test::callback_qry_options_copy_;
+
+unsigned HookAllocEngine4Test::callback_skip_;
+
+// This test checks if the lease4_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine4Test, lease4_select) {
+
+ // Note: The following order is working as expected:
+ // 1. create AllocEngine (that register hook points)
+ // 2. call loadLibraries()
+ //
+ // This order, however, causes segfault in HooksManager
+ // 1. call loadLibraries()
+ // 2. create AllocEngine (that register hook points)
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ ASSERT_NO_THROW(HooksManager::loadLibraries(libraries));
+
+ // Install lease4_select
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_select", lease4_select_callout));
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ ctx.callout_handle_ = callout_handle;
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check that callouts were indeed called
+ EXPECT_EQ("lease4_select", callback_name_);
+
+ // Check that query4 argument was set correctly
+ ASSERT_TRUE(callback_qry_pkt4_);
+ EXPECT_EQ(callback_qry_pkt4_.get(), ctx.query_.get());
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ ASSERT_TRUE(callback_lease4_);
+ detailCompareLease(callback_lease4_, from_mgr);
+
+ ASSERT_TRUE(callback_subnet4_);
+ EXPECT_EQ(subnet_->toText(), callback_subnet4_->toText());
+
+ EXPECT_EQ(callback_fake_allocation_, false);
+
+ // Check if all expected parameters are reported. The order needs to be
+ // alphabetical to match the order returned by
+ // CallbackHandle::getArgumentNames()
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("fake_allocation");
+ expected_argument_names.push_back("lease4");
+ expected_argument_names.push_back("offer_lft");
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("subnet4");
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ EXPECT_TRUE(callback_qry_options_copy_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(ctx.query_);
+}
+
+// This test checks if lease4_select callout is able to override the values
+// in a lease4.
+TEST_F(HookAllocEngine4Test, change_lease4_select) {
+
+ // Make sure that the overridden values are different than the ones from
+ // subnet originally used to create the lease
+ ASSERT_NE(valid_override_, subnet_->getValid());
+ ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ ASSERT_NO_THROW(HooksManager::loadLibraries(libraries));
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_select", lease4_select_different_callout));
+
+ // Normally, dhcpv4_srv would passed the handle when calling allocateLease4,
+ // but in tests we need to create it on our own.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false, true, "somehost.example.com.", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ ctx.callout_handle_ = callout_handle;
+
+ // Call allocateLease4. Callouts should be triggered here.
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // See if the values overridden by callout are there
+ EXPECT_TRUE(lease->addr_.equals(addr_override_));
+ EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+ // Now check if the lease is in the database
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check if values in the database are overridden
+ EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+ EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(ctx.query_);
+}
+
+// This test checks if lease4_select callout can set the status to next
+// step skip without the engine to retry.
+TEST_F(HookAllocEngine4Test, skip_lease4_select) {
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ HookLibsCollection libraries; // no libraries at this time
+ ASSERT_NO_THROW(HooksManager::loadLibraries(libraries));
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_select", lease4_select_skip_callout));
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "", false);
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ ctx.callout_handle_ = callout_handle;
+
+ Lease4Ptr lease = engine->allocateLease4(ctx);
+
+ // Check that we got no lease
+ EXPECT_FALSE(lease);
+
+ // Check no retry was attempted
+ EXPECT_EQ(1, callback_skip_);
+
+ // Check if the callout handle state was reset after the callout.
+ checkCalloutHandleReset(ctx.query_);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
new file mode 100644
index 0000000..0823bb3
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc
@@ -0,0 +1,697 @@
+// Copyright (C) 2012-2023 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 <dhcp/duid.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+#include <stats/stats_mgr.h>
+
+#include <dhcpsrv/testutils/test_utils.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+#include <set>
+#include <time.h>
+
+using namespace std;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+using namespace isc::stats;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+bool testStatistics(const std::string& stat_name, const int64_t exp_value,
+ const SubnetID subnet_id, bool fail_missing) {
+ try {
+ std::string name = (subnet_id == SUBNET_ID_UNUSED ? stat_name :
+ StatsMgr::generateName("subnet", subnet_id, stat_name));
+ ObservationPtr observation = StatsMgr::instance().getObservation(name);
+ if (observation) {
+ if (observation->getInteger().first != exp_value) {
+ ADD_FAILURE()
+ << "value of the observed statistics '"
+ << name << "' ("
+ << observation->getInteger().first << ") "
+ << "doesn't match expected value (" << exp_value << ")";
+ }
+ return (observation->getInteger().first == exp_value);
+ } else {
+ if (fail_missing) {
+ ADD_FAILURE() << "Expected statistic " << name
+ << " not found.";
+ } else {
+ if (exp_value) {
+ ADD_FAILURE() << "Checking non existent statistic and expected value is not 0. Broken test?";
+ }
+ }
+ }
+ if (subnet_id != SUBNET_ID_UNUSED) {
+ name = StatsMgr::generateName("subnet", subnet_id, StatsMgr::generateName("pool", 0, stat_name));
+ observation = StatsMgr::instance().getObservation(name);
+ if (observation) {
+ if (observation->getInteger().first != exp_value) {
+ ADD_FAILURE()
+ << "value of the observed statistics '"
+ << name << "' ("
+ << observation->getInteger().first << ") "
+ << "doesn't match expected value (" << exp_value << ")";
+ }
+ return (observation->getInteger().first == exp_value);
+ } else {
+ if (fail_missing) {
+ ADD_FAILURE() << "Expected statistic " << name
+ << " not found.";
+ } else {
+ if (exp_value) {
+ ADD_FAILURE() << "Checking non existent statistic and expected value is not 0. Broken test?";
+ }
+ }
+ }
+ }
+ } catch (const std::exception& e) {
+ ADD_FAILURE() << "Uncaught exception " << e.what();
+ } catch (...) {
+ ADD_FAILURE() << "Unknown exception";
+ }
+ return (false);
+}
+
+int64_t getStatistics(const std::string& stat_name, const SubnetID subnet_id) {
+ try {
+ std::string name = (subnet_id == SUBNET_ID_UNUSED ? stat_name :
+ StatsMgr::generateName("subnet", subnet_id, stat_name));
+ ObservationPtr observation = StatsMgr::instance().getObservation(name);
+ if (observation) {
+ return (observation->getInteger().first);
+ }
+ } catch (const std::exception& e) {
+ ADD_FAILURE() << "Uncaught exception " << e.what();
+ } catch (...) {
+ ADD_FAILURE() << "Unknown exception";
+ }
+ return (0);
+}
+
+void
+AllocEngine4Test::testReuseLease4(const AllocEnginePtr& engine,
+ Lease4Ptr& existing_lease,
+ const std::string& addr,
+ const bool fake_allocation,
+ ExpectedResult exp_result,
+ Lease4Ptr& result) {
+ ASSERT_TRUE(engine);
+
+ if (existing_lease) {
+ // If an existing lease was specified, we need to add it to the
+ // database. Let's wipe any leases for that address (if any). We
+ // ignore any errors (previous lease may not exist)
+ (void) LeaseMgrFactory::instance().deleteLease(existing_lease);
+
+ // Let's add it.
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(existing_lease));
+ }
+
+ // A client comes along, asking specifically for a given address
+ AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_,
+ IOAddress(addr), false, false,
+ "", fake_allocation);
+ if (fake_allocation) {
+ ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234));
+ } else {
+ ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+ }
+ result = engine->allocateLease4(ctx);
+
+ switch (exp_result) {
+ case SHOULD_PASS:
+ ASSERT_TRUE(result);
+
+ checkLease4(result);
+ break;
+
+ case SHOULD_FAIL:
+ ASSERT_FALSE(result);
+ break;
+ }
+}
+
+Lease4Ptr
+AllocEngine4Test::generateDeclinedLease(const std::string& addr,
+ time_t probation_period,
+ int32_t expired) {
+ // There's an assumption that hardware address is always present for IPv4
+ // packet (always non-null). Client-id is optional (may be null).
+ HWAddrPtr hwaddr(new HWAddr());
+ time_t now = time(NULL);
+ Lease4Ptr declined(new Lease4(addr, hwaddr, ClientIdPtr(), 495,
+ now, subnet_->getID()));
+ declined->decline(probation_period);
+ declined->cltt_ = now - probation_period + expired;
+ return (declined);
+}
+
+AllocEngine6Test::AllocEngine6Test() {
+ // No longer used but this means too that tests relied far too much on it.
+ //Subnet::resetSubnetID();
+
+ CfgMgr::instance().clear();
+
+ // This lease mgr needs to exist to before configuration commits.
+ factory_.create("type=memfile universe=6 persist=false");
+
+ duid_ = DuidPtr(new DUID(std::vector<uint8_t>(8, 0x42)));
+ iaid_ = 42;
+
+ // Create fresh instance of the HostMgr, and drop any previous HostMgr state.
+ HostMgr::instance().create();
+
+ // Let's use odd hardware type to check if there is no Ethernet
+ // hardcoded anywhere.
+ const uint8_t mac[] = { 0, 1, 22, 33, 44, 55 };
+ hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+ // Initialize a subnet and short address pool.
+ initSubnet(IOAddress("2001:db8:1::"),
+ IOAddress("2001:db8:1::10"),
+ IOAddress("2001:db8:1::20"),
+ IOAddress("2001:db8:1:2::"),
+ 64, 80);
+
+ initFqdn("", false, false);
+
+ StatsMgr::instance().resetAll();
+}
+
+void
+AllocEngine6Test::initSubnet(const asiolink::IOAddress& subnet,
+ const asiolink::IOAddress& pool_start,
+ const asiolink::IOAddress& pool_end,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_length,
+ const uint8_t pd_delegated_length) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ static SubnetID id(1);
+ subnet_ = Subnet6::create(subnet, 56, 100, 200, 300, 400, id++);
+ pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, pool_start, pool_end));
+
+ subnet_->addPool(pool_);
+
+ if (!pd_pool_prefix.isV6Zero()) {
+ pd_pool_ = Pool6Ptr(new Pool6(Lease::TYPE_PD, pd_pool_prefix,
+ pd_pool_length, pd_delegated_length));
+ }
+ subnet_->addPool(pd_pool_);
+
+ cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+ cfg_mgr.commit();
+}
+
+void
+AllocEngine6Test::findReservation(AllocEngine& engine,
+ AllocEngine::ClientContext6& ctx) {
+ engine.findReservation(ctx);
+ // Let's check whether there's a hostname specified in the reservation
+ if (ctx.currentHost()) {
+ std::string hostname = ctx.currentHost()->getHostname();
+ // If there is, let's use it
+ if (!hostname.empty()) {
+ ctx.hostname_ = hostname;
+ }
+ }
+}
+
+HostPtr
+AllocEngine6Test::createHost6HWAddr(bool add_to_host_mgr, IPv6Resrv::Type type,
+ HWAddrPtr& hwaddr, const asiolink::IOAddress& addr,
+ uint8_t prefix_len) {
+ HostPtr host(new Host(&hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
+ Host::IDENT_HWADDR, SUBNET_ID_UNUSED, subnet_->getID(),
+ asiolink::IOAddress("0.0.0.0")));
+ IPv6Resrv resv(type, addr, prefix_len);
+ host->addReservation(resv);
+
+ if (add_to_host_mgr) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ CfgMgr::instance().commit();
+ }
+ return (host);
+}
+
+Lease6Collection
+AllocEngine6Test::allocateTest(AllocEngine& engine, const Pool6Ptr& pool,
+ const asiolink::IOAddress& hint, bool fake,
+ bool in_pool, uint8_t hint_prefix_length) {
+ Lease::Type type = pool->getType();
+ uint8_t expected_len = pool->getLength();
+
+ Pkt6Ptr query(new Pkt6(fake ? DHCPV6_SOLICIT : DHCPV6_REQUEST, 1234));
+
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "",
+ fake, query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().type_ = type;
+ ctx.currentIA().addHint(hint, hint_prefix_length);
+
+ Lease6Collection leases;
+
+ findReservation(engine, ctx);
+ EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
+
+ for (Lease6Collection::iterator it = leases.begin(); it != leases.end(); ++it) {
+
+ // Do all checks on the lease
+ checkLease6(duid_, *it, type, expected_len, in_pool, in_pool);
+
+ // Check that context has been updated with allocated addresses or
+ // prefixes.
+ checkAllocatedResources(*it, ctx);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(type,
+ (*it)->addr_);
+ if (!fake) {
+ // This is a real (REQUEST) allocation, the lease must be in the DB
+ EXPECT_TRUE(from_mgr) << "Lease " << from_mgr->addr_.toText()
+ << " returned by allocateLeases6(), "
+ << "but was not present in LeaseMgr";
+ if (!from_mgr) {
+ return (leases);
+ }
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(*it, from_mgr);
+ } else {
+ // This is a fake (SOLICIT) allocation, the lease must not be in DB
+ EXPECT_FALSE(from_mgr) << "Lease " << from_mgr->addr_.toText()
+ << " returned by allocateLeases6(), "
+ << "was present in LeaseMgr (expected to be"
+ << " not present)";
+ if (from_mgr) {
+ return (leases);
+ }
+ }
+ }
+
+ return (leases);
+}
+
+Lease6Ptr
+AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
+ bool fake, bool in_pool) {
+ return (simpleAlloc6Test(pool, duid_, hint, fake, in_pool));
+}
+
+Lease6Ptr
+AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const IOAddress& hint,
+ uint32_t preferred, uint32_t valid,
+ uint32_t exp_preferred, uint32_t exp_valid,
+ ClientClassDefPtr class_def) {
+ Lease::Type type = pool->getType();
+ uint8_t expected_len = pool->getLength();
+
+ boost::scoped_ptr<AllocEngine> engine;
+ EXPECT_NO_THROW(engine.reset(new AllocEngine(100)));
+ // We can't use ASSERT macros in non-void methods
+ EXPECT_TRUE(engine);
+ if (!engine) {
+ return (Lease6Ptr());
+ }
+
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
+ ctx.hwaddr_ = hwaddr_;
+ ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().type_ = type;
+ ctx.currentIA().addHint(hint, expected_len, preferred, valid);
+ subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
+ subnet_->setValid(Triplet<uint32_t>(300, 400, 500));
+
+ if (class_def) {
+ std::cout << "adding class defintion" << std::endl;
+ CfgMgr::instance().getStagingCfg()->getClientClassDictionary()->addClass(class_def);
+ ctx.query_->addClass(class_def->getName());
+ }
+
+ // Set some non-standard callout status to make sure it doesn't affect the
+ // allocation.
+ ctx.callout_handle_ = HooksManager::createCalloutHandle();
+ ctx.callout_handle_->setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ findReservation(*engine, ctx);
+ Lease6Ptr lease;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that we got a lease
+ EXPECT_TRUE(lease);
+ if (!lease) {
+ return (Lease6Ptr());
+ }
+
+ // Do all checks on the lease
+ checkLease6(duid_, lease, type, expected_len, true, true);
+
+ // Check expected preferred and valid lifetimes.
+ EXPECT_EQ(exp_preferred, lease->preferred_lft_);
+ EXPECT_EQ(exp_valid, lease->valid_lft_);
+
+ return (lease);
+}
+
+Lease6Ptr
+AllocEngine6Test::simpleAlloc6Test(const Pool6Ptr& pool, const DuidPtr& duid,
+ const IOAddress& hint, bool fake, bool in_pool) {
+ Lease::Type type = pool->getType();
+ uint8_t expected_len = pool->getLength();
+
+ boost::scoped_ptr<AllocEngine> engine;
+ EXPECT_NO_THROW(engine.reset(new AllocEngine(100)));
+ // We can't use ASSERT macros in non-void methods
+ EXPECT_TRUE(engine);
+ if (!engine) {
+ return (Lease6Ptr());
+ }
+
+ Pkt6Ptr query(new Pkt6(fake ? DHCPV6_SOLICIT : DHCPV6_REQUEST, 1234));
+
+ AllocEngine::ClientContext6 ctx(subnet_, duid, false, false, "", fake, query);
+ ctx.hwaddr_ = hwaddr_;
+ ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().type_ = type;
+ ctx.currentIA().addHint(hint);
+
+ // Set some non-standard callout status to make sure it doesn't affect the
+ // allocation.
+ ctx.callout_handle_ = HooksManager::createCalloutHandle();
+ ctx.callout_handle_->setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+ findReservation(*engine, ctx);
+ Lease6Ptr lease;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that we got a lease
+ EXPECT_TRUE(lease);
+ if (!lease) {
+ return (Lease6Ptr());
+ }
+
+ // Do all checks on the lease
+ checkLease6(duid, lease, type, expected_len, in_pool, in_pool);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(type, lease->addr_);
+ if (!fake) {
+ // This is a real (REQUEST) allocation, the lease must be in the DB
+ EXPECT_TRUE(from_mgr);
+ if (!from_mgr) {
+ return (Lease6Ptr());
+ }
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+ } else {
+ // This is a fake (SOLICIT) allocation, the lease must not be in DB
+ EXPECT_FALSE(from_mgr);
+ if (from_mgr) {
+ return (Lease6Ptr());
+ }
+ }
+
+ return (lease);
+}
+
+Lease6Collection
+AllocEngine6Test::renewTest(AllocEngine& engine, const Pool6Ptr& pool,
+ AllocEngine::HintContainer& hints,
+ bool in_subnet, bool in_pool) {
+ Lease::Type type = pool->getType();
+ uint8_t expected_len = pool->getLength();
+
+ Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "",
+ false, query);
+ ctx.currentIA().hints_ = hints;
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().type_ = type;
+
+ findReservation(engine, ctx);
+ Lease6Collection leases = engine.renewLeases6(ctx);
+
+ for (Lease6Collection::iterator it = leases.begin(); it != leases.end(); ++it) {
+
+ // Do all checks on the lease
+ checkLease6(duid_, *it, type, expected_len, in_subnet, in_pool);
+
+ // Check that context has been updated with allocated addresses or
+ // prefixes.
+ checkAllocatedResources(*it, ctx);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(type,
+ (*it)->addr_);
+
+ // This is a real (REQUEST) allocation, the lease must be in the DB
+ EXPECT_TRUE(from_mgr) << "Lease " << from_mgr->addr_.toText()
+ << " returned by allocateLeases6(), "
+ << "but was not present in LeaseMgr";
+ if (!from_mgr) {
+ return (leases);
+ }
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(*it, from_mgr);
+ }
+
+ return (leases);
+}
+
+void
+AllocEngine6Test::allocWithUsedHintTest(Lease::Type type, IOAddress used_addr,
+ IOAddress requested,
+ uint8_t expected_pd_len) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Let's create a lease and put it in the LeaseMgr
+ DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
+ time_t now = time(NULL);
+ Lease6Ptr used(new Lease6(type, used_addr,
+ duid2, 1, 2, now, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Another client comes in and request an address that is in pool, but
+ // unfortunately it is used already. The same address must not be allocated
+ // twice.
+
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().type_ = type;
+ ctx.currentIA().addHint(requested);
+
+ Lease6Ptr lease;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Allocated address must be different
+ EXPECT_NE(used_addr, lease->addr_);
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_NE(requested, lease->addr_);
+
+ // Do all checks on the lease
+ checkLease6(duid_, lease, type, expected_pd_len);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+void
+AllocEngine6Test::allocBogusHint6(Lease::Type type, asiolink::IOAddress hint,
+ uint8_t expected_pd_len) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
+ ASSERT_TRUE(engine);
+
+ // Client would like to get a 3000::abc lease, which does not belong to any
+ // supported lease. Allocation engine should ignore it and carry on
+ // with the normal allocation
+
+ Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+ query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().type_ = type;
+ ctx.currentIA().addHint(hint);
+
+ Lease6Ptr lease;
+ EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_NE(hint, lease->addr_);
+
+ // Do all checks on the lease
+ checkLease6(duid_, lease, type, expected_pd_len);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+ lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+void
+AllocEngine6Test::testReuseLease6(const AllocEnginePtr& engine,
+ Lease6Ptr& existing_lease,
+ const std::string& addr,
+ const bool fake_allocation,
+ ExpectedResult exp_result,
+ Lease6Ptr& result) {
+ ASSERT_TRUE(engine);
+
+ if (existing_lease) {
+ // If an existing lease was specified, we need to add it to the
+ // database. Let's wipe any leases for that address (if any). We
+ // ignore any errors (previous lease may not exist)
+ (void) LeaseMgrFactory::instance().deleteLease(existing_lease);
+
+ // Let's add it.
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(existing_lease));
+ }
+
+ // A client comes along, asking specifically for a given address
+
+ Pkt6Ptr query(new Pkt6(fake_allocation ? DHCPV6_SOLICIT : DHCPV6_REQUEST, 1234));
+ AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "",
+ fake_allocation, query);
+ ctx.currentIA().iaid_ = iaid_;
+ ctx.currentIA().addHint(IOAddress(addr));
+
+ Lease6Collection leases;
+
+ leases = engine->allocateLeases6(ctx);
+
+ switch (exp_result) {
+ case SHOULD_PASS:
+ ASSERT_FALSE(leases.empty());
+ ASSERT_EQ(1, leases.size());
+ result = leases[0];
+
+ checkLease6(duid_, result, Lease::TYPE_NA, 128);
+ break;
+
+ case SHOULD_FAIL:
+ ASSERT_TRUE(leases.empty());
+ break;
+ }
+}
+
+Lease6Ptr
+AllocEngine6Test::generateDeclinedLease(const std::string& addr,
+ time_t probation_period,
+ int32_t expired) {
+ Lease6Ptr declined(new Lease6(Lease::TYPE_NA, IOAddress(addr),
+ duid_, iaid_, 100, 100, subnet_->getID()));
+
+ time_t now = time(NULL);
+ declined->decline(probation_period);
+ declined->cltt_ = now - probation_period + expired;
+ return (declined);
+}
+
+void
+AllocEngine4Test::initSubnet(const asiolink::IOAddress& pool_start,
+ const asiolink::IOAddress& pool_end) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ static SubnetID id(1);
+ subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, id++);
+ pool_ = Pool4Ptr(new Pool4(pool_start, pool_end));
+ subnet_->addPool(pool_);
+
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+}
+
+AllocEngine4Test::AllocEngine4Test() {
+ // No longer used but this means too that tests relied far too much on it.
+ //Subnet::resetSubnetID();
+
+ CfgMgr::instance().clear();
+
+ // This lease mgr needs to exist to before configuration commits.
+ factory_.create("type=memfile universe=4 persist=false");
+
+ // Create fresh instance of the HostMgr, and drop any previous HostMgr state.
+ HostMgr::instance().create();
+
+ clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
+ clientid2_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x56)));
+
+ uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+
+ // Let's use odd hardware type to check if there is no Ethernet
+ // hardcoded anywhere.
+ hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+
+ // Allocate different MAC address for the tests that require two
+ // different MAC addresses.
+ ++mac[sizeof(mac) - 1];
+ hwaddr2_ = HWAddrPtr(new HWAddr(mac, sizeof (mac), HTYPE_FDDI));
+
+ // instantiate cfg_mgr
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.109"));
+ cfg_mgr.commit();
+
+
+ // Create a default context. Note that remaining parameters must be
+ // assigned when needed.
+ ctx_.subnet_ = subnet_;
+ ctx_.clientid_ = clientid_;
+ ctx_.hwaddr_ = hwaddr_;
+ ctx_.callout_handle_ = HooksManager::createCalloutHandle();
+ ctx_.query_.reset(new Pkt4(DHCPREQUEST, 1234));
+
+ StatsMgr::instance().resetAll();
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
new file mode 100644
index 0000000..756b92c
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
@@ -0,0 +1,662 @@
+// Copyright (C) 2015-2023 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 LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
+#define LIBDHCPSRV_ALLOC_ENGINE_UTILS_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/alloc_engine.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @file alloc_engine_utils.h
+///
+/// @brief This is a header file for all Allocation Engine tests.
+///
+/// There used to be one, huge (over 3kloc) alloc_engine_unittest.cc. It is now
+/// split into serveral smaller files:
+/// alloc_engine_utils.h - contains test class definitions (this file)
+/// alloc_engine_utils.cc - contains test class implementation
+/// alloc_engine4_unittest.cc - all unit-tests dedicated to IPv4
+/// alloc_engine6_unittest.cc - all unit-tests dedicated to IPv6
+/// alloc_engine_hooks_unittest.cc - all unit-tests dedicated to hooks
+
+/// @brief Test that statistic manager holds a given value.
+///
+/// This function may be used in many allocation tests and there's no
+/// single base class for it. @todo consider moving it src/lib/util.
+///
+/// It checks statistics by name, either the global version when the subnet ID
+/// is SUBNET_ID_UNUSED or the subnet statistic, including the pool 0 respective
+/// statistic.
+///
+/// @param stat_name Statistic name.
+/// @param exp_value Expected value.
+/// @param subnet_id subnet_id of the desired subnet, if not zero
+/// @param fail_missing flag which indicate if test should fail if the statistic
+/// does not exist, or simply ignore it.
+///
+/// @return true if the statistic manager holds a particular value,
+/// false otherwise.
+bool testStatistics(const std::string& stat_name,
+ const int64_t exp_value,
+ const SubnetID subnet_id = SUBNET_ID_UNUSED,
+ bool fail_missing = true);
+
+/// @brief Get a value held by statistic manager.
+///
+/// This function may be used in some allocation tests and there's no
+/// single base class for it. @todo consider moving it src/lib/util.
+///
+/// @param stat_name Statistic name.
+/// @param subnet_id subnet_id of the desired subnet, if not zero.
+///
+/// @return the value held by the statistic manager or zero.
+int64_t getStatistics(const std::string& stat_name,
+ const SubnetID subnet_id = SUBNET_ID_UNUSED);
+
+/// @brief IterativeAllocator with internal methods exposed
+class NakedIterativeAllocator : public IterativeAllocator {
+public:
+ /// @brief constructor
+ ///
+ /// @param type pool types that will be iterated through
+ NakedIterativeAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
+ : IterativeAllocator(type, subnet) {
+ }
+
+ using IterativeAllocator::increaseAddress;
+ using IterativeAllocator::increasePrefix;
+};
+
+/// @brief Allocation engine with some internal methods exposed
+class NakedAllocEngine : public AllocEngine {
+public:
+ /// @brief the sole constructor
+ ///
+ /// @param attempts number of lease selection attempts before giving up
+ NakedAllocEngine(unsigned int attempts)
+ : AllocEngine(attempts) {
+ }
+
+ // Expose internal classes for testing purposes
+ using AllocEngine::updateLease4ExtendedInfo;
+
+ /// @brief Wrapper method for invoking AllocEngine4::updateLease4ExtendedInfo().
+ ///
+ /// @param lease lease to update
+ /// @param ctx current packet processing context
+ ///
+ /// @return true if extended information was changed
+ bool callUpdateLease4ExtendedInfo(const Lease4Ptr& lease,
+ AllocEngine::ClientContext4& ctx) const {
+ return (updateLease4ExtendedInfo(lease, ctx));
+ }
+
+ /// @brief Wrapper method for invoking AllocEngine6::updateLease6ExtendedInfo().
+ ///
+ /// @param lease lease to update
+ /// @param ctx current packet processing context
+ void callUpdateLease6ExtendedInfo(const Lease6Ptr& lease,
+ AllocEngine::ClientContext6& ctx) const {
+ updateLease6ExtendedInfo(lease, ctx);
+ }
+};
+
+/// @brief Used in Allocation Engine tests for IPv6
+class AllocEngine6Test : public ::testing::Test {
+public:
+
+ /// @brief Specified expected result of a given operation
+ enum ExpectedResult {
+ SHOULD_PASS,
+ SHOULD_FAIL
+ };
+
+ /// @brief Default constructor
+ ///
+ /// Sets duid_, iaid_, subnet_, pool_ fields to example values used
+ /// in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ AllocEngine6Test();
+
+ /// @brief Configures a subnet and adds one pool to it.
+ ///
+ /// This function removes existing v6 subnets before configuring
+ /// a new one.
+ ///
+ /// @param subnet Address of a subnet to be configured.
+ /// @param pool_start First address in the address pool.
+ /// @param pool_end Last address in the address pool.
+ /// @param pd_pool_prefix Prefix for the prefix delegation pool. It
+ /// defaults to 0 which means that PD pool is not specified.
+ /// @param pd_pool_length Length of the PD pool prefix.
+ /// @param pd_delegated_length Delegated prefix length.
+ void initSubnet(const asiolink::IOAddress& subnet,
+ const asiolink::IOAddress& pool_start,
+ const asiolink::IOAddress& pool_end,
+ const asiolink::IOAddress& pd_pool_prefix =
+ asiolink::IOAddress::IPV6_ZERO_ADDRESS(),
+ const uint8_t pd_pool_length = 0,
+ const uint8_t pd_delegated_length = 0);
+
+ /// @brief Initializes FQDN data for a test.
+ ///
+ /// The initialized values are used by the test fixture class members to
+ /// verify the correctness of a lease.
+ ///
+ /// @param hostname Hostname to be assigned to a lease.
+ /// @param fqdn_fwd Indicates whether or not to perform forward DNS update
+ /// for a lease.
+ /// @param fqdn_fwd Indicates whether or not to perform reverse DNS update
+ /// for a lease.
+ void initFqdn(const std::string& hostname, const bool fqdn_fwd,
+ const bool fqdn_rev) {
+ hostname_ = hostname;
+ fqdn_fwd_ = fqdn_fwd;
+ fqdn_rev_ = fqdn_rev;
+ }
+
+ /// @brief Wrapper around call to AllocEngine6::findReservation
+ ///
+ /// If a reservation is found by the engine, the function sets
+ /// ctx.hostname_ accordingly.
+ ///
+ /// @param engine allocation engine to use
+ /// @param ctx client context to pass into engine's findReservation method
+ void findReservation(AllocEngine& engine, AllocEngine::ClientContext6& ctx);
+
+ /// @brief attempts to convert leases collection to a single lease
+ ///
+ /// This operation makes sense if there is at most one lease in the
+ /// collection. Otherwise it will throw.
+ ///
+ /// @param col collection of leases (zero or one leases allowed)
+ /// @throw MultipleRecords if there is more than one lease
+ ///
+ /// @return Lease6 pointer (or NULL if collection was empty)
+ Lease6Ptr expectOneLease(const Lease6Collection& col) {
+ if (col.size() > 1) {
+ isc_throw(db::MultipleRecords, "More than one lease found in collection");
+ }
+ if (col.empty()) {
+ return (Lease6Ptr());
+ }
+ return (*col.begin());
+ }
+
+ /// @brief checks if Lease6 matches expected configuration
+ ///
+ /// @param duid pointer to the client's DUID.
+ /// @param lease lease to be checked
+ /// @param exp_type expected lease type
+ /// @param exp_pd_len expected prefix length
+ /// @param expected_in_subnet whether the lease is expected to be in subnet
+ /// @param expected_in_pool whether the lease is expected to be in dynamic
+ void checkLease6(const DuidPtr& duid,
+ const Lease6Ptr& lease,
+ Lease::Type exp_type,
+ uint8_t exp_pd_len = 128,
+ bool expected_in_subnet = true,
+ bool expected_in_pool = true) {
+
+ // that is belongs to the right subnet
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+
+ if (expected_in_subnet) {
+ EXPECT_TRUE(subnet_->inRange(lease->addr_))
+ << " address: " << lease->addr_.toText();
+ } else {
+ EXPECT_FALSE(subnet_->inRange(lease->addr_))
+ << " address: " << lease->addr_.toText();
+ }
+
+ if (expected_in_pool) {
+ EXPECT_TRUE(subnet_->inPool(exp_type, lease->addr_));
+ } else {
+ EXPECT_FALSE(subnet_->inPool(exp_type, lease->addr_));
+ }
+
+ // that it have proper parameters
+ EXPECT_EQ(exp_type, lease->type_);
+ EXPECT_EQ(iaid_, lease->iaid_);
+ if (subnet_->getValid().getMin() == subnet_->getValid().getMax()) {
+ EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+ } else {
+ EXPECT_LE(subnet_->getValid().getMin(), lease->valid_lft_);
+ EXPECT_GE(subnet_->getValid().getMax(), lease->valid_lft_);
+ }
+ if (subnet_->getPreferred().getMin() == subnet_->getPreferred().getMax()) {
+ EXPECT_EQ(subnet_->getPreferred(), lease->preferred_lft_);
+ } else {
+ EXPECT_LE(subnet_->getPreferred().getMin(), lease->preferred_lft_);
+ EXPECT_GE(subnet_->getPreferred().getMax(), lease->preferred_lft_);
+ }
+ EXPECT_EQ(exp_pd_len, lease->prefixlen_);
+ EXPECT_EQ(fqdn_fwd_, lease->fqdn_fwd_);
+ EXPECT_EQ(fqdn_rev_, lease->fqdn_rev_);
+ EXPECT_EQ(hostname_, lease->hostname_);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+ /// @todo: check cltt
+ }
+
+ /// @brief Checks if specified address or prefix has been recorded as
+ /// allocated to the client.
+ ///
+ /// @param lease Allocated lease.
+ /// @param ctx Context structure in which this function should check if
+ /// leased address is stored as allocated resource.
+ void checkAllocatedResources(const Lease6Ptr& lease,
+ AllocEngine::ClientContext6& ctx) {
+ EXPECT_TRUE(ctx.isAllocated(lease->addr_, lease->prefixlen_));
+ }
+
+ /// @brief Checks if specified address is increased properly
+ ///
+ /// Method uses gtest macros to mark check failure. This is a proxy
+ /// method, since increaseAddress was moved to IOAddress class.
+ ///
+ /// @param alloc IterativeAllocator that is tested
+ /// @param input address to be increased
+ /// @param exp_output expected address after increase
+ void checkAddrIncrease(NakedIterativeAllocator& alloc,
+ std::string input,
+ std::string exp_output) {
+ EXPECT_EQ(exp_output, alloc.increaseAddress(asiolink::IOAddress(input),
+ false, 0).toText());
+ }
+
+ /// @brief Checks if increasePrefix() works as expected
+ ///
+ /// Method uses gtest macros to mark check failure.
+ ///
+ /// @param alloc allocator to be tested
+ /// @param input IPv6 prefix (as a string)
+ /// @param prefix_len prefix len
+ /// @param exp_output expected output (string)
+ void checkPrefixIncrease(NakedIterativeAllocator& alloc,
+ std::string input,
+ uint8_t prefix_len,
+ std::string exp_output) {
+ EXPECT_EQ(exp_output,
+ alloc.increasePrefix(asiolink::IOAddress(input),
+ prefix_len).toText());
+ }
+
+ /// @brief Checks if the simple allocation can succeed
+ ///
+ /// The type of lease is determined by pool type (pool->getType())
+ ///
+ /// @param pool pool from which the lease will be allocated from
+ /// @param hint address to be used as a hint
+ /// @param fake true - this is fake allocation (SOLICIT)
+ /// @param in_pool specifies whether the lease is expected to be in pool
+ ///
+ /// @return allocated lease (or NULL)
+ Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
+ const asiolink::IOAddress& hint,
+ bool fake,
+ bool in_pool = true);
+
+ /// @brief Checks if the simple allocation can succeed with lifetimes.
+ ///
+ /// The type of lease is determined by pool type (pool->getType())
+ ///
+ /// @param pool pool from which the lease will be allocated from
+ /// @param hint address to be used as a hint
+ /// @param preferred preferred lifetime to be used as a hint
+ /// @param valid valid lifetime to be used as a hint
+ /// @param exp_preferred expected lease preferred lifetime
+ /// @param exp_valid expected lease valid lifetime
+ /// @param class_def class definition to add to the context
+ ///
+ /// @return allocated lease (or NULL)
+ Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
+ const asiolink::IOAddress& hint,
+ uint32_t preferred,
+ uint32_t valid,
+ uint32_t exp_preferred,
+ uint32_t exp_valid,
+ ClientClassDefPtr class_def = ClientClassDefPtr());
+
+ /// @brief Checks if the simple allocation can succeed for custom DUID.
+ ///
+ /// The type of lease is determined by pool type (pool->getType())
+ ///
+ /// @param pool pool from which the lease will be allocated from
+ /// @param duid pointer to the DUID used for allocation.
+ /// @param hint address to be used as a hint
+ /// @param fake true - this is fake allocation (SOLICIT)
+ /// @param in_pool specifies whether the lease is expected to be in pool
+ ///
+ /// @return allocated lease (or NULL)
+ Lease6Ptr simpleAlloc6Test(const Pool6Ptr& pool,
+ const DuidPtr& duid,
+ const asiolink::IOAddress& hint,
+ bool fake,
+ bool in_pool = true);
+
+ /// @brief Checks if the allocation can succeed.
+ ///
+ /// The type of lease is determined by pool type (pool->getType()).
+ /// This test is particularly useful in connection with @ref renewTest.
+ ///
+ /// @param engine a reference to Allocation Engine
+ /// @param pool pool from which the lease will be allocated from
+ /// @param hint address to be used as a hint
+ /// @param fake true - this is fake allocation (SOLICIT)
+ /// @param in_pool specifies whether the lease is expected to be in pool
+ /// @param hint_prefix_length The hint prefix length that the client
+ /// provided.
+ ///
+ /// @return allocated lease(s) (may be empty)
+ Lease6Collection allocateTest(AllocEngine& engine,
+ const Pool6Ptr& pool,
+ const asiolink::IOAddress& hint,
+ bool fake,
+ bool in_pool = true,
+ uint8_t hint_prefix_length = 128);
+
+ /// @brief Checks if the allocation can be renewed.
+ ///
+ /// The type of lease is determined by pool type (pool->getType()).
+ /// This test is particularly useful as a follow up to @ref allocateTest.
+ ///
+ /// @param engine a reference to Allocation Engine
+ /// @param pool pool from which the lease will be allocated from
+ /// @param hints address to be used as a hint
+ /// @param in_subnet whether the lease is expected to be in subnet
+ /// @param in_pool specifies whether the lease is expected to be in pool
+ ///
+ /// @return allocated lease(s) (may be empty)
+ Lease6Collection renewTest(AllocEngine& engine,
+ const Pool6Ptr& pool,
+ AllocEngine::HintContainer& hints,
+ bool in_subnet,
+ bool in_pool);
+
+ /// @brief Checks if the address allocation with a hint that is in range,
+ /// in pool, but is currently used, can succeed
+ ///
+ /// Method uses gtest macros to mark check failure.
+ ///
+ /// @param type lease type
+ /// @param used_addr address should be preallocated (simulates prior
+ /// allocation by some other user)
+ /// @param requested address requested by the client
+ /// @param expected_pd_len expected PD len (128 for addresses)
+ void allocWithUsedHintTest(Lease::Type type,
+ asiolink::IOAddress used_addr,
+ asiolink::IOAddress requested,
+ uint8_t expected_pd_len);
+
+ /// @brief Generic test used for IPv6 lease allocation and reuse
+ ///
+ /// This test inserts existing_lease (if specified, may be null) into the
+ /// LeaseMgr, then conducts lease allocation (pretends that client
+ /// sent either Solicit or Request, depending on fake_allocation).
+ /// Allocated lease is then returned (using result) for further inspection.
+ ///
+ /// @param alloc_engine allocation engine
+ /// @param existing_lease optional lease to be inserted in the database
+ /// @param addr address to be requested by client
+ /// @param fake_allocation true = SOLICIT, false = REQUEST
+ /// @param exp_result expected result
+ /// @param result [out] allocated lease
+ void testReuseLease6(const AllocEnginePtr& alloc_engine,
+ Lease6Ptr& existing_lease,
+ const std::string& addr,
+ const bool fake_allocation,
+ ExpectedResult exp_result,
+ Lease6Ptr& result);
+
+ /// @brief Creates a declined IPv6 lease with specified expiration time
+ ///
+ /// expired parameter controls probation period. Positive value
+ /// means that the lease will expire in X seconds. Negative means
+ /// that the lease expired X seconds ago. 0 means it expires now.
+ /// Probation period is a parameter that specifies for how long
+ /// a lease will stay unavailable after decline.
+ ///
+ /// @param addr address of the lease
+ /// @param probation_period expressed in seconds
+ /// @param expired number of seconds when the lease will expire
+ ///
+ /// @return generated lease
+ Lease6Ptr generateDeclinedLease(const std::string& addr,
+ time_t probation_period,
+ int32_t expired);
+
+ /// @brief checks if bogus hint can be ignored and the allocation succeeds
+ ///
+ /// This test checks if the allocation with a hing that is out of the blue
+ /// can succeed. The invalid hint should be ignored completely.
+ ///
+ /// @param type Lease type
+ /// @param hint hint (as sent by a client)
+ /// @param expected_pd_len (used in validation)
+ void allocBogusHint6(Lease::Type type, asiolink::IOAddress hint,
+ uint8_t expected_pd_len);
+
+ /// @brief Utility function that creates a host reservation (duid)
+ ///
+ /// @param add_to_host_mgr true if the reservation should be added
+ /// @param type specifies reservation type
+ /// @param addr specifies reserved address or prefix
+ /// @param prefix_len prefix length (should be 128 for addresses)
+ ///
+ /// @return created Host object.
+ HostPtr createHost6(bool add_to_host_mgr,
+ IPv6Resrv::Type type,
+ const asiolink::IOAddress& addr,
+ uint8_t prefix_len) {
+ HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), Host::IDENT_DUID,
+ SUBNET_ID_UNUSED, subnet_->getID(), asiolink::IOAddress("0.0.0.0")));
+ IPv6Resrv resv(type, addr, prefix_len);
+ host->addReservation(resv);
+
+ if (add_to_host_mgr) {
+ addHost(host);
+ }
+
+ return (host);
+ }
+
+ /// @brief Add a host reservation to the current configuration
+ ///
+ /// Adds the given host reservation to the current configuration by
+ /// casting it to non-const. We do it this way rather than adding it to
+ /// staging and then committing as that wipes out the current configuration
+ /// such as subnets.
+ ///
+ /// @param host host reservation to add
+ void addHost(HostPtr& host) {
+ SrvConfigPtr cfg = boost::const_pointer_cast<SrvConfig>(CfgMgr::instance().getCurrentCfg());
+ cfg->getCfgHosts()->add(host);
+ }
+
+ /// @brief Utility function that creates a host reservation (hwaddr)
+ ///
+ /// @param add_to_host_mgr true if the reservation should be added
+ /// @param type specifies reservation type
+ /// @param hwaddr hardware address to be reserved to
+ /// @param addr specifies reserved address or prefix
+ /// @param prefix_len prefix length (should be 128 for addresses)
+ ///
+ /// @return created Host object.
+ HostPtr createHost6HWAddr(bool add_to_host_mgr,
+ IPv6Resrv::Type type,
+ HWAddrPtr& hwaddr,
+ const asiolink::IOAddress& addr,
+ uint8_t prefix_len);
+
+ /// @brief Utility function that decrements cltt of a persisted lease
+ ///
+ /// This function is used to simulate the passage of time by decrementing
+ /// the lease's cltt, currently by 1. It fetches the desired lease from the
+ /// lease manager, decrements the cltt, then updates the lease in the lease
+ /// manager. Next, it refetches the lease and verifies the update took place.
+ ///
+ /// @param[in][out] lease pointer reference to the lease to modify. Upon
+ /// return it will point to the newly updated lease.
+ void rollbackPersistedCltt(Lease6Ptr& lease) {
+ ASSERT_TRUE(lease) << "rollbackPersistedCltt lease is empty";
+
+ // Fetch it, so we can update it.
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, lease->addr_);
+ ASSERT_TRUE(from_mgr) << "rollbackPersistedCltt: lease not found?";
+
+ // Decrement cltt then update it in the manager.
+ --from_mgr->cltt_;
+ ASSERT_NO_THROW(LeaseMgrFactory::instance().updateLease6(from_mgr));
+
+ // Fetch it fresh.
+ lease = LeaseMgrFactory::instance().getLease6(lease->type_, lease->addr_);
+
+ // Make sure it stuck.
+ ASSERT_EQ(lease->cltt_, from_mgr->cltt_);
+ }
+
+ virtual ~AllocEngine6Test() {
+ factory_.destroy();
+ }
+
+ DuidPtr duid_; ///< client-identifier (value used in tests)
+ HWAddrPtr hwaddr_; ///< client's hardware address
+ uint32_t iaid_; ///< IA identifier (value used in tests)
+ Subnet6Ptr subnet_; ///< subnet6 (used in tests)
+ Pool6Ptr pool_; ///< NA pool belonging to subnet_
+ Pool6Ptr pd_pool_; ///< PD pool belonging to subnet_
+ std::string hostname_; ///< Hostname
+ bool fqdn_fwd_; ///< Perform forward update for a lease.
+ bool fqdn_rev_; ///< Perform reverse update for a lease.
+ LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+ ClientClasses cc_; ///< client classes
+};
+
+/// @brief Used in Allocation Engine tests for IPv4
+class AllocEngine4Test : public ::testing::Test {
+public:
+
+ /// @brief Specified expected result of a given operation
+ enum ExpectedResult {
+ SHOULD_PASS,
+ SHOULD_FAIL
+ };
+
+ /// @brief Default constructor
+ ///
+ /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
+ /// used in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ ///
+ /// It also re-initializes the Host Manager.
+ AllocEngine4Test();
+
+ /// @brief checks if Lease4 matches expected configuration
+ ///
+ /// @param lease lease to be checked
+ void checkLease4(const Lease4Ptr& lease) {
+ // Check that is belongs to the right subnet
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_));
+
+ // Check that it has proper parameters
+ if (subnet_->getValid().getMin() == subnet_->getValid().getMax()) {
+ EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+ } else {
+ EXPECT_LE(subnet_->getValid().getMin(), lease->valid_lft_);
+ EXPECT_GE(subnet_->getValid().getMax(), lease->valid_lft_);
+ }
+ if (lease->client_id_ && !clientid_) {
+ ADD_FAILURE() << "Lease4 has a client-id, while it should have none.";
+ } else if (!lease->client_id_ && clientid_) {
+ ADD_FAILURE() << "Lease4 has no client-id, but it was expected to have one.";
+ } else if (lease->client_id_ && clientid_) {
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ }
+ EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_);
+ EXPECT_EQ(0, lease->reuseable_valid_lft_);
+ /// @todo: check cltt
+ }
+
+ /// @brief Generic test used for IPv4 lease allocation and reuse
+ ///
+ /// This test inserts existing_lease (if specified, may be null) into the
+ /// LeaseMgr, then conducts lease allocation (pretends that client
+ /// sent either Discover or Request, depending on fake_allocation).
+ /// Allocated lease is then returned (using result) for further inspection.
+ ///
+ /// @param alloc_engine allocation engine
+ /// @param existing_lease optional lease to be inserted in the database
+ /// @param addr address to be requested by client
+ /// @param fake_allocation true = DISCOVER, false = REQUEST
+ /// @param exp_result expected result
+ /// @param result [out] allocated lease
+ void testReuseLease4(const AllocEnginePtr& alloc_engine,
+ Lease4Ptr& existing_lease,
+ const std::string& addr,
+ const bool fake_allocation,
+ ExpectedResult exp_result,
+ Lease4Ptr& result);
+
+ /// @brief Creates a declined IPv4 lease with specified expiration time
+ ///
+ /// expired parameter controls probation period. Positive value
+ /// means that the lease will expire in X seconds. Negative means
+ /// that the lease expired X seconds ago. 0 means it expires now.
+ /// Probation period is a parameter that specifies for how long
+ /// a lease will stay unavailable after decline.
+ ///
+ /// @param addr address of the lease
+ /// @param probation_period expressed in seconds
+ /// @param expired number of seconds when the lease will expire
+ ///
+ /// @return generated lease
+ Lease4Ptr generateDeclinedLease(const std::string& addr,
+ time_t probation_period,
+ int32_t expired);
+
+ /// @brief Create a subnet with a specified pool of addresses.
+ ///
+ /// @param pool_start First address in the pool.
+ /// @param pool_end Last address in the pool.
+ void initSubnet(const asiolink::IOAddress& pool_start,
+ const asiolink::IOAddress& pool_end);
+
+ /// @brief Default constructor
+ virtual ~AllocEngine4Test() {
+ factory_.destroy();
+ }
+
+ ClientIdPtr clientid_; ///< Client-identifier (value used in tests)
+ ClientIdPtr clientid2_; ///< Alternative client-identifier.
+ HWAddrPtr hwaddr_; ///< Hardware address (value used in tests)
+ HWAddrPtr hwaddr2_; ///< Alternative hardware address.
+ Subnet4Ptr subnet_; ///< Subnet4 (used in tests)
+ Pool4Ptr pool_; ///< Pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory
+ AllocEngine::ClientContext4 ctx_; ///< Context information passed to various
+ ClientClasses cc_; ///< Client classes
+ ///< allocation engine functions.
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/tests/allocation_state_unittest.cc b/src/lib/dhcpsrv/tests/allocation_state_unittest.cc
new file mode 100644
index 0000000..749f757
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/allocation_state_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright (C) 2022-2023 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 <dhcpsrv/allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <testutils/multi_threading_utils.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::gregorian;
+using namespace boost::posix_time;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Derivation of the @c SubnetAllocationState with protected
+/// members and method exposed.
+class NakedSubnetAllocationState : public SubnetAllocationState {
+public:
+
+ using SubnetAllocationState::setCurrentAllocatedTimeInternal;
+ using SubnetAllocationState::last_allocated_time_;
+
+};
+
+// Tests initialization of the SubnetAllocationState.
+TEST(SubnetAllocationState, constructor) {
+ SubnetAllocationState state;
+ EXPECT_TRUE(state.getLastAllocatedTime().is_neg_infinity());
+}
+
+// Tests that the explicitly initialized allocation time can be retrieved.
+TEST(SubnetAllocationState, getLastAllocatedTime) {
+ NakedSubnetAllocationState state;
+ date gdate(greg_year(2002), greg_month(1), greg_day(20));
+
+ time_duration t(22, 59, 59, 0);
+ ptime pt = ptime(gdate, t);
+
+ state.last_allocated_time_= pt;
+
+ EXPECT_EQ(pt, state.getLastAllocatedTime());
+}
+
+// Tests that the explicitly initialized allocation time can be retrieved.
+// Multi threaded variant.
+TEST(SubnetAllocationState, getLastAllocatedTimeMultiThreading) {
+ MultiThreadingTest mt(true);
+
+ NakedSubnetAllocationState state;
+ date gdate(greg_year(2002), greg_month(1), greg_day(20));
+
+ time_duration t(22, 59, 59, 0);
+ ptime pt = ptime(gdate, t);
+
+ state.last_allocated_time_ = pt;
+
+ EXPECT_EQ(pt, state.getLastAllocatedTime());
+}
+
+// Test that current allocation time is set.
+TEST(SubnetAllocationState, setCurrentAllocatedTime) {
+ NakedSubnetAllocationState state;
+
+ state.setCurrentAllocatedTimeInternal();
+ auto pt = state.getLastAllocatedTime();
+ auto duration = pt - microsec_clock::universal_time();
+ EXPECT_LT(duration.seconds(), 10);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc b/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
new file mode 100644
index 0000000..380ed58
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright (C) 2012-2018 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include "test_get_callout_handle.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+
+namespace {
+
+TEST(CalloutHandleStoreTest, StoreRetrieve) {
+
+ // Create two DHCP4 packets during tests. The constructor arguments are
+ // arbitrary.
+ Pkt4Ptr pktptr_1(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr pktptr_2(new Pkt4(DHCPDISCOVER, 5678));
+
+ // Check that the pointers point to objects that are different, and that
+ // the pointers are the only pointers pointing to the packets.
+ ASSERT_TRUE(pktptr_1);
+ ASSERT_TRUE(pktptr_2);
+
+ ASSERT_TRUE(pktptr_1 != pktptr_2);
+
+ // Get the CalloutHandle for the first packet.
+ CalloutHandlePtr chptr_1 = getCalloutHandle(pktptr_1);
+ ASSERT_TRUE(chptr_1);
+
+ // Try getting another pointer for the same packet. Use a different
+ // pointer object to check that the function returns a handle based on the
+ // pointed-to data and not the pointer.
+ Pkt4Ptr pktptr_temp = pktptr_1;
+ CalloutHandlePtr chptr_2 = getCalloutHandle(pktptr_temp);
+ pktptr_temp.reset();
+
+ ASSERT_TRUE(chptr_2);
+ EXPECT_TRUE(chptr_1 == chptr_2);
+
+ // Now ask for a CalloutHandle for a different object. This should return
+ // a different CalloutHandle.
+ chptr_2 = getCalloutHandle(pktptr_2);
+ EXPECT_FALSE(chptr_1 == chptr_2);
+}
+
+// The followings is a trivial test to check that if the template function
+// is referred to in a separate compilation unit, only one copy of the static
+// objects stored in it are returned. (For a change, we'll use a Pkt6 as the
+// packet object.)
+
+TEST(CalloutHandleStoreTest, SeparateCompilationUnit) {
+
+ // Access the template function here.
+ Pkt6Ptr pktptr_1(new Pkt6(DHCPV6_ADVERTISE, 4321));
+ CalloutHandlePtr chptr_1 = getCalloutHandle(pktptr_1);
+ ASSERT_TRUE(chptr_1);
+
+ // Access it from within another compilation unit.
+ CalloutHandlePtr chptr_2 = isc::dhcp::test::testGetCalloutHandle(pktptr_1);
+ EXPECT_TRUE(chptr_1 == chptr_2);
+}
+
+} // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/callout_library.cc b/src/lib/dhcpsrv/tests/callout_library.cc
new file mode 100644
index 0000000..bfc2f9e
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/callout_library.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2013-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/.
+
+/// @file
+/// @brief Callout Library
+///
+/// This is the source of a test library for the basic DHCP parser
+/// tests. It just has to load - nothing else.
+
+#include <config.h>
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+// Framework functions
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+};
diff --git a/src/lib/dhcpsrv/tests/callout_params_library.cc b/src/lib/dhcpsrv/tests/callout_params_library.cc
new file mode 100644
index 0000000..4e19ed1
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/callout_params_library.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 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/.
+
+/// @file
+/// @brief Callout Library
+///
+/// This is the source of a test library for the DHCP parser tests that
+/// specify parameters. It will attempt to obtain its own parameters.
+
+#include <config.h>
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+// Framework functions
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+};
diff --git a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc
new file mode 100644
index 0000000..8706767
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc
@@ -0,0 +1,2157 @@
+// Copyright (C) 2019-2023 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 <asiolink/io_address.h>
+#include <cc/stamped_value.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cb_ctl_dhcp4.h>
+#include <dhcpsrv/cb_ctl_dhcp6.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/random_allocator.h>
+#include <dhcpsrv/testutils/memory_host_data_source.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp4.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp6.h>
+#include <hooks/server_hooks.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
+#include <testutils/gtest_utils.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+#include <iostream>
+#include <map>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::hooks;
+
+namespace {
+
+/// @brief Derivation of the @c MemHostDataSource which always returns
+/// @c false when setting IP reservations unique/non-unique mode.
+class NonUniqueHostDataSource : public MemHostDataSource {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~NonUniqueHostDataSource() {}
+
+ /// @brief Configure unique/non-unique IP reservations.
+ ///
+ /// @return Always false.
+ virtual bool setIPReservationsUnique(const bool) {
+ return (false);
+ }
+};
+
+/// @brief Pointer to the @c NonUniqueHostDataSource instance.
+typedef boost::shared_ptr<NonUniqueHostDataSource> NonUniqueHostDataSourcePtr;
+
+/// @brief Base class for testing derivations of the CBControlDHCP.
+class CBControlDHCPTest : public GenericBackendTest {
+public:
+
+ /// @brief Constructor.
+ CBControlDHCPTest()
+ : timestamp_(), object_timestamp_(), audit_entries_(),
+ modification_id_(2345) {
+ CfgMgr::instance().clear();
+ initTimestamps();
+ callback_name_ = std::string("");
+ callback_audit_entries_.reset();
+ HostMgr::create();
+ }
+
+ /// @brief Destructor.
+ virtual ~CBControlDHCPTest() {
+ // Unregister the factory to be tidy.
+ ConfigBackendDHCPv4Mgr::instance().unregisterBackendFactory("memfile");
+ CfgMgr::instance().clear();
+ // Unregister hooks.
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("cb4_updated");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("cb6_updated");
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ std::cerr << "(fixture dtor) unloadLibraries failed" << std::endl;
+ }
+ HostDataSourceFactory::deregisterFactory("test");
+ }
+
+ /// @brief Creates new CREATE audit entry.
+ ///
+ /// The audit entry is added to the @c audit_entries_ collection.
+ ///
+ /// @param object_type Object type to be associated with the audit
+ /// entry.
+ /// @param object_id Identifier of the object to be associated with
+ /// the audit entry.
+ void addCreateAuditEntry(const std::string& object_type,
+ const uint64_t object_id) {
+ AuditEntryPtr entry(new AuditEntry(object_type, object_id,
+ AuditEntry::ModificationType::CREATE,
+ ++modification_id_,
+ "some log message"));
+ audit_entries_.insert(entry);
+ }
+
+ /// @brief Creates new DELETE audit entry.
+ ///
+ /// The audit entry is added to the @c audit_entries_ collection.
+ ///
+ /// @param object_type Object type to be associated with the audit
+ /// entry.
+ /// @param object_id Identifier of the object to be associated with
+ /// the audit entry.
+ void addDeleteAuditEntry(const std::string& object_type,
+ const uint64_t object_id) {
+ AuditEntryPtr entry(new AuditEntry(object_type, object_id,
+ AuditEntry::ModificationType::DELETE,
+ ++modification_id_,
+ "some log message"));
+ audit_entries_.insert(entry);
+ }
+
+ /// @brief Initializes timestamps used in tests.
+ void initTimestamps() {
+ // Get the current timestamp and move it 30 seconds backwards.
+ auto now = boost::posix_time::second_clock::local_time() -
+ boost::posix_time::seconds(30);
+
+ // Initialize multiple timestamps from the base timestamp. The
+ // values with indexes [-5, 0] are in the past. The remaining
+ // four are in the future.
+ for (int i = -5; i < 5; ++i) {
+ timestamp_[i] = now + boost::posix_time::minutes(i);
+ }
+ }
+
+ /// @brief Returns timestamp associated with a given index.
+ ///
+ /// @param timestamp_index Index of the timestamp to be returned.
+ boost::posix_time::ptime getTimestamp(const int timestamp_index) {
+ return (timestamp_[timestamp_index]);
+ }
+
+ /// @brief Returns timestamp to be associated with a given object type.
+ ///
+ /// The object types correspond to the names of the SQL tables holding
+ /// them, e.g. dhcp4_global_parameter, dhcp4_subnet etc.
+ ///
+ /// @param object_type Object type.
+ boost::posix_time::ptime getTimestamp(const std::string& object_type) {
+ return (object_timestamp_[object_type]);
+ }
+
+ /// @brief Associates object type with a timestamp.
+ ///
+ /// When adding objects to the database, each one is associated with
+ /// a modification time value. This value is setup by unit tests
+ /// via this method.
+ void setTimestamp(const std::string& object_type, const int timestamp_index) {
+ object_timestamp_[object_type] = timestamp_[timestamp_index];
+ }
+
+ /// @brief Sets timestamps for various object types to the same value.
+ ///
+ /// @param timestamp_index Index of the timestamp to be set.
+ virtual void setAllTimestamps(const int timestamp_index) = 0;
+
+ /// @brief Checks if @c databaseConfigApply should fetch updates for specified
+ /// object types.
+ ///
+ /// @param object_type Object type.
+ bool hasConfigElement(const std::string& object_type) const {
+ if (!audit_entries_.empty()) {
+ const auto& index = audit_entries_.get<AuditEntryObjectTypeTag>();
+ auto range = index.equal_range(object_type);
+ for (auto it = range.first; it != range.second; ++it) {
+ if (((*it)->getModificationType() != AuditEntry::ModificationType::DELETE)) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ return (true);
+ }
+
+ /// @brief Check if @c databaseConfigApply should delete a given object from the
+ /// local configuration.
+ ///
+ /// @param object_type Object type.
+ /// @param object_id Object identifier.
+ bool deleteConfigElement(const std::string& object_type,
+ const uint64_t object_id) const {
+ if (!audit_entries_.empty()) {
+ const auto& index = audit_entries_.get<AuditEntryObjectTypeTag>();
+ auto range = index.equal_range(boost::make_tuple(object_type,
+ AuditEntry::ModificationType::DELETE));
+ for (auto it = range.first; it != range.second; ++it) {
+ if ((*it)->getObjectId() == object_id) {
+ return (true);
+ }
+ }
+ }
+ return (false);
+ }
+
+ /// @brief Callback that stores received callout name and received value.
+ ///
+ /// @param callout_handle Callout handle.
+ static int
+ cb4_updated_callout(CalloutHandle& callout_handle) {
+ callback_name_ = std::string("cb4_updated");
+ callout_handle.getArgument("audit_entries", callback_audit_entries_);
+ return (0);
+ }
+
+ /// @brief Callback that stores received callout name and received value.
+ ///
+ /// @param callout_handle Callout handle.
+ static int
+ cb6_updated_callout(CalloutHandle& callout_handle) {
+ callback_name_ = std::string("cb6_updated");
+ callout_handle.getArgument("audit_entries", callback_audit_entries_);
+ return (0);
+ }
+
+ /// @brief Holds test timestamps.
+ std::map<int, boost::posix_time::ptime> timestamp_;
+
+ /// @brief Holds mapping of the objects types to their timestamps.
+ std::map<std::string, boost::posix_time::ptime> object_timestamp_;
+
+ /// @brief Collection of audit entries used in the unit tests.
+ AuditEntryCollection audit_entries_;
+
+ /// @brief Modification id counter.
+ uint64_t modification_id_;
+
+ /// @brief Callback name.
+ static std::string callback_name_;
+
+ /// @brief Callback value.
+ static AuditEntryCollectionPtr callback_audit_entries_;
+};
+
+std::string CBControlDHCPTest::callback_name_;
+AuditEntryCollectionPtr CBControlDHCPTest::callback_audit_entries_;
+
+// ************************ V4 tests *********************
+
+/// @brief Naked @c CBControlDHCPv4 class exposing protected methods.
+class TestCBControlDHCPv4 : public CBControlDHCPv4 {
+public:
+ /// @brief Constructor.
+ TestCBControlDHCPv4() {
+ CfgMgr::instance().setFamily(AF_INET);
+ }
+
+ using CBControlDHCPv4::getInitialAuditRevisionTime;
+ using CBControlDHCPv4::databaseConfigApply;
+};
+
+/// @brief Test fixture class for @c CBControlDHCPv4 unit tests.
+class CBControlDHCPv4Test : public CBControlDHCPTest {
+public:
+
+ /// @brief Constructor.
+ CBControlDHCPv4Test()
+ : CBControlDHCPTest(), ctl_() {
+ ConfigBackendDHCPv4Mgr::instance().registerBackendFactory("memfile",
+ [](const DatabaseConnection::ParameterMap& params)
+ -> ConfigBackendDHCPv4Ptr {
+ return (TestConfigBackendDHCPv4Ptr(new TestConfigBackendDHCPv4(params)));
+ });
+ ConfigBackendDHCPv4Mgr::instance().addBackend("type=memfile");
+
+ // By default, set timestamps for all object types to -4. That leaves
+ // us with the possibility to use index -5 (earlier) to use as lower
+ // bound modification time so as all objects are fetched.
+ setAllTimestamps(-4);
+ }
+
+ /// @brief Sets timestamps of all DHCPv4 specific object types.
+ ///
+ /// @param timestamp_index Index of the timestamp to be set.
+ virtual void setAllTimestamps(const int timestamp_index) {
+ setTimestamp("dhcp4_global_parameter", timestamp_index);
+ setTimestamp("dhcp4_option_def", timestamp_index);
+ setTimestamp("dhcp4_options", timestamp_index);
+ setTimestamp("dhcp4_shared_network", timestamp_index);
+ setTimestamp("dhcp4_subnet", timestamp_index);
+ setTimestamp("dhcp4_client_class", timestamp_index);
+ }
+
+ /// @brief Creates test server configuration and stores it in a test
+ /// configuration backend.
+ ///
+ /// There are pairs of configuration elements stored in the database.
+ /// For example: two global parameters, two option definitions etc.
+ /// Having two elements of each type in the database is useful in tests
+ /// which verify that an element is deleted from the local configuration
+ /// as a result of being deleted from the configuration backend. In that
+ /// case the test verifies that one of the elements of the given type
+ /// is deleted and one is left.
+ void remoteStoreTestConfiguration() {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ // Insert global parameters into a database.
+ StampedValuePtr global_parameter = StampedValue::create("comment", "bar");
+ global_parameter->setModificationTime(getTimestamp("dhcp4_global_parameter"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+
+ global_parameter = StampedValue::create("next-server", "teta");
+ global_parameter->setModificationTime(getTimestamp("dhcp4_global_parameter"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+
+ // Insert option definitions into the database.
+ OptionDefinitionPtr def(new OptionDefinition("one", 101, "isc", "uint16"));
+ def->setId(1);
+ def->setModificationTime(getTimestamp("dhcp4_option_def"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ def));
+ def.reset(new OptionDefinition("two", 102, "isc", "uint16"));
+ def->setId(2);
+ def->setModificationTime(getTimestamp("dhcp4_option_def"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ def));
+
+ // Insert global options into the database.
+ OptionDescriptorPtr opt(new OptionDescriptor(createOption<OptionString>
+ (Option::V4, DHO_HOST_NAME,
+ true, false, false,
+ "new.example.com")));
+ opt->setId(1);
+ opt->space_name_ = DHCP4_OPTION_SPACE;
+ opt->setModificationTime(getTimestamp("dhcp4_options"));
+ mgr.getPool()->createUpdateOption4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ opt);
+
+ opt.reset(new OptionDescriptor(createOption<OptionString>
+ (Option::V4, DHO_TFTP_SERVER_NAME,
+ true, false, false, "tftp-my")));
+ opt->setId(2);
+ opt->space_name_ = DHCP4_OPTION_SPACE;
+ opt->setModificationTime(getTimestamp("dhcp4_options"));
+ mgr.getPool()->createUpdateOption4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ opt);
+
+ // Insert shared networks into the database.
+ SharedNetwork4Ptr network(new SharedNetwork4("one"));
+ network->setId(1);
+ network->setModificationTime(getTimestamp("dhcp4_shared_network"));
+ mgr.getPool()->createUpdateSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ network);
+
+ network.reset(new SharedNetwork4("two"));
+ network->setId(2);
+ network->setModificationTime(getTimestamp("dhcp4_shared_network"));
+ mgr.getPool()->createUpdateSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ network);
+
+ // Insert subnets into the database.
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.3.0"), 26, 1, 2, 3, SubnetID(1)));
+ subnet->setModificationTime(getTimestamp("dhcp4_subnet"));
+ subnet->setAllocatorType("random");
+ subnet->setSharedNetworkName("one");
+ mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(2)));
+ subnet->setModificationTime(getTimestamp("dhcp4_subnet"));
+ mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ subnet);
+
+ // Insert client classes into the database.
+ auto expression = boost::make_shared<Expression>();
+ ClientClassDefPtr client_class = boost::make_shared<ClientClassDef>("first-class", expression);
+ client_class->setTest("substring(option[1].hex, 0, 8) == 'my-value'");
+ client_class->setId(1);
+ client_class->setModificationTime(getTimestamp("dhcp4_client_class"));
+
+ // Add a standard option to the class.
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ OptionDescriptorPtr desc = OptionDescriptor::create(option,
+ true,
+ false,
+ "bogus-file.txt");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ desc->setModificationTime(getTimestamp("dhcp4_client_class"));
+ client_class->getCfgOption()->add(*desc, desc->space_name_);
+
+ // Add a custom option definition to the class.
+ CfgOptionDefPtr cc_cfg_option_def(new CfgOptionDef());
+ def.reset(new OptionDefinition("v4str", 201, "isc", "string"));
+ def->setId(201);
+ def->setModificationTime(getTimestamp("dhcp4_client_class"));
+ cc_cfg_option_def->add(def);
+ client_class->setCfgOptionDef(cc_cfg_option_def);
+
+ // Add a custom option to the class.
+ option = Option::create(Option::V4, 201);
+ desc = OptionDescriptor::create(option, true, false, "custom-stuff");
+ desc->space_name_ = "isc";
+ desc->setModificationTime(getTimestamp("dhcp4_client_class"));
+ client_class->getCfgOption()->add(*desc, desc->space_name_);
+
+ mgr.getPool()->createUpdateClientClass4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+
+ client_class = boost::make_shared<ClientClassDef>("second-class", expression);
+ client_class->setId(2);
+ client_class->setModificationTime(getTimestamp("dhcp4_client_class"));
+ mgr.getPool()->createUpdateClientClass4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+ }
+
+ /// @brief Deletes specified global parameter from the configuration
+ /// backend and generates audit entry.
+ ///
+ /// Note that the current Kea implementation does not track database
+ /// identifiers of the global parameters. Therefore, the identifier to
+ /// be used to create the audit entry for the deleted parameter must
+ /// be explicitly specified.
+ ///
+ /// @param parameter_name Parameter name.
+ /// @param id Parameter id.
+ void remoteDeleteGlobalParameter(const std::string& parameter_name,
+ const uint64_t id) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+ mgr.getPool()->deleteGlobalParameter4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ parameter_name);
+ addDeleteAuditEntry("dhcp4_global_parameter", id);
+ }
+
+ /// @brief Deletes specified option definition from the configuration
+ /// backend and generates audit entry.
+ ///
+ /// @param code Option code.
+ /// @param space Option space.
+ void remoteDeleteOptionDef(const uint16_t code, const std::string& space) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ auto option_def = mgr.getPool()->getOptionDef4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ code, space);
+
+ if (option_def) {
+ mgr.getPool()->deleteOptionDef4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ code, space);
+ addDeleteAuditEntry("dhcp4_option_def", option_def->getId());
+ }
+ }
+
+ /// @brief Deletes specified global option from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param code Option code.
+ /// @param space Option space.
+ void remoteDeleteOption(const uint16_t code, const std::string& space) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ auto option = mgr.getPool()->getOption4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ code, space);
+
+ if (option) {
+ mgr.getPool()->deleteOptionDef4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ code, space);
+ addDeleteAuditEntry("dhcp4_option_def", option->getId());
+ }
+ }
+
+ /// @brief Deletes specified shared network from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param name Name of the shared network to be deleted.
+ void remoteDeleteSharedNetwork(const std::string& name) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ auto network = mgr.getPool()->getSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+
+ if (network) {
+ mgr.getPool()->deleteSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+ addDeleteAuditEntry("dhcp4_shared_network", network->getId());
+ }
+ }
+
+ /// @brief Deletes specified subnet from the configuration backend and
+ /// generates audit entry.
+ void remoteDeleteSubnet(const SubnetID& id) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ mgr.getPool()->deleteSubnet4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ id);
+ addDeleteAuditEntry("dhcp4_subnet", id);
+ }
+
+ /// @brief Deletes specified client class from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param name Name of the client class to be deleted.
+ void remoteDeleteClientClass(const std::string& name) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ auto client_class = mgr.getPool()->getClientClass4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+
+ if (client_class) {
+ mgr.getPool()->deleteClientClass4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+ addDeleteAuditEntry("dhcp4_client_class", client_class->getId());
+ }
+ }
+
+ /// @brief Tests the @c CBControlDHCPv4::databaseConfigApply method.
+ ///
+ /// This test inserts configuration elements of each type into the
+ /// configuration database. Next, it calls the @c databaseConfigApply,
+ /// which should merge each object from the database for which the
+ /// CREATE or UPDATE audit entry is found. The test then verifies
+ /// if the appropriate entries have been merged.
+ ///
+ /// @param lb_modification_time Lower bound modification time to be
+ /// passed to the @c databaseConfigApply.
+ void testDatabaseConfigApply(const boost::posix_time::ptime& lb_modification_time) {
+ remoteStoreTestConfiguration();
+
+ ASSERT_FALSE(audit_entries_.empty())
+ << "Require at least one audit entry. The test is broken!";
+
+ ASSERT_NO_THROW_LOG(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ lb_modification_time, audit_entries_));
+
+ // The updates should have been merged into current configuration.
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+
+ // If there is an audit entry for global parameter and the parameter
+ // modification time is later than last audit revision time it should
+ // be merged.
+ if (hasConfigElement("dhcp4_global_parameter") &&
+ (getTimestamp("dhcp4_global_parameter") > lb_modification_time)) {
+ checkConfiguredGlobal(srv_cfg, "comment", Element::create("bar"));
+
+ } else {
+ // Otherwise it shouldn't exist.
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+ }
+
+ // If there is an audit entry for option definition and the definition
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one");
+ if (hasConfigElement("dhcp4_option_def") &&
+ getTimestamp("dhcp4_option_def") > lb_modification_time) {
+ ASSERT_TRUE(found_def);
+ EXPECT_EQ(101, found_def->getCode());
+ EXPECT_EQ(OptionDataType::OPT_UINT16_TYPE, found_def->getType());
+
+ } else {
+ EXPECT_FALSE(found_def);
+ }
+
+ // If there is an audit entry for an option and the option
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto options = srv_cfg->getCfgOption();
+ auto found_opt = options->get(DHCP4_OPTION_SPACE, DHO_HOST_NAME);
+ if (hasConfigElement("dhcp4_options") &&
+ (getTimestamp("dhcp4_options") > lb_modification_time)) {
+ ASSERT_TRUE(found_opt.option_);
+ EXPECT_EQ("new.example.com", found_opt.option_->toString());
+
+ } else {
+ EXPECT_FALSE(found_opt.option_);
+ }
+
+ // If there is an audit entry for a shared network and the network
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto networks = srv_cfg->getCfgSharedNetworks4();
+ auto found_network = networks->getByName("one");
+ if (hasConfigElement("dhcp4_shared_network") &&
+ (getTimestamp("dhcp4_shared_network") > lb_modification_time)) {
+ ASSERT_TRUE(found_network);
+ EXPECT_TRUE(found_network->hasFetchGlobalsFn());
+
+ } else {
+ EXPECT_FALSE(found_network);
+ }
+
+ // If there is an audit entry for a subnet and the subnet modification
+ // time is later than last audit revision time it should be merged.
+ auto subnets = srv_cfg->getCfgSubnets4();
+ auto found_subnet = subnets->getBySubnetId(1);
+ if (hasConfigElement("dhcp4_subnet") &&
+ (getTimestamp("dhcp4_subnet") > lb_modification_time)) {
+ ASSERT_TRUE(found_subnet);
+ EXPECT_TRUE(found_subnet->hasFetchGlobalsFn());
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (found_subnet->getAllocator(Lease::TYPE_V4)));
+
+ } else {
+ EXPECT_FALSE(found_subnet);
+ }
+
+ auto client_classes = srv_cfg->getClientClassDictionary();
+ auto found_class = client_classes->findClass("first-class");
+ if (hasConfigElement("dhcp4_client_class") &&
+ (getTimestamp("dhcp4_client_class") > lb_modification_time)) {
+ ASSERT_TRUE(found_class);
+ ASSERT_TRUE(found_class->getMatchExpr());
+ EXPECT_GT(found_class->getMatchExpr()->size(), 0);
+ EXPECT_EQ("first-class", found_class->getName());
+
+ // Check for the standard class option, make sure it has been "created".
+ auto cfg_option_desc = found_class->getCfgOption();
+ ASSERT_TRUE(cfg_option_desc);
+ auto option_desc = cfg_option_desc->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ auto option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(),
+ option_desc.formatted_value_.end()),
+ option->getData());
+
+ // Check for the custom class option, make sure it has been "created".
+ option_desc = cfg_option_desc->get("isc", 201);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(),
+ option_desc.formatted_value_.end()),
+ option->getData());
+ } else {
+ EXPECT_FALSE(found_class);
+ }
+ }
+
+ /// @brief Tests deletion of the configuration elements by the
+ /// @c CBControlDHCPv4::databaseConfigApply method.
+ ///
+ /// This test inserts configuration elements of each type into the
+ /// configuration database and calls the @c databaseConfigApply
+ /// to fetch this configuration and merge into the local server
+ /// configuration.
+ ///
+ /// Next, the test calls the specified callback function, i.e.
+ /// @c db_modifications, which deletes selected configuration
+ /// elements from the database and generates appropriate audit
+ /// entries. Finally, it calls the @c databaseConfigApply again
+ /// to process the audit entries and checks if the appropriate
+ /// configuration elements are deleted from the local configuration
+ ///
+ /// @param lb_modification_time Lower bound modification time to be
+ /// passed to the @c databaseConfigApply.
+ /// @param db_modifications Pointer to the callback function which
+ /// applies test specific modifications into the database.
+ void testDatabaseConfigApplyDelete(const boost::posix_time::ptime& lb_modification_time,
+ std::function<void()> db_modifications) {
+ // Store initial configuration into the database.
+ remoteStoreTestConfiguration();
+
+ // Since we pass an empty audit collection the server treats this
+ // as if the server is starting up and fetches the entire
+ // configuration from the database.
+ ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ ctl_.getInitialAuditRevisionTime(),
+ AuditEntryCollection());
+ // Commit the configuration so as it is moved from the staging
+ // to current.
+ CfgMgr::instance().commit();
+
+ // Run user defined callback which should delete selected configuration
+ // elements from the configuration backend. The appropriate DELETE
+ // audit entries should now be stored in the audit_entries_ collection.
+ if (db_modifications) {
+ db_modifications();
+ }
+
+ // Process the DELETE audit entries.
+ ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ lb_modification_time, audit_entries_);
+
+ // All changes should have been applied in the current configuration.
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+
+ {
+ SCOPED_TRACE("global parameters");
+ // One of the global parameters should still be there.
+ EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("next-server"));
+ if (deleteConfigElement("dhcp4_global_parameter", 1)) {
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+
+ } else {
+ EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("next-server"));
+ }
+ }
+
+ {
+ SCOPED_TRACE("option definitions");
+ // One of the option definitions should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgOptionDef()->get("isc", "two"));
+ auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one");
+ if (deleteConfigElement("dhcp4_option_def", 1)) {
+ EXPECT_FALSE(found_def);
+
+ } else {
+ EXPECT_TRUE(found_def);
+ }
+ }
+
+ {
+ SCOPED_TRACE("global options");
+ // One of the options should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_TFTP_SERVER_NAME).option_);
+ auto found_opt = srv_cfg->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_HOST_NAME);
+ if (deleteConfigElement("dhcp4_options", 1)) {
+ EXPECT_FALSE(found_opt.option_);
+
+ } else {
+ EXPECT_TRUE(found_opt.option_);
+ }
+ }
+
+ {
+ SCOPED_TRACE("shared networks");
+ // One of the shared networks should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgSharedNetworks4()->getByName("two"));
+ auto found_network = srv_cfg->getCfgSharedNetworks4()->getByName("one");
+ if (deleteConfigElement("dhcp4_shared_network", 1)) {
+ EXPECT_FALSE(found_network);
+
+ } else {
+ EXPECT_TRUE(found_network);
+ }
+ }
+
+ {
+ SCOPED_TRACE("subnets");
+ // One of the subnets should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgSubnets4()->getBySubnetId(2));
+ auto found_subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(1);
+ if (deleteConfigElement("dhcp4_subnet", 1)) {
+ EXPECT_FALSE(found_subnet);
+
+ // If the subnet has been deleted, make sure that
+ // it was detached from the shared network it belonged
+ // to, if the shared network still exists.
+ auto found_network = srv_cfg->getCfgSharedNetworks4()->getByName("one");
+ if (found_network) {
+ EXPECT_TRUE(found_network->getAllSubnets()->empty());
+ }
+
+ } else {
+ EXPECT_TRUE(found_subnet);
+ }
+ }
+
+ {
+ SCOPED_TRACE("client classes");
+ // One of the subnets should still be there.
+ EXPECT_TRUE(srv_cfg->getClientClassDictionary()->findClass("second-class"));
+ auto found_client_class = srv_cfg->getClientClassDictionary()->findClass("first-class");
+ if (deleteConfigElement("dhcp4_client_class", 1)) {
+ EXPECT_FALSE(found_client_class);
+
+ } else {
+ EXPECT_TRUE(found_client_class);
+ }
+ }
+ }
+
+ /// @brief Instance of the @c CBControlDHCPv4 used for testing.
+ TestCBControlDHCPv4 ctl_;
+};
+
+
+// This test verifies that the configuration updates for all object
+// types are merged into the current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyAll) {
+
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+ addCreateAuditEntry("dhcp4_client_class", 1);
+ addCreateAuditEntry("dhcp4_client_class", 2);
+
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that multiple configuration elements are
+// deleted from the local configuration as a result of being
+// deleted from the database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteAll) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteGlobalParameter("comment", 1);
+ remoteDeleteOptionDef(101, "isc");
+ remoteDeleteOption(DHO_HOST_NAME, DHCP4_OPTION_SPACE);
+ remoteDeleteSharedNetwork("one");
+ remoteDeleteSubnet(SubnetID(1));
+ remoteDeleteClientClass("first-class");
+ });
+}
+
+// This test verifies that an attempt to delete non-existing
+// configuration element does not cause an error.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteNonExisting) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ // Add several audit entries instructing to delete the
+ // non-existing configuration elements. The ids are set
+ // to 3, but the only existing elements have ids of 1
+ // and 2.
+ addDeleteAuditEntry("dhcp4_global_parameter", 3);
+ addDeleteAuditEntry("dhcp4_option_def", 3);
+ addDeleteAuditEntry("dhcp4_options", 3);
+ addDeleteAuditEntry("dhcp4_shared_network", 3);
+ addDeleteAuditEntry("dhcp4_subnet", 3);
+ addDeleteAuditEntry("dhcp4_client_class", 3);
+ });
+}
+
+// This test verifies that only a global parameter is merged into
+// the current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyGlobal) {
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the global parameter is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteGlobal) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteGlobalParameter("comment", 1);
+ });
+}
+
+// This test verifies that global parameter is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyGlobalNotFetched) {
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only an option definition is merged into
+// the current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyOptionDef) {
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the option definition is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteOptionDef) {
+ addDeleteAuditEntry("dhcp4_option_def", 1);
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteOptionDef(101, "isc");
+ });
+}
+
+// This test verifies that option definition is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyOptionDefNotFetched) {
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a DHCPv4 option is merged into the
+// current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyOption) {
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the global option is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteOption) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteOption(DHO_HOST_NAME, DHCP4_OPTION_SPACE);
+ });
+}
+
+// This test verifies that DHCPv4 option is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyOptionNotFetched) {
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a shared network is merged into the
+// current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplySharedNetwork) {
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the shared network is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteSharedNetwork) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteSharedNetwork("one");
+ });
+}
+
+// This test verifies that shared network is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplySharedNetworkNotFetched) {
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a subnet is merged into the current
+// configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplySubnet) {
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the subnet is deleted from the local
+// configuration as a result of being deleted from the database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteSubnet) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteSubnet(SubnetID(1));
+ });
+}
+
+// This test verifies that subnet is not fetched from the database
+// when the modification time is earlier than the last fetched audit
+// entry.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplySubnetNotFetched) {
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only client classes are merged into the current
+// configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyClientClasses) {
+ addCreateAuditEntry("dhcp4_client_class", 1);
+ addCreateAuditEntry("dhcp4_client_class", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that a client class is deleted from the local
+// configuration as a result of being deleted from the database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteClientClass) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteClientClass("first-class");
+ });
+}
+
+// This test verifies that the configuration update calls the hook.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyHook) {
+
+ // Initialize Hooks Manager.
+ HooksManager::loadLibraries(HookLibsCollection());
+
+ // Install cb4_updated.
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "cb4_updated", cb4_updated_callout));
+
+ // Create audit entries.
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+
+ // Run the test.
+ testDatabaseConfigApply(getTimestamp(-5));
+
+ // Checks the callout.
+ EXPECT_EQ("cb4_updated", callback_name_);
+ ASSERT_TRUE(callback_audit_entries_);
+ EXPECT_TRUE(audit_entries_ == *callback_audit_entries_);
+}
+
+// This test verifies that it is possible to set ip-reservations-unique
+// parameter via configuration backend and that it is successful when
+// host database backend accepts the new setting.
+TEST_F(CBControlDHCPv4Test, ipReservationsNonUniqueAccepted) {
+ // Create host data source which accepts setting non-unique IP
+ // reservations.
+ MemHostDataSourcePtr hds(new MemHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The new setting should be visible in both CfgDbAccess and HostMgr.
+ EXPECT_FALSE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_FALSE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// This test verifies that the new setting of ip-reservations-unique is not
+// accepted when one of the host database backends does not support it.
+TEST_F(CBControlDHCPv4Test, ipReservationsNonUniqueRefused) {
+ // Create host data source which does not accept setting IP reservations
+ // non-unique setting.
+ NonUniqueHostDataSourcePtr hds(new NonUniqueHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The default setting should be applied, because the backend refused to
+ // set it to false.
+ EXPECT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// This test verifies that the allocator type is inherited from the global to
+// shared network to subnet level.
+TEST_F(CBControlDHCPv4Test, allocatorInheritance) {
+ remoteStoreTestConfiguration();
+
+ // Set non-default global allocator.
+ StampedValuePtr global_parameter = StampedValue::create("allocator",
+ Element::create("flq"));
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+
+ // Merge the configuration including the allocator setting into the current
+ // configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet that has no allocator specification. The global allocator should
+ // be used for this subnet.
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+ auto subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ auto allocator = subnet->getAllocator(Lease::TYPE_V4);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("flq", allocator->getType());
+
+ // Update the shared network but use a different allocator type for it.
+ auto updated_network = boost::make_shared<SharedNetwork4>("one");
+ updated_network->setId(1);
+ updated_network->setModificationTime(getTimestamp("dhcp4_shared_network"));
+ updated_network->setAllocatorType("random");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_network));
+
+
+ // Include our subnet in this shared network.
+ auto updated_subnet = boost::make_shared<Subnet4>(IOAddress("192.0.4.0"), 26, 1, 2, 3,
+ SubnetID(2));
+ updated_subnet->setSharedNetworkName("one");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_subnet));
+ // Merge the updated configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet again. Expect that the subnet uses the shared network-level allocator.
+ subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ allocator = subnet->getAllocator(Lease::TYPE_V4);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("random", allocator->getType());
+
+ // Override the allocator at the subnet level.
+ updated_subnet = boost::make_shared<Subnet4>(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(2));
+ updated_subnet->setModificationTime(getTimestamp("dhcp4_subnet"));
+ updated_subnet->setAllocatorType("iterative");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_subnet));
+
+ addCreateAuditEntry("dhcp4_subnet", 2);
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet again and expect the subnet-level allocator.
+ subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ allocator = subnet->getAllocator(Lease::TYPE_V4);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("iterative", allocator->getType());
+}
+
+// ************************ V6 tests *********************
+
+/// @brief Naked @c CBControlDHCPv6 class exposing protected methods.
+class TestCBControlDHCPv6 : public CBControlDHCPv6 {
+public:
+ /// @brief Constructor.
+ TestCBControlDHCPv6() {
+ CfgMgr::instance().setFamily(AF_INET6);
+ }
+
+ using CBControlDHCPv6::getInitialAuditRevisionTime;
+ using CBControlDHCPv6::databaseConfigApply;
+};
+
+/// @brief Test fixture class for @c CBControlDHCPv6 unit tests.
+class CBControlDHCPv6Test : public CBControlDHCPTest {
+public:
+
+ /// @brief Constructor.
+ CBControlDHCPv6Test()
+ : CBControlDHCPTest(), ctl_() {
+ ConfigBackendDHCPv6Mgr::instance().registerBackendFactory("memfile",
+ [](const DatabaseConnection::ParameterMap& params)
+ -> ConfigBackendDHCPv6Ptr {
+ return (TestConfigBackendDHCPv6Ptr(new TestConfigBackendDHCPv6(params)));
+ });
+ ConfigBackendDHCPv6Mgr::instance().addBackend("type=memfile");
+
+ // By default, set timestamps for all object types to -4. That leaves
+ // us with the possibility to use index -5 (earlier) to use as lower
+ // bound modification time so as all objects are fetched.
+ setAllTimestamps(-4);
+ }
+
+ /// @brief Sets timestamps of all DHCPv6 specific object types.
+ ///
+ /// @param timestamp_index Index of the timestamp to be set.
+ virtual void setAllTimestamps(const int timestamp_index) {
+ setTimestamp("dhcp6_global_parameter", timestamp_index);
+ setTimestamp("dhcp6_option_def", timestamp_index);
+ setTimestamp("dhcp6_options", timestamp_index);
+ setTimestamp("dhcp6_shared_network", timestamp_index);
+ setTimestamp("dhcp6_subnet", timestamp_index);
+ setTimestamp("dhcp6_client_class", timestamp_index);
+ }
+
+ /// @brief Creates test server configuration and stores it in a test
+ /// configuration backend.
+ ///
+ /// There are pairs of configuration elements stored in the database.
+ /// For example: two global parameters, two option definitions etc.
+ /// Having two elements of each type in the database is useful in tests
+ /// which verify that an element is deleted from the local configuration
+ /// as a result of being deleted from the configuration backend. In that
+ /// case the test verifies that one of the elements of the given type
+ /// is deleted and one is left.
+ void remoteStoreTestConfiguration() {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+
+ // Insert global parameters into a database.
+ StampedValuePtr global_parameter = StampedValue::create("comment", "bar");
+ global_parameter->setModificationTime(getTimestamp("dhcp6_global_parameter"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+
+ global_parameter = StampedValue::create("data-directory", "teta");
+ global_parameter->setModificationTime(getTimestamp("dhcp6_global_parameter"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+
+ // Insert option definitions into the database.
+ OptionDefinitionPtr def(new OptionDefinition("one", 101, "isc", "uint16"));
+ def->setId(1);
+ def->setModificationTime(getTimestamp("dhcp6_option_def"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ def));
+ def.reset(new OptionDefinition("two", 102, "isc", "uint16"));
+ def->setId(2);
+ def->setModificationTime(getTimestamp("dhcp6_option_def"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ def));
+
+ // Insert global options into the database.
+ OptionDescriptorPtr opt(new OptionDescriptor(createOption<OptionString>
+ (Option::V6, D6O_BOOTFILE_URL,
+ true, false, false,
+ "some.bootfile")));
+ opt->setId(1);
+ opt->space_name_ = DHCP6_OPTION_SPACE;
+ opt->setModificationTime(getTimestamp("dhcp6_options"));
+ mgr.getPool()->createUpdateOption6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ opt);
+
+ opt.reset(new OptionDescriptor(createOption<OptionString>
+ (Option::V6, D6O_AFTR_NAME,
+ true, true, true,
+ "some.example.com")));
+ opt->setId(2);
+ opt->space_name_ = DHCP6_OPTION_SPACE;
+ opt->setModificationTime(getTimestamp("dhcp6_options"));
+ mgr.getPool()->createUpdateOption6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ opt);
+
+ // Insert shared networks into the database.
+ SharedNetwork6Ptr network(new SharedNetwork6("one"));
+ network->setId(1);
+ network->setModificationTime(getTimestamp("dhcp6_shared_network"));
+ mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ network);
+
+ network.reset(new SharedNetwork6("two"));
+ network->setId(2);
+ network->setModificationTime(getTimestamp("dhcp6_shared_network"));
+ mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ network);
+
+ // Insert subnets into the database.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4, SubnetID(1)));
+ subnet->setAllocatorType("random");
+ subnet->setPdAllocatorType("random");
+ subnet->setModificationTime(getTimestamp("dhcp6_subnet"));
+ subnet->setSharedNetworkName("one");
+ mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ subnet);
+
+ subnet.reset(new Subnet6(IOAddress("2001:db8:2::"), 64, 1, 2, 3, 4, SubnetID(2)));
+ subnet->setModificationTime(getTimestamp("dhcp6_subnet"));
+ mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ subnet);
+
+ // Insert client classes into the database.
+ auto expression = boost::make_shared<Expression>();
+ ClientClassDefPtr client_class = boost::make_shared<ClientClassDef>("first-class", expression);
+ client_class->setTest("substring(option[1].hex, 0, 8) == 'my-value'");
+ client_class->setId(1);
+ client_class->setModificationTime(getTimestamp("dhcp6_client_class"));
+
+ // Add an option to the class.
+ OptionPtr option = Option::create(Option::V6, D6O_BOOTFILE_URL);
+ OptionDescriptorPtr desc = OptionDescriptor::create(option, true,
+ false,
+ "client.boot.url");
+ desc->space_name_ = DHCP6_OPTION_SPACE;
+ desc->setModificationTime(getTimestamp("dhcp6_client_class"));
+ client_class->getCfgOption()->add(*desc, desc->space_name_);
+
+ mgr.getPool()->createUpdateClientClass6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+
+ client_class = boost::make_shared<ClientClassDef>("second-class", expression);
+ client_class->setId(2);
+ client_class->setModificationTime(getTimestamp("dhcp6_client_class"));
+ mgr.getPool()->createUpdateClientClass6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+ }
+
+ /// @brief Deletes specified global parameter from the configuration
+ /// backend and generates audit entry.
+ ///
+ /// Note that the current Kea implementation does not track database
+ /// identifiers of the global parameters. Therefore, the identifier to
+ /// be used to create the audit entry for the deleted parameter must
+ /// be explicitly specified.
+ ///
+ /// @param parameter_name Parameter name.
+ /// @param id Parameter id.
+ void remoteDeleteGlobalParameter(const std::string& parameter_name,
+ const uint64_t id) {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+ mgr.getPool()->deleteGlobalParameter6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ parameter_name);
+ addDeleteAuditEntry("dhcp6_global_parameter", id);
+ }
+
+ /// @brief Deletes specified option definition from the configuration
+ /// backend and generates audit entry.
+ ///
+ /// @param code Option code.
+ /// @param space Option space.
+ void remoteDeleteOptionDef(const uint16_t code, const std::string& space) {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+
+ auto option_def = mgr.getPool()->getOptionDef6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ code, space);
+
+ if (option_def) {
+ mgr.getPool()->deleteOptionDef6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ code, space);
+ addDeleteAuditEntry("dhcp6_option_def", option_def->getId());
+ }
+ }
+
+ /// @brief Deletes specified global option from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param code Option code.
+ /// @param space Option space.
+ void remoteDeleteOption(const uint16_t code, const std::string& space) {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+
+ auto option = mgr.getPool()->getOption6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ code, space);
+
+ if (option) {
+ mgr.getPool()->deleteOptionDef6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ code, space);
+ addDeleteAuditEntry("dhcp6_option_def", option->getId());
+ }
+ }
+
+ /// @brief Deletes specified shared network from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param name Name of the shared network to be deleted.
+ void remoteDeleteSharedNetwork(const std::string& name) {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+
+ auto network = mgr.getPool()->getSharedNetwork6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+
+ if (network) {
+ mgr.getPool()->deleteSharedNetwork6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+ addDeleteAuditEntry("dhcp6_shared_network", network->getId());
+ }
+ }
+
+ /// @brief Deletes specified subnet from the configuration backend and
+ /// generates audit entry.
+ void remoteDeleteSubnet(const SubnetID& id) {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+
+ mgr.getPool()->deleteSubnet6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ id);
+ addDeleteAuditEntry("dhcp6_subnet", id);
+ }
+
+ /// @brief Deletes specified client class from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param name Name of the client class to be deleted.
+ void remoteDeleteClientClass(const std::string& name) {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+
+ auto client_class = mgr.getPool()->getClientClass6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+
+ if (client_class) {
+ mgr.getPool()->deleteClientClass6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+ addDeleteAuditEntry("dhcp6_client_class", client_class->getId());
+ }
+ }
+
+ /// @brief Tests the @c CBControlDHCPv6::databaseConfigApply method.
+ ///
+ /// This test inserts configuration elements of each type into the
+ /// configuration database. Next, it calls the @c databaseConfigApply,
+ /// which should merge each object from the database for which the
+ /// CREATE or UPDATE audit entry is found. The test then verifies
+ /// if the appropriate entries have been merged.
+ ///
+ /// @param lb_modification_time Lower bound modification time to be
+ /// passed to the @c databaseConfigApply.
+ void testDatabaseConfigApply(const boost::posix_time::ptime& lb_modification_time) {
+ remoteStoreTestConfiguration();
+
+ ASSERT_FALSE(audit_entries_.empty())
+ << "Require at least one audit entry. The test is broken!";
+
+ ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ lb_modification_time, audit_entries_);
+
+ // The updates should have been merged into current configuration.
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+
+ // If there is an audit entry for global parameter and the parameter
+ // modification time is later than last audit revision time it should
+ // be merged.
+ if (hasConfigElement("dhcp6_global_parameter") &&
+ (getTimestamp("dhcp6_global_parameter") > lb_modification_time)) {
+ checkConfiguredGlobal(srv_cfg, "comment", Element::create("bar"));
+
+ } else {
+ // Otherwise it shouldn't exist.
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+ }
+
+ // If there is an audit entry for option definition and the definition
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one");
+ if (hasConfigElement("dhcp6_option_def") &&
+ getTimestamp("dhcp6_option_def") > lb_modification_time) {
+ ASSERT_TRUE(found_def);
+ EXPECT_EQ(101, found_def->getCode());
+ EXPECT_EQ(OptionDataType::OPT_UINT16_TYPE, found_def->getType());
+
+ } else {
+ EXPECT_FALSE(found_def);
+ }
+
+ // If there is an audit entry for an option and the option
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto options = srv_cfg->getCfgOption();
+ auto found_opt = options->get(DHCP6_OPTION_SPACE, D6O_BOOTFILE_URL);
+ if (hasConfigElement("dhcp6_options") &&
+ (getTimestamp("dhcp6_options") > lb_modification_time)) {
+ ASSERT_TRUE(found_opt.option_);
+ EXPECT_EQ("some.bootfile", found_opt.option_->toString());
+
+ } else {
+ EXPECT_FALSE(found_opt.option_);
+ }
+
+ // If there is an audit entry for a shared network and the network
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto networks = srv_cfg->getCfgSharedNetworks6();
+ auto found_network = networks->getByName("one");
+ if (hasConfigElement("dhcp6_shared_network") &&
+ (getTimestamp("dhcp6_shared_network") > lb_modification_time)) {
+ ASSERT_TRUE(found_network);
+ EXPECT_TRUE(found_network->hasFetchGlobalsFn());
+
+ } else {
+ EXPECT_FALSE(found_network);
+ }
+
+ // If there is an audit entry for a subnet and the subnet modification
+ // time is later than last audit revision time it should be merged.
+ auto subnets = srv_cfg->getCfgSubnets6();
+ auto found_subnet = subnets->getBySubnetId(1);
+ if (hasConfigElement("dhcp6_subnet") &&
+ (getTimestamp("dhcp6_subnet") > lb_modification_time)) {
+ ASSERT_TRUE(found_subnet);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (found_subnet->getAllocator(Lease::TYPE_NA)));
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (found_subnet->getAllocator(Lease::TYPE_PD)));
+ EXPECT_TRUE(found_subnet->hasFetchGlobalsFn());
+
+ } else {
+ EXPECT_FALSE(found_subnet);
+ }
+
+ auto client_classes = srv_cfg->getClientClassDictionary();
+ auto found_class = client_classes->findClass("first-class");
+ if (hasConfigElement("dhcp6_client_class") &&
+ (getTimestamp("dhcp6_client_class") > lb_modification_time)) {
+ ASSERT_TRUE(found_class);
+ ASSERT_TRUE(found_class->getMatchExpr());
+ EXPECT_GT(found_class->getMatchExpr()->size(), 0);
+ EXPECT_EQ("first-class", found_class->getName());
+
+ // Check for class option, make sure it has been "created".
+ auto cfg_option_desc = found_class->getCfgOption();
+ ASSERT_TRUE(cfg_option_desc);
+ auto option_desc = cfg_option_desc->get(DHCP6_OPTION_SPACE, D6O_BOOTFILE_URL);
+ auto option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(),
+ option_desc.formatted_value_.end()),
+ option->getData());
+ } else {
+ EXPECT_FALSE(found_class);
+ }
+ }
+
+ /// @brief Tests deletion of the configuration elements by the
+ /// @c CBControlDHCPv6::databaseConfigApply method.
+ ///
+ /// This test inserts configuration elements of each type into the
+ /// configuration database and calls the @c databaseConfigApply
+ /// to fetch this configuration and merge into the local server
+ /// configuration.
+ ///
+ /// Next, the test calls the specified callback function, i.e.
+ /// @c db_modifications, which deletes selected configuration
+ /// elements from the database and generates appropriate audit
+ /// entries. Finally, it calls the @c databaseConfigApply again
+ /// to process the audit entries and checks if the appropriate
+ /// configuration elements are deleted from the local configuration
+ ///
+ /// @param lb_modification_time Lower bound modification time to be
+ /// passed to the @c databaseConfigApply.
+ /// @param db_modifications Pointer to the callback function which
+ /// applies test specific modifications into the database.
+ void testDatabaseConfigApplyDelete(const boost::posix_time::ptime& lb_modification_time,
+ std::function<void()> db_modifications) {
+ // Store initial configuration into the database.
+ remoteStoreTestConfiguration();
+
+ // Since we pass an empty audit collection the server treats this
+ // as if the server is starting up and fetches the entire
+ // configuration from the database.
+ ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ ctl_.getInitialAuditRevisionTime(),
+ AuditEntryCollection());
+ // Commit the configuration so as it is moved from the staging
+ // to current.
+ CfgMgr::instance().commit();
+
+ // Run user defined callback which should delete selected configuration
+ // elements from the configuration backend. The appropriate DELETE
+ // audit entries should now be stored in the audit_entries_ collection.
+ if (db_modifications) {
+ db_modifications();
+ }
+
+ // Process the DELETE audit entries.
+ ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ lb_modification_time, audit_entries_);
+
+ // All changes should have been applied in the current configuration.
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+
+ {
+ SCOPED_TRACE("global parameters");
+ // One of the global parameters should still be there.
+ EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("data-directory"));
+ if (deleteConfigElement("dhcp6_global_parameter", 1)) {
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+
+ } else {
+ EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("comment"));
+ }
+ }
+
+ {
+ SCOPED_TRACE("option definitions");
+ // One of the option definitions should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgOptionDef()->get("isc", "two"));
+ auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one");
+ if (deleteConfigElement("dhcp6_option_def", 1)) {
+ EXPECT_FALSE(found_def);
+
+ } else {
+ EXPECT_TRUE(found_def);
+ }
+ }
+
+ {
+ SCOPED_TRACE("global options");
+ // One of the options should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_AFTR_NAME).option_);
+ auto found_opt = srv_cfg->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_AFTR_NAME);
+ if (deleteConfigElement("dhcp6_options", 1)) {
+ EXPECT_FALSE(found_opt.option_);
+
+ } else {
+ EXPECT_TRUE(found_opt.option_);
+ }
+ }
+
+ {
+ SCOPED_TRACE("shared networks");
+ // One of the shared networks should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgSharedNetworks6()->getByName("two"));
+ auto found_network = srv_cfg->getCfgSharedNetworks6()->getByName("one");
+ if (deleteConfigElement("dhcp6_shared_network", 1)) {
+ EXPECT_FALSE(found_network);
+
+ } else {
+ EXPECT_TRUE(found_network);
+ }
+ }
+
+ {
+ SCOPED_TRACE("subnets");
+ // One of the subnets should still be there.
+ EXPECT_TRUE(srv_cfg->getCfgSubnets6()->getBySubnetId(2));
+ auto found_subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(1);
+ if (deleteConfigElement("dhcp6_subnet", 1)) {
+ EXPECT_FALSE(found_subnet);
+ // If the subnet has been deleted, make sure that
+ // it was detached from the shared network it belonged
+ // to, if the shared network still exists.
+ auto found_network = srv_cfg->getCfgSharedNetworks6()->getByName("one");
+ if (found_network) {
+ EXPECT_TRUE(found_network->getAllSubnets()->empty());
+ }
+
+ } else {
+ EXPECT_TRUE(found_subnet);
+ }
+ }
+ }
+
+ /// @brief Instance of the @c CBControlDHCPv6 used for testing.
+ TestCBControlDHCPv6 ctl_;
+};
+
+
+// This test verifies that the configuration updates for all object
+// types are merged into the current configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyAll) {
+
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ addCreateAuditEntry("dhcp6_option_def", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ addCreateAuditEntry("dhcp6_options", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ addCreateAuditEntry("dhcp6_subnet", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+ addCreateAuditEntry("dhcp6_client_class", 1);
+ addCreateAuditEntry("dhcp6_client_class", 2);
+
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that multiple configuration elements are
+// deleted from the local configuration as a result of being
+// deleted from the database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteAll) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteGlobalParameter("comment", 1);
+ remoteDeleteOptionDef(101, "isc");
+ remoteDeleteOption(D6O_BOOTFILE_URL, DHCP6_OPTION_SPACE);
+ remoteDeleteSharedNetwork("one");
+ remoteDeleteSubnet(SubnetID(1));
+ });
+}
+
+// This test verifies that an attempt to delete non-existing
+// configuration element does not cause an error.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteNonExisting) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ // Add several audit entries instructing to delete the
+ // non-existing configuration elements. The ids are set
+ // to 3, but the only existing elements have ids of 1
+ // and 2.
+ addDeleteAuditEntry("dhcp6_global_parameter", 3);
+ addDeleteAuditEntry("dhcp6_option_def", 3);
+ addDeleteAuditEntry("dhcp6_options", 3);
+ addDeleteAuditEntry("dhcp6_shared_network", 3);
+ addDeleteAuditEntry("dhcp6_subnet", 3);
+ });
+}
+
+// This test verifies that only a global parameter is merged into
+// the current configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyGlobal) {
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the global parameter is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteGlobal) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteGlobalParameter("comment", 1);
+ });
+}
+
+// This test verifies that global parameter is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyGlobalNotFetched) {
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only an option definition is merged into
+// the current configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyOptionDef) {
+ addCreateAuditEntry("dhcp6_option_def", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the option definition is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteOptionDef) {
+ addDeleteAuditEntry("dhcp6_option_def", 1);
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteOptionDef(101, "isc");
+ });
+}
+
+// This test verifies that option definition is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyOptionDefNotFetched) {
+ addCreateAuditEntry("dhcp6_option_def", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a DHCPv6 option is merged into the
+// current configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyOption) {
+ addCreateAuditEntry("dhcp6_options", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the global option is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteOption) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteOption(D6O_BOOTFILE_URL, DHCP6_OPTION_SPACE);
+ });
+}
+
+// This test verifies that DHCPv6 option is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyOptionNotFetched) {
+ addCreateAuditEntry("dhcp6_options", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a shared network is merged into the
+// current configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplySharedNetwork) {
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the shared network is deleted from
+// the local configuration as a result of being deleted from the
+// database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteSharedNetwork) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteSharedNetwork("one");
+ });
+}
+
+// This test verifies that shared network is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplySharedNetworkNotFetched) {
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a subnet is merged into the current
+// configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplySubnet) {
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ addCreateAuditEntry("dhcp6_subnet", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that the subnet is deleted from the local
+// configuration as a result of being deleted from the database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteSubnet) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteSubnet(SubnetID(1));
+ });
+}
+
+// This test verifies that subnet is not fetched from the database
+// when the modification time is earlier than the last fetched audit
+// entry.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplySubnetNotFetched) {
+ addCreateAuditEntry("dhcp6_subnet", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only client classes are merged into the current
+// configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyClientClasses) {
+ addCreateAuditEntry("dhcp6_client_class", 1);
+ addCreateAuditEntry("dhcp6_client_class", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that a client class is deleted from the local
+// configuration as a result of being deleted from the database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteClientClass) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteClientClass("first-class");
+ });
+}
+
+// This test verifies that the configuration update calls the hook.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyHook) {
+
+ // Initialize Hooks Manager.
+ HooksManager::loadLibraries(HookLibsCollection());
+
+ // Install cb6_updated.
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "cb6_updated", cb6_updated_callout));
+
+ // Create audit entries.
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ addCreateAuditEntry("dhcp6_option_def", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ addCreateAuditEntry("dhcp6_options", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ addCreateAuditEntry("dhcp6_subnet", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+
+ // Run the test.
+ testDatabaseConfigApply(getTimestamp(-5));
+
+ // Checks the callout.
+ EXPECT_EQ("cb6_updated", callback_name_);
+ ASSERT_TRUE(callback_audit_entries_);
+ EXPECT_TRUE(audit_entries_ == *callback_audit_entries_);
+}
+
+// This test verifies that it is possible to set ip-reservations-unique
+// parameter via configuration backend and that it is successful when
+// host database backend accepts the new setting.
+TEST_F(CBControlDHCPv6Test, ipReservationsNonUniqueAccepted) {
+ // Create host data source which accepts setting non-unique IP
+ // reservations.
+ MemHostDataSourcePtr hds(new MemHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The new setting should be visible in both CfgDbAccess and HostMgr.
+ EXPECT_FALSE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_FALSE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// This test verifies that the new setting of ip-reservations-unique is not
+// accepted when one of the host database backends does not support it.
+TEST_F(CBControlDHCPv6Test, ipReservationsNonUniqueRefused) {
+ // Create host data source which does not accept setting IP reservations
+ // non-unique setting.
+ NonUniqueHostDataSourcePtr hds(new NonUniqueHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The default setting should be applied, because the backend refused to
+ // set it to false.
+ EXPECT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// This test verifies that the allocator type is inherited from the global to
+// shared network to subnet level.
+TEST_F(CBControlDHCPv6Test, allocatorInheritance) {
+ remoteStoreTestConfiguration();
+
+ // Set non-default global allocator.
+ StampedValuePtr global_parameter = StampedValue::create("allocator",
+ Element::create("random"));
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+
+ // Merge the configuration including the allocator setting into the current
+ // configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet that has no allocator specification. The global allocator should
+ // be used for this subnet.
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+ auto subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ auto allocator = subnet->getAllocator(Lease::TYPE_NA);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("random", allocator->getType());
+
+ // Update the shared network but use a different allocator type for it.
+ auto updated_network = boost::make_shared<SharedNetwork6>("one");
+ updated_network->setId(1);
+ updated_network->setModificationTime(getTimestamp("dhcp6_shared_network"));
+ updated_network->setAllocatorType("iterative");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_network));
+
+ // Include our subnet in this shared network.
+ auto updated_subnet = boost::make_shared<Subnet6>(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4,
+ SubnetID(2));
+ updated_subnet->setSharedNetworkName("one");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_subnet));
+ // Merge the updated configuration.
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet again. Expect that the subnet uses the shared network-level allocator.
+ subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ allocator = subnet->getAllocator(Lease::TYPE_NA);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("iterative", allocator->getType());
+
+ // Override the allocator at the subnet level.
+ updated_subnet = boost::make_shared<Subnet6>(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4,
+ SubnetID(2));
+ updated_subnet->setSharedNetworkName("one");
+ updated_subnet->setModificationTime(getTimestamp("dhcp6_subnet"));
+ updated_subnet->setAllocatorType("random");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_subnet));
+
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet again and expect the subnet-level allocator.
+ subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ allocator = subnet->getAllocator(Lease::TYPE_NA);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("random", allocator->getType());
+}
+
+// This test verifies that the prefix delegation allocator type is inherited from the
+// global to shared network to subnet level.
+TEST_F(CBControlDHCPv6Test, pdAllocatorInheritance) {
+ remoteStoreTestConfiguration();
+
+ // Set non-default global allocator.
+ StampedValuePtr global_parameter = StampedValue::create("pd-allocator",
+ Element::create("flq"));
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+
+ // Merge the configuration including the allocator setting into the current
+ // configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet that has no allocator specification. The global allocator should
+ // be used for this subnet.
+ auto srv_cfg = CfgMgr::instance().getCurrentCfg();
+ auto subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ auto allocator = subnet->getAllocator(Lease::TYPE_PD);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("flq", allocator->getType());
+
+ // Update the shared network but use a different allocator type for it.
+ auto updated_network = boost::make_shared<SharedNetwork6>("one");
+ updated_network->setId(1);
+ updated_network->setModificationTime(getTimestamp("dhcp6_shared_network"));
+ updated_network->setPdAllocatorType("iterative");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_network));
+
+ // Include our subnet in this shared network.
+ auto updated_subnet = boost::make_shared<Subnet6>(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4,
+ SubnetID(2));
+ updated_subnet->setSharedNetworkName("one");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_subnet));
+ // Merge the updated configuration.
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet again. Expect that the subnet uses the shared network-level allocator.
+ subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ allocator = subnet->getAllocator(Lease::TYPE_PD);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("iterative", allocator->getType());
+
+ // Override the allocator at the subnet level.
+ updated_subnet = boost::make_shared<Subnet6>(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4,
+ SubnetID(2));
+ updated_subnet->setSharedNetworkName("one");
+ updated_subnet->setModificationTime(getTimestamp("dhcp6_subnet"));
+ updated_subnet->setPdAllocatorType("random");
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ updated_subnet));
+
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+
+ // Get the subnet again and expect the subnet-level allocator.
+ subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ allocator = subnet->getAllocator(Lease::TYPE_PD);
+ ASSERT_TRUE(allocator);
+ EXPECT_EQ("random", allocator->getType());
+}
+
+
+}
diff --git a/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc b/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc
new file mode 100644
index 0000000..738ddfc
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc
@@ -0,0 +1,338 @@
+// Copyright (C) 2016-2022 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 <cc/data.h>
+#include <database/dbaccess_parser.h>
+#include <dhcpsrv/cfg_db_access.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+#if defined HAVE_MYSQL
+#include <mysql/testutils/mysql_schema.h>
+#endif
+
+#if defined HAVE_PGSQL
+#include <pgsql/testutils/pgsql_schema.h>
+#endif
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+// Test default values of the lease and host database access strings.
+TEST(CfgDbAccessTest, defaults) {
+ CfgDbAccess cfg;
+ EXPECT_EQ("type=memfile", cfg.getLeaseDbAccessString());
+ std::string expected = "{ \"type\": \"memfile\" }";
+ runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
+ EXPECT_TRUE(cfg.getHostDbAccessString().empty());
+ runToElementTest<CfgHostDbAccess>("[ ]", CfgHostDbAccess(cfg));
+}
+
+// This test verifies that it is possible to set the lease database
+// string.
+TEST(CfgDbAccessTest, setLeaseDbAccessString) {
+ CfgDbAccess cfg;
+ ASSERT_NO_THROW(cfg.setLeaseDbAccessString("type=mysql"));
+ EXPECT_EQ("type=mysql", cfg.getLeaseDbAccessString());
+
+ // Check unparse
+ std::string expected = "{ \"type\": \"mysql\" }";
+ runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
+ // Append additional parameter.
+ ASSERT_NO_THROW(cfg.setAppendedParameters("universe=4"));
+ EXPECT_EQ("type=mysql universe=4", cfg.getLeaseDbAccessString());
+
+ // Additional parameters are not in lease_db_access_
+ runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
+ // If access string is empty, no parameters will be appended.
+ ASSERT_NO_THROW(cfg.setLeaseDbAccessString(""));
+ EXPECT_TRUE(cfg.getLeaseDbAccessString().empty());
+}
+
+
+// This test verifies that it is possible to set the host database
+// string.
+TEST(CfgDbAccessTest, setHostDbAccessString) {
+ CfgDbAccess cfg;
+ ASSERT_NO_THROW(cfg.setHostDbAccessString("type=mysql"));
+ EXPECT_EQ("type=mysql", cfg.getHostDbAccessString());
+
+ // Check unparse
+ std::string expected = "[ { \"type\": \"mysql\" } ]";
+ runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg));
+
+ // Append additional parameter.
+ ASSERT_NO_THROW(cfg.setAppendedParameters("universe=4"));
+ EXPECT_EQ("type=mysql universe=4", cfg.getHostDbAccessString());
+
+ // Additional parameters are not in host_db_access_
+ runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg));
+
+ // If access string is empty, no parameters will be appended.
+ CfgDbAccess cfg1;
+ ASSERT_NO_THROW(cfg1.setHostDbAccessString(""));
+ EXPECT_TRUE(cfg1.getHostDbAccessString().empty());
+}
+
+// This test verifies that it is possible to set multiple host
+// database string.
+TEST(CfgDbAccessTest, pushHostDbAccessString) {
+ // Push a string.
+ CfgDbAccess cfg;
+ ASSERT_NO_THROW(cfg.setHostDbAccessString("type=foo"));
+
+ // Push another in front.
+ ASSERT_NO_THROW(cfg.setHostDbAccessString("type=mysql", true));
+ EXPECT_EQ("type=mysql", cfg.getHostDbAccessString());
+
+ // Push a third string.
+ ASSERT_NO_THROW(cfg.setHostDbAccessString("type=bar"));
+
+ // Check unparse
+ std::string expected = "[ { \"type\": \"mysql\" }, ";
+ expected += "{ \"type\": \"foo\" }, { \"type\": \"bar\" } ]";
+ runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg));
+
+ // Check access strings
+ std::list<std::string> hal = cfg.getHostDbAccessStringList();
+ ASSERT_EQ(3, hal.size());
+ std::list<std::string>::const_iterator it = hal.cbegin();
+ ASSERT_NE(hal.cend(), it);
+ EXPECT_EQ("type=mysql", *it);
+ ASSERT_NE(hal.cend(), ++it);
+ EXPECT_EQ("type=foo", *it);
+ ASSERT_NE(hal.cend(), ++it);
+ EXPECT_EQ("type=bar", *it);
+
+ // Build a similar list with the first string empty so it will be ignored.
+ CfgDbAccess cfg1;
+ ASSERT_NO_THROW(cfg1.setHostDbAccessString(""));
+ ASSERT_NO_THROW(cfg1.setHostDbAccessString("type=foo"));
+ ASSERT_NO_THROW(cfg1.setHostDbAccessString("type=bar"));
+ expected = "[ { \"type\": \"foo\" }, { \"type\": \"bar\" } ]";
+ runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg1));
+ hal = cfg1.getHostDbAccessStringList();
+ ASSERT_EQ(2, hal.size());
+ EXPECT_EQ("type=foo", hal.front());
+ EXPECT_EQ("type=bar", hal.back());
+}
+
+// Tests that lease manager can be created from a specified configuration.
+TEST(CfgDbAccessTest, createLeaseMgr) {
+ CfgDbAccess cfg;
+ ASSERT_NO_THROW(cfg.setLeaseDbAccessString("type=memfile persist=false universe=4"));
+ ASSERT_NO_THROW(cfg.createManagers());
+
+ ASSERT_NO_THROW({
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ EXPECT_EQ("memfile",lease_mgr.getType());
+ });
+}
+
+// The following tests require MySQL enabled.
+#if defined HAVE_MYSQL
+
+/// @brief Test fixture class for testing @ref CfgDbAccessTest using MySQL
+/// backend.
+class CfgMySQLDbAccessTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ CfgMySQLDbAccessTest() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createMySQLSchema();
+ }
+
+ /// @brief Destructor.
+ virtual ~CfgMySQLDbAccessTest() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ db::test::destroyMySQLSchema();
+ LeaseMgrFactory::destroy();
+ }
+};
+
+
+// Tests that MySQL lease manager and host data source can be created from a
+// specified configuration.
+TEST_F(CfgMySQLDbAccessTest, createManagers) {
+ CfgDbAccess cfg;
+ ASSERT_NO_THROW(cfg.setLeaseDbAccessString(db::test::validMySQLConnectionString()));
+ ASSERT_NO_THROW(cfg.setHostDbAccessString(db::test::validMySQLConnectionString()));
+ ASSERT_NO_THROW(cfg.createManagers());
+
+ ASSERT_NO_THROW({
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ EXPECT_EQ("mysql", lease_mgr.getType());
+ });
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("mysql", host_data_source->getType());
+ });
+
+ // Because of the lazy initialization of the HostMgr instance, it is
+ // possible that the first call to the instance() function tosses
+ // existing connection to the database created by the call to
+ // createManagers(). Let's make sure that this doesn't happen.
+ ASSERT_NO_THROW(HostMgr::instance());
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("mysql", host_data_source->getType());
+ });
+
+ EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// Tests that the createManagers function utilizes the setting in the
+// CfgDbAccess class which controls whether the IP reservations must
+// be unique or can be non-unique.
+TEST_F(CfgMySQLDbAccessTest, createManagersIPResrvUnique) {
+ CfgDbAccess cfg;
+
+ cfg.setIPReservationsUnique(false);
+
+ ASSERT_NO_THROW(cfg.setLeaseDbAccessString(db::test::validMySQLConnectionString()));
+ ASSERT_NO_THROW(cfg.setHostDbAccessString(db::test::validMySQLConnectionString()));
+ ASSERT_NO_THROW(cfg.createManagers());
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("mysql", host_data_source->getType());
+ });
+
+ // Because of the lazy initialization of the HostMgr instance, it is
+ // possible that the first call to the instance() function tosses
+ // existing connection to the database created by the call to
+ // createManagers(). Let's make sure that this doesn't happen.
+ ASSERT_NO_THROW(HostMgr::instance());
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("mysql", host_data_source->getType());
+ });
+
+ EXPECT_FALSE(HostMgr::instance().getIPReservationsUnique());
+}
+
+#endif
+
+// The following tests require PgSQL enabled.
+#if defined HAVE_PGSQL
+
+/// @brief Test fixture class for testing @ref CfgDbAccessTest using PgSQL
+/// backend.
+class CfgPgSQLDbAccessTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ CfgPgSQLDbAccessTest() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createPgSQLSchema();
+ }
+
+ /// @brief Destructor.
+ virtual ~CfgPgSQLDbAccessTest() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ db::test::destroyPgSQLSchema();
+ LeaseMgrFactory::destroy();
+ }
+};
+
+
+// Tests that PgSQL lease manager and host data source can be created from a
+// specified configuration.
+TEST_F(CfgPgSQLDbAccessTest, createManagers) {
+ CfgDbAccess cfg;
+ ASSERT_NO_THROW(cfg.setLeaseDbAccessString(db::test::validPgSQLConnectionString()));
+ ASSERT_NO_THROW(cfg.setHostDbAccessString(db::test::validPgSQLConnectionString()));
+ ASSERT_NO_THROW(cfg.createManagers());
+
+ ASSERT_NO_THROW({
+ LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
+ EXPECT_EQ("postgresql", lease_mgr.getType());
+ });
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("postgresql", host_data_source->getType());
+ });
+
+ // Because of the lazy initialization of the HostMgr instance, it is
+ // possible that the first call to the instance() function tosses
+ // existing connection to the database created by the call to
+ // createManagers(). Let's make sure that this doesn't happen.
+ ASSERT_NO_THROW(HostMgr::instance());
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("postgresql", host_data_source->getType());
+ });
+
+ EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// Tests that the createManagers function utilizes the setting in the
+// CfgDbAccess class which controls whether the IP reservations must
+// be unique or can be non-unique.
+TEST_F(CfgPgSQLDbAccessTest, createManagersIPResrvUnique) {
+ CfgDbAccess cfg;
+
+ cfg.setIPReservationsUnique(false);
+
+ ASSERT_NO_THROW(cfg.setLeaseDbAccessString(db::test::validPgSQLConnectionString()));
+ ASSERT_NO_THROW(cfg.setHostDbAccessString(db::test::validPgSQLConnectionString()));
+ ASSERT_NO_THROW(cfg.createManagers());
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("postgresql", host_data_source->getType());
+ });
+
+ // Because of the lazy initialization of the HostMgr instance, it is
+ // possible that the first call to the instance() function tosses
+ // existing connection to the database created by the call to
+ // createManagers(). Let's make sure that this doesn't happen.
+ ASSERT_NO_THROW(HostMgr::instance());
+
+ ASSERT_NO_THROW({
+ const HostDataSourcePtr& host_data_source =
+ HostMgr::instance().getHostDataSource();
+ ASSERT_TRUE(host_data_source);
+ EXPECT_EQ("postgresql", host_data_source->getType());
+ });
+
+ EXPECT_FALSE(HostMgr::instance().getIPReservationsUnique());
+}
+
+#endif
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc b/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc
new file mode 100644
index 0000000..10da5b9
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_duid_unittest.cc
@@ -0,0 +1,280 @@
+// Copyright (C) 2015-2020 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 <dhcp/dhcp4.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <exceptions/exceptions.h>
+#include <testutils/io_utils.h>
+#include <testutils/test_to_element.h>
+#include <util/encode/hex.h>
+#include <gtest/gtest.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Specifies file name holding DUID.
+const std::string DUID_FILE_NAME = "test.duid";
+
+/// @brief Test fixture class for @c CfgDUID.
+class CfgDUIDTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Removes DUID file if present.
+ CfgDUIDTest() {
+ static_cast<void>(remove(absolutePath(DUID_FILE_NAME).c_str()));
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes DUID file if present.
+ virtual ~CfgDUIDTest() {
+ static_cast<void>(remove(absolutePath(DUID_FILE_NAME).c_str()));
+ }
+
+ /// @brief Returns absolute path to a file used by tests.
+ ///
+ /// @param filename File name.
+ std::string absolutePath(const std::string& filename) const;
+
+ /// @brief Converts vector to string of hexadecimal digits.
+ ///
+ /// @param vec Input vector.
+ /// @return String of hexadecimal digits converted from vector.
+ std::string toString(const std::vector<uint8_t>& vec) const;
+};
+
+std::string
+CfgDUIDTest::absolutePath(const std::string& filename) const {
+ std::ostringstream s;
+ s << DHCP_DATA_DIR << "/" << filename;
+ return (s.str());
+}
+
+/// @brief Converts vector to string of hexadecimal digits.
+///
+/// @param vec Input vector.
+/// @return String of hexadecimal digits converted from vector.
+std::string
+CfgDUIDTest::toString(const std::vector<uint8_t>& vec) const {
+ try {
+ return (util::encode::encodeHex(vec));
+ } catch (...) {
+ ADD_FAILURE() << "toString: unable to encode vector to"
+ " hexadecimal string";
+ }
+ return ("");
+}
+
+
+// This test verifies default values of the DUID configuration.
+TEST_F(CfgDUIDTest, defaults) {
+ CfgDUID cfg_duid;
+ EXPECT_EQ(DUID::DUID_LLT, cfg_duid.getType());
+
+ EXPECT_TRUE(cfg_duid.getIdentifier().empty())
+ << "expected empty identifier, found: "
+ << toString(cfg_duid.getIdentifier());
+
+ EXPECT_EQ(0, cfg_duid.getHType());
+ EXPECT_EQ(0, cfg_duid.getTime());
+ EXPECT_EQ(0, cfg_duid.getEnterpriseId());
+ EXPECT_TRUE(cfg_duid.persist());
+ EXPECT_FALSE(cfg_duid.getContext());
+
+ std::string expected = "{ \"type\": \"LLT\",\n"
+ "\"identifier\": \"\",\n"
+ "\"htype\": 0,\n"
+ "\"time\": 0,\n"
+ "\"enterprise-id\": 0,\n"
+ "\"persist\": true }";
+ runToElementTest<CfgDUID>(expected, cfg_duid);
+}
+
+// This test verifies that it is possible to set values for the CfgDUID.
+TEST_F(CfgDUIDTest, setValues) {
+ CfgDUID cfg_duid;
+ // Set values.
+ ASSERT_NO_THROW(cfg_duid.setType(DUID::DUID_EN));
+ ASSERT_NO_THROW(cfg_duid.setIdentifier("ABCDEF"));
+ ASSERT_NO_THROW(cfg_duid.setHType(100));
+ ASSERT_NO_THROW(cfg_duid.setTime(32100));
+ ASSERT_NO_THROW(cfg_duid.setEnterpriseId(10));
+ ASSERT_NO_THROW(cfg_duid.setPersist(false));
+ std::string user_context = "{ \"comment\": \"bar\", \"foo\": 1 }";
+ ASSERT_NO_THROW(cfg_duid.setContext(Element::fromJSON(user_context)));
+
+ // Check that values have been set correctly.
+ EXPECT_EQ(DUID::DUID_EN, cfg_duid.getType());
+ EXPECT_EQ("ABCDEF", toString(cfg_duid.getIdentifier()));
+ EXPECT_EQ(100, cfg_duid.getHType());
+ EXPECT_EQ(32100, cfg_duid.getTime());
+ EXPECT_EQ(10, cfg_duid.getEnterpriseId());
+ EXPECT_FALSE(cfg_duid.persist());
+ ASSERT_TRUE(cfg_duid.getContext());
+ EXPECT_EQ(user_context, cfg_duid.getContext()->str());
+
+ std::string expected = "{\n"
+ " \"type\": \"EN\",\n"
+ " \"identifier\": \"ABCDEF\",\n"
+ " \"htype\": 100,\n"
+ " \"time\": 32100,\n"
+ " \"enterprise-id\": 10,\n"
+ " \"persist\": false,\n"
+ " \"user-context\": { \"foo\": 1,\n"
+ " \"comment\": \"bar\" }\n"
+ "}";
+ runToElementTest<CfgDUID>(expected, cfg_duid);
+}
+
+// This test checks positive scenarios for setIdentifier.
+TEST_F(CfgDUIDTest, setIdentifier) {
+ CfgDUID cfg_duid;
+ // Check that hexadecimal characters may be lower case.
+ ASSERT_NO_THROW(cfg_duid.setIdentifier("a1b2c3"));
+ EXPECT_EQ("A1B2C3", toString(cfg_duid.getIdentifier()));
+
+ // Check that whitespaces are allowed.
+ ASSERT_NO_THROW(cfg_duid.setIdentifier(" ABC DEF "));
+ EXPECT_EQ("ABCDEF", toString(cfg_duid.getIdentifier()));
+
+ // Check that identifier including only whitespaces is ignored.
+ ASSERT_NO_THROW(cfg_duid.setIdentifier(" "));
+ EXPECT_TRUE(cfg_duid.getIdentifier().empty())
+ << "expected empty identifier, found: "
+ << toString(cfg_duid.getIdentifier());
+}
+
+// This test verifies that the invalid identifier is rejected and
+// exception is thrown.
+TEST_F(CfgDUIDTest, setInvalidIdentifier) {
+ CfgDUID cfg_duid;
+ // Check that hexadecimal characters may be lower case.
+ ASSERT_NO_THROW(cfg_duid.setIdentifier("a1b2c3"));
+ EXPECT_EQ("A1B2C3", toString(cfg_duid.getIdentifier()));
+
+ // Try to set invalid value. This should not modify original
+ // value.
+ ASSERT_THROW(cfg_duid.setIdentifier("hola!"), isc::BadValue);
+ EXPECT_EQ("A1B2C3", toString(cfg_duid.getIdentifier()));
+}
+
+// This method checks that the DUID-LLT can be created from the
+// specified configuration.
+TEST_F(CfgDUIDTest, createLLT) {
+ CfgDUID cfg;
+ ASSERT_NO_THROW(cfg.setType(DUID::DUID_LLT));
+ ASSERT_NO_THROW(cfg.setTime(0x1123));
+ ASSERT_NO_THROW(cfg.setHType(8));
+ ASSERT_NO_THROW(cfg.setIdentifier("12564325A63F"));
+
+ // Generate DUID from this configuration.
+ DuidPtr duid;
+ ASSERT_NO_THROW(duid = cfg.create(absolutePath(DUID_FILE_NAME)));
+ ASSERT_TRUE(duid);
+
+ // Verify if the DUID is correct.
+ EXPECT_EQ("00:01:00:08:00:00:11:23:12:56:43:25:a6:3f",
+ duid->toText());
+
+ // Verify that the DUID file has been created.
+ EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
+
+ // Verify getCurrentDuid() returns the value created.
+ DuidPtr current_duid = cfg.getCurrentDuid();
+ ASSERT_TRUE(current_duid);
+ EXPECT_EQ(*current_duid, *duid);
+}
+
+// This method checks that the DUID-EN can be created from the
+// specified configuration.
+TEST_F(CfgDUIDTest, createEN) {
+ CfgDUID cfg;
+ ASSERT_NO_THROW(cfg.setType(DUID::DUID_EN));
+ ASSERT_NO_THROW(cfg.setIdentifier("250F3E26A762"));
+ ASSERT_NO_THROW(cfg.setEnterpriseId(0x1010));
+
+ // Generate DUID from this configuration.
+ DuidPtr duid;
+ ASSERT_NO_THROW(duid = cfg.create(absolutePath(DUID_FILE_NAME)));
+ ASSERT_TRUE(duid);
+
+ // Verify if the DUID is correct.
+ EXPECT_EQ("00:02:00:00:10:10:25:0f:3e:26:a7:62", duid->toText());
+
+ // Verify that the DUID file has been created.
+ EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
+
+ // Verify getCurrentDuid() returns the value created.
+ DuidPtr current_duid = cfg.getCurrentDuid();
+ ASSERT_TRUE(current_duid);
+ EXPECT_EQ(*current_duid, *duid);
+}
+
+// This method checks that the DUID-LL can be created from the
+// specified configuration.
+TEST_F(CfgDUIDTest, createLL) {
+ CfgDUID cfg;
+ ASSERT_NO_THROW(cfg.setType(DUID::DUID_LL));
+ ASSERT_NO_THROW(cfg.setIdentifier("124134A4B367"));
+ ASSERT_NO_THROW(cfg.setHType(2));
+
+ // Generate DUID from this configuration.
+ DuidPtr duid;
+ ASSERT_NO_THROW(duid = cfg.create(absolutePath(DUID_FILE_NAME)));
+ ASSERT_TRUE(duid);
+
+ // Verify if the DUID is correct.
+ EXPECT_EQ("00:03:00:02:12:41:34:a4:b3:67", duid->toText());
+
+ // Verify that the DUID file has been created.
+ EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
+
+ // Verify getCurrentDuid() returns the value created.
+ DuidPtr current_duid = cfg.getCurrentDuid();
+ ASSERT_TRUE(current_duid);
+ EXPECT_EQ(*current_duid, *duid);
+}
+
+// This test verifies that it is possible to disable storing
+// generated DUID on a hard drive.
+TEST_F(CfgDUIDTest, createDisableWrite) {
+ CfgDUID cfg;
+ ASSERT_NO_THROW(cfg.setType(DUID::DUID_EN));
+ ASSERT_NO_THROW(cfg.setIdentifier("250F3E26A762"));
+ ASSERT_NO_THROW(cfg.setEnterpriseId(0x1010));
+ ASSERT_NO_THROW(cfg.setPersist(false));
+
+ // Generate DUID from this configuration.
+ DuidPtr duid;
+ ASSERT_NO_THROW(duid = cfg.create(absolutePath(DUID_FILE_NAME)));
+ ASSERT_TRUE(duid);
+
+ // Verify if the DUID is correct.
+ EXPECT_EQ("00:02:00:00:10:10:25:0f:3e:26:a7:62", duid->toText());
+
+ // DUID persistence is disabled so there should be no DUID file.
+ EXPECT_FALSE(fileExists(absolutePath(DUID_FILE_NAME)));
+
+ // Verify getCurrentDuid() returns the value created.
+ DuidPtr current_duid = cfg.getCurrentDuid();
+ ASSERT_TRUE(current_duid);
+ EXPECT_EQ(*current_duid, *duid);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc b/src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc
new file mode 100644
index 0000000..b7e74d8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc
@@ -0,0 +1,433 @@
+// Copyright (C) 2015-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+#include <testutils/test_to_element.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <functional>
+#include <stdint.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Type definition of the @c CfgExpiration modified function.
+typedef std::function<void(CfgExpiration*, const int64_t)> ModifierFun;
+/// @brief Type definition of the @c CfgExpiration accessor function
+/// returning uint16_t value.
+typedef std::function<uint16_t(CfgExpiration*)> AccessorFunUint16;
+/// @brief Type definition of the @c CfgExpiration accessor function
+/// returning uint32_t value.
+typedef std::function<uint32_t(CfgExpiration*)> AccessorFunUint32;
+
+/// @brief Tests the accessor and modifier function for a particular
+/// configuration parameter held in @c CfgExpiration.
+///
+/// This is a simple test which tries to set the given parameter to
+/// different values:
+/// - value greater than maximum allowed for this parameter - expects
+/// the exception to be thrown,
+/// - value lower than 0 - expects the exception to be thrown,
+/// - value equal to the maximum allowed,
+/// - value equal to maximum allowed minus 1.
+///
+/// @param limit Maximum allowed value for the parameter.
+/// @param modifier Pointer to the modifier function to be tested.
+/// @param accessor Pointer to the accessor function to be tested.
+/// @tparam ReturnType Type of the value returned by the accessor,
+/// i.e. uint16_t or uint32_t.
+template<typename ReturnType>
+void
+testAccessModify(const int64_t limit, const ModifierFun& modifier,
+ const std::function<ReturnType(CfgExpiration*)>& accessor) {
+ CfgExpiration cfg;
+
+ // Setting the value to maximum allowed + 1 should result in
+ // an exception.
+ ASSERT_THROW(modifier(&cfg, limit + 1), OutOfRange);
+
+ // Setting to the negative value should result in an exception.
+ ASSERT_THROW(modifier(&cfg, -1), OutOfRange);
+
+ // Setting the value to the maximum allowed should pass.
+ ASSERT_NO_THROW(modifier(&cfg, limit));
+ EXPECT_EQ(limit, accessor(&cfg));
+
+ // Setting the value to the maximum allowed - 1 should pass.
+ ASSERT_NO_THROW(modifier(&cfg, limit - 1));
+ EXPECT_EQ(limit - 1, accessor(&cfg));
+
+ // Setting the value to 0 should pass.
+ ASSERT_NO_THROW(modifier(&cfg, 0));
+ EXPECT_EQ(0, accessor(&cfg));
+}
+
+/// @brief Tests that modifier and the accessor returning uint16_t value
+/// work as expected.
+///
+/// @param limit Maximum allowed value for the parameter.
+/// @param modifier Pointer to the modifier function to be tested.
+/// @param accessor Pointer to the accessor function to be tested.
+void
+testAccessModifyUint16(const int64_t limit, const ModifierFun& modifier,
+ const AccessorFunUint16& accessor) {
+ testAccessModify<uint16_t>(limit, modifier, accessor);
+}
+
+/// @brief Tests that modifier and the accessor returning uint32_t value
+/// work as expected.
+///
+/// @param limit Maximum allowed value for the parameter.
+/// @param modifier Pointer to the modifier function to be tested.
+/// @param accessor Pointer to the accessor function to be tested.
+void
+testAccessModifyUint32(const int64_t limit, const ModifierFun& modifier,
+ const AccessorFunUint32& accessor) {
+ testAccessModify<uint32_t>(limit, modifier, accessor);
+}
+
+/// Test the default values of CfgExpiration object.
+TEST(CfgExpirationTest, defaults) {
+ CfgExpiration cfg;
+ EXPECT_EQ(CfgExpiration::DEFAULT_RECLAIM_TIMER_WAIT_TIME,
+ cfg.getReclaimTimerWaitTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME,
+ cfg.getFlushReclaimedTimerWaitTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_HOLD_RECLAIMED_TIME,
+ cfg.getHoldReclaimedTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES,
+ cfg.getMaxReclaimLeases());
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_TIME,
+ cfg.getMaxReclaimTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_UNWARNED_RECLAIM_CYCLES,
+ cfg.getUnwarnedReclaimCycles());
+}
+
+/// @brief Tests that unparse returns an expected value
+TEST(CfgExpirationTest, unparse) {
+ CfgExpiration cfg;
+ std::string defaults = "{\n"
+ "\"reclaim-timer-wait-time\": 10,\n"
+ "\"flush-reclaimed-timer-wait-time\": 25,\n"
+ "\"hold-reclaimed-time\": 3600,\n"
+ "\"max-reclaim-leases\": 100,\n"
+ "\"max-reclaim-time\": 250,\n"
+ "\"unwarned-reclaim-cycles\": 5 }";
+ isc::test::runToElementTest<CfgExpiration>(defaults, cfg);
+}
+
+// Test the {get,set}ReclaimTimerWaitTime.
+TEST(CfgExpirationTest, getReclaimTimerWaitTime) {
+ testAccessModify<uint16_t>(CfgExpiration::LIMIT_RECLAIM_TIMER_WAIT_TIME,
+ &CfgExpiration::setReclaimTimerWaitTime,
+ &CfgExpiration::getReclaimTimerWaitTime);
+}
+
+// Test the {get,set}FlushReclaimedTimerWaitTime.
+TEST(CfgExpirationTest, getFlushReclaimedTimerWaitTime) {
+ testAccessModifyUint16(CfgExpiration::LIMIT_FLUSH_RECLAIMED_TIMER_WAIT_TIME,
+ &CfgExpiration::setFlushReclaimedTimerWaitTime,
+ &CfgExpiration::getFlushReclaimedTimerWaitTime);
+}
+
+// Test the {get,set}HoldReclaimedTime.
+TEST(CfgExpirationTest, getHoldReclaimedTime) {
+ testAccessModifyUint32(CfgExpiration::LIMIT_HOLD_RECLAIMED_TIME,
+ &CfgExpiration::setHoldReclaimedTime,
+ &CfgExpiration::getHoldReclaimedTime);
+}
+
+// Test the {get,set}MaxReclaimLeases.
+TEST(CfgExpirationTest, getMaxReclaimLeases) {
+ testAccessModifyUint32(CfgExpiration::LIMIT_MAX_RECLAIM_LEASES,
+ &CfgExpiration::setMaxReclaimLeases,
+ &CfgExpiration::getMaxReclaimLeases);
+}
+
+// Test the {get,set}MaxReclaimTime.
+TEST(CfgExpirationTest, getMaxReclaimTime) {
+ testAccessModifyUint16(CfgExpiration::LIMIT_MAX_RECLAIM_TIME,
+ &CfgExpiration::setMaxReclaimTime,
+ &CfgExpiration::getMaxReclaimTime);
+}
+
+// Test the {get,set}UnwarnedReclaimCycles.
+TEST(CfgExpirationTest, getUnwarnedReclaimCycles) {
+ testAccessModifyUint16(CfgExpiration::LIMIT_UNWARNED_RECLAIM_CYCLES,
+ &CfgExpiration::setUnwarnedReclaimCycles,
+ &CfgExpiration::getUnwarnedReclaimCycles);
+}
+
+/// @brief Implements test routines for leases reclamation.
+///
+/// This class implements two routines called by the @c CfgExpiration object
+/// instead of the typical routines for leases' reclamation in the
+/// @c AllocEngine. These methods do not perform the actual reclamation,
+/// but instead they record the number of calls to them and the parameters
+/// with which they were executed. This allows for checking if the
+/// @c CfgExpiration object calls the leases reclamation routine with the
+/// appropriate parameters.
+class LeaseReclamationStub {
+public:
+
+ /// @brief Collection of parameters with which the @c reclaimExpiredLeases
+ /// method is called.
+ ///
+ /// Examination of these values allows for assessment if the @c CfgExpiration
+ /// calls the routine with the appropriate values.
+ struct RecordedParams {
+ /// @brief Maximum number of leases to be processed.
+ size_t max_leases;
+
+ /// @brief Timeout for processing leases in milliseconds.
+ uint16_t timeout;
+
+ /// @brief Boolean flag which indicates if the leases should be removed
+ /// when reclaimed.
+ bool remove_lease;
+
+ /// @brief Maximum number of reclamation attempts after which all leases
+ /// should be reclaimed.
+ uint16_t max_unwarned_cycles;
+
+ /// @brief Constructor
+ ///
+ /// Sets all numeric values to 0xFFFF and the boolean values to false.
+ RecordedParams()
+ : max_leases(0xFFFF), timeout(0xFFFF), remove_lease(false),
+ max_unwarned_cycles(0xFFFF) {
+ }
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Resets recorded parameters and obtains the instance of the @c TimerMgr.
+ LeaseReclamationStub()
+ : reclaim_calls_count_(0), delete_calls_count_(0), reclaim_params_(),
+ secs_param_(0), timer_mgr_(TimerMgr::instance()) {
+ }
+
+ /// @brief Stub implementation of the leases' reclamation routine.
+ ///
+ /// @param max_leases Maximum number of leases to be processed.
+ /// @param timeout Timeout for processing leases in milliseconds.
+ /// @remove_lease Boolean flag which indicates if the leases should be
+ /// removed when it is reclaimed.
+ /// @param Maximum number of reclamation attempts after which all leases
+ /// should be reclaimed.
+ void
+ reclaimExpiredLeases(const size_t max_leases, const uint16_t timeout,
+ const bool remove_lease,
+ const uint16_t max_unwarned_cycles) {
+ // Increase calls counter for this method.
+ ++reclaim_calls_count_;
+ // Record all parameters with which this method has been called.
+ reclaim_params_.max_leases = max_leases;
+ reclaim_params_.timeout = timeout;
+ reclaim_params_.remove_lease = remove_lease;
+ reclaim_params_.max_unwarned_cycles = max_unwarned_cycles;
+
+ // Leases' reclamation routine is responsible for re-scheduling
+ // the timer.
+ timer_mgr_->setup(CfgExpiration::RECLAIM_EXPIRED_TIMER_NAME);
+ }
+
+ /// @brief Stub implementation of the routine which flushes
+ /// expired-reclaimed leases.
+ ///
+ /// @param secs Specifies the minimum amount of time, expressed in
+ /// seconds, that must elapse before the expired-reclaimed lease is
+ /// deleted from the database.
+ void
+ deleteReclaimedLeases(const uint32_t secs) {
+ // Increase calls counter for this method.
+ ++delete_calls_count_;
+ // Record the value of the parameter.
+ secs_param_ = secs;
+
+ // Routine which flushes the reclaimed leases is responsible for
+ // re-scheduling the timer.
+ timer_mgr_->setup(CfgExpiration::FLUSH_RECLAIMED_TIMER_NAME);
+ }
+
+ /// @brief Counter holding the number of calls to @c reclaimExpiredLeases.
+ long reclaim_calls_count_;
+
+ /// @brief Counter holding the number of calls to @c deleteReclaimedLeases.
+ long delete_calls_count_;
+
+ /// @brief Structure holding values of parameters with which the
+ /// @c reclaimExpiredLeases was called.
+ ///
+ /// These values are overridden on subsequent calls to this method.
+ RecordedParams reclaim_params_;
+
+ /// @brief Value of the parameter with which the @c deleteReclaimedLeases
+ /// was called.
+ uint32_t secs_param_;
+
+private:
+
+ /// @brief Pointer to the @c TimerMgr.
+ TimerMgrPtr timer_mgr_;
+
+};
+
+/// @brief Pointer to the @c LeaseReclamationStub.
+typedef boost::shared_ptr<LeaseReclamationStub> LeaseReclamationStubPtr;
+
+/// @brief Test fixture class for the @c CfgExpiration.
+class CfgExpirationTimersTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates instance of the test fixture class. Besides initialization
+ /// of the class members, it also stops the @c TimerMgr worker thread
+ /// and removes any registered timers.
+ CfgExpirationTimersTest()
+ : io_service_(new IOService()),
+ timer_mgr_(TimerMgr::instance()),
+ stub_(new LeaseReclamationStub()),
+ cfg_(true) {
+ cleanupTimerMgr();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// It stops the @c TimerMgr worker thread and removes any registered
+ /// timers.
+ virtual ~CfgExpirationTimersTest() {
+ cleanupTimerMgr();
+ }
+
+ /// @brief Stop @c TimerMgr worker thread and remove the timers.
+ void cleanupTimerMgr() const {
+ timer_mgr_->unregisterTimers();
+ timer_mgr_->setIOService(io_service_);
+ }
+
+ /// @brief Runs IOService and stops after a specified time.
+ ///
+ /// @param timeout_ms Amount of time after which the method returns.
+ void runTimersWithTimeout(const long timeout_ms) {
+ IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, timeout_ms, IntervalTimer::ONE_SHOT);
+
+ io_service_->run();
+ io_service_->get_io_service().reset();
+ }
+
+ /// @brief Setup timers according to the configuration and run them
+ /// for the specified amount of time.
+ ///
+ /// @param timeout_ms Timeout in milliseconds.
+ void setupAndRun(const long timeout_ms) {
+ cfg_.setupTimers(&LeaseReclamationStub::reclaimExpiredLeases,
+ &LeaseReclamationStub::deleteReclaimedLeases,
+ stub_.get());
+ // Run timers.
+ ASSERT_NO_THROW({
+ runTimersWithTimeout(timeout_ms);
+ });
+ }
+
+ /// @brief Pointer to the IO service used by the tests.
+ IOServicePtr io_service_;
+
+ /// @brief Pointer to the @c TimerMgr.
+ TimerMgrPtr timer_mgr_;
+
+ /// @brief Pointer to the @c LeaseReclamationStub instance.
+ LeaseReclamationStubPtr stub_;
+
+ /// @brief Instance of the @c CfgExpiration class used by the tests.
+ CfgExpiration cfg_;
+};
+
+// Test that the reclamation routines are called with the appropriate parameters.
+TEST_F(CfgExpirationTimersTest, reclamationParameters) {
+ // Set this value to true, to make sure that the timer callback would
+ // modify this value to false.
+ stub_->reclaim_params_.remove_lease = true;
+
+ // Set parameters to some non-default values.
+ cfg_.setMaxReclaimLeases(1000);
+ cfg_.setMaxReclaimTime(1500);
+ cfg_.setHoldReclaimedTime(1800);
+ cfg_.setUnwarnedReclaimCycles(13);
+
+ // Run timers for 500ms.
+ ASSERT_NO_FATAL_FAILURE(setupAndRun(500));
+
+ // Make sure we had more than one call to the reclamation routine.
+ ASSERT_GT(stub_->reclaim_calls_count_, 1);
+ // Make sure it was called with appropriate arguments.
+ EXPECT_EQ(1000, stub_->reclaim_params_.max_leases);
+ EXPECT_EQ(1500, stub_->reclaim_params_.timeout);
+ EXPECT_FALSE(stub_->reclaim_params_.remove_lease);
+ EXPECT_EQ(13, stub_->reclaim_params_.max_unwarned_cycles);
+
+ // Make sure we had more than one call to the routine which flushes
+ // expired reclaimed leases.
+ ASSERT_GT(stub_->delete_calls_count_, 1);
+ // Make sure that the argument was correct.
+ EXPECT_EQ(1800, stub_->secs_param_);
+}
+
+// This test verifies that if the value of "flush-reclaimed-timer-wait-time"
+// configuration parameter is set to 0, the lease reclamation routine would
+// delete reclaimed leases from a lease database.
+TEST_F(CfgExpirationTimersTest, noLeaseAffinity) {
+ // Set the timer for flushing leases to 0. This effectively disables
+ // the timer.
+ cfg_.setFlushReclaimedTimerWaitTime(0);
+
+ // Run the lease reclamation timer for a while.
+ ASSERT_NO_FATAL_FAILURE(setupAndRun(500));
+
+ // Make sure that the lease reclamation routine has been executed a
+ // couple of times.
+ ASSERT_GT(stub_->reclaim_calls_count_, 1);
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES,
+ stub_->reclaim_params_.max_leases);
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_TIME,
+ stub_->reclaim_params_.timeout);
+ // When the "flush" timer is disabled, the lease reclamation routine is
+ // responsible for removal of reclaimed leases. This is controlled using
+ // the "remove_lease" parameter which should be set to true in this case.
+ EXPECT_TRUE(stub_->reclaim_params_.remove_lease);
+
+ // The routine flushing reclaimed leases should not be run at all.
+ EXPECT_EQ(0, stub_->delete_calls_count_);
+}
+
+// This test verifies that lease reclamation may be disabled.
+TEST_F(CfgExpirationTimersTest, noLeaseReclamation) {
+ // Disable both timers.
+ cfg_.setReclaimTimerWaitTime(0);
+ cfg_.setFlushReclaimedTimerWaitTime(0);
+
+ // Wait for 500ms.
+ ASSERT_NO_FATAL_FAILURE(setupAndRun(500));
+
+ // Make sure that neither leases' reclamation routine nor the routine
+ // flushing expired-reclaimed leases was run.
+ EXPECT_EQ(0, stub_->reclaim_calls_count_);
+ EXPECT_EQ(0, stub_->delete_calls_count_);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc b/src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc
new file mode 100644
index 0000000..0176eb4
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc
@@ -0,0 +1,113 @@
+// Copyright (C) 2016-2017 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 <dhcp/dhcp6.h>
+#include <dhcpsrv/cfg_host_operations.h>
+#include <dhcpsrv/host.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <iterator>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Checks if specified identifier is present.
+///
+/// @param cfg Object holding current configuration.
+/// @param id Identifier type which presence should be checked.
+/// @return true if specified identifier is present.
+bool
+identifierPresent(const CfgHostOperations& cfg, const Host::IdentifierType& id) {
+ CfgHostOperations::IdentifierTypes types = cfg.getIdentifierTypes();
+ return (std::find(types.begin(), types.end(), id) != types.end());
+}
+
+/// @brief Checks if specified identifier is at specified position.
+///
+/// @param cfg Object holding current configuration.
+/// @param id Identifier type which presence should be checked.
+/// @param pos Position at which the identifier is expected on the list.
+/// @return true if specified identifier exists at specified position.
+bool
+identifierAtPosition(const CfgHostOperations& cfg, const Host::IdentifierType& id,
+ const size_t pos) {
+ CfgHostOperations::IdentifierTypes types = cfg.getIdentifierTypes();
+ if (types.size() > pos) {
+ CfgHostOperations::IdentifierTypes::const_iterator type = types.begin();
+ std::advance(type, pos);
+ return (*type == id);
+ }
+ return (false);
+}
+
+// This test checks that the list of identifiers is initially
+// empty.
+TEST(CfgHostOperationsTest, defaults) {
+ CfgHostOperations cfg;
+ EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+ runToElementTest<CfgHostOperations>("[ ]", cfg);
+}
+
+// This test verifies that identifier types can be added into an
+// ordered collection and then removed.
+TEST(CfgHostOperationsTest, addIdentifier) {
+ CfgHostOperations cfg;
+
+ // Only HW address added.
+ ASSERT_NO_THROW(cfg.addIdentifierType("hw-address"));
+ EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_HWADDR, 0));
+ EXPECT_FALSE(identifierPresent(cfg, Host::IDENT_DUID));
+ EXPECT_FALSE(identifierPresent(cfg, Host::IDENT_CIRCUIT_ID));
+
+ // HW address and DUID should be present.
+ ASSERT_NO_THROW(cfg.addIdentifierType("duid"));
+ EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_HWADDR, 0));
+ EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_DUID, 1));
+ EXPECT_FALSE(identifierPresent(cfg, Host::IDENT_CIRCUIT_ID));
+
+ // All three identifiers should be present now.
+ ASSERT_NO_THROW(cfg.addIdentifierType("circuit-id"));
+ EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_HWADDR, 0));
+ EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_DUID, 1));
+ EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_CIRCUIT_ID, 2));
+
+ // Check unparse
+ std::string ids = "[ \"hw-address\", \"duid\", \"circuit-id\" ]";
+ runToElementTest<CfgHostOperations>(ids, cfg);
+
+ // Let's clear and make sure no identifiers are present.
+ ASSERT_NO_THROW(cfg.clearIdentifierTypes());
+ EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+ runToElementTest<CfgHostOperations>("[ ]", cfg);
+}
+
+// This test verifies that the default DHCPv4 configuration is created
+// as expected.
+TEST(CfgHostOperationsTest, createConfig4) {
+ CfgHostOperationsPtr cfg = CfgHostOperations::createConfig4();
+
+ EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_HWADDR, 0));
+ EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_DUID, 1));
+ EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_CIRCUIT_ID, 2));
+ EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_CLIENT_ID, 3));
+}
+
+// This test verifies that the default DHCPv6 configuration is created
+// as expected.
+TEST(CfgHostOperationsTest, createConfig6) {
+ CfgHostOperationsPtr cfg = CfgHostOperations::createConfig6();
+
+ EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_HWADDR, 0));
+ EXPECT_TRUE(identifierAtPosition(*cfg, Host::IDENT_DUID, 1));
+ EXPECT_FALSE(identifierPresent(*cfg, Host::IDENT_CIRCUIT_ID));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
new file mode 100644
index 0000000..58c0d03
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
@@ -0,0 +1,1512 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <set>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test fixture class for testing @c CfgHost object holding
+/// host reservations specified in the configuration file.
+class CfgHostsTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor allocates a collection of @c HWAddr and @c DuidPtr
+ /// objects used by the unit tests.
+ ///
+ /// The allocated HW addresses use the following pattern: 01:02:0A:BB:03:XX
+ /// where XX is a number between 0 and 0x32. All of them are of the
+ /// HTYPE_ETHER type.
+ ///
+ /// The allocated DUID LLTs use the following pattern:
+ /// 01:02:03:04:05:06:07:08:09:0A:XX where the XX is a number between
+ /// 0 and 0x32.
+ CfgHostsTest();
+
+ /// @brief Destructor.
+ ///
+ /// This destructor resets global state after tests are run.
+ ~CfgHostsTest();
+
+ /// @brief Increases last byte of an address.
+ ///
+ /// @param address Address to be increased.
+ IOAddress increase(const IOAddress& address, const uint8_t num) const;
+
+ /// @brief Collection of HW address objects allocated for unit tests.
+ std::vector<HWAddrPtr> hwaddrs_;
+ /// @brief Collection of DUIDs allocated for unit tests.
+ std::vector<DuidPtr> duids_;
+ /// @brief Collection of IPv4 address objects allocated for unit tests.
+ std::vector<IOAddress> addressesa_;
+ std::vector<IOAddress> addressesb_;
+};
+
+CfgHostsTest::CfgHostsTest() {
+ const uint8_t mac_template[] = {
+ 0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+ };
+ for (unsigned i = 0; i < 50; ++i) {
+ std::vector<uint8_t> vec(mac_template,
+ mac_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+ hwaddrs_.push_back(hwaddr);
+ }
+
+ const uint8_t duid_template[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+ };
+ for (unsigned i = 0; i < 50; ++i) {
+ std::vector<uint8_t> vec(duid_template,
+ duid_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ DuidPtr duid(new DUID(vec));
+ duids_.push_back(duid);
+ }
+
+ const uint32_t addra_template = 0xc0000205; // 192.0.2.5
+ const uint32_t addrb_template = 0xc00a020a; // 192.10.2.10
+ for (unsigned i = 0; i < 50; ++i) {
+ IOAddress addra(addra_template + i);
+ addressesa_.push_back(addra);
+ IOAddress addrb(addrb_template + i);
+ addressesb_.push_back(addrb);
+ }
+}
+
+CfgHostsTest::~CfgHostsTest() {
+ CfgMgr::instance().setFamily(AF_INET);
+}
+
+IOAddress
+CfgHostsTest::increase(const IOAddress& address, const uint8_t num) const {
+ std::vector<uint8_t> vec = address.toBytes();
+ if (!vec.empty()) {
+ vec[vec.size() - 1] += num;
+ return (IOAddress::fromBytes(address.getFamily(), &vec[0]));
+ }
+ return (address);
+}
+
+// This test checks that hosts with unique HW addresses and DUIDs can be
+// retrieved from the host configuration.
+TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address and 25 hosts identified by
+ // DUID. They are added to different subnets.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(i % 10 + 1), SubnetID(i % 5 + 1),
+ addressesa_[i])));
+
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(i % 5 + 1), SubnetID(i % 10 + 1),
+ addressesb_[i])));
+
+ }
+
+ // Try to retrieve each added reservation using HW address and DUID. Do it
+ // in the reverse order to make sure that the order doesn't matter.
+ for (int i = 24; i >= 0; --i) {
+ // Get host identified by HW address.
+ HostCollection hosts = cfg.getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(i % 10 + 1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(addressesa_[i].toText(),
+ hosts[0]->getIPv4Reservation().toText());
+
+ // Get host identified by DUID.
+ hosts = cfg.getAll(Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size());
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(i % 5 + 1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(addressesb_[i].toText(),
+ hosts[0]->getIPv4Reservation().toText());
+ }
+
+ // Make sure that the reservations do not exist for the hardware addresses
+ // and DUIDs from the range of 25 to 49.
+ for (int i = 49; i >= 25; --i) {
+ EXPECT_TRUE(cfg.getAll(Host::IDENT_HWADDR, &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size()).empty());
+ EXPECT_TRUE(cfg.getAll(Host::IDENT_DUID, &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size()).empty());
+ }
+}
+
+// This test verifies that the host can be added to multiple subnets and
+// that the getAll message retrieves all instances of the host.
+TEST_F(CfgHostsTest, getAllRepeatingHosts) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add two hosts, using the same HW address to two distinct subnets.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(2),
+ addressesa_[i])));
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(2), SubnetID(3),
+ addressesb_[i])));
+
+ // Add two hosts, using the same DUID to two distinct subnets.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1), SubnetID(2),
+ addressesb_[i])));
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(2), SubnetID(3),
+ addressesa_[i])));
+ }
+
+ // Verify that hosts can be retrieved.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Get host by HW address.
+ HostCollection hosts = cfg.getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_EQ(2, hosts.size());
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(addressesa_[i], hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+ EXPECT_EQ(addressesb_[i], hosts[1]->getIPv4Reservation().toText());
+
+ // The HW address is non-null but there are no reservations
+ // for the HW addresses from the range of 25 to 49.
+ hosts = cfg.getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[i + 25]->hwaddr_[0],
+ hwaddrs_[i + 25]->hwaddr_.size());
+ EXPECT_TRUE(hosts.empty());
+
+ // Get host by DUID.
+ hosts = cfg.getAll(Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size());
+
+ // The DUID is non-null but there are no reservations
+ // for the DUIDs from the range of 25 to 49.
+ hosts = cfg.getAll(Host::IDENT_DUID,
+ &duids_[i + 25]->getDuid()[0],
+ duids_[i + 25]->getDuid().size());
+ EXPECT_TRUE(hosts.empty());
+ }
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration.
+TEST_F(CfgHostsTest, getAll4BySubnet) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(1),
+ addressesa_[i])));
+ }
+
+ // Check that other subnets are empty.
+ HostCollection hosts = cfg.getAll4(SubnetID(100));
+ EXPECT_EQ(0, hosts.size());
+
+ // Try to retrieve all added reservations.
+ hosts = cfg.getAll4(SubnetID(1));
+ ASSERT_EQ(25, hosts.size());
+ for (unsigned i = 0; i < 25; ++i) {
+ EXPECT_EQ(1, hosts[i]->getIPv4SubnetID());
+ EXPECT_EQ(addressesa_[i].toText(),
+ hosts[i]->getIPv4Reservation().toText());
+ }
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration.
+TEST_F(CfgHostsTest, getAll6BySubnet) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ // Check that other subnets are empty.
+ HostCollection hosts = cfg.getAll6(SubnetID(100));
+ EXPECT_EQ(0, hosts.size());
+
+ // Try to retrieve all added reservations.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(25, hosts.size());
+ for (unsigned i = 0; i < 25; ++i) {
+ EXPECT_EQ(1, hosts[i]->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ hosts[i]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that hosts with the same reserved address can be retrieved
+// from the host configuration.
+TEST_F(CfgHostsTest, getAll6ByAddress) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(i+1),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i % 5)));
+ cfg.add(host);
+ }
+
+ // Try to retrieve all added reservations with IP equals 2001:db8:1::1.
+ auto hosts = cfg.getAll6(IOAddress("2001:db8:1::1"));
+ EXPECT_EQ(5, hosts.size());
+ for (unsigned i = 0; i < 5; ++i) {
+ EXPECT_EQ(1 + 5 * i, hosts[i]->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ hosts[i]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(IOAddress("2001:db8:1::1"),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration by pages.
+TEST_F(CfgHostsTest, getPage4) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ addressesa_[i])));
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Check that other subnets are empty.
+ HostCollection page = cfg.getPage4(SubnetID(100), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage4(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that hosts in the same subnet can be retrieved from
+// the host configuration by pages.
+TEST_F(CfgHostsTest, getPage6) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address in the same subnet.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Check that other subnets are empty.
+ HostCollection page = cfg.getPage6(SubnetID(100), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage6(SubnetID(1), idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that all hosts can be retrieved from the host
+// configuration by pages.
+TEST_F(CfgHostsTest, getPage4All) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by DUID.
+ for (unsigned i = 0; i < 25; ++i) {
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(i), SubnetID(i),
+ addressesa_[i])));
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ HostCollection page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage4(idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that all hosts can be retrieved from the host
+// configuration by pages.
+TEST_F(CfgHostsTest, getPage6All) {
+ CfgHosts cfg;
+ // Add 25 hosts identified by HW address.
+ for (unsigned i = 0; i < 25; ++i) {
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(i), SubnetID(i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+ size_t idx(0);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+
+ // Try to retrieve all added reservations.
+ // Get first page.
+ HostCollection page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+
+ // Get second and last pages.
+ page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ page = cfg.getPage6(idx, host_id, page_size);
+ EXPECT_EQ(0, page.size());
+}
+
+// This test checks that all reservations for the specified IPv4 address can
+// be retrieved.
+TEST_F(CfgHostsTest, getAll4ByAddress) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by the HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.5"))));
+ // Add host identified by the DUID.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(),
+ "duid",
+ SubnetID(1 + i), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.10"))));
+ }
+
+ HostCollection hosts = cfg.getAll4(IOAddress("192.0.2.10"));
+ std::set<uint32_t> subnet_ids;
+ for (HostCollection::const_iterator host = hosts.begin(); host != hosts.end();
+ ++host) {
+ subnet_ids.insert((*host)->getIPv4SubnetID());
+ }
+ ASSERT_EQ(25, subnet_ids.size());
+ EXPECT_EQ(1, *subnet_ids.begin());
+ EXPECT_EQ(25, *subnet_ids.rbegin());
+}
+
+// This test checks that the IPv4 reservation for the specified IPv4 address can
+// be deleted.
+TEST_F(CfgHostsTest, deleteForIPv4) {
+ CfgHosts cfg;
+ // Add hosts.
+ IOAddress address("10.0.0.42");
+ SubnetID subnet_id(42);
+ size_t host_count = 10;
+
+ for (size_t i = 0; i < host_count; i++)
+ {
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ subnet_id, SUBNET_ID_UNUSED,
+ increase(address, i))));
+ }
+
+ // Get all inserted hosts.
+ HostCollection hosts_by_subnet = cfg.getAll4(subnet_id);
+ HostCollection hosts_by_address = cfg.getAll4(address);
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(host_count, hosts_by_subnet.size());
+ ASSERT_EQ(1, hosts_by_address.size());
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del(subnet_id, address));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet = cfg.getAll4(subnet_id);
+ hosts_by_address = cfg.getAll4(address);
+ EXPECT_EQ(host_count-1, hosts_by_subnet.size());
+ EXPECT_EQ(0, hosts_by_address.size());
+}
+
+// This test checks that the IPv6 reservation for the specified subnet ID and
+// IPv6 address can be deleted.
+TEST_F(CfgHostsTest, deleteForIPv6) {
+ CfgHosts cfg;
+ // Add hosts.
+ IOAddress address("2001:db8:1::1");
+ size_t host_count = 10;
+ SubnetID subnet_id(42);
+
+ for (size_t i = 0; i < host_count; i++)
+ {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, subnet_id,
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress(address), i)));
+ cfg.add(host);
+ }
+
+
+ // Get all inserted hosts.
+ auto hosts_by_subnet_and_address = cfg.getAll6(subnet_id, address);
+ auto hosts_by_subnet = cfg.getAll6(subnet_id);
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(1, hosts_by_subnet_and_address.size());
+ ASSERT_EQ(host_count, hosts_by_subnet.size());
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del(subnet_id, address));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet_and_address = cfg.getAll6(subnet_id, address);
+ hosts_by_subnet = cfg.getAll6(subnet_id);
+ EXPECT_EQ(0, hosts_by_subnet_and_address.size());
+ EXPECT_EQ(host_count-1, hosts_by_subnet.size());
+}
+
+// This test checks that false is returned for deleting the IPv4 reservation
+// that doesn't exist.
+TEST_F(CfgHostsTest, deleteForMissingIPv4) {
+ CfgHosts cfg;
+
+ // Delete non-existent host.
+ EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("10.0.0.42"))));
+}
+
+// This test checks that false is returned for deleting the IPv6 reservation
+// that doesn't exist.
+TEST_F(CfgHostsTest, deleteForMissingIPv6) {
+ CfgHosts cfg;
+
+ // Delete non-existent host.
+ EXPECT_FALSE(cfg.del(SubnetID(42), IOAddress(("2001:db8:1::1"))));
+}
+
+// This test checks that the reservation for the specified IPv4 subnet and
+// identifier can be deleted.
+TEST_F(CfgHostsTest, del4) {
+ CfgHosts cfg;
+
+ // Add hosts.
+ size_t host_count = 20;
+ size_t host_id = 5;
+ SubnetID subnet_id(42);
+ IOAddress address("10.0.0.1");
+
+ // Add half of the hosts with the same subnet ID but differ with DUID and
+ // address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ subnet_id, SUBNET_ID_UNUSED,
+ increase(address, i)));
+ cfg.add(host);
+ }
+ // Add half of the hosts with the same subnet DUID and address but
+ // differ with address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[host_id]->toText(), "duid",
+ SubnetID(subnet_id + i + 1), SUBNET_ID_UNUSED,
+ increase(address, host_id)));
+ cfg.add(host);
+ }
+
+
+ // Get all inserted hosts.
+ HostCollection hosts_by_subnet = cfg.getAll4(subnet_id);
+ HostCollection hosts_by_address = cfg.getAll4(increase(address, host_id));
+ HostPtr host = cfg.get4(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(host_count / 2, hosts_by_subnet.size());
+ ASSERT_EQ(host_count / 2 + 1, hosts_by_address.size());
+ ASSERT_TRUE(host);
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del4(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0], duids_[host_id]->getDuid().size()));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet = cfg.getAll4(subnet_id);
+ hosts_by_address = cfg.getAll4(increase(address, host_id));
+ host = cfg.get4(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ EXPECT_EQ((host_count / 2)-1, hosts_by_subnet.size());
+ EXPECT_EQ(host_count / 2, hosts_by_address.size());
+ EXPECT_FALSE(host);
+}
+
+// This test checks that the host and its reservations for the specified IPv6
+// subnet and identifier can be deleted.
+TEST_F(CfgHostsTest, del6) {
+ CfgHosts cfg;
+
+ // Add hosts.
+ size_t host_count = 20;
+ size_t host_id = 5;
+ SubnetID subnet_id(42);
+ IOAddress address("2001:db8:1::1");
+
+ // Add half of the hosts with the same subnet ID but differ with DUID and
+ // address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, subnet_id,
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress(address), i)));
+ cfg.add(host);
+ }
+ // Add half of the hosts with the same subnet DUID and address but
+ // differ with address.
+ for (size_t i = 0; i < host_count / 2; i++) {
+ HostPtr host = HostPtr(new Host(duids_[host_id]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(subnet_id + i + 1),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(address, host_id)));
+ cfg.add(host);
+ }
+
+
+ // Get all inserted hosts.
+ HostCollection hosts_by_subnet = cfg.getAll6(subnet_id);
+ HostCollection hosts_by_address = cfg.getAll6(increase(address, host_id));
+ HostPtr host = cfg.get6(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ // Make sure the hosts and IP reservations were added.
+ ASSERT_EQ(host_count / 2, hosts_by_subnet.size());
+ ASSERT_EQ(host_count / 2 + 1, hosts_by_address.size());
+ ASSERT_TRUE(host);
+
+ // Delete one host.
+ EXPECT_TRUE(cfg.del6(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0], duids_[host_id]->getDuid().size()));
+
+ // Check if the host is actually deleted.
+ hosts_by_subnet = cfg.getAll6(subnet_id);
+ hosts_by_address = cfg.getAll6(increase(address, host_id));
+ host = cfg.get6(subnet_id, Host::IdentifierType::IDENT_DUID,
+ &duids_[host_id]->getDuid()[0],
+ duids_[host_id]->getDuid().size());
+ EXPECT_EQ((host_count / 2)-1, hosts_by_subnet.size());
+ EXPECT_EQ(host_count / 2, hosts_by_address.size());
+ EXPECT_FALSE(host);
+}
+
+// This test checks that false is returned for deleting the IPv4 host that
+// doesn't exist.
+TEST_F(CfgHostsTest, del4MissingHost) {
+ CfgHosts cfg;
+ EXPECT_FALSE(cfg.del4(SubnetID(42), Host::IdentifierType::IDENT_DUID,
+ &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
+}
+
+// This test checks that false is returned for deleting the IPv6 host that
+// doesn't exist.
+TEST_F(CfgHostsTest, del6MissingHost) {
+ CfgHosts cfg;
+ EXPECT_FALSE(cfg.del6(SubnetID(42), Host::IdentifierType::IDENT_DUID,
+ &duids_[0]->getDuid()[0], duids_[0]->getDuid().size()));
+}
+
+// This test checks that all reservations for the specified IPv4 subnet can
+// be deleted.
+TEST_F(CfgHostsTest, deleteAll4) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Hosts will differ by hostname. It is easier than differentiating by
+ // IPv4 address because if they all have zero IPv4 address it is
+ // easier to retrieve all of them to check the host counts.
+ std::ostringstream s;
+ s << "hostname" << i;
+
+ // Add host identified by the HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i % 2), SUBNET_ID_UNUSED,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ s.str())));
+ }
+
+ // Get all inserted hosts.
+ HostCollection hosts = cfg.getAll4(IOAddress::IPV4_ZERO_ADDRESS());
+ std::set<uint32_t> subnet_ids;
+ for (HostCollection::const_iterator host = hosts.begin(); host != hosts.end();
+ ++host) {
+ subnet_ids.insert((*host)->getIPv4SubnetID());
+ }
+ // Make sure there are two unique subnets: 1 and 2.
+ ASSERT_EQ(2, subnet_ids.size());
+ EXPECT_EQ(1, *subnet_ids.begin());
+ EXPECT_EQ(2, *subnet_ids.rbegin());
+
+ // Delete all hosts for subnet id 2. There should be 12 of them.
+ EXPECT_EQ(12, cfg.delAll4(SubnetID(2)));
+
+ // Gather the host counts again.
+ subnet_ids.clear();
+ hosts = cfg.getAll4(IOAddress::IPV4_ZERO_ADDRESS());
+ for (HostCollection::const_iterator host = hosts.begin(); host != hosts.end();
+ ++host) {
+ subnet_ids.insert((*host)->getIPv4SubnetID());
+ }
+ // We should only have hosts for one subnet and it should be the subnet
+ // with ID of 1.
+ ASSERT_EQ(1, subnet_ids.size());
+ EXPECT_EQ(1, *subnet_ids.begin());
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv4 subnet (by subnet id).
+TEST_F(CfgHostsTest, get4) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i % 2), SubnetID(13),
+ increase(IOAddress("192.0.2.5"), i))));
+
+ // Add host identified by DUID.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1 + i % 2), SubnetID(13),
+ increase(IOAddress("192.0.2.100"), i))));
+ }
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Retrieve host by HW address.
+ HostPtr host = cfg.get4(SubnetID(1 + i % 2), Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+ EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+ host->getIPv4Reservation());
+
+ // Retrieve host by DUID.
+ host = cfg.get4(SubnetID(1 + i % 2), Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0], duids_[i]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+ EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+ host->getIPv4Reservation());
+
+ }
+}
+
+// This test checks that the DHCPv4 reservations can be unparsed
+TEST_F(CfgHostsTest, unparsed4) {
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHosts cfg;
+ CfgHostsList list;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(1 + i), SubnetID(13),
+ increase(IOAddress("192.0.2.5"), i))));
+
+ // Add host identified by DUID.
+ cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(1 + i), SubnetID(13),
+ increase(IOAddress("192.0.2.100"), i))));
+ }
+
+ using namespace isc::data;
+ ConstElementPtr cfg_unparsed;
+ ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+ ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+ for (unsigned i = 0; i < 25; ++i) {
+ ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+ ASSERT_TRUE(unparsed);
+ ASSERT_EQ(Element::list, unparsed->getType());
+ EXPECT_EQ(2, unparsed->size());
+ ASSERT_NE(0, unparsed->size());
+
+ // Check by HW address entries
+ bool checked_hw = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("hw-address")) {
+ continue;
+ }
+ checked_hw = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("duid"));
+ // Check the HW address
+ ConstElementPtr hw = host->get("hw-address");
+ ASSERT_TRUE(hw);
+ ASSERT_EQ(Element::string, hw->getType());
+ EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+ // Check the reservation
+ ConstElementPtr resv = host->get("ip-address");
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_hw);
+
+ // Check by DUID entries
+ bool checked_duid = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("duid")) {
+ continue;
+ }
+ checked_duid = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("hw-address"));
+ // Check the DUID
+ ConstElementPtr duid = host->get("duid");
+ ASSERT_TRUE(duid);
+ ASSERT_EQ(Element::string, duid->getType());
+ EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+ // Check the reservation
+ ConstElementPtr resv = host->get("ip-address");
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_duid);
+ }
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv6 subnet (by subnet id).
+TEST_F(CfgHostsTest, get6) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(10), SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+
+ // Add host identified by DUID.
+ host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(10), SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:2::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Retrieve host by HW address.
+ HostPtr host = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ reservations.first->second.getPrefix());
+
+ // Retrieve host by DUID.
+ host = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0], duids_[i]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+ reservations = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that all reservations for the specified IPv6 subnet can
+// be deleted.
+TEST_F(CfgHostsTest, deleteAll6) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address. The subnet for which we're
+ // adding the host has id of 1 for even values of i and 2 for
+ // odd values of i.
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(10), SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ // Delete all hosts for subnet id. There should be 13 of them.
+ EXPECT_EQ(13, cfg.delAll6(SubnetID(1)));
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Calculate subnet id for the given i.
+ SubnetID subnet_id = 1 + i % 2;
+
+ // Try to retrieve host by HW address.
+ HostPtr host = cfg.get6(subnet_id, Host::IDENT_HWADDR,
+ &hwaddrs_[i]->hwaddr_[0],
+ hwaddrs_[i]->hwaddr_.size());
+ // The host should exist for subnet id of 2.
+ if (subnet_id == 2) {
+ ASSERT_TRUE(host);
+ EXPECT_EQ(subnet_id, host->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ reservations.first->second.getPrefix());
+
+ } else {
+ // All hosts for subnet id 1 should be gone.
+ EXPECT_FALSE(host);
+ }
+ }
+}
+
+// This test checks that the DHCPv6 reservations can be unparsed
+TEST_F(CfgHostsTest, unparse6) {
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHosts cfg;
+ CfgHostsList list;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+ // Add host identified by HW address.
+ HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+ "hw-address",
+ SubnetID(10), SubnetID(1 + i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:1::1"),
+ i)));
+ cfg.add(host);
+
+ // Add host identified by DUID.
+ host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SubnetID(10), SubnetID(1 + i),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:2::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ using namespace isc::data;
+ ConstElementPtr cfg_unparsed;
+ ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+ ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+ for (unsigned i = 0; i < 25; ++i) {
+ ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+ ASSERT_TRUE(unparsed);
+ ASSERT_EQ(Element::list, unparsed->getType());
+ EXPECT_EQ(2, unparsed->size());
+ ASSERT_NE(0, unparsed->size());
+
+ // Check by HW address entries
+ bool checked_hw = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("hw-address")) {
+ continue;
+ }
+ checked_hw = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("duid"));
+ // Check the HW address
+ ConstElementPtr hw = host->get("hw-address");
+ ASSERT_TRUE(hw);
+ ASSERT_EQ(Element::string, hw->getType());
+ EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+ // Check the reservation
+ ConstElementPtr resvs = host->get("ip-addresses");
+ ASSERT_TRUE(resvs);
+ ASSERT_EQ(Element::list, resvs->getType());
+ EXPECT_EQ(1, resvs->size());
+ ASSERT_GE(1, resvs->size());
+ ConstElementPtr resv = resvs->get(0);
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_hw);
+
+ // Check by DUID entries
+ bool checked_duid = false;
+ for (unsigned j = 0; j < unparsed->size(); ++j) {
+ ConstElementPtr host = unparsed->get(j);
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::map, host->getType());
+ if (!host->contains("duid")) {
+ continue;
+ }
+ checked_duid = true;
+ // Not both hw-address and duid
+ EXPECT_FALSE(host->contains("hw-address"));
+ // Check the DUID
+ ConstElementPtr duid = host->get("duid");
+ ASSERT_TRUE(duid);
+ ASSERT_EQ(Element::string, duid->getType());
+ EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+ // Check the reservation
+ ConstElementPtr resvs = host->get("ip-addresses");
+ ASSERT_TRUE(resvs);
+ ASSERT_EQ(Element::list, resvs->getType());
+ EXPECT_EQ(1, resvs->size());
+ ASSERT_GE(1, resvs->size());
+ ConstElementPtr resv = resvs->get(0);
+ ASSERT_TRUE(resv);
+ ASSERT_EQ(Element::string, resv->getType());
+ EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+ IOAddress(resv->stringValue()));
+ }
+ ASSERT_TRUE(checked_duid);
+ }
+}
+
+// This test checks that the IPv6 reservations can be retrieved for a particular
+// (subnet-id, address) tuple.
+TEST_F(CfgHostsTest, get6ByAddr) {
+ CfgHosts cfg;
+ // Add hosts.
+ for (unsigned i = 0; i < 25; ++i) {
+
+ // Add host identified by DUID.
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ increase(IOAddress("2001:db8:2::1"),
+ i)));
+ cfg.add(host);
+ }
+
+ for (unsigned i = 0; i < 25; ++i) {
+ // Retrieve host by (subnet-id,address).
+ HostPtr host = cfg.get6(SubnetID(1 + i % 2),
+ increase(IOAddress("2001:db8:2::1"), i));
+ ASSERT_TRUE(host);
+
+ EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+ IPv6ResrvRange reservations =
+ host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+ EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+ reservations.first->second.getPrefix());
+ }
+}
+
+// This test checks that the IPv6 reservations can be retrieved for a particular
+// (subnet-id, address) tuple.
+TEST_F(CfgHostsTest, get6MultipleAddrs) {
+ CfgHosts cfg;
+
+ // Add 25 hosts. Each host has reservations for 5 addresses.
+ for (unsigned i = 0; i < 25; ++i) {
+
+ // Add host identified by DUID.
+ HostPtr host = HostPtr(new Host(duids_[i]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1 + i % 2),
+ IOAddress("0.0.0.0")));
+
+ // Generate 5 unique addresses for this host.
+ for (unsigned j = 0; j < 5; ++j) {
+ std::stringstream address_stream;
+ address_stream << "2001:db8:" << i << "::" << j;
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ address_stream.str()));
+ }
+ cfg.add(host);
+ }
+
+ // Now check if we can retrieve each of those 25 hosts by using each
+ // of their addresses.
+ for (unsigned i = 0; i < 25; ++i) {
+
+ // Check that the host is there.
+ HostPtr by_duid = cfg.get6(SubnetID(1 + i % 2), Host::IDENT_DUID,
+ &duids_[i]->getDuid()[0],
+ duids_[i]->getDuid().size());
+ ASSERT_TRUE(by_duid);
+
+ for (unsigned j = 0; j < 5; ++j) {
+ std::stringstream address_stream;
+ address_stream << "2001:db8:" << i << "::" << j;
+
+ // Retrieve host by (subnet-id,address).
+ HostPtr by_addr = cfg.get6(SubnetID(1 + i % 2),
+ address_stream.str());
+ ASSERT_TRUE(by_addr);
+
+ // The pointers should match. Maybe we should compare contents
+ // rather than just pointers? I think there's no reason why
+ // the code would make any copies of the Host object, so
+ // the pointers should always point to the same object.
+ EXPECT_EQ(by_duid, by_addr);
+ }
+ }
+}
+
+
+// Checks that it's not possible for a second host to reserve an address
+// which is already reserved.
+TEST_F(CfgHostsTest, add4AlreadyReserved) {
+ CfgHosts cfg;
+
+ // First host has a reservation for address 192.0.2.1
+ HostPtr host1 = HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(SUBNET_ID_UNUSED),
+ IOAddress("192.0.2.1")));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(hwaddrs_[1]->toText(false),
+ "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.1")));
+
+ // This second host has a reservation for an address that is already
+ // reserved for the first host, so it should be rejected.
+ EXPECT_THROW(cfg.add(host2), isc::dhcp::ReservedAddress);
+}
+
+// Test that it is possible to allow inserting multiple reservations for
+// the same IP address.
+TEST_F(CfgHostsTest, allow4AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same IP address.
+ ASSERT_TRUE(cfg.setIPReservationsUnique(false));
+
+ // First host has a reservation for address 192.0.2.1
+ HostPtr host1 = HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(1), SubnetID(SUBNET_ID_UNUSED),
+ IOAddress("192.0.2.1")));
+ ASSERT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(hwaddrs_[1]->toText(false),
+ "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.1")));
+ // Adding this should work because the HW address is different.
+ ASSERT_NO_THROW(cfg.add(host2));
+
+ // Get both hosts.
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = cfg.getAll4(host1->getIPv4SubnetID(), IOAddress("192.0.2.1")));
+ EXPECT_EQ(2, returned.size());
+
+ // Make sure the address is the same but the identifiers are different.
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+ EXPECT_EQ(returned[0]->getIPv4Reservation().toText(),
+ returned[1]->getIPv4Reservation().toText());
+}
+
+// Checks that it's not possible for two hosts to have the same address
+// reserved at the same time.
+TEST_F(CfgHostsTest, add6Invalid2Hosts) {
+ CfgHosts cfg;
+
+ // First host has a reservation for address 2001:db8::1
+ HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+
+ // This second host has a reservation for an address that is already
+ // reserved for the first host, so it should be rejected.
+ EXPECT_THROW(cfg.add(host2), isc::dhcp::DuplicateHost);
+}
+
+// Test that it is possible to allow inserting multiple reservations for
+// the same IPv6 address.
+TEST_F(CfgHostsTest, allowAddress6AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same IP address.
+ ASSERT_TRUE(cfg.setIPReservationsUnique(false));
+
+ // First host has a reservation for address 2001:db8::1
+ HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same address.
+ HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8::1")));
+
+ // Adding this should work because the DUID is different.
+ ASSERT_NO_THROW(cfg.add(host2));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = cfg.getAll6(host1->getIPv6SubnetID(), IOAddress("2001:db8::1")));
+ EXPECT_EQ(2, returned.size());
+
+ // Make sure the address is the same but the identifiers are different.
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ auto range0 = returned[0]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ EXPECT_EQ(1, std::distance(range0.first, range0.second));
+ auto range1 = returned[1]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ EXPECT_EQ(1, std::distance(range1.first, range1.second));
+ EXPECT_EQ(range0.first->second.getPrefix().toText(),
+ range1.first->second.getPrefix().toText());
+}
+
+// Test that it is possible to allow inserting multiple reservations for
+// the same IPv6 delegated prefix.
+TEST_F(CfgHostsTest, allowPrefix6AlreadyReserved) {
+ CfgHosts cfg;
+ // Allow creating multiple reservations for the same delegated prefix.
+ ASSERT_TRUE(cfg.setIPReservationsUnique(false));
+
+ // First host has a reservation for prefix 3000::/64.
+ HostPtr host1 = HostPtr(new Host(duids_[0]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("3000::"), 64));
+ // Adding this should work.
+ EXPECT_NO_THROW(cfg.add(host1));
+
+ // The second host has a reservation for the same prefix.
+ HostPtr host2 = HostPtr(new Host(duids_[1]->toText(), "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0")));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("3000::"), 64));
+
+ // Adding this should work because the DUID is different.
+ ASSERT_NO_THROW(cfg.add(host2));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = cfg.getAll6(host1->getIPv6SubnetID(), IOAddress("3000::")));
+ EXPECT_EQ(2, returned.size());
+
+ // Make sure the prefix is the same but the identifiers are different.
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ auto range0 = returned[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ EXPECT_EQ(1, std::distance(range0.first, range0.second));
+ auto range1 = returned[1]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ EXPECT_EQ(1, std::distance(range1.first, range1.second));
+ EXPECT_EQ(range0.first->second.getPrefix().toText(),
+ range1.first->second.getPrefix().toText());
+}
+
+// Check that no error is reported when adding a host with subnet
+// ids equal to global.
+TEST_F(CfgHostsTest, globalSubnetIDs) {
+ CfgHosts cfg;
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_GLOBAL, SUBNET_ID_GLOBAL,
+ IOAddress("10.0.0.1")))));
+}
+
+
+// Check that error is reported when trying to add a host with subnet
+// ids equal to unused.
+TEST_F(CfgHostsTest, unusedSubnetIDs) {
+ CfgHosts cfg;
+ ASSERT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.1")))),
+ isc::BadValue);
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.1")))));
+
+ // Try to add the host with the same HW address to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SubnetID(11), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.1")))));
+
+ // Try to add the host with the same DUID to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SubnetID(11), SUBNET_ID_UNUSED,
+ IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv6 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+
+ // Try to add the host with the same HW address to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+ "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(2),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv6 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
+ CfgHosts cfg;
+ // Add a host.
+ ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+
+ // Try to add the host with the same DUID to the same subnet. The fact
+ // that the IP address is different here shouldn't really matter.
+ EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))),
+ isc::dhcp::DuplicateHost);
+
+ // Now try to add it to a different subnet. It should go through.
+ EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+ "duid",
+ SUBNET_ID_UNUSED, SubnetID(2),
+ IOAddress("0.0.0.0"),
+ "foo.example.com"))));
+}
+
+// Checks that updates work correctly.
+TEST_F(CfgHostsTest, update) {
+ CfgHosts cfg;
+
+ HostPtr const host(boost::make_shared<Host>(duids_[0]->toText(), "duid", SUBNET_ID_UNUSED,
+ SubnetID(1), IOAddress("0.0.0.0"),
+ "foo.example.com"));
+
+ // Updating a host that doesn't exist should throw.
+ EXPECT_THROW_MSG(cfg.update(host), HostNotFound, "Host not updated (not found).");
+
+ // There should be no hosts.
+ HostCollection hosts(cfg.getAll6(SubnetID(1)));
+ EXPECT_EQ(0, hosts.size());
+
+ // Add a host.
+ EXPECT_NO_THROW(cfg.add(host));
+
+ // The host should be in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=foo.example.com "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+
+ // Update the host. Change nothing.
+ EXPECT_NO_THROW(cfg.update(host));
+
+ // The same host should be in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=foo.example.com "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+
+ // Update the host with new hostname.
+ host->setHostname("bar.example.com");
+ EXPECT_NO_THROW(cfg.update(host));
+
+ // The change should be reflected in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=bar.example.com "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+
+ // Remove hostname from host.
+ host->setHostname("");
+ EXPECT_NO_THROW(cfg.update(host));
+
+ // The change should be reflected in the config.
+ hosts = cfg.getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ("duid=010203040500 ipv6_subnet_id=1 hostname=(empty) "
+ "ipv4_reservation=(no) siaddr=(no) sname=(empty) file=(empty) "
+ "key=(empty) ipv6_reservations=(none)", hosts[0]->toText());
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc b/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
new file mode 100644
index 0000000..03591c0
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
@@ -0,0 +1,1065 @@
+// Copyright (C) 2014-2022 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 <dhcp/dhcp4.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_filter_test_stub.h>
+#include <dhcp/tests/pkt_filter6_test_stub.h>
+#include <dhcpsrv/cfg_iface.h>
+#include <asiolink/io_service.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for testing the @c CfgIface class.
+class CfgIfaceTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// By initializing the @c IfaceMgrTestConfig object it creates a set of
+ /// fake interfaces: lo, eth0, eth1.
+ CfgIfaceTest() :
+ iface_mgr_test_config_(true) {
+ }
+
+ /// @brief Checks if socket of the specified family is opened on interface.
+ ///
+ /// @param iface_name Interface name.
+ /// @param family One of: AF_INET or AF_INET6
+ bool socketOpen(const std::string& iface_name, const int family) const;
+
+ /// @brief Checks if socket is opened on the specified interface and bound
+ /// to a specific IPv4 address.
+ ///
+ /// @param iface_name Interface name.
+ /// @param address Address that the socket should be bound to.
+ bool socketOpen(const std::string& iface_name,
+ const std::string& address) const;
+
+ /// @brief Checks if unicast socket is opened on interface.
+ ///
+ /// @param iface_name Interface name.
+ bool unicastOpen(const std::string& iface_name) const;
+
+ /// @brief Wait for specific timeout.
+ ///
+ /// @param timeout Wait timeout in milliseconds.
+ void doWait(const long timeout);
+
+ /// @brief Holds a fake configuration of the interfaces.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+ /// @brief Pointer to IO service used by the tests.
+ asiolink::IOServicePtr io_service_;
+
+private:
+
+ /// @brief Prepares the class for a test.
+ virtual void SetUp();
+
+ /// @brief Cleans up after the test.
+ virtual void TearDown();
+};
+
+void
+CfgIfaceTest::SetUp() {
+ IfaceMgr::instance().setTestMode(true);
+ io_service_.reset(new asiolink::IOService());
+ TimerMgr::instance()->setIOService(io_service_);
+}
+
+void
+CfgIfaceTest::TearDown() {
+ // Remove all timers.
+ TimerMgr::instance()->unregisterTimers();
+
+ IfaceMgr::instance().setTestMode(false);
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().detectIfaces();
+
+ // Reset global handlers
+ CfgIface::open_sockets_failed_callback_ = 0;
+}
+
+bool
+CfgIfaceTest::socketOpen(const std::string& iface_name,
+ const int family) const {
+ return (iface_mgr_test_config_.socketOpen(iface_name, family));
+}
+
+bool
+CfgIfaceTest::socketOpen(const std::string& iface_name,
+ const std::string& address) const {
+ return (iface_mgr_test_config_.socketOpen(iface_name, address));
+}
+
+bool
+CfgIfaceTest::unicastOpen(const std::string& iface_name) const {
+ return (iface_mgr_test_config_.unicastOpen(iface_name));
+}
+
+void
+CfgIfaceTest::doWait(const long timeout) {
+ asiolink::IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, timeout, asiolink::IntervalTimer::ONE_SHOT);
+ io_service_->run();
+ io_service_->get_io_service().reset();
+}
+
+// This test checks that the interface names can be explicitly selected
+// by their names and IPv4 sockets are opened on these interfaces.
+TEST_F(CfgIfaceTest, explicitNamesV4) {
+ CfgIface cfg;
+ // Specify valid interface names. There should be no error.
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth0"));
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth1"));
+
+ // Open sockets on specified interfaces.
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+ // Sockets should be now open on eth0 and eth1, but not on loopback.
+ EXPECT_TRUE(socketOpen("eth0", AF_INET));
+ EXPECT_TRUE(socketOpen("eth1", AF_INET));
+ EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+ // No IPv6 sockets should be present because we wanted IPv4 sockets.
+ EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+ EXPECT_FALSE(socketOpen("eth1", AF_INET6));
+ EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+ // Close all sockets and make sure they are really closed.
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("eth0", AF_INET));
+ ASSERT_FALSE(socketOpen("eth1", AF_INET));
+ ASSERT_FALSE(socketOpen("lo", AF_INET));
+
+ // Reset configuration and select only one interface this time.
+ cfg.reset();
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth1"));
+
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+ // Socket should be open on eth1 only.
+ EXPECT_FALSE(socketOpen("eth0", AF_INET));
+ EXPECT_TRUE(socketOpen("eth1", AF_INET));
+ EXPECT_FALSE(socketOpen("lo", AF_INET));
+}
+
+// This test checks that it is possible to specify an interface and address
+// on this interface to which the socket should be bound. The sockets should
+// not be opened on other addresses on this interface.
+TEST_F(CfgIfaceTest, explicitNamesAndAddressesV4) {
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth0/10.0.0.1"));
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"));
+
+ // Open sockets on specified interfaces and addresses.
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+ EXPECT_TRUE(socketOpen("eth0", "10.0.0.1"));
+ EXPECT_TRUE(socketOpen("eth1", "192.0.2.3"));
+ EXPECT_FALSE(socketOpen("eth1", "192.0.2.5"));
+
+ // Close all sockets and make sure they are really closed.
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("eth0", "10.0.0.1"));
+ ASSERT_FALSE(socketOpen("eth1", "192.0.2.3"));
+ ASSERT_FALSE(socketOpen("eth1", "192.0.2.5"));
+
+ // Reset configuration.
+ cfg.reset();
+
+ // Now check that the socket can be bound to a different address on
+ // eth1.
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.5"));
+
+ // Open sockets according to the new configuration.
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+ EXPECT_FALSE(socketOpen("eth0", "10.0.0.1"));
+ EXPECT_FALSE(socketOpen("eth1", "192.0.2.3"));
+ EXPECT_TRUE(socketOpen("eth1", "192.0.2.5"));
+}
+
+// This test checks that the invalid interface name and/or IPv4 address
+// results in error.
+TEST_F(CfgIfaceTest, explicitNamesAndAddressesInvalidV4) {
+ CfgIface cfg;
+ // An address not assigned to the interface.
+ EXPECT_THROW(cfg.use(AF_INET, "eth0/10.0.0.2"), NoSuchAddress);
+ // IPv6 address.
+ EXPECT_THROW(cfg.use(AF_INET, "eth0/2001:db8:1::1"), InvalidIfaceName);
+ // Wildcard interface name with an address.
+ EXPECT_THROW(cfg.use(AF_INET, "*/10.0.0.1"), InvalidIfaceName);
+
+ // Duplicated interface.
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth1"));
+ EXPECT_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"), DuplicateIfaceName);
+}
+
+// This test checks that it is possible to explicitly select multiple
+// IPv4 addresses on a single interface.
+TEST_F(CfgIfaceTest, multipleAddressesSameInterfaceV4) {
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"));
+ // Cannot add the same address twice.
+ ASSERT_THROW(cfg.use(AF_INET, "eth1/192.0.2.3"), DuplicateAddress);
+ // Can add another address on this interface.
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth1/192.0.2.5"));
+ // Can't select the whole interface.
+ ASSERT_THROW(cfg.use(AF_INET, "eth1"), DuplicateIfaceName);
+
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+ EXPECT_FALSE(socketOpen("eth0", "10.0.0.1"));
+ EXPECT_TRUE(socketOpen("eth1", "192.0.2.3"));
+ EXPECT_TRUE(socketOpen("eth1", "192.0.2.5"));
+}
+
+// This test checks that it is possible to specify the loopback interface.
+TEST_F(CfgIfaceTest, explicitLoopbackV4) {
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
+
+ // Use UDP sockets
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
+
+ // Open sockets on specified interfaces and addresses.
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+ EXPECT_TRUE(socketOpen("lo", "127.0.0.1"));
+
+ // Close all sockets and make sure they are really closed.
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("lo", "127.0.0.1"));
+
+ // Reset configuration.
+ cfg.reset();
+
+ // Retry with wildcard
+ ASSERT_NO_THROW(cfg.use(AF_INET, "*"));
+ ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+ // It is now allowed to use loopback, even with wildcard.
+ EXPECT_TRUE(socketOpen("lo", "127.0.0.1"));
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("lo", "127.0.0.1"));
+
+ // Retry without UDP sockets (lo can be only used with udp sockets)
+ cfg.reset();
+ ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+ // No loopback socket
+ EXPECT_FALSE(socketOpen("lo", "127.0.0.1"));
+
+ // Retry with a second interface
+ cfg.reset();
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth0"));
+ ASSERT_NO_THROW(cfg.use(AF_INET, "lo"));
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+ // The logic used to require lo to be the only interface. That constraint
+ // was removed.
+ EXPECT_TRUE(socketOpen("lo", "127.0.0.1"));
+ cfg.closeSockets();
+ EXPECT_FALSE(socketOpen("lo", "127.0.0.1"));
+
+ // Finally with interfaces and addresses
+ cfg.reset();
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth0/10.0.0.1"));
+ ASSERT_NO_THROW(cfg.use(AF_INET, "lo/127.0.0.1"));
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+ // Only loopback is no longer a constraint
+ EXPECT_TRUE(socketOpen("lo", "127.0.0.1"));
+ cfg.closeSockets();
+ EXPECT_FALSE(socketOpen("lo", "127.0.0.1"));
+}
+
+// This test checks that the interface names can be explicitly selected
+// by their names and IPv6 sockets are opened on these interfaces.
+TEST_F(CfgIfaceTest, explicitNamesV6) {
+ CfgIface cfg;
+ // Specify valid interface names. There should be no error.
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0"));
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "eth1"));
+
+ // Open sockets on specified interfaces.
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+
+ // Sockets should be now open on eth0 and eth1, but not on loopback.
+ EXPECT_TRUE(socketOpen("eth0", AF_INET6));
+ EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+ EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+ // No IPv4 sockets should be present because we wanted IPv6 sockets.
+ EXPECT_FALSE(socketOpen("eth0", AF_INET));
+ EXPECT_FALSE(socketOpen("eth1", AF_INET));
+ EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+ // Close all sockets and make sure they are really closed.
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("eth0", AF_INET6));
+ ASSERT_FALSE(socketOpen("eth1", AF_INET6));
+ ASSERT_FALSE(socketOpen("lo", AF_INET6));
+
+ // Reset configuration and select only one interface this time.
+ cfg.reset();
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "eth1"));
+
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+
+ // Socket should be open on eth1 only.
+ EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+ EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+ EXPECT_FALSE(socketOpen("lo", AF_INET6));
+}
+
+// This test checks that the wildcard interface name can be specified to
+// select all interfaces to open IPv4 sockets.
+TEST_F(CfgIfaceTest, wildcardV4) {
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(AF_INET, "*"));
+
+ cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
+
+ // Sockets should be now open on eth0 and eth1, but not on loopback.
+ EXPECT_TRUE(socketOpen("eth0", AF_INET));
+ EXPECT_TRUE(socketOpen("eth1", AF_INET));
+ EXPECT_FALSE(socketOpen("lo", AF_INET));
+
+ // No IPv6 sockets should be present because we wanted IPv4 sockets.
+ EXPECT_FALSE(socketOpen("eth0", AF_INET6));
+ EXPECT_FALSE(socketOpen("eth1", AF_INET6));
+ EXPECT_FALSE(socketOpen("lo", AF_INET6));
+}
+
+// This test checks that the wildcard interface name can be specified to
+// select all interfaces to open IPv6 sockets.
+TEST_F(CfgIfaceTest, wildcardV6) {
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "*"));
+
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+
+ // Sockets should be now open on eth0 and eth1, but not on loopback.
+ EXPECT_TRUE(socketOpen("eth0", AF_INET6));
+ EXPECT_TRUE(socketOpen("eth1", AF_INET6));
+ EXPECT_FALSE(socketOpen("lo", AF_INET6));
+
+ // No IPv6 sockets should be present because we wanted IPv6 sockets.
+ EXPECT_FALSE(socketOpen("eth0", AF_INET));
+ EXPECT_FALSE(socketOpen("eth1", AF_INET));
+ EXPECT_FALSE(socketOpen("lo", AF_INET));
+}
+
+// Test that unicast address can be specified for the socket to be opened on
+// the interface on which the socket bound to link local address is also
+// opened.
+TEST_F(CfgIfaceTest, validUnicast) {
+ CfgIface cfg;
+
+ // One socket will be opened on link-local address, one on unicast but
+ // on the same interface.
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0"));
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0/2001:db8:1::1"));
+
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+
+ EXPECT_TRUE(socketOpen("eth0", AF_INET6));
+ EXPECT_TRUE(unicastOpen("eth0"));
+}
+
+// Test that when invalid interface names are specified an exception is thrown.
+TEST_F(CfgIfaceTest, invalidValues) {
+ CfgIface cfg;
+ ASSERT_THROW(cfg.use(AF_INET, ""), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(AF_INET, " "), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(AF_INET, "bogus"), NoSuchIface);
+
+ ASSERT_NO_THROW(cfg.use(AF_INET, "eth0"));
+ ASSERT_THROW(cfg.use(AF_INET, "eth0"), DuplicateIfaceName);
+
+ ASSERT_THROW(cfg.use(AF_INET, "eth0/2001:db8:1::1"), InvalidIfaceName);
+
+ ASSERT_THROW(cfg.use(AF_INET6, "eth0/"), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(AF_INET6, "/2001:db8:1::1"), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(AF_INET6, "*/2001:db8:1::1"), InvalidIfaceName);
+ ASSERT_THROW(cfg.use(AF_INET6, "bogus/2001:db8:1::1"), NoSuchIface);
+ ASSERT_THROW(cfg.use(AF_INET6, "eth0/2001:db8:1::2"), NoSuchAddress);
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "*"));
+ ASSERT_THROW(cfg.use(AF_INET6, "*"), DuplicateIfaceName);
+}
+
+// This test checks that it is possible to specify the loopback interface.
+// Note that without a link-local address an unicast address is required.
+TEST_F(CfgIfaceTest, explicitLoopbackV6) {
+ CfgIface cfg;
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
+
+ // Open sockets on specified interfaces and addresses.
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+
+ EXPECT_TRUE(socketOpen("lo", AF_INET6));
+
+ // Close all sockets and make sure they are really closed.
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("lo", AF_INET6));
+
+ // Reset configuration.
+ cfg.reset();
+
+ // Retry with wildcard
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "*"));
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+ // The logic used to require lo to be used only on its own, not with a
+ // wildcard. That constraint was removed.
+ EXPECT_TRUE(socketOpen("lo", AF_INET6));
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("lo", AF_INET6));
+
+ // Retry with a second interface
+ cfg.reset();
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0"));
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+ // The logic used to require lo to be used only on its own, not with a
+ // wildcard. That constraint was removed.
+ EXPECT_TRUE(socketOpen("lo", AF_INET6));
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("lo", AF_INET6));
+
+ // Finally with interfaces and addresses
+ cfg.reset();
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "eth0/2001:db8:1::1"));
+ ASSERT_NO_THROW(cfg.use(AF_INET6, "lo/::1"));
+ cfg.openSockets(AF_INET6, DHCP6_SERVER_PORT);
+ // The logic used to require lo to be used only on its own, not with a
+ // wildcard. That constraint was removed.
+ EXPECT_TRUE(socketOpen("lo", AF_INET6));
+ cfg.closeSockets();
+ ASSERT_FALSE(socketOpen("lo", AF_INET6));
+}
+
+// Test that the equality and inequality operators work fine for CfgIface.
+TEST_F(CfgIfaceTest, equality) {
+ CfgIface cfg1;
+ CfgIface cfg2;
+
+ // Initially objects must be equal.
+ EXPECT_TRUE(cfg1 == cfg2);
+ EXPECT_FALSE(cfg1 != cfg2);
+
+ // Differ by one interface.
+ cfg1.use(AF_INET, "eth0");
+ EXPECT_FALSE(cfg1 == cfg2);
+ EXPECT_TRUE(cfg1 != cfg2);
+
+ // Now interfaces should be equal.
+ cfg2.use(AF_INET, "eth0");
+ EXPECT_TRUE(cfg1 == cfg2);
+ EXPECT_FALSE(cfg1 != cfg2);
+
+ // Differ by unicast address.
+ cfg1.use(AF_INET6, "eth0/2001:db8:1::1");
+ EXPECT_FALSE(cfg1 == cfg2);
+ EXPECT_TRUE(cfg1 != cfg2);
+
+ // Differ by unicast address and one interface.
+ cfg2.use(AF_INET6, "eth1");
+ EXPECT_FALSE(cfg1 == cfg2);
+ EXPECT_TRUE(cfg1 != cfg2);
+
+ // Now, the unicast addresses are equal but still differ by one interface.
+ cfg2.use(AF_INET6, "eth0/2001:db8:1::1");
+ EXPECT_FALSE(cfg1 == cfg2);
+ EXPECT_TRUE(cfg1 != cfg2);
+
+ // They should be now back to equal.
+ cfg1.use(AF_INET6, "eth1");
+ EXPECT_TRUE(cfg1 == cfg2);
+ EXPECT_FALSE(cfg1 != cfg2);
+
+ // Even though the wildcard doesn't change anything because all interfaces
+ // are already in use, the fact that the wildcard is specified should
+ // cause them to be not equal.
+ cfg1.use(AF_INET6, "*");
+ EXPECT_FALSE(cfg1 == cfg2);
+ EXPECT_TRUE(cfg1 != cfg2);
+
+ // Finally, both are equal as they use wildcard.
+ cfg2.use(AF_INET, "*");
+ EXPECT_TRUE(cfg1 == cfg2);
+ EXPECT_FALSE(cfg1 != cfg2);
+
+ // Differ by socket type.
+ cfg1.useSocketType(AF_INET, "udp");
+ EXPECT_FALSE(cfg1 == cfg2);
+ EXPECT_TRUE(cfg1 != cfg2);
+
+ // Now, both should use the same socket type.
+ cfg2.useSocketType(AF_INET, "udp");
+ EXPECT_TRUE(cfg1 == cfg2);
+ EXPECT_FALSE(cfg1 != cfg2);
+}
+
+// This test verifies that it is possible to unparse the interface config.
+TEST_F(CfgIfaceTest, unparse) {
+ CfgIface cfg4;
+
+ // Add things in it
+ EXPECT_NO_THROW(cfg4.use(AF_INET, "*"));
+ EXPECT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+ EXPECT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
+ std::string comment = "{ \"comment\": \"foo\", \"bar\": 1 }";
+ EXPECT_NO_THROW(cfg4.setContext(Element::fromJSON(comment)));
+
+ // Check unparse
+ std::string expected =
+ "{ "
+ "\"interfaces\": [ \"*\", \"eth0\", \"eth1/192.0.2.3\" ], "
+ "\"re-detect\": false, "
+ "\"user-context\": { \"comment\": \"foo\", \"bar\": 1 } }";
+ runToElementTest<CfgIface>(expected, cfg4);
+
+ // Now check IPv6
+ CfgIface cfg6;
+ EXPECT_NO_THROW(cfg6.use(AF_INET6, "*"));
+ EXPECT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+ EXPECT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
+ comment = "{ \"comment\": \"bar\", \"foo\": 2 }";
+ EXPECT_NO_THROW(cfg6.setContext(Element::fromJSON(comment)));
+
+ expected =
+ "{ "
+ "\"interfaces\": [ \"*\", \"eth1\", \"eth0/2001:db8:1::1\" ], "
+ "\"re-detect\": false, "
+ "\"user-context\": { \"comment\": \"bar\", \"foo\": 2 } }";
+ runToElementTest<CfgIface>(expected, cfg6);
+}
+
+// This test verifies that it is possible to require that all
+// service sockets are opened properly. If any socket fails to
+// bind then an exception should be thrown.
+TEST_F(CfgIfaceTest, requireOpenAllServiceSockets) {
+ CfgIface cfg4;
+ CfgIface cfg6;
+
+ // Configure a fail callback
+ uint16_t fail_calls = 0;
+ CfgIface::OpenSocketsFailedCallback on_fail_callback =
+ [&fail_calls](ReconnectCtlPtr reconnect_ctl) {
+ EXPECT_TRUE(reconnect_ctl);
+ EXPECT_TRUE(reconnect_ctl->exitOnFailure());
+ fail_calls++;
+ };
+
+ CfgIface::open_sockets_failed_callback_ = on_fail_callback;
+
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+
+ // Require all sockets bind successfully
+ cfg4.setServiceSocketsRequireAll(true);
+ cfg4.setServiceSocketsMaxRetries(0);
+ cfg6.setServiceSocketsRequireAll(true);
+ cfg6.setServiceSocketsMaxRetries(0);
+
+ // Open the available ports
+ ASSERT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
+ ASSERT_NO_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT));
+ cfg4.closeSockets();
+ cfg6.closeSockets();
+
+ // Set the callback to throw an exception on open
+ auto open_callback = [](uint16_t) {
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ };
+ boost::shared_ptr<isc::dhcp::test::PktFilterTestStub> filter(new isc::dhcp::test::PktFilterTestStub());
+ boost::shared_ptr<isc::dhcp::test::PktFilter6TestStub> filter6(new isc::dhcp::test::PktFilter6TestStub());
+ filter->setOpenSocketCallback(open_callback);
+ filter6->setOpenSocketCallback(open_callback);
+ ASSERT_TRUE(filter);
+ ASSERT_TRUE(filter6);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter6));
+
+ // Open an unavailable port
+ EXPECT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
+ EXPECT_NO_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT));
+
+ // Both instances should call the fail callback.
+ EXPECT_EQ(fail_calls, 2);
+}
+
+// This test verifies that if any IPv4 socket fails to bind,
+// the opening will retry.
+TEST_F(CfgIfaceTest, retryOpenServiceSockets4) {
+ CfgIface cfg4;
+
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
+
+ // Parameters
+ const uint16_t RETRIES = 5;
+ const uint16_t WAIT_TIME = 10; // miliseconds
+ // The number of sockets opened in a single retry attempt.
+ // iface: eth0 addr: 10.0.0.1 port: 67 rbcast: 0 sbcast: 0
+ // iface: eth1 addr: 192.0.2.3 port: 67 rbcast: 0 sbcast: 0
+ const uint16_t CALLS_PER_RETRY = 2;
+
+ // Require retry socket binding
+ cfg4.setServiceSocketsMaxRetries(RETRIES);
+ cfg4.setServiceSocketsRetryWaitTime(WAIT_TIME);
+
+ // Set the callback to count calls and check wait time
+ size_t total_calls = 0;
+ auto last_call_time = std::chrono::system_clock::time_point::min();
+ auto open_callback = [&total_calls, &last_call_time, WAIT_TIME](uint16_t) {
+ auto now = std::chrono::system_clock::now();
+
+ // Check waiting time only for the first call in a retry attempt.
+ if (total_calls % CALLS_PER_RETRY == 0) {
+ // Don't check the waiting time for initial call.
+ if (total_calls != 0) {
+ auto interval = now - last_call_time;
+ auto interval_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval
+ ).count();
+
+ EXPECT_GE(interval_ms, WAIT_TIME);
+ }
+
+ last_call_time = now;
+ }
+
+ total_calls++;
+
+ // Fail to open a socket
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilterTestStub> filter(
+ new isc::dhcp::test::PktFilterTestStub()
+ );
+
+ filter->setOpenSocketCallback(open_callback);
+
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // Open an unavailable port
+ ASSERT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
+
+ // Wait for a finish sockets binding (with a safe margin).
+ doWait(RETRIES * WAIT_TIME * 2);
+
+ // For each interface perform 1 init open and a few retries.
+ EXPECT_EQ(CALLS_PER_RETRY * (RETRIES + 1), total_calls);
+}
+
+// This test verifies that if any IPv4 socket fails to bind, the opening will
+// retry, but the opened sockets will not be re-bound.
+TEST_F(CfgIfaceTest, retryOpenServiceSockets4OmitBound) {
+ CfgIface cfg4;
+
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
+
+ // Parameters
+ const uint16_t RETRIES = 5;
+ const uint16_t WAIT_TIME = 10; // miliseconds
+
+ // Require retry socket binding
+ cfg4.setServiceSocketsMaxRetries(RETRIES);
+ cfg4.setServiceSocketsRetryWaitTime(WAIT_TIME);
+
+ // Set the callback to count calls and check wait time
+ size_t total_calls = 0;
+ auto last_call_time = std::chrono::system_clock::time_point::min();
+ auto open_callback = [&total_calls, &last_call_time, WAIT_TIME](uint16_t) {
+ auto now = std::chrono::system_clock::now();
+ bool is_eth1 = total_calls == 1;
+
+ // Skip the wait time check for the socket when two sockets are
+ // binding in a single attempt.
+
+ // Don't check the waiting time for initial calls.
+ // iface: eth0 addr: 10.0.0.1 port: 67 rbcast: 0 sbcast: 0
+ // iface: eth1 addr: 192.0.2.3 port: 67 rbcast: 0 sbcast: 0 - fails
+ if (total_calls > 1) {
+ auto interval = now - last_call_time;
+ auto interval_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval
+ ).count();
+
+ EXPECT_GE(interval_ms, WAIT_TIME);
+ }
+
+ last_call_time = now;
+
+ total_calls++;
+
+ // Fail to open a socket on eth0, success for eth1
+ if (!is_eth1) {
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ }
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilterTestStub> filter(
+ new isc::dhcp::test::PktFilterTestStub()
+ );
+
+ filter->setOpenSocketCallback(open_callback);
+
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // Open an unavailable port
+ ASSERT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
+
+ // Wait for a finish sockets binding (with a safe margin).
+ doWait(RETRIES * WAIT_TIME * 2);
+
+ // For eth0 interface perform 1 init open and a few retries,
+ // for eth1 interface perform only init open.
+ EXPECT_EQ((RETRIES + 1) + 1, total_calls);
+}
+
+// Test that only one reopen timer is active simultaneously. If a new opening
+// starts, then the previous should be interrupted.
+TEST_F(CfgIfaceTest, retryDoubleOpenServiceSockets4) {
+ CfgIface cfg4;
+
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+
+ // Initial timer has a high frequency.
+ cfg4.setServiceSocketsMaxRetries(10000);
+ cfg4.setServiceSocketsRetryWaitTime(1);
+
+ // Set the callback that interrupt the previous execution.
+ uint16_t first_port_calls = 0;
+ uint16_t second_port_calls = 0;
+ auto open_callback = [&first_port_calls, &second_port_calls](uint16_t port) {
+ // First timer must be interrupted.
+ if (second_port_calls > 0) {
+ EXPECT_TRUE(port == 2);
+ }
+
+ if (port == 1) {
+ first_port_calls++;
+ } else {
+ second_port_calls++;
+ }
+
+ // Fail to open and retry.
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilterTestStub> filter(
+ new isc::dhcp::test::PktFilterTestStub()
+ );
+
+ filter->setOpenSocketCallback(open_callback);
+
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // First opening.
+ ASSERT_NO_THROW(cfg4.openSockets(AF_INET, 1));
+
+ // Wait a short time.
+ doWait(10);
+
+ // Reconfigure the interface parameters.
+ cfg4.setServiceSocketsMaxRetries(1);
+ cfg4.setServiceSocketsRetryWaitTime(10);
+
+ // Second opening.
+ ASSERT_NO_THROW(cfg4.openSockets(AF_INET, 2));
+
+ doWait(50);
+
+ // The first timer should perform some calls.
+ EXPECT_GT(first_port_calls, 0);
+ // The secondary timer should make 2 calls: initial and 1 retry.
+ EXPECT_EQ(second_port_calls, 2);
+}
+
+// This test verifies that if any IPv6 socket fails to bind,
+// the opening will retry.
+TEST_F(CfgIfaceTest, retryOpenServiceSockets6) {
+ CfgIface cfg6;
+
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+
+ // Parameters
+ const uint16_t RETRIES = 5;
+ const uint16_t WAIT_TIME = 10; // miliseconds
+ // The number of sockets opened in a single retry attempt.
+ // 1 unicast and 2 multicast sockets.
+ // iface: eth0 addr: 2001:db8:1::1 port: 547 multicast: 0
+ // iface: eth0 addr: fe80::3a60:77ff:fed5:cdef port: 547 multicast: 1
+ // iface: eth1 addr: fe80::3a60:77ff:fed5:abcd port: 547 multicast: 1
+ const uint16_t CALLS_PER_RETRY = 3;
+
+ // Require retry socket binding
+ cfg6.setServiceSocketsMaxRetries(RETRIES);
+ cfg6.setServiceSocketsRetryWaitTime(WAIT_TIME);
+
+ // Set the callback to count calls and check wait time
+ size_t total_calls = 0;
+ auto last_call_time = std::chrono::system_clock::time_point::min();
+ auto open_callback = [&total_calls, &last_call_time, WAIT_TIME](uint16_t) {
+ auto now = std::chrono::system_clock::now();
+
+ // Check waiting time only for the first call in a retry attempt.
+ if (total_calls % CALLS_PER_RETRY == 0) {
+ // Don't check the waiting time for initial call.
+ if (total_calls != 0) {
+ auto interval = now - last_call_time;
+ auto interval_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval
+ ).count();
+
+ EXPECT_GE(interval_ms, WAIT_TIME);
+ }
+
+ last_call_time = now;
+ }
+
+ total_calls++;
+
+ // Fail to open a socket
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilter6TestStub> filter(
+ new isc::dhcp::test::PktFilter6TestStub()
+ );
+ filter->setOpenSocketCallback(open_callback);
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // Open an unavailable port
+ ASSERT_NO_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT));
+
+ // Wait for a finish sockets binding (with a safe margin).
+ doWait(RETRIES * WAIT_TIME * 2);
+
+ // For each interface perform 1 init open and a few retries.
+ EXPECT_EQ(CALLS_PER_RETRY * (RETRIES + 1), total_calls);
+}
+
+// This test verifies that if any IPv6 socket fails to bind, the opening will
+// retry, but the opened sockets will not be re-bound.
+TEST_F(CfgIfaceTest, retryOpenServiceSockets6OmitBound) {
+ CfgIface cfg6;
+
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+
+ // Parameters
+ const uint16_t RETRIES = 5;
+ const uint16_t WAIT_TIME = 10; // miliseconds
+
+ // Require retry socket binding
+ cfg6.setServiceSocketsMaxRetries(RETRIES);
+ cfg6.setServiceSocketsRetryWaitTime(WAIT_TIME);
+
+#if defined OS_LINUX
+ const uint32_t opened_by_eth0 = 3;
+#else
+ const uint32_t opened_by_eth0 = 2;
+#endif
+
+ // Set the callback to count calls and check wait time
+ size_t total_calls = 0;
+ auto last_call_time = std::chrono::system_clock::time_point::min();
+ auto open_callback = [&total_calls, &last_call_time, WAIT_TIME](uint16_t) {
+ auto now = std::chrono::system_clock::now();
+ bool is_eth0 = total_calls < opened_by_eth0;
+
+ // Skip the wait time check for the socket when two sockets are
+ // binding in a single attempt.
+
+ // Don't check the waiting time for initial calls.
+ // iface: eth0 addr: 2001:db8:1::1 port: 547 multicast: 0
+ // iface: eth0 addr: fe80::3a60:77ff:fed5:cdef port: 547 multicast: 1
+ // iface: eth0 addr: ff02::1:2 port: 547 multicast: 0 --- only on Linux systems
+ // iface: eth1 addr: fe80::3a60:77ff:fed5:abcd port: 547 multicast: 1 - fails
+
+ if (total_calls > (opened_by_eth0 + 1)) {
+ auto interval = now - last_call_time;
+ auto interval_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval
+ ).count();
+
+ EXPECT_GE(interval_ms, WAIT_TIME);
+ }
+
+ last_call_time = now;
+
+ total_calls++;
+
+ // Fail to open a socket on eth1, success for eth0
+ if (!is_eth0) {
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ }
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilter6TestStub> filter(
+ new isc::dhcp::test::PktFilter6TestStub()
+ );
+
+ filter->setOpenSocketCallback(open_callback);
+
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // Open an unavailable port
+ ASSERT_NO_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT));
+
+ // Wait for a finish sockets binding (with a safe margin).
+ doWait(RETRIES * WAIT_TIME * 2);
+
+ // For eth0 interface perform only 3 (on Linux Systems or 2 otherwise) init open,
+ // for eth1 interface perform 1 init open and a few retries.
+ EXPECT_EQ((RETRIES + 1) + opened_by_eth0, total_calls);
+}
+
+// Test that only one reopen timer is active simultaneously. If a new opening
+// starts, then the previous should be interrupted.
+TEST_F(CfgIfaceTest, retryDoubleOpenServiceSockets6) {
+ CfgIface cfg6;
+
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth0"));
+
+ // Initial timer has a high frequency.
+ cfg6.setServiceSocketsMaxRetries(10000);
+ cfg6.setServiceSocketsRetryWaitTime(1);
+
+ // Set the callback that interrupt the previous execution.
+ uint16_t first_port_calls = 0;
+ uint16_t second_port_calls = 0;
+ auto open_callback = [&first_port_calls, &second_port_calls](uint16_t port) {
+ // First timer must be interrupted.
+ if (second_port_calls > 0) {
+ EXPECT_TRUE(port == 2);
+ }
+
+ if (port == 1) {
+ first_port_calls++;
+ } else {
+ second_port_calls++;
+ }
+
+ // Fail to open and retry.
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilter6TestStub> filter(
+ new isc::dhcp::test::PktFilter6TestStub()
+ );
+
+ filter->setOpenSocketCallback(open_callback);
+
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // First opening.
+ ASSERT_NO_THROW(cfg6.openSockets(AF_INET6, 1));
+
+ // Wait a short time.
+ doWait(10);
+
+ // Reconfigure the interface parameters.
+ cfg6.setServiceSocketsMaxRetries(1);
+ cfg6.setServiceSocketsRetryWaitTime(10);
+
+ // Second opening.
+ ASSERT_NO_THROW(cfg6.openSockets(AF_INET6, 2));
+
+ doWait(50);
+
+ // The first timer should perform some calls.
+ EXPECT_GT(first_port_calls, 0);
+ // The secondary timer should make 2 calls: initial and 1 retry.
+ EXPECT_EQ(second_port_calls, 2);
+}
+
+// This test verifies that it is possible to specify the socket
+// type to be used by the DHCPv4 server.
+// This test is enabled on LINUX and BSD only, because the
+// direct traffic is only supported on those systems.
+#if defined OS_LINUX || defined OS_BSD
+TEST(CfgIfaceNoStubTest, useSocketType) {
+ CfgIface cfg;
+ // Select datagram sockets.
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, "udp"));
+ EXPECT_EQ("udp", cfg.socketTypeToText());
+ ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+ // For datagram sockets, the direct traffic is not supported.
+ ASSERT_TRUE(!IfaceMgr::instance().isDirectResponseSupported());
+
+ // Check unparse
+ std::string expected = "{\n"
+ " \"interfaces\": [ ],\n"
+ " \"dhcp-socket-type\": \"udp\",\n"
+ " \"re-detect\": false }";
+ runToElementTest<CfgIface>(expected, cfg);
+
+ // Select raw sockets.
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, "raw"));
+ EXPECT_EQ("raw", cfg.socketTypeToText());
+ ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+ // For raw sockets, the direct traffic is supported.
+ ASSERT_TRUE(IfaceMgr::instance().isDirectResponseSupported());
+
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_UDP));
+ EXPECT_EQ("udp", cfg.socketTypeToText());
+ ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+ ASSERT_TRUE(!IfaceMgr::instance().isDirectResponseSupported());
+
+ ASSERT_NO_THROW(cfg.useSocketType(AF_INET, CfgIface::SOCKET_RAW));
+ EXPECT_EQ("raw", cfg.socketTypeToText());
+ ASSERT_NO_THROW(cfg.openSockets(AF_INET, 10067, true));
+ ASSERT_TRUE(IfaceMgr::instance().isDirectResponseSupported());
+
+ // Test invalid values.
+ EXPECT_THROW(cfg.useSocketType(AF_INET, "default"),
+ InvalidSocketType);
+ EXPECT_THROW(cfg.useSocketType(AF_INET6, "udp"),
+ InvalidSocketType);
+}
+#endif
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc b/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc
new file mode 100644
index 0000000..b02c791
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc
@@ -0,0 +1,84 @@
+// Copyright (C) 2014-2015,2017 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 <dhcpsrv/cfg_mac_source.h>
+#include <dhcp/hwaddr.h>
+#include <exceptions/exceptions.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+#include <string>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+// Checks whether CfgMACSource::MACSourceFromText is working correctly.
+// Technically, this is a Pkt, not Pkt6 test, but since there is no separate
+// unit-tests for Pkt and it is abstract, so it would be tricky to test it
+// directly. Hence test is being run in Pkt6.
+TEST(CfgMACSourceTest, MACSourceFromText) {
+ EXPECT_THROW(CfgMACSource::MACSourceFromText("unknown"), BadValue);
+
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_ANY, CfgMACSource::MACSourceFromText("any"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, CfgMACSource::MACSourceFromText("raw"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, CfgMACSource::MACSourceFromText("duid"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL,
+ CfgMACSource::MACSourceFromText("ipv6-link-local"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION,
+ CfgMACSource::MACSourceFromText("client-link-addr-option"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION,
+ CfgMACSource::MACSourceFromText("rfc6939"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID,
+ CfgMACSource::MACSourceFromText("remote-id"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID,
+ CfgMACSource::MACSourceFromText("rfc4649"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID,
+ CfgMACSource::MACSourceFromText("subscriber-id"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID,
+ CfgMACSource::MACSourceFromText("rfc4580"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS,
+ CfgMACSource::MACSourceFromText("docsis-cmts"));
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM,
+ CfgMACSource::MACSourceFromText("docsis-modem"));
+}
+
+// Checks whether the opposite operation is working correctly.
+TEST(CfgMACSourceTest, unparse) {
+ CfgMACSource cfg;
+ // any was added by the constructor
+ cfg.add(HWAddr::HWADDR_SOURCE_RAW);
+ cfg.add(HWAddr::HWADDR_SOURCE_DUID);
+ cfg.add(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+ cfg.add(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION);
+ cfg.add(HWAddr::HWADDR_SOURCE_REMOTE_ID);
+ cfg.add(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID);
+ cfg.add(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS);
+ cfg.add(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM);
+
+ // Unparse
+ std::string expected = "["
+ "\"any\","
+ "\"raw\","
+ "\"duid\","
+ "\"ipv6-link-local\","
+ "\"client-link-addr-option\","
+ "\"remote-id\","
+ "\"subscriber-id\","
+ "\"docsis-cmts\","
+ "\"docsis-modem\""
+ "]";
+ runToElementTest<CfgMACSource>(expected, cfg);
+
+ // Add an unknown type
+ cfg.add(0x12345678);
+ ASSERT_THROW(cfg.toElement(), ToElementError);
+}
+
+};
diff --git a/src/lib/dhcpsrv/tests/cfg_multi_threading_unittest.cc b/src/lib/dhcpsrv/tests/cfg_multi_threading_unittest.cc
new file mode 100644
index 0000000..d86ad7d
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_multi_threading_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2020-2023 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 <cc/data.h>
+#include <dhcpsrv/cfg_multi_threading.h>
+#include <util/multi_threading_mgr.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for @c MultiThreadingConfigParser
+class CfgMultiThreadingTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ CfgMultiThreadingTest() = default;
+
+ /// @brief Destructor
+ virtual ~CfgMultiThreadingTest() = default;
+
+protected:
+
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c MultiThreadingMgr.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ ///
+ /// Clears the configuration in the @c MultiThreadingMgr.
+ virtual void TearDown();
+};
+
+void
+CfgMultiThreadingTest::SetUp() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+}
+
+void
+CfgMultiThreadingTest::TearDown() {
+ MultiThreadingMgr::instance().apply(false, 0 , 0);
+}
+
+/// @brief Verifies that extracting multi threading settings works
+TEST_F(CfgMultiThreadingTest, extract) {
+ bool enabled = false;
+ uint32_t thread_count = 0;
+ uint32_t queue_size = 0;
+ std::string content_json =
+ "{"
+ " \"enable-multi-threading\": true,\n"
+ " \"thread-pool-size\": 4,\n"
+ " \"packet-queue-size\": 64\n"
+ "}";
+ ConstElementPtr param;
+ ASSERT_NO_THROW(param = Element::fromJSON(content_json))
+ << "invalid context_json, test is broken";
+ ASSERT_NO_THROW(CfgMultiThreading::extract(param, enabled, thread_count,
+ queue_size));
+ EXPECT_EQ(enabled, true);
+ EXPECT_EQ(thread_count, 4);
+ EXPECT_EQ(queue_size, 64);
+
+ content_json = "{}";
+ ASSERT_NO_THROW(param = Element::fromJSON(content_json))
+ << "invalid context_json, test is broken";
+ //check empty config
+ ASSERT_NO_THROW(CfgMultiThreading::extract(param, enabled, thread_count,
+ queue_size));
+ EXPECT_EQ(enabled, true);
+ EXPECT_EQ(thread_count, 0);
+ EXPECT_EQ(queue_size, 0);
+
+ enabled = true;
+ thread_count = 4;
+ queue_size = 64;
+ // check empty data
+ ASSERT_NO_THROW(CfgMultiThreading::extract(ConstElementPtr(), enabled,
+ thread_count, queue_size));
+ EXPECT_EQ(enabled, true);
+ EXPECT_EQ(thread_count, 0);
+ EXPECT_EQ(queue_size, 0);
+}
+
+/// @brief Verifies that applying multi threading settings works
+TEST_F(CfgMultiThreadingTest, apply) {
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 0);
+ std::string content_json =
+ "{"
+ " \"enable-multi-threading\": true,\n"
+ " \"thread-pool-size\": 4,\n"
+ " \"packet-queue-size\": 64\n"
+ "}";
+ ConstElementPtr param;
+ ASSERT_NO_THROW(param = Element::fromJSON(content_json))
+ << "invalid context_json, test is broken";
+ CfgMultiThreading::apply(param);
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 4);
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 64);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 64);
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
new file mode 100644
index 0000000..e798173
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
@@ -0,0 +1,400 @@
+// Copyright (C) 2014-2021 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/cfg_option_def.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test checks that two option definition configurations can be
+// compared for equality.
+TEST(CfgOptionDefTest, equal) {
+ CfgOptionDef cfg1;
+ CfgOptionDef cfg2;
+
+ // Default objects must be equal.
+ ASSERT_TRUE(cfg1 == cfg2);
+ ASSERT_FALSE(cfg1 != cfg2);
+
+ // Let's add the same option but to two different option spaces.
+ cfg1.add(OptionDefinitionPtr(new OptionDefinition("option-foo", 5, "isc",
+ "uint16")));
+ cfg2.add(OptionDefinitionPtr(new OptionDefinition("option-foo", 5, "dns",
+ "uint16")));
+ // Configurations must be unequal.
+ ASSERT_FALSE(cfg1 == cfg2);
+ ASSERT_TRUE(cfg1 != cfg2);
+
+ // Now, let's add them again but to original option spaces. Both objects
+ // should now contain the same options under two option spaces. The
+ // order should not matter so configurations should be equal.
+ cfg1.add(OptionDefinitionPtr(new OptionDefinition("option-foo", 5, "dns",
+ "uint16")));
+ cfg2.add(OptionDefinitionPtr(new OptionDefinition("option-foo", 5, "isc",
+ "uint16")));
+
+ EXPECT_TRUE(cfg1 == cfg2);
+ EXPECT_FALSE(cfg1 != cfg2);
+
+}
+
+// This test verifies that multiple option definitions can be added
+// under different option spaces and then removed.
+TEST(CfgOptionDefTest, getAllThenDelete) {
+ CfgOptionDef cfg;
+
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "isc", "uint16"));
+ // We're setting id of 123 for all option definitions in this
+ // code range.
+ def->setId(123);
+
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg.add(def));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "abcde", "uint16"));
+ // We're setting id of 234 for all option definitions in this
+ // code range.
+ def->setId(234);
+
+ ASSERT_NO_THROW(cfg.add(def));
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs1 = cfg.getAll("isc");
+ ASSERT_TRUE(option_defs1);
+ ASSERT_EQ(10, option_defs1->size());
+
+ // Iterate over all option definitions and check that they have
+ // valid codes. Also, their order should be the same as they
+ // were added (codes 100-109).
+ uint16_t code = 100;
+ for (OptionDefContainer::const_iterator it = option_defs1->begin();
+ it != option_defs1->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs2 = cfg.getAll("abcde");
+ ASSERT_TRUE(option_defs2);
+ ASSERT_EQ(10, option_defs2->size());
+
+ // Check that the option codes are valid.
+ code = 105;
+ for (OptionDefContainer::const_iterator it = option_defs2->begin();
+ it != option_defs2->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Let's make one more check that the empty set is returned when
+ // invalid option space is used.
+ OptionDefContainerPtr option_defs3 = cfg.getAll("non-existing");
+ ASSERT_TRUE(option_defs3);
+ EXPECT_TRUE(option_defs3->empty());
+
+ // Check that we can delete option definitions by id.
+ uint64_t num_deleted = 0;
+ ASSERT_NO_THROW(num_deleted = cfg.del(123));
+ EXPECT_EQ(10, num_deleted);
+
+ option_defs1 = cfg.getAll("isc");
+ ASSERT_TRUE(option_defs1);
+ ASSERT_EQ(0, option_defs1->size());
+
+ option_defs2 = cfg.getAll("abcde");
+ ASSERT_TRUE(option_defs2);
+ ASSERT_EQ(10, option_defs2->size());
+
+ // Second attempt to delete the same option definitions should
+ // result in 0 deletions.
+ ASSERT_NO_THROW(num_deleted = cfg.del(123));
+ EXPECT_EQ(0, num_deleted);
+
+ // Delete all other option definitions.
+ ASSERT_NO_THROW(num_deleted = cfg.del(234));
+ EXPECT_EQ(10, num_deleted);
+
+ option_defs2 = cfg.getAll("abcde");
+ ASSERT_TRUE(option_defs2);
+ ASSERT_EQ(0, option_defs2->size());
+}
+
+// This test verifies that single option definition is correctly
+// returned with get function.
+TEST(CfgOptionDefTest, get) {
+ CfgOptionDef cfg;
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "isc", "uint16"));
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg.add(def));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "abcde", "uint16"));
+ ASSERT_NO_THROW(cfg.add(def));
+ }
+
+ // Try to get option definitions one by one using all codes
+ // that we expect to be there.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionDefinitionPtr def = cfg.get("isc", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+ EXPECT_EQ(code, def->getCode());
+
+ // Try to get the same option definition using an option name as
+ // a key.
+ def = cfg.get("isc", option_name.str());
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that the option codes are valid.
+ for (uint16_t code = 105; code < 115; ++code) {
+ OptionDefinitionPtr def = cfg.get("abcde", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-other-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+ EXPECT_EQ(code, def->getCode());
+
+ // Try to get the same option definition using an option name as
+ // a key.
+ def = cfg.get("abcde", option_name.str());
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that an option definition can be added to the standard
+ // (dhcp4 and dhcp6) option spaces when the option code is not
+ // reserved by the standard option.
+ OptionDefinitionPtr def6(new OptionDefinition("option-foo", 1000,
+ DHCP6_OPTION_SPACE,
+ "uint16"));
+ EXPECT_NO_THROW(cfg.add(def6));
+
+ OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222,
+ DHCP4_OPTION_SPACE,
+ "uint16"));
+ EXPECT_NO_THROW(cfg.add(def4));
+
+ // Try to query the option definition from an non-existing
+ // option space and expect NULL pointer.
+ OptionDefinitionPtr def = cfg.get("non-existing", 56);
+ EXPECT_FALSE(def);
+
+ // Try to get the non-existing option definition from an
+ // existing option space.
+ EXPECT_FALSE(cfg.get("isc", 56));
+}
+
+// This test verifies that it is not allowed to override a definition of the
+// standard option which has its definition defined in libdhcp++, but it is
+// allowed to create a definition for the standard option which doesn't have
+// its definition in libdhcp++.
+TEST(CfgOptionDefTest, overrideStdOptionDef) {
+ CfgOptionDef cfg;
+ OptionDefinitionPtr def;
+ // There is a definition for routers option in libdhcp++, so an attempt
+ // to add (override) another definition for this option should fail.
+ def.reset(new OptionDefinition("routers", DHO_ROUTERS,
+ DHCP4_OPTION_SPACE, "uint32"));
+ EXPECT_THROW(cfg.add(def), isc::BadValue);
+
+ // Check code duplicate (same code, different name).
+ def.reset(new OptionDefinition("routers-bis", DHO_ROUTERS,
+ DHCP4_OPTION_SPACE, "uint32"));
+ EXPECT_THROW(cfg.add(def), isc::BadValue);
+
+ // Check name duplicate (different code, same name).
+ def.reset(new OptionDefinition("routers", 170, DHCP4_OPTION_SPACE,
+ "uint32"));
+ EXPECT_THROW(cfg.add(def), isc::BadValue);
+
+ /// There is no definition for unassigned option 170.
+ def.reset(new OptionDefinition("unassigned-option-170", 170,
+ DHCP4_OPTION_SPACE, "string"));
+ EXPECT_NO_THROW(cfg.add(def));
+
+ // It is not allowed to override the definition of the option which
+ // has its definition in the libdhcp++.
+ def.reset(new OptionDefinition("sntp-servers", D6O_SNTP_SERVERS,
+ DHCP6_OPTION_SPACE, "ipv4-address"));
+ EXPECT_THROW(cfg.add(def), isc::BadValue);
+ // There is no definition for option 163 in libdhcp++ yet, so it should
+ // be possible provide a custom definition.
+ def.reset(new OptionDefinition("geolocation", 163, DHCP6_OPTION_SPACE,
+ "uint32"));
+ EXPECT_NO_THROW(cfg.add(def));
+}
+
+// This test verifies that the function that adds new option definition
+// throws exceptions when arguments are invalid.
+TEST(CfgOptionDefTest, addNegative) {
+ CfgOptionDef cfg;
+
+ OptionDefinitionPtr def(new OptionDefinition("option-foo", 1000, "isc",
+ "uint16"));
+
+ // Try NULL option definition.
+ ASSERT_THROW(cfg.add(OptionDefinitionPtr()),
+ isc::dhcp::MalformedOptionDefinition);
+ // Try adding option definition twice and make sure that it
+ // fails on the second attempt.
+ ASSERT_NO_THROW(cfg.add(def));
+ EXPECT_THROW(cfg.add(def), DuplicateOptionDefinition);
+}
+
+// This test verifies that the function that unparses configuration
+// works as expected.
+TEST(CfgOptionDefTest, unparse) {
+ CfgOptionDef cfg;
+
+ // Add some options.
+ cfg.add(OptionDefinitionPtr(new
+ OptionDefinition("option-foo", 5, "isc", "uint16")));
+ cfg.add(OptionDefinitionPtr(new
+ OptionDefinition("option-bar", 5, "dns", "uint16", true)));
+ cfg.add(OptionDefinitionPtr(new
+ OptionDefinition("option-baz", 6, "isc", "uint16", "dns")));
+ OptionDefinitionPtr rec(new OptionDefinition("option-rec", 6, "dns", "record"));
+ std::string json = "{ \"comment\": \"foo\", \"bar\": 1 }";
+ rec->setContext(data::Element::fromJSON(json));
+ rec->addRecordField("uint16");
+ rec->addRecordField("uint16");
+ cfg.add(rec);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"name\": \"option-bar\",\n"
+ " \"code\": 5,\n"
+ " \"type\": \"uint16\",\n"
+ " \"array\": true,\n"
+ " \"record-types\": \"\",\n"
+ " \"encapsulate\": \"\",\n"
+ " \"space\": \"dns\"\n"
+ "},{\n"
+ " \"name\": \"option-rec\",\n"
+ " \"code\": 6,\n"
+ " \"type\": \"record\",\n"
+ " \"array\": false,\n"
+ " \"record-types\": \"uint16, uint16\",\n"
+ " \"encapsulate\": \"\",\n"
+ " \"space\": \"dns\",\n"
+ " \"user-context\": { \"comment\": \"foo\", \"bar\": 1 }\n"
+ "},{\n"
+ " \"name\": \"option-foo\",\n"
+ " \"code\": 5,\n"
+ " \"type\": \"uint16\",\n"
+ " \"array\": false,\n"
+ " \"record-types\": \"\",\n"
+ " \"encapsulate\": \"\",\n"
+ " \"space\": \"isc\"\n"
+ "},{\n"
+ " \"name\": \"option-baz\",\n"
+ " \"code\": 6,\n"
+ " \"type\": \"uint16\",\n"
+ " \"array\": false,\n"
+ " \"record-types\": \"\",\n"
+ " \"encapsulate\": \"dns\",\n"
+ " \"space\": \"isc\"\n"
+ "}]\n";
+ isc::test::runToElementTest<CfgOptionDef>(expected, cfg);
+}
+
+// This test verifies that configured option definitions can be merged
+// correctly.
+TEST(CfgOptionDefTest, merge) {
+ CfgOptionDef to; // Configuration we are merging to.
+
+ // Add some options to the "to" config.
+ to.add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "uint16"))));
+ to.add((OptionDefinitionPtr(new OptionDefinition("two", 2, "isc", "uint16"))));
+ to.add((OptionDefinitionPtr(new OptionDefinition("three", 3, "fluff", "uint16"))));
+ to.add((OptionDefinitionPtr(new OptionDefinition("four", 4, "fluff", "uint16"))));
+
+ // Clone the "to" config and use that for merging.
+ CfgOptionDef to_clone;
+ to.copyTo(to_clone);
+
+ // Ensure they are equal before we do anything.
+ ASSERT_TRUE(to.equals(to_clone));
+
+ // Merge from an empty config.
+ CfgOptionDef empty_from;
+ ASSERT_NO_THROW(to_clone.merge(empty_from));
+
+ // Should have the same content as before.
+ ASSERT_TRUE(to.equals(to_clone));
+
+ // Construct a non-empty "from" config.
+ // Options "two" and "three" will be updated definitions and "five" will be new.
+ CfgOptionDef from;
+ from.add((OptionDefinitionPtr(new OptionDefinition("two", 22, "isc", "uint16"))));
+ from.add((OptionDefinitionPtr(new OptionDefinition("three", 3, "fluff", "string"))));
+ from.add((OptionDefinitionPtr(new OptionDefinition("five", 5, "fluff", "uint16"))));
+
+ // Now let's clone "from" config and use that manually construct the expected config.
+ CfgOptionDef expected;
+ from.copyTo(expected);
+ expected.add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "uint16"))));
+ expected.add((OptionDefinitionPtr(new OptionDefinition("four", 4, "fluff", "uint16"))));
+
+ // Do the merge.
+ ASSERT_NO_THROW(to_clone.merge(from));
+
+ // Verify we have the expected content.
+ ASSERT_TRUE(expected.equals(to_clone));
+}
+
+}
diff --git a/src/lib/dhcpsrv/tests/cfg_option_unittest.cc b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
new file mode 100644
index 0000000..ab3c1de
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_option_unittest.cc
@@ -0,0 +1,1309 @@
+// Copyright (C) 2014-2023 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 <cc/data.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcpsrv/cfg_option.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+#include <iterator>
+#include <limits>
+#include <list>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that the OptionDescriptor factory function creates a
+// valid instance.
+TEST(OptionDescriptorTest, create) {
+ OptionPtr option = Option::create(Option::V4, 234);
+ ElementPtr context = Element::createMap();
+ context->set("name", Element::create("value"));
+ auto desc = OptionDescriptor::create(option, true, true, "value", context);
+
+ ASSERT_TRUE(desc);
+ EXPECT_EQ(option, desc->option_);
+ EXPECT_TRUE(desc->persistent_);
+ EXPECT_TRUE(desc->cancelled_);
+ EXPECT_EQ("value", desc->formatted_value_);
+ EXPECT_EQ(context, desc->getContext());
+}
+
+// This test verifies that the OptionDescriptor factory function variant
+// taking persistent flag as an argument creates valid instance.
+TEST(OptionDescriptorTest, createPersistent) {
+ auto desc = OptionDescriptor::create(true, true);
+ ASSERT_TRUE(desc);
+
+ EXPECT_FALSE(desc->option_);
+ EXPECT_TRUE(desc->persistent_);
+ EXPECT_TRUE(desc->cancelled_);
+ EXPECT_TRUE(desc->formatted_value_.empty());
+ EXPECT_FALSE(desc->getContext());
+}
+
+// This test verifies that the OptionDescriptor factory function variant
+// copying a descriptor provided as an argument creates valid instance.
+TEST(OptionDescriptorTest, createCopy) {
+ OptionPtr option = Option::create(Option::V4, 234);
+ ElementPtr context = Element::createMap();
+ context->set("name", Element::create("value"));
+ auto desc = OptionDescriptor::create(option, true, true, "value", context);
+
+ auto desc_copy = OptionDescriptor::create(*desc);
+ ASSERT_TRUE(desc_copy);
+
+ ASSERT_TRUE(desc_copy);
+ EXPECT_EQ(option, desc_copy->option_);
+ EXPECT_TRUE(desc_copy->persistent_);
+ EXPECT_TRUE(desc_copy->cancelled_);
+ EXPECT_EQ("value", desc_copy->formatted_value_);
+ EXPECT_EQ(context, desc_copy->getContext());
+}
+
+// This test verifies that the OptionDescriptor assignment operator
+// does the shallow copy.
+TEST(OptionDescriptorTest, assign) {
+ // Create a persistent option descriptor.
+ auto desc = OptionDescriptor::create(true, true);
+ ASSERT_TRUE(desc);
+
+ // Create another option descriptor.
+ OptionPtr option = Option::create(Option::V4, 234);
+ ElementPtr context = Element::createMap();
+ context->set("name", Element::create("value"));
+ auto desc1 = OptionDescriptor::create(option, false, false, "value",
+ context);
+ ASSERT_TRUE(desc1);
+
+ // Assign the option descriptor.
+ desc = desc1;
+
+ // Check it.
+ ASSERT_TRUE(desc);
+ EXPECT_EQ(option, desc->option_);
+ EXPECT_FALSE(desc->persistent_);
+ EXPECT_FALSE(desc->cancelled_);
+ EXPECT_EQ("value", desc->formatted_value_);
+ EXPECT_EQ(context, desc->getContext());
+}
+
+/// This class fixture for testing @c CfgOption class, holding option
+/// configuration.
+class CfgOptionTest : public ::testing::Test {
+public:
+
+ /// @brief Generates encapsulated options and adds them to CfgOption
+ ///
+ /// This method generates the following options:
+ /// - 1000-1019 options: uint16 with value 1234, encapsulate "foo"
+ /// - 1-19 options: uint8 with value 1, encapsulate "foo-subs"
+ /// - 1-9 options: uint8 with value 3
+ /// - 1020-1039 options: uint16 with value 2345, encapsulate "bar"
+ /// - 100-119 options: uint8 with value 2, encapsulate "bar-subs"
+ /// - 501-509 options: uint8 with value 4
+ void generateEncapsulatedOptions(CfgOption& cfg) {
+ // Create top-level options encapsulating "foo" option space.
+ for (uint16_t code = 1000; code < 1020; ++code) {
+ OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+ code, 1234));
+ option->setEncapsulatedSpace("foo");
+
+ // In order to easier identify the options by id, let's use the option
+ // code as the id.
+ ASSERT_NO_THROW(cfg.add(option, false, false, DHCP6_OPTION_SPACE,
+ static_cast<uint64_t>(code)));
+ }
+
+ // Create top level options encapsulating "bar" option space.
+ for (uint16_t code = 1020; code < 1040; ++code) {
+ OptionUint16Ptr option = OptionUint16Ptr(new OptionUint16(Option::V6,
+ code, 2345));
+ option->setEncapsulatedSpace("bar");
+ ASSERT_NO_THROW(cfg.add(option, false, false, DHCP6_OPTION_SPACE,
+ static_cast<uint64_t>(code)));
+ }
+
+ // Create sub-options belonging to "foo" option space and encapsulating
+ // foo-subs option space.
+ for (uint16_t code = 1; code < 20; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
+ 0x01));
+ option->setEncapsulatedSpace("foo-subs");
+ ASSERT_NO_THROW(cfg.add(option, false, false, "foo",
+ static_cast<uint64_t>(code)));
+ }
+
+ // Create sub-options belonging to "bar" option space and encapsulating
+ // bar-subs option space.
+ for (uint16_t code = 100; code < 119; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
+ code, 0x02));
+ option->setEncapsulatedSpace("bar-subs");
+ ASSERT_NO_THROW(cfg.add(option, false, false, "bar",
+ static_cast<uint64_t>(code)));
+ }
+
+ // Create sub-options belonging to "foo-subs" option space.
+ for (uint16_t code = 1; code < 10; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6, code,
+ 0x03));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "foo-subs",
+ static_cast<uint64_t>(code)));
+ }
+
+ // Create sub-options belonging to "bar-subs" option space.
+ for (uint16_t code = 501; code < 510; ++code) {
+ OptionUint8Ptr option = OptionUint8Ptr(new OptionUint8(Option::V6,
+ code, 0x04));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "bar-subs",
+ static_cast<uint64_t>(code)));
+ }
+ }
+};
+
+// This test verifies the empty predicate.
+TEST_F(CfgOptionTest, empty) {
+ CfgOption cfg1;
+ CfgOption cfg2;
+
+ // Initially the option configurations should be empty.
+ ASSERT_TRUE(cfg1.empty());
+ ASSERT_TRUE(cfg2.empty());
+
+ // Add an option to each configuration
+ OptionPtr option(new Option(Option::V6, 1));
+ ASSERT_NO_THROW(cfg1.add(option, false, false, DHCP6_OPTION_SPACE));
+ ASSERT_NO_THROW(cfg2.add(option, true, true, "isc"));
+
+ // The first option configuration has an option
+ ASSERT_FALSE(cfg1.empty());
+
+ // The second option configuration has a vendor option
+ ASSERT_FALSE(cfg2.empty());
+}
+
+// This test verifies that the option configurations can be compared.
+TEST_F(CfgOptionTest, equals) {
+ CfgOption cfg1;
+ CfgOption cfg2;
+
+ // Initially the configurations should be equal.
+ ASSERT_TRUE(cfg1 == cfg2);
+ ASSERT_FALSE(cfg1 != cfg2);
+
+ // Add 9 options to two different option spaces. Each option have different
+ // option code and content.
+ for (uint16_t code = 1; code < 10; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, code)));
+ ASSERT_NO_THROW(cfg1.add(option, false, false, "isc"));
+ ASSERT_NO_THROW(cfg1.add(option, true, true, "vendor-123"));
+ }
+
+ // Configurations should now be different.
+ ASSERT_FALSE(cfg1 == cfg2);
+ ASSERT_TRUE(cfg1 != cfg2);
+
+ // Add 8 options (excluding the option with code 1) to the same option
+ // spaces.
+ for (uint16_t code = 2; code < 10; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, code)));
+ ASSERT_NO_THROW(cfg2.add(option, false, false, "isc"));
+ ASSERT_NO_THROW(cfg2.add(option, true, true, "vendor-123"));
+ }
+
+ // Configurations should still be unequal.
+ ASSERT_FALSE(cfg1 == cfg2);
+ ASSERT_TRUE(cfg1 != cfg2);
+
+ // Add missing option to the option space isc.
+ ASSERT_NO_THROW(cfg2.add(OptionPtr(new Option(Option::V6, 1,
+ OptionBuffer(10, 0x01))),
+ false, false, "isc"));
+ // Configurations should still be unequal because option with code 1
+ // is missing in the option space vendor-123.
+ ASSERT_FALSE(cfg1 == cfg2);
+ ASSERT_TRUE(cfg1 != cfg2);
+
+ // Add missing option.
+ ASSERT_NO_THROW(cfg2.add(OptionPtr(new Option(Option::V6, 1,
+ OptionBuffer(10, 0x01))),
+ true, true, "vendor-123"));
+ // Configurations should now be equal.
+ ASSERT_TRUE(cfg1 == cfg2);
+ ASSERT_FALSE(cfg1 != cfg2);
+}
+
+// This test verifies that multiple options can be added to the configuration
+// and that they can be retrieved using the option space name.
+TEST_F(CfgOptionTest, add) {
+ CfgOption cfg;
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, DHCP6_OPTION_SPACE));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "isc"));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try another function variant.
+ options = cfg.getAllCombined("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = cfg.getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test verifies that options can be replaced with updated content.
+TEST_F(CfgOptionTest, replace) {
+ CfgOption cfg;
+
+ // Let's add some options to the config to the config.
+ OptionStringPtr option(new OptionString(Option::V6, 1, "one"));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "isc"));
+
+ option.reset(new OptionString(Option::V6, 2, "two"));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "isc"));
+
+ option.reset(new OptionString(Option::V6, 3, "three"));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "isc"));
+
+ // Now let's make sure we can find them and they are as expected.
+ OptionDescriptor desc = cfg.get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("one", opstr->getValue());
+
+ desc = cfg.get("isc", 2);
+ ASSERT_TRUE(desc.option_);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("two", opstr->getValue());
+
+ desc = cfg.get("isc", 3);
+ ASSERT_TRUE(desc.option_);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("three", opstr->getValue());
+
+ // Now let's replace one and three.
+ desc.option_.reset(new OptionString(Option::V6, 1, "new one"));
+ ASSERT_NO_THROW(cfg.replace(desc, "isc"));
+
+ desc.option_.reset(new OptionString(Option::V6, 3, "new three"));
+ ASSERT_NO_THROW(cfg.replace(desc, "isc"));
+
+ // Now let's make sure we can find them again and they are as expected.
+ desc = cfg.get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("new one", opstr->getValue());
+
+ desc = cfg.get("isc", 2);
+ ASSERT_TRUE(desc.option_);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("two", opstr->getValue());
+
+ desc = cfg.get("isc", 3);
+ ASSERT_TRUE(desc.option_);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("new three", opstr->getValue());
+}
+
+// This test verifies that one configuration can be merged into another.
+TEST_F(CfgOptionTest, mergeTo) {
+ CfgOption cfg_src;
+ CfgOption cfg_dst;
+
+ // Create collection of options in option space dhcp6, with option codes
+ // from the range of 100 to 109 and holding one byte of data equal to 0xFF.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+ ASSERT_NO_THROW(cfg_src.add(option, false, false, DHCP6_OPTION_SPACE));
+ }
+
+ // Create collection of options in vendor space 123, with option codes
+ // from the range of 100 to 109 and holding one byte of data equal to 0xFF.
+ for (uint16_t code = 100; code < 110; code += 2) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+ ASSERT_NO_THROW(cfg_src.add(option, false, false, "vendor-123"));
+ }
+
+ // Create destination configuration (configuration that we merge the
+ // other configuration to).
+
+ // Create collection of options having even option codes in the range of
+ // 100 to 108.
+ for (uint16_t code = 100; code < 110; code += 2) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
+ ASSERT_NO_THROW(cfg_dst.add(option, false, false, DHCP6_OPTION_SPACE));
+ }
+
+ // Create collection of options having odd option codes in the range of
+ // 101 to 109.
+ for (uint16_t code = 101; code < 110; code += 2) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
+ ASSERT_NO_THROW(cfg_dst.add(option, false, false, "vendor-123"));
+ }
+
+ // Merge source configuration to the destination configuration. The options
+ // in the destination should be preserved. The options from the source
+ // configuration should be added.
+ ASSERT_NO_THROW(cfg_src.mergeTo(cfg_dst));
+
+ // Validate the options in the dhcp6 option space in the destination.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionDescriptor desc = cfg_dst.get(DHCP6_OPTION_SPACE, code);
+ ASSERT_TRUE(desc.option_);
+ ASSERT_EQ(1, desc.option_->getData().size());
+ // The options with even option codes should hold one byte of data
+ // equal to 0x1. These are the ones that we have initially added to
+ // the destination configuration. The other options should hold the
+ // values of 0xFF which indicates that they have been merged from the
+ // source configuration.
+ if ((code % 2) == 0) {
+ EXPECT_EQ(0x01, desc.option_->getData()[0]);
+ } else {
+ EXPECT_EQ(0xFF, desc.option_->getData()[0]);
+ }
+ }
+
+ // Validate the options in the vendor space.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionDescriptor desc = cfg_dst.get(123, code);
+ ASSERT_TRUE(desc.option_);
+ ASSERT_EQ(1, desc.option_->getData().size());
+ // This time, the options with even option codes should hold a byte
+ // of data equal to 0xFF. The other options should hold the byte of
+ // data equal to 0x01.
+ if ((code % 2) == 0) {
+ EXPECT_EQ(0xFF, desc.option_->getData()[0]);
+ } else {
+ EXPECT_EQ(0x01, desc.option_->getData()[0]);
+ }
+ }
+}
+
+// This test verifies that the options configuration can be copied between
+// objects.
+TEST_F(CfgOptionTest, copy) {
+ CfgOption cfg_src;
+ // Add 10 options to the custom option space in the source configuration.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0x01)));
+ ASSERT_NO_THROW(cfg_src.add(option, false, false, "foo"));
+ }
+
+ CfgOption cfg_dst;
+ // Add 20 options to the custom option space in destination configuration.
+ for (uint16_t code = 100; code < 120; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+ ASSERT_NO_THROW(cfg_dst.add(option, false, false, "isc"));
+ }
+
+ // Copy entire configuration to the destination. This should override any
+ // existing data.
+ ASSERT_NO_THROW(cfg_src.copyTo(cfg_dst));
+
+ // Validate options in the destination configuration.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionDescriptor desc = cfg_dst.get("foo", code);
+ ASSERT_TRUE(desc.option_);
+ ASSERT_EQ(1, desc.option_->getData().size());
+ EXPECT_EQ(0x01, desc.option_->getData()[0]);
+ }
+
+ // Any existing options should be removed.
+ OptionContainerPtr container = cfg_dst.getAll("isc");
+ ASSERT_TRUE(container);
+ EXPECT_TRUE(container->empty());
+
+ // The option space "foo" should contain exactly 10 options.
+ container = cfg_dst.getAll("foo");
+ ASSERT_TRUE(container);
+ EXPECT_EQ(10, container->size());
+}
+
+// This test verifies that DHCP options from one configuration
+// can be used to update options in another configuration.
+// In other words, options from an "other" configuration
+// can be merged into an existing configuration, with any
+// duplicates in other overwriting those in the existing
+// configuration.
+TEST_F(CfgOptionTest, validMerge) {
+ CfgOption this_cfg;
+ CfgOption other_cfg;
+
+ // We need to create a dictionary of definitions pass into option merge.
+ CfgOptionDefPtr defs(new CfgOptionDef());
+ defs->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "uint8"))));
+ defs->add((OptionDefinitionPtr(new OptionDefinition("two", 2, "isc", "uint8"))));
+ defs->add((OptionDefinitionPtr(new OptionDefinition("four", 4, "isc", "uint8"))));
+ defs->add((OptionDefinitionPtr(new OptionDefinition("three", 3, "fluff", "uint8"))));
+ defs->add((OptionDefinitionPtr(new OptionDefinition("four", 4, "fluff", "uint8"))));
+
+ // Create our existing config, that gets merged into.
+ OptionPtr option(new Option(Option::V4, 1, OptionBuffer(1, 0x01)));
+ ASSERT_NO_THROW(this_cfg.add(option, false, false, "isc"));
+
+ // Add option 3 to "fluff"
+ option.reset(new Option(Option::V4, 3, OptionBuffer(1, 0x03)));
+ ASSERT_NO_THROW(this_cfg.add(option, false, false, "fluff"));
+
+ // Add option 4 to "fluff".
+ option.reset(new Option(Option::V4, 4, OptionBuffer(1, 0x04)));
+ ASSERT_NO_THROW(this_cfg.add(option, false, false, "fluff"));
+
+ // Create our other config that will be merged from.
+ // Add Option 1 to "isc", this should "overwrite" the original.
+ option.reset(new Option(Option::V4, 1, OptionBuffer(1, 0x10)));
+ ASSERT_NO_THROW(other_cfg.add(option, false, false, "isc"));
+
+ // Add option 2 to "isc".
+ option.reset(new Option(Option::V4, 2, OptionBuffer(1, 0x20)));
+ ASSERT_NO_THROW(other_cfg.add(option, false, false, "isc"));
+
+ // Add option 4 to "isc".
+ option.reset(new Option(Option::V4, 4, OptionBuffer(1, 0x40)));
+ ASSERT_NO_THROW(other_cfg.add(option, false, false, "isc"));
+
+ // Merge source configuration to the destination configuration. The options
+ // in the destination should be preserved. The options from the source
+ // configuration should be added.
+ ASSERT_NO_THROW(this_cfg.merge(defs, other_cfg));
+
+ // isc:1 should come from "other" config.
+ OptionDescriptor desc = this_cfg.get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ OptionUint8Ptr opint = boost::dynamic_pointer_cast<OptionUint8>(desc.option_);
+ ASSERT_TRUE(opint);
+ EXPECT_EQ(16, opint->getValue());
+
+ // isc:2 should come from "other" config.
+ desc = this_cfg.get("isc", 2);
+ ASSERT_TRUE(desc.option_);
+ opint = boost::dynamic_pointer_cast<OptionUint8>(desc.option_);
+ ASSERT_TRUE(opint);
+ EXPECT_EQ(32, opint->getValue());
+
+ // isc:4 should come from "other" config.
+ desc = this_cfg.get("isc", 4);
+ ASSERT_TRUE(desc.option_);
+ opint = boost::dynamic_pointer_cast<OptionUint8>(desc.option_);
+ ASSERT_TRUE(opint);
+ EXPECT_EQ(64, opint->getValue());
+
+ // fluff:3 should come from "this" config.
+ desc = this_cfg.get("fluff", 3);
+ ASSERT_TRUE(desc.option_);
+ opint = boost::dynamic_pointer_cast<OptionUint8>(desc.option_);
+ ASSERT_TRUE(opint);
+ EXPECT_EQ(3, opint->getValue());
+
+ // fluff:4 should come from "this" config.
+ desc = this_cfg.get("fluff", 4);
+ ASSERT_TRUE(desc.option_);
+ opint = boost::dynamic_pointer_cast<OptionUint8>(desc.option_);
+ ASSERT_TRUE(opint);
+ EXPECT_EQ(4, opint->getValue());
+}
+
+// This test verifies that attempting to merge options
+// which are by incompatible with "known" option definitions
+// are detected.
+TEST_F(CfgOptionTest, mergeInvalid) {
+ CfgOption this_cfg;
+ CfgOption other_cfg;
+
+ // Create an empty dictionary of defintions pass into option merge.
+ CfgOptionDefPtr defs(new CfgOptionDef());
+
+ // Create our other config that will be merged from.
+ // Add an option without a formatted value.
+ OptionPtr option(new Option(Option::V4, 1, OptionBuffer(1, 0x01)));
+ ASSERT_NO_THROW(other_cfg.add(option, false, false, "isc"));
+
+ // Add an option with a formatted value.
+ option.reset(new Option(Option::V4, 2));
+ OptionDescriptor desc(option, false, false, "one,two,three");
+ ASSERT_NO_THROW(other_cfg.add(desc, "isc"));
+
+ // When we attempt to merge, it should fail, recognizing that
+ // option 2, which has a formatted value, has no definition.
+ ASSERT_THROW_MSG(this_cfg.merge(defs, other_cfg), InvalidOperation,
+ "option: isc.2 has a formatted value: "
+ "'one,two,three' but no option definition");
+
+ // Now let's add an option definition that will force data truncated
+ // error for option 1.
+ defs->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "uint16"))));
+
+ // When we attempt to merge, it should fail because option 1's data
+ // is not valid per its definition.
+ EXPECT_THROW_MSG(this_cfg.merge(defs, other_cfg), InvalidOperation,
+ "could not create option: isc.1"
+ " from data specified, reason:"
+ " OptionInt 1 truncated");
+}
+
+// This test verifies the all of the valid option cases
+// in createDescriptorOption():
+// 1. standard option
+// 2. vendor option
+// 3. user-defined, unformatted option
+// 4. user-defined, formatted option
+// 5. undefined, unformatted option
+TEST_F(CfgOptionTest, createDescriptorOptionValid) {
+ // First we'll create our "known" user definitions
+ CfgOptionDefPtr defs(new CfgOptionDef());
+ defs->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "uint8"))));
+ defs->add((OptionDefinitionPtr(new OptionDefinition("two", 2, "isc", "uint8", true))));
+
+ // We'll try a standard V4 option first.
+ std::string space = DHCP4_OPTION_SPACE;
+ std::string value = "v4.example.com";
+ OptionPtr option(new Option(Option::V6, DHO_HOST_NAME));
+ option->setData(value.begin(), value.end());
+ OptionDescriptorPtr desc(new OptionDescriptor(option, false, false));
+
+ bool updated = false;
+ ASSERT_NO_THROW(updated = CfgOption::createDescriptorOption(defs, space, *desc));
+ ASSERT_TRUE(updated);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc->option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("v4.example.com", opstr->getValue());
+
+ // Next we'll try a standard V6 option.
+ space = DHCP6_OPTION_SPACE;
+ std::vector<uint8_t> fqdn =
+ { 2, 'v', '6', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 };
+ option.reset(new Option(Option::V6, D6O_AFTR_NAME));
+ option->setData(fqdn.begin(), fqdn.end());
+ desc.reset(new OptionDescriptor(option, false, false));
+
+ ASSERT_NO_THROW(updated = CfgOption::createDescriptorOption(defs, space, *desc));
+ ASSERT_TRUE(updated);
+ OptionCustomPtr opcustom = boost::dynamic_pointer_cast<OptionCustom>(desc->option_);
+ ASSERT_TRUE(opcustom);
+ EXPECT_EQ("v6.example.com.", opcustom->readFqdn());
+
+ // Next we'll try a vendor option with a formatted value
+ space = "vendor-4491";
+ value = "192.0.2.1, 192.0.2.2";
+ option.reset(new Option(Option::V4, 2));
+ desc.reset(new OptionDescriptor(option, false, false, value));
+
+ ASSERT_NO_THROW(updated = CfgOption::createDescriptorOption(defs, space, *desc));
+ ASSERT_TRUE(updated);
+ Option4AddrLstPtr opaddrs = boost::dynamic_pointer_cast<Option4AddrLst>(desc->option_);
+ ASSERT_TRUE(opaddrs);
+ Option4AddrLst::AddressContainer exp_addresses = { IOAddress("192.0.2.1"), IOAddress("192.0.2.2") };
+ EXPECT_EQ(exp_addresses, opaddrs->getAddresses());
+
+ // Now, a user defined uint8 option
+ space = "isc";
+ option.reset(new Option(Option::V4, 1, OptionBuffer(1, 0x77)));
+ desc.reset(new OptionDescriptor(option, false, false));
+
+ ASSERT_NO_THROW(updated = CfgOption::createDescriptorOption(defs, space, *desc));
+ ASSERT_TRUE(updated);
+ OptionUint8Ptr opint = boost::dynamic_pointer_cast<OptionUint8>(desc->option_);
+ ASSERT_TRUE(opint);
+ EXPECT_EQ(119, opint->getValue());
+
+ // Now, a user defined array of ints from a formatted value
+ option.reset(new Option(Option::V4, 2));
+ desc.reset(new OptionDescriptor(option, false, false, "1,2,3"));
+
+ ASSERT_NO_THROW(updated = CfgOption::createDescriptorOption(defs, space, *desc));
+ ASSERT_TRUE(updated);
+ OptionUint8ArrayPtr oparray = boost::dynamic_pointer_cast<OptionUint8Array>(desc->option_);
+ ASSERT_TRUE(oparray);
+ std::vector<uint8_t> exp_ints = { 1, 2, 3 };
+ EXPECT_EQ(exp_ints, oparray->getValues());
+
+ // Finally, a generic, undefined option
+ option.reset(new Option(Option::V4, 199, OptionBuffer(1, 0x77)));
+ desc.reset(new OptionDescriptor(option, false, false));
+
+ ASSERT_NO_THROW(updated = CfgOption::createDescriptorOption(defs, space, *desc));
+ ASSERT_FALSE(updated);
+ ASSERT_EQ(1, desc->option_->getData().size());
+ EXPECT_EQ(0x77, desc->option_->getData()[0]);
+}
+
+// This test verifies that encapsulated options are added as sub-options
+// to the top level options on request.
+TEST_F(CfgOptionTest, encapsulate) {
+ CfgOption cfg;
+
+ generateEncapsulatedOptions(cfg);
+
+ EXPECT_FALSE(cfg.isEncapsulated());
+
+ // Append options from "foo" and "bar" space as sub-options and options
+ // from "foo-subs" and "bar-subs" as sub-options of "foo" and "bar"
+ // options.
+ ASSERT_NO_THROW(cfg.encapsulate());
+
+ EXPECT_TRUE(cfg.isEncapsulated());
+
+ // Verify that we have 40 top-level options.
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(40, options->size());
+
+ // Iterate over top level options.
+ for (uint16_t code = 1000; code < 1040; ++code) {
+
+ OptionUint16Ptr option = boost::dynamic_pointer_cast<
+ OptionUint16>(cfg.get(DHCP6_OPTION_SPACE, code).option_);
+ ASSERT_TRUE(option) << "option with code " << code << " not found";
+
+ // First level sub options. There are 19 sub-options for each top
+ // level option.
+ const OptionCollection& first_level = option->getOptions();
+ ASSERT_EQ(19, first_level.size());
+
+ // Iterate over all first level sub-options.
+ std::pair<unsigned int, OptionPtr> first_level_opt;
+ BOOST_FOREACH(first_level_opt, first_level) {
+ // Each option in this test comprises a single one byte field and
+ // should cast to OptionUint8 type.
+ OptionUint8Ptr first_level_uint8 = boost::dynamic_pointer_cast<
+ OptionUint8>(first_level_opt.second);
+ ASSERT_TRUE(first_level_uint8);
+
+ const unsigned int value = static_cast<unsigned int>(first_level_uint8->getValue());
+ // There are two sets of first level sub-options. Those that include
+ // a value of 1 and those that include a value of 2.
+ if (first_level_uint8->getType() < 20) {
+ EXPECT_EQ(1, value);
+ } else {
+ EXPECT_EQ(2, value);
+ }
+
+ // Each first level sub-option should include 9 second level
+ // sub options.
+ const OptionCollection& second_level = first_level_uint8->getOptions();
+ ASSERT_EQ(9, second_level.size());
+
+ // Iterate over sub-options and make sure they include the expected
+ // values.
+ std::pair<unsigned int, OptionPtr> second_level_opt;
+ BOOST_FOREACH(second_level_opt, second_level) {
+ OptionUint8Ptr second_level_uint8 = boost::dynamic_pointer_cast<
+ OptionUint8>(second_level_opt.second);
+ ASSERT_TRUE(second_level_uint8);
+ const unsigned value = static_cast<
+ unsigned>(second_level_uint8->getValue());
+ // Certain sub-options should have a value of 3, other the values
+ // of 4.
+ if (second_level_uint8->getType() < 20) {
+ EXPECT_EQ(3, value);
+ } else {
+ EXPECT_EQ(4, value);
+ }
+ }
+ }
+ }
+}
+
+// This test verifies that an option can be deleted from the configuration.
+TEST_F(CfgOptionTest, deleteOptions) {
+ CfgOption cfg;
+
+ generateEncapsulatedOptions(cfg);
+
+ // Append options from "foo" and "bar" space as sub-options and options
+ // from "foo-subs" and "bar-subs" as sub-options of "foo" and "bar"
+ // options.
+ ASSERT_NO_THROW(cfg.encapsulate());
+
+ // The option with the code 5 should exist in the option space "foo".
+ ASSERT_TRUE(cfg.get("foo", 5).option_);
+
+ // Because we called "encapsulate", this option should have been
+ // propagated to the options encapsulating option space "foo".
+ for (uint16_t code = 1000; code < 1020; ++code) {
+ OptionDescriptor top_level_option(false, false);
+ ASSERT_NO_THROW(top_level_option = cfg.get(DHCP6_OPTION_SPACE, code));
+ // Make sure that the option with code 5 is there.
+ ASSERT_TRUE(top_level_option.option_);
+ ASSERT_TRUE(top_level_option.option_->getOption(5));
+
+ // Make sure that options belonging to space "foo-subs" should contain
+ // options with the code of 5.
+ for (uint16_t code_subs = 1; code_subs != 19; ++code_subs) {
+ OptionPtr sub_option;
+ ASSERT_NO_THROW(sub_option = top_level_option.option_->getOption(code_subs));
+ ASSERT_TRUE(sub_option);
+ ASSERT_TRUE(sub_option->getOption(5));
+ }
+ }
+
+ // Delete option with the code 5 and belonging to option space "foo".
+ uint64_t deleted_num;
+ ASSERT_NO_THROW(deleted_num = cfg.del("foo", 5));
+ EXPECT_EQ(1, deleted_num);
+
+ // The option should now be gone from options config.
+ EXPECT_FALSE(cfg.get("foo", 5).option_);
+ // Option with the code of 5 in other option spaces should remain.
+ EXPECT_TRUE(cfg.get("foo-subs", 5).option_);
+
+ // Iterate over the options encapsulating "foo" option space. Make sure
+ // that the option with code 5 is no longer encapsulated by these options.
+ for (uint16_t code = 1000; code < 1020; ++code) {
+ OptionDescriptor top_level_option(false, false);
+ ASSERT_NO_THROW(top_level_option = cfg.get(DHCP6_OPTION_SPACE, code));
+ ASSERT_TRUE(top_level_option.option_);
+ EXPECT_FALSE(top_level_option.option_->getOption(5));
+ // Other options should remain.
+ EXPECT_TRUE(top_level_option.option_->getOption(1));
+
+ // Iterate over options within foo-subs option space and make sure
+ // that they still contain options with the code of 5.
+ for (uint16_t code_subs = 1; code_subs != 19; ++code_subs) {
+ // There shouldn't be option with the code of 5 in the "foo" space.
+ if (code_subs == 5) {
+ continue;
+ }
+
+ OptionPtr sub_option;
+ ASSERT_NO_THROW(sub_option = top_level_option.option_->getOption(code_subs));
+ // Options belonging to option space "foo-subs" should include option
+ // with the code of 5.
+ ASSERT_TRUE(sub_option);
+ ASSERT_TRUE(sub_option->getOption(5));
+ }
+ }
+}
+
+// This test verifies that an option can be deleted from the configuration
+// by database id.
+TEST_F(CfgOptionTest, deleteOptionsById) {
+ CfgOption cfg;
+
+ generateEncapsulatedOptions(cfg);
+
+ // Append options from "foo" and "bar" space as sub-options and options
+ // from "foo-subs" and "bar-subs" as sub-options of "foo" and "bar"
+ // options.
+ ASSERT_NO_THROW(cfg.encapsulate());
+
+ // Create multiple vendor options for vendor id 123.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "vendor-123",
+ static_cast<uint64_t>(code)));
+ }
+
+ // Delete options with id of 100. It includes both regular options and
+ // the vendor options. There are two options with id of 100.
+ EXPECT_EQ(2, cfg.del(100));
+
+ // Make sure that the option 100 was deleted but another option
+ // in the same option space was not.
+ EXPECT_FALSE(cfg.get("bar", 100).option_);
+ EXPECT_TRUE(cfg.get("bar", 101).option_);
+
+ // Make sure that the deleted option was dereferenced from the
+ // top level options but that does not affect encapsulation of
+ // other options.
+ for (uint16_t option_code = 1020; option_code < 1040; ++option_code) {
+ auto top_level_option = cfg.get(DHCP6_OPTION_SPACE, option_code);
+ ASSERT_TRUE(top_level_option.option_);
+ EXPECT_FALSE(top_level_option.option_->getOption(100));
+
+ // The second level encapsulation should have been preserved.
+ auto second_level_option = top_level_option.option_->getOption(101);
+ ASSERT_TRUE(second_level_option);
+ EXPECT_TRUE(second_level_option->getOption(501));
+ }
+
+ // Vendor option with id of 100 should have been deleted too.
+ EXPECT_FALSE(cfg.get(123, 100).option_);
+ EXPECT_TRUE(cfg.get(123, 101).option_);
+}
+
+// This test verifies that a vendor option can be deleted from the configuration.
+TEST_F(CfgOptionTest, delVendorOption) {
+ CfgOption cfg;
+
+ // Create multiple vendor options for vendor id 123.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "vendor-123"));
+ }
+
+ // Create multiple vendor options for vendor id 234.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(1, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "vendor-234"));
+ }
+
+ // Make sure that the option we're trying to delete is there.
+ ASSERT_TRUE(cfg.get(123, 105).option_);
+ // There should also be an option 105 for vendor id 234.
+ ASSERT_TRUE(cfg.get(234, 105).option_);
+
+ // Delete the option for vendor id 123.
+ uint64_t deleted_num;
+ ASSERT_NO_THROW(deleted_num = cfg.del(123, 105));
+ EXPECT_EQ(1, deleted_num);
+
+ // Make sure the option is gone.
+ EXPECT_FALSE(cfg.get(123, 105).option_);
+ // Make sure that the option with code 105 is not affected for vendor id 234.
+ EXPECT_TRUE(cfg.get(234, 105).option_);
+
+ // Other options, like 107, shouldn't be gone.
+ EXPECT_TRUE(cfg.get(123, 107).option_);
+}
+
+// This test verifies that single option can be retrieved from the configuration
+// using option code and option space.
+TEST_F(CfgOptionTest, get) {
+ CfgOption cfg;
+
+ // Add 10 options to a "dhcp6" option space in the subnet.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, DHCP6_OPTION_SPACE));
+ }
+
+ // Check that we can get each added option descriptor using
+ // individually.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream stream;
+ // First, try the invalid option space name.
+ OptionDescriptor desc = cfg.get("isc", code);
+ // Returned descriptor should contain null option ptr.
+ EXPECT_FALSE(desc.option_);
+ // Now, try the valid option space.
+ desc = cfg.get(DHCP6_OPTION_SPACE, code);
+ // Test that the option code matches the expected code.
+ ASSERT_TRUE(desc.option_);
+ EXPECT_EQ(code, desc.option_->getType());
+ }
+}
+
+// This test verifies that multiple options can be retrieved from the
+// configuration using option code and option space.
+TEST_F(CfgOptionTest, getList) {
+ CfgOption cfg;
+
+ // Add twice 10 options to a "dhcp4" option space in the subnet.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, DHCP4_OPTION_SPACE));
+ OptionPtr option2(new Option(Option::V4, code, OptionBuffer(10, 0xEE)));
+ ASSERT_NO_THROW(cfg.add(option2, false, false, DHCP4_OPTION_SPACE));
+ }
+
+ // Check that we can get each added option descriptors.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream stream;
+ // First, try the invalid option space name.
+ OptionDescriptorList list = cfg.getList("isc", code);
+ // Returned descriptor list should be empty.
+ EXPECT_TRUE(list.empty());
+ // Now, try the valid option space.
+ list = cfg.getList(DHCP4_OPTION_SPACE, code);
+ // Test that the option code matches the expected code.
+ ASSERT_EQ(2, list.size());
+ OptionDescriptor desc = list[0];
+ ASSERT_TRUE(desc.option_);
+ EXPECT_EQ(code, desc.option_->getType());
+ OptionBuffer content = desc.option_->getData();
+ ASSERT_EQ(10, content.size());
+ uint8_t val = content[8];
+ EXPECT_TRUE((val == 0xFF) || (val == 0xEE));
+ desc = list[1];
+ ASSERT_TRUE(desc.option_);
+ EXPECT_EQ(code, desc.option_->getType());
+ content = desc.option_->getData();
+ ASSERT_EQ(10, content.size());
+ if (val == 0xFF) {
+ EXPECT_EQ(0xEE, content[4]);
+ } else {
+ EXPECT_EQ(0xFF, content[4]);
+ }
+ }
+}
+
+// This test verifies that multiple options can be retrieved from the
+// configuration using option code and vendor space.
+TEST_F(CfgOptionTest, getListVendor) {
+ CfgOption cfg;
+ std::string vendor_space("vendor-12345678");
+
+ // Add twice 10 options to a "dhcp4" option space in the subnet.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, vendor_space));
+ OptionPtr option2(new Option(Option::V4, code, OptionBuffer(10, 0xEE)));
+ ASSERT_NO_THROW(cfg.add(option2, false, false, vendor_space));
+ }
+
+ // Check that we can get each added option descriptors.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream stream;
+ // First, try the invalid option space name.
+ OptionDescriptorList list = cfg.getList(11111111, code);
+ // Returned descriptor list should be empty.
+ EXPECT_TRUE(list.empty());
+ // Now, try the valid option space.
+ list = cfg.getList(12345678, code);
+ // Test that the option code matches the expected code.
+ ASSERT_EQ(2, list.size());
+ OptionDescriptor desc = list[0];
+ ASSERT_TRUE(desc.option_);
+ EXPECT_EQ(code, desc.option_->getType());
+ OptionBuffer content = desc.option_->getData();
+ ASSERT_EQ(10, content.size());
+ uint8_t val = content[8];
+ EXPECT_TRUE((val == 0xFF) || (val == 0xEE));
+ desc = list[1];
+ ASSERT_TRUE(desc.option_);
+ EXPECT_EQ(code, desc.option_->getType());
+ content = desc.option_->getData();
+ ASSERT_EQ(10, content.size());
+ if (val == 0xFF) {
+ EXPECT_EQ(0xEE, content[4]);
+ } else {
+ EXPECT_EQ(0xFF, content[4]);
+ }
+ }
+}
+
+// This test verifies that the same options can be added to the configuration
+// under different option space.
+TEST_F(CfgOptionTest, addNonUniqueOptions) {
+ CfgOption cfg;
+
+ // Create a set of options with non-unique codes.
+ for (int i = 0; i < 2; ++i) {
+ // In the inner loop we create options with unique codes (100-109).
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, DHCP6_OPTION_SPACE));
+ }
+ }
+
+ // Sanity check that all options are there.
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(20, options->size());
+
+ // Use container index #1 to get the options by their codes.
+ OptionContainerTypeIndex& idx = options->get<1>();
+ // Look for the codes 100-109.
+ for (uint16_t code = 100; code < 110; ++ code) {
+ // For each code we should get two instances of options->
+ OptionContainerTypeRange range = idx.equal_range(code);
+ // Distance between iterators indicates how many options
+ // have been returned for the particular code.
+ ASSERT_EQ(2, distance(range.first, range.second));
+ // Check that returned options actually have the expected option code.
+ for (OptionContainerTypeIndex::const_iterator option_desc = range.first;
+ option_desc != range.second; ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(code, option_desc->option_->getType());
+ }
+ }
+
+ // Let's try to find some non-exiting option.
+ const uint16_t non_existing_code = 150;
+ OptionContainerTypeRange range = idx.equal_range(non_existing_code);
+ // Empty set is expected.
+ EXPECT_EQ(0, distance(range.first, range.second));
+}
+
+// This test verifies that the option with the persistency flag can be
+// added to the configuration and that options with the persistency flags
+// can be retrieved.
+TEST(Subnet6Test, addPersistentOption) {
+ CfgOption cfg;
+
+ // Add 10 options to the subnet with option codes 100 - 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ // We create 10 options and want some of them to be flagged
+ // persistent and some non-persistent. Persistent options are
+ // those that server sends to clients regardless if they ask
+ // for them or not. We pick 3 out of 10 options and mark them
+ // non-persistent and 7 other options persistent.
+ // Code values: 102, 105 and 108 are divisible by 3
+ // and options with these codes will be flagged non-persistent.
+ // Options with other codes will be flagged persistent.
+ bool persistent = (code % 3) ? true : false;
+ ASSERT_NO_THROW(cfg.add(option, persistent, true, DHCP6_OPTION_SPACE));
+ }
+
+ // Get added options from the subnet.
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
+
+ // options->get<2> returns reference to container index #2. This
+ // index is used to access options by the 'persistent' flag.
+ OptionContainerPersistIndex& idx = options->get<2>();
+
+ // Get all persistent options->
+ OptionContainerPersistRange range_persistent = idx.equal_range(true);
+ // 7 out of 10 options have been flagged persistent.
+ ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
+
+ // Get all non-persistent options->
+ OptionContainerPersistRange range_non_persistent = idx.equal_range(false);
+ // 3 out of 10 options have been flagged not persistent.
+ ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
+}
+
+// This test verifies that the option with the cancellation flag can be
+// added to the configuration and that options with the cancellation flags
+// can be retrieved.
+TEST(Subnet6Test, addCancelledOption) {
+ CfgOption cfg;
+
+ // Add 10 options to the subnet with option codes 100 - 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ // We create 10 options and want some of them to be flagged
+ // cancelled and some non-cancelled. Cancelled options are
+ // those that server must never send to clients.
+ // We pick 3 out of 10 options and mark them
+ // non-cancelled and 7 other options cancelled.
+ // Code values: 102, 105 and 108 are divisible by 3
+ // and options with these codes will be flagged non-cancelled.
+ // Options with other codes will be flagged cancelled.
+ bool cancelled = (code % 3) ? true : false;
+ ASSERT_NO_THROW(cfg.add(option, true, cancelled, DHCP6_OPTION_SPACE));
+ }
+
+ // Get added options from the subnet.
+ OptionContainerPtr options = cfg.getAll(DHCP6_OPTION_SPACE);
+
+ // options->get<2> returns reference to container index #5. This
+ // index is used to access options by the 'cancelled' flag.
+ OptionContainerCancelIndex& idx = options->get<5>();
+
+ // Get all cancelled options->
+ OptionContainerCancelRange range_cancelled = idx.equal_range(true);
+ // 7 out of 10 options have been flagged cancelled.
+ ASSERT_EQ(7, distance(range_cancelled.first, range_cancelled.second));
+
+ // Get all non-cancelled options->
+ OptionContainerCancelRange range_non_cancelled = idx.equal_range(false);
+ // 3 out of 10 options have been flagged not cancelled.
+ ASSERT_EQ(3, distance(range_non_cancelled.first, range_non_cancelled.second));
+}
+
+// This test verifies that the vendor option can be added to the configuration.
+TEST_F(CfgOptionTest, addVendorOptions) {
+ CfgOption cfg;
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, "vendor-12345678"));
+ }
+
+ // Second option space uses corner case value for vendor id = max uint8.
+ uint32_t vendor_id = std::numeric_limits<uint32_t>::max();
+ std::ostringstream option_space;
+ option_space << "vendor-" << vendor_id;
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(cfg.add(option, false, false, option_space.str()));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = cfg.getAll(12345678);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Make sure we can get vendor options by option space.
+ options = cfg.getAllCombined("vendor-12345678");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = cfg.getAll(vendor_id);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = cfg.getAll(1111111);
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ // Try another function variant.
+ options = cfg.getAll("vendor-1111111");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test verifies that option space names for the vendor options are
+// correct.
+TEST_F(CfgOptionTest, getVendorIdsSpaceNames) {
+ CfgOption cfg;
+
+ // Create 10 options, each goes under a different vendor id.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ // Generate space name for a unique vendor id.
+ std::ostringstream s;
+ s << "vendor-" << code;
+ ASSERT_NO_THROW(cfg.add(option, false, false, s.str()));
+ }
+
+ // We should now have 10 different vendor ids.
+ std::list<std::string> space_names = cfg.getVendorIdsSpaceNames();
+ ASSERT_EQ(10, space_names.size());
+
+ // Check that the option space names for those vendor ids are correct.
+ for (std::list<std::string>::iterator name = space_names.begin();
+ name != space_names.end(); ++name) {
+ uint16_t id = static_cast<uint16_t>(std::distance(space_names.begin(),
+ name));
+ std::ostringstream s;
+ s << "vendor-" << (100 + id);
+ EXPECT_EQ(s.str(), *name);
+ }
+}
+
+// This test verifies that the unparse function returns what is expected.
+TEST_F(CfgOptionTest, unparse) {
+ CfgOption cfg;
+
+ // Add some options.
+ OptionPtr opt1(new Option(Option::V6, 100, OptionBuffer(4, 0x12)));
+ cfg.add(opt1, false, true, "dns");
+ OptionPtr opt2(new Option(Option::V6, 101, OptionBuffer(4, 12)));
+ OptionDescriptor desc2(opt2, false, true, "12, 12, 12, 12");
+ std::string ctx = "{ \"comment\": \"foo\", \"bar\": 1 }";
+ desc2.setContext(data::Element::fromJSON(ctx));
+ cfg.add(desc2, "dns");
+ OptionPtr opt3(new Option(Option::V6, D6O_STATUS_CODE, OptionBuffer(2, 0)));
+ cfg.add(opt3, false, false, DHCP6_OPTION_SPACE);
+ OptionPtr opt4(new Option(Option::V6, 100, OptionBuffer(4, 0x21)));
+ cfg.add(opt4, true, true, "vendor-1234");
+ OptionPtr opt5(new Option(Option::V6, 111));
+ cfg.add(opt5, false, true, "vendor-5678");
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"code\": 100,\n"
+ " \"space\": \"dns\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"12121212\",\n"
+ " \"always-send\": false,\n"
+ " \"never-send\": true\n"
+ "},{\n"
+ " \"code\": 101,\n"
+ " \"space\": \"dns\",\n"
+ " \"csv-format\": true,\n"
+ " \"data\": \"12, 12, 12, 12\",\n"
+ " \"always-send\": false,\n"
+ " \"never-send\": true,\n"
+ " \"user-context\": { \"comment\": \"foo\", \"bar\": 1 }\n"
+ "},{\n"
+ " \"code\": 13,\n"
+ " \"name\": \"status-code\",\n"
+ " \"space\": \"dhcp6\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"0000\",\n"
+ " \"always-send\": false,\n"
+ " \"never-send\": false\n"
+ "},{\n"
+ " \"code\": 100,\n"
+ " \"space\": \"vendor-1234\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"21212121\",\n"
+ " \"always-send\": true,\n"
+ " \"never-send\": true\n"
+ "},{\n"
+ " \"code\": 111,\n"
+ " \"space\": \"vendor-5678\",\n"
+ " \"always-send\": false,\n"
+ " \"never-send\": true\n"
+ "}]\n";
+ isc::test::runToElementTest<CfgOption>(expected, cfg);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc b/src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc
new file mode 100644
index 0000000..de2d5bb
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright (C) 2015,2017 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 <dhcp/dhcp6.h>
+#include <dhcpsrv/cfg_rsoo.h>
+#include <testutils/test_to_element.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that the RSOO configuration holds the default
+// RSOO-enabled options.
+TEST(CfgRSOOTest, defaults) {
+ CfgRSOO rsoo;
+ EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+ for (uint16_t code = 0; code < 200; ++code) {
+ if (code != D6O_ERP_LOCAL_DOMAIN_NAME) {
+ EXPECT_FALSE(rsoo.enabled(code))
+ << "expected that the option with code "
+ << code << " is by default RSOO-disabled, but"
+ " it is enabled";
+ }
+ }
+
+ // Now, let's see if we can remove the default options.
+ ASSERT_NO_THROW(rsoo.clear());
+ EXPECT_FALSE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+ // Make sure it can be added again.
+ ASSERT_NO_THROW(rsoo.enable(D6O_ERP_LOCAL_DOMAIN_NAME));
+ EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+}
+
+// This test verifies that it is possible to enable more RSOO options
+// and later remove all of them.
+TEST(CfgRSOOTest, enableAndClear) {
+ CfgRSOO rsoo;
+ EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+ // Enable option 88.
+ ASSERT_FALSE(rsoo.enabled(88));
+ ASSERT_NO_THROW(rsoo.enable(88));
+ EXPECT_TRUE(rsoo.enabled(88));
+
+ // Enable option 89.
+ ASSERT_FALSE(rsoo.enabled(89));
+ ASSERT_NO_THROW(rsoo.enable(89));
+ EXPECT_TRUE(rsoo.enabled(89));
+
+ // Remove them and make sure they have been removed.
+ ASSERT_NO_THROW(rsoo.clear());
+ for (uint16_t code = 0; code < 200; ++code) {
+ EXPECT_FALSE(rsoo.enabled(code))
+ << "expected that the option with code "
+ << code << " is RSOO-disabled after clearing"
+ " the RSOO configuration, but it is not";
+ }
+}
+
+// This test verifies that the same option may be specified
+// multiple times and that the code doesn't fail.
+TEST(CfgRSOOTest, enableTwice) {
+ CfgRSOO rsoo;
+ // By default there should be the default option enabled.
+ // Let's try to enable it again. It should pass.
+ ASSERT_NO_THROW(rsoo.enable(D6O_ERP_LOCAL_DOMAIN_NAME));
+ EXPECT_TRUE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+
+ // Enable option 88.
+ ASSERT_FALSE(rsoo.enabled(88));
+ ASSERT_NO_THROW(rsoo.enable(88));
+ EXPECT_TRUE(rsoo.enabled(88));
+
+ // And enable it again.
+ ASSERT_NO_THROW(rsoo.enabled(88));
+ EXPECT_TRUE(rsoo.enabled(88));
+
+ // Remove all.
+ ASSERT_NO_THROW(rsoo.clear());
+ ASSERT_FALSE(rsoo.enabled(D6O_ERP_LOCAL_DOMAIN_NAME));
+ ASSERT_FALSE(rsoo.enabled(88));
+}
+
+// This test verifies that the unparse function returns what is expected.
+TEST(CfgRSOOTest, unparse) {
+ CfgRSOO rsoo;
+ // option codes are put in strings
+ isc::test::runToElementTest<CfgRSOO>("[ \"65\" ]", rsoo);
+ // isc::test::runToElementTest<CfgRSOO>("[ 65 ]", rsoo);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_shared_networks4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_shared_networks4_unittest.cc
new file mode 100644
index 0000000..f737d92
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_shared_networks4_unittest.cc
@@ -0,0 +1,383 @@
+// Copyright (C) 2017-2023 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 <dhcp/option_string.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <testutils/test_to_element.h>
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace asiolink;
+
+namespace {
+
+/// @brief Attempts to verify an expected network within a collection
+/// of networks
+///
+/// @param networks set of networks in which to look
+/// @param name name of the expected network
+/// @param exp_valid expected valid lifetime of the network
+/// @param exp_subnets list of subnet IDs the network is expected to own
+void checkMergedNetwork(const CfgSharedNetworks4& networks, const std::string& name,
+ const Triplet<uint32_t>& exp_valid,
+ const std::vector<SubnetID>& exp_subnets) {
+ auto network = networks.getByName(name);
+ ASSERT_TRUE(network) << "expected network: " << name << " not found";
+ ASSERT_EQ(exp_valid, network->getValid().get()) << " network valid lifetime wrong";
+ const Subnet4SimpleCollection* subnets = network->getAllSubnets();
+ ASSERT_EQ(exp_subnets.size(), subnets->size()) << " wrong number of subnets";
+ for (auto exp_id : exp_subnets) {
+ ASSERT_TRUE(network->getSubnet(exp_id))
+ << " did not find expected subnet: " << exp_id;
+ }
+}
+
+// This test verifies that shared networks can be added to the configruation
+// and retrieved by name.
+TEST(CfgSharedNetworks4Test, getByName) {
+ SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+ SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+
+ CfgSharedNetworks4 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_NO_THROW(cfg.add(network2));
+
+ SharedNetwork4Ptr returned_network1 = cfg.getByName("frog");
+ ASSERT_TRUE(returned_network1);
+ SharedNetwork4Ptr returned_network2 = cfg.getByName("dog");
+ ASSERT_TRUE(returned_network2);
+
+ // Check that non-existent name does not return bogus data.
+ EXPECT_FALSE(cfg.getByName("ant"));
+}
+
+// This test verifies that it is possible to delete a network.
+TEST(CfgSharedNetworks4Test, deleteByName) {
+ SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+ SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+
+ // Add two networks to the configuration.
+ CfgSharedNetworks4 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_NO_THROW(cfg.add(network2));
+
+ // Try to delete non-existing network. This should throw.
+ ASSERT_THROW(cfg.del("lion"), BadValue);
+
+ // Delete network #1.
+ ASSERT_NO_THROW(cfg.del(network1->getName()));
+ ASSERT_FALSE(cfg.getByName(network1->getName()));
+ ASSERT_TRUE(cfg.getByName(network2->getName()));
+
+ // Delete network #2.
+ ASSERT_NO_THROW(cfg.del(network2->getName()));
+ ASSERT_FALSE(cfg.getByName(network1->getName()));
+ ASSERT_FALSE(cfg.getByName(network2->getName()));
+
+ // Check that attempting to delete the same subnet twice will fail.
+ ASSERT_THROW(cfg.del(network1->getName()), BadValue);
+ ASSERT_THROW(cfg.del(network2->getName()), BadValue);
+}
+
+// Checks that subnets have their shared network pointers updated when
+// the network is deleted. This is used when the shared network is deleted
+// by admin commands.
+TEST(CfgSharedNetworks4Test, deleteNetworkWithSubnets) {
+ CfgSharedNetworks4 cfg;
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+ SubnetID id1(100);
+ SubnetID id2(101);
+ Subnet4Ptr sub1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, id1));
+ Subnet4Ptr sub2(new Subnet4(IOAddress("192.0.3.0"), 24, 1, 2, 3, id2));
+ network->add(sub1);
+ network->add(sub2);
+ cfg.add(network);
+
+ // Make sure the subnets are part of the network.
+ SharedNetwork4Ptr test;
+ sub1->getSharedNetwork(test);
+ EXPECT_TRUE(test);
+ EXPECT_EQ(network->toElement()->str(), test->toElement()->str());
+ sub2->getSharedNetwork(test);
+ EXPECT_TRUE(test);
+ EXPECT_EQ(network->toElement()->str(), test->toElement()->str());
+
+ // Now remove the network. Subnets should be disassociated with the network.
+ cfg.del("frog");
+ sub1->getSharedNetwork(test);
+ EXPECT_FALSE(test);
+ sub2->getSharedNetwork(test);
+ EXPECT_FALSE(test);
+}
+
+// This test verifies that it is possible to delete a shared network by
+// its database identifier.
+TEST(CfgSharedNetworks4Test, deleteNetworksById) {
+ // Create three shared networks.
+ CfgSharedNetworks4 cfg;
+ SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+ SharedNetwork4Ptr network2(new SharedNetwork4("whale"));
+ SharedNetwork4Ptr network3(new SharedNetwork4("fly"));
+
+ // Add one subnet to each shared network.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 1));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.3.0"), 24, 1, 2, 3, 2));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.4.0"), 24, 1, 2, 3, 3));
+
+ network1->add(subnet1);
+ network2->add(subnet2);
+ network3->add(subnet3);
+
+ // Set unique identifier for the second shared network.
+ network2->setId(123);
+
+ // Verify that we have two networks with a default identifier and one
+ // with a unique identifier.
+ EXPECT_EQ(0, network1->getId());
+ EXPECT_EQ(123, network2->getId());
+ EXPECT_EQ(0, network3->getId());
+
+ // Add our networks to the configuration.
+ cfg.add(network1);
+ cfg.add(network2);
+ cfg.add(network3);
+
+ // Delete second network by id.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW(deleted_num = cfg.del(network2->getId()));
+ EXPECT_EQ(1, deleted_num);
+
+ // Make sure that the subnet no longer points to the deleted network.
+ SharedNetwork4Ptr returned_network;
+ subnet2->getSharedNetwork(returned_network);
+ EXPECT_FALSE(returned_network);
+ EXPECT_FALSE(cfg.getByName("whale"));
+
+ // Delete the remaining two shared network using id of 0.
+ ASSERT_NO_THROW(deleted_num = cfg.del(network1->getId()));
+ EXPECT_EQ(2, deleted_num);
+
+ // The subnets should no longer point to the deleted networks and
+ // the shared networks should no longer exist in the configuration.
+ subnet1->getSharedNetwork(returned_network);
+ EXPECT_FALSE(returned_network);
+ EXPECT_FALSE(cfg.getByName("frog"));
+
+ subnet3->getSharedNetwork(returned_network);
+ EXPECT_FALSE(returned_network);
+ EXPECT_FALSE(cfg.getByName("fly"));
+
+ EXPECT_EQ(0, cfg.del(network1->getId()));
+}
+
+// This test verifies that shared networks must have unique names.
+TEST(CfgSharedNetworks4Test, duplicateName) {
+ SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+ SharedNetwork4Ptr network2(new SharedNetwork4("frog"));
+
+ CfgSharedNetworks4 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_THROW(cfg.add(network2), BadValue);
+}
+
+// This test verifies that unparsing shared networks returns valid structure.
+TEST(CfgSharedNetworks4Test, unparse) {
+ SharedNetwork4Ptr network1(new SharedNetwork4("frog"));
+ SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+ SharedNetwork4Ptr network3(new SharedNetwork4("cat"));
+
+ network1->setIface("eth0");
+ network1->addRelayAddress(IOAddress("198.16.1.1"));
+ network1->addRelayAddress(IOAddress("198.16.1.2"));
+ network1->setCalculateTeeTimes(true);
+ network1->setT1Percent(.35);
+ network1->setT2Percent(.655);
+ network1->setDdnsSendUpdates(true);
+ network1->setDdnsOverrideNoUpdate(true);
+ network1->setDdnsOverrideClientUpdate(true);
+ network1->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
+ network1->setDdnsGeneratedPrefix("prefix");
+ network1->setDdnsQualifyingSuffix("example.com.");
+ network1->setHostnameCharSet("[^A-Z]");
+ network1->setHostnameCharReplacement("x");
+ network1->setCacheThreshold(.20);
+ network1->setOfferLft(77);
+
+ network2->setIface("eth1");
+ network2->setT1(Triplet<uint32_t>(100));
+ network2->setT2(Triplet<uint32_t>(200));
+ network2->setValid(Triplet<uint32_t>(200, 300, 400));
+ network2->setDdnsSendUpdates(false);
+ network2->setStoreExtendedInfo(true);
+ network2->setCacheMaxAge(50);
+
+ network3->setIface("eth2");
+ network3->setValid(Triplet<uint32_t>(100));
+
+ CfgSharedNetworks4 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_NO_THROW(cfg.add(network2));
+ ASSERT_NO_THROW(cfg.add(network3));
+
+ std::string expected =
+ "[\n"
+ " {\n"
+ " \"interface\": \"eth2\",\n"
+ " \"name\": \"cat\",\n"
+ " \"option-data\": [ ],\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"subnet4\": [ ],\n"
+ " \"valid-lifetime\": 100,\n"
+ " \"min-valid-lifetime\": 100,\n"
+ " \"max-valid-lifetime\": 100\n"
+ " },\n"
+ " {\n"
+ " \"ddns-send-updates\": false,\n"
+ " \"interface\": \"eth1\",\n"
+ " \"name\": \"dog\",\n"
+ " \"rebind-timer\": 200,\n"
+ " \"option-data\": [ ],\n"
+ " \"renew-timer\": 100,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"subnet4\": [ ],\n"
+ " \"valid-lifetime\": 300,\n"
+ " \"min-valid-lifetime\": 200,\n"
+ " \"max-valid-lifetime\": 400,\n"
+ " \"store-extended-info\": true,\n"
+ " \"cache-max-age\": 50\n"
+ " },\n"
+ " {\n"
+ " \"calculate-tee-times\": true,\n"
+ " \"ddns-generated-prefix\": \"prefix\",\n"
+ " \"ddns-override-no-update\": true,\n"
+ " \"ddns-override-client-update\": true,\n"
+ " \"ddns-qualifying-suffix\": \"example.com.\",\n"
+ " \"ddns-replace-client-name\": \"always\",\n"
+ " \"ddns-send-updates\": true,\n"
+ " \"interface\": \"eth0\",\n"
+ " \"name\": \"frog\",\n"
+ " \"option-data\": [ ],\n"
+ " \"relay\": { \"ip-addresses\": [ \"198.16.1.1\", \"198.16.1.2\" ] },\n"
+ " \"subnet4\": [ ],\n"
+ " \"t1-percent\": .35,\n"
+ " \"t2-percent\": .655,\n"
+ " \"hostname-char-replacement\": \"x\",\n"
+ " \"hostname-char-set\": \"[^A-Z]\",\n"
+ " \"cache-threshold\": .20,\n"
+ " \"offer-lifetime\": 77\n"
+ " }\n"
+ "]\n";
+
+ test::runToElementTest<CfgSharedNetworks4>(expected, cfg);
+}
+
+// This test verifies that shared-network configurations are properly merged.
+TEST(CfgSharedNetworks4Test, mergeNetworks) {
+ // Create custom options dictionary for testing merge. We're keeping it
+ // simple because they are more rigorous tests elsewhere.
+ CfgOptionDefPtr cfg_def(new CfgOptionDef());
+ cfg_def->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "string"))));
+
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
+ 26, 1, 2, 100, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 100, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 1, 2, 100, SubnetID(3)));
+ Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"),
+ 26, 1, 2, 100, SubnetID(4)));
+
+ // Create network1 and add two subnets to it
+ SharedNetwork4Ptr network1(new SharedNetwork4("network1"));
+ network1->setValid(Triplet<uint32_t>(100));
+ ASSERT_NO_THROW(network1->add(subnet1));
+ ASSERT_NO_THROW(network1->add(subnet2));
+
+ // Create network2 with no subnets.
+ SharedNetwork4Ptr network2(new SharedNetwork4("network2"));
+ network2->setValid(Triplet<uint32_t>(200));
+
+ // Create network3 with one subnet.
+ SharedNetwork4Ptr network3(new SharedNetwork4("network3"));
+ network3->setValid(Triplet<uint32_t>(300));
+ ASSERT_NO_THROW(network3->add(subnet3));
+
+ // Create our "existing" configured networks.
+ // Add all three networks to the existing config.
+ CfgSharedNetworks4 cfg_to;
+ ASSERT_NO_THROW(cfg_to.add(network1));
+ ASSERT_NO_THROW(cfg_to.add(network2));
+ ASSERT_NO_THROW(cfg_to.add(network3));
+
+ // Merge in an "empty" config. Should have the original config, still intact.
+ CfgSharedNetworks4 cfg_from;
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, cfg_from));
+
+ ASSERT_EQ(3, cfg_to.getAll()->size());
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(100),
+ std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
+ std::vector<SubnetID>()));
+
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(300),
+ std::vector<SubnetID>{SubnetID(3)}));
+
+ // Create network1b, this is an "update" of network1
+ // We'll double the valid time and add subnet4 to it
+ SharedNetwork4Ptr network1b(new SharedNetwork4("network1"));
+ network1b->setValid(Triplet<uint32_t>(200));
+
+ // Now let's a add generic option 1 to network1b.
+ std::string value("Yay!");
+ OptionPtr option(new Option(Option::V4, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(network1b->getCfgOption()->add(option, false, false, "isc"));
+ ASSERT_NO_THROW(network1b->add(subnet4));
+
+ // Network2 we will not touch.
+
+ // Create network3b, this is an "update" of network3.
+ // We'll double it's valid time, but leave off the subnet.
+ SharedNetwork4Ptr network3b(new SharedNetwork4("network3"));
+ network3b->setValid(Triplet<uint32_t>(600));
+
+ // Create our "existing" configured networks.
+ ASSERT_NO_THROW(cfg_from.add(network1b));
+ ASSERT_NO_THROW(cfg_from.add(network3b));
+
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, cfg_from));
+
+ // Should still have 3 networks.
+
+ // Network1 should have doubled its valid lifetime but still only have
+ // the orignal two subnets. Merge should discard associations on CB
+ // subnets and preserve the associations from existing config.
+ ASSERT_EQ(3, cfg_to.getAll()->size());
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(200),
+ std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
+
+ // Make sure we have option 1 and that it has been replaced with a string option.
+ auto network = cfg_to.getByName("network1");
+ auto desc = network->getCfgOption()->get("isc", 1);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("Yay!", opstr->getValue());
+
+ // No changes to network2.
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
+ std::vector<SubnetID>()));
+
+ // Network1 should have doubled its valid lifetime and still subnet3.
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(600),
+ std::vector<SubnetID>{SubnetID(3)}));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc
new file mode 100644
index 0000000..de33f1a
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_shared_networks6_unittest.cc
@@ -0,0 +1,391 @@
+// Copyright (C) 2017-2023 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 <dhcp/option_string.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <asiolink/io_address.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace asiolink;
+
+namespace {
+
+/// @brief Attempts to verify an expected network within a collection
+/// of networks
+///
+/// @param networks set of networks in which to look
+/// @param name name of the expected network
+/// @param exp_valid expected valid lifetime of the network
+/// @param exp_subnets list of subnet IDs the network is expected to own
+void checkMergedNetwork(const CfgSharedNetworks6& networks, const std::string& name,
+ const Triplet<uint32_t>& exp_valid,
+ const std::vector<SubnetID>& exp_subnets) {
+ auto network = networks.getByName(name);
+ ASSERT_TRUE(network) << "expected network: " << name << " not found";
+ ASSERT_EQ(exp_valid, network->getValid().get()) << " network valid lifetime wrong";
+ const Subnet6SimpleCollection* subnets = network->getAllSubnets();
+ ASSERT_EQ(exp_subnets.size(), subnets->size()) << " wrong number of subnets";
+ for (auto exp_id : exp_subnets) {
+ ASSERT_TRUE(network->getSubnet(exp_id))
+ << " did not find expected subnet: " << exp_id;
+ }
+}
+
+// This test verifies that shared networks can be added to the configruation
+// and retrieved by name.
+TEST(CfgSharedNetworks6Test, getByName) {
+ SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+ SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+
+ CfgSharedNetworks6 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_NO_THROW(cfg.add(network2));
+
+ SharedNetwork6Ptr returned_network1 = cfg.getByName("frog");
+ ASSERT_TRUE(returned_network1);
+ SharedNetwork6Ptr returned_network2 = cfg.getByName("dog");
+ ASSERT_TRUE(returned_network2);
+
+ // Check that non-existent name does not return bogus data.
+ EXPECT_FALSE(cfg.getByName("ant"));
+}
+
+// This test verifies that it is possible to delete a network.
+TEST(CfgSharedNetworks6Test, deleteByName) {
+ SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+ SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+
+ // Add two networks to the configuration.
+ CfgSharedNetworks6 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_NO_THROW(cfg.add(network2));
+
+ // Try to delete non-existing network. This should throw.
+ ASSERT_THROW(cfg.del("lion"), BadValue);
+
+ // Delete network #1.
+ ASSERT_NO_THROW(cfg.del(network1->getName()));
+ ASSERT_FALSE(cfg.getByName(network1->getName()));
+ ASSERT_TRUE(cfg.getByName(network2->getName()));
+
+ // Delete network #2.
+ ASSERT_NO_THROW(cfg.del(network2->getName()));
+ ASSERT_FALSE(cfg.getByName(network1->getName()));
+ ASSERT_FALSE(cfg.getByName(network2->getName()));
+
+ // Check that attempting to delete the same subnet twice will fail.
+ ASSERT_THROW(cfg.del(network1->getName()), BadValue);
+ ASSERT_THROW(cfg.del(network2->getName()), BadValue);
+}
+
+// Checks that subnets have their shared network pointers updated when
+// the network is deleted. This is used when the shared network is deleted
+// by admin commands.
+TEST(CfgSharedNetworks6Test, deleteNetworkWithSubnets) {
+ CfgSharedNetworks6 cfg;
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+ SubnetID id1(100);
+ SubnetID id2(101);
+ Subnet6Ptr sub1(new Subnet6(IOAddress("2001:db8::"), 48, 1, 2, 3, 4, id1));
+ Subnet6Ptr sub2(new Subnet6(IOAddress("fec0::"), 12, 1, 2, 3, 4, id2));
+ network->add(sub1);
+ network->add(sub2);
+ cfg.add(network);
+
+ // Make sure the subnets are part of the network.
+ SharedNetwork6Ptr test;
+ sub1->getSharedNetwork(test);
+ EXPECT_TRUE(test);
+ EXPECT_EQ(network->toElement()->str(), test->toElement()->str());
+ sub2->getSharedNetwork(test);
+ EXPECT_TRUE(test);
+ EXPECT_EQ(network->toElement()->str(), test->toElement()->str());
+
+ // Now remove the network. Subnets should be disassociated with the network.
+ cfg.del("frog");
+ sub1->getSharedNetwork(test);
+ EXPECT_FALSE(test);
+ sub2->getSharedNetwork(test);
+ EXPECT_FALSE(test);
+}
+
+
+// This test verifies that it is possible to delete a shared network by
+// its database identifier.
+TEST(CfgSharedNetworks6Test, deleteNetworksById) {
+ // Create three shared networks.
+ CfgSharedNetworks6 cfg;
+ SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+ SharedNetwork6Ptr network2(new SharedNetwork6("whale"));
+ SharedNetwork6Ptr network3(new SharedNetwork6("fly"));
+
+ // Add one subnet to each shared network.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4, 1));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 64, 1, 2, 3, 4, 2));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 64, 1, 2, 3, 4, 3));
+
+ network1->add(subnet1);
+ network2->add(subnet2);
+ network3->add(subnet3);
+
+ // Set unique identifier for the second shared network.
+ network2->setId(123);
+
+ // Verify that we have two networks with a default identifier and one
+ // with a unique identifier.
+ EXPECT_EQ(0, network1->getId());
+ EXPECT_EQ(123, network2->getId());
+ EXPECT_EQ(0, network3->getId());
+
+ // Add our networks to the configuration.
+ cfg.add(network1);
+ cfg.add(network2);
+ cfg.add(network3);
+
+ // Delete second network by id.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW(deleted_num = cfg.del(network2->getId()));
+ EXPECT_EQ(1, deleted_num);
+
+ // Make sure that the subnet no longer points to the deleted network.
+ SharedNetwork6Ptr returned_network;
+ subnet2->getSharedNetwork(returned_network);
+ EXPECT_FALSE(returned_network);
+ EXPECT_FALSE(cfg.getByName("whale"));
+
+ // Delete the remaining two shared network using id of 0.
+ ASSERT_NO_THROW(deleted_num = cfg.del(network1->getId()));
+ EXPECT_EQ(2, deleted_num);
+
+ // The subnets should no longer point to the deleted networks and
+ // the shared networks should no longer exist in the configuration.
+ subnet1->getSharedNetwork(returned_network);
+ EXPECT_FALSE(returned_network);
+ EXPECT_FALSE(cfg.getByName("frog"));
+
+ subnet3->getSharedNetwork(returned_network);
+ EXPECT_FALSE(returned_network);
+ EXPECT_FALSE(cfg.getByName("fly"));
+
+ EXPECT_EQ(0, cfg.del(network1->getId()));
+}
+
+// This test verifies that shared networks must have unique names.
+TEST(CfgSharedNetworks6Test, duplicateName) {
+ SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+ SharedNetwork6Ptr network2(new SharedNetwork6("frog"));
+
+ CfgSharedNetworks6 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_THROW(cfg.add(network2), BadValue);
+}
+
+// This test verifies that unparsing shared networks returns valid structure.
+TEST(CfgSharedNetworks6Test, unparse) {
+ SharedNetwork6Ptr network1(new SharedNetwork6("frog"));
+ SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+ SharedNetwork6Ptr network3(new SharedNetwork6("cat"));
+
+ network1->setIface("eth0");
+ network1->addRelayAddress(IOAddress("2001:db8:1::1"));
+ network1->addRelayAddress(IOAddress("2001:db8:1::2"));
+ network1->setCalculateTeeTimes(true);
+ network1->setT1Percent(.35);
+ network1->setT2Percent(.655);
+ network1->setDdnsSendUpdates(true);
+ network1->setDdnsOverrideNoUpdate(true);
+ network1->setDdnsOverrideClientUpdate(true);
+ network1->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
+ network1->setDdnsGeneratedPrefix("prefix");
+ network1->setDdnsQualifyingSuffix("example.com.");
+ network1->setHostnameCharSet("[^A-Z]");
+ network1->setHostnameCharReplacement("x");
+ network1->setCacheThreshold(.20);
+
+ network2->setIface("eth1");
+ network2->setT1(Triplet<uint32_t>(100));
+ network2->setT2(Triplet<uint32_t>(200));
+ network2->setPreferred(Triplet<uint32_t>(200));
+ network2->setValid(Triplet<uint32_t>(300));
+ network2->setDdnsSendUpdates(false);
+ network2->setStoreExtendedInfo(true);
+ network2->setCacheMaxAge(80);
+
+ network3->setIface("eth2");
+ network3->setPreferred(Triplet<uint32_t>(100,200,300));
+ network3->setValid(Triplet<uint32_t>(200,300,400));
+
+ CfgSharedNetworks6 cfg;
+ ASSERT_NO_THROW(cfg.add(network1));
+ ASSERT_NO_THROW(cfg.add(network2));
+ ASSERT_NO_THROW(cfg.add(network3));
+
+ std::string expected =
+ "[\n"
+ " {\n"
+ " \"interface\": \"eth2\",\n"
+ " \"name\": \"cat\",\n"
+ " \"option-data\": [ ],\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"subnet6\": [ ],\n"
+ " \"preferred-lifetime\": 200,\n"
+ " \"min-preferred-lifetime\": 100,\n"
+ " \"max-preferred-lifetime\": 300,\n"
+ " \"valid-lifetime\": 300,\n"
+ " \"min-valid-lifetime\": 200,\n"
+ " \"max-valid-lifetime\": 400\n"
+ " },\n"
+ " {\n"
+ " \"ddns-send-updates\": false,\n"
+ " \"interface\": \"eth1\",\n"
+ " \"name\": \"dog\",\n"
+ " \"option-data\": [ ],\n"
+ " \"rebind-timer\": 200,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"renew-timer\": 100,\n"
+ " \"subnet6\": [ ],\n"
+ " \"preferred-lifetime\": 200,\n"
+ " \"min-preferred-lifetime\": 200,\n"
+ " \"max-preferred-lifetime\": 200,\n"
+ " \"valid-lifetime\": 300,\n"
+ " \"min-valid-lifetime\": 300,\n"
+ " \"max-valid-lifetime\": 300,\n"
+ " \"store-extended-info\": true,\n"
+ " \"cache-max-age\": 80\n"
+ " },\n"
+ " {\n"
+ " \"calculate-tee-times\": true,\n"
+ " \"ddns-generated-prefix\": \"prefix\",\n"
+ " \"ddns-override-no-update\": true,\n"
+ " \"ddns-override-client-update\": true,\n"
+ " \"ddns-qualifying-suffix\": \"example.com.\",\n"
+ " \"ddns-replace-client-name\": \"always\",\n"
+ " \"ddns-send-updates\": true,\n"
+ " \"interface\": \"eth0\",\n"
+ " \"name\": \"frog\",\n"
+ " \"option-data\": [ ],\n"
+ " \"relay\": { \"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ] },\n"
+ " \"subnet6\": [ ],\n"
+ " \"t1-percent\": .35,\n"
+ " \"t2-percent\": .655,\n"
+ " \"hostname-char-replacement\": \"x\",\n"
+ " \"hostname-char-set\": \"[^A-Z]\",\n"
+ " \"cache-threshold\": .20\n"
+ " }\n"
+ "]\n";
+
+ test::runToElementTest<CfgSharedNetworks6>(expected, cfg);
+}
+
+// This test verifies that shared-network configurations are properly merged.
+TEST(CfgSharedNetworks6Test, mergeNetworks) {
+ // Create custom options dictionary for testing merge. We're keeping it
+ // simple because they are more rigorous tests elsewhere.
+ CfgOptionDefPtr cfg_def(new CfgOptionDef());
+ cfg_def->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "string"))));
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:1::"),
+ 64, 60, 80, 100, 200, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:2::"),
+ 64, 60, 80, 100, 200, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:3::"),
+ 64, 60, 80, 100, 200, SubnetID(3)));
+ Subnet6Ptr subnet4(new Subnet6(IOAddress("2001:4::"),
+ 64, 60, 80, 100, 200, SubnetID(4)));
+
+ // Create network1 and add two subnets to it
+ SharedNetwork6Ptr network1(new SharedNetwork6("network1"));
+ network1->setValid(Triplet<uint32_t>(100));
+ ASSERT_NO_THROW(network1->add(subnet1));
+ ASSERT_NO_THROW(network1->add(subnet2));
+
+ // Create network2 with no subnets.
+ SharedNetwork6Ptr network2(new SharedNetwork6("network2"));
+ network2->setValid(Triplet<uint32_t>(200));
+
+ // Create network3 with one subnet.
+ SharedNetwork6Ptr network3(new SharedNetwork6("network3"));
+ network3->setValid(Triplet<uint32_t>(300));
+ ASSERT_NO_THROW(network3->add(subnet3));
+
+ // Create our "existing" configured networks.
+ // Add all three networks to the existing config.
+ CfgSharedNetworks6 cfg_to;
+ ASSERT_NO_THROW(cfg_to.add(network1));
+ ASSERT_NO_THROW(cfg_to.add(network2));
+ ASSERT_NO_THROW(cfg_to.add(network3));
+
+ // Merge in an "empty" config. Should have the original config, still intact.
+ CfgSharedNetworks6 cfg_from;
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, cfg_from));
+
+ ASSERT_EQ(3, cfg_to.getAll()->size());
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(100),
+ std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
+ std::vector<SubnetID>()));
+
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(300),
+ std::vector<SubnetID>{SubnetID(3)}));
+
+ // Create network1b, this is an "update" of network1
+ // We'll double the valid time and add subnet4 to it
+ SharedNetwork6Ptr network1b(new SharedNetwork6("network1"));
+ network1b->setValid(Triplet<uint32_t>(200));
+
+ // Now let's a add generic option 1 to network1b.
+ std::string value("Yay!");
+ OptionPtr option(new Option(Option::V6, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(network1b->getCfgOption()->add(option, false, false, "isc"));
+ ASSERT_NO_THROW(network1b->add(subnet4));
+
+ // Network2 we will not touch.
+
+ // Create network3b, this is an "update" of network3.
+ // We'll double it's valid time, but leave off the subnet.
+ SharedNetwork6Ptr network3b(new SharedNetwork6("network3"));
+ network3b->setValid(Triplet<uint32_t>(600));
+
+ // Create our "existing" configured networks.
+ ASSERT_NO_THROW(cfg_from.add(network1b));
+ ASSERT_NO_THROW(cfg_from.add(network3b));
+
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, cfg_from));
+
+ // Should still have 3 networks.
+
+ // Network1 should have doubled its valid lifetime but still only have
+ // the orignal two subnets. Merge should discard associations on CB
+ // subnets and preserve the associations from existing config.
+ ASSERT_EQ(3, cfg_to.getAll()->size());
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network1", Triplet<uint32_t>(200),
+ std::vector<SubnetID>{SubnetID(1), SubnetID(2)}));
+
+ // Make sure we have option 1 and that it has been replaced with a string option.
+ auto network = cfg_to.getByName("network1");
+ auto desc = network->getCfgOption()->get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("Yay!", opstr->getValue());
+
+ // No changes to network2.
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network2", Triplet<uint32_t>(200),
+ std::vector<SubnetID>()));
+
+ // Network1 should have doubled its valid lifetime and still subnet3.
+ ASSERT_NO_FATAL_FAILURE(checkMergedNetwork(cfg_to, "network3", Triplet<uint32_t>(600),
+ std::vector<SubnetID>{SubnetID(3)}));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
new file mode 100644
index 0000000..73c36a7
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
@@ -0,0 +1,2481 @@
+// Copyright (C) 2014-2023 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 <cc/data.h>
+#include <dhcp/classify.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/log_utils.h>
+#include <testutils/test_to_element.h>
+#include <util/doubles.h>
+
+#include <gtest/gtest.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+using namespace isc::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief An allocator recording calls to @c initAfterConfigure.
+class InitRecordingAllocator : public IterativeAllocator {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param type specifies the type of allocated leases.
+ /// @param subnet weak pointer to the subnet owning the allocator.
+ InitRecordingAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
+ : IterativeAllocator(type, subnet), callcount_(0) {
+ }
+
+ /// @brief Increases the call count of this function.
+ ///
+ /// The call count can be later examined to check whether or not
+ /// the function was called.
+ virtual void initAfterConfigureInternal() {
+ ++callcount_;
+ };
+
+ /// @brief Call count of the @c initAllocatorsAfterConfigure.
+ int callcount_;
+};
+
+/// @brief Verifies that a set of subnets contains a given a subnet
+///
+/// @param cfg_subnets set of subnets in which to look
+/// @param prefix prefix of the target subnet
+/// @param exp_subnet_id expected id of the target subnet
+/// @param exp_valid expected valid lifetime of the subnet
+/// @param exp_network pointer to the subnet's shared-network (if one)
+void checkMergedSubnet(CfgSubnets4& cfg_subnets,
+ const std::string& prefix,
+ const SubnetID exp_subnet_id,
+ int exp_valid,
+ SharedNetwork4Ptr exp_network) {
+ // Look for the network by prefix.
+ auto subnet = cfg_subnets.getByPrefix(prefix);
+ ASSERT_TRUE(subnet) << "subnet: " << prefix << " not found";
+
+ // Make sure we have the one we expect.
+ ASSERT_EQ(exp_subnet_id, subnet->getID()) << "subnet ID is wrong";
+ ASSERT_EQ(exp_valid, subnet->getValid().get()) << "subnet valid time is wrong";
+
+ SharedNetwork4Ptr shared_network;
+ subnet->getSharedNetwork(shared_network);
+ if (exp_network) {
+ ASSERT_TRUE(shared_network)
+ << " expected network: " << exp_network->getName() << " not found";
+ ASSERT_TRUE(shared_network == exp_network) << " networks do no match";
+ } else {
+ ASSERT_FALSE(shared_network) << " unexpected network assignment: "
+ << shared_network->getName();
+ }
+}
+
+// This test verifies that specific subnet can be retrieved by specifying
+// subnet identifier or subnet prefix.
+TEST(CfgSubnets4Test, getSpecificSubnet) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(5)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(8)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(10)));
+
+ // Store the subnets in a vector to make it possible to loop over
+ // all configured subnets.
+ std::vector<Subnet4Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+
+ // Add all subnets to the configuration.
+ for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); ++subnet) {
+ ASSERT_NO_THROW(cfg.add(*subnet)) << "failed to add subnet with id: "
+ << (*subnet)->getID();
+ }
+
+ // Iterate over all subnets and make sure they can be retrieved by
+ // subnet identifier.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet4Ptr subnet_returned = cfg.getBySubnetId((*subnet)->getID());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Repeat the previous test, but this time retrieve subnets by their
+ // prefixes.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet4Ptr subnet_returned = cfg.getByPrefix((*subnet)->toText());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Make sure that null pointers are returned for non-existing subnets.
+ EXPECT_FALSE(cfg.getBySubnetId(SubnetID(123)));
+ EXPECT_FALSE(cfg.getByPrefix("10.20.30.0/29"));
+}
+
+// This test verifies that a single subnet can be removed from the configuration.
+TEST(CfgSubnets4Test, deleteSubnet) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(5)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 1, 2, 3, SubnetID(8)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.4.0"),
+ 26, 1, 2, 3, SubnetID(10)));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // There should be three subnets.
+ ASSERT_EQ(3, cfg.getAll()->size());
+ // We're going to remove the subnet #2. Let's make sure it exists before
+ // we remove it.
+ ASSERT_TRUE(cfg.getByPrefix("192.0.3.0/26"));
+
+ // Remove the subnet and make sure it is gone.
+ ASSERT_NO_THROW(cfg.del(subnet2));
+ ASSERT_EQ(2, cfg.getAll()->size());
+ EXPECT_FALSE(cfg.getByPrefix("192.0.3.0/26"));
+
+ // Remove another subnet by ID.
+ ASSERT_NO_THROW(cfg.del(subnet1->getID()));
+ ASSERT_EQ(1, cfg.getAll()->size());
+ EXPECT_FALSE(cfg.getByPrefix("192.0.2.0/26"));
+}
+
+// This test verifies that replace a subnet works as expected.
+TEST(CfgSubnets4Test, replaceSubnet) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
+ 26, 1, 2, 100, SubnetID(10)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 100, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 1, 2, 100, SubnetID(13)));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // There should be three subnets.
+ ASSERT_EQ(3, cfg.getAll()->size());
+ // We're going to replace the subnet #2. Let's make sure it exists before
+ // we replace it.
+ ASSERT_TRUE(cfg.getByPrefix("192.0.3.0/26"));
+
+ // Replace the subnet and make sure it was updated.
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 10, 20, 1000, SubnetID(2)));
+ Subnet4Ptr replaced = cfg.replace(subnet);
+ ASSERT_TRUE(replaced);
+ EXPECT_TRUE(replaced == subnet2);
+ ASSERT_EQ(3, cfg.getAll()->size());
+ Subnet4Ptr returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet);
+
+ // Restore.
+ replaced = cfg.replace(replaced);
+ ASSERT_TRUE(replaced);
+ EXPECT_TRUE(replaced == subnet);
+ ASSERT_EQ(3, cfg.getAll()->size());
+ returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet2);
+
+ // Prefix conflict returns null.
+ subnet.reset(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 10, 20, 1000, SubnetID(2)));
+ replaced = cfg.replace(subnet);
+ EXPECT_FALSE(replaced);
+ returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet2);
+
+ // Changing prefix works even it is highly not recommended.
+ subnet.reset(new Subnet4(IOAddress("192.0.10.0"),
+ 26, 10, 20, 1000, SubnetID(2)));
+ replaced = cfg.replace(subnet);
+ ASSERT_TRUE(replaced);
+ EXPECT_TRUE(replaced == subnet2);
+ returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet);
+}
+
+// This test verifies that subnets configuration is properly merged.
+TEST(CfgSubnets4Test, mergeSubnets) {
+ // Create custom options dictionary for testing merge. We're keeping it
+ // simple because they are more rigorous tests elsewhere.
+ CfgOptionDefPtr cfg_def(new CfgOptionDef());
+ cfg_def->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "string"))));
+
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
+ 26, 1, 2, 100, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 100, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 1, 2, 100, SubnetID(3)));
+ Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.4.0"),
+ 26, 1, 2, 100, SubnetID(4)));
+
+ // Create the "existing" list of shared networks
+ CfgSharedNetworks4Ptr networks(new CfgSharedNetworks4());
+ SharedNetwork4Ptr shared_network1(new SharedNetwork4("shared-network1"));
+ networks->add(shared_network1);
+ SharedNetwork4Ptr shared_network2(new SharedNetwork4("shared-network2"));
+ networks->add(shared_network2);
+
+ // Empty network pointer.
+ SharedNetwork4Ptr no_network;
+
+ // Add Subnets 1, 2 and 4 to shared networks.
+ ASSERT_NO_THROW(shared_network1->add(subnet1));
+ ASSERT_NO_THROW(shared_network2->add(subnet2));
+ ASSERT_NO_THROW(shared_network2->add(subnet4));
+
+ // Create our "existing" configured subnets.
+ CfgSubnets4 cfg_to;
+ ASSERT_NO_THROW(cfg_to.add(subnet1));
+ ASSERT_NO_THROW(cfg_to.add(subnet2));
+ ASSERT_NO_THROW(cfg_to.add(subnet3));
+ ASSERT_NO_THROW(cfg_to.add(subnet4));
+
+ // Merge in an "empty" config. Should have the original config,
+ // still intact.
+ CfgSubnets4 cfg_from;
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, networks, cfg_from));
+
+ // We should have all four subnets, with no changes.
+ ASSERT_EQ(4, cfg_to.getAll()->size());
+
+ // Should be no changes to the configuration.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.1.0/26",
+ SubnetID(1), 100, shared_network1));
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.2.0/26",
+ SubnetID(2), 100, shared_network2));
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.3.0/26",
+ SubnetID(3), 100, no_network));
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.4.0/26",
+ SubnetID(4), 100, shared_network2));
+
+ // Fill cfg_from configuration with subnets.
+ // subnet 1b updates subnet 1 but leaves it in network 1 with the same ID.
+ Subnet4Ptr subnet1b(new Subnet4(IOAddress("192.0.10.0"),
+ 26, 2, 3, 400, SubnetID(1)));
+ subnet1b->setSharedNetworkName("shared-network1");
+
+ // Add generic option 1 to subnet 1b.
+ std::string value("Yay!");
+ OptionPtr option(new Option(Option::V4, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(subnet1b->getCfgOption()->add(option, false, false, "isc"));
+
+ // subnet 3b updates subnet 3 with different ID and removes it
+ // from network 2
+ Subnet4Ptr subnet3b(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 3, 4, 500, SubnetID(30)));
+
+ // Now Add generic option 1 to subnet 3b.
+ value = "Team!";
+ option.reset(new Option(Option::V4, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(subnet3b->getCfgOption()->add(option, false, false, "isc"));
+
+ // subnet 4b updates subnet 4 and moves it from network2 to network 1
+ Subnet4Ptr subnet4b(new Subnet4(IOAddress("192.0.4.0"),
+ 26, 3, 4, 500, SubnetID(4)));
+ subnet4b->setSharedNetworkName("shared-network1");
+
+ // subnet 5 is new and belongs to network 2
+ // Has two pools both with an option 1
+ Subnet4Ptr subnet5(new Subnet4(IOAddress("192.0.5.0"),
+ 26, 1, 2, 300, SubnetID(5)));
+ subnet5->setSharedNetworkName("shared-network2");
+
+ // Add pool 1
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.5.10"), IOAddress("192.0.5.20")));
+ value = "POOLS";
+ option.reset(new Option(Option::V4, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false, "isc"));
+ subnet5->addPool(pool);
+
+ // Add pool 2
+ pool.reset(new Pool4(IOAddress("192.0.5.30"), IOAddress("192.0.5.40")));
+ value ="RULE!";
+ option.reset(new Option(Option::V4, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false, "isc"));
+ subnet5->addPool(pool);
+
+ // Add subnets to the merge from config.
+ ASSERT_NO_THROW(cfg_from.add(subnet1b));
+ ASSERT_NO_THROW(cfg_from.add(subnet3b));
+ ASSERT_NO_THROW(cfg_from.add(subnet4b));
+ ASSERT_NO_THROW(cfg_from.add(subnet5));
+
+ // Merge again.
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, networks, cfg_from));
+ ASSERT_EQ(5, cfg_to.getAll()->size());
+
+ // The subnet1 should be replaced by subnet1b.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.10.0/26",
+ SubnetID(1), 400, shared_network1));
+
+ // Let's verify that our option is there and populated correctly.
+ auto subnet = cfg_to.getByPrefix("192.0.10.0/26");
+ auto desc = subnet->getCfgOption()->get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("Yay!", opstr->getValue());
+
+ // The subnet2 should not be affected because it was not present.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.2.0/26",
+ SubnetID(2), 100, shared_network2));
+
+ // subnet3 should be replaced by subnet3b and no longer assigned to a network.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.3.0/26",
+ SubnetID(30), 500, no_network));
+ // Let's verify that our option is there and populated correctly.
+ subnet = cfg_to.getByPrefix("192.0.3.0/26");
+ desc = subnet->getCfgOption()->get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("Team!", opstr->getValue());
+
+ // subnet4 should be replaced by subnet4b and moved to network1.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.4.0/26",
+ SubnetID(4), 500, shared_network1));
+
+ // subnet5 should have been added to configuration.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "192.0.5.0/26",
+ SubnetID(5), 300, shared_network2));
+
+ // Let's verify that both pools have the proper options.
+ subnet = cfg_to.getByPrefix("192.0.5.0/26");
+ const PoolPtr merged_pool = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.5.10"));
+ ASSERT_TRUE(merged_pool);
+ desc = merged_pool->getCfgOption()->get("isc", 1);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("POOLS", opstr->getValue());
+
+ const PoolPtr merged_pool2 = subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.5.30"));
+ ASSERT_TRUE(merged_pool2);
+ desc = merged_pool2->getCfgOption()->get("isc", 1);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("RULE!", opstr->getValue());
+}
+
+// This test verifies that it is possible to retrieve a subnet using an
+// IP address.
+TEST(CfgSubnets4Test, selectSubnetByCiaddr) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Make sure that initially the subnets don't exist.
+ SubnetSelector selector;
+ selector.ciaddr_ = IOAddress("192.0.2.0");
+ // Set some unicast local address to simulate a Renew.
+ selector.local_address_ = IOAddress("10.0.0.100");
+ ASSERT_FALSE(cfg.selectSubnet(selector));
+
+ // Add one subnet and make sure it is returned.
+ cfg.add(subnet1);
+ selector.ciaddr_ = IOAddress("192.0.2.63");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+
+ // Add all other subnets.
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Make sure they are returned for the appropriate addresses.
+ selector.ciaddr_ = IOAddress("192.0.2.15");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.85");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.191");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Also, make sure that the NULL pointer is returned if the subnet
+ // cannot be found.
+ selector.ciaddr_ = IOAddress("192.0.2.192");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// This test verifies that it is possible to select a subnet by
+// matching an interface name.
+TEST(CfgSubnets4Test, selectSubnetByIface) {
+ // The IfaceMgrTestConfig object initializes fake interfaces:
+ // eth0, eth1 and lo on the configuration manager. The CfgSubnets4
+ // object uses interface names to select the appropriate subnet.
+ IfaceMgrTestConfig config(true);
+
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+ // No interface defined for subnet1
+ subnet2->setIface("lo");
+ subnet3->setIface("eth1");
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Make sure that initially the subnets don't exist.
+ SubnetSelector selector;
+ // Set an interface to a name that is not defined in the config.
+ // Subnet selection should fail.
+ selector.iface_name_ = "eth0";
+ ASSERT_FALSE(cfg.selectSubnet(selector));
+
+ // Now select an interface name that matches. Selection should succeed
+ // and return subnet3.
+ selector.iface_name_ = "eth1";
+ Subnet4Ptr selected = cfg.selectSubnet(selector);
+ ASSERT_TRUE(selected);
+ EXPECT_EQ(subnet3, selected);
+}
+
+// This test verifies that it is possible to select subnet by interface
+// name specified on the shared network level.
+TEST(CfgSubnets4Test, selectSharedNetworkByIface) {
+ // The IfaceMgrTestConfig object initializes fake interfaces:
+ // eth0, eth1 and lo on the configuration manager. The CfgSubnets4
+ // object uses interface names to select the appropriate subnet.
+ IfaceMgrTestConfig config(true);
+
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("172.16.2.0"), 24, 1, 2, 3,
+ SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3,
+ SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.3.4.0"), 24, 1, 2, 3,
+ SubnetID(3)));
+ subnet2->setIface("lo");
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SharedNetwork4Ptr network(new SharedNetwork4("network_eth1"));
+ network->setIface("eth1");
+ ASSERT_NO_THROW(network->add(subnet1));
+ ASSERT_NO_THROW(network->add(subnet2));
+
+ // Make sure that initially the subnets don't exist.
+ SubnetSelector selector;
+ // Set an interface to a name that is not defined in the config.
+ // Subnet selection should fail.
+ selector.iface_name_ = "eth0";
+ ASSERT_FALSE(cfg.selectSubnet(selector));
+
+ // Now select an interface name that matches. Selection should succeed
+ // and return subnet3.
+ selector.iface_name_ = "eth1";
+ Subnet4Ptr selected = cfg.selectSubnet(selector);
+ ASSERT_TRUE(selected);
+ SharedNetwork4Ptr network_returned;
+ selected->getSharedNetwork(network_returned);
+ ASSERT_TRUE(network_returned);
+ EXPECT_EQ(network, network_returned);
+
+ const Subnet4SimpleCollection* subnets_eth1 =
+ network_returned->getAllSubnets();
+ EXPECT_EQ(2, subnets_eth1->size());
+ ASSERT_TRUE(network_returned->getSubnet(SubnetID(1)));
+ ASSERT_TRUE(network_returned->getSubnet(SubnetID(2)));
+
+ // Make sure that it is still possible to select subnet2 which is
+ // outside of a shared network.
+ selector.iface_name_ = "lo";
+ selected = cfg.selectSubnet(selector);
+ ASSERT_TRUE(selected);
+ EXPECT_EQ(2, selected->getID());
+
+ // Try selecting by eth1 again, but this time set subnet specific
+ // interface name to eth0. Subnet selection should fail.
+ selector.iface_name_ = "eth1";
+ subnet1->setIface("eth0");
+ subnet3->setIface("eth0");
+ selected = cfg.selectSubnet(selector);
+ ASSERT_FALSE(selected);
+
+ // It should be possible to select by eth0, though.
+ selector.iface_name_ = "eth0";
+ selected = cfg.selectSubnet(selector);
+ ASSERT_TRUE(selected);
+ EXPECT_EQ(subnet1, selected);
+}
+
+// This test verifies that when the classification information is specified for
+// subnets, the proper subnets are returned by the subnet configuration.
+TEST(CfgSubnets4Test, selectSubnetByClasses) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Add them to the configuration.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SubnetSelector selector;
+
+ selector.local_address_ = IOAddress("10.0.0.10");
+
+ selector.ciaddr_ = IOAddress("192.0.2.5");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.70");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.130");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ ClientClasses client_classes;
+ client_classes.insert("bar");
+ selector.client_classes_ = client_classes;
+
+ // There are no class restrictions defined, so everything should work
+ // as before
+ selector.ciaddr_ = IOAddress("192.0.2.5");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.70");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.130");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Now let's add client class restrictions.
+ subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+ subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+ subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+ // The same check as above should result in client being served only in
+ // bar class, i.e. subnet2.
+ selector.ciaddr_ = IOAddress("192.0.2.5");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.70");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.130");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Now let's check that client with wrong class is not supported.
+ client_classes.clear();
+ client_classes.insert("some_other_class");
+ selector.client_classes_ = client_classes;
+ selector.ciaddr_ = IOAddress("192.0.2.5");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.70");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.130");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Finally, let's check that client without any classes is not supported.
+ client_classes.clear();
+ selector.ciaddr_ = IOAddress("192.0.2.5");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.70");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.ciaddr_ = IOAddress("192.0.2.130");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// This test verifies that shared network can be selected based on client
+// classification.
+TEST(CfgSubnets4Test, selectSharedNetworkByClasses) {
+ IfaceMgrTestConfig config(true);
+
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Add them to the configuration.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Create first network and add first two subnets to it.
+ SharedNetwork4Ptr network1(new SharedNetwork4("network1"));
+ network1->setIface("eth1");
+ network1->allowClientClass("device-type1");
+ ASSERT_NO_THROW(network1->add(subnet1));
+ ASSERT_NO_THROW(network1->add(subnet2));
+
+ // Create second network and add last subnet there.
+ SharedNetwork4Ptr network2(new SharedNetwork4("network2"));
+ network2->setIface("eth1");
+ network2->allowClientClass("device-type2");
+ ASSERT_NO_THROW(network2->add(subnet3));
+
+ // Use interface name as a selector. This guarantees that subnet
+ // selection will be made based on the classification.
+ SubnetSelector selector;
+ selector.iface_name_ = "eth1";
+
+ // If the client has "device-type2" class, it is expected that the
+ // second network will be used. This network has only one subnet
+ // in it, i.e. subnet3.
+ ClientClasses client_classes;
+ client_classes.insert("device-type2");
+ selector.client_classes_ = client_classes;
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Switch to device-type1 and expect that we assigned a subnet from
+ // another shared network.
+ client_classes.clear();
+ client_classes.insert("device-type1");
+ selector.client_classes_ = client_classes;
+
+ Subnet4Ptr subnet = cfg.selectSubnet(selector);
+ ASSERT_TRUE(subnet);
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ ASSERT_TRUE(network);
+ EXPECT_EQ("network1", network->getName());
+}
+
+// This test verifies the option selection can be used and is only
+// used when present.
+TEST(CfgSubnets4Test, selectSubnetByOptionSelect) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Add them to the configuration.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SubnetSelector selector;
+
+ // Check that without option selection something else is used
+ selector.ciaddr_ = IOAddress("192.0.2.5");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+
+ // The option selection has precedence
+ selector.option_select_ = IOAddress("192.0.2.130");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Over relay-info too
+ selector.giaddr_ = IOAddress("10.0.0.1");
+ subnet2->addRelayAddress(IOAddress("10.0.0.1"));
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+ selector.option_select_ = IOAddress("0.0.0.0");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+
+ // Check that a not matching option selection it shall fail
+ selector.option_select_ = IOAddress("10.0.0.1");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// This test verifies that the relay information can be used to retrieve the
+// subnet.
+TEST(CfgSubnets4Test, selectSubnetByRelayAddress) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Add them to the configuration.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SubnetSelector selector;
+
+ // Check that without relay-info specified, subnets are not selected
+ selector.giaddr_ = IOAddress("10.0.0.1");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.2");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.3");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Now specify relay info
+ subnet1->addRelayAddress(IOAddress("10.0.0.1"));
+ subnet2->addRelayAddress(IOAddress("10.0.0.2"));
+ subnet3->addRelayAddress(IOAddress("10.0.0.3"));
+
+ // And try again. This time relay-info is there and should match.
+ selector.giaddr_ = IOAddress("10.0.0.1");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.2");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.3");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test verifies that the relay information specified on the shared
+// network level can be used to select a subnet.
+TEST(CfgSubnets4Test, selectSharedNetworkByRelayAddressNetworkLevel) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Add them to the configuration.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SharedNetwork4Ptr network(new SharedNetwork4("network"));
+ network->add(subnet2);
+
+ SubnetSelector selector;
+
+ // Now specify relay info. Note that for the second subnet we specify
+ // relay info on the network level.
+ subnet1->addRelayAddress(IOAddress("10.0.0.1"));
+ network->addRelayAddress(IOAddress("10.0.0.2"));
+ subnet3->addRelayAddress(IOAddress("10.0.0.3"));
+
+ // And try again. This time relay-info is there and should match.
+ selector.giaddr_ = IOAddress("10.0.0.1");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.2");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.3");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test verifies that the relay information specified on the subnet
+// level can be used to select a subnet and the fact that a subnet belongs
+// to a shared network doesn't affect the process.
+TEST(CfgSubnets4Test, selectSharedNetworkByRelayAddressSubnetLevel) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Add them to the configuration.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SharedNetwork4Ptr network1(new SharedNetwork4("network1"));
+ network1->add(subnet1);
+
+ SharedNetwork4Ptr network2(new SharedNetwork4("network2"));
+ network2->add(subnet2);
+
+ SubnetSelector selector;
+
+ // Now specify relay info. Note that for the second subnet we specify
+ // relay info on the network level.
+ subnet1->addRelayAddress(IOAddress("10.0.0.1"));
+ subnet2->addRelayAddress(IOAddress("10.0.0.2"));
+ subnet3->addRelayAddress(IOAddress("10.0.0.3"));
+
+ // And try again. This time relay-info is there and should match.
+ selector.giaddr_ = IOAddress("10.0.0.1");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.2");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.giaddr_ = IOAddress("10.0.0.3");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test verifies that the subnet can be selected for the client
+// using a source address if the client hasn't set the ciaddr.
+TEST(CfgSubnets4Test, selectSubnetNoCiaddr) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Make sure that initially the subnets don't exist.
+ SubnetSelector selector;
+ selector.remote_address_ = IOAddress("192.0.2.0");
+ // Set some unicast local address to simulate a Renew.
+ selector.local_address_ = IOAddress("10.0.0.100");
+ ASSERT_FALSE(cfg.selectSubnet(selector));
+
+ // Add one subnet and make sure it is returned.
+ cfg.add(subnet1);
+ selector.remote_address_ = IOAddress("192.0.2.63");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+
+ // Add all other subnets.
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Make sure they are returned for the appropriate addresses.
+ selector.remote_address_ = IOAddress("192.0.2.15");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.remote_address_ = IOAddress("192.0.2.85");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.remote_address_ = IOAddress("192.0.2.191");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Also, make sure that the NULL pointer is returned if the subnet
+ // cannot be found.
+ selector.remote_address_ = IOAddress("192.0.2.192");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// This test verifies that the subnet can be selected using an address
+// set on the local interface.
+TEST(CfgSubnets4Test, selectSubnetInterface) {
+ // The IfaceMgrTestConfig object initializes fake interfaces:
+ // eth0, eth1 and lo on the configuration manager. The CfgSubnets4
+ // object uses addresses assigned to these fake interfaces to
+ // select the appropriate subnet.
+ IfaceMgrTestConfig config(true);
+
+ CfgSubnets4 cfg;
+ SubnetSelector selector;
+
+ // Initially, there are no subnets configured, so none of the IPv4
+ // addresses assigned to eth0 and eth1 can match with any subnet.
+ selector.iface_name_ = "eth0";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.iface_name_ = "eth1";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Configure first subnet which address on eth0 corresponds to.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.1"),
+ 24, 1, 2, 3, SubnetID(1)));
+ cfg.add(subnet1);
+
+ // The address on eth0 should match the existing subnet.
+ selector.iface_name_ = "eth0";
+ Subnet4Ptr subnet1_ret = cfg.selectSubnet(selector);
+ ASSERT_TRUE(subnet1_ret);
+ EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
+ // There should still be no match for eth1.
+ selector.iface_name_ = "eth1";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Configure a second subnet.
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.1"),
+ 24, 1, 2, 3, SubnetID(2)));
+ cfg.add(subnet2);
+
+ // There should be match between eth0 and subnet1 and between eth1 and
+ // subnet 2.
+ selector.iface_name_ = "eth0";
+ subnet1_ret = cfg.selectSubnet(selector);
+ ASSERT_TRUE(subnet1_ret);
+ EXPECT_EQ(subnet1->get().first, subnet1_ret->get().first);
+ selector.iface_name_ = "eth1";
+ Subnet4Ptr subnet2_ret = cfg.selectSubnet(selector);
+ ASSERT_TRUE(subnet2_ret);
+ EXPECT_EQ(subnet2->get().first, subnet2_ret->get().first);
+
+ // This function throws an exception if the name of the interface is wrong.
+ selector.iface_name_ = "bogus-interface";
+ EXPECT_THROW(cfg.selectSubnet(selector), isc::BadValue);
+}
+
+// Checks that detection of duplicated subnet IDs works as expected. It should
+// not be possible to add two IPv4 subnets holding the same ID.
+TEST(CfgSubnets4Test, duplication) {
+ CfgSubnets4 cfg;
+
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 123));
+ Subnet4Ptr subnet4(new Subnet4(IOAddress("192.0.2.1"), 26, 1, 2, 3, 125));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ EXPECT_NO_THROW(cfg.add(subnet2));
+ // Subnet 3 has the same ID as subnet 1. It shouldn't be able to add it.
+ EXPECT_THROW(cfg.add(subnet3), isc::dhcp::DuplicateSubnetID);
+ // Subnet 4 has a similar but different subnet as subnet 1.
+ EXPECT_NO_THROW(cfg.add(subnet4));
+}
+
+// This test checks if the IPv4 subnet can be selected based on the IPv6 address.
+TEST(CfgSubnets4Test, 4o6subnetMatchByAddress) {
+ CfgSubnets4 cfg;
+
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 125));
+
+ subnet2->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 48);
+ subnet3->get4o6().setSubnet4o6(IOAddress("2001:db8:2::"), 48);
+
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SubnetSelector selector;
+ selector.dhcp4o6_ = true;
+ selector.remote_address_ = IOAddress("2001:db8:1::dead:beef");
+
+ EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
+}
+
+// This test checks if the IPv4 subnet can be selected based on the value of
+// interface-id option.
+TEST(CfgSubnets4Test, 4o6subnetMatchByInterfaceId) {
+ CfgSubnets4 cfg;
+
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 125));
+
+ const uint8_t dummyPayload1[] = { 1, 2, 3, 4};
+ const uint8_t dummyPayload2[] = { 1, 2, 3, 5};
+ std::vector<uint8_t> data1(dummyPayload1, dummyPayload1 + sizeof(dummyPayload1));
+ std::vector<uint8_t> data2(dummyPayload2, dummyPayload2 + sizeof(dummyPayload2));
+
+ OptionPtr interfaceId1(new Option(Option::V6, D6O_INTERFACE_ID, data1));
+ OptionPtr interfaceId2(new Option(Option::V6, D6O_INTERFACE_ID, data2));
+
+ subnet2->get4o6().setInterfaceId(interfaceId1);
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SubnetSelector selector;
+ selector.dhcp4o6_ = true;
+ selector.interface_id_ = interfaceId2;
+ // We have mismatched interface-id options (data1 vs data2). Should not match.
+ EXPECT_FALSE(cfg.selectSubnet4o6(selector));
+
+ // This time we have correct interface-id. Should match.
+ selector.interface_id_ = interfaceId1;
+ EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
+}
+
+// This test checks if the IPv4 subnet can be selected based on the value of
+// interface name option.
+TEST(CfgSubnets4Test, 4o6subnetMatchByInterfaceName) {
+ CfgSubnets4 cfg;
+
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 125));
+
+ subnet2->get4o6().setIface4o6("eth7");
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ SubnetSelector selector;
+ selector.dhcp4o6_ = true;
+ selector.iface_name_ = "eth5";
+ // We have mismatched interface names. Should not match.
+ EXPECT_FALSE(cfg.selectSubnet4o6(selector));
+
+ // This time we have correct names. Should match.
+ selector.iface_name_ = "eth7";
+ EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
+}
+
+// This test check if IPv4 subnets can be unparsed in a predictable way,
+TEST(CfgSubnets4Test, unparseSubnet) {
+ CfgSubnets4 cfg;
+
+ // Add some subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 125));
+
+ subnet1->allowClientClass("foo");
+
+ subnet1->setT1Percent(0.45);
+ subnet1->setT2Percent(0.70);
+ subnet1->setCacheThreshold(0.20);
+
+ subnet2->setIface("lo");
+ subnet2->addRelayAddress(IOAddress("10.0.0.1"));
+ subnet2->setValid(Triplet<uint32_t>(100));
+ subnet2->setStoreExtendedInfo(true);
+ subnet2->setCacheMaxAge(80);
+ subnet2->setOfferLft(99);
+
+ subnet3->setIface("eth1");
+ subnet3->requireClientClass("foo");
+ subnet3->requireClientClass("bar");
+ subnet3->setCalculateTeeTimes(true);
+ subnet3->setT1Percent(0.50);
+ subnet3->setT2Percent(0.65);
+ subnet3->setReservationsGlobal(false);
+ subnet3->setReservationsInSubnet(true);
+ subnet3->setReservationsOutOfPool(false);
+ subnet3->setAuthoritative(false);
+ subnet3->setMatchClientId(true);
+ subnet3->setSiaddr(IOAddress("192.0.2.2"));
+ subnet3->setSname("frog");
+ subnet3->setFilename("/dev/null");
+ subnet3->setValid(Triplet<uint32_t>(100, 200, 300));
+ subnet3->setDdnsSendUpdates(true);
+ subnet3->setDdnsOverrideNoUpdate(true);
+ subnet3->setDdnsOverrideClientUpdate(true);
+ subnet3->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
+ subnet3->setDdnsGeneratedPrefix("prefix");
+ subnet3->setDdnsQualifyingSuffix("example.com.");
+ subnet3->setHostnameCharSet("[^A-Z]");
+ subnet3->setHostnameCharReplacement("x");
+
+ data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
+ subnet1->setContext(ctx1);
+ data::ElementPtr ctx2 = data::Element::createMap();
+ subnet2->setContext(ctx2);
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"192.0.2.0/26\",\n"
+ " \"t1-percent\": 0.45,"
+ " \"t2-percent\": 0.7,"
+ " \"cache-threshold\": .20,\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"valid-lifetime\": 3,\n"
+ " \"min-valid-lifetime\": 3,\n"
+ " \"max-valid-lifetime\": 3,\n"
+ " \"client-class\": \"foo\",\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ],\n"
+ " \"user-context\": { \"comment\": \"foo\" }\n"
+ "},{\n"
+ " \"id\": 124,\n"
+ " \"subnet\": \"192.0.2.64/26\",\n"
+ " \"interface\": \"lo\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ \"10.0.0.1\" ] },\n"
+ " \"valid-lifetime\": 100,\n"
+ " \"min-valid-lifetime\": 100,\n"
+ " \"max-valid-lifetime\": 100,\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"user-context\": {},\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ],\n"
+ " \"store-extended-info\": true,\n"
+ " \"cache-max-age\": 80,\n"
+ " \"offer-lifetime\": 99\n"
+ "},{\n"
+ " \"id\": 125,\n"
+ " \"subnet\": \"192.0.2.128/26\",\n"
+ " \"interface\": \"eth1\",\n"
+ " \"match-client-id\": true,\n"
+ " \"next-server\": \"192.0.2.2\",\n"
+ " \"server-hostname\": \"frog\",\n"
+ " \"boot-file-name\": \"/dev/null\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"valid-lifetime\": 200,\n"
+ " \"min-valid-lifetime\": 100,\n"
+ " \"max-valid-lifetime\": 300,\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"authoritative\": false,\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": false,\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ]\n,"
+ " \"require-client-classes\": [ \"foo\", \"bar\" ],\n"
+ " \"calculate-tee-times\": true,\n"
+ " \"t1-percent\": 0.50,\n"
+ " \"t2-percent\": 0.65,\n"
+ " \"ddns-generated-prefix\": \"prefix\",\n"
+ " \"ddns-override-client-update\": true,\n"
+ " \"ddns-override-no-update\": true,\n"
+ " \"ddns-qualifying-suffix\": \"example.com.\",\n"
+ " \"ddns-replace-client-name\": \"always\",\n"
+ " \"ddns-send-updates\": true,\n"
+ " \"hostname-char-replacement\": \"x\",\n"
+ " \"hostname-char-set\": \"[^A-Z]\"\n"
+ "} ]\n";
+
+ runToElementTest<CfgSubnets4>(expected, cfg);
+}
+
+// This test check if IPv4 pools can be unparsed in a predictable way,
+TEST(CfgSubnets4Test, unparsePool) {
+ CfgSubnets4 cfg;
+
+ // Add a subnet with pools
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 123));
+ Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.1"), IOAddress("192.0.2.10")));
+ Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.64"), 26));
+ pool2->allowClientClass("bar");
+
+ std::string json1 = "{ \"comment\": \"foo\", \"version\": 1 }";
+ data::ElementPtr ctx1 = data::Element::fromJSON(json1);
+ pool1->setContext(ctx1);
+ data::ElementPtr ctx2 = data::Element::fromJSON("{ \"foo\": \"bar\" }");
+ pool2->setContext(ctx2);
+ pool2->requireClientClass("foo");
+
+ subnet->addPool(pool1);
+ subnet->addPool(pool2);
+ cfg.add(subnet);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"valid-lifetime\": 3,\n"
+ " \"min-valid-lifetime\": 3,\n"
+ " \"max-valid-lifetime\": 3,\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"option-data\": [],\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"option-data\": [ ],\n"
+ " \"pool\": \"192.0.2.1-192.0.2.10\",\n"
+ " \"user-context\": { \"comment\": \"foo\",\n"
+ " \"version\": 1 }\n"
+ " },{\n"
+ " \"option-data\": [ ],\n"
+ " \"pool\": \"192.0.2.64/26\",\n"
+ " \"user-context\": { \"foo\": \"bar\" },\n"
+ " \"client-class\": \"bar\",\n"
+ " \"require-client-classes\": [ \"foo\" ]\n"
+ " }\n"
+ " ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets4>(expected, cfg);
+}
+
+// This test verifies that it is possible to retrieve a subnet using subnet-id.
+TEST(CfgSubnets4Test, getSubnet) {
+ CfgSubnets4 cfg;
+
+ // Create 3 subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 100));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 200));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 300));
+
+ // Add one subnet and make sure it is returned.
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ EXPECT_EQ(subnet1, cfg.getSubnet(100));
+ EXPECT_EQ(subnet2, cfg.getSubnet(200));
+ EXPECT_EQ(subnet3, cfg.getSubnet(300));
+ EXPECT_EQ(Subnet4Ptr(), cfg.getSubnet(400)); // no such subnet
+}
+
+// This test verifies that hasSubnetWithServerId returns correct value.
+TEST(CfgSubnets4Test, hasSubnetWithServerId) {
+ CfgSubnets4 cfg;
+
+ // Initially, there is no server identifier option present.
+ EXPECT_FALSE(cfg.hasSubnetWithServerId(IOAddress("1.2.3.4")));
+
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ OptionCustomPtr opt_server_id(new OptionCustom(*def, Option::V4));
+ opt_server_id->writeAddress(IOAddress("1.2.3.4"));
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 100));
+ subnet->getCfgOption()->add(opt_server_id, false, false,
+ DHCP4_OPTION_SPACE);
+ cfg.add(subnet);
+
+ EXPECT_TRUE(cfg.hasSubnetWithServerId(IOAddress("1.2.3.4")));
+ EXPECT_FALSE(cfg.hasSubnetWithServerId(IOAddress("2.3.4.5")));
+}
+
+// This test verifies the Subnet4 parser's validation logic for
+// t1-percent and t2-percent parameters.
+TEST(CfgSubnets4Test, teeTimePercentValidation) {
+
+ // Describes a single test scenario.
+ struct Scenario {
+ std::string label; // label used for logging test failures
+ bool calculate_tee_times; // value of calculate-tee-times parameter
+ double t1_percent; // value of t1-percent parameter
+ double t2_percent; // value of t2-percent parameter
+ std::string error_message; // expected error message is parsing should fail
+ };
+
+ // Test Scenarios.
+ std::vector<Scenario> tests = {
+ {"off and valid", false, .5, .95, ""},
+ {"on and valid", true, .5, .95, ""},
+ {"t2_negative", true, .5, -.95,
+ "subnet configuration failed: t2-percent:"
+ " -0.95 is invalid, it must be greater than 0.0 and less than 1.0"
+ },
+ {"t2_too_big", true, .5, 1.95,
+ "subnet configuration failed: t2-percent:"
+ " 1.95 is invalid, it must be greater than 0.0 and less than 1.0"
+ },
+ {"t1_negative", true, -.5, .95,
+ "subnet configuration failed: t1-percent:"
+ " -0.5 is invalid it must be greater than 0.0 and less than 1.0"
+ },
+ {"t1_too_big", true, 1.5, .95,
+ "subnet configuration failed: t1-percent:"
+ " 1.5 is invalid it must be greater than 0.0 and less than 1.0"
+ },
+ {"t1_bigger_than_t2", true, .85, .45,
+ "subnet configuration failed: t1-percent:"
+ " 0.85 is invalid, it must be less than t2-percent: 0.45"
+ }
+ };
+
+ // First we create a set of elements that provides all
+ // required for a Subnet4.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"valid-lifetime\": 300, \n"
+ " \"match-client-id\": false, \n"
+ " \"authoritative\": false, \n"
+ " \"next-server\": \"\", \n"
+ " \"server-hostname\": \"\", \n"
+ " \"boot-file-name\": \"\", \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false, \n"
+ " \"4o6-interface\": \"\", \n"
+ " \"4o6-interface-id\": \"\", \n"
+ " \"4o6-subnet\": \"\" \n"
+ " }";
+
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ // Iterate over the test scenarios, verifying each prescribed
+ // outcome.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE("test: " + (*test).label);
+
+ // Set this scenario's configuration parameters
+ elems->set("calculate-tee-times", data::Element::create((*test).calculate_tee_times));
+ elems->set("t1-percent", data::Element::create((*test).t1_percent));
+ elems->set("t2-percent", data::Element::create((*test).t2_percent));
+
+ Subnet4Ptr subnet;
+ try {
+ // Attempt to parse the configuration.
+ Subnet4ConfigParser parser;
+ subnet = parser.parse(elems);
+ } catch (const std::exception& ex) {
+ if (!(*test).error_message.empty()) {
+ // We expected a failure, did we fail the correct way?
+ EXPECT_EQ((*test).error_message, ex.what());
+ } else {
+ // Should not have failed.
+ ADD_FAILURE() << "Scenario should not have failed: " << ex.what();
+ }
+
+ // Either way we're done with this scenario.
+ continue;
+ }
+
+ // We parsed correctly, make sure the values are right.
+ EXPECT_EQ((*test).calculate_tee_times, subnet->getCalculateTeeTimes());
+ EXPECT_TRUE(util::areDoublesEquivalent((*test).t1_percent, subnet->getT1Percent()));
+ EXPECT_TRUE(util::areDoublesEquivalent((*test).t2_percent, subnet->getT2Percent()));
+ }
+ }
+}
+
+// This test verifies the Subnet4 parser's validation logic for
+// valid-lifetime and indirectly shared lifetime parsing.
+TEST(CfgSubnets4Test, validLifetimeValidation) {
+ // First we create a set of elements that provides all
+ // required for a Subnet4.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"match-client-id\": false, \n"
+ " \"authoritative\": false, \n"
+ " \"next-server\": \"\", \n"
+ " \"server-hostname\": \"\", \n"
+ " \"boot-file-name\": \"\", \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false, \n"
+ " \"4o6-interface\": \"\", \n"
+ " \"4o6-interface-id\": \"\", \n"
+ " \"4o6-subnet\": \"\" \n"
+ " }";
+
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ {
+ SCOPED_TRACE("no valid-lifetime");
+
+ data::ElementPtr copied = data::copy(elems);
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getValid().unspecified());
+ data::ConstElementPtr repr = subnet->toElement();
+ EXPECT_FALSE(repr->get("valid-lifetime"));
+ EXPECT_FALSE(repr->get("min-valid-lifetime"));
+ EXPECT_FALSE(repr->get("max-valid-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("valid-lifetime only");
+
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("valid-lifetime", data::Element::create(100));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getValid().unspecified());
+ EXPECT_EQ(100, subnet->getValid().get());
+ EXPECT_EQ(100, subnet->getValid().getMin());
+ EXPECT_EQ(100, subnet->getValid().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("valid-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-valid-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-valid-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("min-valid-lifetime only");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-valid-lifetime", data::Element::create(100));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getValid().unspecified());
+ EXPECT_EQ(100, subnet->getValid().get());
+ EXPECT_EQ(100, subnet->getValid().getMin());
+ EXPECT_EQ(100, subnet->getValid().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("valid-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-valid-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-valid-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("max-valid-lifetime only");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("max-valid-lifetime", data::Element::create(100));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getValid().unspecified());
+ EXPECT_EQ(100, subnet->getValid().get());
+ EXPECT_EQ(100, subnet->getValid().getMin());
+ EXPECT_EQ(100, subnet->getValid().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("valid-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-valid-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-valid-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("min-valid-lifetime and valid-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-valid-lifetime", data::Element::create(100));
+ // Use a different (and greater) value for the default.
+ copied->set("valid-lifetime", data::Element::create(200));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getValid().unspecified());
+ EXPECT_EQ(200, subnet->getValid().get());
+ EXPECT_EQ(100, subnet->getValid().getMin());
+ EXPECT_EQ(200, subnet->getValid().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("valid-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("200", value->str());
+ data::ConstElementPtr min_value = repr->get("min-valid-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-valid-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("200", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("max-valid-lifetime and valid-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("max-valid-lifetime", data::Element::create(200));
+ // Use a different (and smaller) value for the default.
+ copied->set("valid-lifetime", data::Element::create(100));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getValid().unspecified());
+ EXPECT_EQ(100, subnet->getValid().get());
+ EXPECT_EQ(100, subnet->getValid().getMin());
+ EXPECT_EQ(200, subnet->getValid().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("valid-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-valid-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-valid-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("200", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("min-valid-lifetime and max-valid-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-valid-lifetime", data::Element::create(100));
+ copied->set("max-valid-lifetime", data::Element::create(200));
+ Subnet4ConfigParser parser;
+ // No idea about the value to use for the default so failing.
+ ASSERT_THROW(parser.parse(copied), DhcpConfigError);
+ }
+
+ {
+ SCOPED_TRACE("all 3 (min, max and default) valid-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-valid-lifetime", data::Element::create(100));
+ // Use a different (and greater) value for the default.
+ copied->set("valid-lifetime", data::Element::create(200));
+ // Use a different (and greater than both) value for max.
+ copied->set("max-valid-lifetime", data::Element::create(300));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getValid().unspecified());
+ EXPECT_EQ(200, subnet->getValid().get());
+ EXPECT_EQ(100, subnet->getValid().getMin());
+ EXPECT_EQ(300, subnet->getValid().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("valid-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("200", value->str());
+ data::ConstElementPtr min_value = repr->get("min-valid-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-valid-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("300", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("default value too small");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-valid-lifetime", data::Element::create(200));
+ // 100 < 200 so it will fail.
+ copied->set("valid-lifetime", data::Element::create(100));
+ // Use a different (and greater than both) value for max.
+ copied->set("max-valid-lifetime", data::Element::create(300));
+ Subnet4ConfigParser parser;
+ ASSERT_THROW(parser.parse(copied), DhcpConfigError);
+ }
+
+ {
+ SCOPED_TRACE("default value too large");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-valid-lifetime", data::Element::create(100));
+ // Use a different (and greater) value for the default.
+ copied->set("valid-lifetime", data::Element::create(300));
+ // 300 > 200 so it will fail.
+ copied->set("max-valid-lifetime", data::Element::create(200));
+ Subnet4ConfigParser parser;
+ ASSERT_THROW(parser.parse(copied), DhcpConfigError);
+ }
+
+ {
+ SCOPED_TRACE("equal bounds are no longer ignored");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-valid-lifetime", data::Element::create(100));
+ copied->set("valid-lifetime", data::Element::create(100));
+ copied->set("max-valid-lifetime", data::Element::create(100));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getValid().unspecified());
+ EXPECT_EQ(100, subnet->getValid().get());
+ EXPECT_EQ(100, subnet->getValid().getMin());
+ EXPECT_EQ(100, subnet->getValid().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("valid-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-valid-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-valid-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+}
+
+// This test verifies the Subnet4 parser's validation logic for
+// hostname sanitizer values.
+TEST(CfgSubnets4Test, hostnameSanitizierValidation) {
+
+ // First we create a set of elements that provides all
+ // required for a Subnet4.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"match-client-id\": false, \n"
+ " \"authoritative\": false, \n"
+ " \"next-server\": \"\", \n"
+ " \"server-hostname\": \"\", \n"
+ " \"boot-file-name\": \"\", \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false, \n"
+ " \"4o6-interface\": \"\", \n"
+ " \"4o6-interface-id\": \"\", \n"
+ " \"4o6-subnet\": \"\" \n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ {
+ SCOPED_TRACE("invalid regular expression");
+
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("hostname-char-set", data::Element::create("^[A-"));
+ copied->set("hostname-char-replacement", data::Element::create("x"));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ EXPECT_THROW_MSG(subnet = parser.parse(copied), DhcpConfigError,
+ "subnet configuration failed: hostname-char-set "
+ "'^[A-' is not a valid regular expression");
+
+ }
+ {
+ SCOPED_TRACE("valid regular expression");
+
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("hostname-char-set", data::Element::create("^[A-Z]"));
+ copied->set("hostname-char-replacement", data::Element::create("x"));
+ Subnet4Ptr subnet;
+ Subnet4ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ EXPECT_EQ("^[A-Z]", subnet->getHostnameCharSet().get());
+ EXPECT_EQ("x", subnet->getHostnameCharReplacement().get());
+ }
+}
+
+// This test verifies the Subnet4 parser's validation logic for
+// lease cache parameters.
+TEST(CfgSubnets4Test, cacheParamValidation) {
+
+ // Describes a single test scenario.
+ struct Scenario {
+ std::string label; // label used for logging test failures
+ double threshold; // value of cache-threshold
+ std::string error_message; // expected error message is parsing should fail
+ };
+
+ // Test Scenarios.
+ std::vector<Scenario> tests = {
+ {"valid", .25, ""},
+ {"negative", -.25,
+ "subnet configuration failed: cache-threshold:"
+ " -0.25 is invalid, it must be greater than 0.0 and less than 1.0"
+ },
+ {"too big", 1.05,
+ "subnet configuration failed: cache-threshold:"
+ " 1.05 is invalid, it must be greater than 0.0 and less than 1.0"
+ }
+ };
+
+ // First we create a set of elements that provides all
+ // required for a Subnet4.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"valid-lifetime\": 300, \n"
+ " \"match-client-id\": false, \n"
+ " \"authoritative\": false, \n"
+ " \"next-server\": \"\", \n"
+ " \"server-hostname\": \"\", \n"
+ " \"boot-file-name\": \"\", \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false, \n"
+ " \"4o6-interface\": \"\", \n"
+ " \"4o6-interface-id\": \"\", \n"
+ " \"4o6-subnet\": \"\" \n"
+ " }";
+
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ // Iterate over the test scenarios, verifying each prescribed
+ // outcome.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE("test: " + (*test).label);
+
+ // Set this scenario's configuration parameters
+ elems->set("cache-threshold", data::Element::create((*test).threshold));
+
+ Subnet4Ptr subnet;
+ try {
+ // Attempt to parse the configuration.
+ Subnet4ConfigParser parser;
+ subnet = parser.parse(elems);
+ } catch (const std::exception& ex) {
+ if (!(*test).error_message.empty()) {
+ // We expected a failure, did we fail the correct way?
+ EXPECT_EQ((*test).error_message, ex.what());
+ } else {
+ // Should not have failed.
+ ADD_FAILURE() << "Scenario should not have failed: " << ex.what();
+ }
+
+ // Either way we're done with this scenario.
+ continue;
+ }
+
+ // We parsed correctly, make sure the values are right.
+ EXPECT_TRUE(util::areDoublesEquivalent((*test).threshold, subnet->getCacheThreshold()));
+ }
+ }
+}
+
+// This test verifies that the optional interface check works as expected.
+TEST(CfgSubnets4Test, iface) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"interface\": \"eth1961\"\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ // The interface check can be disabled.
+ Subnet4ConfigParser parser_no_check(false);
+ Subnet4Ptr subnet;
+ EXPECT_NO_THROW(subnet = parser_no_check.parse(elems));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getIface().unspecified());
+ EXPECT_EQ("eth1961", subnet->getIface().get());
+
+ // Retry with the interface check enabled.
+ Subnet4ConfigParser parser;
+ EXPECT_THROW(parser.parse(elems), DhcpConfigError);
+
+ // Configure default test interfaces.
+ IfaceMgrTestConfig config(true);
+
+ EXPECT_NO_THROW(subnet = parser_no_check.parse(elems));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getIface().unspecified());
+ EXPECT_EQ("eth1961", subnet->getIface().get());
+
+ EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getIface().unspecified());
+ EXPECT_EQ("eth1961", subnet->getIface().get());
+}
+
+// This test verifies that update statistics works as expected.
+TEST(CfgSubnets4Test, updateStatistics) {
+ CfgMgr::instance().clear();
+
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4();
+ ObservationPtr observation;
+ SubnetID subnet_id = 100;
+
+ LeaseMgrFactory::create("type=memfile universe=4 persist=false");
+
+ // remove all statistics
+ StatsMgr::instance().removeAll();
+
+ // Create subnet.
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 100));
+
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"), 26));
+ subnet->addPool(pool);
+
+ // Add subnet.
+ cfg->add(subnet);
+
+ observation = StatsMgr::instance().getObservation(
+ "cumulative-assigned-addresses");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ "declined-addresses");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-declined-addresses");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-leases");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_FALSE(observation);
+
+ cfg->updateStatistics();
+
+ observation = StatsMgr::instance().getObservation(
+ "cumulative-assigned-addresses");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ "declined-addresses");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-declined-addresses");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-leases");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(64, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(64, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+}
+
+// This test verifies that remove statistics works as expected.
+TEST(CfgSubnets4Test, removeStatistics) {
+ CfgSubnets4 cfg;
+ ObservationPtr observation;
+ SubnetID subnet_id = 100;
+
+ // remove all statistics and then set them all to 0
+ StatsMgr::instance().removeAll();
+
+ // Create subnet.
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 100));
+
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"), 26));
+ subnet->addPool(pool);
+
+ // Add subnet.
+ cfg.add(subnet);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-addresses"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-addresses")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-addresses"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-addresses")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-addresses"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-addresses")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ // remove all statistics
+ cfg.removeStatistics();
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_FALSE(observation);
+}
+
+// This test verifies that in range host reservation works as expected.
+TEST(CfgSubnets4Test, host) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-address\": \"10.1.2.1\" } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet4ConfigParser parser;
+ Subnet4Ptr subnet;
+ EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(cfg_hosts);
+ HostCollection hosts = cfg_hosts->getAll4(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ ConstHostPtr host = hosts[0];
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText());
+ EXPECT_EQ("10.1.2.1", host->getIPv4Reservation().toText());
+
+ CfgMgr::instance().clear();
+}
+
+// This test verifies that an out of range host reservation is rejected.
+TEST(CfgSubnets4Test, outOfRangeHost) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-address\": \"10.2.2.1\" } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet4ConfigParser parser;
+ std::string msg = "specified reservation '10.2.2.1' is not within ";
+ msg += "the IPv4 subnet '10.1.2.0/24'";
+ EXPECT_THROW_MSG(parser.parse(elems), DhcpConfigError, msg);
+}
+
+// This test verifies that the getLinks tool works as expected.
+TEST(CfgSubnets4Test, getLinks) {
+ CfgSubnets4 cfg;
+
+ // Create 4 subnets.
+ Subnet4Ptr subnet0(new Subnet4(IOAddress("0.0.0.0"),
+ 24, 1, 2, 3, SubnetID(111)));
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ // Add all subnets to the configuration.
+ ASSERT_NO_THROW(cfg.add(subnet0));
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // No 192.0.4.0 subnet.
+ SubnetIDSet links;
+ uint8_t link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("192.0.4.0"), link_len));
+ EXPECT_TRUE(links.empty());
+ EXPECT_EQ(111, link_len);
+
+ // A 192.0.2.0/26 subnet.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("192.0.2.0"), link_len));
+ SubnetIDSet expected = { 2 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(26, link_len);
+
+ // Check that any address in the subnet works.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("192.0.2.23"), link_len));
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(26, link_len);
+
+ // Check that an address outside the subnet does not work.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("192.0.2.123"), link_len));
+ EXPECT_TRUE(links.empty());
+ EXPECT_EQ(111, link_len);
+
+ // Add a second 192.0.2.0/26 subnet.
+ Subnet4Ptr subnet10(new Subnet4(IOAddress("192.0.2.10"),
+ 26, 1, 2, 3, SubnetID(10)));
+ ASSERT_NO_THROW(cfg.add(subnet10));
+
+ // Now we should get 2 subnets.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("192.0.2.0"), link_len));
+ expected = { 2, 10 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(26, link_len);
+
+ // Add a larger subnet.
+ Subnet4Ptr subnet20(new Subnet4(IOAddress("192.0.2.20"),
+ 24, 1, 2, 3, SubnetID(20)));
+ ASSERT_NO_THROW(cfg.add(subnet20));
+
+ // Now we should get 3 subnets and a smaller prefix length.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("192.0.2.0"), link_len));
+ expected = { 2, 10, 20 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(24, link_len);
+
+ // But only the larger subnet if the address is only in it.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("192.0.2.123"), link_len));
+ expected = { 20 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(24, link_len);
+
+ // Even it is not used for Bulk Leasequery, it works for 0.0.0.0 too.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("0.0.0.0"), link_len));
+ expected = { 111 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(24, link_len);
+}
+
+// This test verifies that for each subnet in the configuration it calls
+// the initAllocatorAfterConfigure function.
+TEST(CfgSubnets4Test, initAllocatorsAfterConfigure) {
+ CfgSubnets4 cfg;
+
+ // Create 4 subnets.
+ Subnet4Ptr subnet0(new Subnet4(IOAddress("0.0.0.0"),
+ 24, 1, 2, 3, SubnetID(111)));
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"),
+ 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"),
+ 26, 1, 2, 3, SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.3.0"),
+ 26, 1, 2, 3, SubnetID(3)));
+
+ auto allocator0 = boost::make_shared<InitRecordingAllocator>(Lease::TYPE_V4, subnet0);
+ subnet0->setAllocator(Lease::TYPE_V4, allocator0);
+
+ auto allocator2 = boost::make_shared<InitRecordingAllocator>(Lease::TYPE_V4, subnet2);
+ subnet2->setAllocator(Lease::TYPE_V4, allocator2);
+
+ cfg.add(subnet0);
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ EXPECT_EQ(0, allocator0->callcount_);
+ EXPECT_EQ(0, allocator2->callcount_);
+
+ cfg.initAllocatorsAfterConfigure();
+
+ EXPECT_EQ(1, allocator0->callcount_);
+ EXPECT_EQ(1, allocator2->callcount_);
+}
+
+/// @brief Test fixture for parsing v4 Subnets that can verify log output.
+class Subnet4ParserTest : public LogContentTest {
+public:
+
+ /// @brief virtual destructor
+ virtual ~Subnet4ParserTest() = default;
+};
+
+// This test verifies that subnet parser for IPv4 works properly
+// when using invalid renew and rebind timers.
+TEST_F(Subnet4ParserTest, parseWithInvalidRenewRebind) {
+ std::string config =
+ "{\n"
+ " \"id\": 1,\n"
+ " \"subnet\": \"10.1.2.0/24\",\n"
+ " \"valid-lifetime\": 399,\n"
+ " \"rebind-timer\": 199,\n"
+ " \"renew-timer\": 200\n"
+ "}";
+
+ // Basic configuration for subnet4 but with a renew-timer value
+ // larger than rebind-timer.
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ Subnet4ConfigParser parser(false);
+ Subnet4Ptr subnet;
+
+ // Parser should not throw.
+ ASSERT_NO_THROW(subnet = parser.parse(config_element));
+ ASSERT_TRUE(subnet);
+
+ // Veriy we emitted the proper log message.
+ addString("DHCPSRV_CFGMGR_RENEW_GTR_REBIND in subnet-id 1,"
+ " the value of renew-timer 200 is greater than the value"
+ " of rebind-timer 199, ignoring renew-timer");
+ EXPECT_TRUE(checkFile());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
new file mode 100644
index 0000000..ece583f
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
@@ -0,0 +1,2510 @@
+// Copyright (C) 2014-2023 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 <cc/data.h>
+#include <dhcp/classify.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_string.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/log_utils.h>
+#include <testutils/test_to_element.h>
+#include <util/doubles.h>
+
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::stats;
+using namespace isc::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief An allocator recording calls to @c initAfterConfigure.
+class InitRecordingAllocator : public IterativeAllocator {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param type specifies the type of allocated leases.
+ /// @param subnet weak pointer to the subnet owning the allocator.
+ InitRecordingAllocator(Lease::Type type, const WeakSubnetPtr& subnet)
+ : IterativeAllocator(type, subnet), callcount_(0) {
+ }
+
+ /// @brief Increases the call count of this function.
+ ///
+ /// The call count can be later examined to check whether or not
+ /// the function was called.
+ virtual void initAfterConfigureInternal() {
+ ++callcount_;
+ };
+
+ /// @brief Call count of the @c initAllocatorsAfterConfigure.
+ int callcount_;
+};
+
+/// @brief Generates interface id option.
+///
+/// @param text Interface id in a textual format.
+OptionPtr
+generateInterfaceId(const std::string& text) {
+ OptionBuffer buffer(text.begin(), text.end());
+ return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
+}
+
+/// @brief Verifies that a set of subnets contains a given a subnet
+///
+/// @param cfg_subnets set of subnets in which to look
+/// @param exp_subnet_id expected id of the target subnet
+/// @param prefix prefix of the target subnet
+/// @param exp_valid expected valid lifetime of the subnet
+/// @param exp_network pointer to the subnet's shared-network (if one)
+void checkMergedSubnet(CfgSubnets6& cfg_subnets,
+ const std::string& prefix,
+ const SubnetID exp_subnet_id,
+ int exp_valid,
+ SharedNetwork6Ptr exp_network) {
+ // Look for the network by prefix.
+ auto subnet = cfg_subnets.getByPrefix(prefix);
+ ASSERT_TRUE(subnet) << "subnet: " << prefix << " not found";
+
+ // Make sure we have the one we expect.
+ ASSERT_EQ(exp_subnet_id, subnet->getID()) << "subnet ID is wrong";
+ ASSERT_EQ(exp_valid, subnet->getValid().get()) << "subnetID:"
+ << subnet->getID() << ", subnet valid time is wrong";
+
+ SharedNetwork6Ptr shared_network;
+ subnet->getSharedNetwork(shared_network);
+ if (exp_network) {
+ ASSERT_TRUE(shared_network)
+ << " expected network: " << exp_network->getName() << " not found";
+ ASSERT_TRUE(shared_network == exp_network) << " networks do no match";
+ } else {
+ ASSERT_FALSE(shared_network) << " unexpected network assignment: "
+ << shared_network->getName();
+ }
+}
+
+// This test verifies that specific subnet can be retrieved by specifying
+// subnet identifier or subnet prefix.
+TEST(CfgSubnets6Test, getSpecificSubnet) {
+ CfgSubnets6 cfg;
+
+ // Create 3 subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4,
+ SubnetID(5)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4,
+ SubnetID(8)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4,
+ SubnetID(10)));
+
+ // Store the subnets in a vector to make it possible to loop over
+ // all configured subnets.
+ std::vector<Subnet6Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+
+ // Add all subnets to the configuration.
+ for (auto subnet = subnets.cbegin(); subnet != subnets.cend(); ++subnet) {
+ ASSERT_NO_THROW(cfg.add(*subnet)) << "failed to add subnet with id: "
+ << (*subnet)->getID();
+ }
+
+ // Iterate over all subnets and make sure they can be retrieved by
+ // subnet identifier.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet6Ptr subnet_returned = cfg.getBySubnetId((*subnet)->getID());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Repeat the previous test, but this time retrieve subnets by their
+ // prefixes.
+ for (auto subnet = subnets.rbegin(); subnet != subnets.rend(); ++subnet) {
+ ConstSubnet6Ptr subnet_returned = cfg.getByPrefix((*subnet)->toText());
+ ASSERT_TRUE(subnet_returned) << "failed to return subnet with id: "
+ << (*subnet)->getID();
+ EXPECT_EQ((*subnet)->getID(), subnet_returned->getID());
+ EXPECT_EQ((*subnet)->toText(), subnet_returned->toText());
+ }
+
+ // Make sure that null pointers are returned for non-existing subnets.
+ EXPECT_FALSE(cfg.getBySubnetId(SubnetID(123)));
+ EXPECT_FALSE(cfg.getByPrefix("3000::/16"));
+}
+
+// This test verifies that a single subnet can be removed from the configuration.
+TEST(CfgSubnets6Test, deleteSubnet) {
+ CfgSubnets6 cfg;
+
+ // Create 3 subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // There should be three subnets.
+ ASSERT_EQ(3, cfg.getAll()->size());
+ // We're going to remove the subnet #2. Let's make sure it exists before
+ // we remove it.
+ ASSERT_TRUE(cfg.getByPrefix("2001:db8:2::/48"));
+
+ // Remove the subnet and make sure it is gone.
+ ASSERT_NO_THROW(cfg.del(subnet2));
+ ASSERT_EQ(2, cfg.getAll()->size());
+ EXPECT_FALSE(cfg.getByPrefix("2001:db8:2::/48"));
+
+ // Remove another subnet by ID.
+ ASSERT_NO_THROW(cfg.del(subnet1->getID()));
+ ASSERT_EQ(1, cfg.getAll()->size());
+ EXPECT_FALSE(cfg.getByPrefix("2001:db8:1::/48"));
+}
+
+// This test verifies that replace a subnet works as expected.
+TEST(CfgSubnets6Test, replaceSubnet) {
+ CfgSubnets6 cfg;
+
+ // Create 3 subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 100, SubnetID(10)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 100, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 100, SubnetID(13)));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // There should be three subnets.
+ ASSERT_EQ(3, cfg.getAll()->size());
+ // We're going to replace the subnet #2. Let's make sure it exists before
+ // we replace it.
+ ASSERT_TRUE(cfg.getByPrefix("2001:db8:2::/48"));
+
+ // Replace the subnet and make sure it was updated.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:2::"),
+ 48, 10, 20, 30, 1000, SubnetID(2)));
+ Subnet6Ptr replaced = cfg.replace(subnet);
+ ASSERT_TRUE(replaced);
+ EXPECT_TRUE(replaced == subnet2);
+ ASSERT_EQ(3, cfg.getAll()->size());
+ Subnet6Ptr returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet);
+
+ // Restore.
+ replaced = cfg.replace(replaced);
+ ASSERT_TRUE(replaced);
+ EXPECT_TRUE(replaced == subnet);
+ ASSERT_EQ(3, cfg.getAll()->size());
+ returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet2);
+
+ // Prefix conflict returns null.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:3::"),
+ 48, 10, 20, 30, 1000, SubnetID(2)));
+ replaced = cfg.replace(subnet);
+ EXPECT_FALSE(replaced);
+ returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet2);
+
+ // Changing prefix works even it is highly not recommended.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:10::"),
+ 48, 10, 20, 30, 1000, SubnetID(2)));
+ replaced = cfg.replace(subnet);
+ ASSERT_TRUE(replaced);
+ EXPECT_TRUE(replaced == subnet2);
+ returned = cfg.getSubnet(SubnetID(2));
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(returned == subnet);
+}
+
+// This test checks that the subnet can be selected using a relay agent's
+// link address.
+TEST(CfgSubnets6Test, selectSubnetByRelayAddress) {
+ CfgSubnets6 cfg;
+
+ // Let's configure 3 subnets
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Make sure that none of the subnets is selected when there is no relay
+ // information configured for them.
+ SubnetSelector selector;
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Now specify relay information.
+ subnet1->addRelayAddress(IOAddress("2001:db8:ff::1"));
+ subnet2->addRelayAddress(IOAddress("2001:db8:ff::2"));
+ subnet3->addRelayAddress(IOAddress("2001:db8:ff::3"));
+
+ // And try again. This time relay-info is there and should match.
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test checks that subnet can be selected using a relay agent's
+// link address specified on the shared network level.
+TEST(CfgSubnets6Test, selectSubnetByNetworkRelayAddress) {
+ // Create 2 shared networks.
+ SharedNetwork6Ptr network1(new SharedNetwork6("net1"));
+ SharedNetwork6Ptr network2(new SharedNetwork6("net2"));
+ SharedNetwork6Ptr network3(new SharedNetwork6("net3"));
+
+ // Let's configure 3 subnets
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+
+ // Allow subnet class of clients to use the subnets.
+ subnet1->allowClientClass("subnet");
+ subnet2->allowClientClass("subnet");
+ subnet3->allowClientClass("subnet");
+
+ // Associate subnets with shared networks.
+ network1->add(subnet1);
+ network2->add(subnet2);
+ network3->add(subnet3);
+
+ // Add subnets to the configuration.
+ CfgSubnets6 cfg;
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Make sure that none of the subnets is selected when there is no relay
+ // information configured for them.
+ SubnetSelector selector;
+ selector.client_classes_.insert("subnet");
+
+ // The relays are not set for networks, so none of the subnets should
+ // be selected.
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Now specify relay information.
+ network1->addRelayAddress(IOAddress("2001:db8:ff::1"));
+ network2->addRelayAddress(IOAddress("2001:db8:ff::2"));
+ network3->addRelayAddress(IOAddress("2001:db8:ff::3"));
+
+ // And try again. This time relay-info is there and should match.
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Modify the client classes associated with the first two subnets.
+ subnet1->allowClientClass("subnet1");
+ subnet2->allowClientClass("subnet2");
+
+ // This time the non-matching classes should prevent selection.
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test checks that the subnet can be selected using an interface
+// name associated with a asubnet.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceName) {
+ CfgSubnets6 cfg;
+
+ // Let's create 3 subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+ subnet1->setIface("foo");
+ subnet2->setIface("bar");
+ subnet3->setIface("foobar");
+
+ // Until subnets are added to the configuration, there should be nothing
+ // returned.
+ SubnetSelector selector;
+ selector.iface_name_ = "foo";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Add one of the subnets.
+ cfg.add(subnet1);
+
+ // The subnet should be now selected for the interface name "foo".
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+
+ // Check that the interface name is checked even when there is
+ // only one subnet defined: there should be nothing returned when
+ // other interface name is specified.
+ selector.iface_name_ = "bar";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Add other subnets.
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // When we specify correct interface names, the subnets should be returned.
+ selector.iface_name_ = "foobar";
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+ selector.iface_name_ = "bar";
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+
+ // When specifying a non-existing interface the subnet should not be
+ // returned.
+ selector.iface_name_ = "xyzzy";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// This test checks that the subnet can be selected using an Interface ID
+// option inserted by a relay.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceId) {
+ CfgSubnets6 cfg;
+
+ // Create 3 subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+
+ // Create Interface-id options used in subnets 1,2, and 3
+ OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+ OptionPtr ifaceid2 = generateInterfaceId("VL32");
+ // That's a strange interface-id, but this is a real life example
+ OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+ // Bogus interface-id.
+ OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+ // Assign interface ids to the respective subnets.
+ subnet1->setInterfaceId(ifaceid1);
+ subnet2->setInterfaceId(ifaceid2);
+ subnet3->setInterfaceId(ifaceid3);
+
+ // There shouldn't be any subnet configured at this stage.
+ SubnetSelector selector;
+ selector.interface_id_ = ifaceid1;
+ // Note that some link address must be specified to indicate that it is
+ // a relayed message!
+ selector.first_relay_linkaddr_ = IOAddress("5000::1");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Add one of the subnets.
+ cfg.add(subnet1);
+
+ // If only one subnet has been specified, it should be returned when the
+ // interface id matches. But, for a different interface id there should be
+ // no match.
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.interface_id_ = ifaceid2;
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Add other subnets.
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Now that we have all subnets added. we should be able to retrieve them
+ // using appropriate interface ids.
+ selector.interface_id_ = ifaceid3;
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+ selector.interface_id_ = ifaceid2;
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+
+ // For invalid interface id, there should be nothing returned.
+ selector.interface_id_ = ifaceid_bogus;
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that the client classes are considered when the subnet is selected by
+// the relay link address.
+TEST(CfgSubnets6Test, selectSubnetByRelayAddressAndClassify) {
+ CfgSubnets6 cfg;
+
+ // Let's configure 3 subnets
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Let's sanity check that we can use that configuration.
+ SubnetSelector selector;
+ selector.first_relay_linkaddr_ = IOAddress("2000::123");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("3000::345");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("4000::567");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Client now belongs to bar class.
+ selector.client_classes_.insert("bar");
+
+ // There are no class restrictions defined, so everything should work
+ // as before.
+ selector.first_relay_linkaddr_ = IOAddress("2000::123");
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("3000::345");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("4000::567");
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ // Now let's add client class restrictions.
+ subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+ subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+ subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+ // The same check as above should result in client being served only in
+ // bar class, i.e. subnet2
+ selector.first_relay_linkaddr_ = IOAddress("2000::123");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("3000::345");
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("4000::567");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Now let's check that client with wrong class is not supported
+ selector.client_classes_.clear();
+ selector.client_classes_.insert("some_other_class");
+ selector.first_relay_linkaddr_ = IOAddress("2000::123");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("3000::345");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("4000::567");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+
+ // Finally, let's check that client without any classes is not supported
+ selector.client_classes_.clear();
+ selector.first_relay_linkaddr_ = IOAddress("2000::123");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("3000::345");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.first_relay_linkaddr_ = IOAddress("4000::567");
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that client classes are considered when the subnet is selected by the
+// interface name.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceNameAndClassify) {
+ CfgSubnets6 cfg;
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+ subnet1->setIface("foo");
+ subnet2->setIface("bar");
+ subnet3->setIface("foobar");
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Now we have only one subnet, any request will be served from it
+ SubnetSelector selector;
+ selector.client_classes_.insert("bar");
+ selector.iface_name_ = "foo";
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.iface_name_ = "bar";
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.iface_name_ = "foobar";
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+ subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+ subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+ selector.iface_name_ = "foo";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.iface_name_ = "bar";
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.iface_name_ = "foobar";
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that client classes are considered when the interface is selected by
+// the interface id.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceIdAndClassify) {
+ CfgSubnets6 cfg;
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"),
+ 48, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"),
+ 48, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"),
+ 48, 1, 2, 3, 4, SubnetID(3)));
+
+ // interface-id options used in subnets 1,2, and 3
+ OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+ OptionPtr ifaceid2 = generateInterfaceId("VL32");
+ // That's a strange interface-id, but this is a real life example
+ OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+ // bogus interface-id
+ OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+ subnet1->setInterfaceId(ifaceid1);
+ subnet2->setInterfaceId(ifaceid2);
+ subnet3->setInterfaceId(ifaceid3);
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // If we have only a single subnet and the request came from a local
+ // address, let's use that subnet
+ SubnetSelector selector;
+ selector.first_relay_linkaddr_ = IOAddress("5000::1");
+ selector.client_classes_.insert("bar");
+ selector.interface_id_ = ifaceid1;
+ EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+ selector.interface_id_ = ifaceid2;
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.interface_id_ = ifaceid3;
+ EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+ subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+ subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+ subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+ selector.interface_id_ = ifaceid2;
+ EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+ selector.interface_id_ = ifaceid3;
+ EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Checks that detection of duplicated subnet IDs works as expected. It should
+// not be possible to add two IPv6 subnets holding the same ID.
+TEST(CfgSubnets6Test, duplication) {
+ CfgSubnets6 cfg;
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4, 123));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4, 124));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4, 123));
+ Subnet6Ptr subnet4(new Subnet6(IOAddress("2000::1"), 48, 1, 2, 3, 4, 125));
+
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ EXPECT_NO_THROW(cfg.add(subnet2));
+ // Subnet 3 has the same ID as subnet 1. It shouldn't be able to add it.
+ EXPECT_THROW(cfg.add(subnet3), isc::dhcp::DuplicateSubnetID);
+ // Subnet 4 has a similar but different subnet as subnet 1.
+ EXPECT_NO_THROW(cfg.add(subnet4));
+}
+
+// This test check if IPv6 subnets can be unparsed in a predictable way,
+TEST(CfgSubnets6Test, unparseSubnet) {
+ CfgSubnets6 cfg;
+
+ // Add some subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, 123));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"),
+ 48, 1, 2, 3, 4, 124));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"),
+ 48, 1, 2, 3, 4, 125));
+
+ OptionPtr ifaceid = generateInterfaceId("relay.eth0");
+ subnet1->setInterfaceId(ifaceid);
+ subnet1->allowClientClass("foo");
+
+ subnet1->setT1Percent(0.45);
+ subnet1->setT2Percent(0.70);
+ subnet1->setCacheThreshold(0.20);
+
+ subnet2->setIface("lo");
+ subnet2->addRelayAddress(IOAddress("2001:db8:ff::2"));
+ subnet2->setValid(Triplet<uint32_t>(200));
+ subnet2->setPreferred(Triplet<uint32_t>(100));
+ subnet2->setStoreExtendedInfo(true);
+ subnet2->setCacheMaxAge(80);
+
+ subnet3->setIface("eth1");
+ subnet3->requireClientClass("foo");
+ subnet3->requireClientClass("bar");
+ subnet3->setReservationsGlobal(false);
+ subnet3->setReservationsInSubnet(true);
+ subnet3->setReservationsOutOfPool(false);
+ subnet3->setRapidCommit(false);
+ subnet3->setCalculateTeeTimes(true);
+ subnet3->setT1Percent(0.50);
+ subnet3->setT2Percent(0.65);
+ subnet3->setValid(Triplet<uint32_t>(100, 200, 300));
+ subnet3->setPreferred(Triplet<uint32_t>(50, 100, 150));
+ subnet3->setDdnsSendUpdates(true);
+ subnet3->setDdnsOverrideNoUpdate(true);
+ subnet3->setDdnsOverrideClientUpdate(true);
+ subnet3->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
+ subnet3->setDdnsGeneratedPrefix("prefix");
+ subnet3->setDdnsQualifyingSuffix("example.com.");
+ subnet3->setHostnameCharSet("[^A-Z]");
+ subnet3->setHostnameCharReplacement("x");
+
+ data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
+ subnet1->setContext(ctx1);
+ data::ElementPtr ctx2 = data::Element::createMap();
+ subnet2->setContext(ctx2);
+
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"2001:db8:1::/48\",\n"
+ " \"t1-percent\": 0.45,"
+ " \"t2-percent\": 0.7,"
+ " \"cache-threshold\": .20,\n"
+ " \"interface-id\": \"relay.eth0\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"min-preferred-lifetime\": 3,\n"
+ " \"max-preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"min-valid-lifetime\": 4,\n"
+ " \"max-valid-lifetime\": 4,\n"
+ " \"client-class\": \"foo\",\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { \"comment\": \"foo\" }\n"
+ "},{\n"
+ " \"id\": 124,\n"
+ " \"subnet\": \"2001:db8:2::/48\",\n"
+ " \"interface\": \"lo\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ \"2001:db8:ff::2\" ] },\n"
+ " \"preferred-lifetime\": 100,\n"
+ " \"min-preferred-lifetime\": 100,\n"
+ " \"max-preferred-lifetime\": 100,\n"
+ " \"valid-lifetime\": 200,\n"
+ " \"min-valid-lifetime\": 200,\n"
+ " \"max-valid-lifetime\": 200,\n"
+ " \"user-context\": { },\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ],\n"
+ " \"store-extended-info\": true,\n"
+ " \"cache-max-age\": 80\n"
+ "},{\n"
+ " \"id\": 125,\n"
+ " \"subnet\": \"2001:db8:3::/48\",\n"
+ " \"interface\": \"eth1\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"preferred-lifetime\": 100,\n"
+ " \"min-preferred-lifetime\": 50,\n"
+ " \"max-preferred-lifetime\": 150,\n"
+ " \"valid-lifetime\": 200,\n"
+ " \"min-valid-lifetime\": 100,\n"
+ " \"max-valid-lifetime\": 300,\n"
+ " \"rapid-commit\": false,\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": false,\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ],\n"
+ " \"require-client-classes\": [ \"foo\", \"bar\" ],\n"
+ " \"calculate-tee-times\": true,\n"
+ " \"t1-percent\": 0.50,\n"
+ " \"t2-percent\": 0.65,\n"
+ " \"ddns-generated-prefix\": \"prefix\",\n"
+ " \"ddns-override-client-update\": true,\n"
+ " \"ddns-override-no-update\": true,\n"
+ " \"ddns-qualifying-suffix\": \"example.com.\",\n"
+ " \"ddns-replace-client-name\": \"always\",\n"
+ " \"ddns-send-updates\": true,\n"
+ " \"hostname-char-replacement\": \"x\",\n"
+ " \"hostname-char-set\": \"[^A-Z]\"\n"
+ "} ]\n";
+
+ runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test check if IPv6 pools can be unparsed in a predictable way,
+TEST(CfgSubnets6Test, unparsePool) {
+ CfgSubnets6 cfg;
+
+ // Add a subnet with pools
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, 123));
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::199")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
+ pool2->allowClientClass("bar");
+
+ std::string json1 = "{ \"comment\": \"foo\", \"version\": 1 }";
+ data::ElementPtr ctx1 = data::Element::fromJSON(json1);
+ pool1->setContext(ctx1);
+ data::ElementPtr ctx2 = data::Element::fromJSON("{ \"foo\": \"bar\" }");
+ pool2->setContext(ctx2);
+ pool2->requireClientClass("foo");
+
+ subnet->addPool(pool1);
+ subnet->addPool(pool2);
+ cfg.add(subnet);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"2001:db8:1::/48\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"min-preferred-lifetime\": 3,\n"
+ " \"max-preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"min-valid-lifetime\": 4,\n"
+ " \"max-valid-lifetime\": 4,\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8:1::100-2001:db8:1::199\",\n"
+ " \"user-context\": { \"version\": 1,\n"
+ " \"comment\": \"foo\" },\n"
+ " \"option-data\": [ ]\n"
+ " },{\n"
+ " \"pool\": \"2001:db8:1:1::/64\",\n"
+ " \"user-context\": { \"foo\": \"bar\" },\n"
+ " \"option-data\": [ ],\n"
+ " \"client-class\": \"bar\",\n"
+ " \"require-client-classes\": [ \"foo\" ]\n"
+ " }\n"
+ " ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"option-data\": [ ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test check if IPv6 prefix delegation pools can be unparsed
+// in a predictable way,
+TEST(CfgSubnets6Test, unparsePdPool) {
+ CfgSubnets6 cfg;
+
+ // Add a subnet with pd-pools
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 1, 2, 3, 4, 123));
+ Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD,
+ IOAddress("2001:db8:2::"), 48, 64));
+ Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56,
+ IOAddress("2001:db8:3::"), 64));
+ pdpool2->allowClientClass("bar");
+
+ data::ElementPtr ctx1 = data::Element::fromJSON("{ \"foo\": [ \"bar\" ] }");
+ pdpool1->setContext(ctx1);
+ pdpool1->requireClientClass("bar");
+ pdpool2->allowClientClass("bar");
+
+ subnet->addPool(pdpool1);
+ subnet->addPool(pdpool2);
+ cfg.add(subnet);
+
+ // Unparse
+ std::string expected = "[\n"
+ "{\n"
+ " \"id\": 123,\n"
+ " \"subnet\": \"2001:db8:1::/48\",\n"
+ " \"renew-timer\": 1,\n"
+ " \"rebind-timer\": 2,\n"
+ " \"relay\": { \"ip-addresses\": [ ] },\n"
+ " \"preferred-lifetime\": 3,\n"
+ " \"min-preferred-lifetime\": 3,\n"
+ " \"max-preferred-lifetime\": 3,\n"
+ " \"valid-lifetime\": 4,\n"
+ " \"min-valid-lifetime\": 4,\n"
+ " \"max-valid-lifetime\": 4,\n"
+ " \"pools\": [ ],\n"
+ " \"pd-pools\": [\n"
+ " {\n"
+ " \"prefix\": \"2001:db8:2::\",\n"
+ " \"prefix-len\": 48,\n"
+ " \"delegated-len\": 64,\n"
+ " \"user-context\": { \"foo\": [ \"bar\" ] },\n"
+ " \"option-data\": [ ],\n"
+ " \"require-client-classes\": [ \"bar\" ]\n"
+ " },{\n"
+ " \"prefix\": \"2001:db8:3::\",\n"
+ " \"prefix-len\": 48,\n"
+ " \"delegated-len\": 56,\n"
+ " \"excluded-prefix\": \"2001:db8:3::\",\n"
+ " \"excluded-prefix-len\": 64,\n"
+ " \"option-data\": [ ],\n"
+ " \"client-class\": \"bar\"\n"
+ " }\n"
+ " ],\n"
+ " \"option-data\": [ ]\n"
+ "} ]\n";
+ runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test verifies that it is possible to retrieve a subnet using subnet-id.
+TEST(CfgSubnets6Test, getSubnet) {
+ CfgSubnets6 cfg;
+
+ // Let's configure 3 subnets
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 100));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, 200));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4, 300));
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ EXPECT_EQ(subnet1, cfg.getSubnet(100));
+ EXPECT_EQ(subnet2, cfg.getSubnet(200));
+ EXPECT_EQ(subnet3, cfg.getSubnet(300));
+ EXPECT_EQ(Subnet6Ptr(), cfg.getSubnet(400)); // no such subnet
+}
+
+// This test verifies that subnets configuration is properly merged.
+TEST(CfgSubnets6Test, mergeSubnets) {
+ // Create custom options dictionary for testing merge. We're keeping it
+ // simple because they are more rigorous tests elsewhere.
+ CfgOptionDefPtr cfg_def(new CfgOptionDef());
+ cfg_def->add((OptionDefinitionPtr(new OptionDefinition("one", 1, "isc", "string"))));
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:1::"),
+ 64, 1, 2, 100, 100, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:2::"),
+ 64, 1, 2, 100, 100, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:3::"),
+ 64, 1, 2, 100, 100, SubnetID(3)));
+ Subnet6Ptr subnet4(new Subnet6(IOAddress("2001:4::"),
+ 64, 1, 2, 100, 100, SubnetID(4)));
+
+ // Create the "existing" list of shared networks
+ CfgSharedNetworks6Ptr networks(new CfgSharedNetworks6());
+ SharedNetwork6Ptr shared_network1(new SharedNetwork6("shared-network1"));
+ networks->add(shared_network1);
+ SharedNetwork6Ptr shared_network2(new SharedNetwork6("shared-network2"));
+ networks->add(shared_network2);
+
+ // Empty network pointer.
+ SharedNetwork6Ptr no_network;
+
+ // Add Subnets 1, 2 and 4 to shared networks.
+ ASSERT_NO_THROW(shared_network1->add(subnet1));
+ ASSERT_NO_THROW(shared_network2->add(subnet2));
+ ASSERT_NO_THROW(shared_network2->add(subnet4));
+
+ // Create our "existing" configured subnets.
+ CfgSubnets6 cfg_to;
+ ASSERT_NO_THROW(cfg_to.add(subnet1));
+ ASSERT_NO_THROW(cfg_to.add(subnet2));
+ ASSERT_NO_THROW(cfg_to.add(subnet3));
+ ASSERT_NO_THROW(cfg_to.add(subnet4));
+
+ // Merge in an "empty" config. Should have the original config,
+ // still intact.
+ CfgSubnets6 cfg_from;
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, networks, cfg_from));
+
+ // We should have all four subnets, with no changes.
+ ASSERT_EQ(4, cfg_to.getAll()->size());
+
+ // Should be no changes to the configuration.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:1::/64",
+ SubnetID(1), 100, shared_network1));
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:2::/64",
+ SubnetID(2), 100, shared_network2));
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:3::/64",
+ SubnetID(3), 100, no_network));
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:4::/64",
+ SubnetID(4), 100, shared_network2));
+
+ // Fill cfg_from configuration with subnets.
+ // subnet 1b updates subnet 1 but leaves it in network 1 with the same ID.
+ Subnet6Ptr subnet1b(new Subnet6(IOAddress("2001:10::"),
+ 64, 2, 3, 100, 400, SubnetID(1)));
+ subnet1b->setSharedNetworkName("shared-network1");
+
+ // Add generic option 1 to subnet 1b.
+ std::string value("Yay!");
+ OptionPtr option(new Option(Option::V6, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(subnet1b->getCfgOption()->add(option, false, false, "isc"));
+
+ // subnet 3b updates subnet 3 with different UD and removes it
+ // from network 2
+ Subnet6Ptr subnet3b(new Subnet6(IOAddress("2001:3::"),
+ 64, 3, 4, 100, 500, SubnetID(30)));
+
+ // Now Add generic option 1 to subnet 3b.
+ value = "Team!";
+ option.reset(new Option(Option::V6, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(subnet3b->getCfgOption()->add(option, false, false, "isc"));
+
+ // subnet 4b updates subnet 4 and moves it from network2 to network 1
+ Subnet6Ptr subnet4b(new Subnet6(IOAddress("2001:4::"),
+ 64, 3, 4, 100, 500, SubnetID(4)));
+ subnet4b->setSharedNetworkName("shared-network1");
+
+ // subnet 5 is new and belongs to network 2
+ // Has two pools both with an option 1
+ Subnet6Ptr subnet5(new Subnet6(IOAddress("2001:5::"),
+ 64, 1, 2, 100, 300, SubnetID(5)));
+ subnet5->setSharedNetworkName("shared-network2");
+
+ // Add pool 1
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:5::10"), IOAddress("2001:5::20")));
+ value = "POOLS";
+ option.reset(new Option(Option::V6, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false, "isc"));
+ subnet5->addPool(pool);
+
+ // Add pool 2
+ pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("2001:6::"), 64));
+ value ="RULE!";
+ option.reset(new Option(Option::V6, 1));
+ option->setData(value.begin(), value.end());
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false, "isc"));
+ subnet5->addPool(pool);
+
+ // Add subnets to the merge from config.
+ ASSERT_NO_THROW(cfg_from.add(subnet1b));
+ ASSERT_NO_THROW(cfg_from.add(subnet3b));
+ ASSERT_NO_THROW(cfg_from.add(subnet4b));
+ ASSERT_NO_THROW(cfg_from.add(subnet5));
+
+ // Merge again.
+ ASSERT_NO_THROW(cfg_to.merge(cfg_def, networks, cfg_from));
+ ASSERT_EQ(5, cfg_to.getAll()->size());
+
+ // The subnet1 should be replaced by subnet1b.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:10::/64",
+ SubnetID(1), 400, shared_network1));
+
+ // Let's verify that our option is there and populated correctly.
+ auto subnet = cfg_to.getByPrefix("2001:10::/64");
+ auto desc = subnet->getCfgOption()->get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ OptionStringPtr opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("Yay!", opstr->getValue());
+
+ // The subnet2 should not be affected because it was not present.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:2::/64",
+ SubnetID(2), 100, shared_network2));
+
+ // subnet3 should be replaced by subnet3b and no longer assigned to a network.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:3::/64",
+ SubnetID(30), 500, no_network));
+ // Let's verify that our option is there and populated correctly.
+ subnet = cfg_to.getByPrefix("2001:3::/64");
+ desc = subnet->getCfgOption()->get("isc", 1);
+ ASSERT_TRUE(desc.option_);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("Team!", opstr->getValue());
+
+ // subnet4 should be replaced by subnet4b and moved to network1.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:4::/64",
+ SubnetID(4), 500, shared_network1));
+
+ // subnet5 should have been added to configuration.
+ ASSERT_NO_FATAL_FAILURE(checkMergedSubnet(cfg_to, "2001:5::/64",
+ SubnetID(5), 300, shared_network2));
+
+ // Let's verify that both pools have the proper options.
+ subnet = cfg_to.getByPrefix("2001:5::/64");
+ const PoolPtr merged_pool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:5::10"));
+ ASSERT_TRUE(merged_pool);
+ desc = merged_pool->getCfgOption()->get("isc", 1);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("POOLS", opstr->getValue());
+
+ const PoolPtr merged_pool2 = subnet->getPool(Lease::TYPE_PD, IOAddress("2001:1::"));
+ ASSERT_TRUE(merged_pool2);
+ desc = merged_pool2->getCfgOption()->get("isc", 1);
+ opstr = boost::dynamic_pointer_cast<OptionString>(desc.option_);
+ ASSERT_TRUE(opstr);
+ EXPECT_EQ("RULE!", opstr->getValue());
+}
+
+// This test verifies the Subnet6 parser's validation logic for
+// t1-percent and t2-percent parameters.
+TEST(CfgSubnets6Test, teeTimePercentValidation) {
+
+ // Describes a single test scenario.
+ struct Scenario {
+ std::string label; // label used for logging test failures
+ bool calculate_tee_times; // value of calculate-tee-times parameter
+ double t1_percent; // value of t1-percent parameter
+ double t2_percent; // value of t2-percent parameter
+ std::string error_message; // expected error message is parsing should fail
+ };
+
+ // Test Scenarios.
+ std::vector<Scenario> tests = {
+ {"off and valid", false, .5, .95, ""},
+ {"on and valid", true, .5, .95, ""},
+ {"t2_negative", true, .5, -.95,
+ "subnet configuration failed: t2-percent:"
+ " -0.95 is invalid, it must be greater than 0.0 and less than 1.0"
+ },
+ {"t2_too_big", true, .5, 1.95,
+ "subnet configuration failed: t2-percent:"
+ " 1.95 is invalid, it must be greater than 0.0 and less than 1.0"
+ },
+ {"t1_negative", true, -.5, .95,
+ "subnet configuration failed: t1-percent:"
+ " -0.5 is invalid it must be greater than 0.0 and less than 1.0"
+ },
+ {"t1_too_big", true, 1.5, .95,
+ "subnet configuration failed: t1-percent:"
+ " 1.5 is invalid it must be greater than 0.0 and less than 1.0"
+ },
+ {"t1_bigger_than_t2", true, .85, .45,
+ "subnet configuration failed: t1-percent:"
+ " 0.85 is invalid, it must be less than t2-percent: 0.45"
+ }
+ };
+
+ // First we create a set of elements that provides all
+ // required for a Subnet6.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"valid-lifetime\": 300, \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false \n"
+ " }";
+
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ // Iterate over the test scenarios, verifying each prescribed
+ // outcome.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE("test: " + (*test).label);
+
+ // Set this scenario's configuration parameters
+ elems->set("calculate-tee-times", data::Element::create((*test).calculate_tee_times));
+ elems->set("t1-percent", data::Element::create((*test).t1_percent));
+ elems->set("t2-percent", data::Element::create((*test).t2_percent));
+
+ Subnet6Ptr subnet;
+ try {
+ // Attempt to parse the configuration.
+ Subnet6ConfigParser parser;
+ subnet = parser.parse(elems);
+ } catch (const std::exception& ex) {
+ if (!(*test).error_message.empty()) {
+ // We expected a failure, did we fail the correct way?
+ EXPECT_EQ((*test).error_message, ex.what());
+ } else {
+ // Should not have failed.
+ ADD_FAILURE() << "Scenario should not have failed: " << ex.what();
+ }
+
+ // Either way we're done with this scenario.
+ continue;
+ }
+
+ // We parsed correctly, make sure the values are right.
+ EXPECT_EQ((*test).calculate_tee_times, subnet->getCalculateTeeTimes());
+ EXPECT_TRUE(util::areDoublesEquivalent((*test).t1_percent, subnet->getT1Percent()))
+ << "expected:" << (*test).t1_percent << " actual: " << subnet->getT1Percent();
+ EXPECT_TRUE(util::areDoublesEquivalent((*test).t2_percent, subnet->getT2Percent()))
+ << "expected:" << (*test).t2_percent << " actual: " << subnet->getT2Percent();
+ }
+ }
+}
+
+// This test verifies the Subnet6 parser's validation logic for
+// preferred-lifetime and indirectly shared lifetime parsing.
+// Note the valid-lifetime stuff is similar and already done for Subnet4.
+TEST(CfgSubnets6Test, preferredLifetimeValidation) {
+ // First we create a set of elements that provides all
+ // required for a Subnet6.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"valid-lifetime\": 300, \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false \n"
+ " }";
+
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ {
+ SCOPED_TRACE("no preferred-lifetime");
+
+ data::ElementPtr copied = data::copy(elems);
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getPreferred().unspecified());
+ data::ConstElementPtr repr = subnet->toElement();
+ EXPECT_FALSE(repr->get("preferred-lifetime"));
+ EXPECT_FALSE(repr->get("min-preferred-lifetime"));
+ EXPECT_FALSE(repr->get("max-preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("preferred-lifetime only");
+
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("preferred-lifetime", data::Element::create(100));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(100, subnet->getPreferred().get());
+ EXPECT_EQ(100, subnet->getPreferred().getMin());
+ EXPECT_EQ(100, subnet->getPreferred().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("preferred-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-preferred-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-preferred-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("min-preferred-lifetime only");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-preferred-lifetime", data::Element::create(100));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(100, subnet->getPreferred().get());
+ EXPECT_EQ(100, subnet->getPreferred().getMin());
+ EXPECT_EQ(100, subnet->getPreferred().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("preferred-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-preferred-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-preferred-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("max-preferred-lifetime only");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("max-preferred-lifetime", data::Element::create(100));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(100, subnet->getPreferred().get());
+ EXPECT_EQ(100, subnet->getPreferred().getMin());
+ EXPECT_EQ(100, subnet->getPreferred().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("preferred-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-preferred-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-preferred-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("min-preferred-lifetime and preferred-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-preferred-lifetime", data::Element::create(100));
+ // Use a different (and greater) value for the default.
+ copied->set("preferred-lifetime", data::Element::create(200));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(200, subnet->getPreferred().get());
+ EXPECT_EQ(100, subnet->getPreferred().getMin());
+ EXPECT_EQ(200, subnet->getPreferred().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("preferred-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("200", value->str());
+ data::ConstElementPtr min_value = repr->get("min-preferred-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-preferred-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("200", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("max-preferred-lifetime and preferred-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("max-preferred-lifetime", data::Element::create(200));
+ // Use a different (and smaller) value for the default.
+ copied->set("preferred-lifetime", data::Element::create(100));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(100, subnet->getPreferred().get());
+ EXPECT_EQ(100, subnet->getPreferred().getMin());
+ EXPECT_EQ(200, subnet->getPreferred().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("preferred-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-preferred-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-preferred-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("200", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("min-preferred-lifetime and max-preferred-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-preferred-lifetime", data::Element::create(100));
+ copied->set("max-preferred-lifetime", data::Element::create(200));
+ Subnet6ConfigParser parser;
+ // No idea about the value to use for the default so failing.
+ ASSERT_THROW(parser.parse(copied), DhcpConfigError);
+ }
+
+ {
+ SCOPED_TRACE("all 3 (min, max and default) preferred-lifetime");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-preferred-lifetime", data::Element::create(100));
+ // Use a different (and greater) value for the default.
+ copied->set("preferred-lifetime", data::Element::create(200));
+ // Use a different (and greater than both) value for max.
+ copied->set("max-preferred-lifetime", data::Element::create(300));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(200, subnet->getPreferred().get());
+ EXPECT_EQ(100, subnet->getPreferred().getMin());
+ EXPECT_EQ(300, subnet->getPreferred().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("preferred-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("200", value->str());
+ data::ConstElementPtr min_value = repr->get("min-preferred-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-preferred-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("300", max_value->str());
+ }
+
+ {
+ SCOPED_TRACE("default value too small");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-preferred-lifetime", data::Element::create(200));
+ // 100 < 200 so it will fail.
+ copied->set("preferred-lifetime", data::Element::create(100));
+ // Use a different (and greater than both) value for max.
+ copied->set("max-preferred-lifetime", data::Element::create(300));
+ Subnet6ConfigParser parser;
+ ASSERT_THROW(parser.parse(copied), DhcpConfigError);
+ }
+
+ {
+ SCOPED_TRACE("default value too large");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-preferred-lifetime", data::Element::create(100));
+ // Use a different (and greater) value for the default.
+ copied->set("preferred-lifetime", data::Element::create(300));
+ // 300 > 200 so it will fail.
+ copied->set("max-preferred-lifetime", data::Element::create(200));
+ Subnet6ConfigParser parser;
+ ASSERT_THROW(parser.parse(copied), DhcpConfigError);
+ }
+
+ {
+ SCOPED_TRACE("equal bounds are no longer ignored");
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("min-preferred-lifetime", data::Element::create(100));
+ copied->set("preferred-lifetime", data::Element::create(100));
+ copied->set("max-preferred-lifetime", data::Element::create(100));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(100, subnet->getPreferred().get());
+ EXPECT_EQ(100, subnet->getPreferred().getMin());
+ EXPECT_EQ(100, subnet->getPreferred().getMax());
+ data::ConstElementPtr repr = subnet->toElement();
+ data::ConstElementPtr value = repr->get("preferred-lifetime");
+ ASSERT_TRUE(value);
+ EXPECT_EQ("100", value->str());
+ data::ConstElementPtr min_value = repr->get("min-preferred-lifetime");
+ ASSERT_TRUE(min_value);
+ EXPECT_EQ("100", min_value->str());
+ data::ConstElementPtr max_value = repr->get("max-preferred-lifetime");
+ ASSERT_TRUE(max_value);
+ EXPECT_EQ("100", max_value->str());
+ }
+}
+
+// This test verifies the Subnet6 parser's validation logic for
+// hostname sanitizer values.
+TEST(CfgSubnets6Test, hostnameSanitizierValidation) {
+
+ // First we create a set of elements that provides all
+ // required for a Subnet6.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"valid-lifetime\": 300, \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false \n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ {
+ SCOPED_TRACE("invalid regular expression");
+
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("hostname-char-set", data::Element::create("^[A-"));
+ copied->set("hostname-char-replacement", data::Element::create("x"));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ EXPECT_THROW_MSG(subnet = parser.parse(copied), DhcpConfigError,
+ "subnet configuration failed: hostname-char-set "
+ "'^[A-' is not a valid regular expression");
+
+ }
+ {
+ SCOPED_TRACE("valid regular expression");
+
+ data::ElementPtr copied = data::copy(elems);
+ copied->set("hostname-char-set", data::Element::create("^[A-Z]"));
+ copied->set("hostname-char-replacement", data::Element::create("x"));
+ Subnet6Ptr subnet;
+ Subnet6ConfigParser parser;
+ ASSERT_NO_THROW(subnet = parser.parse(copied));
+ EXPECT_EQ("^[A-Z]", subnet->getHostnameCharSet().get());
+ EXPECT_EQ("x", subnet->getHostnameCharReplacement().get());
+ }
+}
+
+// This test verifies the Subnet6 parser's validation logic for
+// lease cache parameters.
+TEST(CfgSubnets6Test, cacheParamValidation) {
+
+ // Describes a single test scenario.
+ struct Scenario {
+ std::string label; // label used for logging test failures
+ double threshold; // value of cache-threshold
+ std::string error_message; // expected error message is parsing should fail
+ };
+
+ // Test Scenarios.
+ std::vector<Scenario> tests = {
+ {"valid", .25, ""},
+ {"negative", -.25,
+ "subnet configuration failed: cache-threshold:"
+ " -0.25 is invalid, it must be greater than 0.0 and less than 1.0"
+ },
+ {"too big", 1.05,
+ "subnet configuration failed: cache-threshold:"
+ " 1.05 is invalid, it must be greater than 0.0 and less than 1.0"
+ }
+ };
+
+ // First we create a set of elements that provides all
+ // required for a Subnet6.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"interface\": \"\", \n"
+ " \"renew-timer\": 100, \n"
+ " \"rebind-timer\": 200, \n"
+ " \"valid-lifetime\": 300, \n"
+ " \"client-class\": \"\", \n"
+ " \"require-client-classes\": [] \n,"
+ " \"reservations-global\": false, \n"
+ " \"reservations-in-subnet\": true, \n"
+ " \"reservations-out-of-pool\": false \n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ // Iterate over the test scenarios, verifying each prescribed
+ // outcome.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE("test: " + (*test).label);
+
+ // Set this scenario's configuration parameters
+ elems->set("cache-threshold", data::Element::create((*test).threshold));
+
+ Subnet6Ptr subnet;
+ try {
+ // Attempt to parse the configuration.
+ Subnet6ConfigParser parser;
+ subnet = parser.parse(elems);
+ } catch (const std::exception& ex) {
+ if (!(*test).error_message.empty()) {
+ // We expected a failure, did we fail the correct way?
+ EXPECT_EQ((*test).error_message, ex.what());
+ } else {
+ // Should not have failed.
+ ADD_FAILURE() << "Scenario should not have failed: " << ex.what();
+ }
+
+ // Either way we're done with this scenario.
+ continue;
+ }
+
+ // We parsed correctly, make sure the values are right.
+ EXPECT_TRUE(util::areDoublesEquivalent((*test).threshold, subnet->getCacheThreshold()));
+ }
+ }
+}
+
+// This test verifies that the optional interface check works as expected.
+TEST(CfgSubnets6Test, iface) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/64\", \n"
+ " \"interface\": \"eth1961\"\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ // The interface check can be disabled.
+ Subnet6ConfigParser parser_no_check(false);
+ Subnet6Ptr subnet;
+ EXPECT_NO_THROW(subnet = parser_no_check.parse(elems));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getIface().unspecified());
+ EXPECT_EQ("eth1961", subnet->getIface().get());
+
+ // Retry with the interface check enabled.
+ Subnet6ConfigParser parser;
+ EXPECT_THROW(parser.parse(elems), DhcpConfigError);
+
+ // Configure default test interfaces.
+ IfaceMgrTestConfig config(true);
+
+ EXPECT_NO_THROW(subnet = parser_no_check.parse(elems));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getIface().unspecified());
+ EXPECT_EQ("eth1961", subnet->getIface().get());
+
+ EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ EXPECT_FALSE(subnet->getIface().unspecified());
+ EXPECT_EQ("eth1961", subnet->getIface().get());
+}
+
+// This test verifies that update statistics works as expected.
+TEST(CfgSubnets6Test, updateStatistics) {
+ CfgMgr::instance().clear();
+
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6();
+ ObservationPtr observation;
+ SubnetID subnet_id = 100;
+
+ LeaseMgrFactory::create("type=memfile universe=6 persist=false");
+
+ // remove all statistics
+ StatsMgr::instance().removeAll();
+
+ // Create subnet.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 100));
+
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), IOAddress("2001:db8:1::FF")));
+ subnet->addPool(pool);
+
+ pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"), 96, 112));
+ subnet->addPool(pool);
+
+ // Add subnet.
+ cfg->add(subnet);
+
+ observation = StatsMgr::instance().getObservation(
+ "cumulative-assigned-nas");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ "cumulative-assigned-pds");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ "declined-addresses");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-declined-addresses");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-leases");
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-nas"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-nas")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-pds"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "total-pds")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-nas"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-nas")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-pds"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "assigned-pds")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-nas"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-nas")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-pds"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "reclaimed-leases")));
+ ASSERT_FALSE(observation);
+
+ cfg->updateStatistics();
+
+ observation = StatsMgr::instance().getObservation(
+ "cumulative-assigned-nas");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ "cumulative-assigned-pds");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ "declined-addresses");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-declined-addresses");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ "reclaimed-leases");
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-nas"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(256, observation->getBigInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-nas")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(256, observation->getBigInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-pds"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(65536, observation->getBigInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "total-pds")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(65536, observation->getBigInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-nas"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-nas")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-pds"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "assigned-pds")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-nas"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-nas")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-pds"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "reclaimed-leases")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+}
+
+// This test verifies that remove statistics works as expected.
+TEST(CfgSubnets6Test, removeStatistics) {
+ CfgSubnets6 cfg;
+ ObservationPtr observation;
+ SubnetID subnet_id = 100;
+
+ // remove all statistics and then set them all to 0
+ StatsMgr::instance().removeAll();
+
+ // Create subnet.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 100));
+
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), IOAddress("2001:db8:1::FF")));
+ subnet->addPool(pool);
+
+ pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"), 96, 112));
+ subnet->addPool(pool);
+
+ // Add subnet.
+ cfg.add(subnet);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-nas"),
+ int128_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-nas"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getBigInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-nas")),
+ int128_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-nas")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getBigInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-pds"),
+ int128_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-pds"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getBigInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "total-pds")),
+ int128_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "total-pds")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getBigInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-nas"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-nas"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-nas")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-nas")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-pds"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-pds"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "assigned-pds")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "assigned-pds")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-nas"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-nas"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-nas")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-nas")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-pds"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-pds"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ StatsMgr::instance().setValue(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "reclaimed-leases")),
+ int64_t(0));
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "reclaimed-leases")));
+ ASSERT_TRUE(observation);
+ ASSERT_EQ(0, observation->getInteger().first);
+
+ // remove all statistics
+ cfg.removeStatistics();
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-nas"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "total-nas")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "total-pds"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "total-pds")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-nas"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "assigned-nas")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "assigned-pds"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "assigned-pds")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-nas"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "cumulative-assigned-nas")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "cumulative-assigned-pds"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "cumulative-assigned-pds")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-declined-addresses"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-declined-addresses")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ "reclaimed-leases"));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pool", 0, "reclaimed-leases")));
+ ASSERT_FALSE(observation);
+
+ observation = StatsMgr::instance().getObservation(
+ StatsMgr::generateName("subnet", subnet_id,
+ StatsMgr::generateName("pd-pool", 0, "reclaimed-leases")));
+ ASSERT_FALSE(observation);
+}
+
+// This test verifies that in range host reservation works as expected.
+TEST(CfgSubnets6Test, hostNA) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-addresses\": [\"2001:db8:1::1\"] } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet6ConfigParser parser;
+ Subnet6Ptr subnet;
+ EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(cfg_hosts);
+ HostCollection hosts = cfg_hosts->getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ ConstHostPtr host = hosts[0];
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv6SubnetID());
+ EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText());
+ IPv6ResrvRange addresses = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(addresses.first, addresses.second));
+ EXPECT_EQ("2001:db8:1::1", addresses.first->second.getPrefix().toText());
+
+ CfgMgr::instance().clear();
+}
+
+// This test verifies that an out of range host reservation is rejected.
+TEST(CfgSubnets6Test, outOfRangeHost) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"ip-addresses\": [\"2001:db8:1::1\", \n"
+ " \"2001:db8:2::1\"] } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet6ConfigParser parser;
+ std::string msg = "specified reservation '2001:db8:2::1' is ";
+ msg += "not within the IPv6 subnet '2001:db8:1::/48'";
+ EXPECT_THROW_MSG(parser.parse(elems), DhcpConfigError, msg);
+}
+
+// This test verifies that in range validation does not applies to prefixes.
+TEST(CfgSubnets6Test, hostPD) {
+ // Create a configuration.
+ std::string json =
+ " {"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/48\", \n"
+ " \"reservations\": [ {\n"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\", \n"
+ " \"prefixes\": [\"2001:db8:2::/64\"] } ]\n"
+ " }";
+
+ data::ElementPtr elems;
+ ASSERT_NO_THROW(elems = data::Element::fromJSON(json))
+ << "invalid JSON:" << json << "\n test is broken";
+
+ Subnet6ConfigParser parser;
+ Subnet6Ptr subnet;
+ try {
+ subnet = parser.parse(elems);
+ } catch (const std::exception& ex) {
+ std::cerr << "parse fail with " << ex.what() << std::endl;
+ }
+ //EXPECT_NO_THROW(subnet = parser.parse(elems));
+ ASSERT_TRUE(subnet);
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_TRUE(cfg_hosts);
+ HostCollection hosts = cfg_hosts->getAll6(SubnetID(1));
+ ASSERT_EQ(1, hosts.size());
+ ConstHostPtr host = hosts[0];
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv6SubnetID());
+ EXPECT_EQ("hwaddr=AABBCCDDEEFF", host->getIdentifierAsText());
+ IPv6ResrvRange prefixes = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+ EXPECT_EQ("2001:db8:2::", prefixes.first->second.getPrefix().toText());
+ EXPECT_EQ(64, prefixes.first->second.getPrefixLen());
+
+ // Verify the prefix is not in the subnet range.
+ EXPECT_FALSE(subnet->inRange(prefixes.first->second.getPrefix()));
+
+ CfgMgr::instance().clear();
+}
+
+// This test verifies that the getLinks tool works as expected.
+TEST(CfgSubnets6Test, getLinks) {
+ CfgSubnets6 cfg;
+
+ // Create 4 subnets.
+ Subnet6Ptr subnet0(new Subnet6(IOAddress("::"), 48, 1, 2, 3, 4,
+ SubnetID(111)));
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4,
+ SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 64, 1, 2, 3, 4,
+ SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 64, 1, 2, 3, 4,
+ SubnetID(3)));
+
+ // Add all subnets to the configuration.
+ ASSERT_NO_THROW(cfg.add(subnet0));
+ ASSERT_NO_THROW(cfg.add(subnet1));
+ ASSERT_NO_THROW(cfg.add(subnet2));
+ ASSERT_NO_THROW(cfg.add(subnet3));
+
+ // No 2001:db8:4:: subnet.
+ SubnetIDSet links;
+ uint8_t link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:4::"), link_len));
+ EXPECT_TRUE(links.empty());
+ EXPECT_EQ(111, link_len);
+
+ // A 2001:db8:2::/64 subnet.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::"), link_len));
+ SubnetIDSet expected = { 2 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(64, link_len);
+
+ // Check that any address in the subnet works.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::1234:5678:9abc:def0"),
+ link_len));
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(64, link_len);
+
+ // Check that an address outside the subnet does not work.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2:1::"), link_len));
+ EXPECT_TRUE(links.empty());
+ EXPECT_EQ(111, link_len);
+
+ // Add a second 2001:db8:2::/64 subnet.
+ Subnet6Ptr subnet10(new Subnet6(IOAddress("2001:db8:2::10"), 64, 1, 2, 3,
+ 4, SubnetID(10)));
+ ASSERT_NO_THROW(cfg.add(subnet10));
+
+ // Now we should get 2 subnets.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::"), link_len));
+ expected = { 2, 10 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(64, link_len);
+
+ // Add a larger subnet.
+ Subnet6Ptr subnet20(new Subnet6(IOAddress("2001:db8:2::20"), 56, 1, 2, 3,
+ 4, SubnetID(20)));
+ ASSERT_NO_THROW(cfg.add(subnet20));
+
+ // Now we should get 3 subnets and a smaller prefix length.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2::"), link_len));
+ expected = { 2, 10, 20 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(56, link_len);
+
+ // But only the larger subnet if the address is only in it.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("2001:db8:2:1::"), link_len));
+ expected = { 20 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(56, link_len);
+
+ // Even it is not used for Bulk Leasequery, it works for :: too.
+ links.clear();
+ link_len = 111;
+ EXPECT_NO_THROW(links = cfg.getLinks(IOAddress("::"), link_len));
+ expected = { 111 };
+ EXPECT_EQ(expected, links);
+ EXPECT_EQ(48, link_len);
+}
+
+// This test verifies that for each subnet in the configuration it calls
+// the registerLeaseMgrCallback function.
+TEST(CfgSubnets6Test, initAllocatorsAfterConfigure) {
+ CfgSubnets6 cfg;
+
+ // Create 4 subnets.
+ Subnet6Ptr subnet0(new Subnet6(IOAddress("2001:db8:1::"),
+ 64, 1, 2, 3, 4, SubnetID(111)));
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:2::"),
+ 64, 1, 2, 3, 4, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:3::"),
+ 64, 1, 2, 3, 4, SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:4::"),
+ 64, 1, 2, 3, 4, SubnetID(3)));
+
+ auto allocator0 = boost::make_shared<InitRecordingAllocator>(Lease::TYPE_NA, subnet0);
+ subnet0->setAllocator(Lease::TYPE_NA, allocator0);
+
+ auto allocator2 = boost::make_shared<InitRecordingAllocator>(Lease::TYPE_PD, subnet2);
+ subnet2->setAllocator(Lease::TYPE_PD, allocator2);
+
+ cfg.add(subnet0);
+ cfg.add(subnet1);
+ cfg.add(subnet2);
+ cfg.add(subnet3);
+
+ EXPECT_EQ(0, allocator0->callcount_);
+ EXPECT_EQ(0, allocator2->callcount_);
+
+ cfg.initAllocatorsAfterConfigure();
+
+ EXPECT_EQ(1, allocator0->callcount_);
+ EXPECT_EQ(1, allocator2->callcount_);
+}
+
+
+/// @brief Test fixture for parsing v6 Subnets that can verify log output.
+class Subnet6ParserTest : public LogContentTest {
+public:
+
+ /// @brief virtual destructor
+ virtual ~Subnet6ParserTest() = default;
+};
+
+// This test verifies that subnet parser for IPv6 works properly
+// when using invalid renew and rebind timers.
+TEST_F(Subnet6ParserTest, parseWithInvalidRenewRebind) {
+ std::string config =
+ "{\n"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:1::/64\",\n"
+ " \"valid-lifetime\": 399,\n"
+ " \"rebind-timer\": 199,\n"
+ " \"renew-timer\": 200\n"
+ "}";
+
+ // Basic configuration for subnet6 but with a renew-timer value
+ // larger than rebind-timer.
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ Subnet6ConfigParser parser(false);
+ Subnet6Ptr subnet;
+
+ // Parser should not throw.
+ ASSERT_NO_THROW(subnet = parser.parse(config_element));
+ ASSERT_TRUE(subnet);
+
+ // Veriy we emitted the proper log message.
+ addString("DHCPSRV_CFGMGR_RENEW_GTR_REBIND in subnet-id 1,"
+ " the value of renew-timer 200 is greater than the value"
+ " of rebind-timer 199, ignoring renew-timer");
+ EXPECT_TRUE(checkFile());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
new file mode 100644
index 0000000..47f9cff
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -0,0 +1,1124 @@
+// Copyright (C) 2012-2023 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 <dhcp/dhcp6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <process/logging_info.h>
+#include <stats/stats_mgr.h>
+#include <util/chrono_time_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+using namespace isc::stats;
+using namespace isc::process;
+using namespace isc;
+
+namespace {
+
+template <typename Storage>
+bool isZeroPosition(const Storage& storage, const std::string& param_name) {
+ Element::Position position = storage.getPosition(param_name);
+ return ((position.line_ == 0) && (position.pos_ == 0) &&
+ (position.file_.empty()));
+}
+
+// This test verifies that BooleanStorage functions properly.
+TEST(ValueStorageTest, BooleanTesting) {
+ BooleanStorage testStore;
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstBool", false, Element::Position("kea.conf", 123, 234));
+ testStore.setParam("secondBool", true, Element::Position("keax.conf", 10, 20));
+
+ EXPECT_FALSE(testStore.getParam("firstBool"));
+ EXPECT_TRUE(testStore.getParam("secondBool"));
+
+ EXPECT_EQ(123, testStore.getPosition("firstBool").line_);
+ EXPECT_EQ(234, testStore.getPosition("firstBool").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("firstBool").file_);
+
+ EXPECT_EQ(10, testStore.getPosition("secondBool").line_);
+ EXPECT_EQ(20, testStore.getPosition("secondBool").pos_);
+ EXPECT_EQ("keax.conf", testStore.getPosition("secondBool").file_);
+
+ // Verify that we can update parameters.
+ testStore.setParam("firstBool", true, Element::Position("keax.conf", 555, 111));
+ testStore.setParam("secondBool", false, Element::Position("kea.conf", 1, 3));
+
+ EXPECT_TRUE(testStore.getParam("firstBool"));
+ EXPECT_FALSE(testStore.getParam("secondBool"));
+
+ EXPECT_EQ(555, testStore.getPosition("firstBool").line_);
+ EXPECT_EQ(111, testStore.getPosition("firstBool").pos_);
+ EXPECT_EQ("keax.conf", testStore.getPosition("firstBool").file_);
+
+ EXPECT_EQ(1, testStore.getPosition("secondBool").line_);
+ EXPECT_EQ(3, testStore.getPosition("secondBool").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("secondBool").file_);
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstBool");
+ EXPECT_THROW(testStore.getParam("firstBool"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "firstBool"));
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_FALSE(testStore.getParam("secondBool"));
+
+ EXPECT_EQ(1, testStore.getPosition("secondBool").line_);
+ EXPECT_EQ(3, testStore.getPosition("secondBool").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("secondBool").file_);
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "bogusBool"));
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusBool"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondBool"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "secondBool"));
+}
+
+// This test verifies that Uint32Storage functions properly.
+TEST(ValueStorageTest, Uint32Testing) {
+ Uint32Storage testStore;
+
+ uint32_t int_one = 77;
+ uint32_t int_two = 33;
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstInt", int_one, Element::Position("kea.conf", 123, 234));
+ testStore.setParam("secondInt", int_two, Element::Position("keax.conf", 10, 20));
+
+ EXPECT_EQ(testStore.getParam("firstInt"), int_one);
+ EXPECT_EQ(testStore.getParam("secondInt"), int_two);
+
+ EXPECT_EQ(123, testStore.getPosition("firstInt").line_);
+ EXPECT_EQ(234, testStore.getPosition("firstInt").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("firstInt").file_);
+
+ EXPECT_EQ(10, testStore.getPosition("secondInt").line_);
+ EXPECT_EQ(20, testStore.getPosition("secondInt").pos_);
+ EXPECT_EQ("keax.conf", testStore.getPosition("secondInt").file_);
+
+ // Verify that we can update parameters.
+ testStore.setParam("firstInt", --int_one, Element::Position("keax.conf", 555, 111));
+ testStore.setParam("secondInt", ++int_two, Element::Position("kea.conf", 1, 3));
+
+ EXPECT_EQ(testStore.getParam("firstInt"), int_one);
+ EXPECT_EQ(testStore.getParam("secondInt"), int_two);
+
+ EXPECT_EQ(555, testStore.getPosition("firstInt").line_);
+ EXPECT_EQ(111, testStore.getPosition("firstInt").pos_);
+ EXPECT_EQ("keax.conf", testStore.getPosition("firstInt").file_);
+
+ EXPECT_EQ(1, testStore.getPosition("secondInt").line_);
+ EXPECT_EQ(3, testStore.getPosition("secondInt").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("secondInt").file_);
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstInt");
+ EXPECT_THROW(testStore.getParam("firstInt"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "firstInt"));
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_EQ(testStore.getParam("secondInt"), int_two);
+
+ EXPECT_EQ(1, testStore.getPosition("secondInt").line_);
+ EXPECT_EQ(3, testStore.getPosition("secondInt").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("secondInt").file_);
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusInt"));
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "bogusInt"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "secondInt"));
+}
+
+// This test verifies that StringStorage functions properly.
+TEST(ValueStorageTest, StringTesting) {
+ StringStorage testStore;
+
+ std::string string_one = "seventy-seven";
+ std::string string_two = "thirty-three";
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstString", string_one,
+ Element::Position("kea.conf", 123, 234));
+ testStore.setParam("secondString", string_two,
+ Element::Position("keax.conf", 10, 20));
+
+ EXPECT_EQ(testStore.getParam("firstString"), string_one);
+ EXPECT_EQ(testStore.getParam("secondString"), string_two);
+
+ EXPECT_EQ(123, testStore.getPosition("firstString").line_);
+ EXPECT_EQ(234, testStore.getPosition("firstString").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("firstString").file_);
+
+ EXPECT_EQ(10, testStore.getPosition("secondString").line_);
+ EXPECT_EQ(20, testStore.getPosition("secondString").pos_);
+ EXPECT_EQ("keax.conf", testStore.getPosition("secondString").file_);
+
+ // Verify that we can update parameters.
+ string_one.append("-boo");
+ string_two.append("-boo");
+
+ testStore.setParam("firstString", string_one,
+ Element::Position("kea.conf", 555, 111));
+ testStore.setParam("secondString", string_two,
+ Element::Position("keax.conf", 1, 3));
+
+ EXPECT_EQ(testStore.getParam("firstString"), string_one);
+ EXPECT_EQ(testStore.getParam("secondString"), string_two);
+
+ EXPECT_EQ(555, testStore.getPosition("firstString").line_);
+ EXPECT_EQ(111, testStore.getPosition("firstString").pos_);
+ EXPECT_EQ("kea.conf", testStore.getPosition("firstString").file_);
+
+ EXPECT_EQ(1, testStore.getPosition("secondString").line_);
+ EXPECT_EQ(3, testStore.getPosition("secondString").pos_);
+ EXPECT_EQ("keax.conf", testStore.getPosition("secondString").file_);
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstString");
+ EXPECT_THROW(testStore.getParam("firstString"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "firstString"));
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_EQ(testStore.getParam("secondString"), string_two);
+
+ EXPECT_EQ(1, testStore.getPosition("secondString").line_);
+ EXPECT_EQ(3, testStore.getPosition("secondString").pos_);
+ EXPECT_EQ("keax.conf", testStore.getPosition("secondString").file_);
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusString"));
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "bogusString"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondString"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the "zero" position is returned when parameter doesn't exist.
+ EXPECT_TRUE(isZeroPosition(testStore, "secondString"));
+}
+
+
+
+class CfgMgrTest : public ::testing::Test {
+public:
+ CfgMgrTest() {
+ // make sure we start with a clean configuration
+ original_datadir_ = CfgMgr::instance().getDataDir();
+ clear();
+ }
+
+ /// @brief generates interface-id option based on provided text
+ ///
+ /// @param text content of the option to be created
+ ///
+ /// @return pointer to the option object created
+ OptionPtr generateInterfaceId(const string& text) {
+ OptionBuffer buffer(text.begin(), text.end());
+ return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
+ }
+
+ ~CfgMgrTest() {
+ // clean up after the test
+ clear();
+ }
+
+ void clear() {
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgMgr::instance().setDataDir(original_datadir_);
+ CfgMgr::instance().clear();
+ LeaseMgrFactory::destroy();
+ }
+
+ /// @brief Creates instance of the backend.
+ ///
+ /// @param family AF_INET for v4, AF_INET6 for v6
+ void startBackend(int family = AF_INET) {
+ try {
+ std::ostringstream s;
+ s << "type=memfile persist=false " << (family == AF_INET6 ?
+ "universe=6" : "universe=4");
+ LeaseMgrFactory::create(s.str());
+ } catch (const std::exception& ex) {
+ std::cerr << "*** ERROR: unable to create instance of the Memfile"
+ " lease database backend: " << ex.what() << std::endl;
+ throw;
+ }
+ CfgMgr::instance().setFamily(family);
+ }
+
+ /// used in client classification (or just empty container for other tests)
+ isc::dhcp::ClientClasses classify_;
+
+private:
+ /// to restore it in destructor.
+ string original_datadir_;
+};
+
+// Checks that there is a configuration structure available and that
+// it is empty by default.
+TEST_F(CfgMgrTest, configuration) {
+
+ ConstSrvConfigPtr configuration = CfgMgr::instance().getCurrentCfg();
+ ASSERT_TRUE(configuration);
+ EXPECT_TRUE(configuration->getLoggingInfo().empty());
+
+ configuration = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(configuration);
+ EXPECT_TRUE(configuration->getLoggingInfo().empty());
+}
+
+// This test checks the data directory handling.
+TEST_F(CfgMgrTest, dataDir) {
+ // It is only in DHCPv6 syntax so switch to IPv6.
+ CfgMgr::instance().setFamily(AF_INET6);
+
+ // Default.
+ EXPECT_TRUE(CfgMgr::instance().getDataDir().unspecified());
+ ConstElementPtr json = CfgMgr::instance().getCurrentCfg()->toElement();
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ ConstElementPtr dhcp = json->get("Dhcp6");
+ ASSERT_TRUE(dhcp);
+ ASSERT_EQ(Element::map, dhcp->getType());
+ ConstElementPtr datadir = dhcp->get("data-directory");
+ EXPECT_FALSE(datadir);
+
+ // Set but not specified.
+ CfgMgr::instance().setDataDir("/tmp");
+ EXPECT_TRUE(CfgMgr::instance().getDataDir().unspecified());
+ EXPECT_EQ("/tmp", string(CfgMgr::instance().getDataDir()));
+ json = CfgMgr::instance().getCurrentCfg()->toElement();
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ dhcp = json->get("Dhcp6");
+ ASSERT_TRUE(dhcp);
+ ASSERT_EQ(Element::map, dhcp->getType());
+ datadir = dhcp->get("data-directory");
+ EXPECT_FALSE(datadir);
+
+ // Set and specified.
+ CfgMgr::instance().setDataDir("/tmp", false);
+ EXPECT_FALSE(CfgMgr::instance().getDataDir().unspecified());
+ EXPECT_EQ("/tmp", string(CfgMgr::instance().getDataDir()));
+ json = CfgMgr::instance().getCurrentCfg()->toElement();
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ dhcp = json->get("Dhcp6");
+ ASSERT_TRUE(dhcp);
+ ASSERT_EQ(Element::map, dhcp->getType());
+ datadir = dhcp->get("data-directory");
+ ASSERT_TRUE(datadir);
+ ASSERT_EQ(Element::string, datadir->getType());
+ EXPECT_EQ("/tmp", datadir->stringValue());
+
+ // Still IPv6 only.
+ CfgMgr::instance().setFamily(AF_INET);
+ EXPECT_FALSE(CfgMgr::instance().getDataDir().unspecified());
+ EXPECT_EQ("/tmp", string(CfgMgr::instance().getDataDir()));
+ json = CfgMgr::instance().getCurrentCfg()->toElement();
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ dhcp = json->get("Dhcp4");
+ ASSERT_TRUE(dhcp);
+ ASSERT_EQ(Element::map, dhcp->getType());
+ datadir = dhcp->get("data-directory");
+ EXPECT_FALSE(datadir);
+}
+
+// This test checks the D2ClientMgr wrapper methods.
+TEST_F(CfgMgrTest, d2ClientConfig) {
+ // After CfgMgr construction, D2ClientMgr member should be initialized
+ // with a D2 configuration that is disabled.
+ // Verify we can Fetch the mgr.
+ D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+ EXPECT_FALSE(d2_mgr.ddnsEnabled());
+
+ // Make sure the convenience method fetches the config correctly.
+ D2ClientConfigPtr original_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(original_config);
+ EXPECT_FALSE(original_config->getEnableUpdates());
+
+ // Verify that we cannot set the configuration to an empty pointer.
+ D2ClientConfigPtr new_cfg;
+ ASSERT_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg), D2ClientError);
+
+ // Create a new, enabled configuration.
+ ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ isc::asiolink::IOAddress("127.0.0.1"), 478,
+ 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+
+ // Verify that we can assign a new, non-empty configuration.
+ ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(new_cfg));
+
+ // Verify that we can fetch the newly assigned configuration.
+ D2ClientConfigPtr updated_config = CfgMgr::instance().getD2ClientConfig();
+ ASSERT_TRUE(updated_config);
+ EXPECT_TRUE(updated_config->getEnableUpdates());
+
+ // Make sure convenience method agrees with updated configuration.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Make sure the configuration we fetched is the one we assigned,
+ // and not the original configuration.
+ EXPECT_EQ(*new_cfg, *updated_config);
+ EXPECT_NE(*original_config, *updated_config);
+
+ // Revert to default configuration.
+ ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(original_config));
+}
+
+// This test verifies that the configuration staging, commit and rollback works
+// as expected.
+TEST_F(CfgMgrTest, staging) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Initially, the current configuration is a default one. We are going
+ // to get the current configuration a couple of times and make sure
+ // that always the same instance is returned.
+ ConstSrvConfigPtr const_config;
+ for (int i = 0; i < 5; ++i) {
+ const_config = cfg_mgr.getCurrentCfg();
+ ASSERT_TRUE(const_config) << "Returned NULL current configuration"
+ " for iteration " << i;
+ EXPECT_EQ(0, const_config->getSequence())
+ << "Returned invalid sequence number "
+ << const_config->getSequence() << " for iteration " << i;
+ }
+
+ // Try to get the new staging configuration. When getStagingCfg() is called
+ // for the first time the new instance of the staging configuration is
+ // returned. This instance is returned for every call to getStagingCfg()
+ // until commit is called.
+ SrvConfigPtr config;
+ for (int i = 0; i < 5; ++i) {
+ config = cfg_mgr.getStagingCfg();
+ ASSERT_TRUE(config) << "Returned NULL staging configuration for"
+ " iteration " << i;
+ // The sequence id is 1 for staging because it is ahead of current
+ // configuration having sequence number 0.
+ EXPECT_EQ(1, config->getSequence()) << "Returned invalid sequence"
+ " number " << config->getSequence() << " for iteration " << i;
+ }
+
+ // This should change the staging configuration so as it becomes a current
+ // one.
+ auto before = boost::posix_time::second_clock::universal_time();
+ cfg_mgr.commit();
+ auto after = boost::posix_time::second_clock::universal_time();
+ const_config = cfg_mgr.getCurrentCfg();
+ ASSERT_TRUE(const_config);
+ // Sequence id equal to 1 indicates that the current configuration points
+ // to the configuration that used to be a staging configuration previously.
+ EXPECT_EQ(1, const_config->getSequence());
+ // Last commit timestamp should be between before and after.
+ auto reload = const_config->getLastCommitTime();
+ ASSERT_FALSE(reload.is_not_a_date_time());
+ EXPECT_LE(before, reload);
+ EXPECT_GE(after, reload);
+
+ // Create a new staging configuration. It should be assigned a new
+ // sequence id.
+ config = cfg_mgr.getStagingCfg();
+ ASSERT_TRUE(config);
+ EXPECT_EQ(2, config->getSequence());
+
+ // Let's execute commit a couple of times. The first invocation to commit
+ // changes the configuration having sequence 2 to current configuration.
+ // Other commits are no-op.
+ for (int i = 0; i < 5; ++i) {
+ cfg_mgr.commit();
+ }
+
+ // The current configuration now have sequence number 2.
+ const_config = cfg_mgr.getCurrentCfg();
+ ASSERT_TRUE(const_config);
+ EXPECT_EQ(2, const_config->getSequence());
+
+ // Clear configuration along with a history.
+ cfg_mgr.clear();
+
+ // After clearing configuration we should successfully get the
+ // new staging configuration.
+ config = cfg_mgr.getStagingCfg();
+ ASSERT_TRUE(config);
+ EXPECT_EQ(1, config->getSequence());
+
+ // Modify the staging configuration.
+ config->addLoggingInfo(LoggingInfo());
+ ASSERT_TRUE(config);
+ // The modified staging configuration should have one logger configured.
+ ASSERT_EQ(1, config->getLoggingInfo().size());
+
+ // Rollback should remove a staging configuration, including the logger.
+ ASSERT_NO_THROW(cfg_mgr.rollback());
+
+ // Make sure that the logger is not set. This is an indication that the
+ // rollback worked.
+ config = cfg_mgr.getStagingCfg();
+ ASSERT_TRUE(config);
+ EXPECT_EQ(0, config->getLoggingInfo().size());
+}
+
+// This test verifies that it is possible to revert to an old configuration.
+TEST_F(CfgMgrTest, revert) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Let's create 5 unique configurations: differing by a debug level in the
+ // range of 10 to 14.
+ for (int i = 0; i < 5; ++i) {
+ SrvConfigPtr config = cfg_mgr.getStagingCfg();
+ LoggingInfo logging_info;
+ logging_info.debuglevel_ = i + 10;
+ config->addLoggingInfo(logging_info);
+ cfg_mgr.commit();
+ }
+
+ // Now we have 6 configurations with:
+ // - debuglevel = 99 (a default one)
+ // - debuglevel = 10
+ // - debuglevel = 11
+ // - debuglevel = 12
+ // - debuglevel = 13
+ // - debuglevel = 14 (current)
+
+ // Hence, the maximum index of the configuration to revert is 5 (which
+ // points to the configuration with debuglevel = 99). For the index greater
+ // than 5 we should get an exception.
+ ASSERT_THROW(cfg_mgr.revert(6), isc::OutOfRange);
+ // Value of 0 also doesn't make sense.
+ ASSERT_THROW(cfg_mgr.revert(0), isc::OutOfRange);
+
+ // We should be able to revert to configuration with debuglevel = 10.
+ ASSERT_NO_THROW(cfg_mgr.revert(4));
+ // And this configuration should be now the current one and the debuglevel
+ // of this configuration is 10.
+ EXPECT_EQ(10, cfg_mgr.getCurrentCfg()->getLoggingInfo()[0].debuglevel_);
+ EXPECT_NE(cfg_mgr.getCurrentCfg()->getSequence(), 1);
+
+ // The new set of configuration is now as follows:
+ // - debuglevel = 99
+ // - debuglevel = 10
+ // - debuglevel = 11
+ // - debuglevel = 12
+ // - debuglevel = 13
+ // - debuglevel = 14
+ // - debuglevel = 10 (current)
+ // So, reverting to configuration having index 3 means that the debug level
+ // of the current configuration will become 12.
+ ASSERT_NO_THROW(cfg_mgr.revert(3));
+ EXPECT_EQ(12, cfg_mgr.getCurrentCfg()->getLoggingInfo()[0].debuglevel_);
+}
+
+// This test verifies that the address family can be set and obtained
+// from the configuration manager.
+TEST_F(CfgMgrTest, family) {
+ ASSERT_EQ(AF_INET, CfgMgr::instance().getFamily());
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ ASSERT_EQ(AF_INET6, CfgMgr::instance().getFamily());
+
+ CfgMgr::instance().setFamily(AF_INET);
+ EXPECT_EQ(AF_INET, CfgMgr::instance().getFamily());
+}
+
+// This test verifies that once the configuration is committed, statistics
+// are updated appropriately.
+TEST_F(CfgMgrTest, commitStats4) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ startBackend(AF_INET);
+
+ // Let's prepare the "old" configuration: a subnet with id 123
+ // and pretend there were addresses assigned, so statistics are non-zero.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 123));
+ CfgSubnets4Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4();
+ subnets->add(subnet1);
+ cfg_mgr.commit();
+ stats_mgr.addValue("subnet[123].total-addresses", int64_t(256));
+ stats_mgr.setValue("subnet[123].assigned-addresses", static_cast<int64_t>(150));
+
+ // Now, let's change the configuration to something new.
+
+ // There's a subnet 192.1.2.0/24 with ID=42
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 42));
+
+ // Let's make pools with 128 addresses available in total.
+ PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 26)); // 64 addrs
+ PoolPtr pool2(new Pool4(IOAddress("192.1.2.64"), 26)); // 64 addrs
+ subnet2->addPool(pool1);
+ subnet2->addPool(pool2);
+
+ subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4();
+ subnets->add(subnet2);
+
+ // Change the stats default limits.
+ cfg_mgr.getStagingCfg()->addConfiguredGlobal("statistic-default-sample-count",
+ Element::create(15));
+ cfg_mgr.getStagingCfg()->addConfiguredGlobal("statistic-default-sample-age",
+ Element::create(2));
+
+ // Let's commit it
+ cfg_mgr.commit();
+
+ EXPECT_EQ(15, stats_mgr.getMaxSampleCountDefault());
+ EXPECT_EQ("00:00:02", durationToText(stats_mgr.getMaxSampleAgeDefault(), 0));
+
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].total-addresses"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].assigned-addresses"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].total-addresses"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].assigned-addresses"));
+
+ ObservationPtr total_addrs;
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].total-addresses"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(128, total_addrs->getInteger().first);
+ EXPECT_TRUE(total_addrs->getMaxSampleCount().first);
+ EXPECT_EQ(15, total_addrs->getMaxSampleCount().second);
+ EXPECT_FALSE(total_addrs->getMaxSampleAge().first);
+ EXPECT_EQ("00:00:02", durationToText(total_addrs->getMaxSampleAge().second, 0));
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].pool[0].total-addresses"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(128, total_addrs->getInteger().first);
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].assigned-addresses"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(0, total_addrs->getInteger().first);
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].pool[0].assigned-addresses"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(0, total_addrs->getInteger().first);
+}
+
+// This test verifies that once the configuration is merged into the current
+// configuration, statistics are updated appropriately.
+TEST_F(CfgMgrTest, mergeIntoCurrentStats4) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ startBackend(AF_INET);
+
+ // Let's prepare the "old" configuration: a subnet with id 123
+ // and pretend there were addresses assigned, so statistics are non-zero.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 123));
+ CfgSubnets4Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4();
+ subnets->add(subnet1);
+ cfg_mgr.commit();
+ stats_mgr.addValue("subnet[123].total-addresses", int64_t(256));
+ stats_mgr.setValue("subnet[123].assigned-addresses", static_cast<int64_t>(150));
+
+ // There should be no stats for subnet 42 at this point.
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[42].total-addresses"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[42].assigned-addresses"));
+
+ // Now, let's create new configuration with updates.
+
+ // There's a subnet 192.1.3.0/24 with ID=42
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.1.3.0"), 24, 1, 2, 3, 42));
+
+ // Let's make a pool with 128 addresses available.
+ PoolPtr pool(new Pool4(IOAddress("192.1.3.0"), 25)); // 128 addrs
+ subnet2->addPool(pool);
+
+ // Create external configuration to be merged into current one.
+ auto external_cfg = CfgMgr::instance().createExternalCfg();
+ subnets = external_cfg->getCfgSubnets4();
+ subnets->add(subnet2);
+
+ external_cfg->addConfiguredGlobal("statistic-default-sample-count",
+ Element::create(16));
+ external_cfg->addConfiguredGlobal("statistic-default-sample-age",
+ Element::create(3));
+
+ // Let's merge it.
+ cfg_mgr.mergeIntoCurrentCfg(external_cfg->getSequence());
+
+ // The stats should have been updated and so we should be able to get
+ // observations for subnet 42.
+ EXPECT_EQ(16, stats_mgr.getMaxSampleCountDefault());
+ EXPECT_EQ("00:00:03", durationToText(stats_mgr.getMaxSampleAgeDefault(), 0));
+
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[42].total-addresses"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[42].assigned-addresses"));
+
+ // And also for 123
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].total-addresses"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].assigned-addresses"));
+
+ ObservationPtr total_addrs;
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].total-addresses"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(128, total_addrs->getInteger().first);
+}
+
+// This test verifies that once the configuration is cleared, the statistics
+// are removed.
+TEST_F(CfgMgrTest, clearStats4) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ StatsMgr& stats_mgr = StatsMgr::instance();
+
+ // Let's prepare the "old" configuration: a subnet with id 123
+ // and pretend there were addresses assigned, so statistics are non-zero.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 123));
+
+ // Let's make pools with 128 addresses available in total.
+ PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 26)); // 64 addrs
+ PoolPtr pool2(new Pool4(IOAddress("192.1.2.64"), 26)); // 64 addrs
+ subnet1->addPool(pool1);
+ subnet1->addPool(pool2);
+
+ CfgSubnets4Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets4();
+ subnets->add(subnet1);
+ cfg_mgr.commit();
+ stats_mgr.setValue("subnet[123].assigned-addresses", static_cast<int64_t>(150));
+ stats_mgr.setValue("subnet[123].pool[0].assigned-addresses", static_cast<int64_t>(150));
+
+ // The stats should be there.
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].assigned-addresses"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].pool[0].assigned-addresses"));
+
+ // Let's remove all configurations
+ cfg_mgr.clear();
+
+ // The stats should not be there anymore.
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].total-addresses"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].assigned-addresses"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].total-addresses"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].assigned-addresses"));
+}
+
+// This test verifies that once the configuration is committed, statistics
+// are updated appropriately.
+TEST_F(CfgMgrTest, commitStats6) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ startBackend(AF_INET6);
+
+ // Let's prepare the "old" configuration: a subnet with id 123
+ // and pretend there were addresses assigned, so statistics are non-zero.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 123));
+ CfgSubnets6Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets6();
+ subnets->add(subnet1);
+ cfg_mgr.commit();
+ stats_mgr.addValue("subnet[123].total-nas", int128_t(256));
+ stats_mgr.setValue("subnet[123].assigned-nas", static_cast<int64_t>(150));
+
+ stats_mgr.addValue("subnet[123].total-pds", int128_t(256));
+ stats_mgr.setValue("subnet[123].assigned-pds", static_cast<int64_t>(150));
+
+ // Now, let's change the configuration to something new.
+
+ // There's a subnet 2001:db8:2::/48 with ID=42
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, 42));
+
+ // Let's make pools with 128 addresses in total and 65536 prefixes available in total.
+ PoolPtr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"), 122)); // 64 addrs
+ PoolPtr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::1:0"), 122)); // 64 addrs
+ PoolPtr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:3::"), 97, 112)); // 32768 prefixes
+ PoolPtr pool4(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:3::8000:0"), 97, 112)); // 32768 prefixes
+ subnet2->addPool(pool1);
+ subnet2->addPool(pool2);
+ subnet2->addPool(pool3);
+ subnet2->addPool(pool4);
+
+ subnets = cfg_mgr.getStagingCfg()->getCfgSubnets6();
+ subnets->add(subnet2);
+
+ // Change the stats default limits.
+ cfg_mgr.getStagingCfg()->addConfiguredGlobal("statistic-default-sample-count",
+ Element::create(14));
+ cfg_mgr.getStagingCfg()->addConfiguredGlobal("statistic-default-sample-age",
+ Element::create(10));
+
+ // Let's commit it
+ cfg_mgr.commit();
+
+ EXPECT_EQ(14, stats_mgr.getMaxSampleCountDefault());
+ EXPECT_EQ("00:00:10", durationToText(stats_mgr.getMaxSampleAgeDefault(), 0));
+
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].total-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].assigned-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].total-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].assigned-nas"));
+
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].total-pds"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].assigned-pds"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pd-pool[0].total-pds"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pd-pool[0].assigned-pds"));
+
+ ObservationPtr total_addrs;
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].total-nas"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(128, total_addrs->getBigInteger().first);
+ EXPECT_TRUE(total_addrs->getMaxSampleCount().first);
+ EXPECT_EQ(14, total_addrs->getMaxSampleCount().second);
+ EXPECT_FALSE(total_addrs->getMaxSampleAge().first);
+ EXPECT_EQ("00:00:10", durationToText(total_addrs->getMaxSampleAge().second, 0));
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].pool[0].total-nas"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(128, total_addrs->getBigInteger().first);
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].assigned-nas"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(0, total_addrs->getInteger().first);
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].pool[0].assigned-nas"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(0, total_addrs->getInteger().first);
+
+ ObservationPtr total_prfx;
+ EXPECT_NO_THROW(total_prfx = stats_mgr.getObservation("subnet[42].total-pds"));
+ ASSERT_TRUE(total_prfx);
+ EXPECT_EQ(65536, total_prfx->getBigInteger().first);
+ EXPECT_TRUE(total_prfx->getMaxSampleCount().first);
+ EXPECT_EQ(14, total_prfx->getMaxSampleCount().second);
+ EXPECT_FALSE(total_prfx->getMaxSampleAge().first);
+ EXPECT_EQ("00:00:10", durationToText(total_prfx->getMaxSampleAge().second, 0));
+ EXPECT_NO_THROW(total_prfx = stats_mgr.getObservation("subnet[42].pd-pool[0].total-pds"));
+ ASSERT_TRUE(total_prfx);
+ EXPECT_EQ(65536, total_prfx->getBigInteger().first);
+ EXPECT_NO_THROW(total_prfx = stats_mgr.getObservation("subnet[42].assigned-pds"));
+ ASSERT_TRUE(total_prfx);
+ EXPECT_EQ(0, total_prfx->getInteger().first);
+ EXPECT_NO_THROW(total_prfx = stats_mgr.getObservation("subnet[42].pd-pool[0].assigned-pds"));
+ ASSERT_TRUE(total_prfx);
+ EXPECT_EQ(0, total_prfx->getInteger().first);
+}
+
+// This test verifies that once the configuration is merged into the current
+// configuration, statistics are updated appropriately.
+TEST_F(CfgMgrTest, mergeIntoCurrentStats6) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ StatsMgr& stats_mgr = StatsMgr::instance();
+ startBackend(AF_INET6);
+
+ // Let's prepare the "old" configuration: a subnet with id 123
+ // and pretend there were addresses assigned, so statistics are non-zero.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4, 123));
+ CfgSubnets6Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets6();
+ subnets->add(subnet1);
+ cfg_mgr.commit();
+ stats_mgr.addValue("subnet[123].total-nas", static_cast<int128_t>(256));
+ stats_mgr.setValue("subnet[123].assigned-nas", static_cast<int64_t>(150));
+
+ stats_mgr.addValue("subnet[123].total-pds", static_cast<int128_t>(256));
+ stats_mgr.setValue("subnet[123].assigned-pds", static_cast<int64_t>(150));
+
+ // There should be no stats for subnet 42 at this point.
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[42].total-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[42].assigned-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[42].total-pds"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[42].assigned-pds"));
+
+ // Now, let's create new configuration with updates.
+
+ // There's a subnet 2001:db8:2::/48 with ID=42
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, 42));
+
+ // Let's make pools with 128 addresses and 65536 prefixes available.
+ PoolPtr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"), 121)); // 128 addrs
+ PoolPtr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:3::"), 96, 112)); // 65536 prefixes
+ subnet2->addPool(pool1);
+ subnet2->addPool(pool2);
+
+ // Create external configuration to be merged into current one.
+ auto external_cfg = CfgMgr::instance().createExternalCfg();
+ subnets = external_cfg->getCfgSubnets6();
+ subnets->add(subnet2);
+
+ external_cfg->addConfiguredGlobal("statistic-default-sample-count",
+ Element::create(17));
+ external_cfg->addConfiguredGlobal("statistic-default-sample-age",
+ Element::create(4));
+
+ // Let's merge it.
+ cfg_mgr.mergeIntoCurrentCfg(external_cfg->getSequence());
+
+ // The stats should have been updated and so we should be able to get
+ // observations for subnet 42.
+ EXPECT_EQ(17, stats_mgr.getMaxSampleCountDefault());
+ EXPECT_EQ("00:00:04", durationToText(stats_mgr.getMaxSampleAgeDefault(), 0));
+
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[42].total-nas"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[42].assigned-nas"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[42].total-pds"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[42].assigned-pds"));
+
+ // And also for 123
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].total-nas"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].assigned-nas"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].total-pds"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].assigned-pds"));
+
+ ObservationPtr total_addrs;
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].total-nas"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(128, total_addrs->getBigInteger().first);
+
+ EXPECT_NO_THROW(total_addrs = stats_mgr.getObservation("subnet[42].total-pds"));
+ ASSERT_TRUE(total_addrs);
+ EXPECT_EQ(65536, total_addrs->getBigInteger().first);
+}
+
+// This test verifies that once the configuration is cleared, the v6 statistics
+// are removed.
+TEST_F(CfgMgrTest, clearStats6) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ StatsMgr& stats_mgr = StatsMgr::instance();
+
+ // Let's prepare the "old" configuration: a subnet with id 123
+ // and pretend there were addresses assigned, so statistics are non-zero.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4, 123));
+
+ // Let's make pools with 128 addresses in total and 65536 prefixes available in total.
+ PoolPtr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"), 122)); // 64 addrs
+ PoolPtr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2:0:0:0:1:0"), 122)); // 64 addrs
+ PoolPtr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:3::"), 97, 112)); // 32768 prefixes
+ PoolPtr pool4(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:3::8000:0"), 97, 112)); // 32768 prefixes
+ subnet1->addPool(pool1);
+ subnet1->addPool(pool2);
+ subnet1->addPool(pool3);
+ subnet1->addPool(pool4);
+
+ CfgSubnets6Ptr subnets = cfg_mgr.getStagingCfg()->getCfgSubnets6();
+ subnets->add(subnet1);
+ cfg_mgr.commit();
+ stats_mgr.setValue("subnet[123].assigned-nas", static_cast<int64_t>(150));
+ stats_mgr.setValue("subnet[123].pool[0].assigned-nas", static_cast<int64_t>(150));
+ stats_mgr.setValue("subnet[123].assigned-pds", static_cast<int64_t>(150));
+ stats_mgr.setValue("subnet[123].pd-pool[0].assigned-pds", static_cast<int64_t>(150));
+
+ // The stats should be there.
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].assigned-nas"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].pool[0].assigned-nas"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].assigned-pds"));
+ EXPECT_TRUE(stats_mgr.getObservation("subnet[123].pd-pool[0].assigned-pds"));
+
+ // Let's remove all configurations
+ cfg_mgr.clear();
+
+ // The stats should not be there anymore.
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].total-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].assigned-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].total-nas"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pool[0].assigned-nas"));
+
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].total-pds"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].assigned-pds"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pd-pool[0].total-pds"));
+ EXPECT_FALSE(stats_mgr.getObservation("subnet[123].pd-pool[0].assigned-pds"));
+}
+
+// This test verifies that the external configuration can be merged into
+// the staging configuration via CfgMgr.
+TEST_F(CfgMgrTest, mergeIntoStagingCfg) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create first external configuration.
+ SrvConfigPtr ext_cfg1;
+ ASSERT_NO_THROW(ext_cfg1 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg1);
+ // It should pick the first available sequence number.
+ EXPECT_EQ(0, ext_cfg1->getSequence());
+
+ // Create second external configuration.
+ SrvConfigPtr ext_cfg2;
+ ASSERT_NO_THROW(ext_cfg2 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg2);
+ // It should pick the next available sequence number.
+ EXPECT_EQ(1, ext_cfg2->getSequence());
+
+ // Those must be two separate instances.
+ ASSERT_FALSE(ext_cfg1 == ext_cfg2);
+
+ // Add a subnet which will be merged from first configuration.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 123));
+ ext_cfg1->getCfgSubnets4()->add(subnet1);
+
+ // Add a subnet which will be merged from the second configuration.
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.1.3.0"), 24, 1, 2, 3, 124));
+ ext_cfg2->getCfgSubnets4()->add(subnet2);
+
+ // Merge first configuration.
+ ASSERT_NO_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg1->getSequence()));
+ // Second attempt should fail because the configuration is discarded after
+ // the merge.
+ ASSERT_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg1->getSequence()), BadValue);
+
+ // Check that the subnet from first configuration has been merged but not
+ // from the second configuration.
+ ASSERT_TRUE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(123));
+ ASSERT_FALSE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+ // Create another configuration instance to check what sequence it would
+ // pick. It should pick the first available one.
+ SrvConfigPtr ext_cfg3;
+ ASSERT_NO_THROW(ext_cfg3 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg3);
+ EXPECT_EQ(2, ext_cfg3->getSequence());
+
+ // Merge the second and third (empty) configuration.
+ ASSERT_NO_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg2->getSequence()));
+ ASSERT_NO_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg3->getSequence()));
+
+ // Make sure that both subnets have been merged.
+ ASSERT_TRUE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(123));
+ ASSERT_TRUE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+ // The next configuration instance should reset the sequence to 0 because
+ // there are no other configurations in CfgMgr.
+ SrvConfigPtr ext_cfg4;
+ ASSERT_NO_THROW(ext_cfg4 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg4);
+ EXPECT_EQ(0, ext_cfg4->getSequence());
+
+ // Try to commit the staging configuration.
+ ASSERT_NO_THROW(cfg_mgr.commit());
+
+ // Make sure that both subnets are present in the current configuration.
+ EXPECT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(123));
+ EXPECT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+ // The staging configuration should not include them.
+ EXPECT_FALSE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(123));
+ EXPECT_FALSE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(124));
+}
+
+// This test verifies that the external configuration can be merged into
+// the current configuration via CfgMgr.
+TEST_F(CfgMgrTest, mergeIntoCurrentCfg) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create first external configuration.
+ SrvConfigPtr ext_cfg1;
+ ASSERT_NO_THROW(ext_cfg1 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg1);
+ // It should pick the first available sequence number.
+ EXPECT_EQ(0, ext_cfg1->getSequence());
+
+ // Create second external configuration.
+ SrvConfigPtr ext_cfg2;
+ ASSERT_NO_THROW(ext_cfg2 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg2);
+ // It should pick the next available sequence number.
+ EXPECT_EQ(1, ext_cfg2->getSequence());
+
+ // Those must be two separate instances.
+ ASSERT_FALSE(ext_cfg1 == ext_cfg2);
+
+ // Add a subnet which will be merged from first configuration.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 123));
+ ext_cfg1->getCfgSubnets4()->add(subnet1);
+
+ // Add a subnet which will be merged from the second configuration.
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.1.3.0"), 24, 1, 2, 3, 124));
+ ext_cfg2->getCfgSubnets4()->add(subnet2);
+
+ // Merge first configuration.
+ ASSERT_NO_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg1->getSequence()));
+ // Second attempt should fail because the configuration is discarded after
+ // the merge.
+ ASSERT_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg1->getSequence()), BadValue);
+
+ // Check that the subnet from first configuration has been merged but not
+ // from the second configuration.
+ ASSERT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(123));
+ ASSERT_FALSE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+ // Create another configuration instance to check what sequence it would
+ // pick. It should pick the first available one.
+ SrvConfigPtr ext_cfg3;
+ ASSERT_NO_THROW(ext_cfg3 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg3);
+ EXPECT_EQ(2, ext_cfg3->getSequence());
+
+ // Merge the second and third (empty) configuration.
+ ASSERT_NO_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg2->getSequence()));
+ ASSERT_NO_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg3->getSequence()));
+
+ // Make sure that both subnets have been merged.
+ ASSERT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(123));
+ ASSERT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+ // The next configuration instance should reset the sequence to 0 because
+ // there are no other configurations in CfgMgr.
+ SrvConfigPtr ext_cfg4;
+ ASSERT_NO_THROW(ext_cfg4 = cfg_mgr.createExternalCfg());
+ ASSERT_TRUE(ext_cfg4);
+ EXPECT_EQ(0, ext_cfg4->getSequence());
+}
+
+/// @todo Add unit-tests for testing:
+/// - addActiveIface() with invalid interface name
+/// - addActiveIface() with the same interface twice
+/// - addActiveIface() with a bogus address
+///
+/// This is somewhat tricky. Care should be taken here, because it is rather
+/// difficult to decide if interface name is valid or not. Some servers, e.g.
+/// dibbler, allow to specify interface names that are not currently present in
+/// the system. The server accepts them, but upon discovering that they are
+/// yet available (for different definitions of not being available), adds
+/// the to to-be-activated list.
+///
+/// Cases covered by dibbler are:
+/// - missing interface (e.g. PPP connection that is not established yet)
+/// - downed interface (no link local address, no way to open sockets)
+/// - up, but not running interface (wifi up, but not associated)
+/// - tentative addresses (interface up and running, but DAD procedure is
+/// still in progress)
+/// - weird interfaces without link-local addresses (don't ask, 6rd tunnels
+/// look weird to me as well)
+
+// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
+// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
+// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
new file mode 100644
index 0000000..7d0cf79
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
@@ -0,0 +1,2091 @@
+// Copyright (C) 2015-2023 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 <cc/data.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <asiolink/io_address.h>
+#include <eval/evaluate.h>
+#include <testutils/gtest_utils.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <stdint.h>
+#include <string>
+
+/// @file client_class_def_parser_unittest.cc Unit tests for client class
+/// definition parsing.
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for @c ExpressionParser.
+class ExpressionParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Test that validate expression can be evaluated against v4 or
+ /// v6 packet.
+ ///
+ /// Verifies that given a valid expression, the ExpressionParser
+ /// produces an Expression which can be evaluated against a v4 or v6
+ /// packet.
+ ///
+ /// @param family AF_INET or AF_INET6
+ /// @param expression Textual representation of the expression to be
+ /// evaluated.
+ /// @param option_string String data to be placed in the hostname
+ /// option, being placed in the packet used for evaluation.
+ /// @param parser_type the expected type of the evaluated expression.
+ /// @tparam Type of the packet: @c Pkt4 or @c Pkt6.
+ template<typename PktType>
+ void testValidExpression(uint16_t family,
+ const std::string& expression,
+ const std::string& option_string,
+ EvalContext::ParserType parser_type = EvalContext::PARSER_BOOL) {
+ ExpressionPtr parsed_expr;
+ ExpressionParser parser;
+
+ // Turn config into elements. This may emit exceptions.
+ ElementPtr config_element = Element::fromJSON(expression);
+
+ // Expression should parse.
+ ASSERT_NO_THROW(parser.parse(parsed_expr, config_element, family, EvalContext::acceptAll, parser_type));
+
+ // Parsed expression should exist.
+ ASSERT_TRUE(parsed_expr);
+
+ // Build a packet that will fail evaluation.
+ uint8_t message_type;
+ if (family == AF_INET) {
+ message_type = DHCPDISCOVER;
+ } else {
+ message_type = DHCPV6_SOLICIT;
+ }
+ boost::shared_ptr<PktType> pkt(new PktType(message_type, 123));
+ if (parser_type == EvalContext::PARSER_BOOL) {
+ EXPECT_FALSE(evaluateBool(*parsed_expr, *pkt));
+ } else {
+ EXPECT_TRUE(evaluateString(*parsed_expr, *pkt).empty());
+ }
+
+ // Now add the option so it will pass. Use a standard option carrying a
+ // single string value, i.e. hostname for DHCPv4 and bootfile url for
+ // DHCPv6.
+ Option::Universe universe(family == AF_INET ? Option::V4 : Option::V6);
+ uint16_t option_type;
+ if (family == AF_INET) {
+ option_type = DHO_HOST_NAME;
+ } else {
+ option_type = D6O_BOOTFILE_URL;
+ }
+ OptionPtr opt(new OptionString(universe, option_type, option_string));
+ pkt->addOption(opt);
+ if (parser_type == EvalContext::PARSER_BOOL) {
+ EXPECT_TRUE(evaluateBool(*parsed_expr, *pkt));
+ } else {
+ EXPECT_EQ(evaluateString(*parsed_expr, *pkt), option_string);
+ }
+ }
+};
+
+/// @brief Test fixture class for @c ClientClassDefParser.
+class ClientClassDefParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Convenience method for parsing a configuration
+ ///
+ /// Attempt to parse a given client class definition.
+ ///
+ /// @param config - JSON string containing the client class configuration
+ /// to parse.
+ /// @param family - the address family in which the parsing should
+ /// occur.
+ /// @return Returns a pointer to class instance created, or NULL if
+ /// for some unforeseen reason it wasn't created in the local dictionary
+ /// @throw indirectly, exceptions converting the JSON text to elements,
+ /// or by the parsing itself are not caught
+ ClientClassDefPtr parseClientClassDef(const std::string& config,
+ uint16_t family) {
+ // Create local dictionary to which the parser add the class.
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+
+ // Turn config into elements. This may emit exceptions.
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration. This may emit exceptions.
+ ClientClassDefParser parser;
+ parser.parse(dictionary, config_element, family);
+
+ // If we didn't throw, then return the first and only class
+ ClientClassDefListPtr classes = dictionary->getClasses();
+ ClientClassDefList::const_iterator it = classes->cbegin();
+ if (it != classes->cend()) {
+ return (*it);
+ }
+
+ // Return NULL if for some reason the class doesn't exist.
+ return (ClientClassDefPtr());
+ }
+
+ /// @brief Test that client class parser throws when unspported parameter
+ /// is specified.
+ ///
+ /// @param config JSON string containing the client class configuration.
+ /// @param family The address family indicating whether the DHCPv4 or
+ /// DHCPv6 client class is parsed.
+ void testClassParamsUnsupported(const std::string& config,
+ const uint16_t family) {
+ ElementPtr config_element = Element::fromJSON(config);
+
+ ClientClassDefParser parser;
+ EXPECT_THROW(parser.checkParametersSupported(config_element, family),
+ DhcpConfigError);
+ }
+};
+
+/// @brief Test fixture class for @c ClientClassDefListParser.
+class ClientClassDefListParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Convenience method for parsing a list of client class
+ /// definitions.
+ ///
+ /// Attempt to parse a given list of client class definitions into a
+ /// ClientClassDictionary.
+ ///
+ /// @param config - JSON string containing the list of definitions to parse.
+ /// @param family - the address family in which the parsing should
+ /// occur.
+ /// @param check_dependencies - indicates if the parser should check whether
+ /// referenced classes exist.
+ /// @return Returns a pointer to class dictionary created
+ /// @throw indirectly, exceptions converting the JSON text to elements,
+ /// or by the parsing itself are not caught
+ ClientClassDictionaryPtr parseClientClassDefList(const std::string& config,
+ uint16_t family,
+ bool check_dependencies = true)
+ {
+ // Turn config into elements. This may emit exceptions.
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration. This may emit exceptions.
+ ClientClassDefListParser parser;
+ return (parser.parse(config_element, family, check_dependencies));
+ }
+};
+
+// Verifies that given a valid expression, the ExpressionParser
+// produces an Expression which can be evaluated against a v4 packet.
+TEST_F(ExpressionParserTest, validExpression4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].text == 'hundred4'\"",
+ "hundred4");
+}
+
+// Verifies that given a valid expression, the ExpressionParser
+// produces an Expression which can be evaluated against a v4 packet.
+TEST_F(ExpressionParserTest, templateValidExpression4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].text\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
+// Verifies that the option name can be used in the evaluated expression.
+TEST_F(ExpressionParserTest, validExpressionWithOptionName4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[host-name].text == 'hundred4'\"",
+ "hundred4");
+}
+
+// Verifies that the option name can be used in the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionName4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[host-name].text\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
+// Verifies that given a valid expression using .hex operator for option, the
+// ExpressionParser produces an Expression which can be evaluated against
+// a v4 packet.
+TEST_F(ExpressionParserTest, validExpressionWithHex4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].hex == 0x68756E6472656434\"",
+ "hundred4");
+}
+
+// Verifies that given a valid expression using .hex operator for option, the
+// ExpressionParser produces an Expression which can be evaluated against
+// a v4 packet.
+TEST_F(ExpressionParserTest, templateValidExpressionWithHex4) {
+ testValidExpression<Pkt4>(AF_INET,
+ "\"option[12].hex\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
+// Verifies that the option name can be used together with .hex operator in
+// the evaluated expression.
+TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex4) {
+ testValidExpression<Pkt6>(AF_INET,
+ "\"option[host-name].text == 0x68756E6472656434\"",
+ "hundred4");
+}
+
+// Verifies that the option name can be used together with .hex operator in
+// the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionNameAndHex4) {
+ testValidExpression<Pkt6>(AF_INET,
+ "\"option[host-name].text\"",
+ "hundred4", EvalContext::PARSER_STRING);
+}
+
+// Verifies that given a valid expression, the ExpressionParser
+// produces an Expression which can be evaluated against a v6 packet.
+TEST_F(ExpressionParserTest, validExpression6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].text == 'hundred6'\"",
+ "hundred6");
+}
+
+// Verifies that given a valid expression, the ExpressionParser
+// produces an Expression which can be evaluated against a v6 packet.
+TEST_F(ExpressionParserTest, templateValidExpression6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].text\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
+// Verifies that the option name can be used in the evaluated expression.
+TEST_F(ExpressionParserTest, validExpressionWithOptionName6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[bootfile-url].text == 'hundred6'\"",
+ "hundred6");
+}
+
+// Verifies that the option name can be used in the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionName6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[bootfile-url].text\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
+// Verifies that given a valid expression using .hex operator for option, the
+// ExpressionParser produces an Expression which can be evaluated against
+// a v6 packet.
+TEST_F(ExpressionParserTest, validExpressionWithHex6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].hex == 0x68756E6472656436\"",
+ "hundred6");
+}
+
+// Verifies that given a valid expression using .hex operator for option, the
+// ExpressionParser produces an Expression which can be evaluated against
+// a v6 packet.
+TEST_F(ExpressionParserTest, templateValidExpressionWithHex6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[59].hex\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
+// Verifies that the option name can be used together with .hex operator in
+// the evaluated expression.
+TEST_F(ExpressionParserTest, validExpressionWithOptionNameAndHex6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[bootfile-url].text == 0x68756E6472656436\"",
+ "hundred6");
+}
+
+// Verifies that the option name can be used together with .hex operator in
+// the evaluated expression.
+TEST_F(ExpressionParserTest, templateValidExpressionWithOptionNameAndHex6) {
+ testValidExpression<Pkt6>(AF_INET6,
+ "\"option[bootfile-url].text\"",
+ "hundred6", EvalContext::PARSER_STRING);
+}
+
+// Verifies that an the ExpressionParser only accepts StringElements.
+TEST_F(ExpressionParserTest, invalidExpressionElement) {
+ // This will create an integer element should fail.
+ std::string cfg_txt = "777";
+ ElementPtr config_element = Element::fromJSON(cfg_txt);
+
+ // Create the parser.
+ ExpressionPtr parsed_expr;
+ ExpressionParser parser;
+
+ // Expression parsing should fail.
+ ASSERT_THROW(parser.parse(parsed_expr, config_element, AF_INET6),
+ DhcpConfigError);
+}
+
+// Verifies that given an invalid expression with a syntax error,
+// the Expression parser will throw a DhdpConfigError. Note this
+// is not intended to be an exhaustive test or expression syntax.
+// It is simply to ensure that if the parser fails, it does so
+// Properly.
+TEST_F(ExpressionParserTest, expressionSyntaxError) {
+ // Turn config into elements.
+ std::string cfg_txt = "\"option 'bogus'\"";
+ ElementPtr config_element = Element::fromJSON(cfg_txt);
+
+ // Create the parser.
+ ExpressionPtr parsed_expr;
+ ExpressionParser parser;
+
+ // Expression parsing should fail.
+ ASSERT_THROW(parser.parse(parsed_expr, config_element, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that the name parameter is required and must not be empty
+TEST_F(ExpressionParserTest, nameEmpty) {
+ std::string cfg_txt = "{ \"name\": \"\" }";
+ ElementPtr config_element = Element::fromJSON(cfg_txt);
+
+ // Create the parser.
+ ExpressionPtr parsed_expr;
+ ExpressionParser parser;
+
+ // Expression parsing should fail.
+ ASSERT_THROW(parser.parse(parsed_expr, config_element, AF_INET6),
+ DhcpConfigError);
+}
+
+// Verifies that the function checking if specified client class parameters
+// are supported does not throw if all specified DHCPv4 client class
+// parameters are recognized.
+TEST_F(ClientClassDefParserTest, checkAllSupported4) {
+ std::string cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"option-def\": [ ],\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false,\n"
+ " \"valid-lifetime\": 1000,\n"
+ " \"min-valid-lifetime\": 1000,\n"
+ " \"max-valid-lifetime\": 1000,\n"
+ " \"next-server\": \"192.0.2.3\",\n"
+ " \"template-test\": \"\",\n"
+ " \"server-hostname\": \"myhost\",\n"
+ " \"boot-file-name\": \"efi\""
+ "}\n";
+
+ ElementPtr config_element = Element::fromJSON(cfg_text);
+
+ ClientClassDefParser parser;
+ EXPECT_NO_THROW(parser.checkParametersSupported(config_element, AF_INET));
+}
+
+// Verifies that the function checking if specified client class parameters
+// are supported does not throw if all specified DHCPv6 client class
+// parameters are recognized.
+TEST_F(ClientClassDefParserTest, checkAllSupported6) {
+ std::string cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false,\n"
+ " \"template-test\": \"\",\n"
+ " \"preferred-lifetime\": 800,\n"
+ " \"min-preferred-lifetime\": 800,\n"
+ " \"max-preferred-lifetime\": 800\n"
+ "}\n";
+
+ ElementPtr config_element = Element::fromJSON(cfg_text);
+
+ ClientClassDefParser parser;
+ EXPECT_NO_THROW(parser.checkParametersSupported(config_element, AF_INET6));
+}
+
+// Verifies that the function checking if specified client class parameters
+// are supported throws if DHCPv4 specific parameters are specified for the
+// DHCPv6 client class.
+TEST_F(ClientClassDefParserTest, checkParams4Unsupported6) {
+ std::string cfg_text;
+
+ {
+ SCOPED_TRACE("option-def");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"option-def\": [ ],\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET6);
+ }
+
+ {
+ SCOPED_TRACE("next-server");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false,\n"
+ " \"next-server\": \"192.0.2.3\"\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET6);
+ }
+
+ {
+ SCOPED_TRACE("server-hostname");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false,\n"
+ " \"server-hostname\": \"myhost\"\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET6);
+ }
+
+ {
+ SCOPED_TRACE("boot-file-name");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false,\n"
+ " \"boot-file-name\": \"efi\""
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET6);
+ }
+}
+
+// Verifies that the function checking if specified client class parameters
+// are supported throws if DHCPv6 specific parameters are specified for the
+// DHCPv4 client class.
+TEST_F(ClientClassDefParserTest, checkParams6Unsupported4) {
+ std::string cfg_text;
+
+ {
+ SCOPED_TRACE("preferred-lifetime");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"preferred-lifetime\": 800,\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET);
+ }
+
+ {
+ SCOPED_TRACE("min-preferred-lifetime");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"min-preferred-lifetime\": 800,\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET);
+ }
+
+ {
+ SCOPED_TRACE("max-preferred-lifetime");
+ cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"test\": \"member('ALL')\","
+ " \"max-preferred-lifetime\": 800,\n"
+ " \"option-data\": [ ],\n"
+ " \"user-context\": { },\n"
+ " \"only-if-required\": false\n"
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET);
+ }
+}
+
+// Verifies that the function checking if specified DHCPv4 client class
+// parameters are supported throws if one of the parameters is not recognized.
+TEST_F(ClientClassDefParserTest, checkParams4Unsupported) {
+ std::string cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"unsupported\": \"member('ALL')\""
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET);
+}
+
+// Verifies that the function checking if specified DHCPv6 client class
+// parameters are supported throws if one of the parameters is not recognized.
+TEST_F(ClientClassDefParserTest, checkParams6Unsupported) {
+ std::string cfg_text =
+ "{\n"
+ " \"name\": \"foo\","
+ " \"unsupported\": \"member('ALL')\""
+ "}\n";
+
+ testClassParamsUnsupported(cfg_text, AF_INET6);
+}
+
+// Verifies you can create a class with only a name
+// Whether that's useful or not, remains to be seen.
+// For now the class allows it.
+TEST_F(ClientClassDefParserTest, nameOnlyValid) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we have no expression.
+ ASSERT_FALSE(cclass->getMatchExpr());
+}
+
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, nameAndExpressionClass4) {
+
+ std::string test = "option[100].text == 'works right'";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"class_one\", \n"
+ " \"test\": \"" + test + "\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("class_one", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt4));
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V4, 100, "works right"));
+ pkt4->addOption(opt);
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
+}
+
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, templateNameAndExpressionClass4) {
+
+ std::string test = "option[100].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"class_one\", \n"
+ " \"template-test\": \"" + test + "\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("class_one", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP4_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt4).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V4, 100, "works right"));
+ pkt4->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt4), "works right");
+}
+
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, nameAndExpressionClass6) {
+
+ std::string test = "option[59].text == 'works right'";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"class_one\", \n"
+ " \"test\": \"" + test + "\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("class_one", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP6_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt6));
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "works right"));
+ pkt6->addOption(opt);
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt6));
+}
+
+// Verifies you can create a class with a name, expression,
+// but no options.
+TEST_F(ClientClassDefParserTest, templateNameAndExpressionClass6) {
+
+ std::string test = "option[59].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"class_one\", \n"
+ " \"template-test\": \"" + test + "\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("class_one", cclass->getName());
+
+ // CfgOption should be a non-null pointer but there
+ // should be no options. Currently there's no good
+ // way to test that there no options.
+ CfgOptionPtr cfg_option;
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionContainerPtr oc;
+ ASSERT_TRUE(oc = cclass->getCfgOption()->getAll(DHCP6_OPTION_SPACE));
+ EXPECT_EQ(0, oc->size());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt6).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "works right"));
+ pkt6->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt6), "works right");
+}
+
+// Verifies you can create a class with a name and options,
+// but no expression.
+TEST_F(ClientClassDefParserTest, nameAndOptionsClass4) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(6, od.option_->getType());
+
+ // Verify we have no expression
+ ASSERT_FALSE(cclass->getMatchExpr());
+}
+
+// Verifies you can create a class with a name and options,
+// but no expression.
+TEST_F(ClientClassDefParserTest, nameAndOptionsClass6) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"code\": 22, \n"
+ " \"space\": \"dhcp6\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP6_OPTION_SPACE, 22);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(22, od.option_->getType());
+
+ // Verify we have no expression
+ ASSERT_FALSE(cclass->getMatchExpr());
+}
+
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, basicValidClass4) {
+
+ std::string test = "option[100].text == 'booya'";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"" + test + "\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(6, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt4));
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V4, 100, "booya"));
+ pkt4->addOption(opt);
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt4));
+}
+
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, templateBasicValidClass4) {
+
+ std::string test = "option[100].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"template-test\": \"" + test + "\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 6);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(6, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt4).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V4, 100, "booya"));
+ pkt4->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt4), "booya");
+}
+
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, basicValidClass6) {
+
+ std::string test = "option[59].text == 'booya'";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"" + test + "\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"code\": 22, \n"
+ " \"space\": \"dhcp6\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP6_OPTION_SPACE, 22);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(22, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_FALSE(evaluateBool(*match_expr, *pkt6));
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "booya"));
+ pkt6->addOption(opt);
+ EXPECT_TRUE(evaluateBool(*match_expr, *pkt6));
+}
+
+// Verifies you can create a class with a name, expression,
+// and options.
+TEST_F(ClientClassDefParserTest, templateBasicValidClass6) {
+
+ std::string test = "option[59].text";
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"template-test\": \"" + test + "\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"code\": 22, \n"
+ " \"space\": \"dhcp6\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("MICROSOFT", cclass->getName());
+
+ // Our one option should exist.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP6_OPTION_SPACE, 22);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(22, od.option_->getType());
+
+ // Verify we can retrieve the expression
+ ExpressionPtr match_expr = cclass->getMatchExpr();
+ ASSERT_TRUE(match_expr);
+
+ // Verify the original expression was saved.
+ EXPECT_EQ(test, cclass->getTest());
+
+ // Build a packet that will fail evaluation.
+ Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+ EXPECT_TRUE(evaluateString(*match_expr, *pkt6).empty());
+
+ // Now add the option so it will pass.
+ OptionPtr opt(new OptionString(Option::V6, 59, "booya"));
+ pkt6->addOption(opt);
+ EXPECT_EQ(evaluateString(*match_expr, *pkt6), "booya");
+}
+
+// Verifies that a class with no name, fails to parse.
+TEST_F(ClientClassDefParserTest, noClassName) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"test\": \"option[123].text == 'abc'\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that a class with a blank name, fails to parse.
+TEST_F(ClientClassDefParserTest, blankClassName) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"\", \n"
+ " \"test\": \"option[123].text == 'abc'\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": true, \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that a class with an invalid expression, fails to parse.
+TEST_F(ClientClassDefParserTest, invalidExpression) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"test\": 777 \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6),
+ DhcpConfigError);
+}
+
+// Verifies that a class with an invalid template expression, fails to parse.
+TEST_F(ClientClassDefParserTest, templateInvalidExpression) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"template-test\": 777 \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6),
+ DhcpConfigError);
+}
+
+// Verifies that a class with an empty expression, fails to parse.
+TEST_F(ClientClassDefParserTest, emptyExpression) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6),
+ DhcpConfigError);
+}
+
+// Verifies that a class with an empty expression, fails to parse.
+TEST_F(ClientClassDefParserTest, templateEmptyExpression) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"template-test\": \"\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6),
+ DhcpConfigError);
+}
+
+// Verifies that a class with both test and template-test expressions, fails to parse.
+TEST_F(ClientClassDefParserTest, bothTestAndTemplateTestExpressions) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"true\", \n"
+ " \"template-test\": \"\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that a class with invalid option-def, fails to parse.
+TEST_F(ClientClassDefParserTest, invalidOptionDef) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"option-def\": [ \n"
+ " { \"bogus\": \"bad\" } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that a class with invalid option-data, fails to parse.
+TEST_F(ClientClassDefParserTest, invalidOptionData) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"option-data\": [ \n"
+ " { \"bogus\": \"bad\" } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that a valid list of client classes will parse.
+TEST_F(ClientClassDefListParserTest, simpleValidList) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"three\" \n"
+ " } \n"
+ "] \n";
+
+ // Parsing the list should succeed.
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET6));
+ ASSERT_TRUE(dictionary);
+
+ // We should have three classes in the dictionary.
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+
+ // Make sure we can find all three.
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("one", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("two", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("three", cclass->getName());
+
+ // For good measure, make sure we can't find a non-existent class.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+}
+
+// Verifies that a valid list of client classes will parse.
+TEST_F(ClientClassDefListParserTest, templateSimpleValidList) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"template-test\": \"''\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-test\": \"''\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"three\", \n"
+ " \"template-test\": \"''\" \n"
+ " } \n"
+ "] \n";
+
+ // Parsing the list should succeed.
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET6));
+ ASSERT_TRUE(dictionary);
+
+ // We should have three classes in the dictionary.
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+
+ // Make sure we can find all three.
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("one", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("two", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("three", cclass->getName());
+
+ // For good measure, make sure we can't find a non-existent class.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+}
+
+// Verifies that class list containing a duplicate class entries, fails
+// to parse.
+TEST_F(ClientClassDefListParserTest, duplicateClass) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " } \n"
+ "] \n";
+
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Verifies that class list containing a duplicate class entries, fails
+// to parse.
+TEST_F(ClientClassDefListParserTest, templateDuplicateClass) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"template-test\": \"''\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-test\": \"''\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\", \n"
+ " \"template-test\": \"''\" \n"
+ " } \n"
+ "] \n";
+
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Test verifies that without any class specified, the fixed fields have their
+// default, empty value.
+TEST_F(ClientClassDefParserTest, noFixedFields4) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And it should not have any fixed fields set
+ EXPECT_EQ(IOAddress("0.0.0.0"), cclass->getNextServer());
+ EXPECT_EQ(0, cclass->getSname().size());
+ EXPECT_EQ(0, cclass->getFilename().size());
+ EXPECT_TRUE(cclass->getOfferLft().unspecified());
+
+ // Nor option definitions
+ CfgOptionDefPtr cfg = cclass->getCfgOptionDef();
+ ASSERT_TRUE(cfg->getAll(DHCP4_OPTION_SPACE)->empty());
+}
+
+// Test verifies that without any class specified, the fixed fields have their
+// default, empty value.
+TEST_F(ClientClassDefParserTest, noFixedFields6) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"sip-server-addr\", \n"
+ " \"data\": \"2003:db8::1, 2003:db8::2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET6));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And it should not have any fixed fields set
+ EXPECT_EQ(0, cclass->getPreferred().get());
+ EXPECT_EQ(0, cclass->getPreferred().getMin());
+ EXPECT_EQ(0, cclass->getPreferred().getMax());
+
+ // Nor option definitions
+ CfgOptionDefPtr cfg = cclass->getCfgOptionDef();
+ ASSERT_TRUE(cfg->getAll(DHCP6_OPTION_SPACE)->empty());
+}
+
+// Test verifies option-def for a bad option fails to parse.
+TEST_F(ClientClassDefParserTest, badOptionDef) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-def\": [ \n"
+ " { \n"
+ " \"name\": \"foo\", \n"
+ " \"code\": 222, \n"
+ " \"type\": \"uint32\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
+ DhcpConfigError);
+}
+
+// Test verifies option-def works for private options (224-254).
+TEST_F(ClientClassDefParserTest, privateOptionDef) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"option-def\": [ \n"
+ " { \n"
+ " \"name\": \"foo\", \n"
+ " \"code\": 232, \n"
+ " \"type\": \"uint32\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And the option definition.
+ CfgOptionDefPtr cfg = cclass->getCfgOptionDef();
+ ASSERT_TRUE(cfg);
+ EXPECT_TRUE(cfg->get(DHCP4_OPTION_SPACE, 232));
+ EXPECT_FALSE(cfg->get(DHCP6_OPTION_SPACE, 232));
+ EXPECT_FALSE(cfg->get(DHCP4_OPTION_SPACE, 233));
+}
+
+// Test verifies option-def works for option 43.
+TEST_F(ClientClassDefParserTest, option43Def) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"test\": \"option[60].text == 'MICROSOFT'\", \n"
+ " \"option-def\": [ \n"
+ " { \n"
+ " \"name\": \"vendor-encapsulated-options\", \n"
+ " \"code\": 43, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"type\": \"empty\", \n"
+ " \"encapsulate\": \"vsi\" \n"
+ " } \n"
+ " ], \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"vendor-encapsulated-options\" \n"
+ " }, \n"
+ " { \n"
+ " \"code\": 1, \n"
+ " \"space\": \"vsi\", \n"
+ " \"csv-format\": false, \n"
+ " \"data\": \"C0000200\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And the option definition.
+ CfgOptionDefPtr cfg_def = cclass->getCfgOptionDef();
+ ASSERT_TRUE(cfg_def);
+ EXPECT_TRUE(cfg_def->get(DHCP4_OPTION_SPACE, 43));
+
+ // Verify the option data.
+ OptionDescriptor od = cclass->getCfgOption()->get(DHCP4_OPTION_SPACE, 43);
+ ASSERT_TRUE(od.option_);
+ EXPECT_EQ(43, od.option_->getType());
+ const OptionCollection& oc = od.option_->getOptions();
+ ASSERT_EQ(1, oc.size());
+ OptionPtr opt = od.option_->getOption(1);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(1, opt->getType());
+ ASSERT_EQ(4, opt->getData().size());
+ const uint8_t expected[4] = { 0xc0, 0x00, 0x02, 0x00 };
+ EXPECT_EQ(0, std::memcmp(expected, &opt->getData()[0], 4));
+}
+
+// Test verifies that it is possible to define next-server field and it
+// is actually set in the class properly.
+TEST_F(ClientClassDefParserTest, nextServer) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"next-server\": \"192.0.2.254\",\n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And it should have next-server set, but everything else not set.
+ EXPECT_EQ(IOAddress("192.0.2.254"), cclass->getNextServer());
+ EXPECT_EQ(0, cclass->getSname().size());
+ EXPECT_EQ(0, cclass->getFilename().size());
+ EXPECT_TRUE(cclass->getOfferLft().unspecified());
+}
+
+// Test verifies that the parser rejects bogus next-server value.
+TEST_F(ClientClassDefParserTest, nextServerBogus) {
+
+ std::string bogus_v6 =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"next-server\": \"2001:db8::1\",\n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+ std::string bogus_junk =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"next-server\": \"not-an-address\",\n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+ std::string bogus_broadcast =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"next-server\": \"255.255.255.255\",\n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ EXPECT_THROW(parseClientClassDef(bogus_v6, AF_INET), DhcpConfigError);
+ EXPECT_THROW(parseClientClassDef(bogus_junk, AF_INET), DhcpConfigError);
+ EXPECT_THROW(parseClientClassDef(bogus_broadcast, AF_INET), DhcpConfigError);
+}
+
+// Test verifies that it is possible to define server-hostname field and it
+// is actually set in the class properly.
+TEST_F(ClientClassDefParserTest, serverName) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"server-hostname\": \"hal9000\",\n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And it should not have any fixed fields set
+ std::string exp_sname("hal9000");
+
+ EXPECT_EQ(exp_sname, cclass->getSname());
+}
+
+// Test verifies that the parser rejects bogus server-hostname value.
+TEST_F(ClientClassDefParserTest, serverNameInvalid) {
+
+ std::string cfg_too_long =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"server-hostname\": \"1234567890123456789012345678901234567890"
+ "1234567890123456789012345\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ EXPECT_THROW(parseClientClassDef(cfg_too_long, AF_INET), DhcpConfigError);
+}
+
+// Test verifies that it is possible to define boot-file-name field and it
+// is actually set in the class properly.
+TEST_F(ClientClassDefParserTest, filename) {
+
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"boot-file-name\": \"ipxe.efi\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ // And it should not have any fixed fields set
+ std::string exp_filename("ipxe.efi");
+ EXPECT_EQ(exp_filename, cclass->getFilename());
+}
+
+// Test verifies that the parser rejects bogus boot-file-name value.
+TEST_F(ClientClassDefParserTest, filenameBogus) {
+
+ // boot-file-name is allowed up to 128 bytes, this one is 129.
+ std::string cfg_too_long =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"boot-file-name\": \"1234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890"
+ "1234567890123456789012345678901234567890"
+ "123456789\", \n"
+ " \"option-data\": [ \n"
+ " { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"data\": \"192.0.2.1, 192.0.2.2\" \n"
+ " } \n"
+ " ] \n"
+ "} \n";
+
+ EXPECT_THROW(parseClientClassDef(cfg_too_long, AF_INET), DhcpConfigError);
+}
+
+// Verifies that backward and built-in dependencies will parse.
+TEST_F(ClientClassDefListParserTest, dependentList) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('VENDOR_CLASS_foo')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"three\", \n"
+ " \"test\": \"member('two')\" \n"
+ " } \n"
+ "] \n";
+
+ // Parsing the list should succeed.
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET));
+ ASSERT_TRUE(dictionary);
+
+ // We should have three classes in the dictionary.
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+
+ // Make sure we can find all three.
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("one", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("two", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("three", cclass->getName());
+}
+
+// Verifies that not defined dependencies will not parse.
+TEST_F(ClientClassDefListParserTest, dependentNotDefined) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('foo')\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+}
+
+// Verifies that error is not reported when a class references another
+// not defined class, but dependency checking is disabled.
+TEST_F(ClientClassDefListParserTest, dependencyCheckingDisabled) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('foo')\" \n"
+ " } \n"
+ "] \n";
+ try {
+ parseClientClassDefList(cfg_text, AF_INET6, false);
+ } catch ( const std::exception& ex) {
+ std::cout << ex.what() << std::endl;
+ }
+ EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6, false));
+}
+
+// Verifies that forward dependencies will not parse.
+TEST_F(ClientClassDefListParserTest, dependentForwardError) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('foo')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"foo\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+}
+
+// Verifies that backward dependencies will parse.
+TEST_F(ClientClassDefListParserTest, dependentBackward) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"foo\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('foo')\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
+}
+
+// Verifies that the depend on known flag is correctly handled.
+TEST_F(ClientClassDefListParserTest, dependOnKnown) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"alpha\", \n"
+ " \"test\": \"member('ALL')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"beta\", \n"
+ " \"test\": \"member('alpha')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"gamma\", \n"
+ " \"test\": \"member('KNOWN') and member('alpha')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"delta\", \n"
+ " \"test\": \"member('beta') and member('gamma')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"zeta\", \n"
+ " \"test\": \"not member('UNKNOWN') and member('alpha')\" \n"
+ " } \n"
+ "] \n";
+
+ // Parsing the list should succeed.
+ ClientClassDictionaryPtr dictionary;
+ EXPECT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET6));
+ ASSERT_TRUE(dictionary);
+
+ // We should have five classes in the dictionary.
+ EXPECT_EQ(5, dictionary->getClasses()->size());
+
+ // Check alpha.
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = dictionary->findClass("alpha"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("alpha", cclass->getName());
+ EXPECT_FALSE(cclass->getDependOnKnown());
+
+ // Check beta.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("beta"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("beta", cclass->getName());
+ EXPECT_FALSE(cclass->getDependOnKnown());
+
+ // Check gamma which directly depends on KNOWN.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("gamma"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("gamma", cclass->getName());
+ EXPECT_TRUE(cclass->getDependOnKnown());
+
+ // Check delta which indirectly depends on KNOWN.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("delta"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("delta", cclass->getName());
+ EXPECT_TRUE(cclass->getDependOnKnown());
+
+ // Check that zeta which directly depends on UNKNOWN.
+ // (and yes I know that I skipped epsilon)
+ ASSERT_NO_THROW(cclass = dictionary->findClass("zeta"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("zeta", cclass->getName());
+ EXPECT_TRUE(cclass->getDependOnKnown());
+}
+
+// Verifies that a built-in class can't be required or evaluated.
+TEST_F(ClientClassDefListParserTest, builtinCheckError) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"ALL\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
+
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"ALL\", \n"
+ " \"only-if-required\": true \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"ALL\", \n"
+ " \"test\": \"'aa' == 'aa'\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"KNOWN\", \n"
+ " \"only-if-required\": true \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"KNOWN\", \n"
+ " \"test\": \"'aa' == 'aa'\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"UNKNOWN\", \n"
+ " \"only-if-required\": true \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"UNKNOWN\", \n"
+ " \"test\": \"'aa' == 'aa'\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+}
+
+// Verifies that the special DROP class can't be required.
+TEST_F(ClientClassDefListParserTest, dropCheckError) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"DROP\", \n"
+ " \"test\": \"option[123].text == 'abc'\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
+
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"DROP\", \n"
+ " \"only-if-required\": true \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET), DhcpConfigError);
+
+ // This constraint was relaxed in #1815.
+ cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"DROP\", \n"
+ " \"test\": \"member('KNOWN')\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
+}
+
+// Verify the ability to configure valid lifetime triplet.
+TEST_F(ClientClassDefParserTest, validLifetimeTests) {
+
+ struct Scenario {
+ std::string desc_;
+ std::string cfg_txt_;
+ Triplet<uint32_t> exp_triplet_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "unspecified",
+ "",
+ Triplet<uint32_t>()
+ },
+ {
+ "valid only",
+ "\"valid-lifetime\": 100",
+ Triplet<uint32_t>(100)
+ },
+ {
+ "min only",
+ "\"min-valid-lifetime\": 50",
+ Triplet<uint32_t>(50, 50, 50)
+ },
+ {
+ "max only",
+ "\"max-valid-lifetime\": 75",
+ Triplet<uint32_t>(75, 75, 75)
+ },
+ {
+ "all three",
+ "\"min-valid-lifetime\": 25, \"valid-lifetime\": 50, \"max-valid-lifetime\": 75",
+ Triplet<uint32_t>(25, 50, 75)
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ std::stringstream oss;
+ oss << "{ \"name\": \"foo\"";
+ if (!scenario.cfg_txt_.empty()) {
+ oss << ",\n" << scenario.cfg_txt_;
+ }
+ oss << "\n}\n";
+
+ ClientClassDefPtr class_def;
+ ASSERT_NO_THROW_LOG(class_def = parseClientClassDef(oss.str(), AF_INET));
+ ASSERT_TRUE(class_def);
+ if (scenario.exp_triplet_.unspecified()) {
+ EXPECT_TRUE(class_def->getValid().unspecified());
+ } else {
+ EXPECT_EQ(class_def->getValid().unspecified(), scenario.exp_triplet_.unspecified());
+ EXPECT_EQ(class_def->getValid().getMin(), scenario.exp_triplet_.getMin());
+ EXPECT_EQ(class_def->getValid().get(), scenario.exp_triplet_.get());
+ EXPECT_EQ(class_def->getValid().getMax(), scenario.exp_triplet_.getMax());
+ }
+ }
+ }
+}
+
+// Verify the ability to configure lease preferred lifetime triplet.
+TEST_F(ClientClassDefParserTest, preferredLifetimeTests) {
+
+ struct Scenario {
+ std::string desc_;
+ std::string cfg_txt_;
+ Triplet<uint32_t> exp_triplet_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "unspecified",
+ "",
+ Triplet<uint32_t>()
+ },
+ {
+ "preferred only",
+ "\"preferred-lifetime\": 100",
+ Triplet<uint32_t>(100)
+ },
+ {
+ "min only",
+ "\"min-preferred-lifetime\": 50",
+ Triplet<uint32_t>(50, 50, 50)
+ },
+ {
+ "max only",
+ "\"max-preferred-lifetime\": 75",
+ Triplet<uint32_t>(75, 75, 75)
+ },
+ {
+ "all three",
+ "\"min-preferred-lifetime\": 25,"
+ "\"preferred-lifetime\": 50,"
+ "\"max-preferred-lifetime\": 75",
+ Triplet<uint32_t>(25, 50, 75)
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.desc_); {
+ std::stringstream oss;
+ oss << "{ \"name\": \"foo\"";
+ if (!scenario.cfg_txt_.empty()) {
+ oss << ",\n" << scenario.cfg_txt_;
+ }
+ oss << "\n}\n";
+
+ ClientClassDefPtr class_def;
+ ASSERT_NO_THROW_LOG(class_def = parseClientClassDef(oss.str(), AF_INET6));
+ ASSERT_TRUE(class_def);
+ if (scenario.exp_triplet_.unspecified()) {
+ EXPECT_TRUE(class_def->getPreferred().unspecified());
+ } else {
+ EXPECT_EQ(class_def->getPreferred().unspecified(), scenario.exp_triplet_.unspecified());
+ EXPECT_EQ(class_def->getPreferred().getMin(), scenario.exp_triplet_.getMin());
+ EXPECT_EQ(class_def->getPreferred().get(), scenario.exp_triplet_.get());
+ EXPECT_EQ(class_def->getPreferred().getMax(), scenario.exp_triplet_.getMax());
+ }
+ }
+ }
+}
+
+// Verifies that an invalid user-context fails to parse.
+TEST_F(ClientClassDefParserTest, invalidUserContext) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"one\", \n"
+ " \"user-context\": \"i am not a map\" \n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_THROW_MSG(cclass = parseClientClassDef(cfg_text, AF_INET),
+ DhcpConfigError, "User context has to be a map (<string>:3:21)");
+}
+
+// Test verifies that it is possible to define offer-lifetime field and it
+// is actually set in the class properly.
+TEST_F(ClientClassDefParserTest, offerLft) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"offer-lifetime\": 99\n"
+ "} \n";
+
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = parseClientClassDef(cfg_text, AF_INET));
+
+ // We should find our class.
+ ASSERT_TRUE(cclass);
+
+ auto offer_lft = cclass->getOfferLft();
+ ASSERT_FALSE(offer_lft.unspecified());
+ EXPECT_EQ(99, offer_lft.get());
+}
+
+// Test verifies that the parser rejects bogus offer-lifetime value.
+TEST_F(ClientClassDefParserTest, offerLftInvalid) {
+ std::string cfg_text =
+ "{ \n"
+ " \"name\": \"MICROSOFT\", \n"
+ " \"offer-lifetime\": -24\n"
+ "} \n";
+
+ EXPECT_THROW_MSG(parseClientClassDef(cfg_text, AF_INET), DhcpConfigError,
+ "the value of offer-lifetime '-24' must be a positive number"
+ " (<string>:3:23)");
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
new file mode 100644
index 0000000..8fc5aed
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
@@ -0,0 +1,1678 @@
+// Copyright (C) 2015-2023 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 <cc/data.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_space.h>
+#include <testutils/test_to_element.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+#include <asiolink/io_address.h>
+
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+/// @file client_class_def_unittest.cc Unit tests for client class storage
+/// classes.
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::test;
+using namespace isc;
+
+namespace {
+
+// Tests basic construction of ClientClassDef
+TEST(ClientClassDef, construction) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new ClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+ EXPECT_EQ(name, cclass->getName());
+ ASSERT_FALSE(cclass->getMatchExpr());
+ EXPECT_FALSE(cclass->getCfgOptionDef());
+
+ // Verify we get an empty collection of cfg_option
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ EXPECT_TRUE(cfg_option->empty());
+
+ // Verify we don't depend on something.
+ EXPECT_FALSE(cclass->dependOnClass("foobar"));
+ EXPECT_FALSE(cclass->dependOnClass(""));
+}
+
+// Test that client class is copied using the copy constructor.
+TEST(ClientClassDef, copyConstruction) {
+ auto expr = boost::make_shared<Expression>();
+
+ auto cfg_option = boost::make_shared<CfgOption>();
+ auto option = boost::make_shared<Option>(Option::V6, 1024);
+ cfg_option->add(option, false, false, DHCP6_OPTION_SPACE);
+
+ auto option_def = boost::make_shared<OptionDefinition>("foo", 1024, "dhcp6", "empty");
+ CfgOptionDefPtr cfg_option_def = boost::make_shared<CfgOptionDef>();
+ cfg_option_def->add(option_def);
+
+ boost::scoped_ptr<ClientClassDef> cclass;
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef("class1", expr, cfg_option)));
+ cclass->setId(123);
+ cclass->setContext(data::Element::create("my-context"));
+ cclass->setCfgOptionDef(cfg_option_def);
+ cclass->setTest("member('KNOWN')");
+ cclass->setRequired(true);
+ cclass->setDependOnKnown(true);
+ cclass->setNextServer(IOAddress("1.2.3.4"));
+ cclass->setSname("ufo");
+ cclass->setFilename("ufo.efi");
+ cclass->setValid(Triplet<uint32_t>(10, 20, 30));
+ cclass->setPreferred(Triplet<uint32_t>(11, 21, 31));
+
+ // Copy the client class.
+ boost::scoped_ptr<ClientClassDef> cclass_copy;
+ ASSERT_NO_THROW(cclass_copy.reset(new ClientClassDef(*cclass)));
+
+ // Ensure that class data was copied.
+ EXPECT_EQ(cclass->getName(), cclass_copy->getName());
+ EXPECT_EQ(cclass->getId(), cclass_copy->getId());
+ ASSERT_TRUE(cclass_copy->getContext());
+ ASSERT_EQ(data::Element::string, cclass_copy->getContext()->getType());
+ EXPECT_EQ("my-context", cclass_copy->getContext()->stringValue());
+ ASSERT_TRUE(cclass->getMatchExpr());
+ EXPECT_NE(cclass_copy->getMatchExpr(), cclass->getMatchExpr());
+ EXPECT_EQ(cclass->getTest(), cclass_copy->getTest());
+ EXPECT_EQ(cclass->getRequired(), cclass_copy->getRequired());
+ EXPECT_EQ(cclass->getDependOnKnown(), cclass_copy->getDependOnKnown());
+ EXPECT_EQ(cclass->getNextServer().toText(), cclass_copy->getNextServer().toText());
+ EXPECT_EQ(cclass->getSname(), cclass_copy->getSname());
+ EXPECT_EQ(cclass->getFilename(), cclass_copy->getFilename());
+ EXPECT_EQ(cclass->getValid().get(), cclass_copy->getValid().get());
+ EXPECT_EQ(cclass->getValid().getMin(), cclass_copy->getValid().getMin());
+ EXPECT_EQ(cclass->getValid().getMax(), cclass_copy->getValid().getMax());
+ EXPECT_EQ(cclass->getPreferred().get(), cclass_copy->getPreferred().get());
+ EXPECT_EQ(cclass->getPreferred().getMin(), cclass_copy->getPreferred().getMin());
+ EXPECT_EQ(cclass->getPreferred().getMax(), cclass_copy->getPreferred().getMax());
+
+ // Ensure that the option was copied into a new structure.
+ ASSERT_TRUE(cclass_copy->getCfgOption());
+ EXPECT_NE(cclass_copy->getCfgOption(), cclass->getCfgOption());
+ EXPECT_TRUE(cclass_copy->getCfgOption()->get("dhcp6", 1024).option_);
+
+ // Ensure that the option definition was copied into a new structure.
+ ASSERT_TRUE(cclass_copy->getCfgOptionDef());
+ EXPECT_NE(cclass_copy->getCfgOptionDef(), cclass->getCfgOptionDef());
+ EXPECT_TRUE(cclass_copy->getCfgOptionDef()->get("dhcp6", 1024));
+}
+
+// Tests options operations. Note we just do the basics
+// as CfgOption is heavily tested elsewhere.
+TEST(ClientClassDef, cfgOptionBasics) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr test_options;
+ CfgOptionPtr class_options;
+ OptionPtr opt;
+
+ // First construct the class with empty option pointer
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
+
+ // We should get back a collection with no entries,
+ // not an empty collection pointer
+ class_options = cclass->getCfgOption();
+ ASSERT_TRUE(class_options);
+
+ // Create an option container and add some options
+ OptionPtr option;
+ test_options.reset(new CfgOption());
+ option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP4_OPTION_SPACE));
+
+ option.reset(new Option(Option::V6, 101, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false, "isc"));
+
+ option.reset(new Option(Option::V6, 100, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP6_OPTION_SPACE));
+
+ // Now remake the client class with cfg_option
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
+ class_options = cclass->getCfgOption();
+ ASSERT_TRUE(class_options);
+
+ // Now make sure we can find all the options
+ OptionDescriptor opt_desc = class_options->get(DHCP4_OPTION_SPACE,17);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(17, opt_desc.option_->getType());
+
+ opt_desc = class_options->get("isc",101);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(101, opt_desc.option_->getType());
+
+ opt_desc = class_options->get(DHCP6_OPTION_SPACE,100);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(100, opt_desc.option_->getType());
+}
+
+// Verifies copy constructor and equality tools (methods/operators)
+TEST(ClientClassDef, copyAndEquality) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr test_options;
+ OptionPtr opt;
+
+ // Make an expression
+ expr.reset(new Expression());
+ TokenPtr token(new TokenString("boo"));
+ expr->push_back(token);
+
+ // Create an option container with an option
+ OptionPtr option;
+ test_options.reset(new CfgOption());
+ option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP4_OPTION_SPACE));
+
+ // Now remake the client class with cfg_option
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef("class_one", expr,
+ test_options)));
+
+ // Now lets make a copy of it.
+ boost::scoped_ptr<ClientClassDef> cclass2;
+ ASSERT_NO_THROW(cclass2.reset(new ClientClassDef(*cclass)));
+
+ // The allocated Expression pointers should not match
+ EXPECT_TRUE(cclass->getMatchExpr().get() !=
+ cclass2->getMatchExpr().get());
+
+ // The allocated CfgOption pointers should not match
+ EXPECT_TRUE(cclass->getCfgOption().get() !=
+ cclass2->getCfgOption().get());
+
+ // Verify the equality tools reflect that the classes are equal.
+ EXPECT_TRUE(cclass->equals(*cclass2));
+ EXPECT_TRUE(*cclass == *cclass2);
+ EXPECT_FALSE(*cclass != *cclass2);
+
+ // Verify the required flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getRequired());
+ cclass2->setRequired(true);
+ EXPECT_TRUE(cclass2->getRequired());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+ cclass2->setRequired(false);
+ EXPECT_TRUE(*cclass == *cclass2);
+
+ // Verify the depend on known flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getDependOnKnown());
+ cclass2->setDependOnKnown(true);
+ EXPECT_TRUE(cclass2->getDependOnKnown());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that differs from the first class only by name and
+ // verify that the equality tools reflect that the classes are not equal.
+ ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_two", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with the same name and options, but no expression
+ // verify that the equality tools reflect that the classes are not equal.
+ expr.reset();
+ ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with the same name and options, but different expression,
+ // verify that the equality tools reflect that the classes are not equal.
+ expr.reset(new Expression());
+ token.reset(new TokenString("yah"));
+ expr->push_back(token);
+ ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that with same name, expression and options, but
+ // different option definitions, verify that the equality tools reflect
+ // that the equality tools reflect that the classes are not equal.
+ ASSERT_NO_THROW(cclass2.reset(new ClientClassDef(*cclass)));
+ EXPECT_TRUE(cclass->equals(*cclass2));
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_FALSE(def);
+ def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_TRUE(def);
+ CfgOptionDefPtr cfg(new CfgOptionDef());
+ ASSERT_NO_THROW(cfg->add(def));
+ cclass2->setCfgOptionDef(cfg);
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with same name and expression, but no options
+ // verify that the equality tools reflect that the classes are not equal.
+ test_options.reset(new CfgOption());
+ ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that with same name and expression, but different options
+ // verify that the equality tools reflect that the classes are not equal.
+ option.reset(new Option(Option::V4, 20, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP4_OPTION_SPACE));
+ ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+}
+
+// Tests dependency.
+TEST(ClientClassDef, dependency) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ ExpressionPtr expr;
+
+ // Make an expression
+ expr.reset(new Expression());
+ TokenPtr token(new TokenMember("foo"));
+ expr->push_back(token);
+
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef("class", expr)));
+ EXPECT_TRUE(cclass->dependOnClass("foo"));
+ EXPECT_FALSE(cclass->dependOnClass("bar"));
+}
+
+// Tests the basic operation of ClientClassDictionary
+// This includes adding, finding, and removing classes
+TEST(ClientClassDictionary, basics) {
+ ClientClassDictionaryPtr dictionary;
+ ClientClassDefPtr cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Verify constructor doesn't throw
+ ASSERT_NO_THROW(dictionary.reset(new ClientClassDictionary()));
+
+ // Verify we can fetch a pointer the list of classes and
+ // that we start with no classes defined
+ const ClientClassDefListPtr classes = dictionary->getClasses();
+ ASSERT_TRUE(classes);
+ EXPECT_EQ(0, classes->size());
+ EXPECT_TRUE(classes->empty());
+
+ // Verify that we can add classes with both addClass variants
+ // First addClass(name, expression, cfg_option)
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", false,
+ false, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option));
+
+ // Verify duplicate add attempt throws using regular class
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option),
+ DuplicateClientClassDef);
+
+ // Verify duplicate add attempt throws using template class
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true),
+ DuplicateClientClassDef);
+
+ // Verify that you cannot add a class with no name.
+ ASSERT_THROW(dictionary->addClass("", expr, "", false,
+ false, cfg_option),
+ BadValue);
+
+ // Now with addClass(class pointer)
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef("cc3", expr, cfg_option)));
+ ASSERT_NO_THROW(dictionary->addClass(cclass));
+
+ // Verify duplicate add attempt throws
+ ASSERT_THROW(dictionary->addClass(cclass), DuplicateClientClassDef);
+
+ // Verify that you cannot add empty class pointer
+ cclass.reset();
+ ASSERT_THROW(dictionary->addClass(cclass), BadValue);
+
+ // Map should show 3 entries.
+ EXPECT_EQ(3, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Removing client class by id of 0 should be no-op.
+ ASSERT_NO_THROW(dictionary->removeClass(0));
+ EXPECT_EQ(3, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Verify we can find them all.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc1"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("cc1", cclass->getName());
+ cclass->setId(1);
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("cc2", cclass->getName());
+ cclass->setId(2);
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("cc3", cclass->getName());
+ cclass->setId(3);
+
+ // Verify the looking for non-existing returns empty pointer
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can remove a class
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Shouldn't be able to find anymore
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can attempt to remove a non-existing class
+ // without harm.
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Verify that we can remove client class by id.
+ ASSERT_NO_THROW(dictionary->removeClass(2));
+ EXPECT_EQ(1, classes->size());
+ EXPECT_FALSE(classes->empty());
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+ EXPECT_FALSE(cclass);
+}
+
+// Verifies copy constructor and equality tools (methods/operators)
+TEST(ClientClassDictionary, copyAndEquality) {
+ ClientClassDictionaryPtr dictionary;
+ ClientClassDictionaryPtr dictionary2;
+ ClientClassDefPtr cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ dictionary.reset(new ClientClassDictionary());
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false,
+ false, options));
+
+ // Copy constructor should succeed.
+ ASSERT_NO_THROW(dictionary2.reset(new ClientClassDictionary(*dictionary)));
+
+ // Allocated class list pointers should not be equal
+ EXPECT_NE(dictionary->getClasses().get(), dictionary2->getClasses().get());
+
+ // Equality tools should reflect that the dictionaries are equal.
+ EXPECT_TRUE(dictionary->equals(*dictionary2));
+ EXPECT_TRUE(*dictionary == *dictionary2);
+ EXPECT_FALSE(*dictionary != *dictionary2);
+
+ // Remove a class from dictionary2.
+ ASSERT_NO_THROW(dictionary2->removeClass("two"));
+
+ // Equality tools should reflect that the dictionaries are not equal.
+ EXPECT_FALSE(dictionary->equals(*dictionary2));
+ EXPECT_FALSE(*dictionary == *dictionary2);
+ EXPECT_TRUE(*dictionary != *dictionary2);
+
+ // Create an empty dictionary.
+ dictionary2.reset(new ClientClassDictionary());
+
+ // Equality tools should reflect that the dictionaries are not equal.
+ EXPECT_FALSE(dictionary->equals(*dictionary2));
+ EXPECT_FALSE(*dictionary == *dictionary2);
+ EXPECT_TRUE(*dictionary != *dictionary2);
+}
+
+// Verify that client class dictionaries are deep-copied.
+TEST(ClientClassDictionary, copy) {
+ ClientClassDictionary dictionary;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ // Get a client class dictionary and fill it.
+ ASSERT_NO_THROW(dictionary.addClass("one", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary.addClass("two", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary.addClass("three", expr, "", false,
+ false, options));
+
+ // Make a copy with a copy constructor. Expect it to be a deep copy.
+ ClientClassDictionary dictionary_copy(dictionary);
+ ASSERT_NO_THROW(dictionary.removeClass("one"));
+ ASSERT_NO_THROW(dictionary.removeClass("two"));
+ ASSERT_NO_THROW(dictionary.removeClass("three"));
+ EXPECT_TRUE(dictionary.empty());
+ EXPECT_FALSE(dictionary_copy.empty());
+
+ // Refill the client class dictionary.
+ ASSERT_NO_THROW(dictionary.addClass("one", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary.addClass("two", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary.addClass("three", expr, "", false,
+ false, options));
+
+ // Make a copy with operator=. Expect it to be a deep copy.
+ dictionary_copy = dictionary;
+ ASSERT_NO_THROW(dictionary.removeClass("one"));
+ ASSERT_NO_THROW(dictionary.removeClass("two"));
+ ASSERT_NO_THROW(dictionary.removeClass("three"));
+ EXPECT_TRUE(dictionary.empty());
+ EXPECT_FALSE(dictionary_copy.empty());
+}
+
+// Tests dependency.
+TEST(ClientClassDictionary, dependency) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Make an expression depending on forward class.
+ ExpressionPtr expr1;
+ expr1.reset(new Expression());
+ TokenPtr token1(new TokenMember("cc2"));
+ expr1->push_back(token1);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr1, "", false,
+ false, cfg_option));
+
+ // Make an expression depending on first class.
+ ExpressionPtr expr2;
+ expr2.reset(new Expression());
+ TokenPtr token2(new TokenMember("cc1"));
+ expr2->push_back(token2);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr2, "", false,
+ false, cfg_option));
+
+ // Make expression with dependency.
+ ASSERT_NO_THROW(dictionary->addClass("cc3", expr, "", false,
+ false, cfg_option));
+
+ ExpressionPtr expr3;
+ expr3.reset(new Expression());
+ TokenPtr token3(new TokenMember("cc3"));
+ expr3->push_back(token3);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc4", expr3, "", false,
+ false, cfg_option));
+
+ // Not matching dependency does not match.
+ string depend;
+ EXPECT_FALSE(dictionary->dependOnClass("foobar", depend));
+ EXPECT_TRUE(depend.empty());
+
+ // Forward dependency is ignored.
+ depend = "";
+ EXPECT_FALSE(dictionary->dependOnClass("cc2", depend));
+ EXPECT_TRUE(depend.empty());
+
+ // Backward dependency is detected.
+ depend = "";
+ EXPECT_TRUE(dictionary->dependOnClass("cc3", depend));
+ EXPECT_EQ("cc4", depend);
+}
+
+// Tests that match expressions are set for all client classes in the
+// dictionary.
+TEST(ClientClassDictionary, initMatchExpr) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Add several classes.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "", false,
+ false, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "member('KNOWN') or member('foo')", false,
+ false, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("baz", expr, "substring(option[61].hex,0,3) == 'foo'", false,
+ false, cfg_option));
+
+ // Create match expressions for all of them.
+ ASSERT_NO_THROW(dictionary->initMatchExpr(AF_INET));
+
+ // Ensure that the expressions were created only if 'test' is not empty.
+ auto classes = *(dictionary->getClasses());
+ EXPECT_FALSE(classes[0]->getMatchExpr());
+
+ EXPECT_TRUE(classes[1]->getMatchExpr());
+ EXPECT_EQ(3, classes[1]->getMatchExpr()->size());
+
+ EXPECT_TRUE(classes[2]->getMatchExpr());
+ EXPECT_EQ(6, classes[2]->getMatchExpr()->size());
+}
+
+// Tests that an error is returned when any of the test expressions is
+// invalid, and that no expressions are initialized if there is an error
+// for a single expression.
+TEST(ClientClassDictionary, initMatchExprError) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Add several classes. One of them has invalid test expression.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "member('KNOWN')", false,
+ false, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "wrong expression", false,
+ false, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("baz", expr, "substring(option[61].hex,0,3) == 'foo'", false,
+ false, cfg_option));
+
+ // An attempt to initialize match expressions should fail because the
+ // test expression for the second class is invalid.
+ ASSERT_THROW(dictionary->initMatchExpr(AF_INET), std::exception);
+
+ // Ensure that no classes have their match expressions modified.
+ for (auto c : (*dictionary->getClasses())) {
+ EXPECT_FALSE(c->getMatchExpr());
+ }
+}
+
+// Tests the default constructor regarding fixed fields
+TEST(ClientClassDef, fixedFieldsDefaults) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new ClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+
+ // Let's checks that it doesn't return any nonsense
+ EXPECT_FALSE(cclass->getRequired());
+ EXPECT_FALSE(cclass->getDependOnKnown());
+ EXPECT_FALSE(cclass->getCfgOptionDef());
+ string empty;
+ ASSERT_EQ(IOAddress("0.0.0.0"), cclass->getNextServer());
+ EXPECT_EQ(empty, cclass->getSname());
+ EXPECT_EQ(empty, cclass->getFilename());
+ EXPECT_TRUE(cclass->getOfferLft().unspecified());
+}
+
+// Tests basic operations of fixed fields
+TEST(ClientClassDef, fixedFieldsBasics) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new ClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+
+ cclass->setRequired(true);
+ cclass->setDependOnKnown(true);
+
+ string sname = "This is a very long string that can be a server name";
+ string filename = "this-is-a-slightly-longish-name-of-a-file.txt";
+
+ cclass->setNextServer(IOAddress("1.2.3.4"));
+ cclass->setSname(sname);
+ cclass->setFilename(filename);
+
+ // Let's checks that it doesn't return any nonsense
+ EXPECT_TRUE(cclass->getRequired());
+ EXPECT_TRUE(cclass->getDependOnKnown());
+ EXPECT_EQ(IOAddress("1.2.3.4"), cclass->getNextServer());
+ EXPECT_EQ(sname, cclass->getSname());
+ EXPECT_EQ(filename, cclass->getFilename());
+}
+
+// Verifies the unparse method of option class definitions
+TEST(ClientClassDef, unparseDef) {
+ CfgMgr::instance().setFamily(AF_INET);
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ // Get a client class definition and fill it
+ std::string name = "class1";
+ ExpressionPtr expr;
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+ std::string test = "option[12].text == 'foo'";
+ cclass->setTest(test);
+ std::string comment = "bar";
+ std::string user_context = "{ \"comment\": \"" + comment + "\", ";
+ user_context += "\"bar\": 1 }";
+ cclass->setContext(isc::data::Element::fromJSON(user_context));
+ cclass->setRequired(true);
+ // The depend on known flag in not visible
+ cclass->setDependOnKnown(true);
+ std::string next_server = "1.2.3.4";
+ cclass->setNextServer(IOAddress(next_server));
+ std::string sname = "my-server.example.com";
+ cclass->setSname(sname);
+ std::string filename = "/boot/kernel";
+ cclass->setFilename(filename);
+
+ // Unparse it
+ std::string expected = "{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"test\": \"" + test + "\",\n"
+ "\"only-if-required\": true,\n"
+ "\"next-server\": \"" + next_server + "\",\n"
+ "\"server-hostname\": \"" + sname + "\",\n"
+ "\"boot-file-name\": \"" + filename + "\",\n"
+ "\"option-data\": [ ],\n"
+ "\"user-context\": { \"bar\": 1,\n"
+ "\"comment\": \"" + comment + "\" } }\n";
+ runToElementTest<ClientClassDef>(expected, *cclass);
+}
+
+// Verifies the unparse method of client class dictionaries
+TEST(ClientClassDictionary, unparseDict) {
+ CfgMgr::instance().setFamily(AF_INET);
+ ClientClassDictionaryPtr dictionary;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ // Get a client class dictionary and fill it
+ dictionary.reset(new ClientClassDictionary());
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false,
+ false, options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false,
+ false, options));
+
+ // Unparse it
+ auto add_defaults =
+ [](std::string name) {
+ return ("{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"next-server\": \"0.0.0.0\",\n"
+ "\"server-hostname\": \"\",\n"
+ "\"boot-file-name\": \"\",\n"
+ "\"option-data\": [ ] }");
+ };
+
+ std::string expected = "[\n";
+ expected += add_defaults("one") + ",\n";
+ expected += add_defaults("two") + ",\n";
+ expected += add_defaults("three") + "]\n";
+
+ runToElementTest<ClientClassDictionary>(expected, *dictionary);
+}
+
+// Tests that options have been created for all client classes in the
+// dictionary.
+TEST(ClientClassDictionary, createOptions) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // First class has no options.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "", false,
+ false, cfg_option));
+
+ // Make some options for the second class.
+ cfg_option.reset(new CfgOption());
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ OptionDescriptorPtr desc = OptionDescriptor::create(option, true, false,
+ "bogus-file.txt");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ cfg_option->add(*desc, desc->space_name_);
+
+ option = Option::create(Option::V4, DHO_TFTP_SERVER_NAME);
+ desc = OptionDescriptor::create(option, true, false, "bogus-tftp-server");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ cfg_option->add(*desc, desc->space_name_);
+
+ // Add the second class with options.
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "", false,
+ false, cfg_option));
+
+ // Make sure first class has no options.
+ ASSERT_TRUE(dictionary->getClasses());
+ auto classes = *(dictionary->getClasses());
+ auto options = classes[0]->getCfgOption();
+ ASSERT_TRUE(options->empty());
+
+ // Make sure second class has both options but their
+ // data buffers are empty.
+ options = classes[1]->getCfgOption();
+ ASSERT_FALSE(options->empty());
+
+ auto option_desc = options->get("dhcp4", DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(option_desc.option_);
+ ASSERT_TRUE(option_desc.option_->getData().empty());
+
+ option_desc = options->get("dhcp4", DHO_TFTP_SERVER_NAME);
+ ASSERT_TRUE(option_desc.option_);
+ ASSERT_TRUE(option_desc.option_->getData().empty());
+
+ // Now create match expressions for all of them.
+ auto cfg_def = CfgMgr::instance().getCurrentCfg()->getCfgOptionDef();
+ ASSERT_NO_THROW(dictionary->createOptions(cfg_def));
+
+ // Make sure first class still has no options.
+ classes = *(dictionary->getClasses());
+ options = classes[0]->getCfgOption();
+ ASSERT_TRUE(options->empty());
+
+ // Make sure second class has both options and that their
+ // data buffers are now correctly populated.
+ options = classes[1]->getCfgOption();
+ ASSERT_FALSE(options->empty());
+
+ option_desc = options->get("dhcp4", DHO_BOOT_FILE_NAME);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(), option_desc.formatted_value_.end()),
+ option->getData());
+
+ option_desc = options->get("dhcp4", DHO_TFTP_SERVER_NAME);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(), option_desc.formatted_value_.end()),
+ option->getData());
+}
+
+// Tests basic construction of TemplateClientClassDef
+TEST(TemplateClientClassDef, construction) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new TemplateClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+ EXPECT_EQ(name, cclass->getName());
+ ASSERT_FALSE(cclass->getMatchExpr());
+ EXPECT_FALSE(cclass->getCfgOptionDef());
+
+ // Verify we get an empty collection of cfg_option
+ cfg_option = cclass->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ EXPECT_TRUE(cfg_option->empty());
+
+ // Verify we don't depend on something.
+ EXPECT_FALSE(cclass->dependOnClass("foobar"));
+ EXPECT_FALSE(cclass->dependOnClass(""));
+}
+
+// Test that template client class is copied using the copy constructor.
+TEST(TemplateClientClassDef, copyConstruction) {
+ auto expr = boost::make_shared<Expression>();
+
+ auto cfg_option = boost::make_shared<CfgOption>();
+ auto option = boost::make_shared<Option>(Option::V6, 1024);
+ cfg_option->add(option, false, false, DHCP6_OPTION_SPACE);
+
+ auto option_def = boost::make_shared<OptionDefinition>("foo", 1024, "dhcp6", "empty");
+ CfgOptionDefPtr cfg_option_def = boost::make_shared<CfgOptionDef>();
+ cfg_option_def->add(option_def);
+
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("class1", expr, cfg_option)));
+ cclass->setId(123);
+ cclass->setContext(data::Element::create("my-context"));
+ cclass->setCfgOptionDef(cfg_option_def);
+ cclass->setTest("member('KNOWN')");
+ cclass->setRequired(true);
+ cclass->setDependOnKnown(true);
+ cclass->setNextServer(IOAddress("1.2.3.4"));
+ cclass->setSname("ufo");
+ cclass->setFilename("ufo.efi");
+ cclass->setValid(Triplet<uint32_t>(10, 20, 30));
+ cclass->setPreferred(Triplet<uint32_t>(11, 21, 31));
+
+ // Copy the client class.
+ boost::scoped_ptr<TemplateClientClassDef> cclass_copy;
+ ASSERT_NO_THROW(cclass_copy.reset(new TemplateClientClassDef(*cclass)));
+
+ // Ensure that class data was copied.
+ EXPECT_EQ(cclass->getName(), cclass_copy->getName());
+ EXPECT_EQ(cclass->getId(), cclass_copy->getId());
+ ASSERT_TRUE(cclass_copy->getContext());
+ ASSERT_EQ(data::Element::string, cclass_copy->getContext()->getType());
+ EXPECT_EQ("my-context", cclass_copy->getContext()->stringValue());
+ ASSERT_TRUE(cclass->getMatchExpr());
+ EXPECT_NE(cclass_copy->getMatchExpr(), cclass->getMatchExpr());
+ EXPECT_EQ(cclass->getTest(), cclass_copy->getTest());
+ EXPECT_EQ(cclass->getRequired(), cclass_copy->getRequired());
+ EXPECT_EQ(cclass->getDependOnKnown(), cclass_copy->getDependOnKnown());
+ EXPECT_EQ(cclass->getNextServer().toText(), cclass_copy->getNextServer().toText());
+ EXPECT_EQ(cclass->getSname(), cclass_copy->getSname());
+ EXPECT_EQ(cclass->getFilename(), cclass_copy->getFilename());
+ EXPECT_EQ(cclass->getValid().get(), cclass_copy->getValid().get());
+ EXPECT_EQ(cclass->getValid().getMin(), cclass_copy->getValid().getMin());
+ EXPECT_EQ(cclass->getValid().getMax(), cclass_copy->getValid().getMax());
+ EXPECT_EQ(cclass->getPreferred().get(), cclass_copy->getPreferred().get());
+ EXPECT_EQ(cclass->getPreferred().getMin(), cclass_copy->getPreferred().getMin());
+ EXPECT_EQ(cclass->getPreferred().getMax(), cclass_copy->getPreferred().getMax());
+
+ // Ensure that the option was copied into a new structure.
+ ASSERT_TRUE(cclass_copy->getCfgOption());
+ EXPECT_NE(cclass_copy->getCfgOption(), cclass->getCfgOption());
+ EXPECT_TRUE(cclass_copy->getCfgOption()->get("dhcp6", 1024).option_);
+
+ // Ensure that the option definition was copied into a new structure.
+ ASSERT_TRUE(cclass_copy->getCfgOptionDef());
+ EXPECT_NE(cclass_copy->getCfgOptionDef(), cclass->getCfgOptionDef());
+ EXPECT_TRUE(cclass_copy->getCfgOptionDef()->get("dhcp6", 1024));
+}
+
+// Tests options operations. Note we just do the basics
+// as CfgOption is heavily tested elsewhere.
+TEST(TemplateClientClassDef, cfgOptionBasics) {
+ boost::scoped_ptr<ClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr test_options;
+ CfgOptionPtr class_options;
+ OptionPtr opt;
+
+ // First construct the class with empty option pointer
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
+
+ // We should get back a collection with no entries,
+ // not an empty collection pointer
+ class_options = cclass->getCfgOption();
+ ASSERT_TRUE(class_options);
+
+ // Create an option container and add some options
+ OptionPtr option;
+ test_options.reset(new CfgOption());
+ option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP4_OPTION_SPACE));
+
+ option.reset(new Option(Option::V6, 101, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false, "isc"));
+
+ option.reset(new Option(Option::V6, 100, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP6_OPTION_SPACE));
+
+ // Now remake the client class with cfg_option
+ ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr, test_options)));
+ class_options = cclass->getCfgOption();
+ ASSERT_TRUE(class_options);
+
+ // Now make sure we can find all the options
+ OptionDescriptor opt_desc = class_options->get(DHCP4_OPTION_SPACE,17);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(17, opt_desc.option_->getType());
+
+ opt_desc = class_options->get("isc",101);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(101, opt_desc.option_->getType());
+
+ opt_desc = class_options->get(DHCP6_OPTION_SPACE,100);
+ ASSERT_TRUE(opt_desc.option_);
+ EXPECT_EQ(100, opt_desc.option_->getType());
+}
+
+// Verifies copy constructor and equality tools (methods/operators)
+TEST(TemplateClientClassDef, copyAndEquality) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr test_options;
+ OptionPtr opt;
+
+ // Make an expression
+ expr.reset(new Expression());
+ TokenPtr token(new TokenString("boo"));
+ expr->push_back(token);
+
+ // Create an option container with an option
+ OptionPtr option;
+ test_options.reset(new CfgOption());
+ option.reset(new Option(Option::V4, 17, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP4_OPTION_SPACE));
+
+ // Now remake the client class with cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+
+ // Now lets make a copy of it.
+ boost::scoped_ptr<TemplateClientClassDef> cclass2;
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef(*cclass)));
+
+ // The allocated Expression pointers should not match
+ EXPECT_TRUE(cclass->getMatchExpr().get() !=
+ cclass2->getMatchExpr().get());
+
+ // The allocated CfgOption pointers should not match
+ EXPECT_TRUE(cclass->getCfgOption().get() !=
+ cclass2->getCfgOption().get());
+
+ // Verify the equality tools reflect that the classes are equal.
+ EXPECT_TRUE(cclass->equals(*cclass2));
+ EXPECT_TRUE(*cclass == *cclass2);
+ EXPECT_FALSE(*cclass != *cclass2);
+
+ // Verify the required flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getRequired());
+ cclass2->setRequired(true);
+ EXPECT_TRUE(cclass2->getRequired());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+ cclass2->setRequired(false);
+ EXPECT_TRUE(*cclass == *cclass2);
+
+ // Verify the depend on known flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getDependOnKnown());
+ cclass2->setDependOnKnown(true);
+ EXPECT_TRUE(cclass2->getDependOnKnown());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that differs from the first class only by name and
+ // verify that the equality tools reflect that the classes are not equal.
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_two", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with the same name and options, but no expression
+ // verify that the equality tools reflect that the classes are not equal.
+ expr.reset();
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with the same name and options, but different expression,
+ // verify that the equality tools reflect that the classes are not equal.
+ expr.reset(new Expression());
+ token.reset(new TokenString("yah"));
+ expr->push_back(token);
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that with same name, expression and options, but
+ // different option definitions, verify that the equality tools reflect
+ // that the equality tools reflect that the classes are not equal.
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef(*cclass)));
+ EXPECT_TRUE(cclass->equals(*cclass2));
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_FALSE(def);
+ def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ EXPECT_TRUE(def);
+ CfgOptionDefPtr cfg(new CfgOptionDef());
+ ASSERT_NO_THROW(cfg->add(def));
+ cclass2->setCfgOptionDef(cfg);
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class with same name and expression, but no options
+ // verify that the equality tools reflect that the classes are not equal.
+ test_options.reset(new CfgOption());
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
+ // Make a class that with same name and expression, but different options
+ // verify that the equality tools reflect that the classes are not equal.
+ option.reset(new Option(Option::V4, 20, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(test_options->add(option, false, false,
+ DHCP4_OPTION_SPACE));
+ ASSERT_NO_THROW(cclass2.reset(new TemplateClientClassDef("class_one", expr,
+ test_options)));
+ EXPECT_FALSE(cclass->equals(*cclass2));
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+}
+
+// Tests dependency.
+TEST(TemplateClientClassDef, dependency) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ ExpressionPtr expr;
+
+ // Make an expression
+ expr.reset(new Expression());
+ TokenPtr token(new TokenMember("foo"));
+ expr->push_back(token);
+
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("class", expr)));
+ EXPECT_TRUE(cclass->dependOnClass("foo"));
+ EXPECT_FALSE(cclass->dependOnClass("bar"));
+}
+
+// Tests the basic operation of ClientClassDictionary
+// This includes adding, finding, and removing classes
+TEST(ClientClassDictionary, templateBasics) {
+ ClientClassDictionaryPtr dictionary;
+ ClientClassDefPtr cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Verify constructor doesn't throw
+ ASSERT_NO_THROW(dictionary.reset(new ClientClassDictionary()));
+
+ // Verify we can fetch a pointer the list of classes and
+ // that we start with no classes defined
+ const ClientClassDefListPtr classes = dictionary->getClasses();
+ ASSERT_TRUE(classes);
+ EXPECT_EQ(0, classes->size());
+ EXPECT_TRUE(classes->empty());
+
+ // Verify that we can add classes with both addClass variants
+ // First addClass(name, expression, cfg_option)
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Verify duplicate add attempt throws using template class
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true),
+ DuplicateClientClassDef);
+
+ // Verify duplicate add attempt throws using regular class
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false,
+ false, cfg_option),
+ DuplicateClientClassDef);
+
+ // Verify that you cannot add a class with no name.
+ ASSERT_THROW(dictionary->addClass("", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true),
+ BadValue);
+
+ // Now with addClass(class pointer)
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef("cc3", expr, cfg_option)));
+ ASSERT_NO_THROW(dictionary->addClass(cclass));
+
+ // Verify duplicate add attempt throws
+ ASSERT_THROW(dictionary->addClass(cclass), DuplicateClientClassDef);
+
+ // Verify that you cannot add empty class pointer
+ cclass.reset();
+ ASSERT_THROW(dictionary->addClass(cclass), BadValue);
+
+ // Map should show 3 entries.
+ EXPECT_EQ(3, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Removing client class by id of 0 should be no-op.
+ ASSERT_NO_THROW(dictionary->removeClass(0));
+ EXPECT_EQ(3, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Verify we can find them all.
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc1"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("cc1", cclass->getName());
+ cclass->setId(1);
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("cc2", cclass->getName());
+ cclass->setId(2);
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ ASSERT_TRUE(cclass);
+ ASSERT_TRUE(dynamic_cast<TemplateClientClassDef*>(cclass.get()));
+ EXPECT_EQ("cc3", cclass->getName());
+ cclass->setId(3);
+
+ // Verify the looking for non-existing returns empty pointer
+ ASSERT_NO_THROW(cclass = dictionary->findClass("bogus"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can remove a class
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Shouldn't be able to find anymore
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc3"));
+ EXPECT_FALSE(cclass);
+
+ // Verify that we can attempt to remove a non-existing class
+ // without harm.
+ ASSERT_NO_THROW(dictionary->removeClass("cc3"));
+ EXPECT_EQ(2, classes->size());
+ EXPECT_FALSE(classes->empty());
+
+ // Verify that we can remove client class by id.
+ ASSERT_NO_THROW(dictionary->removeClass(2));
+ EXPECT_EQ(1, classes->size());
+ EXPECT_FALSE(classes->empty());
+ ASSERT_NO_THROW(cclass = dictionary->findClass("cc2"));
+ EXPECT_FALSE(cclass);
+}
+
+// Verifies copy constructor and equality tools (methods/operators)
+TEST(ClientClassDictionary, templateCopyAndEquality) {
+ ClientClassDictionaryPtr dictionary;
+ ClientClassDictionaryPtr dictionary2;
+ ClientClassDefPtr cclass;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ dictionary.reset(new ClientClassDictionary());
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Copy constructor should succeed.
+ ASSERT_NO_THROW(dictionary2.reset(new ClientClassDictionary(*dictionary)));
+
+ // Allocated class list pointers should not be equal
+ EXPECT_NE(dictionary->getClasses().get(), dictionary2->getClasses().get());
+
+ // Equality tools should reflect that the dictionaries are equal.
+ EXPECT_TRUE(dictionary->equals(*dictionary2));
+ EXPECT_TRUE(*dictionary == *dictionary2);
+ EXPECT_FALSE(*dictionary != *dictionary2);
+
+ // Remove a class from dictionary2.
+ ASSERT_NO_THROW(dictionary2->removeClass("two"));
+
+ // Equality tools should reflect that the dictionaries are not equal.
+ EXPECT_FALSE(dictionary->equals(*dictionary2));
+ EXPECT_FALSE(*dictionary == *dictionary2);
+ EXPECT_TRUE(*dictionary != *dictionary2);
+
+ // Create an empty dictionary.
+ dictionary2.reset(new ClientClassDictionary());
+
+ // Equality tools should reflect that the dictionaries are not equal.
+ EXPECT_FALSE(dictionary->equals(*dictionary2));
+ EXPECT_FALSE(*dictionary == *dictionary2);
+ EXPECT_TRUE(*dictionary != *dictionary2);
+}
+
+// Verify that client class dictionaries are deep-copied.
+TEST(ClientClassDictionary, templateCopy) {
+ ClientClassDictionary dictionary;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ // Get a client class dictionary and fill it.
+ ASSERT_NO_THROW(dictionary.addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make a copy with a copy constructor. Expect it to be a deep copy.
+ ClientClassDictionary dictionary_copy(dictionary);
+ ASSERT_NO_THROW(dictionary.removeClass("one"));
+ ASSERT_NO_THROW(dictionary.removeClass("two"));
+ ASSERT_NO_THROW(dictionary.removeClass("three"));
+ EXPECT_TRUE(dictionary.empty());
+ EXPECT_FALSE(dictionary_copy.empty());
+
+ // Refill the client class dictionary.
+ ASSERT_NO_THROW(dictionary.addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary.addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make a copy with operator=. Expect it to be a deep copy.
+ dictionary_copy = dictionary;
+ ASSERT_NO_THROW(dictionary.removeClass("one"));
+ ASSERT_NO_THROW(dictionary.removeClass("two"));
+ ASSERT_NO_THROW(dictionary.removeClass("three"));
+ EXPECT_TRUE(dictionary.empty());
+ EXPECT_FALSE(dictionary_copy.empty());
+}
+
+// Tests dependency.
+TEST(ClientClassDictionary, templateDependency) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Make an expression depending on forward class.
+ ExpressionPtr expr1;
+ expr1.reset(new Expression());
+ TokenPtr token1(new TokenMember("cc2"));
+ expr1->push_back(token1);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr1, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make an expression depending on first class.
+ ExpressionPtr expr2;
+ expr2.reset(new Expression());
+ TokenPtr token2(new TokenMember("cc1"));
+ expr2->push_back(token2);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr2, "", false,
+ false, cfg_option));
+
+ // Make expression with dependency.
+ ASSERT_NO_THROW(dictionary->addClass("cc3", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ ExpressionPtr expr3;
+ expr3.reset(new Expression());
+ TokenPtr token3(new TokenMember("cc3"));
+ expr3->push_back(token3);
+
+ ASSERT_NO_THROW(dictionary->addClass("cc4", expr3, "", false,
+ false, cfg_option));
+
+ // Not matching dependency does not match.
+ string depend;
+ EXPECT_FALSE(dictionary->dependOnClass("foobar", depend));
+ EXPECT_TRUE(depend.empty());
+
+ // Forward dependency is ignored.
+ depend = "";
+ EXPECT_FALSE(dictionary->dependOnClass("cc2", depend));
+ EXPECT_TRUE(depend.empty());
+
+ // Backward dependency is detected.
+ depend = "";
+ EXPECT_TRUE(dictionary->dependOnClass("cc3", depend));
+ EXPECT_EQ("cc4", depend);
+}
+
+// Tests that match expressions are set for all client classes in the
+// dictionary.
+TEST(ClientClassDictionary, templateInitMatchExpr) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Add several classes.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "'template'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("baz", expr, "'foo' + 'template'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Create match expressions for all of them.
+ ASSERT_NO_THROW(dictionary->initMatchExpr(AF_INET));
+
+ // Ensure that the expressions were created only if 'test' is not empty.
+ auto classes = *(dictionary->getClasses());
+ EXPECT_FALSE(classes[0]->getMatchExpr());
+
+ EXPECT_TRUE(classes[1]->getMatchExpr());
+ EXPECT_EQ(1, classes[1]->getMatchExpr()->size());
+
+ EXPECT_TRUE(classes[2]->getMatchExpr());
+ EXPECT_EQ(3, classes[2]->getMatchExpr()->size());
+}
+
+// Tests that an error is returned when any of the test expressions is
+// invalid, and that no expressions are initialized if there is an error
+// for a single expression.
+TEST(ClientClassDictionary, templateInitMatchExprError) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Add several classes. One of them has invalid test expression.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "'template'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "wrong expression", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("baz", expr, "'foo'", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // An attempt to initialize match expressions should fail because the
+ // test expression for the second class is invalid.
+ ASSERT_THROW(dictionary->initMatchExpr(AF_INET), std::exception);
+
+ // Ensure that no classes have their match expressions modified.
+ for (auto c : (*dictionary->getClasses())) {
+ EXPECT_FALSE(c->getMatchExpr());
+ }
+}
+
+// Tests the default constructor regarding fixed fields
+TEST(TemplateClientClassDef, fixedFieldsDefaults) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new TemplateClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+
+ // Let's checks that it doesn't return any nonsense
+ EXPECT_FALSE(cclass->getRequired());
+ EXPECT_FALSE(cclass->getDependOnKnown());
+ EXPECT_FALSE(cclass->getCfgOptionDef());
+ string empty;
+ ASSERT_EQ(IOAddress("0.0.0.0"), cclass->getNextServer());
+ EXPECT_EQ(empty, cclass->getSname());
+ EXPECT_EQ(empty, cclass->getFilename());
+}
+
+// Tests basic operations of fixed fields
+TEST(TemplateClientClassDef, fixedFieldsBasics) {
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ std::string name = "class1";
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // Classes cannot have blank names
+ ASSERT_THROW(cclass.reset(new TemplateClientClassDef("", expr, cfg_option)),
+ BadValue);
+
+ // Verify we can create a class with a name, expression, and no cfg_option
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+
+ cclass->setRequired(true);
+ cclass->setDependOnKnown(true);
+
+ string sname = "This is a very long string that can be a server name";
+ string filename = "this-is-a-slightly-longish-name-of-a-file.txt";
+
+ cclass->setNextServer(IOAddress("1.2.3.4"));
+ cclass->setSname(sname);
+ cclass->setFilename(filename);
+
+ // Let's checks that it doesn't return any nonsense
+ EXPECT_TRUE(cclass->getRequired());
+ EXPECT_TRUE(cclass->getDependOnKnown());
+ EXPECT_EQ(IOAddress("1.2.3.4"), cclass->getNextServer());
+ EXPECT_EQ(sname, cclass->getSname());
+ EXPECT_EQ(filename, cclass->getFilename());
+}
+
+// Verifies the unparse method of option class definitions
+TEST(TemplateClientClassDef, unparseDef) {
+ CfgMgr::instance().setFamily(AF_INET);
+ boost::scoped_ptr<TemplateClientClassDef> cclass;
+
+ // Get a client class definition and fill it
+ std::string name = "class1";
+ ExpressionPtr expr;
+ ASSERT_NO_THROW(cclass.reset(new TemplateClientClassDef(name, expr)));
+ std::string test = "option[12].text";
+ cclass->setTest(test);
+ std::string comment = "bar";
+ std::string user_context = "{ \"comment\": \"" + comment + "\", ";
+ user_context += "\"bar\": 1 }";
+ cclass->setContext(isc::data::Element::fromJSON(user_context));
+ cclass->setRequired(true);
+ // The depend on known flag in not visible
+ cclass->setDependOnKnown(true);
+ std::string next_server = "1.2.3.4";
+ cclass->setNextServer(IOAddress(next_server));
+ std::string sname = "my-server.example.com";
+ cclass->setSname(sname);
+ std::string filename = "/boot/kernel";
+ cclass->setFilename(filename);
+
+ // Unparse it
+ std::string expected = "{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"template-test\": \"" + test + "\",\n"
+ "\"only-if-required\": true,\n"
+ "\"next-server\": \"" + next_server + "\",\n"
+ "\"server-hostname\": \"" + sname + "\",\n"
+ "\"boot-file-name\": \"" + filename + "\",\n"
+ "\"option-data\": [ ],\n"
+ "\"user-context\": { \"bar\": 1,\n"
+ "\"comment\": \"" + comment + "\" } }\n";
+ runToElementTest<TemplateClientClassDef>(expected, *cclass);
+}
+
+// Verifies the unparse method of client class dictionaries
+TEST(ClientClassDictionary, templateUnparseDict) {
+ CfgMgr::instance().setFamily(AF_INET);
+ ClientClassDictionaryPtr dictionary;
+ ExpressionPtr expr;
+ CfgOptionPtr options;
+
+ // Get a client class dictionary and fill it
+ dictionary.reset(new ClientClassDictionary());
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false,
+ false, options, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Unparse it
+ auto add_defaults =
+ [](std::string name) {
+ return ("{\n"
+ "\"name\": \"" + name + "\",\n"
+ "\"next-server\": \"0.0.0.0\",\n"
+ "\"server-hostname\": \"\",\n"
+ "\"template-test\": \"\",\n"
+ "\"boot-file-name\": \"\",\n"
+ "\"option-data\": [ ] }");
+ };
+
+ std::string expected = "[\n";
+ expected += add_defaults("one") + ",\n";
+ expected += add_defaults("two") + ",\n";
+ expected += add_defaults("three") + "]\n";
+
+ runToElementTest<ClientClassDictionary>(expected, *dictionary);
+}
+
+// Tests that options have been created for all client classes in the
+// dictionary.
+TEST(ClientClassDictionary, templateCreateOptions) {
+ ClientClassDictionaryPtr dictionary(new ClientClassDictionary());
+ ExpressionPtr expr;
+ CfgOptionPtr cfg_option;
+
+ // First class has no options.
+ ASSERT_NO_THROW(dictionary->addClass("foo", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make some options for the second class.
+ cfg_option.reset(new CfgOption());
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ OptionDescriptorPtr desc = OptionDescriptor::create(option, true, false,
+ "bogus-file.txt");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ cfg_option->add(*desc, desc->space_name_);
+
+ option = Option::create(Option::V4, DHO_TFTP_SERVER_NAME);
+ desc = OptionDescriptor::create(option, true, false, "bogus-tftp-server");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ cfg_option->add(*desc, desc->space_name_);
+
+ // Add the second class with options.
+ ASSERT_NO_THROW(dictionary->addClass("bar", expr, "", false,
+ false, cfg_option, CfgOptionDefPtr(),
+ ConstElementPtr(), IOAddress("0.0.0.0"),
+ "", "", Triplet<uint32_t>(),
+ Triplet<uint32_t>(), true));
+
+ // Make sure first class has no options.
+ ASSERT_TRUE(dictionary->getClasses());
+ auto classes = *(dictionary->getClasses());
+ auto options = classes[0]->getCfgOption();
+ ASSERT_TRUE(options->empty());
+
+ // Make sure second class has both options but their
+ // data buffers are empty.
+ options = classes[1]->getCfgOption();
+ ASSERT_FALSE(options->empty());
+
+ auto option_desc = options->get("dhcp4", DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(option_desc.option_);
+ ASSERT_TRUE(option_desc.option_->getData().empty());
+
+ option_desc = options->get("dhcp4", DHO_TFTP_SERVER_NAME);
+ ASSERT_TRUE(option_desc.option_);
+ ASSERT_TRUE(option_desc.option_->getData().empty());
+
+ // Now create match expressions for all of them.
+ auto cfg_def = CfgMgr::instance().getCurrentCfg()->getCfgOptionDef();
+ ASSERT_NO_THROW(dictionary->createOptions(cfg_def));
+
+ // Make sure first class still has no options.
+ classes = *(dictionary->getClasses());
+ options = classes[0]->getCfgOption();
+ ASSERT_TRUE(options->empty());
+
+ // Make sure second class has both options and that their
+ // data buffers are now correctly populated.
+ options = classes[1]->getCfgOption();
+ ASSERT_FALSE(options->empty());
+
+ option_desc = options->get("dhcp4", DHO_BOOT_FILE_NAME);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(), option_desc.formatted_value_.end()),
+ option->getData());
+
+ option_desc = options->get("dhcp4", DHO_TFTP_SERVER_NAME);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(), option_desc.formatted_value_.end()),
+ option->getData());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc b/src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc
new file mode 100644
index 0000000..fcc59b8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc
@@ -0,0 +1,649 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/testutils/lease_file_io.h>
+#include <gtest/gtest.h>
+#include <ctime>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+// HWADDR values used by unit tests.
+const uint8_t HWADDR0[] = { 0, 1, 2, 3, 4, 5 };
+const uint8_t HWADDR1[] = { 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf };
+
+const uint8_t CLIENTID[] = { 1, 2, 3, 4 };
+
+/// @brief Test fixture class for @c CSVLeaseFile4 validation.
+class CSVLeaseFile4Test : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes IO for lease file used by unit tests.
+ CSVLeaseFile4Test();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Creates the lease file to be parsed by unit tests.
+ void writeSampleFile() const;
+
+ /// @brief Checks the stats for the file
+ ///
+ /// This method is passed a leasefile and the values for the statistics it
+ /// should have for comparison.
+ ///
+ /// @param lease_file A reference to the file we are using
+ /// @param reads the number of attempted reads
+ /// @param read_leases the number of valid leases read
+ /// @param read_errs the number of errors while reading leases
+ /// @param writes the number of attempted writes
+ /// @param write_leases the number of leases successfully written
+ /// @param write_errs the number of errors while writing
+ void checkStats(CSVLeaseFile4& lease_file,
+ uint32_t reads, uint32_t read_leases,
+ uint32_t read_errs, uint32_t writes,
+ uint32_t write_leases, uint32_t write_errs) const {
+ EXPECT_EQ(reads, lease_file.getReads());
+ EXPECT_EQ(read_leases, lease_file.getReadLeases());
+ EXPECT_EQ(read_errs, lease_file.getReadErrs());
+ EXPECT_EQ(writes, lease_file.getWrites());
+ EXPECT_EQ(write_leases, lease_file.getWriteLeases());
+ EXPECT_EQ(write_errs, lease_file.getWriteErrs());
+ }
+
+ /// @brief Name of the test lease file.
+ std::string filename_;
+
+ /// @brief Object providing access to lease file IO.
+ LeaseFileIO io_;
+
+ /// @brief hardware address 0 (corresponds to HWADDR0 const)
+ HWAddrPtr hwaddr0_;
+
+ /// @brief hardware address 1 (corresponds to HWADDR1 const)
+ HWAddrPtr hwaddr1_;
+
+};
+
+CSVLeaseFile4Test::CSVLeaseFile4Test()
+ : filename_(absolutePath("leases4.csv")), io_(filename_) {
+ hwaddr0_.reset(new HWAddr(HWADDR0, sizeof(HWADDR0), HTYPE_ETHER));
+ hwaddr1_.reset(new HWAddr(HWADDR1, sizeof(HWADDR1), HTYPE_ETHER));
+}
+
+std::string
+CSVLeaseFile4Test::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << DHCP_DATA_DIR << "/" << filename;
+ return (s.str());
+}
+
+void
+CSVLeaseFile4Test::writeSampleFile() const {
+ io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context\n"
+ "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,"
+ "host.example.com,0,\n"
+ "192.0.2.2,,,200,200,8,1,1,host.example.com,0,\n"
+ "192.0.2.3,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,100,7,"
+ "0,0,,1,{ \"foobar\": true }\n"
+ "192.0.2.4,,11:22:33:44:55:66,200,200,8,1,1,host.example.com,0,\n"
+ "192.0.2.5,,,200,200,8,1,1,,1,\n");
+}
+
+// This test checks the capability to read and parse leases from the file.
+TEST_F(CSVLeaseFile4Test, parse) {
+ // Create a file to be parsed.
+ writeSampleFile();
+
+ // Open the lease file.
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+
+ // Verify the counters are cleared
+ {
+ SCOPED_TRACE("Check stats are empty");
+ checkStats(lf, 0, 0, 0, 0, 0, 0);
+ }
+
+ Lease4Ptr lease;
+ // Reading first read should be successful.
+ {
+ SCOPED_TRACE("First lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 1, 1, 0, 0, 0, 0);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("192.0.2.1", lease->addr_.toText());
+ HWAddr hwaddr1(*lease->hwaddr_);
+ EXPECT_EQ("06:07:08:09:0a:bc", hwaddr1.toText(false));
+ EXPECT_FALSE(lease->client_id_);
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("host.example.com", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ // Second lease is malformed - has no HW address or client id and state
+ // is not declined.
+ {
+ SCOPED_TRACE("Second lease malformed");
+ EXPECT_FALSE(lf.next(lease));
+ EXPECT_FALSE(lease);
+ checkStats(lf, 2, 1, 1, 0, 0, 0);
+ }
+
+ // Even though parsing previous lease failed, reading the next lease should be
+ // successful.
+ {
+ SCOPED_TRACE("Third lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 3, 2, 1, 0, 0, 0);
+
+ // Verify that the third lease is correct.
+ EXPECT_EQ("192.0.2.3", lease->addr_.toText());
+ HWAddr hwaddr3(*lease->hwaddr_);
+ EXPECT_EQ("dd:de:ba:0d:1b:2e:3e:4f", hwaddr3.toText(false));
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_EQ("0a:00:01:04", lease->client_id_->toText());
+ EXPECT_EQ(100, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(7, lease->subnet_id_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_TRUE(lease->hostname_.empty());
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
+ }
+
+ // Fourth lease has no hardware address but has client id
+ {
+ SCOPED_TRACE("Fourth lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 4, 3, 1, 0, 0, 0);
+
+ EXPECT_EQ("192.0.2.4", lease->addr_.toText());
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(lease->hwaddr_->hwaddr_.empty());
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_EQ("11:22:33:44:55:66", lease->client_id_->toText());
+ }
+
+ // Fifth lease has no hardware address or client id but is declined
+ {
+ SCOPED_TRACE("Fifth lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 5, 4, 1, 0, 0, 0);
+
+ EXPECT_EQ("192.0.2.5", lease->addr_.toText());
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_TRUE(lease->hwaddr_->hwaddr_.empty());
+ ASSERT_FALSE(lease->client_id_);
+ EXPECT_EQ(lease->state_, Lease::STATE_DECLINED);
+ }
+
+ // There are no more leases. Reading should cause no error, but the returned
+ // lease pointer should be NULL.
+ {
+ SCOPED_TRACE("Sixth read empty");
+ EXPECT_TRUE(lf.next(lease));
+ EXPECT_FALSE(lease);
+ checkStats(lf, 6, 4, 1, 0, 0, 0);
+ }
+
+ // We should be able to do it again.
+ {
+ SCOPED_TRACE("Seventh read empty");
+ EXPECT_TRUE(lf.next(lease));
+ EXPECT_FALSE(lease);
+ checkStats(lf, 7, 4, 1, 0, 0, 0);
+ }
+}
+
+// This test checks creation of the lease file and writing leases.
+TEST_F(CSVLeaseFile4Test, recreate) {
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ // Verify the counters are cleared
+ checkStats(lf, 0, 0, 0, 0, 0, 0);
+
+ // Create first lease, with NULL client id.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
+ hwaddr0_,
+ NULL, 0,
+ 200, 0, 8, true, true,
+ "host.example.com"));
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ {
+ SCOPED_TRACE("First write");
+ ASSERT_NO_THROW(lf.append(*lease));
+ checkStats(lf, 0, 0, 0, 1, 1, 0);
+ }
+
+ // Create second lease, with non-NULL client id and user context.
+ lease.reset(new Lease4(IOAddress("192.0.3.10"),
+ hwaddr1_,
+ CLIENTID, sizeof(CLIENTID),
+ 100, 0, 7));
+ lease->setContext(Element::fromJSON("{ \"foobar\": true }"));
+ {
+ SCOPED_TRACE("Second write");
+ ASSERT_NO_THROW(lf.append(*lease));
+ checkStats(lf, 0, 0, 0, 2, 2, 0);
+ }
+
+ // Close the lease file.
+ lf.close();
+ // Check that the contents of the csv file are correct.
+ EXPECT_EQ("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.3.2,00:01:02:03:04:05,,200,200,8,1,1,host.example.com,"
+ "2,,0\n"
+ "192.0.3.10,0d:0e:0a:0d:0b:0e:0e:0f,01:02:03:04,100,100,7,0,"
+ "0,,0,{ \"foobar\": true },0\n",
+ io_.readFile());
+}
+
+// Verifies that a schema 1.0 file with records from
+// schema 1.0 and 2.0 loads correctly.
+TEST_F(CSVLeaseFile4Test, mixedSchemaload) {
+ // Create mixed schema file
+ io_.writeFile(
+ // schema 1.0 header
+ "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname\n"
+ // schema 1.0 record
+ "192.0.2.1,06:07:08:09:1a:bc,,200,200,8,1,1,"
+ "one.example.com\n"
+ // schema 2.0 record - has state
+ "192.0.2.2,06:07:08:09:2a:bc,,200,200,8,1,1,"
+ "two.example.com,1\n"
+ // schema 2.1 record - has state and user context
+ "192.0.2.3,06:07:08:09:3a:bc,,200,200,8,1,1,"
+ "three.example.com,2,{ \"foobar\": true }\n"
+ );
+
+ // Open the lease file.
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+
+ Lease4Ptr lease;
+
+ // Reading first read should be successful.
+ {
+ SCOPED_TRACE("First lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("192.0.2.1", lease->addr_.toText());
+ HWAddr hwaddr1(*lease->hwaddr_);
+ EXPECT_EQ("06:07:08:09:1a:bc", hwaddr1.toText(false));
+ EXPECT_FALSE(lease->client_id_);
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("one.example.com", lease->hostname_);
+ // Verify that added state is DEFAULT
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ {
+ SCOPED_TRACE("Second lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("192.0.2.2", lease->addr_.toText());
+ HWAddr hwaddr1(*lease->hwaddr_);
+ EXPECT_EQ("06:07:08:09:2a:bc", hwaddr1.toText(false));
+ EXPECT_FALSE(lease->client_id_);
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("two.example.com", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ {
+ SCOPED_TRACE("Third lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the third lease is correct.
+ EXPECT_EQ("192.0.2.3", lease->addr_.toText());
+ HWAddr hwaddr1(*lease->hwaddr_);
+ EXPECT_EQ("06:07:08:09:3a:bc", hwaddr1.toText(false));
+ EXPECT_FALSE(lease->client_id_);
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("three.example.com", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_EXPIRED_RECLAIMED, lease->state_);
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
+ }
+}
+
+// Verifies that a lease file with fewer header columns than the
+// minimum allowed will not open.
+TEST_F(CSVLeaseFile4Test, tooFewHeaderColumns) {
+ // Create 1.0 file
+ io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev\n");
+
+ // Open the lease file.
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_THROW(lf.open(), CSVFileError);
+}
+
+// Verifies that a lease file with an unrecognized column header
+// will not open.
+TEST_F(CSVLeaseFile4Test, invalidHeaderColumn) {
+ // Create 1.0 file
+ io_.writeFile("address,hwaddr,BOGUS,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context\n");
+
+ // Open the lease file.
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_THROW(lf.open(), CSVFileError);
+}
+
+// Verifies that a lease file with more header columns than defined
+// columns will downgrade.
+TEST_F(CSVLeaseFile4Test, downGrade) {
+ // Create 3.0 PLUS a column file
+ io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id,FUTURE_COL\n"
+
+ "192.0.2.3,06:07:08:09:3a:bc,,200,200,8,1,1,"
+ "three.example.com,2,,0,FUTURE_VALUE\n");
+
+ // Lease file should open and report as needing downgrade.
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+ EXPECT_TRUE(lf.needsConversion());
+ EXPECT_EQ(util::VersionedCSVFile::NEEDS_DOWNGRADE,
+ lf.getInputSchemaState());
+ Lease4Ptr lease;
+
+ {
+ SCOPED_TRACE("First lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the third lease is correct.
+ EXPECT_EQ("192.0.2.3", lease->addr_.toText());
+ HWAddr hwaddr1(*lease->hwaddr_);
+ EXPECT_EQ("06:07:08:09:3a:bc", hwaddr1.toText(false));
+ EXPECT_FALSE(lease->client_id_);
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("three.example.com", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_EXPIRED_RECLAIMED, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+}
+
+// Verifies that leases with no hardware address are only permitted
+// if they are in the declined state.
+TEST_F(CSVLeaseFile4Test, declinedLeaseTest) {
+ io_.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.1,,,200,200,8,1,1,host.example.com,0,,0\n"
+ "192.0.2.1,,,200,200,8,1,1,host.example.com,1,,0\n");
+
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+ EXPECT_FALSE(lf.needsConversion());
+ EXPECT_EQ(util::VersionedCSVFile::CURRENT, lf.getInputSchemaState());
+ Lease4Ptr lease;
+
+ {
+ SCOPED_TRACE("No hardware and not declined, invalid");
+ EXPECT_FALSE(lf.next(lease));
+ ASSERT_FALSE(lease);
+ EXPECT_EQ(lf.getReadErrs(),1);
+ }
+
+ {
+ SCOPED_TRACE("No hardware and declined, valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lf.getReadErrs(),1);
+ }
+}
+
+// Verifies that it is possible to output a lease with very high valid
+// lifetime (infinite in RFC2131 terms) and current time, and then read
+// back this lease.
+TEST_F(CSVLeaseFile4Test, highLeaseLifetime) {
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ // Write lease with very high lease lifetime and current time.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
+ hwaddr0_,
+ NULL, 0,
+ 0xFFFFFFFF, time(0),
+ 8, true, true,
+ "host.example.com"));
+ // Write this lease out to the lease file.
+ ASSERT_NO_THROW(lf.append(*lease));
+
+ // Close the lease file.
+ lf.close();
+
+ Lease4Ptr lease_read;
+
+ // Re-open the file for reading.
+ ASSERT_NO_THROW(lf.open());
+
+ // Read the lease and make sure it is successful.
+ EXPECT_TRUE(lf.next(lease_read));
+ ASSERT_TRUE(lease_read);
+
+ // The valid lifetime and the cltt should match with the original lease.
+ EXPECT_EQ(lease->valid_lft_, lease_read->valid_lft_);
+ EXPECT_EQ(lease->cltt_, lease_read->cltt_);
+}
+
+// Verifies that it is not possible to output a lease with empty hwaddr in other
+// than the declined state
+TEST_F(CSVLeaseFile4Test, emptyHWAddrDefaultStateOnly) {
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ HWAddrPtr hwaddr;
+
+ // Create lease with null hwaddr and default state
+ Lease4Ptr lease_null_hwaddr(new Lease4(IOAddress("192.0.3.2"),
+ hwaddr,
+ NULL, 0,
+ 0xFFFFFFFF, time(0),
+ 8, true, true,
+ "host.example.com"));
+ // Try to write this lease out to the lease file.
+ ASSERT_THROW(lf.append(*lease_null_hwaddr), BadValue);
+
+ hwaddr.reset(new HWAddr());
+
+ // Create lease with empty hwaddr and default state
+ Lease4Ptr lease_empty_hwaddr(new Lease4(IOAddress("192.0.3.2"),
+ hwaddr,
+ NULL, 0,
+ 0xFFFFFFFF, time(0),
+ 8, true, true,
+ "host.example.com"));
+ // Try to write this lease out to the lease file.
+ ASSERT_THROW(lf.append(*lease_empty_hwaddr), BadValue);
+
+ // Create lease with hwaddr and current time.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
+ hwaddr0_,
+ NULL, 0,
+ 0xFFFFFFFF, time(0),
+ 8, true, true,
+ "host.example.com"));
+
+ // Decline the lease
+ lease->decline(1000);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ(lease->hwaddr_->toText(false), "");
+
+ // Write this lease out to the lease file.
+ ASSERT_NO_THROW(lf.append(*lease));
+
+ // Close the lease file.
+ lf.close();
+
+ Lease4Ptr lease_read;
+
+ // Re-open the file for reading.
+ ASSERT_NO_THROW(lf.open());
+
+ // Read the lease and make sure it is successful.
+ EXPECT_TRUE(lf.next(lease_read));
+ ASSERT_TRUE(lease_read);
+
+ // The valid lifetime and the cltt should match with the original lease.
+ EXPECT_EQ(lease->valid_lft_, lease_read->valid_lft_);
+ EXPECT_EQ(lease->cltt_, lease_read->cltt_);
+}
+
+// Verifies that it is possible to write and read a lease with commas
+// in hostname and user context.
+TEST_F(CSVLeaseFile4Test, embeddedCommas) {
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ std::string hostname("host,example,com");
+ std::string context_str("{ \"bar\": true, \"foo\": false, \"x\": \"factor\" }");
+
+ // Create a lease with commas in the hostname.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
+ hwaddr0_,
+ NULL, 0,
+ 0xFFFFFFFF, time(0),
+ 8, true, true,
+ hostname));
+
+ // Add the user context with commas.
+ lease->setContext(Element::fromJSON(context_str));
+
+ // Write this lease out to the lease file.
+ ASSERT_NO_THROW(lf.append(*lease));
+
+ // Close the lease file.
+ lf.close();
+
+ Lease4Ptr lease_read;
+
+ // Re-open the file for reading.
+ ASSERT_NO_THROW(lf.open());
+
+ // Read the lease and make sure it is successful.
+ EXPECT_TRUE(lf.next(lease_read));
+ ASSERT_TRUE(lease_read);
+
+ // Expect the hostname and user context to retain the commas
+ // they started with.
+ EXPECT_EQ(hostname, lease->hostname_);
+ EXPECT_EQ(context_str, lease->getContext()->str());
+}
+
+// Verifies that it is possible to write and read a lease with
+// escape tags and sequences in hostname and user context.
+TEST_F(CSVLeaseFile4Test, embeddedEscapes) {
+ CSVLeaseFile4 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ std::string hostname("host&#xexample&#x2ccom");
+ std::string context_str("{ \"&#xbar\": true, \"foo\": false, \"x\": \"fac&#x2ctor\" }");
+
+ // Create a lease with commas in the hostname.
+ Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
+ hwaddr0_,
+ NULL, 0,
+ 0xFFFFFFFF, time(0),
+ 8, true, true,
+ hostname));
+
+ // Add the user context with commas.
+ lease->setContext(Element::fromJSON(context_str));
+
+ // Write this lease out to the lease file.
+ ASSERT_NO_THROW(lf.append(*lease));
+
+ // Close the lease file.
+ lf.close();
+
+ Lease4Ptr lease_read;
+
+ // Re-open the file for reading.
+ ASSERT_NO_THROW(lf.open());
+
+ // Read the lease and make sure it is successful.
+ EXPECT_TRUE(lf.next(lease_read));
+ ASSERT_TRUE(lease_read);
+
+ // Expect the hostname and user context to retain the commas
+ // they started with.
+ EXPECT_EQ(hostname, lease->hostname_);
+ EXPECT_EQ(context_str, lease->getContext()->str());
+}
+
+/// @todo Currently we don't check invalid lease attributes, such as invalid
+/// lease type, invalid preferred lifetime vs valid lifetime etc. The Lease6
+/// should be extended with the function that validates lease attributes. Once
+/// this is implemented we should provide more tests for malformed leases
+/// in the CSV file.
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc b/src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc
new file mode 100644
index 0000000..227f960
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/csv_lease_file6_unittest.cc
@@ -0,0 +1,717 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/testutils/lease_file_io.h>
+#include <gtest/gtest.h>
+#include <ctime>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+// DUID values used by unit tests.
+const uint8_t DUID0[] = { 0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
+const uint8_t DUID1[] = { 1, 1, 1, 1, 0xa, 1, 2, 3, 4, 5 };
+
+/// @brief Test fixture class for @c CSVLeaseFile6 validation.
+class CSVLeaseFile6Test : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes IO for lease file used by unit tests.
+ CSVLeaseFile6Test();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Create DUID object from the binary.
+ ///
+ /// @param duid Binary value representing a DUID.
+ /// @param size Size of the DUID.
+ /// @return Pointer to the @c DUID object.
+ DuidPtr makeDUID(const uint8_t* duid, const unsigned int size) const {
+ return (DuidPtr(new DUID(duid, size)));
+ }
+
+ /// @brief Create lease file that can be parsed by unit tests.
+ void writeSampleFile() const;
+
+ /// @brief Checks the stats for the file
+ ///
+ /// This method is passed a leasefile and the values for the statistics it
+ /// should have for comparison.
+ ///
+ /// @param lease_file A reference to the file we are using
+ /// @param reads the number of attempted reads
+ /// @param read_leases the number of valid leases read
+ /// @param read_errs the number of errors while reading leases
+ /// @param writes the number of attempted writes
+ /// @param write_leases the number of leases successfully written
+ /// @param write_errs the number of errors while writing
+ void checkStats(CSVLeaseFile6& lease_file,
+ uint32_t reads, uint32_t read_leases,
+ uint32_t read_errs, uint32_t writes,
+ uint32_t write_leases, uint32_t write_errs) const {
+ EXPECT_EQ(reads, lease_file.getReads());
+ EXPECT_EQ(read_leases, lease_file.getReadLeases());
+ EXPECT_EQ(read_errs, lease_file.getReadErrs());
+ EXPECT_EQ(writes, lease_file.getWrites());
+ EXPECT_EQ(write_leases, lease_file.getWriteLeases());
+ EXPECT_EQ(write_errs, lease_file.getWriteErrs());
+ }
+
+ /// @brief Name of the test lease file.
+ std::string filename_;
+
+ /// @brief Object providing access to lease file IO.
+ LeaseFileIO io_;
+
+};
+
+CSVLeaseFile6Test::CSVLeaseFile6Test()
+ : filename_(absolutePath("leases6.csv")), io_(filename_) {
+}
+
+std::string
+CSVLeaseFile6Test::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << DHCP_DATA_DIR << "/" << filename;
+ return (s.str());
+}
+
+void
+CSVLeaseFile6Test::writeSampleFile() const {
+ io_.writeFile("address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "200,200,8,100,0,7,0,1,1,host.example.com,,1,,"
+ "1,0,0\n"
+ "2001:db8:1::1,,200,200,8,100,0,7,0,1,1,host.example.com,,1,,"
+ "1,0,0\n"
+ "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,300,300,6,150,"
+ "0,8,0,0,0,,,1,,"
+ "1,0,0\n"
+ "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,0,200,8,0,2,"
+ "16,64,0,0,,,1,{ \"foobar\": true },,"
+ "1,0,0\n"
+ "2001:db8:1::2,00:00:00,200,200,8,100,0,7,0,1,1,host.example.com,,0,,"
+ "1,0,0\n"
+ "2001:db8:1::3,00:00:00,200,200,8,100,0,7,0,1,1,host.example.com,,1,,"
+ "1,0,0\n");
+}
+
+// This test checks the capability to read and parse leases from the file.
+TEST_F(CSVLeaseFile6Test, parse) {
+ // Create a file to be parsed.
+ writeSampleFile();
+
+ // Open the lease file.
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+
+ // Verify the counters are cleared
+ {
+ SCOPED_TRACE("Check stats are empty");
+ checkStats(lf, 0, 0, 0, 0, 0, 0);
+ }
+
+ Lease6Ptr lease;
+ // Reading first read should be successful.
+ {
+ SCOPED_TRACE("First lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 1, 1, 0, 0, 0, 0);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_EQ(100, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(7, lease->iaid_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("host.example.com", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ // Second lease is malformed - DUID is blank (i.e. ",,")
+ {
+ SCOPED_TRACE("Second lease malformed");
+ EXPECT_FALSE(lf.next(lease));
+ checkStats(lf, 2, 1, 1, 0, 0, 0);
+ }
+
+ // Even, parsing previous lease failed, reading the next lease should be
+ // successful.
+ {
+ SCOPED_TRACE("Third lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 3, 2, 1, 0, 0, 0);
+
+ // Verify that the third lease is correct.
+ EXPECT_EQ("2001:db8:2::10", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("01:01:01:01:0a:01:02:03:04:05", lease->duid_->toText());
+ EXPECT_EQ(300, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(6, lease->subnet_id_);
+ EXPECT_EQ(150, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(8, lease->iaid_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_TRUE(lease->hostname_.empty());
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ // Reading the fourth lease should be successful.
+ {
+ SCOPED_TRACE("Fourth lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 4, 3, 1, 0, 0, 0);
+
+ // Verify that the lease is correct.
+ EXPECT_EQ("3000:1::", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
+ EXPECT_EQ(0, lease->valid_lft_);
+ EXPECT_EQ(200, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_EQ(0, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_PD, lease->type_);
+ EXPECT_EQ(16, lease->iaid_);
+ EXPECT_EQ(64, lease->prefixlen_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_TRUE(lease->hostname_.empty());
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
+ }
+
+ // Fifth lease is invalid - DUID is empty, state is not DECLINED
+ {
+ SCOPED_TRACE("Fifth lease invalid");
+ EXPECT_FALSE(lf.next(lease));
+ checkStats(lf, 5, 3, 2, 0, 0, 0);
+ }
+
+ // Reading the sixth lease should be successful.
+ {
+ SCOPED_TRACE("sixth lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+ checkStats(lf, 6, 4, 2, 0, 0, 0);
+
+ // Verify that the lease is correct.
+ EXPECT_EQ("2001:db8:1::3", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:00:00", lease->duid_->toText());
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ }
+
+ // There are no more leases. Reading should cause no error, but the returned
+ // lease pointer should be NULL.
+ {
+ SCOPED_TRACE("Sixth read empty");
+ EXPECT_TRUE(lf.next(lease));
+ EXPECT_FALSE(lease);
+ checkStats(lf, 7, 4, 2, 0, 0, 0);
+ }
+
+ // We should be able to do it again.
+ {
+ SCOPED_TRACE("Seventh read empty");
+ EXPECT_TRUE(lf.next(lease));
+ EXPECT_FALSE(lease);
+ checkStats(lf, 8, 4, 2, 0, 0, 0);
+ }
+}
+
+// This test checks creation of the lease file and writing leases.
+TEST_F(CSVLeaseFile6Test, recreate) {
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ // Verify the counters are cleared
+ {
+ SCOPED_TRACE("Check stats are empty");
+ checkStats(lf, 0, 0, 0, 0, 0, 0);
+ }
+
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ makeDUID(DUID0, sizeof(DUID0)),
+ 7, 100, 200, 8, true, true,
+ "host.example.com"));
+ lease->cltt_ = 0;
+ {
+ SCOPED_TRACE("First write");
+ ASSERT_NO_THROW(lf.append(*lease));
+ checkStats(lf, 0, 0, 0, 1, 1, 0);
+ }
+
+ lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::10"),
+ makeDUID(DUID1, sizeof(DUID1)),
+ 8, 150, 300, 6, false, false,
+ "", HWAddrPtr(), 128));
+ lease->cltt_ = 0;
+ {
+ SCOPED_TRACE("Second write");
+ ASSERT_NO_THROW(lf.append(*lease));
+ checkStats(lf, 0, 0, 0, 2, 2, 0);
+ }
+
+ lease.reset(new Lease6(Lease::TYPE_PD, IOAddress("3000:1:1::"),
+ makeDUID(DUID0, sizeof(DUID0)),
+ 7, 150, 300, 10, false, false,
+ "", HWAddrPtr(), 64));
+ lease->cltt_ = 0;
+ lease->setContext(Element::fromJSON("{ \"foobar\": true }"));
+ {
+ SCOPED_TRACE("Third write");
+ ASSERT_NO_THROW(lf.append(*lease));
+ checkStats(lf, 0, 0, 0, 3, 3, 0);
+ }
+
+ DuidPtr empty(new DUID(DUID::EMPTY()));
+ lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::10"),
+ empty, 8, 150, 300, 6, false, false,
+ "", HWAddrPtr(), 128));
+ lease->cltt_ = 0;
+ {
+ SCOPED_TRACE("Fourth write - invalid, no DUID, not declined");
+ ASSERT_THROW(lf.append(*lease), BadValue);
+ checkStats(lf, 0, 0, 0, 4, 3, 1);
+ }
+
+ {
+ SCOPED_TRACE("Fifth write - valid, no DUID, declined");
+ lease->state_ = Lease::STATE_DECLINED;
+ ASSERT_NO_THROW(lf.append(*lease));
+ checkStats(lf, 0, 0, 0, 5, 4, 1);
+ }
+
+ EXPECT_EQ("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,hwaddr,"
+ "state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "200,200,8,100,0,7,128,1,1,host.example.com,,0,,,,0\n"
+ "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05"
+ ",300,300,6,150,0,8,128,0,0,,,0,,,,0\n"
+ "3000:1:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "300,300,10,150,2,7,64,0,0,,,0,{ \"foobar\": true },,,0\n"
+ "2001:db8:2::10,00:00:00,300,300,6,150,0,8,128,0,0,,,1,,,,0\n",
+ io_.readFile());
+}
+
+// Verifies that a 1.0 schema file with records from
+// schema 1.0, 2.0, and 3.0 loads correctly.
+TEST_F(CSVLeaseFile6Test, mixedSchemaLoad) {
+ // Create a mixed schema file
+ io_.writeFile(
+ // schema 1.0 header
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname\n"
+ // schema 1.0 record
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:01,"
+ "200,200,8,100,0,7,0,1,1,one.example.com\n"
+
+ // schema 2.0 record - has hwaddr
+ "2001:db8:1::2,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:02,"
+ "200,200,8,100,0,7,0,1,1,two.example.com,01:02:03:04:05\n"
+
+ // schema 3.0 record - has hwaddr and state
+ "2001:db8:1::3,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:03,"
+ "200,200,8,100,0,7,0,1,1,three.example.com,0a:0b:0c:0d:0e,1\n"
+
+ // schema 3.1 record - has hwaddr, state and user context
+ "2001:db8:1::4,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:03,"
+ "200,200,8,100,0,7,0,1,1,three.example.com,0a:0b:0c:0d:0e,1,"
+ "{ \"foobar\": true }\n");
+
+ // Open the lease file.
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+
+ Lease6Ptr lease;
+ {
+ SCOPED_TRACE("First lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:01", lease->duid_->toText());
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_EQ(100, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(7, lease->iaid_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("one.example.com", lease->hostname_);
+ // Verify that added HWaddr is empty
+ EXPECT_FALSE(lease->hwaddr_);
+ // Verify that added state is STATE_DEFAULT
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ {
+ SCOPED_TRACE("Second lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("2001:db8:1::2", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:02", lease->duid_->toText());
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_EQ(100, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(7, lease->iaid_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("two.example.com", lease->hostname_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ("01:02:03:04:05", lease->hwaddr_->toText(false));
+ // Verify that added state is STATE_DEFAULT
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ {
+ SCOPED_TRACE("Third lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("2001:db8:1::3", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:03", lease->duid_->toText());
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_EQ(100, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(7, lease->iaid_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("three.example.com", lease->hostname_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ("0a:0b:0c:0d:0e", lease->hwaddr_->toText(false));
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ {
+ SCOPED_TRACE("Forth lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("2001:db8:1::4", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:03", lease->duid_->toText());
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_EQ(100, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(7, lease->iaid_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("three.example.com", lease->hostname_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ("0a:0b:0c:0d:0e", lease->hwaddr_->toText(false));
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
+ }
+
+}
+
+// Verifies that a lease file with fewer header columns than the
+// minimum allowed will not open.
+TEST_F(CSVLeaseFile6Test, tooFewHeaderColumns) {
+ io_.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev\n");
+
+ // Open should fail.
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_THROW(lf.open(), CSVFileError);
+}
+
+// Verifies that a lease file with an unrecognized column header
+// will not open.
+TEST_F(CSVLeaseFile6Test, invalidHeaderColumn) {
+ io_.writeFile("address,BOGUS,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context\n");
+
+ // Open should fail.
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_THROW(lf.open(), CSVFileError);
+}
+
+// Verifies that a lease file with more header columns than defined
+// columns will open as needing a downgrade.
+TEST_F(CSVLeaseFile6Test, downGrade) {
+ // Create a mixed schema file
+ io_.writeFile(
+ // schema 5.0 header
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id,FUTURE_COLUMN\n"
+
+ // schema 5.0 record
+ "2001:db8:1::3,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:03,"
+ "200,200,8,100,0,7,0,1,1,three.example.com,0a:0b:0c:0d:0e,1,"
+ "{ \"foobar\": true },1,0,0,FUTURE_VALUE\n");
+
+ // Open should succeed in the event someone is downgrading.
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+ EXPECT_TRUE(lf.needsConversion());
+ EXPECT_EQ(util::VersionedCSVFile::NEEDS_DOWNGRADE,
+ lf.getInputSchemaState());
+
+ Lease6Ptr lease;
+ {
+ SCOPED_TRACE("First lease valid");
+ EXPECT_TRUE(lf.next(lease));
+ ASSERT_TRUE(lease);
+
+ // Verify that the lease attributes are correct.
+ EXPECT_EQ("2001:db8:1::3", lease->addr_.toText());
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:03", lease->duid_->toText());
+ EXPECT_EQ(200, lease->valid_lft_);
+ EXPECT_EQ(0, lease->cltt_);
+ EXPECT_EQ(8, lease->subnet_id_);
+ EXPECT_EQ(100, lease->preferred_lft_);
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(7, lease->iaid_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("three.example.com", lease->hostname_);
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ("0a:0b:0c:0d:0e", lease->hwaddr_->toText(false));
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
+ EXPECT_EQ(1, lease->hwaddr_->htype_);
+ EXPECT_EQ(0, lease->hwaddr_->source_);
+ }
+}
+
+// Verifies that leases with no DUID are invalid, and that leases
+// with the "Empty" DUID (1 byte duid = 0x0) are valid only when
+// in the declined state.
+TEST_F(CSVLeaseFile6Test, declinedLeaseTest) {
+ io_.writeFile("address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::1,00:00:00,"
+ "200,200,8,100,0,7,0,1,1,host.example.com,,0,,,,0\n"
+ "2001:db8:1::1,,"
+ "200,200,8,100,0,7,0,1,1,host.example.com,,0,,,,0\n"
+ "2001:db8:1::1,00:00:00,"
+ "200,200,8,100,0,7,0,1,1,host.example.com,,1,,,,0\n");
+
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.open());
+ EXPECT_FALSE(lf.needsConversion());
+ EXPECT_EQ(util::VersionedCSVFile::CURRENT, lf.getInputSchemaState());
+ Lease6Ptr lease;
+
+ {
+ SCOPED_TRACE("\"Empty\" DUID and not declined, invalid");
+ EXPECT_FALSE(lf.next(lease));
+ EXPECT_FALSE(lease);
+ EXPECT_EQ(lf.getReadErrs(), 1);
+ EXPECT_EQ(lf.getReadMsg(),
+ "The Empty DUID is only valid for declined leases");
+ }
+
+ {
+ SCOPED_TRACE("Missing (blank) DUID and not declined, invalid");
+ EXPECT_FALSE(lf.next(lease));
+ EXPECT_FALSE(lease);
+ EXPECT_EQ(lf.getReadErrs(), 2);
+ EXPECT_EQ(lf.getReadMsg(), "identifier is too short (0), at least 3 is required");
+ }
+
+ {
+ SCOPED_TRACE("Empty DUID and declined, valid");
+ EXPECT_TRUE(lf.next(lease));
+ EXPECT_TRUE(lease);
+ EXPECT_EQ(lf.getReadErrs(), 2);
+ EXPECT_EQ(lf.getReadMsg(), "validation not started");
+ }
+}
+
+// Verifies that it is possible to output a lease with very high valid
+// lifetime (infinite in RFC2131 terms) and current time, and then read
+// back this lease.
+TEST_F(CSVLeaseFile6Test, highLeaseLifetime) {
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ // Write lease with very high lease lifetime and current time.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ makeDUID(DUID0, sizeof(DUID0)),
+ 7, 100, 0xFFFFFFFF, 8, true, true,
+ "host.example.com"));
+
+ // Write this lease out to the lease file.
+ ASSERT_NO_THROW(lf.append(*lease));
+
+ // Close the lease file.
+ lf.close();
+
+ Lease6Ptr lease_read;
+
+ // Re-open the file for reading.
+ ASSERT_NO_THROW(lf.open());
+
+ // Read the lease and make sure it is successful.
+ EXPECT_TRUE(lf.next(lease_read));
+ ASSERT_TRUE(lease_read);
+
+ // The valid lifetime and the cltt should match with the original lease.
+ EXPECT_EQ(lease->valid_lft_, lease_read->valid_lft_);
+ EXPECT_EQ(lease->cltt_, lease_read->cltt_);
+}
+
+// Verifies that it is possible to write and read a lease with commas
+// in hostname and user context.
+TEST_F(CSVLeaseFile6Test, embeddedCommas) {
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ std::string hostname("host,example,com");
+ std::string context_str("{ \"bar\": true, \"foo\": false, \"x\": \"factor\" }");
+
+ // Create a lease with commas in the hostname.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ makeDUID(DUID0, sizeof(DUID0)),
+ 7, 100, 0xFFFFFFFF, 8, true, true,
+ hostname));
+
+ // Add the user context with commas.
+ lease->setContext(Element::fromJSON(context_str));
+
+ // Write this lease out to the lease file.
+ ASSERT_NO_THROW(lf.append(*lease));
+
+ // Close the lease file.
+ lf.close();
+
+ Lease6Ptr lease_read;
+
+ // Re-open the file for reading.
+ ASSERT_NO_THROW(lf.open());
+
+ // Read the lease and make sure it is successful.
+ EXPECT_TRUE(lf.next(lease_read));
+ ASSERT_TRUE(lease_read);
+
+ // Expect the hostname and user context to retain the commas
+ // they started with.
+ EXPECT_EQ(hostname, lease->hostname_);
+ EXPECT_EQ(context_str, lease->getContext()->str());
+}
+
+// Verifies that it is possible to write and read a lease with
+// escape tags and sequences in hostname and user context.
+TEST_F(CSVLeaseFile6Test, embeddedEscapes) {
+ CSVLeaseFile6 lf(filename_);
+ ASSERT_NO_THROW(lf.recreate());
+ ASSERT_TRUE(io_.exists());
+
+ std::string hostname("host&#xexample&#x2ccom");
+ std::string context_str("{ \"&#xbar\": true, \"foo\": false, \"x\": \"fac&#x2ctor\" }");
+
+ // Create a lease with commas in the hostname.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ makeDUID(DUID0, sizeof(DUID0)),
+ 7, 100, 0xFFFFFFFF, 8, true, true,
+ hostname));
+
+ // Add the user context with commas.
+ lease->setContext(Element::fromJSON(context_str));
+
+ // Write this lease out to the lease file.
+ ASSERT_NO_THROW(lf.append(*lease));
+
+ // Close the lease file.
+ lf.close();
+
+ Lease6Ptr lease_read;
+
+ // Re-open the file for reading.
+ ASSERT_NO_THROW(lf.open());
+
+ // Read the lease and make sure it is successful.
+ EXPECT_TRUE(lf.next(lease_read));
+ ASSERT_TRUE(lease_read);
+
+ // Expect the hostname and user context to retain the commas
+ // they started with.
+ EXPECT_EQ(hostname, lease->hostname_);
+ EXPECT_EQ(context_str, lease->getContext()->str());
+}
+
+/// @todo Currently we don't check invalid lease attributes, such as invalid
+/// lease type, invalid preferred lifetime vs valid lifetime etc. The Lease6
+/// should be extended with the function that validates lease attributes. Once
+/// this is implemented we should provide more tests for malformed leases
+/// in the CSV file.
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/d2_client_unittest.cc b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
new file mode 100644
index 0000000..c15afc5
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/d2_client_unittest.cc
@@ -0,0 +1,1240 @@
+// Copyright (C) 2012-2021 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 <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <testutils/test_to_element.h>
+#include <exceptions/exceptions.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::test;
+using namespace isc::data;
+using namespace isc;
+
+namespace {
+
+/// @brief Tests conversion of NameChangeFormat between enum and strings.
+TEST(ReplaceClientNameModeTest, formatEnumConversion){
+ ASSERT_EQ(D2ClientConfig::stringToReplaceClientNameMode("never"),
+ D2ClientConfig::RCM_NEVER);
+ ASSERT_EQ(D2ClientConfig::stringToReplaceClientNameMode("always"),
+ D2ClientConfig::RCM_ALWAYS);
+ ASSERT_EQ(D2ClientConfig::stringToReplaceClientNameMode("when-present"),
+ D2ClientConfig::RCM_WHEN_PRESENT);
+ ASSERT_EQ(D2ClientConfig::stringToReplaceClientNameMode("when-not-present"),
+ D2ClientConfig::RCM_WHEN_NOT_PRESENT);
+ ASSERT_THROW(D2ClientConfig::stringToReplaceClientNameMode("BOGUS"),
+ isc::BadValue);
+
+ ASSERT_EQ(D2ClientConfig::
+ replaceClientNameModeToString(D2ClientConfig::RCM_NEVER),
+ "never");
+ ASSERT_EQ(D2ClientConfig::
+ replaceClientNameModeToString(D2ClientConfig::RCM_ALWAYS),
+ "always");
+ ASSERT_EQ(D2ClientConfig::
+ replaceClientNameModeToString(D2ClientConfig::RCM_WHEN_PRESENT),
+ "when-present");
+ ASSERT_EQ(D2ClientConfig::
+ replaceClientNameModeToString(D2ClientConfig::
+ RCM_WHEN_NOT_PRESENT),
+ "when-not-present");
+}
+
+/// @brief Checks constructors and accessors of D2ClientConfig.
+TEST(D2ClientConfigTest, constructorsAndAccessors) {
+ D2ClientConfigPtr d2_client_config;
+
+ // Verify default constructor creates a disabled instance.
+ ASSERT_NO_THROW(d2_client_config.reset(new D2ClientConfig()));
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ // Verify the enable-updates can be toggled.
+ d2_client_config->enableUpdates(true);
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ d2_client_config->enableUpdates(false);
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+ d2_client_config.reset();
+
+ bool enable_updates = true;
+ isc::asiolink::IOAddress server_ip("127.0.0.1");
+ size_t server_port = 477;
+ isc::asiolink::IOAddress sender_ip("127.0.0.1");
+ size_t sender_port = 478;
+ size_t max_queue_size = 2048;
+ dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
+ dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
+ std::string generated_prefix = "the_prefix";
+ std::string qualifying_suffix = "the.suffix.";
+ std::string hostname_char_set = "[^A-Z]";
+ std::string hostname_char_replacement = "*";
+
+ // Verify that we can construct a valid, enabled instance.
+ ASSERT_NO_THROW(d2_client_config.reset(new
+ D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ sender_ip,
+ sender_port,
+ max_queue_size,
+ ncr_protocol,
+ ncr_format)));
+ ASSERT_TRUE(d2_client_config);
+
+ // Add user context
+ std::string user_context = "{ \"comment\": \"bar\", \"foo\": 1 }";
+ EXPECT_FALSE(d2_client_config->getContext());
+ d2_client_config->setContext(Element::fromJSON(user_context));
+
+ // Verify that the accessors return the expected values.
+ EXPECT_EQ(d2_client_config->getEnableUpdates(), enable_updates);
+
+ EXPECT_EQ(d2_client_config->getServerIp(), server_ip);
+ EXPECT_EQ(d2_client_config->getServerPort(), server_port);
+ EXPECT_EQ(d2_client_config->getSenderIp(), sender_ip);
+ EXPECT_EQ(d2_client_config->getSenderPort(), sender_port);
+ EXPECT_EQ(d2_client_config->getMaxQueueSize(), max_queue_size);
+ EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol);
+ EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format);
+ ASSERT_TRUE(d2_client_config->getContext());
+ EXPECT_EQ(d2_client_config->getContext()->str(), user_context);
+
+ // Verify that toText called by << operator doesn't bomb.
+ ASSERT_NO_THROW(std::cout << "toText test:" << std::endl <<
+ *d2_client_config << std::endl);
+
+ // Verify what toElement returns.
+ std::string expected = "{\n"
+ "\"enable-updates\": true,\n"
+ "\"server-ip\": \"127.0.0.1\",\n"
+ "\"server-port\": 477,\n"
+ "\"sender-ip\": \"127.0.0.1\",\n"
+ "\"sender-port\": 478,\n"
+ "\"max-queue-size\": 2048,\n"
+ "\"ncr-protocol\": \"UDP\",\n"
+ "\"ncr-format\": \"JSON\",\n"
+ "\"user-context\": { \"foo\": 1, \"comment\": \"bar\" }\n"
+ "}\n";
+ runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+
+ // Verify that constructor does not allow use of NCR_TCP.
+ /// @todo obviously this becomes invalid once TCP is supported.
+ ASSERT_THROW(d2_client_config.reset(new
+ D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ sender_ip,
+ sender_port,
+ max_queue_size,
+ dhcp_ddns::NCR_TCP,
+ ncr_format)),
+ D2ClientError);
+
+ Optional<std::string> opt_hostname_char_set("", true);
+ Optional<std::string> opt_hostname_char_replacement("", true);
+
+ // Verify that constructor handles optional hostname char stuff.
+ ASSERT_NO_THROW(d2_client_config.reset(new
+ D2ClientConfig(enable_updates,
+ server_ip,
+ server_port,
+ sender_ip,
+ sender_port,
+ max_queue_size,
+ ncr_protocol,
+ ncr_format)));
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify what toElement returns.
+ expected = "{\n"
+ "\"enable-updates\": true,\n"
+ "\"server-ip\": \"127.0.0.1\",\n"
+ "\"server-port\": 477,\n"
+ "\"sender-ip\": \"127.0.0.1\",\n"
+ "\"sender-port\": 478,\n"
+ "\"max-queue-size\": 2048,\n"
+ "\"ncr-protocol\": \"UDP\",\n"
+ "\"ncr-format\": \"JSON\"\n"
+ "}\n";
+ runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+
+ /// @todo if additional validation is added to ctor, this test needs to
+ /// expand accordingly.
+}
+
+/// @brief Tests the equality and inequality operators of D2ClientConfig.
+TEST(D2ClientConfigTest, equalityOperator) {
+ D2ClientConfigPtr ref_config;
+ D2ClientConfigPtr test_config;
+
+ isc::asiolink::IOAddress ref_address("127.0.0.1");
+ isc::asiolink::IOAddress test_address("127.0.0.2");
+
+ // Create an instance to use as a reference.
+ ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true,
+ ref_address, 477, ref_address, 478, 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(ref_config);
+
+ // Check a configuration that is identical to reference configuration.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477, ref_address, 478, 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(test_config);
+ EXPECT_TRUE(*ref_config == *test_config);
+ EXPECT_FALSE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by enable flag.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false,
+ ref_address, 477, ref_address, 478, 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by server ip.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ test_address, 477, ref_address, 478, 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by server port.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 333, ref_address, 478, 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by sender ip.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477, test_address, 478, 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by sender port.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477, ref_address, 333, 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+
+ // Check a configuration that differs only by max queue size.
+ ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+ ref_address, 477, ref_address, 478, 2048,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+ ASSERT_TRUE(test_config);
+ EXPECT_FALSE(*ref_config == *test_config);
+ EXPECT_TRUE(*ref_config != *test_config);
+}
+
+/// @brief Checks the D2ClientMgr constructor.
+TEST(D2ClientMgr, constructor) {
+ D2ClientMgrPtr d2_client_mgr;
+
+ // Verify we can construct with the default constructor.
+ ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
+
+ // After construction, D2 configuration should be disabled.
+ // Fetch it and verify this is the case.
+ D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(original_config);
+ EXPECT_FALSE(original_config->getEnableUpdates());
+
+ // Make sure convenience method agrees.
+ EXPECT_FALSE(d2_client_mgr->ddnsEnabled());
+}
+
+/// @brief Checks passing the D2ClientMgr a valid D2 client configuration.
+/// @todo Once NameChangeSender is integrated, this test needs to expand, and
+/// additional scenario tests will need to be written.
+TEST(D2ClientMgr, validConfig) {
+ D2ClientMgrPtr d2_client_mgr;
+
+ // Construct the manager and fetch its initial configuration.
+ ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
+ D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(original_config);
+
+ // Verify that we cannot set the config to an empty pointer.
+ D2ClientConfigPtr new_cfg;
+ ASSERT_THROW(d2_client_mgr->setD2ClientConfig(new_cfg), D2ClientError);
+
+ // Create a new, enabled config.
+ ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("127.0.0.1"), 477,
+ isc::asiolink::IOAddress("127.0.0.1"), 478,
+ 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+
+ // Verify that we can assign a new, non-empty configuration.
+ ASSERT_NO_THROW(d2_client_mgr->setD2ClientConfig(new_cfg));
+
+ // Verify that we can fetch the newly assigned configuration.
+ D2ClientConfigPtr updated_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(updated_config);
+ EXPECT_TRUE(updated_config->getEnableUpdates());
+
+ // Make sure convenience method agrees with the updated configuration.
+ EXPECT_TRUE(d2_client_mgr->ddnsEnabled());
+
+ // Make sure the configuration we fetched is the one we assigned,
+ // and not the original configuration.
+ EXPECT_EQ(*new_cfg, *updated_config);
+ EXPECT_NE(*original_config, *updated_config);
+}
+
+/// @brief Checks passing the D2ClientMgr a valid D2 client configuration
+/// using IPv6 service.
+TEST(D2ClientMgr, ipv6Config) {
+ D2ClientMgrPtr d2_client_mgr;
+
+ // Construct the manager and fetch its initial configuration.
+ ASSERT_NO_THROW(d2_client_mgr.reset(new D2ClientMgr()));
+ D2ClientConfigPtr original_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(original_config);
+
+ // Create a new, enabled config.
+ D2ClientConfigPtr new_cfg;
+ ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+ isc::asiolink::IOAddress("::1"), 477,
+ isc::asiolink::IOAddress("::1"), 478,
+ 1024,
+ dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON)));
+
+ // Verify that we can assign a new, non-empty configuration.
+ ASSERT_NO_THROW(d2_client_mgr->setD2ClientConfig(new_cfg));
+
+ // Verify that we can fetch the newly assigned configuration.
+ D2ClientConfigPtr updated_config = d2_client_mgr->getD2ClientConfig();
+ ASSERT_TRUE(updated_config);
+ EXPECT_TRUE(updated_config->getEnableUpdates());
+
+ // Make sure convenience method agrees with the updated configuration.
+ EXPECT_TRUE(d2_client_mgr->ddnsEnabled());
+
+ // Make sure the configuration we fetched is the one we assigned,
+ // and not the original configuration.
+ EXPECT_EQ(*new_cfg, *updated_config);
+ EXPECT_NE(*original_config, *updated_config);
+}
+
+/// @brief Test class for execerising manager functions that are
+/// influenced by DDNS parameters.
+class D2ClientMgrParamsTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ D2ClientMgrParamsTest() = default;
+
+ /// @brief Destructor
+ virtual ~D2ClientMgrParamsTest() = default;
+
+private:
+ /// @brief Prepares the class for a test.
+ virtual void SetUp() {
+ // Create a subnet and then a DdnsParams instance.
+ // We'll use the subnet's setters to alter DDNS parameter values.
+ subnet_.reset(new Subnet4(IOAddress("192.0.2.2"), 16, 1, 2, 3, 10));
+ ddns_params_.reset(new DdnsParams(subnet_, true));
+ }
+
+ /// @brief Cleans up after the test.
+ virtual void TearDown() {};
+
+public:
+ /// @brief Acts as the "selected" subnet. It is passed into the
+ /// constructor of ddns_params_. This allows DDNS parameters to
+ /// be modified via setters on subnet_.
+ Subnet4Ptr subnet_;
+ /// @brief Parameter instance based into D2ClientMgr functions
+ DdnsParamsPtr ddns_params_;
+};
+
+/// @brief Tests that analyzeFqdn detects invalid combination of both the
+/// client S and N flags set to true.
+TEST(D2ClientMgr, analyzeFqdnInvalidCombination) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ DdnsParams ddns_params;
+
+ // client S=1 N=1 is invalid. analyzeFqdn should throw.
+ ASSERT_THROW(mgr.analyzeFqdn(true, true, server_s, server_n, ddns_params),
+ isc::BadValue);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and all overrides are off.
+TEST_F(D2ClientMgrParamsTest, analyzeFqdnEnabledNoOverrides) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ // Create enabled configuration with all controls off (no overrides).
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("");
+ subnet_->setDdnsQualifyingSuffix("");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 0 (server doing reverse updates)
+ mgr.analyzeFqdn(false, false, server_s, server_n, *ddns_params_);
+ EXPECT_FALSE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(true, false, server_s, server_n, *ddns_params_);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+
+ // client S=0 N=1 means client wants no one to do forward updates.
+ // server S should be 0 (server is not forward updates)
+ // and server N should be 1 (server is not doing any updates)
+ mgr.analyzeFqdn(false, true, server_s, server_n, *ddns_params_);
+ EXPECT_FALSE(server_s);
+ EXPECT_TRUE(server_n);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and override-no-update is on.
+TEST_F(D2ClientMgrParamsTest, analyzeFqdnEnabledOverrideNoUpdate) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ // Create enabled configuration with override-no-update true.
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(true);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("");
+ subnet_->setDdnsQualifyingSuffix("");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 0 (server is doing reverse updates)
+ mgr.analyzeFqdn(false, false, server_s, server_n, *ddns_params_);
+ EXPECT_FALSE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(true, false, server_s, server_n, *ddns_params_);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=0 N=1 means client wants no one to do forward updates.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server is doing updates)
+ mgr.analyzeFqdn(false, true, server_s, server_n, *ddns_params_);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+}
+
+/// @brief Tests that analyzeFqdn generates correct server S and N flags when
+/// updates are enabled and override-client-update is on.
+TEST_F(D2ClientMgrParamsTest, analyzeFqdnEnabledOverrideClientUpdate) {
+ D2ClientMgr mgr;
+ bool server_s = false;
+ bool server_n = false;
+
+ // Create enabled configuration with override-client-update true.
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(true);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("");
+ subnet_->setDdnsQualifyingSuffix("");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(false, false, server_s, server_n, *ddns_params_);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ mgr.analyzeFqdn(true, false, server_s, server_n, *ddns_params_);
+ EXPECT_TRUE(server_s);
+ EXPECT_FALSE(server_n);
+
+ // client S=0 N=1 means client wants no one to do forward updates.
+ // server S should be 0 (server is not forward updates)
+ // and server N should be 1 (server is not doing any updates)
+ mgr.analyzeFqdn(false, true, server_s, server_n, *ddns_params_);
+ EXPECT_FALSE(server_s);
+ EXPECT_TRUE(server_n);
+}
+
+/// @brief Verifies the adustFqdnFlags template with Option4ClientFqdn objects.
+/// Ensures that the method can set the N, S, and O flags properly.
+/// Other permutations are covered by analyzeFqdnFlag tests.
+TEST_F(D2ClientMgrParamsTest, adjustFqdnFlagsV4) {
+ D2ClientMgr mgr;
+ Option4ClientFqdnPtr request;
+ Option4ClientFqdnPtr response;
+
+ // Create enabled configuration with override-no-update true.
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(true);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("");
+ subnet_->setDdnsQualifyingSuffix("");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 0 (server is doing reverse updates)
+ // and server O should be 0
+ request.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response, *ddns_params_);
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and server O should be 0
+ request.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response, *ddns_params_);
+ EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // client S=0 N=1 means client wants no one to do updates
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and O should be 1 (overriding client S)
+ request.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_N,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ response.reset(new Option4ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option4ClientFqdn>(*request, *response, *ddns_params_);
+ EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(response->getFlag(Option4ClientFqdn::FLAG_O));
+}
+
+/// @brief Verified the getUpdateDirections template method with
+/// Option4ClientFqdn objects.
+TEST(D2ClientMgr, updateDirectionsV4) {
+ D2ClientMgr mgr;
+ Option4ClientFqdnPtr response;
+
+ bool do_forward = false;
+ bool do_reverse = false;
+
+ // Response S=0, N=0 should mean do reverse only.
+ response.reset(new Option4ClientFqdn(0,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ mgr.getUpdateDirections(*response, do_forward, do_reverse);
+ EXPECT_FALSE(do_forward);
+ EXPECT_TRUE(do_reverse);
+
+ // Response S=0, N=1 should mean don't do either.
+ response.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_N,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ mgr.getUpdateDirections(*response, do_forward, do_reverse);
+ EXPECT_FALSE(do_forward);
+ EXPECT_FALSE(do_reverse);
+
+ // Response S=1, N=0 should mean do both.
+ response.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "", Option4ClientFqdn::PARTIAL));
+ mgr.getUpdateDirections(*response, do_forward, do_reverse);
+ EXPECT_TRUE(do_forward);
+ EXPECT_TRUE(do_reverse);
+
+ // Response S=1, N=1 isn't possible.
+}
+
+/// @brief Tests the qualifyName method's ability to construct FQDNs
+TEST_F(D2ClientMgrParamsTest, qualifyName) {
+ D2ClientMgr mgr;
+ bool do_not_dot = false;
+ bool do_dot = true;
+
+ // Create enabled configuration
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("prefix");
+ subnet_->setDdnsQualifyingSuffix("suffix.com");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // Verify that the qualifying suffix gets appended with a trailing dot added.
+ std::string partial_name = "somehost";
+ std::string qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.suffix.com.", qualified_name);
+
+ // Verify that the qualifying suffix gets appended without a trailing dot.
+ partial_name = "somehost";
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_not_dot);
+ EXPECT_EQ("somehost.suffix.com", qualified_name);
+
+ // Verify that an empty suffix and false flag, does not change the name
+ subnet_->setDdnsQualifyingSuffix("");
+ partial_name = "somehost";
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_not_dot);
+ EXPECT_EQ("somehost", qualified_name);
+
+ // Verify that a qualifying suffix that already has a trailing
+ // dot gets appended without doubling the dot.
+ subnet_->setDdnsQualifyingSuffix("hasdot.com.");
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.hasdot.com.", qualified_name);
+
+ // Verify that the qualifying suffix gets appended without an
+ // extraneous dot when partial_name ends with a "."
+ qualified_name = mgr.qualifyName("somehost.", *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.hasdot.com.", qualified_name);
+
+ // Verify that a name with a trailing dot does not get an extraneous
+ // dot when the suffix is blank
+ subnet_->setDdnsQualifyingSuffix("");
+ qualified_name = mgr.qualifyName("somehost.", *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.", qualified_name);
+
+ // Verify that a name with no trailing dot gets just a dot when the
+ // suffix is blank
+ qualified_name = mgr.qualifyName("somehost", *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.", qualified_name);
+
+ // Verify that a name with no trailing dot does not get dotted when the
+ // suffix is blank and trailing dot is false
+ qualified_name = mgr.qualifyName("somehost", *ddns_params_, do_not_dot);
+ EXPECT_EQ("somehost", qualified_name);
+
+ // Verify that a name with trailing dot gets "undotted" when the
+ // suffix is blank and trailing dot is false
+ qualified_name = mgr.qualifyName("somehost.", *ddns_params_, do_not_dot);
+ EXPECT_EQ("somehost", qualified_name);
+
+}
+
+/// @brief Tests the qualifyName method's ability to avoid duplicating
+/// qualifying suffix.
+TEST_F(D2ClientMgrParamsTest, qualifyNameWithoutDuplicatingSuffix) {
+ D2ClientMgr mgr;
+ bool do_dot = true;
+
+ // Create enabled configuration
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("prefix");
+ subnet_->setDdnsQualifyingSuffix("suffix.com");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // Verify that the qualifying suffix does not get appended when the
+ // input name has the suffix but no trailing dot.
+ std::string partial_name = "somehost.suffix.com";
+ std::string qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.suffix.com.", qualified_name);
+
+ // Verify that the qualifying suffix does not get appended when the
+ // input name has the suffix and a trailing dot.
+ partial_name = "somehost.suffix.com.";
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.suffix.com.", qualified_name);
+
+ // Verify that the qualifying suffix does get appended when the
+ // input name has the suffix embedded in it but does not begin
+ // at a label boundary.
+ partial_name = "somehost.almostsuffix.com";
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.almostsuffix.com.suffix.com.", qualified_name);
+
+ // Verify that the qualifying suffix does get appended when the
+ // input name has the suffix embedded in it.
+ partial_name = "somehost.suffix.com.org";
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("somehost.suffix.com.org.suffix.com.", qualified_name);
+
+ // Verify that the qualifying suffix does not get appended when the
+ // input name is the suffix itself.
+ partial_name = "suffix.com";
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("suffix.com.", qualified_name);
+
+ subnet_->setDdnsQualifyingSuffix("one.two.suffix.com");
+ partial_name = "two.suffix.com";
+ qualified_name = mgr.qualifyName(partial_name, *ddns_params_, do_dot);
+ EXPECT_EQ("two.suffix.com.one.two.suffix.com.", qualified_name);
+}
+
+/// @brief Tests the generateFdqn method's ability to construct FQDNs
+TEST_F(D2ClientMgrParamsTest, generateFqdn) {
+ D2ClientMgr mgr;
+ bool do_dot = true;
+
+ // Create enabled configuration
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("prefix");
+ subnet_->setDdnsQualifyingSuffix("suffix.com");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // Verify that it works with an IPv4 address.
+ asiolink::IOAddress v4address("192.0.2.75");
+ EXPECT_EQ("prefix-192-0-2-75.suffix.com.",
+ mgr.generateFqdn(v4address, *ddns_params_, do_dot));
+
+ // Verify that it works with an IPv6 address.
+ asiolink::IOAddress v6address("2001:db8::2");
+ EXPECT_EQ("prefix-2001-db8--2.suffix.com.",
+ mgr.generateFqdn(v6address, *ddns_params_, do_dot));
+
+ // Create a disabled config.
+ subnet_->setDdnsSendUpdates(false);
+
+ // Verify names generate properly with a disabled configuration.
+ EXPECT_EQ("prefix-192-0-2-75.suffix.com.",
+ mgr.generateFqdn(v4address, *ddns_params_, do_dot));
+ EXPECT_EQ("prefix-2001-db8--2.suffix.com.",
+ mgr.generateFqdn(v6address, *ddns_params_, do_dot));
+}
+
+/// @brief Tests adjustDomainName template method with Option4ClientFqdn
+TEST_F(D2ClientMgrParamsTest, adjustDomainNameV4) {
+ D2ClientMgr mgr;
+
+ // Create enabled configuration
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("prefix");
+ subnet_->setDdnsQualifyingSuffix("suffix.com");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ struct Scenario {
+ std::string description_;
+ D2ClientConfig::ReplaceClientNameMode mode_;
+ std::string client_name_;
+ Option4ClientFqdn::DomainNameType client_name_type_;
+ std::string expected_name_;
+ Option4ClientFqdn::DomainNameType expected_name_type_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "RCM_NEVER #1, empty client name",
+ D2ClientConfig::RCM_NEVER,
+ "", Option4ClientFqdn::PARTIAL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_NEVER #2, partial client name",
+ D2ClientConfig::RCM_NEVER,
+ "myhost", Option4ClientFqdn::PARTIAL,
+ "myhost.suffix.com.", Option4ClientFqdn::FULL
+ },
+ {
+ "RCM_NEVER #3, full client name",
+ D2ClientConfig::RCM_NEVER,
+ "myhost.example.com.", Option4ClientFqdn::FULL,
+ "myhost.example.com.", Option4ClientFqdn::FULL
+ },
+ {
+ "RCM_ALWAYS #1, empty client name",
+ D2ClientConfig::RCM_ALWAYS,
+ "", Option4ClientFqdn::PARTIAL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_ALWAYS #2, partial client name",
+ D2ClientConfig::RCM_ALWAYS,
+ "myhost", Option4ClientFqdn::PARTIAL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_ALWAYS #3, full client name",
+ D2ClientConfig::RCM_ALWAYS,
+ "myhost.example.com.", Option4ClientFqdn::FULL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_PRESENT #1, empty client name",
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ "", Option4ClientFqdn::PARTIAL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_PRESENT #2, partial client name",
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ "myhost", Option4ClientFqdn::PARTIAL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_PRESENT #3, full client name",
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ "myhost.example.com.", Option4ClientFqdn::FULL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_NOT_PRESENT #1, empty client name",
+ D2ClientConfig::RCM_WHEN_NOT_PRESENT,
+ "", Option4ClientFqdn::PARTIAL,
+ "", Option4ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_NOT_PRESENT #2, partial client name",
+ D2ClientConfig::RCM_WHEN_NOT_PRESENT,
+ "myhost", Option4ClientFqdn::PARTIAL,
+ "myhost.suffix.com.", Option4ClientFqdn::FULL
+ },
+ {
+ "RCM_WHEN_NOT_PRESENT #3, full client name",
+ D2ClientConfig::RCM_WHEN_NOT_PRESENT,
+ "myhost.example.com.", Option4ClientFqdn::FULL,
+ "myhost.example.com.", Option4ClientFqdn::FULL,
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ subnet_->setDdnsReplaceClientNameMode(scenario.mode_);
+ Option4ClientFqdn request (0, Option4ClientFqdn::RCODE_CLIENT(),
+ scenario.client_name_,
+ scenario.client_name_type_);
+
+ Option4ClientFqdn response(request);
+ mgr.adjustDomainName<Option4ClientFqdn>(request, response, *ddns_params_);
+ EXPECT_EQ(scenario.expected_name_, response.getDomainName());
+ EXPECT_EQ(scenario.expected_name_type_, response.getDomainNameType());
+ }
+ }
+}
+
+
+/// @brief Tests adjustDomainName template method with Option6ClientFqdn
+TEST_F(D2ClientMgrParamsTest, adjustDomainNameV6) {
+ D2ClientMgr mgr;
+
+ // Create enabled configuration
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("prefix");
+ subnet_->setDdnsQualifyingSuffix("suffix.com");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ struct Scenario {
+ std::string description_;
+ D2ClientConfig::ReplaceClientNameMode mode_;
+ std::string client_name_;
+ Option6ClientFqdn::DomainNameType client_name_type_;
+ std::string expected_name_;
+ Option6ClientFqdn::DomainNameType expected_name_type_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "RCM_NEVER #1, empty client name",
+ D2ClientConfig::RCM_NEVER,
+ "", Option6ClientFqdn::PARTIAL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_NEVER #2, partial client name",
+ D2ClientConfig::RCM_NEVER,
+ "myhost", Option6ClientFqdn::PARTIAL,
+ "myhost.suffix.com.", Option6ClientFqdn::FULL
+ },
+ {
+ "RCM_NEVER #3, full client name",
+ D2ClientConfig::RCM_NEVER,
+ "myhost.example.com.", Option6ClientFqdn::FULL,
+ "myhost.example.com.", Option6ClientFqdn::FULL
+ },
+ {
+ "RCM_ALWAYS #1, empty client name",
+ D2ClientConfig::RCM_ALWAYS,
+ "", Option6ClientFqdn::PARTIAL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_ALWAYS #2, partial client name",
+ D2ClientConfig::RCM_ALWAYS,
+ "myhost", Option6ClientFqdn::PARTIAL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_ALWAYS #3, full client name",
+ D2ClientConfig::RCM_ALWAYS,
+ "myhost.example.com.", Option6ClientFqdn::FULL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_PRESENT #1, empty client name",
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ "", Option6ClientFqdn::PARTIAL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_PRESENT #2, partial client name",
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ "myhost", Option6ClientFqdn::PARTIAL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_PRESENT #3, full client name",
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ "myhost.example.com.", Option6ClientFqdn::FULL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_NOT_PRESENT #1, empty client name",
+ D2ClientConfig::RCM_WHEN_NOT_PRESENT,
+ "", Option6ClientFqdn::PARTIAL,
+ "", Option6ClientFqdn::PARTIAL
+ },
+ {
+ "RCM_WHEN_NOT_PRESENT #2, partial client name",
+ D2ClientConfig::RCM_WHEN_NOT_PRESENT,
+ "myhost", Option6ClientFqdn::PARTIAL,
+ "myhost.suffix.com.", Option6ClientFqdn::FULL
+ },
+ {
+ "RCM_WHEN_NOT_PRESENT #3, full client name",
+ D2ClientConfig::RCM_WHEN_NOT_PRESENT,
+ "myhost.example.com.", Option6ClientFqdn::FULL,
+ "myhost.example.com.", Option6ClientFqdn::FULL,
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ subnet_->setDdnsReplaceClientNameMode(scenario.mode_);
+ Option6ClientFqdn request(0, scenario.client_name_,
+ scenario.client_name_type_);
+
+ Option6ClientFqdn response(request);
+ mgr.adjustDomainName<Option6ClientFqdn>(request, response, *ddns_params_);
+ EXPECT_EQ(scenario.expected_name_, response.getDomainName());
+ EXPECT_EQ(scenario.expected_name_type_, response.getDomainNameType());
+ }
+ }
+}
+
+/// @brief Verifies the adustFqdnFlags template with Option6ClientFqdn objects.
+/// Ensures that the method can set the N, S, and O flags properly.
+/// Other permutations are covered by analyzeFqdnFlags tests.
+TEST_F(D2ClientMgrParamsTest, adjustFqdnFlagsV6) {
+ D2ClientMgr mgr;
+ Option6ClientFqdnPtr request;
+ Option6ClientFqdnPtr response;
+
+ // Create enabled configuration with override-no-update true.
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(true);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("");
+ subnet_->setDdnsQualifyingSuffix("");
+ subnet_->setHostnameCharSet("");
+ subnet_->setHostnameCharReplacement("");
+
+ // client S=0 N=0 means client wants to do forward update.
+ // server S should be 0 (server is not doing forward updates)
+ // and server N should be 0 (server doing reverse updates)
+ // and server O should be 0
+ request.reset(new Option6ClientFqdn(0, "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response, *ddns_params_);
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // client S=1 N=0 means client wants server to do forward update.
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and server O should be 0
+ request.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response, *ddns_params_);
+ EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // client S=0 N=1 means client wants no one to do updates
+ // server S should be 1 (server is doing forward updates)
+ // and server N should be 0 (server doing updates)
+ // and O should be 1 (overriding client S)
+ request.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "", Option6ClientFqdn::PARTIAL));
+ response.reset(new Option6ClientFqdn(*request));
+ response->resetFlags();
+
+ mgr.adjustFqdnFlags<Option6ClientFqdn>(*request, *response, *ddns_params_);
+ EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(response->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(response->getFlag(Option6ClientFqdn::FLAG_O));
+}
+
+
+/// @brief Verified the getUpdateDirections template method with
+/// Option6ClientFqdn objects.
+TEST(D2ClientMgr, updateDirectionsV6) {
+ D2ClientMgr mgr;
+ Option6ClientFqdnPtr response;
+
+ bool do_forward = false;
+ bool do_reverse = false;
+
+ // Response S=0, N=0 should mean do reverse only.
+ response.reset(new Option6ClientFqdn(0,
+ "", Option6ClientFqdn::PARTIAL));
+ mgr.getUpdateDirections(*response, do_forward, do_reverse);
+ EXPECT_FALSE(do_forward);
+ EXPECT_TRUE(do_reverse);
+
+ // Response S=0, N=1 should mean don't do either.
+ response.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "", Option6ClientFqdn::PARTIAL));
+ mgr.getUpdateDirections(*response, do_forward, do_reverse);
+ EXPECT_FALSE(do_forward);
+ EXPECT_FALSE(do_reverse);
+
+ // Response S=1, N=0 should mean do both.
+ response.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "", Option6ClientFqdn::PARTIAL));
+ mgr.getUpdateDirections(*response, do_forward, do_reverse);
+ EXPECT_TRUE(do_forward);
+ EXPECT_TRUE(do_reverse);
+
+ // Response S=1, N=1 isn't possible.
+}
+
+/// @brief Tests v4 FQDN name sanitizing
+TEST_F(D2ClientMgrParamsTest, sanitizeFqdnV4) {
+ D2ClientMgr mgr;
+
+ // Create enabled configuration with override-no-update true.
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("prefix");
+ subnet_->setDdnsQualifyingSuffix("suffix.com");
+ subnet_->setHostnameCharSet("[^A-Za-z0-9-]");
+ subnet_->setHostnameCharReplacement("x");
+
+ // Get the sanitizer.
+ str::StringSanitizerPtr hostname_sanitizer;
+ ASSERT_NO_THROW(hostname_sanitizer = ddns_params_->getHostnameSanitizer());
+ ASSERT_TRUE(hostname_sanitizer);
+
+ struct Scenario {
+ std::string description_;
+ std::string client_name_;
+ Option4ClientFqdn::DomainNameType name_type_;
+ std::string expected_name_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "full FQDN, name unchanged",
+ "One.123.example.com.",
+ Option4ClientFqdn::FULL,
+ "one.123.example.com."
+ },
+ {
+ "partial FQDN, name unchanged, but qualified",
+ "One.123",
+ Option4ClientFqdn::PARTIAL,
+ "one.123.suffix.com."
+ },
+ {
+ "full FQDN, scrubbed",
+ "O#n^e.123.ex&a*mple.com.",
+ Option4ClientFqdn::FULL,
+ "oxnxe.123.exxaxmple.com."
+ },
+ {
+ "partial FQDN, scrubbed and qualified",
+ "One.1+2|3",
+ Option4ClientFqdn::PARTIAL,
+ "one.1x2x3.suffix.com."
+ },
+ {
+ "full FQDN with characters that get escaped",
+ "O n e.123.exa(m)ple.com.",
+ Option4ClientFqdn::FULL,
+ "oxnxe.123.exaxmxple.com."
+ },
+ {
+ "full FQDN with escape sequences",
+ "O\032n\032e.123.example.com.",
+ Option4ClientFqdn::FULL,
+ "oxnxe.123.example.com."
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ Option4ClientFqdn request(0, Option4ClientFqdn::RCODE_CLIENT(),
+ scenario.client_name_, scenario.name_type_);
+ Option4ClientFqdn response(request);
+
+ mgr.adjustDomainName<Option4ClientFqdn>(request, response, *ddns_params_);
+ EXPECT_EQ(scenario.expected_name_, response.getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, response.getDomainNameType());
+ }
+ }
+}
+
+/// @brief Tests v6 FQDN name sanitizing
+/// @todo This test currently verifies that Option6ClientFqdn::DomainName
+/// downcases strings used to construct it. For some reason, currently
+/// unknown, Option4ClientFqdn preserves the case, while Option6ClientFqdn
+/// downcases it (see setDomainName() in both classes. See Trac #5700.
+TEST_F(D2ClientMgrParamsTest, sanitizeFqdnV6) {
+ D2ClientMgr mgr;
+
+ // Create enabled configuration with override-no-update true.
+ subnet_->setDdnsSendUpdates(true);
+ subnet_->setDdnsOverrideNoUpdate(false);
+ subnet_->setDdnsOverrideClientUpdate(false);
+ subnet_->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_NEVER);
+ subnet_->setDdnsGeneratedPrefix("prefix");
+ subnet_->setDdnsQualifyingSuffix("suffix.com");
+ subnet_->setHostnameCharSet("[^A-Za-z0-9-]");
+ subnet_->setHostnameCharReplacement("x");
+
+ // Get the sanitizer.
+ str::StringSanitizerPtr hostname_sanitizer;
+ ASSERT_NO_THROW(hostname_sanitizer = ddns_params_->getHostnameSanitizer());
+ ASSERT_TRUE(hostname_sanitizer);
+
+ struct Scenario {
+ std::string description_;
+ std::string client_name_;
+ Option6ClientFqdn::DomainNameType name_type_;
+ std::string expected_name_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "full FQDN, name unchanged",
+ "One.123.example.com.",
+ Option6ClientFqdn::FULL,
+ "one.123.example.com."
+ },
+ {
+ "partial FQDN, name unchanged, but qualified",
+ "One.123",
+ Option6ClientFqdn::PARTIAL,
+ "one.123.suffix.com."
+ },
+ {
+ "full FQDN, scrubbed",
+ "O#n^e.123.ex&a*mple.com.",
+ Option6ClientFqdn::FULL,
+ "oxnxe.123.exxaxmple.com."
+ },
+ {
+ "partial FQDN, scrubbed and qualified",
+ "One.1+2|3",
+ Option6ClientFqdn::PARTIAL,
+ "one.1x2x3.suffix.com."
+ },
+ {
+ "full FQDN with characters that get escaped",
+ "O n e.123.exa(m)ple.com.",
+ Option6ClientFqdn::FULL,
+ "oxnxe.123.exaxmxple.com."
+ },
+ {
+ "full FQDN with escape sequences",
+ "O\032n\032e.123.example.com.",
+ Option6ClientFqdn::FULL,
+ "oxnxe.123.example.com."
+ }
+ };
+
+ Option6ClientFqdnPtr response;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ Option6ClientFqdn request(0, scenario.client_name_, scenario.name_type_);
+ Option6ClientFqdn response(request);
+
+ mgr.adjustDomainName<Option6ClientFqdn>(request, response, *ddns_params_);
+ EXPECT_EQ(scenario.expected_name_, response.getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, response.getDomainNameType());
+ }
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/d2_udp_unittest.cc b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
new file mode 100644
index 0000000..2d83e97
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/d2_udp_unittest.cc
@@ -0,0 +1,504 @@
+// Copyright (C) 2014-2021 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/.
+
+/// @file d2_upd_unittest.cc Unit tests for D2ClientMgr UDP communications.
+/// Note these tests are not intended to verify the actual send and receive
+/// across UDP sockets. This level of testing is done in libdhcp-ddns.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <sys/select.h>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Test fixture for exercising D2ClientMgr send management
+/// services. It inherits from D2ClientMgr to allow overriding various
+/// methods and accessing otherwise restricted member. In particular it
+/// overrides the NameChangeSender completion completion callback, allowing
+/// the injection of send errors.
+class D2ClientMgrTest : public D2ClientMgr, public ::testing::Test {
+public:
+ /// @brief If true simulates a send which completed with a failed status.
+ bool simulate_send_failure_;
+ /// @brief If true causes an exception throw in the client error handler.
+ bool error_handler_throw_;
+ /// @brief Tracks the number times the completion handler is called.
+ int callback_count_;
+ /// @brief Tracks the number of times the client error handler was called.
+ int error_handler_count_;
+
+ /// @brief Constructor
+ D2ClientMgrTest() : simulate_send_failure_(false),
+ error_handler_throw_(false),
+ callback_count_(0), error_handler_count_(0) {
+ }
+
+ /// @brief virtual Destructor
+ virtual ~D2ClientMgrTest(){
+ }
+
+ /// @brief Updates the D2ClientMgr's configuration to DDNS enabled.
+ ///
+ /// @param server_address IP address of kea-dhcp-ddns.
+ /// @param server_port IP port number of kea-dhcp-ddns.
+ /// @param protocol NCR protocol to use. (Currently only UDP is
+ /// supported).
+ void enableDdns(const std::string& server_address,
+ const size_t server_port,
+ const dhcp_ddns::NameChangeProtocol protocol) {
+ // Update the configuration with one that is enabled.
+ D2ClientConfigPtr new_cfg;
+
+ isc::asiolink::IOAddress server_ip(server_address);
+ isc::asiolink::IOAddress sender_ip(server_ip.isV4() ?
+ D2ClientConfig::DFT_V4_SENDER_IP :
+ D2ClientConfig::DFT_V6_SENDER_IP);
+
+ ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
+ server_ip, server_port,
+ sender_ip, D2ClientConfig::DFT_SENDER_PORT,
+ D2ClientConfig::DFT_MAX_QUEUE_SIZE,
+ protocol, dhcp_ddns::FMT_JSON)));
+
+ ASSERT_NO_THROW(setD2ClientConfig(new_cfg));
+ ASSERT_TRUE(ddnsEnabled());
+ }
+
+ /// @brief Checks sender's select-fd against an expected state of readiness.
+ ///
+ /// Uses select() to determine if the sender's select_fd is marked as
+ /// ready to read, and compares this against the expected state. The
+ /// select function is called with a timeout of 0.0 (non blocking).
+ ///
+ /// @param expect_ready Expected state of readiness (True if expecting
+ /// a ready to ready result, false if expecting otherwise).
+ void selectCheck(bool expect_ready) {
+ fd_set read_fds;
+ int maxfd = 0;
+
+ FD_ZERO(&read_fds);
+
+ // cppcheck-suppress redundantAssignment
+ int select_fd = -1;
+ ASSERT_NO_THROW(
+ // cppcheck-suppress redundantAssignment
+ select_fd = getSelectFd()
+ );
+
+ FD_SET(select_fd, &read_fds);
+ maxfd = select_fd;
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 0;
+
+ int result = (select(maxfd + 1, &read_fds, NULL, NULL,
+ &select_timeout));
+
+ if (result < 0) {
+ const char *errstr = strerror(errno);
+ FAIL() << "select failed :" << errstr;
+ }
+
+ if (expect_ready) {
+ ASSERT_TRUE(result > 0);
+ } else {
+ ASSERT_TRUE(result == 0);
+ }
+ }
+
+ /// @brief Overrides base class completion callback.
+ ///
+ /// This method will be invoked each time a send completes. It allows
+ /// intervention prior to calling the production implementation in the
+ /// base. If simulate_send_failure_ is true, the base call impl will
+ /// be called with an error status, otherwise it will be called with
+ /// the result parameter given.
+ ///
+ /// @param result Result code of the send operation.
+ /// @param ncr NameChangeRequest which failed to send.
+ virtual void operator()(const dhcp_ddns::NameChangeSender::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ ++callback_count_;
+ if (simulate_send_failure_) {
+ simulate_send_failure_ = false;
+ D2ClientMgr::operator()(dhcp_ddns::NameChangeSender::ERROR, ncr);
+ } else {
+ D2ClientMgr::operator()(result, ncr);
+ }
+ }
+
+ /// @brief Serves as the "application level" client error handler.
+ ///
+ /// This method is passed into calls to startSender as the client error
+ /// handler. It should be invoked whenever the completion callback is
+ /// passed a result other than SUCCESS. If error_handler_throw_
+ /// is true it will throw an exception.
+ ///
+ /// @param result unused - Result code of the send operation.
+ /// @param ncr unused -NameChangeRequest which failed to send.
+ void error_handler(const dhcp_ddns::NameChangeSender::Result /*result*/,
+ dhcp_ddns::NameChangeRequestPtr& /*ncr*/) {
+ if (error_handler_throw_) {
+ error_handler_throw_ = false;
+ isc_throw(isc::InvalidOperation, "Simulated client handler throw");
+ }
+
+ ++error_handler_count_;
+ }
+
+ /// @brief Returns D2ClientErroHandler bound to this::error_handler_.
+ D2ClientErrorHandler getErrorHandler() {
+ return (std::bind(&D2ClientMgrTest::error_handler, this, ph::_1, ph::_2));
+ }
+
+ /// @brief Constructs a NameChangeRequest message from a fixed JSON string.
+ dhcp_ddns::NameChangeRequestPtr buildTestNcr() {
+ // Build an NCR from json string.
+ const char* ncr_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"myhost.example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20140121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ return (dhcp_ddns::NameChangeRequest::fromJSON(ncr_str));
+ }
+
+ /// Expose restricted members.
+ using D2ClientMgr::getSelectFd;
+};
+
+
+/// @brief Checks that D2ClientMgr disable and enable a UDP sender.
+TEST_F(D2ClientMgrTest, udpSenderEnableDisable) {
+ // Verify DDNS is disabled by default.
+ ASSERT_FALSE(ddnsEnabled());
+
+ // Verify we are not in send mode.
+ ASSERT_FALSE(amSending());
+
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+ ASSERT_FALSE(amSending());
+
+ ASSERT_NO_THROW(startSender(getErrorHandler()));
+ ASSERT_TRUE(amSending());
+
+ // Verify that we take sender out of send mode.
+ ASSERT_NO_THROW(stopSender());
+ ASSERT_FALSE(amSending());
+}
+
+/// @brief Checks D2ClientMgr queuing methods with a UDP sender.
+TEST_F(D2ClientMgrTest, udpSenderQueing) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+ ASSERT_FALSE(amSending());
+
+ // Queue should be empty.
+ EXPECT_EQ(0, getQueueSize());
+
+ // Trying to peek past the end of the queue should throw.
+ EXPECT_THROW(peekAt(1), dhcp_ddns::NcrSenderError);
+
+ // Trying to send a NCR when not in send mode should fail.
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ EXPECT_THROW(sendRequest(ncr), D2ClientError);
+
+ // Place sender in send mode.
+ ASSERT_NO_THROW(startSender(getErrorHandler()));
+ ASSERT_TRUE(amSending());
+
+ // Send should succeed now.
+ ASSERT_NO_THROW(sendRequest(ncr));
+
+ // Queue should have 1 entry.
+ EXPECT_EQ(1, getQueueSize());
+
+ // Attempt to fetch the entry we just queued.
+ dhcp_ddns::NameChangeRequestPtr ncr2;
+ ASSERT_NO_THROW(ncr2 = peekAt(0));
+
+ // Verify what we queued matches what we fetched.
+ EXPECT_TRUE(*ncr == *ncr2);
+
+ // Clearing the queue while in send mode should fail.
+ ASSERT_THROW(clearQueue(), dhcp_ddns::NcrSenderError);
+
+ // We should still have 1 in the queue.
+ EXPECT_EQ(1, getQueueSize());
+
+ // Get out of send mode.
+ ASSERT_NO_THROW(stopSender());
+ ASSERT_FALSE(amSending());
+
+ // Clear queue should succeed now.
+ ASSERT_NO_THROW(clearQueue());
+ EXPECT_EQ(0, getQueueSize());
+}
+
+/// @brief Checks that D2ClientMgr can send with a UDP sender and
+/// a private IOService.
+TEST_F(D2ClientMgrTest, udpSend) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+
+ // Trying to fetch the select-fd when not sending should fail.
+ ASSERT_THROW(getSelectFd(), D2ClientError);
+
+ // Place sender in send mode.
+ ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+ // select_fd should evaluate to NOT ready to read.
+ selectCheck(false);
+
+ // Build a test request and send it.
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(sendRequest(ncr));
+
+ // select_fd should evaluate to ready to read.
+ selectCheck(true);
+
+ // Call service handler.
+ runReadyIO();
+
+ // select_fd should evaluate to not ready to read.
+ selectCheck(false);
+}
+
+/// @brief Checks that D2ClientMgr can send with a UDP sender and
+/// an external IOService.
+TEST_F(D2ClientMgrTest, udpSendExternalIOService) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ enableDdns("127.0.0.1", 53001, dhcp_ddns::NCR_UDP);
+
+ // Place sender in send mode using an external IO service.
+ asiolink::IOService io_service;
+ ASSERT_NO_THROW(startSender(getErrorHandler(), io_service));
+
+ // select_fd should evaluate to NOT ready to read.
+ selectCheck(false);
+
+ // Build a test request and send it.
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(sendRequest(ncr));
+
+ // select_fd should evaluate to ready to read.
+ selectCheck(true);
+
+ // Call service handler.
+ runReadyIO();
+
+ // select_fd should evaluate to not ready to read.
+ selectCheck(false);
+
+ // Explicitly stop the sender. This ensures the sender's
+ // ASIO socket is closed prior to the local io_service
+ // instance goes out of scope.
+ ASSERT_NO_THROW(stopSender());
+}
+
+/// @brief Checks that D2ClientMgr can send with a UDP sender and
+/// an external IOService.
+TEST_F(D2ClientMgrTest, udpSendExternalIOService6) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ enableDdns("::1", 53001, dhcp_ddns::NCR_UDP);
+
+ // Place sender in send mode using an external IO service.
+ asiolink::IOService io_service;
+ ASSERT_NO_THROW(startSender(getErrorHandler(), io_service));
+
+ // select_fd should evaluate to NOT ready to read.
+ selectCheck(false);
+
+ // Build a test request and send it.
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(sendRequest(ncr));
+
+ // select_fd should evaluate to ready to read.
+ selectCheck(true);
+
+ // Call service handler.
+ runReadyIO();
+
+ // select_fd should evaluate to not ready to read.
+ selectCheck(false);
+
+ // Explicitly stop the sender. This ensures the sender's
+ // ASIO socket is closed prior to the local io_service
+ // instance goes out of scope.
+ ASSERT_NO_THROW(stopSender());
+}
+
+
+/// @brief Checks that D2ClientMgr invokes the client error handler
+/// when send errors occur.
+TEST_F(D2ClientMgrTest, udpSendErrorHandler) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ // Place sender in send mode.
+ enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+ ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+ // Simulate a failed response in the send call back. This should
+ // cause the error handler to get invoked.
+ simulate_send_failure_ = true;
+
+ // Verify error count is zero.
+ ASSERT_EQ(0, error_handler_count_);
+
+ // Send a test request.
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(sendRequest(ncr));
+
+ // Call the ready handler. This should complete the message with an error.
+ ASSERT_NO_THROW(runReadyIO());
+
+ // If we executed error handler properly, the error count should one.
+ ASSERT_EQ(1, error_handler_count_);
+}
+
+
+/// @brief Checks that client error handler exceptions are handled gracefully.
+TEST_F(D2ClientMgrTest, udpSendErrorHandlerThrow) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ // Place sender in send mode.
+ enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+ ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+ // Simulate a failed response in the send call back and
+ // force a throw in the error handler.
+ simulate_send_failure_ = true;
+ error_handler_throw_ = true;
+
+ // Verify error count is zero.
+ ASSERT_EQ(0, error_handler_count_);
+
+ // Send a test request.
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(sendRequest(ncr));
+
+ // Call the ready handler. This should complete the message with an error.
+ // The handler should throw but the exception should not escape.
+ ASSERT_NO_THROW(runReadyIO());
+
+ // If throw flag is false, then we were in the error handler should
+ // have thrown.
+ ASSERT_FALSE(error_handler_throw_);
+
+ // If error count is still zero, then we did throw.
+ ASSERT_EQ(0, error_handler_count_);
+}
+
+/// @brief Tests that D2ClientMgr registers and unregisters with IfaceMgr.
+TEST_F(D2ClientMgrTest, ifaceRegister) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+
+ // Place sender in send mode.
+ ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+ // Queue three messages.
+ for (unsigned i = 0; i < 3; ++i) {
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(sendRequest(ncr));
+ }
+
+ // Make sure queue count is correct.
+ EXPECT_EQ(3, getQueueSize());
+
+ // select_fd should evaluate to ready to read.
+ selectCheck(true);
+
+ // Calling receive should complete the first message and start the second.
+ IfaceMgr::instance().receive4(0, 0);
+
+ // Verify the callback handler was invoked, no errors counted.
+ EXPECT_EQ(2, getQueueSize());
+ ASSERT_EQ(1, callback_count_);
+ ASSERT_EQ(0, error_handler_count_);
+
+ // Stop the sender. This should complete the second message but leave
+ // the third in the queue.
+ ASSERT_NO_THROW(stopSender());
+ EXPECT_EQ(1, getQueueSize());
+ ASSERT_EQ(2, callback_count_);
+ ASSERT_EQ(0, error_handler_count_);
+
+ // Calling receive again should have no affect.
+ IfaceMgr::instance().receive4(0, 0);
+ EXPECT_EQ(1, getQueueSize());
+ ASSERT_EQ(2, callback_count_);
+ ASSERT_EQ(0, error_handler_count_);
+}
+
+/// @brief Checks that D2ClientMgr suspendUpdates works properly.
+TEST_F(D2ClientMgrTest, udpSuspendUpdates) {
+ // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+ // Place sender in send mode.
+ enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+ ASSERT_NO_THROW(startSender(getErrorHandler()));
+
+ // Send a test request.
+ for (unsigned i = 0; i < 3; ++i) {
+ dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+ ASSERT_NO_THROW(sendRequest(ncr));
+ }
+ ASSERT_EQ(3, getQueueSize());
+
+ // Call the ready handler. This should complete the first message
+ // and initiate sending the second message.
+ ASSERT_NO_THROW(runReadyIO());
+
+ // Queue count should have gone down by 1.
+ ASSERT_EQ(2, getQueueSize());
+
+ // Suspend updates. This should disable updates and stop the sender.
+ ASSERT_NO_THROW(suspendUpdates());
+
+ EXPECT_FALSE(ddnsEnabled());
+ EXPECT_FALSE(amSending());
+
+ // Stopping the sender should have completed the second message's
+ // in-progress send, so queue size should be 1.
+ ASSERT_EQ(1, getQueueSize());
+}
+
+/// @brief Tests that invokeErrorHandler does not fail if there is no handler.
+TEST_F(D2ClientMgrTest, missingErrorHandler) {
+ // Ensure we aren't in send mode.
+ ASSERT_FALSE(ddnsEnabled());
+ ASSERT_FALSE(amSending());
+
+ // There is no error handler at this point, so invoking should not throw.
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(invokeClientErrorHandler(dhcp_ddns::NameChangeSender::ERROR,
+ ncr));
+
+ // Verify we didn't invoke the error handler, error count is zero.
+ ASSERT_EQ(0, error_handler_count_);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc
new file mode 100644
index 0000000..192bb63
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc
@@ -0,0 +1,584 @@
+// Copyright (C) 2015-2023 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_int.h>
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
+
+#include <gtest/gtest.h>
+#include <functional>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test port.
+const uint16_t TEST_PORT = 12345;
+
+/// @brief Number of iterations used by the tests.
+const uint16_t TEST_ITERATIONS = 10;
+
+/// @brief Type definition for the function creating DHCP message.
+typedef std::function<Pkt6Ptr(uint16_t, uint16_t)> CreateMsgFun;
+
+/// @brief Define short name for test IPC class.
+typedef Dhcp4o6TestIpc TestIpc;
+
+/// @brief Test fixture class for @c Dhcp4o6IpcBase.
+class Dhcp4o6IpcBaseTest : public ::testing::Test {
+protected:
+
+ /// @brief Constructor.
+ ///
+ /// Replaces the real configuration of interfaces with a fake configuration.
+ /// The IPC uses the @c IfaceMgr to check whether the interfaces which names
+ /// are carried within DHCP options exist in the system. Providing the fake
+ /// configuration for the @c IfaceMgr guarantees that the configuration is
+ /// consistent on any machine running the unit tests.
+ Dhcp4o6IpcBaseTest();
+
+ /// @brief Concatenates the prefix and postfix.
+ ///
+ /// @param prefix Prefix.
+ /// @param postfix Postfix.
+ /// @return String representing concatenated prefix and postfix.
+ static std::string concatenate(const std::string& prefix, uint16_t postfix);
+
+ /// @brief Creates an instance of the DHCPv4o6 message.
+ ////
+ /// @param msg_type Message type.
+ /// @param postfix Postfix to be appended to the remote address. For example,
+ /// for postfix = 5 the resulting remote address will be 2001:db8:1::5.
+ /// The postfix value is also used to generate the postfix for the interface.
+ /// The possible interface names are "eth0" and "eth1". For even postfix values
+ /// the "eth0" will be used, for odd postfix values "eth1" will be used.
+ ///
+ /// @return Pointer to the created message.
+ static Pkt6Ptr createDHCPv4o6Message(uint16_t msg_type,
+ uint16_t postfix = 0);
+
+ /// @brief Creates an instance of the DHCPv4o6 message with vendor option.
+ ///
+ /// @param msg_type Message type.
+ /// @param postfix Postfix to be appended to the remote address. See the
+ /// documentation of @c createDHCPv4o6Message for details.
+ /// @param enterprise_id Enterprise ID for the vendor option.
+ ///
+ /// @return Pointer to the created message.
+ static Pkt6Ptr createDHCPv4o6MsgWithVendorOption(uint16_t msg_type,
+ uint16_t postfix,
+ uint32_t enterprise_id);
+
+ /// @brief Creates an instance of the DHCPv4o6 message with ISC
+ /// vendor option.
+ ///
+ /// This is useful to test scenarios when the IPC is forwarding messages
+ /// that contain vendor option with ISC enterprise ID.
+ ///
+ /// @param msg_type Message type.
+ /// @param postfix Postfix to be appended to the remote address. See the
+ /// documentation of @c createDHCPv4o6Message for details.
+ ///
+ /// @return Pointer to the created message.
+ static Pkt6Ptr createDHCPv4o6MsgWithISCVendorOption(uint16_t msg_type,
+ uint16_t postfix);
+
+ /// @brief Creates an instance of the DHCPv4o6 message with vendor
+ /// option holding enterprise id of 32000.
+ ///
+ /// This is useful to test scenarios when the IPC is forwarding messages
+ /// that contain some vendor option and when IPC also appends the ISC
+ /// vendor option to carry some DHCPv4o6 specific information.
+ ///
+ /// @param msg_type Message type.
+ /// @param postfix Postfix to be appended to the remote address. See the
+ /// documentation of @c createDHCPv4o6Message for details.
+ ///
+ /// @return Pointer to the created message.
+ static Pkt6Ptr createDHCPv4o6MsgWithAnyVendorOption(uint16_t msg_type,
+ uint16_t postfix);
+
+ /// @brief Creates an instance of the DHCPv4o6 Message option.
+ ///
+ /// @param src Type of the source endpoint. It can be 4 or 6.
+ /// @return Pointer to the instance of the option.
+ static OptionPtr createDHCPv4MsgOption(TestIpc::EndpointType src);
+
+ /// @brief Tests sending and receiving packets over the IPC.
+ ///
+ /// @param iterations_num Number of packets to be sent over the IPC.
+ /// @param src Type of the source IPC endpoint. It can be 4 or 6.
+ /// @param dest Type of the destination IPC endpoint. It can be 4 or 6.
+ /// @param create_msg_fun Function called to create the packet.
+ void testSendReceive(uint16_t iterations_num,
+ TestIpc::EndpointType src,
+ TestIpc::EndpointType dest,
+ const CreateMsgFun& create_msg_fun);
+
+ /// @brief Tests that error is reported when invalid message is received.
+ ///
+ /// @param pkt Pointer to the invalid message.
+ void testReceiveError(const Pkt6Ptr& pkt);
+
+private:
+
+ /// @brief Holds the fake configuration of the interfaces.
+ IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+Dhcp4o6IpcBaseTest::Dhcp4o6IpcBaseTest()
+ : iface_mgr_test_config_(true) {
+}
+
+std::string
+Dhcp4o6IpcBaseTest::concatenate(const std::string& prefix,
+ uint16_t postfix) {
+ std::ostringstream s;
+ s << prefix << postfix;
+ return (s.str());
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6Message(uint16_t msg_type,
+ uint16_t postfix) {
+ // Create the DHCPv4o6 message.
+ Pkt6Ptr pkt(new Pkt6(msg_type, 0));
+
+ // The interface name is carried in the dedicated option between
+ // the servers. The receiving server will check that such interface
+ // is present in the system. The fake configuration we're using for
+ // this test includes two interfaces: "eth0" and "eth1". Therefore,
+ // we pick one or another, depending on the index of the iteration.
+ pkt->setIface(concatenate("eth", postfix % 2));
+ pkt->setIndex(ETH0_INDEX + postfix % 2);
+
+ // The remote address of the sender of the DHCPv6 packet is carried
+ // between the servers in the dedicated option. We use different
+ // address for each iteration to make sure that the IPC delivers the
+ // right address.
+ pkt->setRemoteAddr(IOAddress(concatenate("2001:db8:1::", postfix)));
+
+ // The remote port of the sender of the DHCPv6 packet is carried
+ // between the servers in the dedicated option.
+ pkt->setRemotePort(10000 + (postfix % 1000));
+
+ // Determine the endpoint type using the message type.
+ TestIpc::EndpointType src = (msg_type == DHCPV6_DHCPV4_QUERY) ?
+ TestIpc::ENDPOINT_TYPE_V6 : TestIpc::ENDPOINT_TYPE_V4;
+
+ // Add DHCPv4 Message option to make sure it is conveyed by the IPC.
+ pkt->addOption(createDHCPv4MsgOption(src));
+
+ return (pkt);
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithVendorOption(uint16_t msg_type,
+ uint16_t postfix,
+ uint32_t enterprise_id) {
+ Pkt6Ptr pkt = createDHCPv4o6Message(msg_type, postfix);
+
+ // Create vendor option with ISC enterprise id.
+ OptionVendorPtr option_vendor(new OptionVendor(Option::V6, enterprise_id));
+
+ // Add some option to the vendor option.
+ option_vendor->addOption(OptionPtr(new Option(Option::V6, 100)));
+
+ // Add vendor option to the message.
+ pkt->addOption(option_vendor);
+
+ return (pkt);
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithISCVendorOption(uint16_t msg_type,
+ uint16_t postfix) {
+ return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, ENTERPRISE_ID_ISC));
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithAnyVendorOption(uint16_t msg_type,
+ uint16_t postfix) {
+ return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, 32000));
+}
+
+OptionPtr
+Dhcp4o6IpcBaseTest::createDHCPv4MsgOption(TestIpc::EndpointType src) {
+ // Create the DHCPv4 message.
+ Pkt4Ptr pkt(new Pkt4(src == TestIpc::ENDPOINT_TYPE_V4 ? DHCPACK : DHCPREQUEST,
+ 1234));
+ // Make a wire representation of the DHCPv4 message.
+ pkt->pack();
+ OutputBuffer& output_buffer = pkt->getBuffer();
+ const uint8_t* data = static_cast<const uint8_t*>(output_buffer.getData());
+ OptionBuffer option_buffer(data, data + output_buffer.getLength());
+
+ // Create the DHCPv4 Message option holding the created message.
+ OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer));
+ return (opt_msg);
+}
+
+void
+Dhcp4o6IpcBaseTest::testSendReceive(uint16_t iterations_num,
+ TestIpc::EndpointType src,
+ TestIpc::EndpointType dest,
+ const CreateMsgFun& create_msg_fun) {
+ // Create IPC instances representing the source and destination endpoints.
+ TestIpc ipc_src(TEST_PORT, src);
+ TestIpc ipc_dest(TEST_PORT, dest);
+
+ // Open the IPC on both ends.
+ ASSERT_NO_THROW(ipc_src.open());
+ ASSERT_NO_THROW(ipc_dest.open());
+
+ // Depending if we're sending from DHCPv6 to DHCPv4 or the opposite
+ // direction we use different message type. This is not really required
+ // for testing IPC, but it better simulates the real use case.
+ uint16_t msg_type = (src == TestIpc::ENDPOINT_TYPE_V6 ? DHCPV6_DHCPV4_QUERY :
+ DHCPV6_DHCPV4_RESPONSE);
+
+ std::vector<bool> has_vendor_option;
+
+ // Send the number of messages configured for the test.
+ for (uint16_t i = 1; i <= iterations_num; ++i) {
+ // Create the DHCPv4o6 message.
+ Pkt6Ptr pkt = create_msg_fun(msg_type, i);
+
+ // Remember if the vendor option exists in the source packet. The
+ // received packet should also contain this option if it exists
+ // in the source packet.
+ has_vendor_option.push_back(static_cast<bool>(pkt->getOption(D6O_VENDOR_OPTS)));
+
+ // Actually send the message through the IPC.
+ ASSERT_NO_THROW(ipc_src.send(pkt))
+ << "Failed to send the message over the IPC for iteration " << i;
+ }
+
+ // Try to receive all messages.
+ for (uint16_t i = 1; i <= iterations_num; ++i) {
+
+ // Call receive with a timeout. The data should appear on the socket
+ // within this time.
+ ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+ // Pop the received message.
+ Pkt6Ptr pkt_received = ipc_dest.popPktReceived();
+ ASSERT_TRUE(pkt_received);
+
+ // Check that the message type is correct.
+ EXPECT_EQ(msg_type, pkt_received->getType());
+
+ // Check that the interface is correct.
+ EXPECT_EQ(concatenate("eth", i % 2), pkt_received->getIface());
+ EXPECT_EQ(ETH0_INDEX + i % 2, pkt_received->getIndex());
+
+ // Check that the address conveyed is correct.
+ EXPECT_EQ(concatenate("2001:db8:1::", i),
+ pkt_received->getRemoteAddr().toText());
+
+ // Check that the port conveyed is correct.
+ EXPECT_EQ(10000 + (i % 1000), pkt_received->getRemotePort());
+
+ // Check that encapsulated DHCPv4 message has been received.
+ EXPECT_TRUE(pkt_received->getOption(D6O_DHCPV4_MSG));
+
+ if (has_vendor_option[i - 1]) {
+ // Make sure that the vendor option wasn't deleted when the packet was
+ // received.
+ OptionPtr option_vendor = pkt_received->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(option_vendor)
+ << "vendor option deleted in the received DHCPv4o6 packet for"
+ " iteration " << i;
+
+ // ISC_V6_4O6_INTERFACE shouldn't be present.
+ EXPECT_FALSE(option_vendor->getOption(ISC_V6_4O6_INTERFACE));
+
+ // ISC_V6_4O6_SRC_ADDRESS shouldn't be present.
+ EXPECT_FALSE(option_vendor->getOption(ISC_V6_4O6_SRC_ADDRESS));
+
+ } else {
+ EXPECT_FALSE(pkt_received->getOption(D6O_VENDOR_OPTS));
+ }
+ }
+}
+
+void
+Dhcp4o6IpcBaseTest::testReceiveError(const Pkt6Ptr& pkt) {
+ TestIpc ipc_src(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+ TestIpc ipc_dest(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+ // Open the IPC on both ends.
+ ASSERT_NO_THROW(ipc_src.open());
+ ASSERT_NO_THROW(ipc_dest.open());
+
+ pkt->setIface("eth0");
+ pkt->setIndex(ETH0_INDEX);
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::1"));
+ pkt->setRemotePort(TEST_PORT);
+ pkt->addOption(createDHCPv4MsgOption(TestIpc::ENDPOINT_TYPE_V6));
+
+ OutputBuffer& buf = pkt->getBuffer();
+ buf.clear();
+ ASSERT_NO_THROW(pkt->pack());
+
+ ASSERT_NE(-1, ::send(ipc_src.getSocketFd(), buf.getData(),
+ buf.getLength(), 0));
+
+ // Call receive with a timeout. The data should appear on the socket
+ // within this time.
+ ASSERT_THROW(IfaceMgr::instance().receive6(1, 0), Dhcp4o6IpcError);
+}
+
+
+// This test verifies that the IPC can transmit messages between the
+// DHCPv4 and DHCPv6 server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6) {
+ testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V4,
+ TestIpc::ENDPOINT_TYPE_V6, &createDHCPv4o6Message);
+}
+
+// This test verifies that the IPC can transmit messages between the
+// DHCPv6 and DHCPv4 server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4) {
+ testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V6,
+ TestIpc::ENDPOINT_TYPE_V4, &createDHCPv4o6Message);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with ISC enterprise ID, between the DHCPv6 and DHCPv4
+// server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4WithISCVendorOption) {
+ testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V6,
+ TestIpc::ENDPOINT_TYPE_V4,
+ &createDHCPv4o6MsgWithISCVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with ISC enterprise ID, between the DHCPv6 and DHCPv4
+// server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6WithISCVendorOption) {
+ testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V4,
+ TestIpc::ENDPOINT_TYPE_V6,
+ &createDHCPv4o6MsgWithISCVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with enterprise id different than ISC, between the DHCPv6
+// and DHCPv4 server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4WithAnyVendorOption) {
+ testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V6,
+ TestIpc::ENDPOINT_TYPE_V4,
+ &createDHCPv4o6MsgWithAnyVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with enterprise id different than ISC, between the DHCPv4
+// and DHCPv6 server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6WithAnyVendorOption) {
+ testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V4,
+ TestIpc::ENDPOINT_TYPE_V6,
+ &createDHCPv4o6MsgWithAnyVendorOption);
+}
+
+// This test checks that the values of the socket descriptor are correct
+// when the socket is opened and then closed.
+TEST_F(Dhcp4o6IpcBaseTest, openClose) {
+ TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+ EXPECT_EQ(-1, ipc.getSocketFd());
+
+ ASSERT_NO_THROW(ipc.open());
+ EXPECT_NE(-1, ipc.getSocketFd());
+
+ ASSERT_NO_THROW(ipc.close());
+ EXPECT_EQ(-1, ipc.getSocketFd());
+}
+
+// This test verifies that it is call open() while the socket is already
+// opened. If the port changes, the new socket should be opened.
+TEST_F(Dhcp4o6IpcBaseTest, openMultipleTimes) {
+ TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+ ASSERT_NO_THROW(ipc.open());
+ int sock_fd = ipc.getSocketFd();
+ ASSERT_NE(-1, sock_fd);
+ ASSERT_EQ(TEST_PORT, ipc.getPort());
+
+ ASSERT_NO_THROW(ipc.open());
+ ASSERT_EQ(sock_fd, ipc.getSocketFd());
+ ASSERT_EQ(TEST_PORT, ipc.getPort());
+
+ ipc.setDesiredPort(TEST_PORT + 10);
+ ASSERT_NO_THROW(ipc.open());
+ ASSERT_NE(-1, ipc.getSocketFd());
+
+ EXPECT_EQ(TEST_PORT + 10, ipc.getPort());
+}
+
+// This test verifies that the socket remains open if there is a failure
+// to open a new socket.
+TEST_F(Dhcp4o6IpcBaseTest, openError) {
+ TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+ ASSERT_NO_THROW(ipc.open());
+ int sock_fd = ipc.getSocketFd();
+ ASSERT_NE(-1, sock_fd);
+
+ TestIpc ipc_bound(TEST_PORT + 10, TestIpc::ENDPOINT_TYPE_V4);
+ ASSERT_NO_THROW(ipc_bound.open());
+ ASSERT_NE(-1, ipc_bound.getSocketFd());
+
+ ipc.setDesiredPort(TEST_PORT + 10);
+ ASSERT_THROW(ipc.open(), isc::dhcp::Dhcp4o6IpcError);
+
+ EXPECT_EQ(sock_fd, ipc.getSocketFd());
+ EXPECT_EQ(TEST_PORT, ipc.getPort());
+
+ ASSERT_NO_THROW(ipc_bound.close());
+ ASSERT_NO_THROW(ipc.open());
+ EXPECT_NE(-1, ipc.getSocketFd());
+ EXPECT_EQ(TEST_PORT + 10, ipc.getPort());
+}
+
+// This test verifies that receiving packet over the IPC fails when there
+// is no vendor option present.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutVendorOption) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+ testReceiveError(pkt);
+}
+
+// This test verifies that receiving packet over the IPC fails when the
+// enterprise ID carried in the vendor option is invalid.
+TEST_F(Dhcp4o6IpcBaseTest, receiveInvalidEnterpriseId) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+ OptionVendorPtr option_vendor(new OptionVendor(Option::V6, 1));
+ option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+ ISC_V6_4O6_INTERFACE,
+ "eth0")));
+ option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+ IOAddress("2001:db8:1::1"))));
+
+ pkt->addOption(option_vendor);
+ testReceiveError(pkt);
+}
+
+// This test verifies that receiving packet over the IPC fails when the
+// interface option is not present.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutInterfaceOption) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+ OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+ option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+ IOAddress("2001:db8:1::1"))));
+ option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
+ ISC_V6_4O6_SRC_PORT,
+ TEST_PORT)));
+
+ pkt->addOption(option_vendor);
+ testReceiveError(pkt);
+}
+
+// This test verifies that receiving packet over the IPC fails when the
+// interface which name is carried in the option is not present in the
+// system.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithInvalidInterface) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+ OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+ option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+ ISC_V6_4O6_INTERFACE,
+ "ethX")));
+ option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+ IOAddress("2001:db8:1::1"))));
+ option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
+ ISC_V6_4O6_SRC_PORT,
+ TEST_PORT)));
+
+ pkt->addOption(option_vendor);
+ testReceiveError(pkt);
+}
+
+
+// This test verifies that receiving packet over the IPC fails when the
+// source address option is not present.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourceAddressOption) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+ OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+ option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+ ISC_V6_4O6_INTERFACE,
+ "eth0")));
+ option_vendor->addOption(OptionUint16Ptr(new OptionUint16(Option::V6,
+ ISC_V6_4O6_SRC_PORT,
+ TEST_PORT)));
+
+ pkt->addOption(option_vendor);
+ testReceiveError(pkt);
+}
+
+// This test verifies that receiving packet over the IPC fails when the
+// source port option is not present.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourcePortOption) {
+ Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+ OptionVendorPtr option_vendor(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC));
+ option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6,
+ ISC_V6_4O6_INTERFACE,
+ "eth0")));
+ option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+ IOAddress("2001:db8:1::1"))));
+
+ pkt->addOption(option_vendor);
+ testReceiveError(pkt);
+}
+
+// This test verifies that send method throws exception when the packet
+// is NULL.
+TEST_F(Dhcp4o6IpcBaseTest, sendNullMessage) {
+ TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+ ASSERT_NO_THROW(ipc.open());
+
+ // NULL message.
+ EXPECT_THROW(ipc.send(Pkt6Ptr()), Dhcp4o6IpcError);
+}
+
+// This test verifies that send method throws exception when the IPC
+// socket is not opened.
+TEST_F(Dhcp4o6IpcBaseTest, sendOverClosedSocket) {
+ TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+ // Create a message.
+ Pkt6Ptr pkt(createDHCPv4o6Message(DHCPV6_DHCPV4_QUERY));
+
+ // Sending over the closed socket should fail.
+ EXPECT_THROW(ipc.send(pkt), Dhcp4o6IpcError);
+}
+
+// This test verifies that send method throws exception when the IPC
+// socket has been unexpectedly closed.
+TEST_F(Dhcp4o6IpcBaseTest, sendOverUnexpectedlyClosedSocket) {
+ TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+ ASSERT_NO_THROW(ipc.open());
+
+ // Close the socket behind the scenes. The IPC doesn't know that the
+ // socket has been closed and it still holds the descriptor.
+ ::close(ipc.getSocketFd());
+
+ // Create a message.
+ Pkt6Ptr pkt(createDHCPv4o6Message(DHCPV6_DHCPV4_QUERY));
+
+ // Sending over the closed socket should fail.
+ EXPECT_THROW(ipc.send(pkt), Dhcp4o6IpcError);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
new file mode 100644
index 0000000..de9f6a2
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -0,0 +1,3557 @@
+// Copyright (C) 2012-2023 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 <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfg_mac_source.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/iterative_allocation_state.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/option_data_parser.h>
+#include <dhcpsrv/parsers/shared_network_parser.h>
+#include <dhcpsrv/parsers/shared_networks_list_parser.h>
+#include <dhcpsrv/random_allocator.h>
+#include <dhcpsrv/random_allocation_state.h>
+#include <dhcpsrv/tests/test_libraries.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_parser.h>
+#include <hooks/hooks_manager.h>
+#include <testutils/test_to_element.h>
+
+#include <gtest/gtest.h>
+#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
+
+#include <map>
+#include <string>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace isc::test;
+
+namespace {
+
+/// @brief DHCP Parser test fixture class
+class DhcpParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ DhcpParserTest() {
+ resetIfaceCfg();
+ }
+
+ /// @brief Destructor.
+ virtual ~DhcpParserTest() {
+ resetIfaceCfg();
+ }
+
+ /// @brief Resets selection of the interfaces from previous tests.
+ void resetIfaceCfg() {
+ CfgMgr::instance().clear();
+ }
+};
+
+/// Verifies the code that parses mac sources and adds them to CfgMgr
+TEST_F(DhcpParserTest, MacSources) {
+
+ // That's an equivalent of the following snippet:
+ // "mac-sources: [ \"duid\", \"ipv6\" ]";
+ ElementPtr values = Element::createList();
+ values->add(Element::create("duid"));
+ values->add(Element::create("ipv6-link-local"));
+
+ // Let's grab server configuration from CfgMgr
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ CfgMACSource& sources = cfg->getMACSources();
+
+ // This should parse the configuration and check that it doesn't throw.
+ MACSourcesListConfigParser parser;
+ EXPECT_NO_THROW(parser.parse(sources, values));
+
+ // Finally, check the sources that were configured
+ CfgMACSources configured_sources = cfg->getMACSources().get();
+ ASSERT_EQ(2, configured_sources.size());
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]);
+}
+
+/// @brief Check MACSourcesListConfigParser rejecting empty list
+///
+/// Verifies that the code rejects an empty mac-sources list.
+TEST_F(DhcpParserTest, MacSourcesEmpty) {
+
+ // That's an equivalent of the following snippet:
+ // "mac-sources: [ \"duid\", \"ipv6\" ]";
+ ElementPtr values = Element::createList();
+
+ // Let's grab server configuration from CfgMgr
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ CfgMACSource& sources = cfg->getMACSources();
+
+ // This should throw, because if specified, at least one MAC source
+ // has to be specified.
+ MACSourcesListConfigParser parser;
+ EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
+}
+
+/// @brief Check MACSourcesListConfigParser rejecting empty list
+///
+/// Verifies that the code rejects fake mac source.
+TEST_F(DhcpParserTest, MacSourcesBogus) {
+
+ // That's an equivalent of the following snippet:
+ // "mac-sources: [ \"duid\", \"ipv6\" ]";
+ ElementPtr values = Element::createList();
+ values->add(Element::create("from-ebay"));
+ values->add(Element::create("just-guess-it"));
+
+ // Let's grab server configuration from CfgMgr
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ CfgMACSource& sources = cfg->getMACSources();
+
+ // This should throw, because these are not valid sources.
+ MACSourcesListConfigParser parser;
+ EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
+}
+
+/// Verifies the code that properly catches duplicate entries
+/// in mac-sources definition.
+TEST_F(DhcpParserTest, MacSourcesDuplicate) {
+
+ // That's an equivalent of the following snippet:
+ // "mac-sources: [ \"duid\", \"ipv6\" ]";
+ ElementPtr values = Element::createList();
+ values->add(Element::create("ipv6-link-local"));
+ values->add(Element::create("duid"));
+ values->add(Element::create("duid"));
+ values->add(Element::create("duid"));
+
+ // Let's grab server configuration from CfgMgr
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ CfgMACSource& sources = cfg->getMACSources();
+
+ // This should parse the configuration and check that it throws.
+ MACSourcesListConfigParser parser;
+ EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
+}
+
+
+/// @brief Test Fixture class which provides basic structure for testing
+/// configuration parsing. This is essentially the same structure provided
+/// by dhcp servers.
+class ParseConfigTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ParseConfigTest()
+ :family_(AF_INET6) {
+ reset_context();
+ }
+
+ ~ParseConfigTest() {
+ reset_context();
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Parses a configuration.
+ ///
+ /// Parse the given configuration, populating the context storage with
+ /// the parsed elements.
+ ///
+ /// @param config_set is the set of elements to parse.
+ /// @param v6 boolean flag indicating if this is a DHCPv6 configuration.
+ /// @return returns an ConstElementPtr containing the numeric result
+ /// code and outcome comment.
+ isc::data::ConstElementPtr
+ parseElementSet(isc::data::ConstElementPtr config_set, bool v6) {
+ // Answer will hold the result.
+ ConstElementPtr answer;
+ if (!config_set) {
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR,
+ string("Can't parse NULL config"));
+ return (answer);
+ }
+
+ ConfigPair config_pair;
+ try {
+ // Iterate over the config elements.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(config_pair, values_map) {
+
+ // These are the simple parsers. No need to go through
+ // the ParserPtr hooplas with them.
+ if ((config_pair.first == "option-data") ||
+ (config_pair.first == "option-def") ||
+ (config_pair.first == "dhcp-ddns")) {
+ continue;
+ }
+
+ // We also don't care about the default values that may be been
+ // inserted
+ if ((config_pair.first == "preferred-lifetime") ||
+ (config_pair.first == "valid-lifetime") ||
+ (config_pair.first == "renew-timer") ||
+ (config_pair.first == "rebind-timer")) {
+ continue;
+ }
+
+ // Save global hostname-char-*.
+ if ((config_pair.first == "hostname-char-set") ||
+ (config_pair.first == "hostname-char-replacement")) {
+ CfgMgr::instance().getStagingCfg()->addConfiguredGlobal(config_pair.first,
+ config_pair.second);
+ continue;
+ }
+
+ if (config_pair.first == "hooks-libraries") {
+ HooksLibrariesParser hook_parser;
+ HooksConfig& libraries =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ hook_parser.parse(libraries, config_pair.second);
+ libraries.verifyLibraries(config_pair.second->getPosition(), false);
+ libraries.loadLibraries(false);
+ continue;
+ }
+ }
+
+ // The option definition parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator
+ def_config = values_map.find("option-def");
+ if (def_config != values_map.end()) {
+
+ CfgOptionDefPtr cfg_def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef();
+ OptionDefListParser def_list_parser(family_);
+ def_list_parser.parse(cfg_def, def_config->second);
+ }
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator
+ option_config = values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ CfgOptionPtr cfg_option = CfgMgr::instance().getStagingCfg()->getCfgOption();
+
+ OptionDataListParser option_list_parser(family_);
+ option_list_parser.parse(cfg_option, option_config->second);
+ }
+
+ // The dhcp-ddns parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator
+ d2_client_config = values_map.find("dhcp-ddns");
+ if (d2_client_config != values_map.end()) {
+ // Used to be done by parser commit
+ D2ClientConfigParser parser;
+ D2ClientConfigPtr cfg = parser.parse(d2_client_config->second);
+ cfg->validateContents();
+ CfgMgr::instance().setD2ClientConfig(cfg);
+ }
+
+ std::map<std::string, ConstElementPtr>::const_iterator
+ subnets4_config = values_map.find("subnet4");
+ if (subnets4_config != values_map.end()) {
+ auto srv_config = CfgMgr::instance().getStagingCfg();
+ Subnets4ListConfigParser parser;
+ parser.parse(srv_config, subnets4_config->second);
+ }
+
+ std::map<std::string, ConstElementPtr>::const_iterator
+ subnets6_config = values_map.find("subnet6");
+ if (subnets6_config != values_map.end()) {
+ auto srv_config = CfgMgr::instance().getStagingCfg();
+ Subnets6ListConfigParser parser;
+ parser.parse(srv_config, subnets6_config->second);
+ }
+
+ std::map<std::string, ConstElementPtr>::const_iterator
+ networks_config = values_map.find("shared-networks");
+ if (networks_config != values_map.end()) {
+ if (v6) {
+ auto cfg_shared_networks = CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6();
+ SharedNetworks6ListParser parser;
+ parser.parse(cfg_shared_networks, networks_config->second);
+
+ } else {
+ auto cfg_shared_networks = CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4();
+ SharedNetworks4ListParser parser;
+ parser.parse(cfg_shared_networks, networks_config->second);
+ }
+ }
+
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Configuration committed.");
+ } catch (const isc::Exception& ex) {
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR,
+ string("Configuration parsing failed: ") + ex.what());
+
+ } catch (...) {
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR,
+ string("Configuration parsing failed"));
+ }
+
+ return (answer);
+ }
+
+ /// @brief DHCP-specific method that sets global, and option specific defaults
+ ///
+ /// This method sets the defaults in the global scope, in option definitions,
+ /// and in option data.
+ ///
+ /// @param global pointer to the Element tree that holds configuration
+ /// @param global_defaults array with global default values
+ /// @param option_defaults array with option-data default values
+ /// @param option_def_defaults array with default values for option definitions
+ /// @return number of default values inserted.
+ size_t setAllDefaults(isc::data::ElementPtr global,
+ const SimpleDefaults& global_defaults,
+ const SimpleDefaults& option_defaults,
+ const SimpleDefaults& option_def_defaults) {
+ size_t cnt = 0;
+ // Set global defaults first.
+ cnt = SimpleParser::setDefaults(global, global_defaults);
+
+ // Now set option definition defaults for each specified option definition
+ ConstElementPtr option_defs = global->get("option-def");
+ if (option_defs) {
+ BOOST_FOREACH(ElementPtr single_def, option_defs->listValue()) {
+ cnt += SimpleParser::setDefaults(single_def, option_def_defaults);
+ }
+ }
+
+ ConstElementPtr options = global->get("option-data");
+ if (options) {
+ BOOST_FOREACH(ElementPtr single_option, options->listValue()) {
+ cnt += SimpleParser::setDefaults(single_option, option_defaults);
+ }
+ }
+
+ return (cnt);
+ }
+
+ /// This table defines default values for option definitions in DHCPv6
+ static const SimpleDefaults OPTION6_DEF_DEFAULTS;
+
+ /// This table defines default values for option definitions in DHCPv4
+ static const SimpleDefaults OPTION4_DEF_DEFAULTS;
+
+ /// This table defines default values for options in DHCPv6
+ static const SimpleDefaults OPTION6_DEFAULTS;
+
+ /// This table defines default values for options in DHCPv4
+ static const SimpleDefaults OPTION4_DEFAULTS;
+
+ /// This table defines default values for both DHCPv4 and DHCPv6
+ static const SimpleDefaults GLOBAL6_DEFAULTS;
+
+ /// @brief sets all default values for DHCPv4 and DHCPv6
+ ///
+ /// This function largely duplicates what SimpleParser4 and SimpleParser6 classes
+ /// provide. However, since there are tons of unit-tests in dhcpsrv that need
+ /// this functionality and there are good reasons to keep those classes in
+ /// src/bin/dhcp{4,6}, the most straightforward way is to simply copy the
+ /// minimum code here. Hence this method.
+ ///
+ /// @todo - TKM, I think this is fairly hideous and we should figure out a
+ /// a way to not have to replicate in this fashion. It may be minimum code
+ /// now, but it won't be fairly soon.
+ ///
+ /// @param config configuration structure to be filled with default values
+ /// @param v6 true = DHCPv6, false = DHCPv4
+ void setAllDefaults(ElementPtr config, bool v6) {
+ if (v6) {
+ setAllDefaults(config, GLOBAL6_DEFAULTS, OPTION6_DEFAULTS,
+ OPTION6_DEF_DEFAULTS);
+ } else {
+ setAllDefaults(config, GLOBAL6_DEFAULTS, OPTION4_DEFAULTS,
+ OPTION4_DEF_DEFAULTS);
+ }
+
+ /// D2 client configuration code is in this library
+ ConstElementPtr d2_client = config->get("dhcp-ddns");
+ if (d2_client) {
+ D2ClientConfigParser::setAllDefaults(d2_client);
+ }
+ }
+
+ /// @brief Convenience method for parsing a configuration
+ ///
+ /// Given a configuration string, convert it into Elements
+ /// and parse them.
+ /// @param config is the configuration string to parse
+ /// @param v6 boolean value indicating if this is DHCPv6 configuration.
+ /// @param set_defaults boolean value indicating if the defaults should
+ /// be derived before parsing the configuration.
+ ///
+ /// @return returns 0 if the configuration parsed successfully,
+ /// non-zero otherwise failure.
+ int parseConfiguration(const std::string& config, bool v6 = false,
+ bool set_defaults = true) {
+ int rcode_ = 1;
+ // Turn config into elements.
+ // Test json just to make sure its valid.
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ if (json) {
+ if (set_defaults) {
+ setAllDefaults(json, v6);
+ }
+
+ ConstElementPtr status = parseElementSet(json, v6);
+ ConstElementPtr comment = parseAnswer(rcode_, status);
+ error_text_ = comment->stringValue();
+ // If error was reported, the error string should contain
+ // position of the data element which caused failure.
+ if (rcode_ != 0) {
+ EXPECT_TRUE(errorContainsPosition(status, "<string>"))
+ << "error text: " << error_text_;
+ }
+ }
+
+ return (rcode_);
+ }
+
+ /// @brief Find an option for a given space and code within the parser
+ /// context.
+ /// @param space is the space name of the desired option.
+ /// @param code is the numeric "type" of the desired option.
+ /// @return returns an OptionPtr which points to the found
+ /// option or is empty.
+ /// ASSERT_ tests don't work inside functions that return values
+ OptionPtr getOptionPtr(std::string space, uint32_t code)
+ {
+ OptionPtr option_ptr;
+ OptionContainerPtr options = CfgMgr::instance().getStagingCfg()->
+ getCfgOption()->getAll(space);
+ // Should always be able to get options list even if it is empty.
+ EXPECT_TRUE(options);
+ if (options) {
+ // Attempt to find desired option.
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ const OptionContainerTypeRange& range = idx.equal_range(code);
+ int cnt = std::distance(range.first, range.second);
+ EXPECT_EQ(1, cnt);
+ if (cnt == 1) {
+ OptionDescriptor desc = *(idx.begin());
+ option_ptr = desc.option_;
+ EXPECT_TRUE(option_ptr);
+ }
+ }
+
+ return (option_ptr);
+ }
+
+ /// @brief Wipes the contents of the context to allowing another parsing
+ /// during a given test if needed.
+ /// @param family protocol family to use during the test, defaults
+ /// to AF_INET6
+ void reset_context(uint16_t family = AF_INET6){
+ // Note set context universe to V6 as it has to be something.
+ CfgMgr::instance().clear();
+ family_ = family;
+
+ // Ensure no hooks libraries are loaded.
+ EXPECT_TRUE(HooksManager::unloadLibraries());
+
+ // Set it to minimal, disabled config
+ D2ClientConfigPtr tmp(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(tmp);
+ }
+
+ /// Allows the tests to interrogate the state of the libraries (if required).
+ const isc::hooks::HookLibsCollection& getLibraries() {
+ return (CfgMgr::instance().getStagingCfg()->getHooksConfig().get());
+ }
+
+ /// @brief specifies IP protocol family (AF_INET or AF_INET6)
+ uint16_t family_;
+
+ /// @brief Error string if the parsing failed
+ std::string error_text_;
+};
+
+/// This table defines default values for option definitions in DHCPv6
+const SimpleDefaults ParseConfigTest::OPTION6_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp6"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// This table defines default values for option definitions in DHCPv4
+const SimpleDefaults ParseConfigTest::OPTION4_DEF_DEFAULTS = {
+ { "record-types", Element::string, ""},
+ { "space", Element::string, "dhcp4"},
+ { "array", Element::boolean, "false"},
+ { "encapsulate", Element::string, "" }
+};
+
+/// This table defines default values for options in DHCPv6
+const SimpleDefaults ParseConfigTest::OPTION6_DEFAULTS = {
+ { "space", Element::string, "dhcp6"},
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean, "false"},
+ { "never-send", Element::boolean, "false"}
+};
+
+/// This table defines default values for options in DHCPv4
+const SimpleDefaults ParseConfigTest::OPTION4_DEFAULTS = {
+ { "space", Element::string, "dhcp4"},
+ { "csv-format", Element::boolean, "true"},
+ { "always-send", Element::boolean, "false"},
+ { "never-send", Element::boolean, "false"}
+};
+
+/// This table defines default values for both DHCPv4 and DHCPv6
+const SimpleDefaults ParseConfigTest::GLOBAL6_DEFAULTS = {
+ { "renew-timer", Element::integer, "900" },
+ { "rebind-timer", Element::integer, "1800" },
+ { "preferred-lifetime", Element::integer, "3600" },
+ { "valid-lifetime", Element::integer, "7200" }
+};
+
+/// @brief Option configuration class
+///
+/// This class handles option-def and option-data which can be recovered
+/// using the toElement() method
+class CfgOptionsTest : public CfgToElement {
+public:
+ /// @brief Constructor
+ ///
+ /// @param cfg the server configuration where to get option-{def,data}
+ CfgOptionsTest(SrvConfigPtr cfg) :
+ cfg_option_def_(cfg->getCfgOptionDef()),
+ cfg_option_(cfg->getCfgOption()) { }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration (a map with
+ /// not empty option-def and option-data lists)
+ ElementPtr toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set option-def
+ ConstElementPtr option_def = cfg_option_def_->toElement();
+ if (!option_def->empty()) {
+ result->set("option-def", option_def);
+ }
+ // Set option-data
+ ConstElementPtr option_data = cfg_option_->toElement();
+ if (!option_data->empty()) {
+ result->set("option-data", option_data);
+ }
+ return (result);
+ }
+
+ /// @brief Run a toElement test (Element version)
+ ///
+ /// Use the runToElementTest template but add defaults to the config
+ ///
+ /// @param family the address family
+ /// @param config the expected result without defaults
+ void runCfgOptionsTest(uint16_t family, ConstElementPtr expected) {
+ ConstElementPtr option_def = expected->get("option-def");
+ if (option_def) {
+ SimpleParser::setListDefaults(option_def,
+ family == AF_INET ?
+ ParseConfigTest::OPTION4_DEF_DEFAULTS :
+ ParseConfigTest::OPTION6_DEF_DEFAULTS);
+ }
+ ConstElementPtr option_data = expected->get("option-data");
+ if (option_data) {
+ SimpleParser::setListDefaults(option_data,
+ family == AF_INET ?
+ ParseConfigTest::OPTION4_DEFAULTS :
+ ParseConfigTest::OPTION6_DEFAULTS);
+ }
+ runToElementTest<CfgOptionsTest>(expected, *this);
+ }
+
+ /// @brief Run a toElement test
+ ///
+ /// Use the runToElementTest template but add defaults to the config
+ ///
+ /// @param family the address family
+ /// @param expected the expected result without defaults
+ void runCfgOptionsTest(uint16_t family, std::string config) {
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config)) << config;
+ runCfgOptionsTest(family, json);
+ }
+
+private:
+ /// @brief Pointer to option definitions configuration.
+ CfgOptionDefPtr cfg_option_def_;
+
+ /// @brief Reference to options (data) configuration.
+ CfgOptionPtr cfg_option_;
+};
+
+/// @brief Check basic parsing of option definitions.
+///
+/// Note that this tests basic operation of the OptionDefinitionListParser and
+/// OptionDefinitionParser. It uses a simple configuration consisting of
+/// one definition and verifies that it is parsed and committed to storage
+/// correctly.
+TEST_F(ParseConfigTest, basicOptionDefTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": false,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+
+ // Verify that the option definition can be retrieved.
+ OptionDefinitionPtr def =
+ CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition is correct.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+ // Check if libdhcp++ runtime options have been updated.
+ OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100);
+ ASSERT_TRUE(def_libdhcp);
+
+ // The LibDHCP should return a separate instance of the option definition
+ // but the values should be equal.
+ EXPECT_TRUE(def_libdhcp != def);
+ EXPECT_TRUE(*def_libdhcp == *def);
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check minimal parsing of option definitions.
+///
+/// Same than basic but without optional parameters set to their default.
+TEST_F(ParseConfigTest, minimalOptionDefTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+
+ // Verify that the option definition can be retrieved.
+ OptionDefinitionPtr def =
+ CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition is correct.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check parsing of option definitions using default dhcp6 space.
+///
+/// Same than minimal but using the fact the default universe is V6
+/// so the default space is dhcp6
+TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 10000,"
+ " \"type\": \"ipv6-address\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config, true);
+ ASSERT_EQ(0, rcode);
+
+
+ // Verify that the option definition can be retrieved.
+ OptionDefinitionPtr def =
+ CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 10000);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition is correct.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(10000, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check parsing of option definitions using invalid code fails.
+TEST_F(ParseConfigTest, badCodeOptionDefTest) {
+
+ {
+ SCOPED_TRACE("negative code");
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"negative\","
+ " \"code\": -1,"
+ " \"type\": \"ipv6-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, true);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("out of range code (v6)");
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"hundred-thousands\","
+ " \"code\": 100000,"
+ " \"type\": \"ipv6-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, true);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("out of range code (v4)");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"thousand\","
+ " \"code\": 1000,"
+ " \"type\": \"ip-address\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with PAD");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"zero\","
+ " \"code\": 0,"
+ " \"type\": \"ip-address\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with END");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"max\","
+ " \"code\": 255,"
+ " \"type\": \"ip-address\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with reserved");
+
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"zero\","
+ " \"code\": 0,"
+ " \"type\": \"ipv6-address\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+}
+
+/// @brief Check parsing of option definitions using invalid space fails.
+TEST_F(ParseConfigTest, badSpaceOptionDefTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv6-address\","
+ " \"space\": \"-1\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string does not parse.
+ int rcode = parseConfiguration(config, true);
+ ASSERT_NE(0, rcode);
+}
+
+/// @brief Check basic parsing of options.
+///
+/// Note that this tests basic operation of the OptionDataListParser and
+/// OptionDataParser. It uses a simple configuration consisting of one
+/// one definition and matching option data. It verifies that the option
+/// is parsed and committed to storage correctly.
+TEST_F(ParseConfigTest, basicOptionDataTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 100,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 100);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option data is correct.
+ std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check parsing of options with code 0.
+TEST_F(ParseConfigTest, optionDataTest0) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 0,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 0,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 0);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option data is correct.
+ std::string val = "type=00000, len=00004: 192.0.2.0 (ipv4-address)";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check parsing of options with code 255.
+TEST_F(ParseConfigTest, optionDataTest255) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 255,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 255,"
+ " \"data\": \"192.0.2.0\","
+ " \"csv-format\": true,"
+ " \"always-send\": false"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 255);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option data is correct.
+ std::string val = "type=00255, len=00004: 192.0.2.0 (ipv4-address)";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+/// @brief Check minimal parsing of options.
+///
+/// Same than basic but without optional parameters set to their default.
+TEST_F(ParseConfigTest, minimalOptionDataTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"space\": \"isc\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"data\": \"192.0.2.0\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 100);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option data is correct.
+ std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(100));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+/// @brief Check parsing of unknown options fails.
+TEST_F(ParseConfigTest, unknownOptionDataTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"data\": \"01\","
+ " \"space\": \"bar\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string does not parse.
+ int rcode = parseConfiguration(config, true);
+ ASSERT_NE(0, rcode);
+}
+
+/// @brief Check parsing of option data using invalid code fails.
+TEST_F(ParseConfigTest, badCodeOptionDataTest) {
+
+ {
+ SCOPED_TRACE("negative code");
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"code\": -1,"
+ " \"data\": \"01\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, true);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("out of range code (v6)");
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"code\": 100000,"
+ " \"data\": \"01\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, true);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("out of range code (v4)");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"code\": 1000,"
+ " \"data\": \"01\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with PAD");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"code\": 0,"
+ " \"data\": \"01\","
+ " \"csv-format\": false,"
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with END");
+ family_ = AF_INET; // Switch to DHCPv4.
+
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"code\": 255,"
+ " \"data\": \"01\","
+ " \"csv-format\": false,"
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+
+ {
+ SCOPED_TRACE("conflict with reserved");
+ family_ = AF_INET6; // Switch to DHCPv6.
+
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"code\": 0,"
+ " \"data\": \"01\","
+ " \"csv-format\": false,"
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false);
+ ASSERT_NE(0, rcode);
+ }
+}
+
+/// @brief Check parsing of options with invalid space fails.
+TEST_F(ParseConfigTest, badSpaceOptionDataTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"code\": 100,"
+ " \"data\": \"01\","
+ " \"space\": \"-1\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string does not parse.
+ int rcode = parseConfiguration(config, true);
+ ASSERT_NE(0, rcode);
+}
+
+/// @brief Check parsing of options with escape characters.
+///
+/// Note that this tests basic operation of the OptionDataListParser and
+/// OptionDataParser. It uses a simple configuration consisting of one
+/// one definition and matching option data. It verifies that the option
+/// is parsed and committed to storage correctly and that its content
+/// has the actual character (e.g. an actual backslash, not double backslash).
+TEST_F(ParseConfigTest, escapedOptionDataTest) {
+
+ family_ = AF_INET;
+
+ // We need to use double escapes here. The first backslash will
+ // be consumed by C++ preprocessor, so the actual string will
+ // have two backslash characters: \\SMSBoot\\x64\\wdsnbp.com.
+ //
+ std::string config =
+ "{\"option-data\": [ {"
+ " \"name\": \"boot-file-name\","
+ " \"data\": \"\\\\SMSBoot\\\\x64\\\\wdsnbp.com\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(opt);
+
+ util::OutputBuffer buf(100);
+
+ uint8_t exp[] = { DHO_BOOT_FILE_NAME, 23, '\\', 'S', 'M', 'S', 'B', 'o', 'o',
+ 't', '\\', 'x', '6', '4', '\\', 'w', 'd', 's', 'n', 'b',
+ 'p', '.', 'c', 'o', 'm' };
+ ASSERT_EQ(25, sizeof(exp));
+
+ opt->pack(buf);
+ EXPECT_EQ(Option::OPTION4_HDR_LEN + 23, buf.getLength());
+
+ EXPECT_TRUE(0 == memcmp(buf.getData(), exp, 25));
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(DHO_BOOT_FILE_NAME));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// This test checks behavior of the configuration parser for option data
+// for different values of csv-format parameter and when there is an option
+// definition present.
+TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"swap-server\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 16,"
+ " \"data\": \"192.0.2.0\""
+ " } ]"
+ "}";
+
+ // The default universe is V6. We need to change it to use dhcp4 option
+ // space.
+ family_ = AF_INET;
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option data is correct.
+ OptionCustomPtr addr_opt = boost::dynamic_pointer_cast<
+ OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
+ ASSERT_TRUE(addr_opt);
+ EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+
+ // Explicitly enable csv-format.
+ CfgMgr::instance().clear();
+ config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"swap-server\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 16,"
+ " \"csv-format\": true,"
+ " \"data\": \"192.0.2.0\""
+ " } ]"
+ "}";
+ ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option data is correct.
+ addr_opt = boost::dynamic_pointer_cast<
+ OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
+ ASSERT_TRUE(addr_opt);
+ EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+
+ // To make runToElementTest to work the csv-format must be removed...
+
+ // Explicitly disable csv-format and use hex instead.
+ CfgMgr::instance().clear();
+ config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"swap-server\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 16,"
+ " \"csv-format\": false,"
+ " \"data\": \"C0000200\""
+ " } ]"
+ "}";
+ ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option data is correct.
+ addr_opt = boost::dynamic_pointer_cast<
+ OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
+ ASSERT_TRUE(addr_opt);
+ EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, config);
+}
+
+// This test verifies that definitions of standard encapsulated
+// options can be used.
+TEST_F(ParseConfigTest, encapsulatedOptionData) {
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"space\": \"s46-cont-mape-options\","
+ " \"name\": \"s46-rule\","
+ " \"data\": \"1, 0, 24, 192.0.2.0, 2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ // Make sure that we're using correct universe.
+ family_ = AF_INET6;
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option data is correct.
+ OptionCustomPtr s46_rule = boost::dynamic_pointer_cast<OptionCustom>
+ (getOptionPtr(MAPE_V6_OPTION_SPACE, D6O_S46_RULE));
+ ASSERT_TRUE(s46_rule);
+
+ uint8_t flags = 0;
+ uint8_t ea_len = 0;
+ uint8_t prefix4_len = 0;
+ IOAddress ipv4_prefix(IOAddress::IPV4_ZERO_ADDRESS());
+ PrefixTuple ipv6_prefix(PrefixLen(0), IOAddress::IPV6_ZERO_ADDRESS());;
+
+ ASSERT_NO_THROW({
+ flags = s46_rule->readInteger<uint8_t>(0);
+ ea_len = s46_rule->readInteger<uint8_t>(1);
+ prefix4_len = s46_rule->readInteger<uint8_t>(2);
+ ipv4_prefix = s46_rule->readAddress(3);
+ ipv6_prefix = s46_rule->readPrefix(4);
+ });
+
+ EXPECT_EQ(1, flags);
+ EXPECT_EQ(0, ea_len);
+ EXPECT_EQ(24, prefix4_len);
+ EXPECT_EQ("192.0.2.0", ipv4_prefix.toText());
+ EXPECT_EQ(64, ipv6_prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", ipv6_prefix.second.toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(D6O_S46_RULE));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// This test checks behavior of the configuration parser for option data
+// for different values of csv-format parameter and when there is no
+// option definition.
+TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
+ // This option doesn't have any definition. It is ok to use such
+ // an option but the data should be specified in hex, not as CSV.
+ // Note that the parser will by default use the CSV format for the
+ // data but only in case there is a suitable option definition.
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"foo-name\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 25000,"
+ " \"data\": \"1, 2, 5\""
+ " } ]"
+ "}";
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_NE(0, rcode);
+
+ CfgMgr::instance().clear();
+ // The data specified here will work both for CSV format and hex format.
+ // What we want to test here is that when the csv-format is enforced, the
+ // parser will fail because of lack of an option definition.
+ config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"foo-name\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 25000,"
+ " \"csv-format\": true,"
+ " \"data\": \"0\""
+ " } ]"
+ "}";
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_NE(0, rcode);
+
+ CfgMgr::instance().clear();
+ // The same test case as above, but for the data specified in hex should
+ // be successful.
+ config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"foo-name\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 25000,"
+ " \"csv-format\": false,"
+ " \"data\": \"0\""
+ " } ]"
+ "}";
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ ASSERT_EQ(0, rcode);
+ OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(1, opt->getData().size());
+ EXPECT_EQ(0, opt->getData()[0]);
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->remove("name");
+ opt_data->set("data", Element::create(std::string("00")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+
+ CfgMgr::instance().clear();
+ // When csv-format is not specified, the parser will check if the definition
+ // exists or not. Since there is no definition, the parser will accept the
+ // data in hex.
+ config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"foo-name\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 25000,"
+ " \"csv-format\": false,"
+ " \"data\": \"123456\""
+ " } ]"
+ "}";
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(3, opt->getData().size());
+ EXPECT_EQ(0x12, opt->getData()[0]);
+ EXPECT_EQ(0x34, opt->getData()[1]);
+ EXPECT_EQ(0x56, opt->getData()[2]);
+
+ expected = Element::fromJSON(config);
+ opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->remove("name");
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, expected);
+}
+
+// This test verifies that the option name is not mandatory, if the option
+// code has been specified.
+TEST_F(ParseConfigTest, optionDataNoName) {
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"space\": \"dhcp6\","
+ " \"code\": 23,"
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ "}";
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(1, opt->getAddresses().size());
+ EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("name", Element::create(std::string("dns-servers")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// This test verifies that the option code is not mandatory, if the option
+// name has been specified.
+TEST_F(ParseConfigTest, optionDataNoCode) {
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"space\": \"dhcp6\","
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ "}";
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(1, opt->getAddresses().size());
+ EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(D6O_NAME_SERVERS));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// This test verifies that the option data configuration with a minimal
+// set of parameters works as expected.
+TEST_F(ParseConfigTest, optionDataMinimal) {
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::10\""
+ " } ]"
+ "}";
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(1, opt->getAddresses().size());
+ EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(D6O_NAME_SERVERS));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+
+ CfgMgr::instance().clear();
+ // This time using an option code.
+ config =
+ "{ \"option-data\": [ {"
+ " \"code\": 23,"
+ " \"data\": \"2001:db8:1::20\""
+ " } ]"
+ "}";
+ rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
+ 23));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(1, opt->getAddresses().size());
+ EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
+
+ expected = Element::fromJSON(config);
+ opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("name", Element::create(std::string("dns-servers")));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, expected);
+}
+
+// This test verifies that the option data configuration with a minimal
+// set of parameters works as expected when option definition is
+// created in the configuration file.
+TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo-name\","
+ " \"code\": 2345,"
+ " \"type\": \"ipv6-address\","
+ " \"array\": true,"
+ " \"space\": \"dhcp6\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"foo-name\","
+ " \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
+ " } ]"
+ "}";
+
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 2345));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(2, opt->getAddresses().size());
+ EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
+ EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(2345));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+
+ CfgMgr::instance().clear();
+ // Do the same test but now use an option code.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo-name\","
+ " \"code\": 2345,"
+ " \"type\": \"ipv6-address\","
+ " \"array\": true,"
+ " \"space\": \"dhcp6\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"code\": 2345,"
+ " \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
+ " } ]"
+ "}";
+
+ rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
+ 2345));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(2, opt->getAddresses().size());
+ EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
+ EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
+
+ expected = Element::fromJSON(config);
+ opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("name", Element::create(std::string("foo-name")));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+ cfg2.runCfgOptionsTest(family_, expected);
+}
+
+// This test verifies an empty option data configuration is supported.
+TEST_F(ParseConfigTest, emptyOptionData) {
+ // Configuration string.
+ const std::string config =
+ "{ \"option-data\": [ {"
+ " \"name\": \"dhcp4o6-server-addr\""
+ " } ]"
+ "}";
+
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
+ EXPECT_EQ(0, rcode);
+ const Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+ Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, D6O_DHCPV4_O_DHCPV6_SERVER));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(0, opt->getAddresses().size());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(D6O_DHCPV4_O_DHCPV6_SERVER));
+ opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ opt_data->set("csv-format", Element::create(false));
+ opt_data->set("data", Element::create(std::string("")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// This test verifies an option data without suboptions is supported
+TEST_F(ParseConfigTest, optionDataNoSubOption) {
+ // Configuration string. A global definition for option 43 is needed.
+ const std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"vendor-encapsulated-options\","
+ " \"code\": 43,"
+ " \"type\": \"empty\","
+ " \"space\": \"dhcp4\","
+ " \"encapsulate\": \"vendor-encapsulated-options\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"vendor-encapsulated-options\""
+ " } ]"
+ "}";
+
+ // The default universe is V6. We need to change it to use dhcp4 option
+ // space.
+ family_ = AF_INET;
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(config));
+ EXPECT_EQ(0, rcode);
+ const OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(0, opt->getOptions().size());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->set("code", Element::create(DHO_VENDOR_ENCAPSULATED_OPTIONS));
+ opt_data->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+ opt_data->set("csv-format", Element::create(false));
+ opt_data->set("data", Element::create(std::string("")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// This tests option-data in CSV format and embedded commas.
+TEST_F(ParseConfigTest, commaCSVFormatOptionData) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-data\": [ {"
+ " \"csv-format\": true,"
+ " \"code\": 41,"
+ " \"data\": \"EST5EDT4\\\\,M3.2.0/02:00\\\\,M11.1.0/02:00\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config, true);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 41);
+ ASSERT_TRUE(opt);
+
+ // Get the option as an option string.
+ OptionStringPtr opt_str = boost::dynamic_pointer_cast<OptionString>(opt);
+ ASSERT_TRUE(opt_str);
+
+
+ // Verify that the option data is correct.
+ string val = "EST5EDT4,M3.2.0/02:00,M11.1.0/02:00";
+ EXPECT_EQ(val, opt_str->getValue());
+
+ ElementPtr expected = Element::fromJSON(config);
+ ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+ opt_data->remove("csv-format");
+ opt_data->set("name", Element::create(std::string("new-posix-timezone")));
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, expected);
+}
+
+// Verifies that hex literals can support a variety of formats.
+TEST_F(ParseConfigTest, hexOptionData) {
+
+ // All of the following variants should parse correctly
+ // into the same two IPv4 addresses: 12.0.3.1 and 192.0.3.2
+ std::vector<std::string> valid_hexes = {
+ "0C000301C0000302", // even number
+ "C000301C0000302", // odd number
+ "0C 00 03 01 C0 00 03 02", // spaces
+ "0C:00:03:01:C0:00:03:02", // colons
+ "0x0C000301C0000302", // 0x
+ "C 0 3 1 C0 0 3 02", // one or two digit octets
+ "0x0c000301C0000302" // upper or lower case digits
+ };
+
+ for (auto hex_str : valid_hexes) {
+ ostringstream os;
+ os <<
+ "{ \n"
+ " \"option-data\": [ { \n"
+ " \"name\": \"domain-name-servers\", \n"
+ " \"code\": 6, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": false, \n"
+ " \"data\": \"" << hex_str << "\" \n"
+ " } ] \n"
+ "} \n";
+
+ reset_context(AF_INET);
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(os.str(), true));
+ EXPECT_EQ(0, rcode);
+
+ Option4AddrLstPtr opt = boost::dynamic_pointer_cast<Option4AddrLst>
+ (getOptionPtr(DHCP4_OPTION_SPACE, 6));
+ ASSERT_TRUE(opt);
+ ASSERT_EQ(2, opt->getAddresses().size());
+ EXPECT_EQ("12.0.3.1", opt->getAddresses()[0].toText());
+ EXPECT_EQ("192.0.3.2", opt->getAddresses()[1].toText());
+ }
+}
+
+// Verifies that binary option data can be configured with either
+// "'strings'" or hex literals.
+TEST_F(ParseConfigTest, stringOrHexBinaryData) {
+ // Structure the defines a given test scenario
+ struct Scenario {
+ std::string description_; // describes the scenario for logging
+ std::string str_data_; // configured data value of the option
+ std::vector<uint8_t> exp_binary_; // expected parsed binary data
+ std::string exp_error_; // expected error test for invalid input
+ };
+
+ // Convenience value to use for initting valid scenarios
+ std::string no_error("");
+
+ // Valid and invalid scenarios we will test.
+ // Note we are not concerned with the varitions of valid or invalid
+ // hex literals those are tested elsewhere.
+ std::vector<Scenario> scenarios = {
+ {
+ "valid hex digits",
+ "0C:00:03:01:C0:00:03:02",
+ {0x0C,0x00,0x03,0x01,0xC0,0x00,0x03,0x02},
+ no_error
+ },
+ {
+ "valid string",
+ "'abcdefghijk'",
+ {0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B},
+ no_error
+ },
+ {
+ "valid empty",
+ "",
+ {},
+ no_error
+ },
+ {
+ "invalid empty",
+ "''",
+ {},
+ "Configuration parsing failed: option data is not a valid string"
+ " of hexadecimal digits: '' (<string>:7:13)"
+ },
+ {
+ "missing end quote",
+ "'abcdefghijk",
+ {},
+ "Configuration parsing failed: option data is not a valid string"
+ " of hexadecimal digits: 'abcdefghijk (<string>:7:13)"
+ },
+ {
+ "missing open quote",
+ "abcdefghijk'",
+ {},
+ "Configuration parsing failed: option data is not a valid string"
+ " of hexadecimal digits: abcdefghijk' (<string>:7:13)"
+ },
+ {
+ "no quotes",
+ "abcdefghijk",
+ {},
+ "Configuration parsing failed: option data is not a valid string"
+ " of hexadecimal digits: abcdefghijk (<string>:7:13)"
+ }
+ };
+
+ // Iterate over our test scenarios
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ // Build the configuration text.
+ ostringstream os;
+ os <<
+ "{ \n"
+ " \"option-data\": [ { \n"
+ " \"name\": \"user-class\", \n"
+ " \"code\": 77, \n"
+ " \"space\": \"dhcp4\", \n"
+ " \"csv-format\": false, \n"
+ " \"data\": \"" << scenario.str_data_ << "\" \n"
+ " } ] \n"
+ "} \n";
+
+ // Attempt to parse it.
+ reset_context(AF_INET);
+ int rcode = 0;
+ ASSERT_NO_THROW(rcode = parseConfiguration(os.str(), true));
+
+ if (!scenario.exp_error_.empty()) {
+ // We expected to fail, did we?
+ ASSERT_NE(0, rcode);
+ // Did we fail for the reason we think we should?
+ EXPECT_EQ(error_text_, scenario.exp_error_);
+ } else {
+ // We expected to succeed, did we?
+ ASSERT_EQ(0, rcode);
+ OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, 77);
+ ASSERT_TRUE(opt);
+ // Verify the parsed data is correct.
+ EXPECT_EQ(opt->getData(), scenario.exp_binary_);
+ }
+ }
+ }
+}
+
+
+/// The next set of tests check basic operation of the HooksLibrariesParser.
+//
+// Convenience function to set a configuration of zero or more hooks
+// libraries:
+//
+// lib1 - No parameters
+// lib2 - Empty parameters statement
+// lib3 - Valid parameters
+std::string
+setHooksLibrariesConfig(const char* lib1 = NULL, const char* lib2 = NULL,
+ const char* lib3 = NULL) {
+ const string lbrace("{");
+ const string rbrace("}");
+ const string quote("\"");
+ const string comma_space(", ");
+ const string library("\"library\": ");
+
+ string config = string("{ \"hooks-libraries\": [");
+ if (lib1 != NULL) {
+ // Library 1 has no parameters
+ config += lbrace;
+ config += library + quote + std::string(lib1) + quote;
+ config += rbrace;
+
+ if (lib2 != NULL) {
+ // Library 2 has an empty parameters statement
+ config += comma_space + lbrace;
+ config += library + quote + std::string(lib2) + quote + comma_space;
+ config += string("\"parameters\": {}");
+ config += rbrace;
+
+ if (lib3 != NULL) {
+ // Library 3 has valid parameters
+ config += comma_space + lbrace;
+ config += library + quote + std::string(lib3) + quote + comma_space;
+ config += string("\"parameters\": {");
+ config += string(" \"svalue\": \"string value\", ");
+ config += string(" \"ivalue\": 42, "); // Integer value
+ config += string(" \"bvalue\": true"); // Boolean value
+ config += string("}");
+ config += rbrace;
+ }
+ }
+ }
+ config += std::string("] }");
+
+ return (config);
+}
+
+// hooks-libraries element that does not contain anything.
+TEST_F(ParseConfigTest, noHooksLibraries) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Create an empty hooks-libraries configuration element.
+ const string config = setHooksLibrariesConfig();
+
+ // Verify that the configuration string parses.
+ const int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
+ // Check that the parser recorded nothing.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ EXPECT_TRUE(libraries.empty());
+
+ // Check that there are still no libraries loaded.
+ hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+}
+
+// hooks-libraries element that contains a single library.
+TEST_F(ParseConfigTest, oneHooksLibrary) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configuration with hooks-libraries set to a single library.
+ const string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1);
+
+ // Verify that the configuration string parses.
+ const int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
+ // Check that the parser recorded a single library.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ ASSERT_EQ(1, libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
+
+ // Check that the change was propagated to the hooks manager.
+ hooks_libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(1, hooks_libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
+}
+
+// hooks-libraries element that contains two libraries
+TEST_F(ParseConfigTest, twoHooksLibraries) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configuration with hooks-libraries set to two libraries.
+ const string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2);
+
+ // Verify that the configuration string parses.
+ const int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
+ // Check that the parser recorded two libraries in the expected order.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ ASSERT_EQ(2, libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
+ EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
+
+ // Verify that the change was propagated to the hooks manager.
+ hooks_libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(2, hooks_libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
+ EXPECT_EQ(CALLOUT_LIBRARY_2, hooks_libraries[1]);
+}
+
+// Configure with two libraries, then reconfigure with the same libraries.
+TEST_F(ParseConfigTest, reconfigureSameHooksLibraries) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configuration with hooks-libraries set to two libraries.
+ const std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2);
+
+ // Verify that the configuration string parses. The twoHooksLibraries
+ // test shows that the list will be as expected.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
+ // The previous test shows that the parser correctly recorded the two
+ // libraries and that they loaded correctly.
+
+ // Parse the string again.
+ rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // The list has not changed between the two parse operations. However,
+ // the parameters (or the files they could point to) could have
+ // changed, so the libraries are reloaded anyway.
+ const HooksConfig& cfg2 =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg2);
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ ASSERT_EQ(2, libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
+ EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
+
+ // ... and check that the same two libraries are still loaded in the
+ // HooksManager.
+ hooks_libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(2, hooks_libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
+ EXPECT_EQ(CALLOUT_LIBRARY_2, hooks_libraries[1]);
+}
+
+// Configure the hooks with two libraries, then reconfigure with the same
+// libraries, but in reverse order.
+TEST_F(ParseConfigTest, reconfigureReverseHooksLibraries) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configuration with hooks-libraries set to two libraries.
+ std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2);
+
+ // Verify that the configuration string parses. The twoHooksLibraries
+ // test shows that the list will be as expected.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // A previous test shows that the parser correctly recorded the two
+ // libraries and that they loaded correctly.
+
+ // Parse the reversed set of libraries.
+ config = setHooksLibrariesConfig(CALLOUT_LIBRARY_2, CALLOUT_LIBRARY_1);
+ rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // The list has changed, and this is what we should see.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ ASSERT_EQ(2, libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[0].first);
+ EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[1].first);
+
+ // ... and check that this was propagated to the HooksManager.
+ hooks_libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(2, hooks_libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_2, hooks_libraries[0]);
+ EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[1]);
+}
+
+// Configure the hooks with two libraries, then reconfigure with
+// no libraries.
+TEST_F(ParseConfigTest, reconfigureZeroHooksLibraries) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configuration with hooks-libraries set to two libraries.
+ std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2);
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // A previous test shows that the parser correctly recorded the two
+ // libraries and that they loaded correctly.
+
+ // Parse the string again, this time without any libraries.
+ config = setHooksLibrariesConfig();
+ rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
+ // The list has changed, and this is what we should see.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ EXPECT_TRUE(libraries.empty());
+
+ // Check that no libraries are currently loaded
+ hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+}
+
+// Check with a set of libraries, some of which are invalid.
+TEST_F(ParseConfigTest, invalidHooksLibraries) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configuration string. This contains an invalid library which should
+ // trigger an error in the "build" stage.
+ const std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ NOT_PRESENT_LIBRARY,
+ CALLOUT_LIBRARY_2);
+
+ // Verify that the configuration fails to parse. (Syntactically it's OK,
+ // but the library is invalid).
+ const int rcode = parseConfiguration(config);
+ ASSERT_FALSE(rcode == 0) << error_text_;
+
+ // Check that the message contains the library in error.
+ EXPECT_FALSE(error_text_.find(NOT_PRESENT_LIBRARY) == string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+
+ // Check that the parser recorded the names but, as they were in error,
+ // does not flag them as changed.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ ASSERT_EQ(3, libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
+ EXPECT_EQ(NOT_PRESENT_LIBRARY, libraries[1].first);
+ EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[2].first);
+
+ // ...and check it did not alter the libraries in the hooks manager.
+ hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+}
+
+// Check that trying to reconfigure with an invalid set of libraries fails.
+TEST_F(ParseConfigTest, reconfigureInvalidHooksLibraries) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configure with a single library.
+ std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1);
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // A previous test shows that the parser correctly recorded the two
+ // libraries and that they loaded correctly.
+
+ // Configuration string. This contains an invalid library which should
+ // trigger an error in the "build" stage.
+ config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1, NOT_PRESENT_LIBRARY,
+ CALLOUT_LIBRARY_2);
+
+ // Verify that the configuration fails to parse. (Syntactically it's OK,
+ // but the library is invalid).
+ rcode = parseConfiguration(config);
+ EXPECT_FALSE(rcode == 0) << error_text_;
+
+ // Check that the message contains the library in error.
+ EXPECT_FALSE(error_text_.find(NOT_PRESENT_LIBRARY) == string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+
+ // Check that the parser recorded the names but, as the library set was
+ // incorrect, did not mark the configuration as changed.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ ASSERT_EQ(3, libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
+ EXPECT_EQ(NOT_PRESENT_LIBRARY, libraries[1].first);
+ EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[2].first);
+
+ // ... but check that the hooks manager was not updated with the incorrect
+ // names.
+ hooks_libraries.clear();
+ hooks_libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(1, hooks_libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, hooks_libraries[0]);
+}
+
+// Check that if hooks-libraries contains invalid syntax, it is detected.
+TEST_F(ParseConfigTest, invalidSyntaxHooksLibraries) {
+
+ // Element holds a mixture of (valid) maps and non-maps.
+ string config1 = "{ \"hooks-libraries\": [ "
+ "{ \"library\": \"/opt/lib/lib1\" }, "
+ "\"/opt/lib/lib2\" "
+ "] }";
+ string error1 = "one or more entries in the hooks-libraries list is not"
+ " a map";
+
+ int rcode = parseConfiguration(config1);
+ ASSERT_NE(0, rcode);
+ EXPECT_TRUE(error_text_.find(error1) != string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+
+ // Element holds valid maps, except one where the library element is not
+ // a string.
+ string config2 = "{ \"hooks-libraries\": [ "
+ "{ \"library\": \"/opt/lib/lib1\" }, "
+ "{ \"library\": 123 } "
+ "] }";
+ string error2 = "value of 'library' element is not a string giving"
+ " the path to a hooks library";
+
+ rcode = parseConfiguration(config2);
+ ASSERT_NE(0, rcode);
+ EXPECT_TRUE(error_text_.find(error2) != string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+
+ // Element holds valid maps, except one where the library element is the
+ // empty string.
+ string config3 = "{ \"hooks-libraries\": [ "
+ "{ \"library\": \"/opt/lib/lib1\" }, "
+ "{ \"library\": \"\" } "
+ "] }";
+ string error3 = "value of 'library' element must not be blank";
+
+ rcode = parseConfiguration(config3);
+ ASSERT_NE(0, rcode);
+ EXPECT_TRUE(error_text_.find(error3) != string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+
+ // Element holds valid maps, except one where the library element is all
+ // spaces.
+ string config4 = "{ \"hooks-libraries\": [ "
+ "{ \"library\": \"/opt/lib/lib1\" }, "
+ "{ \"library\": \" \" } "
+ "] }";
+ string error4 = "value of 'library' element must not be blank";
+
+ rcode = parseConfiguration(config4);
+ ASSERT_NE(0, rcode);
+ EXPECT_TRUE(error_text_.find(error3) != string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+
+ // Element holds valid maps, except one that does not contain a
+ // 'library' element.
+ string config5 = "{ \"hooks-libraries\": [ "
+ "{ \"library\": \"/opt/lib/lib1\" }, "
+ "{ \"parameters\": { \"alpha\": 123 } }, "
+ "{ \"library\": \"/opt/lib/lib2\" } "
+ "] }";
+ string error5 = "one or more hooks-libraries elements are missing the"
+ " name of the library";
+
+ rcode = parseConfiguration(config5);
+ ASSERT_NE(0, rcode);
+ EXPECT_TRUE(error_text_.find(error5) != string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+}
+
+// Check that some parameters may have configuration parameters configured.
+TEST_F(ParseConfigTest, HooksLibrariesParameters) {
+ // Check that no libraries are currently loaded
+ vector<string> hooks_libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(hooks_libraries.empty());
+
+ // Configuration string. This contains an invalid library which should
+ // trigger an error in the "build" stage.
+ const std::string config = setHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2,
+ CALLOUT_PARAMS_LIBRARY);
+
+ // Verify that the configuration fails to parse. (Syntactically it's OK,
+ // but the library is invalid).
+ const int rcode = parseConfiguration(config);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected =
+ Element::fromJSON(config)->get("hooks-libraries"));
+ ASSERT_TRUE(expected);
+ const HooksConfig& cfg =
+ CfgMgr::instance().getStagingCfg()->getHooksConfig();
+ runToElementTest<HooksConfig>(expected, cfg);
+
+ // Check that the parser recorded the names.
+ isc::hooks::HookLibsCollection libraries = getLibraries();
+ ASSERT_EQ(3, libraries.size());
+ EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
+ EXPECT_EQ(CALLOUT_LIBRARY_2, libraries[1].first);
+ EXPECT_EQ(CALLOUT_PARAMS_LIBRARY, libraries[2].first);
+
+ // Also, check that the third library has its parameters specified.
+ // They were set by setHooksLibrariesConfig. The first has no
+ // parameters, the second one has an empty map and the third
+ // one has actual parameters.
+ EXPECT_FALSE(libraries[0].second);
+ EXPECT_TRUE(libraries[1].second);
+ ASSERT_TRUE(libraries[2].second);
+
+ // Ok, get the parameter for the third library.
+ ConstElementPtr params = libraries[2].second;
+
+ // It must be a map.
+ ASSERT_EQ(Element::map, params->getType());
+
+ // This map should have 3 parameters:
+ // - svalue (and will expect its value to be "string value")
+ // - ivalue (and will expect its value to be 42)
+ // - bvalue (and will expect its value to be true)
+ ConstElementPtr svalue = params->get("svalue");
+ ConstElementPtr ivalue = params->get("ivalue");
+ ConstElementPtr bvalue = params->get("bvalue");
+
+ // There should be no extra parameters.
+ EXPECT_FALSE(params->get("nonexistent"));
+
+ ASSERT_TRUE(svalue);
+ ASSERT_TRUE(ivalue);
+ ASSERT_TRUE(bvalue);
+
+ ASSERT_EQ(Element::string, svalue->getType());
+ ASSERT_EQ(Element::integer, ivalue->getType());
+ ASSERT_EQ(Element::boolean, bvalue->getType());
+
+ EXPECT_EQ("string value", svalue->stringValue());
+ EXPECT_EQ(42, ivalue->intValue());
+ EXPECT_EQ(true, bvalue->boolValue());
+}
+
+/// @brief Checks that a valid, enabled D2 client configuration works correctly.
+TEST_F(ParseConfigTest, validD2Config) {
+
+ // Configuration string containing valid values.
+ std::string config_str =
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 3432, "
+ " \"sender-ip\" : \"192.0.2.1\", "
+ " \"sender-port\" : 3433, "
+ " \"max-queue-size\" : 2048, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"user-context\": { \"foo\": \"bar\" } "
+ " }"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config_str);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that DHCP-DDNS is enabled and we can fetch the configuration.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+ D2ClientConfigPtr d2_client_config;
+ ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are as expected.
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("192.0.2.0", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(3432, d2_client_config->getServerPort());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+ ASSERT_TRUE(d2_client_config->getContext());
+ EXPECT_EQ("{ \"foo\": \"bar\" }", d2_client_config->getContext()->str());
+
+ // Verify that the configuration object unparses.
+ ConstElementPtr expected;
+ ASSERT_NO_THROW(expected = Element::fromJSON(config_str)->get("dhcp-ddns"));
+ ASSERT_TRUE(expected);
+ runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+
+ // Another valid Configuration string.
+ // This one is disabled, has IPV6 server ip, control flags false,
+ // empty prefix/suffix
+ std::string config_str2 =
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : false, "
+ " \"server-ip\" : \"2001:db8::\", "
+ " \"server-port\" : 43567, "
+ " \"sender-ip\" : \"2001:db8::1\", "
+ " \"sender-port\" : 3433, "
+ " \"max-queue-size\" : 2048, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\", "
+ " \"user-context\": { \"foo\": \"bar\" } "
+ " }"
+ "}";
+
+ // Verify that the configuration string parses.
+ rcode = parseConfiguration(config_str2);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that DHCP-DDNS is disabled and we can fetch the configuration.
+ EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
+ ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+ ASSERT_TRUE(d2_client_config);
+
+ // Verify that the configuration values are as expected.
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ("2001:db8::", d2_client_config->getServerIp().toText());
+ EXPECT_EQ(43567, d2_client_config->getServerPort());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
+ ASSERT_TRUE(d2_client_config->getContext());
+ EXPECT_EQ("{ \"foo\": \"bar\" }", d2_client_config->getContext()->str());
+
+ ASSERT_NO_THROW(expected = Element::fromJSON(config_str2)->get("dhcp-ddns"));
+ ASSERT_TRUE(expected);
+ runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+}
+
+/// @brief Checks that D2 client can be configured with enable flag of
+/// false only.
+TEST_F(ParseConfigTest, validDisabledD2Config) {
+
+ // Configuration string. This defines a disabled D2 client config.
+ std::string config_str =
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : false"
+ " }"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config_str);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that DHCP-DDNS is disabled.
+ EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+ // Make sure fetched config agrees.
+ D2ClientConfigPtr d2_client_config;
+ ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+ EXPECT_TRUE(d2_client_config);
+ EXPECT_FALSE(d2_client_config->getEnableUpdates());
+}
+
+/// @brief Checks that given a partial configuration, parser supplies
+/// default values
+TEST_F(ParseConfigTest, parserDefaultsD2Config) {
+
+ // Configuration string. This defines an enabled D2 client config
+ // with the mandatory parameter in such a case, all other parameters
+ // are optional and their default values will be used.
+ std::string config_str =
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true "
+ " }"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config_str);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Verify that DHCP-DDNS is enabled.
+ EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+ // Make sure fetched config is correct.
+ D2ClientConfigPtr d2_client_config;
+ ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
+ EXPECT_TRUE(d2_client_config);
+ EXPECT_TRUE(d2_client_config->getEnableUpdates());
+ EXPECT_EQ(D2ClientConfig::DFT_SERVER_IP,
+ d2_client_config->getServerIp().toText());
+ EXPECT_EQ(D2ClientConfig::DFT_SERVER_PORT,
+ d2_client_config->getServerPort());
+ EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(D2ClientConfig::DFT_NCR_PROTOCOL),
+ d2_client_config->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::stringToNcrFormat(D2ClientConfig::DFT_NCR_FORMAT),
+ d2_client_config->getNcrFormat());
+}
+
+
+/// @brief Check various invalid D2 client configurations.
+TEST_F(ParseConfigTest, invalidD2Config) {
+ std::string invalid_configs[] = {
+ // Invalid server ip value
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"x192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\" "
+ " }"
+ "}",
+ // Unknown protocol
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"Bogus\", "
+ " \"ncr-format\" : \"JSON\" "
+ " }"
+ "}",
+ // Unsupported protocol
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"TCP\", "
+ " \"ncr-format\" : \"JSON\" "
+ " }"
+ "}",
+ // Unknown format
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 53001, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"Bogus\" "
+ " }"
+ "}",
+ // Invalid Port
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : \"bogus\", "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\" "
+ " }"
+ "}",
+ // Mismatched server and sender IPs
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"192.0.2.0\", "
+ " \"server-port\" : 3432, "
+ " \"sender-ip\" : \"3001::5\", "
+ " \"sender-port\" : 3433, "
+ " \"max-queue-size\" : 2048, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\" "
+ " }"
+ "}",
+ // Identical server and sender IP/port
+ "{ \"dhcp-ddns\" :"
+ " {"
+ " \"enable-updates\" : true, "
+ " \"server-ip\" : \"3001::5\", "
+ " \"server-port\" : 3433, "
+ " \"sender-ip\" : \"3001::5\", "
+ " \"sender-port\" : 3433, "
+ " \"max-queue-size\" : 2048, "
+ " \"ncr-protocol\" : \"UDP\", "
+ " \"ncr-format\" : \"JSON\" "
+ " }"
+ "}",
+ // stop
+ ""
+ };
+
+ // Fetch the original config.
+ D2ClientConfigPtr original_config;
+ ASSERT_NO_THROW(original_config = CfgMgr::instance().getD2ClientConfig());
+
+ // Iterate through the invalid configuration strings, attempting to
+ // parse each one. They should fail to parse, but fail gracefully.
+ D2ClientConfigPtr current_config;
+ int i = 0;
+ while (!invalid_configs[i].empty()) {
+ // Verify that the configuration string parses without throwing.
+ int rcode = parseConfiguration(invalid_configs[i]);
+
+ // Verify that parse result indicates a parsing error.
+ ASSERT_TRUE(rcode != 0) << "Invalid config #: " << i
+ << " should not have passed!";
+
+ // Verify that the "official" config still matches the original config.
+ ASSERT_NO_THROW(current_config =
+ CfgMgr::instance().getD2ClientConfig());
+ EXPECT_EQ(*original_config, *current_config);
+ ++i;
+ }
+}
+
+/// @brief Checks that a valid relay info structure for IPv4 can be handled
+TEST_F(ParseConfigTest, validRelayInfo4) {
+
+ // Relay information structure. Very simple for now.
+ std::string config_str =
+ " {"
+ " \"ip-address\" : \"192.0.2.1\""
+ " }";
+ ElementPtr json = Element::fromJSON(config_str);
+
+ // Create an "empty" RelayInfo to hold the parsed result.
+ Network::RelayInfoPtr result(new Network::RelayInfo());
+
+ RelayInfoParser parser(Option::V4);
+
+ EXPECT_NO_THROW(parser.parse(result, json));
+ EXPECT_TRUE(result->containsAddress(IOAddress("192.0.2.1")));
+}
+
+/// @brief Checks that a bogus relay info structure for IPv4 is rejected.
+TEST_F(ParseConfigTest, bogusRelayInfo4) {
+
+ // Invalid config (wrong family type of the ip-address field)
+ std::string config_str_bogus1 =
+ " {"
+ " \"ip-address\" : \"2001:db8::1\""
+ " }";
+ ElementPtr json_bogus1 = Element::fromJSON(config_str_bogus1);
+
+ // Invalid config (that thing is not an IPv4 address)
+ std::string config_str_bogus2 =
+ " {"
+ " \"ip-address\" : \"256.345.123.456\""
+ " }";
+ ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
+
+ // Invalid config (ip-address is mandatory)
+ std::string config_str_bogus3 =
+ " {"
+ " }";
+ ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
+
+ // Create an "empty" RelayInfo to hold the parsed result.
+ Network::RelayInfoPtr result(new Network::RelayInfo());
+
+ RelayInfoParser parser(Option::V4);
+
+ // wrong family type
+ EXPECT_THROW(parser.parse(result, json_bogus1), DhcpConfigError);
+
+ // Too large byte values in pseudo-IPv4 addr
+ EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
+
+ // Mandatory ip-address is missing. What a pity.
+ EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
+}
+
+/// @brief Checks that a valid relay info structure for IPv6 can be handled
+TEST_F(ParseConfigTest, validRelayInfo6) {
+
+ // Relay information structure. Very simple for now.
+ std::string config_str =
+ " {"
+ " \"ip-address\" : \"2001:db8::1\""
+ " }";
+ ElementPtr json = Element::fromJSON(config_str);
+
+ // Create an "empty" RelayInfo to hold the parsed result.
+ Network::RelayInfoPtr result(new Network::RelayInfo());
+
+ RelayInfoParser parser(Option::V6);
+
+ EXPECT_NO_THROW(parser.parse(result, json));
+ EXPECT_TRUE(result->containsAddress(IOAddress("2001:db8::1")));
+}
+
+/// @brief Checks that a valid relay info structure for IPv6 can be handled
+TEST_F(ParseConfigTest, bogusRelayInfo6) {
+
+ // Invalid config (wrong family type of the ip-address field
+ std::string config_str_bogus1 =
+ " {"
+ " \"ip-address\" : \"192.0.2.1\""
+ " }";
+ ElementPtr json_bogus1 = Element::fromJSON(config_str_bogus1);
+
+ // That IPv6 address doesn't look right
+ std::string config_str_bogus2 =
+ " {"
+ " \"ip-address\" : \"2001:db8:::4\""
+ " }";
+ ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
+
+ // Missing mandatory ip-address field.
+ std::string config_str_bogus3 =
+ " {"
+ " }";
+ ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
+
+ // Create an "empty" RelayInfo to hold the parsed result.
+ Network::RelayInfoPtr result(new Network::RelayInfo());
+
+ RelayInfoParser parser(Option::V6);
+
+ // Negative scenario (wrong family type)
+ EXPECT_THROW(parser.parse(result, json_bogus1), DhcpConfigError);
+
+ // Looks like IPv6 address, but has too many colons
+ EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
+
+ // Mandatory ip-address is missing. What a pity.
+ EXPECT_THROW(parser.parse(result, json_bogus3), DhcpConfigError);
+}
+
+// This test verifies that it is possible to parse an IPv4 subnet for which
+// only mandatory parameters are specified without setting the defaults.
+TEST_F(ParseConfigTest, defaultSubnet4) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 123"
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false, false);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getBySubnetId(123);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_TRUE(subnet->hasFetchGlobalsFn());
+
+ EXPECT_TRUE(subnet->getIface().unspecified());
+ EXPECT_TRUE(subnet->getIface().empty());
+
+ EXPECT_TRUE(subnet->getClientClass().unspecified());
+ EXPECT_TRUE(subnet->getClientClass().empty());
+
+ EXPECT_TRUE(subnet->getValid().unspecified());
+ EXPECT_EQ(0, subnet->getValid().get());
+
+ EXPECT_TRUE(subnet->getT1().unspecified());
+ EXPECT_EQ(0, subnet->getT1().get());
+
+ EXPECT_TRUE(subnet->getT2().unspecified());
+ EXPECT_EQ(0, subnet->getT2().get());
+
+ EXPECT_TRUE(subnet->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(subnet->getReservationsGlobal().get());
+
+ EXPECT_TRUE(subnet->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(subnet->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(subnet->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(subnet->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(subnet->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(subnet->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, subnet->getT1Percent().get());
+
+ EXPECT_TRUE(subnet->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, subnet->getT2Percent().get());
+
+ EXPECT_TRUE(subnet->getMatchClientId().unspecified());
+ EXPECT_TRUE(subnet->getMatchClientId().get());
+
+ EXPECT_TRUE(subnet->getAuthoritative().unspecified());
+ EXPECT_FALSE(subnet->getAuthoritative().get());
+
+ EXPECT_TRUE(subnet->getSiaddr().unspecified());
+ EXPECT_TRUE(subnet->getSiaddr().get().isV4Zero());
+
+ EXPECT_TRUE(subnet->getSname().unspecified());
+ EXPECT_TRUE(subnet->getSname().empty());
+
+ EXPECT_TRUE(subnet->getFilename().unspecified());
+ EXPECT_TRUE(subnet->getFilename().empty());
+
+ EXPECT_FALSE(subnet->get4o6().enabled());
+
+ EXPECT_TRUE(subnet->get4o6().getIface4o6().unspecified());
+ EXPECT_TRUE(subnet->get4o6().getIface4o6().empty());
+
+ EXPECT_TRUE(subnet->get4o6().getSubnet4o6().unspecified());
+ EXPECT_TRUE(subnet->get4o6().getSubnet4o6().get().first.isV6Zero());
+ EXPECT_EQ(128, subnet->get4o6().getSubnet4o6().get().second);
+
+ EXPECT_TRUE(subnet->getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(subnet->getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(subnet->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(subnet->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(subnet->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(subnet->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(subnet->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, subnet->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(subnet->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(subnet->getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(subnet->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(subnet->getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(subnet->getHostnameCharSet().unspecified());
+ EXPECT_TRUE(subnet->getHostnameCharSet().empty());
+
+ EXPECT_TRUE(subnet->getHostnameCharReplacement().unspecified());
+ EXPECT_TRUE(subnet->getHostnameCharReplacement().empty());
+
+ EXPECT_TRUE(subnet->getStoreExtendedInfo().unspecified());
+ EXPECT_FALSE(subnet->getStoreExtendedInfo().get());
+
+ EXPECT_TRUE(subnet->getDdnsUpdateOnRenew().unspecified());
+ EXPECT_FALSE(subnet->getDdnsUpdateOnRenew().get());
+
+ EXPECT_TRUE(subnet->getDdnsUseConflictResolution().unspecified());
+ EXPECT_FALSE(subnet->getDdnsUseConflictResolution().get());
+
+ EXPECT_TRUE(subnet->getAllocationState(Lease::TYPE_V4));
+ EXPECT_TRUE(subnet->getAllocator(Lease::TYPE_V4));
+
+ auto allocator = subnet->getAllocator(Lease::TYPE_V4);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+
+ EXPECT_TRUE(subnet->getOfferLft().unspecified());
+ EXPECT_EQ(0, subnet->getOfferLft().get());
+}
+
+// This test verifies that it is possible to parse an IPv6 subnet for which
+// only mandatory parameters are specified without setting the defaults.
+TEST_F(ParseConfigTest, defaultSubnet6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 123"
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, true, false);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getBySubnetId(123);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_TRUE(subnet->hasFetchGlobalsFn());
+
+ EXPECT_TRUE(subnet->getIface().unspecified());
+ EXPECT_TRUE(subnet->getIface().empty());
+
+ EXPECT_TRUE(subnet->getClientClass().unspecified());
+ EXPECT_TRUE(subnet->getClientClass().empty());
+
+ EXPECT_TRUE(subnet->getValid().unspecified());
+ EXPECT_EQ(0, subnet->getValid().get());
+
+ EXPECT_TRUE(subnet->getT1().unspecified());
+ EXPECT_EQ(0, subnet->getT1().get());
+
+ EXPECT_TRUE(subnet->getT2().unspecified());
+ EXPECT_EQ(0, subnet->getT2().get());
+
+ EXPECT_TRUE(subnet->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(subnet->getReservationsGlobal().get());
+
+ EXPECT_TRUE(subnet->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(subnet->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(subnet->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(subnet->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(subnet->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(subnet->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(subnet->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, subnet->getT1Percent().get());
+
+ EXPECT_TRUE(subnet->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, subnet->getT2Percent().get());
+
+ EXPECT_TRUE(subnet->getPreferred().unspecified());
+ EXPECT_EQ(0, subnet->getPreferred().get());
+
+ EXPECT_TRUE(subnet->getRapidCommit().unspecified());
+ EXPECT_FALSE(subnet->getRapidCommit().get());
+
+ EXPECT_TRUE(subnet->getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(subnet->getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(subnet->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(subnet->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(subnet->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(subnet->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(subnet->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, subnet->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(subnet->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(subnet->getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(subnet->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(subnet->getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(subnet->getHostnameCharSet().unspecified());
+ EXPECT_TRUE(subnet->getHostnameCharSet().empty());
+
+ EXPECT_TRUE(subnet->getHostnameCharReplacement().unspecified());
+ EXPECT_TRUE(subnet->getHostnameCharReplacement().empty());
+
+ EXPECT_TRUE(subnet->getStoreExtendedInfo().unspecified());
+ EXPECT_FALSE(subnet->getStoreExtendedInfo().get());
+
+ EXPECT_TRUE(subnet->getDdnsUpdateOnRenew().unspecified());
+ EXPECT_FALSE(subnet->getDdnsUpdateOnRenew().get());
+
+ EXPECT_TRUE(subnet->getDdnsUseConflictResolution().unspecified());
+ EXPECT_FALSE(subnet->getDdnsUseConflictResolution().get());
+
+ EXPECT_TRUE(subnet->getAllocationState(Lease::TYPE_NA));
+ EXPECT_TRUE(subnet->getAllocationState(Lease::TYPE_TA));
+ EXPECT_TRUE(subnet->getAllocationState(Lease::TYPE_PD));
+
+ auto allocator = subnet->getAllocator(Lease::TYPE_NA);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+
+ allocator = subnet->getAllocator(Lease::TYPE_TA);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+
+ allocator = subnet->getAllocator(Lease::TYPE_PD);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+}
+
+// This test verifies that it is possible to parse an IPv4 shared network
+// for which only mandatory parameter is specified without setting the
+// defaults.
+TEST_F(ParseConfigTest, defaultSharedNetwork4) {
+ std::string config =
+ "{"
+ " \"shared-networks\": [ {"
+ " \"name\": \"frog\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false, false);
+ ASSERT_EQ(0, rcode);
+
+ auto network =
+ CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks4()->getByName("frog");
+ ASSERT_TRUE(network);
+
+ EXPECT_TRUE(network->hasFetchGlobalsFn());
+ EXPECT_TRUE(network->getIface().unspecified());
+ EXPECT_TRUE(network->getIface().empty());
+
+ EXPECT_TRUE(network->getClientClass().unspecified());
+ EXPECT_TRUE(network->getClientClass().empty());
+
+ EXPECT_TRUE(network->getValid().unspecified());
+ EXPECT_EQ(0, network->getValid().get());
+
+ EXPECT_TRUE(network->getT1().unspecified());
+ EXPECT_EQ(0, network->getT1().get());
+
+ EXPECT_TRUE(network->getT2().unspecified());
+ EXPECT_EQ(0, network->getT2().get());
+
+ EXPECT_TRUE(network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT1Percent().get());
+
+ EXPECT_TRUE(network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT2Percent().get());
+
+ EXPECT_TRUE(network->getMatchClientId().unspecified());
+ EXPECT_TRUE(network->getMatchClientId().get());
+
+ EXPECT_TRUE(network->getAuthoritative().unspecified());
+ EXPECT_FALSE(network->getAuthoritative().get());
+
+ EXPECT_TRUE(network->getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(network->getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(network->getStoreExtendedInfo().unspecified());
+ EXPECT_FALSE(network->getStoreExtendedInfo().get());
+
+ EXPECT_TRUE(network->getDdnsUpdateOnRenew().unspecified());
+ EXPECT_FALSE(network->getDdnsUpdateOnRenew().get());
+
+ EXPECT_TRUE(network->getDdnsUseConflictResolution().unspecified());
+ EXPECT_FALSE(network->getDdnsUseConflictResolution().get());
+
+ EXPECT_TRUE(network->getAllocatorType().unspecified());
+ EXPECT_TRUE(network->getAllocatorType().get().empty());
+
+ EXPECT_TRUE(network->getOfferLft().unspecified());
+ EXPECT_EQ(0, network->getOfferLft().get());
+}
+
+// This test verifies that it is possible to parse an IPv6 shared network
+// for which only mandatory parameter is specified without setting the
+// defaults.
+TEST_F(ParseConfigTest, defaultSharedNetwork6) {
+ std::string config =
+ "{"
+ " \"shared-networks\": [ {"
+ " \"name\": \"frog\""
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, true, false);
+ ASSERT_EQ(0, rcode);
+
+ auto network =
+ CfgMgr::instance().getStagingCfg()->getCfgSharedNetworks6()->getByName("frog");
+ ASSERT_TRUE(network);
+
+ EXPECT_TRUE(network->hasFetchGlobalsFn());
+
+ EXPECT_TRUE(network->getIface().unspecified());
+ EXPECT_TRUE(network->getIface().empty());
+
+ EXPECT_TRUE(network->getClientClass().unspecified());
+ EXPECT_TRUE(network->getClientClass().empty());
+
+ EXPECT_TRUE(network->getValid().unspecified());
+ EXPECT_EQ(0, network->getValid().get());
+
+ EXPECT_TRUE(network->getT1().unspecified());
+ EXPECT_EQ(0, network->getT1().get());
+
+ EXPECT_TRUE(network->getT2().unspecified());
+ EXPECT_EQ(0, network->getT2().get());
+
+ EXPECT_TRUE(network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT1Percent().get());
+
+ EXPECT_TRUE(network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT2Percent().get());
+
+ EXPECT_TRUE(network->getPreferred().unspecified());
+ EXPECT_EQ(0, network->getPreferred().get());
+
+ EXPECT_TRUE(network->getRapidCommit().unspecified());
+ EXPECT_FALSE(network->getRapidCommit().get());
+
+ EXPECT_TRUE(network->getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(network->getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(network->getStoreExtendedInfo().unspecified());
+ EXPECT_FALSE(network->getStoreExtendedInfo().get());
+
+ EXPECT_TRUE(network->getDdnsUpdateOnRenew().unspecified());
+ EXPECT_FALSE(network->getDdnsUpdateOnRenew().get());
+
+ EXPECT_TRUE(network->getDdnsUseConflictResolution().unspecified());
+ EXPECT_FALSE(network->getDdnsUseConflictResolution().get());
+
+ EXPECT_TRUE(network->getAllocatorType().unspecified());
+ EXPECT_TRUE(network->getAllocatorType().get().empty());
+
+ EXPECT_TRUE(network->getPdAllocatorType().unspecified());
+ EXPECT_TRUE(network->getPdAllocatorType().get().empty());
+}
+
+// This test verifies a negative value for the subnet ID is rejected (v4).
+TEST_F(ParseConfigTest, negativeSubnetId4) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": -1"
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "subnet configuration failed: ";
+ expected += "The 'id' value (-1) is not within expected range: ";
+ expected += "(0 - 4294967294)";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// This test verifies a negative value for the subnet ID is rejected (v6).
+TEST_F(ParseConfigTest, negativeSubnetId6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": -1"
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, true);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "subnet configuration failed: ";
+ expected += "The 'id' value (-1) is not within expected range: ";
+ expected += "(0 - 4294967294)";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// This test verifies a too high value for the subnet ID is rejected (v4).
+TEST_F(ParseConfigTest, reservedSubnetId4) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 4294967295"
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "subnet configuration failed: ";
+ expected += "The 'id' value (4294967295) is not within expected range: ";
+ expected += "(0 - 4294967294)";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// This test verifies a too high value for the subnet ID is rejected (v6).
+TEST_F(ParseConfigTest, reservedSubnetId6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 4294967295"
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, true);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "subnet configuration failed: ";
+ expected += "The 'id' value (4294967295) is not within expected range: ";
+ expected += "(0 - 4294967294)";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// This test verifies that random allocator can be selected for
+// a subnet.
+TEST_F(ParseConfigTest, randomSubnetAllocator4) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 1,"
+ " \"allocator\": \"random\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getBySubnetId(1);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_EQ("random", subnet->getAllocatorType().get());
+ auto allocator = subnet->getAllocator(Lease::TYPE_V4);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>(allocator));
+}
+
+// This test verifies that Free Lease Queue allocator can be selected for
+// a subnet.
+TEST_F(ParseConfigTest, flqSubnetAllocator4) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 1,"
+ " \"allocator\": \"flq\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getBySubnetId(1);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_EQ("flq", subnet->getAllocatorType().get());
+ auto allocator = subnet->getAllocator(Lease::TYPE_V4);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<FreeLeaseQueueAllocator>(allocator));
+}
+
+// This test verifies that unknown allocator is rejected.
+TEST_F(ParseConfigTest, invalidSubnetAllocator4) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 1,"
+ " \"allocator\": \"unsupported\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "supported allocators are: iterative, random and flq";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// This test verifies that random allocator can be selected for
+// a subnet.
+TEST_F(ParseConfigTest, randomSubnetAllocator6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"allocator\": \"random\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getBySubnetId(1);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_EQ("random", subnet->getAllocatorType().get());
+ auto allocator = subnet->getAllocator(Lease::TYPE_NA);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>(allocator));
+ allocator = subnet->getAllocator(Lease::TYPE_TA);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>(allocator));
+ // PD allocator should be iterative.
+ allocator = subnet->getAllocator(Lease::TYPE_PD);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+}
+
+// This test verifies that FLQ allocator is not supported for
+// IPv6 address pools.
+TEST_F(ParseConfigTest, flqSubnetAllocator6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"allocator\": \"flq\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "Free Lease Queue allocator is not supported for IPv6 address pools";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// This test verifies that unknown allocator is rejected.
+TEST_F(ParseConfigTest, invalidSubnetAllocator6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"allocator\": \"unsupported\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "supported allocators are: iterative, random and flq";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// This test verifies that random allocator can be selected for
+// a subnet.
+TEST_F(ParseConfigTest, randomSubnetPdAllocator6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pd-allocator\": \"random\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getBySubnetId(1);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_EQ("random", subnet->getPdAllocatorType().get());
+
+ // Address allocators should be iterative.
+ auto allocator = subnet->getAllocator(Lease::TYPE_NA);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+ allocator = subnet->getAllocator(Lease::TYPE_TA);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+ // PD allocator should be random.
+ allocator = subnet->getAllocator(Lease::TYPE_PD);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>(allocator));
+}
+
+// This test verifies that the FLQ allocator can be selected for
+// a v6 subnet's pd-allocator.
+TEST_F(ParseConfigTest, flqSubnetPdAllocator6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pd-allocator\": \"flq\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getBySubnetId(1);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_EQ("flq", subnet->getPdAllocatorType().get());
+
+ // Address allocators should be iterative.
+ auto allocator = subnet->getAllocator(Lease::TYPE_NA);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+ allocator = subnet->getAllocator(Lease::TYPE_TA);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>(allocator));
+ // PD allocator should use FLQ.
+ allocator = subnet->getAllocator(Lease::TYPE_PD);
+ ASSERT_TRUE(allocator);
+ EXPECT_TRUE(boost::dynamic_pointer_cast<FreeLeaseQueueAllocator>(allocator));
+}
+
+// This test verifies that unknown prefix delegation allocator is rejected.
+TEST_F(ParseConfigTest, invalidSubnetPdAllocator6) {
+ std::string config =
+ "{"
+ " \"subnet6\": [ {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pd-allocator\": \"unsupported\""
+ " } ]"
+ "}";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ ConstElementPtr status = parseElementSet(json, false);
+ int rcode = 0;
+ ConstElementPtr comment = parseAnswer(rcode, status);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(comment->getType(), Element::string);
+ EXPECT_EQ(1, rcode);
+ std::string expected = "Configuration parsing failed: ";
+ expected += "supported allocators are: iterative, random and flq";
+ EXPECT_EQ(expected, comment->stringValue());
+}
+
+// There's no test for ControlSocketParser, as it is tested in the DHCPv4 code
+// (see CtrlDhcpv4SrvTest.commandSocketBasic in
+// src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc).
+
+
+// Verifies that parsing an option which encapsulates its own option space
+// is detected.
+TEST_F(ParseConfigTest, selfEncapsulationTest) {
+ // Verify that the option definition can be retrieved.
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, 45);
+ ASSERT_TRUE(def);
+
+ // Configuration string.
+ std::string config =
+ "{"
+ " \"option-data\": ["
+ "{"
+ " \"name\": \"client-data\","
+ " \"code\": 45,"
+ " \"csv-format\": false,"
+ " \"space\": \"dhcp6\","
+ " \"data\": \"0001000B0102020202030303030303\""
+ "}"
+ "]}";
+
+ // Verify that the configuration string parses.
+ family_ = AF_INET6;
+
+ int rcode = parseConfiguration(config, true, true);
+ ASSERT_EQ(0, rcode);
+
+ // Verify that the option can be retrieved.
+ OptionCustomPtr opt = boost::dynamic_pointer_cast<OptionCustom>
+ (getOptionPtr(DHCP6_OPTION_SPACE, D6O_CLIENT_DATA));
+ ASSERT_TRUE(opt);
+
+ // Verify length is correct and doesn't infinitely recurse.
+ EXPECT_EQ(19, opt->len());
+
+ // Check if it can be unparsed.
+ CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+ cfg.runCfgOptionsTest(family_, config);
+}
+
+// This test verifies parsing offer-lifetime for Subnet4.
+TEST_F(ParseConfigTest, subnet4OfferLft) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 123,"
+ " \"offer-lifetime\": 888"
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false, false);
+ ASSERT_EQ(0, rcode);
+
+ auto subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getBySubnetId(123);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_FALSE(subnet->getOfferLft().unspecified());
+ EXPECT_EQ(888, subnet->getOfferLft().get());
+}
+
+// This test verifies parsing invalid offer-lifetime for Subnet4.
+TEST_F(ParseConfigTest, subnet4InvalidOfferLft) {
+ std::string config =
+ "{"
+ " \"subnet4\": [ {"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"id\": 123,"
+ " \"offer-lifetime\": -77"
+ " } ]"
+ "}";
+
+ int rcode = parseConfiguration(config, false, false);
+ ASSERT_NE(0, rcode);
+}
+
+
+} // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc
new file mode 100644
index 0000000..4f617f6
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc
@@ -0,0 +1,211 @@
+// Copyright (C) 2018-2023 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 <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/dhcp_queue_control_parser.h>
+#include <testutils/multi_threading_utils.h>
+#include <testutils/test_to_element.h>
+#include <util/multi_threading_mgr.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for @c DHCPQueueControlParser
+class DHCPQueueControlParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ DHCPQueueControlParserTest() = default;
+
+ /// @brief Destructor
+ virtual ~DHCPQueueControlParserTest() = default;
+
+protected:
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void TearDown();
+};
+
+void
+DHCPQueueControlParserTest::SetUp() {
+ CfgMgr::instance().clear();
+}
+
+void
+DHCPQueueControlParserTest::TearDown() {
+ CfgMgr::instance().clear();
+}
+
+// Verifies that DHCPQueueControlParser handles
+// expected valid dhcp-queue-control content
+TEST_F(DHCPQueueControlParserTest, validContent) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "queue disabled",
+ "{ \n"
+ " \"enable-queue\": false \n"
+ "} \n"
+ },
+ {
+ "queue disabled, arbitrary content allowed",
+ "{ \n"
+ " \"enable-queue\": false, \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n"
+ },
+ {
+ "queue enabled, with queue-type",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\" \n"
+ "} \n"
+ },
+ {
+ "queue enabled with queue-type and arbitrary content",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\", \n"
+ " \"foo\": \"bogus\", \n"
+ " \"random-int\" : 1234 \n"
+ "} \n"
+ }
+ };
+
+ // Iterate over the valid scenarios and verify they succeed.
+ ConstElementPtr config_elems;
+ ConstElementPtr queue_control;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ // Construct the config JSON
+ ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
+ << "invalid JSON, test is broken";
+
+ // Parsing config into a queue control should succeed.
+ DHCPQueueControlParser parser;
+ try {
+ queue_control = parser.parse(config_elems, false);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "parser threw an exception: " << ex.what();
+ }
+
+ // Verify the resultant queue control.
+ ASSERT_TRUE(queue_control);
+
+ // The parser should have created a duplicate of the
+ // configuration elements.
+ ASSERT_TRUE(queue_control.get() != config_elems.get());
+ EXPECT_TRUE(queue_control->equals(*config_elems));
+ }
+ }
+}
+
+// Verifies that DHCPQueueControlParser correctly catches
+// invalid dhcp-queue-control content
+TEST_F(DHCPQueueControlParserTest, invalidContent) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "enable-queue missing",
+ "{ \n"
+ " \"enable-type\": \"some-type\" \n"
+ "} \n"
+ },
+ {
+ "enable-queue not boolean",
+ "{ \n"
+ " \"enable-queue\": \"always\" \n"
+ "} \n"
+ },
+ {
+ "queue enabled, type missing",
+ "{ \n"
+ " \"enable-queue\": true \n"
+ "} \n"
+ },
+ {
+ "queue enabled, type not a string",
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": 7777 \n"
+ "} \n"
+ }
+ };
+
+ // Iterate over the valid scenarios and verify they succeed.
+ ConstElementPtr config_elems;
+ ConstElementPtr queue_control;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ // Construct the config JSON
+ ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
+ << "invalid JSON, test is broken";
+
+ // Parsing config into a queue control should succeed.
+ DHCPQueueControlParser parser;
+ EXPECT_THROW(parser.parse(config_elems, false), DhcpConfigError);
+ }
+ }
+}
+
+// Verifies that DHCPQueueControlParser disables the queue when multi-threading
+// is enabled
+TEST_F(DHCPQueueControlParserTest, multiThreading) {
+ // Enable config with some queue type.
+ std::string config =
+ "{ \n"
+ " \"enable-queue\": true, \n"
+ " \"queue-type\": \"some-type\" \n"
+ "} \n";
+
+ // Construct the config JSON.
+ ConstElementPtr config_elems;
+ ASSERT_NO_THROW(config_elems = Element::fromJSON(config))
+ << "invalid JSON, test is broken";
+
+ // Parse config.
+ DHCPQueueControlParser parser;
+ ConstElementPtr queue_control;
+ ASSERT_NO_THROW(queue_control = parser.parse(config_elems, false))
+ << "parse fails, test is broken";
+
+ // Verify that queue is enabled.
+ ASSERT_TRUE(queue_control);
+ ASSERT_TRUE(queue_control->get("enable-queue"));
+ EXPECT_EQ("true", queue_control->get("enable-queue")->str());
+
+ // Retry with multi-threading.
+ ASSERT_NO_THROW(queue_control = parser.parse(config_elems, true));
+ ASSERT_TRUE(queue_control);
+ ASSERT_TRUE(queue_control->get("enable-queue"));
+ EXPECT_EQ("false", queue_control->get("enable-queue")->str());
+}
+
+}; // anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc
new file mode 100644
index 0000000..be7e96b
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc
@@ -0,0 +1,224 @@
+// Copyright (C) 2015-2017 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 <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_duid.h>
+#include <dhcpsrv/parsers/duid_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <testutils/test_to_element.h>
+#include <util/encode/hex.h>
+#include <gtest/gtest.h>
+#include <limits>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for @c DUIDConfigParser
+class DUIDConfigParserTest : public ::testing::Test {
+public:
+
+ /// @brief constructor
+ ///
+ /// Initializes cfg_duid_ to a new empty object
+ DUIDConfigParserTest()
+ :cfg_duid_(new CfgDUID()){
+ }
+
+ /// @brief Creates simple configuration with DUID type only.
+ ///
+ /// @param duid_type DUID type in the textual format.
+ std::string createConfigWithType(const std::string& duid_type) const;
+
+ /// @brief Creates simple configuration with DUID type and one
+ /// numeric parameter.
+ ///
+ /// @param name Parameter name.
+ /// @param value Parameter value.
+ std::string createConfigWithInteger(const std::string& name,
+ const int64_t value) const;
+
+ /// @brief Parse configuration.
+ ///
+ /// @param config String representing DUID configuration.
+ void build(const std::string& config) const;
+
+ /// @brief Test that only a DUID type can be specified.
+ ///
+ /// @param duid_type DUID type in numeric format.
+ /// @param duid_type_text DUID type in textual format.
+ void testTypeOnly(const DUID::DUIDType& duid_type,
+ const std::string& duid_type_text) const;
+
+ /// @brief Test that invalid configuration is rejected.
+ ///
+ /// @param config Holds JSON configuration to be used.
+ void testInvalidConfig(const std::string& config) const;
+
+ /// @brief Test out of range numeric values.
+ ///
+ /// @param param_name Parameter name.
+ /// @tparam Type of the numeric parameter.
+ template<typename NumericType>
+ void testOutOfRange(const std::string& param_name) {
+ // Obtain maximum value for the specified numeric type.
+ const uint64_t max_value = std::numeric_limits<NumericType>::max();
+
+ // Negative values are not allowed.
+ EXPECT_THROW(build(createConfigWithInteger(param_name, -1)),
+ DhcpConfigError);
+ // Zero is allowed.
+ EXPECT_NO_THROW(build(createConfigWithInteger(param_name, 0)));
+ // Maximum value.
+ EXPECT_NO_THROW(build(createConfigWithInteger(param_name, max_value)));
+ // Value greater than maximum should result in exception.
+ EXPECT_THROW(build(createConfigWithInteger(param_name, max_value + 1)),
+ DhcpConfigError);
+ }
+
+ /// @brief Converts vector to string of hexadecimal digits.
+ ///
+ /// @param vec Input vector.
+ /// @return String of hexadecimal digits converted from vector.
+ std::string toString(const std::vector<uint8_t>& vec) const;
+
+ /// Config DUID pointer
+ CfgDUIDPtr cfg_duid_;
+};
+
+std::string
+DUIDConfigParserTest::createConfigWithType(const std::string& duid_type) const {
+ std::ostringstream s;
+ s << "{ \"type\": \"" << duid_type << "\" }";
+ return (s.str());
+}
+
+std::string
+DUIDConfigParserTest::createConfigWithInteger(const std::string& name,
+ const int64_t value) const {
+ std::ostringstream s;
+ s << "{ \"type\": \"LLT\", \"" << name << "\": " << value << " }";
+ return (s.str());
+}
+
+void
+DUIDConfigParserTest::build(const std::string& config) const {
+ ElementPtr config_element = Element::fromJSON(config);
+ DUIDConfigParser parser;
+ parser.parse(cfg_duid_, config_element);
+}
+
+void
+DUIDConfigParserTest::testTypeOnly(const DUID::DUIDType& duid_type,
+ const std::string& duid_type_text) const {
+ // Use DUID configuration with only a "type".
+ ASSERT_NO_THROW(build(createConfigWithType(duid_type_text)));
+
+ // Make sure that the type is correct and that other parameters are set
+ // to their defaults.
+ ASSERT_TRUE(cfg_duid_);
+ EXPECT_EQ(duid_type, cfg_duid_->getType());
+ EXPECT_TRUE(cfg_duid_->getIdentifier().empty());
+ EXPECT_EQ(0, cfg_duid_->getHType());
+ EXPECT_EQ(0, cfg_duid_->getTime());
+ EXPECT_EQ(0, cfg_duid_->getEnterpriseId());
+}
+
+void
+DUIDConfigParserTest::testInvalidConfig(const std::string& config) const {
+ EXPECT_THROW(build(config), DhcpConfigError);
+}
+
+std::string
+DUIDConfigParserTest::toString(const std::vector<uint8_t>& vec) const {
+ try {
+ return (util::encode::encodeHex(vec));
+ } catch (...) {
+ ADD_FAILURE() << "toString: unable to encode vector to"
+ " hexadecimal string";
+ }
+ return ("");
+}
+
+// This test verifies that it is allowed to specify a DUID-LLT type.
+TEST_F(DUIDConfigParserTest, typeOnlyLLT) {
+ testTypeOnly(DUID::DUID_LLT, "LLT");
+}
+
+// This test verifies that it is allowed to specify a DUID-EN type.
+TEST_F(DUIDConfigParserTest, typeOnlyEN) {
+ testTypeOnly(DUID::DUID_EN, "EN");
+}
+
+// This test verifies that it is allowed to specify a DUID-LL type.
+TEST_F(DUIDConfigParserTest, typeOnlyLL) {
+ testTypeOnly(DUID::DUID_LL, "LL");
+}
+
+// This test verifies that using unsupported DUID type will result in
+// configuration error.
+TEST_F(DUIDConfigParserTest, typeInvalid) {
+ testInvalidConfig(createConfigWithType("WRONG"));
+}
+
+// This test verifies that DUID type is required.
+TEST_F(DUIDConfigParserTest, noType) {
+ // First check that the configuration with DUID type specified is
+ // accepted.
+ ASSERT_NO_THROW(build("{ \"type\": \"LLT\", \"time\": 1 }"));
+ // Now remove the type and expect an error.
+ testInvalidConfig("{ \"time\": 1 }");
+}
+
+// This test verifies that all parameters can be set.
+TEST_F(DUIDConfigParserTest, allParameters) {
+ // Set all parameters.
+ std::string config = "{"
+ " \"type\": \"EN\","
+ " \"identifier\": \"ABCDEF\","
+ " \"time\": 100,"
+ " \"htype\": 8,"
+ " \"enterprise-id\": 2024,"
+ " \"persist\": false"
+ "}";
+ ASSERT_NO_THROW(build(config));
+
+ // Verify that parameters have been set correctly.
+ ASSERT_TRUE(cfg_duid_);
+ EXPECT_EQ(DUID::DUID_EN, cfg_duid_->getType());
+ EXPECT_EQ("ABCDEF", toString(cfg_duid_->getIdentifier()));
+ EXPECT_EQ(8, cfg_duid_->getHType());
+ EXPECT_EQ(100, cfg_duid_->getTime());
+ EXPECT_EQ(2024, cfg_duid_->getEnterpriseId());
+ EXPECT_FALSE(cfg_duid_->persist());
+
+ // Check the config can be got back.
+ isc::test::runToElementTest<CfgDUID>(config, *cfg_duid_);
+}
+
+// Test out of range values for time.
+TEST_F(DUIDConfigParserTest, timeOutOfRange) {
+ testOutOfRange<uint32_t>("time");
+}
+
+// Test out of range values for hardware type.
+TEST_F(DUIDConfigParserTest, htypeOutOfRange) {
+ testOutOfRange<uint16_t>("htype");
+}
+
+// Test out of range values for enterprise id.
+TEST_F(DUIDConfigParserTest, enterpriseIdOutOfRange) {
+ testOutOfRange<uint32_t>("enterprise-id");
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc
new file mode 100644
index 0000000..fd76507
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright (C) 2015,2017 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 <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_expiration.h>
+#include <dhcpsrv/parsers/expiration_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <stdint.h>
+#include <string>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for @c ExpirationConfigParser.
+class ExpirationConfigParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void TearDown();
+
+ /// @brief Include a specified parameter in the configuration.
+ ///
+ /// If the specified parameter already exists, its value is replaced.
+ ///
+ /// @param param_name Parameter name.
+ /// @param value Parameter value.
+ void addParam(const std::string& param_name, const int64_t value);
+
+ /// @brief Creates configuration and parses it with the parser under test.
+ ///
+ /// This method creates the JSON configuration form the parameters
+ /// specified using the @c ExpirationConfigParserTest::addParam method.
+ /// It then uses the parser to parse this configuration. Any exceptions
+ /// emitted by the parser are propagated to the caller (they aren't
+ /// caught by this method).
+ ///
+ /// @return Pointer to the parsed configuration.
+ CfgExpirationPtr renderConfig() const;
+
+ /// @brief Tests that the out of range parameter value is not accepted.
+ ///
+ /// This test checks that the negative value and the value which is
+ /// greater than the maximum for the given parameter is not accepted.
+ ///
+ /// @param param Parameter name.
+ /// @param max_value Maximum value allowed for the parameter.
+ void testOutOfRange(const std::string& param, const uint64_t max_value);
+
+private:
+
+ /// @brief Holds configuration parameters specified for a test.
+ std::map<std::string, int64_t> config_params_;
+
+};
+
+void
+ExpirationConfigParserTest::SetUp() {
+ CfgMgr::instance().clear();
+}
+
+void
+ExpirationConfigParserTest::TearDown() {
+ CfgMgr::instance().clear();
+}
+
+void
+ExpirationConfigParserTest::addParam(const std::string& param_name,
+ const int64_t value) {
+ config_params_[param_name] = value;
+}
+
+CfgExpirationPtr
+ExpirationConfigParserTest::renderConfig() const {
+ std::ostringstream s;
+ // Create JSON configuration from the parameters in the map.
+ s << "{";
+ for (std::map<std::string, int64_t>::const_iterator param =
+ config_params_.begin(); param != config_params_.end();
+ ++param) {
+ // Include comma sign if we're at the subsequent parameter.
+ if (std::distance(config_params_.begin(), param) > 0) {
+ s << ",";
+ }
+ s << "\"" << param->first << "\": " << param->second;
+ }
+ s << "}";
+
+ ElementPtr config_element = Element::fromJSON(s.str());
+
+ // Parse the configuration. This may emit exceptions.
+ ExpirationConfigParser parser;
+ parser.parse(config_element);
+
+ // No exception so return configuration.
+ return (CfgMgr::instance().getStagingCfg()->getCfgExpiration());
+}
+
+void
+ExpirationConfigParserTest::testOutOfRange(const std::string& param,
+ const uint64_t max_value) {
+ // Remove any existing parameters which would influence the
+ // behavior of the test.
+ config_params_.clear();
+
+ // Negative value is not allowed.
+ addParam(param, -3);
+ EXPECT_THROW(renderConfig(), DhcpConfigError)
+ << "test for negative value of '" << param << "' failed";
+
+ // Value greater than maximum is not allowed.
+ addParam(param, max_value + 1);
+ EXPECT_THROW(renderConfig(), DhcpConfigError)
+ << "test for out of range value of '" << param << "' failed";
+
+ // Value in range should be accepted.
+ addParam(param, max_value);
+ EXPECT_NO_THROW(renderConfig())
+ << "test for in range value of '" << param << "' failed";
+
+ // Value of 0 should be accepted.
+ addParam(param, 0);
+ EXPECT_NO_THROW(renderConfig())
+ << "test for zero value of '" << param << "' failed";
+}
+
+
+// This test verifies that all parameters for the expiration may be configured.
+TEST_F(ExpirationConfigParserTest, allParameters) {
+ // Create configuration which overrides default values of all parameters.
+ addParam("reclaim-timer-wait-time", 20);
+ addParam("flush-reclaimed-timer-wait-time", 35);
+ addParam("hold-reclaimed-time", 1800);
+ addParam("max-reclaim-leases", 50);
+ addParam("max-reclaim-time", 100);
+ addParam("unwarned-reclaim-cycles", 10);
+
+ CfgExpirationPtr cfg;
+ ASSERT_NO_THROW(cfg = renderConfig());
+ EXPECT_EQ(20, cfg->getReclaimTimerWaitTime());
+ EXPECT_EQ(35, cfg->getFlushReclaimedTimerWaitTime());
+ EXPECT_EQ(1800, cfg->getHoldReclaimedTime());
+ EXPECT_EQ(50, cfg->getMaxReclaimLeases());
+ EXPECT_EQ(100, cfg->getMaxReclaimTime());
+ EXPECT_EQ(10, cfg->getUnwarnedReclaimCycles());
+}
+
+// This test verifies that default values are used if no parameter is
+// specified.
+TEST_F(ExpirationConfigParserTest, noParameters) {
+ CfgExpirationPtr cfg;
+ ASSERT_NO_THROW(cfg = renderConfig());
+ EXPECT_EQ(CfgExpiration::DEFAULT_RECLAIM_TIMER_WAIT_TIME,
+ cfg->getReclaimTimerWaitTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME,
+ cfg->getFlushReclaimedTimerWaitTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_HOLD_RECLAIMED_TIME,
+ cfg->getHoldReclaimedTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES,
+ cfg->getMaxReclaimLeases());
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_TIME,
+ cfg->getMaxReclaimTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_UNWARNED_RECLAIM_CYCLES,
+ cfg->getUnwarnedReclaimCycles());
+}
+
+// This test verifies that a subset of parameters may be specified and
+// that default values are used for those that aren't specified.
+TEST_F(ExpirationConfigParserTest, someParameters) {
+ addParam("reclaim-timer-wait-time", 15);
+ addParam("hold-reclaimed-time", 2000);
+ addParam("max-reclaim-time", 200);
+
+ CfgExpirationPtr cfg;
+ ASSERT_NO_THROW(cfg = renderConfig());
+ EXPECT_EQ(15, cfg->getReclaimTimerWaitTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_FLUSH_RECLAIMED_TIMER_WAIT_TIME,
+ cfg->getFlushReclaimedTimerWaitTime());
+ EXPECT_EQ(2000, cfg->getHoldReclaimedTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_LEASES,
+ cfg->getMaxReclaimLeases());
+ EXPECT_EQ(200, cfg->getMaxReclaimTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_UNWARNED_RECLAIM_CYCLES,
+ cfg->getUnwarnedReclaimCycles());
+}
+
+// This test verifies that another subset of parameters may be specified
+// and that default values are used for those that aren't specified.
+TEST_F(ExpirationConfigParserTest, otherParameters) {
+ addParam("flush-reclaimed-timer-wait-time", 50);
+ addParam("max-reclaim-leases", 60);
+ addParam("unwarned-reclaim-cycles", 20);
+
+ CfgExpirationPtr cfg;
+ ASSERT_NO_THROW(cfg = renderConfig());
+
+ EXPECT_EQ(CfgExpiration::DEFAULT_RECLAIM_TIMER_WAIT_TIME,
+ cfg->getReclaimTimerWaitTime());
+ EXPECT_EQ(50, cfg->getFlushReclaimedTimerWaitTime());
+ EXPECT_EQ(CfgExpiration::DEFAULT_HOLD_RECLAIMED_TIME,
+ cfg->getHoldReclaimedTime());
+ EXPECT_EQ(60, cfg->getMaxReclaimLeases());
+ EXPECT_EQ(CfgExpiration::DEFAULT_MAX_RECLAIM_TIME,
+ cfg->getMaxReclaimTime());
+ EXPECT_EQ(20, cfg->getUnwarnedReclaimCycles());
+}
+
+// This test verifies that negative parameter values are not allowed.
+TEST_F(ExpirationConfigParserTest, outOfRangeValues) {
+ testOutOfRange("reclaim-timer-wait-time",
+ CfgExpiration::LIMIT_RECLAIM_TIMER_WAIT_TIME);
+ testOutOfRange("flush-reclaimed-timer-wait-time",
+ CfgExpiration::LIMIT_FLUSH_RECLAIMED_TIMER_WAIT_TIME);
+ testOutOfRange("hold-reclaimed-time",
+ CfgExpiration::LIMIT_HOLD_RECLAIMED_TIME);
+ testOutOfRange("max-reclaim-leases",
+ CfgExpiration::LIMIT_MAX_RECLAIM_LEASES);
+ testOutOfRange("max-reclaim-time",
+ CfgExpiration::LIMIT_MAX_RECLAIM_TIME);
+ testOutOfRange("unwarned-reclaim-cycles",
+ CfgExpiration::LIMIT_UNWARNED_RECLAIM_CYCLES);
+}
+
+// This test verifies that it is not allowed to specify a value as
+// a text.
+TEST_F(ExpirationConfigParserTest, notNumberValue) {
+ // The value should not be in quotes.
+ std::string config = "{ \"reclaim-timer-wait-time\": \"10\" }";
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration. It should throw exception.
+ ExpirationConfigParser parser;
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc b/src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc
new file mode 100644
index 0000000..23b4d56
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/flq_allocation_state_unittest.cc
@@ -0,0 +1,278 @@
+// Copyright (C) 2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/pool.h>
+#include <testutils/multi_threading_utils.h>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+// Test creating a new free lease queue allocation state for an IPv4
+// address pool.
+TEST(PoolFreeLeaseAllocationState, createV4) {
+ auto pool = boost::make_shared<Pool4>(IOAddress("192.0.2.1"), IOAddress("192.0.2.10"));
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ EXPECT_TRUE(state->exhausted());
+}
+
+// Test adding and deleting free IPv4 leases.
+TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseV4) {
+ auto pool = boost::make_shared<Pool4>(IOAddress("192.0.2.1"), IOAddress("192.0.2.10"));
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ // A new state lacks free leases until we add them.
+ EXPECT_EQ(0, state->getFreeLeaseCount());
+
+ // Add the first free lease. The pool should now have one free lease
+ // that is always offered.
+ state->addFreeLease(IOAddress("192.0.2.1"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+ // The same lease is always offered.
+ EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+ EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+
+ // Add another free lease. We should now have two free leases.
+ state->addFreeLease(IOAddress("192.0.2.3"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(2, state->getFreeLeaseCount());
+ // The new free lease is appended at the end of the queue. Thus, our
+ // first lease should be offered now.
+ EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+ // Now, the second lease should be offered.
+ EXPECT_EQ("192.0.2.3", state->offerFreeLease().toText());
+
+ // Try to delete a non-existing lease. It should not affect the
+ // existing leases.
+ state->deleteFreeLease(IOAddress("192.0.2.2"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(2, state->getFreeLeaseCount());
+ EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+ EXPECT_EQ("192.0.2.3", state->offerFreeLease().toText());
+
+ // Delete one of the free leases.
+ state->deleteFreeLease(IOAddress("192.0.2.1"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+ // The sole lease should be now offered.
+ EXPECT_EQ("192.0.2.3", state->offerFreeLease().toText());
+ EXPECT_EQ("192.0.2.3", state->offerFreeLease().toText());
+
+ // Delete the remaining lease. The pool is now exhausted.
+ state->deleteFreeLease(IOAddress("192.0.2.3"));
+ EXPECT_TRUE(state->exhausted());
+ EXPECT_TRUE(state->offerFreeLease().isV4Zero());
+}
+
+// Test that duplicate leases are not added to the queue.
+TEST(PoolFreeLeaseAllocationState, addFreeLeaseV4SeveralTimes) {
+ auto pool = boost::make_shared<Pool4>(IOAddress("192.0.2.1"), IOAddress("192.0.2.10"));
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ EXPECT_EQ(0, state->getFreeLeaseCount());
+
+ // Add the free lease for the first time.
+ state->addFreeLease(IOAddress("192.0.2.1"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+
+ // Add the same lease the second time. The second lease instance should
+ // not be inserted.
+ state->addFreeLease(IOAddress("192.0.2.1"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ("192.0.2.1", state->offerFreeLease().toText());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+
+ // Delete the sole lease and ensure there are no more leases.
+ state->deleteFreeLease(IOAddress("192.0.2.1"));
+ EXPECT_TRUE(state->exhausted());
+ EXPECT_EQ(0, state->getFreeLeaseCount());
+}
+
+
+// Test creating a new free lease queue allocation state for an IPv6
+// address pool.
+TEST(PoolFreeLeaseAllocationState, createNA) {
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1::"),
+ IOAddress("2001:db8:1::10"));
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ EXPECT_TRUE(state->exhausted());
+}
+
+// Test adding and deleting free IPv6 address leases.
+TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeaseNA) {
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1::"),
+ IOAddress("2001:db8:1::10"));
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ // A new state lacks free leases until we add them.
+ EXPECT_EQ(0, state->getFreeLeaseCount());
+
+ // Add the first free lease. The pool should now have one free lease
+ // that is always offered.
+ state->addFreeLease(IOAddress("2001:db8:1::1"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+ // The same lease is always offered.
+ EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
+ EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
+
+ // Add another free lease. We should now have two free leases.
+ state->addFreeLease(IOAddress("2001:db8:1::3"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(2, state->getFreeLeaseCount());
+ // The new free lease is appended at the end of the queue. Thus, our
+ // first lease should be offered now.
+ EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
+ // Now, the second lease should be offered.
+ EXPECT_EQ("2001:db8:1::3", state->offerFreeLease().toText());
+
+ // Try to delete a non-existing lease. It should not affect the
+ // existing leases.
+ state->deleteFreeLease(IOAddress("2001:db8:1::2"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(2, state->getFreeLeaseCount());
+ EXPECT_EQ("2001:db8:1::1", state->offerFreeLease().toText());
+ EXPECT_EQ("2001:db8:1::3", state->offerFreeLease().toText());
+
+ // Delete one of the free leases.
+ state->deleteFreeLease(IOAddress("2001:db8:1::1"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+ // The sole lease should be now offered.
+ EXPECT_EQ("2001:db8:1::3", state->offerFreeLease().toText());
+ EXPECT_EQ("2001:db8:1::3", state->offerFreeLease().toText());
+
+ // Delete the remaining lease. The pool is now exhausted.
+ state->deleteFreeLease(IOAddress("2001:db8:1::3"));
+ EXPECT_TRUE(state->exhausted());
+ EXPECT_TRUE(state->offerFreeLease().isV6Zero());
+}
+
+// Test that duplicate leases are not added to the queue.
+TEST(PoolFreeLeaseAllocationState, addFreeLeaseNASeveralTimes) {
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1::"),
+ IOAddress("2001:db8:1::10"));
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ EXPECT_EQ(0, state->getFreeLeaseCount());
+
+ // Add the free lease for the first time.
+ state->addFreeLease(IOAddress("2001:db8:1::5"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ("2001:db8:1::5", state->offerFreeLease().toText());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+
+ // Add the same lease the second time. The second lease instance should
+ // not be inserted.
+ state->addFreeLease(IOAddress("2001:db8:1::5"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ("2001:db8:1::5", state->offerFreeLease().toText());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+
+ // Delete the sole lease and ensure there are no more leases.
+ state->deleteFreeLease(IOAddress("2001:db8:1::5"));
+ EXPECT_TRUE(state->exhausted());
+}
+
+// Test creating a new free lease queue allocation state for an IPv6
+// prefix pool.
+TEST(PoolFreeLeaseAllocationState, createPD) {
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ EXPECT_TRUE(state->exhausted());
+}
+
+// Test creating a new free lease queue allocation state for a
+// delegated prefix pool.
+TEST(PoolFreeLeaseAllocationState, addDeleteFreeLeasePD) {
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ // A new state lacks free leases until we add them.
+ EXPECT_EQ(0, state->getFreeLeaseCount());
+
+ // Add the first free lease. The pool should now have one free lease
+ // that is always offered.
+ state->addFreeLease(IOAddress("3000::5600"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+ // The same lease is always offered.
+ EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+
+ // Add another free lease. We should now have two free leases.
+ state->addFreeLease(IOAddress("3000::7800"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(2, state->getFreeLeaseCount());
+ // The new free lease is appended at the end of the queue. Thus, our
+ // first lease should be offered now.
+ EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+ // Now, the second lease should be offered.
+ EXPECT_EQ("3000::7800", state->offerFreeLease().toText());
+
+ // Try to delete a non-existing lease. It should not affect the
+ // existing leases.
+ state->deleteFreeLease(IOAddress("3000::6400"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(2, state->getFreeLeaseCount());
+ EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+ EXPECT_EQ("3000::7800", state->offerFreeLease().toText());
+
+ // Delete one of the free leases.
+ state->deleteFreeLease(IOAddress("3000::5600"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+ // The sole lease should be now offered.
+ EXPECT_EQ("3000::7800", state->offerFreeLease().toText());
+ EXPECT_EQ("3000::7800", state->offerFreeLease().toText());
+
+ // Delete the remaining lease. The pool is now exhausted.
+ state->deleteFreeLease(IOAddress("3000::7800"));
+ EXPECT_TRUE(state->exhausted());
+ EXPECT_TRUE(state->offerFreeLease().isV6Zero());
+}
+
+// Test that duplicate leases are not added to the queue.
+TEST(PoolFreeLeaseAllocationState, addFreeLeasPDSeveralTimes) {
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+ auto state = PoolFreeLeaseQueueAllocationState::create(pool);
+ ASSERT_TRUE(state);
+ EXPECT_EQ(0, state->getFreeLeaseCount());
+
+ // Add the free lease for the first time.
+ state->addFreeLease(IOAddress("3000::5600"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+
+ // Add the same lease the second time. The second lease instance should
+ // not be inserted.
+ state->addFreeLease(IOAddress("3000::5600"));
+ EXPECT_FALSE(state->exhausted());
+ EXPECT_EQ("3000::5600", state->offerFreeLease().toText());
+ EXPECT_EQ(1, state->getFreeLeaseCount());
+
+ // Delete the sole lease and ensure there are no more leases.
+ state->deleteFreeLease(IOAddress("3000::5600"));
+ EXPECT_TRUE(state->exhausted());
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc
new file mode 100644
index 0000000..350106f
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc
@@ -0,0 +1,1016 @@
+// Copyright (C) 2023 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 <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture class for the DHCPv4 Free Lease Queue allocator.
+class FreeLeaseQueueAllocatorTest4 : public AllocEngine4Test {
+public:
+
+ /// @brief Creates a DHCPv4 lease for an address and MAC address.
+ ///
+ /// @param address Lease address.
+ /// @param hw_address_seed a seed from which the hardware address is generated.
+ /// @return Created lease pointer.
+ Lease4Ptr
+ createLease4(const IOAddress& address, uint64_t hw_address_seed) const {
+ vector<uint8_t> hw_address_vec(sizeof(hw_address_seed));
+ for (auto i = 0; i < sizeof(hw_address_seed); ++i) {
+ hw_address_vec[i] = (hw_address_seed >> i) & 0xFF;
+ }
+ auto hw_address = boost::make_shared<HWAddr>(hw_address_vec, HTYPE_ETHER);
+ auto lease = boost::make_shared<Lease4>(address, hw_address, ClientIdPtr(),
+ 3600, time(0), subnet_->getID());
+ return (lease);
+ }
+};
+
+// Test that the allocator returns the correct type.
+TEST_F(FreeLeaseQueueAllocatorTest4, getType) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+ EXPECT_EQ("flq", alloc.getType());
+}
+
+// Test populating free DHCPv4 leases to the queue.
+TEST_F(FreeLeaseQueueAllocatorTest4, populateFreeAddressLeases) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.100"), 0))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.102"), 1))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.104"), 2))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.106"), 3))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease4(IOAddress("192.0.2.108"), 4))));
+
+ EXPECT_NO_THROW(alloc.initAfterConfigure());
+
+ auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool_->getAllocationState());
+ ASSERT_TRUE(pool_state);
+ EXPECT_FALSE(pool_state->exhausted());
+
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 5; ++i) {
+ auto lease = pool_state->offerFreeLease();
+ ASSERT_FALSE(lease.isV4Zero());
+ addresses.insert(lease);
+ }
+ ASSERT_EQ(5, addresses.size());
+ EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.101")));
+ EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.103")));
+ EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.105")));
+ EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.107")));
+ EXPECT_EQ(1, addresses.count(IOAddress("192.0.2.109")));
+}
+
+// Test allocating IPv4 addresses when a subnet has a single pool.
+TEST_F(FreeLeaseQueueAllocatorTest4, singlePool) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ addresses.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ }
+ // The pool comprises 10 addresses. All should be returned.
+ EXPECT_EQ(10, addresses.size());
+}
+
+// Test allocating IPv4 addresses and re-allocating these that are
+// deleted (released).
+TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithAllocations) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::map<IOAddress, Lease4Ptr> leases;
+ for (auto i = 0; i < 10; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ auto lease = createLease4(candidate, i);
+ leases[candidate] = lease;
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+ // The pool comprises 10 addresses. All should be returned.
+ EXPECT_EQ(10, leases.size());
+
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+
+ auto i = 0;
+ for (auto address_lease : leases) {
+ if (i % 2) {
+ EXPECT_TRUE(lease_mgr.deleteLease(address_lease.second));
+ }
+ ++i;
+ }
+
+ for (auto i = 0; i < 5; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ auto lease = createLease4(candidate, i);
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+
+ candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+}
+
+// Test allocating IPv4 addresses and re-allocating these that are
+// reclaimed.
+TEST_F(FreeLeaseQueueAllocatorTest4, singlePoolWithReclamations) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::map<IOAddress, Lease4Ptr> leases;
+ for (auto i = 0; i < 10; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ auto lease = createLease4(candidate, i);
+ leases[candidate] = lease;
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+ // The pool comprises 10 addresses. All should be returned.
+ EXPECT_EQ(10, leases.size());
+
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+
+ auto i = 0;
+ for (auto address_lease : leases) {
+ if (i % 2) {
+ auto lease = address_lease.second;
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ EXPECT_NO_THROW(lease_mgr.updateLease4(lease));
+ }
+ ++i;
+ }
+ for (auto i = 0; i < 5; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ auto lease = lease_mgr.getLease4(candidate);
+ lease->state_ = Lease::STATE_DEFAULT;
+ EXPECT_NO_THROW(lease_mgr.updateLease4(lease));
+ }
+
+ candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+}
+
+// Test allocating DHCPv4 leases for many pools in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest4, manyPools) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ // Add several more pools.
+ for (int i = 1; i < 10; ++i) {
+ stringstream min, max;
+ min << "192.0.2." << i * 10;
+ max << "192.0.2." << i * 10 + 9;
+ auto pool = boost::make_shared<Pool4>(IOAddress(min.str()),
+ IOAddress(max.str()));
+ subnet_->addPool(pool);
+ }
+
+ // There are ten pools with 10 addresses each.
+ int total = 100;
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ std::set<IOAddress> addresses_set;
+ std::vector<IOAddress> addresses_vector;
+ std::vector<PoolPtr> pools_vector;
+
+ // Pick random addresses the number of times equal to the
+ // subnet capacity to ensure that all addresses are returned.
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ addresses_set.insert(candidate);
+ addresses_vector.push_back(candidate);
+ auto lease = createLease4(candidate, i);
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ pools_vector.push_back(subnet_->getPool(Lease::TYPE_V4, candidate));
+ }
+ // Make sure that unique addresses have been returned.
+ EXPECT_EQ(total, addresses_set.size());
+
+ // Verify that the addresses are returned in the random order.
+ // Count how many times we found consecutive addresses. It should
+ // be 0 or close to 0.
+ int consecutive_addresses = 0;
+ for (auto k = 0; k < addresses_vector.size()-1; ++k) {
+ if (addresses_vector[k].toUint32() == addresses_vector[k+1].toUint32()-1) {
+ ++consecutive_addresses;
+ }
+ }
+ // Ideally, the number of consecutive occurrences should be 0 but we
+ // allow some to make sure the test doesn't fall over sporadically.
+ EXPECT_LT(consecutive_addresses, addresses_vector.size()/4);
+
+ // Repeat similar check for pools. The pools should be picked in the
+ // random order too.
+ int consecutive_pools = 0;
+ for (auto k = 0; k < pools_vector.size()-1; ++k) {
+ // Check if the pools are adjacent (i.e., last address of the
+ // previous pool is a neighbor of the first address of the next
+ // pool).
+ if (pools_vector[k]->getLastAddress().toUint32()+1 ==
+ pools_vector[k+1]->getFirstAddress().toUint32()) {
+ ++consecutive_pools;
+ }
+ }
+ EXPECT_LT(consecutive_pools, pools_vector.size()/2);
+}
+
+// Test that the allocator returns a zero address when there are no pools
+// in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest4, noPools) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ subnet_->delPools(Lease::TYPE_V4);
+
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+}
+
+// Test that the allocator respects client class guards.
+TEST_F(FreeLeaseQueueAllocatorTest4, clientClasses) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ // First pool only allows the client class foo.
+ pool_->allowClientClass("foo");
+
+ // Second pool. It only allows client class bar.
+ auto pool1 = boost::make_shared<Pool4>(IOAddress("192.0.2.120"),
+ IOAddress("192.0.2.129"));
+ pool1->allowClientClass("bar");
+ subnet_->addPool(pool1);
+
+ // Third pool. It only allows client class foo.
+ auto pool2 = boost::make_shared<Pool4>(IOAddress("192.0.2.140"),
+ IOAddress("192.0.2.149"));
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool2);
+
+ // Forth pool. It only allows client class bar.
+ auto pool3 = boost::make_shared<Pool4>(IOAddress("192.0.2.160"),
+ IOAddress("192.0.2.169"));
+ pool3->allowClientClass("bar");
+ subnet_->addPool(pool3);
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // Remember offered addresses.
+ std::set<IOAddress> addresses_set;
+
+ // Simulate client's request belonging to the class bar.
+ cc_.insert("bar");
+ for (auto i = 0; i < 20; ++i) {
+ // Allocate random addresses and make sure they belong to the
+ // pools associated with the class bar.
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_FALSE(candidate.isV4Zero());
+ EXPECT_TRUE(lease_mgr.addLease(createLease4(candidate, i+50)));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(pool1->inRange(candidate) || pool3->inRange(candidate));
+ }
+ EXPECT_EQ(20, addresses_set.size());
+
+ addresses_set.clear();
+
+ // Simulate the case that the client also belongs to the class foo.
+ // All pools should now be available.
+ cc_.insert("foo");
+ for (auto i = 0; i < 20; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(lease_mgr.addLease(createLease4(candidate, i+100)));
+ EXPECT_TRUE(subnet_->inRange(candidate));
+ }
+ EXPECT_EQ(20, addresses_set.size());
+
+ // When the client does not belong to any client class the allocator
+ // can't offer any address to the client.
+ cc_.clear();
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+}
+
+/// @brief Test fixture class for the DHCPv6 Free Lease Queue allocator.
+class FreeLeaseQueueAllocatorTest6 : public AllocEngine6Test {
+public:
+
+ /// @brief Creates a DHCPv6 lease for an address and DUID.
+ ///
+ /// @param type lease type.
+ /// @param address Lease address.
+ /// @param duid_seed a seed from which the DUID is generated.
+ /// @return Created lease pointer.
+ Lease6Ptr
+ createLease6(Lease::Type type, const IOAddress& address, uint64_t duid_seed) const {
+ vector<uint8_t> duid_vec(sizeof(duid_seed));
+ for (auto i = 0; i < sizeof(duid_seed); ++i) {
+ duid_vec[i] = (duid_seed >> i) & 0xFF;
+ }
+ auto duid = boost::make_shared<DUID>(duid_vec);
+ auto lease = boost::make_shared<Lease6>(type, address, duid, 1, 1800,
+ 3600, subnet_->getID());
+ return (lease);
+ }
+
+};
+
+// Test that the allocator returns the correct type.
+TEST_F(FreeLeaseQueueAllocatorTest6, getType) {
+ FreeLeaseQueueAllocator allocNA(Lease::TYPE_NA, subnet_);
+ EXPECT_EQ("flq", allocNA.getType());
+
+ FreeLeaseQueueAllocator allocPD(Lease::TYPE_PD, subnet_);
+ EXPECT_EQ("flq", allocPD.getType());
+}
+
+// Test populating free DHCPv6 address leases to the queue.
+TEST_F(FreeLeaseQueueAllocatorTest6, populateFreeAddressLeases) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::10"), 0))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::12"), 1))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::14"), 2))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::16"), 3))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::18"), 4))));
+
+ EXPECT_NO_THROW(alloc.initAfterConfigure());
+
+ auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool_->getAllocationState());
+ ASSERT_TRUE(pool_state);
+ EXPECT_FALSE(pool_state->exhausted());
+
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 12; ++i) {
+ auto lease = pool_state->offerFreeLease();
+ ASSERT_FALSE(lease.isV6Zero());
+ addresses.insert(lease);
+ }
+ ASSERT_EQ(12, addresses.size());
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::11")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::13")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::15")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::17")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::19")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1a")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1b")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1c")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1d")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1e")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::1f")));
+ EXPECT_EQ(1, addresses.count(IOAddress("2001:db8:1::20")));
+}
+
+// Test allocating IPv6 addresses when a subnet has a single pool.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePool) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_FALSE(candidate.isV6Zero());
+ addresses.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ }
+ // The pool comprises 17 addresses. All should be returned.
+ EXPECT_EQ(17, addresses.size());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// deleted (released).
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePoolWithAllocations) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::map<IOAddress, Lease6Ptr> leases;
+ for (auto i = 0; i < 17; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_FALSE(candidate.isV6Zero());
+ auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+ leases[candidate] = lease;
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+ // The pool comprises 17 addresses. All should be returned.
+ EXPECT_EQ(17, leases.size());
+
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(candidate.isV6Zero());
+
+ auto i = 0;
+ for (auto address_lease : leases) {
+ if (i % 2) {
+ EXPECT_TRUE(lease_mgr.deleteLease(address_lease.second));
+ }
+ ++i;
+ }
+
+ for (auto i = 0; i < 8; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+
+ candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// reclaimed.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePoolWithReclamations) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::map<IOAddress, Lease6Ptr> leases;
+ for (auto i = 0; i < 17; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_FALSE(candidate.isV6Zero());
+ auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+ leases[candidate] = lease;
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+ // The pool comprises 17 addresses. All should be returned.
+ EXPECT_EQ(17, leases.size());
+
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(candidate.isV6Zero());
+
+ auto i = 0;
+ for (auto address_lease : leases) {
+ if (i % 2) {
+ auto lease = address_lease.second;
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+ }
+ ++i;
+ }
+
+ for (auto i = 0; i < 8; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ auto lease = lease_mgr.getLease6(Lease::TYPE_NA, candidate);
+ lease->state_ = Lease::STATE_DEFAULT;
+ EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+ }
+
+ candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test allocating DHCPv6 leases for many pools in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPools) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // Add several more pools.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+ min << "2001:db8:1::" << hex << i * 16 + 1;
+ max << "2001:db8:1::" << hex << i * 16 + 16;
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress(min.str()),
+ IOAddress(max.str()));
+ subnet_->addPool(pool);
+ }
+
+ // First pool (::10 - ::20) has 17 addresses.
+ // There are 8 extra pools with 16 addresses in each.
+ int total = 17 + 8 * 16;
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ std::set<IOAddress> addresses_set;
+ std::vector<IOAddress> addresses_vector;
+ std::vector<PoolPtr> pools_vector;
+
+ // Pick random addresses the number of times equal to the
+ // subnet capacity to ensure that all addresses are returned.
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ addresses_set.insert(candidate);
+ addresses_vector.push_back(candidate);
+ auto lease = createLease6(Lease::TYPE_NA, candidate, i);
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ pools_vector.push_back(subnet_->getPool(Lease::TYPE_NA, candidate));
+ }
+ // Make sure that unique addresses have been returned.
+ EXPECT_EQ(total, addresses_set.size());
+
+ // Verify that the addresses are returned in the random order.
+ // Count how many times we found consecutive addresses. It should
+ // be 0 or close to 0.
+ int consecutive_addresses = 0;
+ for (auto k = 0; k < addresses_vector.size()-1; ++k) {
+ if (IOAddress::increase(addresses_vector[k]) == addresses_vector[k+1]) {
+ ++consecutive_addresses;
+ }
+ }
+ // Ideally, the number of consecutive occurrences should be 0 but we
+ // allow some to make sure the test doesn't fall over sporadically.
+ EXPECT_LT(consecutive_addresses, addresses_vector.size()/4);
+
+ // Repeat similar check for pools. The pools should be picked in the
+ // random order too.
+ int consecutive_pools = 0;
+ for (auto k = 0; k < pools_vector.size()-1; ++k) {
+ if (IOAddress::increase(pools_vector[k]->getLastAddress()) ==
+ pools_vector[k]->getFirstAddress()) {
+ ++consecutive_pools;
+ }
+ }
+ EXPECT_LT(consecutive_pools, pools_vector.size()/2);
+}
+
+// Test that the allocator returns a zero address when there are no pools
+// in a subnet.
+TEST_F(FreeLeaseQueueAllocatorTest6, noPools) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ subnet_->delPools(Lease::TYPE_NA);
+
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test that the allocator respects client class guards.
+TEST_F(FreeLeaseQueueAllocatorTest6, clientClasses) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // First pool only allows the client class foo.
+ pool_->allowClientClass("foo");
+
+ // Second pool. It only allows client class bar.
+ auto pool1 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::120"),
+ IOAddress("2001:db8:1::129"));
+ pool1->allowClientClass("bar");
+ subnet_->addPool(pool1);
+
+ // Third pool. It only allows client class foo.
+ auto pool2 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::140"),
+ IOAddress("2001:db8:1::149"));
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool2);
+
+ // Forth pool. It only allows client class bar.
+ auto pool3 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::160"),
+ IOAddress("2001:db8:1::169"));
+ pool3->allowClientClass("bar");
+ subnet_->addPool(pool3);
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // When the client does not belong to any client class the allocator
+ // can't offer any address to the client.
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(candidate.isV6Zero());
+
+ // Remember offered addresses.
+ std::set<IOAddress> addresses_set;
+
+ // Simulate client's request belonging to the class bar.
+ cc_.insert("bar");
+ for (auto i = 0; i < 20; ++i) {
+ // Allocate random addresses and make sure they belong to the
+ // pools associated with the class bar.
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_FALSE(candidate.isV6Zero());
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_NA, candidate, i+50)));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(pool1->inRange(candidate) || pool3->inRange(candidate));
+ }
+ EXPECT_EQ(20, addresses_set.size());
+
+ // Simulate the case that the client also belongs to the class foo.
+ // All pools should now be available.
+ cc_.insert("foo");
+ for (auto i = 0; i < 27; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_NA, candidate, i+100)));
+ EXPECT_TRUE(subnet_->inRange(candidate));
+ }
+ EXPECT_EQ(47, addresses_set.size());
+}
+
+// Test populating free DHCPv6 prefix leases to the queue.
+TEST_F(FreeLeaseQueueAllocatorTest6, populateFreePrefixDelegationLeases) {
+ subnet_->delPools(Lease::TYPE_PD);
+
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ auto pool = Pool6::create(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 112, 120);
+ subnet_->addPool(pool);
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 0))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::1000"), 1))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::2000"), 2))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::3000"), 3))));
+ EXPECT_TRUE(lease_mgr.addLease((createLease6(Lease::TYPE_PD, IOAddress("2001:db8:2::4000"), 4))));
+
+ EXPECT_NO_THROW(alloc.initAfterConfigure());
+
+ auto pool_state = boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>(pool->getAllocationState());
+ ASSERT_TRUE(pool_state);
+ EXPECT_FALSE(pool_state->exhausted());
+
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 256; ++i) {
+ auto lease = pool_state->offerFreeLease();
+ ASSERT_FALSE(lease.isV6Zero());
+ addresses.insert(lease);
+ }
+ ASSERT_EQ(251, addresses.size());
+ EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::")));
+ EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::1000")));
+ EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::2000")));
+ EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::3000")));
+ EXPECT_EQ(0, addresses.count(IOAddress("2001:db8:2::4000")));
+}
+
+// Test allocating delegated prefixes when a subnet has a single pool.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPool) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ Pool6Ptr pool;
+
+ // Remember returned prefixes, so we can verify that unique addresses
+ // are returned.
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < 65536; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_EQ(pd_pool_, pool);
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // The pool comprises 65536 prefixes. All should be returned.
+ EXPECT_EQ(65536, prefixes.size());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// deleted (released).
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithAllocations) {
+ // Remove the default pool because it is too large for this test case.
+ subnet_->delPools(Lease::TYPE_PD);
+ // Add a smaller pool.
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress("3000::"),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::map<IOAddress, Lease6Ptr> leases;
+ for (auto i = 0; i < 256; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_FALSE(candidate.isV6Zero());
+ auto lease = createLease6(Lease::TYPE_PD, candidate, i);
+ leases[candidate] = lease;
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+ // The pool comprises 256 delegated prefixes. All should be returned.
+ EXPECT_EQ(256, leases.size());
+
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_TRUE(candidate.isV6Zero());
+
+ auto i = 0;
+ for (auto address_lease : leases) {
+ if (i % 2) {
+ EXPECT_TRUE(lease_mgr.deleteLease(address_lease.second));
+ }
+ ++i;
+ }
+
+ for (auto i = 0; i < 128; ++i) {
+ candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ auto lease = createLease6(Lease::TYPE_PD, candidate, i);
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+
+ candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test allocating IPv6 addresses and re-allocating these that are
+// reclaimed.
+TEST_F(FreeLeaseQueueAllocatorTest6, singlePdPoolWithReclamations) {
+ // Remove the default pool because it is too large for this test case.
+ subnet_->delPools(Lease::TYPE_PD);
+ // Add a smaller pool.
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress("3000::"),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::map<IOAddress, Lease6Ptr> leases;
+ for (auto i = 0; i < 256; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_FALSE(candidate.isV6Zero());
+ auto lease = createLease6(Lease::TYPE_PD, candidate, i);
+ leases[candidate] = lease;
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ EXPECT_TRUE(lease_mgr.addLease(lease));
+ }
+ // The pool comprises 256 delegated prefixes. All should be returned.
+ EXPECT_EQ(256, leases.size());
+
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_TRUE(candidate.isV6Zero());
+
+ auto i = 0;
+ for (auto address_lease : leases) {
+ if (i % 2) {
+ auto lease = address_lease.second;
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+ }
+ ++i;
+ }
+
+ for (auto i = 0; i < 128; ++i) {
+ candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ auto lease = lease_mgr.getLease6(Lease::TYPE_PD, candidate);
+ lease->state_ = Lease::STATE_DEFAULT;
+ EXPECT_NO_THROW(lease_mgr.updateLease6(lease));
+ }
+
+ candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_TRUE(candidate.isV6Zero());
+}
+
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPools) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+ size_t total = 65536 + 10 * 256;
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ Pool6Ptr pool;
+
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ EXPECT_TRUE(pool);
+ EXPECT_FALSE(candidate.isV6Zero());
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferLower) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+
+ size_t total = 65536;
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+
+ Pool6Ptr pool;
+
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 120);
+ EXPECT_FALSE(candidate.isV6Zero());
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferEqual) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+
+ size_t total = 10 * 256;
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ Pool6Ptr pool;
+
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_EQUAL, IOAddress("::"), 128);
+ EXPECT_FALSE(candidate.isV6Zero());
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(FreeLeaseQueueAllocatorTest6, manyPdPoolsPreferHigher) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+
+ size_t total = 10 * 256;
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ Pool6Ptr pool;
+
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+ EXPECT_FALSE(candidate.isV6Zero());
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+}
+
+// Test that the allocator respects client class guards.
+TEST_F(FreeLeaseQueueAllocatorTest6, pdPoolsClientClasses) {
+ FreeLeaseQueueAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ // First pool only allows the client class foo.
+ pd_pool_->allowClientClass("foo");
+
+ auto pool2 = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress("3000:1::"),
+ 120,
+ 128);
+ // Second pool only allows the client class bar.
+ pool2->allowClientClass("bar");
+ subnet_->addPool(pool2);
+
+ ASSERT_NO_THROW(alloc.initAfterConfigure());
+ auto& lease_mgr = LeaseMgrFactory::instance();
+
+ Pool6Ptr pool;
+
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+ EXPECT_TRUE(candidate.isV6Zero());
+
+ cc_.insert("bar");
+ for (auto i = 0; i < 256; ++i) {
+ candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+ EXPECT_FALSE(candidate.isV6Zero());
+ EXPECT_TRUE(lease_mgr.addLease(createLease6(Lease::TYPE_PD, candidate, i)));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+
+ candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+ EXPECT_TRUE(candidate.isV6Zero());
+}
+
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
new file mode 100644
index 0000000..ad2f23f
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
@@ -0,0 +1,4599 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_exceptions.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <exceptions/exceptions.h>
+#include <stats/stats_mgr.h>
+#include <stats/testutils/stats_test_utils.h>
+#include <testutils/gtest_utils.h>
+#include <util/bigints.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <limits>
+#include <sstream>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::stats;
+using namespace isc::util;
+
+using isc::stats::test::checkStat;
+
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// IPv4 and IPv6 addresses used in the tests
+const char* ADDRESS4[] = {
+ "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+ "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7",
+ NULL
+};
+const char* ADDRESS6[] = {
+ "2001:db8::", "2001:db8:1::", "2001:db8:2::", "2001:db8:3::",
+ "2001:db8:4::", "2001:db8:5::", "2001:db8:6::", "2001:db8:7::",
+ NULL
+};
+
+// Lease types that correspond to ADDRESS6 leases
+static const Lease::Type LEASETYPE6[] = {
+ Lease::TYPE_NA, Lease::TYPE_TA, Lease::TYPE_PD, Lease::TYPE_NA,
+ Lease::TYPE_TA, Lease::TYPE_PD, Lease::TYPE_NA, Lease::TYPE_TA
+};
+
+GenericLeaseMgrTest::GenericLeaseMgrTest()
+ : lmptr_(NULL) {
+ // Initialize address strings and IOAddresses
+ for (int i = 0; ADDRESS4[i] != NULL; ++i) {
+ string addr(ADDRESS4[i]);
+ straddress4_.push_back(addr);
+ IOAddress ioaddr(addr);
+ ioaddress4_.push_back(ioaddr);
+ }
+
+ for (int i = 0; ADDRESS6[i] != NULL; ++i) {
+ string addr(ADDRESS6[i]);
+ straddress6_.push_back(addr);
+ IOAddress ioaddr(addr);
+ ioaddress6_.push_back(ioaddr);
+
+ /// Let's create different lease types. We use LEASETYPE6 values as
+ /// a template
+ leasetype6_.push_back(LEASETYPE6[i]);
+ }
+
+ // Clear all subnets defined in previous tests.
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->clear();
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->clear();
+
+ // Clear all stats.
+ StatsMgr::instance().removeAll();
+}
+
+GenericLeaseMgrTest::~GenericLeaseMgrTest() {
+ // Does nothing. The derived classes are expected to clean up, i.e.
+ // remove the lmptr_ pointer.
+}
+
+Lease4Ptr
+GenericLeaseMgrTest::initializeLease4(std::string address) {
+ Lease4Ptr lease(new Lease4());
+
+ // Set the address of the lease
+ lease->addr_ = IOAddress(address);
+
+ // Set other parameters. For historical reasons, address 0 is not used.
+ if (address == straddress4_[0]) {
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(6, 0x08), HTYPE_ETHER));
+ lease->client_id_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x42)));
+ lease->valid_lft_ = 8677;
+ lease->cltt_ = 168256;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 23;
+ lease->fqdn_rev_ = true;
+ lease->fqdn_fwd_ = false;
+ lease->hostname_ = "myhost.example.com.";
+
+ } else if (address == straddress4_[1]) {
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(6, 0x19), HTYPE_ETHER));
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53)));
+ lease->valid_lft_ = 3677;
+ lease->cltt_ = 123456;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 73;
+ lease->fqdn_rev_ = true;
+ lease->fqdn_fwd_ = true;
+ lease->hostname_ = "myhost.example.com.";
+ lease->pool_id_ = 7;
+
+ } else if (address == straddress4_[2]) {
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(6, 0x2a), HTYPE_ETHER));
+ lease->client_id_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x64)));
+ lease->valid_lft_ = 5412;
+ lease->cltt_ = 234567;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 73; // Same as lease 1
+ lease->fqdn_rev_ = false;
+ lease->fqdn_fwd_ = false;
+ lease->hostname_ = "";
+
+ } else if (address == straddress4_[3]) {
+ // Hardware address same as lease 1.
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(6, 0x19), HTYPE_ETHER));
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x75)));
+
+ // The times used in the next tests are deliberately restricted - we
+ // should be able to cope with valid lifetimes up to 0xffffffff.
+ // However, this will lead to overflows.
+ // @TODO: test overflow conditions when code has been fixed.
+ lease->valid_lft_ = 7000;
+ lease->cltt_ = 234567;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 37;
+ lease->fqdn_rev_ = true;
+ lease->fqdn_fwd_ = true;
+ lease->hostname_ = "otherhost.example.com.";
+
+ } else if (address == straddress4_[4]) {
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(6, 0x4c), HTYPE_ETHER));
+ // Same ClientId as straddr4_[1]
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
+ lease->valid_lft_ = 7736;
+ lease->cltt_ = 222456;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 85;
+ lease->fqdn_rev_ = true;
+ lease->fqdn_fwd_ = true;
+ lease->hostname_ = "otherhost.example.com.";
+
+ } else if (address == straddress4_[5]) {
+ // Same as lease 1
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(6, 0x19), HTYPE_ETHER));
+ // Same ClientId and IAID as straddress4_1
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
+ lease->valid_lft_ = 7832;
+ lease->cltt_ = 227476;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 175;
+ lease->fqdn_rev_ = false;
+ lease->fqdn_fwd_ = false;
+ lease->hostname_ = "otherhost.example.com.";
+ lease->setContext(Element::fromJSON("{ \"foo\": true }"));
+
+ } else if (address == straddress4_[6]) {
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(6, 0x6e), HTYPE_ETHER));
+ // Same ClientId as straddress4_1
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
+ lease->valid_lft_ = 1832;
+ lease->cltt_ = 627476;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 112;
+ lease->fqdn_rev_ = false;
+ lease->fqdn_fwd_ = true;
+ lease->hostname_ = "myhost.example.com.";
+
+ } else if (address == straddress4_[7]) {
+ lease->hwaddr_.reset(new HWAddr(vector<uint8_t>(), HTYPE_ETHER)); // Empty
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x77)));
+ lease->valid_lft_ = 7975;
+ lease->cltt_ = 213876;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 19;
+ lease->fqdn_rev_ = true;
+ lease->fqdn_fwd_ = true;
+ lease->hostname_ = "myhost.example.com.";
+ lease->setContext(Element::fromJSON("{ \"bar\": false }"));
+
+ } else {
+ // Unknown address, return an empty pointer.
+ lease.reset();
+
+ }
+
+ return (lease);
+}
+
+Lease6Ptr
+GenericLeaseMgrTest::initializeLease6(std::string address) {
+ Lease6Ptr lease(new Lease6());
+
+ // Set the address of the lease
+ lease->addr_ = IOAddress(address);
+
+ // Set other parameters. For historical reasons, address 0 is not used.
+ if (address == straddress6_[0]) {
+ lease->type_ = leasetype6_[0];
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 142;
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x77)));
+ lease->preferred_lft_ = 900;
+ lease->valid_lft_ = 8677;
+ lease->cltt_ = 168256;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 23;
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+
+ } else if (address == straddress6_[1]) {
+ lease->type_ = leasetype6_[1];
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 42;
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ lease->preferred_lft_ = 3600;
+ lease->valid_lft_ = 3677;
+ lease->cltt_ = 123456;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 73;
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "myhost.example.com.";
+ lease->pool_id_ = 7;
+
+ } else if (address == straddress6_[2]) {
+ lease->type_ = leasetype6_[2];
+ lease->prefixlen_ = 48;
+ lease->iaid_ = 89;
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x3a)));
+ lease->preferred_lft_ = 1800;
+ lease->valid_lft_ = 5412;
+ lease->cltt_ = 234567;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 73; // Same as lease 1
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = false;
+ lease->hostname_ = "myhost.example.com.";
+
+ } else if (address == straddress6_[3]) {
+ lease->type_ = leasetype6_[3];
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 0xfffffffe;
+ vector<uint8_t> duid;
+ for (uint8_t i = 31; i < 126; ++i) {
+ duid.push_back(i);
+ }
+ lease->duid_ = DuidPtr(new DUID(duid));
+
+ // The times used in the next tests are deliberately restricted - we
+ // should be able to cope with valid lifetimes up to 0xffffffff.
+ // However, this will lead to overflows.
+ // @TODO: test overflow conditions when code has been fixed.
+ lease->preferred_lft_ = 7200;
+ lease->valid_lft_ = 7000;
+ lease->cltt_ = 234567;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 37;
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = false;
+ lease->hostname_ = "myhost.example.com.";
+
+ } else if (address == straddress6_[4]) {
+ // Same DUID and IAID as straddress6_1
+ lease->type_ = leasetype6_[4];
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 42;
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ lease->preferred_lft_ = 4800;
+ lease->valid_lft_ = 7736;
+ lease->cltt_ = 222456;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 671;
+ lease->fqdn_fwd_ = true;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "otherhost.example.com.";
+
+ } else if (address == straddress6_[5]) {
+ // Same DUID and IAID as straddress6_1
+ lease->type_ = leasetype6_[5];
+ lease->prefixlen_ = 56;
+ lease->iaid_ = 42; // Same as lease 4
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ // Same as lease 4
+ lease->preferred_lft_ = 5400;
+ lease->valid_lft_ = 7832;
+ lease->cltt_ = 227476;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 175;
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "hostname.example.com.";
+ lease->setContext(Element::fromJSON("{ \"foo\": true }"));
+
+ } else if (address == straddress6_[6]) {
+ // Same DUID as straddress6_1
+ lease->type_ = leasetype6_[6];
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 93;
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ // Same as lease 4
+ lease->preferred_lft_ = 5400;
+ lease->valid_lft_ = 1832;
+ lease->cltt_ = 627476;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 112;
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "hostname.example.com.";
+
+ } else if (address == straddress6_[7]) {
+ // Same IAID as straddress6_1
+ lease->type_ = leasetype6_[7];
+ lease->prefixlen_ = 128;
+ lease->iaid_ = 42;
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0xe5)));
+ lease->preferred_lft_ = 5600;
+ lease->valid_lft_ = 7975;
+ lease->cltt_ = 213876;
+ lease->updateCurrentExpirationTime();
+ lease->subnet_id_ = 19;
+ lease->fqdn_fwd_ = false;
+ lease->fqdn_rev_ = true;
+ lease->hostname_ = "hostname.example.com.";
+ lease->setContext(Element::fromJSON("{ \"bar\": false }"));
+
+ } else {
+ // Unknown address, return an empty pointer.
+ lease.reset();
+
+ }
+
+ return (lease);
+}
+
+template <typename T>
+void GenericLeaseMgrTest::checkLeasesDifferent(const std::vector<T>& leases) const {
+
+ // Check they were created
+ for (size_t i = 0; i < leases.size(); ++i) {
+ ASSERT_TRUE(leases[i]);
+ }
+
+ // Check they are different
+ for (size_t i = 0; i < (leases.size() - 1); ++i) {
+ for (size_t j = (i + 1); j < leases.size(); ++j) {
+ stringstream s;
+ s << "Comparing leases " << i << " & " << j << " for equality";
+ SCOPED_TRACE(s.str());
+ EXPECT_TRUE(*leases[i] != *leases[j]);
+ }
+ }
+}
+
+vector<Lease4Ptr>
+GenericLeaseMgrTest::createLeases4() {
+
+ // Create leases for each address
+ vector<Lease4Ptr> leases;
+ for (size_t i = 0; i < straddress4_.size(); ++i) {
+ leases.push_back(initializeLease4(straddress4_[i]));
+ }
+ EXPECT_EQ(8, leases.size());
+
+ // Check all were created and that they are different.
+ checkLeasesDifferent(leases);
+
+ return (leases);
+}
+
+vector<Lease6Ptr>
+GenericLeaseMgrTest::createLeases6() {
+
+ // Create leases for each address
+ vector<Lease6Ptr> leases;
+ for (size_t i = 0; i < straddress6_.size(); ++i) {
+ leases.push_back(initializeLease6(straddress6_[i]));
+ }
+ EXPECT_EQ(8, leases.size());
+
+ // Check all were created and that they are different.
+ checkLeasesDifferent(leases);
+
+ return (leases);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientId() {
+ // Let's initialize a specific lease ...
+ Lease4Ptr lease = initializeLease4(straddress4_[1]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ Lease4Collection returned = lmptr_->getLease4(*lease->client_id_);
+
+ ASSERT_EQ(1, returned.size());
+ // We should retrieve our lease...
+ detailCompareLease(lease, *returned.begin());
+ lease = initializeLease4(straddress4_[2]);
+ returned = lmptr_->getLease4(*lease->client_id_);
+
+ ASSERT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLease4NullClientId() {
+ // Let's initialize a specific lease ... But this time
+ // We keep its client id for further lookup and
+ // We clearly 'reset' it ...
+ Lease4Ptr leaseA = initializeLease4(straddress4_[4]);
+ ClientIdPtr client_id = leaseA->client_id_;
+ leaseA->client_id_ = ClientIdPtr();
+ ASSERT_TRUE(lmptr_->addLease(leaseA));
+
+ Lease4Collection returned = lmptr_->getLease4(*client_id);
+ // Shouldn't have our previous lease ...
+ ASSERT_TRUE(returned.empty());
+
+ // Add another lease with the non-NULL client id, and make sure that the
+ // lookup will not break due to existence of both leases with non-NULL and
+ // NULL client ids.
+ Lease4Ptr leaseB = initializeLease4(straddress4_[0]);
+ // Shouldn't throw any null pointer exception
+ ASSERT_TRUE(lmptr_->addLease(leaseB));
+ // Try to get the lease.
+ returned = lmptr_->getLease4(*client_id);
+ ASSERT_TRUE(returned.empty());
+
+ // Let's make it more interesting and add another lease with NULL client id.
+ Lease4Ptr leaseC = initializeLease4(straddress4_[5]);
+ leaseC->client_id_.reset();
+ ASSERT_TRUE(lmptr_->addLease(leaseC));
+ returned = lmptr_->getLease4(*client_id);
+ ASSERT_TRUE(returned.empty());
+
+ // But getting the lease with non-NULL client id should be successful.
+ returned = lmptr_->getLease4(*leaseB->client_id_);
+ ASSERT_EQ(1, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testLease4NullClientId() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Let's clear client-id pointers
+ leases[1]->client_id_ = ClientIdPtr();
+ leases[2]->client_id_ = ClientIdPtr();
+ leases[3]->client_id_ = ClientIdPtr();
+
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
+
+ // Reopen the database to ensure that they actually got stored.
+ reopen();
+
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+
+ // Check that we can't add a second lease with the same address
+ EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+ // Check that we can get the lease by HWAddr
+ HWAddr tmp(*leases[2]->hwaddr_);
+ Lease4Collection returned = lmptr_->getLease4(tmp);
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[2], *returned.begin());
+
+ l_returned = lmptr_->getLease4(tmp, leases[2]->subnet_id_);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ // Check that we can update the lease
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->subnet_id_;
+ leases[1]->valid_lft_ *= 2;
+ lmptr_->updateLease4(leases[1]);
+
+ // ... and check that the lease is indeed updated
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Delete a lease, check that it's gone, and that we can't delete it
+ // a second time.
+ EXPECT_TRUE(lmptr_->deleteLease(leases[1]));
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ EXPECT_FALSE(l_returned);
+ EXPECT_FALSE(lmptr_->deleteLease(leases[1]));
+
+ // Check that the second address is still there.
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddr1() {
+ // Let's initialize two different leases 4 and just add the first ...
+ Lease4Ptr leaseA = initializeLease4(straddress4_[5]);
+ HWAddr hwaddrA(*leaseA->hwaddr_);
+ HWAddr hwaddrB(vector<uint8_t>(6, 0x80), HTYPE_ETHER);
+
+ EXPECT_TRUE(lmptr_->addLease(leaseA));
+
+ // we should not have a lease, with this MAC Addr
+ Lease4Collection returned = lmptr_->getLease4(hwaddrB);
+ ASSERT_EQ(0, returned.size());
+
+ // But with this one
+ returned = lmptr_->getLease4(hwaddrA);
+ ASSERT_EQ(1, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddr2() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the hardware address of lease 1
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ HWAddr tmp(*leases[1]->hwaddr_);
+ Lease4Collection returned = lmptr_->getLease4(tmp);
+
+ // Should be three leases, matching leases[1], [3] and [5].
+ ASSERT_EQ(3, returned.size());
+
+ // Check the lease[5] (and only this one) has an user context.
+ size_t contexts = 0;
+ for (Lease4Collection::const_iterator i = returned.begin();
+ i != returned.end(); ++i) {
+ if ((*i)->getContext()) {
+ ++contexts;
+ EXPECT_EQ("{ \"foo\": true }", (*i)->getContext()->str());
+ }
+ }
+ EXPECT_EQ(1, contexts);
+
+ // Easiest way to check is to look at the addresses.
+ vector<string> addresses;
+ for (Lease4Collection::const_iterator i = returned.begin();
+ i != returned.end(); ++i) {
+ addresses.push_back((*i)->addr_.toText());
+ }
+ sort(addresses.begin(), addresses.end());
+ EXPECT_EQ(straddress4_[1], addresses[0]);
+ EXPECT_EQ(straddress4_[3], addresses[1]);
+ EXPECT_EQ(straddress4_[5], addresses[2]);
+
+ // Repeat test with just one expected match
+ returned = lmptr_->getLease4(*leases[2]->hwaddr_);
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[2], *returned.begin());
+
+ // Check that an empty vector is valid
+ EXPECT_TRUE(leases[7]->hwaddr_->hwaddr_.empty());
+ EXPECT_FALSE(leases[7]->client_id_->getClientId().empty());
+ returned = lmptr_->getLease4(*leases[7]->hwaddr_);
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[7], *returned.begin());
+
+ // Try to get something with invalid hardware address
+ vector<uint8_t> invalid(6, 0);
+ returned = lmptr_->getLease4(invalid);
+ EXPECT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testAddGetDelete6() {
+ const std::string addr234("2001:db8:1::234");
+ const std::string addr456("2001:db8:1::456");
+ const std::string addr789("2001:db8:1::789");
+ IOAddress addr(addr456);
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ uint32_t iaid = 7; // just a number
+
+ SubnetID subnet_id = 8; // just another number
+
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid, iaid, 100, 200,
+ subnet_id));
+
+ EXPECT_TRUE(lmptr_->addLease(lease));
+
+ // should not be allowed to add a second lease with the same address
+ EXPECT_FALSE(lmptr_->addLease(lease));
+
+ Lease6Ptr x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr234));
+ EXPECT_EQ(Lease6Ptr(), x);
+
+ x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr456));
+ ASSERT_TRUE(x);
+
+ EXPECT_EQ(x->addr_, addr);
+ EXPECT_TRUE(*x->duid_ == *duid);
+ EXPECT_EQ(x->iaid_, iaid);
+ EXPECT_EQ(x->subnet_id_, subnet_id);
+
+ // These are not important from lease management perspective, but
+ // let's check them anyway.
+ EXPECT_EQ(Lease::TYPE_NA, x->type_);
+ EXPECT_EQ(100, x->preferred_lft_);
+ EXPECT_EQ(200, x->valid_lft_);
+
+ // Test getLease6(duid, iaid, subnet_id) - positive case
+ Lease6Ptr y = lmptr_->getLease6(Lease::TYPE_NA, *duid, iaid, subnet_id);
+ ASSERT_TRUE(y);
+ EXPECT_TRUE(*y->duid_ == *duid);
+ EXPECT_EQ(y->iaid_, iaid);
+ EXPECT_EQ(y->addr_, addr);
+
+ // Test getLease6(duid, iaid, subnet_id) - wrong iaid
+ uint32_t invalid_iaid = 9; // no such iaid
+ y = lmptr_->getLease6(Lease::TYPE_NA, *duid, invalid_iaid, subnet_id);
+ EXPECT_FALSE(y);
+
+ uint32_t invalid_subnet_id = 999;
+ y = lmptr_->getLease6(Lease::TYPE_NA, *duid, iaid, invalid_subnet_id);
+ EXPECT_FALSE(y);
+
+ // truncated duid
+ DuidPtr invalid_duid(new DUID(llt, sizeof(llt) - 1));
+ y = lmptr_->getLease6(Lease::TYPE_NA, *invalid_duid, iaid, subnet_id);
+ EXPECT_FALSE(y);
+
+ // should return false - there's no such address
+ Lease6Ptr non_existing_lease(new Lease6(Lease::TYPE_NA, IOAddress(addr789),
+ duid, iaid, 100, 200,
+ subnet_id));
+ EXPECT_FALSE(lmptr_->deleteLease(non_existing_lease));
+
+ // this one should succeed
+ EXPECT_TRUE(lmptr_->deleteLease(x));
+
+ // after the lease is deleted, it should really be gone
+ x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr456));
+ EXPECT_FALSE(x);
+
+ // Reopen the lease storage to make sure that lease is gone from the
+ // persistent storage.
+ reopen(V6);
+ x = lmptr_->getLease6(Lease::TYPE_NA, IOAddress(addr456));
+ EXPECT_FALSE(x);
+}
+
+void
+GenericLeaseMgrTest::testMaxDate4() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+ Lease4Ptr lease = leases[1];
+
+ // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
+ // time too large to store.
+ lease->valid_lft_ = 24*60*60;
+ lease->cltt_ = DatabaseConnection::MAX_DB_TIME;
+
+ // Insert should throw.
+ ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+
+ // Set valid_lft_ to 0, which should make expire time small enough to
+ // store and try again.
+ lease->valid_lft_ = 0;
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testInfiniteLifeTime4() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+ Lease4Ptr lease = leases[1];
+
+ // Set valid_lft_ to infinite, cllt_ to now.
+ lease->valid_lft_ = Lease::INFINITY_LFT;
+ lease->cltt_ = time(NULL);
+
+ // Insert should not throw.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testBasicLease4() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+ leases[2]->setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
+
+ // Reopen the database to ensure that they actually got stored.
+ reopen(V4);
+
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+
+ // Check that we can't add a second lease with the same address
+ EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+ // Delete a lease, check that it's gone, and that we can't delete it
+ // a second time.
+ EXPECT_TRUE(lmptr_->deleteLease(leases[1]));
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ EXPECT_FALSE(l_returned);
+ EXPECT_FALSE(lmptr_->deleteLease(leases[1]));
+
+ // Check that the second address is still there.
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ reopen(V4);
+
+ // The deleted lease should be still gone after we re-read leases from
+ // persistent storage.
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ EXPECT_FALSE(l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+
+ // Update some FQDN data, so as we can check that update in
+ // persistent storage works as expected.
+ leases[2]->hostname_ = "memfile.example.com.";
+ leases[2]->fqdn_rev_ = true;
+
+ ASSERT_NO_THROW(lmptr_->updateLease4(leases[2]));
+
+ reopen(V4);
+
+ // The lease should be now updated in the storage.
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testBasicLease6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ leases[2]->setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
+
+ // Reopen the database to ensure that they actually got stored.
+ reopen(V6);
+
+ Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease6(leasetype6_[3], ioaddress6_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+
+ // Check that we can't add a second lease with the same address
+ EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+ // Delete a lease, check that it's gone, and that we can't delete it
+ // a second time.
+ EXPECT_TRUE(lmptr_->deleteLease(leases[1]));
+ l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ EXPECT_FALSE(l_returned);
+ EXPECT_FALSE(lmptr_->deleteLease(leases[1]));
+
+ // Check that the second address is still there.
+ l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ reopen(V6);
+
+ // The deleted lease should be still gone after we re-read leases from
+ // persistent storage.
+ l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ EXPECT_FALSE(l_returned);
+
+ // Check that the second address is still there.
+ l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ // Update some FQDN data, so as we can check that update in
+ // persistent storage works as expected.
+ leases[2]->hostname_ = "memfile.example.com.";
+ leases[2]->fqdn_rev_ = true;
+
+ ASSERT_NO_THROW(lmptr_->updateLease6(leases[2]));
+
+ reopen(V6);
+
+ // The lease should be now updated in the storage.
+ l_returned = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testMaxDate6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ Lease6Ptr lease = leases[1];
+
+ // Set valid_lft_ to 1 day, cllt_ to max time. This should make expire
+ // time too large to store.
+ lease->valid_lft_ = 24*60*60;
+ lease->cltt_ = DatabaseConnection::MAX_DB_TIME;
+
+ // Insert should throw.
+ ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+
+ // Set valid_lft_ to 0, which should make expire time small enough to
+ // store and try again.
+ lease->valid_lft_ = 0;
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+}
+
+void
+GenericLeaseMgrTest::testInfiniteLifeTime6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ Lease6Ptr lease = leases[1];
+
+ // Set valid_lft_ to infinite, cllt_ to now.
+ lease->valid_lft_ = Lease::INFINITY_LFT;
+ lease->cltt_ = time(NULL);
+
+ // Insert should not throw.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+}
+
+// Checks whether a MAC address can be stored and retrieved together with
+// a lease.
+void
+GenericLeaseMgrTest::testLease6MAC() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ HWAddrPtr hwaddr1(new HWAddr(vector<uint8_t>(6, 11), HTYPE_ETHER));
+ HWAddrPtr hwaddr2(new HWAddr(vector<uint8_t>(6, 22), HTYPE_DOCSIS));
+
+ leases[1]->hwaddr_ = hwaddr1; // Add hardware address to leases 1 and 2
+ leases[2]->hwaddr_ = hwaddr2;
+ leases[3]->hwaddr_ = HWAddrPtr(); // No hardware address for the third one
+
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
+
+ // Reopen the database to ensure that they actually got stored.
+ reopen(V6);
+
+ // First lease should have a hardware address in it
+ Lease6Ptr stored1 = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ ASSERT_TRUE(stored1);
+ ASSERT_TRUE(stored1->hwaddr_);
+ EXPECT_TRUE(*hwaddr1 == *stored1->hwaddr_);
+
+ // Second lease should have a hardware address in it
+ Lease6Ptr stored2 = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+ ASSERT_TRUE(stored2);
+ ASSERT_TRUE(stored2->hwaddr_);
+ EXPECT_TRUE(*hwaddr2 == *stored2->hwaddr_);
+
+ // Third lease should NOT have any hardware address.
+ Lease6Ptr stored3 = lmptr_->getLease6(leasetype6_[3], ioaddress6_[3]);
+ ASSERT_TRUE(stored3);
+ EXPECT_FALSE(stored3->hwaddr_);
+}
+
+// Checks whether a hardware address type can be stored and retrieved.
+void
+GenericLeaseMgrTest::testLease6HWTypeAndSource() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ HWAddrPtr hwaddr1(new HWAddr(vector<uint8_t>(6, 11), 123));
+ HWAddrPtr hwaddr2(new HWAddr(vector<uint8_t>(6, 22), 456));
+
+ // Those should use defines from Pkt::HWADDR_SOURCE_*, but let's
+ // test an uncommon value (and 0 which means unknown).
+ hwaddr1->source_ = HWAddr::HWADDR_SOURCE_RAW;
+ hwaddr2->source_ = HWAddr::HWADDR_SOURCE_DUID;
+
+ leases[1]->hwaddr_ = hwaddr1; // Add hardware address to leases 1 and 2
+ leases[2]->hwaddr_ = hwaddr2;
+ leases[3]->hwaddr_ = HWAddrPtr(); // No hardware address for the third one
+
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
+
+ // Reopen the database to ensure that they actually got stored.
+ reopen(V6);
+
+ // First lease should have a hardware address in it
+ Lease6Ptr stored1 = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ ASSERT_TRUE(stored1);
+ ASSERT_TRUE(stored1->hwaddr_);
+ EXPECT_EQ(123, stored1->hwaddr_->htype_);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_RAW, stored1->hwaddr_->source_);
+
+ // Second lease should have a hardware address in it
+ Lease6Ptr stored2 = lmptr_->getLease6(leasetype6_[2], ioaddress6_[2]);
+ ASSERT_TRUE(stored2);
+ ASSERT_TRUE(stored2->hwaddr_);
+ EXPECT_EQ(456, stored2->hwaddr_->htype_);
+ EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, stored2->hwaddr_->source_);
+
+ // Third lease should NOT have any hardware address.
+ Lease6Ptr stored3 = lmptr_->getLease6(leasetype6_[3], ioaddress6_[3]);
+ ASSERT_TRUE(stored3);
+ EXPECT_FALSE(stored3->hwaddr_);
+}
+
+void
+GenericLeaseMgrTest::testLease4InvalidHostname() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Create a dummy hostname, consisting of 255 characters.
+ leases[1]->hostname_.assign(255, 'a');
+ ASSERT_TRUE(lmptr_->addLease(leases[1]));
+
+ // The new lease must be in the database.
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ detailCompareLease(leases[1], l_returned);
+
+ // Let's delete the lease, so as we can try to add it again with
+ // invalid hostname.
+ EXPECT_TRUE(lmptr_->deleteLease(leases[1]));
+
+ // Create a hostname with 256 characters. It should not be accepted.
+ leases[1]->hostname_.assign(256, 'a');
+ EXPECT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+}
+
+/// @brief Verify that too long hostname for Lease6 is not accepted.
+void
+GenericLeaseMgrTest::testLease6InvalidHostname() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Create a dummy hostname, consisting of 255 characters.
+ leases[1]->hostname_.assign(255, 'a');
+ ASSERT_TRUE(lmptr_->addLease(leases[1]));
+
+ // The new lease must be in the database.
+ Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ detailCompareLease(leases[1], l_returned);
+
+ // Let's delete the lease, so as we can try to add it again with
+ // invalid hostname.
+ EXPECT_TRUE(lmptr_->deleteLease(leases[1]));
+
+ // Create a hostname with 256 characters. It should not be accepted.
+ leases[1]->hostname_.assign(256, 'a');
+ EXPECT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddrSize() {
+ // Create leases, although we need only one.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Now add leases with increasing hardware address size.
+ for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
+ leases[1]->hwaddr_->hwaddr_.resize(i, i);
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Collection returned =
+ lmptr_->getLease4(*leases[1]->hwaddr_);
+
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[1], *returned.begin());
+ ASSERT_TRUE(lmptr_->deleteLease(leases[1]));
+ }
+
+ // Database should not let us add one that is too big
+ // (The 42 is a random value put in each byte of the address.)
+ leases[1]->hwaddr_->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+ EXPECT_THROW(lmptr_->addLease(leases[1]), isc::db::DbOperationError);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddrSubnetId() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the hardware address of lease 1 and
+ // subnet ID of lease 1. Result should be a single lease - lease 1.
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(*leases[1]->hwaddr_,
+ leases[1]->subnet_id_);
+
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+
+ // Try for a match to the hardware address of lease 1 and the wrong
+ // subnet ID.
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(*leases[1]->hwaddr_, leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to the subnet ID of lease 1 (and lease 4) but
+ // the wrong hardware address.
+ vector<uint8_t> invalid_hwaddr(15, 0x77);
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
+ leases[1]->subnet_id_);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to an unknown hardware address and an unknown
+ // subnet ID.
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
+ leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+
+ // Add a second lease with the same values as the first and check that
+ // an attempt to access the database by these parameters throws a
+ // "multiple records" exception. (We expect there to be only one record
+ // with that combination, so getting them via getLeaseX() (as opposed
+ // to getLeaseXCollection() should throw an exception.)
+ EXPECT_TRUE(lmptr_->deleteLease(leases[2]));
+ leases[1]->addr_ = leases[2]->addr_;
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ EXPECT_THROW(returned = lmptr_->getLease4(*leases[1]->hwaddr_,
+ leases[1]->subnet_id_),
+ isc::db::MultipleRecords);
+
+}
+
+void
+GenericLeaseMgrTest::testGetLease4HWAddrSubnetIdSize() {
+ // Create leases, although we need only one.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Now add leases with increasing hardware address size and check
+ // that they can be retrieved.
+ for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
+ leases[1]->hwaddr_->hwaddr_.resize(i, i);
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ /// @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(*leases[1]->hwaddr_,
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+ ASSERT_TRUE(lmptr_->deleteLease(leases[1]));
+ }
+
+ // Database should not let us add one that is too big
+ // (The 42 is a random value put in each byte of the address.)
+ leases[1]->hwaddr_->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+ EXPECT_THROW(lmptr_->addLease(leases[1]), isc::db::DbOperationError);
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientId2() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the Client ID address of lease 1
+ Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
+
+ // Should be four leases, matching leases[1], [4], [5] and [6].
+ ASSERT_EQ(4, returned.size());
+
+ // Check the lease[5] (and only this one) has an user context.
+ size_t contexts = 0;
+ for (Lease4Collection::const_iterator i = returned.begin();
+ i != returned.end(); ++i) {
+ if ((*i)->getContext()) {
+ ++contexts;
+ EXPECT_EQ("{ \"foo\": true }", (*i)->getContext()->str());
+ }
+ }
+ EXPECT_EQ(1, contexts);
+
+ // Easiest way to check is to look at the addresses.
+ vector<string> addresses;
+ for (Lease4Collection::const_iterator i = returned.begin();
+ i != returned.end(); ++i) {
+ addresses.push_back((*i)->addr_.toText());
+ }
+ sort(addresses.begin(), addresses.end());
+ EXPECT_EQ(straddress4_[1], addresses[0]);
+ EXPECT_EQ(straddress4_[4], addresses[1]);
+ EXPECT_EQ(straddress4_[5], addresses[2]);
+ EXPECT_EQ(straddress4_[6], addresses[3]);
+
+ // Repeat test with just one expected match
+ returned = lmptr_->getLease4(*leases[3]->client_id_);
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[3], *returned.begin());
+
+ // Try to get something with invalid client ID
+ const uint8_t invalid_data[] = {0, 0, 0};
+ ClientId invalid(invalid_data, sizeof(invalid_data));
+ returned = lmptr_->getLease4(invalid);
+ EXPECT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientIdSize() {
+ // Create leases, although we need only one.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Now add leases with increasing Client ID size can be retrieved.
+ // For speed, go from 0 to 128 is steps of 16.
+ // Intermediate client_id_max is to overcome problem if
+ // ClientId::MAX_CLIENT_ID_LEN is used in an EXPECT_EQ.
+ int client_id_max = ClientId::MAX_CLIENT_ID_LEN;
+ EXPECT_EQ(255, client_id_max);
+
+ int client_id_min = ClientId::MIN_CLIENT_ID_LEN;
+ EXPECT_EQ(2, client_id_min); // See RFC2132, section 9.14
+
+ for (uint16_t i = client_id_min; i <= client_id_max; i += 16) {
+ uint8_t data = static_cast<uint8_t>(i);
+ vector<uint8_t> clientid_vec(data, data);
+ leases[1]->client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
+ ASSERT_EQ(returned.size(), 1u);
+ detailCompareLease(leases[1], *returned.begin());
+ ASSERT_TRUE(lmptr_->deleteLease(leases[1]));
+ }
+
+ // Don't bother to check client IDs longer than the maximum -
+ // these cannot be constructed, and that limitation is tested
+ // in the DUID/Client ID unit tests.
+}
+
+void
+GenericLeaseMgrTest::testGetLease4ClientIdSubnetId() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the client ID of lease 1 and
+ // subnet ID of lease 1. Result should be a single lease - lease 1.
+ Lease4Ptr returned = lmptr_->getLease4(*leases[1]->client_id_,
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+
+ // Try for a match to the client ID of lease 1 and the wrong
+ // subnet ID.
+ returned = lmptr_->getLease4(*leases[1]->client_id_,
+ leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to the subnet ID of lease 1 (and lease 4) but
+ // the wrong client ID
+ const uint8_t invalid_data[] = {0, 0, 0};
+ ClientId invalid(invalid_data, sizeof(invalid_data));
+ returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to an unknown hardware address and an unknown
+ // subnet ID.
+ returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+}
+
+void
+GenericLeaseMgrTest::testGetLeases4SubnetId() {
+ // Get the leases to be used for the test and add to the database.
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // There should be exactly two leases for the subnet id that the second
+ // lease belongs to.
+ Lease4Collection returned = lmptr_->getLeases4(leases[1]->subnet_id_);
+ ASSERT_EQ(2, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases4Hostname() {
+ // Get the leases to be used for the test and add to the database.
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // There should be no lease for hostname foobar.
+ Lease4Collection returned = lmptr_->getLeases4(string("foobar"));
+ EXPECT_TRUE(returned.empty());
+
+ // There should be exactly 4 leases for the hostname of the second lease.
+ ASSERT_FALSE(leases[1]->hostname_.empty());
+ returned = lmptr_->getLeases4(leases[1]->hostname_);
+ EXPECT_EQ(4, returned.size());
+
+ // And 3 for the forth lease.
+ ASSERT_FALSE(leases[3]->hostname_.empty());
+ returned = lmptr_->getLeases4(leases[3]->hostname_);
+ EXPECT_EQ(3, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases4() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // All leases should be returned.
+ Lease4Collection returned = lmptr_->getLeases4();
+ ASSERT_EQ(leases.size(), returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases4Paged() {
+ // Get the leases to be used for the test and add to the database.
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ Lease4Collection all_leases;
+
+ IOAddress last_address = IOAddress("0.0.0.0");
+ for (auto i = 0; i < 4; ++i) {
+ Lease4Collection page = lmptr_->getLeases4(last_address, LeasePageSize(3));
+
+ // Collect leases in a common structure. They may be out of order.
+ for (Lease4Ptr lease : page) {
+ all_leases.push_back(lease);
+ }
+
+ // Empty page means there are no more leases.
+ if (page.empty()) {
+ break;
+
+ } else {
+ // Record last returned address because it is going to be used
+ // as an argument for the next call.
+ last_address = page[page.size() - 1]->addr_;
+ }
+ }
+
+ // Make sure that we got exactly the number of leases that we earlier
+ // stored in the database.
+ EXPECT_EQ(leases.size(), all_leases.size());
+
+ // Make sure that all leases that we stored in the lease database
+ // have been retrieved.
+ for (Lease4Ptr lease : leases) {
+ bool found = false;
+ for (Lease4Ptr returned_lease : all_leases) {
+ if (lease->addr_ == returned_lease->addr_) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found) << "lease for address " << lease->addr_.toText()
+ << " was not returned in any of the pages";
+ }
+
+ boost::scoped_ptr<LeasePageSize> lease_page_size;
+
+ // The maximum allowed value for the limit is max for uint32_t.
+ size_t oor = static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 1;
+ EXPECT_THROW(lease_page_size.reset(new LeasePageSize(oor)), OutOfRange);
+
+ // Zero page size is illegal too.
+ EXPECT_THROW(lease_page_size.reset(new LeasePageSize(0)), OutOfRange);
+
+ // Only IPv4 address can be used.
+ EXPECT_THROW(lmptr_->getLeases4(IOAddress("2001:db8::1"), LeasePageSize(3)),
+ InvalidAddressFamily);
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6SubnetId() {
+ // Get the leases to be used for the test and add to the database.
+ vector<Lease6Ptr> leases = createLeases6();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // There should be exactly two leases for the subnet id that the second
+ // lease belongs to.
+ Lease6Collection returned = lmptr_->getLeases6(leases[1]->subnet_id_);
+ EXPECT_EQ(2, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6Hostname() {
+ // Get the leases to be used for the test and add to the database.
+ vector<Lease6Ptr> leases = createLeases6();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // There should be no lease for hostname foobar.
+ Lease6Collection returned = lmptr_->getLeases6(string("foobar"));
+ EXPECT_TRUE(returned.empty());
+
+ // There should be exactly 4 leases for the hostname of the second lease.
+ ASSERT_FALSE(leases[1]->hostname_.empty());
+ returned = lmptr_->getLeases6(leases[1]->hostname_);
+ EXPECT_EQ(4, returned.size());
+
+ // One for the fifth lease.
+ ASSERT_FALSE(leases[4]->hostname_.empty());
+ returned = lmptr_->getLeases6(leases[4]->hostname_);
+ EXPECT_EQ(1, returned.size());
+
+ // And 3 for the sixth lease.
+ ASSERT_FALSE(leases[5]->hostname_.empty());
+ returned = lmptr_->getLeases6(leases[5]->hostname_);
+ EXPECT_EQ(3, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease6Ptr> leases = createLeases6();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // All leases should be returned.
+ Lease6Collection returned = lmptr_->getLeases6();
+ ASSERT_EQ(leases.size(), returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6Paged() {
+ // Get the leases to be used for the test and add to the database.
+ vector<Lease6Ptr> leases = createLeases6();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ Lease6Collection all_leases;
+
+ IOAddress last_address = IOAddress::IPV6_ZERO_ADDRESS();
+ for (auto i = 0; i < 4; ++i) {
+ Lease6Collection page = lmptr_->getLeases6(last_address, LeasePageSize(3));
+
+ // Collect leases in a common structure. They may be out of order.
+ for (Lease6Ptr lease : page) {
+ all_leases.push_back(lease);
+ }
+
+ // Empty page means there are no more leases.
+ if (page.empty()) {
+ break;
+
+ } else {
+ // Record last returned address because it is going to be used
+ // as an argument for the next call.
+ last_address = page[page.size() - 1]->addr_;
+ }
+ }
+
+ // Make sure that we got exactly the number of leases that we earlier
+ // stored in the database.
+ EXPECT_EQ(leases.size(), all_leases.size());
+
+ // Make sure that all leases that we stored in the lease database
+ // have been retrieved.
+ for (Lease6Ptr lease : leases) {
+ bool found = false;
+ for (Lease6Ptr returned_lease : all_leases) {
+ if (lease->addr_ == returned_lease->addr_) {
+ found = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(found) << "lease for address " << lease->addr_.toText()
+ << " was not returned in any of the pages";
+ }
+
+ // Only IPv6 address can be used.
+ EXPECT_THROW(lmptr_->getLeases6(IOAddress("192.0.2.0"), LeasePageSize(3)),
+ InvalidAddressFamily);
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6DuidIaid() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ ASSERT_LE(6, leases.size()); // Expect to access leases 0 through 5
+
+ // Add them to the database
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the DUID and IAID of lease[1].
+ Lease6Collection returned = lmptr_->getLeases6(leasetype6_[1],
+ *leases[1]->duid_,
+ leases[1]->iaid_);
+
+ // Should be two leases, matching leases[1] and [4].
+ ASSERT_EQ(2, returned.size());
+
+ // Easiest way to check is to look at the addresses.
+ vector<string> addresses;
+ for (Lease6Collection::const_iterator i = returned.begin();
+ i != returned.end(); ++i) {
+ addresses.push_back((*i)->addr_.toText());
+ }
+ sort(addresses.begin(), addresses.end());
+ EXPECT_EQ(straddress6_[1], addresses[0]);
+ EXPECT_EQ(straddress6_[4], addresses[1]);
+
+ // Check that nothing is returned when either the IAID or DUID match
+ // nothing.
+ returned = lmptr_->getLeases6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_ + 1);
+ EXPECT_EQ(0, returned.size());
+
+ // Alter the leases[1] DUID to match nothing in the database.
+ vector<uint8_t> duid_vector = leases[1]->duid_->getDuid();
+ ++duid_vector[0];
+ DUID new_duid(duid_vector);
+ returned = lmptr_->getLeases6(leasetype6_[1], new_duid, leases[1]->iaid_);
+ EXPECT_EQ(0, returned.size());
+}
+
+void
+GenericLeaseMgrTest::testGetLeases6DuidSize() {
+ // Create leases, although we need only one.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Now add leases with increasing DUID size can be retrieved.
+ // For speed, go from 0 to 128 is steps of 16.
+ int duid_max = DUID::MAX_DUID_LEN;
+ EXPECT_EQ(130, duid_max);
+
+ int duid_min = DUID::MIN_DUID_LEN;
+ EXPECT_EQ(3, duid_min);
+
+ for (uint8_t i = duid_min; i <= duid_max; i += 16) {
+ vector<uint8_t> duid_vec(i, i);
+ leases[1]->duid_.reset(new DUID(duid_vec));
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease6Collection returned = lmptr_->getLeases6(leasetype6_[1],
+ *leases[1]->duid_,
+ leases[1]->iaid_);
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[1], *returned.begin());
+ ASSERT_TRUE(lmptr_->deleteLease(leases[1]));
+ }
+
+ // Don't bother to check DUIDs longer than the maximum - these cannot be
+ // constructed, and that limitation is tested in the DUID/Client ID unit
+ // tests.
+
+}
+
+void
+GenericLeaseMgrTest::testLease6LeaseTypeCheck() {
+ Lease6Ptr empty_lease(new Lease6());
+
+ DuidPtr duid(new DUID(vector<uint8_t>(8, 0x77)));
+
+ empty_lease->iaid_ = 142;
+ empty_lease->duid_ = DuidPtr(new DUID(*duid));
+ empty_lease->subnet_id_ = 23;
+ empty_lease->preferred_lft_ = 100;
+ empty_lease->valid_lft_ = 100;
+ empty_lease->cltt_ = 100;
+ empty_lease->fqdn_fwd_ = true;
+ empty_lease->fqdn_rev_ = true;
+ empty_lease->hostname_ = "myhost.example.com.";
+ empty_lease->prefixlen_ = 128;
+
+ // Make Two leases per lease type, all with the same DUID, IAID but
+ // alternate the subnet_ids.
+ vector<Lease6Ptr> leases;
+ for (int i = 0; i < 6; ++i) {
+ if (i > 3) {
+ empty_lease->prefixlen_ = 48;
+ } else {
+ empty_lease->prefixlen_ = 128;
+ }
+ Lease6Ptr lease(new Lease6(*empty_lease));
+ lease->type_ = leasetype6_[i / 2];
+ lease->addr_ = IOAddress(straddress6_[(i / 2) + (i % 2) * 3]);
+ lease->subnet_id_ += (i % 2);
+ leases.push_back(lease);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ }
+
+ // Verify getting a single lease by type and address.
+ for (int i = 0; i < 6; ++i) {
+ // Look for exact match for each lease type.
+ Lease6Ptr returned = lmptr_->getLease6(leasetype6_[i / 2],
+ leases[i]->addr_);
+ // We should match one per lease type.
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(*returned == *leases[i]);
+
+ // Same address but wrong lease type, should not match.
+ returned = lmptr_->getLease6(leasetype6_[i / 2 + 1], leases[i]->addr_);
+ ASSERT_FALSE(returned);
+ }
+
+ // Verify getting a collection of leases by type, DUID, and IAID.
+ // Iterate over the lease types, asking for leases based on
+ // lease type, DUID, and IAID.
+ for (size_t i = 0; i < 3; ++i) {
+ Lease6Collection returned = lmptr_->getLeases6(leasetype6_[i], *duid, 142);
+
+ auto compare = [](const Lease6Ptr& left, const Lease6Ptr& right) {
+ return (left->addr_ < right->addr_);
+ };
+ std::sort(returned.begin(), returned.end(), compare);
+
+ // We should match two per lease type.
+ ASSERT_EQ(2, returned.size());
+
+ // Collection order returned is not guaranteed.
+ // Easiest way to check is to look at the addresses.
+ vector<string> addresses;
+ for (Lease6Collection::const_iterator it = returned.begin();
+ it != returned.end(); ++it) {
+ addresses.push_back((*it)->addr_.toText());
+ }
+
+ auto compare_addr = [](const string& left, const string& right) {
+ return (IOAddress(left) < IOAddress(right));
+ };
+ sort(addresses.begin(), addresses.end(), compare_addr);
+
+ // Now verify that the lease addresses match.
+ EXPECT_EQ(addresses[0], leases[(i * 2)]->addr_.toText());
+ EXPECT_EQ(addresses[1], leases[(i * 2 + 1)]->addr_.toText());
+ }
+
+ // Verify getting a collection of leases by type, DUID, IAID, and subnet id.
+ // Iterate over the lease types, asking for leases based on
+ // lease type, DUID, IAID, and subnet_id.
+ for (int i = 0; i < 3; ++i) {
+ Lease6Collection returned = lmptr_->getLeases6(leasetype6_[i], *duid, 142, 23);
+ // We should match one per lease type.
+ ASSERT_EQ(1, returned.size());
+ EXPECT_TRUE(*(returned[0]) == *leases[i * 2]);
+ }
+
+ // Verify getting a single lease by type, duid, iad, and subnet id.
+ for (int i = 0; i < 6; ++i) {
+ Lease6Ptr returned = lmptr_->getLease6(leasetype6_[i / 2], *duid, 142, (23 + (i % 2)));
+ // We should match one per lease type.
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(*returned == *leases[i]);
+ }
+}
+
+void
+GenericLeaseMgrTest::testLease6LargeIaidCheck() {
+
+ DuidPtr duid(new DUID(vector<uint8_t>(8, 0x77)));
+ IOAddress addr(std::string("2001:db8:1::111"));
+ SubnetID subnet_id = 8; // random number
+
+ // Use a value we know is larger than 32-bit signed max.
+ uint32_t large_iaid = 0xFFFFFFFE;
+
+ // We should be able to add with no problems.
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid, large_iaid,
+ 100, 200, subnet_id));
+ ASSERT_TRUE(lmptr_->addLease(lease));
+
+ // Sanity check that we added it.
+ Lease6Ptr found_lease = lmptr_->getLease6(Lease::TYPE_NA, addr);
+ ASSERT_TRUE(found_lease);
+ EXPECT_TRUE(*found_lease == *lease);
+
+ // Verify getLease6() duid/iaid finds it.
+ found_lease = lmptr_->getLease6(Lease::TYPE_NA, *duid, large_iaid, subnet_id);
+ ASSERT_TRUE(found_lease);
+ EXPECT_TRUE(*found_lease == *lease);
+
+ // Verify getLeases6() duid/iaid finds it.
+ Lease6Collection found_leases = lmptr_->getLeases6(Lease::TYPE_NA,
+ *duid, large_iaid);
+ // We should match the lease.
+ ASSERT_EQ(1, found_leases.size());
+ EXPECT_TRUE(*(found_leases[0]) == *lease);
+}
+
+void
+GenericLeaseMgrTest::testGetLease6DuidIaidSubnetId() {
+ // Get the leases to be used for the test and add them to the database.
+ vector<Lease6Ptr> leases = createLeases6();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the DUID and IAID of lease[1].
+ Lease6Ptr returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_,
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ EXPECT_TRUE(*returned == *leases[1]);
+
+ // Modify each of the three parameters (DUID, IAID, Subnet ID) and
+ // check that nothing is returned.
+ returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_ + 1, leases[1]->subnet_id_);
+ EXPECT_FALSE(returned);
+
+ returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_, leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+
+ // Alter the leases[1] DUID to match nothing in the database.
+ vector<uint8_t> duid_vector = leases[1]->duid_->getDuid();
+ ++duid_vector[0];
+ DUID new_duid(duid_vector);
+ returned = lmptr_->getLease6(leasetype6_[1], new_duid, leases[1]->iaid_,
+ leases[1]->subnet_id_);
+ EXPECT_FALSE(returned);
+}
+
+/// @brief verifies getLeases6(DUID)
+void
+GenericLeaseMgrTest::testGetLeases6Duid() {
+ //add leases
+ IOAddress addr1(std::string("2001:db8:1::"));
+ IOAddress addr2(std::string("2001:db8:2::"));
+ IOAddress addr3(std::string("2001:db8:3::"));
+
+ DuidPtr duid1(new DUID({0, 1, 1, 1, 1, 1, 1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}));
+ DuidPtr duid2(new DUID({0, 2, 2, 2, 2, 2, 2, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}));
+ DuidPtr duid3(new DUID({0, 3, 3, 3, 3, 3, 3, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}));
+ DuidPtr duid4(new DUID({0, 4, 4, 4, 4, 4, 4, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}));
+
+ uint32_t iaid = 7; // random number
+
+ SubnetID subnet_id = 8; // random number
+
+ Lease6Ptr lease1(new Lease6(Lease::TYPE_NA, addr1, duid1, iaid, 100, 200,
+ subnet_id));
+ Lease6Ptr lease2(new Lease6(Lease::TYPE_NA, addr2, duid2, iaid, 100, 200,
+ subnet_id));
+ Lease6Ptr lease3(new Lease6(Lease::TYPE_PD, addr3, duid3, iaid, 100, 200,
+ subnet_id, HWAddrPtr(), 64));
+
+ EXPECT_TRUE(lmptr_->addLease(lease1));
+ EXPECT_TRUE(lmptr_->addLease(lease2));
+ EXPECT_TRUE(lmptr_->addLease(lease3));
+
+ Lease6Collection returned1 = lmptr_->getLeases6(*(lease1->duid_));
+ Lease6Collection returned2 = lmptr_->getLeases6(*(lease2->duid_));
+ Lease6Collection returned3 = lmptr_->getLeases6(*(lease3->duid_));
+
+ //verify if the returned lease mathces
+ ASSERT_EQ(returned1.size(), 1);
+ ASSERT_EQ(returned2.size(), 1);
+ ASSERT_EQ(returned3.size(), 1);
+
+ //verify that the returned lease are same
+ EXPECT_TRUE(returned1[0]->addr_ == lease1->addr_);
+ EXPECT_TRUE(returned2[0]->addr_ == lease2->addr_);
+ EXPECT_TRUE(returned3[0]->addr_ == lease3->addr_);
+
+ //now verify we return empty for a lease that has not been stored
+ returned3 = lmptr_->getLeases6(*duid4);
+ EXPECT_TRUE(returned3.empty());
+
+ //clean up
+ ASSERT_TRUE(lmptr_->deleteLease(lease1));
+ ASSERT_TRUE(lmptr_->deleteLease(lease2));
+ ASSERT_TRUE(lmptr_->deleteLease(lease3));
+
+ //now verify we return empty for a lease that has not been stored
+ returned3 = lmptr_->getLeases6(*duid4);
+ EXPECT_TRUE(returned3.empty());
+}
+
+/// @brief Checks that getLease6() works with different DUID sizes
+void
+GenericLeaseMgrTest::testGetLease6DuidIaidSubnetIdSize() {
+
+ // Create leases, although we need only one.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Now add leases with increasing DUID size can be retrieved.
+ // For speed, go from 0 to 128 is steps of 16.
+ int duid_max = DUID::MAX_DUID_LEN;
+ EXPECT_EQ(130, duid_max);
+
+ int duid_min = DUID::MIN_DUID_LEN;
+ EXPECT_EQ(3, duid_min);
+
+ for (uint8_t i = duid_min; i <= duid_max; i += 16) {
+ vector<uint8_t> duid_vec(i, i);
+ leases[1]->duid_.reset(new DUID(duid_vec));
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease6Ptr returned = lmptr_->getLease6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_,
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+ ASSERT_TRUE(lmptr_->deleteLease(leases[1]));
+ }
+
+ // Don't bother to check DUIDs longer than the maximum - these cannot be
+ // constructed, and that limitation is tested in the DUID/Client ID unit
+ // tests.
+}
+
+void
+GenericLeaseMgrTest::testUpdateLease4() {
+ // Get the leases to be used for the test and add them to the database.
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+ lmptr_->commit();
+
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->subnet_id_;
+ leases[1]->valid_lft_ *= 2;
+ leases[1]->hostname_ = "modified.hostname.";
+ leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
+ leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
+ leases[1]->setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ lmptr_->updateLease4(leases[1]);
+
+ // ... and check what is returned is what is expected.
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Alter the lease again and check.
+ ++leases[1]->subnet_id_;
+ leases[1]->cltt_ += 6;
+ leases[1]->setContext(Element::fromJSON("{ \"foo\": \"bar\" }"));
+ lmptr_->updateLease4(leases[1]);
+
+ // Explicitly clear the returned pointer before getting new data to ensure
+ // that the new data is returned.
+ l_returned.reset();
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Check we can do an update without changing data.
+ lmptr_->updateLease4(leases[1]);
+ l_returned.reset();
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Try to update the lease with the too long hostname.
+ leases[1]->hostname_.assign(256, 'a');
+ EXPECT_THROW(lmptr_->updateLease4(leases[1]), isc::db::DbOperationError);
+
+ // Try updating a lease not in the database.
+ ASSERT_TRUE(lmptr_->deleteLease(leases[2]));
+ EXPECT_THROW(lmptr_->updateLease4(leases[2]), isc::dhcp::NoSuchLease);
+}
+
+void
+GenericLeaseMgrTest::testConcurrentUpdateLease4() {
+ // Get the leases to be used for the test and add them to the database.
+ vector<Lease4Ptr> leases = createLeases4();
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+ lmptr_->commit();
+
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Save initial lease to be used for concurrent update
+ Lease4Ptr initialLease = boost::make_shared<Lease4>(*leases[1]);
+ detailCompareLease(leases[1], initialLease);
+
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->subnet_id_;
+ leases[1]->valid_lft_ *= 2;
+ leases[1]->hostname_ = "modified.hostname.";
+ leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
+ leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
+ leases[1]->setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ lmptr_->updateLease4(leases[1]);
+
+ // ... and check what is returned is what is expected.
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Concurrently updating lease should fail
+ EXPECT_THROW(lmptr_->updateLease4(initialLease), isc::dhcp::NoSuchLease);
+}
+
+void
+GenericLeaseMgrTest::testUpdateLease6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ ASSERT_LE(3, leases.size()); // Expect to access leases 0 through 2
+
+ // Add a lease to the database and check that the lease is there.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ lmptr_->commit();
+
+ Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->iaid_;
+ leases[1]->type_ = Lease::TYPE_PD;
+ leases[1]->prefixlen_ = 93;
+ leases[1]->valid_lft_ *= 2;
+ leases[1]->hostname_ = "modified.hostname.v6.";
+ leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
+ leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
+ leases[1]->setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ lmptr_->updateLease6(leases[1]);
+
+ // ... and check what is returned is what is expected.
+ l_returned.reset();
+ l_returned = lmptr_->getLease6(Lease::TYPE_PD, ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Alter the lease again and check.
+ ++leases[1]->iaid_;
+ leases[1]->type_ = Lease::TYPE_TA;
+ leases[1]->cltt_ += 6;
+ leases[1]->prefixlen_ = 128;
+ leases[1]->setContext(Element::fromJSON("{ \"foo\": \"bar\" }"));
+ lmptr_->updateLease6(leases[1]);
+
+ l_returned.reset();
+ l_returned = lmptr_->getLease6(Lease::TYPE_TA, ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Check we can do an update without changing data.
+ lmptr_->updateLease6(leases[1]);
+ l_returned.reset();
+ l_returned = lmptr_->getLease6(Lease::TYPE_TA, ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Try to update the lease with the too long hostname.
+ leases[1]->hostname_.assign(256, 'a');
+ EXPECT_THROW(lmptr_->updateLease6(leases[1]), isc::db::DbOperationError);
+
+ // Try updating a lease not in the database.
+ EXPECT_THROW(lmptr_->updateLease6(leases[2]), isc::dhcp::NoSuchLease);
+}
+
+void
+GenericLeaseMgrTest::testConcurrentUpdateLease6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ ASSERT_LE(3, leases.size()); // Expect to access leases 0 through 2
+
+ // Add a lease to the database and check that the lease is there.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ lmptr_->commit();
+
+ Lease6Ptr l_returned = lmptr_->getLease6(leasetype6_[1], ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Save initial lease to be used for concurrent update
+ Lease6Ptr initialLease = boost::make_shared<Lease6>(*leases[1]);
+ detailCompareLease(leases[1], initialLease);
+
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->iaid_;
+ leases[1]->type_ = Lease::TYPE_PD;
+ leases[1]->valid_lft_ *= 2;
+ leases[1]->hostname_ = "modified.hostname.v6.";
+ leases[1]->fqdn_fwd_ = !leases[1]->fqdn_fwd_;
+ leases[1]->fqdn_rev_ = !leases[1]->fqdn_rev_;;
+ leases[1]->setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ lmptr_->updateLease6(leases[1]);
+
+ // ... and check what is returned is what is expected.
+ l_returned.reset();
+ l_returned = lmptr_->getLease6(Lease::TYPE_PD, ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Concurrently updating lease should fail
+ EXPECT_THROW(lmptr_->updateLease6(initialLease), isc::dhcp::NoSuchLease);
+}
+
+void
+GenericLeaseMgrTest::testRecreateLease4() {
+ // Create a lease.
+ std::vector<Lease4Ptr> leases = createLeases4();
+ // Copy the lease so as we can freely modify it.
+ Lease4Ptr lease(new Lease4(*leases[0]));
+
+ // Add a lease.
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ lmptr_->commit();
+
+ // Check that the lease has been successfully added.
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[0]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(lease, l_returned);
+
+ // Delete a lease, check that it's gone.
+ EXPECT_TRUE(lmptr_->deleteLease(leases[0]));
+ EXPECT_FALSE(lmptr_->getLease4(ioaddress4_[0]));
+
+ // Modify the copy of the lease. Increasing values or negating them ensures
+ // that they are really modified, because we will never get the same values.
+ ++lease->subnet_id_;
+ ++lease->valid_lft_;
+ lease->fqdn_fwd_ = !lease->fqdn_fwd_;
+ // Make sure that the lease has been really modified.
+ ASSERT_NE(*lease, *leases[0]);
+ // Add the updated lease.
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ lmptr_->commit();
+
+ // Reopen the lease database, so as the lease is re-read.
+ reopen(V4);
+
+ // The lease in the database should be modified.
+ l_returned = lmptr_->getLease4(ioaddress4_[0]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(lease, l_returned);
+}
+
+void
+GenericLeaseMgrTest::testRecreateLease6() {
+ // Create a lease.
+ std::vector<Lease6Ptr> leases = createLeases6();
+ // Copy the lease so as we can freely modify it.
+ Lease6Ptr lease(new Lease6(*leases[0]));
+
+ // Add a lease.
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ lmptr_->commit();
+
+ // Check that the lease has been successfully added.
+ Lease6Ptr l_returned = lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(lease, l_returned);
+
+ // Delete a lease, check that it's gone.
+ EXPECT_TRUE(lmptr_->deleteLease(leases[0]));
+ EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]));
+
+ // Modify the copy of the lease. Increasing values or negating them ensures
+ // that they are really modified, because we will never get the same values.
+ ++lease->subnet_id_;
+ ++lease->valid_lft_;
+ lease->fqdn_fwd_ = !lease->fqdn_fwd_;
+ // Make sure that the lease has been really modified.
+ ASSERT_NE(*lease, *leases[0]);
+ // Add the updated lease.
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ lmptr_->commit();
+
+ // Reopen the lease database, so as the lease is re-read.
+ reopen(V6);
+
+ // The lease in the database should be modified.
+ l_returned = lmptr_->getLease6(Lease::TYPE_NA, ioaddress6_[0]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(lease, l_returned);
+}
+
+void
+GenericLeaseMgrTest::testNullDuid() {
+ // Create leases, although we need only one.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Set DUID to empty pointer.
+ leases[1]->duid_.reset();
+
+ // Insert should throw.
+ ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+}
+
+void
+GenericLeaseMgrTest::testVersion(int major, int minor) {
+ EXPECT_EQ(major, lmptr_->getVersion().first);
+ EXPECT_EQ(minor, lmptr_->getVersion().second);
+}
+
+void
+GenericLeaseMgrTest::testGetExpiredLeases4() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+ // Make sure we have at least 6 leases there.
+ ASSERT_GE(leases.size(), 6);
+
+ // Use the same current time for all leases.
+ time_t current_time = time(NULL);
+
+ // Add them to the database
+ for (size_t i = 0; i < leases.size(); ++i) {
+ // Mark every other lease as expired.
+ if (i % 2 == 0) {
+ // Set client last transmission time to the value older than the
+ // valid lifetime to make it expired. The expiration time also
+ // depends on the lease index, so as we can later check that the
+ // leases are ordered by the expiration time.
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+
+ } else {
+ // Set current time as cltt for remaining leases. These leases are
+ // not expired.
+ leases[i]->cltt_ = current_time;
+ }
+ ASSERT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Retrieve at most 1000 expired leases.
+ Lease4Collection expired_leases;
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 1000));
+ // Leases with even indexes should be returned as expired.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ // The expired leases should be returned from the most to least expired.
+ // This matches the reverse order to which they have been added.
+ for (Lease4Collection::reverse_iterator lease = expired_leases.rbegin();
+ lease != expired_leases.rend(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.rbegin(), lease));
+ // Multiple current index by two, because only leases with even indexes
+ // should have been returned.
+ ASSERT_LE(2 * index, leases.size());
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+
+ // Update current time for the next test.
+ current_time = time(NULL);
+ // Also, remove expired leases collected during the previous test.
+ expired_leases.clear();
+
+ // This time let's reverse the expiration time and see if they will be returned
+ // in the correct order.
+ for (int i = 0; i < leases.size(); ++i) {
+ // Update the time of expired leases with even indexes.
+ if (i % 2 == 0) {
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+ } else {
+ // Make sure remaining leases remain unexpired.
+ leases[i]->cltt_ = current_time + 100;
+ }
+ ASSERT_NO_THROW(lmptr_->updateLease4(leases[i]));
+ }
+
+ // Retrieve expired leases again. The limit of 0 means return all expired
+ // leases.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0));
+ // The same leases should be returned.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ // This time leases should be returned in the non-reverse order.
+ for (Lease4Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ ASSERT_LE(2 * index, leases.size());
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+
+ // Remember expired leases returned.
+ std::vector<Lease4Ptr> saved_expired_leases = expired_leases;
+
+ // Remove expired leases again.
+ expired_leases.clear();
+
+ // Limit the number of leases to be returned to 2.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 2));
+
+ // Make sure we have exactly 2 leases returned.
+ ASSERT_EQ(2, expired_leases.size());
+
+ // Test that most expired leases have been returned.
+ for (Lease4Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ ASSERT_LE(2 * index, leases.size());
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+
+ // Mark every other expired lease as reclaimed.
+ for (int i = 0; i < saved_expired_leases.size(); ++i) {
+ if (i % 2 != 0) {
+ saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ }
+ ASSERT_NO_THROW(lmptr_->updateLease4(saved_expired_leases[i]));
+ }
+
+ expired_leases.clear();
+
+ // This the returned leases should exclude reclaimed ones. So the number
+ // of returned leases should be roughly half of the expired leases.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0));
+ ASSERT_EQ(static_cast<size_t>(saved_expired_leases.size() / 2),
+ expired_leases.size());
+
+ // Make sure that returned leases are those that are not reclaimed, i.e.
+ // those that have even index.
+ for (Lease4Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_);
+ }
+}
+
+void
+GenericLeaseMgrTest::testGetExpiredLeases6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ // Make sure we have at least 6 leases there.
+ ASSERT_GE(leases.size(), 6);
+
+ // Use the same current time for all leases.
+ time_t current_time = time(NULL);
+
+ // Add them to the database
+ for (size_t i = 0; i < leases.size(); ++i) {
+ // Mark every other lease as expired.
+ if (i % 2 == 0) {
+ // Set client last transmission time to the value older than the
+ // valid lifetime to make it expired. The expiration time also
+ // depends on the lease index, so as we can later check that the
+ // leases are ordered by the expiration time.
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+
+ } else {
+ // Set current time as cltt for remaining leases. These leases are
+ // not expired.
+ leases[i]->cltt_ = current_time;
+ }
+ ASSERT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Retrieve at most 1000 expired leases.
+ Lease6Collection expired_leases;
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 1000));
+ // Leases with even indexes should be returned as expired.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ // The expired leases should be returned from the most to least expired.
+ // This matches the reverse order to which they have been added.
+ for (Lease6Collection::reverse_iterator lease = expired_leases.rbegin();
+ lease != expired_leases.rend(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.rbegin(), lease));
+ // Multiple current index by two, because only leases with even indexes
+ // should have been returned.
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+
+ // Update current time for the next test.
+ current_time = time(NULL);
+ // Also, remove expired leases collected during the previous test.
+ expired_leases.clear();
+
+ // This time let's reverse the expiration time and see if they will be returned
+ // in the correct order.
+ for (int i = 0; i < leases.size(); ++i) {
+ // Update the time of expired leases with even indexes.
+ if (i % 2 == 0) {
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+
+ } else {
+ // Make sure remaining leases remain unexpired.
+ leases[i]->cltt_ = current_time + 100;
+ }
+ ASSERT_NO_THROW(lmptr_->updateLease6(leases[i]));
+ }
+
+ // Retrieve expired leases again. The limit of 0 means return all expired
+ // leases.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0));
+ // The same leases should be returned.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ // This time leases should be returned in the non-reverse order.
+ for (Lease6Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+
+ // Remember expired leases returned.
+ std::vector<Lease6Ptr> saved_expired_leases = expired_leases;
+
+ // Remove expired leases again.
+ expired_leases.clear();
+
+ // Limit the number of leases to be returned to 2.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 2));
+
+ // Make sure we have exactly 2 leases returned.
+ ASSERT_EQ(2, expired_leases.size());
+
+ // Test that most expired leases have been returned.
+ for (Lease6Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+
+ // Mark every other expired lease as reclaimed.
+ for (int i = 0; i < saved_expired_leases.size(); ++i) {
+ if (i % 2 != 0) {
+ saved_expired_leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ }
+ ASSERT_NO_THROW(lmptr_->updateLease6(saved_expired_leases[i]));
+ }
+
+ expired_leases.clear();
+
+ // This the returned leases should exclude reclaimed ones. So the number
+ // of returned leases should be roughly half of the expired leases.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0));
+ ASSERT_EQ(static_cast<size_t>(saved_expired_leases.size() / 2),
+ expired_leases.size());
+
+ // Make sure that returned leases are those that are not reclaimed, i.e.
+ // those that have even index.
+ for (Lease6Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(saved_expired_leases[2 * index]->addr_, (*lease)->addr_);
+ }
+}
+
+void
+GenericLeaseMgrTest::testInfiniteAreNotExpired4() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+ Lease4Ptr lease = leases[1];
+
+ // Set valid_lft_ to infinite. Leave the small cltt even it won't happen
+ // in the real world to catch more possible issues.
+ lease->valid_lft_ = Lease::INFINITY_LFT;
+
+ // Add it to the database.
+ ASSERT_TRUE(lmptr_->addLease(leases[1]));
+
+ // Retrieve at most 10 expired leases.
+ Lease4Collection expired_leases;
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 10));
+
+ // No lease should be returned.
+ EXPECT_EQ(0, expired_leases.size());
+}
+
+void
+GenericLeaseMgrTest::testInfiniteAreNotExpired6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ Lease6Ptr lease = leases[1];
+
+ // Set valid_lft_ to infinite. Leave the small cltt even it won't happen
+ // in the real world to catch more possible issues.
+ lease->valid_lft_ = Lease::INFINITY_LFT;
+
+ // Add it to the database.
+ ASSERT_TRUE(lmptr_->addLease(leases[1]));
+
+ // Retrieve at most 10 expired leases.
+ Lease6Collection expired_leases;
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 10));
+
+ // No lease should be returned.
+ EXPECT_EQ(0, expired_leases.size());
+}
+
+void
+GenericLeaseMgrTest::testDeleteExpiredReclaimedLeases4() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+ // Make sure we have at least 6 leases there.
+ ASSERT_GE(leases.size(), 6);
+
+ time_t current_time = time(NULL);
+
+ // Add them to the database
+ for (size_t i = 0; i < leases.size(); ++i) {
+ // Mark every other lease as expired.
+ if (i % 2 == 0) {
+ // Set client last transmission time to the value older than the
+ // valid lifetime to make it expired. We also substract the value
+ // of 10, 20, 30, 40 etc, depending on the lease index. This
+ // simulates different expiration times for various leases.
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - i * 10;
+ // Set reclaimed state.
+ leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+
+ } else {
+ // Other leases are left as not expired - client last transmission
+ // time set to current time.
+ leases[i]->cltt_ = current_time;
+ }
+ ASSERT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Keep reclaimed lease for 15 seconds after expiration.
+ const uint32_t lease_affinity_time = 15;
+
+ // Delete expired and reclaimed leases which have expired earlier than
+ // 15 seconds ago. This should affect leases with index 2, 3, 4 etc.
+ uint64_t deleted_num;
+ uint64_t should_delete_num = 0;
+ ASSERT_NO_THROW(
+ deleted_num = lmptr_->deleteExpiredReclaimedLeases4(lease_affinity_time)
+ );
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ // Obtain lease from the server.
+ Lease4Ptr lease = lmptr_->getLease4(leases[i]->addr_);
+
+ // If the lease is reclaimed and the expiration time passed more than
+ // 15 seconds ago, the lease should have been deleted.
+ if (leases[i]->stateExpiredReclaimed() &&
+ ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+ EXPECT_FALSE(lease) << "The following lease should have been"
+ " deleted: " << leases[i]->toText();
+ ++should_delete_num;
+ } else {
+ // If the lease is not reclaimed or it has expired less than
+ // 15 seconds ago, the lease should still be there.
+ EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+ " deleted: " << leases[i]->toText();
+ }
+ }
+
+ // Check that the number of leases deleted is correct.
+ EXPECT_EQ(deleted_num, should_delete_num);
+
+ // Make sure we can make another attempt, when there are no more leases
+ // to be deleted.
+ ASSERT_NO_THROW(
+ deleted_num = lmptr_->deleteExpiredReclaimedLeases4(lease_affinity_time)
+ );
+ // No lease should have been deleted.
+ EXPECT_EQ(0, deleted_num);
+
+ // Reopen the database. This to ensure that the leases have been deleted
+ // from the persistent storage.
+ reopen(V4);
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ /// @todo Leases with empty HW address are dropped by the memfile
+ /// backend. We will have to reevaluate if this is right behavior
+ /// of the backend when client identifier is present.
+ if (leases[i]->hwaddr_ && leases[i]->hwaddr_->hwaddr_.empty()) {
+ continue;
+ }
+ // Obtain lease from the server.
+ Lease4Ptr lease = lmptr_->getLease4(leases[i]->addr_);
+
+ // If the lease is reclaimed and the expiration time passed more than
+ // 15 seconds ago, the lease should have been deleted.
+ if (leases[i]->stateExpiredReclaimed() &&
+ ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+ EXPECT_FALSE(lease) << "The following lease should have been"
+ " deleted: " << leases[i]->toText();
+
+ } else {
+ // If the lease is not reclaimed or it has expired less than
+ // 15 seconds ago, the lease should still be there.
+ EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+ " deleted: " << leases[i]->toText();
+ }
+ }
+}
+
+void
+GenericLeaseMgrTest::testDeleteExpiredReclaimedLeases6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+ // Make sure we have at least 6 leases there.
+ ASSERT_GE(leases.size(), 6);
+
+ time_t current_time = time(NULL);
+
+ // Add them to the database
+ for (size_t i = 0; i < leases.size(); ++i) {
+ // Mark every other lease as expired.
+ if (i % 2 == 0) {
+ // Set client last transmission time to the value older than the
+ // valid lifetime to make it expired. We also substract the value
+ // of 10, 20, 30, 40 etc, depending on the lease index. This
+ // simulates different expiration times for various leases.
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - i * 10;
+ // Set reclaimed state.
+ leases[i]->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+
+ } else {
+ // Other leases are left as not expired - client last transmission
+ // time set to current time.
+ leases[i]->cltt_ = current_time;
+ }
+ ASSERT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Keep reclaimed lease for 15 seconds after expiration.
+ const uint32_t lease_affinity_time = 15;
+
+ // Delete expired and reclaimed leases which have expired earlier than
+ // 15 seconds ago. This should affect leases with index 2, 3, 4 etc.
+ uint64_t deleted_num;
+ uint64_t should_delete_num = 0;
+ ASSERT_NO_THROW(
+ deleted_num = lmptr_->deleteExpiredReclaimedLeases6(lease_affinity_time)
+ );
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ // Obtain lease from the server.
+ Lease6Ptr lease = lmptr_->getLease6(leases[i]->type_, leases[i]->addr_);
+
+ // If the lease is reclaimed and the expiration time passed more than
+ // 15 seconds ago, the lease should have been deleted.
+ if (leases[i]->stateExpiredReclaimed() &&
+ ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+ EXPECT_FALSE(lease) << "The following lease should have been"
+ " deleted: " << leases[i]->toText();
+ ++should_delete_num;
+
+ } else {
+ // If the lease is not reclaimed or it has expired less than
+ // 15 seconds ago, the lease should still be there.
+ EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+ " deleted: " << leases[i]->toText();
+ }
+ }
+ // Check that the number of deleted leases is correct.
+ EXPECT_EQ(should_delete_num, deleted_num);
+
+ // Make sure we can make another attempt, when there are no more leases
+ // to be deleted.
+ ASSERT_NO_THROW(
+ deleted_num = lmptr_->deleteExpiredReclaimedLeases6(lease_affinity_time)
+ );
+ // No lease should have been deleted.
+ EXPECT_EQ(0, deleted_num);
+
+ // Reopen the database. This to ensure that the leases have been deleted
+ // from the persistent storage.
+ reopen(V6);
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ // Obtain lease from the server.
+ Lease6Ptr lease = lmptr_->getLease6(leases[i]->type_, leases[i]->addr_);
+
+ // If the lease is reclaimed and the expiration time passed more than
+ // 15 seconds ago, the lease should have been deleted.
+ if (leases[i]->stateExpiredReclaimed() &&
+ ((leases[i]->getExpirationTime() + lease_affinity_time) < current_time)) {
+ EXPECT_FALSE(lease) << "The following lease should have been"
+ " deleted: " << leases[i]->toText();
+
+ } else {
+ // If the lease is not reclaimed or it has expired less than
+ // 15 seconds ago, the lease should still be there.
+ EXPECT_TRUE(lease) << "The following lease shouldn't have been"
+ " deleted: " << leases[i]->toText();
+ }
+ }
+}
+
+void
+GenericLeaseMgrTest::testGetDeclinedLeases4() {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Make sure we have at least 8 leases there.
+ ASSERT_GE(leases.size(), 8);
+
+ // Use the same current time for all leases.
+ time_t current_time = time(NULL);
+
+ // Add them to the database
+ for (size_t i = 0; i < leases.size(); ++i) {
+
+ // Mark the first half of the leases as DECLINED
+ if (i < leases.size()/2) {
+ // Mark as declined with 1000 seconds of probation-period
+ leases[i]->decline(1000);
+ }
+
+ // Mark every second lease as expired.
+ if (i % 2 == 0) {
+ // Set client last transmission time to the value older than the
+ // valid lifetime to make it expired. The expiration time also
+ // depends on the lease index, so as we can later check that the
+ // leases are ordered by the expiration time.
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+
+ } else {
+ // Set current time as cltt for remaining leases. These leases are
+ // not expired.
+ leases[i]->cltt_ = current_time;
+ }
+
+ ASSERT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // The leases are:
+ // 0 - declined, expired
+ // 1 - declined, not expired
+ // 2 - declined, expired
+ // 3 - declined, not expired
+ // 4 - default, expired
+ // 5 - default, not expired
+ // 6 - default, expired
+ // 7 - default, not expired
+
+ // Retrieve at most 1000 expired leases.
+ Lease4Collection expired_leases;
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 1000));
+
+ // Leases with even indexes should be returned as expired. It shouldn't
+ // matter if the state is default or expired. The getExpiredLeases4 does
+ // not pay attention to state, just expiration timers.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ unsigned int declined_state = 0;
+ unsigned int default_state = 0;
+
+ // The expired leases should be returned from the most to least expired.
+ // This matches the reverse order to which they have been added.
+ for (Lease4Collection::reverse_iterator lease = expired_leases.rbegin();
+ lease != expired_leases.rend(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.rbegin(), lease));
+ // Multiple current index by two, because only leases with even indexes
+ // should have been returned.
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+
+ // Count leases in default and declined states
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ default_state++;
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ declined_state++;
+ }
+ }
+
+ // LeaseMgr is supposed to return both default and declined leases
+ EXPECT_NE(0, declined_state);
+ EXPECT_NE(0, default_state);
+
+ // Update current time for the next test.
+ current_time = time(NULL);
+ // Also, remove expired leases collected during the previous test.
+ expired_leases.clear();
+
+ // This time let's reverse the expiration time and see if they will be returned
+ // in the correct order.
+ leases = createLeases4();
+ for (int i = 0; i < leases.size(); ++i) {
+
+ // Mark the second half of the leases as DECLINED
+ if (i >= leases.size()/2) {
+ // Mark as declined with 1000 seconds of probation-period
+ leases[i]->decline(1000);
+ }
+
+ // Update the time of expired leases with even indexes.
+ if (i % 2 == 0) {
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+
+ } else {
+ // Make sure remaining leases remain unexpired.
+ leases[i]->cltt_ = current_time + 100;
+ }
+ ASSERT_NO_THROW(lmptr_->updateLease4(leases[i]));
+ }
+
+ // Retrieve expired leases again. The limit of 0 means return all expired
+ // leases.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 0));
+ // The same leases should be returned.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ // This time leases should be returned in the non-reverse order.
+ declined_state = 0;
+ default_state = 0;
+ for (Lease4Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+
+ // Count leases in default and declined states
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ default_state++;
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ declined_state++;
+ }
+ }
+
+ // Check that both declined and default state leases were returned.
+ EXPECT_NE(0, declined_state);
+ EXPECT_NE(0, default_state);
+
+ // Remove expired leases again.
+ expired_leases.clear();
+
+ // Limit the number of leases to be returned to 2.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases4(expired_leases, 2));
+
+ // Make sure we have exactly 2 leases returned.
+ ASSERT_EQ(2, expired_leases.size());
+
+ // Test that most expired leases have been returned.
+ for (Lease4Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+}
+
+void
+GenericLeaseMgrTest::testGetDeclinedLeases6() {
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Make sure we have at least 8 leases there.
+ ASSERT_GE(leases.size(), 8);
+
+ // Use the same current time for all leases.
+ time_t current_time = time(NULL);
+
+ // Add them to the database
+ for (size_t i = 0; i < leases.size(); ++i) {
+
+ // Mark the first half of the leases as DECLINED
+ if (i < leases.size()/2) {
+ // Mark as declined with 1000 seconds of probation-period
+ leases[i]->decline(1000);
+ }
+
+ // Mark every second lease as expired.
+ if (i % 2 == 0) {
+ // Set client last transmission time to the value older than the
+ // valid lifetime to make it expired. The expiration time also
+ // depends on the lease index, so as we can later check that the
+ // leases are ordered by the expiration time.
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 10 - i;
+
+ } else {
+ // Set current time as cltt for remaining leases. These leases are
+ // not expired.
+ leases[i]->cltt_ = current_time;
+ }
+
+ ASSERT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // The leases are:
+ // 0 - declined, expired
+ // 1 - declined, not expired
+ // 2 - declined, expired
+ // 3 - declined, not expired
+ // 4 - default, expired
+ // 5 - default, not expired
+ // 6 - default, expired
+ // 7 - default, not expired
+
+ // Retrieve at most 1000 expired leases.
+ Lease6Collection expired_leases;
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 1000));
+
+ // Leases with even indexes should be returned as expired. It shouldn't
+ // matter if the state is default or expired. The getExpiredLeases4 does
+ // not pay attention to state, just expiration timers.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ unsigned int declined_state = 0;
+ unsigned int default_state = 0;
+
+ // The expired leases should be returned from the most to least expired.
+ // This matches the reverse order to which they have been added.
+ for (Lease6Collection::reverse_iterator lease = expired_leases.rbegin();
+ lease != expired_leases.rend(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.rbegin(), lease));
+ // Multiple current index by two, because only leases with even indexes
+ // should have been returned.
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+
+ // Count leases in default and declined states
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ default_state++;
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ declined_state++;
+ }
+ }
+
+ // LeaseMgr is supposed to return both default and declined leases
+ EXPECT_NE(0, declined_state);
+ EXPECT_NE(0, default_state);
+
+ // Update current time for the next test.
+ current_time = time(NULL);
+ // Also, remove expired leases collected during the previous test.
+ expired_leases.clear();
+
+ // This time let's reverse the expiration time and see if they will be returned
+ // in the correct order.
+ leases = createLeases6();
+ for (int i = 0; i < leases.size(); ++i) {
+
+ // Mark the second half of the leases as DECLINED
+ if (i >= leases.size()/2) {
+ // Mark as declined with 1000 seconds of probation-period
+ leases[i]->decline(1000);
+ }
+
+ // Update the time of expired leases with even indexes.
+ if (i % 2 == 0) {
+ leases[i]->cltt_ = current_time - leases[i]->valid_lft_ - 1000 + i;
+
+ } else {
+ // Make sure remaining leases remain unexpired.
+ leases[i]->cltt_ = current_time + 100;
+ }
+ ASSERT_NO_THROW(lmptr_->updateLease6(leases[i]));
+ }
+
+ // Retrieve expired leases again. The limit of 0 means return all expired
+ // leases.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 0));
+ // The same leases should be returned.
+ ASSERT_EQ(static_cast<size_t>(leases.size() / 2), expired_leases.size());
+
+ // This time leases should be returned in the non-reverse order.
+ declined_state = 0;
+ default_state = 0;
+ for (Lease6Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+
+ // Count leases in default and declined states
+ if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+ default_state++;
+ } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+ declined_state++;
+ }
+ }
+
+ // Check that both declined and default state leases were returned.
+ EXPECT_NE(0, declined_state);
+ EXPECT_NE(0, default_state);
+
+ // Remove expired leases again.
+ expired_leases.clear();
+
+ // Limit the number of leases to be returned to 2.
+ ASSERT_NO_THROW(lmptr_->getExpiredLeases6(expired_leases, 2));
+
+ // Make sure we have exactly 2 leases returned.
+ ASSERT_EQ(2, expired_leases.size());
+
+ // Test that most expired leases have been returned.
+ for (Lease6Collection::iterator lease = expired_leases.begin();
+ lease != expired_leases.end(); ++lease) {
+ int index = static_cast<int>(std::distance(expired_leases.begin(), lease));
+ EXPECT_EQ(leases[2 * index]->addr_, (*lease)->addr_);
+ }
+}
+
+void
+GenericLeaseMgrTest::checkLeaseStats(const StatValMapList& expectedStats) {
+ // Global accumulators
+ int64_t declined_addresses = 0;
+ int64_t reclaimed_declined_addresses = 0;
+
+ // Iterate over all stats for each subnet
+ for (int subnet_idx = 0; subnet_idx < expectedStats.size(); ++subnet_idx) {
+ BOOST_FOREACH(StatValPair expectedStat, expectedStats[subnet_idx]) {
+ // Verify the per subnet value.
+ checkStat(stats::StatsMgr::generateName("subnet", subnet_idx + 1,
+ expectedStat.first),
+ expectedStat.second.value_);
+
+ if (expectedStat.second.check_pool_) {
+ if (expectedStat.first.find("pd") != string::npos) {
+ checkStat(stats::StatsMgr::generateName("subnet", subnet_idx + 1,
+ stats::StatsMgr::generateName("pd-pool", 0,
+ expectedStat.first)),
+ expectedStat.second.value_);
+ } else {
+ checkStat(stats::StatsMgr::generateName("subnet", subnet_idx + 1,
+ stats::StatsMgr::generateName("pool", 0,
+ expectedStat.first)),
+ expectedStat.second.value_);
+ }
+ } else {
+ string name;
+ if (expectedStat.first.find("pd") != string::npos) {
+ name = stats::StatsMgr::generateName("subnet", subnet_idx + 1,
+ stats::StatsMgr::generateName("pd-pool", 0,
+ expectedStat.first));
+ } else {
+ name = stats::StatsMgr::generateName("subnet", subnet_idx + 1,
+ stats::StatsMgr::generateName("pool", 0,
+ expectedStat.first));
+ }
+ ObservationPtr const obs(stats::StatsMgr::instance().getObservation(name));
+ ASSERT_FALSE(obs) << "stat " << name << " should not be present";
+ }
+
+ // Add the value to globals as needed.
+ if (expectedStat.first == "declined-addresses") {
+ declined_addresses += expectedStat.second.value_;
+ } else if (expectedStat.first == "reclaimed-declined-addresses") {
+ reclaimed_declined_addresses += expectedStat.second.value_;
+ }
+ }
+ }
+
+ // Verify the globals.
+ checkStat("declined-addresses", declined_addresses);
+ checkStat("reclaimed-declined-addresses", reclaimed_declined_addresses);
+}
+
+Lease4Ptr
+GenericLeaseMgrTest::makeLease4(const std::string& address,
+ const SubnetID& subnet_id,
+ const uint32_t state,
+ const ConstElementPtr user_context /* = ConstElementPtr() */) {
+ Lease4Ptr lease(new Lease4());
+
+ // set the address
+ lease->addr_ = IOAddress(address);
+
+ // make a MAC from the address
+ std::vector<uint8_t> hwaddr = lease->addr_.toBytes();
+ hwaddr.push_back(0);
+ hwaddr.push_back(0);
+
+ lease->hwaddr_.reset(new HWAddr(hwaddr, HTYPE_ETHER));
+ lease->valid_lft_ = 86400;
+ lease->cltt_ = 168256;
+ lease->subnet_id_ = subnet_id;
+ lease->state_ = state;
+ if (user_context) {
+ lease->setContext(user_context);
+ }
+
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ return lease;
+}
+
+Lease6Ptr
+GenericLeaseMgrTest::makeLease6(const Lease::Type& type,
+ const std::string& address,
+ uint8_t prefix_len,
+ const SubnetID& subnet_id,
+ const uint32_t state,
+ const ConstElementPtr user_context /* = ConstElementPtr() */) {
+ IOAddress addr(address);
+
+ // make a DUID from the address
+ std::vector<uint8_t> bytes = addr.toBytes();
+ bytes.push_back(prefix_len);
+
+ Lease6Ptr lease(new Lease6(type, addr, DuidPtr(new DUID(bytes)), 77,
+ 16000, 24000, subnet_id, HWAddrPtr(),
+ prefix_len));
+ lease->state_ = state;
+ if (user_context) {
+ lease->setContext(user_context);
+ }
+
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ return lease;
+}
+
+void
+GenericLeaseMgrTest::logCallback(TrackingLeaseMgr::CallbackType type, SubnetID subnet_id,
+ LeasePtr lease) {
+ auto locked = (lmptr_ ? lmptr_->isLocked(lease) : false);
+ logs_.push_back(Log{type, subnet_id, lease, locked});
+}
+
+int
+GenericLeaseMgrTest::countLogs(TrackingLeaseMgr::CallbackType type, SubnetID subnet_id,
+ Lease::Type lease_type) const {
+ int count = 0;
+ for (auto log : logs_) {
+ if ((log.type == type) && (log.subnet_id == subnet_id) && (log.lease->getType() == lease_type)) {
+ ++count;
+ }
+ }
+ return (count);
+}
+
+void
+GenericLeaseMgrTest::testRecountLeaseStats4() {
+ using namespace stats;
+
+ StatsMgr::instance().removeAll();
+
+ // Create two subnets.
+ int num_subnets = 2;
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet;
+ Pool4Ptr pool;
+
+ subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 1));
+ pool.reset(new Pool4(IOAddress("192.0.1.0"), IOAddress("192.0.1.127")));
+ subnet->addPool(pool);
+ pool.reset(new Pool4(IOAddress("192.0.1.128"), IOAddress("192.0.1.255")));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 2));
+ pool.reset(new Pool4(IOAddress("192.0.2.0"), 24));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Create the expected stats list. At this point, the only stat
+ // that should be non-zero is total-addresses.
+ StatValMapList expectedStats(num_subnets);
+ for (int i = 0; i < num_subnets; ++i) {
+ expectedStats[i]["total-addresses"] = 256;
+ expectedStats[i]["assigned-addresses"] = 0;
+ expectedStats[i]["cumulative-assigned-addresses"] = 0;
+ expectedStats[i]["declined-addresses"] = 0;
+ expectedStats[i]["reclaimed-declined-addresses"] = 0;
+ expectedStats[i]["reclaimed-leases"] = 0;
+ }
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+ // Recount stats. We should have the same results.
+ // The call to removeStatistics is needed to clear all statistics.
+ // The call to updateStatistics is needed to generate all counters,
+ // including total-addresses. The updateStatistics method calls
+ // recountLeaseStats4 internally.
+ ASSERT_NO_THROW(cfg->removeStatistics());
+ ASSERT_NO_THROW(cfg->updateStatistics());
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+ // Check that cumulative global stats always exist.
+ EXPECT_TRUE(StatsMgr::instance().getObservation("cumulative-assigned-addresses"));
+
+ // Now let's insert some leases into subnet 1.
+ int subnet_id = 1;
+
+ // Insert one lease in default state, i.e. assigned.
+ Lease4Ptr lease1 = makeLease4("192.0.1.1", subnet_id);
+
+ // Insert one lease in declined state.
+ Lease4Ptr lease2 = makeLease4("192.0.1.2", subnet_id, Lease::STATE_DECLINED);
+
+ // Insert one lease in the expired state.
+ makeLease4("192.0.1.3", subnet_id, Lease::STATE_EXPIRED_RECLAIMED);
+
+ // Insert another lease in default state, i.e. assigned.
+ makeLease4("192.0.1.4", subnet_id);
+
+ // Update the expected stats list for subnet 1.
+ expectedStats[subnet_id - 1]["assigned-addresses"] = 3; // 2 + 1 declined
+ expectedStats[subnet_id - 1]["declined-addresses"] = 1;
+
+ // Now let's add leases to subnet 2.
+ subnet_id = 2;
+
+ // Insert one declined lease.
+ makeLease4("192.0.2.2", subnet_id, Lease::STATE_DECLINED);
+
+ // Update the expected stats.
+ expectedStats[subnet_id - 1]["assigned-addresses"] = 1; // 0 + 1 declined
+ expectedStats[subnet_id - 1]["declined-addresses"] = 1;
+
+ // Now Recount the stats.
+ // The call to removeStatistics is needed to clear all statistics.
+ // The call to updateStatistics is needed to generate all counters,
+ // including total-addresses. The updateStatistics method calls
+ // recountLeaseStats4 internally.
+ ASSERT_NO_THROW(cfg->removeStatistics());
+ ASSERT_NO_THROW(cfg->updateStatistics());
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+ // Delete some leases from subnet, and update the expected stats.
+ EXPECT_TRUE(lmptr_->deleteLease(lease1));
+ expectedStats[0]["assigned-addresses"] = 2;
+
+ EXPECT_TRUE(lmptr_->deleteLease(lease2));
+ expectedStats[0]["assigned-addresses"] = 1;
+ expectedStats[0]["declined-addresses"] = 0;
+
+ // Recount the stats.
+ // The call to removeStatistics is needed to clear all statistics.
+ // The call to updateStatistics is needed to generate all counters,
+ // including total-addresses. The updateStatistics method calls
+ // recountLeaseStats4 internally.
+ ASSERT_NO_THROW(cfg->removeStatistics());
+ ASSERT_NO_THROW(cfg->updateStatistics());
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+}
+
+void
+GenericLeaseMgrTest::testRecountLeaseStats6() {
+ using namespace stats;
+
+ StatsMgr::instance().removeAll();
+
+ // Create two subnets.
+ int num_subnets = 2;
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ Subnet6Ptr subnet;
+ Pool6Ptr pool;
+ StatValMapList expectedStats(num_subnets);
+
+ int subnet_id = 1;
+ subnet.reset(new Subnet6(IOAddress("3001:1::"), 64, 1, 2, 3, 4, subnet_id));
+ pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("3001:1::"),
+ IOAddress("3001:1::7F")));
+ subnet->addPool(pool);
+ pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("3001:1::80"),
+ IOAddress("3001:1::FF")));
+ subnet->addPool(pool);
+ expectedStats[subnet_id - 1]["total-nas"] = 256;
+
+ pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"),96,112));
+ subnet->addPool(pool);
+ expectedStats[subnet_id - 1]["total-pds"] = 65536;
+ cfg->add(subnet);
+
+ ++subnet_id;
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4,
+ subnet_id));
+ pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 120));
+ subnet->addPool(pool);
+ expectedStats[subnet_id - 1]["total-nas"] = 256;
+ expectedStats[subnet_id - 1]["total-pds"] = 0;
+ cfg->add(subnet);
+
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Create the expected stats list. At this point, the only stat
+ // that should be non-zero is total-nas/total-pds.
+ for (int i = 0; i < num_subnets; ++i) {
+ expectedStats[i]["assigned-nas"] = 0;
+ expectedStats[i]["cumulative-assigned-nas"] = 0;
+ expectedStats[i]["declined-addresses"] = 0;
+ expectedStats[i]["reclaimed-declined-addresses"] = 0;
+ expectedStats[i]["reclaimed-leases"] = 0;
+ expectedStats[i]["assigned-pds"] = 0;
+ expectedStats[i]["cumulative-assigned-pds"] = 0;
+ }
+
+ // Stats should not be present because the subnet has no PD pool.
+ expectedStats[subnet_id - 1]["total-pds"].check_pool_ = false;
+ expectedStats[subnet_id - 1]["assigned-pds"].check_pool_ = false;
+ expectedStats[subnet_id - 1]["cumulative-assigned-pds"].check_pool_ = false;
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+ // Recount stats. We should have the same results.
+ // The call to removeStatistics is needed to clear all statistics.
+ // The call to updateStatistics is needed to generate all counters,
+ // including total-nas and total-pds. The updateStatistics method calls
+ // recountLeaseStats6 internally.
+ ASSERT_NO_THROW(cfg->removeStatistics());
+ ASSERT_NO_THROW(cfg->updateStatistics());
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+ // Check that cumulative global stats always exist.
+ EXPECT_TRUE(StatsMgr::instance().getObservation("cumulative-assigned-nas"));
+ EXPECT_TRUE(StatsMgr::instance().getObservation("cumulative-assigned-pds"));
+
+ // Now let's insert some leases into subnet 1.
+ subnet_id = 1;
+
+ // Insert three assigned NAs.
+ makeLease6(Lease::TYPE_NA, "3001:1::1", 128, subnet_id);
+ Lease6Ptr lease2 = makeLease6(Lease::TYPE_NA, "3001:1::2", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "3001:1::3", 128, subnet_id);
+ expectedStats[subnet_id - 1]["assigned-nas"] = 5; // 3 + 2 declined
+
+ // Insert two declined NAs.
+ makeLease6(Lease::TYPE_NA, "3001:1::4", 128, subnet_id,
+ Lease::STATE_DECLINED);
+ makeLease6(Lease::TYPE_NA, "3001:1::5", 128, subnet_id,
+ Lease::STATE_DECLINED);
+ expectedStats[subnet_id - 1]["declined-addresses"] = 2;
+
+ // Insert one expired NA.
+ makeLease6(Lease::TYPE_NA, "3001:1::6", 128, subnet_id,
+ Lease::STATE_EXPIRED_RECLAIMED);
+
+ // Insert two assigned PDs.
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0100::", 112, subnet_id);
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0200::", 112, subnet_id);
+ expectedStats[subnet_id - 1]["assigned-pds"] = 2;
+
+ // Insert two expired PDs.
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0300::", 112, subnet_id,
+ Lease::STATE_EXPIRED_RECLAIMED);
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0400::", 112, subnet_id,
+ Lease::STATE_EXPIRED_RECLAIMED);
+
+ // Now let's add leases to subnet 2.
+ subnet_id = 2;
+
+ // Insert two assigned NAs.
+ makeLease6(Lease::TYPE_NA, "2001:db81::1", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "2001:db81::2", 128, subnet_id);
+ expectedStats[subnet_id - 1]["assigned-nas"] = 3; // 2 + 1 declined
+
+ // Insert one declined NA.
+ Lease6Ptr lease3 = makeLease6(Lease::TYPE_NA, "2001:db81::3", 128, subnet_id,
+ Lease::STATE_DECLINED);
+ expectedStats[subnet_id - 1]["declined-addresses"] = 1;
+
+ // Now Recount the stats.
+ // The call to removeStatistics is needed to clear all statistics.
+ // The call to updateStatistics is needed to generate all counters,
+ // including total-nas and total-pds. The updateStatistics method calls
+ // recountLeaseStats6 internally.
+ ASSERT_NO_THROW(cfg->removeStatistics());
+ ASSERT_NO_THROW(cfg->updateStatistics());
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+ // Delete some leases and update the expected stats.
+ EXPECT_TRUE(lmptr_->deleteLease(lease2));
+ expectedStats[0]["assigned-nas"] = 4;
+
+ EXPECT_TRUE(lmptr_->deleteLease(lease3));
+ expectedStats[1]["assigned-nas"] = 2;
+ expectedStats[1]["declined-addresses"] = 0;
+
+ // Recount the stats.
+ // The call to removeStatistics is needed to clear all statistics.
+ // The call to updateStatistics is needed to generate all counters,
+ // including total-nas and total-pds. The updateStatistics method calls
+ // recountLeaseStats6 internally.
+ ASSERT_NO_THROW(cfg->removeStatistics());
+ ASSERT_NO_THROW(cfg->updateStatistics());
+
+ // Make sure stats are as expected.
+ ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+}
+
+void
+GenericLeaseMgrTest::testWipeLeases6() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease6Ptr> leases = createLeases6();
+ leases[0]->subnet_id_ = 1;
+ leases[1]->subnet_id_ = 1;
+ leases[2]->subnet_id_ = 1;
+ leases[3]->subnet_id_ = 22;
+ leases[4]->subnet_id_ = 333;
+ leases[5]->subnet_id_ = 333;
+ leases[6]->subnet_id_ = 333;
+ leases[7]->subnet_id_ = 333;
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Let's try something simple. There shouldn't be any leases in
+ // subnet 2. The keep deleting the leases, perhaps in a different
+ // order they were added.
+ EXPECT_EQ(0, lmptr_->wipeLeases6(2));
+ EXPECT_EQ(4, lmptr_->wipeLeases6(333));
+ EXPECT_EQ(3, lmptr_->wipeLeases6(1));
+ EXPECT_EQ(1, lmptr_->wipeLeases6(22));
+
+ // All the leases should be gone now. Check that that repeated
+ // attempt to delete them will not result in any additional removals.
+ EXPECT_EQ(0, lmptr_->wipeLeases6(1));
+ EXPECT_EQ(0, lmptr_->wipeLeases6(22));
+ EXPECT_EQ(0, lmptr_->wipeLeases6(333));
+}
+
+void
+GenericLeaseMgrTest::testWipeLeases4() {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ leases[0]->subnet_id_ = 1;
+ leases[1]->subnet_id_ = 1;
+ leases[2]->subnet_id_ = 1;
+ leases[3]->subnet_id_ = 22;
+ leases[4]->subnet_id_ = 333;
+ leases[5]->subnet_id_ = 333;
+ leases[6]->subnet_id_ = 333;
+ leases[7]->subnet_id_ = 333;
+
+ for (size_t i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Let's try something simple. There shouldn't be any leases in
+ // subnet 2. The keep deleting the leases, perhaps in a different
+ // order they were added.
+ EXPECT_EQ(0, lmptr_->wipeLeases4(2));
+ EXPECT_EQ(4, lmptr_->wipeLeases4(333));
+ EXPECT_EQ(3, lmptr_->wipeLeases4(1));
+ EXPECT_EQ(1, lmptr_->wipeLeases4(22));
+
+ // All the leases should be gone now. Check that that repeated
+ // attempt to delete them will not result in any additional removals.
+ EXPECT_EQ(0, lmptr_->wipeLeases4(1));
+ EXPECT_EQ(0, lmptr_->wipeLeases4(22));
+ EXPECT_EQ(0, lmptr_->wipeLeases4(333));
+}
+
+void
+LeaseMgrDbLostCallbackTest::SetUp() {
+ destroySchema();
+ createSchema();
+ isc::dhcp::LeaseMgrFactory::destroy();
+}
+
+void
+LeaseMgrDbLostCallbackTest::TearDown() {
+ destroySchema();
+ isc::dhcp::LeaseMgrFactory::destroy();
+}
+
+void
+LeaseMgrDbLostCallbackTest::testNoCallbackOnOpenFailure() {
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ ASSERT_THROW(LeaseMgrFactory::create(invalidConnectString()),
+ DbOpenError);
+
+ io_service_->poll();
+
+ EXPECT_EQ(0, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+LeaseMgrDbLostCallbackTest::testDbLostAndRecoveredCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ // Connect to the lease backend.
+ ASSERT_NO_THROW(LeaseMgrFactory::create(access));
+
+ // The most recently opened socket should be for our SQL client.
+ int sql_socket = test::findLastSocketFd();
+ ASSERT_TRUE(sql_socket > -1);
+
+ // Verify we can execute a query. We do not care if
+ // we find a lease or not.
+ LeaseMgr& lm = LeaseMgrFactory::instance();
+
+ Lease4Ptr lease;
+ ASSERT_NO_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")));
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+LeaseMgrDbLostCallbackTest::testDbLostAndFailedCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ // Connect to the lease backend.
+ ASSERT_NO_THROW(LeaseMgrFactory::create(access));
+
+ // The most recently opened socket should be for our SQL client.
+ int sql_socket = test::findLastSocketFd();
+ ASSERT_TRUE(sql_socket > -1);
+
+ // Verify we can execute a query. We do not care if
+ // we find a lease or not.
+ LeaseMgr& lm = LeaseMgrFactory::instance();
+
+ Lease4Ptr lease;
+ ASSERT_NO_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")));
+
+ access = invalidConnectString();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+LeaseMgrDbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ // Connect to the lease backend.
+ ASSERT_NO_THROW(LeaseMgrFactory::create(access));
+
+ // The most recently opened socket should be for our SQL client.
+ int sql_socket = test::findLastSocketFd();
+ ASSERT_TRUE(sql_socket > -1);
+
+ // Verify we can execute a query. We do not care if
+ // we find a lease or not.
+ LeaseMgr& lm = LeaseMgrFactory::instance();
+
+ Lease4Ptr lease;
+ ASSERT_NO_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")));
+
+ access = invalidConnectString();
+ access += extra;
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ access = validConnectString();
+ access += extra;
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // No callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+LeaseMgrDbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&LeaseMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ // Connect to the lease backend.
+ ASSERT_NO_THROW(LeaseMgrFactory::create(access));
+
+ // The most recently opened socket should be for our SQL client.
+ int sql_socket = test::findLastSocketFd();
+ ASSERT_TRUE(sql_socket > -1);
+
+ // Verify we can execute a query. We do not care if
+ // we find a lease or not.
+ LeaseMgr& lm = LeaseMgrFactory::instance();
+
+ Lease4Ptr lease;
+ ASSERT_NO_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")));
+
+ access = invalidConnectString();
+ access += extra;
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setLeaseDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(lease = lm.getLease4(IOAddress("192.0.1.0")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(3, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+GenericLeaseMgrTest::checkLeaseRange(const Lease4Collection& returned,
+ const std::vector<std::string>& expected_addresses) {
+ ASSERT_EQ(expected_addresses.size(), returned.size());
+
+ for (auto a = returned.cbegin(); a != returned.cend(); ++a) {
+ EXPECT_EQ(expected_addresses[std::distance(returned.cbegin(), a)],
+ (*a)->addr_.toText());
+ }
+}
+
+void
+GenericLeaseMgrTest::checkQueryAgainstRowSet(const LeaseStatsQueryPtr& query,
+ const RowSet& expected_rows) {
+ ASSERT_TRUE(query) << "query is null";
+
+ int rows_matched = 0;
+ LeaseStatsRow row;
+ while (query->getNextRow(row)) {
+ auto found_row = expected_rows.find(row);
+ if (found_row == expected_rows.end()) {
+ ADD_FAILURE() << "query row not in expected set"
+ << " id: " << row.subnet_id_
+ << " type: " << row.lease_type_
+ << " state: " << row.lease_state_
+ << " count: " << row.state_count_;
+ } else {
+ if (row.state_count_ != (*found_row).state_count_) {
+ ADD_FAILURE() << "row count wrong for"
+ << " id: " << row.subnet_id_
+ << " type: " << row.lease_type_
+ << " state: " << row.lease_state_
+ << " count: " << row.state_count_
+ << "; expected: " << (*found_row).state_count_;
+ } else {
+ ++rows_matched;
+ }
+ }
+ }
+
+ ASSERT_EQ(rows_matched, expected_rows.size()) << "rows mismatched";
+}
+
+void
+GenericLeaseMgrTest::testLeaseStatsQuery4() {
+ // Create three subnets.
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet;
+ Pool4Ptr pool;
+
+ subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 1));
+ pool.reset(new Pool4(IOAddress("192.0.1.0"), 24));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 2));
+ pool.reset(new Pool4(IOAddress("192.0.2.0"), 24));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 1, 2, 3, 3));
+ pool.reset(new Pool4(IOAddress("192.0.3.0"), 24));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Make sure invalid values throw.
+ LeaseStatsQueryPtr query;
+ ASSERT_THROW(query = lmptr_->startSubnetLeaseStatsQuery4(0), BadValue);
+ ASSERT_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery4(0,1), BadValue);
+ ASSERT_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery4(1,0), BadValue);
+ ASSERT_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery4(10,1), BadValue);
+
+ // Start tests with an empty expected row set.
+ RowSet expected_rows;
+
+ // Before we add leases, test an empty return for get all subnets
+ {
+ SCOPED_TRACE("GET ALL WITH NO LEASES");
+ ASSERT_NO_THROW(query = lmptr_->startLeaseStatsQuery4());
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Now let's insert some leases into subnet 1.
+ // Two leases in the default state, i.e. assigned.
+ // One lease in declined state.
+ // One lease in the expired state.
+ int subnet_id = 1;
+ makeLease4("192.0.1.1", subnet_id);
+ makeLease4("192.0.1.2", subnet_id, Lease::STATE_DECLINED);
+ makeLease4("192.0.1.3", subnet_id, Lease::STATE_EXPIRED_RECLAIMED);
+ makeLease4("192.0.1.4", subnet_id);
+
+ // Now let's add leases to subnet 2.
+ // One declined lease.
+ subnet_id = 2;
+ makeLease4("192.0.2.2", subnet_id, Lease::STATE_DECLINED);
+
+ // Now add leases to subnet 3
+ // Two leases in default state, i.e. assigned.
+ // One declined lease.
+ subnet_id = 3;
+ makeLease4("192.0.3.1", subnet_id);
+ makeLease4("192.0.3.2", subnet_id);
+ makeLease4("192.0.3.3", subnet_id, Lease::STATE_DECLINED);
+
+ // Test single subnet for non-matching subnet
+ {
+ SCOPED_TRACE("NO MATCHING SUBNET");
+ ASSERT_NO_THROW(query = lmptr_->startSubnetLeaseStatsQuery4(777));
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test an empty range
+ {
+ SCOPED_TRACE("EMPTY SUBNET RANGE");
+ ASSERT_NO_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery4(777, 900));
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test a single subnet
+ {
+ SCOPED_TRACE("SINGLE SUBNET");
+ // Add expected rows for Subnet 2
+ expected_rows.insert(LeaseStatsRow(2, Lease::STATE_DECLINED, 1));
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startSubnetLeaseStatsQuery4(2));
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test a range of subnets
+ {
+ SCOPED_TRACE("SUBNET RANGE");
+ // Add expected rows for Subnet 3
+ expected_rows.insert(LeaseStatsRow(3, Lease::STATE_DEFAULT, 2));
+ expected_rows.insert(LeaseStatsRow(3, Lease::STATE_DECLINED, 1));
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery4(2,3));
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test all subnets
+ {
+ SCOPED_TRACE("ALL SUBNETS");
+ // Add expected rows for Subnet 1
+ expected_rows.insert(LeaseStatsRow(1, Lease::STATE_DEFAULT, 2));
+ expected_rows.insert(LeaseStatsRow(1, Lease::STATE_DECLINED, 1));
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startLeaseStatsQuery4());
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+}
+
+void
+GenericLeaseMgrTest::testLeaseStatsQuery6() {
+ // Create three subnets.
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ Subnet6Ptr subnet;
+ Pool6Ptr pool;
+
+ int subnet_id = 1;
+ subnet.reset(new Subnet6(IOAddress("3001:1::"), 64, 1, 2, 3, 4, subnet_id));
+ pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("3001:1::"),
+ IOAddress("3001:1::FF")));
+ subnet->addPool(pool);
+
+ pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"),96,112));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ ++subnet_id;
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4,
+ subnet_id));
+ pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 120));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ ++subnet_id;
+ subnet.reset(new Subnet6(IOAddress("2002:db8:1::"), 64, 1, 2, 3, 4,
+ subnet_id));
+ pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("2002:db8:1::"), 120));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Make sure invalid values throw.
+ LeaseStatsQueryPtr query;
+ ASSERT_THROW(query = lmptr_->startSubnetLeaseStatsQuery6(0), BadValue);
+ ASSERT_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery6(0,1), BadValue);
+ ASSERT_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery6(1,0), BadValue);
+ ASSERT_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery6(10,1), BadValue);
+
+ // Start tests with an empty expected row set.
+ RowSet expected_rows;
+
+ // Before we add leases, test an empty return for get all subnets
+ {
+ SCOPED_TRACE("GET ALL WITH NO LEASES");
+ ASSERT_NO_THROW(query = lmptr_->startLeaseStatsQuery6());
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Now let's insert some leases into subnet 1.
+ // Three assigned NAs.
+ // Two declined NAs.
+ // One expired NA.
+ // Two assigned PDs.
+ // Two expired PDs.
+ subnet_id = 1;
+ makeLease6(Lease::TYPE_NA, "3001:1::1", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "3001:1::2", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "3001:1::3", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "3001:1::4", 128, subnet_id,
+ Lease::STATE_DECLINED);
+ makeLease6(Lease::TYPE_NA, "3001:1::5", 128, subnet_id,
+ Lease::STATE_DECLINED);
+ makeLease6(Lease::TYPE_NA, "3001:1::6", 128, subnet_id,
+ Lease::STATE_EXPIRED_RECLAIMED);
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0100::", 112, subnet_id);
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0200::", 112, subnet_id);
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0300::", 112, subnet_id,
+ Lease::STATE_EXPIRED_RECLAIMED);
+ makeLease6(Lease::TYPE_PD, "3001:1:2:0400::", 112, subnet_id,
+ Lease::STATE_EXPIRED_RECLAIMED);
+
+ // Now let's add leases to subnet 2.
+ // Two assigned NAs
+ // One declined NAs
+ subnet_id = 2;
+ makeLease6(Lease::TYPE_NA, "2001:db81::1", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "2001:db81::2", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "2001:db81::3", 128, subnet_id,
+ Lease::STATE_DECLINED);
+
+ // Now let's add leases to subnet 3.
+ // Two assigned NAs
+ // One declined NAs
+ subnet_id = 3;
+ makeLease6(Lease::TYPE_NA, "2002:db81::1", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "2002:db81::2", 128, subnet_id);
+ makeLease6(Lease::TYPE_NA, "2002:db81::3", 128, subnet_id,
+ Lease::STATE_DECLINED);
+
+ // Test single subnet for non-matching subnet
+ {
+ SCOPED_TRACE("NO MATCHING SUBNET");
+ ASSERT_NO_THROW(query = lmptr_->startSubnetLeaseStatsQuery6(777));
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test an empty range
+ {
+ SCOPED_TRACE("EMPTY SUBNET RANGE");
+ ASSERT_NO_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery6(777, 900));
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test a single subnet
+ {
+ SCOPED_TRACE("SINGLE SUBNET");
+ // Add expected row for Subnet 2
+ expected_rows.insert(LeaseStatsRow(2, Lease::TYPE_NA, Lease::STATE_DEFAULT, 2));
+ expected_rows.insert(LeaseStatsRow(2, Lease::TYPE_NA, Lease::STATE_DECLINED, 1));
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startSubnetLeaseStatsQuery6(2));
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test a range of subnets
+ {
+ SCOPED_TRACE("SUBNET RANGE");
+ // Add expected rows for Subnet 3
+ expected_rows.insert(LeaseStatsRow(3, Lease::TYPE_NA, Lease::STATE_DEFAULT, 2));
+ expected_rows.insert(LeaseStatsRow(3, Lease::TYPE_NA, Lease::STATE_DECLINED, 1));
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startSubnetRangeLeaseStatsQuery6(2,3));
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+
+ // Test all subnets
+ {
+ SCOPED_TRACE("ALL SUBNETS");
+ // Add expected rows for Subnet 1
+ expected_rows.insert(LeaseStatsRow(1, Lease::TYPE_NA, Lease::STATE_DEFAULT, 3));
+ expected_rows.insert(LeaseStatsRow(1, Lease::TYPE_NA, Lease::STATE_DECLINED, 2));
+ expected_rows.insert(LeaseStatsRow(1, Lease::TYPE_PD, Lease::STATE_DEFAULT, 2));
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startLeaseStatsQuery6());
+
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+ }
+}
+
+void
+GenericLeaseMgrTest::testLeaseStatsQueryAttribution4() {
+ // Create two subnets for the same range.
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet;
+
+ subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 1));
+ cfg->add(subnet);
+
+ // Note it is even allowed to use 192.0.1.1/24 here...
+ subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 25, 1, 2, 3, 2));
+ cfg->add(subnet);
+
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ LeaseStatsQueryPtr query;
+ RowSet expected_rows;
+
+ // Now let's insert two leases into subnet 1.
+ int subnet_id = 1;
+ makeLease4("192.0.1.1", subnet_id);
+ Lease4Ptr lease = makeLease4("192.0.1.2", subnet_id);
+
+ // And one lease into subnet 2.
+ subnet_id = 2;
+ makeLease4("192.0.1.3", subnet_id);
+
+ // Move a lease to the second subnet.
+ lease->subnet_id_ = subnet_id;
+ EXPECT_NO_THROW(lmptr_->updateLease4(lease));
+
+ // Add expected rows for Subnets.
+ expected_rows.insert(LeaseStatsRow(1, Lease::STATE_DEFAULT, 1));
+ expected_rows.insert(LeaseStatsRow(2, Lease::STATE_DEFAULT, 2));
+
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startLeaseStatsQuery4());
+
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+}
+
+void
+GenericLeaseMgrTest::testLeaseStatsQueryAttribution6() {
+ // Create two subnets.
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ Subnet6Ptr subnet;
+
+ int subnet_id = 1;
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4,
+ subnet_id));
+ cfg->add(subnet);
+
+ ++subnet_id;
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::1"), 64, 1, 2, 3, 4,
+ subnet_id));
+ cfg->add(subnet);
+
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ LeaseStatsQueryPtr query;
+ RowSet expected_rows;
+
+ // Now let's insert two leases into subnet 1.
+ subnet_id = 1;
+ makeLease6(Lease::TYPE_NA, "2001:db81::1", 128, subnet_id);
+ Lease6Ptr lease = makeLease6(Lease::TYPE_NA, "2001:db81::2", 128, subnet_id);
+
+ // And one lease into subnet 2.
+ subnet_id = 2;
+ makeLease6(Lease::TYPE_NA, "2001:db81::3", 128, subnet_id);
+
+ // Move a lease to the second subnet.
+ lease->subnet_id_ = subnet_id;
+ EXPECT_NO_THROW(lmptr_->updateLease6(lease));
+
+ // Add expected rows for Subnets.
+ expected_rows.insert(LeaseStatsRow(1, Lease::TYPE_NA,
+ Lease::STATE_DEFAULT, 1));
+ expected_rows.insert(LeaseStatsRow(2, Lease::TYPE_NA,
+ Lease::STATE_DEFAULT, 2));
+ // Start the query
+ ASSERT_NO_THROW(query = lmptr_->startLeaseStatsQuery6());
+
+ // Verify contents
+ checkQueryAgainstRowSet(query, expected_rows);
+}
+
+void
+GenericLeaseMgrTest::testLeaseLimits4() {
+ std::string text;
+ ElementPtr user_context;
+
+ // -- A limit of 0 always denies a lease. --
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "client-classes": [ { "name": "foo", "address-limit": 0 } ] } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(user_context));
+ EXPECT_EQ(text, "address limit 0 for client class \"foo\", current lease count 0");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "subnet": { "id": 1, "address-limit": 0 } } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(user_context));
+ EXPECT_EQ(text, "address limit 0 for subnet ID 1, current lease count 0");
+
+ // -- A limit of 1 with no leases should allow a lease. --
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "client-classes": [ { "name": "foo", "address-limit": 1 } ] } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(user_context));
+ EXPECT_EQ(text, "");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "subnet": { "id": 1, "address-limit": 1 } } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(user_context));
+ EXPECT_EQ(text, "");
+
+ // -- A limit of 1 with 1 current lease should deny further leases. --
+
+ makeLease4("192.0.1.1", 1, Lease::STATE_DEFAULT, Element::fromJSON(
+ R"({ "ISC": { "client-classes": [ "foo" ] } })"));
+
+ // Since we did not go through allocation engine stats won't be altered.
+ ASSERT_NO_THROW(lmptr_->recountLeaseStats4());
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "client-classes": [ { "name": "foo", "address-limit": 1 } ] } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(user_context));
+ EXPECT_EQ(text, "address limit 1 for client class \"foo\", current lease count 1");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "subnet": { "id": 1, "address-limit": 1 } } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(user_context));
+ EXPECT_EQ(text, "address limit 1 for subnet ID 1, current lease count 1");
+}
+
+void
+GenericLeaseMgrTest::testLeaseLimits6() {
+ std::string text;
+ ElementPtr user_context;
+
+ // -- A limit of 0 always denies a lease. --
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "client-classes": [ { "name": "foo", "address-limit": 0 } ] } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "address limit 0 for client class \"foo\", current lease count 0");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "subnet": { "id": 1, "address-limit": 0 } } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "address limit 0 for subnet ID 1, current lease count 0");
+
+ // -- A limit of 1 with no leases should allow a lease. --
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "client-classes": [ { "name": "foo", "address-limit": 1 } ] } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "subnet": { "id": 1, "address-limit": 1 } } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "");
+
+ // -- A limit of 1 with 1 current lease should deny further leases. --
+ makeLease6(Lease::TYPE_NA, "2001:db8::", 128, 1, Lease::STATE_DEFAULT, Element::fromJSON(
+ R"({ "ISC": { "client-classes": [ "foo" ] } })"));
+
+ makeLease6(Lease::TYPE_PD, "2001:db8:1::", 64, 1, Lease::STATE_DEFAULT, Element::fromJSON(
+ R"({ "ISC": { "client-classes": [ "foo" ] } })"));
+
+ // Since we did not go through allocation engine stats won't be altered.
+ ASSERT_NO_THROW(lmptr_->recountLeaseStats6());
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "client-classes": [ { "name": "foo", "address-limit": 1 } ] } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "address limit 1 for client class \"foo\", current lease count 1");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "client-classes": [ { "name": "foo", "prefix-limit": 1 } ] } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "prefix limit 1 for client class \"foo\", current lease count 1");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "subnet": { "id": 1, "address-limit": 1 } } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "address limit 1 for subnet ID 1, current lease count 1");
+
+ user_context = Element::fromJSON(R"({ "ISC": { "limits": {
+ "subnet": { "id": 1, "prefix-limit": 1 } } } })");
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(user_context));
+ EXPECT_EQ(text, "prefix limit 1 for subnet ID 1, current lease count 1");
+}
+
+ElementPtr
+GenericLeaseMgrTest::makeContextWithClasses(const std::list<ClientClass>& classes) {
+ ElementPtr ctx = Element::createMap();
+ if (classes.size()) {
+ ElementPtr clist = Element::createList();
+ for (auto client_class : classes ) {
+ clist->add(Element::create(client_class));
+ }
+
+ ElementPtr client_classes = Element::createMap();
+ client_classes->set("client-classes", clist);
+ ctx->set("ISC", client_classes);
+ }
+
+ return (ctx);
+}
+
+void
+GenericLeaseMgrTest::testClassLeaseCount4() {
+ // Make user-contexts with different class lists.
+ std::list<ClientClass> classes1{"water"};
+ ElementPtr ctx1 = makeContextWithClasses(classes1);
+
+ std::list<ClientClass> classes2{"melon"};
+ ElementPtr ctx2 = makeContextWithClasses(classes2);
+
+ // Counts should be 0.
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("water"));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("melon"));
+
+ // Create a lease to add to the lease store.
+ vector<Lease4Ptr> leases = createLeases4();
+ Lease4Ptr lease = leases[1];
+
+ // Set the lease state to STATE_DEFAULT so it classes should be counted.
+ lease->state_ = Lease::STATE_DEFAULT;
+
+ // Add class list 1 to the lease.
+ lease->setContext(ctx1);
+
+ // Add the lease to the lease store and verify class lease counts.
+ ASSERT_NO_THROW_LOG(lmptr_->addLease(lease));
+ EXPECT_EQ(1, lmptr_->getClassLeaseCount("water"));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("melon"));
+
+ // Re-fetch lease. This returns a copy of the persisted lease, which is
+ // what Kea logic always does. Fetches a copy. Otherwise we're changing
+ // the persisted lease which would make old and new the same thing.
+ lease = lmptr_->getLease4(lease->addr_);
+ ASSERT_TRUE(lease);
+
+ // Change the class list.
+ lease->setContext(ctx2);
+
+ // Update the lease in the lease store and verify class lease counts.
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease4(lease));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("water"));
+ EXPECT_EQ(1, lmptr_->getClassLeaseCount("melon"));
+
+ lease = lmptr_->getLease4(lease->addr_);
+ ASSERT_TRUE(lease);
+
+ // Now delete the lease from the store and verify counts.
+ ASSERT_NO_THROW_LOG(lmptr_->deleteLease(lease));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("water"));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("melon"));
+
+ lease = lmptr_->getLease4(lease->addr_);
+ ASSERT_FALSE(lease);
+}
+
+void
+GenericLeaseMgrTest::testClassLeaseCount6(Lease::Type ltype) {
+ ASSERT_TRUE(ltype == Lease::TYPE_NA || ltype == Lease::TYPE_PD);
+
+ // Make user-contexts with different class lists.
+ std::list<ClientClass> classes1{"water"};
+ ElementPtr ctx1 = makeContextWithClasses(classes1);
+
+ std::list<ClientClass> classes2{"melon"};
+ ElementPtr ctx2 = makeContextWithClasses(classes2);
+
+ // Counts should be 0.
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("water", ltype));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("melon", ltype));
+
+ // Create a lease to add to the lease store.
+ vector<Lease6Ptr> leases = createLeases6();
+ Lease6Ptr lease = leases[1];
+ lease->type_ = ltype;
+
+ // Set the lease state to STATE_DEFAULT so it classes should be counted.
+ lease->state_ = Lease::STATE_DEFAULT;
+
+ // Add class list 1 to the lease.
+ lease->setContext(ctx1);
+
+ // Add the lease to the lease store and verify class lease counts.
+ ASSERT_NO_THROW_LOG(lmptr_->addLease(lease));
+ EXPECT_EQ(1, lmptr_->getClassLeaseCount("water", ltype));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("melon", ltype));
+
+ // Re-fetch lease. This returns a copy of the persisted lease, which is
+ // what Kea logic always does. Fetches a copy. Otherwise we're changing
+ // the persisted lease which would make old and new the same thing.
+ lease = lmptr_->getLease6(ltype, lease->addr_);
+ ASSERT_TRUE(lease);
+
+ // Change the class list.
+ lease->setContext(ctx2);
+
+ // Update the lease in the lease store and verify class lease counts.
+ ASSERT_NO_THROW_LOG(lmptr_->updateLease6(lease));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("water", ltype));
+ EXPECT_EQ(1, lmptr_->getClassLeaseCount("melon", ltype));
+
+ lease = lmptr_->getLease6(ltype, lease->addr_);
+ ASSERT_TRUE(lease);
+
+ // Now delete the lease from the store and verify counts.
+ ASSERT_NO_THROW_LOG(lmptr_->deleteLease(lease));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("water", ltype));
+ EXPECT_EQ(0, lmptr_->getClassLeaseCount("melon", ltype));
+
+ lease = lmptr_->getLease6(ltype, lease->addr_);
+ ASSERT_FALSE(lease);
+}
+
+void
+GenericLeaseMgrTest::testTrackAddLease4(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease. It should trigger the callback.
+ Lease4Ptr lease = initializeLease4(straddress4_[1]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackAddLeaseNA(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_NA,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease. It should trigger the callback.
+ Lease6Ptr lease = initializeLease6(straddress6_[0]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackAddLeasePD(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_PD,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease. It should trigger the callback.
+ Lease6Ptr lease = initializeLease6(straddress6_[2]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackUpdateLease4(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq",
+ SUBNET_ID_GLOBAL,
+ Lease::TYPE_V4,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease.
+ Lease4Ptr lease = initializeLease4(straddress4_[1]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ EXPECT_TRUE(logs_.empty());
+
+ lmptr_->updateLease4(lease);
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackUpdateLeaseNA(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_NA,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease.
+ Lease6Ptr lease = initializeLease6(straddress6_[0]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ EXPECT_TRUE(logs_.empty());
+
+ lmptr_->updateLease6(lease);
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackUpdateLeasePD(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_PD,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease.
+ Lease6Ptr lease = initializeLease6(straddress6_[2]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ EXPECT_TRUE(logs_.empty());
+
+ lmptr_->updateLease6(lease);
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackDeleteLease4(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease.
+ Lease4Ptr lease = initializeLease4(straddress4_[1]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ EXPECT_TRUE(logs_.empty());
+
+ lmptr_->deleteLease(lease);
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackDeleteLeaseNA(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_NA,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease.
+ Lease6Ptr lease = initializeLease6(straddress6_[0]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ EXPECT_TRUE(logs_.empty());
+
+ lmptr_->deleteLease(lease);
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testTrackDeleteLeasePD(bool expect_locked) {
+ // Register a callback for all subnets.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_PD,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1));
+ // Add a lease.
+ Lease6Ptr lease = initializeLease6(straddress6_[2]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ EXPECT_TRUE(logs_.empty());
+
+ lmptr_->deleteLease(lease);
+
+ // Make sure that the callback has been invoked.
+ ASSERT_EQ(1, logs_.size());
+
+ // This flag should be false for the Memfile backend and true
+ // for the SQL backends.
+ if (expect_locked) {
+ EXPECT_TRUE(logs_[0].locked);
+ } else {
+ EXPECT_FALSE(logs_[0].locked);
+ }
+
+ // The lease locks should have been released.
+ EXPECT_FALSE(lmptr_->isLocked(lease));
+}
+
+void
+GenericLeaseMgrTest::testRecreateWithCallbacks(const std::string& access) {
+ // Register a callback.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq", 0,
+ Lease::TYPE_V4,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 0, ph::_1));
+
+ // Recreate the lease manager with the callbacks.
+ ASSERT_NO_THROW(LeaseMgrFactory::recreate(access, true));
+ lmptr_ = &(LeaseMgrFactory::instance());
+
+ // Add a lease. It should trigger the callback.
+ Lease4Ptr lease = initializeLease4(straddress4_[1]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+
+ // Make sure that the callback has been invoked.
+ EXPECT_EQ(1, logs_.size());
+}
+
+void
+GenericLeaseMgrTest::testRecreateWithoutCallbacks(const std::string& access) {
+ // Register a callback.
+ lmptr_->registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq", 0,
+ Lease::TYPE_V4,
+ std::bind(&GenericLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 0, ph::_1));
+
+ // Recreate the lease manager without the callbacks.
+ ASSERT_NO_THROW(LeaseMgrFactory::recreate(access, false));
+ lmptr_ = &(LeaseMgrFactory::instance());
+
+ // Add a lease. It should not trigger the callback.
+ Lease4Ptr lease = initializeLease4(straddress4_[1]);
+ EXPECT_TRUE(lmptr_->addLease(lease));
+ EXPECT_TRUE(logs_.empty());
+}
+
+void
+GenericLeaseMgrTest::testBigStats() {
+ StatValMapList expected_stats(1);
+
+ // Create the largest possible subnet with the largest possible IPv4 pool.
+ Subnet4Ptr subnet4(new Subnet4(IOAddress("0.0.0.0"), 1, 1, 2, 3, 1));
+ subnet4->addPool(Pool4Ptr(new Pool4(IOAddress("0.0.0.0"), 1)));
+ expected_stats[0]["total-addresses"] = 2147483648;
+
+ // Create the largest possible subnet with the largest possible IA_NA pool.
+ Subnet6Ptr subnet6(new Subnet6(IOAddress("::"), 1, 1, 2, 3, 4, 1));
+ subnet6->addPool(Pool6Ptr(new Pool6(Lease::TYPE_NA, IOAddress("::"), 1)));
+
+ // Add the largest possible IA_PD pool.
+ subnet6->addPool(Pool6Ptr(new Pool6(Lease::TYPE_PD, IOAddress("8000::"), 1)));
+
+ // Commit the subnets to the configurations.
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet4);
+ CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet6);
+ ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+ // Create the expected stats list. At this point, the only stats
+ // that should be non-zero are total-addresses, total-nas, total-pds.
+ expected_stats[0]["assigned-nas"] = 0;
+ expected_stats[0]["declined-addresses"] = 0;
+ expected_stats[0]["reclaimed-declined-addresses"] = 0;
+ expected_stats[0]["assigned-pds"] = 0;
+ expected_stats[0]["reclaimed-leases"] = 0;
+
+ // Make sure stats are as expected.
+ ASSERT_NO_THROW(checkLeaseStats(expected_stats));
+
+ // Check the big integers separately.
+ int128_t const two_to_the_power_of_127(int128_t(1) << 127);
+ checkStat(StatsMgr::generateName("subnet", 1, "total-nas"), two_to_the_power_of_127);
+ checkStat(StatsMgr::generateName("subnet", 1, "total-pds"), two_to_the_power_of_127);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
new file mode 100644
index 0000000..47ec9e1
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
@@ -0,0 +1,812 @@
+// Copyright (C) 2014-2023 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 GENERIC_LEASE_MGR_UNITTEST_H
+#define GENERIC_LEASE_MGR_UNITTEST_H
+
+#include <asiolink/io_service.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/make_shared.hpp>
+#include <vector>
+#include <set>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief typedefs to simplify lease statistic testing
+struct SubnetPoolVal {
+ int64_t value_;
+ bool check_pool_;
+ SubnetPoolVal() : value_(0), check_pool_(true) {
+ };
+ SubnetPoolVal(int64_t value) : value_(value), check_pool_(true) {
+ };
+ SubnetPoolVal(int64_t value, bool check) : value_(value), check_pool_(check) {
+ };
+};
+typedef std::map<std::string, SubnetPoolVal> StatValMap;
+typedef std::pair<std::string, SubnetPoolVal> StatValPair;
+typedef std::vector<StatValMap> StatValMapList;
+typedef std::set<LeaseStatsRow> RowSet;
+
+/// @brief Test Fixture class with utility functions for LeaseMgr backends
+///
+/// It contains utility functions, like dummy lease creation.
+/// All concrete LeaseMgr test classes should be derived from it.
+class GenericLeaseMgrTest : public ::testing::Test {
+public:
+ /// @brief Universe (V4 or V6).
+ enum Universe { V4, V6 };
+
+ /// @brief A structure holding a single callback log entry.
+ ///
+ /// The @c logCallback function inserts logs of this type into the
+ /// @c logs vector. The vector can be later examined to see what
+ /// callbacks have been invoked.
+ typedef struct {
+ TrackingLeaseMgr::CallbackType type;
+ SubnetID subnet_id;
+ LeasePtr lease;
+ bool locked;
+ } Log;
+
+ /// @brief Default constructor.
+ GenericLeaseMgrTest();
+
+ /// @brief Virtual destructor.
+ virtual ~GenericLeaseMgrTest();
+
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-opens it. It must be implemented
+ /// in derived classes.
+ ///
+ /// @param u Universe (V4 or V6), required by some backends.
+ virtual void reopen(Universe u = V4) = 0;
+
+ /// @brief Initialize Lease4 Fields
+ ///
+ /// Returns a pointer to a Lease4 structure. Different values are put into
+ /// the lease according to the address passed.
+ ///
+ /// This is just a convenience function for the test methods.
+ ///
+ /// @param address Address to use for the initialization
+ ///
+ /// @return Lease4Ptr. This will not point to anything if the
+ /// initialization failed (e.g. unknown address).
+ Lease4Ptr initializeLease4(std::string address);
+
+ /// @brief Initialize Lease6 Fields
+ ///
+ /// Returns a pointer to a Lease6 structure. Different values are put into
+ /// the lease according to the address passed.
+ ///
+ /// This is just a convenience function for the test methods.
+ ///
+ /// @param address Address to use for the initialization
+ ///
+ /// @return Lease6Ptr. This will not point to anything if the
+ /// initialization failed (e.g. unknown address).
+ Lease6Ptr initializeLease6(std::string address);
+
+ /// @brief Check Leases present and different
+ ///
+ /// Checks a vector of lease pointers and ensures that all the leases
+ /// they point to are present and different. If not, a GTest assertion
+ /// will fail.
+ ///
+ /// @param leases Vector of pointers to leases
+ /// @tparam Type of the leases held in the vector: @c Lease4 or
+ /// @c Lease6.
+ template <typename T>
+ void checkLeasesDifferent(const std::vector<T>& leases) const;
+
+ /// @brief Creates leases for the test
+ ///
+ /// Creates all leases for the test and checks that they are different.
+ ///
+ /// @return vector<Lease4Ptr> Vector of pointers to leases
+ std::vector<Lease4Ptr> createLeases4();
+
+ /// @brief Creates leases for the test
+ ///
+ /// Creates all leases for the test and checks that they are different.
+ ///
+ /// @return vector<Lease6Ptr> Vector of pointers to leases
+ std::vector<Lease6Ptr> createLeases6();
+
+ /// @brief Compares StatsMgr statistics against an expected list of values
+ ///
+ /// Iterates over a list of statistic names and expected values, attempting
+ /// to fetch each from the StatsMgr and if found, compare its observed value
+ /// to the expected value. Fails if any of the expected stats are not
+ /// found or if the values do not match.
+ ///
+ /// @param expected_stats Map of expected static names and values.
+ void checkLeaseStats(const StatValMapList& expected_stats);
+
+ /// @brief Constructs a minimal IPv4 lease and adds it to the lease storage
+ ///
+ /// @param address - IPv4 address for the lease
+ /// @param subnet_id - subnet ID to which the lease belongs
+ /// @param state - the state of the lease
+ /// @param user_context - the lease's user context
+ ///
+ /// @return pointer to created Lease4
+ Lease4Ptr makeLease4(const std::string& address,
+ const SubnetID& subnet_id,
+ const uint32_t state = Lease::STATE_DEFAULT,
+ const data::ConstElementPtr user_context = data::ConstElementPtr());
+
+ /// @brief Constructs a minimal IPv6 lease and adds it to the lease storage
+ ///
+ /// The DUID is constructed from the address and prefix length.
+ ///
+ /// @param type - type of lease to create (TYPE_NA, TYPE_PD...)
+ /// @param address - IPv6 address/prefix for the lease
+ /// @param prefix_len = length of the prefix (should be 0 for TYPE_NA)
+ /// @param subnet_id - subnet ID to which the lease belongs
+ /// @param state - the state of the lease
+ /// @param user_context - the lease's user context
+ ///
+ /// @return pointer to created Lease6
+ Lease6Ptr makeLease6(const Lease::Type& type,
+ const std::string& address,
+ uint8_t prefix_len,
+ const SubnetID& subnet_id,
+ const uint32_t state = Lease::STATE_DEFAULT,
+ const data::ConstElementPtr user_context = data::ConstElementPtr());
+
+ /// @brief Callback function recording its parameters.
+ ///
+ /// It is used in the unit tests that verify that appropriate callbacks
+ /// have been invoked.
+ ///
+ /// @param type callback type.
+ /// @param subnet_id subnet identifier.
+ /// @param lease lease instance.
+ void logCallback(TrackingLeaseMgr::CallbackType type, SubnetID subnet_id,
+ LeasePtr lease);
+
+ /// @brief Counts log entries.
+ ///
+ /// It counts the logs associated with the specific callback type, subnet id
+ /// and lease type.
+ ///
+ /// @param type callback type.
+ /// @param subnet_id subnet identifier.
+ /// @return The number of callback logs associated with the specific type and
+ /// the subnet id.
+ int countLogs(TrackingLeaseMgr::CallbackType type, SubnetID subnet_id,
+ Lease::Type lease_type) const;
+
+ /// @brief checks that addLease, getLease4(addr) and deleteLease() works
+ void testBasicLease4();
+
+ /// @brief checks that invalid dates are safely handled.
+ void testMaxDate4();
+
+ /// @brief checks that infinite lifetimes do not overflow.
+ void testInfiniteLifeTime4();
+
+ /// @brief Test lease retrieval using client id.
+ void testGetLease4ClientId();
+
+ /// @brief Test lease retrieval when leases with NULL client id are present.
+ void testGetLease4NullClientId();
+
+ /// @brief Test lease retrieval using HW address.
+ void testGetLease4HWAddr1();
+
+ /// @brief Check GetLease4 methods - access by Hardware Address
+ ///
+ /// Adds leases to the database and checks that they can be accessed using
+ /// HWAddr information.
+ void testGetLease4HWAddr2();
+
+ /// @brief Get lease4 by hardware address (2)
+ ///
+ /// Check that the system can cope with getting a hardware address of
+ /// any size.
+ void testGetLease4HWAddrSize();
+
+ /// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+ ///
+ /// Adds leases to the database and checks that they can be accessed via
+ /// a combination of hardware address and subnet ID
+ void testGetLease4HWAddrSubnetId();
+
+ /// @brief Get lease4 by hardware address and subnet ID (2)
+ ///
+ /// Check that the system can cope with getting a hardware address of
+ /// any size.
+ void testGetLease4HWAddrSubnetIdSize();
+
+ /// @brief Check GetLease4 methods - access by Client ID
+ ///
+ /// Adds leases to the database and checks that they can be accessed via
+ /// the Client ID.
+ void testGetLease4ClientId2();
+
+ /// @brief Get Lease4 by client ID (2)
+ ///
+ /// Check that the system can cope with a client ID of any size.
+ void testGetLease4ClientIdSize();
+
+ /// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+ ///
+ /// Adds leases to the database and checks that they can be accessed via
+ /// a combination of client and subnet IDs.
+ void testGetLease4ClientIdSubnetId();
+
+ /// @brief Test method which returns all IPv4 leases for Subnet ID.
+ void testGetLeases4SubnetId();
+
+ /// @brief Test method which returns all IPv4 leases for Hostname.
+ void testGetLeases4Hostname();
+
+ /// @brief Test method which returns all IPv4 leases.
+ void testGetLeases4();
+
+ /// @brief Test method which returns range of IPv4 leases with paging.
+ void testGetLeases4Paged();
+
+ /// @brief Test method which returns all IPv6 leases for Subnet ID.
+ void testGetLeases6SubnetId();
+
+ /// @brief Test method which returns all IPv6 leases for Hostname.
+ void testGetLeases6Hostname();
+
+ /// @brief Test making/fetching leases with IAIDs > signed 32-bit max.
+ void testLease6LargeIaidCheck();
+
+ /// @brief Test method which returns all IPv6 leases.
+ void testGetLeases6();
+
+ /// @brief Test method which returns range of IPv6 leases with paging.
+ void testGetLeases6Paged();
+
+ /// @brief Basic Lease4 Checks
+ ///
+ /// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+ /// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
+ /// (client-id is optional and may not be present)
+ ///
+ /// @todo: check if it does overlap with @ref testGetLease4NullClientId()
+ void testLease4NullClientId();
+
+ /// @brief Check that the DHCPv4 lease can be added, removed and recreated.
+ ///
+ /// This test creates a lease, removes it and then recreates it with some
+ /// of the attributes changed. Next it verifies that the lease in the
+ /// persistent storage has been updated as expected.
+ void testRecreateLease4();
+
+ /// @brief Basic Lease6 Checks
+ ///
+ /// Checks that the addLease, getLease6 (by address) and deleteLease (with an
+ /// IPv6 address) works.
+ void testBasicLease6();
+
+ /// @brief Checks that invalid dates are safely handled.
+ void testMaxDate6();
+
+ /// @brief checks that infinite lifetimes do not overflow.
+ void testInfiniteLifeTime6();
+
+ /// @brief Checks that Lease6 can be stored with and without a hardware address.
+ void testLease6MAC();
+
+ /// @brief Checks that Lease6 stores hardware type and hardware source.
+ void testLease6HWTypeAndSource();
+
+ /// @brief Test that IPv6 lease can be added, retrieved and deleted.
+ void testAddGetDelete6();
+
+ /// @brief Check GetLease6 methods - access by DUID/IAID
+ ///
+ /// Adds leases to the database and checks that they can be accessed via
+ /// a combination of DUID and IAID.
+ void testGetLeases6DuidIaid();
+
+ /// @brief Check that the system can cope with a DUID of allowed size.
+ void testGetLeases6DuidSize();
+
+ /// @brief Check that getLease6 methods discriminate by lease type.
+ ///
+ /// Adds six leases, two per lease type all with the same duid and iad but
+ /// with alternating subnet_ids.
+ /// It then verifies that all of getLeases6() method variants correctly
+ /// discriminate between the leases based on lease type alone.
+ void testLease6LeaseTypeCheck();
+
+ /// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+ ///
+ /// Adds leases to the database and checks that they can be accessed via
+ /// a combination of DIUID and IAID.
+ void testGetLease6DuidIaidSubnetId();
+
+ /// @brief verifies getLeases6 method by DUID
+ ///
+ /// Adds 3 leases to backend and retrieves, verifes empty
+ /// retrieval of non existent DUID.
+ void testGetLeases6Duid();
+
+ /// @brief Checks that getLease6() works with different DUID sizes
+ void testGetLease6DuidIaidSubnetIdSize();
+
+ /// @brief Verify that too long hostname for Lease4 is not accepted.
+ ///
+ /// Checks that the it is not possible to create a lease when the hostname
+ /// length exceeds 255 characters.
+ void testLease4InvalidHostname();
+
+ /// @brief Verify that too long hostname for Lease6 is not accepted.
+ ///
+ /// Checks that the it is not possible to create a lease when the hostname
+ /// length exceeds 255 characters.
+ void testLease6InvalidHostname();
+
+ /// @brief Lease4 update test
+ ///
+ /// Checks that the code is able to update an IPv4 lease in the database.
+ void testUpdateLease4();
+
+ /// @brief Lease4 concurrent update test
+ ///
+ /// Checks that the code is not able to concurrently update an IPv4 lease in
+ /// the database.
+ void testConcurrentUpdateLease4();
+
+ /// @brief Lease6 update test
+ ///
+ /// Checks that the code is able to update an IPv6 lease in the database.
+ void testUpdateLease6();
+
+ /// @brief Lease6 concurrent update test
+ ///
+ /// Checks that the code is not able to concurrently update an IPv6 lease in
+ /// the database.
+ void testConcurrentUpdateLease6();
+
+ /// @brief Check that the IPv6 lease can be added, removed and recreated.
+ ///
+ /// This test creates a lease, removes it and then recreates it with some
+ /// of the attributes changed. Next it verifies that the lease in the
+ /// persistent storage has been updated as expected.
+ void testRecreateLease6();
+
+ /// @brief Verifies that a null DUID is not allowed.
+ void testNullDuid();
+
+ /// @brief Verifies that the backend reports expected version numbers.
+ /// @param major Expected major version to be reported.
+ /// @param minor Expected minor version to be reported.
+ void testVersion(int major, int minor);
+
+ /// @brief Checks that the expired DHCPv4 leases can be retrieved.
+ ///
+ /// This test checks the following:
+ /// - all expired and not reclaimed leases are returned
+ /// - number of leases returned can be limited
+ /// - leases are returned in the order from the most expired to the
+ /// least expired
+ /// - reclaimed leases are not returned.
+ void testGetExpiredLeases4();
+
+ /// @brief Checks that the expired IPv6 leases can be retrieved.
+ ///
+ /// This test checks the following:
+ /// - all expired and not reclaimed leases are returned
+ /// - number of leases returned can be limited
+ /// - leases are returned in the order from the most expired to the
+ /// least expired
+ /// - reclaimed leases are not returned.
+ void testGetExpiredLeases6();
+
+ /// @brief Checks that DHCPv4 leases with infinite valid lifetime
+ /// will never expire.
+ void testInfiniteAreNotExpired4();
+
+ /// @brief Checks that DHCPv6 leases with infinite valid lifetime
+ /// will never expire.
+ void testInfiniteAreNotExpired6();
+
+ /// @brief Checks that declined IPv4 leases that have expired can be retrieved.
+ ///
+ /// This test checks that the following:
+ /// - all expired and not reclaimed leases are returned, regardless if
+ /// they're normal or declined
+ /// - the order in which they're updated in LeaseMgr doesn't matter
+ /// - leases are returned in the order from most expired to the least
+ /// expired
+ void testGetDeclinedLeases4();
+
+ /// @brief Checks that declined IPv6 leases that have expired can be retrieved.
+ ///
+ /// This test checks that the following:
+ /// - all expired and not reclaimed leases are returned, regardless if
+ /// they're normal or declined
+ /// - the order in which they're updated in LeaseMgr doesn't matter
+ /// - leases are returned in the order from most expired to the least
+ /// expired
+ void testGetDeclinedLeases6();
+
+ /// @brief Checks that selected expired-reclaimed IPv6 leases
+ /// are removed.
+ ///
+ /// This creates a number of DHCPv6 leases and marks some of them
+ /// as expired-reclaimed. It later verifies that the expired-reclaimed
+ /// leases can be removed.
+ void testDeleteExpiredReclaimedLeases6();
+
+ /// @brief Checks that selected expired-reclaimed IPv4 leases
+ /// are removed.
+ ///
+ /// This creates a number of DHCPv4 leases and marks some of them
+ /// as expired-reclaimed. It later verifies that the expired-reclaimed
+ /// leases can be removed.
+ void testDeleteExpiredReclaimedLeases4();
+
+ /// @brief Check that the IPv4 lease statistics can be recounted
+ ///
+ /// This test creates two subnets and several leases associated with
+ /// them, then verifies that lease statistics are recalculated correctly
+ /// after altering the lease states in various ways.
+ void testRecountLeaseStats4();
+
+ /// @brief Check that the IPv6 lease statistics can be recounted
+ ///
+ /// This test creates two subnets and several leases associated with
+ /// them, then verifies that lease statistics are recalculated correctly
+ /// after altering the lease states in various ways.
+ void testRecountLeaseStats6();
+
+
+ /// @brief Check if wipeLeases4 works properly.
+ ///
+ /// This test creates a bunch of leases in several subnets and then
+ /// attempts to delete them, one subnet at a time.
+ void testWipeLeases4();
+
+ /// @brief Check if wipeLeases6 works properly.
+ ///
+ /// This test creates a bunch of leases in several subnets and then
+ /// attempts to delete them, one subnet at a time.
+ void testWipeLeases6();
+
+ /// @brief Checks operation of v4 LeaseStatsQuery variants
+ ///
+ /// It creates three subnets with leases in various states in
+ /// each. It runs and verifies the returned query contents for
+ /// each of the v4 startLeaseQuery variants:
+ ///
+ /// - startSubnetLeaseQuery()
+ /// - startSubneRangetLeaseQuery()
+ /// - startLeaseQuery()
+ ///
+ void testLeaseStatsQuery4();
+
+ /// @brief Checks operation of v6 LeaseStatsQuery variants
+ ///
+ /// It creates three subnets with leases in various states in
+ /// each. It runs and verifies the returned query contents for
+ /// each of the v6 startLeaseQuery variants:
+ ///
+ /// - startSubnetLeaseQuery()
+ /// - startSubneRangetLeaseQuery()
+ /// - startLeaseQuery()
+ ///
+ void testLeaseStatsQuery6();
+
+ /// @brief Checks if v4 LeaseStatsQuery can get bad attribution.
+ ///
+ /// It creates two subnets with leases and move one from the first
+ /// to the second. If counters are not updated this can lead to
+ /// bad attribution i.e. a lease is counted in a subnet when it
+ /// belongs to another one.
+ ///
+ void testLeaseStatsQueryAttribution4();
+
+ /// @brief Checks if v6 LeaseStatsQuery can get bad attribution.
+ ///
+ /// It creates two subnets with leases and move one from the first
+ /// to the second. If counters are not updated this can lead to
+ /// bad attribution i.e. a lease is counted in a subnet when it
+ /// belongs to another one.
+ ///
+ /// @note We can check the lease type change too but in the real
+ /// world this never happens.
+ void testLeaseStatsQueryAttribution6();
+
+ /// @brief Compares LeaseQueryStats content to expected set of rows
+ ///
+ /// @param qry - a started LeaseStatsQuery
+ /// @param row_set - set of rows expected to be found in the query rows
+ void checkQueryAgainstRowSet(const LeaseStatsQueryPtr& qry, const RowSet& row_set);
+
+ /// @brief Checks if specified range of leases was returned.
+ ///
+ /// @param returned collection of leases returned.
+ /// @param expected_addresses ordered collection of expected addresses.
+ void checkLeaseRange(const Lease4Collection& returned,
+ const std::vector<std::string>& expected_addresses);
+
+ /// @brief Create a user-context with a given list of classes
+ ///
+ /// Creates an Element::map with the following content:
+ ///
+ /// {
+ /// "ISC": {
+ /// "classes": [ "class0", "class1", ... ]
+ /// }
+ /// }
+ ///
+ /// @param classes list of classes to include in the context
+ /// @return ElementPtr containing the user-context
+ data::ElementPtr makeContextWithClasses(const std::list<ClientClass>& classes);
+
+ /// @brief Tests class lease counts when adding, updating, and deleting
+ /// leases with class lists.
+ void testClassLeaseCount4();
+
+ /// @brief Tests class lease counts when adding, updating, and deleting
+ /// leases with class lists.
+ ///
+ /// @param ltype type of lease, either Lease::TYPE_NA or Lease::TYPE_PD
+ void testClassLeaseCount6(Lease::Type ltype);
+
+ /// @brief Checks a few v4 lease limit checking scenarios.
+ void testLeaseLimits4();
+
+ /// @brief Checks a few v6 lease limit checking scenarios.
+ void testLeaseLimits6();
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv4 lease is added.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackAddLease4(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv6 address lease is added.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackAddLeaseNA(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv6 prefix lease is added.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackAddLeasePD(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv4 lease is updated.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackUpdateLease4(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv6 address lease is updated.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackUpdateLeaseNA(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv6 prefix lease is updated.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackUpdateLeasePD(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv4 lease is deleted.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackDeleteLease4(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv6 address lease is deleted.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackDeleteLeaseNA(bool expect_locked);
+
+ /// @brief Checks if the backends call the callbacks when an
+ /// IPv6 prefix lease is deleted.
+ ///
+ /// @param expect_locked a boolean flag indicating if the test should
+ /// expect that the lease is locked before the callback.
+ void testTrackDeleteLeasePD(bool expect_locked);
+
+ /// @brief Checks that the lease manager can be recreated and its
+ /// registered callbacks preserved, if desired.
+ ///
+ /// @param access database connection string used for recreating the
+ /// lease manager.
+ void testRecreateWithCallbacks(const std::string& access);
+
+ /// @brief Checks that the lease manager can be recreated without the
+ /// previously registered callbacks.
+ ///
+ /// @param access database connection string used for recreating the
+ /// lease manager.
+ void testRecreateWithoutCallbacks(const std::string& access);
+
+ /// @brief Checks that statistic with big integer values are handled correctly.
+ void testBigStats();
+
+ /// @brief String forms of IPv4 addresses
+ std::vector<std::string> straddress4_;
+
+ /// @brief IOAddress forms of IPv4 addresses
+ std::vector<isc::asiolink::IOAddress> ioaddress4_;
+
+ /// @brief String forms of IPv6 addresses
+ std::vector<std::string> straddress6_;
+
+ /// @brief Types of IPv6 Leases
+ std::vector<Lease::Type> leasetype6_;
+
+ /// @brief IOAddress forms of IPv6 addresses
+ std::vector<isc::asiolink::IOAddress> ioaddress6_;
+
+ /// Recorded callback logs.
+ std::vector<Log> logs_;
+
+ /// @brief Pointer to the lease manager
+ TrackingLeaseMgr* lmptr_;
+};
+
+class LeaseMgrDbLostCallbackTest : public ::testing::Test {
+public:
+ LeaseMgrDbLostCallbackTest()
+ : db_lost_callback_called_(0), db_recovered_callback_called_(0),
+ db_failed_callback_called_(0),
+ io_service_(boost::make_shared<isc::asiolink::IOService>()) {
+ db::DatabaseConnection::db_lost_callback_ = 0;
+ db::DatabaseConnection::db_recovered_callback_ = 0;
+ db::DatabaseConnection::db_failed_callback_ = 0;
+ LeaseMgr::setIOService(io_service_);
+ TimerMgr::instance()->setIOService(io_service_);
+ }
+
+ virtual ~LeaseMgrDbLostCallbackTest() {
+ db::DatabaseConnection::db_lost_callback_ = 0;
+ db::DatabaseConnection::db_recovered_callback_ = 0;
+ db::DatabaseConnection::db_failed_callback_ = 0;
+ LeaseMgr::setIOService(isc::asiolink::IOServicePtr());
+ TimerMgr::instance()->unregisterTimers();
+ }
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and wipe out any residual lease manager
+ virtual void SetUp();
+
+ /// @brief Pre-text exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created and toss our lease manager.
+ virtual void TearDown();
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns the back end specific connection
+ /// string
+ virtual std::string validConnectString() = 0;
+
+ /// @brief Abstract method which returns invalid back end specific connection
+ /// string
+ virtual std::string invalidConnectString() = 0;
+
+ /// @brief Verifies open failures do NOT invoke db lost callback
+ ///
+ /// The db lost callback should only be invoked after successfully
+ /// opening the DB and then subsequently losing it. Failing to
+ /// open should be handled directly by the application layer.
+ void testNoCallbackOnOpenFailure();
+
+ /// @brief Verifies the lease manager's behavior if DB connection is lost
+ ///
+ /// This function creates a lease manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked
+ void testDbLostAndRecoveredCallback();
+
+ /// @brief Verifies the lease manager's behavior if DB connection is lost
+ ///
+ /// This function creates a lease manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked
+ void testDbLostAndFailedCallback();
+
+ /// @brief Verifies the lease manager's behavior if DB connection is lost
+ ///
+ /// This function creates a lease manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndRecoveredAfterTimeoutCallback();
+
+ /// @brief Verifies the lease manager's behavior if DB connection is lost
+ ///
+ /// This function creates a lease manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndFailedAfterTimeoutCallback();
+
+ /// @brief Callback function registered with the lease manager
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_lost_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_lost_callback function
+ uint32_t db_lost_callback_called_;
+
+ /// @brief Callback function registered with the lease manager
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_recovered_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_recovered_callback function
+ uint32_t db_recovered_callback_called_;
+
+ /// @brief Callback function registered with the lease manager
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_failed_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_failed_callback function
+ uint32_t db_failed_callback_called_;
+
+ /// The IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/tests/host_cache_unittest.cc b/src/lib/dhcpsrv/tests/host_cache_unittest.cc
new file mode 100644
index 0000000..fa2e643
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/host_cache_unittest.cc
@@ -0,0 +1,1000 @@
+// Copyright (C) 2018-2023 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 <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/cache_host_data_source.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/testutils/host_data_source_utils.h>
+#include <dhcpsrv/testutils/memory_host_data_source.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test dump cache class.
+class TestHostCache : public MemHostDataSource, public CacheHostDataSource {
+public:
+
+ /// Constructor
+ TestHostCache() : adds_(0), inserts_(0) { }
+
+ /// Destructor
+ virtual ~TestHostCache() { }
+
+ /// Override add
+ void add(const HostPtr& host) {
+ isc_throw(NotImplemented,
+ "add is not implemented: " << host->toText());
+ }
+
+ /// Insert
+ size_t insert(const ConstHostPtr& host, bool overwrite) {
+ if (overwrite) {
+ ++inserts_;
+ } else {
+ ++adds_;
+ }
+ HostPtr host_copy(new Host(*host));
+ store_.push_back(host_copy);
+ return (0);
+ }
+
+ /// Remove
+ bool remove(const HostPtr& host) {
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if (*h == host) {
+ store_.erase(h);
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ /// Flush
+ void flush(size_t) {
+ isc_throw(NotImplemented, "flush is not implemented");
+ }
+
+ /// Size
+ size_t size() const {
+ return (store_.size());
+ }
+
+ /// Capacity
+ size_t capacity() const {
+ return (0);
+ }
+
+ /// Type
+ string getType() const {
+ return ("cache");
+ }
+
+ /// Add counter
+ size_t adds_;
+
+ /// Insert counter
+ size_t inserts_;
+};
+
+/// @brief TestHostCache pointer type
+typedef boost::shared_ptr<TestHostCache> TestHostCachePtr;
+
+/// @brief Test data source class.
+class TestHostDataSource : public MemHostDataSource {
+public:
+
+ /// Destructor
+ virtual ~TestHostDataSource() { }
+
+ /// Type
+ string getType() const {
+ return ("test");
+ }
+};
+
+/// @brief TestHostDataSource pointer type
+typedef boost::shared_ptr<TestHostDataSource> TestHostDataSourcePtr;
+
+/// @brief Test fixture for testing cache feature.
+class HostCacheTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ HostCacheTest() {
+ HostMgr::create();
+
+ // Host cache.
+ hcptr_.reset(new TestHostCache());
+ auto cacheFactory = [this](const DatabaseConnection::ParameterMap&) {
+ return (hcptr_);
+ };
+ HostDataSourceFactory::registerFactory("cache", cacheFactory);
+ HostMgr::addBackend("type=cache");
+
+ // Host data source.
+ memptr_.reset(new TestHostDataSource());
+ auto testFactory = [this](const DatabaseConnection::ParameterMap&) {
+ return (memptr_);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+ }
+
+ /// @brief Destructor.
+ virtual ~HostCacheTest() {
+ HostDataSourceFactory::deregisterFactory("test");
+ HostDataSourceFactory::deregisterFactory("cache");
+ }
+
+ /// @brief Test host cache.
+ TestHostCachePtr hcptr_;
+
+ /// @brief Test host data source.
+ TestHostDataSourcePtr memptr_;
+};
+
+// Check basic cache feature for IPv4.
+TEST_F(HostCacheTest, identifier4) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->inserts_);
+ ASSERT_TRUE(memptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1",
+ Host::IDENT_HWADDR);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+ const IOAddress& address = host->getIPv4Reservation();
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(memptr_->add(host));
+
+ // Try to get it cached.
+ ConstHostPtr got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify it was cached.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+
+ // Remove it from test host data source.
+ EXPECT_TRUE(memptr_->del(host->getIPv4SubnetID(), address));
+
+ // It is cached so we can still get it.
+ got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Even by address.
+ got = HostMgr::instance().get4(host->getIPv4SubnetID(), address);
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify cache status.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+}
+
+// Check basic cache feature for IPv6.
+TEST_F(HostCacheTest, identifier6) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->inserts_);
+ ASSERT_TRUE(memptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID,
+ false);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+
+ // Get the address.
+ IPv6ResrvRange resrvs = host->getIPv6Reservations();
+ ASSERT_EQ(1, std::distance(resrvs.first, resrvs.second));
+ const IOAddress& address = resrvs.first->second.getPrefix();
+ ASSERT_EQ("2001:db8::1", address.toText());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(memptr_->add(host));
+
+ // Try to get it cached.
+ ConstHostPtr got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify it was cached.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+
+ // Remove it from test host data source.
+ EXPECT_TRUE(memptr_->del(host->getIPv6SubnetID(), address));
+
+ // It is cached so we can still get it.
+ got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Even by address.
+ got = HostMgr::instance().get6(host->getIPv6SubnetID(), address);
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify cache status.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+}
+
+// Check by address caching for IPv4.
+TEST_F(HostCacheTest, address4) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->inserts_);
+ ASSERT_TRUE(memptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1",
+ Host::IDENT_HWADDR);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+ const IOAddress& address = host->getIPv4Reservation();
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(memptr_->add(host));
+
+ // Try to get it cached.
+ ConstHostPtr got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ address);
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify it was cached.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+
+ // Remove it from test host data source.
+ EXPECT_TRUE(memptr_->del(host->getIPv4SubnetID(), address));
+
+ // It is cached so we can still get it.
+ got = HostMgr::instance().get4(host->getIPv4SubnetID(), address);
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Even by identifier.
+ got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify cache status.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+}
+
+// Check by address caching for IPv6.
+TEST_F(HostCacheTest, address6) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->inserts_);
+ ASSERT_TRUE(memptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID,
+ false);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+
+ // Get the address.
+ IPv6ResrvRange resrvs = host->getIPv6Reservations();
+ ASSERT_EQ(1, std::distance(resrvs.first, resrvs.second));
+ const IOAddress& address = resrvs.first->second.getPrefix();
+ ASSERT_EQ("2001:db8::1", address.toText());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(memptr_->add(host));
+
+ // Try to get it cached.
+ ConstHostPtr got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ address);
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify it was cached.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+
+ // Remove it from test host data source.
+ EXPECT_TRUE(memptr_->del(host->getIPv6SubnetID(), address));
+
+ // It is cached so we can still get it.
+ got = HostMgr::instance().get6(host->getIPv6SubnetID(), address);
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Even by identifier.
+ got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ HostDataSourceUtils::compareHosts(got, host);
+
+ // Verify cache status.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->inserts_);
+}
+
+// Check negative cache feature for IPv4.
+TEST_F(HostCacheTest, negativeIdentifier4) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->adds_);
+ ASSERT_TRUE(memptr_);
+ ASSERT_FALSE(HostMgr::instance().getNegativeCaching());
+
+ // Create a host reservation.
+ // We will not add it anywhere, just will use its values.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ ASSERT_TRUE(host);
+
+ // Try to get it cached.
+ ConstHostPtr got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_FALSE(got);
+ // Again to be sure it was not negatively cached.
+ got = HostMgr::instance().get4Any(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_FALSE(got);
+
+ // Enable negative caching.
+ HostMgr::instance().setNegativeCaching(true);
+ ASSERT_TRUE(HostMgr::instance().getNegativeCaching());
+
+ // Try it again. There is no such host, but this time negative cache is enabled,
+ // so this negative response will be added to the cache.
+ got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_FALSE(got);
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->adds_);
+ got = HostMgr::instance().get4Any(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ EXPECT_TRUE(got->getNegative());
+
+ // Look at inside the negative cached value.
+ EXPECT_EQ(host->getIPv4SubnetID(), got->getIPv4SubnetID());
+ EXPECT_EQ(host->getIdentifierType(), got->getIdentifierType());
+ ASSERT_EQ(host->getIdentifier().size(), got->getIdentifier().size());
+ EXPECT_EQ(0, memcmp(&host->getIdentifier()[0],
+ &got->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_EQ("0.0.0.0", got->getIPv4Reservation().toText());
+
+ // get4() (vs get4Any()) does not return negative cached values.
+ got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ EXPECT_FALSE(got);
+
+ // Verify cache status.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->adds_);
+ EXPECT_EQ(0, hcptr_->inserts_);
+
+ // We can verify other overloads of get4() but the hwaddr/duid is
+ // not implemented by the memory test backend and the negative cache
+ // entry has no IP reservation when inserted by the host manager.
+}
+
+// Check negative cache feature for IPv6.
+TEST_F(HostCacheTest, negativeIdentifier6) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->adds_);
+ ASSERT_TRUE(memptr_);
+
+ // Create a host reservation.
+ // We will not add it anywhere, just will use its values.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID,
+ false);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+
+ // Do not add it to the host data source.
+
+ // Try to get it cached.
+ ConstHostPtr got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_FALSE(got);
+ // Again to be sure it was not negatively cached.
+ got = HostMgr::instance().get6Any(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_FALSE(got);
+
+ // Enable negative caching.
+ HostMgr::instance().setNegativeCaching(true);
+ ASSERT_TRUE(HostMgr::instance().getNegativeCaching());
+
+ // Try it but it will be cached only when get6 (vs get6Any) is called.
+ got = HostMgr::instance().get6Any(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_FALSE(got);
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->adds_);
+
+ got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_FALSE(got);
+
+ // There is a negative cached value now.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->adds_);
+ got = HostMgr::instance().get6Any(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(got);
+ EXPECT_TRUE(got->getNegative());
+
+ // Look at inside the negative cached value.
+ EXPECT_EQ(host->getIPv6SubnetID(), got->getIPv6SubnetID());
+ EXPECT_EQ(host->getIdentifierType(), got->getIdentifierType());
+ ASSERT_EQ(host->getIdentifier().size(), got->getIdentifier().size());
+ EXPECT_EQ(0, memcmp(&host->getIdentifier()[0],
+ &got->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_FALSE(got->hasIPv6Reservation());
+
+ // get6() (vs get6Any()) does not return negative cached values.
+ got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ EXPECT_FALSE(got);
+
+ // Verify cache status.
+ EXPECT_EQ(1, hcptr_->size());
+ EXPECT_EQ(1, hcptr_->adds_);
+ EXPECT_EQ(0, hcptr_->inserts_);
+
+ // No other tests, cf negativeIdentifier4 end comment.
+}
+
+// Check that negative caching by address is not done for IPv4.
+TEST_F(HostCacheTest, negativeAddress4) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->adds_);
+ ASSERT_TRUE(memptr_);
+
+ // Create a host reservation.
+ // We will not add it anywhere, just will use its values.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1",
+ Host::IDENT_HWADDR);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+ const IOAddress& address = host->getIPv4Reservation();
+
+ // Do not add it to the host data source.
+
+ // Enable negative caching.
+ HostMgr::instance().setNegativeCaching(true);
+ ASSERT_TRUE(HostMgr::instance().getNegativeCaching());
+
+ // Try to get it in negative cache.
+ ConstHostPtr got = HostMgr::instance().get4(host->getIPv4SubnetID(),
+ address);
+ ASSERT_FALSE(got);
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->adds_);
+}
+
+// Check that negative caching by address is not done for IPv6.
+TEST_F(HostCacheTest, negativeAddress6) {
+ // Check we have what we need.
+ ASSERT_TRUE(hcptr_);
+ EXPECT_TRUE(HostMgr::checkCacheBackend());
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->adds_);
+ ASSERT_TRUE(memptr_);
+
+ // Create a host reservation.
+ // We will not add it anywhere, just will use its values.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID,
+ false);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+
+ // Get the address.
+ IPv6ResrvRange resrvs = host->getIPv6Reservations();
+ ASSERT_EQ(1, std::distance(resrvs.first, resrvs.second));
+ const IOAddress& address = resrvs.first->second.getPrefix();
+ ASSERT_EQ("2001:db8::1", address.toText());
+
+ // Do not add it to the host data source.
+
+ // Enable negative caching.
+ HostMgr::instance().setNegativeCaching(true);
+ ASSERT_TRUE(HostMgr::instance().getNegativeCaching());
+
+ // Try to get it in negative cache.
+ ConstHostPtr got = HostMgr::instance().get6(host->getIPv6SubnetID(),
+ address);
+ ASSERT_FALSE(got);
+ EXPECT_EQ(0, hcptr_->size());
+ EXPECT_EQ(0, hcptr_->adds_);
+}
+
+/// @brief Test one backend class.
+///
+/// This class has one entry which is returned to any lookup.
+class TestOneBackend : public BaseHostDataSource {
+public:
+
+ /// Constructor
+ TestOneBackend() : value_() { }
+
+ /// Destructor
+ virtual ~TestOneBackend() { }
+
+ ConstHostCollection getAll(const Host::IdentifierType&, const uint8_t*,
+ const size_t) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAll4(const SubnetID&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAll6(const SubnetID&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAll6(const IOAddress&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAllbyHostname(const std::string&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAllbyHostname4(const std::string&,
+ const SubnetID&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAllbyHostname6(const std::string&,
+ const SubnetID&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getPage4(const SubnetID&, size_t&, uint64_t,
+ const HostPageSize&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getPage6(const SubnetID&, size_t&, uint64_t,
+ const HostPageSize&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getPage4(size_t&, uint64_t,
+ const HostPageSize&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getPage6(size_t&, uint64_t,
+ const HostPageSize&) const {
+ return (getCollection());
+ }
+
+ ConstHostCollection getAll4(const IOAddress&) const {
+ return (getCollection());
+ }
+
+ ConstHostPtr get4(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get4(const SubnetID&, const IOAddress&) const {
+ return (getOne());
+ }
+
+ ConstHostCollection
+ getAll4(const SubnetID&, const asiolink::IOAddress&) const {
+ return (getCollection());
+ }
+
+ ConstHostPtr get6(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get6(const IOAddress&, const uint8_t) const {
+ return (getOne());
+ }
+
+ ConstHostPtr get6(const SubnetID&, const IOAddress&) const {
+ return (getOne());
+ }
+
+ ConstHostCollection
+ getAll6(const SubnetID&, const IOAddress&) const {
+ return (getCollection());
+ }
+
+ void add(const HostPtr&) {
+ }
+
+ bool del(const SubnetID&, const IOAddress&) {
+ return (false);
+ }
+
+ bool del4(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) {
+ return (false);
+ }
+
+ bool del6(const SubnetID&, const Host::IdentifierType&,
+ const uint8_t*, const size_t) {
+ return (false);
+ }
+
+ std::string getType() const {
+ return ("one");
+ }
+
+ bool setIPReservationsUnique(const bool) {
+ return (true);
+ }
+
+ /// Specific methods
+
+ /// @brief Set the entry
+ void setValue(const HostPtr& value) {
+ value_ = value;
+ }
+
+protected:
+ /// @brief Return collection
+ ConstHostCollection getCollection() const {
+ ConstHostCollection hosts;
+ hosts.push_back(value_);
+ return (hosts);
+ }
+
+ /// @brief Return one entry
+ ConstHostPtr getOne() const {
+ return(value_);
+ }
+
+ /// #brief The value
+ HostPtr value_;
+};
+
+/// @brief TestOneBackend pointer type
+typedef boost::shared_ptr<TestOneBackend> TestOneBackendPtr;
+
+/// @brief Test no cache class.
+///
+/// This class looks like a cache but throws when insert() is called.
+class TestNoCache : public MemHostDataSource, public CacheHostDataSource {
+public:
+
+ /// Destructor
+ virtual ~TestNoCache() { }
+
+ /// Override add
+ void add(const HostPtr& host) {
+ isc_throw(NotImplemented,
+ "add is not implemented: " << host->toText());
+ }
+
+ /// Insert throws
+ size_t insert(const ConstHostPtr& host, bool overwrite) {
+ isc_throw(NotImplemented,
+ "insert is not implemented: " << host->toText()
+ << " with overwrite: " << overwrite);
+ }
+
+ /// Remove throws
+ bool remove(const HostPtr& host) {
+ isc_throw(NotImplemented,
+ "remove is not implemented: " << host->toText());
+ }
+
+ /// Flush
+ void flush(size_t) {
+ isc_throw(NotImplemented, "flush is not implemented");
+ }
+
+ /// Size
+ size_t size() const {
+ return (0);
+ }
+
+ /// Capacity
+ size_t capacity() const {
+ return (0);
+ }
+
+ /// Type
+ string getType() const {
+ return ("nocache");
+ }
+};
+
+/// @brief TestNoCache pointer type
+typedef boost::shared_ptr<TestNoCache> TestNoCachePtr;
+
+/// @brief Test fixture for testing generic negative cache handling.
+class NegativeCacheTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ NegativeCacheTest() {
+ HostMgr::create();
+
+ // No cache.
+ ncptr_.reset(new TestNoCache());
+ auto nocacheFactory = [this](const DatabaseConnection::ParameterMap&) {
+ return (ncptr_);
+ };
+ HostDataSourceFactory::registerFactory("nocache", nocacheFactory);
+ HostMgr::addBackend("type=nocache");
+
+ // One backend.
+ obptr_.reset(new TestOneBackend());
+ auto oneFactory = [this](const DatabaseConnection::ParameterMap&) {
+ return (obptr_);
+ };
+ HostDataSourceFactory::registerFactory("one", oneFactory);
+ HostMgr::addBackend("type=one");
+ }
+
+ /// @brief Destructor.
+ virtual ~NegativeCacheTest() {
+ HostDataSourceFactory::deregisterFactory("one");
+ HostDataSourceFactory::deregisterFactory("nocache");
+ }
+
+ /// @brief Test one backend.
+ TestOneBackendPtr obptr_;
+
+ /// @brief Test no cache.
+ TestNoCachePtr ncptr_;
+
+ /// Test methods
+
+ /// @brief Set negative caching.
+ void setNegativeCaching() {
+ ASSERT_TRUE(HostMgr::instance().checkCacheBackend());
+ ASSERT_FALSE(HostMgr::instance().getNegativeCaching());
+ HostMgr::instance().setNegativeCaching(true);
+ ASSERT_TRUE(HostMgr::instance().getNegativeCaching());
+ }
+
+ void testGetAll();
+ void testGet4();
+ void testGet6();
+};
+
+/// Verify that getAll* methods ignore negative caching.
+void NegativeCacheTest::testGetAll() {
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1",
+ Host::IDENT_HWADDR);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getHWAddress());
+ ASSERT_EQ("192.0.2.1", host->getIPv4Reservation().toText());
+
+ // Make it a negative cached entry.
+ host->setNegative(true);
+ ASSERT_TRUE(host->getNegative());
+
+ // Set the backend with it.
+ obptr_->setValue(host);
+
+ // Verifies getAll* return a collection with it.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts =
+ HostMgr::instance().getAll(host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(host, hosts[0]);
+
+ ASSERT_NO_THROW(hosts =
+ HostMgr::instance().getAll4(host->getIPv4Reservation()));
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(host, hosts[0]);
+}
+
+/// Verify that get4* methods handle negative caching.
+void NegativeCacheTest::testGet4() {
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1",
+ Host::IDENT_HWADDR);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getHWAddress());
+ ASSERT_EQ("192.0.2.1", host->getIPv4Reservation().toText());
+
+ // Make it a negative cached entry.
+ host->setNegative(true);
+ ASSERT_TRUE(host->getNegative());
+
+ // Set the backend with it.
+ obptr_->setValue(host);
+
+ // Verifies get4 overloads return a null pointer.
+ ConstHostPtr got;
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get4(host->getIPv4SubnetID(),
+ host->getIPv4Reservation()));
+ EXPECT_FALSE(got);
+
+ // Only getAny returns the negative cached entry.
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get4Any(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_TRUE(got);
+ EXPECT_EQ(host, got);
+}
+
+/// Verify that get6* methods handle negative caching.
+void NegativeCacheTest::testGet6() {
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID,
+ false);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getDuid());
+
+ // Get the address.
+ IPv6ResrvRange resrvs = host->getIPv6Reservations();
+ ASSERT_EQ(1, std::distance(resrvs.first, resrvs.second));
+ const IOAddress& address = resrvs.first->second.getPrefix();
+ ASSERT_EQ("2001:db8::1", address.toText());
+
+ // Make it a negative cached entry.
+ host->setNegative(true);
+ ASSERT_TRUE(host->getNegative());
+
+ // Set the backend with it.
+ obptr_->setValue(host);
+
+ // Verifies get6 overloads return a null pointer.
+ ConstHostPtr got;
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get6(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got = HostMgr::instance().get6(address, 128));
+ EXPECT_FALSE(got);
+
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get6(host->getIPv6SubnetID(),
+ address));
+ EXPECT_FALSE(got);
+
+ // Only getAny returns the negative cached entry.
+ ASSERT_NO_THROW(got =
+ HostMgr::instance().get6Any(host->getIPv6SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size()));
+ EXPECT_TRUE(got);
+ EXPECT_EQ(host, got);
+}
+
+/// Verify that getAll* methods ignore negative caching.
+TEST_F(NegativeCacheTest, getAll) {
+ testGetAll();
+}
+
+/// Verify that get4* methods handle negative caching.
+TEST_F(NegativeCacheTest, get4) {
+ testGet4();
+}
+
+/// Verify that get6* methods handle negative caching.
+TEST_F(NegativeCacheTest, get6) {
+ testGet6();
+}
+
+/// Verify that getAll* methods behavior does not change with
+/// host manager negative caching.
+TEST_F(NegativeCacheTest, getAllwithNegativeCaching) {
+ setNegativeCaching();
+ testGetAll();
+}
+
+/// Verify that get4* methods behavior does not change with
+/// host manager negative caching.
+TEST_F(NegativeCacheTest, get4withNegativeCaching) {
+ setNegativeCaching();
+ testGet4();
+}
+
+/// Verify that get6* methods behavior does not change with
+/// host manager negative caching.
+TEST_F(NegativeCacheTest, get6withNegativeCaching) {
+ setNegativeCaching();
+ testGet6();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc b/src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc
new file mode 100644
index 0000000..35ef6ee
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/host_data_source_factory_unittest.cc
@@ -0,0 +1,202 @@
+// Copyright (C) 2018-2021 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 <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/testutils/memory_host_data_source.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+// @brief Register memFactory
+bool registerFactory() {
+ return (HostDataSourceFactory::registerFactory("mem", memFactory));
+}
+
+// @brief Derive mem1 class
+class Mem1HostDataSource : public MemHostDataSource {
+public:
+ virtual string getType() const {
+ return ("mem1");
+ }
+};
+
+// @brief Factory of mem1
+HostDataSourcePtr
+mem1Factory(const DatabaseConnection::ParameterMap&) {
+ return (HostDataSourcePtr(new Mem1HostDataSource()));
+}
+
+// @brief Register mem1Factory
+bool registerFactory1() {
+ return (HostDataSourceFactory::registerFactory("mem1", mem1Factory));
+}
+
+// @brief Derive mem2 class
+class Mem2HostDataSource : public MemHostDataSource {
+public:
+ virtual string getType() const {
+ return ("mem2");
+ }
+};
+
+// @brief Factory of mem2
+HostDataSourcePtr
+mem2Factory(const DatabaseConnection::ParameterMap&) {
+ return (HostDataSourcePtr(new Mem2HostDataSource()));
+}
+
+// @brief Register mem2Factory
+bool registerFactory2() {
+ return (HostDataSourceFactory::registerFactory("mem2", mem2Factory));
+}
+
+// @brief Factory function returning 0
+HostDataSourcePtr
+factory0(const DatabaseConnection::ParameterMap&) {
+ return (HostDataSourcePtr());
+}
+
+// @brief Test fixture class
+class HostDataSourceFactoryTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ HostDataSourceFactoryTest() = default;
+
+ /// @brief Destructor
+ virtual ~HostDataSourceFactoryTest() = default;
+
+private:
+ // @brief Prepares the class for a test.
+ virtual void SetUp() {
+ }
+
+ // @brief Cleans up after the test.
+ virtual void TearDown() {
+ sources_.clear();
+ HostDataSourceFactory::deregisterFactory("mem");
+ HostDataSourceFactory::deregisterFactory("mem1");
+ HostDataSourceFactory::deregisterFactory("mem2");
+ }
+public:
+ HostDataSourceList sources_;
+};
+
+// Verify a factory can be registered and only once.
+TEST_F(HostDataSourceFactoryTest, registerFactory) {
+ EXPECT_TRUE(registerFactory());
+
+ // Only once
+ EXPECT_FALSE(registerFactory());
+}
+
+// Verify a factory registration can be checked.
+TEST_F(HostDataSourceFactoryTest, registeredFactory) {
+ // Not yet registered
+ EXPECT_FALSE(HostDataSourceFactory::registeredFactory("mem"));
+ EXPECT_FALSE(HostDataSourceFactory::registeredFactory("mem1"));
+
+ // Register mem
+ EXPECT_TRUE(registerFactory());
+
+ // Now mem is registered but not mem1
+ EXPECT_TRUE(HostDataSourceFactory::registeredFactory("mem"));
+ EXPECT_FALSE(HostDataSourceFactory::registeredFactory("mem1"));
+}
+
+// Verify a factory can be registered and deregistered
+TEST_F(HostDataSourceFactoryTest, deregisterFactory) {
+ // Does not exist at the beginning
+ EXPECT_FALSE(HostDataSourceFactory::deregisterFactory("mem"));
+
+ // Register and deregister
+ EXPECT_TRUE(registerFactory());
+ EXPECT_TRUE(HostDataSourceFactory::deregisterFactory("mem"));
+ EXPECT_FALSE(HostDataSourceFactory::registeredFactory("mem"));
+
+ // No longer exists
+ EXPECT_FALSE(HostDataSourceFactory::deregisterFactory("mem"));
+}
+
+// Verify a registered factory can be called
+TEST_F(HostDataSourceFactoryTest, add) {
+ EXPECT_TRUE(registerFactory());
+ EXPECT_NO_THROW(HostDataSourceFactory::add(sources_, "type=mem"));
+ ASSERT_EQ(1, sources_.size());
+ EXPECT_EQ("mem", sources_[0]->getType());
+}
+
+// Verify that type is required
+TEST_F(HostDataSourceFactoryTest, notype) {
+ EXPECT_THROW(HostDataSourceFactory::add(sources_, "tp=mem"),
+ InvalidParameter);
+ EXPECT_THROW(HostDataSourceFactory::add(sources_, "type=mem"),
+ InvalidType);
+}
+
+// Verify that factory must not return NULL
+TEST_F(HostDataSourceFactoryTest, null) {
+ EXPECT_TRUE(HostDataSourceFactory::registerFactory("mem", factory0));
+ EXPECT_THROW(HostDataSourceFactory::add(sources_, "type=mem"),
+ Unexpected);
+}
+
+// Verify del class method
+TEST_F(HostDataSourceFactoryTest, del) {
+ // No sources at the beginning
+ EXPECT_FALSE(HostDataSourceFactory::del(sources_, "mem"));
+
+ // Add mem
+ EXPECT_TRUE(registerFactory());
+ EXPECT_NO_THROW(HostDataSourceFactory::add(sources_, "type=mem"));
+ ASSERT_EQ(1, sources_.size());
+
+ // Delete another
+ EXPECT_FALSE(HostDataSourceFactory::del(sources_, "another"));
+
+ // Delete mem
+ EXPECT_TRUE(HostDataSourceFactory::del(sources_, "mem"));
+
+ // No longer mem in sources
+ EXPECT_FALSE(HostDataSourceFactory::del(sources_, "mem"));
+}
+
+// Verify add and del class method on multiple backends
+TEST_F(HostDataSourceFactoryTest, multiple) {
+ // Add foo twice
+ EXPECT_TRUE(registerFactory1());
+ EXPECT_NO_THROW(HostDataSourceFactory::add(sources_, "type=mem1"));
+ EXPECT_NO_THROW(HostDataSourceFactory::add(sources_, "type=mem1"));
+
+ // Add mem2 once
+ EXPECT_TRUE(registerFactory2());
+ EXPECT_NO_THROW(HostDataSourceFactory::add(sources_, "type=mem2"));
+
+ // Delete them
+ EXPECT_TRUE(HostDataSourceFactory::del(sources_, "mem1"));
+ EXPECT_TRUE(HostDataSourceFactory::del(sources_, "mem2"));
+ // Second instance of mem1
+ EXPECT_TRUE(HostDataSourceFactory::del(sources_, "mem1"));
+
+ // No more sources
+ EXPECT_EQ(0, sources_.size());
+ EXPECT_FALSE(HostDataSourceFactory::del(sources_, "mem1"));
+ EXPECT_FALSE(HostDataSourceFactory::del(sources_, "mem2"));
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/host_mgr_unittest.cc b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc
new file mode 100644
index 0000000..28ed24e
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/host_mgr_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright (C) 2014-2023 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 <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/testutils/generic_host_data_source_unittest.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+using namespace isc;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::asiolink;
+namespace {
+
+// The tests in this file only address the in memory hosts.
+
+// This test verifies that HostMgr returns all reservations for the
+// specified HW address. The reservations are defined in the server's
+// configuration.
+TEST_F(HostMgrTest, getAll) {
+ testGetAll(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// specified DHCPv4 subnet. The reservations are defined in the server's
+// configuration.
+TEST_F(HostMgrTest, getAll4BySubnet) {
+ testGetAll4BySubnet(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// IPv4 subnet and reserved address. The reservations are specified in the
+// server's configuration.
+TEST_F(HostMgrTest, getAll4BySubnetIP) {
+ testGetAll4BySubnetIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// IPv6 subnet and reserved address. The reservations are specified in the
+// server's configuration.
+TEST_F(HostMgrTest, getAll6BySubnetIP) {
+ testGetAll6BySubnetIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// IPv6 reserved address. The reservations are specified in the
+// server's configuration.
+TEST_F(HostMgrTest, getAll6ByIP) {
+ testGetAll6ByIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// IPv6 reserved prefix. The reservations are specified in the
+// server's configuration.
+TEST_F(HostMgrTest, getAll6ByIpPrefix) {
+ testGetAll6ByIpPrefix(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// specified DHCPv6 subnet. The reservations are defined in the server's
+// configuration.
+TEST_F(HostMgrTest, getAll6BySubnet) {
+ testGetAll6BySubnet(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// hostname. The reservations are defined in the server's configuration.
+TEST_F(HostMgrTest, getAllbyHostname) {
+ testGetAllbyHostname(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// hostname and DHCPv4 subnet. The reservations are defined in the server's
+// configuration.
+TEST_F(HostMgrTest, getAllbyHostnameSubnet4) {
+ testGetAllbyHostnameSubnet4(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// hostname and DHCPv6 subnet. The reservations are defined in the server's
+// configuration.
+TEST_F(HostMgrTest, getAllbyHostnameSubnet6) {
+ testGetAllbyHostnameSubnet6(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// hostname and DHCPv6 subnet. The reservations are defined in the server's
+// configuration.
+TEST_F(HostMgrTest, getAllbyHostname6) {
+ testGetAllbyHostnameSubnet4(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// specified DHCPv4 subnet by pages. The reservations are defined in
+// the server's configuration.
+TEST_F(HostMgrTest, getPage4) {
+ testGetPage4(false);
+}
+
+// This test verifies that HostMgr returns all v4 reservations by pages.
+// The reservations are defined in the server's configuration.
+TEST_F(HostMgrTest, getPage4All) {
+ testGetPage4All(false);
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// specified DHCPv6 subnet by pages. The reservations are defined in
+// the server's configuration.
+TEST_F(HostMgrTest, getPage6) {
+ testGetPage6(false);
+}
+
+// This test verifies that HostMgr returns all v6 reservations by pages.
+// The reservations are defined in the server's configuration.
+TEST_F(HostMgrTest, getPage6All) {
+ testGetPage6All(false);
+}
+
+// This test verifies that it is possible to gather all reservations for the
+// specified IPv4 address from the HostMgr. The reservations are specified in
+// the server's configuration.
+TEST_F(HostMgrTest, getAll4) {
+ testGetAll4(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that it is possible to retrieve a reservation for the
+// particular host using HostMgr. The reservation is specified in the server's
+// configuration.
+TEST_F(HostMgrTest, get4) {
+ testGet4(*getCfgHosts());
+}
+
+// This test verifies handling of negative caching by get4/get4Any.
+TEST_F(HostMgrTest, get4Any) {
+ testGet4Any();
+}
+
+// This test verifies that it is possible to retrieve IPv6 reservations for
+// the particular host using HostMgr. The reservation is specified in the
+// server's configuration.
+TEST_F(HostMgrTest, get6) {
+ testGet6(*getCfgHosts());
+}
+
+// This test verifies handling of negative caching by get4/get4Any.
+TEST_F(HostMgrTest, get6Any) {
+ testGet6Any();
+}
+
+// This test verifies that it is possible to retrieve the reservation of the
+// particular IPv6 prefix using HostMgr.
+TEST_F(HostMgrTest, get6ByPrefix) {
+ testGet6ByPrefix(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that the reservations can be added using HostMgr.
+TEST_F(HostMgrTest, add) {
+ testAdd(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that the reservations can be deleted by subnet ID and
+// address using HostMgr..
+TEST_F(HostMgrTest, del) {
+ testDeleteByIDAndAddress(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that the IPv4 reservations can be deleted by subnet ID
+// and identifier using HostMgr.
+TEST_F(HostMgrTest, del4) {
+ testDelete4ByIDAndIdentifier(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that the IPv6 reservations can be deleted by subnet ID
+// and identifier using HostMgr..
+TEST_F(HostMgrTest, del6) {
+ testDelete6ByIDAndIdentifier(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that without a host data source an exception is thrown.
+TEST_F(HostMgrTest, noDataSource) {
+ // Remove all configuration.
+ CfgMgr::instance().clear();
+ // Recreate HostMgr instance.
+ HostMgr::create();
+
+ HostPtr host(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED, IOAddress("192.0.2.5")));
+ EXPECT_THROW_MSG(HostMgr::instance().add(host), NoHostDataSourceManager,
+ "Unable to add new host because there is no hosts-database configured.");
+ EXPECT_THROW_MSG(HostMgr::instance().update(host), NoHostDataSourceManager,
+ "Unable to update existing host because there is no hosts-database "
+ "configured.");
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
new file mode 100644
index 0000000..fd2ed2e
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
@@ -0,0 +1,1499 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <testutils/test_to_element.h>
+#include <boost/pointer_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+#include <iterator>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Holds a type of the last identifier in @c IdentifierType enum.
+///
+/// This value must be updated when new identifiers are added to the enum.
+// const Host::IdentifierType LAST_IDENTIFIER_TYPE = Host::IDENT_CIRCUIT_ID;
+
+/// @brief Test fixture class for @c HostReservationParser.
+class HostReservationParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ HostReservationParserTest() = default;
+
+ /// @brief Destructor
+ virtual ~HostReservationParserTest() = default;
+
+protected:
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void TearDown();
+
+ /// @brief Checks if the reservation is in the range of reservations.
+ ///
+ /// @param resrv Reservation to be searched for.
+ /// @param range Range of reservations returned by the @c Host object
+ /// in which the reservation will be searched.
+ bool
+ reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
+ for (IPv6ResrvIterator it = range.first; it != range.second;
+ ++it) {
+ if (resrv == it->second) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ /// @brief Retrieves DHCP option from a host.
+ ///
+ /// @param host Reference to a host object for which an option should be
+ /// retrieved.
+ /// @param option_space Option space name.
+ /// @param option_code Code of an option to be retrieved.
+ ///
+ /// @return Pointer to the option retrieved or NULL pointer if option
+ /// hasn't been found.
+ OptionPtr
+ retrieveOption(const Host& host, const std::string& option_space,
+ const uint16_t option_code) const {
+ if ((option_space != DHCP6_OPTION_SPACE) && (option_space != DHCP4_OPTION_SPACE)) {
+ return (OptionPtr());
+ }
+
+ // Retrieve a pointer to the appropriate container depending if we're
+ // interested in DHCPv4 or DHCPv6 options.
+ ConstCfgOptionPtr cfg_option = (option_space == DHCP4_OPTION_SPACE ?
+ host.getCfgOption4() : host.getCfgOption6());
+
+ // Retrieve options.
+ OptionContainerPtr options = cfg_option->getAll(option_space);
+ if (options) {
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ OptionContainerTypeIndex::const_iterator it = idx.find(option_code);
+ if (it != idx.end()) {
+ return (it->option_);
+ }
+ }
+
+ return (OptionPtr());
+ }
+
+ /// @brief This test verifies that it is possible to specify an empty list
+ /// of options for a host.
+ ///
+ /// @tparam ParserType Type of the parser to be tested.
+ template<typename ParserType>
+ void testEmptyOptionList() const {
+ // Create configuration with empty option list. Note that we have to
+ // add reservation for at least one resource because host declarations
+ // without any reservations are rejected. Thus, we have added hostname.
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"hostname\": \"foo.isc.org\","
+ "\"option-data\": [ ]"
+ "}";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ ParserType parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+
+ ASSERT_TRUE(host);
+
+ // There should be no options assigned to a host.
+ EXPECT_TRUE(host->getCfgOption4()->empty());
+ EXPECT_TRUE(host->getCfgOption6()->empty());
+ }
+
+ /// @brief This test verifies that the parser can parse a DHCPv4
+ /// reservation configuration including a specific identifier.
+ ///
+ /// @param identifier_name Identifier name.
+ /// @param identifier_type Identifier type.
+ void testIdentifier4(const std::string& identifier_name,
+ const std::string& identifier_value,
+ const Host::IdentifierType& /*expected_identifier_type*/,
+ const std::vector<uint8_t>& /*expected_identifier*/) const {
+ std::ostringstream config;
+ config << "{ \"" << identifier_name << "\": \"" << identifier_value
+ << "\","
+ << "\"ip-address\": \"192.0.2.112\","
+ << "\"hostname\": \"\" }";
+
+ ElementPtr config_element = Element::fromJSON(config.str());
+
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(SUBNET_ID_UNUSED, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.112", host->getIPv4Reservation().toText());
+ EXPECT_TRUE(host->getHostname().empty());
+ }
+
+ /// @brief This test verifies that the parser returns an error when
+ /// configuration is invalid.
+ ///
+ /// @param config JSON configuration to be tested.
+ /// @tparam ParserType Type of the parser class to use.
+ template<typename ParserType>
+ void testInvalidConfig(const std::string& config) const {
+ ElementPtr config_element = Element::fromJSON(config);
+ HostPtr host;
+ ParserType parser;
+ EXPECT_THROW({
+ host = parser.parse(SubnetID(10), config_element);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ }, isc::Exception);
+ }
+
+ /// @brief HW Address object used by tests.
+ HWAddrPtr hwaddr_;
+
+ /// @brief DUID object used by tests.
+ DuidPtr duid_;
+
+ /// @brief Vector holding circuit id used by tests.
+ std::vector<uint8_t> circuit_id_;
+
+ /// @brief Vector holding client id used by tests.
+ std::vector<uint8_t> client_id_;
+};
+
+void
+HostReservationParserTest::SetUp() {
+ CfgMgr::instance().clear();
+ // Initialize HW address used by tests.
+ const uint8_t hwaddr_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+ hwaddr_ = HWAddrPtr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+ HTYPE_ETHER));
+
+ // Initialize DUID used by tests.
+ const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A };
+ duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data)));
+
+ const std::string circuit_id_str = "howdy";
+ circuit_id_.assign(circuit_id_str.begin(), circuit_id_str.end());
+
+ client_id_.push_back(0x01); // Client identifier type.
+ // Often client id comprises HW address.
+ client_id_.insert(client_id_.end(), hwaddr_->hwaddr_.begin(),
+ hwaddr_->hwaddr_.end());
+}
+
+void
+HostReservationParserTest::TearDown() {
+ CfgMgr::instance().clear();
+}
+
+/// @brief class of subnet_id reservations
+class CfgHostsSubnet : public CfgToElement {
+public:
+ /// @brief constructor
+ CfgHostsSubnet(ConstCfgHostsPtr hosts, SubnetID id)
+ : hosts_(hosts), id_(id) { }
+
+ /// @brief unparse method
+ ElementPtr toElement() const;
+
+private:
+ /// @brief the host reservation configuration
+ ConstCfgHostsPtr hosts_;
+
+ /// @brief the subnet ID
+ SubnetID id_;
+};
+
+ElementPtr
+CfgHostsSubnet::toElement() const {
+ CfgHostsList list;
+ try {
+ list.internalize(hosts_->toElement());
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "CfgHostsSubnet::toElement: " << ex.what();
+ }
+ ElementPtr result = boost::const_pointer_cast<Element>(list.get(id_));
+
+ // Strip
+ for (size_t i = 0; i < result->size(); ++i) {
+ ElementPtr resv = result->getNonConst(i);
+ ConstElementPtr ip_address = resv->get("ip-address");
+ if (ip_address && (ip_address->stringValue() == "0.0.0.0")) {
+ resv->remove("ip-address");
+ }
+ ConstElementPtr ip_addresses = resv->get("ip-addresses");
+ if (ip_addresses && ip_addresses->empty()) {
+ resv->remove("ip-addresses");
+ }
+ ConstElementPtr prefixes = resv->get("prefixes");
+ if (prefixes && prefixes->empty()) {
+ resv->remove("prefixes");
+ }
+ ConstElementPtr hostname = resv->get("hostname");
+ if (hostname && hostname->stringValue().empty()) {
+ resv->remove("hostname");
+ }
+ ConstElementPtr next_server = resv->get("next-server");
+ if (next_server && (next_server->stringValue() == "0.0.0.0")) {
+ resv->remove("next-server");
+ }
+ ConstElementPtr server_hostname = resv->get("server-hostname");
+ if (server_hostname && server_hostname->stringValue().empty()) {
+ resv->remove("server-hostname");
+ }
+ ConstElementPtr boot_file_name = resv->get("boot-file-name");
+ if (boot_file_name && boot_file_name->stringValue().empty()) {
+ resv->remove("boot-file-name");
+ }
+ ConstElementPtr client_classes = resv->get("client-classes");
+ if (client_classes && client_classes->empty()) {
+ resv->remove("client-classes");
+ }
+ ConstElementPtr option_data = resv->get("option-data");
+ if (option_data && option_data->empty()) {
+ resv->remove("option-data");
+ }
+ }
+ return (result);
+}
+
+// This test verifies that the parser can parse the reservation entry for
+// which hw-address is a host identifier.
+TEST_F(HostReservationParserTest, dhcp4HWaddr) {
+ testIdentifier4("hw-address", "1:2:3:4:5:6", Host::IDENT_HWADDR,
+ hwaddr_->hwaddr_);
+}
+
+// This test verifies that the parser can parse the reservation entry for
+// which DUID is a host identifier.
+TEST_F(HostReservationParserTest, dhcp4DUID) {
+ testIdentifier4("duid", "01:02:03:04:05:06:07:08:09:0A",
+ Host::IDENT_DUID, duid_->getDuid());
+}
+
+// This test verifies that the parser can parse the reservation entry for
+// which DUID specified as a string of hexadecimal digits with '0x' prefix
+// is a host identifier
+TEST_F(HostReservationParserTest, dhcp4DUIDWithPrefix) {
+ testIdentifier4("duid", "0x0102030405060708090A",
+ Host::IDENT_DUID, duid_->getDuid());
+}
+
+// This test verifies that the parser can parse a reservation entry for
+// which circuit-id is an identifier. The circuit-id is specified as
+// a string in quotes.
+TEST_F(HostReservationParserTest, dhcp4CircuitIdStringInQuotes) {
+ testIdentifier4("circuit-id", "'howdy'", Host::IDENT_CIRCUIT_ID,
+ circuit_id_);
+}
+
+// This test verifies that the parser can parse a reservation entry for
+// which circuit-id is an identifier. The circuit-id is specified in
+// hexadecimal format.
+TEST_F(HostReservationParserTest, dhcp4CircuitIdHex) {
+ testIdentifier4("circuit-id", "686F776479", Host::IDENT_CIRCUIT_ID,
+ circuit_id_);
+}
+
+// This test verifies that the parser can parse a reservation entry for
+// which circuit-id is an identifier. The circuit-id is specified in
+// hexadecimal format with a '0x' prefix.
+TEST_F(HostReservationParserTest, dhcp4CircuitIdHexWithPrefix) {
+ testIdentifier4("circuit-id", "0x686F776479", Host::IDENT_CIRCUIT_ID,
+ circuit_id_);
+}
+
+// This test verifies that the parser can parse a reservation entry for
+// which client-id is an identifier. The client-id is specified in
+// hexadecimal format.
+TEST_F(HostReservationParserTest, dhcp4ClientIdHex) {
+ testIdentifier4("client-id", "01010203040506", Host::IDENT_CLIENT_ID,
+ client_id_);
+}
+
+// This test verifies that the parser can parse a reservation entry for
+// which client-id is an identifier. The client-id is specified in
+// hexadecimal format with a '0x' prefix.
+TEST_F(HostReservationParserTest, dhcp4ClientIdHexWithPrefix) {
+ testIdentifier4("client-id", "0x01010203040506", Host::IDENT_CLIENT_ID,
+ client_id_);
+}
+
+// This test verifies that the parser can parse the reservation entry
+// when IPv4 address is specified, but hostname is not.
+TEST_F(HostReservationParserTest, dhcp4NoHostname) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0a\","
+ "\"ip-address\": \"192.0.2.10\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_TRUE(hosts[0]->getHostname().empty());
+
+ // lower duid value
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that it is possible to specify DHCPv4 client classes
+// within the host reservation.
+TEST_F(HostReservationParserTest, dhcp4ClientClasses) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"client-classes\": [ \"foo\", \"bar\" ] }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+ &hwaddr_->hwaddr_[0],
+ hwaddr_->hwaddr_.size()));
+ ASSERT_EQ(1, hosts.size());
+
+ const ClientClasses& classes = hosts[0]->getClientClasses4();
+ ASSERT_EQ(2, classes.size());
+ EXPECT_TRUE(classes.contains("foo"));
+ EXPECT_TRUE(classes.contains("bar"));
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that the parser can parse reservation entry
+// containing next-server, server-hostname and boot-file-name values for
+// DHCPv4 message fields.
+TEST_F(HostReservationParserTest, dhcp4MessageFields) {
+ std::string config = "{ \"hw-address\": \"1:2:3:4:5:6\","
+ "\"next-server\": \"192.0.2.11\","
+ "\"server-hostname\": \"some-name.example.org\","
+ "\"boot-file-name\": \"/tmp/some-file.efi\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+ &hwaddr_->hwaddr_[0],
+ hwaddr_->hwaddr_.size()));
+
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.11", hosts[0]->getNextServer().toText());
+ EXPECT_EQ("some-name.example.org", hosts[0]->getServerHostname());
+ EXPECT_EQ("/tmp/some-file.efi", hosts[0]->getBootFileName());
+
+ // canonize hw-address
+ config_element->set("hw-address",
+ Element::create(std::string("01:02:03:04:05:06")));
+ ElementPtr expected = Element::createList();
+ expected->add(config_element);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that the invalid value of the next server is rejected.
+TEST_F(HostReservationParserTest, invalidNextServer) {
+ // Invalid IPv4 address.
+ std::string config = "{ \"hw-address\": \"1:2:3:4:5:6\","
+ "\"next-server\": \"192.0.2.foo\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+
+ // Broadcast address.
+ config = "{ \"hw-address\": \"1:2:3:4:5:6\","
+ "\"next-server\": \"255.255.255.255\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+
+ // IPv6 address.
+ config = "{ \"hw-address\": \"1:2:3:4:5:6\","
+ "\"next-server\": \"2001:db8:1::1\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the invalid server hostname is rejected.
+TEST_F(HostReservationParserTest, invalidServerHostname) {
+ std::ostringstream config;
+ config << "{ \"hw-address\": \"1:2:3:4:5:6\","
+ "\"server-hostname\": \"";
+ config << std::string(64, 'a');
+ config << "\" }";
+ testInvalidConfig<HostReservationParser4>(config.str());
+}
+
+// This test verifies that the invalid boot file name is rejected.
+TEST_F(HostReservationParserTest, invalidBootFileName) {
+ std::ostringstream config;
+ config << "{ \"hw-address\": \"1:2:3:4:5:6\","
+ "\"boot-file-name\": \"";
+ config << std::string(128, 'a');
+ config << "\" }";
+ testInvalidConfig<HostReservationParser4>(config.str());
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when IPv6 address is specified for IPv4 address
+// reservation.
+TEST_F(HostReservationParserTest, dhcp4IPv6Address) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"ip-address\": \"2001:db8:1::1\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when no HW address nor DUID is specified.
+TEST_F(HostReservationParserTest, noIdentifier) {
+ std::string config = "{ \"ip-address\": \"192.0.2.112\","
+ "\"hostname\": \"\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// no longer throws when neither ip address nor hostname is specified.
+TEST_F(HostReservationParserTest, noResource) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\" }";
+ ElementPtr config_element = Element::fromJSON(config);
+ HostPtr host;
+ HostReservationParser4 parser;
+ EXPECT_NO_THROW({
+ host = parser.parse(SubnetID(10), config_element);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ });
+}
+
+// This test verifies that the parser can parse the reservation entry
+// when IP address is not specified, but hostname is specified.
+TEST_F(HostReservationParserTest, noIPAddress) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"hostname\": \"foo.example.com\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ("0.0.0.0", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+ // lower duid value
+ boost::algorithm::to_lower(config);
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that the configuration parser for host reservations
+// no longer throws when hostname is empty, and IP address is not specified.
+TEST_F(HostReservationParserTest, emptyHostname) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"hostname\": \"\" }";
+ ElementPtr config_element = Element::fromJSON(config);
+ HostPtr host;
+ HostReservationParser4 parser;
+ EXPECT_NO_THROW({
+ host = parser.parse(SubnetID(10), config_element);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ });
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when invalid IP address is specified.
+TEST_F(HostReservationParserTest, malformedAddress) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"ip-address\": \"192.0.2.bogus\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when zero IP address is specified.
+TEST_F(HostReservationParserTest, zeroAddress) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"ip-address\": \"0.0.0.0\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when broadcast IP address is specified.
+TEST_F(HostReservationParserTest, bcastAddress) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"ip-address\": \"255.255.255.255\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when invalid next server address is specified.
+TEST_F(HostReservationParserTest, malformedNextServer) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"next-server\": \"192.0.2.bogus\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// no longer throws when zero next server address is specified.
+TEST_F(HostReservationParserTest, zeroNextServer) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"next-server\": \"0.0.0.0\" }";
+ ElementPtr config_element = Element::fromJSON(config);
+ HostPtr host;
+ HostReservationParser4 parser;
+ EXPECT_NO_THROW({
+ host = parser.parse(SubnetID(10), config_element);
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+ });
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when broadcast next server address is specified.
+TEST_F(HostReservationParserTest, bcastNextServer) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"next-server\": \"255.255.255.255\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when unsupported parameter is specified.
+TEST_F(HostReservationParserTest, invalidParameterName) {
+ // The "ip-addresses" parameter name is incorrect for the DHCPv4
+ // case - it is only valid for DHCPv6 case. Trying to set this
+ // parameter should result in error.
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"hostname\": \"foo.bar.isc.org\","
+ "\"ip-addresses\": \"2001:db8:1::1\" }";
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test verifies that the parser can parse the IPv6 reservation entry for
+// which hw-address is a host identifier.
+TEST_F(HostReservationParserTest, dhcp6HWaddr) {
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ],"
+ "\"prefixes\": [ \"2001:db8:2000:0101::/64\", "
+ "\"2001:db8:2000:0102::/64\" ],"
+ "\"hostname\": \"foo.example.com\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+ &hwaddr_->hwaddr_[0],
+ hwaddr_->hwaddr_.size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(10, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+ IPv6ResrvRange addresses = hosts[0]->
+ getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")),
+ addresses));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::2")),
+ addresses));
+
+ IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:2000:0101::"),
+ 64),
+ prefixes));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:2000:0102::"),
+ 64),
+ prefixes));
+
+ // canonize prefixes
+ config_element->set("prefixes",
+ Element::fromJSON("[ \"2001:db8:2000:101::/64\", "
+ "\"2001:db8:2000:102::/64\" ]"));
+ ElementPtr expected = Element::createList();
+ expected->add(config_element);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that the parser can parse the IPv6 reservation entry for
+// which DUID is a host identifier.
+TEST_F(HostReservationParserTest, dhcp6DUID) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+ "\"prefixes\": [ ],"
+ "\"hostname\": \"foo.example.com\" }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(12), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+ IPv6ResrvRange addresses = hosts[0]->
+ getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::100")),
+ addresses));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::200")),
+ addresses));
+
+ IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+
+ // remove prefixes and lower duid value
+ config_element->remove("prefixes");
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that host reservation parser for DHCPv6 rejects
+// "circuit-id" as a host identifier.
+TEST_F(HostReservationParserTest, dhcp6CircuitId) {
+ // Use DHCPv4 specific identifier 'circuit-id' with DHCPv6 parser.
+ std::string config = "{ \"circuit-id\": \"'howdy'\","
+ "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+ "\"prefixes\": [ ],"
+ "\"hostname\": \"foo.example.com\" }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that host reservation parser for DHCPv6 rejects
+// "client-id" as a host identifier.
+TEST_F(HostReservationParserTest, dhcp6ClientId) {
+ // Use DHCPv4 specific identifier 'client-id' with DHCPv6 parser.
+ std::string config = "{ \"client-id\": \"01010203040506\","
+ "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+ "\"prefixes\": [ ],"
+ "\"hostname\": \"foo.example.com\" }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the parser can parse the IPv6 reservation entry
+// which lacks hostname parameter.
+TEST_F(HostReservationParserTest, dhcp6NoHostname) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+ "\"prefixes\": [ ] }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(12), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->getHostname().empty());
+
+ IPv6ResrvRange addresses = hosts[0]->
+ getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::100")),
+ addresses));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::200")),
+ addresses));
+
+ IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+
+ // remove prefixes and lower duid value
+ config_element->remove("prefixes");
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that it is possible to specify DHCPv4 client classes
+// within the host reservation.
+TEST_F(HostReservationParserTest, dhcp6ClientClasses) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"client-classes\": [ \"foo\", \"bar\" ] }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ const ClientClasses& classes = hosts[0]->getClientClasses6();
+ ASSERT_EQ(2, classes.size());
+ EXPECT_TRUE(classes.contains("foo"));
+ EXPECT_TRUE(classes.contains("bar"));
+
+ // lower duid value
+ boost::algorithm::to_lower(config);
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when IPv4 address is specified for IPv6 reservation.
+TEST_F(HostReservationParserTest, dhcp6IPv4Address) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"ip-addresses\": [ \"192.0.2.3\", \"2001:db8:1::200\" ],"
+ "\"prefixes\": [ ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when empty address has been specified.
+TEST_F(HostReservationParserTest, dhcp6NullAddress) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"ip-addresses\": [ \"\" ],"
+ "\"prefixes\": [ ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when invalid prefix length type is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLengthType) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8:1::/abc\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when invalid prefix length is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLength) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8:1::/32\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when empty prefix is specified.
+TEST_F(HostReservationParserTest, dhcp6NullPrefix) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"/64\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when only slash is specified for the prefix..
+TEST_F(HostReservationParserTest, dhcp6NullPrefix2) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"/\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when slash is missing for the prefix..
+TEST_F(HostReservationParserTest, dhcp6NullPrefix3) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8:2000:0101::\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when slash is followed by nothing for the prefix..
+TEST_F(HostReservationParserTest, dhcp6NullPrefix4) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8:2000:0101::/\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when slash is not followed by a number for the prefix..
+TEST_F(HostReservationParserTest, dhcp6NullPrefix5) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8:2000:0101::/foo\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the same address is reserved twice.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedAddress) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::1\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the same prefix is reserved twice.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedPrefix) {
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"prefixes\": [ \"2001:db8:0101::/64\", \"2001:db8:0101::/64\" ] }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when unsupported parameter is specified.
+TEST_F(HostReservationParserTest, dhcp6invalidParameterName) {
+ // The "ip-address" parameter name is incorrect for the DHCPv6
+ // case - it is only valid for DHCPv4 case. Trying to set this
+ // parameter should result in error.
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"hostname\": \"foo.bar.isc.org\","
+ "\"ip-address\": \"192.0.2.3\" }";
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that it is possible to specify DHCPv4 options for
+// a host.
+TEST_F(HostReservationParserTest, options4) {
+ // Create configuration with three options for a host.
+ std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+ "\"option-data\": ["
+ "{"
+ "\"name\": \"name-servers\","
+ "\"data\": \"172.16.15.10, 172.16.15.20\""
+ "},"
+ "{"
+ "\"name\": \"log-servers\","
+ "\"code\": 7,"
+ "\"csv-format\": true,"
+ "\"space\": \"dhcp4\","
+ "\"data\": \"172.16.15.23\","
+ "\"always-send\": false,"
+ "\"never-send\": false"
+ "},"
+ "{"
+ "\"name\": \"default-ip-ttl\","
+ "\"data\": \"64\""
+ "} ]"
+ "}";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser4 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+ &hwaddr_->hwaddr_[0],
+ hwaddr_->hwaddr_.size()));
+ ASSERT_EQ(1, hosts.size());
+
+ // Retrieve and sanity check name servers.
+ Option4AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
+ Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_NAME_SERVERS));
+ ASSERT_TRUE(opt_dns);
+ Option4AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(2, dns_addrs.size());
+ EXPECT_EQ("172.16.15.10", dns_addrs[0].toText());
+ EXPECT_EQ("172.16.15.20", dns_addrs[1].toText());
+
+ // Retrieve and sanity check log servers.
+ Option4AddrLstPtr opt_log = boost::dynamic_pointer_cast<
+ Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_LOG_SERVERS));
+ ASSERT_TRUE(opt_log);
+ Option4AddrLst::AddressContainer log_addrs = opt_log->getAddresses();
+ ASSERT_EQ(1, log_addrs.size());
+ EXPECT_EQ("172.16.15.23", log_addrs[0].toText());
+
+ // Retrieve and sanity check default IP TTL.
+ OptionUint8Ptr opt_ttl = boost::dynamic_pointer_cast<
+ OptionUint8>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL));
+ ASSERT_TRUE(opt_ttl);
+ EXPECT_EQ(64, opt_ttl->getValue());
+
+ // Canonize the config
+ ElementPtr option = config_element->get("option-data")->getNonConst(0);
+ option->set("code", Element::create(DHO_NAME_SERVERS));
+ option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ option->set("never-send", Element::create(false));
+ option = config_element->get("option-data")->getNonConst(1);
+ option = config_element->get("option-data")->getNonConst(2);
+ option->set("code", Element::create(DHO_DEFAULT_IP_TTL));
+ option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ option->set("never-send", Element::create(false));
+ ElementPtr expected = Element::createList();
+ expected->add(config_element);
+
+ // Try to unparse it.
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that it is possible to specify DHCPv6 options for
+// a host.
+TEST_F(HostReservationParserTest, options6) {
+ // Create configuration with three options for a host.
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"option-data\": ["
+ "{"
+ "\"name\": \"dns-servers\","
+ "\"data\": \"2001:db8:1::1, 2001:db8:1::2\""
+ "},"
+ "{"
+ "\"name\": \"nis-servers\","
+ "\"code\": 27,"
+ "\"csv-format\": true,"
+ "\"space\": \"dhcp6\","
+ "\"data\": \"2001:db8:1::1204\","
+ "\"always-send\": true,"
+ "\"never-send\": true"
+ "},"
+ "{"
+ "\"name\": \"preference\","
+ "\"data\": \"11\""
+ "} ]"
+ "}";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostPtr host;
+ HostReservationParser6 parser;
+ ASSERT_NO_THROW(host = parser.parse(SubnetID(10), config_element));
+ ASSERT_TRUE(host);
+
+ // One host should have been added to the configuration.
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+ ASSERT_NO_THROW(cfg_hosts->add(host));
+
+ HostCollection hosts;
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ // Retrieve and sanity check DNS servers option.
+ Option6AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
+ Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NAME_SERVERS));
+ ASSERT_TRUE(opt_dns);
+ Option6AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
+ ASSERT_EQ(2, dns_addrs.size());
+ EXPECT_EQ("2001:db8:1::1", dns_addrs[0].toText());
+ EXPECT_EQ("2001:db8:1::2", dns_addrs[1].toText());
+
+ // Retrieve and sanity check NIS servers option.
+ Option6AddrLstPtr opt_nis = boost::dynamic_pointer_cast<
+ Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NIS_SERVERS));
+ ASSERT_TRUE(opt_nis);
+ Option6AddrLst::AddressContainer nis_addrs = opt_nis->getAddresses();
+ ASSERT_EQ(1, nis_addrs.size());
+ EXPECT_EQ("2001:db8:1::1204", nis_addrs[0].toText());
+
+ // Retrieve and sanity check preference option.
+ OptionUint8Ptr opt_prf = boost::dynamic_pointer_cast<
+ OptionUint8>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_PREFERENCE));
+ ASSERT_TRUE(opt_prf);
+ EXPECT_EQ(11, opt_prf->getValue());
+
+ // Canonize the config
+ ElementPtr option = config_element->get("option-data")->getNonConst(0);
+ option->set("code", Element::create(D6O_NAME_SERVERS));
+ option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ option->set("never-send", Element::create(false));
+ option = config_element->get("option-data")->getNonConst(1);
+ option = config_element->get("option-data")->getNonConst(2);
+ option->set("code", Element::create(D6O_PREFERENCE));
+ option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+ option->set("csv-format", Element::create(true));
+ option->set("always-send", Element::create(false));
+ option->set("never-send", Element::create(false));
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+
+ // Try to unparse it.
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that it is possible to specify an empty list of
+// DHCPv4 options for a host declaration.
+TEST_F(HostReservationParserTest, options4Empty) {
+ testEmptyOptionList<HostReservationParser4>();
+}
+
+// This test verifies that it is possible to specify an empty list of
+// DHCPv6 options for a host declaration.
+TEST_F(HostReservationParserTest, options6Empty) {
+ testEmptyOptionList<HostReservationParser6>();
+}
+
+// This test checks that specifying DHCPv6 options for the DHCPv4 host
+// reservation parser is not allowed.
+TEST_F(HostReservationParserTest, options4InvalidOptionSpace) {
+ // Create configuration specifying DHCPv6 option for a DHCPv4 host
+ // reservation parser.
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"option-data\": ["
+ "{"
+ "\"name\": \"dns-servers\","
+ "\"space\": \"dhcp6\","
+ "\"data\": \"2001:db8:1::1, 2001:db8:1::2\""
+ "} ]"
+ "}";
+
+ testInvalidConfig<HostReservationParser4>(config);
+}
+
+// This test checks that specifying DHCPv4 options for the DHCPv6 host
+// reservation parser is not allowed.
+TEST_F(HostReservationParserTest, options6InvalidOptionSpace) {
+ // Create configuration specifying DHCPv4 option for a DHCPv6 host
+ // reservation parser.
+ std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ "\"option-data\": ["
+ "{"
+ "\"name\": \"name-servers\","
+ "\"space\": \"dhcp4\","
+ "\"data\": \"172.16.15.10, 172.16.15.20\""
+ "} ]"
+ "}";
+
+ testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that host identifiers for DHCPv4 are mutually exclusive.
+TEST_F(HostReservationParserTest, mutuallyExclusiveIdentifiers4) {
+ std::vector<std::string> identifiers;
+ identifiers.push_back("hw-address");
+ identifiers.push_back("duid");
+ identifiers.push_back("circuit-id");
+
+ for (unsigned int i = 0; i < identifiers.size(); ++i) {
+ // j points to an index of the next identifier. If it
+ // overflows, we set it to 0.
+ unsigned int j = (i + 1) % (identifiers.size());
+ Host::IdentifierType first = static_cast<Host::IdentifierType>(i);
+ Host::IdentifierType second = static_cast<Host::IdentifierType>(j);
+
+ SCOPED_TRACE("Using identifiers " + Host::getIdentifierName(first)
+ + " and " + Host::getIdentifierName(second));
+
+ // Create configuration with two different identifiers.
+ std::ostringstream config;
+ config << "{ \"" << Host::getIdentifierName(first) << "\": \"121314151617\","
+ "\"" << Host::getIdentifierName(second) << "\": \"0A0B0C0D0E0F\","
+ "\"ip-address\": \"192.0.2.3\" }";
+ testInvalidConfig<HostReservationParser4>(config.str());
+ }
+}
+
+// This test verifies that host identifiers for DHCPv6 are mutually exclusive.
+TEST_F(HostReservationParserTest, mutuallyExclusiveIdentifiers6) {
+ std::vector<std::string> identifiers;
+ identifiers.push_back("hw-address");
+ identifiers.push_back("duid");
+
+ for (unsigned int i = 0; i < identifiers.size(); ++i) {
+ // j points to an index of the next identifier. If it
+ // overflows, we set it to 0.
+ unsigned int j = (i + 1) % (identifiers.size());
+
+ SCOPED_TRACE("Using identifiers " + identifiers[i] + " and "
+ + identifiers[j]);
+
+ // Create configuration with two different identifiers.
+ std::ostringstream config;
+ config << "{ \"" << identifiers[i] << "\": \"121314151617\","
+ "\"" << identifiers[j] << "\": \"0A0B0C0D0E0F\","
+ "\"ip-addresses\": \"2001:db8:1::1\" }";
+ testInvalidConfig<HostReservationParser6>(config.str());
+ }
+}
+
+/// @brief Test fixture class for @ref HostReservationIdsParser.
+class HostReservationIdsParserTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Clears current configuration.
+ HostReservationIdsParserTest() {
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Clears current configuration.
+ virtual ~HostReservationIdsParserTest() {
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Test verifies that invalid configuration causes an error.
+ ///
+ /// @param config Configuration string.
+ /// @tparam ParserType @ref HostReservationIdsParser4 or
+ /// @ref HostReservationIdsParser6
+ template<typename ParserType>
+ void testInvalidConfig(const std::string& config) const {
+ ElementPtr config_element = Element::fromJSON(config);
+ ParserType parser;
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
+ }
+
+};
+
+// Test that list of supported DHCPv4 identifiers list is correctly
+// parsed.
+TEST_F(HostReservationIdsParserTest, dhcp4Identifiers) {
+ std::string config =
+ "[ \"circuit-id\", \"duid\", \"hw-address\", \"client-id\" ]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationIdsParser4 parser;
+ ASSERT_NO_THROW(parser.parse(config_element));
+
+ ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+ getCfgHostOperations4();
+ const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+ ASSERT_EQ(4, ids.size());
+
+ CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+ EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
+ EXPECT_EQ(*id++, Host::IDENT_DUID);
+ EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+ EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
+
+ runToElementTest<CfgHostOperations>(config, *cfg);
+}
+
+// Test that list of supported DHCPv6 identifiers list is correctly
+// parsed.
+TEST_F(HostReservationIdsParserTest, dhcp6Identifiers) {
+ std::string config = "[ \"duid\", \"hw-address\" ]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationIdsParser6 parser;
+ ASSERT_NO_THROW(parser.parse(config_element));
+
+ ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+ getCfgHostOperations6();
+ const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+ ASSERT_EQ(2, ids.size());
+
+ CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+ EXPECT_EQ(*id++, Host::IDENT_DUID);
+ EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+
+ runToElementTest<CfgHostOperations>(config, *cfg);
+}
+
+// Test that invalid DHCPv4 identifier causes error.
+TEST_F(HostReservationIdsParserTest, dhcp4InvalidIdentifier) {
+ // Create configuration including unsupported identifier.
+ std::string config = "[ \"unsupported-id\" ]";
+ testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// Test that invalid DHCPv6 identifier causes error.
+TEST_F(HostReservationIdsParserTest, dhcp6InvalidIdentifier) {
+ // Create configuration including unsupported identifier for DHCPv6.
+ // The circuit-id is only supported in DHCPv4.
+ std::string config = "[ \"circuit-id\" ]";
+ testInvalidConfig<HostReservationIdsParser6>(config);
+}
+
+// Check that all supported identifiers are used when 'auto' keyword
+// is specified for DHCPv4 case.
+TEST_F(HostReservationIdsParserTest, dhcp4AutoIdentifiers) {
+ std::string config = "[ \"auto\" ]";
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationIdsParser4 parser;
+ ASSERT_NO_THROW(parser.parse(config_element));
+
+ ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+ getCfgHostOperations4();
+ const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+ ASSERT_EQ(5, ids.size());
+
+ CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+ EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+ EXPECT_EQ(*id++, Host::IDENT_DUID);
+ EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
+ EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
+ EXPECT_EQ(*id++, Host::IDENT_FLEX);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed before the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp4AutoBeforeIdentifier) {
+ std::string config = "[ \"auto\", \"duid\" ]";
+ testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed after the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp4AutoAfterIdentifier) {
+ std::string config = "[ \"duid\", \"auto\" ]";
+ testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// Test that empty list of identifier types is not allowed.
+TEST_F(HostReservationIdsParserTest, dhcp4EmptyList) {
+ std::string config = "[ ]";
+ testInvalidConfig<HostReservationIdsParser4>(config);
+}
+
+// Check that all supported identifiers are used when 'auto' keyword
+// is specified for DHCPv6 case.
+TEST_F(HostReservationIdsParserTest, dhcp6AutoIdentifiers) {
+ std::string config = "[ \"auto\" ]";
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostReservationIdsParser6 parser;
+ ASSERT_NO_THROW(parser.parse(config_element));
+
+ ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
+ getCfgHostOperations6();
+ const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
+ ASSERT_EQ(3, ids.size());
+
+ CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
+ EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+ EXPECT_EQ(*id++, Host::IDENT_DUID);
+ EXPECT_EQ(*id++, Host::IDENT_FLEX);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed before the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp6AutoBeforeIdentifier) {
+ std::string config = "[ \"auto\", \"duid\" ]";
+ testInvalidConfig<HostReservationIdsParser6>(config);
+}
+
+// This test verifies that use of "auto" together with an explicit
+// identifier causes an error. "auto" is placed after the explicit
+// identifier.
+TEST_F(HostReservationIdsParserTest, dhcp6AutoAfterIdentifier) {
+ std::string config = "[ \"duid\", \"auto\" ]";
+ testInvalidConfig<HostReservationIdsParser6>(config);
+}
+
+// Test that empty list of identifier types is not allowed.
+TEST_F(HostReservationIdsParserTest, dhcp6EmptyList) {
+ std::string config = "[ ]";
+ testInvalidConfig<HostReservationIdsParser6>(config);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc
new file mode 100644
index 0000000..c10adde
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc
@@ -0,0 +1,388 @@
+// Copyright (C) 2014-2018,2021 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 <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <testutils/test_to_element.h>
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Test fixture class for @c HostReservationsListParser.
+class HostReservationsListParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ HostReservationsListParserTest() = default;
+
+ /// @brief Destructor
+ virtual ~HostReservationsListParserTest() = default;
+
+protected:
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr. It also initializes
+ /// hwaddr_ and duid_ class members.
+ virtual void SetUp();
+
+ /// @brief Clean up after each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void TearDown();
+
+ /// @brief HW Address object used by tests.
+ HWAddrPtr hwaddr_;
+
+ /// @brief DUID object used by tests.
+ DuidPtr duid_;
+};
+
+void
+HostReservationsListParserTest::SetUp() {
+ CfgMgr::instance().clear();
+ // Initialize HW address used by tests.
+ const uint8_t hwaddr_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+ hwaddr_ = HWAddrPtr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+ HTYPE_ETHER));
+
+ // Initialize DUID used by tests.
+ const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A };
+ duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data)));
+}
+
+void
+HostReservationsListParserTest::TearDown() {
+ CfgMgr::instance().clear();
+}
+
+/// @brief class of subnet_id reservations
+class CfgHostsSubnet : public CfgToElement {
+public:
+ /// @brief constructor
+ CfgHostsSubnet(ConstCfgHostsPtr hosts, SubnetID id)
+ : hosts_(hosts), id_(id) { }
+
+ /// @brief unparse method
+ ElementPtr toElement() const;
+
+private:
+ /// @brief the host reservation configuration
+ ConstCfgHostsPtr hosts_;
+
+ /// @brief the subnet ID
+ SubnetID id_;
+};
+
+ElementPtr
+CfgHostsSubnet::toElement() const {
+ CfgHostsList list;
+ try {
+ list.internalize(hosts_->toElement());
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "CfgHostsSubnet::toElement: " << ex.what();
+ }
+ ElementPtr result = boost::const_pointer_cast<Element>(list.get(id_));
+
+ // Strip
+ for (size_t i = 0; i < result->size(); ++i) {
+ ElementPtr resv = result->getNonConst(i);
+ ConstElementPtr ip_address = resv->get("ip-address");
+ if (ip_address && (ip_address->stringValue() == "0.0.0.0")) {
+ resv->remove("ip-address");
+ }
+ ConstElementPtr ip_addresses = resv->get("ip-addresses");
+ if (ip_addresses && ip_addresses->empty()) {
+ resv->remove("ip-addresses");
+ }
+ ConstElementPtr prefixes = resv->get("prefixes");
+ if (prefixes && prefixes->empty()) {
+ resv->remove("prefixes");
+ }
+ ConstElementPtr hostname = resv->get("hostname");
+ if (hostname && hostname->stringValue().empty()) {
+ resv->remove("hostname");
+ }
+ ConstElementPtr next_server = resv->get("next-server");
+ if (next_server && (next_server->stringValue() == "0.0.0.0")) {
+ resv->remove("next-server");
+ }
+ ConstElementPtr server_hostname = resv->get("server-hostname");
+ if (server_hostname && server_hostname->stringValue().empty()) {
+ resv->remove("server-hostname");
+ }
+ ConstElementPtr boot_file_name = resv->get("boot-file-name");
+ if (boot_file_name && boot_file_name->stringValue().empty()) {
+ resv->remove("boot-file-name");
+ }
+ ConstElementPtr client_classes = resv->get("client-classes");
+ if (client_classes && client_classes->empty()) {
+ resv->remove("client-classes");
+ }
+ ConstElementPtr option_data = resv->get("option-data");
+ if (option_data && option_data->empty()) {
+ resv->remove("option-data");
+ }
+ }
+ return (result);
+}
+
+// This test verifies that the parser for the list of the host reservations
+// parses IPv4 reservations correctly.
+TEST_F(HostReservationsListParserTest, ipv4Reservations) {
+ CfgMgr::instance().setFamily(AF_INET);
+ // hexadecimal in lower case for toElement()
+ std::string config =
+ "[ "
+ " { "
+ " \"hw-address\": \"01:02:03:04:05:06\","
+ " \"ip-address\": \"192.0.2.134\","
+ " \"hostname\": \"foo.example.com\""
+ " }, "
+ " { "
+ " \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ " \"ip-address\": \"192.0.2.110\","
+ " \"hostname\": \"bar.example.com\""
+ " } "
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ ASSERT_NO_THROW(parser.parse(SubnetID(1), config_element, hosts));
+
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+
+ // Get the first reservation for the host identified by the HW address.
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+ &hwaddr_->hwaddr_[0],
+ hwaddr_->hwaddr_.size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.134", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+ // Get the second reservation for the host identified by the DUID.
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.110", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("bar.example.com", hosts[0]->getHostname());
+
+ // Get back the config from cfg_hosts
+ boost::algorithm::to_lower(config);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>(config, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SUBNET_ID_UNUSED);
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that an attempt to add two reservations with the
+// same identifier value will return an error.
+TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue4) {
+ std::vector<std::string> identifiers;
+ identifiers.push_back("hw-address");
+ identifiers.push_back("duid");
+ identifiers.push_back("circuit-id");
+ identifiers.push_back("flex-id");
+
+ for (unsigned int i = 0; i < identifiers.size(); ++i) {
+ SCOPED_TRACE("Using identifier " + identifiers[i]);
+
+ std::ostringstream config;
+ config <<
+ "[ "
+ " { "
+ " \"" << identifiers[i] << "\": \"010203040506\","
+ " \"ip-address\": \"192.0.2.134\","
+ " \"hostname\": \"foo.example.com\""
+ " }, "
+ " { "
+ " \"" << identifiers[i] << "\": \"010203040506\","
+ " \"ip-address\": \"192.0.2.110\","
+ " \"hostname\": \"bar.example.com\""
+ " } "
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config.str());
+
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser4> parser;
+ EXPECT_THROW({
+ parser.parse(SubnetID(1), config_element, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }, DuplicateHost);
+ // The code threw exception, because the second insertion failed.
+ // Nevertheless, the first host insertion succeeded, so the next
+ // time we try to insert them, we will get ReservedAddress exception,
+ // rather than DuplicateHost. Therefore we need to remove the
+ // first host that's still there.
+ CfgMgr::instance().clear();
+ }
+}
+
+// This test verifies that the parser for the list of the host reservations
+// parses IPv6 reservations correctly.
+TEST_F(HostReservationsListParserTest, ipv6Reservations) {
+ // hexadecimal in lower case for toElement()
+ std::string config =
+ "[ "
+ " { \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+ " \"ip-addresses\": [ ],"
+ " \"prefixes\": [ \"2001:db8:1:2::/80\" ],"
+ " \"hostname\": \"foo.example.com\" "
+ " }, "
+ " { \"hw-address\": \"01:02:03:04:05:06\","
+ " \"ip-addresses\": [ \"2001:db8:1::123\" ],"
+ " \"hostname\": \"bar.example.com\" "
+ " } "
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration.
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ ASSERT_NO_THROW(parser.parse(SubnetID(2), config_element, hosts));
+
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+
+ CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+
+ // Get the reservation for the host identified by the HW address.
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+ &hwaddr_->hwaddr_[0],
+ hwaddr_->hwaddr_.size()));
+ ASSERT_EQ(1, hosts.size());
+
+ // Make sure it belongs to a valid subnet.
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+
+ // Get the reserved addresses for the host. There should be exactly one
+ // address reserved for this host.
+ IPv6ResrvRange prefixes =
+ hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+
+ EXPECT_EQ(IPv6Resrv::TYPE_NA, prefixes.first->second.getType());
+ EXPECT_EQ("2001:db8:1::123", prefixes.first->second.getPrefix().toText());
+ EXPECT_EQ(128, prefixes.first->second.getPrefixLen());
+
+ // Validate the second reservation.
+ ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
+ &duid_->getDuid()[0],
+ duid_->getDuid().size()));
+ ASSERT_EQ(1, hosts.size());
+
+ EXPECT_EQ(SUBNET_ID_UNUSED, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+
+ // This reservation was for a prefix, instead of an IPv6 address.
+ prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+
+ EXPECT_EQ(IPv6Resrv::TYPE_PD, prefixes.first->second.getType());
+ EXPECT_EQ("2001:db8:1:2::", prefixes.first->second.getPrefix().toText());
+ EXPECT_EQ(80, prefixes.first->second.getPrefixLen());
+
+ // Get back the config from cfg_hosts
+ ElementPtr resv = config_element->getNonConst(0);
+ resv->remove("ip-addresses");
+ config = prettyPrint(config_element);
+ boost::algorithm::to_lower(config);
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(2));
+ runToElementTest<CfgHostsSubnet>(config, cfg_subnet);
+
+ CfgMgr::instance().setFamily(AF_INET);
+ CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(2));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+ runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
+// This test verifies that an attempt to add two reservations with the
+// same identifier value will return an error.
+TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue6) {
+ std::vector<std::string> identifiers;
+ identifiers.push_back("hw-address");
+ identifiers.push_back("duid");
+ identifiers.push_back("flex-id");
+
+ for (unsigned int i = 0; i < identifiers.size(); ++i) {
+ SCOPED_TRACE("Using identifier " + identifiers[i]);
+
+ std::ostringstream config;
+ config <<
+ "[ "
+ " { "
+ " \"" << identifiers[i] << "\": \"010203040506\","
+ " \"ip-addresses\": [ \"2001:db8:1::123\" ],"
+ " \"hostname\": \"foo.example.com\""
+ " }, "
+ " { "
+ " \"" << identifiers[i] << "\": \"010203040506\","
+ " \"ip-addresses\": [ \"2001:db8:1::123\" ],"
+ " \"hostname\": \"bar.example.com\""
+ " } "
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config.str());
+
+ HostCollection hosts;
+ HostReservationsListParser<HostReservationParser6> parser;
+ EXPECT_THROW({
+ parser.parse(SubnetID(1), config_element, hosts);
+ for (auto h = hosts.begin(); h != hosts.end(); ++h) {
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(*h);
+ }
+ }, DuplicateHost);
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/host_unittest.cc b/src/lib/dhcpsrv/tests/host_unittest.cc
new file mode 100644
index 0000000..be8c674
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/host_unittest.cc
@@ -0,0 +1,1455 @@
+// Copyright (C) 2014-2023 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 <dhcpsrv/host.h>
+#include <dhcp/option_space.h>
+#include <testutils/gtest_utils.h>
+#include <util/encode/hex.h>
+#include <util/range_utilities.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <cstdlib>
+#include <unordered_set>
+#include <sstream>
+#include <boost/functional/hash.hpp>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+/// @brief Holds a type of the last identifier in @c IdentifierType enum.
+///
+/// This value must be updated when new identifiers are added to the enum.
+const Host::IdentifierType LAST_IDENTIFIER_TYPE = Host::IDENT_CLIENT_ID;
+
+// This test verifies that it is possible to create IPv6 address
+// reservation.
+TEST(IPv6ResrvTest, constructorAddress) {
+ IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"));
+ EXPECT_EQ("2001:db8:1::cafe", resrv.getPrefix().toText());
+ EXPECT_EQ(128, resrv.getPrefixLen());
+ EXPECT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
+}
+
+// This test verifies that it is possible to create IPv6 prefix
+// reservation.
+TEST(IPv6ResrvTest, constructorPrefix) {
+ IPv6Resrv resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
+ EXPECT_EQ("2001:db8:1::", resrv.getPrefix().toText());
+ EXPECT_EQ(64, resrv.getPrefixLen());
+ EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
+}
+
+// This test verifies that the toText() function prints correctly.
+TEST(IPv6ResrvTest, toText) {
+ IPv6Resrv resrv_prefix(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
+ EXPECT_EQ("2001:db8:1::/64", resrv_prefix.toText());
+
+ IPv6Resrv resrv_address(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:111::23"));
+ EXPECT_EQ("2001:db8:111::23", resrv_address.toText());
+}
+
+// This test verifies that invalid prefix is rejected.
+TEST(IPv6ResrvTest, constructorInvalidPrefix) {
+ // IPv4 address is invalid for IPv6 reservation.
+ string expected = "invalid prefix '10.0.0.1' for new IPv6 reservation";
+ EXPECT_THROW_MSG(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+ isc::BadValue, expected);
+ // Multicast address is invalid for IPv6 reservation.
+ expected = "invalid prefix 'ff02:1::2' for new IPv6 reservation";
+ EXPECT_THROW_MSG(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("ff02:1::2"), 128),
+ isc::BadValue, expected);
+}
+
+// This test verifies that invalid prefix length is rejected.
+TEST(IPv6ResrvTest, constructiorInvalidPrefixLength) {
+ ASSERT_NO_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"),
+ 128));
+ string expected = "invalid prefix length '129' for new IPv6 reservation";
+ EXPECT_THROW_MSG(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1::"), 129),
+ isc::BadValue, expected);
+ expected = "invalid prefix length '244' for new IPv6 reservation";
+ EXPECT_THROW_MSG(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1::"), 244),
+ isc::BadValue, expected);
+ expected = "invalid prefix length '64' for reserved IPv6 address, expected 128";
+ EXPECT_THROW_MSG(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::"), 64),
+ isc::BadValue, expected);
+}
+
+// This test verifies that it is possible to modify prefix and its
+// length in an existing reservation.
+TEST(IPv6ResrvTest, setPrefix) {
+ // Create a reservation using an address and prefix length 128.
+ IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"));
+ ASSERT_EQ("2001:db8:1::1", resrv.getPrefix().toText());
+ ASSERT_EQ(128, resrv.getPrefixLen());
+ ASSERT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
+
+ // Modify the reservation to use a prefix having a length of 48.
+ ASSERT_NO_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+ EXPECT_EQ("2001:db8::", resrv.getPrefix().toText());
+ EXPECT_EQ(48, resrv.getPrefixLen());
+ EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
+
+ // IPv4 address is invalid for IPv6 reservation.
+ string expected = "invalid prefix '10.0.0.1' for new IPv6 reservation";
+ EXPECT_THROW_MSG(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+ isc::BadValue, expected);
+ // IPv6 multicast address is invalid for IPv6 reservation.
+ expected = "invalid prefix 'ff02::1:2' for new IPv6 reservation";
+ EXPECT_THROW_MSG(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("ff02::1:2"), 128),
+ isc::BadValue, expected);
+ // Prefix length greater than 128 is invalid.
+ expected = "invalid prefix length '129' for new IPv6 reservation";
+ EXPECT_THROW_MSG(resrv.set(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1::"), 129),
+ isc::BadValue, expected);
+}
+
+// This test checks that the equality operators work fine.
+TEST(IPv6ResrvTest, equal) {
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
+
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) ==
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) !=
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
+}
+
+/// @brief Test fixture class for @c Host.
+class HostTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Re-initializes random number generator.
+ HostTest() {
+ srand(1);
+ }
+
+ /// @brief Checks if the reservation is in the range of reservations.
+ ///
+ /// @param resrv Reservation to be searched for.
+ /// @param range Range of reservations returned by the @c Host object
+ /// in which the reservation will be searched.
+ ///
+ /// @return true if reservation exists, false otherwise.
+ bool
+ reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
+ for (IPv6ResrvIterator it = range.first; it != range.second;
+ ++it) {
+ if (resrv == it->second) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ /// @brief Returns upper bound of the supported identifier types.
+ ///
+ /// Some unit tests verify the @c Host class behavior for all
+ /// supported identifier types. The unit test needs to iterate
+ /// over all supported identifier types and thus it must be
+ /// aware of the upper bound of the @c Host::IdentifierType
+ /// enum. The upper bound is the numeric representation of the
+ /// last identifier type plus 1.
+ unsigned int
+ identifierTypeUpperBound() const {
+ return (static_cast<unsigned int>(LAST_IDENTIFIER_TYPE) + 1);
+ }
+};
+
+// This test verifies that expected identifier max length is returned.
+TEST_F(HostTest, getIdentifierMaxLength) {
+ EXPECT_EQ(20, Host::getIdentifierMaxLength(Host::IDENT_HWADDR));
+ EXPECT_EQ(130, Host::getIdentifierMaxLength(Host::IDENT_DUID));
+ EXPECT_EQ(128, Host::getIdentifierMaxLength(Host::IDENT_CIRCUIT_ID));
+ EXPECT_EQ(255, Host::getIdentifierMaxLength(Host::IDENT_CLIENT_ID));
+ EXPECT_EQ(128, Host::getIdentifierMaxLength(Host::IDENT_FLEX));
+}
+
+// This test verifies that correct identifier name is returned for
+// a given identifier name and that an error is reported for an
+// unsupported identifier name.
+TEST_F(HostTest, getIdentifier) {
+ EXPECT_EQ(Host::IDENT_HWADDR, Host::getIdentifierType("hw-address"));
+ EXPECT_EQ(Host::IDENT_DUID, Host::getIdentifierType("duid"));
+ EXPECT_EQ(Host::IDENT_CIRCUIT_ID, Host::getIdentifierType("circuit-id"));
+ EXPECT_EQ(Host::IDENT_CLIENT_ID, Host::getIdentifierType("client-id"));
+ EXPECT_EQ(Host::IDENT_FLEX, Host::getIdentifierType("flex-id"));
+
+ string expected = "invalid client identifier type 'unsupported'";
+ EXPECT_THROW_MSG(Host::getIdentifierType("unsupported"),
+ isc::BadValue, expected);
+}
+
+// This test verifies that it is possible to create a Host object
+// using hardware address in the textual format.
+TEST_F(HostTest, createFromHWAddrString) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ string(), string(),
+ IOAddress("192.0.0.2"),
+ "server-hostname.example.org",
+ "bootfile.efi", AuthKey("12345678"))));
+ // The HW address should be set to non-null.
+ HWAddrPtr hwaddr = host->getHWAddress();
+ ASSERT_TRUE(hwaddr);
+
+ EXPECT_EQ("hwtype=1 01:02:03:04:05:06", hwaddr->toText());
+
+ // DUID should be null if hardware address is in use.
+ EXPECT_FALSE(host->getDuid());
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
+ EXPECT_EQ("somehost.example.org", host->getHostname());
+ EXPECT_EQ("192.0.0.2", host->getNextServer().toText());
+ EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
+ EXPECT_EQ("bootfile.efi", host->getBootFileName());
+ EXPECT_EQ("12345678", host->getKey().toText());
+ EXPECT_FALSE(host->getContext());
+
+ // Use invalid identifier name
+ string expected = "invalid client identifier type 'bogus'";
+ EXPECT_THROW_MSG(Host("01:02:03:04:05:06", "bogus",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+
+ // Use invalid HW address.
+ expected = "invalid host identifier value '01:0203040506'";
+ EXPECT_THROW_MSG(Host("01:0203040506", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+
+ // Use too long HW address.
+ string too_long = "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f";
+ too_long += ":10:11:12:13:14";
+ expected = "too long client identifier type hw-address length 21";
+ EXPECT_THROW_MSG(Host(too_long, "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+}
+
+// This test verifies that it is possible to create Host object using
+// a DUID in the textual format.
+TEST_F(HostTest, createFromDUIDString) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("a1:b2:c3:d4:e5:06", "duid",
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+
+ // DUID should be set to non-null value.
+ DuidPtr duid = host->getDuid();
+ ASSERT_TRUE(duid);
+
+ EXPECT_EQ("a1:b2:c3:d4:e5:06", duid->toText());
+
+ // Hardware address must be null if DUID is in use.
+ EXPECT_FALSE(host->getHWAddress());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+
+ // Use invalid DUID.
+ string expected = "invalid host identifier value 'bogus'";
+ EXPECT_THROW_MSG(Host("bogus", "duid", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+
+ // Empty DUID (and identifiers in general) is also not allowed.
+ expected = "empty host identifier used";
+ EXPECT_THROW_MSG(Host("", "duid", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+ EXPECT_THROW_MSG(Host("", "flex-id", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+
+ // Too long DUID (and identifiers in general, hardware addresses are
+ // shorter) is not allowed too.
+ string too_long = "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f";
+ too_long += ":10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f";
+ too_long += ":20:21:22:23:24:25:26:27:28:29:2a:2b:2c:2d:2e:2f";
+ too_long += ":30:31:32:33:34:35:36:37:38:39:3a:3b:3c:3d:3e:3f";
+ too_long += ":40:41:42:43:44:45:46:47:48:49:4a:4b:4c:4d:4e:4f";
+ too_long += ":50:51:52:53:54:55:56:57:58:59:5a:5b:5c:5d:5e:5f";
+ too_long += ":60:61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f";
+ too_long += ":70:71:72:73:74:75:76:77:78:79:7a:7b:7c:7d:7e:7f";
+ too_long += ":80:81:ff";
+ expected = "too long client identifier type duid length 131";
+ EXPECT_THROW_MSG(Host(too_long, "duid", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+ expected = "too long client identifier type circuit-id length 131";
+ EXPECT_THROW_MSG(Host(too_long, "circuit-id", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue, expected);
+}
+
+// This test verifies that it is possible to create Host object using
+// hardware address in the binary format.
+TEST_F(HostTest, createFromHWAddrBinary) {
+ HostPtr host;
+ // Prepare the hardware address in binary format.
+ const uint8_t hwaddr_data[] = {
+ 0xaa, 0xab, 0xca, 0xda, 0xbb, 0xee
+ };
+ ASSERT_NO_THROW(host.reset(new Host(hwaddr_data,
+ sizeof(hwaddr_data),
+ Host::IDENT_HWADDR,
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ string(), string(),
+ IOAddress("192.0.0.2"),
+ "server-hostname.example.org",
+ "bootfile.efi", AuthKey("0abc1234"))));
+
+ // Hardware address should be non-null.
+ HWAddrPtr hwaddr = host->getHWAddress();
+ ASSERT_TRUE(hwaddr);
+
+ EXPECT_EQ("hwtype=1 aa:ab:ca:da:bb:ee", hwaddr->toText());
+
+ // DUID should be null if hardware address is in use.
+ EXPECT_FALSE(host->getDuid());
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
+ EXPECT_EQ("somehost.example.org", host->getHostname());
+ EXPECT_EQ("192.0.0.2", host->getNextServer().toText());
+ EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
+ EXPECT_EQ("bootfile.efi", host->getBootFileName());
+ EXPECT_EQ("0ABC1234", host->getKey().toText());
+ EXPECT_FALSE(host->getContext());
+
+ uint8_t too_long[21];
+ for (uint8_t i = 0; i < 21; ++i) {
+ too_long[i] = i;
+ }
+ string expected = "too long client identifier type hw-address length 21";
+ EXPECT_THROW_MSG(host.reset(new Host(too_long,
+ sizeof(too_long),
+ Host::IDENT_HWADDR,
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ string(), string(),
+ IOAddress("192.0.0.2"))),
+ isc::BadValue, expected);
+}
+
+// This test verifies that it is possible to create a Host object using
+// DUID in the binary format.
+TEST_F(HostTest, createFromDuidBinary) {
+ HostPtr host;
+ // Prepare DUID binary.
+ const uint8_t duid_data[] = {
+ 1, 2, 3, 4, 5, 6
+ };
+ ASSERT_NO_THROW(host.reset(new Host(duid_data,
+ sizeof(duid_data),
+ Host::IDENT_DUID,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+ // DUID should be non null.
+ DuidPtr duid = host->getDuid();
+ ASSERT_TRUE(duid);
+
+ EXPECT_EQ("01:02:03:04:05:06", duid->toText());
+
+ // Hardware address should be null if DUID is in use.
+ EXPECT_FALSE(host->getHWAddress());
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+
+ uint8_t too_long[DUID::MAX_DUID_LEN + 1];
+ for (uint8_t i = 0; i < sizeof(too_long); ++i) {
+ too_long[i] = i;
+ }
+ string expected = "too long client identifier type duid length 131";
+ EXPECT_THROW_MSG(host.reset(new Host(too_long,
+ sizeof(too_long),
+ Host::IDENT_DUID,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")),
+ isc::BadValue, expected);
+}
+
+// This test verifies that it is possible create Host instance using all
+// supported identifiers in a binary format.
+TEST_F(HostTest, createFromIdentifierBinary) {
+ HostPtr host;
+ // Iterate over all supported identifier types.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+ const Host::IdentifierType type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of variable length and fill with random values.
+ vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // Try to create a Host instance using this identifier.
+ ASSERT_NO_THROW(host.reset(new Host(&identifier[0], identifier.size(),
+ type, SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+
+ // Retrieve identifier from Host instance and check if it is correct.
+ const vector<uint8_t>& identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to create Host instance using
+// all supported identifiers in hexadecimal format.
+TEST_F(HostTest, createFromIdentifierHex) {
+ HostPtr host;
+ // Iterate over all supported identifiers.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+ const Host::IdentifierType type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of a variable length.
+ vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // HW address is a special case, because it must contain colons
+ // between consecutive octets.
+ HWAddrPtr hwaddr;
+ if (type == Host::IDENT_HWADDR) {
+ hwaddr.reset(new HWAddr(identifier, HTYPE_ETHER));
+ }
+
+ // Convert identifier to hexadecimal representation.
+ const string identifier_hex = (hwaddr ?
+ hwaddr->toText(false) :
+ util::encode::encodeHex(identifier));
+ const string identifier_name = Host::getIdentifierName(type);
+
+ // Try to create Host instance.
+ ASSERT_NO_THROW(host.reset(new Host(identifier_hex, identifier_name,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")))
+ << "test failed for " << identifier_name << "="
+ << identifier_hex;
+
+ // Retrieve the identifier from the Host instance and verify if it
+ // is correct.
+ const vector<uint8_t>& identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to create Host instance using
+// identifiers specified as text in quotes.
+TEST_F(HostTest, createFromIdentifierString) {
+ HostPtr host;
+ // It is not allowed to specify HW address or DUID as a string in quotes.
+ for (unsigned int i = 2; i < identifierTypeUpperBound(); ++i) {
+ const Host::IdentifierType type = static_cast<Host::IdentifierType>(i);
+ const string identifier_name = Host::getIdentifierName(type);
+
+ // Construct unique identifier for a host. This is a string
+ // consisting of a word "identifier", hyphen and the name of
+ // the identifier, e.g. "identifier-hw-address".
+ ostringstream identifier_without_quotes;
+ identifier_without_quotes << "identifier-" << identifier_name;
+
+ // Insert quotes to the identifier to indicate to the Host
+ // constructor that it is encoded as a text.
+ ostringstream identifier;
+ identifier << "'" << identifier_without_quotes.str() << "'";
+
+ ASSERT_NO_THROW(host.reset(new Host(identifier.str(), identifier_name,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")))
+ << "test failed for " << identifier_name << "="
+ << identifier.str();
+
+ // Get the identifier from the Host and convert it back to the string
+ // format, so as it can be compared with the identifier used during
+ // Host object construction.
+ const vector<uint8_t>& identifier_returned = host->getIdentifier();
+ const string identifier_returned_str(identifier_returned.begin(),
+ identifier_returned.end());
+ // Exclude quotes in comparison. Quotes should have been removed.
+ EXPECT_EQ(identifier_without_quotes.str(), identifier_returned_str);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to override a host identifier
+// using setIdentifier method with an identifier specified in
+// hexadecimal format.
+TEST_F(HostTest, setIdentifierHex) {
+ HostPtr host;
+ // Iterate over all supported identifiers.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+
+ // In order to test that setIdentifier replaces an existing
+ // identifier we have to initialize Host with a different
+ // identifier first. We pick the next identifier after the
+ // one we want to set. If 'i' points to the last one, we
+ // use the first one.
+ unsigned int j = (i + 1) % identifierTypeUpperBound();
+
+ Host::IdentifierType type = static_cast<Host::IdentifierType>(j);
+ // Create identifier of a variable length.
+ vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // HW address is a special case, because it must contain colons
+ // between consecutive octets.
+ HWAddrPtr hwaddr;
+ if (type == Host::IDENT_HWADDR) {
+ hwaddr.reset(new HWAddr(identifier, HTYPE_ETHER));
+ }
+
+ // Convert identifier to hexadecimal representation.
+ string identifier_hex = (hwaddr ?
+ hwaddr->toText(false) :
+ util::encode::encodeHex(identifier));
+ string identifier_name = Host::getIdentifierName(type);
+
+ // Try to create Host instance.
+ ASSERT_NO_THROW(host.reset(new Host(identifier_hex, identifier_name,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")))
+ << "test failed for " << identifier_name << "="
+ << identifier_hex;
+
+ // Retrieve the identifier from the Host instance and verify if it
+ // is correct.
+ vector<uint8_t> identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+
+ // Now use another identifier.
+ type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of a variable length.
+ identifier.resize(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ hwaddr.reset();
+ if (type == Host::IDENT_HWADDR) {
+ hwaddr.reset(new HWAddr(identifier, HTYPE_ETHER));
+ }
+
+ // Convert identifier to hexadecimal representation.
+ identifier_hex = (hwaddr ? hwaddr->toText(false) :
+ util::encode::encodeHex(identifier));
+ identifier_name = Host::getIdentifierName(type);
+
+ // Try to replace identifier for a host.
+ ASSERT_NO_THROW(host->setIdentifier(identifier_hex, identifier_name))
+ << "test failed for " << identifier_name << "="
+ << identifier_hex;
+
+ // Retrieve the identifier from the Host instance and verify if it
+ // is correct.
+ identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to override a host identifier
+// using setIdentifier method with an identifier specified in binary
+// format.
+TEST_F(HostTest, setIdentifierBinary) {
+ HostPtr host;
+ // Iterate over all supported identifier types.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+
+ // In order to test that setIdentifier replaces an existing
+ // identifier we have to initialize Host with a different
+ // identifier first. We pick the next identifier after the
+ // one we want to set. If 'i' points to the last one, we
+ // use the first one.
+ unsigned int j = (i + 1) % identifierTypeUpperBound();
+
+ Host::IdentifierType type = static_cast<Host::IdentifierType>(j);
+ // Create identifier of variable length and fill with random values.
+ vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // Try to create a Host instance using this identifier.
+ ASSERT_NO_THROW(host.reset(new Host(&identifier[0], identifier.size(),
+ type, SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+
+ // Retrieve identifier from Host instance and check if it is correct.
+ vector<uint8_t> identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+
+ type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of variable length and fill with random values.
+ identifier.resize(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // Try to set new identifier.
+ ASSERT_NO_THROW(host->setIdentifier(&identifier[0], identifier.size(),
+ type));
+
+ // Retrieve identifier from Host instance and check if it is correct.
+ identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that the IPv6 reservations of a different type can
+// be added for the host.
+TEST_F(HostTest, addReservations) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"))));
+
+ EXPECT_FALSE(host->hasIPv6Reservation());
+
+ // Add 4 reservations: 2 for NAs, 2 for PDs
+ ASSERT_NO_THROW(
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")));
+ );
+
+ EXPECT_TRUE(host->hasIPv6Reservation());
+
+ // Check that reservations exist.
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe"))));
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"),
+ 64)));
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"),
+ 64)));
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+
+ // Get only NA reservations.
+ IPv6ResrvRange addresses = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")),
+ addresses));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")),
+ addresses));
+
+
+ // Get only PD reservations.
+ IPv6ResrvRange prefixes = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64),
+ prefixes));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64),
+ prefixes));
+}
+
+// This test checks that various modifiers may be used to replace the current
+// values of the Host class.
+TEST_F(HostTest, setValues) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "some-host.eXAMple.org")));
+
+ ASSERT_EQ(1, host->getIPv4SubnetID());
+ ASSERT_EQ(2, host->getIPv6SubnetID());
+ ASSERT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
+ ASSERT_EQ("some-host.eXAMple.org", host->getHostname());
+ ASSERT_EQ("some-host.example.org", host->getLowerHostname());
+ ASSERT_FALSE(host->getContext());
+ ASSERT_FALSE(host->getNegative());
+
+ host->setIPv4SubnetID(SubnetID(123));
+ host->setIPv6SubnetID(SubnetID(234));
+ host->setIPv4Reservation(IOAddress("10.0.0.1"));
+ host->setHostname("other-host.eXAMple.org");
+ host->setNextServer(IOAddress("192.0.2.2"));
+ host->setServerHostname("server-hostname.example.org");
+ host->setBootFileName("bootfile.efi");
+ const vector<uint8_t>& random_value(AuthKey::getRandomKeyString());
+ host->setKey(AuthKey(random_value));
+ string user_context = "{ \"foo\": \"bar\" }";
+ host->setContext(Element::fromJSON(user_context));
+ host->setNegative(true);
+
+ EXPECT_EQ(123, host->getIPv4SubnetID());
+ EXPECT_EQ(234, host->getIPv6SubnetID());
+ EXPECT_EQ("10.0.0.1", host->getIPv4Reservation().toText());
+ EXPECT_EQ("other-host.eXAMple.org", host->getHostname());
+ ASSERT_EQ("other-host.example.org", host->getLowerHostname());
+ EXPECT_EQ("192.0.2.2", host->getNextServer().toText());
+ EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
+ EXPECT_EQ("bootfile.efi", host->getBootFileName());
+ EXPECT_EQ(random_value, host->getKey().getAuthKey());
+ ASSERT_TRUE(host->getContext());
+ EXPECT_EQ(user_context, host->getContext()->str());
+ EXPECT_TRUE(host->getNegative());
+
+ // Remove IPv4 reservation.
+ host->removeIPv4Reservation();
+ EXPECT_EQ(IOAddress::IPV4_ZERO_ADDRESS(), host->getIPv4Reservation());
+
+ // An IPv6 address can't be used for IPv4 reservations.
+ string expected = "address '2001:db8:1::1' is not a valid IPv4 address";
+ EXPECT_THROW_MSG(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
+ isc::BadValue, expected);
+ // Zero address can't be set, the removeIPv4Reservation should be
+ // used instead.
+ expected = "must not make reservation for the '0.0.0.0' address";
+ EXPECT_THROW_MSG(host->setIPv4Reservation(IOAddress::IPV4_ZERO_ADDRESS()),
+ isc::BadValue, expected);
+ // Broadcast address can't be set.
+ expected = "must not make reservation for the '255.255.255.255' address";
+ EXPECT_THROW_MSG(host->setIPv4Reservation(IOAddress::IPV4_BCAST_ADDRESS()),
+ isc::BadValue, expected);
+
+ // Broadcast and IPv6 are invalid addresses for next server.
+ expected = "invalid next server address '255.255.255.255'";
+ EXPECT_THROW_MSG(host->setNextServer(asiolink::IOAddress::IPV4_BCAST_ADDRESS()),
+ isc::BadValue, expected);
+ expected = "next server address '2001:db8:1::1' is not a valid IPv4 address";
+ EXPECT_THROW_MSG(host->setNextServer(IOAddress("2001:db8:1::1")),
+ isc::BadValue, expected);
+}
+
+// Test that Host constructors initialize client classes from string.
+TEST_F(HostTest, clientClassesFromConstructor) {
+ HostPtr host;
+ // Prepare the hardware address in binary format.
+ const uint8_t hwaddr_data[] = {
+ 0xaa, 0xab, 0xca, 0xda, 0xbb, 0xee
+ };
+
+ // Try the "from binary" constructor.
+ ASSERT_NO_THROW(host.reset(new Host(hwaddr_data,
+ sizeof(hwaddr_data),
+ Host::IDENT_HWADDR,
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ "alpha, , beta",
+ "gamma")));
+
+ EXPECT_TRUE(host->getClientClasses4().contains("alpha"));
+ EXPECT_TRUE(host->getClientClasses4().contains("beta"));
+ EXPECT_FALSE(host->getClientClasses4().contains("gamma"));
+ EXPECT_TRUE(host->getClientClasses6().contains("gamma"));
+ EXPECT_FALSE(host->getClientClasses6().contains("alpha"));
+ EXPECT_FALSE(host->getClientClasses6().contains("beta"));
+
+ // Try the "from string" constructor.
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ "alpha, beta, gamma",
+ "beta, gamma")));
+
+ EXPECT_TRUE(host->getClientClasses4().contains("alpha"));
+ EXPECT_TRUE(host->getClientClasses4().contains("beta"));
+ EXPECT_TRUE(host->getClientClasses4().contains("gamma"));
+ EXPECT_FALSE(host->getClientClasses6().contains("alpha"));
+ EXPECT_TRUE(host->getClientClasses6().contains("beta"));
+ EXPECT_TRUE(host->getClientClasses6().contains("gamma"));
+}
+
+// Test that new client classes can be added for the Host.
+TEST_F(HostTest, addClientClasses) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"))));
+
+ EXPECT_FALSE(host->getClientClasses4().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses6().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses4().contains("bar"));
+ EXPECT_FALSE(host->getClientClasses6().contains("bar"));
+
+ host->addClientClass4("foo");
+ host->addClientClass6("bar");
+ EXPECT_TRUE(host->getClientClasses4().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses6().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses4().contains("bar"));
+ EXPECT_TRUE(host->getClientClasses6().contains("bar"));
+
+ host->addClientClass4("bar");
+ host->addClientClass6("foo");
+ EXPECT_TRUE(host->getClientClasses4().contains("foo"));
+ EXPECT_TRUE(host->getClientClasses6().contains("foo"));
+ EXPECT_TRUE(host->getClientClasses4().contains("bar"));
+ EXPECT_TRUE(host->getClientClasses6().contains("bar"));
+}
+
+// This test checks that it is possible to add DHCPv4 options for a host.
+TEST_F(HostTest, addOptions4) {
+ Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, false,
+ DHCP4_OPTION_SPACE));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp4 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, false, "isc"));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // It should be possible to retrieve DHCPv6 options but the container
+ // should be empty.
+ OptionContainerPtr options6 = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options6);
+ EXPECT_TRUE(options6->empty());
+
+ // Also make sure that for dhcp4 option space no DHCPv6 options are
+ // returned. This is to check that containers for DHCPv4 and DHCPv6
+ // options do not share information.
+ options6 = host.getCfgOption6()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options6);
+ EXPECT_TRUE(options6->empty());
+
+ // Validate codes of options added to dhcp4 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = host.getCfgOption4()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = host.getCfgOption4()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test checks that host-specific DHCPv4 options can be encapsulated.
+TEST_F(HostTest, encapsulateOptions4) {
+ Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+
+ OptionPtr option43(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS));
+ option43->setEncapsulatedSpace(VENDOR_ENCAPSULATED_OPTION_SPACE);
+ ASSERT_NO_THROW(host.getCfgOption4()->add(option43, false, false, DHCP4_OPTION_SPACE));
+
+ OptionPtr option1(new Option(Option::V4, 1));
+ ASSERT_NO_THROW(host.getCfgOption4()->add(option1, false, false,
+ VENDOR_ENCAPSULATED_OPTION_SPACE));
+
+ ASSERT_NO_THROW(host.encapsulateOptions());
+
+ auto returned_option43 = host.getCfgOption4()->get(DHCP4_OPTION_SPACE,
+ DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(returned_option43.option_);
+
+ auto returned_option1 = returned_option43.option_->getOption(1);
+ ASSERT_TRUE(returned_option1);
+}
+
+// This test checks that it is possible to add DHCPv6 options for a host.
+TEST_F(HostTest, addOptions6) {
+ Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, false,
+ DHCP6_OPTION_SPACE));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, false, "isc"));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // It should be possible to retrieve DHCPv4 options but the container
+ // should be empty.
+ OptionContainerPtr options4 = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options4);
+ EXPECT_TRUE(options4->empty());
+
+ // Also make sure that for dhcp6 option space no DHCPv4 options are
+ // returned. This is to check that containers for DHCPv4 and DHCPv6
+ // options do not share information.
+ options4 = host.getCfgOption4()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options4);
+ EXPECT_TRUE(options4->empty());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = host.getCfgOption6()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = host.getCfgOption6()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test checks that it is possible to add DHCPv6 options for a host.
+TEST_F(HostTest, encapsulateOptions6) {
+ Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+
+ OptionPtr option94(new Option(Option::V6, D6O_S46_CONT_MAPE));
+ option94->setEncapsulatedSpace(MAPE_V6_OPTION_SPACE);
+ ASSERT_NO_THROW(host.getCfgOption6()->add(option94, false, false, DHCP6_OPTION_SPACE));
+
+ OptionPtr option1(new Option(Option::V6, 1));
+ ASSERT_NO_THROW(host.getCfgOption6()->add(option1, false, false,
+ MAPE_V6_OPTION_SPACE));
+
+ ASSERT_NO_THROW(host.encapsulateOptions());
+
+ auto returned_option94 = host.getCfgOption6()->get(DHCP6_OPTION_SPACE,
+ D6O_S46_CONT_MAPE);
+ ASSERT_TRUE(returned_option94.option_);
+
+ auto returned_option1 = returned_option94.option_->getOption(1);
+ ASSERT_TRUE(returned_option1);
+}
+
+// This test verifies that it is possible to retrieve a textual
+// representation of the host identifier.
+TEST_F(HostTest, getIdentifierAsText) {
+ // HW address
+ Host host1("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+ EXPECT_EQ("hwaddr=010203040506", host1.getIdentifierAsText());
+
+ // DUID
+ Host host2("0a:0b:0c:0d:0e:0f:ab:cd:ef", "duid",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+ EXPECT_EQ("duid=0A0B0C0D0E0FABCDEF",
+ host2.getIdentifierAsText());
+
+ // Circuit id.
+ Host host3("'marcin's-home'", "circuit-id",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+ EXPECT_EQ("circuit-id=6D617263696E27732D686F6D65",
+ host3.getIdentifierAsText());
+}
+
+// This test verifies that conversion of the identifier type to a
+// name works correctly.
+TEST_F(HostTest, getIdentifierName) {
+ EXPECT_EQ("hw-address", Host::getIdentifierName(Host::IDENT_HWADDR));
+
+}
+
+// This test checks that Host object is correctly described in the
+// textual format using the toText method.
+TEST_F(HostTest, toText) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+
+ // Add 4 reservations: 2 for NAs, 2 for PDs.
+ ASSERT_NO_THROW(
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")));
+ );
+
+ // Add invisible user context
+ string user_context = "{ \"foo\": \"bar\" }";
+ host->setContext(Element::fromJSON(user_context));
+
+ // Make sure that the output is correct,
+ EXPECT_EQ("hwaddr=010203040506 ipv4_subnet_id=1 ipv6_subnet_id=2"
+ " hostname=myhost.example.com"
+ " ipv4_reservation=192.0.2.3"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservation0=2001:db8:1::cafe"
+ " ipv6_reservation1=2001:db8:1::1"
+ " ipv6_reservation2=2001:db8:1:1::/64"
+ " ipv6_reservation3=2001:db8:1:2::/64",
+ host->toText());
+
+ // Reset some of the data and make sure that the output is affected.
+ host->setHostname("");
+ host->removeIPv4Reservation();
+ host->setIPv4SubnetID(SUBNET_ID_UNUSED);
+ host->setNegative(true);
+
+ EXPECT_EQ("hwaddr=010203040506 ipv6_subnet_id=2"
+ " hostname=(empty) ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservation0=2001:db8:1::cafe"
+ " ipv6_reservation1=2001:db8:1::1"
+ " ipv6_reservation2=2001:db8:1:1::/64"
+ " ipv6_reservation3=2001:db8:1:2::/64"
+ " negative cached",
+ host->toText());
+
+ // Create host identified by DUID, instead of HWADDR, with a very
+ // basic configuration.
+ ASSERT_NO_THROW(host.reset(new Host("11:12:13:14:15", "duid",
+ SUBNET_ID_UNUSED, SUBNET_ID_UNUSED,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ "myhost")));
+
+ EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)", host->toText());
+
+ // Add some classes.
+ host->addClientClass4("modem");
+ host->addClientClass4("router");
+
+ EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)"
+ " dhcp4_class0=modem dhcp4_class1=router",
+ host->toText());
+
+ host->addClientClass6("hub");
+ host->addClientClass6("device");
+
+ // Note that now classes are in insert order.
+ EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)"
+ " dhcp4_class0=modem dhcp4_class1=router"
+ " dhcp6_class0=hub dhcp6_class1=device",
+ host->toText());
+
+ // Create global host identified by DUID, with a very basic configuration.
+ ASSERT_NO_THROW(host.reset(new Host("11:12:13:14:15", "duid",
+ SUBNET_ID_GLOBAL, SUBNET_ID_GLOBAL,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ "myhost")));
+
+ EXPECT_EQ("duid=1112131415 ipv4_subnet_id=0 ipv6_subnet_id=0 "
+ "hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)", host->toText());
+
+}
+
+// This test checks that Host object is correctly unparsed,
+TEST_F(HostTest, unparse) {
+ boost::scoped_ptr<Host> host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+
+ // Add 4 reservations: 2 for NAs, 2 for PDs.
+ ASSERT_NO_THROW(
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")));
+ );
+
+ // Add user context
+ string user_context = "{ \"comment\": \"a host reservation\" }";
+ host->setContext(Element::fromJSON(user_context));
+
+ // Make sure that the output is correct,
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"myhost.example.com\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"ip-address\": \"192.0.2.3\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\", "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"myhost.example.com\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"ip-addresses\": [ \"2001:db8:1::cafe\", \"2001:db8:1::1\" ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ \"2001:db8:1:1::/64\", \"2001:db8:1:2::/64\" ], "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement6()->str());
+
+ // Reset some of the data and make sure that the output is affected.
+ host->setHostname("");
+ host->removeIPv4Reservation();
+ host->setIPv4SubnetID(SUBNET_ID_UNUSED);
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\", "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"ip-addresses\": [ \"2001:db8:1::cafe\", \"2001:db8:1::1\" ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ \"2001:db8:1:1::/64\", \"2001:db8:1:2::/64\" ], "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement6()->str());
+
+ // Create host identified by DUID, instead of HWADDR, with a very
+ // basic configuration.
+ ASSERT_NO_THROW(host.reset(new Host("11:12:13:14:15", "duid",
+ SUBNET_ID_UNUSED, SUBNET_ID_UNUSED,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ "myhost")));
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\" "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"ip-addresses\": [ ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ ] "
+ "}",
+ host->toElement6()->str());
+
+ // Add some classes.
+ host->addClientClass4("modem");
+ host->addClientClass4("router");
+ // Set invisible negative cache.
+ host->setNegative(true);
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ \"modem\", \"router\" ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\" "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"ip-addresses\": [ ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ ] "
+ "}",
+ host->toElement6()->str());
+
+ // Now the classes are in defined order (vs. alphabetical order).
+ host->addClientClass6("hub");
+ host->addClientClass6("device");
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ \"modem\", \"router\" ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\" "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ \"hub\", \"device\" ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"ip-addresses\": [ ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ ] "
+ "}",
+ host->toElement6()->str());
+}
+
+// Test verifies if the host can store HostId properly.
+TEST_F(HostTest, hostId) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+ EXPECT_EQ(0, host->getHostId());
+
+ EXPECT_NO_THROW(host->setHostId(12345));
+
+ EXPECT_EQ(12345, host->getHostId());
+}
+
+// Test verifies if we can modify the host keys.
+TEST_F(HostTest, keys) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+ // Key must be empty
+ EXPECT_EQ(0, host->getKey().getAuthKey().size());
+ EXPECT_EQ("", host->getKey().toText());
+
+ // now set to random value
+ const vector<uint8_t>& random_key(AuthKey::getRandomKeyString());
+ host->setKey(AuthKey(random_key));
+ EXPECT_EQ(random_key, host->getKey().getAuthKey());
+}
+
+// Test verifies if getRandomKeyString can generate 1000 keys which are random
+TEST_F(HostTest, randomKeys) {
+ // use hashtable and set size to 1000
+ std::unordered_set<vector<uint8_t>,
+ boost::hash<vector<uint8_t>>> keys;
+
+ int dup_element = 0;
+ const uint16_t max_iter = 1000;
+ uint16_t iter_num = 0;
+ size_t max_hash_size = 1000;
+
+ keys.reserve(max_hash_size);
+
+ for (iter_num = 0; iter_num < max_iter; iter_num++) {
+ vector<uint8_t> key = AuthKey::getRandomKeyString();
+ if (keys.count(key)) {
+ dup_element++;
+ break;
+ }
+
+ keys.insert(key);
+ }
+
+ EXPECT_EQ(0, dup_element);
+}
+
+// Test performs basic functionality test of the AuthKey class
+TEST(AuthKeyTest, basicTest) {
+ // Call the constructor with default argument
+ // Default constructor should generate random string of 16 bytes
+ AuthKey defaultKey;
+ ASSERT_EQ(16, defaultKey.getAuthKey().size());
+
+ AuthKey longKey("0123456789abcdef1122334455667788");
+ ASSERT_EQ(16, longKey.getAuthKey().size());
+
+ // Check the setters for valid and invalid string
+ string key16ByteStr = "000102030405060708090A0B0C0D0E0F";
+ vector<uint8_t> key16ByteBin = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
+ };
+ string key18ByteStr = "0123456789abcdefgh";
+
+ AuthKey defaultTestKey;
+
+ defaultTestKey.setAuthKey(key16ByteStr);
+ ASSERT_EQ(16, defaultTestKey.getAuthKey().size());
+ ASSERT_EQ(key16ByteStr, defaultTestKey.toText());
+ ASSERT_EQ(key16ByteBin, defaultTestKey.getAuthKey());
+
+ string expected = "bad auth key: attempt to decode a value not in base16 char set";
+ ASSERT_THROW_MSG(defaultTestKey.setAuthKey(key18ByteStr),
+ BadValue, expected);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
new file mode 100644
index 0000000..9e65462
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
@@ -0,0 +1,392 @@
+// Copyright (C) 2015-2022 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 <cc/data.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Test fixture class for @c IfacesConfigParser
+class IfacesConfigParserTest : public ::testing::Test {
+protected:
+
+ /// @brief Setup for each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ ///
+ /// Clears the configuration in the @c CfgMgr.
+ virtual void TearDown();
+
+};
+
+void
+IfacesConfigParserTest::SetUp() {
+ CfgMgr::instance().clear();
+ IfaceMgr::instance().setTestMode(true);
+}
+
+void
+IfacesConfigParserTest::TearDown() {
+ CfgMgr::instance().clear();
+ IfaceMgr::instance().setTestMode(false);
+ IfaceMgr::instance().clearIfaces();
+ IfaceMgr::instance().closeSockets();
+ IfaceMgr::instance().detectIfaces();
+}
+
+// This test checks that the parser correctly parses the list of interfaces
+// on which the server should listen.
+TEST_F(IfacesConfigParserTest, interfaces) {
+ // Creates fake interfaces with fake addresses.
+ IfaceMgrTestConfig test_config(true);
+
+ // Configuration with one interface.
+ std::string config =
+ "{ \"interfaces\": [ \"eth0\" ], \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+
+ // Open sockets according to the parsed configuration.
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ ASSERT_NO_THROW(cfg->getCfgIface()->openSockets(AF_INET, 10000));
+
+ // Only eth0 should have an open socket.
+ EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+ EXPECT_FALSE(test_config.socketOpen("eth1", AF_INET));
+
+ // Reset configuration.
+ cfg->getCfgIface()->closeSockets();
+ CfgMgr::instance().clear();
+
+ // Try similar configuration but this time add a wildcard interface
+ // to see if sockets will open on all interfaces.
+ config = "{ \"interfaces\": [ \"eth0\", \"*\" ], \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+
+ cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ runToElementTest<CfgIface>(config, *cfg_iface);
+
+ cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_NO_THROW(cfg->getCfgIface()->openSockets(AF_INET, 10000));
+
+ EXPECT_TRUE(test_config.socketOpen("eth0", AF_INET));
+ EXPECT_TRUE(test_config.socketOpen("eth1", AF_INET));
+}
+
+// This test checks that the parser does not re-detect interfaces in test mode.
+TEST_F(IfacesConfigParserTest, testMode) {
+ // Creates fake interfaces with fake addresses.
+ IfaceMgrTestConfig test_config(true);
+
+ // Configuration with wildcard..
+ std::string config =
+ "{ \"interfaces\": [ \"*\" ], \"re-detect\": true }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration in test mode.
+ IfacesConfigParser parser(AF_INET, true);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Verify we still have the eth1961 interface.
+ EXPECT_TRUE(IfaceMgr::instance().getIface("eth1961"));
+
+ // Reparse in not test mode.
+ IfacesConfigParser parser2(AF_INET, false);
+ CfgMgr::instance().clear();
+ cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser2.parse(cfg_iface, config_element));
+
+ // The eth1961 interface no longer exists.
+ EXPECT_FALSE(IfaceMgr::instance().getIface("eth1961"));
+}
+
+// This test checks that the parsed structure can be converted back to Element
+// tree.
+TEST_F(IfacesConfigParserTest, toElement) {
+ // Creates fake interfaces with fake addresses.
+ IfaceMgrTestConfig test_config(true);
+
+ // Configuration with one interface.
+ std::string config =
+ "{ \"user-context\": { \"foo\": \"bar\" }, "
+ " \"interfaces\": [ \"eth0\" ], "
+ " \"dhcp-socket-type\": \"udp\","
+ " \"outbound-interface\": \"use-routing\", "
+ " \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+}
+
+
+// This test verifies that it is possible to select the raw socket
+// use in the configuration for interfaces.
+TEST_F(IfacesConfigParserTest, socketTypeRaw) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a raw socket selected.
+ std::string config = "{ ""\"interfaces\": [ ],"
+ " \"dhcp-socket-type\": \"raw\","
+ " \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Compare the resulting configuration with a reference
+ // configuration using the raw socket.
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ cfg_ref.useSocketType(AF_INET, CfgIface::SOCKET_RAW);
+ EXPECT_TRUE(*cfg->getCfgIface() == cfg_ref);
+}
+
+// This test verifies that it is possible to select the datagram socket
+// use in the configuration for interfaces.
+TEST_F(IfacesConfigParserTest, socketTypeDatagram) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a datagram socket selected.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"dhcp-socket-type\": \"udp\","
+ " \"re-detect\": false }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+
+ // Compare the resulting configuration with a reference
+ // configuration using the raw socket.
+ SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
+ ASSERT_TRUE(cfg);
+ cfg_ref.useSocketType(AF_INET, CfgIface::SOCKET_UDP);
+ ASSERT_TRUE(cfg->getCfgIface());
+ EXPECT_TRUE(*cfg->getCfgIface() == cfg_ref);
+}
+
+// Test that the configuration rejects the invalid socket type.
+TEST_F(IfacesConfigParserTest, socketTypeInvalid) {
+ // For DHCPv4 we only accept the raw socket or datagram socket.
+ IfacesConfigParser parser4(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ std::string config = "{ \"interfaces\": [ ],"
+ "\"dhcp-socket-type\": \"default\","
+ " \"re-detect\": false }";
+ ElementPtr config_element = Element::fromJSON(config);
+ ASSERT_THROW(parser4.parse(cfg_iface, config_element), DhcpConfigError);
+
+ // For DHCPv6 we don't accept any socket type.
+ IfacesConfigParser parser6(AF_INET6, false);
+ config = "{ \"interfaces\": [ ],"
+ " \"dhcp-socket-type\": \"udp\","
+ " \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+ ASSERT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+}
+
+// Tests that outbound-interface is parsed properly.
+TEST_F(IfacesConfigParserTest, outboundInterface) {
+ // For DHCPv4 we accept 'use-routing' or 'same-as-inbound'.
+ IfacesConfigParser parser4(AF_INET, false);
+
+ // For DHCPv6 we don't accept this at all.
+ IfacesConfigParser parser6(AF_INET6, false);
+
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+
+ // The default should be to use the same as client's query packet.
+ EXPECT_EQ(CfgIface::SAME_AS_INBOUND, cfg_iface->getOutboundIface());
+
+ // Value 1: use-routing
+ std::string config = "{ \"interfaces\": [ ],"
+ "\"outbound-interface\": \"use-routing\","
+ " \"re-detect\": false }";
+ ElementPtr config_element = Element::fromJSON(config);
+ ASSERT_NO_THROW(parser4.parse(cfg_iface, config_element));
+ EXPECT_EQ(CfgIface::USE_ROUTING, cfg_iface->getOutboundIface());
+ EXPECT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+
+ // Value 2: same-as-inbound
+ config = "{ \"interfaces\": [ ],"
+ "\"outbound-interface\": \"same-as-inbound\","
+ " \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+ ASSERT_NO_THROW(parser4.parse(cfg_iface, config_element));
+ EXPECT_EQ(CfgIface::SAME_AS_INBOUND, cfg_iface->getOutboundIface());
+ EXPECT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+
+ // Other values are not supported.
+ config = "{ \"interfaces\": [ ],"
+ "\"outbound-interface\": \"default\","
+ " \"re-detect\": false }";
+ config_element = Element::fromJSON(config);
+ EXPECT_THROW(parser4.parse(cfg_iface, config_element), DhcpConfigError);
+ EXPECT_THROW(parser6.parse(cfg_iface, config_element), DhcpConfigError);
+}
+
+// Tests that service-sockets-require-all is parsed properly.
+TEST_F(IfacesConfigParserTest, serviceSocketRequireAll) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a require all sockets to open selected.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-require-all\": true }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_TRUE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+}
+
+// Tests that service-sockets-max-retries is parsed properly.
+TEST_F(IfacesConfigParserTest, serviceSocketMaxRetries) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a non-zero retries selected.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-max-retries\": 42 }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_FALSE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Configuration should contain a number of retries and a wait time.
+ std::string expected_config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-retry-wait-time\": 5000,"
+ " \"service-sockets-max-retries\": 42 }";
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(expected_config, *cfg_iface);
+}
+
+// Tests that service-sockets-retry-wait-time is parsed properly if
+// service-sockets-max-retries is provided.
+TEST_F(IfacesConfigParserTest, serviceSocketRetryWaitTime) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with a non-zero number of retries and a non-default wait time.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-retry-wait-time\": 4224,"
+ " \"service-sockets-max-retries\": 42 }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_FALSE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(config, *cfg_iface);
+}
+
+// Tests that service-sockets-retry-wait-time is ignored if
+// service-sockets-max-retries is not provided.
+TEST_F(IfacesConfigParserTest, serviceSocketRetryWaitTimeWithoutMaxRetries) {
+ // Create the reference configuration, which we will compare
+ // the parsed configuration to.
+ CfgIface cfg_ref;
+
+ // Configuration with zero (default) retries and a non-default wait time.
+ std::string config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false,"
+ " \"service-sockets-retry-wait-time\": 4224 }";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse the configuration.
+ IfacesConfigParser parser(AF_INET, false);
+ CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
+ ASSERT_TRUE(cfg_iface);
+ ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
+ EXPECT_FALSE(cfg_iface->getServiceSocketsRequireAll());
+
+ // Retry wait time is not applicable; it is skipped.
+ std::string expected_config = "{ \"interfaces\": [ ],"
+ " \"re-detect\": false }";
+
+ // Check it can be unparsed.
+ runToElementTest<CfgIface>(expected_config, *cfg_iface);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc b/src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc
new file mode 100644
index 0000000..92fbced
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/ip_range_permutation_unittest.cc
@@ -0,0 +1,290 @@
+// Copyright (C) 2020-2023 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 <dhcpsrv/ip_range_permutation.h>
+
+#include <gtest/gtest.h>
+
+#include <set>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that the object can be successfully constructed for
+// both IPv4 and IPv6 address range.
+TEST(IPRangePermutationTest, constructor) {
+ ASSERT_NO_THROW({
+ AddressRange range(IOAddress("192.0.2.10"), IOAddress("192.0.2.100"));
+ IPRangePermutation perm(range);
+ });
+ ASSERT_NO_THROW({
+ AddressRange range(IOAddress("3000::"), IOAddress("3000::10"));
+ IPRangePermutation perm(range);
+ });
+}
+
+// This test verifies that a permutation of IPv4 address range can
+// be generated and each time a different permutation is generated.
+TEST(IPRangePermutationTest, ipv4) {
+ // Create address range with 91 addresses.
+ AddressRange range(IOAddress("192.0.2.10"), IOAddress("192.0.2.100"));
+
+ std::vector<std::vector<IOAddress>> iterations;
+ for (auto i = 0; i < 2; ++i) {
+ IPRangePermutation perm(range);
+ // This set will record unique IP addresses generated.
+ std::set<IOAddress> addrs;
+ // This vector will record the addresses assignment order.
+ std::vector<IOAddress> ordered_addrs;
+ bool done = false;
+ // Call the next() function 95 times. The first 91 calls should return non-zero
+ // IP addresses.
+ for (auto i = 0; i < 95; ++i) {
+ auto next = perm.next(done);
+ if (!next.isV4Zero()) {
+ // Make sure the returned address is within the range.
+ EXPECT_LE(range.start_, next);
+ EXPECT_LE(next, range.end_);
+ }
+ // If we went over all addresses in the range, the flags indicating that
+ // the permutation is exhausted should be set to true.
+ if (i >= 90) {
+ EXPECT_TRUE(done);
+ EXPECT_TRUE(perm.exhausted());
+ } else {
+ // We're not done yet, so these flag should still be false.
+ EXPECT_FALSE(done);
+ EXPECT_FALSE(perm.exhausted());
+ }
+ // Insert the address returned to the set and vector.
+ addrs.insert(next);
+ ordered_addrs.push_back(next);
+ }
+
+ // We should have recorded 92 unique addresses, including the zero address.
+ EXPECT_EQ(92, addrs.size());
+ EXPECT_TRUE(addrs.begin()->isV4Zero());
+
+ iterations.push_back(ordered_addrs);
+ }
+
+ // We want to make sure that each new permutation instance produces a different
+ // sequence of addresses. It checks whether or not the random device has been
+ // initialized properly. If the random device uses the same seed for each
+ // new permutation, the output sequence is always the same. The test below
+ // checks that the sequences are different by comparing the respective addresses
+ // for two different permutations. It is ok if some of them are equal because it
+ // is statistically probable. The threshold of 20% should guard against some
+ // of them being equal without a risk of sporadic test failures.
+ int overlaps = 0;
+ for (auto i = 0; i < iterations[0].size(); ++i) {
+ if (iterations[0][i] == iterations[1][i]) {
+ ++overlaps;
+ }
+ }
+
+ EXPECT_LE(overlaps, iterations[0].size()/5)
+ << "The number of overlapping random address between the test two iterations"
+ << " is greater than 20% of all allocated addresses in each iteration."
+ << " It means that the permutation mechanism does not sufficiently randomize"
+ << " addresses. Perhaps the randomization device is not properly initialized?";
+}
+
+// This test verifies that a permutation of IPv6 address range can
+// be generated and each time a different permutation is generated.
+TEST(IPRangePermutationTest, ipv6) {
+ AddressRange range(IOAddress("2001:db8:1::1:fea0"),
+ IOAddress("2001:db8:1::2:abcd"));
+
+ std::vector<std::vector<IOAddress>> iterations;
+ for (auto i = 0; i < 2; ++i) {
+ IPRangePermutation perm(range);
+ std::set<IOAddress> addrs;
+ std::vector<IOAddress> ordered_addrs;
+ bool done = false;
+ for (auto i = 0; i < 44335; ++i) {
+ auto next = perm.next(done);
+ if (!next.isV6Zero()) {
+ // Make sure that the address is within the range.
+ EXPECT_LE(range.start_, next);
+ EXPECT_LE(next, range.end_);
+ }
+ // If we went over all addresses in the range, the flags indicating that
+ // the permutation is exhausted should be set to true.
+ if (i >= 44333) {
+ EXPECT_TRUE(done);
+ EXPECT_TRUE(perm.exhausted());
+ } else {
+ // We're not done yet, so these flag should still be false.
+ EXPECT_FALSE(done);
+ EXPECT_FALSE(perm.exhausted());
+ }
+ // Insert the address returned to the set and vector.
+ addrs.insert(next);
+ ordered_addrs.push_back(next);
+ }
+ // We should have recorded 44335 unique addresses, including the zero address.
+ EXPECT_EQ(44335, addrs.size());
+ EXPECT_TRUE(addrs.begin()->isV6Zero());
+
+ iterations.push_back(ordered_addrs);
+ }
+
+ // We want to make sure that each new permutation instance produces a different
+ // sequence of addresses. It checks whether or not the random device has been
+ // initialized properly. If the random device uses the same seed for each
+ // new permutation, the output sequence is always the same. The test below
+ // checks that the sequences are different by comparing the respective addresses
+ // for two different permutations. It is ok if some of them are equal because it
+ // is statistically probable. The threshold of 20% should guard against some
+ // of them being equal without a risk of sporadic test failures.
+ int overlaps = 0;
+ for (auto i = 0; i < iterations[0].size(); ++i) {
+ if (iterations[0][i] == iterations[1][i]) {
+ ++overlaps;
+ }
+ }
+
+ EXPECT_LE(overlaps, iterations[0].size()/5)
+ << "The number of overlapping random address between the test two iterations"
+ << " is greater than 20% of all allocated addresses in each iteration."
+ << " It means that the permutation mechanism does not sufficiently randomize"
+ << " addresses. Perhaps the randomization device is not properly initialized?";
+}
+
+// This test verifies that a permutation of delegated prefixes can be
+// generated and each time a different permutation is generated.
+TEST(IPRangePermutationTest, pd) {
+ PrefixRange range(IOAddress("3000::"), 112, 120);
+
+ std::vector<std::vector<IOAddress>> iterations;
+ for (auto i = 0; i < 2; ++i) {
+ IPRangePermutation perm(range);
+ std::set<IOAddress> addrs;
+ std::vector<IOAddress> ordered_addrs;
+ bool done = false;
+ for (auto i = 0; i < 257; ++i) {
+ auto next = perm.next(done);
+ if (!next.isV6Zero()) {
+ // Make sure the prefix is within the range.
+ EXPECT_LE(range.start_, next);
+ EXPECT_LE(next, range.end_);
+ }
+ // If we went over all delegated prefixes in the range, the flags indicating
+ // that the permutation is exhausted should be set to true.
+ if (i >= 255) {
+ EXPECT_TRUE(done);
+ EXPECT_TRUE(perm.exhausted());
+ } else {
+ // We're not done yet, so these flag should still be false.
+ EXPECT_FALSE(done);
+ EXPECT_FALSE(perm.exhausted());
+ }
+ // Insert the prefix returned to the set and vector.
+ addrs.insert(next);
+ ordered_addrs.push_back(next);
+ }
+ // We should have recorded 257 unique addresses, including the zero address.
+ EXPECT_EQ(257, addrs.size());
+ EXPECT_TRUE(addrs.begin()->isV6Zero());
+
+ iterations.push_back(ordered_addrs);
+ }
+
+ ASSERT_EQ(2, iterations.size());
+
+ // We want to make sure that each new permutation instance produces a different
+ // sequence of prefixes. It checks whether or not the random device has been
+ // initialized properly. If the random device uses the same seed for each
+ // new permutation, the output sequence is always the same. The test below
+ // checks that the sequences are different by comparing the respective prefixes
+ // for two different permutations. It is ok if some of them are equal because it
+ // is statistically probable. The threshold of 20% should guard against some
+ // of them being equal without a risk of sporadic test failures.
+ int overlaps = 0;
+ for (auto i = 0; i < iterations[0].size(); ++i) {
+ if (iterations[0][i] == iterations[1][i]) {
+ ++overlaps;
+ }
+ }
+
+ EXPECT_LE(overlaps, iterations[0].size()/5)
+ << "The number of overlapping random prefixes between the test two iterations"
+ << " is greater than 20% of all allocated addresses in each iteration."
+ << " It means that the permutation mechanism does not sufficiently randomize"
+ << " prefixes. Perhaps the randomization device is not properly initialized?";
+}
+
+// This test verifies that a permutation of delegated prefixes is
+// generated from the prefix range specified using first and last
+// address.
+TEST(IPRangePermutationTest, pdStartEnd) {
+ PrefixRange range(IOAddress("3000::"), IOAddress("3000::ffff"), 120);
+ IPRangePermutation perm(range);
+
+ std::set<IOAddress> addrs;
+ bool done = false;
+ for (auto i = 0; i < 257; ++i) {
+ auto next = perm.next(done);
+ if (!next.isV6Zero()) {
+ // Make sure the prefix is within the range.
+ EXPECT_LE(range.start_, next);
+ EXPECT_LE(next, range.end_);
+ }
+ // If we went over all delegated prefixes in the range, the flags indicating
+ // that the permutation is exhausted should be set to true.
+ if (i >= 255) {
+ EXPECT_TRUE(done);
+ EXPECT_TRUE(perm.exhausted());
+ } else {
+ // We're not done yet, so these flag should still be false.
+ EXPECT_FALSE(done);
+ EXPECT_FALSE(perm.exhausted());
+ }
+ // Insert the prefix returned to the set.
+ addrs.insert(next);
+ }
+
+ // We should have recorded 257 unique addresses, including the zero address.
+ EXPECT_EQ(257, addrs.size());
+ EXPECT_TRUE(addrs.begin()->isV6Zero());
+}
+
+// This test verifies that it is possible to reset the permutation state.
+TEST(IPRangePermutationTest, reset) {
+ // Create address range with 11 addresses.
+ AddressRange range(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"));
+ IPRangePermutation perm(range);
+
+ // This set will record unique IP addresses generated.
+ std::set<IOAddress> addrs;
+ bool done = false;
+
+ // Call the next() function several times to consume several addresses.
+ for (auto i = 0; i < 5; ++i) {
+ auto next = perm.next(done);
+ EXPECT_FALSE(next.isV4Zero());
+ addrs.insert(next);
+ }
+ EXPECT_EQ(5, addrs.size());
+
+ // Reset the permutation. We should be able to get all addresses again.
+ perm.reset();
+
+ for (auto i = 0; i < 11; ++i) {
+ auto next = perm.next(done);
+ EXPECT_FALSE(next.isV4Zero());
+ addrs.insert(next);
+ }
+ EXPECT_EQ(11, addrs.size());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/ip_range_unittest.cc b/src/lib/dhcpsrv/tests/ip_range_unittest.cc
new file mode 100644
index 0000000..f705ff8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/ip_range_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (C) 2020-2023 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 <dhcpsrv/ip_range.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+// This test verifies that an exception is thrown upon an attempt to
+// create an address range from invalid values and that no exception
+// is thrown when the values are correct.
+TEST(AddressRangeTest, constructor) {
+ // The start address must be lower or equal the end address.
+ EXPECT_THROW(AddressRange(IOAddress("192.0.2.100"), IOAddress("192.0.2.99")),
+ BadValue);
+ // The start and end address must be of the same family.
+ EXPECT_THROW(AddressRange(IOAddress("192.0.2.100"), IOAddress("2001:db8:1::1")),
+ BadValue);
+ // It is allowed to create address range with a single IP address.
+ EXPECT_NO_THROW(AddressRange(IOAddress("192.0.2.100"), IOAddress("192.0.2.100")));
+}
+
+// This test verifies successful construction of the prefix range.
+TEST(PrefixRangeTest, constructor) {
+ boost::scoped_ptr<PrefixRange> range;
+ ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1::"), 64, 96)));
+ EXPECT_EQ("2001:db8:1::", range->start_.toText());
+ EXPECT_EQ("2001:db8:1:0:ffff:ffff:ffff:ffff", range->end_.toText());
+ EXPECT_EQ(64, range->prefix_length_);
+ EXPECT_EQ(96, range->delegated_length_);
+
+ ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1:2::"), 80, 120)));
+ EXPECT_EQ("2001:db8:1:2::", range->start_.toText());
+ EXPECT_EQ("2001:db8:1:2:0:ffff:ffff:ffff", range->end_.toText());
+ EXPECT_EQ(80, range->prefix_length_);
+ EXPECT_EQ(120, range->delegated_length_);
+
+ ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1:2::"), 120, 127)));
+ EXPECT_EQ("2001:db8:1:2::", range->start_.toText());
+ EXPECT_EQ("2001:db8:1:2::ff", range->end_.toText());
+ EXPECT_EQ(120, range->prefix_length_);
+ EXPECT_EQ(127, range->delegated_length_);
+
+ ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1::"), IOAddress("2001:db8:1:0:ffff:ffff:ffff:ffff"), 96)));
+ EXPECT_EQ("2001:db8:1::", range->start_.toText());
+ EXPECT_EQ("2001:db8:1:0:ffff:ffff:ffff:ffff", range->end_.toText());
+ EXPECT_EQ(64, range->prefix_length_);
+ EXPECT_EQ(96, range->delegated_length_);
+
+ ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1:2::"), IOAddress("2001:db8:1:2:0:ffff:ffff:ffff"), 120)));
+ EXPECT_EQ("2001:db8:1:2::", range->start_.toText());
+ EXPECT_EQ("2001:db8:1:2:0:ffff:ffff:ffff", range->end_.toText());
+ EXPECT_EQ(80, range->prefix_length_);
+ EXPECT_EQ(120, range->delegated_length_);
+
+ ASSERT_NO_THROW(range.reset(new PrefixRange(IOAddress("2001:db8:1:2::"), IOAddress("2001:db8:1:2::ff"), 127)));
+ EXPECT_EQ("2001:db8:1:2::", range->start_.toText());
+ EXPECT_EQ("2001:db8:1:2::ff", range->end_.toText());
+ EXPECT_EQ(120, range->prefix_length_);
+ EXPECT_EQ(127, range->delegated_length_);
+}
+
+// This test verifies that exception is thrown upon an attempt to
+// create a prefix range from invalid values.
+TEST(PrefixRangeTest, constructorWithInvalidValues) {
+ boost::scoped_ptr<PrefixRange> range;
+ // It must be IPv6 prefix.
+ EXPECT_THROW(PrefixRange(IOAddress("192.0.2.0"), 8, 16), BadValue);
+ // Delegated length must not be lower than prefix length.
+ EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1::"), 96, 64), BadValue);
+ // Lengths must not exceed 128.
+ EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1::"), 200, 204), BadValue);
+ // End must not be lower than start.
+ EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1:1::6:0"), IOAddress("2001:db8:1::5:0"), 112),
+ BadValue);
+ // Length must not exceed 128.
+ EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1:1::"), IOAddress("2001:db8:1:f::"), 200),
+ BadValue);
+ // The upper boundary of the prefix range must have non-significant
+ // bits set to 1.
+ EXPECT_THROW(PrefixRange(IOAddress("2001:db8:1:1::"), IOAddress("2001:db8:1:1::ff00"), 112),
+ BadValue);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/iterative_allocation_state_unittest.cc b/src/lib/dhcpsrv/tests/iterative_allocation_state_unittest.cc
new file mode 100644
index 0000000..9c93bb9
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/iterative_allocation_state_unittest.cc
@@ -0,0 +1,188 @@
+// Copyright (C) 2022-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/iterative_allocation_state.h>
+#include <testutils/multi_threading_utils.h>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+// Checks that last allocated IPv4 address is remembered in the iterative
+// allocation state.
+TEST(IterativeAllocationStateTest, subnetLastAllocated4) {
+ IOAddress addr("192.0.2.17");
+
+ IOAddress last("192.0.2.255");
+
+ auto subnet(boost::make_shared<Subnet4>(IOAddress("192.0.2.0"),
+ 24, 1, 2, 3, SubnetID(1)));
+ auto state = SubnetIterativeAllocationState::create(subnet);
+
+ // Check initial conditions (all should be set to the last address in range)
+ EXPECT_EQ(last.toText(), state->getLastAllocated().toText());
+
+ // Now set last allocated.
+ EXPECT_NO_THROW(state->setLastAllocated(addr));
+ EXPECT_EQ(addr.toText(), state->getLastAllocated().toText());
+}
+
+// Checks that the last allocated IPv4 address is remembered in the
+// iterative allocation state when multi threading is turned on.
+TEST(IterativeAllocationStateTest, subnetLastAllocated4MultiThreading) {
+ MultiThreadingTest mt(true);
+ IOAddress addr("192.0.2.17");
+
+ IOAddress last("192.0.2.255");
+
+ auto subnet(boost::make_shared<Subnet4>(IOAddress("192.0.2.0"),
+ 24, 1, 2, 3, SubnetID(1)));
+ auto state = SubnetIterativeAllocationState::create(subnet);
+
+ // Check initial conditions (all should be set to the last address in range)
+ EXPECT_EQ(last.toText(), state->getLastAllocated().toText());
+
+ // Now set last allocated.
+ EXPECT_NO_THROW(state->setLastAllocated(addr));
+ EXPECT_EQ(addr.toText(), state->getLastAllocated().toText());
+}
+
+// Checks if last allocated address/prefix is stored/retrieved properly.
+TEST(IterativeAllocationStateTest, subnetLastAllocated6) {
+ IOAddress na("2001:db8:1::1");
+ IOAddress ta("2001:db8:1::abcd");
+ IOAddress pd("2001:db8:1::1234:5678");
+
+ IOAddress last("2001:db8:1::ffff:ffff:ffff:ffff");
+
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 64, 1, 2, 3, 4, SubnetID(1));
+ auto state_na = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_NA));
+ auto state_ta = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_TA));
+ auto state_pd = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_PD));
+
+ // Check initial conditions (all should be set to the last address in range)
+ EXPECT_EQ(last.toText(), state_na->getLastAllocated().toText());
+ EXPECT_EQ(last.toText(), state_ta->getLastAllocated().toText());
+ EXPECT_EQ(last.toText(), state_pd->getLastAllocated().toText());
+
+ // Now set last allocated for IA
+ EXPECT_NO_THROW(state_na->setLastAllocated(na));
+ EXPECT_EQ(na.toText(), state_na->getLastAllocated().toText());
+
+ // TA and PD should be unchanged
+ EXPECT_EQ(last.toText(), state_ta->getLastAllocated().toText());
+ EXPECT_EQ(last.toText(), state_pd->getLastAllocated().toText());
+
+ // Now set TA and PD
+ EXPECT_NO_THROW(state_ta->setLastAllocated(ta));
+ EXPECT_NO_THROW(state_pd->setLastAllocated(pd));
+
+ EXPECT_EQ(na.toText(), state_na->getLastAllocated().toText());
+ EXPECT_EQ(ta.toText(), state_ta->getLastAllocated().toText());
+ EXPECT_EQ(pd.toText(), state_pd->getLastAllocated().toText());
+}
+
+// Checks if last allocated address/prefix is stored/retrieved properly when
+// multi threading is turned on.
+TEST(IterativeAllocationStateTest, subnetLastAllocated6MultiThreading) {
+ MultiThreadingTest mt(true);
+ IOAddress na("2001:db8:1::1");
+ IOAddress ta("2001:db8:1::abcd");
+ IOAddress pd("2001:db8:1::1234:5678");
+
+ IOAddress last("2001:db8:1::ffff:ffff:ffff:ffff");
+
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 64, 1, 2, 3, 4, SubnetID(1));
+ auto state_na = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_NA));
+ auto state_ta = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_TA));
+ auto state_pd = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_PD));
+
+ // Check initial conditions (all should be set to the last address in range)
+ EXPECT_EQ(last.toText(), state_na->getLastAllocated().toText());
+ EXPECT_EQ(last.toText(), state_ta->getLastAllocated().toText());
+ EXPECT_EQ(last.toText(), state_pd->getLastAllocated().toText());
+
+ // Now set last allocated for IA
+ EXPECT_NO_THROW(state_na->setLastAllocated(na));
+ EXPECT_EQ(na.toText(), state_na->getLastAllocated().toText());
+
+ // TA and PD should be unchanged
+ EXPECT_EQ(last.toText(), state_ta->getLastAllocated().toText());
+ EXPECT_EQ(last.toText(), state_pd->getLastAllocated().toText());
+
+ // Now set TA and PD
+ EXPECT_NO_THROW(state_ta->setLastAllocated(ta));
+ EXPECT_NO_THROW(state_pd->setLastAllocated(pd));
+
+ EXPECT_EQ(na.toText(), state_na->getLastAllocated().toText());
+ EXPECT_EQ(ta.toText(), state_ta->getLastAllocated().toText());
+ EXPECT_EQ(pd.toText(), state_pd->getLastAllocated().toText());
+}
+
+// Checks that last allocated IPv4 address is stored in the pool-specific
+// allocation state.
+TEST(IterativeAllocationStateTest, poolLastAllocated4) {
+ // Create a pool.
+ IOAddress first("192.0.2.0");
+ auto pool = boost::make_shared<Pool4>(first, IOAddress("192.0.2.255"));
+ auto state = PoolIterativeAllocationState::create(pool);
+
+ // Initial values are first invalid.
+ EXPECT_EQ(first.toText(), state->getLastAllocated().toText());
+ EXPECT_FALSE(state->isLastAllocatedValid());
+
+ // Now set last allocated
+ IOAddress addr("192.0.2.100");
+ EXPECT_NO_THROW(state->setLastAllocated(addr));
+ EXPECT_EQ(addr.toText(), state->getLastAllocated().toText());
+ EXPECT_TRUE(state->isLastAllocatedValid());
+
+ // Reset makes it invalid and does not touch address
+ state->resetLastAllocated();
+ EXPECT_EQ(addr.toText(), state->getLastAllocated().toText());
+ EXPECT_FALSE(state->isLastAllocatedValid());
+}
+
+// Checks that last allocated IPv6 lease is stored in the pool-specific
+// allocation state.
+TEST(IterativeAllocationStateTest, poolLastAllocated6) {
+ // Create a pool.
+ IOAddress first("2001:db8::1");
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, first, IOAddress("2001:db8::200"));
+ auto state = PoolIterativeAllocationState::create(pool);
+
+ // Initial values are first invalid.
+ EXPECT_EQ(first.toText(), state->getLastAllocated().toText());
+ EXPECT_FALSE(state->isLastAllocatedValid());
+
+ // Now set last allocated
+ IOAddress addr("2001:db8::100");
+ EXPECT_NO_THROW(state->setLastAllocated(addr));
+ EXPECT_EQ(addr.toText(), state->getLastAllocated().toText());
+ EXPECT_TRUE(state->isLastAllocatedValid());
+
+ // Reset makes it invalid and does not touch address
+ state->resetLastAllocated();
+ EXPECT_EQ(addr.toText(), state->getLastAllocated().toText());
+ EXPECT_FALSE(state->isLastAllocatedValid());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc b/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc
new file mode 100644
index 0000000..649a999
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc
@@ -0,0 +1,822 @@
+// Copyright (C) 2022-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using IterativeAllocatorTest4 = AllocEngine4Test;
+
+// Test that the allocator returns the correct type.
+TEST_F(IterativeAllocatorTest4, getType) {
+ IterativeAllocator alloc(Lease::TYPE_V4, subnet_);
+ EXPECT_EQ("iterative", alloc.getType());
+}
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(IterativeAllocatorTest4, basic) {
+ boost::scoped_ptr<Allocator> alloc(new IterativeAllocator(Lease::TYPE_V4, subnet_));
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ }
+}
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool using classification
+TEST_F(IterativeAllocatorTest4, clientClass) {
+ boost::scoped_ptr<Allocator> alloc(new IterativeAllocator(Lease::TYPE_V4, subnet_));
+
+ // Restrict pool_ to the foo class. Add a second pool with bar class.
+ pool_->allowClientClass("foo");
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.200"),
+ IOAddress("192.0.2.209")));
+ pool->allowClientClass("bar");
+ subnet_->addPool(pool);
+
+ // Clients are in bar
+ cc_.insert("bar");
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ }
+}
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space.
+TEST_F(IterativeAllocatorTest4, manyPools) {
+ IterativeAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+
+ min << "192.0.2." << i * 10 + 1;
+ max << "192.0.2." << i * 10 + 9;
+
+ Pool4Ptr pool(new Pool4(IOAddress(min.str()),
+ IOAddress(max.str())));
+ // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
+ subnet_->addPool(pool);
+ }
+
+ int total = 10 + 8 * 9; // first pool (.100 - .109) has 10 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
+
+ // Let's keep picked addresses here and check their uniqueness.
+ std::set<IOAddress> generated_addrs;
+ int cnt = 0;
+ while (++cnt) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+
+ // One way to easily verify that the iterative allocator really works is
+ // to uncomment the following line and observe its output that it
+ // covers all defined subnets.
+ // cout << candidate.toText() << endl;
+
+ if (generated_addrs.find(candidate) == generated_addrs.end()) {
+ // We haven't had this
+ generated_addrs.insert(candidate);
+ } else {
+ // We have seen this address before. That should mean that we
+ // iterated over all addresses.
+ if (generated_addrs.size() == total) {
+ // We have exactly the number of address in all pools
+ break;
+ }
+ ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+ break;
+ }
+
+ if (cnt > total) {
+ ADD_FAILURE() << "Too many unique addresses generated.";
+ break;
+ }
+ }
+}
+
+using IterativeAllocatorTest6 = AllocEngine6Test;
+
+// Test that the allocator returns the correct type.
+TEST_F(IterativeAllocatorTest6, getType) {
+ IterativeAllocator allocNA(Lease::TYPE_NA, subnet_);
+ EXPECT_EQ("iterative", allocNA.getType());
+
+ IterativeAllocator allocPD(Lease::TYPE_PD, subnet_);
+ EXPECT_EQ("iterative", allocPD.getType());
+}
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(IterativeAllocatorTest6, basic) {
+ boost::scoped_ptr<Allocator> alloc(new NakedIterativeAllocator(Lease::TYPE_NA, subnet_));
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ }
+}
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool using classification
+TEST_F(IterativeAllocatorTest6, clientClass) {
+ boost::scoped_ptr<Allocator> alloc(new NakedIterativeAllocator(Lease::TYPE_NA, subnet_));
+
+ // Restrict pool_ to the foo class. Add a second pool with bar class.
+ pool_->allowClientClass("foo");
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::109")));
+ pool->allowClientClass("bar");
+ subnet_->addPool(pool);
+
+ // Clients are in bar
+ cc_.insert("bar");
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ }
+}
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice.
+// unless it runs out of pool space.
+TEST_F(IterativeAllocatorTest6, manyPools) {
+ NakedIterativeAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+
+ min << "2001:db8:1::" << hex << i * 16 + 1;
+ max << "2001:db8:1::" << hex << i * 16 + 9;
+
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress(min.str()),
+ IOAddress(max.str())));
+ subnet_->addPool(pool);
+ }
+
+ int total = 17 + 8 * 9; // First pool (::10 - ::20) has 17 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
+
+ // Let's keep picked addresses here and check their uniqueness.
+ std::set<IOAddress> generated_addrs;
+ int cnt = 0;
+ while (++cnt) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+
+ // One way to easily verify that the iterative allocator really works is
+ // to uncomment the following line and observe its output that it
+ // covers all defined pools.
+ // cout << candidate.toText() << endl;
+
+ if (generated_addrs.find(candidate) == generated_addrs.end()) {
+ // We haven't had this.
+ generated_addrs.insert(candidate);
+ } else {
+ // We have seen this address before. That should mean that we
+ // iterated over all addresses.
+ if (generated_addrs.size() == total) {
+ // We have exactly the number of address in all pools.
+ break;
+ }
+ ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+ break;
+ }
+
+ if (cnt > total) {
+ ADD_FAILURE() << "Too many unique addresses generated.";
+ break;
+ }
+ }
+}
+
+// This test verifies that the allocator walks over the addresses in the
+// non-contiguous pools.
+TEST_F(IterativeAllocatorTest6, addrStep) {
+ subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::5")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::100")));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"),
+ IOAddress("2001:db8:1::106")));
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // Let's check the first pool (5 addresses here)
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // The second pool is easy - only one address here
+ EXPECT_EQ("2001:db8:1::100",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // This is the third and last pool, with 2 addresses in it
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // We iterated over all addresses and reached to the end of the last pool.
+ // Let's wrap around and start from the beginning
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator walks over the addresses in the
+// non-contiguous pools when pools contain class guards.
+TEST_F(IterativeAllocatorTest6, addrStepInClass) {
+ subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::5")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::100")));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"),
+ IOAddress("2001:db8:1::106")));
+ // Set pool1 and pool3 but not pool2 in foo class
+ pool1->allowClientClass("foo");
+ pool3->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // Clients are in foo
+ cc_.insert("foo");
+
+ // Let's check the first pool (5 addresses here)
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // The second pool is easy - only one address here
+ EXPECT_EQ("2001:db8:1::100",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // This is the third and last pool, with 2 addresses in it
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // We iterated over all addresses and reached to the end of the last pool.
+ // Let's wrap around and start from the beginning
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator omits pools with non-matching class guards.
+TEST_F(IterativeAllocatorTest6, addrStepOutClass) {
+ subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::5")));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"),
+ IOAddress("2001:db8:1::100")));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"),
+ IOAddress("2001:db8:1::106")));
+ // Set pool2 in foo
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // Let's check the first pool (5 addresses here)
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // The second pool is skipped
+
+ // This is the third and last pool, with 2 addresses in it
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+
+ // We iterated over all addresses and reached to the end of the last pool.
+ // Let's wrap around and start from the beginning
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(cc_, duid_, IOAddress("::")).toText());
+}
+
+// This test verifies that the allocator picks delegated prefixes from several
+// pools.
+TEST_F(IterativeAllocatorTest6, prefixStep) {
+ subnet_ = Subnet6::create(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ Pool6Ptr pool;
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // First pool check (Let's check over all 16 leases)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // Second pool (just one lease here)
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // Third pool (256 leases, let's check first and last explicitly and the
+ // rest over in a pool
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ for (int i = 1; i < 255; i++) {
+ stringstream exp;
+ exp << "2001:db8:2:" << hex << i << dec << "::";
+ EXPECT_EQ(exp.str(),
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ }
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over first pool again)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+}
+
+// This test verifies that the allocator picks delegated prefixes from several
+// pools.
+TEST_F(IterativeAllocatorTest6, prefixStepPreferLower) {
+ subnet_ = Subnet6::create(IOAddress("2001:db8::"),
+ 32, 1, 2, 3, 4, SubnetID(1));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ Pool6Ptr pool;
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // First pool check (Let's check over all 16 leases)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+
+ // Second pool (just one lease here)
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over first pool again)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 64).toText());
+}
+
+// This test verifies that the allocator picks delegated prefixes from several
+// pools.
+TEST_F(IterativeAllocatorTest6, prefixStepPreferEqual) {
+ subnet_ = Subnet6::create(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ Pool6Ptr pool;
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // Second pool (just one lease here)
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_EQUAL, IOAddress("::"), 48).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over second pool again)
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_EQUAL, IOAddress("::"), 48).toText());
+}
+
+// This test verifies that the allocator picks delegated prefixes from several
+// pools.
+TEST_F(IterativeAllocatorTest6, prefixStepPreferHigher) {
+ subnet_ = Subnet6::create(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ Pool6Ptr pool;
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // Third pool (256 leases, let's check first and last explicitly and the
+ // rest over in a pool
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 60).toText());
+ for (int i = 1; i < 255; i++) {
+ stringstream exp;
+ exp << "2001:db8:2:" << hex << i << dec << "::";
+ EXPECT_EQ(exp.str(),
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 60).toText());
+
+ }
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 60).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over third pool again)
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 60).toText());
+}
+
+// This test verifies that the allocator picks delegated prefixes from the pools
+// with class guards.
+TEST_F(IterativeAllocatorTest6, prefixStepInClass) {
+ subnet_ = Subnet6::create(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ // Set pool1 and pool3 but not pool2 in foo class
+ pool1->allowClientClass("foo");
+ pool3->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ Pool6Ptr pool;
+
+ // Clients are in foo
+ cc_.insert("foo");
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // First pool check (Let's check over all 16 leases)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // Second pool (just one lease here)
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // Third pool (256 leases, let's check first and last explicitly and the
+ // rest over in a pool
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ for (int i = 1; i < 255; i++) {
+ stringstream exp;
+ exp << "2001:db8:2:" << hex << i << dec << "::";
+ EXPECT_EQ(exp.str(),
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ }
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over first pool again)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+}
+
+// This test verifies that the allocator omits pools with non-matching client classes.
+TEST_F(IterativeAllocatorTest6, prefixStepOutClass) {
+ subnet_ = Subnet6::create(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60));
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48));
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64));
+ // Set pool2 in foo
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool1);
+ subnet_->addPool(pool2);
+ subnet_->addPool(pool3);
+
+ NakedIterativeAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ Pool6Ptr pool;
+
+ // We have a 2001:db8::/48 subnet that has 3 pools defined in it:
+ // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::)
+ // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease)
+ // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
+
+ // First pool check (Let's check over all 16 leases)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // The second pool is skipped
+
+ // Third pool (256 leases, let's check first and last explicitly and the
+ // rest over in a pool
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ for (int i = 1; i < 255; i++) {
+ stringstream exp;
+ exp << "2001:db8:2:" << hex << i << dec << "::";
+ EXPECT_EQ(exp.str(),
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ }
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+
+ // Ok, we've iterated over all prefixes in all pools. We now wrap around.
+ // We're looping over now (iterating over first pool again)
+ EXPECT_EQ("2001:db8::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0).toText());
+}
+
+// This test verifies that the iterative allocator can step over addresses.
+TEST_F(IterativeAllocatorTest6, addressIncrease) {
+ NakedIterativeAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // Let's pick the first address
+ IOAddress addr1 = alloc.pickAddress(cc_, duid_, IOAddress("2001:db8:1::10"));
+
+ // Check that we can indeed pick the first address from the pool
+ EXPECT_EQ("2001:db8:1::10", addr1.toText());
+
+ // Check that addresses can be increased properly
+ checkAddrIncrease(alloc, "2001:db8::9", "2001:db8::a");
+ checkAddrIncrease(alloc, "2001:db8::f", "2001:db8::10");
+ checkAddrIncrease(alloc, "2001:db8::10", "2001:db8::11");
+ checkAddrIncrease(alloc, "2001:db8::ff", "2001:db8::100");
+ checkAddrIncrease(alloc, "2001:db8::ffff", "2001:db8::1:0");
+ checkAddrIncrease(alloc, "::", "::1");
+ checkAddrIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::");
+}
+
+// This test verifies that the allocator can step over prefixes.
+TEST_F(IterativeAllocatorTest6, prefixIncrease) {
+ NakedIterativeAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ // For /128 prefix, increasePrefix should work the same as addressIncrease
+ checkPrefixIncrease(alloc, "2001:db8::9", 128, "2001:db8::a");
+ checkPrefixIncrease(alloc, "2001:db8::f", 128, "2001:db8::10");
+ checkPrefixIncrease(alloc, "2001:db8::10", 128, "2001:db8::11");
+ checkPrefixIncrease(alloc, "2001:db8::ff", 128, "2001:db8::100");
+ checkPrefixIncrease(alloc, "2001:db8::ffff", 128, "2001:db8::1:0");
+ checkPrefixIncrease(alloc, "::", 128, "::1");
+ checkPrefixIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, "::");
+
+ // Check that /64 prefixes can be generated
+ checkPrefixIncrease(alloc, "2001:db8::", 64, "2001:db8:0:1::");
+
+ // Check that prefix length not divisible by 8 are working
+ checkPrefixIncrease(alloc, "2001:db8::", 128, "2001:db8::1");
+ checkPrefixIncrease(alloc, "2001:db8::", 127, "2001:db8::2");
+ checkPrefixIncrease(alloc, "2001:db8::", 126, "2001:db8::4");
+ checkPrefixIncrease(alloc, "2001:db8::", 125, "2001:db8::8");
+ checkPrefixIncrease(alloc, "2001:db8::", 124, "2001:db8::10");
+ checkPrefixIncrease(alloc, "2001:db8::", 123, "2001:db8::20");
+ checkPrefixIncrease(alloc, "2001:db8::", 122, "2001:db8::40");
+ checkPrefixIncrease(alloc, "2001:db8::", 121, "2001:db8::80");
+ checkPrefixIncrease(alloc, "2001:db8::", 120, "2001:db8::100");
+
+ // These are not really useful cases, because there are bits set
+ // int the last (128 - prefix_len) bits. Nevertheless, it shows
+ // that the algorithm is working even in such cases
+ checkPrefixIncrease(alloc, "2001:db8::1", 128, "2001:db8::2");
+ checkPrefixIncrease(alloc, "2001:db8::1", 127, "2001:db8::3");
+ checkPrefixIncrease(alloc, "2001:db8::1", 126, "2001:db8::5");
+ checkPrefixIncrease(alloc, "2001:db8::1", 125, "2001:db8::9");
+ checkPrefixIncrease(alloc, "2001:db8::1", 124, "2001:db8::11");
+ checkPrefixIncrease(alloc, "2001:db8::1", 123, "2001:db8::21");
+ checkPrefixIncrease(alloc, "2001:db8::1", 122, "2001:db8::41");
+ checkPrefixIncrease(alloc, "2001:db8::1", 121, "2001:db8::81");
+ checkPrefixIncrease(alloc, "2001:db8::1", 120, "2001:db8::101");
+
+ // Let's try out couple real life scenarios
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 64, "2001:db8:1:abce::");
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 60, "2001:db8:1:abdd::");
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 56, "2001:db8:1:accd::");
+ checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 52, "2001:db8:1:bbcd::");
+
+ // And now let's try something over the top
+ checkPrefixIncrease(alloc, "::", 1, "8000::");
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc b/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc
new file mode 100644
index 0000000..5cb49a7
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/lease_file_loader_unittest.cc
@@ -0,0 +1,893 @@
+// Copyright (C) 2015-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/lease_file_loader.h>
+#include <dhcpsrv/testutils/lease_file_io.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture class for @c LeaseFileLoader class.
+class LeaseFileLoaderTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes filename used for unit tests and creates an
+ /// IO object to be used to write to this file.
+ LeaseFileLoaderTest();
+
+ /// @brief Destructor
+ ///
+ /// Removes any configuration that may have been added in CfgMgr.
+ virtual ~LeaseFileLoaderTest();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Retrieves the lease from the storage using an IP address.
+ ///
+ /// This method returns the pointer to the @c Lease4 or @c Lease6
+ /// object representing the lease in the container. This is used to
+ /// check if the lease was parsed in the lease file and added to the
+ /// container by the @c LeaseFileLoader::load method.
+ ///
+ /// @param address A string representation of the leased address.
+ /// @param storage A reference to the container in which the lease
+ /// is to be searched.
+ /// @tparam LeasePtrType Type of the returned object: @c Lease4Ptr
+ /// @c Lease6Ptr.
+ /// @tparam LeaseStorage Type of the container: @c Lease4Container
+ /// @c Lease6Container.
+ ///
+ /// @return A pointer to the lease or NULL if no lease found.
+ template<typename LeasePtrType, typename LeaseStorage>
+ static LeasePtrType getLease(const std::string& address, const LeaseStorage& storage) {
+ typedef typename LeaseStorage::template nth_index<0>::type SearchIndex;
+ // Both Lease4Storage and Lease6Storage use index 0 to retrieve the
+ // lease using an IP address.
+ const SearchIndex& idx = storage.template get<0>();
+ typename SearchIndex::iterator lease = idx.find(IOAddress(address));
+ // Lease found. Return it.
+ if (lease != idx.end()) {
+ return (*lease);
+ }
+ // No lease found.
+ return (LeasePtrType());
+ }
+
+ /// @brief Tests the write function.
+ ///
+ /// This method writes the leases from the storage container to the lease file
+ /// then compares the output to the string provided in the arguments to verify
+ /// the write was correct. The order of the leases in the output will depend
+ /// on the order in which the container provides the leases.
+ ///
+ /// @param lease_file A reference to the file to write to
+ /// @param storage A reference to the container to be written to the file
+ /// @param compare The string to compare to what was read from the file
+ ///
+ /// @tparam LeaseObjectType A @c Lease4 or @c Lease6.
+ /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
+ /// @tparam StorageType A @c Lease4Storage or @c Lease6Storage.
+ ///
+ template<typename LeaseObjectType, typename LeaseFileType,
+ typename StorageType>
+ void writeLeases(LeaseFileType& lease_file,
+ const StorageType& storage,
+ const std::string& compare) {
+ // Prepare for a new file, close and remove the old
+ lease_file.close();
+ io_.removeFile();
+
+ // Write the current leases to the file
+ LeaseFileLoader::write<LeaseObjectType, LeaseFileType, StorageType>
+ (lease_file, storage);
+
+ // Compare to see if we got what we expected.
+ EXPECT_EQ(compare, io_.readFile());
+ }
+
+ /// @brief Checks the stats for the file
+ ///
+ /// This method is passed a leasefile and the values for the statistics it
+ /// should have for comparison.
+ ///
+ /// @param lease_file A reference to the file we are using
+ /// @param reads the number of attempted reads
+ /// @param read_leases the number of valid leases read
+ /// @param read_errs the number of errors while reading leases
+ /// @param writes the number of attempted writes
+ /// @param write_leases the number of leases successfully written
+ /// @param write_errs the number of errors while writing
+ ///
+ /// @tparam LeaseFileType A @c CSVLeaseFile4 or @c CSVLeaseFile6.
+ template<typename LeaseFileType>
+ void checkStats(LeaseFileType& lease_file,
+ uint32_t reads, uint32_t read_leases,
+ uint32_t read_errs, uint32_t writes,
+ uint32_t write_leases, uint32_t write_errs) const {
+ EXPECT_EQ(reads, lease_file.getReads());
+ EXPECT_EQ(read_leases, lease_file.getReadLeases());
+ EXPECT_EQ(read_errs, lease_file.getReadErrs());
+ EXPECT_EQ(writes, lease_file.getWrites());
+ EXPECT_EQ(write_leases, lease_file.getWriteLeases());
+ EXPECT_EQ(write_errs, lease_file.getWriteErrs());
+ }
+
+ /// @brief Name of the test lease file.
+ std::string filename_;
+
+ /// @brief Object providing access to lease file IO.
+ LeaseFileIO io_;
+
+ std::string v4_hdr_; ///< String for the header of the v4 csv test file
+ std::string v6_hdr_; ///< String for the header of the v6 csv test file
+
+ Lease4Storage storage4_; ///< Storage for IPv4 leases
+ Lease6Storage storage6_; ///< Storage for IPv4 leases
+
+ /// @brief Creates IPv4 subnet with specified parameters
+ ///
+ /// @param subnet_txt subnet in textual form, e.g. 192.0.2.0/24
+ /// @param subnet_id id to be used.
+ /// @return A pointer to Subnet4 object
+ Subnet4Ptr createSubnet4(std::string subnet_txt, SubnetID id) {
+ size_t pos = subnet_txt.find("/");
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+ size_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ return (Subnet4Ptr(new Subnet4(addr, len, 1000, 2000, 3000, id)));
+ }
+
+ /// @brief Creates IPv6 subnet with specified parameters
+ ///
+ /// @param subnet_txt subnet in textual form, e.g. 2001:db8::/32
+ /// @param subnet_id id to be used.
+ /// @return A pointer to Subnet4 object
+ Subnet6Ptr createSubnet6(std::string subnet_txt, SubnetID id) {
+ size_t pos = subnet_txt.find("/");
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+ size_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ return (Subnet6Ptr(new Subnet6(addr, len, 1000, 2000, 3000, 4000, id)));
+ }
+
+ /// @brief Checks if IPv4 lease loaded from file is sanity checked.
+ ///
+ /// This method writes a simple lease file with one lease in it,
+ /// then sets sanity checks to tested level, then tries to load
+ /// the lease file and finally checks whether the lease was loaded
+ /// or not.
+ ///
+ /// @param lease address of the lease in text form
+ /// @param lease_id subnet-id to be used in a lease
+ /// @param subnet_txt Text representation of the subnet, e.g. 192.0.2.0/24
+ /// @param subnet_id Subnet-id of the subnet to be created
+ /// @param sanity level of sanity checks
+ /// @param exp_present is the lease expected to be loaded (true = yes)
+ /// @param exp_id expected subnet-id of the loaded lease
+ void sanityChecks4(std::string lease, SubnetID lease_id,
+ std::string subnet_txt, SubnetID subnet_id,
+ CfgConsistency::LeaseSanity sanity,
+ bool exp_present, SubnetID exp_id) {
+
+ // Create the subnet and add it to configuration.
+ if (!subnet_txt.empty()) {
+ Subnet4Ptr subnet = createSubnet4(subnet_txt, subnet_id);
+ ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet));
+ }
+
+ std::stringstream file_content;
+ file_content << v4_hdr_ << lease << ",dd:de:ba:0d:1b:2e,"
+ << "0a:00:01:04,100,100," << static_cast<int>(lease_id)
+ << ",0,0,,1,,0\n";
+
+ ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getConsistency()
+ ->setLeaseSanityCheck(sanity));
+
+ io_.writeFile(file_content.str());
+
+ boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Load leases from the file.
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage4_, 10));
+
+ {
+ SCOPED_TRACE("Read leases");
+ checkStats(*lf, 2, 1, 0, 0, 0, 0);
+ }
+
+ // Check how many leases were actually loaded.
+ ASSERT_EQ((exp_present ? 1 : 0), storage4_.size());
+
+ Lease4Ptr l = getLease<Lease4Ptr>(lease, storage4_);
+
+ if (exp_present) {
+ ASSERT_TRUE(l) << "lease not found, but expected";
+ EXPECT_EQ(l->subnet_id_, exp_id);
+ } else {
+ EXPECT_FALSE(l) << "lease found, but was not expected";
+ }
+ }
+
+ /// @brief Checks if IPv6 lease loaded from file is sanity checked.
+ ///
+ /// This method writes a simple lease file with one lease in it,
+ /// then sets sanity checks to tested level, then tries to load
+ /// the lease file and finally checks whether the lease was loaded
+ /// or not.
+ ///
+ /// @param lease address of the lease in text form
+ /// @param lease_id subnet-id to be used in a lease
+ /// @param subnet_txt Text representation of the subnet, e.g. 192.0.2.0/24
+ /// @param subnet_id Subnet-id of the subnet to be created
+ /// @param sanity level of sanity checks
+ /// @param exp_present is the lease expected to be loaded (true = yes)
+ /// @param exp_id expected subnet-id of the loaded lease
+ /// @param prefix_len length of the prefix if the lease created should be
+ /// a PD lease. Defaults to 0.
+ void sanityChecks6(std::string lease, SubnetID lease_id,
+ std::string subnet_txt, SubnetID subnet_id,
+ CfgConsistency::LeaseSanity sanity,
+ bool exp_present, SubnetID exp_id,
+ unsigned int prefix_len = 0) {
+
+ // Create the subnet and add it to configuration.
+ if (!subnet_txt.empty()) {
+ Subnet6Ptr subnet = createSubnet6(subnet_txt, subnet_id);
+ ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet));
+ }
+
+ std::stringstream file_content;
+
+ file_content << v6_hdr_ << lease << ",dd:de:ba:0d:1b:2e,"
+ << "300,300," << static_cast<int>(lease_id) << ",150,"
+ << (prefix_len > 0 ? Lease::TYPE_PD : Lease::TYPE_NA)
+ << ",8," << prefix_len << ",0,0,,,1,,,,0\n";
+
+ ASSERT_NO_THROW(CfgMgr::instance().getStagingCfg()->getConsistency()
+ ->setLeaseSanityCheck(sanity));
+
+ io_.writeFile(file_content.str());
+
+ boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Load leases from the file.
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage6_, 10));
+
+ {
+ SCOPED_TRACE("Read leases");
+ checkStats(*lf, 2, 1, 0, 0, 0, 0);
+ }
+
+ // Check how many leases were actually loaded.
+ ASSERT_EQ((exp_present ? 1 : 0), storage6_.size());
+
+ Lease6Ptr l = getLease<Lease6Ptr>(lease, storage6_);
+
+ if (exp_present) {
+ ASSERT_TRUE(l) << "lease not found, but expected";
+ EXPECT_EQ(exp_id, l->subnet_id_);
+ } else {
+ EXPECT_FALSE(l) << "lease found, but was not expected";
+ }
+ }
+
+protected:
+ /// @brief Sets up the header strings
+ virtual void SetUp() {
+ v4_hdr_ = "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ v6_hdr_ = "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source,pool_id\n";
+ }
+};
+
+LeaseFileLoaderTest::LeaseFileLoaderTest()
+ : filename_(absolutePath("leases.csv")), io_(filename_) {
+ CfgMgr::instance().clear();
+}
+
+LeaseFileLoaderTest::~LeaseFileLoaderTest() {
+ CfgMgr::instance().clear();
+}
+
+std::string
+LeaseFileLoaderTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << DHCP_DATA_DIR << "/" << filename;
+ return (s.str());
+}
+
+// This test verifies that the DHCPv4 leases can be loaded from the lease
+// file and that only the most recent entry for each lease is loaded and
+// the previous entries are discarded.
+//
+// It also tests the write function by writing the storage to a file
+// and comparing that with the expected value.
+TEST_F(LeaseFileLoaderTest, loadWrite4) {
+ std::string test_str;
+ std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
+ "200,200,8,1,1,host.example.com,1,"
+ "{ \"foobar\": true },0\n";
+ std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
+ "200,500,8,1,1,host.example.com,1,"
+ "{ \"foobar\": true },0\n";
+
+ std::string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
+ "100,100,7,0,0,,1,,0\n";
+ std::string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
+ "100,135,7,0,0,,1,,0\n";
+
+ std::string c_1 = "192.0.2.3,,,"
+ "200,200,8,1,1,host.example.com,0,,0\n";
+
+ // Create lease file with leases for 192.0.2.1, 192.0.3.15. The lease
+ // entry for the 192.0.2.3 is invalid (lacks HW address and client id)
+ // and should be discarded.
+ test_str = v4_hdr_ + a_1 + b_1 + c_1 + b_2 + a_2;
+ io_.writeFile(test_str);
+
+ boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Load leases from the file.
+ Lease4Storage storage;
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
+
+ // We should have made 6 attempts to read, with 4 leases read and 1 error
+ {
+ SCOPED_TRACE("Read leases");
+ checkStats(*lf, 6, 4, 1, 0, 0, 0);
+ }
+
+ // There are two unique leases.
+ ASSERT_EQ(2, storage.size());
+
+ // The lease for 192.0.2.1 should exist and the cltt should be
+ // set to the expire-valid_lifetime for the second entry for
+ // this lease, i.e. 500 - 200 = 300.
+ Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(300, lease->cltt_);
+
+ // The lease for 192.0.2.1 should have user context.
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
+
+ // The invalid entry should not be loaded.
+ lease = getLease<Lease4Ptr>("192.0.2.3", storage);
+ ASSERT_FALSE(lease);
+
+ // The other lease should be present and the cltt time should
+ // be set according to the values in the second entry for this
+ // lease.
+ lease = getLease<Lease4Ptr>("192.0.3.15", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(35, lease->cltt_);
+ EXPECT_FALSE(lease->getContext());
+
+ test_str = v4_hdr_ + a_2 + b_2;
+ writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str);
+
+ // We should have made 2 attempts to write, with 2 leases written and 0 errors
+ {
+ SCOPED_TRACE("Write leases");
+ checkStats(*lf, 0, 0, 0, 2, 2, 0);
+ }
+}
+
+// This test verifies that the lease with a valid lifetime of 0
+// is removed from the storage. The valid lifetime of 0 is set
+// for the released leases.
+//
+// It also tests the write function by writing the storage to a file
+// and comparing that with the expected value.
+TEST_F(LeaseFileLoaderTest, loadWrite4LeaseRemove) {
+ std::string test_str;
+ std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,"
+ "200,200,8,1,1,host.example.com,1,,0\n";
+ std::string a_2 = "192.0.2.1,06:07:08:09:0a:bc,,"
+ "0,500,8,1,1,host.example.com,1,,0\n";
+
+ std::string b_1 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
+ "100,100,7,0,0,,1,,0\n";
+ std::string b_2 = "192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,"
+ "100,135,7,0,0,,1,,0\n";
+
+ // Create lease file in which one of the entries for 192.0.2.1
+ // has a valid_lifetime of 0 and results in the deletion of the
+ // lease.
+ test_str = v4_hdr_ + a_1 + b_1 + b_2 + a_2;
+ io_.writeFile(test_str);
+
+ boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ Lease4Storage storage;
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 10));
+
+ // We should have made 5 attempts to read, with 4 leases read and 0 error
+ {
+ SCOPED_TRACE("Read leases");
+ checkStats(*lf, 5, 4, 0, 0, 0, 0);
+ }
+
+ // There should only be one lease. The one with the valid_lifetime
+ // of 0 should be removed.
+ ASSERT_EQ(1, storage.size());
+
+ Lease4Ptr lease = getLease<Lease4Ptr>("192.0.3.15", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(35, lease->cltt_);
+
+ test_str = v4_hdr_ + b_2;
+ writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str);
+
+ // We should have made 1 attempts to write, with 1 leases written and 0 errors
+ {
+ SCOPED_TRACE("Write leases");
+ checkStats(*lf, 0, 0, 0, 1, 1, 0);
+ }
+}
+
+// This test verifies that max-row-errors works correctly for
+// DHCPv4 lease files
+TEST_F(LeaseFileLoaderTest, maxRowErrors4) {
+ // We have 9 rows: 2 that are good, 7 that are flawed (too few fields).
+ std::vector<std::string> rows = {
+ "192.0.2.100,08:00:27:25:d3:f4,31:31:31:31,3600,1565356064,1,0,0,,0,,0\n",
+ "192.0.2.101,FF:FF:FF:FF:FF:01,32:32:32:31,3600,1565356073,1,0,0\n",
+ "192.0.2.102,FF:FF:FF:FF:FF:02,32:32:32:32,3600,1565356073,1,0,0\n",
+ "192.0.2.103,FF:FF:FF:FF:FF:03,32:32:32:33,3600,1565356073,1,0,0\n",
+ "192.0.2.104,FF:FF:FF:FF:FF:04,32:32:32:34,3600,1565356073,1,0,0\n",
+ "192.0.2.105,FF:FF:FF:FF:FF:05,32:32:32:35,3600,1565356073,1,0,0\n",
+ "192.0.2.106,FF:FF:FF:FF:FF:06,32:32:32:36,3600,1565356073,1,0,0\n",
+ "192.0.2.107,FF:FF:FF:FF:FF:07,32:32:32:37,3600,1565356073,1,0,0\n",
+ "192.0.2.108,08:00:27:25:d3:f4,32:32:32:32,3600,1565356073,1,0,0,,0,,0\n"
+ };
+
+ std::ostringstream os;
+ os << v4_hdr_;
+ for (auto row : rows) {
+ os << row;
+ }
+
+ io_.writeFile(os.str());
+
+ boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Let's limit the number of errors to 5 (we have 7 in the data) and
+ // try to load the leases.
+ uint32_t max_errors = 5;
+ Lease4Storage storage;
+ ASSERT_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, max_errors), util::CSVFileError);
+
+ // We should have made 7 reads, with 1 lease read, and 6 errors.
+ {
+ SCOPED_TRACE("Failed load stats");
+ checkStats(*lf, 7, 1, 6, 0, 0, 0);
+ }
+
+ // Now let's disable the error limit and try again.
+ max_errors = 0;
+
+ // Load leases from the file. Note, we have to reopen the file.
+ ASSERT_NO_THROW(lf->open());
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, max_errors));
+
+ // We should have made 10 reads, with 2 leases read, and 7 errors.
+ {
+ SCOPED_TRACE("Good load stats");
+ checkStats(*lf, 10, 2, 7, 0, 0, 0);
+ }
+}
+
+// This test verifies that the DHCPv6 leases can be loaded from the lease
+// file and that only the most recent entry for each lease is loaded and
+// the previous entries are discarded.
+//
+// It also tests the write function by writing the storage to a file
+// and comparing that with the expected value.
+TEST_F(LeaseFileLoaderTest, loadWrite6) {
+ std::string test_str;
+ std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "200,200,8,100,0,7,0,1,1,host.example.com,,1,"
+ "{ \"foobar\": true },,,0\n";
+ std::string a_2 = "2001:db8:1::1,,"
+ "200,200,8,100,0,7,0,1,1,host.example.com,,1,"
+ "{ \"foobar\": true },,,0\n";
+ std::string a_3 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "200,400,8,100,0,7,0,1,1,host.example.com,,1,"
+ "{ \"foobar\": true },,,0\n";
+
+ std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+ "300,300,6,150,0,8,0,0,0,,,1,,,,0\n";
+ std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+ "300,800,6,150,0,8,0,0,0,,,1,,,,0\n";
+
+ std::string c_1 = "3000:1::,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "100,200,8,0,2,16,64,0,0,,,1,,,,0\n";
+
+ // new files have 128 prefixlen for non PD type
+ std::string a_3_n = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "200,400,8,100,0,7,128,1,1,host.example.com,,1,"
+ "{ \"foobar\": true },,,0\n";
+
+ std::string b_2_n = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+ "300,800,6,150,0,8,128,0,0,,,1,,,,0\n";
+
+ // Create a lease file with three valid leases: 2001:db8:1::1,
+ // 3000:1:: and 2001:db8:2::10.
+ test_str = v6_hdr_ + a_1 + a_2 + b_1 + c_1 + b_2 + a_3;
+ io_.writeFile(test_str);
+
+ boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Load leases from the lease file.
+ Lease6Storage storage;
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
+
+ // We should have made 7 attempts to read, with 5 leases read and 1 error
+ {
+ SCOPED_TRACE("Read leases");
+ checkStats(*lf, 7, 5, 1, 0, 0, 0);
+ }
+
+ // There should be 3 unique leases.
+ ASSERT_EQ(3, storage.size());
+
+ // The 2001:db8:1::1 should be present and its cltt should be
+ // calculated according to the expiration time and the valid
+ // lifetime from the last entry for this lease: 400 - 200 = 200.
+ Lease6Ptr lease = getLease<Lease6Ptr>("2001:db8:1::1", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(200, lease->cltt_);
+
+ // The 2001:db8:1::1 should have user context.
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": true }", lease->getContext()->str());
+
+ // The 3000:1:: lease should be present.
+ lease = getLease<Lease6Ptr>("3000:1::", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(100, lease->cltt_);
+ EXPECT_FALSE(lease->getContext());
+
+ // The 2001:db8:2::10 should be present and the cltt should be
+ // calculated according to the last entry in the lease file.
+ lease = getLease<Lease6Ptr>("2001:db8:2::10", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(500, lease->cltt_);
+ EXPECT_FALSE(lease->getContext());
+
+ test_str = v6_hdr_ + a_3_n + b_2_n + c_1;
+ writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>(*lf, storage, test_str);
+
+ // We should have made 3 attempts to write, with 3 leases written and 0 errors
+ {
+ SCOPED_TRACE("Write leases");
+ checkStats(*lf, 0, 0, 0, 3, 3, 0);
+ }
+}
+
+// This test verifies that the lease with a valid lifetime of 0
+// is removed from the storage. The valid lifetime of 0 set set
+// for the released leases.
+//
+// It also tests the write function by writing the storage to a file
+// and comparing that with the expected value.
+TEST_F(LeaseFileLoaderTest, loadWrite6LeaseRemove) {
+ std::string test_str;
+ std::string a_1 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "200,200,8,100,0,7,0,1,1,host.example.com,,1,,,,0\n";
+ std::string a_2 = "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,"
+ "0,400,8,100,0,7,0,1,1,host.example.com,,1,,,,0\n";
+
+ std::string b_1 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+ "300,300,6,150,0,8,0,0,0,,,1,,,,0\n";
+
+ std::string b_2 = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+ "300,800,6,150,0,8,0,0,0,,,1,,,,0\n";
+
+ // new files have 128 prefixlen for non PD type
+ std::string b_2_n = "2001:db8:2::10,01:01:01:01:0a:01:02:03:04:05,"
+ "300,800,6,150,0,8,128,0,0,,,1,,,,0\n";
+
+ // Create lease file in which one of the entries for the 2001:db8:1::1
+ // has valid lifetime set to 0, in which case the lease should be
+ // deleted.
+ test_str = v6_hdr_ + a_1 + b_1 + b_2 + a_2;
+ io_.writeFile(test_str);
+
+ boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Loaded leases.
+ Lease6Storage storage;
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, 10));
+
+ // We should have made 5 attempts to read, with 4 leases read and 0 error
+ {
+ SCOPED_TRACE("Read leases");
+ checkStats(*lf, 5, 4, 0, 0, 0, 0);
+ }
+
+ // There should be only one lease for 2001:db8:2::10. The other one
+ // should have been deleted (or rather not loaded).
+ ASSERT_EQ(1, storage.size());
+
+ Lease6Ptr lease = getLease<Lease6Ptr>("2001:db8:2::10", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(500, lease->cltt_);
+
+ test_str = v6_hdr_ + b_2_n;
+ writeLeases<Lease6, CSVLeaseFile6, Lease6Storage>(*lf, storage, test_str);
+
+ // We should have made 1 attempts to write, with 1 leases written and 0 errors
+ {
+ SCOPED_TRACE("Write leases");
+ checkStats(*lf, 0, 0, 0, 1, 1, 0);
+ }
+}
+
+// This test verifies that max-row-errors works correctly for
+// DHCPv6 lease files
+TEST_F(LeaseFileLoaderTest, maxRowErrors6) {
+ // We have 9 rows: 2 that are good, 7 that are flawed (too few fields).
+ std::vector<std::string> rows = {
+ "3002::01,00:03:00:01:08:00:27:25:d3:01,30,1565361388,2,20,0,"
+ "11189196,128,0,0,,08:00:27:25:d3:f4,0,,,,0\n",
+ "3002::02,00:03:00:01:08:00:27:25:d3:02,30,1565361388,2,20,0\n",
+ "3002::03,00:03:00:01:08:00:27:25:d3:03,30,1565361388,2,20,0\n",
+ "3002::04,00:03:00:01:08:00:27:25:d3:04,30,1565361388,2,20,0\n",
+ "3002::05,00:03:00:01:08:00:27:25:d3:05,30,1565361388,2,20,0\n",
+ "3002::06,00:03:00:01:08:00:27:25:d3:06,30,1565361388,2,20,0\n",
+ "3002::07,00:03:00:01:08:00:27:25:d3:07,30,1565361388,2,20,0\n",
+ "3002::08,00:03:00:01:08:00:27:25:d3:08,30,1565361388,2,20,0\n",
+ "3002::09,00:03:00:01:08:00:27:25:d3:09,30,1565361388,2,20,0,"
+ "11189196,128,0,0,,08:00:27:25:d3:f4,0,,,,0\n"
+ };
+
+ std::ostringstream os;
+ os << v6_hdr_;
+ for (auto row : rows) {
+ os << row;
+ }
+
+ io_.writeFile(os.str());
+
+ boost::scoped_ptr<CSVLeaseFile6> lf(new CSVLeaseFile6(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Let's limit the number of errors to 5 (we have 7 in the data) and
+ // try to load the leases.
+ uint32_t max_errors = 5;
+ Lease6Storage storage;
+ ASSERT_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, max_errors), util::CSVFileError);
+
+ // We should have made 7 reads, with 1 lease read, and 6 errors.
+ {
+ SCOPED_TRACE("Failed load stats");
+ checkStats(*lf, 7, 1, 6, 0, 0, 0);
+ }
+
+ // Now let's disable the error limit and try again.
+ max_errors = 0;
+
+ // Load leases from the file. Note, we have to reopen the file.
+ ASSERT_NO_THROW(lf->open());
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease6>(*lf, storage, max_errors));
+
+ // We should have made 10 reads, with 2 leases read, and 7 errors.
+ {
+ SCOPED_TRACE("Good load stats");
+ checkStats(*lf, 10, 2, 7, 0, 0, 0);
+ }
+}
+
+// This test verifies that the lease with a valid lifetime set to 0 is
+// not loaded if there are no previous entries for this lease in the
+// lease file.
+//
+// It also tests the write function by writing the storage to a file
+// and comparing that with the expected value.
+TEST_F(LeaseFileLoaderTest, loadWriteLeaseWithZeroLifetime) {
+ std::string test_str;
+ std::string a_1 = "192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,,1,,0\n";
+ std::string b_2 = "192.0.2.3,06:07:08:09:0a:bd,,0,200,8,1,1,,1,,0\n";
+
+ // Create lease file. The second lease has a valid lifetime of 0.
+ test_str = v4_hdr_ + a_1 + b_2;
+ io_.writeFile(test_str);
+
+ boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+ ASSERT_NO_THROW(lf->open());
+
+ // Set the error count to 0 to make sure that lease with a zero
+ // lifetime doesn't cause an error.
+ Lease4Storage storage;
+ ASSERT_NO_THROW(LeaseFileLoader::load<Lease4>(*lf, storage, 0));
+
+ // We should have made 3 attempts to read, with 2 leases read and 0 error
+ {
+ SCOPED_TRACE("Read leases");
+ checkStats(*lf, 3, 2, 0, 0, 0, 0);
+ }
+
+ // The first lease should be present.
+ Lease4Ptr lease = getLease<Lease4Ptr>("192.0.2.1", storage);
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // The lease with a valid lifetime of 0 should not be loaded.
+ EXPECT_FALSE(getLease<Lease4Ptr>("192.0.2.3", storage));
+
+ test_str = v4_hdr_ + a_1;
+ writeLeases<Lease4, CSVLeaseFile4, Lease4Storage>(*lf, storage, test_str);
+
+ // We should have made 1 attempts to write, with 1 leases written and 0 errors
+ {
+ SCOPED_TRACE("Write leases");
+ checkStats(*lf, 0, 0, 0, 1, 1, 0);
+ }
+}
+
+// This test checks if the lease can be loaded, even though there are no
+// subnets configured that it would match.
+// Scenario: print a warning, there's no subnet,
+// expected outcome: add the lease as is
+TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsWarn) {
+ sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_WARN, true, 1);
+}
+
+// This test checks if the lease can be fixed.
+// Scenario: try to fix the lease, there's no subnet,
+// expected outcome: add the lease as is
+TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsFix) {
+ sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX, true, 1);
+}
+
+// This test checks if the lease can be discarded if it's impossible to fix.
+// Scenario: try to fix the lease, there's no subnet,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsFixDel) {
+ sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1);
+}
+
+// This test checks if the lease can be discarded.
+// Scenario: try to fix the lease, there's no subnet,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker4NoSubnetsDel) {
+ sanityChecks4("192.0.2.1", 1, "", 0, CfgConsistency::LEASE_CHECK_DEL, false, 1);
+}
+
+// This test checks if the lease can be fixed.
+// Scenario: try to fix the lease, there's a subnet,
+// expected outcome: correct the lease
+TEST_F(LeaseFileLoaderTest, sanityChecker4Fix) {
+ sanityChecks4("192.0.2.1", 1, "192.0.2.0/24", 2, CfgConsistency::LEASE_CHECK_FIX, true, 2);
+}
+
+// This test checks if the lease can be fixed when it's possible.
+// Scenario: try to fix the lease, there's a subnet,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker4FixDel1) {
+ sanityChecks4("192.0.2.1", 1, "192.0.2.0/24", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, true, 2);
+}
+
+// This test checks if the lease is discarded, when fix is not possible.
+// Scenario: try to fix the lease, there's a subnet, but it doesn't match,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker4FixDel2) {
+ sanityChecks4("192.0.2.1", 1, "192.0.3.0/24", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1);
+}
+
+// This test checks if the lease is discarded.
+// Scenario: delete the lease, there's a subnet,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker4Del) {
+ sanityChecks4("192.0.2.1", 1, "192.0.2.0/24", 2, CfgConsistency::LEASE_CHECK_DEL, false, 1);
+}
+
+// This test checks if the lease can be loaded, even though there are no
+// subnets configured that it would match.
+TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsWarn) {
+ sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_WARN, true, 1);
+}
+
+// This test checks if the lease can be fixed.
+// Scenario: try to fix the lease, there's no subnet,
+// expected outcome: add the lease as is
+TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsFix) {
+ sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX, true, 1);
+}
+
+// This test checks if the lease can be discarded if it's impossible to fix.
+// Scenario: try to fix the lease, there's no subnet,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsFixDel) {
+ sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1);
+}
+
+// This test checks if the lease can be discarded. Note we are
+// verifying whether the checks are conducted at all when loading a file.
+// Thorough tests were conducted in sanity_checks_unittest.cc.
+TEST_F(LeaseFileLoaderTest, sanityChecker6NoSubnetsDel) {
+ sanityChecks6("2001::1", 1, "", 0, CfgConsistency::LEASE_CHECK_DEL, false, 1);
+}
+
+// This test checks if the lease can be fixed.
+// Scenario: try to fix the lease, there's a subnet,
+// expected outcome: correct the lease
+TEST_F(LeaseFileLoaderTest, sanityChecker6Fix) {
+ sanityChecks6("2001::1", 1, "2001::/16", 2, CfgConsistency::LEASE_CHECK_FIX, true, 2);
+}
+
+// This test checks if the lease can be fixed when it's possible.
+// Scenario: try to fix the lease, there's a subnet,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker6FixDel1) {
+ sanityChecks6("2001::1", 1, "2001::/16", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, true, 2);
+}
+
+// This test checks if the lease is discarded, when fix is not possible.
+// Scenario: try to fix the lease, there's a subnet, but it doesn't match,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker6FixDel2) {
+ sanityChecks6("2001::1", 1, "2002::/16", 2, CfgConsistency::LEASE_CHECK_FIX_DEL, false, 1);
+}
+
+// This test checks if the lease is discarded.
+// Scenario: delete the lease, there's a subnet,
+// expected outcome: the lease is not loaded
+TEST_F(LeaseFileLoaderTest, sanityChecker6Del) {
+ sanityChecks6("2001::1", 1, "2001::/16", 2, CfgConsistency::LEASE_CHECK_DEL, false, 1);
+}
+
+// This test checks to make sure PD leases are not sanity checked,
+// and thus not discarded.
+TEST_F(LeaseFileLoaderTest, sanityChecker6PD) {
+ int prefix_len = 64;
+
+ // We check a prefix lease whose subnet-id does not exist and
+ // is clearly outside the only known subnet.
+ sanityChecks6("2001:1::", 2, // create prefix lease in subnet 2
+ "3001::/64", 1, // create subnet 1
+ CfgConsistency::LEASE_CHECK_DEL,
+ true, 2, // lease should still exist with subnet id of 2
+ prefix_len);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
new file mode 100644
index 0000000..5e8b999
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2012-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 <asiolink/io_address.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc::dhcp;
+
+// This set of tests only check the parsing functions of LeaseMgrFactory.
+// Tests of the LeaseMgr create/instance/destroy are implicitly carried out
+// in the tests for the different concrete lease managers (e.g. MySqlLeaseMgr).
+
+// Currently there are no unit-tests as the sole testable method (parse)
+// was moved to its own class (DataSource). All existing unit-tests were
+// moved there.
+
+namespace {
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
new file mode 100644
index 0000000..54c9332
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -0,0 +1,1038 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <dhcpsrv/testutils/concrete_lease_mgr.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <testutils/gtest_utils.h>
+
+#include <iostream>
+#include <list>
+#include <sstream>
+
+#include <time.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+class LeaseMgrTest : public GenericLeaseMgrTest {
+public:
+ LeaseMgrTest() {
+ }
+
+ /// @brief Reopen the database
+ ///
+ /// No-op implementation. We need to provide concrete implementation,
+ /// as this is a pure virtual method in GenericLeaseMgrTest.
+ virtual void reopen(Universe) {
+ }
+
+};
+
+namespace {
+
+// This test checks if getLease6() method is working properly for 0 (NULL),
+// 1 (return the lease) and more than 1 leases (throw).
+TEST_F(LeaseMgrTest, getLease6) {
+
+ DatabaseConnection::ParameterMap pmap;
+ boost::scoped_ptr<ConcreteLeaseMgr> mgr(new ConcreteLeaseMgr(pmap));
+
+ vector<Lease6Ptr> leases = createLeases6();
+
+ mgr->leases6_.clear();
+ // For no leases, the function should return NULL pointer
+ Lease6Ptr lease;
+
+ // the getLease6() is calling getLeases6(), which is a dummy. It returns
+ // whatever is there in leases6_ field.
+ EXPECT_NO_THROW(lease = mgr->getLease6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_,
+ leases[1]->subnet_id_));
+ EXPECT_FALSE(lease);
+
+ // For a single lease, the function should return that lease
+ mgr->leases6_.push_back(leases[1]);
+ EXPECT_NO_THROW(lease = mgr->getLease6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_,
+ leases[1]->subnet_id_));
+ EXPECT_TRUE(lease);
+
+ EXPECT_NO_THROW(detailCompareLease(lease, leases[1]));
+
+ // Add one more lease. There are 2 now. It should throw
+ mgr->leases6_.push_back(leases[2]);
+
+ EXPECT_THROW(lease = mgr->getLease6(leasetype6_[1], *leases[1]->duid_,
+ leases[1]->iaid_,
+ leases[1]->subnet_id_),
+ MultipleRecords);
+}
+
+// Verify LeaseStatsQuery default construction
+TEST (LeaseStatsQueryTest, defaultCtor) {
+ LeaseStatsQueryPtr qry;
+
+ // Valid construction, verifiy member values.
+ ASSERT_NO_THROW(qry.reset(new LeaseStatsQuery()));
+ ASSERT_EQ(0, qry->getFirstSubnetID());
+ ASSERT_EQ(0, qry->getLastSubnetID());
+ ASSERT_EQ(LeaseStatsQuery::ALL_SUBNETS, qry->getSelectMode());
+}
+
+// Verify LeaseStatsQuery single-subnet construction
+TEST (LeaseStatsQueryTest, singleSubnetCtor) {
+ LeaseStatsQueryPtr qry;
+
+ // Invalid values for subnet_id
+ ASSERT_THROW(qry.reset(new LeaseStatsQuery(0)), BadValue);
+
+ // Valid values should work and set mode accordingly.
+ ASSERT_NO_THROW(qry.reset(new LeaseStatsQuery(77)));
+ ASSERT_EQ(77, qry->getFirstSubnetID());
+ ASSERT_EQ(0, qry->getLastSubnetID());
+ ASSERT_EQ(LeaseStatsQuery::SINGLE_SUBNET, qry->getSelectMode());
+}
+
+// Verify LeaseStatsQuery subnet-range construction
+TEST (LeaseStatsQueryTest, subnetRangeCtor) {
+ LeaseStatsQueryPtr qry;
+
+ // Either ID set to 0, or a backward range should throw
+ ASSERT_THROW(qry.reset(new LeaseStatsQuery(0,1)), BadValue);
+ ASSERT_THROW(qry.reset(new LeaseStatsQuery(1,0)), BadValue);
+ ASSERT_THROW(qry.reset(new LeaseStatsQuery(2,1)), BadValue);
+
+ // Valid values should work and set mode accordingly.
+ ASSERT_NO_THROW(qry.reset(new LeaseStatsQuery(1,2)));
+ ASSERT_EQ(1, qry->getFirstSubnetID());
+ ASSERT_EQ(2, qry->getLastSubnetID());
+ ASSERT_EQ(LeaseStatsQuery::SUBNET_RANGE, qry->getSelectMode());
+}
+
+// Verify Lease4 user context upgrade basic operations.
+TEST(Lease4ExtendedInfoTest, basic) {
+ Lease4Ptr lease;
+
+ // No Lease.
+ ASSERT_FALSE(lease);
+ EXPECT_FALSE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+
+ // No user context.
+ lease.reset(new Lease4());
+ ASSERT_TRUE(lease);
+ ASSERT_FALSE(lease->getContext());
+ EXPECT_FALSE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+
+ // Not map user context.
+ ElementPtr user_context = Element::createList();
+ lease->setContext(user_context);
+ EXPECT_TRUE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+ EXPECT_FALSE(lease->getContext());
+
+ // No ISC.
+ user_context = Element::createMap();
+ user_context->set("foo", Element::create(string("bar")));
+ lease->setContext(user_context);
+ EXPECT_FALSE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+
+ // Not a map ISC.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ ElementPtr isc = Element::create(string("..."));
+ user_context->set("ISC", isc);
+ EXPECT_TRUE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+ EXPECT_FALSE(lease->getContext());
+
+ // No relay agent info.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ isc->set("foo", Element::create(string("bar")));
+ EXPECT_FALSE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+
+ // Not string or map relay agent info.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ ElementPtr rai = Element::createMap();
+ rai->set("foo", Element::create(string("bar")));
+ isc->set("relay-agent-info", rai);
+ EXPECT_FALSE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+
+ // Positive case.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ rai = Element::create(string("0xAA01BB"));
+ isc->set("relay-agent-info", rai);
+ EXPECT_TRUE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+
+ ConstElementPtr new_user_context = lease->getContext();
+ ASSERT_TRUE(new_user_context);
+ ConstElementPtr new_isc = new_user_context->get("ISC");
+ ASSERT_TRUE(new_isc);
+ ConstElementPtr new_rai = new_isc->get("relay-agent-info");
+ ASSERT_TRUE(new_rai);
+ ASSERT_EQ(Element::map, new_rai->getType());
+ ASSERT_EQ("{ \"sub-options\": \"0xAA01BB\" }", new_rai->str());
+}
+
+// Verify Lease4 user context upgrade complex operations.
+TEST(Lease4ExtendedInfoTest, upgradeLease4ExtendedInfo) {
+ // Structure that defines a test scenario.
+ struct Scenario {
+ string description_; // test description.
+ string orig_; // original user context.
+ string expected_; // expected user context.
+ };
+
+ // Test scenarios.
+ vector<Scenario> scenarios {
+ {
+ "no user context",
+ "",
+ ""
+ },
+ {
+ "user context is not a map",
+ "[ ]",
+ ""
+ },
+ {
+ "no ISC entry",
+ "{ }",
+ ""
+ },
+ {
+ "no ISC entry but not empty",
+ "{ \"foo\": true }",
+ "{ \"foo\": true }"
+ },
+ {
+ "ISC entry is not a map",
+ "{ \"ISC\": true }",
+ ""
+ },
+ {
+ "ISC entry is not a map, user context not empty",
+ "{ \"foo\": true, \"ISC\": true }",
+ "{ \"foo\": true }"
+ },
+ {
+ "no relay agent info",
+ "{ \"ISC\": { } }",
+ ""
+ },
+ {
+ "no relay agent info, ISC not empty",
+ "{ \"ISC\": { \"foo\": true } }",
+ "{ \"ISC\": { \"foo\": true } }"
+ },
+ {
+ "relay agent info is not a string or a map",
+ "{ \"ISC\": { \"relay-agent-info\": false } }",
+ ""
+ },
+ {
+ "relay agent info is not a string or a map, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relay-agent-info\": false } }",
+ "{ \"ISC\": { \"foo\": true } }"
+ },
+ {
+ "relay agent info has a junk value",
+ "{ \"ISC\": { \"relay-agent-info\": \"foobar\" } }",
+ ""
+ },
+ {
+ "relay agent info has a junk value, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relay-agent-info\": \"foobar\" } }",
+ "{ \"ISC\": { \"foo\": true } }"
+ },
+ {
+ "relay agent info has a rai without ids",
+ "{ \"ISC\": { \"relay-agent-info\": \"0x0104AABBCCDD\" } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }"
+ },
+ {
+ "relay agent info with other entries",
+ "{ \"foo\": 123, \"ISC\": { \"bar\": 456,"
+ " \"relay-agent-info\": \"0x0104AABBCCDD\" } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" }, \"bar\": 456 }, \"foo\": 123 }"
+ },
+ {
+ "relay agent info has a rai with ids",
+ "{ \"ISC\": { \"relay-agent-info\": \"0x02030102030C03AABBCC\" } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\","
+ " \"relay-id\": \"AABBCC\" } } }"
+ }
+ };
+
+ Lease4Ptr lease(new Lease4());
+ ElementPtr orig_context;
+ ElementPtr exp_context;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ // Create the original user context from JSON.
+ if (scenario.orig_.empty()) {
+ orig_context.reset();
+ } else {
+ ASSERT_NO_THROW(orig_context = Element::fromJSON(scenario.orig_))
+ << "invalid original context, test " << scenario.description_
+ << " is broken";
+ }
+
+ // Create the expected user context from JSON.
+ if (scenario.expected_.empty()) {
+ exp_context.reset();
+ } else {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.expected_))
+ << "invalid expected context, test is broken";
+ }
+
+ // Perform the test.
+ lease->setContext(orig_context);
+ ConstElementPtr before;
+ if (orig_context) {
+ before = isc::data::copy(orig_context);
+ }
+ bool ret = LeaseMgr::upgradeLease4ExtendedInfo(lease);
+ ConstElementPtr after = lease->getContext();
+ if (!before && !after) {
+ EXPECT_FALSE(ret) << "null before and null after";
+ } else if ((before && !after) || (!before && after)) {
+ EXPECT_TRUE(ret) << "only one of before and after is null";
+ } else if (before->equals(*after)) {
+ EXPECT_FALSE(ret) << "before == after";
+ } else {
+ EXPECT_TRUE(ret) << "before != after";
+ }
+ if (!exp_context) {
+ EXPECT_FALSE(after) << "expected null, got " << *after;
+ } else {
+ ASSERT_TRUE(after) << "expected not null, got null";
+ EXPECT_TRUE(exp_context->equals(*after))
+ << "expected: " << *exp_context << std::endl
+ << "actual: " << *after << std::endl;
+ }
+ }
+}
+
+// Verify Lease4 user context extract basic operations.
+TEST(Lease4ExtendedInfoTest, extract) {
+ Lease4Ptr lease;
+
+ // No Lease.
+ ASSERT_FALSE(lease);
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease, false));
+
+ // No user context.
+ lease.reset(new Lease4());
+ ASSERT_TRUE(lease);
+ ASSERT_FALSE(lease->getContext());
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease, false));
+
+ // Not map user context.
+ ElementPtr user_context = Element::createList();
+ lease->setContext(user_context);
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+
+ // No ISC.
+ user_context = Element::createMap();
+ user_context->set("foo", Element::create(string("bar")));
+ lease->setContext(user_context);
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease, false));
+
+ // Not a map ISC.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ ElementPtr isc = Element::create(string("..."));
+ user_context->set("ISC", isc);
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+
+ // No relay agent info.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ isc->set("foo", Element::create(string("bar")));
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease, false));
+
+ // Not a map relay agent info.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ ElementPtr rai = Element::createMap();
+ rai->set("foo", Element::create(string("bar")));
+ isc->set("relay-agent-info", rai);
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+
+ // No upgraded.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ rai = Element::create(string("0x02030102030C03AABBCC"));
+ isc->set("relay-agent-info", rai);
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+
+ // Upgraded.
+ EXPECT_TRUE(LeaseMgr::upgradeLease4ExtendedInfo(lease));
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease, false));
+ const vector<uint8_t> relay_id = { 0xaa, 0xbb, 0xcc };
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ const vector<uint8_t> remote_id = { 1, 2, 3 };
+ EXPECT_EQ(remote_id, lease->remote_id_);
+}
+
+// Verify Lease4 user context extract complex operations.
+TEST(Lease4ExtendedInfoTest, extractLease4ExtendedInfo) {
+ struct Scenario {
+ string description_; // test description.
+ string context_; // user context.
+ string msg_; // error message.
+ };
+
+ // Test scenarios.
+ vector<Scenario> scenarios {
+ {
+ "no user context",
+ "",
+ ""
+ },
+ {
+ "user context is not a map",
+ "[ ]",
+ "user context is not a map"
+ },
+ {
+ "no ISC entry",
+ "{ }",
+ ""
+ },
+ {
+ "no ISC entry but not empty",
+ "{ \"foo\": true }",
+ ""
+ },
+ {
+ "ISC entry is not a map",
+ "{ \"ISC\": true }",
+ "ISC entry is not a map"
+ },
+ {
+ "ISC entry is not a map, user context not empty",
+ "{ \"foo\": true, \"ISC\": true }",
+ "ISC entry is not a map"
+ },
+ {
+ "no relay agent info",
+ "{ \"ISC\": { } }",
+ ""
+ },
+ {
+ "no relay agent info, ISC not empty",
+ "{ \"ISC\": { \"foo\": true } }",
+ ""
+ },
+ {
+ "relay agent info is not a string or a map",
+ "{ \"ISC\": { \"relay-agent-info\": false } }",
+ "relay-agent-info is not a map"
+ },
+ {
+ "relay agent info is not a string or a map, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relay-agent-info\": false } }",
+ "relay-agent-info is not a map"
+ },
+ {
+ "relay agent info has a junk value",
+ "{ \"ISC\": { \"relay-agent-info\": \"foobar\" } }",
+ "relay-agent-info is not a map"
+ },
+ {
+ "relay agent info has a junk value, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relay-agent-info\": \"foobar\" } }",
+ "relay-agent-info is not a map"
+ },
+ {
+ "relay agent info has a rai without ids",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ ""
+ },
+ {
+ "relay agent info with other entries",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" }, \"bar\": 456 }, \"foo\": 123 }",
+ ""
+ },
+ {
+ "relay agent info has a rai with ids",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\","
+ " \"relay-id\": \"AABBCC\" } } }",
+ ""
+ }
+ };
+
+ Lease4Ptr lease(new Lease4());
+ ElementPtr user_context;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ // Create the original user context from JSON.
+ if (scenario.context_.empty()) {
+ user_context.reset();
+ } else {
+ ASSERT_NO_THROW(user_context = Element::fromJSON(scenario.context_))
+ << "invalid user context, test " << scenario.description_
+ << " is broken";
+ }
+
+ // Perform the test.
+ lease->setContext(user_context);
+ if (scenario.msg_.empty()) {
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease, false));
+ } else {
+ EXPECT_NO_THROW(LeaseMgr::extractLease4ExtendedInfo(lease));
+ EXPECT_THROW_MSG(LeaseMgr::extractLease4ExtendedInfo(lease, false),
+ BadValue, scenario.msg_);
+ }
+ }
+ // Last scenario sets relay and remote ids.
+ const vector<uint8_t> relay_id = { 0xaa, 0xbb, 0xcc };
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ const vector<uint8_t> remote_id = { 1, 2, 3 };
+ EXPECT_EQ(remote_id, lease->remote_id_);
+}
+
+// Verify Lease6 user context upgrade basic operations.
+TEST(Lease6ExtendedInfoTest, basic) {
+ Lease6Ptr lease;
+
+ // No Lease.
+ ASSERT_FALSE(lease);
+ EXPECT_FALSE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+
+ // No user context.
+ lease.reset(new Lease6());
+ ASSERT_TRUE(lease);
+ ASSERT_FALSE(lease->getContext());
+ EXPECT_FALSE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+
+ // Not map user context.
+ ElementPtr user_context = Element::createList();
+ lease->setContext(user_context);
+ EXPECT_TRUE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+ EXPECT_FALSE(lease->getContext());
+
+ // No ISC.
+ user_context = Element::createMap();
+ user_context->set("foo", Element::create(string("bar")));
+ lease->setContext(user_context);
+ EXPECT_FALSE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+
+ // Not map ISC.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ ElementPtr isc = Element::create(string("..."));
+ user_context->set("ISC", isc);
+ EXPECT_TRUE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+ EXPECT_FALSE(lease->getContext());
+
+ // No relays.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ isc->set("foo", Element::create(string("bar")));
+ EXPECT_FALSE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+
+ // Not list relays.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ ElementPtr relays = Element::create(string("foo"));
+ isc->set("relays", relays);
+ EXPECT_TRUE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+ EXPECT_FALSE(lease->getContext());
+
+ // Empty relays.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ relays = Element::createList();
+ isc->set("relays", relays);
+ EXPECT_TRUE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+ EXPECT_FALSE(lease->getContext());
+
+ // Not map relay.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ relays = Element::createList();
+ isc->set("relays", relays);
+ relays->add(Element::create(string("foo")));
+ EXPECT_TRUE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+ EXPECT_FALSE(lease->getContext());
+
+ // Positive case.
+ user_context = Element::createMap();
+ lease->setContext(user_context);
+ isc = Element::createMap();
+ user_context->set("ISC", isc);
+ relays = Element::createList();
+ isc->set("relays", relays);
+ ElementPtr relay = Element::createMap();
+ relays->add(relay);
+ relay->set("foo", Element::create(string("bar")));
+ EXPECT_TRUE(LeaseMgr::upgradeLease6ExtendedInfo(lease));
+
+ ConstElementPtr new_user_context = lease->getContext();
+ ASSERT_TRUE(new_user_context);
+ ASSERT_EQ(Element::map, new_user_context->getType());
+ ConstElementPtr new_isc = new_user_context->get("ISC");
+ ASSERT_TRUE(new_isc);
+ ASSERT_EQ(Element::map, new_isc->getType());
+ ConstElementPtr relay_info = new_isc->get("relay-info");
+ ASSERT_TRUE(relay_info);
+ ASSERT_EQ(Element::list, relay_info->getType());
+ ASSERT_EQ("[ { \"foo\": \"bar\" } ]", relay_info->str());
+}
+
+// Verify Lease6 user context upgrade complex operations.
+TEST(Lease6ExtendedInfoTest, upgradeLease6ExtendedInfo) {
+ // Structure that defines a test scenario.
+ struct Scenario {
+ string description_; // test description.
+ string orig_; // original user context.
+ string expected_; // expected user context.
+ };
+
+ // Test scenarios.
+ vector<Scenario> scenarios {
+ {
+ "no user context",
+ "",
+ ""
+ },
+ {
+ "user context is not a map",
+ "[ ]",
+ ""
+ },
+ {
+ "no ISC entry",
+ "{ }",
+ ""
+ },
+ {
+ "no ISC entry but not empty",
+ "{ \"foo\": true }",
+ "{ \"foo\": true }"
+ },
+ {
+ "ISC entry is not a map",
+ "{ \"ISC\": true }",
+ ""
+ },
+ {
+ "ISC entry is not a map, user context not empty",
+ "{ \"foo\": true, \"ISC\": true }",
+ "{ \"foo\": true }"
+ },
+ {
+ "no relays",
+ "{ \"ISC\": { } }",
+ ""
+ },
+ {
+ "no relays, ISC not empty",
+ "{ \"ISC\": { \"foo\": true } }",
+ "{ \"ISC\": { \"foo\": true } }"
+ },
+ {
+ "relays is not a list",
+ "{ \"ISC\": { \"relays\": { } } }",
+ ""
+ },
+ {
+ "relays is not a list, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relays\": { } } }",
+ "{ \"ISC\": { \"foo\": true } }"
+ },
+ {
+ "relays is empty",
+ "{ \"ISC\": { \"relays\": [ ] } }",
+ ""
+ },
+ {
+ "relays is empty, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relays\": [ ] } }",
+ "{ \"ISC\": { \"foo\": true } }"
+ },
+ {
+ "relay is not a map",
+ "{ \"ISC\": { \"relays\": [ \"foobar\" ] } }",
+ ""
+ },
+ {
+ "relay has other values",
+ "{ \"ISC\": { \"relays\": [ { \"foo\": \"bar\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"foo\": \"bar\" } ] } }"
+ },
+ {
+ "one relay with no ids",
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" } ] } }"
+ },
+ {
+ "one relay with remote and relay ids",
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }"
+ },
+ {
+ "two relays",
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" }, { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" }, { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }"
+ }
+ };
+
+ Lease6Ptr lease(new Lease6());
+ ElementPtr orig_context;
+ ElementPtr exp_context;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ // Create the original user context from JSON.
+ if (scenario.orig_.empty()) {
+ orig_context.reset();
+ } else {
+ ASSERT_NO_THROW(orig_context = Element::fromJSON(scenario.orig_))
+ << "invalid original context, test " << scenario.description_
+ << " is broken";
+ }
+
+ // Create the expected user context from JSON.
+ if (scenario.expected_.empty()) {
+ exp_context.reset();
+ } else {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.expected_))
+ << "invalid expected context, test is broken";
+ }
+
+ // Perform the test.
+ lease->setContext(orig_context);
+ ConstElementPtr before;
+ if (orig_context) {
+ before = isc::data::copy(orig_context);
+ }
+ bool ret = LeaseMgr::upgradeLease6ExtendedInfo(lease);
+ ConstElementPtr after = lease->getContext();
+ if (!before && !after) {
+ EXPECT_FALSE(ret) << "null before and null after";
+ } else if ((before && !after) || (!before && after)) {
+ EXPECT_TRUE(ret) << "only one of before and after is null";
+ } else if (before->equals(*after)) {
+ EXPECT_FALSE(ret) << "before == after";
+ } else {
+ EXPECT_TRUE(ret) << "before != after";
+ }
+ if (!exp_context) {
+ EXPECT_FALSE(after) << "expected null, got " << *after;
+ } else {
+ ASSERT_TRUE(after) << "expected not null, got null";
+ EXPECT_TRUE(exp_context->equals(*after))
+ << "expected: " << *exp_context << std::endl
+ << "actual: " << *after << std::endl;
+ }
+ }
+}
+
+/// Verify setExtendedInfoTablesEnabled without valid extended info.
+TEST(Lease6ExtendedInfoTest, invalidSetExtendedInfoTablesEnabled) {
+
+ DatabaseConnection::ParameterMap pmap;
+ boost::scoped_ptr<ConcreteLeaseMgr> mgr(new ConcreteLeaseMgr(pmap));
+
+ // Structure that defines a test scenario.
+ struct Scenario {
+ string description_; // test description.
+ string user_context_txt_; // user context.
+ };
+
+ // Test scenarios.
+ vector<Scenario> scenarios {
+ {
+ "no user context",
+ ""
+ },
+ {
+ "user context is not a map",
+ "[ ]"
+ },
+ {
+ "no ISC entry",
+ "{ }"
+ },
+ {
+ "no ISC entry but not empty",
+ "{ \"foo\": true }"
+ },
+ {
+ "ISC entry is not a map",
+ "{ \"ISC\": true }"
+ },
+ {
+ "ISC entry is not a map, user context not empty",
+ "{ \"foo\": true, \"ISC\": true }"
+ },
+ {
+ "no relay-info",
+ "{ \"ISC\": { } }"
+ },
+ {
+ "no relay-info, ISC not empty",
+ "{ \"ISC\": { \"foo\": true } }"
+ },
+ {
+ "relay-info is not a list",
+ "{ \"ISC\": { \"relay-info\": { } } }"
+ },
+ {
+ "relay-info is not a list, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relay-info\": { } } }"
+ },
+ {
+ "relay-info is empty",
+ "{ \"ISC\": { \"relay-info\": [ ] } }"
+ },
+ {
+ "relay-info is empty, ISC not empty",
+ "{ \"ISC\": { \"foo\": true, \"relay-info\": [ ] } }"
+ },
+ {
+ "relay is not a map",
+ "{ \"ISC\": { \"relay-info\": [ \"foobar\" ] } }"
+ },
+ {
+ "relay has other values",
+ "{ \"ISC\": { \"relay-info\": [ { \"foo\": \"bar\" } ] } }"
+ }
+ };
+
+ Lease6Ptr lease(new Lease6());
+ ElementPtr user_context;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+
+ // Create the user context from JSON.
+ if (scenario.user_context_txt_.empty()) {
+ user_context.reset();
+ } else {
+ ASSERT_NO_THROW(user_context = Element::fromJSON(scenario.user_context_txt_))
+ << "invalid user context, test " << scenario.description_
+ << " is broken";
+ }
+
+ // Perform the test.
+ lease->setContext(user_context);
+ mgr->relay_id6_.clear();
+ mgr->remote_id6_.clear();
+ EXPECT_NO_THROW(mgr->addExtendedInfo6(lease));
+ EXPECT_TRUE(mgr->relay_id6_.empty());
+ EXPECT_TRUE(mgr->remote_id6_.empty());
+ }
+}
+
+/// Verify setExtendedInfoTablesEnabled with one relay without ids.
+TEST(Lease6ExtendedInfoTest, noIdSetExtendedInfoTablesEnabled) {
+
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" } ] } }";
+
+ DatabaseConnection::ParameterMap pmap;
+ boost::scoped_ptr<ConcreteLeaseMgr> mgr(new ConcreteLeaseMgr(pmap));
+
+ Lease6Ptr lease(new Lease6());
+ lease->addr_ = IOAddress("2001:db8::100");
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+ EXPECT_NO_THROW(mgr->addExtendedInfo6(lease));
+ EXPECT_TRUE(mgr->relay_id6_.empty());
+ EXPECT_TRUE(mgr->remote_id6_.empty());
+}
+
+/// Verify setExtendedInfoTablesEnabled with one relay with ids.
+TEST(Lease6ExtendedInfoTest, idsSetExtendedInfoTablesEnabled) {
+
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+
+ DatabaseConnection::ParameterMap pmap;
+ boost::scoped_ptr<ConcreteLeaseMgr> mgr(new ConcreteLeaseMgr(pmap));
+
+ Lease6Ptr lease(new Lease6());
+ lease->addr_ = IOAddress("2001:db8::100");
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+ EXPECT_NO_THROW(mgr->addExtendedInfo6(lease));
+
+ EXPECT_EQ(1, mgr->relay_id6_.size());
+ Lease6ExtendedInfoPtr ex_info = mgr->relay_id6_.front();
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8::100", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& relay_id = ex_info->id_;
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, relay_id);
+
+ EXPECT_EQ(1, mgr->remote_id6_.size());
+ ex_info = mgr->remote_id6_.front();
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8::100", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& remote_id = ex_info->id_;
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, remote_id);
+}
+
+/// Verify setExtendedInfoTablesEnabled with one relay with ids but
+/// :: link address.
+TEST(Lease6ExtendedInfoTest, linkZeroSetExtendedInfoTablesEnabled) {
+
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"::\", \"peer\": \"2001:db8::6\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+
+ DatabaseConnection::ParameterMap pmap;
+ boost::scoped_ptr<ConcreteLeaseMgr> mgr(new ConcreteLeaseMgr(pmap));
+
+ Lease6Ptr lease(new Lease6());
+ lease->addr_ = IOAddress("2001:db8::100");
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+ EXPECT_NO_THROW(mgr->addExtendedInfo6(lease));
+
+ EXPECT_EQ(1, mgr->relay_id6_.size());
+ Lease6ExtendedInfoPtr ex_info = mgr->relay_id6_.front();
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8::100", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& relay_id = ex_info->id_;
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, relay_id);
+
+ EXPECT_EQ(1, mgr->remote_id6_.size());
+ ex_info = mgr->remote_id6_.front();
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8::100", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& remote_id = ex_info->id_;
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, remote_id);
+}
+
+/// Verify setExtendedInfoTablesEnabled with two relays.
+TEST(Lease6ExtendedInfoTest, twoSetExtendedInfoTablesEnabled) {
+
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" }, { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+
+ DatabaseConnection::ParameterMap pmap;
+ boost::scoped_ptr<ConcreteLeaseMgr> mgr(new ConcreteLeaseMgr(pmap));
+
+ Lease6Ptr lease(new Lease6());
+ lease->addr_ = IOAddress("2001:db8::100");
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+ EXPECT_NO_THROW(mgr->addExtendedInfo6(lease));
+
+ EXPECT_EQ(1, mgr->relay_id6_.size());
+ Lease6ExtendedInfoPtr ex_info = mgr->relay_id6_.front();
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8::100", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& relay_id = ex_info->id_;
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, relay_id);
+
+ EXPECT_EQ(1, mgr->remote_id6_.size());
+ ex_info = mgr->remote_id6_.front();
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8::100", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& remote_id = ex_info->id_;
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, remote_id);
+}
+
+// There's no point in calling any other methods in LeaseMgr, as they
+// are purely virtual, so we would only call ConcreteLeaseMgr methods.
+// Those methods are just stubs that do not return anything.
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/lease_unittest.cc b/src/lib/dhcpsrv/tests/lease_unittest.cc
new file mode 100644
index 0000000..e50db58
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/lease_unittest.cc
@@ -0,0 +1,1368 @@
+// Copyright (C) 2013-2023 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 <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/lease.h>
+#include <util/pointer_util.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <cc/data.h>
+#include <gtest/gtest.h>
+#include <vector>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::test;
+
+namespace {
+
+/// Hardware address used by different tests.
+const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+/// Client id used by different tests.
+const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+/// Valid lifetime value used by different tests.
+const uint32_t VALID_LIFETIME = 500;
+/// Subnet ID used by different tests.
+const uint32_t SUBNET_ID = 42;
+/// IAID value used by different tests.
+const uint32_t IAID = 7;
+
+/// @brief Creates an instance of the lease with certain FQDN data.
+///
+/// @param hostname Hostname.
+/// @param fqdn_fwd Forward FQDN update setting for a created lease.
+/// @param fqdn_rev Reverse FQDN update setting for a created lease.
+///
+/// @return Instance of the created lease.
+Lease4 createLease4(const std::string& hostname, const bool fqdn_fwd,
+ const bool fqdn_rev) {
+ Lease4 lease;
+ lease.hostname_ = hostname;
+ lease.fqdn_fwd_ = fqdn_fwd;
+ lease.fqdn_rev_ = fqdn_rev;
+ return (lease);
+}
+
+/// @brief Tests that an exception is thrown when one of the lease
+/// parameters is missing or invalid during lease parsing.
+///
+/// If the tested parameter is mandatory the test first removes the
+/// specified parameter from the JSON structure and tries to re-create
+/// the lease, expecting an error.
+/// Next, the test sets invalid value for this parameter and also
+/// expected an error.
+///
+/// @tparam LeaseType @c Lease4 or @c Lease6.
+/// @tparam ValueType type of the invalid value to be used for the
+/// specified parameter.
+/// @param lease_as_json a string contains lease in a JSON format.
+/// @param parameter_name name of the lease parameter to be tested.
+/// @param invalid_value invalid parameter value.
+/// @param mandatory boolean value indicating if the parameter is
+/// mandatory.
+template<typename LeaseType, typename ValueType>
+void testInvalidElement(const std::string& lease_as_json,
+ const std::string& parameter_name,
+ ValueType invalid_value,
+ const bool mandatory = true) {
+ ElementPtr lease;
+
+ // If the parameter is mandatory, check that the exception is
+ // thrown if it is missing.
+ if (mandatory) {
+ ASSERT_NO_THROW(lease = Element::fromJSON(lease_as_json));
+ lease->remove(parameter_name);
+ EXPECT_THROW(LeaseType::fromElement(lease), BadValue)
+ << "test failed for " << parameter_name;
+ }
+
+ // Set invalid value and expect an error.
+ ASSERT_NO_THROW(lease = Element::fromJSON(lease_as_json));
+ lease->set(parameter_name, Element::create(invalid_value));
+ EXPECT_THROW(LeaseType::fromElement(lease), BadValue)
+ << "test failed for " << parameter_name
+ << " and invalid value " << invalid_value;
+}
+
+/// @brief Fixture class used in Lease4 testing.
+class Lease4Test : public ::testing::Test {
+public:
+
+ /// @brief Default constructor
+ ///
+ /// Currently it only initializes hardware address.
+ Lease4Test() {
+ hwaddr_.reset(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+ clientid_.reset(new ClientId(CLIENTID, sizeof(CLIENTID)));
+ }
+
+ /// Hardware address, used by tests.
+ HWAddrPtr hwaddr_;
+
+ /// Pointer to the client identifier used by tests.
+ ClientIdPtr clientid_;
+};
+
+// This test checks if the Lease4 structure can be instantiated correctly.
+TEST_F(Lease4Test, constructor) {
+ // Get current time for the use in Lease.
+ const time_t current_time = time(0);
+
+ // We want to check that various addresses work, so let's iterate over
+ // these.
+ const uint32_t ADDRESS[] = {
+ 0x00000000, 0x01020304, 0x7fffffff, 0x80000000, 0x80000001, 0xffffffff
+ };
+
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+
+ // Create the lease
+ Lease4 lease(ADDRESS[i], hwaddr_, clientid_, VALID_LIFETIME,
+ current_time, SUBNET_ID, true, true,
+ "Hostname.Example.Com.");
+
+ EXPECT_EQ(ADDRESS[i], lease.addr_.toUint32());
+ EXPECT_TRUE(util::equalValues(hwaddr_, lease.hwaddr_));
+ EXPECT_TRUE(util::equalValues(clientid_, lease.client_id_));
+ EXPECT_EQ(VALID_LIFETIME, lease.valid_lft_);
+ EXPECT_EQ(current_time, lease.cltt_);
+ EXPECT_EQ(SUBNET_ID, lease.subnet_id_);
+ EXPECT_EQ("hostname.example.com.", lease.hostname_);
+ EXPECT_TRUE(lease.fqdn_fwd_);
+ EXPECT_TRUE(lease.fqdn_rev_);
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease.state_);
+ EXPECT_FALSE(lease.getContext());
+ }
+}
+
+// This test verifies that it is correctly determined when the lease
+// belongs to the particular client identified by the client identifier
+// and hw address.
+TEST_F(Lease4Test, leaseBelongsToClient) {
+ // Client identifier that matches the one in the lease.
+ ClientIdPtr matching_client_id = ClientId::fromText("01:02:03:04");
+ // Client identifier that doesn't match the one in the lease.
+ ClientIdPtr diff_client_id = ClientId::fromText("01:02:03:05");
+ // Null (no) client identifier.
+ ClientIdPtr null_client_id;
+
+ // HW Address that matches the one in the lease.
+ HWAddrPtr matching_hw(new HWAddr(HWAddr::fromText("00:01:02:03:04:05",
+ HTYPE_ETHER)));
+ // HW Address that doesn't match the one in the lease.
+ HWAddrPtr diff_hw(new HWAddr(HWAddr::fromText("00:01:02:03:04:06",
+ HTYPE_ETHER)));
+ // Null HW Address.
+ HWAddrPtr null_hw;
+
+ // Create the lease with MAC address and Client Identifier.
+ Lease4 lease(IOAddress("192.0.2.1"), matching_hw, matching_client_id,
+ 60, time(0), 0, 0, 1);
+
+ // Verify cases for lease that has both hw address and client identifier.
+ EXPECT_TRUE(lease.belongsToClient(matching_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(matching_hw, diff_client_id));
+ EXPECT_TRUE(lease.belongsToClient(matching_hw, null_client_id));
+ EXPECT_TRUE(lease.belongsToClient(diff_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(diff_hw, diff_client_id));
+ EXPECT_FALSE(lease.belongsToClient(diff_hw, null_client_id));
+ EXPECT_TRUE(lease.belongsToClient(null_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(null_hw, diff_client_id));
+ EXPECT_FALSE(lease.belongsToClient(null_hw, null_client_id));
+
+ // Verify cases for lease that has only HW address.
+ lease.client_id_ = null_client_id;
+ EXPECT_TRUE(lease.belongsToClient(matching_hw, matching_client_id));
+ EXPECT_TRUE(lease.belongsToClient(matching_hw, diff_client_id));
+ EXPECT_TRUE(lease.belongsToClient(matching_hw, null_client_id));
+ EXPECT_FALSE(lease.belongsToClient(diff_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(diff_hw, diff_client_id));
+ EXPECT_FALSE(lease.belongsToClient(diff_hw, null_client_id));
+ EXPECT_FALSE(lease.belongsToClient(null_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(null_hw, diff_client_id));
+ EXPECT_FALSE(lease.belongsToClient(null_hw, null_client_id));
+
+ // Verify cases for lease that has only client identifier.
+ lease.client_id_ = matching_client_id;
+ lease.hwaddr_ = null_hw;
+ EXPECT_TRUE(lease.belongsToClient(matching_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(matching_hw, diff_client_id));
+ EXPECT_FALSE(lease.belongsToClient(matching_hw, null_client_id));
+ EXPECT_TRUE(lease.belongsToClient(diff_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(diff_hw, diff_client_id));
+ EXPECT_FALSE(lease.belongsToClient(diff_hw, null_client_id));
+ EXPECT_TRUE(lease.belongsToClient(null_hw, matching_client_id));
+ EXPECT_FALSE(lease.belongsToClient(null_hw, diff_client_id));
+ EXPECT_FALSE(lease.belongsToClient(null_hw, null_client_id));
+}
+
+/// @brief Lease4 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST_F(Lease4Test, operatorEquals) {
+
+ // Random values for the tests
+ const uint32_t ADDRESS = 0x01020304;
+ const time_t current_time = time(0);
+
+ // Check when the leases are equal.
+ Lease4 lease1(ADDRESS, hwaddr_, clientid_, VALID_LIFETIME, current_time,
+ SUBNET_ID);
+ lease1.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ // We need to make an explicit copy. Otherwise the second lease will just
+ // store a pointer and we'll have two leases pointing to a single HWAddr
+ // or client. That would make modifications to only one impossible.
+ HWAddrPtr hwcopy(new HWAddr(*hwaddr_));
+ ClientIdPtr clientid_copy(new ClientId(*clientid_));
+
+ Lease4 lease2(ADDRESS, hwcopy, clientid_copy, VALID_LIFETIME, current_time,
+ SUBNET_ID);
+ lease2.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ EXPECT_TRUE(lease1 == lease2);
+ EXPECT_FALSE(lease1 != lease2);
+
+ // Now vary individual fields in a lease and check that the leases compare
+ // not equal in every case.
+ lease1.addr_ = IOAddress(ADDRESS + 1);
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.addr_ = lease2.addr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.hwaddr_->hwaddr_[0];
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hwaddr_ = lease2.hwaddr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ std::vector<uint8_t> clientid_vec = clientid_->getClientId();
+ ++clientid_vec[0];
+ lease1.client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ --clientid_vec[0];
+ lease1.client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.valid_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.valid_lft_ = lease2.valid_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.cltt_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.cltt_ = lease2.cltt_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.subnet_id_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.subnet_id_ = lease2.subnet_id_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.hostname_ += std::string("something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hostname_ = lease2.hostname_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_rev_ = lease2.fqdn_rev_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.state_ += 1;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease2.state_ += 1;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.setContext(Element::fromJSON("{ \"foobar\": 5678 }"));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.setContext(ConstElementPtr());
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease2.setContext(ConstElementPtr());
+ EXPECT_TRUE(lease1 == lease2); // Check that no user context has mase the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
+
+// Verify that the client id can be returned as a vector object and if client
+// id is null the empty vector is returned.
+TEST_F(Lease4Test, getClientIdVector) {
+ // Create a lease.
+ Lease4 lease;
+ // By default, the lease should have client id set to null. If it doesn't,
+ // continuing the test makes no sense.
+ ASSERT_FALSE(lease.client_id_);
+ // When client id is null the vector returned should be empty.
+ EXPECT_TRUE(lease.getClientIdVector().empty());
+
+ // Initialize client identifier to non-null value.
+ lease.client_id_ = clientid_;
+ // Check that the returned vector, encapsulating client id is equal to
+ // the one that has been used to set the client id for the lease.
+ std::vector<uint8_t> returned_vec = lease.getClientIdVector();
+ EXPECT_TRUE(returned_vec == clientid_->getClientId());
+}
+
+// Verify the behavior of the function which checks FQDN data for equality.
+TEST_F(Lease4Test, hasIdenticalFqdn) {
+ Lease4 lease = createLease4("myhost.example.com.", true, true);
+ EXPECT_TRUE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ true, true)));
+ // Case insensitive comparison.
+ EXPECT_TRUE(lease.hasIdenticalFqdn(createLease4("myHOst.ExamplE.coM.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("other.example.com.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ false, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ true, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("myhost.example.com.",
+ false, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease4("other.example.com.",
+ false, false)));
+}
+
+// Verify that toText() method reports Lease4 structure properly.
+TEST_F(Lease4Test, toText) {
+
+ const time_t current_time = 12345678;
+ Lease4 lease(IOAddress("192.0.2.3"), hwaddr_, clientid_, 3600,
+ current_time, 789);
+ lease.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ std::stringstream expected;
+ expected << "Address: 192.0.2.3\n"
+ << "Valid life: 3600\n"
+ << "Cltt: 12345678\n"
+ << "Hardware addr: " << hwaddr_->toText(false) << "\n"
+ << "Client id: " << clientid_->toText() << "\n"
+ << "Subnet ID: 789\n"
+ << "Pool ID: 0\n"
+ << "State: default\n"
+ << "Relay ID: (none)\n"
+ << "Remote ID: (none)\n"
+ << "User context: { \"foobar\": 1234 }\n";
+
+ EXPECT_EQ(expected.str(), lease.toText());
+
+ // Now let's try with a lease without hardware address, client identifier
+ // and user context.
+ lease.hwaddr_.reset();
+ lease.client_id_.reset();
+ lease.setContext(ConstElementPtr());
+ expected.str("");
+ expected << "Address: 192.0.2.3\n"
+ << "Valid life: 3600\n"
+ << "Cltt: 12345678\n"
+ << "Hardware addr: (none)\n"
+ << "Client id: (none)\n"
+ << "Subnet ID: 789\n"
+ << "Pool ID: 0\n"
+ << "State: default\n"
+ << "Relay ID: (none)\n"
+ << "Remote ID: (none)\n";
+
+ EXPECT_EQ(expected.str(), lease.toText());
+}
+
+// Verify that Lease4 structure can be converted to JSON properly.
+TEST_F(Lease4Test, toElement) {
+
+ const time_t current_time = 12345678;
+ Lease4 lease(IOAddress("192.0.2.3"), hwaddr_, clientid_, 3600,
+ current_time, 789, true, true, "URANIA.example.org");
+ lease.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ std::string expected = "{"
+ "\"client-id\": \"17:34:e2:ff:09:92:54\","
+ "\"cltt\": 12345678,"
+ "\"fqdn-fwd\": true,"
+ "\"fqdn-rev\": true,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"ip-address\": \"192.0.2.3\","
+ "\"state\": 0,"
+ "\"subnet-id\": 789,"
+ "\"user-context\": { \"foobar\": 1234 },"
+ "\"valid-lft\": 3600 "
+ "}";
+
+ runToElementTest<Lease4>(expected, lease);
+
+ // Now let's try with a lease without client-id and user context.
+ lease.client_id_.reset();
+ lease.setContext(ConstElementPtr());
+ lease.pool_id_ = 5;
+
+ expected = "{"
+ "\"cltt\": 12345678,"
+ "\"fqdn-fwd\": true,"
+ "\"fqdn-rev\": true,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"ip-address\": \"192.0.2.3\","
+ "\"state\": 0,"
+ "\"subnet-id\": 789,"
+ "\"pool-id\": 5,"
+ "\"valid-lft\": 3600 "
+ "}";
+
+ runToElementTest<Lease4>(expected, lease);
+
+ // And to finish try with a comment.
+ lease.setContext(Element::fromJSON("{ \"comment\": \"a comment\" }"));
+
+ expected = "{"
+ "\"cltt\": 12345678,"
+ "\"user-context\": { \"comment\": \"a comment\" },"
+ "\"fqdn-fwd\": true,"
+ "\"fqdn-rev\": true,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"ip-address\": \"192.0.2.3\","
+ "\"state\": 0,"
+ "\"subnet-id\": 789,"
+ "\"pool-id\": 5,"
+ "\"valid-lft\": 3600 "
+ "}";
+
+ runToElementTest<Lease4>(expected, lease);
+}
+
+// Verify that the Lease4 can be created from JSON.
+TEST_F(Lease4Test, fromElement) {
+ std::string json = "{"
+ "\"client-id\": \"17:34:e2:ff:09:92:54\","
+ "\"cltt\": 12345678,"
+ "\"fqdn-fwd\": true,"
+ "\"fqdn-rev\": true,"
+ "\"hostname\": \"urania.example.ORG\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"ip-address\": \"192.0.2.3\","
+ "\"state\": 0,"
+ "\"subnet-id\": 789,"
+ "\"pool-id\": 5,"
+ "\"user-context\": { \"foo\": \"bar\" },"
+ "\"valid-lft\": 3600 "
+ "}";
+
+ Lease4Ptr lease;
+ ASSERT_NO_THROW(lease = Lease4::fromElement(Element::fromJSON(json)));
+
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("192.0.2.3", lease->addr_.toText());
+ EXPECT_EQ(789, static_cast<uint32_t>(lease->subnet_id_));
+ EXPECT_EQ(5, static_cast<uint32_t>(lease->pool_id_));
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ("hwtype=1 08:00:2b:02:3f:4e", lease->hwaddr_->toText());
+ ASSERT_TRUE(lease->client_id_);
+ EXPECT_EQ("17:34:e2:ff:09:92:54", lease->client_id_->toText());
+ EXPECT_EQ(12345678, lease->cltt_);
+ EXPECT_EQ(lease->cltt_, lease->current_cltt_);
+ EXPECT_EQ(3600, lease->valid_lft_);
+ EXPECT_EQ(lease->valid_lft_, lease->current_valid_lft_);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("urania.example.org", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_DEFAULT, lease->state_);
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foo\": \"bar\" }", lease->getContext()->str());
+}
+
+// Test that specifying invalid values for a lease or not specifying
+// mandatory lease parameters causes an error while parsing the lease.
+TEST_F(Lease4Test, fromElementInvalidValues) {
+ // Create valid configuration. We use it as a base from which we will
+ // be removing some of the parameters and some values will be selectively
+ // modified.
+ std::string json = "{"
+ "\"client-id\": \"17:34:e2:ff:09:92:54\","
+ "\"cltt\": 12345678,"
+ "\"fqdn-fwd\": true,"
+ "\"fqdn-rev\": true,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"ip-address\": \"192.0.2.3\","
+ "\"state\": 0,"
+ "\"subnet-id\": 789,"
+ "\"valid-lft\": 3600 "
+ "}";
+
+ // Test invalid parameter values and missing parameters.
+ testInvalidElement<Lease4>(json, "client-id", std::string("rock"), false);
+ testInvalidElement<Lease4>(json, "cltt", std::string("xyz"));
+ testInvalidElement<Lease4>(json, "cltt", -1, false);
+ testInvalidElement<Lease4>(json, "fqdn-fwd", 123);
+ testInvalidElement<Lease4>(json, "fqdn-rev", std::string("foo"));
+ testInvalidElement<Lease4, bool>(json, "hostname", true);
+ testInvalidElement<Lease4>(json, "hw-address", "01::00::");
+ testInvalidElement<Lease4>(json, "hw-address", 1234, false);
+ testInvalidElement<Lease4, long int>(json, "ip-address", 0xFF000201);
+ testInvalidElement<Lease4>(json, "ip-address", "2001:db8:1::1", false);
+ testInvalidElement<Lease4>(json, "state", std::string("xyz"));
+ testInvalidElement<Lease4>(json, "state", 1234, false);
+ testInvalidElement<Lease4>(json, "subnet-id", std::string("xyz"));
+ testInvalidElement<Lease4>(json, "subnet-id", -5, false);
+ testInvalidElement<Lease4>(json, "subnet-id", 0x100000000, false);
+ testInvalidElement<Lease4>(json, "pool-id", std::string("xyz"), false);
+ testInvalidElement<Lease4>(json, "pool-id", -5, false);
+ testInvalidElement<Lease4>(json, "pool-id", 0, false);
+ testInvalidElement<Lease4>(json, "pool-id", 0x100000000, false);
+ testInvalidElement<Lease4>(json, "valid-lft", std::string("xyz"));
+ testInvalidElement<Lease4>(json, "valid-lft", -3, false);
+ testInvalidElement<Lease4>(json, "user-context", "[ ]", false);
+ testInvalidElement<Lease4>(json, "user-context", 1234, false);
+ testInvalidElement<Lease4>(json, "user-context", false, false);
+ testInvalidElement<Lease4>(json, "user-context", "foo", false);
+}
+
+// Verify that decline() method properly clears up specific fields.
+TEST_F(Lease4Test, decline) {
+
+ const time_t current_time = 12345678;
+ Lease4 lease(IOAddress("192.0.2.3"), hwaddr_, clientid_, 3600,
+ current_time, 789);
+ lease.hostname_ = "foo.example.org";
+ lease.fqdn_fwd_ = true;
+ lease.fqdn_rev_ = true;
+
+ time_t now = time(0);
+
+ // Move lease to declined state and set its valid-lifetime to 123 seconds
+ lease.decline(123);
+ ASSERT_TRUE(lease.hwaddr_);
+ EXPECT_EQ("", lease.hwaddr_->toText(false));
+ EXPECT_FALSE(lease.client_id_);
+
+ EXPECT_TRUE(now <= lease.cltt_);
+ EXPECT_TRUE(lease.cltt_ <= now + 1);
+ EXPECT_EQ("", lease.hostname_);
+ EXPECT_FALSE(lease.fqdn_fwd_);
+ EXPECT_FALSE(lease.fqdn_rev_);
+ EXPECT_EQ(Lease::STATE_DECLINED, lease.state_);
+ EXPECT_EQ(123, lease.valid_lft_);
+ EXPECT_FALSE(lease.getContext());
+}
+
+// Verify that the lease states are correctly returned in the textual format.
+TEST_F(Lease4Test, stateToText) {
+ EXPECT_EQ("default", Lease4::statesToText(Lease::STATE_DEFAULT));
+ EXPECT_EQ("declined", Lease4::statesToText(Lease::STATE_DECLINED));
+ EXPECT_EQ("expired-reclaimed", Lease4::statesToText(Lease::STATE_EXPIRED_RECLAIMED));
+}
+
+/// @brief Creates an instance of the lease with certain FQDN data.
+///
+/// @param hostname Hostname.
+/// @param fqdn_fwd Forward FQDN update setting for a created lease.
+/// @param fqdn_rev Reverse FQDN update setting for a created lease.
+///
+/// @return Instance of the created lease.
+Lease6 createLease6(const std::string& hostname, const bool fqdn_fwd,
+ const bool fqdn_rev) {
+ Lease6 lease;
+ lease.hostname_ = hostname;
+ lease.fqdn_fwd_ = fqdn_fwd;
+ lease.fqdn_rev_ = fqdn_rev;
+ return (lease);
+}
+
+// Lease6 is also defined in lease_mgr.h, so is tested in this file as well.
+// This test checks if the Lease6 structure can be instantiated correctly
+TEST(Lease6Test, constructorDefault) {
+
+ // check a variety of addresses with different bits set.
+ const char* ADDRESS[] = {
+ "::", "::1", "2001:db8:1::456",
+ "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "8000::", "8000::1",
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ };
+
+ // Other values
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+ uint32_t iaid = IAID; // Just a number
+ SubnetID subnet_id = 8; // Just another number
+
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+ IOAddress addr(ADDRESS[i]);
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
+ duid, iaid, 100, 200,
+ subnet_id));
+
+ EXPECT_TRUE(lease->addr_ == addr);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_TRUE(lease->iaid_ == iaid);
+ EXPECT_TRUE(lease->subnet_id_ == subnet_id);
+ EXPECT_TRUE(lease->type_ == Lease::TYPE_NA);
+ EXPECT_TRUE(lease->preferred_lft_ == 100);
+ EXPECT_TRUE(lease->valid_lft_ == 200);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_TRUE(lease->hostname_.empty());
+ EXPECT_FALSE(lease->getContext());
+ }
+
+ // Lease6 must be instantiated with a DUID, not with null pointer
+ IOAddress addr(ADDRESS[0]);
+ Lease6Ptr lease2;
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ DuidPtr(), iaid, 100, 200,
+ subnet_id)),
+ BadValue, "DUID is mandatory for an IPv6 lease");
+
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ DuidPtr(), iaid, 100, 200,
+ subnet_id, true, true, "", HWAddrPtr())),
+ BadValue, "DUID is mandatory for an IPv6 lease");
+
+ // Lease6 must have a prefixlen set to 128 for non prefix type.
+ addr = IOAddress(ADDRESS[4]);
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ duid, iaid, 100, 200,
+ subnet_id, HWAddrPtr(), 96)),
+ BadValue, "prefixlen must be 128 for non prefix type");
+
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ duid, iaid, 100, 200,
+ subnet_id, true, true, "", HWAddrPtr(), 96)),
+ BadValue, "prefixlen must be 128 for non prefix type");
+
+ addr = IOAddress(ADDRESS[4]);
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_TA, addr,
+ duid, iaid, 100, 200,
+ subnet_id, HWAddrPtr(), 96)),
+ BadValue, "prefixlen must be 128 for non prefix type");
+
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_TA, addr,
+ duid, iaid, 100, 200,
+ subnet_id, true, true, "", HWAddrPtr(), 96)),
+ BadValue, "prefixlen must be 128 for non prefix type");
+}
+
+// This test verifies that the Lease6 constructor which accepts FQDN data,
+// sets the data correctly for the lease.
+TEST(Lease6Test, constructorWithFQDN) {
+
+ // check a variety of addresses with different bits set.
+ const char* ADDRESS[] = {
+ "::", "::1", "2001:db8:1::456",
+ "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "8000::", "8000::1",
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ };
+
+ // Other values
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+ uint32_t iaid = IAID; // Just a number
+ SubnetID subnet_id = 8; // Just another number
+
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+ IOAddress addr(ADDRESS[i]);
+ Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
+ duid, iaid, 100, 200, subnet_id,
+ true, true, "Host.Example.Com."));
+
+ EXPECT_TRUE(lease->addr_ == addr);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_TRUE(lease->iaid_ == iaid);
+ EXPECT_TRUE(lease->subnet_id_ == subnet_id);
+ EXPECT_TRUE(lease->type_ == Lease::TYPE_NA);
+ EXPECT_TRUE(lease->preferred_lft_ == 100);
+ EXPECT_TRUE(lease->valid_lft_ == 200);
+ EXPECT_TRUE(lease->fqdn_fwd_);
+ EXPECT_TRUE(lease->fqdn_rev_);
+ EXPECT_EQ("host.example.com.", lease->hostname_);
+ }
+
+ // Lease6 must be instantiated with a DUID, not with null pointer
+ IOAddress addr(ADDRESS[0]);
+ Lease6Ptr lease2;
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ DuidPtr(), iaid, 100, 200,
+ subnet_id)),
+ BadValue, "DUID is mandatory for an IPv6 lease");
+
+ EXPECT_THROW_MSG(lease2.reset(new Lease6(Lease::TYPE_NA, addr,
+ DuidPtr(), iaid, 100, 200,
+ subnet_id, true, true, "", HWAddrPtr())),
+ BadValue, "DUID is mandatory for an IPv6 lease");
+}
+
+/// @brief Lease6 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease6Test, operatorEquals) {
+
+ // check a variety of addresses with different bits set.
+ const IOAddress addr("2001:db8:1::456");
+ uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+ uint32_t iaid = IAID; // just a number
+ SubnetID subnet_id = 8; // just another number
+
+ // Check for equality.
+ Lease6 lease1(Lease::TYPE_NA, addr, duid, iaid, 100, 200, subnet_id);
+ Lease6 lease2(Lease::TYPE_NA, addr, duid, iaid, 100, 200, subnet_id);
+
+ lease1.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ lease2.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ // cltt_ constructs with time(0), make sure they are always equal
+ lease1.cltt_ = lease2.cltt_;
+
+ EXPECT_TRUE(lease1 == lease2);
+ EXPECT_FALSE(lease1 != lease2);
+
+ // Go through and alter all the fields one by one
+
+ lease1.addr_ = IOAddress("::1");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.addr_ = lease2.addr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.type_ = Lease::TYPE_PD;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.type_ = lease2.type_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.prefixlen_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.prefixlen_ = lease2.prefixlen_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.iaid_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.iaid_ = lease2.iaid_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++duid_array[0];
+ lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ --duid_array[0];
+ lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.preferred_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.preferred_lft_ = lease2.preferred_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.valid_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.valid_lft_ = lease2.valid_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.cltt_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.cltt_ = lease2.cltt_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.subnet_id_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.subnet_id_ = lease2.subnet_id_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.hostname_ += std::string("something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hostname_ = lease2.hostname_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_rev_ = lease2.fqdn_rev_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.state_ += 1;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease2.state_ += 1;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.setContext(Element::fromJSON("{ \"foobar\": 5678 }"));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.setContext(ConstElementPtr());
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease2.setContext(ConstElementPtr());
+ EXPECT_TRUE(lease1 == lease2); // Check that no user context has mase the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
+
+// Checks if lease expiration is calculated properly
+TEST(Lease6Test, lease6Expired) {
+ const IOAddress addr("2001:db8:1::456");
+ const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+ const uint32_t iaid = IAID; // Just a number
+ const SubnetID subnet_id = 8; // Just another number
+ Lease6 lease(Lease::TYPE_NA, addr, duid, iaid, 100, 200, subnet_id);
+
+ // Case 1: a second before expiration
+ lease.cltt_ = time(0) - 100;
+ lease.valid_lft_ = 101;
+ EXPECT_FALSE(lease.expired());
+
+ // Case 2: the lease will expire after this second is concluded
+ lease.cltt_ = time(0) - 101;
+ EXPECT_FALSE(lease.expired());
+
+ // Case 3: the lease is expired
+ lease.cltt_ = time(0) - 102;
+ EXPECT_TRUE(lease.expired());
+
+ // Case 4: the lease is static
+ lease.cltt_ = 1;
+ lease.valid_lft_ = Lease::INFINITY_LFT;
+ EXPECT_FALSE(lease.expired());
+}
+
+// Verify that the DUID can be returned as a vector object and if DUID is null
+// the empty vector is returned.
+TEST(Lease6Test, getDuidVector) {
+ // Create a lease.
+ Lease6 lease;
+ // By default, the lease should have client id set to null. If it doesn't,
+ // continuing the test makes no sense.
+ ASSERT_FALSE(lease.duid_);
+ // When client id is null the vector returned should be empty.
+ EXPECT_TRUE(lease.getDuidVector().empty());
+ // Now, let's set the non null DUID. Fill it with the 8 bytes, each
+ // holding a value of 0x42.
+ std::vector<uint8_t> duid_vec(8, 0x42);
+ lease.duid_ = DuidPtr(new DUID(duid_vec));
+ // Check that the returned vector, encapsulating DUID is equal to
+ // the one that has been used to set the DUID for the lease.
+ std::vector<uint8_t> returned_vec = lease.getDuidVector();
+ EXPECT_TRUE(returned_vec == duid_vec);
+}
+
+// Verify that decline() method properly clears up specific fields.
+TEST(Lease6Test, decline) {
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+
+ // Let's create a lease for 2001:db8::1, DUID, iaid=1234,
+ // pref=3000, valid=4000, subnet-id = 1
+ Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid,
+ 1234, 3000, 4000, 1, hwaddr);
+ lease.cltt_ = 12345678;
+ lease.hostname_ = "foo.example.org";
+ lease.fqdn_fwd_ = true;
+ lease.fqdn_rev_ = true;
+
+ time_t now = time(0);
+
+ // Move the lease to declined state and set probation-period to 123 seconds
+ lease.decline(123);
+
+ ASSERT_TRUE(lease.duid_);
+ ASSERT_EQ("00:00:00", lease.duid_->toText());
+ ASSERT_FALSE(lease.hwaddr_);
+ EXPECT_EQ(0, lease.preferred_lft_);
+
+ EXPECT_TRUE(now <= lease.cltt_);
+ EXPECT_TRUE(lease.cltt_ <= now + 1);
+ EXPECT_EQ("", lease.hostname_);
+ EXPECT_FALSE(lease.fqdn_fwd_);
+ EXPECT_FALSE(lease.fqdn_rev_);
+ EXPECT_EQ(Lease::STATE_DECLINED, lease.state_);
+ EXPECT_EQ(123, lease.valid_lft_);
+ EXPECT_FALSE(lease.getContext());
+}
+
+// Verify the behavior of the function which checks FQDN data for equality.
+TEST(Lease6Test, hasIdenticalFqdn) {
+ Lease6 lease = createLease6("myhost.example.com.", true, true);
+ EXPECT_TRUE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ true, true)));
+ // Case insensitive comparison.
+ EXPECT_TRUE(lease.hasIdenticalFqdn(createLease6("myHOst.ExamplE.coM.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("other.example.com.",
+ true, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ false, true)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ true, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("myhost.example.com.",
+ false, false)));
+ EXPECT_FALSE(lease.hasIdenticalFqdn(createLease6("other.example.com.",
+ false, false)));
+}
+
+// Verify that toText() method reports Lease6 structure properly.
+TEST(Lease6Test, toText) {
+
+ HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid, 123456,
+ 400, 800, 5678, hwaddr, 128);
+ lease.cltt_ = 12345678;
+ lease.state_ = Lease::STATE_DECLINED;
+ lease.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ std::stringstream expected;
+ expected << "Type: IA_NA(" << static_cast<int>(Lease::TYPE_NA) << ")\n"
+ << "Address: 2001:db8::1\n"
+ << "Prefix length: 128\n"
+ << "IAID: 123456\n"
+ << "Pref life: 400\n"
+ << "Valid life: 800\n"
+ << "Cltt: 12345678\n"
+ << "DUID: 00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\n"
+ << "Hardware addr: " << hwaddr->toText(false) << "\n"
+ << "Subnet ID: 5678\n"
+ << "Pool ID: 0\n"
+ << "State: declined\n"
+ << "User context: { \"foobar\": 1234 }\n";
+
+ EXPECT_EQ(expected.str(), lease.toText());
+
+ // Now let's try with a lease without hardware address and user context.
+ lease.hwaddr_.reset();
+ lease.setContext(ConstElementPtr());
+ expected.str("");
+ expected << "Type: IA_NA(" << static_cast<int>(Lease::TYPE_NA) << ")\n"
+ << "Address: 2001:db8::1\n"
+ << "Prefix length: 128\n"
+ << "IAID: 123456\n"
+ << "Pref life: 400\n"
+ << "Valid life: 800\n"
+ << "Cltt: 12345678\n"
+ << "DUID: 00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\n"
+ << "Hardware addr: (none)\n"
+ << "Subnet ID: 5678\n"
+ << "Pool ID: 0\n"
+ << "State: declined\n";
+ EXPECT_EQ(expected.str(), lease.toText());
+}
+
+// Verify that Lease6 structure can be converted to JSON properly.
+// This tests an address lease conversion.
+TEST(Lease6Test, toElementAddress) {
+
+ HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ Lease6 lease(Lease::TYPE_NA, IOAddress("2001:db8::1"), duid, 123456,
+ 400, 800, 5678, hwaddr, 128);
+ lease.cltt_ = 12345678;
+ lease.state_ = Lease::STATE_DECLINED;
+ lease.hostname_ = "urania.example.org";
+ lease.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ std::string expected = "{"
+ "\"cltt\": 12345678,"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"2001:db8::1\","
+ "\"preferred-lft\": 400,"
+ "\"state\": 1,"
+ "\"subnet-id\": 5678,"
+ "\"type\": \"IA_NA\","
+ "\"user-context\": { \"foobar\": 1234 },"
+ "\"valid-lft\": 800"
+ "}";
+
+ runToElementTest<Lease6>(expected, lease);
+
+ // Now let's try with a lease without hardware address and user context.
+ lease.hwaddr_.reset();
+ lease.setContext(ConstElementPtr());
+ lease.pool_id_ = 5;
+
+ expected = "{"
+ "\"cltt\": 12345678,"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"urania.example.org\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"2001:db8::1\","
+ "\"preferred-lft\": 400,"
+ "\"state\": 1,"
+ "\"subnet-id\": 5678,"
+ "\"pool-id\": 5,"
+ "\"type\": \"IA_NA\","
+ "\"valid-lft\": 800"
+ "}";
+
+ runToElementTest<Lease6>(expected, lease);
+
+ // And to finish try with a comment.
+ lease.setContext(Element::fromJSON("{ \"comment\": \"a comment\" }"));
+
+ expected = "{"
+ "\"cltt\": 12345678,"
+ "\"user-context\": { \"comment\": \"a comment\" },"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"urania.example.org\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"2001:db8::1\","
+ "\"preferred-lft\": 400,"
+ "\"state\": 1,"
+ "\"subnet-id\": 5678,"
+ "\"pool-id\": 5,"
+ "\"type\": \"IA_NA\","
+ "\"valid-lft\": 800"
+ "}";
+
+ runToElementTest<Lease6>(expected, lease);
+}
+
+// Verify that Lease6 structure can be converted to JSON properly.
+// This tests an address lease conversion.
+TEST(Lease6Test, toElementPrefix) {
+
+ HWAddrPtr hwaddr(new HWAddr(HWADDR, sizeof(HWADDR), HTYPE_ETHER));
+
+ uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(llt, sizeof(llt)));
+
+ Lease6 lease(Lease::TYPE_PD, IOAddress("2001:db8::"), duid, 123456,
+ 400, 800, 5678, hwaddr, 56);
+ lease.cltt_ = 12345678;
+ lease.state_ = Lease::STATE_DEFAULT;
+ lease.hostname_ = "urania.example.org";
+ lease.setContext(Element::fromJSON("{ \"foobar\": 1234 }"));
+
+ ElementPtr l = lease.toElement();
+
+ ASSERT_TRUE(l);
+
+ ASSERT_TRUE(l->contains("ip-address"));
+ EXPECT_EQ("2001:db8::", l->get("ip-address")->stringValue());
+
+ ASSERT_TRUE(l->contains("type"));
+ EXPECT_EQ("IA_PD", l->get("type")->stringValue());
+
+ // This is a prefix lease, it must have a prefix length.
+ ASSERT_TRUE(l->contains("prefix-len"));
+ EXPECT_EQ(56, l->get("prefix-len")->intValue());
+
+ ASSERT_TRUE(l->contains("iaid"));
+ EXPECT_EQ(123456, l->get("iaid")->intValue());
+
+ ASSERT_TRUE(l->contains("preferred-lft"));
+ EXPECT_EQ(400, l->get("preferred-lft")->intValue());
+
+ ASSERT_TRUE(l->contains("valid-lft"));
+ EXPECT_EQ(800, l->get("valid-lft")->intValue());
+
+ ASSERT_TRUE(l->contains("duid"));
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f",
+ l->get("duid")->stringValue());
+
+ ASSERT_TRUE(l->contains("hw-address"));
+ EXPECT_EQ(hwaddr->toText(false), l->get("hw-address")->stringValue());
+
+ ASSERT_TRUE(l->contains("subnet-id"));
+ EXPECT_EQ(5678, l->get("subnet-id")->intValue());
+
+ ASSERT_TRUE(l->contains("state"));
+ EXPECT_EQ(static_cast<int>(Lease::STATE_DEFAULT),
+ l->get("state")->intValue());
+
+ ASSERT_TRUE(l->contains("fqdn-fwd"));
+ EXPECT_FALSE(l->get("fqdn-fwd")->boolValue());
+
+ ASSERT_TRUE(l->contains("fqdn-rev"));
+ EXPECT_FALSE(l->get("fqdn-rev")->boolValue());
+
+ ASSERT_TRUE(l->contains("hostname"));
+ EXPECT_EQ("urania.example.org", l->get("hostname")->stringValue());
+
+ ASSERT_TRUE(l->contains("user-context"));
+ EXPECT_EQ("{ \"foobar\": 1234 }", l->get("user-context")->str());
+
+ ASSERT_FALSE(l->contains("pool-id"));
+
+ // Now let's try with a lease without hardware address or user context.
+ lease.hwaddr_.reset();
+ lease.setContext(ConstElementPtr());
+ lease.pool_id_ = 5;
+
+ l = lease.toElement();
+ EXPECT_FALSE(l->contains("hw-address"));
+ EXPECT_FALSE(l->contains("user-context"));
+
+ ASSERT_TRUE(l->contains("pool-id"));
+ EXPECT_EQ(5, l->get("pool-id")->intValue());
+
+ // And to finish try with a comment.
+ lease.setContext(Element::fromJSON("{ \"comment\": \"a comment\" }"));
+
+ l = lease.toElement();
+ EXPECT_FALSE(l->contains("hw-address"));
+ ConstElementPtr ctx = l->get("user-context");
+ ASSERT_TRUE(ctx);
+ ASSERT_EQ(Element::map, ctx->getType());
+ EXPECT_EQ(1, ctx->size());
+ ASSERT_TRUE(ctx->contains("comment"));
+ EXPECT_EQ("a comment", ctx->get("comment")->stringValue());
+
+ ASSERT_TRUE(l->contains("pool-id"));
+ EXPECT_EQ(5, l->get("pool-id")->intValue());
+}
+
+// Verify that the IA_NA can be created from JSON.
+TEST(Lease6Test, fromElementNA) {
+ std::string json = "{"
+ "\"cltt\": 12345678,"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"urania.EXAMPLE.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"2001:db8::1\","
+ "\"preferred-lft\": 400,"
+ "\"state\": 1,"
+ "\"subnet-id\": 5678,"
+ "\"pool-id\": 5,"
+ "\"type\": \"IA_NA\","
+ "\"user-context\": { \"foobar\": 1234 },"
+ "\"valid-lft\": 800"
+ "}";
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = Lease6::fromElement(Element::fromJSON(json)));
+
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("2001:db8::1", lease->addr_.toText());
+ EXPECT_EQ(5678, static_cast<uint32_t>(lease->subnet_id_));
+ EXPECT_EQ(5, static_cast<uint32_t>(lease->pool_id_));
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ("hwtype=1 08:00:2b:02:3f:4e", lease->hwaddr_->toText());
+ EXPECT_EQ(12345678, lease->cltt_);
+ EXPECT_EQ(lease->cltt_, lease->current_cltt_);
+ EXPECT_EQ(800, lease->valid_lft_);
+ EXPECT_EQ(lease->valid_lft_, lease->current_valid_lft_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_EQ("urania.example.org", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_DECLINED, lease->state_);
+ ASSERT_TRUE(lease->getContext());
+ EXPECT_EQ("{ \"foobar\": 1234 }", lease->getContext()->str());
+
+ // IPv6 specific properties.
+ EXPECT_EQ(Lease::TYPE_NA, lease->type_);
+ EXPECT_EQ(128, lease->prefixlen_);
+ EXPECT_EQ(123456, lease->iaid_);
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
+ EXPECT_EQ(400, lease->preferred_lft_);
+}
+
+// Verify that the IA_PD can be created from JSON.
+TEST(Lease6Test, fromElementPD) {
+ std::string json = "{"
+ "\"cltt\": 12345678,"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"uraniA.exaMple.orG\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"3000::\","
+ "\"preferred-lft\": 400,"
+ "\"prefix-len\": 32,"
+ "\"state\": 0,"
+ "\"subnet-id\": 1234,"
+ "\"pool-id\": 5,"
+ "\"type\": \"IA_PD\","
+ "\"valid-lft\": 600"
+ "}";
+
+ Lease6Ptr lease;
+ ASSERT_NO_THROW(lease = Lease6::fromElement(Element::fromJSON(json)));
+
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("3000::", lease->addr_.toText());
+ EXPECT_EQ(1234, static_cast<uint32_t>(lease->subnet_id_));
+ EXPECT_EQ(5, static_cast<uint32_t>(lease->pool_id_));
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ("hwtype=1 08:00:2b:02:3f:4e", lease->hwaddr_->toText());
+ EXPECT_EQ(12345678, lease->cltt_);
+ EXPECT_EQ(lease->cltt_, lease->current_cltt_);
+ EXPECT_EQ(600, lease->valid_lft_);
+ EXPECT_EQ(lease->valid_lft_, lease->current_valid_lft_);
+ EXPECT_FALSE(lease->fqdn_fwd_);
+ EXPECT_FALSE(lease->fqdn_rev_);
+ EXPECT_EQ("urania.example.org", lease->hostname_);
+ EXPECT_EQ(Lease::STATE_DEFAULT , lease->state_);
+ EXPECT_FALSE(lease->getContext());
+
+ // IPv6 specific properties.
+ EXPECT_EQ(Lease::TYPE_PD, lease->type_);
+ EXPECT_EQ(32, static_cast<int>(lease->prefixlen_));
+ EXPECT_EQ(123456, lease->iaid_);
+ ASSERT_TRUE(lease->duid_);
+ EXPECT_EQ("00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f", lease->duid_->toText());
+ EXPECT_EQ(400, lease->preferred_lft_);
+}
+
+// Test that specifying invalid values for a lease or not specifying
+// mandatory lease parameters causes an error while parsing the lease.
+TEST(Lease6Test, fromElementInvalidValues) {
+ // Create valid configuration. We use it as a base from which we will
+ // be removing some of the parameters and some values will be selectively
+ // modified.
+ std::string json = "{"
+ "\"cltt\": 12345678,"
+ "\"duid\": \"00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f\","
+ "\"fqdn-fwd\": false,"
+ "\"fqdn-rev\": false,"
+ "\"hostname\": \"urania.example.org\","
+ "\"hw-address\": \"08:00:2b:02:3f:4e\","
+ "\"iaid\": 123456,"
+ "\"ip-address\": \"3000::\","
+ "\"preferred-lft\": 400,"
+ "\"prefix-len\": 32,"
+ "\"state\": 0,"
+ "\"subnet-id\": 1234,"
+ "\"type\": \"IA_PD\","
+ "\"valid-lft\": 600"
+ "}";
+
+ // Test invalid parameter values and missing parameters.
+ testInvalidElement<Lease6>(json, "cltt", std::string("xyz"));
+ testInvalidElement<Lease6>(json, "cltt", -1, false);
+ testInvalidElement<Lease6>(json, "duid", "01::00::");
+ testInvalidElement<Lease6>(json, "duid", 1234, false);
+ testInvalidElement<Lease6>(json, "fqdn-fwd", 123);
+ testInvalidElement<Lease6>(json, "fqdn-rev", std::string("foo"));
+ testInvalidElement<Lease6, bool>(json, "hostname", true);
+ testInvalidElement<Lease6>(json, "hw-address", "01::00::", false);
+ testInvalidElement<Lease6>(json, "hw-address", 1234, false);
+ testInvalidElement<Lease6>(json, "iaid", std::string("1234"));
+ testInvalidElement<Lease6>(json, "iaid", -1);
+ testInvalidElement<Lease6, long int>(json, "ip-address", 0xFF000201);
+ testInvalidElement<Lease6>(json, "ip-address", "192.0.2.0", false);
+ testInvalidElement<Lease6>(json, "preferred-lft", std::string("1234"));
+ testInvalidElement<Lease6>(json, "preferred-lft", -1, false);
+ testInvalidElement<Lease6>(json, "prefix-len", std::string("1234"));
+ testInvalidElement<Lease6>(json, "prefix-len", 130);
+ testInvalidElement<Lease6>(json, "state", std::string("xyz"));
+ testInvalidElement<Lease6>(json, "state", 1234, false);
+ testInvalidElement<Lease6>(json, "subnet-id", std::string("xyz"));
+ testInvalidElement<Lease6>(json, "subnet-id", -5, false);
+ testInvalidElement<Lease6>(json, "subnet-id", 0x100000000, false);
+ testInvalidElement<Lease6>(json, "pool-id", std::string("xyz"), false);
+ testInvalidElement<Lease6>(json, "pool-id", -5, false);
+ testInvalidElement<Lease6>(json, "pool-id", 0, false);
+ testInvalidElement<Lease6>(json, "pool-id", 0x100000000, false);
+ testInvalidElement<Lease6>(json, "type", std::string("IA_XY"));
+ testInvalidElement<Lease6>(json, "type", -3, false);
+ testInvalidElement<Lease6>(json, "valid-lft", std::string("xyz"));
+ testInvalidElement<Lease6>(json, "valid-lft", -3, false);
+ testInvalidElement<Lease6>(json, "user-context", "[ ]", false);
+ testInvalidElement<Lease6>(json, "user-context", 1234, false);
+ testInvalidElement<Lease6>(json, "user-context", false, false);
+ testInvalidElement<Lease6>(json, "user-context", "foo", false);
+}
+
+// Verify that the lease states are correctly returned in the textual format.
+TEST(Lease6Test, stateToText) {
+ EXPECT_EQ("default", Lease6::statesToText(Lease::STATE_DEFAULT));
+ EXPECT_EQ("declined", Lease6::statesToText(Lease::STATE_DECLINED));
+ EXPECT_EQ("expired-reclaimed", Lease6::statesToText(Lease::STATE_EXPIRED_RECLAIMED));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_extended_info_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_extended_info_unittest.cc
new file mode 100644
index 0000000..4b1efcd
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/memfile_lease_extended_info_unittest.cc
@@ -0,0 +1,1729 @@
+// Copyright (C) 2022-2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief IPv4 addresses used in the tests.
+const vector<string> ADDRESS4 = {
+ "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+ "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7"
+};
+
+/// @brief IPv6 addresses used in the tests.
+const vector<string> ADDRESS6 = {
+ "2001:db8::", "2001:db8::1", "2001:db8::2", "2001:db8::3",
+ "2001:db8::4", "2001:db8::5", "2001:db8::6", "2001:db8::7"
+};
+
+/// @brief DUIDs used in the tests.
+const vector<string> DUIDS = {
+ "wwwwwwww", "BBBBBBBB", "::::::::", "0123456789acdef",
+ "BBBBBBBB", "$$$$$$$$", "^^^^^^^^", "\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5"
+};
+
+/// @brief A derivation of the lease manager exposing protected methods.
+class NakedMemfileLeaseMgr : public Memfile_LeaseMgr {
+public:
+ /// @brief Constructor.
+ ///
+ /// Creates an instance of the lease manager.
+ ///
+ /// @param parameters Parameter map.
+ NakedMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+ : Memfile_LeaseMgr(parameters) {
+ }
+
+ /// @brief Destructor.
+ virtual ~NakedMemfileLeaseMgr() {
+ }
+
+ /// @brief Exposes protected methods and members.
+ using LeaseMgr::setExtendedInfoTablesEnabled;
+ using Memfile_LeaseMgr::relay_id6_;
+ using Memfile_LeaseMgr::remote_id6_;
+ using Memfile_LeaseMgr::deleteExtendedInfo6;
+ using Memfile_LeaseMgr::addRelayId6;
+ using Memfile_LeaseMgr::addRemoteId6;
+};
+
+/// @brief Type of unique pointers to naked lease manager.
+typedef unique_ptr<NakedMemfileLeaseMgr> NakedMemfileLeaseMgrPtr;
+
+/// @brief Test fixture class for extended info tests.
+class MemfileExtendedInfoTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ MemfileExtendedInfoTest() {
+ pmap_.clear();
+ lease_mgr_.reset();
+ leases4.clear();
+ leases6.clear();
+ MultiThreadingMgr::instance().setMode(false);
+ now_ = time(0);
+ }
+
+ /// @brief Destructor.
+ ~MemfileExtendedInfoTest() {
+ pmap_.clear();
+ lease_mgr_.reset();
+ leases4.clear();
+ leases6.clear();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Start lease manager.
+ ///
+ /// @param universe Universe (@c Memfile_LeaseMgr::V4 or
+ // @c Memfile_LeaseMgr::v6).
+ void start(Memfile_LeaseMgr::Universe u) {
+ pmap_["universe"] = (u == Memfile_LeaseMgr::V4 ? "4" : "6");
+ pmap_["persist"] = "false";
+ if (u == Memfile_LeaseMgr::V6) {
+ pmap_["extended-info-tables"] = "true";
+ }
+
+ ASSERT_NO_THROW(lease_mgr_.reset(new NakedMemfileLeaseMgr(pmap_)));
+ if (u == Memfile_LeaseMgr::V6) {
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+ }
+ }
+
+ /// @brief Create and set v4 leases.
+ ///
+ /// @param insert When true insert in the database.
+ void initLease4(bool insert = true) {
+ ASSERT_EQ(ADDRESS4.size(), DUIDS.size());
+ for (size_t i = 0; i < ADDRESS4.size(); ++i) {
+ Lease4Ptr lease;
+ vector<uint8_t> hwaddr_data(5, 0x08);
+ hwaddr_data.push_back(0x80 + i);
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_data, HTYPE_ETHER));
+ vector<uint8_t> client_id = createFromString(DUIDS[i]);
+ IOAddress address(ADDRESS4[i]);
+ ASSERT_NO_THROW(lease.reset(new Lease4(address, hwaddr,
+ &client_id[0],
+ client_id.size(),
+ 1000, now_,
+ static_cast<SubnetID>(i))));
+ leases4.push_back(lease);
+ if (insert) {
+ EXPECT_TRUE(lease_mgr_->addLease(lease));
+ }
+ }
+ ASSERT_EQ(ADDRESS4.size(), leases4.size());
+ }
+
+ /// @brief Create and set v6 leases.
+ void initLease6() {
+ ASSERT_EQ(ADDRESS6.size(), DUIDS.size());
+ for (size_t i = 0; i < ADDRESS6.size(); ++i) {
+ Lease6Ptr lease;
+ vector<uint8_t> duid_data = createFromString(DUIDS[i]);
+ DuidPtr duid(new DUID(duid_data));
+ IOAddress addr(ADDRESS6[i]);
+ ASSERT_NO_THROW(lease.reset(new Lease6(((i % 2) ? Lease::TYPE_NA : Lease::TYPE_PD), addr, duid,
+ 123, 1000, 2000,
+ static_cast<SubnetID>(i))));
+ leases6.push_back(lease);
+ EXPECT_TRUE(lease_mgr_->addLease(lease));
+ }
+ ASSERT_EQ(ADDRESS6.size(), leases6.size());
+ }
+
+ /// @brief Create a vector of uint8_t from a string.
+ ///
+ /// @param content A not empty string holding the content.
+ /// @return A vector of uint8_t with the given content.
+ inline vector<uint8_t> createFromString(const string& content) {
+ vector<uint8_t> v;
+ v.resize(content.size());
+ memmove(&v[0], &content[0], v.size());
+ return (v);
+ }
+
+ /// @brief Test initLease4.
+ void testInitLease4();
+
+ /// @brief Test initLease6.
+ void testInitLease6();
+
+ /// @brief Test getLease4ByRelayId.
+ void testGetLeases4ByRelayId();
+
+ /// @brief Test getLease4ByRemoteId.
+ void testGetLeases4ByRemoteId();
+
+ /// @brief Test getLeases6ByRelayId.
+ void testGetLeases6ByRelayId();
+
+ /// @brief Test getLeases6ByRemoteId.
+ void testGetLeases6ByRemoteId();
+
+ /// @brief Test getLeases6ByLink.
+ void testGetLeases6ByLink();
+
+ /// @brief Parameter map.
+ DatabaseConnection::ParameterMap pmap_;
+
+ /// @brief Lease manager.
+ NakedMemfileLeaseMgrPtr lease_mgr_;
+
+ /// @brief V4 leases.
+ Lease4Collection leases4;
+
+ /// @brief V6 leases.
+ Lease6Collection leases6;
+
+ /// @brief Current timestamp.
+ time_t now_;
+};
+
+/// @brief Verifies that the lease manager can start in V4.
+TEST_F(MemfileExtendedInfoTest, startV4) {
+ start(Memfile_LeaseMgr::V4);
+}
+
+/// @brief Verifies that the lease manager can start in V4 with MT.
+TEST_F(MemfileExtendedInfoTest, startV4MultiThreading) {
+ MultiThreadingTest mt(true);
+ start(Memfile_LeaseMgr::V4);
+}
+
+/// @brief Verifies that the lease manager can add the v4 leases.
+void
+MemfileExtendedInfoTest::testInitLease4() {
+ start(Memfile_LeaseMgr::V4);
+ initLease4();
+ EXPECT_EQ(8, leases4.size());
+ Lease4Collection got;
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4());
+ ASSERT_EQ(leases4.size(), got.size());
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ ConstElementPtr expected = leases4[i]->toElement();
+ LeasePtr lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(expected->equals(*lease->toElement()))
+ << "expected: " << expected->str() << "\n"
+ << "got: " << lease->toElement()->str() << "\n";
+ }
+}
+
+TEST_F(MemfileExtendedInfoTest, initLease4) {
+ testInitLease4();
+}
+
+TEST_F(MemfileExtendedInfoTest, initLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testInitLease4();
+}
+
+/// @brief Verifies that getLeases4ByRelayId works as expected.
+void
+MemfileExtendedInfoTest::testGetLeases4ByRelayId() {
+ // Lease manager is created with empty tables.
+ start(Memfile_LeaseMgr::V4);
+ initLease4(false);
+
+ // Create leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> relay_id0 = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> relay_id1 = { 1, 2, 3, 4 };
+ vector<uint8_t> relay_id2 = createFromString(DUIDS[2]);
+ string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt0 += " \"sub-options\": \"0C03AABBCC\",";
+ user_context_txt0 += " \"relay-id\": \"AABBCC\" } } }";
+ ElementPtr user_context0;
+ ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+ string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt1 += " \"sub-options\": \"0C0401020304\",";
+ user_context_txt1 += " \"relay-id\": \"01020304\" } } }";
+ ElementPtr user_context1;
+ ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+ Lease4Ptr lease;
+ // lease0: addr0, id0, now.
+ lease = leases4[0];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+
+ // lease1: addr1, id1, now.
+ lease = leases4[1];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id1;
+ lease->setContext(user_context1);
+
+ // lease2: addr2, id0, now - 500.
+ lease = leases4[2];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 500;
+
+ // lease3: addr3, id0, now - 800.
+ lease = leases4[3];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 800;
+
+ // lease4: addr4, id0, now - 100.
+ lease = leases4[4];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 100;
+
+ // Add leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_TRUE(lease_mgr_->addLease(leases4[i]));
+ }
+
+ Lease4Collection got;
+ // Unknown relay id #2: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown relay id #2, now - 1000, now + 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+ zero,
+ LeasePageSize(100),
+ now_ - 1000,
+ now_ + 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, now - 2000, now - 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 2000,
+ now_ - 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, now + 1000, now + 2000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ + 1000,
+ now_ + 2000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0: 3 entries (0, 2, 3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(4, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[3];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, partial: 2 entries (0, 2).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, partial from previous: 2 entries (3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr2,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, final partial: no entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr4,
+ LeasePageSize(2)));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, from now - 500: 3 entries (0, 2, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 500));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, to now - 200: 3 entries (2, 3).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ 0, now_ - 200));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr2,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, final partial.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr4,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases4ByRelayId) {
+ testGetLeases4ByRelayId();
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases4ByRelayIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4ByRelayId();
+}
+
+/// @brief Verifies that getLeases4ByRemoteId works as expected.
+void
+MemfileExtendedInfoTest::testGetLeases4ByRemoteId() {
+ // Lease manager is created with empty tables.
+ start(Memfile_LeaseMgr::V4);
+ initLease4(true);
+
+ // Update leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> remote_id0 = { 1, 2, 3, 4 };
+ vector<uint8_t> remote_id1 = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> remote_id2 = createFromString(DUIDS[2]);
+ string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt0 += " \"sub-options\": \"020401020304\",";
+ user_context_txt0 += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context0;
+ ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+ string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt1 += " \"sub-options\": \"0203AABBCC\",";
+ user_context_txt1 += " \"remote-id\": \"AABBCC\" } } }";
+ ElementPtr user_context1;
+ ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+ // The multi-index requires to keep all fields used as indexes read only
+ // so instead of modifying members of leases4 first take a copy on them.
+ // If you do not do this the multi-index replace operation which implements
+ // the updateLease4 method will fail to update all modified indexes
+ // because some will have the same value as in the stored object...
+
+ Lease4Ptr lease;
+ // lease0: addr0, id0, now.
+ lease.reset(new Lease4(*leases4[0]));
+ leases4[0] = lease;
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+
+ // lease1: addr1, id1, now.
+ lease.reset(new Lease4(*leases4[1]));
+ leases4[1] = lease;
+ lease->remote_id_ = remote_id1;
+ lease->setContext(user_context1);
+
+ // lease2: addr2, id0, now - 500.
+ lease.reset(new Lease4(*leases4[2]));
+ leases4[2] = lease;
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 500;
+
+ // lease3: addr3, id0, now - 800.
+ lease.reset(new Lease4(*leases4[3]));
+ leases4[3] = lease;
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 800;
+
+ // lease4: addr4, id0, now - 100.
+ lease.reset(new Lease4(*leases4[4]));
+ leases4[4] = lease;
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 100;
+
+ // Update leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_NO_THROW(lease_mgr_->updateLease4(leases4[i]));
+ }
+
+ Lease4Collection got;
+ // Unknown remote id #2: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown remote id #2, now - 1000, now + 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+ zero,
+ LeasePageSize(100),
+ now_ - 1000,
+ now_ + 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, now - 2000, now - 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 2000,
+ now_ - 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, now + 1000, now + 2000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ + 1000,
+ now_ + 2000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0: 3 entries (0, 2, 3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(4, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[3];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, partial: 2 entries (0, 2).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, partial from previous: 2 entries (3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr2,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, final partial: no entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr4,
+ LeasePageSize(2)));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, from now - 500: 3 entries (0, 2, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 500));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, to now - 200: 3 entries (2, 3).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ 0, now_ - 200));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr2,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, final partial.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr4,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases4ByRemoteId) {
+ testGetLeases4ByRemoteId();
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases4ByRemoteIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4ByRemoteId();
+}
+
+/// @brief Verifies that the lease manager can start in V6.
+TEST_F(MemfileExtendedInfoTest, startV6) {
+ start(Memfile_LeaseMgr::V6);
+}
+
+/// @brief Verifies that the lease manager can start in V6 with MT.
+TEST_F(MemfileExtendedInfoTest, startV6MultiThreading) {
+ MultiThreadingTest mt(true);
+ start(Memfile_LeaseMgr::V6);
+}
+
+/// @brief Verifies that the lease manager can add the v6 leases.
+void
+MemfileExtendedInfoTest::testInitLease6() {
+ start(Memfile_LeaseMgr::V6);
+ initLease6();
+ EXPECT_EQ(8, leases6.size());
+ Lease6Collection got;
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6());
+ ASSERT_EQ(leases6.size(), got.size());
+ for (size_t i = 0; i < leases6.size(); ++i) {
+ ConstElementPtr expected = leases6[i]->toElement();
+ LeasePtr lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(expected->equals(*lease->toElement()))
+ << "expected: " << expected->str() << "\n"
+ << "got: " << lease->toElement()->str() << "\n";
+ }
+}
+
+TEST_F(MemfileExtendedInfoTest, initLease6) {
+ testInitLease6();
+}
+
+TEST_F(MemfileExtendedInfoTest, initLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testInitLease6();
+}
+
+/// @brief Verifies that add and delete work on the by relay id table.
+TEST_F(MemfileExtendedInfoTest, relayIdTable6) {
+ // Lease manager is created with empty tables.
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_EQ(0, lease_mgr_->relay_id6_.size());
+
+ // Create parameter values.
+ IOAddress lease_addr0(ADDRESS6[0]);
+ IOAddress lease_addr1(ADDRESS6[1]);
+ IOAddress lease_addr2(ADDRESS6[2]);
+ IOAddress other_lease_addr("2001:db8:1::4");
+ vector<uint8_t> relay_id0 = createFromString(DUIDS[0]);
+ vector<uint8_t> relay_id1 = createFromString(DUIDS[1]);
+
+ // Fill the table.
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr0, relay_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr0, relay_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr0, relay_id1));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr1, relay_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr1, relay_id1));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr2, relay_id1));
+
+ // Check delete.
+ EXPECT_EQ(6, lease_mgr_->relay_id6_.size());
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(other_lease_addr));
+ // No match so doing nothing.
+ EXPECT_EQ(6, lease_mgr_->relay_id6_.size());
+
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr0));
+ // Removed 3 entries.
+ EXPECT_EQ(3, lease_mgr_->relay_id6_.size());
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr0));
+ // Already removed: doing nothing again.
+ EXPECT_EQ(3, lease_mgr_->relay_id6_.size());
+
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr1));
+ // Removed 2 entries.
+ EXPECT_EQ(1, lease_mgr_->relay_id6_.size());
+
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr2));
+ // Removed last entry.
+ EXPECT_EQ(0, lease_mgr_->relay_id6_.size());
+}
+
+/// @brief Verifies that add and delete work on the by remote id table.
+TEST_F(MemfileExtendedInfoTest, remoteIdTable6) {
+ // Lease manager is created with empty tables.
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_EQ(0, lease_mgr_->remote_id6_.size());
+
+ // Create parameter values.
+ IOAddress lease_addr0(ADDRESS6[0]);
+ IOAddress lease_addr1(ADDRESS6[1]);
+ IOAddress lease_addr2(ADDRESS6[2]);
+ IOAddress other_lease_addr("2001:db8:1::4");
+ vector<uint8_t> remote_id0 = createFromString(DUIDS[0]);
+ vector<uint8_t> remote_id1 = createFromString(DUIDS[1]);
+
+ // Fill the table.
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr0, remote_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr0, remote_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr0, remote_id1));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr1, remote_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr1, remote_id1));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr2, remote_id1));
+
+ // Check delete.
+ EXPECT_EQ(6, lease_mgr_->remote_id6_.size());
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(other_lease_addr));
+ // No match so doing nothing.
+ EXPECT_EQ(6, lease_mgr_->remote_id6_.size());
+
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr0));
+ // Removed 3 entries.
+ EXPECT_EQ(3, lease_mgr_->remote_id6_.size());
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr0));
+ // Already removed: doing nothing again.
+ EXPECT_EQ(3, lease_mgr_->remote_id6_.size());
+
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr1));
+ // Removed 2 entries.
+ EXPECT_EQ(1, lease_mgr_->remote_id6_.size());
+
+ EXPECT_NO_THROW(lease_mgr_->deleteExtendedInfo6(lease_addr2));
+ // Removed last entry.
+ EXPECT_EQ(0, lease_mgr_->remote_id6_.size());
+}
+
+/// @brief Verifies that getLeases6ByRelayId works as expected.
+void
+MemfileExtendedInfoTest::testGetLeases6ByRelayId() {
+ // Lease manager is created with empty tables.
+ start(Memfile_LeaseMgr::V6);
+ initLease6();
+ EXPECT_EQ(0, lease_mgr_->relay_id6_.size());
+
+ // Create parameter values.
+ IOAddress lease_addr0(ADDRESS6[0]);
+ IOAddress lease_addr1(ADDRESS6[1]);
+ IOAddress lease_addr2(ADDRESS6[2]);
+ IOAddress link_addr(ADDRESS6[4]);
+ IOAddress other_link_addr("2001:db8:1::4");
+ IOAddress zero = IOAddress::IPV6_ZERO_ADDRESS();
+ vector<uint8_t> relay_id_data0 = createFromString(DUIDS[0]);
+ DUID relay_id0(relay_id_data0);
+ vector<uint8_t> relay_id_data1 = createFromString(DUIDS[1]);
+ DUID relay_id1(relay_id_data1);
+ vector<uint8_t> relay_id_data2 = createFromString(DUIDS[2]);
+ DUID relay_id2(relay_id_data2);
+
+ // Fill the table.
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr0, relay_id_data0));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr0, relay_id_data0));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr0, relay_id_data1));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr1, relay_id_data0));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr1, relay_id_data1));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr2, relay_id_data1));
+ EXPECT_EQ(6, lease_mgr_->relay_id6_.size());
+
+ Lease6Collection got;
+ // Unknown relay id #2, no link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id2,
+ zero,
+ 0,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown relay id #2, link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id2,
+ link_addr,
+ 64,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, other link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id0,
+ other_link_addr,
+ 64,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, no link: 3 entries but 2 addresses.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id0,
+ zero,
+ 0,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(2, got.size());
+ Lease6Ptr lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+
+ // Relay id #1, no link, partial: 2 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id1,
+ zero,
+ 0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+
+ // Relay id #1, no link, partial from previous: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id1,
+ zero,
+ 0,
+ lease->addr_,
+ LeasePageSize(2)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr2, lease->addr_);
+
+ // Add another entry for last tests.
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr0, relay_id_data1));
+ EXPECT_EQ(7, lease_mgr_->relay_id6_.size());
+
+ // Relay id #1, link: 3 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id1,
+ link_addr,
+ 64,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr2, lease->addr_);
+
+ // Relay id #1, link, initial partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id1,
+ link_addr,
+ 64,
+ zero,
+ LeasePageSize(1)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+
+ // Relay id #1, link, next partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id1,
+ link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(1)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+
+ // Relay id #1, link, next partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id1,
+ link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(1)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr2, lease->addr_);
+
+ // Relay id #1, link, final partial: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRelayId(relay_id1,
+ link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(1)));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases6ByRelayId) {
+ testGetLeases6ByRelayId();
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases6ByRelayIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6ByRelayId();
+}
+
+/// @brief Verifies that getLeases6ByRemoteId works as expected.
+void
+MemfileExtendedInfoTest::testGetLeases6ByRemoteId() {
+ // Lease manager is created with empty tables.
+ start(Memfile_LeaseMgr::V6);
+ initLease6();
+ EXPECT_EQ(0, lease_mgr_->remote_id6_.size());
+
+ // Create parameter values.
+ IOAddress lease_addr0(ADDRESS6[0]);
+ IOAddress lease_addr1(ADDRESS6[1]);
+ IOAddress lease_addr2(ADDRESS6[2]);
+ IOAddress link_addr(ADDRESS6[4]);
+ IOAddress other_link_addr("2001:db8:1::4");
+ IOAddress zero = IOAddress::IPV6_ZERO_ADDRESS();
+ vector<uint8_t> remote_id0 = createFromString(DUIDS[0]);
+ vector<uint8_t> remote_id1 = createFromString(DUIDS[1]);
+ vector<uint8_t> remote_id2 = createFromString(DUIDS[2]);
+
+ // Fill the table.
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr0, remote_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr0, remote_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr0, remote_id1));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr1, remote_id0));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr1, remote_id1));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr2, remote_id1));
+ EXPECT_EQ(6, lease_mgr_->remote_id6_.size());
+
+ Lease6Collection got;
+ // Unknown remote id #2, no link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id2,
+ zero,
+ 0,
+ zero,
+ LeasePageSize(10)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown remote id #2, link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id2,
+ link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, other link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id0,
+ other_link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, no link: 3 entries but 2 addresses.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id0,
+ zero,
+ 0,
+ zero,
+ LeasePageSize(10)));
+ ASSERT_EQ(2, got.size());
+ Lease6Ptr lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+
+ // Remote id #1, no link, partial: 2 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id1,
+ zero,
+ 0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+
+ // Remote id #1, no link, partial from previous: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id1,
+ zero,
+ 0,
+ lease->addr_,
+ LeasePageSize(2)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr2, lease->addr_);
+
+ // Add another entry for last tests.
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr0, remote_id1));
+ EXPECT_EQ(7, lease_mgr_->remote_id6_.size());
+
+ // Remote id #1, link: 3 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id1,
+ link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr2, lease->addr_);
+
+ // Remote id #1, link, initial partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id1,
+ link_addr,
+ 64,
+ zero,
+ LeasePageSize(1)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr0, lease->addr_);
+
+ // Remote id #1, link, next partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id1,
+ link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(1)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr1, lease->addr_);
+
+ // Remote id #1, link, next partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id1,
+ link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(1)));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr2, lease->addr_);
+
+ // Remote id #1, link, final partial: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByRemoteId(remote_id1,
+ link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(1)));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases6ByRemoteId) {
+ testGetLeases6ByRemoteId();
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases6ByRemoteIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6ByRemoteId();
+}
+
+/// @brief Verifies that getLeases6ByLink works as expected.
+void
+MemfileExtendedInfoTest::testGetLeases6ByLink() {
+ // Lease manager is created with empty tables.
+ start(Memfile_LeaseMgr::V6);
+ initLease6();
+
+ // Create parameter values.
+ IOAddress link_addr(ADDRESS6[4]);
+ IOAddress other_link_addr("2001:db8:1::4");
+ IOAddress zero = IOAddress::IPV6_ZERO_ADDRESS();
+
+ Lease6Collection got;
+ // Other link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(other_link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+ EXPECT_EQ(0, got.size());
+
+ // Link: 8 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+
+ ASSERT_EQ(8, got.size());
+ Lease6Ptr lease;
+ for (size_t i = 0; i < 8; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i]), lease->addr_);
+ }
+
+ // Link: initial partial: 4 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ zero,
+ LeasePageSize(4)));
+ ASSERT_EQ(4, got.size());
+ for (size_t i = 0; i < 4; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i]), lease->addr_);
+ }
+
+ // Link: next partial: 4 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(4)));
+ ASSERT_EQ(4, got.size());
+ for (size_t i = 0; i < 4; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i + 4]), lease->addr_);
+ }
+
+ // Link: further partial: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(4)));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases6ByLink) {
+ testGetLeases6ByLink();
+}
+
+TEST_F(MemfileExtendedInfoTest, getLeases6ByLinkMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6ByLink();
+}
+
+/// @brief Verifies that v6 deleteLease removes references from extended
+/// info tables.
+TEST_F(MemfileExtendedInfoTest, deleteLease6) {
+ start(Memfile_LeaseMgr::V6);
+ initLease6();
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+
+ // Create parameter values.
+ IOAddress lease_addr(ADDRESS6[0]);
+ vector<uint8_t> relay_id = createFromString(DUIDS[0]);
+ vector<uint8_t> remote_id = createFromString(DUIDS[1]);
+ vector<uint8_t> relay_id2 = createFromString(DUIDS[2]);
+ vector<uint8_t> remote_id2 = createFromString(DUIDS[3]);
+
+ // Fill the table.
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr, relay_id));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr, remote_id));
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr, relay_id2));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr, remote_id2));
+
+ EXPECT_EQ(2, lease_mgr_->relay_id6_.size());
+ EXPECT_EQ(2, lease_mgr_->remote_id6_.size());
+
+ // Delete the second lease: no side effect on tables.
+ Lease6Ptr lease = leases6[1];
+ ASSERT_TRUE(lease);
+ EXPECT_NE(lease_addr, lease->addr_);
+ // Put a value different of the expected one.
+ lease->extended_info_action_ = Lease6::ACTION_UPDATE;
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->deleteLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+ EXPECT_EQ(2, lease_mgr_->relay_id6_.size());
+ EXPECT_EQ(2, lease_mgr_->remote_id6_.size());
+
+ // Delete the first lease: tables are cleared.
+ lease = leases6[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr, lease->addr_);
+ EXPECT_NO_THROW(ret = lease_mgr_->deleteLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_TRUE(lease_mgr_->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr_->remote_id6_.empty());
+}
+
+/// @brief Verifies that v6 deleteLease does not touch extended info tables
+/// when they are disabled.
+TEST_F(MemfileExtendedInfoTest, deleteLease6disabled) {
+ start(Memfile_LeaseMgr::V6);
+ initLease6();
+ lease_mgr_->setExtendedInfoTablesEnabled(false);
+
+ // Create parameter values.
+ IOAddress lease_addr(ADDRESS6[0]);
+ vector<uint8_t> relay_id = createFromString(DUIDS[0]);
+ vector<uint8_t> remote_id = createFromString(DUIDS[1]);
+
+ // Fill the table.
+ EXPECT_NO_THROW(lease_mgr_->addRelayId6(lease_addr, relay_id));
+ EXPECT_NO_THROW(lease_mgr_->addRemoteId6(lease_addr, remote_id));
+
+ EXPECT_EQ(1, lease_mgr_->relay_id6_.size());
+ EXPECT_EQ(1, lease_mgr_->remote_id6_.size());
+
+ // Delete the first lease: tables are no longer cleared.
+ Lease6Ptr lease = leases6[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease_addr, lease->addr_);
+ // Put a value different from the expected one.
+ lease->extended_info_action_ = Lease6::ACTION_UPDATE;
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->deleteLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+ EXPECT_EQ(1, lease_mgr_->relay_id6_.size());
+ EXPECT_EQ(1, lease_mgr_->remote_id6_.size());
+}
+
+/// @brief Verifies that v6 addLease adds references to extended info tables.
+TEST_F(MemfileExtendedInfoTest, addLease6) {
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+ EXPECT_TRUE(lease_mgr_->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr_->remote_id6_.empty());
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+ // Put a value different of the expected one.
+ lease->extended_info_action_ = Lease6::ACTION_DELETE;
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+
+ // Check extended info tables.
+ ASSERT_EQ(1, lease_mgr_->relay_id6_.size());
+ auto relay_id_it = lease_mgr_->relay_id6_.cbegin();
+ ASSERT_NE(relay_id_it, lease_mgr_->relay_id6_.cend());
+ Lease6ExtendedInfoPtr ex_info = *relay_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& relay_id = ex_info->id_;
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, relay_id);
+
+ ASSERT_EQ(1, lease_mgr_->remote_id6_.size());
+ auto remote_id_it = lease_mgr_->remote_id6_.cbegin();
+ ASSERT_NE(remote_id_it, lease_mgr_->remote_id6_.cend());
+ ex_info = *remote_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& remote_id = ex_info->id_;
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, remote_id);
+}
+
+/// @brief Verifies that v6 addLease does not touch extended info tables
+/// when they are disabled.
+TEST_F(MemfileExtendedInfoTest, addLease6disabled) {
+ start(Memfile_LeaseMgr::V6);
+ lease_mgr_->setExtendedInfoTablesEnabled(false);
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+ lease->extended_info_action_ = Lease6::ACTION_UPDATE;
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+ EXPECT_TRUE(lease_mgr_->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr_->remote_id6_.empty());
+}
+
+/// @brief Verifies that updateLease6 does not touch references to extended
+/// info tables when the action is ACTION_IGNORE.
+TEST_F(MemfileExtendedInfoTest, updateLease6ignore) {
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+
+ // Add the lease.
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+
+ // Set user context.
+ lease.reset(new Lease6(*lease));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+
+ // Set action and call updateLease6.
+ lease->extended_info_action_ = Lease6::ACTION_IGNORE;
+ ASSERT_NO_THROW(lease_mgr_->updateLease6(lease));
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+
+ // Tables were not touched.
+ EXPECT_TRUE(lease_mgr_->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr_->remote_id6_.empty());
+
+ // Note that with persist when the database is reloaded the user context
+ // is still there so tables will be updated: the ACTION_IGNORE setting
+ // has no persistent effect: instead cleanup the user context before
+ // calling updateLease6...
+}
+
+/// @brief Verifies that updateLease6 clears references from extended
+/// info tables when the action is ACTION_DELETE.
+TEST_F(MemfileExtendedInfoTest, updateLease6delete) {
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+
+ // Add the lease.
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(1, lease_mgr_->relay_id6_.size());
+ EXPECT_EQ(1, lease_mgr_->remote_id6_.size());
+
+ // Set action and call updateLease6.
+ lease.reset(new Lease6(*lease));
+ lease->extended_info_action_ = Lease6::ACTION_DELETE;;
+ ASSERT_NO_THROW(lease_mgr_->updateLease6(lease));
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+
+ // Tables were cleared.
+ EXPECT_TRUE(lease_mgr_->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr_->remote_id6_.empty());
+}
+
+/// @brief Verifies that updateLease6 does not clears references from extended
+/// info tables when the action is ACTION_DELETE but tables are disabled.
+TEST_F(MemfileExtendedInfoTest, updateLease6deleteDisabled) {
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+
+ // Add the lease.
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(1, lease_mgr_->relay_id6_.size());
+ EXPECT_EQ(1, lease_mgr_->remote_id6_.size());
+
+ // Disable on the fly extended info tables.
+ // Note it is a priori an illegal operation so this could have to be
+ // changed...
+ lease_mgr_->setExtendedInfoTablesEnabled(false);
+
+ // Set action and call updateLease6.
+ lease.reset(new Lease6(*lease));
+ lease->extended_info_action_ = Lease6::ACTION_DELETE;;
+ ASSERT_NO_THROW(lease_mgr_->updateLease6(lease));
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+
+ // Tables were not touched.
+ EXPECT_EQ(1, lease_mgr_->relay_id6_.size());
+ EXPECT_EQ(1, lease_mgr_->remote_id6_.size());
+}
+
+/// @brief Verifies that updateLease6 adds references to extended
+/// info tables when the action is ACTION_UPDATE.
+TEST_F(MemfileExtendedInfoTest, updateLease6update) {
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+
+ // Add the lease.
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+ EXPECT_TRUE(lease_mgr_->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr_->remote_id6_.empty());
+
+ // Set user context.
+ lease.reset(new Lease6(*lease));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+
+ // Set action and call updateLease6.
+ lease->extended_info_action_ = Lease6::ACTION_UPDATE;
+ ASSERT_NO_THROW(lease_mgr_->updateLease6(lease));
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+
+ // Tables were updated.
+ ASSERT_EQ(1, lease_mgr_->relay_id6_.size());
+ auto relay_id_it = lease_mgr_->relay_id6_.cbegin();
+ ASSERT_NE(relay_id_it, lease_mgr_->relay_id6_.cend());
+ Lease6ExtendedInfoPtr ex_info = *relay_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& relay_id = ex_info->id_;
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, relay_id);
+
+ ASSERT_EQ(1, lease_mgr_->remote_id6_.size());
+ auto remote_id_it = lease_mgr_->remote_id6_.cbegin();
+ ASSERT_NE(remote_id_it, lease_mgr_->remote_id6_.cend());
+ ex_info = *remote_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& remote_id = ex_info->id_;
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, remote_id);
+}
+
+/// @brief Verifies that updateLease6 does not add references to extended
+/// info tables when the action is ACTION_UPDATE but tables are disabled.
+TEST_F(MemfileExtendedInfoTest, updateLease6updateDisabled) {
+ start(Memfile_LeaseMgr::V6);
+ lease_mgr_->setExtendedInfoTablesEnabled(false);
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+
+ // Add the lease.
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+
+ // Set user context.
+ lease.reset(new Lease6(*lease));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+
+ // Set action and call updateLease6.
+ lease->extended_info_action_ = Lease6::ACTION_UPDATE;
+ ASSERT_NO_THROW(lease_mgr_->updateLease6(lease));
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+
+ // Tables were not touched.
+ EXPECT_TRUE(lease_mgr_->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr_->remote_id6_.empty());
+}
+
+/// @brief Verifies that updateLease6 modifies references to extended
+/// info tables when the action is ACTION_UPDATE and the extended
+/// info is modified before the call.
+TEST_F(MemfileExtendedInfoTest, updateLease6update2) {
+ start(Memfile_LeaseMgr::V6);
+ EXPECT_TRUE(lease_mgr_->getExtendedInfoTablesEnabled());
+
+ // Create parameter values.
+ Lease6Ptr lease;
+ IOAddress lease_addr(ADDRESS6[1]);
+ vector<uint8_t> duid_data = createFromString(DUIDS[0]);
+ DuidPtr duid(new DUID(duid_data));
+ ASSERT_NO_THROW(lease.reset(new Lease6(Lease::TYPE_NA, lease_addr, duid,
+ 123, 1000, 2000, 1)));
+ lease.reset(new Lease6(*lease));
+ string user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::2\", \"peer\": \"2001:db8::3\","
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease->setContext(user_context);
+
+ // Add the lease.
+ bool ret = false;
+ EXPECT_NO_THROW(ret = lease_mgr_->addLease(lease));
+ EXPECT_TRUE(ret);
+
+ // Verify updated extended info tables.
+ ASSERT_EQ(1, lease_mgr_->relay_id6_.size());
+ auto relay_id_it = lease_mgr_->relay_id6_.cbegin();
+ ASSERT_NE(relay_id_it, lease_mgr_->relay_id6_.cend());
+ Lease6ExtendedInfoPtr ex_info = *relay_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& relay_id = ex_info->id_;
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, relay_id);
+
+ ASSERT_EQ(1, lease_mgr_->remote_id6_.size());
+ auto remote_id_it = lease_mgr_->remote_id6_.cbegin();
+ ASSERT_NE(remote_id_it, lease_mgr_->remote_id6_.cend());
+ ex_info = *remote_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& remote_id = ex_info->id_;
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, remote_id);
+
+ // Change the user context.
+ user_context_txt =
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 44,"
+ " \"link\": \"2001:db8::4\", \"peer\": \"2001:db8::5\","
+ " \"options\": \"0x00250006010203040507003500086465656565656565\","
+ " \"remote-id\": \"010203040507\","
+ " \"relay-id\": \"6565656565656565\" } ] } }";
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ lease.reset(new Lease6(*lease));
+ lease->setContext(user_context);
+
+ // Set action and call updateLease6.
+ lease->extended_info_action_ = Lease6::ACTION_UPDATE;
+ ASSERT_NO_THROW(lease_mgr_->updateLease6(lease));
+ EXPECT_EQ(Lease6::ACTION_IGNORE, lease->extended_info_action_);
+
+ // Tables were updated.
+ ASSERT_EQ(1, lease_mgr_->relay_id6_.size());
+ relay_id_it = lease_mgr_->relay_id6_.cbegin();
+ ASSERT_NE(relay_id_it, lease_mgr_->relay_id6_.cend());
+ ex_info = *relay_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& relay_id2 = ex_info->id_;
+ const vector<uint8_t>& exp_relay_id2 = vector<uint8_t>(8, 0x65);
+ EXPECT_NE(exp_relay_id, relay_id2);
+ EXPECT_EQ(exp_relay_id2, relay_id2);
+
+ ASSERT_EQ(1, lease_mgr_->remote_id6_.size());
+ remote_id_it = lease_mgr_->remote_id6_.cbegin();
+ ASSERT_NE(remote_id_it, lease_mgr_->remote_id6_.cend());
+ ex_info = *remote_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ(ADDRESS6[1], ex_info->lease_addr_.toText());
+ const vector<uint8_t>& remote_id2 = ex_info->id_;
+ const vector<uint8_t>& exp_remote_id2 = { 1, 2, 3, 4, 5, 7 };
+ EXPECT_NE(exp_remote_id, remote_id2);
+ EXPECT_EQ(exp_remote_id2, remote_id2);
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_limits_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_limits_unittest.cc
new file mode 100644
index 0000000..5344a4b
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/memfile_lease_limits_unittest.cc
@@ -0,0 +1,600 @@
+// Copyright (C) 2022 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/memfile_lease_limits.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Test fixture for exercising ClassLeaseCounter.
+class ClassLeaseCounterTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ClassLeaseCounterTest() {
+ // Create two lists of classes and user contexts containing each.
+ // Note the lists have one class in common, "three".
+ classes1_.push_back("one");
+ classes1_.push_back("three");
+ classes1_.push_back("five");
+ ctx1_ = makeContextWithClasses(classes1_);
+
+ classes2_.push_back("two");
+ classes2_.push_back("three");
+ classes2_.push_back("four");
+ ctx2_ = makeContextWithClasses(classes2_);
+ }
+
+ /// @brief Destructor
+ virtual ~ClassLeaseCounterTest() = default;
+
+ /// @brief Create a user-context with a given list of classes
+ ///
+ /// Creates an Element::map with the following content:
+ ///
+ /// {
+ /// "ISC": {
+ /// "client-classes": [ "class0", "class1", ... ]
+ /// }
+ /// }
+ ///
+ /// @param classes list of classes to include in the context
+ /// @return ElementPtr containing the user-context
+ ElementPtr makeContextWithClasses(const std::list<ClientClass>& classes) {
+ ElementPtr ctx = Element::createMap();
+ if (classes.size()) {
+ ElementPtr clist = Element::createList();
+ for (auto client_class : classes ) {
+ clist->add(Element::create(client_class));
+ }
+
+ ElementPtr client_classes = Element::createMap();
+ client_classes->set("client-classes", clist);
+ ctx->set("ISC", client_classes);
+ }
+
+ return (ctx);
+ }
+
+ /// @brief Constructs a lease of a given Lease::Type
+ ///
+ /// @param ltype type of the lease desired (e.g Lease::TYPE_V4,
+ /// TYPE_NA, or TYPE_PD
+ ///
+ /// @return LeasePtr pointing the newly created lease.
+ LeasePtr leaseFactory(const Lease::Type& ltype) {
+ LeasePtr lease;
+ switch (ltype) {
+ case Lease::TYPE_V4: {
+ uint8_t hwaddr_data[] = { 0, 0x11, 0x11, 0x11, 0x11, 0x11 };
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data), HTYPE_ETHER));
+ time_t now = time(NULL);
+ lease.reset(new Lease4(IOAddress("192.0.2.100"), hwaddr, ClientIdPtr(), 500, now, 100));
+ break;
+ }
+ case Lease::TYPE_NA: {
+ DuidPtr duid(new DUID(DUID::fromText("01:02:03:04:05:06:07:08:09")));
+ lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), duid, 1010,
+ 2400, 3600, 100));
+ break;
+ }
+ case Lease::TYPE_PD: {
+ DuidPtr duid(new DUID(DUID::fromText("11:22:33:44:55:66:77:88:99")));
+ lease.reset(new Lease6(Lease::TYPE_PD, IOAddress("3001::"), duid, 1010,
+ 2400, 3600, 100));
+ break;
+ }
+ default:
+ ADD_FAILURE() << "TYPE_TA is not supported";
+ break;
+ }
+
+ return (lease);
+ }
+
+ /// @brief Verifies that the ClassLeaseCounter has the expected entries
+ ///
+ /// @param classes list of classes to verify
+ /// @param classes list of expected lease count for each class (assumed to be
+ /// in the same order as classes)
+ /// @param ltype type of lease counted
+ void checkClassCounts(std::list<ClientClass> classes, std::list<size_t> expected_counts,
+ const Lease::Type& ltype = Lease::TYPE_V4) {
+ ASSERT_EQ(classes.size(), expected_counts.size())
+ << "test is broken, number of classes and counts do not match";
+
+ EXPECT_EQ(classes.size(), clc_.size(ltype));
+ auto expected_count = expected_counts.begin();
+ for (auto client_class : classes) {
+ size_t count;
+ ASSERT_NO_THROW_LOG(count = clc_.getClassCount(client_class, ltype));
+ EXPECT_EQ(count, *expected_count) << ", count is wrong for client_class: " << client_class;
+ ++expected_count;
+ }
+ }
+
+ /// @brief Exercises ClassLeaseCounter::addLease() and removeLease()
+ ///
+ /// Performs a series of adds and removes of a given lease in different
+ /// lease states.
+ ///
+ /// @param lease Lease to add and remove
+ void addAndRemoveLeaseTest(LeasePtr lease) {
+ // Get the lease type for convenience.
+ Lease::Type ltype = lease->getType();
+
+ // Set the lease state to DEFAULT.
+ lease->state_ = Lease::STATE_DEFAULT;
+
+ // Add a lease with no classes. Should create no counts.
+ ASSERT_NO_THROW_LOG(clc_.addLease(lease));
+ EXPECT_EQ(0, clc_.size(ltype));
+
+ // Remove the lease with no classes. Should create no counts.
+ ASSERT_NO_THROW_LOG(clc_.removeLease(lease));
+ EXPECT_EQ(0, clc_.size(ltype));
+
+ // Add user-context with class list to lease.
+ lease->setContext(ctx1_);
+ ASSERT_NO_THROW_LOG(clc_.addLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 1, 1, 1 }), ltype);
+
+ // Add the same lease again. Counts should increment.
+ ASSERT_NO_THROW_LOG(clc_.addLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 2, 2, 2 }), ltype);
+
+ // Set lease state to DECLINED and add it. Counts should not increment.
+ lease->state_ = Lease::STATE_DECLINED;
+ ASSERT_NO_THROW_LOG(clc_.addLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 2, 2, 2 }), ltype);
+
+ // Set lease state to EXPIRED_RECLAIMED and add it. Counts should not increment.
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ ASSERT_NO_THROW_LOG(clc_.addLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 2, 2, 2 }), ltype);
+
+ // Set lease state back to DEFAULT and remove it. Counts should decrement.
+ lease->state_ = Lease::STATE_DEFAULT;
+ ASSERT_NO_THROW_LOG(clc_.removeLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 1, 1, 1 }), ltype);
+
+ // Set lease state to DECLINED and remove it. Counts should not decrement.
+ lease->state_ = Lease::STATE_DECLINED;
+ ASSERT_NO_THROW_LOG(clc_.removeLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 1, 1, 1 }), ltype);
+
+ // Set lease state to EXPIRED_RECLAIMED and remove it. Counts should not decrement.
+ lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
+ ASSERT_NO_THROW_LOG(clc_.removeLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 1, 1, 1 }), ltype);
+
+ // Set lease state back to DEFAULT and remove it. Counts should reach 0.
+ lease->state_ = Lease::STATE_DEFAULT;
+ ASSERT_NO_THROW_LOG(clc_.removeLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 0, 0, 0 }), ltype);
+
+ // Remove it again. Counts should not go negative.
+ ASSERT_NO_THROW_LOG(clc_.removeLease(lease));
+ checkClassCounts(classes1_, std::list<size_t>({ 0, 0, 0 }), ltype);
+ }
+
+ /// #brief Test updating type of lease for given old and new states and class lists
+ ///
+ /// The test creates an old and new version of the same lease and passes them into
+ /// @c ClassLeaseCounter::updateLease(). It then verifies the class lease counts
+ /// against expected count lists.
+ ///
+ /// @param old_state state of the old lease
+ /// @param old_classes class list of the old lease
+ /// @param expected_old_counts list of expected class lease counts (assumed to be
+ /// in same order as old_classes)
+ /// @param new_state state of the new lease
+ /// @param new_classes class list of the new lease
+ /// @param expected_new_counts list of expected class lease counts (assumed to be
+ /// in same order as new_classes)
+ /// @param ltype type of lease to update
+ void updateLeaseTest(uint32_t old_state, const std::list<ClientClass> old_classes,
+ std::list<size_t> expected_old_counts,
+ uint32_t new_state, const std::list<ClientClass> new_classes,
+ std::list<size_t> expected_new_counts, const Lease::Type& ltype) {
+ // Start the map with a count of one for all the old classes.
+ clc_.clear();
+ for (auto client_class : old_classes) {
+ ASSERT_NO_THROW_LOG(clc_.setClassCount(client_class, 1, ltype));
+ }
+
+ // Create the old and new lease, that beyond classes and state, are the same.
+ LeasePtr old_lease = leaseFactory(ltype);
+ old_lease->state_ = old_state;
+ old_lease->setContext(makeContextWithClasses(old_classes));
+
+ LeasePtr new_lease = leaseFactory(ltype);
+ new_lease->state_ = new_state;
+ new_lease->setContext(makeContextWithClasses(new_classes));
+
+ // Update the count.
+ ASSERT_NO_THROW_LOG(clc_.updateLease(new_lease, old_lease));
+
+ // Verify class counts for the old lease's classes are right.
+ auto expected_count = expected_old_counts.begin();
+ for (auto client_class : old_classes) {
+ size_t count;
+ ASSERT_NO_THROW_LOG(count = clc_.getClassCount(client_class, ltype));
+ EXPECT_EQ(count, *expected_count)
+ << ", old count is wrong for client_class: " << client_class;
+ ++expected_count;
+ }
+
+ // Verify class counts for the new lease's classes are right.
+ expected_count = expected_new_counts.begin();
+ for (auto client_class : new_classes) {
+ size_t count;
+ ASSERT_NO_THROW_LOG(count = clc_.getClassCount(client_class, ltype));
+ EXPECT_EQ(count, *expected_count)
+ << ", new count is wrong for client_class: " << client_class;
+ ++expected_count;
+ }
+ }
+
+ /// @brief Verifies updateLease() over a permutation of class lists and lease states
+ /// for a given lease type
+ /// @param ltype type of lease to test
+ void updateLeaseTests(const Lease::Type& ltype) {
+ // Both state DEFAULT, no old class list.
+ updateLeaseTest(Lease::STATE_DEFAULT, no_classes_, std::list<size_t>{},
+ Lease::STATE_DEFAULT, classes2_, std::list<size_t>{1,1,1}, ltype);
+
+ // Both state DEFAULT, same class list.
+ updateLeaseTest(Lease::STATE_DEFAULT, classes1_, std::list<size_t>{1,1,1},
+ Lease::STATE_DEFAULT, classes1_, std::list<size_t>{1,1,1}, ltype);
+
+ // Both state DEFAULT, different class list. Class "three" is in both lists.
+ updateLeaseTest(Lease::STATE_DEFAULT, classes1_, std::list<size_t>{0,1,0},
+ Lease::STATE_DEFAULT, classes2_, std::list<size_t>{1,1,1}, ltype);
+
+ // Old state is DEFAULT, new state is DECLINED. Old classes should be decremented,
+ // new classes should not be incremented.
+ updateLeaseTest(Lease::STATE_DEFAULT, classes1_, std::list<size_t>{0,0,0},
+ Lease::STATE_DECLINED, classes2_, std::list<size_t>{0,0,0}, ltype);
+
+ // Old state is DEFAULT, new state is EXPIRED_RECLAIMED. Old classes should be decremented,
+ // new classes should not be incremented.
+ updateLeaseTest(Lease::STATE_DEFAULT, classes1_, std::list<size_t>{0,0,0},
+ Lease::STATE_EXPIRED_RECLAIMED, classes2_, std::list<size_t>{0,0,0}, ltype);
+
+ // Old state is DECLINED, new state is DEFAULT. Old classes should not decremented,
+ // new classes should be incremented. Class "three" is in both lists.
+ updateLeaseTest(Lease::STATE_DECLINED, classes1_, std::list<size_t>{1,2,1},
+ Lease::STATE_DEFAULT, classes2_, std::list<size_t>{1,2,1}, ltype);
+
+ // Old state is DECLINED, new state is DECLINED. No count changes.
+ updateLeaseTest(Lease::STATE_DECLINED, classes1_, std::list<size_t>{1,1,1},
+ Lease::STATE_DECLINED, classes2_, std::list<size_t>{0,1,0}, ltype);
+
+ // Old state is DECLINED, new state is EXPIRED-RECLAIMED. No count changes.
+ updateLeaseTest(Lease::STATE_DECLINED, classes1_, std::list<size_t>{1,1,1},
+ Lease::STATE_EXPIRED_RECLAIMED, classes2_, std::list<size_t>{0,1,0}, ltype);
+
+ // Old state is EXPIRED_RECLAIMED , new state is DEFAULT. Old classes should not decremented,
+ // new classes should be incremented. Class "three" is in both lists.
+ updateLeaseTest(Lease::STATE_EXPIRED_RECLAIMED, classes1_, std::list<size_t>{1,2,1},
+ Lease::STATE_DEFAULT, classes2_, std::list<size_t>{1,2,1}, ltype);
+
+ // Old state is EXPIRED_RECLAIMED , new state is DECLINED. No count changes.
+ updateLeaseTest(Lease::STATE_EXPIRED_RECLAIMED, classes1_, std::list<size_t>{1,1,1},
+ Lease::STATE_DECLINED, classes2_, std::list<size_t>{0,1,0}, ltype);
+
+ // Old state is EXPIRED_RECLAIMED , new state is EXPIRED_RECLAIMED. No count changes.
+ updateLeaseTest(Lease::STATE_EXPIRED_RECLAIMED, classes1_, std::list<size_t>{1,1,1},
+ Lease::STATE_EXPIRED_RECLAIMED, classes2_, std::list<size_t>{0,1,0}, ltype);
+
+ }
+
+ /// @brief Verifies ClassLeaseCounter::adjustClassCounts
+ ///
+ /// @param ltype type of lease to count.
+ void adjustClassCountsTest(const Lease::Type ltype) {
+ ConstElementPtr class_list1 = ctx1_->find("ISC/client-classes");
+ ASSERT_TRUE(class_list1);
+
+ // Increment the classes by 2 and verify.
+ ASSERT_NO_THROW_LOG(clc_.adjustClassCounts(class_list1, 2, ltype));
+ checkClassCounts(classes1_, std::list<size_t>({ 2, 2, 2 }), ltype);
+
+ // Decrement the classes by 1 and verify.
+ ASSERT_NO_THROW_LOG(clc_.adjustClassCounts(class_list1, -1, ltype));
+ checkClassCounts(classes1_, std::list<size_t>({ 1, 1, 1 }), ltype);
+
+ // Decrement the classes by 2 and verify that roll-over is avoided..
+ ASSERT_NO_THROW_LOG(clc_.adjustClassCounts(class_list1, -2, ltype));
+ checkClassCounts(classes1_, std::list<size_t>({ 0, 0, 0 }), ltype);
+ }
+
+ /// @brief First test list of classes
+ std::list<ClientClass> classes1_;
+
+ /// @brief Second test list of classes
+ std::list<ClientClass> classes2_;
+
+ /// @brief Empty list of classes
+ std::list<ClientClass> no_classes_;
+
+ /// @brief User context containing classes1_;
+ ElementPtr ctx1_;
+
+ /// @brief User context containing classes2_;
+ ElementPtr ctx2_;
+
+ /// @brief ClassLeaseCounter instance to use in the test.
+ ClassLeaseCounter clc_;
+};
+
+// Tests getting and adjusting basic class counts for
+// a Lease::TYPE_V4.
+TEST_F(ClassLeaseCounterTest, basicCountingTests4) {
+ // Create test classes.
+ ClientClass melon("melon");
+ ClientClass water("water");
+
+ // Fetching the count for a non-existent class
+ // should return 0.
+ ASSERT_EQ(0, clc_.size());
+ size_t count;
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon));
+ EXPECT_EQ(0, count);
+
+ // Calling adjustClassCount() for non-existent class
+ // should result in an entry with the adjustment value.
+ ASSERT_EQ(0, clc_.size());
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, 1));
+ EXPECT_EQ(1, clc_.size());
+ ASSERT_NO_THROW_LOG(count = clc_.getClassCount(melon));
+ EXPECT_EQ(1, count);
+
+ // Calling adjust() again to add 1 should work.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, 1));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon));
+ EXPECT_EQ(2, count);
+
+ // Calling adjust() to subtract 1 should work.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, -1));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon));
+ EXPECT_EQ(1, count);
+
+ // Calling adjust() to subtract 2 should not rollover.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, -2));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon));
+ EXPECT_EQ(0, count);
+
+ // Set class value for a new class.
+ ASSERT_NO_THROW(clc_.setClassCount(water, 40));
+ EXPECT_EQ(2, clc_.size());
+ ASSERT_NO_THROW(count = clc_.getClassCount(water));
+ EXPECT_EQ(40, count);
+
+ // Should be able to adjust the new class entry.
+ ASSERT_NO_THROW(clc_.adjustClassCount(water, -1));
+ ASSERT_NO_THROW(count = clc_.getClassCount(water));
+ EXPECT_EQ(39, count);
+}
+
+// Tests getting and adjusting basic class counts for
+// a Lease::TYPE_V4.
+TEST_F(ClassLeaseCounterTest, adjustClassCountsTest4) {
+ adjustClassCountsTest(Lease::TYPE_V4);
+}
+
+// Tests getting and adjusting basic class counts for
+// a Lease::TYPE_NA and TYPE_PD.
+TEST_F(ClassLeaseCounterTest, adjustClassCountsTest6) {
+ adjustClassCountsTest(Lease::TYPE_NA);
+ adjustClassCountsTest(Lease::TYPE_PD);
+}
+
+// Tests getting and adjusting basic class counts for
+// a Lease::TYPE_NA and TYPE_PD.
+TEST_F(ClassLeaseCounterTest, basicCountingTests6) {
+ // Create test classes.
+ ClientClass melon("melon");
+ ClientClass water("water");
+
+ // Fetching the count for a non-existent class
+ // should return 0.
+ ASSERT_EQ(0, clc_.size(Lease::TYPE_NA));
+ size_t count;
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon, Lease::TYPE_NA));
+ EXPECT_EQ(0, count);
+
+ // Calling adjustClassCount() for non-existent class
+ // should result in an entry with the adjustment value.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, 1, Lease::TYPE_NA));
+ EXPECT_EQ(1, clc_.size(Lease::TYPE_NA));
+ ASSERT_NO_THROW_LOG(count = clc_.getClassCount(melon, Lease::TYPE_NA));
+ EXPECT_EQ(1, count);
+
+ // Calling adjust() again to add 1 should work.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, 1, Lease::TYPE_NA));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon, Lease::TYPE_NA));
+ EXPECT_EQ(2, count);
+
+ // Calling adjust() to subtract 1 should work.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, -1, Lease::TYPE_NA));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon, Lease::TYPE_NA));
+ EXPECT_EQ(1, count);
+
+ // Calling adjust() to subtract 2 should not rollover.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, -2, Lease::TYPE_NA));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon, Lease::TYPE_NA));
+ EXPECT_EQ(0, count);
+
+ // Set class value for a new class.
+ ASSERT_NO_THROW(clc_.setClassCount(water, 40, Lease::TYPE_NA));
+ EXPECT_EQ(2, clc_.size(Lease::TYPE_NA));
+ ASSERT_NO_THROW(count = clc_.getClassCount(water, Lease::TYPE_NA));
+ EXPECT_EQ(40, count);
+
+ // Should be able to adjust the new class entry.
+ ASSERT_NO_THROW(clc_.adjustClassCount(water, -1, Lease::TYPE_NA));
+ ASSERT_NO_THROW(count = clc_.getClassCount(water, Lease::TYPE_NA));
+ EXPECT_EQ(39, count);
+
+ // Existing class should be able to count prefixes independently.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, 5, Lease::TYPE_PD));
+ EXPECT_EQ(1, clc_.size(Lease::TYPE_PD));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon, Lease::TYPE_PD));
+ EXPECT_EQ(5, count);
+
+ // Should be able to adjust the new class prefix entry.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, -2, Lease::TYPE_PD));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon, Lease::TYPE_PD));
+ EXPECT_EQ(3, count);
+
+ // Calling adjust() to subtract 4 should not rollover.
+ ASSERT_NO_THROW(clc_.adjustClassCount(melon, -4, Lease::TYPE_PD));
+ ASSERT_NO_THROW(count = clc_.getClassCount(melon, Lease::TYPE_PD));
+ EXPECT_EQ(0, count);
+}
+
+// Exercises ClassLeaseCounter::getLeaseClientClasses()
+// No need for v4 and v6 versions of this test, getLeaseClientClasses()
+// is not protocol specific.
+TEST_F(ClassLeaseCounterTest, getLeaseClientClassesTest) {
+ LeasePtr lease;
+
+ // Describes an invalid context scenario, that
+ // is expected to cause an exception throw.
+ struct InvalidScenario {
+ std::string ctx_json_; // User context contents in JSON form.
+ std::string exp_message_; // Expected exception text.
+ };
+
+ // Invalid context scenarios.
+ std::list<InvalidScenario> invalid_scenarios {
+ {
+ " \"bogus\" ",
+ "getLeaseClientClasses - invalid context: \"bogus\","
+ " find(string) called on a non-map Element in (<string>:1:2)"
+ },
+ {
+ "{\"ISC\": \"bogus\" }",
+ "getLeaseClientClasses - invalid context: {\n \"ISC\": \"bogus\"\n},"
+ " find(string) called on a non-map Element in (<string>:1:9)"
+ },
+ {
+ "{\"ISC\": { \"client-classes\": \"bogus\" } }",
+ "getLeaseClientClasses - invalid context:"
+ " {\n \"ISC\": {\n \"client-classes\": \"bogus\"\n }\n},"
+ " client-classes is not a list"
+ }
+ };
+
+ // Iterate over the invalid scenarios.
+ for (auto scenario : invalid_scenarios) {
+ // Construct the lease and context.
+ lease = leaseFactory(Lease::TYPE_V4);
+ ElementPtr ctx;
+ ASSERT_NO_THROW(ctx = Element::fromJSON(scenario.ctx_json_))
+ << "test is broken" << scenario.ctx_json_;
+ lease->setContext(ctx);
+
+ // Calling getLeaseClientClasses() should throw.
+ ASSERT_THROW_MSG(clc_.getLeaseClientClasses(lease), BadValue, scenario.exp_message_);
+ }
+
+ // Describes an valid context scenario, that is expected
+ // to return normally.
+ struct ValidScenario {
+ std::string ctx_json_; // User context contents in JSON form.
+ std::string exp_classes_; // Expected class list in JSON form.
+ };
+
+ // Valid scenarios.
+ std::list<ValidScenario> valid_scenarios {
+ {
+ // No context
+ "",
+ ""
+ },
+ {
+ // No client-classes element
+ "{\"ISC\": {} }",
+ ""
+ },
+ {
+ // Empty client-classes list
+ "{\"ISC\": { \"client-classes\": []} }",
+ "[]"
+ },
+ {
+ "{\"ISC\": { \"client-classes\": [ \"one\", \"two\", \"three\" ]} }",
+ "[ \"one\", \"two\", \"three\" ]"
+ }
+ };
+
+ // Iterate over the scenarios.
+ for (auto scenario : valid_scenarios) {
+ // Construct the lease and context.
+ lease = leaseFactory(Lease::TYPE_V4);
+ if (!scenario.ctx_json_.empty()) {
+ ElementPtr ctx;
+ ASSERT_NO_THROW(ctx = Element::fromJSON(scenario.ctx_json_))
+ << "test is broken" << scenario.ctx_json_;
+ lease->setContext(ctx);
+ }
+
+ // Call getLeaseClientClasses().
+ ConstElementPtr classes;
+ ASSERT_NO_THROW_LOG(classes = clc_.getLeaseClientClasses(lease));
+
+ // Verify we got the expected outcome for a class list.
+ if (scenario.exp_classes_.empty()) {
+ ASSERT_FALSE(classes);
+ } else {
+ ASSERT_TRUE(classes);
+ ElementPtr exp_classes;
+ ASSERT_NO_THROW(exp_classes = Element::fromJSON(scenario.exp_classes_))
+ << "test is broken" << scenario.exp_classes_;
+ EXPECT_EQ(*classes, *exp_classes);
+ }
+ }
+}
+
+TEST_F(ClassLeaseCounterTest, addRemoveLeaseTest4) {
+ addAndRemoveLeaseTest(leaseFactory(Lease::TYPE_V4));
+}
+
+TEST_F(ClassLeaseCounterTest, addRemoveLeaseTest6_NA) {
+ addAndRemoveLeaseTest(leaseFactory(Lease::TYPE_NA));
+}
+
+TEST_F(ClassLeaseCounterTest, addRemoveLeaseTest6_PD) {
+ addAndRemoveLeaseTest(leaseFactory(Lease::TYPE_PD));
+}
+
+TEST_F(ClassLeaseCounterTest, updateLeaseTests4) {
+ updateLeaseTests(Lease::TYPE_V4);
+}
+
+TEST_F(ClassLeaseCounterTest, updateLeaseTests6_NA) {
+ updateLeaseTests(Lease::TYPE_NA);
+}
+
+TEST_F(ClassLeaseCounterTest, updateLeaseTests6_PD) {
+ updateLeaseTests(Lease::TYPE_PD);
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
new file mode 100644
index 0000000..709b3ab
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
@@ -0,0 +1,4413 @@
+// Copyright (C) 2012-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/csv_lease_file6.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <dhcpsrv/testutils/lease_file_io.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <testutils/gtest_utils.h>
+#include <util/multi_threading_mgr.h>
+#include <util/pid_file.h>
+#include <util/range_utilities.h>
+#include <util/stopwatch.h>
+
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <iostream>
+#include <fstream>
+#include <queue>
+#include <sstream>
+
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Class derived from @c Memfile_LeaseMgr to test LFC timer.
+///
+/// This class provides a custom callback function which is invoked
+/// when the timer for Lease File Cleanup goes off. It is used to
+/// test that the timer is correctly installed.
+class LFCMemfileLeaseMgr : public Memfile_LeaseMgr {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets the counter for callbacks to 0.
+ LFCMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+ : Memfile_LeaseMgr(parameters), lfc_cnt_(0) {
+ }
+
+ /// @brief Returns the number of callback executions.
+ int getLFCCount() {
+ return (lfc_cnt_);
+ }
+
+protected:
+
+ /// @brief Custom callback.
+ ///
+ /// This callback function increases the counter of callback executions.
+ /// By examining the counter value a test may verify that the callback
+ /// was triggered an expected number of times.
+ virtual void lfcCallback() {
+ ++lfc_cnt_;
+ }
+
+private:
+
+ /// @brief Counter of callback function executions.
+ int lfc_cnt_;
+};
+
+/// @brief A derivation of the lease manager exposing protected methods.
+class NakedMemfileLeaseMgr : public Memfile_LeaseMgr {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates instance of the lease manager.
+ NakedMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+ : Memfile_LeaseMgr(parameters) {
+ }
+
+ using Memfile_LeaseMgr::lfcCallback;
+ using Memfile_LeaseMgr::setExtendedInfoTablesEnabled;
+ using Memfile_LeaseMgr::relay_id6_;
+ using Memfile_LeaseMgr::remote_id6_;
+};
+
+/// @brief Test fixture class for @c Memfile_LeaseMgr
+class MemfileLeaseMgrTest : public GenericLeaseMgrTest {
+public:
+
+ /// @brief memfile lease mgr test constructor
+ ///
+ /// Creates memfile and stores it in lmptr_ pointer
+ MemfileLeaseMgrTest() :
+ io4_(getLeaseFilePath("leasefile4_0.csv")),
+ io6_(getLeaseFilePath("leasefile6_0.csv")),
+ io_service_(getIOService()),
+ timer_mgr_(TimerMgr::instance()),
+ extra_files_() {
+
+ timer_mgr_->setIOService(io_service_);
+ LeaseMgr::setIOService(io_service_);
+
+ std::ostringstream s;
+ s << KEA_LFC_BUILD_DIR << "/kea-lfc";
+ setenv("KEA_LFC_EXECUTABLE", s.str().c_str(), 1);
+
+ // Remove lease files and products of Lease File Cleanup.
+ removeFiles(getLeaseFilePath("leasefile4_0.csv"));
+ removeFiles(getLeaseFilePath("leasefile6_0.csv"));
+
+ // Reset staging extended info sanitizer to its default.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Reopens the connection to the backend.
+ ///
+ /// This function is called by unit tests to force reconnection of the
+ /// backend to check that the leases are stored and can be retrieved
+ /// from the storage.
+ ///
+ /// @param u Universe (V4 or V6)
+ virtual void reopen(Universe u) {
+ LeaseMgrFactory::destroy();
+ startBackend(u);
+ }
+
+ /// @brief destructor
+ ///
+ /// destroys lease manager backend.
+ virtual ~MemfileLeaseMgrTest() {
+ // Stop TimerMgr worker thread if it is running.
+ // Make sure there are no timers registered.
+ timer_mgr_->unregisterTimers();
+ LeaseMgrFactory::destroy();
+ // Remove lease files and products of Lease File Cleanup.
+ removeFiles(getLeaseFilePath("leasefile4_0.csv"));
+ removeFiles(getLeaseFilePath("leasefile6_0.csv"));
+ // Remove other files.
+ removeOtherFiles();
+ // Reset staging extended info sanitizer to its default.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ // Disable multi-threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Remove files being products of Lease File Cleanup.
+ ///
+ /// @param base_name Path to the lease file name. This file is removed
+ /// and all files which names are created from this name (having specific
+ /// suffixes used by Lease File Cleanup mechanism).
+ void removeFiles(const std::string& base_name) const {
+ // Generate suffixes and append them to the base name. The
+ // resulting file names are the ones that may exist as a
+ // result of LFC.
+ for (int i = static_cast<int>(Memfile_LeaseMgr::FILE_CURRENT);
+ i <= static_cast<int>(Memfile_LeaseMgr::FILE_FINISH);
+ ++i) {
+ Memfile_LeaseMgr::LFCFileType type = static_cast<
+ Memfile_LeaseMgr::LFCFileType>(i);
+ LeaseFileIO io(Memfile_LeaseMgr::appendSuffix(base_name, type));
+ io.removeFile();
+ }
+ }
+
+ /// @brief Remove other files.
+ void removeOtherFiles() const {
+ for (const auto& file : extra_files_) {
+ LeaseFileIO io(file);
+ io.removeFile();
+ }
+ }
+
+ /// @brief Return path to the lease file used by unit tests.
+ ///
+ /// @param filename Name of the lease file appended to the path to the
+ /// directory where test data is held.
+ ///
+ /// @return Full path to the lease file.
+ static std::string getLeaseFilePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+ return (s.str());
+ }
+
+ /// @brief Returns the configuration string for the backend.
+ ///
+ /// This string configures the @c LeaseMgrFactory to create the memfile
+ /// backend and use leasefile4_0.csv and leasefile6_0.csv files as
+ /// storage for leases.
+ ///
+ /// @param no_universe Indicates whether universe parameter should be
+ /// included (false), or not (true).
+ ///
+ /// @return Configuration string for @c LeaseMgrFactory.
+ static std::string getConfigString(Universe u) {
+ std::ostringstream s;
+ s << "type=memfile " << (u == V4 ? "universe=4 " : "universe=6 ")
+ << "name="
+ << getLeaseFilePath(u == V4 ? "leasefile4_0.csv" : "leasefile6_0.csv")
+ << " lfc-interval=0";
+ return (s.str());
+ }
+
+ /// @brief Creates instance of the backend.
+ ///
+ /// @param u Universe (v4 or V6).
+ void startBackend(Universe u) {
+ try {
+ LeaseMgrFactory::create(getConfigString(u));
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to create instance of the Memfile"
+ " lease database backend.\n";
+ throw;
+ }
+ lmptr_ = &(LeaseMgrFactory::instance());
+ }
+
+ /// @brief Runs IOService and stops after a specified time.
+ ///
+ /// @param ms Duration in milliseconds.
+ void setTestTime(const uint32_t ms) {
+ IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, ms, IntervalTimer::ONE_SHOT);
+
+ io_service_->run();
+ io_service_->get_io_service().reset();
+ }
+
+ /// @brief Waits for the specified process to finish.
+ ///
+ /// @param process An object which started the process.
+ /// @param timeout Timeout in seconds.
+ ///
+ /// @return true if the process ended, false otherwise
+ bool waitForProcess(const Memfile_LeaseMgr& lease_mgr,
+ const uint8_t timeout) {
+ const uint32_t iterations_max = timeout * 1000;
+ IntervalTimer fast_path_timer(*io_service_);
+ IntervalTimer timer(*io_service_);
+ bool elapsed = false;
+ timer.setup([&]() {
+ io_service_->stop();
+ elapsed = true;
+ }, iterations_max, IntervalTimer::ONE_SHOT);
+
+ fast_path_timer.setup([&]() {
+ if (!lease_mgr.isLFCRunning()) {
+ io_service_->stop();
+ }
+ }, 1, IntervalTimer::REPEATING);
+
+ io_service_->run();
+ io_service_->get_io_service().reset();
+ return (!elapsed);
+ }
+
+ /// @brief Single instance of IOService.
+ static asiolink::IOServicePtr getIOService() {
+ static asiolink::IOServicePtr io_service(new asiolink::IOService());
+ return (io_service);
+ }
+
+ /// @brief Generates a DHCPv4 lease with random content.
+ ///
+ /// The following lease parameters are randomly generated:
+ /// - HW address,
+ /// - client identifier,
+ /// - hostname,
+ /// - subnet identifier,
+ /// - client last transmission time,
+ ///
+ /// The following lease parameters are set to constant values:
+ /// - valid lifetime = 1200,
+ /// - DNS update forward flag = false,
+ /// - DNS update reverse flag = false,
+ ///
+ /// The lease address is set to address passed as function
+ /// argument.
+ ///
+ /// @param address Lease address.
+ ///
+ /// @return new lease with random content
+ Lease4Ptr initiateRandomLease4(const IOAddress& address) {
+
+ // Randomize HW address.
+ vector<uint8_t> mac(6);
+ fillRandom(mac.begin(), mac.end());
+ HWAddrPtr hwaddr(new HWAddr(mac, HTYPE_ETHER));
+
+ // Let's generate clientid of random length
+ vector<uint8_t> clientid(4 + random()%20);
+ // And then fill it with random value.
+ fillRandom(clientid.begin(), clientid.end());
+
+ uint32_t valid_lft = 1200;
+ time_t timestamp = time(NULL) - 86400 + random()%86400;
+ bool fqdn_fwd = false;
+ bool fqdn_rev = false;
+ uint32_t subnet_id = 1000 + random()%16;
+
+ std::ostringstream hostname;
+ hostname << "hostname" << (random() % 2048);
+
+ // Return created lease.
+ return (Lease4Ptr(new Lease4(address, hwaddr, &clientid[0],
+ clientid.size(), valid_lft,
+ timestamp, subnet_id, fqdn_fwd,
+ fqdn_rev, hostname.str())));
+ }
+
+ /// @brief Generates a DHCPv6 lease with random content.
+ ///
+ /// The following lease parameters are randomly generated:
+ /// - DUID,
+ /// - IAID,
+ /// - hostname,
+ /// - subnet identifier,
+ /// - client last transmission time,
+ ///
+ /// The following lease parameters are set to constant values:
+ /// - lease type = IA_NA
+ /// - valid lifetime = 1200,
+ /// - preferred lifetime = 1000
+ /// - DNS update forward flag = false,
+ /// - DNS update reverse flag = false,
+ ///
+ /// The lease address is set to address passed as function
+ /// argument.
+ ///
+ /// @param address Lease address.
+ ///
+ /// @return new lease with random content
+ Lease6Ptr initiateRandomLease6(const IOAddress& address) {
+ // Let's generate DUID of random length.
+ std::vector<uint8_t> duid_vec(8 + random()%20);
+ // And then fill it with random value.
+ fillRandom(duid_vec.begin(), duid_vec.end());
+ DuidPtr duid(new DUID(duid_vec));
+
+ Lease::Type lease_type = Lease::TYPE_NA;
+ uint32_t iaid = 1 + random()%100;
+ uint32_t valid_lft = 1200;
+ uint32_t preferred_lft = 1000;
+ time_t timestamp = time(NULL) - 86400 + random()%86400;
+ bool fqdn_fwd = false;
+ bool fqdn_rev = false;
+ uint32_t subnet_id = 1000 + random()%16;
+
+ std::ostringstream hostname;
+ hostname << "hostname" << (random() % 2048);
+
+ // Return created lease.
+ Lease6Ptr lease(new Lease6(lease_type, address, duid, iaid,
+ preferred_lft, valid_lft,
+ subnet_id, fqdn_fwd, fqdn_rev,
+ hostname.str()));
+ lease->cltt_ = timestamp;
+ return (lease);
+ }
+
+ /// @brief Object providing access to v4 lease IO.
+ LeaseFileIO io4_;
+
+ /// @brief Object providing access to v6 lease IO.
+ LeaseFileIO io6_;
+
+ /// @brief Pointer to the IO service used by the tests.
+ IOServicePtr io_service_;
+
+ /// @brief Pointer to the instance of the @c TimerMgr.
+ TimerMgrPtr timer_mgr_;
+
+ /// @brief List of names of other files to removed.
+ vector<string> extra_files_;
+};
+
+/// @brief This test checks if the LeaseMgr can be instantiated and that it
+/// parses parameters string properly.
+TEST_F(MemfileLeaseMgrTest, constructor) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["universe"] = "4";
+ pmap["persist"] = "false";
+ boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr;
+
+ EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
+
+ // Check the extended info enable flag.
+ EXPECT_FALSE(lease_mgr->getExtendedInfoTablesEnabled());
+ pmap["extended-info-tables"] = "true";
+ EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
+ EXPECT_TRUE(lease_mgr->getExtendedInfoTablesEnabled());
+
+ // Expecting that persist parameter is yes or no. Everything other than
+ // that is wrong.
+ pmap["lfc-interval"] = "10";
+ pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+ pmap["max-row-errors"] = "5";
+ pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+ pmap["persist"] = "bogus";
+ EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
+
+ // The lfc-interval must be an integer.
+ pmap["persist"] = "true";
+ pmap["lfc-interval"] = "bogus";
+ EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
+
+ // The max-row-errors must be an integer.
+ pmap["lfc-interval"] = "10";
+ pmap["max-row-errors"] = "bogus";
+ EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
+
+ // The max-row-errors must be >= 0.
+ pmap["max-row-errors"] = "-1";
+ EXPECT_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)), isc::BadValue);
+
+ // Moved to the end as it can leave the timer registered.
+ pmap["max-row-errors"] = "5";
+ EXPECT_NO_THROW(lease_mgr.reset(new Memfile_LeaseMgr(pmap)));
+}
+
+/// @brief Checks if there is no lease manager NoLeaseManager is thrown.
+TEST_F(MemfileLeaseMgrTest, noLeaseManager) {
+ LeaseMgrFactory::destroy();
+ EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager);
+}
+
+/// @brief Checks if the getType() and getName() methods both return "memfile".
+TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
+ startBackend(V4);
+ EXPECT_EQ(std::string("memfile"), lmptr_->getType());
+ EXPECT_EQ(std::string("memory"), lmptr_->getName());
+}
+
+/// @brief Checks if the path to the lease files is initialized correctly.
+TEST_F(MemfileLeaseMgrTest, getLeaseFilePath) {
+ // Initialize IO objects, so as the test csv files get removed after the
+ // test (when destructors are called).
+ LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv"));
+ LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv"));
+
+ DatabaseConnection::ParameterMap pmap;
+ pmap["universe"] = "4";
+ pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+ boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
+
+ EXPECT_EQ(pmap["name"],
+ lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4));
+
+ pmap["persist"] = "false";
+ lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+ EXPECT_TRUE(lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V4).empty());
+ EXPECT_TRUE(lease_mgr->getLeaseFilePath(Memfile_LeaseMgr::V6).empty());
+}
+
+/// @brief Check if the persistLeases correctly checks that leases should not be written
+/// to disk when disabled through configuration.
+TEST_F(MemfileLeaseMgrTest, persistLeases) {
+ // Initialize IO objects, so as the test csv files get removed after the
+ // test (when destructors are called).
+ LeaseFileIO io4(getLeaseFilePath("leasefile4_1.csv"));
+ LeaseFileIO io6(getLeaseFilePath("leasefile6_1.csv"));
+
+ DatabaseConnection::ParameterMap pmap;
+ pmap["universe"] = "4";
+ pmap["lfc-interval"] = "0";
+ // Specify the names of the lease files. Leases will be written.
+ pmap["name"] = getLeaseFilePath("leasefile4_1.csv");
+ boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
+
+ lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+ EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
+ EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
+
+ pmap["universe"] = "6";
+ pmap["name"] = getLeaseFilePath("leasefile6_1.csv");
+ lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+ EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
+ EXPECT_TRUE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
+
+ // This should disable writes of leases to disk.
+ pmap["persist"] = "false";
+ lease_mgr.reset(new Memfile_LeaseMgr(pmap));
+ EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V4));
+ EXPECT_FALSE(lease_mgr->persistLeases(Memfile_LeaseMgr::V6));
+}
+
+/// @brief Check if it is possible to schedule the timer to perform the Lease
+/// File Cleanup periodically.
+TEST_F(MemfileLeaseMgrTest, lfcTimer) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ // Specify the names of the lease files. Leases will be written.
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ pmap["lfc-interval"] = "1";
+
+ boost::scoped_ptr<LFCMemfileLeaseMgr>
+ lease_mgr(new LFCMemfileLeaseMgr(pmap));
+
+ // Run the test for at most 2.9 seconds.
+ setTestTime(2900);
+
+ // Within 2.9 we should record two LFC executions.
+ EXPECT_EQ(2, lease_mgr->getLFCCount());
+}
+
+/// @brief This test checks if the LFC timer is disabled (doesn't trigger)
+/// cleanups when the lfc-interval is set to 0.
+TEST_F(MemfileLeaseMgrTest, lfcTimerDisabled) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ // Set the LFC interval to 0, which should effectively disable it.
+ pmap["lfc-interval"] = "0";
+
+ boost::scoped_ptr<LFCMemfileLeaseMgr>
+ lease_mgr(new LFCMemfileLeaseMgr(pmap));
+
+ // Run the test for at most 1.9 seconds.
+ setTestTime(1900);
+
+ // There should be no LFC execution recorded.
+ EXPECT_EQ(0, lease_mgr->getLFCCount());
+}
+
+/// @brief This test checks that the callback function executing the cleanup of the
+/// DHCPv4 lease file works as expected.
+TEST_F(MemfileLeaseMgrTest, leaseFileCleanup4) {
+ // This string contains the lease file header, which matches
+ // the contents of the new file in which no leases have been
+ // stored.
+ std::string new_file_contents =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ // This string contains the contents of the lease file with exactly
+ // one lease, but two entries. One of the entries should be removed
+ // as a result of lease file cleanup.
+ std::string current_file_contents = new_file_contents +
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,{ \"foo\": true },0\n"
+ "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,1,,0\n";
+ LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv"));
+ current_file.writeFile(current_file_contents);
+
+ std::string previous_file_contents = new_file_contents +
+ "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,1,,0\n"
+ "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,1,{ \"bar\": true },0\n";
+ LeaseFileIO previous_file(getLeaseFilePath("leasefile4_0.csv.2"));
+ previous_file.writeFile(previous_file_contents);
+
+ // Create the backend.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ pmap["lfc-interval"] = "1";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap));
+
+ // Try to run the lease file cleanup.
+ ASSERT_NO_THROW(lease_mgr->lfcCallback());
+
+ // The new lease file should have been created and it should contain
+ // no leases.
+ ASSERT_TRUE(current_file.exists());
+ EXPECT_EQ(new_file_contents, current_file.readFile());
+
+ // Wait for the LFC process to complete.
+ ASSERT_TRUE(waitForProcess(*lease_mgr, 2));
+
+ // And make sure it has returned an exit status of 0.
+ EXPECT_EQ(0, lease_mgr->getLFCExitStatus())
+ << "Executing the LFC process failed: make sure that"
+ " the kea-lfc program has been compiled.";
+
+ // Check if we can still write to the lease file.
+ std::vector<uint8_t> hwaddr_vec(6);
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_vec, HTYPE_ETHER));
+ Lease4Ptr new_lease(new Lease4(IOAddress("192.0.2.45"), hwaddr,
+ static_cast<const uint8_t*>(0), 0,
+ 100, 0, 1));
+ ASSERT_NO_THROW(lease_mgr->addLease(new_lease));
+
+ std::string updated_file_contents = new_file_contents +
+ "192.0.2.45,00:00:00:00:00:00,,100,100,1,0,0,,0,,0\n";
+ EXPECT_EQ(updated_file_contents, current_file.readFile());
+
+ // This string contains the contents of the lease file we
+ // expect after the LFC run. It has two leases with one
+ // entry each.
+ std::string result_file_contents = new_file_contents +
+ "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,1,,0\n"
+ "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,1,{ \"bar\": true },0\n";
+
+ // The LFC should have created a file with the two leases and moved it
+ // to leasefile4_0.csv.2
+ LeaseFileIO input_file(getLeaseFilePath("leasefile4_0.csv.2"), false);
+ ASSERT_TRUE(input_file.exists());
+ // And this file should contain the contents of the result file.
+ EXPECT_EQ(result_file_contents, input_file.readFile());
+}
+
+/// @brief This test checks that the callback function executing the cleanup of the
+/// DHCPv6 lease file works as expected.
+TEST_F(MemfileLeaseMgrTest, leaseFileCleanup6) {
+ // This string contains the lease file header, which matches
+ // the contents of the new file in which no leases have been
+ // stored.
+ std::string new_file_contents =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source,pool_id\n";
+
+ // This string contains the contents of the lease file with exactly
+ // one lease, but two entries. One of the entries should be removed
+ // as a result of lease file cleanup.
+ std::string current_file_contents = new_file_contents +
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,200,"
+ "8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
+ "8,100,0,7,0,1,1,,,1,{ \"foo\": true },,,0\n";
+ LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv"));
+ current_file.writeFile(current_file_contents);
+
+ std::string previous_file_contents = new_file_contents +
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,200,"
+ "8,100,0,7,0,1,1,,,1,{ \"bar\": true },,,0\n"
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
+ "8,100,0,7,0,1,1,,,1,,,,0\n";
+ LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2"));
+ previous_file.writeFile(previous_file_contents);
+
+ // Create the backend.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ pmap["lfc-interval"] = "1";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap));
+
+ // Try to run the lease file cleanup.
+ ASSERT_NO_THROW(lease_mgr->lfcCallback());
+
+ // The new lease file should have been created and it should contain
+ // no leases.
+ ASSERT_TRUE(current_file.exists());
+ EXPECT_EQ(new_file_contents, current_file.readFile());
+
+ // Wait for the LFC process to complete.
+ ASSERT_TRUE(waitForProcess(*lease_mgr, 2));
+
+ // And make sure it has returned an exit status of 0.
+ EXPECT_EQ(0, lease_mgr->getLFCExitStatus())
+ << "Executing the LFC process failed: make sure that"
+ " the kea-lfc program has been compiled.";
+
+ // Check if we can still write to the lease file.
+ std::vector<uint8_t> duid_vec(13);
+ DuidPtr duid(new DUID(duid_vec));
+ Lease6Ptr new_lease(new Lease6(Lease::TYPE_NA, IOAddress("3000::1"), duid,
+ 123, 300, 400, 2));
+ new_lease->cltt_ = 0;
+ ASSERT_NO_THROW(lease_mgr->addLease(new_lease));
+
+ std::string update_file_contents = new_file_contents +
+ "3000::1,00:00:00:00:00:00:00:00:00:00:00:00:00,400,"
+ "400,2,300,0,123,128,0,0,,,0,,,,0\n";
+ EXPECT_EQ(update_file_contents, current_file.readFile());
+
+ // This string contains the contents of the lease file we
+ // expect after the LFC run. It has two leases with one
+ // entry each.
+ std::string result_file_contents = new_file_contents +
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
+ "8,100,0,7,128,1,1,,,1,{ \"foo\": true },,,0\n"
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
+ "8,100,0,7,128,1,1,,,1,,,,0\n";
+
+ // The LFC should have created a file with the two leases and moved it
+ // to leasefile6_0.csv.2
+ LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.2"), false);
+ ASSERT_TRUE(input_file.exists());
+ // And this file should contain the contents of the result file.
+ EXPECT_EQ(result_file_contents, input_file.readFile());
+}
+
+/// @brief This test verifies that EXIT_FAILURE status code is returned when
+/// the LFC process fails to start.
+TEST_F(MemfileLeaseMgrTest, leaseFileCleanupStartFail) {
+ // This string contains the lease file header, which matches
+ // the contents of the new file in which no leases have been
+ // stored.
+ std::string new_file_contents =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ // Create the lease file to be used by the backend.
+ std::string current_file_contents = new_file_contents +
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,,0\n";
+ LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv"));
+ current_file.writeFile(current_file_contents);
+
+ // Specify invalid path to the kea-lfc executable. This should result
+ // in failure status code returned by the child process.
+ setenv("KEA_LFC_EXECUTABLE", "foobar", 1);
+
+ // Create the backend.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ pmap["lfc-interval"] = "1";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)), ProcessSpawnError);
+}
+
+/// @brief This test checks that the callback function executing the cleanup of the
+/// files doesn't move the current file if the finish file exists
+TEST_F(MemfileLeaseMgrTest, leaseFileFinish) {
+ // This string contains the lease file header, which matches
+ // the contents of the new file in which no leases have been
+ // stored.
+ std::string new_file_contents =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source,pool_id\n";
+
+ // This string contains the contents of the current lease file.
+ // It should not be moved.
+ std::string current_file_contents = new_file_contents +
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,200,"
+ "8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
+ "8,100,0,7,0,1,1,,,1,,,,0\n";
+ LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv"));
+ current_file.writeFile(current_file_contents);
+
+ // This string contains the contents of the finish file. It should
+ // be moved to the previous file.
+ std::string finish_file_contents = new_file_contents +
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
+ "8,100,0,7,0,1,1,,,1,,0\n";
+ LeaseFileIO finish_file(getLeaseFilePath("leasefile6_0.csv.completed"));
+ finish_file.writeFile(finish_file_contents);
+
+ // Create the backend.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ pmap["lfc-interval"] = "1";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap));
+
+ // Try to run the lease file cleanup.
+ ASSERT_NO_THROW(lease_mgr->lfcCallback());
+
+ // The current lease file should not have been touched.
+ ASSERT_TRUE(current_file.exists());
+ EXPECT_EQ(current_file_contents, current_file.readFile());
+
+ // Wait for the LFC process to complete.
+ ASSERT_TRUE(waitForProcess(*lease_mgr, 5));
+
+ // And make sure it has returned an exit status of 0.
+ EXPECT_EQ(0, lease_mgr->getLFCExitStatus())
+ << "Executing the LFC process failed: make sure that"
+ " the kea-lfc program has been compiled.";
+
+ // The LFC should have moved the finish file to the previous file -
+ // leasefile6_0.csv.2
+ LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2"), false);
+ ASSERT_TRUE(previous_file.exists());
+ // And this file should contain the contents of the finish file.
+ EXPECT_EQ(finish_file_contents, previous_file.readFile());
+
+ // The finish file should have been removed
+ ASSERT_FALSE(finish_file.exists());
+}
+
+/// @brief This test checks that the callback function executing the cleanup of the
+/// files doesn't move the current file if the copy file exists
+TEST_F(MemfileLeaseMgrTest, leaseFileCopy) {
+ // This string contains the lease file header, which matches
+ // the contents of the new file in which no leases have been
+ // stored.
+ std::string new_file_contents =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source,pool_id\n";
+
+ // This string contains the contents of the current lease file.
+ // It should not be moved.
+ std::string current_file_contents = new_file_contents +
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,200,"
+ "8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
+ "8,100,0,7,0,1,1,,,1,{ \"foo\": true },,,0\n";
+ LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv"));
+ current_file.writeFile(current_file_contents);
+
+ // This string contains the contents of the copy file. It should
+ // be processed and moved to the previous file. As there is only
+ // one lease the processing should result in the previous file being
+ // the same.
+ std::string input_file_contents = new_file_contents +
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
+ "8,100,0,7,128,1,1,,,1,{ \"foo\": true },,,0\n";
+ LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.1"));
+ input_file.writeFile(input_file_contents);
+
+ // Create the backend.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ pmap["lfc-interval"] = "1";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap));
+
+ // Try to run the lease file cleanup.
+ ASSERT_NO_THROW(lease_mgr->lfcCallback());
+
+ // The current lease file should not have been touched.
+ ASSERT_TRUE(current_file.exists());
+ EXPECT_EQ(current_file_contents, current_file.readFile());
+
+ // Wait for the LFC process to complete.
+ ASSERT_TRUE(waitForProcess(*lease_mgr, 5));
+
+ // And make sure it has returned an exit status of 0.
+ EXPECT_EQ(0, lease_mgr->getLFCExitStatus())
+ << "Executing the LFC process failed: make sure that"
+ " the kea-lfc program has been compiled.";
+
+ // The LFC should have processed the lease and moved it to the previous
+ // file - leasefile6_0.csv.2
+ LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2"), false);
+ ASSERT_TRUE(previous_file.exists());
+ // And this file should contain the contents of the copy file.
+ EXPECT_EQ(input_file_contents, previous_file.readFile());
+
+ // The input file should have been removed
+ ASSERT_FALSE(input_file.exists());
+}
+
+/// @brief Checks that adding/getting/deleting a Lease6 object works.
+TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
+ startBackend(V6);
+ testAddGetDelete6();
+}
+
+/// @brief Checks that adding/getting/deleting a Lease6 object works.
+TEST_F(MemfileLeaseMgrTest, addGetDelete6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testAddGetDelete6();
+}
+
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4 (by address) and deleteLease (with an
+/// IPv4 address) works.
+TEST_F(MemfileLeaseMgrTest, basicLease4) {
+ startBackend(V4);
+ testBasicLease4();
+}
+
+/// @brief Basic Lease4 Checks
+TEST_F(MemfileLeaseMgrTest, basicLease4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testBasicLease4();
+}
+
+/// @todo Write more memfile tests
+
+/// @brief Simple test about lease4 retrieval through client id method
+TEST_F(MemfileLeaseMgrTest, getLease4ClientId) {
+ startBackend(V4);
+ testGetLease4ClientId();
+}
+
+/// @brief Simple test about lease4 retrieval through client id method
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4ClientId();
+}
+
+/// @brief Checks that lease4 retrieval client id is null is working
+TEST_F(MemfileLeaseMgrTest, getLease4NullClientId) {
+ startBackend(V4);
+ testGetLease4NullClientId();
+}
+
+/// @brief Checks that lease4 retrieval client id is null is working
+TEST_F(MemfileLeaseMgrTest, getLease4NullClientIdMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4NullClientId();
+}
+
+/// @brief Checks lease4 retrieval through HWAddr
+TEST_F(MemfileLeaseMgrTest, getLease4HWAddr1) {
+ startBackend(V4);
+ testGetLease4HWAddr1();
+}
+
+/// @brief Checks lease4 retrieval through HWAddr
+TEST_F(MemfileLeaseMgrTest, getLease4HWAddr1MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4HWAddr1();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DUID and IAID.
+TEST_F(MemfileLeaseMgrTest, getLease4HWAddr2) {
+ startBackend(V4);
+ testGetLease4HWAddr2();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(MemfileLeaseMgrTest, getLease4HWAddr2MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4HWAddr2();
+}
+
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+/// updateLease4() and deleteLease can handle NULL client-id.
+/// (client-id is optional and may not be present)
+TEST_F(MemfileLeaseMgrTest, lease4NullClientId) {
+ startBackend(V4);
+ testLease4NullClientId();
+}
+
+/// @brief Basic Lease4 Checks
+TEST_F(MemfileLeaseMgrTest, lease4NullClientIdMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testLease4NullClientId();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of hardware address and subnet ID
+TEST_F(MemfileLeaseMgrTest, DISABLED_getLease4HwaddrSubnetId) {
+ /// @todo: fails on memfile. It's probably a memfile bug.
+ startBackend(V4);
+ testGetLease4HWAddrSubnetId();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+TEST_F(MemfileLeaseMgrTest, DISABLED_getLease4HwaddrSubnetIdMultiThread) {
+ /// @todo: fails on memfile. It's probably a memfile bug.
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4HWAddrSubnetId();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// the Client ID.
+TEST_F(MemfileLeaseMgrTest, getLease4ClientId2) {
+ startBackend(V4);
+ testGetLease4ClientId2();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID
+TEST_F(MemfileLeaseMgrTest, getLease4ClientId2MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4ClientId2();
+}
+
+/// @brief Get Lease4 by client ID
+///
+/// Check that the system can cope with a client ID of any size.
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSize) {
+ startBackend(V4);
+ testGetLease4ClientIdSize();
+}
+
+/// @brief Get Lease4 by client ID
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSizeMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4ClientIdSize();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of client and subnet IDs.
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSubnetId) {
+ startBackend(V4);
+ testGetLease4ClientIdSubnetId();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+TEST_F(MemfileLeaseMgrTest, getLease4ClientIdSubnetIdMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease4ClientIdSubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases for a specified subnet id are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases4SubnetId) {
+ startBackend(V4);
+ testGetLeases4SubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases for a specified subnet id are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases4SubnetIdMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases4SubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases with a specified hostname are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases4Hostname) {
+ startBackend(V4);
+ testGetLeases4Hostname();
+}
+
+/// @brief This test checks that all IPv4 leases with a specified hostname are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases4HostnameMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases4Hostname();
+}
+
+/// @brief This test checks that all IPv4 leases are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases4) {
+ startBackend(V4);
+ testGetLeases4();
+}
+
+/// @brief This test checks that all IPv4 leases are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases4();
+}
+
+/// @brief Test that a range of IPv4 leases is returned with paging.
+TEST_F(MemfileLeaseMgrTest, getLeases4Paged) {
+ startBackend(V4);
+ testGetLeases4Paged();
+}
+
+/// @brief Test that a range of IPv4 leases is returned with paging.
+TEST_F(MemfileLeaseMgrTest, getLeases4PagedMultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases4Paged();
+}
+
+/// @brief This test checks that all IPv6 leases for a specified subnet id are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases6SubnetId) {
+ startBackend(V6);
+ testGetLeases6SubnetId();
+}
+
+/// @brief This test checks that all IPv6 leases for a specified subnet id are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases6SubnetIdMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases6SubnetId();
+}
+
+/// @brief This test checks that all IPv6 leases with a specified hostname are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases6Hostname) {
+ startBackend(V6);
+ testGetLeases6Hostname();
+}
+
+/// @brief This test checks that all IPv6 leases with a specified hostname are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases6HostnameMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases6Hostname();
+}
+
+/// @brief This test adds 3 leases and verifies fetch by DUID.
+/// Verifies retrieval of non existant DUID fails
+TEST_F(MemfileLeaseMgrTest, getLeases6Duid) {
+ startBackend(V6);
+ testGetLeases6Duid();
+}
+
+/// @brief This test adds 3 leases and verifies fetch by DUID.
+TEST_F(MemfileLeaseMgrTest, getLeases6DuidMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases6Duid();
+}
+
+/// @brief This test checks that all IPv6 leases are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases6) {
+ startBackend(V6);
+ testGetLeases6();
+}
+
+/// @brief This test checks that all IPv6 leases are returned.
+TEST_F(MemfileLeaseMgrTest, getLeases6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases6();
+}
+
+/// @brief Test that a range of IPv6 leases is returned with paging.
+TEST_F(MemfileLeaseMgrTest, getLeases6Paged) {
+ startBackend(V6);
+ testGetLeases6Paged();
+}
+
+/// @brief Test that a range of IPv6 leases is returned with paging.
+TEST_F(MemfileLeaseMgrTest, getLeases6PagedMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases6Paged();
+}
+
+/// @brief Basic Lease6 Checks
+///
+/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
+/// IPv6 address) works.
+TEST_F(MemfileLeaseMgrTest, basicLease6) {
+ startBackend(V6);
+ testBasicLease6();
+}
+
+/// @brief Basic Lease6 Checks
+TEST_F(MemfileLeaseMgrTest, basicLease6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testBasicLease6();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DUID and IAID.
+/// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
+/// const DUID& duid, uint32_t iaid) const is not implemented yet.
+TEST_F(MemfileLeaseMgrTest, getLeases6DuidIaid) {
+ startBackend(V6);
+ testGetLeases6DuidIaid();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID
+TEST_F(MemfileLeaseMgrTest, getLeases6DuidIaidMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases6DuidIaid();
+}
+
+/// @brief Check that the system can cope with a DUID of allowed size.
+TEST_F(MemfileLeaseMgrTest, getLeases6DuidSize) {
+ startBackend(V6);
+ testGetLeases6DuidSize();
+}
+
+/// @brief Check that the system can cope with a DUID of allowed size.
+TEST_F(MemfileLeaseMgrTest, getLeases6DuidSizeMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLeases6DuidSize();
+}
+
+/// @brief Check that the expired DHCPv4 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(MemfileLeaseMgrTest, getExpiredLeases4) {
+ startBackend(V4);
+ testGetExpiredLeases4();
+}
+
+/// @brief Check that the expired DHCPv4 leases can be retrieved.
+TEST_F(MemfileLeaseMgrTest, getExpiredLeases4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetExpiredLeases4();
+}
+
+/// @brief Check that the expired DHCPv6 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(MemfileLeaseMgrTest, getExpiredLeases6) {
+ startBackend(V6);
+ testGetExpiredLeases6();
+}
+
+/// @brief Check that the expired DHCPv6 leases can be retrieved.
+TEST_F(MemfileLeaseMgrTest, getExpiredLeases6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetExpiredLeases6();
+}
+
+/// @brief Check that expired reclaimed DHCPv6 leases are removed.
+TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases6) {
+ startBackend(V6);
+ testDeleteExpiredReclaimedLeases6();
+}
+
+/// @brief Check that expired reclaimed DHCPv6 leases are removed.
+TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testDeleteExpiredReclaimedLeases6();
+}
+
+/// @brief Check that expired reclaimed DHCPv4 leases are removed.
+TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases4) {
+ startBackend(V4);
+ testDeleteExpiredReclaimedLeases4();
+}
+
+/// @brief Check that expired reclaimed DHCPv4 leases are removed.
+TEST_F(MemfileLeaseMgrTest, deleteExpiredReclaimedLeases4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testDeleteExpiredReclaimedLeases4();
+}
+
+/// @brief Check that getLease6 methods discriminate by lease type.
+///
+/// Adds six leases, two per lease type all with the same duid and iad but
+/// with alternating subnet_ids.
+/// It then verifies that all of getLeases6() method variants correctly
+/// discriminate between the leases based on lease type alone.
+/// @todo: Disabled, because type parameter in Memfile_LeaseMgr::getLease6
+/// (Lease::Type, const isc::asiolink::IOAddress& addr) const is not used.
+TEST_F(MemfileLeaseMgrTest, lease6LeaseTypeCheck) {
+ startBackend(V6);
+ testLease6LeaseTypeCheck();
+}
+
+/// @brief Check that getLease6 methods discriminate by lease type.
+TEST_F(MemfileLeaseMgrTest, lease6LeaseTypeCheckMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testLease6LeaseTypeCheck();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DIUID and IAID.
+TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetId) {
+ startBackend(V6);
+ testGetLease6DuidIaidSubnetId();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease6DuidIaidSubnetId();
+}
+
+/// @brief Checks that getLease6(type, duid, iaid, subnet-id) works with different
+/// DUID sizes
+TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
+ startBackend(V6);
+ testGetLease6DuidIaidSubnetIdSize();
+}
+
+/// @brief Checks that getLease6(type, duid, iaid, subnet-id) works with different
+/// DUID sizes
+TEST_F(MemfileLeaseMgrTest, getLease6DuidIaidSubnetIdSizeMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetLease6DuidIaidSubnetIdSize();
+}
+
+/// @brief Lease4 update tests
+///
+/// Checks that we are able to update a lease in the database.
+/// @todo: Disabled, because memfile does not throw when lease is updated.
+/// We should reconsider if lease{4,6} structures should have a limit
+/// implemented in them.
+TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease4) {
+ startBackend(V4);
+ testUpdateLease4();
+}
+
+/// @brief Lease4 update tests
+TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testUpdateLease4();
+}
+
+/// @brief Lease6 update tests
+///
+/// Checks that we are able to update a lease in the database.
+/// @todo: Disabled, because memfile does not throw when lease is updated.
+/// We should reconsider if lease{4,6} structures should have a limit
+/// implemented in them.
+TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease6) {
+ startBackend(V6);
+ testUpdateLease6();
+}
+
+/// @brief Lease6 update tests
+TEST_F(MemfileLeaseMgrTest, DISABLED_updateLease6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testUpdateLease6();
+}
+
+/// @brief DHCPv4 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MemfileLeaseMgrTest, testRecreateLease4) {
+ startBackend(V4);
+ testRecreateLease4();
+}
+
+/// @brief DHCPv4 Lease recreation tests
+TEST_F(MemfileLeaseMgrTest, testRecreateLease4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testRecreateLease4();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MemfileLeaseMgrTest, testRecreateLease6) {
+ startBackend(V6);
+ testRecreateLease6();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+TEST_F(MemfileLeaseMgrTest, testRecreateLease6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testRecreateLease6();
+}
+
+// The following tests are not applicable for memfile. When adding
+// new tests to the list here, make sure to provide brief explanation
+// why they are not applicable:
+//
+// testGetLease4HWAddrSubnetIdSize() - memfile just keeps Lease structure
+// and does not do any checks of HWAddr content
+
+/// @brief Checks that null DUID is not allowed.
+/// Test is disabled as Memfile does not currently defend against a null DUID.
+TEST_F(MemfileLeaseMgrTest, DISABLED_nullDuid) {
+ // Create leases, although we need only one.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ leases[1]->duid_.reset();
+ ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+}
+
+/// @brief Checks that null DUID is not allowed.
+TEST_F(MemfileLeaseMgrTest, DISABLED_nullDuidMultiThread) {
+ MultiThreadingMgr::instance().setMode(true);
+ // Create leases, although we need only one.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ leases[1]->duid_.reset();
+ ASSERT_THROW(lmptr_->addLease(leases[1]), DbOperationError);
+}
+
+/// @brief Tests whether memfile can store and retrieve hardware addresses
+TEST_F(MemfileLeaseMgrTest, testLease6Mac) {
+ startBackend(V6);
+ testLease6MAC();
+}
+
+/// @brief Tests whether memfile can store and retrieve hardware addresses
+TEST_F(MemfileLeaseMgrTest, testLease6MacMultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testLease6MAC();
+}
+
+/// @brief Check that memfile reports version correctly.
+TEST_F(MemfileLeaseMgrTest, versionCheck) {
+ // Check that V4 backend reports versions correctly.
+ startBackend(V4);
+ testVersion(Memfile_LeaseMgr::MAJOR_VERSION_V4,
+ Memfile_LeaseMgr::MINOR_VERSION_V4);
+ LeaseMgrFactory::destroy();
+
+ // Check that V6 backends reports them ok, too.
+ startBackend(V6);
+ testVersion(Memfile_LeaseMgr::MAJOR_VERSION_V6,
+ Memfile_LeaseMgr::MINOR_VERSION_V6);
+ LeaseMgrFactory::destroy();
+}
+
+/// @brief Checks that declined IPv4 leases can be returned correctly.
+TEST_F(MemfileLeaseMgrTest, getDeclined4) {
+ startBackend(V4);
+ testGetDeclinedLeases4();
+}
+
+/// @brief Checks that declined IPv4 leases can be returned correctly.
+TEST_F(MemfileLeaseMgrTest, getDeclined4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetDeclinedLeases4();
+}
+
+/// @brief Checks that declined IPv6 leases can be returned correctly.
+TEST_F(MemfileLeaseMgrTest, getDeclined6) {
+ startBackend(V6);
+ testGetDeclinedLeases6();
+}
+
+/// @brief Checks that declined IPv6 leases can be returned correctly.
+TEST_F(MemfileLeaseMgrTest, getDeclined6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testGetDeclinedLeases6();
+}
+
+/// @brief This test checks that the backend reads DHCPv4 lease data from multiple
+/// files.
+TEST_F(MemfileLeaseMgrTest, load4MultipleLeaseFiles) {
+ LeaseFileIO io2(getLeaseFilePath("leasefile4_0.csv.2"));
+ io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,,0\n"
+ "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,1,,0\n");
+
+ LeaseFileIO io1(getLeaseFilePath("leasefile4_0.csv.1"));
+ io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,1,,0\n"
+ "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,1,,0\n"
+ "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,1,,0\n");
+
+ LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv"));
+ io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,1,,0\n"
+ "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,1,,0\n");
+
+ startBackend(V4);
+
+ // This lease only exists in the second file and the cltt should
+ // be 0.
+ Lease4Ptr lease = lmptr_->getLease4(IOAddress("192.0.2.1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // This lease only exists in the first file and the cltt should
+ // be 0.
+ lease = lmptr_->getLease4(IOAddress("192.0.2.2"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // This lease only exists in the third file and the cltt should
+ // be 0.
+ lease = lmptr_->getLease4(IOAddress("192.0.2.10"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // This lease exists in the first and second file and the cltt
+ // should be calculated using the expiration time and the
+ // valid lifetime from the second file.
+ lease = lmptr_->getLease4(IOAddress("192.0.2.11"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(200, lease->cltt_);
+
+ // This lease exists in the second and third file and the cltt
+ // should be calculated using the expiration time and the
+ // valid lifetime from the third file.
+ lease = lmptr_->getLease4(IOAddress("192.0.2.12"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(200, lease->cltt_);
+}
+
+/// @brief This test checks that the lease database backend loads the file with
+/// the .completed postfix instead of files with postfixes .1 and .2 if
+/// the file with .completed postfix exists.
+TEST_F(MemfileLeaseMgrTest, load4CompletedFile) {
+ LeaseFileIO io2(getLeaseFilePath("leasefile4_0.csv.2"));
+ io2.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,,0\n"
+ "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,200,8,1,1,,1,,0\n");
+
+ LeaseFileIO io1(getLeaseFilePath("leasefile4_0.csv.1"));
+ io1.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.1,01:01:01:01:01:01,,200,200,8,1,1,,1,,0\n"
+ "192.0.2.11,bb:bb:bb:bb:bb:bb,,200,400,8,1,1,,1,,0\n"
+ "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,200,8,1,1,,1,,0\n");
+
+ LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv"));
+ io.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.10,0a:0a:0a:0a:0a:0a,,200,200,8,1,1,,1,,0\n"
+ "192.0.2.12,cc:cc:cc:cc:cc:cc,,200,400,8,1,1,,1,,0\n");
+
+ LeaseFileIO ioc(getLeaseFilePath("leasefile4_0.csv.completed"));
+ ioc.writeFile("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+ "192.0.2.13,ff:ff:ff:ff:ff:ff,,200,200,8,1,1,,1,,0\n");
+
+ startBackend(V4);
+
+ // We expect that this file only holds leases that belong to the
+ // lease file or to the file with .completed postfix.
+ Lease4Ptr lease = lmptr_->getLease4(IOAddress("192.0.2.10"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ lease = lmptr_->getLease4(IOAddress("192.0.2.12"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(200, lease->cltt_);
+
+ // This lease is in the .completed file.
+ lease = lmptr_->getLease4(IOAddress("192.0.2.13"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // Leases from the .1 and .2 files should not be loaded.
+ EXPECT_FALSE(lmptr_->getLease4(IOAddress("192.0.2.11")));
+ EXPECT_FALSE(lmptr_->getLease4(IOAddress("192.0.2.1")));
+}
+
+/// @brief This test checks that backend constructor refuses to load leases from the
+/// lease files if the LFC is in progress.
+TEST_F(MemfileLeaseMgrTest, load4LFCInProgress) {
+ // Create the backend configuration.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ pmap["lfc-interval"] = "1";
+
+ // Create a pid file holding the PID of the current process. Choosing the
+ // pid of the current process guarantees that when the backend starts up
+ // the process is alive.
+ PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+ pid_file.write();
+
+ // There is a pid file and the process which pid is in the file is
+ // running, so the backend should refuse to start.
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)),
+ DbOpenError);
+
+ // Remove the pid file, and retry. The backend should be created.
+ pid_file.deleteFile();
+ ASSERT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+}
+
+/// @brief This test checks that the backend reads DHCPv6 lease data from multiple
+/// files.
+TEST_F(MemfileLeaseMgrTest, load6MultipleLeaseFiles) {
+ LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2"));
+ io2.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1"));
+ io1.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "300,800,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
+ io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "400,1000,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ startBackend(V6);
+
+ // This lease only exists in the first file and the cltt should be 0.
+ Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // This lease exists in the first and second file and the cltt should
+ // be calculated using the expiration time and the valid lifetime
+ // from the second file.
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(500, lease->cltt_);
+
+ // This lease only exists in the second file and the cltt should be 0.
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // This lease exists in the second and third file and the cltt should
+ // be calculated using the expiration time and the valid lifetime
+ // from the third file.
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(600, lease->cltt_);
+
+ // This lease only exists in the third file and the cltt should be 0.
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+}
+
+/// @brief This test checks that the backend reads DHCPv6 lease data from the
+/// leasefile without the postfix and the file with a .1 postfix when
+/// the file with the .2 postfix is missing.
+TEST_F(MemfileLeaseMgrTest, load6MultipleNoSecondFile) {
+ LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1"));
+ io1.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "300,800,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
+ io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "400,1000,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ startBackend(V6);
+
+ // Check that leases from the leasefile6_0 and leasefile6_0.1 have
+ // been loaded.
+ Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(500, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(600, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // Make sure that a lease which is not in those files is not loaded.
+ EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")));
+}
+
+/// @brief This test checks that the backend reads DHCPv6 lease data from the
+/// leasefile without the postfix and the file with a .2 postfix when
+/// the file with the .1 postfix is missing.
+TEST_F(MemfileLeaseMgrTest, load6MultipleNoFirstFile) {
+ LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2"));
+ io2.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
+ io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "400,1000,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ startBackend(V6);
+
+ // Verify that leases which belong to the leasefile6_0.csv and
+ // leasefile6_0.2 are loaded.
+ Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(600, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ // A lease which doesn't belong to these files should not be loaded.
+ EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3")));
+}
+
+/// @brief This test checks that the lease database backend loads the file with
+/// the .completed postfix instead of files with postfixes .1 and .2 if
+/// the file with .completed postfix exists.
+TEST_F(MemfileLeaseMgrTest, load6CompletedFile) {
+ LeaseFileIO io2(getLeaseFilePath("leasefile6_0.csv.2"));
+ io2.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ LeaseFileIO io1(getLeaseFilePath("leasefile6_0.csv.1"));
+ io1.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::3,03:03:03:03:03:03:03:03:03:03:03:03:03,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "300,800,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
+ io.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "400,1000,8,100,0,7,0,1,1,,,1,,,,0\n"
+ "2001:db8:1::5,05:05:05:05:05:05:05:05:05:05:05:05:05,"
+ "200,200,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ LeaseFileIO ioc(getLeaseFilePath("leasefile6_0.csv.completed"));
+ ioc.writeFile("address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+ "2001:db8:1::125,ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff,"
+ "400,1000,8,100,0,7,0,1,1,,,1,,,,0\n");
+
+ startBackend(V6);
+
+ // We expect that this file only holds leases that belong to the
+ // lease file or to the file with .completed postfix.
+ Lease6Ptr lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(600, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(0, lease->cltt_);
+
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::125"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(600, lease->cltt_);
+
+ // Leases from the .1 and .2 files should not be loaded.
+ EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")));
+ EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2")));
+ EXPECT_FALSE(lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::3")));
+}
+
+/// @brief This test checks that backend constructor refuses to load leases from the
+/// lease files if the LFC is in progress.
+TEST_F(MemfileLeaseMgrTest, load6LFCInProgress) {
+ // Create the backend configuration.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ pmap["lfc-interval"] = "1";
+
+ // Create a pid file holding the PID of the current process. Choosing the
+ // pid of the current process guarantees that when the backend starts up
+ // the process is alive.
+ PIDFile pid_file(Memfile_LeaseMgr::appendSuffix(pmap["name"], Memfile_LeaseMgr::FILE_PID));
+ pid_file.write();
+
+ // There is a pid file and the process which pid is in the file is
+ // running, so the backend should refuse to start.
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ ASSERT_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)),
+ DbOpenError);
+
+ // Remove the pid file, and retry. The backend should be created.
+ pid_file.deleteFile();
+ ASSERT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+}
+
+/// @brief Verifies that LFC is automatically run during MemfileLeasemMgr construction
+/// when the lease file(s) being loaded need to be upgraded.
+TEST_F(MemfileLeaseMgrTest, leaseUpgrade4) {
+ // Create header strings for each schema
+ std::string header_1_0 =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname\n";
+
+ std::string header_2_0 =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context\n";
+
+ std::string header_3_0 =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ // Create 1.0 Schema current lease file with two entries for
+ // the same lease
+ std::string current_file_contents = header_1_0 +
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,\n"
+ "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,\n";
+ LeaseFileIO current_file(getLeaseFilePath("leasefile4_0.csv"));
+ current_file.writeFile(current_file_contents);
+
+ // Create 1.0 Schema previous lease file, with two entries for
+ // a another lease
+ std::string previous_file_contents = header_1_0 +
+ "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,\n"
+ "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,\n";
+ LeaseFileIO previous_file(getLeaseFilePath("leasefile4_0.csv.2"));
+ previous_file.writeFile(previous_file_contents);
+
+ // Create the backend.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ pmap["lfc-interval"] = "0";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap));
+
+ // Since lease files are loaded during lease manager
+ // constructor, LFC should get launched automatically.
+ // The new lease file should be 3.0 schema and have no entries
+ ASSERT_TRUE(current_file.exists());
+ EXPECT_EQ(header_3_0, current_file.readFile());
+
+ // Wait for the LFC process to complete and
+ // make sure it has returned an exit status of 0.
+ ASSERT_TRUE(waitForProcess(*lease_mgr, 2));
+
+ ASSERT_EQ(0, lease_mgr->getLFCExitStatus())
+ << "Executing the LFC process failed: make sure that"
+ " the kea-lfc program has been compiled.";
+
+ // The LFC should have created a 3.0 schema completion file with the
+ // one entry for each lease and moved it to leasefile4_0.csv.2
+ LeaseFileIO input_file(getLeaseFilePath("leasefile4_0.csv.2"), false);
+ ASSERT_TRUE(input_file.exists());
+
+ // Verify cleaned, converted contents
+ std::string result_file_contents = header_3_0 +
+ "192.0.2.2,02:02:02:02:02:02,,200,800,8,1,1,,0,,0\n"
+ "192.0.2.3,03:03:03:03:03:03,,200,800,8,1,1,,0,,0\n";
+ EXPECT_EQ(result_file_contents, input_file.readFile());
+}
+
+/// @brief Verifies that LFC is automatically run during MemfileLeasemMgr construction
+/// when the lease file(s) being loaded need to be upgraded.
+TEST_F(MemfileLeaseMgrTest, leaseUpgrade6) {
+ // Create header strings for all schemas.
+ std::string header_1_0 =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname\n";
+
+ std::string header_2_0 =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr\n";
+
+ std::string header_3_0 =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context\n";
+
+ std::string header_4_0 =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source\n";
+
+ std::string header_5_0 =
+ "address,duid,valid_lifetime,expire,subnet_id,"
+ "pref_lifetime,lease_type,iaid,prefix_len,fqdn_fwd,"
+ "fqdn_rev,hostname,hwaddr,state,user_context,"
+ "hwtype,hwaddr_source,pool_id\n";
+
+ // The current lease file is schema 1.0 and has two entries for
+ // the same lease
+ std::string current_file_contents = header_1_0 +
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,200,"
+ "8,100,0,7,0,1,1,,\n"
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
+ "8,100,0,7,0,1,1,,\n";
+ LeaseFileIO current_file(getLeaseFilePath("leasefile6_0.csv"));
+ current_file.writeFile(current_file_contents);
+
+ // The previous lease file is schema 2.0 and has two entries for
+ // a different lease
+ std::string previous_file_contents = header_2_0 +
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,200,"
+ "8,100,0,7,0,1,1,,11:22:33:44:55\n"
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
+ "8,100,0,7,0,1,1,,11:22:33:44:55\n";
+ LeaseFileIO previous_file(getLeaseFilePath("leasefile6_0.csv.2"));
+ previous_file.writeFile(previous_file_contents);
+
+ // Create the backend.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ pmap["lfc-interval"] = "0";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr(new NakedMemfileLeaseMgr(pmap));
+
+ // Since lease files are loaded during lease manager
+ // constructor, LFC should get launched automatically.
+ // The new lease file should been 5.0 and contain no leases.
+ ASSERT_TRUE(current_file.exists());
+ EXPECT_EQ(header_5_0, current_file.readFile());
+
+ // Wait for the LFC process to complete and
+ // make sure it has returned an exit status of 0.
+ ASSERT_TRUE(waitForProcess(*lease_mgr, 2));
+
+ ASSERT_EQ(0, lease_mgr->getLFCExitStatus())
+ << "Executing the LFC process failed: make sure that"
+ " the kea-lfc program has been compiled.";
+
+ // The LFC should have created a 5.0 schema cleaned file with one entry
+ // for each lease as leasefile6_0.csv.2
+ LeaseFileIO input_file(getLeaseFilePath("leasefile6_0.csv.2"), false);
+ ASSERT_TRUE(input_file.exists());
+
+ // Verify cleaned, converted contents
+ std::string result_file_contents = header_5_0 +
+ "2001:db8:1::1,00:01:02:03:04:05:06:0a:0b:0c:0d:0e:0f,200,800,"
+ "8,100,0,7,128,1,1,,,0,,,,0\n"
+ "2001:db8:1::2,01:01:01:01:01:01:01:01:01:01:01:01:01,200,800,"
+ "8,100,0,7,128,1,1,,11:22:33:44:55,0,,1,0,0\n";
+ EXPECT_EQ(result_file_contents, input_file.readFile());
+}
+
+/// @brief This test verifies that the indexes of the container holding
+/// DHCPv4 leases are updated correctly when a lease is updated.
+TEST_F(MemfileLeaseMgrTest, lease4ContainerIndexUpdate) {
+
+ const uint32_t seed = 12345678; // Used to initialize the random generator
+ const uint32_t leases_cnt = 100; // Number of leases generated per round.
+ const uint32_t updates_cnt = 5; // Number of times existing leases are updated
+
+ const string leasefile(getLeaseFilePath("leasefile4_0.csv"));
+
+ std::string dbaccess = "universe=4 type=memfile persist=true lfc-interval=0 name=";
+ dbaccess += leasefile;
+
+ LeaseMgrFactory::create(dbaccess);
+
+ srand(seed);
+
+ IOAddress addr("10.0.0.1"); // Let's generate leases sequentially
+
+ // Recreate Memfile_LeaseMgr.
+ LeaseMgrFactory::destroy();
+ ASSERT_NO_THROW(LeaseMgrFactory::create(dbaccess));
+ lmptr_ = &(LeaseMgrFactory::instance());
+
+ // We will store addresses here, so it will be easier to randomly
+ // pick a lease.
+ std::vector<IOAddress> lease_addresses;
+
+ // Generate random leases. We remember their addresses in
+ // lease_addresses.
+ for (uint32_t i = 0; i < leases_cnt; ++i) {
+ Lease4Ptr lease = initiateRandomLease4(addr);
+ lease_addresses.push_back(addr);
+ ASSERT_NO_THROW(lmptr_->addLease(lease));
+ addr = IOAddress::increase(addr);
+ }
+
+ // Check that we inserted correct number of leases.
+ ASSERT_EQ(leases_cnt, lease_addresses.size());
+
+ // Now, conduct updates. We call initiateRandomLease4(), so most
+ // of the fields are randomly changed. The only constant field
+ // is the address.
+ for (uint32_t i = 0; i < updates_cnt; ++i) {
+ uint32_t offset = random() % lease_addresses.size();
+ Lease4Ptr existing(lmptr_->getLease4(lease_addresses[offset]));
+ Lease4Ptr updated(initiateRandomLease4(lease_addresses[offset]));
+
+ // Update a lease with new data but preserve lease address.
+ // This update should also cause lease container indexes to
+ // be updated.
+ ASSERT_NO_THROW(lmptr_->updateLease4(updated))
+ << "Attempt " << i << " out of " << updates_cnt
+ << ":Failed to update lease for address "
+ << lease_addresses[offset];
+ }
+
+ // Re-create lease manager to cause it to reload leases
+ // from a lease file. We want to make sure that lease
+ // container is rebuilt correctly and the indexes are
+ // consistent with lease information held.
+ ASSERT_NO_THROW({
+ // Recreate Memfile_LeaseMgr.
+ LeaseMgrFactory::destroy();
+ LeaseMgrFactory::create(dbaccess);
+ lmptr_ = &(LeaseMgrFactory::instance());
+ });
+
+ // Ok, let's check if the leases are really accessible.
+ // First, build an array of leases. Get them by address.
+ // This should work in general, as we haven't updated the addresses.
+ std::vector<Lease4Ptr> leases;
+ for (uint32_t i = 0; i < lease_addresses.size(); ++i) {
+ Lease4Ptr from_mgr = lmptr_->getLease4(lease_addresses[i]);
+ ASSERT_TRUE(from_mgr) << "Lease for address " << lease_addresses[i].toText()
+ << " not found";
+ leases.push_back(from_mgr);
+ }
+
+ ASSERT_EQ(leases_cnt, leases.size());
+
+ // Now do the actual checks.
+ for (uint32_t i = 0; i < leases.size(); ++i) {
+ Lease4Ptr tested = leases[i];
+
+ // Get the lease by different access patterns.
+ // In properly working lease manager all queries should return
+ // exactly the same lease.
+
+ std::string error_desc = " which indicates that the lease indexes were"
+ " not updated correctly when the lease was updated.";
+
+ // Retrieve lease by address.
+ Lease4Ptr lease_by_address = lmptr_->getLease4(tested->addr_);
+ ASSERT_TRUE(lease_by_address)
+ << "Lease " << tested->addr_.toText()
+ << " not found by getLease4(addr)"
+ << error_desc;
+ detailCompareLease(tested, lease_by_address);
+
+ // Retrieve lease by HW address and subnet id.
+ Lease4Ptr lease_by_hwaddr_subnet = lmptr_->getLease4(*tested->hwaddr_,
+ tested->subnet_id_);
+ ASSERT_TRUE(lease_by_hwaddr_subnet)
+ << "Lease " << tested->addr_.toText()
+ << " not found by getLease4(hwaddr, subnet_id)"
+ << error_desc;
+ detailCompareLease(tested, lease_by_hwaddr_subnet);
+
+ // Retrieve lease by client identifier and subnet id.
+ Lease4Ptr lease_by_clientid_subnet = lmptr_->getLease4(*tested->client_id_,
+ tested->subnet_id_);
+ ASSERT_TRUE(lease_by_clientid_subnet)
+ << "Lease " << tested->addr_.toText()
+ << " not found by getLease4(clientid, subnet_id)"
+ << error_desc;
+ detailCompareLease(tested, lease_by_clientid_subnet);
+
+ // Retrieve lease by HW address.
+ Lease4Collection leases_by_hwaddr = lmptr_->getLease4(*tested->hwaddr_);
+ ASSERT_EQ(1, leases_by_hwaddr.size());
+ detailCompareLease(tested, leases_by_hwaddr[0]);
+
+ // Retrieve lease by client identifier.
+ Lease4Collection leases_by_client_id = lmptr_->getLease4(*tested->client_id_);
+ ASSERT_EQ(1, leases_by_client_id.size());
+ detailCompareLease(tested, leases_by_client_id[0]);
+ }
+}
+
+/// @brief This test verifies that the indexes of the container holding
+/// DHCPv4 leases are updated correctly when a lease is updated.
+TEST_F(MemfileLeaseMgrTest, lease6ContainerIndexUpdate) {
+
+ const uint32_t seed = 12345678; // Used to initialize the random generator
+ const uint32_t leases_cnt = 100; // Number of leases generated per round.
+ const uint32_t updates_cnt = 5; // Number of times existing leases are updated
+
+ const string leasefile(getLeaseFilePath("leasefile6_0.csv"));
+
+ std::string dbaccess = "universe=6 type=memfile persist=true lfc-interval=0 name=";
+ dbaccess += leasefile;
+
+ LeaseMgrFactory::create(dbaccess);
+
+ srand(seed);
+
+ IOAddress addr("2001:db8:1::1"); // Let's generate leases sequentially
+
+ // Recreate Memfile_LeaseMgr.
+ LeaseMgrFactory::destroy();
+ ASSERT_NO_THROW(LeaseMgrFactory::create(dbaccess));
+ lmptr_ = &(LeaseMgrFactory::instance());
+
+ // We will store addresses here, so it will be easier to randomly
+ // pick a lease.
+ std::vector<IOAddress> lease_addresses;
+
+ // Generate random leases. We remember their addresses in
+ // lease_addresses.
+ for (uint32_t i = 0; i < leases_cnt; ++i) {
+ Lease6Ptr lease = initiateRandomLease6(addr);
+ lease_addresses.push_back(addr);
+ ASSERT_NO_THROW(lmptr_->addLease(lease));
+ addr = IOAddress::increase(addr);
+ }
+
+ // Check that we inserted correct number of leases.
+ ASSERT_EQ(leases_cnt, lease_addresses.size());
+
+ // Now, conduct updates. We call initiateRandomLease6(), so most
+ // of the fields are randomly changed. The only constant field
+ // is the address.
+ for (uint32_t i = 0; i < updates_cnt; ++i) {
+ uint32_t offset = random() % lease_addresses.size();
+ Lease6Ptr existing(lmptr_->getLease6(Lease::TYPE_NA,
+ lease_addresses[offset]));
+ Lease6Ptr updated(initiateRandomLease6(lease_addresses[offset]));
+
+ // Update a lease with new data but preserve lease address.
+ // This update should also cause lease container indexes to
+ // be updated.
+ ASSERT_NO_THROW(lmptr_->updateLease6(updated))
+ << "Attempt " << i << " out of " << updates_cnt
+ << ":Failed to update lease for address "
+ << lease_addresses[offset];
+ }
+
+ // Re-create lease manager to cause it to reload leases
+ // from a lease file. We want to make sure that lease
+ // container is rebuilt correctly and the indexes are
+ // consistent with lease information held.
+ ASSERT_NO_THROW({
+ // Recreate Memfile_LeaseMgr.
+ LeaseMgrFactory::destroy();
+ LeaseMgrFactory::create(dbaccess);
+ lmptr_ = &(LeaseMgrFactory::instance());
+ });
+
+ // Ok, let's check if the leases are really accessible.
+ // First, build an array of leases. Get them by address.
+ // This should work in general, as we haven't updated the addresses.
+ std::vector<Lease6Ptr> leases;
+ for (uint32_t i = 0; i < lease_addresses.size(); ++i) {
+ Lease6Ptr from_mgr = lmptr_->getLease6(Lease::TYPE_NA,
+ lease_addresses[i]);
+ ASSERT_TRUE(from_mgr) << "Lease for address " << lease_addresses[i].toText()
+ << " not found";
+ leases.push_back(from_mgr);
+ }
+
+ ASSERT_EQ(leases_cnt, leases.size());
+
+ // Now do the actual checks.
+ for (uint32_t i = 0; i < leases.size(); ++i) {
+ Lease6Ptr tested = leases[i];
+
+ // Get the lease by different access patterns.
+ // In properly working lease manager all queries should return
+ // exactly the same lease.
+
+ std::string error_desc = " which indicates that the lease indexes were"
+ " not updated correctly when the lease was updated.";
+
+ // Retrieve lease by address.
+ Lease6Ptr lease_by_address = lmptr_->getLease6(Lease::TYPE_NA,
+ tested->addr_);
+ ASSERT_TRUE(lease_by_address)
+ << "Lease " << tested->addr_.toText()
+ << " not found by getLease6(addr)"
+ << error_desc;
+ detailCompareLease(tested, lease_by_address);
+
+ // Retrieve lease by type, DUID, IAID.
+ Lease6Collection leases_by_duid_iaid = lmptr_->getLeases6(tested->type_,
+ *tested->duid_,
+ tested->iaid_);
+ ASSERT_EQ(1, leases_by_duid_iaid.size());
+ ASSERT_TRUE(leases_by_duid_iaid[0])
+ << "Lease " << tested->addr_.toText()
+ << " not found by getLease6(type, duid, iaid)"
+ << error_desc;
+ detailCompareLease(tested, leases_by_duid_iaid[0]);
+
+ // Retrieve lease by type, DUID, IAID, subnet identifier.
+ Lease6Collection leases_by_duid_iaid_subnet =
+ lmptr_->getLeases6(tested->type_, *tested->duid_,
+ tested->iaid_, tested->subnet_id_);
+ ASSERT_EQ(1, leases_by_duid_iaid_subnet.size());
+ ASSERT_TRUE(leases_by_duid_iaid_subnet[0])
+ << "Lease " << tested->addr_.toText()
+ << " not found by getLease6(type, duid, iaid, subnet_id)"
+ << error_desc;
+ detailCompareLease(tested, leases_by_duid_iaid_subnet[0]);
+ }
+}
+
+/// @brief Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(MemfileLeaseMgrTest, recountLeaseStats4) {
+ startBackend(V4);
+ testRecountLeaseStats4();
+}
+
+/// @brief Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(MemfileLeaseMgrTest, recountLeaseStats6) {
+ startBackend(V6);
+ testRecountLeaseStats6();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(MemfileLeaseMgrTest, wipeLeases4) {
+ startBackend(V4);
+ testWipeLeases4();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(MemfileLeaseMgrTest, wipeLeases6) {
+ startBackend(V6);
+ testWipeLeases6();
+}
+
+/// @brief Tests v4 lease stats query variants.
+TEST_F(MemfileLeaseMgrTest, leaseStatsQuery4) {
+ startBackend(V4);
+ testLeaseStatsQuery4();
+}
+
+/// @brief Tests v6 lease stats query variants.
+TEST_F(MemfileLeaseMgrTest, leaseStatsQuery6) {
+ startBackend(V6);
+ testLeaseStatsQuery6();
+}
+
+/// @brief Tests v4 lease stats to be attributed to the wrong subnet.
+TEST_F(MemfileLeaseMgrTest, leaseStatsQueryAttribution4) {
+ startBackend(V4);
+ testLeaseStatsQueryAttribution4();
+}
+
+/// @brief Tests v6 lease stats to be attributed to the wrong subnet.
+TEST_F(MemfileLeaseMgrTest, leaseStatsQueryAttribution6) {
+ startBackend(V6);
+ testLeaseStatsQueryAttribution6();
+}
+
+TEST_F(MemfileLeaseMgrTest, checkVersion4) {
+ // Create the backend.
+ DatabaseConnection::ParameterMap parameters;
+ parameters["type"] = "memfile";
+ parameters["universe"] = "4";
+ parameters["name"] = getLeaseFilePath("leasefile4_0.csv");
+ std::unique_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ ASSERT_NO_THROW_LOG(lease_mgr.reset(new NakedMemfileLeaseMgr(parameters)));
+
+ // Get the backend version.
+ auto const& backend_version(lease_mgr->getVersion());
+ std::stringstream s;
+ s << backend_version.first << '.' << backend_version.second;
+
+ // Get the CSV version.
+ CSVLeaseFile4 f(getLeaseFilePath("leasefile4_0.csv"));
+
+ // They should match.
+ EXPECT_EQ(s.str(), f.getSchemaVersion());
+
+ // DBVersion too.
+ EXPECT_EQ("Memfile backend " + s.str(),
+ lease_mgr->getDBVersion(Memfile_LeaseMgr::V4));
+}
+
+TEST_F(MemfileLeaseMgrTest, checkVersion6) {
+ // Create the backend.
+ DatabaseConnection::ParameterMap parameters;
+ parameters["type"] = "memfile";
+ parameters["universe"] = "6";
+ parameters["name"] = getLeaseFilePath("leasefile6_0.csv");
+ std::unique_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ ASSERT_NO_THROW_LOG(lease_mgr.reset(new NakedMemfileLeaseMgr(parameters)));
+
+ // Get the backend version.
+ auto const& backend_version(lease_mgr->getVersion());
+ std::stringstream s;
+ s << backend_version.first << '.' << backend_version.second;
+
+ // Get the CSV version.
+ CSVLeaseFile6 f(getLeaseFilePath("leasefile6_0.csv"));
+
+ // They should match.
+ EXPECT_EQ(s.str(), f.getSchemaVersion());
+
+ // DBVersion too.
+ EXPECT_EQ("Memfile backend " + s.str(),
+ lease_mgr->getDBVersion(Memfile_LeaseMgr::V6));
+}
+
+/// @brief Checks that complex user context can be read in v4.
+TEST_F(MemfileLeaseMgrTest, v4UserContext) {
+ // Add some leases to the CSV file.
+ LeaseFileIO io(getLeaseFilePath("leasefile4_0.csv"));
+ io.writeFile(
+ "address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+ "fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+
+ "192.0.0.1,01:01:01:01:01:01,,100,100,1,1,1,,1,"
+ "{},0\n"
+
+ "192.0.0.2,02:02:02:02:02:02,,200,400,1,1,1,,0,"
+ "{ \"comment\": \"this lease is for the kitchen computer\"},0\n"
+
+ // The next lines have escaped commas in user context.
+
+ "192.0.0.4,04:04:04:04:04:04,,400,1600,1,1,1,,0,"
+ "{ \"comment\": \"this lease is for the mainframe computer\"&#x2c"
+ " \"comment2\": \"don't release it\" },0\n"
+
+ "192.0.0.8,08:08:08:08:08:08,,800,6400,1,1,1,,0,"
+ "{ \"a\": \"b\"&#x2c \"c\": { \"d\": 1&#x2c\"e\": 2 } },0\n"
+ );
+
+ // Not sanitize user contexts.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ startBackend(V4);
+
+ // Check the lease with no key-value pairs in the user context.
+ Lease4Ptr lease(lmptr_->getLease4(IOAddress("192.0.0.1")));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Address: 192.0.0.1\n"
+ "Valid life: 100\n"
+ "Cltt: 0\n"
+ "Hardware addr: 01:01:01:01:01:01\n"
+ "Client id: (none)\n"
+ "Subnet ID: 1\n"
+ "Pool ID: 0\n"
+ "State: declined\n"
+ "Relay ID: (none)\n"
+ "Remote ID: (none)\n"
+ "User context: { }\n"
+ );
+
+ // Check the lease with one key-value pair in the user context.
+ lease = lmptr_->getLease4(IOAddress("192.0.0.2"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Address: 192.0.0.2\n"
+ "Valid life: 200\n"
+ "Cltt: 200\n"
+ "Hardware addr: 02:02:02:02:02:02\n"
+ "Client id: (none)\n"
+ "Subnet ID: 1\n"
+ "Pool ID: 0\n"
+ "State: default\n"
+ "Relay ID: (none)\n"
+ "Remote ID: (none)\n"
+ "User context: { \"comment\": \"this lease is for the kitchen computer\" }\n"
+ );
+
+ // Check the lease with two key-value pairs in the user context.
+ lease = lmptr_->getLease4(IOAddress("192.0.0.4"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Address: 192.0.0.4\n"
+ "Valid life: 400\n"
+ "Cltt: 1200\n"
+ "Hardware addr: 04:04:04:04:04:04\n"
+ "Client id: (none)\n"
+ "Subnet ID: 1\n"
+ "Pool ID: 0\n"
+ "State: default\n"
+ "Relay ID: (none)\n"
+ "Remote ID: (none)\n"
+ "User context: "
+ "{ \"comment\": \"this lease is for the mainframe computer\", "
+ "\"comment2\": \"don't release it\" }\n"
+ );
+
+ // Check the lease with nested key-value pairs in the user context.
+ lease = lmptr_->getLease4(IOAddress("192.0.0.8"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Address: 192.0.0.8\n"
+ "Valid life: 800\n"
+ "Cltt: 5600\n"
+ "Hardware addr: 08:08:08:08:08:08\n"
+ "Client id: (none)\n"
+ "Subnet ID: 1\n"
+ "Pool ID: 0\n"
+ "State: default\n"
+ "Relay ID: (none)\n"
+ "Remote ID: (none)\n"
+ "User context: { \"a\": \"b\", \"c\": { \"d\": 1, \"e\": 2 } }\n"
+ );
+}
+
+/// @brief Checks that complex user context can be read in v6.
+TEST_F(MemfileLeaseMgrTest, v6UserContext) {
+ // Add some leases to the CSV file.
+ LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
+ io.writeFile(
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,0,1,1,,,1,"
+ "{},,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"comment\": \"this lease is for the kitchen computer\"},,,0\n"
+
+ // The next lines have escaped commas in user context.
+
+ "2001:db8:1::4,04:04:04:04:04:04:04:04:04:04:04:04:04,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"comment\": \"this lease is for the mainframe computer\"&#x2c"
+ " \"comment2\": \"don't release it\" },,,0\n"
+
+ "2001:db8:1::8,08:08:08:08:08:08:08:08:08:08:08:08:08,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"a\": \"b\"&#x2c \"c\": { \"d\": 1&#x2c\"e\": 2 } },,,0\n"
+ );
+
+ // Not sanitize user contexts.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ startBackend(V6);
+
+ // Check the lease with no key-value pairs in the user context.
+ Lease6Ptr lease(
+ lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1")));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Type: IA_NA(0)\n"
+ "Address: 2001:db8:1::1\n"
+ "Prefix length: 128\n"
+ "IAID: 7\n"
+ "Pref life: 100\n"
+ "Valid life: 400\n"
+ "Cltt: 600\n"
+ "DUID: 01:01:01:01:01:01:01:01:01:01:01:01:01\n"
+ "Hardware addr: (none)\n"
+ "Subnet ID: 8\n"
+ "Pool ID: 0\n"
+ "State: declined\n"
+ "User context: { }\n"
+ );
+
+ // Check the lease with one key-value pair in the user context.
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Type: IA_NA(0)\n"
+ "Address: 2001:db8:1::2\n"
+ "Prefix length: 128\n"
+ "IAID: 7\n"
+ "Pref life: 100\n"
+ "Valid life: 200\n"
+ "Cltt: 0\n"
+ "DUID: 02:02:02:02:02:02:02:02:02:02:02:02:02\n"
+ "Hardware addr: (none)\n"
+ "Subnet ID: 8\n"
+ "Pool ID: 0\n"
+ "State: declined\n"
+ "User context: { \"comment\": \"this lease is for the kitchen computer\" }\n"
+ );
+
+ // Check the lease with two key-value pairs in the user context.
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::4"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Type: IA_NA(0)\n"
+ "Address: 2001:db8:1::4\n"
+ "Prefix length: 128\n"
+ "IAID: 7\n"
+ "Pref life: 100\n"
+ "Valid life: 200\n"
+ "Cltt: 0\n"
+ "DUID: 04:04:04:04:04:04:04:04:04:04:04:04:04\n"
+ "Hardware addr: (none)\n"
+ "Subnet ID: 8\n"
+ "Pool ID: 0\n"
+ "State: declined\n"
+ "User context: { \"comment\": \"this lease is for the mainframe computer\","
+ " \"comment2\": \"don't release it\" }\n"
+ );
+
+ // Check the lease with nested key-value pairs in the user context.
+ lease = lmptr_->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::8"));
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(
+ lease->toText(),
+ "Type: IA_NA(0)\n"
+ "Address: 2001:db8:1::8\n"
+ "Prefix length: 128\n"
+ "IAID: 7\n"
+ "Pref life: 100\n"
+ "Valid life: 200\n"
+ "Cltt: 0\n"
+ "DUID: 08:08:08:08:08:08:08:08:08:08:08:08:08\n"
+ "Hardware addr: (none)\n"
+ "Subnet ID: 8\n"
+ "Pool ID: 0\n"
+ "State: declined\n"
+ "User context: { \"a\": \"b\", \"c\": { \"d\": 1, \"e\": 2 } }\n"
+ );
+}
+
+/// @brief Checks that various hardware information is correctly imported from a
+/// CSV file.
+TEST_F(MemfileLeaseMgrTest, testHWAddr) {
+ // Add some leases to the CSV file.
+ LeaseFileIO io(getLeaseFilePath("leasefile6_0.csv"));
+ std::stringstream contents;
+ contents << "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n";
+
+ // Create combinations with all valid values except for 255 which is an
+ // unused value for both hwtype and hwaddr_source, but lease construction
+ // doesn't complain in that case either.
+ std::vector<uint16_t> const hwtypes{0, 1, 6, 8, 255};
+ std::vector<uint32_t> const hwsources{
+ 0xffffffff, 0x00000000, 0x00000001, 0x00000002, 0x00000004, 0x00000008,
+ 0x00000010, 0x00000020, 0x00000040, 0x00000080, 0x000000ff};
+ int i = 0;
+ for (uint16_t hwtype : hwtypes) {
+ for (uint32_t hwsource : hwsources) {
+ std::stringstream hex;
+ hex << std::hex << std::setw(2) << std::setfill('0') << i;
+ contents << "2001:db8:1::" << hex.str()
+ << ",00:00:00:00:00:" << hex.str()
+ << std::dec << ",7200,8000,1,3600,0,1,128,0,0,,ff:ff:ff:ff:ff:"
+ << hex.str() << ",1,," << hwtype << "," << hwsource << ",0\n";
+ ++i;
+ }
+ }
+
+ io.writeFile(contents.str());
+ startBackend(V6);
+
+ // Check leases.
+ i = 0;
+ for (uint16_t hwtype : hwtypes) {
+ for (uint32_t hwsource : hwsources) {
+ std::stringstream hex;
+ hex << std::hex << std::setw(2) << std::setfill('0') << i;
+ IOAddress address("2001:db8:1::" + hex.str());
+ Lease6Ptr lease(lmptr_->getLease6(Lease::TYPE_NA, address.toText()));
+
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(lease->toText(),
+ "Type: IA_NA(0)\n"
+ "Address: " + address.toText() + "\n"
+ "Prefix length: 128\n"
+ "IAID: 1\n"
+ "Pref life: 3600\n"
+ "Valid life: 7200\n"
+ "Cltt: 800\n"
+ "DUID: 00:00:00:00:00:" + hex.str() + "\n"
+ "Hardware addr: ff:ff:ff:ff:ff:" + hex.str() + "\n"
+ "Subnet ID: 1\n"
+ "Pool ID: 0\n"
+ "State: declined\n");
+ ASSERT_TRUE(lease->hwaddr_);
+ EXPECT_EQ(lease->hwaddr_->htype_, hwtype);
+ EXPECT_EQ(lease->hwaddr_->source_, hwsource);
+ ++i;
+ }
+ }
+}
+
+// Verifies that isJsonSupported() returns true for Memfile.
+TEST_F(MemfileLeaseMgrTest, isJsonSupported4) {
+ startBackend(V4);
+ bool json_supported;
+ ASSERT_NO_THROW_LOG(json_supported = LeaseMgrFactory::instance().isJsonSupported());
+ ASSERT_TRUE(json_supported);
+}
+
+// Verifies that isJsonSupported() returns true for Memfile.
+TEST_F(MemfileLeaseMgrTest, isJsonSupported6) {
+ startBackend(V6);
+ bool json_supported;
+ ASSERT_NO_THROW_LOG(json_supported = LeaseMgrFactory::instance().isJsonSupported());
+ ASSERT_TRUE(json_supported);
+}
+
+// Verifies that v4 class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(MemfileLeaseMgrTest, classLeaseCount4) {
+ startBackend(V4);
+ testClassLeaseCount4();
+}
+
+// Verifies that v6 IA_NA class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(MemfileLeaseMgrTest, classLeaseCount6_NA) {
+ startBackend(V6);
+ testClassLeaseCount6(Lease::TYPE_NA);
+}
+
+// Verifies that v6 IA_PD class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(MemfileLeaseMgrTest, classLeaseCount6_PD) {
+ startBackend(V6);
+ testClassLeaseCount6(Lease::TYPE_PD);
+}
+
+// brief Checks that a null user context allows allocation.
+TEST_F(MemfileLeaseMgrTest, checkLimitsNull4) {
+ startBackend(V4);
+ std::string text;
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(nullptr));
+ EXPECT_TRUE(text.empty());
+}
+
+// brief Checks that a null user context allows allocation.
+TEST_F(MemfileLeaseMgrTest, checkLimitsNull6) {
+ startBackend(V6);
+ std::string text;
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(nullptr));
+ EXPECT_TRUE(text.empty());
+}
+
+// Checks a few v4 lease limit checking scenarios.
+TEST_F(MemfileLeaseMgrTest, checkLimits4) {
+ startBackend(V4);
+ testLeaseLimits4();
+}
+
+// Checks a few v6 lease limit checking scenarios.
+TEST_F(MemfileLeaseMgrTest, checkLimits6) {
+ startBackend(V6);
+ testLeaseLimits6();
+}
+
+// Verifies that v4 class lease counts can be recounted.
+TEST_F(MemfileLeaseMgrTest, classLeaseRecount4) {
+ startBackend(V4);
+
+ // Create a subnet
+ CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+ Subnet4Ptr subnet;
+ Pool4Ptr pool;
+
+ subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 777));
+ pool.reset(new Pool4(IOAddress("192.0.1.0"), 24));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ // We need a Memfile_LeaseMgr pointer to access recount and clear functions.
+ Memfile_LeaseMgr* memfile_mgr = dynamic_cast<Memfile_LeaseMgr*>(lmptr_);
+ ASSERT_TRUE(memfile_mgr);
+
+ // Verify class lease counts are zero.
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water"));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon"));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice"));
+
+ // Structure that describes a recipe for a lease.
+ struct Recipe {
+ std::string address_;
+ uint32_t state_;
+ std::list<ClientClass> classes_;
+ };
+
+ // List of lease recipes.
+ std::list<Recipe> recipes{
+ { "192.0.1.100", Lease::STATE_DEFAULT, {"water", "slice"} },
+ { "192.0.1.101", Lease::STATE_DEFAULT, {"melon"} },
+ { "192.0.1.102", Lease::STATE_DEFAULT, {"melon", "slice"} }
+ };
+
+ // Bake all the leases.
+ for ( auto recipe : recipes ) {
+ ElementPtr ctx = makeContextWithClasses(recipe.classes_);
+ ASSERT_TRUE(makeLease4(recipe.address_, 777, recipe.state_, ctx));
+ }
+
+ // Verify counts are as expected.
+ EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water"));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon"));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice"));
+
+ // Clear counts
+ ASSERT_NO_THROW_LOG(memfile_mgr->clearClassLeaseCounts());
+
+ // Verify counts are zero.
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water"));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon"));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice"));
+
+ // Recount
+ ASSERT_NO_THROW_LOG(memfile_mgr->recountClassLeases4());
+
+ // Verify counts are recounted correctly.
+ EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water"));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon"));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice"));
+}
+
+// Verifies that v6 class lease counts can be recounted.
+TEST_F(MemfileLeaseMgrTest, classLeaseRecount6) {
+ startBackend(V6);
+
+ // Create a subnet
+ CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+ Subnet6Ptr subnet;
+ Pool6Ptr pool;
+
+ subnet.reset(new Subnet6(IOAddress("3001:1::"), 64, 1, 2, 3, 4, 777));
+ pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("3001:1::"),
+ IOAddress("3001:1::FF")));
+ subnet->addPool(pool);
+
+ pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"), 96, 112));
+ subnet->addPool(pool);
+ cfg->add(subnet);
+
+ // We need a Memfile_LeaseMgr pointer to access recount and clear functions.
+ Memfile_LeaseMgr* memfile_mgr = dynamic_cast<Memfile_LeaseMgr*>(lmptr_);
+ ASSERT_TRUE(memfile_mgr);
+
+ // Verify class lease counts are zero.
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA));
+
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD));
+
+ // Structure that describes a recipe for a lease.
+ struct Recipe {
+ Lease::Type ltype_;
+ std::string address_;
+ uint32_t prefix_len_;
+ uint32_t state_;
+ std::list<ClientClass> classes_;
+ };
+
+ // List of lease recipes.
+ std::list<Recipe> recipes{
+ { Lease::TYPE_NA, "3001::1", 128, Lease::STATE_DEFAULT, {"water", "slice"} },
+ { Lease::TYPE_NA, "3001::2", 128, Lease::STATE_DEFAULT, {"melon"} },
+ { Lease::TYPE_NA, "3001::3", 128, Lease::STATE_DEFAULT, {"melon", "slice"} },
+
+ { Lease::TYPE_PD, "3001:1:2:0100::", 112, Lease::STATE_DEFAULT, {"grapes", "slice"} },
+ { Lease::TYPE_PD, "3001:1:2:0200::", 112, Lease::STATE_DEFAULT, {"wrath"} },
+ { Lease::TYPE_PD, "3001:1:2:0300::", 112, Lease::STATE_DEFAULT, {"wrath", "slice"} },
+ };
+
+ // Bake all the leases.
+ for ( auto recipe : recipes ) {
+ ElementPtr ctx = makeContextWithClasses(recipe.classes_);
+ ASSERT_TRUE(makeLease6(recipe.ltype_, recipe.address_, recipe.prefix_len_, 777, recipe.state_, ctx));
+ }
+
+ // Verify counts are as expected.
+ EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA));
+
+ EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD));
+
+ // Clear counts
+ ASSERT_NO_THROW_LOG(memfile_mgr->clearClassLeaseCounts());
+
+ // Verify counts are zero.
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA));
+
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD));
+ EXPECT_EQ(0, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD));
+
+ // Recount
+ ASSERT_NO_THROW_LOG(memfile_mgr->recountClassLeases6());
+
+ // Verify counts are recounted correctly.
+ EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("water", Lease::TYPE_NA));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("melon", Lease::TYPE_NA));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_NA));
+
+ EXPECT_EQ(1, memfile_mgr->getClassLeaseCount("grapes", Lease::TYPE_PD));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("wrath", Lease::TYPE_PD));
+ EXPECT_EQ(2, memfile_mgr->getClassLeaseCount("slice", Lease::TYPE_PD));
+}
+
+/// @brief Class derived from @c Memfile_LeaseMgr to test write to file.
+class WFMemfileLeaseMgr : public Memfile_LeaseMgr {
+public:
+
+ /// @brief Constructor.
+ WFMemfileLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
+ : Memfile_LeaseMgr(parameters) {
+ }
+
+ using Memfile_LeaseMgr::lease_file4_;
+ using Memfile_LeaseMgr::lease_file6_;
+};
+
+/// @brief Check if writeLease fails on bad file name (v4).
+TEST_F(MemfileLeaseMgrTest, badWriteLease4) {
+ startBackend(V4);
+ string expected = "unable to open '/this/does/not/exist'";
+ EXPECT_THROW_MSG(lmptr_->writeLeases4("/this/does/not/exist"),
+ CSVFileError, expected);
+}
+
+/// @brief Check if writeLease fails on bad file name (v4+MT).
+TEST_F(MemfileLeaseMgrTest, badWriteLease4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ string expected = "unable to open '/this/does/not/exist'";
+ EXPECT_THROW_MSG(lmptr_->writeLeases4("/this/does/not/exist"),
+ CSVFileError, expected);
+}
+
+/// @brief Check if writeLease fails on bad file name (v6).
+TEST_F(MemfileLeaseMgrTest, badWriteLease6) {
+ startBackend(V6);
+ string expected = "unable to open '/this/does/not/exist'";
+ EXPECT_THROW_MSG(lmptr_->writeLeases6("/this/does/not/exist"),
+ CSVFileError, expected);
+}
+
+/// @brief Check if writeLease fails on bad file name (v6+MT).
+TEST_F(MemfileLeaseMgrTest, badWriteLease6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ string expected = "unable to open '/this/does/not/exist'";
+ EXPECT_THROW_MSG(lmptr_->writeLeases6("/this/does/not/exist"),
+ CSVFileError, expected);
+}
+
+/// @brief Check writeLease basic scenario (v4).
+TEST_F(MemfileLeaseMgrTest, basicWriteLease4) {
+ startBackend(V4);
+
+ // Empty database should give a header only file.
+ string header =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ extra_files_.push_back(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lmptr_->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Add leases.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+
+ // Try again.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_NO_THROW(lmptr_->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lmptr_->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lmptr_->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Check writeLease basic scenario (v4+MT).
+TEST_F(MemfileLeaseMgrTest, basicWriteLease4MultiThread) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Empty database should give a header only file.
+ string header =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ extra_files_.push_back(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lmptr_->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Add leases.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+
+ // Try again.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_NO_THROW(lmptr_->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lmptr_->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lmptr_->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Check writeLease basic scenario (v6).
+TEST_F(MemfileLeaseMgrTest, basicWriteLease6) {
+ startBackend(V6);
+
+ // empty database should give a header only file.
+ string header =
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n";
+
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ extra_files_.push_back(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lmptr_->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Add leases.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+
+ // Try again.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_NO_THROW(lmptr_->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lmptr_->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lmptr_->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Check writeLease basic scenario (v6+MT).
+TEST_F(MemfileLeaseMgrTest, basicWriteLease6MultiThread) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+
+ // empty database should give a header only file.
+ string header =
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n";
+
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ extra_files_.push_back(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lmptr_->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Add leases.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+
+ // Try again.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_NO_THROW(lmptr_->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lmptr_->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lmptr_->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Check if writeLease can overwrite the lease file (v4).
+TEST_F(MemfileLeaseMgrTest, overWriteLease4) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "true";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ // Empty database should give a header only file.
+ string header =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ ASSERT_TRUE(lease_mgr->lease_file4_);
+ string filename = lease_mgr->lease_file4_->getFilename();
+ LeaseFileIO file(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+
+ // Backup should have the previous database image.
+ ASSERT_TRUE(backup.exists());
+ EXPECT_EQ(content, backup.readFile());
+}
+
+/// @brief Check if writeLease can overwrite the lease file (v4+MT).
+TEST_F(MemfileLeaseMgrTest, overWriteLease4MultiThread) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "true";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Empty database should give a header only file.
+ string header =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ ASSERT_TRUE(lease_mgr->lease_file4_);
+ string filename = lease_mgr->lease_file4_->getFilename();
+ LeaseFileIO file(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+
+ // Backup should have the previous database image.
+ ASSERT_TRUE(backup.exists());
+ EXPECT_EQ(content, backup.readFile());
+}
+
+/// @brief Check if writeLease can overwrite the lease file (v6).
+TEST_F(MemfileLeaseMgrTest, overWriteLease6) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "true";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ // Empty database should give a header only file.
+ string header =
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n";
+
+ ASSERT_TRUE(lease_mgr->lease_file6_);
+ string filename = lease_mgr->lease_file6_->getFilename();
+ LeaseFileIO file(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+
+ // Backup should have the previous database image.
+ ASSERT_TRUE(backup.exists());
+ EXPECT_EQ(content, backup.readFile());
+}
+
+/// @brief Check if writeLease can overwrite the lease file (v6+MT).
+TEST_F(MemfileLeaseMgrTest, overWriteLease6MultiThread) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "true";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Empty database should give a header only file.
+ string header =
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n";
+
+ ASSERT_TRUE(lease_mgr->lease_file6_);
+ string filename = lease_mgr->lease_file6_->getFilename();
+ LeaseFileIO file(filename);
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+
+ // Backup should have the previous database image.
+ ASSERT_TRUE(backup.exists());
+ EXPECT_EQ(content, backup.readFile());
+}
+
+/// @brief Check if writeLease works without a lease file (v4).
+TEST_F(MemfileLeaseMgrTest, notPersistWriteLease4) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "false";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ // Empty database should give a header only file.
+ string header =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ ASSERT_FALSE(lease_mgr->lease_file4_);
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Check if writeLease works without a lease file (v4+MT).
+TEST_F(MemfileLeaseMgrTest, notPersistWriteLease4MultiThread) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "false";
+ pmap["name"] = getLeaseFilePath("leasefile4_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Empty database should give a header only file.
+ string header =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n";
+
+ ASSERT_FALSE(lease_mgr->lease_file4_);
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases4(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Check if writeLease works without a lease file (v6).
+TEST_F(MemfileLeaseMgrTest, notPersistWriteLease6) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "false";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ // Empty database should give a header only file.
+ string header =
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n";
+
+ ASSERT_FALSE(lease_mgr->lease_file6_);
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Check if writeLease works without a lease file (v6+MT).
+TEST_F(MemfileLeaseMgrTest, notPersistWriteLease6MultiThread) {
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["lfc-interval"] = "0";
+ pmap["persist"] = "false";
+ pmap["name"] = getLeaseFilePath("leasefile6_0.csv");
+ boost::scoped_ptr<WFMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new WFMemfileLeaseMgr(pmap)));
+
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Empty database should give a header only file.
+ string header =
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n";
+
+ ASSERT_FALSE(lease_mgr->lease_file6_);
+ string filename = getLeaseFilePath("backup.csv");
+ LeaseFileIO file(filename);
+ file.removeFile();
+ ostringstream b;
+ b << filename << ".bak" << getpid();
+ LeaseFileIO backup(b.str());
+ backup.removeFile();
+ extra_files_.push_back(b.str());
+
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ EXPECT_EQ(header, file.readFile());
+
+ // Get the leases to be used for the test.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Add leases.
+ EXPECT_TRUE(lease_mgr->addLease(leases[1]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[2]));
+ EXPECT_TRUE(lease_mgr->addLease(leases[3]));
+
+ // Try again.
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content = file.readFile();
+ ASSERT_GT(content.size(), header.size());
+ EXPECT_EQ(header, content.substr(0, header.size()));
+
+ // Delete a lease so shrink the database.
+ file.removeFile();
+ EXPECT_FALSE(file.exists());
+ EXPECT_TRUE(lease_mgr->deleteLease(leases[2]));
+ EXPECT_NO_THROW(lease_mgr->writeLeases6(filename));
+ ASSERT_TRUE(file.exists());
+ string content1 = file.readFile();
+ EXPECT_GT(content.size(), content1.size());
+}
+
+/// @brief Checks that extractExtendedInfo4 both updates extended info
+/// and set id fields at startup.
+TEST_F(MemfileLeaseMgrTest, extractExtendedInfo4) {
+ // Add some leases to the CSV file: one empty map, one old extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile4_0.csv");
+ LeaseFileIO io(lease_file);
+ io.writeFile(
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,{},0\n"
+
+ "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,1,"
+ "{ \"ISC\": { \"relay-agent-info\": \"0x02030102030C03AABBCC\" } },0\n"
+ );
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval"] = "0";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was updated.
+ Lease4Ptr lease = lease_mgr->getLease4(IOAddress("192.0.2.2"));
+ ASSERT_TRUE(lease);
+ EXPECT_FALSE(lease->getContext());
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+
+ // Check the lease with extended info was upgraded.
+ lease = lease_mgr->getLease4(IOAddress("192.0.2.3"));
+ ASSERT_TRUE(lease);
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ConstElementPtr isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ ConstElementPtr rai = isc->get("relay-agent-info");
+ ASSERT_TRUE(rai);
+ ASSERT_EQ(Element::map, rai->getType());
+ EXPECT_EQ(3, rai->size());
+ ConstElementPtr sub_options = rai->get("sub-options");
+ ASSERT_TRUE(sub_options);
+ EXPECT_EQ("\"0x02030102030C03AABBCC\"", sub_options->str());
+ ConstElementPtr relay_id = rai->get("relay-id");
+ ASSERT_TRUE(relay_id);
+ EXPECT_EQ("\"AABBCC\"", relay_id->str());
+ ConstElementPtr remote_id = rai->get("remote-id");
+ ASSERT_TRUE(remote_id);
+ EXPECT_EQ("\"010203\"", remote_id->str());
+
+ // Check the lease has the ids.
+ const vector<uint8_t> relay = { 0xaa, 0xbb, 0xcc };
+ EXPECT_EQ(relay, lease->relay_id_);
+ const vector<uint8_t> remote = { 1, 2, 3 };
+ EXPECT_EQ(remote, lease->remote_id_);
+
+ // Check the id indexes where updated.
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ Lease4Collection leases;
+ EXPECT_NO_THROW(leases = lease_mgr->getLeases4ByRelayId(relay,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(1, leases.size());
+ EXPECT_EQ(*lease, *leases[0]);
+ EXPECT_NO_THROW(leases = lease_mgr->getLeases4ByRemoteId(remote,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(1, leases.size());
+ EXPECT_EQ(*lease, *leases[0]);
+}
+
+/// @brief Checks that extractExtendedInfo4 does not update
+/// when extended info sanitizing is disabled.
+TEST_F(MemfileLeaseMgrTest, extractExtendedInfo4noSanitize) {
+ // Add some leases to the CSV file: one empty map, one old extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile4_0.csv");
+ LeaseFileIO io(lease_file);
+ io.writeFile(
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,{},0\n"
+
+ "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,1,"
+ "{ \"ISC\": { \"relay-agent-info\": \"0x02030102030C03AABBCC\" } },0\n"
+ );
+
+ // Disable sanitizing.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval"] = "0";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was not updated.
+ Lease4Ptr lease = lease_mgr->getLease4(IOAddress("192.0.2.2"));
+ ASSERT_TRUE(lease);
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ASSERT_EQ(Element::map, user_context->getType());
+ EXPECT_TRUE(user_context->empty());
+
+ // Check the lease with extended info was not upgraded.
+ lease = lease_mgr->getLease4(IOAddress("192.0.2.3"));
+ ASSERT_TRUE(lease);
+ user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ConstElementPtr isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ ConstElementPtr rai = isc->get("relay-agent-info");
+ ASSERT_TRUE(rai);
+ EXPECT_EQ(Element::string, rai->getType());
+ EXPECT_EQ("\"0x02030102030C03AABBCC\"", rai->str());
+
+ // Check the lease has no ids.
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+}
+
+/// @brief Checks that extractExtendedInfo4 updates extended info
+/// when explicitly requested.
+TEST_F(MemfileLeaseMgrTest, extractExtendedInfo4ExplicitSanitize) {
+ // Add some leases to the CSV file: one empty map, one old extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile4_0.csv");
+ LeaseFileIO io(lease_file);
+ string content =
+ "address,hwaddr,client_id,valid_lifetime,expire,"
+ "subnet_id,fqdn_fwd,fqdn_rev,hostname,state,user_context,pool_id\n"
+
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,{},0\n"
+
+ "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,1,"
+ "{ \"ISC\": { \"relay-agent-info\": \"0x02030102030C03AABBCC\" } },0\n";
+ io.writeFile(content);
+
+ // Disable sanitizing.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "4";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval"] = "0";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was not updated.
+ Lease4Ptr lease = lease_mgr->getLease4(IOAddress("192.0.2.2"));
+ ASSERT_TRUE(lease);
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ASSERT_EQ(Element::map, user_context->getType());
+ EXPECT_TRUE(user_context->empty());
+
+ // Check the lease with extended info was not upgraded.
+ lease = lease_mgr->getLease4(IOAddress("192.0.2.3"));
+ ASSERT_TRUE(lease);
+ user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ConstElementPtr isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ ConstElementPtr rai = isc->get("relay-agent-info");
+ ASSERT_TRUE(rai);
+ EXPECT_EQ(Element::string, rai->getType());
+ EXPECT_EQ("\"0x02030102030C03AABBCC\"", rai->str());
+
+ // Check the lease has no ids.
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+
+ // Enable sanitizing.
+ CfgMgr::instance().getCurrentCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ // Now run extractExtendedInfo4 with update set to true.
+ size_t updated = 0;
+ EXPECT_NO_THROW(updated = lease_mgr->extractExtendedInfo4(true, true));
+ EXPECT_EQ(2, updated);
+
+ // Check the lease with empty user context was updated.
+ lease = lease_mgr->getLease4(IOAddress("192.0.2.2"));
+ ASSERT_TRUE(lease);
+ EXPECT_FALSE(lease->getContext());
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+
+ // Check the lease with extended info was upgraded.
+ lease = lease_mgr->getLease4(IOAddress("192.0.2.3"));
+ ASSERT_TRUE(lease);
+ user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ rai = isc->get("relay-agent-info");
+ ASSERT_TRUE(rai);
+ ASSERT_EQ(Element::map, rai->getType());
+ EXPECT_EQ(3, rai->size());
+ ConstElementPtr sub_options = rai->get("sub-options");
+ ASSERT_TRUE(sub_options);
+ EXPECT_EQ("\"0x02030102030C03AABBCC\"", sub_options->str());
+ ConstElementPtr relay_id = rai->get("relay-id");
+ ASSERT_TRUE(relay_id);
+ EXPECT_EQ("\"AABBCC\"", relay_id->str());
+ ConstElementPtr remote_id = rai->get("remote-id");
+ ASSERT_TRUE(remote_id);
+ EXPECT_EQ("\"010203\"", remote_id->str());
+
+ // Check the lease has the ids.
+ const vector<uint8_t> relay = { 0xaa, 0xbb, 0xcc };
+ EXPECT_EQ(relay, lease->relay_id_);
+ const vector<uint8_t> remote = { 1, 2, 3 };
+ EXPECT_EQ(remote, lease->remote_id_);
+
+
+ // Check the id indexes where updated.
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ Lease4Collection leases;
+ EXPECT_NO_THROW(leases = lease_mgr->getLeases4ByRelayId(relay,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(1, leases.size());
+ EXPECT_EQ(*lease, *leases[0]);
+ EXPECT_NO_THROW(leases = lease_mgr->getLeases4ByRemoteId(remote,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(1, leases.size());
+ EXPECT_EQ(*lease, *leases[0]);
+ // Check the lease file was updated.
+ string new_content =
+ "192.0.2.2,02:02:02:02:02:02,,200,200,8,1,1,,1,,0\n"
+
+ "192.0.2.3,03:03:03:03:03:03,,200,200,8,1,1,,1,"
+ "{ \"ISC\": { \"relay-agent-info\": { "
+ "\"relay-id\": \"AABBCC\"&#x2c "
+ "\"remote-id\": \"010203\"&#x2c "
+ "\"sub-options\": \"0x02030102030C03AABBCC\" } } },0\n";
+ string expected = content + new_content;
+ EXPECT_EQ(expected, io.readFile());
+}
+
+/// @brief Checks that buildExtendedInfoTables6 both updates extended info
+/// and add them into tables at startup.
+TEST_F(MemfileLeaseMgrTest, buildExtendedInfoTables6) {
+ // Add some leases to the CSV file: one empty map, one old extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile6_0.csv");
+ LeaseFileIO io(lease_file);
+ io.writeFile(
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,0,1,1,,,1,"
+ "{},,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 44&#x2c"
+ " \"link\": \"2001:db8::4\"&#x2c \"peer\": \"2001:db8::5\"&#x2c"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\""
+ " } ] } },,,0\n"
+ );
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval"] = "0";
+ pmap["extended-info-tables"] = "true";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was updated.
+ Lease6Ptr lease = lease_mgr->getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_FALSE(lease->getContext());
+
+ // Check the lease with extended info was upgraded.
+ lease = lease_mgr->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ConstElementPtr isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ EXPECT_FALSE(isc->contains("relays"));
+ EXPECT_TRUE(isc->contains("relay-info"));
+
+ // Check that extended info tables were updated.
+ ASSERT_EQ(1, lease_mgr->relay_id6_.size());
+ auto relay_id_it = lease_mgr->relay_id6_.cbegin();
+ ASSERT_NE(relay_id_it, lease_mgr->relay_id6_.cend());
+ Lease6ExtendedInfoPtr ex_info = *relay_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8:1::2", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, ex_info->id_);
+
+ ASSERT_EQ(1, lease_mgr->remote_id6_.size());
+ auto remote_id_it = lease_mgr->remote_id6_.cbegin();
+ ASSERT_NE(remote_id_it, lease_mgr->remote_id6_.cend());
+ ex_info = *remote_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8:1::2", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, ex_info->id_);
+}
+
+/// @brief Checks that buildExtendedInfoTables6 does not update
+/// when extended info sanitizing is disabled.
+TEST_F(MemfileLeaseMgrTest, buildExtendedInfoTables6noSanitize) {
+ // Add some leases to the CSV file: one empty map, one old extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile6_0.csv");
+ LeaseFileIO io(lease_file);
+ io.writeFile(
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,0,1,1,,,1,"
+ "{},,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 44&#x2c"
+ " \"link\": \"2001:db8::4\"&#x2c \"peer\": \"2001:db8::5\"&#x2c"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\""
+ " } ] } },,,0\n"
+ );
+
+ // Disable sanitizing.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval"] = "0";
+ pmap["extended-info-tables"] = "true";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was not updated.
+ Lease6Ptr lease = lease_mgr->getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ASSERT_EQ(Element::map, user_context->getType());
+ EXPECT_TRUE(user_context->empty());
+
+ // Check the lease with extended info was not upgraded.
+ lease = lease_mgr->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ConstElementPtr isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ EXPECT_TRUE(isc->contains("relays"));
+ EXPECT_FALSE(isc->contains("relay-info"));
+
+ // Check that extended info tables were not updated.
+ EXPECT_TRUE(lease_mgr->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr->remote_id6_.empty());
+}
+
+/// @brief Checks that buildExtendedInfoTables6 adds extended info to tables
+/// when enabled.
+TEST_F(MemfileLeaseMgrTest, buildExtendedInfoTables6enabled) {
+ // Add some leases to the CSV file: one empty map, one new extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile6_0.csv");
+ LeaseFileIO io(lease_file);
+ io.writeFile(
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,0,1,1,,,1,"
+ "{},,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 44&#x2c"
+ " \"link\": \"2001:db8::4\"&#x2c \"peer\": \"2001:db8::5\"&#x2c"
+ " \"remote-id\": \"010203040506\"&#x2c"
+ " \"relay-id\": \"6464646464646464\"&#x2c"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\""
+ " } ] } },,,0\n"
+ );
+
+ // Disable sanitizing.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval=0"] = "0";
+ pmap["extended-info-tables"] = "true";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was updated.
+ Lease6Ptr lease = lease_mgr->getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ASSERT_EQ(Element::map, user_context->getType());
+ EXPECT_TRUE(user_context->empty());
+
+ // Check that extended info tables were updated.
+ ASSERT_EQ(1, lease_mgr->relay_id6_.size());
+ auto relay_id_it = lease_mgr->relay_id6_.cbegin();
+ ASSERT_NE(relay_id_it, lease_mgr->relay_id6_.cend());
+ Lease6ExtendedInfoPtr ex_info = *relay_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8:1::2", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, ex_info->id_);
+
+ ASSERT_EQ(1, lease_mgr->remote_id6_.size());
+ auto remote_id_it = lease_mgr->remote_id6_.cbegin();
+ ASSERT_NE(remote_id_it, lease_mgr->remote_id6_.cend());
+ ex_info = *remote_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8:1::2", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, ex_info->id_);
+}
+
+/// @brief Checks that buildExtendedInfoTables6 does not add extended info
+/// to tables when disabled.
+TEST_F(MemfileLeaseMgrTest, buildExtendedInfoTables6disabled) {
+ // Add some leases to the CSV file: one empty map, one new extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile6_0.csv");
+ LeaseFileIO io(lease_file);
+ io.writeFile(
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,0,1,1,,,1,"
+ "{},,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 44&#x2c"
+ " \"link\": \"2001:db8::4\"&#x2c \"peer\": \"2001:db8::5\"&#x2c"
+ " \"remote-id\": \"010203040506\"&#x2c"
+ " \"relay-id\": \"6464646464646464\"&#x2c"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\""
+ " } ] } },,,0\n"
+ );
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval"] = "0";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was updated.
+ Lease6Ptr lease = lease_mgr->getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_FALSE(lease->getContext());
+
+ // Check that extended info tables were not updated.
+ EXPECT_TRUE(lease_mgr->relay_id6_.empty());
+ EXPECT_TRUE(lease_mgr->remote_id6_.empty());
+}
+
+/// @brief Checks that buildExtendedInfoTables6 updates when explicitly
+/// requested.
+TEST_F(MemfileLeaseMgrTest, buildExtendedInfoTables6ExplicitSanitize) {
+ // Add some leases to the CSV file: one empty map, one old extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile6_0.csv");
+ LeaseFileIO io(lease_file);
+ string content =
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,0,1,1,,,1,"
+ "{},,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 44&#x2c"
+ " \"link\": \"2001:db8::4\"&#x2c \"peer\": \"2001:db8::5\"&#x2c"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\""
+ " } ] } },,,0\n";
+ io.writeFile(content);
+
+ // Disable sanitizing.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval"] = "0";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check the lease with empty user context was not updated.
+ Lease6Ptr lease = lease_mgr->getLease6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ ConstElementPtr user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ASSERT_EQ(Element::map, user_context->getType());
+ EXPECT_TRUE(user_context->empty());
+
+ // Check the lease with extended info was not upgraded.
+ lease = lease_mgr->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ ConstElementPtr isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ EXPECT_TRUE(isc->contains("relays"));
+ EXPECT_FALSE(isc->contains("relay-info"));
+
+ // Enable sanitizing.
+ CfgMgr::instance().getCurrentCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ // Now run buildExtendedInfoTables6 with update set to true.
+ size_t updated = 0;
+ EXPECT_NO_THROW(updated = lease_mgr->buildExtendedInfoTables6(true, true));
+ EXPECT_EQ(2, updated);
+
+ // Check the lease with empty user context was updated.
+ lease = lease_mgr->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"));
+ ASSERT_TRUE(lease);
+ EXPECT_FALSE(lease->getContext());
+
+ // Check the lease with extended info was upgraded.
+ lease = lease_mgr->getLease6(Lease::TYPE_NA, IOAddress("2001:db8:1::2"));
+ ASSERT_TRUE(lease);
+ user_context = lease->getContext();
+ ASSERT_TRUE(user_context);
+ isc = user_context->get("ISC");
+ ASSERT_TRUE(isc);
+ EXPECT_FALSE(isc->contains("relays"));
+ EXPECT_TRUE(isc->contains("relay-info"));
+
+ // Check the lease file was updated.
+ string new_content =
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,128,1,1,,,1,,,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,128,1,1,,,1,"
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 44&#x2c"
+ " \"link\": \"2001:db8::4\"&#x2c"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\""
+ "&#x2c \"peer\": \"2001:db8::5\"&#x2c"
+ " \"relay-id\": \"6464646464646464\"&#x2c"
+ " \"remote-id\": \"010203040506\" } ] } },,,0\n";
+ string expected = content + new_content;
+ EXPECT_EQ(expected, io.readFile());
+}
+
+/// @brief Checks that buildExtendedInfoTables6 can rebuild tables.
+TEST_F(MemfileLeaseMgrTest, buildExtendedInfoTables6rebuild) {
+ // Add some leases to the CSV file: one empty map, one new extended
+ // info format.
+ string lease_file = getLeaseFilePath("leasefile6_0.csv");
+ LeaseFileIO io(lease_file);
+ io.writeFile(
+ "address,duid,valid_lifetime,expire,subnet_id,pref_lifetime,"
+ "lease_type,iaid,prefix_len,fqdn_fwd,fqdn_rev,hostname,"
+ "hwaddr,state,user_context,hwtype,hwaddr_source,pool_id\n"
+
+ "2001:db8:1::1,01:01:01:01:01:01:01:01:01:01:01:01:01,"
+ "400,1000,8,100,0,7,0,1,1,,,1,"
+ "{},,,0\n"
+
+ "2001:db8:1::2,02:02:02:02:02:02:02:02:02:02:02:02:02,"
+ "200,200,8,100,0,7,0,1,1,,,1,"
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 44&#x2c"
+ " \"link\": \"2001:db8::4\"&#x2c \"peer\": \"2001:db8::5\"&#x2c"
+ " \"remote-id\": \"010203040506\"&#x2c"
+ " \"relay-id\": \"6464646464646464\"&#x2c"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\""
+ " } ] } },,,0\n"
+ );
+
+ // Disable sanitizing.
+ CfgMgr::instance().getStagingCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+
+ // Start the lease manager.
+ DatabaseConnection::ParameterMap pmap;
+ pmap["type"] = "memfile";
+ pmap["universe"] = "6";
+ pmap["name"] = lease_file;
+ pmap["lfc-interval=0"] = "0";
+ pmap["extended-info-tables"] = "true";
+ boost::scoped_ptr<NakedMemfileLeaseMgr> lease_mgr;
+ EXPECT_NO_THROW(lease_mgr.reset(new NakedMemfileLeaseMgr(pmap)));
+
+ // Check that extended info tables were updated.
+ EXPECT_EQ(1, lease_mgr->relay_id6_.size());
+ EXPECT_EQ(1, lease_mgr->remote_id6_.size());
+
+ // Add a junk entry in each table.
+ IOAddress lease_addr("2001:db8:1::10");
+ const vector<uint8_t>& relay_id = vector<uint8_t>(10, 0x65);
+ Lease6ExtendedInfoPtr relay;
+ relay.reset(new Lease6ExtendedInfo(lease_addr, relay_id));
+ lease_mgr->relay_id6_.insert(relay);
+ const vector<uint8_t>& remote_id = { 10, 11, 12, 13, 14, 15, 16 };
+ Lease6ExtendedInfoPtr remote;
+ remote.reset(new Lease6ExtendedInfo(lease_addr, remote_id));
+ lease_mgr->remote_id6_.insert(remote);
+
+ // Check that tables grown.
+ EXPECT_EQ(2, lease_mgr->relay_id6_.size());
+ EXPECT_EQ(2, lease_mgr->remote_id6_.size());
+
+ // Rebuild the tables.
+ size_t updated = 0;
+ EXPECT_NO_THROW(updated = lease_mgr->buildExtendedInfoTables6(false, false));
+ EXPECT_EQ(0, updated);
+
+ // Check tables.
+ ASSERT_EQ(1, lease_mgr->relay_id6_.size());
+ auto relay_id_it = lease_mgr->relay_id6_.cbegin();
+ ASSERT_NE(relay_id_it, lease_mgr->relay_id6_.cend());
+ Lease6ExtendedInfoPtr ex_info = *relay_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8:1::2", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& exp_relay_id = vector<uint8_t>(8, 0x64);
+ EXPECT_EQ(exp_relay_id, ex_info->id_);
+
+ ASSERT_EQ(1, lease_mgr->remote_id6_.size());
+ auto remote_id_it = lease_mgr->remote_id6_.cbegin();
+ ASSERT_NE(remote_id_it, lease_mgr->remote_id6_.cend());
+ ex_info = *remote_id_it;
+ ASSERT_TRUE(ex_info);
+ EXPECT_EQ("2001:db8:1::2", ex_info->lease_addr_.toText());
+ const vector<uint8_t>& exp_remote_id = { 1, 2, 3, 4, 5, 6 };
+ EXPECT_EQ(exp_remote_id, ex_info->id_);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MemfileLeaseMgrTest, trackAddLease4) {
+ startBackend(V4);
+ testTrackAddLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MemfileLeaseMgrTest, trackAddLease4MultiThreading) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is added.
+TEST_F(MemfileLeaseMgrTest, trackAddLeaseNA) {
+ startBackend(V6);
+ testTrackAddLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is added.
+TEST_F(MemfileLeaseMgrTest, trackAddLeaseNAMultiThreading) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is added.
+TEST_F(MemfileLeaseMgrTest, trackAddLeasePD) {
+ startBackend(V6);
+ testTrackAddLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is added.
+TEST_F(MemfileLeaseMgrTest, trackAddLeasePDMultiThreading) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MemfileLeaseMgrTest, trackUpdateLease4) {
+ startBackend(V4);
+ testTrackUpdateLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MemfileLeaseMgrTest, trackUpdateLease4MultiThreading) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is updated.
+TEST_F(MemfileLeaseMgrTest, trackUpdateLeaseNA) {
+ startBackend(V6);
+ testTrackUpdateLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is updated.
+TEST_F(MemfileLeaseMgrTest, trackUpdateLeaseNAMultiThreading) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is updated.
+TEST_F(MemfileLeaseMgrTest, trackUpdateLeasePD) {
+ startBackend(V6);
+ testTrackUpdateLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is updated.
+TEST_F(MemfileLeaseMgrTest, trackUpdateLeasePDMultiThreading) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MemfileLeaseMgrTest, trackDeleteLease4) {
+ startBackend(V4);
+ testTrackDeleteLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MemfileLeaseMgrTest, trackDeleteLease4MultiThreading) {
+ startBackend(V4);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is deleted.
+TEST_F(MemfileLeaseMgrTest, trackDeleteLeaseNA) {
+ startBackend(V6);
+ testTrackDeleteLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 addres lease is deleted.
+TEST_F(MemfileLeaseMgrTest, trackDeleteLeaseNAMultiThreading) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is deleted.
+TEST_F(MemfileLeaseMgrTest, trackDeleteLeasePD) {
+ startBackend(V6);
+ testTrackDeleteLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is deleted.
+TEST_F(MemfileLeaseMgrTest, trackDeleteLeasePDMultiThreading) {
+ startBackend(V6);
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLeasePD(false);
+}
+
+/// @brief Checks that the lease manager can be recreated and its
+/// registered callbacks preserved, if desired.
+TEST_F(MemfileLeaseMgrTest, recreateWithCallbacks) {
+ startBackend(V4);
+ testRecreateWithCallbacks(getConfigString(V4));
+}
+
+/// @brief Checks that the lease manager can be recreated without the
+/// previously registered callbacks.
+TEST_F(MemfileLeaseMgrTest, recreateWithoutCallbacks) {
+ startBackend(V4);
+ testRecreateWithoutCallbacks(getConfigString(V4));
+}
+
+TEST_F(MemfileLeaseMgrTest, bigStats) {
+ startBackend(V4);
+ testBigStats();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/multi_threading_config_parser_unittest.cc b/src/lib/dhcpsrv/tests/multi_threading_config_parser_unittest.cc
new file mode 100644
index 0000000..223c4d3
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/multi_threading_config_parser_unittest.cc
@@ -0,0 +1,190 @@
+// Copyright (C) 2020-2023 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 <cc/data.h>
+#include <dhcpsrv/parsers/multi_threading_config_parser.h>
+#include <dhcpsrv/cfg_multi_threading.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/test_to_element.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for @c MultiThreadingConfigParser
+class MultiThreadingConfigParserTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ MultiThreadingConfigParserTest() = default;
+
+ /// @brief Destructor
+ virtual ~MultiThreadingConfigParserTest() = default;
+
+protected:
+
+ /// @brief Setup for each test.
+ virtual void SetUp();
+
+ /// @brief Cleans up after each test.
+ virtual void TearDown();
+};
+
+void
+MultiThreadingConfigParserTest::SetUp() {
+ MultiThreadingMgr::instance().setMode(false);
+}
+
+void
+MultiThreadingConfigParserTest::TearDown() {
+ MultiThreadingMgr::instance().setMode(false);
+}
+
+// Verifies that MultiThreadingConfigParser handles
+// expected valid content
+TEST_F(MultiThreadingConfigParserTest, validContent) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "enable-multi-threading, without thread-pool-size or packet-queue-size",
+ "{ \n"
+ " \"enable-multi-threading\": true \n"
+ "} \n"
+ },
+ {
+ "enable-multi-threading disabled",
+ "{ \n"
+ " \"enable-multi-threading\": false \n"
+ "} \n"
+ },
+ {
+ "enable-multi-threading, with thread-pool-size and packet-queue-size",
+ "{ \n"
+ " \"enable-multi-threading\": true, \n"
+ " \"thread-pool-size\": 4, \n"
+ " \"packet-queue-size\": 64 \n"
+ "} \n"
+ }
+ };
+
+ // Iterate over the valid scenarios and verify they succeed.
+ ConstElementPtr config_elems;
+ ConstElementPtr multi_threading_config;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ SrvConfig srv_config;
+ // Construct the config JSON
+ ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
+ << "invalid JSON, test is broken";
+
+ // Parsing config should succeed.
+ MultiThreadingConfigParser parser;
+ try {
+ parser.parse(srv_config, config_elems);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "parser threw an exception: " << ex.what();
+ }
+
+ multi_threading_config = srv_config.getDHCPMultiThreading();
+ // Verify the resultant configuration.
+ ASSERT_TRUE(multi_threading_config);
+
+ bool enabled = false;
+ uint32_t thread_count = 0;
+ uint32_t queue_size = 0;
+
+ CfgMultiThreading::extract(multi_threading_config, enabled,
+ thread_count, queue_size);
+
+ EXPECT_TRUE(multi_threading_config->equals(*config_elems));
+ }
+ }
+}
+
+// Verifies that MultiThreadingConfigParser correctly catches
+// invalid content
+TEST_F(MultiThreadingConfigParserTest, invalidContent) {
+ struct Scenario {
+ std::string description_;
+ std::string json_;
+ };
+
+ std::vector<Scenario> scenarios = {
+ {
+ "enable-multi-threading not boolean",
+ "{ \n"
+ " \"enable-multi-threading\": \"always\" \n"
+ "} \n"
+ },
+ {
+ "thread-pool-size not integer",
+ "{ \n"
+ " \"thread-pool-size\": true \n"
+ "} \n"
+ },
+ {
+ "thread-pool-size negative",
+ "{ \n"
+ " \"thread-pool-size\": -1 \n"
+ "} \n"
+ },
+ {
+ "thread-pool-size too large",
+ "{ \n"
+ " \"thread-pool-size\": 200000 \n"
+ "} \n"
+ },
+ {
+ "packet-queue-size not integer",
+ "{ \n"
+ " \"packet-queue-size\": true \n"
+ "} \n"
+ },
+ {
+ "packet-queue-size-size negative",
+ "{ \n"
+ " \"packet-queue-size\": -1 \n"
+ "} \n"
+ },
+ {
+ "packet-queue-size too large",
+ "{ \n"
+ " \"packet-queue-size\": 200000 \n"
+ "} \n"
+ }
+ };
+
+ // Iterate over the valid scenarios and verify they succeed.
+ ConstElementPtr config_elems;
+ ConstElementPtr queue_control;
+ for (auto scenario : scenarios) {
+ SCOPED_TRACE(scenario.description_);
+ {
+ SrvConfig srv_config;
+ // Construct the config JSON
+ ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_))
+ << "invalid JSON, test is broken";
+
+ // Parsing config into a queue control should succeed.
+ MultiThreadingConfigParser parser;
+ EXPECT_THROW(parser.parse(srv_config, config_elems), DhcpConfigError);
+ }
+ }
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
new file mode 100644
index 0000000..8ad593c
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/mysql_host_data_source_unittest.cc
@@ -0,0 +1,1753 @@
+// Copyright (C) 2015-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/mysql_host_data_source.h>
+#include <dhcpsrv/testutils/generic_host_data_source_unittest.h>
+#include <dhcpsrv/testutils/host_data_source_utils.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <mysql/mysql_connection.h>
+#include <mysql/testutils/mysql_schema.h>
+#include <testutils/multi_threading_utils.h>
+#include <util/multi_threading_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::data;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+class MySqlHostDataSourceTest : public GenericHostDataSourceTest {
+public:
+ /// @brief Clears the database and opens connection to it.
+ void initializeTest() {
+ // Ensure we have the proper schema with no transient data.
+ createMySQLSchema();
+
+ // Connect to the database
+ try {
+ HostMgr::create();
+ HostMgr::addBackend(validMySQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the MySQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ hdsptr_ = HostMgr::instance().getHostDataSource();
+ hdsptr_->setIPReservationsUnique(true);
+
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destroys the HDS and the schema.
+ void destroyTest() {
+ try {
+ hdsptr_->rollback();
+ } catch (...) {
+ // Rollback may fail if backend is in read only mode. That's ok.
+ }
+ HostMgr::delAllBackends();
+ hdsptr_.reset();
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroyMySQLSchema();
+ }
+
+ /// @brief Constructor
+ ///
+ /// Deletes everything from the database and opens it.
+ MySqlHostDataSourceTest() {
+ initializeTest();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Rolls back all pending transactions. The deletion of hdsptr_ will close
+ /// the database. Then reopen it and delete everything created by the test.
+ virtual ~MySqlHostDataSourceTest() {
+ destroyTest();
+ }
+
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-open it. Anything committed should be
+ /// visible.
+ ///
+ /// Parameter is ignored for MySQL backend as the v4 and v6 hosts share
+ /// the same database.
+ void reopen(Universe) {
+ HostMgr::create();
+ HostMgr::addBackend(validMySQLConnectionString());
+ hdsptr_ = HostMgr::instance().getHostDataSource();
+ }
+
+ /// @brief returns number of rows in a table
+ ///
+ /// Note: This method uses its own connection. It will not work if your test
+ /// uses transactions.
+ ///
+ /// @param name of the table
+ /// @return number of rows currently present in the table
+ int countRowsInTable(const std::string& table) {
+ string query = "SELECT * FROM " + table;
+
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+
+ MySqlConnection conn(params);
+ conn.openDatabase();
+
+ int status = MysqlQuery(conn.mysql_, query.c_str());
+ if (status != 0) {
+ isc_throw(DbOperationError, "Query failed: " << mysql_error(conn.mysql_));
+ }
+
+ MYSQL_RES * res = mysql_store_result(conn.mysql_);
+ int numrows = static_cast<int>(mysql_num_rows(res));
+ mysql_free_result(res);
+
+ return (numrows);
+ }
+
+ /// @brief Returns number of IPv4 options currently stored in DB.
+ virtual int countDBOptions4() {
+ return (countRowsInTable("dhcp4_options"));
+ }
+
+ /// @brief Returns number of IPv6 options currently stored in DB.
+ virtual int countDBOptions6() {
+ return (countRowsInTable("dhcp6_options"));
+ }
+
+ /// @brief Returns number of IPv6 reservations currently stored in DB.
+ virtual int countDBReservations6() {
+ return (countRowsInTable("ipv6_reservations"));
+ }
+
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the MySqlHostDataSource can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// MySqlHostMgr test fixture set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+TEST(MySqlHostDataSource, OpenDatabase) {
+ // Schema needs to be created for the test to work.
+ destroyMySQLSchema();
+ createMySQLSchema();
+
+ // Check that host manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(validMySQLConnectionString()));
+ HostMgr::delBackend("mysql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that host manager opens the database correctly with a longer
+ // timeout. If it fails, print the error message.
+ try {
+ string connection_string = validMySQLConnectionString() + string(" ") +
+ string(VALID_TIMEOUT);
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(connection_string));
+ HostMgr::delBackend("mysql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the host data source when
+ // none is set returns empty pointer.
+ EXPECT_FALSE(HostMgr::instance().getHostDataSource());
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on HostDataSourceFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT, INVALID_READONLY_DB)), DbInvalidReadOnly);
+
+ // Check for missing parameters
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Tidy up after the test
+ destroyMySQLSchema();
+}
+
+/// @brief Check that database can be opened with Multi-Threading
+///
+/// This test checks if the MySqlHostDataSource can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// MySqlHostMgr test fixture set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+TEST(MySqlHostDataSource, OpenDatabaseMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+
+ // Schema needs to be created for the test to work.
+ destroyMySQLSchema();
+ createMySQLSchema();
+
+ // Check that host manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(validMySQLConnectionString()));
+ HostMgr::delBackend("mysql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that host manager opens the database correctly with a longer
+ // timeout. If it fails, print the error message.
+ try {
+ string connection_string = validMySQLConnectionString() + string(" ") +
+ string(VALID_TIMEOUT);
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(connection_string));
+ HostMgr::delBackend("mysql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the host data source when
+ // none is set returns empty pointer.
+ EXPECT_FALSE(HostMgr::instance().getHostDataSource());
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on HostDataSourceFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT, INVALID_READONLY_DB)), DbInvalidReadOnly);
+
+ // Check for missing parameters
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Tidy up after the test
+ destroyMySQLSchema();
+}
+
+/// @brief Flag used to detect calls to db_lost_callback function
+bool callback_called = false;
+
+/// @brief Callback function used in open database testing
+bool db_lost_callback(ReconnectCtlPtr /* db_conn_retry */) {
+ return (callback_called = true);
+}
+
+/// @brief Make sure open failures do NOT invoke db lost callback
+/// The db lost callback should only be invoked after successfully
+/// opening the DB and then subsequently losing it. Failing to
+/// open should be handled directly by the application layer.
+/// There is simply no good way to break the connection in a
+/// unit test environment. So testing the callback invocation
+/// in a unit test is next to impossible. That has to be done
+/// as a system test.
+TEST(MySqlHostDataSource, NoCallbackOnOpenFail) {
+ // Schema needs to be created for the test to work.
+ destroyMySQLSchema();
+ createMySQLSchema();
+
+ callback_called = false;
+ DatabaseConnection::db_lost_callback_ = db_lost_callback;
+ HostMgr::create();
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ EXPECT_FALSE(callback_called);
+ destroyMySQLSchema();
+}
+
+/// @brief Make sure open failures do NOT invoke db lost callback
+/// The db lost callback should only be invoked after successfully
+/// opening the DB and then subsequently losing it. Failing to
+/// open should be handled directly by the application layer.
+/// There is simply no good way to break the connection in a
+/// unit test environment. So testing the callback invocation
+/// in a unit test is next to impossible. That has to be done
+/// as a system test.
+TEST(MySqlHostDataSource, NoCallbackOnOpenFailMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+
+ // Schema needs to be created for the test to work.
+ destroyMySQLSchema();
+ createMySQLSchema();
+
+ callback_called = false;
+ DatabaseConnection::db_lost_callback_ = db_lost_callback;
+ HostMgr::create();
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ EXPECT_FALSE(callback_called);
+ destroyMySQLSchema();
+}
+
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime. In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct. It does not check that the
+/// data is entered into the database correctly, only that the MYSQL_TIME
+/// structure used for the entry is correctly set up.
+TEST(MySqlConnection, checkTimeConversion) {
+ const time_t cltt = time(NULL);
+ const uint32_t valid_lft = 86400; // 1 day
+ struct tm tm_expire;
+ MYSQL_TIME mysql_expire;
+
+ // Work out what the broken-down time will be for one day
+ // after the current time.
+ time_t expire_time = cltt + valid_lft;
+ (void) localtime_r(&expire_time, &tm_expire);
+
+ // Convert to the database time
+ MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+
+ // Are the times the same?
+ EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
+ EXPECT_EQ(tm_expire.tm_mon + 1, mysql_expire.month);
+ EXPECT_EQ(tm_expire.tm_mday, mysql_expire.day);
+ EXPECT_EQ(tm_expire.tm_hour, mysql_expire.hour);
+ EXPECT_EQ(tm_expire.tm_min, mysql_expire.minute);
+ EXPECT_EQ(tm_expire.tm_sec, mysql_expire.second);
+ EXPECT_EQ(0, mysql_expire.second_part);
+ EXPECT_EQ(0, mysql_expire.neg);
+
+ // Convert back
+ time_t converted_cltt = 0;
+ MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
+ EXPECT_EQ(cltt, converted_cltt);
+}
+
+/// @brief This test verifies that database backend can operate in Read-Only mode.
+TEST_F(MySqlHostDataSourceTest, testReadOnlyDatabase) {
+ testReadOnlyDatabase(MYSQL_VALID_TYPE);
+}
+
+/// @brief This test verifies that database backend can operate in Read-Only mode.
+TEST_F(MySqlHostDataSourceTest, testReadOnlyDatabaseMultiThreading) {
+ MultiThreadingTest mt(true);
+ testReadOnlyDatabase(MYSQL_VALID_TYPE);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses hw address as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4HWAddr) {
+ testBasic4(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses hw address as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4HWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasic4(Host::IDENT_HWADDR);
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have the global
+/// subnet id value
+TEST_F(MySqlHostDataSourceTest, globalSubnetId4) {
+ testGlobalSubnetId4();
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have the global
+/// subnet id value
+TEST_F(MySqlHostDataSourceTest, globalSubnetId4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGlobalSubnetId4();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have the global
+/// subnet id value
+TEST_F(MySqlHostDataSourceTest, globalSubnetId6) {
+ testGlobalSubnetId6();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have the global
+/// subnet id value
+TEST_F(MySqlHostDataSourceTest, globalSubnetId6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGlobalSubnetId6();
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have a max value
+/// for dhcp4_subnet id
+TEST_F(MySqlHostDataSourceTest, maxSubnetId4) {
+ testMaxSubnetId4();
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have a max value
+/// for dhcp4_subnet id
+TEST_F(MySqlHostDataSourceTest, maxSubnetId4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxSubnetId4();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have a max value
+/// for dhcp6_subnet id
+TEST_F(MySqlHostDataSourceTest, maxSubnetId6) {
+ testMaxSubnetId6();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have a max value
+/// for dhcp6_subnet id
+TEST_F(MySqlHostDataSourceTest, maxSubnetId6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxSubnetId6();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAll4BySubnet) {
+ testGetAll4();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAll4BySubnetMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAll4();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAll6BySubnet) {
+ testGetAll6();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAll6BySubnetMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAll6();
+}
+
+/// @brief Verifies that host reservations with the same hostname can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAllbyHostname) {
+ testGetAllbyHostname();
+}
+
+/// @brief Verifies that host reservations with the same hostname can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAllbyHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAllbyHostname();
+}
+
+/// @brief Verifies that IPv4 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAllbyHostnameSubnet4) {
+ testGetAllbyHostnameSubnet4();
+}
+
+/// @brief Verifies that IPv4 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAllbyHostnameSubnet4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAllbyHostnameSubnet4();
+}
+
+/// @brief Verifies that IPv6 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAllbyHostnameSubnet6) {
+ testGetAllbyHostnameSubnet6();
+}
+
+/// @brief Verifies that IPv6 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(MySqlHostDataSourceTest, getAllbyHostnameSubnet6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAllbyHostnameSubnet6();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(MySqlHostDataSourceTest, getPage4) {
+ testGetPage4();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(MySqlHostDataSourceTest, getPage4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage4();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(MySqlHostDataSourceTest, getPage6) {
+ testGetPage6();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(MySqlHostDataSourceTest, getPage6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage6();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(MySqlHostDataSourceTest, getPageLimit4) {
+ testGetPageLimit4(Host::IDENT_DUID);
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(MySqlHostDataSourceTest, getPageLimit4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPageLimit4(Host::IDENT_DUID);
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(MySqlHostDataSourceTest, getPageLimit6) {
+ testGetPageLimit6(Host::IDENT_HWADDR);
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(MySqlHostDataSourceTest, getPageLimit6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPageLimit6(Host::IDENT_HWADDR);
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(MySqlHostDataSourceTest, getPage4Subnets) {
+ testGetPage4Subnets();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(MySqlHostDataSourceTest, getPage4SubnetsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage4Subnets();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(MySqlHostDataSourceTest, getPage6Subnets) {
+ testGetPage6Subnets();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(MySqlHostDataSourceTest, getPage6SubnetsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage6Subnets();
+}
+
+// Verifies that all IPv4 host reservations can be retrieved by pages.
+TEST_F(MySqlHostDataSourceTest, getPage4All) {
+ testGetPage4All();
+}
+
+// Verifies that all IPv4 host reservations can be retrieved by pages.
+TEST_F(MySqlHostDataSourceTest, getPage4AllMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage4All();
+}
+
+// Verifies that all IPv6 host reservations can be retrieved by pages.
+TEST_F(MySqlHostDataSourceTest, getPage6All) {
+ testGetPage6All();
+}
+
+// Verifies that all IPv6 host reservations can be retrieved by pages.
+TEST_F(MySqlHostDataSourceTest, getPage6AllMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage6All();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses client-id (DUID) as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4ClientId) {
+ testBasic4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses client-id (DUID) as identifier.
+TEST_F(MySqlHostDataSourceTest, basic4ClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasic4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4HWaddr) {
+ testGetByIPv4(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4HWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv4(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4ClientId) {
+ testGetByIPv4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(MySqlHostDataSourceTest, getByIPv4ClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(MySqlHostDataSourceTest, get4ByHWaddr) {
+ testGet4ByIdentifier(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(MySqlHostDataSourceTest, get4ByHWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// DUID.
+TEST_F(MySqlHostDataSourceTest, get4ByDUID) {
+ testGet4ByIdentifier(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// DUID.
+TEST_F(MySqlHostDataSourceTest, get4ByDUIDMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// circuit id.
+TEST_F(MySqlHostDataSourceTest, get4ByCircuitId) {
+ testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// circuit id.
+TEST_F(MySqlHostDataSourceTest, get4ByCircuitIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client-id.
+TEST_F(MySqlHostDataSourceTest, get4ByClientId) {
+ testGet4ByIdentifier(Host::IDENT_CLIENT_ID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client-id.
+TEST_F(MySqlHostDataSourceTest, get4ByClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_CLIENT_ID);
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId1) {
+ testHWAddrNotClientId();
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId1MultiThreading) {
+ MultiThreadingTest mt(true);
+ testHWAddrNotClientId();
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId2) {
+ testClientIdNotHWAddr();
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(MySqlHostDataSourceTest, hwaddrNotClientId2MultiThreading) {
+ MultiThreadingTest mt(true);
+ testClientIdNotHWAddr();
+}
+
+/// @brief Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDN) {
+ testHostname("foo.example.org", 1);
+}
+
+/// @brief Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDNMultiThreading) {
+ MultiThreadingTest mt(true);
+ testHostname("foo.example.org", 1);
+}
+
+/// @brief Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+/// retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDN100) {
+ testHostname("foo.example.org", 100);
+}
+
+/// @brief Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+/// retrieved.
+TEST_F(MySqlHostDataSourceTest, hostnameFQDN100MultiThreading) {
+ MultiThreadingTest mt(true);
+ testHostname("foo.example.org", 100);
+}
+
+/// @brief Test verifies if a host without any hostname specified can be stored and later
+/// retrieved.
+TEST_F(MySqlHostDataSourceTest, noHostname) {
+ testHostname("", 1);
+}
+
+/// @brief Test verifies if a host without any hostname specified can be stored and later
+/// retrieved.
+TEST_F(MySqlHostDataSourceTest, noHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testHostname("", 1);
+}
+
+/// @brief Test verifies if a host with user context can be stored and later retrieved.
+TEST_F(MySqlHostDataSourceTest, usercontext) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testUserContext(Element::fromJSON(comment));
+}
+
+/// @brief Test verifies if a host with user context can be stored and later retrieved.
+TEST_F(MySqlHostDataSourceTest, usercontextMultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testUserContext(Element::fromJSON(comment));
+}
+
+/// @brief Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with hardware address X, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId1MultiThreading) {
+ MultiThreadingTest mt(true);
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with hardware address X, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies if the hardware or client-id query can match client-id.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with client identifier Y, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies if the hardware or client-id query can match client-id.
+TEST_F(MySqlHostDataSourceTest, DISABLED_hwaddrOrClientId2MultiThreading) {
+ MultiThreadingTest mt(true);
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with client identifier Y, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies that host with IPv6 address and DUID can be added and
+/// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, get6AddrWithDuid) {
+ testGetByIPv6(Host::IDENT_DUID, false);
+}
+
+/// @brief Test verifies that host with IPv6 address and DUID can be added and
+/// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, get6AddrWithDuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_DUID, false);
+}
+
+/// @brief Test verifies that host with IPv6 address and HWAddr can be added and
+/// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, get6AddrWithHWAddr) {
+ testGetByIPv6(Host::IDENT_HWADDR, false);
+}
+
+/// @brief Test verifies that host with IPv6 address and HWAddr can be added and
+/// later retrieved by IPv6 address.
+TEST_F(MySqlHostDataSourceTest, get6AddrWithHWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_HWADDR, false);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and DUID can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, get6PrefixWithDuid) {
+ testGetByIPv6(Host::IDENT_DUID, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and DUID can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, get6PrefixWithDuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_DUID, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and HWAddr can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, get6PrefixWithHWaddr) {
+ testGetByIPv6(Host::IDENT_HWADDR, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and HWAddr can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(MySqlHostDataSourceTest, get6PrefixWithHWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_HWADDR, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix reservation can be retrieved
+/// by subnet id and prefix value.
+TEST_F(MySqlHostDataSourceTest, get6SubnetPrefix) {
+ testGetBySubnetIPv6();
+}
+
+/// @brief Test verifies that host with IPv6 prefix reservation can be retrieved
+/// by subnet id and prefix value.
+TEST_F(MySqlHostDataSourceTest, get6SubnetPrefixMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetBySubnetIPv6();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(MySqlHostDataSourceTest, get6ByHWaddr) {
+ testGet6ByHWAddr();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(MySqlHostDataSourceTest, get6ByHWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet6ByHWAddr();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client identifier.
+TEST_F(MySqlHostDataSourceTest, get6ByClientId) {
+ testGet6ByClientId();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client identifier.
+TEST_F(MySqlHostDataSourceTest, get6ByClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet6ByClientId();
+}
+
+/// @brief Test verifies if a host reservation can be stored with both IPv6 address and
+/// prefix.
+TEST_F(MySqlHostDataSourceTest, addr6AndPrefix) {
+ testAddr6AndPrefix();
+}
+
+/// @brief Test verifies if a host reservation can be stored with both IPv6 address and
+/// prefix.
+TEST_F(MySqlHostDataSourceTest, addr6AndPrefixMultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddr6AndPrefix();
+}
+
+/// @brief Tests if host with multiple IPv6 reservations can be added and then
+/// retrieved correctly. Test checks reservations comparing.
+TEST_F(MySqlHostDataSourceTest, multipleReservations) {
+ testMultipleReservations();
+}
+
+/// @brief Tests if host with multiple IPv6 reservations can be added and then
+/// retrieved correctly. Test checks reservations comparing.
+TEST_F(MySqlHostDataSourceTest, multipleReservationsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleReservations();
+}
+
+/// @brief Tests if compareIPv6Reservations() method treats same pool of reservations
+/// but added in different order as equal.
+TEST_F(MySqlHostDataSourceTest, multipleReservationsDifferentOrder) {
+ testMultipleReservationsDifferentOrder();
+}
+
+/// @brief Tests if compareIPv6Reservations() method treats same pool of reservations
+/// but added in different order as equal.
+TEST_F(MySqlHostDataSourceTest, multipleReservationsDifferentOrderMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleReservationsDifferentOrder();
+}
+
+/// @brief Test that multiple client classes for IPv4 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(MySqlHostDataSourceTest, multipleClientClasses4) {
+ testMultipleClientClasses4();
+}
+
+/// @brief Test that multiple client classes for IPv4 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(MySqlHostDataSourceTest, multipleClientClasses4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleClientClasses4();
+}
+
+/// @brief Test that multiple client classes for IPv6 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(MySqlHostDataSourceTest, multipleClientClasses6) {
+ testMultipleClientClasses6();
+}
+
+/// @brief Test that multiple client classes for IPv6 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(MySqlHostDataSourceTest, multipleClientClasses6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleClientClasses6();
+}
+
+/// @brief Test that multiple client classes for both IPv4 and IPv6 can
+/// be inserted and retrieved for a given host reservation.
+TEST_F(MySqlHostDataSourceTest, multipleClientClassesBoth) {
+ testMultipleClientClassesBoth();
+}
+
+/// @brief Test that multiple client classes for both IPv4 and IPv6 can
+/// be inserted and retrieved for a given host reservation.
+TEST_F(MySqlHostDataSourceTest, multipleClientClassesBothMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleClientClassesBoth();
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same hardware address). The test logic is as follows:
+/// Insert 10 host reservations for a given physical host (the same
+/// hardware address), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them all correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsHWAddr) {
+ testMultipleSubnets(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same hardware address). The test logic is as follows:
+/// Insert 10 host reservations for a given physical host (the same
+/// hardware address), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them all correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsHWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleSubnets(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same client identifier). The test logic is as follows:
+///
+/// Insert 10 host reservations for a given physical host (the same
+/// client-identifier), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsClientId) {
+ testMultipleSubnets(10, Host::IDENT_DUID);
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same client identifier). The test logic is as follows:
+///
+/// Insert 10 host reservations for a given physical host (the same
+/// client-identifier), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them correctly.
+TEST_F(MySqlHostDataSourceTest, multipleSubnetsClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleSubnets(10, Host::IDENT_DUID);
+}
+
+/// @brief Test if host reservations made for different IPv6 subnets are handled correctly.
+/// The test logic is as follows:
+///
+/// Insert 10 host reservations for different subnets. Make sure that
+/// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(MySqlHostDataSourceTest, subnetId6) {
+ testSubnetId6(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if host reservations made for different IPv6 subnets are handled correctly.
+/// The test logic is as follows:
+///
+/// Insert 10 host reservations for different subnets. Make sure that
+/// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(MySqlHostDataSourceTest, subnetId6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testSubnetId6(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same DUID.
+TEST_F(MySqlHostDataSourceTest, addDuplicate6WithDUID) {
+ testAddDuplicate6WithSameDUID();
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same DUID.
+TEST_F(MySqlHostDataSourceTest, addDuplicate6WithDUIDMultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicate6WithSameDUID();
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same HWAddr.
+TEST_F(MySqlHostDataSourceTest, addDuplicate6WithHWAddr) {
+ testAddDuplicate6WithSameHWAddr();
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same HWAddr.
+TEST_F(MySqlHostDataSourceTest, addDuplicate6WithHWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicate6WithSameHWAddr();
+}
+
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(MySqlHostDataSourceTest, addDuplicateIPv6) {
+ testAddDuplicateIPv6();
+}
+
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(MySqlHostDataSourceTest, addDuplicateIPv6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicateIPv6();
+}
+
+/// @brief Test if the host reservation for the same IPv6 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv6) {
+ testAllowDuplicateIPv6();
+}
+
+/// @brief Test if the host reservation for the same IPv6 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAllowDuplicateIPv6();
+}
+
+/// @brief Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+TEST_F(MySqlHostDataSourceTest, addDuplicateIPv4) {
+ testAddDuplicateIPv4();
+}
+
+/// @brief Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+TEST_F(MySqlHostDataSourceTest, addDuplicateIPv4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicateIPv4();
+}
+
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv4) {
+ testAllowDuplicateIPv4();
+}
+
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(MySqlHostDataSourceTest, allowDuplicateIPv4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAllowDuplicateIPv4();
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations4) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations4MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations6) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations6MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations46) {
+ testOptionsReservations46(false);
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, optionsReservations46MultiThreading) {
+ MultiThreadingTest mt(true);
+ testOptionsReservations46(false);
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations4) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations4MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations6) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the MySQL host database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations6MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// textual format and retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations46) {
+ testOptionsReservations46(true);
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// textual format and retrieved with a single query to the database.
+TEST_F(MySqlHostDataSourceTest, formattedOptionsReservations46MultiThreading) {
+ MultiThreadingTest mt(true);
+ testOptionsReservations46(true);
+}
+
+/// @brief This test checks transactional insertion of the host information
+/// into the database. The failure to insert host information at
+/// any stage should cause the whole transaction to be rolled back.
+TEST_F(MySqlHostDataSourceTest, testAddRollback) {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // To test the transaction rollback mechanism we need to cause the
+ // insertion of host information to fail at some stage. The 'hosts'
+ // table should be updated correctly but the failure should occur
+ // when inserting reservations or options. The simplest way to
+ // achieve that is to simply drop one of the tables. To do so, we
+ // connect to the database and issue a DROP query.
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+ MySqlConnection conn(params);
+ ASSERT_NO_THROW(conn.openDatabase());
+
+ int status = MysqlQuery(conn.mysql_,
+ "DROP TABLE IF EXISTS ipv6_reservations");
+ ASSERT_EQ(0, status) << mysql_error(conn.mysql_);
+
+ // Create a host with a reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::1",
+ Host::IDENT_HWADDR, false);
+ // Let's assign some DHCPv4 subnet to the host, because we will use the
+ // DHCPv4 subnet to try to retrieve the host after failed insertion.
+ host->setIPv4SubnetID(SubnetID(4));
+
+ // There is no ipv6_reservations table, so the insertion should fail.
+ ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+ // Even though we have created a DHCPv6 host, we can't use get6()
+ // method to retrieve the host from the database, because the
+ // query would expect that the ipv6_reservations table is present.
+ // Therefore, the query would fail. Instead, we use the get4 method
+ // which uses the same client identifier, but doesn't attempt to
+ // retrieve the data from ipv6_reservations table. The query should
+ // pass but return no host because the (insert) transaction is expected
+ // to be rolled back.
+ ConstHostPtr from_hds = hdsptr_->get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ EXPECT_FALSE(from_hds);
+}
+
+/// @brief This test checks transactional insertion of the host information
+/// into the database. The failure to insert host information at
+/// any stage should cause the whole transaction to be rolled back.
+TEST_F(MySqlHostDataSourceTest, testAddRollbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // To test the transaction rollback mechanism we need to cause the
+ // insertion of host information to fail at some stage. The 'hosts'
+ // table should be updated correctly but the failure should occur
+ // when inserting reservations or options. The simplest way to
+ // achieve that is to simply drop one of the tables. To do so, we
+ // connect to the database and issue a DROP query.
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+ MySqlConnection conn(params);
+ ASSERT_NO_THROW(conn.openDatabase());
+
+ int status = MysqlQuery(conn.mysql_,
+ "DROP TABLE IF EXISTS ipv6_reservations");
+ ASSERT_EQ(0, status) << mysql_error(conn.mysql_);
+
+ // Create a host with a reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::1",
+ Host::IDENT_HWADDR, false);
+ // Let's assign some DHCPv4 subnet to the host, because we will use the
+ // DHCPv4 subnet to try to retrieve the host after failed insertion.
+ host->setIPv4SubnetID(SubnetID(4));
+
+ // There is no ipv6_reservations table, so the insertion should fail.
+ ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+ // Even though we have created a DHCPv6 host, we can't use get6()
+ // method to retrieve the host from the database, because the
+ // query would expect that the ipv6_reservations table is present.
+ // Therefore, the query would fail. Instead, we use the get4 method
+ // which uses the same client identifier, but doesn't attempt to
+ // retrieve the data from ipv6_reservations table. The query should
+ // pass but return no host because the (insert) transaction is expected
+ // to be rolled back.
+ ConstHostPtr from_hds = hdsptr_->get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ EXPECT_FALSE(from_hds);
+}
+
+/// @brief This test checks that siaddr, sname, file fields can be retrieved
+/// from a database for a host.
+TEST_F(MySqlHostDataSourceTest, messageFields) {
+ testMessageFields4();
+}
+
+/// @brief This test checks that siaddr, sname, file fields can be retrieved
+/// from a database for a host.
+TEST_F(MySqlHostDataSourceTest, messageFieldsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMessageFields4();
+}
+
+/// @brief Check that delete(subnet-id, addr4) works.
+TEST_F(MySqlHostDataSourceTest, deleteByAddr4) {
+ testDeleteByAddr4();
+}
+
+/// @brief Check that delete(subnet-id, addr4) works.
+TEST_F(MySqlHostDataSourceTest, deleteByAddr4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteByAddr4();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById4) {
+ testDeleteById4();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById4();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById4Options) {
+ testDeleteById4Options();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById4OptionsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById4Options();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById6) {
+ testDeleteById6();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(MySqlHostDataSourceTest, deleteById6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById6();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById6Options) {
+ testDeleteById6Options();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(MySqlHostDataSourceTest, deleteById6OptionsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById6Options();
+}
+
+/// @brief Tests that multiple reservations without IPv4 addresses can be
+/// specified within a subnet.
+TEST_F(MySqlHostDataSourceTest, testMultipleHostsNoAddress4) {
+ testMultipleHostsNoAddress4();
+}
+
+/// @brief Tests that multiple reservations without IPv4 addresses can be
+/// specified within a subnet.
+TEST_F(MySqlHostDataSourceTest, testMultipleHostsNoAddress4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleHostsNoAddress4();
+}
+
+/// @brief Tests that multiple hosts can be specified within an IPv6 subnet.
+TEST_F(MySqlHostDataSourceTest, testMultipleHosts6) {
+ testMultipleHosts6();
+}
+
+/// @brief Tests that multiple hosts can be specified within an IPv6 subnet.
+TEST_F(MySqlHostDataSourceTest, testMultipleHosts6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleHosts6();
+}
+
+/// @brief Tests that hosts can be updated.
+TEST_F(MySqlHostDataSourceTest, update) {
+ testUpdate();
+}
+
+/// @brief Tests that hosts can be updated.
+TEST_F(MySqlHostDataSourceTest, updateMultiThreading) {
+ MultiThreadingTest mt(true);
+ testUpdate();
+}
+
+/// @brief Test fixture class for validating @c HostMgr using
+/// MySQL as alternate host data source.
+class MySQLHostMgrTest : public HostMgrTest {
+protected:
+
+ /// @brief Build MySQL schema for a test.
+ virtual void SetUp();
+
+ /// @brief Rollback and drop MySQL schema after the test.
+ virtual void TearDown();
+};
+
+void
+MySQLHostMgrTest::SetUp() {
+ HostMgrTest::SetUp();
+
+ // Ensure we have the proper schema with no transient data.
+ db::test::createMySQLSchema();
+
+ // Connect to the database
+ try {
+ HostMgr::addBackend(db::test::validMySQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the MySQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+}
+
+void
+MySQLHostMgrTest::TearDown() {
+ try {
+ HostMgr::instance().getHostDataSource()->rollback();
+ } catch(...) {
+ // we don't care if we aren't in a transaction.
+ }
+
+ HostMgr::delBackend("mysql");
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ db::test::destroyMySQLSchema();
+}
+
+/// @brief Test fixture class for validating @c HostMgr using
+/// MySQL as alternate host data source and MySQL connectivity loss.
+class MySQLHostMgrDbLostCallbackTest : public HostMgrDbLostCallbackTest {
+public:
+ virtual void destroySchema() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ db::test::destroyMySQLSchema();
+ }
+
+ virtual void createSchema() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createMySQLSchema();
+ }
+
+ virtual std::string validConnectString() {
+ return (db::test::validMySQLConnectionString());
+ }
+
+ virtual std::string invalidConnectString() {
+ return (connectionString(MYSQL_VALID_TYPE, INVALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+ }
+};
+
+// This test verifies that reservations for a particular client can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(MySQLHostMgrTest, getAll) {
+ testGetAll(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(MySQLHostMgrTest, getAll4BySubnet) {
+ testGetAll4BySubnet(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(MySQLHostMgrTest, getAll6BySubnet) {
+ testGetAll6BySubnet(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// IPv4 subnet and reserved address.
+TEST_F(MySQLHostMgrTest, getAll4BySubnetIP) {
+ testGetAll4BySubnetIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// IPv6 subnet and reserved address.
+TEST_F(MySQLHostMgrTest, getAll6BySubnetIP) {
+ testGetAll6BySubnetIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// IPv6 reserved address.
+TEST_F(MySQLHostMgrTest, getAll6ByIP) {
+ testGetAll6ByIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// IPv6 reserved prefix.
+TEST_F(MySQLHostMgrTest, getAll6ByIpPrefix) {
+ testGetAll6ByIpPrefix(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that reservations for a particular hostname can be
+// retrieved from the configuration file and a database simultaneously.
+TEST_F(MySQLHostMgrTest, getAllbyHostname) {
+ testGetAllbyHostname(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular hostname and
+// DHCPv4 subnet can be retrieved from the configuration file and a
+// database simultaneously.
+TEST_F(MySQLHostMgrTest, getAllbyHostnameSubnet4) {
+ testGetAllbyHostnameSubnet4(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular hostname and
+// DHCPv6 subnet can be retrieved from the configuration file and a
+// database simultaneously.
+TEST_F(MySQLHostMgrTest, getAllbyHostnameSubnet6) {
+ testGetAllbyHostnameSubnet6(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved by pages from the configuration file and a database
+// simultaneously.
+TEST_F(MySQLHostMgrTest, getPage4) {
+ testGetPage4(true);
+}
+
+// This test verifies that all v4 reservations be retrieved by pages
+// from the configuration file and a database simultaneously.
+TEST_F(MySQLHostMgrTest, getPage4All) {
+ testGetPage4All(true);
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved by pages from the configuration file and a database
+// simultaneously.
+TEST_F(MySQLHostMgrTest, getPage6) {
+ testGetPage6(true);
+}
+
+// This test verifies that all v6 reservations be retrieved by pages
+// from the configuration file and a database simultaneously.
+TEST_F(MySQLHostMgrTest, getPage6All) {
+ testGetPage6All(true);
+}
+
+// This test verifies that IPv4 reservations for a particular client can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(MySQLHostMgrTest, getAll4) {
+ testGetAll4(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the IPv4 reservation can be retrieved from a
+// database.
+TEST_F(MySQLHostMgrTest, get4) {
+ testGet4(HostMgr::instance());
+}
+
+// This test verifies that the IPv6 reservation can be retrieved from a
+// database.
+TEST_F(MySQLHostMgrTest, get6) {
+ testGet6(HostMgr::instance());
+}
+
+// This test verifies that the IPv6 prefix reservation can be retrieved
+// from a configuration file and a database.
+TEST_F(MySQLHostMgrTest, get6ByPrefix) {
+ testGet6ByPrefix(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the reservations can be added to a configuration
+// file and a database.
+TEST_F(MySQLHostMgrTest, add) {
+ testAdd(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the reservations can be deleted from a configuration
+// file and a database by subnet ID and address.
+TEST_F(MySQLHostMgrTest, del) {
+ testDeleteByIDAndAddress(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the IPv4 reservations can be deleted from a
+// configuration file and a database by subnet ID and identifier.
+TEST_F(MySQLHostMgrTest, del4) {
+ testDelete4ByIDAndIdentifier(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the IPv6 reservations can be deleted from a
+// configuration file and a database by subnet ID and identifier.
+TEST_F(MySQLHostMgrTest, del6) {
+ testDelete6ByIDAndIdentifier(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that it is possible to control whether the reserved
+// IP addresses are unique or non unique via the HostMgr.
+TEST_F(MySQLHostMgrTest, setIPReservationsUnique) {
+ EXPECT_TRUE(HostMgr::instance().setIPReservationsUnique(true));
+ EXPECT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(MySQLHostMgrDbLostCallbackTest, testNoCallbackOnOpenFailure) {
+ MultiThreadingTest mt(false);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(MySQLHostMgrDbLostCallbackTest, testNoCallbackOnOpenFailureMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndFailedCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndFailedCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySQLHostMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_extended_info_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_extended_info_unittest.cc
new file mode 100644
index 0000000..fda2195
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/mysql_lease_extended_info_unittest.cc
@@ -0,0 +1,1111 @@
+// Copyright (C) 2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/mysql_lease_mgr.h>
+#include <mysql/testutils/mysql_schema.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief IPv4 addresses used in the tests.
+const vector<string> ADDRESS4 = {
+ "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+ "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7"
+};
+
+/// @brief IPv6 addresses used in the tests.
+const vector<string> ADDRESS6 = {
+ "2001:db8::", "2001:db8::1", "2001:db8::2", "2001:db8::3",
+ "2001:db8::4", "2001:db8::5", "2001:db8::6", "2001:db8::7"
+};
+
+/// @brief DUIDs used in the tests.
+const vector<string> DUIDS = {
+ "wwwwwwww", "BBBBBBBB", "::::::::", "0123456789acdef",
+ "BBBBBBBB", "$$$$$$$$", "^^^^^^^^", "\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5"
+};
+
+/// @brief Test fixture class for extended info tests.
+class MySqlExtendedInfoTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ MySqlExtendedInfoTest() {
+ // Ensure we have the proper schema with no transient data.
+ createMySQLSchema();
+
+ // Connect to the database.
+ try {
+ LeaseMgrFactory::create(validMySQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the MySQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ lease_mgr_ = &(LeaseMgrFactory::instance());
+ leases4.clear();
+ leases6.clear();
+ MultiThreadingMgr::instance().setMode(false);
+ now_ = time(0);
+ }
+
+ /// @brief Destructor.
+ ~MySqlExtendedInfoTest() {
+ LeaseMgrFactory::destroy();
+ // If data wipe enabled, delete transient data otherwise destroy
+ // the schema.
+ destroyMySQLSchema();
+
+ leases4.clear();
+ leases6.clear();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Create and set v4 leases.
+ ///
+ /// @param insert When true insert in the database.
+ void initLease4(bool insert = true) {
+ ASSERT_EQ(ADDRESS4.size(), DUIDS.size());
+ for (size_t i = 0; i < ADDRESS4.size(); ++i) {
+ Lease4Ptr lease;
+ vector<uint8_t> hwaddr_data(5, 0x08);
+ hwaddr_data.push_back(0x80 + i);
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_data, HTYPE_ETHER));
+ vector<uint8_t> client_id = createFromString(DUIDS[i]);
+ IOAddress address(ADDRESS4[i]);
+ ASSERT_NO_THROW(lease.reset(new Lease4(address, hwaddr,
+ &client_id[0],
+ client_id.size(),
+ 1000, now_,
+ static_cast<SubnetID>(i))));
+ leases4.push_back(lease);
+ if (insert) {
+ EXPECT_TRUE(lease_mgr_->addLease(lease));
+ }
+ }
+ ASSERT_EQ(ADDRESS4.size(), leases4.size());
+ }
+
+ /// @brief Create and set v6 leases.
+ void initLease6() {
+ ASSERT_EQ(ADDRESS6.size(), DUIDS.size());
+ for (size_t i = 0; i < ADDRESS6.size(); ++i) {
+ Lease6Ptr lease;
+ vector<uint8_t> duid_data = createFromString(DUIDS[i]);
+ DuidPtr duid(new DUID(duid_data));
+ IOAddress addr(ADDRESS6[i]);
+ ASSERT_NO_THROW(lease.reset(new Lease6(((i % 2) ? Lease::TYPE_NA : Lease::TYPE_PD), addr, duid,
+ 123, 1000, 2000,
+ static_cast<SubnetID>(i))));
+ leases6.push_back(lease);
+ EXPECT_TRUE(lease_mgr_->addLease(lease));
+ }
+ ASSERT_EQ(ADDRESS6.size(), leases6.size());
+ }
+
+ /// @brief Create a vector of uint8_t from a string.
+ ///
+ /// @param content A not empty string holding the content.
+ /// @return A vector of uint8_t with the given content.
+ inline vector<uint8_t> createFromString(const string& content) {
+ vector<uint8_t> v;
+ v.resize(content.size());
+ memmove(&v[0], &content[0], v.size());
+ return (v);
+ }
+
+ /// @brief Test initLease4.
+ void testInitLease4();
+
+ /// @brief Test initLease6.
+ void testInitLease6();
+
+ /// @brief Test getLease4ByRelayId.
+ void testGetLeases4ByRelayId();
+
+ /// @brief Test getLease4ByRemoteId.
+ void testGetLeases4ByRemoteId();
+
+ /// @brief Test upgradeExtendedInfo4.
+ void testUpgradeExtendedInfo4(const CfgConsistency::ExtendedInfoSanity& check,
+ const LeasePageSize& page_size);
+
+ /// @brief Test getLeases6ByLink.
+ void testGetLeases6ByLink();
+
+ /// @brief Lease manager.
+ LeaseMgr* lease_mgr_;
+
+ /// @brief V4 leases.
+ Lease4Collection leases4;
+
+ /// @brief V6 leases.
+ Lease6Collection leases6;
+
+ /// @brief Current timestamp.
+ time_t now_;
+};
+
+/// @brief Verifies that the lease manager can add the v4 leases.
+void
+MySqlExtendedInfoTest::testInitLease4() {
+ initLease4();
+ EXPECT_EQ(8, leases4.size());
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ Lease4Collection got;
+ // Use the page version as it returns leases in order.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4(zero, LeasePageSize(100)));
+ ASSERT_EQ(leases4.size(), got.size());
+ auto compare = [](Lease4Ptr const& left, Lease4Ptr const& right) {
+ return (left->addr_ < right->addr_);
+ };
+ std::sort(got.begin(), got.end(), compare);
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ ConstElementPtr expected = leases4[i]->toElement();
+ LeasePtr lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(expected->equals(*lease->toElement()))
+ << "expected: " << expected->str() << "\n"
+ << "got: " << lease->toElement()->str() << "\n";
+ }
+}
+
+TEST_F(MySqlExtendedInfoTest, initLease4) {
+ testInitLease4();
+}
+
+TEST_F(MySqlExtendedInfoTest, initLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testInitLease4();
+}
+
+/// @brief Verifies that getLeases4ByRelayId works as expected.
+void
+MySqlExtendedInfoTest::testGetLeases4ByRelayId() {
+ // Lease manager is created with empty tables.
+ initLease4(false);
+
+ // Create leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> relay_id0 = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> relay_id1 = { 1, 2, 3, 4 };
+ vector<uint8_t> relay_id2 = createFromString(DUIDS[2]);
+ string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt0 += " \"sub-options\": \"0C03AABBCC\",";
+ user_context_txt0 += " \"relay-id\": \"AABBCC\" } } }";
+ ElementPtr user_context0;
+ ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+ string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt1 += " \"sub-options\": \"0C0401020304\",";
+ user_context_txt1 += " \"relay-id\": \"01020304\" } } }";
+ ElementPtr user_context1;
+ ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+ Lease4Ptr lease;
+ // lease0: addr0, id0, now.
+ lease = leases4[0];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+
+ // lease1: addr1, id1, now.
+ lease = leases4[1];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id1;
+ lease->setContext(user_context1);
+
+ // lease2: addr2, id0, now - 500.
+ lease = leases4[2];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 500;
+
+ // lease3: addr3, id0, now - 800.
+ lease = leases4[3];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 800;
+
+ // lease4: addr4, id0, now - 100.
+ lease = leases4[4];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 100;
+
+ // Add leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_TRUE(lease_mgr_->addLease(leases4[i]));
+ }
+
+ Lease4Collection got;
+ // Unknown relay id #2: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown relay id #2, now - 1000, now + 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+ zero,
+ LeasePageSize(100),
+ now_ - 1000,
+ now_ + 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, now - 2000, now - 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 2000,
+ now_ - 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, now + 1000, now + 2000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ + 1000,
+ now_ + 2000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0: 3 entries (0, 2, 3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(4, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[3];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, partial: 2 entries (0, 2).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, partial from previous: 2 entries (3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr2,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, final partial: no entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr4,
+ LeasePageSize(2)));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, from now - 500: 3 entries (0, 2, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 500));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, to now - 200: 3 entries (2, 3).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ 0, now_ - 200));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr2,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, final partial.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr4,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRelayId) {
+ testGetLeases4ByRelayId();
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRelayIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4ByRelayId();
+}
+
+/// @brief Verifies that getLeases4ByRemoteId works as expected.
+void
+MySqlExtendedInfoTest::testGetLeases4ByRemoteId() {
+ // Lease manager is created with empty tables.
+ initLease4(true);
+
+ // Update leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> remote_id0 = { 1, 2, 3, 4 };
+ vector<uint8_t> remote_id1 = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> remote_id2 = createFromString(DUIDS[2]);
+ string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt0 += " \"sub-options\": \"020401020304\",";
+ user_context_txt0 += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context0;
+ ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+ string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt1 += " \"sub-options\": \"0203AABBCC\",";
+ user_context_txt1 += " \"remote-id\": \"AABBCC\" } } }";
+ ElementPtr user_context1;
+ ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+ Lease4Ptr lease;
+ // lease0: addr0, id0, now.
+ lease = leases4[0];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+
+ // lease1: addr1, id1, now.
+ lease = leases4[1];
+ lease->remote_id_ = remote_id1;
+ lease->setContext(user_context1);
+
+ // lease2: addr2, id0, now - 500.
+ lease = leases4[2];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 500;
+
+ // lease3: addr3, id0, now - 800.
+ lease = leases4[3];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 800;
+
+ // lease4: addr4, id0, now - 100.
+ lease = leases4[4];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 100;
+
+ // Update leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_NO_THROW(lease_mgr_->updateLease4(leases4[i]));
+ }
+
+ Lease4Collection got;
+ // Unknown remote id #2: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown remote id #2, now - 1000, now + 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+ zero,
+ LeasePageSize(100),
+ now_ - 1000,
+ now_ + 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, now - 2000, now - 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 2000,
+ now_ - 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, now + 1000, now + 2000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ + 1000,
+ now_ + 2000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0: 3 entries (0, 2, 3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(4, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[3];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, partial: 2 entries (0, 2).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, partial from previous: 2 entries (3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr2,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, final partial: no entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr4,
+ LeasePageSize(2)));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, from now - 500: 3 entries (0, 2, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 500));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, to now - 200: 3 entries (2, 3).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ 0, now_ - 200));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr2,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, final partial.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr4,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRemoteId) {
+ testGetLeases4ByRemoteId();
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases4ByRemoteIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4ByRemoteId();
+}
+
+void
+MySqlExtendedInfoTest::testUpgradeExtendedInfo4(const CfgConsistency::ExtendedInfoSanity& check,
+ const LeasePageSize& page_size) {
+ // Lease manager is created with empty tables.
+ initLease4(false);
+
+ // Create leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress addr5(ADDRESS4[5]);
+ IOAddress addr6(ADDRESS4[6]);
+ IOAddress addr7(ADDRESS4[7]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> relay_id = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> relay_id2 = { 0xdd, 0xee, 0xff };
+ vector<uint8_t> remote_id = { 1, 2, 3, 4 };
+ vector<uint8_t> remote_id2 = { 5, 6, 7, 8 };
+ string user_context_txt = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt += " \"sub-options\": \"0204010203040C03AABBCC\",";
+ user_context_txt += " \"relay-id\": \"AABBCC\",";
+ user_context_txt += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ string user_context_txt_old = "{ \"ISC\": { \"relay-agent-info\":";
+ user_context_txt_old += " \"0204010203040C03AABBCC\" } }";
+ ElementPtr user_context_old;
+ ASSERT_NO_THROW(user_context_old = Element::fromJSON(user_context_txt_old));
+ string user_context_list_txt = "{ \"ISC\": { \"relay-agent-info\": [ ] } }";
+ ElementPtr user_context_list;
+ ASSERT_NO_THROW(user_context_list = Element::fromJSON(user_context_list_txt));
+ string user_context_lower_txt = "{ \"isc\": { \"relay-agent-info\":";
+ user_context_lower_txt += " \"0204010203040c03aabbcc\" } }";
+ ElementPtr user_context_lower;
+ ASSERT_NO_THROW(user_context_lower = Element::fromJSON(user_context_lower_txt));
+ string user_context_badsub_txt = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_badsub_txt += " \"sub-options\": \"foobar\",";
+ user_context_badsub_txt += " \"relay-id\": \"AABBCC\",";
+ user_context_badsub_txt += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context_badsub;
+ ASSERT_NO_THROW(user_context_badsub = Element::fromJSON(user_context_badsub_txt));
+ string user_context_extra_txt = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_extra_txt += " \"foo\": \"bar\", ";
+ user_context_extra_txt += " \"sub-options\": \"0204010203040C03AABBCC\",";
+ user_context_extra_txt += " \"relay-id\": \"AABBCC\",";
+ user_context_extra_txt += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context_extra;
+ ASSERT_NO_THROW(user_context_extra = Element::fromJSON(user_context_extra_txt));
+
+ Lease4Ptr lease;
+ // lease0: addr0, ids, before: always not updated.
+ lease = leases4[0];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context);
+ lease->cltt_ = now_ - 500;
+
+ // lease1: addr1, ids, after: always not updated.
+ lease = leases4[1];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context);
+ lease->cltt_ = now_ + 500;
+
+ // lease2: addr2, no id, old user context: updated on check > NONE.
+ lease = leases4[2];
+ ASSERT_TRUE(lease);
+ lease->setContext(user_context_old);
+
+ // lease3: addr3, ids, lower case old user context: always updated.
+ lease = leases4[3];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_lower);
+
+ // Lease4: addr4, ids, bad (list) user context: updated on check > NONE.
+ lease = leases4[4];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_list);
+
+ // Lease5: addr5, other ids: always updated.
+ lease = leases4[5];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id2;
+ lease->remote_id_ = remote_id2;
+ lease->setContext(user_context);
+
+ // Lease6: addr6, ids, bad sub-options: updated on check > FIX.
+ lease = leases4[6];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_badsub);
+
+ // Lease7: addr7, ids, extra in ISC: updated on check > STRICT.
+ lease = leases4[7];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_extra);
+
+ // Add leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_TRUE(lease_mgr_->addLease(leases4[i]));
+ }
+
+ // Set extended info consistency.
+ CfgMgr::instance().getCurrentCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(check);
+
+ size_t updated;
+ ASSERT_NO_THROW(updated = lease_mgr_->upgradeExtendedInfo4(page_size));
+
+ // Verify result.
+ switch (check) {
+ case CfgConsistency::EXTENDED_INFO_CHECK_NONE:
+ // Updated leases: 3, 5.
+ EXPECT_EQ(updated, 2);
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_FIX:
+ // Updated leases: 2, 3, 4, 5.
+ EXPECT_EQ(updated, 4);
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_STRICT:
+ // Updated leases: 2, 3, 4, 5, 6.
+ EXPECT_EQ(updated, 5);
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC:
+ default:
+ // Updated leases: 2, 3, 4, 5, 6, 7.
+ EXPECT_EQ(updated, 6);
+ break;
+ }
+
+ // Verify stored leases.
+ Lease4Collection got;
+ // Use the page version as it returns leases in order.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4(zero, LeasePageSize(100)));
+ ASSERT_EQ(leases4.size(), got.size());
+
+ // Check lease0.
+ lease = got[0];
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+
+ // Check lease1.
+ lease = got[1];
+ EXPECT_EQ(*lease, *leases4[1]);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+
+ // Check lease2.
+ lease = got[2];
+ Lease4Ptr expected2(new Lease4(*leases4[2]));
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) {
+ EXPECT_EQ(*lease, *expected2);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ } else {
+ expected2->setContext(user_context);
+ expected2->relay_id_ = relay_id;
+ expected2->remote_id_ = remote_id;
+ EXPECT_EQ(*lease, *expected2);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ }
+
+ // Check lease3.
+ lease = got[3];
+ Lease4Ptr expected3(new Lease4(*leases4[3]));
+ expected3->relay_id_.clear();
+ expected3->remote_id_.clear();
+ EXPECT_EQ(*lease, *expected3);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+
+ // Check lease4.
+ lease = got[4];
+ Lease4Ptr expected4(new Lease4(*leases4[4]));
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) {
+ EXPECT_EQ(*lease, *expected4);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ } else {
+ expected4->relay_id_.clear();
+ expected4->remote_id_.clear();
+ expected4->setContext(ElementPtr());
+ EXPECT_EQ(*lease, *expected4);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ }
+
+ // Check lease5.
+ lease = got[5];
+ Lease4Ptr expected5(new Lease4(*leases4[5]));
+ expected5->relay_id_ = relay_id;
+ expected5->remote_id_ = remote_id;
+ EXPECT_EQ(*lease, *expected5);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+
+ // Check lease6.
+ lease = got[6];
+ Lease4Ptr expected6(new Lease4(*leases4[6]));
+ if ((check != CfgConsistency::EXTENDED_INFO_CHECK_STRICT) &&
+ (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC)) {
+ EXPECT_EQ(*lease, *expected6);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ } else {
+ expected6->relay_id_.clear();
+ expected6->remote_id_.clear();
+ expected6->setContext(ElementPtr());
+ EXPECT_EQ(*lease, *expected6);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ }
+
+ // Check lease7.
+ lease = got[7];
+ Lease4Ptr expected7(new Lease4(*leases4[7]));
+ if (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC) {
+ EXPECT_EQ(*lease, *expected7);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ } else {
+ expected7->relay_id_.clear();
+ expected7->remote_id_.clear();
+ expected7->setContext(ElementPtr());
+ EXPECT_EQ(*lease, *expected7);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ }
+
+ // Verify getLeases4ByRelayId.
+ Lease4Collection by_relay_id;
+ EXPECT_NO_THROW(by_relay_id =
+ lease_mgr_->getLeases4ByRelayId(relay_id,
+ zero,
+ LeasePageSize(100)));
+ switch (check) {
+ case CfgConsistency::EXTENDED_INFO_CHECK_NONE:
+ // Got leases: 0, 1, 4, 5, 6, 7.
+ EXPECT_EQ(6, by_relay_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_FIX:
+ // Got leases: 0, 1, 2, 5, 6, 7.
+ EXPECT_EQ(6, by_relay_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_STRICT:
+ // Got leases: 0, 1, 2, 4, 7.
+ EXPECT_EQ(5, by_relay_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC:
+ default:
+ // Got leases: 0, 1, 2, 4.
+ EXPECT_EQ(4, by_relay_id.size());
+ break;
+ }
+
+ // Verify getLeases4ByRemoteId.
+ Lease4Collection by_remote_id;
+ EXPECT_NO_THROW(by_remote_id =
+ lease_mgr_->getLeases4ByRemoteId(remote_id,
+ zero,
+ LeasePageSize(100)));
+ switch (check) {
+ case CfgConsistency::EXTENDED_INFO_CHECK_NONE:
+ // Got leases: 0, 1, 4, 5, 6, 7.
+ EXPECT_EQ(6, by_remote_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_FIX:
+ // Got leases: 0, 1, 2, 5, 6, 7.
+ EXPECT_EQ(6, by_remote_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_STRICT:
+ // Got leases: 0, 1, 2, 4, 7.
+ EXPECT_EQ(5, by_remote_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC:
+ default:
+ // Got leases: 0, 1, 2, 4.
+ EXPECT_EQ(4, by_remote_id.size());
+ break;
+ }
+
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4None) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_NONE,
+ LeasePageSize(100));
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4Fix) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(100));
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4Strict) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ LeasePageSize(100));
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4Pedantic) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ LeasePageSize(100));
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4_10) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(10));
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4_5) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(5));
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4_2) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(2));
+}
+
+TEST_F(MySqlExtendedInfoTest, upgradeExtendedInfo4_1) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(1));
+}
+
+/// @brief Verifies that the lease manager can add the v6 leases.
+void
+MySqlExtendedInfoTest::testInitLease6() {
+ initLease6();
+ EXPECT_EQ(8, leases6.size());
+ Lease6Collection got;
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6());
+ ASSERT_EQ(leases6.size(), got.size());
+ auto compare = [](Lease6Ptr const& left, Lease6Ptr const& right) {
+ return (left->addr_ < right->addr_);
+ };
+ std::sort(got.begin(), got.end(), compare);
+ for (size_t i = 0; i < leases6.size(); ++i) {
+ ConstElementPtr expected = leases6[i]->toElement();
+ LeasePtr lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(expected->equals(*lease->toElement()))
+ << "expected: " << expected->str() << "\n"
+ << "got: " << lease->toElement()->str() << "\n";
+ }
+}
+
+TEST_F(MySqlExtendedInfoTest, initLease6) {
+ testInitLease6();
+}
+
+TEST_F(MySqlExtendedInfoTest, initLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testInitLease6();
+}
+
+/// @brief Verifies that getLeases6ByLink works as expected.
+void
+MySqlExtendedInfoTest::testGetLeases6ByLink() {
+ // Lease manager is created with empty tables.
+ initLease6();
+
+ // Create parameter values.
+ IOAddress link_addr(ADDRESS6[4]);
+ IOAddress other_link_addr("2001:db8:1::4");
+ IOAddress zero = IOAddress::IPV6_ZERO_ADDRESS();
+
+ Lease6Collection got;
+ // Other link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(other_link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+ EXPECT_EQ(0, got.size());
+
+ // Link: 8 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+
+ ASSERT_EQ(8, got.size());
+ Lease6Ptr lease;
+ for (size_t i = 0; i < 8; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i]), lease->addr_);
+ }
+
+ // Link: initial partial: 4 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ zero,
+ LeasePageSize(4)));
+ ASSERT_EQ(4, got.size());
+ for (size_t i = 0; i < 4; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i]), lease->addr_);
+ }
+
+ // Link: next partial: 4 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(4)));
+ ASSERT_EQ(4, got.size());
+ for (size_t i = 0; i < 4; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i + 4]), lease->addr_);
+ }
+
+ // Link: further partial: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(4)));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases6ByLink) {
+ testGetLeases6ByLink();
+}
+
+TEST_F(MySqlExtendedInfoTest, getLeases6ByLinkMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6ByLink();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
new file mode 100644
index 0000000..b58e4b3
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -0,0 +1,1310 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/mysql_lease_mgr.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <dhcpsrv/testutils/mysql_generic_backend_unittest.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <exceptions/exceptions.h>
+#include <mysql/mysql_connection.h>
+#include <mysql/testutils/mysql_schema.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+#include <util/multi_threading_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+
+/// @brief Test fixture class for testing MySQL Lease Manager
+///
+/// Opens the database prior to each test and closes it afterwards.
+/// All pending transactions are deleted prior to closure.
+
+class MySqlLeaseMgrTest : public GenericLeaseMgrTest {
+public:
+ /// @brief Clears the database and opens connection to it.
+ void initializeTest() {
+ // Ensure we have the proper schema with no transient data.
+ createMySQLSchema();
+
+ // Connect to the database
+ try {
+ LeaseMgrFactory::create(validMySQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the MySQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ lmptr_ = &(LeaseMgrFactory::instance());
+
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destroys the LM and the schema.
+ void destroyTest() {
+ LeaseMgrFactory::destroy();
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroyMySQLSchema();
+
+ // Disable Multi-Threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Constructor
+ ///
+ /// Deletes everything from the database and opens it.
+ MySqlLeaseMgrTest() {
+ initializeTest();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Rolls back all pending transactions. The deletion of lmptr_ will close
+ /// the database. Then reopen it and delete everything created by the test.
+ virtual ~MySqlLeaseMgrTest() {
+ destroyTest();
+ }
+
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-open it. Anything committed should be
+ /// visible.
+ ///
+ /// Parameter is ignored for MySQL backend as the v4 and v6 leases share
+ /// the same database.
+ void reopen(Universe) {
+ LeaseMgrFactory::destroy();
+ LeaseMgrFactory::create(validMySQLConnectionString());
+ lmptr_ = &(LeaseMgrFactory::instance());
+ }
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the MySqlLeaseMgr can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// MySqlLeaseMgr test fixture set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+TEST(MySqlOpenTest, OpenDatabase) {
+ // Explicitly disable Multi-Threading.
+ MultiThreadingMgr::instance().setMode(false);
+
+ // Schema needs to be created for the test to work.
+ createMySQLSchema(true);
+
+ // Check that lease manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ LeaseMgrFactory::create(validMySQLConnectionString());
+ EXPECT_NO_THROW((void)LeaseMgrFactory::instance());
+ LeaseMgrFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that lease manager opens the database correctly with a longer
+ // timeout. If it fails, print the error message.
+ try {
+ string connection_string = validMySQLConnectionString() + string(" ") +
+ string(VALID_TIMEOUT);
+ LeaseMgrFactory::create(connection_string);
+ EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
+ LeaseMgrFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the lease manager when
+ // none is set throws an exception.
+ EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager);
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on LeaseMgrFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+#ifndef OS_OSX
+ // Under MacOS, connecting with an invalid host can cause a TCP/IP socket
+ // to be orphaned and never closed. This can interfere with subsequent tests
+ // which attempt to locate and manipulate MySQL client socket descriptor.
+ // In the interests of progress, we'll just avoid this test.
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+#endif
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+
+ // Check for invalid timeouts
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
+ DbInvalidTimeout);
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
+ DbInvalidTimeout);
+
+ // Check for missing parameters
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, NULL, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Check for extended info tables.
+ const char* EX_INFO = "extended-info-tables=true";
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, EX_INFO)),
+ NotImplemented);
+
+ // Tidy up after the test
+ destroyMySQLSchema(true);
+ LeaseMgrFactory::destroy();
+}
+
+/// @brief Check that database can be opened with Multi-Threading
+TEST(MySqlOpenTest, OpenDatabaseMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+
+ // Schema needs to be created for the test to work.
+ createMySQLSchema(true);
+
+ // Check that lease manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ LeaseMgrFactory::create(validMySQLConnectionString());
+ EXPECT_NO_THROW((void)LeaseMgrFactory::instance());
+ LeaseMgrFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the MySQL tests will run correctly.\n";
+ }
+
+ // Tidy up after the test
+ destroyMySQLSchema(true);
+ LeaseMgrFactory::destroy();
+}
+
+/// @brief Check the getType() method
+///
+/// getType() returns a string giving the type of the backend, which should
+/// always be "mysql".
+TEST_F(MySqlLeaseMgrTest, getType) {
+ EXPECT_EQ(std::string("mysql"), lmptr_->getType());
+}
+
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime. In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct. It does not check that the
+/// data is entered into the database correctly, only that the MYSQL_TIME
+/// structure used for the entry is correctly set up.
+TEST_F(MySqlLeaseMgrTest, checkTimeConversion) {
+ const time_t cltt = time(NULL);
+ const uint32_t valid_lft = 86400; // 1 day
+ struct tm tm_expire;
+ MYSQL_TIME mysql_expire;
+
+ // Work out what the broken-down time will be for one day
+ // after the current time.
+ time_t expire_time = cltt + valid_lft;
+ (void) localtime_r(&expire_time, &tm_expire);
+
+ // Convert to the database time
+ MySqlConnection::convertToDatabaseTime(cltt, valid_lft, mysql_expire);
+
+ // Are the times the same?
+ EXPECT_EQ(tm_expire.tm_year + 1900, mysql_expire.year);
+ EXPECT_EQ(tm_expire.tm_mon + 1, mysql_expire.month);
+ EXPECT_EQ(tm_expire.tm_mday, mysql_expire.day);
+ EXPECT_EQ(tm_expire.tm_hour, mysql_expire.hour);
+ EXPECT_EQ(tm_expire.tm_min, mysql_expire.minute);
+ EXPECT_EQ(tm_expire.tm_sec, mysql_expire.second);
+ EXPECT_EQ(0, mysql_expire.second_part);
+ EXPECT_EQ(0, mysql_expire.neg);
+
+ // Convert back
+ time_t converted_cltt = 0;
+ MySqlConnection::convertFromDatabaseTime(mysql_expire, valid_lft, converted_cltt);
+ EXPECT_EQ(cltt, converted_cltt);
+}
+
+/// @brief Check getName() returns correct database name
+TEST_F(MySqlLeaseMgrTest, getName) {
+ EXPECT_EQ(std::string("keatest"), lmptr_->getName());
+}
+
+/// @brief Check that getVersion() returns the expected version
+TEST_F(MySqlLeaseMgrTest, checkVersion) {
+ // Check version
+ pair<uint32_t, uint32_t> version;
+ ASSERT_NO_THROW(version = lmptr_->getVersion());
+ EXPECT_EQ(MYSQL_SCHEMA_VERSION_MAJOR, version.first);
+ EXPECT_EQ(MYSQL_SCHEMA_VERSION_MINOR, version.second);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// LEASE4 /////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4 (by address) and deleteLease (with an
+/// IPv4 address) works.
+TEST_F(MySqlLeaseMgrTest, basicLease4) {
+ testBasicLease4();
+}
+
+/// @brief Basic Lease4 Checks
+TEST_F(MySqlLeaseMgrTest, basicLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasicLease4();
+}
+
+/// @brief Check that Lease4 code safely handles invalid dates.
+TEST_F(MySqlLeaseMgrTest, maxDate4) {
+ testMaxDate4();
+}
+
+/// @brief Check that Lease4 code safely handles invalid dates.
+TEST_F(MySqlLeaseMgrTest, maxDate4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxDate4();
+}
+
+/// @brief checks that infinite lifetimes do not overflow.
+TEST_F(MySqlLeaseMgrTest, infiniteLifeTime4) {
+ testInfiniteLifeTime4();
+}
+
+/// @brief Lease4 update tests
+///
+/// Checks that we are able to update a lease in the database.
+TEST_F(MySqlLeaseMgrTest, updateLease4) {
+ testUpdateLease4();
+}
+
+/// @brief Lease4 update tests
+TEST_F(MySqlLeaseMgrTest, updateLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testUpdateLease4();
+}
+
+/// @brief Lease4 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(MySqlLeaseMgrTest, concurrentUpdateLease4) {
+ testConcurrentUpdateLease4();
+}
+
+/// @brief Lease4 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(MySqlLeaseMgrTest, concurrentUpdateLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testConcurrentUpdateLease4();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddr1) {
+ testGetLease4HWAddr1();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddr1MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddr1();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddr2) {
+ testGetLease4HWAddr2();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddr2MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddr2();
+}
+
+/// @brief Get lease4 by hardware address (2)
+///
+/// Check that the system can cope with getting a hardware address of
+/// any size.
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddrSize) {
+ testGetLease4HWAddrSize();
+}
+
+/// @brief Get lease4 by hardware address (2)
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddrSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddrSize();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of hardware address and subnet ID
+TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
+ testGetLease4HWAddrSubnetId();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddrSubnetId();
+}
+
+/// @brief Get lease4 by hardware address and subnet ID (2)
+///
+/// Check that the system can cope with getting a hardware address of
+/// any size.
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddrSubnetIdSize) {
+ testGetLease4HWAddrSubnetIdSize();
+}
+
+/// @brief Get lease4 by hardware address and subnet ID (2)
+TEST_F(MySqlLeaseMgrTest, getLease4HWAddrSubnetIdSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddrSubnetIdSize();
+}
+
+/// @brief This test was derived from memfile.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientId) {
+ testGetLease4ClientId();
+}
+
+/// @brief This test was derived from memfile.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientId();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// the Client ID.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientId2) {
+ testGetLease4ClientId2();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID
+TEST_F(MySqlLeaseMgrTest, getLease4ClientId2MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientId2();
+}
+
+/// @brief Get Lease4 by client ID (2)
+///
+/// Check that the system can cope with a client ID of any size.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSize) {
+ testGetLease4ClientIdSize();
+}
+
+/// @brief Get Lease4 by client ID (2)
+TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientIdSize();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of client and subnet IDs.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSubnetId) {
+ testGetLease4ClientIdSubnetId();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientIdSubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases for a specified subnet id are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases4SubnetId) {
+ testGetLeases4SubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases for a specified subnet id are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases4SubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4SubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases with a specified hostname are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases4Hostname) {
+ testGetLeases4Hostname();
+}
+
+/// @brief This test checks that all IPv4 leases with a specified hostname are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases4HostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4Hostname();
+}
+
+/// @brief This test checks that all IPv4 leases are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases4) {
+ testGetLeases4();
+}
+
+/// @brief This test checks that all IPv4 leases are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4();
+}
+
+/// @brief Test that a range of IPv4 leases is returned with paging.
+TEST_F(MySqlLeaseMgrTest, getLeases4Paged) {
+ testGetLeases4Paged();
+}
+
+/// @brief Test that a range of IPv4 leases is returned with paging.
+TEST_F(MySqlLeaseMgrTest, getLeases4PagedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4Paged();
+}
+
+/// @brief This test checks that all IPv6 leases for a specified subnet id are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases6SubnetId) {
+ testGetLeases6SubnetId();
+}
+
+/// @brief This test checks that all IPv6 leases for a specified subnet id are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases6SubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6SubnetId();
+}
+
+/// @brief This test checks that all IPv6 leases with a specified hostname are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases6Hostname) {
+ testGetLeases6Hostname();
+}
+
+/// @brief This test checks that all IPv6 leases with a specified hostname are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases6HostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6Hostname();
+}
+
+/// @brief This test checks that all IPv6 leases are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases6) {
+ testGetLeases6();
+}
+
+/// @brief This test checks that all IPv6 leases are returned.
+TEST_F(MySqlLeaseMgrTest, getLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6();
+}
+
+/// @brief Test that a range of IPv6 leases is returned with paging.
+TEST_F(MySqlLeaseMgrTest, getLeases6Paged) {
+ testGetLeases6Paged();
+}
+
+/// @brief Test that a range of IPv6 leases is returned with paging.
+TEST_F(MySqlLeaseMgrTest, getLeases6PagedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6Paged();
+}
+
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+/// updateLease4() and deleteLease can handle NULL client-id.
+/// (client-id is optional and may not be present)
+TEST_F(MySqlLeaseMgrTest, lease4NullClientId) {
+ testLease4NullClientId();
+}
+
+/// @brief Basic Lease4 Checks
+TEST_F(MySqlLeaseMgrTest, lease4NullClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease4NullClientId();
+}
+
+/// @brief Verify that too long hostname for Lease4 is not accepted.
+///
+/// Checks that the it is not possible to create a lease when the hostname
+/// length exceeds 255 characters.
+TEST_F(MySqlLeaseMgrTest, lease4InvalidHostname) {
+ testLease4InvalidHostname();
+}
+
+/// @brief Verify that too long hostname for Lease4 is not accepted.
+TEST_F(MySqlLeaseMgrTest, lease4InvalidHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease4InvalidHostname();
+}
+
+/// @brief Check that the expired DHCPv4 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(MySqlLeaseMgrTest, getExpiredLeases4) {
+ testGetExpiredLeases4();
+}
+
+/// @brief Check that the expired DHCPv4 leases can be retrieved.
+TEST_F(MySqlLeaseMgrTest, getExpiredLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetExpiredLeases4();
+}
+
+/// @brief Checks that DHCPv4 leases with infinite valid lifetime
+/// will never expire.
+TEST_F(MySqlLeaseMgrTest, infiniteAreNotExpired4) {
+ testInfiniteAreNotExpired4();
+}
+
+/// @brief Check that expired reclaimed DHCPv4 leases are removed.
+TEST_F(MySqlLeaseMgrTest, deleteExpiredReclaimedLeases4) {
+ testDeleteExpiredReclaimedLeases4();
+}
+
+/// @brief Check that expired reclaimed DHCPv4 leases are removed.
+TEST_F(MySqlLeaseMgrTest, deleteExpiredReclaimedLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteExpiredReclaimedLeases4();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// LEASE6 /////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+/// @brief Test checks whether simple add, get and delete operations
+/// are possible on Lease6
+TEST_F(MySqlLeaseMgrTest, testAddGetDelete6) {
+ testAddGetDelete6();
+}
+
+/// @brief Test checks whether simple add, get and delete operations
+/// are possible on Lease6
+TEST_F(MySqlLeaseMgrTest, testAddGetDelete6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddGetDelete6();
+}
+
+/// @brief Basic Lease6 Checks
+///
+/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
+/// IPv6 address) works.
+TEST_F(MySqlLeaseMgrTest, basicLease6) {
+ testBasicLease6();
+}
+
+/// @brief Basic Lease6 Checks
+TEST_F(MySqlLeaseMgrTest, basicLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasicLease6();
+}
+
+/// @brief Check that Lease6 code safely handles invalid dates.
+TEST_F(MySqlLeaseMgrTest, maxDate6) {
+ testMaxDate6();
+}
+
+/// @brief Check that Lease6 code safely handles invalid dates.
+TEST_F(MySqlLeaseMgrTest, maxDate6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxDate6();
+}
+
+/// @brief checks that infinite lifetimes do not overflow.
+TEST_F(MySqlLeaseMgrTest, infiniteLifeTime6) {
+ testInfiniteLifeTime6();
+}
+
+/// @brief Verify that too long hostname for Lease6 is not accepted.
+///
+/// Checks that the it is not possible to create a lease when the hostname
+/// length exceeds 255 characters.
+TEST_F(MySqlLeaseMgrTest, lease6InvalidHostname) {
+ testLease6InvalidHostname();
+}
+
+/// @brief Verify that too long hostname for Lease6 is not accepted.
+TEST_F(MySqlLeaseMgrTest, lease6InvalidHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6InvalidHostname();
+}
+
+/// @brief Verify that large IAID values work correctly.
+///
+/// Adds lease with a large IAID to the database and verifies it can
+/// fetched correctly.
+TEST_F(MySqlLeaseMgrTest, leases6LargeIaidCheck) {
+ testLease6LargeIaidCheck();
+}
+
+/// @brief Verify that large IAID values work correctly.
+TEST_F(MySqlLeaseMgrTest, leases6LargeIaidCheckMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6LargeIaidCheck();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DUID and IAID.
+TEST_F(MySqlLeaseMgrTest, getLeases6DuidIaid) {
+ testGetLeases6DuidIaid();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID
+TEST_F(MySqlLeaseMgrTest, getLeases6DuidIaidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6DuidIaid();
+}
+
+/// @brief Check that the system can cope with a DUID of allowed size.
+TEST_F(MySqlLeaseMgrTest, getLeases6DuidSize) {
+ testGetLeases6DuidSize();
+}
+
+/// @brief Check that the system can cope with a DUID of allowed size.
+TEST_F(MySqlLeaseMgrTest, getLeases6DuidSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6DuidSize();
+}
+
+/// @brief Check that getLease6 methods discriminate by lease type.
+///
+/// Adds six leases, two per lease type all with the same duid and iad but
+/// with alternating subnet_ids.
+/// It then verifies that all of getLeases6() method variants correctly
+/// discriminate between the leases based on lease type alone.
+TEST_F(MySqlLeaseMgrTest, lease6LeaseTypeCheck) {
+ testLease6LeaseTypeCheck();
+}
+
+/// @brief Check that getLease6 methods discriminate by lease type.
+TEST_F(MySqlLeaseMgrTest, lease6LeaseTypeCheckMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6LeaseTypeCheck();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DIUID and IAID.
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetId) {
+ testGetLease6DuidIaidSubnetId();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease6DuidIaidSubnetId();
+}
+
+/// @brief Test checks that getLease6() works with different DUID sizes
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
+ testGetLease6DuidIaidSubnetIdSize();
+}
+
+/// @brief Test checks that getLease6() works with different DUID sizes
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease6DuidIaidSubnetIdSize();
+}
+
+/// @brief check leases could be retrieved by DUID
+///
+/// Create leases, add them to backend and verify if it can be queried
+/// using DUID index
+TEST_F(MySqlLeaseMgrTest, getLeases6Duid) {
+ testGetLeases6Duid();
+}
+
+/// @brief check leases could be retrieved by DUID
+TEST_F(MySqlLeaseMgrTest, getLeases6DuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6Duid();
+}
+
+/// @brief Lease6 update tests
+///
+/// Checks that we are able to update a lease in the database.
+TEST_F(MySqlLeaseMgrTest, updateLease6) {
+ testUpdateLease6();
+}
+
+/// @brief Lease6 update tests
+TEST_F(MySqlLeaseMgrTest, updateLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testUpdateLease6();
+}
+
+/// @brief Lease6 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(MySqlLeaseMgrTest, concurrentUpdateLease6) {
+ testConcurrentUpdateLease6();
+}
+
+/// @brief Lease6 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(MySqlLeaseMgrTest, concurrentUpdateLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testConcurrentUpdateLease6();
+}
+
+/// @brief DHCPv4 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MySqlLeaseMgrTest, testRecreateLease4) {
+ testRecreateLease4();
+}
+
+/// @brief DHCPv4 Lease recreation tests
+TEST_F(MySqlLeaseMgrTest, testRecreateLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecreateLease4();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(MySqlLeaseMgrTest, testRecreateLease6) {
+ testRecreateLease6();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+TEST_F(MySqlLeaseMgrTest, testRecreateLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecreateLease6();
+}
+
+/// @brief Checks that null DUID is not allowed.
+TEST_F(MySqlLeaseMgrTest, nullDuid) {
+ testNullDuid();
+}
+
+/// @brief Checks that null DUID is not allowed.
+TEST_F(MySqlLeaseMgrTest, nullDuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNullDuid();
+}
+
+/// @brief Tests whether MySQL can store and retrieve hardware addresses
+TEST_F(MySqlLeaseMgrTest, testLease6Mac) {
+ testLease6MAC();
+}
+
+/// @brief Tests whether MySQL can store and retrieve hardware addresses
+TEST_F(MySqlLeaseMgrTest, testLease6MacMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6MAC();
+}
+
+/// @brief Tests whether MySQL can store and retrieve hardware addresses
+TEST_F(MySqlLeaseMgrTest, testLease6HWTypeAndSource) {
+ testLease6HWTypeAndSource();
+}
+
+/// @brief Tests whether MySQL can store and retrieve hardware addresses
+TEST_F(MySqlLeaseMgrTest, testLease6HWTypeAndSourceMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6HWTypeAndSource();
+}
+
+/// @brief Check that the expired DHCPv6 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(MySqlLeaseMgrTest, getExpiredLeases6) {
+ testGetExpiredLeases6();
+}
+
+/// @brief Check that the expired DHCPv6 leases can be retrieved.
+TEST_F(MySqlLeaseMgrTest, getExpiredLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetExpiredLeases6();
+}
+
+/// @brief Checks that DHCPv6 leases with infinite valid lifetime
+/// will never expire.
+TEST_F(MySqlLeaseMgrTest, infiniteAreNotExpired6) {
+ testInfiniteAreNotExpired6();
+}
+
+/// @brief Check that expired reclaimed DHCPv6 leases are removed.
+TEST_F(MySqlLeaseMgrTest, deleteExpiredReclaimedLeases6) {
+ testDeleteExpiredReclaimedLeases6();
+}
+
+/// @brief Check that expired reclaimed DHCPv6 leases are removed.
+TEST_F(MySqlLeaseMgrTest, deleteExpiredReclaimedLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteExpiredReclaimedLeases6();
+}
+
+/// @brief Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountLeaseStats4) {
+ testRecountLeaseStats4();
+}
+
+/// @brief Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountLeaseStats4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecountLeaseStats4();
+}
+
+/// @brief Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountLeaseStats6) {
+ testRecountLeaseStats6();
+}
+
+/// @brief Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountLeaseStats6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecountLeaseStats6();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases4) {
+ testWipeLeases4();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testWipeLeases4();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases6) {
+ testWipeLeases6();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(MySqlLeaseMgrTest, DISABLED_wipeLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testWipeLeases6();
+}
+
+/// @brief Test fixture class for validating @c LeaseMgr using
+/// MySQL as back end and MySQL connectivity loss.
+class MySqlLeaseMgrDbLostCallbackTest : public LeaseMgrDbLostCallbackTest {
+public:
+ virtual void destroySchema() {
+ destroyMySQLSchema();
+ }
+
+ virtual void createSchema() {
+ createMySQLSchema();
+ }
+
+ virtual std::string validConnectString() {
+ return (validMySQLConnectionString());
+ }
+
+ virtual std::string invalidConnectString() {
+ return (connectionString(MYSQL_VALID_TYPE, INVALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+ }
+};
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testNoCallbackOnOpenFailure) {
+ MultiThreadingTest mt(false);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testNoCallbackOnOpenFailureMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to MySQL is handled correctly.
+TEST_F(MySqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+/// @brief Tests v4 lease stats query variants.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQuery4) {
+ testLeaseStatsQuery4();
+}
+
+/// @brief Tests v4 lease stats query variants.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQuery4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQuery4();
+}
+
+/// @brief Tests v6 lease stats query variants.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQuery6) {
+ testLeaseStatsQuery6();
+}
+
+/// @brief Tests v6 lease stats query variants.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQuery6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQuery6();
+}
+
+/// @brief Tests v4 lease stats to be attributed to the wrong subnet.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQueryAttribution4) {
+ testLeaseStatsQueryAttribution4();
+}
+
+/// @brief Tests v4 lease stats to be attributed to the wrong subnet.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQueryAttribution4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQueryAttribution4();
+}
+
+/// @brief Tests v6 lease stats to be attributed to the wrong subnet.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQueryAttribution6) {
+ testLeaseStatsQueryAttribution6();
+}
+
+/// @brief Tests v6 lease stats to be attributed to the wrong subnet.
+TEST_F(MySqlLeaseMgrTest, leaseStatsQueryAttribution6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQueryAttribution6();
+}
+
+/// @brief This test is a basic check for the generic backend test class,
+/// rather than any production code check.
+TEST_F(MySqlGenericBackendTest, leaseCount) {
+
+ // Create database connection parameter list
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+
+ // Create and open the database connection
+ MySqlConnection conn(params);
+ conn.openDatabase();
+
+ // Check that the countRows is working. It's used extensively in other
+ // tests, so basic check is enough here.
+ EXPECT_EQ(0, countRows(conn, "lease4"));
+}
+
+/// @brief Checks that no exceptions are thrown when inquiring about JSON
+/// support and prints an informative message.
+TEST_F(MySqlLeaseMgrTest, isJsonSupported) {
+ bool json_supported;
+ ASSERT_NO_THROW_LOG(json_supported = LeaseMgrFactory::instance().isJsonSupported());
+ std::cout << "JSON support is " << (json_supported ? "" : "not ") <<
+ "enabled in the database." << std::endl;
+}
+
+// Verifies that v4 class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(MySqlLeaseMgrTest, classLeaseCount4) {
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ testClassLeaseCount4();
+}
+
+// Verifies that v6 IA_NA class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(MySqlLeaseMgrTest, classLeaseCount6_NA) {
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ testClassLeaseCount6(Lease::TYPE_NA);
+}
+
+// Verifies that v6 IA_PD class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(MySqlLeaseMgrTest, classLeaseCount6_PD) {
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ testClassLeaseCount6(Lease::TYPE_PD);
+}
+
+/// @brief Checks that a null user context allows allocation.
+TEST_F(MySqlLeaseMgrTest, checkLimitsNull) {
+ std::string text;
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(nullptr));
+ EXPECT_TRUE(text.empty());
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(nullptr));
+ EXPECT_TRUE(text.empty());
+}
+
+/// @brief Checks a few v4 limit checking scenarios.
+TEST_F(MySqlLeaseMgrTest, checkLimits4) {
+ // Can't assume anything about the error message.
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ // The rest of the checks are only for databases with JSON support.
+ testLeaseLimits4();
+}
+
+/// @brief Checks a few v6 limit checking scenarios.
+TEST_F(MySqlLeaseMgrTest, checkLimits6) {
+ // Can't assume anything about the error message.
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ // The rest of the checks are only for databases with JSON support.
+ testLeaseLimits6();
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MySqlLeaseMgrTest, trackAddLease4) {
+ testTrackAddLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(MySqlLeaseMgrTest, trackAddLease4MultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLease4(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is added.
+TEST_F(MySqlLeaseMgrTest, trackAddLeaseNA) {
+ testTrackAddLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is added.
+TEST_F(MySqlLeaseMgrTest, trackAddLeaseNAMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLeaseNA(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is added.
+TEST_F(MySqlLeaseMgrTest, trackAddLeasePD) {
+ testTrackAddLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is added.
+TEST_F(MySqlLeaseMgrTest, trackAddLeasePDMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLeasePD(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is updated.
+TEST_F(MySqlLeaseMgrTest, trackUpdateLease4) {
+ testTrackUpdateLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is updated.
+TEST_F(MySqlLeaseMgrTest, trackUpdateLease4MultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLease4(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is updated.
+TEST_F(MySqlLeaseMgrTest, trackUpdateLeaseNA) {
+ testTrackUpdateLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is updated.
+TEST_F(MySqlLeaseMgrTest, trackUpdateLeaseNAMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLeaseNA(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is updated.
+TEST_F(MySqlLeaseMgrTest, trackUpdateLeasePD) {
+ testTrackUpdateLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is updated.
+TEST_F(MySqlLeaseMgrTest, trackUpdateLeasePDMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLeasePD(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is deleted.
+TEST_F(MySqlLeaseMgrTest, trackDeleteLease4) {
+ testTrackDeleteLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is deleted.
+TEST_F(MySqlLeaseMgrTest, trackDeleteLease4MultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLease4(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is deleted.
+TEST_F(MySqlLeaseMgrTest, trackDeleteLeaseNA) {
+ testTrackDeleteLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is deleted.
+TEST_F(MySqlLeaseMgrTest, trackDeleteLeaseNAMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLeaseNA(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is deleted.
+TEST_F(MySqlLeaseMgrTest, trackDeleteLeasePD) {
+ testTrackDeleteLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is deleted.
+TEST_F(MySqlLeaseMgrTest, trackDeleteLeasePDMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLeasePD(true);
+}
+
+/// @brief Checks that the lease manager can be recreated and its
+/// registered callbacks preserved, if desired.
+TEST_F(MySqlLeaseMgrTest, recreateWithCallbacks) {
+ testRecreateWithCallbacks(validMySQLConnectionString());
+}
+
+/// @brief Checks that the lease manager can be recreated without the
+/// previously registered callbacks.
+TEST_F(MySqlLeaseMgrTest, recreateWithoutCallbacks) {
+ testRecreateWithoutCallbacks(validMySQLConnectionString());
+}
+
+TEST_F(MySqlLeaseMgrTest, bigStats) {
+ testBigStats();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/ncr_generator_unittest.cc b/src/lib/dhcpsrv/tests/ncr_generator_unittest.cc
new file mode 100644
index 0000000..8b1d122
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/ncr_generator_unittest.cc
@@ -0,0 +1,737 @@
+// Copyright (C) 2015-2023 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 <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/ncr_generator.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/d2_client_mgr.h>
+#include <dhcpsrv/lease.h>
+#include <util/optional.h>
+
+#include <gtest/gtest.h>
+#include <ctime>
+#include <functional>
+#include <stdint.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Base test fixture class for testing generation of the name
+/// change requests from leases.
+///
+/// @tparam LeasePtrType One of the @c Lease4Ptr or @c Lease6Ptr.
+template<typename LeasePtrType>
+class NCRGeneratorTest : public ::testing::Test {
+public:
+
+ /// @brief Reference to the D2 client manager.
+ D2ClientMgr& d2_mgr_;
+
+ /// @brief Pointer to the lease object used by the tests.
+ LeasePtrType lease_;
+
+ /// @brief Pointer to the lease's subnet
+ SubnetPtr subnet_;
+
+ /// @brief Constructor.
+ NCRGeneratorTest()
+ : d2_mgr_(CfgMgr::instance().getD2ClientMgr()), lease_() {
+ }
+
+ /// @brief Destructor
+ virtual ~NCRGeneratorTest() = default;
+
+ /// @brief Initializes the lease pointer used by the tests and starts D2.
+ ///
+ /// This method initializes the pointer to the lease which will be used
+ /// throughout the tests. Because the lease may be either a v4 or v6 lease
+ /// it calls a virtual function @c initLease, which must be implemented
+ /// in the derived classes as appropriate. Note that lease object can't
+ /// be initialized in the constructor, because it is not allowed to
+ /// call virtual functions in the constructors. Hence, the @c SetUp
+ /// function is needed.
+ virtual void SetUp() {
+ // Base class SetUp.
+ ::testing::Test::SetUp();
+ // Initialize lease_ object.
+ initLease();
+ // Start D2 by default.
+ enableD2();
+ }
+
+ /// @brief Stops D2.
+ virtual void TearDown() {
+ // Stop D2 if running.
+ disableD2();
+ // Base class TearDown.
+ ::testing::Test::TearDown();
+
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Enables DHCP-DDNS updates.
+ ///
+ /// Replaces the current D2ClientConfiguration with a configuration
+ /// which has updates enabled and the control options set based upon
+ /// the bit mask of options.
+ void enableD2() {
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ ASSERT_NO_THROW(cfg->enableUpdates(true));
+ ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
+ d2_mgr_.startSender(std::bind(&NCRGeneratorTest::d2ErrorHandler, this,
+ ph::_1, ph::_2));
+ }
+
+ /// @brief Disables DHCP-DDNS updates.
+ void disableD2() {
+ d2_mgr_.stopSender();
+ // Default constructor creates a config with DHCP-DDNS updates
+ // disabled.
+ D2ClientConfigPtr cfg(new D2ClientConfig());
+ CfgMgr::instance().setD2ClientConfig(cfg);
+ }
+
+ /// @brief No-op error handler for D2.
+ void d2ErrorHandler(const NameChangeSender::Result, NameChangeRequestPtr&) {
+ // no-op
+ }
+
+ /// @brief Abstract method to initialize @c lease_ object.
+ virtual void initLease() = 0;
+
+ /// @brief Verify that NameChangeRequest holds valid values.
+ ///
+ /// This function picks first NameChangeRequest from the internal server's
+ /// queue and checks that it holds valid parameters. The NameChangeRequest
+ /// is removed from the queue.
+ ///
+ /// @param type An expected type of the NameChangeRequest (Add or Remove).
+ /// @param reverse An expected setting of the reverse update flag.
+ /// @param forward An expected setting of the forward update flag.
+ /// @param addr A string representation of the IPv6 address held in the
+ /// NameChangeRequest.
+ /// @param dhcid An expected DHCID value.
+ /// @note This value is the value that is produced by
+ /// dhcp_ddns::D2Dhcid::createDigest() with the appropriate arguments. This
+ /// method uses encryption tools to produce the value which cannot be
+ /// easily duplicated by hand. It is more or less necessary to generate
+ /// these values programmatically and place them here. Should the
+ /// underlying implementation of createDigest() change these test values
+ /// will likely need to be updated as well.
+ /// @param expires A timestamp when the lease associated with the
+ /// NameChangeRequest expires.
+ /// @param len A valid lifetime of the lease associated with the
+ /// NameChangeRequest.
+ /// @param fqdn The expected string value of the FQDN, if blank the
+ /// check is skipped
+ void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
+ const bool reverse, const bool forward,
+ const std::string& addr,
+ const std::string& dhcid,
+ const uint64_t expires,
+ const uint16_t len,
+ const std::string& fqdn="",
+ const bool use_conflict_resolution = true) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = CfgMgr::instance().getD2ClientMgr().peekAt(0));
+ ASSERT_TRUE(ncr);
+
+ EXPECT_EQ(type, ncr->getChangeType());
+ EXPECT_EQ(forward, ncr->isForwardChange());
+ EXPECT_EQ(reverse, ncr->isReverseChange());
+ EXPECT_EQ(addr, ncr->getIpAddress());
+ EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
+ EXPECT_EQ(expires, ncr->getLeaseExpiresOn());
+ EXPECT_EQ(len, ncr->getLeaseLength());
+ EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
+ EXPECT_EQ(use_conflict_resolution, ncr->useConflictResolution());
+
+ if (!fqdn.empty()) {
+ EXPECT_EQ(fqdn, ncr->getFqdn());
+ }
+
+ // Process the message off the queue
+ ASSERT_NO_THROW(CfgMgr::instance().getD2ClientMgr().runReadyIO());
+ }
+
+ /// @brief Sets the FQDN information for a lease and queues an NCR.
+ ///
+ /// @param fwd Perform forward update.
+ /// @param rev Perform reverse update.
+ /// @param fqdn Hostname.
+ void queueRemovalNCR(const bool fwd, const bool rev, const std::string& fqdn) {
+ lease_->fqdn_fwd_ = fwd;
+ lease_->fqdn_rev_ = rev;
+ lease_->hostname_ = fqdn;
+
+ /// Send NCR to D2.
+ ASSERT_NO_THROW(queueNCR(CHG_REMOVE, lease_));
+ }
+
+ /// @brief Sets the FQDN information for a lease and queues an NCR.
+ ///
+ /// @param chg_type Name change type.
+ /// @param fwd Perform forward update.
+ /// @param rev Perform reverse update.
+ /// @param fqdn Hostname.
+ void sendNCR(const NameChangeType chg_type, const bool fwd, const bool rev,
+ const std::string& fqdn) {
+ lease_->fqdn_fwd_ = fwd;
+ lease_->fqdn_rev_ = rev;
+ lease_->hostname_ = fqdn;
+
+ /// Send NCR to D2.
+ ASSERT_NO_THROW(queueNCR(chg_type, lease_));
+ }
+
+ /// @brief Test that for the given values the NCR is not generated.
+ ///
+ /// @param chg_type Name change type.
+ /// @param fwd Perform forward update.
+ /// @param rev Perform reverse update.
+ /// @param fqdn Hostname.
+ void testNoUpdate(const NameChangeType chg_type, const bool fwd, const bool rev,
+ const std::string& fqdn) {
+ ASSERT_NO_FATAL_FAILURE(sendNCR(chg_type, fwd, rev, fqdn));
+ ASSERT_EQ(0, d2_mgr_.getQueueSize());
+ }
+
+ /// @brief Test that sending an NCR while DNS updates would not throw.
+ ///
+ /// @param chg_type Name change type.
+ void testD2Disabled(const NameChangeType chg_type) {
+ // Disable DDNS updates.
+ disableD2();
+ ASSERT_NO_FATAL_FAILURE(sendNCR(chg_type, true, true, "MYHOST.example.com."));
+ }
+
+ /// @brief Test that NCR is generated as expected.
+ ///
+ /// @param chg_type Name change type.
+ /// @param fwd Perform forward update.
+ /// @param rev Perform reverse update.
+ /// @param fqdn Hostname.
+ /// @param exp_dhcid Expected DHCID.
+ /// @param exp_use_cr expected value of conflict resolution flag
+ void testNCR(const NameChangeType chg_type, const bool fwd, const bool rev,
+ const std::string& fqdn, const std::string exp_dhcid,
+ const bool exp_use_cr = true,
+ const Optional<double> ttl_percent = Optional<double>()) {
+ // Queue NCR.
+ ASSERT_NO_FATAL_FAILURE(sendNCR(chg_type, fwd, rev, fqdn));
+ // Expecting one NCR be generated.
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ // Calculate expected ttl.
+ uint32_t ttl = calculateDdnsTtl(lease_->valid_lft_, ttl_percent);
+
+ // Check the details of the NCR.
+ verifyNameChangeRequest(chg_type, rev, fwd, lease_->addr_.toText(), exp_dhcid,
+ lease_->cltt_ + ttl, ttl, fqdn, exp_use_cr);
+ }
+
+ /// @brief Test that calling queueNCR for NULL lease doesn't cause
+ /// an exception.
+ ///
+ /// @param chg_type Name change type.
+ void testNullLease(const NameChangeType chg_type) {
+ lease_.reset();
+ ASSERT_NO_FATAL_FAILURE(queueNCR(chg_type, lease_));
+ EXPECT_EQ(0, d2_mgr_.getQueueSize());
+ }
+};
+
+/// @brief Test fixture class implementation for DHCPv6.
+class NCRGenerator6Test : public NCRGeneratorTest<Lease6Ptr> {
+public:
+
+ /// @brief Pointer to the DUID used in the tests.
+ DuidPtr duid_;
+
+ /// @brief Constructor.
+ ///
+ /// Initializes DUID.
+ NCRGenerator6Test()
+ : duid_() {
+ duid_.reset(new DUID(DUID::fromText("01:02:03:04:05:06:07:08:09")));
+ }
+
+ /// @brief Implementation of the method creating DHCPv6 lease instance.
+ virtual void initLease() {
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 100, 200, 300, 400, SubnetID(1)));
+ // Normally, this would be set via defaults
+ subnet->setDdnsUseConflictResolution(true);
+
+ Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::200")));
+ subnet->addPool(pool);
+ subnet_ = subnet;
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet);
+ cfg_mgr.commit();
+
+ lease_.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ duid_, 1234, 501, 502, subnet_->getID(), HWAddrPtr()));
+ }
+};
+
+
+// Test creation of the NameChangeRequest for both forward and reverse
+// mapping for the given lease.
+TEST_F(NCRGenerator6Test, fwdRev) {
+ // Part of the domain name is in upper case, to test that it gets converted
+ // to lower case before DHCID is computed. So, we should get the same DHCID
+ // as if we typed domain-name in lower case.
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+
+ // Now try the same test with all lower case.
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, true, true, "myhost.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, true, true, "MYHOST.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, true, true, "myhost.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+
+}
+
+// Checks that NameChangeRequests are not created when ddns updates are disabled.
+TEST_F(NCRGenerator6Test, d2Disabled) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testD2Disabled(CHG_REMOVE);
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testD2Disabled(CHG_ADD);
+ }
+}
+
+// Test creation of the NameChangeRequest for reverse mapping in the
+// given lease.
+TEST_F(NCRGenerator6Test, revOnly) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, false, true, "myhost.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, false, true, "myhost.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+}
+
+// Test creation of the NameChangeRequest for forward mapping in the
+// given lease.
+TEST_F(NCRGenerator6Test, fwdOnly) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, true, false, "myhost.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, true, false, "myhost.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3"
+ "D0ECCEA5E0DD71730F392119A");
+ }
+}
+
+
+// Test that NameChangeRequest is not generated when neither forward
+// nor reverse DNS update has been performed for a lease.
+TEST_F(NCRGenerator6Test, noFwdRevUpdate) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNoUpdate(CHG_REMOVE, false, false, "myhost.example.com.");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNoUpdate(CHG_ADD, false, false, "myhost.example.com.");
+ }
+}
+
+// Test that NameChangeRequest is not generated if the hostname hasn't been
+// specified for a lease for which forward and reverse mapping has been set.
+TEST_F(NCRGenerator6Test, noHostname) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNoUpdate(CHG_REMOVE, false, false, "");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNoUpdate(CHG_ADD, false, false, "");
+ }
+}
+
+// Test that NameChangeRequest is not generated if an invalid hostname has
+// been specified for a lease for which forward and reverse mapping has been
+// set.
+TEST_F(NCRGenerator6Test, wrongHostname) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNoUpdate(CHG_REMOVE, false, false, "myhost...example.com.");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNoUpdate(CHG_ADD, false, false, "myhost...example.com.");
+ }
+}
+
+// Test that NameChangeRequest is not generated if the lease is not an
+// address lease, i.e. is a prefix.
+TEST_F(NCRGenerator6Test, wrongLeaseType) {
+ // Change lease type to delegated prefix.
+ lease_->type_ = Lease::TYPE_PD;
+
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNoUpdate(CHG_REMOVE, true, true, "myhost.example.org.");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNoUpdate(CHG_ADD, true, true, "myhost.example.org.");
+ }
+}
+
+// Test that NameChangeRequest is not generated if the lease is NULL,
+// and that the call to queueNCR doesn't cause an exception or
+// assertion.
+TEST_F(NCRGenerator6Test, nullLease) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNullLease(CHG_REMOVE);
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNullLease(CHG_ADD);
+ }
+}
+
+// Verify that conflict resolution is set correctly by v6 queueNCR()
+TEST_F(NCRGenerator6Test, useConflictResolution) {
+ {
+ SCOPED_TRACE("Subnet flag is false");
+ subnet_->setDdnsUseConflictResolution(false);
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3D0ECCEA5E0DD71730F392119A",
+ false);
+ }
+ {
+ SCOPED_TRACE("Subnet flag is true");
+ subnet_->setDdnsUseConflictResolution(true);
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000201BE0D7A66F8AB6C4082E7F8B81E2656667A102E3D0ECCEA5E0DD71730F392119A",
+ true);
+ }
+}
+
+/// @brief Test fixture class implementation for DHCPv4.
+class NCRGenerator4Test : public NCRGeneratorTest<Lease4Ptr> {
+public:
+
+ /// @brief Pointer to HW address used by the tests.
+ HWAddrPtr hwaddr_;
+
+ /// @brief Constructor.
+ ///
+ /// Initializes HW address.
+ NCRGenerator4Test()
+ : hwaddr_(new HWAddr(HWAddr::fromText("01:02:03:04:05:06"))) {
+ }
+
+ /// @brief Implementation of the method creating DHCPv4 lease instance.
+ virtual void initLease() {
+
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(1)));
+ // Normally, this would be set via defaults
+ subnet->setDdnsUseConflictResolution(true);
+
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.200")));
+ subnet->addPool(pool);
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet);
+ cfg_mgr.commit();
+
+ subnet_ = subnet;
+ lease_.reset(new Lease4(IOAddress("192.0.2.1"), hwaddr_, ClientIdPtr(),
+ 100, time(NULL), subnet_->getID()));
+ }
+
+};
+
+// Test creation of the NameChangeRequest for both forward and reverse
+// mapping for the given lease.
+TEST_F(NCRGenerator4Test, fwdRev) {
+ // Part of the domain name is in upper case, to test that it gets converted
+ // to lower case before DHCID is computed. So, we should get the same DHCID
+ // as if we typed domain-name in lower case.
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD");
+ }
+
+ // Now try the same with all lower case.
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, true, true, "myhost.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD");
+ }
+
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, true, true, "MYHOST.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD");
+ }
+
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, true, true, "myhost.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD");
+ }
+}
+
+// Checks that NameChangeRequests are not created when ddns updates are disabled.
+TEST_F(NCRGenerator4Test, d2Disabled) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testD2Disabled(CHG_REMOVE);
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testD2Disabled(CHG_ADD);
+ }
+}
+
+// Test creation of the NameChangeRequest for reverse mapping in the
+// given lease.
+TEST_F(NCRGenerator4Test, revOnly) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, false, true, "myhost.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3B"
+ "03AB370BFF46BFA309AE7BFD");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, false, true, "myhost.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3B"
+ "03AB370BFF46BFA309AE7BFD");
+ }
+}
+
+// Test creation of the NameChangeRequest for forward mapping in the
+// given lease.
+TEST_F(NCRGenerator4Test, fwdOnly) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, true, false, "myhost.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3B"
+ "03AB370BFF46BFA309AE7BFD");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, true, false, "myhost.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3B"
+ "03AB370BFF46BFA309AE7BFD");
+ }
+}
+
+// Test that NameChangeRequest is not generated when neither forward
+// nor reverse DNS update has been performed for a lease.
+TEST_F(NCRGenerator4Test, noFwdRevUpdate) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNoUpdate(CHG_REMOVE, false, false, "myhost.example.com.");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNoUpdate(CHG_ADD, false, false, "myhost.example.com.");
+ }
+}
+
+// Test that NameChangeRequest is not generated if the hostname hasn't been
+// specified for a lease for which forward and reverse mapping has been set.
+TEST_F(NCRGenerator4Test, noHostname) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNoUpdate(CHG_REMOVE, false, false, "");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNoUpdate(CHG_ADD, false, false, "");
+ }
+}
+
+// Test that NameChangeRequest is not generated if the invalid hostname has
+// been specified for a lease for which forward and reverse mapping has been
+// set.
+TEST_F(NCRGenerator4Test, wrongHostname) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNoUpdate(CHG_REMOVE, false, false, "myhost...example.org.");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNoUpdate(CHG_ADD, false, false, "myhost...example.org.");
+ }
+}
+
+// Test that the correct NameChangeRequest is generated when the lease
+// includes client identifier.
+TEST_F(NCRGenerator4Test, useClientId) {
+ lease_->client_id_ = ClientId::fromText("01:01:01:01");
+
+ ASSERT_NO_FATAL_FAILURE(queueRemovalNCR(true, true, "myhost.example.com."));
+ ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
+ uint32_t ttl = calculateDdnsTtl(lease_->valid_lft_);
+ verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "192.0.2.1",
+ "000101C7AA5420483BDA99C437636EA7DA2FE18"
+ "31C9679FEB031C360CA571298F3D1FA",
+ lease_->cltt_ + ttl, ttl);
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNCR(CHG_REMOVE, true, true, "myhost.example.com.",
+ "000101C7AA5420483BDA99C437636EA7DA2FE1831C9679"
+ "FEB031C360CA571298F3D1FA");
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNCR(CHG_ADD, true, true, "myhost.example.com.",
+ "000101C7AA5420483BDA99C437636EA7DA2FE1831C9679"
+ "FEB031C360CA571298F3D1FA");
+ }
+}
+
+// Test that NameChangeRequest is not generated if the lease is NULL,
+// and that the call to queueNCR doesn't cause an exception or
+// assertion.
+TEST_F(NCRGenerator4Test, nullLease) {
+ {
+ SCOPED_TRACE("case CHG_REMOVE");
+ testNullLease(CHG_REMOVE);
+ }
+ {
+ SCOPED_TRACE("case CHG_ADD");
+ testNullLease(CHG_ADD);
+ }
+}
+
+// Verify that conflict resolution is set correctly by v4 queueNCR()
+TEST_F(NCRGenerator4Test, useConflictResolution) {
+ {
+ SCOPED_TRACE("Subnet flag is false");
+ subnet_->setDdnsUseConflictResolution(false);
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD", false);
+ }
+ {
+ SCOPED_TRACE("Subnet flag is true");
+ subnet_->setDdnsUseConflictResolution(true);
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD", true);
+ }
+}
+
+// Verify that calculateDdnsTtl() produces the expected values.
+TEST_F(NCRGenerator4Test, calculateDdnsTtl) {
+
+ // A life time less than or equal to 1800 should yield a TTL of 600 seconds.
+ EXPECT_EQ(600, calculateDdnsTtl(100));
+
+ // A life time > 1800 should be 1/3 of the value.
+ EXPECT_EQ(601, calculateDdnsTtl(1803));
+
+ // Now check permutations of values for ddns-ttl-percent.
+ util::Optional<double> ddns_ttl_percent;
+
+ // Unspecified percent should result in normal per RFC calculation.
+ EXPECT_EQ(601, calculateDdnsTtl(1803, ddns_ttl_percent));
+
+ // A percentage of zero should be ignored.
+ ddns_ttl_percent = 0.0;
+ EXPECT_EQ(601, calculateDdnsTtl(1803, ddns_ttl_percent));
+
+ // A percentage that results in near zero should be ignored.
+ ddns_ttl_percent = 0.000005;
+ EXPECT_EQ(601, calculateDdnsTtl(1803, ddns_ttl_percent));
+
+ // A large enough percentage should be used.
+ ddns_ttl_percent = 0.01;
+ EXPECT_EQ(18, calculateDdnsTtl(1803, ddns_ttl_percent));
+
+ // A large enough percentage should be used.
+ ddns_ttl_percent = 1.50;
+ EXPECT_EQ(2705, calculateDdnsTtl(1803, ddns_ttl_percent));
+}
+
+// Verify that ddns-ttl-percent is used correctly by v4 queueNCR()
+TEST_F(NCRGenerator4Test, withTtlPercent) {
+ {
+ SCOPED_TRACE("Ttl percent of 0");
+ Optional<double> ttl_percent(0);
+ subnet_->setDdnsTtlPercent(ttl_percent);
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD", true, ttl_percent);
+ }
+ {
+ SCOPED_TRACE("Ttl percent of 1.5");
+ Optional<double> ttl_percent(1.5);
+ subnet_->setDdnsTtlPercent(ttl_percent);
+ testNCR(CHG_REMOVE, true, true, "MYHOST.example.com.",
+ "000001E356D43E5F0A496D65BCA24D982D646140813E3"
+ "B03AB370BFF46BFA309AE7BFD", true, ttl_percent);
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/network_state_unittest.cc b/src/lib/dhcpsrv/tests/network_state_unittest.cc
new file mode 100644
index 0000000..7edf458
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/network_state_unittest.cc
@@ -0,0 +1,784 @@
+// Copyright (C) 2017-2021 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 <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <dhcpsrv/network_state.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <gtest/gtest.h>
+#include <functional>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test fixture class for @c NetworkState class.
+class NetworkStateTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ NetworkStateTest()
+ : io_service_(new IOService()) {
+ TimerMgr::instance()->unregisterTimers();
+ TimerMgr::instance()->setIOService(io_service_);
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ virtual ~NetworkStateTest() {
+ // Cancel timers.
+ TimerMgr::instance()->unregisterTimers();
+ // Make sure IO service will stop when no timers are scheduled.
+ io_service_->stopWork();
+ // Run outstanding tasks.
+ io_service_->run();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief This test verifies the default is enable state.
+ void defaultTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv4
+ /// service using 'user command' origin.
+ void disableEnableService4UsingUserCommandOriginTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv4
+ /// service using 'HA command' origin.
+ void disableEnableService4UsingHACommandOriginTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv4
+ /// service using 'DB connection' origin.
+ void disableEnableService4UsingDBConnectionOriginTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv4
+ /// service using a combination of origins.
+ /// 1. Disable using 'user command' origin 2 times (expect disabled state).
+ /// 2. Disable using 'HA command' origin 2 times (expect disabled state).
+ /// 3. Disable using 'DB connection' origin 2 times (expect disabled state).
+ /// 4. Enable using 'user command' origin 1 time (expect disabled state).
+ /// 5. Enable using 'HA command' origin 1 time (expect disabled state).
+ /// 6. Enable using 'DB connection' origin 2 times (expect enabled state).
+ void disableEnableService4UsingMultipleOriginsTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv6
+ /// service using 'user command' origin.
+ void disableEnableService6UsingUserCommandOriginTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv6
+ /// service using 'HA command' origin.
+ void disableEnableService6UsingHACommandOriginTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv6
+ /// service using 'DB connection' origin.
+ void disableEnableService6UsingDBConnectionOriginTest();
+
+ /// @brief This test verifies that it is possible to disable and then enable DHCPv6
+ /// service using a combination of origins.
+ /// 1. Disable using 'user command' origin 2 times (expect disabled state).
+ /// 2. Disable using 'HA command' origin 2 times (expect disabled state).
+ /// 3. Disable using 'DB connection' origin 2 times (expect disabled state).
+ /// 4. Enable using 'user command' origin 1 time (expect disabled state).
+ /// 5. Enable using 'HA command' origin 1 time (expect disabled state).
+ /// 6. Enable using 'DB connection' origin 2 times (expect enabled state).
+ void disableEnableService6UsingMultipleOriginsTest();
+
+ /// @brief This test verifies that reset works, so that internal state is reset after
+ /// all managers are recreated.
+ /// 1. Disable using 'user command' origin 3 times (expect disabled state).
+ /// 2. Disable using 'HA command' origin 1 time (expect disabled state).
+ /// 3. Disable using 'DB connection' origin 1 time (expect disabled state).
+ /// 4. Reset using 'user command' origin (expect disabled state).
+ /// 5. Enable using 'HA command' origin 1 time (expect disabled state).
+ /// 6. Enable using 'DB connection' origin 1 time (expect enabled state).
+ /// 7. Disable using 'user command' origin 3 times (expect disabled state).
+ /// 8. Reset using 'user command' origin (expect enabled state).
+ void resetUsingUserCommandOriginTest();
+
+ /// @brief This test verifies that reset works, so that internal state is reset after
+ /// all managers are recreated.
+ /// 1. Disable using 'user command' origin 1 time (expect disabled state).
+ /// 2. Disable using 'HA command' origin 3 times (expect disabled state).
+ /// 3. Disable using 'DB connection' origin 1 time (expect disabled state).
+ /// 4. Reset using 'HA command' origin (expect disabled state).
+ /// 5. Enable using 'user command' origin 1 time (expect disabled state).
+ /// 6. Enable using 'DB connection' origin 1 time (expect enabled state).
+ /// 7. Disable using 'HA command' origin 3 times (expect disabled state).
+ /// 8. Reset using 'HA command' origin (expect enabled state).
+ void resetUsingHACommandOriginTest();
+
+ /// @brief This test verifies that reset works, so that internal state is reset after
+ /// all managers are recreated.
+ /// 1. Disable using 'user command' origin 1 time (expect disabled state).
+ /// 2. Disable using 'HA command' origin 1 time (expect disabled state).
+ /// 3. Disable using 'DB connection' origin 3 time (expect disabled state).
+ /// 4. Reset using 'DB connection' origin (expect disabled state).
+ /// 5. Enable using 'user command' origin 1 time (expect disabled state).
+ /// 6. Enable using 'DB connection' origin 1 time (expect enabled state).
+ /// 7. Disable using 'DB connection' origin 3 times (expect disabled state).
+ /// 8. Reset using 'DB connection' origin (expect enabled state).
+ void resetUsingDBConnectionOriginTest();
+
+ /// @brief This test verifies that enableAll() enables the service. This test will be
+ /// extended in the future to verify that it also enables disabled scopes.
+ void enableAllTest();
+
+ /// @brief This test verifies that it is possible to setup delayed execution of enableAll
+ /// function.
+ void delayedEnableAllTest();
+
+ /// @brief This test verifies that explicitly enabling the service cancels the timer
+ /// scheduled for automatically enabling it.
+ void earlyEnableAllTest();
+
+ /// @brief This test verifies that it is possible to call delayedEnableAll multiple times
+ /// and that it results in only one timer being scheduled.
+ void multipleDelayedEnableAllTest();
+
+ /// @brief This test verifies that it is possible to call delayedEnableAll multiple times
+ /// from different origins and that it results in each timer being scheduled.
+ void multipleDifferentOriginsDelayedEnableAllTest();
+
+ /// @brief Runs IO service with a timeout.
+ ///
+ /// @param timeout_ms Timeout for running IO service in milliseconds.
+ void runIOService(const long timeout_ms) {
+ sleep(timeout_ms/1000);
+ io_service_->poll();
+ }
+
+ /// @brief IO service used during the tests.
+ IOServicePtr io_service_;
+};
+
+// This test verifies the default is enable state.
+void
+NetworkStateTest::defaultTest() {
+ NetworkState state4(NetworkState::DHCPv4);
+ EXPECT_TRUE(state4.isServiceEnabled());
+ NetworkState state6(NetworkState::DHCPv6);
+ EXPECT_TRUE(state6.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv4
+// service using 'user command' origin.
+void
+NetworkStateTest::disableEnableService4UsingUserCommandOriginTest() {
+ NetworkState state(NetworkState::DHCPv4);
+
+ // Test that enable/disable using 'user command' origin works
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test that using 'user command' origin does not use internal counter
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv4
+// service using 'HA command' origin.
+void
+NetworkStateTest::disableEnableService4UsingHACommandOriginTest() {
+ NetworkState state(NetworkState::DHCPv4);
+
+ // Test that enable/disable using 'HA command' origin works
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test that using 'HA command' origin does not use internal counter
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv4
+// service using 'DB connection' origin.
+void
+NetworkStateTest::disableEnableService4UsingDBConnectionOriginTest() {
+ NetworkState state(NetworkState::DHCPv4);
+
+ // Test that enable/disable using 'DB connection' origin works
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test that using 'DB connection' origin uses internal counter
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv4
+// service using a combination of origins.
+// 1. Disable using 'user command' origin 2 times (expect disabled state).
+// 2. Disable using 'HA command' origin 2 times (expect disabled state).
+// 3. Disable using 'DB connection' origin 2 times (expect disabled state).
+// 4. Enable using 'user command' origin 1 time (expect disabled state).
+// 5. Enable using 'HA command' origin 1 time (expect disabled state).
+// 6. Enable using 'DB connection' origin 2 times (expect enabled state).
+void
+NetworkStateTest::disableEnableService4UsingMultipleOriginsTest() {
+ NetworkState state(NetworkState::DHCPv4);
+
+ // Test that a combination properly affects the state
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv6
+// service using 'user command' origin.
+void
+NetworkStateTest::disableEnableService6UsingUserCommandOriginTest() {
+ NetworkState state(NetworkState::DHCPv6);
+
+ // Test that enable/disable using 'user command' origin works
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test that using 'user command' origin does not use internal counter
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv6
+// service using 'HA command' origin.
+void
+NetworkStateTest::disableEnableService6UsingHACommandOriginTest() {
+ NetworkState state(NetworkState::DHCPv6);
+
+ // Test that enable/disable using 'HA command' origin works
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test that using 'HA command' origin does not use internal counter
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv6
+// service using 'DB connection' origin.
+void
+NetworkStateTest::disableEnableService6UsingDBConnectionOriginTest() {
+ NetworkState state(NetworkState::DHCPv6);
+
+ // Test that enable/disable using 'DB connection' origin works
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test that using 'DB connection' origin uses internal counter
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to disable and then enable DHCPv6
+// service using a combination of origins.
+// 1. Disable using 'user command' origin 2 times (expect disabled state).
+// 2. Disable using 'HA command' origin 2 times (expect disabled state).
+// 3. Disable using 'DB connection' origin 2 times (expect disabled state).
+// 4. Enable using 'user command' origin 1 time (expect disabled state).
+// 5. Enable using 'HA command' origin 1 time (expect disabled state).
+// 6. Enable using 'DB connection' origin 2 times (expect enabled state).
+void
+NetworkStateTest::disableEnableService6UsingMultipleOriginsTest() {
+ NetworkState state(NetworkState::DHCPv6);
+
+ // Test that a combination properly affects the state
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that reset works, so that internal state is reset after
+// all managers are recreated.
+// 1. Disable using 'user command' origin 3 times (expect disabled state).
+// 2. Disable using 'HA command' origin 1 time (expect disabled state).
+// 3. Disable using 'DB connection' origin 1 time (expect disabled state).
+// 4. Reset using 'user command' origin (expect disabled state).
+// 5. Enable using 'HA command' origin 1 time (expect disabled state).
+// 6. Enable using 'DB connection' origin 1 time (expect enabled state).
+// 7. Disable using 'user command' origin 3 times (expect disabled state).
+// 8. Reset using 'user command' origin (expect enabled state).
+void
+NetworkStateTest::resetUsingUserCommandOriginTest() {
+ NetworkState state(NetworkState::DHCPv4);
+
+ // Test User COMMAND + HA COMMAND + DB CONNECTION origins
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.reset(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test User COMMAND origin only
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.reset(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that reset works, so that internal state is reset after
+// all managers are recreated.
+// 1. Disable using 'user command' origin 1 time (expect disabled state).
+// 2. Disable using 'HA command' origin 3 times (expect disabled state).
+// 3. Disable using 'DB connection' origin 1 time (expect disabled state).
+// 4. Reset using 'HA command' origin (expect disabled state).
+// 5. Enable using 'user command' origin 1 time (expect disabled state).
+// 6. Enable using 'DB connection' origin 1 time (expect enabled state).
+// 7. Disable using 'HA command' origin 3 times (expect disabled state).
+// 8. Reset using 'HA command' origin (expect enabled state).
+void
+NetworkStateTest::resetUsingHACommandOriginTest() {
+ NetworkState state(NetworkState::DHCPv4);
+
+ // Test HA COMMAND + User COMMAND + DB CONNECTION origins
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.reset(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test HA COMMAND origin only
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.reset(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that reset works, so that internal state is reset after
+// all managers are recreated.
+// 1. Disable using 'user command' origin 1 time (expect disabled state).
+// 2. Disable using 'HA command' origin 1 time (expect disabled state).
+// 3. Disable using 'DB connection' origin 3 time (expect disabled state).
+// 4. Reset using 'DB connection' origin (expect disabled state).
+// 5. Enable using 'user command' origin 1 time (expect disabled state).
+// 6. Enable using 'DB connection' origin 1 time (expect enabled state).
+// 7. Disable using 'DB connection' origin 3 times (expect disabled state).
+// 8. Reset using 'DB connection' origin (expect enabled state).
+void
+NetworkStateTest::resetUsingDBConnectionOriginTest() {
+ NetworkState state(NetworkState::DHCPv4);
+
+ // Test DB CONNECTION + User COMMAND + HA COMMAND origins
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.reset(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+
+ // Test DB CONNECTION origin only
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.reset(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that enableAll() enables the service. This test will be
+// extended in the future to verify that it also enables disabled scopes.
+void
+NetworkStateTest::enableAllTest() {
+ NetworkState state(NetworkState::DHCPv4);
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableAll(NetworkState::Origin::USER_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableAll(NetworkState::Origin::HA_COMMAND);
+ EXPECT_TRUE(state.isServiceEnabled());
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_FALSE(state.isServiceEnabled());
+ state.enableAll(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to setup delayed execution of enableAll
+// function.
+void
+NetworkStateTest::delayedEnableAllTest() {
+ NetworkState state(NetworkState::DHCPv4);
+ // Disable the service and then schedule enabling it in 1 second.
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ state.delayedEnableAll(1, NetworkState::Origin::USER_COMMAND);
+ // Initially the service should be still disabled.
+ EXPECT_FALSE(state.isServiceEnabled());
+ // After running IO service for 2 seconds, the service should be enabled.
+ runIOService(2000);
+ EXPECT_TRUE(state.isServiceEnabled());
+ // Disable the service and then schedule enabling it in 1 second.
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ state.delayedEnableAll(1, NetworkState::Origin::HA_COMMAND);
+ // Initially the service should be still disabled.
+ EXPECT_FALSE(state.isServiceEnabled());
+ // After running IO service for 2 seconds, the service should be enabled.
+ runIOService(2000);
+ EXPECT_TRUE(state.isServiceEnabled());
+ // Disable the service and then schedule enabling it in 1 second.
+ state.disableService(NetworkState::Origin::DB_CONNECTION);
+ EXPECT_THROW(state.delayedEnableAll(1, NetworkState::Origin::DB_CONNECTION), BadValue);
+}
+
+// This test verifies that explicitly enabling the service cancels the timer
+// scheduled for automatically enabling it.
+void
+NetworkStateTest::earlyEnableAllTest() {
+ NetworkState state(NetworkState::DHCPv4);
+ // Disable the service.
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ EXPECT_FALSE(state.isServiceEnabled());
+ // Schedule enabling the service in 2 seconds.
+ state.delayedEnableAll(2, NetworkState::Origin::USER_COMMAND);
+ // Explicitly enable the service.
+ state.enableAll(NetworkState::Origin::USER_COMMAND);
+ // The timer should be now canceled and the service should be enabled.
+ EXPECT_FALSE(state.isDelayedEnableAll());
+ EXPECT_TRUE(state.isServiceEnabled());
+}
+
+// This test verifies that it is possible to call delayedEnableAll multiple times
+// and that it results in only one timer being scheduled.
+void
+NetworkStateTest::multipleDelayedEnableAllTest() {
+ NetworkState state(NetworkState::DHCPv4);
+ // Disable the service and then schedule enabling it in 5 second.
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ // Schedule the first timer for 5 seconds.
+ state.delayedEnableAll(5, NetworkState::Origin::USER_COMMAND);
+ // When calling it the second time the old timer should be destroyed and
+ // the timeout should be set to 2 seconds.
+ state.delayedEnableAll(2, NetworkState::Origin::USER_COMMAND);
+ // Initially the service should be still disabled.
+ EXPECT_FALSE(state.isServiceEnabled());
+ // After running IO service for 3 seconds, the service should be enabled.
+ runIOService(3000);
+ EXPECT_TRUE(state.isServiceEnabled());
+ // The timer should not be present, even though the first timer was created
+ // with 5 seconds interval.
+ EXPECT_FALSE(state.isDelayedEnableAll());
+}
+
+// This test verifies that it is possible to call delayedEnableAll multiple times
+// from different origins and that it results in each timer being scheduled.
+void
+NetworkStateTest::multipleDifferentOriginsDelayedEnableAllTest() {
+ NetworkState state(NetworkState::DHCPv4);
+ // Disable the service and then schedule enabling it in 5 second.
+ state.disableService(NetworkState::Origin::HA_COMMAND);
+ // Disable the service and then schedule enabling it in 2 second.
+ state.disableService(NetworkState::Origin::USER_COMMAND);
+ // Schedule the first timer for 5 seconds.
+ state.delayedEnableAll(5, NetworkState::Origin::HA_COMMAND);
+ // When calling it the second time the old timer should not be destroyed and
+ // the new timeout should be set to 2 seconds.
+ state.delayedEnableAll(2, NetworkState::Origin::USER_COMMAND);
+ // Initially the service should be still disabled.
+ EXPECT_FALSE(state.isServiceEnabled());
+ // After running IO service for 3 seconds, the service should not be enabled.
+ runIOService(3000);
+ EXPECT_FALSE(state.isServiceEnabled());
+ // The timer should be present, because the first timer was created with 5
+ // seconds interval.
+ EXPECT_TRUE(state.isDelayedEnableAll());
+ // After running IO service for 3 seconds, the service should be enabled.
+ runIOService(3000);
+ EXPECT_TRUE(state.isServiceEnabled());
+ // The timer should not be present, because the first timer was created with
+ // 5 seconds interval.
+ EXPECT_FALSE(state.isDelayedEnableAll());
+}
+
+// Test invocations.
+
+TEST_F(NetworkStateTest, defaultTest) {
+ defaultTest();
+}
+
+TEST_F(NetworkStateTest, defaultTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ defaultTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingUserCommandOriginTest) {
+ disableEnableService4UsingUserCommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingUserCommandOriginTestMultilThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService4UsingUserCommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingHACommandOriginTest) {
+ disableEnableService4UsingHACommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingHACommandOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService4UsingHACommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingDBConnectionOriginTest) {
+ disableEnableService4UsingDBConnectionOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingDBConnectionOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService4UsingDBConnectionOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingMultipleOriginsTest) {
+ disableEnableService4UsingMultipleOriginsTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService4UsingMultipleOriginsTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService4UsingMultipleOriginsTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingUserCommandOriginTest) {
+ disableEnableService6UsingUserCommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingUserCommandOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService6UsingUserCommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingHACommandOriginTest) {
+ disableEnableService6UsingHACommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingHACommandOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService6UsingHACommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingDBConnectionOriginTest) {
+ disableEnableService6UsingDBConnectionOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingDBConnectionOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService6UsingDBConnectionOriginTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingMultipleOriginsTest) {
+ disableEnableService6UsingMultipleOriginsTest();
+}
+
+TEST_F(NetworkStateTest, disableEnableService6UsingMultipleOriginsTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ disableEnableService6UsingMultipleOriginsTest();
+}
+
+TEST_F(NetworkStateTest, resetUsingUserCommandOriginTest) {
+ resetUsingUserCommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, resetUsingUserCommandOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ resetUsingUserCommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, resetUsingDBConnectionOriginTest) {
+ resetUsingDBConnectionOriginTest();
+}
+
+TEST_F(NetworkStateTest, resetUsingDBConnectionOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ resetUsingDBConnectionOriginTest();
+}
+
+TEST_F(NetworkStateTest, resetUsingHACommandOriginTest) {
+ resetUsingHACommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, resetUsingHACommandOriginTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ resetUsingHACommandOriginTest();
+}
+
+TEST_F(NetworkStateTest, enableAllTest) {
+ enableAllTest();
+}
+
+TEST_F(NetworkStateTest, enableAllTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ enableAllTest();
+}
+
+TEST_F(NetworkStateTest, delayedEnableAllTest) {
+ delayedEnableAllTest();
+}
+
+TEST_F(NetworkStateTest, delayedEnableAllTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ delayedEnableAllTest();
+}
+
+TEST_F(NetworkStateTest, earlyEnableAllTest) {
+ earlyEnableAllTest();
+}
+
+TEST_F(NetworkStateTest, earlyEnableAllTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ earlyEnableAllTest();
+}
+
+TEST_F(NetworkStateTest, multipleDelayedEnableAllTest) {
+ multipleDelayedEnableAllTest();
+}
+
+TEST_F(NetworkStateTest, multipleDelayedEnableAllTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ multipleDelayedEnableAllTest();
+}
+
+TEST_F(NetworkStateTest, multipleDifferentOriginsDelayedEnableAllTest) {
+ multipleDifferentOriginsDelayedEnableAllTest();
+}
+
+TEST_F(NetworkStateTest, multipleDifferentOriginsDelayedEnableAllTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ multipleDifferentOriginsDelayedEnableAllTest();
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/network_unittest.cc b/src/lib/dhcpsrv/tests/network_unittest.cc
new file mode 100644
index 0000000..9185d03
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/network_unittest.cc
@@ -0,0 +1,837 @@
+// Copyright (C) 2019-2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/parsers/base_network_parser.h>
+#include <testutils/gtest_utils.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::test;
+
+namespace {
+
+class TestNetwork;
+
+/// @brief Shared pointer to the derivation of the @c Network class
+/// used in tests.
+typedef boost::shared_ptr<TestNetwork> TestNetworkPtr;
+
+/// @brief Derivation of the @c Network class allowing to set
+/// the parent @c Network object.
+class TestNetwork : public virtual Network {
+public:
+
+ /// @brief Associates parent network with this network.
+ ///
+ /// @param parent Pointer to the instance of the parent network.
+ void setParent(TestNetworkPtr parent) {
+ parent_network_ = boost::dynamic_pointer_cast<Network>(parent);
+ }
+};
+
+/// @brief Derivation of the @c Network4 class allowing to set
+/// the parent @c Network object.
+class TestNetwork4 : public TestNetwork, public Network4 { };
+
+/// @brief Derivation of the @c Network6 class allowing to set
+/// the parent @c Network object.
+class TestNetwork6 : public TestNetwork, public Network6 { };
+
+/// @brief Test fixture class for testing @c Network class and
+/// its derivations.
+class NetworkTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ NetworkTest() : globals_(new CfgGlobals()) {
+ }
+
+ /// @brief Returns pointer to the function which returns configured
+ /// global parameters.
+ FetchNetworkGlobalsFn getFetchGlobalsFn() {
+ return (std::bind(&NetworkTest::fetchGlobalsFn, this));
+ }
+
+ /// @brief Returns configured global parameters.
+ ConstCfgGlobalsPtr fetchGlobalsFn() {
+ return (globals_);
+ }
+
+ /// @brief Test that inheritance mechanism is used for the particular
+ /// network parameter.
+ ///
+ /// @tparam BaseType1 Type of the network object to be tested, e.g.
+ /// @c TestNetwork, @c TestNetwork4 etc.
+ /// @tparam BaseType2 Type of the object to which the tested parameter
+ /// belongs, e.g. @c Network, @c Network4 etc.
+ /// @tparam ParameterType1 Type returned by the accessor of the parameter
+ /// under test, e.g. @c Optional<std::string>.
+ /// @tparam ParameterType2 Type of the value accepted by the modifier
+ /// method which sets the value under test. It may be the same as
+ /// @c ParameterType1 but not necessarily. For example, the @c ParameterType1
+ /// may be @c Optional<std::string> while the @c ParameterType2 is
+ /// @c std::string.
+ ///
+ /// @param GetMethodPointer Pointer to the method of the class under test
+ /// which returns the particular parameter.
+ /// @param SetMethodPointer Pointer to the method of the class under test
+ /// used to set the particular parameter.
+ /// @param network_value Value of the parameter to be assigned to the
+ /// parent network.
+ /// @param global_value Global value of the parameter to be assigned.
+ /// @param test_global_value Boolean value indicating if the inheritance
+ /// of the global value should be tested.
+ template<typename BaseType1, typename BaseType2, typename ParameterType1,
+ typename ParameterType2>
+ void testNetworkInheritance(ParameterType1(BaseType2::*GetMethodPointer)
+ (const Network::Inheritance&) const,
+ void(BaseType2::*SetMethodPointer)(const ParameterType2&),
+ typename ParameterType1::ValueType network_value,
+ typename ParameterType1::ValueType global_value,
+ const bool test_global_value = true) {
+
+ // Create child network object. The value retrieved from that
+ // object should be unspecified until we set the value for the
+ // parent network or a global value.
+ boost::shared_ptr<BaseType1> net_child(new BaseType1());
+ EXPECT_TRUE(((*net_child).*GetMethodPointer)(Network::Inheritance::ALL).unspecified());
+
+ // Create parent network and set the value.
+ boost::shared_ptr<BaseType1> net_parent(new BaseType1());
+ ((*net_parent).*SetMethodPointer)(network_value);
+ EXPECT_EQ(network_value,
+ ((*net_parent).*GetMethodPointer)(Network::Inheritance::ALL).get());
+
+ // Assign callbacks that fetch global values to the networks.
+ net_child->setFetchGlobalsFn(getFetchGlobalsFn());
+ net_parent->setFetchGlobalsFn(getFetchGlobalsFn());
+
+ // Not all parameters have the corresponding global values.
+ if (test_global_value) {
+ // If there is a global value it should now be returned.
+ EXPECT_FALSE(((*net_child).*GetMethodPointer)(Network::Inheritance::ALL).unspecified());
+ EXPECT_EQ(global_value,
+ ((*net_child).*GetMethodPointer)(Network::Inheritance::ALL).get());
+
+ EXPECT_FALSE(((*net_child).*GetMethodPointer)(Network::Inheritance::GLOBAL).unspecified());
+ EXPECT_EQ(global_value,
+ ((*net_child).*GetMethodPointer)(Network::Inheritance::GLOBAL).get());
+
+ EXPECT_TRUE(((*net_child).*GetMethodPointer)(Network::Inheritance::NONE).unspecified());
+ EXPECT_TRUE(((*net_child).*GetMethodPointer)(Network::Inheritance::PARENT_NETWORK).unspecified());
+ }
+
+ // Associated the network with its parent.
+ ASSERT_NO_THROW(net_child->setParent(net_parent));
+
+ // This time the parent specific value should be returned.
+ EXPECT_FALSE(((*net_child).*GetMethodPointer)(Network::Inheritance::ALL).unspecified());
+ EXPECT_EQ(network_value,
+ ((*net_child).*GetMethodPointer)(Network::Inheritance::ALL).get());
+ }
+
+ /// @brief Holds the collection of configured globals.
+ CfgGlobalsPtr globals_;
+};
+
+// This test verifies that the inheritance is supported for certain
+// network parameters.
+TEST_F(NetworkTest, inheritanceSupport4) {
+ // Set global values for each parameter.
+ // One day move to indexes...
+ globals_->set("valid-lifetime", Element::create(80));
+ globals_->set("renew-timer", Element::create(80));
+ globals_->set("rebind-timer", Element::create(80));
+ globals_->set("reservations-global", Element::create(false));
+ globals_->set("reservations-in-subnet", Element::create(false));
+ globals_->set("reservations-out-of-pool", Element::create(false));
+ globals_->set("calculate-tee-times", Element::create(false));
+ globals_->set("t1-percent", Element::create(0.75));
+ globals_->set("t2-percent", Element::create(0.6));
+ globals_->set("match-client-id", Element::create(true));
+ globals_->set("authoritative", Element::create(false));
+ globals_->set("next-server", Element::create("192.0.2.3"));
+ globals_->set("server-hostname", Element::create("g"));
+ globals_->set("boot-file-name", Element::create("g"));
+ globals_->set("ddns-send-updates", Element::create(true));
+ globals_->set("ddns-override-no-update", Element::create(true));
+ globals_->set("ddns-override-client-update", Element::create(true));
+ globals_->set("ddns-replace-client-name", Element::create("always"));
+ globals_->set("ddns-generated-prefix", Element::create("gp"));
+ globals_->set("ddns-qualifying-suffix", Element::create("gs"));
+ globals_->set("hostname-char-set", Element::create("gc"));
+ globals_->set("hostname-char-replacement", Element::create("gr"));
+ globals_->set("store-extended-info", Element::create(true));
+ globals_->set("cache-threshold", Element::create(.25));
+ globals_->set("cache-max-age", Element::create(20));
+ globals_->set("ddns-update-on-renew", Element::create(true));
+ globals_->set("ddns-use-conflict-resolution", Element::create(true));
+ globals_->set("allocator", Element::create("random"));
+ globals_->set("offer-lifetime", Element::create(45));
+ globals_->set("ddns-ttl-percent", Element::create(0.75));
+
+ // For each parameter for which inheritance is supported run
+ // the test that checks if the values are inherited properly.
+
+ {
+ SCOPED_TRACE("client_class");
+ testNetworkInheritance<TestNetwork>(&Network::getClientClass,
+ &Network::allowClientClass,
+ "n", "g", false);
+ }
+ {
+ SCOPED_TRACE("valid-lifetime");
+ testNetworkInheritance<TestNetwork>(&Network::getValid, &Network::setValid,
+ 60, 80);
+ }
+ {
+ SCOPED_TRACE("renew-timer");
+ testNetworkInheritance<TestNetwork>(&Network::getT1, &Network::setT1,
+ 60, 80);
+ }
+ {
+ SCOPED_TRACE("rebind-timer");
+ testNetworkInheritance<TestNetwork>(&Network::getT2, &Network::setT2,
+ 60, 80);
+ }
+ {
+ SCOPED_TRACE("reservation-global");
+ testNetworkInheritance<TestNetwork>(&Network::getReservationsGlobal,
+ &Network::setReservationsGlobal,
+ true, false);
+ }
+ {
+ SCOPED_TRACE("reservation-in-subnet");
+ testNetworkInheritance<TestNetwork>(&Network::getReservationsInSubnet,
+ &Network::setReservationsInSubnet,
+ true, false);
+ }
+ {
+ SCOPED_TRACE("reservation-out-of-pool");
+ testNetworkInheritance<TestNetwork>(&Network::getReservationsOutOfPool,
+ &Network::setReservationsOutOfPool,
+ true, false);
+ }
+ {
+ SCOPED_TRACE("calculate-tee-times");
+ testNetworkInheritance<TestNetwork>(&Network::getCalculateTeeTimes,
+ &Network::setCalculateTeeTimes,
+ true, false);
+ }
+ {
+ SCOPED_TRACE("t1-percent");
+ testNetworkInheritance<TestNetwork>(&Network::getT1Percent,
+ &Network::setT1Percent,
+ 0.5, 0.75);
+ }
+ {
+ SCOPED_TRACE("t2-percent");
+ testNetworkInheritance<TestNetwork>(&Network::getT2Percent,
+ &Network::setT2Percent,
+ 0.3, 0.6);
+ }
+ {
+ SCOPED_TRACE("match-client-id");
+ testNetworkInheritance<TestNetwork4>(&Network4::getMatchClientId,
+ &Network4::setMatchClientId,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("authoritative");
+ testNetworkInheritance<TestNetwork4>(&Network4::getAuthoritative,
+ &Network4::setAuthoritative,
+ true, false);
+ }
+ {
+ SCOPED_TRACE("next-server");
+ testNetworkInheritance<TestNetwork4>(&Network4::getSiaddr,
+ &Network4::setSiaddr,
+ IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.3"));
+ }
+ {
+ SCOPED_TRACE("server-hostname");
+ testNetworkInheritance<TestNetwork4>(&Network4::getSname,
+ &Network4::setSname,
+ "n", "g");
+ }
+ {
+ SCOPED_TRACE("boot-file-name");
+ testNetworkInheritance<TestNetwork4>(&Network4::getFilename,
+ &Network4::setFilename,
+ "n", "g");
+ }
+ {
+ SCOPED_TRACE("ddns-send-updates");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsSendUpdates,
+ &Network4::setDdnsSendUpdates,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("ddns-override-no-update");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsOverrideNoUpdate,
+ &Network4::setDdnsOverrideNoUpdate,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("ddns-override-client-update");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsOverrideClientUpdate,
+ &Network4::setDdnsOverrideClientUpdate,
+ false, true);
+ }
+
+ {
+ SCOPED_TRACE("ddns-replace-client-name");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsReplaceClientNameMode,
+ &Network4::setDdnsReplaceClientNameMode,
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ D2ClientConfig::RCM_ALWAYS);
+ }
+ {
+ SCOPED_TRACE("ddns-generated-prefix");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsGeneratedPrefix,
+ &Network4::setDdnsGeneratedPrefix,
+ "np", "gp");
+ }
+ {
+ SCOPED_TRACE("ddns-qualifying-suffix");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsQualifyingSuffix,
+ &Network4::setDdnsQualifyingSuffix,
+ "ns", "gs");
+ }
+ {
+ SCOPED_TRACE("hostname-char-set");
+ testNetworkInheritance<TestNetwork4>(&Network4::getHostnameCharSet,
+ &Network4::setHostnameCharSet,
+ "nc", "gc");
+ }
+ {
+ SCOPED_TRACE("hostname-char-replacement");
+ testNetworkInheritance<TestNetwork4>(&Network4::getHostnameCharReplacement,
+ &Network4::setHostnameCharReplacement,
+ "nr", "gr");
+ }
+ {
+ SCOPED_TRACE("store-extended-info");
+ testNetworkInheritance<TestNetwork4>(&Network4::getStoreExtendedInfo,
+ &Network4::setStoreExtendedInfo,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("cache-threshold");
+ testNetworkInheritance<TestNetwork4>(&Network::getCacheThreshold,
+ &Network::setCacheThreshold,
+ .1, .25);
+ }
+ {
+ SCOPED_TRACE("cache-max-age");
+ testNetworkInheritance<TestNetwork4>(&Network::getCacheMaxAge,
+ &Network::setCacheMaxAge,
+ 10, 20);
+ }
+ {
+ SCOPED_TRACE("ddns-update-on-renew");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsUpdateOnRenew,
+ &Network4::setDdnsUpdateOnRenew,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("ddns-use-conflict-resolution");
+ testNetworkInheritance<TestNetwork4>(&Network4::getDdnsUseConflictResolution,
+ &Network4::setDdnsUseConflictResolution,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("allocator");
+ testNetworkInheritance<TestNetwork4>(&Network4::getAllocatorType,
+ &Network4::setAllocatorType,
+ "iterative", "random");
+ }
+ {
+ SCOPED_TRACE("default-allocator-type");
+ testNetworkInheritance<TestNetwork4>(&Network::getDefaultAllocatorType,
+ &Network::setDefaultAllocatorType,
+ "random", "flq", false);
+ }
+ {
+ SCOPED_TRACE("offer-lifetime");
+ testNetworkInheritance<TestNetwork4>(&Network4::getOfferLft,
+ &Network4::setOfferLft,
+ 10, 45);
+ }
+ {
+ SCOPED_TRACE("ddns-ttl-percent");
+ testNetworkInheritance<TestNetwork4>(&Network::getDdnsTtlPercent,
+ &Network::setDdnsTtlPercent,
+ .33, .75);
+ }
+}
+
+// This test verifies that the inheritance is supported for DHCPv6
+// specific network parameters.
+TEST_F(NetworkTest, inheritanceSupport6) {
+ // Set global values for each parameter.
+ globals_->set("preferred-lifetime", Element::create(80));
+ // Note that currently rapid commit is not a global parameter.
+ globals_->set("ddns-send-updates", Element::create(true));
+ globals_->set("ddns-override-no-update", Element::create(true));
+ globals_->set("ddns-override-client-update", Element::create(true));
+ globals_->set("ddns-replace-client-name", Element::create("always"));
+ globals_->set("ddns-generated-prefix", Element::create("gp"));
+ globals_->set("ddns-qualifying-suffix", Element::create("gs"));
+ globals_->set("hostname-char-set", Element::create("gc"));
+ globals_->set("hostname-char-replacement", Element::create("gr"));
+ globals_->set("store-extended-info", Element::create(true));
+ globals_->set("ddns-update-on-renew", Element::create(true));
+ globals_->set("ddns-use-conflict-resolution", Element::create(true));
+ globals_->set("allocator", Element::create("random"));
+ globals_->set("pd-allocator", Element::create("random"));
+ globals_->set("ddns-ttl-percent", Element::create(0.55));
+
+ // For each parameter for which inheritance is supported run
+ // the test that checks if the values are inherited properly.
+
+ {
+ SCOPED_TRACE("preferred-lifetime");
+ testNetworkInheritance<TestNetwork6>(&Network6::getPreferred,
+ &Network6::setPreferred,
+ 60, 80);
+ }
+ {
+ SCOPED_TRACE("ddns-send-updates");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsSendUpdates,
+ &Network6::setDdnsSendUpdates,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("ddns-override-no-update");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsOverrideNoUpdate,
+ &Network6::setDdnsOverrideNoUpdate,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("ddns-override-client-update");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsOverrideClientUpdate,
+ &Network6::setDdnsOverrideClientUpdate,
+ false, true);
+ }
+
+ {
+ SCOPED_TRACE("ddns-replace-client-name");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsReplaceClientNameMode,
+ &Network6::setDdnsReplaceClientNameMode,
+ D2ClientConfig::RCM_WHEN_PRESENT,
+ D2ClientConfig::RCM_ALWAYS);
+ }
+ {
+ SCOPED_TRACE("ddns-generated-prefix");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsGeneratedPrefix,
+ &Network6::setDdnsGeneratedPrefix,
+ "np", "gp");
+ }
+ {
+ SCOPED_TRACE("ddns-qualifying-suffix");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsQualifyingSuffix,
+ &Network6::setDdnsQualifyingSuffix,
+ "ns", "gs");
+ }
+ {
+ SCOPED_TRACE("hostname-char-set");
+ testNetworkInheritance<TestNetwork6>(&Network6::getHostnameCharSet,
+ &Network6::setHostnameCharSet,
+ "nc", "gc");
+ }
+ {
+ SCOPED_TRACE("hostname-char-replacement");
+ testNetworkInheritance<TestNetwork6>(&Network6::getHostnameCharReplacement,
+ &Network6::setHostnameCharReplacement,
+ "nr", "gr");
+ }
+ {
+ SCOPED_TRACE("store-extended-info");
+ testNetworkInheritance<TestNetwork6>(&Network6::getStoreExtendedInfo,
+ &Network6::setStoreExtendedInfo,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("ddns-update-on-renew");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsUpdateOnRenew,
+ &Network6::setDdnsUpdateOnRenew,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("ddns-use-conflict-resolution");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDdnsUseConflictResolution,
+ &Network6::setDdnsUseConflictResolution,
+ false, true);
+ }
+ {
+ SCOPED_TRACE("allocator");
+ testNetworkInheritance<TestNetwork6>(&Network6::getAllocatorType,
+ &Network6::setAllocatorType,
+ "iterative", "random");
+ }
+ {
+ SCOPED_TRACE("pd-allocator");
+ testNetworkInheritance<TestNetwork6>(&Network6::getPdAllocatorType,
+ &Network6::setPdAllocatorType,
+ "iterative", "random");
+ }
+ {
+ SCOPED_TRACE("default-allocator-type");
+ testNetworkInheritance<TestNetwork6>(&Network::getDefaultAllocatorType,
+ &Network::setDefaultAllocatorType,
+ "random", "iterative", false);
+ }
+ {
+ SCOPED_TRACE("default-pd-allocator-type");
+ testNetworkInheritance<TestNetwork6>(&Network6::getDefaultPdAllocatorType,
+ &Network6::setDefaultPdAllocatorType,
+ "random", "iterative", false);
+ }
+ {
+ SCOPED_TRACE("ddns-ttl-percent");
+ testNetworkInheritance<TestNetwork6>(&Network::getDdnsTtlPercent,
+ &Network::setDdnsTtlPercent,
+ .22, .55);
+ }
+
+ // Interface-id requires special type of test.
+ boost::shared_ptr<TestNetwork6> net_child(new TestNetwork6());
+ EXPECT_FALSE(net_child->getInterfaceId());
+ EXPECT_FALSE(net_child->getInterfaceId(Network::Inheritance::NONE));
+ EXPECT_FALSE(net_child->getInterfaceId(Network::Inheritance::PARENT_NETWORK));
+ EXPECT_FALSE(net_child->getInterfaceId(Network::Inheritance::GLOBAL));
+
+ OptionPtr interface_id(new Option(Option::V6, D6O_INTERFACE_ID,
+ OptionBuffer(10, 0xFF)));
+
+ boost::shared_ptr<TestNetwork6> net_parent(new TestNetwork6());
+ net_parent->setInterfaceId(interface_id);
+
+ ASSERT_NO_THROW(net_child->setParent(net_parent));
+
+ // The interface-id belongs to the parent.
+ EXPECT_TRUE(net_child->getInterfaceId());
+ EXPECT_FALSE(net_child->getInterfaceId(Network::Inheritance::NONE));
+ EXPECT_TRUE(net_child->getInterfaceId(Network::Inheritance::PARENT_NETWORK));
+ EXPECT_FALSE(net_child->getInterfaceId(Network::Inheritance::GLOBAL));
+
+ // Check the values are expected.
+ EXPECT_EQ(interface_id, net_child->getInterfaceId());
+ EXPECT_EQ(interface_id, net_child->getInterfaceId(Network::Inheritance::PARENT_NETWORK));
+
+ // Assign different interface id to a child.
+ interface_id.reset(new Option(Option::V6, D6O_INTERFACE_ID,
+ OptionBuffer(10, 0xFE)));
+ net_child->setInterfaceId(interface_id);
+
+ // This time, the child specific value can be fetched.
+ EXPECT_TRUE(net_child->getInterfaceId());
+ EXPECT_TRUE(net_child->getInterfaceId(Network::Inheritance::NONE));
+ EXPECT_TRUE(net_child->getInterfaceId(Network::Inheritance::PARENT_NETWORK));
+ EXPECT_FALSE(net_child->getInterfaceId(Network::Inheritance::GLOBAL));
+
+ EXPECT_EQ(interface_id, net_child->getInterfaceId());
+ EXPECT_EQ(interface_id, net_child->getInterfaceId(Network::Inheritance::NONE));
+}
+
+// Test that child network returns unspecified value if neither
+// parent no global value exists.
+TEST_F(NetworkTest, getPropertyNoParentNoChild) {
+ NetworkPtr net_child(new Network());
+ EXPECT_TRUE(net_child->getValid().unspecified());
+}
+
+// Test that child network returns specified value.
+TEST_F(NetworkTest, getPropertyNoParentChild) {
+ NetworkPtr net_child(new Network());
+ net_child->setValid(12345);
+
+ EXPECT_FALSE(net_child->getValid().unspecified());
+ EXPECT_FALSE(net_child->getValid(Network::Inheritance::NONE).unspecified());
+ EXPECT_TRUE(net_child->getValid(Network::Inheritance::PARENT_NETWORK).unspecified());
+ EXPECT_TRUE(net_child->getValid(Network::Inheritance::GLOBAL).unspecified());
+
+ EXPECT_EQ(12345, net_child->getValid(Network::Inheritance::NONE).get());
+ EXPECT_EQ(12345, net_child->getValid().get());
+}
+
+// Test that parent specific value is returned when the value
+// is not specified for the child network.
+TEST_F(NetworkTest, getPropertyParentNoChild) {
+ TestNetworkPtr net_child(new TestNetwork());
+ EXPECT_TRUE(net_child->getValid().unspecified());
+
+ TestNetworkPtr net_parent(new TestNetwork());
+ net_parent->setValid(23456);
+ EXPECT_EQ(23456, net_parent->getValid().get());
+
+ ASSERT_NO_THROW(net_child->setParent(net_parent));
+
+ EXPECT_FALSE(net_child->getValid().unspecified());
+ EXPECT_TRUE(net_child->getValid(Network::Inheritance::NONE).unspecified());
+ EXPECT_FALSE(net_child->getValid(Network::Inheritance::PARENT_NETWORK).unspecified());
+ EXPECT_TRUE(net_child->getValid(Network::Inheritance::GLOBAL).unspecified());
+
+ EXPECT_EQ(23456, net_child->getValid().get());
+}
+
+// Test that value specified for the child network takes
+// precedence over the value specified for the parent network.
+TEST_F(NetworkTest, getPropertyParentChild) {
+ TestNetworkPtr net_child(new TestNetwork());
+ net_child->setValid(12345);
+ EXPECT_EQ(12345, net_child->getValid().get());
+
+ TestNetworkPtr net_parent(new TestNetwork());
+ net_parent->setValid(23456);
+ EXPECT_EQ(23456, net_parent->getValid().get());
+
+ ASSERT_NO_THROW(net_child->setParent(net_parent));
+
+ EXPECT_FALSE(net_child->getValid().unspecified());
+ EXPECT_FALSE(net_child->getValid(Network::Inheritance::NONE).unspecified());
+ EXPECT_FALSE(net_child->getValid(Network::Inheritance::PARENT_NETWORK).unspecified());
+ EXPECT_TRUE(net_child->getValid(Network::Inheritance::GLOBAL).unspecified());
+
+ EXPECT_EQ(12345, net_child->getValid().get());
+}
+
+// Test that global value is inherited if there is no network
+// specific value.
+TEST_F(NetworkTest, getPropertyGlobalNoParentNoChild) {
+ TestNetworkPtr net_child(new TestNetwork());
+
+ globals_->set("valid-lifetime", Element::create(34567));
+
+ net_child->setFetchGlobalsFn(getFetchGlobalsFn());
+
+ EXPECT_FALSE(net_child->getValid().unspecified());
+ EXPECT_TRUE(net_child->getValid(Network::Inheritance::NONE).unspecified());
+ EXPECT_TRUE(net_child->getValid(Network::Inheritance::PARENT_NETWORK).unspecified());
+ EXPECT_FALSE(net_child->getValid(Network::Inheritance::GLOBAL).unspecified());
+
+ EXPECT_EQ(34567, net_child->getValid().get());
+}
+
+// Test that getSiaddr() never fails.
+TEST_F(NetworkTest, getSiaddrNeverFail) {
+ TestNetworkPtr net_child(new TestNetwork4());
+
+ // Set the next-server textual address to the empty string.
+ // Note that IOAddress("") throws IOError.
+ globals_->set("next-server", Element::create(""));
+
+ net_child->setFetchGlobalsFn(getFetchGlobalsFn());
+
+ // Get an IPv4 view of the test network.
+ auto net4_child = boost::dynamic_pointer_cast<Network4>(net_child);
+ EXPECT_NO_THROW(net4_child->getSiaddr());
+}
+
+/// @brief Test fixture class for testing @c moveReservationMode.
+class NetworkReservationTest : public ::testing::Test {
+public:
+
+ /// @brief Move test error case.
+ ///
+ /// Error cases of @ref BaseNetworkParser::moveReservationMode.
+ ///
+ /// @param config String with the config to test.
+ /// @param expected String with the expected error message.
+ void TestError(const std::string& config, const std::string& expected) {
+ ElementPtr cfg;
+ ASSERT_NO_THROW(cfg = Element::fromJSON(config))
+ << "bad config, test broken";
+
+ ElementPtr copy = isc::data::copy(cfg);
+
+ EXPECT_THROW_MSG(BaseNetworkParser::moveReservationMode(cfg),
+ DhcpConfigError, expected);
+
+ ASSERT_TRUE(copy->equals(*cfg));
+ }
+
+ /// @brief Move test case.
+ ///
+ /// Test cases of @ref BaseNetworkParser::moveReservationMode.
+ ///
+ /// @param config String with the config to test.
+ /// @param expected String with the config after move.
+ void TestMove(const std::string& config, const std::string& expected) {
+ ElementPtr cfg;
+ ASSERT_NO_THROW(cfg = Element::fromJSON(config))
+ << "bad config, test broken";
+
+ EXPECT_NO_THROW(BaseNetworkParser::moveReservationMode(cfg));
+
+ EXPECT_EQ(expected, cfg->str());
+ }
+};
+
+/// @brief Test @ref BaseNetworkParser::moveReservationMode error cases.
+TEST_F(NetworkReservationTest, errors) {
+ // Conflicts.
+ std::string config = "{\n"
+ "\"reservation-mode\": \"all\",\n"
+ "\"reservations-global\": true\n"
+ "}";
+ std::string expected = "invalid use of both 'reservation-mode'"
+ " and one of 'reservations-global', 'reservations-in-subnet'"
+ " or 'reservations-out-of-pool' parameters";
+ TestError(config, expected);
+
+ config = "{\n"
+ "\"reservation-mode\": \"all\",\n"
+ "\"reservations-in-subnet\": true\n"
+ "}";
+ TestError(config, expected);
+
+ config = "{\n"
+ "\"reservation-mode\": \"all\",\n"
+ "\"reservations-out-of-pool\": false\n"
+ "}";
+ TestError(config, expected);
+
+ // Unknown mode.
+ config = "{\n"
+ "\"reservation-mode\": \"foo\"\n"
+ "}";
+ expected = "invalid reservation-mode parameter: 'foo' (<string>:2:21)";
+ TestError(config, expected);
+}
+
+/// @brief Test @ref BaseNetworkParser::moveReservationMode.
+TEST_F(NetworkReservationTest, move) {
+ // No-ops.
+ std::string config = "{\n"
+ "}";
+ std::string expected = "{ "
+ " }";
+ TestMove(config, expected);
+
+ config = "{\n"
+ "\"reservations-global\": true\n"
+ "}";
+ expected = "{"
+ " \"reservations-global\": true"
+ " }";
+ TestMove(config, expected);
+
+ // Disabled.
+ config = "{\n"
+ "\"reservation-mode\": \"disabled\"\n"
+ "}";
+ expected = "{"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": false"
+ " }";
+ TestMove(config, expected);
+
+ config = "{\n"
+ "\"reservation-mode\": \"off\"\n"
+ "}";
+ TestMove(config, expected);
+
+ // Out-of-pool.
+ config = "{\n"
+ "\"reservation-mode\": \"out-of-pool\"\n"
+ "}";
+ expected = "{"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true"
+ " }";
+ TestMove(config, expected);
+
+ // Global.
+ config = "{\n"
+ "\"reservation-mode\": \"global\"\n"
+ "}";
+ expected = "{"
+ " \"reservations-global\": true,"
+ " \"reservations-in-subnet\": false"
+ " }";
+ TestMove(config, expected);
+
+ // All.
+ config = "{\n"
+ "\"reservation-mode\": \"all\"\n"
+ "}";
+ expected = "{"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " }";
+ TestMove(config, expected);
+
+ config = "{\n"
+ "\"foobar\": 1234,\n"
+ "\"reservation-mode\": \"all\"\n"
+ "}";
+ expected = "{"
+ " \"foobar\": 1234,"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false"
+ " }";
+ TestMove(config, expected);
+}
+
+// This test verifies that the inheritance is supported for triplets.
+// Note that triplets have no comparison operator.
+TEST_F(NetworkTest, inheritanceTriplet) {
+ NetworkPtr net(new Network());
+ EXPECT_TRUE(net->getValid().unspecified());
+ EXPECT_TRUE(net->getValid(Network::Inheritance::ALL).unspecified());
+ EXPECT_TRUE(net->getValid(Network::Inheritance::GLOBAL).unspecified());
+
+ // Set valid lifetime global parameter.
+ globals_->set("valid-lifetime", Element::create(200));
+ net->setFetchGlobalsFn(getFetchGlobalsFn());
+ EXPECT_FALSE(net->getValid().unspecified());
+ EXPECT_FALSE(net->getValid(Network::Inheritance::ALL).unspecified());
+ EXPECT_FALSE(net->getValid(Network::Inheritance::GLOBAL).unspecified());
+ EXPECT_EQ(200, net->getValid().get());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::ALL).get());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::GLOBAL).get());
+ EXPECT_EQ(200, net->getValid().getMin());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::ALL).getMin());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::GLOBAL).getMin());
+ EXPECT_EQ(200, net->getValid().getMax());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::ALL).getMax());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::GLOBAL).getMax());
+
+ // Set all valid lifetime global parameters.
+ globals_->set("min-valid-lifetime", Element::create(100));
+ globals_->set("max-valid-lifetime", Element::create(300));
+ EXPECT_FALSE(net->getValid().unspecified());
+ EXPECT_FALSE(net->getValid(Network::Inheritance::ALL).unspecified());
+ EXPECT_FALSE(net->getValid(Network::Inheritance::GLOBAL).unspecified());
+ EXPECT_EQ(200, net->getValid().get());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::ALL).get());
+ EXPECT_EQ(200, net->getValid(Network::Inheritance::GLOBAL).get());
+ EXPECT_EQ(100, net->getValid().getMin());
+ EXPECT_EQ(100, net->getValid(Network::Inheritance::ALL).getMin());
+ EXPECT_EQ(100, net->getValid(Network::Inheritance::GLOBAL).getMin());
+ EXPECT_EQ(300, net->getValid().getMax());
+ EXPECT_EQ(300, net->getValid(Network::Inheritance::ALL).getMax());
+ EXPECT_EQ(300, net->getValid(Network::Inheritance::GLOBAL).getMax());
+}
+
+}
diff --git a/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
new file mode 100644
index 0000000..561e059
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/pgsql_host_data_source_unittest.cc
@@ -0,0 +1,1721 @@
+// Copyright (C) 2016-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/pgsql_host_data_source.h>
+#include <dhcpsrv/testutils/generic_host_data_source_unittest.h>
+#include <dhcpsrv/testutils/host_data_source_utils.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <testutils/multi_threading_utils.h>
+#include <util/multi_threading_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::data;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+class PgSqlHostDataSourceTest : public GenericHostDataSourceTest {
+public:
+ /// @brief Clears the database and opens connection to it.
+ void initializeTest() {
+ // Ensure we have the proper schema with no transient data.
+ createPgSQLSchema();
+
+ // Connect to the database
+ try {
+ HostMgr::create();
+ HostMgr::addBackend(validPgSQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the PostgreSQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ hdsptr_ = HostMgr::instance().getHostDataSource();
+ hdsptr_->setIPReservationsUnique(true);
+
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destroys the HDS and the schema.
+ void destroyTest() {
+ try {
+ hdsptr_->rollback();
+ } catch (...) {
+ // Rollback may fail if backend is in read only mode. That's ok.
+ }
+ HostMgr::delAllBackends();
+ hdsptr_.reset();
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroyPgSQLSchema();
+ }
+
+ /// @brief Constructor
+ ///
+ /// Deletes everything from the database and opens it.
+ PgSqlHostDataSourceTest() {
+ initializeTest();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Rolls back all pending transactions. The deletion of hdsptr_ will close
+ /// the database. Then reopen it and delete everything created by the test.
+ virtual ~PgSqlHostDataSourceTest() {
+ destroyTest();
+ }
+
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-open it. Anything committed should be
+ /// visible.
+ ///
+ /// Parameter is ignored for PostgreSQL backend as the v4 and v6 hosts share
+ /// the same database.
+ void reopen(Universe) {
+ HostMgr::create();
+ HostMgr::addBackend(validPgSQLConnectionString());
+ hdsptr_ = HostMgr::instance().getHostDataSource();
+ }
+
+ /// @brief returns number of rows in a table
+ ///
+ /// Note: This method uses its own connection. It will not work if your test
+ /// uses transactions.
+ ///
+ /// @param name of the table
+ /// @return number of rows currently present in the table
+ int countRowsInTable(const std::string& table) {
+ string query = "SELECT * FROM " + table;
+
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+
+ PgSqlConnection conn(params);
+ conn.openDatabase();
+
+ PgSqlResult r(PQexec(conn, query.c_str()));
+ if (PQresultStatus(r) != PGRES_TUPLES_OK) {
+ isc_throw(DbOperationError, "Query failed: " << PQerrorMessage(conn));
+ }
+
+ int numrows = PQntuples(r);
+
+ return (numrows);
+ }
+
+ /// @brief Returns number of IPv4 options currently stored in DB.
+ virtual int countDBOptions4() {
+ return (countRowsInTable("dhcp4_options"));
+ }
+
+ /// @brief Returns number of IPv6 options currently stored in DB.
+ virtual int countDBOptions6() {
+ return (countRowsInTable("dhcp6_options"));
+ }
+
+ /// @brief Returns number of IPv6 reservations currently stored in DB.
+ virtual int countDBReservations6() {
+ return (countRowsInTable("ipv6_reservations"));
+ }
+
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the PgSqlHostDataSource can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// PgSqlHostMgr test fixture set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+TEST(PgSqlHostDataSource, OpenDatabase) {
+ // Schema needs to be created for the test to work.
+ destroyPgSQLSchema();
+ createPgSQLSchema();
+
+ // Check that host manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(validPgSQLConnectionString()));
+ HostMgr::delBackend("postgresql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the PostgreSQL tests will run correctly.\n";
+ }
+
+ // Check that host manager opens the database correctly with a longer
+ // timeout. If it fails, print the error message.
+ try {
+ string connection_string = validPgSQLConnectionString() + string(" ") +
+ string(VALID_TIMEOUT);
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(connection_string));
+ HostMgr::delBackend("postgresql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the PostgreSQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the host data source when
+ // none is set returns empty pointer.
+ EXPECT_FALSE(HostMgr::instance().getHostDataSource());
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on HostDataSourceFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT, INVALID_READONLY_DB)), DbInvalidReadOnly);
+
+ // Check for missing parameters
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Check for SSL/TLS support.
+#ifdef HAVE_PGSQL_SSL
+ EXPECT_NO_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ 0, 0, 0, 0, VALID_CA)));
+#else
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ 0, 0, 0, 0, VALID_CA)), DbOpenError);
+#endif
+
+ // Tidy up after the test
+ destroyPgSQLSchema();
+}
+
+/// @brief Check that database can be opened with Multi-Threading
+///
+/// This test checks if the PgSqlHostDataSource can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// PgSqlHostMgr test fixture set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+TEST(PgSqlHostDataSource, OpenDatabaseMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+
+ // Schema needs to be created for the test to work.
+ destroyPgSQLSchema();
+ createPgSQLSchema();
+
+ // Check that host manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(validPgSQLConnectionString()));
+ HostMgr::delBackend("postgresql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the PostgreSQL tests will run correctly.\n";
+ }
+
+ // Check that host manager opens the database correctly with a longer
+ // timeout. If it fails, print the error message.
+ try {
+ string connection_string = validPgSQLConnectionString() + string(" ") +
+ string(VALID_TIMEOUT);
+ HostMgr::create();
+ EXPECT_NO_THROW(HostMgr::addBackend(connection_string));
+ HostMgr::delBackend("postgresql");
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the PostgreSQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the host data source when
+ // none is set returns empty pointer.
+ EXPECT_FALSE(HostMgr::instance().getHostDataSource());
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on HostDataSourceFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
+ DbInvalidTimeout);
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT, INVALID_READONLY_DB)), DbInvalidReadOnly);
+
+ // Check for missing parameters
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Tidy up after the test
+ destroyPgSQLSchema();
+}
+
+/// @brief Flag used to detect calls to db_lost_callback function
+bool callback_called = false;
+
+/// @brief Callback function used in open database testing
+bool db_lost_callback(ReconnectCtlPtr /* db_conn_retry */) {
+ return (callback_called = true);
+}
+
+/// @brief Make sure open failures do NOT invoke db lost callback
+/// The db lost callback should only be invoked after successfully
+/// opening the DB and then subsequently losing it. Failing to
+/// open should be handled directly by the application layer.
+/// There is simply no good way to break the connection in a
+/// unit test environment. So testing the callback invocation
+/// in a unit test is next to impossible. That has to be done
+/// as a system test.
+TEST(PgSqlHostDataSource, NoCallbackOnOpenFail) {
+ // Schema needs to be created for the test to work.
+ destroyPgSQLSchema();
+ createPgSQLSchema();
+
+ callback_called = false;
+ DatabaseConnection::db_lost_callback_ = db_lost_callback;
+ HostMgr::create();
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ EXPECT_FALSE(callback_called);
+ destroyPgSQLSchema();
+}
+
+/// @brief Make sure open failures do NOT invoke db lost callback
+/// The db lost callback should only be invoked after successfully
+/// opening the DB and then subsequently losing it. Failing to
+/// open should be handled directly by the application layer.
+/// There is simply no good way to break the connection in a
+/// unit test environment. So testing the callback invocation
+/// in a unit test is next to impossible. That has to be done
+/// as a system test.
+TEST(PgSqlHostDataSource, NoCallbackOnOpenFailMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+
+ // Schema needs to be created for the test to work.
+ destroyPgSQLSchema();
+ createPgSQLSchema();
+
+ callback_called = false;
+ DatabaseConnection::db_lost_callback_ = db_lost_callback;
+ HostMgr::create();
+ EXPECT_THROW(HostMgr::addBackend(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ EXPECT_FALSE(callback_called);
+ destroyPgSQLSchema();
+}
+
+/// @brief This test verifies that database backend can operate in Read-Only mode.
+TEST_F(PgSqlHostDataSourceTest, testReadOnlyDatabase) {
+ testReadOnlyDatabase(PGSQL_VALID_TYPE);
+}
+
+/// @brief This test verifies that database backend can operate in Read-Only mode.
+TEST_F(PgSqlHostDataSourceTest, testReadOnlyDatabaseMultiThreading) {
+ MultiThreadingTest mt(true);
+ testReadOnlyDatabase(PGSQL_VALID_TYPE);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses hw address as identifier.
+TEST_F(PgSqlHostDataSourceTest, basic4HWAddr) {
+ testBasic4(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses hw address as identifier.
+TEST_F(PgSqlHostDataSourceTest, basic4HWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasic4(Host::IDENT_HWADDR);
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have the global
+/// subnet id value
+TEST_F(PgSqlHostDataSourceTest, globalSubnetId4) {
+ testGlobalSubnetId4();
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have the global
+/// subnet id value
+TEST_F(PgSqlHostDataSourceTest, globalSubnetId4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGlobalSubnetId4();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have the global
+/// subnet id value
+TEST_F(PgSqlHostDataSourceTest, globalSubnetId6) {
+ testGlobalSubnetId6();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have the global
+/// subnet id value
+TEST_F(PgSqlHostDataSourceTest, globalSubnetId6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGlobalSubnetId6();
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have a max value
+/// for dhcp4_subnet id
+TEST_F(PgSqlHostDataSourceTest, maxSubnetId4) {
+ testMaxSubnetId4();
+}
+
+/// @brief Verifies that IPv4 host reservation with options can have a max value
+/// for dhcp4_subnet id
+TEST_F(PgSqlHostDataSourceTest, maxSubnetId4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxSubnetId4();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have a max value
+/// for dhcp6_subnet id
+TEST_F(PgSqlHostDataSourceTest, maxSubnetId6) {
+ testMaxSubnetId6();
+}
+
+/// @brief Verifies that IPv6 host reservation with options can have a max value
+/// for dhcp6_subnet id
+TEST_F(PgSqlHostDataSourceTest, maxSubnetId6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxSubnetId6();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAll4BySubnet) {
+ testGetAll4();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAll4BySubnetMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAll4();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAll6BySubnet) {
+ testGetAll6();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAll6BySubnetMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAll6();
+}
+
+/// @brief Verifies that host reservations with the same hostname can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAllbyHostname) {
+ testGetAllbyHostname();
+}
+
+/// @brief Verifies that host reservations with the same hostname can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAllbyHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAllbyHostname();
+}
+
+/// @brief Verifies that IPv4 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAllbyHostnameSubnet4) {
+ testGetAllbyHostnameSubnet4();
+}
+
+/// @brief Verifies that IPv4 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAllbyHostnameSubnet4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAllbyHostnameSubnet4();
+}
+
+/// @brief Verifies that IPv6 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAllbyHostnameSubnet6) {
+ testGetAllbyHostnameSubnet6();
+}
+
+/// @brief Verifies that IPv6 host reservations with the same hostname and in
+/// the same subnet can be retrieved
+TEST_F(PgSqlHostDataSourceTest, getAllbyHostnameSubnet6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetAllbyHostnameSubnet6();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage4) {
+ testGetPage4();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage4();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage6) {
+ testGetPage6();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage6();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(PgSqlHostDataSourceTest, getPageLimit4) {
+ testGetPageLimit4(Host::IDENT_DUID);
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(PgSqlHostDataSourceTest, getPageLimit4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPageLimit4(Host::IDENT_DUID);
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(PgSqlHostDataSourceTest, getPageLimit6) {
+ testGetPageLimit6(Host::IDENT_HWADDR);
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages without truncation from the limit.
+TEST_F(PgSqlHostDataSourceTest, getPageLimit6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPageLimit6(Host::IDENT_HWADDR);
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(PgSqlHostDataSourceTest, getPage4Subnets) {
+ testGetPage4Subnets();
+}
+
+/// @brief Verifies that IPv4 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(PgSqlHostDataSourceTest, getPage4SubnetsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage4Subnets();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(PgSqlHostDataSourceTest, getPage6Subnets) {
+ testGetPage6Subnets();
+}
+
+/// @brief Verifies that IPv6 host reservations in the same subnet can be retrieved
+/// by pages even with multiple subnets.
+TEST_F(PgSqlHostDataSourceTest, getPage6SubnetsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage6Subnets();
+}
+
+// Verifies that all IPv4 host reservations can be retrieved by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage4All) {
+ testGetPage4All();
+}
+
+// Verifies that all IPv4 host reservations can be retrieved by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage4AllMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage4All();
+}
+
+// Verifies that all IPv6 host reservations can be retrieved by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage6All) {
+ testGetPage6All();
+}
+
+// Verifies that all IPv6 host reservations can be retrieved by pages.
+TEST_F(PgSqlHostDataSourceTest, getPage6AllMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetPage6All();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses client-id (DUID) as identifier.
+TEST_F(PgSqlHostDataSourceTest, basic4ClientId) {
+ testBasic4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by IPv4
+/// address. Host uses client-id (DUID) as identifier.
+TEST_F(PgSqlHostDataSourceTest, basic4ClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasic4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(PgSqlHostDataSourceTest, getByIPv4HWaddr) {
+ testGetByIPv4(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses HW addresses as identifiers.
+TEST_F(PgSqlHostDataSourceTest, getByIPv4HWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv4(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(PgSqlHostDataSourceTest, getByIPv4ClientId) {
+ testGetByIPv4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies that multiple hosts can be added and later retrieved by their
+/// reserved IPv4 address. This test uses client-id (DUID) as identifiers.
+TEST_F(PgSqlHostDataSourceTest, getByIPv4ClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv4(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(PgSqlHostDataSourceTest, get4ByHWaddr) {
+ testGet4ByIdentifier(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(PgSqlHostDataSourceTest, get4ByHWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_HWADDR);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// DUID.
+TEST_F(PgSqlHostDataSourceTest, get4ByDUID) {
+ testGet4ByIdentifier(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// DUID.
+TEST_F(PgSqlHostDataSourceTest, get4ByDUIDMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_DUID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// circuit id.
+TEST_F(PgSqlHostDataSourceTest, get4ByCircuitId) {
+ testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// circuit id.
+TEST_F(PgSqlHostDataSourceTest, get4ByCircuitIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client-id.
+TEST_F(PgSqlHostDataSourceTest, get4ByClientId) {
+ testGet4ByIdentifier(Host::IDENT_CLIENT_ID);
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client-id.
+TEST_F(PgSqlHostDataSourceTest, get4ByClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet4ByIdentifier(Host::IDENT_CLIENT_ID);
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId1) {
+ testHWAddrNotClientId();
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId1MultiThreading) {
+ MultiThreadingTest mt(true);
+ testHWAddrNotClientId();
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId2) {
+ testClientIdNotHWAddr();
+}
+
+/// @brief Test verifies if hardware address and client identifier are not confused.
+TEST_F(PgSqlHostDataSourceTest, hwaddrNotClientId2MultiThreading) {
+ MultiThreadingTest mt(true);
+ testClientIdNotHWAddr();
+}
+
+/// @brief Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(PgSqlHostDataSourceTest, hostnameFQDN) {
+ testHostname("foo.example.org", 1);
+}
+
+/// @brief Test verifies if a host with FQDN hostname can be stored and later retrieved.
+TEST_F(PgSqlHostDataSourceTest, hostnameFQDNMultiThreading) {
+ MultiThreadingTest mt(true);
+ testHostname("foo.example.org", 1);
+}
+
+/// @brief Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+/// retrieved.
+TEST_F(PgSqlHostDataSourceTest, hostnameFQDN100) {
+ testHostname("foo.example.org", 100);
+}
+
+/// @brief Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
+/// retrieved.
+TEST_F(PgSqlHostDataSourceTest, hostnameFQDN100MultiThreading) {
+ MultiThreadingTest mt(true);
+ testHostname("foo.example.org", 100);
+}
+
+/// @brief Test verifies if a host without any hostname specified can be stored and later
+/// retrieved.
+TEST_F(PgSqlHostDataSourceTest, noHostname) {
+ testHostname("", 1);
+}
+
+/// @brief Test verifies if a host without any hostname specified can be stored and later
+/// retrieved.
+TEST_F(PgSqlHostDataSourceTest, noHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testHostname("", 1);
+}
+
+/// @brief Test verifies if a host with user context can be stored and later retrieved.
+TEST_F(PgSqlHostDataSourceTest, usercontext) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testUserContext(Element::fromJSON(comment));
+}
+
+/// @brief Test verifies if a host with user context can be stored and later retrieved.
+TEST_F(PgSqlHostDataSourceTest, usercontextMultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testUserContext(Element::fromJSON(comment));
+}
+
+/// @brief Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with hardware address X, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies if the hardware or client-id query can match hardware address.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_hwaddrOrClientId1MultiThreading) {
+ MultiThreadingTest mt(true);
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with hardware address X, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies if the hardware or client-id query can match client-id.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with client identifier Y, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies if the hardware or client-id query can match client-id.
+TEST_F(PgSqlHostDataSourceTest, DISABLED_hwaddrOrClientId2MultiThreading) {
+ MultiThreadingTest mt(true);
+ /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
+ /// be discussed.
+ ///
+ /// @todo: Add host reservation with client identifier Y, try to retrieve
+ /// host for hardware address X or client identifier Y, verify that the
+ /// reservation is returned.
+}
+
+/// @brief Test verifies that host with IPv6 address and DUID can be added and
+/// later retrieved by IPv6 address.
+TEST_F(PgSqlHostDataSourceTest, get6AddrWithDuid) {
+ testGetByIPv6(Host::IDENT_DUID, false);
+}
+
+/// @brief Test verifies that host with IPv6 address and DUID can be added and
+/// later retrieved by IPv6 address.
+TEST_F(PgSqlHostDataSourceTest, get6AddrWithDuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_DUID, false);
+}
+
+/// @brief Test verifies that host with IPv6 address and HWAddr can be added and
+/// later retrieved by IPv6 address.
+TEST_F(PgSqlHostDataSourceTest, get6AddrWithHWAddr) {
+ testGetByIPv6(Host::IDENT_HWADDR, false);
+}
+
+/// @brief Test verifies that host with IPv6 address and HWAddr can be added and
+/// later retrieved by IPv6 address.
+TEST_F(PgSqlHostDataSourceTest, get6AddrWithHWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_HWADDR, false);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and DUID can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(PgSqlHostDataSourceTest, get6PrefixWithDuid) {
+ testGetByIPv6(Host::IDENT_DUID, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and DUID can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(PgSqlHostDataSourceTest, get6PrefixWithDuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_DUID, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and HWAddr can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(PgSqlHostDataSourceTest, get6PrefixWithHWaddr) {
+ testGetByIPv6(Host::IDENT_HWADDR, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix and HWAddr can be added and
+/// later retrieved by IPv6 prefix.
+TEST_F(PgSqlHostDataSourceTest, get6PrefixWithHWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetByIPv6(Host::IDENT_HWADDR, true);
+}
+
+/// @brief Test verifies that host with IPv6 prefix reservation can be retrieved
+/// by subnet id and prefix value.
+TEST_F(PgSqlHostDataSourceTest, get6SubnetPrefix) {
+ testGetBySubnetIPv6();
+}
+
+/// @brief Test verifies that host with IPv6 prefix reservation can be retrieved
+/// by subnet id and prefix value.
+TEST_F(PgSqlHostDataSourceTest, get6SubnetPrefixMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetBySubnetIPv6();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(PgSqlHostDataSourceTest, get6ByHWaddr) {
+ testGet6ByHWAddr();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// hardware address.
+TEST_F(PgSqlHostDataSourceTest, get6ByHWaddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet6ByHWAddr();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client identifier.
+TEST_F(PgSqlHostDataSourceTest, get6ByClientId) {
+ testGet6ByClientId();
+}
+
+/// @brief Test verifies if a host reservation can be added and later retrieved by
+/// client identifier.
+TEST_F(PgSqlHostDataSourceTest, get6ByClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGet6ByClientId();
+}
+
+/// @brief Test verifies if a host reservation can be stored with both IPv6 address and
+/// prefix.
+TEST_F(PgSqlHostDataSourceTest, addr6AndPrefix) {
+ testAddr6AndPrefix();
+}
+
+/// @brief Test verifies if a host reservation can be stored with both IPv6 address and
+/// prefix.
+TEST_F(PgSqlHostDataSourceTest, addr6AndPrefixMultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddr6AndPrefix();
+}
+
+/// @brief Tests if host with multiple IPv6 reservations can be added and then
+/// retrieved correctly. Test checks reservations comparing.
+TEST_F(PgSqlHostDataSourceTest, multipleReservations) {
+ testMultipleReservations();
+}
+
+/// @brief Tests if host with multiple IPv6 reservations can be added and then
+/// retrieved correctly. Test checks reservations comparing.
+TEST_F(PgSqlHostDataSourceTest, multipleReservationsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleReservations();
+}
+
+/// @brief Tests if compareIPv6Reservations() method treats same pool of reservations
+/// but added in different order as equal.
+TEST_F(PgSqlHostDataSourceTest, multipleReservationsDifferentOrder) {
+ testMultipleReservationsDifferentOrder();
+}
+
+/// @brief Tests if compareIPv6Reservations() method treats same pool of reservations
+/// but added in different order as equal.
+TEST_F(PgSqlHostDataSourceTest, multipleReservationsDifferentOrderMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleReservationsDifferentOrder();
+}
+
+/// @brief Test that multiple client classes for IPv4 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(PgSqlHostDataSourceTest, multipleClientClasses4) {
+ testMultipleClientClasses4();
+}
+
+/// @brief Test that multiple client classes for IPv4 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(PgSqlHostDataSourceTest, multipleClientClasses4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleClientClasses4();
+}
+
+/// @brief Test that multiple client classes for IPv6 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(PgSqlHostDataSourceTest, multipleClientClasses6) {
+ testMultipleClientClasses6();
+}
+
+/// @brief Test that multiple client classes for IPv6 can be inserted and
+/// retrieved for a given host reservation.
+TEST_F(PgSqlHostDataSourceTest, multipleClientClasses6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleClientClasses6();
+}
+
+/// @brief Test that multiple client classes for both IPv4 and IPv6 can
+/// be inserted and retrieved for a given host reservation.
+TEST_F(PgSqlHostDataSourceTest, multipleClientClassesBoth) {
+ testMultipleClientClassesBoth();
+}
+
+/// @brief Test that multiple client classes for both IPv4 and IPv6 can
+/// be inserted and retrieved for a given host reservation.
+TEST_F(PgSqlHostDataSourceTest, multipleClientClassesBothMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleClientClassesBoth();
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same hardware address). The test logic is as follows:
+/// Insert 10 host reservations for a given physical host (the same
+/// hardware address), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them all correctly.
+TEST_F(PgSqlHostDataSourceTest, multipleSubnetsHWAddr) {
+ testMultipleSubnets(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same hardware address). The test logic is as follows:
+/// Insert 10 host reservations for a given physical host (the same
+/// hardware address), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them all correctly.
+TEST_F(PgSqlHostDataSourceTest, multipleSubnetsHWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleSubnets(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same client identifier). The test logic is as follows:
+///
+/// Insert 10 host reservations for a given physical host (the same
+/// client-identifier), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them correctly.
+TEST_F(PgSqlHostDataSourceTest, multipleSubnetsClientId) {
+ testMultipleSubnets(10, Host::IDENT_DUID);
+}
+
+/// @brief Test if the same host can have reservations in different subnets (with the
+/// same client identifier). The test logic is as follows:
+///
+/// Insert 10 host reservations for a given physical host (the same
+/// client-identifier), but for different subnets (different subnet-ids).
+/// Make sure that getAll() returns them correctly.
+TEST_F(PgSqlHostDataSourceTest, multipleSubnetsClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleSubnets(10, Host::IDENT_DUID);
+}
+
+/// @brief Test if host reservations made for different IPv6 subnets are handled correctly.
+/// The test logic is as follows:
+///
+/// Insert 10 host reservations for different subnets. Make sure that
+/// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(PgSqlHostDataSourceTest, subnetId6) {
+ testSubnetId6(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if host reservations made for different IPv6 subnets are handled correctly.
+/// The test logic is as follows:
+///
+/// Insert 10 host reservations for different subnets. Make sure that
+/// get6(subnet-id, ...) calls return correct reservation.
+TEST_F(PgSqlHostDataSourceTest, subnetId6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testSubnetId6(10, Host::IDENT_HWADDR);
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same DUID.
+TEST_F(PgSqlHostDataSourceTest, addDuplicate6WithDUID) {
+ testAddDuplicate6WithSameDUID();
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same DUID.
+TEST_F(PgSqlHostDataSourceTest, addDuplicate6WithDUIDMultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicate6WithSameDUID();
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same HWAddr.
+TEST_F(PgSqlHostDataSourceTest, addDuplicate6WithHWAddr) {
+ testAddDuplicate6WithSameHWAddr();
+}
+
+/// @brief Test if the duplicate host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+/// Hosts with same HWAddr.
+TEST_F(PgSqlHostDataSourceTest, addDuplicate6WithHWAddrMultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicate6WithSameHWAddr();
+}
+
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(PgSqlHostDataSourceTest, addDuplicateIPv6) {
+ testAddDuplicateIPv6();
+}
+
+/// @brief Test if the same IPv6 reservation can't be inserted multiple times.
+TEST_F(PgSqlHostDataSourceTest, addDuplicateIPv6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicateIPv6();
+}
+
+/// @brief Test if the host reservation for the same IPv6 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(PgSqlHostDataSourceTest, allowDuplicateIPv6) {
+ testAllowDuplicateIPv6();
+}
+
+/// @brief Test if the host reservation for the same IPv6 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(PgSqlHostDataSourceTest, allowDuplicateIPv6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAllowDuplicateIPv6();
+}
+
+/// @brief Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+TEST_F(PgSqlHostDataSourceTest, addDuplicateIPv4) {
+ testAddDuplicateIPv4();
+}
+
+/// @brief Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
+/// follows: try to add multiple instances of the same host reservation and
+/// verify that the second and following attempts will throw exceptions.
+TEST_F(PgSqlHostDataSourceTest, addDuplicateIPv4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddDuplicateIPv4();
+}
+
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(PgSqlHostDataSourceTest, allowDuplicateIPv4) {
+ testAllowDuplicateIPv4();
+}
+
+/// @brief Test if the host reservation for the same IPv4 address can be inserted
+/// multiple times when allowed by the configuration and when the host identifier
+/// is different.
+TEST_F(PgSqlHostDataSourceTest, allowDuplicateIPv4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAllowDuplicateIPv4();
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations4) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a binary format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations4MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations6) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a binary format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations6MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(false, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations46) {
+ testOptionsReservations46(false);
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// binary format and retrieved with a single query to the database.
+TEST_F(PgSqlHostDataSourceTest, optionsReservations46MultiThreading) {
+ MultiThreadingTest mt(true);
+ testOptionsReservations46(false);
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations4) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 options can be inserted in a textual format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations4MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations4(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations6) {
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv6 options can be inserted in a textual format
+/// and retrieved from the PostgreSQL host database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations6MultiThreading) {
+ MultiThreadingTest mt(true);
+ string comment = "{ \"comment\": \"a host reservation\" }";
+ testOptionsReservations6(true, Element::fromJSON(comment));
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// textual format and retrieved with a single query to the database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations46) {
+ testOptionsReservations46(true);
+}
+
+/// @brief This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
+/// textual format and retrieved with a single query to the database.
+TEST_F(PgSqlHostDataSourceTest, formattedOptionsReservations46MultiThreading) {
+ MultiThreadingTest mt(true);
+ testOptionsReservations46(true);
+}
+
+/// @brief This test checks transactional insertion of the host information
+/// into the database. The failure to insert host information at
+/// any stage should cause the whole transaction to be rolled back.
+TEST_F(PgSqlHostDataSourceTest, testAddRollback) {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // To test the transaction rollback mechanism we need to cause the
+ // insertion of host information to fail at some stage. The 'hosts'
+ // table should be updated correctly but the failure should occur
+ // when inserting reservations or options. The simplest way to
+ // achieve that is to simply drop one of the tables. To do so, we
+ // connect to the database and issue a DROP query.
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+ PgSqlConnection conn(params);
+ ASSERT_NO_THROW(conn.openDatabase());
+
+ PgSqlResult r(PQexec(conn, "DROP TABLE IF EXISTS ipv6_reservations"));
+ ASSERT_TRUE (PQresultStatus(r) == PGRES_COMMAND_OK)
+ << " drop command failed :" << PQerrorMessage(conn);
+
+ // Create a host with a reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::1",
+ Host::IDENT_HWADDR, false, "randomKey");
+ // Let's assign some DHCPv4 subnet to the host, because we will use the
+ // DHCPv4 subnet to try to retrieve the host after failed insertion.
+ host->setIPv4SubnetID(SubnetID(4));
+
+ // There is no ipv6_reservations table, so the insertion should fail.
+ ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+ // Even though we have created a DHCPv6 host, we can't use get6()
+ // method to retrieve the host from the database, because the
+ // query would expect that the ipv6_reservations table is present.
+ // Therefore, the query would fail. Instead, we use the get4 method
+ // which uses the same client identifier, but doesn't attempt to
+ // retrieve the data from ipv6_reservations table. The query should
+ // pass but return no host because the (insert) transaction is expected
+ // to be rolled back.
+ ConstHostPtr from_hds = hdsptr_->get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ EXPECT_FALSE(from_hds);
+}
+
+/// @brief This test checks transactional insertion of the host information
+/// into the database. The failure to insert host information at
+/// any stage should cause the whole transaction to be rolled back.
+TEST_F(PgSqlHostDataSourceTest, testAddRollbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // To test the transaction rollback mechanism we need to cause the
+ // insertion of host information to fail at some stage. The 'hosts'
+ // table should be updated correctly but the failure should occur
+ // when inserting reservations or options. The simplest way to
+ // achieve that is to simply drop one of the tables. To do so, we
+ // connect to the database and issue a DROP query.
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+ PgSqlConnection conn(params);
+ ASSERT_NO_THROW(conn.openDatabase());
+
+ PgSqlResult r(PQexec(conn, "DROP TABLE IF EXISTS ipv6_reservations"));
+ ASSERT_TRUE (PQresultStatus(r) == PGRES_COMMAND_OK)
+ << " drop command failed :" << PQerrorMessage(conn);
+
+ // Create a host with a reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::1",
+ Host::IDENT_HWADDR, false, "randomKey");
+ // Let's assign some DHCPv4 subnet to the host, because we will use the
+ // DHCPv4 subnet to try to retrieve the host after failed insertion.
+ host->setIPv4SubnetID(SubnetID(4));
+
+ // There is no ipv6_reservations table, so the insertion should fail.
+ ASSERT_THROW(hdsptr_->add(host), DbOperationError);
+
+ // Even though we have created a DHCPv6 host, we can't use get6()
+ // method to retrieve the host from the database, because the
+ // query would expect that the ipv6_reservations table is present.
+ // Therefore, the query would fail. Instead, we use the get4 method
+ // which uses the same client identifier, but doesn't attempt to
+ // retrieve the data from ipv6_reservations table. The query should
+ // pass but return no host because the (insert) transaction is expected
+ // to be rolled back.
+ ConstHostPtr from_hds = hdsptr_->get4(host->getIPv4SubnetID(),
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ EXPECT_FALSE(from_hds);
+}
+
+/// @brief This test checks that siaddr, sname, file fields can be retrieved
+/// from a database for a host.
+TEST_F(PgSqlHostDataSourceTest, messageFields) {
+ testMessageFields4();
+}
+
+/// @brief This test checks that siaddr, sname, file fields can be retrieved
+/// from a database for a host.
+TEST_F(PgSqlHostDataSourceTest, messageFieldsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testMessageFields4();
+}
+
+/// @brief Check that delete(subnet-id, addr4) works.
+TEST_F(PgSqlHostDataSourceTest, deleteByAddr4) {
+ testDeleteByAddr4();
+}
+
+/// @brief Check that delete(subnet-id, addr4) works.
+TEST_F(PgSqlHostDataSourceTest, deleteByAddr4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteByAddr4();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById4) {
+ testDeleteById4();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById4();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById4Options) {
+ testDeleteById4Options();
+}
+
+/// @brief Check that delete(subnet4-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById4OptionsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById4Options();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById6) {
+ testDeleteById6();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works.
+TEST_F(PgSqlHostDataSourceTest, deleteById6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById6();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById6Options) {
+ testDeleteById6Options();
+}
+
+/// @brief Check that delete(subnet6-id, identifier-type, identifier) works,
+/// even when options are present.
+TEST_F(PgSqlHostDataSourceTest, deleteById6OptionsMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteById6Options();
+}
+
+/// @brief Tests that multiple reservations without IPv4 addresses can be
+/// specified within a subnet.
+TEST_F(PgSqlHostDataSourceTest, testMultipleHostsNoAddress4) {
+ testMultipleHostsNoAddress4();
+}
+
+/// @brief Tests that multiple reservations without IPv4 addresses can be
+/// specified within a subnet.
+TEST_F(PgSqlHostDataSourceTest, testMultipleHostsNoAddress4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleHostsNoAddress4();
+}
+
+/// @brief Tests that multiple hosts can be specified within an IPv6 subnet.
+TEST_F(PgSqlHostDataSourceTest, testMultipleHosts6) {
+ testMultipleHosts6();
+}
+
+/// @brief Tests that multiple hosts can be specified within an IPv6 subnet.
+TEST_F(PgSqlHostDataSourceTest, testMultipleHosts6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMultipleHosts6();
+}
+
+/// @brief Tests that hosts can be updated.
+TEST_F(PgSqlHostDataSourceTest, update) {
+ testUpdate();
+}
+
+/// @brief Tests that hosts can be updated.
+TEST_F(PgSqlHostDataSourceTest, updateMultiThreading) {
+ MultiThreadingTest mt(true);
+ testUpdate();
+}
+
+/// @brief Test fixture class for validating @c HostMgr using
+/// PostgreSQL as alternate host data source.
+class PgSQLHostMgrTest : public HostMgrTest {
+protected:
+
+ /// @brief Build PostgreSQL schema for a test.
+ virtual void SetUp();
+
+ /// @brief Rollback and drop PostgreSQL schema after the test.
+ virtual void TearDown();
+};
+
+void
+PgSQLHostMgrTest::SetUp() {
+ HostMgrTest::SetUp();
+
+ // Ensure we have the proper schema with no transient data.
+ db::test::createPgSQLSchema();
+
+ // Connect to the database
+ try {
+ HostMgr::addBackend(db::test::validPgSQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the PostgreSQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+}
+
+void
+PgSQLHostMgrTest::TearDown() {
+ try {
+ HostMgr::instance().getHostDataSource()->rollback();
+ } catch(...) {
+ // we don't care if we aren't in a transaction.
+ }
+
+ HostMgr::delBackend("postgresql");
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ db::test::destroyPgSQLSchema();
+}
+
+/// @brief Test fixture class for validating @c HostMgr using
+/// PostgreSQL as alternate host data source and PostgreSQL connectivity loss.
+class PgSQLHostMgrDbLostCallbackTest : public HostMgrDbLostCallbackTest {
+public:
+ virtual void destroySchema() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ db::test::destroyPgSQLSchema();
+ }
+
+ virtual void createSchema() {
+ // Ensure we have the proper schema with no transient data.
+ db::test::createPgSQLSchema();
+ }
+
+ virtual std::string validConnectString() {
+ return (db::test::validPgSQLConnectionString());
+ }
+
+ virtual std::string invalidConnectString() {
+ return (connectionString(PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+ }
+};
+
+// This test verifies that reservations for a particular client can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(PgSQLHostMgrTest, getAll) {
+ testGetAll(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(PgSQLHostMgrTest, getAll4BySubnet) {
+ testGetAll4BySubnet(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(PgSQLHostMgrTest, getAll6BySubnet) {
+ testGetAll6BySubnet(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// IPv4 subnet and reserved address.
+TEST_F(PgSQLHostMgrTest, getAll4BySubnetIP) {
+ testGetAll4BySubnetIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// IPv6 subnet and reserved address.
+TEST_F(PgSQLHostMgrTest, getAll6BySubnetIP) {
+ testGetAll6BySubnetIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the specified
+// IPv6 reserved address.
+TEST_F(PgSQLHostMgrTest, getAll6ByIP) {
+ testGetAll6ByIP(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that HostMgr returns all reservations for the
+// IPv6 reserved prefix.
+TEST_F(PgSQLHostMgrTest, getAll6ByIpPrefix) {
+ testGetAll6ByIpPrefix(*getCfgHosts(), *getCfgHosts());
+}
+
+// This test verifies that reservations for a particular hostname can be
+// retrieved from the configuration file and a database simultaneously.
+TEST_F(PgSQLHostMgrTest, getAllbyHostname) {
+ testGetAllbyHostname(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular hostname and
+// DHCPv4 subnet can be retrieved from the configuration file and a
+// database simultaneously.
+TEST_F(PgSQLHostMgrTest, getAllbyHostnameSubnet4) {
+ testGetAllbyHostnameSubnet4(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular hostname and
+// DHCPv6 subnet can be retrieved from the configuration file and a
+// database simultaneously.
+TEST_F(PgSQLHostMgrTest, getAllbyHostnameSubnet6) {
+ testGetAllbyHostnameSubnet6(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved by pages from the configuration file and a database
+// simultaneously.
+TEST_F(PgSQLHostMgrTest, getPage4) {
+ testGetPage4(true);
+}
+
+// This test verifies that all v4 reservations be retrieved by pages
+// from the configuration file and a database simultaneously.
+TEST_F(PgSQLHostMgrTest, getPage4All) {
+ testGetPage4All(true);
+}
+
+// This test verifies that reservations for a particular subnet can
+// be retrieved by pages from the configuration file and a database
+// simultaneously.
+TEST_F(PgSQLHostMgrTest, getPage6) {
+ testGetPage6(true);
+}
+
+// This test verifies that all v6 reservations be retrieved by pages
+// from the configuration file and a database simultaneously.
+TEST_F(PgSQLHostMgrTest, getPage6All) {
+ testGetPage6All(true);
+}
+
+// This test verifies that IPv4 reservations for a particular client can
+// be retrieved from the configuration file and a database simultaneously.
+TEST_F(PgSQLHostMgrTest, getAll4) {
+ testGetAll4(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the IPv4 reservation can be retrieved from a
+// database.
+TEST_F(PgSQLHostMgrTest, get4) {
+ testGet4(HostMgr::instance());
+}
+
+// This test verifies that the IPv6 reservation can be retrieved from a
+// database.
+TEST_F(PgSQLHostMgrTest, get6) {
+ testGet6(HostMgr::instance());
+}
+
+// This test verifies that the IPv6 prefix reservation can be retrieved
+// from a configuration file and a database.
+TEST_F(PgSQLHostMgrTest, get6ByPrefix) {
+ testGet6ByPrefix(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the reservations can be added to a configuration
+// file and a database.
+TEST_F(PgSQLHostMgrTest, add) {
+ testAdd(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the reservations can be deleted from a configuration
+// file and a database by subnet ID and address.
+TEST_F(PgSQLHostMgrTest, del) {
+ testDeleteByIDAndAddress(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the IPv4 reservations can be deleted from a
+// configuration file and a database by subnet ID and identifier.
+TEST_F(PgSQLHostMgrTest, del4) {
+ testDelete4ByIDAndIdentifier(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that the IPv6 reservations can be deleted from a
+// configuration file and a database by subnet ID and identifier.
+TEST_F(PgSQLHostMgrTest, del6) {
+ testDelete6ByIDAndIdentifier(*getCfgHosts(), HostMgr::instance());
+}
+
+// This test verifies that it is possible to control whether the reserved
+// IP addresses are unique or non unique via the HostMgr.
+TEST_F(PgSQLHostMgrTest, setIPReservationsUnique) {
+ EXPECT_TRUE(HostMgr::instance().setIPReservationsUnique(true));
+ EXPECT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testNoCallbackOnOpenFailure) {
+ MultiThreadingTest mt(false);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testNoCallbackOnOpenFailureMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndFailedCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndFailedCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSQLHostMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_extended_info_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_extended_info_unittest.cc
new file mode 100644
index 0000000..b654716
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/pgsql_lease_extended_info_unittest.cc
@@ -0,0 +1,1111 @@
+// Copyright (C) 2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcpsrv/cfg_consistency.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief IPv4 addresses used in the tests.
+const vector<string> ADDRESS4 = {
+ "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+ "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7"
+};
+
+/// @brief IPv6 addresses used in the tests.
+const vector<string> ADDRESS6 = {
+ "2001:db8::", "2001:db8::1", "2001:db8::2", "2001:db8::3",
+ "2001:db8::4", "2001:db8::5", "2001:db8::6", "2001:db8::7"
+};
+
+/// @brief DUIDs used in the tests.
+const vector<string> DUIDS = {
+ "wwwwwwww", "BBBBBBBB", "::::::::", "0123456789acdef",
+ "BBBBBBBB", "$$$$$$$$", "^^^^^^^^", "\xe5\xe5\xe5\xe5\xe5\xe5\xe5\xe5"
+};
+
+/// @brief Test fixture class for extended info tests.
+class PgSqlExtendedInfoTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ PgSqlExtendedInfoTest() {
+ // Ensure we have the proper schema with no transient data.
+ createPgSQLSchema();
+
+ // Connect to the database.
+ try {
+ LeaseMgrFactory::create(validPgSQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the PostgreSQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ lease_mgr_ = &(LeaseMgrFactory::instance());
+ leases4.clear();
+ leases6.clear();
+ MultiThreadingMgr::instance().setMode(false);
+ now_ = time(0);
+ }
+
+ /// @brief Destructor.
+ ~PgSqlExtendedInfoTest() {
+ LeaseMgrFactory::destroy();
+ // If data wipe enabled, delete transient data otherwise destroy
+ // the schema.
+ destroyPgSQLSchema();
+
+ leases4.clear();
+ leases6.clear();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Create and set v4 leases.
+ ///
+ /// @param insert When true insert in the database.
+ void initLease4(bool insert = true) {
+ ASSERT_EQ(ADDRESS4.size(), DUIDS.size());
+ for (size_t i = 0; i < ADDRESS4.size(); ++i) {
+ Lease4Ptr lease;
+ vector<uint8_t> hwaddr_data(5, 0x08);
+ hwaddr_data.push_back(0x80 + i);
+ HWAddrPtr hwaddr(new HWAddr(hwaddr_data, HTYPE_ETHER));
+ vector<uint8_t> client_id = createFromString(DUIDS[i]);
+ IOAddress address(ADDRESS4[i]);
+ ASSERT_NO_THROW(lease.reset(new Lease4(address, hwaddr,
+ &client_id[0],
+ client_id.size(),
+ 1000, now_,
+ static_cast<SubnetID>(i))));
+ leases4.push_back(lease);
+ if (insert) {
+ EXPECT_TRUE(lease_mgr_->addLease(lease));
+ }
+ }
+ ASSERT_EQ(ADDRESS4.size(), leases4.size());
+ }
+
+ /// @brief Create and set v6 leases.
+ void initLease6() {
+ ASSERT_EQ(ADDRESS6.size(), DUIDS.size());
+ for (size_t i = 0; i < ADDRESS6.size(); ++i) {
+ Lease6Ptr lease;
+ vector<uint8_t> duid_data = createFromString(DUIDS[i]);
+ DuidPtr duid(new DUID(duid_data));
+ IOAddress addr(ADDRESS6[i]);
+ ASSERT_NO_THROW(lease.reset(new Lease6(((i % 2) ? Lease::TYPE_NA : Lease::TYPE_PD), addr, duid,
+ 123, 1000, 2000,
+ static_cast<SubnetID>(i))));
+ leases6.push_back(lease);
+ EXPECT_TRUE(lease_mgr_->addLease(lease));
+ }
+ ASSERT_EQ(ADDRESS6.size(), leases6.size());
+ }
+
+ /// @brief Create a vector of uint8_t from a string.
+ ///
+ /// @param content A not empty string holding the content.
+ /// @return A vector of uint8_t with the given content.
+ inline vector<uint8_t> createFromString(const string& content) {
+ vector<uint8_t> v;
+ v.resize(content.size());
+ memmove(&v[0], &content[0], v.size());
+ return (v);
+ }
+
+ /// @brief Test initLease4.
+ void testInitLease4();
+
+ /// @brief Test initLease6.
+ void testInitLease6();
+
+ /// @brief Test getLease4ByRelayId.
+ void testGetLeases4ByRelayId();
+
+ /// @brief Test getLease4ByRemoteId.
+ void testGetLeases4ByRemoteId();
+
+ /// @brief Test upgradeExtendedInfo4.
+ void testUpgradeExtendedInfo4(const CfgConsistency::ExtendedInfoSanity& check,
+ const LeasePageSize& page_size);
+
+ /// @brief Test getLeases6ByLink.
+ void testGetLeases6ByLink();
+
+ /// @brief Lease manager.
+ LeaseMgr* lease_mgr_;
+
+ /// @brief V4 leases.
+ Lease4Collection leases4;
+
+ /// @brief V6 leases.
+ Lease6Collection leases6;
+
+ /// @brief Current timestamp.
+ time_t now_;
+};
+
+/// @brief Verifies that the lease manager can add the v4 leases.
+void
+PgSqlExtendedInfoTest::testInitLease4() {
+ initLease4();
+ EXPECT_EQ(8, leases4.size());
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ Lease4Collection got;
+ // Use the page version as it returns leases in order.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4(zero, LeasePageSize(100)));
+ ASSERT_EQ(leases4.size(), got.size());
+ auto compare = [](Lease4Ptr const& left, Lease4Ptr const& right) {
+ return (left->addr_ < right->addr_);
+ };
+ std::sort(got.begin(), got.end(), compare);
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ ConstElementPtr expected = leases4[i]->toElement();
+ LeasePtr lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(expected->equals(*lease->toElement()))
+ << "expected: " << expected->str() << "\n"
+ << "got: " << lease->toElement()->str() << "\n";
+ }
+}
+
+TEST_F(PgSqlExtendedInfoTest, initLease4) {
+ testInitLease4();
+}
+
+TEST_F(PgSqlExtendedInfoTest, initLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testInitLease4();
+}
+
+/// @brief Verifies that getLeases4ByRelayId works as expected.
+void
+PgSqlExtendedInfoTest::testGetLeases4ByRelayId() {
+ // Lease manager is created with empty tables.
+ initLease4(false);
+
+ // Create leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> relay_id0 = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> relay_id1 = { 1, 2, 3, 4 };
+ vector<uint8_t> relay_id2 = createFromString(DUIDS[2]);
+ string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt0 += " \"sub-options\": \"0C03AABBCC\",";
+ user_context_txt0 += " \"relay-id\": \"AABBCC\" } } }";
+ ElementPtr user_context0;
+ ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+ string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt1 += " \"sub-options\": \"0C0401020304\",";
+ user_context_txt1 += " \"relay-id\": \"01020304\" } } }";
+ ElementPtr user_context1;
+ ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+ Lease4Ptr lease;
+ // lease0: addr0, id0, now.
+ lease = leases4[0];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+
+ // lease1: addr1, id1, now.
+ lease = leases4[1];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id1;
+ lease->setContext(user_context1);
+
+ // lease2: addr2, id0, now - 500.
+ lease = leases4[2];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 500;
+
+ // lease3: addr3, id0, now - 800.
+ lease = leases4[3];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 800;
+
+ // lease4: addr4, id0, now - 100.
+ lease = leases4[4];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 100;
+
+ // Add leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_TRUE(lease_mgr_->addLease(leases4[i]));
+ }
+
+ Lease4Collection got;
+ // Unknown relay id #2: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown relay id #2, now - 1000, now + 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id2,
+ zero,
+ LeasePageSize(100),
+ now_ - 1000,
+ now_ + 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, now - 2000, now - 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 2000,
+ now_ - 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, now + 1000, now + 2000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ + 1000,
+ now_ + 2000));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0: 3 entries (0, 2, 3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(4, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[3];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, partial: 2 entries (0, 2).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, partial from previous: 2 entries (3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr2,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, final partial: no entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr4,
+ LeasePageSize(2)));
+ EXPECT_EQ(0, got.size());
+
+ // Relay id #0, from now - 500: 3 entries (0, 2, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 500));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, to now - 200: 3 entries (2, 3).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(100),
+ 0, now_ - 200));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ zero,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr2,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(relay_id0, lease->relay_id_);
+
+ // Relay id #0, from now - 500 to now - 100, final partial.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRelayId(relay_id0,
+ addr4,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRelayId) {
+ testGetLeases4ByRelayId();
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRelayIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4ByRelayId();
+}
+
+/// @brief Verifies that getLeases4ByRemoteId works as expected.
+void
+PgSqlExtendedInfoTest::testGetLeases4ByRemoteId() {
+ // Lease manager is created with empty tables.
+ initLease4(true);
+
+ // Update leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> remote_id0 = { 1, 2, 3, 4 };
+ vector<uint8_t> remote_id1 = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> remote_id2 = createFromString(DUIDS[2]);
+ string user_context_txt0 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt0 += " \"sub-options\": \"020401020304\",";
+ user_context_txt0 += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context0;
+ ASSERT_NO_THROW(user_context0 = Element::fromJSON(user_context_txt0));
+ string user_context_txt1 = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt1 += " \"sub-options\": \"0203AABBCC\",";
+ user_context_txt1 += " \"remote-id\": \"AABBCC\" } } }";
+ ElementPtr user_context1;
+ ASSERT_NO_THROW(user_context1 = Element::fromJSON(user_context_txt1));
+
+ Lease4Ptr lease;
+ // lease0: addr0, id0, now.
+ lease = leases4[0];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+
+ // lease1: addr1, id1, now.
+ lease = leases4[1];
+ lease->remote_id_ = remote_id1;
+ lease->setContext(user_context1);
+
+ // lease2: addr2, id0, now - 500.
+ lease = leases4[2];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 500;
+
+ // lease3: addr3, id0, now - 800.
+ lease = leases4[3];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 800;
+
+ // lease4: addr4, id0, now - 100.
+ lease = leases4[4];
+ lease->remote_id_ = remote_id0;
+ lease->setContext(user_context0);
+ lease->cltt_ = now_ - 100;
+
+ // Update leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_NO_THROW(lease_mgr_->updateLease4(leases4[i]));
+ }
+
+ Lease4Collection got;
+ // Unknown remote id #2: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+ zero,
+ LeasePageSize(100)));
+ EXPECT_EQ(0, got.size());
+
+ // Unknown remote id #2, now - 1000, now + 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id2,
+ zero,
+ LeasePageSize(100),
+ now_ - 1000,
+ now_ + 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, now - 2000, now - 1000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 2000,
+ now_ - 1000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, now + 1000, now + 2000: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ + 1000,
+ now_ + 2000));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0: 3 entries (0, 2, 3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100)));
+ ASSERT_EQ(4, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[3];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, partial: 2 entries (0, 2).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, partial from previous: 2 entries (3, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr2,
+ LeasePageSize(2)));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, final partial: no entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr4,
+ LeasePageSize(2)));
+ EXPECT_EQ(0, got.size());
+
+ // Remote id #0, from now - 500: 3 entries (0, 2, 4).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ now_ - 500));
+ ASSERT_EQ(3, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[2];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, to now - 200: 3 entries (2, 3).
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(100),
+ 0, now_ - 200));
+ ASSERT_EQ(2, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+ lease = got[1];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[3]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, partial: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ zero,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[2]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, partial from 2: 1 entry.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr2,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ ASSERT_EQ(1, got.size());
+ lease = got[0];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(*lease, *leases4[4]);
+ EXPECT_EQ(remote_id0, lease->remote_id_);
+
+ // Remote id #0, from now - 500 to now - 100, final partial.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4ByRemoteId(remote_id0,
+ addr4,
+ LeasePageSize(1),
+ now_ - 500,
+ now_ - 100));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRemoteId) {
+ testGetLeases4ByRemoteId();
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases4ByRemoteIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4ByRemoteId();
+}
+
+void
+PgSqlExtendedInfoTest::testUpgradeExtendedInfo4(const CfgConsistency::ExtendedInfoSanity& check,
+ const LeasePageSize& page_size) {
+ // Lease manager is created with empty tables.
+ initLease4(false);
+
+ // Create leases.
+ IOAddress addr0(ADDRESS4[0]);
+ IOAddress addr1(ADDRESS4[1]);
+ IOAddress addr2(ADDRESS4[2]);
+ IOAddress addr3(ADDRESS4[3]);
+ IOAddress addr4(ADDRESS4[4]);
+ IOAddress addr5(ADDRESS4[5]);
+ IOAddress addr6(ADDRESS4[6]);
+ IOAddress addr7(ADDRESS4[7]);
+ IOAddress zero = IOAddress::IPV4_ZERO_ADDRESS();
+ vector<uint8_t> relay_id = { 0xaa, 0xbb, 0xcc };
+ vector<uint8_t> relay_id2 = { 0xdd, 0xee, 0xff };
+ vector<uint8_t> remote_id = { 1, 2, 3, 4 };
+ vector<uint8_t> remote_id2 = { 5, 6, 7, 8 };
+ string user_context_txt = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_txt += " \"sub-options\": \"0204010203040C03AABBCC\",";
+ user_context_txt += " \"relay-id\": \"AABBCC\",";
+ user_context_txt += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context;
+ ASSERT_NO_THROW(user_context = Element::fromJSON(user_context_txt));
+ string user_context_txt_old = "{ \"ISC\": { \"relay-agent-info\":";
+ user_context_txt_old += " \"0204010203040C03AABBCC\" } }";
+ ElementPtr user_context_old;
+ ASSERT_NO_THROW(user_context_old = Element::fromJSON(user_context_txt_old));
+ string user_context_list_txt = "{ \"ISC\": { \"relay-agent-info\": [ ] } }";
+ ElementPtr user_context_list;
+ ASSERT_NO_THROW(user_context_list = Element::fromJSON(user_context_list_txt));
+ string user_context_lower_txt = "{ \"isc\": { \"relay-agent-info\":";
+ user_context_lower_txt += " \"0204010203040c03aabbcc\" } }";
+ ElementPtr user_context_lower;
+ ASSERT_NO_THROW(user_context_lower = Element::fromJSON(user_context_lower_txt));
+ string user_context_badsub_txt = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_badsub_txt += " \"sub-options\": \"foobar\",";
+ user_context_badsub_txt += " \"relay-id\": \"AABBCC\",";
+ user_context_badsub_txt += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context_badsub;
+ ASSERT_NO_THROW(user_context_badsub = Element::fromJSON(user_context_badsub_txt));
+ string user_context_extra_txt = "{ \"ISC\": { \"relay-agent-info\": {";
+ user_context_extra_txt += " \"foo\": \"bar\", ";
+ user_context_extra_txt += " \"sub-options\": \"0204010203040C03AABBCC\",";
+ user_context_extra_txt += " \"relay-id\": \"AABBCC\",";
+ user_context_extra_txt += " \"remote-id\": \"01020304\" } } }";
+ ElementPtr user_context_extra;
+ ASSERT_NO_THROW(user_context_extra = Element::fromJSON(user_context_extra_txt));
+
+ Lease4Ptr lease;
+ // lease0: addr0, ids, before: always not updated.
+ lease = leases4[0];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context);
+ lease->cltt_ = now_ - 500;
+
+ // lease1: addr1, ids, after: always not updated.
+ lease = leases4[1];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context);
+ lease->cltt_ = now_ + 500;
+
+ // lease2: addr2, no id, old user context: updated on check > NONE.
+ lease = leases4[2];
+ ASSERT_TRUE(lease);
+ lease->setContext(user_context_old);
+
+ // lease3: addr3, ids, lower case old user context: always updated.
+ lease = leases4[3];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_lower);
+
+ // Lease4: addr4, ids, bad (list) user context: updated on check > NONE.
+ lease = leases4[4];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_list);
+
+ // Lease5: addr5, other ids: always updated.
+ lease = leases4[5];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id2;
+ lease->remote_id_ = remote_id2;
+ lease->setContext(user_context);
+
+ // Lease6: addr6, ids, bad sub-options: updated on check > FIX.
+ lease = leases4[6];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_badsub);
+
+ // Lease7: addr7, ids, extra in ISC: updated on check > STRICT.
+ lease = leases4[7];
+ ASSERT_TRUE(lease);
+ lease->relay_id_ = relay_id;
+ lease->remote_id_ = remote_id;
+ lease->setContext(user_context_extra);
+
+ // Add leases.
+ for (size_t i = 0; i < leases4.size(); ++i) {
+ EXPECT_TRUE(lease_mgr_->addLease(leases4[i]));
+ }
+
+ // Set extended info consistency.
+ CfgMgr::instance().getCurrentCfg()->getConsistency()->
+ setExtendedInfoSanityCheck(check);
+
+ size_t updated;
+ ASSERT_NO_THROW(updated = lease_mgr_->upgradeExtendedInfo4(page_size));
+
+ // Verify result.
+ switch (check) {
+ case CfgConsistency::EXTENDED_INFO_CHECK_NONE:
+ // Updated leases: 3, 5.
+ EXPECT_EQ(updated, 2);
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_FIX:
+ // Updated leases: 2, 3, 4, 5.
+ EXPECT_EQ(updated, 4);
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_STRICT:
+ // Updated leases: 2, 3, 4, 5, 6.
+ EXPECT_EQ(updated, 5);
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC:
+ default:
+ // Updated leases: 2, 3, 4, 5, 6, 7.
+ EXPECT_EQ(updated, 6);
+ break;
+ }
+
+ // Verify stored leases.
+ Lease4Collection got;
+ // Use the page version as it returns leases in order.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases4(zero, LeasePageSize(100)));
+ ASSERT_EQ(leases4.size(), got.size());
+
+ // Check lease0.
+ lease = got[0];
+ EXPECT_EQ(*lease, *leases4[0]);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+
+ // Check lease1.
+ lease = got[1];
+ EXPECT_EQ(*lease, *leases4[1]);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+
+ // Check lease2.
+ lease = got[2];
+ Lease4Ptr expected2(new Lease4(*leases4[2]));
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) {
+ EXPECT_EQ(*lease, *expected2);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ } else {
+ expected2->setContext(user_context);
+ expected2->relay_id_ = relay_id;
+ expected2->remote_id_ = remote_id;
+ EXPECT_EQ(*lease, *expected2);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ }
+
+ // Check lease3.
+ lease = got[3];
+ Lease4Ptr expected3(new Lease4(*leases4[3]));
+ expected3->relay_id_.clear();
+ expected3->remote_id_.clear();
+ EXPECT_EQ(*lease, *expected3);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+
+ // Check lease4.
+ lease = got[4];
+ Lease4Ptr expected4(new Lease4(*leases4[4]));
+ if (check == CfgConsistency::EXTENDED_INFO_CHECK_NONE) {
+ EXPECT_EQ(*lease, *expected4);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ } else {
+ expected4->relay_id_.clear();
+ expected4->remote_id_.clear();
+ expected4->setContext(ElementPtr());
+ EXPECT_EQ(*lease, *expected4);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ }
+
+ // Check lease5.
+ lease = got[5];
+ Lease4Ptr expected5(new Lease4(*leases4[5]));
+ expected5->relay_id_ = relay_id;
+ expected5->remote_id_ = remote_id;
+ EXPECT_EQ(*lease, *expected5);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+
+ // Check lease6.
+ lease = got[6];
+ Lease4Ptr expected6(new Lease4(*leases4[6]));
+ if ((check != CfgConsistency::EXTENDED_INFO_CHECK_STRICT) &&
+ (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC)) {
+ EXPECT_EQ(*lease, *expected6);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ } else {
+ expected6->relay_id_.clear();
+ expected6->remote_id_.clear();
+ expected6->setContext(ElementPtr());
+ EXPECT_EQ(*lease, *expected6);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ }
+
+ // Check lease7.
+ lease = got[7];
+ Lease4Ptr expected7(new Lease4(*leases4[7]));
+ if (check != CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC) {
+ EXPECT_EQ(*lease, *expected7);
+ EXPECT_EQ(relay_id, lease->relay_id_);
+ EXPECT_EQ(remote_id, lease->remote_id_);
+ } else {
+ expected7->relay_id_.clear();
+ expected7->remote_id_.clear();
+ expected7->setContext(ElementPtr());
+ EXPECT_EQ(*lease, *expected7);
+ EXPECT_TRUE(lease->relay_id_.empty());
+ EXPECT_TRUE(lease->remote_id_.empty());
+ }
+
+ // Verify getLeases4ByRelayId.
+ Lease4Collection by_relay_id;
+ EXPECT_NO_THROW(by_relay_id =
+ lease_mgr_->getLeases4ByRelayId(relay_id,
+ zero,
+ LeasePageSize(100)));
+ switch (check) {
+ case CfgConsistency::EXTENDED_INFO_CHECK_NONE:
+ // Got leases: 0, 1, 4, 5, 6, 7.
+ EXPECT_EQ(6, by_relay_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_FIX:
+ // Got leases: 0, 1, 2, 5, 6, 7.
+ EXPECT_EQ(6, by_relay_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_STRICT:
+ // Got leases: 0, 1, 2, 4, 7.
+ EXPECT_EQ(5, by_relay_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC:
+ default:
+ // Got leases: 0, 1, 2, 4.
+ EXPECT_EQ(4, by_relay_id.size());
+ break;
+ }
+
+ // Verify getLeases4ByRemoteId.
+ Lease4Collection by_remote_id;
+ EXPECT_NO_THROW(by_remote_id =
+ lease_mgr_->getLeases4ByRemoteId(remote_id,
+ zero,
+ LeasePageSize(100)));
+ switch (check) {
+ case CfgConsistency::EXTENDED_INFO_CHECK_NONE:
+ // Got leases: 0, 1, 4, 5, 6, 7.
+ EXPECT_EQ(6, by_remote_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_FIX:
+ // Got leases: 0, 1, 2, 5, 6, 7.
+ EXPECT_EQ(6, by_remote_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_STRICT:
+ // Got leases: 0, 1, 2, 4, 7.
+ EXPECT_EQ(5, by_remote_id.size());
+ break;
+
+ case CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC:
+ default:
+ // Got leases: 0, 1, 2, 4.
+ EXPECT_EQ(4, by_remote_id.size());
+ break;
+ }
+
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4None) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_NONE,
+ LeasePageSize(100));
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4Fix) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(100));
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4Strict) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ LeasePageSize(100));
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4Pedantic) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ LeasePageSize(100));
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4_10) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(10));
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4_5) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(5));
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4_2) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(2));
+}
+
+TEST_F(PgSqlExtendedInfoTest, upgradeExtendedInfo4_1) {
+ testUpgradeExtendedInfo4(CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ LeasePageSize(1));
+}
+
+/// @brief Verifies that the lease manager can add the v6 leases.
+void
+PgSqlExtendedInfoTest::testInitLease6() {
+ initLease6();
+ EXPECT_EQ(8, leases6.size());
+ Lease6Collection got;
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6());
+ ASSERT_EQ(leases6.size(), got.size());
+ auto compare = [](Lease6Ptr const& left, Lease6Ptr const& right) {
+ return (left->addr_ < right->addr_);
+ };
+ std::sort(got.begin(), got.end(), compare);
+ for (size_t i = 0; i < leases6.size(); ++i) {
+ ConstElementPtr expected = leases6[i]->toElement();
+ LeasePtr lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_TRUE(expected->equals(*lease->toElement()))
+ << "expected: " << expected->str() << "\n"
+ << "got: " << lease->toElement()->str() << "\n";
+ }
+}
+
+TEST_F(PgSqlExtendedInfoTest, initLease6) {
+ testInitLease6();
+}
+
+TEST_F(PgSqlExtendedInfoTest, initLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testInitLease6();
+}
+
+/// @brief Verifies that getLeases6ByLink works as expected.
+void
+PgSqlExtendedInfoTest::testGetLeases6ByLink() {
+ // Lease manager is created with empty tables.
+ initLease6();
+
+ // Create parameter values.
+ IOAddress link_addr(ADDRESS6[4]);
+ IOAddress other_link_addr("2001:db8:1::4");
+ IOAddress zero = IOAddress::IPV6_ZERO_ADDRESS();
+
+ Lease6Collection got;
+ // Other link: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(other_link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+ EXPECT_EQ(0, got.size());
+
+ // Link: 8 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ zero,
+ LeasePageSize(10)));
+
+ ASSERT_EQ(8, got.size());
+ Lease6Ptr lease;
+ for (size_t i = 0; i < 8; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i]), lease->addr_);
+ }
+
+ // Link: initial partial: 4 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ zero,
+ LeasePageSize(4)));
+ ASSERT_EQ(4, got.size());
+ for (size_t i = 0; i < 4; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i]), lease->addr_);
+ }
+
+ // Link: next partial: 4 entries.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(4)));
+ ASSERT_EQ(4, got.size());
+ for (size_t i = 0; i < 4; ++i) {
+ lease = got[i];
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(IOAddress(ADDRESS6[i + 4]), lease->addr_);
+ }
+
+ // Link: further partial: nothing.
+ EXPECT_NO_THROW(got = lease_mgr_->getLeases6ByLink(link_addr,
+ 64,
+ lease->addr_,
+ LeasePageSize(4)));
+ EXPECT_EQ(0, got.size());
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases6ByLink) {
+ testGetLeases6ByLink();
+}
+
+TEST_F(PgSqlExtendedInfoTest, getLeases6ByLinkMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6ByLink();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
new file mode 100644
index 0000000..17a0087
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc
@@ -0,0 +1,1299 @@
+// Copyright (C) 2014-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/pgsql_lease_mgr.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <dhcpsrv/testutils/pgsql_generic_backend_unittest.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <exceptions/exceptions.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+#include <util/multi_threading_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::test;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+
+/// @brief Test fixture class for testing PostgreSQL Lease Manager
+///
+/// Opens the database prior to each test and closes it afterwards.
+/// All pending transactions are deleted prior to closure.
+
+class PgSqlLeaseMgrTest : public GenericLeaseMgrTest {
+public:
+ /// @brief Clears the database and opens connection to it.
+ void initializeTest() {
+ // Ensure we have the proper schema with no transient data.
+ createPgSQLSchema();
+
+ // Connect to the database
+ try {
+ LeaseMgrFactory::create(validPgSQLConnectionString());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the PostgreSQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ lmptr_ = &(LeaseMgrFactory::instance());
+
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destroys the LM and the schema.
+ void destroyTest() {
+ LeaseMgrFactory::destroy();
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroyPgSQLSchema();
+
+ // Disable Multi-Threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Constructor
+ ///
+ /// Deletes everything from the database and opens it.
+ PgSqlLeaseMgrTest() {
+ initializeTest();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Rolls back all pending transactions. The deletion of lmptr_ will close
+ /// the database. Then reopen it and delete everything created by the test.
+ virtual ~PgSqlLeaseMgrTest() {
+ destroyTest();
+ }
+
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-open it. Anything committed should be
+ /// visible.
+ ///
+ /// Parameter is ignored for PostgreSQL backend as the v4 and v6 leases share
+ /// the same database.
+ void reopen(Universe) {
+ LeaseMgrFactory::destroy();
+ LeaseMgrFactory::create(validPgSQLConnectionString());
+ lmptr_ = &(LeaseMgrFactory::instance());
+ }
+};
+
+/// @brief Check that database can be opened
+///
+/// This test checks if the PgSqlLeaseMgr can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// PgSqlLeaseMgr test fixture set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
+TEST(PgSqlOpenTest, OpenDatabase) {
+ // Explicitly disable Multi-Threading.
+ MultiThreadingMgr::instance().setMode(false);
+
+ // Schema needs to be created for the test to work.
+ createPgSQLSchema();
+
+ // Check that lease manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ LeaseMgrFactory::create(validPgSQLConnectionString());
+ EXPECT_NO_THROW((void)LeaseMgrFactory::instance());
+ LeaseMgrFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the PostgreSQL tests will run correctly.\n";
+ }
+
+ // Check that lease manager opens the database correctly with a longer
+ // timeout. If it fails, print the error message.
+ try {
+ string connection_string = validPgSQLConnectionString() + string(" ") +
+ string(VALID_TIMEOUT);
+ LeaseMgrFactory::create(connection_string);
+ EXPECT_NO_THROW((void) LeaseMgrFactory::instance());
+ LeaseMgrFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the PostgreSQL tests will run correctly.\n";
+ }
+
+ // Check that attempting to get an instance of the lease manager when
+ // none is set throws an exception.
+ EXPECT_THROW(LeaseMgrFactory::instance(), NoLeaseManager);
+
+ // Check that wrong specification of backend throws an exception.
+ // (This is really a check on LeaseMgrFactory, but is convenient to
+ // perform here.)
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ InvalidParameter);
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ InvalidType);
+
+ // Check that invalid login data causes an exception.
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
+ DbOpenError);
+
+ // This test might fail if 'auth-method' in PostgresSQL host-based authentication
+ // file (/var/lib/pgsql/9.4/data/pg_hba.conf) is set to 'trust',
+ // which allows logging without password. 'Auth-method' should be changed to 'password'.
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)),
+ DbOpenError);
+
+ // Check for invalid timeouts
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
+ DbInvalidTimeout);
+
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
+ DbInvalidTimeout);
+
+ // Check for missing parameters
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, NULL, VALID_HOST, VALID_USER, VALID_PASSWORD)),
+ NoDatabaseName);
+
+ // Check for SSL/TLS support.
+#ifdef HAVE_PGSQL_SSL
+ EXPECT_NO_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ 0, 0, 0, 0, VALID_CA)));
+#else
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD,
+ 0, 0, 0, 0, VALID_CA)), DbOpenError);
+#endif
+
+ // Check for extended info tables.
+ const char* EX_INFO = "extended-info-tables=true";
+ EXPECT_THROW(LeaseMgrFactory::create(connectionString(
+ PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, EX_INFO)),
+ NotImplemented);
+
+ // Tidy up after the test
+ destroyPgSQLSchema();
+ LeaseMgrFactory::destroy();
+}
+
+/// @brief Check that database can be opened with Multi-Threading
+TEST(PgSqlOpenTest, OpenDatabaseMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+
+ // Schema needs to be created for the test to work.
+ createPgSQLSchema();
+
+ // Check that lease manager opens the database correctly and tidy up. If it
+ // fails, print the error message.
+ try {
+ LeaseMgrFactory::create(validPgSQLConnectionString());
+ EXPECT_NO_THROW((void)LeaseMgrFactory::instance());
+ LeaseMgrFactory::destroy();
+ } catch (const isc::Exception& ex) {
+ FAIL() << "*** ERROR: unable to open database, reason:\n"
+ << " " << ex.what() << "\n"
+ << "*** The test environment is broken and must be fixed\n"
+ << "*** before the PostgreSQL tests will run correctly.\n";
+ }
+
+ // Tidy up after the test
+ destroyPgSQLSchema();
+ LeaseMgrFactory::destroy();
+}
+
+/// @brief Check the getType() method
+///
+/// getType() returns a string giving the type of the backend, which should
+/// always be "postgresql".
+TEST_F(PgSqlLeaseMgrTest, getType) {
+ EXPECT_EQ(std::string("postgresql"), lmptr_->getType());
+}
+
+/// @brief Check getName() returns correct database name
+TEST_F(PgSqlLeaseMgrTest, getName) {
+ EXPECT_EQ(std::string("keatest"), lmptr_->getName());
+}
+
+/// @brief Check that getVersion() returns the expected version
+TEST_F(PgSqlLeaseMgrTest, checkVersion) {
+ // Check version
+ pair<uint32_t, uint32_t> version;
+ ASSERT_NO_THROW(version = lmptr_->getVersion());
+ EXPECT_EQ(PGSQL_SCHEMA_VERSION_MAJOR, version.first);
+ EXPECT_EQ(PGSQL_SCHEMA_VERSION_MINOR, version.second);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// LEASE4 /////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4 (by address) and deleteLease (with an
+/// IPv4 address) works.
+TEST_F(PgSqlLeaseMgrTest, basicLease4) {
+ testBasicLease4();
+}
+
+/// @brief Basic Lease4 Checks
+TEST_F(PgSqlLeaseMgrTest, basicLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasicLease4();
+}
+
+/// @brief Check that Lease4 code safely handles invalid dates.
+TEST_F(PgSqlLeaseMgrTest, maxDate4) {
+ testMaxDate4();
+}
+
+/// @brief Check that Lease4 code safely handles invalid dates.
+TEST_F(PgSqlLeaseMgrTest, maxDate4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxDate4();
+}
+
+/// @brief checks that infinite lifetimes do not overflow.
+TEST_F(PgSqlLeaseMgrTest, infiniteLifeTime4) {
+ testInfiniteLifeTime4();
+}
+
+/// @brief Lease4 update tests
+///
+/// Checks that we are able to update a lease in the database.
+TEST_F(PgSqlLeaseMgrTest, updateLease4) {
+ testUpdateLease4();
+}
+
+/// @brief Lease4 update tests
+TEST_F(PgSqlLeaseMgrTest, updateLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testUpdateLease4();
+}
+
+/// @brief Lease4 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(PgSqlLeaseMgrTest, concurrentUpdateLease4) {
+ testConcurrentUpdateLease4();
+}
+
+/// @brief Lease4 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(PgSqlLeaseMgrTest, concurrentUpdateLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testConcurrentUpdateLease4();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddr1) {
+ testGetLease4HWAddr1();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddr1MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddr1();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddr2) {
+ testGetLease4HWAddr2();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddr2MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddr2();
+}
+
+/// @brief Get lease4 by hardware address (2)
+///
+/// Check that the system can cope with getting a hardware address of
+/// any size.
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddrSize) {
+ testGetLease4HWAddrSize();
+}
+
+/// @brief Get lease4 by hardware address (2)
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddrSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddrSize();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of hardware address and subnet ID
+TEST_F(PgSqlLeaseMgrTest, getLease4HwaddrSubnetId) {
+ testGetLease4HWAddrSubnetId();
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+TEST_F(PgSqlLeaseMgrTest, getLease4HwaddrSubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddrSubnetId();
+}
+
+/// @brief Get lease4 by hardware address and subnet ID (2)
+///
+/// Check that the system can cope with getting a hardware address of
+/// any size.
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddrSubnetIdSize) {
+ testGetLease4HWAddrSubnetIdSize();
+}
+
+/// @brief Get lease4 by hardware address and subnet ID (2)
+TEST_F(PgSqlLeaseMgrTest, getLease4HWAddrSubnetIdSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4HWAddrSubnetIdSize();
+}
+
+/// @brief This test was derived from memfile.
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientId) {
+ testGetLease4ClientId();
+}
+
+/// @brief This test was derived from memfile.
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientId();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// the Client ID.
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientId2) {
+ testGetLease4ClientId2();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientId2MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientId2();
+}
+
+/// @brief Get Lease4 by client ID (2)
+///
+/// Check that the system can cope with a client ID of any size.
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientIdSize) {
+ testGetLease4ClientIdSize();
+}
+
+/// @brief Get Lease4 by client ID (2)
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientIdSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientIdSize();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of client and subnet IDs.
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientIdSubnetId) {
+ testGetLease4ClientIdSubnetId();
+}
+
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+TEST_F(PgSqlLeaseMgrTest, getLease4ClientIdSubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease4ClientIdSubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases for a specified subnet id are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases4SubnetId) {
+ testGetLeases4SubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases for a specified subnet id are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases4SubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4SubnetId();
+}
+
+/// @brief This test checks that all IPv4 leases with a specified hostname are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases4Hostname) {
+ testGetLeases4Hostname();
+}
+
+/// @brief This test checks that all IPv4 leases with a specified hostname are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases4HostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4Hostname();
+}
+
+/// @brief This test checks that all IPv4 leases are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases4) {
+ testGetLeases4();
+}
+
+/// @brief This test checks that all IPv4 leases are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4();
+}
+
+/// @brief Test that a range of IPv4 leases is returned with paging.
+TEST_F(PgSqlLeaseMgrTest, getLeases4Paged) {
+ testGetLeases4Paged();
+}
+
+/// @brief Test that a range of IPv4 leases is returned with paging.
+TEST_F(PgSqlLeaseMgrTest, getLeases4PagedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases4Paged();
+}
+
+/// @brief This test checks that all IPv6 leases for a specified subnet id are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases6SubnetId) {
+ testGetLeases6SubnetId();
+}
+
+/// @brief This test checks that all IPv6 leases for a specified subnet id are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases6SubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6SubnetId();
+}
+
+/// @brief This test checks that all IPv6 leases with a specified hostname are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases6Hostname) {
+ testGetLeases6Hostname();
+}
+
+/// @brief This test checks that all IPv6 leases with a specified hostname are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases6HostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6Hostname();
+}
+
+/// @brief This test checks that all IPv6 leases are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases6) {
+ testGetLeases6();
+}
+
+/// @brief This test checks that all IPv6 leases are returned.
+TEST_F(PgSqlLeaseMgrTest, getLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6();
+}
+
+/// @brief Test that a range of IPv6 leases is returned with paging.
+TEST_F(PgSqlLeaseMgrTest, getLeases6Paged) {
+ testGetLeases6Paged();
+}
+
+/// @brief Test that a range of IPv6 leases is returned with paging.
+TEST_F(PgSqlLeaseMgrTest, getLeases6PagedMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6Paged();
+}
+
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+/// updateLease4() and deleteLease can handle NULL client-id.
+/// (client-id is optional and may not be present)
+TEST_F(PgSqlLeaseMgrTest, lease4NullClientId) {
+ testLease4NullClientId();
+}
+
+/// @brief Basic Lease4 Checks
+TEST_F(PgSqlLeaseMgrTest, lease4NullClientIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease4NullClientId();
+}
+
+/// @brief Verify that too long hostname for Lease4 is not accepted.
+///
+/// Checks that the it is not possible to create a lease when the hostname
+/// length exceeds 255 characters.
+TEST_F(PgSqlLeaseMgrTest, lease4InvalidHostname) {
+ testLease4InvalidHostname();
+}
+
+/// @brief Verify that too long hostname for Lease4 is not accepted.
+TEST_F(PgSqlLeaseMgrTest, lease4InvalidHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease4InvalidHostname();
+}
+
+/// @brief Check that the expired DHCPv4 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(PgSqlLeaseMgrTest, getExpiredLeases4) {
+ testGetExpiredLeases4();
+}
+
+/// @brief Check that the expired DHCPv4 leases can be retrieved.
+TEST_F(PgSqlLeaseMgrTest, getExpiredLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetExpiredLeases4();
+}
+
+/// @brief Checks that DHCPv4 leases with infinite valid lifetime
+/// will never expire.
+TEST_F(PgSqlLeaseMgrTest, infiniteAreNotExpired4) {
+ testInfiniteAreNotExpired4();
+}
+
+/// @brief Check that expired reclaimed DHCPv4 leases are removed.
+TEST_F(PgSqlLeaseMgrTest, deleteExpiredReclaimedLeases4) {
+ testDeleteExpiredReclaimedLeases4();
+}
+
+/// @brief Check that expired reclaimed DHCPv4 leases are removed.
+TEST_F(PgSqlLeaseMgrTest, deleteExpiredReclaimedLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteExpiredReclaimedLeases4();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// LEASE6 /////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+/// @brief Test checks whether simple add, get and delete operations
+/// are possible on Lease6
+TEST_F(PgSqlLeaseMgrTest, testAddGetDelete6) {
+ testAddGetDelete6();
+}
+
+/// @brief Test checks whether simple add, get and delete operations
+/// are possible on Lease6
+TEST_F(PgSqlLeaseMgrTest, testAddGetDelete6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testAddGetDelete6();
+}
+
+/// @brief Basic Lease6 Checks
+///
+/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
+/// IPv6 address) works.
+TEST_F(PgSqlLeaseMgrTest, basicLease6) {
+ testBasicLease6();
+}
+
+/// @brief Basic Lease6 Checks
+TEST_F(PgSqlLeaseMgrTest, basicLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testBasicLease6();
+}
+
+/// @brief Check that Lease6 code safely handles invalid dates.
+TEST_F(PgSqlLeaseMgrTest, maxDate6) {
+ testMaxDate6();
+}
+
+/// @brief Check that Lease6 code safely handles invalid dates.
+TEST_F(PgSqlLeaseMgrTest, maxDate6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testMaxDate6();
+}
+
+/// @brief checks that infinite lifetimes do not overflow.
+TEST_F(PgSqlLeaseMgrTest, infiniteLifeTime6) {
+ testInfiniteLifeTime6();
+}
+
+/// @brief Verify that too long hostname for Lease6 is not accepted.
+///
+/// Checks that the it is not possible to create a lease when the hostname
+/// length exceeds 255 characters.
+TEST_F(PgSqlLeaseMgrTest, lease6InvalidHostname) {
+ testLease6InvalidHostname();
+}
+
+/// @brief Verify that too long hostname for Lease6 is not accepted.
+TEST_F(PgSqlLeaseMgrTest, lease6InvalidHostnameMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6InvalidHostname();
+}
+
+/// @brief Verify that large IAID values work correctly.
+///
+/// Adds lease with a large IAID to the database and verifies it can
+/// fetched correctly.
+TEST_F(PgSqlLeaseMgrTest, leases6LargeIaidCheck) {
+ testLease6LargeIaidCheck();
+}
+
+/// @brief Verify that large IAID values work correctly.
+TEST_F(PgSqlLeaseMgrTest, leases6LargeIaidCheckMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6LargeIaidCheck();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DUID and IAID.
+TEST_F(PgSqlLeaseMgrTest, getLeases6DuidIaid) {
+ testGetLeases6DuidIaid();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID
+TEST_F(PgSqlLeaseMgrTest, getLeases6DuidIaidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6DuidIaid();
+}
+
+/// @brief Check that the system can cope with a DUID of allowed size.
+TEST_F(PgSqlLeaseMgrTest, getLeases6DuidSize) {
+ testGetLeases6DuidSize();
+}
+
+/// @brief Check that the system can cope with a DUID of allowed size.
+TEST_F(PgSqlLeaseMgrTest, getLeases6DuidSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6DuidSize();
+}
+
+/// @brief Check that getLease6 methods discriminate by lease type.
+///
+/// Adds six leases, two per lease type all with the same duid and iad but
+/// with alternating subnet_ids.
+/// It then verifies that all of getLeases6() method variants correctly
+/// discriminate between the leases based on lease type alone.
+TEST_F(PgSqlLeaseMgrTest, lease6LeaseTypeCheck) {
+ testLease6LeaseTypeCheck();
+}
+
+/// @brief Check that getLease6 methods discriminate by lease type.
+TEST_F(PgSqlLeaseMgrTest, lease6LeaseTypeCheckMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6LeaseTypeCheck();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DIUID and IAID.
+TEST_F(PgSqlLeaseMgrTest, getLease6DuidIaidSubnetId) {
+ testGetLease6DuidIaidSubnetId();
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+TEST_F(PgSqlLeaseMgrTest, getLease6DuidIaidSubnetIdMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease6DuidIaidSubnetId();
+}
+
+/// @brief Test checks that getLease6() works with different DUID sizes
+TEST_F(PgSqlLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
+ testGetLease6DuidIaidSubnetIdSize();
+}
+
+/// @brief Test checks that getLease6() works with different DUID sizes
+TEST_F(PgSqlLeaseMgrTest, getLease6DuidIaidSubnetIdSizeMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLease6DuidIaidSubnetIdSize();
+}
+
+/// @brief check leases could be retrieved by DUID
+///
+/// Create leases, add them to backend and verify if it can be queried
+/// using DUID index
+TEST_F(PgSqlLeaseMgrTest, getLeases6Duid) {
+ testGetLeases6Duid();
+}
+
+/// @brief check leases could be retrieved by DUID
+TEST_F(PgSqlLeaseMgrTest, getLeases6DuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetLeases6Duid();
+}
+
+/// @brief Lease6 update tests
+///
+/// Checks that we are able to update a lease in the database.
+TEST_F(PgSqlLeaseMgrTest, updateLease6) {
+ testUpdateLease6();
+}
+
+/// @brief Lease6 update tests
+TEST_F(PgSqlLeaseMgrTest, updateLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testUpdateLease6();
+}
+
+/// @brief Lease6 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(PgSqlLeaseMgrTest, concurrentUpdateLease6) {
+ testConcurrentUpdateLease6();
+}
+
+/// @brief Lease6 concurrent update tests
+///
+/// Checks that we are not able to concurrently update a lease in the database.
+TEST_F(PgSqlLeaseMgrTest, concurrentUpdateLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testConcurrentUpdateLease6();
+}
+
+/// @brief DHCPv4 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(PgSqlLeaseMgrTest, testRecreateLease4) {
+ testRecreateLease4();
+}
+
+/// @brief DHCPv4 Lease recreation tests
+TEST_F(PgSqlLeaseMgrTest, testRecreateLease4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecreateLease4();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+///
+/// Checks that the lease can be created, deleted and recreated with
+/// different parameters. It also checks that the re-created lease is
+/// correctly stored in the lease database.
+TEST_F(PgSqlLeaseMgrTest, testRecreateLease6) {
+ testRecreateLease6();
+}
+
+/// @brief DHCPv6 Lease recreation tests
+TEST_F(PgSqlLeaseMgrTest, testRecreateLease6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecreateLease6();
+}
+
+/// @brief Checks that null DUID is not allowed.
+TEST_F(PgSqlLeaseMgrTest, nullDuid) {
+ testNullDuid();
+}
+
+/// @brief Checks that null DUID is not allowed.
+TEST_F(PgSqlLeaseMgrTest, nullDuidMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNullDuid();
+}
+
+/// @brief Tests whether PostgreSQL can store and retrieve hardware addresses
+TEST_F(PgSqlLeaseMgrTest, testLease6Mac) {
+ testLease6MAC();
+}
+
+/// @brief Tests whether PostgreSQL can store and retrieve hardware addresses
+TEST_F(PgSqlLeaseMgrTest, testLease6MacMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6MAC();
+}
+
+/// @brief Tests whether PostgreSQL can store and retrieve hardware addresses
+TEST_F(PgSqlLeaseMgrTest, testLease6HWTypeAndSource) {
+ testLease6HWTypeAndSource();
+}
+
+/// @brief Tests whether PostgreSQL can store and retrieve hardware addresses
+TEST_F(PgSqlLeaseMgrTest, testLease6HWTypeAndSourceMultiThreading) {
+ MultiThreadingTest mt(true);
+ testLease6HWTypeAndSource();
+}
+
+/// @brief Check that the expired DHCPv6 leases can be retrieved.
+///
+/// This test adds a number of leases to the lease database and marks
+/// some of them as expired. Then it queries for expired leases and checks
+/// whether only expired leases are returned, and that they are returned in
+/// the order from most to least expired. It also checks that the lease
+/// which is marked as 'reclaimed' is not returned.
+TEST_F(PgSqlLeaseMgrTest, getExpiredLeases6) {
+ testGetExpiredLeases6();
+}
+
+/// @brief Check that the expired DHCPv6 leases can be retrieved.
+TEST_F(PgSqlLeaseMgrTest, getExpiredLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testGetExpiredLeases6();
+}
+
+/// @brief Checks that DHCPv6 leases with infinite valid lifetime
+/// will never expire.
+TEST_F(PgSqlLeaseMgrTest, infiniteAreNotExpired6) {
+ testInfiniteAreNotExpired6();
+}
+
+/// @brief Check that expired reclaimed DHCPv6 leases are removed.
+TEST_F(PgSqlLeaseMgrTest, deleteExpiredReclaimedLeases6) {
+ testDeleteExpiredReclaimedLeases6();
+}
+
+/// @brief Check that expired reclaimed DHCPv6 leases are removed.
+TEST_F(PgSqlLeaseMgrTest, deleteExpiredReclaimedLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testDeleteExpiredReclaimedLeases6();
+}
+
+/// @brief Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(PgSqlLeaseMgrTest, recountLeaseStats4) {
+ testRecountLeaseStats4();
+}
+
+/// @brief Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(PgSqlLeaseMgrTest, recountLeaseStats4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecountLeaseStats4();
+}
+
+/// @brief Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(PgSqlLeaseMgrTest, recountLeaseStats6) {
+ testRecountLeaseStats6();
+}
+
+/// @brief Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(PgSqlLeaseMgrTest, recountLeaseStats6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testRecountLeaseStats6();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases4) {
+ testWipeLeases4();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testWipeLeases4();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases6) {
+ testWipeLeases6();
+}
+
+/// @brief Tests that leases from specific subnet can be removed.
+TEST_F(PgSqlLeaseMgrTest, DISABLED_wipeLeases6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testWipeLeases6();
+}
+
+/// @brief Test fixture class for validating @c LeaseMgr using
+/// PostgreSQL as back end and PostgreSQL connectivity loss.
+class PgSqlLeaseMgrDbLostCallbackTest : public LeaseMgrDbLostCallbackTest {
+public:
+ virtual void destroySchema() {
+ destroyPgSQLSchema();
+ }
+
+ virtual void createSchema() {
+ createPgSQLSchema();
+ }
+
+ virtual std::string validConnectString() {
+ return (validPgSQLConnectionString());
+ }
+
+ virtual std::string invalidConnectString() {
+ return (connectionString(PGSQL_VALID_TYPE, INVALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+ }
+};
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testNoCallbackOnOpenFailure) {
+ MultiThreadingTest mt(false);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that db lost callback is not invoked on an open failure
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testNoCallbackOnOpenFailureMultiThreading) {
+ MultiThreadingTest mt(true);
+ testNoCallbackOnOpenFailure();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndRecoveredAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndRecoveredAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallback) {
+ MultiThreadingTest mt(false);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+/// @brief Verifies that loss of connectivity to PostgreSQL is handled correctly.
+TEST_F(PgSqlLeaseMgrDbLostCallbackTest, testDbLostAndFailedAfterTimeoutCallbackMultiThreading) {
+ MultiThreadingTest mt(true);
+ testDbLostAndFailedAfterTimeoutCallback();
+}
+
+/// @brief Tests v4 lease stats query variants.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQuery4) {
+ testLeaseStatsQuery4();
+}
+
+/// @brief Tests v4 lease stats query variants.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQuery4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQuery4();
+}
+
+/// @brief Tests v6 lease stats query variants.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQuery6) {
+ testLeaseStatsQuery6();
+}
+
+/// @brief Tests v6 lease stats query variants.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQuery6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQuery6();
+}
+
+/// @brief Tests v4 lease stats to be attributed to the wrong subnet.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQueryAttribution4) {
+ testLeaseStatsQueryAttribution4();
+}
+
+/// @brief Tests v4 lease stats to be attributed to the wrong subnet.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQueryAttribution4MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQueryAttribution4();
+}
+
+/// @brief Tests v6 lease stats to be attributed to the wrong subnet.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQueryAttribution6) {
+ testLeaseStatsQueryAttribution6();
+}
+
+/// @brief Tests v6 lease stats to be attributed to the wrong subnet.
+TEST_F(PgSqlLeaseMgrTest, leaseStatsQueryAttribution6MultiThreading) {
+ MultiThreadingTest mt(true);
+ testLeaseStatsQueryAttribution6();
+}
+
+/// @brief This test is a basic check for the generic backend test class,
+/// rather than any production code check.
+TEST_F(PgSqlGenericBackendTest, leaseCount) {
+
+ // Create database connection parameter list
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+
+ // Create and open the database connection
+ PgSqlConnection conn(params);
+ conn.openDatabase();
+
+ // Check that the countRows is working. It's used extensively in other
+ // tests, so basic check is enough here.
+ EXPECT_EQ(0, countRows(conn, "lease4"));
+}
+
+// Verifies that v4 class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(PgSqlLeaseMgrTest, classLeaseCount4) {
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ testClassLeaseCount4();
+}
+
+// Verifies that v6 IA_NA class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(PgSqlLeaseMgrTest, classLeaseCount6_NA) {
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ testClassLeaseCount6(Lease::TYPE_NA);
+}
+
+// Verifies that v6 IA_PD class lease counts are correctly adjusted
+// when leases have class lists.
+TEST_F(PgSqlLeaseMgrTest, classLeaseCount6_PD) {
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ std::cout << "Skipped test because of lack of JSON support in the database." << std::endl;
+ return;
+ }
+
+ testClassLeaseCount6(Lease::TYPE_PD);
+}
+
+/// @brief Checks that no exceptions are thrown when inquiring about JSON
+/// support and prints an informative message.
+TEST_F(PgSqlLeaseMgrTest, isJsonSupported) {
+ bool json_supported;
+ ASSERT_NO_THROW_LOG(json_supported = LeaseMgrFactory::instance().isJsonSupported());
+ std::cout << "JSON support is " << (json_supported ? "" : "not ") <<
+ "enabled in the database." << std::endl;
+}
+
+/// @brief Checks that a null user context allows allocation.
+TEST_F(PgSqlLeaseMgrTest, checkLimitsNull) {
+ std::string text;
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits4(nullptr));
+ EXPECT_TRUE(text.empty());
+ ASSERT_NO_THROW_LOG(text = LeaseMgrFactory::instance().checkLimits6(nullptr));
+ EXPECT_TRUE(text.empty());
+}
+
+/// @brief Checks a few v4 limit checking scenarios.
+TEST_F(PgSqlLeaseMgrTest, checkLimits4) {
+ // Limit checking should be precluded at reconfiguration time on systems
+ // that don't have JSON support in the database. It's fine if it throws.
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ ASSERT_THROW_MSG(LeaseMgrFactory::instance().checkLimits4(
+ isc::data::Element::createMap()), isc::db::DbOperationError,
+ "Statement exec failed for: check_lease4_limits, status: 7sqlstate:[ 42883 ], "
+ "reason: ERROR: operator does not exist: json -> unknown\n"
+ "LINE 1: ...* FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)->'ISC'->'...\n"
+ " ^\n"
+ "HINT: No operator matches the given name and argument type(s). "
+ "You might need to add explicit type casts.\n"
+ "QUERY: SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)"
+ "->'ISC'->'limits'->'client-classes')\n"
+ "CONTEXT: PL/pgSQL function checklease4limits(text) line 10 at FOR over SELECT rows\n");
+ return;
+ }
+
+ // The rest of the checks are only for databases with JSON support.
+ testLeaseLimits4();
+}
+
+/// @brief Checks a few v6 limit checking scenarios.
+TEST_F(PgSqlLeaseMgrTest, checkLimits6) {
+ // Limit checking should be precluded at reconfiguration time on systems
+ // that don't have JSON support in the database. It's fine if it throws.
+ if (!LeaseMgrFactory::instance().isJsonSupported()) {
+ ASSERT_THROW_MSG(LeaseMgrFactory::instance().checkLimits6(
+ isc::data::Element::createMap()), isc::db::DbOperationError,
+ "Statement exec failed for: check_lease6_limits, status: 7sqlstate:[ 42883 ], "
+ "reason: ERROR: operator does not exist: json -> unknown\n"
+ "LINE 1: ...* FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)->'ISC'->'...\n"
+ " ^\n"
+ "HINT: No operator matches the given name and argument type(s). "
+ "You might need to add explicit type casts.\n"
+ "QUERY: SELECT * FROM JSON_ARRAY_ELEMENTS(json_cast(user_context)"
+ "->'ISC'->'limits'->'client-classes')\n"
+ "CONTEXT: PL/pgSQL function checklease6limits(text) line 10 at FOR over SELECT rows\n");
+ return;
+ }
+
+ // The rest of the checks are only for databases with JSON support.
+ testLeaseLimits6();
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(PgSqlLeaseMgrTest, trackAddLease4) {
+ testTrackAddLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is added.
+TEST_F(PgSqlLeaseMgrTest, trackAddLease4MultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLease4(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is added.
+TEST_F(PgSqlLeaseMgrTest, trackAddLeaseNA) {
+ testTrackAddLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is added.
+TEST_F(PgSqlLeaseMgrTest, trackAddLeaseNAMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLeaseNA(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is added.
+TEST_F(PgSqlLeaseMgrTest, trackAddLeasePD) {
+ testTrackAddLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is added.
+TEST_F(PgSqlLeaseMgrTest, trackAddLeasePDMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackAddLeasePD(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is updated.
+TEST_F(PgSqlLeaseMgrTest, trackUpdateLease4) {
+ testTrackUpdateLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is updated.
+TEST_F(PgSqlLeaseMgrTest, trackUpdateLease4MultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLease4(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is updated.
+TEST_F(PgSqlLeaseMgrTest, trackUpdateLeaseNA) {
+ testTrackUpdateLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is updated.
+TEST_F(PgSqlLeaseMgrTest, trackUpdateLeaseNAMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLeaseNA(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is updated.
+TEST_F(PgSqlLeaseMgrTest, trackUpdateLeasePD) {
+ testTrackUpdateLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is updated.
+TEST_F(PgSqlLeaseMgrTest, trackUpdateLeasePDMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackUpdateLeasePD(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is deleted.
+TEST_F(PgSqlLeaseMgrTest, trackDeleteLease4) {
+ testTrackDeleteLease4(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv4 lease is deleted.
+TEST_F(PgSqlLeaseMgrTest, trackDeleteLease4MultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLease4(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is deleted.
+TEST_F(PgSqlLeaseMgrTest, trackDeleteLeaseNA) {
+ testTrackDeleteLeaseNA(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 address lease is deleted.
+TEST_F(PgSqlLeaseMgrTest, trackDeleteLeaseNAMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLeaseNA(true);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is deleted.
+TEST_F(PgSqlLeaseMgrTest, trackDeleteLeasePD) {
+ testTrackDeleteLeasePD(false);
+}
+
+/// @brief Checks if the backends call the callbacks when an
+/// IPv6 prefix lease is deleted.
+TEST_F(PgSqlLeaseMgrTest, trackDeleteLeasePDMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testTrackDeleteLeasePD(true);
+}
+
+/// @brief Checks that the lease manager can be recreated and its
+/// registered callbacks preserved, if desired.
+TEST_F(PgSqlLeaseMgrTest, recreateWithCallbacks) {
+ testRecreateWithCallbacks(validPgSQLConnectionString());
+}
+
+/// @brief Checks that the lease manager can be recreated without the
+/// previously registered callbacks.
+TEST_F(PgSqlLeaseMgrTest, recreateWithoutCallbacks) {
+ testRecreateWithoutCallbacks(validPgSQLConnectionString());
+}
+
+TEST_F(PgSqlLeaseMgrTest, bigStats) {
+ testBigStats();
+}
+
+} // namespace
diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc
new file mode 100644
index 0000000..d40ee33
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/pool_unittest.cc
@@ -0,0 +1,676 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/option6_pdexclude.h>
+#include <dhcpsrv/pool.h>
+#include <testutils/test_to_element.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <vector>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace {
+
+TEST(Pool4Test, constructorFirstLast) {
+
+ // let's construct 192.0.2.1-192.0.2.255 pool
+ Pool4 pool1(IOAddress("192.0.2.1"), IOAddress("192.0.2.255"));
+
+ EXPECT_EQ(IOAddress("192.0.2.1"), pool1.getFirstAddress());
+ EXPECT_EQ(IOAddress("192.0.2.255"), pool1.getLastAddress());
+
+ // This is Pool4, IPv6 addresses do not belong here
+ EXPECT_THROW(Pool4(IOAddress("2001:db8::1"),
+ IOAddress("192.168.0.5")), BadValue);
+ EXPECT_THROW(Pool4(IOAddress("192.168.0.2"),
+ IOAddress("2001:db8::1")), BadValue);
+
+ // Should throw. Range should be 192.0.2.1-192.0.2.2, not
+ // the other way around.
+ EXPECT_THROW(Pool4(IOAddress("192.0.2.2"), IOAddress("192.0.2.1")),
+ BadValue);
+}
+
+TEST(Pool4Test, constructorPrefixLen) {
+
+ // let's construct 2001:db8:1::/96 pool
+ Pool4 pool1(IOAddress("192.0.2.0"), 25);
+
+ EXPECT_EQ("192.0.2.0", pool1.getFirstAddress().toText());
+ EXPECT_EQ("192.0.2.127", pool1.getLastAddress().toText());
+
+ // No such thing as /33 prefix
+ EXPECT_THROW(Pool4(IOAddress("192.0.2.1"), 33), BadValue);
+
+ // /0 prefix does not make sense
+ EXPECT_THROW(Pool4(IOAddress("192.0.2.0"), 0), BadValue);
+
+ // This is Pool6, IPv4 addresses do not belong here
+ EXPECT_THROW(Pool4(IOAddress("2001:db8::1"), 20), BadValue);
+}
+
+TEST(Pool4Test, in_range) {
+ Pool4 pool1(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"));
+
+ EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.0")));
+ EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.10")));
+ EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.17")));
+ EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.20")));
+ EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.21")));
+ EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.255")));
+ EXPECT_FALSE(pool1.inRange(IOAddress("255.255.255.255")));
+ EXPECT_FALSE(pool1.inRange(IOAddress("0.0.0.0")));
+}
+
+// Checks if the number of possible leases in range is reported correctly.
+TEST(Pool4Test, leasesCount) {
+ Pool4 pool1(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"));
+ EXPECT_EQ(11, pool1.getCapacity());
+
+ Pool4 pool2(IOAddress("192.0.2.0"), IOAddress("192.0.2.255"));
+ EXPECT_EQ(256, pool2.getCapacity());
+
+ Pool4 pool3(IOAddress("192.168.0.0"), IOAddress("192.168.255.255"));
+ EXPECT_EQ(65536, pool3.getCapacity());
+
+ Pool4 pool4(IOAddress("10.0.0.0"), IOAddress("10.255.255.255"));
+ EXPECT_EQ(16777216, pool4.getCapacity());
+}
+
+// Simple check if toText returns reasonable values
+TEST(Pool4Test, toText) {
+ Pool4 pool1(IOAddress("192.0.2.7"), IOAddress("192.0.2.17"));
+ EXPECT_EQ("type=V4, 192.0.2.7-192.0.2.17", pool1.toText());
+
+ Pool4 pool2(IOAddress("192.0.2.128"), 28);
+ EXPECT_EQ("type=V4, 192.0.2.128-192.0.2.143", pool2.toText());
+
+ Pool4 pool3(IOAddress("192.0.2.0"), IOAddress("192.0.2.127"));
+ EXPECT_EQ("type=V4, 192.0.2.0-192.0.2.127", pool3.toText());
+}
+
+// Simple check if toElement returns reasonable values
+TEST(Pool4Test, toElement) {
+ Pool4 pool1(IOAddress("192.0.2.7"), IOAddress("192.0.2.17"));
+ std::string expected1 = "{"
+ " \"pool\": \"192.0.2.7-192.0.2.17\", "
+ " \"option-data\": [ ] "
+ "}";
+ isc::test::runToElementTest<Pool4>(expected1, pool1);
+
+ Pool4 pool2(IOAddress("192.0.2.128"), 28);
+ std::string expected2 = "{"
+ " \"pool\": \"192.0.2.128/28\", "
+ " \"option-data\": [ ] "
+ "}";
+ isc::test::runToElementTest<Pool4>(expected2, pool2);
+
+ Pool4 pool3(IOAddress("192.0.2.0"), IOAddress("192.0.2.127"));
+ pool3.setID(5);
+ std::string expected3 = "{"
+ " \"pool\": \"192.0.2.0/25\", "
+ " \"option-data\": [ ], "
+ " \"pool-id\": 5 "
+ "}";
+ isc::test::runToElementTest<Pool4>(expected3, pool3);
+}
+
+// This test checks that it is possible to specify pool specific options.
+TEST(Pool4Test, addOptions) {
+ // Create a pool to add options to it.
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false,
+ "dhcp4"));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp4 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false, "isc"));
+ }
+
+ // Get options from the pool and check if all 10 are there.
+ OptionContainerPtr options = pool->getCfgOption()->getAll("dhcp4");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp4 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = pool->getCfgOption()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = pool->getCfgOption()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test checks that handling for user-context is valid.
+TEST(Pool4Test, userContext) {
+ // Create a pool to add options to it.
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+
+ // Context should be empty until explicitly set.
+ EXPECT_FALSE(pool->getContext());
+
+ // When set, should be returned properly.
+ ElementPtr ctx = Element::create("{ \"comment\": \"foo\" }");
+ EXPECT_NO_THROW(pool->setContext(ctx));
+ ASSERT_TRUE(pool->getContext());
+ EXPECT_EQ(ctx->str(), pool->getContext()->str());
+}
+
+// This test checks that handling for client-class is valid.
+TEST(Pool4Test, clientClass) {
+ // Create a pool.
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ // No class restrictions defined, any client should be supported
+ EXPECT_TRUE(pool->getClientClass().empty());
+ EXPECT_TRUE(pool->clientSupported(no_class));
+ EXPECT_TRUE(pool->clientSupported(foo_class));
+ EXPECT_TRUE(pool->clientSupported(bar_class));
+ EXPECT_TRUE(pool->clientSupported(three_classes));
+
+ // Let's allow only clients belonging to "bar" class.
+ pool->allowClientClass("bar");
+ EXPECT_EQ("bar", pool->getClientClass());
+
+ EXPECT_FALSE(pool->clientSupported(no_class));
+ EXPECT_FALSE(pool->clientSupported(foo_class));
+ EXPECT_TRUE(pool->clientSupported(bar_class));
+ EXPECT_TRUE(pool->clientSupported(three_classes));
+}
+
+// This test checks that handling for require-client-classes is valid.
+TEST(Pool4Test, requiredClasses) {
+ // Create a pool.
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+
+ // This client starts with no required classes.
+ EXPECT_TRUE(pool->getRequiredClasses().empty());
+
+ // Add the first class
+ pool->requireClientClass("router");
+ EXPECT_EQ(1, pool->getRequiredClasses().size());
+
+ // Add a second class
+ pool->requireClientClass("modem");
+ EXPECT_EQ(2, pool->getRequiredClasses().size());
+ EXPECT_TRUE(pool->getRequiredClasses().contains("router"));
+ EXPECT_TRUE(pool->getRequiredClasses().contains("modem"));
+ EXPECT_FALSE(pool->getRequiredClasses().contains("foo"));
+
+ // Check that it's ok to add the same class repeatedly
+ EXPECT_NO_THROW(pool->requireClientClass("foo"));
+ EXPECT_NO_THROW(pool->requireClientClass("foo"));
+ EXPECT_NO_THROW(pool->requireClientClass("foo"));
+
+ // Check that 'foo' is marked for required evaluation
+ EXPECT_TRUE(pool->getRequiredClasses().contains("foo"));
+}
+
+TEST(Pool6Test, constructorFirstLast) {
+
+ // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool
+ Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8:1::"),
+ IOAddress("2001:db8:1::ffff:ffff:ffff:ffff"));
+
+ EXPECT_EQ(Lease::TYPE_NA, pool1.getType());
+ EXPECT_EQ(IOAddress("2001:db8:1::"), pool1.getFirstAddress());
+ EXPECT_EQ(IOAddress("2001:db8:1::ffff:ffff:ffff:ffff"),
+ pool1.getLastAddress());
+
+ // This is Pool6, IPv4 addresses do not belong here
+ EXPECT_THROW(Pool6(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("192.168.0.5")), BadValue);
+ EXPECT_THROW(Pool6(Lease::TYPE_NA, IOAddress("192.168.0.2"),
+ IOAddress("2001:db8::1")), BadValue);
+
+ // Should throw. Range should be 2001:db8::1 - 2001:db8::2, not
+ // the other way around.
+ EXPECT_THROW(Pool6(Lease::TYPE_NA, IOAddress("2001:db8::2"),
+ IOAddress("2001:db8::1")), BadValue);
+}
+
+TEST(Pool6Test, constructorPrefixLen) {
+
+ // let's construct 2001:db8:1::/96 pool
+ Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 96);
+
+ EXPECT_EQ(Lease::TYPE_NA, pool1.getType());
+ EXPECT_EQ("2001:db8:1::", pool1.getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff", pool1.getLastAddress().toText());
+
+ // No such thing as /130 prefix
+ EXPECT_THROW(Pool6(Lease::TYPE_NA, IOAddress("2001:db8::"), 130),
+ BadValue);
+
+ // /0 prefix does not make sense
+ EXPECT_THROW(Pool6(Lease::TYPE_NA, IOAddress("2001:db8::"), 0),
+ BadValue);
+
+ // This is Pool6, IPv4 addresses do not belong here
+ EXPECT_THROW(Pool6(Lease::TYPE_NA, IOAddress("192.168.0.2"), 96),
+ BadValue);
+
+ // Delegated prefix length for addresses must be /128
+ EXPECT_THROW(Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 96, 125),
+ BadValue);
+}
+
+TEST(Pool6Test, inRange) {
+ Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::f"));
+
+ EXPECT_FALSE(pool1.inRange(IOAddress("2001:db8:1::")));
+ EXPECT_TRUE(pool1.inRange(IOAddress("2001:db8:1::1")));
+ EXPECT_TRUE(pool1.inRange(IOAddress("2001:db8:1::7")));
+ EXPECT_TRUE(pool1.inRange(IOAddress("2001:db8:1::f")));
+ EXPECT_FALSE(pool1.inRange(IOAddress("2001:db8:1::10")));
+ EXPECT_FALSE(pool1.inRange(IOAddress("::")));
+}
+
+// Checks that Prefix Delegation pools are handled properly
+TEST(Pool6Test, PD) {
+
+ // Let's construct 2001:db8:1::/96 PD pool, split into /112 prefixes
+ Pool6 pool1(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 96, 112);
+
+ EXPECT_EQ(Lease::TYPE_PD, pool1.getType());
+ EXPECT_EQ(112, pool1.getLength());
+ EXPECT_EQ("2001:db8:1::", pool1.getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff", pool1.getLastAddress().toText());
+
+ // Check that it's not possible to have min-max range for PD
+ EXPECT_THROW(Pool6 pool2(Lease::TYPE_PD, IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::f")), BadValue);
+
+ // Check that it's not allowed to specify bigger prefix address than the
+ // pool prefix length
+ // Should not be able to compute prefix if first address is not starting
+ // from prefix length 32 (2001:db8::)
+ EXPECT_THROW(Pool6 pool3(Lease::TYPE_PD, IOAddress("2001:db8:1::"),
+ 32, 56), BadValue);
+
+ // Check that it's not allowed to delegate bigger prefix than the pool
+ // Let's try to split /64 prefix into /56 chunks (should be impossible)
+ EXPECT_THROW(Pool6 pool4(Lease::TYPE_PD, IOAddress("2001:db8:1::"),
+ 64, 56), BadValue);
+
+ // It should be possible to have a pool split into just a single chunk
+ // Let's try to split 2001:db8:1::/77 into a single /77 delegated prefix
+ EXPECT_NO_THROW(Pool6 pool5(Lease::TYPE_PD, IOAddress("2001:db8:1::"),
+ 77, 77));
+}
+
+// Checks that prefix pools with excluded prefixes are handled properly.
+TEST(Pool6Test, PDExclude) {
+ Pool6Ptr pool;
+
+ // Create a pool with a good excluded prefix. The good excluded prefix
+ // is the one for which is a sub-prefix of the main prefix.
+ ASSERT_NO_THROW(pool.reset(new Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::2000"), 120)));
+
+ // Verify pool properties.
+ EXPECT_EQ(Lease::TYPE_PD, pool->getType());
+ EXPECT_EQ(112, pool->getLength());
+ EXPECT_EQ("2001:db8:1::", pool->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff", pool->getLastAddress().toText());
+
+ // It should include Prefix Exclude option.
+ Option6PDExcludePtr pd_exclude_option = pool->getPrefixExcludeOption();
+ ASSERT_TRUE(pd_exclude_option);
+ EXPECT_EQ("2001:db8:1::2:2000", pd_exclude_option->
+ getExcludedPrefix(IOAddress("2001:db8:1:0:0:0:2::"), 112).toText());
+ EXPECT_EQ(120, static_cast<unsigned>(pd_exclude_option->getExcludedPrefixLength()));
+
+ // Create another pool instance, but with the excluded prefix being
+ // "unspecified".
+ ASSERT_NO_THROW(pool.reset(new Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress::IPV6_ZERO_ADDRESS(), 0)));
+
+ EXPECT_EQ(Lease::TYPE_PD, pool->getType());
+ EXPECT_EQ(112, pool->getLength());
+ EXPECT_EQ("2001:db8:1::", pool->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff", pool->getLastAddress().toText());
+
+ ASSERT_FALSE(pool->getPrefixExcludeOption());
+
+ // Excluded prefix length must be greater than the main prefix length.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::"), 112),
+ BadValue);
+
+ // Again, the excluded prefix length must be greater than main prefix
+ // length.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::"), 104),
+ BadValue);
+
+ // The "unspecified" excluded prefix must have both values set to 0.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+ IOAddress("2001:db8:1::"), 0),
+ BadValue);
+
+ // Similar case as above, but the prefix value is 0 and the length
+ // is non zero.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+ IOAddress::IPV6_ZERO_ADDRESS(), 72),
+ BadValue);
+
+ // Excluded prefix must be an IPv6 prefix.
+ EXPECT_THROW(Pool6(IOAddress("100::"), 8, 16,
+ IOAddress("10.0.0.0"), 24),
+ BadValue);
+
+ // Excluded prefix length must not be greater than 128.
+ EXPECT_THROW(Pool6(IOAddress("2001:db8:1::"), 48, 64,
+ IOAddress("2001:db8:1::"), 129),
+ BadValue);
+}
+
+// Checks that temporary address pools are handled properly
+TEST(Pool6Test, TA) {
+ // Note: since we defined TA pool types during PD work, we can test it
+ // now. Although the configuration to take advantage of it is not
+ // planned for now, we will support it some day.
+
+ // Let's construct 2001:db8:1::/96 temporary addresses
+ Pool6Ptr pool1;
+ EXPECT_NO_THROW(pool1.reset(new Pool6(Lease::TYPE_TA,
+ IOAddress("2001:db8:1::"), 96)));
+
+ // Check that TA range can be only defined for single addresses
+ EXPECT_THROW(Pool6(Lease::TYPE_TA, IOAddress("2001:db8:1::"), 96, 127),
+ BadValue);
+
+ ASSERT_TRUE(pool1);
+ EXPECT_EQ(Lease::TYPE_TA, pool1->getType());
+ EXPECT_EQ(128, pool1->getLength()); // singular addresses, not prefixes
+ EXPECT_EQ("2001:db8:1::", pool1->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::ffff:ffff", pool1->getLastAddress().toText());
+
+ // Check that it's possible to have min-max range for TA
+ Pool6Ptr pool2;
+ EXPECT_NO_THROW(pool2.reset(new Pool6(Lease::TYPE_TA,
+ IOAddress("2001:db8:1::1"),
+ IOAddress("2001:db8:1::f"))));
+ ASSERT_TRUE(pool2);
+ EXPECT_EQ(Lease::TYPE_TA, pool2->getType());
+ EXPECT_EQ(128, pool2->getLength()); // singular addresses, not prefixes
+ EXPECT_EQ("2001:db8:1::1", pool2->getFirstAddress().toText());
+ EXPECT_EQ("2001:db8:1::f", pool2->getLastAddress().toText());
+}
+
+// Simple check if toText returns reasonable values
+TEST(Pool6Test, toText) {
+ Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+ EXPECT_EQ("type=IA_NA, 2001:db8::1-2001:db8::2, delegated_len=128",
+ pool1.toText());
+
+ Pool6 pool2(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 96, 112);
+ EXPECT_EQ("type=IA_PD, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=112",
+ pool2.toText());
+
+ Pool6 pool3(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::1000"), 120);
+ EXPECT_EQ("type=IA_PD, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=112,"
+ " excluded_prefix_len=120",
+ pool3.toText());
+
+ Pool6 pool4(Lease::TYPE_NA, IOAddress("2001:db8::"),
+ IOAddress("2001:db8::ffff"));
+ EXPECT_EQ("type=IA_NA, 2001:db8::-2001:db8::ffff, delegated_len=128",
+ pool4.toText());
+}
+
+// Simple check if toElement returns reasonable values
+TEST(Pool6Test, toElement) {
+ Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+ std::string expected1 = "{"
+ " \"pool\": \"2001:db8::1-2001:db8::2\", "
+ " \"option-data\": [ ] "
+ "}";
+ isc::test::runToElementTest<Pool6>(expected1, pool1);
+
+ Pool6 pool2(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 96, 112);
+ std::string expected2 = "{"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 96, "
+ " \"delegated-len\": 112, "
+ " \"option-data\": [ ] "
+ "}";
+ isc::test::runToElementTest<Pool6>(expected2, pool2);
+
+ Pool6 pool3(IOAddress("2001:db8:1::"), 96, 112,
+ IOAddress("2001:db8:1::1000"), 120);
+ std::string expected3 = "{"
+ " \"prefix\": \"2001:db8:1::\", "
+ " \"prefix-len\": 96, "
+ " \"delegated-len\": 112, "
+ " \"excluded-prefix\": \"2001:db8:1::1000\", "
+ " \"excluded-prefix-len\": 120, "
+ " \"option-data\": [ ] "
+ "}";
+ isc::test::runToElementTest<Pool6>(expected3, pool3);
+
+ Pool6 pool4(Lease::TYPE_NA, IOAddress("2001:db8::"),
+ IOAddress("2001:db8::ffff"));
+ pool4.setID(5);
+ std::string expected4 = "{"
+ " \"pool\": \"2001:db8::/112\", "
+ " \"option-data\": [ ], "
+ " \"pool-id\": 5 "
+ "}";
+ isc::test::runToElementTest<Pool6>(expected4, pool4);
+}
+
+// Checks if the number of possible leases in range is reported correctly.
+TEST(Pool6Test, leasesCount) {
+ Pool6 pool1(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+ EXPECT_EQ(2, pool1.getCapacity());
+
+ Pool6 pool2(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 96, 112);
+ EXPECT_EQ(65536, pool2.getCapacity());
+}
+
+// This test checks that it is possible to specify pool specific options.
+TEST(Pool6Test, addOptions) {
+ // Create a pool to add options to it.
+ Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("3000::"), 64, 128));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false,
+ "dhcp6"));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(pool->getCfgOption()->add(option, false, false, "isc"));
+ }
+
+ // Get options from the pool and check if all 10 are there.
+ OptionContainerPtr options = pool->getCfgOption()->getAll("dhcp6");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = pool->getCfgOption()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = pool->getCfgOption()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test checks that handling for user-context is valid.
+TEST(Pool6Test, userContext) {
+ // Create a pool to add options to it.
+ Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+
+ // Context should be empty until explicitly set.
+ EXPECT_FALSE(pool.getContext());
+
+ // When set, should be returned properly.
+ ElementPtr ctx = Element::create("{ \"comment\": \"foo\" }");
+ EXPECT_NO_THROW(pool.setContext(ctx));
+ ASSERT_TRUE(pool.getContext());
+ EXPECT_EQ(ctx->str(), pool.getContext()->str());
+}
+
+// This test checks that handling for client-class is valid.
+TEST(Pool6Test, clientClass) {
+ // Create a pool.
+ Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ // No class restrictions defined, any client should be supported
+ EXPECT_TRUE(pool.getClientClass().empty());
+ EXPECT_TRUE(pool.clientSupported(no_class));
+ EXPECT_TRUE(pool.clientSupported(foo_class));
+ EXPECT_TRUE(pool.clientSupported(bar_class));
+ EXPECT_TRUE(pool.clientSupported(three_classes));
+
+ // Let's allow only clients belonging to "bar" class.
+ pool.allowClientClass("bar");
+ EXPECT_EQ("bar", pool.getClientClass());
+
+ EXPECT_FALSE(pool.clientSupported(no_class));
+ EXPECT_FALSE(pool.clientSupported(foo_class));
+ EXPECT_TRUE(pool.clientSupported(bar_class));
+ EXPECT_TRUE(pool.clientSupported(three_classes));
+}
+
+// This test checks that handling for require-client-classes is valid.
+TEST(Pool6Test, requiredClasses) {
+ // Create a pool.
+ Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+
+ // This client starts with no required classes.
+ EXPECT_TRUE(pool.getRequiredClasses().empty());
+
+ // Add the first class
+ pool.requireClientClass("router");
+ EXPECT_EQ(1, pool.getRequiredClasses().size());
+
+ // Add a second class
+ pool.requireClientClass("modem");
+ EXPECT_EQ(2, pool.getRequiredClasses().size());
+ EXPECT_TRUE(pool.getRequiredClasses().contains("router"));
+ EXPECT_TRUE(pool.getRequiredClasses().contains("modem"));
+ EXPECT_FALSE(pool.getRequiredClasses().contains("foo"));
+
+ // Check that it's ok to add the same class repeatedly
+ EXPECT_NO_THROW(pool.requireClientClass("foo"));
+ EXPECT_NO_THROW(pool.requireClientClass("foo"));
+ EXPECT_NO_THROW(pool.requireClientClass("foo"));
+
+ // Check that 'foo' is marked for required evaluation
+ EXPECT_TRUE(pool.getRequiredClasses().contains("foo"));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/random_allocation_state_unittest.cc b/src/lib/dhcpsrv/tests/random_allocation_state_unittest.cc
new file mode 100644
index 0000000..d80d200
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/random_allocation_state_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (C) 2022-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/random_allocation_state.h>
+#include <testutils/multi_threading_utils.h>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+#include <set>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+// Test creating the random allocation state for an IPv4 pool.
+TEST(PoolRandomAllocationStateTest, ipv4Pool) {
+ // Create the pool and state.
+ IOAddress first("192.0.2.1");
+ IOAddress last("192.0.2.255");
+ auto pool = boost::make_shared<Pool4>(first, last);
+ auto state = PoolRandomAllocationState::create(pool);
+ ASSERT_TRUE(state);
+
+ // Make sure that the permutation has been initialized.
+ auto permutation = state->getPermutation();
+ ASSERT_TRUE(permutation);
+
+ // Keep the record of the addresses returned by the permutation
+ // to ensure it returns unique addresses.
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 10; ++i) {
+ auto done = true;
+ auto next = permutation->next(done);
+ // Returned address must belong to the pool.
+ EXPECT_TRUE(pool->inRange(next));
+ EXPECT_FALSE(done);
+ addresses.insert(next);
+ }
+ // Make sure that unique addresses were returned.
+ EXPECT_EQ(10, addresses.size());
+}
+
+// Test creating the random allocation state for an IPv6 pool.
+TEST(PoolRandomAllocationStateTest, ipv6AddressPool) {
+ // Create the pool and state.
+ IOAddress first("2001:db8::1");
+ IOAddress last("2001:db8::1:0");
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, first, last);
+ auto state = PoolRandomAllocationState::create(pool);
+ ASSERT_TRUE(state);
+
+ // Make sure that the permutation has been initialized.
+ auto permutation = state->getPermutation();
+ ASSERT_TRUE(permutation);
+
+ // Keep the record of the addresses returned by the permutation
+ // to ensure it returns unique addresses.
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 10; ++i) {
+ auto done = true;
+ auto next = permutation->next(done);
+ // Returned address must belong to the pool.
+ EXPECT_TRUE(pool->inRange(next));
+ EXPECT_FALSE(done);
+ addresses.insert(next);
+ }
+ // Make sure that unique addresses were returned.
+ EXPECT_EQ(10, addresses.size());
+}
+
+// Test creating the random allocation state for an IPv6 prefix pool.
+TEST(PoolRandomAllocationStateTest, ipv6PrefixPool) {
+ // Create the pool and state.
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 96);
+ auto state = PoolRandomAllocationState::create(pool);
+ ASSERT_TRUE(state);
+
+ // Make sure that the permutation has been initialized.
+ auto permutation = state->getPermutation();
+ ASSERT_TRUE(permutation);
+
+ // Keep the record of the addresses returned by the permutation
+ // to ensure it returns unique prefixes.
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < 10; ++i) {
+ auto done = true;
+ auto next = permutation->next(done);
+ // Returned prefix must belong to the pool.
+ EXPECT_TRUE(pool->inRange(next));
+ EXPECT_FALSE(done);
+ prefixes.insert(next);
+ }
+ // Make sure that unique prefixes were returned.
+ EXPECT_EQ(10, prefixes.size());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/random_allocator_unittest.cc b/src/lib/dhcpsrv/tests/random_allocator_unittest.cc
new file mode 100644
index 0000000..caf27f4
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/random_allocator_unittest.cc
@@ -0,0 +1,485 @@
+// Copyright (C) 2022-2023 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 <asiolink/io_address.h>
+#include <dhcpsrv/random_allocator.h>
+#include <dhcpsrv/tests/alloc_engine_utils.h>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+#include <set>
+#include <vector>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using RandomAllocatorTest4 = AllocEngine4Test;
+
+// Test that the allocator returns the correct type.
+TEST_F(RandomAllocatorTest4, getType) {
+ RandomAllocator alloc(Lease::TYPE_V4, subnet_);
+ EXPECT_EQ("random", alloc.getType());
+}
+
+// Test allocating IPv4 addresses when a subnet has a single pool.
+TEST_F(RandomAllocatorTest4, singlePool) {
+ RandomAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ addresses.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ }
+ // The pool comprises 10 addresses. All should be returned.
+ EXPECT_EQ(10, addresses.size());
+}
+
+// Test allocating IPv4 addresses from multiple pools.
+TEST_F(RandomAllocatorTest4, manyPools) {
+ RandomAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ // Add several more pools.
+ for (int i = 1; i < 10; ++i) {
+ stringstream min, max;
+ min << "192.0.2." << i * 10;
+ max << "192.0.2." << i * 10 + 9;
+ auto pool = boost::make_shared<Pool4>(IOAddress(min.str()),
+ IOAddress(max.str()));
+ subnet_->addPool(pool);
+ }
+
+ // There are ten pools with 10 addresses each.
+ int total = 100;
+
+ // Repeat allocation of all addresses several times. Make sure that
+ // the same addresses are returned when all pools are exhausted.
+ for (auto j = 0; j < 6; ++j) {
+ std::set<IOAddress> addresses_set;
+ std::vector<IOAddress> addresses_vector;
+ std::vector<PoolPtr> pools_vector;
+
+ // Pick random addresses the number of times equal to the
+ // subnet capacity to ensure that all addresses are returned.
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ addresses_set.insert(candidate);
+ addresses_vector.push_back(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, candidate, cc_));
+ pools_vector.push_back(subnet_->getPool(Lease::TYPE_V4, candidate));
+ }
+ // Make sure that unique addresses have been returned.
+ EXPECT_EQ(total, addresses_set.size());
+
+ // Verify that the addresses are returned in the random order.
+ // Count how many times we found consecutive addresses. It should
+ // be 0 or close to 0.
+ int consecutive_addresses = 0;
+ for (auto k = 0; k < addresses_vector.size()-1; ++k) {
+ if (addresses_vector[k].toUint32() == addresses_vector[k+1].toUint32()-1) {
+ ++consecutive_addresses;
+ }
+ }
+ // Ideally, the number of consecutive occurrences should be 0 but we
+ // allow some to make sure the test doesn't fall over sporadically.
+ EXPECT_LT(consecutive_addresses, addresses_vector.size()/4);
+
+ // Repeat similar check for pools. The pools should be picked in the
+ // random order too.
+ int consecutive_pools = 0;
+ for (auto k = 0; k < pools_vector.size()-1; ++k) {
+ // Check if the pools are adjacent (i.e., last address of the
+ // previous pool is a neighbor of the first address of the next
+ // pool).
+ if (pools_vector[k]->getLastAddress().toUint32()+1 ==
+ pools_vector[k+1]->getFirstAddress().toUint32()) {
+ ++consecutive_pools;
+ }
+ }
+ EXPECT_LT(consecutive_pools, pools_vector.size()/2);
+ }
+}
+
+// Test that the allocator returns a zero address when there are no
+// pools.
+TEST_F(RandomAllocatorTest4, noPools) {
+ RandomAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ subnet_->delPools(Lease::TYPE_V4);
+
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+}
+
+// Test that the allocator respects client classes while it picks
+// pools and addresses.
+TEST_F(RandomAllocatorTest4, clientClasses) {
+ RandomAllocator alloc(Lease::TYPE_V4, subnet_);
+
+ // First pool only allows the client class foo.
+ pool_->allowClientClass("foo");
+
+ // Second pool. It only allows client class bar.
+ auto pool1 = boost::make_shared<Pool4>(IOAddress("192.0.2.120"),
+ IOAddress("192.0.2.129"));
+ pool1->allowClientClass("bar");
+ subnet_->addPool(pool1);
+
+ // Third pool. It only allows client class foo.
+ auto pool2 = boost::make_shared<Pool4>(IOAddress("192.0.2.140"),
+ IOAddress("192.0.2.149"));
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool2);
+
+ // Forth pool. It only allows client class bar.
+ auto pool3 = boost::make_shared<Pool4>(IOAddress("192.0.2.160"),
+ IOAddress("192.0.2.169"));
+ pool3->allowClientClass("bar");
+ subnet_->addPool(pool3);
+
+ // Remember offered addresses.
+ std::set<IOAddress> addresses_set;
+
+ // Simulate client's request belonging to the class bar.
+ cc_.insert("bar");
+ for (auto i = 0; i < 40; ++i) {
+ // Allocate random addresses and make sure they belong to the
+ // pools associated with the class bar.
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(pool1->inRange(candidate) || pool3->inRange(candidate));
+ }
+ EXPECT_EQ(20, addresses_set.size());
+
+ addresses_set.clear();
+
+ // Simulate the case that the client also belongs to the class foo.
+ // All pools should now be available.
+ cc_.insert("foo");
+ for (auto i = 0; i < 60; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(subnet_->inRange(candidate));
+ }
+ EXPECT_EQ(40, addresses_set.size());
+
+ // When the client does not belong to any client class the allocator
+ // can't offer any address to the client.
+ cc_.clear();
+ IOAddress candidate = alloc.pickAddress(cc_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV4Zero());
+}
+
+using RandomAllocatorTest6 = AllocEngine6Test;
+
+// Test that the allocator returns the correct type.
+TEST_F(RandomAllocatorTest6, getType) {
+ RandomAllocator allocNA(Lease::TYPE_NA, subnet_);
+ EXPECT_EQ("random", allocNA.getType());
+
+ RandomAllocator allocPD(Lease::TYPE_PD, subnet_);
+ EXPECT_EQ("random", allocPD.getType());
+}
+
+// Test allocating IPv6 addresses when a subnet has a single pool.
+TEST_F(RandomAllocatorTest6, singlePool) {
+ RandomAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // Remember returned addresses, so we can verify that unique addresses
+ // are returned.
+ std::set<IOAddress> addresses;
+ for (auto i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ addresses.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ }
+ // The pool comprises 17 addresses. All should be returned.
+ EXPECT_EQ(17, addresses.size());
+}
+
+// Test allocating IPv6 addresses from multiple pools.
+TEST_F(RandomAllocatorTest6, manyPools) {
+ RandomAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // Add several more pools.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+ min << "2001:db8:1::" << hex << i * 16 + 1;
+ max << "2001:db8:1::" << hex << i * 16 + 16;
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress(min.str()),
+ IOAddress(max.str()));
+ subnet_->addPool(pool);
+ }
+
+ // First pool (::10 - ::20) has 17 addresses.
+ // There are 8 extra pools with 16 addresses in each.
+ int total = 17 + 8 * 16;
+
+ // Repeat allocation of all addresses several times. Make sure that
+ // the same addresses are returned when all pools are exhausted.
+ for (auto j = 0; j < 6; ++j) {
+ std::set<IOAddress> addresses_set;
+ std::vector<IOAddress> addresses_vector;
+ std::vector<PoolPtr> pools_vector;
+
+ // Pick random addresses the number of times equal to the
+ // subnet capacity to ensure that all addresses are returned.
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ addresses_set.insert(candidate);
+ addresses_vector.push_back(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
+ pools_vector.push_back(subnet_->getPool(Lease::TYPE_NA, candidate));
+ }
+ // Make sure that unique addresses have been returned.
+ EXPECT_EQ(total, addresses_set.size());
+
+ // Verify that the addresses are returned in the random order.
+ // Count how many times we found consecutive addresses. It should
+ // be 0 or close to 0.
+ int consecutive_addresses = 0;
+ for (auto k = 0; k < addresses_vector.size()-1; ++k) {
+ if (IOAddress::increase(addresses_vector[k]) == addresses_vector[k+1]) {
+ ++consecutive_addresses;
+ }
+ }
+ // Ideally, the number of consecutive occurrences should be 0 but we
+ // allow some to make sure the test doesn't fall over sporadically.
+ EXPECT_LT(consecutive_addresses, addresses_vector.size()/4);
+
+ // Repeat similar check for pools. The pools should be picked in the
+ // random order too.
+ int consecutive_pools = 0;
+ for (auto k = 0; k < pools_vector.size()-1; ++k) {
+ if (IOAddress::increase(pools_vector[k]->getLastAddress()) ==
+ pools_vector[k]->getFirstAddress()) {
+ ++consecutive_pools;
+ }
+ }
+ EXPECT_LT(consecutive_pools, pools_vector.size()/2);
+ }
+}
+
+// Test that the allocator respects client classes while it picks
+// pools and addresses.
+TEST_F(RandomAllocatorTest6, clientClasses) {
+ RandomAllocator alloc(Lease::TYPE_NA, subnet_);
+
+ // First pool only allows the client class foo.
+ pool_->allowClientClass("foo");
+
+ // Second pool. It only allows client class bar.
+ auto pool1 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::120"),
+ IOAddress("2001:db8:1::129"));
+ pool1->allowClientClass("bar");
+ subnet_->addPool(pool1);
+
+ // Third pool. It only allows client class foo.
+ auto pool2 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::140"),
+ IOAddress("2001:db8:1::149"));
+ pool2->allowClientClass("foo");
+ subnet_->addPool(pool2);
+
+ // Forth pool. It only allows client class bar.
+ auto pool3 = boost::make_shared<Pool6>(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::160"),
+ IOAddress("2001:db8:1::169"));
+ pool3->allowClientClass("bar");
+ subnet_->addPool(pool3);
+
+ // Remember offered addresses.
+ std::set<IOAddress> addresses_set;
+
+ // Simulate client's request belonging to the class bar.
+ cc_.insert("bar");
+ for (auto i = 0; i < 60; ++i) {
+ // Allocate random addresses and make sure they belong to the
+ // pools associated with the class bar.
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(pool1->inRange(candidate) || pool3->inRange(candidate));
+ }
+ EXPECT_EQ(20, addresses_set.size());
+
+ addresses_set.clear();
+
+ // Simulate the case that the client also belongs to the class foo.
+ // All pools should now be available.
+ cc_.insert("foo");
+ for (auto i = 0; i < 100; ++i) {
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("::"));
+ addresses_set.insert(candidate);
+ EXPECT_TRUE(subnet_->inRange(candidate));
+ }
+ EXPECT_EQ(47, addresses_set.size());
+
+ // When the client does not belong to any client class the allocator
+ // can't offer any address to the client.
+ cc_.clear();
+ IOAddress candidate = alloc.pickAddress(cc_, duid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(candidate.isV6Zero());
+}
+
+// Test allocating delegated prefixes when a subnet has a single pool.
+TEST_F(RandomAllocatorTest6, singlePdPool) {
+ RandomAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ Pool6Ptr pool;
+
+ // Remember returned prefixes, so we can verify that unique addresses
+ // are returned.
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < 66000; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // The pool comprises 65536 prefixes. All should be returned.
+ EXPECT_EQ(65536, prefixes.size());
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(RandomAllocatorTest6, manyPdPools) {
+ RandomAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+
+ size_t total = 65536 + 10 * 256;
+
+ Pool6Ptr pool;
+
+ for (auto j = 0; j < 2; ++j) {
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 0);
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+ }
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(RandomAllocatorTest6, manyPdPoolsPreferLower) {
+ RandomAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+
+ size_t total = 65536;
+
+ Pool6Ptr pool;
+
+ for (auto j = 0; j < 2; ++j) {
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_LOWER, IOAddress("::"), 120);
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+ }
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(RandomAllocatorTest6, manyPdPoolsPreferEqual) {
+ RandomAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+
+ size_t total = 10 * 256;
+
+ Pool6Ptr pool;
+
+ for (auto j = 0; j < 2; ++j) {
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_EQUAL, IOAddress("::"), 128);
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+ }
+}
+
+// Test allocating delegated prefixes from multiple pools.
+TEST_F(RandomAllocatorTest6, manyPdPoolsPreferHigher) {
+ RandomAllocator alloc(Lease::TYPE_PD, subnet_);
+
+ for (auto i = 0; i < 10; ++i) {
+ ostringstream s;
+ s << "300" << hex << i + 1 << "::";
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_PD,
+ IOAddress(s.str()),
+ 120,
+ 128);
+ subnet_->addPool(pool);
+ }
+
+ size_t total = 10 * 256;
+
+ Pool6Ptr pool;
+
+ for (auto j = 0; j < 2; ++j) {
+ std::set<IOAddress> prefixes;
+ for (auto i = 0; i < total; ++i) {
+ IOAddress candidate = alloc.pickPrefix(cc_, pool, duid_, Allocator::PREFIX_LEN_HIGHER, IOAddress("::"), 64);
+ prefixes.insert(candidate);
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate));
+ EXPECT_TRUE(subnet_->inPool(Lease::TYPE_PD, candidate, cc_));
+ }
+ // Make sure that unique prefixes have been returned.
+ EXPECT_EQ(total, prefixes.size());
+ }
+}
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/tests/resource_handler_unittest.cc b/src/lib/dhcpsrv/tests/resource_handler_unittest.cc
new file mode 100644
index 0000000..41f4d69
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/resource_handler_unittest.cc
@@ -0,0 +1,509 @@
+// Copyright (C) 2020 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 <dhcpsrv/resource_handler.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+// Verifies behavior with empty block.
+TEST(ResourceHandleTest, empty) {
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with empty block (v4).
+TEST(ResourceHandleTest, empty4) {
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with one handler.
+TEST(ResourceHandleTest, one) {
+ IOAddress addr("2001:db8::1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with one IPv4 handler.
+TEST(ResourceHandleTest, one4) {
+ IOAddress addr("192.0.2.1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two handlers.
+TEST(ResourceHandleTest, two) {
+ IOAddress addr("2001:db8::");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_PD, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Get a second resource handler.
+ ResourceHandler resource_handler2;
+
+ // Try to lock it.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_PD, addr));
+
+ // Should return true (busy);
+ EXPECT_TRUE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two IPv4 handlers.
+TEST(ResourceHandleTest, two4) {
+ IOAddress addr("192.0.2.1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Get a second resource handler.
+ ResourceHandler4 resource_handler2;
+
+ // Try to lock it.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr));
+
+ // Should return true (busy);
+ EXPECT_TRUE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two handlers in different blocks (sequence).
+TEST(ResourceHandleTest, sequence) {
+ IOAddress addr("2001:db8::1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ try {
+ // Get a second resource handler.
+ ResourceHandler resource_handler2;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free)
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two IPv4 handlers in different blocks (sequence).
+TEST(ResourceHandleTest, sequence4) {
+ IOAddress addr("192.0.2.1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+
+ try {
+ // Get a second resource handler.
+ ResourceHandler4 resource_handler2;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr));
+
+ // Should return false (free)
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two handlers for different addresses.
+TEST(ResourceHandleTest, differentAddress) {
+ IOAddress addr("2001:db8::1");
+ IOAddress addr2("2001:db8::2");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Get a second resource handler.
+ ResourceHandler resource_handler2;
+
+ // Try to lock it.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr2));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two IPv4 handlers for different addresses.
+TEST(ResourceHandleTest, differentAddress4) {
+ IOAddress addr("192.0.2.1");
+ IOAddress addr2("192.0.2.2");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Get a second resource handler.
+ ResourceHandler4 resource_handler2;
+
+ // Try to lock it.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr2));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two handlers for different types.
+TEST(ResourceHandleTest, differentTypes) {
+ IOAddress addr("2001:db8::");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Get a second resource handler.
+ ResourceHandler resource_handler2;
+
+ // Try to lock it.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_PD, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior of the isLocked predicate.
+TEST(ResourceHandleTest, isLocked) {
+ IOAddress addr("2001:db8::1");
+ IOAddress addr2("2001:db8::2");
+ IOAddress addr3("2001:db8::3");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Get a second resource handler.
+ ResourceHandler resource_handler2;
+
+ // Try to lock it.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr2));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Check ownership.
+ EXPECT_TRUE(resource_handler.isLocked(Lease::TYPE_NA, addr));
+ EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr2));
+ EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr3));
+ EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_PD, addr));
+ EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_NA, addr));
+ EXPECT_TRUE(resource_handler2.isLocked(Lease::TYPE_NA, addr2));
+ EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_NA, addr3));
+ EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_PD, addr2));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior with two IPv4 handlers.
+TEST(ResourceHandleTest, isLocked4) {
+ IOAddress addr("192.0.2.1");
+ IOAddress addr2("192.0.2.2");
+ IOAddress addr3("192.0.2.3");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Get a second resource handler.
+ ResourceHandler4 resource_handler2;
+
+ // Try to lock it.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr2));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Check ownership.
+ EXPECT_TRUE(resource_handler.isLocked4(addr));
+ EXPECT_FALSE(resource_handler.isLocked4(addr2));
+ EXPECT_FALSE(resource_handler.isLocked4(addr3));
+ EXPECT_FALSE(resource_handler2.isLocked4(addr));
+ EXPECT_TRUE(resource_handler2.isLocked4(addr2));
+ EXPECT_FALSE(resource_handler2.isLocked4(addr3));
+
+ // ResourceHandler4 derives from ResourceHandler.
+ EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr));
+ EXPECT_FALSE(resource_handler2.isLocked(Lease::TYPE_NA, addr2));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies that double tryLock call for the same resource returns busy.
+TEST(ResourceHandleTest, doubleTryLock) {
+ IOAddress addr("2001:db8::");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_PD, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Try to lock it again.
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_PD, addr));
+
+ // Should return true (busy);
+ EXPECT_TRUE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies that double tryLock call for the same resource returns busy (v4).
+TEST(ResourceHandleTest, doubleTryLock4) {
+ IOAddress addr("192.0.2.1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // Try to lock it again.
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return true (busy);
+ EXPECT_TRUE(busy);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior of the unLock method.
+TEST(ResourceHandleTest, unLock) {
+ IOAddress addr("2001:db8::1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // The resource is owned by us.
+ EXPECT_TRUE(resource_handler.isLocked(Lease::TYPE_NA, addr));
+
+ // Try to unlock it.
+ EXPECT_NO_THROW(resource_handler.unLock(Lease::TYPE_NA, addr));
+
+ // The resource is no longer owned by us.
+ EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr));
+
+ // Get a second resource handler.
+ ResourceHandler resource_handler2;
+
+ // Try to lock it by the second handler.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock(Lease::TYPE_NA, addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // The resource is owned by the second handler.
+ EXPECT_FALSE(resource_handler.isLocked(Lease::TYPE_NA, addr));
+ EXPECT_TRUE(resource_handler2.isLocked(Lease::TYPE_NA, addr));
+
+ // Only the owner is allowed to release a resource.
+ EXPECT_THROW(resource_handler.unLock(Lease::TYPE_NA, addr), NotFound);
+ EXPECT_NO_THROW(resource_handler2.unLock(Lease::TYPE_NA, addr));
+ // Once.
+ EXPECT_THROW(resource_handler2.unLock(Lease::TYPE_NA, addr), NotFound);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+// Verifies behavior of the unLock method.
+TEST(ResourceHandleTest, unLock4) {
+ IOAddress addr("192.0.2.1");
+
+ try {
+ // Get a resource handler.
+ ResourceHandler4 resource_handler;
+
+ // Try to lock it.
+ bool busy = false;
+ EXPECT_NO_THROW(busy = !resource_handler.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // The resource is owned by us.
+ EXPECT_TRUE(resource_handler.isLocked4(addr));
+
+ // Try to unlock it.
+ EXPECT_NO_THROW(resource_handler.unLock4(addr));
+
+ // The resource is no longer owned by us.
+ EXPECT_FALSE(resource_handler.isLocked4(addr));
+
+ // Get a second resource handler
+ ResourceHandler4 resource_handler2;
+
+ // Try to lock it by the second handler.
+ EXPECT_NO_THROW(busy = !resource_handler2.tryLock4(addr));
+
+ // Should return false (free).
+ EXPECT_FALSE(busy);
+
+ // The resource is owned by the second handler.
+ EXPECT_FALSE(resource_handler.isLocked4(addr));
+ EXPECT_TRUE(resource_handler2.isLocked4(addr));
+
+ // Only the owner is allowed to release a resource.
+ EXPECT_THROW(resource_handler.unLock4(addr), NotFound);
+ EXPECT_NO_THROW(resource_handler2.unLock4(addr));
+ // Once.
+ EXPECT_THROW(resource_handler2.unLock4(addr), NotFound);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "unexpected exception: " << ex.what();
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/run_unittests.cc b/src/lib/dhcpsrv/tests/run_unittests.cc
new file mode 100644
index 0000000..76b2ebf
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2012-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 <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/dhcpsrv/tests/sanity_checks_unittest.cc b/src/lib/dhcpsrv/tests/sanity_checks_unittest.cc
new file mode 100644
index 0000000..7185b93
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/sanity_checks_unittest.cc
@@ -0,0 +1,1614 @@
+// Copyright (C) 2018-2022 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 <dhcpsrv/cfg_consistency.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/sanity_checks_parser.h>
+#include <dhcpsrv/srv_config.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/sanity_checker.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <testutils/log_utils.h>
+#include <util/range_utilities.h>
+#include <cc/data.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+class SanityChecksTest : public ::testing::Test {
+public:
+
+ SanityChecksTest() {
+ LeaseMgrFactory::destroy();
+ }
+
+ void startLeaseBackend(bool v6) {
+ std::ostringstream s;
+ s << "type=memfile " << (v6 ? "universe=6 " : "universe=4 ")
+ << "persist=false lfc-interval=0";
+ LeaseMgrFactory::create(s.str());
+ }
+
+ void setLeaseCheck(CfgConsistency::LeaseSanity sanity) {
+ CfgMgr::instance().getCurrentCfg()->getConsistency()->setLeaseSanityCheck(sanity);
+ }
+
+ ~SanityChecksTest() {
+ CfgMgr::instance().clear();
+ LeaseMgrFactory::destroy();
+ }
+
+ /// @brief Generates a simple IPv4 lease.
+ ///
+ /// The HW address is randomly generated, subnet_id is specified.
+ ///
+ /// @param address Lease address.
+ /// @param subnet_id ID of the subnet to use.
+ ///
+ /// @return new lease with random content
+ Lease4Ptr newLease4(const IOAddress& address, SubnetID subnet_id) {
+
+ // Randomize HW address.
+ vector<uint8_t> mac(6);
+ isc::util::fillRandom(mac.begin(), mac.end());
+ HWAddrPtr hwaddr(new HWAddr(mac, HTYPE_ETHER));
+
+ vector<uint8_t> clientid(1);
+
+ time_t timestamp = time(0) - 86400 + random() % 86400;
+
+ // Return created lease.
+ return (Lease4Ptr(new Lease4(address, hwaddr,
+ &clientid[0], 0, // no client-id
+ 1200, // valid
+ timestamp, subnet_id, false, false, "")));
+ }
+
+ /// @brief Generates a simple IPv6 lease.
+ ///
+ /// The DUID and IAID are randomly generated, subnet_id is specified.
+ ///
+ /// @param address Lease address.
+ /// @param subnet_id ID of the subnet to use.
+ ///
+ /// @return new lease with random content
+ Lease6Ptr newLease6(const IOAddress& address, SubnetID subnet_id) {
+ // Let's generate DUID of random length.
+ std::vector<uint8_t> duid_vec(8 + random()%20);
+ // And then fill it with random value.
+ isc::util::fillRandom(duid_vec.begin(), duid_vec.end());
+ DuidPtr duid(new DUID(duid_vec));
+
+ Lease::Type lease_type = Lease::TYPE_NA;
+ uint32_t iaid = 1 + random()%100;
+
+ std::ostringstream hostname;
+ hostname << "hostname" << (random() % 2048);
+
+ // Return created lease.
+ Lease6Ptr lease(new Lease6(lease_type, address, duid, iaid,
+ 1000, 1200, // pref, valid
+ subnet_id,
+ false, false, "")); // fqdn fwd, rev, hostname
+ return (lease);
+ }
+
+ Subnet4Ptr createSubnet4(string subnet_txt, SubnetID id) {
+ size_t pos = subnet_txt.find("/");
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+ size_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ return (Subnet4Ptr(new Subnet4(addr, len, 1000, 2000, 3000, id)));
+ }
+
+ Subnet6Ptr createSubnet6(string subnet_txt, SubnetID id) {
+ size_t pos = subnet_txt.find("/");
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+ size_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ return (Subnet6Ptr(new Subnet6(addr, len, 1000, 2000, 3000, 4000, id)));
+ }
+
+ void
+ parserCheck(SrvConfig& cfg, const string& txt, bool exp_throw,
+ CfgConsistency::LeaseSanity exp_sanity,
+ CfgConsistency::ExtendedInfoSanity exp_sanity2) {
+
+ // Reset to defaults.
+ cfg.getConsistency()->setLeaseSanityCheck(CfgConsistency::LEASE_CHECK_NONE);
+ cfg.getConsistency()->setExtendedInfoSanityCheck(CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ SanityChecksParser parser;
+
+ ElementPtr json;
+ EXPECT_NO_THROW(json = Element::fromJSON(txt));
+
+ if (exp_throw) {
+ // Should throw DhcpConfigError.
+ try {
+ parser.parse(cfg, json);
+ ADD_FAILURE() << "should throw not did not throw";
+ return;
+ } catch (const DhcpConfigError&) {
+ return;
+ } catch (const exception& ex) {
+ ADD_FAILURE() << "throw another exception with " << ex.what();
+ }
+ } else {
+ // Should not throw.
+ try {
+ parser.parse(cfg, json);
+ } catch (const exception& ex) {
+ ADD_FAILURE() << "throw an exception with " << ex.what();
+ }
+ }
+
+ EXPECT_EQ(cfg.getConsistency()->getLeaseSanityCheck(), exp_sanity);
+ EXPECT_EQ(cfg.getConsistency()->getExtendedInfoSanityCheck(), exp_sanity2);
+ }
+
+};
+
+// Verify whether configuration parser is able to understand the values
+// that are valid and reject those that are not.
+TEST_F(SanityChecksTest, leaseCheck) {
+
+ // These are valid and should be accepted.
+ string valid1 = "{ \"lease-checks\": \"none\" }";
+ string valid2 = "{ \"lease-checks\": \"warn\" }";
+ string valid3 = "{ \"lease-checks\": \"fix\" }";
+ string valid4 = "{ \"lease-checks\": \"fix-del\" }";
+ string valid5 = "{ \"lease-checks\": \"del\" }";
+
+ // These are not valid values or types.
+ string bogus1 = "{ \"lease-checks\": \"sanitize\" }";
+ string bogus2 = "{ \"lease-checks\": \"ignore\" }";
+ string bogus3 = "{ \"lease-checks\": true }";
+ string bogus4 = "{ \"lease-checks\": 42 }";
+
+ // These are valid and should be accepted.
+ string valid6 = "{ \"extended-info-checks\": \"none\" }";
+ string valid7 = "{ \"extended-info-checks\": \"fix\" }";
+ string valid8 = "{ \"extended-info-checks\": \"strict\" }";
+ string valid9 = "{ \"extended-info-checks\": \"pedantic\" }";
+ string valid10 = "{ \"lease-checks\": \"fix\",\n"
+ " \"extended-info-checks\": \"fix\" }";
+
+ string bogus5 = "{ \"extended-info-checks\": \"sanitize\" }";
+ string bogus6 = "{ \"extended-info-checks\": \"ignore\" }";
+ string bogus7 = "{ \"extended-info-checks\": true }";
+ string bogus8 = "{ \"extended-info-checks\": 42 }";
+
+ SrvConfig cfg;
+
+ // The lease default should be to none.
+ EXPECT_EQ(cfg.getConsistency()->getLeaseSanityCheck(),
+ CfgConsistency::LEASE_CHECK_NONE);
+
+ // The extended info default should be to fix.
+ EXPECT_EQ(cfg.getConsistency()->getExtendedInfoSanityCheck(),
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ parserCheck(cfg, valid1, false, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, valid2, false, CfgConsistency::LEASE_CHECK_WARN,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, valid3, false, CfgConsistency::LEASE_CHECK_FIX,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, valid4, false, CfgConsistency::LEASE_CHECK_FIX_DEL,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, valid5, false, CfgConsistency::LEASE_CHECK_DEL,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ parserCheck(cfg, bogus1, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, bogus2, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, bogus3, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, bogus4, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ parserCheck(cfg, valid6, false, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+ parserCheck(cfg, valid7, false, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, valid8, false, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT);
+ parserCheck(cfg, valid9, false, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+ parserCheck(cfg, valid10, false, CfgConsistency::LEASE_CHECK_FIX,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+
+ parserCheck(cfg, bogus5, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, bogus6, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, bogus7, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+ parserCheck(cfg, bogus8, true, CfgConsistency::LEASE_CHECK_NONE,
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// Verify whether sanity checker works as expected (valid v4).
+TEST_F(SanityChecksTest, valid4) {
+ // Create network and lease.
+ CfgMgr::instance().setFamily(AF_INET);
+ Subnet4Ptr subnet = createSubnet4("192.168.1.0/24", 1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->add(subnet);
+ IOAddress addr("192.168.1.1");
+ Lease4Ptr lease = newLease4(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here in the same subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet->getID(), lease->subnet_id_);
+}
+
+// Verify whether sanity checker works as expected (valid v6).
+TEST_F(SanityChecksTest, valid6) {
+ // Create network and lease.
+ CfgMgr::instance().setFamily(AF_INET6);
+ Subnet6Ptr subnet = createSubnet6("2001:db8:1::/64", 1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->add(subnet);
+ IOAddress addr("2001:db8:1::1");
+ Lease6Ptr lease = newLease6(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here in the same subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet->getID(), lease->subnet_id_);
+}
+
+// Verify whether sanity checker works as expected (wrong subnet v4).
+TEST_F(SanityChecksTest, wrongSubnet4) {
+ // Create networks and lease in the second and wrong subnet.
+ CfgMgr::instance().setFamily(AF_INET);
+ Subnet4Ptr subnet1 = createSubnet4("192.168.1.0/24", 1);
+ Subnet4Ptr subnet2 = createSubnet4("192.168.2.0/24", 2);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->add(subnet1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->add(subnet2);
+ IOAddress addr("192.168.1.1");
+ Lease4Ptr lease = newLease4(addr, 2);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here but was moved to the first and right subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet1->getID(), lease->subnet_id_);
+}
+
+// Verify whether sanity checker works as expected (wrong subnet v6).
+TEST_F(SanityChecksTest, wrongSubnet6) {
+ // Create networks and lease in the second and wrong subnet.
+ CfgMgr::instance().setFamily(AF_INET6);
+ Subnet6Ptr subnet1 = createSubnet6("2001:db8:1::/64", 1);
+ Subnet6Ptr subnet2 = createSubnet6("2001:db8:2::/64", 2);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->add(subnet2);
+ IOAddress addr("2001:db8:1::1");
+ Lease6Ptr lease = newLease6(addr, 2);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here but was moved to the first and right subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet1->getID(), lease->subnet_id_);
+}
+
+// Verify whether sanity checker works as expected (no subnet v4).
+TEST_F(SanityChecksTest, noSubnet4) {
+ // Create network and lease in a wrong subnet.
+ CfgMgr::instance().setFamily(AF_INET);
+ Subnet4Ptr subnet = createSubnet4("192.168.2.0/24", 1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->add(subnet);
+ IOAddress addr("192.168.1.1");
+ Lease4Ptr lease = newLease4(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease was removed because its subnet does not exist,
+ EXPECT_FALSE(lease);
+}
+
+// Verify whether sanity checker works as expected (no subnet v6).
+TEST_F(SanityChecksTest, noSubnet6) {
+ // Create network and lease in a wrong subnet.
+ CfgMgr::instance().setFamily(AF_INET6);
+ Subnet6Ptr subnet = createSubnet6("2001:db8:2::/64", 1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->add(subnet);
+ IOAddress addr("2001:db8:1::1");
+ Lease6Ptr lease = newLease6(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease was removed because its subnet does not exist,
+ EXPECT_FALSE(lease);
+}
+
+// Verify whether sanity checker works as expected (guard v4).
+TEST_F(SanityChecksTest, guard4) {
+ // Create networks and lease in the first and guarded subnet.
+ CfgMgr::instance().setFamily(AF_INET);
+ Subnet4Ptr subnet1 = createSubnet4("192.168.1.0/24", 1);
+ subnet1->allowClientClass("foo");
+ Subnet4Ptr subnet2 = createSubnet4("192.168.1.100/24", 2);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->add(subnet1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->add(subnet2);
+ IOAddress addr("192.168.1.1");
+ Lease4Ptr lease = newLease4(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here and in the guarded subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet1->getID(), lease->subnet_id_);
+}
+
+// Verify whether sanity checker works as expected (guard v6).
+TEST_F(SanityChecksTest, guard6) {
+ // Create networks and lease in the first and guarded subnet.
+ CfgMgr::instance().setFamily(AF_INET6);
+ Subnet6Ptr subnet1 = createSubnet6("2001:db8:1::/64", 1);
+ subnet1->allowClientClass("foo");
+ Subnet6Ptr subnet2 = createSubnet6("2001:db8:2::100/64", 2);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->add(subnet1);
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->add(subnet2);
+ IOAddress addr("2001:db8:1::1");
+ Lease6Ptr lease = newLease6(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here and in the guarded subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet1->getID(), lease->subnet_id_);
+}
+
+// Verify whether sanity checker works as expected (guard only v4).
+TEST_F(SanityChecksTest, guardOnly4) {
+ // Create guarded network and lease.
+ CfgMgr::instance().setFamily(AF_INET);
+ Subnet4Ptr subnet = createSubnet4("192.168.1.0/24", 1);
+ subnet->allowClientClass("foo");
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->add(subnet);
+ IOAddress addr("192.168.1.1");
+ Lease4Ptr lease = newLease4(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here in the same subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet->getID(), lease->subnet_id_);
+}
+
+// Verify whether sanity checker works as expected (valid v6).
+TEST_F(SanityChecksTest, guardOnly6) {
+ // Create guarded network and lease.
+ CfgMgr::instance().setFamily(AF_INET6);
+ Subnet6Ptr subnet = createSubnet6("2001:db8:1::/64", 1);
+ subnet->allowClientClass("foo");
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->add(subnet);
+ IOAddress addr("2001:db8:1::1");
+ Lease6Ptr lease = newLease6(addr, 1);
+
+ // Check the lease.
+ setLeaseCheck(CfgConsistency::LEASE_CHECK_FIX_DEL);
+ SanityChecker checker;
+ checker.checkLease(lease);
+
+ // Verify the lease is still here in the same subnet.
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(subnet->getID(), lease->subnet_id_);
+}
+
+class ExtendedInfoChecksTest : public LogContentTest {
+public:
+
+ /// @brief Generates a simple IPv4 lease.
+ ///
+ /// The HW address is randomly generated, user context is specified.
+ ///
+ /// @param address Lease address.
+ /// @param user_context User context to use.
+ ///
+ /// @return new lease with random content.
+ Lease4Ptr newLease4(const IOAddress& address,
+ ConstElementPtr user_context) {
+
+ // Randomize HW address.
+ vector<uint8_t> mac(6);
+ isc::util::fillRandom(mac.begin(), mac.end());
+ HWAddrPtr hwaddr(new HWAddr(mac, HTYPE_ETHER));
+
+ vector<uint8_t> clientid(1);
+
+ time_t timestamp = time(0) - 86400 + random() % 86400;
+
+ // Return created lease.
+ Lease4Ptr lease(new Lease4(address, hwaddr,
+ &clientid[0], 0, // no client-id
+ 1200, // valid
+ timestamp, 1, false, false, ""));
+ lease->setContext(user_context);
+ return(lease);
+ }
+
+ /// @brief Generates a simple IPv6 lease.
+ ///
+ /// The DUID and IAID are randomly generated, user context is specified.
+ ///
+ /// @param address Lease address.
+ /// @param user_context User context to use.
+ ///
+ /// @return new lease with random content.
+ Lease6Ptr newLease6(const IOAddress& address,
+ ConstElementPtr user_context) {
+ // Let's generate DUID of random length.
+ std::vector<uint8_t> duid_vec(8 + random()%20);
+ // And then fill it with random value.
+ isc::util::fillRandom(duid_vec.begin(), duid_vec.end());
+ DuidPtr duid(new DUID(duid_vec));
+
+ Lease::Type lease_type = Lease::TYPE_NA;
+ uint32_t iaid = 1 + random()%100;
+
+ std::ostringstream hostname;
+ hostname << "hostname" << (random() % 2048);
+
+ // Return created lease.
+ Lease6Ptr lease(new Lease6(lease_type, address, duid, iaid,
+ 1000, 1200, // pref, valid
+ 1, false, false, ""));
+ lease->setContext(user_context);
+ return(lease);
+ }
+
+ /// @brief Check IPv4 scenario.
+ ///
+ /// @brief original Original user context.
+ /// @brief expected Expected user context.
+ /// @brief sanity Extended info sanity level.
+ /// @break logs Expected log messages.
+ void check4(string description, string original, string expected,
+ CfgConsistency::ExtendedInfoSanity sanity,
+ vector<string> logs = {}) {
+ ElementPtr orig_context;
+ if (!original.empty()) {
+ ASSERT_NO_THROW(orig_context = Element::fromJSON(original))
+ << "invalid original user context, test " << description
+ << " is broken";
+ }
+ ElementPtr exp_context;
+ if (!expected.empty()) {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(expected))
+ << "invalid expected user context, test " << description
+ << " is broken";
+ }
+
+ Lease4Ptr lease = newLease4(IOAddress("192.168.1.1"), orig_context);
+ ConstElementPtr before;
+ if (orig_context) {
+ before = isc::data::copy(orig_context);
+ }
+ for (const string& log : logs) {
+ addString(log);
+ }
+
+ bool ret = LeaseMgr::upgradeLease4ExtendedInfo(lease, sanity);
+ ConstElementPtr after = lease->getContext();
+ if (!before && !after) {
+ EXPECT_FALSE(ret) << "null before and null after";
+ } else if ((before && !after) || (!before && after)) {
+ EXPECT_TRUE(ret) << "only one of before and after is null";
+ } else if (before->equals(*after)) {
+ EXPECT_FALSE(ret) << "before == after";
+ } else {
+ EXPECT_TRUE(ret) << "before != after";
+ }
+ if (!exp_context) {
+ EXPECT_FALSE(after) << "expected null, got " << *after;
+ } else {
+ ASSERT_TRUE(after) << "expected not null, got null";
+ EXPECT_TRUE(exp_context->equals(*after))
+ << "expected: " << *exp_context << std::endl
+ << "actual: " << *after << std::endl;
+ }
+ EXPECT_TRUE(checkFile());
+ }
+
+ /// @brief Check IPv6 scenario.
+ ///
+ /// @brief original Original user context.
+ /// @brief expected Expected user context.
+ /// @brief sanity Extended info sanity level.
+ /// @break logs Expected log messages.
+ void check6(string description, string original, string expected,
+ CfgConsistency::ExtendedInfoSanity sanity,
+ vector<string> logs = {}) {
+ ElementPtr orig_context;
+ if (!original.empty()) {
+ ASSERT_NO_THROW(orig_context = Element::fromJSON(original))
+ << "invalid original user context, test " << description
+ << " is broken";
+ }
+ ElementPtr exp_context;
+ if (!expected.empty()) {
+ ASSERT_NO_THROW(exp_context = Element::fromJSON(expected))
+ << "invalid expected user context, test " << description
+ << " is broken";
+ }
+
+ Lease6Ptr lease = newLease6(IOAddress("2001::1"), orig_context);
+ ConstElementPtr before;
+ if (orig_context) {
+ before = isc::data::copy(orig_context);
+ }
+ for (const string& log : logs) {
+ addString(log);
+ }
+
+ bool ret = LeaseMgr::upgradeLease6ExtendedInfo(lease, sanity);
+ ConstElementPtr after = lease->getContext();
+ if (!before && !after) {
+ EXPECT_FALSE(ret) << "null before and null after";
+ } else if ((before && !after) || (!before && after)) {
+ EXPECT_TRUE(ret) << "only one of before and after is null";
+ } else if (before->equals(*after)) {
+ EXPECT_FALSE(ret) << "before == after";
+ } else {
+ EXPECT_TRUE(ret) << "before != after";
+ }
+ if (!exp_context) {
+ EXPECT_FALSE(after) << "expected null, got " << *after;
+ } else {
+ ASSERT_TRUE(after) << "expected not null, got null";
+ EXPECT_TRUE(exp_context->equals(*after))
+ << "expected: " << *exp_context << std::endl
+ << "actual: " << *after << std::endl;
+ }
+ EXPECT_TRUE(checkFile());
+ }
+};
+
+// No user context is right for any sanity check level including the highest.
+TEST_F(ExtendedInfoChecksTest, noUserContext4) {
+ string description = "no user context, pedantic";
+ check4(description, "", "", CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+}
+
+// User context with a bad type is right only when no check is done.
+TEST_F(ExtendedInfoChecksTest, badTypeUserContext4none) {
+ string description = "user context not a map, none";
+ check4(description, "1", "1", CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// User context with a bad type is refused by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeUserContext4) {
+ string description = "user context not a map, fix";
+ check4(description, "1", "", CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in user context a problem was found:"
+ " user context is not a map)" });
+}
+
+// Empty user context is right only when no check is done.
+TEST_F(ExtendedInfoChecksTest, emptyTypeUserContext4none) {
+ string description = "empty user context, none";
+ check4(description, "{ }", "{ }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// Empty user context is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, emptyTypeUserContext4) {
+ string description = "empty user context, fix";
+ check4(description, "{ }", "", CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// No ISC entry is right in all cases.
+TEST_F(ExtendedInfoChecksTest, noISC4) {
+ string description = "no ISC entry, pedantic";
+ check4(description, "{ \"foo\": 1 }", "{ \"foo\": 1 }",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+}
+
+// ISC entry with a bad type is right only when no check is done.
+TEST_F(ExtendedInfoChecksTest, badTypeISC4none) {
+ string description = "ISC entry no a map, none";
+ check4(description, "{ \"ISC\": 1 }", "{ \"ISC\": 1 }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// ISC entry with a bad type is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeISC4) {
+ string description = "ISC entry no a map, fix";
+ check4(description, "{ \"ISC\": 1 }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in isc a problem was found:"
+ " ISC entry is not a map)" });
+}
+
+// When the ISC entry is dropped other entries are kept.
+TEST_F(ExtendedInfoChecksTest, badTypeISC4other) {
+ string description = "ISC entry no a map with others, fix";
+ check4(description, "{ \"ISC\": 1, \"foo\": 2 }", "{ \"foo\": 2 }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in isc a problem was found:"
+ " ISC entry is not a map)" });
+}
+
+// Empty ISC entry is kept only when no check is done.
+TEST_F(ExtendedInfoChecksTest, emptyISC4none) {
+ string description = "empty ISC entry, none";
+ check4(description, "{ \"ISC\": { } }", "{ \"ISC\": { } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// Empty ISC entry is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, emptyISC4) {
+ string description = "empty ISC entry, fix";
+ check4(description, "{ \"ISC\": { } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// No relay-agent-info entry is right at all sanity levels.
+TEST_F(ExtendedInfoChecksTest, noRAI) {
+ string description = "no RAI, pedantic";
+ check4(description, "{ \"ISC\": { \"foo\": 1 } }",
+ "{ \"ISC\": { \"foo\": 1 } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+}
+
+// relay-agent-info entry with a bad type is right only when no check is done.
+TEST_F(ExtendedInfoChecksTest, badTypeRAInone) {
+ string description = "RAI is not a string or a map, none";
+ check4(description, "{ \"ISC\": { \"relay-agent-info\": true } }",
+ "{ \"ISC\": { \"relay-agent-info\": true } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// relay-agent-info entry with a bad type is dropped by all not none sanity
+// check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRAI) {
+ string description = "RAI is not a string or a map, fix";
+ check4(description, "{ \"ISC\": { \"relay-agent-info\": true } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in relay-agent-info a problem was found:"
+ " relay-agent-info is not a map or a string)" });
+}
+
+// When relay-agent-info entry is dropped other entries are kept.
+TEST_F(ExtendedInfoChecksTest, badTypeRAIother) {
+ string description = "RAI is not a string or a map with others, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": true, \"foo\": 1 } }",
+ "{ \"ISC\": { \"foo\": 1 } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in relay-agent-info a problem was found:"
+ " relay-agent-info is not a map or a string)" });
+}
+
+// String relay-agent-info entry which can't be decoded is right
+// only when no check is done.
+TEST_F(ExtendedInfoChecksTest, badEncodingStringRAInone) {
+ string description = "string RAI with a junk value, none";
+ check4(description, "{ \"ISC\": { \"relay-agent-info\": \"foo\" } }",
+ "{ \"ISC\": { \"relay-agent-info\": \"foo\" } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// String relay-agent-info entry which can't be decoded is dropped
+// by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingStringRAI) {
+ string description = "string RAI with a junk value, fix";
+ check4(description, "{ \"ISC\": { \"relay-agent-info\": \"foo\" } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in rai a problem was found:"
+ " 'foo' is not a valid string of hexadecimal digits)" });
+}
+
+// String relay-agent-info entry for an empty option is dropped
+// by all not none sanity check levels (this should not happen).
+TEST_F(ExtendedInfoChecksTest, emptyStringRAI) {
+ string description = "string RAI with empty content, fix";
+ check4(description, "{ \"ISC\": { \"relay-agent-info\": \"0x\" } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in rai a problem was found:"
+ " '0x' is not a valid string of hexadecimal digits)" });
+}
+
+// Valid string relay-agent-info entry is upgraded by all not none
+// sanity check levels.
+TEST_F(ExtendedInfoChecksTest, upgradedRAI) {
+ string description = "valid string RAI, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": \"0x0104AABBCCDD\" } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x0104AABBCCDD\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED"
+ " extended info for lease 192.168.1.1 was upgraded" });
+}
+
+// Upgraded string relay-agent-info entry with ids.
+TEST_F(ExtendedInfoChecksTest, upgradedRAIwithIds) {
+ string description = "valid string RAI with ids, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": \"0x02030102030C03AABBCC\" } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\","
+ " \"relay-id\": \"AABBCC\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_UPGRADED"
+ " extended info for lease 192.168.1.1 was upgraded" });
+}
+
+// sub-options entry with a bad type is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, badTypeSubOptionsfix) {
+ string description = "sub-options not a string, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\": 1 } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\": 1 } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// sub-options entry with a bad type is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badTypeSubOptionsstrict) {
+ string description = "sub-options not a string, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\": 1 } } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in sub-options a problem was found:"
+ " sub-options is not a string)" });
+}
+
+// sub-options entry which can't be decoded is right up to the fix
+// sanity level.
+TEST_F(ExtendedInfoChecksTest, badEncodingSubOptionsfix) {
+ string description = "sub-options with a junk value, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"foo\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"foo\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// sub-options entry which can't be decoded is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingSubOptionsstrict) {
+ string description = "sub-options with a junk value, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":"
+ " \"foo\" } } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in sub-options a problem was found:"
+ " 'foo' is not a valid string of hexadecimal digits)" });
+}
+
+// remote-id entry with a bad type is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, badTypeRemoteId4fix) {
+ string description = "remote-id not a string, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\": 1 } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\": 1 } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// remote-id entry with a bad type is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRemoteId4strict) {
+ string description = "remote-id not a string, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\": 1 } } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in remote-id a problem was found:"
+ " remote-id is not a string)" });
+}
+
+// remote-id entry which can't be decoded is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, badEncodingRemoteId4fix) {
+ string description = "remote-id with a junk value, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\":"
+ " \"foo\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\":"
+ " \"foo\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// remote-id entry which can't be decoded is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingRemoteId4strict) {
+ string description = "remote-id with a junk value, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\":"
+ " \"foo\" } } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in remote-id a problem was found:"
+ " attempt to decode a value not in base16 char set)" });
+}
+
+// Empty remote-id entry is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, emptyRemoteId4fix) {
+ string description = "empty remote-id, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\": \"\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\": \"\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// Empty remote-id entry is dropped at strict or higher sanity levels.
+TEST_F(ExtendedInfoChecksTest, emptyRemoteId4strict) {
+ string description = "empty remote-id, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\": \"\" } } }",
+ "", CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in remote-id a problem was found:"
+ " remote-id is empty)" });
+}
+
+// Valid remote-id entry is right for all sanity levels.
+TEST_F(ExtendedInfoChecksTest, validRemoteId4strict) {
+ string description = "valid remote-id, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\":"
+ " \"AABB\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"remote-id\":"
+ " \"AABB\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT);
+}
+
+// relay-id entry with a bad type is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, badTypeRelayId4fix) {
+ string description = "relay-id not a string, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\": 1 } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\": 1 } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// relay-id entry with a bad type is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRelayId4strict) {
+ string description = "relay-id not a string, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\": 1 } } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in relay-id a problem was found:"
+ " relay-id is not a string)" });
+}
+
+// relay-id entry which can't be decoded is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, badEncodingRelayId4fix) {
+ string description = "relay-id with a junk value, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\":"
+ " \"foo\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\":"
+ " \"foo\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// relay-id entry which can't be decoded is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingRelayId4strict) {
+ string description = "relay-id with a junk value, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\":"
+ " \"foo\" } } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in relay-id a problem was found:"
+ " attempt to decode a value not in base16 char set)" });
+}
+
+// Empty relay-id entry is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, emptyRelayId4fix) {
+ string description = "empty relay-id, fix";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\": \"\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\": \"\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// Empty relay-id entry is dropped at strict or higher sanity levels.
+TEST_F(ExtendedInfoChecksTest, emptyRelayId4strict) {
+ string description = "empty relay-id, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\": \"\" } } }",
+ "", CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in relay-id a problem was found:"
+ " relay-id is empty)" });
+}
+
+// Valid relay-id entry is right for all sanity levels.
+TEST_F(ExtendedInfoChecksTest, validRelayId4strict) {
+ string description = "valid relay-id, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\":"
+ " \"AABB\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"relay-id\":"
+ " \"AABB\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT);
+}
+
+// Junk entries are right up to the strict sanity level.
+TEST_F(ExtendedInfoChecksTest, junk4strict) {
+ string description = "junk entry, strict";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"foo\": 1 } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"foo\": 1 } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT);
+}
+
+// Junk entries are dropped at the pedantic level.
+TEST_F(ExtendedInfoChecksTest, junk4pedantic) {
+ string description = "junk entry, pedantic";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"foo\": 1 } } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE4_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 192.168.1.1 failed checks"
+ " (in relay-agent-info a problem was found:"
+ " spurious 'foo' entry in relay-agent-info)" });
+}
+
+// comment is not considered as a junk entry.
+TEST_F(ExtendedInfoChecksTest, comment4) {
+ string description = "comment entry, pedantic";
+ check4(description,
+ "{ \"ISC\": { \"relay-agent-info\": { \"comment\": \"good\" } } }",
+ "{ \"ISC\": { \"relay-agent-info\": { \"comment\": \"good\" } } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+}
+
+// No user context is right for any sanity check level including the highest.
+TEST_F(ExtendedInfoChecksTest, noUserContext6) {
+ string description = "no user context, pedantic";
+ check6(description, "", "", CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+}
+
+// User context with a bad type is right only when no check is done.
+TEST_F(ExtendedInfoChecksTest, badTypeUserContext6none) {
+ string description = "user context not a map, none";
+ check6(description, "1", "1", CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// User context with a bad type is refused by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeUserContext6) {
+ string description = "user context not a map, fix";
+ check6(description, "1", "", CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in user context a problem was found:"
+ " user context is not a map)" });
+}
+
+// Empty user context is right only when no check is done.
+TEST_F(ExtendedInfoChecksTest, emptyTypeUserContext6none) {
+ string description = "empty user context, none";
+ check6(description, "{ }", "{ }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// Empty user context is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, emptyTypeUserContext6) {
+ string description = "empty user context, fix";
+ check6(description, "{ }", "", CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// No ISC entry is right in all cases.
+TEST_F(ExtendedInfoChecksTest, noISC6) {
+ string description = "no ISC entry, pedantic";
+ check6(description, "{ \"foo\": 1 }", "{ \"foo\": 1 }",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+}
+
+// ISC entry with a bad type is right only when no check is done.
+TEST_F(ExtendedInfoChecksTest, badTypeISC6none) {
+ string description = "ISC entry no a map, none";
+ check6(description, "{ \"ISC\": 1 }", "{ \"ISC\": 1 }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// ISC entry with a bad type is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeISC6) {
+ string description = "ISC entry no a map, fix";
+ check6(description, "{ \"ISC\": 1 }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in isc a problem was found:"
+ " ISC entry is not a map)" });
+}
+
+// When the ISC entry is dropped other entries are kept.
+TEST_F(ExtendedInfoChecksTest, badTypeISC6other) {
+ string description = "ISC entry no a map with others, fix";
+ check6(description, "{ \"ISC\": 1, \"foo\": 2 }", "{ \"foo\": 2 }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in isc a problem was found:"
+ " ISC entry is not a map)" });
+}
+
+// Empty ISC entry is kept only when no check is done.
+TEST_F(ExtendedInfoChecksTest, emptyISC6none) {
+ string description = "empty ISC entry, none";
+ check6(description, "{ \"ISC\": { } }", "{ \"ISC\": { } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// Empty ISC entry is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, emptyISC6) {
+ string description = "empty ISC entry, fix";
+ check6(description, "{ \"ISC\": { } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// relays is not upgraded when no check is done.
+TEST_F(ExtendedInfoChecksTest, relaysnone) {
+ string description = "relays, none";
+ check6(description, "{ \"ISC\": { \"relays\": true } }",
+ "{ \"ISC\": { \"relays\": true } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_NONE);
+}
+
+// relays is dropped when relay-info is present by all not none sanity
+// check levels.
+TEST_F(ExtendedInfoChecksTest, relaysAndRelayInfo) {
+ string description = "relays and relay-info, fix";
+ check6(description,
+ "{ \"ISC\": { \"relays\": true, \"relay-info\":"
+ " [ { \"foo\": 1 } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"foo\": 1 } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// relays with bad type is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRelays) {
+ string description = "relays is not a list, fix";
+ check6(description, "{ \"ISC\": { \"relays\": true } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relays a problem was found:"
+ " relays is not a list)" });
+}
+
+// Empty relays is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, emptyRelays) {
+ string description = "empty relays, fix";
+ check6(description, "{ \"ISC\": { \"relays\": [ ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relays a problem was found:"
+ " relays is empty)" });
+}
+
+// Relay (element of relays) with bad type is dropped by all not none
+// sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRelay) {
+ string description = "relay is not a map, fix";
+ check6(description, "{ \"ISC\": { \"relays\": [ 1 ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay [relay#0] a problem was found:"
+ " relay#0 is not a map)" });
+}
+
+// options with bad type is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeOptions) {
+ string description = "options is not a string, fix";
+ check6(description, "{ \"ISC\": { \"relays\": [ { \"options\": 1 } ] } }",
+ "", CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in options [relay#0] a problem was found:"
+ " options is not a string)" });
+}
+
+// options which can't be decoded is dropped by all not none sanity
+// check levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingOptions) {
+ string description = "options with junk value, fix";
+ check6(description,
+ "{ \"ISC\": { \"relays\": [ { \"options\": \"foo\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in options [relay#0] a problem was found:"
+ " 'foo' is not a valid string of hexadecimal digits)" });
+}
+
+// Valid relays is upgraded by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, upgradedRelays) {
+ string description = "upgraded relays, fix";
+ check6(description,
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED"
+ " extended info for lease 2001::1 was upgraded" });
+}
+
+// Valid relays with ids is upgraded by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, upgradedRelaysWithIds) {
+ string description = "upgraded relays with ids, fix";
+ check6(description,
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED"
+ " extended info for lease 2001::1 was upgraded" });
+}
+
+// Valid relays with 2 relays is upgraded by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, upgradedRelays2) {
+ string description = "upgraded relays with 2 relays, fix";
+ check6(description,
+ "{ \"ISC\": { \"relays\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" }, { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33,"
+ " \"link\": \"2001:db8::1\", \"peer\": \"2001:db8::2\","
+ " \"options\": \"0x00C800080102030405060708\" }, { \"hop\": 100,"
+ " \"options\": \"0x00250006010203040506003500086464646464646464\","
+ " \"link\": \"2001:db8::5\", \"peer\": \"2001:db8::6\","
+ " \"remote-id\": \"010203040506\","
+ " \"relay-id\": \"6464646464646464\" } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_UPGRADED"
+ " extended info for lease 2001::1 was upgraded" });
+}
+
+// relayinfo with bad type is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRelayInfo) {
+ string description = "relayinfo is not a list, fix";
+ check6(description, "{ \"ISC\": { \"relay-info\": true } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay-info a problem was found:"
+ " relay-info is not a list)" });
+}
+
+// Empty relay-info is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, emptyRelayInfo) {
+ string description = "empty relay-info, fix";
+ check6(description, "{ \"ISC\": { \"relay-info\": [ ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay-info a problem was found:"
+ " relay-info is empty)" });
+}
+
+// Relay (element of relay-info) with bad type is dropped by all not none
+// sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRelay2) {
+ string description = "relay is not a map, fix";
+ check6(description, "{ \"ISC\": { \"relay-info\": [ 1 ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay [relay#0] a problem was found:"
+ " relay#0 is not a map)" });
+}
+
+// options with bad type is dropped by all not none sanity check levels.
+TEST_F(ExtendedInfoChecksTest, badTypeOptions2) {
+ string description = "options is not a string, fix";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"options\": 1 } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in options [relay#0] a problem was found:"
+ " options is not a string)" });
+}
+
+// options which can't be decoded is dropped by all not none sanity
+// check levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingOptions2) {
+ string description = "options with junk value, fix";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"options\": \"foo\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in options [relay#0] a problem was found:"
+ " 'foo' is not a valid string of hexadecimal digits)" });
+}
+
+// Relay without a link entry is right up to fix sanity check level.
+TEST_F(ExtendedInfoChecksTest, noLinkfix) {
+ string description = "no link, fix";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"foo\": 1 } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"foo\": 1 } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// Relay without a link entry is dropped by strict sanity check level and
+// higher.
+TEST_F(ExtendedInfoChecksTest, noLinkstrict) {
+ string description = "no link, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"foo\": 1 } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in link [relay#0] a problem was found:"
+ " no link)" });
+}
+
+// link entry with bad type is dropped by strict sanity check level and higher.
+TEST_F(ExtendedInfoChecksTest, badTypeLink) {
+ string description = "link is not a string, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": 1 } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in link [relay#0] a problem was found:"
+ " link is not a string)" });
+}
+
+// link entry which is not an address is dropped by strict sanity check level
+// and higher.
+TEST_F(ExtendedInfoChecksTest, notAddressLink) {
+ string description = "link is not an address, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"foo\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in link [relay#0] a problem was found:"
+ " Failed to convert string to address 'foo':"
+ " Invalid argument)" });
+}
+
+// link entry which is an IPv4 (vs IPv6) address is dropped by strict sanity
+// check level and higher.
+TEST_F(ExtendedInfoChecksTest, notV6Link) {
+ string description = "link is v4, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\":"
+ " \"192.128.1.1\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in link [relay#0] a problem was found:"
+ " link is not an IPv6 address)" });
+}
+
+// Valid link entry is right for all sanity levels.
+TEST_F(ExtendedInfoChecksTest, validLink) {
+ string description = "valid link, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\" } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT);
+}
+
+// remote-id entry with a bad type is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, badTypeRemoteId6fix) {
+ string description = "remote-id not a string, fix";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"remote-id\": 1 } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"remote-id\": 1 } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// remote-id entry with a bad type is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRemoteId6strict) {
+ string description = "remote-id not a string, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"remote-id\": 1,"
+ " \"link\": \"2001::2\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in remote-id [relay#0] a problem was found:"
+ " remote-id is not a string)" });
+}
+
+// remote-id entry which can't be decoded is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingRemoteId6strict) {
+ string description = "remote-id with a junk value, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"remote-id\": \"foo\","
+ " \"link\": \"2001::2\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in remote-id [relay#0] a problem was found:"
+ " attempt to decode a value not in base16 char set)" });
+}
+
+// Empty remote-id entry is dropped at strict or higher sanity levels.
+TEST_F(ExtendedInfoChecksTest, emptyRemoteId6strict) {
+ string description = "empty remote-id, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"remote-id\": \"\","
+ " \"link\": \"2001::2\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in remote-id [relay#0] a problem was found:"
+ " remote-id is empty)" });
+}
+
+// Valid remote-id entry is right at all sanity levels.
+TEST_F(ExtendedInfoChecksTest, validRemoteId6strict) {
+ string description = "valid remote-id, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"remote-id\": \"AABB\","
+ " \"link\": \"2001::2\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"remote-id\": \"AABB\","
+ " \"link\": \"2001::2\" } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT);
+}
+
+// relay-id entry with a bad type is right up to the fix sanity level.
+TEST_F(ExtendedInfoChecksTest, badTypeRelayId6fix) {
+ string description = "relay-id not a string, fix";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"relay-id\": 1 } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"relay-id\": 1 } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_FIX);
+}
+
+// relay-id entry with a bad type is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badTypeRelayId6strict) {
+ string description = "relay-id not a string, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"relay-id\": 1,"
+ " \"link\": \"2001::2\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay-id [relay#0] a problem was found:"
+ " relay-id is not a string)" });
+}
+
+// relay-id entry which can't be decoded is dropped at strict or higher
+// sanity levels.
+TEST_F(ExtendedInfoChecksTest, badEncodingRelayId6strict) {
+ string description = "relay-id with a junk value, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"relay-id\": \"foo\","
+ " \"link\": \"2001::2\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay-id [relay#0] a problem was found:"
+ " attempt to decode a value not in base16 char set)" });
+}
+
+// Empty relay-id entry is dropped at strict or higher sanity levels.
+TEST_F(ExtendedInfoChecksTest, emptyRelayId6strict) {
+ string description = "empty relay-id, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"relay-id\": \"\","
+ " \"link\": \"2001::2\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay-id [relay#0] a problem was found:"
+ " relay-id is empty)" });
+}
+
+// Valid relay-id entry is right at all sanity levels.
+TEST_F(ExtendedInfoChecksTest, validRelayId6strict) {
+ string description = "valid relay-id, strict";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"relay-id\": \"AABB\","
+ " \"link\": \"2001::2\" } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"relay-id\": \"AABB\","
+ " \"link\": \"2001::2\" } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_STRICT);
+}
+
+// Pedantic requires a peer entry.
+TEST_F(ExtendedInfoChecksTest, noPeerpedantic) {
+ string description = "no peer, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\" } ] } }",
+ "", CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in peer [relay#0] a problem was found:"
+ " no peer)" });
+}
+
+// peer entry with bad type is dropped by pedantic sanity check level.
+TEST_F(ExtendedInfoChecksTest, badTypePeer) {
+ string description = "peer is not a string, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": 1 } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in peer [relay#0] a problem was found:"
+ " peer is not a string)" });
+}
+
+// peer entry which is not an address is dropped by pedantic sanity check level.
+TEST_F(ExtendedInfoChecksTest, notAddressPeer) {
+ string description = "peer is not an address, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"foo\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in peer [relay#0] a problem was found:"
+ " Failed to convert string to address 'foo':"
+ " Invalid argument)" });
+}
+
+// peer entry which is an IPv4 (vs IPv6) address is dropped by pedantic sanity
+// check level.
+TEST_F(ExtendedInfoChecksTest, notV6Peer) {
+ string description = "peer is v4, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"192.128.1.1\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in peer [relay#0] a problem was found:"
+ " peer is not an IPv6 address)" });
+}
+
+// Pedantic requires a hop entry.
+TEST_F(ExtendedInfoChecksTest, noHop) {
+ string description = "no hop, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"2001::3\" } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in hop [relay#0] a problem was found:"
+ " no hop)" });
+}
+
+// hop entry with bad type is dropped by pedantic sanity check level.
+TEST_F(ExtendedInfoChecksTest, badTypeHop) {
+ string description = "hop is not an integer pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"2001::3\", \"hop\": false } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in hop [relay#0] a problem was found:"
+ " hop is not an integer)" });
+}
+
+// Valid relay.
+TEST_F(ExtendedInfoChecksTest, valid6Pedantic) {
+ string description = "valid, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"2001::3\", \"hop\": 10 } ] } }",
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"2001::3\", \"hop\": 10 } ] } }",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC);
+}
+
+// Junk entries are dropped at the pedantic level.
+TEST_F(ExtendedInfoChecksTest, junk6pedantic) {
+ string description = "junk entry, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relay-info\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"2001::3\", \"hop\": 10, \"foo\": 1 } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relay-info [relay#0] a problem was found:"
+ " spurious 'foo' entry)" });
+}
+
+// Same with relays post upgrade checks.
+TEST_F(ExtendedInfoChecksTest, junkRelayspedantic) {
+ string description = "junk entry, pedantic";
+ check6(description,
+ "{ \"ISC\": { \"relays\": [ { \"link\": \"2001::2\","
+ " \"peer\": \"2001::3\", \"hop\": 10, \"foo\": 1 } ] } }", "",
+ CfgConsistency::EXTENDED_INFO_CHECK_PEDANTIC,
+ { "DHCPSRV_LEASE6_EXTENDED_INFO_SANITY_FAIL"
+ " extended info for lease 2001::1 failed checks"
+ " (in relays [relay#0] a problem was found:"
+ " spurious 'foo' entry)" });
+}
diff --git a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
new file mode 100644
index 0000000..ea2f3b3
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
@@ -0,0 +1,1106 @@
+// Copyright (C) 2017-2023 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 <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/parsers/shared_network_parser.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/log_utils.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+class SharedNetworkParserTest : public LogContentTest {
+public:
+
+ /// @brief Structure for describing a single relay test scenario
+ struct RelayTest {
+ /// @brief Description of the test scenario, used for logging
+ std::string description_;
+ /// @brief JSON configuration body of the "relay" element
+ std::string json_content_;
+ /// @brief indicates if parsing should pass or fail
+ bool should_parse_;
+ /// @brief list of addresses expected after parsing
+ std::vector<IOAddress> addresses_;
+ };
+
+ /// @brief virtual destructor
+ virtual ~SharedNetworkParserTest() = default;
+
+ /// @brief Fetch valid shared network configuration JSON text
+ virtual std::string getWorkingConfig() const = 0;
+ ElementPtr makeTestConfig(const std::string& name, const std::string& json_content) {
+ // Create working config element tree
+ ElementPtr config = Element::fromJSON(getWorkingConfig());
+
+ // Create test element contents
+ ElementPtr content = Element::fromJSON(json_content);
+
+ // Add the test element to working config
+ config->set(name, content);
+ return (config);
+ }
+
+ /// @brief Executes a single "relay" parsing scenario
+ ///
+ /// Each test pass consists of the following steps:
+ /// -# Attempt to parse the given JSON text
+ /// -# If parsing is expected to fail and it does return otherwise fatal fail
+ /// -# If parsing is expected to succeed, fatal fail if it does not
+ /// -# Verify the network's relay address list matches the expected list
+ /// in size and content.
+ ///
+ /// @param test RelayTest which describes the test to conduct
+ void relayTest(const RelayTest& test) {
+ ElementPtr test_config;
+ ASSERT_NO_THROW(test_config =
+ makeTestConfig("relay", test.json_content_));
+
+ // Init our ref to a place holder
+ Network4 dummy;
+ Network& network = dummy;
+
+ // If parsing should fail, call parse expecting a throw.
+ if (!test.should_parse_) {
+ ASSERT_THROW(network = parseIntoNetwork(test_config), DhcpConfigError);
+ // No throw so test outcome is correct, nothing else to do.
+ return;
+ }
+
+ // Should parse without error, let's see if it does.
+ ASSERT_NO_THROW(network = parseIntoNetwork(test_config));
+
+ // It parsed, are the number of entries correct?
+ ASSERT_EQ(test.addresses_.size(), network.getRelayAddresses().size());
+
+ // Are the expected addresses in the list?
+ for (auto exp_address = test.addresses_.begin(); exp_address != test.addresses_.end();
+ ++exp_address) {
+ EXPECT_TRUE(network.hasRelayAddress(*exp_address))
+ << " expected address: " << (*exp_address).toText() << " not found" ;
+ }
+ }
+
+ /// @brief Attempts to parse the given configuration into a shared network
+ ///
+ /// Virtual function used by relayTest() to parse a test configuration.
+ /// Implementation should not catch parsing exceptions.
+ ///
+ /// @param test_config JSON configuration text to parse
+ /// @return A reference to the Network created if parsing is successful
+ virtual Network& parseIntoNetwork(ConstElementPtr test_config) = 0;
+};
+
+
+/// @brief Test fixture class for SharedNetwork4Parser class.
+class SharedNetwork4ParserTest : public SharedNetworkParserTest {
+public:
+
+ /// @brief Creates valid shared network configuration.
+ ///
+ /// @return Valid shared network configuration.
+ virtual std::string getWorkingConfig() const {
+ std::string config = "{"
+ " \"allocator\": \"iterative\","
+ " \"authoritative\": true,"
+ " \"boot-file-name\": \"/dev/null\","
+ " \"client-class\": \"srv1\","
+ " \"interface\": \"eth1961\","
+ " \"match-client-id\": true,"
+ " \"name\": \"bird\","
+ " \"next-server\": \"10.0.0.1\","
+ " \"rebind-timer\": 199,"
+ " \"relay\": { \"ip-addresses\": [ \"10.1.1.1\" ] },"
+ " \"renew-timer\": 99,"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true,"
+ " \"server-hostname\": \"example.org\","
+ " \"require-client-classes\": [ \"runner\" ],"
+ " \"user-context\": { \"comment\": \"example\" },"
+ " \"valid-lifetime\": 399,"
+ " \"min-valid-lifetime\": 299,"
+ " \"max-valid-lifetime\": 499,"
+ " \"calculate-tee-times\": true,"
+ " \"t1-percent\": 0.345,"
+ " \"t2-percent\": 0.721,"
+ " \"ddns-send-updates\": true,"
+ " \"ddns-override-no-update\": true,"
+ " \"ddns-override-client-update\": true,"
+ " \"ddns-replace-client-name\": \"always\","
+ " \"ddns-generated-prefix\": \"prefix\","
+ " \"ddns-qualifying-suffix\": \"example.com.\","
+ " \"hostname-char-set\": \"[^A-Z]\","
+ " \"hostname-char-replacement\": \"x\","
+ " \"store-extended-info\": true,"
+ " \"cache-threshold\": 0.123,"
+ " \"cache-max-age\": 123,"
+ " \"offer-lifetime\": 777,"
+ " \"ddns-update-on-renew\": true,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"id\": 1,"
+ " \"subnet\": \"10.1.2.0/24\","
+ " \"interface\": \"\","
+ " \"renew-timer\": 100,"
+ " \"rebind-timer\": 200,"
+ " \"valid-lifetime\": 300,"
+ " \"min-valid-lifetime\": 200,"
+ " \"max-valid-lifetime\": 400,"
+ " \"match-client-id\": false,"
+ " \"authoritative\": false,"
+ " \"next-server\": \"\","
+ " \"server-hostname\": \"\","
+ " \"boot-file-name\": \"\","
+ " \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false,"
+ " \"4o6-interface\": \"\","
+ " \"4o6-interface-id\": \"\","
+ " \"4o6-subnet\": \"\","
+ " \"calculate-tee-times\": true,"
+ " \"t1-percent\": .45,"
+ " \"t2-percent\": .65,"
+ " \"hostname-char-set\": \"\","
+ " \"cache-threshold\": .20,"
+ " \"cache-max-age\": 50,"
+ " \"allocator\": \"random\""
+ " },"
+ " {"
+ " \"id\": 2,"
+ " \"subnet\": \"192.0.2.0/24\","
+ " \"interface\": \"\","
+ " \"renew-timer\": 10,"
+ " \"rebind-timer\": 20,"
+ " \"valid-lifetime\": 30,"
+ " \"match-client-id\": false,"
+ " \"authoritative\": false,"
+ " \"next-server\": \"\","
+ " \"server-hostname\": \"\","
+ " \"boot-file-name\": \"\","
+ " \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false,"
+ " \"4o6-interface\": \"\","
+ " \"4o6-interface-id\": \"\","
+ " \"4o6-subnet\": \"\","
+ " \"calculate-tee-times\": false,"
+ " \"t1-percent\": .40,"
+ " \"t2-percent\": .80,"
+ " \"cache-threshold\": .15,"
+ " \"cache-max-age\": 5"
+ " }"
+ " ]"
+ "}";
+
+ return (config);
+ }
+
+ virtual Network& parseIntoNetwork(ConstElementPtr test_config) {
+ // Parse configuration.
+ SharedNetwork4Parser parser;
+ network_ = parser.parse(test_config);
+ return (*network_);
+ }
+
+private:
+ SharedNetwork4Ptr network_;
+};
+
+// This test verifies that shared network parser for IPv4 works properly
+// in a positive test scenario.
+TEST_F(SharedNetwork4ParserTest, parse) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network. A bunch of parameters
+ // have to be specified for subnets because subnet parsers expect
+ // that default and global values are set.
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ SharedNetwork4Parser parser;
+ SharedNetwork4Ptr network;
+
+ ASSERT_NO_THROW_LOG(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+
+ // Check basic parameters.
+ EXPECT_TRUE(network->getAuthoritative());
+ EXPECT_EQ("srv1", network->getClientClass().get());
+ EXPECT_EQ("bird", network->getName());
+ EXPECT_EQ("eth1961", network->getIface().get());
+ EXPECT_EQ(99, network->getT1().get());
+ EXPECT_EQ(199, network->getT2().get());
+ EXPECT_EQ(399, network->getValid().get());
+ EXPECT_EQ(299, network->getValid().getMin());
+ EXPECT_EQ(499, network->getValid().getMax());
+ EXPECT_TRUE(network->getCalculateTeeTimes());
+ EXPECT_EQ(0.345, network->getT1Percent());
+ EXPECT_EQ(0.721, network->getT2Percent());
+ EXPECT_EQ("/dev/null", network->getFilename().get());
+ EXPECT_EQ("10.0.0.1", network->getSiaddr().get().toText());
+ EXPECT_EQ("example.org", network->getSname().get());
+ EXPECT_FALSE(network->getReservationsGlobal());
+ EXPECT_TRUE(network->getReservationsInSubnet());
+ EXPECT_TRUE(network->getReservationsOutOfPool());
+ EXPECT_TRUE(network->getDdnsSendUpdates().get());
+ EXPECT_TRUE(network->getDdnsOverrideNoUpdate().get());
+ EXPECT_TRUE(network->getDdnsOverrideClientUpdate().get());
+ EXPECT_EQ(D2ClientConfig::RCM_ALWAYS, network->getDdnsReplaceClientNameMode().get());
+ EXPECT_EQ("prefix", network->getDdnsGeneratedPrefix().get());
+ EXPECT_EQ("example.com.", network->getDdnsQualifyingSuffix().get());
+ EXPECT_EQ("[^A-Z]", network->getHostnameCharSet().get());
+ EXPECT_EQ("x", network->getHostnameCharReplacement().get());
+ EXPECT_TRUE(network->getStoreExtendedInfo().get());
+ EXPECT_EQ(0.123, network->getCacheThreshold());
+ EXPECT_EQ(123, network->getCacheMaxAge().get());
+ EXPECT_EQ(777, network->getOfferLft().get());
+ EXPECT_TRUE(network->getDdnsUpdateOnRenew().get());
+ EXPECT_EQ("iterative", network->getAllocatorType().get());
+
+ // Relay information.
+ auto relay_info = network->getRelayInfo();
+ EXPECT_EQ(1, relay_info.getAddresses().size());
+ EXPECT_TRUE(relay_info.containsAddress(IOAddress("10.1.1.1")));
+
+ // Required client classes.
+ auto required = network->getRequiredClasses();
+ ASSERT_EQ(1, required.size());
+ EXPECT_EQ("runner", *required.cbegin());
+
+ // Check user context.
+ ConstElementPtr context = network->getContext();
+ ASSERT_TRUE(context);
+ EXPECT_TRUE(context->get("comment"));
+
+ // Subnet with id 1
+ Subnet4Ptr subnet = network->getSubnet(SubnetID(1));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("10.1.2.0", subnet->get().first.toText());
+ EXPECT_EQ(300, subnet->getValid().get());
+ EXPECT_EQ(200, subnet->getValid().getMin());
+ EXPECT_EQ(400, subnet->getValid().getMax());
+ EXPECT_FALSE(subnet->getHostnameCharSet().unspecified());
+ EXPECT_EQ("", subnet->getHostnameCharSet().get());
+ EXPECT_EQ("random", subnet->getAllocatorType().get());
+
+ // Subnet with id 2
+ subnet = network->getSubnet(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+ EXPECT_EQ(30, subnet->getValid().get());
+ EXPECT_EQ(30, subnet->getValid().getMin());
+ EXPECT_EQ(30, subnet->getValid().getMax());
+ EXPECT_EQ("[^A-Z]", subnet->getHostnameCharSet().get());
+ EXPECT_EQ("x", subnet->getHostnameCharReplacement().get());
+ EXPECT_EQ("iterative", subnet->getAllocatorType().get());
+
+ // DHCP options
+ ConstCfgOptionPtr cfg_option = network->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionDescriptor opt_dns_servers = cfg_option->get("dhcp4",
+ DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns_servers.option_);
+ Option4AddrLstPtr dns_servers = boost::dynamic_pointer_cast<
+ Option4AddrLst>(opt_dns_servers.option_);
+ ASSERT_TRUE(dns_servers);
+ Option4AddrLst::AddressContainer addresses = dns_servers->getAddresses();
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ("192.0.2.3", addresses[0].toText());
+}
+
+// This test verifies that parser throws an exception when mandatory parameter
+// "name" is not specified.
+TEST_F(SharedNetwork4ParserTest, missingName) {
+ // Remove a name parameter from the valid configuration.
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+ ASSERT_NO_THROW(config_element->remove("name"));
+
+ // Parse configuration specified above.
+ SharedNetwork4Parser parser;
+ SharedNetwork4Ptr network;
+ ASSERT_THROW(network = parser.parse(config_element), DhcpConfigError);
+}
+
+// This test verifies that it's possible to specify client-class,
+// match-client-id, and authoritative on shared-network level.
+TEST_F(SharedNetwork4ParserTest, clientClassMatchClientIdAuthoritative) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ config_element->set("authoritative", Element::create(true));
+ config_element->set("match-client-id", Element::create(false));
+ config_element->set("client-class", Element::create("alpha"));
+
+ // Parse configuration specified above.
+ SharedNetwork4Parser parser;
+ SharedNetwork4Ptr network;
+ network = parser.parse(config_element);
+ ASSERT_TRUE(network);
+
+ EXPECT_EQ("alpha", network->getClientClass().get());
+
+ EXPECT_FALSE(network->getMatchClientId());
+
+ EXPECT_TRUE(network->getAuthoritative());
+}
+
+// This test verifies that parsing of the "relay" element.
+// It checks both valid and invalid scenarios.
+TEST_F(SharedNetwork4ParserTest, relayInfoTests) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Create the vector of test scenarios.
+ std::vector<RelayTest> tests = {
+ {
+ "valid ip-address #1",
+ "{ \"ip-address\": \"192.168.2.1\" }",
+ true,
+ { asiolink::IOAddress("192.168.2.1") }
+ },
+ {
+ "invalid ip-address #1",
+ "{ \"ip-address\": \"not an address\" }",
+ false,
+ { }
+ },
+ {
+ "invalid ip-address #2",
+ "{ \"ip-address\": \"2001:db8::1\" }",
+ false,
+ { }
+ },
+ {
+ "valid ip-addresses #1",
+ "{ \"ip-addresses\": [ ] }",
+ true,
+ {}
+ },
+ {
+ "valid ip-addresses #2",
+ "{ \"ip-addresses\": [ \"192.168.2.1\" ] }",
+ true,
+ { asiolink::IOAddress("192.168.2.1") }
+ },
+ {
+ "valid ip-addresses #3",
+ "{ \"ip-addresses\": [ \"192.168.2.1\", \"192.168.2.2\" ] }",
+ true,
+ { asiolink::IOAddress("192.168.2.1"), asiolink::IOAddress("192.168.2.2") }
+ },
+ {
+ "invalid ip-addresses #1",
+ "{ \"ip-addresses\": [ \"not an address\" ] }",
+ false,
+ { }
+ },
+ {
+ "invalid ip-addresses #2",
+ "{ \"ip-addresses\": [ \"2001:db8::1\" ] }",
+ false,
+ { }
+ },
+ {
+ "invalid both ip-address and ip-addresses",
+ "{"
+ " \"ip-address\": \"192.168.2.1\", "
+ " \"ip-addresses\": [ \"192.168.2.1\", \"192.168.2.2\" ]"
+ " }",
+ false,
+ { }
+ },
+ {
+ "invalid neither ip-address nor ip-addresses",
+ "{}",
+ false,
+ { }
+ }
+ };
+
+ // Iterate over the test scenarios, verifying each prescribed
+ // outcome.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE((*test).description_);
+ relayTest(*test);
+ }
+ }
+}
+
+// This test verifies that the optional interface check works as expected.
+TEST_F(SharedNetwork4ParserTest, iface) {
+ // Basic configuration for shared network.
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+
+ // The interface check can be disabled.
+ SharedNetwork4Parser parser_no_check(false);
+ SharedNetwork4Ptr network;
+ EXPECT_NO_THROW(network = parser_no_check.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_FALSE(network->getIface().unspecified());
+ EXPECT_EQ("eth1961", network->getIface().get());
+
+ // Retry with the interface check enabled.
+ SharedNetwork4Parser parser;
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
+
+ // Configure default test interfaces.
+ IfaceMgrTestConfig ifmgr(true);
+
+ EXPECT_NO_THROW(network = parser_no_check.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_FALSE(network->getIface().unspecified());
+ EXPECT_EQ("eth1961", network->getIface().get());
+
+ EXPECT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_FALSE(network->getIface().unspecified());
+ EXPECT_EQ("eth1961", network->getIface().get());
+}
+
+// This test verifies that shared network parser for IPv4 works properly
+// when using invalid renew and rebind timers.
+TEST_F(SharedNetwork4ParserTest, parseWithInvalidRenewRebind) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network but with a renew-timer value
+ // larger than rebind-timer.
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+ ConstElementPtr valid_element = config_element->get("rebind-timer");
+ int64_t value = valid_element->intValue();
+ valid_element = config_element->get("renew-timer");
+ ElementPtr mutable_element = boost::const_pointer_cast<Element>(valid_element);
+ mutable_element->setValue(value + 1);
+
+ // Parse configuration specified above.
+ SharedNetwork4Parser parser;
+ SharedNetwork4Ptr network;
+
+ // Parser should not throw.
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+
+ // Veriy we emitted the proper log message.
+ addString("DHCPSRV_CFGMGR_RENEW_GTR_REBIND in shared-network bird,"
+ " the value of renew-timer 200 is greater than the value"
+ " of rebind-timer 199, ignoring renew-timer");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that shared network parser for IPv4 works properly
+// when renew and rebind timers are equal.
+TEST_F(SharedNetwork4ParserTest, parseValidWithEqualRenewRebind) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network.
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+ ConstElementPtr valid_element = config_element->get("rebind-timer");
+ int64_t value = valid_element->intValue();
+ valid_element = config_element->get("renew-timer");
+ ElementPtr mutable_element = boost::const_pointer_cast<Element>(valid_element);
+ mutable_element->setValue(value);
+
+ // Parse configuration specified above.
+ SharedNetwork4Parser parser;
+ SharedNetwork4Ptr network;
+
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+}
+
+// Test that FLQ allocator can be used for a shared network.
+TEST_F(SharedNetwork4ParserTest, parseFLQAllocator) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network.
+ std::string config = "{ \"name\": \"lion\", \"allocator\": \"flq\" }";
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ SharedNetwork4Parser parser;
+ SharedNetwork4Ptr network;
+
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_EQ("flq", network->getAllocatorType().get());
+}
+
+
+/// @brief Test fixture class for SharedNetwork6Parser class.
+class SharedNetwork6ParserTest : public SharedNetworkParserTest {
+public:
+
+ /// @brief Constructor.
+ SharedNetwork6ParserTest()
+ : SharedNetworkParserTest(), network_(), use_iface_id_(false) {
+ }
+
+ /// @brief Creates valid shared network configuration.
+ ///
+ /// @return Valid shared network configuration.
+ virtual std::string getWorkingConfig() const {
+ std::string config = "{"
+ " \"client-class\": \"srv1\","
+ + std::string(use_iface_id_ ? "\"interface-id\": " : "\"interface\": ") +
+ "\"eth1961\","
+ " \"name\": \"bird\","
+ " \"preferred-lifetime\": 211,"
+ " \"min-preferred-lifetime\": 111,"
+ " \"max-preferred-lifetime\": 311,"
+ " \"rapid-commit\": true,"
+ " \"rebind-timer\": 199,"
+ " \"relay\": { \"ip-addresses\": [ \"2001:db8:1::1\" ] },"
+ " \"renew-timer\": 99,"
+ " \"require-client-classes\": [ \"runner\" ],"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": true,"
+ " \"user-context\": { },"
+ " \"valid-lifetime\": 399,"
+ " \"min-valid-lifetime\": 299,"
+ " \"max-valid-lifetime\": 499,"
+ " \"calculate-tee-times\": true,"
+ " \"t1-percent\": 0.345,"
+ " \"t2-percent\": 0.721,"
+ " \"ddns-send-updates\": true,"
+ " \"ddns-override-no-update\": true,"
+ " \"ddns-override-client-update\": true,"
+ " \"ddns-replace-client-name\": \"always\","
+ " \"ddns-generated-prefix\": \"prefix\","
+ " \"ddns-qualifying-suffix\": \"example.com.\","
+ " \"hostname-char-set\": \"[^A-Z]\","
+ " \"hostname-char-replacement\": \"x\","
+ " \"store-extended-info\": true,"
+ " \"cache-threshold\": 0.123,"
+ " \"cache-max-age\": 123,"
+ " \"ddns-update-on-renew\": true,"
+ " \"allocator\": \"random\","
+ " \"pd-allocator\": \"iterative\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::cafe\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"id\": 1,"
+ " \"subnet\": \"3000::/16\","
+ " \"interface\": \"\","
+ " \"interface-id\": \"\","
+ " \"renew-timer\": 100,"
+ " \"rebind-timer\": 200,"
+ " \"preferred-lifetime\": 300,"
+ " \"min-preferred-lifetime\": 200,"
+ " \"max-preferred-lifetime\": 400,"
+ " \"valid-lifetime\": 400,"
+ " \"min-valid-lifetime\": 300,"
+ " \"max-valid-lifetime\": 500,"
+ " \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false,"
+ " \"rapid-commit\": false,"
+ " \"hostname-char-set\": \"\","
+ " \"allocator\": \"iterative\","
+ " \"pd-allocator\": \"random\""
+ " },"
+ " {"
+ " \"id\": 2,"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"interface\": \"\","
+ " \"interface-id\": \"\","
+ " \"renew-timer\": 10,"
+ " \"rebind-timer\": 20,"
+ " \"preferred-lifetime\": 30,"
+ " \"valid-lifetime\": 40,"
+ " \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
+ " \"reservations-global\": false,"
+ " \"reservations-in-subnet\": true,"
+ " \"reservations-out-of-pool\": false,"
+ " \"rapid-commit\": false"
+ " }"
+ " ]"
+ "}";
+
+ return (config);
+ }
+
+ virtual Network& parseIntoNetwork(ConstElementPtr test_config) {
+ // Parse configuration.
+ SharedNetwork6Parser parser;
+ network_ = parser.parse(test_config);
+ return (*network_);
+ }
+
+public:
+
+ SharedNetwork6Ptr network_;
+
+ /// Boolean flag indicating if the interface-id should be used instead
+ /// of interface.
+ bool use_iface_id_;
+};
+
+// This test verifies that shared network parser for IPv6 works properly
+// in a positive test scenario.
+TEST_F(SharedNetwork6ParserTest, parse) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network. A bunch of parameters
+ // have to be specified for subnets because subnet parsers expect
+ // that default and global values are set.
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+
+ // Check basic parameters.
+ EXPECT_EQ("srv1", network->getClientClass().get());
+ EXPECT_EQ("bird", network->getName());
+ EXPECT_EQ("eth1961", network->getIface().get());
+ EXPECT_EQ(211, network->getPreferred().get());
+ EXPECT_EQ(111, network->getPreferred().getMin());
+ EXPECT_EQ(311, network->getPreferred().getMax());
+ EXPECT_TRUE(network->getRapidCommit());
+ EXPECT_EQ(99, network->getT1().get());
+ EXPECT_EQ(199, network->getT2().get());
+ EXPECT_EQ(399, network->getValid().get());
+ EXPECT_EQ(299, network->getValid().getMin());
+ EXPECT_EQ(499, network->getValid().getMax());
+ EXPECT_TRUE(network->getCalculateTeeTimes());
+ EXPECT_EQ(0.345, network->getT1Percent());
+ EXPECT_EQ(0.721, network->getT2Percent());
+ EXPECT_TRUE(network->getDdnsSendUpdates().get());
+ EXPECT_TRUE(network->getDdnsOverrideNoUpdate().get());
+ EXPECT_TRUE(network->getDdnsOverrideClientUpdate().get());
+ EXPECT_EQ(D2ClientConfig::RCM_ALWAYS, network->getDdnsReplaceClientNameMode().get());
+ EXPECT_EQ("prefix", network->getDdnsGeneratedPrefix().get());
+ EXPECT_EQ("example.com.", network->getDdnsQualifyingSuffix().get());
+ EXPECT_EQ("[^A-Z]", network->getHostnameCharSet().get());
+ EXPECT_EQ("x", network->getHostnameCharReplacement().get());
+ EXPECT_TRUE(network->getStoreExtendedInfo().get());
+ EXPECT_EQ(0.123, network->getCacheThreshold());
+ EXPECT_EQ(123, network->getCacheMaxAge().get());
+ EXPECT_TRUE(network->getDdnsUpdateOnRenew().get());
+ EXPECT_EQ("random", network->getAllocatorType().get());
+ EXPECT_EQ("iterative", network->getPdAllocatorType().get());
+
+ // Relay information.
+ auto relay_info = network->getRelayInfo();
+ EXPECT_EQ(1, relay_info.getAddresses().size());
+ EXPECT_TRUE(relay_info.containsAddress(IOAddress("2001:db8:1::1")));
+
+ // Required client classes.
+ auto required = network->getRequiredClasses();
+ ASSERT_EQ(1, required.size());
+ EXPECT_EQ("runner", *required.cbegin());
+
+ // Check user context.
+ ConstElementPtr context = network->getContext();
+ ASSERT_TRUE(context);
+ EXPECT_EQ(0, context->size());
+
+ // Subnet with id 1
+ Subnet6Ptr subnet = network->getSubnet(SubnetID(1));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("3000::", subnet->get().first.toText());
+ EXPECT_EQ(300, subnet->getPreferred().get());
+ EXPECT_EQ(200, subnet->getPreferred().getMin());
+ EXPECT_EQ(400, subnet->getPreferred().getMax());
+ EXPECT_EQ(400, subnet->getValid().get());
+ EXPECT_EQ(300, subnet->getValid().getMin());
+ EXPECT_EQ(500, subnet->getValid().getMax());
+ EXPECT_FALSE(subnet->getHostnameCharSet().unspecified());
+ EXPECT_EQ("", subnet->getHostnameCharSet().get());
+ EXPECT_EQ("iterative", subnet->getAllocatorType().get());
+ EXPECT_EQ("random", subnet->getPdAllocatorType().get());
+
+ // Subnet with id 2
+ subnet = network->getSubnet(SubnetID(2));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("2001:db8:1::", subnet->get().first.toText());
+ EXPECT_EQ(30, subnet->getPreferred().get());
+ EXPECT_EQ(30, subnet->getPreferred().getMin());
+ EXPECT_EQ(30, subnet->getPreferred().getMax());
+ EXPECT_EQ(40, subnet->getValid().get());
+ EXPECT_EQ(40, subnet->getValid().getMin());
+ EXPECT_EQ(40, subnet->getValid().getMax());
+ EXPECT_EQ("[^A-Z]", subnet->getHostnameCharSet().get());
+ EXPECT_EQ("x", subnet->getHostnameCharReplacement().get());
+ EXPECT_EQ("random", subnet->getAllocatorType().get());
+ EXPECT_EQ("iterative", subnet->getPdAllocatorType().get());
+
+ // DHCP options
+ ConstCfgOptionPtr cfg_option = network->getCfgOption();
+ ASSERT_TRUE(cfg_option);
+ OptionDescriptor opt_dns_servers = cfg_option->get("dhcp6",
+ D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt_dns_servers.option_);
+ Option6AddrLstPtr dns_servers = boost::dynamic_pointer_cast<
+ Option6AddrLst>(opt_dns_servers.option_);
+ ASSERT_TRUE(dns_servers);
+ Option6AddrLst::AddressContainer addresses = dns_servers->getAddresses();
+ ASSERT_EQ(1, addresses.size());
+ EXPECT_EQ("2001:db8:1::cafe", addresses[0].toText());
+}
+
+// This test verifies that shared network parser for IPv6 works properly
+// in a positive test scenario.
+TEST_F(SharedNetwork6ParserTest, parseWithInterfaceId) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Use the configuration with interface-id instead of interface parameter.
+ use_iface_id_ = true;
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+
+ // Check that interface-id has been parsed.
+ auto opt_iface_id = network->getInterfaceId();
+ ASSERT_TRUE(opt_iface_id);
+}
+
+// This test verifies that shared network parser for IPv6 works properly
+// when using invalid renew and rebind timers.
+TEST_F(SharedNetwork6ParserTest, parseWithInvalidRenewRebind) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network but with a renew-timer value
+ // larger than rebind-timer.
+ use_iface_id_ = true;
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+ ConstElementPtr valid_element = config_element->get("rebind-timer");
+ int64_t value = valid_element->intValue();
+ valid_element = config_element->get("renew-timer");
+ ElementPtr mutable_element = boost::const_pointer_cast<Element>(valid_element);
+ mutable_element->setValue(value + 1);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+
+ // Parser should not throw.
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+
+ // Veriy we emitted the proper log message.
+ addString("DHCPSRV_CFGMGR_RENEW_GTR_REBIND in shared-network bird,"
+ " the value of renew-timer 200 is greater than the value"
+ " of rebind-timer 199, ignoring renew-timer");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that shared network parser for IPv6 works properly
+// when renew and rebind timers are equal.
+TEST_F(SharedNetwork6ParserTest, parseValidWithEqualRenewRebind) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Use the configuration with interface-id instead of interface parameter.
+ use_iface_id_ = true;
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+ ConstElementPtr valid_element = config_element->get("rebind-timer");
+ int64_t value = valid_element->intValue();
+ valid_element = config_element->get("renew-timer");
+ ElementPtr mutable_element = boost::const_pointer_cast<Element>(valid_element);
+ mutable_element->setValue(value);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+}
+
+// This test verifies that error is returned when trying to configure a
+// shared network with both interface and interface id.
+TEST_F(SharedNetwork6ParserTest, mutuallyExclusiveInterfaceId) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Use the configuration with interface-id instead of interface parameter.
+ use_iface_id_ = true;
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Add interface which is mutually exclusive with interface-id
+ config_element->set("interface", Element::create("eth1"));
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
+}
+
+// This test verifies that it's possible to specify client-class
+// on shared-network level.
+TEST_F(SharedNetwork6ParserTest, clientClass) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ config_element->set("client-class", Element::create("alpha"));
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ network = parser.parse(config_element);
+ ASSERT_TRUE(network);
+
+ EXPECT_EQ("alpha", network->getClientClass().get());
+}
+
+// This test verifies that it's possible to specify require-client-classes
+// on shared-network level.
+TEST_F(SharedNetwork6ParserTest, evalClientClasses) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ ElementPtr class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create("beta"));
+ config_element->set("require-client-classes", class_list);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ network = parser.parse(config_element);
+ ASSERT_TRUE(network);
+
+ const ClientClasses& classes = network->getRequiredClasses();
+ EXPECT_EQ(2, classes.size());
+ EXPECT_EQ("alpha, beta", classes.toText());
+}
+
+// This test verifies that bad require-client-classes configs raise
+// expected errors.
+TEST_F(SharedNetwork6ParserTest, badEvalClientClasses) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Element of the list must be strings.
+ ElementPtr class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create(1234));
+ config_element->set("require-client-classes", class_list);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+
+ // Empty class name is forbidden.
+ class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create(""));
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+
+ // And of course the list must be a list even the parser can only
+ // trigger the previous error case...
+ class_list = Element::createMap();
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+}
+
+// This test verifies that v6 parsing of the "relay" element.
+// It checks both valid and invalid scenarios.
+TEST_F(SharedNetwork6ParserTest, relayInfoTests) {
+ IfaceMgrTestConfig ifmgr(true);
+
+
+ // Create the vector of test scenarios.
+ std::vector<RelayTest> tests = {
+ {
+ "valid ip-address #1",
+ "{ \"ip-address\": \"2001:db8::1\" }",
+ true,
+ { asiolink::IOAddress("2001:db8::1") }
+ },
+ {
+ "invalid ip-address #1",
+ "{ \"ip-address\": \"not an address\" }",
+ false,
+ { }
+ },
+ {
+ "invalid ip-address #2",
+ "{ \"ip-address\": \"192.168.2.1\" }",
+ false,
+ { }
+ },
+ {
+ "valid ip-addresses #1",
+ "{ \"ip-addresses\": [ ] }",
+ true,
+ {}
+ },
+ {
+ "valid ip-addresses #2",
+ "{ \"ip-addresses\": [ \"2001:db8::1\" ] }",
+ true,
+ { asiolink::IOAddress("2001:db8::1") }
+ },
+ {
+ "valid ip-addresses #3",
+ "{ \"ip-addresses\": [ \"2001:db8::1\", \"2001:db8::2\" ] }",
+ true,
+ { asiolink::IOAddress("2001:db8::1"), asiolink::IOAddress("2001:db8::2") }
+ },
+ {
+ "invalid ip-addresses #1",
+ "{ \"ip-addresses\": [ \"not an address\" ] }",
+ false,
+ { }
+ },
+ {
+ "invalid ip-addresses #2",
+ "{ \"ip-addresses\": [ \"192.168.1.1\" ] }",
+ false,
+ { }
+ },
+ {
+ "invalid both ip-address and ip-addresses",
+ "{"
+ " \"ip-address\": \"2001:db8::1\", "
+ " \"ip-addresses\": [ \"2001:db8::1\", \"2001:db8::2\" ]"
+ " }",
+ false,
+ { }
+ },
+ {
+ "invalid neither ip-address nor ip-addresses",
+ "{}",
+ false,
+ { }
+ }
+ };
+
+ // Iterate over the test scenarios, verifying each prescribed
+ // outcome.
+ for (auto test = tests.begin(); test != tests.end(); ++test) {
+ {
+ SCOPED_TRACE((*test).description_);
+ relayTest(*test);
+ }
+ }
+}
+
+// This test verifies that the optional interface check works as expected.
+TEST_F(SharedNetwork6ParserTest, iface) {
+ // Basic configuration for shared network.
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+
+ // The interface check can be disabled.
+ SharedNetwork6Parser parser_no_check(false);
+ SharedNetwork6Ptr network;
+ EXPECT_NO_THROW(network = parser_no_check.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_FALSE(network->getIface().unspecified());
+ EXPECT_EQ("eth1961", network->getIface().get());
+
+ // Retry with the interface check enabled.
+ SharedNetwork6Parser parser;
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
+
+ // Configure default test interfaces.
+ IfaceMgrTestConfig ifmgr(true);
+
+ EXPECT_NO_THROW(network = parser_no_check.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_FALSE(network->getIface().unspecified());
+ EXPECT_EQ("eth1961", network->getIface().get());
+
+ EXPECT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_FALSE(network->getIface().unspecified());
+ EXPECT_EQ("eth1961", network->getIface().get());
+}
+
+// Test that the FLQ allocator can't be used for DHCPv6 address assignment.
+TEST_F(SharedNetwork6ParserTest, parseFLQAllocatorNA) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network.
+ std::string config = "{ \"name\": \"lion\", \"allocator\": \"flq\" }";
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
+}
+
+// Test that FLQ allocator can be used for prefix delegation.
+TEST_F(SharedNetwork6ParserTest, parseFLQAllocatorPD) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration for shared network.
+ std::string config = "{ \"name\": \"lion\", \"pd-allocator\": \"flq\" }";
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+
+ ASSERT_NO_THROW(network = parser.parse(config_element));
+ ASSERT_TRUE(network);
+ EXPECT_EQ("flq", network->getPdAllocatorType().get());
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/shared_network_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_unittest.cc
new file mode 100644
index 0000000..00a42b0
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/shared_network_unittest.cc
@@ -0,0 +1,1607 @@
+// Copyright (C) 2017-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/iterative_allocation_state.h>
+#include <util/triplet.h>
+#include <exceptions/exceptions.h>
+#include <testutils/test_to_element.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::test;
+
+namespace {
+
+class TestSubnetIterativeAllocationState;
+
+/// @brief Shared pointer to the @c TestSubnetIterativeAllocationState
+typedef boost::shared_ptr<TestSubnetIterativeAllocationState> TestSubnetIterativeAllocationStatePtr;
+
+/// @brief A derivation exposing the @c last_allocated_time_ member.
+class TestSubnetIterativeAllocationState : public SubnetIterativeAllocationState {
+public:
+
+ /// @brief Creates the state instance.
+ ///
+ /// @param subnet subnet instance for which the state is created.
+ /// @return state instance.
+ static TestSubnetIterativeAllocationStatePtr create(const SubnetPtr& subnet) {
+ auto subnet_prefix = subnet->get();
+ return (boost::make_shared<TestSubnetIterativeAllocationState>
+ (subnet_prefix.first, subnet_prefix.second));
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param prefix subnet prefix.
+ /// @param prefix_length subnet prefix length.
+ TestSubnetIterativeAllocationState(const IOAddress& prefix,
+ const uint8_t prefix_length)
+ : SubnetIterativeAllocationState(prefix, prefix_length) {
+ }
+ using SubnetIterativeAllocationState::last_allocated_time_;
+};
+
+// This test verifies that the SharedNetwork4 factory function creates a
+// valid shared network instance.
+TEST(SharedNetwork4Test, create) {
+ auto network = SharedNetwork4::create("frog");
+ ASSERT_TRUE(network);
+ EXPECT_EQ("frog", network->getName());
+}
+
+// This test verifies the default values set for the shared
+// networks and verifies that the optional values are unspecified.
+TEST(SharedNetwork4Test, defaults) {
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+ EXPECT_TRUE(network->getIface().unspecified());
+ EXPECT_TRUE(network->getIface().empty());
+
+ EXPECT_TRUE(network->getClientClass().unspecified());
+ EXPECT_TRUE(network->getClientClass().empty());
+
+ EXPECT_TRUE(network->getValid().unspecified());
+ EXPECT_EQ(0, network->getValid().get());
+
+ EXPECT_TRUE(network->getT1().unspecified());
+ EXPECT_EQ(0, network->getT1().get());
+
+ EXPECT_TRUE(network->getT2().unspecified());
+ EXPECT_EQ(0, network->getT2().get());
+
+ EXPECT_TRUE(network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT1Percent().get());
+
+ EXPECT_TRUE(network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT2Percent().get());
+
+ EXPECT_TRUE(network->getMatchClientId().unspecified());
+ EXPECT_TRUE(network->getMatchClientId().get());
+
+ EXPECT_TRUE(network->getAuthoritative().unspecified());
+ EXPECT_FALSE(network->getAuthoritative().get());
+
+ EXPECT_TRUE(network->getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(network->getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(network->getHostnameCharSet().unspecified());
+ EXPECT_TRUE(network->getHostnameCharSet().empty());
+
+ EXPECT_TRUE(network->getHostnameCharReplacement().unspecified());
+ EXPECT_TRUE(network->getHostnameCharReplacement().empty());
+
+ EXPECT_TRUE(network->getDdnsUpdateOnRenew().unspecified());
+ EXPECT_FALSE(network->getDdnsSendUpdates().get());
+}
+
+// This test verifies that shared network can be given a name and that
+// this name can be retrieved.
+TEST(SharedNetwork4Test, getName) {
+ // Create shared network with an initial name "dog".
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+ EXPECT_EQ("frog", network->getName());
+
+ // Override the name.
+ network->setName("dog");
+ EXPECT_EQ("dog", network->getName());
+}
+
+// This test verifies that an IPv4 subnet can be added to a shared network.
+// It also verifies that two subnets with the same ID can't be added to
+// a shared network and that a single subnet can't be added to two different
+// shared subnets.
+TEST(SharedNetwork4Test, addSubnet4) {
+ // First, create a network.
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+ // Try to add null pointer. It should throw.
+ Subnet4Ptr subnet;
+ ASSERT_THROW(network->add(subnet), BadValue);
+
+ // Create a valid subnet. It should now be added successfully.
+ subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(15)));
+ ASSERT_NO_THROW(network->add(subnet));
+ ASSERT_EQ(1, network->getAllSubnets()->size());
+
+ // Retrieve the subnet from the network and make sure it is returned
+ // as expected.
+ ASSERT_FALSE(network->getAllSubnets()->empty());
+ Subnet4Ptr returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet->getID(), returned_subnet->getID());
+ SharedNetwork4Ptr network1;
+ subnet->getSharedNetwork(network1);
+ ASSERT_TRUE(network1);
+ EXPECT_TRUE(network1 == network);
+
+ // Create another subnet with the same ID. Adding a network with the
+ // same ID should cause an error.
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(15)));
+ ASSERT_THROW(network->add(subnet2), DuplicateSubnetID);
+
+ // Create another subnet with the same prefix. Adding a network with the
+ // same prefix should cause an error.
+ subnet2.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1234)));
+ ASSERT_THROW(network->add(subnet2), DuplicateSubnetID);
+
+ // Create another network and try to add a subnet to it. It should fail
+ // because the subnet is already associated with the first network.
+ SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+ ASSERT_THROW(network2->add(subnet), InvalidOperation);
+}
+
+// This test verifies that an IPv4 subnet can be replaced in a shared network.
+// It does the same tests than for addSubnet4 (at the exception of conflicts)
+// and check the random order is kept.
+TEST(SharedNetwork4Test, replaceSubnet4) {
+ // First, create a network.
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+ // Try to replace null pointer. It should throw.
+ Subnet4Ptr subnet;
+ ASSERT_THROW(network->replace(subnet), BadValue);
+
+ // Create some valid subnets. they should now be added successfully.
+ subnet.reset(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(15)));
+ ASSERT_NO_THROW(network->add(subnet));
+ subnet.reset(new Subnet4(IOAddress("192.168.0.0"), 24, 10, 20, 30,
+ SubnetID(1)));
+ ASSERT_NO_THROW(network->add(subnet));
+ subnet.reset(new Subnet4(IOAddress("192.168.1.0"), 24, 10, 20, 30,
+ SubnetID(10)));
+ ASSERT_NO_THROW(network->add(subnet));
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+
+ // Create another subnet with another ID. Replace should return false.
+ subnet.reset(new Subnet4(IOAddress("192.168.2.0"), 24, 10, 20, 30,
+ SubnetID(2)));
+ EXPECT_FALSE(network->replace(subnet));
+
+ // Subnets did not changed.
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ auto returned_it = network->getAllSubnets()->begin();
+ Subnet4Ptr returned_subnet = *returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(1, returned_subnet->getID());
+ ++returned_it;
+ returned_subnet = *returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(10, returned_subnet->getID());
+ ++returned_it;
+ returned_subnet = *returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(15, returned_subnet->getID());
+
+ // Reset the returned subnet to the subnet with subnet id 1.
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+
+ // Create another subnet with the same ID than the second subnet.
+ subnet.reset(new Subnet4(IOAddress("192.168.0.0"), 24, 100, 200, 300,
+ SubnetID(1)));
+ EXPECT_TRUE(network->replace(subnet));
+
+ // Second subnet was updated.
+ EXPECT_EQ(10, returned_subnet->getT1().get());
+ EXPECT_EQ(20, returned_subnet->getT2().get());
+ EXPECT_EQ(30, returned_subnet->getValid().get());
+ SharedNetwork4Ptr network1;
+ returned_subnet->getSharedNetwork(network1);
+ EXPECT_FALSE(network1);
+
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+ EXPECT_EQ(100, returned_subnet->getT1().get());
+ EXPECT_EQ(200, returned_subnet->getT2().get());
+ EXPECT_EQ(300, returned_subnet->getValid().get());
+ returned_subnet->getSharedNetwork(network1);
+ EXPECT_TRUE(network1);
+ EXPECT_TRUE(network == network1);
+
+ // Other subnets did not changed.
+ returned_it = network->getAllSubnets()->begin();
+ returned_subnet = *++returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(10, returned_subnet->getID());
+ returned_subnet = *++returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(15, returned_subnet->getID());
+
+ // Create another network and try to replace a subnet to it. It should fail
+ // because the subnet is already associated with the first network.
+ SharedNetwork4Ptr network2(new SharedNetwork4("dog"));
+ ASSERT_THROW(network2->replace(subnet), InvalidOperation);
+
+ // Try to change the prefix. Not recommended but should work.
+ subnet.reset(new Subnet4(IOAddress("192.168.10.0"), 24, 100, 200, 300,
+ SubnetID(1)));
+ EXPECT_TRUE(network->replace(subnet));
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+ EXPECT_EQ("192.168.10.0/24", returned_subnet->toText());
+
+ // but not if the prefix already exists for another subnet.
+ subnet.reset(new Subnet4(IOAddress("192.168.1.0"), 24, 100, 200, 300,
+ SubnetID(1)));
+ EXPECT_FALSE(network->replace(subnet));
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+ EXPECT_EQ("192.168.10.0/24", returned_subnet->toText());
+}
+
+// This test verifies that it is possible to remove a specified subnet.
+TEST(SharedNetwork4Test, delSubnet4) {
+ // Create two subnets and add them to the shared network.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(2)));
+
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+ ASSERT_NO_THROW(network->add(subnet1));
+ ASSERT_NO_THROW(network->add(subnet2));
+
+ // Make sure they have been added successfully.
+ ASSERT_EQ(2, network->getAllSubnets()->size());
+
+ // Try to remove a subnet that doesn't exist in this shared network.
+ // It should cause an error.
+ ASSERT_THROW(network->del(SubnetID(5)), BadValue);
+
+ // Now delete the subnet that exists.
+ ASSERT_NO_THROW(network->del(subnet1->getID()));
+ // We should be left with only one subnet.
+ ASSERT_EQ(1, network->getAllSubnets()->size());
+ Subnet4Ptr subnet_returned = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(subnet_returned);
+ EXPECT_EQ(subnet2->getID(), subnet_returned->getID());
+
+ // Check that shared network has been cleared for the removed subnet.
+ SharedNetwork4Ptr network1;
+ subnet1->getSharedNetwork(network1);
+ EXPECT_FALSE(network1);
+
+ // Remove another subnet and make sure there are no subnets left.
+ ASSERT_NO_THROW(network->del(subnet2->getID()));
+ EXPECT_EQ(0, network->getAllSubnets()->size());
+
+ // The network pointer should be cleared for this second subnet too.
+ SharedNetwork4Ptr network2;
+ subnet1->getSharedNetwork(network2);
+ EXPECT_FALSE(network2);
+}
+
+// This test verifies that it is possible to iterate over the subnets
+// associated with a particular shared network.
+TEST(SharedNetwork4Test, getNextSubnet) {
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+ // Create three subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(2)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("172.16.25.0"), 24, 10, 20, 30,
+ SubnetID(3)));
+ std::vector<Subnet4Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+
+ // Subnets have unique IDs so they should successfully be added to the
+ // network.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ ASSERT_NO_THROW(network->add(subnets[i]))
+ << "failed to add subnet with id " << subnets[i]->getID()
+ << " to shared network";
+ }
+
+ // Collect networks associated with our subnets in the vector.
+ std::vector<SharedNetwork4Ptr> networks;
+ for (auto i = 0; i < subnets.size(); ++i) {
+ SharedNetwork4Ptr network;
+ subnets[i]->getSharedNetwork(network);
+ ASSERT_TRUE(network) << "failed to retrieve shared network for a"
+ << " subnet id " << subnets[i]->getID();
+ networks.push_back(network);
+ }
+
+ // All subnets should be associated with the same network.
+ for (auto i = 1; i < networks.size(); ++i) {
+ EXPECT_TRUE(networks[0] == networks[i]);
+ }
+
+ // Perform the test 3 times where each subnet belonging to the shared
+ // network is treated as a "first" subnet in the call to getNextSubnet.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ Subnet4Ptr s = subnets[i];
+
+ // Iterate over the subnets starting from the subnet with index i.
+ for (auto j = 0; j < subnets.size(); ++j) {
+ // Get next subnet (following the one currently in s).
+ s = networks[0]->getNextSubnet(subnets[i], s->getID());
+ // The last iteration should return empty pointer to indicate end of
+ // the subnets within shared network. If we're not at last iteration
+ // check that the subnet identifier of the returned subnet is valid.
+ if (j < subnets.size() - 1) {
+ ASSERT_TRUE(s) << "retrieving next subnet failed for pair of"
+ " indexes (i, j) = (" << i << ", " << j << ")";
+ const auto expected_subnet_id = (i + j + 1) % subnets.size() + 1;
+ EXPECT_EQ(expected_subnet_id, s->getID());
+ } else {
+ // Null subnet returned for a last iteration.
+ ASSERT_FALSE(s) << "expected null pointer to be returned as"
+ " next subnet for pair of indexes (i, j) = ("
+ << i << ", " << j << ")";
+ }
+ }
+ }
+}
+
+// This test verifies that preferred subnet is returned based on the timestamp
+// when the subnet was last used and allowed client classes.
+TEST(SharedNetwork4Test, getPreferredSubnet) {
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+ // Create five subnets.
+ auto subnet1 = Subnet4::create(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1));
+ auto subnet2 = Subnet4::create(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(2));
+ auto subnet3 = Subnet4::create(IOAddress("172.16.25.0"), 24, 10, 20, 30,
+ SubnetID(3));
+ auto subnet4 = Subnet4::create(IOAddress("172.16.28.0"), 24, 10, 20, 30,
+ SubnetID(4));
+ auto subnet5 = Subnet4::create(IOAddress("172.16.30.0"), 24, 10, 20, 30,
+ SubnetID(5));
+
+ auto state1 = TestSubnetIterativeAllocationState::create(subnet1);
+ auto state2 = TestSubnetIterativeAllocationState::create(subnet1);
+ auto state3 = TestSubnetIterativeAllocationState::create(subnet1);
+ auto state4 = TestSubnetIterativeAllocationState::create(subnet1);
+ auto state5 = TestSubnetIterativeAllocationState::create(subnet1);
+
+ subnet1->setAllocationState(Lease::TYPE_V4, state1);
+ subnet2->setAllocationState(Lease::TYPE_V4, state2);
+ subnet3->setAllocationState(Lease::TYPE_V4, state3);
+ subnet4->setAllocationState(Lease::TYPE_V4, state4);
+ subnet5->setAllocationState(Lease::TYPE_V4, state5);
+
+ // Associate first two subnets with classes.
+ subnet1->allowClientClass("class1");
+ subnet2->allowClientClass("class1");
+
+ std::vector<Subnet4Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+ subnets.push_back(subnet4);
+ subnets.push_back(subnet5);
+
+ // Subnets have unique IDs so they should successfully be added to the
+ // network.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ ASSERT_NO_THROW(network->add(subnets[i]))
+ << "failed to add subnet with id " << subnets[i]->getID()
+ << " to shared network";
+ }
+
+ Subnet4Ptr preferred;
+
+ // Initially, for every subnet we should get the same subnet as the preferred
+ // one, because none of them have been used.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ preferred = network->getPreferredSubnet(subnets[i]);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ }
+
+ // Allocating an address from subnet2 updates the last allocated timestamp
+ // for this subnet, which makes this subnet preferred over subnet1.
+ state2->setLastAllocated(IOAddress("192.0.2.25"));
+ preferred = network->getPreferredSubnet(subnet1);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If selected is subnet2, the same is returned.
+ preferred = network->getPreferredSubnet(subnet2);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // Even though the subnet1 has been most recently used, the preferred
+ // subnet is subnet3 in this case, because of the client class
+ // mismatch.
+ preferred = network->getPreferredSubnet(subnet3);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Same for subnet4.
+ preferred = network->getPreferredSubnet(subnet4);
+ EXPECT_EQ(subnet4->getID(), preferred->getID());
+
+ // Same for subnet5.
+ preferred = network->getPreferredSubnet(subnet5);
+ EXPECT_EQ(subnet5->getID(), preferred->getID());
+
+ // Allocate an address from the subnet3. This makes it preferred to
+ // subnet4 and subnet5.
+ state3->setLastAllocated(IOAddress("172.16.25.23"));
+
+ // If the selected is subnet1, the preferred subnet is subnet2, because
+ // it has the same set of classes as subnet1. The subnet3 can't be
+ // preferred here because of the client class mismatch.
+ preferred = network->getPreferredSubnet(subnet1);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If we select subnet4, the preferred subnet is subnet3 because
+ // it was used more recently.
+ preferred = network->getPreferredSubnet(subnet4);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Repeat the test for subnet3 being a selected subnet.
+ preferred = network->getPreferredSubnet(subnet3);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Allocate an address from remaining subnets and make sure that the
+ // allocation from the subnet4 is slightly more recent. Both are
+ // more recent than subnet3.
+ state4->setLastAllocated(IOAddress("172.16.28.24"));
+ state5->setLastAllocated(IOAddress("172.16.30.1"));
+ state4->last_allocated_time_ += boost::posix_time::seconds(2);
+ state5->last_allocated_time_ += boost::posix_time::seconds(1);
+
+ // The subnet4 should now be preferred.
+ preferred = network->getPreferredSubnet(subnet3);
+ EXPECT_EQ(subnet4->getID(), preferred->getID());
+}
+
+// This test verifies that preferred subnet is returned based on the timestamp
+// when the subnet was last used and allowed client classes.
+TEST(SharedNetwork4Test, getPreferredSubnetMultiThreading) {
+ MultiThreadingTest mt(true);
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+ // Create four subnets.
+ auto subnet1 = Subnet4::create(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1));
+ auto subnet2 = Subnet4::create(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(2));
+ auto subnet3 = Subnet4::create(IOAddress("172.16.25.0"), 24, 10, 20, 30,
+ SubnetID(3));
+ auto subnet4 = Subnet4::create(IOAddress("172.16.28.0"), 24, 10, 20, 30,
+ SubnetID(4));
+
+ // Associate first two subnets with classes.
+ subnet1->allowClientClass("class1");
+ subnet2->allowClientClass("class1");
+
+ std::vector<Subnet4Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+ subnets.push_back(subnet4);
+
+ // Subnets have unique IDs so they should successfully be added to the
+ // network.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ ASSERT_NO_THROW(network->add(subnets[i]))
+ << "failed to add subnet with id " << subnets[i]->getID()
+ << " to shared network";
+ }
+
+ Subnet4Ptr preferred;
+
+ // Initially, for every subnet we should get the same subnet as the preferred
+ // one, because none of them have been used.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ preferred = network->getPreferredSubnet(subnets[i]);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ }
+
+ // Allocating an address from subnet2 updates the last allocated timestamp
+ // for this subnet, which makes this subnet preferred over subnet1
+ auto state2 = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet2->getAllocationState(Lease::TYPE_V4));
+ state2->setLastAllocated(IOAddress("192.0.2.25"));
+ preferred = network->getPreferredSubnet(subnet1);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If selected is subnet2, the same is returned.
+ preferred = network->getPreferredSubnet(subnet2);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // Even though the subnet1 has been most recently used, the preferred
+ // subnet is subnet3 in this case, because of the client class
+ // mismatch.
+ preferred = network->getPreferredSubnet(subnet3);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Same for subnet4.
+ preferred = network->getPreferredSubnet(subnet4);
+ EXPECT_EQ(subnet4->getID(), preferred->getID());
+
+ // Allocate an address from the subnet3. This makes it preferred to
+ // subnet4.
+ auto state3 = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet3->getAllocationState(Lease::TYPE_V4));
+ state3->setLastAllocated(IOAddress("172.16.25.23"));
+
+ // If the selected is subnet1, the preferred subnet is subnet2, because
+ // it has the same set of classes as subnet1. The subnet3 can't be
+ // preferred here because of the client class mismatch.
+ preferred = network->getPreferredSubnet(subnet1);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If we select subnet4, the preferred subnet is subnet3 because
+ // it was used more recently.
+ preferred = network->getPreferredSubnet(subnet4);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Repeat the test for subnet3 being a selected subnet.
+ preferred = network->getPreferredSubnet(subnet3);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+}
+
+// This test verifies that subnetsIncludeMatchClientId() works as expected.
+TEST(SharedNetwork4Test, subnetsIncludeMatchClientId) {
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+ ClientClasses classes;
+
+ // Create a subnet and add it to the shared network.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1)));
+ subnet1->setMatchClientId(false);
+ ASSERT_NO_THROW(network->add(subnet1));
+
+ // The subnet does not match client id.
+ EXPECT_FALSE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes));
+
+ // Create a second subnet and add it.
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(2)));
+ ASSERT_NO_THROW(network->add(subnet2));
+
+ // Default is to match client id.
+ EXPECT_TRUE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes));
+
+ // Add a class.
+ classes.insert("class1");
+
+ //The second subnet is not guarded so matches.
+ EXPECT_TRUE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes));
+
+ // Put the second subnet in another class
+ subnet2->allowClientClass("class2");
+ EXPECT_FALSE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes));
+
+ // Put the second subnet in the class.
+ subnet2->allowClientClass("class1");
+ EXPECT_TRUE(SharedNetwork4::subnetsIncludeMatchClientId(subnet1, classes));
+}
+
+// This test verifies operations on the network's relay list
+TEST(SharedNetwork4Test, relayInfoList) {
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+ EXPECT_FALSE(network->hasRelays());
+ EXPECT_FALSE(network->hasRelayAddress(IOAddress("192.168.2.1")));
+
+ // Add relay addresses to the network.
+ network->addRelayAddress(IOAddress("192.168.2.1"));
+ network->addRelayAddress(IOAddress("192.168.2.2"));
+ network->addRelayAddress(IOAddress("192.168.2.3"));
+
+ // Verify we believe we have relays and we can match them accordingly.
+ EXPECT_TRUE(network->hasRelays());
+ EXPECT_TRUE(network->hasRelayAddress(IOAddress("192.168.2.1")));
+ EXPECT_TRUE(network->hasRelayAddress(IOAddress("192.168.2.2")));
+ EXPECT_TRUE(network->hasRelayAddress(IOAddress("192.168.2.3")));
+ EXPECT_FALSE(network->hasRelayAddress(IOAddress("192.168.2.4")));
+}
+
+// This test verifies that unparsing shared network returns valid structure.
+TEST(SharedNetwork4Test, unparse) {
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+
+ // Set interface name.
+ network->setIface("eth1");
+
+ network->setT1(100);
+ network->setT2(150);
+ network->setValid(200);
+ network->setMatchClientId(false);
+
+ std::string uc = "{ \"comment\": \"bar\", \"foo\": 1}";
+ data::ElementPtr ctx = data::Element::fromJSON(uc);
+ network->setContext(ctx);
+ network->requireClientClass("foo");
+ network->addRelayAddress(IOAddress("192.168.2.1"));
+ network->setAuthoritative(false);
+ network->setMatchClientId(false);
+ network->setReservationsGlobal(false);
+ network->setReservationsInSubnet(true);
+ network->setReservationsOutOfPool(false);
+
+ // Add several subnets.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1)));
+ subnet1->addRelayAddress(IOAddress("10.0.0.1"));
+ subnet1->addRelayAddress(IOAddress("10.0.0.2"));
+
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(2)));
+ network->add(subnet1);
+ network->add(subnet2);
+
+ std::string expected = "{\n"
+ " \"authoritative\": false,\n"
+ " \"interface\": \"eth1\",\n"
+ " \"match-client-id\": false,\n"
+ " \"name\": \"frog\",\n"
+ " \"option-data\": [ ],\n"
+ " \"rebind-timer\": 150,\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ \"192.168.2.1\" ]\n"
+ " },\n"
+ " \"renew-timer\": 100,\n"
+ " \"require-client-classes\": [ \"foo\" ],\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": false,\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"id\": 1,\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ],\n"
+ " \"rebind-timer\": 20,\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ \"10.0.0.1\", \"10.0.0.2\" ]\n"
+ " },\n"
+ " \"renew-timer\": 10,\n"
+ " \"subnet\": \"10.0.0.0/8\",\n"
+ " \"valid-lifetime\": 30,\n"
+ " \"min-valid-lifetime\": 30,\n"
+ " \"max-valid-lifetime\": 30\n"
+ " },\n"
+ " {\n"
+ " \"4o6-interface\": \"\",\n"
+ " \"4o6-interface-id\": \"\",\n"
+ " \"4o6-subnet\": \"\",\n"
+ " \"id\": 2,\n"
+ " \"option-data\": [ ],\n"
+ " \"pools\": [ ],\n"
+ " \"rebind-timer\": 20,\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ ]\n"
+ " },\n"
+ " \"renew-timer\": 10,\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"valid-lifetime\": 30,\n"
+ " \"min-valid-lifetime\": 30,\n"
+ " \"max-valid-lifetime\": 30\n"
+ " }\n"
+ " ],\n"
+ " \"user-context\": { \"comment\": \"bar\", \"foo\": 1 },\n"
+ " \"valid-lifetime\": 200,\n"
+ " \"min-valid-lifetime\": 200,\n"
+ " \"max-valid-lifetime\": 200\n"
+ "}\n";
+
+ test::runToElementTest<SharedNetwork4>(expected, *network);
+}
+
+// This test verifies that when the shared network object is destroyed,
+// the subnets belonging to this shared network will not hold the pointer
+// to the destroyed network.
+TEST(SharedNetwork4Test, destructSharedNetwork) {
+ // Create a network and add a subnet to it.
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+ Subnet4Ptr subnet(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1)));
+ ASSERT_NO_THROW(network->add(subnet));
+
+ // Get the pointer to the network from subnet.
+ SharedNetwork4Ptr subnet_to_network;
+ subnet->getSharedNetwork(subnet_to_network);
+ ASSERT_TRUE(subnet_to_network);
+
+ // Reset the pointer to not hold the reference to the shared network.
+ subnet_to_network.reset();
+
+ // Destroy the network object.
+ network.reset();
+
+ // The reference to the network from the subnet should be lost.
+ subnet->getSharedNetwork(subnet_to_network);
+ ASSERT_FALSE(subnet_to_network);
+}
+
+// This test verifies that it is possible to remove all subnets.
+TEST(SharedNetwork4Test, delAll) {
+ // Create two subnets and add them to the shared network.
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
+ SubnetID(1)));
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24, 10, 20, 30,
+ SubnetID(2)));
+
+ SharedNetwork4Ptr network(new SharedNetwork4("frog"));
+ ASSERT_NO_THROW(network->add(subnet1));
+ ASSERT_NO_THROW(network->add(subnet2));
+
+ // Make sure they have been added successfully.
+ ASSERT_EQ(2, network->getAllSubnets()->size());
+
+ ASSERT_NO_THROW(network->delAll());
+
+ // Now check that there are no subnets.
+ ASSERT_EQ(0, network->getAllSubnets()->size());
+}
+
+// This test verifies that the SharedNetwork6 factory function creates a
+// valid shared network instance.
+TEST(SharedNetwork6Test, create) {
+ auto network = SharedNetwork6::create("frog");
+ ASSERT_TRUE(network);
+ EXPECT_EQ("frog", network->getName());
+}
+
+// This test verifies the default values set for the shared
+// networks and verifies that the optional values are unspecified.
+TEST(SharedNetwork6Test, defaults) {
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+ EXPECT_TRUE(network->getIface().unspecified());
+ EXPECT_TRUE(network->getIface().empty());
+
+ EXPECT_TRUE(network->getClientClass().unspecified());
+ EXPECT_TRUE(network->getClientClass().empty());
+
+ EXPECT_TRUE(network->getValid().unspecified());
+ EXPECT_EQ(0, network->getValid().get());
+
+ EXPECT_TRUE(network->getT1().unspecified());
+ EXPECT_EQ(0, network->getT1().get());
+
+ EXPECT_TRUE(network->getT2().unspecified());
+ EXPECT_EQ(0, network->getT2().get());
+
+ EXPECT_TRUE(network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT1Percent().get());
+
+ EXPECT_TRUE(network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, network->getT2Percent().get());
+
+ EXPECT_TRUE(network->getPreferred().unspecified());
+ EXPECT_EQ(0, network->getPreferred().get());
+
+ EXPECT_TRUE(network->getRapidCommit().unspecified());
+ EXPECT_FALSE(network->getRapidCommit().get());
+
+ EXPECT_TRUE(network->getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(network->getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(network->getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(network->getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(network->getHostnameCharSet().unspecified());
+ EXPECT_TRUE(network->getHostnameCharSet().empty());
+
+ EXPECT_TRUE(network->getHostnameCharReplacement().unspecified());
+ EXPECT_TRUE(network->getHostnameCharReplacement().empty());
+}
+
+// This test verifies that shared network can be given a name and that
+// this name can be retrieved.
+TEST(SharedNetwork6Test, getName) {
+ // Create shared network with an initial name "frog".
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+ EXPECT_EQ("frog", network->getName());
+
+ // Override the name.
+ network->setName("dog");
+ EXPECT_EQ("dog", network->getName());
+}
+
+// This test verifies that an IPv6 subnet can be added to a shared network.
+// It also verifies that two subnets with the same ID can't be added to
+// a shared network and that a single subnet can't be added to two different
+// shared subnets.
+TEST(SharedNetwork6Test, addSubnet6) {
+ // First, create a network.
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+ // Try to add null pointer. It should throw.
+ Subnet6Ptr subnet;
+ ASSERT_THROW(network->add(subnet), BadValue);
+
+ // Create a valid subnet. It should now be added successfully.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, 40,
+ SubnetID(15)));
+ ASSERT_NO_THROW(network->add(subnet));
+ ASSERT_EQ(1, network->getAllSubnets()->size());
+
+ // Retrieve the subnet from the network and make sure it is returned
+ // as expected.
+ Subnet6Ptr returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet->getID(), returned_subnet->getID());
+ SharedNetwork6Ptr network1;
+ subnet->getSharedNetwork(network1);
+ ASSERT_TRUE(network1);
+ EXPECT_TRUE(network1 == network);
+
+ // Create another subnet with the same ID. Adding a network with the
+ // same ID should cause an error.
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+ SubnetID(15)));
+ ASSERT_THROW(network->add(subnet2), DuplicateSubnetID);
+
+ // Create another subnet with the same prefix. Adding a network with the
+ // same prefix should cause an error.
+ subnet2.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30, 40,
+ SubnetID(1234)));
+ ASSERT_THROW(network->add(subnet2), DuplicateSubnetID);
+
+ // Create another network and try to add a subnet to it. It should fail
+ // because the subnet is already associated with the first network.
+ SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+ ASSERT_THROW(network2->add(subnet), InvalidOperation);
+}
+
+// This test verifies that an IPv6 subnet can be replaced in a shared network.
+// It does the same tests than for addSubnet6 (at the exception of conflicts)
+// and check the random order is kept.
+TEST(SharedNetwork6Test, replaceSubnet6) {
+ // First, create a network.
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+ // Try to replace null pointer. It should throw.
+ Subnet6Ptr subnet;
+ ASSERT_THROW(network->replace(subnet), BadValue);
+
+ // Create some valid subnets. they should now be added successfully.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 48, 10, 20, 30, 40,
+ SubnetID(15)));
+ ASSERT_NO_THROW(network->add(subnet));
+ subnet.reset(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30, 40,
+ SubnetID(1)));
+ ASSERT_NO_THROW(network->add(subnet));
+ subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), 64, 10, 20, 30, 40,
+ SubnetID(10)));
+ ASSERT_NO_THROW(network->add(subnet));
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+
+ // Create another subnet with another ID. Replace should return false.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:4::1"), 64, 10, 20, 30, 40,
+ SubnetID(2)));
+ EXPECT_FALSE(network->replace(subnet));
+
+ // Subnets did not changed.
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ auto returned_it = network->getAllSubnets()->begin();
+ Subnet6Ptr returned_subnet = *returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(1, returned_subnet->getID());
+ ++returned_it;
+ returned_subnet = *returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(10, returned_subnet->getID());
+ ++returned_it;
+ returned_subnet = *returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(15, returned_subnet->getID());
+
+ // Reset the returned subnet to the subnet with subnet id 1.
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+
+ // Create another subnet with the same ID than the second subnet.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:2::"), 64, 100, 200, 300, 400,
+ SubnetID(1)));
+ EXPECT_TRUE(network->replace(subnet));
+
+ // Second subnet was updated.
+ EXPECT_EQ(10, returned_subnet->getT1().get());
+ EXPECT_EQ(20, returned_subnet->getT2().get());
+ EXPECT_EQ(30, returned_subnet->getPreferred().get());
+ EXPECT_EQ(40, returned_subnet->getValid().get());
+ SharedNetwork6Ptr network1;
+ returned_subnet->getSharedNetwork(network1);
+ EXPECT_FALSE(network1);
+
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+ EXPECT_EQ(100, returned_subnet->getT1().get());
+ EXPECT_EQ(200, returned_subnet->getT2().get());
+ EXPECT_EQ(300, returned_subnet->getPreferred().get());
+ EXPECT_EQ(400, returned_subnet->getValid().get());
+ returned_subnet->getSharedNetwork(network1);
+ EXPECT_TRUE(network1);
+ EXPECT_TRUE(network == network1);
+
+ // Other subnets did not changed.
+ returned_it = network->getAllSubnets()->begin();
+ returned_subnet = *++returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(10, returned_subnet->getID());
+ returned_subnet = *++returned_it;
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(15, returned_subnet->getID());
+
+ // Create another network and try to replace a subnet to it. It should fail
+ // because the subnet is already associated with the first network.
+ SharedNetwork6Ptr network2(new SharedNetwork6("dog"));
+ ASSERT_THROW(network2->replace(subnet), InvalidOperation);
+
+ // Try to change the prefix. Not recommended but should work.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:10::"), 64, 100, 200, 300,
+ 400, SubnetID(1)));
+ EXPECT_TRUE(network->replace(subnet));
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+ EXPECT_EQ("2001:db8:10::/64", returned_subnet->toText());
+
+ // but not if the prefix already exists for another subnet.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), 64, 100, 200, 300, 400,
+ SubnetID(1)));
+ EXPECT_FALSE(network->replace(subnet));
+ ASSERT_EQ(3, network->getAllSubnets()->size());
+ returned_subnet = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getID());
+ EXPECT_EQ("2001:db8:10::/64", returned_subnet->toText());
+}
+
+// This test verifies that it is possible to remove a specified subnet.
+TEST(SharedNetwork6Test, delSubnet6) {
+ // Create two subnets and add them to the shared network.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+ 40, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+ SubnetID(2)));
+
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+ ASSERT_NO_THROW(network->add(subnet1));
+ ASSERT_NO_THROW(network->add(subnet2));
+
+ // Make sure they have been added successfully.
+ ASSERT_EQ(2, network->getAllSubnets()->size());
+
+ // Try to remove a subnet that doesn't exist in this shared network.
+ // It should cause an error.
+ ASSERT_THROW(network->del(SubnetID(5)), BadValue);
+
+ // Now delete the subnet that exists.
+ ASSERT_NO_THROW(network->del(subnet1->getID()));
+ // We should be left with only one subnet.
+ ASSERT_EQ(1, network->getAllSubnets()->size());
+ Subnet6Ptr subnet_returned = *network->getAllSubnets()->begin();
+ ASSERT_TRUE(subnet_returned);
+ EXPECT_EQ(subnet2->getID(), subnet_returned->getID());
+
+ // Check that shared network has been cleared for the removed subnet.
+ SharedNetwork6Ptr network1;
+ subnet1->getSharedNetwork(network1);
+ EXPECT_FALSE(network1);
+
+ // Remove another subnet and make sure there are no subnets left.
+ ASSERT_NO_THROW(network->del(subnet2->getID()));
+ EXPECT_EQ(0, network->getAllSubnets()->size());
+
+ // The network pointer should be cleared for this second subnet too.
+ SharedNetwork6Ptr network2;
+ subnet1->getSharedNetwork(network2);
+ EXPECT_FALSE(network2);
+}
+
+// This test verifies that it is possible to iterate over the subnets
+// associated with a particular shared network.
+TEST(SharedNetwork6Test, getNextSubnet) {
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+ // Create three subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+ 40, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+ SubnetID(2)));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:2::"), 64, 10, 20, 30,
+ 40, SubnetID(3)));
+ std::vector<Subnet6Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+
+ // Subnets have unique IDs so they should successfully be added to the
+ // network.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ ASSERT_NO_THROW(network->add(subnets[i]))
+ << "failed to add subnet with id " << subnets[i]->getID()
+ << " to shared network";
+ }
+
+ // Collect networks associated with our subnets in the vector.
+ std::vector<SharedNetwork6Ptr> networks;
+ for (auto i = 0; i < subnets.size(); ++i) {
+ SharedNetwork6Ptr network;
+ subnets[i]->getSharedNetwork(network);
+ ASSERT_TRUE(network) << "failed to retrieve shared network for a"
+ << " subnet id " << subnets[i]->getID();
+ networks.push_back(network);
+ }
+
+ // All subnets should be associated with the same network.
+ for (auto i = 1; i < networks.size(); ++i) {
+ EXPECT_TRUE(networks[0] == networks[i]);
+ }
+
+ // Perform the test 3 times where each subnet belonging to the shared
+ // network is treated as a "first" subnet in the call to getNextSubnet.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ Subnet6Ptr s = subnets[i];
+
+ // Iterate over the subnets starting from the subnet with index i.
+ for (auto j = 0; j < subnets.size(); ++j) {
+ // Get next subnet (following the one currently in s).
+ s = networks[0]->getNextSubnet(subnets[i], s->getID());
+ // The last iteration should return empty pointer to indicate end of
+ // the subnets within shared network. If we're not at last iteration
+ // check that the subnet identifier of the returned subnet is valid.
+ if (j < subnets.size() - 1) {
+ ASSERT_TRUE(s) << "retrieving next subnet failed for pair of"
+ " indexes (i, j) = (" << i << ", " << j << ")";
+ const auto expected_subnet_id = (i + j + 1) % subnets.size() + 1;
+ EXPECT_EQ(expected_subnet_id, s->getID());
+ } else {
+ // Null subnet returned for a last iteration.
+ ASSERT_FALSE(s) << "expected null pointer to be returned as"
+ " next subnet for pair of indexes (i, j) = ("
+ << i << ", " << j << ")";
+ }
+ }
+ }
+}
+
+// This test verifies that preferred subnet is returned based on the timestamp
+// when the subnet was last used and allowed client classes.
+TEST(SharedNetwork6Test, getPreferredSubnet) {
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+ // Create four subnets.
+ auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+ 40, SubnetID(1));
+ auto subnet2 = Subnet6::create(IOAddress("3000::"), 16, 10, 20, 30, 40,
+ SubnetID(2));
+ auto subnet3 = Subnet6::create(IOAddress("2001:db8:2::"), 64, 10, 20, 30,
+ 40, SubnetID(3));
+ auto subnet4 = Subnet6::create(IOAddress("3000:1::"), 64, 10, 20, 30,
+ 40, SubnetID(4));
+
+ // Associate first two subnets with classes.
+ subnet1->allowClientClass("class1");
+ subnet2->allowClientClass("class1");
+
+ std::vector<Subnet6Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+ subnets.push_back(subnet4);
+
+ // Subnets have unique IDs so they should successfully be added to the
+ // network.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ ASSERT_NO_THROW(network->add(subnets[i]))
+ << "failed to add subnet with id " << subnets[i]->getID()
+ << " to shared network";
+ }
+
+ Subnet6Ptr preferred;
+
+ // Initially, for every subnet we should get the same subnet as the preferred
+ // one, because none of them have been used.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_NA);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_TA);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_PD);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ }
+
+ // Allocating an address from subnet2 updates the last allocated timestamp
+ // for this subnet, which makes this subnet preferred over subnet1
+ auto state = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet2->getAllocationState(Lease::TYPE_NA));
+ state->setLastAllocated(IOAddress("2001:db8:1:2::"));
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If selected is subnet2, the same is returned.
+ preferred = network->getPreferredSubnet(subnet2, Lease::TYPE_NA);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // The preferred subnet is dependent on the lease type. For the PD
+ // we should get the same subnet as selected.
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+ EXPECT_EQ(subnet1->getID(), preferred->getID());
+
+ // Although, if we pick a prefix from the subnet2, we should get the
+ // subnet2 as preferred instead.
+ state = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet2->getAllocationState(Lease::TYPE_PD));
+ state->setLastAllocated(IOAddress("3000:1234::"));
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // Even though the subnet1 has been most recently used, the preferred
+ // subnet is subnet3 in this case, because of the client class
+ // mismatch.
+ preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Same for subnet4.
+ preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+ EXPECT_EQ(subnet4->getID(), preferred->getID());
+
+ // Allocate an address from the subnet3. This makes it preferred to
+ // subnet4.
+ state = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet3->getAllocationState(Lease::TYPE_NA));
+ state->setLastAllocated(IOAddress("2001:db8:2:1234::"));
+
+ // If the selected is subnet1, the preferred subnet is subnet2, because
+ // it has the same set of classes as subnet1. The subnet3 can't be
+ // preferred here because of the client class mismatch.
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If we select subnet4, the preferred subnet is subnet3 because
+ // it was used more recently.
+ preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Repeat the test for subnet3 being a selected subnet.
+ preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+}
+
+// This test verifies that preferred subnet is returned based on the timestamp
+// when the subnet was last used and allowed client classes.
+TEST(SharedNetwork6Test, getPreferredSubnetMultiThreading) {
+ MultiThreadingTest mt(true);
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+ // Create four subnets.
+ auto subnet1 = Subnet6::create(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+ 40, SubnetID(1));
+ auto subnet2 = Subnet6::create(IOAddress("3000::"), 16, 10, 20, 30, 40,
+ SubnetID(2));
+ auto subnet3 = Subnet6::create(IOAddress("2001:db8:2::"), 64, 10, 20, 30,
+ 40, SubnetID(3));
+ auto subnet4 = Subnet6::create(IOAddress("3000:1::"), 64, 10, 20, 30,
+ 40, SubnetID(4));
+
+ // Associate first two subnets with classes.
+ subnet1->allowClientClass("class1");
+ subnet2->allowClientClass("class1");
+
+ std::vector<Subnet6Ptr> subnets;
+ subnets.push_back(subnet1);
+ subnets.push_back(subnet2);
+ subnets.push_back(subnet3);
+ subnets.push_back(subnet4);
+
+ // Subnets have unique IDs so they should successfully be added to the
+ // network.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ ASSERT_NO_THROW(network->add(subnets[i]))
+ << "failed to add subnet with id " << subnets[i]->getID()
+ << " to shared network";
+ }
+
+ Subnet6Ptr preferred;
+
+ // Initially, for every subnet we should get the same subnet as the preferred
+ // one, because none of them have been used.
+ for (auto i = 0; i < subnets.size(); ++i) {
+ preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_NA);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_TA);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ preferred = network->getPreferredSubnet(subnets[i], Lease::TYPE_PD);
+ EXPECT_EQ(subnets[i]->getID(), preferred->getID());
+ }
+
+ // Allocating an address from subnet2 updates the last allocated timestamp
+ // for this subnet, which makes this subnet preferred over subnet1.
+ auto state = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet2->getAllocationState(Lease::TYPE_NA));
+ state->setLastAllocated(IOAddress("2001:db8:1:2::"));
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If selected is subnet2, the same is returned.
+ preferred = network->getPreferredSubnet(subnet2, Lease::TYPE_NA);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // The preferred subnet is dependent on the lease type. For the PD
+ // we should get the same subnet as selected.
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+ EXPECT_EQ(subnet1->getID(), preferred->getID());
+
+ // Although, if we pick a prefix from the subnet2, we should get the
+ // subnet2 as preferred instead.
+ state = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet2->getAllocationState(Lease::TYPE_PD));
+ state->setLastAllocated(IOAddress("3000:1234::"));
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_PD);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // Even though the subnet1 has been most recently used, the preferred
+ // subnet is subnet3 in this case, because of the client class
+ // mismatch.
+ preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Same for subnet4.
+ preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+ EXPECT_EQ(subnet4->getID(), preferred->getID());
+
+ // Allocate an address from the subnet3. This makes it preferred to
+ // subnet4.
+ state = boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet3->getAllocationState(Lease::TYPE_NA));
+ state->setLastAllocated(IOAddress("2001:db8:2:1234::"));
+
+ // If the selected is subnet1, the preferred subnet is subnet2, because
+ // it has the same set of classes as subnet1. The subnet3 can't be
+ // preferred here because of the client class mismatch.
+ preferred = network->getPreferredSubnet(subnet1, Lease::TYPE_NA);
+ EXPECT_EQ(subnet2->getID(), preferred->getID());
+
+ // If we select subnet4, the preferred subnet is subnet3 because
+ // it was used more recently.
+ preferred = network->getPreferredSubnet(subnet4, Lease::TYPE_NA);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+
+ // Repeat the test for subnet3 being a selected subnet.
+ preferred = network->getPreferredSubnet(subnet3, Lease::TYPE_NA);
+ EXPECT_EQ(subnet3->getID(), preferred->getID());
+}
+
+// This test verifies operations on the network's relay list
+TEST(SharedNetwork6Test, relayInfoList) {
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+
+ EXPECT_FALSE(network->hasRelays());
+ EXPECT_FALSE(network->hasRelayAddress(IOAddress("2001:db8:2::1")));
+
+ // Add relay addresses to the network.
+ network->addRelayAddress(IOAddress("2001:db8:2::1"));
+ network->addRelayAddress(IOAddress("2001:db8:2::2"));
+ network->addRelayAddress(IOAddress("2001:db8:2::3"));
+
+ // Verify we believe we have relays and we can match them accordingly.
+ EXPECT_TRUE(network->hasRelays());
+ EXPECT_TRUE(network->hasRelayAddress(IOAddress("2001:db8:2::1")));
+ EXPECT_TRUE(network->hasRelayAddress(IOAddress("2001:db8:2::2")));
+ EXPECT_TRUE(network->hasRelayAddress(IOAddress("2001:db8:2::3")));
+ EXPECT_FALSE(network->hasRelayAddress(IOAddress("2001:db8:2::4")));
+}
+
+// This test verifies that unparsing shared network returns valid structure.
+TEST(SharedNetwork6Test, unparse) {
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+ network->setIface("eth1");
+ network->setT1(100);
+ network->setT2(150);
+ network->setPreferred(200);
+ network->setValid(300);
+ network->setRapidCommit(true);
+ network->requireClientClass("foo");
+
+ data::ElementPtr ctx = data::Element::fromJSON("{ \"foo\": \"bar\" }");
+ network->setContext(ctx);
+ network->requireClientClass("foo");
+
+ network->addRelayAddress(IOAddress("2001:db8:1::7"));
+ network->addRelayAddress(IOAddress("2001:db8:1::8"));
+
+ network->setRapidCommit(true);
+ network->setReservationsGlobal(false);
+ network->setReservationsInSubnet(true);
+ network->setReservationsOutOfPool(false);
+
+ // Include interface-id at shared network level. After unparsing the
+ // network we should only see it at shared network level and not at
+ // the subnet level.
+ std::string iface_id_value = "vlan102";
+ OptionBuffer iface_id_buffer(iface_id_value.begin(), iface_id_value.end());
+ OptionPtr iface_id_opt(new Option(Option::V6, D6O_INTERFACE_ID, iface_id_buffer));
+ network->setInterfaceId(iface_id_opt);
+
+ // Add several subnets.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+ 40, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+ SubnetID(2)));
+ subnet2->addRelayAddress(IOAddress("2001:db8:1::8"));
+
+ // Set subnet specific interface-id for subnet2. This is to ensure that
+ // the subnet specific value is not overridden by shared network specific
+ // value.
+ std::string subnet_interface_id_value = "vlan222";
+ OptionBuffer subnet_iface_id_buffer(subnet_interface_id_value.begin(),
+ subnet_interface_id_value.end());
+ OptionPtr subnet_iface_id_opt(new Option(Option::V6, D6O_INTERFACE_ID, subnet_iface_id_buffer));
+ subnet2->setInterfaceId(subnet_iface_id_opt);
+
+ network->add(subnet1);
+ network->add(subnet2);
+
+ std::string expected = "{\n"
+ " \"interface\": \"eth1\",\n"
+ " \"interface-id\": \"vlan102\",\n"
+ " \"name\": \"frog\",\n"
+ " \"option-data\": [ ],\n"
+ " \"preferred-lifetime\": 200,\n"
+ " \"min-preferred-lifetime\": 200,\n"
+ " \"max-preferred-lifetime\": 200,\n"
+ " \"rapid-commit\": true,\n"
+ " \"rebind-timer\": 150,\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ \"2001:db8:1::7\", \"2001:db8:1::8\" ]\n"
+ " },\n"
+ " \"renew-timer\": 100,\n"
+ " \"require-client-classes\": [ \"foo\" ],\n"
+ " \"reservations-global\": false,\n"
+ " \"reservations-in-subnet\": true,\n"
+ " \"reservations-out-of-pool\": false,\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 1,\n"
+ " \"option-data\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"pools\": [ ],\n"
+ " \"preferred-lifetime\": 30,\n"
+ " \"min-preferred-lifetime\": 30,\n"
+ " \"max-preferred-lifetime\": 30,\n"
+ " \"rebind-timer\": 20,\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ ]\n"
+ " },\n"
+ " \"renew-timer\": 10,\n"
+ " \"subnet\": \"2001:db8:1::/64\",\n"
+ " \"valid-lifetime\": 40,\n"
+ " \"min-valid-lifetime\": 40,\n"
+ " \"max-valid-lifetime\": 40\n"
+ " },\n"
+ " {\n"
+ " \"id\": 2,\n"
+ " \"interface-id\": \"vlan222\",\n"
+ " \"option-data\": [ ],\n"
+ " \"pd-pools\": [ ],\n"
+ " \"pools\": [ ],\n"
+ " \"preferred-lifetime\": 30,\n"
+ " \"min-preferred-lifetime\": 30,\n"
+ " \"max-preferred-lifetime\": 30,\n"
+ " \"rebind-timer\": 20,\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ \"2001:db8:1::8\" ]\n"
+ " },\n"
+ " \"renew-timer\": 10,\n"
+ " \"subnet\": \"3000::/16\",\n"
+ " \"valid-lifetime\": 40,\n"
+ " \"min-valid-lifetime\": 40,\n"
+ " \"max-valid-lifetime\": 40\n"
+ " }\n"
+ " ],\n"
+ " \"user-context\": { \"foo\": \"bar\" },\n"
+ " \"valid-lifetime\": 300,\n"
+ " \"min-valid-lifetime\": 300,\n"
+ " \"max-valid-lifetime\": 300\n"
+ "}\n";
+
+ test::runToElementTest<SharedNetwork6>(expected, *network);
+}
+
+// This test verifies that when the shared network object is destroyed,
+// the subnets belonging to this shared network will not hold the pointer
+// to the destroyed network.
+TEST(SharedNetwork6Test, destructSharedNetwork) {
+ // Create a network and add a subnet to it.
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+ 40, SubnetID(1)));
+ ASSERT_NO_THROW(network->add(subnet));
+
+ // Get the pointer to the network from subnet.
+ SharedNetwork6Ptr subnet_to_network;
+ subnet->getSharedNetwork(subnet_to_network);
+ ASSERT_TRUE(subnet_to_network);
+
+ // Reset the pointer to not hold the reference to the shared network.
+ subnet_to_network.reset();
+
+ // Destroy the network object.
+ network.reset();
+
+ // The reference to the network from the subnet should be lost.
+ subnet->getSharedNetwork(subnet_to_network);
+ ASSERT_FALSE(subnet_to_network);
+}
+
+// This test verifies that it is possible to remove all subnets.
+TEST(SharedNetwork6Test, delAll) {
+ // Create two subnets and add them to the shared network.
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
+ 40, SubnetID(1)));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 16, 10, 20, 30, 40,
+ SubnetID(2)));
+
+ SharedNetwork6Ptr network(new SharedNetwork6("frog"));
+ ASSERT_NO_THROW(network->add(subnet1));
+ ASSERT_NO_THROW(network->add(subnet2));
+
+ // Make sure they have been added successfully.
+ ASSERT_EQ(2, network->getAllSubnets()->size());
+
+ ASSERT_NO_THROW(network->delAll());
+
+ // Now check that there are no subnets.
+ ASSERT_EQ(0, network->getAllSubnets()->size());
+}
+
+// This test verifies that the IPv4 shared network can be fetched by name.
+TEST(SharedNetworkFetcherTest, getSharedNetwork4ByName) {
+ SharedNetwork4Collection collection;
+
+ // Shared network hasn't been added to the collection. A null pointer should
+ // be returned.
+ auto network = SharedNetworkFetcher4::get(collection, "network1");
+ EXPECT_FALSE(network);
+
+ network.reset(new SharedNetwork4("network1"));
+ EXPECT_NO_THROW(collection.push_back(network));
+
+ network.reset(new SharedNetwork4("network2"));
+ EXPECT_NO_THROW(collection.push_back(network));
+
+ network = SharedNetworkFetcher4::get(collection, "network1");
+ ASSERT_TRUE(network);
+ EXPECT_EQ("network1", network->getName());
+
+ network = SharedNetworkFetcher4::get(collection, "network2");
+ ASSERT_TRUE(network);
+ EXPECT_EQ("network2", network->getName());
+}
+
+// This test verifies that the IPv6 shared network can be fetched by name.
+TEST(SharedNetworkFetcherTest, getSharedNetwork6ByName) {
+ SharedNetwork6Collection collection;
+
+ // Shared network hasn't been added to the collection. A null pointer should
+ // be returned.
+ auto network = SharedNetworkFetcher6::get(collection, "network1");
+ EXPECT_FALSE(network);
+
+ network.reset(new SharedNetwork6("network1"));
+ EXPECT_NO_THROW(collection.push_back(network));
+
+ network.reset(new SharedNetwork6("network2"));
+ EXPECT_NO_THROW(collection.push_back(network));
+
+ network = SharedNetworkFetcher6::get(collection, "network1");
+ ASSERT_TRUE(network);
+ EXPECT_EQ("network1", network->getName());
+
+ network = SharedNetworkFetcher6::get(collection, "network2");
+ ASSERT_TRUE(network);
+ EXPECT_EQ("network2", network->getName());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/shared_networks_list_parser_unittest.cc b/src/lib/dhcpsrv/tests/shared_networks_list_parser_unittest.cc
new file mode 100644
index 0000000..cf7516f
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/shared_networks_list_parser_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright (C) 2017-2020 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 <cc/data.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/parsers/shared_networks_list_parser.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+// This is a basic test verifying that all shared networks are correctly
+// parsed.
+TEST(SharedNetworkListParserTest, parse) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration with array of shared networks.
+ std::string config = "["
+ " {"
+ " \"name\": \"bird\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"name\": \"monkey\","
+ " \"interface\": \"eth1\","
+ " \"user-context\": { \"comment\": \"example\" }"
+ " }"
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ SharedNetworks4ListParser parser;
+ CfgSharedNetworks4Ptr cfg(new CfgSharedNetworks4());
+ ASSERT_NO_THROW(parser.parse(cfg, config_element));
+
+ SharedNetwork4Ptr network1 = cfg->getByName("bird");
+ ASSERT_TRUE(network1);
+ EXPECT_EQ("bird", network1->getName());
+ EXPECT_EQ("eth0", network1->getIface().get());
+ EXPECT_FALSE(network1->getContext());
+
+ SharedNetwork4Ptr network2 = cfg->getByName("monkey");
+ ASSERT_TRUE(network2);
+ EXPECT_EQ("monkey", network2->getName());
+ EXPECT_EQ("eth1", network2->getIface().get());
+ ASSERT_TRUE(network2->getContext());
+ EXPECT_EQ(1, network2->getContext()->size());
+ EXPECT_TRUE(network2->getContext()->get("comment"));
+}
+
+// This test verifies that specifying two networks with the same name
+// yields an error.
+TEST(SharedNetworkListParserTest, duplicatedName) {
+ IfaceMgrTestConfig ifmgr(true);
+
+ // Basic configuration with two networks having the same name.
+ std::string config = "["
+ " {"
+ " \"name\": \"bird\","
+ " \"interface\": \"eth0\""
+ " },"
+ " {"
+ " \"name\": \"bird\","
+ " \"interface\": \"eth1\""
+ " }"
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ SharedNetworks4ListParser parser;
+ CfgSharedNetworks4Ptr cfg(new CfgSharedNetworks4());
+ EXPECT_THROW(parser.parse(cfg, config_element), DhcpConfigError);
+}
+
+// This test verifies that the optional interface check works as expected.
+TEST(SharedNetworkListParserTest, iface) {
+ // Basic configuration with a shared network.
+ std::string config = "["
+ " {"
+ " \"name\": \"bird\","
+ " \"interface\": \"eth1961\""
+ " }"
+ "]";
+
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // The interface check can be disabled.
+ SharedNetworks6ListParser parser_no_check(false);
+ CfgSharedNetworks6Ptr cfg(new CfgSharedNetworks6());
+ EXPECT_NO_THROW(parser_no_check.parse(cfg, config_element));
+ cfg.reset(new CfgSharedNetworks6());
+
+ // Retry with the interface check enabled.
+ SharedNetworks6ListParser parser;
+ EXPECT_THROW(parser.parse(cfg, config_element), DhcpConfigError);
+ cfg.reset(new CfgSharedNetworks6());
+
+ // Configure default test interfaces.
+ IfaceMgrTestConfig ifmgr(true);
+ EXPECT_NO_THROW(parser_no_check.parse(cfg, config_element));
+ cfg.reset(new CfgSharedNetworks6());
+ EXPECT_NO_THROW(parser.parse(cfg, config_element));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc
new file mode 100644
index 0000000..9a54eb3
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/srv_config_unittest.cc
@@ -0,0 +1,2333 @@
+// Copyright (C) 2014-2023 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 <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/srv_config.h>
+#include <dhcpsrv/subnet.h>
+#include <process/logging_info.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::process;
+using namespace isc::util;
+
+// Those are the tests for SrvConfig storage. Right now they are minimal,
+// but the number is expected to grow significantly once we migrate more
+// parameters from CfgMgr storage to SrvConfig storage.
+
+namespace {
+
+/// @brief Derivation of the @c ConfigBase not being @c SrvConfig.
+///
+/// This is used to verify that the appropriate error is returned
+/// when other derivation of the @c ConfigBase than @c SrvConfig
+/// is used.
+class NonSrvConfig : public ConfigBase { };
+
+/// @brief Number of IPv4 and IPv6 subnets to be created for a test.
+const int TEST_SUBNETS_NUM = 3;
+
+/// @brief Test fixture class for testing configuration data storage.
+class SrvConfigTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Creates IPv4 and IPv6 subnets for unit test. The number of subnets
+ /// is @c TEST_SUBNETS_NUM for IPv4 and IPv6 each.
+ SrvConfigTest()
+ : iface_mgr_test_config_(true),
+ ref_dictionary_(new ClientClassDictionary()) {
+
+ // Disable DDNS.
+ enableD2Client(false);
+
+ // Create IPv4 subnets.
+ for (int i = 0; i < TEST_SUBNETS_NUM; ++i) {
+ // Default triplet carried undefined value.
+ Triplet<uint32_t> def_triplet;
+ // Create a collection of subnets: 192.0.X.0/24 where X is
+ // 0, 1, 2 etc.
+ Subnet4Ptr subnet(new Subnet4(IOAddress(0xC0000000 | (i << 2)),
+ 24, def_triplet, def_triplet,
+ 4000, SubnetID(100 + i)));
+ test_subnets4_.insert(subnet);
+ }
+ // Create IPv6 subnets.
+ IOAddress prefix("2001:db8:1::");
+ for (int i = 0; i < TEST_SUBNETS_NUM; ++i) {
+ // This is a base prefix. All other prefixes will be created by
+ // modifying this one.
+ std::vector<uint8_t> prefix_bytes = prefix.toBytes();
+ // Modify 5th byte of the prefix, so 2001:db8:1::0 becomes
+ // 2001:db8:2::0 etc.
+ ++prefix_bytes[5];
+ prefix = IOAddress::fromBytes(prefix.getFamily(), &prefix_bytes[0]);
+ Subnet6Ptr subnet(new Subnet6(prefix, 64, 1000, 2000, 3000, 4000,
+ SubnetID(200 + i)));
+ test_subnets6_.insert(subnet);
+ }
+
+ // Build our reference dictionary of client classes
+ ref_dictionary_->addClass("cc1", ExpressionPtr(),
+ "", false, false, CfgOptionPtr());
+ ref_dictionary_->addClass("cc2", ExpressionPtr(),
+ "", false, false, CfgOptionPtr());
+ ref_dictionary_->addClass("cc3", ExpressionPtr(),
+ "", false, false, CfgOptionPtr());
+ }
+
+
+ /// @brief Destructor.
+ virtual ~SrvConfigTest() {
+ }
+
+ /// @brief Convenience function which adds IPv4 subnet to the configuration.
+ ///
+ /// @param index Index of the subnet in the @c test_subnets4_ collection
+ /// which should be added to the configuration. The configuration is stored
+ /// in the @ conf_ member. This value must be lower than
+ /// @c TEST_SUBNETS_NUM.
+ ///
+ /// @todo Until the subnets configuration is migrated from the @c CfgMgr to
+ /// the @c SrvConfig object, this function adds the subnet to the
+ /// @c CfgMgr. Once, the subnet configuration is held in the
+ /// @c SrvConfig this function must be modified to store the subnets in
+ /// the @c conf_ object.
+ void addSubnet4(const unsigned int index);
+
+ /// @brief Convenience function which adds IPv6 subnet to the configuration.
+ ///
+ /// @param index Index of the subnet in the @c test_subnets6_ collection
+ /// which should be added to the configuration. The configuration is stored
+ /// in the @ conf_ member. This value must be lower than
+ /// @c TEST_SUBNETS_NUM.
+ ///
+ /// @todo Until the subnets configuration is migrated from the @c CfgMgr to
+ /// the @c SrvConfig object, this function adds the subnet to the
+ /// @c CfgMgr. Once, the subnet configuration is held in the
+ /// @c SrvConfig this function must be modified to store the subnets in
+ /// @c conf_ object.
+ void addSubnet6(const unsigned int index);
+
+ /// @brief Enable/disable DDNS.
+ ///
+ /// @param enable A boolean value indicating if the DDNS should be
+ /// enabled (true) or disabled (false).
+ void enableD2Client(const bool enable);
+
+ /// @brief Stores configuration.
+ SrvConfig conf_;
+ /// @brief A collection of IPv4 subnets used by unit tests.
+ Subnet4Collection test_subnets4_;
+ /// @brief A collection of IPv6 subnets used by unit tests.
+ Subnet6Collection test_subnets6_;
+ /// @brief Fakes interface configuration.
+ isc::dhcp::test::IfaceMgrTestConfig iface_mgr_test_config_;
+
+ /// @brief Client class dictionary with fixed content
+ ClientClassDictionaryPtr ref_dictionary_;
+};
+
+void
+SrvConfigTest::addSubnet4(const unsigned int index) {
+ if (index >= TEST_SUBNETS_NUM) {
+ FAIL() << "Subnet index " << index << "out of range (0.."
+ << TEST_SUBNETS_NUM << "): unable to add IPv4 subnet";
+ }
+ // std::advance is not available for this iterator.
+ auto it = test_subnets4_.begin();
+ for (unsigned int i = 0; i < index; ++i, ++it) {
+ ASSERT_FALSE(it == test_subnets4_.end());
+ }
+ conf_.getCfgSubnets4()->add(*it);
+}
+
+void
+SrvConfigTest::addSubnet6(const unsigned int index) {
+ if (index >= TEST_SUBNETS_NUM) {
+ FAIL() << "Subnet index " << index << "out of range (0.."
+ << TEST_SUBNETS_NUM << "): unable to add IPv6 subnet";
+ }
+ // std::advance is not available for this iterator.
+ auto it = test_subnets6_.begin();
+ for (unsigned int i = 0; i < index; ++i, ++it) {
+ ASSERT_FALSE(it == test_subnets6_.end());
+ }
+ conf_.getCfgSubnets6()->add(*it);
+}
+
+void
+SrvConfigTest::enableD2Client(const bool enable) {
+ const D2ClientConfigPtr& d2_config = conf_.getD2ClientConfig();
+ ASSERT_TRUE(d2_config);
+ d2_config->enableUpdates(enable);
+}
+
+// Check that by default there are no logging entries
+TEST_F(SrvConfigTest, basic) {
+ EXPECT_TRUE(conf_.getLoggingInfo().empty());
+}
+
+// Check that SrvConfig can store logging information.
+TEST_F(SrvConfigTest, loggingInfo) {
+ LoggingInfo log1;
+ log1.clearDestinations();
+ log1.name_ = "foo";
+ log1.severity_ = isc::log::WARN;
+ log1.debuglevel_ = 77;
+
+ LoggingDestination dest;
+ dest.output_ = "some-logfile.txt";
+ dest.maxver_ = 5;
+ dest.maxsize_ = 2097152;
+
+ log1.destinations_.push_back(dest);
+
+ conf_.addLoggingInfo(log1);
+
+ EXPECT_EQ("foo", conf_.getLoggingInfo()[0].name_);
+ EXPECT_EQ(isc::log::WARN, conf_.getLoggingInfo()[0].severity_);
+ EXPECT_EQ(77, conf_.getLoggingInfo()[0].debuglevel_);
+
+ EXPECT_EQ("some-logfile.txt", conf_.getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_EQ(5, conf_.getLoggingInfo()[0].destinations_[0].maxver_);
+ EXPECT_EQ(2097152, conf_.getLoggingInfo()[0].destinations_[0].maxsize_);
+}
+
+// Check that the configuration summary including information about the status
+// of DDNS is returned.
+TEST_F(SrvConfigTest, summaryDDNS) {
+ EXPECT_EQ("DDNS: disabled",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_DDNS));
+
+ enableD2Client(true);
+ EXPECT_EQ("DDNS: enabled",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_DDNS));
+
+ enableD2Client(false);
+ EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!; DDNS: disabled",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_ALL));
+}
+
+// Check that the configuration summary including information about added
+// subnets is returned.
+TEST_F(SrvConfigTest, summarySubnets) {
+ EXPECT_EQ("no config details available",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_NONE));
+
+ // Initially, there are no subnets added but it should be explicitly
+ // reported when we query for information about the subnets.
+ EXPECT_EQ("no IPv4 subnets!; no IPv6 subnets!",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
+
+ // If we just want information about IPv4 subnets, there should be no
+ // mention of IPv6 subnets, even though there are none added.
+ EXPECT_EQ("no IPv4 subnets!",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET4));
+
+ // If we just want information about IPv6 subnets, there should be no
+ // mention of IPv4 subnets, even though there are none added.
+ EXPECT_EQ("no IPv6 subnets!",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET6));
+
+ // Add IPv4 subnet and make sure it is reported.
+ addSubnet4(0);
+ EXPECT_EQ("added IPv4 subnets: 1",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET4));
+ EXPECT_EQ("added IPv4 subnets: 1; no IPv6 subnets!",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
+
+ // Add IPv6 subnet and make sure it is reported.
+ addSubnet6(0);
+ EXPECT_EQ("added IPv6 subnets: 1",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET6));
+ EXPECT_EQ("added IPv4 subnets: 1; added IPv6 subnets: 1",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
+
+ // Add one more subnet and make sure the bumped value is only
+ // for IPv4, but not for IPv6.
+ addSubnet4(1);
+ EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 1",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
+ EXPECT_EQ("added IPv4 subnets: 2",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET4));
+
+ addSubnet6(1);
+ EXPECT_EQ("added IPv4 subnets: 2; added IPv6 subnets: 2",
+ conf_.getConfigSummary(SrvConfig::CFGSEL_SUBNET));
+}
+
+// Verifies that we can get and set the client class dictionary
+TEST_F(SrvConfigTest, classDictionaryBasics) {
+ ClientClassDictionaryPtr d1;
+ SrvConfig conf(32);
+
+ // Upon construction the dictionary should be empty.
+ ASSERT_TRUE(d1 = conf.getClientClassDictionary());
+ EXPECT_EQ(0, d1->getClasses()->size());
+
+ // Verify we can replace it with a new dictionary.
+ ASSERT_NO_THROW(conf.setClientClassDictionary(ref_dictionary_));
+ ASSERT_TRUE(d1 = conf.getClientClassDictionary());
+ EXPECT_EQ(ref_dictionary_->getClasses()->size(), d1->getClasses()->size());
+
+ // Verify const fetcher works too.
+ const ClientClassDictionaryPtr cd = conf.getClientClassDictionary();
+ ASSERT_TRUE(cd);
+ EXPECT_EQ(ref_dictionary_->getClasses()->size(), cd->getClasses()->size());
+}
+
+// This test verifies that RFC6842 (echo client-id) compatibility may be
+// configured.
+TEST_F(SrvConfigTest, echoClientId) {
+ SrvConfig conf;
+
+ // Check that the default is true
+ EXPECT_TRUE(conf.getEchoClientId());
+
+ // Check that it can be modified to false
+ conf.setEchoClientId(false);
+ EXPECT_FALSE(conf.getEchoClientId());
+
+ // Check that the default value can be restored
+ conf.setEchoClientId(true);
+ EXPECT_TRUE(conf.getEchoClientId());
+
+ // Check the other constructor has the same default
+ SrvConfig conf1(1);
+ EXPECT_TRUE(conf1.getEchoClientId());
+}
+
+// This test verifies that compatibility flags are correctly managed.
+TEST_F(SrvConfigTest, compatibility) {
+ SrvConfig conf;
+
+ // Check that defaults are false.
+ EXPECT_FALSE(conf.getLenientOptionParsing());
+ EXPECT_FALSE(conf.getIgnoreServerIdentifier());
+ EXPECT_FALSE(conf.getIgnoreRAILinkSelection());
+ EXPECT_FALSE(conf.getExcludeFirstLast24());
+
+ // Check that they can be modified to true.
+ conf.setLenientOptionParsing(true);
+ conf.setIgnoreServerIdentifier(true);
+ conf.setIgnoreRAILinkSelection(true);
+ conf.setExcludeFirstLast24(true);
+ EXPECT_TRUE(conf.getLenientOptionParsing());
+ EXPECT_TRUE(conf.getIgnoreServerIdentifier());
+ EXPECT_TRUE(conf.getIgnoreRAILinkSelection());
+ EXPECT_TRUE(conf.getExcludeFirstLast24());
+
+ // Check that default values can be restored.
+ conf.setLenientOptionParsing(false);
+ conf.setIgnoreServerIdentifier(false);
+ conf.setIgnoreRAILinkSelection(false);
+ conf.setExcludeFirstLast24(false);
+ EXPECT_FALSE(conf.getLenientOptionParsing());
+ EXPECT_FALSE(conf.getIgnoreServerIdentifier());
+ EXPECT_FALSE(conf.getIgnoreRAILinkSelection());
+ EXPECT_FALSE(conf.getExcludeFirstLast24());
+
+ // Check the other constructor has the same default.
+ SrvConfig conf1(1);
+ EXPECT_FALSE(conf1.getLenientOptionParsing());
+ EXPECT_FALSE(conf.getIgnoreServerIdentifier());
+ EXPECT_FALSE(conf1.getIgnoreRAILinkSelection());
+ EXPECT_FALSE(conf1.getExcludeFirstLast24());
+}
+
+// This test verifies that host reservations lookup first flag can be configured.
+TEST_F(SrvConfigTest, reservationsLookupFirst) {
+ SrvConfig conf;
+
+ // Check that the default is false
+ EXPECT_FALSE(conf.getReservationsLookupFirst());
+
+ // Check that it can be modified to true
+ conf.setReservationsLookupFirst(true);
+ EXPECT_TRUE(conf.getReservationsLookupFirst());
+
+ // Check that the default value can be restored
+ conf.setReservationsLookupFirst(false);
+ EXPECT_FALSE(conf.getReservationsLookupFirst());
+
+ // Check the other constructor has the same default
+ SrvConfig conf1(1);
+ EXPECT_FALSE(conf1.getReservationsLookupFirst());
+}
+
+// This test checks if entire configuration can be copied and that the sequence
+// number is not affected.
+TEST_F(SrvConfigTest, copy) {
+ // Create two configurations with different sequence numbers.
+ SrvConfig conf1(32);
+ SrvConfig conf2(64);
+
+ // Set logging information for conf1.
+ LoggingInfo info;
+ info.name_ = "foo";
+ info.severity_ = isc::log::DEBUG;
+ info.debuglevel_ = 64;
+ info.destinations_.push_back(LoggingDestination());
+
+ // Set interface configuration for conf1.
+ conf1.getCfgIface()->use(AF_INET, "eth0");
+ conf1.addLoggingInfo(info);
+
+ // Add option definition.
+ OptionDefinitionPtr def(new OptionDefinition("option-foo", 5, "isc", "string"));
+ conf1.getCfgOptionDef()->add(def);
+
+ // Add an option.
+ OptionPtr option(new Option(Option::V6, 1000, OptionBuffer(10, 0xFF)));
+ conf1.getCfgOption()->add(option, true, false, DHCP6_OPTION_SPACE);
+
+ // Add a class dictionary
+ conf1.setClientClassDictionary(ref_dictionary_);
+
+ // Make sure both configurations are different.
+ ASSERT_TRUE(conf1 != conf2);
+
+ // Copy conf1 to conf2.
+ ASSERT_NO_THROW(conf1.copy(conf2));
+
+ // Now they should be equal.
+ EXPECT_TRUE(conf1 == conf2);
+
+ // But, their sequence numbers should be unequal.
+ EXPECT_FALSE(conf1.sequenceEquals(conf2));
+}
+
+// This test checks that two configurations can be compared for (in)equality.
+TEST_F(SrvConfigTest, equality) {
+ SrvConfig conf1(32);
+ SrvConfig conf2(64);
+
+ // Initially, both objects should be equal, even though the configuration
+ // sequences are not matching.
+ EXPECT_TRUE(conf1 == conf2);
+ EXPECT_FALSE(conf1 != conf2);
+
+ // Differ by logging information.
+ LoggingInfo info1;
+ LoggingInfo info2;
+ info1.name_ = "foo";
+ info2.name_ = "bar";
+
+ conf1.addLoggingInfo(info1);
+ conf2.addLoggingInfo(info2);
+
+ EXPECT_FALSE(conf1 == conf2);
+ EXPECT_TRUE(conf1 != conf2);
+
+ conf1.addLoggingInfo(info2);
+ conf2.addLoggingInfo(info1);
+
+ EXPECT_TRUE(conf1 == conf2);
+ EXPECT_FALSE(conf1 != conf2);
+
+ // Differ by interface configuration.
+ conf1.getCfgIface()->use(AF_INET, "eth0");
+
+ EXPECT_FALSE(conf1 == conf2);
+ EXPECT_TRUE(conf1 != conf2);
+
+ conf2.getCfgIface()->use(AF_INET, "eth0");
+
+ EXPECT_TRUE(conf1 == conf2);
+ EXPECT_FALSE(conf1 != conf2);
+
+ // Differ by option definitions.
+ conf1.getCfgOptionDef()->
+ add(OptionDefinitionPtr(new OptionDefinition("option-foo", 123, "isc",
+ "uint16_t")));
+
+ EXPECT_FALSE(conf1 == conf2);
+ EXPECT_TRUE(conf1 != conf2);
+
+ conf2.getCfgOptionDef()->
+ add(OptionDefinitionPtr(new OptionDefinition("option-foo", 123, "isc",
+ "uint16_t")));
+ EXPECT_TRUE(conf1 == conf2);
+ EXPECT_FALSE(conf1 != conf2);
+
+ // Differ by option data.
+ OptionPtr option(new Option(Option::V6, 1000, OptionBuffer(1, 0xFF)));
+ conf1.getCfgOption()->add(option, false, false, "isc");
+
+ EXPECT_FALSE(conf1 == conf2);
+ EXPECT_TRUE(conf1 != conf2);
+
+ conf2.getCfgOption()->add(option, false, false, "isc");
+
+ EXPECT_TRUE(conf1 == conf2);
+ EXPECT_FALSE(conf1 != conf2);
+
+ // Add a class dictionary to conf1
+ conf1.setClientClassDictionary(ref_dictionary_);
+ EXPECT_FALSE(conf1 == conf2);
+ EXPECT_TRUE(conf1 != conf2);
+
+ // Add same class dictionary to conf2
+ conf2.setClientClassDictionary(ref_dictionary_);
+ EXPECT_TRUE(conf1 == conf2);
+ EXPECT_FALSE(conf1 != conf2);
+}
+
+// Verifies that we can get and set configured hooks libraries
+TEST_F(SrvConfigTest, hooksLibraries) {
+ SrvConfig conf(32);
+ isc::hooks::HooksConfig& libraries = conf.getHooksConfig();
+
+ // Upon construction configured hooks libraries should be empty.
+ EXPECT_EQ(0, libraries.get().size());
+
+ // Verify we can update it.
+ ConstElementPtr elem0;
+ libraries.add("foo", elem0);
+ std::string config = "{ \"library\": \"bar\" }";
+ ConstElementPtr elem1 = Element::fromJSON(config);
+ libraries.add("bar", elem1);
+ EXPECT_EQ(2, libraries.get().size());
+ EXPECT_EQ(2, conf.getHooksConfig().get().size());
+
+ // Try to copy
+ SrvConfig copied(64);
+ ASSERT_TRUE(conf != copied);
+ ASSERT_NO_THROW(conf.copy(copied));
+ ASSERT_TRUE(conf == copied);
+ EXPECT_EQ(2, copied.getHooksConfig().get().size());
+
+ EXPECT_TRUE(copied.getHooksConfig().equal(conf.getHooksConfig()));
+}
+
+// Verifies basic functions of configured global handling.
+TEST_F(SrvConfigTest, configuredGlobals) {
+ // Create an instance.
+ SrvConfig conf(32);
+
+ // The map of configured globals should be empty.
+ ConstCfgGlobalsPtr srv_globals = conf.getConfiguredGlobals();
+ ASSERT_TRUE(srv_globals);
+ ASSERT_TRUE(srv_globals->valuesMap().empty());
+
+ // Attempting to extract globals from a non-map should throw.
+ ASSERT_THROW(conf.extractConfiguredGlobals(Element::create(777)), isc::BadValue);
+
+ // Now let's create a configuration from which to extract global scalars.
+ // Extraction (currently) has no business logic, so the elements we use
+ // can be arbitrary when not scalar.
+ ConstElementPtr global_cfg;
+ std::string global_cfg_str =
+ "{\n"
+ " \"comment\": \"okay\",\n" // a string
+ " \"amap\": { \"not-this\":777, \"not-that\": \"poo\" },\n"
+ " \"valid-lifetime\": 444,\n" // an int
+ " \"alist\": [ 1, 2, 3 ],\n"
+ " \"store-extended-info\": true,\n" // a bool
+ " \"t1-percent\": 1.234\n" // a real
+ "}\n";
+ ASSERT_NO_THROW(global_cfg = Element::fromJSON(global_cfg_str));
+
+ // Extract globals from the config.
+ ASSERT_NO_THROW(conf.extractConfiguredGlobals(global_cfg));
+
+ // Now see if the extract was correct.
+ srv_globals = conf.getConfiguredGlobals();
+ ASSERT_FALSE(srv_globals->valuesMap().empty());
+
+ // Maps and lists should be excluded.
+ auto globals = srv_globals->valuesMap();
+ for (auto global = globals.begin(); global != globals.end(); ++global) {
+ if (global->first == "comment") {
+ ASSERT_EQ(Element::string, global->second->getType());
+ EXPECT_EQ("okay", global->second->stringValue());
+ } else if (global->first == "valid-lifetime") {
+ ASSERT_EQ(Element::integer, global->second->getType());
+ EXPECT_EQ(444, global->second->intValue());
+ } else if (global->first == "store-extended-info") {
+ ASSERT_EQ(Element::boolean, global->second->getType());
+ EXPECT_TRUE(global->second->boolValue());
+ } else if (global->first == "t1-percent") {
+ ASSERT_EQ(Element::real, global->second->getType());
+ EXPECT_EQ(1.234, global->second->doubleValue());
+ } else {
+ ADD_FAILURE() << "unexpected element found:" << global->first;
+ }
+ }
+
+ // Verify that using getConfiguredGlobal() to fetch an individual
+ // parameters works.
+ ConstElementPtr global;
+ // We should find global "comment".
+ ASSERT_NO_THROW(global = conf.getConfiguredGlobal("comment"));
+ ASSERT_TRUE(global);
+ ASSERT_EQ(Element::string, global->getType());
+ EXPECT_EQ("okay", global->stringValue());
+
+ // Not finding global "not-there" should throw.
+ // without throwing.
+ ASSERT_THROW(conf.getConfiguredGlobal("not-there"), isc::NotFound);
+}
+
+// Verifies that the toElement method works well (tests limited to
+// direct parameters)
+TEST_F(SrvConfigTest, unparse) {
+ SrvConfig conf(32);
+ std::string header4 = "{\n\"Dhcp4\": {\n";
+ std::string header6 = "{\n\"Dhcp6\": {\n";
+
+ std::string defaults = "\"decline-probation-period\": 0,\n";
+ defaults += "\"interfaces-config\": { \"interfaces\": [ ],\n";
+ defaults += " \"re-detect\": false },\n";
+ defaults += "\"option-def\": [ ],\n";
+ defaults += "\"option-data\": [ ],\n";
+ defaults += "\"expired-leases-processing\": ";
+ defaults += conf.getCfgExpiration()->toElement()->str() + ",\n";
+ defaults += "\"lease-database\": { \"type\": \"memfile\" },\n";
+ defaults += "\"hooks-libraries\": [ ],\n";
+ defaults += "\"sanity-checks\": {\n";
+ defaults += " \"lease-checks\": \"none\",\n";
+ defaults += " \"extended-info-checks\": \"fix\"\n";
+ defaults += " },\n";
+ defaults += "\"dhcp-ddns\": \n";
+
+ defaults += conf.getD2ClientConfig()->toElement()->str() + ",\n";
+
+ std::string defaults4 = "\"shared-networks\": [ ],\n";
+ defaults4 += "\"subnet4\": [ ],\n";
+ defaults4 += "\"host-reservation-identifiers\": ";
+ defaults4 += "[ \"hw-address\", \"duid\", \"circuit-id\", \"client-id\" ],\n";
+
+ std::string defaults6 = "\"relay-supplied-options\": [ \"65\" ],\n";
+ defaults6 += "\"shared-networks\": [ ],\n";
+ defaults6 += "\"subnet6\": [ ],\n";
+ defaults6 += "\"server-id\": ";
+ defaults6 += conf.getCfgDUID()->toElement()->str() + ",\n";
+ defaults6 += "\"host-reservation-identifiers\": ";
+ defaults6 += "[ \"hw-address\", \"duid\" ],\n";
+ defaults6 += "\"dhcp4o6-port\": 0,\n";
+ defaults6 += "\"mac-sources\": [ \"any\" ]\n";
+
+ std::string params = "\"echo-client-id\": true,\n";
+ params += "\"dhcp4o6-port\": 0\n";
+ std::string trailer = "}\n}\n";
+
+ // Verify DHCPv4
+ CfgMgr::instance().setFamily(AF_INET);
+ isc::test::runToElementTest<SrvConfig>
+ (header4 + defaults + defaults4 + params + trailer, conf);
+
+ // Verify DHCPv6
+ CfgMgr::instance().setFamily(AF_INET6);
+ isc::test::runToElementTest<SrvConfig>
+ (header6 + defaults + defaults6 + trailer, conf);
+
+ // Verify direct non-default parameters and configured globals
+ CfgMgr::instance().setFamily(AF_INET);
+ conf.setEchoClientId(false);
+ conf.setDhcp4o6Port(6767);
+ // Add compatibility flags.
+ conf.setLenientOptionParsing(true);
+ conf.setIgnoreRAILinkSelection(true);
+ conf.setExcludeFirstLast24(true);
+ params = "\"compatibility\": {\n";
+ params += " \"lenient-option-parsing\": true,\n";
+ params += " \"ignore-rai-link-selection\": true,\n";
+ params += " \"exclude-first-last-24\": true\n";
+ params += "},\n";
+ // Add "configured globals"
+ conf.addConfiguredGlobal("renew-timer", Element::create(777));
+ conf.addConfiguredGlobal("comment", Element::create("bar"));
+ params += "\"echo-client-id\": false,\n";
+ params += "\"dhcp4o6-port\": 6767,\n";
+ params += "\"renew-timer\": 777,\n";
+ params += "\"comment\": \"bar\"\n";
+ isc::test::runToElementTest<SrvConfig>
+ (header4 + defaults + defaults4 + params + trailer, conf);
+
+ // Verify direct non-default parameters and configured globals
+ CfgMgr::instance().setFamily(AF_INET6);
+ // Add compatibility flag.
+ conf.setIgnoreRAILinkSelection(false);
+ conf.setExcludeFirstLast24(false);
+ params = ",\"compatibility\": {\n";
+ params += " \"lenient-option-parsing\": true\n";
+ params += "},\n";
+ // Add "configured globals"
+ params += "\"dhcp4o6-port\": 6767,\n";
+ params += "\"renew-timer\": 777,\n";
+ params += "\"comment\": \"bar\"\n";
+ isc::test::runToElementTest<SrvConfig>
+ (header6 + defaults + defaults6 + params + trailer, conf);
+}
+
+// Verifies that the toElement method does not miss host reservations
+TEST_F(SrvConfigTest, unparseHR) {
+ // DHCPv4 version
+ CfgMgr::instance().setFamily(AF_INET);
+ SrvConfig conf4(32);
+
+ // Add a plain subnet
+ Triplet<uint32_t> def_triplet;
+ SubnetID p_id(1);
+ Subnet4Ptr psubnet4(new Subnet4(IOAddress("192.0.1.0"), 24,
+ def_triplet, def_triplet, 4000, p_id));
+ conf4.getCfgSubnets4()->add(psubnet4);
+
+ // Add a shared network
+ SharedNetwork4Ptr network4(new SharedNetwork4("frog"));
+ conf4.getCfgSharedNetworks4()->add(network4);
+
+ // Add a shared subnet
+ SubnetID s_id(100);
+ Subnet4Ptr ssubnet4(new Subnet4(IOAddress("192.0.2.0"), 24,
+ def_triplet, def_triplet, 4000, s_id));
+ network4->add(ssubnet4);
+
+ // Add a v4 global host reservation to the plain subnet
+ HostPtr ghost4(new Host("AA:01:02:03:04:05", "hw-address",
+ SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED,
+ IOAddress("192.0.3.1")));
+ conf4.getCfgHosts()->add(ghost4);
+
+ // Add a host reservation to the plain subnet
+ HostPtr phost4(new Host("00:01:02:03:04:05", "hw-address",
+ p_id, SUBNET_ID_UNUSED, IOAddress("192.0.1.1")));
+ conf4.getCfgHosts()->add(phost4);
+
+ // Add a host reservation to the shared subnet
+ HostPtr shost4(new Host("00:05:04:03:02:01", "hw-address",
+ s_id, SUBNET_ID_UNUSED, IOAddress("192.0.2.1")));
+ conf4.getCfgHosts()->add(shost4);
+
+ // Unparse the config
+ ConstElementPtr unparsed4 = conf4.toElement();
+ ASSERT_TRUE(unparsed4);
+ ASSERT_EQ(Element::map, unparsed4->getType());
+
+ // Get Dhcp4 entry
+ ConstElementPtr dhcp4;
+ ASSERT_NO_THROW(dhcp4 = unparsed4->get("Dhcp4"));
+ ASSERT_TRUE(dhcp4);
+ ASSERT_EQ(Element::map, dhcp4->getType());
+
+ // Get global host reservations
+ ConstElementPtr check;
+ ASSERT_NO_THROW(check = dhcp4->get("reservations"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the global host reservation
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Check the reserved address
+ ASSERT_NO_THROW(check = check->get("ip-address"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::string, check->getType());
+ EXPECT_EQ("192.0.3.1", check->stringValue());
+
+ // Get plain subnets
+ ASSERT_NO_THROW(check = dhcp4->get("subnet4"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the plain subnet
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Its ID is 1
+ ConstElementPtr sub;
+ ASSERT_NO_THROW(sub = check->get("id"));
+ ASSERT_TRUE(sub);
+ ASSERT_EQ(Element::integer, sub->getType());
+ EXPECT_EQ(p_id, sub->intValue());
+
+ // Get its host reservations
+ ASSERT_NO_THROW(check = check->get("reservations"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the plain host reservation
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Check the reserved address
+ ASSERT_NO_THROW(check = check->get("ip-address"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::string, check->getType());
+ EXPECT_EQ("192.0.1.1", check->stringValue());
+
+ // Get shared networks
+ ASSERT_NO_THROW(check = dhcp4->get("shared-networks"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the shared network
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Its name is "frog"
+ ASSERT_NO_THROW(sub = check->get("name"));
+ ASSERT_TRUE(sub);
+ ASSERT_EQ(Element::string, sub->getType());
+ EXPECT_EQ("frog", sub->stringValue());
+
+ // Get shared subnets
+ ASSERT_NO_THROW(check = check->get("subnet4"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the shared subnet
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Its ID is 100
+ ASSERT_NO_THROW(sub = check->get("id"));
+ ASSERT_TRUE(sub);
+ ASSERT_EQ(Element::integer, sub->getType());
+ EXPECT_EQ(s_id, sub->intValue());
+
+ // Get its host reservations
+ ASSERT_NO_THROW(check = check->get("reservations"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the shared host reservation
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Check the reserved address
+ ASSERT_NO_THROW(check = check->get("ip-address"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::string, check->getType());
+ EXPECT_EQ("192.0.2.1", check->stringValue());
+
+ // DHCPv6 version
+ CfgMgr::instance().setFamily(AF_INET6);
+ SrvConfig conf6(32);
+
+ // Add a plain subnet
+ Subnet6Ptr psubnet6(new Subnet6(IOAddress("2001:db8:1::"), 64,
+ 1000, 2000, 3000, 4000, p_id));
+ conf6.getCfgSubnets6()->add(psubnet6);
+
+ // Add a shared network
+ SharedNetwork6Ptr network6(new SharedNetwork6("frog"));
+ conf6.getCfgSharedNetworks6()->add(network6);
+
+ // Add a shared subnet
+ Subnet6Ptr ssubnet6(new Subnet6(IOAddress("2001:db8:2::"), 64,
+ 1000, 2000, 3000, 4000, s_id));
+ network6->add(ssubnet6);
+
+ // Add a v6 global host reservation
+ HostPtr ghost6(new Host("ff:b2:c3:d4:e5:f6", "duid", SUBNET_ID_UNUSED,
+ SUBNET_ID_GLOBAL, IOAddress::IPV4_ZERO_ADDRESS(),
+ "global.example.org"));
+ conf6.getCfgHosts()->add(ghost6);
+
+ // Add a host reservation to the plain subnet
+ HostPtr phost6(new Host("a1:b2:c3:d4:e5:f6", "duid", SUBNET_ID_UNUSED,
+ p_id, IOAddress::IPV4_ZERO_ADDRESS(),
+ "foo.example.org"));
+
+ conf6.getCfgHosts()->add(phost6);
+
+ // Add a host reservation to the shared subnet
+ HostPtr shost6(new Host("f6:e5:d4:c3:b2:a1", "duid", SUBNET_ID_UNUSED,
+ s_id, IOAddress::IPV4_ZERO_ADDRESS(),
+ "bar.example.org"));
+ conf6.getCfgHosts()->add(shost6);
+
+ // Unparse the config
+ ConstElementPtr unparsed6 = conf6.toElement();
+ ASSERT_TRUE(unparsed6);
+ ASSERT_EQ(Element::map, unparsed6->getType());
+
+ // Get Dhcp6 entry
+ ConstElementPtr dhcp6;
+ ASSERT_NO_THROW(dhcp6 = unparsed6->get("Dhcp6"));
+ ASSERT_TRUE(dhcp6);
+ ASSERT_EQ(Element::map, dhcp6->getType());
+
+ // Get global host reservations
+ ASSERT_NO_THROW(check = dhcp6->get("reservations"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the global host reservation
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Check the host name
+ ASSERT_NO_THROW(check = check->get("hostname"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::string, check->getType());
+ EXPECT_EQ("global.example.org", check->stringValue());
+
+ // Get plain subnets
+ ASSERT_NO_THROW(check = dhcp6->get("subnet6"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the plain subnet
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Its ID is 1
+ ASSERT_NO_THROW(sub = check->get("id"));
+ ASSERT_TRUE(sub);
+ ASSERT_EQ(Element::integer, sub->getType());
+ EXPECT_EQ(p_id, sub->intValue());
+
+ // Get its host reservations
+ ASSERT_NO_THROW(check = check->get("reservations"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the plain host reservation
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Check the host name
+ ASSERT_NO_THROW(check = check->get("hostname"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::string, check->getType());
+ EXPECT_EQ("foo.example.org", check->stringValue());
+
+ // Get shared networks
+ ASSERT_NO_THROW(check = dhcp6->get("shared-networks"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the shared network
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Its name is "frog"
+ ASSERT_NO_THROW(sub = check->get("name"));
+ ASSERT_TRUE(sub);
+ ASSERT_EQ(Element::string, sub->getType());
+ EXPECT_EQ("frog", sub->stringValue());
+
+ // Get shared subnets
+ ASSERT_NO_THROW(check = check->get("subnet6"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the shared subnet
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Its ID is 100
+ ASSERT_NO_THROW(sub = check->get("id"));
+ ASSERT_TRUE(sub);
+ ASSERT_EQ(Element::integer, sub->getType());
+ EXPECT_EQ(s_id, sub->intValue());
+
+ // Get its host reservations
+ ASSERT_NO_THROW(check = check->get("reservations"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::list, check->getType());
+ EXPECT_EQ(1, check->size());
+
+ // Get the shared host reservation
+ ASSERT_NO_THROW(check = check->get(0));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Check the host name
+ ASSERT_NO_THROW(check = check->get("hostname"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::string, check->getType());
+ EXPECT_EQ("bar.example.org", check->stringValue());
+}
+
+// Verifies that the toElement method does not miss config control info
+TEST_F(SrvConfigTest, unparseConfigControlInfo4) {
+ // DHCPv4 version
+ CfgMgr::instance().setFamily(AF_INET);
+ SrvConfig conf4(32);
+
+ // Unparse the config
+ ConstElementPtr unparsed4 = conf4.toElement();
+ ASSERT_TRUE(unparsed4);
+ ASSERT_EQ(Element::map, unparsed4->getType());
+
+ // Get Dhcp4 entry
+ ConstElementPtr dhcp4;
+ ASSERT_NO_THROW(dhcp4 = unparsed4->get("Dhcp4"));
+ ASSERT_TRUE(dhcp4);
+ ASSERT_EQ(Element::map, dhcp4->getType());
+
+ // Config control should not be present.
+ ConstElementPtr check;
+ ASSERT_NO_THROW(check = dhcp4->get("config-control"));
+ EXPECT_FALSE(check);
+
+ // Now let's create the info and add it to the configuration
+ ConfigControlInfoPtr info(new ConfigControlInfo());
+ ASSERT_NO_THROW(info->addConfigDatabase("type=mysql"));
+ ASSERT_NO_THROW(conf4.setConfigControlInfo(info));
+
+ // Unparse the config again
+ unparsed4 = conf4.toElement();
+ ASSERT_NO_THROW(dhcp4 = unparsed4->get("Dhcp4"));
+ ASSERT_TRUE(dhcp4);
+ ASSERT_EQ(Element::map, dhcp4->getType());
+
+ // Config control should be present.
+ ASSERT_NO_THROW(check = dhcp4->get("config-control"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Unparse the info object and compare its elements to
+ // that in unparsed config. They should be equal.
+ ElementPtr info_elem = info->toElement();
+ EXPECT_TRUE(info_elem->equals(*check));
+}
+
+// Verifies that the toElement method does not miss config control info
+TEST_F(SrvConfigTest, unparseConfigControlInfo6) {
+ // DHCPv6 version
+ CfgMgr::instance().setFamily(AF_INET6);
+ SrvConfig conf6(32);
+
+ // Unparse the config
+ ConstElementPtr unparsed6 = conf6.toElement();
+ ASSERT_TRUE(unparsed6);
+ ASSERT_EQ(Element::map, unparsed6->getType());
+
+ // Get Dhcp4 entry
+ ConstElementPtr dhcp6;
+ ASSERT_NO_THROW(dhcp6 = unparsed6->get("Dhcp6"));
+ ASSERT_TRUE(dhcp6);
+ ASSERT_EQ(Element::map, dhcp6->getType());
+
+ // Config control should not be present.
+ ConstElementPtr check;
+ ASSERT_NO_THROW(check = dhcp6->get("config-control"));
+ EXPECT_FALSE(check);
+
+ // Now let's create the info and add it to the configuration
+ ConfigControlInfoPtr info(new ConfigControlInfo());
+ ASSERT_NO_THROW(info->addConfigDatabase("type=mysql"));
+ ASSERT_NO_THROW(conf6.setConfigControlInfo(info));
+
+ // Unparse the config again
+ unparsed6 = conf6.toElement();
+ ASSERT_NO_THROW(dhcp6 = unparsed6->get("Dhcp6"));
+ ASSERT_TRUE(dhcp6);
+ ASSERT_EQ(Element::map, dhcp6->getType());
+
+ // Config control should be present.
+ ASSERT_NO_THROW(check = dhcp6->get("config-control"));
+ ASSERT_TRUE(check);
+ ASSERT_EQ(Element::map, check->getType());
+
+ // Unparse the info object and compare its elements to
+ // that in unparsed config. They should be equal.
+ ElementPtr info_elem = info->toElement();
+ EXPECT_TRUE(info_elem->equals(*check));
+}
+
+// Verifies that exception is thrown when instead of SrvConfig
+// another derivation of ConfigBase is used in the call to
+// merge.
+TEST_F(SrvConfigTest, mergeBadCast) {
+ SrvConfig srv_config;
+ NonSrvConfig non_srv_config;
+ ASSERT_THROW(srv_config.merge(non_srv_config), isc::InvalidOperation);
+}
+
+// This test verifies that globals from one SrvConfig
+// can be merged into another. It verifies that values
+// in the from-config override those in to-config which
+// override those in GLOBAL4_DEFAULTS.
+TEST_F(SrvConfigTest, mergeGlobals4) {
+ // Set the family we're working with.
+ CfgMgr::instance().setFamily(AF_INET);
+
+ // Let's create the "existing" config we will merge into.
+ SrvConfig cfg_to;
+
+ // Set some explicit values.
+ cfg_to.setDeclinePeriod(100);
+ cfg_to.setEchoClientId(false);
+ cfg_to.setDhcp4o6Port(777);
+ cfg_to.setServerTag("not_this_server");
+
+ // Add some configured globals
+ cfg_to.addConfiguredGlobal("decline-probation-period", Element::create(300));
+ cfg_to.addConfiguredGlobal("dhcp4o6-port", Element::create(888));
+
+ // Now we'll create the config we'll merge from.
+ SrvConfig cfg_from;
+
+ // Set some explicit values. None of these should be preserved.
+ cfg_from.setDeclinePeriod(200);
+ cfg_from.setEchoClientId(true);
+ cfg_from.setDhcp4o6Port(888);
+ cfg_from.setServerTag("nor_this_server");
+ cfg_from.setReservationsLookupFirst(true);
+
+ // Add a configured global ip-reservations-unique. It should be populated
+ // to the CfgDbAccess and CfgHosts.
+ cfg_from.addConfiguredGlobal("ip-reservations-unique", Element::create(false));
+
+ // Add some configured globals:
+ cfg_to.addConfiguredGlobal("dhcp4o6-port", Element::create(999));
+ cfg_to.addConfiguredGlobal("server-tag", Element::create("use_this_server"));
+ cfg_to.addConfiguredGlobal("reservations-lookup-first", Element::create(true));
+
+ // Now let's merge.
+ ASSERT_NO_THROW(cfg_to.merge(cfg_from));
+
+ // Make sure the explicit values are set correctly.
+
+ // decline-probation-period should be the "to" configured value.
+ EXPECT_EQ(300, cfg_to.getDeclinePeriod());
+
+ // echo-client-id should be the preserved "to" member value.
+ EXPECT_FALSE(cfg_to.getEchoClientId());
+
+ // dhcp4o6-port should be the "from" configured value.
+ EXPECT_EQ(999, cfg_to.getDhcp4o6Port());
+
+ // server-tag port should be the "from" configured value.
+ EXPECT_EQ("use_this_server", cfg_to.getServerTag().get());
+
+ // reservations-lookup-first should be the "from" configured value.
+ EXPECT_TRUE(cfg_to.getReservationsLookupFirst());
+
+ // ip-reservations-unique
+ EXPECT_FALSE(cfg_to.getCfgDbAccess()->getIPReservationsUnique());
+
+ // Next we check the explicitly "configured" globals.
+ // The list should be all of the "to" + "from", with the
+ // latter overwriting the former.
+ std::string exp_globals =
+ "{ \n"
+ " \"decline-probation-period\": 300, \n"
+ " \"dhcp4o6-port\": 999, \n"
+ " \"ip-reservations-unique\": false, \n"
+ " \"server-tag\": \"use_this_server\", \n"
+ " \"reservations-lookup-first\": true"
+ "} \n";
+
+ ConstElementPtr expected_globals;
+ ASSERT_NO_THROW(expected_globals = Element::fromJSON(exp_globals))
+ << "exp_globals didn't parse, test is broken";
+
+ EXPECT_TRUE(isEquivalent(expected_globals,
+ cfg_to.getConfiguredGlobals()->toElement()));
+}
+
+// This test verifies that globals from one SrvConfig
+// can be merged into another. It verifies that values
+// in the from-config override those in to-config which
+// override those in GLOBAL6_DEFAULTS.
+TEST_F(SrvConfigTest, mergeGlobals6) {
+ // Set the family we're working with.
+ CfgMgr::instance().setFamily(AF_INET6);
+
+ // Let's create the "existing" config we will merge into.
+ SrvConfig cfg_to;
+
+ // Set some explicit values.
+ cfg_to.setDeclinePeriod(100);
+ cfg_to.setDhcp4o6Port(777);
+ cfg_to.setServerTag("not_this_server");
+
+ // Add some configured globals
+ cfg_to.addConfiguredGlobal("decline-probation-period", Element::create(300));
+ cfg_to.addConfiguredGlobal("dhcp4o6-port", Element::create(888));
+
+ // Now we'll create the config we'll merge from.
+ SrvConfig cfg_from;
+
+ // Set some explicit values. None of these should be preserved.
+ cfg_from.setDeclinePeriod(200);
+ cfg_from.setEchoClientId(true);
+ cfg_from.setDhcp4o6Port(888);
+ cfg_from.setServerTag("nor_this_server");
+ cfg_from.setReservationsLookupFirst(true);
+
+ // Add a configured global ip-reservations-unique. It should be populated
+ // to the CfgDbAccess and CfgHosts.
+ cfg_from.addConfiguredGlobal("ip-reservations-unique", Element::create(false));
+
+ // Add some configured globals:
+ cfg_to.addConfiguredGlobal("dhcp4o6-port", Element::create(999));
+ cfg_to.addConfiguredGlobal("server-tag", Element::create("use_this_server"));
+ cfg_to.addConfiguredGlobal("reservations-lookup-first", Element::create(true));
+
+ // Now let's merge.
+ ASSERT_NO_THROW(cfg_to.merge(cfg_from));
+
+ // Make sure the explicit values are set correctly.
+
+ // decline-probation-period should be the "to" configured value.
+ EXPECT_EQ(300, cfg_to.getDeclinePeriod());
+
+ // dhcp4o6-port should be the "from" configured value.
+ EXPECT_EQ(999, cfg_to.getDhcp4o6Port());
+
+ // server-tag port should be the "from" configured value.
+ EXPECT_EQ("use_this_server", cfg_to.getServerTag().get());
+
+ // reservations-lookup-first should be the "from" configured value.
+ EXPECT_TRUE(cfg_to.getReservationsLookupFirst());
+
+ // ip-reservations-unique
+ EXPECT_FALSE(cfg_to.getCfgDbAccess()->getIPReservationsUnique());
+
+ // Next we check the explicitly "configured" globals.
+ // The list should be all of the "to" + "from", with the
+ // latter overwriting the former.
+ std::string exp_globals =
+ "{ \n"
+ " \"decline-probation-period\": 300, \n"
+ " \"dhcp4o6-port\": 999, \n"
+ " \"ip-reservations-unique\": false, \n"
+ " \"server-tag\": \"use_this_server\", \n"
+ " \"reservations-lookup-first\": true"
+ "} \n";
+
+ ConstElementPtr expected_globals;
+ ASSERT_NO_THROW(expected_globals = Element::fromJSON(exp_globals))
+ << "exp_globals didn't parse, test is broken";
+
+ EXPECT_TRUE(isEquivalent(expected_globals,
+ cfg_to.getConfiguredGlobals()->toElement()));
+}
+
+// This test verifies that new list of client classes replaces and old list
+// when server configuration is merged.
+TEST_F(SrvConfigTest, mergeClientClasses) {
+ // Let's create the "existing" config we will merge into.
+ SrvConfig cfg_to;
+
+ auto expression = boost::make_shared<Expression>();
+ auto client_class = boost::make_shared<ClientClassDef>("foo", expression);
+ cfg_to.getClientClassDictionary()->addClass(client_class);
+
+ client_class = boost::make_shared<ClientClassDef>("bar", expression);
+ cfg_to.getClientClassDictionary()->addClass(client_class);
+
+ // Now we'll create the config we'll merge from.
+ SrvConfig cfg_from;
+ client_class = boost::make_shared<ClientClassDef>("baz", expression);
+ cfg_from.getClientClassDictionary()->addClass(client_class);
+
+ client_class = boost::make_shared<ClientClassDef>("abc", expression);
+ cfg_from.getClientClassDictionary()->addClass(client_class);
+
+ ASSERT_NO_THROW(cfg_to.merge(cfg_from));
+
+ // The old classes should be replaced with new classes.
+ EXPECT_FALSE(cfg_to.getClientClassDictionary()->findClass("foo"));
+ EXPECT_FALSE(cfg_to.getClientClassDictionary()->findClass("bar"));
+ EXPECT_TRUE(cfg_to.getClientClassDictionary()->findClass("baz"));
+ EXPECT_TRUE(cfg_to.getClientClassDictionary()->findClass("abc"));
+}
+
+// This test verifies that client classes are not modified if the merged
+// list of classes is empty.
+TEST_F(SrvConfigTest, mergeEmptyClientClasses) {
+ // Let's create the "existing" config we will merge into.
+ SrvConfig cfg_to;
+
+ auto expression = boost::make_shared<Expression>();
+ auto client_class = boost::make_shared<ClientClassDef>("foo", expression);
+ cfg_to.getClientClassDictionary()->addClass(client_class);
+
+ client_class = boost::make_shared<ClientClassDef>("bar", expression);
+ cfg_to.getClientClassDictionary()->addClass(client_class);
+
+ // Now we'll create the config we'll merge from.
+ SrvConfig cfg_from;
+
+ ASSERT_NO_THROW(cfg_to.merge(cfg_from));
+
+ // Empty list of classes should not replace an existing list.
+ EXPECT_TRUE(cfg_to.getClientClassDictionary()->findClass("foo"));
+ EXPECT_TRUE(cfg_to.getClientClassDictionary()->findClass("bar"));
+}
+
+// Validates SrvConfig::moveDdnsParams by ensuring that deprecated dhcp-ddns
+// parameters are:
+// 1. Translated to their global counterparts if they do not exist globally
+// 2. Removed from the dhcp-ddns element
+TEST_F(SrvConfigTest, moveDdnsParamsTest) {
+ DdnsParamsPtr params;
+
+ CfgMgr::instance().setFamily(AF_INET);
+
+ struct Scenario {
+ std::string description;
+ std::string input_cfg;
+ std::string exp_cfg;
+ };
+
+ std::vector<Scenario> scenarios {
+ {
+ "scenario 1, move with no global conflicts",
+ // input_cfg
+ "{\n"
+ " \"dhcp-ddns\": {\n"
+ " \"enable-updates\": true, \n"
+ " \"server-ip\" : \"192.0.2.0\",\n"
+ " \"server-port\" : 3432,\n"
+ " \"sender-ip\" : \"192.0.2.1\",\n"
+ " \"sender-port\" : 3433,\n"
+ " \"max-queue-size\" : 2048,\n"
+ " \"ncr-protocol\" : \"UDP\",\n"
+ " \"ncr-format\" : \"JSON\",\n"
+ " \"user-context\": { \"foo\": \"bar\" },\n"
+ " \"override-no-update\": true,\n"
+ " \"override-client-update\": false,\n"
+ " \"replace-client-name\": \"always\",\n"
+ " \"generated-prefix\": \"prefix\",\n"
+ " \"qualifying-suffix\": \"suffix.com.\",\n"
+ " \"hostname-char-set\": \"[^A-Z]\",\n"
+ " \"hostname-char-replacement\": \"x\"\n"
+ " }\n"
+ "}\n",
+ // exp_cfg
+ "{\n"
+ " \"dhcp-ddns\": {\n"
+ " \"enable-updates\": true, \n"
+ " \"server-ip\" : \"192.0.2.0\",\n"
+ " \"server-port\" : 3432,\n"
+ " \"sender-ip\" : \"192.0.2.1\",\n"
+ " \"sender-port\" : 3433,\n"
+ " \"max-queue-size\" : 2048,\n"
+ " \"ncr-protocol\" : \"UDP\",\n"
+ " \"ncr-format\" : \"JSON\",\n"
+ " \"user-context\": { \"foo\": \"bar\" }\n"
+ " },\n"
+ " \"ddns-override-no-update\": true,\n"
+ " \"ddns-override-client-update\": false,\n"
+ " \"ddns-replace-client-name\": \"always\",\n"
+ " \"ddns-generated-prefix\": \"prefix\",\n"
+ " \"ddns-qualifying-suffix\": \"suffix.com.\",\n"
+ " \"hostname-char-set\": \"[^A-Z]\",\n"
+ " \"hostname-char-replacement\": \"x\"\n"
+ "}\n"
+ },
+ {
+ "scenario 2, globals already exist for all movable params",
+ // input_cfg
+ "{\n"
+ " \"dhcp-ddns\" : {\n"
+ " \"enable-updates\": true, \n"
+ " \"override-no-update\": true,\n"
+ " \"override-client-update\": true,\n"
+ " \"replace-client-name\": \"always\",\n"
+ " \"generated-prefix\": \"prefix\",\n"
+ " \"qualifying-suffix\": \"suffix.com.\",\n"
+ " \"hostname-char-set\": \"[^A-Z]\",\n"
+ " \"hostname-char-replacement\": \"x\"\n"
+ " },\n"
+ " \"ddns-override-no-update\": false,\n"
+ " \"ddns-override-client-update\": false,\n"
+ " \"ddns-replace-client-name\": \"when-present\",\n"
+ " \"ddns-generated-prefix\": \"org_prefix\",\n"
+ " \"ddns-qualifying-suffix\": \"org_suffix.com.\",\n"
+ " \"hostname-char-set\": \"[^a-z]\",\n"
+ " \"hostname-char-replacement\": \"y\"\n"
+ "}\n",
+ // exp_cfg
+ "{\n"
+ " \"dhcp-ddns\" : {\n"
+ " \"enable-updates\": true\n"
+ " },\n"
+ " \"ddns-override-no-update\": false,\n"
+ " \"ddns-override-client-update\": false,\n"
+ " \"ddns-replace-client-name\": \"when-present\",\n"
+ " \"ddns-generated-prefix\": \"org_prefix\",\n"
+ " \"ddns-qualifying-suffix\": \"org_suffix.com.\",\n"
+ " \"hostname-char-set\": \"[^a-z]\",\n"
+ " \"hostname-char-replacement\": \"y\"\n"
+ "}\n"
+ },
+ {
+ "scenario 3, nothing to move",
+ // input_cfg
+ "{\n"
+ " \"dhcp-ddns\" : {\n"
+ " \"enable-updates\": true, \n"
+ " \"server-ip\" : \"192.0.2.0\",\n"
+ " \"server-port\" : 3432,\n"
+ " \"sender-ip\" : \"192.0.2.1\"\n"
+ " }\n"
+ "}\n",
+ // exp_output
+ "{\n"
+ " \"dhcp-ddns\" : {\n"
+ " \"enable-updates\": true, \n"
+ " \"server-ip\" : \"192.0.2.0\",\n"
+ " \"server-port\" : 3432,\n"
+ " \"sender-ip\" : \"192.0.2.1\"\n"
+ " }\n"
+ "}\n"
+ }
+ };
+
+ for (auto scenario : scenarios) {
+ SrvConfig conf(32);
+ ElementPtr input_cfg;
+ ConstElementPtr exp_cfg;
+ {
+ SCOPED_TRACE(scenario.description);
+ // Parse the input cfg into a mutable Element map.
+ ASSERT_NO_THROW(input_cfg = boost::const_pointer_cast<Element>
+ (Element::fromJSON(scenario.input_cfg)))
+ << "input_cfg didn't parse, test is broken";
+
+ // Parse the expected cfg into an Element map.
+ ASSERT_NO_THROW(exp_cfg = Element::fromJSON(scenario.exp_cfg))
+ << "exp_cfg didn't parse, test is broken";
+
+ // Now call moveDdnsParams.
+ ASSERT_NO_THROW(SrvConfig::moveDdnsParams(input_cfg));
+
+ // Make sure the resultant configuration is as expected.
+ EXPECT_TRUE(input_cfg->equals(*exp_cfg));
+ }
+ }
+}
+
+// Verifies that the scoped values for DDNS parameters can be fetched
+// for a given Subnet4.
+TEST_F(SrvConfigTest, getDdnsParamsTest4) {
+ DdnsParamsPtr params;
+
+ CfgMgr::instance().setFamily(AF_INET);
+ SrvConfig conf(32);
+
+ // This disables D2 connectivity. When it is false, updates
+ // are off at all scopes, regardless of ddns-send-updates values.
+ enableD2Client(false);
+
+ // Disable sending updates globally.
+ conf.addConfiguredGlobal("ddns-send-updates", Element::create(false));
+ // Configure global host sanitizing.
+ conf.addConfiguredGlobal("hostname-char-set", Element::create("[^A-Z]"));
+ conf.addConfiguredGlobal("hostname-char-replacement", Element::create("x"));
+ // Enable conflict resolution globally.
+ conf.addConfiguredGlobal("ddns-use-conflict-resolution", Element::create(true));
+ // Configure TTL percent globally.
+ conf.addConfiguredGlobal("ddns-ttl-percent", Element::create(20.0));
+
+ // Add a plain subnet
+ Triplet<uint32_t> def_triplet;
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.1.0"), 24,
+ def_triplet, def_triplet, 4000, SubnetID(1)));
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a subnet we need to set a callback function for each
+ // subnet to allow for fetching global parameters.
+ subnet1->setFetchGlobalsFn([conf]() -> ConstCfgGlobalsPtr {
+ return (conf.getConfiguredGlobals());
+ });
+
+ conf.getCfgSubnets4()->add(subnet1);
+
+ // Add a shared network
+ SharedNetwork4Ptr frognet(new SharedNetwork4("frog"));
+ conf.getCfgSharedNetworks4()->add(frognet);
+
+ // Add a shared subnet
+ Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.0"), 24,
+ def_triplet, def_triplet, 4000, SubnetID(2)));
+
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a subnet we need to set a callback function for each
+ // subnet to allow for fetching global parameters.
+ subnet2->setFetchGlobalsFn([conf]() -> ConstCfgGlobalsPtr {
+ return (conf.getConfiguredGlobals());
+ });
+
+ frognet->add(subnet2);
+ subnet2->setDdnsSendUpdates(true);
+ subnet2->setDdnsOverrideNoUpdate(true);
+ subnet2->setDdnsOverrideClientUpdate(true);
+ subnet2->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
+ subnet2->setDdnsGeneratedPrefix("prefix");
+ subnet2->setDdnsQualifyingSuffix("example.com.");
+ subnet2->setHostnameCharSet("");
+ subnet2->setDdnsUpdateOnRenew(true);
+ subnet2->setDdnsUseConflictResolution(false);
+ subnet2->setDdnsTtlPercent(Optional<double>(40.0));
+
+ // Get DDNS params for subnet1.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet1));
+
+ // Verify subnet1 values are right. Note, updates should be disabled.
+ EXPECT_FALSE(params->getEnableUpdates());
+ EXPECT_FALSE(params->getOverrideNoUpdate());
+ EXPECT_FALSE(params->getOverrideClientUpdate());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, params->getReplaceClientNameMode());
+ EXPECT_TRUE(params->getGeneratedPrefix().empty());
+ EXPECT_TRUE(params->getQualifyingSuffix().empty());
+ EXPECT_EQ("[^A-Z]", params->getHostnameCharSet());
+ EXPECT_EQ("x", params->getHostnameCharReplacement());
+ EXPECT_FALSE(params->getUpdateOnRenew());
+ EXPECT_TRUE(params->getUseConflictResolution());
+ EXPECT_FALSE(params->getTtlPercent().unspecified());
+ EXPECT_EQ(20.0, params->getTtlPercent().get());
+
+ // We inherited a non-blank hostname_char_set so we
+ // should get a sanitizer instance.
+ isc::util::str::StringSanitizerPtr sanitizer;
+ ASSERT_NO_THROW(sanitizer = params->getHostnameSanitizer());
+ EXPECT_TRUE(sanitizer);
+
+ // Get DDNS params for subnet2.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet2));
+
+ // Verify subnet2 values are right. Note, updates should be disabled,
+ // because D2Client is disabled.
+ EXPECT_FALSE(params->getEnableUpdates());
+ EXPECT_TRUE(params->getOverrideNoUpdate());
+ EXPECT_TRUE(params->getOverrideClientUpdate());
+ EXPECT_EQ(D2ClientConfig::RCM_ALWAYS, params->getReplaceClientNameMode());
+ EXPECT_EQ("prefix", params->getGeneratedPrefix());
+ EXPECT_EQ("example.com.", params->getQualifyingSuffix());
+ EXPECT_EQ("", params->getHostnameCharSet());
+ EXPECT_EQ("x", params->getHostnameCharReplacement());
+ EXPECT_TRUE(params->getUpdateOnRenew());
+ EXPECT_FALSE(params->getUseConflictResolution());
+ EXPECT_FALSE(params->getTtlPercent().unspecified());
+ EXPECT_EQ(40.0, params->getTtlPercent().get());
+
+ // We have a blank hostname-char-set so we should not get a sanitizer instance.
+ ASSERT_NO_THROW(sanitizer = params->getHostnameSanitizer());
+ ASSERT_FALSE(sanitizer);
+
+ // Enable D2Client.
+ enableD2Client(true);
+
+ // Make sure subnet1 updates are still disabled.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet1));
+ EXPECT_FALSE(params->getEnableUpdates());
+
+ // Make sure subnet2 updates are now enabled.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet2));
+ EXPECT_TRUE(params->getEnableUpdates());
+
+ // Enable sending updates globally. This should inherit down subnet1.
+ conf.addConfiguredGlobal("ddns-send-updates", Element::create(true));
+
+ // Make sure subnet1 updates are now enabled.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet1));
+ EXPECT_TRUE(params->getEnableUpdates());
+
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (ConstCfgGlobalsPtr());
+ });
+
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (ConstCfgGlobalsPtr());
+ });
+}
+
+// Verifies that the fallback values for DDNS parameters when
+// no Subnet4 has been selected. In theory, we should never want
+// these values without a selected subnet.
+TEST_F(SrvConfigTest, getDdnsParamsNoSubnetTest4) {
+ DdnsParamsPtr params;
+
+ CfgMgr::instance().setFamily(AF_INET);
+ SrvConfig conf(32);
+
+ // Enable D2 connectivity.
+ enableD2Client(true);
+
+ // Give all of the parameters a global value.
+ conf.addConfiguredGlobal("ddns-send-updates", Element::create(true));
+ conf.addConfiguredGlobal("ddns-override-no-update", Element::create(true));
+ conf.addConfiguredGlobal("ddns-override-client-update", Element::create(true));
+ conf.addConfiguredGlobal("ddns-replace-client-name", Element::create("always"));
+ conf.addConfiguredGlobal("ddns-generated-prefix", Element::create("some_prefix"));
+ conf.addConfiguredGlobal("ddns-qualifying-suffix", Element::create("example.com"));
+ conf.addConfiguredGlobal("hostname-char-set", Element::create("[^A-Z]"));
+ conf.addConfiguredGlobal("hostname-char-replacement", Element::create("x"));
+ conf.addConfiguredGlobal("ddns-update-on-renew", Element::create(true));
+ conf.addConfiguredGlobal("ddns-use-conflict-resolution", Element::create(false));
+ conf.addConfiguredGlobal("ddns-ttl-percent", Element::create(77.0));
+
+ // Get DDNS params for no subnet.
+ Subnet4Ptr subnet4;
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet4));
+
+ // Verify fallback values are right. Note, updates should be disabled.
+ EXPECT_FALSE(params->getEnableUpdates());
+ EXPECT_FALSE(params->getOverrideNoUpdate());
+ EXPECT_FALSE(params->getOverrideClientUpdate());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, params->getReplaceClientNameMode());
+ EXPECT_TRUE(params->getGeneratedPrefix().empty());
+ EXPECT_TRUE(params->getQualifyingSuffix().empty());
+ EXPECT_TRUE(params->getHostnameCharSet().empty());
+ EXPECT_TRUE(params->getHostnameCharReplacement().empty());
+ EXPECT_FALSE(params->getUpdateOnRenew());
+ EXPECT_TRUE(params->getUseConflictResolution());
+ EXPECT_TRUE(params->getTtlPercent().unspecified());
+}
+
+// Verifies that the scoped values for DDNS parameters can be fetched
+// for a given Subnet6.
+TEST_F(SrvConfigTest, getDdnsParamsTest6) {
+ DdnsParamsPtr params;
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ SrvConfig conf(32);
+
+ // This disables D2 connectivity. When it is false, updates
+ // are off at all scopes, regardless of ddns-send-updates values.
+ enableD2Client(false);
+
+ // Disable sending updates globally.
+ conf.addConfiguredGlobal("ddns-send-updates", Element::create(false));
+ // Configure global host sanitizing.
+ conf.addConfiguredGlobal("hostname-char-set", Element::create("[^A-Z]"));
+ conf.addConfiguredGlobal("hostname-char-replacement", Element::create("x"));
+ // Enable conflict resolution globally.
+ conf.addConfiguredGlobal("ddns-use-conflict-resolution", Element::create(true));
+ // Configure TTL percent globally.
+ conf.addConfiguredGlobal("ddns-ttl-percent", Element::create(25.0));
+
+ // Add a plain subnet
+ Triplet<uint32_t> def_triplet;
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64,
+ 1000, 2000, 3000, 4000, SubnetID(1)));
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a subnet we need to set a callback function for each
+ // subnet to allow for fetching global parameters.
+ subnet1->setFetchGlobalsFn([conf]() -> ConstCfgGlobalsPtr {
+ return (conf.getConfiguredGlobals());
+ });
+
+ conf.getCfgSubnets6()->add(subnet1);
+
+ // Add a shared network
+ SharedNetwork6Ptr frognet(new SharedNetwork6("frog"));
+ conf.getCfgSharedNetworks6()->add(frognet);
+
+ // Add a shared subnet
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 64,
+ 1000, 2000, 3000, 4000, SubnetID(2)));
+
+ // In order to take advantage of the dynamic inheritance of global
+ // parameters to a subnet we need to set a callback function for each
+ // subnet to allow for fetching global parameters.
+ subnet2->setFetchGlobalsFn([conf]() -> ConstCfgGlobalsPtr {
+ return (conf.getConfiguredGlobals());
+ });
+
+ frognet->add(subnet2);
+ subnet2->setDdnsSendUpdates(true);
+ subnet2->setDdnsOverrideNoUpdate(true);
+ subnet2->setDdnsOverrideClientUpdate(true);
+ subnet2->setDdnsReplaceClientNameMode(D2ClientConfig::RCM_ALWAYS);
+ subnet2->setDdnsGeneratedPrefix("prefix");
+ subnet2->setDdnsQualifyingSuffix("example.com.");
+ subnet2->setHostnameCharSet("");
+ subnet2->setDdnsUpdateOnRenew(true);
+ subnet2->setDdnsUseConflictResolution(false);
+ subnet2->setDdnsTtlPercent(Optional<double>(45.0));
+
+ // Get DDNS params for subnet1.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet1));
+
+ // Verify subnet1 values are right. Note, updates should be disabled.
+ EXPECT_FALSE(params->getEnableUpdates());
+ EXPECT_FALSE(params->getOverrideNoUpdate());
+ EXPECT_FALSE(params->getOverrideClientUpdate());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, params->getReplaceClientNameMode());
+ EXPECT_TRUE(params->getGeneratedPrefix().empty());
+ EXPECT_TRUE(params->getQualifyingSuffix().empty());
+ EXPECT_EQ("[^A-Z]", params->getHostnameCharSet());
+ EXPECT_EQ("x", params->getHostnameCharReplacement());
+ EXPECT_FALSE(params->getUpdateOnRenew());
+ EXPECT_TRUE(params->getUseConflictResolution());
+ EXPECT_FALSE(params->getTtlPercent().unspecified());
+ EXPECT_EQ(25.0, params->getTtlPercent().get());
+
+ // We inherited a non-blank hostname_char_set so we
+ // should get a sanitizer instance.
+ isc::util::str::StringSanitizerPtr sanitizer;
+ ASSERT_NO_THROW(sanitizer = params->getHostnameSanitizer());
+ EXPECT_TRUE(sanitizer);
+
+ // Get DDNS params for subnet2.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet2));
+
+ // Verify subnet2 values are right. Note, updates should be disabled,
+ // because D2Client is disabled.
+ EXPECT_FALSE(params->getEnableUpdates());
+ EXPECT_TRUE(params->getOverrideNoUpdate());
+ EXPECT_TRUE(params->getOverrideClientUpdate());
+ EXPECT_EQ(D2ClientConfig::RCM_ALWAYS, params->getReplaceClientNameMode());
+ EXPECT_EQ("prefix", params->getGeneratedPrefix());
+ EXPECT_EQ("example.com.", params->getQualifyingSuffix());
+ EXPECT_EQ("", params->getHostnameCharSet());
+ EXPECT_EQ("x", params->getHostnameCharReplacement());
+ EXPECT_TRUE(params->getUpdateOnRenew());
+ EXPECT_FALSE(params->getUseConflictResolution());
+ EXPECT_FALSE(params->getTtlPercent().unspecified());
+ EXPECT_EQ(45.0, params->getTtlPercent().get());
+
+ // We have a blank hostname-char-set so we should not get a sanitizer instance.
+ ASSERT_NO_THROW(sanitizer = params->getHostnameSanitizer());
+ ASSERT_FALSE(sanitizer);
+
+ // Enable D2Client.
+ enableD2Client(true);
+
+ // Make sure subnet1 updates are still disabled.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet1));
+ EXPECT_FALSE(params->getEnableUpdates());
+
+ // Make sure subnet2 updates are now enabled.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet2));
+ EXPECT_TRUE(params->getEnableUpdates());
+
+ // Enable sending updates globally. This should inherit down subnet1.
+ conf.addConfiguredGlobal("ddns-send-updates", Element::create(true));
+
+ // Make sure subnet1 updates are now enabled.
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet1));
+ EXPECT_TRUE(params->getEnableUpdates());
+
+ subnet1->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (ConstCfgGlobalsPtr());
+ });
+
+ subnet2->setFetchGlobalsFn([]() -> ConstCfgGlobalsPtr {
+ return (ConstCfgGlobalsPtr());
+ });
+}
+
+// Verifies that the fallback values for DDNS parameters when
+// no Subnet6 has been selected. In theory, we should never want
+// these values without a selected subnet.
+TEST_F(SrvConfigTest, getDdnsParamsNoSubnetTest6) {
+ DdnsParamsPtr params;
+
+ CfgMgr::instance().setFamily(AF_INET6);
+ SrvConfig conf(32);
+
+ // Enable D2 connectivity.
+ enableD2Client(true);
+
+ // Give all of the parameters a global value.
+ conf.addConfiguredGlobal("ddns-send-updates", Element::create(true));
+ conf.addConfiguredGlobal("ddns-override-no-update", Element::create(true));
+ conf.addConfiguredGlobal("ddns-override-client-update", Element::create(true));
+ conf.addConfiguredGlobal("ddns-replace-client-name", Element::create("always"));
+ conf.addConfiguredGlobal("ddns-generated-prefix", Element::create("some_prefix"));
+ conf.addConfiguredGlobal("ddns-qualifying-suffix", Element::create("example.com"));
+ conf.addConfiguredGlobal("hostname-char-set", Element::create("[^A-Z]"));
+ conf.addConfiguredGlobal("hostname-char-replacement", Element::create("x"));
+ conf.addConfiguredGlobal("ddns-update-on-renew", Element::create(true));
+ conf.addConfiguredGlobal("ddns-use-conflict-resolution", Element::create(false));
+ conf.addConfiguredGlobal("ddns-ttl-percent", Element::create(77.0));
+
+ // Get DDNS params for no subnet.
+ Subnet6Ptr subnet6;
+ ASSERT_NO_THROW(params = conf_.getDdnsParams(subnet6));
+
+ // Verify fallback values are right. Note, updates should be disabled.
+ EXPECT_FALSE(params->getEnableUpdates());
+ EXPECT_FALSE(params->getOverrideNoUpdate());
+ EXPECT_FALSE(params->getOverrideClientUpdate());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, params->getReplaceClientNameMode());
+ EXPECT_TRUE(params->getGeneratedPrefix().empty());
+ EXPECT_TRUE(params->getQualifyingSuffix().empty());
+ EXPECT_TRUE(params->getHostnameCharSet().empty());
+ EXPECT_TRUE(params->getHostnameCharReplacement().empty());
+ EXPECT_FALSE(params->getUpdateOnRenew());
+ EXPECT_TRUE(params->getUseConflictResolution());
+ EXPECT_TRUE(params->getTtlPercent().unspecified());
+}
+
+// Verifies that adding multi threading settings works
+TEST_F(SrvConfigTest, multiThreadingSettings) {
+ SrvConfig conf(32);
+ ElementPtr param = Element::createMap();
+ param->set("enable-multi-threading", Element::create(true));
+ conf.setDHCPMultiThreading(param);
+ EXPECT_TRUE(isEquivalent(param, conf.getDHCPMultiThreading()));
+}
+
+// Verifies that sanityChecksLifetime works as expected.
+TEST_F(SrvConfigTest, sanityChecksLifetime) {
+ // First the overload checking the current config.
+ // Note that lifetimes have a default so some cases here should not happen.
+ {
+ SCOPED_TRACE("no lifetime");
+
+ SrvConfig conf(32);
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("lifetime only");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime only");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("max-lifetime only");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime and max-lifetime but no lifetime");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(1000));
+ std::string msg = "have min-valid-lifetime and max-valid-lifetime ";
+ msg += "but no valid-lifetime (default)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "have min-preferred-lifetime and max-preferred-lifetime ";
+ msg += "but no preferred-lifetime (default)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("all lifetime parameters");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(3000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(1500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime("preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime > max-lifetime");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(500));
+ std::string msg = "the value of min-valid-lifetime (2000) is ";
+ msg += "not less than max-valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of min-preferred-lifetime (1000) is ";
+ msg += "not less than max-preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime > lifetime");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(500));
+ std::string msg = "the value of min-valid-lifetime (2000) is ";
+ msg += "not less than (default) valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of min-preferred-lifetime (1000) is ";
+ msg += "not less than (default) preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime > max-lifetime");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(500));
+ std::string msg = "the value of (default) valid-lifetime (2000) is ";
+ msg += "not less than max-valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of (default) preferred-lifetime (1000) is ";
+ msg += "not less than max-preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime not between min-lifetime and max-lifetime (too small)");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(250));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(1000));
+ std::string msg = "the value of (default) valid-lifetime (500) is ";
+ msg += "not between min-valid-lifetime (1000) and ";
+ msg += "max-valid-lifetime (2000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of (default) preferred-lifetime (250) is ";
+ msg += "not between min-preferred-lifetime (500) and ";
+ msg += "max-preferred-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime not between min-lifetime and max-lifetime (too large)");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(3000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(1500));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(1000));
+ std::string msg = "the value of (default) valid-lifetime (3000) is ";
+ msg += "not between min-valid-lifetime (1000) and ";
+ msg += "max-valid-lifetime (2000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of (default) preferred-lifetime (1500) is ";
+ msg += "not between min-preferred-lifetime (500) and ";
+ msg += "max-preferred-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime("preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ // Second the overload checking an external config before merging.
+ // We assume that the target config is correct as this was the case
+ // when this overload is used, and this lowers the number of cases...
+
+ SrvConfig target(10);
+ target.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ target.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ target.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ target.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ target.addConfiguredGlobal("max-valid-lifetime", Element::create(3000));
+ target.addConfiguredGlobal("max-preferred-lifetime", Element::create(1500));
+
+ {
+ SCOPED_TRACE("no lifetime");
+
+ SrvConfig conf(32);
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target, "valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("lifetime only");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target, "valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime only");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target, "valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("max-lifetime only");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(3000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(1500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target, "valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime and max-lifetime but no lifetime");
+
+ SrvConfig empty(10);
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(3000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(1500));
+ std::string msg = "have min-valid-lifetime and ";
+ msg += "max-valid-lifetime but no valid-lifetime (default)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(empty, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "have min-preferred-lifetime and ";
+ msg += "max-preferred-lifetime but no preferred-lifetime (default)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(empty,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("all lifetime parameters");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(3000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(1500));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target, "valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("overwrite all lifetime parameters");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(100));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(50));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(200));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(100));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(300));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(150));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target, "valid-lifetime"));
+ EXPECT_NO_THROW(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"));
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime > max-lifetime");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(500));
+ std::string msg = "the value of new min-valid-lifetime (2000) is ";
+ msg += "not less than new max-valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new min-preferred-lifetime (1000) is ";
+ msg += "not less than new max-preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("target min-lifetime > max-lifetime");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(250));
+ std::string msg = "the value of previous min-valid-lifetime (1000) is ";
+ msg += "not less than new max-valid-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of previous min-preferred-lifetime (500) is ";
+ msg += "not less than new max-preferred-lifetime (250)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime > target max-lifetime");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(4000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(2000));
+ std::string msg = "the value of new min-valid-lifetime (4000) is ";
+ msg += "not less than previous max-valid-lifetime (3000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new min-preferred-lifetime (2000) is ";
+ msg += "not less than previous max-preferred-lifetime (1500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime > lifetime");
+
+ SrvConfig empty(10);
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(500));
+ std::string msg = "the value of new min-valid-lifetime (2000) is ";
+ msg += "not less than new (default) valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(empty, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new min-preferred-lifetime (1000) is ";
+ msg += "not less than new (default) preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(empty,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("target min-lifetime > lifetime");
+
+ SrvConfig conf(32);
+ SrvConfig target2(20);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(500));
+ target2.addConfiguredGlobal("min-valid-lifetime", Element::create(2000));
+ target2.addConfiguredGlobal("min-preferred-lifetime", Element::create(1000));
+ std::string msg = "the value of previous min-valid-lifetime (2000) ";
+ msg += "is not less than new (default) valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of previous min-preferred-lifetime (1000) ";
+ msg += "is not less than new (default) preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("min-lifetime > target lifetime");
+
+ SrvConfig conf(32);
+ SrvConfig target2(20);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(1000));
+ target2.addConfiguredGlobal("valid-lifetime", Element::create(1000));
+ target2.addConfiguredGlobal("preferred-lifetime", Element::create(500));
+ std::string msg = "the value of new min-valid-lifetime (2000) is ";
+ msg += "not less than previous (default) valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new min-preferred-lifetime (1000) is ";
+ msg += "not less than previous (default) preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime > max-lifetime");
+
+ SrvConfig empty(10);
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(500));
+ std::string msg = "the value of new (default) valid-lifetime (2000) ";
+ msg += "is not less than new max-valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(empty, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new (default) preferred-lifetime (1000) ";
+ msg += "is not less than new max-preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(empty,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("target lifetime > max-lifetime");
+
+ SrvConfig conf(32);
+ SrvConfig target2(20);
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(1000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(500));
+ target2.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ target2.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ std::string msg = "the value of previous (default) valid-lifetime ";
+ msg += "(2000) is not less than new max-valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of previous (default) preferred-lifetime ";
+ msg += "(1000) is not less than new max-preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime > target max-lifetime");
+
+ SrvConfig conf(32);
+ SrvConfig target2(20);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(2000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(1000));
+ target2.addConfiguredGlobal("max-valid-lifetime", Element::create(1000));
+ target2.addConfiguredGlobal("max-preferred-lifetime", Element::create(500));
+ std::string msg = "the value of new (default) valid-lifetime (2000) ";
+ msg += "is not less than previous max-valid-lifetime (1000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new (default) preferred-lifetime (1000) ";
+ msg += "is not less than previous max-preferred-lifetime (500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target2,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime not between min-lifetime and max-lifetime (too small)");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(500));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(250));
+ std::string msg = "the value of new (default) valid-lifetime (500) ";
+ msg += "is not between previous min-valid-lifetime (1000) and ";
+ msg += "previous max-valid-lifetime (3000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new (default) preferred-lifetime (250) ";
+ msg += "is not between previous min-preferred-lifetime (500) and ";
+ msg += "previous max-preferred-lifetime (1500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime not between min-lifetime and max-lifetime (too large)");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("valid-lifetime", Element::create(4000));
+ conf.addConfiguredGlobal("preferred-lifetime", Element::create(2000));
+ std::string msg = "the value of new (default) valid-lifetime (4000) ";
+ msg += "is not between previous min-valid-lifetime (1000) and ";
+ msg += "previous max-valid-lifetime (3000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of new (default) preferred-lifetime (2000) ";
+ msg += "is not between previous min-preferred-lifetime (500) and ";
+ msg += "previous max-preferred-lifetime (1500)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime not between min-lifetime and max-lifetime (too low)");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(100));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(50));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(300));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(150));
+ std::string msg = "the value of previous (default) valid-lifetime ";
+ msg += "(2000) is not between new min-valid-lifetime (100) and ";
+ msg += "new max-valid-lifetime (300)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of previous (default) preferred-lifetime ";
+ msg += "(1000) is not between new min-preferred-lifetime (50) and ";
+ msg += "new max-preferred-lifetime (150)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+
+ {
+ SCOPED_TRACE("lifetime not between min-lifetime and max-lifetime (too high)");
+
+ SrvConfig conf(32);
+ conf.addConfiguredGlobal("min-valid-lifetime", Element::create(10000));
+ conf.addConfiguredGlobal("min-preferred-lifetime", Element::create(5000));
+ conf.addConfiguredGlobal("max-valid-lifetime", Element::create(30000));
+ conf.addConfiguredGlobal("max-preferred-lifetime", Element::create(15000));
+ std::string msg = "the value of previous (default) valid-lifetime ";
+ msg += "(2000) is not between new min-valid-lifetime (10000) and ";
+ msg += "new max-valid-lifetime (30000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target, "valid-lifetime"),
+ isc::BadValue, msg);
+ msg = "the value of previous (default) preferred-lifetime ";
+ msg += "(1000) is not between new min-preferred-lifetime (5000) and ";
+ msg += "new max-preferred-lifetime (15000)";
+ EXPECT_THROW_MSG(conf.sanityChecksLifetime(target,
+ "preferred-lifetime"),
+ isc::BadValue, msg);
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
new file mode 100644
index 0000000..69f762c
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -0,0 +1,2045 @@
+// Copyright (C) 2012-2023 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 <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
+#include <dhcpsrv/flq_allocator.h>
+#include <dhcpsrv/flq_allocation_state.h>
+#include <dhcpsrv/iterative_allocator.h>
+#include <dhcpsrv/iterative_allocation_state.h>
+#include <dhcpsrv/random_allocator.h>
+#include <dhcpsrv/random_allocation_state.h>
+#include <dhcpsrv/shared_network.h>
+#include <dhcpsrv/subnet.h>
+#include <exceptions/exceptions.h>
+#include <testutils/log_utils.h>
+
+#include <boost/pointer_cast.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+TEST(Subnet4Test, constructor) {
+ EXPECT_NO_THROW(Subnet4 subnet1(IOAddress("192.0.2.2"), 16,
+ 1, 2, 3, 10));
+
+ EXPECT_THROW(Subnet4 subnet2(IOAddress("192.0.2.0"),
+ 33, 1, 2, 3, SubnetID(2)),
+ BadValue); // invalid prefix length
+ EXPECT_THROW(Subnet4 subnet3(IOAddress("2001:db8::1"),
+ 24, 1, 2, 3, SubnetID(3)),
+ BadValue); // IPv6 addresses are not allowed in Subnet4
+}
+
+// This test verifies that the Subnet4 factory function creates a
+// valid subnet instance.
+TEST(Subnet4Test, create) {
+ auto subnet = Subnet4::create(IOAddress("192.0.2.2"), 16,
+ 1, 2, 3, 10);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_EQ("192.0.2.2/16", subnet->toText());
+ EXPECT_EQ(1, subnet->getT1().get());
+ EXPECT_EQ(2, subnet->getT2().get());
+ EXPECT_EQ(3, subnet->getValid().get());
+ EXPECT_EQ(10, subnet->getID());
+}
+
+// This test verifies the default values set for the subnets and verifies
+// that the optional values are unspecified.
+TEST(Subnet4Test, defaults) {
+ Triplet<uint32_t> t1;
+ Triplet<uint32_t> t2;
+ Triplet<uint32_t> valid_lft;
+ Subnet4 subnet(IOAddress("192.0.2.0"), 24,
+ t1, t2, valid_lft, SubnetID(10));
+
+ EXPECT_TRUE(subnet.getIface().unspecified());
+ EXPECT_TRUE(subnet.getIface().empty());
+
+ EXPECT_TRUE(subnet.getClientClass().unspecified());
+ EXPECT_TRUE(subnet.getClientClass().empty());
+
+ EXPECT_TRUE(subnet.getValid().unspecified());
+ EXPECT_EQ(0, subnet.getValid().get());
+
+ EXPECT_TRUE(subnet.getT1().unspecified());
+ EXPECT_EQ(0, subnet.getT1().get());
+
+ EXPECT_TRUE(subnet.getT2().unspecified());
+ EXPECT_EQ(0, subnet.getT2().get());
+
+ EXPECT_TRUE(subnet.getReservationsGlobal().unspecified());
+ EXPECT_FALSE(subnet.getReservationsGlobal().get());
+
+ EXPECT_TRUE(subnet.getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(subnet.getReservationsInSubnet().get());
+
+ EXPECT_TRUE(subnet.getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(subnet.getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(subnet.getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(subnet.getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(subnet.getT1Percent().unspecified());
+ EXPECT_EQ(0.0, subnet.getT1Percent().get());
+
+ EXPECT_TRUE(subnet.getT2Percent().unspecified());
+ EXPECT_EQ(0.0, subnet.getT2Percent().get());
+
+ EXPECT_TRUE(subnet.getMatchClientId().unspecified());
+ EXPECT_TRUE(subnet.getMatchClientId().get());
+
+ EXPECT_TRUE(subnet.getAuthoritative().unspecified());
+ EXPECT_FALSE(subnet.getAuthoritative().get());
+
+ EXPECT_TRUE(subnet.getSiaddr().unspecified());
+ EXPECT_TRUE(subnet.getSiaddr().get().isV4Zero());
+
+ EXPECT_TRUE(subnet.getSname().unspecified());
+ EXPECT_TRUE(subnet.getSname().empty());
+
+ EXPECT_TRUE(subnet.getFilename().unspecified());
+ EXPECT_TRUE(subnet.getFilename().empty());
+
+ EXPECT_FALSE(subnet.get4o6().enabled());
+
+ EXPECT_TRUE(subnet.get4o6().getIface4o6().unspecified());
+ EXPECT_TRUE(subnet.get4o6().getIface4o6().empty());
+
+ EXPECT_TRUE(subnet.get4o6().getSubnet4o6().unspecified());
+ EXPECT_TRUE(subnet.get4o6().getSubnet4o6().get().first.isV6Zero());
+ EXPECT_EQ(128, subnet.get4o6().getSubnet4o6().get().second);
+
+ EXPECT_TRUE(subnet.getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(subnet.getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(subnet.getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(subnet.getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(subnet.getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(subnet.getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(subnet.getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, subnet.getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(subnet.getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(subnet.getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(subnet.getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(subnet.getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(subnet.getHostnameCharSet().unspecified());
+ EXPECT_TRUE(subnet.getHostnameCharSet().empty());
+
+ EXPECT_TRUE(subnet.getHostnameCharReplacement().unspecified());
+ EXPECT_TRUE(subnet.getHostnameCharReplacement().empty());
+
+ EXPECT_TRUE(subnet.getDdnsUpdateOnRenew().unspecified());
+ EXPECT_FALSE(subnet.getDdnsUpdateOnRenew().get());
+
+ EXPECT_TRUE(subnet.getOfferLft().unspecified());
+ EXPECT_EQ(0, subnet.getOfferLft().get());
+}
+
+TEST(Subnet4Test, inRange) {
+ Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000, SubnetID(1));
+
+ EXPECT_EQ(1000, subnet.getT1().get());
+ EXPECT_EQ(2000, subnet.getT2().get());
+ EXPECT_EQ(3000, subnet.getValid().get());
+
+ EXPECT_FALSE(subnet.hasRelays());
+
+ EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
+ EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
+ EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
+ EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.255")));
+ EXPECT_FALSE(subnet.inRange(IOAddress("192.0.3.0")));
+ EXPECT_FALSE(subnet.inRange(IOAddress("0.0.0.0")));
+ EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
+}
+
+// Checks whether the relay list is empty by default
+// and basic operations function
+TEST(Subnet4Test, relay) {
+ Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000, SubnetID(1));
+
+ // Should be empty.
+ EXPECT_FALSE(subnet.hasRelays());
+ EXPECT_EQ(0, subnet.getRelayAddresses().size());
+
+ // Matching should fail.
+ EXPECT_FALSE(subnet.hasRelayAddress(IOAddress("192.0.123.45")));
+
+ // Should be able to add them.
+ subnet.addRelayAddress(IOAddress("192.0.123.45"));
+ subnet.addRelayAddress(IOAddress("192.0.123.46"));
+
+ // Should not be empty.
+ EXPECT_TRUE(subnet.hasRelays());
+
+ // Should be two in the list.
+ EXPECT_EQ(2, subnet.getRelayAddresses().size());
+
+ // Should be able to match them if they are there.
+ EXPECT_TRUE(subnet.hasRelayAddress(IOAddress("192.0.123.45")));
+ EXPECT_TRUE(subnet.hasRelayAddress(IOAddress("192.0.123.46")));
+
+ // Should not match those that are not.
+ EXPECT_FALSE(subnet.hasRelayAddress(IOAddress("192.0.123.47")));
+}
+
+// Checks whether siaddr field can be set and retrieved correctly.
+TEST(Subnet4Test, siaddr) {
+ Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000, SubnetID(1));
+
+ // Check if the default is 0.0.0.0
+ EXPECT_EQ("0.0.0.0", subnet.getSiaddr().get().toText());
+
+ // Check that we can set it up
+ EXPECT_NO_THROW(subnet.setSiaddr(IOAddress("1.2.3.4")));
+
+ // Check that we can get it back
+ EXPECT_EQ("1.2.3.4", subnet.getSiaddr().get().toText());
+
+ // Check that only v4 addresses are supported
+ EXPECT_THROW(subnet.setSiaddr(IOAddress("2001:db8::1")),
+ BadValue);
+}
+
+// Checks whether server-hostname field can be set and retrieved correctly.
+TEST(Subnet4Test, serverHostname) {
+ Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000, SubnetID(1));
+
+ // Check if the default is empty
+ EXPECT_TRUE(subnet.getSname().empty());
+
+ // Check that we can set it up
+ EXPECT_NO_THROW(subnet.setSname("foobar"));
+
+ // Check that we can get it back
+ EXPECT_EQ("foobar", subnet.getSname().get());
+}
+
+// Checks whether boot-file-name field can be set and retrieved correctly.
+TEST(Subnet4Test, bootFileName) {
+ Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000, SubnetID(1));
+
+ // Check if the default is empty
+ EXPECT_TRUE(subnet.getFilename().empty());
+
+ // Check that we can set it up
+ EXPECT_NO_THROW(subnet.setFilename("foobar"));
+
+ // Check that we can get it back
+ EXPECT_EQ("foobar", subnet.getFilename().get());
+}
+
+// Checks if the match-client-id flag can be set and retrieved.
+TEST(Subnet4Test, matchClientId) {
+ Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000, SubnetID(1));
+
+ // By default the flag should be set to true.
+ EXPECT_TRUE(subnet.getMatchClientId());
+
+ // Modify it and retrieve.
+ subnet.setMatchClientId(false);
+ EXPECT_FALSE(subnet.getMatchClientId());
+
+ // Modify again.
+ subnet.setMatchClientId(true);
+ EXPECT_TRUE(subnet.getMatchClientId());
+}
+
+// Checks that it is possible to add and retrieve multiple pools.
+TEST(Subnet4Test, pool4InSubnet4) {
+
+ auto subnet = Subnet4::create(IOAddress("192.1.2.0"),
+ 24, 1, 2, 3, SubnetID(1));
+
+ PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+ PoolPtr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+ PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+ pool3->allowClientClass("bar");
+ PoolPtr pool4(new Pool4(IOAddress("192.1.2.200"), 30));
+
+ // Add pools in reverse order to make sure that they get ordered by
+ // first address.
+ EXPECT_NO_THROW(subnet->addPool(pool4));
+
+ // If there's only one pool, get that pool
+ PoolPtr mypool = subnet->getAnyPool(Lease::TYPE_V4);
+ EXPECT_EQ(mypool, pool4);
+
+ EXPECT_NO_THROW(subnet->addPool(pool3));
+ EXPECT_NO_THROW(subnet->addPool(pool2));
+ EXPECT_NO_THROW(subnet->addPool(pool1));
+
+ // If there are more than one pool and we didn't provide hint, we
+ // should get the first pool
+ EXPECT_NO_THROW(mypool = subnet->getAnyPool(Lease::TYPE_V4));
+
+ EXPECT_EQ(mypool, pool1);
+
+ // If we provide a hint, we should get a pool that this hint belongs to
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.1.2.201")));
+ EXPECT_EQ(mypool, pool4);
+
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.1.2.129")));
+ EXPECT_EQ(mypool, pool2);
+
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.1.2.64")));
+ EXPECT_EQ(mypool, pool1);
+
+ // Specify addresses which don't belong to any existing pools. The
+ // third parameter prevents it from returning "any" available
+ // pool if a good match is not found.
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.1.2.210"),
+ false));
+ EXPECT_FALSE(mypool);
+
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.1.1.254"),
+ false));
+ EXPECT_FALSE(mypool);
+
+ // Now play with classes
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ // If we provide a hint, we should get a pool that this hint belongs to
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class,
+ IOAddress("192.1.2.201")));
+ EXPECT_EQ(mypool, pool4);
+
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class,
+ IOAddress("192.1.2.129")));
+ EXPECT_EQ(mypool, pool2);
+
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class,
+ IOAddress("192.1.2.64")));
+ EXPECT_EQ(mypool, pool1);
+
+ // Specify addresses which don't belong to any existing pools.
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, three_classes,
+ IOAddress("192.1.2.210")));
+ EXPECT_FALSE(mypool);
+
+ // Pool3 requires a member of bar
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, no_class,
+ IOAddress("192.1.2.195")));
+ EXPECT_FALSE(mypool);
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, foo_class,
+ IOAddress("192.1.2.195")));
+ EXPECT_FALSE(mypool);
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, bar_class,
+ IOAddress("192.1.2.195")));
+ EXPECT_EQ(mypool, pool3);
+ ASSERT_NO_THROW(mypool = subnet->getPool(Lease::TYPE_V4, three_classes,
+ IOAddress("192.1.2.195")));
+ EXPECT_EQ(mypool, pool3);
+}
+
+// Check if it's possible to get specified number of possible leases for
+// an IPv4 subnet.
+TEST(Subnet4Test, getCapacity) {
+
+ // There's one /24 pool.
+ auto subnet = Subnet4::create(IOAddress("192.1.2.0"),
+ 24, 1, 2, 3, SubnetID(1));
+
+ // There are no pools defined, so the total number of available addrs is 0.
+ EXPECT_EQ(0, subnet->getPoolCapacity(Lease::TYPE_V4));
+
+ // Let's add a /25 pool. That's 128 addresses.
+ PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+ subnet->addPool(pool1);
+ EXPECT_EQ(128, subnet->getPoolCapacity(Lease::TYPE_V4));
+
+ // Let's add another /26 pool. That's extra 64 addresses.
+ PoolPtr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+ subnet->addPool(pool2);
+ EXPECT_EQ(192, subnet->getPoolCapacity(Lease::TYPE_V4));
+
+ // Let's add a third pool /30. This one has 4 addresses.
+ PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+ subnet->addPool(pool3);
+ EXPECT_EQ(196, subnet->getPoolCapacity(Lease::TYPE_V4));
+
+ // Let's add a forth pool /30. This one has 4 addresses.
+ PoolPtr pool4(new Pool4(IOAddress("192.1.2.200"), 30));
+ subnet->addPool(pool4);
+ EXPECT_EQ(200, subnet->getPoolCapacity(Lease::TYPE_V4));
+
+ // Now play with classes
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ pool3->allowClientClass("bar");
+
+ // Pool3 requires a member of bar
+ EXPECT_EQ(196, subnet->getPoolCapacity(Lease::TYPE_V4, no_class));
+ EXPECT_EQ(196, subnet->getPoolCapacity(Lease::TYPE_V4, foo_class));
+ EXPECT_EQ(200, subnet->getPoolCapacity(Lease::TYPE_V4, bar_class));
+ EXPECT_EQ(200, subnet->getPoolCapacity(Lease::TYPE_V4, three_classes));
+}
+
+// Checks that it is not allowed to add invalid pools.
+TEST(Subnet4Test, pool4Checks) {
+
+ auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+ 8, 1, 2, 3, SubnetID(1));
+
+ // this one is in subnet
+ Pool4Ptr pool1(new Pool4(IOAddress("192.254.0.0"), 16));
+ subnet->addPool(pool1);
+
+ // this one is larger than the subnet!
+ Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
+
+ ASSERT_THROW(subnet->addPool(pool2), BadValue);
+
+ // this one is totally out of blue
+ Pool4Ptr pool3(new Pool4(IOAddress("1.2.0.0"), 16));
+ ASSERT_THROW(subnet->addPool(pool3), BadValue);
+
+ // This pool should be added just fine.
+ Pool4Ptr pool4(new Pool4(IOAddress("192.0.2.10"),
+ IOAddress("192.0.2.20")));
+ ASSERT_NO_THROW(subnet->addPool(pool4));
+
+ // This one overlaps with the previous pool.
+ Pool4Ptr pool5(new Pool4(IOAddress("192.0.2.1"),
+ IOAddress("192.0.2.15")));
+ ASSERT_THROW(subnet->addPool(pool5), BadValue);
+
+ // This one also overlaps.
+ Pool4Ptr pool6(new Pool4(IOAddress("192.0.2.20"),
+ IOAddress("192.0.2.30")));
+ ASSERT_THROW(subnet->addPool(pool6), BadValue);
+
+ // This one "surrounds" the other pool.
+ Pool4Ptr pool7(new Pool4(IOAddress("192.0.2.8"),
+ IOAddress("192.0.2.23")));
+ ASSERT_THROW(subnet->addPool(pool7), BadValue);
+
+ // This one does not overlap.
+ Pool4Ptr pool8(new Pool4(IOAddress("192.0.2.30"),
+ IOAddress("192.0.2.40")));
+ ASSERT_NO_THROW(subnet->addPool(pool8));
+
+ // This one has a lower bound in the pool of 192.0.2.10-20.
+ Pool4Ptr pool9(new Pool4(IOAddress("192.0.2.18"),
+ IOAddress("192.0.2.30")));
+ ASSERT_THROW(subnet->addPool(pool9), BadValue);
+
+ // This one has an upper bound in the pool of 192.0.2.30-40.
+ Pool4Ptr pool10(new Pool4(IOAddress("192.0.2.25"),
+ IOAddress("192.0.2.32")));
+ ASSERT_THROW(subnet->addPool(pool10), BadValue);
+
+ // Add a pool with a single address.
+ Pool4Ptr pool11(new Pool4(IOAddress("192.255.0.50"),
+ IOAddress("192.255.0.50")));
+ ASSERT_NO_THROW(subnet->addPool(pool11));
+
+ // Now we're going to add the same pool again. This is an interesting
+ // case because we're checking if the code is properly using upper_bound
+ // function, which returns a pool that has an address greater than the
+ // specified one.
+ ASSERT_THROW(subnet->addPool(pool11), BadValue);
+}
+
+// Tests whether Subnet4 object is able to store and process properly
+// information about allowed client class (a single class).
+TEST(Subnet4Test, clientClass) {
+ // Create the V4 subnet.
+ auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+ 8, 1, 2, 3, SubnetID(1));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ // This client belongs to foo, bar, baz and network classes.
+ isc::dhcp::ClientClasses four_classes;
+ four_classes.insert("foo");
+ four_classes.insert("bar");
+ four_classes.insert("baz");
+ four_classes.insert("network");
+
+ // No class restrictions defined, any client should be supported
+ EXPECT_TRUE(subnet->getClientClass().empty());
+ EXPECT_TRUE(subnet->clientSupported(no_class));
+ EXPECT_TRUE(subnet->clientSupported(foo_class));
+ EXPECT_TRUE(subnet->clientSupported(bar_class));
+ EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+ // Let's allow only clients belonging to "bar" class.
+ subnet->allowClientClass("bar");
+ EXPECT_EQ("bar", subnet->getClientClass().get());
+
+ EXPECT_FALSE(subnet->clientSupported(no_class));
+ EXPECT_FALSE(subnet->clientSupported(foo_class));
+ EXPECT_TRUE(subnet->clientSupported(bar_class));
+ EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+ // Add shared network which can only be selected when the client
+ // class is "network".
+ SharedNetwork4Ptr network(new SharedNetwork4("network"));
+ network->allowClientClass("network");
+ ASSERT_NO_THROW(network->add(subnet));
+
+ // This time, if the client doesn't support network class,
+ // the subnets from the shared network can't be selected.
+ EXPECT_FALSE(subnet->clientSupported(bar_class));
+ EXPECT_FALSE(subnet->clientSupported(three_classes));
+
+ // If the classes include "network", the subnet is selected.
+ EXPECT_TRUE(subnet->clientSupported(four_classes));
+}
+
+TEST(Subnet4Test, addInvalidOption) {
+ // Create the V4 subnet.
+ auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+ 8, 1, 2, 3, SubnetID(1));
+
+ // Create NULL pointer option. Attempt to add NULL option
+ // should result in exception.
+ OptionPtr option2;
+ ASSERT_FALSE(option2);
+ EXPECT_THROW(subnet->getCfgOption()->add(option2, false, false,
+ DHCP4_OPTION_SPACE),
+ isc::BadValue);
+}
+
+// This test verifies that inRange() and inPool() methods work properly.
+TEST(Subnet4Test, inRangeinPool) {
+ auto subnet = Subnet4::create(IOAddress("192.0.0.0"),
+ 8, 1, 2, 3, SubnetID(1));
+
+ // this one is in subnet
+ Pool4Ptr pool1(new Pool4(IOAddress("192.2.0.0"), 16));
+ subnet->addPool(pool1);
+
+ // 192.1.1.1 belongs to the subnet...
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.1.1.1")));
+
+ // ... but it does not belong to any pool within
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.1.1.1")));
+
+ // the last address that is in range, but out of pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.1.255.255")));
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.1.255.255")));
+
+ // the first address that is in range, in pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.2.0.0")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.0.0")));
+
+ // let's try something in the middle as well
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.2.3.4")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4")));
+
+ // the last address that is in range, in pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.2.255.255")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.255.255")));
+
+ // the first address that is in range, but out of pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("192.3.0.0")));
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.3.0.0")));
+
+ // Add with classes
+ pool1->allowClientClass("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4")));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), no_class));
+
+ // This client belongs to foo only
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), foo_class));
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), bar_class));
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_V4, IOAddress("192.2.3.4"), three_classes));
+}
+
+// This test checks if the toText() method returns text representation
+TEST(Subnet4Test, toText) {
+ auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+ 24, 1, 2, 3, SubnetID(1));
+ EXPECT_EQ("192.0.2.0/24", subnet->toText());
+}
+
+// This test verifies that the IPv4 prefix can be parsed into prefix/length pair.
+TEST(Subnet4Test, parsePrefix) {
+ std::pair<IOAddress, uint8_t> parsed =
+ std::make_pair(IOAddress::IPV4_ZERO_ADDRESS(), 0);
+
+ // Valid prefix.
+ EXPECT_NO_THROW(parsed = Subnet4::parsePrefix("192.0.5.0/24"));
+ EXPECT_EQ("192.0.5.0", parsed.first.toText());
+ EXPECT_EQ(24, static_cast<int>(parsed.second));
+
+ // Invalid IPv4 address.
+ EXPECT_THROW(Subnet4::parsePrefix("192.0.2.322/24"), BadValue);
+
+ // Invalid prefix length.
+ EXPECT_THROW(Subnet4::parsePrefix("192.0.2.0/64"), BadValue);
+ EXPECT_THROW(Subnet4::parsePrefix("192.0.2.0/0"), BadValue);
+
+ // No IP address.
+ EXPECT_THROW(Subnet4::parsePrefix(" /24"), BadValue);
+
+ // No prefix length but slash present.
+ EXPECT_THROW(Subnet4::parsePrefix("10.0.0.0/ "), BadValue);
+
+ // No slash sign.
+ EXPECT_THROW(Subnet4::parsePrefix("10.0.0.1"), BadValue);
+ // IPv6 is not allowed here.
+ EXPECT_THROW(Subnet4::parsePrefix("3000::/24"), BadValue);
+}
+
+// This test checks if the get() method returns proper parameters
+TEST(Subnet4Test, get) {
+ auto subnet = Subnet4::create(IOAddress("192.0.2.0"),
+ 28, 1, 2, 3, SubnetID(1));
+ EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+ EXPECT_EQ(28, subnet->get().second);
+}
+
+// Checks if the V4 is the only allowed type for Pool4 and if getPool()
+// is working properly.
+TEST(Subnet4Test, PoolType) {
+
+ auto subnet = Subnet4::create(IOAddress("192.2.0.0"),
+ 16, 1, 2, 3, SubnetID(1));
+
+ PoolPtr pool1(new Pool4(IOAddress("192.2.1.0"), 24));
+ PoolPtr pool2(new Pool4(IOAddress("192.2.2.0"), 24));
+ PoolPtr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:3::"), 64));
+ PoolPtr pool4(new Pool6(Lease::TYPE_TA, IOAddress("2001:db8:1:4::"), 64));
+ PoolPtr pool5(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:1::"), 64));
+
+ // There should be no pools of any type by default
+ EXPECT_EQ(PoolPtr(), subnet->getAnyPool(Lease::TYPE_V4));
+
+ // It should not be possible to ask for V6 pools in Subnet4
+ EXPECT_THROW(subnet->getAnyPool(Lease::TYPE_NA), BadValue);
+ EXPECT_THROW(subnet->getAnyPool(Lease::TYPE_TA), BadValue);
+ EXPECT_THROW(subnet->getAnyPool(Lease::TYPE_PD), BadValue);
+
+ // Let's add a single V4 pool and check that it can be retrieved
+ EXPECT_NO_THROW(subnet->addPool(pool1));
+
+ // If there's only one IA pool, get that pool (without and with hint)
+ EXPECT_EQ(pool1, subnet->getAnyPool(Lease::TYPE_V4));
+ EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_V4, IOAddress("192.0.1.167")));
+
+ // Let's add additional V4 pool
+ EXPECT_NO_THROW(subnet->addPool(pool2));
+
+ // Try without hints
+ EXPECT_EQ(pool1, subnet->getAnyPool(Lease::TYPE_V4));
+
+ // Try with valid hints
+ EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_V4, IOAddress("192.2.1.5")));
+ EXPECT_EQ(pool2, subnet->getPool(Lease::TYPE_V4, IOAddress("192.2.2.254")));
+
+ // Try with bogus hints (hints should be ignored)
+ EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_V4, IOAddress("10.1.1.1")));
+
+ // Trying to add Pool6 to Subnet4 is a big no,no!
+ EXPECT_THROW(subnet->addPool(pool3), BadValue);
+ EXPECT_THROW(subnet->addPool(pool4), BadValue);
+ EXPECT_THROW(subnet->addPool(pool5), BadValue);
+}
+
+// Tests if correct value of server identifier is returned when getServerId is
+// called.
+TEST(Subnet4Test, getServerId) {
+ // Initially, the subnet has no server identifier.
+ Subnet4 subnet(IOAddress("192.2.0.0"), 16, 1, 2, 3, SubnetID(1));
+ EXPECT_TRUE(subnet.getServerId().isV4Zero());
+
+ // Add server identifier.
+ OptionDefinitionPtr option_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ OptionCustomPtr option_server_id(new OptionCustom(*option_def, Option::V4));
+ option_server_id->writeAddress(IOAddress("1.2.3.4"));
+
+ CfgOptionPtr cfg_option = subnet.getCfgOption();
+ cfg_option->add(option_server_id, false, false, DHCP4_OPTION_SPACE);
+
+ // Verify that the server identifier returned by the Subnet4 object is
+ // correct.
+ OptionBuffer server_id_buf = { 1, 2, 3, 4 };
+ EXPECT_EQ("1.2.3.4", subnet.getServerId().toText());
+}
+
+// This test verifies that an iterative allocator and the corresponding
+// states are instantiated for a subnet.
+TEST(Subnet4Test, createAllocatorsIterative) {
+ // Create a subnet.
+ auto subnet = Subnet4::create(IOAddress("192.2.0.0"),
+ 16, 1, 2, 3, SubnetID(1));
+ ASSERT_TRUE(subnet);
+ // Create a pool.
+ auto pool = boost::make_shared<Pool4>(IOAddress("192.2.0.0"), 16);
+ subnet->addPool(pool);
+ // Instantiate the allocator.
+ ASSERT_NO_THROW(subnet->createAllocators());
+ // Expect iterative allocator.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>
+ (subnet->getAllocator(Lease::TYPE_V4)));
+ // Expect iterative allocation state for the subnet.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_V4)));
+ // Expect iterative allocation state for the pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolIterativeAllocationState>
+ (pool->getAllocationState()));
+}
+
+// This test verifies that a random allocator and the corresponding
+// states are instantiated for a subnet.
+TEST(Subnet4Test, createAllocatorsRandom) {
+ // Create a subnet.
+ auto subnet = Subnet4::create(IOAddress("192.2.0.0"),
+ 16, 1, 2, 3, SubnetID(1));
+ ASSERT_TRUE(subnet);
+ // Create a pool.
+ auto pool = boost::make_shared<Pool4>(IOAddress("192.2.0.0"), 16);
+ subnet->addPool(pool);
+ // Select the random allocator.
+ subnet->setAllocatorType("random");
+ // Instantiate the allocator.
+ ASSERT_NO_THROW(subnet->createAllocators());
+ // Expect random allocator.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (subnet->getAllocator(Lease::TYPE_V4)));
+ // Expect null subnet allocation state.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_V4));
+ // Expect random allocation state for the pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolRandomAllocationState>
+ (pool->getAllocationState()));
+}
+
+// This test verifies that an FLQ allocator and the corresponding
+// states are instantiated for a subnet.
+TEST(Subnet4Test, createAllocatorsFreeLeaseQueue) {
+ // Create a subnet.
+ auto subnet = Subnet4::create(IOAddress("192.2.0.0"),
+ 16, 1, 2, 3, SubnetID(1));
+ ASSERT_TRUE(subnet);
+ // Create a pool.
+ auto pool = boost::make_shared<Pool4>(IOAddress("192.2.0.0"), 16);
+ subnet->addPool(pool);
+ // Select the FLQ allocator.
+ subnet->setAllocatorType("flq");
+ // Instantiate the allocator.
+ ASSERT_NO_THROW(subnet->createAllocators());
+ // Expect FLQ allocator.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<FreeLeaseQueueAllocator>
+ (subnet->getAllocator(Lease::TYPE_V4)));
+ // Expect null subnet allocation state.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_V4));
+ // Expect FLQ allocation state for the pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>
+ (pool->getAllocationState()));
+}
+
+// Tests for Subnet6
+
+TEST(Subnet6Test, constructor) {
+
+ EXPECT_NO_THROW(Subnet6 subnet1(IOAddress("2001:db8:1::"), 64,
+ 1, 2, 3, 4, SubnetID(1)));
+
+ EXPECT_THROW(Subnet6 subnet2(IOAddress("2001:db8:1::"),
+ 129, 1, 2, 3, 4, SubnetID(2)),
+ BadValue); // invalid prefix length
+ EXPECT_THROW(Subnet6 subnet3(IOAddress("192.168.0.0"),
+ 32, 1, 2, 3, 4, SubnetID(3)),
+ BadValue); // IPv4 addresses are not allowed in Subnet6
+}
+
+// This test verifies that the Subnet6 factory function creates a
+// valid subnet instance.
+TEST(Subnet6Test, create) {
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"), 64,
+ 1, 2, 3, 4, 10);
+ ASSERT_TRUE(subnet);
+
+ EXPECT_EQ("2001:db8:1::/64", subnet->toText());
+ EXPECT_EQ(1, subnet->getT1().get());
+ EXPECT_EQ(2, subnet->getT2().get());
+ EXPECT_EQ(3, subnet->getPreferred().get());
+ EXPECT_EQ(4, subnet->getValid().get());
+ EXPECT_EQ(10, subnet->getID());
+}
+
+// This test verifies the default values set for the shared
+// networks and verifies that the optional values are unspecified.
+TEST(SharedNetwork6Test, defaults) {
+ Triplet<uint32_t> t1;
+ Triplet<uint32_t> t2;
+ Triplet<uint32_t> preferred_lft;
+ Triplet<uint32_t> valid_lft;
+ Subnet6 subnet(IOAddress("2001:db8:1::"), 64, t1, t2, preferred_lft,
+ valid_lft, SubnetID(1));
+
+ EXPECT_TRUE(subnet.getIface().unspecified());
+ EXPECT_TRUE(subnet.getIface().empty());
+
+ EXPECT_TRUE(subnet.getClientClass().unspecified());
+ EXPECT_TRUE(subnet.getClientClass().empty());
+
+ EXPECT_TRUE(subnet.getValid().unspecified());
+ EXPECT_EQ(0, subnet.getValid().get());
+
+ EXPECT_TRUE(subnet.getT1().unspecified());
+ EXPECT_EQ(0, subnet.getT1().get());
+
+ EXPECT_TRUE(subnet.getT2().unspecified());
+ EXPECT_EQ(0, subnet.getT2().get());
+
+ EXPECT_TRUE(subnet.getReservationsGlobal().unspecified());
+ EXPECT_FALSE(subnet.getReservationsGlobal().get());
+
+ EXPECT_TRUE(subnet.getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(subnet.getReservationsInSubnet().get());
+
+ EXPECT_TRUE(subnet.getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(subnet.getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(subnet.getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(subnet.getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(subnet.getT1Percent().unspecified());
+ EXPECT_EQ(0.0, subnet.getT1Percent().get());
+
+ EXPECT_TRUE(subnet.getT2Percent().unspecified());
+ EXPECT_EQ(0.0, subnet.getT2Percent().get());
+
+ EXPECT_TRUE(subnet.getPreferred().unspecified());
+ EXPECT_EQ(0, subnet.getPreferred().get());
+
+ EXPECT_TRUE(subnet.getRapidCommit().unspecified());
+ EXPECT_FALSE(subnet.getRapidCommit().get());
+
+ EXPECT_TRUE(subnet.getDdnsSendUpdates().unspecified());
+ EXPECT_FALSE(subnet.getDdnsSendUpdates().get());
+
+ EXPECT_TRUE(subnet.getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_FALSE(subnet.getDdnsOverrideNoUpdate().get());
+
+ EXPECT_TRUE(subnet.getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(subnet.getDdnsOverrideClientUpdate().get());
+
+ EXPECT_TRUE(subnet.getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::RCM_NEVER, subnet.getDdnsReplaceClientNameMode().get());
+
+ EXPECT_TRUE(subnet.getDdnsGeneratedPrefix().unspecified());
+ EXPECT_TRUE(subnet.getDdnsGeneratedPrefix().empty());
+
+ EXPECT_TRUE(subnet.getDdnsQualifyingSuffix().unspecified());
+ EXPECT_TRUE(subnet.getDdnsQualifyingSuffix().empty());
+
+ EXPECT_TRUE(subnet.getHostnameCharSet().unspecified());
+ EXPECT_TRUE(subnet.getHostnameCharSet().empty());
+
+ EXPECT_TRUE(subnet.getHostnameCharReplacement().unspecified());
+ EXPECT_TRUE(subnet.getHostnameCharReplacement().empty());
+
+ EXPECT_TRUE(subnet.getDdnsUpdateOnRenew().unspecified());
+ EXPECT_FALSE(subnet.getDdnsUpdateOnRenew().get());
+}
+
+TEST(Subnet6Test, inRange) {
+ Subnet6 subnet(IOAddress("2001:db8:1::"),
+ 64, 1000, 2000, 3000, 4000, SubnetID(1));
+
+ EXPECT_EQ(1000, subnet.getT1().get());
+ EXPECT_EQ(2000, subnet.getT2().get());
+ EXPECT_EQ(3000, subnet.getPreferred().get());
+ EXPECT_EQ(4000, subnet.getValid().get());
+
+ EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:0:ffff:ffff:ffff:ffff:ffff")));
+ EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::")));
+ EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::1")));
+ EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::ffff:ffff:ffff:ffff")));
+ EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:1:1::")));
+ EXPECT_FALSE(subnet.inRange(IOAddress("::")));
+}
+
+// Checks whether the relay list is empty by default
+// and basic operations function
+TEST(Subnet6Test, relay) {
+ Subnet6 subnet(IOAddress("2001:db8:1::"),
+ 64, 1000, 2000, 3000, 4000, SubnetID(1));
+
+ // Should be empty.
+ EXPECT_FALSE(subnet.hasRelays());
+ EXPECT_EQ(0, subnet.getRelayAddresses().size());
+
+ // Matching should fail.
+ EXPECT_FALSE(subnet.hasRelayAddress(IOAddress("2001:ffff::45")));
+
+ // Should be able to add them.
+ subnet.addRelayAddress(IOAddress("2001:ffff::45"));
+ subnet.addRelayAddress(IOAddress("2001:ffff::46"));
+
+ // Should not be empty.
+ EXPECT_TRUE(subnet.hasRelays());
+
+ // Should be two in the list.
+ EXPECT_EQ(2, subnet.getRelayAddresses().size());
+
+ // Should be able to match them if they are there.
+ EXPECT_TRUE(subnet.hasRelayAddress(IOAddress("2001:ffff::45")));
+ EXPECT_TRUE(subnet.hasRelayAddress(IOAddress("2001:ffff::46")));
+
+ // Should not match those that are not.
+ EXPECT_FALSE(subnet.hasRelayAddress(IOAddress("2001:ffff::47")));
+}
+
+// Test checks whether the number of addresses available in the pools are
+// calculated properly.
+TEST(Subnet6Test, Pool6getCapacity) {
+
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // There's 2^16 = 65536 addresses in this one.
+ PoolPtr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 112));
+
+ // There's 2^32 = 4294967296 addresses in each of those.
+ PoolPtr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::"), 96));
+ PoolPtr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:3::"), 96));
+
+ EXPECT_EQ(0, subnet->getPoolCapacity(Lease::TYPE_NA));
+ EXPECT_EQ(0, subnet->getPoolCapacity(Lease::TYPE_TA));
+ EXPECT_EQ(0, subnet->getPoolCapacity(Lease::TYPE_PD));
+
+ subnet->addPool(pool1);
+ EXPECT_EQ(65536, subnet->getPoolCapacity(Lease::TYPE_NA));
+
+ subnet->addPool(pool2);
+ EXPECT_EQ(uint64_t(4294967296ull + 65536), subnet->getPoolCapacity(Lease::TYPE_NA));
+
+ subnet->addPool(pool3);
+ EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536),
+ subnet->getPoolCapacity(Lease::TYPE_NA));
+
+ // Now play with classes
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ pool3->allowClientClass("bar");
+
+ // Pool3 requires a member of bar
+ EXPECT_EQ(uint64_t(4294967296ull + 65536),
+ subnet->getPoolCapacity(Lease::TYPE_NA, no_class));
+ EXPECT_EQ(uint64_t(4294967296ull + 65536),
+ subnet->getPoolCapacity(Lease::TYPE_NA, foo_class));
+ EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536),
+ subnet->getPoolCapacity(Lease::TYPE_NA, bar_class));
+ EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536),
+ subnet->getPoolCapacity(Lease::TYPE_NA, three_classes));
+}
+
+// Test checks whether the number of prefixes available in the pools are
+// calculated properly.
+TEST(Subnet6Test, Pool6PdgetPoolCapacity) {
+
+ auto subnet = Subnet6::create(IOAddress("2001:db8::"),
+ 32, 1, 2, 3, 4, SubnetID(1));
+
+ // There's 2^16 = 65536 addresses in this one.
+ PoolPtr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 64));
+
+ // There's 2^32 = 4294967296 addresses in each of those.
+ PoolPtr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 48, 80));
+ PoolPtr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:3::"), 48, 80));
+
+ EXPECT_EQ(0, subnet->getPoolCapacity(Lease::TYPE_NA));
+ EXPECT_EQ(0, subnet->getPoolCapacity(Lease::TYPE_TA));
+ EXPECT_EQ(0, subnet->getPoolCapacity(Lease::TYPE_PD));
+
+ subnet->addPool(pool1);
+ EXPECT_EQ(65536, subnet->getPoolCapacity(Lease::TYPE_PD));
+
+ subnet->addPool(pool2);
+ EXPECT_EQ(uint64_t(4294967296ull + 65536), subnet->getPoolCapacity(Lease::TYPE_PD));
+
+ subnet->addPool(pool3);
+ EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536),
+ subnet->getPoolCapacity(Lease::TYPE_PD));
+
+ // This is 2^64.
+ PoolPtr pool4(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:4::"), 48, 112));
+ subnet->addPool(pool4);
+ EXPECT_EQ(65536 + 4294967296ull + 4294967296ull + (int128_t(1) << 64),
+ subnet->getPoolCapacity(Lease::TYPE_PD));
+
+ PoolPtr pool5(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:5::"), 48, 112));
+ subnet->addPool(pool5);
+ EXPECT_EQ(65536 + 4294967296ull + 4294967296ull + (int128_t(1) << 64) + (int128_t(1) << 64),
+ subnet->getPoolCapacity(Lease::TYPE_PD));
+}
+
+TEST(Subnet6Test, Pool6InSubnet6) {
+
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ PoolPtr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
+ PoolPtr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::"), 64));
+ PoolPtr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:3::"), 64));
+
+ subnet->addPool(pool1);
+
+ // If there's only one pool, get that pool
+ PoolPtr mypool = subnet->getAnyPool(Lease::TYPE_NA);
+ EXPECT_EQ(mypool, pool1);
+
+ subnet->addPool(pool2);
+ subnet->addPool(pool3);
+
+ // If there are more than one pool and we didn't provide hint, we
+ // should get the first pool
+ mypool = subnet->getAnyPool(Lease::TYPE_NA);
+
+ EXPECT_EQ(mypool, pool1);
+
+ // If we provide a hint, we should get a pool that this hint belongs to
+ mypool = subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1:3::dead:beef"));
+
+ EXPECT_EQ(mypool, pool3);
+
+ // Now play with classes
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ pool3->allowClientClass("bar");
+
+ // Pool3 requires a member of bar
+ mypool = subnet->getPool(Lease::TYPE_NA, no_class,
+ IOAddress("2001:db8:1:3::dead:beef"));
+ EXPECT_FALSE(mypool);
+ mypool = subnet->getPool(Lease::TYPE_NA, foo_class,
+ IOAddress("2001:db8:1:3::dead:beef"));
+ EXPECT_FALSE(mypool);
+ mypool = subnet->getPool(Lease::TYPE_NA, bar_class,
+ IOAddress("2001:db8:1:3::dead:beef"));
+ EXPECT_EQ(mypool, pool3);
+ mypool = subnet->getPool(Lease::TYPE_NA, three_classes,
+ IOAddress("2001:db8:1:3::dead:beef"));
+ EXPECT_EQ(mypool, pool3);
+}
+
+// Check if Subnet6 supports different types of pools properly.
+TEST(Subnet6Test, poolTypes) {
+
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ PoolPtr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
+ PoolPtr pool2(new Pool6(Lease::TYPE_TA, IOAddress("2001:db8:1:2::"), 64));
+ PoolPtr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 64));
+ PoolPtr pool4(new Pool6(Lease::TYPE_PD, IOAddress("3000:1::"), 64));
+
+ PoolPtr pool5(new Pool4(IOAddress("192.0.2.0"), 24));
+
+ // There should be no pools of any type by default
+ EXPECT_EQ(PoolPtr(), subnet->getAnyPool(Lease::TYPE_NA));
+ EXPECT_EQ(PoolPtr(), subnet->getAnyPool(Lease::TYPE_TA));
+ EXPECT_EQ(PoolPtr(), subnet->getAnyPool(Lease::TYPE_PD));
+
+ // Trying to get IPv4 pool from Subnet6 is not allowed
+ EXPECT_THROW(subnet->getAnyPool(Lease::TYPE_V4), BadValue);
+
+ // Let's add a single IA pool and check that it can be retrieved
+ EXPECT_NO_THROW(subnet->addPool(pool1));
+
+ // If there's only one IA pool, get that pool
+ EXPECT_EQ(pool1, subnet->getAnyPool(Lease::TYPE_NA));
+ EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1:1::1")));
+
+ // Check if pools of different type are not returned
+ EXPECT_EQ(PoolPtr(), subnet->getAnyPool(Lease::TYPE_TA));
+ EXPECT_EQ(PoolPtr(), subnet->getAnyPool(Lease::TYPE_PD));
+
+ // We ask with good hints, but wrong types, should return nothing
+ EXPECT_EQ(PoolPtr(), subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:1:2::1")));
+ EXPECT_EQ(PoolPtr(), subnet->getPool(Lease::TYPE_TA, IOAddress("2001:db8:1:3::1")));
+
+ // Let's add TA and PD pools
+ EXPECT_NO_THROW(subnet->addPool(pool2));
+ EXPECT_NO_THROW(subnet->addPool(pool3));
+
+ // Try without hints
+ EXPECT_EQ(pool1, subnet->getAnyPool(Lease::TYPE_NA));
+ EXPECT_EQ(pool2, subnet->getAnyPool(Lease::TYPE_TA));
+ EXPECT_EQ(pool3, subnet->getAnyPool(Lease::TYPE_PD));
+
+ // Try with valid hints
+ EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1:1::1")));
+ EXPECT_EQ(pool2, subnet->getPool(Lease::TYPE_TA, IOAddress("2001:db8:1:2::1")));
+ EXPECT_EQ(pool3, subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:1:3::1")));
+
+ // Try with bogus hints (hints should be ignored)
+ EXPECT_EQ(pool1, subnet->getPool(Lease::TYPE_NA, IOAddress("2001:db8:1:7::1")));
+ EXPECT_EQ(pool2, subnet->getPool(Lease::TYPE_TA, IOAddress("2001:db8:1:7::1")));
+ EXPECT_EQ(pool3, subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:1:7::1")));
+
+ // Let's add a second PD pool
+ EXPECT_NO_THROW(subnet->addPool(pool4));
+
+ // Without hints, it should return the first pool
+ EXPECT_EQ(pool3, subnet->getAnyPool(Lease::TYPE_PD));
+
+ // With valid hint, it should return that hint
+ EXPECT_EQ(pool3, subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8:1:3::1")));
+ EXPECT_EQ(pool4, subnet->getPool(Lease::TYPE_PD, IOAddress("3000:1::")));
+
+ // With invalid hint, it should return the first pool
+ EXPECT_EQ(pool3, subnet->getPool(Lease::TYPE_PD, IOAddress("2001:db8::123")));
+
+ // Adding Pool4 to Subnet6 is a big no, no!
+ EXPECT_THROW(subnet->addPool(pool5), BadValue);
+}
+
+// Tests whether Subnet6 object is able to store and process properly
+// information about allowed client class (a single class).
+TEST(Subnet6Test, clientClass) {
+ // Create the V6 subnet.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+
+ // This client belongs to foo only.
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+
+ // This client belongs to foo, bar, baz and network classes.
+ isc::dhcp::ClientClasses four_classes;
+ four_classes.insert("foo");
+ four_classes.insert("bar");
+ four_classes.insert("baz");
+ four_classes.insert("network");
+
+ // No class restrictions defined, any client should be supported
+ EXPECT_TRUE(subnet->getClientClass().empty());
+ EXPECT_TRUE(subnet->clientSupported(no_class));
+ EXPECT_TRUE(subnet->clientSupported(foo_class));
+ EXPECT_TRUE(subnet->clientSupported(bar_class));
+ EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+ // Let's allow only clients belonging to "bar" class.
+ subnet->allowClientClass("bar");
+ EXPECT_EQ("bar", subnet->getClientClass().get());
+
+ EXPECT_FALSE(subnet->clientSupported(no_class));
+ EXPECT_FALSE(subnet->clientSupported(foo_class));
+ EXPECT_TRUE(subnet->clientSupported(bar_class));
+ EXPECT_TRUE(subnet->clientSupported(three_classes));
+
+ // Add shared network which can only be selected when the client
+ // class is "network".
+ SharedNetwork6Ptr network(new SharedNetwork6("network"));
+ network->allowClientClass("network");
+ ASSERT_NO_THROW(network->add(subnet));
+
+ // This time, if the client doesn't support network class,
+ // the subnets from the shared network can't be selected.
+ EXPECT_FALSE(subnet->clientSupported(bar_class));
+ EXPECT_FALSE(subnet->clientSupported(three_classes));
+
+ // If the classes include "network", the subnet is selected.
+ EXPECT_TRUE(subnet->clientSupported(four_classes));
+}
+
+// Checks that it is not allowed to add invalid pools.
+TEST(Subnet6Test, pool6Checks) {
+
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // this one is in subnet
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
+ ASSERT_NO_THROW(subnet->addPool(pool1));
+
+ // this one is larger than the subnet!
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8::"), 48));
+
+ ASSERT_THROW(subnet->addPool(pool2), BadValue);
+
+ // this one is totally out of blue
+ Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("3000::"), 16));
+ ASSERT_THROW(subnet->addPool(pool3), BadValue);
+
+ Pool6Ptr pool4(new Pool6(Lease::TYPE_NA, IOAddress("4001:db8:1::"), 80));
+ ASSERT_THROW(subnet->addPool(pool4), BadValue);
+
+ // This pool should be added just fine.
+ Pool6Ptr pool5(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::100"),
+ IOAddress("2001:db8:1:2::200")));
+ ASSERT_NO_THROW(subnet->addPool(pool5));
+
+ // This pool overlaps with a previously added pool.
+ Pool6Ptr pool6(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::1"),
+ IOAddress("2001:db8:1:2::150")));
+ ASSERT_THROW(subnet->addPool(pool6), BadValue);
+
+ // This pool also overlaps
+ Pool6Ptr pool7(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::150"),
+ IOAddress("2001:db8:1:2::300")));
+ ASSERT_THROW(subnet->addPool(pool7), BadValue);
+
+ // This one "surrounds" the other pool.
+ Pool6Ptr pool8(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::50"),
+ IOAddress("2001:db8:1:2::250")));
+ ASSERT_THROW(subnet->addPool(pool8), BadValue);
+
+ // This one does not overlap.
+ Pool6Ptr pool9(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::300"),
+ IOAddress("2001:db8:1:2::400")));
+ ASSERT_NO_THROW(subnet->addPool(pool9));
+
+ // This one has a lower bound in the pool of 2001:db8:1::100-200.
+ Pool6Ptr pool10(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::200"),
+ IOAddress("2001:db8:1:2::225")));
+ ASSERT_THROW(subnet->addPool(pool10), BadValue);
+
+ // This one has an upper bound in the pool of 2001:db8:1::300-400.
+ Pool6Ptr pool11(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:2::250"),
+ IOAddress("2001:db8:1:2::300")));
+ ASSERT_THROW(subnet->addPool(pool11), BadValue);
+
+ // Add a pool with a single address.
+ Pool6Ptr pool12(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:3::250"),
+ IOAddress("2001:db8:1:3::250")));
+ ASSERT_NO_THROW(subnet->addPool(pool12));
+
+ // Now we're going to add the same pool again. This is an interesting
+ // case because we're checking if the code is properly using upper_bound
+ // function, which returns a pool that has an address greater than the
+ // specified one.
+ ASSERT_THROW(subnet->addPool(pool12), BadValue);
+
+ // Prefix pool overlaps with the pool1. We can't hand out addresses and
+ // prefixes from the same range.
+ Pool6Ptr pool13(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:1:2::"),
+ 80, 96));
+ ASSERT_THROW(subnet->addPool(pool13), BadValue);
+}
+
+TEST(Subnet6Test, addOptions) {
+ // Create as subnet to add options to it.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, false,
+ DHCP6_OPTION_SPACE));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, false,
+ "isc"));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = subnet->getCfgOption()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = subnet->getCfgOption()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+TEST(Subnet6Test, addNonUniqueOptions) {
+ // Create as subnet to add options to it.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // Create a set of options with non-unique codes.
+ for (int i = 0; i < 2; ++i) {
+ // In the inner loop we create options with unique codes (100-109).
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, false,
+ DHCP6_OPTION_SPACE));
+ }
+ }
+
+ // Sanity check that all options are there.
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_EQ(20, options->size());
+
+ // Use container index #1 to get the options by their codes.
+ OptionContainerTypeIndex& idx = options->get<1>();
+ // Look for the codes 100-109.
+ for (uint16_t code = 100; code < 110; ++ code) {
+ // For each code we should get two instances of options->
+ OptionContainerTypeRange range = idx.equal_range(code);
+ // Distance between iterators indicates how many options
+ // have been returned for the particular code.
+ ASSERT_EQ(2, distance(range.first, range.second));
+ // Check that returned options actually have the expected option code.
+ for (OptionContainerTypeIndex::const_iterator option_desc = range.first;
+ option_desc != range.second; ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(code, option_desc->option_->getType());
+ }
+ }
+
+ // Let's try to find some non-exiting option.
+ const uint16_t non_existing_code = 150;
+ OptionContainerTypeRange range = idx.equal_range(non_existing_code);
+ // Empty set is expected.
+ EXPECT_EQ(0, distance(range.first, range.second));
+}
+
+TEST(Subnet6Test, addPersistentOption) {
+ // Create as subnet to add options to it.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // Add 10 options to the subnet with option codes 100 - 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ // We create 10 options and want some of them to be flagged
+ // persistent and some non-persistent. Persistent options are
+ // those that server sends to clients regardless if they ask
+ // for them or not. We pick 3 out of 10 options and mark them
+ // non-persistent and 7 other options persistent.
+ // Code values: 102, 105 and 108 are divisible by 3
+ // and options with these codes will be flagged non-persistent.
+ // Options with other codes will be flagged persistent.
+ bool persistent = (code % 3) ? true : false;
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, persistent,
+ false,
+ DHCP6_OPTION_SPACE));
+ }
+
+ // Get added options from the subnet.
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(DHCP6_OPTION_SPACE);
+
+ // options->get<2> returns reference to container index #2. This
+ // index is used to access options by the 'persistent' flag.
+ OptionContainerPersistIndex& idx = options->get<2>();
+
+ // Get all persistent options->
+ OptionContainerPersistRange range_persistent = idx.equal_range(true);
+ // 7 out of 10 options have been flagged persistent.
+ ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
+
+ // Get all non-persistent options->
+ OptionContainerPersistRange range_non_persistent = idx.equal_range(false);
+ // 3 out of 10 options have been flagged not persistent.
+ ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
+}
+
+TEST(Subnet6Test, getOptions) {
+ auto subnet = Subnet6::create(IOAddress("2001:db8::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // Add 10 options to a "dhcp6" option space in the subnet.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, false,
+ DHCP6_OPTION_SPACE));
+ }
+
+ // Check that we can get each added option descriptor using
+ // individually.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream stream;
+ // First, try the invalid option space name.
+ OptionDescriptor desc = subnet->getCfgOption()->get("isc", code);
+ // Returned descriptor should contain NULL option ptr.
+ EXPECT_FALSE(desc.option_);
+ // Now, try the valid option space.
+ desc = subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, code);
+ // Test that the option code matches the expected code.
+ ASSERT_TRUE(desc.option_);
+ EXPECT_EQ(code, desc.option_->getType());
+ }
+}
+
+TEST(Subnet6Test, addVendorOption) {
+
+ // Create as subnet to add options to it.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, false,
+ "vendor-12345678"));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->getCfgOption()->add(option, false, false,
+ "vendor-87654321"));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = subnet->getCfgOption()->getAll(12345678);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = subnet->getCfgOption()->getAll(87654321);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = subnet->getCfgOption()->getAll(1111111);
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test verifies that inRange() and inPool() methods work properly.
+TEST(Subnet6Test, inRangeinPool) {
+ auto subnet = Subnet6::create(IOAddress("2001:db8::"),
+ 32, 1, 2, 3, 4, SubnetID(1));
+
+ // this one is in subnet
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8::10"),
+ IOAddress("2001:db8::20")));
+ subnet->addPool(pool1);
+
+ // 2001:db8::1 belongs to the subnet...
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
+ // ... but it does not belong to any pool within
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::1")));
+
+ // the last address that is in range, but out of pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::f")));
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::f")));
+
+ // the first address that is in range, in pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::10")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::10")));
+
+ // let's try something in the middle as well
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::18")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18")));
+
+ // the last address that is in range, in pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::20")));
+ EXPECT_TRUE (subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::20")));
+
+ // the first address that is in range, but out of pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::21")));
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::21")));
+
+ // Add with classes
+ pool1->allowClientClass("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18")));
+
+ // This client does not belong to any class.
+ isc::dhcp::ClientClasses no_class;
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), no_class));
+
+ // This client belongs to foo only
+ isc::dhcp::ClientClasses foo_class;
+ foo_class.insert("foo");
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), foo_class));
+
+ // This client belongs to bar only. I like that client.
+ isc::dhcp::ClientClasses bar_class;
+ bar_class.insert("bar");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), bar_class));
+
+ // This client belongs to foo, bar and baz classes.
+ isc::dhcp::ClientClasses three_classes;
+ three_classes.insert("foo");
+ three_classes.insert("bar");
+ three_classes.insert("baz");
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_NA, IOAddress("2001:db8::18"), three_classes));
+}
+
+// This test verifies that inRange() and inPool() methods work properly
+// for prefixes too.
+TEST(Subnet6Test, PdinRangeinPool) {
+ auto subnet = Subnet6::create(IOAddress("2001:db8::"),
+ 64, 1, 2, 3, 4, SubnetID(1));
+
+ // this one is in subnet
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"),
+ 96, 112));
+ subnet->addPool(pool1);
+
+ // this one is not in subnet
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"),
+ 96, 112));
+ subnet->addPool(pool2);
+
+ // 2001:db8::1:0:0 belongs to the subnet...
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1:0:0")));
+ // ... but it does not belong to any pool within
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8::1:0:0")));
+
+ // 2001:db8:1::1 does not belong to the subnet...
+ EXPECT_FALSE(subnet->inRange(IOAddress("2001:db8:1::1")));
+ // ... but it belongs to the second pool
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8:1::1")));
+
+ // 2001:db8::1 belongs to the subnet and to the first pool
+ EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
+ EXPECT_TRUE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8::1")));
+
+ // 2001:db8:0:1:0:1:: does not belong to the subnet and any pool
+ EXPECT_FALSE(subnet->inRange(IOAddress("2001:db8:0:1:0:1::")));
+ EXPECT_FALSE(subnet->inPool(Lease::TYPE_PD, IOAddress("2001:db8:0:1:0:1::")));
+}
+
+// This test checks if the toText() method returns text representation
+TEST(Subnet6Test, toText) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+ EXPECT_EQ("2001:db8::/32", subnet.toText());
+}
+
+// This test verifies that the IPv6 prefix can be parsed into prefix/length pair.
+TEST(Subnet6Test, parsePrefix) {
+ std::pair<IOAddress, uint8_t> parsed =
+ std::make_pair(IOAddress::IPV6_ZERO_ADDRESS(), 0);
+
+ // Valid prefix.
+ EXPECT_NO_THROW(parsed = Subnet6::parsePrefix("2001:db8:1::/64"));
+ EXPECT_EQ("2001:db8:1::", parsed.first.toText());
+ EXPECT_EQ(64, static_cast<int>(parsed.second));
+
+ // Invalid IPv6 address.
+ EXPECT_THROW(Subnet6::parsePrefix("2001:db8::1::/64"), BadValue);
+
+ // Invalid prefix length.
+ EXPECT_THROW(Subnet6::parsePrefix("2001:db8:1::/164"), BadValue);
+ EXPECT_THROW(Subnet6::parsePrefix("2001:db8:1::/0"), BadValue);
+
+ // No IP address.
+ EXPECT_THROW(Subnet6::parsePrefix(" /64"), BadValue);
+
+ // No prefix length but slash present.
+ EXPECT_THROW(Subnet6::parsePrefix("3000::/ "), BadValue);
+
+ // No slash sign.
+ EXPECT_THROW(Subnet6::parsePrefix("3000::"), BadValue);
+
+ // IPv4 is not allowed here.
+ EXPECT_THROW(Subnet6::parsePrefix("192.0.2.0/24"), BadValue);
+}
+
+// This test checks if the get() method returns proper parameters
+TEST(Subnet6Test, get) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+ EXPECT_EQ("2001:db8::", subnet.get().first.toText());
+ EXPECT_EQ(32, subnet.get().second);
+}
+
+// This trivial test checks if interface name is stored properly
+// in Subnet6 objects.
+TEST(Subnet6Test, iface) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(1));
+
+ EXPECT_TRUE(subnet.getIface().empty());
+
+ subnet.setIface("en1");
+ EXPECT_EQ("en1", subnet.getIface().get());
+}
+
+// This trivial test checks if the interface-id option can be set and
+// later retrieved for a subnet6 object.
+TEST(Subnet6Test, interfaceId) {
+ // Create as subnet to add options to it.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ EXPECT_FALSE(subnet->getInterfaceId());
+
+ OptionPtr option(new Option(Option::V6, D6O_INTERFACE_ID, OptionBuffer(10, 0xFF)));
+ subnet->setInterfaceId(option);
+
+ EXPECT_EQ(option, subnet->getInterfaceId());
+
+}
+
+// This test checks that the Rapid Commit support can be enabled or
+// disabled for a subnet. It also checks that the Rapid Commit
+// support is disabled by default.
+TEST(Subnet6Test, rapidCommit) {
+ Subnet6 subnet(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+
+ // By default, the RC should be disabled.
+ EXPECT_FALSE(subnet.getRapidCommit());
+
+ // Enable Rapid Commit.
+ subnet.setRapidCommit(true);
+ EXPECT_TRUE(subnet.getRapidCommit());
+
+ // Disable again.
+ subnet.setRapidCommit(false);
+ EXPECT_FALSE(subnet.getRapidCommit());
+}
+
+// This test verifies that an iterative allocator and the corresponding
+// states are instantiated for a subnet.
+TEST(Subnet6Test, createAllocatorsIterative) {
+ // Create a subnet.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+ ASSERT_TRUE(subnet);
+ // NA pool.
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 112);
+ subnet->addPool(pool);
+ // TA pool.
+ auto ta_pool = boost::make_shared<Pool6>(Lease::TYPE_TA, IOAddress("2001:db8:1:2::"), 112);
+ subnet->addPool(ta_pool);
+ // PD pool.
+ auto pd_pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+ subnet->addPool(pd_pool);
+ // Instantiate the allocators.
+ ASSERT_NO_THROW(subnet->createAllocators());
+ // Expect iterative allocator for NA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>
+ (subnet->getAllocator(Lease::TYPE_NA)));
+ // Expect iterative allocator for TA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>
+ (subnet->getAllocator(Lease::TYPE_TA)));
+ // Expect iterative allocator for PD.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<IterativeAllocator>
+ (subnet->getAllocator(Lease::TYPE_PD)));
+ // Expect iterative allocation state for NA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_NA)));
+ // Expect iterative allocation state for TA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_TA)));
+ // Expect iterative allocation state for PD.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<SubnetIterativeAllocationState>
+ (subnet->getAllocationState(Lease::TYPE_PD)));
+ // Expect iterative allocation state for the NA pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolIterativeAllocationState>
+ (pool->getAllocationState()));
+ // Expect iterative allocation state for the TA pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolIterativeAllocationState>
+ (pool->getAllocationState()));
+ // Expect iterative allocation state for the PD pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolIterativeAllocationState>
+ (pd_pool->getAllocationState()));
+}
+
+// This test verifies that a random allocator and the corresponding
+// states are instantiated for a subnet.
+TEST(Subnet6Test, createAllocatorsRandom) {
+ // Create a subnet.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+ ASSERT_TRUE(subnet);
+ // NA pool.
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 112);
+ subnet->addPool(pool);
+ // TA pool.
+ auto ta_pool = boost::make_shared<Pool6>(Lease::TYPE_TA, IOAddress("2001:db8:1:2::"), 112);
+ subnet->addPool(ta_pool);
+ // PD pool.
+ auto pd_pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+ subnet->addPool(pd_pool);
+ // Select the random allocators.
+ subnet->setAllocatorType("random");
+ subnet->setPdAllocatorType("random");
+ // Instantiate the allocators.
+ ASSERT_NO_THROW(subnet->createAllocators());
+ // Expect random allocator for NA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (subnet->getAllocator(Lease::TYPE_NA)));
+ // Expect random allocator for TA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (subnet->getAllocator(Lease::TYPE_TA)));
+ // Expect random allocator for PD.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (subnet->getAllocator(Lease::TYPE_PD)));
+ // Expect null subnet allocation state for NA.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_NA));
+ // Expect null subnet allocation state for TA.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_TA));
+ // Expect null subnet allocation state for PD.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_PD));
+ // Expect random allocation state for the NA pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolRandomAllocationState>
+ (pool->getAllocationState()));
+ // Expect random allocation state for the TA pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolRandomAllocationState>
+ (pool->getAllocationState()));
+ // Expect random allocation state for the PD pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolRandomAllocationState>
+ (pd_pool->getAllocationState()));
+}
+
+// This test verifies that a FLQ allocator and the corresponding
+// states are instantiated for a subnet.
+TEST(Subnet6Test, createAllocatorsFreeLeaseQueue) {
+ // Create a subnet.
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+ ASSERT_TRUE(subnet);
+ // NA pool.
+ auto pool = boost::make_shared<Pool6>(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 112);
+ subnet->addPool(pool);
+ // TA pool.
+ auto ta_pool = boost::make_shared<Pool6>(Lease::TYPE_TA, IOAddress("2001:db8:1:2::"), 112);
+ subnet->addPool(ta_pool);
+ // PD pool.
+ auto pd_pool = boost::make_shared<Pool6>(Lease::TYPE_PD, IOAddress("3000::"), 112, 120);
+ subnet->addPool(pd_pool);
+ // Select the random allocator for addresses.
+ subnet->setAllocatorType("random");
+ // Select the FLQ allocator for the prefix delegation.
+ subnet->setPdAllocatorType("flq");
+ // Instantiate the allocators.
+ ASSERT_NO_THROW(subnet->createAllocators());
+ // Expect random allocator for NA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (subnet->getAllocator(Lease::TYPE_NA)));
+ // Expect random allocator for TA.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<RandomAllocator>
+ (subnet->getAllocator(Lease::TYPE_TA)));
+ // Expect FLQ allocator for PD.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<FreeLeaseQueueAllocator>
+ (subnet->getAllocator(Lease::TYPE_PD)));
+ // Expect null subnet allocation state for NA.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_NA));
+ // Expect null subnet allocation state for TA.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_TA));
+ // Expect null subnet allocation state for PD.
+ EXPECT_FALSE(subnet->getAllocationState(Lease::TYPE_PD));
+ // Expect random allocation state for the NA pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolRandomAllocationState>
+ (pool->getAllocationState()));
+ // Expect random allocation state for the TA pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolRandomAllocationState>
+ (pool->getAllocationState()));
+ // Expect FLQ allocation state for the PD pool.
+ EXPECT_TRUE(boost::dynamic_pointer_cast<PoolFreeLeaseQueueAllocationState>
+ (pd_pool->getAllocationState()));
+}
+
+// Test that it is not allowed to use the FLQ allocator for the address pools.
+TEST(Subnet6Test, createAllocatorsFreeLeaseQueueNotAllowed) {
+ auto subnet = Subnet6::create(IOAddress("2001:db8:1::"),
+ 56, 1, 2, 3, 4, SubnetID(1));
+ ASSERT_TRUE(subnet);
+
+ subnet->setAllocatorType("flq");
+ EXPECT_THROW(subnet->createAllocators(), BadValue);
+}
+
+// This test verifies that the IPv4 subnet can be fetched by id.
+TEST(SubnetFetcherTest, getSubnet4ById) {
+ Subnet4Collection collection;
+
+ // Subnet hasn't been added to the collection. A null pointer should
+ // be returned.
+ auto subnet = SubnetFetcher4::get(collection, SubnetID(1024));
+ EXPECT_FALSE(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 1024));
+ EXPECT_NO_THROW(collection.insert(subnet));
+
+ subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 1, 2, 3, 2048));
+ EXPECT_NO_THROW(collection.insert(subnet));
+
+ subnet = SubnetFetcher4::get(collection, SubnetID(1024));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1024, subnet->getID());
+ EXPECT_EQ("192.0.2.0/24", subnet->toText());
+
+ subnet = SubnetFetcher4::get(collection, SubnetID(2048));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(2048, subnet->getID());
+ EXPECT_EQ("192.0.3.0/24", subnet->toText());
+}
+
+// This test verifies that the IPv6 subnet can be fetched by id.
+TEST(SubnetFetcherTest, getSubnet6ById) {
+ Subnet6Collection collection;
+
+ // Subnet hasn't been added to the collection. A null pointer should
+ // be returned.
+ auto subnet = SubnetFetcher6::get(collection, SubnetID(1026));
+ EXPECT_FALSE(subnet);
+
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4, 1024));
+ EXPECT_NO_THROW(collection.insert(subnet));
+
+ subnet.reset(new Subnet6(IOAddress("2001:db8:2::"), 64, 1, 2, 3, 4, 2048));
+ EXPECT_NO_THROW(collection.insert(subnet));
+
+ subnet = SubnetFetcher6::get(collection, SubnetID(1024));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1024, subnet->getID());
+ EXPECT_EQ("2001:db8:1::/64", subnet->toText());
+
+ subnet = SubnetFetcher6::get(collection, SubnetID(2048));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(2048, subnet->getID());
+ EXPECT_EQ("2001:db8:2::/64", subnet->toText());
+}
+
+// Test fixture for subnet identifier auto-generation.
+class SubnetIdTest : public LogContentTest {
+public:
+
+ /// @brief virtual destructor.
+ virtual ~SubnetIdTest() {
+ Subnet::resetSubnetID();
+ }
+};
+
+// Test class for subnets with id = 0.
+class TestSubnet : public Subnet {
+public:
+ // @brief Constructor.
+ //
+ // @param prefix subnet prefix.
+ // @param len prefix length for the subnet.
+ TestSubnet(const IOAddress& prefix, uint8_t len)
+ : Subnet(prefix, len, 0) {
+ }
+
+ // @brief Returns the default address that will be used for pool selection.
+ virtual IOAddress default_pool() const {
+ isc_throw(NotImplemented, "default_pool");
+ }
+
+ /// @brief Instantiates the allocators and their states.
+ virtual void createAllocators() {
+ isc_throw(NotImplemented, "createAllocators");
+ }
+
+ /// @brief Checks if used pool type is valid.
+ virtual void checkType(Lease::Type type) const {
+ isc_throw(NotImplemented, "checkType " << type);
+ }
+};
+
+// Type of pointers to test subnets.
+typedef boost::shared_ptr<TestSubnet> TestSubnetPtr;
+
+// Test subnet identifier auto-generation.
+TEST_F(SubnetIdTest, unnumbered) {
+ // Reset subnet identifier counter.
+ Subnet::resetSubnetID();
+
+ // First subnet.
+ IOAddress addr1("192.0.2.0");
+ uint8_t len1(25);
+ TestSubnetPtr subnet1;
+ ASSERT_NO_THROW(subnet1.reset(new TestSubnet(addr1, len1)));
+ ASSERT_TRUE(subnet1);
+ EXPECT_EQ(1, subnet1->getID());
+ EXPECT_EQ("192.0.2.0/25", subnet1->toText());
+
+ // Second subnet.
+ IOAddress addr2("192.0.2.128");
+ uint8_t len2(25);
+ TestSubnetPtr subnet2;
+ ASSERT_NO_THROW(subnet2.reset(new TestSubnet(addr2, len2)));
+ ASSERT_TRUE(subnet2);
+ EXPECT_EQ(2, subnet2->getID());
+ EXPECT_EQ("192.0.2.128/25", subnet2->toText());
+
+ // Reset subnet identifier counter again to get another log.
+ Subnet::resetSubnetID();
+
+ // Third subnet.
+ IOAddress addr3("2001:db8:1::");
+ uint8_t len3(64);
+ TestSubnetPtr subnet3;
+ ASSERT_NO_THROW(subnet3.reset(new TestSubnet(addr3, len3)));
+ ASSERT_TRUE(subnet3);
+ EXPECT_EQ(1, subnet3->getID());
+ EXPECT_EQ("2001:db8:1::/64", subnet3->toText());
+
+ // Subnet 1 and 3 are logged.
+ std::string msg = "DHCPSRV_CONFIGURED_SUBNET_WITHOUT_ID ";
+ msg += "a subnet was configured without an id: ";
+ addString(msg + subnet1->toText());
+ addString(msg + subnet3->toText());
+ EXPECT_TRUE(checkFile());
+}
+
+}
diff --git a/src/lib/dhcpsrv/tests/test_get_callout_handle.cc b/src/lib/dhcpsrv/tests/test_get_callout_handle.cc
new file mode 100644
index 0000000..a5eac6b
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_get_callout_handle.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 2013-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 <dhcpsrv/callout_handle_store.h>
+#include "test_get_callout_handle.h"
+
+// Just instantiate the getCalloutHandle function and call it.
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+isc::hooks::CalloutHandlePtr
+testGetCalloutHandle(const Pkt6Ptr& pktptr) {
+ return (isc::dhcp::getCalloutHandle(pktptr));
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/tests/test_get_callout_handle.h b/src/lib/dhcpsrv/tests/test_get_callout_handle.h
new file mode 100644
index 0000000..c6f4a40
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_get_callout_handle.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2013-2017 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 TEST_GET_CALLOUT_HANDLE_H
+#define TEST_GET_CALLOUT_HANDLE_H
+
+#include <dhcp/pkt6.h>
+#include <hooks/callout_handle.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @file
+/// @brief Get Callout Handle
+///
+/// This function is a shall around getCalloutHandle. It's purpose is to
+/// ensure that the getCalloutHandle() template function is referred to by
+/// two separate compilation units, and so test that data stored in one unit
+/// can be accessed by another. (This should be the case, but some compilers
+/// maybe be odd when it comes to template instantiation.)
+///
+/// @param pktptr Pointer to a Pkt6 object.
+///
+/// @return CalloutHandlePtr pointing to CalloutHandle associated with the
+/// Pkt6 object.
+isc::hooks::CalloutHandlePtr
+testGetCalloutHandle(const Pkt6Ptr& pktptr);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+
+#endif // TEST_GET_CALLOUT_HANDLE_H
diff --git a/src/lib/dhcpsrv/tests/test_libraries.h.in b/src/lib/dhcpsrv/tests/test_libraries.h.in
new file mode 100644
index 0000000..5a5545e
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_libraries.h.in
@@ -0,0 +1,35 @@
+// Copyright (C) 2013-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/.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+static const char* CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1.so";
+static const char* CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2.so";
+
+// This library will try to get the following parameters:
+// - svalue (and will expect its value to be "string value")
+// - ivalue (and will expect its value to be 42)
+// - bvalue (and will expect its value to be true)
+static const char* CALLOUT_PARAMS_LIBRARY = "@abs_builddir@/.libs/libco3.so";
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
+
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc
new file mode 100644
index 0000000..4f7b58d
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/timer_mgr_unittest.cc
@@ -0,0 +1,484 @@
+// Copyright (C) 2016-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <sstream>
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Test fixture class for @c TimerMgr.
+class TimerMgrTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ TimerMgrTest() = default;
+
+ /// @brief Destructor
+ virtual ~TimerMgrTest() = default;
+
+private:
+
+ /// @brief Prepares the class for a test.
+ virtual void SetUp();
+
+ /// @brief Cleans up after the test.
+ virtual void TearDown();
+
+public:
+
+ /// @brief Wrapper method for registering a new timer.
+ ///
+ /// This method registers a new timer in the @c TimerMgr. It associates a
+ /// @c timerCallback method with a timer. This method registers a number of
+ /// calls to the particular timer in the @c calls_count_ map.
+ ///
+ /// @param timer_name Unique timer name.
+ /// @param timer_interval Timer interval.
+ /// @param mode Interval timer mode, which defaults to
+ /// @c IntervalTimer::ONE_SHOT.
+ void registerTimer(const std::string& timer_name, const long timer_interval,
+ const IntervalTimer::Mode& timer_mode = IntervalTimer::ONE_SHOT);
+
+ /// @brief Wait for one or many ready handlers.
+ ///
+ /// @param timeout Wait timeout in milliseconds.
+ /// @param call_receive Indicates if the @c IfaceMgr::receive6
+ /// should be called to run pending callbacks and clear
+ /// watch sockets.
+ void doWait(const long timeout, const bool call_receive = true);
+
+ /// @brief Generic callback for timers under test.
+ ///
+ /// This callback increases the calls count for specified timer name.
+ ///
+ /// @param timer_name Name of the timer for which callback counter should
+ /// be increased.
+ void timerCallback(const std::string& timer_name);
+
+ /// @brief Callback which generates exception.
+ ///
+ /// This callback is used to test that the @c TimerMgr can handle
+ /// the case when the callback generates exceptions.
+ void timerCallbackWithException();
+
+ /// @brief Create a generic callback function for the timer.
+ ///
+ /// This is just a wrapped to make it a bit more convenient
+ /// in the test.
+ std::function<void ()> makeCallback(const std::string& timer_name);
+
+ /// @brief Create a callback which generates exception.
+ std::function<void ()> makeCallbackWithException();
+
+ /// @brief Callback for timeout.
+ ///
+ /// This callback indicates the test timeout by setting the
+ /// @c timeout_ member.
+ void timeoutCallback();
+
+ // @brief This test checks that certain errors are returned when invalid
+ // parameters are specified when registering a timer, or when
+ // the registration can't be made.
+ void testRegisterTimer();
+
+ /// @brief This test verifies that it is possible to unregister a timer from
+ /// the TimerMgr.
+ void testUnregisterTimer();
+
+ /// @brief This test verifies that it is possible to unregister all timers.
+ void testUnregisterTimers();
+
+ /// @brief This test verifies that the timer execution can be cancelled.
+ void testCancel();
+
+ /// @brief This test verifies that the callbacks for the scheduled timers
+ /// are actually called.
+ void testScheduleTimers();
+
+ /// @brief This test verifies that exceptions emitted from the callback
+ /// would be handled by the TimerMgr.
+ void testCallbackWithException();
+
+ /// @brief Type definition for a map holding calls counters for
+ /// timers.
+ typedef std::map<std::string, unsigned int> CallsCount;
+
+ /// @brief Pointer to IO service used by the tests.
+ IOServicePtr io_service_;
+
+ /// @brief Holds the calls count for test timers.
+ ///
+ /// The key of this map holds the timer names. The value holds the number
+ /// of calls to the timer handlers.
+ CallsCount calls_count_;
+
+ /// @brief Instance of @c TimerMgr used by the tests.
+ TimerMgrPtr timer_mgr_;
+};
+
+void
+TimerMgrTest::SetUp() {
+ io_service_.reset(new IOService());
+ timer_mgr_ = TimerMgr::instance();
+ timer_mgr_->setIOService(io_service_);
+ calls_count_.clear();
+}
+
+void
+TimerMgrTest::TearDown() {
+ // Remove all timers.
+ timer_mgr_->unregisterTimers();
+}
+
+void
+TimerMgrTest::registerTimer(const std::string& timer_name, const long timer_interval,
+ const IntervalTimer::Mode& timer_mode) {
+ // Register the timer with the generic callback that counts the
+ // number of callback invocations.
+ ASSERT_NO_THROW(
+ timer_mgr_->registerTimer(timer_name, makeCallback(timer_name), timer_interval,
+ timer_mode)
+ );
+
+ calls_count_[timer_name] = 0;
+
+}
+
+void
+TimerMgrTest::doWait(const long timeout, const bool /*call_receive*/) {
+ IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, timeout, IntervalTimer::ONE_SHOT);
+ io_service_->run();
+ io_service_->get_io_service().reset();
+}
+
+void
+TimerMgrTest::timerCallback(const std::string& timer_name) {
+ // Accumulate the number of calls to the timer handler.
+ ++calls_count_[timer_name];
+
+ // The timer installed is the ONE_SHOT timer, so we have
+ // to reschedule the timer.
+ timer_mgr_->setup(timer_name);
+}
+
+void
+TimerMgrTest::timerCallbackWithException() {
+ isc_throw(Exception, "timerCallbackWithException");
+}
+
+std::function<void ()>
+TimerMgrTest::makeCallback(const std::string& timer_name) {
+ return (std::bind(&TimerMgrTest::timerCallback, this, timer_name));
+}
+
+std::function<void ()>
+TimerMgrTest::makeCallbackWithException() {
+ return (std::bind(&TimerMgrTest::timerCallbackWithException, this));
+}
+
+// This test checks that certain errors are returned when invalid
+// parameters are specified when registering a timer, or when
+// the registration can't be made.
+void
+TimerMgrTest::testRegisterTimer() {
+ // Empty timer name is not allowed.
+ ASSERT_THROW(timer_mgr_->registerTimer("", makeCallback("timer1"), 1,
+ IntervalTimer::ONE_SHOT),
+ BadValue);
+
+ // Add a timer with a correct name.
+ ASSERT_NO_THROW(timer_mgr_->registerTimer("timer2", makeCallback("timer2"), 1,
+ IntervalTimer::ONE_SHOT));
+ EXPECT_TRUE(timer_mgr_->isTimerRegistered("timer2"));
+
+ // Adding the timer with the same name as the existing timer is not
+ // allowed.
+ ASSERT_THROW(timer_mgr_->registerTimer("timer2", makeCallback("timer2"), 1,
+ IntervalTimer::ONE_SHOT),
+ BadValue);
+}
+
+TEST_F(TimerMgrTest, registerTimer) {
+ // Disable Multi-Threading.
+ MultiThreadingTest mt(false);
+ testRegisterTimer();
+}
+
+TEST_F(TimerMgrTest, registerTimerMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+ testRegisterTimer();
+}
+
+void
+TimerMgrTest::testUnregisterTimer() {
+ // Register a timer and start it.
+ ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
+ ASSERT_EQ(1, timer_mgr_->timersCount());
+ ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
+
+ // Wait for the timer to execute several times.
+ doWait(100);
+
+ // Remember how many times the timer's callback was executed.
+ const unsigned int calls_count = calls_count_["timer1"];
+ ASSERT_GT(calls_count, 0);
+
+ // Check that an attempt to unregister a non-existing timer would
+ // result in exception.
+ ASSERT_THROW(timer_mgr_->unregisterTimer("timer2"), BadValue);
+ // Number of timers shouldn't have changed.
+ ASSERT_EQ(1, timer_mgr_->timersCount());
+
+ // Now unregister the correct one.
+ ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1"));
+ ASSERT_EQ(0, timer_mgr_->timersCount());
+ EXPECT_FALSE(timer_mgr_->isTimerRegistered("timer1"));
+
+ doWait(100);
+
+ // The number of calls for the timer1 shouldn't change as the
+ // timer had been unregistered.
+ EXPECT_EQ(calls_count_["timer1"], calls_count);
+}
+
+TEST_F(TimerMgrTest, unregisterTimer) {
+ // Disable Multi-Threading.
+ MultiThreadingTest mt(false);
+ testUnregisterTimer();
+}
+
+TEST_F(TimerMgrTest, unregisterTimerMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+ testUnregisterTimer();
+}
+
+void
+TimerMgrTest::testUnregisterTimers() {
+ // Register 10 timers.
+ for (int i = 1; i <= 20; ++i) {
+ std::ostringstream s;
+ s << "timer" << i;
+ ASSERT_NO_FATAL_FAILURE(registerTimer(s.str(), 1))
+ << "fatal failure occurred while registering "
+ << s.str();
+ ASSERT_EQ(i, timer_mgr_->timersCount())
+ << "invalid number of registered timers returned";
+ ASSERT_NO_THROW(timer_mgr_->setup(s.str()))
+ << "exception thrown while calling setup() for the "
+ << s.str();
+ }
+
+ doWait(500);
+
+ // Make sure that all timers have been executed at least once.
+ for (CallsCount::iterator it = calls_count_.begin();
+ it != calls_count_.end(); ++it) {
+ unsigned int calls_count = it->second;
+ ASSERT_GT(calls_count, 0)
+ << "expected calls counter for timer"
+ << (std::distance(calls_count_.begin(), it) + 1)
+ << " greater than 0";
+ }
+
+ // Copy counters for all timers.
+ CallsCount calls_count(calls_count_);
+
+ // Let's unregister all timers.
+ ASSERT_NO_THROW(timer_mgr_->unregisterTimers());
+
+ // Make sure there are no timers registered.
+ ASSERT_EQ(0, timer_mgr_->timersCount());
+
+ doWait(500);
+
+ // The calls counter shouldn't change because there are
+ // no timers registered.
+ EXPECT_TRUE(calls_count == calls_count_);
+}
+
+TEST_F(TimerMgrTest, unregisterTimers) {
+ // Disable Multi-Threading.
+ MultiThreadingTest mt(false);
+ testUnregisterTimers();
+}
+
+TEST_F(TimerMgrTest, unregisterTimersMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+ testUnregisterTimers();
+}
+
+void
+TimerMgrTest::testCancel() {
+ // Register timer.
+ ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 1));
+
+ // Kick in the timer and wait for 500ms.
+ ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
+
+ doWait(500);
+
+ // Canceling non-existing timer should fail.
+ EXPECT_THROW(timer_mgr_->cancel("timer2"), BadValue);
+
+ // Canceling the good one should pass, even when the worker
+ // thread is running.
+ ASSERT_NO_THROW(timer_mgr_->cancel("timer1"));
+
+ // Remember how many calls have been invoked and wait for
+ // another 500ms.
+ unsigned int calls_count = calls_count_["timer1"];
+
+ doWait(500);
+
+ // The number of calls shouldn't change because the timer had been
+ // cancelled.
+ ASSERT_EQ(calls_count, calls_count_["timer1"]);
+
+ // Setup the timer again.
+ ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
+
+ // Restart the thread.
+ doWait(500);
+
+ // New calls should be recorded.
+ EXPECT_GT(calls_count_["timer1"], calls_count);
+}
+
+TEST_F(TimerMgrTest, cancel) {
+ // Disable Multi-Threading.
+ MultiThreadingTest mt(false);
+ testCancel();
+}
+
+TEST_F(TimerMgrTest, cancelMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+ testCancel();
+}
+
+void
+TimerMgrTest::testScheduleTimers() {
+ // Register two timers: 'timer1' and 'timer2'. The first timer will
+ // be executed at the 50ms interval. The second one at the 100ms
+ // interval.
+ ASSERT_NO_FATAL_FAILURE(registerTimer("timer1", 50));
+ ASSERT_NO_FATAL_FAILURE(registerTimer("timer2", 100));
+
+ // Kick in the timers.
+ ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
+ ASSERT_NO_THROW(timer_mgr_->setup("timer2"));
+
+ // Run IfaceMgr::receive6() in the loop for 1000ms. This function
+ // will read data from the watch sockets created when the timers
+ // were registered. The data is delivered to the watch sockets
+ // at the interval of the timers, which should break the blocking
+ // call to receive6(). As a result, the callbacks associated
+ // with the watch sockets should be called.
+ doWait(1000);
+
+ // We have been running the timer for 1000ms at the interval of
+ // 50ms. The maximum number of callbacks is 20. However, the
+ // callback itself takes time. Stopping the thread takes time.
+ // So, the real number differs significantly. We don't know
+ // exactly how many have been executed. It should be more
+ // than 10 for sure. But we really made up the numbers here.
+ EXPECT_GT(calls_count_["timer1"], 10);
+ // For the second timer it should be more than 5.
+ EXPECT_GT(calls_count_["timer2"], 5);
+
+ // Because the interval of the 'timer1' is lower than the
+ // interval of the 'timer2' the number of calls should
+ // be higher for the 'timer1'.
+ EXPECT_GT(calls_count_["timer1"], calls_count_["timer2"]);
+
+ // Remember the number of calls from 'timer1' and 'timer2'.
+ unsigned int calls_count_timer1 = calls_count_["timer1"];
+ unsigned int calls_count_timer2 = calls_count_["timer2"];
+
+ // Unregister the 'timer1'.
+ ASSERT_NO_THROW(timer_mgr_->unregisterTimer("timer1"));
+
+ // Wait another 500ms. The 'timer1' was unregistered so it
+ // should not make any more calls. The 'timer2' should still
+ // work as previously.
+ doWait(500);
+
+ // The number of calls shouldn't have changed.
+ EXPECT_EQ(calls_count_timer1, calls_count_["timer1"]);
+ // There should be some new calls registered for the 'timer2'.
+ EXPECT_GT(calls_count_["timer2"], calls_count_timer2);
+}
+
+TEST_F(TimerMgrTest, scheduleTimers) {
+ // Disable Multi-Threading.
+ MultiThreadingTest mt(false);
+ testScheduleTimers();
+}
+
+TEST_F(TimerMgrTest, scheduleTimersMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+ testScheduleTimers();
+}
+
+void
+TimerMgrTest::testCallbackWithException() {
+ // Create timer which will trigger callback generating exception.
+ ASSERT_NO_THROW(
+ timer_mgr_->registerTimer("timer1", makeCallbackWithException(), 1,
+ IntervalTimer::ONE_SHOT)
+ );
+
+ // Setup the timer.
+ ASSERT_NO_THROW(timer_mgr_->setup("timer1"));
+
+ // Start thread. We hope that exception will be caught by the @c TimerMgr
+ // and will not kill the process.
+ doWait(500);
+}
+
+TEST_F(TimerMgrTest, callbackWithException) {
+ // Disable Multi-Threading.
+ MultiThreadingTest mt(false);
+ testCallbackWithException();
+}
+
+TEST_F(TimerMgrTest, callbackWithExceptionMultiThreading) {
+ // Enable Multi-Threading.
+ MultiThreadingTest mt(true);
+ testCallbackWithException();
+}
+
+// This test verifies that IO service specified for the TimerMgr
+// must not be null.
+TEST_F(TimerMgrTest, setIOService) {
+ EXPECT_THROW(timer_mgr_->setIOService(IOServicePtr()),
+ BadValue);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc
new file mode 100644
index 0000000..1361c1c
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/tracking_lease_mgr_unittest.cc
@@ -0,0 +1,426 @@
+// Copyright (C) 2023 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 <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/testutils/concrete_lease_mgr.h>
+#include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
+#include <exceptions/exceptions.h>
+#include <functional>
+#include <gtest/gtest.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Test fixture class for @c TrackingLeaseMgr.
+class TrackingLeaseMgrTest : public GenericLeaseMgrTest {
+public:
+
+ /// @brief Reopen the database
+ ///
+ /// No-op implementation. We need to provide concrete implementation,
+ /// as this is a pure virtual method in GenericLeaseMgrTest.
+ virtual void reopen(Universe) {}
+
+ /// @brief Convenience function creating a lease instance.
+ ///
+ /// We don't use the @c initializeLease4 function here because it uses
+ /// tons of parameters we don't really need here. We simply need the
+ /// subnet id and the address.
+ ///
+ /// @param subnet_id subnet identifier for the lease.
+ /// @param address leased address.
+ Lease4Ptr initializeLease(int subnet_id, std::string address) const {
+ auto lease = boost::make_shared<Lease4>();
+ lease->subnet_id_ = SubnetID(subnet_id);
+ lease->addr_ = IOAddress(address);
+ return (lease);
+ }
+
+ /// @brief Convenience function creating a lease instance.
+ ///
+ /// We don't use the initializeLease6 function here because it uses
+ /// tons of parameters we don't really need here. We simply need the
+ /// subnet id, lease type and the address.
+ ///
+ /// @param subnet_id subnet identifier for the lease.
+ /// @param lease_type lease type.
+ /// @param address leased address.
+ Lease6Ptr initializeLease(int subnet_id, Lease::Type lease_type, std::string address) const {
+ auto lease = boost::make_shared<Lease6>();
+ lease->subnet_id_ = SubnetID(subnet_id);
+ lease->addr_ = IOAddress(address);
+ lease->type_ = lease_type;
+ return (lease);
+ }
+
+};
+
+/// Tests that leases can be locked and unlocked. When a lease is locked
+/// an attempt to lock it again fails.
+TEST_F(TrackingLeaseMgrTest, tryLock) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ // An attempt to lock an already locked lease should fail.
+ EXPECT_TRUE(mgr.tryLock(initializeLease(1, "192.0.2.1")));
+ EXPECT_FALSE(mgr.tryLock(initializeLease(1, "192.0.2.1")));
+ EXPECT_TRUE(mgr.isLocked(initializeLease(1, "192.0.2.1")));
+
+ // We can lock another lease but we cannot lock an already locked one.
+ EXPECT_TRUE(mgr.tryLock(initializeLease(1, "192.0.2.2")));
+ EXPECT_FALSE(mgr.tryLock(initializeLease(1, "192.0.2.1")));
+ EXPECT_FALSE(mgr.tryLock(initializeLease(2, "192.0.2.2")));
+ EXPECT_TRUE(mgr.isLocked(initializeLease(1, "192.0.2.1")));
+ EXPECT_TRUE(mgr.isLocked(initializeLease(2, "192.0.2.2")));
+
+ // If we unlock the lease, it can be locked again. However, unlocking
+ // the lease should not affect other locks.
+ mgr.unlock(initializeLease(1, "192.0.2.1"));
+ EXPECT_FALSE(mgr.isLocked(initializeLease(1, "192.0.2.1")));
+ EXPECT_TRUE(mgr.isLocked(initializeLease(2, "192.0.2.2")));
+ EXPECT_FALSE(mgr.tryLock(initializeLease(2, "192.0.2.2")));
+ EXPECT_TRUE(mgr.tryLock(initializeLease(1, "192.0.2.1")));
+}
+
+/// Tests registering the callbacks.
+TEST_F(TrackingLeaseMgrTest, registerCallbacks) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ // Callback for lease add and global subnet id.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ // Callback for lease add and subnet id 1.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq", 1,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 1, ph::_1)));
+ // Callback for lease add and subnet id 2.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq", 2,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 1, ph::_1)));
+ // Callback for lease update and global subnet id.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ // Callback for lease delete and global subnet id.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+
+ // This call should trigger the lease add callbacks for subnet id 0 and 1.
+ EXPECT_NO_THROW(mgr.trackAddLease(initializeLease(1, "192.0.2.1")));
+ EXPECT_EQ(2, logs_.size());
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, SUBNET_ID_GLOBAL, Lease::TYPE_V4));
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, 1, Lease::TYPE_V4));
+
+ // This call should trigger the lease add callback for subnet id 0 only. That's
+ // because we have no callback for the subnet id 3.
+ EXPECT_NO_THROW(mgr.trackAddLease(initializeLease(3, "192.0.2.1")));
+ EXPECT_EQ(3, logs_.size());
+ EXPECT_EQ(2, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, SUBNET_ID_GLOBAL, Lease::TYPE_V4));
+}
+
+/// Test that registering the callbacks of the same type, for the same subnet id by the
+/// same owner fails.
+TEST_F(TrackingLeaseMgrTest, registerCallbacksConflicts) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ // Add the callback for lease add and subnet id 0.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_NA,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+
+ // Another attempt should fail.
+ EXPECT_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_NA,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)),
+ InvalidOperation);
+
+ // It should succeed for a different owner.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "qlf",
+ SUBNET_ID_GLOBAL,
+ Lease::TYPE_NA,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+
+ // It should also succeed for a different subnet id.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "qlf", 5,
+ Lease::TYPE_NA,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 5, ph::_1)));
+
+ // But, another attempt for the subnet id should fail.
+ EXPECT_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "qlf", 5,
+ Lease::TYPE_NA,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 5, ph::_1)),
+ InvalidOperation);
+
+ // However, changing a lease type should make it succeed.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "qlf", 5,
+ Lease::TYPE_PD,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 5, ph::_1)));
+}
+
+/// Test invoking the registered lease add callbacks.
+TEST_F(TrackingLeaseMgrTest, trackAddLeaseDifferentLeaseTypes) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_NA,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_PD,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+
+ EXPECT_NO_THROW(mgr.trackAddLease(initializeLease(1, "192.0.2.1")));
+ EXPECT_EQ(1, logs_.size());
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, SUBNET_ID_GLOBAL, Lease::TYPE_V4));
+
+ EXPECT_NO_THROW(mgr.trackAddLease(initializeLease(1, Lease::TYPE_NA, "2001:db8:1::1")));
+ EXPECT_EQ(2, logs_.size());
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, SUBNET_ID_GLOBAL, Lease::TYPE_NA));
+
+ EXPECT_NO_THROW(mgr.trackAddLease(initializeLease(1, Lease::TYPE_PD, "3000::")));
+ EXPECT_EQ(3, logs_.size());
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, SUBNET_ID_GLOBAL, Lease::TYPE_PD));
+}
+
+/// Test invoking the registered lease update callbacks.
+TEST_F(TrackingLeaseMgrTest, trackUpdateLease) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq",
+ SUBNET_ID_GLOBAL,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.trackUpdateLease(initializeLease(1, "192.0.2.1")));
+ EXPECT_EQ(1, logs_.size());
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_UPDATE_LEASE, 0, Lease::TYPE_V4));
+}
+
+/// Test invoking the registered lease delete callbacks.
+TEST_F(TrackingLeaseMgrTest, trackDeleteLease) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.trackDeleteLease(initializeLease(1, "192.0.2.1")));
+ EXPECT_EQ(1, logs_.size());
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_DELETE_LEASE, SUBNET_ID_GLOBAL, Lease::TYPE_V4));
+}
+
+// Test unregistering the callbacks by subnet id.
+TEST_F(TrackingLeaseMgrTest, unregisterCallbacksBySubnetID) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ // Register different callback types for different subnet identifiers.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq", 1,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 1, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq", 2,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ 2, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq", 1,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ 1, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq", 2,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ 2, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq", 1,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ 1, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_DELETE_LEASE, "flq", 2,
+ Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_DELETE_LEASE,
+ 2, ph::_1)));
+
+ // Unregister the callbacks for subnet id 1.
+ EXPECT_NO_THROW(mgr.unregisterCallbacks(SubnetID(1), Lease::TYPE_V4));
+
+ // Invoke the remaining callbacks for the subnet id 1.
+ EXPECT_NO_THROW(mgr.trackAddLease(initializeLease(1, "192.0.2.1")));
+ EXPECT_NO_THROW(mgr.trackUpdateLease(initializeLease(1, "192.0.2.1")));
+ EXPECT_NO_THROW(mgr.trackDeleteLease(initializeLease(1, "192.0.2.1")));
+
+ // It should only run the callback for the global subnet id that is still
+ // registered.
+ EXPECT_EQ(1, logs_.size());
+ EXPECT_EQ(1, countLogs(TrackingLeaseMgr::TRACK_ADD_LEASE, SUBNET_ID_GLOBAL, Lease::TYPE_V4));
+
+ // Unregister this callback.
+ EXPECT_NO_THROW(mgr.unregisterCallbacks(SUBNET_ID_GLOBAL, Lease::TYPE_V4));
+
+ // Make sure it is no longer invoked.
+ EXPECT_NO_THROW(mgr.trackAddLease(initializeLease(1, "192.0.2.1")));
+ EXPECT_EQ(1, logs_.size());
+
+ // Unregistering it again should be no-op.
+ EXPECT_NO_THROW(mgr.unregisterCallbacks(SUBNET_ID_GLOBAL, Lease::TYPE_V4));
+}
+
+/// Test unregistering all callbacks.
+TEST_F(TrackingLeaseMgrTest, unregisterAllCallbacks) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+
+ // Register some callbacks.
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_UPDATE_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_UPDATE_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ // Make sure they have been registered.
+ EXPECT_TRUE(mgr.hasCallbacks());
+
+ // Unregister them and make sure it was successful.
+ EXPECT_NO_THROW(mgr.unregisterAllCallbacks());
+ EXPECT_FALSE(mgr.hasCallbacks());
+}
+
+/// Test the function checking if any callbacks have been registered.
+TEST_F(TrackingLeaseMgrTest, hasCallbacks) {
+ DatabaseConnection::ParameterMap pmap;
+ ConcreteLeaseMgr mgr(pmap);
+ EXPECT_FALSE(mgr.hasCallbacks());
+
+ EXPECT_NO_THROW(mgr.registerCallback(TrackingLeaseMgr::TRACK_ADD_LEASE, "flq",
+ SUBNET_ID_GLOBAL, Lease::TYPE_V4,
+ std::bind(&TrackingLeaseMgrTest::logCallback,
+ this,
+ TrackingLeaseMgr::TRACK_ADD_LEASE,
+ SUBNET_ID_GLOBAL, ph::_1)));
+ EXPECT_TRUE(mgr.hasCallbacks());
+}
+
+/// Test conversion of the callback type to string.
+TEST_F(TrackingLeaseMgrTest, callbackTypeToString) {
+ EXPECT_EQ("add_lease", ConcreteLeaseMgr::callbackTypeToString(TrackingLeaseMgr::TRACK_ADD_LEASE));
+ EXPECT_EQ("update_lease", ConcreteLeaseMgr::callbackTypeToString(TrackingLeaseMgr::TRACK_UPDATE_LEASE));
+ EXPECT_EQ("delete_lease", ConcreteLeaseMgr::callbackTypeToString(TrackingLeaseMgr::TRACK_DELETE_LEASE));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am
new file mode 100644
index 0000000..e7060b5
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/Makefile.am
@@ -0,0 +1,64 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libdhcpsrvtest.la
+
+libdhcpsrvtest_la_SOURCES = concrete_lease_mgr.cc concrete_lease_mgr.h
+libdhcpsrvtest_la_SOURCES += config_result_check.cc config_result_check.h
+libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h
+libdhcpsrvtest_la_SOURCES += host_data_source_utils.cc host_data_source_utils.h
+libdhcpsrvtest_la_SOURCES += memory_host_data_source.cc memory_host_data_source.h
+libdhcpsrvtest_la_SOURCES += test_utils.cc test_utils.h
+libdhcpsrvtest_la_SOURCES += generic_backend_unittest.cc generic_backend_unittest.h
+libdhcpsrvtest_la_SOURCES += generic_host_data_source_unittest.cc generic_host_data_source_unittest.h
+libdhcpsrvtest_la_SOURCES += generic_cb_dhcp4_unittest.h generic_cb_dhcp4_unittest.cc
+libdhcpsrvtest_la_SOURCES += generic_cb_dhcp6_unittest.h generic_cb_dhcp6_unittest.cc
+libdhcpsrvtest_la_SOURCES += generic_cb_recovery_unittest.h generic_cb_recovery_unittest.cc
+libdhcpsrvtest_la_SOURCES += lease_file_io.cc lease_file_io.h
+libdhcpsrvtest_la_SOURCES += test_config_backend.h
+libdhcpsrvtest_la_SOURCES += test_config_backend_dhcp4.cc test_config_backend_dhcp4.h
+libdhcpsrvtest_la_SOURCES += test_config_backend_dhcp6.cc test_config_backend_dhcp6.h
+
+if HAVE_MYSQL
+libdhcpsrvtest_la_SOURCES += mysql_generic_backend_unittest.cc mysql_generic_backend_unittest.h
+endif
+if HAVE_PGSQL
+libdhcpsrvtest_la_SOURCES += pgsql_generic_backend_unittest.cc pgsql_generic_backend_unittest.h
+endif
+
+libdhcpsrvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libdhcpsrvtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libdhcpsrvtest_la_LIBADD =
+
+if HAVE_PGSQL
+libdhcpsrvtest_la_CPPFLAGS += $(PGSQL_CPPFLAGS)
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+endif
+if HAVE_MYSQL
+libdhcpsrvtest_la_CPPFLAGS += $(MYSQL_CPPFLAGS)
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+endif
+
+libdhcpsrvtest_la_LDFLAGS = $(AM_LDFLAGS)
+if HAVE_MYSQL
+libdhcpsrvtest_la_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+libdhcpsrvtest_la_LDFLAGS += $(PGSQL_LIBS)
+endif
+
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcpsrvtest_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+
+endif
diff --git a/src/lib/dhcpsrv/testutils/Makefile.in b/src/lib/dhcpsrv/testutils/Makefile.in
new file mode 100644
index 0000000..77e7d98
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/Makefile.in
@@ -0,0 +1,1103 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_1 = mysql_generic_backend_unittest.cc mysql_generic_backend_unittest.h
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_2 = pgsql_generic_backend_unittest.cc pgsql_generic_backend_unittest.h
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_3 = $(PGSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_4 = $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_5 = $(MYSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_6 = $(top_builddir)/src/lib/mysql/libkea-mysql.la
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_7 = $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_8 = $(PGSQL_LIBS)
+subdir = src/lib/dhcpsrv/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_DEPENDENCIES = $(am__append_4) \
+@HAVE_GTEST_TRUE@ $(am__append_6) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+am__libdhcpsrvtest_la_SOURCES_DIST = concrete_lease_mgr.cc \
+ concrete_lease_mgr.h config_result_check.cc \
+ config_result_check.h dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h \
+ host_data_source_utils.cc host_data_source_utils.h \
+ memory_host_data_source.cc memory_host_data_source.h \
+ test_utils.cc test_utils.h generic_backend_unittest.cc \
+ generic_backend_unittest.h \
+ generic_host_data_source_unittest.cc \
+ generic_host_data_source_unittest.h \
+ generic_cb_dhcp4_unittest.h generic_cb_dhcp4_unittest.cc \
+ generic_cb_dhcp6_unittest.h generic_cb_dhcp6_unittest.cc \
+ generic_cb_recovery_unittest.h generic_cb_recovery_unittest.cc \
+ lease_file_io.cc lease_file_io.h test_config_backend.h \
+ test_config_backend_dhcp4.cc test_config_backend_dhcp4.h \
+ test_config_backend_dhcp6.cc test_config_backend_dhcp6.h \
+ mysql_generic_backend_unittest.cc \
+ mysql_generic_backend_unittest.h \
+ pgsql_generic_backend_unittest.cc \
+ pgsql_generic_backend_unittest.h
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__objects_1 = libdhcpsrvtest_la-mysql_generic_backend_unittest.lo
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__objects_2 = libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo
+@HAVE_GTEST_TRUE@am_libdhcpsrvtest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-concrete_lease_mgr.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-config_result_check.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-dhcp4o6_test_ipc.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-host_data_source_utils.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-memory_host_data_source.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-test_utils.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_backend_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_host_data_source_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-generic_cb_recovery_unittest.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-lease_file_io.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-test_config_backend_dhcp4.lo \
+@HAVE_GTEST_TRUE@ libdhcpsrvtest_la-test_config_backend_dhcp6.lo \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2)
+libdhcpsrvtest_la_OBJECTS = $(am_libdhcpsrvtest_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 =
+libdhcpsrvtest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libdhcpsrvtest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libdhcpsrvtest_la_rpath =
+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)/libdhcpsrvtest_la-concrete_lease_mgr.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo \
+ ./$(DEPDIR)/libdhcpsrvtest_la-test_utils.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 = $(libdhcpsrvtest_la_SOURCES)
+DIST_SOURCES = $(am__libdhcpsrvtest_la_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\" \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libdhcpsrvtest.la
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_SOURCES = concrete_lease_mgr.cc \
+@HAVE_GTEST_TRUE@ concrete_lease_mgr.h config_result_check.cc \
+@HAVE_GTEST_TRUE@ config_result_check.h dhcp4o6_test_ipc.cc \
+@HAVE_GTEST_TRUE@ dhcp4o6_test_ipc.h host_data_source_utils.cc \
+@HAVE_GTEST_TRUE@ host_data_source_utils.h \
+@HAVE_GTEST_TRUE@ memory_host_data_source.cc \
+@HAVE_GTEST_TRUE@ memory_host_data_source.h test_utils.cc \
+@HAVE_GTEST_TRUE@ test_utils.h generic_backend_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_backend_unittest.h \
+@HAVE_GTEST_TRUE@ generic_host_data_source_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_host_data_source_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp4_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp4_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp6_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_dhcp6_unittest.cc \
+@HAVE_GTEST_TRUE@ generic_cb_recovery_unittest.h \
+@HAVE_GTEST_TRUE@ generic_cb_recovery_unittest.cc \
+@HAVE_GTEST_TRUE@ lease_file_io.cc lease_file_io.h \
+@HAVE_GTEST_TRUE@ test_config_backend.h \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp4.cc \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp4.h \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp6.cc \
+@HAVE_GTEST_TRUE@ test_config_backend_dhcp6.h $(am__append_1) \
+@HAVE_GTEST_TRUE@ $(am__append_2)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_CPPFLAGS = $(AM_CPPFLAGS) \
+@HAVE_GTEST_TRUE@ $(GTEST_INCLUDES) $(am__append_3) \
+@HAVE_GTEST_TRUE@ $(am__append_5)
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_LIBADD = $(am__append_4) \
+@HAVE_GTEST_TRUE@ $(am__append_6) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+@HAVE_GTEST_TRUE@libdhcpsrvtest_la_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(am__append_7) $(am__append_8)
+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/dhcpsrv/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/dhcpsrv/testutils/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libdhcpsrvtest.la: $(libdhcpsrvtest_la_OBJECTS) $(libdhcpsrvtest_la_DEPENDENCIES) $(EXTRA_libdhcpsrvtest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libdhcpsrvtest_la_LINK) $(am_libdhcpsrvtest_la_rpath) $(libdhcpsrvtest_la_OBJECTS) $(libdhcpsrvtest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-concrete_lease_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libdhcpsrvtest_la-test_utils.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)$(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 $@ $<
+
+libdhcpsrvtest_la-concrete_lease_mgr.lo: concrete_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-concrete_lease_mgr.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-concrete_lease_mgr.Tpo -c -o libdhcpsrvtest_la-concrete_lease_mgr.lo `test -f 'concrete_lease_mgr.cc' || echo '$(srcdir)/'`concrete_lease_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-concrete_lease_mgr.Tpo $(DEPDIR)/libdhcpsrvtest_la-concrete_lease_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='concrete_lease_mgr.cc' object='libdhcpsrvtest_la-concrete_lease_mgr.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-concrete_lease_mgr.lo `test -f 'concrete_lease_mgr.cc' || echo '$(srcdir)/'`concrete_lease_mgr.cc
+
+libdhcpsrvtest_la-config_result_check.lo: config_result_check.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-config_result_check.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-config_result_check.Tpo -c -o libdhcpsrvtest_la-config_result_check.lo `test -f 'config_result_check.cc' || echo '$(srcdir)/'`config_result_check.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-config_result_check.Tpo $(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_result_check.cc' object='libdhcpsrvtest_la-config_result_check.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-config_result_check.lo `test -f 'config_result_check.cc' || echo '$(srcdir)/'`config_result_check.cc
+
+libdhcpsrvtest_la-dhcp4o6_test_ipc.lo: dhcp4o6_test_ipc.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-dhcp4o6_test_ipc.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Tpo -c -o libdhcpsrvtest_la-dhcp4o6_test_ipc.lo `test -f 'dhcp4o6_test_ipc.cc' || echo '$(srcdir)/'`dhcp4o6_test_ipc.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Tpo $(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp4o6_test_ipc.cc' object='libdhcpsrvtest_la-dhcp4o6_test_ipc.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-dhcp4o6_test_ipc.lo `test -f 'dhcp4o6_test_ipc.cc' || echo '$(srcdir)/'`dhcp4o6_test_ipc.cc
+
+libdhcpsrvtest_la-host_data_source_utils.lo: host_data_source_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-host_data_source_utils.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Tpo -c -o libdhcpsrvtest_la-host_data_source_utils.lo `test -f 'host_data_source_utils.cc' || echo '$(srcdir)/'`host_data_source_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Tpo $(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='host_data_source_utils.cc' object='libdhcpsrvtest_la-host_data_source_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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-host_data_source_utils.lo `test -f 'host_data_source_utils.cc' || echo '$(srcdir)/'`host_data_source_utils.cc
+
+libdhcpsrvtest_la-memory_host_data_source.lo: memory_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-memory_host_data_source.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Tpo -c -o libdhcpsrvtest_la-memory_host_data_source.lo `test -f 'memory_host_data_source.cc' || echo '$(srcdir)/'`memory_host_data_source.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Tpo $(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_host_data_source.cc' object='libdhcpsrvtest_la-memory_host_data_source.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-memory_host_data_source.lo `test -f 'memory_host_data_source.cc' || echo '$(srcdir)/'`memory_host_data_source.cc
+
+libdhcpsrvtest_la-test_utils.lo: test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-test_utils.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-test_utils.Tpo -c -o libdhcpsrvtest_la-test_utils.lo `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-test_utils.Tpo $(DEPDIR)/libdhcpsrvtest_la-test_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_utils.cc' object='libdhcpsrvtest_la-test_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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-test_utils.lo `test -f 'test_utils.cc' || echo '$(srcdir)/'`test_utils.cc
+
+libdhcpsrvtest_la-generic_backend_unittest.lo: generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_backend_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Tpo -c -o libdhcpsrvtest_la-generic_backend_unittest.lo `test -f 'generic_backend_unittest.cc' || echo '$(srcdir)/'`generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_backend_unittest.cc' object='libdhcpsrvtest_la-generic_backend_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_backend_unittest.lo `test -f 'generic_backend_unittest.cc' || echo '$(srcdir)/'`generic_backend_unittest.cc
+
+libdhcpsrvtest_la-generic_host_data_source_unittest.lo: generic_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_host_data_source_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Tpo -c -o libdhcpsrvtest_la-generic_host_data_source_unittest.lo `test -f 'generic_host_data_source_unittest.cc' || echo '$(srcdir)/'`generic_host_data_source_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_host_data_source_unittest.cc' object='libdhcpsrvtest_la-generic_host_data_source_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_host_data_source_unittest.lo `test -f 'generic_host_data_source_unittest.cc' || echo '$(srcdir)/'`generic_host_data_source_unittest.cc
+
+libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo: generic_cb_dhcp4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Tpo -c -o libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo `test -f 'generic_cb_dhcp4_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp4_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_cb_dhcp4_unittest.cc' object='libdhcpsrvtest_la-generic_cb_dhcp4_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_cb_dhcp4_unittest.lo `test -f 'generic_cb_dhcp4_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp4_unittest.cc
+
+libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo: generic_cb_dhcp6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Tpo -c -o libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo `test -f 'generic_cb_dhcp6_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp6_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_cb_dhcp6_unittest.cc' object='libdhcpsrvtest_la-generic_cb_dhcp6_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_cb_dhcp6_unittest.lo `test -f 'generic_cb_dhcp6_unittest.cc' || echo '$(srcdir)/'`generic_cb_dhcp6_unittest.cc
+
+libdhcpsrvtest_la-generic_cb_recovery_unittest.lo: generic_cb_recovery_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-generic_cb_recovery_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Tpo -c -o libdhcpsrvtest_la-generic_cb_recovery_unittest.lo `test -f 'generic_cb_recovery_unittest.cc' || echo '$(srcdir)/'`generic_cb_recovery_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='generic_cb_recovery_unittest.cc' object='libdhcpsrvtest_la-generic_cb_recovery_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-generic_cb_recovery_unittest.lo `test -f 'generic_cb_recovery_unittest.cc' || echo '$(srcdir)/'`generic_cb_recovery_unittest.cc
+
+libdhcpsrvtest_la-lease_file_io.lo: lease_file_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-lease_file_io.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Tpo -c -o libdhcpsrvtest_la-lease_file_io.lo `test -f 'lease_file_io.cc' || echo '$(srcdir)/'`lease_file_io.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Tpo $(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lease_file_io.cc' object='libdhcpsrvtest_la-lease_file_io.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-lease_file_io.lo `test -f 'lease_file_io.cc' || echo '$(srcdir)/'`lease_file_io.cc
+
+libdhcpsrvtest_la-test_config_backend_dhcp4.lo: test_config_backend_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-test_config_backend_dhcp4.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Tpo -c -o libdhcpsrvtest_la-test_config_backend_dhcp4.lo `test -f 'test_config_backend_dhcp4.cc' || echo '$(srcdir)/'`test_config_backend_dhcp4.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Tpo $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_config_backend_dhcp4.cc' object='libdhcpsrvtest_la-test_config_backend_dhcp4.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-test_config_backend_dhcp4.lo `test -f 'test_config_backend_dhcp4.cc' || echo '$(srcdir)/'`test_config_backend_dhcp4.cc
+
+libdhcpsrvtest_la-test_config_backend_dhcp6.lo: test_config_backend_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-test_config_backend_dhcp6.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Tpo -c -o libdhcpsrvtest_la-test_config_backend_dhcp6.lo `test -f 'test_config_backend_dhcp6.cc' || echo '$(srcdir)/'`test_config_backend_dhcp6.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Tpo $(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_config_backend_dhcp6.cc' object='libdhcpsrvtest_la-test_config_backend_dhcp6.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-test_config_backend_dhcp6.lo `test -f 'test_config_backend_dhcp6.cc' || echo '$(srcdir)/'`test_config_backend_dhcp6.cc
+
+libdhcpsrvtest_la-mysql_generic_backend_unittest.lo: mysql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-mysql_generic_backend_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Tpo -c -o libdhcpsrvtest_la-mysql_generic_backend_unittest.lo `test -f 'mysql_generic_backend_unittest.cc' || echo '$(srcdir)/'`mysql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_generic_backend_unittest.cc' object='libdhcpsrvtest_la-mysql_generic_backend_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-mysql_generic_backend_unittest.lo `test -f 'mysql_generic_backend_unittest.cc' || echo '$(srcdir)/'`mysql_generic_backend_unittest.cc
+
+libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo: pgsql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -MT libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo -MD -MP -MF $(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Tpo -c -o libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo `test -f 'pgsql_generic_backend_unittest.cc' || echo '$(srcdir)/'`pgsql_generic_backend_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Tpo $(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_generic_backend_unittest.cc' object='libdhcpsrvtest_la-pgsql_generic_backend_unittest.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) $(libdhcpsrvtest_la_CPPFLAGS) $(CPPFLAGS) $(libdhcpsrvtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libdhcpsrvtest_la-pgsql_generic_backend_unittest.lo `test -f 'pgsql_generic_backend_unittest.cc' || echo '$(srcdir)/'`pgsql_generic_backend_unittest.cc
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-concrete_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_utils.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-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)/libdhcpsrvtest_la-concrete_lease_mgr.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-config_result_check.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-dhcp4o6_test_ipc.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp4_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_dhcp6_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_cb_recovery_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-generic_host_data_source_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-host_data_source_utils.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-lease_file_io.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-memory_host_data_source.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-mysql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-pgsql_generic_backend_unittest.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp4.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_config_backend_dhcp6.Plo
+ -rm -f ./$(DEPDIR)/libdhcpsrvtest_la-test_utils.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/dhcpsrv/testutils/concrete_lease_mgr.cc b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.cc
new file mode 100644
index 0000000..b24b404
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.cc
@@ -0,0 +1,345 @@
+// Copyright (C) 2023 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 <asiolink/io_address.h>
+#include <concrete_lease_mgr.h>
+
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+ConcreteLeaseMgr::ConcreteLeaseMgr(const DatabaseConnection::ParameterMap&)
+ : TrackingLeaseMgr() {
+}
+
+ConcreteLeaseMgr::~ConcreteLeaseMgr() {
+}
+
+bool
+ConcreteLeaseMgr::addLease(const Lease4Ptr&) {
+ return (false);
+}
+
+bool
+ConcreteLeaseMgr::addLease(const Lease6Ptr&) {
+ return (false);
+}
+
+Lease4Ptr
+ConcreteLeaseMgr::getLease4(const IOAddress&) const {
+ return (Lease4Ptr());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLease4(const HWAddr&) const {
+ return (Lease4Collection());
+}
+
+Lease4Ptr
+ConcreteLeaseMgr::getLease4(const HWAddr&, SubnetID) const {
+ return (Lease4Ptr());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLease4(const ClientId&) const {
+ return (Lease4Collection());
+}
+
+Lease4Ptr
+ConcreteLeaseMgr::getLease4(const ClientId&, SubnetID) const {
+ return (Lease4Ptr());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4(SubnetID) const {
+ return (Lease4Collection());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4(const std::string&) const {
+ return (Lease4Collection());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4() const {
+ return (Lease4Collection());
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4(const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) const {
+ return (Lease4Collection());
+}
+
+Lease6Ptr
+ConcreteLeaseMgr::getLease6(Lease::Type /* not used yet */,
+ const IOAddress&) const {
+ return (Lease6Ptr());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(Lease::Type /* not used yet */,
+ const DUID&, uint32_t) const {
+ return (leases6_);
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(Lease::Type /* not used yet */,
+ const DUID&, uint32_t, SubnetID) const {
+ return (leases6_);
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(const DUID&) const {
+ return (leases6_);
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(SubnetID) const {
+ return (Lease6Collection());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(const std::string&) const {
+ return (Lease6Collection());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6() const {
+ return (Lease6Collection());
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6(const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) const {
+ return (Lease6Collection());
+};
+
+void
+ConcreteLeaseMgr::getExpiredLeases6(Lease6Collection&, const size_t) const {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases6 is not"
+ " implemented");
+}
+
+void
+ConcreteLeaseMgr::getExpiredLeases4(Lease4Collection&, const size_t) const {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getExpiredLeases4 is not"
+ " implemented");
+}
+
+void
+ConcreteLeaseMgr::updateLease4(const Lease4Ptr&) {}
+
+void
+ConcreteLeaseMgr::updateLease6(const Lease6Ptr&) {}
+
+bool
+ConcreteLeaseMgr::deleteLease(const Lease4Ptr&) {
+ return (false);
+}
+
+bool
+ConcreteLeaseMgr::deleteLease(const Lease6Ptr&) {
+ return (false);
+}
+
+uint64_t
+ConcreteLeaseMgr::deleteExpiredReclaimedLeases4(const uint32_t) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpir§edReclaimedLeases4"
+ " is not implemented");
+}
+
+uint64_t
+ConcreteLeaseMgr::deleteExpiredReclaimedLeases6(const uint32_t) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::deleteExpiredReclaimedLeases6"
+ " is not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::wipeLeases4(const SubnetID&) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases4 not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::wipeLeases6(const SubnetID&) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::wipeLeases6 not implemented");
+}
+
+std::string
+ConcreteLeaseMgr::checkLimits4(isc::data::ConstElementPtr const& /* user_context */) const {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits4() not implemented");
+}
+
+std::string
+ConcreteLeaseMgr::checkLimits6(isc::data::ConstElementPtr const& /* user_context */) const {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::checkLimits6() not implemented");
+}
+
+bool
+ConcreteLeaseMgr::isJsonSupported() const {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::isJsonSupported() not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::getClassLeaseCount(const ClientClass& /* client_class */,
+ const Lease::Type& /* ltype = Lease::TYPE_V4 */) const {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getClassLeaseCount() not implemented");
+}
+
+void
+ConcreteLeaseMgr::recountClassLeases4() {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::recountClassLeases4() not implemented");
+}
+
+void
+ConcreteLeaseMgr::recountClassLeases6() {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::recountClassLeases6() not implemented");
+}
+
+void
+ConcreteLeaseMgr::clearClassLeaseCounts() {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::clearClassLeaseCounts() not implemented");
+}
+
+void
+ConcreteLeaseMgr::deleteExtendedInfo6(const IOAddress& addr) {
+ auto relay_id_it = relay_id6_.begin();
+ while (relay_id_it != relay_id6_.end()) {
+ if ((*relay_id_it)->lease_addr_ == addr) {
+ relay_id_it = relay_id6_.erase(relay_id_it);
+ } else {
+ ++relay_id_it;
+ }
+ }
+ auto remote_id_it = remote_id6_.begin();
+ while (remote_id_it != remote_id6_.end()) {
+ if ((*remote_id_it)->lease_addr_ == addr) {
+ remote_id_it = remote_id6_.erase(remote_id_it);
+ } else {
+ ++remote_id_it;
+ }
+ }
+}
+
+void
+ConcreteLeaseMgr::addRelayId6(const IOAddress& lease_addr,
+ const vector<uint8_t>& relay_id) {
+ Lease6ExtendedInfoPtr ex_info;
+ ex_info.reset(new Lease6ExtendedInfo(lease_addr, relay_id));
+ relay_id6_.push_back(ex_info);
+}
+
+void
+ConcreteLeaseMgr::addRemoteId6(const IOAddress& lease_addr,
+ const vector<uint8_t>& remote_id) {
+ Lease6ExtendedInfoPtr ex_info;
+ ex_info.reset(new Lease6ExtendedInfo(lease_addr, remote_id));
+ remote_id6_.push_back(ex_info);
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4ByRelayId(const OptionBuffer& /* relay_id */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */,
+ const time_t& /* qry_start_time = 0 */,
+ const time_t& /* qry_end_time = 0 */) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases4ByRelayId not implemented");
+}
+
+Lease4Collection
+ConcreteLeaseMgr::getLeases4ByRemoteId(const OptionBuffer& /* remote_id */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */,
+ const time_t& /* qry_start_time = 0 */,
+ const time_t& /* qry_end_time = 0 */) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases4ByRemoteId not implemented");
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6ByRelayId(const DUID& /* relay_id */,
+ const IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByRelayId not implemented");
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6ByRemoteId(const OptionBuffer& /* remote_id */,
+ const IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size*/) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByRemoteId not implemented");
+}
+
+Lease6Collection
+ConcreteLeaseMgr::getLeases6ByLink(const IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::getLeases6ByLink not implemented");
+}
+
+size_t
+ConcreteLeaseMgr::upgradeExtendedInfo4(const LeasePageSize& /* page_size */) {
+ return (0);
+}
+
+size_t
+ConcreteLeaseMgr::buildExtendedInfoTables6(bool /* update */,
+ bool /* current */) {
+ isc_throw(isc::NotImplemented, "ConcreteLeaseMgr:buildExtendedInfoTables6 not implemented");
+}
+
+void
+ConcreteLeaseMgr::writeLeases4(const std::string&) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::writeLeases4() not implemented");
+}
+
+void
+ConcreteLeaseMgr::writeLeases6(const std::string&) {
+ isc_throw(NotImplemented, "ConcreteLeaseMgr::writeLeases6() not implemented");
+}
+
+std::string
+ConcreteLeaseMgr::getType() const {
+ return (std::string("concrete"));
+}
+
+std::string
+ConcreteLeaseMgr::getName() const {
+ return (std::string("concrete"));
+}
+
+std::string
+ConcreteLeaseMgr::getDescription() const {
+ return (std::string("This is a dummy concrete backend implementation."));
+}
+
+std::pair<uint32_t, uint32_t>
+ConcreteLeaseMgr::getVersion() const {
+ return (make_pair(uint32_t(0), uint32_t(0)));
+}
+
+void
+ConcreteLeaseMgr::commit() {
+}
+
+void
+ConcreteLeaseMgr::rollback() {
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/concrete_lease_mgr.h b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.h
new file mode 100644
index 0000000..53956dc
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/concrete_lease_mgr.h
@@ -0,0 +1,447 @@
+// Copyright (C) 2023 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 TEST_CONCRETE_LEASE_MGR_H
+#define TEST_CONCRETE_LEASE_MGR_H
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <dhcpsrv/memfile_lease_storage.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <list>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// This is a concrete implementation of a Lease database. It does not do
+// anything useful and is used for abstract LeaseMgr class testing.
+class ConcreteLeaseMgr : public TrackingLeaseMgr {
+public:
+
+ /// @brief The sole lease manager constructor
+ ///
+ /// dbconfig is a generic way of passing parameters. Parameters
+ /// are passed in the "name=value" format, separated by spaces.
+ /// Values may be enclosed in double quotes, if needed.
+ ConcreteLeaseMgr(const db::DatabaseConnection::ParameterMap&);
+
+ /// @brief Destructor
+ virtual ~ConcreteLeaseMgr();
+
+ /// @brief Adds an IPv4 lease.
+ ///
+ /// @param lease lease to be added
+ virtual bool addLease(const Lease4Ptr&) override;
+
+ /// @brief Adds an IPv6 lease.
+ ///
+ /// @param lease lease to be added
+ virtual bool addLease(const Lease6Ptr&) override;
+
+ /// @brief Returns existing IPv4 lease for specified IPv4 address.
+ ///
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress&) const override;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address.
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param hwaddr hardware address of the client
+ ///
+ /// @return lease collection
+ virtual Lease4Collection getLease4(const HWAddr&) const override;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address
+ /// and a subnet
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const HWAddr&, SubnetID) const override;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// @param clientid client identifier
+ ///
+ /// @return lease collection
+ virtual Lease4Collection getLease4(const ClientId&) const override;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param clientid client identifier
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const ClientId&, SubnetID) const override;
+
+ /// @brief Returns all IPv4 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(SubnetID) const override;
+
+ /// @brief Returns all IPv4 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4(const std::string&) const override;
+
+ /// @brief Returns all IPv4 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection getLeases4() const override;
+
+ /// @brief Returns range of IPv4 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv4 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv4 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv4 lease found).
+ virtual Lease4Collection
+ getLeases4(const asiolink::IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) const override;
+
+ /// @brief Returns existing IPv6 lease for a given IPv6 address.
+ ///
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ virtual Lease6Ptr getLease6(Lease::Type /* not used yet */,
+ const isc::asiolink::IOAddress&) const override;
+
+ /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+ ///
+ /// @param duid ignored
+ /// @param iaid ignored
+ ///
+ /// @return whatever is set in leases6_ field
+ virtual Lease6Collection getLeases6(Lease::Type /* not used yet */,
+ const DUID&, uint32_t) const override;
+
+ /// @brief Returns existing IPv6 lease for a given DUID+IA+subnet-id combination
+ ///
+ /// @param duid ignored
+ /// @param iaid ignored
+ /// @param subnet_id ignored
+ ///
+ /// @return whatever is set in leases6_ field
+ virtual Lease6Collection getLeases6(Lease::Type /* not used yet */,
+ const DUID&, uint32_t, SubnetID) const override;
+
+ /// @brief Returns collection of lease for matching DUID
+ ///
+ /// @param duid ignored
+ /// @return whatever is set in leases6_ field
+ virtual Lease6Collection getLeases6(const DUID&) const override;
+
+ /// @brief Returns all IPv6 leases for the particular subnet identifier.
+ ///
+ /// @param subnet_id subnet identifier.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(SubnetID) const override;
+
+ /// @brief Returns all IPv6 leases for the particular hostname.
+ ///
+ /// @param hostname hostname in lower case.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6(const std::string&) const override;
+
+ /// @brief Returns all IPv6 leases.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection getLeases6() const override;
+
+ /// @brief Returns range of IPv6 leases using paging.
+ ///
+ /// This method implements paged browsing of the lease database. The first
+ /// parameter specifies a page size. The second parameter is optional and
+ /// specifies the starting address of the range. This address is excluded
+ /// from the returned range. The IPv6 zero address (default) denotes that
+ /// the first page should be returned. There is no guarantee about the
+ /// order of returned leases.
+ ///
+ /// The typical usage of this method is as follows:
+ /// - Get the first page of leases by specifying IPv6 zero address as the
+ /// beginning of the range.
+ /// - Last address of the returned range should be used as a starting
+ /// address for the next page in the subsequent call.
+ /// - If the number of leases returned is lower than the page size, it
+ /// indicates that the last page has been retrieved.
+ /// - If there are no leases returned it indicates that the previous page
+ /// was the last page.
+ ///
+ /// @param lower_bound_address IPv4 address used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Lease collection (may be empty if no IPv6 lease found).
+ virtual Lease6Collection
+ getLeases6(const asiolink::IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) const override;
+
+ /// @brief Returns expired DHCPv6 leases.
+ ///
+ /// This method is not implemented.
+ virtual void getExpiredLeases6(Lease6Collection&, const size_t) const override;
+
+ /// @brief Returns expired DHCPv4 leases.
+ ///
+ /// This method is not implemented.
+ virtual void getExpiredLeases4(Lease4Collection&, const size_t) const override;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// If no such lease is present, an exception will be thrown.
+ virtual void updateLease4(const Lease4Ptr&) override;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// If no such lease is present, an exception will be thrown.
+ virtual void updateLease6(const Lease6Ptr&) override;
+
+ /// @brief Deletes an IPv4 lease.
+ ///
+ /// @param lease IPv4 lease to be deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ virtual bool deleteLease(const Lease4Ptr&) override;
+
+ /// @brief Deletes an IPv6 lease.
+ ///
+ /// @param lease IPv6 lease to be deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists.
+ virtual bool deleteLease(const Lease6Ptr&) override;
+
+ /// @brief Deletes all expired and reclaimed DHCPv4 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases4(const uint32_t) override;
+
+ /// @brief Deletes all expired and reclaimed DHCPv6 leases.
+ ///
+ /// @param secs Number of seconds since expiration of leases before
+ /// they can be removed. Leases which have expired later than this
+ /// time will not be deleted.
+ virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t) override;
+
+ /// @brief Pretends to wipe all IPv4 leases from a subnet
+ /// @param subnet_id (ignored, but one day may specify the subnet)
+ virtual size_t wipeLeases4(const SubnetID&) override;
+
+ /// @brief Pretends to wipe all IPv4 leases from a subnet
+ /// @param subnet_id (ignored, but one day may specify the subnet)
+ virtual size_t wipeLeases6(const SubnetID&) override;
+
+ /// @brief Pretends to check if the IPv4 lease limits set in the given user
+ /// context are exceeded.
+ virtual std::string
+ checkLimits4(isc::data::ConstElementPtr const& /* user_context */) const override;
+
+ /// @brief Pretends to check if the IPv6 lease limits set in the given user
+ /// context are exceeded.
+ virtual std::string
+ checkLimits6(isc::data::ConstElementPtr const& /* user_context */) const override;
+
+ /// @brief Pretends to check if JSON support is enabled in the database.
+ ///
+ /// @return true if there is JSON support, false otherwise
+ virtual bool isJsonSupported() const override;
+
+ /// @brief Pretends to return the class lease count for a given class and lease type.
+ ///
+ /// @param client_class client class for which the count is desired
+ /// @param ltype type of lease for which the count is desired. Defaults to
+ /// Lease::TYPE_V4.
+ ///
+ /// @return number of leases
+ virtual size_t getClassLeaseCount(const ClientClass& /* client_class */,
+ const Lease::Type& /* ltype = Lease::TYPE_V4 */) const override;
+
+ /// @brief Pretends to recount the leases per class for V4 leases.
+ virtual void recountClassLeases4() override;
+
+ /// @brief Pretends to recount the leases per class for V6 leases.
+ virtual void recountClassLeases6() override;
+
+ /// @brief Pretends to clear the class-lease count map.
+ virtual void clearClassLeaseCounts() override;
+
+ /// @brief Import addExtendedInfo6.
+ using LeaseMgr::addExtendedInfo6;
+
+ /// @brief Delete lease6 extended info from tables.
+ ///
+ /// @param addr The address of the lease.
+ void
+ deleteExtendedInfo6(const asiolink::IOAddress& addr) override;
+
+ /// @brief Add lease6 extended info into by-relay-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ /// @param relay_id The relay id from the relay header options.
+ void
+ addRelayId6(const asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& relay_id) override;
+
+ /// @brief Add lease6 extended info into by-remote-id table.
+ ///
+ /// @param lease_addr The address of the lease.
+ void
+ addRemoteId6(const asiolink::IOAddress& lease_addr,
+ const std::vector<uint8_t>& remote_id) override;
+
+ /// @brief Stub implementation.
+ Lease4Collection
+ getLeases4ByRelayId(const OptionBuffer& /* relay_id */,
+ const asiolink::IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */,
+ const time_t& /* qry_start_time = 0 */,
+ const time_t& /* qry_end_time = 0 */) override;
+
+ /// @brief Stub implementation.
+ Lease4Collection
+ getLeases4ByRemoteId(const OptionBuffer& /* remote_id */,
+ const asiolink::IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */,
+ const time_t& /* qry_start_time = 0 */,
+ const time_t& /* qry_end_time = 0 */) override;
+
+ /// @brief Stub implementation.
+ Lease6Collection
+ getLeases6ByRelayId(const DUID& /* relay_id */,
+ const asiolink::IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const asiolink::IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) override;
+
+ /// @brief Stub implementation.
+ Lease6Collection
+ getLeases6ByRemoteId(const OptionBuffer& /* remote_id */,
+ const asiolink::IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const asiolink::IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size*/) override;
+
+ /// @brief Stub implementation.
+ Lease6Collection
+ getLeases6ByLink(const asiolink::IOAddress& /* link_addr */,
+ uint8_t /* link_len */,
+ const asiolink::IOAddress& /* lower_bound_address */,
+ const LeasePageSize& /* page_size */) override;
+
+ /// @brief Stub implementation.
+ virtual size_t
+ upgradeExtendedInfo4(const LeasePageSize& /* page_size */) override;
+
+ /// @brief Stub implementation.
+ virtual size_t buildExtendedInfoTables6(bool /* update */,
+ bool /* current */) override;
+
+ /// @brief Pretends to write V4 leases to a file.
+ virtual void writeLeases4(const std::string&) override;
+
+ /// @brief Pretends to write V6 leases to a file.
+ virtual void writeLeases6(const std::string&) override;
+
+ /// @brief Returns backend type.
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const override;
+
+ /// @brief Returns backend name.
+ ///
+ /// If the backend is a database, this is the name of the database or the
+ /// file. Otherwise it is just the same as the type.
+ ///
+ /// @return Name of the backend.
+ virtual std::string getName() const override;
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ ///
+ /// @return Description of the backend.
+ virtual std::string getDescription() const override;
+
+ /// @brief Returns backend version.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const override;
+
+ /// @brief Commit transactions
+ virtual void commit() override;
+
+ /// @brief Rollback transactions
+ virtual void rollback() override;
+
+ // We need to use them in ConcreteLeaseMgr
+ using LeaseMgr::getLease6;
+ using TrackingLeaseMgr::tryLock;
+ using TrackingLeaseMgr::unlock;
+ using TrackingLeaseMgr::trackAddLease;
+ using TrackingLeaseMgr::trackUpdateLease;
+ using TrackingLeaseMgr::trackDeleteLease;
+ using TrackingLeaseMgr::hasCallbacks;
+ using TrackingLeaseMgr::callbackTypeToString;
+
+ Lease6Collection leases6_; ///< getLease6 methods return this as is
+
+ // List supports easier erase.
+ std::list<Lease6ExtendedInfoPtr> relay_id6_;
+ std::list<Lease6ExtendedInfoPtr> remote_id6_;
+};
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // TEST_CONCRETE_LEASE_MGR_H
diff --git a/src/lib/dhcpsrv/testutils/config_result_check.cc b/src/lib/dhcpsrv/testutils/config_result_check.cc
new file mode 100644
index 0000000..35b31fe
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/config_result_check.cc
@@ -0,0 +1,89 @@
+// Copyright (C) 2014-2017 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 <cc/command_interpreter.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace isc;
+using namespace isc::data;
+
+bool errorContainsPosition(ConstElementPtr error_element,
+ const std::string& file_name) {
+ if (error_element->contains(isc::config::CONTROL_RESULT)) {
+ ConstElementPtr result = error_element->get(isc::config::CONTROL_RESULT);
+ ConstElementPtr text = error_element->get(isc::config::CONTROL_TEXT);
+ if (!result || (result->getType() != Element::integer) || !text
+ || (text->getType() != Element::string)) {
+ return (false);
+ }
+
+ // Get the error message in the textual format.
+ std::string error_string = text->stringValue();
+
+ // The position of the data element causing an error has the following
+ // format: <filename>:<linenum>:<pos>. The <filename> has been specified
+ // by a caller, so let's first check if this file name is present in the
+ // error message.
+ size_t pos = error_string.find(file_name);
+
+ // If the file name is present, check that it is followed by the line
+ // number and position within the line.
+ if (pos != std::string::npos) {
+ // Split the string starting at the beginning of the <filename>. It
+ // should return a vector of strings.
+ std::string sub = error_string.substr(pos);
+ std::vector<std::string> split_pos;
+ boost::split(split_pos, sub, boost::is_any_of(":"),
+ boost::algorithm::token_compress_off);
+
+ // There should be at least three elements: <filename>, <linenum>
+ // and <pos>. There can be even more, because one error string may
+ // contain more positions of data elements for multiple
+ // configuration nesting levels. We want at least one position.
+ if ((split_pos.size() >= 3) && (split_pos[0] == file_name) &&
+ (!split_pos[1].empty()) && !(split_pos[2].empty())) {
+
+ // Make sure that the line number comprises only digits.
+ for (int i = 0; i < split_pos[1].size(); ++i) {
+ if (!isdigit(split_pos[1][i])) {
+ return (false);
+ }
+ }
+
+ // Go over digits of the position within the line.
+ int i = 0;
+ while (isdigit(split_pos[2][i])) {
+ ++i;
+ }
+
+ // Make sure that there has been at least one digit and that the
+ // position is followed by the paren.
+ if ((i == 0) || (split_pos[2][i] != ')')) {
+ return (false);
+ }
+
+ // All checks passed.
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+}
+
+}
+}
+}
diff --git a/src/lib/dhcpsrv/testutils/config_result_check.h b/src/lib/dhcpsrv/testutils/config_result_check.h
new file mode 100644
index 0000000..29d4477
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/config_result_check.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2014-2017 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 CONFIG_RESULT_CHECK_H
+#define CONFIG_RESULT_CHECK_H
+
+#include <cc/data.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Checks that the error string created by the configuration parsers
+/// contains the location of the data element...
+///
+/// This function checks that the error string returned by the configuration
+/// parsers contains the position of the element which caused an error. The
+/// error string is expected to contain at least one occurrence of the following:
+///
+/// @code
+/// [filename]:[linenum]:[pos]
+/// @endcode
+///
+/// where:
+/// - [filename] is a configuration file name (provided by a caller),
+/// - [linenum] is a line number of the element,
+/// - [pos] is a position of the element within the line.
+///
+/// Both [linenum] and [pos] must contain decimal digits. The [filename]
+/// must match the name provided by the caller.
+///
+/// @param error_element A result returned by the configuration.
+/// @param file_name A configuration file name.
+///
+/// @return true if the provided configuration result comprises a string
+/// which holds a position of the data element which caused the error;
+/// false otherwise.
+bool errorContainsPosition(isc::data::ConstElementPtr error_element,
+ const std::string& file_name);
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc
new file mode 100644
index 0000000..a50a5c2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2015-2020 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 <dhcp/iface_mgr.h>
+#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
+#include <functional>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+Dhcp4o6TestIpc::Dhcp4o6TestIpc(uint16_t port, EndpointType endpoint_type)
+ : desired_port_(port), endpoint_type_(endpoint_type), pkt_received_() {
+}
+
+void
+Dhcp4o6TestIpc::open() {
+ // Use the base IPC to open the socket.
+ socket_fd_ = Dhcp4o6IpcBase::open(desired_port_, endpoint_type_);
+ // If the socket has been opened correctly, register it in the @c IfaceMgr.
+ // BTW if it has not an exception has been thrown so it is only
+ // a sanity / recommended check.
+ if (socket_fd_ != -1) {
+ IfaceMgr& iface_mgr = IfaceMgr::instance();
+ iface_mgr.addExternalSocket(socket_fd_,
+ std::bind(&Dhcp4o6TestIpc::receiveHandler, this));
+ }
+}
+
+void
+Dhcp4o6TestIpc::receiveHandler() {
+ pkt_received_ = receive();
+}
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h
new file mode 100644
index 0000000..5109de0
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2015-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/.
+
+#ifndef DHCP4O6_TEST_IPC_H
+#define DHCP4O6_TEST_IPC_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Implements a simple IPC for the test.
+class Dhcp4o6TestIpc : public Dhcp4o6IpcBase {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param port Desired port.
+ /// @param endpoint_type Type of the IPC endpoint. It should be 4 or 6.
+ Dhcp4o6TestIpc(uint16_t port, EndpointType endpoint_type);
+
+ /// @brief Sets new port to be used with @c open.
+ ///
+ /// @param desired_port New desired port.
+ void setDesiredPort(uint16_t desired_port) {
+ desired_port_ = desired_port;
+ }
+
+ /// @brief Opens the IPC socket and registers it in @c IfaceMgr.
+ ///
+ /// This method opens the IPC socket and registers it as external
+ /// socket in the IfaceMgr. The @c TestIpc::receiveHandler is used as a
+ /// callback to be called by the @c IfaceMgr when the data is received
+ /// over the socket.
+ virtual void open();
+
+ /// @brief Retrieve port which socket is bound to.
+ uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Retrieve socket descriptor.
+ int getSocketFd() const {
+ return (socket_fd_);
+ }
+
+ /// @brief Pops and returns a received message.
+ ///
+ /// @return Pointer to the received message over the IPC.
+ Pkt6Ptr popPktReceived() {
+ // Copy the received message.
+ Pkt6Ptr pkt_copy(pkt_received_);
+ // Set the received message to NULL (pop).
+ pkt_received_.reset();
+ // Return the copy.
+ return (pkt_copy);
+ }
+
+private:
+
+ /// @brief Callback for the IPC socket.
+ ///
+ /// This callback is called by the @c IfaceMgr when the data is received
+ /// over the IPC socket.
+ void receiveHandler();
+
+ /// @brief Port number.
+ uint16_t desired_port_;
+
+ /// @brief Endpoint type, i.e. 4 or 6.
+ EndpointType endpoint_type_;
+
+ /// @brief Pointer to the last received message.
+ Pkt6Ptr pkt_received_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP4O6_TEST_IPC_H
diff --git a/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc
new file mode 100644
index 0000000..f10d3e8
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_backend_unittest.cc
@@ -0,0 +1,287 @@
+// Copyright (C) 2018-2023 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 <dhcp/libdhcp++.h>
+#include <dhcp/option_vendor.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <util/buffer.h>
+#include <typeinfo>
+#include <testutils/gtest_utils.h>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericBackendTest::GenericBackendTest()
+ : timestamps_(), audit_entries_() {
+ LibDHCP::clearRuntimeOptionDefs();
+ initTimestamps();
+}
+
+GenericBackendTest::~GenericBackendTest() {
+ LibDHCP::clearRuntimeOptionDefs();
+}
+
+OptionDescriptor
+GenericBackendTest::createEmptyOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist,
+ const bool cancel) const {
+ OptionPtr option(new Option(universe, option_type));
+ OptionDescriptor desc(option, persist, cancel);
+ return (desc);
+}
+
+OptionDescriptor
+GenericBackendTest::createVendorOption(const Option::Universe& universe,
+ const bool persist,
+ const bool cancel,
+ const bool formatted,
+ const uint32_t vendor_id) const {
+ OptionVendorPtr option(new OptionVendor(universe, vendor_id));
+
+ std::ostringstream s;
+ if (formatted) {
+ // Vendor id comprises vendor-id field, for which we need to
+ // assign a value in the textual (formatted) format.
+ s << vendor_id;
+ }
+
+ OptionDescriptor desc(option, persist, cancel, s.str());
+ return (desc);
+}
+
+void
+GenericBackendTest::testOptionsEquivalent(const OptionDescriptor& ref_option,
+ const OptionDescriptor& tested_option) const {
+ // Make sure that all pointers are non-null.
+ ASSERT_TRUE(ref_option.option_);
+ ASSERT_TRUE(tested_option.option_);
+
+ // Get the reference to the tested option. Make should it has
+ // generic type.
+ Option& tested_option_reference = *tested_option.option_;
+ EXPECT_TRUE(typeid(tested_option_reference) == typeid(Option));
+
+ // Only test the binary data if the formatted value is not provided.
+ if (tested_option.formatted_value_.empty()) {
+
+ // Prepare on-wire data of the option under test.
+ isc::util::OutputBuffer tested_option_buf(1);
+ tested_option.option_->pack(tested_option_buf);
+ const uint8_t* tested_option_buf_data = static_cast<const uint8_t*>
+ (tested_option_buf.getData());
+ std::vector<uint8_t> tested_option_buf_vec(tested_option_buf_data,
+ tested_option_buf_data + tested_option_buf.getLength());
+
+ // Prepare on-wire data of the reference option.
+ isc::util::OutputBuffer ref_option_buf(1);
+ ref_option.option_->pack(ref_option_buf);
+ const uint8_t* ref_option_buf_data = static_cast<const uint8_t*>
+ (ref_option_buf.getData());
+ std::vector<uint8_t> ref_option_buf_vec(ref_option_buf_data,
+ ref_option_buf_data + ref_option_buf.getLength());
+
+ // Compare the on-wire data.
+ EXPECT_EQ(ref_option_buf_vec, tested_option_buf_vec);
+
+ } else {
+ // If the formatted value is non-empty the buffer should be empty.
+ EXPECT_TRUE(tested_option.option_->getData().empty());
+ }
+
+ // Compare other members of the @c OptionDescriptor, e.g. the
+ // tested option may contain formatted option data which can be
+ // later used to turn this option instance into a formatted
+ // option when an option definition is available.
+ EXPECT_EQ(ref_option.formatted_value_, tested_option.formatted_value_);
+ EXPECT_EQ(ref_option.persistent_, tested_option.persistent_);
+ EXPECT_EQ(ref_option.cancelled_, tested_option.cancelled_);
+ EXPECT_EQ(ref_option.space_name_, tested_option.space_name_);
+}
+
+void
+GenericBackendTest::checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ const std::string &name,
+ ConstElementPtr exp_value) {
+ ConstCfgGlobalsPtr globals = srv_cfg->getConfiguredGlobals();
+ ConstElementPtr found_global = globals->get(name);
+ ASSERT_TRUE(found_global) << "expected global: "
+ << name << " not found";
+
+ ASSERT_EQ(exp_value->getType(), found_global->getType())
+ << "expected global: " << name << " has wrong type";
+
+ ASSERT_EQ(*exp_value, *found_global)
+ << "expected global: " << name << " has wrong value";
+}
+
+void
+GenericBackendTest::checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ StampedValuePtr& exp_global) {
+ checkConfiguredGlobal(srv_cfg, exp_global->getName(), exp_global->getElementValue());
+}
+
+
+void
+GenericBackendTest::testNewAuditEntry(const std::string& exp_object_type,
+ const AuditEntry::ModificationType& exp_modification_type,
+ const std::string& exp_log_message,
+ const ServerSelector& server_selector,
+ const size_t new_entries_num,
+ const size_t max_tested_entries) {
+ // Get the server tag for which the entries are fetched.
+ std::string tag;
+ if (server_selector.getType() == ServerSelector::Type::ALL) {
+ // Server tag is 'all'.
+ tag = "all";
+ } else {
+ const auto& tags = server_selector.getTags();
+ // This test is not meant to handle multiple server tags all at once.
+ if (tags.size() > 1) {
+ ADD_FAILURE() << "Test error: do not use multiple server tags";
+ } else if (tags.size() == 1) {
+ // Get the server tag for which we run the current test.
+ tag = tags.begin()->get();
+ }
+ }
+
+ auto audit_entries_size_save = audit_entries_[tag].size();
+
+ // Audit entries for different server tags are stored in separate
+ // containers.
+ ASSERT_NO_THROW_LOG(audit_entries_[tag]
+ = getRecentAuditEntries(server_selector, timestamps_["two days ago"], 0));
+
+ ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size())
+ << logExistingAuditEntries(tag);
+
+ auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>();
+
+ // Iterate over specified number of entries starting from the most recent
+ // one and check they have correct values.
+ for (auto audit_entry_it = mod_time_idx.rbegin();
+ ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num) &&
+ (std::distance(mod_time_idx.rbegin(), audit_entry_it) < max_tested_entries));
+ ++audit_entry_it) {
+ auto audit_entry = *audit_entry_it;
+ EXPECT_EQ(exp_object_type, audit_entry->getObjectType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ(exp_modification_type, audit_entry->getModificationType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ(exp_log_message, audit_entry->getLogMessage())
+ << logExistingAuditEntries(tag);
+ }
+}
+
+void
+GenericBackendTest::testNewAuditEntry(const std::vector<ExpAuditEntry>& exp_entries,
+ const ServerSelector& server_selector) {
+ // Get the server tag for which the entries are fetched.
+ std::string tag;
+ if (server_selector.getType() == ServerSelector::Type::ALL) {
+ // Server tag is 'all'.
+ tag = "all";
+ } else {
+ const auto& tags = server_selector.getTags();
+ // This test is not meant to handle multiple server tags all at once.
+ if (tags.size() != 1) {
+ ADD_FAILURE() << "Test error: tags.size(): " << tags.size()
+ << ", you must specify one and only one server tag";
+ }
+
+ // Get the server tag for which we run the current test.
+ tag = tags.begin()->get();
+ }
+
+ size_t new_entries_num = exp_entries.size();
+
+ auto audit_entries_size_save = audit_entries_[tag].size();
+
+ // Audit entries for different server tags are stored in separate
+ // containers.
+ ASSERT_NO_THROW_LOG(audit_entries_[tag]
+ = getRecentAuditEntries(server_selector, timestamps_["two days ago"], 0));
+
+ ASSERT_EQ(audit_entries_size_save + new_entries_num, audit_entries_[tag].size())
+ << logExistingAuditEntries(tag);
+
+ auto& mod_time_idx = audit_entries_[tag].get<AuditEntryModificationTimeIdTag>();
+
+ // Iterate over specified number of entries starting from the most recent
+ // one and check they have correct values.
+ auto exp_entry = exp_entries.rbegin();
+ for (auto audit_entry_it = mod_time_idx.rbegin();
+ ((std::distance(mod_time_idx.rbegin(), audit_entry_it) < new_entries_num));
+ ++audit_entry_it) {
+
+ auto audit_entry = *audit_entry_it;
+ EXPECT_EQ((*exp_entry).object_type, audit_entry->getObjectType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ((*exp_entry).modification_type, audit_entry->getModificationType())
+ << logExistingAuditEntries(tag);
+ EXPECT_EQ((*exp_entry).log_message, audit_entry->getLogMessage())
+ << logExistingAuditEntries(tag);
+
+ ++exp_entry;
+ }
+}
+
+void
+GenericBackendTest::initTimestamps() {
+ // Current time minus 1 hour to make sure it is in the past.
+ timestamps_["today"] = boost::posix_time::second_clock::local_time()
+ - boost::posix_time::hours(1);
+ // One second after today.
+ timestamps_["after today"] = timestamps_["today"] + boost::posix_time::seconds(1);
+ // Yesterday.
+ timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
+ // One second after yesterday.
+ timestamps_["after yesterday"] = timestamps_["yesterday"] + boost::posix_time::seconds(1);
+ // Two days ago.
+ timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
+ // Tomorrow.
+ timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
+ // One second after tomorrow.
+ timestamps_["after tomorrow"] = timestamps_["tomorrow"] + boost::posix_time::seconds(1);
+}
+
+std::string
+GenericBackendTest::logExistingAuditEntries(const std::string& server_tag) {
+ std::ostringstream s;
+
+ auto& mod_time_idx = audit_entries_[server_tag].get<AuditEntryModificationTimeIdTag>();
+
+ for (auto audit_entry_it = mod_time_idx.begin();
+ audit_entry_it != mod_time_idx.end();
+ ++audit_entry_it) {
+ auto audit_entry = *audit_entry_it;
+ s << audit_entry->getObjectType() << ", "
+ << audit_entry->getObjectId() << ", "
+ << static_cast<int>(audit_entry->getModificationType()) << ", "
+ << audit_entry->getModificationTime() << ", "
+ << audit_entry->getRevisionId() << ", "
+ << audit_entry->getLogMessage()
+ << std::endl;
+ }
+
+ return (s.str());
+}
+
+AuditEntryCollection
+GenericBackendTest::getRecentAuditEntries(const ServerSelector&, const boost::posix_time::ptime&,
+ const uint64_t&) const {
+ return (AuditEntryCollection());
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/generic_backend_unittest.h
new file mode 100644
index 0000000..8b18d95
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_backend_unittest.h
@@ -0,0 +1,366 @@
+// Copyright (C) 2018-2023 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 GENERIC_BACKEND_UNITTEST_H
+#define GENERIC_BACKEND_UNITTEST_H
+
+#include <asiolink/io_address.h>
+#include <cc/stamped_value.h>
+#include <database/audit_entry.h>
+#include <database/server_selector.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_option.h>
+#include <dhcpsrv/srv_config.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <cstdint>
+#include <sstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Describes an expected audit table entry.
+struct ExpAuditEntry {
+ /// @brief Type of object changed.
+ std::string object_type;
+
+ /// @brief Timestamp the change occurred.
+ db::AuditEntry::ModificationType modification_type;
+
+ /// @brief Log message describing the change.
+ std::string log_message;
+};
+
+/// @brief Generic test fixture class with utility functions for
+/// testing database backends.
+class GenericBackendTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ GenericBackendTest();
+
+ /// @brief Virtual destructor.
+ virtual ~GenericBackendTest();
+
+ /// @brief Creates an option descriptor holding an empty option.
+ ///
+ /// @param universe V4 or V6.
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param cancel A boolean flag indicating if the option must never
+ /// be returned to the client,
+ ///
+ /// @return Descriptor holding an empty option.
+ OptionDescriptor createEmptyOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist,
+ const bool cancel) const;
+
+ /// @brief Creates an instance of the option for which it is possible to
+ /// specify universe, option type, persistence flag and value in
+ /// the constructor.
+ ///
+ /// Examples of options that can be created using this function are:
+ /// - @ref OptionString
+ /// - different variants of @ref OptionInt.
+ ///
+ /// @param universe V4 or V6.
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param cancel A boolean flag indicating if the option must never
+ /// be returned to the client,
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param value Option value to be assigned to the option.
+ /// @tparam OptionType Class encapsulating the option.
+ /// @tparam DataType Option value data type.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ template<typename OptionType, typename DataType>
+ OptionDescriptor createOption(const Option::Universe& universe,
+ const uint16_t option_type,
+ const bool persist,
+ const bool cancel,
+ const bool formatted,
+ const DataType& value) const {
+ boost::shared_ptr<OptionType> option(new OptionType(universe, option_type,
+ value));
+ std::ostringstream s;
+ if (formatted) {
+ // Using formatted option value. Convert option value to a
+ // textual format.
+ s << value;
+ }
+ OptionDescriptor desc(option, persist, cancel, s.str());
+ return (desc);
+ }
+
+ /// @brief Creates an instance of the option for which it is possible to
+ /// specify option type, persistence flag and value in the constructor.
+ ///
+ /// Examples of options that can be created using this function are:
+ /// - @ref Option4AddrLst
+ /// - @ref Option6AddrLst
+ ///
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param cancel A boolean flag indicating if the option must never
+ /// be returned to the client,
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param value Option value to be assigned to the option.
+ /// @tparam OptionType Class encapsulating the option.
+ /// @tparam DataType Option value data type.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ template<typename OptionType, typename DataType>
+ OptionDescriptor createOption(const uint16_t option_type,
+ const bool persist,
+ const bool cancel,
+ const bool formatted,
+ const DataType& value) const {
+ boost::shared_ptr<OptionType> option(new OptionType(option_type, value));
+
+ std::ostringstream s;
+ if (formatted) {
+ // Using formatted option value. Convert option value to a
+ // textual format.
+ s << value;
+ }
+
+ OptionDescriptor desc(option, persist, cancel, s.str());
+ return (desc);
+ }
+
+ /// @brief Creates an instance of the option holding list of IP addresses.
+ ///
+ /// @param option_type Option type.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param cancel A boolean flag indicating if the option must never
+ /// be returned to the client,
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param address1 First address to be included. If address is empty, it is
+ /// not included.
+ /// @param address2 Second address to be included. If address is empty, it
+ /// is not included.
+ /// @param address3 Third address to be included. If address is empty, it
+ /// is not included.
+ /// @tparam OptionType Class encapsulating the option.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ template<typename OptionType>
+ OptionDescriptor
+ createAddressOption(const uint16_t option_type,
+ const bool persist,
+ const bool cancel,
+ const bool formatted,
+ const std::string& address1 = "",
+ const std::string& address2 = "",
+ const std::string& address3 = "") const {
+ std::ostringstream s;
+ // First address.
+ typename OptionType::AddressContainer addresses;
+ if (!address1.empty()) {
+ addresses.push_back(asiolink::IOAddress(address1));
+ if (formatted) {
+ s << address1;
+ }
+ }
+ // Second address.
+ if (!address2.empty()) {
+ addresses.push_back(asiolink::IOAddress(address2));
+ if (formatted) {
+ if (s.tellp() != std::streampos(0)) {
+ s << ",";
+ }
+ s << address2;
+ }
+ }
+ // Third address.
+ if (!address3.empty()) {
+ addresses.push_back(asiolink::IOAddress(address3));
+ if (formatted) {
+ if (s.tellp() != std::streampos(0)) {
+ s << ",";
+ }
+ s << address3;
+ }
+ }
+
+ boost::shared_ptr<OptionType> option(new OptionType(option_type,
+ addresses));
+ OptionDescriptor desc(option, persist, cancel, s.str());
+ return (desc);
+ }
+
+ /// @brief Creates an instance of the vendor option.
+ ///
+ /// @param universe V4 or V6.
+ /// @param persist A boolean flag indicating if the option is always
+ /// returned to the client or only when requested.
+ /// @param cancel A boolean flag indicating if the option must never
+ /// be returned to the client,
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param vendor_id Vendor identifier.
+ ///
+ /// @return Descriptor holding an instance of the option created.
+ OptionDescriptor createVendorOption(const Option::Universe& universe,
+ const bool persist,
+ const bool cancel,
+ const bool formatted,
+ const uint32_t vendor_id) const;
+
+ /// @brief Verify the option returned by the backend against a
+ /// reference option.
+ ///
+ /// DHCP option value can be specified in two ways. First, it can be
+ /// specified as a string of hexadecimal digits which is converted to
+ /// a binary option value. Second, it can be specified as a string of
+ /// comma separated values in a user readable form. The comma separated
+ /// values are parsed according to the definition of the given option
+ /// and then stored in the respective fields of the option. The second
+ /// approach always requires an option definition to be known to the
+ /// parser. It may either be a standard option definition or a runtime
+ /// option definition created by a user. While standard option
+ /// definitions are available in the Kea header files, the custom
+ /// option definitions may not be available to the Config Backend
+ /// fetching an option from the database for the following reasons:
+ ///
+ /// - the server to which the Config Backend is attached is not the
+ /// one for which the configuration is being returned.
+ /// - the server is starting up and hasn't yet configured its runtime
+ /// option definitions.
+ /// - the Config Backend implementation is not attached to the DHCP
+ /// server but to the Control Agent.
+ ///
+ /// Note that the last case it currently not supported but may be
+ /// supported in the future.
+ ///
+ /// Since the option definitions aren't always available to the Config
+ /// Backend fetching the options from the database, the backend doesn't
+ /// interpret formatted options (those that use comma separated values
+ /// notation). It simply creates an @c OptionDescriptor with the generic
+ /// option instance (containing an option code and no option value) and
+ /// the other @c OptionDescriptor parameters set appropriately. The
+ /// @c CfgOption class contains methods that can be used on demand to
+ /// replace these instances with the appropriate types (derived from
+ /// @c Option) which represent formatted option data, if necessary.
+ ///
+ /// This test verifies that the @c OptionDescriptor returned by the
+ /// Config Backend is correct in that:
+ /// - the @c option_ member is non-null,
+ /// - the option instance is of a @c Option type rather than any of the
+ /// derived types (is a raw option),
+ /// - the wire data of the returned option is equal to the wire data of
+ /// the reference option (the reference option can be of a type derived
+ /// from @c Option),
+ /// - the @c formatted_value_, @c persistent_ and @c space_name_ members
+ /// of the returned option are equal to the respective members of the
+ /// reference option.
+ ///
+ /// @param ref_option Reference option to compare tested option to.
+ /// @param tested_option Option returned by the backend to be tested.
+ void testOptionsEquivalent(const OptionDescriptor& ref_option,
+ const OptionDescriptor& tested_option) const;
+
+ /// @brief Tests that a given global is in the configured globals
+ ///
+ /// @param srv_cfg server config where the global should be checked.
+ /// @param name name of the global parameter
+ /// @param exp_value expected value of the global parameter as an Element
+ void checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ const std::string &name,
+ data::ConstElementPtr exp_value);
+
+ /// @brief Tests that a given global is in the configured globals
+ ///
+ /// @param srv_cfg server config where the global should be checked.
+ /// @param exp_global StampedValue representing the global value to verify
+ ///
+ /// @todo At the point in time StampedVlaue carries type, exp_type should be
+ /// replaced with exp_global->getType()
+ void checkConfiguredGlobal(const SrvConfigPtr& srv_cfg,
+ data::StampedValuePtr& exp_global);
+
+ /// @brief Tests that the new audit entry is added.
+ ///
+ /// This method retrieves a collection of the existing audit entries and
+ /// checks that the new one has been added at the end of this collection.
+ /// It then verifies the values of the audit entry against the values
+ /// specified by the caller.
+ ///
+ /// @param exp_object_type Expected object type.
+ /// @param exp_modification_type Expected modification type.
+ /// @param exp_log_message Expected log message.
+ /// @param server_selector Server selector to be used for next query.
+ /// @param new_entries_num Number of the new entries expected to be inserted.
+ /// @param max_tested_entries Maximum number of entries tested.
+ void testNewAuditEntry(const std::string& exp_object_type,
+ const db::AuditEntry::ModificationType& exp_modification_type,
+ const std::string& exp_log_message,
+ const db::ServerSelector& server_selector = db::ServerSelector::ALL(),
+ const size_t new_entries_num = 1,
+ const size_t max_tested_entries = 65535);
+
+ /// @brief Checks the new audit entries against a list of
+ /// expected entries.
+ ///
+ /// This method retrieves a collection of the existing audit entries and
+ /// checks that number and content of the expected new entries have been
+ /// added to the end of this collection.
+ ///
+ /// @param exp_entries a list of the expected new audit entries.
+ /// @param server_selector Server selector to be used for next query.
+ void testNewAuditEntry(const std::vector<ExpAuditEntry>& exp_entries,
+ const db::ServerSelector& server_selector);
+
+ /// @brief Logs audit entries in the @c audit_entries_ member.
+ ///
+ /// This function is called in case of an error.
+ ///
+ /// @param server_tag Server tag for which the audit entries should be logged.
+ std::string logExistingAuditEntries(const std::string& server_tag);
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const ;
+
+ /// @brief Initialize posix time values used in tests.
+ void initTimestamps();
+
+ /// @brief Holds timestamp values used in tests.
+ std::map<std::string, boost::posix_time::ptime> timestamps_;
+
+ /// @brief Holds the most recent audit entries.
+ std::map<std::string, db::AuditEntryCollection> audit_entries_;
+};
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
new file mode 100644
index 0000000..505813d
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.cc
@@ -0,0 +1,4621 @@
+// Copyright (C) 2022-2023 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 <asiolink/addr_utilities.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/server.h>
+#include <database/testutils/schema.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/testutils/generic_cb_dhcp4_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::test;
+namespace ph = std::placeholders;
+
+void
+GenericConfigBackendDHCPv4Test::SetUp() {
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+
+ try {
+ // Create a connection parameter map and use it to start the backend.
+ DatabaseConnection::ParameterMap params =
+ DatabaseConnection::parse(validConnectionString());
+ cbptr_ = backendFactory(params);
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ // Create test data.
+ initTestServers();
+ initTestOptions();
+ initTestSubnets();
+ initTestSharedNetworks();
+ initTestOptionDefs();
+ initTestClientClasses();
+}
+
+void
+GenericConfigBackendDHCPv4Test::TearDown() {
+ cbptr_.reset();
+ // If data wipe enabled, delete transient data otherwise destroy the schema.
+ destroySchema();
+}
+
+db::AuditEntryCollection
+GenericConfigBackendDHCPv4Test::getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const {
+ return (cbptr_->getRecentAuditEntries(server_selector, modification_time, modification_id));
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestServers() {
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1"));
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis"));
+ test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2"));
+ test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestSubnets() {
+ // First subnet includes all parameters.
+ std::string interface_id_text = "whale";
+ OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24,
+ 30, 40, 60, 1024));
+ subnet->get4o6().setIface4o6("eth0");
+ subnet->get4o6().setInterfaceId(OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID,
+ interface_id)));
+ subnet->get4o6().setSubnet4o6(IOAddress("2001:db8:1::"), 64);
+ subnet->setFilename("/tmp/filename");
+ subnet->allowClientClass("home");
+ subnet->setIface("eth1");
+ subnet->setMatchClientId(false);
+ subnet->setSiaddr(IOAddress("10.1.2.3"));
+ subnet->setT2(323212);
+ subnet->addRelayAddress(IOAddress("10.2.3.4"));
+ subnet->addRelayAddress(IOAddress("10.5.6.7"));
+ subnet->setT1(1234);
+ subnet->requireClientClass("required-class1");
+ subnet->requireClientClass("required-class2");
+ subnet->setReservationsGlobal(false);
+ subnet->setReservationsInSubnet(false);
+ subnet->setSname("server-hostname");
+ subnet->setContext(user_context);
+ subnet->setValid(555555);
+ subnet->setAuthoritative(true);
+ subnet->setCalculateTeeTimes(true);
+ subnet->setT1Percent(0.345);
+ subnet->setT2Percent(0.444);
+ subnet->setDdnsSendUpdates(false);
+ subnet->setCacheThreshold(0.25);
+ subnet->setCacheMaxAge(20);
+ subnet->setOfferLft(77);
+ subnet->setAllocatorType("random");
+
+ Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.10"),
+ IOAddress("192.0.2.20")));
+ subnet->addPool(pool1);
+
+ Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.50"),
+ IOAddress("192.0.2.60")));
+ subnet->addPool(pool2);
+
+ // Add several options to the subnet.
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->cancelled_,
+ test_options_[1]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->cancelled_,
+ test_options_[2]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Adding another subnet with the same subnet id to test
+ // cases that this second instance can override existing
+ // subnet instance.
+ subnet.reset(new Subnet4(IOAddress("10.0.0.0"),
+ 8, 20, 30, 40, 1024));
+
+ pool1.reset(new Pool4(IOAddress("10.0.0.10"),
+ IOAddress("10.0.0.20")));
+ subnet->addPool(pool1);
+
+ pool1->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->cancelled_,
+ test_options_[3]->space_name_);
+
+ pool1->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->cancelled_,
+ test_options_[4]->space_name_);
+
+ pool2.reset(new Pool4(IOAddress("10.0.0.50"),
+ IOAddress("10.0.0.60")));
+
+ pool2->allowClientClass("work");
+ pool2->requireClientClass("required-class3");
+ pool2->requireClientClass("required-class4");
+ user_context = Element::createMap();
+ user_context->set("bar", Element::create("foo"));
+ pool2->setContext(user_context);
+
+ subnet->addPool(pool2);
+
+ test_subnets_.push_back(subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.3.0"), 24, 20, 30, 40, 2048));
+ Triplet<uint32_t> null_timer;
+ subnet->setT1(null_timer);
+ subnet->setT2(null_timer);
+ subnet->setValid(null_timer);
+ subnet->setDdnsSendUpdates(true);
+ subnet->setDdnsOverrideNoUpdate(true);
+ subnet->setDdnsOverrideClientUpdate(false);
+ subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ subnet->setDdnsGeneratedPrefix("myhost");
+ subnet->setDdnsQualifyingSuffix("example.org");
+
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Add a subnet with all defaults.
+ subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 24,
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ Triplet<uint32_t>(), 4096));
+ test_subnets_.push_back(subnet);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestSharedNetworks() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ SharedNetwork4Ptr shared_network(new SharedNetwork4("level1"));
+ shared_network->allowClientClass("foo");
+ shared_network->setIface("eth1");
+ shared_network->setMatchClientId(false);
+ shared_network->setT2(323212);
+ shared_network->addRelayAddress(IOAddress("10.2.3.4"));
+ shared_network->addRelayAddress(IOAddress("10.5.6.7"));
+ shared_network->setT1(1234);
+ shared_network->requireClientClass("required-class1");
+ shared_network->requireClientClass("required-class2");
+ shared_network->setReservationsGlobal(false);
+ shared_network->setReservationsInSubnet(false);
+ shared_network->setContext(user_context);
+ shared_network->setValid(5555);
+ shared_network->setCalculateTeeTimes(true);
+ shared_network->setT1Percent(0.345);
+ shared_network->setT2Percent(0.444);
+ shared_network->setSiaddr(IOAddress("192.0.1.2"));
+ shared_network->setSname("frog");
+ shared_network->setFilename("/dev/null");
+ shared_network->setAuthoritative(true);
+ shared_network->setDdnsSendUpdates(false);
+ shared_network->setCacheThreshold(0.26);
+ shared_network->setCacheMaxAge(21);
+ shared_network->setOfferLft(78);
+ shared_network->setAllocatorType("iterative");
+
+ // Add several options to the shared network.
+ shared_network->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->cancelled_,
+ test_options_[2]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->cancelled_,
+ test_options_[3]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->cancelled_,
+ test_options_[4]->space_name_);
+
+ test_networks_.push_back(shared_network);
+
+ // Adding another shared network called "level1" to test
+ // cases that this second instance can override existing
+ // "level1" instance.
+ shared_network.reset(new SharedNetwork4("level1"));
+ test_networks_.push_back(shared_network);
+
+ // Add more shared networks.
+ shared_network.reset(new SharedNetwork4("level2"));
+ Triplet<uint32_t> null_timer;
+ shared_network->setT1(null_timer);
+ shared_network->setT2(null_timer);
+ shared_network->setValid(null_timer);
+ shared_network->setDdnsSendUpdates(true);
+ shared_network->setDdnsOverrideNoUpdate(true);
+ shared_network->setDdnsOverrideClientUpdate(false);
+ shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ shared_network->setDdnsGeneratedPrefix("myhost");
+ shared_network->setDdnsQualifyingSuffix("example.org");
+
+ shared_network->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_);
+ test_networks_.push_back(shared_network);
+
+ shared_network.reset(new SharedNetwork4("level3"));
+ test_networks_.push_back(shared_network);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestOptionDefs() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefinitionPtr option_def(new OptionDefinition("foo", 234,
+ DHCP4_OPTION_SPACE,
+ "string",
+ "espace"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("bar", 234, DHCP4_OPTION_SPACE,
+ "uint32", true));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("fish", 235, DHCP4_OPTION_SPACE,
+ "record", true));
+ option_def->addRecordField("uint32");
+ option_def->addRecordField("string");
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("whale", 236, "xyz", "string"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("foobar", 234, DHCP4_OPTION_SPACE,
+ "uint64", true));
+ test_option_defs_.push_back(option_def);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestOptions() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefSpaceContainer defs;
+
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, false, "my-boot-file");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+ false, true, true, 64);
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint32>(Option::V4, 1, false, false, false, 312131),
+ desc.space_name_ = "vendor-encapsulated-options";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option4AddrLst>(254, true, true, true,
+ "192.0.2.3");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createEmptyOption(Option::V4, 1, true, true);
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option4AddrLst>(2, false, false, true,
+ "10.0.0.5",
+ "10.0.0.3",
+ "10.0.3.4");
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, false, "my-boot-file-2");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, true, false, "my-boot-file-3");
+ desc.space_name_ = DHCP4_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ // Add definitions for DHCPv4 non-standard options in case we need to
+ // compare subnets, networks and pools in JSON format. In that case,
+ // the @c toElement functions require option definitions to generate the
+ // proper output.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("vendor-encapsulated-1", 1,
+ "vendor-encapsulated-options",
+ "uint32")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-254", 254,
+ DHCP4_OPTION_SPACE,
+ "ipv4-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc",
+ "ipv4-address", true)));
+
+ // Register option definitions.
+ LibDHCP::setRuntimeOptionDefs(defs);
+}
+
+void
+GenericConfigBackendDHCPv4Test::initTestClientClasses() {
+ ExpressionPtr match_expr = boost::make_shared<Expression>();
+ CfgOptionPtr cfg_option = boost::make_shared<CfgOption>();
+ auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option);
+ class1->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class1->setRequired(true);
+ class1->setNextServer(IOAddress("1.2.3.4"));
+ class1->setSname("cool");
+ class1->setFilename("epc.cfg");
+ class1->setValid(Triplet<uint32_t>(30, 60, 90));
+ ElementPtr user_context = Element::createMap();
+ user_context->set("melon", Element::create("water"));
+ class1->setContext(user_context);
+ class1->setOfferLft(20);
+ test_client_classes_.push_back(class1);
+
+ auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option);
+ class2->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class2->setTest("member('foo')");
+ test_client_classes_.push_back(class2);
+
+ auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option);
+ class3->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class3->setTest("member('foo') and member('bar')");
+ test_client_classes_.push_back(class3);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getTypeTest(const std::string& expected_type) {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(expected_type, cbptr_->getType());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getHostTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ("localhost", cbptr_->getHost());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getPortTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(0, cbptr_->getPort());
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteServerTest() {
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ test_servers_[0]->setModificationTime(timestamps_["yesterday"]);
+ test_servers_[1]->setModificationTime(timestamps_["today"]);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // It should not be possible to create a duplicate of the logical
+ // server 'all'.
+ auto all_server = Server::create(ServerTag("all"), "this is logical server all");
+ ASSERT_THROW(cbptr_->createUpdateServer4(all_server), isc::InvalidOperation);
+
+ ServerPtr returned_server;
+
+ // An attempt to fetch the server that hasn't been inserted should return
+ // a null pointer.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server2")));
+ EXPECT_FALSE(returned_server);
+
+ // Try to fetch the server which we expect to exist.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTagAsText());
+ EXPECT_EQ("this is server 1", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime());
+
+ // This call is expected to update the existing server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::UPDATE,
+ "server set");
+ }
+
+ // Verify that the server has been updated.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTag().get());
+ EXPECT_EQ("this is server 1 bis", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime());
+
+ uint64_t servers_deleted = 0;
+
+ // Try to delete non-existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server2")));
+ EXPECT_EQ(0, servers_deleted);
+
+ // Make sure that the server1 wasn't deleted.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ EXPECT_TRUE(returned_server);
+
+ // Deleting logical server 'all' is not allowed.
+ ASSERT_THROW(cbptr_->deleteServer4(ServerTag()), isc::InvalidOperation);
+
+ // Delete the existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer4(ServerTag("server1")));
+ EXPECT_EQ(1, servers_deleted);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server");
+ }
+
+ // Make sure that the server is gone.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer4(ServerTag("server1")));
+ EXPECT_FALSE(returned_server);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAndDeleteAllServersTest() {
+ for (auto i = 1; i < test_servers_.size(); ++i) {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[i]));
+ }
+
+ ServerCollection servers;
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4());
+ ASSERT_EQ(test_servers_.size() - 1, servers.size());
+
+ // All servers should have been returned.
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The logical server all should not be returned. We merely return the
+ // user configured servers.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag()));
+
+ // Delete all servers and make sure they are gone.
+ uint64_t deleted_servers = 0;
+ ASSERT_NO_THROW_LOG(deleted_servers = cbptr_->deleteAllServers4());
+
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers4());
+ EXPECT_TRUE(servers.empty());
+
+ // All servers should be gone.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The number of deleted server should be equal to the number of
+ // inserted servers. The logical 'all' server should be excluded.
+ EXPECT_EQ(test_servers_.size() - 1, deleted_servers);
+
+ EXPECT_EQ(1, countRows("dhcp4_server"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteGlobalParameter4Test() {
+ StampedValuePtr global_parameter = StampedValue::create("global", "whale");
+
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ global_parameter->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ global_parameter);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for global parameter");
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set");
+ }
+
+ // Verify returned parameter and the modification time.
+ StampedValuePtr returned_global_parameter =
+ cbptr_->getGlobalParameter4(ServerSelector::ALL(), "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Because we have added the global parameter for all servers, it
+ // should be also returned for the explicitly specified server.
+ returned_global_parameter = cbptr_->getGlobalParameter4(ServerSelector::ONE("server1"),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Check that the parameter is updated when selector is specified correctly.
+ global_parameter = StampedValue::create("global", "fish");
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ global_parameter);
+ returned_global_parameter = cbptr_->getGlobalParameter4(ServerSelector::ALL(),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("fish", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the global parameter");
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::UPDATE,
+ "global parameter set");
+ }
+
+ // Should not delete parameter specified for all servers if explicit
+ // server name is provided.
+ EXPECT_EQ(0, cbptr_->deleteGlobalParameter4(ServerSelector::ONE("server1"),
+ "global"));
+
+ // Delete parameter and make sure it is gone.
+ cbptr_->deleteGlobalParameter4(ServerSelector::ALL(), "global");
+ returned_global_parameter = cbptr_->getGlobalParameter4(ServerSelector::ALL(),
+ "global");
+ EXPECT_FALSE(returned_global_parameter);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter");
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "global parameter deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::globalParameters4WithServerTagsTest() {
+ // Create three global parameters having the same name.
+ StampedValuePtr global_parameter1 = StampedValue::create("global", "value1");
+ StampedValuePtr global_parameter2 = StampedValue::create("global", "value2");
+ StampedValuePtr global_parameter3 = StampedValue::create("global", "value3");
+
+ // Try to insert one of them and associate with non-existing server.
+ // This should fail because the server must be inserted first.
+ ASSERT_THROW(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
+ global_parameter1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time inserting the global parameters for the server1 and server2 should
+ // be successful.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
+ global_parameter1));
+ {
+ SCOPED_TRACE("Global parameter for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global value.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server2"),
+ global_parameter2));
+ {
+ SCOPED_TRACE("Global parameter for server2 is set");
+ // Same as in case of the server2, there should be 3 audit entries of
+ // which one we validate.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // The last parameter is associated with all servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ global_parameter3));
+ {
+ SCOPED_TRACE("Global parameter for all servers is set");
+ // There should be one new audit entry for all servers. It indicates
+ // the insertion of the global value.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ StampedValuePtr returned_global;
+
+ // Try to fetch the value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter4(ServerSelector::ALL(),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Try to fetch the value specified for the server1. This should override the
+ // value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter4(ServerSelector::ONE("server1"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter1->getValue(), returned_global->getValue());
+
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server1", returned_global->getServerTags().begin()->get());
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter4(ServerSelector::ONE("server2"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter2->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server2", returned_global->getServerTags().begin()->get());
+
+ StampedValueCollection returned_globals;
+
+ // Try to fetch the collection of globals for the server1, server2 and server3.
+ // The server3 does not have an explicit value so for this server we should get
+ /// the value for 'all'.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_globals.size());
+
+ // Capture the returned values into the map so as we can check the
+ // values against the servers.
+ std::map<std::string, std::string> values;
+ for (auto g = returned_globals.begin(); g != returned_globals.end(); ++g) {
+ ASSERT_EQ(1, (*g)->getServerTags().size());
+ values[(*g)->getServerTags().begin()->get()] = ((*g)->getValue());
+ }
+
+ ASSERT_EQ(3, values.size());
+ EXPECT_EQ(global_parameter1->getValue(), values["server1"]);
+ EXPECT_EQ(global_parameter2->getValue(), values["server2"]);
+ EXPECT_EQ(global_parameter3->getValue(), values["all"]);
+
+ // Try to fetch the collection of global parameters specified for all servers.
+ // This excludes the values specific to server1 and server2. It returns only the
+ // common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::ALL())
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Delete the server1. It should remove associations of this server with the
+ // global parameter and the global parameter itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer4(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::ONE("server1"))
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // As a result, the value fetched for the server1 should be the one available for
+ // all servers, rather than the one dedicated for server1. The association of
+ // the server1 specific value with the server1 should be gone.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after server deletion");
+ // We expect two new audit entries for the server1, one indicating that the
+ // server has been deleted and another one indicating that the corresponding
+ // global value has been deleted. We check the latter entry.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global parameter for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter4(ServerSelector::ONE("server1"),
+ "global"));
+ // No parameters should be deleted. In particular, the parameter for the logical
+ // server 'all' should not be deleted.
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing value for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter4(ServerSelector::ONE("server2"),
+ "global"));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create it again to test that deletion of all server removes this too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server2"),
+ global_parameter2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers4());
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters4(ServerSelector::ALL())
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // The common value for all servers should still be available because 'all'
+ // logical server should not be deleted.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after deletion of"
+ " all servers");
+ // There should be 4 new audit entries. One for deleting the global, one for
+ // re-creating it, one for deleting the server2 and one for deleting the
+ // global again as a result of deleting the server2.
+ testNewAuditEntry("dhcp4_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllGlobalParameters4Test() {
+ // Create 3 parameters and put them into the database.
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name1", "value1"));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name2", Element::create(static_cast<int64_t>(65))));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name3", "value3"));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name4", Element::create(static_cast<bool>(true))));
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ StampedValue::create("name5", Element::create(static_cast<double>(1.65))));
+
+ // Fetch all parameters.
+ auto parameters = cbptr_->getAllGlobalParameters4(ServerSelector::ALL());
+ ASSERT_EQ(5, parameters.size());
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // Verify their values.
+ EXPECT_EQ("value1", (*parameters_index.find("name1"))->getValue());
+ EXPECT_EQ(65, (*parameters_index.find("name2"))->getIntegerValue());
+ EXPECT_EQ("value3", (*parameters_index.find("name3"))->getValue());
+ EXPECT_TRUE((*parameters_index.find("name4"))->getBoolValue());
+ EXPECT_EQ(1.65, (*parameters_index.find("name5"))->getDoubleValue());
+
+ for (auto param = parameters_index.begin(); param != parameters_index.end();
+ ++param) {
+ ASSERT_EQ(1, (*param)->getServerTags().size());
+ EXPECT_EQ("all", (*param)->getServerTags().begin()->get());
+ }
+
+ // Should be able to fetch these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getAllGlobalParameters4(ServerSelector::ONE("server1"));
+ EXPECT_EQ(5, parameters.size());
+
+ // Deleting global parameters with non-matching server selector
+ // should fail.
+ EXPECT_EQ(0, cbptr_->deleteAllGlobalParameters4(ServerSelector::ONE("server1")));
+
+ // Delete all parameters and make sure they are gone.
+ EXPECT_EQ(5, cbptr_->deleteAllGlobalParameters4(ServerSelector::ALL()));
+ parameters = cbptr_->getAllGlobalParameters4(ServerSelector::ALL());
+ EXPECT_TRUE(parameters.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedGlobalParameters4Test() {
+ // Create 3 global parameters and assign modification times:
+ // "yesterday", "today" and "tomorrow" respectively.
+ StampedValuePtr value = StampedValue::create("name1", "value1");
+ value->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name2", Element::create(static_cast<int64_t>(65)));
+ value->setModificationTime(timestamps_["today"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name3", "value3");
+ value->setModificationTime(timestamps_["tomorrow"]);
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ALL(),
+ value);
+
+ // Get parameters modified after "today".
+ auto parameters = cbptr_->getModifiedGlobalParameters4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // It should be the one modified "tomorrow".
+ ASSERT_EQ(1, parameters_index.size());
+
+ auto parameter = parameters_index.find("name3");
+ ASSERT_FALSE(parameter == parameters_index.end());
+
+ ASSERT_TRUE(*parameter);
+ EXPECT_EQ("value3", (*parameter)->getValue());
+
+ // Should be able to fetct these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getModifiedGlobalParameters4(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ EXPECT_EQ(1, parameters.size());
+}
+
+void
+GenericConfigBackendDHCPv4Test::nullKeyErrorTest() {
+ // Create a global parameter (it should work with any object type).
+ StampedValuePtr global_parameter = StampedValue::create("global", "value");
+
+ // Try to insert it and associate with non-existing server.
+ std::string msg;
+ try {
+ cbptr_->createUpdateGlobalParameter4(ServerSelector::ONE("server1"),
+ global_parameter);
+ msg = "got no exception";
+ } catch (const NullKeyError& ex) {
+ msg = ex.what();
+ } catch (const std::exception&) {
+ msg = "got another exception";
+ }
+ EXPECT_EQ("server 'server1' does not exist", msg);
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateSubnet4SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ // Supported selectors.
+ Subnet4Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ subnet));
+ subnet = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"),
+ subnet));
+ subnet = test_subnets_[3];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::ANY(), subnet),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::UNASSIGNED(),
+ subnet),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+
+ // An attempt to add a subnet to a non-existing server (server1) should fail.
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet2),
+ NullKeyError);
+
+ // The subnet shouldn't have been added, even though one of the servers exists.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server2"),
+ subnet2->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // Insert two subnets, one for all servers and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("A. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2));
+ {
+ SCOPED_TRACE("B. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ONE("subnet2"),
+ 2, 1);
+ }
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->getID()),
+ isc::InvalidOperation);
+
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->toText()),
+ isc::InvalidOperation);
+
+ // Test that this subnet will be fetched for various server selectors.
+ auto test_get_subnet = [this, &subnet] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+
+ // Test fetching subnet by id.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(server_selector, subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ ASSERT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Test fetching subnet by prefix.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(server_selector,
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ subnet = subnet2;
+ {
+ SCOPED_TRACE("testing server selectors for another server");
+ test_get_subnet("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_subnet("any server", ServerSelector::ANY(), "server2");
+ }
+
+ // Update the subnet in the database (both use the same ID).
+ subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("C. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+ }
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ // The server2 specific subnet should not be returned if the server selector
+ // is not matching.
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ALL(), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ALL(), subnet2->toText()));
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ONE("server1"), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet4(ServerSelector::ONE("server1"), subnet2->toText()));
+
+ // Update the subnet in the database (both use the same prefix).
+ subnet2.reset(new Subnet4(IOAddress("192.0.3.0"),
+ 24, 30, 40, 60, 8192));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2));
+
+ // Fetch again and verify.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server2"), subnet2->toText());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the subnet when it conflicts same id and same prefix both
+ // with different subnets. This should throw.
+ // Subnets are 10.0.0.0/8 id 1024 and 192.0.3.0/24 id 8192
+ subnet2.reset(new Subnet4(IOAddress("10.0.0.0"),
+ 8, 30, 40, 60, 8192));
+ ASSERT_THROW(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2),
+ DuplicateEntry);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4byIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::UNASSIGNED(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4WithOptionalUnspecifiedTest() {
+ // Create a subnet and wrap it within a shared network. It is important
+ // to have the shared network to verify that the subnet doesn't inherit
+ // the values of the shared network but stores the NULL values in the
+ // for those parameters that are unspecified on the subnet level.
+ Subnet4Ptr subnet = test_subnets_[2];
+ SharedNetwork4Ptr shared_network = test_networks_[0];
+ shared_network->add(subnet);
+
+ // Need to add the shared network to the database because otherwise
+ // the subnet foreign key would fail.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_TRUE(returned_subnet->getIface().unspecified());
+ EXPECT_TRUE(returned_subnet->getIface().empty());
+
+ EXPECT_TRUE(returned_subnet->getClientClass().unspecified());
+ EXPECT_TRUE(returned_subnet->getClientClass().empty());
+
+ EXPECT_TRUE(returned_subnet->getValid().unspecified());
+ EXPECT_EQ(0, returned_subnet->getValid().get());
+
+ EXPECT_TRUE(returned_subnet->getT1().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT1().get());
+
+ EXPECT_TRUE(returned_subnet->getT2().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT2().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_subnet->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_subnet->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_subnet->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT1Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT2Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getMatchClientId().unspecified());
+ EXPECT_TRUE(returned_subnet->getMatchClientId().get());
+
+ EXPECT_TRUE(returned_subnet->getAuthoritative().unspecified());
+ EXPECT_FALSE(returned_subnet->getAuthoritative().get());
+
+ EXPECT_TRUE(returned_subnet->getSiaddr().unspecified());
+ EXPECT_TRUE(returned_subnet->getSiaddr().get().isV4Zero());
+
+ EXPECT_TRUE(returned_subnet->getSname().unspecified());
+ EXPECT_TRUE(returned_subnet->getSname().empty());
+
+ EXPECT_TRUE(returned_subnet->getFilename().unspecified());
+ EXPECT_TRUE(returned_subnet->getFilename().empty());
+
+ EXPECT_FALSE(returned_subnet->get4o6().enabled());
+
+ EXPECT_TRUE(returned_subnet->get4o6().getIface4o6().unspecified());
+ EXPECT_TRUE(returned_subnet->get4o6().getIface4o6().empty());
+
+ EXPECT_TRUE(returned_subnet->get4o6().getSubnet4o6().unspecified());
+ EXPECT_TRUE(returned_subnet->get4o6().getSubnet4o6().get().first.isV6Zero());
+ EXPECT_EQ(128, returned_subnet->get4o6().getSubnet4o6().get().second);
+
+ EXPECT_FALSE(returned_subnet->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_subnet->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_subnet->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_subnet->getDdnsQualifyingSuffix().get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4SharedNetworkTest() {
+ Subnet4Ptr subnet = test_subnets_[0];
+ SharedNetwork4Ptr shared_network = test_networks_[0];
+
+ // Add subnet to a shared network.
+ shared_network->add(subnet);
+
+ // Store shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+
+ // Store subnet associated with the shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // However, the check above doesn't verify whether shared network name was
+ // correctly returned from the database.
+ EXPECT_EQ(shared_network->getName(), returned_subnet->getSharedNetworkName());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4ByPrefixTest() {
+ // Insert subnet to the database.
+ Subnet4Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ // Fetch the subnet by prefix.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ "192.0.2.0/24");
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // Verify subnet contents.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Fetching the subnet for an explicitly specified server tag should
+ // succeed too.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server1"),
+ "192.0.2.0/24");
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSubnet4byPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ANY(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::UNASSIGNED(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ALL(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet4(ServerSelector::ONE("server1"), "192.0.2.0/24"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/24"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSubnets4Test() {
+ // Insert test subnets into the database. Note that the second subnet will
+ // overwrite the first subnet as they use the same ID.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // That subnet overrides the first subnet so the audit entry should
+ // indicate an update.
+ if (subnet->toText() == "10.0.0.0/8") {
+ SCOPED_TRACE("UPDATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+ }
+
+ // Fetch all subnets.
+ Subnet4Collection subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // See if the subnets are returned ok.
+ auto subnet_it = subnets.begin();
+ for (auto i = 0; i < subnets.size(); ++i, ++subnet_it) {
+ ASSERT_EQ(1, (*subnet_it)->getServerTags().size());
+ EXPECT_EQ("all", (*subnet_it)->getServerTags().begin()->get());
+ EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(),
+ (*subnet_it)->toElement()->str());
+ }
+
+ // Attempt to remove the non existing subnet should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ALL(), 22));
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ALL(),
+ "155.0.3.0/24"));
+ // All subnets should be still there.
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // Should not delete the subnet for explicit server tag because
+ // our subnet is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ONE("server1"),
+ test_subnets_[1]->getID()));
+
+ // Also, verify that behavior when deleting by prefix.
+ EXPECT_EQ(0, cbptr_->deleteSubnet4(ServerSelector::ONE("server1"),
+ test_subnets_[2]->toText()));
+
+ // Same for all subnets.
+ EXPECT_EQ(0, cbptr_->deleteAllSubnets4(ServerSelector::ONE("server1")));
+
+ // Delete first subnet by id and verify that it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet4(ServerSelector::ALL(),
+ test_subnets_[1]->getID()));
+
+ {
+ SCOPED_TRACE("DELETE first subnet audit entry");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 2, subnets.size());
+
+ // Delete second subnet by prefix and verify it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet4(ServerSelector::ALL(),
+ test_subnets_[2]->toText()));
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 3, subnets.size());
+
+ {
+ SCOPED_TRACE("DELETE second subnet audit entry");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(1, cbptr_->deleteAllSubnets4(ServerSelector::ALL()));
+ subnets = cbptr_->getAllSubnets4(ServerSelector::ALL());
+ ASSERT_TRUE(subnets.empty());
+
+ {
+ SCOPED_TRACE("DELETE all subnets audit entry");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSubnets4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSubnets4(ServerSelector::ANY()), isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSubnets4WithServerTagsTest() {
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ subnet1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"),
+ subnet2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3));
+
+ Subnet4Collection subnets;
+
+ // All three subnets are associated with the server1.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, subnets.size());
+
+ // First subnet is associated with all servers.
+ auto returned_subnet = SubnetFetcher4::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Second subnet is only associated with the server1.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(2048));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Third subnet is associated with both server1 and server2.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two subnets, i.e. first and last.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, subnets.size());
+
+ // First subnet is associated with all servers.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Last subnet is associated with server1 and server2.
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Only the first subnet is associated with all servers.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ALL()));
+ EXPECT_EQ(1, subnets.size());
+
+ returned_subnet = SubnetFetcher4::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSubnets4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ EXPECT_THROW(cbptr_->getModifiedSubnets4(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSubnet4Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ auto create_test_subnets = [&] () {
+ // Insert three subnets, one for all servers, one for server2 and one for two
+ // servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ONE("server2"), subnet2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3)
+ );
+ };
+
+ create_test_subnets();
+
+ // Test that subnet is not deleted for a specified server selector.
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ subnet1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ subnet2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ subnet3);
+ }
+
+ // Test successful deletion of a subnet by ID.
+ auto test_delete_by_id = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet4(server_selector, subnet->getID()));
+ };
+
+ test_delete_by_id("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_id("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_id("one server", ServerSelector::ONE("server1"), subnet3);
+
+ // Re-create deleted subnets.
+ create_test_subnets();
+
+ // Test successful deletion of a subnet by prefix.
+ auto test_delete_by_prefix = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet4(server_selector, subnet->toText()));
+ };
+
+ test_delete_by_prefix("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_prefix("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_prefix("one server", ServerSelector::ONE("server1"), subnet3);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSubnet4ByIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::UNASSIGNED(), SubnetID(1)),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSubnet4ByPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ANY(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), "192.0.2.0/24"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ONE("server1"), "192.0.2.0/24"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/24"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet4(ServerSelector::UNASSIGNED(), "192.0.2.0/24"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteAllSubnets4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets4(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSubnets4(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::unassignedSubnet4Test() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Create the subnets and associate them with the server1.
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"), subnet)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet4(ServerSelector::ONE("server1"), subnet2)
+ );
+
+ // Delete the server. The subnets should be preserved but are considered orphaned,
+ // i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer4(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch the subnet by server tag should return no result.
+ Subnet4Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server1"),
+ subnet->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // The same if we use other calls.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ONE("server1"),
+ subnet->toText()));
+ EXPECT_FALSE(returned_subnet);
+
+ Subnet4Collection returned_subnets;
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets4(ServerSelector::ONE("server1")));
+ EXPECT_TRUE(returned_subnets.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets4(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_subnets.empty());
+
+ // We should get the subnet if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::UNASSIGNED(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Also if we ask for all unassigned subnets it should be returned.
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets4(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // Same for modified subnets.
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets4(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // If we ask for any subnet by subnet id, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ANY(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet4(ServerSelector::ANY(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Deleting the subnet with the mismatched server tag should not affect our
+ // subnet.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(ServerSelector::ONE("server1"),
+ subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all subnets for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets4(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this subnet when we specify ANY and the matching id.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet4(ServerSelector::ANY(), subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all subnets using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets4(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSubnets4Test() {
+ // Explicitly set timestamps of subnets. First subnet has a timestamp
+ // pointing to the future. Second subnet has timestamp pointing to the
+ // past (yesterday). Third subnet has a timestamp pointing to the
+ // past (an hour ago).
+ test_subnets_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_subnets_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_subnets_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert subnets into the database.
+ for (int i = 1; i < test_subnets_.size(); ++i) {
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ test_subnets_[i]);
+ }
+
+ // Fetch subnets with timestamp later than today. Only one subnet
+ // should be returned.
+ Subnet4Collection
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // Fetch subnets with timestamp later than yesterday. We should get
+ // two subnets.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, subnets.size());
+
+ // Fetch subnets with timestamp later than tomorrow. Nothing should
+ // be returned.
+ subnets = cbptr_->getModifiedSubnets4(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(subnets.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetLifetimeTest() {
+ // Insert new subnet with unspecified valid lifetime
+ Triplet<uint32_t> unspecified;
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 30, 40,
+ unspecified, 1111));
+ subnet->setIface("eth1");
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // Verified returned and original subnets match.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the valid lifetime.
+ subnet->setValid( Triplet<uint32_t>(100, 200, 300));
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // Fetch and verify again.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(), subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetworkSubnets4Test() {
+ // Assign test subnets to shared networks level1 and level2.
+ test_subnets_[1]->setSharedNetworkName("level1");
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_subnets_[3]->setSharedNetworkName("level2");
+
+ // Store shared networks in the database.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+ }
+
+ // Store subnets in the database.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+ }
+
+ // Fetch all subnets belonging to shared network level1.
+ Subnet4Collection subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ALL(),
+ "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // All subnets should also be returned for ANY server.
+ subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ANY(), "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // Check server tag
+ ASSERT_EQ(1, (*subnets.begin())->getServerTags().size());
+ EXPECT_EQ("all", (*subnets.begin())->getServerTags().begin()->get());
+
+ // Fetch all subnets belonging to shared network level2.
+ subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ALL(), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ ElementPtr test_list = Element::createList();
+ test_list->add(test_subnets_[2]->toElement());
+ test_list->add(test_subnets_[3]->toElement());
+
+ ElementPtr returned_list = Element::createList();
+ auto subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getSharedNetworkSubnets4(ServerSelector::ONE("server1"), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ returned_list = Element::createList();
+ subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetUpdatePoolsTest() {
+
+ auto test_subnet_update = [this](const std::string& subnet_prefix,
+ const SubnetID& subnet_id) {
+ // Add the subnet with two pools.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]));
+ // Make sure that the pools have been added to the database.
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+
+ // Create the subnet without options which updates the existing
+ // subnet.
+ Subnet4Ptr subnet(new Subnet4(IOAddress(subnet_prefix), 24, 30, 40, 60,
+ subnet_id));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+ // Check that options are gone.
+ EXPECT_EQ(0, countRows("dhcp4_pool"));
+ };
+
+ {
+ SCOPED_TRACE("update subnet, modify subnet id");
+ // Create another subnet with the same prefix as the original subnet but
+ // different id. This is legal to update the subnet id if the prefix is
+ // stable. However, the new subnet has no address pools, so we need to
+ // check of the pools associated with the existing subnet instance are
+ // gone after the update.
+ test_subnet_update("192.0.2.0", 2048);
+ }
+
+ {
+ SCOPED_TRACE("update subnet, modify prefix");
+ // Create a subnet with the same subnet id but different prefix.
+ // The prefix should be updated.
+ test_subnet_update("192.0.3.0", 1024);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetOptionsTest() {
+ // Add the subnet with two pools and three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // The second subnet uses the same subnet id, so this operation should replace
+ // the existing subnet and its options. The new instance has two pools, each
+ // including one option, so we should end up with two options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(2, countRows("dhcp4_options"));
+
+ // Add third subnet with a single option. The number of options in the database
+ // should now be 3.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // Delete the subnet. All options and pools it contains should also be removed, leaving
+ // the last added subnet and its sole option.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), test_subnets_[1]->getID()));
+ EXPECT_EQ(1, countRows("dhcp4_subnet"));
+ EXPECT_EQ(0, countRows("dhcp4_pool"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+
+ // Add the first subnet again. We should now have 4 options: 3 options from the
+ // newly added subnet and one option from the existing subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // Delete the subnet including 3 options. The option from the other subnet should not
+ // be affected.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet4(ServerSelector::ALL(), test_subnets_[0]->getID()));
+ EXPECT_EQ(1, countRows("dhcp4_subnet"));
+ EXPECT_EQ(0, countRows("dhcp4_pool"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetwork4Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+
+ // Insert two shared networks, one for all servers, and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server2"),
+ shared_network2));
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[0]->getName()),
+ isc::InvalidOperation);
+
+ // Test that this shared network will be fetched for various server selectors.
+ auto test_get_network = [this, &shared_network] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+ SharedNetwork4Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork4(server_selector,
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+
+ EXPECT_GT(network->getId(), 0);
+ ASSERT_EQ(1, network->getServerTags().size());
+ EXPECT_EQ(expected_tag, network->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned shared network matches the
+ // inserted shared network is to convert both to text.
+ EXPECT_EQ(shared_network->toElement()->str(), network->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_network("all servers", ServerSelector::ALL());
+ test_get_network("one server", ServerSelector::ONE("server1"));
+ test_get_network("any server", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // Update shared network in the database.
+ shared_network = test_networks_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_network("all servers after update", ServerSelector::ALL());
+ test_get_network("one server after update", ServerSelector::ONE("server1"));
+ test_get_network("any server after update", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ // The server2 specific shared network should not be returned if the
+ // server selector is not matching.
+ EXPECT_FALSE(cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network2->getName()));
+ EXPECT_FALSE(cbptr_->getSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network2->getName()));
+
+ {
+ SCOPED_TRACE("testing selectors for server2 specific shared network");
+ shared_network = shared_network2;
+ test_get_network("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_network("any server", ServerSelector::ANY(), "server2");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetwork4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork4(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateSharedNetwork4Test() {
+ auto shared_network = test_networks_[0];
+
+ // An attempt to insert the shared network for non-existing server should fail.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network),
+ NullKeyError);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+ {
+ SCOPED_TRACE("CREATE audit entry for shared network and ALL servers");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+ {
+ SCOPED_TRACE("UPDATE audit entry for shared network and MULTIPLE servers");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ SharedNetwork4Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork4(ServerSelector::ANY(),
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server2")));
+ EXPECT_FALSE(network->hasServerTag(ServerTag()));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateSharedNetwork4SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ // Supported selectors.
+ SharedNetwork4Ptr shared_network(new SharedNetwork4("all"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network));
+ shared_network.reset(new SharedNetwork4("one"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network));
+ shared_network.reset(new SharedNetwork4("multiple"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork4(ServerSelector::ANY(), shared_network),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork4(ServerSelector::UNASSIGNED(),
+ shared_network),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getSharedNetwork4WithOptionalUnspecifiedTest() {
+ // Insert new shared network.
+ SharedNetwork4Ptr shared_network = test_networks_[2];
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork4Ptr
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[2]->getName());
+ ASSERT_TRUE(returned_network);
+
+ EXPECT_TRUE(returned_network->getIface().unspecified());
+ EXPECT_TRUE(returned_network->getIface().empty());
+
+ EXPECT_TRUE(returned_network->getClientClass().unspecified());
+ EXPECT_TRUE(returned_network->getClientClass().empty());
+
+ EXPECT_TRUE(returned_network->getValid().unspecified());
+ EXPECT_EQ(0, returned_network->getValid().get());
+
+ EXPECT_TRUE(returned_network->getT1().unspecified());
+ EXPECT_EQ(0, returned_network->getT1().get());
+
+ EXPECT_TRUE(returned_network->getT2().unspecified());
+ EXPECT_EQ(0, returned_network->getT2().get());
+
+ EXPECT_TRUE(returned_network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT1Percent().get());
+
+ EXPECT_TRUE(returned_network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT2Percent().get());
+
+ EXPECT_TRUE(returned_network->getMatchClientId().unspecified());
+ EXPECT_TRUE(returned_network->getMatchClientId().get());
+
+ EXPECT_TRUE(returned_network->getAuthoritative().unspecified());
+ EXPECT_FALSE(returned_network->getAuthoritative().get());
+
+ EXPECT_FALSE(returned_network->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_network->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_network->getDdnsQualifyingSuffix().get());
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSharedNetworkSubnets4Test() {
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::UNASSIGNED(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::ALL(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSharedNetworks4Test() {
+ // Insert test shared networks into the database. Note that the second shared
+ // network will overwrite the first shared network as they use the same name.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+
+ // That shared network overrides the first one so the audit entry should
+ // indicate an update.
+ if ((network->getName() == "level1") && (!audit_entries_["all"].empty())) {
+ SCOPED_TRACE("UPDATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+ }
+
+ // Fetch all shared networks.
+ SharedNetwork4Collection networks =
+ cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // All shared networks should also be returned for explicitly specified
+ // server tag.
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ EXPECT_EQ(test_networks_[i + 1]->toElement()->str(),
+ networks[i]->toElement()->str());
+ ASSERT_EQ(1, networks[i]->getServerTags().size());
+ EXPECT_EQ("all", networks[i]->getServerTags().begin()->get());
+ }
+
+ // Add some subnets.
+ test_networks_[1]->add(test_subnets_[0]);
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_networks_[2]->add(test_subnets_[3]);
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]);
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[2]);
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[3]);
+
+ // Both ways to attach a subnet are equivalent.
+ Subnet4Ptr subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("level1", subnet->getSharedNetworkName());
+
+ {
+ SCOPED_TRACE("CREATE audit entry for subnets");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ALL(), 3);
+ }
+
+ // Deleting non-existing shared network should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ "big-fish"));
+ // All shared networks should be still there.
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // Should not delete the shared network for explicit server tag
+ // because our shared network is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork4(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()));
+
+ // Same for all shared networks.
+ EXPECT_EQ(0, cbptr_->deleteAllSharedNetworks4(ServerSelector::ONE("server1")));
+
+ // Delete first shared network with it subnets and verify it is gone.
+ // Begin by its subnet.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetworkSubnets4(ServerSelector::ANY(),
+ test_networks_[1]->getName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for subnets of the first shared network");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets for a shared network");
+ }
+
+ // Check that the subnet is gone..
+ subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ EXPECT_FALSE(subnet);
+
+ // And after the shared network itself.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 2, networks.size());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::DELETE,
+ "shared network deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(2, cbptr_->deleteAllSharedNetworks4(ServerSelector::ALL()));
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_TRUE(networks.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the remaining two shared networks");
+ // The last parameter indicates that we expect four new audit entries,
+ // two for deleted shared networks and two for updated subnets
+ std::vector<ExpAuditEntry> exp_entries({
+ {
+ "dhcp4_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ },
+ {
+ "dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp4_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ }
+ });
+
+ testNewAuditEntry(exp_entries, ServerSelector::ALL());
+ }
+
+ // Check that subnets are still there but detached.
+ subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[2]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+ subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ test_subnets_[3]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSharedNetworks4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks4(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSharedNetworks4(ServerSelector::ANY()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllSharedNetworks4WithServerTagsTest() {
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"),
+ shared_network2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3));
+
+ SharedNetwork4Collection networks;
+
+ // All three networks are associated with the server1.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, networks.size());
+
+ // First network is associated with all servers.
+ auto returned_network = SharedNetworkFetcher4::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Second network is only associated with the server1.
+ returned_network = SharedNetworkFetcher4::get(networks, "level2");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Third network is associated with both server1 and server2.
+ returned_network = SharedNetworkFetcher4::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two shared networks, i.e. first and last.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, networks.size());
+
+ // First shared network is associated with all servers.
+ returned_network = SharedNetworkFetcher4::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Last shared network is associated with server1 and server2.
+ returned_network = SharedNetworkFetcher4::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Only the first shared network is associated with all servers.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL()));
+ EXPECT_EQ(1, networks.size());
+
+ returned_network = SharedNetworkFetcher4::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSharedNetworks4Test() {
+ // Explicitly set timestamps of shared networks. First shared
+ // network has a timestamp pointing to the future. Second shared
+ // network has timestamp pointing to the past (yesterday).
+ // Third shared network has a timestamp pointing to the
+ // past (an hour ago).
+ test_networks_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_networks_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_networks_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert shared networks into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[i]);
+ }
+
+ // Fetch shared networks with timestamp later than today. Only one
+ // shared network should be returned.
+ SharedNetwork4Collection
+ networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, networks.size());
+
+ // Fetch shared networks with timestamp later than yesterday. We
+ // should get two shared networks.
+ networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, networks.size());
+
+ // Fetch shared networks with timestamp later than tomorrow. Nothing
+ // should be returned.
+ networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(networks.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedSharedNetworks4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getModifiedSharedNetworks4(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSharedNetwork4Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ // Insert three shared networks, one for all servers, one for server2 and
+ // one for two servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), shared_network1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server2"), shared_network2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3)
+ );
+
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ shared_network1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ shared_network2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ shared_network3);
+ }
+
+ // We are not going to support deletion of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3->getName()),
+ isc::InvalidOperation);
+
+ // We currently don't support deleting a shared network with specifying
+ // an unassigned server tag. Use ANY to delete any subnet instead.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::UNASSIGNED(),
+ shared_network1->getName()),
+ isc::NotImplemented);
+
+ // Test successful deletion of a shared network.
+ auto test_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSharedNetwork4(server_selector,
+ shared_network->getName()));
+ };
+
+ test_delete("all servers", ServerSelector::ALL(), shared_network1);
+ test_delete("any server", ServerSelector::ANY(), shared_network2);
+ test_delete("one server", ServerSelector::ONE("server1"), shared_network3);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteSharedNetwork4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork4(ServerSelector::UNASSIGNED(), "level1"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteAllSharedNetworks4SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks4(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks4(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks4(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks4(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks4(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::unassignedSharedNetworkTest() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Create the shared networks and associate them with the server1.
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"), shared_network)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ONE("server1"), shared_network2)
+ );
+
+ // Delete the server. The shared networks should be preserved but are
+ // considered orphaned, i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer4(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch this shared network by server tag should return no result.
+ SharedNetwork4Ptr returned_network;
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork4(ServerSelector::ONE("server1"),
+ "level1"));
+ EXPECT_FALSE(returned_network);
+
+ // The same if we use other calls.
+ SharedNetwork4Collection returned_networks;
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getAllSharedNetworks4(ServerSelector::ONE("server1"))
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ // We should get the shared network if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork4(ServerSelector::UNASSIGNED(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Also if we ask for all unassigned networks it should be returned.
+ ASSERT_NO_THROW_LOG(returned_networks = cbptr_->getAllSharedNetworks4(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_networks.size());
+
+ // And all modified.
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks4(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_networks.size());
+
+ // If we ask for any network by name, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork4(ServerSelector::ANY(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Deleting a shared network with the mismatched server tag should not affect
+ // our shared network.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(ServerSelector::ONE("server1"),
+ "level1")
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all shared networks for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks4(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this shared network when we specify ANY and the matching name.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork4(ServerSelector::ANY(), "level1")
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all networks using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks4(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv4Test::sharedNetworkLifetimeTest() {
+ // Insert new shared network with unspecified valid lifetime
+ SharedNetwork4Ptr network(new SharedNetwork4("foo"));
+ Triplet<uint32_t> unspecified;
+ network->setValid(unspecified);
+ network->setIface("eth1");
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+
+ // Fetch this shared network.
+ SharedNetwork4Ptr returned_network =
+ cbptr_->getSharedNetwork4(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+
+ // Verified returned and original shared networks match.
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+
+ // Update the preferred and valid lifetime.
+ network->setValid( Triplet<uint32_t>(100, 200, 300));
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), network);
+
+ // Fetch and verify again.
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv4Test::sharedNetworkOptionsTest() {
+ // Add shared network with three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // Add another shared network with a single option. The numnber of options in the
+ // database should now be 4.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[2]));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // The second shared network uses the same name as the first shared network, so
+ // this operation should replace the existing shared network and its options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[1]));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+
+ // Remove the shared network. This should not affect options assigned to the
+ // other shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+ EXPECT_EQ(1, countRows("dhcp4_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+
+ // Create the first option again. The number of options should be equal to the
+ // sum of options associated with both shared networks.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // Delete this shared network. This should not affect the option associated
+ // with the remaining shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork4(ServerSelector::ALL(),
+ test_networks_[0]->getName()));
+ EXPECT_EQ(1, countRows("dhcp4_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::getOptionDef4Test() {
+ // Insert new option definition.
+ OptionDefinitionPtr option_def = test_option_defs_[0];
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(), option_def);
+
+ // Fetch this option_definition by subnet identifier.
+ OptionDefinitionPtr returned_option_def =
+ cbptr_->getOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[0]->getCode(),
+ test_option_defs_[0]->getOptionSpaceName());
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_GT(returned_option_def->getId(), 0);
+ ASSERT_EQ(1, returned_option_def->getServerTags().size());
+ EXPECT_EQ("all", returned_option_def->getServerTags().begin()->get());
+
+ EXPECT_TRUE(returned_option_def->equals(*option_def));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option definition");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+
+ // Update the option definition in the database.
+ OptionDefinitionPtr option_def2 = test_option_defs_[1];
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(), option_def2);
+
+ // Fetch updated option definition and see if it matches.
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ // Fetching option definition for an explicitly specified server tag
+ // should succeed too.
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option definition");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::optionDefs4WithServerTagsTest() {
+ OptionDefinitionPtr option1 = test_option_defs_[0];
+ OptionDefinitionPtr option2 = test_option_defs_[1];
+ OptionDefinitionPtr option3 = test_option_defs_[4];
+
+ // An attempt to create option definition for non-existing server should
+ // fail.
+ ASSERT_THROW(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server1"),
+ option1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time creation of the option definition for the server1 should pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server1"),
+ option1));
+ {
+ SCOPED_TRACE("option definition for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the option definition.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ // Creation of the option definition for the server2 should also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server2"),
+ option2));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // Finally, creation of the option definition for all servers should
+ // also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ALL(),
+ option3));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the option definition.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ OptionDefinitionPtr returned_option_def;
+
+ // Try to fetch the option definition specified for all servers. It should
+ // return the third one.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ALL(),
+ option3->getCode(),
+ option3->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option3));
+
+ // Try to fetch the option definition specified for server1. It should
+ // override the definition for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option1));
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef4(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option2));
+
+ OptionDefContainer returned_option_defs;
+
+ // Try to fetch the collection of the option definitions for server1, server2
+ // and server3. The server3 does not have an explicit option definition, so
+ // for this server we should get the definition associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_option_defs.size());
+
+ // Check that expected option definitions have been returned.
+ auto current_option = returned_option_defs.begin();
+ EXPECT_TRUE((*current_option)->equals(*option1));
+ EXPECT_TRUE((*(++current_option))->equals(*option2));
+ EXPECT_TRUE((*(++current_option))->equals(*option3));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ // Delete the server1. It should remove associations of this server with the
+ // option definitions and the option definition itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer4(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ONE("server1"));
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after server deletion");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete option definition for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef4(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName()));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option definition for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef4(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName()));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option definition again to test that deletion of all servers
+ // removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef4(ServerSelector::ONE("server2"),
+ option2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers4());
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ EXPECT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllOptionDefs4Test() {
+ // Insert test option definitions into the database. Note that the second
+ // option definition will overwrite the first option definition as they use
+ // the same code and space.
+ size_t updates_num = 0;
+ for (auto option_def : test_option_defs_) {
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(), option_def);
+
+ // That option definition overrides the first one so the audit entry should
+ // indicate an update.
+ auto name = option_def->getName();
+ if (name.find("bar") != std::string::npos) {
+ SCOPED_TRACE("UPDATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ ++updates_num;
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+ }
+
+ // Fetch all option_definitions.
+ OptionDefContainer option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // All option definitions should also be returned for explicitly specified
+ // server tag.
+ option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // See if option definitions are returned ok.
+ for (auto def = option_defs.begin(); def != option_defs.end(); ++def) {
+ ASSERT_EQ(1, (*def)->getServerTags().size());
+ EXPECT_EQ("all", (*def)->getServerTags().begin()->get());
+ bool success = false;
+ for (auto i = 1; i < test_option_defs_.size(); ++i) {
+ if ((*def)->equals(*test_option_defs_[i])) {
+ success = true;
+ }
+ }
+ ASSERT_TRUE(success) << "failed for option definition " << (*def)->getCode()
+ << ", option space " << (*def)->getOptionSpaceName();
+ }
+
+ // Deleting non-existing option definition should return 0.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef4(ServerSelector::ALL(),
+ 99, "non-exiting-space"));
+ // All option definitions should be still there.
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // Should not delete option definition for explicit server tag
+ // because our option definition is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef4(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName()));
+
+ // Same for all option definitions.
+ EXPECT_EQ(0, cbptr_->deleteAllOptionDefs4(ServerSelector::ONE("server1")));
+
+ // Delete one of the option definitions and see if it is gone.
+ EXPECT_EQ(1, cbptr_->deleteOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+ ASSERT_FALSE(cbptr_->getOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first option definition");
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "option definition deleted");
+ }
+
+ // Delete all remaining option definitions.
+ EXPECT_EQ(2, cbptr_->deleteAllOptionDefs4(ServerSelector::ALL()));
+ option_defs = cbptr_->getAllOptionDefs4(ServerSelector::ALL());
+ ASSERT_TRUE(option_defs.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entries for the remaining option definitions");
+ // The last parameter indicates that we expect two new audit entries.
+ testNewAuditEntry("dhcp4_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all option definitions",
+ ServerSelector::ALL(), 2);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedOptionDefs4Test() {
+ // Explicitly set timestamps of option definitions. First option
+ // definition has a timestamp pointing to the future. Second option
+ // definition has timestamp pointing to the past (yesterday).
+ // Third option definitions has a timestamp pointing to the
+ // past (an hour ago).
+ test_option_defs_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_option_defs_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_option_defs_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert option definitions into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateOptionDef4(ServerSelector::ALL(),
+ test_option_defs_[i]);
+ }
+
+ // Fetch option definitions with timestamp later than today. Only one
+ // option definition should be returned.
+ OptionDefContainer
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, option_defs.size());
+
+ // Fetch option definitions with timestamp later than yesterday. We
+ // should get two option definitions.
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, option_defs.size());
+
+ // Fetch option definitions with timestamp later than tomorrow. Nothing
+ // should be returned.
+ option_defs = cbptr_->getModifiedOptionDefs4(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(option_defs.empty());
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteOption4Test() {
+ // Add option to the database.
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ opt_boot_file_name);
+
+ // Make sure we can retrieve this option and that it is equal to the
+ // option we have inserted into the database.
+ OptionDescriptorPtr returned_opt_boot_file_name =
+ cbptr_->getOption4(ServerSelector::ALL(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_);
+ ASSERT_TRUE(returned_opt_boot_file_name);
+
+ {
+ SCOPED_TRACE("verify created option");
+ testOptionsEquivalent(*opt_boot_file_name,
+ *returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set");
+ }
+
+ // Modify option and update it in the database.
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ opt_boot_file_name->cancelled_ = !opt_boot_file_name->cancelled_;
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ opt_boot_file_name);
+
+ // Retrieve the option again and make sure that updates were
+ // properly propagated to the database. Use explicit server selector
+ // which should also return this option.
+ returned_opt_boot_file_name = cbptr_->getOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_);
+ ASSERT_TRUE(returned_opt_boot_file_name);
+
+ {
+ SCOPED_TRACE("verify updated option");
+ testOptionsEquivalent(*opt_boot_file_name,
+ *returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::UPDATE,
+ "global option set");
+ }
+
+ // Deleting an option with explicitly specified server tag should fail.
+ EXPECT_EQ(0, cbptr_->deleteOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ // Deleting option for all servers should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ALL(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ EXPECT_FALSE(cbptr_->getOption4(ServerSelector::ALL(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for an option");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::DELETE,
+ "global option deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::globalOptions4WithServerTagsTest() {
+ OptionDescriptorPtr opt_boot_file_name1 = test_options_[0];
+ OptionDescriptorPtr opt_boot_file_name2 = test_options_[6];
+ OptionDescriptorPtr opt_boot_file_name3 = test_options_[7];
+
+ ASSERT_THROW(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1));
+ {
+ SCOPED_TRACE("global option for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global option.
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2));
+ {
+ SCOPED_TRACE("global option for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ opt_boot_file_name3));
+ {
+ SCOPED_TRACE("global option for all servers is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the global option.
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ALL(),
+ 1, 1);
+
+ }
+
+ OptionDescriptorPtr returned_option;
+
+ // Try to fetch the option specified for all servers. It should return
+ // the third option.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption4(ServerSelector::ALL(),
+ opt_boot_file_name3->option_->getType(),
+ opt_boot_file_name3->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_option);
+
+ // Try to fetch the option specified for the server1. It should override the
+ // option specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1->option_->getType(),
+ opt_boot_file_name1->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_boot_file_name1, *returned_option);
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2->option_->getType(),
+ opt_boot_file_name2->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_boot_file_name2, *returned_option);
+
+ OptionContainer returned_options;
+
+ // Try to fetch the collection of global options for the server1, server2
+ // and server3. The server3 does not have an explicit value so for this server
+ // we should get the option associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_options.size());
+
+ // Check that expected options have been returned.
+ auto current_option = returned_options.begin();
+ testOptionsEquivalent(*opt_boot_file_name1, *current_option);
+ testOptionsEquivalent(*opt_boot_file_name2, *(++current_option));
+ testOptionsEquivalent(*opt_boot_file_name3, *(++current_option));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+ // Delete the server1. It should remove associations of this server with the
+ // option and the option itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer4(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ONE("server1"));
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after server deletion");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global option for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption4(ServerSelector::ONE("server1"),
+ opt_boot_file_name1->option_->getType(),
+ opt_boot_file_name1->space_name_));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2->option_->getType(),
+ opt_boot_file_name2->space_name_));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option again to test that deletion of all servers removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ONE("server2"),
+ opt_boot_file_name2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers4());
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_boot_file_name3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp4_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getAllOptions4Test() {
+ // Add three global options to the database.
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Retrieve all these options.
+ OptionContainer returned_options = cbptr_->getAllOptions4(ServerSelector::ALL());
+ ASSERT_EQ(3, returned_options.size());
+
+ // Fetching global options with explicitly specified server tag should return
+ // the same result.
+ returned_options = cbptr_->getAllOptions4(ServerSelector::ONE("server1"));
+ ASSERT_EQ(3, returned_options.size());
+
+ // Get the container index used to search options by option code.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+
+ // Verify that all options we put into the database were
+ // returned.
+ {
+ SCOPED_TRACE("verify test_options_[0]");
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ testOptionsEquivalent(*test_options_[0], *option0);
+ EXPECT_GT(option0->getId(), 0);
+ ASSERT_EQ(1, option0->getServerTags().size());
+ EXPECT_EQ("all", option0->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[1]");
+ auto option1 = index.find(test_options_[1]->option_->getType());
+ ASSERT_FALSE(option1 == index.end());
+ testOptionsEquivalent(*test_options_[1], *option1);
+ EXPECT_GT(option1->getId(), 0);
+ ASSERT_EQ(1, option1->getServerTags().size());
+ EXPECT_EQ("all", option1->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[5]");
+ auto option5 = index.find(test_options_[5]->option_->getType());
+ ASSERT_FALSE(option5 == index.end());
+ testOptionsEquivalent(*test_options_[5], *option5);
+ EXPECT_GT(option5->getId(), 0);
+ ASSERT_EQ(1, option5->getServerTags().size());
+ EXPECT_EQ("all", option5->getServerTags().begin()->get());
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedOptions4Test() {
+ // Assign timestamps to the options we're going to store in the
+ // database.
+ test_options_[0]->setModificationTime(timestamps_["tomorrow"]);
+ test_options_[1]->setModificationTime(timestamps_["yesterday"]);
+ test_options_[5]->setModificationTime(timestamps_["today"]);
+
+ // Put options into the database.
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption4(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Get options with the timestamp later than today. Only
+ // one option should be returned.
+ OptionContainer returned_options =
+ cbptr_->getModifiedOptions4(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // Fetching modified options with explicitly specified server selector
+ // should return the same result.
+ returned_options = cbptr_->getModifiedOptions4(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // The returned option should be the one with the timestamp
+ // set to tomorrow.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*test_options_[0], *option0);
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteSubnetOption4Test() {
+ // Insert new subnet.
+ Subnet4Ptr subnet = test_subnets_[1];
+ cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a new subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // The inserted subnet contains two options.
+ ASSERT_EQ(2, countRows("dhcp4_options"));
+
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ cbptr_->createUpdateOption4(ServerSelector::ANY(), subnet->getID(),
+ opt_boot_file_name);
+
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ OptionDescriptor returned_opt_boot_file_name =
+ returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ EXPECT_GT(returned_opt_boot_file_name.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an added subnet option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire subnet so as the server refreshes the
+ // subnet with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // We have added one option to the existing subnet. We should now have
+ // three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ opt_boot_file_name->cancelled_ = !opt_boot_file_name->cancelled_;
+ cbptr_->createUpdateOption4(ServerSelector::ANY(), subnet->getID(),
+ opt_boot_file_name);
+
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ANY(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ returned_opt_boot_file_name =
+ returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned option with modified persistence");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an updated subnet option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // Updating the option should replace the existing instance with the new
+ // instance. Therefore, we should still have three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ // It should succeed for any server.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(), subnet->getID(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a deleted subnet option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option deleted");
+ }
+
+ // We should have only two options after deleting one of them.
+ ASSERT_EQ(2, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeletePoolOption4Test() {
+ // Insert new subnet.
+ Subnet4Ptr subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), subnet));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a subnet");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // Inserted subnet has two options.
+ ASSERT_EQ(2, countRows("dhcp4_options"));
+
+ // Add an option into the pool.
+ const PoolPtr pool = subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(pool);
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_boot_file_name));
+
+ // Query for a subnet.
+ Subnet4Ptr returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // The returned subnet should include our pool.
+ const PoolPtr returned_pool = returned_subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(returned_pool);
+
+ // The pool should contain option we added earlier.
+ OptionDescriptor returned_opt_boot_file_name =
+ returned_pool->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned pool option");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ EXPECT_GT(returned_opt_boot_file_name.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option "
+ "to the pool");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "pool specific option set");
+ }
+
+ // With the newly inserted option we should now have three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ // Modify the option and update it in the database.
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ opt_boot_file_name->cancelled_ = !opt_boot_file_name->cancelled_;
+ cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_boot_file_name);
+
+ // Fetch the subnet and the corresponding pool.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool1 = returned_subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(returned_pool1);
+
+ // Test that the option has been correctly updated in the database.
+ returned_opt_boot_file_name =
+ returned_pool1->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when updating pool "
+ "specific option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "pool specific option set");
+ }
+
+ // The new option instance should replace the existing one, so we should
+ // still have three options.
+ ASSERT_EQ(3, countRows("dhcp4_options"));
+
+ // Delete option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+
+ // Fetch the subnet and the pool from the database again to make sure
+ // that the option is really gone.
+ returned_subnet = cbptr_->getSubnet4(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool2 = returned_subnet->getPool(Lease::TYPE_V4,
+ IOAddress("192.0.2.10"));
+ ASSERT_TRUE(returned_pool2);
+
+ // Option should be gone.
+ EXPECT_FALSE(returned_pool2->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_BOOT_FILE_NAME).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when deleting pool "
+ "specific option");
+ testNewAuditEntry("dhcp4_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "pool specific option deleted");
+ }
+
+ // The option has been deleted so the number of options should now
+ // be down to 2.
+ EXPECT_EQ(2, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateDeleteSharedNetworkOption4Test() {
+ // Insert new shared network.
+ SharedNetwork4Ptr shared_network = test_networks_[1];
+ cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork4Ptr returned_network =
+ cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for the new shared network");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // The inserted shared network has no options.
+ ASSERT_EQ(0, countRows("dhcp4_options"));
+
+ OptionDescriptorPtr opt_boot_file_name = test_options_[0];
+ cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_boot_file_name);
+
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ OptionDescriptor returned_opt_boot_file_name =
+ returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ EXPECT_GT(returned_opt_boot_file_name.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the added shared network option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire shared network so as the server refreshes the
+ // shared network with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // One option should now be stored in the database.
+ ASSERT_EQ(1, countRows("dhcp4_options"));
+
+ opt_boot_file_name->persistent_ = !opt_boot_file_name->persistent_;
+ opt_boot_file_name->cancelled_ = !opt_boot_file_name->cancelled_;
+ cbptr_->createUpdateOption4(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_boot_file_name);
+
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ returned_opt_boot_file_name =
+ returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_boot_file_name, returned_opt_boot_file_name);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the updated shared network option");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // The new option instance should replace the existing option instance,
+ // so we should still have one option.
+ ASSERT_EQ(1, countRows("dhcp4_options"));
+
+ // Deleting an option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption4(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_boot_file_name->option_->getType(),
+ opt_boot_file_name->space_name_));
+ returned_network = cbptr_->getSharedNetwork4(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the deleted shared network option");
+ testNewAuditEntry("dhcp4_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option deleted");
+ }
+
+ // After deleting the option we should be back to 0.
+ EXPECT_EQ(0, countRows("dhcp4_options"));
+}
+
+void
+GenericConfigBackendDHCPv4Test::subnetOptionIdOrderTest() {
+
+ // Add a subnet with two pools with one option each.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(2, countRows("dhcp4_options"));
+
+ // Add a second subnet with a single option. The number of options in the database
+ // should now be 3.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(3, countRows("dhcp4_options"));
+
+ // Now replace the first subnet with three options and two pools. This will cause
+ // the option id values for this subnet to be larger than those in the second
+ // subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet4(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp4_pool"));
+ EXPECT_EQ(4, countRows("dhcp4_options"));
+
+ // Now fetch all subnets.
+ Subnet4Collection subnets;
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets4(ServerSelector::ALL()));
+ ASSERT_EQ(2, subnets.size());
+
+ // Verify that the subnets returned are as expected.
+ for (auto subnet : subnets) {
+ ASSERT_EQ(1, subnet->getServerTags().size());
+ EXPECT_EQ("all", subnet->getServerTags().begin()->get());
+ if (subnet->getID() == 1024) {
+ EXPECT_EQ(test_subnets_[0]->toElement()->str(), subnet->toElement()->str());
+ } else if (subnet->getID() == 2048) {
+ EXPECT_EQ(test_subnets_[2]->toElement()->str(), subnet->toElement()->str());
+ } else {
+ ADD_FAILURE() << "unexpected subnet id:" << subnet->getID();
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::sharedNetworkOptionIdOrderTest() {
+ auto level1_options = test_networks_[0];
+ auto level1_no_options = test_networks_[1];
+ auto level2 = test_networks_[2];
+
+ // Insert two shared networks. We insert level1 without options first,
+ // then level2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ level1_no_options));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ level2));
+ // Fetch all shared networks.
+ SharedNetwork4Collection networks =
+ cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_no_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork4(ServerSelector::ALL(),
+ level1_options));
+
+ // Fetch all shared networks.
+ networks = cbptr_->getAllSharedNetworks4(ServerSelector::ALL());
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::setAndGetAllClientClasses4Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create first class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("pkt4.msgtype == 1");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ // Create second class.
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create third class.
+ auto class3 = test_client_classes_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Update the third class to depend on the second class.
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class bar is updated");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::UPDATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ // Only the first class should be returned for the server selector ALL.
+ auto client_classes = cbptr_->getAllClientClasses4(ServerSelector::ALL());
+ ASSERT_EQ(1, client_classes.getClasses()->size());
+
+ // All three classes should be returned for the server1.
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ auto classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+
+ auto fetched_class = classes_list->begin();
+ ASSERT_EQ("foo", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class1->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("bar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class2->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("foobar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class3->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ // Move the third class between the first and second class.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, "foo"));
+
+ // Ensure that the classes order has changed.
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+
+ // Update the foobar class without specifying its position. It should not
+ // be moved.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+
+ client_classes = cbptr_->getAllClientClasses4(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+}
+
+void
+GenericConfigBackendDHCPv4Test::getClientClass4Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Add classes.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->cancelled_,
+ test_options_[1]->space_name_));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+
+ // Get the first client class and validate its contents.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+ EXPECT_EQ("foo", client_class->getName());
+ EXPECT_TRUE(client_class->getRequired());
+ EXPECT_EQ("1.2.3.4", client_class->getNextServer().toText());
+ EXPECT_EQ("cool", client_class->getSname());
+ EXPECT_EQ("epc.cfg", client_class->getFilename());
+ EXPECT_EQ(30, client_class->getValid().getMin());
+ EXPECT_EQ(60, client_class->getValid().get());
+ EXPECT_EQ(90, client_class->getValid().getMax());
+
+ // Validate options belonging to this class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_boot_file_name =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ OptionDescriptor returned_opt_ip_ttl =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+
+ // Fetch the same class using different server selectors.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ONE("server1"),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::UNASSIGNED(),
+ class1->getName()));
+ EXPECT_FALSE(client_class);
+
+ // Fetch the second client class using different selectors. This time the
+ // class should not be returned for the ALL server selector because it is
+ // associated with the server1.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ANY(),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::UNASSIGNED(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+}
+
+void
+GenericConfigBackendDHCPv4Test::createUpdateClientClass4OptionsTest() {
+ // Add class with two options and two option definitions.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->cancelled_,
+ test_options_[1]->space_name_));
+ auto cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[0]));
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ // Fetch the class and the options from the database.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Validate options belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_boot_file_name =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ ASSERT_TRUE(returned_opt_boot_file_name.option_);
+
+ OptionDescriptor returned_opt_ip_ttl =
+ client_class->getCfgOption()->get(DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+
+ // Validate option definitions belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ auto returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ ASSERT_TRUE(returned_def_foo);
+ EXPECT_EQ(234, returned_def_foo->getCode());
+ EXPECT_EQ("foo", returned_def_foo->getName());
+ EXPECT_EQ(DHCP4_OPTION_SPACE, returned_def_foo->getOptionSpaceName());
+ EXPECT_EQ("espace", returned_def_foo->getEncapsulatedSpace());
+ EXPECT_EQ(OPT_STRING_TYPE, returned_def_foo->getType());
+ EXPECT_FALSE(returned_def_foo->getArrayType());
+
+ auto returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ ASSERT_TRUE(returned_def_fish);
+ EXPECT_EQ(235, returned_def_fish->getCode());
+ EXPECT_EQ("fish", returned_def_fish->getName());
+ EXPECT_EQ(DHCP4_OPTION_SPACE, returned_def_fish->getOptionSpaceName());
+ EXPECT_TRUE(returned_def_fish->getEncapsulatedSpace().empty());
+ EXPECT_EQ(OPT_RECORD_TYPE, returned_def_fish->getType());
+ EXPECT_TRUE(returned_def_fish->getArrayType());
+
+ // Replace client class specific option definitions. Leave only one option
+ // definition.
+ cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+
+ // Delete one of the options and update the class.
+ class1->getCfgOption()->del(test_options_[0]->space_name_,
+ test_options_[0]->option_->getType());
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass4(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Ensure that the first option definition is gone.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ EXPECT_FALSE(returned_def_foo);
+
+ // The second option definition should be present.
+ returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ EXPECT_TRUE(returned_def_fish);
+
+ // Make sure that the first option is gone.
+ ASSERT_TRUE(client_class->getCfgOption());
+ returned_opt_boot_file_name = client_class->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_BOOT_FILE_NAME);
+ EXPECT_FALSE(returned_opt_boot_file_name.option_);
+
+ // The second option should be there.
+ returned_opt_ip_ttl = client_class->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_DEFAULT_IP_TTL);
+ ASSERT_TRUE(returned_opt_ip_ttl.option_);
+}
+
+void
+GenericConfigBackendDHCPv4Test::getModifiedClientClasses4Test() {
+ // Create server1.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Add three classes to the database with different timestamps.
+ auto class1 = test_client_classes_[0];
+ class1->setModificationTime(timestamps_["yesterday"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ class2->setModificationTime(timestamps_["today"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""));
+
+ auto class3 = test_client_classes_[2];
+ class3->setModificationTime(timestamps_["tomorrow"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class3, ""));
+
+ // Get modified client classes configured for all servers.
+ auto client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get modified client classes appropriate for server1. It includes classes
+ // for all servers and for the server1.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get the classes again but use the timestamp equal to the modification
+ // time of the first class.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get modified classes starting from today. It should return only two.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["today"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get client classes modified in the future. It should return none.
+ client_classes = cbptr_->getModifiedClientClasses4(ServerSelector::ONE("server1"),
+ timestamps_["after tomorrow"]);
+ EXPECT_EQ(0, client_classes.getClasses()->size());
+
+ // Getting modified client classes for any server is unsupported.
+ ASSERT_THROW(cbptr_->getModifiedClientClasses4(ServerSelector::ANY(),
+ timestamps_["two days ago"]),
+ InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteClientClass4Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass4(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class bar is deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass4(ServerSelector::ONE("server2"),
+ class3->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foobar is deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass4(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+}
+
+void
+GenericConfigBackendDHCPv4Test::deleteAllClientClasses4Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp4_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::UNASSIGNED()));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server2 deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server2")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server1 deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ONE("server1")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ALL()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for all deleted");
+ testNewAuditEntry("dhcp4_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses4(ServerSelector::ALL()));
+ EXPECT_EQ(0, result);
+
+ // Deleting multiple objects using ANY server tag is unsupported.
+ ASSERT_THROW(cbptr_->deleteAllClientClasses4(ServerSelector::ANY()), InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv4Test::clientClassDependencies4Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[0]));
+
+ // Create first class. It depends on KNOWN built-in class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("member('KNOWN')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""));
+
+ // Create second class which depends on the first class. This yelds indirect
+ // dependency on KNOWN class.
+ auto class2 = test_client_classes_[1];
+ class2->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""));
+
+ // Create third class depending on the second class. This also yelds indirect
+ // dependency on KNOWN class.
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('bar')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class3, ""));
+
+ // An attempt to move the first class to the end of the class hierarchy should
+ // fail because other classes depend on it.
+ ASSERT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, "bar"),
+ DbOperationError);
+
+ // Try to change the dependency of the first class. There are other classes
+ // having indirect dependency on KNOWN class via this class. Therefore, the
+ // update should be unsuccessful.
+ class1->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class1, ""),
+ DbOperationError);
+
+ // Try to change the dependency of the second class. This should result in
+ // an error because the third class depends on it.
+ class2->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class2, ""),
+ DbOperationError);
+
+ // Changing the indirect dependency of the third class should succeed, because
+ // no other classes depend on this class.
+ class3->setTest("member('HA_server1')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass4(ServerSelector::ALL(), class3, ""));
+}
+
+void
+GenericConfigBackendDHCPv4Test::multipleAuditEntriesTest() {
+ // Get current time.
+ boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer4(test_servers_[1]));
+
+ // Create a global parameter and update it many times.
+ const ServerSelector& server_selector = ServerSelector::ALL();
+ StampedValuePtr param;
+ ElementPtr value;
+ for (int i = 0; i < 100; ++i) {
+ value = Element::create(i);
+ param = StampedValue::create("my-parameter", value);
+ cbptr_->createUpdateGlobalParameter4(server_selector, param);
+ }
+
+ // Get all audit entries from now.
+ AuditEntryCollection audit_entries =
+ cbptr_->getRecentAuditEntries(server_selector, now, 0);
+
+ // Check that partial retrieves return the right count.
+ auto& mod_time_idx = audit_entries.get<AuditEntryModificationTimeIdTag>();
+ for (auto it = mod_time_idx.begin(); it != mod_time_idx.end(); ++it) {
+ size_t partial_size =
+ cbptr_->getRecentAuditEntries(server_selector,
+ (*it)->getModificationTime(),
+ (*it)->getRevisionId()).size();
+ EXPECT_EQ(partial_size + 1,
+ std::distance(it, mod_time_idx.end()));
+ }
+}
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h
new file mode 100644
index 0000000..a2fa5dc
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp4_unittest.h
@@ -0,0 +1,390 @@
+// Copyright (C) 2022 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 GENERIC_CONFIG_BACKEND_DHCP4_H
+#define GENERIC_CONFIG_BACKEND_DHCP4_H
+
+#include <database/database_connection.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class for testing DHCPv4
+/// config backend operations.
+class GenericConfigBackendDHCPv4Test : public GenericBackendTest {
+public:
+ /// @brief Constructor.
+ GenericConfigBackendDHCPv4Test()
+ : test_subnets_(), test_networks_(), test_option_defs_(),
+ test_options_(), test_client_classes_(), test_servers_(), cbptr_() {
+ }
+
+ /// @brief Destructor.
+ virtual ~GenericConfigBackendDHCPv4Test() {};
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic host manager to
+ /// wipe out any prior instance
+ virtual void SetUp();
+
+ /// @brief Pre-test exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown();
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns the back end specific connection
+ /// string
+ virtual std::string validConnectionString() = 0;
+
+ /// @brief Abstract method which instantiates an instance of a
+ /// DHCPv4 configuration back end.
+ ///
+ /// @params Connection parameters describing the back end to create.
+ /// @return Pointer to the newly created back end instance.
+ virtual ConfigBackendDHCPv4Ptr backendFactory(db::DatabaseConnection::ParameterMap&
+ params) = 0;
+
+ /// @brief Counts rows in a selected table in the back end database.
+ ///
+ /// This method can be used to verify that some configuration elements were
+ /// deleted from a selected table as a result of cascade delete or a trigger.
+ /// For example, deleting a subnet should trigger deletion of its address
+ /// pools and options. By counting the rows on each table we can determine
+ /// whether the deletion took place on all tables for which it was expected.
+ ///
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ virtual size_t countRows(const std::string& table) const = 0;
+
+ /// @brief Creates several servers used in tests.
+ void initTestServers();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSubnets();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSharedNetworks();
+
+ /// @brief Creates several option definitions used in tests.
+ void initTestOptionDefs();
+
+ /// @brief Creates several DHCP options used in tests.
+ void initTestOptions();
+
+ /// @brief Creates several client classes used in tests.
+ void initTestClientClasses();
+
+ /// @brief Tests that a backend of the given type can be instantiated.
+ ///
+ /// @param expected_type type of the back end created (i.e. "mysql",
+ /// "postgresql").
+ void getTypeTest(const std::string& expected_type);
+
+ /// @brief Verifies that a backend on the localhost can be instantiated.
+ void getHostTest();
+
+ /// @brief Verifies that a backend on the localhost port 0 can be instantiated.
+ void getPortTest();
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief This test verifies that the server can be added, updated and deleted.
+ void createUpdateDeleteServerTest();
+
+ /// @brief This test verifies that it is possible to retrieve all servers from the
+ /// database and then delete all of them.
+ void getAndDeleteAllServersTest();
+
+ /// @brief This test verifies that the global parameter can be added, updated and
+ /// deleted.
+ void createUpdateDeleteGlobalParameter4Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global parameters by server tag and that the value specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalParameters4WithServerTagsTest();
+
+ /// @brief This test verifies that all global parameters can be retrieved and deleted.
+ void getAllGlobalParameters4Test();
+
+ /// @brief This test verifies that modified global parameters can be retrieved.
+ void getModifiedGlobalParameters4Test();
+
+ /// @brief Test that the NullKeyError message is correctly updated.
+ void nullKeyErrorTest();
+
+ /// @brief Test that createUpdateSubnet4 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSubnet4SelectorsTest();
+
+ /// @brief Test that subnet can be inserted, fetched, updated and then fetched again.
+ void getSubnet4Test();
+
+ /// @brief Test that getSubnet4 by ID throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet4byIdSelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSubnet4WithOptionalUnspecifiedTest();
+
+ /// @brief Test that subnet can be associated with a shared network.
+ void getSubnet4SharedNetworkTest();
+
+ /// @brief Test that subnet can be fetched by prefix.
+ void getSubnet4ByPrefixTest();
+
+ /// @brief Test that getSubnet4 by prefix throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet4byPrefixSelectorsTest();
+
+ /// @brief Test that all subnets can be fetched and then deleted.
+ void getAllSubnets4Test();
+
+ /// @brief Test that getAllSubnets4 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSubnets4SelectorsTest();
+
+ /// @brief Test that subnets with different server associations are returned.
+ void getAllSubnets4WithServerTagsTest();
+
+ /// @brief Test that getModifiedSubnets4 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSubnets4SelectorsTest();
+
+ /// @brief Test that selected subnet can be deleted.
+ void deleteSubnet4Test();
+
+ /// @brief Test that deleteSubnet4 by ID throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet4ByIdSelectorsTest();
+
+ /// @brief Test that deleteSubnet4 by prefix throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet4ByPrefixSelectorsTest();
+
+ /// @brief Test that deleteAllSubnets4 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSubnets4SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned subnet.
+ void unassignedSubnet4Test();
+
+ /// @brief Test that subnets modified after given time can be fetched.
+ void getModifiedSubnets4Test();
+
+ /// @brief Test that lifetimes in subnets are handled as expected.
+ void subnetLifetimeTest();
+
+ /// @brief Test that subnets belonging to a shared network can be retrieved.
+ void getSharedNetworkSubnets4Test();
+
+ /// @brief Test that pools are properly updated as a result a subnet update.
+ void subnetUpdatePoolsTest();
+
+ /// @brief Test that deleting a subnet triggers deletion of the options associated
+ /// with the subnet and pools.
+ void subnetOptionsTest();
+
+ /// @brief Test that shared network can be inserted, fetched, updated and then
+ /// fetched again.
+ void getSharedNetwork4Test();
+
+ /// @brief Test that getSharedNetwork4 throws appropriate exceptions for various
+ /// server selectors.
+ void getSharedNetwork4SelectorsTest();
+
+ /// @brief Test that shared network may be created and updated and the server tags
+ /// are properly assigned to it.
+ void createUpdateSharedNetwork4Test();
+
+ /// @brief Test that createUpdateSharedNetwork4 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSharedNetwork4SelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSharedNetwork4WithOptionalUnspecifiedTest();
+
+ /// @brief Test that deleteSharedNetworkSubnets4 with not ANY selector throw.
+ void deleteSharedNetworkSubnets4Test();
+
+ /// @brief Test that all shared networks can be fetched.
+ void getAllSharedNetworks4Test();
+
+ /// @brief Test that getAllSharedNetworks4 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSharedNetworks4SelectorsTest();
+
+ /// @brief Test that shared networks with different server associations are returned.
+ void getAllSharedNetworks4WithServerTagsTest();
+
+ /// @brief Test that shared networks modified after given time can be fetched.
+ void getModifiedSharedNetworks4Test();
+
+ /// @brief Test that getModifiedSharedNetworks4 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSharedNetworks4SelectorsTest();
+
+ /// @brief Test that selected shared network can be deleted.
+ void deleteSharedNetwork4Test();
+
+ /// @brief Test that deleteSharedNetwork4 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSharedNetwork4SelectorsTest();
+
+ /// @brief Test that deleteAllSharedNetworks4 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSharedNetworks4SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned shared network.
+ void unassignedSharedNetworkTest();
+
+ /// @brief Test that lifetimes in shared networks are handled as expected.
+ void sharedNetworkLifetimeTest();
+
+ /// @brief Test that deleting a shared network triggers deletion of the options
+ /// associated with the shared network.
+ void sharedNetworkOptionsTest();
+
+ /// @brief Test that option definition can be inserted, fetched, updated and then
+ /// fetched again.
+ void getOptionDef4Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// option definitions by server tag and that the option definition
+ /// specified for the particular server overrides the definition for
+ /// all servers.
+ void optionDefs4WithServerTagsTest();
+
+ /// @brief Test that all option definitions can be fetched.
+ void getAllOptionDefs4Test();
+
+ /// @brief Test that option definitions modified after given time can be fetched.
+ void getModifiedOptionDefs4Test();
+
+ /// @brief This test verifies that global option can be added, updated and deleted.
+ void createUpdateDeleteOption4Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global options by server tag and that the option specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalOptions4WithServerTagsTest();
+
+ /// @brief This test verifies that all global options can be retrieved.
+ void getAllOptions4Test();
+
+ /// @brief This test verifies that modified global options can be retrieved.
+ void getModifiedOptions4Test();
+
+ /// @brief This test verifies that subnet level option can be added, updated and
+ /// deleted.
+ void createUpdateDeleteSubnetOption4Test();
+
+ /// @brief This test verifies that option can be inserted, updated and deleted
+ /// from the pool.
+ void createUpdateDeletePoolOption4Test();
+
+ /// @brief This test verifies that shared network level option can be added,
+ /// updated and deleted.
+ void createUpdateDeleteSharedNetworkOption4Test();
+
+ /// @brief This test verifies that option id values in one subnet do
+ /// not impact options returned in subsequent subnets when
+ /// fetching subnets from the backend.
+ void subnetOptionIdOrderTest();
+
+ /// @brief This test verifies that option id values in one shared network do
+ /// not impact options returned in subsequent shared networks when
+ /// fetching shared networks from the backend.
+ void sharedNetworkOptionIdOrderTest();
+
+ /// @brief This test verifies that it is possible to create client classes, update them
+ /// and retrieve all classes for a given server.
+ void setAndGetAllClientClasses4Test();
+
+ /// @brief This test verifies that a single class can be retrieved from the database.
+ void getClientClass4Test();
+
+ /// @brief This test verifies that client class specific DHCP options can be
+ /// modified during the class update.
+ void createUpdateClientClass4OptionsTest();
+
+ /// @brief This test verifies that modified client classes can be retrieved from the database.
+ void getModifiedClientClasses4Test();
+
+ /// @brief This test verifies that a specified client class can be deleted.
+ void deleteClientClass4Test();
+
+ /// @brief This test verifies that all client classes can be deleted using
+ /// a specified server selector.
+ void deleteAllClientClasses4Test();
+
+ /// @brief This test verifies that client class dependencies are tracked when the
+ /// classes are added to the database. It verifies that an attempt to update
+ /// a class violating the dependencies results in an error.
+ void clientClassDependencies4Test();
+
+ /// @brief This test verifies that audit entries can be retrieved from a given
+ /// timestamp and id including when two entries can get the same timestamp.
+ /// (either it is a common even and this should catch it, or it is a rare
+ /// event and it does not matter).
+ void multipleAuditEntriesTest();
+
+ /// @brief Holds pointers to subnets used in tests.
+ std::vector<Subnet4Ptr> test_subnets_;
+
+ /// @brief Holds pointers to shared networks used in tests.
+ std::vector<SharedNetwork4Ptr> test_networks_;
+
+ /// @brief Holds pointers to option definitions used in tests.
+ std::vector<OptionDefinitionPtr> test_option_defs_;
+
+ /// @brief Holds pointers to options used in tests.
+ std::vector<OptionDescriptorPtr> test_options_;
+
+ /// @brief Holds pointers to classes used in tests.
+ std::vector<ClientClassDefPtr> test_client_classes_;
+
+ /// @brief Holds pointers to the servers used in tests.
+ std::vector<db::ServerPtr> test_servers_;
+
+ /// @brief Holds pointer to the backend.
+ boost::shared_ptr<ConfigBackendDHCPv4> cbptr_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // GENERIC_CONFIG_BACKEND_DHCP4_H
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc
new file mode 100644
index 0000000..5224009
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.cc
@@ -0,0 +1,4772 @@
+// Copyright (C) 2022-2023 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 <asiolink/addr_utilities.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/server.h>
+#include <database/testutils/schema.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <dhcpsrv/pool.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/testutils/generic_cb_dhcp6_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::test;
+namespace ph = std::placeholders;
+
+void
+GenericConfigBackendDHCPv6Test::SetUp() {
+ CfgMgr::instance().setFamily(AF_INET6);
+
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+
+ try {
+ // Create a connection parameter map and use it to start the backend.
+ DatabaseConnection::ParameterMap params =
+ DatabaseConnection::parse(validConnectionString());
+ cbptr_ = backendFactory(params);
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+
+ // Create test data.
+ initTestServers();
+ initTestOptions();
+ initTestSubnets();
+ initTestSharedNetworks();
+ initTestOptionDefs();
+ initTestClientClasses();
+}
+
+void
+GenericConfigBackendDHCPv6Test::TearDown() {
+ cbptr_.reset();
+ // If data wipe enabled, delete transient data otherwise destroy the schema.
+ destroySchema();
+}
+
+db::AuditEntryCollection
+GenericConfigBackendDHCPv6Test::getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const {
+ return (cbptr_->getRecentAuditEntries(server_selector, modification_time, modification_id));
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestServers() {
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1"));
+ test_servers_.push_back(Server::create(ServerTag("server1"), "this is server 1 bis"));
+ test_servers_.push_back(Server::create(ServerTag("server2"), "this is server 2"));
+ test_servers_.push_back(Server::create(ServerTag("server3"), "this is server 3"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestSubnets() {
+ // First subnet includes all parameters.
+ std::string interface_id_text = "whale";
+ OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+ OptionPtr opt_interface_id(new Option(Option::V6, D6O_INTERFACE_ID,
+ interface_id));
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 64,
+ 30, 40, 50, 60, 1024));
+ subnet->allowClientClass("home");
+ subnet->setIface("eth1");
+ subnet->setInterfaceId(opt_interface_id);
+ subnet->setT2(323212);
+ subnet->addRelayAddress(IOAddress("2001:db8:1::2"));
+ subnet->addRelayAddress(IOAddress("2001:db8:3::4"));
+ subnet->setT1(1234);
+ subnet->requireClientClass("required-class1");
+ subnet->requireClientClass("required-class2");
+ subnet->setReservationsGlobal(false);
+ subnet->setReservationsInSubnet(false);
+ subnet->setContext(user_context);
+ subnet->setValid(555555);
+ subnet->setPreferred(4444444);
+ subnet->setCalculateTeeTimes(true);
+ subnet->setT1Percent(0.345);
+ subnet->setT2Percent(0.444);
+ subnet->setDdnsSendUpdates(false);
+ subnet->setAllocatorType("random");
+ subnet->setPdAllocatorType("iterative");
+
+ Pool6Ptr pool1(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"),
+ IOAddress("2001:db8::20")));
+ subnet->addPool(pool1);
+
+ Pool6Ptr pool2(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8::50"),
+ IOAddress("2001:db8::60")));
+ subnet->addPool(pool2);
+
+ Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD,
+ IOAddress("2001:db8:a::"), 48, 64));
+ subnet->addPool(pdpool1);
+
+ Pool6Ptr pdpool2(new Pool6(Lease::TYPE_PD,
+ IOAddress("2001:db8:b::"), 48, 64));
+ subnet->addPool(pdpool2);
+ // Add several options to the subnet.
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->cancelled_,
+ test_options_[1]->space_name_);
+
+ subnet->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->cancelled_,
+ test_options_[2]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Adding another subnet with the same subnet id to test
+ // cases that this second instance can override existing
+ // subnet instance.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 20, 30, 40, 50, 1024));
+
+ pool1.reset(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::10"),
+ IOAddress("2001:db8:1::20")));
+ subnet->addPool(pool1);
+
+ pool1->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->cancelled_,
+ test_options_[3]->space_name_);
+
+ pool1->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->cancelled_,
+ test_options_[4]->space_name_);
+
+ pool2.reset(new Pool6(Lease::TYPE_NA,
+ IOAddress("2001:db8:1::50"),
+ IOAddress("2001:db8:1::60")));
+ subnet->addPool(pool2);
+
+ pool2->allowClientClass("work");
+ pool2->requireClientClass("required-class3");
+ pool2->requireClientClass("required-class4");
+ user_context = Element::createMap();
+ user_context->set("bar", Element::create("foo"));
+ pool2->setContext(user_context);
+ pdpool1.reset(new Pool6(IOAddress("2001:db8:c::"), 48, 64,
+ IOAddress("2001:db8:c::1"), 96));
+ subnet->addPool(pdpool1);
+
+ pdpool1->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->cancelled_,
+ test_options_[3]->space_name_);
+
+ pdpool1->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->cancelled_,
+ test_options_[4]->space_name_);
+
+ // Create the prefix delegation pool with an excluded prefix.
+ pdpool2.reset(new Pool6(IOAddress("2001:db8:d::"), 48, 64,
+ IOAddress("2001:db8:d::2000"), 120));
+
+ subnet->addPool(pdpool2);
+
+ pdpool2->allowClientClass("work");
+ pdpool2->requireClientClass("required-class3");
+ pdpool2->requireClientClass("required-class4");
+ user_context = Element::createMap();
+ user_context->set("bar", Element::create("foo"));
+ pdpool2->setContext(user_context);
+
+ test_subnets_.push_back(subnet);
+
+ subnet.reset(new Subnet6(IOAddress("2001:db8:3::"), 64, 20, 30, 40, 50, 2048));
+ Triplet<uint32_t> null_timer;
+ subnet->setPreferred(null_timer);
+ subnet->setT1(null_timer);
+ subnet->setT2(null_timer);
+ subnet->setValid(null_timer);
+ subnet->setPreferred(null_timer);
+ subnet->setDdnsSendUpdates(true);
+ subnet->setDdnsOverrideNoUpdate(true);
+ subnet->setDdnsOverrideClientUpdate(false);
+ subnet->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ subnet->setDdnsGeneratedPrefix("myhost");
+ subnet->setDdnsQualifyingSuffix("example.org");
+
+ subnet->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_);
+
+ test_subnets_.push_back(subnet);
+
+ // Add a subnet with all defaults.
+ subnet.reset(new Subnet6(IOAddress("2001:db8:4::"), 64,
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ Triplet<uint32_t>(), Triplet<uint32_t>(),
+ 4096));
+ test_subnets_.push_back(subnet);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestSharedNetworks() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ std::string interface_id_text = "fish";
+ OptionBuffer interface_id(interface_id_text.begin(), interface_id_text.end());
+ OptionPtr opt_interface_id(new Option(Option::V6, D6O_INTERFACE_ID, interface_id));
+
+ SharedNetwork6Ptr shared_network(new SharedNetwork6("level1"));
+ shared_network->allowClientClass("foo");
+ shared_network->setIface("eth1");
+ shared_network->setInterfaceId(opt_interface_id);
+ shared_network->setT2(323212);
+ shared_network->addRelayAddress(IOAddress("2001:db8:1::2"));
+ shared_network->addRelayAddress(IOAddress("2001:db8:3::4"));
+ shared_network->setT1(1234);
+ shared_network->requireClientClass("required-class1");
+ shared_network->requireClientClass("required-class2");
+ shared_network->setReservationsGlobal(false);
+ shared_network->setReservationsInSubnet(false);
+ shared_network->setContext(user_context);
+ shared_network->setValid(5555);
+ shared_network->setPreferred(4444);
+ shared_network->setCalculateTeeTimes(true);
+ shared_network->setT1Percent(0.345);
+ shared_network->setT2Percent(0.444);
+ shared_network->setDdnsSendUpdates(false);
+ shared_network->setAllocatorType("iterative");
+ shared_network->setPdAllocatorType("random");
+
+ // Add several options to the shared network.
+ shared_network->getCfgOption()->add(test_options_[2]->option_,
+ test_options_[2]->persistent_,
+ test_options_[2]->cancelled_,
+ test_options_[2]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[3]->option_,
+ test_options_[3]->persistent_,
+ test_options_[3]->cancelled_,
+ test_options_[3]->space_name_);
+
+ shared_network->getCfgOption()->add(test_options_[4]->option_,
+ test_options_[4]->persistent_,
+ test_options_[4]->cancelled_,
+ test_options_[4]->space_name_);
+
+ test_networks_.push_back(shared_network);
+
+ // Adding another shared network called "level1" to test
+ // cases that this second instance can override existing
+ // "level1" instance.
+ shared_network.reset(new SharedNetwork6("level1"));
+ test_networks_.push_back(shared_network);
+
+ // Add more shared networks.
+ shared_network.reset(new SharedNetwork6("level2"));
+ Triplet<uint32_t> null_timer;
+ shared_network->setPreferred(null_timer);
+ shared_network->setT1(null_timer);
+ shared_network->setT2(null_timer);
+ shared_network->setValid(null_timer);
+ shared_network->setPreferred(null_timer);
+ shared_network->setDdnsSendUpdates(true);
+ shared_network->setDdnsOverrideNoUpdate(true);
+ shared_network->setDdnsOverrideClientUpdate(false);
+ shared_network->setDdnsReplaceClientNameMode(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT);
+ shared_network->setDdnsGeneratedPrefix("myhost");
+ shared_network->setDdnsQualifyingSuffix("example.org");
+
+ shared_network->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_);
+ test_networks_.push_back(shared_network);
+
+ shared_network.reset(new SharedNetwork6("level3"));
+ test_networks_.push_back(shared_network);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestOptionDefs() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefinitionPtr option_def(new OptionDefinition("foo", 1234,
+ DHCP6_OPTION_SPACE,
+ "string",
+ "espace"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("bar", 1234, DHCP6_OPTION_SPACE,
+ "uint32", true));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("fish", 5235, DHCP6_OPTION_SPACE,
+ "record", true));
+ option_def->addRecordField("uint32");
+ option_def->addRecordField("string");
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("whale", 20236, "xyz", "string"));
+ test_option_defs_.push_back(option_def);
+
+ option_def.reset(new OptionDefinition("bar", 1234, DHCP6_OPTION_SPACE,
+ "uint64", true));
+ test_option_defs_.push_back(option_def);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestOptions() {
+ ElementPtr user_context = Element::createMap();
+ user_context->set("foo", Element::create("bar"));
+
+ OptionDefSpaceContainer defs;
+
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+ true, false, false, "my-timezone");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint8>(Option::V6, D6O_PREFERENCE,
+ false, false, true, 64);
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionUint32>(Option::V6, 1, false, false, false, 312131),
+ desc.space_name_ = "vendor-encapsulated-options";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option6AddrLst>(1254, true, true, true,
+ "2001:db8::3");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createEmptyOption(Option::V6, 1, true, false);
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createAddressOption<Option6AddrLst>(2, false, false, true,
+ "2001:db8:1::5",
+ "2001:db8:1::3",
+ "2001:db8:3::4");
+ desc.space_name_ = "isc";
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+ true, false, false, "my-timezone-2");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ desc = createOption<OptionString>(Option::V6, D6O_NEW_POSIX_TIMEZONE,
+ true, true, false, "my-timezone-3");
+ desc.space_name_ = DHCP6_OPTION_SPACE;
+ desc.setContext(user_context);
+ test_options_.push_back(OptionDescriptorPtr(new OptionDescriptor(desc)));
+
+ // Add definitions for DHCPv6 non-standard options in case we need to
+ // compare subnets, networks and pools in JSON format. In that case,
+ // the @c toElement functions require option definitions to generate the
+ // proper output.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("vendor-encapsulated-1", 1,
+ "vendor-encapsulated-options",
+ "uint32")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-1254", 1254,
+ DHCP6_OPTION_SPACE,
+ "ipv6-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc",
+ "ipv6-address", true)));
+
+ // Register option definitions.
+ LibDHCP::setRuntimeOptionDefs(defs);
+}
+
+void
+GenericConfigBackendDHCPv6Test::initTestClientClasses() {
+ ExpressionPtr match_expr = boost::make_shared<Expression>();
+ CfgOptionPtr cfg_option = boost::make_shared<CfgOption>();
+ auto class1 = boost::make_shared<ClientClassDef>("foo", match_expr, cfg_option);
+ class1->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class1->setRequired(true);
+ class1->setValid(Triplet<uint32_t>(30, 60, 90));
+ class1->setPreferred(Triplet<uint32_t>(25, 55, 85));
+ test_client_classes_.push_back(class1);
+ ElementPtr user_context = Element::createMap();
+ user_context->set("melon", Element::create("water"));
+ class1->setContext(user_context);
+
+ auto class2 = boost::make_shared<ClientClassDef>("bar", match_expr, cfg_option);
+ class2->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class2->setTest("member('foo')");
+ test_client_classes_.push_back(class2);
+
+ auto class3 = boost::make_shared<ClientClassDef>("foobar", match_expr, cfg_option);
+ class3->setCfgOptionDef(boost::make_shared<CfgOptionDef>());
+ class3->setTest("member('foo') and member('bar')");
+ test_client_classes_.push_back(class3);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getTypeTest(const std::string& expected_type) {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(expected_type, cbptr_->getType());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getHostTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ("localhost", cbptr_->getHost());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getPortTest() {
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["password"] = "keatest";
+ params["user"] = "keatest";
+ ASSERT_NO_THROW_LOG(cbptr_ = backendFactory(params));
+ ASSERT_NE(cbptr_->getParameters(), DatabaseConnection::ParameterMap());
+ EXPECT_EQ(0, cbptr_->getPort());
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteServerTest() {
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ test_servers_[0]->setModificationTime(timestamps_["yesterday"]);
+ test_servers_[1]->setModificationTime(timestamps_["today"]);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // It should not be possible to create a duplicate of the logical
+ // server 'all'.
+ auto all_server = Server::create(ServerTag("all"), "this is logical server all");
+ ASSERT_THROW(cbptr_->createUpdateServer6(all_server), isc::InvalidOperation);
+
+ ServerPtr returned_server;
+
+ // An attempt to fetch the server that hasn't been inserted should return
+ // a null pointer.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server2")));
+ EXPECT_FALSE(returned_server);
+
+ // Try to fetch the server which we expect to exist.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTagAsText());
+ EXPECT_EQ("this is server 1", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["yesterday"], returned_server->getModificationTime());
+
+ // This call is expected to update the existing server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::UPDATE,
+ "server set");
+ }
+
+ // Verify that the server has been updated.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ ASSERT_TRUE(returned_server);
+ EXPECT_EQ("server1", returned_server->getServerTag().get());
+ EXPECT_EQ("this is server 1 bis", returned_server->getDescription());
+ EXPECT_EQ(timestamps_["today"], returned_server->getModificationTime());
+
+ uint64_t servers_deleted = 0;
+
+ // Try to delete non-existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer6(ServerTag("server2")));
+ EXPECT_EQ(0, servers_deleted);
+
+ // Make sure that the server1 wasn't deleted.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ EXPECT_TRUE(returned_server);
+
+ // Deleting logical server 'all' is not allowed.
+ ASSERT_THROW(cbptr_->deleteServer6(ServerTag()), isc::InvalidOperation);
+
+ // Delete the existing server.
+ ASSERT_NO_THROW_LOG(servers_deleted = cbptr_->deleteServer6(ServerTag("server1")));
+ EXPECT_EQ(1, servers_deleted);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server");
+ }
+
+ // Make sure that the server is gone.
+ ASSERT_NO_THROW_LOG(returned_server = cbptr_->getServer6(ServerTag("server1")));
+ EXPECT_FALSE(returned_server);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAndDeleteAllServersTest() {
+ for (auto i = 1; i < test_servers_.size(); ++i) {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[i]));
+ }
+
+ ServerCollection servers;
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers6());
+ ASSERT_EQ(test_servers_.size() - 1, servers.size());
+
+ // All servers should have been returned.
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_TRUE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The logical server all should not be returned. We merely return the
+ // user configured servers.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag()));
+
+ // Delete all servers and make sure they are gone.
+ uint64_t deleted_servers = 0;
+ ASSERT_NO_THROW_LOG(deleted_servers = cbptr_->deleteAllServers6());
+
+ ASSERT_NO_THROW_LOG(servers = cbptr_->getAllServers6());
+ EXPECT_TRUE(servers.empty());
+
+ // All servers should be gone.
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server1")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server2")));
+ EXPECT_FALSE(ServerFetcher::get(servers, ServerTag("server3")));
+
+ // The number of deleted server should be equal to the number of
+ // inserted servers. The logical 'all' server should be excluded.
+ EXPECT_EQ(test_servers_.size() - 1, deleted_servers);
+
+ EXPECT_EQ(1, countRows("dhcp6_server"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteGlobalParameter6Test() {
+ StampedValuePtr global_parameter = StampedValue::create("global", "whale");
+
+ // Explicitly set modification time to make sure that the time
+ // returned from the database is correct.
+ global_parameter->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ global_parameter);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for global parameter");
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set");
+ }
+
+ // Verify returned parameter and the modification time.
+ StampedValuePtr returned_global_parameter =
+ cbptr_->getGlobalParameter6(ServerSelector::ALL(), "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Because we have added the global parameter for all servers, it
+ // should be also returned for the explicitly specified server.
+ returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("whale", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ // Check that the parameter is updated when selector is specified correctly.
+ global_parameter = StampedValue::create("global", "fish");
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ global_parameter);
+ returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(),
+ "global");
+ ASSERT_TRUE(returned_global_parameter);
+ EXPECT_EQ("global", returned_global_parameter->getName());
+ EXPECT_EQ("fish", returned_global_parameter->getValue());
+ EXPECT_TRUE(returned_global_parameter->getModificationTime() ==
+ global_parameter->getModificationTime());
+ ASSERT_EQ(1, returned_global_parameter->getServerTags().size());
+ EXPECT_EQ("all", returned_global_parameter->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the global parameter");
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::UPDATE,
+ "global parameter set");
+ }
+
+ // Should not delete parameter specified for all servers if explicit
+ // server name is provided.
+ EXPECT_EQ(0, cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"),
+ "global"));
+
+ // Delete parameter and make sure it is gone.
+ cbptr_->deleteGlobalParameter6(ServerSelector::ALL(), "global");
+ returned_global_parameter = cbptr_->getGlobalParameter6(ServerSelector::ALL(),
+ "global");
+ EXPECT_FALSE(returned_global_parameter);
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter");
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "global parameter deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::globalParameters6WithServerTagsTest() {
+ // Create three global parameters having the same name.
+ StampedValuePtr global_parameter1 = StampedValue::create("global", "value1");
+ StampedValuePtr global_parameter2 = StampedValue::create("global", "value2");
+ StampedValuePtr global_parameter3 = StampedValue::create("global", "value3");
+
+ // Try to insert one of them and associate with non-existing server.
+ // This should fail because the server must be inserted first.
+ ASSERT_THROW(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"),
+ global_parameter1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time inserting the global parameters for the server1 and server2 should
+ // be successful.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"),
+ global_parameter1));
+ {
+ SCOPED_TRACE("Global parameter for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global value.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server2"),
+ global_parameter2));
+ {
+ SCOPED_TRACE("Global parameter for server2 is set");
+ // Same as in case of the server2, there should be 3 audit entries of
+ // which one we validate.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // The last parameter is associated with all servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ global_parameter3));
+ {
+ SCOPED_TRACE("Global parameter for all servers is set");
+ // There should be one new audit entry for all servers. It indicates
+ // the insertion of the global value.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::CREATE,
+ "global parameter set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ StampedValuePtr returned_global;
+
+ // Try to fetch the value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter6(ServerSelector::ALL(),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Try to fetch the value specified for the server1. This should override the
+ // value specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter6(ServerSelector::ONE("server1"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter1->getValue(), returned_global->getValue());
+
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server1", returned_global->getServerTags().begin()->get());
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_global = cbptr_->getGlobalParameter6(ServerSelector::ONE("server2"),
+ "global")
+ );
+ ASSERT_TRUE(returned_global);
+ EXPECT_EQ(global_parameter2->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("server2", returned_global->getServerTags().begin()->get());
+
+ StampedValueCollection returned_globals;
+
+ // Try to fetch the collection of globals for the server1, server2 and server3.
+ // The server3 does not have an explicit value so for this server we should get
+ /// the value for 'all'.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_globals.size());
+
+ // Capture the returned values into the map so as we can check the
+ // values against the servers.
+ std::map<std::string, std::string> values;
+ for (auto g = returned_globals.begin(); g != returned_globals.end(); ++g) {
+ ASSERT_EQ(1, (*g)->getServerTags().size());
+ values[(*g)->getServerTags().begin()->get()] = ((*g)->getValue());
+ }
+
+ ASSERT_EQ(3, values.size());
+ EXPECT_EQ(global_parameter1->getValue(), values["server1"]);
+ EXPECT_EQ(global_parameter2->getValue(), values["server2"]);
+ EXPECT_EQ(global_parameter3->getValue(), values["all"]);
+
+ // Try to fetch the collection of global parameters specified for all servers.
+ // This excludes the values specific to server1 and server2. It returns only the
+ // common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ALL())
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ // Delete the server1. It should remove associations of this server with the
+ // global parameter and the global parameter itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1"))
+ );
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // As a result, the value fetched for the server1 should be the one available for
+ // all servers, rather than the one dedicated for server1. The association of
+ // the server1 specific value with the server1 should be gone.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after server deletion");
+ // We expect two new audit entries for the server1, one indicating that the
+ // server has been deleted and another one indicating that the corresponding
+ // global value has been deleted. We check the latter entry.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global parameter for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server1"),
+ "global"));
+ // No parameters should be deleted. In particular, the parameter for the logical
+ // server 'all' should not be deleted.
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing value for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteGlobalParameter6(ServerSelector::ONE("server2"),
+ "global"));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create it again to test that deletion of all server removes this too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server2"),
+ global_parameter2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6());
+ ASSERT_NO_THROW_LOG(
+ returned_globals = cbptr_->getAllGlobalParameters6(ServerSelector::ALL())
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_globals.size());
+ returned_global = *returned_globals.begin();
+ // The common value for all servers should still be available because 'all'
+ // logical server should not be deleted.
+ EXPECT_EQ(global_parameter3->getValue(), returned_global->getValue());
+ ASSERT_EQ(1, returned_global->getServerTags().size());
+ EXPECT_EQ("all", returned_global->getServerTags().begin()->get());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global parameter after deletion of"
+ " all servers");
+ // There should be 4 new audit entries. One for deleting the global, one for
+ // re-creating it, one for deleting the server2 and one for deleting the
+ // global again as a result of deleting the server2.
+ testNewAuditEntry("dhcp6_global_parameter",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllGlobalParameters6Test() {
+ // Create 3 parameters and put them into the database.
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name1", "value1"));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name2", Element::create(static_cast<int64_t>(65))));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name3", "value3"));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name4", Element::create(static_cast<bool>(true))));
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ StampedValue::create("name5", Element::create(static_cast<double>(1.65))));
+
+ // Fetch all parameters.
+ auto parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL());
+ ASSERT_EQ(5, parameters.size());
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // Verify their values.
+ EXPECT_EQ("value1", (*parameters_index.find("name1"))->getValue());
+ EXPECT_EQ(65, (*parameters_index.find("name2"))->getIntegerValue());
+ EXPECT_EQ("value3", (*parameters_index.find("name3"))->getValue());
+ EXPECT_TRUE((*parameters_index.find("name4"))->getBoolValue());
+ EXPECT_EQ(1.65, (*parameters_index.find("name5"))->getDoubleValue());
+
+ for (auto param = parameters_index.begin(); param != parameters_index.end();
+ ++param) {
+ ASSERT_EQ(1, (*param)->getServerTags().size());
+ EXPECT_EQ("all", (*param)->getServerTags().begin()->get());
+ }
+
+ // Should be able to fetch these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ONE("server1"));
+ EXPECT_EQ(5, parameters.size());
+
+ // Deleting global parameters with non-matching server selector
+ // should fail.
+ EXPECT_EQ(0, cbptr_->deleteAllGlobalParameters6(ServerSelector::ONE("server1")));
+
+ // Delete all parameters and make sure they are gone.
+ EXPECT_EQ(5, cbptr_->deleteAllGlobalParameters6(ServerSelector::ALL()));
+ parameters = cbptr_->getAllGlobalParameters6(ServerSelector::ALL());
+ EXPECT_TRUE(parameters.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedGlobalParameters6Test() {
+ // Create 3 global parameters and assign modification times:
+ // "yesterday", "today" and "tomorrow" respectively.
+ StampedValuePtr value = StampedValue::create("name1", "value1");
+ value->setModificationTime(timestamps_["yesterday"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name2", Element::create(static_cast<int64_t>(65)));
+ value->setModificationTime(timestamps_["today"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ value);
+
+ value = StampedValue::create("name3", "value3");
+ value->setModificationTime(timestamps_["tomorrow"]);
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ALL(),
+ value);
+
+ // Get parameters modified after "today".
+ auto parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+
+ const auto& parameters_index = parameters.get<StampedValueNameIndexTag>();
+
+ // It should be the one modified "tomorrow".
+ ASSERT_EQ(1, parameters_index.size());
+
+ auto parameter = parameters_index.find("name3");
+ ASSERT_FALSE(parameter == parameters_index.end());
+
+ ASSERT_TRUE(*parameter);
+ EXPECT_EQ("value3", (*parameter)->getValue());
+
+ // Should be able to fetct these parameters when explicitly providing
+ // the server tag.
+ parameters = cbptr_->getModifiedGlobalParameters6(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ EXPECT_EQ(1, parameters.size());
+}
+
+void
+GenericConfigBackendDHCPv6Test::nullKeyErrorTest() {
+ // Create a global parameter (it should work with any object type).
+ StampedValuePtr global_parameter = StampedValue::create("global", "value");
+
+ // Try to insert it and associate with non-existing server.
+ std::string msg;
+ try {
+ cbptr_->createUpdateGlobalParameter6(ServerSelector::ONE("server1"),
+ global_parameter);
+ msg = "got no exception";
+ } catch (const NullKeyError& ex) {
+ msg = ex.what();
+ } catch (const std::exception&) {
+ msg = "got another exception";
+ }
+ EXPECT_EQ("server 'server1' does not exist", msg);
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateSubnet6SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ // Supported selectors.
+ Subnet6Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ subnet));
+ subnet = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"),
+ subnet));
+ subnet = test_subnets_[3];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ANY(), subnet),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::UNASSIGNED(),
+ subnet),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+
+ // An attempt to add a subnet to a non-existing server (server1) should fail.
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet2),
+ NullKeyError);
+
+ // The subnet shouldn't have been added, even though one of the servers exists.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server2"),
+ subnet2->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // Insert two subnets, one for all servers and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("A. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2));
+ {
+ SCOPED_TRACE("B. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ONE("subnet2"),
+ 2, 1);
+ }
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->getID()),
+ isc::InvalidOperation);
+
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet->toText()),
+ isc::InvalidOperation);
+
+ // Test that this subnet will be fetched for various server selectors.
+ auto test_get_subnet = [this, &subnet] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+
+ // Test fetching subnet by id.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(server_selector, subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ ASSERT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Test fetching subnet by prefix.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(server_selector,
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag(expected_tag)));
+
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ subnet = subnet2;
+ {
+ SCOPED_TRACE("testing server selectors for another server");
+ test_get_subnet("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_subnet("any server", ServerSelector::ANY(), "server2");
+ }
+
+ // Update the subnet in the database (both use the same ID).
+ subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+ {
+ SCOPED_TRACE("C. CREATE audit entry for the subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+ }
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_subnet("all servers", ServerSelector::ALL());
+ test_get_subnet("one server", ServerSelector::ONE("server1"));
+ test_get_subnet("any server", ServerSelector::ANY());
+ }
+
+ // The server2 specific subnet should not be returned if the server selector
+ // is not matching.
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ALL(), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ALL(), subnet2->toText()));
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ONE("server1"), subnet2->getID()));
+ EXPECT_FALSE(cbptr_->getSubnet6(ServerSelector::ONE("server1"), subnet2->toText()));
+
+ // Update the subnet in the database (both use the same prefix).
+ subnet2.reset(new Subnet6(IOAddress("2001:db8:3::"),
+ 64, 30, 40, 50, 80, 8192));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2));
+
+ // Fetch again and verify.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server2"), subnet2->toText());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet2->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the subnet when it conflicts same id and same prefix both
+ // with different subnets. This should throw.
+ // Subnets are 2001:db8:1::/48 id 1024 and 2001:db8:3::/64 id 8192
+ subnet2.reset(new Subnet6(IOAddress("2001:db8:1::"),
+ 48, 30, 40, 50, 80, 8192));
+ ASSERT_THROW(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2),
+ DuplicateEntry);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6byIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6WithOptionalUnspecifiedTest() {
+ // Create a subnet and wrap it within a shared network. It is important
+ // to have the shared network to verify that the subnet doesn't inherit
+ // the values of the shared network but stores the NULL values in the
+ // for those parameters that are unspecified on the subnet level.
+ Subnet6Ptr subnet = test_subnets_[2];
+ SharedNetwork6Ptr shared_network = test_networks_[0];
+ shared_network->add(subnet);
+
+ // Need to add the shared network to the database because otherwise
+ // the subnet foreign key would fail.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_TRUE(returned_subnet->getIface().unspecified());
+ EXPECT_TRUE(returned_subnet->getIface().empty());
+
+ EXPECT_TRUE(returned_subnet->getClientClass().unspecified());
+ EXPECT_TRUE(returned_subnet->getClientClass().empty());
+
+ EXPECT_TRUE(returned_subnet->getValid().unspecified());
+ EXPECT_EQ(0, returned_subnet->getValid().get());
+
+ EXPECT_TRUE(returned_subnet->getPreferred().unspecified());
+ EXPECT_EQ(0, returned_subnet->getPreferred().get());
+
+ EXPECT_TRUE(returned_subnet->getT1().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT1().get());
+
+ EXPECT_TRUE(returned_subnet->getT2().unspecified());
+ EXPECT_EQ(0, returned_subnet->getT2().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_subnet->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_subnet->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_subnet->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_subnet->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_subnet->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_subnet->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT1Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_subnet->getT2Percent().get());
+
+ EXPECT_TRUE(returned_subnet->getRapidCommit().unspecified());
+ EXPECT_FALSE(returned_subnet->getRapidCommit().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_subnet->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_subnet->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_subnet->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_subnet->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_subnet->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_subnet->getDdnsQualifyingSuffix().get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6SharedNetworkTest() {
+ Subnet6Ptr subnet = test_subnets_[0];
+ SharedNetwork6Ptr shared_network = test_networks_[0];
+
+ // Add subnet to a shared network.
+ shared_network->add(subnet);
+
+ // Store shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+
+ // Store subnet associated with the shared network in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ // Fetch this subnet by subnet identifier.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned subnet matches the inserted
+ // subnet is to convert both to text.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // However, the check above doesn't verify whether shared network name was
+ // correctly returned from the database.
+ EXPECT_EQ(shared_network->getName(), returned_subnet->getSharedNetworkName());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6ByPrefixTest() {
+ // Insert subnet to the database.
+ Subnet6Ptr subnet = test_subnets_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ // Fetch the subnet by prefix.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ "2001:db8::/64");
+ ASSERT_TRUE(returned_subnet);
+ ASSERT_EQ(1, returned_subnet->getServerTags().size());
+ EXPECT_EQ("all", returned_subnet->getServerTags().begin()->get());
+
+ // Verify subnet contents.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Fetching the subnet for an explicitly specified server tag should
+ // succeed too.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"),
+ "2001:db8::/64");
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSubnet6byPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ANY(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ALL(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/26"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSubnets6Test() {
+ // Insert test subnets into the database. Note that the second subnet will
+ // overwrite the first subnet as they use the same ID.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // That subnet overrides the first subnet so the audit entry should
+ // indicate an update.
+ if (subnet->toText() == "2001:db8:1::/48") {
+ SCOPED_TRACE("UPDATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the subnet " + subnet->toText());
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+ }
+
+ // Fetch all subnets.
+ Subnet6Collection subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // See if the subnets are returned ok.
+ auto subnet_it = subnets.begin();
+ for (auto i = 0; i < subnets.size(); ++i, ++subnet_it) {
+ ASSERT_EQ(1, (*subnet_it)->getServerTags().size());
+ EXPECT_EQ("all", (*subnet_it)->getServerTags().begin()->get());
+ EXPECT_EQ(test_subnets_[i + 1]->toElement()->str(),
+ (*subnet_it)->toElement()->str());
+ }
+
+ // Attempt to remove the non existing subnet should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(), 22));
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ALL(),
+ "2001:db8:555::/64"));
+ // All subnets should be still there.
+ ASSERT_EQ(test_subnets_.size() - 1, subnets.size());
+
+ // Should not delete the subnet for explicit server tag because
+ // our subnet is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"),
+ test_subnets_[1]->getID()));
+
+ // Also, verify that behavior when deleting by prefix.
+ EXPECT_EQ(0, cbptr_->deleteSubnet6(ServerSelector::ONE("server1"),
+ test_subnets_[2]->toText()));
+
+ // Same for all subnets.
+ EXPECT_EQ(0, cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1")));
+
+ // Delete first subnet by id and verify that it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(),
+ test_subnets_[1]->getID()));
+
+ {
+ SCOPED_TRACE("DELETE first subnet audit entry");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 2, subnets.size());
+
+ // Delete second subnet by prefix and verify it is gone.
+ EXPECT_EQ(1, cbptr_->deleteSubnet6(ServerSelector::ALL(),
+ test_subnets_[2]->toText()));
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_EQ(test_subnets_.size() - 3, subnets.size());
+
+ {
+ SCOPED_TRACE("DELETE second subnet audit entry");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "subnet deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(1, cbptr_->deleteAllSubnets6(ServerSelector::ALL()));
+ subnets = cbptr_->getAllSubnets6(ServerSelector::ALL());
+ ASSERT_TRUE(subnets.empty());
+
+ {
+ SCOPED_TRACE("DELETE all subnets audit entry");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSubnets6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSubnets6(ServerSelector::ANY()), isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSubnets6WithServerTagsTest() {
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ subnet1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"),
+ subnet2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3));
+
+ Subnet6Collection subnets;
+
+ // All three subnets are associated with the server1.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, subnets.size());
+
+ // First subnet is associated with all servers.
+ auto returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Second subnet is only associated with the server1.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(2048));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Third subnet is associated with both server1 and server2.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two subnets, i.e. first and last.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, subnets.size());
+
+ // First subnet is associated with all servers.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Last subnet is associated with server1 and server2.
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(4096));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_FALSE(returned_subnet->hasAllServerTag());
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_subnet->hasServerTag(ServerTag("server2")));
+
+ // Only the first subnet is associated with all servers.
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()));
+ EXPECT_EQ(1, subnets.size());
+
+ returned_subnet = SubnetFetcher6::get(subnets, SubnetID(1024));
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_TRUE(returned_subnet->hasAllServerTag());
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_subnet->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSubnets6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ EXPECT_THROW(cbptr_->getModifiedSubnets6(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSubnet6Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto subnet1 = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ auto subnet3 = test_subnets_[3];
+
+ auto create_test_subnets = [&] () {
+ // Insert three subnets, one for all servers, one for server2 and one for two
+ // servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ONE("server2"), subnet2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ subnet3)
+ );
+ };
+
+ create_test_subnets();
+
+ // Test that subnet is not deleted for a specified server selector.
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ subnet1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ subnet2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ subnet3);
+ }
+
+ // Test successful deletion of a subnet by ID.
+ auto test_delete_by_id = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet6(server_selector, subnet->getID()));
+ };
+
+ test_delete_by_id("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_id("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_id("one server", ServerSelector::ONE("server1"), subnet3);
+
+ // Re-create deleted subnets.
+ create_test_subnets();
+
+ // Test successful deletion of a subnet by prefix.
+ auto test_delete_by_prefix = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(server_selector, subnet->toText())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSubnet6(server_selector, subnet->toText()));
+ };
+
+ test_delete_by_prefix("all servers", ServerSelector::ALL(), subnet1);
+ test_delete_by_prefix("any server", ServerSelector::ANY(), subnet2);
+ test_delete_by_prefix("one server", ServerSelector::ONE("server1"), subnet3);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSubnet6ByIdSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ANY(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), SubnetID(1)));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), SubnetID(1)));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ SubnetID(1)),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), SubnetID(1)),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSubnet6ByPrefixSelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ANY(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), "192.0.2.0/26"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ONE("server1"), "192.0.2.0/26"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "192.0.2.0/26"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSubnet6(ServerSelector::UNASSIGNED(), "192.0.2.0/26"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteAllSubnets6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::unassignedSubnet6Test() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Create the subnets and associate them with the server1.
+ auto subnet = test_subnets_[0];
+ auto subnet2 = test_subnets_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), subnet)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSubnet6(ServerSelector::ONE("server1"), subnet2)
+ );
+
+ // Delete the server. The subnets should be preserved but are considered orphaned,
+ // i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer6(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch the subnet by server tag should return no result.
+ Subnet6Ptr returned_subnet;
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"),
+ subnet->getID()));
+ EXPECT_FALSE(returned_subnet);
+
+ // The same if we use other calls.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ONE("server1"),
+ subnet->toText()));
+ EXPECT_FALSE(returned_subnet);
+
+ Subnet6Collection returned_subnets;
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::ONE("server1")));
+ EXPECT_TRUE(returned_subnets.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_subnets.empty());
+
+ // We should get the subnet if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::UNASSIGNED(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Also if we ask for all unassigned subnets it should be returned.
+ ASSERT_NO_THROW_LOG(returned_subnets = cbptr_->getAllSubnets6(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // Same for modified subnets.
+ ASSERT_NO_THROW_LOG(
+ returned_subnets = cbptr_->getModifiedSubnets6(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_subnets.size());
+
+ // If we ask for any subnet by subnet id, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ANY(),
+ subnet->getID()));
+ ASSERT_TRUE(returned_subnet);
+
+ ASSERT_NO_THROW_LOG(returned_subnet = cbptr_->getSubnet6(ServerSelector::ANY(),
+ subnet->toText()));
+ ASSERT_TRUE(returned_subnet);
+
+ // Deleting the subnet with the mismatched server tag should not affect our
+ // subnet.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(ServerSelector::ONE("server1"),
+ subnet->getID())
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all subnets for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets6(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this subnet when we specify ANY and the matching id.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSubnet6(ServerSelector::ANY(), subnet->getID())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all subnets using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSubnets6(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSubnets6Test() {
+ // Explicitly set timestamps of subnets. First subnet has a timestamp
+ // pointing to the future. Second subnet has timestamp pointing to the
+ // past (yesterday). Third subnet has a timestamp pointing to the
+ // past (an hour ago).
+ test_subnets_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_subnets_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_subnets_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert subnets into the database.
+ for (int i = 1; i < test_subnets_.size(); ++i) {
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ test_subnets_[i]);
+ }
+
+ // Fetch subnets with timestamp later than today. Only one subnet
+ // should be returned.
+ Subnet6Collection
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, subnets.size());
+
+ // Fetch subnets with timestamp later than yesterday. We should get
+ // two subnets.
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, subnets.size());
+
+ // Fetch subnets with timestamp later than tomorrow. Nothing should
+ // be returned.
+ subnets = cbptr_->getModifiedSubnets6(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(subnets.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetLifetimeTest() {
+ // Insert new subnet with unspecified valid lifetime
+ Triplet<uint32_t> unspecified;
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 64, 30, 40,
+ unspecified, unspecified, 1111));
+ subnet->setIface("eth1");
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // Verified returned and original subnets match.
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+
+ // Update the preferred and valid lifetime.
+ subnet->setPreferred( Triplet<uint32_t>(100, 200, 300));
+ subnet->setValid( Triplet<uint32_t>(200, 300, 400));
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // Fetch and verify again.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(), subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ EXPECT_EQ(subnet->toElement()->str(), returned_subnet->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetworkSubnets6Test() {
+ // Assign test subnets to shared networks level1 and level2.
+ test_subnets_[1]->setSharedNetworkName("level1");
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_subnets_[3]->setSharedNetworkName("level2");
+
+ // Store shared networks in the database.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+ }
+
+ // Store subnets in the database.
+ for (auto subnet : test_subnets_) {
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+ }
+
+ // Fetch all subnets belonging to shared network level1.
+ Subnet6Collection subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(),
+ "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // All subnets should also be returned for ANY server.
+ subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ANY(), "level1");
+ ASSERT_EQ(1, subnets.size());
+
+ // Returned subnet should match test subnet #1.
+ EXPECT_TRUE(isEquivalent(test_subnets_[1]->toElement(),
+ (*subnets.begin())->toElement()));
+
+ // Check server tag
+ ASSERT_EQ(1, (*subnets.begin())->getServerTags().size());
+ EXPECT_EQ("all", (*subnets.begin())->getServerTags().begin()->get());
+
+ // Fetch all subnets belonging to shared network level2.
+ subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ALL(), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ ElementPtr test_list = Element::createList();
+ test_list->add(test_subnets_[2]->toElement());
+ test_list->add(test_subnets_[3]->toElement());
+
+ ElementPtr returned_list = Element::createList();
+ auto subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+
+ // All subnets should also be returned for explicitly specified server tag.
+ subnets = cbptr_->getSharedNetworkSubnets6(ServerSelector::ONE("server1"), "level2");
+ ASSERT_EQ(2, subnets.size());
+
+ returned_list = Element::createList();
+ subnet = subnets.begin();
+ returned_list->add((*subnet)->toElement());
+ returned_list->add((*++subnet)->toElement());
+
+ EXPECT_TRUE(isEquivalent(returned_list, test_list));
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetUpdatePoolsTest() {
+
+ auto test_subnet_update = [this](const std::string& subnet_prefix,
+ const SubnetID& subnet_id) {
+ // Add the subnet with two address pools and two prefix delegation
+ // pools.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]));
+ // Make sure that the pools have been added to the database.
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+
+ // Create the subnet without options which updates the existing
+ // subnet.
+ Subnet6Ptr subnet(new Subnet6(IOAddress(subnet_prefix), 64, 30, 60, 50, 60,
+ subnet_id));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+ // Check that options are gone.
+ EXPECT_EQ(0, countRows("dhcp6_pool"));
+ EXPECT_EQ(0, countRows("dhcp6_pd_pool"));
+ };
+
+ {
+ SCOPED_TRACE("update subnet, modify subnet id");
+ // Create another subnet with the same prefix as the original subnet but
+ // different id. This is legal to update the subnet id if the prefix is
+ // stable. However, the new subnet has no address pools, so we need to
+ // check of the pools associated with the existing subnet instance are
+ // gone after the update.
+ test_subnet_update("2001:db8::", 2048);
+ }
+
+ {
+ SCOPED_TRACE("update subnet, modify prefix");
+ // Create a subnet with the same subnet id but different prefix.
+ // The prefix should be updated.
+ test_subnet_update("2001:db9::", 1024);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetOptionsTest() {
+ // Add the subnet with two pools and three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(3, countRows("dhcp6_options"));
+
+ // The second subnet uses the same subnet id, so this operation should replace
+ // the existing subnet and its options. The new instance has four pools, each
+ // including one option, so we should end up with four options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Add third subnet with a single option. The number of options in the database
+ // should now be 5.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(5, countRows("dhcp6_options"));
+
+ // Delete the subnet. All options and pools it contains should also be removed, leaving
+ // the last added subnet and its sole option.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[1]->getID()));
+ EXPECT_EQ(1, countRows("dhcp6_subnet"));
+ EXPECT_EQ(0, countRows("dhcp6_pool"));
+ EXPECT_EQ(0, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+
+ // Add the first subnet again. We should now have 4 options: 3 options from the
+ // newly added subnet and one option from the existing subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(2, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Delete the subnet including 3 options. The option from the other subnet should not
+ // be affected.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSubnet6(ServerSelector::ALL(), test_subnets_[0]->getID()));
+ EXPECT_EQ(1, countRows("dhcp6_subnet"));
+ EXPECT_EQ(0, countRows("dhcp6_pool"));
+ EXPECT_EQ(0, countRows("dhcp6_pd_pool"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetwork6Test() {
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+
+ // Insert two shared networks, one for all servers, and one for server2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"),
+ shared_network2));
+
+ // We are not going to support selection of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->getSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[0]->getName()),
+ isc::InvalidOperation);
+
+ // Test that this shared network will be fetched for various server selectors.
+ auto test_get_network = [this, &shared_network] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const std::string& expected_tag = ServerTag::ALL) {
+ SCOPED_TRACE(test_case_name);
+ SharedNetwork6Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork6(server_selector,
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+
+ EXPECT_GT(network->getId(), 0);
+ ASSERT_EQ(1, network->getServerTags().size());
+ EXPECT_EQ(expected_tag, network->getServerTags().begin()->get());
+
+ // The easiest way to verify whether the returned shared network matches the
+ // inserted shared network is to convert both to text.
+ EXPECT_EQ(shared_network->toElement()->str(), network->toElement()->str());
+ };
+
+ {
+ SCOPED_TRACE("testing various server selectors before update");
+ test_get_network("all servers", ServerSelector::ALL());
+ test_get_network("one server", ServerSelector::ONE("server1"));
+ test_get_network("any server", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // Update shared network in the database.
+ shared_network = test_networks_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+
+ {
+ SCOPED_TRACE("testing various server selectors after update");
+ test_get_network("all servers after update", ServerSelector::ALL());
+ test_get_network("one server after update", ServerSelector::ONE("server1"));
+ test_get_network("any server after update", ServerSelector::ANY());
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ // The server2 specific shared network should not be returned if the
+ // server selector is not matching.
+ EXPECT_FALSE(cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network2->getName()));
+ EXPECT_FALSE(cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network2->getName()));
+
+ {
+ SCOPED_TRACE("testing selectors for server2 specific shared network");
+ shared_network = shared_network2;
+ test_get_network("one server", ServerSelector::ONE("server2"), "server2");
+ test_get_network("any server", ServerSelector::ANY(), "server2");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetwork6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateSharedNetwork6Test() {
+ auto shared_network = test_networks_[0];
+
+ // An attempt to insert the shared network for non-existing server should fail.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network),
+ NullKeyError);
+
+ // Insert the server1 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // Insert the server2 into the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+ {
+ SCOPED_TRACE("CREATE audit entry for shared network and ALL servers");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+ {
+ SCOPED_TRACE("UPDATE audit entry for shared network and MULTIPLE servers");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+ }
+
+ SharedNetwork6Ptr network;
+ ASSERT_NO_THROW_LOG(network = cbptr_->getSharedNetwork6(ServerSelector::ANY(),
+ shared_network->getName()));
+ ASSERT_TRUE(network);
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(network->hasServerTag(ServerTag("server2")));
+ EXPECT_FALSE(network->hasServerTag(ServerTag()));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateSharedNetwork6SelectorsTest() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ // Supported selectors.
+ SharedNetwork6Ptr shared_network(new SharedNetwork6("all"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network));
+ shared_network.reset(new SharedNetwork6("one"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network));
+ shared_network.reset(new SharedNetwork6("multiple"));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network));
+
+ // Not supported server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::ANY(), shared_network),
+ isc::InvalidOperation);
+
+ // Not implemented server selectors.
+ ASSERT_THROW(cbptr_->createUpdateSharedNetwork6(ServerSelector::UNASSIGNED(),
+ shared_network),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getSharedNetwork6WithOptionalUnspecifiedTest() {
+ // Insert new shared network.
+ SharedNetwork6Ptr shared_network = test_networks_[2];
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork6Ptr
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[2]->getName());
+ ASSERT_TRUE(returned_network);
+
+ EXPECT_TRUE(returned_network->getIface().unspecified());
+ EXPECT_TRUE(returned_network->getIface().empty());
+
+ EXPECT_TRUE(returned_network->getClientClass().unspecified());
+ EXPECT_TRUE(returned_network->getClientClass().empty());
+
+ EXPECT_TRUE(returned_network->getValid().unspecified());
+ EXPECT_EQ(0, returned_network->getValid().get());
+
+ EXPECT_TRUE(returned_network->getPreferred().unspecified());
+ EXPECT_EQ(0, returned_network->getPreferred().get());
+
+ EXPECT_TRUE(returned_network->getT1().unspecified());
+ EXPECT_EQ(0, returned_network->getT1().get());
+
+ EXPECT_TRUE(returned_network->getT2().unspecified());
+ EXPECT_EQ(0, returned_network->getT2().get());
+
+ EXPECT_TRUE(returned_network->getReservationsGlobal().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsGlobal().get());
+
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().unspecified());
+ EXPECT_TRUE(returned_network->getReservationsInSubnet().get());
+
+ EXPECT_TRUE(returned_network->getReservationsOutOfPool().unspecified());
+ EXPECT_FALSE(returned_network->getReservationsOutOfPool().get());
+
+ EXPECT_TRUE(returned_network->getCalculateTeeTimes().unspecified());
+ EXPECT_FALSE(returned_network->getCalculateTeeTimes().get());
+
+ EXPECT_TRUE(returned_network->getT1Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT1Percent().get());
+
+ EXPECT_TRUE(returned_network->getT2Percent().unspecified());
+ EXPECT_EQ(0.0, returned_network->getT2Percent().get());
+
+ EXPECT_TRUE(returned_network->getRapidCommit().unspecified());
+ EXPECT_FALSE(returned_network->getRapidCommit().get());
+
+ EXPECT_FALSE(returned_network->getDdnsSendUpdates().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsSendUpdates().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideNoUpdate().unspecified());
+ EXPECT_TRUE(returned_network->getDdnsOverrideNoUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().unspecified());
+ EXPECT_FALSE(returned_network->getDdnsOverrideClientUpdate().get());
+
+ EXPECT_FALSE(returned_network->getDdnsReplaceClientNameMode().unspecified());
+ EXPECT_EQ(D2ClientConfig::ReplaceClientNameMode::RCM_WHEN_PRESENT,
+ returned_network->getDdnsReplaceClientNameMode().get());
+
+ EXPECT_FALSE(returned_network->getDdnsGeneratedPrefix().unspecified());
+ EXPECT_EQ("myhost", returned_network->getDdnsGeneratedPrefix().get());
+
+ EXPECT_FALSE(returned_network->getDdnsQualifyingSuffix().unspecified());
+ EXPECT_EQ("example.org", returned_network->getDdnsQualifyingSuffix().get());
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSharedNetworkSubnets6Test() {
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::UNASSIGNED(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ALL(),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteSharedNetworkSubnets6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ test_networks_[1]->getName()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSharedNetworks6Test() {
+ // Insert test shared networks into the database. Note that the second shared
+ // network will overwrite the first shared network as they use the same name.
+ for (auto network : test_networks_) {
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+
+ // That shared network overrides the first one so the audit entry should
+ // indicate an update.
+ if ((network->getName() == "level1") && (!audit_entries_["all"].empty())) {
+ SCOPED_TRACE("UPDATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network set");
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the shared network " +
+ network->getName());
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+ }
+
+ // Fetch all shared networks.
+ SharedNetwork6Collection networks =
+ cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // All shared networks should also be returned for explicitly specified
+ // server tag.
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ EXPECT_EQ(test_networks_[i + 1]->toElement()->str(),
+ networks[i]->toElement()->str());
+ ASSERT_EQ(1, networks[i]->getServerTags().size());
+ EXPECT_EQ("all", networks[i]->getServerTags().begin()->get());
+ }
+
+ // Add some subnets.
+ test_networks_[1]->add(test_subnets_[0]);
+ test_subnets_[2]->setSharedNetworkName("level2");
+ test_networks_[2]->add(test_subnets_[3]);
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]);
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2]);
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[3]);
+
+ // Both ways to attach a subnet are equivalent.
+ Subnet6Ptr subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("level1", subnet->getSharedNetworkName());
+
+ {
+ SCOPED_TRACE("CREATE audit entry for subnets");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set", ServerSelector::ALL(), 3);
+ }
+
+ // Deleting non-existing shared network should return 0.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ "big-fish"));
+ // All shared networks should be still there.
+ ASSERT_EQ(test_networks_.size() - 1, networks.size());
+
+ // Should not delete the shared network for explicit server tag
+ // because our shared network is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"),
+ test_networks_[1]->getName()));
+
+ // Same for all shared networks.
+ EXPECT_EQ(0, cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1")));
+
+ // Delete first shared network with it subnets and verify it is gone.
+ // Begin by its subnet.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetworkSubnets6(ServerSelector::ANY(),
+ test_networks_[1]->getName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for subnets of the first shared network");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all subnets for a shared network");
+ }
+
+ // Check that the subnet is gone..
+ subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[0]->getID());
+ EXPECT_FALSE(subnet);
+
+ // And after the shared network itself.
+ EXPECT_EQ(1, cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_EQ(test_networks_.size() - 2, networks.size());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::DELETE,
+ "shared network deleted");
+ }
+
+ // Delete all.
+ EXPECT_EQ(2, cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL()));
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_TRUE(networks.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the remaining two shared networks");
+ // The last parameter indicates that we expect four new audit entries,
+ // two for deleted shared networks and two for updated subnets
+ std::vector<ExpAuditEntry> exp_entries({
+ {
+ "dhcp6_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ },
+ {
+ "dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE, "deleted all shared networks"
+ },
+ {
+ "dhcp6_shared_network",
+ AuditEntry::ModificationType::DELETE, "deleted all shared networks"
+ }
+ });
+
+ testNewAuditEntry(exp_entries, ServerSelector::ALL());
+ }
+
+ // Check that subnets are still there but detached.
+ subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[2]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+ subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ test_subnets_[3]->getID());
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(subnet->getSharedNetworkName().empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSharedNetworks6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")));
+ ASSERT_NO_THROW_LOG(cbptr_->getAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" })));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getAllSharedNetworks6(ServerSelector::ANY()),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllSharedNetworks6WithServerTagsTest() {
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network1));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"),
+ shared_network2));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3));
+
+ SharedNetwork6Collection networks;
+
+ // All three networks are associated with the server1.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(3, networks.size());
+
+ // First network is associated with all servers.
+ auto returned_network = SharedNetworkFetcher6::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Second network is only associated with the server1.
+ returned_network = SharedNetworkFetcher6::get(networks, "level2");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Third network is associated with both server1 and server2.
+ returned_network = SharedNetworkFetcher6::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // For server2 we should only get two shared networks, i.e. first and last.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(2, networks.size());
+
+ // First shared network is associated with all servers.
+ returned_network = SharedNetworkFetcher6::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Last shared network is associated with server1 and server2.
+ returned_network = SharedNetworkFetcher6::get(networks, "level3");
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->hasAllServerTag());
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_TRUE(returned_network->hasServerTag(ServerTag("server2")));
+
+ // Only the first shared network is associated with all servers.
+ ASSERT_NO_THROW_LOG(networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL()));
+ EXPECT_EQ(1, networks.size());
+
+ returned_network = SharedNetworkFetcher6::get(networks, "level1");
+ ASSERT_TRUE(returned_network);
+ EXPECT_TRUE(returned_network->hasAllServerTag());
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server1")));
+ EXPECT_FALSE(returned_network->hasServerTag(ServerTag("server2")));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSharedNetworks6Test() {
+ // Explicitly set timestamps of shared networks. First shared
+ // network has a timestamp pointing to the future. Second shared
+ // network has timestamp pointing to the past (yesterday).
+ // Third shared network has a timestamp pointing to the
+ // past (an hour ago).
+ test_networks_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_networks_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_networks_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert shared networks into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[i]);
+ }
+
+ // Fetch shared networks with timestamp later than today. Only one
+ // shared network should be returned.
+ SharedNetwork6Collection
+ networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, networks.size());
+
+ // Fetch shared networks with timestamp later than yesterday. We
+ // should get two shared networks.
+ networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, networks.size());
+
+ // Fetch shared networks with timestamp later than tomorrow. Nothing
+ // should be returned.
+ networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(networks.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedSharedNetworks6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::UNASSIGNED(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::ALL(),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]));
+ ASSERT_NO_THROW_LOG(cbptr_->getModifiedSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ timestamps_["yesterday"]));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->getModifiedSharedNetworks6(ServerSelector::ANY(),
+ timestamps_["yesterday"]),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSharedNetwork6Test() {
+ // Create two servers in the database.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("CREATE audit entry for server");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ auto shared_network1 = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ auto shared_network3 = test_networks_[3];
+
+ // Insert three shared networks, one for all servers, one for server2 and
+ // one for two servers: server1 and server2.
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), shared_network1)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server2"), shared_network2)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3)
+ );
+
+ auto test_no_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(0, deleted_count);
+ };
+
+ {
+ SCOPED_TRACE("Test valid but non matching server selectors");
+ test_no_delete("selector: one, actual: all", ServerSelector::ONE("server2"),
+ shared_network1);
+ test_no_delete("selector: all, actual: one", ServerSelector::ALL(),
+ shared_network2);
+ test_no_delete("selector: all, actual: multiple", ServerSelector::ALL(),
+ shared_network3);
+ }
+
+ // We are not going to support deletion of a single entry for multiple servers.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ shared_network3->getName()),
+ isc::InvalidOperation);
+
+ // We currently don't support deleting a shared network with specifying
+ // an unassigned server tag. Use ANY to delete any subnet instead.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::UNASSIGNED(),
+ shared_network1->getName()),
+ isc::NotImplemented);
+
+ // Test successful deletion of a shared network.
+ auto test_delete = [this] (const std::string& test_case_name,
+ const ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) {
+ SCOPED_TRACE(test_case_name);
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(server_selector,
+ shared_network->getName())
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ EXPECT_FALSE(cbptr_->getSharedNetwork6(server_selector,
+ shared_network->getName()));
+ };
+
+ test_delete("all servers", ServerSelector::ALL(), shared_network1);
+ test_delete("any server", ServerSelector::ANY(), shared_network2);
+ test_delete("one server", ServerSelector::ONE("server1"), shared_network3);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteSharedNetwork6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(), "level1"));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"), "level1"));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::MULTIPLE({ "server1", "server2" }),
+ "level1"),
+ isc::InvalidOperation);
+
+ // Not implemented selectors.
+ ASSERT_THROW(cbptr_->deleteSharedNetwork6(ServerSelector::UNASSIGNED(), "level1"),
+ isc::NotImplemented);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteAllSharedNetworks6SelectorsTest() {
+ // Supported selectors.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::ALL()));
+ ASSERT_NO_THROW_LOG(cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1")));
+
+ // Not supported selectors.
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::ANY()),
+ isc::InvalidOperation);
+ ASSERT_THROW(cbptr_->deleteAllSharedNetworks6(ServerSelector::MULTIPLE({ "server1", "server2" })),
+ isc::InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::unassignedSharedNetworkTest() {
+ // Create the server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Create the shared networks and associate them with the server1.
+ auto shared_network = test_networks_[0];
+ auto shared_network2 = test_networks_[2];
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), shared_network)
+ );
+ ASSERT_NO_THROW_LOG(
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ONE("server1"), shared_network2)
+ );
+
+ // Delete the server. The shared networks should be preserved but are
+ // considered orphaned, i.e. do not belong to any server.
+ uint64_t deleted_count = 0;
+ ASSERT_NO_THROW_LOG(deleted_count = cbptr_->deleteServer6(ServerTag("server1")));
+ EXPECT_EQ(1, deleted_count);
+
+ // Trying to fetch this shared network by server tag should return no result.
+ SharedNetwork6Ptr returned_network;
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::ONE("server1"),
+ "level1"));
+ EXPECT_FALSE(returned_network);
+
+ // The same if we use other calls.
+ SharedNetwork6Collection returned_networks;
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::ONE("server1"))
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"])
+ );
+ EXPECT_TRUE(returned_networks.empty());
+
+ // We should get the shared network if we ask for unassigned.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::UNASSIGNED(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Also if we ask for all unassigned networks it should be returned.
+ ASSERT_NO_THROW_LOG(returned_networks = cbptr_->getAllSharedNetworks6(ServerSelector::UNASSIGNED()));
+ ASSERT_EQ(2, returned_networks.size());
+
+ // And all modified.
+ ASSERT_NO_THROW_LOG(
+ returned_networks = cbptr_->getModifiedSharedNetworks6(ServerSelector::UNASSIGNED(),
+ timestamps_["two days ago"])
+ );
+ ASSERT_EQ(2, returned_networks.size());
+
+ // If we ask for any network by name, it should be returned too.
+ ASSERT_NO_THROW_LOG(returned_network = cbptr_->getSharedNetwork6(ServerSelector::ANY(),
+ "level1"));
+ ASSERT_TRUE(returned_network);
+
+ // Deleting a shared network with the mismatched server tag should not affect
+ // our shared network.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ONE("server1"),
+ "level1")
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // Also, if we delete all shared networks for server1.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks6(ServerSelector::ONE("server1"))
+ );
+ EXPECT_EQ(0, deleted_count);
+
+ // We can delete this shared network when we specify ANY and the matching name.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteSharedNetwork6(ServerSelector::ANY(), "level1")
+ );
+ EXPECT_EQ(1, deleted_count);
+
+ // We can delete all networks using UNASSIGNED selector.
+ ASSERT_NO_THROW_LOG(
+ deleted_count = cbptr_->deleteAllSharedNetworks6(ServerSelector::UNASSIGNED());
+ );
+ EXPECT_EQ(1, deleted_count);
+}
+
+void
+GenericConfigBackendDHCPv6Test::sharedNetworkLifetimeTest() {
+ // Insert new shared network with unspecified valid lifetime
+ SharedNetwork6Ptr network(new SharedNetwork6("foo"));
+ Triplet<uint32_t> unspecified;
+ network->setPreferred(unspecified);
+ network->setValid(unspecified);
+ network->setIface("eth1");
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+
+ // Fetch this shared network.
+ SharedNetwork6Ptr returned_network =
+ cbptr_->getSharedNetwork6(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+
+ // Verified returned and original shared networks match.
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+
+ // Update the preferred and valid lifetime.
+ network->setPreferred( Triplet<uint32_t>(100, 200, 300));
+ network->setValid( Triplet<uint32_t>(200, 300, 400));
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), network);
+
+ // Fetch and verify again.
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(), "foo");
+ ASSERT_TRUE(returned_network);
+ EXPECT_EQ(network->toElement()->str(),
+ returned_network->toElement()->str());
+}
+
+void
+GenericConfigBackendDHCPv6Test::sharedNetworkOptionsTest() {
+ // Add shared network with three options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(3, countRows("dhcp6_options"));
+
+ // Add another shared network with a single option. The numnber of options in the
+ // database should now be 4.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[2]));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // The second shared network uses the same name as the first shared network, so
+ // this operation should replace the existing shared network and its options.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[1]));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+
+ // Remove the shared network. This should not affect options assigned to the
+ // other shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[1]->getName()));
+ EXPECT_EQ(1, countRows("dhcp6_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+
+ // Create the first option again. The number of options should be equal to the
+ // sum of options associated with both shared networks.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(), test_networks_[0]));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Delete this shared network. This should not affect the option associated
+ // with the remaining shared network.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteSharedNetwork6(ServerSelector::ALL(),
+ test_networks_[0]->getName()));
+ EXPECT_EQ(1, countRows("dhcp6_shared_network"));
+ EXPECT_EQ(1, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::getOptionDef6Test() {
+ // Insert new option definition.
+ OptionDefinitionPtr option_def = test_option_defs_[0];
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def);
+
+ // Fetch this option_definition by subnet identifier.
+ OptionDefinitionPtr returned_option_def =
+ cbptr_->getOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[0]->getCode(),
+ test_option_defs_[0]->getOptionSpaceName());
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_GT(returned_option_def->getId(), 0);
+ ASSERT_EQ(1, returned_option_def->getServerTags().size());
+ EXPECT_EQ("all", returned_option_def->getServerTags().begin()->get());
+
+ EXPECT_TRUE(returned_option_def->equals(*option_def));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option definition");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+
+ // Update the option definition in the database.
+ OptionDefinitionPtr option_def2 = test_option_defs_[1];
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def2);
+
+ // Fetch updated option definition and see if it matches.
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ // Fetching option definition for an explicitly specified server tag
+ // should succeed too.
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName());
+ EXPECT_TRUE(returned_option_def->equals(*option_def2));
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option definition");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::optionDefs6WithServerTagsTest() {
+ OptionDefinitionPtr option1 = test_option_defs_[0];
+ OptionDefinitionPtr option2 = test_option_defs_[1];
+ OptionDefinitionPtr option3 = test_option_defs_[4];
+
+ // An attempt to create option definition for non-existing server should
+ // fail.
+ ASSERT_THROW(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server1"),
+ option1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ // This time creation of the option definition for the server1 should pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server1"),
+ option1));
+ {
+ SCOPED_TRACE("option definition for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the option definition.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+ }
+
+ // Creation of the option definition for the server2 should also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server2"),
+ option2));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+ }
+
+ // Finally, creation of the option definition for all servers should
+ // also pass.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ALL(),
+ option3));
+ {
+ SCOPED_TRACE("option definition for server2 is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the option definition.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set",
+ ServerSelector::ALL(),
+ 1, 1);
+ }
+
+ OptionDefinitionPtr returned_option_def;
+
+ // Try to fetch the option definition specified for all servers. It should
+ // return the third one.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ALL(),
+ option3->getCode(),
+ option3->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option3));
+
+ // Try to fetch the option definition specified for server1. It should
+ // override the definition for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option1));
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option_def = cbptr_->getOptionDef6(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName())
+ );
+ ASSERT_TRUE(returned_option_def);
+ EXPECT_TRUE(returned_option_def->equals(*option2));
+
+ OptionDefContainer returned_option_defs;
+
+ // Try to fetch the collection of the option definitions for server1, server2
+ // and server3. The server3 does not have an explicit option definition, so
+ // for this server we should get the definition associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_option_defs.size());
+
+ // Check that expected option definitions have been returned.
+ auto current_option = returned_option_defs.begin();
+ EXPECT_TRUE((*current_option)->equals(*option1));
+ EXPECT_TRUE((*(++current_option))->equals(*option2));
+ EXPECT_TRUE((*(++current_option))->equals(*option3));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ // Delete the server1. It should remove associations of this server with the
+ // option definitions and the option definition itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ONE("server1"));
+
+ );
+ ASSERT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after server deletion");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete option definition for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef6(ServerSelector::ONE("server1"),
+ option1->getCode(),
+ option1->getOptionSpaceName()));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option definition for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOptionDef6(ServerSelector::ONE("server2"),
+ option2->getCode(),
+ option2->getOptionSpaceName()));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option definition again to test that deletion of all servers
+ // removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOptionDef6(ServerSelector::ONE("server2"),
+ option2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6());
+ ASSERT_NO_THROW_LOG(
+ returned_option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ EXPECT_EQ(1, returned_option_defs.size());
+ EXPECT_TRUE((*returned_option_defs.begin())->equals(*option3));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the option definition after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllOptionDefs6Test() {
+ // Insert test option definitions into the database. Note that the second
+ // option definition will overwrite the first option definition as they use
+ // the same code and space.
+ size_t updates_num = 0;
+ for (auto option_def : test_option_defs_) {
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(), option_def);
+
+ // That option definition overrides the first one so the audit entry should
+ // indicate an update.
+ auto name = option_def->getName();
+ if (name.find("bar") != std::string::npos) {
+ SCOPED_TRACE("UPDATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::UPDATE,
+ "option definition set");
+ ++updates_num;
+
+ } else {
+ SCOPED_TRACE("CREATE audit entry for the option definition " + name);
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::CREATE,
+ "option definition set");
+ }
+ }
+
+ // Fetch all option_definitions.
+ OptionDefContainer option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // All option definitions should also be returned for explicitly specified
+ // server tag.
+ option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // See if option definitions are returned ok.
+ for (auto def = option_defs.begin(); def != option_defs.end(); ++def) {
+ ASSERT_EQ(1, (*def)->getServerTags().size());
+ EXPECT_EQ("all", (*def)->getServerTags().begin()->get());
+ bool success = false;
+ for (auto i = 1; i < test_option_defs_.size(); ++i) {
+ if ((*def)->equals(*test_option_defs_[i])) {
+ success = true;
+ }
+ }
+ ASSERT_TRUE(success) << "failed for option definition " << (*def)->getCode()
+ << ", option space " << (*def)->getOptionSpaceName();
+ }
+
+ // Deleting non-existing option definition should return 0.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ALL(),
+ 99, "non-exiting-space"));
+ // All option definitions should be still there.
+ ASSERT_EQ(test_option_defs_.size() - updates_num, option_defs.size());
+
+ // Should not delete option definition for explicit server tag
+ // because our option definition is for all servers.
+ EXPECT_EQ(0, cbptr_->deleteOptionDef6(ServerSelector::ONE("server1"),
+ test_option_defs_[1]->getCode(),
+ test_option_defs_[1]->getOptionSpaceName()));
+
+ // Same for all option definitions.
+ EXPECT_EQ(0, cbptr_->deleteAllOptionDefs6(ServerSelector::ONE("server1")));
+
+ // Delete one of the option definitions and see if it is gone.
+ EXPECT_EQ(1, cbptr_->deleteOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+ ASSERT_FALSE(cbptr_->getOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[2]->getCode(),
+ test_option_defs_[2]->getOptionSpaceName()));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the first option definition");
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "option definition deleted");
+ }
+
+ // Delete all remaining option definitions.
+ EXPECT_EQ(2, cbptr_->deleteAllOptionDefs6(ServerSelector::ALL()));
+ option_defs = cbptr_->getAllOptionDefs6(ServerSelector::ALL());
+ ASSERT_TRUE(option_defs.empty());
+
+ {
+ SCOPED_TRACE("DELETE audit entries for the remaining option definitions");
+ // The last parameter indicates that we expect two new audit entries.
+ testNewAuditEntry("dhcp6_option_def",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all option definitions",
+ ServerSelector::ALL(), 2);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedOptionDefs6Test() {
+ // Explicitly set timestamps of option definitions. First option
+ // definition has a timestamp pointing to the future. Second option
+ // definition has timestamp pointing to the past (yesterday).
+ // Third option definitions has a timestamp pointing to the
+ // past (an hour ago).
+ test_option_defs_[1]->setModificationTime(timestamps_["tomorrow"]);
+ test_option_defs_[2]->setModificationTime(timestamps_["yesterday"]);
+ test_option_defs_[3]->setModificationTime(timestamps_["today"]);
+
+ // Insert option definitions into the database.
+ for (int i = 1; i < test_networks_.size(); ++i) {
+ cbptr_->createUpdateOptionDef6(ServerSelector::ALL(),
+ test_option_defs_[i]);
+ }
+
+ // Fetch option definitions with timestamp later than today. Only one
+ // option definition should be returned.
+ OptionDefContainer
+ option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, option_defs.size());
+
+ // Fetch option definitions with timestamp later than yesterday. We
+ // should get two option definitions.
+ option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(),
+ timestamps_["after yesterday"]);
+ ASSERT_EQ(2, option_defs.size());
+
+ // Fetch option definitions with timestamp later than tomorrow. Nothing
+ // should be returned.
+ option_defs = cbptr_->getModifiedOptionDefs6(ServerSelector::ALL(),
+ timestamps_["after tomorrow"]);
+ ASSERT_TRUE(option_defs.empty());
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteOption6Test() {
+ // Add option to the database.
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ opt_posix_timezone);
+
+ // Make sure we can retrieve this option and that it is equal to the
+ // option we have inserted into the database.
+ OptionDescriptorPtr returned_opt_posix_timezone =
+ cbptr_->getOption6(ServerSelector::ALL(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_);
+ ASSERT_TRUE(returned_opt_posix_timezone);
+
+ {
+ SCOPED_TRACE("verify created option");
+ testOptionsEquivalent(*opt_posix_timezone,
+ *returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("CREATE audit entry for an option");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set");
+ }
+
+ // Modify option and update it in the database.
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ opt_posix_timezone->cancelled_ = !opt_posix_timezone->cancelled_;
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ opt_posix_timezone);
+
+ // Retrieve the option again and make sure that updates were
+ // properly propagated to the database. Use explicit server selector
+ // which should also return this option.
+ returned_opt_posix_timezone = cbptr_->getOption6(ServerSelector::ONE("server1"),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_);
+ ASSERT_TRUE(returned_opt_posix_timezone);
+
+ {
+ SCOPED_TRACE("verify updated option");
+ testOptionsEquivalent(*opt_posix_timezone,
+ *returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an option");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::UPDATE,
+ "global option set");
+ }
+
+ // Deleting an option with explicitly specified server tag should fail.
+ EXPECT_EQ(0, cbptr_->deleteOption6(ServerSelector::ONE("server1"),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ // Deleting option for all servers should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ALL(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ EXPECT_FALSE(cbptr_->getOption6(ServerSelector::ALL(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ {
+ SCOPED_TRACE("DELETE audit entry for an option");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::DELETE,
+ "global option deleted");
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::globalOptions6WithServerTagsTest() {
+ OptionDescriptorPtr opt_timezone1 = test_options_[0];
+ OptionDescriptorPtr opt_timezone2 = test_options_[6];
+ OptionDescriptorPtr opt_timezone3 = test_options_[7];
+
+ ASSERT_THROW(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"),
+ opt_timezone1),
+ NullKeyError);
+
+ // Create two servers.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server2 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set");
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server1"),
+ opt_timezone1));
+ {
+ SCOPED_TRACE("global option for server1 is set");
+ // The value of 3 means there should be 3 audit entries available for the
+ // server1, two that indicate creation of the servers and one that we
+ // validate, which sets the global option.
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server1"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"),
+ opt_timezone2));
+ {
+ SCOPED_TRACE("global option for server2 is set");
+ // Same as in case of the server1, there should be 3 audit entries and
+ // we validate one of them.
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ONE("server2"),
+ 3, 1);
+
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ opt_timezone3));
+ {
+ SCOPED_TRACE("global option for all servers is set");
+ // There should be one new audit entry for all servers. It logs
+ // the insertion of the global option.
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::CREATE,
+ "global option set",
+ ServerSelector::ALL(),
+ 1, 1);
+
+ }
+
+ OptionDescriptorPtr returned_option;
+
+ // Try to fetch the option specified for all servers. It should return
+ // the third option.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption6(ServerSelector::ALL(),
+ opt_timezone3->option_->getType(),
+ opt_timezone3->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_timezone3, *returned_option);
+
+ // Try to fetch the option specified for the server1. It should override the
+ // option specified for all servers.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption6(ServerSelector::ONE("server1"),
+ opt_timezone1->option_->getType(),
+ opt_timezone1->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_timezone1, *returned_option);
+
+ // The same in case of the server2.
+ ASSERT_NO_THROW_LOG(
+ returned_option = cbptr_->getOption6(ServerSelector::ONE("server2"),
+ opt_timezone2->option_->getType(),
+ opt_timezone2->space_name_);
+ );
+ ASSERT_TRUE(returned_option);
+ testOptionsEquivalent(*opt_timezone2, *returned_option);
+
+ OptionContainer returned_options;
+
+ // Try to fetch the collection of global options for the server1, server2
+ // and server3. The server3 does not have an explicit value so for this server
+ // we should get the option associated with "all" servers.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::
+ MULTIPLE({ "server1", "server2",
+ "server3" }));
+ );
+ ASSERT_EQ(3, returned_options.size());
+
+ // Check that expected options have been returned.
+ auto current_option = returned_options.begin();
+ testOptionsEquivalent(*opt_timezone1, *current_option);
+ testOptionsEquivalent(*opt_timezone2, *(++current_option));
+ testOptionsEquivalent(*opt_timezone3, *(++current_option));
+
+ // Try to fetch the collection of options specified for all servers.
+ // This excludes the options specific to server1 and server2. It returns
+ // only the common ones.
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+ // Delete the server1. It should remove associations of this server with the
+ // option and the option itself.
+ ASSERT_NO_THROW_LOG(cbptr_->deleteServer6(ServerTag("server1")));
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1"));
+ );
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after server deletion");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting a server", ServerSelector::ONE("server1"),
+ 2, 1);
+ }
+
+ // Attempt to delete global option for server1.
+ uint64_t deleted_num = 0;
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server1"),
+ opt_timezone1->option_->getType(),
+ opt_timezone1->space_name_));
+ EXPECT_EQ(0, deleted_num);
+
+ // Deleting the existing option for server2 should succeed.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteOption6(ServerSelector::ONE("server2"),
+ opt_timezone2->option_->getType(),
+ opt_timezone2->space_name_));
+ EXPECT_EQ(1, deleted_num);
+
+ // Create this option again to test that deletion of all servers removes it too.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ONE("server2"),
+ opt_timezone2));
+
+ // Delete all servers, except 'all'.
+ ASSERT_NO_THROW_LOG(deleted_num = cbptr_->deleteAllServers6());
+ ASSERT_NO_THROW_LOG(
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+ );
+ EXPECT_EQ(1, deleted_num);
+ ASSERT_EQ(1, returned_options.size());
+ testOptionsEquivalent(*opt_timezone3, *returned_options.begin());
+
+ {
+ SCOPED_TRACE("DELETE audit entry for the global option after deletion of"
+ " all servers");
+ testNewAuditEntry("dhcp6_options",
+ AuditEntry::ModificationType::DELETE,
+ "deleting all servers", ServerSelector::ONE("server2"),
+ 4, 1);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getAllOptions6Test() {
+ // Add three global options to the database.
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Retrieve all these options.
+ OptionContainer returned_options = cbptr_->getAllOptions6(ServerSelector::ALL());
+ ASSERT_EQ(3, returned_options.size());
+
+ // Fetching global options with explicitly specified server tag should return
+ // the same result.
+ returned_options = cbptr_->getAllOptions6(ServerSelector::ONE("server1"));
+ ASSERT_EQ(3, returned_options.size());
+
+ // Get the container index used to search options by option code.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+
+ // Verify that all options we put into the database were
+ // returned.
+ {
+ SCOPED_TRACE("verify test_options_[0]");
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ testOptionsEquivalent(*test_options_[0], *option0);
+ EXPECT_GT(option0->getId(), 0);
+ ASSERT_EQ(1, option0->getServerTags().size());
+ EXPECT_EQ("all", option0->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[1]");
+ auto option1 = index.find(test_options_[1]->option_->getType());
+ ASSERT_FALSE(option1 == index.end());
+ testOptionsEquivalent(*test_options_[1], *option1);
+ EXPECT_GT(option1->getId(), 0);
+ ASSERT_EQ(1, option1->getServerTags().size());
+ EXPECT_EQ("all", option1->getServerTags().begin()->get());
+ }
+
+ {
+ SCOPED_TRACE("verify test_options_[5]");
+ auto option5 = index.find(test_options_[5]->option_->getType());
+ ASSERT_FALSE(option5 == index.end());
+ testOptionsEquivalent(*test_options_[5], *option5);
+ EXPECT_GT(option5->getId(), 0);
+ ASSERT_EQ(1, option5->getServerTags().size());
+ EXPECT_EQ("all", option5->getServerTags().begin()->get());
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedOptions6Test() {
+ // Assign timestamps to the options we're going to store in the
+ // database.
+ test_options_[0]->setModificationTime(timestamps_["tomorrow"]);
+ test_options_[1]->setModificationTime(timestamps_["yesterday"]);
+ test_options_[5]->setModificationTime(timestamps_["today"]);
+
+ // Put options into the database.
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[0]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[1]);
+ cbptr_->createUpdateOption6(ServerSelector::ALL(),
+ test_options_[5]);
+
+ // Get options with the timestamp later than today. Only
+ // one option should be returned.
+ OptionContainer returned_options =
+ cbptr_->getModifiedOptions6(ServerSelector::ALL(),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // Fetching modified options with explicitly specified server selector
+ // should return the same result.
+ returned_options = cbptr_->getModifiedOptions6(ServerSelector::ONE("server1"),
+ timestamps_["after today"]);
+ ASSERT_EQ(1, returned_options.size());
+
+ // The returned option should be the one with the timestamp
+ // set to tomorrow.
+ const OptionContainerTypeIndex& index = returned_options.get<1>();
+ auto option0 = index.find(test_options_[0]->option_->getType());
+ ASSERT_FALSE(option0 == index.end());
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*test_options_[0], *option0);
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteSubnetOption6Test() {
+ // Insert new subnet.
+ Subnet6Ptr subnet = test_subnets_[1];
+ cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet);
+
+ // Fetch this subnet by subnet identifier.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a new subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // The inserted subnet contains four options.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ cbptr_->createUpdateOption6(ServerSelector::ANY(), subnet->getID(),
+ opt_posix_timezone);
+
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an added subnet option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire subnet so as the server refreshes the
+ // subnet with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // We have added one option to the existing subnet. We should now have
+ // five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ opt_posix_timezone->cancelled_ = !opt_posix_timezone->cancelled_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(), subnet->getID(),
+ opt_posix_timezone);
+
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ returned_opt_posix_timezone =
+ returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for an updated subnet option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option set");
+ }
+
+ // Updating the option should replace the existing instance with the new
+ // instance. Therefore, we should still have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // It should succeed for any server.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(), subnet->getID(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ EXPECT_FALSE(returned_subnet->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a deleted subnet option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "subnet specific option deleted");
+ }
+
+ // We should have only four options after deleting one of them.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeletePoolOption6Test() {
+ // Insert new subnet.
+ Subnet6Ptr subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // Inserted subnet has four options.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+
+ // Add an option into the pool.
+ const PoolPtr pool = subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(pool);
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_posix_timezone));
+
+ // Query for a subnet.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // The returned subnet should include our pool.
+ const PoolPtr returned_pool = returned_subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(returned_pool);
+
+ // The pool should contain option we added earlier.
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned pool option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option "
+ "to the address pool");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "address pool specific option set");
+ }
+
+ // With the newly inserted option we should now have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Modify the option and update it in the database.
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ opt_posix_timezone->cancelled_ = !opt_posix_timezone->cancelled_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_posix_timezone);
+
+ // Fetch the subnet and the corresponding pool.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool1 = returned_subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(returned_pool1);
+
+ // Test that the option has been correctly updated in the database.
+ returned_opt_posix_timezone =
+ returned_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when updating "
+ "address pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "address pool specific option set");
+ }
+
+ // The new option instance should replace the existing one, so we should
+ // still have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Delete option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(),
+ pool->getFirstAddress(),
+ pool->getLastAddress(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ // Fetch the subnet and the pool from the database again to make sure
+ // that the option is really gone.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pool2 = returned_subnet->getPool(Lease::TYPE_NA,
+ IOAddress("2001:db8::10"));
+ ASSERT_TRUE(returned_pool2);
+
+ // Option should be gone.
+ EXPECT_FALSE(returned_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when deleting "
+ "address pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "address pool specific option deleted");
+ }
+
+ // The option has been deleted so the number of options should now
+ // be down to 4.
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeletePdPoolOption6Test() {
+ // Insert new subnet.
+ Subnet6Ptr subnet = test_subnets_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), subnet));
+
+ {
+ SCOPED_TRACE("CREATE audit entry for a subnet");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::CREATE,
+ "subnet set");
+ }
+
+ // Inserted subnet has four options.
+ ASSERT_EQ(4, countRows("dhcp6_options"));
+
+ // Add an option into the pd pool.
+ const PoolPtr pd_pool = subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(pd_pool);
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ int pd_pool_len = prefixLengthFromRange(pd_pool->getFirstAddress(),
+ pd_pool->getLastAddress());
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pd_pool->getFirstAddress(),
+ static_cast<uint8_t>(pd_pool_len),
+ opt_posix_timezone));
+
+ // Query for a subnet.
+ Subnet6Ptr returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+
+ // The returned subnet should include our pool.
+ const PoolPtr returned_pd_pool = returned_subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(returned_pd_pool);
+
+ // The pd pool should contain option we added earlier.
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_pd_pool->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned pool option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet after adding an option "
+ "to the prefix delegation pool");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "prefix delegation pool specific option set");
+ }
+
+ // With the newly inserted option we should now have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Modify the option and update it in the database.
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ opt_posix_timezone->cancelled_ = !opt_posix_timezone->cancelled_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ pd_pool->getFirstAddress(),
+ static_cast<uint8_t>(pd_pool_len),
+ opt_posix_timezone);
+
+ // Fetch the subnet and the corresponding pd pool.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pd_pool1 = returned_subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(returned_pd_pool1);
+
+ // Test that the option has been correctly updated in the database.
+ returned_opt_posix_timezone =
+ returned_pd_pool1->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when updating "
+ "prefix delegation pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "prefix delegation pool specific option set");
+ }
+
+ // The new option instance should replace the existing one, so we should
+ // still have five options.
+ ASSERT_EQ(5, countRows("dhcp6_options"));
+
+ // Delete option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(),
+ pd_pool->getFirstAddress(),
+ static_cast<uint8_t>(pd_pool_len),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+
+ // Fetch the subnet and the pool from the database again to make sure
+ // that the option is really gone.
+ returned_subnet = cbptr_->getSubnet6(ServerSelector::ALL(),
+ subnet->getID());
+ ASSERT_TRUE(returned_subnet);
+ const PoolPtr returned_pd_pool2 = returned_subnet->getPool(Lease::TYPE_PD,
+ IOAddress("2001:db8:a:10::"));
+ ASSERT_TRUE(returned_pd_pool2);
+
+ // Option should be gone.
+ EXPECT_FALSE(returned_pd_pool2->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for a subnet when deleting "
+ "prefix delegation pool specific option");
+ testNewAuditEntry("dhcp6_subnet",
+ AuditEntry::ModificationType::UPDATE,
+ "prefix delegation pool specific option deleted");
+ }
+
+ // The option has been deleted so the number of options should now
+ // be down to 4.
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateDeleteSharedNetworkOption6Test() {
+ // Insert new shared network.
+ SharedNetwork6Ptr shared_network = test_networks_[1];
+ cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ shared_network);
+
+ // Fetch this shared network by name.
+ SharedNetwork6Ptr returned_network =
+ cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ {
+ SCOPED_TRACE("CREATE audit entry for the new shared network");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::CREATE,
+ "shared network set");
+ }
+
+ // The inserted shared network has no options.
+ ASSERT_EQ(0, countRows("dhcp6_options"));
+
+ OptionDescriptorPtr opt_posix_timezone = test_options_[0];
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_posix_timezone);
+
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+
+ OptionDescriptor returned_opt_posix_timezone =
+ returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify returned option");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ EXPECT_GT(returned_opt_posix_timezone.getId(), 0);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the added shared network option");
+ // Instead of adding an audit entry for an option we add an audit
+ // entry for the entire shared network so as the server refreshes the
+ // shared network with the new option. Note that the server doesn't
+ // have means to retrieve only the newly added option.
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // One option should now be stored in the database.
+ ASSERT_EQ(1, countRows("dhcp6_options"));
+
+ opt_posix_timezone->persistent_ = !opt_posix_timezone->persistent_;
+ opt_posix_timezone->cancelled_ = !opt_posix_timezone->cancelled_;
+ cbptr_->createUpdateOption6(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_posix_timezone);
+
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ returned_opt_posix_timezone =
+ returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_posix_timezone.option_);
+
+ {
+ SCOPED_TRACE("verify updated option with modified persistence");
+ testOptionsEquivalent(*opt_posix_timezone, returned_opt_posix_timezone);
+ }
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the updated shared network option");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option set");
+ }
+
+ // The new option instance should replace the existing option instance,
+ // so we should still have one option.
+ ASSERT_EQ(1, countRows("dhcp6_options"));
+
+ // Deleting an option for any server should succeed.
+ EXPECT_EQ(1, cbptr_->deleteOption6(ServerSelector::ANY(),
+ shared_network->getName(),
+ opt_posix_timezone->option_->getType(),
+ opt_posix_timezone->space_name_));
+ returned_network = cbptr_->getSharedNetwork6(ServerSelector::ALL(),
+ shared_network->getName());
+ ASSERT_TRUE(returned_network);
+ EXPECT_FALSE(returned_network->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE).option_);
+
+ {
+ SCOPED_TRACE("UPDATE audit entry for the deleted shared network option");
+ testNewAuditEntry("dhcp6_shared_network",
+ AuditEntry::ModificationType::UPDATE,
+ "shared network specific option deleted");
+ }
+
+ // After deleting the option we should be back to 0.
+ EXPECT_EQ(0, countRows("dhcp6_options"));
+}
+
+void
+GenericConfigBackendDHCPv6Test::subnetOptionIdOrderTest() {
+
+ // Add a subnet with two pools with two options each.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[1]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Add a second subnet with a single option. The number of options in the database
+ // should now be 3.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[2]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(5, countRows("dhcp6_options"));
+
+ // Now replace the first subnet with three options and two pools. This will cause
+ // the option id values for this subnet to be larger than those in the second
+ // subnet.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSubnet6(ServerSelector::ALL(), test_subnets_[0]));
+ EXPECT_EQ(2, countRows("dhcp6_pool"));
+ EXPECT_EQ(4, countRows("dhcp6_options"));
+
+ // Now fetch all subnets.
+ Subnet6Collection subnets;
+ ASSERT_NO_THROW_LOG(subnets = cbptr_->getAllSubnets6(ServerSelector::ALL()));
+ ASSERT_EQ(2, subnets.size());
+
+ // Verify that the subnets returned are as expected.
+ for (auto subnet : subnets) {
+ ASSERT_EQ(1, subnet->getServerTags().size());
+ EXPECT_EQ("all", subnet->getServerTags().begin()->get());
+ if (subnet->getID() == 1024) {
+ EXPECT_EQ(test_subnets_[0]->toElement()->str(), subnet->toElement()->str());
+ } else if (subnet->getID() == 2048) {
+ EXPECT_EQ(test_subnets_[2]->toElement()->str(), subnet->toElement()->str());
+ } else {
+ ADD_FAILURE() << "unexpected subnet id:" << subnet->getID();
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::sharedNetworkOptionIdOrderTest() {
+ auto level1_options = test_networks_[0];
+ auto level1_no_options = test_networks_[1];
+ auto level2 = test_networks_[2];
+
+ // Insert two shared networks. We insert level1 without options first,
+ // then level2.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ level1_no_options));
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ level2));
+ // Fetch all shared networks.
+ SharedNetwork6Collection networks =
+ cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_no_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateSharedNetwork6(ServerSelector::ALL(),
+ level1_options));
+
+ // Fetch all shared networks.
+ networks = cbptr_->getAllSharedNetworks6(ServerSelector::ALL());
+ ASSERT_EQ(2, networks.size());
+
+ // See if shared networks are returned ok.
+ for (auto i = 0; i < networks.size(); ++i) {
+ if (i == 0) {
+ // level1_no_options
+ EXPECT_EQ(level1_options->toElement()->str(),
+ networks[i]->toElement()->str());
+ } else {
+ // bar
+ EXPECT_EQ(level2->toElement()->str(),
+ networks[i]->toElement()->str());
+ }
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::setAndGetAllClientClasses6Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create first class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("pkt6.msgtype == 1");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create second class.
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Create third class.
+ auto class3 = test_client_classes_[2];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Update the third class to depend on the second class.
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+ {
+ SCOPED_TRACE("client class bar is updated");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::UPDATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ // Only the first class should be returned for the server selector ALL.
+ auto client_classes = cbptr_->getAllClientClasses6(ServerSelector::ALL());
+ ASSERT_EQ(1, client_classes.getClasses()->size());
+ // All three classes should be returned for the server1.
+ client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1"));
+ auto classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+
+ auto fetched_class = classes_list->begin();
+ ASSERT_EQ("foo", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class1->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("bar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class2->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ ++fetched_class;
+ ASSERT_EQ("foobar", (*fetched_class)->getName());
+ EXPECT_FALSE((*fetched_class)->getMatchExpr());
+ EXPECT_EQ(class3->toElement()->str(), (*fetched_class)->toElement()->str());
+
+ // Move the third class between the first and second class.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, "foo"));
+
+ // Ensure that the classes order has changed.
+ client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+
+ // Update the foobar class without specifying its position. It should not
+ // be moved.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+
+ client_classes = cbptr_->getAllClientClasses6(ServerSelector::ONE("server1"));
+ classes_list = client_classes.getClasses();
+ ASSERT_EQ(3, classes_list->size());
+ EXPECT_EQ("foo", (*classes_list->begin())->getName());
+ EXPECT_FALSE((*classes_list->begin())->getMatchExpr());
+ EXPECT_EQ("foobar", (*(classes_list->begin() + 1))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 1))->getMatchExpr());
+ EXPECT_EQ("bar", (*(classes_list->begin() + 2))->getName());
+ EXPECT_FALSE((*(classes_list->begin() + 2))->getMatchExpr());
+}
+
+void
+GenericConfigBackendDHCPv6Test::getClientClass6Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Add classes.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->cancelled_,
+ test_options_[1]->space_name_));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+
+ // Get the first client class and validate its contents.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+ EXPECT_EQ("foo", client_class->getName());
+ EXPECT_TRUE(client_class->getRequired());
+ EXPECT_EQ(30, client_class->getValid().getMin());
+ EXPECT_EQ(60, client_class->getValid().get());
+ EXPECT_EQ(90, client_class->getValid().getMax());
+
+ // Validate options belonging to this class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_new_posix_timezone =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_new_posix_timezone.option_);
+
+ OptionDescriptor returned_opt_preference =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE);
+ ASSERT_TRUE(returned_opt_preference.option_);
+
+ // Fetch the same class using different server selectors.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"),
+ class1->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::UNASSIGNED(),
+ class1->getName()));
+ EXPECT_FALSE(client_class);
+
+ // Fetch the second client class using different selectors. This time the
+ // class should not be returned for the ALL server selector because it is
+ // associated with the server1.
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ANY(),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_TRUE(client_class);
+
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::UNASSIGNED(),
+ class2->getName()));
+ EXPECT_FALSE(client_class);
+}
+
+void
+GenericConfigBackendDHCPv6Test::createUpdateClientClass6OptionsTest() {
+ // Add class with two options and two option definitions.
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[0]->option_,
+ test_options_[0]->persistent_,
+ test_options_[0]->cancelled_,
+ test_options_[0]->space_name_));
+ ASSERT_NO_THROW_LOG(class1->getCfgOption()->add(test_options_[1]->option_,
+ test_options_[1]->persistent_,
+ test_options_[1]->cancelled_,
+ test_options_[1]->space_name_));
+ auto cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[0]));
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ // Fetch the class and the options from the database.
+ ClientClassDefPtr client_class;
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Validate options belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOption());
+ OptionDescriptor returned_opt_new_posix_timezone =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_NEW_POSIX_TIMEZONE);
+ ASSERT_TRUE(returned_opt_new_posix_timezone.option_);
+
+ OptionDescriptor returned_opt_preference =
+ client_class->getCfgOption()->get(DHCP6_OPTION_SPACE, D6O_PREFERENCE);
+ ASSERT_TRUE(returned_opt_preference.option_);
+
+ // Validate option definitions belonging to the class.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ auto returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ ASSERT_TRUE(returned_def_foo);
+ EXPECT_EQ(1234, returned_def_foo->getCode());
+ EXPECT_EQ("foo", returned_def_foo->getName());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, returned_def_foo->getOptionSpaceName());
+ EXPECT_EQ("espace", returned_def_foo->getEncapsulatedSpace());
+ EXPECT_EQ(OPT_STRING_TYPE, returned_def_foo->getType());
+ EXPECT_FALSE(returned_def_foo->getArrayType());
+
+ auto returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ ASSERT_TRUE(returned_def_fish);
+ EXPECT_EQ(5235, returned_def_fish->getCode());
+ EXPECT_EQ("fish", returned_def_fish->getName());
+ EXPECT_EQ(DHCP6_OPTION_SPACE, returned_def_fish->getOptionSpaceName());
+ EXPECT_TRUE(returned_def_fish->getEncapsulatedSpace().empty());
+ EXPECT_EQ(OPT_RECORD_TYPE, returned_def_fish->getType());
+ EXPECT_TRUE(returned_def_fish->getArrayType());
+
+ // Replace client class specific option definitions. Leave only one option
+ // definition.
+ cfg_option_def = boost::make_shared<CfgOptionDef>();
+ class1->setCfgOptionDef(cfg_option_def);
+ ASSERT_NO_THROW_LOG(class1->getCfgOptionDef()->add(test_option_defs_[2]));
+
+ // Delete one of the options and update the class.
+ class1->getCfgOption()->del(test_options_[0]->space_name_,
+ test_options_[0]->option_->getType());
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ ASSERT_NO_THROW_LOG(client_class = cbptr_->getClientClass6(ServerSelector::ALL(), class1->getName()));
+ ASSERT_TRUE(client_class);
+
+ // Ensure that the first option definition is gone.
+ ASSERT_TRUE(client_class->getCfgOptionDef());
+ returned_def_foo = client_class->getCfgOptionDef()->get(test_option_defs_[0]->getOptionSpaceName(),
+ test_option_defs_[0]->getCode());
+ EXPECT_FALSE(returned_def_foo);
+
+ // The second option definition should be present.
+ returned_def_fish = client_class->getCfgOptionDef()->get(test_option_defs_[2]->getOptionSpaceName(),
+ test_option_defs_[2]->getCode());
+ EXPECT_TRUE(returned_def_fish);
+
+ // Make sure that the first option is gone.
+ ASSERT_TRUE(client_class->getCfgOption());
+ returned_opt_new_posix_timezone = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_NEW_POSIX_TIMEZONE);
+ EXPECT_FALSE(returned_opt_new_posix_timezone.option_);
+
+ // The second option should be there.
+ returned_opt_preference = client_class->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ D6O_PREFERENCE);
+ ASSERT_TRUE(returned_opt_preference.option_);
+}
+
+void
+GenericConfigBackendDHCPv6Test::getModifiedClientClasses6Test() {
+ // Create server1.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Add three classes to the database with different timestamps.
+ auto class1 = test_client_classes_[0];
+ class1->setModificationTime(timestamps_["yesterday"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ auto class2 = test_client_classes_[1];
+ class2->setModificationTime(timestamps_["today"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, ""));
+
+ auto class3 = test_client_classes_[2];
+ class3->setModificationTime(timestamps_["tomorrow"]);
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class3, ""));
+
+ // Get modified client classes configured for all servers.
+ auto client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ALL(),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get modified client classes appropriate for server1. It includes classes
+ // for all servers and for the server1.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["two days ago"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get the classes again but use the timestamp equal to the modification
+ // time of the first class.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["yesterday"]);
+ EXPECT_EQ(3, client_classes.getClasses()->size());
+
+ // Get modified classes starting from today. It should return only two.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["today"]);
+ EXPECT_EQ(2, client_classes.getClasses()->size());
+
+ // Get client classes modified in the future. It should return none.
+ client_classes = cbptr_->getModifiedClientClasses6(ServerSelector::ONE("server1"),
+ timestamps_["after tomorrow"]);
+ EXPECT_EQ(0, client_classes.getClasses()->size());
+
+ // Getting modified client classes for any server is unsupported.
+ ASSERT_THROW(cbptr_->getModifiedClientClasses6(ServerSelector::ANY(),
+ timestamps_["two days ago"]),
+ InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteClientClass6Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server1"),
+ class2->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class bar is deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ONE("server2"),
+ class3->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foobar is deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteClientClass6(ServerSelector::ANY(),
+ class1->getName()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server1");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is deleted and no longer available for the server2");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "client class deleted",
+ ServerSelector::ONE("server2"));
+ }
+}
+
+void
+GenericConfigBackendDHCPv6Test::deleteAllClientClasses6Test() {
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[2]));
+ {
+ SCOPED_TRACE("server1 is created and available for server1");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("server1 is created and available for server2");
+ testNewAuditEntry("dhcp6_server",
+ AuditEntry::ModificationType::CREATE,
+ "server set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class1 = test_client_classes_[0];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+ {
+ SCOPED_TRACE("client class foo is created and available for server1");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+ {
+ SCOPED_TRACE("client class foo is created and available for server 2");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ auto class2 = test_client_classes_[1];
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server1"), class2, ""));
+ {
+ SCOPED_TRACE("client class bar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server1"));
+ }
+
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ONE("server2"), class3, ""));
+ {
+ SCOPED_TRACE("client class foobar is created");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::CREATE,
+ "client class set",
+ ServerSelector::ONE("server2"));
+ }
+
+ uint64_t result;
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::UNASSIGNED()));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server2 deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server2"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server2")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for server1 deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ONE("server1")));
+ EXPECT_EQ(0, result);
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL()));
+ EXPECT_EQ(1, result);
+ {
+ SCOPED_TRACE("client classes for all deleted");
+ testNewAuditEntry("dhcp6_client_class",
+ AuditEntry::ModificationType::DELETE,
+ "deleted all client classes",
+ ServerSelector::ONE("server1"));
+ }
+
+ ASSERT_NO_THROW_LOG(result = cbptr_->deleteAllClientClasses6(ServerSelector::ALL()));
+ EXPECT_EQ(0, result);
+
+ // Deleting multiple objects using ANY server tag is unsupported.
+ ASSERT_THROW(cbptr_->deleteAllClientClasses6(ServerSelector::ANY()), InvalidOperation);
+}
+
+void
+GenericConfigBackendDHCPv6Test::clientClassDependencies6Test() {
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[0]));
+
+ // Create first class. It depends on KNOWN built-in class.
+ auto class1 = test_client_classes_[0];
+ class1->setTest("member('KNOWN')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""));
+
+ // Create second class which depends on the first class. This yelds indirect
+ // dependency on KNOWN class.
+ auto class2 = test_client_classes_[1];
+ class2->setTest("member('foo')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, ""));
+
+ // Create third class depending on the second class. This also yelds indirect
+ // dependency on KNOWN class.
+ auto class3 = test_client_classes_[2];
+ class3->setTest("member('bar')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class3, ""));
+
+ // An attempt to move the first class to the end of the class hierarchy should
+ // fail because other classes depend on it.
+ ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, "bar"),
+ DbOperationError);
+
+ // Try to change the dependency of the first class. There are other classes
+ // having indirect dependency on KNOWN class via this class. Therefore, the
+ // update should be unsuccessful.
+ class1->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class1, ""),
+ DbOperationError);
+
+ // Try to change the dependency of the second class. This should result in
+ // an error because the third class depends on it.
+ class2->setTest("member('HA_server1')");
+ ASSERT_THROW(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class2, ""),
+ DbOperationError);
+
+ // Changing the indirect dependency of the third class should succeed, because
+ // no other classes depend on this class.
+ class3->setTest("member('HA_server1')");
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateClientClass6(ServerSelector::ALL(), class3, ""));
+}
+
+void
+GenericConfigBackendDHCPv6Test::multipleAuditEntriesTest() {
+ // Get current time.
+ boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+
+ // Create a server.
+ ASSERT_NO_THROW_LOG(cbptr_->createUpdateServer6(test_servers_[1]));
+
+ // Create a global parameter and update it many times.
+ const ServerSelector& server_selector = ServerSelector::ALL();
+ StampedValuePtr param;
+ ElementPtr value;
+ for (int i = 0; i < 100; ++i) {
+ value = Element::create(i);
+ param = StampedValue::create("my-parameter", value);
+ cbptr_->createUpdateGlobalParameter6(server_selector, param);
+ }
+
+ // Get all audit entries from now.
+ AuditEntryCollection audit_entries =
+ cbptr_->getRecentAuditEntries(server_selector, now, 0);
+
+ // Check that partial retrieves return the right count.
+ auto& mod_time_idx = audit_entries.get<AuditEntryModificationTimeIdTag>();
+ for (auto it = mod_time_idx.begin(); it != mod_time_idx.end(); ++it) {
+ size_t partial_size =
+ cbptr_->getRecentAuditEntries(server_selector,
+ (*it)->getModificationTime(),
+ (*it)->getRevisionId()).size();
+ EXPECT_EQ(partial_size + 1,
+ std::distance(it, mod_time_idx.end()));
+ }
+}
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h
new file mode 100644
index 0000000..61e8564
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_dhcp6_unittest.h
@@ -0,0 +1,394 @@
+// Copyright (C) 2022 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 GENERIC_CONFIG_BACKEND_DHCP6_H
+#define GENERIC_CONFIG_BACKEND_DHCP6_H
+
+#include <database/database_connection.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class for testing DHCPv6
+/// config backend operations.
+class GenericConfigBackendDHCPv6Test : public GenericBackendTest {
+public:
+ /// @brief Constructor.
+ GenericConfigBackendDHCPv6Test()
+ : test_subnets_(), test_networks_(), test_option_defs_(),
+ test_options_(), test_client_classes_(), test_servers_(), cbptr_() {
+ }
+
+ /// @brief Destructor.
+ virtual ~GenericConfigBackendDHCPv6Test() {};
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic host manager to
+ /// wipe out any prior instance
+ virtual void SetUp();
+
+ /// @brief Pre-test exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown();
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns the back end specific connection
+ /// string
+ virtual std::string validConnectionString() = 0;
+
+ /// @brief Abstract method which instantiates an instance of a
+ /// DHCPv6 configuration back end.
+ ///
+ /// @params Connection parameters describing the back end to create.
+ /// @return Pointer to the newly created back end instance.
+ virtual ConfigBackendDHCPv6Ptr backendFactory(db::DatabaseConnection::ParameterMap&
+ params) = 0;
+
+ /// @brief Counts rows in a selected table in the back end database.
+ ///
+ /// This method can be used to verify that some configuration elements were
+ /// deleted from a selected table as a result of cascade delete or a trigger.
+ /// For example, deleting a subnet should trigger deletion of its address
+ /// pools and options. By counting the rows on each table we can determine
+ /// whether the deletion took place on all tables for which it was expected.
+ ///
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ virtual size_t countRows(const std::string& table) const = 0;
+
+ /// @brief Creates several servers used in tests.
+ void initTestServers();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSubnets();
+
+ /// @brief Creates several subnets used in tests.
+ void initTestSharedNetworks();
+
+ /// @brief Creates several option definitions used in tests.
+ void initTestOptionDefs();
+
+ /// @brief Creates several DHCP options used in tests.
+ void initTestOptions();
+
+ /// @brief Creates several client classes used in tests.
+ void initTestClientClasses();
+
+ /// @brief Tests that a backend of the given type can be instantiated.
+ ///
+ /// @param expected_type type of the back end created (i.e. "mysql",
+ /// "postgresql").
+ void getTypeTest(const std::string& expected_type);
+
+ /// @brief Verifies that a backend on the localhost can be instantiated.
+ void getHostTest();
+
+ /// @brief Verifies that a backend on the localhost port 0 can be instantiated.
+ void getPortTest();
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// Allowed server selectors: ALL, ONE.
+ /// Not allowed server selectors: ANY, UNASSIGNED, MULTIPLE.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief This test verifies that the server can be added, updated and deleted.
+ void createUpdateDeleteServerTest();
+
+ /// @brief This test verifies that it is possible to retrieve all servers from the
+ /// database and then delete all of them.
+ void getAndDeleteAllServersTest();
+
+ /// @brief This test verifies that the global parameter can be added, updated and
+ /// deleted.
+ void createUpdateDeleteGlobalParameter6Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global parameters by server tag and that the value specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalParameters6WithServerTagsTest();
+
+ /// @brief This test verifies that all global parameters can be retrieved and deleted.
+ void getAllGlobalParameters6Test();
+
+ /// @brief This test verifies that modified global parameters can be retrieved.
+ void getModifiedGlobalParameters6Test();
+
+ /// @brief Test that the NullKeyError message is correctly updated.
+ void nullKeyErrorTest();
+
+ /// @brief Test that createUpdateSubnet6 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSubnet6SelectorsTest();
+
+ /// @brief Test that subnet can be inserted, fetched, updated and then fetched again.
+ void getSubnet6Test();
+
+ /// @brief Test that getSubnet6 by ID throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet6byIdSelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSubnet6WithOptionalUnspecifiedTest();
+
+ /// @brief Test that subnet can be associated with a shared network.
+ void getSubnet6SharedNetworkTest();
+
+ /// @brief Test that subnet can be fetched by prefix.
+ void getSubnet6ByPrefixTest();
+
+ /// @brief Test that getSubnet6 by prefix throws appropriate exceptions for various server
+ /// selectors.
+ void getSubnet6byPrefixSelectorsTest();
+
+ /// @brief Test that all subnets can be fetched and then deleted.
+ void getAllSubnets6Test();
+
+ /// @brief Test that getAllSubnets6 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSubnets6SelectorsTest();
+
+ /// @brief Test that subnets with different server associations are returned.
+ void getAllSubnets6WithServerTagsTest();
+
+ /// @brief Test that getModifiedSubnets6 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSubnets6SelectorsTest();
+
+ /// @brief Test that selected subnet can be deleted.
+ void deleteSubnet6Test();
+
+ /// @brief Test that deleteSubnet6 by ID throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet6ByIdSelectorsTest();
+
+ /// @brief Test that deleteSubnet6 by prefix throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSubnet6ByPrefixSelectorsTest();
+
+ /// @brief Test that deleteAllSubnets6 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSubnets6SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned subnet.
+ void unassignedSubnet6Test();
+
+ /// @brief Test that subnets modified after given time can be fetched.
+ void getModifiedSubnets6Test();
+
+ /// @brief Test that lifetimes in subnets are handled as expected.
+ void subnetLifetimeTest();
+
+ /// @brief Test that subnets belonging to a shared network can be retrieved.
+ void getSharedNetworkSubnets6Test();
+
+ /// @brief Test that pools are properly updated as a result a subnet update.
+ void subnetUpdatePoolsTest();
+
+ /// @brief Test that deleting a subnet triggers deletion of the options associated
+ /// with the subnet and pools.
+ void subnetOptionsTest();
+
+ /// @brief Test that shared network can be inserted, fetched, updated and then
+ /// fetched again.
+ void getSharedNetwork6Test();
+
+ /// @brief Test that getSharedNetwork6 throws appropriate exceptions for various
+ /// server selectors.
+ void getSharedNetwork6SelectorsTest();
+
+ /// @brief Test that shared network may be created and updated and the server tags
+ /// are properly assigned to it.
+ void createUpdateSharedNetwork6Test();
+
+ /// @brief Test that createUpdateSharedNetwork6 throws appropriate exceptions for various
+ /// server selectors.
+ void createUpdateSharedNetwork6SelectorsTest();
+
+ /// @brief Test that the information about unspecified optional parameters gets
+ /// propagated to the database.
+ void getSharedNetwork6WithOptionalUnspecifiedTest();
+
+ /// @brief Test that deleteSharedNetworkSubnets6 with not ANY selector throw.
+ void deleteSharedNetworkSubnets6Test();
+
+ /// @brief Test that all shared networks can be fetched.
+ void getAllSharedNetworks6Test();
+
+ /// @brief Test that getAllSharedNetworks6 throws appropriate exceptions for various
+ /// server selectors.
+ void getAllSharedNetworks6SelectorsTest();
+
+ /// @brief Test that shared networks with different server associations are returned.
+ void getAllSharedNetworks6WithServerTagsTest();
+
+ /// @brief Test that shared networks modified after given time can be fetched.
+ void getModifiedSharedNetworks6Test();
+
+ /// @brief Test that getModifiedSharedNetworks6 throws appropriate exceptions for various
+ /// server selectors.
+ void getModifiedSharedNetworks6SelectorsTest();
+
+ /// @brief Test that selected shared network can be deleted.
+ void deleteSharedNetwork6Test();
+
+ /// @brief Test that deleteSharedNetwork6 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteSharedNetwork6SelectorsTest();
+
+ /// @brief Test that deleteAllSharedNetworks6 throws appropriate exceptions for various
+ /// server selectors.
+ void deleteAllSharedNetworks6SelectorsTest();
+
+ /// @brief Test that it is possible to retrieve and delete orphaned shared network.
+ void unassignedSharedNetworkTest();
+
+ /// @brief Test that lifetimes in shared networks are handled as expected.
+ void sharedNetworkLifetimeTest();
+
+ /// @brief Test that deleting a shared network triggers deletion of the options
+ /// associated with the shared network.
+ void sharedNetworkOptionsTest();
+
+ /// @brief Test that option definition can be inserted, fetched, updated and then
+ /// fetched again.
+ void getOptionDef6Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// option definitions by server tag and that the option definition
+ /// specified for the particular server overrides the definition for
+ /// all servers.
+ void optionDefs6WithServerTagsTest();
+
+ /// @brief Test that all option definitions can be fetched.
+ void getAllOptionDefs6Test();
+
+ /// @brief Test that option definitions modified after given time can be fetched.
+ void getModifiedOptionDefs6Test();
+
+ /// @brief This test verifies that global option can be added, updated and deleted.
+ void createUpdateDeleteOption6Test();
+
+ /// @brief This test verifies that it is possible to differentiate between the
+ /// global options by server tag and that the option specified for the
+ /// particular server overrides the value specified for all servers.
+ void globalOptions6WithServerTagsTest();
+
+ /// @brief This test verifies that all global options can be retrieved.
+ void getAllOptions6Test();
+
+ /// @brief This test verifies that modified global options can be retrieved.
+ void getModifiedOptions6Test();
+
+ /// @brief This test verifies that subnet level option can be added, updated and
+ /// deleted.
+ void createUpdateDeleteSubnetOption6Test();
+
+ /// @brief This test verifies that option can be inserted, updated and deleted
+ /// from the pool.
+ void createUpdateDeletePoolOption6Test();
+
+ /// @brief This test verifies that option can be inserted, updated and deleted
+ /// from the pd pool.
+ void createUpdateDeletePdPoolOption6Test();
+
+ /// @brief This test verifies that shared network level option can be added,
+ /// updated and deleted.
+ void createUpdateDeleteSharedNetworkOption6Test();
+
+ /// @brief This test verifies that option id values in one subnet do
+ /// not impact options returned in subsequent subnets when
+ /// fetching subnets from the backend.
+ void subnetOptionIdOrderTest();
+
+ /// @brief This test verifies that option id values in one shared network do
+ /// not impact options returned in subsequent shared networks when
+ /// fetching shared networks from the backend.
+ void sharedNetworkOptionIdOrderTest();
+
+ /// @brief This test verifies that it is possible to create client classes, update them
+ /// and retrieve all classes for a given server.
+ void setAndGetAllClientClasses6Test();
+
+ /// @brief This test verifies that a single class can be retrieved from the database.
+ void getClientClass6Test();
+
+ /// @brief This test verifies that client class specific DHCP options can be
+ /// modified during the class update.
+ void createUpdateClientClass6OptionsTest();
+
+ /// @brief This test verifies that modified client classes can be retrieved from the database.
+ void getModifiedClientClasses6Test();
+
+ /// @brief This test verifies that a specified client class can be deleted.
+ void deleteClientClass6Test();
+
+ /// @brief This test verifies that all client classes can be deleted using
+ /// a specified server selector.
+ void deleteAllClientClasses6Test();
+
+ /// @brief This test verifies that client class dependencies are tracked when the
+ /// classes are added to the database. It verifies that an attempt to update
+ /// a class violating the dependencies results in an error.
+ void clientClassDependencies6Test();
+
+ /// @brief This test verifies that audit entries can be retrieved from a given
+ /// timestamp and id including when two entries can get the same timestamp.
+ /// (either it is a common even and this should catch it, or it is a rare
+ /// event and it does not matter).
+ void multipleAuditEntriesTest();
+
+ /// @brief Holds pointers to subnets used in tests.
+ std::vector<Subnet6Ptr> test_subnets_;
+
+ /// @brief Holds pointers to shared networks used in tests.
+ std::vector<SharedNetwork6Ptr> test_networks_;
+
+ /// @brief Holds pointers to option definitions used in tests.
+ std::vector<OptionDefinitionPtr> test_option_defs_;
+
+ /// @brief Holds pointers to options used in tests.
+ std::vector<OptionDescriptorPtr> test_options_;
+
+ /// @brief Holds pointers to classes used in tests.
+ std::vector<ClientClassDefPtr> test_client_classes_;
+
+ /// @brief Holds pointers to the servers used in tests.
+ std::vector<db::ServerPtr> test_servers_;
+
+ /// @brief Holds pointer to the backend.
+ boost::shared_ptr<ConfigBackendDHCPv6> cbptr_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // GENERIC_CONFIG_BACKEND_DHCP6_H
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc
new file mode 100644
index 0000000..87e3ce2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.cc
@@ -0,0 +1,376 @@
+// Copyright (C) 2022 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 <database/db_exceptions.h>
+#include <database/server.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/testutils/generic_cb_recovery_unittest.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace isc::test;
+namespace ph = std::placeholders;
+
+GenericConfigBackendDbLostCallbackTest::GenericConfigBackendDbLostCallbackTest()
+ : db_lost_callback_called_(0), db_recovered_callback_called_(0),
+ db_failed_callback_called_(0),
+ io_service_(boost::make_shared<IOService>()) {
+}
+
+GenericConfigBackendDbLostCallbackTest::~GenericConfigBackendDbLostCallbackTest() {
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::SetUp() {
+ DatabaseConnection::db_lost_callback_ = 0;
+ DatabaseConnection::db_recovered_callback_ = 0;
+ DatabaseConnection::db_failed_callback_ = 0;
+ setConfigBackendImplIOService(io_service_);
+ isc::dhcp::TimerMgr::instance()->setIOService(io_service_);
+ isc::dhcp::CfgMgr::instance().clear();
+
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+ isc::dhcp::CfgMgr::instance().clear();
+ registerBackendType();
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::TearDown() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroySchema();
+ isc::dhcp::CfgMgr::instance().clear();
+
+ unregisterBackendType();
+ DatabaseConnection::db_lost_callback_ = 0;
+ DatabaseConnection::db_recovered_callback_ = 0;
+ DatabaseConnection::db_failed_callback_ = 0;
+ setConfigBackendImplIOService(IOServicePtr());
+ isc::dhcp::TimerMgr::instance()->unregisterTimers();
+ isc::dhcp::CfgMgr::instance().clear();
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testNoCallbackOnOpenFailure() {
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = invalidConnectionString();
+
+ // Connect to the CB backend.
+ ASSERT_THROW(addBackend(access), DbOpenError);
+
+ io_service_->poll();
+
+ EXPECT_EQ(0, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndRecoveredCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW_LOG(servers = getAllServers());
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndFailedCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW(servers = getAllServers());
+
+ access = invalidConnectionString();
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+ const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+ (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW(servers = getAllServers());
+
+ access = invalidConnectionString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+ const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+ (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ access = validConnectionString();
+ access += extra;
+ CfgMgr::instance().clear();
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // No callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+GenericConfigBackendDbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ DatabaseConnection::db_lost_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ DatabaseConnection::db_recovered_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ DatabaseConnection::db_failed_callback_ =
+ std::bind(&GenericConfigBackendDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectionString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the CB backend.
+ ASSERT_NO_THROW(addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ServerCollection servers;
+ ASSERT_NO_THROW(servers = getAllServers());
+
+ access = invalidConnectionString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ config_ctl_info.reset(new ConfigControlInfo());
+ config_ctl_info->addConfigDatabase(access);
+ CfgMgr::instance().getCurrentCfg()->setConfigControlInfo(config_ctl_info);
+ const ConfigDbInfoList& cfg = CfgMgr::instance().getCurrentCfg()->getConfigControlInfo()->getConfigDatabases();
+ (const_cast<ConfigDbInfoList&>(cfg))[0].setAccessString(access, true);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(servers = getAllServers(), DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(3, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
diff --git a/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h
new file mode 100644
index 0000000..af1ce4b
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_cb_recovery_unittest.h
@@ -0,0 +1,164 @@
+// Copyright (C) 2022 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 GENERIC_CONFIG_BACKEND_RECOVERY_H
+#define GENERIC_CONFIG_BACKEND_RECOVERY_H
+
+#include <util/reconnect_ctl.h>
+#include <database/server_collection.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test fixture for verifying config backend database connection
+/// loss-recovery behavior.
+class GenericConfigBackendDbLostCallbackTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ GenericConfigBackendDbLostCallbackTest();
+
+ /// @brief Destructor
+ virtual ~GenericConfigBackendDbLostCallbackTest();
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns a valid back end specific connection
+ /// string
+ virtual std::string validConnectionString() = 0;
+
+ /// @brief Abstract method which returns an invalid back end specific connection
+ /// string
+ virtual std::string invalidConnectionString() = 0;
+
+ /// @brief Abstract method which registers a CB backend type.
+ virtual void registerBackendType() = 0;
+
+ /// @brief Abstract method which unregisters a CB backend type.
+ virtual void unregisterBackendType() = 0;
+
+ /// @brief Abstract method which sets the IOService instance in the CB
+ /// implementation object.
+ ///
+ /// @param io_service pointer to the IOService instance to use. It may be
+ /// an empty pointer.
+ virtual void setConfigBackendImplIOService(isc::asiolink::IOServicePtr io_service) = 0;
+
+ /// @brief Abstract method which sets the IOService instance in the CB
+ virtual void addBackend(const std::string& access) = 0;
+
+ /// @brief Abstract method which sets the IOService instance in the CB
+ virtual db::ServerCollection getAllServers() = 0;
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic DB manager to
+ /// wipe out any prior instance
+ virtual void SetUp();
+
+ /// @brief Pre-text exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown();
+
+ /// @brief Verifies open failures do NOT invoke db lost callback
+ ///
+ /// The db lost callback should only be invoked after successfully
+ /// opening the DB and then subsequently losing it. Failing to
+ /// open should be handled directly by the application layer.
+ void testNoCallbackOnOpenFailure();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked
+ void testDbLostAndRecoveredCallback();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked
+ void testDbLostAndFailedCallback();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndRecoveredAfterTimeoutCallback();
+
+ /// @brief Verifies the CB manager's behavior if DB connection is lost
+ ///
+ /// This function creates a CB manager with a back end that supports
+ /// connectivity lost callback. It verifies connectivity by issuing a known
+ /// valid query. Next it simulates connectivity lost by identifying and
+ /// closing the socket connection to the CB backend. It then reissues the
+ /// query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndFailedAfterTimeoutCallback();
+
+ /// @brief Callback function registered with the CB manager
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_lost_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_lost_callback function
+ uint32_t db_lost_callback_called_;
+
+ /// @brief Callback function registered with the CB manager
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_recovered_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_recovered_callback function
+ uint32_t db_recovered_callback_called_;
+
+ /// @brief Callback function registered with the CB manager
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_failed_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_failed_callback function
+ uint32_t db_failed_callback_called_;
+
+ /// The IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // GENERIC_CONFIG_BACKEND_RECOVERY_H
diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
new file mode 100644
index 0000000..a7462ac
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.cc
@@ -0,0 +1,5226 @@
+// Copyright (C) 2015-2023 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 <asiolink/addr_utilities.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcpsrv/host_data_source_factory.h>
+#include <dhcpsrv/testutils/generic_host_data_source_unittest.h>
+#include <dhcpsrv/testutils/host_data_source_utils.h>
+#include <dhcpsrv/testutils/test_utils.h>
+#include <database/testutils/schema.h>
+#include <testutils/gtest_utils.h>
+#include <util/buffer.h>
+
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <string>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::util;
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+GenericHostDataSourceTest::GenericHostDataSourceTest()
+ : GenericBackendTest(), hdsptr_() {
+}
+
+GenericHostDataSourceTest::~GenericHostDataSourceTest() {
+ hdsptr_.reset();
+}
+
+bool
+GenericHostDataSourceTest::compareHostsForSort4(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ if (host1->getIPv4SubnetID() < host2->getIPv4SubnetID()) {
+ return true;
+ }
+ return false;
+}
+
+bool
+GenericHostDataSourceTest::compareHostsForSort6(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ if (host1->getIPv6SubnetID() < host2->getIPv6SubnetID()) {
+ return true;
+ }
+ return false;
+}
+
+bool
+GenericHostDataSourceTest::compareHostsIdentifier(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ auto host1_i = host1->getIdentifier();
+ auto host2_i = host2->getIdentifier();
+ auto count1 = host1_i.size();
+ auto count2 = host2_i.size();
+ if (count1 > count2) {
+ count1 = count2;
+ }
+ for (uint8_t i = 0; i < count1; ++i) {
+ if (host1_i[i] != host2_i[i]) {
+ return (host1_i[i] < host2_i[i]);
+ }
+ }
+ return false;
+}
+
+DuidPtr
+GenericHostDataSourceTest::HWAddrToDuid(const HWAddrPtr& hwaddr) {
+ if (!hwaddr) {
+ return (DuidPtr());
+ }
+
+ return (DuidPtr(new DUID(hwaddr->hwaddr_)));
+}
+
+HWAddrPtr
+GenericHostDataSourceTest::DuidToHWAddr(const DuidPtr& duid) {
+ if (!duid) {
+ return (HWAddrPtr());
+ }
+
+ return (HWAddrPtr(new HWAddr(duid->getDuid(), HTYPE_ETHER)));
+}
+
+void
+GenericHostDataSourceTest::addTestOptions(const HostPtr& host,
+ const bool formatted,
+ const AddedOptions& added_options,
+ ConstElementPtr user_context) const {
+
+ OptionDefSpaceContainer defs;
+
+ if ((added_options == DHCP4_ONLY) || (added_options == DHCP4_AND_DHCP6)) {
+ // Add DHCPv4 options.
+ CfgOptionPtr opts = host->getCfgOption4();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, formatted, "my-boot-file");
+ desc.setContext(user_context);
+ opts->add(desc, DHCP4_OPTION_SPACE);
+ opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+ false, false, formatted, 64),
+ DHCP4_OPTION_SPACE);
+ opts->add(createOption<OptionUint32>(Option::V4, 1, false, false,
+ formatted, 312131),
+ "vendor-encapsulated-options-space");
+ opts->add(createAddressOption<Option4AddrLst>(254, false, false,
+ formatted, "192.0.2.3"),
+ DHCP4_OPTION_SPACE);
+ opts->add(createEmptyOption(Option::V4, 1, true, false), "isc");
+ opts->add(createAddressOption<Option4AddrLst>(2, false, false,
+ formatted, "10.0.0.5",
+ "10.0.0.3", "10.0.3.4"),
+ "isc");
+ auto def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE,
+ DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ opts->add(OptionDescriptor(def->optionFactory(Option::V4,
+ DHO_VENDOR_ENCAPSULATED_OPTIONS,
+ OptionBuffer()),
+ true, false), DHCP4_OPTION_SPACE);
+
+ // Add definitions for DHCPv4 non-standard options.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+ "vendor-encapsulated-1", 1,
+ "vendor-encapsulated-options-space", "uint32")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+ "option-254", 254, DHCP4_OPTION_SPACE,
+ "ipv4-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-1", 1, "isc", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("isc-2", 2, "isc", "ipv4-address", true)));
+ }
+
+ if ((added_options == DHCP6_ONLY) || (added_options == DHCP4_AND_DHCP6)) {
+ // Add DHCPv6 options.
+ CfgOptionPtr opts = host->getCfgOption6();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+ true, false, formatted, "my-boot-file");
+ desc.setContext(user_context);
+ opts->add(desc, DHCP6_OPTION_SPACE);
+ opts->add(createOption<OptionUint32>(Option::V6, D6O_INFORMATION_REFRESH_TIME,
+ false, false, formatted, 3600),
+ DHCP6_OPTION_SPACE);
+ opts->add(createVendorOption(Option::V6, false, false, formatted, 2495),
+ DHCP6_OPTION_SPACE);
+ opts->add(createAddressOption<Option6AddrLst>(1024, false, false,
+ formatted, "2001:db8:1::1"),
+ DHCP6_OPTION_SPACE);
+ opts->add(createEmptyOption(Option::V6, 1, true, false), "isc2");
+ opts->add(createAddressOption<Option6AddrLst>(2, false, false,
+ formatted, "3000::1",
+ "3000::2", "3000::3"),
+ "isc2");
+
+ desc = createOption<OptionString>(Option::V6, DOCSIS3_V6_TFTP_SERVERS,
+ true, false, true, "3000:1::234");
+ opts->add(desc, "vendor-4491");
+
+ // Add definitions for DHCPv6 non-standard options.
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition(
+ "option-1024", 1024, DHCP6_OPTION_SPACE,
+ "ipv6-address", true)));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-1", 1, "isc2", "empty")));
+ defs.addItem(OptionDefinitionPtr(new OptionDefinition("option-2", 2, "isc2", "ipv6-address", true)));
+ }
+
+ // Register created "runtime" option definitions. They will be used by a
+ // host data source to convert option data into the appropriate option
+ // classes when the options are retrieved.
+ LibDHCP::setRuntimeOptionDefs(defs);
+}
+
+void
+GenericHostDataSourceTest::addIPv6Address(const HostPtr& host, const std::string& address) const {
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress(address)));
+}
+
+void
+GenericHostDataSourceTest::testReadOnlyDatabase(const char* valid_db_type) {
+ ASSERT_TRUE(hdsptr_);
+
+ // The database is initially opened in "read-write" mode. We can
+ // insert some data to the database.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ ASSERT_TRUE(host);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // Make sure that the host has been inserted and that the data can be
+ // retrieved.
+ ConstHostPtr host_by_id =
+ hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(host_by_id);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Close the database connection and reopen in "read-only" mode as
+ // specified by the "VALID_READONLY_DB" parameter.
+ HostMgr::create();
+ HostMgr::addBackend(connectionString(
+ valid_db_type, VALID_NAME, VALID_HOST, VALID_READONLY_USER,
+ VALID_PASSWORD, VALID_READONLY_DB));
+
+ hdsptr_ = HostMgr::instance().getHostDataSource();
+ ASSERT_NE(hdsptr_->getParameters(), DatabaseConnection::ParameterMap());
+
+ // Check that an attempt to insert new host would result in
+ // exception.
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+ ASSERT_TRUE(host2);
+ ASSERT_THROW(hdsptr_->add(host2), ReadOnlyDb);
+ ASSERT_THROW(hdsptr_->commit(), ReadOnlyDb);
+ ASSERT_THROW(hdsptr_->rollback(), ReadOnlyDb);
+
+ // Reading from the database should still be possible, though.
+ host_by_id =
+ hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(host_by_id);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+}
+
+void
+GenericHostDataSourceTest::testBasic4(const Host::IdentifierType& id) {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ ASSERT_TRUE(host); // Make sure the host is generate properly.
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // This should not return anything
+ ConstHostPtr from_hds = hdsptr_->get4(subnet, IOAddress("10.10.10.10"));
+ ASSERT_FALSE(from_hds);
+
+ // This time it should return a host
+ from_hds = hdsptr_->get4(subnet, IOAddress("192.0.2.1"));
+ ASSERT_TRUE(from_hds);
+
+ // Finally, let's check if what we got makes any sense.
+ HostDataSourceUtils::compareHosts(host, from_hds);
+}
+
+void
+GenericHostDataSourceTest::testGlobalSubnetId4() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id4 = SUBNET_ID_GLOBAL;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ subnet_id4, SUBNET_ID_UNUSED, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP4_ONLY));
+ (hdsptr_->add(host));
+ //ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get4(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get4(subnet_id4,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet_id4, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id4, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+void GenericHostDataSourceTest::testGlobalSubnetId6() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id6 = SUBNET_ID_GLOBAL;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ SUBNET_ID_UNUSED, subnet_id6, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP6_ONLY));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get6(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get6(subnet_id6,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del6(subnet6-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet_id6, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id6, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+
+void
+GenericHostDataSourceTest::testMaxSubnetId4() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id4 = SUBNET_ID_MAX;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ subnet_id4, SUBNET_ID_UNUSED, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP4_ONLY));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get4(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get4(subnet_id4,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet_id4, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id4, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+void
+GenericHostDataSourceTest::testMaxSubnetId6() {
+ std::vector<uint8_t> ident;
+
+ ident = HostDataSourceUtils::generateIdentifier();
+ SubnetID subnet_id6 = SUBNET_ID_MAX;
+ HostPtr host(new Host(&ident[0], ident.size(), Host::IDENT_DUID,
+ SUBNET_ID_UNUSED, subnet_id6, IOAddress("0.0.0.0")));
+
+ ASSERT_NO_THROW(addTestOptions(host, true, DHCP6_ONLY));
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // get6(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id = hdsptr_->get6(subnet_id6,
+ host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // Now try to delete it: del6(subnet6-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet_id6, Host::IDENT_DUID, &ident[0],
+ ident.size()));
+
+ host_by_id = hdsptr_->get4(subnet_id6, host->getIdentifierType(),
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ EXPECT_FALSE(host_by_id);
+}
+
+void
+GenericHostDataSourceTest::testGetAll4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ HostPtr host3 = HostDataSourceUtils::initializeHost4("192.0.2.3", id);
+ HostPtr host4 = HostDataSourceUtils::initializeHost4("192.0.2.4", id);
+
+ // Set them in the same subnets.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // And then try to retrieve them back.
+ ConstHostCollection from_hds = hdsptr_->getAll4(subnet4);
+
+ // Make sure we got something back.
+ ASSERT_EQ(4, from_hds.size());
+
+ HostDataSourceUtils::compareHosts(host1, from_hds[0]);
+ HostDataSourceUtils::compareHosts(host2, from_hds[1]);
+ HostDataSourceUtils::compareHosts(host3, from_hds[2]);
+ HostDataSourceUtils::compareHosts(host4, from_hds[3]);
+}
+
+void
+GenericHostDataSourceTest::testGetAll6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", id, false);
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", id, false);
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::3", id, false);
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::4", id, false);
+
+ // Set them in the same subnets.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // And then try to retrieve them back.
+ ConstHostCollection from_hds = hdsptr_->getAll6(subnet6);
+
+ // Make sure we got something back.
+ ASSERT_EQ(4, from_hds.size());
+
+ HostDataSourceUtils::compareHosts(host1, from_hds[0]);
+ HostDataSourceUtils::compareHosts(host2, from_hds[1]);
+ HostDataSourceUtils::compareHosts(host3, from_hds[2]);
+ HostDataSourceUtils::compareHosts(host4, from_hds[3]);
+}
+
+void
+GenericHostDataSourceTest::testGetAllbyHostname() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ Host::IdentifierType id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ host1->setHostname("host");
+
+ id = Host::IDENT_DUID;
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ host2->setHostname("Host");
+
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::1", id, false);
+ host3->setHostname("hOSt");
+
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::2", id, false);
+ host4->setHostname("host.example.com");
+
+ // Now add them all to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // Retrieve unknown name.
+ ConstHostCollection from_hds = hdsptr_->getAllbyHostname("foo");
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve one reservation.
+ from_hds = hdsptr_->getAllbyHostname("host.example.com");
+ ASSERT_EQ(1, from_hds.size());
+ HostDataSourceUtils::compareHosts(host4, from_hds[0]);
+
+ // Retrieve all reservations with host hostname.
+ from_hds = hdsptr_->getAllbyHostname("host");
+ EXPECT_EQ(3, from_hds.size());
+ bool got1 = false;
+ bool got2 = false;
+ bool got3 = false;
+ for (auto host : from_hds) {
+ if (host->getIdentifierType() == Host::IDENT_HWADDR) {
+ EXPECT_FALSE(got1);
+ got1 = true;
+ HostDataSourceUtils::compareHosts(host1, host);
+ } else if (host->getIPv4Reservation().isV4Zero()) {
+ EXPECT_FALSE(got3);
+ got3 = true;
+ HostDataSourceUtils::compareHosts(host3, host);
+ } else {
+ EXPECT_FALSE(got2);
+ got2 = true;
+ HostDataSourceUtils::compareHosts(host2, host);
+ }
+ }
+ EXPECT_TRUE(got1);
+ EXPECT_TRUE(got2);
+ EXPECT_TRUE(got3);
+}
+
+void
+GenericHostDataSourceTest::testGetAllbyHostnameSubnet4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ Host::IdentifierType id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ host1->setHostname("host");
+
+ id = Host::IDENT_DUID;
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ host2->setHostname("Host");
+ CfgOptionPtr opts = host2->getCfgOption4();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, false, "my-boot-file");
+ opts->add(desc, DHCP4_OPTION_SPACE);
+
+ HostPtr host3 = HostDataSourceUtils::initializeHost4("192.0.2.3", id);
+ host3->setHostname("hOSt");
+
+ HostPtr host4 = HostDataSourceUtils::initializeHost4("192.0.2.4", id);
+ host4->setHostname("host.example.com");
+
+ HostPtr host5 = HostDataSourceUtils::initializeHost4("192.0.2.5", id);
+
+ // Set them in the same subnet at the exception of host5.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // Now add them all to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+ ASSERT_NO_THROW(hdsptr_->add(host5));
+
+ // Retrieve unknown name.
+ ConstHostCollection from_hds = hdsptr_->getAllbyHostname4("foo", subnet4);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve one reservation.
+ from_hds = hdsptr_->getAllbyHostname4("host.example.com", subnet4);
+ ASSERT_EQ(1, from_hds.size());
+ HostDataSourceUtils::compareHosts(host4, from_hds[0]);
+
+ // Check that the subnet is checked.
+ from_hds = hdsptr_->getAllbyHostname4("host.example.com", subnet4 + 1);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve all reservations with host hostname.
+ from_hds = hdsptr_->getAllbyHostname4("host", subnet4);
+ EXPECT_EQ(3, from_hds.size());
+ bool got1 = false;
+ bool got2 = false;
+ bool got3 = false;
+ for (auto host : from_hds) {
+ if (host->getIdentifierType() == Host::IDENT_HWADDR) {
+ EXPECT_FALSE(got1);
+ got1 = true;
+ HostDataSourceUtils::compareHosts(host1, host);
+ } else if (!host->getCfgOption4()->empty()) {
+ EXPECT_FALSE(got2);
+ got2 = true;
+ HostDataSourceUtils::compareHosts(host2, host);
+ } else {
+ EXPECT_FALSE(got3);
+ got3 = true;
+ HostDataSourceUtils::compareHosts(host3, host);
+ }
+ }
+ EXPECT_TRUE(got1);
+ EXPECT_TRUE(got2);
+ EXPECT_TRUE(got3);
+}
+
+void
+GenericHostDataSourceTest::testGetAllbyHostnameSubnet6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ Host::IdentifierType id = Host::IDENT_HWADDR;
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", id, false);
+ host1->setHostname("host");
+
+ id = Host::IDENT_DUID;
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", id, false);
+ host2->setHostname("Host");
+ CfgOptionPtr opts = host2->getCfgOption6();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+ true, false, true, "my-boot-file");
+ opts->add(desc, DHCP6_OPTION_SPACE);
+
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8::3", id, false);
+ host3->setHostname("hOSt");
+
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8::4", id, false);
+ host4->setHostname("host.example.com");
+
+ HostPtr host5 = HostDataSourceUtils::initializeHost6("2001:db8::5", id, false);
+
+ // Set them in the same subnet at the exception of host5.
+ SubnetID subnet4 = host1->getIPv4SubnetID();
+ host2->setIPv4SubnetID(subnet4);
+ host3->setIPv4SubnetID(subnet4);
+ host4->setIPv4SubnetID(subnet4);
+ SubnetID subnet6 = host1->getIPv6SubnetID();
+ host2->setIPv6SubnetID(subnet6);
+ host3->setIPv6SubnetID(subnet6);
+ host4->setIPv6SubnetID(subnet6);
+
+ // Now add them all to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+ ASSERT_NO_THROW(hdsptr_->add(host5));
+
+ // Retrieve unknown name.
+ ConstHostCollection from_hds = hdsptr_->getAllbyHostname6("foo", subnet6);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve one reservation.
+ from_hds = hdsptr_->getAllbyHostname6("host.example.com", subnet6);
+ ASSERT_EQ(1, from_hds.size());
+ HostDataSourceUtils::compareHosts(host4, from_hds[0]);
+
+ // Check that the subnet is checked.
+ from_hds = hdsptr_->getAllbyHostname6("host.example.com", subnet6 + 1);
+ EXPECT_TRUE(from_hds.empty());
+
+ // Retrieve all reservations with host hostname.
+ from_hds = hdsptr_->getAllbyHostname6("host", subnet6);
+ EXPECT_EQ(3, from_hds.size());
+ bool got1 = false;
+ bool got2 = false;
+ bool got3 = false;
+ for (auto host : from_hds) {
+ if (host->getIdentifierType() == Host::IDENT_HWADDR) {
+ EXPECT_FALSE(got1);
+ got1 = true;
+ HostDataSourceUtils::compareHosts(host1, host);
+ } else if (!host->getCfgOption6()->empty()) {
+ EXPECT_FALSE(got2);
+ got2 = true;
+ HostDataSourceUtils::compareHosts(host2, host);
+ } else {
+ EXPECT_FALSE(got3);
+ got3 = true;
+ HostDataSourceUtils::compareHosts(host3, host);
+ }
+ }
+ EXPECT_TRUE(got1);
+ EXPECT_TRUE(got2);
+ EXPECT_TRUE(got3);
+}
+
+void
+GenericHostDataSourceTest::testGetPage4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ for (unsigned i = 0; i < 25; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+ ConstHostCollection page;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+ host_id = 0;
+
+ // Other subnets are empty.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+}
+
+void
+GenericHostDataSourceTest::testGetPage6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ for (unsigned i = 0; i < 25; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(10);
+ ConstHostCollection page;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(10, page.size());
+ host_id = page[9]->getHostId();
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(5, page.size());
+ host_id = page[4]->getHostId();
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+ host_id = 0;
+
+ // Other subnets are empty.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+}
+
+void
+GenericHostDataSourceTest::testGetPageLimit4(const Host::IdentifierType& id) {
+ // From the ticket: add 5 hosts each with 3 options.
+ // call getPage4 with limit of 4.
+ // The first page should return 4 hosts,
+ // the second should return one host.
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ for (unsigned i = 0; i < 5; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ // Add DHCPv4 options.
+ CfgOptionPtr opts = host->getCfgOption4();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V4, DHO_BOOT_FILE_NAME,
+ true, false, false, "my-boot-file");
+ opts->add(desc, DHCP4_OPTION_SPACE);
+ opts->add(createOption<OptionUint8>(Option::V4, DHO_DEFAULT_IP_TTL,
+ false, true, false, 64 + i),
+ DHCP4_OPTION_SPACE);
+ opts->add(createEmptyOption(Option::V4, 1, true, true), "isc");
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(4);
+ ConstHostCollection page;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(4, page.size());
+ host_id = page[3]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(1, page.size());
+ host_id = page[0]->getHostId();
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+}
+
+void
+GenericHostDataSourceTest::testGetPageLimit6(const Host::IdentifierType& id) {
+ // From the ticket: add several v6 hosts with multiple address/prefix
+ // reservations and multiple options.
+ // Get hosts by page with page size 1.
+ // Make sure all address/prefix reservations are returned.
+ // Make sure all options are returned as expected.
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+
+ vector<HostPtr> hosts;
+
+ for (unsigned i = 0; i < 5; ++i) {
+ addr = IOAddress::increase(addr);
+ ostringstream pref;
+ pref << "2001:db8:2:" << 10 + i << "::";
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4);
+ host->setIPv6SubnetID(subnet6);
+
+ // Add address/prefix.
+ addr = IOAddress::increase(addr);
+ IPv6Resrv resva(IPv6Resrv::TYPE_NA, addr, 128);
+ host->addReservation(resva);
+ IPv6Resrv resvp(IPv6Resrv::TYPE_PD, IOAddress(pref.str()), 64);
+ host->addReservation(resvp);
+
+ // Add DHCPv6 options.
+ CfgOptionPtr opts = host->getCfgOption6();
+ OptionDescriptor desc =
+ createOption<OptionString>(Option::V6, D6O_BOOTFILE_URL,
+ true, false, false, "my-boot-file");
+ opts->add(desc, DHCP6_OPTION_SPACE);
+ opts->add(createOption<OptionUint32>(Option::V6,
+ D6O_INFORMATION_REFRESH_TIME,
+ false, true, false, 3600 + i),
+ DHCP6_OPTION_SPACE);
+ opts->add(createAddressOption<Option6AddrLst>(D6O_SIP_SERVERS_ADDR,
+ false, false, false,
+ addr.toText()),
+ DHCP6_OPTION_SPACE);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(4);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(4, page.size());
+ host_id = page[3]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(1, page.size());
+ host_id = page[0]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage4Subnets() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 5 times. Get hosts from subnet1 with page size 3.
+ // Make sure the right hosts are returned and in expected page
+ // sizes (3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 10; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // First subnet.
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2], all_pages[i]);
+ }
+
+ all_pages.clear();
+
+ // Second subnet.
+ ++subnet4;
+
+ // Get first page.
+ idx = 0;
+ host_id = 0;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(subnet4, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2 + 1], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage6Subnets() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 5 times. Get hosts from subnet1 with page size 3.
+ // Make sure the right hosts are returned and in expected page
+ // sizes (3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 10; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // First subnet.
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2], all_pages[i]);
+ }
+
+ all_pages.clear();
+
+ // Second subnet.
+ ++subnet6;
+
+ // Get first page.
+ idx = 0;
+ host_id = 0;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second and last pages.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(subnet6, idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 5; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i * 2 + 1], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage4All() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 4 times. Get all hosts with page size 3.
+ // Make sure all hosts are returned and in expected page
+ // sizes (3, 3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_HWADDR;
+ IOAddress addr("192.0.2.0");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 8; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), id);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get last page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage4(idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 8; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetPage6All() {
+ // From the ticket: add one host to subnet1, add one host to subnet2.
+ // repeat 4 times. Get all hosts with page size 3.
+ // Make sure all hosts are returned and in expected page
+ // sizes (3, 3, then 2).
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create some hosts...
+ const Host::IdentifierType& id = Host::IDENT_DUID;
+ IOAddress addr("2001:db8:1::");
+ SubnetID subnet4(4);
+ SubnetID subnet6(6);
+ vector<HostPtr> hosts;
+ for (unsigned i = 0; i < 8; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost6(addr.toText(), id, false);
+ host->setIPv4SubnetID(subnet4 + (i & 1));
+ host->setIPv6SubnetID(subnet6 + (i & 1));
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ hosts.push_back(host);
+ }
+
+ // Get first page.
+ size_t idx(1);
+ uint64_t host_id(0);
+ HostPageSize page_size(3);
+ ConstHostCollection page;
+ ConstHostCollection all_pages;
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+ ASSERT_NE(0, host_id);
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get second page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(3, page.size());
+ host_id = page[2]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Get last page.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(2, page.size());
+ host_id = page[1]->getHostId();
+
+ std::copy(page.begin(), page.end(), std::back_inserter(all_pages));
+
+ // Verify we have everything.
+ ASSERT_NO_THROW(page = hdsptr_->getPage6(idx, host_id, page_size));
+ ASSERT_EQ(0, page.size());
+
+ // hosts are sorted by generated host_id (which is an auto increment for
+ // MySql and PostgreSql) so the hosts must be sorted by host identifier
+ std::sort(all_pages.begin(), all_pages.end(), compareHostsIdentifier);
+
+ // Verify we got what we expected.
+ for (size_t i = 0; i < 8; ++i) {
+ HostDataSourceUtils::compareHosts(hosts[i], all_pages[i]);
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetByIPv4(const Host::IdentifierType& id) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", id);
+ HostPtr host3 = HostDataSourceUtils::initializeHost4("192.0.2.3", id);
+ HostPtr host4 = HostDataSourceUtils::initializeHost4("192.0.2.4", id);
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+ SubnetID subnet2 = host2->getIPv4SubnetID();
+ SubnetID subnet3 = host3->getIPv4SubnetID();
+ SubnetID subnet4 = host4->getIPv4SubnetID();
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+ ConstHostPtr from_hds2 = hdsptr_->get4(subnet2, IOAddress("192.0.2.2"));
+ ConstHostPtr from_hds3 = hdsptr_->get4(subnet3, IOAddress("192.0.2.3"));
+ ConstHostPtr from_hds4 = hdsptr_->get4(subnet4, IOAddress("192.0.2.4"));
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+ HostDataSourceUtils::compareHosts(host3, from_hds3);
+ HostDataSourceUtils::compareHosts(host4, from_hds4);
+
+ // Ok, finally let's check that getting by a different address
+ // will not work.
+ EXPECT_FALSE(hdsptr_->get4(subnet1, IOAddress("192.0.1.5")));
+}
+
+void
+GenericHostDataSourceTest::testGet4ByIdentifier(
+ const Host::IdentifierType& identifier_type) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", identifier_type);
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("192.0.2.2", identifier_type);
+
+ // Sanity check: make sure the hosts have different identifiers..
+ ASSERT_FALSE(host1->getIdentifier() == host2->getIdentifier());
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+ SubnetID subnet2 = host2->getIPv4SubnetID();
+
+ ConstHostPtr from_hds1 =
+ hdsptr_->get4(subnet1, identifier_type, &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ ConstHostPtr from_hds2 =
+ hdsptr_->get4(subnet2, identifier_type, &host2->getIdentifier()[0],
+ host2->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testHWAddrNotClientId() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with HW address
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ ASSERT_TRUE(host->getHWAddress());
+ ASSERT_FALSE(host->getDuid());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ DuidPtr duid = HWAddrToDuid(host->getHWAddress());
+
+ // Get the host by HW address (should succeed)
+ ConstHostPtr by_hwaddr =
+ hdsptr_->get4(subnet, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Get the host by DUID (should fail)
+ ConstHostPtr by_duid =
+ hdsptr_->get4(subnet, Host::IDENT_DUID, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ EXPECT_TRUE(by_hwaddr);
+ EXPECT_FALSE(by_duid);
+}
+
+void
+GenericHostDataSourceTest::testClientIdNotHWAddr() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with client-id
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID);
+ ASSERT_FALSE(host->getHWAddress());
+ ASSERT_TRUE(host->getDuid());
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ HWAddrPtr hwaddr = DuidToHWAddr(host->getDuid());
+
+ // Get the host by DUID (should succeed)
+ ConstHostPtr by_duid =
+ hdsptr_->get4(subnet, Host::IDENT_DUID, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Get the host by HW address (should fail)
+ ConstHostPtr by_hwaddr =
+ hdsptr_->get4(subnet, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ EXPECT_TRUE(by_duid);
+ EXPECT_FALSE(by_hwaddr);
+}
+
+void
+GenericHostDataSourceTest::testHostname(std::string name, int num) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Initialize the address to 192.0.2.0 (this will be bumped
+ // up to 192.0.2.1 in the first iteration)
+ IOAddress addr("192.0.2.0");
+
+ vector<HostPtr> hosts;
+
+ // Prepare a vector of hosts with unique hostnames
+ for (int i = 0; i < num; ++i) {
+ addr = IOAddress::increase(addr);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4(addr.toText(), Host::IDENT_DUID);
+
+ stringstream hostname;
+ hostname.str("");
+ if (num > 1) {
+ hostname << i;
+ }
+ hostname << name;
+ host->setHostname(hostname.str());
+
+ hosts.push_back(host);
+ }
+
+ // Now add them all to the host data source.
+ for (vector<HostPtr>::const_iterator it = hosts.begin(); it != hosts.end();
+ ++it) {
+ // Try to add both of the to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(*it));
+ }
+
+ // And finally retrieve them one by one and check
+ // if the hostname was preserved.
+ for (vector<HostPtr>::const_iterator it = hosts.begin(); it != hosts.end();
+ ++it) {
+ ConstHostPtr from_hds;
+ ASSERT_NO_THROW(from_hds = hdsptr_->get4((*it)->getIPv4SubnetID(),
+ (*it)->getIPv4Reservation()));
+ ASSERT_TRUE(from_hds);
+
+ EXPECT_EQ((*it)->getHostname(), from_hds->getHostname());
+ }
+}
+
+void
+GenericHostDataSourceTest::testUserContext(ConstElementPtr user_context) {
+
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID);
+ ASSERT_TRUE(host); // Make sure the host is generated properly.
+ host->setContext(user_context);
+ SubnetID subnet = host->getIPv4SubnetID();
+
+ // Try to add it to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Retrieve it.
+ ConstHostPtr from_hds = hdsptr_->get4(subnet, IOAddress("192.0.2.1"));
+ ASSERT_TRUE(from_hds);
+
+ // Finally, let's check if what we got makes any sense.
+ HostDataSourceUtils::compareHosts(host, from_hds);
+
+ // Retry with IPv6
+ host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+ ASSERT_TRUE(host);
+ ASSERT_TRUE(host->getHWAddress());
+ host->setContext(user_context);
+ host->setHostname("foo.example.com");
+ subnet = host->getIPv6SubnetID();
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ from_hds = hdsptr_->get6(subnet, Host::IDENT_HWADDR,
+ &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+
+ HostDataSourceUtils::compareHosts(host, from_hds);
+}
+
+void
+GenericHostDataSourceTest::testMultipleSubnets(int subnets,
+ const Host::IdentifierType& id) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", id);
+ host->setIPv6SubnetID(SUBNET_ID_UNUSED);
+
+ for (int i = 0; i < subnets; ++i) {
+ host->setIPv4SubnetID(i + 1000);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ }
+
+ // Now check that the reservations can be retrieved by IPv4 address from
+ // each subnet separately.
+ for (int i = 0; i < subnets; ++i) {
+ // Try to retrieve the host by IPv4 address.
+ ConstHostPtr from_hds =
+ hdsptr_->get4(i + 1000, host->getIPv4Reservation());
+
+ ASSERT_TRUE(from_hds);
+ EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+
+ // Try to retrieve the host by either HW address of client-id
+ from_hds = hdsptr_->get4(i + 1000, id, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ EXPECT_EQ(i + 1000, from_hds->getIPv4SubnetID());
+ }
+
+ // Now check that they can be retrieved all at once, by IPv4 address.
+ ConstHostCollection all_by_addr = hdsptr_->getAll4(IOAddress("192.0.2.1"));
+ ASSERT_EQ(subnets, all_by_addr.size());
+
+ // Verify that the values returned are proper.
+ int i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_addr.begin();
+ it != all_by_addr.end(); ++it) {
+ EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+ }
+
+ // Finally, check that the hosts can be retrieved by HW address or DUID
+ ConstHostCollection all_by_id = hdsptr_->getAll(
+ id, &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_EQ(subnets, all_by_id.size());
+
+ // Check that the returned values are as expected.
+ i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_id.begin();
+ it != all_by_id.end(); ++it) {
+ EXPECT_EQ(IOAddress("192.0.2.1"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv4SubnetID());
+ }
+}
+
+void
+GenericHostDataSourceTest::testGet6ByHWAddr() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_HWADDR, false);
+
+ // Sanity check: make sure the hosts have different HW addresses.
+ ASSERT_TRUE(host1->getHWAddress());
+ ASSERT_TRUE(host2->getHWAddress());
+
+ HostDataSourceUtils::compareHwaddrs(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ SubnetID subnet2 = host2->getIPv6SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, Host::IDENT_HWADDR,
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, Host::IDENT_HWADDR,
+ &host2->getIdentifier()[0],
+ host2->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testGet6ByClientId() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+
+ // Sanity check: make sure the hosts have different HW addresses.
+ ASSERT_TRUE(host1->getDuid());
+ ASSERT_TRUE(host2->getDuid());
+
+ HostDataSourceUtils::compareDuids(host1, host2, false);
+
+ // Try to add both of them to the host data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ SubnetID subnet2 = host2->getIPv6SubnetID();
+
+ ConstHostPtr from_hds1 = hdsptr_->get6(subnet1, Host::IDENT_DUID,
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ ConstHostPtr from_hds2 = hdsptr_->get6(subnet2, Host::IDENT_DUID,
+ &host2->getIdentifier()[0],
+ host2->getIdentifier().size());
+
+ // Now let's check if we got what we expected.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+}
+
+void
+GenericHostDataSourceTest::testSubnetId6(int subnets, Host::IdentifierType id) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ HostPtr host;
+ IOAddress current_address("2001:db8::");
+ ASSERT_LT(subnets, std::numeric_limits<uint16_t>::max()) << "Too many subnets. Broken test?";
+ for (int i = 0; i < subnets; ++i) {
+ // Last boolean value set to false indicates that the same identifier
+ // must be used for each generated host.
+ host = HostDataSourceUtils::initializeHost6(current_address.toText(),
+ id, true, false, "");
+
+ host->setIPv4SubnetID(i + 1000);
+ host->setIPv6SubnetID(i + 1000);
+
+ // Check that the same host can have reservations in multiple subnets.
+ EXPECT_NO_THROW(hdsptr_->add(host));
+
+ // Increase address to make sure we don't assign the same address
+ // in different subnets.
+ current_address = offsetAddress(current_address, (uint128_t(1) << 80));
+ }
+
+ // Check that the reservations can be retrieved from each subnet separately.
+ for (int i = 0; i < subnets; ++i) {
+ // Try to retrieve the host
+ ConstHostPtr from_hds = hdsptr_->get6(i + 1000, id, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+
+ ASSERT_TRUE(from_hds) << "failed for i=" << i;
+ EXPECT_EQ(i + 1000, from_hds->getIPv6SubnetID());
+ }
+
+ // Check that the hosts can all be retrieved by HW address or DUID
+ ConstHostCollection all_by_id = hdsptr_->getAll(id, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(subnets, all_by_id.size());
+
+ // Check that the returned values are as expected.
+ int i = 0;
+ for (ConstHostCollection::const_iterator it = all_by_id.begin();
+ it != all_by_id.end(); ++it) {
+ EXPECT_EQ(IOAddress("0.0.0.0"), (*it)->getIPv4Reservation());
+ EXPECT_EQ(1000 + i++, (*it)->getIPv6SubnetID());
+ }
+}
+
+void
+GenericHostDataSourceTest::testGetByIPv6(Host::IdentifierType id, bool prefix) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8:1::",
+ id, prefix, "key##1");
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8:2::",
+ id, prefix, "key##2");
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8:3::",
+ id, prefix, "key##3");
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8:4::",
+ id, prefix, "key##4");
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // Are we talking about addresses or prefixes?
+ uint8_t len = prefix ? 64 : 128;
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get6(IOAddress("2001:db8:1::"), len);
+ ConstHostPtr from_hds2 = hdsptr_->get6(IOAddress("2001:db8:2::"), len);
+ ConstHostPtr from_hds3 = hdsptr_->get6(IOAddress("2001:db8:3::"), len);
+ ConstHostPtr from_hds4 = hdsptr_->get6(IOAddress("2001:db8:4::"), len);
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+ HostDataSourceUtils::compareHosts(host3, from_hds3);
+ HostDataSourceUtils::compareHosts(host4, from_hds4);
+
+ // Ok, finally let's check that getting by a different address
+ // will not work.
+ EXPECT_FALSE(hdsptr_->get6(IOAddress("2001:db8::5"), len));
+}
+
+void
+GenericHostDataSourceTest::testGetBySubnetIPv6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a couple of hosts...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_DUID, true);
+ addIPv6Address(host1, "2001:db8:1::10");
+ ASSERT_NO_THROW(addTestOptions(host1, true, DHCP6_ONLY));
+
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8:2::", Host::IDENT_DUID, true);
+ addIPv6Address(host2, "2001:db8:1::20");
+ HostPtr host3 = HostDataSourceUtils::initializeHost6("2001:db8:3::", Host::IDENT_DUID, true);
+ addIPv6Address(host3, "2001:db8:1::30");
+ HostPtr host4 = HostDataSourceUtils::initializeHost6("2001:db8:4::", Host::IDENT_DUID, true);
+ addIPv6Address(host4, "2001:db8:1::40");
+
+ // ... and add them to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+ ASSERT_NO_THROW(hdsptr_->add(host3));
+ ASSERT_NO_THROW(hdsptr_->add(host4));
+
+ // And then try to retrieve them back.
+ ConstHostPtr from_hds1 = hdsptr_->get6(host1->getIPv6SubnetID(), IOAddress("2001:db8:1::"));
+ ConstHostPtr from_hds2 = hdsptr_->get6(host2->getIPv6SubnetID(), IOAddress("2001:db8:2::"));
+ ConstHostPtr from_hds3 = hdsptr_->get6(host3->getIPv6SubnetID(), IOAddress("2001:db8:3::"));
+ ConstHostPtr from_hds4 = hdsptr_->get6(host4->getIPv6SubnetID(), IOAddress("2001:db8:4::"));
+
+ // Make sure we got something back.
+ ASSERT_TRUE(from_hds1);
+ ASSERT_TRUE(from_hds2);
+ ASSERT_TRUE(from_hds3);
+ ASSERT_TRUE(from_hds4);
+
+ // Then let's check that what we got seems correct.
+ HostDataSourceUtils::compareHosts(host1, from_hds1);
+ HostDataSourceUtils::compareHosts(host2, from_hds2);
+ HostDataSourceUtils::compareHosts(host3, from_hds3);
+ HostDataSourceUtils::compareHosts(host4, from_hds4);
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicate6WithSameDUID() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_DUID, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicate6WithSameHWAddr() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_HWADDR, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicateIPv6() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservation.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_HWADDR, true);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Create a host with a different identifier but the same IPv6 address. An attempt
+ // to create the reservation for the same IPv6 address should fail.
+ host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_HWADDR, true);
+ EXPECT_THROW(hdsptr_->add(host), DuplicateEntry);
+}
+
+void
+GenericHostDataSourceTest::testAllowDuplicateIPv6() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ ASSERT_TRUE(hdsptr_->setIPReservationsUnique(false));
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_HWADDR, true, true);
+ addIPv6Address(host, "2001:db8:2::");
+ auto host_id = host->getHostId();
+ auto subnet_id = host->getIPv6SubnetID();
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception because the
+ // HWADDR is the same.
+ host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_HWADDR, true, false);
+ addIPv6Address(host, "2001:db8:2::");
+ host->setHostId(++host_id);
+ host->setIPv6SubnetID(subnet_id);
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should succeed because we permitted to create
+ // multiple IP reservations for the same IP address but different
+ // identifier.
+ host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_HWADDR, true, true);
+ host->setHostId(++host_id);
+ host->setIPv6SubnetID(subnet_id);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = hdsptr_->getAll6(host->getIPv6SubnetID(), IOAddress("2001:db8:1::")));
+ EXPECT_EQ(2, returned.size());
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ // Let's now try to delete the hosts by subnet_id and address.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = hdsptr_->del(subnet_id, IOAddress("2001:db8:1::")));
+ ASSERT_TRUE(deleted);
+ ASSERT_NO_THROW(returned = hdsptr_->getAll6(host->getIPv6SubnetID(), IOAddress("2001:db8:1::")));
+ EXPECT_TRUE(returned.empty());
+}
+
+void
+GenericHostDataSourceTest::testAddDuplicateIPv4() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID);
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception.
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should be rejected because of duplicated
+ // address.
+ ASSERT_NO_THROW(host->setIdentifier("01:02:03:04:05:06", "hw-address"));
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // Modify address to avoid its duplication and make sure
+ // we can now add the host.
+ ASSERT_NO_THROW(host->setIPv4Reservation(IOAddress("192.0.2.3")));
+ EXPECT_NO_THROW(hdsptr_->add(host));
+}
+
+void
+GenericHostDataSourceTest::testAllowDuplicateIPv4() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ ASSERT_TRUE(hdsptr_->setIPReservationsUnique(false));
+
+ // Create a host reservations.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, true);
+ auto host_id = host->getHostId();
+ auto subnet_id = host->getIPv4SubnetID();
+
+ // Add this reservation once.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Then try to add it again, it should throw an exception because the
+ // DUID is the same.
+ host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, false);
+ host->setHostId(++host_id);
+ host->setIPv4SubnetID(subnet_id);
+ ASSERT_THROW(hdsptr_->add(host), DuplicateEntry);
+
+ // This time use a different host identifier and try again.
+ // This update should succeed because we permitted to create
+ // multiple IP reservations for the same IP address but different
+ // identifier.
+ host = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_DUID, true);
+ host->setHostId(++host_id);
+ host->setIPv4SubnetID(subnet_id);
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ ConstHostCollection returned;
+ ASSERT_NO_THROW(returned = hdsptr_->getAll4(host->getIPv4SubnetID(), IOAddress("192.0.2.1")));
+ EXPECT_EQ(2, returned.size());
+ EXPECT_NE(returned[0]->getIdentifierAsText(), returned[1]->getIdentifierAsText());
+
+ // Let's now try to delete the hosts by subnet_id and address.
+ bool deleted = false;
+ ASSERT_NO_THROW(deleted = hdsptr_->del(subnet_id, IOAddress("192.0.2.1")));
+ ASSERT_TRUE(deleted);
+ ASSERT_NO_THROW(returned = hdsptr_->getAll4(host->getIPv4SubnetID(), IOAddress("192.0.2.1")));
+ EXPECT_TRUE(returned.empty());
+}
+
+void
+GenericHostDataSourceTest::testDisallowDuplicateIP() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ // The backend does not support switching to the mode in which multiple
+ // reservations for the same address can be created.
+ EXPECT_FALSE(hdsptr_->setIPReservationsUnique(false));
+
+ // The default mode still can be used.
+ EXPECT_TRUE(hdsptr_->setIPReservationsUnique(true));
+}
+
+void
+GenericHostDataSourceTest::testAddr6AndPrefix() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host reservations with prefix reservation (prefix = true)
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::", Host::IDENT_DUID, true);
+
+ // Create IPv6 reservation (for an address) and add it to the host
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2"), 128);
+ host->addReservation(resv);
+
+ // Add this reservation
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Get this host by DUID
+ ConstHostPtr from_hds =
+ hdsptr_->get6(host->getIPv6SubnetID(), Host::IDENT_DUID,
+ &host->getIdentifier()[0], host->getIdentifier().size());
+
+ // Make sure we got something back
+ ASSERT_TRUE(from_hds);
+
+ // Check if reservations are the same
+ HostDataSourceUtils::compareReservations6(host->getIPv6Reservations(),
+ from_hds->getIPv6Reservations());
+}
+
+void
+GenericHostDataSourceTest::testMultipleReservations() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ uint8_t len = 128;
+
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+
+ // Add some reservations
+ IPv6Resrv resv1(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::6"), len);
+ IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::7"), len);
+ IPv6Resrv resv3(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::8"), len);
+ IPv6Resrv resv4(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::9"), len);
+
+ host->addReservation(resv1);
+ host->addReservation(resv2);
+ host->addReservation(resv3);
+ host->addReservation(resv4);
+
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ ConstHostPtr from_hds = hdsptr_->get6(IOAddress("2001:db8::1"), len);
+
+ // Make sure we got something back
+ ASSERT_TRUE(from_hds);
+
+ // Check if hosts are the same
+ HostDataSourceUtils::compareHosts(host, from_hds);
+}
+
+void
+GenericHostDataSourceTest::testMultipleReservationsDifferentOrder() {
+ // Make sure we have the pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+ uint8_t len = 128;
+
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID, false, "key##1");
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID, false, "key##1");
+
+ // Add some reservations
+ IPv6Resrv resv1(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::6"), len);
+ IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::7"), len);
+ IPv6Resrv resv3(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::8"), len);
+ IPv6Resrv resv4(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::9"), len);
+
+ host1->addReservation(resv1);
+ host1->addReservation(resv2);
+ host1->addReservation(resv3);
+ host1->addReservation(resv4);
+
+ host2->addReservation(resv4);
+ host2->addReservation(resv3);
+ host2->addReservation(resv2);
+ host2->addReservation(resv1);
+
+ // Check if reservations are the same
+ HostDataSourceUtils::compareReservations6(host1->getIPv6Reservations(),
+ host2->getIPv6Reservations());
+}
+
+void
+GenericHostDataSourceTest::testOptionsReservations4(const bool formatted,
+ ConstElementPtr user_context) {
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host, formatted, DHCP4_ONLY, user_context));
+ // Insert host and the options into respective tables.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv4SubnetID();
+
+ // getAll4(subnet_id)
+ ConstHostCollection hosts_by_subnet = hdsptr_->getAll4(subnet_id);
+ ASSERT_EQ(1, hosts_by_subnet.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_subnet.begin()));
+
+ auto returned_host = *hosts_by_subnet.begin();
+ EXPECT_FALSE(returned_host->getCfgOption4()->isEncapsulated());
+ ASSERT_NO_THROW(returned_host->encapsulateOptions());
+ auto cfg_option = returned_host->getCfgOption4();
+
+ auto option43 = cfg_option->get(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
+ ASSERT_TRUE(option43.option_);
+
+ EXPECT_TRUE(cfg_option->get("vendor-encapsulated-options-space", 1).option_);
+
+ auto option43_1 = option43.option_->getOption(1);
+ EXPECT_TRUE(option43_1);
+
+ // getAll4(address)
+ ConstHostCollection hosts_by_addr =
+ hdsptr_->getAll4(host->getIPv4Reservation());
+ ASSERT_EQ(1, hosts_by_addr.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_addr.begin()));
+
+ // get4(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id =
+ hdsptr_->get4(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // get4(subnet_id, address)
+ ConstHostPtr host_by_addr =
+ hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_addr));
+}
+
+void
+GenericHostDataSourceTest::testOptionsReservations6(const bool formatted,
+ ConstElementPtr user_context) {
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host, formatted, DHCP6_ONLY, user_context));
+ // Insert host, options and IPv6 reservations into respective tables.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // get6(subnet_id, identifier_type, identifier, identifier_size)
+ ConstHostPtr host_by_id =
+ hdsptr_->get6(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_id));
+
+ // get6(address, prefix_len)
+ ConstHostPtr host_by_addr = hdsptr_->get6(IOAddress("2001:db8::1"), 128);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, host_by_addr));
+}
+
+void
+GenericHostDataSourceTest::testOptionsReservations46(const bool formatted) {
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host, formatted, DHCP4_AND_DHCP6));
+ // Insert host, options and IPv6 reservations into respective tables.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // getAll6(subnet_id)
+ ConstHostCollection hosts_by_subnet = hdsptr_->getAll6(subnet_id);
+ EXPECT_EQ(1, hosts_by_subnet.size());
+ // Don't compare as getAll6() returns the v6 part only.
+
+ // getAll(identifier_type, identifier, identifier_size)
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+}
+
+void
+GenericHostDataSourceTest::testMultipleClientClasses4() {
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
+
+ // Add v4 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class4_" << i;
+ host->addClientClass4(os.str());
+ }
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv4SubnetID();
+
+ // Fetch the host via:
+ // getAll(const Host::IdentifierType, const uint8_t* identifier_begin,
+ // const size_t identifier_len) const;
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // getAll4(const asiolink::IOAddress& address) const;
+ hosts_by_id = hdsptr_->getAll4(IOAddress("192.0.2.5"));
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // get4(const SubnetID& subnet_id, const Host::IdentifierType&
+ // identifier_type,
+ // const uint8_t* identifier_begin, const size_t identifier_len) const;
+ ConstHostPtr from_hds =
+ hdsptr_->get4(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+
+ // Fetch the host via:
+ // get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+ from_hds = hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::testMultipleClientClasses6() {
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+
+ // Add v6 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class6_" << i;
+ host->addClientClass6(os.str());
+ }
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // getAll(const Host::IdentifierType& identifier_type,
+ // const uint8_t* identifier_begin,
+ // const size_t identifier_len) const;
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via:
+ // get6(const SubnetID& subnet_id, const Host::IdentifierType&
+ // identifier_type,
+ // const uint8_t* identifier_begin, const size_t identifier_len) const;
+ ConstHostPtr from_hds =
+ hdsptr_->get6(subnet_id, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+
+ // Fetch the host via:
+ // get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+ from_hds = hdsptr_->get6(IOAddress("2001:db8::1"), 128);
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::testMultipleClientClassesBoth() {
+ /// Add host reservation with a multiple v4 and v6 client-classes,
+ /// retrieve it and make sure that all client classes are retrieved
+ /// properly.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_HWADDR, false);
+
+ // Add v4 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class4_" << i;
+ host->addClientClass4(os.str());
+ }
+
+ // Add v6 classes to the host.
+ for (int i = 0; i < 4; ++i) {
+ std::ostringstream os;
+ os << "class6_" << i;
+ host->addClientClass6(os.str());
+ }
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv6SubnetID();
+
+ // Fetch the host from the source.
+ ConstHostPtr from_hds =
+ hdsptr_->get6(subnet_id, Host::IDENT_HWADDR, &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+
+ // Verify they match.
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::testMessageFields4() {
+ ASSERT_TRUE(hdsptr_);
+
+ // Create the Host object.
+ HostPtr host = HostDataSourceUtils::initializeHost4("192.0.2.5", Host::IDENT_HWADDR);
+ // And assign values for DHCPv4 message fields.
+ ASSERT_NO_THROW({
+ host->setNextServer(IOAddress("10.1.1.1"));
+ host->setServerHostname("server-name.example.org");
+ host->setBootFileName("bootfile.efi");
+ });
+
+ // Add the host.
+ ASSERT_NO_THROW(hdsptr_->add(host));
+
+ // Subnet id will be used in queries to the database.
+ SubnetID subnet_id = host->getIPv4SubnetID();
+
+ // Fetch the host via:
+ // getAll(const Host::IdentifierType, const uint8_t* identifier_begin,
+ // const size_t identifier_len) const;
+ ConstHostCollection hosts_by_id =
+ hdsptr_->getAll(host->getIdentifierType(), &host->getIdentifier()[0],
+ host->getIdentifier().size());
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // getAll4(const asiolink::IOAddress& address) const;
+ hosts_by_id = hdsptr_->getAll4(IOAddress("192.0.2.5"));
+ ASSERT_EQ(1, hosts_by_id.size());
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, *hosts_by_id.begin()));
+
+ // Fetch the host via
+ // get4(const SubnetID& subnet_id, const Host::IdentifierType&
+ // identifier_type,
+ // const uint8_t* identifier_begin, const size_t identifier_len) const;
+ ConstHostPtr from_hds =
+ hdsptr_->get4(subnet_id, host->getIdentifierType(),
+ &host->getIdentifier()[0], host->getIdentifier().size());
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+
+ // Fetch the host via:
+ // get4(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+ from_hds = hdsptr_->get4(subnet_id, IOAddress("192.0.2.5"));
+ ASSERT_TRUE(from_hds);
+ ASSERT_NO_FATAL_FAILURE(HostDataSourceUtils::compareHosts(host, from_hds));
+}
+
+void
+GenericHostDataSourceTest::stressTest(unsigned int nOfHosts /* = 0xfffdU */) {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Make sure the variable part of the generated address fits in a 16-bit
+ // field.
+ ASSERT_LE(nOfHosts, 0xfffdU);
+
+ // Create hosts.
+ std::vector<HostPtr> hosts;
+ hosts.reserve(nOfHosts);
+ for (unsigned int i = 0x0001U; i < 0x0001U + nOfHosts; ++i) {
+ /// @todo: Check if this is written in hexadecimal format.
+ std::stringstream ss;
+ std::string n_host;
+ ss << std::hex << i;
+ ss >> n_host;
+
+ const std::string prefix = std::string("2001:db8::") + n_host;
+ hosts.push_back(HostDataSourceUtils::initializeHost6(prefix,
+ Host::IDENT_HWADDR, false, "key##1"));
+
+ IPv6ResrvRange range = hosts.back()->getIPv6Reservations();
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ EXPECT_TRUE(HostDataSourceUtils::reservationExists
+ (IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress(prefix)), range));
+ }
+ const size_t hosts_size = hosts.size();
+
+ std::cout << "Starting to add hosts..." << std::endl;
+ struct timespec start, end;
+ start = (struct timespec){0, 0};
+ end = (struct timespec){0, 0};
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+ for (std::vector<HostPtr>::const_iterator it = hosts.begin();
+ it != hosts.end(); it++) {
+ ASSERT_NO_THROW(hdsptr_->add(*it));
+ }
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+ double s = static_cast<double>(end.tv_sec - start.tv_sec) +
+ static_cast<double>(end.tv_nsec - start.tv_nsec) / 1e9;
+ std::cout << "Adding " << hosts_size
+ << (hosts_size == 1 ? " host" : " hosts") << " took "
+ << std::fixed << std::setprecision(2) << s << " seconds."
+ << std::endl;
+
+ // And then try to retrieve them back.
+ std::cout << "Starting to retrieve hosts..." << std::endl;
+ start = (struct timespec){0, 0};
+ end = (struct timespec){0, 0};
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &start);
+ for (std::vector<HostPtr>::const_iterator it = hosts.begin();
+ it != hosts.end(); it++) {
+ IPv6ResrvRange range = (*it)->getIPv6Reservations();
+ // This get6() call is particularly useful to test because it involves a
+ // subquery for MySQL and PostgreSQL.
+ ConstHostPtr from_hds =
+ hdsptr_->get6(range.first->second.getPrefix(), 128);
+ ASSERT_TRUE(from_hds);
+ HostDataSourceUtils::compareHosts(*it, from_hds);
+ }
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
+ s = static_cast<double>(end.tv_sec - start.tv_sec) +
+ static_cast<double>(end.tv_nsec - start.tv_nsec) / 1e9;
+ std::cout << "Retrieving " << hosts_size
+ << (hosts_size == 1 ? " host" : " hosts") << " took "
+ << std::fixed << std::setprecision(2) << s << " seconds."
+ << std::endl;
+}
+
+void
+GenericHostDataSourceTest::testDeleteByAddr4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+
+ // Now try to delete it: del(subnet-id, addr4)
+ EXPECT_TRUE(hdsptr_->del(subnet1, IOAddress("192.0.2.1")));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1, IOAddress("192.0.2.1"));
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del(subnet1, IOAddress("192.0.2.1")));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testDeleteById4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+// Test checks when a IPv4 host with options is deleted that the options are
+// deleted as well.
+void
+GenericHostDataSourceTest::testDeleteById4Options() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v4 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR);
+ // Add a bunch of DHCPv4 and DHCPv6 options for the host.
+ ASSERT_NO_THROW(addTestOptions(host1, true, DHCP4_ONLY));
+ // Insert host and the options into respective tables.
+
+ SubnetID subnet1 = host1->getIPv4SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // There must be some options
+ EXPECT_NE(0, countDBOptions4());
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get4(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBOptions4());
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del4(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testDeleteById6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v6 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1",
+ Host::IDENT_DUID, false, "key##1");
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testDeleteById6Options() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Let's create a v6 host...
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ SubnetID subnet1 = host1->getIPv6SubnetID();
+ ASSERT_NO_THROW(addTestOptions(host1, true, DHCP6_ONLY));
+
+ // ... and add it to the data source.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // Check that the options are stored...
+ EXPECT_NE(0, countDBOptions6());
+
+ // ... and so are v6 reservations.
+ EXPECT_NE(0, countDBReservations6());
+
+ // And then try to retrieve it back.
+ ConstHostPtr before = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Now try to delete it: del4(subnet4-id, identifier-type, identifier)
+ EXPECT_TRUE(hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+
+ // Check if it's still there.
+ ConstHostPtr after = hdsptr_->get6(subnet1,
+ host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size());
+
+ // Make sure the host was there before...
+ EXPECT_TRUE(before);
+
+ // ... and that it's gone after deletion.
+ EXPECT_FALSE(after);
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBOptions6());
+
+ // Check the options are indeed gone.
+ EXPECT_EQ(0, countDBReservations6());
+
+ // An attempt to delete it should not cause an exception. It
+ // should return false.
+ bool result = false;
+ EXPECT_NO_THROW(result = hdsptr_->del6(subnet1, host1->getIdentifierType(),
+ &host1->getIdentifier()[0],
+ host1->getIdentifier().size()));
+ EXPECT_FALSE(result);
+}
+
+void
+GenericHostDataSourceTest::testMultipleHostsNoAddress4() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with zero IPv4 address.
+ HostPtr host1 = HostDataSourceUtils::initializeHost4("0.0.0.0", Host::IDENT_HWADDR);
+ host1->setIPv4SubnetID(1);
+ host1->setIPv6SubnetID(SUBNET_ID_UNUSED);
+ // Add the host to the database.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // An attempt to add this host again should fail due to client identifier
+ // duplication.
+ ASSERT_THROW(hdsptr_->add(host1), DuplicateEntry);
+
+ // Create another host with zero IPv4 address. Adding this host to the
+ // database should be successful because zero addresses are not counted
+ // in the unique index.
+ HostPtr host2 = HostDataSourceUtils::initializeHost4("0.0.0.0", Host::IDENT_HWADDR);
+ host2->setIPv4SubnetID(1);
+ host2->setIPv6SubnetID(SUBNET_ID_UNUSED);
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+}
+
+void
+GenericHostDataSourceTest::testMultipleHosts6() {
+ // Make sure we have a pointer to the host data source.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create first host.
+ HostPtr host1 = HostDataSourceUtils::initializeHost6("2001:db8::1", Host::IDENT_DUID, false);
+ host1->setIPv4SubnetID(SUBNET_ID_UNUSED);
+ host1->setIPv6SubnetID(1);
+ // Add the host to the database.
+ ASSERT_NO_THROW(hdsptr_->add(host1));
+
+ // An attempt to add this host again should fail due to client identifier
+ // duplication.
+ ASSERT_THROW(hdsptr_->add(host1), DuplicateEntry);
+
+ HostPtr host2 = HostDataSourceUtils::initializeHost6("2001:db8::2", Host::IDENT_DUID, false);
+ host2->setIPv4SubnetID(SUBNET_ID_UNUSED);
+ host2->setIPv6SubnetID(1);
+ // Add the host to the database.
+ ASSERT_NO_THROW(hdsptr_->add(host2));
+}
+
+void
+HostMgrDbLostCallbackTest::testNoCallbackOnOpenFailure() {
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ // Connect to the host backend.
+ ASSERT_THROW(HostMgr::addBackend(invalidConnectString()), DbOpenError);
+
+ io_service_->poll();
+
+ EXPECT_EQ(0, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndRecoveredCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndFailedCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ access = invalidConnectString();
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndRecoveredAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ access = invalidConnectString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ access = validConnectString();
+ access += extra;
+ CfgMgr::instance().clear();
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and recovered connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // No callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(1, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+}
+
+void
+HostMgrDbLostCallbackTest::testDbLostAndFailedAfterTimeoutCallback() {
+ // Set the connectivity lost callback.
+ isc::db::DatabaseConnection::db_lost_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_lost_callback, this, ph::_1);
+
+ // Set the connectivity recovered callback.
+ isc::db::DatabaseConnection::db_recovered_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_recovered_callback, this, ph::_1);
+
+ // Set the connectivity failed callback.
+ isc::db::DatabaseConnection::db_failed_callback_ =
+ std::bind(&HostMgrDbLostCallbackTest::db_failed_callback, this, ph::_1);
+
+ std::string access = validConnectString();
+ std::string extra = " max-reconnect-tries=3 reconnect-wait-time=1";
+ access += extra;
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Create the HostMgr.
+ HostMgr::create();
+
+ // Find the most recently opened socket. Our SQL client's socket should
+ // be the next one.
+ int last_open_socket = findLastSocketFd();
+
+ // Fill holes.
+ FillFdHoles holes(last_open_socket);
+
+ // Connect to the host backend.
+ ASSERT_NO_THROW(HostMgr::addBackend(access));
+
+ // Find the SQL client socket.
+ int sql_socket = findLastSocketFd();
+ ASSERT_TRUE(sql_socket > last_open_socket);
+
+ // Verify we can execute a query. We don't care about the answer.
+ ConstHostCollection hosts;
+ ASSERT_NO_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")));
+
+ access = invalidConnectString();
+ access += extra;
+ CfgMgr::instance().clear();
+ // by adding an invalid access will cause the manager factory to throw
+ // resulting in failure to recreate the manager
+ CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->setHostDbAccessString(access);
+
+ // Now close the sql socket out from under backend client
+ ASSERT_EQ(0, close(sql_socket));
+
+ // A query should fail with DbConnectionUnusable.
+ ASSERT_THROW(hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5")),
+ DbConnectionUnusable);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(1, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost connectivity callback should have been invoked.
+ EXPECT_EQ(2, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(0, db_failed_callback_called_);
+
+ sleep(1);
+
+ io_service_->poll();
+
+ // Our lost and failed connectivity callback should have been invoked.
+ EXPECT_EQ(3, db_lost_callback_called_);
+ EXPECT_EQ(0, db_recovered_callback_called_);
+ EXPECT_EQ(1, db_failed_callback_called_);
+}
+
+void
+// cppcheck-suppress unusedFunction
+HostMgrTest::SetUp() {
+ // Remove all configuration which may be dangling from the previous test.
+ CfgMgr::instance().clear();
+ // Recreate HostMgr instance. It drops any previous state.
+ HostMgr::create();
+ // Create HW addresses from the template.
+ const uint8_t mac_template[] = {
+ 0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+ };
+ for (uint8_t i = 0; i < 10; ++i) {
+ std::vector<uint8_t> vec(mac_template,
+ mac_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+ hwaddrs_.push_back(hwaddr);
+ }
+ // Create DUIDs from the template.
+ const uint8_t duid_template[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+ };
+ for (uint8_t i = 0; i < 10; ++i) {
+ std::vector<uint8_t> vec(duid_template,
+ duid_template + sizeof(mac_template));
+ vec[vec.size() - 1] = i;
+ DuidPtr duid(new DUID(vec));
+ duids_.push_back(duid);
+ }
+}
+
+CfgHostsPtr
+HostMgrTest::getCfgHosts() const {
+ return (CfgMgr::instance().getStagingCfg()->getCfgHosts());
+}
+
+void
+HostMgrTest::addHost4(BaseHostDataSource& data_source,
+ const HWAddrPtr& hwaddr,
+ const SubnetID& subnet_id,
+ const IOAddress& address) {
+ data_source.add(HostPtr(new Host(hwaddr->toText(false),
+ "hw-address", subnet_id, SUBNET_ID_UNUSED,
+ address)));
+}
+
+void
+HostMgrTest::addHost6(BaseHostDataSource& data_source,
+ const DuidPtr& duid,
+ const SubnetID& subnet_id,
+ const IOAddress& address,
+ const uint8_t prefix_len) {
+ HostPtr new_host(new Host(duid->toText(), "duid", SubnetID(1),
+ subnet_id, IOAddress::IPV4_ZERO_ADDRESS()));
+ new_host->addReservation(IPv6Resrv(prefix_len == 128 ? IPv6Resrv::TYPE_NA :
+ IPv6Resrv::TYPE_PD,
+ address, prefix_len));
+ data_source.add(new_host);
+}
+
+void
+HostMgrTest::addHost6(BaseHostDataSource& data_source,
+ const DuidPtr& duid,
+ const SubnetID& subnet_id,
+ const std::vector<IOAddress>& addresses,
+ const uint8_t prefix_len) {
+ HostPtr new_host(new Host(duid->toText(), "duid", SubnetID(1),
+ subnet_id, IOAddress::IPV4_ZERO_ADDRESS()));
+ for (const IOAddress& address : addresses) {
+ new_host->addReservation(IPv6Resrv(prefix_len == 128 ? IPv6Resrv::TYPE_NA :
+ IPv6Resrv::TYPE_PD,
+ address, prefix_len));
+ }
+
+ data_source.add(new_host);
+}
+
+void
+HostMgrTest::testGetAll(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[1]->hwaddr_[0],
+ hwaddrs_[1]->hwaddr_.size());
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same HW address. They differ by the IP
+ // address reserved and the IPv4 subnet.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[0], SubnetID(10), IOAddress("192.0.3.10"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching HW address is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[1]->hwaddr_[0],
+ hwaddrs_[1]->hwaddr_.size());
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct HW address, there should be two reservations.
+ hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_EQ(2, hosts.size());
+
+ // We don't know the order in which the reservations are returned so
+ // we have to match with any of the two reservations returned.
+
+ // Look for the first reservation.
+ bool found = false;
+ for (unsigned i = 0; i < 2; ++i) {
+ if (hosts[i]->getIPv4Reservation() == IOAddress("192.0.2.5")) {
+ ASSERT_EQ(1, hosts[i]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.2.5"
+ " not found using getAll method";
+ }
+
+ // Look for the second reservation.
+ found = false;
+ for (unsigned i = 0; i < 2; ++i) {
+ if (hosts[i]->getIPv4Reservation() == IOAddress("192.0.3.10")) {
+ ASSERT_EQ(10, hosts[i]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.3.10"
+ " not found using getAll method";
+ }
+
+ // Check handling of operation target.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Primary source target.
+ hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ found = false;
+ for (unsigned i = 0; i < hosts.size(); ++i) {
+ if (hosts[i]->getIPv4Reservation() == IOAddress("192.0.2.5")) {
+ ASSERT_EQ(1, hosts[i]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.2.5"
+ " not found using getAll method with PRIMARY_SOURCE operation"
+ " target";
+ }
+ }
+ if (is_second_source_primary) {
+ found = false;
+ for (unsigned i = 0; i < hosts.size(); ++i) {
+ if (hosts[i]->getIPv4Reservation() == IOAddress("192.0.3.10")) {
+ ASSERT_EQ(10, hosts[i]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.3.10"
+ " not found using getAll method with PRIMARY_SOURCE operation"
+ " target";
+ }
+ }
+
+ // Alternate sources target.
+ hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+
+ if (!is_first_source_primary) {
+ found = false;
+ for (unsigned i = 0; i < hosts.size(); ++i) {
+ if (hosts[i]->getIPv4Reservation() == IOAddress("192.0.2.5")) {
+ ASSERT_EQ(1, hosts[i]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.2.5"
+ " not found using getAll method with PRIMARY_SOURCE operation"
+ " target";
+ }
+ }
+ if (!is_second_source_primary) {
+ found = false;
+ for (unsigned i = 0; i < hosts.size(); ++i) {
+ if (hosts[i]->getIPv4Reservation() == IOAddress("192.0.3.10")) {
+ ASSERT_EQ(10, hosts[i]->getIPv4SubnetID());
+ found = true;
+ }
+ }
+ if (!found) {
+ ADD_FAILURE() << "Reservation for the IPv4 address 192.0.3.10"
+ " not found using getAll method with PRIMARY_SOURCE operation"
+ " target";
+ }
+ }
+
+ // Unspecified source target.
+ hosts = HostMgr::instance().getAll(Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAll4BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll4(SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll4(SubnetID(100));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll4(SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ }
+ if (is_second_source_primary) {
+ EXPECT_EQ("192.0.2.6", hosts[hosts_in_primary_source-1]->getIPv4Reservation().toText());
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ }
+ if (!is_second_source_primary) {
+ EXPECT_EQ("192.0.2.6", hosts[2 - hosts_in_primary_source - 1]->getIPv4Reservation().toText());
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAll6BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll6(SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[1], SubnetID(1), IOAddress("2001:db8:1::6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll6(SubnetID(100));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll6(SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAll6(SubnetID(1), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (is_second_source_primary) {
+ EXPECT_TRUE(hosts[hosts_in_primary_source-1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAll6(SubnetID(1), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (!is_second_source_primary) {
+ EXPECT_TRUE(hosts[2 - hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAll6(SubnetID(1), HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAllbyHostname(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAllbyHostname("host");
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations with the same hostname.
+ HostPtr host1(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.5")));
+ host1->setHostname("Host");
+ data_source1.add(host1);
+ HostPtr host2(new Host(hwaddrs_[1]->toText(false), "hw-address",
+ SubnetID(10), SUBNET_ID_UNUSED,
+ IOAddress("192.0.3.10")));
+ host2->setHostname("hosT");
+ data_source2.add(host2);
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching hostname is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname("foobar");
+ EXPECT_TRUE(hosts.empty());
+
+ // For the correct hostname, there should be two reservations.
+ hosts = HostMgr::instance().getAllbyHostname("host");
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(10, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that hostname is correct including its case.
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ EXPECT_EQ("hosT", hosts[1]->getHostname());
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAllbyHostname("host", HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ }
+ if (is_second_source_primary) {
+ EXPECT_EQ(10, hosts[hosts_in_primary_source-1]->getIPv4SubnetID());
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAllbyHostname("host", HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ }
+ if (!is_second_source_primary) {
+ EXPECT_EQ(10, hosts[2 - hosts_in_primary_source - 1]->getIPv4SubnetID());
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAllbyHostname("host", HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAllbyHostnameSubnet4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAllbyHostname4("host", SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations with the same hostname.
+ HostPtr host1(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.5")));
+ host1->setHostname("Host");
+ data_source1.add(host1);
+ HostPtr host2(new Host(hwaddrs_[1]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.6")));
+ host2->setHostname("hosT");
+ data_source2.add(host2);
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching hostname is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname4("foobar", SubnetID(1));
+ EXPECT_TRUE(hosts.empty());
+
+ // If there non-matching subnet is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname4("host", SubnetID(10));
+ EXPECT_TRUE(hosts.empty());
+
+ // For the correct hostname, there should be two reservations.
+ hosts = HostMgr::instance().getAllbyHostname4("host", SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+
+ // Make sure that hostname is correct including its case.
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ EXPECT_EQ("hosT", hosts[1]->getHostname());
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAllbyHostname4("host", SubnetID(1), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ }
+ if (is_second_source_primary) {
+ EXPECT_EQ("hosT", hosts[hosts_in_primary_source-1]->getHostname());
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAllbyHostname4("host", SubnetID(1), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ }
+ if (!is_second_source_primary) {
+ EXPECT_EQ("hosT", hosts[2 - hosts_in_primary_source - 1]->getHostname());
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAllbyHostname4("host", SubnetID(1), HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAllbyHostnameSubnet6(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAllbyHostname6("host", SubnetID(1));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations with the same hostname.
+ HostPtr host1(new Host(duids_[0]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host1->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::5"), 128));
+ host1->setHostname("Host");
+ data_source1.add(host1);
+ HostPtr host2(new Host(duids_[1]->toText(), "duid",
+ SubnetID(1), SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ host2->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::6"), 128));
+ host2->setHostname("hosT");
+ data_source2.add(host2);
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching hostname is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname6("foobar", SubnetID(1));
+ EXPECT_TRUE(hosts.empty());
+
+ // If there non-matching subnet is specified, nothing should be
+ // returned.
+ hosts = HostMgr::instance().getAllbyHostname6("host", SubnetID(10));
+ EXPECT_TRUE(hosts.empty());
+
+ // For the correct hostname, there should be two reservations.
+ hosts = HostMgr::instance().getAllbyHostname6("host", SubnetID(1));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Make sure that hostname is correct including its case.
+ EXPECT_EQ("Host", hosts[0]->getHostname());
+ EXPECT_EQ("hosT", hosts[1]->getHostname());
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAllbyHostname6("host", SubnetID(1), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (is_second_source_primary) {
+ EXPECT_TRUE(hosts[hosts_in_primary_source-1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAllbyHostname6("host", SubnetID(1), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (!is_second_source_primary) {
+ EXPECT_TRUE(hosts[2 - hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAllbyHostname6("host", SubnetID(1), HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetPage4(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations for the same subnet.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(use_database ? data_source2 : data_source1,
+ hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ idx = 0;
+ hosts = HostMgr::instance().getPage4(SubnetID(100), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ if (!use_database) {
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage4(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetPage6(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations for the same subnet.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(use_database ? data_source2 : data_source1,
+ duids_[1], SubnetID(1), IOAddress("2001:db8:1::6"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ idx = 0;
+ hosts = HostMgr::instance().getPage6(SubnetID(100), idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ if (!use_database) {
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage6(SubnetID(1), idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetPage4All(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(use_database ? data_source2 : data_source1,
+ hwaddrs_[1], SubnetID(2), IOAddress("192.0.2.6"));
+
+ CfgMgr::instance().commit();
+
+ // There should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ if (!use_database) {
+ EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[1]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage4(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage4(idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(2, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(2, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.6", hosts[0]->getIPv4Reservation().toText());
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage4(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage4(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetPage6All(bool use_database) {
+ BaseHostDataSource& data_source1 = *getCfgHosts();
+ BaseHostDataSource& data_source2 = HostMgr::instance();
+
+ // Initially, no reservations should be present.
+ size_t idx(0);
+ HostPageSize page_size(10);
+ ConstHostCollection hosts =
+ HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_TRUE(hosts.empty());
+ if (use_database) {
+ EXPECT_EQ(2, idx);
+ } else {
+ EXPECT_EQ(1, idx);
+ }
+
+ // Add two reservations.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(use_database ? data_source2 : data_source1,
+ duids_[1], SubnetID(2), IOAddress("2001:db8:1::6"));
+
+ CfgMgr::instance().commit();
+
+ // There should be two reservations.
+ idx = 0;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ if (use_database) {
+ ASSERT_EQ(1, hosts.size());
+ } else {
+ ASSERT_EQ(2, hosts.size());
+ }
+
+ // Make sure that returned values are correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ if (!use_database) {
+ EXPECT_EQ(2, hosts[1]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ uint64_t hid = hosts[1]->getHostId();
+ hosts = HostMgr::instance().getPage6(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+
+ if (use_database) {
+ uint64_t hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ ASSERT_EQ(0, idx);
+ hosts = HostMgr::instance().getPage6(idx, hid, page_size);
+ ASSERT_EQ(1, hosts.size());
+ ASSERT_NE(0, idx);
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Alternate way to use the database.
+ idx = 1;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_EQ(1, hosts.size());
+ EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Check it was the last page.
+ hid = hosts[0]->getHostId();
+ ASSERT_NE(0, hid);
+ hosts = HostMgr::instance().getPage6(idx, hid, page_size);
+ ASSERT_EQ(0, hosts.size());
+ idx = 2;
+ hosts = HostMgr::instance().getPage6(idx, 0, page_size);
+ ASSERT_EQ(0, hosts.size());
+ }
+}
+
+void
+HostMgrTest::testGetAll4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Initially, no hosts should be present.
+ ConstHostCollection hosts =
+ HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two hosts to different data sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[1], SubnetID(10), IOAddress("192.0.2.5"));
+
+ CfgMgr::instance().commit();
+
+ // Retrieve all hosts, This should return hosts from both sources
+ // in a single container.
+ hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that IPv4 address is correct.
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.5", hosts[1]->getIPv4Reservation().toText());
+
+ // Make sure that two different hosts were returned.
+ EXPECT_NE(hosts[0]->getIPv4SubnetID(), hosts[1]->getIPv4SubnetID());
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5"), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ }
+ if (is_second_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[hosts_in_primary_source-1]->getIPv4Reservation().toText());
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5"), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ }
+ if (!is_second_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[2 - hosts_in_primary_source - 1]->getIPv4Reservation().toText());
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGet4(BaseHostDataSource& data_source) {
+ // Initially, no host should be present.
+ ConstHostPtr host =
+ HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ addHost4(data_source, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+
+ // Make sure that the operation target is supported.
+ // Select host by explicit, matched operation target.
+ HostMgrOperationTarget operation_target = isPrimaryDataSource(data_source)
+ ? HostMgrOperationTarget::PRIMARY_SOURCE
+ : HostMgrOperationTarget::ALTERNATE_SOURCES;
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ operation_target);
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+
+ // Select host by explicit but unmatched operation target.
+ operation_target = isPrimaryDataSource(data_source)
+ ? HostMgrOperationTarget::ALTERNATE_SOURCES
+ : HostMgrOperationTarget::PRIMARY_SOURCE;
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ operation_target);
+ ASSERT_FALSE(host);
+
+ // Select host for an unspecified operation target.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ ASSERT_FALSE(host);
+}
+
+void
+HostMgrTest::testGet4Any() {
+ // Initially, no host should be present.
+ ConstHostPtr host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_FALSE(host);
+ HostMgr::instance().get4Any(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1),
+ SUBNET_ID_UNUSED, IOAddress("192.0.2.5")));
+ // Abuse of the server's configuration.
+ getCfgHosts()->add(new_host);
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+
+ // Set the negative cache flag on the host.
+ new_host->setNegative(true);
+
+ // get4 is not supposed to get it.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ EXPECT_FALSE(host);
+
+ // But get4Any should.
+ host = HostMgr::instance().get4Any(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_TRUE(host->getNegative());
+
+ // To be sure. Note we use the CfgHosts source so only this
+ // get4 overload works.
+ host = HostMgr::instance().get4(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ EXPECT_FALSE(host);
+
+ // Make sure that the operation target is supported.
+ // Select host by explicit, matched operation target.
+ host = HostMgr::instance().get4Any(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::PRIMARY_SOURCE);
+ ASSERT_TRUE(host);
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_TRUE(host->getNegative());
+
+ // Select host by explicit but unmatched operation target.
+ host = HostMgr::instance().get4Any(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALTERNATE_SOURCES);
+ ASSERT_FALSE(host);
+
+ // Select host for an unspecified operation target.
+ host = HostMgr::instance().get4Any(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ ASSERT_FALSE(host);
+}
+
+void
+HostMgrTest::testGet6(BaseHostDataSource& data_source) {
+ // Initially, no host should be present.
+ ConstHostPtr host =
+ HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ addHost6(data_source, duids_[0], SubnetID(2), IOAddress("2001:db8:1::1"));
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+
+ // Make sure that the operation target is supported.
+ // Select host by explicit, matched operation target.
+ HostMgrOperationTarget operation_target = isPrimaryDataSource(data_source)
+ ? HostMgrOperationTarget::PRIMARY_SOURCE
+ : HostMgrOperationTarget::ALTERNATE_SOURCES;
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ operation_target);
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+
+ // Select host by explicit but unmatched operation target.
+ operation_target = isPrimaryDataSource(data_source)
+ ? HostMgrOperationTarget::ALTERNATE_SOURCES
+ : HostMgrOperationTarget::PRIMARY_SOURCE;
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ operation_target);
+ ASSERT_FALSE(host);
+
+ // Select host for an unspecified operation target.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ ASSERT_FALSE(host);
+}
+
+void
+HostMgrTest::testGet6Any() {
+ // Initially, no host should be present.
+ ConstHostPtr host = HostMgr::instance().get6(SubnetID(2),
+ Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_FALSE(host);
+ host = HostMgr::instance().get6Any(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_FALSE(host);
+
+ // Add new host to the database.
+ HostPtr new_host(new Host(hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress::IPV4_ZERO_ADDRESS()));
+ new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"), 128));
+ // Abuse of the server's configuration.
+ getCfgHosts()->add(new_host);
+
+ CfgMgr::instance().commit();
+
+ // Retrieve the host from the database and expect that the parameters match.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+
+ // Set the negative cache flag on the host.
+ new_host->setNegative(true);
+
+ // get6 is not supposed to get it.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ EXPECT_FALSE(host);
+
+ // But get6Any should.
+ host = HostMgr::instance().get6Any(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+ EXPECT_TRUE(host->getNegative());
+
+ // To be sure. Note we use the CfgHosts source so only this
+ // get6 overload works.
+ host = HostMgr::instance().get6(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size());
+ EXPECT_FALSE(host);
+
+ // Make sure that the operation target is supported.
+ // Select host by explicit, matched operation target.
+ host = HostMgr::instance().get6Any(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::PRIMARY_SOURCE);
+ ASSERT_TRUE(host);
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+
+ // Select host by explicit but unmatched operation target.
+ host = HostMgr::instance().get6Any(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALTERNATE_SOURCES);
+ ASSERT_FALSE(host);
+
+ // Select host for an unspecified operation target.
+ host = HostMgr::instance().get6Any(SubnetID(2), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ ASSERT_FALSE(host);
+}
+
+void
+HostMgrTest::testGet6ByPrefix(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ ConstHostPtr host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+ ASSERT_FALSE(host);
+
+ // Add a host with a reservation for a prefix 2001:db8:1::/64.
+ addHost6(data_source1, duids_[0], SubnetID(2), IOAddress("2001:db8:1::"), 64);
+
+ // Add another host having a reservation for prefix 2001:db8:1:0:6::/72.
+ addHost6(data_source2, duids_[1], SubnetID(3), IOAddress("2001:db8:1:0:6::"), 80);
+
+ CfgMgr::instance().commit();
+
+ // Retrieve first reservation.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1::"), 64)));
+
+ // Make sure the first reservation is not retrieved when the prefix
+ // length is incorrect.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 72);
+ EXPECT_FALSE(host);
+
+ // Retrieve second reservation.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 80);
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:0:6::"), 80)));
+
+ // Make sure the second reservation is not retrieved when the prefix
+ // length is incorrect.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 64);
+ EXPECT_FALSE(host);
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+
+ // Select host only from the primary source.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64, HostMgrOperationTarget::PRIMARY_SOURCE);
+ if (is_first_source_primary) {
+ EXPECT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1::"), 64)));
+ } else {
+ EXPECT_FALSE(host);
+ }
+
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 80, HostMgrOperationTarget::PRIMARY_SOURCE);
+ if (is_second_source_primary) {
+ EXPECT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:0:6::"), 80)));
+ } else {
+ EXPECT_FALSE(host);
+ }
+
+ // Select hosts only from the alternate sources.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64, HostMgrOperationTarget::ALTERNATE_SOURCES);
+ if (!is_first_source_primary) {
+ EXPECT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1::"), 64)));
+ } else {
+ EXPECT_FALSE(host);
+ }
+
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 80, HostMgrOperationTarget::ALTERNATE_SOURCES);
+ if (!is_second_source_primary) {
+ EXPECT_TRUE(host);
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:0:6::"), 80)));
+ } else {
+ EXPECT_FALSE(host);
+ }
+
+ // Select hosts for an unspecified source.
+ host = HostMgr::instance().get6(IOAddress("2001:db8:1:0:6::"), 80, HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_FALSE(host);
+}
+
+void
+HostMgrTest::testGetAll4BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll4(SubnetID(1),
+ IOAddress("192.0.2.5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet and IP address.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.5"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll4(SubnetID(100), IOAddress("192.0.2.5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), IOAddress("192.0.2.5"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv4SubnetID());
+
+ // Make sure that two hosts were returned with different identifiers
+ // but the same address.
+ EXPECT_NE(hosts[0]->getIdentifierAsText(), hosts[1]->getIdentifierAsText());
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ EXPECT_EQ("192.0.2.5", hosts[1]->getIPv4Reservation().toText());
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ }
+ if (is_second_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[hosts_in_primary_source-1]->getIPv4Reservation().toText());
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+ }
+ if (!is_second_source_primary) {
+ EXPECT_EQ("192.0.2.5", hosts[2 - hosts_in_primary_source - 1]->getIPv4Reservation().toText());
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAll6BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll6(SubnetID(1),
+ IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Add two reservations for the same subnet.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[1], SubnetID(1), IOAddress("2001:db8:1::5"));
+
+ CfgMgr::instance().commit();
+
+ // If there non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll6(SubnetID(100), IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // For the correct subnet, there should be two reservations.
+ hosts = HostMgr::instance().getAll6(SubnetID(1), IOAddress("2001:db8:1::5"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that two hosts were returned with different identifiers
+ // but the same address.
+ EXPECT_NE(hosts[0]->getIdentifierAsText(), hosts[1]->getIdentifierAsText());
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ EXPECT_TRUE(hosts[1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAll6(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (is_second_source_primary) {
+ EXPECT_TRUE(hosts[hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAll6(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (!is_second_source_primary) {
+ EXPECT_TRUE(hosts[2 - hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAll4(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAll6ByIP(BaseHostDataSource& data_source1, BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll6(SubnetID(1),
+ IOAddress("2001:db8:1::10"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Prepare vectors of IPv6 address reservations for new hosts.
+ std::vector<IOAddress> addresses1;
+ std::vector<IOAddress> addresses2;
+ addresses1.push_back(IOAddress("2001:db8:1::5"));
+ addresses1.push_back(IOAddress("2001:db8:1::10"));
+ addresses2.push_back(IOAddress("2001:db8:1::6"));
+ addresses2.push_back(IOAddress("2001:db8:1::10"));
+
+ // Add two hosts for the same subnet with 2 IPv6 addresses reservations per host.
+ addHost6(data_source1, duids_[0], SubnetID(1), addresses1);
+ addHost6(data_source2, duids_[1], SubnetID(1), addresses2);
+
+ CfgMgr::instance().commit();
+
+ // If a non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll6(SubnetID(100), IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(hosts.empty());
+
+ // For given IP there should be one reservation.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1::5"));
+ ASSERT_EQ(1, hosts.size());
+
+ // For given IP there should be one reservation.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1::6"));
+ ASSERT_EQ(1, hosts.size());
+
+ // For given IP there should be two reservations.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1::10"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that all hosts were returned with different identifiers, and
+ // they have expected reservations.
+ EXPECT_NE(hosts[0]->getIdentifierAsText(), hosts[1]->getIdentifierAsText());
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::10"))));
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ EXPECT_TRUE(
+ hosts[1]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::10"))));
+ EXPECT_TRUE(
+ hosts[1]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1::10"),
+ HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::10"))));
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (is_second_source_primary) {
+ EXPECT_TRUE(hosts[hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::10"))));
+ EXPECT_TRUE(hosts[hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1::10"),
+ HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::10"))));
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::5"))));
+ }
+ if (!is_second_source_primary) {
+ EXPECT_TRUE(hosts[2 - hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::10"))));
+ EXPECT_TRUE(hosts[2 - hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::6"))));
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAll4(IOAddress("2001:db8:1::10"),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testGetAll6ByIpPrefix(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts = HostMgr::instance().getAll6(SubnetID(1),
+ IOAddress("2001:db8:1:10::"));
+ ASSERT_TRUE(hosts.empty());
+
+ // Prepare vectors of IPv6 prefix reservations for new hosts.
+ std::vector<IOAddress> addresses1;
+ std::vector<IOAddress> addresses2;
+ addresses1.push_back(IOAddress("2001:db8:1:10::"));
+ addresses1.push_back(IOAddress("2001:db8:1:11::"));
+ addresses2.push_back(IOAddress("2001:db8:1:10::"));
+ addresses2.push_back(IOAddress("2001:db8:1:12::"));
+
+ // Add two hosts for the same subnet with 2 IPv6 prefix reservations per host.
+ addHost6(data_source1, duids_[0], SubnetID(1), addresses1, 64);
+ addHost6(data_source2, duids_[1], SubnetID(1), addresses2, 64);
+
+ CfgMgr::instance().commit();
+
+ // If a non-matching subnet is specified, nothing should be returned.
+ hosts = HostMgr::instance().getAll6(SubnetID(100), IOAddress("2001:db8:1:10::"));
+ ASSERT_TRUE(hosts.empty());
+
+ // For given IP prefix there should be one reservation.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1:11::"));
+ ASSERT_EQ(1, hosts.size());
+
+ // For given IP prefix there should be one reservation.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1:12::"));
+ ASSERT_EQ(1, hosts.size());
+
+ // For given IP prefix there should be two reservations.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1:10::"));
+ ASSERT_EQ(2, hosts.size());
+
+ // Make sure that subnet is correct.
+ EXPECT_EQ(1, hosts[0]->getIPv6SubnetID());
+ EXPECT_EQ(1, hosts[1]->getIPv6SubnetID());
+
+ // Make sure that all hosts were returned with different identifiers, and
+ // they have expected reservations.
+ EXPECT_NE(hosts[0]->getIdentifierAsText(), hosts[1]->getIdentifierAsText());
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:10::"), 64)));
+ EXPECT_TRUE(
+ hosts[0]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:11::"), 64)));
+ EXPECT_TRUE(
+ hosts[1]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:10::"), 64)));
+ EXPECT_TRUE(
+ hosts[1]->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:12::"), 64)));
+
+ // Make sure that the operation target is supported.
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ size_t hosts_in_primary_source = is_first_source_primary + is_second_source_primary;
+
+ // Select hosts only from the primary source.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1:10::"),
+ HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source, hosts.size());
+ if (is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:10::"), 64)));
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:11::"), 64)));
+ }
+ if (is_second_source_primary) {
+ EXPECT_TRUE(hosts[hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:10::"), 64)));
+ EXPECT_TRUE(hosts[hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:12::"), 64)));
+ }
+
+ // Select hosts only from the alternate sources.
+ hosts = HostMgr::instance().getAll6(IOAddress("2001:db8:1:10::"),
+ HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(2 - hosts_in_primary_source, hosts.size());
+ if (!is_first_source_primary) {
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:10::"), 64)));
+ EXPECT_TRUE(hosts[0]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:11::"), 64)));
+ }
+ if (!is_second_source_primary) {
+ EXPECT_TRUE(hosts[2 - hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:10::"), 64)));
+ EXPECT_TRUE(hosts[2 - hosts_in_primary_source - 1]->hasReservation(
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1:11::"), 64)));
+ }
+
+ // Select hosts for an unspecified source.
+ hosts = HostMgr::instance().getAll4(IOAddress("2001:db8:1:10::"),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+ EXPECT_EQ(0, hosts.size());
+}
+
+void
+HostMgrTest::testAdd(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ bool has_alternate_source = !is_first_source_primary || !is_second_source_primary;
+ // Initially, no reservations should be present.
+ ConstHostCollection hosts4 = HostMgr::instance().getAll4(SubnetID(1));
+ ConstHostCollection hosts6 = HostMgr::instance().getAll6(SubnetID(1));
+ ASSERT_TRUE(hosts4.empty());
+ ASSERT_TRUE(hosts6.empty());
+
+ // Add hosts using the implicit operation target.
+ auto host = HostPtr(new Host(
+ hwaddrs_[0]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.5")
+ ));
+ if (has_alternate_source) {
+ EXPECT_NO_THROW(HostMgr::instance().add(host));
+ } else {
+ EXPECT_THROW(HostMgr::instance().add(host), NoHostDataSourceManager);
+ }
+
+ host = HostPtr(new Host(
+ hwaddrs_[1]->toText(false), "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()
+ ));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::5"), 128));
+ if (has_alternate_source) {
+ EXPECT_NO_THROW(HostMgr::instance().add(host));
+ } else {
+ EXPECT_THROW(HostMgr::instance().add(host), NoHostDataSourceManager);
+ }
+
+ // Add hosts using the explicit operation target - all data sources.
+ host = HostPtr(new Host(
+ hwaddrs_[2]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.6")
+ ));
+ HostMgr::instance().add(host, HostMgrOperationTarget::ALL_SOURCES);
+
+ host = HostPtr(new Host(
+ hwaddrs_[3]->toText(false), "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()
+ ));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::6"), 128));
+ HostMgr::instance().add(host, HostMgrOperationTarget::ALL_SOURCES);
+
+ // Add hosts using the explicit operation target - primary data source.
+ host = HostPtr(new Host(
+ hwaddrs_[4]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.7")
+ ));
+ HostMgr::instance().add(host, HostMgrOperationTarget::PRIMARY_SOURCE);
+
+ host = HostPtr(new Host(
+ hwaddrs_[5]->toText(false), "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()
+ ));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::7"), 128));
+ HostMgr::instance().add(host, HostMgrOperationTarget::PRIMARY_SOURCE);
+
+ // Add hosts using the explicit operation target - alternate data sources.
+ host = HostPtr(new Host(
+ hwaddrs_[6]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.8")
+ ));
+ if (has_alternate_source) {
+ EXPECT_NO_THROW(HostMgr::instance().add(host, HostMgrOperationTarget::ALTERNATE_SOURCES));
+ } else {
+ EXPECT_THROW(HostMgr::instance().add(host, HostMgrOperationTarget::ALTERNATE_SOURCES), NoHostDataSourceManager);
+ }
+
+ host = HostPtr(new Host(
+ hwaddrs_[7]->toText(false), "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()
+ ));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::8"), 128));
+ if (has_alternate_source) {
+ EXPECT_NO_THROW(HostMgr::instance().add(host, HostMgrOperationTarget::ALTERNATE_SOURCES));
+ } else {
+ EXPECT_THROW(HostMgr::instance().add(host, HostMgrOperationTarget::ALTERNATE_SOURCES), NoHostDataSourceManager);
+ }
+
+ // Add hosts using the explicit operation target - unspecified data source.
+ host = HostPtr(new Host(
+ hwaddrs_[8]->toText(false), "hw-address",
+ SubnetID(1), SUBNET_ID_UNUSED,
+ IOAddress("192.0.2.9")
+ ));
+ HostMgr::instance().add(host, HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+
+ host = HostPtr(new Host(
+ hwaddrs_[9]->toText(false), "hw-address",
+ SUBNET_ID_UNUSED, SubnetID(1),
+ IOAddress::IPV4_ZERO_ADDRESS()
+ ));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::9"), 128));
+ HostMgr::instance().add(host, HostMgrOperationTarget::UNSPECIFIED_SOURCE);
+
+ // Verify the hosts were added.
+ // ALL_SOURCES + PRIMARY_SOURCE targets for IPv4 and IPv6.
+ size_t hosts_in_primary_source = 2 * 2 * (is_first_source_primary || is_second_source_primary);
+ // Default + ALL_SOURCES + ALTERNATE_SOURCES targets for IPv4 and IPv6.
+ size_t hosts_in_alternate_sources = 3 * 2 * (!is_first_source_primary || !is_second_source_primary);
+
+ // Verify primary sources.
+ hosts4 = HostMgr::instance().getAll4(SubnetID(1), HostMgrOperationTarget::PRIMARY_SOURCE);
+ hosts6 = HostMgr::instance().getAll6(SubnetID(1), HostMgrOperationTarget::PRIMARY_SOURCE);
+ EXPECT_EQ(hosts_in_primary_source / 2, hosts4.size());
+ EXPECT_EQ(hosts_in_primary_source / 2, hosts6.size());
+
+ // Verify alternate sources.
+ hosts4 = HostMgr::instance().getAll4(SubnetID(1), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ hosts6 = HostMgr::instance().getAll6(SubnetID(1), HostMgrOperationTarget::ALTERNATE_SOURCES);
+ EXPECT_EQ(hosts_in_alternate_sources / 2, hosts4.size());
+ EXPECT_EQ(hosts_in_alternate_sources / 2, hosts6.size());
+}
+
+void
+HostMgrTest::testDeleteByIDAndAddress(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+ bool has_alternate_source = !is_first_source_primary || !is_second_source_primary;
+ bool has_primary_source = is_first_source_primary || is_second_source_primary;
+ size_t hosts4_in_primary_source = 2 * (is_first_source_primary + is_second_source_primary);
+ size_t hosts6_in_primary_source = is_first_source_primary + is_second_source_primary;
+ size_t hosts4_in_alternate_sources = 4 - hosts4_in_primary_source;
+ size_t hosts6_in_alternate_sources = 2 - hosts6_in_primary_source;
+
+ // Delete from the explicit operation target - all sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[1], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[1], SubnetID(1), IOAddress("2001:db8:1::5"));
+ CfgMgr::instance().commit();
+ // 4 IPv4 reservations - 2 sources * 2 addresses.
+ ASSERT_EQ(4, HostMgr::instance().getAll4(SubnetID(1)).size());
+ // 2 IPv6 reservations - each with 2 reserved addresses.
+ ASSERT_EQ(2, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::ALL_SOURCES));
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::ALL_SOURCES));
+
+ EXPECT_TRUE(HostMgr::instance().getAll4(SubnetID(1)).empty());
+ EXPECT_TRUE(HostMgr::instance().getAll6(SubnetID(1)).empty());
+
+ // Delete from the default operation target.
+ addHost4(data_source1, hwaddrs_[2], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[3], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost6(data_source1, duids_[2], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[3], SubnetID(1), IOAddress("2001:db8:1::5"));
+ CfgMgr::instance().commit();
+ ASSERT_EQ(4, HostMgr::instance().getAll4(SubnetID(1)).size());
+ ASSERT_EQ(2, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ if (has_alternate_source) {
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5")));
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5")));
+ } else {
+ EXPECT_THROW(HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5")), NoHostDataSourceManager);
+ EXPECT_THROW(HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5")), NoHostDataSourceManager);
+ }
+
+ EXPECT_EQ(hosts4_in_primary_source, HostMgr::instance().getAll4(SubnetID(1)).size());
+ EXPECT_EQ(hosts6_in_primary_source, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::ALL_SOURCES);
+ HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::ALL_SOURCES);
+
+ // Delete from the explicit operation target - alternate sources.
+ addHost4(data_source1, hwaddrs_[4], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[5], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost6(data_source1, duids_[4], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[5], SubnetID(1), IOAddress("2001:db8:1::5"));
+ CfgMgr::instance().commit();
+
+ if (has_alternate_source) {
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1),
+ IOAddress("192.0.2.5"),
+ HostMgrOperationTarget::ALTERNATE_SOURCES));
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1),
+ IOAddress("2001:db8:1::5"),
+ HostMgrOperationTarget::ALTERNATE_SOURCES));
+ } else {
+ EXPECT_THROW(HostMgr::instance().del(SubnetID(1),
+ IOAddress("192.0.2.5"),
+ HostMgrOperationTarget::ALTERNATE_SOURCES),
+ NoHostDataSourceManager);
+ EXPECT_THROW(HostMgr::instance().del(SubnetID(1),
+ IOAddress("2001:db8:1::5"),
+ HostMgrOperationTarget::ALTERNATE_SOURCES),
+ NoHostDataSourceManager);
+ }
+
+ EXPECT_EQ(hosts4_in_primary_source, HostMgr::instance().getAll4(SubnetID(1)).size());
+ EXPECT_EQ(hosts6_in_primary_source, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::ALL_SOURCES);
+ HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::ALL_SOURCES);
+
+ // Delete from the explicit operation target - primary source.
+ addHost4(data_source1, hwaddrs_[6], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[7], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost6(data_source1, duids_[6], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[7], SubnetID(1), IOAddress("2001:db8:1::5"));
+ CfgMgr::instance().commit();
+
+ if (has_primary_source) {
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::PRIMARY_SOURCE));
+ EXPECT_TRUE(HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::PRIMARY_SOURCE));
+ } else {
+ EXPECT_FALSE(HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::PRIMARY_SOURCE));
+ EXPECT_FALSE(HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::PRIMARY_SOURCE));
+ }
+
+ EXPECT_EQ(hosts4_in_alternate_sources, HostMgr::instance().getAll4(SubnetID(1)).size());
+ EXPECT_EQ(hosts6_in_alternate_sources, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::ALL_SOURCES);
+ HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::ALL_SOURCES);
+
+ // Delete from the explicit operation target - unspecified source.
+ addHost4(data_source1, hwaddrs_[8], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost4(data_source2, hwaddrs_[9], SubnetID(1), IOAddress("192.0.2.5"));
+ addHost6(data_source1, duids_[8], SubnetID(1), IOAddress("2001:db8:1::5"));
+ addHost6(data_source2, duids_[9], SubnetID(1), IOAddress("2001:db8:1::5"));
+ CfgMgr::instance().commit();
+
+ EXPECT_FALSE(HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::UNSPECIFIED_SOURCE));
+ EXPECT_FALSE(HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::UNSPECIFIED_SOURCE));
+
+ EXPECT_EQ(4, HostMgr::instance().getAll4(SubnetID(1)).size());
+ EXPECT_EQ(2, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ HostMgr::instance().del(SubnetID(1), IOAddress("192.0.2.5"), HostMgrOperationTarget::ALL_SOURCES);
+ HostMgr::instance().del(SubnetID(1), IOAddress("2001:db8:1::5"), HostMgrOperationTarget::ALL_SOURCES);
+}
+
+void
+HostMgrTest::testDelete4ByIDAndIdentifier(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+
+ if (is_first_source_primary && is_second_source_primary) {
+ // Two primary data sources - in fact it is a single source.
+
+ // Delete from all sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ CfgMgr::instance().commit();
+ EXPECT_TRUE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALL_SOURCES));
+ EXPECT_TRUE(HostMgr::instance().getAll4(SubnetID(1)).empty());
+
+ // Delete from default (alternate) sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ CfgMgr::instance().commit();
+ EXPECT_THROW(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size()),
+ NoHostDataSourceManager);
+ EXPECT_EQ(1, HostMgr::instance().getAll4(SubnetID(1)).size());
+
+ // Delete from explicit alternate sources.
+ EXPECT_THROW(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALTERNATE_SOURCES),
+ NoHostDataSourceManager);
+ EXPECT_EQ(1, HostMgr::instance().getAll4(SubnetID(1)).size());
+
+ // Delete from unspecified source.
+ EXPECT_FALSE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE));
+ EXPECT_EQ(1, HostMgr::instance().getAll4(SubnetID(1)).size());
+
+ // Delete from primary source.
+ EXPECT_TRUE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::PRIMARY_SOURCE));
+ EXPECT_TRUE(HostMgr::instance().getAll4(SubnetID(1)).empty());
+ } else if (is_first_source_primary != is_second_source_primary) {
+ // One primary data source and one alternate data source.
+
+ // Delete from all sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ addHost4(data_source2, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ CfgMgr::instance().commit();
+ EXPECT_TRUE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALL_SOURCES));
+ EXPECT_TRUE(HostMgr::instance().getAll4(SubnetID(1)).empty());
+
+ // Delete from default (alternate) sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ addHost4(data_source2, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ CfgMgr::instance().commit();
+ EXPECT_TRUE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size()));
+ // The host reservation in the primary source still exists.
+ EXPECT_EQ(1, HostMgr::instance().getAll4(SubnetID(1)).size());
+ // Clean the host reservations.
+ HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALL_SOURCES);
+
+ // Delete from explicit alternate sources.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ addHost4(data_source2, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ EXPECT_TRUE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALTERNATE_SOURCES));
+ // The host reservation in the primary source still exists.
+ EXPECT_EQ(1, HostMgr::instance().getAll4(SubnetID(1)).size());
+ // Clean the host reservations.
+ HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALL_SOURCES);
+
+ // Delete from unspecified source.
+ addHost4(data_source1, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ addHost4(data_source2, hwaddrs_[0], SubnetID(1), IOAddress("192.168.0.2"));
+ EXPECT_FALSE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE));
+ EXPECT_EQ(2, HostMgr::instance().getAll4(SubnetID(1)).size());
+
+ // Delete from primary source.
+ EXPECT_TRUE(HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::PRIMARY_SOURCE));
+ // The host reservation in the alternate source still exists.
+ EXPECT_EQ(1, HostMgr::instance().getAll4(SubnetID(1)).size());
+ // Clean the host reservations.
+ HostMgr::instance().del4(SubnetID(1), Host::IDENT_HWADDR,
+ &hwaddrs_[0]->hwaddr_[0],
+ hwaddrs_[0]->hwaddr_.size(),
+ HostMgrOperationTarget::ALL_SOURCES);
+ } else {
+ // Not defined.
+ isc_throw(NotImplemented, "not implemented test case for two alternate sources");
+ }
+}
+
+void
+HostMgrTest::testDelete6ByIDAndIdentifier(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2) {
+ // Set the mode of operation with multiple reservations for the same
+ // IP address.
+ ASSERT_TRUE(HostMgr::instance().setIPReservationsUnique(false));
+ CfgMgr::instance().getStagingCfg()->getCfgHosts()->setIPReservationsUnique(false);
+
+ bool is_first_source_primary = isPrimaryDataSource(data_source1);
+ bool is_second_source_primary = isPrimaryDataSource(data_source2);
+
+ if (is_first_source_primary && is_second_source_primary) {
+ // Two primary data sources - in fact it is a single source.
+
+ // Delete from all sources.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ CfgMgr::instance().commit();
+ EXPECT_TRUE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALL_SOURCES));
+ EXPECT_TRUE(HostMgr::instance().getAll6(SubnetID(1)).empty());
+
+ // Delete from default (alternate) sources.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ CfgMgr::instance().commit();
+ EXPECT_THROW(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size()),
+ NoHostDataSourceManager);
+ EXPECT_EQ(1, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ // Delete from explicit alternate sources.
+ EXPECT_THROW(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALTERNATE_SOURCES),
+ NoHostDataSourceManager);
+ EXPECT_EQ(1, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ // Delete from unspecified source.
+ EXPECT_FALSE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE));
+ EXPECT_EQ(1, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ // Delete from primary source.
+ EXPECT_TRUE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::PRIMARY_SOURCE));
+ EXPECT_TRUE(HostMgr::instance().getAll6(SubnetID(1)).empty());
+ } else if (is_first_source_primary != is_second_source_primary) {
+ // One primary data source and one alternate data source.
+
+ // Delete from all sources.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ addHost6(data_source2, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ CfgMgr::instance().commit();
+ EXPECT_TRUE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALL_SOURCES));
+ EXPECT_TRUE(HostMgr::instance().getAll6(SubnetID(1)).empty());
+
+ // Delete from default (alternate) sources.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ addHost6(data_source2, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ CfgMgr::instance().commit();
+ EXPECT_TRUE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size()));
+ // The host reservation in the primary source still exists.
+ EXPECT_EQ(1, HostMgr::instance().getAll6(SubnetID(1)).size());
+ // Clean the host reservations.
+ HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALL_SOURCES);
+
+ // Delete from explicit alternate sources.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ addHost6(data_source2, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ EXPECT_TRUE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALTERNATE_SOURCES));
+ // The host reservation in the primary source still exists.
+ EXPECT_EQ(1, HostMgr::instance().getAll6(SubnetID(1)).size());
+ // Clean the host reservations.
+ HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALL_SOURCES);
+
+ // Delete from unspecified source.
+ addHost6(data_source1, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ addHost6(data_source2, duids_[0], SubnetID(1), IOAddress("2001:db8:1::5"), 128);
+ EXPECT_FALSE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::UNSPECIFIED_SOURCE));
+ EXPECT_EQ(2, HostMgr::instance().getAll6(SubnetID(1)).size());
+
+ // Delete from primary source.
+ EXPECT_TRUE(HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::PRIMARY_SOURCE));
+ // The host reservation in the alternate source still exists.
+ EXPECT_EQ(1, HostMgr::instance().getAll6(SubnetID(1)).size());
+ // Clean the host reservations.
+ HostMgr::instance().del6(SubnetID(1), Host::IDENT_DUID,
+ &duids_[0]->getDuid()[0],
+ duids_[0]->getDuid().size(),
+ HostMgrOperationTarget::ALL_SOURCES);
+ } else {
+ // Not defined.
+ isc_throw(NotImplemented, "not implemented test case for two alternate sources");
+ }
+}
+
+bool HostMgrTest::isPrimaryDataSource(const BaseHostDataSource& data_source) const {
+ const auto ptr = dynamic_cast<const CfgHosts*>(&data_source);
+ return ptr != nullptr;
+}
+
+void
+GenericHostDataSourceTest::testUpdate() {
+ // Make sure the host data source is initialized.
+ ASSERT_TRUE(hdsptr_);
+
+ // Create a host with an IPv4 address reservation.
+ HostPtr const host(HostDataSourceUtils::initializeHost4("192.0.2.1", Host::IDENT_HWADDR));
+ SubnetID const v4_subnet(host->getIPv4SubnetID());
+ SubnetID const v6_subnet(host->getIPv6SubnetID());
+ string hwaddr(host->getHWAddress()->toText(false));
+ boost::replace_all(hwaddr, ":", "");
+ boost::to_upper(hwaddr);
+
+ // Updating a host that doesn't exist should throw.
+ EXPECT_THROW_MSG(hdsptr_->update(host), HostNotFound, "Host not updated (not found).");
+
+ // There should be no hosts.
+ ConstHostCollection hosts(hdsptr_->getAll4(v4_subnet));
+ EXPECT_EQ(0, hosts.size());
+
+ // Add the host.
+ EXPECT_NO_THROW(hdsptr_->add(host));
+
+ // The host should be there.
+ hosts = hdsptr_->getAll4(v4_subnet);
+ EXPECT_EQ(1, hosts.size());
+ EXPECT_EQ("hwaddr=" + hwaddr + " ipv4_subnet_id=" + to_string(v4_subnet) +
+ " ipv6_subnet_id=" + to_string(v6_subnet) +
+ " hostname=(empty) "
+ "ipv4_reservation=192.0.2.1 siaddr=(no) sname=(empty) file=(empty) key=(empty) "
+ "ipv6_reservations=(none)",
+ hosts[0]->toText());
+
+ // Update the host. Change nothing.
+ EXPECT_NO_THROW(hdsptr_->update(host));
+
+ // The same host should be in the data source.
+ hosts = hdsptr_->getAll4(v4_subnet);
+ EXPECT_EQ(1, hosts.size());
+ EXPECT_EQ("hwaddr=" + hwaddr + " ipv4_subnet_id=" + to_string(v4_subnet) +
+ " ipv6_subnet_id=" + to_string(v6_subnet) +
+ " hostname=(empty) "
+ "ipv4_reservation=192.0.2.1 siaddr=(no) sname=(empty) file=(empty) key=(empty) "
+ "ipv6_reservations=(none)",
+ hosts[0]->toText());
+
+ // Update the host with new hostname.
+ host->setHostname("foo.example.com");
+ EXPECT_NO_THROW(hdsptr_->update(host));
+
+ // The change should be reflected in the data source.
+ hosts = hdsptr_->getAll4(v4_subnet);
+ EXPECT_EQ(1, hosts.size());
+ EXPECT_EQ("hwaddr=" + hwaddr + " ipv4_subnet_id=" + to_string(v4_subnet) +
+ " ipv6_subnet_id=" + to_string(v6_subnet) +
+ " hostname=foo.example.com "
+ "ipv4_reservation=192.0.2.1 siaddr=(no) sname=(empty) file=(empty) key=(empty) "
+ "ipv6_reservations=(none)",
+ hosts[0]->toText());
+
+ // Remove hostname from host.
+ host->setHostname("");
+ EXPECT_NO_THROW(hdsptr_->update(host));
+
+ // The change should be reflected in the data source.
+ hosts = hdsptr_->getAll4(v4_subnet);
+ EXPECT_EQ(1, hosts.size());
+ EXPECT_EQ("hwaddr=" + hwaddr + " ipv4_subnet_id=" + to_string(v4_subnet) +
+ " ipv6_subnet_id=" + to_string(v6_subnet) +
+ " hostname=(empty) "
+ "ipv4_reservation=192.0.2.1 siaddr=(no) sname=(empty) file=(empty) key=(empty) "
+ "ipv6_reservations=(none)",
+ hosts[0]->toText());
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h
new file mode 100644
index 0000000..6717bbb
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/generic_host_data_source_unittest.h
@@ -0,0 +1,1051 @@
+// Copyright (C) 2015-2023 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 GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+#define GENERIC_HOST_DATA_SOURCE_UNITTEST_H
+
+#include <asiolink/io_address.h>
+#include <util/reconnect_ctl.h>
+#include <dhcpsrv/base_host_data_source.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test Fixture class with utility functions for HostDataSource backends
+///
+/// It contains utility functions for test purposes.
+/// All concrete HostDataSource test classes should be derived from it.
+class GenericHostDataSourceTest : public GenericBackendTest {
+public:
+
+ /// @brief Universe (V4 or V6).
+ enum Universe {
+ V4,
+ V6
+ };
+
+ /// @brief Options to be inserted into a host.
+ ///
+ /// Parameter of this type is passed to the @ref addTestOptions to
+ /// control which option types should be inserted into a host.
+ enum AddedOptions {
+ DHCP4_ONLY,
+ DHCP6_ONLY,
+ DHCP4_AND_DHCP6
+ };
+
+ /// @brief Default constructor.
+ GenericHostDataSourceTest();
+
+ /// @brief Virtual destructor.
+ virtual ~GenericHostDataSourceTest();
+
+ /// @brief Used to sort a host collection by IPv4 subnet id.
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @result return true if host1's subnet id is smaller than host2's
+ /// subnet id
+ static bool compareHostsForSort4(const ConstHostPtr& host1,
+ const ConstHostPtr& host2);
+
+ /// @brief Used to sort a host collection by IPv6 subnet id.
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @result return true if host1's subnet id is smaller than host2's
+ /// subnet id
+ static bool compareHostsForSort6(const ConstHostPtr& host1,
+ const ConstHostPtr& host2);
+
+ /// @brief Used to sort a host collection by host identifier.
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @result return true if host1's identifier is smaller than host2's
+ /// identifier
+ static bool compareHostsIdentifier(const ConstHostPtr& host1,
+ const ConstHostPtr& host2);
+
+ /// @brief Returns number of entries in the v4 options table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// host options in a table.
+ ///
+ /// @param number of existing entries in options table
+ virtual int countDBOptions4() {
+ return (-1);
+ }
+
+ /// @brief Returns number of entries in the v6 options table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// host options in a table.
+ ///
+ /// @param number of existing entries in options table
+ virtual int countDBOptions6() {
+ return (-1);
+ }
+
+ /// @brief Returns number of entries in the v6 reservations table.
+ ///
+ /// This utility method is expected to be implemented by specific backends.
+ /// The code here is just a boilerplate for backends that do not store
+ /// v6 reservations in a table.
+ ///
+ /// @param number of existing entries in v6_reservations table
+ virtual int countDBReservations6() {
+ return (-1);
+ }
+
+ /// @brief Adds multiple options into the host.
+ ///
+ /// This method creates the following options into the host object:
+ /// - DHCPv4 boot file name option,
+ /// - DHCPv4 default ip ttl option,
+ /// - DHCPv4 option 1 within vendor-encapsulated-options space,
+ /// - DHCPv4 option 254 with a single IPv4 address,
+ /// - DHCPv4 option 1 within isc option space,
+ /// - DHCPv6 boot file url option,
+ /// - DHCPv6 information refresh time option,
+ /// - DHCPv6 vendor option with vendor id 2495,
+ /// - DHCPv6 option 1024, with a single IPv6 address,
+ /// - DHCPv6 empty option 1, within isc2 option space,
+ /// - DHCPv6 option 2, within isc2 option space with 3 IPv6 addresses,
+ ///
+ /// This method also creates option definitions for the non-standard
+ /// options and registers them in the LibDHCP as runtime option
+ /// definitions.
+ ///
+ /// @param host Host object into which options should be added.
+ /// @param formatted A boolean value selecting if the formatted option
+ /// value should be used (if true), or binary value (if false).
+ /// @param added_options Controls which options should be inserted into
+ /// a host: DHCPv4, DHCPv6 options or both.
+ /// @param user_context Optional user context
+ void addTestOptions(const HostPtr& host, const bool formatted,
+ const AddedOptions& added_options,
+ isc::data::ConstElementPtr user_context =
+ isc::data::ConstElementPtr()) const;
+
+ /// @brief Adds an IPv6 address to the host.
+ ///
+ /// @param host pointer to the host instance.
+ /// @param address an IPv6 address to be added as a string.
+ void addIPv6Address(const HostPtr& host, const std::string& address) const;
+
+ /// @brief Pointer to the host data source
+ HostDataSourcePtr hdsptr_;
+
+ /// @brief Test that backend can be started in read-only mode.
+ ///
+ /// Some backends can operate when the database is read only, e.g.
+ /// host reservation tables are read only, or the database user has
+ /// read only privileges on the entire database. In such cases, the
+ /// Kea server administrator can specify in the backend configuration
+ /// that the database should be opened in read only mode, i.e.
+ /// INSERT, UPDATE, DELETE statements can't be issued. If any of the
+ /// functions updating the database is called for the backend, the
+ /// error is reported. The database running in read only mode can
+ /// be merely used to retrieve existing host reservations from the
+ /// database. This test verifies that this is the case.
+ ///
+ /// @param valid_db_type Parameter specifying type of backend to
+ /// be used, e.g. type=mysql.
+ void testReadOnlyDatabase(const char* valid_db_type);
+
+ /// @brief Test that checks that simple host with IPv4 reservation
+ /// can be inserted and later retrieved.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type.
+ void testBasic4(const Host::IdentifierType& id);
+
+ /// @brief Test that verifies that an IPv4 host reservation with
+ /// options can have the global subnet id value.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGlobalSubnetId4();
+
+ /// @brief Test that verifies that an IPv6 host reservation with
+ /// options can have the global subnet id value.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGlobalSubnetId6();
+
+ /// @brief Test that verifies that an IPv4 host reservation with
+ /// options can have a max value for dhcp4_subnet id
+ ///
+ /// Uses gtest macros to report failures.
+ void testMaxSubnetId4();
+
+ /// @brief Test that Verifies that an IPv6 host reservation with
+ /// options can have a max value for dhcp6_subnet id
+ ///
+ /// Uses gtest macros to report failures.
+ void testMaxSubnetId6();
+
+ /// @brief Test that Verifies that IPv4 host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAll4();
+
+ /// @brief Test that Verifies that IPv6 host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAll6();
+
+ /// @brief Test that Verifies that host reservations with the same
+ /// hostname can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAllbyHostname();
+
+ /// @brief Test that Verifies that IPv4 host reservations with the same
+ /// hostname and in the same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAllbyHostnameSubnet4();
+
+ /// @brief Test that Verifies that IPv6 host reservations with the same
+ /// hostname and in the same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetAllbyHostnameSubnet6();
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage4();
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage6();
+
+ /// @brief Test that Verifies that pages of complex host reservations
+ /// are not truncated, i.e. the limit applies on the number of hosts
+ /// and not on the number of rows.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type (hwaddr or duid).
+ void testGetPageLimit4(const Host::IdentifierType& id);
+
+ /// @brief Test that Verifies that pages of complex host reservations
+ /// are not truncated, i.e. the limit applies on the number of hosts
+ /// and not on the number of rows.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type (hwaddr or duid).
+ void testGetPageLimit6(const Host::IdentifierType& id);
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly even with multiple subnets.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage4Subnets();
+
+ /// @brief Test that Verifies that pages of host reservations in the
+ /// same subnet can be retrieved properly even with multiple subnets.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage6Subnets();
+
+ /// @brief Test that Verifies that pages of all host reservations
+ /// can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage4All();
+
+ /// @brief Test that Verifies that pages of all host reservations
+ /// can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGetPage6All();
+
+ /// @brief Test inserts several hosts with unique IPv4 address and
+ /// checks that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id Identifier type.
+ void testGetByIPv4(const Host::IdentifierType& id);
+
+ /// @brief Test that hosts can be retrieved by host identifier.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet4ByIdentifier(const Host::IdentifierType& identifier_type);
+
+ /// @brief Test that clients with stored HW address can't be retrieved
+ /// by DUID with the same value.
+ ///
+ /// Test procedure: add host reservation with hardware address X, try to retrieve
+ /// host by client-identifier X, verify that the reservation is not returned.
+ ///
+ /// Uses gtest macros to report failures.
+ void testHWAddrNotClientId();
+
+ /// @brief Test that clients with stored DUID can't be retrieved
+ /// by HW address of the same value.
+ ///
+ /// Test procedure: add host reservation with client identifier X, try to
+ /// retrieve host by hardware address X, verify that the reservation is not
+ /// returned.
+ ///
+ /// Uses gtest macros to report failures.
+ void testClientIdNotHWAddr();
+
+ /// @brief Test adds specified number of hosts with unique hostnames, then
+ /// retrieves them and checks that the hostnames are set properly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param name hostname to be used (if n>1, numbers will be appended)
+ /// @param num number of hostnames to be added.
+ void testHostname(std::string name, int num);
+
+ /// @brief Test insert and retrieve a host with user context.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param user_context The user context.
+ void testUserContext(isc::data::ConstElementPtr user_context);
+
+ /// @brief Test inserts multiple reservations for the same host for different
+ /// subnets and check that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param subnets number of subnets to test
+ /// @param id Host identifier type.
+ void testMultipleSubnets(int subnets, const Host::IdentifierType& id);
+
+ /// @brief Test inserts several hosts with unique IPv6 addresses and
+ /// checks that they can be retrieved properly.
+ ///
+ /// Uses gtest macros to report failures.
+ /// @param id type of the identifier to be used (IDENT_HWADDR or IDENT_DUID)
+ /// @param prefix true - reserve IPv6 prefix, false - reserve IPv6 address
+ void testGetByIPv6(Host::IdentifierType id, bool prefix);
+
+ /// @brief Test inserts several hosts with unique prefixes and checks
+ /// that the can be retrieved by subnet id and prefix value.
+ void testGetBySubnetIPv6();
+
+ /// @brief Test that hosts can be retrieved by hardware address.
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet6ByHWAddr();
+
+ /// @brief Test that hosts can be retrieved by client-id
+ ///
+ /// Uses gtest macros to report failures.
+ void testGet6ByClientId();
+
+ /// @brief Test verifies if a host reservation can be stored with both
+ /// IPv6 address and prefix.
+ /// Uses gtest macros to report failures.
+ void testAddr6AndPrefix();
+
+ /// @brief Tests if host with multiple IPv6 reservations can be added and then
+ /// retrieved correctly.
+ void testMultipleReservations();
+
+ /// @brief Tests if compareIPv6Reservations() method treats same pool of
+ /// reservations but added in different order as equal.
+ void testMultipleReservationsDifferentOrder();
+
+ /// @brief Test if host reservations made for different IPv6 subnets
+ /// are handled correctly.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param subnets number of subnets to test
+ /// @param id identifier type (IDENT_HWADDR or IDENT_DUID)
+ void testSubnetId6(int subnets, Host::IdentifierType id);
+
+ /// @brief Test if the duplicate host with same DUID can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicate6WithSameDUID();
+
+ /// @brief Test if the duplicate host with same HWAddr can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicate6WithSameHWAddr();
+
+ /// @brief Test that duplicate IPv6 reservation can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicateIPv6();
+
+ /// @brief Test if the reservation for the same IPv6 address can be
+ /// inserted when allowed by the configuration.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAllowDuplicateIPv6();
+
+ /// @brief Test that duplicate IPv4 reservation can't be inserted.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAddDuplicateIPv4();
+
+ /// @brief Test if the reservation for the same IPv4 address can be
+ /// inserted when allowed by the configuration.
+ ///
+ /// Uses gtest macros to report failures.
+ void testAllowDuplicateIPv4();
+
+ /// @brief Test that the backend does not support a mode in which multiple
+ /// host reservations for the same IP address can be created.
+ void testDisallowDuplicateIP();
+
+ /// @brief Test that DHCPv4 options can be inserted and retrieved from
+ /// the database.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ /// @param user_context Optional user context.
+ void testOptionsReservations4(const bool formatted,
+ isc::data::ConstElementPtr user_context =
+ isc::data::ConstElementPtr());
+
+ /// @brief Test that DHCPv6 options can be inserted and retrieved from
+ /// the database.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ /// @param user_context Optional user context.
+ void testOptionsReservations6(const bool formatted,
+ isc::data::ConstElementPtr user_context =
+ isc::data::ConstElementPtr());
+
+ /// @brief Test that DHCPv4 and DHCPv6 options can be inserted and retrieved
+ /// with a single query to the database.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ /// @param formatted Boolean value indicating if the option values
+ /// should be stored in the textual format in the database.
+ void testOptionsReservations46(const bool formatted);
+
+ /// @brief Test that multiple client classes for IPv4 can be inserted and
+ /// retrieved for a given host reservation.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ void testMultipleClientClasses4();
+
+ /// @brief Test that multiple client classes for IPv6 can be inserted and
+ /// retrieved for a given host reservation.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ void testMultipleClientClasses6();
+
+ /// @brief Test that multiple client classes for both IPv4 and IPv6 can
+ /// be inserted and retrieved for a given host reservation.
+ ///
+ /// Uses gtest macros to report failures.
+ ///
+ void testMultipleClientClassesBoth();
+
+ /// @brief Test that siaddr, sname, file fields can be retrieved
+ /// from a database for a host.
+ ///
+ /// Uses gtest macros to report failures.
+ void testMessageFields4();
+
+ /// @brief Stress test on adding and retrieving hosts
+ ///
+ /// Rather than checking for correctness, this test gives interpretable
+ /// performance results.
+ ///
+ /// @param n_of_hosts number of hosts to insert into and retrieve from the
+ /// database
+ void stressTest(uint32_t n_of_hosts);
+ /// @brief Tests that delete(subnet-id, addr4) call works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteByAddr4();
+
+ /// @brief Tests that delete(subnet4-id, identifier-type, identifier) works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById4();
+
+ /// @brief Tests that delete(subnet4-id, id-type, id) also deletes options.
+ void testDeleteById4Options();
+
+ /// @brief Tests that delete(subnet6-id, identifier-type, identifier) works.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById6();
+
+ /// @brief Tests that delete(subnet6-id, id-type, id) also deletes options.
+ ///
+ /// Uses gtest macros to report failures.
+ void testDeleteById6Options();
+
+ /// @brief Tests that multiple reservations without IPv4 addresses can be
+ /// specified within a subnet.
+ ///
+ /// Uses gtest macros to report failures.
+ void testMultipleHostsNoAddress4();
+
+ /// @brief Tests that multiple hosts can be specified within an IPv6 subnet.
+ ///
+ /// Uses gtest macros to report failures.
+ void testMultipleHosts6();
+
+ /// @brief Tests that hosts can be updated.
+ ///
+ /// Uses gtest macros to report failures.
+ void testUpdate();
+
+ /// @brief Returns DUID with identical content as specified HW address
+ ///
+ /// This method does not have any sense in real life and is only useful
+ /// in testing corner cases in the database backends (e.g. whether the DB
+ /// is able to tell the difference between hwaddr and duid)
+ ///
+ /// @param hwaddr hardware address to be copied
+ /// @return duid with the same value as specified HW address
+ DuidPtr HWAddrToDuid(const HWAddrPtr& hwaddr);
+
+ /// @brief Returns HW address with identical content as specified DUID
+ ///
+ /// This method does not have any sense in real life and is only useful
+ /// in testing corner cases in the database backends (e.g. whether the DB
+ /// is able to tell the difference between hwaddr and duid)
+ ///
+ /// @param duid DUID to be copied
+ /// @return HW address with the same value as specified DUID
+ HWAddrPtr DuidToHWAddr(const DuidPtr& duid);
+
+};
+
+class HostMgrDbLostCallbackTest : public ::testing::Test {
+public:
+ HostMgrDbLostCallbackTest()
+ : db_lost_callback_called_(0), db_recovered_callback_called_(0),
+ db_failed_callback_called_(0),
+ io_service_(boost::make_shared<isc::asiolink::IOService>()) {
+ isc::db::DatabaseConnection::db_lost_callback_ = 0;
+ isc::db::DatabaseConnection::db_recovered_callback_ = 0;
+ isc::db::DatabaseConnection::db_failed_callback_ = 0;
+ isc::dhcp::HostMgr::setIOService(io_service_);
+ isc::dhcp::TimerMgr::instance()->setIOService(io_service_);
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ virtual ~HostMgrDbLostCallbackTest() {
+ isc::db::DatabaseConnection::db_lost_callback_ = 0;
+ isc::db::DatabaseConnection::db_recovered_callback_ = 0;
+ isc::db::DatabaseConnection::db_failed_callback_ = 0;
+ isc::dhcp::HostMgr::setIOService(isc::asiolink::IOServicePtr());
+ isc::dhcp::TimerMgr::instance()->unregisterTimers();
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// Invoked by gtest prior test entry, we create the
+ /// appropriate schema and create a basic host manager to
+ /// wipe out any prior instance
+ virtual void SetUp() {
+ // Ensure we have the proper schema with no transient data.
+ createSchema();
+ // Wipe out any pre-existing mgr
+ isc::dhcp::HostMgr::create();
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ /// @brief Pre-text exit clean up
+ ///
+ /// Invoked by gtest upon test exit, we destroy the schema
+ /// we created.
+ virtual void TearDown() {
+ // If data wipe enabled, delete transient data otherwise destroy the schema
+ destroySchema();
+ isc::dhcp::CfgMgr::instance().clear();
+ }
+
+ /// @brief Abstract method for destroying the back end specific schema
+ virtual void destroySchema() = 0;
+
+ /// @brief Abstract method for creating the back end specific schema
+ virtual void createSchema() = 0;
+
+ /// @brief Abstract method which returns the back end specific connection
+ /// string
+ virtual std::string validConnectString() = 0;
+
+ /// @brief Abstract method which returns invalid back end specific connection
+ /// string
+ virtual std::string invalidConnectString() = 0;
+
+ /// @brief Verifies open failures do NOT invoke db lost callback
+ ///
+ /// The db lost callback should only be invoked after successfully
+ /// opening the DB and then subsequently losing it. Failing to
+ /// open should be handled directly by the application layer.
+ void testNoCallbackOnOpenFailure();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked
+ void testDbLostAndRecoveredCallback();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked
+ void testDbLostAndFailedCallback();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbRecoveredCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndRecoveredAfterTimeoutCallback();
+
+ /// @brief Verifies the host manager's behavior if DB connection is lost
+ ///
+ /// This function creates a host manager with a back end that supports
+ /// connectivity lost callback (currently only MySQL and PostgreSQL). It
+ /// verifies connectivity by issuing a known valid query. Next it simulates
+ /// connectivity lost by identifying and closing the socket connection to
+ /// the CB backend. It then reissues the query and verifies that:
+ /// -# The Query throws DbOperationError (rather than exiting)
+ /// -# The registered DbLostCallback was invoked
+ /// -# The registered DbFailedCallback was invoked after two reconnect
+ /// attempts (once failing and second triggered by timer)
+ void testDbLostAndFailedAfterTimeoutCallback();
+
+ /// @brief Callback function registered with the host manager
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_lost_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_lost_callback function
+ uint32_t db_lost_callback_called_;
+
+ /// @brief Callback function registered with the host manager
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_recovered_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_recovered_callback function
+ uint32_t db_recovered_callback_called_;
+
+ /// @brief Callback function registered with the host manager
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
+ return (++db_failed_callback_called_);
+ }
+
+ /// @brief Flag used to detect calls to db_failed_callback function
+ uint32_t db_failed_callback_called_;
+
+ /// The IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+};
+
+/// @brief Test fixture class for @c HostMgr class.
+class HostMgrTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ HostMgrTest() = default;
+
+ /// @brief Destructor
+ virtual ~HostMgrTest() = default;
+
+protected:
+
+ /// @brief Prepares the class for a test.
+ ///
+ /// This method crates a handful of unique HW address and DUID objects
+ /// for use in unit tests. These objects are held in the @c hwaddrs_ and
+ /// @c duids_ members respectively.
+ ///
+ /// This method also resets the @c CfgMgr configuration and re-creates
+ /// the @c HostMgr object.
+ virtual void SetUp();
+
+ /// @brief Convenience method returning a pointer to the @c CfgHosts object
+ /// in the @c CfgMgr.
+ CfgHostsPtr getCfgHosts() const;
+
+ /// @brief Inserts IPv4 reservation into the host data source.
+ ///
+ /// @param data_source Reference to the data source to which the reservation
+ /// should be inserted.
+ /// @param hwaddr Pointer to the hardware address to be associated with the
+ /// reservation.
+ /// @param subnet_id IPv4 subnet id.
+ /// @param address IPv4 address to be reserved.
+ void addHost4(BaseHostDataSource& data_source,
+ const HWAddrPtr& hwaddr,
+ const SubnetID& subnet_id,
+ const isc::asiolink::IOAddress& address);
+
+ /// @brief Inserts IPv6 reservation into the host data source.
+ ///
+ /// @param data_source Reference to the data source to which the reservation
+ /// should be inserted.
+ /// @param duid Pointer to the DUID to be associated with the reservation.
+ /// @param subnet_id IPv6 subnet id.
+ /// @param address IPv6 address/prefix to be reserved.
+ /// @param prefix_len Prefix length. The default value is 128 which
+ /// indicates that the reservation is for an IPv6 address rather than a
+ /// prefix.
+ void addHost6(BaseHostDataSource& data_source,
+ const DuidPtr& duid,
+ const SubnetID& subnet_id,
+ const isc::asiolink::IOAddress& address,
+ const uint8_t prefix_len = 128);
+
+ /// @brief Inserts IPv6 reservation into the host data source.
+ ///
+ /// @param data_source Reference to the data source to which the reservation
+ /// should be inserted.
+ /// @param duid Pointer to the DUID to be associated with the reservation.
+ /// @param subnet_id IPv6 subnet id.
+ /// @param addresses IPv6 addresses/prefixes to be reserved.
+ /// @param prefix_len Prefix length. The default value is 128 which
+ /// indicates that the reservation is for an IPv6 address rather than a
+ /// prefix. Notice that this is common for all addresses in given vector
+ /// of addresses.
+ void addHost6(BaseHostDataSource& data_source,
+ const DuidPtr& duid,
+ const SubnetID& subnet_id,
+ const std::vector<isc::asiolink::IOAddress>& addresses,
+ const uint8_t prefix_len = 128);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified HW address.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv4 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll4BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv6 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll6BySubnet(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified hostname.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAllbyHostname(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified hostname and DHCPv4 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAllbyHostnameSubnet4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified hostname and DHCPv6 subnet.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAllbyHostnameSubnet6(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv4 subnet by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage4(bool use_database);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv6 subnet by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage6(bool use_database);
+
+ /// @brief This test verifies that HostMgr returns all reservations
+ /// by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage4All(bool use_database);
+
+ /// @brief This test verifies that HostMgr returns all reservations
+ /// by pages.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param use_database True when the second reservation is inserted
+ /// in a database.
+ void testGetPage6All(bool use_database);
+
+ /// @brief This test verifies that it is possible to retrieve IPv4
+ /// reservation for the particular host using HostMgr.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll4(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that it is possible to retrieve an IPv4
+ /// reservation for the particular host using HostMgr.
+ ///
+ /// @param data_source Host data source to which reservation is inserted and
+ /// from which it will be retrieved.
+ void testGet4(BaseHostDataSource& data_source);
+
+ /// @brief This test verifies that it is possible to retrieve negative
+ /// cached reservation with and only with get4Any.
+ void testGet4Any();
+
+ /// @brief This test verifies that it is possible to retrieve an IPv6
+ /// reservation for the particular host using HostMgr.
+ ///
+ /// @param data_source Host data source to which reservation is inserted and
+ /// from which it will be retrieved.
+ void testGet6(BaseHostDataSource& data_source);
+
+ /// @brief This test verifies that it is possible to retrieve negative
+ /// cached reservation with and only with get6Any.
+ void testGet6Any();
+
+ /// @brief This test verifies that it is possible to retrieve an IPv6
+ /// prefix reservation for the particular host using HostMgr.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGet6ByPrefix(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv4 subnet and IPv4 address.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll4BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified DHCPv6 subnet and IPv6 address.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll6BySubnetIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified IPv6 address.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll6ByIP(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr returns all reservations for the
+ /// specified IPv6 prefix/es.
+ ///
+ /// If reservations are added to different host data sources, it is expected
+ /// that the @c HostMgr will retrieve reservations from both of them.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testGetAll6ByIpPrefix(BaseHostDataSource& data_source1, BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr adds the reservations to any
+ /// data source.
+ ///
+ /// The reservations are added to the external database (alternate sources)
+ /// by default but the primary source may be changed on demand too.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testAdd(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr deletes the reservations by
+ /// the subnet ID and subnet address.
+ ///
+ /// The reservations are deleted from the external database (alternate
+ /// sources) only by default but the primary source may be changed on
+ /// demand too.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testDeleteByIDAndAddress(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr deletes the IPv4 reservations by
+ /// the subnet ID and identifier.
+ ///
+ /// The reservations are deleted from the external database (alternate
+ /// sources) only by default but the primary source may be changed on
+ /// demand too.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testDelete4ByIDAndIdentifier(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief This test verifies that HostMgr deletes the IPv6 reservations by
+ /// the subnet ID and identifier.
+ ///
+ /// The reservations are deleted from the external database (alternate
+ /// sources) only by default but the primary source may be changed on
+ /// demand too.
+ ///
+ /// @param data_source1 Host data source to which first reservation is
+ /// inserted.
+ /// @param data_source2 Host data source to which second reservation is
+ /// inserted.
+ void testDelete6ByIDAndIdentifier(BaseHostDataSource& data_source1,
+ BaseHostDataSource& data_source2);
+
+ /// @brief Utility function that returns true if a given data source
+ /// is primary (it isn't an alternate source).
+ /// @param data_source Host data source to check.
+ /// @return True if the data source is primary. Otherwise, false.
+ bool isPrimaryDataSource(const BaseHostDataSource& data_source) const;
+
+ /// @brief HW addresses to be used by the tests.
+ std::vector<HWAddrPtr> hwaddrs_;
+ /// @brief DUIDs to be used by the tests.
+ std::vector<DuidPtr> duids_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/host_data_source_utils.cc b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc
new file mode 100644
index 0000000..8d378d0
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/host_data_source_utils.cc
@@ -0,0 +1,407 @@
+// Copyright (C) 2018-2023 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 <dhcpsrv/testutils/host_data_source_utils.h>
+#include <asiolink/io_address.h>
+#include <boost/foreach.hpp>
+#include <cc/data.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+std::vector<uint8_t>
+HostDataSourceUtils::generateHWAddr(const bool new_identifier) {
+ // Let's use something that is easily printable. That's convenient
+ // if you need to enter MySQL queries by hand.
+ static uint8_t hwaddr[] = {65, 66, 67, 68, 69, 70};
+
+ if (new_identifier) {
+ // Increase the address for the next time we use it.
+ // This is primitive, but will work for 65k unique
+ // addresses.
+ hwaddr[sizeof(hwaddr) - 1]++;
+ if (hwaddr[sizeof(hwaddr) - 1] == 0) {
+ hwaddr[sizeof(hwaddr) - 2]++;
+ }
+ }
+ return (std::vector<uint8_t>(hwaddr, hwaddr + sizeof(hwaddr)));
+}
+
+std::vector<uint8_t>
+HostDataSourceUtils::generateIdentifier(const bool new_identifier) {
+ // Let's use something that is easily printable. That's convenient
+ // if you need to enter MySQL queries by hand.
+ static uint8_t ident[] = {65, 66, 67, 68, 69, 70, 71, 72, 73, 74};
+
+ if (new_identifier) {
+ // Increase the identifier for the next time we use it.
+ // This is primitive, but will work for 65k unique identifiers.
+ ident[sizeof(ident) - 1]++;
+ if (ident[sizeof(ident) - 1] == 0) {
+ ident[sizeof(ident) - 2]++;
+ }
+ }
+ return (std::vector<uint8_t>(ident, ident + sizeof(ident)));
+}
+
+HostPtr
+HostDataSourceUtils::initializeHost4(const std::string& address,
+ const Host::IdentifierType& id,
+ const bool new_identifier) {
+ std::vector<uint8_t> ident;
+ if (id == Host::IDENT_HWADDR) {
+ ident = generateHWAddr(new_identifier);
+ } else {
+ ident = generateIdentifier(new_identifier);
+ }
+
+ // Let's create ever increasing subnet-ids. Let's keep those different,
+ // so subnet4 != subnet6. Useful for catching cases if the code confuses
+ // subnet4 with subnet6.
+ static SubnetID subnet4 = 0;
+ static SubnetID subnet6 = 100;
+ ++subnet4;
+ ++subnet6;
+
+ IOAddress addr(address);
+ HostPtr host(new Host(&ident[0], ident.size(), id, subnet4, subnet6, addr));
+
+ return (host);
+}
+
+HostPtr
+HostDataSourceUtils::initializeHost6(std::string address,
+ Host::IdentifierType identifier,
+ bool prefix,
+ bool new_identifier,
+ const std::string auth_key) {
+ std::vector<uint8_t> ident;
+ switch (identifier) {
+ case Host::IDENT_HWADDR:
+ ident = generateHWAddr(new_identifier);
+ break;
+ case Host::IDENT_DUID:
+ ident = generateIdentifier(new_identifier);
+ break;
+ default:
+ ADD_FAILURE() << "Unknown IdType: " << identifier;
+ return HostPtr();
+ }
+
+ // Let's create ever increasing subnet-ids. Let's keep those different,
+ // so subnet4 != subnet6. Useful for catching cases if the code confuses
+ // subnet4 with subnet6.
+ static SubnetID subnet4 = 0;
+ static SubnetID subnet6 = 100;
+ ++subnet4;
+ ++subnet6;
+
+ HostPtr host(new Host(&ident[0], ident.size(), identifier, subnet4, subnet6,
+ IOAddress("0.0.0.0")));
+
+ host->setKey(AuthKey(auth_key));
+
+ if (!prefix) {
+ // Create IPv6 reservation (for an address)
+ IPv6Resrv resv(IPv6Resrv::TYPE_NA, IOAddress(address), 128);
+ host->addReservation(resv);
+ } else {
+ // Create IPv6 reservation for a /64 prefix
+ IPv6Resrv resv(IPv6Resrv::TYPE_PD, IOAddress(address), 64);
+ host->addReservation(resv);
+ }
+ return (host);
+}
+
+bool
+HostDataSourceUtils::reservationExists(const IPv6Resrv& resrv,
+ const IPv6ResrvRange& range) {
+ for (IPv6ResrvIterator it = range.first; it != range.second; ++it) {
+ if (resrv == it->second) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+HostDataSourceUtils::compareHwaddrs(const ConstHostPtr& host1,
+ const ConstHostPtr& host2,
+ bool expect_match) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+
+ // Compare if both have or have not HWaddress set.
+ if ((host1->getHWAddress() && !host2->getHWAddress()) ||
+ (!host1->getHWAddress() && host2->getHWAddress())) {
+ // One host has hardware address set while the other has not.
+ // Let's see if it's a problem.
+ if (expect_match) {
+ ADD_FAILURE() << "Host comparison failed: host1 hwaddress="
+ << host1->getHWAddress()
+ << ", host2 hwaddress=" << host2->getHWAddress();
+ }
+ return;
+ }
+
+ // Now we know that either both or neither have hw address set.
+ // If host1 has it, we can proceed to value comparison.
+ if (host1->getHWAddress()) {
+ if (expect_match) {
+ // Compare the actual address if they match.
+ EXPECT_TRUE(*host1->getHWAddress() == *host2->getHWAddress());
+ } else {
+ EXPECT_FALSE(*host1->getHWAddress() == *host2->getHWAddress());
+ }
+ if (*host1->getHWAddress() != *host2->getHWAddress()) {
+ cout << host1->getHWAddress()->toText(true) << endl;
+ cout << host2->getHWAddress()->toText(true) << endl;
+ }
+ }
+}
+
+void
+HostDataSourceUtils::compareDuids(const ConstHostPtr& host1,
+ const ConstHostPtr& host2,
+ bool expect_match) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+
+ // compare if both have or have not DUID set
+ if ((host1->getDuid() && !host2->getDuid()) ||
+ (!host1->getDuid() && host2->getDuid())) {
+ // One host has a DUID and the other doesn't.
+ // Let's see if it's a problem.
+ if (expect_match) {
+ ADD_FAILURE() << "DUID comparison failed: host1 duid="
+ << host1->getDuid()
+ << ", host2 duid=" << host2->getDuid();
+ }
+ return;
+ }
+
+ // Now we know that either both or neither have DUID set.
+ // If host1 has it, we can proceed to value comparison.
+ if (host1->getDuid()) {
+ if (expect_match) {
+ EXPECT_TRUE(*host1->getDuid() == *host2->getDuid());
+ } else {
+ EXPECT_FALSE(*host1->getDuid() == *host2->getDuid());
+ }
+ if (*host1->getDuid() != *host2->getDuid()) {
+ cout << host1->getDuid()->toText() << endl;
+ cout << host2->getDuid()->toText() << endl;
+ }
+ }
+}
+
+void
+HostDataSourceUtils::compareHosts(const ConstHostPtr& host1,
+ const ConstHostPtr& host2) {
+ ASSERT_TRUE(host1);
+ ASSERT_TRUE(host2);
+ // Let's compare HW addresses and expect match.
+ compareHwaddrs(host1, host2, true);
+
+ // Now compare DUIDs
+ compareDuids(host1, host2, true);
+
+ // Now check that the identifiers returned as vectors are the same
+ EXPECT_EQ(host1->getIdentifierType(), host2->getIdentifierType());
+ EXPECT_TRUE(host1->getIdentifier() == host2->getIdentifier());
+
+ // Check host parameters
+ EXPECT_EQ(host1->getIPv4SubnetID(), host2->getIPv4SubnetID());
+ EXPECT_EQ(host1->getIPv6SubnetID(), host2->getIPv6SubnetID());
+ EXPECT_EQ(host1->getIPv4Reservation(), host2->getIPv4Reservation());
+ EXPECT_EQ(host1->getHostname(), host2->getHostname());
+ EXPECT_EQ(host1->getNextServer(), host2->getNextServer());
+ EXPECT_EQ(host1->getServerHostname(), host2->getServerHostname());
+ EXPECT_EQ(host1->getBootFileName(), host2->getBootFileName());
+ EXPECT_TRUE(host1->getKey() == host2->getKey());
+ ConstElementPtr ctx1 = host1->getContext();
+ ConstElementPtr ctx2 = host2->getContext();
+ if (ctx1) {
+ EXPECT_TRUE(ctx2);
+ if (ctx2) {
+ EXPECT_EQ(*ctx1, *ctx2);
+ }
+ } else {
+ EXPECT_FALSE(ctx2);
+ }
+
+ // Compare IPv6 reservations
+ compareReservations6(host1->getIPv6Reservations(),
+ host2->getIPv6Reservations());
+
+ // Compare client classification details
+ compareClientClasses(host1->getClientClasses4(),
+ host2->getClientClasses4());
+
+ compareClientClasses(host1->getClientClasses6(),
+ host2->getClientClasses6());
+
+ // Compare DHCPv4 and DHCPv6 options.
+ compareOptions(host1->getCfgOption4(), host2->getCfgOption4());
+ compareOptions(host1->getCfgOption6(), host2->getCfgOption6());
+}
+
+void
+HostDataSourceUtils::compareReservations6(IPv6ResrvRange resrv1,
+ IPv6ResrvRange resrv2) {
+ // Compare number of reservations for both hosts
+ if (std::distance(resrv1.first, resrv1.second) !=
+ std::distance(resrv2.first, resrv2.second)) {
+ ADD_FAILURE() << "Reservation comparison failed, "
+ "hosts got different number of reservations.";
+ return;
+ }
+
+ // Iterate over the range of reservations to find a match in the
+ // reference range.
+ for (IPv6ResrvIterator r1 = resrv1.first; r1 != resrv1.second; ++r1) {
+ IPv6ResrvIterator r2 = resrv2.first;
+ for (; r2 != resrv2.second; ++r2) {
+ // IPv6Resrv object implements equality operator.
+ if (r1->second == r2->second) {
+ break;
+ }
+ }
+ // If r2 iterator reached the end of the range it means that there
+ // is no match.
+ if (r2 == resrv2.second) {
+ ADD_FAILURE() << "No match found for reservation: "
+ << resrv1.first->second.getPrefix().toText();
+ }
+ }
+
+ if (std::distance(resrv1.first, resrv1.second) > 0) {
+ for (; resrv1.first != resrv1.second; resrv1.first++) {
+ IPv6ResrvIterator iter = resrv2.first;
+ while (iter != resrv2.second) {
+ if ((resrv1.first->second.getType() ==
+ iter->second.getType()) &&
+ (resrv1.first->second.getPrefixLen() ==
+ iter->second.getPrefixLen()) &&
+ (resrv1.first->second.getPrefix() ==
+ iter->second.getPrefix())) {
+ break;
+ }
+ iter++;
+ if (iter == resrv2.second) {
+ ADD_FAILURE() << "Reservation comparison failed, "
+ "no match for reservation: "
+ << resrv1.first->second.getPrefix().toText();
+ }
+ }
+ }
+ }
+}
+
+void
+HostDataSourceUtils::compareClientClasses(const ClientClasses& classes1,
+ const ClientClasses& classes2) {
+ EXPECT_TRUE(std::equal(classes1.cbegin(), classes1.cend(), classes2.cbegin()));
+}
+
+void
+HostDataSourceUtils::compareOptions(const ConstCfgOptionPtr& cfg1,
+ const ConstCfgOptionPtr& cfg2) {
+ ASSERT_TRUE(cfg1);
+ ASSERT_TRUE(cfg2);
+
+ // Combine option space names with vendor space names in a single list.
+ std::list<std::string> option_spaces = cfg2->getOptionSpaceNames();
+ std::list<std::string> vendor_spaces = cfg2->getVendorIdsSpaceNames();
+
+ // Make sure that the number of option spaces is equal in both
+ // configurations.
+ EXPECT_EQ(option_spaces.size(), cfg1->getOptionSpaceNames().size());
+ EXPECT_EQ(vendor_spaces.size(), cfg1->getVendorIdsSpaceNames().size());
+
+ // Iterate over all option spaces existing in cfg2.
+ BOOST_FOREACH (std::string space, option_spaces) {
+ // Retrieve options belonging to the current option space.
+ OptionContainerPtr options1 = cfg1->getAll(space);
+ OptionContainerPtr options2 = cfg2->getAll(space);
+ ASSERT_TRUE(options1) << "failed for option space " << space;
+ ASSERT_TRUE(options2) << "failed for option space " << space;
+
+ // If number of options doesn't match, the test fails.
+ ASSERT_EQ(options1->size(), options2->size())
+ << "failed for option space " << space;
+
+ // Iterate over all options within this option space.
+ BOOST_FOREACH (OptionDescriptor desc1, *options1) {
+ OptionDescriptor desc2 = cfg2->get(space, desc1.option_->getType());
+ // Compare persistent flag.
+ EXPECT_EQ(desc1.persistent_, desc2.persistent_)
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+ // Compare formatted value.
+ EXPECT_EQ(desc1.formatted_value_, desc2.formatted_value_)
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+
+ // Compare user context.
+ ConstElementPtr ctx1 = desc1.getContext();
+ ConstElementPtr ctx2 = desc2.getContext();
+ if (ctx1) {
+ EXPECT_TRUE(ctx2);
+ if (ctx2) {
+ EXPECT_EQ(*ctx1, *ctx2)
+ << "failed for option " << space << "." << desc1.option_->getType();
+ }
+ } else {
+ EXPECT_FALSE(ctx2);
+ }
+
+ // Retrieve options.
+ Option* option1 = desc1.option_.get();
+ Option* option2 = desc2.option_.get();
+
+ // Options must be represented by the same C++ class derived from
+ // the Option class.
+ EXPECT_TRUE(typeid(*option1) == typeid(*option2))
+ << "Compared DHCP options, having option code "
+ << desc1.option_->getType() << " and belonging to the " << space
+ << " option space, are represented "
+ "by different C++ classes: "
+ << typeid(*option1).name() << " vs " << typeid(*option2).name();
+
+ // Because we use different C++ classes to represent different
+ // options, the simplest way to make sure that the options are
+ // equal is to simply compare them in wire format.
+ OutputBuffer buf1(option1->len());
+ ASSERT_NO_THROW(option1->pack(buf1));
+ OutputBuffer buf2(option2->len());
+ ASSERT_NO_THROW(option2->pack(buf2));
+
+ ASSERT_EQ(buf1.getLength(), buf2.getLength())
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+ EXPECT_EQ(0,
+ memcmp(buf1.getData(), buf2.getData(), buf1.getLength()))
+ << "failed for option " << space << "."
+ << desc1.option_->getType();
+ }
+ }
+}
+
+
+}
+}
+}
+
diff --git a/src/lib/dhcpsrv/testutils/host_data_source_utils.h b/src/lib/dhcpsrv/testutils/host_data_source_utils.h
new file mode 100644
index 0000000..ac96f11
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/host_data_source_utils.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2018-2020 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 HOST_DATA_SOURCE_UTILS_H
+#define HOST_DATA_SOURCE_UTILS_H
+
+#include <config.h>
+#include <dhcpsrv/host.h>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief A helper class for manipulating hosts
+///
+/// Intended to be used in tests and benchmarks.
+class HostDataSourceUtils {
+public:
+ /// @brief Creates a host reservation for specified IPv4 address.
+ ///
+ /// @param address IPv4 address to be set
+ /// @param id Identifier type.
+ /// @param new_identifier Boolean value indicating if new host
+ /// identifier should be generated or the same as previously.
+ ///
+ /// @return generated Host object
+ static isc::dhcp::HostPtr initializeHost4(const std::string& address,
+ const Host::IdentifierType& id,
+ const bool new_identifier = true);
+
+ /// @brief Creates a host reservation for specified IPv6 address.
+ ///
+ /// @param address IPv6 address to be reserved
+ /// @param id type of identifier (IDENT_DUID or IDENT_HWADDR are supported)
+ /// @param prefix reservation type (true = prefix, false = address)
+ /// @param new_identifier Boolean value indicating if new host
+ /// identifier should be generated or the same as previously.
+ ///
+ /// @return generated Host object
+ static HostPtr initializeHost6(std::string address, Host::IdentifierType id,
+ bool prefix, bool new_identifier = true,
+ const std::string key = "");
+
+ /// @brief Generates a hardware address in text version.
+ ///
+ /// @param increase A boolean value indicating if new address (increased)
+ /// must be generated or the same address as previously.
+ /// @return HW address in textual form acceptable by Host constructor
+ static std::vector<uint8_t> generateHWAddr(const bool new_identifier = true);
+
+ /// @brief Generates a host identifier in a textual form..
+ ///
+ /// @param increase A boolean value indicating if new identifier (increased)
+ /// must be generated or the same identifier as previously.
+ /// @return Identifier in textual form acceptable by Host constructor
+ static std::vector<uint8_t> generateIdentifier(const bool new_identifier = true);
+
+ /// @brief Checks if the reservation is in the range of reservations.
+ ///
+ /// @param resrv Reservation to be searched for.
+ /// @param range Range of reservations returned by the @c Host object
+ /// in which the reservation will be searched
+ static bool reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range);
+
+ /// @brief Compares hardware addresses of the two hosts.
+ ///
+ /// This method compares two hardware address and uses gtest
+ /// macros to signal unexpected (mismatch if expect_match is true;
+ /// match if expect_match is false) values.
+ ///
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @param expect_match true = HW addresses expected to be the same,
+ /// false = HW addresses expected to be different
+ static void compareHwaddrs(const ConstHostPtr& host1, const ConstHostPtr& host2,
+ bool expect_match);
+
+ /// @brief Compares DUIDs of the two hosts.
+ ///
+ /// This method compares two DUIDs (client-ids) and uses gtest
+ /// macros to signal unexpected (mismatch if expect_match is true;
+ /// match if expect_match is false) values.
+ ///
+ /// @param host1 first host to be compared
+ /// @param host2 second host to be compared
+ /// @param expect_match true = DUIDs expected to be the same,
+ /// false = DUIDs expected to be different
+ static void compareDuids(const ConstHostPtr& host1, const ConstHostPtr& host2,
+ bool expect_match);
+
+ /// @brief Compares two hosts
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param host1 first host to compare
+ /// @param host2 second host to compare
+ static void compareHosts(const isc::dhcp::ConstHostPtr& host1, const ConstHostPtr& host2);
+
+ /// @brief Compares two IPv6 reservation lists.
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param resv1 first IPv6 reservations list
+ /// @param resv2 second IPv6 reservations list
+ static void compareReservations6(IPv6ResrvRange resv1, IPv6ResrvRange resv2);
+
+ /// @brief Compares two client classes
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param classes1 first list of client classes
+ /// @param classes2 second list of client classes
+ static void compareClientClasses(const ClientClasses& classes1,
+ const ClientClasses& classes2);
+
+ /// @brief Compares options within two configurations.
+ ///
+ /// This method uses gtest macros to signal errors.
+ ///
+ /// @param cfg1 First configuration.
+ /// @param cfg2 Second configuration.
+ static void compareOptions(const ConstCfgOptionPtr& cfg1,
+ const ConstCfgOptionPtr& cfg2);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/lease_file_io.cc b/src/lib/dhcpsrv/testutils/lease_file_io.cc
new file mode 100644
index 0000000..d641a69
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/lease_file_io.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2015-2018 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 <dhcpsrv/testutils/lease_file_io.h>
+#include <fstream>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+LeaseFileIO::LeaseFileIO(const std::string& filename, const bool recreate)
+ : testfile_(filename), recreate_(recreate) {
+ if (recreate_) {
+ removeFile();
+ }
+}
+
+LeaseFileIO::~LeaseFileIO() {
+ if (recreate_) {
+ removeFile();
+ }
+}
+
+bool
+LeaseFileIO::exists() const {
+ std::ifstream fs(testfile_.c_str());
+ bool ok = fs.good();
+ fs.close();
+ return (ok);
+}
+
+std::string
+LeaseFileIO::readFile() const {
+ std::ifstream fs(testfile_.c_str());
+ if (!fs.is_open()) {
+ return ("");
+ }
+ std::string contents((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+ return (contents);
+}
+
+void
+LeaseFileIO::removeFile() const {
+ static_cast<void>(remove(testfile_.c_str()));
+}
+
+void
+LeaseFileIO::writeFile(const std::string& contents) const {
+ std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+ if (fs.is_open()) {
+ fs << contents;
+ fs.close();
+ }
+}
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
diff --git a/src/lib/dhcpsrv/testutils/lease_file_io.h b/src/lib/dhcpsrv/testutils/lease_file_io.h
new file mode 100644
index 0000000..25efd96
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/lease_file_io.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2014-2018 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 LEASE_FILE_IO_H
+#define LEASE_FILE_IO_H
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief This class contains functions to perform IO operations on files.
+///
+/// This class is solely used by unit tests. Some tests often need files
+/// as an input. This class allows for easy creation of text files that can
+/// be later used by unit tests. It also provides method to read the contents
+/// of the existing file and remove existing file (cleanup after unit test).
+class LeaseFileIO {
+public:
+ /// @brief Constructor
+ ///
+ /// @param filename Absolute path to the file.
+ /// @param recreate A boolean flag indicating if the new file should
+ /// be created, even if one exists.
+ LeaseFileIO(const std::string& filename, const bool recreate = true);
+
+ /// @brief Destructor.
+ ~LeaseFileIO();
+
+ /// @brief Check if test file exists on disk.
+ bool exists() const;
+
+ /// @brief Reads whole lease file.
+ ///
+ /// @return Contents of the file.
+ std::string readFile() const;
+
+ /// @brief Removes existing file (if any).
+ void removeFile() const;
+
+ /// @brief Creates file with contents.
+ ///
+ /// @param contents Contents of the file.
+ void writeFile(const std::string& contents) const;
+
+ /// @brief Absolute path to the file used in the tests.
+ std::string testfile_;
+
+ /// @brief Indicates if the file should be recreated during object
+ /// construction and removed during destruction.
+ bool recreate_;
+
+};
+
+}
+}
+}
+
+#endif // LEASE_FILE_IO_H
diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.cc b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc
new file mode 100644
index 0000000..7b6bd22
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.cc
@@ -0,0 +1,424 @@
+// Copyright (C) 2018-2023 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 <dhcpsrv/testutils/memory_host_data_source.h>
+
+using namespace isc::db;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+ConstHostCollection
+MemHostDataSource::getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If identifier type do not match, it's not for us
+ if ((*h)->getIdentifierType() != identifier_type) {
+ continue;
+ }
+ // If the identifier matches, we found one!
+ if ((*h)->getIdentifier() == ident) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAll4(const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when subnet_id matches.
+ if ((*h)->getIPv4SubnetID() == subnet_id) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAll6(const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when subnet_id matches.
+ if ((*h)->getIPv6SubnetID() == subnet_id) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAllbyHostname(const std::string& hostname) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when hostname matches.
+ if ((*h)->getLowerHostname() == hostname) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAllbyHostname4(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when hostname and subnet_id match.
+ if (((*h)->getLowerHostname() == hostname) &&
+ ((*h)->getIPv4SubnetID() == subnet_id)) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAllbyHostname6(const std::string& hostname,
+ const SubnetID& subnet_id) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Keep it when hostname and subnet_id match.
+ if (((*h)->getLowerHostname() == hostname) &&
+ ((*h)->getIPv6SubnetID() == subnet_id)) {
+ hosts.push_back(*h);
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage4(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Skip it when subnet_id does not match.
+ if ((*h)->getIPv4SubnetID() != subnet_id) {
+ continue;
+ }
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage6(const SubnetID& subnet_id,
+ size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // Skip it when subnet_id does not match.
+ if ((*h)->getIPv6SubnetID() != subnet_id) {
+ continue;
+ }
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage4(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getPage6(size_t& /*source_index*/,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const {
+ ConstHostCollection hosts;
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if (lower_host_id && ((*h)->getHostId() <= lower_host_id)) {
+ continue;
+ }
+ hosts.push_back(*h);
+ if (hosts.size() == page_size.page_size_) {
+ break;
+ }
+ }
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAll4(const asiolink::IOAddress& address) const {
+ ConstHostCollection hosts;
+ for (const auto & h : store_) {
+ if (h->getIPv4Reservation() == address) {
+ hosts.push_back(h);
+ }
+ }
+
+ return (hosts);
+}
+
+ConstHostPtr
+MemHostDataSource::get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv4SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ return (*h);
+ }
+ }
+
+ // Nothing found
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+MemHostDataSource::get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv6SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ return (*h);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+MemHostDataSource::get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if ((*h)->getIPv4SubnetID() == subnet_id &&
+ (*h)->getIPv4Reservation() == address) {
+ return (*h);
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+ConstHostCollection
+MemHostDataSource::getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ ConstHostCollection hosts;
+ for (const auto & h : store_) {
+ if (h->getIPv4SubnetID() == subnet_id &&
+ h->getIPv4Reservation() == address) {
+ hosts.push_back(h);
+ }
+ }
+
+ return (hosts);
+}
+
+ConstHostPtr
+MemHostDataSource::get6(const asiolink::IOAddress& /*prefix*/,
+ const uint8_t /*prefix_len*/) const {
+ return (ConstHostPtr());
+}
+
+ConstHostPtr
+MemHostDataSource::get6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+
+ // Naive approach: check hosts one by one
+
+ // First check: subnet-id must match.
+ if ((*h)->getIPv6SubnetID() != subnet_id) {
+ // wrong subnet-id? ok, skip this one
+ continue;
+ }
+
+ // Second check: the v6 reservation must much. This is very simple
+ // as we ignore the reservation type.
+ auto resrvs = (*h)->getIPv6Reservations();
+ for (auto r = resrvs.first; r != resrvs.second; ++r) {
+ if ((*r).second.getPrefix() == address) {
+ return (*h);
+ }
+ }
+ }
+
+ return (ConstHostPtr());
+}
+
+ConstHostCollection
+MemHostDataSource::getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const {
+ ConstHostCollection hosts;
+ for (const auto & h : store_) {
+ if (h->getIPv6SubnetID() != subnet_id) {
+ continue;
+ }
+
+ auto resrvs = h->getIPv6Reservations();
+ for (auto r = resrvs.first; r != resrvs.second; ++r) {
+ if ((*r).second.getPrefix() == address) {
+ hosts.push_back(h);
+ }
+ }
+ }
+
+ return (hosts);
+}
+
+ConstHostCollection
+MemHostDataSource::getAll6(const asiolink::IOAddress& address) const {
+ ConstHostCollection hosts;
+ for (const auto & h : store_) {
+ auto resrvs = h->getIPv6Reservations();
+ for (auto r = resrvs.first; r != resrvs.second; ++r) {
+ if ((*r).second.getPrefix() == address) {
+ hosts.push_back(h);
+ }
+ }
+ }
+
+ return (hosts);
+}
+
+void
+MemHostDataSource::add(const HostPtr& host) {
+ host->setHostId(++next_host_id_);
+ store_.push_back(host);
+}
+
+bool
+MemHostDataSource::del(const SubnetID& subnet_id,
+ const asiolink::IOAddress& addr) {
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ if (addr.isV4()) {
+ if ((*h)->getIPv4SubnetID() == subnet_id &&
+ (*h)->getIPv4Reservation() == addr) {
+ store_.erase(h);
+ return (true);
+ }
+ } else {
+
+ if ((*h)->getIPv6SubnetID() != subnet_id) {
+ continue;
+ }
+
+ // Second check: the v6 reservation must much. This is very simple
+ // as we ignore the reservation type.
+ auto resrvs = (*h)->getIPv6Reservations();
+ for (auto r = resrvs.first; r != resrvs.second; ++r) {
+ if ((*r).second.getPrefix() == addr) {
+ store_.erase(h);
+ return (true);
+ }
+ }
+ }
+ }
+
+ return (false);
+}
+
+bool
+MemHostDataSource::del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv4SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ store_.erase(h);
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+bool
+MemHostDataSource::del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) {
+ vector<uint8_t> ident(identifier_begin, identifier_begin + identifier_len);
+ for (auto h = store_.begin(); h != store_.end(); ++h) {
+ // If either subnet-id or identifier type do not match,
+ // it's not our host
+ if (((*h)->getIPv6SubnetID() != subnet_id) ||
+ ((*h)->getIdentifierType() != identifier_type)) {
+ continue;
+ }
+ // If the identifier matches, we found it!
+ if ((*h)->getIdentifier() == ident) {
+ store_.erase(h);
+ return (true);
+ }
+ }
+ return (false);
+}
+
+size_t
+MemHostDataSource::size() const {
+ return (store_.size());
+}
+
+HostDataSourcePtr
+memFactory(const DatabaseConnection::ParameterMap& /*parameters*/) {
+ return (HostDataSourcePtr(new MemHostDataSource()));
+}
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/memory_host_data_source.h b/src/lib/dhcpsrv/testutils/memory_host_data_source.h
new file mode 100644
index 0000000..6bca9f2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/memory_host_data_source.h
@@ -0,0 +1,352 @@
+// Copyright (C) 2018-2023 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 MEMORY_HOST_DATA_SOURCE_H
+#define MEMORY_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/host_data_source_factory.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Simple host data source implementation for tests.
+///
+/// It used vector<HostPtr> as a storage and iterates through all hosts when
+/// conducting operations. Most operations are skeleton methods that don't
+/// work, just several are implemented. Those are used in the tests.
+class MemHostDataSource : public virtual BaseHostDataSource {
+public:
+
+ /// @brief Constructor.
+ MemHostDataSource() : next_host_id_(0) {
+ }
+
+ /// @brief Destructor.
+ virtual ~MemHostDataSource() = default;
+
+ /// BaseHostDataSource methods.
+
+ /// @brief Return all hosts connected to any subnet for which reservations
+ /// have been made using a specified identifier.
+ ///
+ /// This may return hosts from multiple subnets.
+ ///
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ virtual ConstHostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Return all hosts in a DHCPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts in a DHCPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// @param hostname The lower case hostname.
+ virtual ConstHostCollection
+ getAllbyHostname(const std::string& hostname) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ virtual ConstHostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id) const;
+
+ /// @brief Return range of hosts in a DHCPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Return range of hosts in a DHCPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Return range of hosts.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Return range of hosts.
+ ///
+ /// @param source_index Index of the source (unused).
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ virtual ConstHostCollection
+ getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) const;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// Currently not implemented.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ /// @return Empty collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const asiolink::IOAddress& address) const;
+
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host connected to the IPv4 subnet and having
+ /// a reservation for a specified IPv4 address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ /// @return Const @c Host object using a specified IPv4 address.
+ virtual ConstHostPtr
+ get4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv4 subnet and having
+ /// a reservation for a specified address.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv4 address.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll4(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return Const @c Host object for which reservation has been made using
+ /// the specified identifier.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) const;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// Currently not implemented.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ /// @return Const @c Host object using a specified IPv6 prefix.
+ virtual ConstHostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address or prefix.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ /// @return Const @c Host object using a specified IPv6 address/prefix.
+ virtual ConstHostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) const;
+
+ /// @brief Returns all hosts connected to the IPv6 subnet and having
+ /// a reservation for a specified address or delegated prefix (lease).
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const SubnetID& subnet_id,
+ const asiolink::IOAddress& address) const;
+
+ // @brief Returns all hosts having a reservation for a specified
+ // address or delegated prefix (lease).
+ ///
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return Collection of const @c Host objects.
+ virtual ConstHostCollection
+ getAll6(const asiolink::IOAddress& address) const;
+
+
+ /// @brief Adds a new host to the collection.
+ ///
+ /// @param host Pointer to the new @c Host object being added.
+ virtual void add(const HostPtr& host);
+
+ /// @brief Attempts to delete a host by (subnet-id, address)
+ ///
+ /// @param subnet_id subnet identifier.
+ /// @param addr specified address.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del(const SubnetID& subnet_id, const asiolink::IOAddress& addr);
+
+ /// @brief Attempts to delete a host by (subnet-id4, identifier, identifier-type)
+ ///
+ /// @param subnet_id IPv4 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del4(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Attempts to delete a host by (subnet-id6, identifier, identifier-type)
+ ///
+ /// @param subnet_id IPv6 Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ /// @return true if deletion was successful, false if the host was not there.
+ /// @throw various exceptions in case of errors
+ virtual bool del6(const SubnetID& subnet_id,
+ const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len);
+
+ /// @brief Return backend type
+ ///
+ /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
+ ///
+ /// @return Type of the backend.
+ virtual std::string getType() const {
+ return ("mem");
+ }
+
+ /// More lease backend?
+
+ /// @brief Returns name of the database or file used by the backend
+ /// @return "mem" string"
+ virtual std::string getName() const {
+ return (std::string("mem"));
+ }
+
+ /// @brief Returns description of the backend.
+ /// @return description
+ virtual std::string getDescription() const {
+ return (std::string("In memory storage, mostly useful for testing."));
+ }
+
+ /// @brief Returns version this backend
+ /// @return two numbers that each represent practical value of this backend.
+ virtual std::pair<uint32_t, uint32_t> getVersion() const {
+ return (std::make_pair(0,0));
+ }
+
+ /// Specific methods.
+
+ /// @brief Returns store size.
+ ///
+ /// @return number of hosts in the store.
+ virtual size_t size() const;
+
+ /// @brief Controls whether IP reservations are unique or non-unique.
+ ///
+ /// In a typical case, the IP reservations are unique and backends verify
+ /// prior to adding a host reservation to the database that the reservation
+ /// for a given IP address does not exist. In some cases it may be required
+ /// to allow non-unique IP reservations, e.g. in the case when a host has
+ /// several interfaces and independently of which interface is used by this
+ /// host to communicate with the DHCP server the same IP address should be
+ /// assigned. In this case the @c unique value should be set to false to
+ /// disable the checks for uniqueness on the backend side.
+ ///
+ /// All backends are required to support the case when unique setting is
+ /// @c true and they must use this setting by default.
+ ///
+ /// @param unique boolean flag indicating if the IP reservations must be
+ /// unique or can be non-unique.
+ /// @return true if the new setting was accepted by the backend or false
+ /// otherwise.
+ virtual bool setIPReservationsUnique(const bool) {
+ return (true);
+ }
+
+protected:
+ // This is very simple storage.
+
+ /// @brief Store
+ std::vector<HostPtr> store_;
+
+ /// @brief Next host id
+ uint64_t next_host_id_;
+};
+
+/// Pointer to the Mem host data source.
+typedef boost::shared_ptr<MemHostDataSource> MemHostDataSourcePtr;
+
+/// @brief Factory function.
+///
+/// @param parameters
+/// @return A pointer to a base host data source instance.
+HostDataSourcePtr
+memFactory(const db::DatabaseConnection::ParameterMap& /*parameters*/);
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc
new file mode 100644
index 0000000..ac142d0
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2019-2022 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 <dhcpsrv/testutils/mysql_generic_backend_unittest.h>
+#include <mysql/testutils/mysql_schema.h>
+
+using namespace isc::db;
+using namespace isc::db::test;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+MySqlGenericBackendTest::MySqlGenericBackendTest()
+ : GenericBackendTest() {
+ createMySQLSchema();
+}
+
+size_t
+MySqlGenericBackendTest::countRows(MySqlConnection& conn, const std::string& table) {
+ // Execute a simple select query on all rows.
+ std::string query = "SELECT * FROM " + table;
+ auto status = mysql_query(conn.mysql_, query.c_str());
+ if (status != 0) {
+ ADD_FAILURE() << "Query failed: " << mysql_error(conn.mysql_);
+ return (0);
+ }
+
+ // Get the number of rows returned.
+ MYSQL_RES* res = mysql_store_result(conn.mysql_);
+ unsigned numrows = static_cast<unsigned>(mysql_num_rows(res));
+
+ // Free the result allocated.
+ mysql_free_result(res);
+
+ return (numrows);
+}
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h
new file mode 100644
index 0000000..1526378
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/mysql_generic_backend_unittest.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2018-2022 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 MYSQL_GENERIC_BACKEND_UNITTEST_H
+#define MYSQL_GENERIC_BACKEND_UNITTEST_H
+
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <mysql/mysql_connection.h>
+
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class with utility functions for testing
+/// MySQL database backends.
+class MySqlGenericBackendTest : public GenericBackendTest {
+public:
+
+ /// @brief Constructor.
+ MySqlGenericBackendTest();
+
+ /// @brief Counts rows in a selected table in MySQL database.
+ ///
+ /// One of the common applications of this method is to check whether the
+ /// expected number of rows were deleted from the specified table. In
+ /// relational databases, a deletion of a raw in one table causes deletion of
+ /// rows in other tables, e.g. via cascaded delete or triggers. This method
+ /// can be used to verify that the deletion took place in the dependent
+ /// tables.
+ ///
+ /// @param conn MySql connection to be used for the query.
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ static size_t countRows(db::MySqlConnection& conn, const std::string& table);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc
new file mode 100644
index 0000000..8d588a1
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright (C) 2021-2022 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 <dhcpsrv/testutils/pgsql_generic_backend_unittest.h>
+#include <pgsql/testutils/pgsql_schema.h>
+
+using namespace isc::db;
+using namespace isc::db::test;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+PgSqlGenericBackendTest::PgSqlGenericBackendTest()
+ : GenericBackendTest() {
+ createPgSQLSchema();
+}
+
+size_t
+PgSqlGenericBackendTest::countRows(PgSqlConnection& conn, const std::string& table) {
+ // Execute a simple select query on all rows.
+ std::string query = "SELECT * FROM " + table;
+ PGresult* result = PQexec(conn.conn_, query.c_str());
+ if (!result) {
+ ADD_FAILURE() << "Query failed and no status returned";
+ return (0);
+ }
+
+ ExecStatusType status = PQresultStatus(result);
+ if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) {
+ ADD_FAILURE() << "The query returned status " << status;
+ }
+
+ // Get the number of rows returned.
+ // We don't care about the content, just the number of rows.
+ unsigned numrows = PQntuples(result);
+
+ // Free the result allocated.
+ PQclear(result);
+
+ return (numrows);
+}
+
+
+} // end of namespace isc::dhcp::test
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h
new file mode 100644
index 0000000..dd944b3
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/pgsql_generic_backend_unittest.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2021-2022 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 PGSQL_GENERIC_BACKEND_UNITTEST_H
+#define PGSQL_GENERIC_BACKEND_UNITTEST_H
+
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <pgsql/pgsql_connection.h>
+
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Generic test fixture class with utility functions for testing
+/// PgSQL database backends.
+class PgSqlGenericBackendTest : public GenericBackendTest {
+public:
+
+ /// @brief Constructor.
+ PgSqlGenericBackendTest();
+
+ /// @brief Counts rows in a selected table in PgSQL database.
+ ///
+ /// One of the common applications of this method is to check whether the
+ /// expected number of rows were deleted from the specified table. In
+ /// relational databases, a deletion of a raw in one table causes deletion of
+ /// rows in other tables, e.g. via cascaded delete or triggers. This method
+ /// can be used to verify that the deletion took place in the dependent
+ /// tables.
+ ///
+ /// @param conn PgSql connection to be used for the query.
+ /// @param table Table name.
+ /// @return Number of rows in the specified table.
+ static size_t countRows(db::PgSqlConnection& conn, const std::string& table);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend.h b/src/lib/dhcpsrv/testutils/test_config_backend.h
new file mode 100644
index 0000000..d78f97f
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2018-2019 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 TEST_CONFIG_BACKEND_H
+#define TEST_CONFIG_BACKEND_H
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Type of pointers to stamped elements.
+typedef boost::shared_ptr<data::StampedElement> StampedElementPtr;
+
+/// @brief Base class for implementing fake backends
+template<typename ConfigBackendType>
+class TestConfigBackend : public ConfigBackendType {
+public:
+ /// @brief Constructor
+ ///
+ /// @param params database connection parameters
+ /// @throw BadValue if parameters do not include "type"
+ TestConfigBackend(const db::DatabaseConnection::ParameterMap& params)
+ : connection_(params) {
+ try {
+ db_type_ = connection_.getParameter("type");
+ } catch (...) {
+ isc_throw(BadValue, "Backend parameters must include \"type\"");
+ }
+
+ try {
+ host_ = connection_.getParameter("host");
+ } catch (...) {
+ host_ = "default_host";
+ }
+
+ try {
+ port_ = boost::lexical_cast<uint16_t>(connection_.getParameter("host"));
+ } catch (...) {
+ port_ = 0;
+ }
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~TestConfigBackend(){};
+
+ /// @brief Returns backend type.
+ ///
+ /// @return string db_type name
+ virtual std::string getType() const {
+ return (db_type_);
+ }
+
+ /// @brief Returns backend host.
+ ///
+ /// @return string host
+ virtual std::string getHost() const {
+ return (host_);
+ }
+
+ /// @brief Returns backend port.
+ ///
+ /// @return uint16_t port
+ virtual uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Returns server tag to be associated with the stored configuration.
+ ///
+ /// @param server_selector Server selector.
+ std::string getServerTag(const db::ServerSelector& server_selector) const {
+ if (server_selector.getType() == db::ServerSelector::Type::ALL) {
+ return ("all");
+ }
+ // Return first tag found.
+ auto tags = server_selector.getTags();
+ if (!tags.empty()) {
+ return (tags.begin()->get());
+ }
+ return ("");
+ }
+
+ /// @brief Merge server tags for a stamped element and a server selector.
+ ///
+ /// @param elem Stamped element to update.
+ /// @param server_selector Server selector.
+ void mergeServerTags(const StampedElementPtr& elem,
+ const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ elem->setServerTag(tag.get());
+ }
+ }
+
+ /// @brief Fake database connection
+ db::DatabaseConnection connection_;
+
+ /// @brief Back end type
+ std::string db_type_;
+
+ /// @brief Back end host
+ std::string host_;
+
+ /// @brief Back end port
+ uint16_t port_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // TEST_CONFIG_BACKEND_H
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc
new file mode 100644
index 0000000..8648d97
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.cc
@@ -0,0 +1,1452 @@
+// Copyright (C) 2018-2021 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 <database/database_connection.h>
+#include <test_config_backend_dhcp4.h>
+#include <list>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+bool
+TestConfigBackendDHCPv4::registerBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type) {
+ return(mgr.registerBackendFactory(db_type,
+ [](const db::DatabaseConnection::ParameterMap& params)
+ -> dhcp::ConfigBackendDHCPv4Ptr {
+ return (TestConfigBackendDHCPv4Ptr(new TestConfigBackendDHCPv4(params)));
+ })
+ );
+}
+
+void
+TestConfigBackendDHCPv4::unregisterBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type) {
+ mgr.unregisterBackendFactory(db_type);
+}
+
+Subnet4Ptr
+TestConfigBackendDHCPv4::getSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const{
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.cend()) {
+ return (Subnet4Ptr());
+ }
+ Subnet4Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet4Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet4Ptr());
+}
+
+Subnet4Ptr
+TestConfigBackendDHCPv4::getSubnet4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.cend()) {
+ return (Subnet4Ptr());
+ }
+ Subnet4Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet4Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet4Ptr());
+}
+
+Subnet4Collection
+TestConfigBackendDHCPv4::getAllSubnets4(const db::ServerSelector& server_selector) const {
+ Subnet4Collection subnets;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ subnets.insert(subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ subnets.insert(subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ subnets.insert(subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet4Collection
+TestConfigBackendDHCPv4::getModifiedSubnets4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = subnets_.get<SubnetModificationTimeIndexTag>();
+ Subnet4Collection subnets;
+ auto lb = index.lower_bound(modification_time);
+ for (auto subnet = lb; subnet != index.end(); ++subnet) {
+ if (server_selector.amAny()) {
+ subnets.insert(*subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*subnet)->getServerTags().empty()) {
+ subnets.insert(*subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ subnets.insert(*subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*subnet)->hasAllServerTag()) {
+ subnets.insert(*subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet4Collection
+TestConfigBackendDHCPv4::getSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const {
+ Subnet4Collection subnets;
+
+ // Subnet collection does not include the index by shared network name.
+ // We need to iterate over the subnets and pick those that are associated
+ // with a shared network.
+ for (auto subnet : subnets_) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !subnet->getServerTags().empty()) {
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !subnet->hasAllServerTag()) {
+ continue;
+ }
+ }
+
+ // The subnet can be associated with a shared network instance or
+ // it may just point to the shared network name. The former is
+ // the case when the subnet belongs to the server configuration.
+ // The latter is the case when the subnet is fetched from the
+ // database.
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (((network && (network->getName() == shared_network_name)) ||
+ (subnet->getSharedNetworkName() == shared_network_name))) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+SharedNetwork4Ptr
+TestConfigBackendDHCPv4::getSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ const auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.cend()) {
+ return (SharedNetwork4Ptr());
+ }
+ SharedNetwork4Ptr network = *network_it;
+ if (server_selector.amAny()) {
+ return (network);
+ }
+ if (server_selector.amUnassigned()) {
+ return (network->getServerTags().empty() ? network : SharedNetwork4Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (network->hasServerTag(ServerTag(tag))) {
+ return (network);
+ }
+ }
+ return (network->hasAllServerTag() ? network : SharedNetwork4Ptr());
+}
+
+SharedNetwork4Collection
+TestConfigBackendDHCPv4::getAllSharedNetworks4(const db::ServerSelector& server_selector) const{
+ SharedNetwork4Collection shared_networks;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ shared_networks.push_back(shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ shared_networks.push_back(shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+SharedNetwork4Collection
+TestConfigBackendDHCPv4::getModifiedSharedNetworks4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = shared_networks_.get<SharedNetworkModificationTimeIndexTag>();
+ SharedNetwork4Collection shared_networks;
+ auto lb = index.lower_bound(modification_time);
+ for (auto shared_network = lb; shared_network != index.end(); ++shared_network) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(*shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*shared_network)->getServerTags().empty()) {
+ shared_networks.push_back(*shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*shared_network)->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(*shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*shared_network)->hasAllServerTag()) {
+ shared_networks.push_back(*shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+OptionDefinitionPtr
+TestConfigBackendDHCPv4::getOptionDef4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDefinitionPtr();
+ const auto& index = option_defs_.get<1>();
+ auto option_def_it_pair = index.equal_range(code);
+
+ for (auto option_def_it = option_def_it_pair.first;
+ option_def_it != option_def_it_pair.second;
+ ++option_def_it) {
+ if ((*option_def_it)->getOptionSpaceName() == space) {
+ for (auto tag : tags) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ return (*option_def_it);
+ }
+ }
+ if ((*option_def_it)->hasAllServerTag()) {
+ candidate = *option_def_it;
+ }
+ }
+ }
+ return (candidate);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv4::getAllOptionDefs4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ for (auto option_def : option_defs_) {
+ bool got = false;
+ if (server_selector.amUnassigned()) {
+ if (option_def->getServerTags().empty()) {
+ option_defs.push_back(option_def);
+ got = true;
+ }
+ } else {
+ for (auto tag : tags) {
+ if (option_def->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(option_def);
+ got = true;
+ break;
+ }
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option_def->hasAllServerTag() && !server_selector.amUnassigned()) {
+ option_defs.push_back(option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv4::getModifiedOptionDefs4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ const auto& index = option_defs_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option_def = lb; option_def != index.end(); ++option_def) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*option_def)->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(*option_def);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*option_def)->hasAllServerTag()) {
+ option_defs.push_back(*option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDescriptorPtr
+TestConfigBackendDHCPv4::getOption4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDescriptorPtr();
+ const auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(code);
+
+ for (auto option_it = option_it_pair.first; option_it != option_it_pair.second;
+ ++option_it) {
+ if (option_it->space_name_ == space) {
+ for (auto tag : tags) {
+ if (option_it->hasServerTag(ServerTag(tag))) {
+ return (OptionDescriptorPtr(new OptionDescriptor(*option_it)));
+ }
+ }
+ if (option_it->hasAllServerTag()) {
+ candidate = OptionDescriptorPtr(new OptionDescriptor(*option_it));
+ }
+ }
+ }
+
+ return (candidate);
+}
+
+OptionContainer
+TestConfigBackendDHCPv4::getAllOptions4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ for (auto option : options_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option.hasServerTag(ServerTag(tag))) {
+ options.push_back(option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option.hasAllServerTag()) {
+ options.push_back(option);
+ }
+ }
+ return (options);
+}
+
+OptionContainer
+TestConfigBackendDHCPv4::getModifiedOptions4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ const auto& index = options_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option = lb; option != index.end(); ++option) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option->hasServerTag(ServerTag(tag))) {
+ options.push_back(*option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option->hasAllServerTag()) {
+ options.push_back(*option);
+ }
+ }
+ return (options);
+}
+
+StampedValuePtr
+TestConfigBackendDHCPv4::getGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ auto tags = server_selector.getTags();
+ auto candidate = StampedValuePtr();
+ const auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_range = index.equal_range(name);
+ for (auto global_it = global_range.first; global_it != global_range.second;
+ ++global_it) {
+ for (auto tag : tags) {
+ if ((*global_it)->hasServerTag(ServerTag(tag))) {
+ return (*global_it);
+ }
+ }
+ if ((*global_it)->hasAllServerTag()) {
+ candidate = *global_it;
+ }
+ }
+
+ return (candidate);
+}
+
+
+StampedValueCollection
+TestConfigBackendDHCPv4::getAllGlobalParameters4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ for (auto global : globals_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (global->hasServerTag(ServerTag(tag))) {
+ globals.insert(global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (global->hasAllServerTag()) {
+ globals.insert(global);
+ }
+ }
+ return (globals);
+}
+
+StampedValueCollection
+TestConfigBackendDHCPv4::getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ const auto& index = globals_.get<StampedValueModificationTimeIndexTag>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto global = lb; global != index.end(); ++global) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*global)->hasServerTag(ServerTag(tag))) {
+ globals.insert(*global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*global)->hasAllServerTag()) {
+ globals.insert(*global);
+ }
+ }
+ return (globals);
+}
+
+ClientClassDefPtr
+TestConfigBackendDHCPv4::getClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ ClientClassDefPtr client_class;
+ for (auto c : classes_) {
+ if (c->getName() == name) {
+ client_class = c;
+ break;
+ }
+ }
+ if (!client_class || server_selector.amAny()) {
+ return (client_class);
+ }
+ if (server_selector.amUnassigned()) {
+ return (client_class->getServerTags().empty() ? client_class : ClientClassDefPtr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ return (client_class);
+ }
+ }
+ return (client_class->hasAllServerTag() ? client_class : ClientClassDefPtr());
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv4::getAllClientClasses4(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary all_classes;
+ for (auto client_class : classes_) {
+ if (server_selector.amAny()) {
+ all_classes.addClass(client_class);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ all_classes.addClass(client_class);
+ }
+ continue;
+ }
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ all_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ all_classes.addClass(client_class);
+ }
+ }
+ return (all_classes);
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv4::getModifiedClientClasses4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary modified_classes;
+ for (auto client_class : classes_) {
+ if (client_class->getModificationTime() >= modification_time) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ modified_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ modified_classes.addClass(client_class);
+ }
+ }
+ }
+ return (modified_classes);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateClientClass4(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) {
+ int follow_class_index = -1;
+ if (!follow_class_name.empty()) {
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == follow_class_name) {
+ follow_class_index = i;
+ break;
+ }
+ }
+ if (follow_class_index < 0) {
+ isc_throw(BadValue, "class " << follow_class_name << " does not exist");
+
+ }
+ }
+
+ mergeServerTags(client_class, server_selector);
+
+ int existing_class_index = -1;
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == client_class->getName()) {
+ existing_class_index = i;
+ break;
+ }
+ }
+
+ if (follow_class_index < 0) {
+ if (existing_class_index >= 0) {
+ classes_[existing_class_index] = client_class;
+ } else {
+ classes_.push_back(client_class);
+ }
+ } else {
+ if (existing_class_index < 0) {
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ } else {
+ classes_.erase(classes_.begin() + existing_class_index);
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ }
+ }
+}
+
+AuditEntryCollection
+TestConfigBackendDHCPv4::getRecentAuditEntries(const db::ServerSelector&,
+ const boost::posix_time::ptime&,
+ const uint64_t&) const {
+ return (AuditEntryCollection());
+}
+
+ServerCollection
+TestConfigBackendDHCPv4::getAllServers4() const {
+ return (servers_);
+}
+
+ServerPtr
+TestConfigBackendDHCPv4::getServer4(const ServerTag& server_tag) const {
+ const auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server_tag.get());
+ return ((server_it != index.cend()) ? (*server_it) : ServerPtr());
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateSubnet4(const db::ServerSelector& server_selector,
+ const Subnet4Ptr& subnet) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet->getID());
+
+ mergeServerTags(subnet, server_selector);
+
+ if (subnet_it != index.cend()) {
+ index.replace(subnet_it, subnet);
+ } else {
+ index.insert(subnet);
+ }
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateSharedNetwork4(const db::ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network->getName());
+
+ mergeServerTags(shared_network, server_selector);
+
+ if (network_it != index.cend()) {
+ index.replace(network_it, shared_network);
+ } else {
+ index.insert(shared_network);
+ }
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOptionDef4(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) {
+ auto tag = getServerTag(server_selector);
+ option_def->setServerTag(tag);
+
+ // Index #1 is by option code.
+ auto& index1 = option_defs_.get<1>();
+ auto option_def_it_pair1 = index1.equal_range(option_def->getCode());
+
+ for (auto option_def_it = option_def_it_pair1.first;
+ option_def_it != option_def_it_pair1.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index1.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ // Index #2 is by option name.
+ auto& index2 = option_defs_.get<2>();
+ auto option_def_it_pair2 = index2.equal_range(option_def->getName());
+
+ for (auto option_def_it = option_def_it_pair2.first;
+ option_def_it != option_def_it_pair2.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index2.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ option_defs_.push_back(option_def);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) {
+ auto tag = getServerTag(server_selector);
+ option->setServerTag(tag);
+
+ auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(option->option_->getType());
+
+ for (auto option_it = option_it_pair.first;
+ option_it != option_it_pair.second;
+ ++option_it) {
+ if ((option_it->space_name_ == option->space_name_) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ index.replace(option_it, *option);
+ return;
+ }
+ }
+
+ options_.push_back(*option);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ shared_network->getCfgOption()->del(option->space_name_, option->option_->getType());
+ shared_network->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ subnet->getCfgOption()->del(option->space_name_, option->option_->getType());
+ subnet->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+ auto pool = subnet->getPool(Lease::TYPE_V4, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ // Update the option.
+ pool->getCfgOption()->del(option->space_name_, option->option_->getType());
+ pool->getCfgOption()->add(*option, option->space_name_);
+
+ return;
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a pool " << pool_start_address
+ << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a non existing pool " << pool_start_address
+ << " - " << pool_end_address);
+ }
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateGlobalParameter4(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value) {
+ auto tag = getServerTag(server_selector);
+ value->setServerTag(tag);
+
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(value->getName());
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto existing_value = *global_it;
+ if (existing_value->hasServerTag(ServerTag(tag))) {
+ index.replace(global_it, value);
+ return;
+ }
+ }
+
+ index.insert(value);
+}
+
+void
+TestConfigBackendDHCPv4::createUpdateServer4(const db::ServerPtr& server) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server->getServerTagAsText());
+
+ if (server_it != index.end()) {
+ index.replace(server_it, server);
+
+ } else {
+ index.insert(server);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) {
+ auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_prefix));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSubnet4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_id));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllSubnets4(const db::ServerSelector& server_selector) {
+ // Collect subnet to remove by ID.
+ std::list<SubnetID> ids;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ ids.push_back(subnet->getID());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ ids.push_back(subnet->getID());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ ids.push_back(subnet->getID());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ ids.push_back(subnet->getID());
+ }
+ }
+
+ // Erase subnets.
+ uint64_t erased = 0;
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ for (auto subnet_id : ids) {
+ erased += index.erase(subnet_id);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) {
+ uint64_t cnt = 0;
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !(*subnet)->getServerTags().empty()) {
+ ++subnet;
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet)->hasAllServerTag()) {
+ ++subnet;
+ continue;
+ }
+ }
+
+ SharedNetwork4Ptr network;
+ (*subnet)->getSharedNetwork(network);
+ if (network && (network->getName() == shared_network_name)) {
+ network->del((*subnet)->getID());
+ }
+
+ if ((network && (network->getName() == shared_network_name)) ||
+ ((*subnet)->getSharedNetworkName() == shared_network_name)) {
+ subnet = subnets_.erase(subnet);
+ ++cnt;
+ } else {
+ ++subnet;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*network_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*network_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*network_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+
+ // Remove this shared network.
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
+ if ((*subnet)->getSharedNetworkName() == name) {
+ (*subnet)->setSharedNetworkName("");
+ }
+ }
+ (*network_it)->delAll();
+ return (index.erase(name));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllSharedNetworks4(const db::ServerSelector& server_selector) {
+ // Collect shared network to remove.
+ std::list<std::string> names;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ names.push_back(shared_network->getName());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ names.push_back(shared_network->getName());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ names.push_back(shared_network->getName());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ names.push_back(shared_network->getName());
+ }
+ }
+
+ // Erase shared networks.
+ uint64_t erased = 0;
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ for (auto name : names) {
+ erased += index.erase(name);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOptionDef4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if (((*option_def_it)->getCode() == code) &&
+ ((*option_def_it)->getOptionSpaceName() == space) &&
+ ((*option_def_it)->hasServerTag(ServerTag(tag)))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllOptionDefs4(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_it = options_.begin(); option_it != options_.end(); ) {
+ if ((option_it->option_->getType() == code) &&
+ (option_it->space_name_ == space) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ option_it = options_.erase(option_it);
+ ++erased;
+ } else {
+ ++option_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ return (shared_network->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ return (subnet->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+
+ auto pool = subnet->getPool(Lease::TYPE_V4, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ return (pool->getCfgOption()->del(space, code));
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to delete an option in a pool "
+ << pool_start_address << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "pool " << pool_start_address << " - " << pool_end_address);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto tag = getServerTag(server_selector);
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(name);
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ index.erase(global_it);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllGlobalParameters4(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t cnt = 0;
+ for (auto global_it = globals_.begin(); global_it != globals_.end(); ) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ global_it = globals_.erase(global_it);
+ cnt++;
+ } else {
+ ++global_it;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ ClientClassDefPtr existing_class;
+ auto c = classes_.begin();
+ for (; c != classes_.end(); ++c) {
+ if ((*c)->getName() == name) {
+ existing_class = (*c);
+ break;
+ }
+ }
+ if (!existing_class) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !existing_class->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (existing_class->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !existing_class->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ classes_.erase(c);
+ return (1);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllClientClasses4(const db::ServerSelector& server_selector) {
+ uint64_t count = 0;
+ for (auto c = classes_.begin(); c != classes_.end(); ++c) {
+ auto client_class = *c;
+ if (server_selector.amAny()) {
+ c = classes_.erase(c);
+ ++count;
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ c = classes_.erase(c);
+ ++count;
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ }
+
+ return (count);
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteServer4(const ServerTag& server_tag) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ return (index.erase(server_tag.get()));
+}
+
+uint64_t
+TestConfigBackendDHCPv4::deleteAllServers4() {
+ auto servers_size = servers_.size();
+ servers_.clear();
+ return (servers_size);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h
new file mode 100644
index 0000000..7791a0f
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp4.h
@@ -0,0 +1,550 @@
+// Copyright (C) 2018-2021 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 TEST_CONFIG_BACKEND_DHCP4
+#define TEST_CONFIG_BACKEND_DHCP4
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/config_backend_dhcp4_mgr.h>
+#include <dhcpsrv/testutils/test_config_backend.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test config backend that implements all of the DHCPv4 API calls
+///
+/// This backend should be used for unit testing the DHCPv4 server and the
+/// commands which manipulate the configuration information stored in the
+/// database.
+///
+/// Server selectors supported by this test configuration backend are a
+/// superset of the server selectors allowed by the API. Therefore, if
+/// additional server selectors are allowed by the API in the future
+/// this backend should not require any additional changes to support them.
+///
+/// This backend stores server configuration information in memory.
+class TestConfigBackendDHCPv4 : public TestConfigBackend<ConfigBackendDHCPv4> {
+public:
+ /// @brief Constructor
+ ///
+ /// @param params Database connection parameters.
+ TestConfigBackendDHCPv4(const db::DatabaseConnection::ParameterMap& params)
+ : TestConfigBackend(params) {
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~TestConfigBackendDHCPv4(){};
+
+ /// @brief Registers the backend type with the given backend manager
+ ///
+ /// @param mgr configuration manager to register with
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when creating backend instances.
+ static bool registerBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Unregisters the backend from the given backend manager
+ ///
+ /// @param mgr configuration manager to unregister from
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when registering the backend type
+ static void unregisterBackendType(ConfigBackendDHCPv4Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet4Ptr
+ getSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getAllSubnets4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getModifiedSubnets4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be retrieved.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet4Collection
+ getSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork4Ptr
+ getSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getAllSharedNetworks4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork4Collection
+ getModifiedSharedNetworks4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter or null if parameter doesn't
+ /// exist.
+ virtual data::StampedValuePtr
+ getGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Collection of global parameters.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters4(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters4(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass4(const db::ServerSelector& selector, const std::string& name) const;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses4(const db::ServerSelector& selector) const;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses4(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief Retrieves all servers.
+ ///
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers4() const;
+
+ /// @brief Retrieves a server.
+ ///
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer4(const data::ServerTag& server_tag) const;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet4(const db::ServerSelector& server_selector,
+ const Subnet4Ptr& subnet);
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork4(const db::ServerSelector& server_selector,
+ const SharedNetwork4Ptr& shared_network);
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef4(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def);
+
+ /// @brief Creates or updates global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter4(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value);
+
+ /// @brief Creates or updates DHCPv4 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass4(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name);
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer4(const db::ServerPtr& server);
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix);
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet4(const db::ServerSelector& server_selector, const SubnetID& subnet_id);
+
+ /// @brief Deletes all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name);
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks..
+ virtual uint64_t
+ deleteSharedNetwork4(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector, const SubnetID& subnet_id,
+ const uint16_t code, const std::string& space);
+
+ /// @brief Deletes pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption4(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter4(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes DHCPv4 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass4(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses4(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer4(const data::ServerTag& server_tag);
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers4();
+
+/// @{
+/// @brief Containers used to house the "database" entries
+ Subnet4Collection subnets_;
+ SharedNetwork4Collection shared_networks_;
+ OptionDefContainer option_defs_;
+ OptionContainer options_;
+ data::StampedValueCollection globals_;
+ std::vector<ClientClassDefPtr> classes_;
+ db::ServerCollection servers_;
+/// @}
+};
+
+/// @brief Shared pointer to the @c TestConfigBackend.
+typedef boost::shared_ptr<TestConfigBackendDHCPv4> TestConfigBackendDHCPv4Ptr;
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // TEST_CONFIG_BACKEND_DHCP4
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc
new file mode 100644
index 0000000..e59ad5d
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.cc
@@ -0,0 +1,1556 @@
+// Copyright (C) 2019-2021 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 <database/database_connection.h>
+#include <test_config_backend_dhcp6.h>
+
+using namespace isc::data;
+using namespace isc::db;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+bool
+TestConfigBackendDHCPv6::registerBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type) {
+ return(mgr.registerBackendFactory(db_type,
+ [](const db::DatabaseConnection::ParameterMap& params)
+ -> dhcp::ConfigBackendDHCPv6Ptr {
+ return (TestConfigBackendDHCPv6Ptr(new TestConfigBackendDHCPv6(params)));
+ })
+ );
+}
+
+void
+TestConfigBackendDHCPv6::unregisterBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type) {
+ mgr.unregisterBackendFactory(db_type);
+}
+
+Subnet6Ptr
+TestConfigBackendDHCPv6::getSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const{
+ const auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.cend()) {
+ return (Subnet6Ptr());
+ }
+ Subnet6Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet6Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet6Ptr());
+}
+
+Subnet6Ptr
+TestConfigBackendDHCPv6::getSubnet6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) const {
+ const auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.cend()) {
+ return (Subnet6Ptr());
+ }
+ Subnet6Ptr subnet = *subnet_it;
+ if (server_selector.amAny()) {
+ return (subnet);
+ }
+ if (server_selector.amUnassigned()) {
+ return (subnet->getServerTags().empty() ? subnet : Subnet6Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ return (subnet);
+ }
+ }
+ return (subnet->hasAllServerTag() ? subnet : Subnet6Ptr());
+}
+
+Subnet6Collection
+TestConfigBackendDHCPv6::getAllSubnets6(const db::ServerSelector& server_selector) const {
+ Subnet6Collection subnets;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ subnets.insert(subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ subnets.insert(subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ subnets.insert(subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet6Collection
+TestConfigBackendDHCPv6::getModifiedSubnets6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = subnets_.get<SubnetModificationTimeIndexTag>();
+ Subnet6Collection subnets;
+ auto lb = index.lower_bound(modification_time);
+ for (auto subnet = lb; subnet != index.end(); ++subnet) {
+ if (server_selector.amAny()) {
+ subnets.insert(*subnet);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*subnet)->getServerTags().empty()) {
+ subnets.insert(*subnet);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ subnets.insert(*subnet);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*subnet)->hasAllServerTag()) {
+ subnets.insert(*subnet);
+ }
+ }
+ return (subnets);
+}
+
+Subnet6Collection
+TestConfigBackendDHCPv6::getSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const {
+ Subnet6Collection subnets;
+
+ // Subnet collection does not include the index by shared network name.
+ // We need to iterate over the subnets and pick those that are associated
+ // with a shared network.
+ for (auto subnet : subnets_) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !subnet->getServerTags().empty()) {
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !subnet->hasAllServerTag()) {
+ continue;
+ }
+ }
+
+ // The subnet can be associated with a shared network instance or
+ // it may just point to the shared network name. The former is
+ // the case when the subnet belongs to the server configuration.
+ // The latter is the case when the subnet is fetched from the
+ // database.
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
+ if (((network && (network->getName() == shared_network_name)) ||
+ (subnet->getSharedNetworkName() == shared_network_name))) {
+ subnets.insert(subnet);
+ }
+ }
+ return (subnets);
+}
+
+SharedNetwork6Ptr
+TestConfigBackendDHCPv6::getSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ const auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.cend()) {
+ return (SharedNetwork6Ptr());
+ }
+ SharedNetwork6Ptr network = *network_it;
+ if (server_selector.amAny()) {
+ return (network);
+ }
+ if (server_selector.amUnassigned()) {
+ return (network->getServerTags().empty() ? network : SharedNetwork6Ptr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (network->hasServerTag(ServerTag(tag))) {
+ return (network);
+ }
+ }
+ return (network->hasAllServerTag() ? network : SharedNetwork6Ptr());
+}
+
+SharedNetwork6Collection
+TestConfigBackendDHCPv6::getAllSharedNetworks6(const db::ServerSelector& server_selector) const{
+ SharedNetwork6Collection shared_networks;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ shared_networks.push_back(shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ shared_networks.push_back(shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+SharedNetwork6Collection
+TestConfigBackendDHCPv6::getModifiedSharedNetworks6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ const auto& index = shared_networks_.get<SharedNetworkModificationTimeIndexTag>();
+ SharedNetwork6Collection shared_networks;
+ auto lb = index.lower_bound(modification_time);
+ for (auto shared_network = lb; shared_network != index.end(); ++shared_network) {
+ if (server_selector.amAny()) {
+ shared_networks.push_back(*shared_network);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if ((*shared_network)->getServerTags().empty()) {
+ shared_networks.push_back(*shared_network);
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*shared_network)->hasServerTag(ServerTag(tag))) {
+ shared_networks.push_back(*shared_network);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*shared_network)->hasAllServerTag()) {
+ shared_networks.push_back(*shared_network);
+ }
+ }
+ return (shared_networks);
+}
+
+OptionDefinitionPtr
+TestConfigBackendDHCPv6::getOptionDef6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDefinitionPtr();
+ const auto& index = option_defs_.get<1>();
+ auto option_def_it_pair = index.equal_range(code);
+
+ for (auto option_def_it = option_def_it_pair.first;
+ option_def_it != option_def_it_pair.second;
+ ++option_def_it) {
+ if ((*option_def_it)->getOptionSpaceName() == space) {
+ for (auto tag : tags) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ return (*option_def_it);
+ }
+ }
+ if ((*option_def_it)->hasAllServerTag()) {
+ candidate = *option_def_it;
+ }
+ }
+ }
+ return (candidate);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv6::getAllOptionDefs6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ for (auto option_def : option_defs_) {
+ bool got = false;
+ if (server_selector.amUnassigned()) {
+ if (option_def->getServerTags().empty()) {
+ option_defs.push_back(option_def);
+ got = true;
+ }
+ } else {
+ for (auto tag : tags) {
+ if (option_def->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(option_def);
+ got = true;
+ break;
+ }
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option_def->hasAllServerTag() && !server_selector.amUnassigned()) {
+ option_defs.push_back(option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDefContainer
+TestConfigBackendDHCPv6::getModifiedOptionDefs6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionDefContainer option_defs;
+ const auto& index = option_defs_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option_def = lb; option_def != index.end(); ++option_def) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*option_def)->hasServerTag(ServerTag(tag))) {
+ option_defs.push_back(*option_def);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*option_def)->hasAllServerTag()) {
+ option_defs.push_back(*option_def);
+ }
+ }
+ return (option_defs);
+}
+
+OptionDescriptorPtr
+TestConfigBackendDHCPv6::getOption6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) const {
+ auto tags = server_selector.getTags();
+ auto candidate = OptionDescriptorPtr();
+ const auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(code);
+
+ for (auto option_it = option_it_pair.first; option_it != option_it_pair.second;
+ ++option_it) {
+ if (option_it->space_name_ == space) {
+ for (auto tag : tags) {
+ if (option_it->hasServerTag(ServerTag(tag))) {
+ return (OptionDescriptorPtr(new OptionDescriptor(*option_it)));
+ }
+ }
+ if (option_it->hasAllServerTag()) {
+ candidate = OptionDescriptorPtr(new OptionDescriptor(*option_it));
+ }
+ }
+ }
+
+ return (candidate);
+}
+
+OptionContainer
+TestConfigBackendDHCPv6::getAllOptions6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ for (auto option : options_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option.hasServerTag(ServerTag(tag))) {
+ options.push_back(option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option.hasAllServerTag()) {
+ options.push_back(option);
+ }
+ }
+ return (options);
+}
+
+OptionContainer
+TestConfigBackendDHCPv6::getModifiedOptions6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ OptionContainer options;
+ const auto& index = options_.get<3>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto option = lb; option != index.end(); ++option) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (option->hasServerTag(ServerTag(tag))) {
+ options.push_back(*option);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (option->hasAllServerTag()) {
+ options.push_back(*option);
+ }
+ }
+ return (options);
+}
+
+StampedValuePtr
+TestConfigBackendDHCPv6::getGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ auto tags = server_selector.getTags();
+ auto candidate = StampedValuePtr();
+ const auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_range = index.equal_range(name);
+ for (auto global_it = global_range.first; global_it != global_range.second;
+ ++global_it) {
+ for (auto tag : tags) {
+ if ((*global_it)->hasServerTag(ServerTag(tag))) {
+ return (*global_it);
+ }
+ }
+ if ((*global_it)->hasAllServerTag()) {
+ candidate = *global_it;
+ }
+ }
+
+ return (candidate);
+}
+
+
+StampedValueCollection
+TestConfigBackendDHCPv6::getAllGlobalParameters6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ for (auto global : globals_) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (global->hasServerTag(ServerTag(tag))) {
+ globals.insert(global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (global->hasAllServerTag()) {
+ globals.insert(global);
+ }
+ }
+ return (globals);
+}
+
+StampedValueCollection
+TestConfigBackendDHCPv6::getModifiedGlobalParameters6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ StampedValueCollection globals;
+ const auto& index = globals_.get<StampedValueModificationTimeIndexTag>();
+ auto lb = index.lower_bound(modification_time);
+ for (auto global = lb; global != index.end(); ++global) {
+ bool got = false;
+ for (auto tag : tags) {
+ if ((*global)->hasServerTag(ServerTag(tag))) {
+ globals.insert(*global);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if ((*global)->hasAllServerTag()) {
+ globals.insert(*global);
+ }
+ }
+ return (globals);
+}
+
+ClientClassDefPtr
+TestConfigBackendDHCPv6::getClientClass6(const db::ServerSelector& server_selector,
+ const std::string& name) const {
+ ClientClassDefPtr client_class;
+ for (auto c : classes_) {
+ if (c->getName() == name) {
+ client_class = c;
+ break;
+ }
+ }
+ if (!client_class || server_selector.amAny()) {
+ return (client_class);
+ }
+ if (server_selector.amUnassigned()) {
+ return (client_class->getServerTags().empty() ? client_class : ClientClassDefPtr());
+ }
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ return (client_class);
+ }
+ }
+ return (client_class->hasAllServerTag() ? client_class : ClientClassDefPtr());
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv6::getAllClientClasses6(const db::ServerSelector& server_selector) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary all_classes;
+ for (auto client_class : classes_) {
+ if (server_selector.amAny()) {
+ all_classes.addClass(client_class);
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ all_classes.addClass(client_class);
+ }
+ continue;
+ }
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ all_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ all_classes.addClass(client_class);
+ }
+ }
+ return (all_classes);
+}
+
+ClientClassDictionary
+TestConfigBackendDHCPv6::getModifiedClientClasses6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const {
+ auto tags = server_selector.getTags();
+ ClientClassDictionary modified_classes;
+ for (auto client_class : classes_) {
+ if (client_class->getModificationTime() >= modification_time) {
+ bool got = false;
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ modified_classes.addClass(client_class);
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ modified_classes.addClass(client_class);
+ }
+ }
+ }
+ return (modified_classes);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateClientClass6(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name) {
+ int follow_class_index = -1;
+ if (!follow_class_name.empty()) {
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == follow_class_name) {
+ follow_class_index = i;
+ break;
+ }
+ }
+ if (follow_class_index < 0) {
+ isc_throw(BadValue, "class " << follow_class_name << " does not exist");
+
+ }
+ }
+
+ mergeServerTags(client_class, server_selector);
+
+ int existing_class_index = -1;
+ for (auto i = 0; i < classes_.size(); ++i) {
+ if (classes_[i]->getName() == client_class->getName()) {
+ existing_class_index = i;
+ break;
+ }
+ }
+
+ if (follow_class_index < 0) {
+ if (existing_class_index >= 0) {
+ classes_[existing_class_index] = client_class;
+ } else {
+ classes_.push_back(client_class);
+ }
+ } else {
+ if (existing_class_index < 0) {
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ } else {
+ classes_.erase(classes_.begin() + existing_class_index);
+ classes_.insert(classes_.begin() + follow_class_index + 1, client_class);
+ }
+ }
+}
+
+AuditEntryCollection
+TestConfigBackendDHCPv6::getRecentAuditEntries(const db::ServerSelector&,
+ const boost::posix_time::ptime&,
+ const uint64_t&) const {
+ return (AuditEntryCollection());
+}
+
+ServerCollection
+TestConfigBackendDHCPv6::getAllServers6() const {
+ return (servers_);
+}
+
+ServerPtr
+TestConfigBackendDHCPv6::getServer6(const ServerTag& server_tag) const {
+ const auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server_tag.get());
+ return ((server_it != index.cend()) ? (*server_it) : ServerPtr());
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateSubnet6(const db::ServerSelector& server_selector,
+ const Subnet6Ptr& subnet) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet->getID());
+
+ mergeServerTags(subnet, server_selector);
+
+ if (subnet_it != index.cend()) {
+ index.replace(subnet_it, subnet);
+ } else {
+ index.insert(subnet);
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateSharedNetwork6(const db::ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network->getName());
+
+ mergeServerTags(shared_network, server_selector);
+
+ if (network_it != index.cend()) {
+ index.replace(network_it, shared_network);
+ } else {
+ index.insert(shared_network);
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOptionDef6(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def) {
+ auto tag = getServerTag(server_selector);
+ option_def->setServerTag(tag);
+
+ // Index #1 is by option code.
+ auto& index1 = option_defs_.get<1>();
+ auto option_def_it_pair1 = index1.equal_range(option_def->getCode());
+
+ for (auto option_def_it = option_def_it_pair1.first;
+ option_def_it != option_def_it_pair1.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index1.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ // Index #2 is by option name.
+ auto& index2 = option_defs_.get<2>();
+ auto option_def_it_pair2 = index2.equal_range(option_def->getName());
+
+ for (auto option_def_it = option_def_it_pair2.first;
+ option_def_it != option_def_it_pair2.second;
+ option_def_it++) {
+ auto existing_option_def = *option_def_it;
+ if ((existing_option_def->getOptionSpaceName() == option_def->getOptionSpaceName()) &&
+ (existing_option_def->hasServerTag(ServerTag(tag)))) {
+ index2.replace(option_def_it, option_def);
+ return;
+ }
+ }
+
+ option_defs_.push_back(option_def);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option) {
+ auto tag = getServerTag(server_selector);
+ option->setServerTag(tag);
+
+ auto& index = options_.get<1>();
+ auto option_it_pair = index.equal_range(option->option_->getType());
+
+ for (auto option_it = option_it_pair.first;
+ option_it != option_it_pair.second;
+ ++option_it) {
+ if ((option_it->space_name_ == option->space_name_) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ index.replace(option_it, *option);
+ return;
+ }
+ }
+
+ options_.push_back(*option);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ shared_network->getCfgOption()->del(option->space_name_, option->option_->getType());
+ shared_network->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to create or update option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to create or update option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ subnet->getCfgOption()->del(option->space_name_, option->option_->getType());
+ subnet->getCfgOption()->add(*option, option->space_name_);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+ auto pool = subnet->getPool(Lease::TYPE_NA, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ // Update the option.
+ pool->getCfgOption()->del(option->space_name_, option->option_->getType());
+ pool->getCfgOption()->add(*option, option->space_name_);
+
+ return;
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a pool " << pool_start_address
+ << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a non existing pool " << pool_start_address
+ << " - " << pool_end_address);
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const OptionDescriptorPtr& option) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pd pool: if it is not here we can directly go to the next subnet.
+ auto pdpool = subnet->getPool(Lease::TYPE_PD, pd_pool_prefix);
+ if (!pdpool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ // Update the option.
+ pdpool->getCfgOption()->del(option->space_name_, option->option_->getType());
+ pdpool->getCfgOption()->add(*option, option->space_name_);
+
+ return;
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length)
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to create or update option in "
+ "a non existing prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length));
+ }
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateGlobalParameter6(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value) {
+ auto tag = getServerTag(server_selector);
+ value->setServerTag(tag);
+
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(value->getName());
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto existing_value = *global_it;
+ if (existing_value->hasServerTag(ServerTag(tag))) {
+ index.replace(global_it, value);
+ return;
+ }
+ }
+
+ index.insert(value);
+}
+
+void
+TestConfigBackendDHCPv6::createUpdateServer6(const db::ServerPtr& server) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ auto server_it = index.find(server->getServerTagAsText());
+
+ if (server_it != index.end()) {
+ index.replace(server_it, server);
+
+ } else {
+ index.insert(server);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) {
+ auto& index = subnets_.get<SubnetPrefixIndexTag>();
+ auto subnet_it = index.find(subnet_prefix);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_prefix));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSubnet6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+ if (subnet_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*subnet_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ return (index.erase(subnet_id));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllSubnets6(const db::ServerSelector& server_selector) {
+ // Collect subnet to remove by ID.
+ std::list<SubnetID> ids;
+ for (auto subnet : subnets_) {
+ if (server_selector.amAny()) {
+ ids.push_back(subnet->getID());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ ids.push_back(subnet->getID());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ ids.push_back(subnet->getID());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (subnet->hasAllServerTag()) {
+ ids.push_back(subnet->getID());
+ }
+ }
+
+ // Erase subnets.
+ uint64_t erased = 0;
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ for (auto subnet_id : ids) {
+ erased += index.erase(subnet_id);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) {
+ uint64_t cnt = 0;
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ) {
+ // Skip subnets which do not match the server selector.
+ if (server_selector.amUnassigned() &&
+ !(*subnet)->getServerTags().empty()) {
+ ++subnet;
+ continue;
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*subnet)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*subnet)->hasAllServerTag()) {
+ ++subnet;
+ continue;
+ }
+ }
+
+ SharedNetwork6Ptr network;
+ (*subnet)->getSharedNetwork(network);
+ if (network && (network->getName() == shared_network_name)) {
+ network->del((*subnet)->getID());
+ }
+
+ if ((network && (network->getName() == shared_network_name)) ||
+ ((*subnet)->getSharedNetworkName() == shared_network_name)) {
+ subnet = subnets_.erase(subnet);
+ ++cnt;
+ } else {
+ ++subnet;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(name);
+ if (network_it == index.end()) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !(*network_it)->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if ((*network_it)->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !(*network_it)->hasAllServerTag()) {
+ return (0);
+ }
+ }
+
+ // Remove this shared network.
+ for (auto subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) {
+ if ((*subnet)->getSharedNetworkName() == name) {
+ (*subnet)->setSharedNetworkName("");
+ }
+ }
+ (*network_it)->delAll();
+ return (index.erase(name));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllSharedNetworks6(const db::ServerSelector& server_selector) {
+ // Collect shared network to remove.
+ std::list<std::string> names;
+ for (auto shared_network : shared_networks_) {
+ if (server_selector.amAny()) {
+ names.push_back(shared_network->getName());
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ names.push_back(shared_network->getName());
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ names.push_back(shared_network->getName());
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (shared_network->hasAllServerTag()) {
+ names.push_back(shared_network->getName());
+ }
+ }
+
+ // Erase shared networks.
+ uint64_t erased = 0;
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ for (auto name : names) {
+ erased += index.erase(name);
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOptionDef6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if (((*option_def_it)->getCode() == code) &&
+ ((*option_def_it)->getOptionSpaceName() == space) &&
+ ((*option_def_it)->hasServerTag(ServerTag(tag)))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllOptionDefs6(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_def_it = option_defs_.begin(); option_def_it != option_defs_.end(); ) {
+ if ((*option_def_it)->hasServerTag(ServerTag(tag))) {
+ option_def_it = option_defs_.erase(option_def_it);
+ ++erased;
+ } else {
+ ++option_def_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const uint16_t code,
+ const std::string& space) {
+ auto tag = getServerTag(server_selector);
+ uint64_t erased = 0;
+ for (auto option_it = options_.begin(); option_it != options_.end(); ) {
+ if ((option_it->option_->getType() == code) &&
+ (option_it->space_name_ == space) &&
+ (option_it->hasServerTag(ServerTag(tag)))) {
+ option_it = options_.erase(option_it);
+ ++erased;
+ } else {
+ ++option_it;
+ }
+ }
+ return (erased);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = shared_networks_.get<SharedNetworkNameIndexTag>();
+ auto network_it = index.find(shared_network_name);
+
+ if (network_it == index.end()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "shared network " << shared_network_name);
+ }
+
+ auto shared_network = *network_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (shared_network->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (shared_network->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (shared_network->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "shared network " << shared_network_name
+ << " not present in a selected server");
+ }
+
+ return (shared_network->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const uint16_t code,
+ const std::string& space) {
+ auto& index = subnets_.get<SubnetSubnetIdIndexTag>();
+ auto subnet_it = index.find(subnet_id);
+
+ if (subnet_it == index.cend()) {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "subnet ID " << subnet_id);
+ }
+
+ auto subnet = *subnet_it;
+ bool found = false;
+ if (server_selector.amUnassigned()) {
+ if (subnet->getServerTags().empty()) {
+ found = true;
+ }
+ } else if (server_selector.amAny()) {
+ found = true;
+ } else if (subnet->hasAllServerTag()) {
+ found = true;
+ } else {
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ isc_throw(BadValue, "attempted to delete option in a "
+ "subnet ID " << subnet_id
+ << " not present in a selected server");
+ }
+
+ return (subnet->getCfgOption()->del(space, code));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pool: if it is not here we can directly go to the next subnet.
+
+ auto pool = subnet->getPool(Lease::TYPE_NA, pool_start_address);
+ if (!pool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ return (pool->getCfgOption()->del(space, code));
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to delete an option in a pool "
+ << pool_start_address << " - " << pool_end_address
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to delete an option in a non existing "
+ "pool " << pool_start_address << " - " << pool_end_address);
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const uint16_t code,
+ const std::string& space) {
+ auto not_in_selected_servers = false;
+ for (auto subnet : subnets_) {
+ // Get the pd pool: if it is not here we can directly go to the next subnet.
+ auto pdpool = subnet->getPool(Lease::TYPE_PD, pd_pool_prefix);
+ if (!pdpool) {
+ continue;
+ }
+
+ // Verify the subnet is in a selected server.
+ if (server_selector.amUnassigned()) {
+ if (!subnet->getServerTags().empty()) {
+ not_in_selected_servers = true;
+ continue;
+ }
+ } else if (!server_selector.amAny() && !subnet->hasAllServerTag()) {
+ auto in_tags = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (subnet->hasServerTag(ServerTag(tag))) {
+ in_tags = true;
+ break;
+ }
+ }
+ if (!in_tags) {
+ // Records the fact a subnet was found but not in a server.
+ not_in_selected_servers = true;
+ continue;
+ }
+ }
+
+ return (pdpool->getCfgOption()->del(space, code));
+ }
+
+ if (not_in_selected_servers) {
+ isc_throw(BadValue, "attempted to delete an option in "
+ "a prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length)
+ << " not present in a selected server");
+ } else {
+ isc_throw(BadValue, "attempted to delete an option in "
+ "a non existing prefix pool " << pd_pool_prefix
+ << "/" << static_cast<unsigned>(pd_pool_prefix_length));
+ }
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ auto tag = getServerTag(server_selector);
+ auto& index = globals_.get<StampedValueNameIndexTag>();
+ auto global_it_pair = index.equal_range(name);
+
+ for (auto global_it = global_it_pair.first; global_it != global_it_pair.second;
+ ++global_it) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ index.erase(global_it);
+ return (1);
+ }
+ }
+ return (0);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllGlobalParameters6(const db::ServerSelector& server_selector) {
+ auto tag = getServerTag(server_selector);
+ uint64_t cnt = 0;
+ for (auto global_it = globals_.begin(); global_it != globals_.end(); ) {
+ auto value = *global_it;
+ if (value->hasServerTag(ServerTag(tag))) {
+ global_it = globals_.erase(global_it);
+ cnt++;
+ } else {
+ ++global_it;
+ }
+ }
+ return (cnt);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteClientClass6(const db::ServerSelector& server_selector,
+ const std::string& name) {
+ ClientClassDefPtr existing_class;
+ auto c = classes_.begin();
+ for (; c != classes_.end(); ++c) {
+ if ((*c)->getName() == name) {
+ existing_class = (*c);
+ break;
+ }
+ }
+ if (!existing_class) {
+ return (0);
+ }
+ if ((server_selector.amUnassigned()) &&
+ !existing_class->getServerTags().empty()) {
+ return (0);
+ }
+ if (!server_selector.amAny()) {
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (existing_class->hasServerTag(ServerTag(tag))) {
+ got = true;
+ break;
+ }
+ }
+ if (!got && !existing_class->hasAllServerTag()) {
+ return (0);
+ }
+ }
+ classes_.erase(c);
+ return (1);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllClientClasses6(const db::ServerSelector& server_selector) {
+ uint64_t count = 0;
+ for (auto c = classes_.begin(); c != classes_.end(); ++c) {
+ auto client_class = *c;
+ if (server_selector.amAny()) {
+ c = classes_.erase(c);
+ ++count;
+ continue;
+ }
+ if (server_selector.amUnassigned()) {
+ if (client_class->getServerTags().empty()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ continue;
+ }
+ bool got = false;
+ auto tags = server_selector.getTags();
+ for (auto tag : tags) {
+ if (client_class->hasServerTag(ServerTag(tag))) {
+ c = classes_.erase(c);
+ ++count;
+ got = true;
+ break;
+ }
+ }
+ if (got) {
+ continue;
+ }
+ if (client_class->hasAllServerTag()) {
+ c = classes_.erase(c);
+ ++count;
+ }
+ }
+
+ return (count);
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteServer6(const ServerTag& server_tag) {
+ auto& index = servers_.get<ServerTagIndexTag>();
+ return (index.erase(server_tag.get()));
+}
+
+uint64_t
+TestConfigBackendDHCPv6::deleteAllServers6() {
+ auto servers_size = servers_.size();
+ servers_.clear();
+ return (servers_size);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h
new file mode 100644
index 0000000..8c65bb2
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_config_backend_dhcp6.h
@@ -0,0 +1,580 @@
+// Copyright (C) 2019-2021 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 TEST_CONFIG_BACKEND_DHCP6
+#define TEST_CONFIG_BACKEND_DHCP6
+
+#include <config.h>
+
+#include <database/database_connection.h>
+#include <database/server.h>
+#include <database/server_collection.h>
+#include <dhcpsrv/config_backend_dhcp6_mgr.h>
+#include <dhcpsrv/testutils/test_config_backend.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test config backend that implements all of the DHCPv6 API calls
+///
+/// This backend should be used for unit testing the DHCPv6 server and the
+/// commands which manipulate the configuration information stored in the
+/// database.
+///
+/// Server selectors supported by this test configuration backend are a
+/// superset of the server selectors allowed by the API. Therefore, if
+/// additional server selectors are allowed by the API in the future
+/// this backend should not require any additional changes to support them.
+///
+/// This backend stores server configuration information in memory.
+class TestConfigBackendDHCPv6 : public TestConfigBackend<ConfigBackendDHCPv6> {
+public:
+ /// @brief Constructor
+ ///
+ /// @param params Database connection parameters.
+ TestConfigBackendDHCPv6(const db::DatabaseConnection::ParameterMap& params)
+ : TestConfigBackend(params) {
+ }
+
+ /// @brief virtual Destructor.
+ virtual ~TestConfigBackendDHCPv6(){};
+
+ /// @brief Registers the backend type with the given backend manager
+ ///
+ /// @param mgr configuration manager to register with
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when creating backend instances.
+ static bool registerBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Unregisters the backend from the given backend manager
+ ///
+ /// @param mgr configuration manager to unregister from
+ /// @brief db_type back end type - Note you will need to
+ /// use the same value here as you do when registering the backend type
+ static void unregisterBackendType(ConfigBackendDHCPv6Mgr& mgr,
+ const std::string& db_type);
+
+ /// @brief Retrieves a single subnet by subnet_prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix) const;
+
+ /// @brief Retrieves a single subnet by subnet identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to be retrieved.
+ /// @return Pointer to the retrieved subnet or NULL if not found.
+ virtual Subnet6Ptr
+ getSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id) const;
+
+ /// @brief Retrieves all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getAllSubnets6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves subnets modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound subnet modification time.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getModifiedSubnets6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be retrieved.
+ /// @return Collection of subnets or empty collection if no subnet found.
+ virtual Subnet6Collection
+ getSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name) const;
+
+ /// @brief Retrieves shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be retrieved.
+ /// @return Pointer to the shared network or NULL if not found.
+ virtual SharedNetwork6Ptr
+ getSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getAllSharedNetworks6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves shared networks modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound shared network modification time.
+ /// @return Collection of shared network or empty collection if
+ /// no shared network found.
+ virtual SharedNetwork6Collection
+ getModifiedSharedNetworks6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option definition by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be retrieved.
+ /// @param space Option space of the option to be retrieved.
+ /// @return Pointer to the option definition or NULL if not found.
+ virtual OptionDefinitionPtr
+ getOptionDef6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getAllOptionDefs6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option definitions modified after specified time.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Lower bound option definition modification
+ /// time.
+ /// @return Collection of option definitions or empty collection if
+ /// no option definition found.
+ virtual OptionDefContainer
+ getModifiedOptionDefs6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves single option by code and space.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Pointer to the retrieved option descriptor or null if
+ /// no option was found.
+ virtual OptionDescriptorPtr
+ getOption6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space) const;
+
+ /// @brief Retrieves all global options.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getAllOptions6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves option modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Lower bound option modification time.
+ /// @return Collection of global options or empty collection if no
+ /// option found.
+ virtual OptionContainer
+ getModifiedOptions6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves global parameter value.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be retrieved.
+ /// @return Value of the global parameter or null if parameter doesn't
+ /// exist.
+ virtual data::StampedValuePtr
+ getGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name) const;
+
+ /// @brief Retrieves all global parameters.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @return Collection of global parameters.
+ virtual data::StampedValueCollection
+ getAllGlobalParameters6(const db::ServerSelector& server_selector) const;
+
+ /// @brief Retrieves global parameters modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of modified global parameters.
+ virtual data::StampedValueCollection
+ getModifiedGlobalParameters6(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves a client class by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Client class name.
+ /// @return Pointer to the retrieved client class.
+ virtual ClientClassDefPtr
+ getClientClass6(const db::ServerSelector& selector, const std::string& name) const;
+
+ /// @brief Retrieves all client classes.
+ ///
+ /// @param selector Server selector.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getAllClientClasses6(const db::ServerSelector& selector) const;
+
+ /// @brief Retrieves client classes modified after specified time.
+ ///
+ /// @param selector Server selector.
+ /// @param modification_time Modification time.
+ /// @return Collection of client classes.
+ virtual ClientClassDictionary
+ getModifiedClientClasses6(const db::ServerSelector& selector,
+ const boost::posix_time::ptime& modification_time) const;
+
+ /// @brief Retrieves the most recent audit entries.
+ ///
+ /// @param server_selector Server selector.
+ /// @param modification_time Timestamp being a lower limit for the returned
+ /// result set, i.e. entries later than specified time are returned.
+ /// @param modification_id Identifier being a lower limit for the returned
+ /// result set, used when two (or more) entries have the same
+ /// modification_time.
+ /// @return Collection of audit entries.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t& modification_id) const;
+
+ /// @brief Retrieves all servers.
+ ///
+ /// @return Collection of servers from the backend.
+ virtual db::ServerCollection
+ getAllServers6() const;
+
+ /// @brief Retrieves a server.
+ ///
+ /// @param server_tag Tag of the server to be retrieved.
+ /// @return Pointer to the server instance or null pointer if no server
+ /// with the particular tag was found.
+ virtual db::ServerPtr
+ getServer6(const data::ServerTag& server_tag) const;
+
+ /// @brief Creates or updates a subnet.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet Subnet to be added or updated.
+ virtual void
+ createUpdateSubnet6(const db::ServerSelector& server_selector,
+ const Subnet6Ptr& subnet);
+
+ /// @brief Creates or updates a shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network Shared network to be added or updated.
+ virtual void
+ createUpdateSharedNetwork6(const db::ServerSelector& server_selector,
+ const SharedNetwork6Ptr& shared_network);
+
+ /// @brief Creates or updates an option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option_def Option definition to be added or updated.
+ virtual void
+ createUpdateOptionDef6(const db::ServerSelector& server_selector,
+ const OptionDefinitionPtr& option_def);
+
+ /// @brief Creates or updates global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of a shared network to which option
+ /// belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of a subnet to which option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const SubnetID& subnet_id,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// the option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates pd pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the pd pool
+ /// to which the option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the pd pool to which
+ /// the option belongs.
+ /// @param option Option to be added or updated.
+ virtual void
+ createUpdateOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const OptionDescriptorPtr& option);
+
+ /// @brief Creates or updates global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param value Value of the global parameter.
+ virtual void
+ createUpdateGlobalParameter6(const db::ServerSelector& server_selector,
+ const data::StampedValuePtr& value);
+
+ /// @brief Creates or updates DHCPv6 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param client_class Client class to be added or updated.
+ /// @param follow_class_name name of the class after which the
+ /// new or updated class should be positioned. An empty value
+ /// causes the class to be appended at the end of the class
+ /// hierarchy.
+ virtual void
+ createUpdateClientClass6(const db::ServerSelector& server_selector,
+ const ClientClassDefPtr& client_class,
+ const std::string& follow_class_name);
+
+ /// @brief Creates or updates a server.
+ ///
+ /// @param server Instance of the server to be stored.
+ virtual void
+ createUpdateServer6(const db::ServerPtr& server);
+
+ /// @brief Deletes subnet by prefix.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_prefix Prefix of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::ServerSelector& server_selector,
+ const std::string& subnet_prefix);
+
+ /// @brief Deletes subnet by identifier.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSubnet6(const db::ServerSelector& server_selector, const SubnetID& subnet_id);
+
+ /// @brief Deletes all subnets.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteAllSubnets6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes all subnets belonging to a specified shared network.
+ ///
+ /// @param server_selector Server selector.
+ /// @param shared_network_name Name of the shared network for which the
+ /// subnets should be deleted.
+ /// @return Number of deleted subnets.
+ virtual uint64_t
+ deleteSharedNetworkSubnets6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name);
+
+ /// @brief Deletes shared network by name.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the shared network to be deleted.
+ /// @return Number of deleted shared networks..
+ virtual uint64_t
+ deleteSharedNetwork6(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all shared networks.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted shared networks.
+ virtual uint64_t
+ deleteAllSharedNetworks6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes option definition.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteOptionDef6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes all option definitions.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted option definitions.
+ virtual uint64_t
+ deleteAllOptionDefs6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes global option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector, const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes shared network level option.
+ ///
+ /// @param selector Server selector.
+ /// @param shared_network_name Name of the shared network which option
+ /// belongs to.
+ /// @param code Code of the option to be deleted.
+ /// @param space Option space of the option to be deleted.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const std::string& shared_network_name,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes subnet level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param subnet_id Identifier of the subnet to which deleted option
+ /// belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector, const SubnetID& subnet_id,
+ const uint16_t code, const std::string& space);
+
+ /// @brief Deletes pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pool_start_address Lower bound address of the pool to which
+ /// deleted option belongs.
+ /// @param pool_end_address Upper bound address of the pool to which the
+ /// deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pool_start_address,
+ const asiolink::IOAddress& pool_end_address,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes pd pool level option.
+ ///
+ /// @param server_selector Server selector.
+ /// @param pd_pool_prefix Address part of the prefix of the pd pool
+ /// to which the deleted option belongs.
+ /// @param pd_pool_prefix_length Prefix length of the pd pool to which
+ /// the deleted option belongs.
+ /// @param code Code of the deleted option.
+ /// @param space Option space of the deleted option.
+ /// @return Number of deleted options.
+ virtual uint64_t
+ deleteOption6(const db::ServerSelector& server_selector,
+ const asiolink::IOAddress& pd_pool_prefix,
+ const uint8_t pd_pool_prefix_length,
+ const uint16_t code,
+ const std::string& space);
+
+ /// @brief Deletes global parameter.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the global parameter to be deleted.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteGlobalParameter6(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all global parameters.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted global parameters.
+ virtual uint64_t
+ deleteAllGlobalParameters6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes DHCPv6 client class.
+ ///
+ /// @param server_selector Server selector.
+ /// @param name Name of the class to be deleted.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteClientClass6(const db::ServerSelector& server_selector,
+ const std::string& name);
+
+ /// @brief Deletes all client classes.
+ ///
+ /// @param server_selector Server selector.
+ /// @return Number of deleted client classes.
+ virtual uint64_t
+ deleteAllClientClasses6(const db::ServerSelector& server_selector);
+
+ /// @brief Deletes a server from the backend.
+ ///
+ /// @param server_tag Tag of the server to be deleted.
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteServer6(const data::ServerTag& server_tag);
+
+ /// @brief Deletes all servers from the backend except the logical
+ /// server 'all'.
+ ///
+ /// @return Number of deleted servers.
+ virtual uint64_t
+ deleteAllServers6();
+
+/// @{
+/// @brief Containers used to house the "database" entries
+ Subnet6Collection subnets_;
+ SharedNetwork6Collection shared_networks_;
+ OptionDefContainer option_defs_;
+ OptionContainer options_;
+ data::StampedValueCollection globals_;
+ std::vector<ClientClassDefPtr> classes_;
+ db::ServerCollection servers_;
+/// @}
+};
+
+/// @brief Shared pointer to the @c TestConfigBackend.
+typedef boost::shared_ptr<TestConfigBackendDHCPv6> TestConfigBackendDHCPv6Ptr;
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // TEST_CONFIG_BACKEND_DHCP6
diff --git a/src/lib/dhcpsrv/testutils/test_utils.cc b/src/lib/dhcpsrv/testutils/test_utils.cc
new file mode 100644
index 0000000..2608698
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_utils.cc
@@ -0,0 +1,155 @@
+// Copyright (C) 2012-2023 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 "test_utils.h"
+#include <asiolink/io_address.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ ASSERT_TRUE(first);
+ ASSERT_TRUE(second);
+ EXPECT_EQ(first->addr_, second->addr_);
+
+ // We need to compare the actual HWAddr objects, not pointers
+ EXPECT_TRUE(*first->hwaddr_ == *second->hwaddr_);
+
+ if (first->client_id_ && second->client_id_) {
+ EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ } else {
+ if (first->client_id_ && !second->client_id_) {
+
+ ADD_FAILURE() << "Client-id present in first lease ("
+ << first->client_id_->getClientId().size()
+ << " bytes), but missing in second.";
+ }
+ if (!first->client_id_ && second->client_id_) {
+ ADD_FAILURE() << "Client-id missing in first lease, but present in second ("
+ << second->client_id_->getClientId().size()
+ << " bytes).";
+ }
+ // else here would mean that both leases do not have client_id_
+ // which makes them equal in that regard. It is ok.
+ }
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+ EXPECT_EQ(first->pool_id_, second->pool_id_);
+ EXPECT_EQ(first->fqdn_fwd_, second->fqdn_fwd_);
+ EXPECT_EQ(first->fqdn_rev_, second->fqdn_rev_);
+ EXPECT_EQ(first->hostname_, second->hostname_);
+ if (first->getContext()) {
+ EXPECT_TRUE(second->getContext());
+ if (second->getContext()) {
+ EXPECT_EQ(first->getContext()->str(), second->getContext()->str());
+ }
+ } else {
+ EXPECT_FALSE(second->getContext());
+ }
+}
+
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
+ ASSERT_TRUE(first);
+ ASSERT_TRUE(second);
+ EXPECT_EQ(first->type_, second->type_);
+
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ EXPECT_EQ(first->addr_, second->addr_);
+ EXPECT_EQ(first->prefixlen_, second->prefixlen_);
+ EXPECT_EQ(first->iaid_, second->iaid_);
+ ASSERT_TRUE(first->duid_);
+ ASSERT_TRUE(second->duid_);
+ EXPECT_TRUE(*first->duid_ == *second->duid_);
+ EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+ EXPECT_EQ(first->pool_id_, second->pool_id_);
+ EXPECT_EQ(first->fqdn_fwd_, second->fqdn_fwd_);
+ EXPECT_EQ(first->fqdn_rev_, second->fqdn_rev_);
+ EXPECT_EQ(first->hostname_, second->hostname_);
+ if (first->getContext()) {
+ EXPECT_TRUE(second->getContext());
+ if (second->getContext()) {
+ EXPECT_EQ(first->getContext()->str(), second->getContext()->str());
+ }
+ } else {
+ EXPECT_FALSE(second->getContext());
+ }
+}
+
+int findLastSocketFd() {
+ int max_fd_number = getdtablesize();
+ int last_socket = -1;
+ struct stat stats;
+
+ // Iterate over the open fds
+ for (int fd = 0; fd <= max_fd_number; fd++ ) {
+ errno = 0;
+ fstat(fd, &stats);
+
+ if (errno == EBADF ) {
+ // Skip any that aren't open
+ continue;
+ }
+
+ // it's a socket, remember it
+ if (S_ISSOCK(stats.st_mode)) {
+ last_socket = fd;
+ }
+ }
+
+ return (last_socket);
+}
+
+FillFdHoles::FillFdHoles(int limit) : fds_() {
+ if (limit <= 0) {
+ return;
+ }
+ for (;;) {
+ int fd = open("/dev/null", O_RDWR, 0);
+ if (fd == -1) {
+ return;
+ }
+ if (fd < limit) {
+ fds_.push_front(fd);
+ } else {
+ static_cast<void>(close(fd));
+ return;
+ }
+ }
+}
+
+FillFdHoles::~FillFdHoles() {
+ while (!fds_.empty()) {
+ static_cast<void>(close(fds_.back()));
+ fds_.pop_back();
+ }
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/testutils/test_utils.h b/src/lib/dhcpsrv/testutils/test_utils.h
new file mode 100644
index 0000000..02af9ba
--- /dev/null
+++ b/src/lib/dhcpsrv/testutils/test_utils.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2012-2020 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 LIBDHCPSRV_TEST_UTILS_H
+#define LIBDHCPSRV_TEST_UTILS_H
+
+#include <dhcpsrv/lease_mgr.h>
+#include <list>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// @brief performs details comparison between two IPv6 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second);
+
+// @brief performs details comparison between two IPv4 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second);
+
+/// @brief Function that finds the last open socket descriptor
+///
+/// This function is used to attempt lost connectivity
+/// with backends, notably MySQL and Postgresql.
+///
+/// The theory being, that in a confined test environment the last
+/// such descriptor is the SQL client socket descriptor. This allows
+/// us to the close that descriptor and simulate a loss of server
+/// connectivity.
+///
+/// @return the descriptor of the last open socket or -1 if there
+/// are none.
+int findLastSocketFd();
+
+/// @brief RAII tool which fills holes in the file descriptor sequence
+///
+/// The @ref findLastSocketFd requires new socket descriptors are allocated
+/// after the last open socket descriptor so there is no hole i.e. a free
+/// file descriptor in the sequence.
+/// This tool detects and fills such holes. It uses the RAII idiom to avoid
+/// file descriptor leaks: the destructor called when the object goes out
+/// of scope closes all file descriptors which were opened by the constructor.
+class FillFdHoles {
+public:
+ /// @brief Constructor
+ ///
+ /// Holes between 0 and the specified limit will be filled by opening
+ /// the null device. Typically the limit argument is the result of
+ /// a previous call to @ref findLastSocketFd. Note if the limit is
+ /// 0 or negative the constructor returns doing nothing.
+ ///
+ /// @param limit Holes will be filled up to this limit
+ FillFdHoles(int limit);
+
+ /// @brief Destructor
+ ///
+ /// The destructor closes members of the list
+ ~FillFdHoles();
+
+private:
+ /// @brief The list of holes
+ std::list<int> fds_;
+};
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcpsrv/timer_mgr.cc b/src/lib/dhcpsrv/timer_mgr.cc
new file mode 100644
index 0000000..0f3b68f
--- /dev/null
+++ b/src/lib/dhcpsrv/timer_mgr.cc
@@ -0,0 +1,531 @@
+// Copyright (C) 2016-2021 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 <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <exceptions/exceptions.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <exception>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include <stddef.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Structure holding information for a single timer.
+///
+/// This structure holds the instance of the watch socket being used to
+/// signal that the timer is "ready". It also holds the instance of the
+/// interval timer and other parameters pertaining to it.
+struct TimerInfo {
+ /// @brief Instance of the interval timer.
+ asiolink::IntervalTimer interval_timer_;
+
+ /// @brief Holds the pointer to the callback supplied when registering
+ /// the timer.
+ asiolink::IntervalTimer::Callback user_callback_;
+
+ /// @brief Interval timer interval supplied during registration.
+ long interval_;
+
+ /// @brief Interval timer scheduling mode supplied during registration.
+ asiolink::IntervalTimer::Mode scheduling_mode_;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service to be used by the
+ /// interval timer created.
+ /// @param user_callback Pointer to the callback function supplied
+ /// during the timer registration.
+ /// @param interval Timer interval in milliseconds.
+ /// @param mode Interval timer scheduling mode.
+ TimerInfo(asiolink::IOService& io_service,
+ const asiolink::IntervalTimer::Callback& user_callback,
+ const long interval,
+ const asiolink::IntervalTimer::Mode& mode)
+ : interval_timer_(io_service),
+ user_callback_(user_callback),
+ interval_(interval),
+ scheduling_mode_(mode) { };
+};
+
+}
+
+namespace isc {
+namespace dhcp {
+
+/// @brief A type definition for the pointer to @c TimerInfo structure.
+typedef boost::shared_ptr<TimerInfo> TimerInfoPtr;
+
+/// @brief A type definition for the map holding timers configuration.
+typedef std::map<std::string, TimerInfoPtr> TimerInfoMap;
+
+/// @brief Implementation of the @c TimerMgr
+class TimerMgrImpl {
+public:
+
+ /// @brief Constructor.
+ TimerMgrImpl();
+
+ /// @brief Sets IO service to be used by the Timer Manager.
+ ///
+ /// @param io_service Pointer to the new IO service.
+ void setIOService(const IOServicePtr& io_service);
+
+ /// @brief Registers new timer in the @c TimerMgr.
+ ///
+ /// @param timer_name Unique name for the timer.
+ /// @param callback Pointer to the callback function to be invoked
+ /// when the timer elapses, e.g. function processing expired leases
+ /// in the DHCP server.
+ /// @param interval Timer interval in milliseconds.
+ /// @param scheduling_mode Scheduling mode of the timer as described in
+ /// @c asiolink::IntervalTimer::Mode.
+ ///
+ /// @throw BadValue if the timer name is invalid or duplicate.
+ void registerTimer(const std::string& timer_name,
+ const asiolink::IntervalTimer::Callback& callback,
+ const long interval,
+ const asiolink::IntervalTimer::Mode& scheduling_mode);
+
+
+ /// @brief Unregisters specified timer.
+ ///
+ /// This method cancels the timer if it is setup and removes the timer
+ /// from the internal collection of timers.
+ ///
+ /// @param timer_name Name of the timer to be unregistered.
+ ///
+ /// @throw BadValue if the specified timer hasn't been registered.
+ void unregisterTimer(const std::string& timer_name);
+
+ /// @brief Unregisters all timers.
+ ///
+ /// This method must be explicitly called prior to termination of the
+ /// process.
+ void unregisterTimers();
+
+ /// @brief Checks if the timer with a specified name has been registered.
+ ///
+ /// @param timer_name Name of the timer.
+ /// @return true if the timer with the specified name has been registered,
+ /// false otherwise.
+ bool isTimerRegistered(const std::string& timer_name);
+
+ /// @brief Returns the number of registered timers.
+ size_t timersCount() const;
+
+ /// @brief Schedules the execution of the interval timer.
+ ///
+ /// This method schedules the timer, i.e. the callback will be executed
+ /// after specified interval elapses. The interval has been specified
+ /// during timer registration. Depending on the mode selected during the
+ /// timer registration, the callback will be executed once after it has
+ /// been scheduled or until it is cancelled. Though, in the former case
+ /// the timer can be re-scheduled in the callback function.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void setup(const std::string& timer_name);
+
+ /// @brief Cancels the execution of the interval timer.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void cancel(const std::string& timer_name);
+
+private:
+
+ /// @name Internal methods called while holding the mutex in multi threading
+ /// mode.
+
+ /// @brief Registers new timer in the @c TimerMgr.
+ ///
+ /// @param timer_name Unique name for the timer.
+ /// @param callback Pointer to the callback function to be invoked
+ /// when the timer elapses, e.g. function processing expired leases
+ /// in the DHCP server.
+ /// @param interval Timer interval in milliseconds.
+ /// @param scheduling_mode Scheduling mode of the timer as described in
+ /// @c asiolink::IntervalTimer::Mode.
+ ///
+ /// @throw BadValue if the timer name is invalid or duplicate.
+ void registerTimerInternal(const std::string& timer_name,
+ const asiolink::IntervalTimer::Callback& callback,
+ const long interval,
+ const asiolink::IntervalTimer::Mode& scheduling_mode);
+
+
+ /// @brief Unregisters specified timer.
+ ///
+ /// This method cancels the timer if it is setup and removes the timer
+ /// from the internal collection of timers.
+ ///
+ /// @param timer_name Name of the timer to be unregistered.
+ ///
+ /// @throw BadValue if the specified timer hasn't been registered.
+ void unregisterTimerInternal(const std::string& timer_name);
+
+ /// @brief Unregisters all timers.
+ ///
+ /// This method must be explicitly called prior to termination of the
+ /// process.
+ void unregisterTimersInternal();
+
+ /// @brief Schedules the execution of the interval timer.
+ ///
+ /// This method schedules the timer, i.e. the callback will be executed
+ /// after specified interval elapses. The interval has been specified
+ /// during timer registration. Depending on the mode selected during the
+ /// timer registration, the callback will be executed once after it has
+ /// been scheduled or until it is cancelled. Though, in the former case
+ /// the timer can be re-scheduled in the callback function.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void setupInternal(const std::string& timer_name);
+
+ /// @brief Cancels the execution of the interval timer.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void cancelInternal(const std::string& timer_name);
+
+ /// @brief Callback function to be executed for each interval timer when
+ /// its scheduled interval elapses.
+ ///
+ /// @param timer_name Unique timer name.
+ void timerCallback(const std::string& timer_name);
+
+ /// @brief Pointer to the io service.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Holds mapping of the timer name to timer instance and other
+ /// parameters pertaining to the timer.
+ TimerInfoMap registered_timers_;
+
+ /// @brief The mutex to protect the timer manager.
+ boost::scoped_ptr<std::mutex> mutex_;
+};
+
+TimerMgrImpl::TimerMgrImpl() : io_service_(new IOService()),
+ registered_timers_(), mutex_(new std::mutex) {
+}
+
+void
+TimerMgrImpl::setIOService(const IOServicePtr& io_service) {
+ if (!io_service) {
+ isc_throw(BadValue, "IO service object must not be null for TimerMgr");
+ }
+
+ io_service_ = io_service;
+}
+
+void
+TimerMgrImpl::registerTimer(const std::string& timer_name,
+ const IntervalTimer::Callback& callback,
+ const long interval,
+ const IntervalTimer::Mode& scheduling_mode) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ registerTimerInternal(timer_name, callback, interval, scheduling_mode);
+ } else {
+ registerTimerInternal(timer_name, callback, interval, scheduling_mode);
+ }
+}
+
+void
+TimerMgrImpl::registerTimerInternal(const std::string& timer_name,
+ const IntervalTimer::Callback& callback,
+ const long interval,
+ const IntervalTimer::Mode& scheduling_mode) {
+ // Timer name must not be empty.
+ if (timer_name.empty()) {
+ isc_throw(BadValue, "registered timer name must not be empty");
+ }
+
+ // Must not register two timers under the same name.
+ if (registered_timers_.find(timer_name) != registered_timers_.end()) {
+ isc_throw(BadValue, "trying to register duplicate timer '"
+ << timer_name << "'");
+ }
+
+ // Create a structure holding the configuration for the timer. It will
+ // create the instance if the IntervalTimer. It will also hold the
+ // callback, interval and scheduling mode parameters.
+ TimerInfoPtr timer_info(new TimerInfo(*io_service_, callback,
+ interval, scheduling_mode));
+
+ // Actually register the timer.
+ registered_timers_.insert(std::pair<std::string, TimerInfoPtr>(timer_name,
+ timer_info));
+}
+
+void
+TimerMgrImpl::unregisterTimer(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ unregisterTimerInternal(timer_name);
+ } else {
+ unregisterTimerInternal(timer_name);
+ }
+}
+
+void
+TimerMgrImpl::unregisterTimerInternal(const std::string& timer_name) {
+ // Find the timer with specified name.
+ TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name);
+
+ // Check if the timer has been registered.
+ if (timer_info_it == registered_timers_.end()) {
+ isc_throw(BadValue, "unable to unregister non existing timer '"
+ << timer_name << "'");
+ }
+
+ // Cancel any pending asynchronous operation and stop the timer.
+ cancelInternal(timer_name);
+
+ // Remove the timer.
+ registered_timers_.erase(timer_info_it);
+}
+
+void
+TimerMgrImpl::unregisterTimers() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ unregisterTimersInternal();
+ } else {
+ unregisterTimersInternal();
+ }
+}
+
+void
+TimerMgrImpl::unregisterTimersInternal() {
+ // Copy the map holding timers configuration. This is required so as
+ // we don't cut the branch which we're sitting on when we will be
+ // erasing the timers. We're going to iterate over the register timers
+ // and remove them with the call to unregisterTimer function. But this
+ // function will remove them from the register_timers_ map. If we
+ // didn't work on the copy here, our iterator would invalidate. The
+ // TimerInfo structure is copyable and since it is using the shared
+ // pointers the copy is not expensive. Also this function is called when
+ // the process terminates so it is not critical for performance.
+ TimerInfoMap registered_timers_copy(registered_timers_);
+
+ // Iterate over the existing timers and unregister them.
+ for (TimerInfoMap::iterator timer_info_it = registered_timers_copy.begin();
+ timer_info_it != registered_timers_copy.end(); ++timer_info_it) {
+ unregisterTimerInternal(timer_info_it->first);
+ }
+}
+
+bool
+TimerMgrImpl::isTimerRegistered(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (registered_timers_.find(timer_name) != registered_timers_.end());
+ } else {
+ return (registered_timers_.find(timer_name) != registered_timers_.end());
+ }
+}
+
+size_t
+TimerMgrImpl::timersCount() const {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (registered_timers_.size());
+ } else {
+ return (registered_timers_.size());
+ }
+}
+
+void
+TimerMgrImpl::setup(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ setupInternal(timer_name);
+ } else {
+ setupInternal(timer_name);
+ }
+}
+
+void
+TimerMgrImpl::setupInternal(const std::string& timer_name) {
+ // Check if the specified timer exists.
+ TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name);
+ if (timer_info_it == registered_timers_.end()) {
+ isc_throw(BadValue, "unable to setup timer '" << timer_name << "': "
+ "no such timer registered");
+ }
+
+ // Schedule the execution of the timer using the parameters supplied
+ // during the registration.
+ const TimerInfoPtr& timer_info = timer_info_it->second;
+ IntervalTimer::Callback cb = std::bind(&TimerMgrImpl::timerCallback, this,
+ timer_name);
+ timer_info->interval_timer_.setup(cb, timer_info->interval_,
+ timer_info->scheduling_mode_);
+}
+
+void
+TimerMgrImpl::cancel(const std::string& timer_name) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ cancelInternal(timer_name);
+ } else {
+ cancelInternal(timer_name);
+ }
+}
+
+void
+TimerMgrImpl::cancelInternal(const std::string& timer_name) {
+ // Find the timer of our interest.
+ TimerInfoMap::const_iterator timer_info_it = registered_timers_.find(timer_name);
+ if (timer_info_it == registered_timers_.end()) {
+ isc_throw(BadValue, "unable to cancel timer '" << timer_name << "': "
+ "no such timer registered");
+ }
+ // Cancel the timer.
+ timer_info_it->second->interval_timer_.cancel();
+}
+
+void
+TimerMgrImpl::timerCallback(const std::string& timer_name) {
+ // Find the specified timer setup.
+ TimerInfoMap::iterator timer_info_it = registered_timers_.find(timer_name);
+ if (timer_info_it != registered_timers_.end()) {
+
+ // Running user-defined operation for the timer. Logging it
+ // on the slightly lower debug level as there may be many
+ // such traces.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_TIMERMGR_RUN_TIMER_OPERATION)
+ .arg(timer_info_it->first);
+
+ std::string error_string;
+ try {
+ timer_info_it->second->user_callback_();
+
+ } catch (const std::exception& ex){
+ error_string = ex.what();
+
+ } catch (...) {
+ error_string = "unknown reason";
+ }
+
+ // Exception was thrown. Log an error.
+ if (!error_string.empty()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_TIMERMGR_CALLBACK_FAILED)
+ .arg(timer_info_it->first)
+ .arg(error_string);
+ }
+ }
+}
+
+const TimerMgrPtr&
+TimerMgr::instance() {
+ static TimerMgrPtr timer_mgr(new TimerMgr());
+ return (timer_mgr);
+}
+
+TimerMgr::TimerMgr()
+ : impl_(new TimerMgrImpl()) {
+}
+
+TimerMgr::~TimerMgr() {
+ impl_->unregisterTimers();
+}
+
+void
+TimerMgr::registerTimer(const std::string& timer_name,
+ const IntervalTimer::Callback& callback,
+ const long interval,
+ const IntervalTimer::Mode& scheduling_mode) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_REGISTER_TIMER)
+ .arg(timer_name)
+ .arg(interval);
+
+ impl_->registerTimer(timer_name, callback, interval, scheduling_mode);
+}
+
+void
+TimerMgr::unregisterTimer(const std::string& timer_name) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_UNREGISTER_TIMER)
+ .arg(timer_name);
+
+ impl_->unregisterTimer(timer_name);
+}
+
+void
+TimerMgr::unregisterTimers() {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_UNREGISTER_ALL_TIMERS);
+
+ impl_->unregisterTimers();
+}
+
+bool
+TimerMgr::isTimerRegistered(const std::string& timer_name) {
+ return (impl_->isTimerRegistered(timer_name));
+}
+
+size_t
+TimerMgr::timersCount() const {
+ return (impl_->timersCount());
+}
+
+void
+TimerMgr::setup(const std::string& timer_name) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_START_TIMER)
+ .arg(timer_name);
+
+ impl_->setup(timer_name);
+}
+
+void
+TimerMgr::cancel(const std::string& timer_name) {
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_TIMERMGR_STOP_TIMER)
+ .arg(timer_name);
+
+ impl_->cancel(timer_name);
+}
+
+void
+TimerMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->setIOService(io_service);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/timer_mgr.h b/src/lib/dhcpsrv/timer_mgr.h
new file mode 100644
index 0000000..f0e014b
--- /dev/null
+++ b/src/lib/dhcpsrv/timer_mgr.h
@@ -0,0 +1,163 @@
+// Copyright (C) 2015-2020 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 TIMER_MGR_H
+#define TIMER_MGR_H
+
+#include <asiolink/interval_timer.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Forward declaration of the @c TimerMgr implementation.
+class TimerMgrImpl;
+
+typedef boost::shared_ptr<TimerMgrImpl> TimerMgrImplPtr;
+
+/// @brief Forward declaration of the @c TimerMgr.
+class TimerMgr;
+
+/// @brief Type definition of the shared pointer to @c TimerMgr.
+typedef boost::shared_ptr<TimerMgr> TimerMgrPtr;
+
+/// @brief Manages a pool of asynchronous interval timers.
+///
+/// This class holds a pool of asynchronous interval timers.
+///
+/// This class is useful for performing periodic actions at the specified
+/// intervals, e.g. act upon expired leases (leases reclamation) or
+/// return declined leases back to the address pool. Other applications
+/// may be added in the future.
+///
+/// The @c TimerMgr is a singleton, thus its instance is available from
+/// different places in the server code. This is convenient because timers
+/// can be installed by different configuration parsers or they can be
+/// re-scheduled from the callback functions.
+///
+/// The timer is registered using the @c TimerMgr::registerTimer method.
+/// Each registered timer has a unique name. It is not possible to register
+/// multiple timers with the same name. Each registered timer is associated
+/// with the callback function supplied by the caller. This callback function
+/// performs the tasks to be executed periodically according to the timer's
+/// interval.
+///
+/// The registered timer's interval does not begin to elapse until the
+/// @c TimerMgr::setup method is called for it.
+///
+/// Before the @c TimerMgr can be used the server process must call
+/// @c TimerMgr::setIOService to associate the manager with the IO service
+/// that the server is using to its run tasks.
+///
+/// @note Only scheduling new timer (calling @ref setup) and canceling existing
+/// timer (calling @ref cancel) are thread safe.
+/// Registering new timers (calling @ref registerTimer) and unregistering
+/// existing timers (calling @ref unregisterTimer) must be handled before
+/// starting processing threads.
+class TimerMgr : public boost::noncopyable {
+public:
+
+ /// @brief Returns pointer to the sole instance of the @c TimerMgr.
+ static const TimerMgrPtr& instance();
+
+ /// @brief Destructor.
+ ///
+ /// Stops the worker thread if it is running and unregisters any
+ /// registered timers.
+ ~TimerMgr();
+
+ /// @brief Sets IO service to be used by the Timer Manager.
+ ///
+ /// @param io_service Pointer to the new IO service.
+ void setIOService(const asiolink::IOServicePtr& io_service);
+
+ /// @name Registering, unregistering and scheduling the timers.
+ //@{
+
+ /// @brief Registers new timer in the @c TimerMgr.
+ ///
+ /// @param timer_name Unique name for the timer.
+ /// @param callback Pointer to the callback function to be invoked
+ /// when the timer elapses, e.g. function processing expired leases
+ /// in the DHCP server.
+ /// @param interval Timer interval in milliseconds.
+ /// @param scheduling_mode Scheduling mode of the timer as described in
+ /// @c asiolink::IntervalTimer::Mode.
+ ///
+ /// @throw BadValue if the timer name is invalid or duplicate.
+ void registerTimer(const std::string& timer_name,
+ const asiolink::IntervalTimer::Callback& callback,
+ const long interval,
+ const asiolink::IntervalTimer::Mode& scheduling_mode);
+
+ /// @brief Unregisters specified timer.
+ ///
+ /// This method cancels the timer if it is setup and removes it from the
+ /// internal collection of timers.
+ ///
+ /// @param timer_name Name of the timer to be unregistered.
+ ///
+ /// @throw BadValue if the specified timer hasn't been registered.
+ void unregisterTimer(const std::string& timer_name);
+
+ /// @brief Unregisters all timers.
+ void unregisterTimers();
+
+ /// @brief Checks if the timer with a specified name has been registered.
+ ///
+ /// @param timer_name Name of the timer.
+ /// @return true if the timer with the specified name has been registered,
+ /// false otherwise.
+ bool isTimerRegistered(const std::string& timer_name);
+
+ /// @brief Returns the number of registered timers.
+ size_t timersCount() const;
+
+ /// @brief Schedules the execution of the interval timer.
+ ///
+ /// This method schedules the timer, i.e. the callback will be executed
+ /// after specified interval elapses. The interval has been specified
+ /// during timer registration. Depending on the mode selected during the
+ /// timer registration, the callback will be executed once after it has
+ /// been scheduled or until it is cancelled. Though, in the former case
+ /// the timer can be re-scheduled in the callback function.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void setup(const std::string& timer_name);
+
+ /// @brief Cancels the execution of the interval timer.
+ ///
+ /// This method has no effect if the timer hasn't been scheduled with
+ /// the @c TimerMgr::setup method.
+ ///
+ /// @param timer_name Unique timer name.
+ ///
+ /// @throw BadValue if the timer hasn't been registered.
+ void cancel(const std::string& timer_name);
+
+ //@}
+
+private:
+
+ /// @brief Private default constructor.
+ ///
+ /// The @c TimerMgr is a singleton class which instance must be created
+ /// using the @c TimerMgr::instance method. Private constructor enforces
+ /// construction via @c TimerMgr::instance.
+ TimerMgr();
+
+ /// @brief The @c TimerMgr implementation.
+ TimerMgrImplPtr impl_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // TIMER_MGR_H
diff --git a/src/lib/dhcpsrv/tracking_lease_mgr.cc b/src/lib/dhcpsrv/tracking_lease_mgr.cc
new file mode 100644
index 0000000..35c8192
--- /dev/null
+++ b/src/lib/dhcpsrv/tracking_lease_mgr.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2023 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 <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/tracking_lease_mgr.h>
+#include <util/multi_threading_mgr.h>
+#include <boost/tuple/tuple.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+TrackingLeaseMgr::TrackingLeaseMgr()
+ : LeaseMgr(), callbacks_(new TrackingLeaseMgr::CallbackContainer()) {
+}
+
+bool
+TrackingLeaseMgr::tryLock(const LeasePtr& lease) {
+ // Try inserting a lease. If such a lease already exists in the set, return false.
+ auto result = locked_leases_.insert(lease->addr_);
+ return (result.second);
+}
+
+void
+TrackingLeaseMgr::unlock(const LeasePtr& lease) {
+ // Remove the locked lease from the set.
+ locked_leases_.erase(lease->addr_);
+}
+
+bool
+TrackingLeaseMgr::isLocked(const LeasePtr& lease) {
+ return (locked_leases_.find(lease->addr_) != locked_leases_.end());
+}
+
+void
+TrackingLeaseMgr::trackAddLease(const LeasePtr& lease) {
+ runCallbacks(TRACK_ADD_LEASE, lease);
+}
+
+void
+TrackingLeaseMgr::trackUpdateLease(const LeasePtr& lease) {
+ runCallbacks(TRACK_UPDATE_LEASE, lease);
+}
+
+void
+TrackingLeaseMgr::trackDeleteLease(const LeasePtr& lease) {
+ runCallbacks(TRACK_DELETE_LEASE, lease);
+}
+
+void
+TrackingLeaseMgr::registerCallback(TrackingLeaseMgr::CallbackType type,
+ std::string owner,
+ SubnetID subnet_id,
+ Lease::Type lease_type,
+ TrackingLeaseMgr::CallbackFn callback_fn) {
+ // The first index filters the callbacks by type and subnet_id.
+ auto& idx = callbacks_->get<0>();
+ auto range = idx.equal_range(boost::make_tuple(type, subnet_id, lease_type));
+ if (range.first != range.second) {
+ // Make sure that the callback for this owner does not exist.
+ if (std::find_if(range.first, range.second,
+ [&owner] (const Callback& cb) -> bool {
+ return (cb.owner == owner);
+ }) != range.second) {
+ isc_throw(InvalidOperation, "the callback owned by the " << owner
+ << ", for subnet ID " << subnet_id
+ << ", and lease type " << Lease::typeToText(lease_type)
+ << " has already been registered in the lease manager");
+ }
+ }
+ TrackingLeaseMgr::Callback callback{type, owner, subnet_id, lease_type, callback_fn};
+ callbacks_->insert(callback);
+}
+
+void
+TrackingLeaseMgr::registerCallback(TrackingLeaseMgr::CallbackType type,
+ std::string owner,
+ Lease::Type lease_type,
+ TrackingLeaseMgr::CallbackFn callback_fn) {
+ registerCallback(type, owner, SUBNET_ID_GLOBAL, lease_type, callback_fn);
+}
+
+void
+TrackingLeaseMgr::unregisterCallbacks(SubnetID subnet_id, Lease::Type lease_type) {
+ // The second index filters the callbacks by the subnet identifier and
+ // the lease type.
+ auto& idx = callbacks_->get<1>();
+ auto range = idx.equal_range(boost::make_tuple(subnet_id, lease_type));
+ if (range.first != range.second) {
+ idx.erase(range.first, range.second);
+ }
+}
+
+void
+TrackingLeaseMgr::unregisterAllCallbacks() {
+ callbacks_->clear();
+}
+
+bool
+TrackingLeaseMgr::hasCallbacks() const {
+ return (!callbacks_->empty());
+}
+
+std::string
+TrackingLeaseMgr::callbackTypeToString(CallbackType type) {
+ switch (type) {
+ case TrackingLeaseMgr::TRACK_ADD_LEASE:
+ return ("add_lease");
+ case TrackingLeaseMgr::TRACK_UPDATE_LEASE:
+ return ("update_lease");
+ case TrackingLeaseMgr::TRACK_DELETE_LEASE:
+ return ("delete_lease");
+ default:
+ return ("unknown");
+ }
+}
+
+void
+TrackingLeaseMgr::runCallbacks(TrackingLeaseMgr::CallbackType type,
+ const LeasePtr& lease) {
+ runCallbacksForSubnetID(type, SUBNET_ID_GLOBAL, lease);
+ runCallbacksForSubnetID(type, lease->subnet_id_, lease);
+}
+
+void
+TrackingLeaseMgr::runCallbacksForSubnetID(CallbackType type, SubnetID subnet_id,
+ const LeasePtr& lease) {
+ // The first index filters by callback type and subnet_id.
+ auto& idx = callbacks_->get<0>();
+ auto cbs = idx.equal_range(boost::make_tuple(type, subnet_id, lease->getType()));
+ if (cbs.first == cbs.second) {
+ return;
+ }
+ for (auto it = cbs.first; it != cbs.second; ++it) {
+ auto cb = *it;
+ try {
+ cb.fn(lease);
+ } catch (const std::exception& ex) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_LEASE_MGR_CALLBACK_EXCEPTION)
+ .arg(callbackTypeToString(type))
+ .arg(subnet_id)
+ .arg(lease->addr_.toText())
+ .arg(ex.what());
+ } catch (...) {
+ LOG_WARN(dhcpsrv_logger, DHCPSRV_LEASE_MGR_CALLBACK_UNKNOWN_EXCEPTION)
+ .arg(callbackTypeToString(type))
+ .arg(subnet_id)
+ .arg(lease->addr_.toText());
+ }
+ }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/dhcpsrv/tracking_lease_mgr.h b/src/lib/dhcpsrv/tracking_lease_mgr.h
new file mode 100644
index 0000000..fe21177
--- /dev/null
+++ b/src/lib/dhcpsrv/tracking_lease_mgr.h
@@ -0,0 +1,308 @@
+// Copyright (C) 2023 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 TRACKING_LEASE_MGR_H
+#define TRACKING_LEASE_MGR_H
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/member.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+#include <unordered_set>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Introduces callbacks into the @c LeaseMgr.
+///
+/// The LeaseMgr is a central point of lease management and is aware of all
+/// lease changes within the server instance. Thus, it is a good opportunity
+/// to allow installing callbacks in the @c LeaseMgr to track all lease
+/// changes. An allocator maintaining a list of free leases (FLQ allocator)
+/// can benefit from it by/ installing the callbacks that add or remove
+/// free leases from this list, depending on the lease manager's activity.
+/// The callbacks are invoked regardless if the lease changes result from a
+/// normal lease allocation or a control command. The callbacks can also be
+/// useful for maintaining a log of lease changes. Such a log could be made
+/// available externally and consumed by another application (e.g., Stork).
+///
+/// The lease manager can track three types of calls: new lease insertion
+/// (add), an existing lease update, and lease deletion. Even though the
+/// lease reclamation is similar to deleting a lease because it becomes free,
+/// it is a lease update from the lease manager's standpoint. Currently,
+/// the lease manager cannot track the deletion of the reclaimed leases
+/// (i.e., the leases in the expired-reclaimed state).
+///
+/// The lease backends should call the suitable tracking functions:
+/// @c trackAddLease, @c trackUpdateLease, and @c trackDeleteLease.
+/// However, the backends must ensure that there are no race conditions
+/// between modifying the lease in the database and running the callbacks.
+/// Suppose that two threads modify the same lease. Thread A inserts the
+/// lease in the database, and thread B removes it. The callback for
+/// thread A should be invoked before the callback for thread B. If they
+/// are invoked in reverse order, it can result in an inconsistent state
+/// in the free lease queue allocator because the allocator should record
+/// the lease as available after the thread B callback. The reverse
+/// invocation order would result in marking the lease as unavailable for
+/// allocation after both callbacks.
+///
+/// The race condition does not occur for the Memfile backend because it
+/// guards entire functions with a mutex. However, the SQL backends rely
+/// on the database to guard against concurrent writes. In these cases,
+/// the backend must protect against the reverse order of callbacks. They
+/// should use the lease locking mechanism introduced in the
+/// @c TrackingLeaseMgr.
+///
+/// The lease locking is modeled on an @c unordered_set container holding
+/// the leases with the ongoing allocations. The leases are inserted into
+/// this container by the @c tryLock function. If another thread has already
+/// locked the lease, this function returns @c false to indicate an
+/// unsuccessful attempt. In this case, the thread should resign from updating
+/// the lease and return early. It can result in a lease allocation failure,
+/// but two concurrent threads extremely rarely work on allocating a lease for
+/// the same client. A passive wait could be another option here, but it is a
+/// much more complicated solution for a bit of gain.
+class TrackingLeaseMgr : public LeaseMgr {
+public:
+
+ /// The @c LeaseMgrFactory manages the @c LeaseMgr instances and has
+ /// to be able to move installed callbacks between them. No other external
+ /// class can have access to the callbacks container. Thus, we can't make
+ /// the container public. The friend declaration deals with it cleanly.
+ friend class LeaseMgrFactory;
+
+ /// @brief An enumeration differentiating between lease write operations.
+ typedef enum {
+ TRACK_ADD_LEASE,
+ TRACK_UPDATE_LEASE,
+ TRACK_DELETE_LEASE
+ } CallbackType;
+
+ /// @brief Type of a callback function invoked upon a lease insertion,
+ /// update or deletion.
+ ///
+ /// The first argument is a pointer to the lease for which the callback
+ /// is invoked.
+ typedef std::function<void(LeasePtr)> CallbackFn;
+
+ /// @brief A structure representing a registered callback.
+ ///
+ /// It associates the callback with a type, its owner, subnet
+ /// identifier, and a lease type. The owner is a string specified
+ /// by the registration function caller. There must be at most one
+ /// callback registered for the particular owner, subnet identifier
+ /// and the lease type.
+ typedef struct {
+ /// @brief Callback type (i.e., lease add, update, delete).
+ CallbackType type;
+
+ /// @brief An entity owning callback registration (e.g., FLQ allocator).
+ std::string owner;
+
+ /// Subnet identifier associated with the callback.
+ SubnetID subnet_id;
+
+ /// @brief Lease types for which the callback should be invoked.
+ Lease::Type lease_type;
+
+ /// @brief Callback function.
+ CallbackFn fn;
+ } Callback;
+
+protected:
+
+ /// @brief A multi-index container holding registered callbacks.
+ ///
+ /// The callbacks are accessible via two indexes. The first composite index
+ /// filters the callbacks by the callback type (i.e., lease add, update or delete)
+ /// and the subnet id. The second index filters the callbacks by the subnet id
+ /// and the lease type.
+ typedef boost::multi_index_container<
+ Callback,
+ boost::multi_index::indexed_by<
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::composite_key<
+ Callback,
+ boost::multi_index::member<Callback, CallbackType, &Callback::type>,
+ boost::multi_index::member<Callback, SubnetID, &Callback::subnet_id>,
+ boost::multi_index::member<Callback, Lease::Type, &Callback::lease_type>
+ >
+ >,
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::composite_key<
+ Callback,
+ boost::multi_index::member<Callback, SubnetID, &Callback::subnet_id>,
+ boost::multi_index::member<Callback, Lease::Type, &Callback::lease_type>
+ >
+ >
+ >
+ > CallbackContainer;
+
+ /// @brief Pointer to the callback container.
+ typedef boost::shared_ptr<CallbackContainer> CallbackContainerPtr;
+
+ /// @brief Constructor.
+ TrackingLeaseMgr();
+
+ /// @brief Attempts to lock a lease.
+ ///
+ /// If a lease is successfully locked, no other thread can lock it. It protects
+ /// against running the callbacks out of order when two threads modify the same
+ /// lease. Such a locking should only be used when the lease allocation followed by
+ /// the callbacks invocation are not protected by some other synchronization
+ /// mechanism. In particular, the Memfile backend uses a mutex for locking in the
+ /// lease allocation functions. In this case, it is unnecessary to apply a lock at the
+ /// lease level. The SQL backends rely on the database locking mechanisms to prevent
+ /// the concurrent updates of the same lease. These backends must use the lease locking
+ /// to ensure the correct callbacks invocation order.
+ ///
+ /// This function is not thread-safe and must be invoked in a thread-safe context.
+ ///
+ /// @param lease a lease instance for which the lock should be attempted.
+ /// @return true when locking was successful, false otherwise. In the latter case,
+ /// the thread should stop a lease allocation or deallocation attempt.
+ bool tryLock(const LeasePtr& lease);
+
+ /// @brief Attempts to unlock a lease.
+ ///
+ /// This function is not thread-safe and must be invoked in a thread-safe context.
+ ///
+ /// @param lease a lease instance for which unlocking should be attempted.
+ void unlock(const LeasePtr& lease);
+
+public:
+
+ /// @brief Checks if the lease is locked.
+ ///
+ /// This function is useful in the unit tests.
+ ///
+ /// @return true if the lease is locked, false otherwise.
+ bool isLocked(const LeasePtr& lease);
+
+protected:
+
+ /// @brief Invokes the callbacks when a new lease is added.
+ ///
+ /// It executes all callbacks of the @c TRACK_ADD_LEASE type for a subnet id of 0
+ /// and the subnet id associated with the lease.
+ ///
+ /// The callbacks execution order is not guaranteed.
+ ///
+ /// @param lease new lease instance.
+ void trackAddLease(const LeasePtr& lease);
+
+ /// @brief Invokes the callbacks when a lease is updated.
+ ///
+ /// It executes all callbacks of the @c TRACK_UPDATE_LEASE type for a subnet id of 0
+ /// and the subnet id associated with the lease.
+ ///
+ /// The callbacks execution order is not guaranteed.
+ ///
+ /// @param lease updated lease instance.
+ void trackUpdateLease(const LeasePtr& lease);
+
+ /// @brief Invokes the callbacks when a lease is deleted.
+ ///
+ /// It executes all callbacks of the @c TRACK_DELETE_LEASE type for a subnet id of 0
+ /// and the subnet id associated with the lease.
+ ///
+ /// The callbacks execution order is not guaranteed.
+ ///
+ /// @param lease deleted lease instance.
+ void trackDeleteLease(const LeasePtr& lease);
+
+public:
+
+ /// @brief Registers a callback function for a subnet.
+ ///
+ /// @param type callback type.
+ /// @param owner callback owner identifier.
+ /// @param subnet_id subnet identifier; it can be set to 0 if the callback should be
+ /// called for all subnets.
+ /// @param lease_type a lease type.
+ /// @param callback_fn callback function instance.
+ /// @throw InvalidOperation when the callback has been already registered for the given owner and
+ /// the subnet identifier.
+ void registerCallback(CallbackType type, std::string owner, SubnetID subnet_id,
+ Lease::Type lease_type, CallbackFn callback_fn);
+
+ /// @brief Registers a callback function for all subnets.
+ ///
+ /// @param type callback type.
+ /// @param owner callback owner identifier.
+ /// @param lease_type a lease type.
+ /// @param callback_fn callback function instance.
+ /// @throw InvalidOperation when the callback has been already registered for the given owner and
+ /// all subnets.
+ void registerCallback(CallbackType type, std::string owner, Lease::Type lease_type,
+ CallbackFn callback_fn);
+
+ /// @brief Unregisters all callbacks for a given subnet identifier.
+ ///
+ /// @param subnet_id a subnet identifier.
+ /// @param lease_type a lease type.
+ void unregisterCallbacks(SubnetID subnet_id, Lease::Type lease_type);
+
+ /// @brief Unregisters all callbacks.
+ void unregisterAllCallbacks();
+
+ /// @brief Checks if any callbacks have been registered.
+ ///
+ /// It is a quick check to be performed by the backends whether or not
+ /// the callbacks mechanism is used.
+ ///
+ /// @return true if any callbacks have been registered.
+ bool hasCallbacks() const;
+
+protected:
+
+ /// @brief Converts callback type to string for logging purposes.
+ ///
+ /// @param type callback type.
+ /// @return callback type name or the 'unknown' string.
+ static std::string callbackTypeToString(CallbackType type);
+
+ /// @brief Runs registered callbacks of the particular type.
+ ///
+ /// The specified lease instance contains the subnet identifier used to
+ /// filter the callbacks to be invoked.
+ ///
+ /// @param type callback type.
+ /// @param lease lease instance for which the callbacks are invoked.
+ void runCallbacks(CallbackType type, const LeasePtr& lease);
+
+ /// @brief Runs registered callbacks of the particular type for a subnet id.
+ ///
+ /// It is called internally by the @c runCallbacks function.
+ ///
+ /// @param type callback type.
+ /// @param subnet_id subnet identifier for which the callbacks are invoked.
+ /// @param lease lease instance for which the callbacks are invoked.
+ void runCallbacksForSubnetID(CallbackType type, SubnetID subnet_id,
+ const LeasePtr& lease);
+
+ /// @brief The multi-index container holding registered callbacks.
+ CallbackContainerPtr callbacks_;
+
+ /// @brief A set of locked leases.
+ ///
+ /// It is empty if locking is not used (e.g. Memfile backend) or when there
+ /// are no ongoing allocations.
+ std::unordered_set<asiolink::IOAddress, asiolink::IOAddress::Hash> locked_leases_;
+};
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
+
+#endif // TRACKING_LEASE_MGR_H
diff --git a/src/lib/dhcpsrv/utils.h b/src/lib/dhcpsrv/utils.h
new file mode 100644
index 0000000..96faa93
--- /dev/null
+++ b/src/lib/dhcpsrv/utils.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2012-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/.
+
+#ifndef DHCPSRV_UTILS_H
+#define DHCPSRV_UTILS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if a DHCPv6 protocol violation occurs while
+/// processing a message (e.g. a mandatory option is missing)
+class RFCViolation : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+RFCViolation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // DHCPSRV_UTILS_H
diff --git a/src/lib/dhcpsrv/writable_host_data_source.h b/src/lib/dhcpsrv/writable_host_data_source.h
new file mode 100644
index 0000000..6115d22
--- /dev/null
+++ b/src/lib/dhcpsrv/writable_host_data_source.h
@@ -0,0 +1,245 @@
+// Copyright (C) 2014-2020 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 WRITABLE_HOST_DATA_SOURCE_H
+#define WRITABLE_HOST_DATA_SOURCE_H
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Interface for retrieving writable host reservations.
+///
+/// This interface specifies the methods which return pointers to the
+/// @c Host objects, which can be modified. Deriving from this interface
+/// is needed if the class implementation must return the pointers to the
+/// objects which may be modified by the caller. Such classes usually
+/// also derive from the @c BaseHostDataSource to implement methods which
+/// return the const objects.
+class WritableHostDataSource {
+public:
+
+ /// @brief Default destructor implementation.
+ virtual ~WritableHostDataSource() { }
+
+ /// @brief Non-const version of the @c getAll const method.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// for a specified identifier. This method may return multiple hosts
+ /// because a particular client may have reservations in multiple subnets.
+ ///
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getAll(const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin,
+ const size_t identifier_len) = 0;
+
+ /// @brief Returns a collection of hosts in the specified DHCPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getAll4(const SubnetID& subnet_id) = 0;
+
+ /// @brief Returns a collection of hosts in the specified DHCPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getAll6(const SubnetID& subnet_id) = 0;
+
+ /// @brief Return all hosts with a hostname.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname.
+ ///
+ /// @param hostname The lower case hostname.
+ ///
+ /// @return Collection of @c Host objects.
+ virtual HostCollection
+ getAllbyHostname(const std::string& hostname) = 0;
+
+ /// @brief Return all hosts with a hostname in a DHCPv4 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of @c Host objects.
+ virtual HostCollection
+ getAllbyHostname4(const std::string& hostname, const SubnetID& subnet_id) = 0;
+
+ /// @brief Return all hosts with a hostname in a DHCPv6 subnet.
+ ///
+ /// This method returns all @c Host objects which represent reservations
+ /// using a specified hostname in a specified subnet.
+ ///
+ /// @param hostname The lower case hostname.
+ /// @param subnet_id Subnet identifier.
+ ///
+ /// @return Collection of @c Host objects.
+ virtual HostCollection
+ getAllbyHostname6(const std::string& hostname, const SubnetID& subnet_id) = 0;
+
+ /// @brief Returns range of hosts in a DHCPv4 subnet.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getPage4(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) = 0;
+
+ /// @brief Returns range of hosts in a DHCPv6 subnet.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getPage6(const SubnetID& subnet_id,
+ size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) = 0;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getPage4(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) = 0;
+
+ /// @brief Returns range of hosts.
+ ///
+ /// This method implements paged browsing of host databases. The
+ /// parameters specify a page size, an index in sources and the
+ /// starting host id of the range. If not zero this host id is
+ /// excluded from the returned range. When a source is exhausted
+ /// the index is updated. There is no guarantee about the order
+ /// of returned host reservations, only the sources and
+ /// reservations from the same source are ordered.
+ ///
+ /// @param source_index Index of the source.
+ /// @param lower_host_id Host identifier used as lower bound for the
+ /// returned range.
+ /// @param page_size maximum size of the page returned.
+ ///
+ /// @return Collection of non-const @c Host objects.
+ virtual HostCollection
+ getPage6(size_t& source_index,
+ uint64_t lower_host_id,
+ const HostPageSize& page_size) = 0;
+
+ /// @brief Returns a collection of hosts using the specified IPv4 address.
+ ///
+ /// This method may return multiple @c Host objects if they are connected
+ /// to different subnets.
+ ///
+ /// @param address IPv4 address for which the @c Host object is searched.
+ ///
+ /// @return Collection of @c Host objects.
+ virtual HostCollection
+ getAll4(const asiolink::IOAddress& address) = 0;
+
+ /// @brief Returns a host connected to the IPv4 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Non-const @c Host object for which reservation has been made
+ /// using the specified identifier.
+ virtual HostPtr
+ get4(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
+ /// @brief Returns a host connected to the IPv6 subnet.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param identifier_type Identifier type.
+ /// @param identifier_begin Pointer to a beginning of a buffer containing
+ /// an identifier.
+ /// @param identifier_len Identifier length.
+ ///
+ /// @return Non-const @c Host object for which reservation has been made
+ /// using the specified identifier.
+ virtual HostPtr
+ get6(const SubnetID& subnet_id, const Host::IdentifierType& identifier_type,
+ const uint8_t* identifier_begin, const size_t identifier_len) = 0;
+
+ /// @brief Returns a host using the specified IPv6 prefix.
+ ///
+ /// @param prefix IPv6 prefix for which the @c Host object is searched.
+ /// @param prefix_len IPv6 prefix length.
+ ///
+ /// @return Non-const @c Host object using a specified IPv6 prefix.
+ virtual HostPtr
+ get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) = 0;
+
+ /// @brief Returns a host connected to the IPv6 subnet and having
+ /// a reservation for a specified IPv6 address or prefix.
+ ///
+ /// @param subnet_id Subnet identifier.
+ /// @param address reserved IPv6 address/prefix.
+ ///
+ /// @return @c Host object using a specified IPv6 address/prefix.
+ virtual HostPtr
+ get6(const SubnetID& subnet_id, const asiolink::IOAddress& address) = 0;
+};
+
+}
+}
+
+#endif // WRITABLE_HOST_DATA_SOURCE_H
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
new file mode 100644
index 0000000..8453588
--- /dev/null
+++ b/src/lib/dns/Makefile.am
@@ -0,0 +1,226 @@
+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
+CLEANFILES += s-rdatacode
+# These two are created with rrtype/class.h, so not explicitly listed in
+# BUILT_SOURCES.
+CLEANFILES += python/rrtype_constants_inc.cc
+CLEANFILES += python/rrclass_constants_inc.cc
+
+DISTCLEANFILES = gen-rdatacode.py
+
+EXTRA_DIST = rrclass-placeholder.h
+EXTRA_DIST += rrparamregistry-placeholder.cc
+EXTRA_DIST += rrtype-placeholder.h
+
+# TODO: double-check that this is the only way
+# NOTE: when an rdata file is added, please also add to this list:
+EXTRA_DIST += rdata/any_255/tsig_250.cc
+EXTRA_DIST += rdata/any_255/tsig_250.h
+EXTRA_DIST += rdata/ch_3/a_1.cc
+EXTRA_DIST += rdata/ch_3/a_1.h
+EXTRA_DIST += rdata/generic/cname_5.cc
+EXTRA_DIST += rdata/generic/cname_5.h
+EXTRA_DIST += rdata/generic/detail/char_string.cc
+EXTRA_DIST += rdata/generic/detail/char_string.h
+EXTRA_DIST += rdata/generic/detail/lexer_util.h
+EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
+EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
+EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
+EXTRA_DIST += rdata/generic/detail/nsec3param_common.h
+EXTRA_DIST += rdata/generic/detail/txt_like.h
+EXTRA_DIST += rdata/generic/detail/ds_like.h
+EXTRA_DIST += rdata/generic/dlv_32769.cc
+EXTRA_DIST += rdata/generic/dlv_32769.h
+EXTRA_DIST += rdata/generic/dname_39.cc
+EXTRA_DIST += rdata/generic/dname_39.h
+EXTRA_DIST += rdata/generic/dnskey_48.cc
+EXTRA_DIST += rdata/generic/dnskey_48.h
+EXTRA_DIST += rdata/generic/ds_43.cc
+EXTRA_DIST += rdata/generic/ds_43.h
+EXTRA_DIST += rdata/generic/hinfo_13.cc
+EXTRA_DIST += rdata/generic/hinfo_13.h
+EXTRA_DIST += rdata/generic/mx_15.cc
+EXTRA_DIST += rdata/generic/mx_15.h
+EXTRA_DIST += rdata/generic/naptr_35.cc
+EXTRA_DIST += rdata/generic/naptr_35.h
+EXTRA_DIST += rdata/generic/ns_2.cc
+EXTRA_DIST += rdata/generic/ns_2.h
+EXTRA_DIST += rdata/generic/nsec3_50.cc
+EXTRA_DIST += rdata/generic/nsec3_50.h
+EXTRA_DIST += rdata/generic/nsec3param_51.cc
+EXTRA_DIST += rdata/generic/nsec3param_51.h
+EXTRA_DIST += rdata/generic/nsec_47.cc
+EXTRA_DIST += rdata/generic/nsec_47.h
+EXTRA_DIST += rdata/generic/opt_41.cc
+EXTRA_DIST += rdata/generic/opt_41.h
+EXTRA_DIST += rdata/generic/ptr_12.cc
+EXTRA_DIST += rdata/generic/ptr_12.h
+EXTRA_DIST += rdata/generic/rp_17.cc
+EXTRA_DIST += rdata/generic/rp_17.h
+EXTRA_DIST += rdata/generic/rrsig_46.cc
+EXTRA_DIST += rdata/generic/rrsig_46.h
+EXTRA_DIST += rdata/generic/soa_6.cc
+EXTRA_DIST += rdata/generic/soa_6.h
+EXTRA_DIST += rdata/generic/spf_99.cc
+EXTRA_DIST += rdata/generic/spf_99.h
+EXTRA_DIST += rdata/generic/sshfp_44.cc
+EXTRA_DIST += rdata/generic/sshfp_44.h
+EXTRA_DIST += rdata/generic/tlsa_52.cc
+EXTRA_DIST += rdata/generic/tlsa_52.h
+EXTRA_DIST += rdata/generic/tkey_249.cc
+EXTRA_DIST += rdata/generic/tkey_249.h
+EXTRA_DIST += rdata/generic/txt_16.cc
+EXTRA_DIST += rdata/generic/txt_16.h
+EXTRA_DIST += rdata/generic/minfo_14.cc
+EXTRA_DIST += rdata/generic/minfo_14.h
+EXTRA_DIST += rdata/generic/afsdb_18.cc
+EXTRA_DIST += rdata/generic/afsdb_18.h
+EXTRA_DIST += rdata/generic/caa_257.cc
+EXTRA_DIST += rdata/generic/caa_257.h
+EXTRA_DIST += rdata/hs_4/a_1.cc
+EXTRA_DIST += rdata/hs_4/a_1.h
+EXTRA_DIST += rdata/in_1/a_1.cc
+EXTRA_DIST += rdata/in_1/a_1.h
+EXTRA_DIST += rdata/in_1/aaaa_28.cc
+EXTRA_DIST += rdata/in_1/aaaa_28.h
+EXTRA_DIST += rdata/in_1/dhcid_49.cc
+EXTRA_DIST += rdata/in_1/dhcid_49.h
+EXTRA_DIST += rdata/in_1/srv_33.cc
+EXTRA_DIST += rdata/in_1/srv_33.h
+EXTRA_DIST += rdata/template.cc
+EXTRA_DIST += rdata/template.h
+
+noinst_SCRIPTS = gen-rdatacode.py
+
+# auto-generate by gen-rdatacode.py:
+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 42:0:0
+libkea_dns___la_LDFLAGS += $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+libkea_dns___la_SOURCES =
+libkea_dns___la_SOURCES += dns_fwd.h
+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 += masterload.h masterload.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 += nsec3hash.h nsec3hash.cc
+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 += rdatafields.h rdatafields.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 += rrcollator.h rrcollator.cc
+libkea_dns___la_SOURCES += qid_gen.h qid_gen.cc
+libkea_dns___la_SOURCES += question.h question.cc
+libkea_dns___la_SOURCES += serial.h serial.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 master_loader_callbacks.cc
+libkea_dns___la_SOURCES += master_loader.h
+libkea_dns___la_SOURCES += rrset_collection_base.h
+libkea_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
+libkea_dns___la_SOURCES += zone_checker.h zone_checker.cc
+libkea_dns___la_SOURCES += rdata_pimpl_holder.h
+libkea_dns___la_SOURCES += rdata/generic/detail/char_string.h
+libkea_dns___la_SOURCES += rdata/generic/detail/char_string.cc
+libkea_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
+libkea_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
+libkea_dns___la_SOURCES += rdata/generic/detail/nsec3param_common.cc
+libkea_dns___la_SOURCES += rdata/generic/detail/nsec3param_common.h
+libkea_dns___la_SOURCES += rdata/generic/detail/txt_like.h
+libkea_dns___la_SOURCES += rdata/generic/detail/ds_like.h
+
+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
+
+rrclass.h: rrclass-placeholder.h
+rrtype.h: rrtype-placeholder.h
+rrparamregistry.cc: rrparamregistry-placeholder.cc
+
+s-rdatacode: Makefile $(EXTRA_DIST)
+ $(PYTHON) ./gen-rdatacode.py
+ touch $@
+
+# In ticket #3413 we removed the whole BIND10/Bundy framework. We also want
+# to not require Python3, hence instead of generating the code every time,
+# we added the generated files to our repo. It is still possible to regenerate
+# those files, but that step is no longer required for successful compilation.
+
+#rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc: s-rdatacode
+
+libdns___includedir = $(pkgincludedir)/dns
+libdns___include_HEADERS = \
+ dns_fwd.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 \
+ masterload.h \
+ message.h \
+ messagerenderer.h \
+ name.h \
+ nsec3hash.h \
+ opcode.h \
+ qid_gen.h \
+ question.h \
+ rcode.h \
+ rdata.h \
+ rdata_pimpl_holder.h \
+ rdataclass.h \
+ rdatafields.h \
+ rrclass.h \
+ rrcollator.h \
+ rrparamregistry.h \
+ rrset.h \
+ rrset_collection.h \
+ rrset_collection_base.h \
+ rrttl.h \
+ rrtype.h \
+ serial.h \
+ tsig.h \
+ tsigerror.h \
+ tsigkey.h \
+ tsigrecord.h \
+ zone_checker.h
+# Purposely not installing these headers:
+# name_internal.h: used only internally, and not actually DNS specific
+# rdata/*/detail/*.h: these are internal use only
+# rrclass-placeholder.h
+# rrtype-placeholder.h
diff --git a/src/lib/dns/Makefile.in b/src/lib/dns/Makefile.in
new file mode 100644
index 0000000..a1cda0f
--- /dev/null
+++ b/src/lib/dns/Makefile.in
@@ -0,0 +1,1521 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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_cpp11.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 = gen-rdatacode.py
+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__dirstamp = $(am__leading_dot)dirstamp
+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-masterload.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-nsec3hash.lo libkea_dns___la-opcode.lo \
+ libkea_dns___la-rcode.lo libkea_dns___la-rdata.lo \
+ libkea_dns___la-rdatafields.lo libkea_dns___la-rrclass.lo \
+ libkea_dns___la-rrset.lo libkea_dns___la-rrttl.lo \
+ libkea_dns___la-rrtype.lo libkea_dns___la-rrcollator.lo \
+ libkea_dns___la-qid_gen.lo libkea_dns___la-question.lo \
+ libkea_dns___la-serial.lo libkea_dns___la-tsig.lo \
+ libkea_dns___la-tsigerror.lo libkea_dns___la-tsigkey.lo \
+ libkea_dns___la-tsigrecord.lo \
+ libkea_dns___la-master_loader_callbacks.lo \
+ libkea_dns___la-rrset_collection.lo \
+ libkea_dns___la-zone_checker.lo \
+ rdata/generic/detail/libkea_dns___la-char_string.lo \
+ rdata/generic/detail/libkea_dns___la-nsec_bitmap.lo \
+ rdata/generic/detail/libkea_dns___la-nsec3param_common.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 $@
+SCRIPTS = $(noinst_SCRIPTS)
+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-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-master_loader_callbacks.Plo \
+ ./$(DEPDIR)/libkea_dns___la-masterload.Plo \
+ ./$(DEPDIR)/libkea_dns___la-message.Plo \
+ ./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo \
+ ./$(DEPDIR)/libkea_dns___la-name.Plo \
+ ./$(DEPDIR)/libkea_dns___la-nsec3hash.Plo \
+ ./$(DEPDIR)/libkea_dns___la-opcode.Plo \
+ ./$(DEPDIR)/libkea_dns___la-qid_gen.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-rdatafields.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrclass.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrcollator.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrset.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrset_collection.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrttl.Plo \
+ ./$(DEPDIR)/libkea_dns___la-rrtype.Plo \
+ ./$(DEPDIR)/libkea_dns___la-serial.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 \
+ ./$(DEPDIR)/libkea_dns___la-zone_checker.Plo \
+ rdata/generic/detail/$(DEPDIR)/libkea_dns___la-char_string.Plo \
+ rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec3param_common.Plo \
+ rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec_bitmap.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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/gen-rdatacode.py.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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)
+# These two are created with rrtype/class.h, so not explicitly listed in
+# BUILT_SOURCES.
+CLEANFILES = *.gcno *.gcda s-rdatacode python/rrtype_constants_inc.cc \
+ python/rrclass_constants_inc.cc
+DISTCLEANFILES = gen-rdatacode.py
+
+# TODO: double-check that this is the only way
+# NOTE: when an rdata file is added, please also add to this list:
+EXTRA_DIST = rrclass-placeholder.h rrparamregistry-placeholder.cc \
+ rrtype-placeholder.h rdata/any_255/tsig_250.cc \
+ rdata/any_255/tsig_250.h rdata/ch_3/a_1.cc rdata/ch_3/a_1.h \
+ rdata/generic/cname_5.cc rdata/generic/cname_5.h \
+ rdata/generic/detail/char_string.cc \
+ rdata/generic/detail/char_string.h \
+ rdata/generic/detail/lexer_util.h \
+ rdata/generic/detail/nsec_bitmap.cc \
+ rdata/generic/detail/nsec_bitmap.h \
+ rdata/generic/detail/nsec3param_common.cc \
+ rdata/generic/detail/nsec3param_common.h \
+ rdata/generic/detail/txt_like.h rdata/generic/detail/ds_like.h \
+ rdata/generic/dlv_32769.cc rdata/generic/dlv_32769.h \
+ rdata/generic/dname_39.cc rdata/generic/dname_39.h \
+ rdata/generic/dnskey_48.cc rdata/generic/dnskey_48.h \
+ rdata/generic/ds_43.cc rdata/generic/ds_43.h \
+ rdata/generic/hinfo_13.cc rdata/generic/hinfo_13.h \
+ rdata/generic/mx_15.cc rdata/generic/mx_15.h \
+ rdata/generic/naptr_35.cc rdata/generic/naptr_35.h \
+ rdata/generic/ns_2.cc rdata/generic/ns_2.h \
+ rdata/generic/nsec3_50.cc rdata/generic/nsec3_50.h \
+ rdata/generic/nsec3param_51.cc rdata/generic/nsec3param_51.h \
+ rdata/generic/nsec_47.cc rdata/generic/nsec_47.h \
+ rdata/generic/opt_41.cc rdata/generic/opt_41.h \
+ rdata/generic/ptr_12.cc rdata/generic/ptr_12.h \
+ rdata/generic/rp_17.cc rdata/generic/rp_17.h \
+ rdata/generic/rrsig_46.cc rdata/generic/rrsig_46.h \
+ rdata/generic/soa_6.cc rdata/generic/soa_6.h \
+ rdata/generic/spf_99.cc rdata/generic/spf_99.h \
+ rdata/generic/sshfp_44.cc rdata/generic/sshfp_44.h \
+ rdata/generic/tlsa_52.cc rdata/generic/tlsa_52.h \
+ rdata/generic/tkey_249.cc rdata/generic/tkey_249.h \
+ rdata/generic/txt_16.cc rdata/generic/txt_16.h \
+ rdata/generic/minfo_14.cc rdata/generic/minfo_14.h \
+ rdata/generic/afsdb_18.cc rdata/generic/afsdb_18.h \
+ rdata/generic/caa_257.cc rdata/generic/caa_257.h \
+ rdata/hs_4/a_1.cc rdata/hs_4/a_1.h rdata/in_1/a_1.cc \
+ rdata/in_1/a_1.h rdata/in_1/aaaa_28.cc rdata/in_1/aaaa_28.h \
+ rdata/in_1/dhcid_49.cc rdata/in_1/dhcid_49.h \
+ rdata/in_1/srv_33.cc rdata/in_1/srv_33.h rdata/template.cc \
+ rdata/template.h
+noinst_SCRIPTS = gen-rdatacode.py
+
+# auto-generate by gen-rdatacode.py:
+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 42: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 = dns_fwd.h edns.h edns.cc exceptions.h \
+ exceptions.cc master_lexer_inputsource.h \
+ master_lexer_inputsource.cc labelsequence.h labelsequence.cc \
+ masterload.h masterload.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 nsec3hash.h nsec3hash.cc \
+ opcode.h opcode.cc rcode.h rcode.cc rdata.h rdata.cc \
+ rdatafields.h rdatafields.cc rrclass.cc rrparamregistry.h \
+ rrset.h rrset.cc rrttl.h rrttl.cc rrtype.cc rrcollator.h \
+ rrcollator.cc qid_gen.h qid_gen.cc question.h question.cc \
+ serial.h serial.cc tsig.h tsig.cc tsigerror.h tsigerror.cc \
+ tsigkey.h tsigkey.cc tsigrecord.h tsigrecord.cc \
+ master_loader_callbacks.h master_loader_callbacks.cc \
+ master_loader.h rrset_collection_base.h rrset_collection.h \
+ rrset_collection.cc zone_checker.h zone_checker.cc \
+ rdata_pimpl_holder.h rdata/generic/detail/char_string.h \
+ rdata/generic/detail/char_string.cc \
+ rdata/generic/detail/nsec_bitmap.h \
+ rdata/generic/detail/nsec_bitmap.cc \
+ rdata/generic/detail/nsec3param_common.cc \
+ rdata/generic/detail/nsec3param_common.h \
+ rdata/generic/detail/txt_like.h rdata/generic/detail/ds_like.h \
+ 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)
+
+# In ticket #3413 we removed the whole BIND10/Bundy framework. We also want
+# to not require Python3, hence instead of generating the code every time,
+# we added the generated files to our repo. It is still possible to regenerate
+# those files, but that step is no longer required for successful compilation.
+
+#rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc: s-rdatacode
+libdns___includedir = $(pkgincludedir)/dns
+libdns___include_HEADERS = \
+ dns_fwd.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 \
+ masterload.h \
+ message.h \
+ messagerenderer.h \
+ name.h \
+ nsec3hash.h \
+ opcode.h \
+ qid_gen.h \
+ question.h \
+ rcode.h \
+ rdata.h \
+ rdata_pimpl_holder.h \
+ rdataclass.h \
+ rdatafields.h \
+ rrclass.h \
+ rrcollator.h \
+ rrparamregistry.h \
+ rrset.h \
+ rrset_collection.h \
+ rrset_collection_base.h \
+ rrttl.h \
+ rrtype.h \
+ serial.h \
+ tsig.h \
+ tsigerror.h \
+ tsigkey.h \
+ tsigrecord.h \
+ zone_checker.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):
+gen-rdatacode.py: $(top_builddir)/config.status $(srcdir)/gen-rdatacode.py.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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}; \
+ }
+rdata/generic/detail/$(am__dirstamp):
+ @$(MKDIR_P) rdata/generic/detail
+ @: > rdata/generic/detail/$(am__dirstamp)
+rdata/generic/detail/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) rdata/generic/detail/$(DEPDIR)
+ @: > rdata/generic/detail/$(DEPDIR)/$(am__dirstamp)
+rdata/generic/detail/libkea_dns___la-char_string.lo: \
+ rdata/generic/detail/$(am__dirstamp) \
+ rdata/generic/detail/$(DEPDIR)/$(am__dirstamp)
+rdata/generic/detail/libkea_dns___la-nsec_bitmap.lo: \
+ rdata/generic/detail/$(am__dirstamp) \
+ rdata/generic/detail/$(DEPDIR)/$(am__dirstamp)
+rdata/generic/detail/libkea_dns___la-nsec3param_common.lo: \
+ rdata/generic/detail/$(am__dirstamp) \
+ rdata/generic/detail/$(DEPDIR)/$(am__dirstamp)
+
+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)
+ -rm -f rdata/generic/detail/*.$(OBJEXT)
+ -rm -f rdata/generic/detail/*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@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-master_loader_callbacks.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-masterload.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-nsec3hash.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-qid_gen.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-rdatafields.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-rrcollator.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-rrset_collection.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-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
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-zone_checker.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@rdata/generic/detail/$(DEPDIR)/libkea_dns___la-char_string.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec3param_common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec_bitmap.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-masterload.lo: masterload.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-masterload.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-masterload.Tpo -c -o libkea_dns___la-masterload.lo `test -f 'masterload.cc' || echo '$(srcdir)/'`masterload.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-masterload.Tpo $(DEPDIR)/libkea_dns___la-masterload.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='masterload.cc' object='libkea_dns___la-masterload.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-masterload.lo `test -f 'masterload.cc' || echo '$(srcdir)/'`masterload.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-nsec3hash.lo: nsec3hash.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-nsec3hash.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-nsec3hash.Tpo -c -o libkea_dns___la-nsec3hash.lo `test -f 'nsec3hash.cc' || echo '$(srcdir)/'`nsec3hash.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-nsec3hash.Tpo $(DEPDIR)/libkea_dns___la-nsec3hash.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nsec3hash.cc' object='libkea_dns___la-nsec3hash.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-nsec3hash.lo `test -f 'nsec3hash.cc' || echo '$(srcdir)/'`nsec3hash.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-rdatafields.lo: rdatafields.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-rdatafields.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rdatafields.Tpo -c -o libkea_dns___la-rdatafields.lo `test -f 'rdatafields.cc' || echo '$(srcdir)/'`rdatafields.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rdatafields.Tpo $(DEPDIR)/libkea_dns___la-rdatafields.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdatafields.cc' object='libkea_dns___la-rdatafields.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-rdatafields.lo `test -f 'rdatafields.cc' || echo '$(srcdir)/'`rdatafields.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-rrcollator.lo: rrcollator.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-rrcollator.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrcollator.Tpo -c -o libkea_dns___la-rrcollator.lo `test -f 'rrcollator.cc' || echo '$(srcdir)/'`rrcollator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrcollator.Tpo $(DEPDIR)/libkea_dns___la-rrcollator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrcollator.cc' object='libkea_dns___la-rrcollator.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-rrcollator.lo `test -f 'rrcollator.cc' || echo '$(srcdir)/'`rrcollator.cc
+
+libkea_dns___la-qid_gen.lo: qid_gen.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-qid_gen.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-qid_gen.Tpo -c -o libkea_dns___la-qid_gen.lo `test -f 'qid_gen.cc' || echo '$(srcdir)/'`qid_gen.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-qid_gen.Tpo $(DEPDIR)/libkea_dns___la-qid_gen.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='qid_gen.cc' object='libkea_dns___la-qid_gen.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-qid_gen.lo `test -f 'qid_gen.cc' || echo '$(srcdir)/'`qid_gen.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-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-master_loader_callbacks.lo: master_loader_callbacks.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_callbacks.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-master_loader_callbacks.Tpo -c -o libkea_dns___la-master_loader_callbacks.lo `test -f 'master_loader_callbacks.cc' || echo '$(srcdir)/'`master_loader_callbacks.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-master_loader_callbacks.Tpo $(DEPDIR)/libkea_dns___la-master_loader_callbacks.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_callbacks.cc' object='libkea_dns___la-master_loader_callbacks.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_callbacks.lo `test -f 'master_loader_callbacks.cc' || echo '$(srcdir)/'`master_loader_callbacks.cc
+
+libkea_dns___la-rrset_collection.lo: rrset_collection.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_collection.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrset_collection.Tpo -c -o libkea_dns___la-rrset_collection.lo `test -f 'rrset_collection.cc' || echo '$(srcdir)/'`rrset_collection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrset_collection.Tpo $(DEPDIR)/libkea_dns___la-rrset_collection.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset_collection.cc' object='libkea_dns___la-rrset_collection.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_collection.lo `test -f 'rrset_collection.cc' || echo '$(srcdir)/'`rrset_collection.cc
+
+libkea_dns___la-zone_checker.lo: zone_checker.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-zone_checker.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-zone_checker.Tpo -c -o libkea_dns___la-zone_checker.lo `test -f 'zone_checker.cc' || echo '$(srcdir)/'`zone_checker.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-zone_checker.Tpo $(DEPDIR)/libkea_dns___la-zone_checker.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='zone_checker.cc' object='libkea_dns___la-zone_checker.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-zone_checker.lo `test -f 'zone_checker.cc' || echo '$(srcdir)/'`zone_checker.cc
+
+rdata/generic/detail/libkea_dns___la-char_string.lo: rdata/generic/detail/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 rdata/generic/detail/libkea_dns___la-char_string.lo -MD -MP -MF rdata/generic/detail/$(DEPDIR)/libkea_dns___la-char_string.Tpo -c -o rdata/generic/detail/libkea_dns___la-char_string.lo `test -f 'rdata/generic/detail/char_string.cc' || echo '$(srcdir)/'`rdata/generic/detail/char_string.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) rdata/generic/detail/$(DEPDIR)/libkea_dns___la-char_string.Tpo rdata/generic/detail/$(DEPDIR)/libkea_dns___la-char_string.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata/generic/detail/char_string.cc' object='rdata/generic/detail/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 rdata/generic/detail/libkea_dns___la-char_string.lo `test -f 'rdata/generic/detail/char_string.cc' || echo '$(srcdir)/'`rdata/generic/detail/char_string.cc
+
+rdata/generic/detail/libkea_dns___la-nsec_bitmap.lo: rdata/generic/detail/nsec_bitmap.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 rdata/generic/detail/libkea_dns___la-nsec_bitmap.lo -MD -MP -MF rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec_bitmap.Tpo -c -o rdata/generic/detail/libkea_dns___la-nsec_bitmap.lo `test -f 'rdata/generic/detail/nsec_bitmap.cc' || echo '$(srcdir)/'`rdata/generic/detail/nsec_bitmap.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec_bitmap.Tpo rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec_bitmap.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata/generic/detail/nsec_bitmap.cc' object='rdata/generic/detail/libkea_dns___la-nsec_bitmap.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 rdata/generic/detail/libkea_dns___la-nsec_bitmap.lo `test -f 'rdata/generic/detail/nsec_bitmap.cc' || echo '$(srcdir)/'`rdata/generic/detail/nsec_bitmap.cc
+
+rdata/generic/detail/libkea_dns___la-nsec3param_common.lo: rdata/generic/detail/nsec3param_common.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 rdata/generic/detail/libkea_dns___la-nsec3param_common.lo -MD -MP -MF rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec3param_common.Tpo -c -o rdata/generic/detail/libkea_dns___la-nsec3param_common.lo `test -f 'rdata/generic/detail/nsec3param_common.cc' || echo '$(srcdir)/'`rdata/generic/detail/nsec3param_common.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec3param_common.Tpo rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec3param_common.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata/generic/detail/nsec3param_common.cc' object='rdata/generic/detail/libkea_dns___la-nsec3param_common.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 rdata/generic/detail/libkea_dns___la-nsec3param_common.lo `test -f 'rdata/generic/detail/nsec3param_common.cc' || echo '$(srcdir)/'`rdata/generic/detail/nsec3param_common.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
+ -rm -rf rdata/generic/detail/.libs rdata/generic/detail/_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) $(SCRIPTS) $(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: 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)
+ -rm -f rdata/generic/detail/$(DEPDIR)/$(am__dirstamp)
+ -rm -f rdata/generic/detail/$(am__dirstamp)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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-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-master_loader_callbacks.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-masterload.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-nsec3hash.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-opcode.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-qid_gen.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-rdatafields.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrclass.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrcollator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrset.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrset_collection.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-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 ./$(DEPDIR)/libkea_dns___la-zone_checker.Plo
+ -rm -f rdata/generic/detail/$(DEPDIR)/libkea_dns___la-char_string.Plo
+ -rm -f rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec3param_common.Plo
+ -rm -f rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec_bitmap.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-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-master_loader_callbacks.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-masterload.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-nsec3hash.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-opcode.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-qid_gen.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-rdatafields.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrclass.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrcollator.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrset.Plo
+ -rm -f ./$(DEPDIR)/libkea_dns___la-rrset_collection.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-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 ./$(DEPDIR)/libkea_dns___la-zone_checker.Plo
+ -rm -f rdata/generic/detail/$(DEPDIR)/libkea_dns___la-char_string.Plo
+ -rm -f rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec3param_common.Plo
+ -rm -f rdata/generic/detail/$(DEPDIR)/libkea_dns___la-nsec_bitmap.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-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
+
+
+rrclass.h: rrclass-placeholder.h
+rrtype.h: rrtype-placeholder.h
+rrparamregistry.cc: rrparamregistry-placeholder.cc
+
+s-rdatacode: Makefile $(EXTRA_DIST)
+ $(PYTHON) ./gen-rdatacode.py
+ touch $@
+# Purposely not installing these headers:
+# name_internal.h: used only internally, and not actually DNS specific
+# rdata/*/detail/*.h: these are internal use only
+# rrclass-placeholder.h
+# rrtype-placeholder.h
+
+# 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/dns_fwd.h b/src/lib/dns/dns_fwd.h
new file mode 100644
index 0000000..99dac37
--- /dev/null
+++ b/src/lib/dns/dns_fwd.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2012-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/.
+
+#ifndef DNS_FWD_H
+#define DNS_FWD_H 1
+
+/// \file dns_fwd.h
+/// \brief Forward declarations for definitions of libdns++
+///
+/// This file provides a set of forward declarations for definitions commonly
+/// used in libdns++ to help minimize dependency when actual the definition
+/// is not necessary.
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class MasterLoader;
+class MasterLoaderCallbacks;
+class Message;
+class AbstractMessageRenderer;
+class MessageRenderer;
+class NSEC3Hash;
+class NSEC3HashCreator;
+class Opcode;
+class Question;
+class Rcode;
+namespace rdata {
+class Rdata;
+}
+class RRCollator;
+class RRClass;
+class RRType;
+class RRTTL;
+class AbstractRRset;
+class RdataIterator;
+class RRsetCollectionBase;
+class RRsetCollection;
+class Serial;
+class TSIGContext;
+class TSIGError;
+class TSIGKey;
+class TSIGKeyRing;
+class TSIGRecord;
+
+} // namespace dns
+} // namespace isc
+#endif // DNS_FWD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/edns.cc b/src/lib/dns/edns.cc
new file mode 100644
index 0000000..49253cf
--- /dev/null
+++ b/src/lib/dns/edns.cc
@@ -0,0 +1,178 @@
+// 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 <stdint.h>
+
+#include <cassert>
+
+#include <boost/lexical_cast.hpp>
+
+#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>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+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>
+int
+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);
+}
+}
+
+unsigned int
+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));
+}
+
+unsigned int
+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..d5e6375
--- /dev/null
+++ b/src/lib/dns/edns.h
@@ -0,0 +1,437 @@
+// 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/.
+
+#ifndef EDNS_H
+#define EDNS_H 1
+
+#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <ostream>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+class OutputBuffer;
+}
+
+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.
+ unsigned int 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.
+ unsigned int 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
+
+// Local Variables:
+// mode: c++
+// End:
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..40f2cc1
--- /dev/null
+++ b/src/lib/dns/exceptions.h
@@ -0,0 +1,76 @@
+// 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/.
+
+// 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 1
+
+#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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
new file mode 100644
index 0000000..f39fc09
--- /dev/null
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -0,0 +1,391 @@
+#!@PYTHON@
+
+# Copyright (C) 2010-2021 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 a supplemental script to (half) auto-generate DNS Rdata related
+classes and constants.
+"""
+
+# This script should be used every time existing RR data changes or when new
+# RR types are added or existing are removed. Since DHCP uses only four
+# (A,AAAA,PTR,DHCID) RR types, its usage is expected to be an uncommon event.
+# The only envisaged use case is if/when we decide to trim down libdns++.
+
+import os
+from os.path import getmtime
+import re
+import sys
+
+re_typecode = re.compile('([\da-z\-]+)_(\d+)')
+classcode2txt = {}
+typecode2txt = {}
+# For meta types and types well-known but not implemented. This is a dict from
+# type code values (as string) to textual mnemonic.
+meta_types = {
+ # Real meta types. We won't have Rdata implement for them, but we need
+ # RRType constants.
+ '251': 'ixfr', '252': 'axfr', '255': 'any',
+ # Obsolete types. We probably won't implement Rdata for them, but it's
+ # better to have RRType constants.
+ '3': 'md', '4': 'mf', '7': 'mb', '8': 'mg', '9': 'mr', '30': 'nxt',
+ '38': 'a6', '254': 'maila',
+ # Types officially assigned but not yet supported in our implementation.
+ '10': 'null', '11': 'wks', '19': 'x25', '21': 'rt', '22': 'nsap',
+ '23': 'nsap-ptr', '24': 'sig', '20': 'isdn', '25': 'key', '26': 'px',
+ '27': 'gpos', '29': 'loc', '36': 'kx', '37': 'cert', '42': 'apl',
+ '45': 'ipseckey', '55': 'hip', '103': 'unspec',
+ '104': 'nid', '105': 'l32', '106': 'l64', '107': 'lp',
+ '253': 'mailb', '256': 'uri'
+ }
+# Classes that don't have any known types. This is a dict from type code
+# values (as string) to textual mnemonic.
+meta_classes = {'254': 'none'}
+typeandclass = []
+generic_code = 65536 # something larger than any code value
+rdata_declarations = ''
+class_definitions = ''
+classdir_mtime = 0
+rdatadef_mtime = 0
+rdatahdr_mtime = 0
+heading_txt = '''///////////////
+///////////////
+/////////////// THIS FILE IS AUTOMATICALLY GENERATED BY gen-rdatacode.py.
+/////////////// DO NOT EDIT!
+///////////////
+///////////////
+
+'''
+
+def import_classdef(class_txt, file):
+ content = ''
+ rdata_source = open(file, 'r')
+ for line in rdata_source.readlines():
+ if re.match('// BEGIN_ISC_NAMESPACE', line):
+ content += 'namespace isc {\n'
+ content += 'namespace dns {\n'
+ continue
+ if re.match('// BEGIN_RDATA_NAMESPACE', line):
+ content += 'namespace rdata {\n'
+ content += 'namespace ' + class_txt + ' {\n'
+ continue
+ if re.match('// END_ISC_NAMESPACE', line):
+ content += '} // end of namespace "dns"\n'
+ content += '} // end of namespace "isc"\n'
+ continue
+ if re.match('// END_RDATA_NAMESPACE', line):
+ content += '} // end of namespace "' + class_txt +'"\n'
+ content += '} // end of namespace "rdata"\n'
+ continue
+ content += line
+ rdata_source.close()
+ return content
+
+def import_classheader(class_txt, type_txt, type_code, file):
+ type_utxt = type_txt.upper()
+ class_utxt = class_txt.upper()
+
+ # for each CLASS_n/TYPE_m.h
+ rdata_header = open(file, 'r')
+ content = ''
+ guard_macro = class_txt.upper() + '_' + type_txt.upper()
+ guard_macro += '_' + type_code + '_H'
+ for line in rdata_header.readlines():
+ if re.match('// BEGIN_HEADER_GUARD', line):
+ content += '#ifndef ' + guard_macro + '\n'
+ content += '#define ' + guard_macro + ' 1\n'
+ continue
+ if re.match('// END_HEADER_GUARD', line):
+ content += '#endif // ' + guard_macro + '\n'
+ continue
+ if re.match('// BEGIN_ISC_NAMESPACE', line):
+ content += 'namespace isc {\n'
+ content += 'namespace util {\n'
+ content += '''
+class InputBuffer;
+class OutputBuffer;\n'''
+ content += '}\n\n'
+ content += 'namespace dns {\n'
+ continue
+ if re.match('// BEGIN_RDATA_NAMESPACE', line):
+ content += 'namespace rdata {\n'
+ content += 'namespace ' + class_txt + ' {\n'
+ continue
+ if re.match('// END_ISC_NAMESPACE', line):
+ content += '} // end of namespace "dns"\n'
+ content += '} // end of namespace "isc"\n'
+ continue
+ if re.match('// END_RDATA_NAMESPACE', line):
+ content += '} // end of namespace "' + class_txt +'"\n'
+ content += '} // end of namespace "rdata"\n'
+ continue
+ if re.match('// Local Variables:', line):
+ break
+ content += line
+ if re.match('// BEGIN_COMMON_DECLARATIONS', line):
+ content += '''
+class AbstractMessageRenderer;\n\n'''
+ if re.match('\s+// BEGIN_COMMON_MEMBERS$', line):
+ content += '''
+ explicit ''' + type_utxt + '''(const std::string& type_str);
+ ''' + type_utxt + '''(isc::util::InputBuffer& buffer, size_t rdata_len);
+ ''' + type_utxt + '''(const ''' + type_utxt + '''& other);
+ ''' + type_utxt + '''(
+ MasterLexer& lexer, const Name* name,
+ MasterLoader::Options options, MasterLoaderCallbacks& callbacks);
+ virtual std::string toText() const;
+ virtual void toWire(isc::util::OutputBuffer& buffer) const;
+ virtual void toWire(AbstractMessageRenderer& renderer) const;
+ virtual int compare(const Rdata& other) const;\n\n'''
+ rdata_header.close()
+ return content
+
+def import_definitions(classcode2txt, typecode2txt, typeandclass):
+ global rdata_declarations
+ global class_definitions
+ global classdir_mtime
+ global rdatadef_mtime
+ global rdatahdr_mtime
+
+ if classdir_mtime < getmtime('@srcdir@/rdata'):
+ classdir_mtime = getmtime('@srcdir@/rdata')
+
+ # Sort directories before iterating through them so that the directory
+ # list is processed in the same order on all systems. The resulting
+ # files should compile regardless of the order in which the components
+ # are included but... Having a fixed order for the directories should
+ # eliminate system-dependent problems. (Note that the directory names
+ # in BIND 10 are ASCII, so the order should be locale-independent.)
+ dirlist = os.listdir('@srcdir@/rdata')
+ dirlist.sort()
+ for dir in dirlist:
+ classdir = '@srcdir@/rdata' + os.sep + dir
+ m = re_typecode.match(dir)
+ if os.path.isdir(classdir) and (m != None or dir == 'generic'):
+ if dir == 'generic':
+ class_txt = 'generic'
+ class_code = generic_code
+ else:
+ class_txt = m.group(1)
+ class_code = m.group(2)
+ if not class_code in classcode2txt:
+ classcode2txt[class_code] = class_txt
+
+ # Same considerations as directories regarding sorted order
+ # also apply to files.
+ filelist = os.listdir(classdir)
+ filelist.sort()
+ for file in filelist:
+ file = classdir + os.sep + file
+ m = re_typecode.match(os.path.split(file)[1])
+ if m != None:
+ type_txt = m.group(1)
+ type_code = m.group(2)
+ if not type_code in typecode2txt:
+ typecode2txt[type_code] = type_txt
+ if re.search('\.cc$', file):
+ if rdatadef_mtime < getmtime(file):
+ rdatadef_mtime = getmtime(file)
+ class_definitions += import_classdef(class_txt, file)
+ elif re.search('\.h$', file):
+ if rdatahdr_mtime < getmtime(file):
+ rdatahdr_mtime = getmtime(file)
+ rdata_declarations += import_classheader(class_txt,
+ type_txt,
+ type_code,
+ file)
+ typeandclass.append((type_txt, int(type_code),
+ (class_txt, class_txt),
+ int(class_code)))
+ if class_txt == 'generic':
+ typeandclass.append((type_txt, int(type_code),
+ (class_txt, 'in'), 1))
+
+def need_generate(file, mtime):
+ '''Check if we need to generate the specified file.
+
+ To avoid unnecessary compilation, we skip (re)generating the file when
+ the file already exists and newer than the base file.
+ '''
+ if os.path.exists(file) and getmtime(file) > mtime:
+ return False
+ return True
+
+def generate_rdatadef(file, basemtime):
+ if not need_generate(file, basemtime):
+ print('skip generating ' + file);
+ return
+ rdata_deffile = open(file, 'w')
+ rdata_deffile.write(heading_txt)
+ rdata_deffile.write(class_definitions)
+ rdata_deffile.close()
+
+def generate_rdatahdr(file, heading, declarations, basemtime):
+ if not need_generate(file, basemtime):
+ print('skip generating ' + file);
+ return
+ heading += '''
+#ifndef DNS_RDATACLASS_H
+#define DNS_RDATACLASS_H 1
+
+#include <dns/master_loader.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class MasterLexer;
+class MasterLoaderCallbacks;
+}
+}
+'''
+ declarations += '''
+#endif // DNS_RDATACLASS_H
+
+// Local Variables:
+// mode: c++
+// End:
+'''
+ rdata_header = open(file, 'w')
+ rdata_header.write(heading)
+ rdata_header.write(declarations)
+ rdata_header.close()
+
+def generate_typeclasscode(fileprefix, basemtime, code2txt, type_or_class):
+ placeholder = '@srcdir@/' + fileprefix + '-placeholder.h'
+ outputfile = '@builddir@/' + fileprefix + '.h'
+ py_outputfile = '@builddir@/python/' + fileprefix + '_constants_inc.cc'
+ upper_key = type_or_class.upper() # TYPE or CLASS
+ lower_key = 'rr' + type_or_class.lower() # rrtype or rrclass
+ cap_key = type_or_class # Type or Class
+
+ # We only decide whether to generate files for libdns++ files; Python
+ # files are generated if and only if libdns++ files are generated.
+ # In practice it should be sufficient.
+ if (not need_generate(outputfile, basemtime) and
+ getmtime(outputfile) > getmtime(placeholder)):
+ print('skip generating ' + outputfile)
+ return
+
+ # Create a list of (code, code-text) pairs, where code-text is generally
+ # upper-cased, with applying special filters when necessary.
+ def convert(code_txt):
+ # Workaround by heuristics: there's a "NULL" RR type, but it would
+ # cause conflict with the C/C++ macro. We use Null as a special case.
+ if code_txt == 'null':
+ return 'Null'
+ # Likewise, convert "nsap-ptr" to "NSAP_PTR" as a dash cannot be part
+ # of a C/C++ variable.
+ if code_txt == 'nsap-ptr':
+ return 'NSAP_PTR'
+ return code_txt.upper()
+ codes = [ (code, convert(txt)) for code, txt in code2txt.items() ]
+
+ # Dump source code for libdns++
+ with open(placeholder, 'r') as header_temp:
+ with open(outputfile, 'w') as header_out:
+ header_out.write(heading_txt)
+ for line in header_temp:
+ header_out.write(line)
+ if re.match('\s+// BEGIN_WELL_KNOWN_' + upper_key +
+ '_DECLARATIONS$', line):
+ for code in codes:
+ header_out.write(' ' * 4 + 'static const RR' +
+ cap_key + '& ' + code[1] + '();\n')
+ if re.match('// BEGIN_WELL_KNOWN_' + upper_key +
+ '_DEFINITIONS$', line):
+ for code in codes:
+ header_out.write('''inline const RR''' + cap_key +
+ '''&
+RR''' + cap_key + '''::''' + code[1] + '''() {
+ static RR''' + cap_key + ''' ''' + lower_key + '''(''' + code[0] + ''');
+ return (''' + lower_key + ''');
+}\n
+''')
+
+ # Dump source code snippet for isc.dns Python module
+ with open(py_outputfile, 'w') as py_out:
+ py_out.write(" // auto-generated by ../gen-rdatacode.py."
+ " Don't edit this file.\n")
+ py_out.write("\n")
+ for code in codes:
+ py_out.write('''\
+ installClassVariable(''' + lower_key + '''_type, "''' + code[1] + '''",
+ createRR''' + cap_key + '''Object(RR''' + \
+ cap_key + '''::''' + code[1] + '''()));
+''')
+
+def generate_rrparam(fileprefix, basemtime):
+ placeholder = '@srcdir@/' + fileprefix + '-placeholder.cc'
+ outputfile = '@builddir@/' + fileprefix + '.cc'
+ if not need_generate(outputfile, basemtime) and getmtime(outputfile) > getmtime(placeholder):
+ print('skip generating ' + outputfile)
+ return
+
+ # sort by class, then by type
+ typeandclassparams = ''
+ typeandclass.sort(key = lambda x: (x[3], x[1]))
+ for param in typeandclass:
+ # for rrparamregistry.cc
+ # each param is a tuple of (type_txt, type_code, class_tuple,
+ # class_code)
+ (type_txt, type_code, class_tuple, class_code) = param
+ type_utxt = type_txt.upper()
+ class_txt = class_tuple[0]
+ class_utxt = class_tuple[1].upper()
+ indent = ' ' * 8
+ typeandclassparams += indent
+
+ if class_tuple[1] != 'generic':
+ typeandclassparams += 'add("' + type_utxt + '", '
+ typeandclassparams += str(type_code) + ', "' + class_utxt
+ typeandclassparams += '", ' + str(class_code)
+ typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
+ typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
+ else:
+ typeandclassparams += 'add("' + type_utxt + '", ' + str(type_code)
+ typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
+ typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
+
+ typeandclassparams += indent + '// Meta and non-implemented RR types\n'
+ for type_code, type_txt in meta_types.items():
+ typeandclassparams += indent + \
+ 'addType("' + type_txt.upper() + '", ' + type_code + ');\n'
+
+ typeandclassparams += indent + '// Meta classes\n'
+ for cls_code, cls_txt in meta_classes.items():
+ typeandclassparams += indent + \
+ 'addClass("' + cls_txt.upper() + '", ' + cls_code + ');\n'
+
+ rrparam_temp = open(placeholder, 'r')
+ rrparam_out = open(outputfile, 'w')
+ rrparam_out.write(heading_txt)
+ for line in rrparam_temp.readlines():
+ rrparam_out.write(line)
+ if re.match('\s+// BEGIN_WELL_KNOWN_PARAMS', line):
+ rrparam_out.write(typeandclassparams)
+ rrparam_temp.close()
+ rrparam_out.close()
+
+if __name__ == "__main__":
+ try:
+ import_definitions(classcode2txt, typecode2txt, typeandclass)
+ generate_rdatadef('@builddir@/rdataclass.cc', rdatadef_mtime)
+ generate_rdatahdr('@builddir@/rdataclass.h', heading_txt,
+ rdata_declarations, rdatahdr_mtime)
+
+ # merge auto-generated types/classes with meta maps and generate the
+ # corresponding code.
+ generate_typeclasscode('rrtype', rdatahdr_mtime,
+ dict(typecode2txt, **meta_types), 'Type')
+ generate_typeclasscode('rrclass', classdir_mtime,
+ dict(classcode2txt, **meta_classes), 'Class')
+
+ generate_rrparam('rrparamregistry', rdatahdr_mtime)
+ except:
+ sys.stderr.write('Code generation failed due to exception: %s\n' %
+ sys.exc_info()[1])
+ exit(1)
diff --git a/src/lib/dns/labelsequence.cc b/src/lib/dns/labelsequence.cc
new file mode 100644
index 0000000..8e0be43
--- /dev/null
+++ b/src/lib/dns/labelsequence.cc
@@ -0,0 +1,472 @@
+// Copyright (C) 2012-2019 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 == NULL) {
+ 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..91dbe59
--- /dev/null
+++ b/src/lib/dns/labelsequence.h
@@ -0,0 +1,456 @@
+// Copyright (C) 2012-2020 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 1
+
+#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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
new file mode 100644
index 0000000..0d1292e
--- /dev/null
+++ b/src/lib/dns/master_lexer.cc
@@ -0,0 +1,614 @@
+// Copyright (C) 2012-2015,2021 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_inputsource.h>
+#include <dns/master_lexer_state.h>
+
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <bitset>
+#include <cassert>
+#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_(NULL), 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() {
+ assert(source_ != NULL);
+ 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() {
+ delete impl_;
+}
+
+bool
+MasterLexer::pushSource(const char* filename, std::string* error) {
+ if (filename == NULL) {
+ isc_throw(InvalidParameter,
+ "NULL filename for MasterLexer::pushSource");
+ }
+ try {
+ impl_->sources_.push_back(InputSourcePtr(new InputSource(filename)));
+ } catch (const InputSource::OpenError& ex) {
+ if (error != NULL) {
+ *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() ? NULL :
+ 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_;
+ BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
+ position += src->getPosition();
+ }
+ return (position);
+}
+
+const MasterToken&
+MasterLexer::getNextToken(Options options) {
+ if (impl_->source_ == NULL) {
+ 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 != NULL) {
+ state->handle(*this);
+ }
+ // Make sure a token was produced. Since this Can Not Happen, we assert
+ // here instead of throwing.
+ assert(impl_->token_.getType() != MasterToken::ERROR ||
+ impl_->token_.getErrorCode() != MasterToken::NO_TOKEN_PRODUCED);
+ return (impl_->token_);
+}
+
+namespace {
+inline MasterLexer::Options
+optionsForTokenType(MasterToken::Type expect) {
+ switch (expect) {
+ case MasterToken::STRING:
+ return (MasterLexer::NONE);
+ case MasterToken::QSTRING:
+ return (MasterLexer::QSTRING);
+ case MasterToken::NUMBER:
+ return (MasterLexer::NUMBER);
+ default:
+ isc_throw(InvalidParameter,
+ "expected type for getNextToken not supported: " << expect);
+ }
+}
+}
+
+const MasterToken&
+MasterLexer::getNextToken(MasterToken::Type expect, bool eol_ok) {
+ // Get the next token, specifying an appropriate option corresponding to
+ // the expected type. The result should be set in impl_->token_.
+ getNextToken(optionsForTokenType(expect));
+
+ if (impl_->token_.getType() == MasterToken::ERROR) {
+ if (impl_->token_.getErrorCode() == MasterToken::NUMBER_OUT_OF_RANGE) {
+ ungetToken();
+ }
+ throw LexerError(__FILE__, __LINE__, impl_->token_);
+ }
+
+ const bool is_eol_like =
+ (impl_->token_.getType() == MasterToken::END_OF_LINE ||
+ impl_->token_.getType() == MasterToken::END_OF_FILE);
+ if (eol_ok && is_eol_like) {
+ return (impl_->token_);
+ }
+ if (impl_->token_.getType() == MasterToken::STRING &&
+ expect == MasterToken::QSTRING) {
+ return (impl_->token_);
+ }
+ if (impl_->token_.getType() != expect) {
+ ungetToken();
+ if (is_eol_like) {
+ throw LexerError(__FILE__, __LINE__,
+ MasterToken(MasterToken::UNEXPECTED_END));
+ }
+ assert(expect == MasterToken::NUMBER);
+ throw LexerError(__FILE__, __LINE__,
+ MasterToken(MasterToken::BAD_NUMBER));
+ }
+
+ return (impl_->token_);
+}
+
+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:
+ 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.
+ 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 (NULL);
+ }
+ lexerimpl.token_ = MasterToken(MasterToken::END_OF_FILE);
+ return (NULL);
+ } else if (c == ' ' || c == '\t') {
+ // If requested and we are not in (), recognize the initial space.
+ if (lexerimpl.last_was_eol_ && paren_count == 0 &&
+ (options & MasterLexer::INITIAL_WS) != 0) {
+ lexerimpl.last_was_eol_ = false;
+ lexerimpl.token_ = MasterToken(MasterToken::INITIAL_WS);
+ return (NULL);
+ }
+ } else if (c == '\n') {
+ lexerimpl.last_was_eol_ = true;
+ if (paren_count == 0) { // we don't recognize EOL if we are in ()
+ lexerimpl.token_ = MasterToken(MasterToken::END_OF_LINE);
+ return (NULL);
+ }
+ } 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 (NULL);
+ }
+ } 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 (NULL);
+ }
+ --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.
+ 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..9ed4e81
--- /dev/null
+++ b/src/lib/dns/master_lexer.h
@@ -0,0 +1,678 @@
+// Copyright (C) 2012-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/.
+
+#ifndef MASTER_LEXER_H
+#define MASTER_LEXER_H 1
+
+#include <dns/exceptions.h>
+
+#include <istream>
+#include <string>
+
+#include <stdint.h>
+
+#include <boost/noncopyable.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 = NULL);
+
+ /// \brief Make the given stream the current input source of MasterLexer.
+ ///
+ /// The caller still holds the ownership of the passed stream; it's the
+ /// caller's responsibility to keep it valid as long as it's used in
+ /// \c MasterLexer or to release any resource for the stream after that.
+ /// The caller can explicitly tell \c MasterLexer to stop using the
+ /// stream by calling the \c popSource() method.
+ ///
+ /// 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;
+ 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_lexer_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc
new file mode 100644
index 0000000..841fc6c
--- /dev/null
+++ b/src/lib/dns/master_lexer_inputsource.cc
@@ -0,0 +1,221 @@
+// Copyright (C) 2012-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/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
+
+#include <istream>
+#include <iostream>
+#include <cassert>
+#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");
+ }
+ 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() {
+ 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..d830239
--- /dev/null
+++ b/src/lib/dns/master_lexer_inputsource.h
@@ -0,0 +1,185 @@
+// Copyright (C) 2012-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/.
+
+#ifndef DNS_INPUTSOURCE_H
+#define DNS_INPUTSOURCE_H 1
+
+#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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_lexer_state.h b/src/lib/dns/master_lexer_state.h
new file mode 100644
index 0000000..d328a70
--- /dev/null
+++ b/src/lib/dns/master_lexer_state.h
@@ -0,0 +1,138 @@
+// Copyright (C) 2012-2020 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 1
+
+#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_);
+ }
+};
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
+#endif // MASTER_LEXER_STATE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
new file mode 100644
index 0000000..568fdea
--- /dev/null
+++ b/src/lib/dns/master_loader.cc
@@ -0,0 +1,1073 @@
+// Copyright (C) 2012-2020 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.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
+ 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 Helper method for \c doGenerate().
+ ///
+ /// This is a helper method for \c doGenerate() that processes the
+ /// LHS or RHS for a single iteration in the range that is requested
+ /// by the $GENERATE directive and returns a generated string (that
+ /// is used to build a name (LHS) or RDATA (RHS) for an RR). See the
+ /// commented implementation for details.
+ std::string generateForIter(const std::string& str, const int it);
+
+ /// \brief Process the $GENERATE directive.
+ ///
+ /// See the commented implementation for details.
+ void doGenerate();
+
+ /// \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.
+ 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
+ }
+ 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, "GENERATE")) {
+ doGenerate();
+ 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
+};
+
+namespace { // begin unnamed namespace
+
+/// \brief Generate a dotted nibble sequence.
+///
+/// This method generates a dotted nibble sequence and returns it as a
+/// string. The nibbles are appended from the least significant digit
+/// (in hex representation of \c num) to the most significant digit with
+/// dots ('.') to separate the digits. If \c width is non-zero and the
+/// dotted nibble sequence has not filled the requested width, the rest
+/// of the width is filled with a dotted nibble sequence of 0 nibbles.
+///
+/// Some sample representations:
+///
+/// num = 0x1234, width = 0
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 1
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 8
+/// "4.3.2.1"
+///
+/// num = 0x1234, width = 9
+/// "4.3.2.1."
+///
+/// num = 0x1234, width = 10
+/// "4.3.2.1.0"
+///
+/// num = 0x1234, width = 11
+/// "4.3.2.1.0."
+///
+/// num = 0xabcd, width = 0, uppercase = true
+/// "D.C.B.A"
+///
+/// num = 0, width = 0
+/// "0"
+///
+/// num = 0, width = 1
+/// "0"
+///
+/// num = 0, width = 2
+/// "0."
+///
+/// num = 0, width = 3
+/// "0.0"
+///
+/// \param num The number for which the dotted nibble sequence should be
+/// generated.
+/// \param width The width of the generated string. This is only
+/// meaningful when it is larger than the dotted nibble sequence
+/// representation of \c num.
+/// \param uppercase Whether to use uppercase characters in nibble
+/// sequence.
+/// \return A string containing the dotted nibble sequence.
+std::string
+genNibbles(int num, unsigned int width, bool uppercase) {
+ static const char *hex = "0123456789abcdef0123456789ABCDEF";
+ std::string rstr;
+
+ do {
+ char ch = hex[(num & 0x0f) + (uppercase ? 16 : 0)];
+ num >>= 4;
+ rstr.push_back(ch);
+
+ if (width > 0) {
+ --width;
+ }
+
+ // If width is non zero then we need to add a label separator.
+ // If value is non zero then we need to add another label and
+ // that requires a label separator.
+ if (width > 0 || num != 0) {
+ rstr.push_back('.');
+
+ if (width > 0) {
+ --width;
+ }
+ }
+ } while ((num != 0) || (width > 0));
+
+ return (rstr);
+}
+
+} // end unnamed namespace
+
+std::string
+MasterLoader::MasterLoaderImpl::generateForIter(const std::string& str,
+ const int num)
+{
+ std::string rstr;
+
+ for (std::string::const_iterator it = str.begin(); it != str.end();) {
+ switch (*it) {
+ case '$':
+ // This is the case when the '$' character is encountered in
+ // the LHS or RHS. A computed value is added in its place in
+ // the generated string.
+ ++it;
+ if ((it != str.end()) && (*it == '$')) {
+ rstr.push_back('$');
+ ++it;
+ continue;
+ }
+
+ // The str.end() check is required.
+ if ((it == str.end()) || (*it != '{')) {
+ // There is no modifier (between {}), so just copy the
+ // passed number into the generated string.
+ rstr += boost::str(boost::format("%d") % num);
+ } else {
+ // There is a modifier (between {}). Parse it and handle
+ // the various cases below.
+ const char* scan_str =
+ str.c_str() + std::distance(str.begin(), it);
+ int offset = 0;
+ unsigned int width;
+ char base[2] = {'d', 0}; // char plus null byte
+ // cppcheck-suppress invalidscanf_libc
+ const int n = sscanf(scan_str, "{%d,%u,%1[doxXnN]}",
+ &offset, &width, base);
+ switch (n) {
+ case 1:
+ // Only 1 item was matched (the offset). Copy (num +
+ // offset) into the generated string.
+ rstr += boost::str(boost::format("%d") % (num + offset));
+ break;
+
+ case 2: {
+ // 2 items were matched (the offset and width). Copy
+ // (num + offset) and format it according to the width
+ // into the generated string.
+ const std::string fmt =
+ boost::str(boost::format("%%0%ud") % width);
+ rstr += boost::str(boost::format(fmt) % (num + offset));
+ break;
+ }
+
+ case 3:
+ // 3 items were matched (offset, width and base).
+ if ((base[0] == 'n') || (base[0] == 'N')) {
+ // The base is requesting nibbles. Format it
+ // specially (see genNibbles() documentation).
+ rstr += genNibbles(num + offset, width, (base[0] == 'N'));
+ } else {
+ // The base is not requesting nibbles. Copy (num +
+ // offset) and format it according to the width
+ // and base into the generated string.
+ const std::string fmt =
+ boost::str(boost::format("%%0%u%c") % width % base[0]);
+ rstr += boost::str(boost::format(fmt) % (num + offset));
+ }
+ break;
+
+ default:
+ // Any other case in the modifiers is an error.
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE format modifiers");
+ return ("");
+ }
+
+ // Find the closing brace. Careful that 'it' can be equal
+ // to str.end() here.
+ while ((it != str.end()) && (*it != '}')) {
+ ++it;
+ }
+ // Skip past the closing brace (if there is one).
+ if (it != str.end()) {
+ ++it;
+ }
+ }
+ break;
+
+ case '\\':
+ // This is the case when the '\' character is encountered in
+ // the LHS or RHS. The '\' and the following character are
+ // copied as-is into the generated string. This is usually
+ // used for escaping the $ character.
+ rstr.push_back(*it);
+ ++it;
+ if (it == str.end()) {
+ continue;
+ }
+ rstr.push_back(*it);
+ ++it;
+ break;
+
+ default:
+ // This is the default case that handles all other
+ // characters. They are copied as-is into the generated
+ // string.
+ rstr.push_back(*it);
+ ++it;
+ break;
+ }
+ }
+
+ return (rstr);
+}
+
+void
+MasterLoader::MasterLoaderImpl::doGenerate() {
+ // Parse the range token
+ const MasterToken& range_token = lexer_.getNextToken(MasterToken::STRING);
+ if (range_token.getType() != MasterToken::STRING) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+ const std::string range = range_token.getString();
+
+ // Parse the LHS token
+ const MasterToken& lhs_token = lexer_.getNextToken(MasterToken::STRING);
+ if (lhs_token.getType() != MasterToken::STRING) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+ const std::string lhs = lhs_token.getString();
+
+ // Parse the TTL, RR class and RR type tokens. Note that TTL and RR
+ // class may come in any order, or may be missing (either or
+ // both). If TTL is missing, we expect that it was either specified
+ // explicitly using $TTL, or is implicitly known from a previous RR,
+ // or that this is the SOA RR from which the MINIMUM field is
+ // used. It's unlikely that $GENERATE will be used with an SOA RR,
+ // but it's possible. The parsing happens within the parseRRParams()
+ // helper method which is called below.
+ const MasterToken& param_token = lexer_.getNextToken(MasterToken::STRING);
+ if (param_token.getType() != MasterToken::STRING) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+
+ bool explicit_ttl = false;
+ const RRType rrtype = parseRRParams(explicit_ttl, param_token);
+
+ // Parse the RHS token. It can be a quoted string.
+ const MasterToken& rhs_token = lexer_.getNextToken(MasterToken::QSTRING);
+ if ((rhs_token.getType() != MasterToken::QSTRING) &&
+ (rhs_token.getType() != MasterToken::STRING))
+ {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Invalid $GENERATE syntax");
+ return;
+ }
+ const std::string rhs = rhs_token.getString();
+
+ // Range can be one of two forms: start-stop or start-stop/step. If
+ // the first form is used, then step is set to 1. All of start, stop
+ // and step must be positive.
+ unsigned int start;
+ unsigned int stop;
+ unsigned int step;
+ // cppcheck-suppress invalidscanf_libc
+ const int n = sscanf(range.c_str(), "%u-%u/%u", &start, &stop, &step);
+ if ((n < 2) || (stop < start)) {
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "$GENERATE: invalid range: " + range);
+ return;
+ }
+
+ if (n == 2) {
+ step = 1;
+ }
+
+ // Generate and add the records.
+ for (unsigned int i = start; i <= stop; i += step) {
+ // Get generated strings for LHS and RHS. LHS goes to form the
+ // name, RHS goes to form the RDATA of the RR.
+ const std::string generated_name = generateForIter(lhs, i);
+ const std::string generated_rdata = generateForIter(rhs, i);
+ if (generated_name.empty() || generated_rdata.empty()) {
+ // The error should have been sent to the callbacks already
+ // by generateForIter().
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "$GENERATE error");
+ return;
+ }
+
+ // generateForIter() can return a string with a trailing '.' in
+ // case of a nibble representation. So we cannot use the
+ // relative Name constructor. We use concatenate() which is
+ // expensive, but keeps the generated LHS-based Name within the
+ // active origin.
+ last_name_.reset
+ (new Name(Name(generated_name).concatenate(active_origin_)));
+ previous_name_ = true;
+
+ const rdata::RdataPtr rdata =
+ rdata::createRdata(rrtype, zone_class_, generated_rdata);
+ // 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 added another one
+ ++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");
+ }
+ }
+ }
+}
+
+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_.get() == NULL) {
+ 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.
+ 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_ = 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");
+ }
+ unique_ptr<MasterLoaderImpl>
+ impl(new MasterLoaderImpl("", zone_origin, zone_class,
+ callbacks, add_callback, options));
+ impl->pushStreamSource(stream);
+ impl_ = impl.release();
+}
+
+MasterLoader::~MasterLoader() {
+ delete impl_;
+}
+
+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..b385806
--- /dev/null
+++ b/src/lib/dns/master_loader.h
@@ -0,0 +1,187 @@
+// Copyright (C) 2012-2017 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/master_loader_callbacks.h>
+
+#include <boost/noncopyable.hpp>
+
+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;
+ MasterLoaderImpl* impl_;
+};
+
+} // end namespace dns
+} // end namespace isc
+
+#endif // MASTER_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_loader_callbacks.cc b/src/lib/dns/master_loader_callbacks.cc
new file mode 100644
index 0000000..088a223
--- /dev/null
+++ b/src/lib/dns/master_loader_callbacks.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2012-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/master_loader_callbacks.h>
+
+#include <string>
+
+namespace isc {
+namespace dns {
+
+namespace {
+void
+nullCallback(const std::string&, size_t, const std::string&) {
+}
+}
+
+MasterLoaderCallbacks
+MasterLoaderCallbacks::getNullCallbacks() {
+ return (MasterLoaderCallbacks(nullCallback, nullCallback));
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
new file mode 100644
index 0000000..a731685
--- /dev/null
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2012-2020 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);
+ }
+
+ /// \brief Return a callbacks instance with null callbacks
+ ///
+ /// This is a convenience wrapper to generate a
+ /// \c MasterLoaderCallbacks object with both callbacks being nothing.
+ /// This will be useful for applications that only need to run
+ /// \c MasterLoader and get the end result.
+ ///
+ /// \throw None
+ static MasterLoaderCallbacks getNullCallbacks();
+
+private:
+ const IssueCallback error_, warning_;
+};
+
+}
+}
+
+#endif // MASTER_LOADER_CALLBACKS_H
diff --git a/src/lib/dns/masterload.cc b/src/lib/dns/masterload.cc
new file mode 100644
index 0000000..94638c1
--- /dev/null
+++ b/src/lib/dns/masterload.cc
@@ -0,0 +1,100 @@
+// Copyright (C) 2010-2020 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/masterload.h>
+#include <dns/master_loader.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rrtype.h>
+#include <dns/rrcollator.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <functional>
+#include <istream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <cctype>
+#include <cerrno>
+
+using namespace isc::dns::rdata;
+
+using namespace std;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dns {
+namespace {
+void
+callbackWrapper(const RRsetPtr& rrset, MasterLoadCallback callback,
+ const Name* origin)
+{
+ // Origin related validation:
+ // - reject out-of-zone data
+ // - reject SOA whose owner is not at the top of zone
+ const NameComparisonResult cmp_result =
+ rrset->getName().compare(*origin);
+ if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+ cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+ isc_throw(MasterLoadError, "Out-of-zone data for " << *origin
+ << "/" << rrset->getClass() << ": " << rrset->getName());
+ }
+ if (rrset->getType() == RRType::SOA() &&
+ cmp_result.getRelation() != NameComparisonResult::EQUAL) {
+ isc_throw(MasterLoadError, "SOA not at top of zone: "
+ << *rrset);
+ }
+
+ callback(rrset);
+}
+
+template <typename InputType>
+void
+loadHelper(InputType input, const Name& origin,
+ const RRClass& zone_class, MasterLoadCallback callback)
+{
+ RRCollator rr_collator(std::bind(callbackWrapper, ph::_1, callback,
+ &origin));
+ MasterLoader loader(input, origin, zone_class,
+ MasterLoaderCallbacks::getNullCallbacks(),
+ rr_collator.getCallback());
+ try {
+ loader.load();
+ } catch (const MasterLoaderError& ex) {
+ isc_throw(MasterLoadError, ex.what());
+ }
+ rr_collator.flush();
+}
+}
+
+void
+masterLoad(const char* const filename, const Name& origin,
+ const RRClass& zone_class, MasterLoadCallback callback)
+{
+ if ((filename == NULL) || (*filename == '\0')) {
+ isc_throw(MasterLoadError, "Name of master file must not be null");
+ }
+
+ loadHelper<const char*>(filename, origin, zone_class, callback);
+}
+
+void
+masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
+ MasterLoadCallback callback, const char*)
+{
+ loadHelper<istream&>(input, origin, zone_class, callback);
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/masterload.h b/src/lib/dns/masterload.h
new file mode 100644
index 0000000..3762d54
--- /dev/null
+++ b/src/lib/dns/masterload.h
@@ -0,0 +1,175 @@
+// Copyright (C) 2010-2020 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 MASTERLOAD_H
+#define MASTERLOAD_H 1
+
+#include <dns/rrset.h>
+#include <exceptions/exceptions.h>
+
+#include <functional>
+#include <iosfwd>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+
+/// \brief An exception that is thrown if an error occurs while loading a
+/// master zone data.
+class MasterLoadError : public isc::Exception {
+public:
+ MasterLoadError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// The type of the \c callback parameter of \c masterLoad().
+///
+/// This represents a functor object or a function that takes one parameter
+/// of type \c RRsetPtr and returns nothing.
+typedef std::function<void(RRsetPtr)> MasterLoadCallback;
+
+///
+/// \name Master zone file loader functions.
+///
+//@{
+/// Master zone file loader from a file.
+///
+/// This function parses a given file as a master DNS zone file for
+/// the given origin name and RR class, constructs a sequence of \c RRset
+/// from the RRs containing in the file, and calls the given \c callback
+/// functor object or function with each \c RRset.
+///
+/// The \c callback parameter is a functor object or a function that
+/// takes one parameter of type \c RRsetPtr and returns nothing,
+/// i.e. \c void (see below for specific examples).
+/// More precisely, it can be anything that this form of std::function
+/// can represent, but the caller normally doesn't have to care about
+/// that level of details.
+///
+/// The ownership of constructed RRsets is transferred to the callback
+/// and this function never uses it once it is called.
+/// The callback can freely modify the passed \c RRset.
+///
+/// This function internally uses the MasterLoader class, and basically
+/// accepts and rejects input that MasterLoader accepts and rejects,
+/// accordingly. In addition, this function performs the following validation:
+/// if an SOA RR is included, its owner name must be the origin name.
+///
+/// It does not perform other semantical checks, however. For example,
+/// it doesn't check if an NS RR of the origin name is included or if
+/// there is more than one SOA RR. Such further checks are the caller's
+/// (or the callback's) responsibility.
+///
+/// <b>Exceptions</b>
+///
+/// This function throws an exception of class \c MasterLoadError in the
+/// following cases:
+/// - Any of the validation checks fails (see above).
+/// - The input data has a syntax error.
+/// - The specified file cannot be opened for loading.
+/// - An I/O error occurs during the loading.
+///
+/// In addition, this function requires resource allocation for parsing and
+/// constructing RRsets. If it fails, the corresponding standard exception
+/// will be thrown.
+///
+/// The callback may throw its own function. This function doesn't catch it
+/// and will simply propagate it towards the caller.
+///
+/// <b>Usage Examples</b>
+///
+/// A simplest example usage of this function would be to parse a zone
+/// file and (after validation) dump the content to the standard output.
+/// This is an example functor object and a call to \c masterLoad
+/// that implements this scenario:
+/// \code struct ZoneDumper {
+/// void operator()(ConstRRsetPtr rrset) const {
+/// std::cout << *rrset;
+/// }
+/// };
+/// ...
+/// masterLoad(zone_file, Name("example.com"), RRClass::IN(), ZoneDumper());
+/// \endcode
+/// Alternatively, you can use a normal function instead of a functor:
+/// \code void zoneDumper(ConstRRsetPtr rrset) {
+/// std::cout << *rrset;
+/// }
+/// ...
+/// masterLoad(zone_file, Name("example.com"), RRClass::IN(), zoneDumper);
+/// \endcode
+/// Or, if you want to use it with a member function of some other class,
+/// wrapping things with \c std::bind would be handy:
+/// \code class ZoneDumper {
+/// public:
+/// void dump(ConstRRsetPtr rrset) const {
+/// std::cout << *rrset;
+/// }
+/// };
+/// ...
+/// ZoneDumper dumper;
+/// masterLoad(rr_stream, Name("example.com"), RRClass::IN(),
+/// std::bind(&ZoneDumper::dump, &dumper, _1));
+/// \endcode
+/// You can find a bit more complicated examples in the unit tests code for
+/// this function.
+///
+/// <b>Implementation Notes</b>
+///
+/// The current implementation is in a preliminary level and needs further
+/// extensions. Some design decisions may also have to be reconsidered as
+/// we gain experiences. Those include:
+/// - We may want to allow optional conditions. For example, we may want to
+/// be generous about some validation failures and be able to continue
+/// parsing.
+/// - Especially if we allow to be generous, we may also want to support
+/// returning an error code instead of throwing an exception when we
+/// encounter validation failure.
+/// - RRSIGs are handled as separate RRsets, i.e. they are not included in
+/// the RRset they cover.
+///
+/// \param filename A path to a master zone file to be loaded.
+/// \param origin The origin name of the zone.
+/// \param zone_class The RR class of the zone.
+/// \param callback A callback functor or function that is to be called
+/// for each RRset.
+void masterLoad(const char* const filename, const Name& origin,
+ const RRClass& zone_class, MasterLoadCallback callback);
+
+/// Master zone file loader from input stream.
+///
+/// This function is same as the other version
+/// (\c masterLoad(const char* const, const Name&, const RRClass&, MasterLoadCallback))
+/// except that it takes a \c std::istream instead of a file.
+/// It extracts lines from the stream and handles each line just as a line
+/// of a file for the other version of function.
+/// All descriptions of the other version apply to this version except those
+/// specific to file I/O.
+///
+/// Note: The 'source' parameter is now ignored, but it was only used in
+/// exception messages on some error. So the compatibility effect should be
+/// minimal.
+///
+/// \param input An input stream object that is to emit zone's RRs.
+/// \param origin The origin name of the zone.
+/// \param zone_class The RR class of the zone.
+/// \param callback A callback functor or function that is to be called for
+/// each RRset.
+/// \param source This parameter is now ignored but left for compatibility.
+void masterLoad(std::istream& input, const Name& origin,
+ const RRClass& zone_class, MasterLoadCallback callback,
+ const char* source = NULL);
+}
+
+
+//@}
+}
+
+#endif // MASTERLOAD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
new file mode 100644
index 0000000..fe05a3e
--- /dev/null
+++ b/src/lib/dns/message.cc
@@ -0,0 +1,1176 @@
+// Copyright (C) 2009-2023 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 <stdint.h>
+
+#include <algorithm>
+#include <cassert>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+#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/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>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+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_ = NULL;
+ opcode_ = NULL;
+ 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_ == NULL) {
+ isc_throw(InvalidMessageOperation,
+ "Message rendering attempted without Rcode set");
+ }
+ if (opcode_ == NULL) {
+ 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 != NULL) ? 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 != NULL && 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 != NULL) {
+ // 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_ == NULL) {
+ 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_ == NULL) {
+ 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));
+ }
+
+ BOOST_FOREACH(ConstRRsetPtr 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 (vector<RRsetPtr>::iterator 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_ == NULL) {
+ isc_throw(InvalidMessageOperation,
+ "Message::toText() attempted without Rcode set");
+ }
+ if (impl_->opcode_ == NULL) {
+ 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_ != NULL) {
+ ++arcount;
+ }
+ if (impl_->tsig_rr_ != NULL) {
+ ++arcount;
+ }
+ s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n";
+
+ if (impl_->edns_ != NULL) {
+ 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_ != NULL) {
+ 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 (QuestionIterator qi = source.beginQuestion();
+ qi != source.endQuestion();
+ ++qi) {
+ addQuestion(*qi);
+ }
+ } else {
+ for (RRsetIterator 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..df5ba62
--- /dev/null
+++ b/src/lib/dns/message.h
@@ -0,0 +1,691 @@
+// Copyright (C) 2009-2023 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 1
+
+#include <stdint.h>
+
+#include <iterator>
+#include <string>
+#include <ostream>
+
+#include <dns/exceptions.h>
+
+#include <dns/edns.h>
+#include <dns/question.h>
+#include <dns/rrset.h>
+
+namespace isc {
+namespace util {
+class InputBuffer;
+}
+
+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_(NULL) {}
+ 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 \c 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 = NULL);
+
+ /// 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..81b2c92
--- /dev/null
+++ b/src/lib/dns/messagerenderer.cc
@@ -0,0 +1,397 @@
+// 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 <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/name_internal.h>
+#include <dns/labelsequence.h>
+#include <dns/messagerenderer.h>
+
+#include <boost/array.hpp>
+#include <boost/static_assert.hpp>
+
+#include <limits>
+#include <cassert>
+#include <vector>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::name::internal::maptolower;
+
+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;
+ 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() {
+ delete impl_;
+}
+
+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 != NULL && buffer_->getLength() != 0) {
+ isc_throw(isc::InvalidParameter,
+ "MessageRenderer buffer cannot be set when in use");
+ }
+ if (buffer == NULL && buffer_ == &local_buffer_) {
+ isc_throw(isc::InvalidParameter,
+ "Default MessageRenderer buffer cannot be reset");
+ }
+
+ if (buffer == NULL) {
+ // 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..1b8b9c0
--- /dev/null
+++ b/src/lib/dns/messagerenderer.h
@@ -0,0 +1,395 @@
+// Copyright (C) 2009-2017 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 1
+
+#include <util/buffer.h>
+
+#include <boost/noncopyable.hpp>
+
+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::dns::InvalidBufferPosition 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;
+ MessageRendererImpl* impl_;
+};
+}
+}
+#endif // MESSAGERENDERER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/name.cc b/src/lib/dns/name.cc
new file mode 100644
index 0000000..3f75fa0
--- /dev/null
+++ b/src/lib/dns/name.cc
@@ -0,0 +1,724 @@
+// Copyright (C) 2009-2019 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 <cctype>
+#include <iterator>
+#include <functional>
+#include <vector>
+#include <iostream>
+#include <algorithm>
+
+#include <exceptions/isc_assert.h>
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/name.h>
+#include <dns/name_internal.h>
+#include <dns/messagerenderer.h>
+#include <dns/labelsequence.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::NameComparisonResult;
+using namespace isc::dns::name::internal;
+
+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 == NULL || 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 == NULL) {
+ 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 (NameOffsets::iterator 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..0720684
--- /dev/null
+++ b/src/lib/dns/name.h
@@ -0,0 +1,766 @@
+// Copyright (C) 2009-2019 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 1
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/exceptions.h>
+
+namespace isc {
+namespace util {
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+class AbstractMessageRenderer;
+
+///
+/// \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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/name_internal.h b/src/lib/dns/name_internal.h
new file mode 100644
index 0000000..8be55ec
--- /dev/null
+++ b/src/lib/dns/name_internal.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2012-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/.
+
+#ifndef NAME_INTERNAL_H
+#define NAME_INTERNAL_H 1
+
+// 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
new file mode 100644
index 0000000..a004915
--- /dev/null
+++ b/src/lib/dns/nsec3hash.cc
@@ -0,0 +1,270 @@
+// Copyright (C) 2012-2023 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 <stdint.h>
+
+#include <cassert>
+#include <cstring>
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base32hex.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hash.h>
+
+#include <dns/name.h>
+#include <dns/labelsequence.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+#include <dns/name_internal.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::cryptolink;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+/// \brief A derived class of \c NSEC3Hash that implements the standard hash
+/// calculation specified in RFC5155.
+///
+/// Currently the only pre-defined algorithm in the RFC is SHA1. So we don't
+/// over-generalize it at the moment, and rather hardcode it and assume that
+/// specific algorithm.
+///
+/// The implementation details are only open within this file, but to avoid
+/// an accidental error in this implementation we explicitly make it non
+/// copyable.
+class NSEC3HashRFC5155 : boost::noncopyable, public NSEC3Hash {
+private:
+ // This is the algorithm number for SHA1/NSEC3 as defined in RFC5155.
+ static const uint8_t NSEC3_HASH_SHA1 = 1;
+ // For digest_ allocation
+ static const size_t DEFAULT_DIGEST_LENGTH = 32;
+
+public:
+ NSEC3HashRFC5155(uint8_t algorithm, uint16_t iterations,
+ const uint8_t* salt_data, size_t salt_length) :
+ algorithm_(algorithm), iterations_(iterations),
+ salt_data_(NULL), salt_length_(salt_length),
+ digest_(DEFAULT_DIGEST_LENGTH), obuf_(Name::MAX_WIRE) {
+ if (algorithm_ != NSEC3_HASH_SHA1) {
+ isc_throw(UnknownNSEC3HashAlgorithm, "Unknown NSEC3 algorithm: " <<
+ static_cast<unsigned int>(algorithm_));
+ }
+
+ if (salt_length > 0) {
+ if (salt_data == NULL) {
+ isc_throw(isc::BadValue, "salt data is NULL");
+ }
+ salt_data_ = static_cast<uint8_t*>(std::malloc(salt_length));
+ if (salt_data_ == NULL) {
+ throw std::bad_alloc();
+ }
+ std::memcpy(salt_data_, salt_data, salt_length);
+ }
+ }
+
+ virtual ~NSEC3HashRFC5155() {
+ std::free(salt_data_);
+ }
+
+ virtual std::string calculate(const Name& name) const;
+ virtual std::string calculate(const LabelSequence& ls) const;
+
+ virtual bool match(const generic::NSEC3& nsec3) const;
+ virtual bool match(const generic::NSEC3PARAM& nsec3param) const;
+ bool match(uint8_t algorithm, uint16_t iterations,
+ const vector<uint8_t>& salt) const;
+
+private:
+ std::string calculateForWiredata(const uint8_t* data, size_t length) const;
+
+ const uint8_t algorithm_;
+ const uint16_t iterations_;
+ uint8_t* salt_data_;
+ const size_t salt_length_;
+
+ // The following members are placeholder of work place and don't hold
+ // any state over multiple calls so can be mutable without breaking
+ // constness.
+ mutable OutputBuffer digest_;
+ mutable vector<uint8_t> vdigest_;
+ mutable OutputBuffer obuf_;
+};
+
+inline void
+iterateSHA1(const uint8_t* input, size_t inlength,
+ const uint8_t* salt, size_t saltlen,
+ OutputBuffer& output)
+{
+ boost::scoped_ptr<Hash> hash(CryptoLink::getCryptoLink().createHash(SHA1));
+ hash->update(input, inlength);
+ hash->update(salt, saltlen); // this works whether saltlen == or > 0
+ hash->final(output, hash->getOutputLength());
+}
+
+string
+NSEC3HashRFC5155::calculateForWiredata(const uint8_t* data,
+ size_t length) const
+{
+ // We first need to normalize the name by converting all upper case
+ // characters in the labels to lower ones.
+
+ uint8_t name_buf[256];
+ assert(length < sizeof (name_buf));
+
+ const uint8_t *p1 = data;
+ uint8_t *p2 = name_buf;
+ while (*p1 != 0) {
+ char len = *p1;
+
+ *p2++ = *p1++;
+ while (len--) {
+ *p2++ = isc::dns::name::internal::maptolower[*p1++];
+ }
+ }
+
+ *p2 = *p1;
+
+ digest_.clear();
+ iterateSHA1(name_buf, length,
+ salt_data_, salt_length_, digest_);
+ const uint8_t* dgst_data = static_cast<const uint8_t*>(digest_.getData());
+ size_t dgst_len = digest_.getLength();
+ for (unsigned int n = 0; n < iterations_; ++n) {
+ digest_.clear();
+ iterateSHA1(dgst_data, dgst_len, salt_data_, salt_length_, digest_);
+ }
+
+ vdigest_.resize(dgst_len);
+ std::memcpy(&vdigest_[0], dgst_data, dgst_len);
+ return (encodeBase32Hex(vdigest_));
+}
+
+string
+NSEC3HashRFC5155::calculate(const Name& name) const {
+ obuf_.clear();
+ name.toWire(obuf_);
+
+ return (calculateForWiredata(static_cast<const uint8_t*>(obuf_.getData()),
+ obuf_.getLength()));
+}
+
+string
+NSEC3HashRFC5155::calculate(const LabelSequence& ls) const {
+ assert(ls.isAbsolute());
+
+ size_t length;
+ const uint8_t* data = ls.getData(&length);
+
+ return (calculateForWiredata(data, length));
+}
+
+bool
+NSEC3HashRFC5155::match(uint8_t algorithm, uint16_t iterations,
+ const vector<uint8_t>& salt) const
+{
+ return (algorithm_ == algorithm && iterations_ == iterations &&
+ salt_length_ == salt.size() &&
+ ((salt_length_ == 0) ||
+ memcmp(salt_data_, &salt[0], salt_length_) == 0));
+}
+
+bool
+NSEC3HashRFC5155::match(const generic::NSEC3& nsec3) const {
+ return (match(nsec3.getHashalg(), nsec3.getIterations(),
+ nsec3.getSalt()));
+}
+
+bool
+NSEC3HashRFC5155::match(const generic::NSEC3PARAM& nsec3param) const {
+ return (match(nsec3param.getHashalg(), nsec3param.getIterations(),
+ nsec3param.getSalt()));
+}
+
+// A static pointer that refers to the currently usable creator.
+// Only get/setNSEC3HashCreator are expected to get access to this variable
+// directly.
+const NSEC3HashCreator* creator;
+
+// The accessor to the current creator. If it's not explicitly set or has
+// been reset from a customized one, the default creator will be used.
+const NSEC3HashCreator*
+getNSEC3HashCreator() {
+ static DefaultNSEC3HashCreator default_creator;
+ if (creator == NULL) {
+ creator = &default_creator;
+ }
+ return (creator);
+}
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3PARAM& param) {
+ return (getNSEC3HashCreator()->create(param));
+}
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3& nsec3) {
+ return (getNSEC3HashCreator()->create(nsec3));
+}
+
+NSEC3Hash*
+NSEC3Hash::create(uint8_t algorithm, uint16_t iterations,
+ const uint8_t* salt_data, size_t salt_length) {
+ return (getNSEC3HashCreator()->create(algorithm, iterations,
+ salt_data, salt_length));
+}
+
+NSEC3Hash*
+DefaultNSEC3HashCreator::create(const generic::NSEC3PARAM& param) const {
+ const vector<uint8_t>& salt = param.getSalt();
+ return (new NSEC3HashRFC5155(param.getHashalg(), param.getIterations(),
+ salt.empty() ? NULL : &salt[0],
+ salt.size()));
+}
+
+NSEC3Hash*
+DefaultNSEC3HashCreator::create(const generic::NSEC3& nsec3) const {
+ const vector<uint8_t>& salt = nsec3.getSalt();
+ return (new NSEC3HashRFC5155(nsec3.getHashalg(), nsec3.getIterations(),
+ salt.empty() ? NULL : &salt[0],
+ salt.size()));
+}
+
+NSEC3Hash*
+DefaultNSEC3HashCreator::create(uint8_t algorithm, uint16_t iterations,
+ const uint8_t* salt_data,
+ size_t salt_length) const
+{
+ return (new NSEC3HashRFC5155(algorithm, iterations,
+ salt_data, salt_length));
+}
+
+void
+setNSEC3HashCreator(const NSEC3HashCreator* new_creator) {
+ creator = new_creator;
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/nsec3hash.h b/src/lib/dns/nsec3hash.h
new file mode 100644
index 0000000..26bb715
--- /dev/null
+++ b/src/lib/dns/nsec3hash.h
@@ -0,0 +1,290 @@
+// Copyright (C) 2012-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/.
+
+#ifndef NSEC3HASH_H
+#define NSEC3HASH_H 1
+
+#include <string>
+#include <vector>
+#include <stdint.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class LabelSequence;
+
+namespace rdata {
+namespace generic {
+class NSEC3;
+class NSEC3PARAM;
+}
+}
+
+/// \brief An exception that is thrown for when an \c NSEC3Hash object is
+/// constructed with an unknown hash algorithm.
+///
+/// A specific exception class is used so that the caller can selectively
+/// catch this exception, e.g., while loading a zone, and handle it
+/// accordingly.
+class UnknownNSEC3HashAlgorithm : public isc::Exception {
+public:
+ UnknownNSEC3HashAlgorithm(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief A calculator of NSEC3 hashes.
+///
+/// This is an abstract base class that defines a simple interface to
+/// calculating NSEC3 hash values as defined in RFC5155.
+///
+/// (Derived classes of) this class is designed to be "stateless" in that it
+/// basically doesn't hold mutable state once constructed, and hash
+/// calculation solely depends on the parameters given on construction and
+/// input to the \c calculate() method. In that sense this could be a
+/// single free function rather than a class, but we decided to provide the
+/// functionality as a class for two reasons: NSEC3 hash calculations would
+/// often take place more than one time in a single query or validation
+/// process, so it would be more efficient if we could hold some internal
+/// resources used for the calculation and reuse it over multiple calls to
+/// \c calculate() (a concrete implementation in this library actually does
+/// this); Second, we may want to customize the hash calculation logic for
+/// testing purposes or for other future extensions. For example, we may
+/// want to use a fake calculator for tests that returns pre-defined hash
+/// values (so a slight change to the test input wouldn't affect the test
+/// result). Using classes from this base would make it possible more
+/// transparently to the application.
+///
+/// A specific derived class instance must be created by the factory method,
+/// \c create().
+///
+/// There can be several ways to extend this class in future. Those include:
+/// - Allow customizing the factory method so the application change the
+/// behavior dynamically.
+/// - Allow to construct the class from a tuple of parameters, that is,
+/// integers for algorithm, iterations and flags, and opaque salt data.
+/// For example, we might want to use that version for validators.
+/// - Allow producing hash value as binary data
+/// - Allow updating NSEC3 parameters of a class object so we can still reuse
+/// the internal resources for different sets of parameters.
+class NSEC3Hash {
+protected:
+ /// \brief The default constructor.
+ ///
+ /// This is defined as protected to prevent this class from being directly
+ /// instantiated even if the class definition is modified (accidentally
+ /// or intentionally) to have no pure virtual methods.
+ NSEC3Hash() {}
+
+public:
+ /// \brief Factory method of NSECHash from NSEC3PARAM RDATA.
+ ///
+ /// The hash algorithm given via \c param must be known to the
+ /// implementation. Otherwise \c UnknownNSEC3HashAlgorithm exception
+ /// will be thrown.
+ ///
+ /// This method creates an \c NSEC3Hash object using \c new. The caller
+ /// is responsible for releasing it with \c delete that is compatible to
+ /// the one used in this library. In practice, the application would
+ /// generally need to store the returned pointer in some form of smart
+ /// pointer; otherwise the resulting code will be quite fragile against
+ /// exceptions (and in this case the application doesn't have to worry
+ /// about explicit \c delete).
+ ///
+ /// \throw UnknownNSEC3HashAlgorithm The specified algorithm in \c param
+ /// is unknown.
+ /// \throw std::bad_alloc Internal resource allocation failure.
+ ///
+ /// \param param NSEC3 parameters used for subsequent calculation.
+ /// \return A pointer to a concrete derived object of \c NSEC3Hash.
+ static NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param);
+
+ /// \brief Factory method of NSECHash from NSEC3 RDATA.
+ ///
+ /// This is similar to the other version, but extracts the parameters
+ /// for hash calculation from an NSEC3 RDATA object.
+ static NSEC3Hash* create(const rdata::generic::NSEC3& nsec3);
+
+ /// \brief Factory method of NSECHash from args.
+ ///
+ /// \param algorithm the NSEC3 algorithm to use; currently only 1
+ /// (SHA-1) is supported
+ /// \param iterations the number of iterations
+ /// \param salt_data the salt data as a byte array
+ /// \param salt_length the length of the salt data
+ static NSEC3Hash* create(uint8_t algorithm, uint16_t iterations,
+ const uint8_t* salt_data, size_t salt_length);
+
+ /// \brief The destructor.
+ virtual ~NSEC3Hash() {}
+
+ /// \brief Calculate the NSEC3 hash (Name variant).
+ ///
+ /// This method calculates the NSEC3 hash value for the given \c name
+ /// with the hash parameters (algorithm, iterations and salt) given at
+ /// construction, and returns the value as a base32hex-encoded string
+ /// (without containing any white spaces). All US-ASCII letters in the
+ /// string will be lower cased.
+ ///
+ /// \param name The domain name for which the hash value is to be
+ /// calculated.
+ /// \return Base32hex-encoded string of the hash value.
+ virtual std::string calculate(const Name& name) const = 0;
+
+ /// \brief Calculate the NSEC3 hash (LabelSequence variant).
+ ///
+ /// This method calculates the NSEC3 hash value for the given
+ /// absolute LabelSequence \c ls with the hash parameters
+ /// (algorithm, iterations and salt) given at construction, and
+ /// returns the value as a base32hex-encoded string (without
+ /// containing any white spaces). All US-ASCII letters in the
+ /// string will be lower cased.
+ ///
+ /// \param ls The absolute label sequence for which the hash value
+ /// is to be calculated.
+ /// \return Base32hex-encoded string of the hash value.
+ virtual std::string calculate(const LabelSequence& ls) const = 0;
+
+ /// \brief Match given NSEC3 parameters with that of the hash.
+ ///
+ /// This method compares NSEC3 parameters used for hash calculation
+ /// in the object with those in the given NSEC3 RDATA, and return
+ /// true iff they completely match. In the current implementation
+ /// only the algorithm, iterations and salt are compared; the flags
+ /// are ignored (as they don't affect hash calculation per RFC5155).
+ ///
+ /// \throw None
+ ///
+ /// \param nsec3 An NSEC3 RDATA object whose hash parameters are to be
+ /// matched
+ /// \return true If the given parameters match the local ones; false
+ /// otherwise.
+ virtual bool match(const rdata::generic::NSEC3& nsec3) const = 0;
+
+ /// \brief Match given NSEC3PARAM parameters with that of the hash.
+ ///
+ /// This is similar to the other version, but extracts the parameters
+ /// to compare from an NSEC3PARAM RDATA object.
+ virtual bool match(const rdata::generic::NSEC3PARAM& nsec3param) const = 0;
+};
+
+/// \brief Factory class of NSEC3Hash.
+///
+/// This class is an abstract base class that provides the creation interfaces
+/// of \c NSEC3Hash objects. By defining a specific derived class of the
+/// creator, normally with a different specific class of \c NSEC3Hash,
+/// the application can use a customized implementation of \c NSEC3Hash
+/// without changing the library itself. The intended primary application of
+/// such customization is tests (it would be convenient for a test to produce
+/// a faked hash value regardless of the input so it doesn't have to identify
+/// a specific input value to produce a particular hash). Another possibility
+/// would be an experimental extension for a newer hash algorithm or
+/// implementation.
+///
+/// The three main methods named \c create() correspond to the static factory
+/// methods of \c NSEC3Hash of the same name.
+///
+/// By default, the library uses the \c DefaultNSEC3HashCreator creator.
+/// The \c setNSEC3HashCreator() function can be used to replace it with a
+/// user defined version. For such customization purposes as implementing
+/// experimental new hash algorithms, the application may internally want to
+/// use the \c DefaultNSEC3HashCreator in general cases while creating a
+/// customized type of \c NSEC3Hash object for that particular hash algorithm.
+///
+/// The creator objects are generally expected to be stateless; they will
+/// only encapsulate the factory logic. The \c create() methods are declared
+/// as const member functions for this reason. But if we see the need for
+/// having a customized creator that benefits from its own state in future,
+/// this condition can be loosened.
+class NSEC3HashCreator {
+protected:
+ /// \brief The default constructor.
+ ///
+ /// Make very sure this isn't directly instantiated by making it protected
+ /// even if this class is modified to lose all pure virtual methods.
+ NSEC3HashCreator() {}
+
+public:
+ /// \brief The destructor.
+ ///
+ /// This does nothing; defined only for allowing derived classes to
+ /// specialize its behavior.
+ virtual ~NSEC3HashCreator() {}
+
+ /// \brief Factory method of NSECHash from NSEC3PARAM RDATA.
+ ///
+ /// See
+ /// <code>NSEC3Hash::create(const rdata::generic::NSEC3PARAM& param)</code>
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& nsec3param)
+ const = 0;
+
+ /// \brief Factory method of NSECHash from NSEC3 RDATA.
+ ///
+ /// See
+ /// <code>NSEC3Hash::create(const rdata::generic::NSEC3& param)</code>
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3)
+ const = 0;
+
+ /// \brief Factory method of NSECHash from args.
+ ///
+ /// See
+ /// <code>NSEC3Hash::create(uint8_t algorithm, uint16_t iterations,
+ /// const uint8_t* salt_data,
+ /// size_t salt_length)</code>
+ ///
+ /// \param algorithm the NSEC3 algorithm to use; currently only 1
+ /// (SHA-1) is supported
+ /// \param iterations the number of iterations
+ /// \param salt_data the salt data as a byte array
+ /// \param salt_length the length of the salt data
+ virtual NSEC3Hash* create(uint8_t algorithm, uint16_t iterations,
+ const uint8_t* salt_data, size_t salt_length)
+ const = 0;
+};
+
+/// \brief The default NSEC3Hash creator.
+///
+/// This derived class implements the \c NSEC3HashCreator interfaces for
+/// the standard NSEC3 hash calculator as defined in RFC5155. The library
+/// will use this creator by default, so normal applications don't have to
+/// be aware of this class at all. This class is publicly visible for the
+/// convenience of special applications that want to customize the creator
+/// behavior for a particular type of parameters while preserving the default
+/// behavior for others.
+class DefaultNSEC3HashCreator : public NSEC3HashCreator {
+public:
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param) const;
+ virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3) const;
+ virtual NSEC3Hash* create(uint8_t algorithm, uint16_t iterations,
+ const uint8_t* salt_data,
+ size_t salt_length) const;
+};
+
+/// \brief The registrar of \c NSEC3HashCreator.
+///
+/// This function sets or resets the system-wide \c NSEC3HashCreator that
+/// is used by \c NSEC3Hash::create().
+///
+/// If \c new_creator is non NULL, the given creator object will replace
+/// any existing creator. If it's NULL, the default builtin creator will be
+/// used again from that point.
+///
+/// When \c new_creator is non NULL, the caller is responsible for keeping
+/// the referenced object valid as long as it can be used via
+/// \c NSEC3Hash::create().
+///
+/// \exception None
+/// \param new_creator A pointer to the new creator object or NULL.
+void setNSEC3HashCreator(const NSEC3HashCreator* new_creator);
+
+}
+}
+#endif // NSEC3HASH_H
+
+// Local Variables:
+// mode: c++
+// End:
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..67dd579
--- /dev/null
+++ b/src/lib/dns/opcode.h
@@ -0,0 +1,282 @@
+/*
+ * 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 <stdint.h>
+
+#include <ostream>
+
+#ifndef OPCODE_H
+#define OPCODE_H 1
+
+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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/qid_gen.cc b/src/lib/dns/qid_gen.cc
new file mode 100644
index 0000000..dad2b6f
--- /dev/null
+++ b/src/lib/dns/qid_gen.cc
@@ -0,0 +1,42 @@
+// Copyright (C) 2011-2021 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/.
+
+// qid_gen defines a generator for query id's
+//
+// We probably want to merge this with the weighted random in the nsas
+// (and other parts where we need randomness, perhaps another thing
+// for a general libutil?)
+
+#include <config.h>
+
+#include <cryptolink/crypto_rng.h>
+#include <dns/qid_gen.h>
+#include <cstring>
+
+namespace isc {
+namespace dns {
+
+QidGenerator qid_generator_instance;
+
+QidGenerator&
+QidGenerator::getInstance() {
+ return (qid_generator_instance);
+}
+
+QidGenerator::QidGenerator()
+{
+}
+
+uint16_t
+QidGenerator::generateQid() {
+ uint16_t val;
+ std::vector<uint8_t> rnd = isc::cryptolink::random(sizeof(uint16_t));
+ memmove(&val, &rnd[0], sizeof(uint16_t));
+ return (val);
+}
+
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/qid_gen.h b/src/lib/dns/qid_gen.h
new file mode 100644
index 0000000..732dd9a
--- /dev/null
+++ b/src/lib/dns/qid_gen.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2011-2021 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/.
+
+// qid_gen defines a generator for query id's
+//
+// We probably want to merge this with the weighted random in the nsas
+// (and other parts where we need randomness, perhaps another thing
+// for a general libutil?)
+
+#ifndef QID_GEN_H
+#define QID_GEN_H
+
+#include <cryptolink/crypto_rng.h>
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+
+/// This class generates Qids for outgoing queries
+///
+/// It is implemented as a singleton; the public way to access it
+/// is to call getInstance()->generateQid().
+///
+/// It automatically seeds it with the current time when it is first
+/// used.
+class QidGenerator {
+public:
+ /// \brief Returns the singleton instance of the QidGenerator
+ ///
+ /// Returns a reference to the singleton instance of the generator
+ static QidGenerator& getInstance();
+
+ /// \brief Default constructor
+ ///
+ /// It is recommended that getInstance is used rather than creating
+ /// separate instances of this class.
+ ///
+ /// The constructor automatically seeds the generator with the
+ /// current time.
+ QidGenerator();
+
+ /// Generate a Qid
+ ///
+ /// \return A random Qid
+ uint16_t generateQid();
+};
+
+} // namespace dns
+} // namespace isc
+
+#endif // QID_GEN_H
diff --git a/src/lib/dns/question.cc b/src/lib/dns/question.cc
new file mode 100644
index 0000000..f0170d8
--- /dev/null
+++ b/src/lib/dns/question.cc
@@ -0,0 +1,81 @@
+// 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 <iostream>
+#include <string>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/question.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace isc::util;
+
+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);
+}
+
+unsigned int
+Question::toWire(OutputBuffer& buffer) const {
+ name_.toWire(buffer);
+ rrtype_.toWire(buffer);
+ rrclass_.toWire(buffer); // number of "entries", which is always 1
+
+ return (1);
+}
+
+unsigned int
+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..05f52ee
--- /dev/null
+++ b/src/lib/dns/question.h
@@ -0,0 +1,291 @@
+// 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/.
+
+#ifndef QUESTION_H
+#define QUESTION_H 1
+
+#include <iostream>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace util {
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+class AbstractMessageRenderer;
+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
+ unsigned int 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
+ unsigned int 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
+
+// Local Variables:
+// mode: c++
+// End:
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..eb192cd
--- /dev/null
+++ b/src/lib/dns/rcode.h
@@ -0,0 +1,338 @@
+/*
+ * 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 <stdint.h>
+
+#include <ostream>
+
+#ifndef RCODE_H
+#define RCODE_H 1
+
+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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
new file mode 100644
index 0000000..357ccc7
--- /dev/null
+++ b/src/lib/dns/rdata.cc
@@ -0,0 +1,408 @@
+// 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 <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.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 <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 std;
+using boost::lexical_cast;
+using namespace isc::util;
+
+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,
+ isc::util::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 == NULL) {
+ callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+ "createRdata from text failed: " + string(reason));
+ return;
+ }
+
+ switch (token->getType()) {
+ case MasterToken::STRING:
+ case MasterToken::QSTRING:
+ callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+ "createRdata from text failed near '" +
+ token->getString() + "': " + string(reason));
+ break;
+ case MasterToken::ERROR:
+ callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+ "createRdata from text failed: " +
+ token->getErrorText());
+ break;
+ default:
+ // 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, NULL, ex.what());
+ }
+ // Other exceptions mean a serious implementation bug or fatal system
+ // error; it doesn't make sense to catch and try to recover from them
+ // here. Just propagate.
+
+ // Consume to end of line / file.
+ // Call callback via fromtextError once if there was an error.
+ do {
+ const MasterToken& token = lexer.getNextToken();
+ switch (token.getType()) {
+ case MasterToken::END_OF_LINE:
+ return (rdata);
+ case MasterToken::END_OF_FILE:
+ callbacks.warning(lexer.getSourceName(), lexer.getSourceLine(),
+ "file does not end with newline");
+ return (rdata);
+ default:
+ rdata.reset(); // we'll return NULL
+ fromtextError(error_issued, lexer, callbacks, &token,
+ "extra input text");
+ // Continue until we see EOL or EOF
+ }
+ } while (true);
+
+ // We shouldn't reach here
+ assert(false);
+ return (RdataPtr()); // add explicit return to silence some compilers
+}
+
+int
+compareNames(const Name& n1, const Name& n2) {
+ size_t len1 = n1.getLength();
+ 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(isc::util::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_ = new GenericImpl(data);
+}
+
+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 {
+ isc::util::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 (new GenericImpl(data));
+}
+
+Generic::Generic(const std::string& rdata_string) :
+ impl_(NULL)
+{
+ // 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 GenericImpl that constructFromLexer() returns.
+ std::unique_ptr<GenericImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(rdata_string);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(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());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+Generic::Generic(MasterLexer& lexer, const Name*,
+ MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+Generic::~Generic() {
+ delete impl_;
+}
+
+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);
+ }
+
+ GenericImpl* newimpl = new GenericImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ 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(isc::util::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..a6515ef
--- /dev/null
+++ b/src/lib/dns/rdata.h
@@ -0,0 +1,582 @@
+// 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/.
+
+#ifndef RDATA_H
+#define RDATA_H 1
+
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+
+#include <dns/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+class InputBuffer;
+class OutputBuffer;
+}
+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 InvalidBufferPosition
+ /// 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:
+ GenericImpl* constructFromLexer(MasterLexer& lexer);
+
+ 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
new file mode 100644
index 0000000..a80d742
--- /dev/null
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -0,0 +1,567 @@
+// 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 <sstream>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigerror.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+// 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
+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 (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) : impl_(NULL) {
+ // 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 TSIGImpl that constructFromLexer() returns.
+ std::unique_ptr<TSIGImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(tsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
+
+ 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());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \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) :
+ impl_(NULL)
+{
+ 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_ = 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) :
+ impl_(NULL)
+{
+ // 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 != NULL) || (mac_size > 0 && mac == NULL)) {
+ isc_throw(InvalidParameter, "TSIG MAC size and data inconsistent");
+ }
+ if ((other_len == 0 && other_data != NULL) ||
+ (other_len > 0 && other_data == NULL)) {
+ 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_ = 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);
+ }
+
+ TSIGImpl* newimpl = new TSIGImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TSIG::~TSIG() {
+ delete impl_;
+}
+
+/// \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 (NULL);
+ }
+}
+
+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 (NULL);
+ }
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/any_255/tsig_250.h b/src/lib/dns/rdata/any_255/tsig_250.h
new file mode 100644
index 0000000..63c2234
--- /dev/null
+++ b/src/lib/dns/rdata/any_255/tsig_250.h
@@ -0,0 +1,148 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+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:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \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:
+ TSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ TSIGImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/ch_3/a_1.cc b/src/lib/dns/rdata/ch_3/a_1.cc
new file mode 100644
index 0000000..cd4c824
--- /dev/null
+++ b/src/lib/dns/rdata/ch_3/a_1.cc
@@ -0,0 +1,64 @@
+// 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 <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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 {
+ return (0); // dummy. TBD
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/ch_3/a_1.h b/src/lib/dns/rdata/ch_3/a_1.h
new file mode 100644
index 0000000..6f319b9
--- /dev/null
+++ b/src/lib/dns/rdata/ch_3/a_1.h
@@ -0,0 +1,32 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class A : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/afsdb_18.cc b/src/lib/dns/rdata/generic/afsdb_18.cc
new file mode 100644
index 0000000..5e82b03
--- /dev/null
+++ b/src/lib/dns/rdata/generic/afsdb_18.cc
@@ -0,0 +1,201 @@
+// 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 <string>
+#include <sstream>
+
+#include <util/buffer.h>
+#include <util/strutil.h>
+
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// \c afsdb_str must be formatted as follows:
+/// \code <subtype> <server name>
+/// \endcode
+/// where server name field must represent a valid domain name.
+///
+/// An example of valid string is:
+/// \code "1 server.example.com." \endcode
+///
+/// <b>Exceptions</b>
+///
+/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \exception std::bad_alloc Memory allocation fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the string is invalid.
+AFSDB::AFSDB(const std::string& afsdb_str) :
+ subtype_(0), server_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(afsdb_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ createFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for AFSDB: "
+ << afsdb_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct AFSDB from '" <<
+ afsdb_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an AFSDB RDATA. The SERVER 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.
+///
+/// The SUBTYPE field must be a valid decimal representation of an
+/// unsigned 16-bit integer.
+///
+/// \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 SERVER when it
+/// is non-absolute.
+AFSDB::AFSDB(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ subtype_(0), server_(".")
+{
+ createFromLexer(lexer, origin);
+}
+
+void
+AFSDB::createFromLexer(MasterLexer& lexer, const Name* origin)
+{
+ const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid AFSDB subtype: " << num);
+ }
+ subtype_ = static_cast<uint16_t>(num);
+
+ server_ = createNameFromLexer(lexer, origin);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \exception std::bad_alloc Memory allocation fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+AFSDB::AFSDB(InputBuffer& buffer, size_t) :
+ subtype_(buffer.readUint16()), server_(buffer)
+{}
+
+/// \brief Copy constructor.
+///
+/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+AFSDB::AFSDB(const AFSDB& other) :
+ Rdata(), subtype_(other.subtype_), server_(other.server_)
+{}
+
+AFSDB&
+AFSDB::operator=(const AFSDB& source) {
+ subtype_ = source.subtype_;
+ server_ = source.server_;
+
+ return (*this);
+}
+
+/// \brief Convert the \c AFSDB to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c AFSDB(const std::string&))).
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c AFSDB object.
+string
+AFSDB::toText() const {
+ return (lexical_cast<string>(subtype_) + " " + server_.toText());
+}
+
+/// \brief Render the \c AFSDB in the wire format without name compression.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+AFSDB::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(subtype_);
+ server_.toWire(buffer);
+}
+
+/// \brief Render the \c AFSDB in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, TYPE AFSDB is not "well-known", the server
+/// field (domain name) will not be compressed.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+AFSDB::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(subtype_);
+ renderer.writeName(server_, false);
+}
+
+/// \brief Compare two instances of \c AFSDB RDATA.
+///
+/// See documentation in \c Rdata.
+int
+AFSDB::compare(const Rdata& other) const {
+ const AFSDB& other_afsdb = dynamic_cast<const AFSDB&>(other);
+ if (subtype_ < other_afsdb.subtype_) {
+ return (-1);
+ } else if (subtype_ > other_afsdb.subtype_) {
+ return (1);
+ }
+
+ return (compareNames(server_, other_afsdb.server_));
+}
+
+const Name&
+AFSDB::getServer() const {
+ return (server_);
+}
+
+uint16_t
+AFSDB::getSubtype() const {
+ return (subtype_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/afsdb_18.h b/src/lib/dns/rdata/generic/afsdb_18.h
new file mode 100644
index 0000000..2fd8fba
--- /dev/null
+++ b/src/lib/dns/rdata/generic/afsdb_18.h
@@ -0,0 +1,68 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::AFSDB class represents the AFSDB RDATA as defined %in
+/// RFC1183.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// AFSDB RDATA.
+class AFSDB : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \brief Assignment operator.
+ ///
+ /// This method never throws an exception.
+ AFSDB& operator=(const AFSDB& source);
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return the value of the server field.
+ ///
+ /// \return A reference to a \c Name class object corresponding to the
+ /// internal server name.
+ ///
+ /// This method never throws an exception.
+ const Name& getServer() const;
+
+ /// \brief Return the value of the subtype field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getSubtype() const;
+
+private:
+ void createFromLexer(MasterLexer& lexer, const Name* origin);
+
+ uint16_t subtype_;
+ Name server_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/caa_257.cc b/src/lib/dns/rdata/generic/caa_257.cc
new file mode 100644
index 0000000..7f8b455
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.cc
@@ -0,0 +1,298 @@
+// Copyright (C) 2014-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 <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/char_string.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct CAAImpl {
+ // straightforward representation of CAA RDATA fields
+ CAAImpl(uint8_t flags, const std::string& tag,
+ const detail::CharStringData& value) :
+ flags_(flags),
+ tag_(tag),
+ value_(value)
+ {
+ if ((sizeof(flags) + 1 + tag_.size() + value_.size()) > 65535) {
+ isc_throw(InvalidRdataLength,
+ "CAA Value field is too large: " << value_.size());
+ }
+ }
+
+ uint8_t flags_;
+ const std::string tag_;
+ const detail::CharStringData value_;
+};
+
+// helper function for string and lexer constructors
+CAAImpl*
+CAA::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t flags =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (flags > 255) {
+ isc_throw(InvalidRdataText,
+ "CAA flags field out of range");
+ }
+
+ // Tag field must not be empty.
+ const std::string tag =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ if (tag.empty()) {
+ isc_throw(InvalidRdataText, "CAA tag field is empty");
+ } else if (tag.size() > 255) {
+ isc_throw(InvalidRdataText,
+ "CAA tag field is too large: " << tag.size());
+ }
+
+ // Value field may be empty.
+ detail::CharStringData value;
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING, true);
+ if ((token.getType() != MasterToken::END_OF_FILE) &&
+ (token.getType() != MasterToken::END_OF_LINE))
+ {
+ detail::stringToCharStringData(token.getStringRegion(), value);
+ }
+
+ return (new CAAImpl(flags, tag, value));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid CAA 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 Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, incorrect, or empty.
+///
+/// \param caa_str A string containing the RDATA to be created
+CAA::CAA(const string& caa_str) :
+ impl_(NULL)
+{
+ // 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 CAAImpl that constructFromLexer() returns.
+ std::unique_ptr<CAAImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(caa_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for CAA: "
+ << caa_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct CAA from '" <<
+ caa_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an CAA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing
+/// field.
+/// \throw InvalidRdataText Fields are out of their valid ranges,
+/// incorrect, or empty.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+CAA::CAA(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid CAA RDATA.
+///
+/// The Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+CAA::CAA(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "CAA record too short");
+ }
+
+ const uint8_t flags = buffer.readUint8();
+ const uint8_t tag_length = buffer.readUint8();
+ rdata_len -= 2;
+ if (tag_length == 0) {
+ isc_throw(InvalidRdataText, "CAA tag field is empty");
+ }
+
+ if (rdata_len < tag_length) {
+ isc_throw(InvalidRdataLength,
+ "RDATA is too short for CAA tag field");
+ }
+
+ std::vector<uint8_t> tag_vec(tag_length);
+ buffer.readData(&tag_vec[0], tag_length);
+ std::string tag(tag_vec.begin(), tag_vec.end());
+ rdata_len -= tag_length;
+
+ detail::CharStringData value;
+ value.resize(rdata_len);
+ if (rdata_len > 0) {
+ buffer.readData(&value[0], rdata_len);
+ }
+
+ impl_ = new CAAImpl(flags, tag, value);
+}
+
+CAA::CAA(uint8_t flags, const std::string& tag, const std::string& value) :
+ impl_(NULL)
+{
+ if (tag.empty()) {
+ isc_throw(isc::InvalidParameter,
+ "CAA tag field is empty");
+ } else if (tag.size() > 255) {
+ isc_throw(isc::InvalidParameter,
+ "CAA tag field is too large: " << tag.size());
+ }
+
+ MasterToken::StringRegion region;
+ region.beg = &value[0]; // note std ensures this works even if str is empty
+ region.len = value.size();
+
+ detail::CharStringData value_vec;
+ detail::stringToCharStringData(region, value_vec);
+
+ impl_ = new CAAImpl(flags, tag, value_vec);
+}
+
+CAA::CAA(const CAA& other) :
+ Rdata(), impl_(new CAAImpl(*other.impl_))
+{}
+
+CAA&
+CAA::operator=(const CAA& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ CAAImpl* newimpl = new CAAImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+CAA::~CAA() {
+ delete impl_;
+}
+
+void
+CAA::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(impl_->flags_);
+
+ // The constructors must ensure that the tag field is not empty.
+ assert(!impl_->tag_.empty());
+ buffer.writeUint8(impl_->tag_.size());
+ buffer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+ if (!impl_->value_.empty()) {
+ buffer.writeData(&impl_->value_[0],
+ impl_->value_.size());
+ }
+}
+
+void
+CAA::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(impl_->flags_);
+
+ // The constructors must ensure that the tag field is not empty.
+ assert(!impl_->tag_.empty());
+ renderer.writeUint8(impl_->tag_.size());
+ renderer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+ if (!impl_->value_.empty()) {
+ renderer.writeData(&impl_->value_[0],
+ impl_->value_.size());
+ }
+}
+
+std::string
+CAA::toText() const {
+ std::string result;
+
+ result = lexical_cast<std::string>(static_cast<int>(impl_->flags_));
+ result += " " + impl_->tag_;
+ result += " \"" + detail::charStringDataToString(impl_->value_) + "\"";
+
+ return (result);
+}
+
+int
+CAA::compare(const Rdata& other) const {
+ const CAA& other_caa = dynamic_cast<const CAA&>(other);
+
+ if (impl_->flags_ < other_caa.impl_->flags_) {
+ return (-1);
+ } else if (impl_->flags_ > other_caa.impl_->flags_) {
+ return (1);
+ }
+
+ // Do a case-insensitive compare of the tag strings.
+ const int result = boost::ilexicographical_compare
+ <std::string, std::string>(impl_->tag_, other_caa.impl_->tag_);
+ if (result != 0) {
+ return (result);
+ }
+
+ return (detail::compareCharStringDatas(impl_->value_,
+ other_caa.impl_->value_));
+}
+
+uint8_t
+CAA::getFlags() const {
+ return (impl_->flags_);
+}
+
+const std::string&
+CAA::getTag() const {
+ return (impl_->tag_);
+}
+
+const std::vector<uint8_t>&
+CAA::getValue() const {
+ return (impl_->value_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/caa_257.h b/src/lib/dns/rdata/generic/caa_257.h
new file mode 100644
index 0000000..0e81e71
--- /dev/null
+++ b/src/lib/dns/rdata/generic/caa_257.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2014-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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <vector>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct CAAImpl;
+
+class CAA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ CAA(uint8_t flags, const std::string& tag, const std::string& value);
+ CAA& operator=(const CAA& source);
+ ~CAA();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return the Flags field of the CAA RDATA.
+ uint8_t getFlags() const;
+
+ /// \brief Return the Tag field of the CAA RDATA.
+ const std::string& getTag() const;
+
+ /// \brief Return the Value field of the CAA RDATA.
+ ///
+ /// Note: The const reference which is returned is valid only during
+ /// the lifetime of this \c generic::CAA object. It should not be
+ /// used afterwards.
+ const std::vector<uint8_t>& getValue() const;
+
+private:
+ CAAImpl* constructFromLexer(MasterLexer& lexer);
+
+ CAAImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/cname_5.cc b/src/lib/dns/rdata/generic/cname_5.cc
new file mode 100644
index 0000000..71cb4dc
--- /dev/null
+++ b/src/lib/dns/rdata/generic/cname_5.cc
@@ -0,0 +1,125 @@
+// 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid CNAME 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 CNAME 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.
+CNAME::CNAME(const std::string& namestr) :
+ // Fill in dummy name and replace it soon below.
+ cname_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(namestr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ cname_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for CNAME: "
+ << namestr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct CNAME from '" <<
+ namestr << "': " << ex.what());
+ }
+}
+
+CNAME::CNAME(InputBuffer& buffer, size_t) :
+ Rdata(), cname_(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 CNAME RDATA. The CNAME 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 CNAME when it
+/// is non-absolute.
+CNAME::CNAME(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ cname_(createNameFromLexer(lexer, origin))
+{}
+
+CNAME::CNAME(const CNAME& other) :
+ Rdata(), cname_(other.cname_)
+{}
+
+CNAME::CNAME(const Name& cname) :
+ cname_(cname)
+{}
+
+void
+CNAME::toWire(OutputBuffer& buffer) const {
+ cname_.toWire(buffer);
+}
+
+void
+CNAME::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(cname_);
+}
+
+string
+CNAME::toText() const {
+ return (cname_.toText());
+}
+
+int
+CNAME::compare(const Rdata& other) const {
+ const CNAME& other_cname = dynamic_cast<const CNAME&>(other);
+
+ return (compareNames(cname_, other_cname.cname_));
+}
+
+const Name&
+CNAME::getCname() const {
+ return (cname_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/cname_5.h b/src/lib/dns/rdata/generic/cname_5.h
new file mode 100644
index 0000000..2149340
--- /dev/null
+++ b/src/lib/dns/rdata/generic/cname_5.h
@@ -0,0 +1,39 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class CNAME : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ // CNAME specific methods
+ CNAME(const Name& cname);
+ const Name& getCname() const;
+private:
+ Name cname_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
new file mode 100644
index 0000000..8eee8c0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -0,0 +1,264 @@
+// Copyright (C) 2012-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 <exceptions/exceptions.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#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);
+ assert(n >= 3);
+ 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, so the following assertion is unnecessary. But we
+ // assert it anyway. 'n' is an unsigned type (size_t) and
+ // can underflow.
+ assert(n >= 3);
+ // '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;
+ for (CharString::const_iterator it = char_string.begin() + 1;
+ it != char_string.end(); ++it) {
+ 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 (CharString::const_iterator it = char_string.begin();
+ it != char_string.end(); ++it) {
+ 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/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
new file mode 100644
index 0000000..2ad12fb
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -0,0 +1,140 @@
+// Copyright (C) 2012-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/.
+
+#ifndef DNS_RDATA_CHARSTRING_H
+#define DNS_RDATA_CHARSTRING_H 1
+
+#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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/ds_like.h b/src/lib/dns/rdata/generic/detail/ds_like.h
new file mode 100644
index 0000000..4d8c2ea
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/ds_like.h
@@ -0,0 +1,277 @@
+// 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/.
+
+#ifndef DS_LIKE_H
+#define DS_LIKE_H 1
+
+#include <stdint.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief \c rdata::DSLikeImpl class represents the DS-like RDATA for DS
+/// and DLV types.
+///
+/// This class implements the basic interfaces inherited by the DS and DLV
+/// classes from the abstract \c rdata::Rdata class, and provides trivial
+/// accessors to DS-like RDATA.
+template <class Type, uint16_t typeCode> class DSLikeImpl {
+ // Common sequence of toWire() operations used for the two versions of
+ // toWire().
+ template <typename Output>
+ void
+ toWireCommon(Output& output) const {
+ output.writeUint16(tag_);
+ output.writeUint8(algorithm_);
+ output.writeUint8(digest_type_);
+ output.writeData(&digest_[0], digest_.size());
+ }
+
+public:
+ /// \brief Constructor from string.
+ ///
+ /// The given string must represent a valid DS-like 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 tag field must be a valid decimal representation of an
+ /// unsigned 16-bit integer. The protocol and algorithm fields must
+ /// be valid decimal representations of unsigned 8-bit integers
+ /// respectively. The digest field may contain whitespace.
+ ///
+ /// \throw InvalidRdataText if any fields are out of their valid range.
+ ///
+ /// \param ds_str A string containing the RDATA to be created
+ DSLikeImpl(const std::string& ds_str) {
+ try {
+ std::istringstream ss(ds_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ constructFromLexer(lexer);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for " << RRType(typeCode) << ": "
+ << ds_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct " << RRType(typeCode) << " from '" <<
+ ds_str << "': " << ex.what());
+ }
+ }
+
+ /// \brief Constructor with a context of MasterLexer.
+ ///
+ /// The \c lexer should point to the beginning of valid textual
+ /// representation of a DS-like RDATA.
+ ///
+ /// The tag field must be a valid decimal representation of an
+ /// unsigned 16-bit integer. The protocol and algorithm fields must
+ /// be valid decimal representations of unsigned 8-bit integers
+ /// respectively.
+ ///
+ /// \throw MasterLexer::LexerError General parsing error such as
+ /// missing field.
+ /// \throw InvalidRdataText if any fields are out of their valid range.
+ ///
+ /// \param lexer A \c MasterLexer object parsing a master file for the
+ /// RDATA to be created
+ DSLikeImpl(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&)
+ {
+ constructFromLexer(lexer);
+ }
+
+private:
+ void constructFromLexer(MasterLexer& lexer) {
+ const uint32_t tag =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (tag > 0xffff) {
+ isc_throw(InvalidRdataText,
+ "Invalid " << RRType(typeCode) << " tag: " << tag);
+ }
+
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 0xff) {
+ isc_throw(InvalidRdataText,
+ "Invalid " << RRType(typeCode) << " algorithm: "
+ << algorithm);
+ }
+
+ const uint32_t digest_type =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (digest_type > 0xff) {
+ isc_throw(InvalidRdataText,
+ "Invalid " << RRType(typeCode) << " digest type: "
+ << digest_type);
+ }
+
+ std::string digest;
+ while (true) {
+ const MasterToken& token = lexer.getNextToken();
+ if (token.getType() != MasterToken::STRING) {
+ break;
+ }
+ digest.append(token.getString());
+ }
+
+ lexer.ungetToken();
+
+ if (digest.size() == 0) {
+ isc_throw(InvalidRdataText,
+ "Missing " << RRType(typeCode) << " digest");
+ }
+
+ tag_ = tag;
+ algorithm_ = algorithm;
+ digest_type_ = digest_type;
+ decodeHex(digest, digest_);
+ }
+
+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 the input data is too short for the
+ /// type.
+ DSLikeImpl(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 4) {
+ isc_throw(InvalidRdataLength, RRType(typeCode) << " too short");
+ }
+
+ tag_ = buffer.readUint16();
+ algorithm_ = buffer.readUint8();
+ digest_type_ = buffer.readUint8();
+
+ rdata_len -= 4;
+ digest_.resize(rdata_len);
+ buffer.readData(&digest_[0], rdata_len);
+ }
+
+ /// \brief The copy constructor.
+ ///
+ /// Trivial for now, we could've used the default one.
+ DSLikeImpl(const DSLikeImpl& source) :
+ tag_(source.tag_),
+ algorithm_(source.algorithm_),
+ digest_type_(source.digest_type_),
+ digest_(source.digest_)
+ {}
+
+ /// \brief Convert the DS-like data to a string.
+ ///
+ /// \return A \c string object that represents the DS-like data.
+ std::string
+ toText() const {
+ using namespace boost;
+ return (lexical_cast<string>(static_cast<int>(tag_)) +
+ " " + lexical_cast<string>(static_cast<int>(algorithm_)) +
+ " " + lexical_cast<string>(static_cast<int>(digest_type_)) +
+ " " + encodeHex(digest_));
+ }
+
+ /// \brief Render the DS-like data in the wire format to an OutputBuffer
+ /// object.
+ ///
+ /// \param buffer An output buffer to store the wire data.
+ void
+ toWire(OutputBuffer& buffer) const {
+ toWireCommon(buffer);
+ }
+
+ /// \brief Render the DS-like data in the wire format to an
+ /// AbstractMessageRenderer object.
+ ///
+ /// \param renderer A renderer object to send the wire data to.
+ void
+ toWire(AbstractMessageRenderer& renderer) const {
+ toWireCommon(renderer);
+ }
+
+ /// \brief Compare two instances of DS-like RDATA.
+ ///
+ /// It is up to the caller to make sure that \c other is an object of the
+ /// same \c DSLikeImpl 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 DSLikeImpl& other_ds) const {
+ if (tag_ != other_ds.tag_) {
+ return (tag_ < other_ds.tag_ ? -1 : 1);
+ }
+ if (algorithm_ != other_ds.algorithm_) {
+ return (algorithm_ < other_ds.algorithm_ ? -1 : 1);
+ }
+ if (digest_type_ != other_ds.digest_type_) {
+ return (digest_type_ < other_ds.digest_type_ ? -1 : 1);
+ }
+
+ size_t this_len = digest_.size();
+ size_t other_len = other_ds.digest_.size();
+ size_t cmplen = min(this_len, other_len);
+ int cmp = memcmp(&digest_[0], &other_ds.digest_[0], cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len)
+ ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+ }
+
+ /// \brief Accessors
+ uint16_t
+ getTag() const {
+ return (tag_);
+ }
+
+private:
+ // straightforward representation of DS RDATA fields
+ uint16_t tag_;
+ uint8_t algorithm_;
+ uint8_t digest_type_;
+ std::vector<uint8_t> digest_;
+};
+
+}
+}
+}
+}
+}
+#endif // DS_LIKE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/lexer_util.h b/src/lib/dns/rdata/generic/detail/lexer_util.h
new file mode 100644
index 0000000..29b6c31
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/lexer_util.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2013-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/.
+
+#ifndef DNS_RDATA_LEXER_UTIL_H
+#define DNS_RDATA_LEXER_UTIL_H 1
+
+#include <dns/name.h>
+#include <dns/master_lexer.h>
+
+/// \file lexer_util.h
+/// \brief Utilities for extracting RDATA fields from lexer.
+///
+/// This file intends to define convenient small routines that can be
+/// commonly used in the RDATA implementation to build RDATA fields from
+/// a \c MasterLexer.
+
+namespace isc {
+namespace dns {
+namespace rdata {
+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));
+}
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif // DNS_RDATA_LEXER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
new file mode 100644
index 0000000..efe488a
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2012-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 <exceptions/exceptions.h>
+
+#include <util/encode/hex.h>
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <sstream>
+#include <vector>
+#include <stdint.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec3 {
+
+ParseNSEC3ParamResult
+parseNSEC3ParamFromLexer(const char* const rrtype_name,
+ MasterLexer& lexer, vector<uint8_t>& salt)
+{
+ const uint32_t hashalg =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (hashalg > 0xff) {
+ isc_throw(InvalidRdataText, rrtype_name <<
+ " hash algorithm out of range: " << hashalg);
+ }
+
+ const uint32_t flags =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (flags > 0xff) {
+ isc_throw(InvalidRdataText, rrtype_name << " flags out of range: " <<
+ flags);
+ }
+
+ const uint32_t iterations =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (iterations > 0xffff) {
+ isc_throw(InvalidRdataText, rrtype_name <<
+ " iterations out of range: " << iterations);
+ }
+
+ const string salthex =
+ lexer.getNextToken(MasterToken::STRING).getString();
+
+ // Salt is up to 255 bytes, and space is not allowed in the HEX encoding,
+ // so the encoded string cannot be longer than the double of max length
+ // of the actual salt.
+ if (salthex.size() > 255 * 2) {
+ isc_throw(InvalidRdataText, rrtype_name << " salt is too long: "
+ << salthex.size() << " (encoded) bytes");
+ }
+ if (salthex != "-") { // "-" means a 0-length salt
+ decodeHex(salthex, salt);
+ }
+
+ return (ParseNSEC3ParamResult(hashalg, flags, iterations));
+}
+
+ParseNSEC3ParamResult
+parseNSEC3ParamWire(const char* const rrtype_name,
+ InputBuffer& buffer,
+ size_t& rdata_len, std::vector<uint8_t>& salt)
+{
+ // NSEC3/NSEC3PARAM RR must have at least 5 octets:
+ // hash algorithm(1), flags(1), iteration(2), saltlen(1)
+ if (rdata_len < 5) {
+ isc_throw(DNSMessageFORMERR, rrtype_name << " too short, length: "
+ << rdata_len);
+ }
+
+ const uint8_t hashalg = buffer.readUint8();
+ const uint8_t flags = buffer.readUint8();
+ const uint16_t iterations = buffer.readUint16();
+
+ const uint8_t saltlen = buffer.readUint8();
+ rdata_len -= 5;
+ if (rdata_len < saltlen) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " salt length is too large: " <<
+ static_cast<unsigned int>(saltlen));
+ }
+
+ salt.resize(saltlen);
+ if (saltlen > 0) {
+ buffer.readData(&salt[0], saltlen);
+ rdata_len -= saltlen;
+ }
+
+ return (ParseNSEC3ParamResult(hashalg, flags, iterations));
+}
+
+} // end of nsec3
+} // end of detail
+} // end of generic
+} // end of rdata
+} // end of dns
+} // end of isc
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.h b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
new file mode 100644
index 0000000..89b2596
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
@@ -0,0 +1,123 @@
+// Copyright (C) 2012-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/.
+
+#ifndef NSEC3PARAM_COMMON_H
+#define NSEC3PARAM_COMMON_H 1
+
+#include <dns/master_lexer.h>
+
+#include <util/buffer.h>
+
+#include <stdint.h>
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec3 {
+
+/// \file
+///
+/// This helper module provides some utilities that handle NSEC3 and
+/// NSEC3PARAM RDATA. They share the first few fields, and some operations
+/// on these fields are sufficiently complicated, so it would make sense to
+/// consolidate the processing logic into a single implementation module.
+///
+/// The functions defined here are essentially private and are only expected
+/// to be called from the \c NSEC3 and \c NSEC3PARAM class implementations.
+
+/// \brief Result values of the utilities.
+///
+/// This structure encapsulates a tuple of NSEC3/NSEC3PARAM algorithm,
+/// flags and iterations field values. This is used as the return value
+/// of the utility functions defined in this module so the caller can
+/// use it for constructing the corresponding RDATA.
+struct ParseNSEC3ParamResult {
+ ParseNSEC3ParamResult(uint8_t param_algorithm, uint8_t param_flags,
+ uint16_t param_iterations) :
+ algorithm(param_algorithm), flags(param_flags),
+ iterations(param_iterations)
+ {}
+ const uint8_t algorithm;
+ const uint8_t flags;
+ const uint16_t iterations;
+};
+
+/// \brief Convert textual representation of NSEC3 parameters.
+///
+/// This function takes an input MasterLexer that points at a complete
+/// textual representation of an NSEC3 or NSEC3PARAM RDATA and parses it
+/// extracting the hash algorithm, flags, iterations, and salt fields.
+///
+/// The first three fields are returned as the return value of this function.
+/// The salt will be stored in the given vector. The vector is expected
+/// to be empty, but if not, the existing content will be overridden.
+///
+/// On successful return the given MasterLexer will reach the end of the
+/// salt field.
+///
+/// \exception isc::BadValue The salt is not a valid hex string.
+/// \exception InvalidRdataText The given RDATA is otherwise invalid for
+/// NSEC3 or NSEC3PARAM fields.
+/// \exception MasterLexer::LexerError There was a syntax error reading
+/// a field from the MasterLexer.
+///
+/// \param rrtype_name Either "NSEC3" or "NSEC3PARAM"; used as part of
+/// exception messages.
+/// \param lexer The MasterLexer to read NSEC3 parameter fields from.
+/// \param salt A placeholder for the salt field value of the RDATA.
+/// Expected to be empty, but it's not checked (and will be overridden).
+///
+/// \return The hash algorithm, flags, iterations in the form of
+/// ParseNSEC3ParamResult.
+ParseNSEC3ParamResult parseNSEC3ParamFromLexer(const char* const rrtype_name,
+ isc::dns::MasterLexer& lexer,
+ std::vector<uint8_t>& salt);
+
+/// \brief Extract NSEC3 parameters from wire-format data.
+///
+/// This function takes an input buffer that stores wire-format NSEC3 or
+/// NSEC3PARAM RDATA and parses it extracting the hash algorithm, flags,
+/// iterations, and salt fields.
+///
+/// The first three fields are returned as the return value of this function.
+/// The salt will be stored in the given vector. The vector is expected
+/// to be empty, but if not, the existing content will be overridden.
+///
+/// On successful return the input buffer will point to the end of the
+/// salt field; rdata_len will be the length of the rest of RDATA
+/// (in the case of a valid NSEC3PARAM, it should be 0).
+///
+/// \exception DNSMessageFORMERR The wire data is invalid.
+///
+/// \param rrtype_name Either "NSEC3" or "NSEC3PARAM"; used as part of
+/// exception messages.
+/// \param buffer An input buffer that stores wire-format RDATA. It must
+/// point to the beginning of the data.
+/// \param rdata_len The total length of the RDATA.
+/// \param salt A placeholder for the salt field value of the RDATA.
+/// Expected to be empty, but it's not checked (and will be overridden).
+///
+/// \return The hash algorithm, flags, iterations in the form of
+/// ParseNSEC3ParamResult.
+ParseNSEC3ParamResult parseNSEC3ParamWire(const char* const rrtype_name,
+ isc::util::InputBuffer& buffer,
+ size_t& rdata_len,
+ std::vector<uint8_t>& salt);
+}
+}
+}
+}
+}
+}
+
+#endif // NSEC3PARAM_COMMON_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
new file mode 100644
index 0000000..d02c11d
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
@@ -0,0 +1,169 @@
+// 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 <exceptions/exceptions.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+
+#include <cassert>
+#include <sstream>
+#include <vector>
+#include <cstring>
+#include <stdint.h>
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec {
+void
+checkRRTypeBitmaps(const char* const rrtype_name,
+ const vector<uint8_t>& typebits)
+{
+ bool first = true;
+ unsigned int lastblock = 0;
+ const size_t total_len = typebits.size();
+ size_t i = 0;
+
+ while (i < total_len) {
+ if (i + 2 > total_len) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: incomplete bit map field");
+ }
+ const unsigned int block = typebits[i];
+ const size_t len = typebits[i + 1];
+ // Check that bitmap window blocks are in the correct order.
+ if (!first && block <= lastblock) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: Disordered window blocks found: "
+ << lastblock << " then " << block);
+ }
+ // Check for legal length
+ if (len < 1 || len > 32) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: Invalid bitmap length: " << len);
+ }
+ // Check for overflow.
+ i += 2;
+ if (i + len > total_len) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: bitmap length too large: " << len);
+ }
+ // The last octet of the bitmap must be non zero.
+ if (typebits[i + len - 1] == 0) {
+ isc_throw(DNSMessageFORMERR, rrtype_name <<
+ " RDATA from wire: bitmap ending an all-zero byte");
+ }
+
+ i += len;
+ lastblock = block;
+ first = false;
+ }
+}
+
+void
+buildBitmapsFromLexer(const char* const rrtype_name,
+ MasterLexer& lexer, vector<uint8_t>& typebits,
+ bool allow_empty)
+{
+ uint8_t bitmap[8 * 1024]; // 64k bits
+ memset(bitmap, 0, sizeof(bitmap));
+
+ bool have_rrtypes = false;
+ std::string type_str;
+ 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 is now assured to be of type STRING.
+
+ have_rrtypes = true;
+ token.getString(type_str);
+ try {
+ const int code = RRType(type_str).getCode();
+ bitmap[code / 8] |= (0x80 >> (code % 8));
+ } catch (const InvalidRRType&) {
+ isc_throw(InvalidRdataText, "Invalid RRtype in "
+ << rrtype_name << " bitmap: " << type_str);
+ }
+ }
+
+ lexer.ungetToken();
+
+ if (!have_rrtypes) {
+ if (allow_empty) {
+ return;
+ }
+ isc_throw(InvalidRdataText,
+ rrtype_name <<
+ " record does not end with RR type mnemonic");
+ }
+
+ for (int window = 0; window < 256; ++window) {
+ int octet;
+ for (octet = 31; octet >= 0; octet--) {
+ if (bitmap[window * 32 + octet] != 0) {
+ break;
+ }
+ }
+ if (octet < 0) {
+ continue;
+ }
+ typebits.push_back(window);
+ typebits.push_back(octet + 1);
+ for (int i = 0; i <= octet; ++i) {
+ typebits.push_back(bitmap[window * 32 + i]);
+ }
+ }
+}
+
+void
+bitmapsToText(const vector<uint8_t>& typebits, ostringstream& oss) {
+ // In the following loop we use string::at() rather than operator[].
+ // Since the index calculation is a bit complicated, it will be safer
+ // and easier to find a bug (if any). Note that this conversion method
+ // is generally not expected to be very efficient, so the slight overhead
+ // of at() should be acceptable.
+ const size_t typebits_len = typebits.size();
+ size_t len = 0;
+ for (size_t i = 0; i < typebits_len; i += len) {
+ assert(i + 2 <= typebits.size());
+ const unsigned int block = typebits.at(i);
+ len = typebits.at(i + 1);
+ assert(len > 0 && len <= 32);
+ i += 2;
+ for (size_t j = 0; j < len; ++j) {
+ if (typebits.at(i + j) == 0) {
+ continue;
+ }
+ for (size_t k = 0; k < 8; ++k) {
+ if ((typebits.at(i + j) & (0x80 >> k)) == 0) {
+ continue;
+ }
+ const unsigned int t = block * 256 + j * 8 + k;
+ assert(t < 65536);
+ oss << " " << RRType(t);
+ }
+ }
+ }
+}
+}
+}
+}
+}
+}
+}
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
new file mode 100644
index 0000000..4e073a0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
@@ -0,0 +1,103 @@
+// 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/.
+
+#ifndef NSECBITMAP_H
+#define NSECBITMAP_H 1
+
+#include <dns/master_lexer.h>
+
+#include <stdint.h>
+
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+namespace nsec {
+
+/// \file
+///
+/// This helper module provides some utilities that handle NSEC and NSEC3
+/// type bitmaps. The format and processing of the type bitmaps are generally
+/// the same for these two RRs, so it would make sense to consolidate
+/// the processing logic into a single implementation module.
+///
+/// The functions defined here are essentially private and are only expected
+/// to be called from the \c NSEC and \c NSEC3 class implementations.
+
+/// \brief Check if a given "type bitmap" for NSEC/NSEC3 is valid.
+///
+/// This function checks given wire format data (stored in a
+/// \c std::vector) is a valid type bitmaps used for the NSEC and NSEC3 RRs
+/// according to RFC4034 and RFC5155.
+///
+/// \exception DNSMessageFORMERR The bitmap is not valid.
+///
+/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
+/// messages.
+/// \param typebits The type bitmaps in wire format. The size of vector
+/// is the total length of the bitmaps.
+void checkRRTypeBitmaps(const char* const rrtype_name,
+ const std::vector<uint8_t>& typebits);
+
+/// \brief Convert textual sequence of RR types read from a lexer into
+/// type bitmaps.
+///
+/// See the other variant above for description. If \c allow_empty is
+/// true and there are no mnemonics, \c typebits is left untouched.
+///
+/// \exception InvalidRdataText Data read from the given lexer does not
+/// meet the assumption (e.g. including invalid form of RR type, not
+/// ending with an RR type string).
+///
+/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
+/// messages.
+/// \param lexer MasterLexer that provides consists of a complete
+/// sequence of textual lexemes of RR types for which the corresponding
+/// bits are set.
+/// \param typebits A placeholder for the resulting bitmaps. Expected to be
+/// empty, but it's not checked.
+/// \param allow_empty If true, the function simply returns if no RR
+/// type mnemonics are found. Otherwise, it throws an exception if no RR
+/// type mnemonics are found.
+void buildBitmapsFromLexer(const char* const rrtype_name,
+ isc::dns::MasterLexer& lexer,
+ std::vector<uint8_t>& typebits,
+ bool allow_empty = false);
+
+/// \brief Convert type bitmaps to textual sequence of RR types.
+///
+/// This function converts wire-format data of type bitmaps for NSEC/NSEC3
+/// into a sequence of corresponding RR type strings, and inserts them
+/// into the given output stream with separating them by a single space
+/// character.
+///
+/// This function assumes the given bitmaps are valid in terms of RFC4034
+/// and RFC5155 (in practice, it assumes it's from a validly constructed
+/// NSEC or NSEC3 object); if it detects a format error, it aborts the
+/// program with assert().
+///
+/// \param typebits The type bitmaps in wire format. The size of vector
+/// is the total length of the bitmaps.
+/// \param oss The output stream to which the converted RR type sequence
+/// are to be inserted.
+void bitmapsToText(const std::vector<uint8_t>& typebits,
+ std::ostringstream& oss);
+}
+}
+}
+}
+}
+}
+
+#endif // NSECBITMAP_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
new file mode 100644
index 0000000..5801b09
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -0,0 +1,237 @@
+// Copyright (C) 2011-2015,2021 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 1
+
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
+#include <stdint.h>
+
+#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 buffer 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).
+ 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();
+
+ 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 = 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/dlv_32769.cc b/src/lib/dns/rdata/generic/dlv_32769.cc
new file mode 100644
index 0000000..66303b7
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dlv_32769.cc
@@ -0,0 +1,120 @@
+// 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 <string>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/ds_like.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const std::string& ds_str) :
+ impl_(new DLVImpl(ds_str))
+{}
+
+/// \brief Constructor from wire-format data.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new DLVImpl(buffer, rdata_len))
+{}
+
+DLV::DLV(MasterLexer& lexer, const Name* origin, MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) :
+ impl_(new DLVImpl(lexer, origin, options, callbacks))
+{}
+
+/// \brief Copy constructor
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const DLV& source) :
+ Rdata(), impl_(new DLVImpl(*source.impl_))
+{}
+
+/// \brief Assignment operator
+///
+/// PIMPL-induced logic
+DLV&
+DLV::operator=(const DLV& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ DLVImpl* newimpl = new DLVImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+/// \brief Destructor
+///
+/// Deallocates an internal resource.
+DLV::~DLV() {
+ delete impl_;
+}
+
+/// \brief Convert the \c DLV to a string.
+///
+/// A pass-thru to the corresponding implementation method.
+string
+DLV::toText() const {
+ return (impl_->toText());
+}
+
+/// \brief Render the \c DLV in the wire format to a OutputBuffer object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+/// \brief Render the \c DLV in the wire format to a AbstractMessageRenderer
+/// object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+/// \brief Compare two instances of \c DLV RDATA.
+///
+/// The type check is performed here. Otherwise, a pass-thru to the
+/// corresponding implementation method.
+int
+DLV::compare(const Rdata& other) const {
+ const DLV& other_ds = dynamic_cast<const DLV&>(other);
+
+ return (impl_->compare(*other_ds.impl_));
+}
+
+/// \brief Tag accessor
+uint16_t
+DLV::getTag() const {
+ return (impl_->getTag());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/dlv_32769.h b/src/lib/dns/rdata/generic/dlv_32769.h
new file mode 100644
index 0000000..26523de
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dlv_32769.h
@@ -0,0 +1,69 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
+
+/// \brief \c rdata::generic::DLV class represents the DLV RDATA as defined in
+/// RFC4431.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DLV RDATA.
+class DLV : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \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.
+ DLV& operator=(const DLV& source);
+
+ /// \brief The destructor.
+ ~DLV();
+
+ /// \brief Return the value of the Tag field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getTag() const;
+private:
+ typedef detail::DSLikeImpl<DLV, 32769> DLVImpl;
+ DLVImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/dname_39.cc b/src/lib/dns/rdata/generic/dname_39.cc
new file mode 100644
index 0000000..9ee669b
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dname_39.cc
@@ -0,0 +1,127 @@
+// 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid DNAME 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 TARGET 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.
+DNAME::DNAME(const std::string& namestr) :
+ // Fill in dummy name and replace it soon below.
+ dname_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(namestr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ dname_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for DNAME: "
+ << namestr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct DNAME from '" <<
+ namestr << "': " << ex.what());
+ }
+}
+
+DNAME::DNAME(InputBuffer& buffer, size_t) :
+ dname_(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 DNAME RDATA. The TARGET 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 TARGET when it
+/// is non-absolute.
+DNAME::DNAME(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ dname_(createNameFromLexer(lexer, origin))
+{}
+
+DNAME::DNAME(const DNAME& other) :
+ Rdata(), dname_(other.dname_)
+{}
+
+DNAME::DNAME(const Name& dname) :
+ dname_(dname)
+{}
+
+void
+DNAME::toWire(OutputBuffer& buffer) const {
+ dname_.toWire(buffer);
+}
+
+void
+DNAME::toWire(AbstractMessageRenderer& renderer) const {
+ // Type DNAME is not "well-known", and name compression must be disabled
+ // per RFC3597.
+ renderer.writeName(dname_, false);
+}
+
+string
+DNAME::toText() const {
+ return (dname_.toText());
+}
+
+int
+DNAME::compare(const Rdata& other) const {
+ const DNAME& other_dname = dynamic_cast<const DNAME&>(other);
+
+ return (compareNames(dname_, other_dname.dname_));
+}
+
+const Name&
+DNAME::getDname() const {
+ return (dname_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/dname_39.h b/src/lib/dns/rdata/generic/dname_39.h
new file mode 100644
index 0000000..998fea0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dname_39.h
@@ -0,0 +1,39 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class DNAME : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ // DNAME specific methods
+ DNAME(const Name& dname);
+ const Name& getDname() const;
+private:
+ Name dname_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/dnskey_48.cc b/src/lib/dns/rdata/generic/dnskey_48.cc
new file mode 100644
index 0000000..7bea847
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dnskey_48.cc
@@ -0,0 +1,316 @@
+// 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 <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
+
+#include <util/encode/base64.h>
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <memory>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct DNSKEYImpl {
+ // straightforward representation of DNSKEY RDATA fields
+ DNSKEYImpl(uint16_t flags, uint8_t protocol, uint8_t algorithm,
+ const vector<uint8_t>& keydata) :
+ flags_(flags), protocol_(protocol), algorithm_(algorithm),
+ keydata_(keydata)
+ {}
+
+ uint16_t flags_;
+ uint8_t protocol_;
+ uint8_t algorithm_;
+ const vector<uint8_t> keydata_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid DNSKEY 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 Protocol and Algorithm fields must be within their valid
+/// ranges. The Public Key field must be present and must contain a
+/// Base64 encoding of the public key. Whitespace is allowed within the
+/// Base64 text.
+///
+/// It is okay for the key data to be missing. Note: BIND 9 also accepts
+/// DNSKEY missing key data. While the RFC is silent in this case, and it
+/// may be debatable what an implementation should do, but since this field
+/// is algorithm dependent and this implementations doesn't reject unknown
+/// algorithms, it's lenient here.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param dnskey_str A string containing the RDATA to be created
+DNSKEY::DNSKEY(const std::string& dnskey_str) :
+ impl_(NULL)
+{
+ // 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 DNSKEYImpl that constructFromLexer() returns.
+ std::unique_ptr<DNSKEYImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(dnskey_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for DNSKEY: " << dnskey_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct DNSKEY from '" << dnskey_str << "': "
+ << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid DNSKEY RDATA.
+///
+/// The Protocol and Algorithm fields are not checked for unknown
+/// values. It is okay for the key data to be missing (see the description
+/// of the constructor from string).
+DNSKEY::DNSKEY(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ if (rdata_len < 4) {
+ isc_throw(InvalidRdataLength, "DNSKEY too short: " << rdata_len);
+ }
+
+ const uint16_t flags = buffer.readUint16();
+ const uint16_t protocol = buffer.readUint8();
+ const uint16_t algorithm = buffer.readUint8();
+
+ rdata_len -= 4;
+
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (rdata_len > 0) {
+ keydata.resize(rdata_len);
+ buffer.readData(&keydata[0], rdata_len);
+ }
+
+ impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an DNSKEY RDATA.
+///
+/// See \c DNSKEY::DNSKEY(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
+DNSKEY::DNSKEY(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+DNSKEYImpl*
+DNSKEY::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t flags = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (flags > 0xffff) {
+ isc_throw(InvalidRdataText,
+ "DNSKEY flags out of range: " << flags);
+ }
+
+ const uint32_t protocol =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (protocol > 0xff) {
+ isc_throw(InvalidRdataText,
+ "DNSKEY protocol out of range: " << protocol);
+ }
+
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 0xff) {
+ isc_throw(InvalidRdataText,
+ "DNSKEY algorithm out of range: " << algorithm);
+ }
+
+ std::string keydata_str;
+ std::string keydata_substr;
+ 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 is now assured to be of type STRING.
+
+ token.getString(keydata_substr);
+ keydata_str.append(keydata_substr);
+ }
+
+ lexer.ungetToken();
+
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (keydata_str.size() > 0) {
+ decodeBase64(keydata_str, keydata);
+ }
+
+ return (new DNSKEYImpl(flags, protocol, algorithm, keydata));
+}
+
+DNSKEY::DNSKEY(const DNSKEY& source) :
+ Rdata(), impl_(new DNSKEYImpl(*source.impl_))
+{}
+
+DNSKEY&
+DNSKEY::operator=(const DNSKEY& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ DNSKEYImpl* newimpl = new DNSKEYImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+DNSKEY::~DNSKEY() {
+ delete impl_;
+}
+
+string
+DNSKEY::toText() const {
+ return (boost::lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + boost::lexical_cast<string>(static_cast<int>(impl_->protocol_)) +
+ " " + boost::lexical_cast<string>(static_cast<int>(impl_->algorithm_)) +
+ " " + encodeBase64(impl_->keydata_));
+}
+
+void
+DNSKEY::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(impl_->flags_);
+ buffer.writeUint8(impl_->protocol_);
+ buffer.writeUint8(impl_->algorithm_);
+ buffer.writeData(&impl_->keydata_[0], impl_->keydata_.size());
+}
+
+void
+DNSKEY::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(impl_->flags_);
+ renderer.writeUint8(impl_->protocol_);
+ renderer.writeUint8(impl_->algorithm_);
+ renderer.writeData(&impl_->keydata_[0], impl_->keydata_.size());
+}
+
+int
+DNSKEY::compare(const Rdata& other) const {
+ const DNSKEY& other_dnskey = dynamic_cast<const DNSKEY&>(other);
+
+ if (impl_->flags_ != other_dnskey.impl_->flags_) {
+ return (impl_->flags_ < other_dnskey.impl_->flags_ ? -1 : 1);
+ }
+ if (impl_->protocol_ != other_dnskey.impl_->protocol_) {
+ return (impl_->protocol_ < other_dnskey.impl_->protocol_ ? -1 : 1);
+ }
+ if (impl_->algorithm_ != other_dnskey.impl_->algorithm_) {
+ return (impl_->algorithm_ < other_dnskey.impl_->algorithm_ ? -1 : 1);
+ }
+
+ const size_t this_len = impl_->keydata_.size();
+ const size_t other_len = other_dnskey.impl_->keydata_.size();
+ const size_t cmplen = min(this_len, other_len);
+ if (cmplen == 0) {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+ const int cmp = memcmp(&impl_->keydata_[0],
+ &other_dnskey.impl_->keydata_[0], cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+uint16_t
+DNSKEY::getTag() const {
+ if (impl_->algorithm_ == 1) {
+ // See RFC 4034 appendix B.1 for why the key data must contain
+ // at least 4 bytes with RSA/MD5: 3 trailing bytes to extract
+ // the tag from, and 1 byte of exponent length subfield before
+ // modulus.
+ const int len = impl_->keydata_.size();
+ if (len < 4) {
+ isc_throw(isc::OutOfRange,
+ "DNSKEY keydata too short for tag extraction");
+ }
+
+ return ((impl_->keydata_[len - 3] << 8) + impl_->keydata_[len - 2]);
+ }
+
+ uint32_t ac = impl_->flags_;
+ ac += (impl_->protocol_ << 8);
+ ac += impl_->algorithm_;
+
+ const size_t size = impl_->keydata_.size();
+ for (size_t i = 0; i < size; i ++) {
+ ac += (i & 1) ? impl_->keydata_[i] : (impl_->keydata_[i] << 8);
+ }
+ ac += (ac >> 16) & 0xffff;
+ return (ac & 0xffff);
+}
+
+uint16_t
+DNSKEY::getFlags() const {
+ return (impl_->flags_);
+}
+
+uint8_t
+DNSKEY::getAlgorithm() const {
+ return (impl_->algorithm_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/dnskey_48.h b/src/lib/dns/rdata/generic/dnskey_48.h
new file mode 100644
index 0000000..a5e9efa
--- /dev/null
+++ b/src/lib/dns/rdata/generic/dnskey_48.h
@@ -0,0 +1,60 @@
+// 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 <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+
+// BEGIN_HEADER_GUARD
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct DNSKEYImpl;
+
+class DNSKEY : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+ DNSKEY& operator=(const DNSKEY& source);
+ ~DNSKEY();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Returns the key tag
+ ///
+ /// \throw isc::OutOfRange if the key data for RSA/MD5 is too short
+ /// to support tag extraction.
+ uint16_t getTag() const;
+
+ uint16_t getFlags() const;
+ uint8_t getAlgorithm() const;
+
+private:
+ DNSKEYImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
+ DNSKEYImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/ds_43.cc b/src/lib/dns/rdata/generic/ds_43.cc
new file mode 100644
index 0000000..48c421c
--- /dev/null
+++ b/src/lib/dns/rdata/generic/ds_43.cc
@@ -0,0 +1,90 @@
+// 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 <string>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/ds_like.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+DS::DS(const std::string& ds_str) :
+ impl_(new DSImpl(ds_str))
+{}
+
+DS::DS(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new DSImpl(buffer, rdata_len))
+{}
+
+DS::DS(MasterLexer& lexer, const Name* origin, MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) :
+ impl_(new DSImpl(lexer, origin, options, callbacks))
+{}
+
+DS::DS(const DS& source) :
+ Rdata(), impl_(new DSImpl(*source.impl_))
+{}
+
+DS&
+DS::operator=(const DS& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ DSImpl* newimpl = new DSImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+DS::~DS() {
+ delete impl_;
+}
+
+string
+DS::toText() const {
+ return (impl_->toText());
+}
+
+void
+DS::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+void
+DS::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+int
+DS::compare(const Rdata& other) const {
+ const DS& other_ds = dynamic_cast<const DS&>(other);
+
+ return (impl_->compare(*other_ds.impl_));
+}
+
+uint16_t
+DS::getTag() const {
+ return (impl_->getTag());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/ds_43.h b/src/lib/dns/rdata/generic/ds_43.h
new file mode 100644
index 0000000..a20e349
--- /dev/null
+++ b/src/lib/dns/rdata/generic/ds_43.h
@@ -0,0 +1,69 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
+
+/// \brief \c rdata::generic::DS class represents the DS RDATA as defined in
+/// RFC3658.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DS RDATA.
+class DS : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \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.
+ DS& operator=(const DS& source);
+
+ /// \brief The destructor.
+ ~DS();
+
+ /// \brief Return the value of the Tag field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getTag() const;
+private:
+ typedef detail::DSLikeImpl<DS, 43> DSImpl;
+ DSImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/hinfo_13.cc b/src/lib/dns/rdata/generic/hinfo_13.cc
new file mode 100644
index 0000000..3bda219
--- /dev/null
+++ b/src/lib/dns/rdata/generic/hinfo_13.cc
@@ -0,0 +1,151 @@
+// 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 <exceptions/exceptions.h>
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::dns;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+class HINFOImpl {
+public:
+ HINFOImpl(const std::string& hinfo_str) {
+ std::istringstream ss(hinfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseHINFOData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid HINFO text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct HINFO RDATA from "
+ << hinfo_str << "': " << ex.what());
+ }
+ }
+
+ HINFOImpl(InputBuffer& buffer, size_t rdata_len) {
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, cpu);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, os);
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "HINFO RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
+ }
+
+ HINFOImpl(MasterLexer& lexer)
+ {
+ parseHINFOData(lexer);
+ }
+
+private:
+ void
+ parseHINFOData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), cpu);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), os);
+ }
+
+public:
+ detail::CharString cpu;
+ detail::CharString os;
+};
+
+HINFO::HINFO(const std::string& hinfo_str) : impl_(new HINFOImpl(hinfo_str))
+{}
+
+
+HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new HINFOImpl(buffer, rdata_len))
+{}
+
+HINFO::HINFO(const HINFO& source):
+ Rdata(), impl_(new HINFOImpl(*source.impl_))
+{
+}
+
+HINFO::HINFO(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new HINFOImpl(lexer))
+{}
+
+HINFO&
+HINFO::operator=(const HINFO& source)
+{
+ impl_.reset(new HINFOImpl(*source.impl_));
+ return (*this);
+}
+
+HINFO::~HINFO() {
+}
+
+std::string
+HINFO::toText() const {
+ string result;
+ result += "\"";
+ result += detail::charStringToString(impl_->cpu);
+ result += "\" \"";
+ result += detail::charStringToString(impl_->os);
+ result += "\"";
+ return (result);
+}
+
+void
+HINFO::toWire(OutputBuffer& buffer) const {
+ toWireHelper(buffer);
+}
+
+void
+HINFO::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(renderer);
+}
+
+int
+HINFO::compare(const Rdata& other) const {
+ const HINFO& other_hinfo = dynamic_cast<const HINFO&>(other);
+
+ const int cmp = compareCharStrings(impl_->cpu, other_hinfo.impl_->cpu);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ return (compareCharStrings(impl_->os, other_hinfo.impl_->os));
+}
+
+const std::string
+HINFO::getCPU() const {
+ return (detail::charStringToString(impl_->cpu));
+}
+
+const std::string
+HINFO::getOS() const {
+ return (detail::charStringToString(impl_->os));
+}
+
+template <typename T>
+void
+HINFO::toWireHelper(T& outputer) const {
+ outputer.writeData(&impl_->cpu[0], impl_->cpu.size());
+ outputer.writeData(&impl_->os[0], impl_->os.size());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/hinfo_13.h b/src/lib/dns/rdata/generic/hinfo_13.h
new file mode 100644
index 0000000..acceb14
--- /dev/null
+++ b/src/lib/dns/rdata/generic/hinfo_13.h
@@ -0,0 +1,64 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+#include <stdint.h>
+
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <util/buffer.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class HINFOImpl;
+
+/// \brief \c HINFO class represents the HINFO rdata defined in
+/// RFC1034, RFC1035
+///
+/// This class implements the basic interfaces inherited from the
+/// \c rdata::Rdata class, and provides accessors specific to the
+/// HINFO rdata.
+class HINFO : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ // HINFO specific methods
+ ~HINFO();
+
+ HINFO& operator=(const HINFO&);
+
+ const std::string getCPU() const;
+ const std::string getOS() const;
+
+private:
+ /// Helper template function for toWire()
+ ///
+ /// \param outputer Where to write data in
+ template <typename T>
+ void toWireHelper(T& outputer) const;
+
+ boost::scoped_ptr<HINFOImpl> impl_;
+};
+
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/minfo_14.cc b/src/lib/dns/rdata/generic/minfo_14.cc
new file mode 100644
index 0000000..d3dfd1e
--- /dev/null
+++ b/src/lib/dns/rdata/generic/minfo_14.cc
@@ -0,0 +1,173 @@
+// 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 <string>
+#include <sstream>
+
+#include <util/buffer.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// \c minfo_str must be formatted as follows:
+/// \code <rmailbox name> <emailbox name>
+/// \endcode
+/// where both fields must represent a valid domain name.
+///
+/// An example of valid string is:
+/// \code "rmail.example.com. email.example.com." \endcode
+///
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// names in the string is invalid.
+MINFO::MINFO(const std::string& minfo_str) :
+ // We cannot construct both names in the initialization list due to the
+ // necessary text processing, so we have to initialize them with a dummy
+ // name and replace them later.
+ rmailbox_(Name::ROOT_NAME()), emailbox_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(minfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ rmailbox_ = createNameFromLexer(lexer, NULL);
+ emailbox_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for MINFO: "
+ << minfo_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct MINFO from '" <<
+ minfo_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an MINFO RDATA. The RMAILBOX and EMAILBOX fields can be non-absolute
+/// if \c origin is non-NULL, in which case \c origin is used to make them
+/// absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and 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 SERVER when it
+/// is non-absolute.
+MINFO::MINFO(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ rmailbox_(createNameFromLexer(lexer, origin)),
+ emailbox_(createNameFromLexer(lexer, origin))
+{
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+MINFO::MINFO(InputBuffer& buffer, size_t) :
+ rmailbox_(buffer), emailbox_(buffer)
+{}
+
+/// \brief Copy constructor.
+///
+/// \throw std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+MINFO::MINFO(const MINFO& other) :
+ Rdata(), rmailbox_(other.rmailbox_), emailbox_(other.emailbox_)
+{}
+
+/// \brief Convert the \c MINFO to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c MINFO(const std::string&))).
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c MINFO object.
+std::string
+MINFO::toText() const {
+ return (rmailbox_.toText() + " " + emailbox_.toText());
+}
+
+/// \brief Render the \c MINFO in the wire format without name compression.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+MINFO::toWire(OutputBuffer& buffer) const {
+ rmailbox_.toWire(buffer);
+ emailbox_.toWire(buffer);
+}
+
+MINFO&
+MINFO::operator=(const MINFO& source) {
+ rmailbox_ = source.rmailbox_;
+ emailbox_ = source.emailbox_;
+
+ return (*this);
+}
+
+/// \brief Render the \c MINFO in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, TYPE MINFO is "well-known", the rmailbox and
+/// emailbox fields (domain names) will be compressed.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+MINFO::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(rmailbox_);
+ renderer.writeName(emailbox_);
+}
+
+/// \brief Compare two instances of \c MINFO RDATA.
+///
+/// See documentation in \c Rdata.
+int
+MINFO::compare(const Rdata& other) const {
+ const MINFO& other_minfo = dynamic_cast<const MINFO&>(other);
+
+ const int cmp = compareNames(rmailbox_, other_minfo.rmailbox_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ return (compareNames(emailbox_, other_minfo.emailbox_));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/minfo_14.h b/src/lib/dns/rdata/generic/minfo_14.h
new file mode 100644
index 0000000..ce9f43d
--- /dev/null
+++ b/src/lib/dns/rdata/generic/minfo_14.h
@@ -0,0 +1,74 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::generic::MINFO class represents the MINFO RDATA as
+/// defined in RFC1035.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// MINFO RDATA.
+class MINFO : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \brief Define the assignment operator.
+ ///
+ /// \exception std::bad_alloc Memory allocation fails in copying
+ /// internal member variables (this should be very rare).
+ MINFO& operator=(const MINFO& source);
+
+ /// \brief Return the value of the rmailbox field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ ///
+ /// \note
+ /// Unlike the case of some other RDATA classes (such as
+ /// \c NS::getNSName()), this method constructs a new \c Name object
+ /// and returns it, instead of returning a reference to a \c Name object
+ /// internally maintained in the class (which is a private member).
+ /// This is based on the observation that this method will be rarely
+ /// used and even when it's used it will not be in a performance context
+ /// (for example, a recursive resolver won't need this field in its
+ /// resolution process). By returning a new object we have flexibility
+ /// of changing the internal representation without the risk of changing
+ /// the interface or method property.
+ /// The same note applies to the \c getEmailbox() method.
+ Name getRmailbox() const { return (rmailbox_); }
+
+ /// \brief Return the value of the emailbox field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ Name getEmailbox() const { return (emailbox_); }
+
+private:
+ Name rmailbox_;
+ Name emailbox_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/mx_15.cc b/src/lib/dns/rdata/generic/mx_15.cc
new file mode 100644
index 0000000..bbe8abc
--- /dev/null
+++ b/src/lib/dns/rdata/generic/mx_15.cc
@@ -0,0 +1,160 @@
+// 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 <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+MX::MX(InputBuffer& buffer, size_t) :
+ preference_(buffer.readUint16()), mxname_(buffer)
+{
+ // we don't need rdata_len for parsing. if necessary, the caller will
+ // check consistency.
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid MX 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 EXCHANGE name must be absolute since there's no parameter that
+/// specifies the origin name; if it is not absolute, \c MissingNameOrigin
+/// exception will be thrown. It 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.
+MX::MX(const std::string& mx_str) :
+ // Fill in dummy name and replace them soon below.
+ preference_(0), mxname_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(mx_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ constructFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for MX: "
+ << mx_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct MX from '" <<
+ mx_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an MX RDATA. The EXCHANGE 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.
+///
+/// The PREFERENCE field must be a valid decimal representation of an
+/// unsigned 16-bit integer.
+///
+/// \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 EXCHANGE when it
+/// is non-absolute.
+MX::MX(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ preference_(0), mxname_(Name::ROOT_NAME())
+{
+ constructFromLexer(lexer, origin);
+}
+
+void
+MX::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid MX preference: " << num);
+ }
+ preference_ = static_cast<uint16_t>(num);
+
+ mxname_ = createNameFromLexer(lexer, origin);
+}
+
+MX::MX(uint16_t preference, const Name& mxname) :
+ preference_(preference), mxname_(mxname)
+{}
+
+MX::MX(const MX& other) :
+ Rdata(), preference_(other.preference_), mxname_(other.mxname_)
+{}
+
+void
+MX::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(preference_);
+ mxname_.toWire(buffer);
+}
+
+void
+MX::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(preference_);
+ renderer.writeName(mxname_);
+}
+
+string
+MX::toText() const {
+ return (lexical_cast<string>(preference_) + " " + mxname_.toText());
+}
+
+int
+MX::compare(const Rdata& other) const {
+ const MX& other_mx = dynamic_cast<const MX&>(other);
+
+ if (preference_ < other_mx.preference_) {
+ return (-1);
+ } else if (preference_ > other_mx.preference_) {
+ return (1);
+ }
+
+ return (compareNames(mxname_, other_mx.mxname_));
+}
+
+const Name&
+MX::getMXName() const {
+ return (mxname_);
+}
+
+uint16_t
+MX::getMXPref() const {
+ return (preference_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/mx_15.h b/src/lib/dns/rdata/generic/mx_15.h
new file mode 100644
index 0000000..b688f88
--- /dev/null
+++ b/src/lib/dns/rdata/generic/mx_15.h
@@ -0,0 +1,52 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class MX : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ MX(uint16_t preference, const Name& mxname);
+
+ ///
+ /// Specialized methods
+ ///
+ const Name& getMXName() const;
+ uint16_t getMXPref() const;
+
+private:
+ void constructFromLexer(isc::dns::MasterLexer& lexer,
+ const isc::dns::Name* origin);
+
+ /// Note: this is a prototype version; we may reconsider
+ /// this representation later.
+ uint16_t preference_;
+ Name mxname_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/naptr_35.cc b/src/lib/dns/rdata/generic/naptr_35.cc
new file mode 100644
index 0000000..aa2d7f5
--- /dev/null
+++ b/src/lib/dns/rdata/generic/naptr_35.cc
@@ -0,0 +1,256 @@
+// 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/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::dns;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+class NAPTRImpl {
+public:
+ NAPTRImpl() : order(0), preference(0), replacement(".") {}
+
+ NAPTRImpl(InputBuffer& buffer, size_t rdata_len) : replacement(".") {
+ if (rdata_len < 4 || buffer.getLength() - buffer.getPosition() < 4) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: insufficient length ");
+ }
+ order = buffer.readUint16();
+ preference = buffer.readUint16();
+ rdata_len -= 4;
+
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, flags);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, services);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, regexp);
+ replacement = Name(buffer);
+ if (rdata_len < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: missing replacement name");
+ }
+ rdata_len -= replacement.getLength();
+
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "NAPTR RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
+ }
+
+ NAPTRImpl(const std::string& naptr_str) : replacement(".") {
+ std::istringstream ss(naptr_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseNAPTRData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct NAPTR RDATA from "
+ << naptr_str << "': " << ex.what());
+ }
+ }
+
+ NAPTRImpl(MasterLexer& lexer) : replacement(".")
+ {
+ parseNAPTRData(lexer);
+ }
+
+private:
+ void
+ parseNAPTRData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: order out of range: "
+ << token.getNumber());
+ }
+ order = token.getNumber();
+ token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: preference out of range: "
+ << token.getNumber());
+ }
+ preference = token.getNumber();
+
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), flags);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), services);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), regexp);
+
+ token = lexer.getNextToken(MasterToken::STRING);
+ replacement = Name(token.getString());
+ }
+
+
+public:
+ uint16_t order;
+ uint16_t preference;
+ detail::CharString flags;
+ detail::CharString services;
+ detail::CharString regexp;
+ Name replacement;
+};
+
+NAPTR::NAPTR(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new NAPTRImpl(buffer, rdata_len))
+{}
+
+NAPTR::NAPTR(const std::string& naptr_str) : impl_(new NAPTRImpl(naptr_str))
+{}
+
+NAPTR::NAPTR(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new NAPTRImpl(lexer))
+{}
+
+NAPTR::NAPTR(const NAPTR& naptr) : Rdata(),
+ impl_(new NAPTRImpl(*naptr.impl_))
+{}
+
+NAPTR&
+NAPTR::operator=(const NAPTR& source)
+{
+ impl_.reset(new NAPTRImpl(*source.impl_));
+ return (*this);
+}
+
+NAPTR::~NAPTR() {
+}
+
+void
+NAPTR::toWire(OutputBuffer& buffer) const {
+ toWireHelper(buffer);
+ impl_->replacement.toWire(buffer);
+}
+
+void
+NAPTR::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(renderer);
+ // Type NAPTR is not "well-known", and name compression must be disabled
+ // per RFC3597.
+ renderer.writeName(impl_->replacement, false);
+}
+
+string
+NAPTR::toText() const {
+ string result;
+ result += lexical_cast<string>(impl_->order);
+ result += " ";
+ result += lexical_cast<string>(impl_->preference);
+ result += " \"";
+ result += detail::charStringToString(impl_->flags);
+ result += "\" \"";
+ result += detail::charStringToString(impl_->services);
+ result += "\" \"";
+ result += detail::charStringToString(impl_->regexp);
+ result += "\" ";
+ result += impl_->replacement.toText();
+ return (result);
+}
+
+int
+NAPTR::compare(const Rdata& other) const {
+ const NAPTR other_naptr = dynamic_cast<const NAPTR&>(other);
+
+ if (impl_->order < other_naptr.impl_->order) {
+ return (-1);
+ } else if (impl_->order > other_naptr.impl_->order) {
+ return (1);
+ }
+
+ if (impl_->preference < other_naptr.impl_->preference) {
+ return (-1);
+ } else if (impl_->preference > other_naptr.impl_->preference) {
+ return (1);
+ }
+
+ const int fcmp = detail::compareCharStrings(impl_->flags,
+ other_naptr.impl_->flags);
+ if (fcmp != 0) {
+ return (fcmp);
+ }
+
+ const int scmp = detail::compareCharStrings(impl_->services,
+ other_naptr.impl_->services);
+ if (scmp != 0) {
+ return (scmp);
+ }
+
+ const int rcmp = detail::compareCharStrings(impl_->regexp,
+ other_naptr.impl_->regexp);
+ if (rcmp != 0) {
+ return (rcmp);
+ }
+
+ return (compareNames(impl_->replacement, other_naptr.impl_->replacement));
+}
+
+uint16_t
+NAPTR::getOrder() const {
+ return (impl_->order);
+}
+
+uint16_t
+NAPTR::getPreference() const {
+ return (impl_->preference);
+}
+
+const std::string
+NAPTR::getFlags() const {
+ return (detail::charStringToString(impl_->flags));
+}
+
+const std::string
+NAPTR::getServices() const {
+ return (detail::charStringToString(impl_->services));
+}
+
+const std::string
+NAPTR::getRegexp() const {
+ return (detail::charStringToString(impl_->regexp));
+}
+
+const Name&
+NAPTR::getReplacement() const {
+ return (impl_->replacement);
+}
+
+template <typename T>
+void
+NAPTR::toWireHelper(T& outputer) const {
+ outputer.writeUint16(impl_->order);
+ outputer.writeUint16(impl_->preference);
+
+ outputer.writeData(&impl_->flags[0], impl_->flags.size());
+ outputer.writeData(&impl_->services[0], impl_->services.size());
+ outputer.writeData(&impl_->regexp[0], impl_->regexp.size());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/naptr_35.h b/src/lib/dns/rdata/generic/naptr_35.h
new file mode 100644
index 0000000..c77b95d
--- /dev/null
+++ b/src/lib/dns/rdata/generic/naptr_35.h
@@ -0,0 +1,64 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <util/buffer.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class NAPTRImpl;
+
+/// \brief \c NAPTR class represents the NAPTR rdata defined in
+/// RFC2915, RFC2168 and RFC3403
+///
+/// This class implements the basic interfaces inherited from the
+/// \c rdata::Rdata class, and provides accessors specific to the
+/// NAPTR rdata.
+class NAPTR : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ // NAPTR specific methods
+ ~NAPTR();
+
+ NAPTR& operator=(const NAPTR& source);
+
+ uint16_t getOrder() const;
+ uint16_t getPreference() const;
+ const std::string getFlags() const;
+ const std::string getServices() const;
+ const std::string getRegexp() const;
+ const Name& getReplacement() const;
+private:
+ /// Helper template function for toWire()
+ ///
+ /// \param outputer Where to write data in
+ template <typename T>
+ void toWireHelper(T& outputer) const;
+
+ boost::scoped_ptr<NAPTRImpl> impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/ns_2.cc b/src/lib/dns/rdata/generic/ns_2.cc
new file mode 100644
index 0000000..7c1fd01
--- /dev/null
+++ b/src/lib/dns/rdata/generic/ns_2.cc
@@ -0,0 +1,121 @@
+// 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \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, NULL);
+
+ 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_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/ns_2.h b/src/lib/dns/rdata/generic/ns_2.h
new file mode 100644
index 0000000..18c3cf6
--- /dev/null
+++ b/src/lib/dns/rdata/generic/ns_2.h
@@ -0,0 +1,43 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class NS : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+ ///
+ /// Specialized constructor
+ ///
+ explicit NS(const Name& nsname) : nsname_(nsname) {}
+ ///
+ /// Specialized methods
+ ///
+ const Name& getNSName() const;
+private:
+ Name nsname_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
new file mode 100644
index 0000000..e99c109
--- /dev/null
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -0,0 +1,343 @@
+// 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 <iostream>
+#include <iomanip>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <cassert>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/encode/base32hex.h>
+#include <util/encode/hex.h>
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
+
+#include <memory>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::dns::rdata::generic::detail::nsec;
+using namespace isc::dns::rdata::generic::detail::nsec3;
+using namespace isc::util::encode;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct NSEC3Impl {
+ // straightforward representation of NSEC3 RDATA fields
+ NSEC3Impl(uint8_t hashalg, uint8_t flags, uint16_t iterations,
+ vector<uint8_t>salt, vector<uint8_t>next,
+ vector<uint8_t> typebits) :
+ hashalg_(hashalg), flags_(flags), iterations_(iterations),
+ salt_(salt), next_(next), typebits_(typebits)
+ {}
+
+ const uint8_t hashalg_;
+ const uint8_t flags_;
+ const uint16_t iterations_;
+ const vector<uint8_t> salt_;
+ const vector<uint8_t> next_;
+ const vector<uint8_t> typebits_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3 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 Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3_str A string containing the RDATA to be created
+NSEC3::NSEC3(const std::string& nsec3_str) :
+ impl_(NULL)
+{
+ // 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 NSEC3Impl that constructFromLexer() returns.
+ std::unique_ptr<NSEC3Impl> impl_ptr;
+
+ try {
+ std::istringstream ss(nsec3_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3: " << nsec3_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3 from '" << nsec3_str << "': "
+ << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3 RDATA.
+///
+/// See \c NSEC3::NSEC3(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
+NSEC3::NSEC3(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3Impl*
+NSEC3::constructFromLexer(MasterLexer& lexer) {
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamFromLexer("NSEC3", lexer, salt);
+
+ const string& nexthash =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ if (*nexthash.rbegin() == '=') {
+ isc_throw(InvalidRdataText, "NSEC3 hash has padding: " << nexthash);
+ }
+
+ vector<uint8_t> next;
+ decodeBase32Hex(nexthash, next);
+ if (next.size() > 255) {
+ isc_throw(InvalidRdataText, "NSEC3 hash is too long: "
+ << next.size() << " bytes");
+ }
+
+ vector<uint8_t> typebits;
+ // For NSEC3 empty bitmap is possible and allowed.
+ buildBitmapsFromLexer("NSEC3", lexer, typebits, true);
+ return (new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits));
+}
+
+NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamWire("NSEC3", buffer, rdata_len, salt);
+
+ if (rdata_len < 1) {
+ isc_throw(DNSMessageFORMERR, "NSEC3 too short to contain hash length, "
+ "length: " << rdata_len + salt.size() + 5);
+ }
+ const uint8_t nextlen = buffer.readUint8();
+ --rdata_len;
+ if (nextlen == 0 || rdata_len < nextlen) {
+ isc_throw(DNSMessageFORMERR, "NSEC3 invalid hash length: " <<
+ static_cast<unsigned int>(nextlen));
+ }
+
+ vector<uint8_t> next(nextlen);
+ buffer.readData(&next[0], nextlen);
+ rdata_len -= nextlen;
+
+ vector<uint8_t> typebits(rdata_len);
+ if (rdata_len > 0) {
+ // Read and parse the bitmaps only when they exist; empty bitmap
+ // is possible for NSEC3.
+ buffer.readData(&typebits[0], rdata_len);
+ checkRRTypeBitmaps("NSEC3", typebits);
+ }
+
+ impl_ = new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits);
+}
+
+NSEC3::NSEC3(const NSEC3& source) :
+ Rdata(), impl_(new NSEC3Impl(*source.impl_))
+{}
+
+NSEC3&
+NSEC3::operator=(const NSEC3& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ NSEC3Impl* newimpl = new NSEC3Impl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+NSEC3::~NSEC3() {
+ delete impl_;
+}
+
+string
+NSEC3::toText() const {
+ ostringstream s;
+ bitmapsToText(impl_->typebits_, s);
+
+ using boost::lexical_cast;
+ return (lexical_cast<string>(static_cast<int>(impl_->hashalg_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
+ " " + (impl_->salt_.empty() ? "-" : encodeHex(impl_->salt_)) +
+ " " + encodeBase32Hex(impl_->next_) + s.str());
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireHelper(const NSEC3Impl& impl, OUTPUT_TYPE& output) {
+ output.writeUint8(impl.hashalg_);
+ output.writeUint8(impl.flags_);
+ output.writeUint16(impl.iterations_);
+ output.writeUint8(impl.salt_.size());
+ if (!impl.salt_.empty()) {
+ output.writeData(&impl.salt_[0], impl.salt_.size());
+ }
+ assert(!impl.next_.empty());
+ output.writeUint8(impl.next_.size());
+ output.writeData(&impl.next_[0], impl.next_.size());
+ if (!impl.typebits_.empty()) {
+ output.writeData(&impl.typebits_[0], impl.typebits_.size());
+ }
+}
+
+void
+NSEC3::toWire(OutputBuffer& buffer) const {
+ toWireHelper(*impl_, buffer);
+}
+
+void
+NSEC3::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(*impl_, renderer);
+}
+
+namespace {
+// This is a helper subroutine for compare(). It compares two binary
+// data stored in vector<uint8_t> objects based on the "Canonical RR Ordering"
+// as defined in Section 6.3 of RFC4034, that is, the data are treated
+// "as a left-justified unsigned octet sequence in which the absence of an
+// octet sorts before a zero octet."
+//
+// If check_length_first is true, it treats the compared data as if they
+// began with a single-octet "length" field whose value is the size of the
+// corresponding vector. In this case, if the sizes of the two vectors are
+// different the shorter one is always considered the "smaller"; the contents
+// of the vector don't matter.
+//
+// This function returns:
+// -1 if v1 is considered smaller than v2
+// 1 if v1 is considered larger than v2
+// 0 otherwise
+int
+compareVectors(const vector<uint8_t>& v1, const vector<uint8_t>& v2,
+ bool check_length_first = true)
+{
+ const size_t len1 = v1.size();
+ const size_t len2 = v2.size();
+ if (check_length_first && len1 != len2) {
+ return (len1 - len2);
+ }
+ const size_t cmplen = min(len1, len2);
+ const int cmp = cmplen == 0 ? 0 : memcmp(&v1.at(0), &v2.at(0), cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return (len1 - len2);
+ }
+}
+}
+
+int
+NSEC3::compare(const Rdata& other) const {
+ const NSEC3& other_nsec3 = dynamic_cast<const NSEC3&>(other);
+
+ if (impl_->hashalg_ != other_nsec3.impl_->hashalg_) {
+ return (impl_->hashalg_ < other_nsec3.impl_->hashalg_ ? -1 : 1);
+ }
+ if (impl_->flags_ != other_nsec3.impl_->flags_) {
+ return (impl_->flags_ < other_nsec3.impl_->flags_ ? -1 : 1);
+ }
+ if (impl_->iterations_ != other_nsec3.impl_->iterations_) {
+ return (impl_->iterations_ < other_nsec3.impl_->iterations_ ? -1 : 1);
+ }
+
+ int cmp = compareVectors(impl_->salt_, other_nsec3.impl_->salt_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ cmp = compareVectors(impl_->next_, other_nsec3.impl_->next_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ // Note that bitmap doesn't have a dedicated length field, so we shouldn't
+ // terminate the comparison just because the lengths are different.
+ return (compareVectors(impl_->typebits_, other_nsec3.impl_->typebits_,
+ false));
+}
+
+uint8_t
+NSEC3::getHashalg() const {
+ return (impl_->hashalg_);
+}
+
+uint8_t
+NSEC3::getFlags() const {
+ return (impl_->flags_);
+}
+
+uint16_t
+NSEC3::getIterations() const {
+ return (impl_->iterations_);
+}
+
+const vector<uint8_t>&
+NSEC3::getSalt() const {
+ return (impl_->salt_);
+}
+
+const vector<uint8_t>&
+NSEC3::getNext() const {
+ return (impl_->next_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/nsec3_50.h b/src/lib/dns/rdata/generic/nsec3_50.h
new file mode 100644
index 0000000..cf73624
--- /dev/null
+++ b/src/lib/dns/rdata/generic/nsec3_50.h
@@ -0,0 +1,54 @@
+// 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 <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+
+// BEGIN_HEADER_GUARD
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct NSEC3Impl;
+
+class NSEC3 : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+ NSEC3& operator=(const NSEC3& source);
+ ~NSEC3();
+
+ uint8_t getHashalg() const;
+ uint8_t getFlags() const;
+ uint16_t getIterations() const;
+ const std::vector<uint8_t>& getSalt() const;
+ const std::vector<uint8_t>& getNext() const;
+
+private:
+ NSEC3Impl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
+ NSEC3Impl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.cc b/src/lib/dns/rdata/generic/nsec3param_51.cc
new file mode 100644
index 0000000..2d28a69
--- /dev/null
+++ b/src/lib/dns/rdata/generic/nsec3param_51.cc
@@ -0,0 +1,232 @@
+// 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 <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <memory>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct NSEC3PARAMImpl {
+ // straightforward representation of NSEC3PARAM RDATA fields
+ NSEC3PARAMImpl(uint8_t hashalg, uint8_t flags, uint16_t iterations,
+ const vector<uint8_t>& salt) :
+ hashalg_(hashalg), flags_(flags), iterations_(iterations), salt_(salt)
+ {}
+
+ const uint8_t hashalg_;
+ const uint8_t flags_;
+ const uint16_t iterations_;
+ const vector<uint8_t> salt_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3PARAM 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 Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3param_str A string containing the RDATA to be created
+NSEC3PARAM::NSEC3PARAM(const std::string& nsec3param_str) :
+ impl_(NULL)
+{
+ // 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 NSEC3PARAMImpl that constructFromLexer() returns.
+ std::unique_ptr<NSEC3PARAMImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(nsec3param_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3PARAM: " << nsec3param_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3PARAM from '" << nsec3param_str
+ << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3PARAM RDATA.
+///
+/// See \c NSEC3PARAM::NSEC3PARAM(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
+NSEC3PARAM::NSEC3PARAM(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3PARAMImpl*
+NSEC3PARAM::constructFromLexer(MasterLexer& lexer) {
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamFromLexer("NSEC3PARAM", lexer, salt);
+
+ return (new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt));
+}
+
+NSEC3PARAM::NSEC3PARAM(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamWire("NSEC3PARAM", buffer, rdata_len, salt);
+
+ impl_ = new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt);
+}
+
+NSEC3PARAM::NSEC3PARAM(const NSEC3PARAM& source) :
+ Rdata(), impl_(new NSEC3PARAMImpl(*source.impl_))
+{}
+
+NSEC3PARAM&
+NSEC3PARAM::operator=(const NSEC3PARAM& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ NSEC3PARAMImpl* newimpl = new NSEC3PARAMImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+NSEC3PARAM::~NSEC3PARAM() {
+ delete impl_;
+}
+
+string
+NSEC3PARAM::toText() const {
+ using boost::lexical_cast;
+ return (lexical_cast<string>(static_cast<int>(impl_->hashalg_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
+ " " + (impl_->salt_.empty() ? "-" : encodeHex(impl_->salt_)));
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireHelper(const NSEC3PARAMImpl& impl, OUTPUT_TYPE& output) {
+ output.writeUint8(impl.hashalg_);
+ output.writeUint8(impl.flags_);
+ output.writeUint16(impl.iterations_);
+ output.writeUint8(impl.salt_.size());
+ if (!impl.salt_.empty()) {
+ output.writeData(&impl.salt_[0], impl.salt_.size());
+ }
+}
+
+void
+NSEC3PARAM::toWire(OutputBuffer& buffer) const {
+ toWireHelper(*impl_, buffer);
+}
+
+void
+NSEC3PARAM::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(*impl_, renderer);
+}
+
+int
+NSEC3PARAM::compare(const Rdata& other) const {
+ const NSEC3PARAM& other_param = dynamic_cast<const NSEC3PARAM&>(other);
+
+ if (impl_->hashalg_ != other_param.impl_->hashalg_) {
+ return (impl_->hashalg_ < other_param.impl_->hashalg_ ? -1 : 1);
+ }
+ if (impl_->flags_ != other_param.impl_->flags_) {
+ return (impl_->flags_ < other_param.impl_->flags_ ? -1 : 1);
+ }
+ if (impl_->iterations_ != other_param.impl_->iterations_) {
+ return (impl_->iterations_ < other_param.impl_->iterations_ ? -1 : 1);
+ }
+
+ const size_t this_len = impl_->salt_.size();
+ const size_t other_len = other_param.impl_->salt_.size();
+ if (this_len != other_len) {
+ return (this_len - other_len);
+ }
+ const size_t cmplen = min(this_len, other_len);
+ const int cmp = (cmplen == 0) ? 0 :
+ memcmp(&impl_->salt_.at(0), &other_param.impl_->salt_.at(0), cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return (this_len - other_len);
+ }
+}
+
+uint8_t
+NSEC3PARAM::getHashalg() const {
+ return (impl_->hashalg_);
+}
+
+uint8_t
+NSEC3PARAM::getFlags() const {
+ return (impl_->flags_);
+}
+
+uint16_t
+NSEC3PARAM::getIterations() const {
+ return (impl_->iterations_);
+}
+
+const vector<uint8_t>&
+NSEC3PARAM::getSalt() const {
+ return (impl_->salt_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.h b/src/lib/dns/rdata/generic/nsec3param_51.h
new file mode 100644
index 0000000..1c7bf03
--- /dev/null
+++ b/src/lib/dns/rdata/generic/nsec3param_51.h
@@ -0,0 +1,56 @@
+// 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 <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+
+// BEGIN_HEADER_GUARD
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct NSEC3PARAMImpl;
+
+class NSEC3PARAM : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+ NSEC3PARAM& operator=(const NSEC3PARAM& source);
+ ~NSEC3PARAM();
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getHashalg() const;
+ uint8_t getFlags() const;
+ uint16_t getIterations() const;
+ const std::vector<uint8_t>& getSalt() const;
+
+private:
+ NSEC3PARAMImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
+ NSEC3PARAMImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
new file mode 100644
index 0000000..f8af0e0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -0,0 +1,216 @@
+// 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 <iostream>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include <util/encode/base64.h>
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail::nsec;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct NSECImpl {
+ // straightforward representation of NSEC RDATA fields
+ NSECImpl(const Name& next, vector<uint8_t> typebits) :
+ nextname_(next), typebits_(typebits)
+ {}
+
+ Name nextname_;
+ vector<uint8_t> typebits_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC 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 Next Domain Name field must be absolute since there's no
+/// parameter that specifies the origin name; if it is not absolute,
+/// \c MissingNameOrigin exception will be thrown. This must not be
+/// represented as a quoted string.
+///
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw MissingNameOrigin Thrown when the Next Domain Name is not absolute.
+/// \throw InvalidRdataText if any fields are out of their valid range.
+///
+/// \param nsec_str A string containing the RDATA to be created
+NSEC::NSEC(const std::string& nsec_str) :
+ impl_(NULL)
+{
+ try {
+ std::istringstream ss(nsec_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ const Name origin_name(createNameFromLexer(lexer, NULL));
+
+ vector<uint8_t> typebits;
+ buildBitmapsFromLexer("NSEC", lexer, typebits);
+
+ impl_ = new NSECImpl(origin_name, typebits);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC: " << nsec_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC from '" << nsec_str << "': "
+ << ex.what());
+ }
+}
+
+NSEC::NSEC(InputBuffer& buffer, size_t rdata_len) {
+ const size_t pos = buffer.getPosition();
+ const Name nextname(buffer);
+
+ // rdata_len must be sufficiently large to hold non empty bitmap.
+ if (rdata_len <= buffer.getPosition() - pos) {
+ isc_throw(DNSMessageFORMERR,
+ "NSEC RDATA from wire too short: " << rdata_len << "bytes");
+ }
+ rdata_len -= (buffer.getPosition() - pos);
+
+ vector<uint8_t> typebits(rdata_len);
+ buffer.readData(&typebits[0], rdata_len);
+ checkRRTypeBitmaps("NSEC", typebits);
+
+ impl_ = new NSECImpl(nextname, typebits);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC RDATA.
+///
+/// The Next Domain Name 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.
+///
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw MissingNameOrigin Thrown when the Next Domain Name is not
+/// absolute and \c origin is NULL.
+/// \throw InvalidRdataText if any fields are out of their valid range.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin The origin to use with a relative Next Domain Name
+/// field
+NSEC::NSEC(MasterLexer& lexer, const Name* origin, MasterLoader::Options,
+ MasterLoaderCallbacks&)
+{
+ const Name next_name(createNameFromLexer(lexer, origin));
+
+ vector<uint8_t> typebits;
+ buildBitmapsFromLexer("NSEC", lexer, typebits);
+
+ impl_ = new NSECImpl(next_name, typebits);
+}
+
+NSEC::NSEC(const NSEC& source) :
+ Rdata(), impl_(new NSECImpl(*source.impl_))
+{}
+
+NSEC&
+NSEC::operator=(const NSEC& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ NSECImpl* newimpl = new NSECImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+NSEC::~NSEC() {
+ delete impl_;
+}
+
+string
+NSEC::toText() const {
+ ostringstream s;
+ s << impl_->nextname_;
+ bitmapsToText(impl_->typebits_, s);
+ return (s.str());
+}
+
+void
+NSEC::toWire(OutputBuffer& buffer) const {
+ impl_->nextname_.toWire(buffer);
+ buffer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+}
+
+void
+NSEC::toWire(AbstractMessageRenderer& renderer) const {
+ // Type NSEC is not "well-known", and name compression must be disabled
+ // per RFC3597.
+ renderer.writeName(impl_->nextname_, false);
+ renderer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+}
+
+const Name&
+NSEC::getNextName() const {
+ return (impl_->nextname_);
+}
+
+int
+NSEC::compare(const Rdata& other) const {
+ const NSEC& other_nsec = dynamic_cast<const NSEC&>(other);
+
+ int cmp = compareNames(impl_->nextname_, other_nsec.impl_->nextname_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+
+ const size_t this_len = impl_->typebits_.size();
+ const size_t other_len = other_nsec.impl_->typebits_.size();
+ const size_t cmplen = min(this_len, other_len);
+ cmp = memcmp(&impl_->typebits_[0], &other_nsec.impl_->typebits_[0],
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/nsec_47.h b/src/lib/dns/rdata/generic/nsec_47.h
new file mode 100644
index 0000000..299d381
--- /dev/null
+++ b/src/lib/dns/rdata/generic/nsec_47.h
@@ -0,0 +1,53 @@
+// 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 <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+// BEGIN_HEADER_GUARD
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct NSECImpl;
+
+class NSEC : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+ NSEC& operator=(const NSEC& source);
+ ~NSEC();
+
+ // specialized methods
+
+ /// Return the next domain name.
+ ///
+ /// \exception std::bad_alloc Resource allocation failure in name copy.
+ ///
+ /// \return The next domain name field in the form of \c Name object.
+ const Name& getNextName() const;
+
+private:
+ NSECImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/opt_41.cc b/src/lib/dns/rdata/generic/opt_41.cc
new file mode 100644
index 0000000..40cb1c7
--- /dev/null
+++ b/src/lib/dns/rdata/generic/opt_41.cc
@@ -0,0 +1,219 @@
+// 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 <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <boost/foreach.hpp>
+
+#include <string>
+#include <string.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \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&) :
+ impl_(NULL)
+{
+ 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&) :
+ impl_(NULL)
+{
+ isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
+}
+
+OPT::OPT(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ std::unique_ptr<OPTImpl> impl_ptr(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_ptr->rdlength_ + option_length) <
+ impl_ptr->rdlength_)
+ {
+ isc_throw(InvalidRdataText,
+ "Option length " << option_length
+ << " would overflow OPT RR RDLEN (currently "
+ << impl_ptr->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_ptr->pseudo_rrs_.push_back(PseudoRR(option_code, option_data));
+ impl_ptr->rdlength_ += option_length;
+ rdata_len -= option_length;
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+OPT::OPT(const OPT& other) :
+ Rdata(), impl_(new OPTImpl(*other.impl_))
+{
+}
+
+OPT&
+OPT::operator=(const OPT& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ OPTImpl* newimpl = new OPTImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+OPT::~OPT() {
+ delete impl_;
+}
+
+std::string
+OPT::toText() const {
+ isc_throw(isc::InvalidOperation,
+ "OPT RRs do not have a presentation format");
+}
+
+void
+OPT::toWire(OutputBuffer& buffer) const {
+ BOOST_FOREACH(const PseudoRR& 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 {
+ BOOST_FOREACH(const PseudoRR& 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_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/opt_41.h b/src/lib/dns/rdata/generic/opt_41.h
new file mode 100644
index 0000000..0c00aed
--- /dev/null
+++ b/src/lib/dns/rdata/generic/opt_41.h
@@ -0,0 +1,90 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/rdata.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct OPTImpl;
+
+class OPT : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ // 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:
+ OPTImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/ptr_12.cc b/src/lib/dns/rdata/generic/ptr_12.cc
new file mode 100644
index 0000000..b3596e8
--- /dev/null
+++ b/src/lib/dns/rdata/generic/ptr_12.cc
@@ -0,0 +1,123 @@
+// 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \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, NULL);
+
+ 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_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/ptr_12.h b/src/lib/dns/rdata/generic/ptr_12.h
new file mode 100644
index 0000000..e562ef7
--- /dev/null
+++ b/src/lib/dns/rdata/generic/ptr_12.h
@@ -0,0 +1,44 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class PTR : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ ///
+ /// Specialized constructor
+ ///
+ explicit PTR(const Name& ptr_name) : ptr_name_(ptr_name) {}
+ ///
+ /// Specialized methods
+ ///
+ const Name& getPTRName() const;
+private:
+ Name ptr_name_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/rp_17.cc b/src/lib/dns/rdata/generic/rp_17.cc
new file mode 100644
index 0000000..43bf647
--- /dev/null
+++ b/src/lib/dns/rdata/generic/rp_17.cc
@@ -0,0 +1,160 @@
+// 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 <string>
+#include <sstream>
+
+#include <util/buffer.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// \c rp_str must be formatted as follows:
+/// \code <mailbox name> <text name>
+/// \endcode
+/// where both fields must represent a valid domain name.
+///
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// given name is invalid.
+RP::RP(const std::string& rp_str) :
+ // We cannot construct both names in the initialization list due to the
+ // necessary text processing, so we have to initialize them with a dummy
+ // name and replace them later.
+ mailbox_(Name::ROOT_NAME()), text_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(rp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mailbox_ = createNameFromLexer(lexer, NULL);
+ text_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for RP: "
+ << rp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct RP from '" <<
+ rp_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RP RDATA. The MAILBOX and TEXT fields can be non-absolute if \c
+/// origin is non-NULL, in which case \c origin is used to make them absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and 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 SERVER when it
+/// is non-absolute.
+RP::RP(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mailbox_(createNameFromLexer(lexer, origin)),
+ text_(createNameFromLexer(lexer, origin))
+{
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+RP::RP(InputBuffer& buffer, size_t) : mailbox_(buffer), text_(buffer) {
+}
+
+/// \brief Copy constructor.
+///
+/// \throw std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+RP::RP(const RP& other) :
+ Rdata(), mailbox_(other.mailbox_), text_(other.text_)
+{}
+
+/// \brief Convert the \c RP to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c RP(const std::string&))).
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c RP object.
+std::string
+RP::toText() const {
+ return (mailbox_.toText() + " " + text_.toText());
+}
+
+/// \brief Render the \c RP in the wire format without name compression.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+RP::toWire(OutputBuffer& buffer) const {
+ mailbox_.toWire(buffer);
+ text_.toWire(buffer);
+}
+
+/// \brief Render the \c RP in the wire format with taking into account
+/// compression.
+///
+// Type RP is not "well-known", and name compression must be disabled
+// per RFC3597.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+RP::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(mailbox_, false);
+ renderer.writeName(text_, false);
+}
+
+/// \brief Compare two instances of \c RP RDATA.
+///
+/// See documentation in \c Rdata.
+int
+RP::compare(const Rdata& other) const {
+ const RP& other_rp = dynamic_cast<const RP&>(other);
+
+ const int cmp = compareNames(mailbox_, other_rp.mailbox_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ return (compareNames(text_, other_rp.text_));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/rp_17.h b/src/lib/dns/rdata/generic/rp_17.h
new file mode 100644
index 0000000..9536ee9
--- /dev/null
+++ b/src/lib/dns/rdata/generic/rp_17.h
@@ -0,0 +1,78 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::generic::RP class represents the RP RDATA as defined in
+/// RFC1183.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// RP RDATA.
+class RP : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// We use the default copy constructor and assignment operator.
+
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// The parameters are a straightforward mapping of %RP RDATA
+ /// fields as defined in RFC1183.
+ RP(const Name& mailbox, const Name& text) :
+ mailbox_(mailbox), text_(text)
+ {}
+
+ /// \brief Return the value of the mailbox field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ ///
+ /// \note
+ /// Unlike the case of some other RDATA classes (such as
+ /// \c NS::getNSName()), this method constructs a new \c Name object
+ /// and returns it, instead of returning a reference to a \c Name object
+ /// internally maintained in the class (which is a private member).
+ /// This is based on the observation that this method will be rarely used
+ /// and even when it's used it will not be in a performance context
+ /// (for example, a recursive resolver won't need this field in its
+ /// resolution process). By returning a new object we have flexibility of
+ /// changing the internal representation without the risk of changing
+ /// the interface or method property.
+ /// The same note applies to the \c getText() method.
+ Name getMailbox() const { return (mailbox_); }
+
+ /// \brief Return the value of the text field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ Name getText() const { return (text_); }
+
+private:
+ Name mailbox_;
+ Name text_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
new file mode 100644
index 0000000..de92c67
--- /dev/null
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -0,0 +1,334 @@
+// 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 <iomanip>
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/encode/base64.h>
+#include <util/buffer.h>
+#include <util/time_utilities.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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
+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 (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) :
+ impl_(NULL)
+{
+ // 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.
+ std::unique_ptr<RRSIGImpl> impl_ptr;
+
+ try {
+ std::istringstream iss(rrsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
+
+ 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());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \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_ = 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);
+ }
+
+ RRSIGImpl* newimpl = new RRSIGImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+RRSIG::~RRSIG() {
+ delete impl_;
+}
+
+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_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
new file mode 100644
index 0000000..aca26ba
--- /dev/null
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -0,0 +1,54 @@
+// 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 <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+
+// BEGIN_HEADER_GUARD
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+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:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+ RRSIG& operator=(const RRSIG& source);
+ ~RRSIG();
+
+ // specialized methods
+ const RRType& typeCovered() const;
+private:
+ // helper function for string and lexer constructors
+ RRSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ RRSIGImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
new file mode 100644
index 0000000..ee4e43d
--- /dev/null
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -0,0 +1,210 @@
+// 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 <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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, NULL);
+ rname_ = createNameFromLexer(lexer, NULL);
+ 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);
+ 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_)));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/soa_6.h b/src/lib/dns/rdata/generic/soa_6.h
new file mode 100644
index 0000000..50a062e
--- /dev/null
+++ b/src/lib/dns/rdata/generic/soa_6.h
@@ -0,0 +1,51 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/serial.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class SOA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ 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];
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/spf_99.cc b/src/lib/dns/rdata/generic/spf_99.cc
new file mode 100644
index 0000000..f25585a
--- /dev/null
+++ b/src/lib/dns/rdata/generic/spf_99.cc
@@ -0,0 +1,139 @@
+// 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 <stdint.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+#include <dns/rdata/generic/detail/txt_like.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief The assignment operator
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+SPF&
+SPF::operator=(const SPF& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ SPFImpl* newimpl = new SPFImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+/// \brief The destructor
+SPF::~SPF() {
+ delete impl_;
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new SPFImpl(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.
+SPF::SPF(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(new SPFImpl(lexer))
+{}
+
+/// \brief Constructor from string.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+SPF::SPF(const std::string& txtstr) :
+ impl_(new SPFImpl(txtstr))
+{}
+
+/// \brief Copy constructor
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+SPF::SPF(const SPF& other) :
+ Rdata(), impl_(new SPFImpl(*other.impl_))
+{}
+
+/// \brief Render the \c SPF in the wire format to a OutputBuffer object
+///
+/// \return is the return of the corresponding implementation method.
+void
+SPF::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+/// \brief Render the \c SPF in the wire format to an AbstractMessageRenderer
+/// object
+///
+/// \return is the return of the corresponding implementation method.
+void
+SPF::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+/// \brief Convert the \c SPF to a string.
+///
+/// \return is the return of the corresponding implementation method.
+string
+SPF::toText() const {
+ return (impl_->toText());
+}
+
+/// \brief Compare two instances of \c SPF RDATA.
+///
+/// This method compares \c this and the \c other \c SPF objects.
+///
+/// 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 SPF object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+///
+/// \param other the right-hand operand to compare against.
+/// \return is the return of the corresponding implementation method.
+int
+SPF::compare(const Rdata& other) const {
+ const SPF& other_txt = dynamic_cast<const SPF&>(other);
+
+ return (impl_->compare(*other_txt.impl_));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/spf_99.h b/src/lib/dns/rdata/generic/spf_99.h
new file mode 100644
index 0000000..3a84d9d
--- /dev/null
+++ b/src/lib/dns/rdata/generic/spf_99.h
@@ -0,0 +1,72 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+namespace detail {
+template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
+
+/// \brief \c rdata::SPF class represents the SPF RDATA as defined %in
+/// RFC4408.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+class SPF : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \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.
+ SPF& operator=(const SPF& source);
+
+ /// \brief The destructor.
+ ~SPF();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return a reference to the data strings
+ ///
+ /// This method never throws an exception.
+ const std::vector<std::vector<uint8_t> >& getString() const;
+
+private:
+ typedef isc::dns::rdata::generic::detail::TXTLikeImpl<SPF, 99> SPFImpl;
+ SPFImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
new file mode 100644
index 0000000..a08a17f
--- /dev/null
+++ b/src/lib/dns/rdata/generic/sshfp_44.cc
@@ -0,0 +1,298 @@
+// Copyright (C) 2012-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 <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct SSHFPImpl {
+ // straightforward representation of SSHFP RDATA fields
+ SSHFPImpl(uint8_t algorithm, uint8_t fingerprint_type,
+ const vector<uint8_t>& fingerprint) :
+ algorithm_(algorithm),
+ fingerprint_type_(fingerprint_type),
+ fingerprint_(fingerprint)
+ {}
+
+ uint8_t algorithm_;
+ uint8_t fingerprint_type_;
+ const vector<uint8_t> fingerprint_;
+};
+
+// helper function for string and lexer constructors
+SSHFPImpl*
+SSHFP::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 255) {
+ isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
+ }
+
+ const uint32_t fingerprint_type =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (fingerprint_type > 255) {
+ isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
+ }
+
+ std::string fingerprint_str;
+ std::string fingerprint_substr;
+ 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(fingerprint_substr);
+ fingerprint_str.append(fingerprint_substr);
+ }
+ lexer.ungetToken();
+
+ vector<uint8_t> fingerprint;
+ // If fingerprint is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (fingerprint_str.size() > 0) {
+ try {
+ decodeHex(fingerprint_str, fingerprint);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
+ }
+ }
+
+ return (new SSHFPImpl(algorithm, fingerprint_type, fingerprint));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SSHFP 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 Algorithm and Fingerprint Type fields must be within their valid
+/// ranges, but are not constrained to the values defined in RFC4255.
+///
+/// The Fingerprint field may be absent, but if present it must contain a
+/// valid hex encoding of the fingerprint. For compatibility with BIND 9,
+/// whitespace is allowed in the hex text (RFC4255 is silent on the matter).
+///
+/// \throw InvalidRdataText if any fields are missing, are out of their
+/// valid ranges or are incorrect, or if the fingerprint is not a valid
+/// hex string.
+///
+/// \param sshfp_str A string containing the RDATA to be created
+SSHFP::SSHFP(const string& sshfp_str) :
+ impl_(NULL)
+{
+ // 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 SSHFPImpl that constructFromLexer() returns.
+ std::unique_ptr<SSHFPImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(sshfp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SSHFP: "
+ << sshfp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SSHFP from '" <<
+ sshfp_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SSHFP RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range or are
+/// incorrect, or if the fingerprint is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+SSHFP::SSHFP(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid SSHFP RDATA.
+///
+/// The Algorithm and Fingerprint Type fields are not checked for unknown
+/// values. It is okay for the fingerprint data to be missing (see the
+/// description of the constructor from string).
+SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "SSHFP record too short");
+ }
+
+ const uint8_t algorithm = buffer.readUint8();
+ const uint8_t fingerprint_type = buffer.readUint8();
+
+ vector<uint8_t> fingerprint;
+ rdata_len -= 2;
+ if (rdata_len > 0) {
+ fingerprint.resize(rdata_len);
+ buffer.readData(&fingerprint[0], rdata_len);
+ }
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
+}
+
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const string& fingerprint_txt) :
+ impl_(NULL)
+{
+ vector<uint8_t> fingerprint;
+ try {
+ decodeHex(fingerprint_txt, fingerprint);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
+ }
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
+}
+
+SSHFP::SSHFP(const SSHFP& other) :
+ Rdata(), impl_(new SSHFPImpl(*other.impl_))
+{}
+
+SSHFP&
+SSHFP::operator=(const SSHFP& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ SSHFPImpl* newimpl = new SSHFPImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+SSHFP::~SSHFP() {
+ delete impl_;
+}
+
+void
+SSHFP::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(impl_->algorithm_);
+ buffer.writeUint8(impl_->fingerprint_type_);
+
+ if (!impl_->fingerprint_.empty()) {
+ buffer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
+ }
+}
+
+void
+SSHFP::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(impl_->algorithm_);
+ renderer.writeUint8(impl_->fingerprint_type_);
+
+ if (!impl_->fingerprint_.empty()) {
+ renderer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
+ }
+}
+
+string
+SSHFP::toText() const {
+ return (lexical_cast<string>(static_cast<int>(impl_->algorithm_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->fingerprint_type_)) +
+ (impl_->fingerprint_.empty() ? "" :
+ " " + encodeHex(impl_->fingerprint_)));
+}
+
+int
+SSHFP::compare(const Rdata& other) const {
+ const SSHFP& other_sshfp = dynamic_cast<const SSHFP&>(other);
+
+ if (impl_->algorithm_ < other_sshfp.impl_->algorithm_) {
+ return (-1);
+ } else if (impl_->algorithm_ > other_sshfp.impl_->algorithm_) {
+ return (1);
+ }
+
+ if (impl_->fingerprint_type_ < other_sshfp.impl_->fingerprint_type_) {
+ return (-1);
+ } else if (impl_->fingerprint_type_ >
+ other_sshfp.impl_->fingerprint_type_) {
+ return (1);
+ }
+
+ const size_t this_len = impl_->fingerprint_.size();
+ const size_t other_len = other_sshfp.impl_->fingerprint_.size();
+ const size_t cmplen = min(this_len, other_len);
+
+ if (cmplen > 0) {
+ const int cmp = memcmp(&impl_->fingerprint_[0],
+ &other_sshfp.impl_->fingerprint_[0],
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ }
+
+ if (this_len == other_len) {
+ return (0);
+ } else if (this_len < other_len) {
+ return (-1);
+ } else {
+ return (1);
+ }
+}
+
+uint8_t
+SSHFP::getAlgorithmNumber() const {
+ return (impl_->algorithm_);
+}
+
+uint8_t
+SSHFP::getFingerprintType() const {
+ return (impl_->fingerprint_type_);
+}
+
+const std::vector<uint8_t>&
+SSHFP::getFingerprint() const {
+ return (impl_->fingerprint_);
+}
+
+size_t
+SSHFP::getFingerprintLength() const {
+ return (impl_->fingerprint_.size());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/sshfp_44.h b/src/lib/dns/rdata/generic/sshfp_44.h
new file mode 100644
index 0000000..4eae696
--- /dev/null
+++ b/src/lib/dns/rdata/generic/sshfp_44.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2012-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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct SSHFPImpl;
+
+class SSHFP : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const std::string& fingerprint);
+ SSHFP& operator=(const SSHFP& source);
+ ~SSHFP();
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getAlgorithmNumber() const;
+ uint8_t getFingerprintType() const;
+ const std::vector<uint8_t>& getFingerprint() const;
+ size_t getFingerprintLength() const;
+
+private:
+ SSHFPImpl* constructFromLexer(MasterLexer& lexer);
+
+ SSHFPImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/tkey_249.cc b/src/lib/dns/rdata/generic/tkey_249.cc
new file mode 100644
index 0000000..435a345
--- /dev/null
+++ b/src/lib/dns/rdata/generic/tkey_249.cc
@@ -0,0 +1,613 @@
+// Copyright (C) 2021 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 <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+#include <util/time_utilities.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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
+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 (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) : impl_(0) {
+ // 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 TKEYImpl that constructFromLexer() returns.
+ std::unique_ptr<TKEYImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(tkey_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(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());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \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) :
+ impl_(0)
+{
+ 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_ = 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) :
+ impl_(0)
+{
+ 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_ = 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);
+ }
+
+ TKEYImpl* newimpl = new TKEYImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TKEY::~TKEY() {
+ delete impl_;
+}
+
+/// \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);
+ }
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/tkey_249.h b/src/lib/dns/rdata/generic/tkey_249.h
new file mode 100644
index 0000000..d630121
--- /dev/null
+++ b/src/lib/dns/rdata/generic/tkey_249.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2021 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+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:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \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:
+ TKEYImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ TKEYImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/tlsa_52.cc b/src/lib/dns/rdata/generic/tlsa_52.cc
new file mode 100644
index 0000000..330b7a2
--- /dev/null
+++ b/src/lib/dns/rdata/generic/tlsa_52.cc
@@ -0,0 +1,342 @@
+// Copyright (C) 2014-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 <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata_pimpl_holder.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct TLSAImpl {
+ // straightforward representation of TLSA RDATA fields
+ TLSAImpl(uint8_t certificate_usage, uint8_t selector,
+ uint8_t matching_type, const vector<uint8_t>& data) :
+ certificate_usage_(certificate_usage),
+ selector_(selector),
+ matching_type_(matching_type),
+ data_(data)
+ {}
+
+ uint8_t certificate_usage_;
+ uint8_t selector_;
+ uint8_t matching_type_;
+ const vector<uint8_t> data_;
+};
+
+// helper function for string and lexer constructors
+TLSAImpl*
+TLSA::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t certificate_usage =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (certificate_usage > 255) {
+ isc_throw(InvalidRdataText,
+ "TLSA certificate usage field out of range");
+ }
+
+ const uint32_t selector =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (selector > 255) {
+ isc_throw(InvalidRdataText,
+ "TLSA selector field out of range");
+ }
+
+ const uint32_t matching_type =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (matching_type > 255) {
+ isc_throw(InvalidRdataText,
+ "TLSA matching type field out of range");
+ }
+
+ std::string certificate_assoc_data;
+ std::string data_substr;
+ 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(data_substr);
+ certificate_assoc_data.append(data_substr);
+ }
+ lexer.ungetToken();
+
+ if (certificate_assoc_data.empty()) {
+ isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+ }
+
+ vector<uint8_t> data;
+ try {
+ decodeHex(certificate_assoc_data, data);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText,
+ "Bad TLSA certificate association data: " << e.what());
+ }
+
+ return (new TLSAImpl(certificate_usage, selector, matching_type, data));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid TLSA 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 Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698.
+///
+/// The Certificate Association Data Field field may be absent, but if
+/// present it must contain a valid hex encoding of the data. Whitespace
+/// is allowed in the hex text.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, or are incorrect, or Certificate Association Data is
+/// not a valid hex string.
+///
+/// \param tlsa_str A string containing the RDATA to be created
+TLSA::TLSA(const string& tlsa_str) :
+ impl_(NULL)
+{
+ // We use a smart pointer here because if there is an exception in
+ // this constructor, the destructor is not called and there could be
+ // a leak of the TLSAImpl that constructFromLexer() returns.
+ RdataPimplHolder<TLSAImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(tlsa_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for TLSA: "
+ << tlsa_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct TLSA from '" <<
+ tlsa_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an TLSA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range, or are
+/// incorrect, or Certificate Association Data is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TLSA::TLSA(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid TLSA RDATA.
+///
+/// The Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698. It is okay for the certificate association data
+/// to be missing (see the description of the constructor from string).
+TLSA::TLSA(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 3) {
+ isc_throw(InvalidRdataLength, "TLSA record too short");
+ }
+
+ const uint8_t certificate_usage = buffer.readUint8();
+ const uint8_t selector = buffer.readUint8();
+ const uint8_t matching_type = buffer.readUint8();
+
+ vector<uint8_t> data;
+ rdata_len -= 3;
+
+ if (rdata_len == 0) {
+ isc_throw(InvalidRdataLength,
+ "Empty TLSA certificate association data");
+ }
+
+ data.resize(rdata_len);
+ buffer.readData(&data[0], rdata_len);
+
+ impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(uint8_t certificate_usage, uint8_t selector,
+ uint8_t matching_type, const std::string& certificate_assoc_data) :
+ impl_(NULL)
+{
+ if (certificate_assoc_data.empty()) {
+ isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+ }
+
+ vector<uint8_t> data;
+ try {
+ decodeHex(certificate_assoc_data, data);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText,
+ "Bad TLSA certificate association data: " << e.what());
+ }
+
+ impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(const TLSA& other) :
+ Rdata(), impl_(new TLSAImpl(*other.impl_))
+{}
+
+TLSA&
+TLSA::operator=(const TLSA& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ TLSAImpl* newimpl = new TLSAImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TLSA::~TLSA() {
+ delete impl_;
+}
+
+void
+TLSA::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(impl_->certificate_usage_);
+ buffer.writeUint8(impl_->selector_);
+ buffer.writeUint8(impl_->matching_type_);
+
+ // The constructors must ensure that the certificate association
+ // data field is not empty.
+ assert(!impl_->data_.empty());
+ buffer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+void
+TLSA::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(impl_->certificate_usage_);
+ renderer.writeUint8(impl_->selector_);
+ renderer.writeUint8(impl_->matching_type_);
+
+ // The constructors must ensure that the certificate association
+ // data field is not empty.
+ assert(!impl_->data_.empty());
+ renderer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+string
+TLSA::toText() const {
+ // The constructors must ensure that the certificate association
+ // data field is not empty.
+ assert(!impl_->data_.empty());
+
+ return (lexical_cast<string>(static_cast<int>(impl_->certificate_usage_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->selector_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->matching_type_)) + " " +
+ encodeHex(impl_->data_));
+}
+
+int
+TLSA::compare(const Rdata& other) const {
+ const TLSA& other_tlsa = dynamic_cast<const TLSA&>(other);
+
+ if (impl_->certificate_usage_ < other_tlsa.impl_->certificate_usage_) {
+ return (-1);
+ } else if (impl_->certificate_usage_ >
+ other_tlsa.impl_->certificate_usage_) {
+ return (1);
+ }
+
+ if (impl_->selector_ < other_tlsa.impl_->selector_) {
+ return (-1);
+ } else if (impl_->selector_ > other_tlsa.impl_->selector_) {
+ return (1);
+ }
+
+ if (impl_->matching_type_ < other_tlsa.impl_->matching_type_) {
+ return (-1);
+ } else if (impl_->matching_type_ >
+ other_tlsa.impl_->matching_type_) {
+ return (1);
+ }
+
+ const size_t this_len = impl_->data_.size();
+ const size_t other_len = other_tlsa.impl_->data_.size();
+ const size_t cmplen = min(this_len, other_len);
+
+ if (cmplen > 0) {
+ const int cmp = memcmp(&impl_->data_[0],
+ &other_tlsa.impl_->data_[0],
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ }
+
+ if (this_len == other_len) {
+ return (0);
+ } else if (this_len < other_len) {
+ return (-1);
+ } else {
+ return (1);
+ }
+}
+
+uint8_t
+TLSA::getCertificateUsage() const {
+ return (impl_->certificate_usage_);
+}
+
+uint8_t
+TLSA::getSelector() const {
+ return (impl_->selector_);
+}
+
+uint8_t
+TLSA::getMatchingType() const {
+ return (impl_->matching_type_);
+}
+
+const std::vector<uint8_t>&
+TLSA::getData() const {
+ return (impl_->data_);
+}
+
+size_t
+TLSA::getDataLength() const {
+ return (impl_->data_.size());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/tlsa_52.h b/src/lib/dns/rdata/generic/tlsa_52.h
new file mode 100644
index 0000000..007aa43
--- /dev/null
+++ b/src/lib/dns/rdata/generic/tlsa_52.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2014-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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <vector>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct TLSAImpl;
+
+class TLSA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ TLSA(uint8_t certificate_usage, uint8_t selector,
+ uint8_t matching_type, const std::string& certificate_assoc_data);
+ TLSA& operator=(const TLSA& source);
+ ~TLSA();
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getCertificateUsage() const;
+ uint8_t getSelector() const;
+ uint8_t getMatchingType() const;
+ const std::vector<uint8_t>& getData() const;
+ size_t getDataLength() const;
+
+private:
+ TLSAImpl* constructFromLexer(MasterLexer& lexer);
+
+ TLSAImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/txt_16.cc b/src/lib/dns/rdata/generic/txt_16.cc
new file mode 100644
index 0000000..52d6b64
--- /dev/null
+++ b/src/lib/dns/rdata/generic/txt_16.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 <stdint.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/txt_like.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+TXT&
+TXT::operator=(const TXT& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ TXTImpl* newimpl = new TXTImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TXT::~TXT() {
+ delete impl_;
+}
+
+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_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/txt_16.h b/src/lib/dns/rdata/generic/txt_16.h
new file mode 100644
index 0000000..83979c4
--- /dev/null
+++ b/src/lib/dns/rdata/generic/txt_16.h
@@ -0,0 +1,46 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+namespace detail {
+template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
+
+class TXT : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ TXT& operator=(const TXT& source);
+ ~TXT();
+
+private:
+ typedef isc::dns::rdata::generic::detail::TXTLikeImpl<TXT, 16> TXTImpl;
+ TXTImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/hs_4/a_1.cc b/src/lib/dns/rdata/hs_4/a_1.cc
new file mode 100644
index 0000000..cd4c824
--- /dev/null
+++ b/src/lib/dns/rdata/hs_4/a_1.cc
@@ -0,0 +1,64 @@
+// 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 <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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 {
+ return (0); // dummy. TBD
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/hs_4/a_1.h b/src/lib/dns/rdata/hs_4/a_1.h
new file mode 100644
index 0000000..6f319b9
--- /dev/null
+++ b/src/lib/dns/rdata/hs_4/a_1.h
@@ -0,0 +1,32 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class A : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/in_1/a_1.cc b/src/lib/dns/rdata/in_1/a_1.cc
new file mode 100644
index 0000000..c6585b9
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/a_1.cc
@@ -0,0 +1,174 @@
+// 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 <stdint.h>
+#include <string.h>
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+
+#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
+#include <sys/socket.h> // for AF_INET/AF_INET6
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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)) == NULL) {
+ 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_)));
+}
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/in_1/a_1.h b/src/lib/dns/rdata/in_1/a_1.h
new file mode 100644
index 0000000..9aaeea8
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/a_1.h
@@ -0,0 +1,38 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class A : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ //We can use the default destructor.
+ //virtual ~A() {}
+ // notyet:
+ //const struct in_addr& getAddress() const { return (addr_); }
+private:
+ uint32_t addr_; // raw IPv4 address (network byte order)
+};
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/in_1/aaaa_28.cc b/src/lib/dns/rdata/in_1/aaaa_28.cc
new file mode 100644
index 0000000..c967935
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/aaaa_28.cc
@@ -0,0 +1,153 @@
+// 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 <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+
+#include <stdint.h>
+#include <string.h>
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+
+#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
+#include <sys/socket.h> // for AF_INET/AF_INET6
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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))
+ == NULL) {
+ 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_)));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/in_1/aaaa_28.h b/src/lib/dns/rdata/in_1/aaaa_28.h
new file mode 100644
index 0000000..a5cabf4
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/aaaa_28.h
@@ -0,0 +1,38 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+class AAAA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+ // notyet:
+ //const struct in6_addr& getAddress() const { return (addr_); }
+private:
+ uint8_t addr_[16]; // raw IPv6 address (network byte order)
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.cc b/src/lib/dns/rdata/in_1/dhcid_49.cc
new file mode 100644
index 0000000..57c79c2
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/dhcid_49.cc
@@ -0,0 +1,161 @@
+// 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 <stdint.h>
+#include <string.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+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_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.h b/src/lib/dns/rdata/in_1/dhcid_49.h
new file mode 100644
index 0000000..7f79602
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/dhcid_49.h
@@ -0,0 +1,53 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \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:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \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_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/in_1/srv_33.cc b/src/lib/dns/rdata/in_1/srv_33.cc
new file mode 100644
index 0000000..a8a050c
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/srv_33.cc
@@ -0,0 +1,298 @@
+// 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 <iostream>
+#include <sstream>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/buffer.h>
+#include <util/strutil.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::str;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+struct SRVImpl {
+ // straightforward representation of SRV RDATA fields
+ SRVImpl(uint16_t priority, uint16_t weight, uint16_t port,
+ const Name& target) :
+ priority_(priority), weight_(weight), port_(port),
+ target_(target)
+ {}
+
+ uint16_t priority_;
+ uint16_t weight_;
+ uint16_t port_;
+ Name target_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SRV 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 TARGET name must be absolute since there's no parameter that
+/// specifies the origin name; if it is not absolute, \c MissingNameOrigin
+/// exception will be thrown. It 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.
+SRV::SRV(const std::string& srv_str) :
+ impl_(NULL)
+{
+ try {
+ std::istringstream ss(srv_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV priority in: " << srv_str);
+ }
+ const uint16_t priority = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV weight in: " << srv_str);
+ }
+ const uint16_t weight = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV port in: " << srv_str);
+ }
+ const uint16_t port = static_cast<uint16_t>(num);
+
+ const Name targetname = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SRV: "
+ << srv_str);
+ }
+
+ impl_ = new SRVImpl(priority, weight, port, targetname);
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SRV from '" <<
+ srv_str << "': " << ex.what());
+ }
+}
+
+/// \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 end 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 RFC2782, the Target field must be a non compressed form
+/// of domain name. But this implementation accepts a %SRV RR even if that
+/// field is compressed as suggested in RFC3597.
+///
+/// \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.
+SRV::SRV(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 6) {
+ isc_throw(InvalidRdataLength, "SRV too short");
+ }
+
+ const uint16_t priority = buffer.readUint16();
+ const uint16_t weight = buffer.readUint16();
+ const uint16_t port = buffer.readUint16();
+ const Name targetname(buffer);
+
+ impl_ = new SRVImpl(priority, weight, port, targetname);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SRV RDATA. The TARGET 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.
+///
+/// The PRIORITY, WEIGHT and PORT fields must each be a valid decimal
+/// representation of an unsigned 16-bit integers respectively.
+///
+/// \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 TARGET when it
+/// is non-absolute.
+SRV::SRV(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV priority: " << num);
+ }
+ const uint16_t priority = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV weight: " << num);
+ }
+ const uint16_t weight = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV port: " << num);
+ }
+ const uint16_t port = static_cast<uint16_t>(num);
+
+ const Name targetname = createNameFromLexer(lexer, origin);
+
+ impl_ = new SRVImpl(priority, weight, port, targetname);
+}
+
+/// \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.
+SRV::SRV(const SRV& source) :
+ Rdata(), impl_(new SRVImpl(*source.impl_))
+{}
+
+SRV&
+SRV::operator=(const SRV& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ SRVImpl* newimpl = new SRVImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+SRV::~SRV() {
+ delete impl_;
+}
+
+/// \brief Convert the \c SRV to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c SRV(const std::string&))).
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+///
+/// \return A \c string object that represents the \c SRV object.
+string
+SRV::toText() const {
+ using boost::lexical_cast;
+ return (lexical_cast<string>(impl_->priority_) +
+ " " + lexical_cast<string>(impl_->weight_) +
+ " " + lexical_cast<string>(impl_->port_) +
+ " " + impl_->target_.toText());
+}
+
+/// \brief Render the \c SRV 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
+SRV::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(impl_->priority_);
+ buffer.writeUint16(impl_->weight_);
+ buffer.writeUint16(impl_->port_);
+ impl_->target_.toWire(buffer);
+}
+
+/// \brief Render the \c SRV in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC2782, the Target 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
+SRV::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(impl_->priority_);
+ renderer.writeUint16(impl_->weight_);
+ renderer.writeUint16(impl_->port_);
+ renderer.writeName(impl_->target_, false);
+}
+
+/// \brief Compare two instances of \c SRV RDATA.
+///
+/// See documentation in \c Rdata.
+int
+SRV::compare(const Rdata& other) const {
+ const SRV& other_srv = dynamic_cast<const SRV&>(other);
+
+ if (impl_->priority_ != other_srv.impl_->priority_) {
+ return (impl_->priority_ < other_srv.impl_->priority_ ? -1 : 1);
+ }
+ if (impl_->weight_ != other_srv.impl_->weight_) {
+ return (impl_->weight_ < other_srv.impl_->weight_ ? -1 : 1);
+ }
+ if (impl_->port_ != other_srv.impl_->port_) {
+ return (impl_->port_ < other_srv.impl_->port_ ? -1 : 1);
+ }
+
+ return (compareNames(impl_->target_, other_srv.impl_->target_));
+}
+
+uint16_t
+SRV::getPriority() const {
+ return (impl_->priority_);
+}
+
+uint16_t
+SRV::getWeight() const {
+ return (impl_->weight_);
+}
+
+uint16_t
+SRV::getPort() const {
+ return (impl_->port_);
+}
+
+const Name&
+SRV::getTarget() const {
+ return (impl_->target_);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/in_1/srv_33.h b/src/lib/dns/rdata/in_1/srv_33.h
new file mode 100644
index 0000000..aca210e
--- /dev/null
+++ b/src/lib/dns/rdata/in_1/srv_33.h
@@ -0,0 +1,85 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+struct SRVImpl;
+
+/// \brief \c rdata::SRV class represents the SRV RDATA as defined %in
+/// RFC2782.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// SRV RDATA.
+class SRV : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// \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.
+ SRV& operator=(const SRV& source);
+
+ /// \brief The destructor.
+ ~SRV();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return the value of the priority field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getPriority() const;
+
+ /// \brief Return the value of the weight field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getWeight() const;
+
+ /// \brief Return the value of the port field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getPort() const;
+
+ /// \brief Return the value of the target field.
+ ///
+ /// \return A reference to a \c Name class object corresponding to the
+ /// internal target name.
+ ///
+ /// This method never throws an exception.
+ const Name& getTarget() const;
+
+private:
+ SRVImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/template.cc b/src/lib/dns/rdata/template.cc
new file mode 100644
index 0000000..d205855
--- /dev/null
+++ b/src/lib/dns/rdata/template.cc
@@ -0,0 +1,67 @@
+// 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 <string>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace isc::util;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+// To add RDATA implementation of a new RR type (say "MyType"), copy this
+// template into the appropriate subdirectory with the appropriate name
+// (see template.h).
+// Then define (at least) the following common methods (that are inherited
+// from the base abstract class).
+// If you added member functions specific to this derived class, you'll need
+// to implement them here, of course.
+
+MyType::MyType(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options, MasterLoaderCallbacks& callbacks)
+{
+}
+
+MyType::MyType(const string& type_str) {
+}
+
+MyType::MyType(InputBuffer& buffer, size_t rdata_len) {
+}
+
+MyType::MyType(const MyType& source) {
+}
+
+std::string
+MyType::toText() const {
+}
+
+void
+MyType::toWire(OutputBuffer& buffer) const {
+}
+
+void
+MyType::toWire(AbstractMessageRenderer& renderer) const {
+}
+
+int
+MyType::compare(const Rdata&) const {
+ // The compare method normally begins with this dynamic cast.
+ // cppcheck-suppress unreadVariable
+ // const MyType& other_mytype = dynamic_cast<const MyType&>(other);
+ // ...
+ return (0);
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/template.h b/src/lib/dns/rdata/template.h
new file mode 100644
index 0000000..d74790e
--- /dev/null
+++ b/src/lib/dns/rdata/template.h
@@ -0,0 +1,54 @@
+// 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/.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+// To add RDATA class definition of a new RR type (say "MyType"), copy this
+// file to an appropriate subdirectory (if it's class-independent type, it
+// should go to "generic/", if it's IN-class specific, it should be in
+// "in_1/", and so on). The copied file should be named as type_nn.h where
+// "type" is textual representation (all lower cased) of the RR type, and "nn"
+// is the 16-bit type code of the RR type.
+// Normally, you'll need to define some specific member variables in the
+// "RR-type specific members" space (please make them private). In addition,
+// you may want to define some specific member functions, either public or
+// private (or, though unlikely for a leaf class, protected).
+//
+// Note: do not remove the comment lines beginning with "BEGIN_" and "END_".
+// These are markers used by a script for auto-generating build-able source
+// files.
+//
+// On completion of implementing a new type of Rdata, remove the corresponding
+// entry from the meta_types dictionary of gen-rdatacode.py.in. Otherwise
+// it will cause build failure.
+
+class MyType : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // Do not remove the BEGIN_xxx and END_xxx comment lines.
+ // END_COMMON_MEMBERS
+private:
+ // RR-type specific members are here.
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata_pimpl_holder.h b/src/lib/dns/rdata_pimpl_holder.h
new file mode 100644
index 0000000..baa343a
--- /dev/null
+++ b/src/lib/dns/rdata_pimpl_holder.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2014-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/.
+
+#ifndef DNS_RDATA_PIMPL_HOLDER_H
+#define DNS_RDATA_PIMPL_HOLDER_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <cstddef> // for NULL
+
+namespace isc {
+namespace dns {
+namespace rdata {
+
+template <typename T>
+class RdataPimplHolder : boost::noncopyable {
+public:
+ RdataPimplHolder(T* obj = NULL) :
+ obj_(obj)
+ {}
+
+ ~RdataPimplHolder() {
+ delete obj_;
+ }
+
+ void reset(T* obj = NULL) {
+ delete obj_;
+ obj_ = obj;
+ }
+
+ T* get() {
+ return (obj_);
+ }
+
+ T* release() {
+ T* obj = obj_;
+ obj_ = NULL;
+ return (obj);
+ }
+
+private:
+ T* obj_;
+};
+
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+
+#endif // DNS_RDATA_PIMPL_HOLDER_H
diff --git a/src/lib/dns/rdataclass.cc b/src/lib/dns/rdataclass.cc
new file mode 100644
index 0000000..dcd5e13
--- /dev/null
+++ b/src/lib/dns/rdataclass.cc
@@ -0,0 +1,7078 @@
+///////////////
+///////////////
+/////////////// THIS FILE IS AUTOMATICALLY GENERATED BY gen-rdatacode.py.
+/////////////// DO NOT EDIT!
+///////////////
+///////////////
+
+// Copyright (C) 2010-2021 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 <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/tsigkey.h>
+#include <dns/tsigerror.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+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
+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 (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) : impl_(NULL) {
+ // 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 TSIGImpl that constructFromLexer() returns.
+ std::unique_ptr<TSIGImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(tsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
+
+ 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());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \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) :
+ impl_(NULL)
+{
+ 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_ = 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) :
+ impl_(NULL)
+{
+ // 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 != NULL) || (mac_size > 0 && mac == NULL)) {
+ isc_throw(InvalidParameter, "TSIG MAC size and data inconsistent");
+ }
+ if ((other_len == 0 && other_data != NULL) ||
+ (other_len > 0 && other_data == NULL)) {
+ 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_ = 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);
+ }
+
+ TSIGImpl* newimpl = new TSIGImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TSIG::~TSIG() {
+ delete impl_;
+}
+
+/// \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 (NULL);
+ }
+}
+
+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 (NULL);
+ }
+}
+
+} // end of namespace "any"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+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 {
+ return (0); // dummy. TBD
+}
+
+} // end of namespace "ch"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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 <util/buffer.h>
+#include <util/strutil.h>
+
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \brief Constructor from string.
+///
+/// \c afsdb_str must be formatted as follows:
+/// \code <subtype> <server name>
+/// \endcode
+/// where server name field must represent a valid domain name.
+///
+/// An example of valid string is:
+/// \code "1 server.example.com." \endcode
+///
+/// <b>Exceptions</b>
+///
+/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \exception std::bad_alloc Memory allocation fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the string is invalid.
+AFSDB::AFSDB(const std::string& afsdb_str) :
+ subtype_(0), server_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(afsdb_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ createFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for AFSDB: "
+ << afsdb_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct AFSDB from '" <<
+ afsdb_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an AFSDB RDATA. The SERVER 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.
+///
+/// The SUBTYPE field must be a valid decimal representation of an
+/// unsigned 16-bit integer.
+///
+/// \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 SERVER when it
+/// is non-absolute.
+AFSDB::AFSDB(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ subtype_(0), server_(".")
+{
+ createFromLexer(lexer, origin);
+}
+
+void
+AFSDB::createFromLexer(MasterLexer& lexer, const Name* origin)
+{
+ const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid AFSDB subtype: " << num);
+ }
+ subtype_ = static_cast<uint16_t>(num);
+
+ server_ = createNameFromLexer(lexer, origin);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \exception std::bad_alloc Memory allocation fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+AFSDB::AFSDB(InputBuffer& buffer, size_t) :
+ subtype_(buffer.readUint16()), server_(buffer)
+{}
+
+/// \brief Copy constructor.
+///
+/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+AFSDB::AFSDB(const AFSDB& other) :
+ Rdata(), subtype_(other.subtype_), server_(other.server_)
+{}
+
+AFSDB&
+AFSDB::operator=(const AFSDB& source) {
+ subtype_ = source.subtype_;
+ server_ = source.server_;
+
+ return (*this);
+}
+
+/// \brief Convert the \c AFSDB to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c AFSDB(const std::string&))).
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c AFSDB object.
+string
+AFSDB::toText() const {
+ return (lexical_cast<string>(subtype_) + " " + server_.toText());
+}
+
+/// \brief Render the \c AFSDB in the wire format without name compression.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+AFSDB::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(subtype_);
+ server_.toWire(buffer);
+}
+
+/// \brief Render the \c AFSDB in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, TYPE AFSDB is not "well-known", the server
+/// field (domain name) will not be compressed.
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+AFSDB::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(subtype_);
+ renderer.writeName(server_, false);
+}
+
+/// \brief Compare two instances of \c AFSDB RDATA.
+///
+/// See documentation in \c Rdata.
+int
+AFSDB::compare(const Rdata& other) const {
+ const AFSDB& other_afsdb = dynamic_cast<const AFSDB&>(other);
+ if (subtype_ < other_afsdb.subtype_) {
+ return (-1);
+ } else if (subtype_ > other_afsdb.subtype_) {
+ return (1);
+ }
+
+ return (compareNames(server_, other_afsdb.server_));
+}
+
+const Name&
+AFSDB::getServer() const {
+ return (server_);
+}
+
+uint16_t
+AFSDB::getSubtype() const {
+ return (subtype_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2014-2021 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 <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/char_string.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+struct CAAImpl {
+ // straightforward representation of CAA RDATA fields
+ CAAImpl(uint8_t flags, const std::string& tag,
+ const detail::CharStringData& value) :
+ flags_(flags),
+ tag_(tag),
+ value_(value)
+ {
+ if ((sizeof(flags) + 1 + tag_.size() + value_.size()) > 65535) {
+ isc_throw(InvalidRdataLength,
+ "CAA Value field is too large: " << value_.size());
+ }
+ }
+
+ uint8_t flags_;
+ const std::string tag_;
+ const detail::CharStringData value_;
+};
+
+// helper function for string and lexer constructors
+CAAImpl*
+CAA::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t flags =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (flags > 255) {
+ isc_throw(InvalidRdataText,
+ "CAA flags field out of range");
+ }
+
+ // Tag field must not be empty.
+ const std::string tag =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ if (tag.empty()) {
+ isc_throw(InvalidRdataText, "CAA tag field is empty");
+ } else if (tag.size() > 255) {
+ isc_throw(InvalidRdataText,
+ "CAA tag field is too large: " << tag.size());
+ }
+
+ // Value field may be empty.
+ detail::CharStringData value;
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING, true);
+ if ((token.getType() != MasterToken::END_OF_FILE) &&
+ (token.getType() != MasterToken::END_OF_LINE))
+ {
+ detail::stringToCharStringData(token.getStringRegion(), value);
+ }
+
+ return (new CAAImpl(flags, tag, value));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid CAA 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 Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, incorrect, or empty.
+///
+/// \param caa_str A string containing the RDATA to be created
+CAA::CAA(const string& caa_str) :
+ impl_(NULL)
+{
+ // 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 CAAImpl that constructFromLexer() returns.
+ std::unique_ptr<CAAImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(caa_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for CAA: "
+ << caa_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct CAA from '" <<
+ caa_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an CAA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing
+/// field.
+/// \throw InvalidRdataText Fields are out of their valid ranges,
+/// incorrect, or empty.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+CAA::CAA(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid CAA RDATA.
+///
+/// The Flags, Tag and Value fields must be within their valid ranges,
+/// but are not constrained to the values defined in RFC6844. The Tag
+/// field must not be empty.
+CAA::CAA(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "CAA record too short");
+ }
+
+ const uint8_t flags = buffer.readUint8();
+ const uint8_t tag_length = buffer.readUint8();
+ rdata_len -= 2;
+ if (tag_length == 0) {
+ isc_throw(InvalidRdataText, "CAA tag field is empty");
+ }
+
+ if (rdata_len < tag_length) {
+ isc_throw(InvalidRdataLength,
+ "RDATA is too short for CAA tag field");
+ }
+
+ std::vector<uint8_t> tag_vec(tag_length);
+ buffer.readData(&tag_vec[0], tag_length);
+ std::string tag(tag_vec.begin(), tag_vec.end());
+ rdata_len -= tag_length;
+
+ detail::CharStringData value;
+ value.resize(rdata_len);
+ if (rdata_len > 0) {
+ buffer.readData(&value[0], rdata_len);
+ }
+
+ impl_ = new CAAImpl(flags, tag, value);
+}
+
+CAA::CAA(uint8_t flags, const std::string& tag, const std::string& value) :
+ impl_(NULL)
+{
+ if (tag.empty()) {
+ isc_throw(isc::InvalidParameter,
+ "CAA tag field is empty");
+ } else if (tag.size() > 255) {
+ isc_throw(isc::InvalidParameter,
+ "CAA tag field is too large: " << tag.size());
+ }
+
+ MasterToken::StringRegion region;
+ region.beg = &value[0]; // note std ensures this works even if str is empty
+ region.len = value.size();
+
+ detail::CharStringData value_vec;
+ detail::stringToCharStringData(region, value_vec);
+
+ impl_ = new CAAImpl(flags, tag, value_vec);
+}
+
+CAA::CAA(const CAA& other) :
+ Rdata(), impl_(new CAAImpl(*other.impl_))
+{}
+
+CAA&
+CAA::operator=(const CAA& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ CAAImpl* newimpl = new CAAImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+CAA::~CAA() {
+ delete impl_;
+}
+
+void
+CAA::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(impl_->flags_);
+
+ // The constructors must ensure that the tag field is not empty.
+ assert(!impl_->tag_.empty());
+ buffer.writeUint8(impl_->tag_.size());
+ buffer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+ if (!impl_->value_.empty()) {
+ buffer.writeData(&impl_->value_[0],
+ impl_->value_.size());
+ }
+}
+
+void
+CAA::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(impl_->flags_);
+
+ // The constructors must ensure that the tag field is not empty.
+ assert(!impl_->tag_.empty());
+ renderer.writeUint8(impl_->tag_.size());
+ renderer.writeData(&impl_->tag_[0], impl_->tag_.size());
+
+ if (!impl_->value_.empty()) {
+ renderer.writeData(&impl_->value_[0],
+ impl_->value_.size());
+ }
+}
+
+std::string
+CAA::toText() const {
+ std::string result;
+
+ result = lexical_cast<std::string>(static_cast<int>(impl_->flags_));
+ result += " " + impl_->tag_;
+ result += " \"" + detail::charStringDataToString(impl_->value_) + "\"";
+
+ return (result);
+}
+
+int
+CAA::compare(const Rdata& other) const {
+ const CAA& other_caa = dynamic_cast<const CAA&>(other);
+
+ if (impl_->flags_ < other_caa.impl_->flags_) {
+ return (-1);
+ } else if (impl_->flags_ > other_caa.impl_->flags_) {
+ return (1);
+ }
+
+ // Do a case-insensitive compare of the tag strings.
+ const int result = boost::ilexicographical_compare
+ <std::string, std::string>(impl_->tag_, other_caa.impl_->tag_);
+ if (result != 0) {
+ return (result);
+ }
+
+ return (detail::compareCharStringDatas(impl_->value_,
+ other_caa.impl_->value_));
+}
+
+uint8_t
+CAA::getFlags() const {
+ return (impl_->flags_);
+}
+
+const std::string&
+CAA::getTag() const {
+ return (impl_->tag_);
+}
+
+const std::vector<uint8_t>&
+CAA::getValue() const {
+ return (impl_->value_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid CNAME 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 CNAME 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.
+CNAME::CNAME(const std::string& namestr) :
+ // Fill in dummy name and replace it soon below.
+ cname_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(namestr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ cname_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for CNAME: "
+ << namestr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct CNAME from '" <<
+ namestr << "': " << ex.what());
+ }
+}
+
+CNAME::CNAME(InputBuffer& buffer, size_t) :
+ Rdata(), cname_(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 CNAME RDATA. The CNAME 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 CNAME when it
+/// is non-absolute.
+CNAME::CNAME(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ cname_(createNameFromLexer(lexer, origin))
+{}
+
+CNAME::CNAME(const CNAME& other) :
+ Rdata(), cname_(other.cname_)
+{}
+
+CNAME::CNAME(const Name& cname) :
+ cname_(cname)
+{}
+
+void
+CNAME::toWire(OutputBuffer& buffer) const {
+ cname_.toWire(buffer);
+}
+
+void
+CNAME::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(cname_);
+}
+
+string
+CNAME::toText() const {
+ return (cname_.toText());
+}
+
+int
+CNAME::compare(const Rdata& other) const {
+ const CNAME& other_cname = dynamic_cast<const CNAME&>(other);
+
+ return (compareNames(cname_, other_cname.cname_));
+}
+
+const Name&
+CNAME::getCname() const {
+ return (cname_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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 <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/ds_like.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \brief Constructor from string.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const std::string& ds_str) :
+ impl_(new DLVImpl(ds_str))
+{}
+
+/// \brief Constructor from wire-format data.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new DLVImpl(buffer, rdata_len))
+{}
+
+DLV::DLV(MasterLexer& lexer, const Name* origin, MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) :
+ impl_(new DLVImpl(lexer, origin, options, callbacks))
+{}
+
+/// \brief Copy constructor
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const DLV& source) :
+ Rdata(), impl_(new DLVImpl(*source.impl_))
+{}
+
+/// \brief Assignment operator
+///
+/// PIMPL-induced logic
+DLV&
+DLV::operator=(const DLV& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ DLVImpl* newimpl = new DLVImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+/// \brief Destructor
+///
+/// Deallocates an internal resource.
+DLV::~DLV() {
+ delete impl_;
+}
+
+/// \brief Convert the \c DLV to a string.
+///
+/// A pass-thru to the corresponding implementation method.
+string
+DLV::toText() const {
+ return (impl_->toText());
+}
+
+/// \brief Render the \c DLV in the wire format to a OutputBuffer object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+/// \brief Render the \c DLV in the wire format to a AbstractMessageRenderer
+/// object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+/// \brief Compare two instances of \c DLV RDATA.
+///
+/// The type check is performed here. Otherwise, a pass-thru to the
+/// corresponding implementation method.
+int
+DLV::compare(const Rdata& other) const {
+ const DLV& other_ds = dynamic_cast<const DLV&>(other);
+
+ return (impl_->compare(*other_ds.impl_));
+}
+
+/// \brief Tag accessor
+uint16_t
+DLV::getTag() const {
+ return (impl_->getTag());
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid DNAME 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 TARGET 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.
+DNAME::DNAME(const std::string& namestr) :
+ // Fill in dummy name and replace it soon below.
+ dname_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(namestr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ dname_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for DNAME: "
+ << namestr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct DNAME from '" <<
+ namestr << "': " << ex.what());
+ }
+}
+
+DNAME::DNAME(InputBuffer& buffer, size_t) :
+ dname_(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 DNAME RDATA. The TARGET 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 TARGET when it
+/// is non-absolute.
+DNAME::DNAME(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ dname_(createNameFromLexer(lexer, origin))
+{}
+
+DNAME::DNAME(const DNAME& other) :
+ Rdata(), dname_(other.dname_)
+{}
+
+DNAME::DNAME(const Name& dname) :
+ dname_(dname)
+{}
+
+void
+DNAME::toWire(OutputBuffer& buffer) const {
+ dname_.toWire(buffer);
+}
+
+void
+DNAME::toWire(AbstractMessageRenderer& renderer) const {
+ // Type DNAME is not "well-known", and name compression must be disabled
+ // per RFC3597.
+ renderer.writeName(dname_, false);
+}
+
+string
+DNAME::toText() const {
+ return (dname_.toText());
+}
+
+int
+DNAME::compare(const Rdata& other) const {
+ const DNAME& other_dname = dynamic_cast<const DNAME&>(other);
+
+ return (compareNames(dname_, other_dname.dname_));
+}
+
+const Name&
+DNAME::getDname() const {
+ return (dname_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <string>
+#include <sstream>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
+
+#include <util/encode/base64.h>
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <memory>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+struct DNSKEYImpl {
+ // straightforward representation of DNSKEY RDATA fields
+ DNSKEYImpl(uint16_t flags, uint8_t protocol, uint8_t algorithm,
+ const vector<uint8_t>& keydata) :
+ flags_(flags), protocol_(protocol), algorithm_(algorithm),
+ keydata_(keydata)
+ {}
+
+ uint16_t flags_;
+ uint8_t protocol_;
+ uint8_t algorithm_;
+ const vector<uint8_t> keydata_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid DNSKEY 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 Protocol and Algorithm fields must be within their valid
+/// ranges. The Public Key field must be present and must contain a
+/// Base64 encoding of the public key. Whitespace is allowed within the
+/// Base64 text.
+///
+/// It is okay for the key data to be missing. Note: BIND 9 also accepts
+/// DNSKEY missing key data. While the RFC is silent in this case, and it
+/// may be debatable what an implementation should do, but since this field
+/// is algorithm dependent and this implementations doesn't reject unknown
+/// algorithms, it's lenient here.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param dnskey_str A string containing the RDATA to be created
+DNSKEY::DNSKEY(const std::string& dnskey_str) :
+ impl_(NULL)
+{
+ // 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 DNSKEYImpl that constructFromLexer() returns.
+ std::unique_ptr<DNSKEYImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(dnskey_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for DNSKEY: " << dnskey_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct DNSKEY from '" << dnskey_str << "': "
+ << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid DNSKEY RDATA.
+///
+/// The Protocol and Algorithm fields are not checked for unknown
+/// values. It is okay for the key data to be missing (see the description
+/// of the constructor from string).
+DNSKEY::DNSKEY(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ if (rdata_len < 4) {
+ isc_throw(InvalidRdataLength, "DNSKEY too short: " << rdata_len);
+ }
+
+ const uint16_t flags = buffer.readUint16();
+ const uint16_t protocol = buffer.readUint8();
+ const uint16_t algorithm = buffer.readUint8();
+
+ rdata_len -= 4;
+
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (rdata_len > 0) {
+ keydata.resize(rdata_len);
+ buffer.readData(&keydata[0], rdata_len);
+ }
+
+ impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an DNSKEY RDATA.
+///
+/// See \c DNSKEY::DNSKEY(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
+DNSKEY::DNSKEY(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+DNSKEYImpl*
+DNSKEY::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t flags = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (flags > 0xffff) {
+ isc_throw(InvalidRdataText,
+ "DNSKEY flags out of range: " << flags);
+ }
+
+ const uint32_t protocol =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (protocol > 0xff) {
+ isc_throw(InvalidRdataText,
+ "DNSKEY protocol out of range: " << protocol);
+ }
+
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 0xff) {
+ isc_throw(InvalidRdataText,
+ "DNSKEY algorithm out of range: " << algorithm);
+ }
+
+ std::string keydata_str;
+ std::string keydata_substr;
+ 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 is now assured to be of type STRING.
+
+ token.getString(keydata_substr);
+ keydata_str.append(keydata_substr);
+ }
+
+ lexer.ungetToken();
+
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (keydata_str.size() > 0) {
+ decodeBase64(keydata_str, keydata);
+ }
+
+ return (new DNSKEYImpl(flags, protocol, algorithm, keydata));
+}
+
+DNSKEY::DNSKEY(const DNSKEY& source) :
+ Rdata(), impl_(new DNSKEYImpl(*source.impl_))
+{}
+
+DNSKEY&
+DNSKEY::operator=(const DNSKEY& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ DNSKEYImpl* newimpl = new DNSKEYImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+DNSKEY::~DNSKEY() {
+ delete impl_;
+}
+
+string
+DNSKEY::toText() const {
+ return (boost::lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + boost::lexical_cast<string>(static_cast<int>(impl_->protocol_)) +
+ " " + boost::lexical_cast<string>(static_cast<int>(impl_->algorithm_)) +
+ " " + encodeBase64(impl_->keydata_));
+}
+
+void
+DNSKEY::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(impl_->flags_);
+ buffer.writeUint8(impl_->protocol_);
+ buffer.writeUint8(impl_->algorithm_);
+ buffer.writeData(&impl_->keydata_[0], impl_->keydata_.size());
+}
+
+void
+DNSKEY::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(impl_->flags_);
+ renderer.writeUint8(impl_->protocol_);
+ renderer.writeUint8(impl_->algorithm_);
+ renderer.writeData(&impl_->keydata_[0], impl_->keydata_.size());
+}
+
+int
+DNSKEY::compare(const Rdata& other) const {
+ const DNSKEY& other_dnskey = dynamic_cast<const DNSKEY&>(other);
+
+ if (impl_->flags_ != other_dnskey.impl_->flags_) {
+ return (impl_->flags_ < other_dnskey.impl_->flags_ ? -1 : 1);
+ }
+ if (impl_->protocol_ != other_dnskey.impl_->protocol_) {
+ return (impl_->protocol_ < other_dnskey.impl_->protocol_ ? -1 : 1);
+ }
+ if (impl_->algorithm_ != other_dnskey.impl_->algorithm_) {
+ return (impl_->algorithm_ < other_dnskey.impl_->algorithm_ ? -1 : 1);
+ }
+
+ const size_t this_len = impl_->keydata_.size();
+ const size_t other_len = other_dnskey.impl_->keydata_.size();
+ const size_t cmplen = min(this_len, other_len);
+ if (cmplen == 0) {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+ const int cmp = memcmp(&impl_->keydata_[0],
+ &other_dnskey.impl_->keydata_[0], cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+uint16_t
+DNSKEY::getTag() const {
+ if (impl_->algorithm_ == 1) {
+ // See RFC 4034 appendix B.1 for why the key data must contain
+ // at least 4 bytes with RSA/MD5: 3 trailing bytes to extract
+ // the tag from, and 1 byte of exponent length subfield before
+ // modulus.
+ const int len = impl_->keydata_.size();
+ if (len < 4) {
+ isc_throw(isc::OutOfRange,
+ "DNSKEY keydata too short for tag extraction");
+ }
+
+ return ((impl_->keydata_[len - 3] << 8) + impl_->keydata_[len - 2]);
+ }
+
+ uint32_t ac = impl_->flags_;
+ ac += (impl_->protocol_ << 8);
+ ac += impl_->algorithm_;
+
+ const size_t size = impl_->keydata_.size();
+ for (size_t i = 0; i < size; i ++) {
+ ac += (i & 1) ? impl_->keydata_[i] : (impl_->keydata_[i] << 8);
+ }
+ ac += (ac >> 16) & 0xffff;
+ return (ac & 0xffff);
+}
+
+uint16_t
+DNSKEY::getFlags() const {
+ return (impl_->flags_);
+}
+
+uint8_t
+DNSKEY::getAlgorithm() const {
+ return (impl_->algorithm_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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 <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/ds_like.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+DS::DS(const std::string& ds_str) :
+ impl_(new DSImpl(ds_str))
+{}
+
+DS::DS(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new DSImpl(buffer, rdata_len))
+{}
+
+DS::DS(MasterLexer& lexer, const Name* origin, MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) :
+ impl_(new DSImpl(lexer, origin, options, callbacks))
+{}
+
+DS::DS(const DS& source) :
+ Rdata(), impl_(new DSImpl(*source.impl_))
+{}
+
+DS&
+DS::operator=(const DS& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ DSImpl* newimpl = new DSImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+DS::~DS() {
+ delete impl_;
+}
+
+string
+DS::toText() const {
+ return (impl_->toText());
+}
+
+void
+DS::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+void
+DS::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+int
+DS::compare(const Rdata& other) const {
+ const DS& other_ds = dynamic_cast<const DS&>(other);
+
+ return (impl_->compare(*other_ds.impl_));
+}
+
+uint16_t
+DS::getTag() const {
+ return (impl_->getTag());
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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/rdataclass.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::dns;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+class HINFOImpl {
+public:
+ HINFOImpl(const std::string& hinfo_str) {
+ std::istringstream ss(hinfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseHINFOData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid HINFO text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct HINFO RDATA from "
+ << hinfo_str << "': " << ex.what());
+ }
+ }
+
+ HINFOImpl(InputBuffer& buffer, size_t rdata_len) {
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, cpu);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, os);
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "HINFO RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
+ }
+
+ HINFOImpl(MasterLexer& lexer)
+ {
+ parseHINFOData(lexer);
+ }
+
+private:
+ void
+ parseHINFOData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), cpu);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), os);
+ }
+
+public:
+ detail::CharString cpu;
+ detail::CharString os;
+};
+
+HINFO::HINFO(const std::string& hinfo_str) : impl_(new HINFOImpl(hinfo_str))
+{}
+
+
+HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new HINFOImpl(buffer, rdata_len))
+{}
+
+HINFO::HINFO(const HINFO& source):
+ Rdata(), impl_(new HINFOImpl(*source.impl_))
+{
+}
+
+HINFO::HINFO(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new HINFOImpl(lexer))
+{}
+
+HINFO&
+HINFO::operator=(const HINFO& source)
+{
+ impl_.reset(new HINFOImpl(*source.impl_));
+ return (*this);
+}
+
+HINFO::~HINFO() {
+}
+
+std::string
+HINFO::toText() const {
+ string result;
+ result += "\"";
+ result += detail::charStringToString(impl_->cpu);
+ result += "\" \"";
+ result += detail::charStringToString(impl_->os);
+ result += "\"";
+ return (result);
+}
+
+void
+HINFO::toWire(OutputBuffer& buffer) const {
+ toWireHelper(buffer);
+}
+
+void
+HINFO::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(renderer);
+}
+
+int
+HINFO::compare(const Rdata& other) const {
+ const HINFO& other_hinfo = dynamic_cast<const HINFO&>(other);
+
+ const int cmp = compareCharStrings(impl_->cpu, other_hinfo.impl_->cpu);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ return (compareCharStrings(impl_->os, other_hinfo.impl_->os));
+}
+
+const std::string
+HINFO::getCPU() const {
+ return (detail::charStringToString(impl_->cpu));
+}
+
+const std::string
+HINFO::getOS() const {
+ return (detail::charStringToString(impl_->os));
+}
+
+template <typename T>
+void
+HINFO::toWireHelper(T& outputer) const {
+ outputer.writeData(&impl_->cpu[0], impl_->cpu.size());
+ outputer.writeData(&impl_->os[0], impl_->os.size());
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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 <util/buffer.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \brief Constructor from string.
+///
+/// \c minfo_str must be formatted as follows:
+/// \code <rmailbox name> <emailbox name>
+/// \endcode
+/// where both fields must represent a valid domain name.
+///
+/// An example of valid string is:
+/// \code "rmail.example.com. email.example.com." \endcode
+///
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// names in the string is invalid.
+MINFO::MINFO(const std::string& minfo_str) :
+ // We cannot construct both names in the initialization list due to the
+ // necessary text processing, so we have to initialize them with a dummy
+ // name and replace them later.
+ rmailbox_(Name::ROOT_NAME()), emailbox_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(minfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ rmailbox_ = createNameFromLexer(lexer, NULL);
+ emailbox_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for MINFO: "
+ << minfo_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct MINFO from '" <<
+ minfo_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an MINFO RDATA. The RMAILBOX and EMAILBOX fields can be non-absolute
+/// if \c origin is non-NULL, in which case \c origin is used to make them
+/// absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and 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 SERVER when it
+/// is non-absolute.
+MINFO::MINFO(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ rmailbox_(createNameFromLexer(lexer, origin)),
+ emailbox_(createNameFromLexer(lexer, origin))
+{
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+MINFO::MINFO(InputBuffer& buffer, size_t) :
+ rmailbox_(buffer), emailbox_(buffer)
+{}
+
+/// \brief Copy constructor.
+///
+/// \throw std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+MINFO::MINFO(const MINFO& other) :
+ Rdata(), rmailbox_(other.rmailbox_), emailbox_(other.emailbox_)
+{}
+
+/// \brief Convert the \c MINFO to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c MINFO(const std::string&))).
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c MINFO object.
+std::string
+MINFO::toText() const {
+ return (rmailbox_.toText() + " " + emailbox_.toText());
+}
+
+/// \brief Render the \c MINFO in the wire format without name compression.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+MINFO::toWire(OutputBuffer& buffer) const {
+ rmailbox_.toWire(buffer);
+ emailbox_.toWire(buffer);
+}
+
+MINFO&
+MINFO::operator=(const MINFO& source) {
+ rmailbox_ = source.rmailbox_;
+ emailbox_ = source.emailbox_;
+
+ return (*this);
+}
+
+/// \brief Render the \c MINFO in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, TYPE MINFO is "well-known", the rmailbox and
+/// emailbox fields (domain names) will be compressed.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+MINFO::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(rmailbox_);
+ renderer.writeName(emailbox_);
+}
+
+/// \brief Compare two instances of \c MINFO RDATA.
+///
+/// See documentation in \c Rdata.
+int
+MINFO::compare(const Rdata& other) const {
+ const MINFO& other_minfo = dynamic_cast<const MINFO&>(other);
+
+ const int cmp = compareNames(rmailbox_, other_minfo.rmailbox_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ return (compareNames(emailbox_, other_minfo.emailbox_));
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+MX::MX(InputBuffer& buffer, size_t) :
+ preference_(buffer.readUint16()), mxname_(buffer)
+{
+ // we don't need rdata_len for parsing. if necessary, the caller will
+ // check consistency.
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid MX 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 EXCHANGE name must be absolute since there's no parameter that
+/// specifies the origin name; if it is not absolute, \c MissingNameOrigin
+/// exception will be thrown. It 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.
+MX::MX(const std::string& mx_str) :
+ // Fill in dummy name and replace them soon below.
+ preference_(0), mxname_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(mx_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ constructFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for MX: "
+ << mx_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct MX from '" <<
+ mx_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an MX RDATA. The EXCHANGE 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.
+///
+/// The PREFERENCE field must be a valid decimal representation of an
+/// unsigned 16-bit integer.
+///
+/// \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 EXCHANGE when it
+/// is non-absolute.
+MX::MX(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ preference_(0), mxname_(Name::ROOT_NAME())
+{
+ constructFromLexer(lexer, origin);
+}
+
+void
+MX::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid MX preference: " << num);
+ }
+ preference_ = static_cast<uint16_t>(num);
+
+ mxname_ = createNameFromLexer(lexer, origin);
+}
+
+MX::MX(uint16_t preference, const Name& mxname) :
+ preference_(preference), mxname_(mxname)
+{}
+
+MX::MX(const MX& other) :
+ Rdata(), preference_(other.preference_), mxname_(other.mxname_)
+{}
+
+void
+MX::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(preference_);
+ mxname_.toWire(buffer);
+}
+
+void
+MX::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(preference_);
+ renderer.writeName(mxname_);
+}
+
+string
+MX::toText() const {
+ return (lexical_cast<string>(preference_) + " " + mxname_.toText());
+}
+
+int
+MX::compare(const Rdata& other) const {
+ const MX& other_mx = dynamic_cast<const MX&>(other);
+
+ if (preference_ < other_mx.preference_) {
+ return (-1);
+ } else if (preference_ > other_mx.preference_) {
+ return (1);
+ }
+
+ return (compareNames(mxname_, other_mx.mxname_));
+}
+
+const Name&
+MX::getMXName() const {
+ return (mxname_);
+}
+
+uint16_t
+MX::getMXPref() const {
+ return (preference_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::dns;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+class NAPTRImpl {
+public:
+ NAPTRImpl() : order(0), preference(0), replacement(".") {}
+
+ NAPTRImpl(InputBuffer& buffer, size_t rdata_len) : replacement(".") {
+ if (rdata_len < 4 || buffer.getLength() - buffer.getPosition() < 4) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: insufficient length ");
+ }
+ order = buffer.readUint16();
+ preference = buffer.readUint16();
+ rdata_len -= 4;
+
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, flags);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, services);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, regexp);
+ replacement = Name(buffer);
+ if (rdata_len < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: missing replacement name");
+ }
+ rdata_len -= replacement.getLength();
+
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "NAPTR RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
+ }
+
+ NAPTRImpl(const std::string& naptr_str) : replacement(".") {
+ std::istringstream ss(naptr_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseNAPTRData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct NAPTR RDATA from "
+ << naptr_str << "': " << ex.what());
+ }
+ }
+
+ NAPTRImpl(MasterLexer& lexer) : replacement(".")
+ {
+ parseNAPTRData(lexer);
+ }
+
+private:
+ void
+ parseNAPTRData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: order out of range: "
+ << token.getNumber());
+ }
+ order = token.getNumber();
+ token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: preference out of range: "
+ << token.getNumber());
+ }
+ preference = token.getNumber();
+
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), flags);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), services);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), regexp);
+
+ token = lexer.getNextToken(MasterToken::STRING);
+ replacement = Name(token.getString());
+ }
+
+
+public:
+ uint16_t order;
+ uint16_t preference;
+ detail::CharString flags;
+ detail::CharString services;
+ detail::CharString regexp;
+ Name replacement;
+};
+
+NAPTR::NAPTR(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new NAPTRImpl(buffer, rdata_len))
+{}
+
+NAPTR::NAPTR(const std::string& naptr_str) : impl_(new NAPTRImpl(naptr_str))
+{}
+
+NAPTR::NAPTR(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new NAPTRImpl(lexer))
+{}
+
+NAPTR::NAPTR(const NAPTR& naptr) : Rdata(),
+ impl_(new NAPTRImpl(*naptr.impl_))
+{}
+
+NAPTR&
+NAPTR::operator=(const NAPTR& source)
+{
+ impl_.reset(new NAPTRImpl(*source.impl_));
+ return (*this);
+}
+
+NAPTR::~NAPTR() {
+}
+
+void
+NAPTR::toWire(OutputBuffer& buffer) const {
+ toWireHelper(buffer);
+ impl_->replacement.toWire(buffer);
+}
+
+void
+NAPTR::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(renderer);
+ // Type NAPTR is not "well-known", and name compression must be disabled
+ // per RFC3597.
+ renderer.writeName(impl_->replacement, false);
+}
+
+string
+NAPTR::toText() const {
+ string result;
+ result += lexical_cast<string>(impl_->order);
+ result += " ";
+ result += lexical_cast<string>(impl_->preference);
+ result += " \"";
+ result += detail::charStringToString(impl_->flags);
+ result += "\" \"";
+ result += detail::charStringToString(impl_->services);
+ result += "\" \"";
+ result += detail::charStringToString(impl_->regexp);
+ result += "\" ";
+ result += impl_->replacement.toText();
+ return (result);
+}
+
+int
+NAPTR::compare(const Rdata& other) const {
+ const NAPTR other_naptr = dynamic_cast<const NAPTR&>(other);
+
+ if (impl_->order < other_naptr.impl_->order) {
+ return (-1);
+ } else if (impl_->order > other_naptr.impl_->order) {
+ return (1);
+ }
+
+ if (impl_->preference < other_naptr.impl_->preference) {
+ return (-1);
+ } else if (impl_->preference > other_naptr.impl_->preference) {
+ return (1);
+ }
+
+ const int fcmp = detail::compareCharStrings(impl_->flags,
+ other_naptr.impl_->flags);
+ if (fcmp != 0) {
+ return (fcmp);
+ }
+
+ const int scmp = detail::compareCharStrings(impl_->services,
+ other_naptr.impl_->services);
+ if (scmp != 0) {
+ return (scmp);
+ }
+
+ const int rcmp = detail::compareCharStrings(impl_->regexp,
+ other_naptr.impl_->regexp);
+ if (rcmp != 0) {
+ return (rcmp);
+ }
+
+ return (compareNames(impl_->replacement, other_naptr.impl_->replacement));
+}
+
+uint16_t
+NAPTR::getOrder() const {
+ return (impl_->order);
+}
+
+uint16_t
+NAPTR::getPreference() const {
+ return (impl_->preference);
+}
+
+const std::string
+NAPTR::getFlags() const {
+ return (detail::charStringToString(impl_->flags));
+}
+
+const std::string
+NAPTR::getServices() const {
+ return (detail::charStringToString(impl_->services));
+}
+
+const std::string
+NAPTR::getRegexp() const {
+ return (detail::charStringToString(impl_->regexp));
+}
+
+const Name&
+NAPTR::getReplacement() const {
+ return (impl_->replacement);
+}
+
+template <typename T>
+void
+NAPTR::toWireHelper(T& outputer) const {
+ outputer.writeUint16(impl_->order);
+ outputer.writeUint16(impl_->preference);
+
+ outputer.writeData(&impl_->flags[0], impl_->flags.size());
+ outputer.writeData(&impl_->services[0], impl_->services.size());
+ outputer.writeData(&impl_->regexp[0], impl_->regexp.size());
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+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, NULL);
+
+ 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_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <iomanip>
+#include <string>
+#include <sstream>
+#include <vector>
+#include <cassert>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/encode/base32hex.h>
+#include <util/encode/hex.h>
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
+
+#include <memory>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::dns::rdata::generic::detail::nsec;
+using namespace isc::dns::rdata::generic::detail::nsec3;
+using namespace isc::util::encode;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+struct NSEC3Impl {
+ // straightforward representation of NSEC3 RDATA fields
+ NSEC3Impl(uint8_t hashalg, uint8_t flags, uint16_t iterations,
+ vector<uint8_t>salt, vector<uint8_t>next,
+ vector<uint8_t> typebits) :
+ hashalg_(hashalg), flags_(flags), iterations_(iterations),
+ salt_(salt), next_(next), typebits_(typebits)
+ {}
+
+ const uint8_t hashalg_;
+ const uint8_t flags_;
+ const uint16_t iterations_;
+ const vector<uint8_t> salt_;
+ const vector<uint8_t> next_;
+ const vector<uint8_t> typebits_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3 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 Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3_str A string containing the RDATA to be created
+NSEC3::NSEC3(const std::string& nsec3_str) :
+ impl_(NULL)
+{
+ // 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 NSEC3Impl that constructFromLexer() returns.
+ std::unique_ptr<NSEC3Impl> impl_ptr;
+
+ try {
+ std::istringstream ss(nsec3_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3: " << nsec3_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3 from '" << nsec3_str << "': "
+ << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3 RDATA.
+///
+/// See \c NSEC3::NSEC3(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
+NSEC3::NSEC3(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3Impl*
+NSEC3::constructFromLexer(MasterLexer& lexer) {
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamFromLexer("NSEC3", lexer, salt);
+
+ const string& nexthash =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ if (*nexthash.rbegin() == '=') {
+ isc_throw(InvalidRdataText, "NSEC3 hash has padding: " << nexthash);
+ }
+
+ vector<uint8_t> next;
+ decodeBase32Hex(nexthash, next);
+ if (next.size() > 255) {
+ isc_throw(InvalidRdataText, "NSEC3 hash is too long: "
+ << next.size() << " bytes");
+ }
+
+ vector<uint8_t> typebits;
+ // For NSEC3 empty bitmap is possible and allowed.
+ buildBitmapsFromLexer("NSEC3", lexer, typebits, true);
+ return (new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits));
+}
+
+NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamWire("NSEC3", buffer, rdata_len, salt);
+
+ if (rdata_len < 1) {
+ isc_throw(DNSMessageFORMERR, "NSEC3 too short to contain hash length, "
+ "length: " << rdata_len + salt.size() + 5);
+ }
+ const uint8_t nextlen = buffer.readUint8();
+ --rdata_len;
+ if (nextlen == 0 || rdata_len < nextlen) {
+ isc_throw(DNSMessageFORMERR, "NSEC3 invalid hash length: " <<
+ static_cast<unsigned int>(nextlen));
+ }
+
+ vector<uint8_t> next(nextlen);
+ buffer.readData(&next[0], nextlen);
+ rdata_len -= nextlen;
+
+ vector<uint8_t> typebits(rdata_len);
+ if (rdata_len > 0) {
+ // Read and parse the bitmaps only when they exist; empty bitmap
+ // is possible for NSEC3.
+ buffer.readData(&typebits[0], rdata_len);
+ checkRRTypeBitmaps("NSEC3", typebits);
+ }
+
+ impl_ = new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits);
+}
+
+NSEC3::NSEC3(const NSEC3& source) :
+ Rdata(), impl_(new NSEC3Impl(*source.impl_))
+{}
+
+NSEC3&
+NSEC3::operator=(const NSEC3& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ NSEC3Impl* newimpl = new NSEC3Impl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+NSEC3::~NSEC3() {
+ delete impl_;
+}
+
+string
+NSEC3::toText() const {
+ ostringstream s;
+ bitmapsToText(impl_->typebits_, s);
+
+ using boost::lexical_cast;
+ return (lexical_cast<string>(static_cast<int>(impl_->hashalg_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
+ " " + (impl_->salt_.empty() ? "-" : encodeHex(impl_->salt_)) +
+ " " + encodeBase32Hex(impl_->next_) + s.str());
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireHelper(const NSEC3Impl& impl, OUTPUT_TYPE& output) {
+ output.writeUint8(impl.hashalg_);
+ output.writeUint8(impl.flags_);
+ output.writeUint16(impl.iterations_);
+ output.writeUint8(impl.salt_.size());
+ if (!impl.salt_.empty()) {
+ output.writeData(&impl.salt_[0], impl.salt_.size());
+ }
+ assert(!impl.next_.empty());
+ output.writeUint8(impl.next_.size());
+ output.writeData(&impl.next_[0], impl.next_.size());
+ if (!impl.typebits_.empty()) {
+ output.writeData(&impl.typebits_[0], impl.typebits_.size());
+ }
+}
+
+void
+NSEC3::toWire(OutputBuffer& buffer) const {
+ toWireHelper(*impl_, buffer);
+}
+
+void
+NSEC3::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(*impl_, renderer);
+}
+
+namespace {
+// This is a helper subroutine for compare(). It compares two binary
+// data stored in vector<uint8_t> objects based on the "Canonical RR Ordering"
+// as defined in Section 6.3 of RFC4034, that is, the data are treated
+// "as a left-justified unsigned octet sequence in which the absence of an
+// octet sorts before a zero octet."
+//
+// If check_length_first is true, it treats the compared data as if they
+// began with a single-octet "length" field whose value is the size of the
+// corresponding vector. In this case, if the sizes of the two vectors are
+// different the shorter one is always considered the "smaller"; the contents
+// of the vector don't matter.
+//
+// This function returns:
+// -1 if v1 is considered smaller than v2
+// 1 if v1 is considered larger than v2
+// 0 otherwise
+int
+compareVectors(const vector<uint8_t>& v1, const vector<uint8_t>& v2,
+ bool check_length_first = true)
+{
+ const size_t len1 = v1.size();
+ const size_t len2 = v2.size();
+ if (check_length_first && len1 != len2) {
+ return (len1 - len2);
+ }
+ const size_t cmplen = min(len1, len2);
+ const int cmp = cmplen == 0 ? 0 : memcmp(&v1.at(0), &v2.at(0), cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return (len1 - len2);
+ }
+}
+}
+
+int
+NSEC3::compare(const Rdata& other) const {
+ const NSEC3& other_nsec3 = dynamic_cast<const NSEC3&>(other);
+
+ if (impl_->hashalg_ != other_nsec3.impl_->hashalg_) {
+ return (impl_->hashalg_ < other_nsec3.impl_->hashalg_ ? -1 : 1);
+ }
+ if (impl_->flags_ != other_nsec3.impl_->flags_) {
+ return (impl_->flags_ < other_nsec3.impl_->flags_ ? -1 : 1);
+ }
+ if (impl_->iterations_ != other_nsec3.impl_->iterations_) {
+ return (impl_->iterations_ < other_nsec3.impl_->iterations_ ? -1 : 1);
+ }
+
+ int cmp = compareVectors(impl_->salt_, other_nsec3.impl_->salt_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ cmp = compareVectors(impl_->next_, other_nsec3.impl_->next_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ // Note that bitmap doesn't have a dedicated length field, so we shouldn't
+ // terminate the comparison just because the lengths are different.
+ return (compareVectors(impl_->typebits_, other_nsec3.impl_->typebits_,
+ false));
+}
+
+uint8_t
+NSEC3::getHashalg() const {
+ return (impl_->hashalg_);
+}
+
+uint8_t
+NSEC3::getFlags() const {
+ return (impl_->flags_);
+}
+
+uint16_t
+NSEC3::getIterations() const {
+ return (impl_->iterations_);
+}
+
+const vector<uint8_t>&
+NSEC3::getSalt() const {
+ return (impl_->salt_);
+}
+
+const vector<uint8_t>&
+NSEC3::getNext() const {
+ return (impl_->next_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec3param_common.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <memory>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+struct NSEC3PARAMImpl {
+ // straightforward representation of NSEC3PARAM RDATA fields
+ NSEC3PARAMImpl(uint8_t hashalg, uint8_t flags, uint16_t iterations,
+ const vector<uint8_t>& salt) :
+ hashalg_(hashalg), flags_(flags), iterations_(iterations), salt_(salt)
+ {}
+
+ const uint8_t hashalg_;
+ const uint8_t flags_;
+ const uint16_t iterations_;
+ const vector<uint8_t> salt_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3PARAM 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 Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3param_str A string containing the RDATA to be created
+NSEC3PARAM::NSEC3PARAM(const std::string& nsec3param_str) :
+ impl_(NULL)
+{
+ // 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 NSEC3PARAMImpl that constructFromLexer() returns.
+ std::unique_ptr<NSEC3PARAMImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(nsec3param_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3PARAM: " << nsec3param_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3PARAM from '" << nsec3param_str
+ << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3PARAM RDATA.
+///
+/// See \c NSEC3PARAM::NSEC3PARAM(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
+NSEC3PARAM::NSEC3PARAM(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3PARAMImpl*
+NSEC3PARAM::constructFromLexer(MasterLexer& lexer) {
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamFromLexer("NSEC3PARAM", lexer, salt);
+
+ return (new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt));
+}
+
+NSEC3PARAM::NSEC3PARAM(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ vector<uint8_t> salt;
+ const ParseNSEC3ParamResult params =
+ parseNSEC3ParamWire("NSEC3PARAM", buffer, rdata_len, salt);
+
+ impl_ = new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt);
+}
+
+NSEC3PARAM::NSEC3PARAM(const NSEC3PARAM& source) :
+ Rdata(), impl_(new NSEC3PARAMImpl(*source.impl_))
+{}
+
+NSEC3PARAM&
+NSEC3PARAM::operator=(const NSEC3PARAM& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ NSEC3PARAMImpl* newimpl = new NSEC3PARAMImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+NSEC3PARAM::~NSEC3PARAM() {
+ delete impl_;
+}
+
+string
+NSEC3PARAM::toText() const {
+ using boost::lexical_cast;
+ return (lexical_cast<string>(static_cast<int>(impl_->hashalg_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+ " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
+ " " + (impl_->salt_.empty() ? "-" : encodeHex(impl_->salt_)));
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireHelper(const NSEC3PARAMImpl& impl, OUTPUT_TYPE& output) {
+ output.writeUint8(impl.hashalg_);
+ output.writeUint8(impl.flags_);
+ output.writeUint16(impl.iterations_);
+ output.writeUint8(impl.salt_.size());
+ if (!impl.salt_.empty()) {
+ output.writeData(&impl.salt_[0], impl.salt_.size());
+ }
+}
+
+void
+NSEC3PARAM::toWire(OutputBuffer& buffer) const {
+ toWireHelper(*impl_, buffer);
+}
+
+void
+NSEC3PARAM::toWire(AbstractMessageRenderer& renderer) const {
+ toWireHelper(*impl_, renderer);
+}
+
+int
+NSEC3PARAM::compare(const Rdata& other) const {
+ const NSEC3PARAM& other_param = dynamic_cast<const NSEC3PARAM&>(other);
+
+ if (impl_->hashalg_ != other_param.impl_->hashalg_) {
+ return (impl_->hashalg_ < other_param.impl_->hashalg_ ? -1 : 1);
+ }
+ if (impl_->flags_ != other_param.impl_->flags_) {
+ return (impl_->flags_ < other_param.impl_->flags_ ? -1 : 1);
+ }
+ if (impl_->iterations_ != other_param.impl_->iterations_) {
+ return (impl_->iterations_ < other_param.impl_->iterations_ ? -1 : 1);
+ }
+
+ const size_t this_len = impl_->salt_.size();
+ const size_t other_len = other_param.impl_->salt_.size();
+ if (this_len != other_len) {
+ return (this_len - other_len);
+ }
+ const size_t cmplen = min(this_len, other_len);
+ const int cmp = (cmplen == 0) ? 0 :
+ memcmp(&impl_->salt_.at(0), &other_param.impl_->salt_.at(0), cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return (this_len - other_len);
+ }
+}
+
+uint8_t
+NSEC3PARAM::getHashalg() const {
+ return (impl_->hashalg_);
+}
+
+uint8_t
+NSEC3PARAM::getFlags() const {
+ return (impl_->flags_);
+}
+
+uint16_t
+NSEC3PARAM::getIterations() const {
+ return (impl_->iterations_);
+}
+
+const vector<uint8_t>&
+NSEC3PARAM::getSalt() const {
+ return (impl_->salt_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <string>
+#include <sstream>
+#include <vector>
+
+#include <util/encode/base64.h>
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/nsec_bitmap.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail::nsec;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+struct NSECImpl {
+ // straightforward representation of NSEC RDATA fields
+ NSECImpl(const Name& next, vector<uint8_t> typebits) :
+ nextname_(next), typebits_(typebits)
+ {}
+
+ Name nextname_;
+ vector<uint8_t> typebits_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC 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 Next Domain Name field must be absolute since there's no
+/// parameter that specifies the origin name; if it is not absolute,
+/// \c MissingNameOrigin exception will be thrown. This must not be
+/// represented as a quoted string.
+///
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw MissingNameOrigin Thrown when the Next Domain Name is not absolute.
+/// \throw InvalidRdataText if any fields are out of their valid range.
+///
+/// \param nsec_str A string containing the RDATA to be created
+NSEC::NSEC(const std::string& nsec_str) :
+ impl_(NULL)
+{
+ try {
+ std::istringstream ss(nsec_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ const Name origin_name(createNameFromLexer(lexer, NULL));
+
+ vector<uint8_t> typebits;
+ buildBitmapsFromLexer("NSEC", lexer, typebits);
+
+ impl_ = new NSECImpl(origin_name, typebits);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC: " << nsec_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC from '" << nsec_str << "': "
+ << ex.what());
+ }
+}
+
+NSEC::NSEC(InputBuffer& buffer, size_t rdata_len) {
+ const size_t pos = buffer.getPosition();
+ const Name nextname(buffer);
+
+ // rdata_len must be sufficiently large to hold non empty bitmap.
+ if (rdata_len <= buffer.getPosition() - pos) {
+ isc_throw(DNSMessageFORMERR,
+ "NSEC RDATA from wire too short: " << rdata_len << "bytes");
+ }
+ rdata_len -= (buffer.getPosition() - pos);
+
+ vector<uint8_t> typebits(rdata_len);
+ buffer.readData(&typebits[0], rdata_len);
+ checkRRTypeBitmaps("NSEC", typebits);
+
+ impl_ = new NSECImpl(nextname, typebits);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC RDATA.
+///
+/// The Next Domain Name 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.
+///
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw MissingNameOrigin Thrown when the Next Domain Name is not
+/// absolute and \c origin is NULL.
+/// \throw InvalidRdataText if any fields are out of their valid range.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin The origin to use with a relative Next Domain Name
+/// field
+NSEC::NSEC(MasterLexer& lexer, const Name* origin, MasterLoader::Options,
+ MasterLoaderCallbacks&)
+{
+ const Name next_name(createNameFromLexer(lexer, origin));
+
+ vector<uint8_t> typebits;
+ buildBitmapsFromLexer("NSEC", lexer, typebits);
+
+ impl_ = new NSECImpl(next_name, typebits);
+}
+
+NSEC::NSEC(const NSEC& source) :
+ Rdata(), impl_(new NSECImpl(*source.impl_))
+{}
+
+NSEC&
+NSEC::operator=(const NSEC& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ NSECImpl* newimpl = new NSECImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+NSEC::~NSEC() {
+ delete impl_;
+}
+
+string
+NSEC::toText() const {
+ ostringstream s;
+ s << impl_->nextname_;
+ bitmapsToText(impl_->typebits_, s);
+ return (s.str());
+}
+
+void
+NSEC::toWire(OutputBuffer& buffer) const {
+ impl_->nextname_.toWire(buffer);
+ buffer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+}
+
+void
+NSEC::toWire(AbstractMessageRenderer& renderer) const {
+ // Type NSEC is not "well-known", and name compression must be disabled
+ // per RFC3597.
+ renderer.writeName(impl_->nextname_, false);
+ renderer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+}
+
+const Name&
+NSEC::getNextName() const {
+ return (impl_->nextname_);
+}
+
+int
+NSEC::compare(const Rdata& other) const {
+ const NSEC& other_nsec = dynamic_cast<const NSEC&>(other);
+
+ int cmp = compareNames(impl_->nextname_, other_nsec.impl_->nextname_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+
+ const size_t this_len = impl_->typebits_.size();
+ const size_t other_len = other_nsec.impl_->typebits_.size();
+ const size_t cmplen = min(this_len, other_len);
+ cmp = memcmp(&impl_->typebits_[0], &other_nsec.impl_->typebits_[0],
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ } else {
+ return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
+ }
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <boost/foreach.hpp>
+
+#include <string>
+#include <string.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \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&) :
+ impl_(NULL)
+{
+ 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&) :
+ impl_(NULL)
+{
+ isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
+}
+
+OPT::OPT(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ std::unique_ptr<OPTImpl> impl_ptr(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_ptr->rdlength_ + option_length) <
+ impl_ptr->rdlength_)
+ {
+ isc_throw(InvalidRdataText,
+ "Option length " << option_length
+ << " would overflow OPT RR RDLEN (currently "
+ << impl_ptr->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_ptr->pseudo_rrs_.push_back(PseudoRR(option_code, option_data));
+ impl_ptr->rdlength_ += option_length;
+ rdata_len -= option_length;
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+OPT::OPT(const OPT& other) :
+ Rdata(), impl_(new OPTImpl(*other.impl_))
+{
+}
+
+OPT&
+OPT::operator=(const OPT& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ OPTImpl* newimpl = new OPTImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+OPT::~OPT() {
+ delete impl_;
+}
+
+std::string
+OPT::toText() const {
+ isc_throw(isc::InvalidOperation,
+ "OPT RRs do not have a presentation format");
+}
+
+void
+OPT::toWire(OutputBuffer& buffer) const {
+ BOOST_FOREACH(const PseudoRR& 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 {
+ BOOST_FOREACH(const PseudoRR& 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_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <util/buffer.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \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, NULL);
+
+ 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_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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 <util/buffer.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \brief Constructor from string.
+///
+/// \c rp_str must be formatted as follows:
+/// \code <mailbox name> <text name>
+/// \endcode
+/// where both fields must represent a valid domain name.
+///
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// given name is invalid.
+RP::RP(const std::string& rp_str) :
+ // We cannot construct both names in the initialization list due to the
+ // necessary text processing, so we have to initialize them with a dummy
+ // name and replace them later.
+ mailbox_(Name::ROOT_NAME()), text_(Name::ROOT_NAME())
+{
+ try {
+ std::istringstream ss(rp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mailbox_ = createNameFromLexer(lexer, NULL);
+ text_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for RP: "
+ << rp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct RP from '" <<
+ rp_str << "': " << ex.what());
+ }
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RP RDATA. The MAILBOX and TEXT fields can be non-absolute if \c
+/// origin is non-NULL, in which case \c origin is used to make them absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and 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 SERVER when it
+/// is non-absolute.
+RP::RP(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mailbox_(createNameFromLexer(lexer, origin)),
+ text_(createNameFromLexer(lexer, origin))
+{
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+RP::RP(InputBuffer& buffer, size_t) : mailbox_(buffer), text_(buffer) {
+}
+
+/// \brief Copy constructor.
+///
+/// \throw std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+RP::RP(const RP& other) :
+ Rdata(), mailbox_(other.mailbox_), text_(other.text_)
+{}
+
+/// \brief Convert the \c RP to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c RP(const std::string&))).
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c RP object.
+std::string
+RP::toText() const {
+ return (mailbox_.toText() + " " + text_.toText());
+}
+
+/// \brief Render the \c RP in the wire format without name compression.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+RP::toWire(OutputBuffer& buffer) const {
+ mailbox_.toWire(buffer);
+ text_.toWire(buffer);
+}
+
+/// \brief Render the \c RP in the wire format with taking into account
+/// compression.
+///
+// Type RP is not "well-known", and name compression must be disabled
+// per RFC3597.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+RP::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeName(mailbox_, false);
+ renderer.writeName(text_, false);
+}
+
+/// \brief Compare two instances of \c RP RDATA.
+///
+/// See documentation in \c Rdata.
+int
+RP::compare(const Rdata& other) const {
+ const RP& other_rp = dynamic_cast<const RP&>(other);
+
+ const int cmp = compareNames(mailbox_, other_rp.mailbox_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ return (compareNames(text_, other_rp.text_));
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <iomanip>
+#include <iostream>
+#include <sstream>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/encode/base64.h>
+#include <util/buffer.h>
+#include <util/time_utilities.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <stdio.h>
+#include <time.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+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
+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 (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) :
+ impl_(NULL)
+{
+ // 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.
+ std::unique_ptr<RRSIGImpl> impl_ptr;
+
+ try {
+ std::istringstream iss(rrsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
+
+ 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());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \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_ = 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);
+ }
+
+ RRSIGImpl* newimpl = new RRSIGImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+RRSIG::~RRSIG() {
+ delete impl_;
+}
+
+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_);
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+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, NULL);
+ rname_ = createNameFromLexer(lexer, NULL);
+ 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);
+ 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_)));
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <stdint.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+#include <dns/rdata/generic/detail/txt_like.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+/// \brief The assignment operator
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+SPF&
+SPF::operator=(const SPF& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ SPFImpl* newimpl = new SPFImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+/// \brief The destructor
+SPF::~SPF() {
+ delete impl_;
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new SPFImpl(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.
+SPF::SPF(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(new SPFImpl(lexer))
+{}
+
+/// \brief Constructor from string.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+SPF::SPF(const std::string& txtstr) :
+ impl_(new SPFImpl(txtstr))
+{}
+
+/// \brief Copy constructor
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+SPF::SPF(const SPF& other) :
+ Rdata(), impl_(new SPFImpl(*other.impl_))
+{}
+
+/// \brief Render the \c SPF in the wire format to a OutputBuffer object
+///
+/// \return is the return of the corresponding implementation method.
+void
+SPF::toWire(OutputBuffer& buffer) const {
+ impl_->toWire(buffer);
+}
+
+/// \brief Render the \c SPF in the wire format to an AbstractMessageRenderer
+/// object
+///
+/// \return is the return of the corresponding implementation method.
+void
+SPF::toWire(AbstractMessageRenderer& renderer) const {
+ impl_->toWire(renderer);
+}
+
+/// \brief Convert the \c SPF to a string.
+///
+/// \return is the return of the corresponding implementation method.
+string
+SPF::toText() const {
+ return (impl_->toText());
+}
+
+/// \brief Compare two instances of \c SPF RDATA.
+///
+/// This method compares \c this and the \c other \c SPF objects.
+///
+/// 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 SPF object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+///
+/// \param other the right-hand operand to compare against.
+/// \return is the return of the corresponding implementation method.
+int
+SPF::compare(const Rdata& other) const {
+ const SPF& other_txt = dynamic_cast<const SPF&>(other);
+
+ return (impl_->compare(*other_txt.impl_));
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2012-2021 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 <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+struct SSHFPImpl {
+ // straightforward representation of SSHFP RDATA fields
+ SSHFPImpl(uint8_t algorithm, uint8_t fingerprint_type,
+ const vector<uint8_t>& fingerprint) :
+ algorithm_(algorithm),
+ fingerprint_type_(fingerprint_type),
+ fingerprint_(fingerprint)
+ {}
+
+ uint8_t algorithm_;
+ uint8_t fingerprint_type_;
+ const vector<uint8_t> fingerprint_;
+};
+
+// helper function for string and lexer constructors
+SSHFPImpl*
+SSHFP::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 255) {
+ isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
+ }
+
+ const uint32_t fingerprint_type =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (fingerprint_type > 255) {
+ isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
+ }
+
+ std::string fingerprint_str;
+ std::string fingerprint_substr;
+ 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(fingerprint_substr);
+ fingerprint_str.append(fingerprint_substr);
+ }
+ lexer.ungetToken();
+
+ vector<uint8_t> fingerprint;
+ // If fingerprint is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (fingerprint_str.size() > 0) {
+ try {
+ decodeHex(fingerprint_str, fingerprint);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
+ }
+ }
+
+ return (new SSHFPImpl(algorithm, fingerprint_type, fingerprint));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SSHFP 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 Algorithm and Fingerprint Type fields must be within their valid
+/// ranges, but are not constrained to the values defined in RFC4255.
+///
+/// The Fingerprint field may be absent, but if present it must contain a
+/// valid hex encoding of the fingerprint. For compatibility with BIND 9,
+/// whitespace is allowed in the hex text (RFC4255 is silent on the matter).
+///
+/// \throw InvalidRdataText if any fields are missing, are out of their
+/// valid ranges or are incorrect, or if the fingerprint is not a valid
+/// hex string.
+///
+/// \param sshfp_str A string containing the RDATA to be created
+SSHFP::SSHFP(const string& sshfp_str) :
+ impl_(NULL)
+{
+ // 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 SSHFPImpl that constructFromLexer() returns.
+ std::unique_ptr<SSHFPImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(sshfp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SSHFP: "
+ << sshfp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SSHFP from '" <<
+ sshfp_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SSHFP RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range or are
+/// incorrect, or if the fingerprint is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+SSHFP::SSHFP(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid SSHFP RDATA.
+///
+/// The Algorithm and Fingerprint Type fields are not checked for unknown
+/// values. It is okay for the fingerprint data to be missing (see the
+/// description of the constructor from string).
+SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "SSHFP record too short");
+ }
+
+ const uint8_t algorithm = buffer.readUint8();
+ const uint8_t fingerprint_type = buffer.readUint8();
+
+ vector<uint8_t> fingerprint;
+ rdata_len -= 2;
+ if (rdata_len > 0) {
+ fingerprint.resize(rdata_len);
+ buffer.readData(&fingerprint[0], rdata_len);
+ }
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
+}
+
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const string& fingerprint_txt) :
+ impl_(NULL)
+{
+ vector<uint8_t> fingerprint;
+ try {
+ decodeHex(fingerprint_txt, fingerprint);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
+ }
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
+}
+
+SSHFP::SSHFP(const SSHFP& other) :
+ Rdata(), impl_(new SSHFPImpl(*other.impl_))
+{}
+
+SSHFP&
+SSHFP::operator=(const SSHFP& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ SSHFPImpl* newimpl = new SSHFPImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+SSHFP::~SSHFP() {
+ delete impl_;
+}
+
+void
+SSHFP::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(impl_->algorithm_);
+ buffer.writeUint8(impl_->fingerprint_type_);
+
+ if (!impl_->fingerprint_.empty()) {
+ buffer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
+ }
+}
+
+void
+SSHFP::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(impl_->algorithm_);
+ renderer.writeUint8(impl_->fingerprint_type_);
+
+ if (!impl_->fingerprint_.empty()) {
+ renderer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
+ }
+}
+
+string
+SSHFP::toText() const {
+ return (lexical_cast<string>(static_cast<int>(impl_->algorithm_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->fingerprint_type_)) +
+ (impl_->fingerprint_.empty() ? "" :
+ " " + encodeHex(impl_->fingerprint_)));
+}
+
+int
+SSHFP::compare(const Rdata& other) const {
+ const SSHFP& other_sshfp = dynamic_cast<const SSHFP&>(other);
+
+ if (impl_->algorithm_ < other_sshfp.impl_->algorithm_) {
+ return (-1);
+ } else if (impl_->algorithm_ > other_sshfp.impl_->algorithm_) {
+ return (1);
+ }
+
+ if (impl_->fingerprint_type_ < other_sshfp.impl_->fingerprint_type_) {
+ return (-1);
+ } else if (impl_->fingerprint_type_ >
+ other_sshfp.impl_->fingerprint_type_) {
+ return (1);
+ }
+
+ const size_t this_len = impl_->fingerprint_.size();
+ const size_t other_len = other_sshfp.impl_->fingerprint_.size();
+ const size_t cmplen = min(this_len, other_len);
+
+ if (cmplen > 0) {
+ const int cmp = memcmp(&impl_->fingerprint_[0],
+ &other_sshfp.impl_->fingerprint_[0],
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ }
+
+ if (this_len == other_len) {
+ return (0);
+ } else if (this_len < other_len) {
+ return (-1);
+ } else {
+ return (1);
+ }
+}
+
+uint8_t
+SSHFP::getAlgorithmNumber() const {
+ return (impl_->algorithm_);
+}
+
+uint8_t
+SSHFP::getFingerprintType() const {
+ return (impl_->fingerprint_type_);
+}
+
+const std::vector<uint8_t>&
+SSHFP::getFingerprint() const {
+ return (impl_->fingerprint_);
+}
+
+size_t
+SSHFP::getFingerprintLength() const {
+ return (impl_->fingerprint_.size());
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2021 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 <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+#include <util/time_utilities.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+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
+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 (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) : impl_(0) {
+ // 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 TKEYImpl that constructFromLexer() returns.
+ std::unique_ptr<TKEYImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(tkey_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(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());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \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) :
+ impl_(0)
+{
+ 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_ = 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) :
+ impl_(0)
+{
+ 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_ = 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);
+ }
+
+ TKEYImpl* newimpl = new TKEYImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TKEY::~TKEY() {
+ delete impl_;
+}
+
+/// \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);
+ }
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2014-2021 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 <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata_pimpl_holder.h>
+
+using namespace std;
+using boost::lexical_cast;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+struct TLSAImpl {
+ // straightforward representation of TLSA RDATA fields
+ TLSAImpl(uint8_t certificate_usage, uint8_t selector,
+ uint8_t matching_type, const vector<uint8_t>& data) :
+ certificate_usage_(certificate_usage),
+ selector_(selector),
+ matching_type_(matching_type),
+ data_(data)
+ {}
+
+ uint8_t certificate_usage_;
+ uint8_t selector_;
+ uint8_t matching_type_;
+ const vector<uint8_t> data_;
+};
+
+// helper function for string and lexer constructors
+TLSAImpl*
+TLSA::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t certificate_usage =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (certificate_usage > 255) {
+ isc_throw(InvalidRdataText,
+ "TLSA certificate usage field out of range");
+ }
+
+ const uint32_t selector =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (selector > 255) {
+ isc_throw(InvalidRdataText,
+ "TLSA selector field out of range");
+ }
+
+ const uint32_t matching_type =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (matching_type > 255) {
+ isc_throw(InvalidRdataText,
+ "TLSA matching type field out of range");
+ }
+
+ std::string certificate_assoc_data;
+ std::string data_substr;
+ 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(data_substr);
+ certificate_assoc_data.append(data_substr);
+ }
+ lexer.ungetToken();
+
+ if (certificate_assoc_data.empty()) {
+ isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+ }
+
+ vector<uint8_t> data;
+ try {
+ decodeHex(certificate_assoc_data, data);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText,
+ "Bad TLSA certificate association data: " << e.what());
+ }
+
+ return (new TLSAImpl(certificate_usage, selector, matching_type, data));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid TLSA 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 Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698.
+///
+/// The Certificate Association Data Field field may be absent, but if
+/// present it must contain a valid hex encoding of the data. Whitespace
+/// is allowed in the hex text.
+///
+/// \throw InvalidRdataText if any fields are missing, out of their
+/// valid ranges, or are incorrect, or Certificate Association Data is
+/// not a valid hex string.
+///
+/// \param tlsa_str A string containing the RDATA to be created
+TLSA::TLSA(const string& tlsa_str) :
+ impl_(NULL)
+{
+ // We use a smart pointer here because if there is an exception in
+ // this constructor, the destructor is not called and there could be
+ // a leak of the TLSAImpl that constructFromLexer() returns.
+ RdataPimplHolder<TLSAImpl> impl_ptr;
+
+ try {
+ std::istringstream ss(tlsa_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for TLSA: "
+ << tlsa_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct TLSA from '" <<
+ tlsa_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an TLSA RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range, or are
+/// incorrect, or Certificate Association Data is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TLSA::TLSA(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
+{
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid TLSA RDATA.
+///
+/// The Certificate Usage, Selector and Matching Type fields must be
+/// within their valid ranges, but are not constrained to the values
+/// defined in RFC6698. It is okay for the certificate association data
+/// to be missing (see the description of the constructor from string).
+TLSA::TLSA(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 3) {
+ isc_throw(InvalidRdataLength, "TLSA record too short");
+ }
+
+ const uint8_t certificate_usage = buffer.readUint8();
+ const uint8_t selector = buffer.readUint8();
+ const uint8_t matching_type = buffer.readUint8();
+
+ vector<uint8_t> data;
+ rdata_len -= 3;
+
+ if (rdata_len == 0) {
+ isc_throw(InvalidRdataLength,
+ "Empty TLSA certificate association data");
+ }
+
+ data.resize(rdata_len);
+ buffer.readData(&data[0], rdata_len);
+
+ impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(uint8_t certificate_usage, uint8_t selector,
+ uint8_t matching_type, const std::string& certificate_assoc_data) :
+ impl_(NULL)
+{
+ if (certificate_assoc_data.empty()) {
+ isc_throw(InvalidRdataText, "Empty TLSA certificate association data");
+ }
+
+ vector<uint8_t> data;
+ try {
+ decodeHex(certificate_assoc_data, data);
+ } catch (const isc::BadValue& e) {
+ isc_throw(InvalidRdataText,
+ "Bad TLSA certificate association data: " << e.what());
+ }
+
+ impl_ = new TLSAImpl(certificate_usage, selector, matching_type, data);
+}
+
+TLSA::TLSA(const TLSA& other) :
+ Rdata(), impl_(new TLSAImpl(*other.impl_))
+{}
+
+TLSA&
+TLSA::operator=(const TLSA& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ TLSAImpl* newimpl = new TLSAImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TLSA::~TLSA() {
+ delete impl_;
+}
+
+void
+TLSA::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint8(impl_->certificate_usage_);
+ buffer.writeUint8(impl_->selector_);
+ buffer.writeUint8(impl_->matching_type_);
+
+ // The constructors must ensure that the certificate association
+ // data field is not empty.
+ assert(!impl_->data_.empty());
+ buffer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+void
+TLSA::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint8(impl_->certificate_usage_);
+ renderer.writeUint8(impl_->selector_);
+ renderer.writeUint8(impl_->matching_type_);
+
+ // The constructors must ensure that the certificate association
+ // data field is not empty.
+ assert(!impl_->data_.empty());
+ renderer.writeData(&impl_->data_[0], impl_->data_.size());
+}
+
+string
+TLSA::toText() const {
+ // The constructors must ensure that the certificate association
+ // data field is not empty.
+ assert(!impl_->data_.empty());
+
+ return (lexical_cast<string>(static_cast<int>(impl_->certificate_usage_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->selector_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->matching_type_)) + " " +
+ encodeHex(impl_->data_));
+}
+
+int
+TLSA::compare(const Rdata& other) const {
+ const TLSA& other_tlsa = dynamic_cast<const TLSA&>(other);
+
+ if (impl_->certificate_usage_ < other_tlsa.impl_->certificate_usage_) {
+ return (-1);
+ } else if (impl_->certificate_usage_ >
+ other_tlsa.impl_->certificate_usage_) {
+ return (1);
+ }
+
+ if (impl_->selector_ < other_tlsa.impl_->selector_) {
+ return (-1);
+ } else if (impl_->selector_ > other_tlsa.impl_->selector_) {
+ return (1);
+ }
+
+ if (impl_->matching_type_ < other_tlsa.impl_->matching_type_) {
+ return (-1);
+ } else if (impl_->matching_type_ >
+ other_tlsa.impl_->matching_type_) {
+ return (1);
+ }
+
+ const size_t this_len = impl_->data_.size();
+ const size_t other_len = other_tlsa.impl_->data_.size();
+ const size_t cmplen = min(this_len, other_len);
+
+ if (cmplen > 0) {
+ const int cmp = memcmp(&impl_->data_[0],
+ &other_tlsa.impl_->data_[0],
+ cmplen);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ }
+
+ if (this_len == other_len) {
+ return (0);
+ } else if (this_len < other_len) {
+ return (-1);
+ } else {
+ return (1);
+ }
+}
+
+uint8_t
+TLSA::getCertificateUsage() const {
+ return (impl_->certificate_usage_);
+}
+
+uint8_t
+TLSA::getSelector() const {
+ return (impl_->selector_);
+}
+
+uint8_t
+TLSA::getMatchingType() const {
+ return (impl_->matching_type_);
+}
+
+const std::vector<uint8_t>&
+TLSA::getData() const {
+ return (impl_->data_);
+}
+
+size_t
+TLSA::getDataLength() const {
+ return (impl_->data_.size());
+}
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <stdint.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/txt_like.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+
+TXT&
+TXT::operator=(const TXT& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ TXTImpl* newimpl = new TXTImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TXT::~TXT() {
+ delete impl_;
+}
+
+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"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace hs {
+
+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 {
+ return (0); // dummy. TBD
+}
+
+} // end of namespace "hs"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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 <stdint.h>
+#include <string.h>
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+
+#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
+#include <sys/socket.h> // for AF_INET/AF_INET6
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+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)) == NULL) {
+ 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_)));
+}
+} // end of namespace "in"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2010-2021 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/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+
+#include <stdint.h>
+#include <string.h>
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+
+#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
+#include <sys/socket.h> // for AF_INET/AF_INET6
+
+using namespace std;
+using namespace isc::util;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace in {
+
+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))
+ == NULL) {
+ 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_)));
+}
+
+} // end of namespace "in"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+// Copyright (C) 2011-2021 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 <stdint.h>
+#include <string.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace in {
+
+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"
+// Copyright (C) 2011-2021 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 <sstream>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/buffer.h>
+#include <util/strutil.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::str;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace in {
+
+struct SRVImpl {
+ // straightforward representation of SRV RDATA fields
+ SRVImpl(uint16_t priority, uint16_t weight, uint16_t port,
+ const Name& target) :
+ priority_(priority), weight_(weight), port_(port),
+ target_(target)
+ {}
+
+ uint16_t priority_;
+ uint16_t weight_;
+ uint16_t port_;
+ Name target_;
+};
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SRV 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 TARGET name must be absolute since there's no parameter that
+/// specifies the origin name; if it is not absolute, \c MissingNameOrigin
+/// exception will be thrown. It 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.
+SRV::SRV(const std::string& srv_str) :
+ impl_(NULL)
+{
+ try {
+ std::istringstream ss(srv_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV priority in: " << srv_str);
+ }
+ const uint16_t priority = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV weight in: " << srv_str);
+ }
+ const uint16_t weight = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV port in: " << srv_str);
+ }
+ const uint16_t port = static_cast<uint16_t>(num);
+
+ const Name targetname = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SRV: "
+ << srv_str);
+ }
+
+ impl_ = new SRVImpl(priority, weight, port, targetname);
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SRV from '" <<
+ srv_str << "': " << ex.what());
+ }
+}
+
+/// \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 end 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 RFC2782, the Target field must be a non compressed form
+/// of domain name. But this implementation accepts a %SRV RR even if that
+/// field is compressed as suggested in RFC3597.
+///
+/// \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.
+SRV::SRV(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 6) {
+ isc_throw(InvalidRdataLength, "SRV too short");
+ }
+
+ const uint16_t priority = buffer.readUint16();
+ const uint16_t weight = buffer.readUint16();
+ const uint16_t port = buffer.readUint16();
+ const Name targetname(buffer);
+
+ impl_ = new SRVImpl(priority, weight, port, targetname);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SRV RDATA. The TARGET 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.
+///
+/// The PRIORITY, WEIGHT and PORT fields must each be a valid decimal
+/// representation of an unsigned 16-bit integers respectively.
+///
+/// \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 TARGET when it
+/// is non-absolute.
+SRV::SRV(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV priority: " << num);
+ }
+ const uint16_t priority = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV weight: " << num);
+ }
+ const uint16_t weight = static_cast<uint16_t>(num);
+
+ num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid SRV port: " << num);
+ }
+ const uint16_t port = static_cast<uint16_t>(num);
+
+ const Name targetname = createNameFromLexer(lexer, origin);
+
+ impl_ = new SRVImpl(priority, weight, port, targetname);
+}
+
+/// \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.
+SRV::SRV(const SRV& source) :
+ Rdata(), impl_(new SRVImpl(*source.impl_))
+{}
+
+SRV&
+SRV::operator=(const SRV& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ SRVImpl* newimpl = new SRVImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+SRV::~SRV() {
+ delete impl_;
+}
+
+/// \brief Convert the \c SRV to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c SRV(const std::string&))).
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+///
+/// \return A \c string object that represents the \c SRV object.
+string
+SRV::toText() const {
+ using boost::lexical_cast;
+ return (lexical_cast<string>(impl_->priority_) +
+ " " + lexical_cast<string>(impl_->weight_) +
+ " " + lexical_cast<string>(impl_->port_) +
+ " " + impl_->target_.toText());
+}
+
+/// \brief Render the \c SRV 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
+SRV::toWire(OutputBuffer& buffer) const {
+ buffer.writeUint16(impl_->priority_);
+ buffer.writeUint16(impl_->weight_);
+ buffer.writeUint16(impl_->port_);
+ impl_->target_.toWire(buffer);
+}
+
+/// \brief Render the \c SRV in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC2782, the Target 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
+SRV::toWire(AbstractMessageRenderer& renderer) const {
+ renderer.writeUint16(impl_->priority_);
+ renderer.writeUint16(impl_->weight_);
+ renderer.writeUint16(impl_->port_);
+ renderer.writeName(impl_->target_, false);
+}
+
+/// \brief Compare two instances of \c SRV RDATA.
+///
+/// See documentation in \c Rdata.
+int
+SRV::compare(const Rdata& other) const {
+ const SRV& other_srv = dynamic_cast<const SRV&>(other);
+
+ if (impl_->priority_ != other_srv.impl_->priority_) {
+ return (impl_->priority_ < other_srv.impl_->priority_ ? -1 : 1);
+ }
+ if (impl_->weight_ != other_srv.impl_->weight_) {
+ return (impl_->weight_ < other_srv.impl_->weight_ ? -1 : 1);
+ }
+ if (impl_->port_ != other_srv.impl_->port_) {
+ return (impl_->port_ < other_srv.impl_->port_ ? -1 : 1);
+ }
+
+ return (compareNames(impl_->target_, other_srv.impl_->target_));
+}
+
+uint16_t
+SRV::getPriority() const {
+ return (impl_->priority_);
+}
+
+uint16_t
+SRV::getWeight() const {
+ return (impl_->weight_);
+}
+
+uint16_t
+SRV::getPort() const {
+ return (impl_->port_);
+}
+
+const Name&
+SRV::getTarget() const {
+ return (impl_->target_);
+}
+
+} // 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..85e6551
--- /dev/null
+++ b/src/lib/dns/rdataclass.h
@@ -0,0 +1,2746 @@
+///////////////
+///////////////
+/////////////// THIS FILE IS AUTOMATICALLY GENERATED BY gen-rdatacode.py.
+/////////////// DO NOT EDIT!
+///////////////
+///////////////
+
+
+#ifndef DNS_RDATACLASS_H
+#define DNS_RDATACLASS_H 1
+
+#include <dns/master_loader.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class MasterLexer;
+class MasterLoaderCallbacks;
+}
+}
+// Copyright (C) 2010-2021 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 ANY_TSIG_250_H
+#define ANY_TSIG_250_H 1
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+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:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+
+ /// \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:
+ TSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ TSIGImpl* impl_;
+};
+
+} // end of namespace "any"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // ANY_TSIG_250_H
+
+// Copyright (C) 2010-2021 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 CH_A_1_H
+#define CH_A_1_H 1
+
+#include <string>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace ch {
+
+class A : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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_COMMON_MEMBERS
+};
+
+} // end of namespace "ch"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // CH_A_1_H
+
+// Copyright (C) 2011-2021 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 GENERIC_AFSDB_18_H
+#define GENERIC_AFSDB_18_H 1
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+/// \brief \c rdata::AFSDB class represents the AFSDB RDATA as defined %in
+/// RFC1183.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// AFSDB RDATA.
+class AFSDB : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit AFSDB(const std::string& type_str);
+ AFSDB(isc::util::InputBuffer& buffer, size_t rdata_len);
+ AFSDB(const AFSDB& other);
+ AFSDB(
+ 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_COMMON_MEMBERS
+
+ /// \brief Assignment operator.
+ ///
+ /// This method never throws an exception.
+ AFSDB& operator=(const AFSDB& source);
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return the value of the server field.
+ ///
+ /// \return A reference to a \c Name class object corresponding to the
+ /// internal server name.
+ ///
+ /// This method never throws an exception.
+ const Name& getServer() const;
+
+ /// \brief Return the value of the subtype field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getSubtype() const;
+
+private:
+ void createFromLexer(MasterLexer& lexer, const Name* origin);
+
+ uint16_t subtype_;
+ Name server_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_AFSDB_18_H
+
+// Copyright (C) 2014-2021 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 GENERIC_CAA_257_H
+#define GENERIC_CAA_257_H 1
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct CAAImpl;
+
+class CAA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit CAA(const std::string& type_str);
+ CAA(isc::util::InputBuffer& buffer, size_t rdata_len);
+ CAA(const CAA& other);
+ CAA(
+ 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_COMMON_MEMBERS
+
+ CAA(uint8_t flags, const std::string& tag, const std::string& value);
+ CAA& operator=(const CAA& source);
+ ~CAA();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return the Flags field of the CAA RDATA.
+ uint8_t getFlags() const;
+
+ /// \brief Return the Tag field of the CAA RDATA.
+ const std::string& getTag() const;
+
+ /// \brief Return the Value field of the CAA RDATA.
+ ///
+ /// Note: The const reference which is returned is valid only during
+ /// the lifetime of this \c generic::CAA object. It should not be
+ /// used afterwards.
+ const std::vector<uint8_t>& getValue() const;
+
+private:
+ CAAImpl* constructFromLexer(MasterLexer& lexer);
+
+ CAAImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_CAA_257_H
+
+// Copyright (C) 2010-2021 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 GENERIC_CNAME_5_H
+#define GENERIC_CNAME_5_H 1
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class CNAME : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit CNAME(const std::string& type_str);
+ CNAME(isc::util::InputBuffer& buffer, size_t rdata_len);
+ CNAME(const CNAME& other);
+ CNAME(
+ 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_COMMON_MEMBERS
+
+ // CNAME specific methods
+ CNAME(const Name& cname);
+ const Name& getCname() const;
+private:
+ Name cname_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_CNAME_5_H
+
+// Copyright (C) 2011-2021 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 GENERIC_DLV_32769_H
+#define GENERIC_DLV_32769_H 1
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
+
+/// \brief \c rdata::generic::DLV class represents the DLV RDATA as defined in
+/// RFC4431.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DLV RDATA.
+class DLV : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit DLV(const std::string& type_str);
+ DLV(isc::util::InputBuffer& buffer, size_t rdata_len);
+ DLV(const DLV& other);
+ DLV(
+ 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_COMMON_MEMBERS
+
+ /// \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.
+ DLV& operator=(const DLV& source);
+
+ /// \brief The destructor.
+ ~DLV();
+
+ /// \brief Return the value of the Tag field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getTag() const;
+private:
+ typedef detail::DSLikeImpl<DLV, 32769> DLVImpl;
+ DLVImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_DLV_32769_H
+
+// Copyright (C) 2010-2021 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 GENERIC_DNAME_39_H
+#define GENERIC_DNAME_39_H 1
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class DNAME : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit DNAME(const std::string& type_str);
+ DNAME(isc::util::InputBuffer& buffer, size_t rdata_len);
+ DNAME(const DNAME& other);
+ DNAME(
+ 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_COMMON_MEMBERS
+
+ // DNAME specific methods
+ DNAME(const Name& dname);
+ const Name& getDname() const;
+private:
+ Name dname_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_DNAME_39_H
+
+// Copyright (C) 2010-2021 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 <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+
+#ifndef GENERIC_DNSKEY_48_H
+#define GENERIC_DNSKEY_48_H 1
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct DNSKEYImpl;
+
+class DNSKEY : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit DNSKEY(const std::string& type_str);
+ DNSKEY(isc::util::InputBuffer& buffer, size_t rdata_len);
+ DNSKEY(const DNSKEY& other);
+ DNSKEY(
+ 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_COMMON_MEMBERS
+ DNSKEY& operator=(const DNSKEY& source);
+ ~DNSKEY();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Returns the key tag
+ ///
+ /// \throw isc::OutOfRange if the key data for RSA/MD5 is too short
+ /// to support tag extraction.
+ uint16_t getTag() const;
+
+ uint16_t getFlags() const;
+ uint8_t getAlgorithm() const;
+
+private:
+ DNSKEYImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
+ DNSKEYImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_DNSKEY_48_H
+
+// Copyright (C) 2011-2021 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 GENERIC_DS_43_H
+#define GENERIC_DS_43_H 1
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
+
+/// \brief \c rdata::generic::DS class represents the DS RDATA as defined in
+/// RFC3658.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DS RDATA.
+class DS : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit DS(const std::string& type_str);
+ DS(isc::util::InputBuffer& buffer, size_t rdata_len);
+ DS(const DS& other);
+ DS(
+ 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_COMMON_MEMBERS
+
+ /// \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.
+ DS& operator=(const DS& source);
+
+ /// \brief The destructor.
+ ~DS();
+
+ /// \brief Return the value of the Tag field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getTag() const;
+private:
+ typedef detail::DSLikeImpl<DS, 43> DSImpl;
+ DSImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_DS_43_H
+
+// Copyright (C) 2011-2021 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 GENERIC_HINFO_13_H
+#define GENERIC_HINFO_13_H 1
+#include <stdint.h>
+
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class HINFOImpl;
+
+/// \brief \c HINFO class represents the HINFO rdata defined in
+/// RFC1034, RFC1035
+///
+/// This class implements the basic interfaces inherited from the
+/// \c rdata::Rdata class, and provides accessors specific to the
+/// HINFO rdata.
+class HINFO : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit HINFO(const std::string& type_str);
+ HINFO(isc::util::InputBuffer& buffer, size_t rdata_len);
+ HINFO(const HINFO& other);
+ HINFO(
+ 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_COMMON_MEMBERS
+
+ // HINFO specific methods
+ ~HINFO();
+
+ HINFO& operator=(const HINFO&);
+
+ const std::string getCPU() const;
+ const std::string getOS() const;
+
+private:
+ /// Helper template function for toWire()
+ ///
+ /// \param outputer Where to write data in
+ template <typename T>
+ void toWireHelper(T& outputer) const;
+
+ boost::scoped_ptr<HINFOImpl> impl_;
+};
+
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_HINFO_13_H
+
+// Copyright (C) 2011-2021 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 GENERIC_MINFO_14_H
+#define GENERIC_MINFO_14_H 1
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+/// \brief \c rdata::generic::MINFO class represents the MINFO RDATA as
+/// defined in RFC1035.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// MINFO RDATA.
+class MINFO : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit MINFO(const std::string& type_str);
+ MINFO(isc::util::InputBuffer& buffer, size_t rdata_len);
+ MINFO(const MINFO& other);
+ MINFO(
+ 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_COMMON_MEMBERS
+
+ /// \brief Define the assignment operator.
+ ///
+ /// \exception std::bad_alloc Memory allocation fails in copying
+ /// internal member variables (this should be very rare).
+ MINFO& operator=(const MINFO& source);
+
+ /// \brief Return the value of the rmailbox field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ ///
+ /// \note
+ /// Unlike the case of some other RDATA classes (such as
+ /// \c NS::getNSName()), this method constructs a new \c Name object
+ /// and returns it, instead of returning a reference to a \c Name object
+ /// internally maintained in the class (which is a private member).
+ /// This is based on the observation that this method will be rarely
+ /// used and even when it's used it will not be in a performance context
+ /// (for example, a recursive resolver won't need this field in its
+ /// resolution process). By returning a new object we have flexibility
+ /// of changing the internal representation without the risk of changing
+ /// the interface or method property.
+ /// The same note applies to the \c getEmailbox() method.
+ Name getRmailbox() const { return (rmailbox_); }
+
+ /// \brief Return the value of the emailbox field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ Name getEmailbox() const { return (emailbox_); }
+
+private:
+ Name rmailbox_;
+ Name emailbox_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_MINFO_14_H
+
+// Copyright (C) 2010-2021 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 GENERIC_MX_15_H
+#define GENERIC_MX_15_H 1
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class MX : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit MX(const std::string& type_str);
+ MX(isc::util::InputBuffer& buffer, size_t rdata_len);
+ MX(const MX& other);
+ MX(
+ 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_COMMON_MEMBERS
+
+ MX(uint16_t preference, const Name& mxname);
+
+ ///
+ /// Specialized methods
+ ///
+ const Name& getMXName() const;
+ uint16_t getMXPref() const;
+
+private:
+ void constructFromLexer(isc::dns::MasterLexer& lexer,
+ const isc::dns::Name* origin);
+
+ /// Note: this is a prototype version; we may reconsider
+ /// this representation later.
+ uint16_t preference_;
+ Name mxname_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_MX_15_H
+
+// Copyright (C) 2011-2021 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 GENERIC_NAPTR_35_H
+#define GENERIC_NAPTR_35_H 1
+
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <util/buffer.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class NAPTRImpl;
+
+/// \brief \c NAPTR class represents the NAPTR rdata defined in
+/// RFC2915, RFC2168 and RFC3403
+///
+/// This class implements the basic interfaces inherited from the
+/// \c rdata::Rdata class, and provides accessors specific to the
+/// NAPTR rdata.
+class NAPTR : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit NAPTR(const std::string& type_str);
+ NAPTR(isc::util::InputBuffer& buffer, size_t rdata_len);
+ NAPTR(const NAPTR& other);
+ NAPTR(
+ 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_COMMON_MEMBERS
+
+ // NAPTR specific methods
+ ~NAPTR();
+
+ NAPTR& operator=(const NAPTR& source);
+
+ uint16_t getOrder() const;
+ uint16_t getPreference() const;
+ const std::string getFlags() const;
+ const std::string getServices() const;
+ const std::string getRegexp() const;
+ const Name& getReplacement() const;
+private:
+ /// Helper template function for toWire()
+ ///
+ /// \param outputer Where to write data in
+ template <typename T>
+ void toWireHelper(T& outputer) const;
+
+ boost::scoped_ptr<NAPTRImpl> impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_NAPTR_35_H
+
+// Copyright (C) 2010-2021 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 GENERIC_NS_2_H
+#define GENERIC_NS_2_H 1
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class NS : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+ ///
+ /// Specialized constructor
+ ///
+ explicit NS(const Name& nsname) : nsname_(nsname) {}
+ ///
+ /// Specialized methods
+ ///
+ const Name& getNSName() const;
+private:
+ Name nsname_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_NS_2_H
+
+// Copyright (C) 2010-2021 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 <string>
+#include <vector>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+
+#ifndef GENERIC_NSEC3_50_H
+#define GENERIC_NSEC3_50_H 1
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct NSEC3Impl;
+
+class NSEC3 : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit NSEC3(const std::string& type_str);
+ NSEC3(isc::util::InputBuffer& buffer, size_t rdata_len);
+ NSEC3(const NSEC3& other);
+ NSEC3(
+ 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_COMMON_MEMBERS
+ NSEC3& operator=(const NSEC3& source);
+ ~NSEC3();
+
+ uint8_t getHashalg() const;
+ uint8_t getFlags() const;
+ uint16_t getIterations() const;
+ const std::vector<uint8_t>& getSalt() const;
+ const std::vector<uint8_t>& getNext() const;
+
+private:
+ NSEC3Impl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
+ NSEC3Impl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_NSEC3_50_H
+
+// Copyright (C) 2010-2021 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 <string>
+#include <vector>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+
+#ifndef GENERIC_NSEC3PARAM_51_H
+#define GENERIC_NSEC3PARAM_51_H 1
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct NSEC3PARAMImpl;
+
+class NSEC3PARAM : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit NSEC3PARAM(const std::string& type_str);
+ NSEC3PARAM(isc::util::InputBuffer& buffer, size_t rdata_len);
+ NSEC3PARAM(const NSEC3PARAM& other);
+ NSEC3PARAM(
+ 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_COMMON_MEMBERS
+ NSEC3PARAM& operator=(const NSEC3PARAM& source);
+ ~NSEC3PARAM();
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getHashalg() const;
+ uint8_t getFlags() const;
+ uint16_t getIterations() const;
+ const std::vector<uint8_t>& getSalt() const;
+
+private:
+ NSEC3PARAMImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
+ NSEC3PARAMImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_NSEC3PARAM_51_H
+
+// Copyright (C) 2010-2021 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 <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+#ifndef GENERIC_NSEC_47_H
+#define GENERIC_NSEC_47_H 1
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct NSECImpl;
+
+class NSEC : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit NSEC(const std::string& type_str);
+ NSEC(isc::util::InputBuffer& buffer, size_t rdata_len);
+ NSEC(const NSEC& other);
+ NSEC(
+ 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_COMMON_MEMBERS
+ NSEC& operator=(const NSEC& source);
+ ~NSEC();
+
+ // specialized methods
+
+ /// Return the next domain name.
+ ///
+ /// \exception std::bad_alloc Resource allocation failure in name copy.
+ ///
+ /// \return The next domain name field in the form of \c Name object.
+ const Name& getNextName() const;
+
+private:
+ NSECImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_NSEC_47_H
+
+// Copyright (C) 2010-2021 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 GENERIC_OPT_41_H
+#define GENERIC_OPT_41_H 1
+
+#include <string>
+
+#include <dns/rdata.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct OPTImpl;
+
+class OPT : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+
+ // 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:
+ OPTImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_OPT_41_H
+
+// Copyright (C) 2010-2021 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 GENERIC_PTR_12_H
+#define GENERIC_PTR_12_H 1
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class PTR : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+
+ ///
+ /// Specialized constructor
+ ///
+ explicit PTR(const Name& ptr_name) : ptr_name_(ptr_name) {}
+ ///
+ /// Specialized methods
+ ///
+ const Name& getPTRName() const;
+private:
+ Name ptr_name_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_PTR_12_H
+
+// Copyright (C) 2011-2021 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 GENERIC_RP_17_H
+#define GENERIC_RP_17_H 1
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+/// \brief \c rdata::generic::RP class represents the RP RDATA as defined in
+/// RFC1183.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// RP RDATA.
+class RP : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit RP(const std::string& type_str);
+ RP(isc::util::InputBuffer& buffer, size_t rdata_len);
+ RP(const RP& other);
+ RP(
+ 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_COMMON_MEMBERS
+
+ /// We use the default copy constructor and assignment operator.
+
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// The parameters are a straightforward mapping of %RP RDATA
+ /// fields as defined in RFC1183.
+ RP(const Name& mailbox, const Name& text) :
+ mailbox_(mailbox), text_(text)
+ {}
+
+ /// \brief Return the value of the mailbox field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ ///
+ /// \note
+ /// Unlike the case of some other RDATA classes (such as
+ /// \c NS::getNSName()), this method constructs a new \c Name object
+ /// and returns it, instead of returning a reference to a \c Name object
+ /// internally maintained in the class (which is a private member).
+ /// This is based on the observation that this method will be rarely used
+ /// and even when it's used it will not be in a performance context
+ /// (for example, a recursive resolver won't need this field in its
+ /// resolution process). By returning a new object we have flexibility of
+ /// changing the internal representation without the risk of changing
+ /// the interface or method property.
+ /// The same note applies to the \c getText() method.
+ Name getMailbox() const { return (mailbox_); }
+
+ /// \brief Return the value of the text field.
+ ///
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
+ Name getText() const { return (text_); }
+
+private:
+ Name mailbox_;
+ Name text_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_RP_17_H
+
+// Copyright (C) 2010-2021 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 <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+
+#ifndef GENERIC_RRSIG_46_H
+#define GENERIC_RRSIG_46_H 1
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+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:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+ RRSIG& operator=(const RRSIG& source);
+ ~RRSIG();
+
+ // specialized methods
+ const RRType& typeCovered() const;
+private:
+ // helper function for string and lexer constructors
+ RRSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ RRSIGImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_RRSIG_46_H
+
+// Copyright (C) 2010-2021 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 GENERIC_SOA_6_H
+#define GENERIC_SOA_6_H 1
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/serial.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+class SOA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+
+ 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];
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_SOA_6_H
+
+// Copyright (C) 2010-2021 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 GENERIC_SPF_99_H
+#define GENERIC_SPF_99_H 1
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+namespace detail {
+template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
+
+/// \brief \c rdata::SPF class represents the SPF RDATA as defined %in
+/// RFC4408.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+class SPF : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit SPF(const std::string& type_str);
+ SPF(isc::util::InputBuffer& buffer, size_t rdata_len);
+ SPF(const SPF& other);
+ SPF(
+ 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_COMMON_MEMBERS
+
+ /// \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.
+ SPF& operator=(const SPF& source);
+
+ /// \brief The destructor.
+ ~SPF();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return a reference to the data strings
+ ///
+ /// This method never throws an exception.
+ const std::vector<std::vector<uint8_t> >& getString() const;
+
+private:
+ typedef isc::dns::rdata::generic::detail::TXTLikeImpl<SPF, 99> SPFImpl;
+ SPFImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_SPF_99_H
+
+// Copyright (C) 2012-2021 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 GENERIC_SSHFP_44_H
+#define GENERIC_SSHFP_44_H 1
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct SSHFPImpl;
+
+class SSHFP : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit SSHFP(const std::string& type_str);
+ SSHFP(isc::util::InputBuffer& buffer, size_t rdata_len);
+ SSHFP(const SSHFP& other);
+ SSHFP(
+ 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_COMMON_MEMBERS
+
+ SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const std::string& fingerprint);
+ SSHFP& operator=(const SSHFP& source);
+ ~SSHFP();
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getAlgorithmNumber() const;
+ uint8_t getFingerprintType() const;
+ const std::vector<uint8_t>& getFingerprint() const;
+ size_t getFingerprintLength() const;
+
+private:
+ SSHFPImpl* constructFromLexer(MasterLexer& lexer);
+
+ SSHFPImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_SSHFP_44_H
+
+// Copyright (C) 2021 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 GENERIC_TKEY_249_H
+#define GENERIC_TKEY_249_H 1
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+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:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+
+ /// \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:
+ TKEYImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
+ TKEYImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_TKEY_249_H
+
+// Copyright (C) 2014-2021 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 GENERIC_TLSA_52_H
+#define GENERIC_TLSA_52_H 1
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+struct TLSAImpl;
+
+class TLSA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit TLSA(const std::string& type_str);
+ TLSA(isc::util::InputBuffer& buffer, size_t rdata_len);
+ TLSA(const TLSA& other);
+ TLSA(
+ 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_COMMON_MEMBERS
+
+ TLSA(uint8_t certificate_usage, uint8_t selector,
+ uint8_t matching_type, const std::string& certificate_assoc_data);
+ TLSA& operator=(const TLSA& source);
+ ~TLSA();
+
+ ///
+ /// Specialized methods
+ ///
+ uint8_t getCertificateUsage() const;
+ uint8_t getSelector() const;
+ uint8_t getMatchingType() const;
+ const std::vector<uint8_t>& getData() const;
+ size_t getDataLength() const;
+
+private:
+ TLSAImpl* constructFromLexer(MasterLexer& lexer);
+
+ TLSAImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_TLSA_52_H
+
+// Copyright (C) 2010-2021 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 GENERIC_TXT_16_H
+#define GENERIC_TXT_16_H 1
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace generic {
+
+namespace detail {
+template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
+
+class TXT : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+
+ TXT& operator=(const TXT& source);
+ ~TXT();
+
+private:
+ typedef isc::dns::rdata::generic::detail::TXTLikeImpl<TXT, 16> TXTImpl;
+ TXTImpl* impl_;
+};
+
+} // end of namespace "generic"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // GENERIC_TXT_16_H
+
+// Copyright (C) 2010-2021 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 HS_A_1_H
+#define HS_A_1_H 1
+
+#include <string>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace hs {
+
+class A : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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_COMMON_MEMBERS
+};
+
+} // end of namespace "hs"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // HS_A_1_H
+
+// Copyright (C) 2010-2021 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 IN_A_1_H
+#define IN_A_1_H 1
+
+#include <string>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace in {
+
+class A : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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_COMMON_MEMBERS
+
+ //We can use the default destructor.
+ //virtual ~A() {}
+ // notyet:
+ //const struct in_addr& getAddress() const { return (addr_); }
+private:
+ uint32_t addr_; // raw IPv4 address (network byte order)
+};
+} // end of namespace "in"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // IN_A_1_H
+
+// Copyright (C) 2010-2021 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 IN_AAAA_28_H
+#define IN_AAAA_28_H 1
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace in {
+
+class AAAA : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+ // notyet:
+ //const struct in6_addr& getAddress() const { return (addr_); }
+private:
+ uint8_t addr_[16]; // raw IPv6 address (network byte order)
+};
+
+} // end of namespace "in"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // IN_AAAA_28_H
+
+// Copyright (C) 2011-2021 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 IN_DHCID_49_H
+#define IN_DHCID_49_H 1
+
+#include <string>
+#include <vector>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace in {
+
+/// \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:
+ // BEGIN_COMMON_MEMBERS
+
+ 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;
+
+ // END_COMMON_MEMBERS
+
+ /// \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 // IN_DHCID_49_H
+
+// Copyright (C) 2011-2021 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 IN_SRV_33_H
+#define IN_SRV_33_H 1
+
+#include <stdint.h>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace util {
+
+class InputBuffer;
+class OutputBuffer;
+}
+
+namespace dns {
+
+// BEGIN_COMMON_DECLARATIONS
+
+class AbstractMessageRenderer;
+
+// END_COMMON_DECLARATIONS
+
+namespace rdata {
+namespace in {
+
+struct SRVImpl;
+
+/// \brief \c rdata::SRV class represents the SRV RDATA as defined %in
+/// RFC2782.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// SRV RDATA.
+class SRV : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+
+ explicit SRV(const std::string& type_str);
+ SRV(isc::util::InputBuffer& buffer, size_t rdata_len);
+ SRV(const SRV& other);
+ SRV(
+ 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_COMMON_MEMBERS
+
+ /// \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.
+ SRV& operator=(const SRV& source);
+
+ /// \brief The destructor.
+ ~SRV();
+
+ ///
+ /// Specialized methods
+ ///
+
+ /// \brief Return the value of the priority field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getPriority() const;
+
+ /// \brief Return the value of the weight field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getWeight() const;
+
+ /// \brief Return the value of the port field.
+ ///
+ /// This method never throws an exception.
+ uint16_t getPort() const;
+
+ /// \brief Return the value of the target field.
+ ///
+ /// \return A reference to a \c Name class object corresponding to the
+ /// internal target name.
+ ///
+ /// This method never throws an exception.
+ const Name& getTarget() const;
+
+private:
+ SRVImpl* impl_;
+};
+
+} // end of namespace "in"
+} // end of namespace "rdata"
+} // end of namespace "dns"
+} // end of namespace "isc"
+#endif // IN_SRV_33_H
+
+
+#endif // DNS_RDATACLASS_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdatafields.cc b/src/lib/dns/rdatafields.cc
new file mode 100644
index 0000000..e02ec8f
--- /dev/null
+++ b/src/lib/dns/rdatafields.cc
@@ -0,0 +1,216 @@
+// Copyright (C) 2010-2015,2017 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 <stdint.h>
+
+#include <cassert>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdatafields.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::util::OutputBuffer;
+using isc::util::InputBuffer;
+
+namespace isc {
+namespace dns {
+namespace rdata {
+
+/// This is a helper class for \c RdataFields.
+///
+/// It manages a local storage for the data when \c RdataFields is constructed
+/// from an \c Rdata.
+/// To minimize construction overhead in the other case, an instance of
+/// this class is instantiated only when necessary - we don't need the vectors
+/// when only rendering.
+struct RdataFields::RdataFieldsDetail {
+ RdataFieldsDetail(const vector<FieldSpec>& fields,
+ const uint8_t* data, size_t data_length) :
+ allocated_fields_(fields),
+ allocated_data_(data, data + data_length)
+ {}
+ const vector<FieldSpec> allocated_fields_;
+ const vector<uint8_t> allocated_data_;
+};
+
+namespace {
+// This class is used to divide the content of RDATA into \c RdataField
+// fields via message rendering logic.
+// The idea is to identify domain name fields in the writeName() method,
+// and determine whether they are compressible using the "compress"
+// parameter.
+// Other types of data are simply copied into the internal buffer, and
+// consecutive such fields are combined into a single \c RdataField field.
+//
+// Technically, this use of inheritance may be considered a violation of
+// Liskov Substitution Principle in that it doesn't actually compress domain
+// names, and some of the methods are not expected to be used.
+// In fact, skip() or trim() may not be make much sense in this context.
+// Nevertheless we keep this idea at the moment. Since the usage is limited
+// (it's only used within this file, and only used with \c Rdata variants),
+// it's hopefully an acceptable practice.
+class RdataFieldComposer : public AbstractMessageRenderer {
+public:
+ RdataFieldComposer() :
+ truncated_(false), length_limit_(65535),
+ mode_(CASE_INSENSITIVE), last_data_pos_(0)
+ {}
+ virtual ~RdataFieldComposer() {}
+ virtual bool isTruncated() const { return (truncated_); }
+ virtual size_t getLengthLimit() const { return (length_limit_); }
+ virtual CompressMode getCompressMode() const { return (mode_); }
+ virtual void setTruncated() { truncated_ = true; }
+ virtual void setLengthLimit(size_t len) { length_limit_ = len; }
+ virtual void setCompressMode(CompressMode mode) { mode_ = mode; }
+ virtual void writeName(const LabelSequence&, bool) {}
+ virtual void writeName(const Name& name, bool compress) {
+ extendData();
+ const RdataFields::Type field_type =
+ compress ? RdataFields::COMPRESSIBLE_NAME :
+ RdataFields::INCOMPRESSIBLE_NAME;
+ // TODO: When we get rid of need for getBuffer, we can output the name
+ // to a buffer and then write the buffer inside
+ name.toWire(getBuffer());
+ fields_.push_back(RdataFields::FieldSpec(field_type,
+ name.getLength()));
+ last_data_pos_ = getLength();
+ }
+
+ virtual void clear() {
+ isc_throw(Unexpected, "unexpected clear() for RdataFieldComposer");
+ }
+ bool truncated_;
+ size_t length_limit_;
+ CompressMode mode_;
+ vector<RdataFields::FieldSpec> fields_;
+ vector<RdataFields::FieldSpec>& getFields() {
+ extendData();
+ return (fields_);
+ }
+ // We use generic write* methods, with the exception of writeName.
+ // So new data can arrive without us knowing it, this considers all new
+ // data to be just data and extends the fields to take it into account.
+ size_t last_data_pos_;
+ void extendData() {
+ // No news, return to work
+ if (getLength() == last_data_pos_) {
+ return;
+ }
+ // The new bytes are just ordinary uninteresting data
+ if (fields_.empty() || fields_.back().type != RdataFields::DATA) {
+ fields_.push_back(RdataFields::FieldSpec(RdataFields::DATA, 0));
+ }
+ // We added this much data from last time
+ fields_.back().len += getLength() - last_data_pos_;
+ last_data_pos_ = getLength();
+ }
+};
+
+}
+
+RdataFields::RdataFields(const Rdata& rdata) {
+ RdataFieldComposer field_composer;
+ rdata.toWire(field_composer);
+ nfields_ = field_composer.getFields().size();
+ data_length_ = field_composer.getLength();
+ if (nfields_ > 0) {
+ assert(data_length_ > 0);
+ detail_ = new RdataFieldsDetail(field_composer.getFields(),
+ static_cast<const uint8_t*>
+ (field_composer.getData()),
+ field_composer.getLength());
+ data_ = &detail_->allocated_data_[0];
+ fields_ = &detail_->allocated_fields_[0];
+ } else {
+ assert(data_length_ == 0);
+ detail_ = NULL;
+ data_ = NULL;
+ fields_ = NULL;
+ }
+}
+
+RdataFields::RdataFields(const void* fields, const unsigned int fields_length,
+ const void* data, const size_t data_length) :
+ fields_(static_cast<const FieldSpec*>(fields)),
+ nfields_(fields_length / sizeof(*fields_)),
+ data_(static_cast<const uint8_t*>(data)),
+ data_length_(data_length),
+ detail_(NULL)
+{
+ if ((fields_ == NULL && nfields_ > 0) ||
+ (fields_ != NULL && nfields_ == 0)) {
+ isc_throw(InvalidParameter,
+ "Inconsistent parameters for RdataFields: fields_length ("
+ << fields_length << ") and fields conflict each other");
+ }
+ if ((data_ == NULL && data_length_ > 0) ||
+ (data_ != NULL && data_length_ == 0)) {
+ isc_throw(InvalidParameter,
+ "Inconsistent parameters for RdataFields: data length ("
+ << data_length_ << ") and data conflict each other");
+ }
+
+ size_t total_length = 0;
+ for (unsigned int i = 0; i < nfields_; ++i) {
+ total_length += fields_[i].len;
+ }
+ if (total_length != data_length_) {
+ isc_throw(InvalidParameter,
+ "Inconsistent parameters for RdataFields: "
+ "fields len: " << total_length <<
+ " data len: " << data_length_);
+ }
+}
+
+RdataFields::~RdataFields() {
+ delete detail_;
+}
+
+RdataFields::FieldSpec
+RdataFields::getFieldSpec(const unsigned int field_id) const {
+ if (field_id >= nfields_) {
+ isc_throw(OutOfRange, "Rdata field ID is out of range: " << field_id);
+ }
+ return (fields_[field_id]);
+}
+
+void
+RdataFields::toWire(AbstractMessageRenderer& renderer) const {
+ size_t offset = 0;
+
+ for (unsigned int i = 0; i < nfields_; ++i) {
+ if (fields_[i].type == DATA) {
+ renderer.writeData(data_ + offset, fields_[i].len);
+ } else {
+ // XXX: this is inefficient. Even if it's quite likely the
+ // data is a valid wire representation of a name we parse
+ // it to construct the Name object in the generic mode.
+ // This should be improved in a future version.
+ InputBuffer buffer(data_ + offset, fields_[i].len);
+ renderer.writeName(Name(buffer),
+ fields_[i].type == COMPRESSIBLE_NAME);
+ }
+ offset += fields_[i].len;
+ }
+}
+
+void
+RdataFields::toWire(OutputBuffer& buffer) const {
+ buffer.writeData(data_, data_length_);
+}
+} // end of namespace rdata
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/rdatafields.h b/src/lib/dns/rdatafields.h
new file mode 100644
index 0000000..c4a64ad
--- /dev/null
+++ b/src/lib/dns/rdatafields.h
@@ -0,0 +1,419 @@
+// 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/.
+
+#ifndef RDATAFIELDS_H
+#define RDATAFIELDS_H 1
+
+#include <stdint.h>
+
+#include <cstddef>
+
+namespace isc {
+namespace util {
+class OutputBuffer;
+}
+namespace dns {
+class AbstractMessageRenderer;
+
+namespace rdata {
+class Rdata;
+
+/// A low-level, RR type-independent representation of DNS RDATA.
+///
+/// <b>Purpose of the Class</b>
+///
+/// This class intends to help "serialization" of the content of RDATA
+/// in a space-efficient manner. Specific derived classes of \c Rdata
+/// focus on the convenience of accessing RDATA fields for RR type-specific
+/// protocol operations, and can be inefficient in terms of space.
+/// For example, a DNS character string may be internally represented as a
+/// \c std::string object with all of the overhead of the richer class.
+/// If an application needs to maintain a very large number of RRs and it
+/// does not have to perform RR specific operation so often, it may make more
+/// sense to store the data in memory in a lower-level but space efficient
+/// form.
+///
+/// Another purpose of this class is to improve rendering performance for
+/// RDATA. If the only requirement were space efficiency, it would be just
+/// sufficient to convert the \c RDATA into a binary sequence in the wire
+/// format. However, to render the data in a DNS message, we'd have to
+/// re-construct a corresponding \c Rdata object in the case where name
+/// compression is necessary. This is not desirable, and this class is
+/// provided to avoid such unnecessary overhead.
+///
+/// <b>Data Format</b>
+///
+/// To meet these goals, this class helps convert an \c Rdata object into
+/// two pieces of information: Wire-format representation of the \c Rdata
+/// and associated meta information for efficient rendering.
+///
+/// Specifically, it maintains the wire-format data as a sequence of typed
+/// fields. The types are:
+/// - Compressible name: a domain name as an RDATA field that can be compressed
+/// - Incompressible name: a domain name as an RDATA field that cannot be
+/// compressed
+/// - Other data: any other fields of RDATA, which should be treated as opaque
+///
+/// (See also the description of \c RdataFields::Type)
+/// Whether a name can or cannot be compressed is determined according to
+/// RFC3597.
+///
+/// A "other data" field may not always correspond to a single RDATA field.
+/// A \c RdataFields field (of other data) is just a contiguous region of the
+/// wire-format data that does not involve name compression.
+/// For example, the SOA RDATA begins with two "compressible" names followed
+/// by 5 32-bit fields.
+/// In \c RdataFields the last 5 fields would be considered a single 20-byte
+/// field.
+///
+/// Each \c RdataFields field is identified by the \c FieldSpec structure,
+/// which provides the type and length of the field.
+/// An \c RdataFields object internally maintains a sequence of \c FieldSpec
+/// objects in a form of plain C-style array, which can be referenced via
+/// a pointer returned by the \c getFieldSpecData() method.
+/// The \c \c FieldSpec for a specific field can also be retrieved by the
+/// \c getFieldSpec() method.
+///
+/// The following diagram shows the internal memory representation of
+/// an SOA RDATA in the form of \c RdataFields object and how an application
+/// can get access to the memory region.
+/** \verbatim
+accessible via |0 getDataLength() bytes
+getData()----------> <MNAME><RNAME><Rest of the data>
+ <---------- 3 * sizeof(FieldSpec) bytes ------------->
+getFieldSpecData()-> { compressible name { compressible name { other data
+ len: MNAME-len } len: RNAME-len } len: 20 }
+\endverbatim
+ */
+/// where MNAME and RNAME are wire format representations of the MNAME and
+/// RNAME fields of the SOA RDATA, respectively, and "Rest of the data"
+/// encodes the remaining 20 bytes of the RDATA in network byte order.
+///
+/// <b>Usage of the Class</b>
+///
+/// One major and common use case of the \c RdataFields class is to convert
+/// a \c Rdata object (possibly given from a DNS message or some configuration
+/// source such as a zone file) in the serialized format and store a copy of
+/// the data somewhere in memory. The following code sample implements this
+/// scenario:
+/// \code // assume "rdata" is a reference type to Rdata
+/// const RdataFields fields(rdata);
+/// const unsigned int fields_size = fields.getFieldDataSize();
+/// memcpy(some_place, fields.getFieldSpecData(), fields_size);
+/// const size_t data_length = fields.getDataLength();
+/// memcpy(other_place, fields.getData(), data_length);
+/// // (fields_size and data_length should be stored somewhere, too)
+/// \endcode
+///
+/// Another typical usage is to render the stored data in the wire format
+/// as efficiently as possible. The following code is an example of such
+/// usage:
+/// \code // assume "renderer" is of type MessageRenderer
+/// // retrieve data_length and fields_size from the storage
+/// RdataFields(some_place, fields_size, other_place,
+/// data_length).toWire(renderer);
+/// \endcode
+///
+/// <b>Notes to Users</b>
+///
+/// The main purposes of this class is to help efficient operation
+/// for some (limited classes of) performance sensitive application.
+/// For this reason the interface and implementation rely on relatively
+/// lower-level, riskier primitives such as passing around bare pointers.
+///
+/// It is therefore discouraged to use this class for general purpose
+/// applications that do not need to maximize performance in terms of either
+/// memory footprint or rendering speed.
+/// All functionality provided by this class can be achieved via higher level
+/// interfaces such as the \c Rdata class variants.
+/// Normal applications should use those interfaces.
+///
+/// The data format is public information so that an application can examine
+/// and use selected parts of data. For example, an application may want to
+/// encode domain names in RDATA in a different way while storing the other
+/// data in a separate place.
+/// However, at this moment the format is still in flux, and it may not
+/// be compatible with future versions (see below).
+///
+/// <b>Development Notes</b>
+///
+/// We should conduct benchmark tests to measure rendering performance.
+///
+/// The current implementation needs to re-construct name objects from
+/// compressible and incompressible name fields as wire-format data.
+/// This is not efficient, and we'll probably want to improve this in a
+/// future version. One possibility is to store offset information as well
+/// as the name data (at the cost of increasing memory footprint), and
+/// to use the pair of data for faster rendering.
+class RdataFields {
+public:
+ /// Types of \c RdataFields fields.
+ ///
+ /// \c COMPRESSIBLE_NAME and \c INCOMPRESSIBLE_NAME represent a domain
+ /// name used as a field of an RDATA that can and cannot be compressed
+ /// per RFC3597.
+ /// \c DATA means all other types of fields.
+ enum Type {
+ DATA, ///< Plain data.
+ COMPRESSIBLE_NAME, ///< A domain name subject to name compression.
+ INCOMPRESSIBLE_NAME ///< A domain name that shouldn't be compressed.
+ };
+
+ /// Structure that specifies a single \c RdataFields field.
+ ///
+ /// This is a straightforward pair of the type and length of a single
+ /// \c RdataFields field.
+ ///
+ /// In some cases an application may want to do deeper inspection of
+ /// some \c RdataFields field(s). For example, an application may want
+ /// to construct a \c Name object for each domain name field of an RDATA
+ /// and use it for some special purpose.
+ /// The \c FieldSpec structure provides necessary parameters to get access
+ /// to a specific \c RdataFields field.
+ ///
+ /// The following code snippet implements the above example scenario:
+ /// \code // assume "fields" is of type RdataFields
+ /// size_t offset = 0;
+ /// for (int i = 0; i < fields.getFieldCount(); ++i) {
+ /// const FieldSpec spec = fields.getFieldSpec(i);
+ /// if (spec.type == RdataFields::COMPRESSIBLE_NAME ||
+ /// spec.type == RdataFields::INCOMPRESSIBLE_NAME) {
+ /// InputBuffer ibuffer(fields.getData() + offset, spec.len);
+ /// Name name(ibuffer);
+ /// // do something with name
+ /// }
+ /// offset += spec.len;
+ /// } \endcode
+ ///
+ /// Note that the offset is not included in \c FieldSpec.
+ /// This is because such deeper inspection would be a relatively rare
+ /// operation while it is desirable to keep this structure as small as
+ /// possible for the purpose of space efficiency.
+ /// Also, if and when an application wants to look into a specific field,
+ /// it would be quite likely that the application iterates over all fields
+ /// and does something special for selected fields like the above example.
+ /// In that case the application can easily and efficiently identify the
+ /// necessary offset, again, as shown in the above code example.
+ ///
+ /// \todo We might find that 16bits per field is generally too much and
+ /// squeeze the two bit type into it as well, having 14bit length
+ /// (in the rare case of having too long field, it could be split into
+ /// multiple ones). That would save 2 bytes per item (one for the type,
+ /// one for padding).
+ struct FieldSpec {
+ FieldSpec(Type type_param, uint16_t len_param) :
+ type(type_param), len(len_param)
+ {}
+ Type type; ///< The type of the field.
+ uint16_t len; ///< The length of the field in bytes.
+ };
+
+ ///
+ /// \name Constructors and Destructor.
+ ///
+ /// \b Note:
+ /// The copy constructor and the assignment operator are intentionally
+ /// defined as private, making this class non copyable.
+ //@{
+private:
+ RdataFields(const RdataFields& source);
+ RdataFields& operator=(const RdataFields& source);
+
+public:
+ /// Constructor from Rdata.
+ ///
+ /// This constructor converts the data of a given \c Rdata object into
+ /// an \c RdataFields object so that the resulting data can be stored
+ /// in memory in a space-efficient way.
+ ///
+ /// It makes a local copy of the original data and dynamically allocates
+ /// necessary memory, so is not very efficient.
+ /// The basic idea is to perform the expensive conversion once and keep
+ /// using the result as long as possible to improve overall performance
+ /// in a longer term.
+ ///
+ /// If the internal resource allocation fails, a corresponding standard
+ /// exception will be thrown.
+ /// The current implementation of this constructor internally calls
+ /// the <code>Rdata::toWire(AbstractMessageRenderer&) const</code> method
+ /// for the conversion.
+ /// If that method throws an exception it will be propagated to the caller
+ /// of this constructor.
+ ///
+ /// \param rdata The RDATA for which the \c RdataFields to be constructed.
+ RdataFields(const Rdata& rdata);
+
+ /// Constructor from field parameters.
+ ///
+ /// The intended usage of this version of constructor is to form a
+ /// structured representation of \c RDATA encoded by the other
+ /// constructor so that the resulting object can be used for subsequent
+ /// operations such as rendering in the wire format.
+ /// This version is intended to be efficient by not making any copy
+ /// of variable length data or expensive data inspection.
+ ///
+ /// This constructor is basically exception free, except against bogus
+ /// input parameters.
+ /// Specifically, the parameters must meet the following conditions;
+ /// otherwise an exception of class \c InvalidParameter will be thrown.
+ /// - \c fields can be \c NULL if and only if \c nfields is 0
+ /// - \c data can be \c NULL if and only if \c data_length is 0
+ /// - the sum of the lengths of \c fields entries must be equal to
+ /// \c data_length
+ ///
+ /// This constructor assumes that the memory region pointed by \c data (if
+ /// non \c NULL) is encoded as a sequence of valid \c RdataFields fields,
+ /// and does not perform deep inspection on each field.
+ /// In particular, for fields of type \c COMPRESSIBLE_NAME or
+ /// \c INCOMPRESSIBLE_NAME, this constructor assumes the corresponding
+ /// memory region is a valid representation of domain name.
+ /// Otherwise, a subsequent method call such as
+ /// <code>toWire(AbstractMessageRenderer&) const</code>
+ /// may trigger an unexpected exception. It also expects the fields reside
+ /// on address that is valid for them (eg. it has valid alignment), see
+ /// getFieldSpecData() for details.
+ ///
+ /// It is the caller's responsibility to ensure this assumption.
+ /// In general, this constructor is expected to be used for serialized data
+ /// generated by the other constructor from a valid \c Rdata.
+ /// The result is not guaranteed if the data is generated in any other
+ /// ways.
+ ///
+ /// The resulting \c RdataFields object does not maintain a copy of
+ /// \c fields or \c data. It is the caller's responsibility to ensure
+ /// the memory regions pointed to by these parameters are valid and intact
+ /// as long as the \c RdataFields object is used.
+ ///
+ /// \param fields An array of \c FieldSpec entries. This can be \c NULL.
+ /// \param fields_length The total length of the \c fields.
+ /// \param data A pointer to memory region for the entire RDATA. This can
+ /// be NULL.
+ /// \param data_length The length of \c data in bytes.
+ RdataFields(const void* fields, const unsigned int fields_length,
+ const void* data, const size_t data_length);
+
+ /// The destructor.
+ ~RdataFields();
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Return the length of the entire RDATA encoded in the
+ /// \c RdataFields in bytes.
+ ///
+ /// This method never throws an exception.
+ unsigned int getDataLength() const { return (data_length_); }
+
+ /// \brief Return a pointer to the RDATA encoded in the \c RdataFields.
+ ///
+ /// The RdataFields holds ownership of the data.
+ ///
+ /// This method never throws an exception.
+ const void* getData() const { return (data_); }
+
+ /// \brief Return the number of bytes the buffer returned by
+ /// getFieldSpecData() will occupy.
+ ///
+ /// This method never throws an exception.
+ unsigned int getFieldSpecDataSize() const { return (nfields_ *
+ sizeof (*fields_)); }
+
+ /// \brief Return the number of specs fields.
+ ///
+ /// It specifies the range of parameter for getFieldSpec().
+ ///
+ /// This method never throws.
+ unsigned int getFieldCount() const { return (nfields_); }
+
+ /// \brief Return a pointer to a sequence of \c FieldSpec for the
+ /// \c RdataFields.
+ ///
+ /// This should be treated as an opaque internal representation you can
+ /// just store off somewhere and use it to construct a new RdataFields.
+ /// from it. If you are really interested, you can typecast it to
+ /// FieldSpec * (which is what it really is internally).
+ ///
+ /// The RdataFields holds ownership of the data.
+ ///
+ /// \note You should, however, be aware of alignment issues. The pointer
+ /// you pass to the constructor must be an address where the FieldSpec
+ /// can live. If you store it at a wrong address (eg. even one with
+ /// current implementation on most architectures), it might lead bad
+ /// things from slow access to SIGBUS. The easiest way is not to
+ /// interleave the fields with data from getData(). It is OK to place
+ /// all the fields first (even from multiple RdataFields) and then
+ /// place all the data after them.
+ ///
+ /// This method never throws an exception.
+ const void* getFieldSpecData() const {
+ return (fields_);
+ }
+
+ /// \brief Return the specification of the field identified by the given
+ /// index.
+ ///
+ /// \c field_id is the field index, which must be in the range of
+ /// <code>[0, getFieldCount())</code>. 0 means the first field, and
+ /// <code>getFieldCount()-1</code> means the last.
+ ///
+ /// If the given index is not in the valid range, an exception of class
+ /// \c OutOfRange will be thrown.
+ /// This method never throws an exception otherwise.
+ ///
+ /// \param field_id The index of an \c RdataFields field to be returned.
+ /// \return A \c FieldSpec structure that contains the information of
+ /// the \c field_id-th field.
+ FieldSpec getFieldSpec(const unsigned int field_id) const;
+ //@}
+
+ ///
+ /// \name Converter Methods
+ ///
+ //@{
+ /// \brief Render the RdataFields in the wire format with name compression.
+ ///
+ /// This method may require resource allocation in \c renderer.
+ /// If it fails, a corresponding standard exception will be thrown.
+ /// It should not throw any other exception as long as the \c RdataFields
+ /// object was constructed from valid parameters (see the description of
+ /// constructors). The result is not guaranteed if it's constructed in
+ /// any other ways.
+ ///
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer and name compression information.
+ void toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Render the RdataFields in the wire format without name
+ /// compression.
+ ///
+ /// This method may require resource allocation in \c buffer.
+ /// If it 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;
+ //@}
+
+private:
+ const FieldSpec* fields_;
+ unsigned int nfields_;
+ const uint8_t* data_;
+ size_t data_length_;
+
+ // hide further details within the implementation and don't create vectors
+ // every time we don't need them.
+ struct RdataFieldsDetail;
+ RdataFieldsDetail* detail_;
+};
+}
+}
+}
+#endif // RDATAFIELDS_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
new file mode 100644
index 0000000..9804c57
--- /dev/null
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -0,0 +1,312 @@
+// Copyright (C) 2010-2017 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 1
+
+#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 util {
+class InputBuffer;
+class OutputBuffer;
+}
+
+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 \c 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 \c 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_); }
+
+ // BEGIN_WELL_KNOWN_CLASS_DECLARATIONS
+ // END_WELL_KNOWN_CLASS_DECLARATIONS
+
+private:
+ uint16_t classcode_;
+};
+
+// BEGIN_WELL_KNOWN_CLASS_DEFINITIONS
+// END_WELL_KNOWN_CLASS_DEFINITIONS
+
+///
+/// \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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
new file mode 100644
index 0000000..4617a3e
--- /dev/null
+++ b/src/lib/dns/rrclass.cc
@@ -0,0 +1,74 @@
+// 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 <stdint.h>
+
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrclass.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+
+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 (NULL);
+}
+
+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..745efa6
--- /dev/null
+++ b/src/lib/dns/rrclass.h
@@ -0,0 +1,354 @@
+///////////////
+///////////////
+/////////////// THIS FILE IS AUTOMATICALLY GENERATED BY gen-rdatacode.py.
+/////////////// DO NOT EDIT!
+///////////////
+///////////////
+
+// Copyright (C) 2010-2021 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 1
+
+#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 util {
+class InputBuffer;
+class OutputBuffer;
+}
+
+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 \c 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 \c 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_); }
+
+ // BEGIN_WELL_KNOWN_CLASS_DECLARATIONS
+ static const RRClass& ANY();
+ static const RRClass& CH();
+ static const RRClass& HS();
+ static const RRClass& IN();
+ static const RRClass& NONE();
+ // END_WELL_KNOWN_CLASS_DECLARATIONS
+
+private:
+ uint16_t classcode_;
+};
+
+// BEGIN_WELL_KNOWN_CLASS_DEFINITIONS
+inline const RRClass&
+RRClass::ANY() {
+ static RRClass rrclass(255);
+ return (rrclass);
+}
+
+inline const RRClass&
+RRClass::CH() {
+ static RRClass rrclass(3);
+ return (rrclass);
+}
+
+inline const RRClass&
+RRClass::HS() {
+ static RRClass rrclass(4);
+ return (rrclass);
+}
+
+inline const RRClass&
+RRClass::IN() {
+ static RRClass rrclass(1);
+ return (rrclass);
+}
+
+inline const RRClass&
+RRClass::NONE() {
+ static RRClass rrclass(254);
+ return (rrclass);
+}
+
+// END_WELL_KNOWN_CLASS_DEFINITIONS
+
+///
+/// \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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrcollator.cc b/src/lib/dns/rrcollator.cc
new file mode 100644
index 0000000..8378021
--- /dev/null
+++ b/src/lib/dns/rrcollator.cc
@@ -0,0 +1,105 @@
+// Copyright (C) 2012-2020 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 this first to check the header is self-contained.
+#include <dns/rrcollator.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+
+#include <algorithm>
+#include <functional>
+
+using namespace isc::dns::rdata;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dns {
+
+class RRCollator::Impl {
+public:
+ Impl(const AddRRsetCallback& callback) : callback_(callback) {}
+
+ void addRR(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& rrttl,
+ const RdataPtr& rdata);
+
+ RRsetPtr current_rrset_;
+ const AddRRsetCallback callback_;
+};
+
+namespace {
+inline bool
+isSameType(RRType type1, const ConstRdataPtr& rdata1,
+ const ConstRRsetPtr& rrset)
+{
+ if (type1 != rrset->getType()) {
+ return (false);
+ }
+ if (type1 == RRType::RRSIG()) {
+ RdataIteratorPtr rit = rrset->getRdataIterator();
+ return (dynamic_cast<const generic::RRSIG&>(*rdata1).typeCovered()
+ == dynamic_cast<const generic::RRSIG&>(
+ rit->getCurrent()).typeCovered());
+ }
+ return (true);
+}
+}
+
+void
+RRCollator::Impl::addRR(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& rrttl,
+ const RdataPtr& rdata)
+{
+ if (current_rrset_ && (!isSameType(rrtype, rdata, current_rrset_) ||
+ current_rrset_->getClass() != rrclass ||
+ current_rrset_->getName() != name)) {
+ callback_(current_rrset_);
+ current_rrset_.reset();
+ }
+
+ if (!current_rrset_) {
+ current_rrset_ = RRsetPtr(new RRset(name, rrclass, rrtype, rrttl));
+ } else if (current_rrset_->getTTL() != rrttl) {
+ // RRs with different TTLs are given. Smaller TTL should win.
+ current_rrset_->setTTL(std::min(current_rrset_->getTTL(), rrttl));
+ }
+ current_rrset_->addRdata(rdata);
+}
+
+RRCollator::RRCollator(const AddRRsetCallback& callback) :
+ impl_(new Impl(callback))
+{}
+
+RRCollator::~RRCollator() {
+ delete impl_;
+}
+
+AddRRCallback
+RRCollator::getCallback() {
+ return (std::bind(&RRCollator::Impl::addRR, this->impl_,
+ ph::_1, ph::_2, ph::_3, ph::_4, ph::_5));
+}
+
+void
+RRCollator::flush() {
+ if (impl_->current_rrset_) {
+ impl_->callback_(impl_->current_rrset_);
+ impl_->current_rrset_.reset();
+ }
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/rrcollator.h b/src/lib/dns/rrcollator.h
new file mode 100644
index 0000000..9442fd3
--- /dev/null
+++ b/src/lib/dns/rrcollator.h
@@ -0,0 +1,125 @@
+// Copyright (C) 2012-2020 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 RRCOLLATOR_H
+#define RRCOLLATOR_H 1
+
+#include <dns/master_loader_callbacks.h>
+#include <dns/rrset.h>
+
+#include <boost/noncopyable.hpp>
+#include <functional>
+
+namespace isc {
+namespace dns {
+
+/// \brief A converter from a stream of RRs to a stream of collated RRsets
+///
+/// This class is mainly intended to be a helper used as an adapter for
+/// user applications of the \c MasterLoader class; it works as a callback
+/// for \c MasterLoader, buffers given RRs from the loader, collating
+/// consecutive RRs that belong to the same RRset (ones having the same
+/// owner name, RR type and class), and produces a stream of RRsets through
+/// its own callback. RRSIGs are also separated if their type covered fields
+/// have different values even if the owner name and RR class are the same.
+///
+/// It also "normalizes" TTLs of the RR; if collated RRs have different TTLs,
+/// this class guarantees that the TTL of the resulting RRsets has the
+/// smallest TTL among them.
+///
+/// The conversion will be useful for applications of \c MasterLoader because
+/// many of this library have interfaces that take an RRset object (or
+/// a pointer to it). Note, however, that this class doesn't guarantee that
+/// all RRs that would belong to the same RRset are collated into the same
+/// single RRset. In fact, it can only collate RRs that are consecutive
+/// in the original stream; once it encounters an RR of a different RRset,
+/// any subsequent RRs of the previous RRset will form a separate RRset object.
+///
+/// This class is non-copyable; it's partially for the convenience of internal
+/// implementation details, but it actually doesn't make sense to copy
+/// an object of this class, if not harmful, for the intended usage of
+/// the class.
+class RRCollator : boost::noncopyable {
+public:
+ /// \brief Callback functor type for \c RRCollator.
+ ///
+ /// This type of callback is given to an \c RRCollator object on its
+ /// construction, and will be called for each collated RRset built in
+ /// the \c RRCollator.
+ ///
+ /// \param rrset The collated RRset.
+ typedef std::function<void(const RRsetPtr& rrset)> AddRRsetCallback;
+
+ /// \brief Constructor.
+ ///
+ /// \throw std::bad_alloc Internal memory allocation fails. This should
+ /// be very rare.
+ ///
+ /// \param callback The callback functor to be called for each collated
+ /// RRset.
+ RRCollator(const AddRRsetCallback& callback);
+
+ /// \brief Destructor.
+ ///
+ /// It only performs trivial internal cleanup. In particular, even if
+ /// it still has a buffered RRset it will be simply discarded. This is
+ /// because the given callback could throw an exception, and it's
+ /// impossible to predict how this class is used (to see if it's a very
+ /// rare case where propagating an exception from a destructor is
+ /// justified). Instead, the application needs to make sure that
+ /// \c flush() is called before the object of this class is destroyed.
+ ///
+ /// \throw None
+ ~RRCollator();
+
+ /// \brief Call the callback on the remaining RRset, if any.
+ ///
+ /// This method is expected to be called that it's supposed all RRs have
+ /// been passed to this class object. Since there is no explicit
+ /// indicator of the end of the stream, the user of this class needs to
+ /// explicitly call this method to call the callback for the last buffered
+ /// RRset (see also the destructor's description).
+ ///
+ /// If there is no buffered RRset, this method does nothing. It can happen
+ /// if it's called without receiving any RRs, or called more than once.
+ ///
+ /// It propagates any exception thrown from the callback; otherwise it
+ /// doesn't throw anything.
+ void flush();
+
+ /// \brief Return \c MasterLoader compatible callback.
+ ///
+ /// This method returns a functor in the form of \c AddRRCallback
+ /// that works as an adapter between \c MasterLoader and an application
+ /// that needs to get a stream of RRsets. When the returned callback
+ /// is called, this \c RRCollator object accepts the corresponding RR,
+ /// and collates it with other RRs of the same RRset if necessary.
+ /// Every time the \c RRCollator object encounters an RR of a different
+ /// RRset, it calls the callback passed to the constructor with the RRset
+ /// built so far.
+ ///
+ /// Like \c flush(), this \c AddRRCallback functor propagates any exception
+ /// thrown from the callback.
+ ///
+ /// This method is expected to be called only once for a given
+ /// \c RRCollator object. It doesn't prohibit duplicate calls, but
+ /// returned functor objects internally refer to the same \c RRCollator
+ /// object, and calling the both callbacks randomly will just cause
+ /// confusion.
+ AddRRCallback getCallback();
+
+private:
+ class Impl;
+ Impl* impl_;
+};
+
+} // namespace dns
+} // namespace isc
+#endif // RRCOLLATOR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
new file mode 100644
index 0000000..4021460
--- /dev/null
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -0,0 +1,556 @@
+// Copyright (C) 2010-2022 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 <cassert>
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <map>
+#include <string>
+#include <sstream>
+#include <utility>
+
+#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrparamregistry.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+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 {
+ // BEGIN_WELL_KNOWN_PARAMS
+ // END_WELL_KNOWN_PARAMS
+ } catch (...) {
+ delete impl_;
+ throw;
+ }
+}
+
+RRParamRegistry::~RRParamRegistry() {
+ delete impl_;
+}
+
+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) {
+ 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
+ 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 (NULL);
+}
+}
+
+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_, rrtype, rrclass);
+ if (factory != NULL) {
+ 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_, rrtype, rrclass);
+ if (factory != NULL) {
+ 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_, rrtype, rrclass);
+ if (factory != NULL) {
+ 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_, rrtype, rrclass);
+ if (factory != NULL) {
+ return (factory->create(lexer, name, options, callbacks));
+ }
+
+ return (RdataPtr(new generic::Generic(lexer, name, options, callbacks)));
+}
+}
+}
diff --git a/src/lib/dns/rrparamregistry.cc b/src/lib/dns/rrparamregistry.cc
new file mode 100644
index 0000000..23c6382
--- /dev/null
+++ b/src/lib/dns/rrparamregistry.cc
@@ -0,0 +1,660 @@
+// Copyright (C) 2010-2022 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/.
+
+/// @file rrparamregistry.cc
+///
+/// THIS FILE USED TO BE AUTOMATICALLY GENERATED BY gen-rdatacode.py.
+/// Once this library was taken over by Kea project, we do not develop
+/// the DNS capabilities anymore, as Kea focuses on DHCP, not DNS.
+/// As such, we stopped adding new RR types as those we have are
+/// sufficient. Therefore it's unlikely this file will ever be
+/// regenerated.
+
+#include <config.h>
+
+#include <cassert>
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <map>
+#include <string>
+#include <sstream>
+#include <utility>
+
+#include <stdint.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/rrparamregistry.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+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 {
+ // BEGIN_WELL_KNOWN_PARAMS
+ add("A", 1, "IN", 1, RdataFactoryPtr(new RdataFactory<in::A>()));
+ add("NS", 2, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NS>()));
+ add("CNAME", 5, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::CNAME>()));
+ add("SOA", 6, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SOA>()));
+ add("PTR", 12, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::PTR>()));
+ add("HINFO", 13, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::HINFO>()));
+ add("MINFO", 14, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::MINFO>()));
+ add("MX", 15, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::MX>()));
+ add("TXT", 16, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TXT>()));
+ add("RP", 17, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::RP>()));
+ add("AFSDB", 18, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::AFSDB>()));
+ add("AAAA", 28, "IN", 1, RdataFactoryPtr(new RdataFactory<in::AAAA>()));
+ add("SRV", 33, "IN", 1, RdataFactoryPtr(new RdataFactory<in::SRV>()));
+ add("NAPTR", 35, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NAPTR>()));
+ add("DNAME", 39, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DNAME>()));
+ add("OPT", 41, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::OPT>()));
+ add("DS", 43, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DS>()));
+ add("SSHFP", 44, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SSHFP>()));
+ add("RRSIG", 46, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::RRSIG>()));
+ add("NSEC", 47, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NSEC>()));
+ add("DNSKEY", 48, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DNSKEY>()));
+ add("DHCID", 49, "IN", 1, RdataFactoryPtr(new RdataFactory<in::DHCID>()));
+ add("NSEC3", 50, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NSEC3>()));
+ add("NSEC3PARAM", 51, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NSEC3PARAM>()));
+ add("TLSA", 52, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TLSA>()));
+ add("SPF", 99, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SPF>()));
+ add("TKEY", 249, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TKEY>()));
+ add("CAA", 257, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::CAA>()));
+ add("DLV", 32769, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::DLV>()));
+ add("A", 1, "CH", 3, RdataFactoryPtr(new RdataFactory<ch::A>()));
+ add("A", 1, "HS", 4, RdataFactoryPtr(new RdataFactory<hs::A>()));
+ add("TSIG", 250, "ANY", 255, RdataFactoryPtr(new RdataFactory<any::TSIG>()));
+ add("NS", 2, RdataFactoryPtr(new RdataFactory<generic::NS>()));
+ add("CNAME", 5, RdataFactoryPtr(new RdataFactory<generic::CNAME>()));
+ add("SOA", 6, RdataFactoryPtr(new RdataFactory<generic::SOA>()));
+ add("PTR", 12, RdataFactoryPtr(new RdataFactory<generic::PTR>()));
+ add("HINFO", 13, RdataFactoryPtr(new RdataFactory<generic::HINFO>()));
+ add("MINFO", 14, RdataFactoryPtr(new RdataFactory<generic::MINFO>()));
+ add("MX", 15, RdataFactoryPtr(new RdataFactory<generic::MX>()));
+ add("TXT", 16, RdataFactoryPtr(new RdataFactory<generic::TXT>()));
+ add("RP", 17, RdataFactoryPtr(new RdataFactory<generic::RP>()));
+ add("AFSDB", 18, RdataFactoryPtr(new RdataFactory<generic::AFSDB>()));
+ add("NAPTR", 35, RdataFactoryPtr(new RdataFactory<generic::NAPTR>()));
+ add("DNAME", 39, RdataFactoryPtr(new RdataFactory<generic::DNAME>()));
+ add("OPT", 41, RdataFactoryPtr(new RdataFactory<generic::OPT>()));
+ add("DS", 43, RdataFactoryPtr(new RdataFactory<generic::DS>()));
+ add("SSHFP", 44, RdataFactoryPtr(new RdataFactory<generic::SSHFP>()));
+ add("RRSIG", 46, RdataFactoryPtr(new RdataFactory<generic::RRSIG>()));
+ add("NSEC", 47, RdataFactoryPtr(new RdataFactory<generic::NSEC>()));
+ add("DNSKEY", 48, RdataFactoryPtr(new RdataFactory<generic::DNSKEY>()));
+ add("NSEC3", 50, RdataFactoryPtr(new RdataFactory<generic::NSEC3>()));
+ add("NSEC3PARAM", 51, RdataFactoryPtr(new RdataFactory<generic::NSEC3PARAM>()));
+ add("TLSA", 52, RdataFactoryPtr(new RdataFactory<generic::TLSA>()));
+ add("SPF", 99, RdataFactoryPtr(new RdataFactory<generic::SPF>()));
+ add("TKEY", 249, RdataFactoryPtr(new RdataFactory<generic::TKEY>()));
+ add("CAA", 257, RdataFactoryPtr(new RdataFactory<generic::CAA>()));
+ add("DLV", 32769, RdataFactoryPtr(new RdataFactory<generic::DLV>()));
+ // Meta and non-implemented RR types
+ addType("IXFR", 251);
+ addType("AXFR", 252);
+ addType("ANY", 255);
+ addType("MD", 3);
+ addType("MF", 4);
+ addType("MB", 7);
+ addType("MG", 8);
+ addType("MR", 9);
+ addType("NXT", 30);
+ addType("A6", 38);
+ addType("MAILA", 254);
+ addType("NULL", 10);
+ addType("WKS", 11);
+ addType("X25", 19);
+ addType("RT", 21);
+ addType("NSAP", 22);
+ addType("NSAP-PTR", 23);
+ addType("SIG", 24);
+ addType("ISDN", 20);
+ addType("KEY", 25);
+ addType("PX", 26);
+ addType("GPOS", 27);
+ addType("LOC", 29);
+ addType("KX", 36);
+ addType("CERT", 37);
+ addType("APL", 42);
+ addType("IPSECKEY", 45);
+ addType("HIP", 55);
+ addType("UNSPEC", 103);
+ addType("NID", 104);
+ addType("L32", 105);
+ addType("L64", 106);
+ addType("LP", 107);
+ addType("MAILB", 253);
+ addType("URI", 256);
+ // Meta classes
+ addClass("NONE", 254);
+ // END_WELL_KNOWN_PARAMS
+ } catch (...) {
+ delete impl_;
+ throw;
+ }
+}
+
+RRParamRegistry::~RRParamRegistry() {
+ delete impl_;
+}
+
+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) {
+ 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
+ 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 (NULL);
+}
+}
+
+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_, rrtype, rrclass);
+ if (factory != NULL) {
+ 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_, rrtype, rrclass);
+ if (factory != NULL) {
+ 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_, rrtype, rrclass);
+ if (factory != NULL) {
+ 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_, rrtype, rrclass);
+ if (factory != NULL) {
+ 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..27e8a4f
--- /dev/null
+++ b/src/lib/dns/rrparamregistry.h
@@ -0,0 +1,545 @@
+// 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/.
+
+#ifndef RRPARAMREGISTRY_H
+#define RRPARAMREGISTRY_H 1
+
+#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:
+ RRParamRegistryImpl* impl_;
+};
+
+}
+}
+#endif // RRPARAMREGISTRY_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrset.cc b/src/lib/dns/rrset.cc
new file mode 100644
index 0000000..b217002
--- /dev/null
+++ b/src/lib/dns/rrset.cc
@@ -0,0 +1,466 @@
+// Copyright (C) 2010-2021 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 <algorithm>
+#include <string>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/foreach.hpp>
+
+#include <util/buffer.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>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+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 unsigned int
+rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
+ unsigned int 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();
+ 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
+
+unsigned int
+AbstractRRset::toWire(OutputBuffer& buffer) const {
+ return (rrsetToWire<OutputBuffer>(*this, buffer, 0));
+}
+
+unsigned int
+AbstractRRset::toWire(AbstractMessageRenderer& renderer) const {
+ const unsigned int 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) {}
+
+ unsigned int 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.
+unsigned int
+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);
+ }
+
+ unsigned int n = 0;
+
+ // sort the set of Rdata based on rrset-order and sortlist, and possible
+ // other options. Details to be considered.
+ BOOST_FOREACH(const ConstRdataPtr& rdata, rdatalist_) {
+ const size_t pos0 = renderer.getLength();
+ 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_ = new BasicRRsetImpl(name, rrclass, rrtype, ttl);
+}
+
+BasicRRset::~BasicRRset() {
+ delete impl_;
+}
+
+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));
+}
+
+unsigned int
+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();
+
+ assert(length + rrlen < 65536);
+ length += rrlen;
+
+ it->next();
+ } while (!it->isLast());
+
+ return (length);
+}
+
+unsigned int
+BasicRRset::toWire(OutputBuffer& buffer) const {
+ return (AbstractRRset::toWire(buffer));
+}
+
+unsigned int
+BasicRRset::toWire(AbstractMessageRenderer& renderer) const {
+ const unsigned int 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)
+{
+ rrsig_ = RRsetPtr();
+}
+
+RRset::~RRset() {}
+
+unsigned int
+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.
+ assert(length + rrsigs_length < 65536);
+ length += rrsigs_length;
+ }
+
+ return (length);
+}
+
+unsigned int
+RRset::toWire(OutputBuffer& buffer) const {
+ unsigned int rrs_written = BasicRRset::toWire(buffer);
+ if (getRdataCount() > rrs_written) {
+ return (rrs_written);
+ }
+
+ if (rrsig_) {
+ rrs_written += rrsig_->toWire(buffer);
+ }
+
+ return (rrs_written);
+}
+
+unsigned int
+RRset::toWire(AbstractMessageRenderer& renderer) const {
+ unsigned int 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..d17846a
--- /dev/null
+++ b/src/lib/dns/rrset.h
@@ -0,0 +1,958 @@
+// Copyright (C) 2010-2021 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 1
+
+#include <iostream>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/exceptions.h>
+
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace util {
+class OututBuffer;
+}
+
+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 unsigned int 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 unsigned int 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 unsigned int 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 unsigned int 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 unsigned int 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 unsigned int 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 unsigned int 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 unsigned int 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:
+ 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 unsigned int toWire(AbstractMessageRenderer& renderer) const;
+
+ /// \brief Render the RRset in the wire format without any compression.
+ ///
+ /// See \c AbstractRRset::toWire(OutputBuffer&)const.
+ virtual unsigned int 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 unsigned int 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrset_collection.cc b/src/lib/dns/rrset_collection.cc
new file mode 100644
index 0000000..a98ed16
--- /dev/null
+++ b/src/lib/dns/rrset_collection.cc
@@ -0,0 +1,123 @@
+// Copyright (C) 2012-2020 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/rrset_collection.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/master_loader.h>
+#include <dns/rrcollator.h>
+
+#include <exceptions/exceptions.h>
+
+#include <functional>
+
+using namespace isc;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dns {
+
+void
+RRsetCollection::loaderCallback(const std::string&, size_t, const std::string&)
+{
+ // We just ignore callbacks for errors and warnings.
+}
+
+void
+RRsetCollection::addRRset(RRsetPtr rrset) {
+ const CollectionKey key(rrset->getClass(), rrset->getType(),
+ rrset->getName());
+ CollectionMap::const_iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ isc_throw(InvalidParameter,
+ "RRset for " << rrset->getName() << "/" << rrset->getClass()
+ << " with type " << rrset->getType() << " already exists");
+ }
+
+ rrsets_.insert(std::pair<CollectionKey, RRsetPtr>(key, rrset));
+}
+
+template<typename T>
+void
+RRsetCollection::constructHelper(T source, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass)
+{
+ RRCollator collator(std::bind(&RRsetCollection::addRRset, this, ph::_1));
+ MasterLoaderCallbacks callbacks
+ (std::bind(&RRsetCollection::loaderCallback, this, ph::_1, ph::_2, ph::_3),
+ std::bind(&RRsetCollection::loaderCallback, this, ph::_1, ph::_2, ph::_3));
+ MasterLoader loader(source, origin, rrclass, callbacks,
+ collator.getCallback(),
+ MasterLoader::DEFAULT);
+ loader.load();
+ collator.flush();
+}
+
+RRsetCollection::RRsetCollection(const char* filename, const Name& origin,
+ const RRClass& rrclass)
+{
+ constructHelper(filename, origin, rrclass);
+}
+
+RRsetCollection::RRsetCollection(std::istream& input_stream, const Name& origin,
+ const RRClass& rrclass)
+{
+ constructHelper<std::istream&>(input_stream, origin, rrclass);
+}
+
+RRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) {
+ const CollectionKey key(rrclass, rrtype, name);
+ CollectionMap::iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ return (it->second);
+ }
+ return (RRsetPtr());
+}
+
+ConstRRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) const
+{
+ const CollectionKey key(rrclass, rrtype, name);
+ CollectionMap::const_iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ return (it->second);
+ }
+ return (ConstRRsetPtr());
+}
+
+bool
+RRsetCollection::removeRRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype)
+{
+ const CollectionKey key(rrclass, rrtype, name);
+
+ CollectionMap::iterator it = rrsets_.find(key);
+ if (it == rrsets_.end()) {
+ return (false);
+ }
+
+ rrsets_.erase(it);
+ return (true);
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getBeginning() {
+ CollectionMap::iterator it = rrsets_.begin();
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getEnd() {
+ CollectionMap::iterator it = rrsets_.end();
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/rrset_collection.h b/src/lib/dns/rrset_collection.h
new file mode 100644
index 0000000..7fb1e9c
--- /dev/null
+++ b/src/lib/dns/rrset_collection.h
@@ -0,0 +1,164 @@
+// Copyright (C) 2012-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/.
+
+#ifndef RRSET_COLLECTION_H
+#define RRSET_COLLECTION_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+
+#include <boost/tuple/tuple.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <map>
+
+namespace isc {
+namespace dns {
+
+/// \brief libdns++ implementation of RRsetCollectionBase using an STL
+/// container.
+class RRsetCollection : public RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ ///
+ /// This constructor creates an empty collection without any data in
+ /// it. RRsets can be added to the collection with the \c addRRset()
+ /// method.
+ RRsetCollection() {}
+
+ /// \brief Constructor.
+ ///
+ /// The \c origin and \c rrclass arguments are required for the zone
+ /// loading, but \c RRsetCollection itself does not do any
+ /// validation, and the collection of RRsets does not have to form a
+ /// valid zone.
+ ///
+ /// \throws MasterLoaderError if there is an error during loading.
+ /// \param filename Name of a file containing a collection of RRs in
+ /// the master file format (which may or may not form a valid zone).
+ /// \param origin The zone origin.
+ /// \param rrclass The zone class.
+ RRsetCollection(const char* filename, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+
+ /// \brief Constructor.
+ ///
+ /// This constructor is similar to the previous one, but instead of
+ /// taking a filename to load a zone from, it takes an input
+ /// stream.
+ ///
+ /// \throws MasterLoaderError if there is an error during loading.
+ /// \param input_stream The input stream to load from.
+ /// \param origin The zone origin.
+ /// \param rrclass The zone class.
+ RRsetCollection(std::istream& input_stream, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+
+ /// \brief Destructor
+ virtual ~RRsetCollection() {}
+
+ /// \brief Add an RRset to the collection.
+ ///
+ /// Does not do any validation whether \c rrset belongs to a
+ /// particular zone or not. A reference to \c rrset is taken in an
+ /// internally managed \c shared_ptr, so even if the caller's
+ /// \c RRsetPtr is destroyed, the RRset it wrapped is still alive
+ /// and managed by the \c RRsetCollection. It throws an
+ /// \c isc::InvalidParameter exception if an rrset with the same
+ /// class, type and name already exists.
+ ///
+ /// Callers must not modify the RRset after adding it to the
+ /// collection, as the rrset is indexed internally by the
+ /// collection.
+ void addRRset(isc::dns::RRsetPtr rrset);
+
+ /// \brief Remove an RRset from the collection.
+ ///
+ /// RRset(s) matching the \c name, \c rrclass and \c rrtype are
+ /// removed from the collection.
+ ///
+ /// \return \c true if a matching RRset was deleted, \c false if no
+ /// such RRset exists.
+ bool removeRRset(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// \param name The name of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \return The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const;
+
+ /// \brief Find a matching RRset in the collection (non-const
+ /// variant).
+ ///
+ /// See above for a description of the method and arguments.
+ isc::dns::RRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+private:
+ template<typename T>
+ void constructHelper(T source, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+ void loaderCallback(const std::string&, size_t, const std::string&);
+
+ typedef boost::tuple<isc::dns::RRClass, isc::dns::RRType, isc::dns::Name>
+ CollectionKey;
+ typedef std::map<CollectionKey, isc::dns::RRsetPtr> CollectionMap;
+
+ CollectionMap rrsets_;
+
+protected:
+ class DnsIter : public RRsetCollectionBase::Iter {
+ public:
+ DnsIter(CollectionMap::iterator& iter) :
+ iter_(iter)
+ {}
+
+ virtual const isc::dns::AbstractRRset& getValue() {
+ isc::dns::RRsetPtr& rrset = iter_->second;
+ return (*rrset);
+ }
+
+ virtual IterPtr getNext() {
+ CollectionMap::iterator it = iter_;
+ ++it;
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+ }
+
+ virtual bool equals(Iter& other) {
+ const DnsIter* other_real = dynamic_cast<DnsIter*>(&other);
+ if (other_real == NULL) {
+ return (false);
+ }
+ return (iter_ == other_real->iter_);
+ }
+
+ private:
+ CollectionMap::iterator iter_;
+ };
+
+ virtual RRsetCollectionBase::IterPtr getBeginning();
+ virtual RRsetCollectionBase::IterPtr getEnd();
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
new file mode 100644
index 0000000..a8ca255
--- /dev/null
+++ b/src/lib/dns/rrset_collection_base.h
@@ -0,0 +1,214 @@
+// Copyright (C) 2012-2022 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_COLLECTION_BASE_H
+#define RRSET_COLLECTION_BASE_H 1
+
+#include <dns/rrset.h>
+#include <dns/name.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <iterator>
+
+namespace isc {
+namespace dns {
+
+/// \brief Error during RRsetCollectionBase find() operation
+///
+/// This exception is thrown when calling an implementation of
+/// \c RRsetCollectionBase::find() results in an error which is not due
+/// to unmatched data, but because of some other underlying error
+/// condition.
+class RRsetCollectionError : public Exception {
+public:
+ RRsetCollectionError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Generic class to represent a set of RRsets.
+///
+/// This is a generic container and the stored set of RRsets does not
+/// necessarily form a valid zone (e.g. there doesn't necessarily have
+/// to be an SOA at the "origin"). Instead, it will be used to represent
+/// a single zone for the purpose of zone loading/checking. It provides
+/// a simple find() method to find an RRset for the given name and type
+/// (and maybe class) and a way to iterate over all RRsets.
+///
+/// See \c RRsetCollection for a simple libdns++ implementation using an
+/// STL container. libdatasrc will have another implementation.
+class RRsetCollectionBase {
+public:
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// This method's implementations currently are not specified to
+ /// handle \c RRTypes such as RRSIG and NSEC3. This interface may be
+ /// refined to clarify this point in the future, and perhaps, provide
+ /// additional API for these RRType.
+ ///
+ /// As for RRSIG, there are some fundamental open questions. For
+ /// example, it's not clear whether we want to return all RRSIGs of
+ /// the given name covering any RR types (in which case, we need to
+ /// figure out how), or we need to extend the interface so we can
+ /// specify the covered type. A specific derived implementation may
+ /// return something if type RRSIG is specified, but this is not
+ /// specified here at the base class level. So, for RRSIGs the
+ /// behavior should be assumed as undefined.
+ ///
+ /// As for NSEC3, it's not clear whether owner names (which included
+ /// hashed labels) are the best choice of search key, because in many
+ /// cases, what the application wants to find is an NSEC3 that has the
+ /// hash of some particular "normal" domain names. Also, if the underlying
+ /// implementation encapsulates a single zone, NSEC3 records conceptually
+ /// belong to a separate name space, which may cause implementation
+ /// difficulty.
+ ///
+ /// Behavior with meta types such as ANY and AXFR are also
+ /// undefined. A specific implementation may return something for
+ /// these. But, unlike the case of RRSIGs, these types of RRsets
+ /// are not expected to be added to any implementation of
+ /// collection in the first place (by the definition of "meta
+ /// types"), so querying for such types is basically an invalid
+ /// operation. The API doesn't require implementations to check
+ /// this condition and reject it, so the behavior is
+ /// undefined. This interface will not be refined in future
+ /// versions for these meta types.
+ ///
+ /// \throw RRsetCollectionError if find() results in some
+ /// implementation-specific error.
+ /// \param name The name of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \return The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find
+ (const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype)
+ const = 0;
+
+ /// \brief Destructor
+ virtual ~RRsetCollectionBase() {}
+
+protected:
+ class Iter; // forward declaration
+
+ /// \brief Wraps Iter with a reference count.
+ typedef boost::shared_ptr<Iter> IterPtr;
+
+ /// \brief A helper iterator interface for \c RRsetCollectionBase.
+ ///
+ /// This is a protected iterator class that is a helper interface
+ /// used by the public iterator. Derived classes of
+ /// \c RRsetCollectionBase are supposed to implement this class and
+ /// the \c getBeginning() and \c getEnd() methods, so that the
+ /// public iterator interface can be provided. This is a forward
+ /// iterator only.
+ class Iter {
+ public:
+ virtual ~Iter() {};
+
+ /// \brief Returns the \c AbstractRRset currently pointed to by
+ /// the iterator.
+ virtual const isc::dns::AbstractRRset& getValue() = 0;
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing to
+ /// the next \c AbstractRRset in sequence in the collection.
+ virtual IterPtr getNext() = 0;
+
+ /// \brief Check if another iterator is equal to this one.
+ ///
+ /// Returns \c true if this iterator is equal to \c other,
+ /// \c false otherwise. Note that if \c other is not the same
+ /// type as \c this, or cannot be compared meaningfully, the
+ /// method must return \c false.
+ ///
+ /// \param other The other iterator to compare against.
+ /// \return \c true if equal, \c false otherwise.
+ virtual bool equals(Iter& other) = 0;
+ };
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing to the
+ /// beginning of the collection.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error.
+ virtual IterPtr getBeginning() = 0;
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing past the
+ /// end of the collection.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error.
+ virtual IterPtr getEnd() = 0;
+
+public:
+ /// \brief A forward \c std::iterator for \c RRsetCollectionBase.
+ ///
+ /// It behaves like a \c std::iterator forward iterator, so please
+ /// see its documentation for usage.
+ class Iterator {
+ public:
+ // Aliases used to enable iterator behavior on this class
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = isc::dns::AbstractRRset const;
+ using difference_type = std::ptrdiff_t;
+ using pointer = isc::dns::AbstractRRset const*;
+ using reference = isc::dns::AbstractRRset const&;
+
+ explicit Iterator(IterPtr iter) :
+ iter_(iter)
+ {}
+
+ reference operator*() {
+ return (iter_->getValue());
+ }
+
+ Iterator& operator++() {
+ iter_ = iter_->getNext();
+ return (*this);
+ }
+
+ Iterator operator++(int) {
+ Iterator tmp(iter_);
+ ++*this;
+ return (tmp);
+ }
+
+ bool operator==(const Iterator& other) const {
+ return (iter_->equals(*other.iter_));
+ }
+
+ bool operator!=(const Iterator& other) const {
+ return (!iter_->equals(*other.iter_));
+ }
+
+ private:
+ IterPtr iter_;
+ };
+
+ /// \brief Returns an iterator pointing to the beginning of the
+ /// collection.
+ Iterator begin() {
+ return Iterator(getBeginning());
+ }
+
+ /// \brief Returns an iterator pointing past the end of the
+ /// collection.
+ Iterator end() {
+ return Iterator(getEnd());
+ }
+};
+
+typedef boost::shared_ptr<RRsetCollectionBase> RRsetCollectionPtr;
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_BASE_H
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
new file mode 100644
index 0000000..83877c7
--- /dev/null
+++ b/src/lib/dns/rrttl.cc
@@ -0,0 +1,214 @@
+// 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 <stdint.h>
+
+#include <sstream>
+#include <ostream>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrttl.h>
+
+#include <boost/lexical_cast.hpp>
+#include <algorithm>
+#include <cctype>
+
+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 != NULL) {
+ *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 != NULL) {
+ *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 != NULL) {
+ *error_txt = "Unknown unit used: " +
+ boost::lexical_cast<string>(*unit) + " in: " + ttlstr;
+ }
+ return (false);
+ }
+ // Now extract the number.
+ if (unit == pos) {
+ if (error_txt != NULL) {
+ *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 != NULL) {
+ *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;
+ 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 != NULL) {
+ *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 != NULL) {
+ *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 != NULL) {
+ *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, NULL)) {
+ return (new RRTTL(ttlval));
+ }
+ return (NULL);
+}
+
+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..abda3e5
--- /dev/null
+++ b/src/lib/dns/rrttl.h
@@ -0,0 +1,306 @@
+// 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/.
+
+#ifndef RRTTL_H
+#define RRTTL_H 1
+
+#include <dns/exceptions.h>
+
+#include <boost/optional.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+class InputBuffer;
+class OutputBuffer;
+}
+
+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 \c 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 \c 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrtype-placeholder.h b/src/lib/dns/rrtype-placeholder.h
new file mode 100644
index 0000000..e86e2f6
--- /dev/null
+++ b/src/lib/dns/rrtype-placeholder.h
@@ -0,0 +1,286 @@
+// 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/.
+
+#ifndef RRTYPE_H
+#define RRTYPE_H 1
+
+#include <stdint.h>
+
+#include <string>
+#include <ostream>
+
+#include <dns/exceptions.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 util {
+class InputBuffer;
+class OutputBuffer;
+}
+
+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_); }
+ //@}
+
+ // BEGIN_WELL_KNOWN_TYPE_DECLARATIONS
+ // END_WELL_KNOWN_TYPE_DECLARATIONS
+
+private:
+ uint16_t typecode_;
+};
+
+// BEGIN_WELL_KNOWN_TYPE_DEFINITIONS
+// END_WELL_KNOWN_TYPE_DEFINITIONS
+
+///
+/// \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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
new file mode 100644
index 0000000..242af51
--- /dev/null
+++ b/src/lib/dns/rrtype.cc
@@ -0,0 +1,65 @@
+// 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 <stdint.h>
+
+#include <string>
+#include <ostream>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrtype.h>
+
+using namespace std;
+using namespace isc::util;
+using isc::dns::RRType;
+
+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..e752253
--- /dev/null
+++ b/src/lib/dns/rrtype.h
@@ -0,0 +1,748 @@
+///////////////
+///////////////
+/////////////// THIS FILE IS AUTOMATICALLY GENERATED BY gen-rdatacode.py.
+/////////////// DO NOT EDIT!
+///////////////
+///////////////
+
+// Copyright (C) 2010-2021 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 1
+
+#include <stdint.h>
+
+#include <string>
+#include <ostream>
+
+#include <dns/exceptions.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 util {
+class InputBuffer;
+class OutputBuffer;
+}
+
+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_); }
+ //@}
+
+ // BEGIN_WELL_KNOWN_TYPE_DECLARATIONS
+ static const RRType& TSIG();
+ static const RRType& A();
+ static const RRType& AFSDB();
+ static const RRType& CAA();
+ static const RRType& CNAME();
+ static const RRType& DLV();
+ static const RRType& DNAME();
+ static const RRType& DNSKEY();
+ static const RRType& DS();
+ static const RRType& HINFO();
+ static const RRType& MINFO();
+ static const RRType& MX();
+ static const RRType& NAPTR();
+ static const RRType& NS();
+ static const RRType& NSEC3();
+ static const RRType& NSEC3PARAM();
+ static const RRType& NSEC();
+ static const RRType& OPT();
+ static const RRType& PTR();
+ static const RRType& RP();
+ static const RRType& RRSIG();
+ static const RRType& SOA();
+ static const RRType& SPF();
+ static const RRType& SSHFP();
+ static const RRType& TKEY();
+ static const RRType& TLSA();
+ static const RRType& TXT();
+ static const RRType& AAAA();
+ static const RRType& DHCID();
+ static const RRType& SRV();
+ static const RRType& IXFR();
+ static const RRType& AXFR();
+ static const RRType& ANY();
+ static const RRType& MD();
+ static const RRType& MF();
+ static const RRType& MB();
+ static const RRType& MG();
+ static const RRType& MR();
+ static const RRType& NXT();
+ static const RRType& A6();
+ static const RRType& MAILA();
+ static const RRType& Null();
+ static const RRType& WKS();
+ static const RRType& X25();
+ static const RRType& RT();
+ static const RRType& NSAP();
+ static const RRType& NSAP_PTR();
+ static const RRType& SIG();
+ static const RRType& ISDN();
+ static const RRType& KEY();
+ static const RRType& PX();
+ static const RRType& GPOS();
+ static const RRType& LOC();
+ static const RRType& KX();
+ static const RRType& CERT();
+ static const RRType& APL();
+ static const RRType& IPSECKEY();
+ static const RRType& HIP();
+ static const RRType& UNSPEC();
+ static const RRType& NID();
+ static const RRType& L32();
+ static const RRType& L64();
+ static const RRType& LP();
+ static const RRType& MAILB();
+ static const RRType& URI();
+ // END_WELL_KNOWN_TYPE_DECLARATIONS
+
+private:
+ uint16_t typecode_;
+};
+
+// BEGIN_WELL_KNOWN_TYPE_DEFINITIONS
+inline const RRType&
+RRType::TSIG() {
+ static RRType rrtype(250);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::A() {
+ static RRType rrtype(1);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::AFSDB() {
+ static RRType rrtype(18);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::CAA() {
+ static RRType rrtype(257);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::CNAME() {
+ static RRType rrtype(5);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::DLV() {
+ static RRType rrtype(32769);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::DNAME() {
+ static RRType rrtype(39);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::DNSKEY() {
+ static RRType rrtype(48);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::DS() {
+ static RRType rrtype(43);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::HINFO() {
+ static RRType rrtype(13);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MINFO() {
+ static RRType rrtype(14);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MX() {
+ static RRType rrtype(15);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NAPTR() {
+ static RRType rrtype(35);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NS() {
+ static RRType rrtype(2);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NSEC3() {
+ static RRType rrtype(50);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NSEC3PARAM() {
+ static RRType rrtype(51);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NSEC() {
+ static RRType rrtype(47);
+ 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::RP() {
+ static RRType rrtype(17);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::RRSIG() {
+ static RRType rrtype(46);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::SOA() {
+ static RRType rrtype(6);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::SPF() {
+ static RRType rrtype(99);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::SSHFP() {
+ static RRType rrtype(44);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::TKEY() {
+ static RRType rrtype(249);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::TLSA() {
+ static RRType rrtype(52);
+ 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::DHCID() {
+ static RRType rrtype(49);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::SRV() {
+ static RRType rrtype(33);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::IXFR() {
+ static RRType rrtype(251);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::AXFR() {
+ static RRType rrtype(252);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::ANY() {
+ static RRType rrtype(255);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MD() {
+ static RRType rrtype(3);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MF() {
+ static RRType rrtype(4);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MB() {
+ static RRType rrtype(7);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MG() {
+ static RRType rrtype(8);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MR() {
+ static RRType rrtype(9);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NXT() {
+ static RRType rrtype(30);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::A6() {
+ static RRType rrtype(38);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MAILA() {
+ static RRType rrtype(254);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::Null() {
+ static RRType rrtype(10);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::WKS() {
+ static RRType rrtype(11);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::X25() {
+ static RRType rrtype(19);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::RT() {
+ static RRType rrtype(21);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NSAP() {
+ static RRType rrtype(22);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NSAP_PTR() {
+ static RRType rrtype(23);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::SIG() {
+ static RRType rrtype(24);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::ISDN() {
+ static RRType rrtype(20);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::KEY() {
+ static RRType rrtype(25);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::PX() {
+ static RRType rrtype(26);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::GPOS() {
+ static RRType rrtype(27);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::LOC() {
+ static RRType rrtype(29);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::KX() {
+ static RRType rrtype(36);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::CERT() {
+ static RRType rrtype(37);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::APL() {
+ static RRType rrtype(42);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::IPSECKEY() {
+ static RRType rrtype(45);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::HIP() {
+ static RRType rrtype(55);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::UNSPEC() {
+ static RRType rrtype(103);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::NID() {
+ static RRType rrtype(104);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::L32() {
+ static RRType rrtype(105);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::L64() {
+ static RRType rrtype(106);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::LP() {
+ static RRType rrtype(107);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::MAILB() {
+ static RRType rrtype(253);
+ return (rrtype);
+}
+
+inline const RRType&
+RRType::URI() {
+ static RRType rrtype(256);
+ return (rrtype);
+}
+
+// END_WELL_KNOWN_TYPE_DEFINITIONS
+
+///
+/// \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
+
+// Local Variables:
+// mode: c++
+// End:
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..7c7e338
--- /dev/null
+++ b/src/lib/dns/serial.h
@@ -0,0 +1,150 @@
+// Copyright (C) 2011-2015,2017 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 1
+
+#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..c89c3db
--- /dev/null
+++ b/src/lib/dns/tests/Makefile.am
@@ -0,0 +1,94 @@
+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 += nsec3hash_unittest.cc
+run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc
+run_unittests_SOURCES += rrttl_unittest.cc
+run_unittests_SOURCES += rrcollator_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 += rdatafields_unittest.cc
+run_unittests_SOURCES += rdata_pimpl_holder_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 rdata_in_aaaa_unittest.cc
+run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
+run_unittests_SOURCES += rdata_txt_like_unittest.cc
+run_unittests_SOURCES += rdata_mx_unittest.cc
+run_unittests_SOURCES += rdata_sshfp_unittest.cc
+run_unittests_SOURCES += rdata_ptr_unittest.cc rdata_cname_unittest.cc
+run_unittests_SOURCES += rdata_dname_unittest.cc
+run_unittests_SOURCES += rdata_afsdb_unittest.cc
+run_unittests_SOURCES += rdata_opt_unittest.cc
+run_unittests_SOURCES += rdata_dhcid_unittest.cc
+run_unittests_SOURCES += rdata_dnskey_unittest.cc
+run_unittests_SOURCES += rdata_ds_like_unittest.cc
+run_unittests_SOURCES += rdata_nsec_unittest.cc
+run_unittests_SOURCES += rdata_nsec3_unittest.cc
+run_unittests_SOURCES += rdata_nsecbitmap_unittest.cc
+run_unittests_SOURCES += rdata_nsec3param_unittest.cc
+run_unittests_SOURCES += rdata_nsec3param_like_unittest.cc
+run_unittests_SOURCES += rdata_rrsig_unittest.cc
+run_unittests_SOURCES += rdata_rp_unittest.cc
+run_unittests_SOURCES += rdata_srv_unittest.cc
+run_unittests_SOURCES += rdata_tlsa_unittest.cc
+run_unittests_SOURCES += rdata_minfo_unittest.cc
+run_unittests_SOURCES += rdata_tsig_unittest.cc
+run_unittests_SOURCES += rdata_naptr_unittest.cc
+run_unittests_SOURCES += rdata_hinfo_unittest.cc
+run_unittests_SOURCES += rdata_caa_unittest.cc
+run_unittests_SOURCES += rdata_tkey_unittest.cc
+run_unittests_SOURCES += rrset_unittest.cc
+run_unittests_SOURCES += qid_gen_unittest.cc
+run_unittests_SOURCES += question_unittest.cc
+run_unittests_SOURCES += rrparamregistry_unittest.cc
+run_unittests_SOURCES += masterload_unittest.cc
+run_unittests_SOURCES += message_unittest.cc
+run_unittests_SOURCES += serial_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 += rrset_collection_unittest.cc
+run_unittests_SOURCES += zone_checker_unittest.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..59bb00f
--- /dev/null
+++ b/src/lib/dns/tests/Makefile.in
@@ -0,0 +1,2357 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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_cpp11.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 \
+ nsec3hash_unittest.cc rrclass_unittest.cc rrtype_unittest.cc \
+ rrttl_unittest.cc rrcollator_unittest.cc opcode_unittest.cc \
+ rcode_unittest.cc rdata_unittest.h rdata_unittest.cc \
+ rdatafields_unittest.cc rdata_pimpl_holder_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_mx_unittest.cc rdata_sshfp_unittest.cc \
+ rdata_ptr_unittest.cc rdata_cname_unittest.cc \
+ rdata_dname_unittest.cc rdata_afsdb_unittest.cc \
+ rdata_opt_unittest.cc rdata_dhcid_unittest.cc \
+ rdata_dnskey_unittest.cc rdata_ds_like_unittest.cc \
+ rdata_nsec_unittest.cc rdata_nsec3_unittest.cc \
+ rdata_nsecbitmap_unittest.cc rdata_nsec3param_unittest.cc \
+ rdata_nsec3param_like_unittest.cc rdata_rrsig_unittest.cc \
+ rdata_rp_unittest.cc rdata_srv_unittest.cc \
+ rdata_tlsa_unittest.cc rdata_minfo_unittest.cc \
+ rdata_tsig_unittest.cc rdata_naptr_unittest.cc \
+ rdata_hinfo_unittest.cc rdata_caa_unittest.cc \
+ rdata_tkey_unittest.cc rrset_unittest.cc qid_gen_unittest.cc \
+ question_unittest.cc rrparamregistry_unittest.cc \
+ masterload_unittest.cc message_unittest.cc serial_unittest.cc \
+ tsig_unittest.cc tsigerror_unittest.cc tsigkey_unittest.cc \
+ tsigrecord_unittest.cc master_loader_callbacks_test.cc \
+ rrset_collection_unittest.cc zone_checker_unittest.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-nsec3hash_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrclass_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrtype_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrttl_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrcollator_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-rdatafields_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_pimpl_holder_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_mx_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_sshfp_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_ptr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_cname_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_dname_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_afsdb_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_dnskey_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_ds_like_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_nsec_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_nsec3_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_nsecbitmap_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_nsec3param_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_nsec3param_like_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_rrsig_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_rp_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_srv_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_tlsa_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_minfo_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_tsig_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_naptr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_hinfo_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_caa_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rdata_tkey_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrset_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-qid_gen_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-question_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-rrparamregistry_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-masterload_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-message_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-serial_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-rrset_collection_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-zone_checker_unittest.$(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-masterload_unittest.Po \
+ ./$(DEPDIR)/run_unittests-message_unittest.Po \
+ ./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po \
+ ./$(DEPDIR)/run_unittests-name_unittest.Po \
+ ./$(DEPDIR)/run_unittests-nsec3hash_unittest.Po \
+ ./$(DEPDIR)/run_unittests-opcode_unittest.Po \
+ ./$(DEPDIR)/run_unittests-qid_gen_unittest.Po \
+ ./$(DEPDIR)/run_unittests-question_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rcode_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_afsdb_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_caa_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_cname_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_dname_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_dnskey_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_ds_like_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_hinfo_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_minfo_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_mx_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_naptr_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_nsec3_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_nsec_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_rp_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_srv_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_sshfp_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rdata_tlsa_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-rdatafields_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrclass_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrcollator_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po \
+ ./$(DEPDIR)/run_unittests-rrset_collection_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-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 \
+ ./$(DEPDIR)/run_unittests-zone_checker_unittest.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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 nsec3hash_unittest.cc \
+@HAVE_GTEST_TRUE@ rrclass_unittest.cc rrtype_unittest.cc \
+@HAVE_GTEST_TRUE@ rrttl_unittest.cc rrcollator_unittest.cc \
+@HAVE_GTEST_TRUE@ opcode_unittest.cc rcode_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_unittest.h rdata_unittest.cc \
+@HAVE_GTEST_TRUE@ rdatafields_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_pimpl_holder_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_mx_unittest.cc rdata_sshfp_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_ptr_unittest.cc rdata_cname_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_dname_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_afsdb_unittest.cc rdata_opt_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_dhcid_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_dnskey_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_ds_like_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_nsec_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_nsec3_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_nsecbitmap_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_nsec3param_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_nsec3param_like_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_rrsig_unittest.cc rdata_rp_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_srv_unittest.cc rdata_tlsa_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_minfo_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_tsig_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_naptr_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_hinfo_unittest.cc rdata_caa_unittest.cc \
+@HAVE_GTEST_TRUE@ rdata_tkey_unittest.cc rrset_unittest.cc \
+@HAVE_GTEST_TRUE@ qid_gen_unittest.cc question_unittest.cc \
+@HAVE_GTEST_TRUE@ rrparamregistry_unittest.cc \
+@HAVE_GTEST_TRUE@ masterload_unittest.cc message_unittest.cc \
+@HAVE_GTEST_TRUE@ serial_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@ rrset_collection_unittest.cc \
+@HAVE_GTEST_TRUE@ zone_checker_unittest.cc 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-masterload_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-nsec3hash_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-qid_gen_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_afsdb_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_caa_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_cname_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_dname_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_dnskey_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_ds_like_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_hinfo_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_minfo_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_mx_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_naptr_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_nsec3_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_nsec_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_nsecbitmap_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_pimpl_holder_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_rp_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_srv_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_sshfp_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_tlsa_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-rdatafields_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-rrcollator_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_collection_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-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
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-zone_checker_unittest.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-nsec3hash_unittest.o: nsec3hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-nsec3hash_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-nsec3hash_unittest.Tpo -c -o run_unittests-nsec3hash_unittest.o `test -f 'nsec3hash_unittest.cc' || echo '$(srcdir)/'`nsec3hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-nsec3hash_unittest.Tpo $(DEPDIR)/run_unittests-nsec3hash_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nsec3hash_unittest.cc' object='run_unittests-nsec3hash_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-nsec3hash_unittest.o `test -f 'nsec3hash_unittest.cc' || echo '$(srcdir)/'`nsec3hash_unittest.cc
+
+run_unittests-nsec3hash_unittest.obj: nsec3hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-nsec3hash_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-nsec3hash_unittest.Tpo -c -o run_unittests-nsec3hash_unittest.obj `if test -f 'nsec3hash_unittest.cc'; then $(CYGPATH_W) 'nsec3hash_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/nsec3hash_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-nsec3hash_unittest.Tpo $(DEPDIR)/run_unittests-nsec3hash_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nsec3hash_unittest.cc' object='run_unittests-nsec3hash_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-nsec3hash_unittest.obj `if test -f 'nsec3hash_unittest.cc'; then $(CYGPATH_W) 'nsec3hash_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/nsec3hash_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-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-rrcollator_unittest.o: rrcollator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrcollator_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrcollator_unittest.Tpo -c -o run_unittests-rrcollator_unittest.o `test -f 'rrcollator_unittest.cc' || echo '$(srcdir)/'`rrcollator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrcollator_unittest.Tpo $(DEPDIR)/run_unittests-rrcollator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrcollator_unittest.cc' object='run_unittests-rrcollator_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-rrcollator_unittest.o `test -f 'rrcollator_unittest.cc' || echo '$(srcdir)/'`rrcollator_unittest.cc
+
+run_unittests-rrcollator_unittest.obj: rrcollator_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrcollator_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrcollator_unittest.Tpo -c -o run_unittests-rrcollator_unittest.obj `if test -f 'rrcollator_unittest.cc'; then $(CYGPATH_W) 'rrcollator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrcollator_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrcollator_unittest.Tpo $(DEPDIR)/run_unittests-rrcollator_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrcollator_unittest.cc' object='run_unittests-rrcollator_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-rrcollator_unittest.obj `if test -f 'rrcollator_unittest.cc'; then $(CYGPATH_W) 'rrcollator_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrcollator_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-rdatafields_unittest.o: rdatafields_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdatafields_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdatafields_unittest.Tpo -c -o run_unittests-rdatafields_unittest.o `test -f 'rdatafields_unittest.cc' || echo '$(srcdir)/'`rdatafields_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdatafields_unittest.Tpo $(DEPDIR)/run_unittests-rdatafields_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdatafields_unittest.cc' object='run_unittests-rdatafields_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-rdatafields_unittest.o `test -f 'rdatafields_unittest.cc' || echo '$(srcdir)/'`rdatafields_unittest.cc
+
+run_unittests-rdatafields_unittest.obj: rdatafields_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdatafields_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdatafields_unittest.Tpo -c -o run_unittests-rdatafields_unittest.obj `if test -f 'rdatafields_unittest.cc'; then $(CYGPATH_W) 'rdatafields_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdatafields_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdatafields_unittest.Tpo $(DEPDIR)/run_unittests-rdatafields_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdatafields_unittest.cc' object='run_unittests-rdatafields_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-rdatafields_unittest.obj `if test -f 'rdatafields_unittest.cc'; then $(CYGPATH_W) 'rdatafields_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdatafields_unittest.cc'; fi`
+
+run_unittests-rdata_pimpl_holder_unittest.o: rdata_pimpl_holder_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_pimpl_holder_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Tpo -c -o run_unittests-rdata_pimpl_holder_unittest.o `test -f 'rdata_pimpl_holder_unittest.cc' || echo '$(srcdir)/'`rdata_pimpl_holder_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Tpo $(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_pimpl_holder_unittest.cc' object='run_unittests-rdata_pimpl_holder_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_pimpl_holder_unittest.o `test -f 'rdata_pimpl_holder_unittest.cc' || echo '$(srcdir)/'`rdata_pimpl_holder_unittest.cc
+
+run_unittests-rdata_pimpl_holder_unittest.obj: rdata_pimpl_holder_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_pimpl_holder_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Tpo -c -o run_unittests-rdata_pimpl_holder_unittest.obj `if test -f 'rdata_pimpl_holder_unittest.cc'; then $(CYGPATH_W) 'rdata_pimpl_holder_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_pimpl_holder_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Tpo $(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_pimpl_holder_unittest.cc' object='run_unittests-rdata_pimpl_holder_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_pimpl_holder_unittest.obj `if test -f 'rdata_pimpl_holder_unittest.cc'; then $(CYGPATH_W) 'rdata_pimpl_holder_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_pimpl_holder_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_mx_unittest.o: rdata_mx_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_mx_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_mx_unittest.Tpo -c -o run_unittests-rdata_mx_unittest.o `test -f 'rdata_mx_unittest.cc' || echo '$(srcdir)/'`rdata_mx_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_mx_unittest.Tpo $(DEPDIR)/run_unittests-rdata_mx_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_mx_unittest.cc' object='run_unittests-rdata_mx_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_mx_unittest.o `test -f 'rdata_mx_unittest.cc' || echo '$(srcdir)/'`rdata_mx_unittest.cc
+
+run_unittests-rdata_mx_unittest.obj: rdata_mx_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_mx_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_mx_unittest.Tpo -c -o run_unittests-rdata_mx_unittest.obj `if test -f 'rdata_mx_unittest.cc'; then $(CYGPATH_W) 'rdata_mx_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_mx_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_mx_unittest.Tpo $(DEPDIR)/run_unittests-rdata_mx_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_mx_unittest.cc' object='run_unittests-rdata_mx_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_mx_unittest.obj `if test -f 'rdata_mx_unittest.cc'; then $(CYGPATH_W) 'rdata_mx_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_mx_unittest.cc'; fi`
+
+run_unittests-rdata_sshfp_unittest.o: rdata_sshfp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_sshfp_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_sshfp_unittest.Tpo -c -o run_unittests-rdata_sshfp_unittest.o `test -f 'rdata_sshfp_unittest.cc' || echo '$(srcdir)/'`rdata_sshfp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_sshfp_unittest.Tpo $(DEPDIR)/run_unittests-rdata_sshfp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_sshfp_unittest.cc' object='run_unittests-rdata_sshfp_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_sshfp_unittest.o `test -f 'rdata_sshfp_unittest.cc' || echo '$(srcdir)/'`rdata_sshfp_unittest.cc
+
+run_unittests-rdata_sshfp_unittest.obj: rdata_sshfp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_sshfp_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_sshfp_unittest.Tpo -c -o run_unittests-rdata_sshfp_unittest.obj `if test -f 'rdata_sshfp_unittest.cc'; then $(CYGPATH_W) 'rdata_sshfp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_sshfp_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_sshfp_unittest.Tpo $(DEPDIR)/run_unittests-rdata_sshfp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_sshfp_unittest.cc' object='run_unittests-rdata_sshfp_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_sshfp_unittest.obj `if test -f 'rdata_sshfp_unittest.cc'; then $(CYGPATH_W) 'rdata_sshfp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_sshfp_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_cname_unittest.o: rdata_cname_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_cname_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_cname_unittest.Tpo -c -o run_unittests-rdata_cname_unittest.o `test -f 'rdata_cname_unittest.cc' || echo '$(srcdir)/'`rdata_cname_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_cname_unittest.Tpo $(DEPDIR)/run_unittests-rdata_cname_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_cname_unittest.cc' object='run_unittests-rdata_cname_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_cname_unittest.o `test -f 'rdata_cname_unittest.cc' || echo '$(srcdir)/'`rdata_cname_unittest.cc
+
+run_unittests-rdata_cname_unittest.obj: rdata_cname_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_cname_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_cname_unittest.Tpo -c -o run_unittests-rdata_cname_unittest.obj `if test -f 'rdata_cname_unittest.cc'; then $(CYGPATH_W) 'rdata_cname_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_cname_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_cname_unittest.Tpo $(DEPDIR)/run_unittests-rdata_cname_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_cname_unittest.cc' object='run_unittests-rdata_cname_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_cname_unittest.obj `if test -f 'rdata_cname_unittest.cc'; then $(CYGPATH_W) 'rdata_cname_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_cname_unittest.cc'; fi`
+
+run_unittests-rdata_dname_unittest.o: rdata_dname_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dname_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dname_unittest.Tpo -c -o run_unittests-rdata_dname_unittest.o `test -f 'rdata_dname_unittest.cc' || echo '$(srcdir)/'`rdata_dname_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dname_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dname_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dname_unittest.cc' object='run_unittests-rdata_dname_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_dname_unittest.o `test -f 'rdata_dname_unittest.cc' || echo '$(srcdir)/'`rdata_dname_unittest.cc
+
+run_unittests-rdata_dname_unittest.obj: rdata_dname_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dname_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dname_unittest.Tpo -c -o run_unittests-rdata_dname_unittest.obj `if test -f 'rdata_dname_unittest.cc'; then $(CYGPATH_W) 'rdata_dname_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dname_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dname_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dname_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dname_unittest.cc' object='run_unittests-rdata_dname_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_dname_unittest.obj `if test -f 'rdata_dname_unittest.cc'; then $(CYGPATH_W) 'rdata_dname_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dname_unittest.cc'; fi`
+
+run_unittests-rdata_afsdb_unittest.o: rdata_afsdb_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_afsdb_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_afsdb_unittest.Tpo -c -o run_unittests-rdata_afsdb_unittest.o `test -f 'rdata_afsdb_unittest.cc' || echo '$(srcdir)/'`rdata_afsdb_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_afsdb_unittest.Tpo $(DEPDIR)/run_unittests-rdata_afsdb_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_afsdb_unittest.cc' object='run_unittests-rdata_afsdb_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_afsdb_unittest.o `test -f 'rdata_afsdb_unittest.cc' || echo '$(srcdir)/'`rdata_afsdb_unittest.cc
+
+run_unittests-rdata_afsdb_unittest.obj: rdata_afsdb_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_afsdb_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_afsdb_unittest.Tpo -c -o run_unittests-rdata_afsdb_unittest.obj `if test -f 'rdata_afsdb_unittest.cc'; then $(CYGPATH_W) 'rdata_afsdb_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_afsdb_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_afsdb_unittest.Tpo $(DEPDIR)/run_unittests-rdata_afsdb_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_afsdb_unittest.cc' object='run_unittests-rdata_afsdb_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_afsdb_unittest.obj `if test -f 'rdata_afsdb_unittest.cc'; then $(CYGPATH_W) 'rdata_afsdb_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_afsdb_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_dnskey_unittest.o: rdata_dnskey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dnskey_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dnskey_unittest.Tpo -c -o run_unittests-rdata_dnskey_unittest.o `test -f 'rdata_dnskey_unittest.cc' || echo '$(srcdir)/'`rdata_dnskey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dnskey_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dnskey_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dnskey_unittest.cc' object='run_unittests-rdata_dnskey_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_dnskey_unittest.o `test -f 'rdata_dnskey_unittest.cc' || echo '$(srcdir)/'`rdata_dnskey_unittest.cc
+
+run_unittests-rdata_dnskey_unittest.obj: rdata_dnskey_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dnskey_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dnskey_unittest.Tpo -c -o run_unittests-rdata_dnskey_unittest.obj `if test -f 'rdata_dnskey_unittest.cc'; then $(CYGPATH_W) 'rdata_dnskey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dnskey_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dnskey_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dnskey_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dnskey_unittest.cc' object='run_unittests-rdata_dnskey_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_dnskey_unittest.obj `if test -f 'rdata_dnskey_unittest.cc'; then $(CYGPATH_W) 'rdata_dnskey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dnskey_unittest.cc'; fi`
+
+run_unittests-rdata_ds_like_unittest.o: rdata_ds_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_ds_like_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ds_like_unittest.Tpo -c -o run_unittests-rdata_ds_like_unittest.o `test -f 'rdata_ds_like_unittest.cc' || echo '$(srcdir)/'`rdata_ds_like_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ds_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ds_like_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ds_like_unittest.cc' object='run_unittests-rdata_ds_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_ds_like_unittest.o `test -f 'rdata_ds_like_unittest.cc' || echo '$(srcdir)/'`rdata_ds_like_unittest.cc
+
+run_unittests-rdata_ds_like_unittest.obj: rdata_ds_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_ds_like_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ds_like_unittest.Tpo -c -o run_unittests-rdata_ds_like_unittest.obj `if test -f 'rdata_ds_like_unittest.cc'; then $(CYGPATH_W) 'rdata_ds_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ds_like_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ds_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ds_like_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ds_like_unittest.cc' object='run_unittests-rdata_ds_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_ds_like_unittest.obj `if test -f 'rdata_ds_like_unittest.cc'; then $(CYGPATH_W) 'rdata_ds_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ds_like_unittest.cc'; fi`
+
+run_unittests-rdata_nsec_unittest.o: rdata_nsec_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsec_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec_unittest.Tpo -c -o run_unittests-rdata_nsec_unittest.o `test -f 'rdata_nsec_unittest.cc' || echo '$(srcdir)/'`rdata_nsec_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec_unittest.cc' object='run_unittests-rdata_nsec_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_nsec_unittest.o `test -f 'rdata_nsec_unittest.cc' || echo '$(srcdir)/'`rdata_nsec_unittest.cc
+
+run_unittests-rdata_nsec_unittest.obj: rdata_nsec_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsec_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec_unittest.Tpo -c -o run_unittests-rdata_nsec_unittest.obj `if test -f 'rdata_nsec_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec_unittest.cc' object='run_unittests-rdata_nsec_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_nsec_unittest.obj `if test -f 'rdata_nsec_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec_unittest.cc'; fi`
+
+run_unittests-rdata_nsec3_unittest.o: rdata_nsec3_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsec3_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec3_unittest.Tpo -c -o run_unittests-rdata_nsec3_unittest.o `test -f 'rdata_nsec3_unittest.cc' || echo '$(srcdir)/'`rdata_nsec3_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec3_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec3_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec3_unittest.cc' object='run_unittests-rdata_nsec3_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_nsec3_unittest.o `test -f 'rdata_nsec3_unittest.cc' || echo '$(srcdir)/'`rdata_nsec3_unittest.cc
+
+run_unittests-rdata_nsec3_unittest.obj: rdata_nsec3_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsec3_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec3_unittest.Tpo -c -o run_unittests-rdata_nsec3_unittest.obj `if test -f 'rdata_nsec3_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec3_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec3_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec3_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec3_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec3_unittest.cc' object='run_unittests-rdata_nsec3_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_nsec3_unittest.obj `if test -f 'rdata_nsec3_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec3_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec3_unittest.cc'; fi`
+
+run_unittests-rdata_nsecbitmap_unittest.o: rdata_nsecbitmap_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsecbitmap_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Tpo -c -o run_unittests-rdata_nsecbitmap_unittest.o `test -f 'rdata_nsecbitmap_unittest.cc' || echo '$(srcdir)/'`rdata_nsecbitmap_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsecbitmap_unittest.cc' object='run_unittests-rdata_nsecbitmap_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_nsecbitmap_unittest.o `test -f 'rdata_nsecbitmap_unittest.cc' || echo '$(srcdir)/'`rdata_nsecbitmap_unittest.cc
+
+run_unittests-rdata_nsecbitmap_unittest.obj: rdata_nsecbitmap_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsecbitmap_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Tpo -c -o run_unittests-rdata_nsecbitmap_unittest.obj `if test -f 'rdata_nsecbitmap_unittest.cc'; then $(CYGPATH_W) 'rdata_nsecbitmap_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsecbitmap_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsecbitmap_unittest.cc' object='run_unittests-rdata_nsecbitmap_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_nsecbitmap_unittest.obj `if test -f 'rdata_nsecbitmap_unittest.cc'; then $(CYGPATH_W) 'rdata_nsecbitmap_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsecbitmap_unittest.cc'; fi`
+
+run_unittests-rdata_nsec3param_unittest.o: rdata_nsec3param_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsec3param_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Tpo -c -o run_unittests-rdata_nsec3param_unittest.o `test -f 'rdata_nsec3param_unittest.cc' || echo '$(srcdir)/'`rdata_nsec3param_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec3param_unittest.cc' object='run_unittests-rdata_nsec3param_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_nsec3param_unittest.o `test -f 'rdata_nsec3param_unittest.cc' || echo '$(srcdir)/'`rdata_nsec3param_unittest.cc
+
+run_unittests-rdata_nsec3param_unittest.obj: rdata_nsec3param_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_nsec3param_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Tpo -c -o run_unittests-rdata_nsec3param_unittest.obj `if test -f 'rdata_nsec3param_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec3param_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec3param_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec3param_unittest.cc' object='run_unittests-rdata_nsec3param_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_nsec3param_unittest.obj `if test -f 'rdata_nsec3param_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec3param_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec3param_unittest.cc'; fi`
+
+run_unittests-rdata_nsec3param_like_unittest.o: rdata_nsec3param_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_nsec3param_like_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Tpo -c -o run_unittests-rdata_nsec3param_like_unittest.o `test -f 'rdata_nsec3param_like_unittest.cc' || echo '$(srcdir)/'`rdata_nsec3param_like_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec3param_like_unittest.cc' object='run_unittests-rdata_nsec3param_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_nsec3param_like_unittest.o `test -f 'rdata_nsec3param_like_unittest.cc' || echo '$(srcdir)/'`rdata_nsec3param_like_unittest.cc
+
+run_unittests-rdata_nsec3param_like_unittest.obj: rdata_nsec3param_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_nsec3param_like_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Tpo -c -o run_unittests-rdata_nsec3param_like_unittest.obj `if test -f 'rdata_nsec3param_like_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec3param_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec3param_like_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_nsec3param_like_unittest.cc' object='run_unittests-rdata_nsec3param_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_nsec3param_like_unittest.obj `if test -f 'rdata_nsec3param_like_unittest.cc'; then $(CYGPATH_W) 'rdata_nsec3param_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_nsec3param_like_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_rp_unittest.o: rdata_rp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_rp_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_rp_unittest.Tpo -c -o run_unittests-rdata_rp_unittest.o `test -f 'rdata_rp_unittest.cc' || echo '$(srcdir)/'`rdata_rp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_rp_unittest.Tpo $(DEPDIR)/run_unittests-rdata_rp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_rp_unittest.cc' object='run_unittests-rdata_rp_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_rp_unittest.o `test -f 'rdata_rp_unittest.cc' || echo '$(srcdir)/'`rdata_rp_unittest.cc
+
+run_unittests-rdata_rp_unittest.obj: rdata_rp_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_rp_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_rp_unittest.Tpo -c -o run_unittests-rdata_rp_unittest.obj `if test -f 'rdata_rp_unittest.cc'; then $(CYGPATH_W) 'rdata_rp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_rp_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_rp_unittest.Tpo $(DEPDIR)/run_unittests-rdata_rp_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_rp_unittest.cc' object='run_unittests-rdata_rp_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_rp_unittest.obj `if test -f 'rdata_rp_unittest.cc'; then $(CYGPATH_W) 'rdata_rp_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_rp_unittest.cc'; fi`
+
+run_unittests-rdata_srv_unittest.o: rdata_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_srv_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_srv_unittest.Tpo -c -o run_unittests-rdata_srv_unittest.o `test -f 'rdata_srv_unittest.cc' || echo '$(srcdir)/'`rdata_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_srv_unittest.Tpo $(DEPDIR)/run_unittests-rdata_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_srv_unittest.cc' object='run_unittests-rdata_srv_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_srv_unittest.o `test -f 'rdata_srv_unittest.cc' || echo '$(srcdir)/'`rdata_srv_unittest.cc
+
+run_unittests-rdata_srv_unittest.obj: rdata_srv_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_srv_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_srv_unittest.Tpo -c -o run_unittests-rdata_srv_unittest.obj `if test -f 'rdata_srv_unittest.cc'; then $(CYGPATH_W) 'rdata_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_srv_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_srv_unittest.Tpo $(DEPDIR)/run_unittests-rdata_srv_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_srv_unittest.cc' object='run_unittests-rdata_srv_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_srv_unittest.obj `if test -f 'rdata_srv_unittest.cc'; then $(CYGPATH_W) 'rdata_srv_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_srv_unittest.cc'; fi`
+
+run_unittests-rdata_tlsa_unittest.o: rdata_tlsa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tlsa_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tlsa_unittest.Tpo -c -o run_unittests-rdata_tlsa_unittest.o `test -f 'rdata_tlsa_unittest.cc' || echo '$(srcdir)/'`rdata_tlsa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tlsa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tlsa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tlsa_unittest.cc' object='run_unittests-rdata_tlsa_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_tlsa_unittest.o `test -f 'rdata_tlsa_unittest.cc' || echo '$(srcdir)/'`rdata_tlsa_unittest.cc
+
+run_unittests-rdata_tlsa_unittest.obj: rdata_tlsa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tlsa_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tlsa_unittest.Tpo -c -o run_unittests-rdata_tlsa_unittest.obj `if test -f 'rdata_tlsa_unittest.cc'; then $(CYGPATH_W) 'rdata_tlsa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tlsa_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tlsa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tlsa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tlsa_unittest.cc' object='run_unittests-rdata_tlsa_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_tlsa_unittest.obj `if test -f 'rdata_tlsa_unittest.cc'; then $(CYGPATH_W) 'rdata_tlsa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tlsa_unittest.cc'; fi`
+
+run_unittests-rdata_minfo_unittest.o: rdata_minfo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_minfo_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_minfo_unittest.Tpo -c -o run_unittests-rdata_minfo_unittest.o `test -f 'rdata_minfo_unittest.cc' || echo '$(srcdir)/'`rdata_minfo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_minfo_unittest.Tpo $(DEPDIR)/run_unittests-rdata_minfo_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_minfo_unittest.cc' object='run_unittests-rdata_minfo_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_minfo_unittest.o `test -f 'rdata_minfo_unittest.cc' || echo '$(srcdir)/'`rdata_minfo_unittest.cc
+
+run_unittests-rdata_minfo_unittest.obj: rdata_minfo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_minfo_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_minfo_unittest.Tpo -c -o run_unittests-rdata_minfo_unittest.obj `if test -f 'rdata_minfo_unittest.cc'; then $(CYGPATH_W) 'rdata_minfo_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_minfo_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_minfo_unittest.Tpo $(DEPDIR)/run_unittests-rdata_minfo_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_minfo_unittest.cc' object='run_unittests-rdata_minfo_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_minfo_unittest.obj `if test -f 'rdata_minfo_unittest.cc'; then $(CYGPATH_W) 'rdata_minfo_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_minfo_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_naptr_unittest.o: rdata_naptr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_naptr_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_naptr_unittest.Tpo -c -o run_unittests-rdata_naptr_unittest.o `test -f 'rdata_naptr_unittest.cc' || echo '$(srcdir)/'`rdata_naptr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_naptr_unittest.Tpo $(DEPDIR)/run_unittests-rdata_naptr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_naptr_unittest.cc' object='run_unittests-rdata_naptr_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_naptr_unittest.o `test -f 'rdata_naptr_unittest.cc' || echo '$(srcdir)/'`rdata_naptr_unittest.cc
+
+run_unittests-rdata_naptr_unittest.obj: rdata_naptr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_naptr_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_naptr_unittest.Tpo -c -o run_unittests-rdata_naptr_unittest.obj `if test -f 'rdata_naptr_unittest.cc'; then $(CYGPATH_W) 'rdata_naptr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_naptr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_naptr_unittest.Tpo $(DEPDIR)/run_unittests-rdata_naptr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_naptr_unittest.cc' object='run_unittests-rdata_naptr_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_naptr_unittest.obj `if test -f 'rdata_naptr_unittest.cc'; then $(CYGPATH_W) 'rdata_naptr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_naptr_unittest.cc'; fi`
+
+run_unittests-rdata_hinfo_unittest.o: rdata_hinfo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_hinfo_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_hinfo_unittest.Tpo -c -o run_unittests-rdata_hinfo_unittest.o `test -f 'rdata_hinfo_unittest.cc' || echo '$(srcdir)/'`rdata_hinfo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_hinfo_unittest.Tpo $(DEPDIR)/run_unittests-rdata_hinfo_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_hinfo_unittest.cc' object='run_unittests-rdata_hinfo_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_hinfo_unittest.o `test -f 'rdata_hinfo_unittest.cc' || echo '$(srcdir)/'`rdata_hinfo_unittest.cc
+
+run_unittests-rdata_hinfo_unittest.obj: rdata_hinfo_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_hinfo_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_hinfo_unittest.Tpo -c -o run_unittests-rdata_hinfo_unittest.obj `if test -f 'rdata_hinfo_unittest.cc'; then $(CYGPATH_W) 'rdata_hinfo_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_hinfo_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_hinfo_unittest.Tpo $(DEPDIR)/run_unittests-rdata_hinfo_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_hinfo_unittest.cc' object='run_unittests-rdata_hinfo_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_hinfo_unittest.obj `if test -f 'rdata_hinfo_unittest.cc'; then $(CYGPATH_W) 'rdata_hinfo_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_hinfo_unittest.cc'; fi`
+
+run_unittests-rdata_caa_unittest.o: rdata_caa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_caa_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_caa_unittest.Tpo -c -o run_unittests-rdata_caa_unittest.o `test -f 'rdata_caa_unittest.cc' || echo '$(srcdir)/'`rdata_caa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_caa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_caa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_caa_unittest.cc' object='run_unittests-rdata_caa_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_caa_unittest.o `test -f 'rdata_caa_unittest.cc' || echo '$(srcdir)/'`rdata_caa_unittest.cc
+
+run_unittests-rdata_caa_unittest.obj: rdata_caa_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_caa_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_caa_unittest.Tpo -c -o run_unittests-rdata_caa_unittest.obj `if test -f 'rdata_caa_unittest.cc'; then $(CYGPATH_W) 'rdata_caa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_caa_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_caa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_caa_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_caa_unittest.cc' object='run_unittests-rdata_caa_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_caa_unittest.obj `if test -f 'rdata_caa_unittest.cc'; then $(CYGPATH_W) 'rdata_caa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_caa_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-qid_gen_unittest.o: qid_gen_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-qid_gen_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-qid_gen_unittest.Tpo -c -o run_unittests-qid_gen_unittest.o `test -f 'qid_gen_unittest.cc' || echo '$(srcdir)/'`qid_gen_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-qid_gen_unittest.Tpo $(DEPDIR)/run_unittests-qid_gen_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='qid_gen_unittest.cc' object='run_unittests-qid_gen_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-qid_gen_unittest.o `test -f 'qid_gen_unittest.cc' || echo '$(srcdir)/'`qid_gen_unittest.cc
+
+run_unittests-qid_gen_unittest.obj: qid_gen_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-qid_gen_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-qid_gen_unittest.Tpo -c -o run_unittests-qid_gen_unittest.obj `if test -f 'qid_gen_unittest.cc'; then $(CYGPATH_W) 'qid_gen_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/qid_gen_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-qid_gen_unittest.Tpo $(DEPDIR)/run_unittests-qid_gen_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='qid_gen_unittest.cc' object='run_unittests-qid_gen_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-qid_gen_unittest.obj `if test -f 'qid_gen_unittest.cc'; then $(CYGPATH_W) 'qid_gen_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/qid_gen_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-masterload_unittest.o: masterload_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-masterload_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-masterload_unittest.Tpo -c -o run_unittests-masterload_unittest.o `test -f 'masterload_unittest.cc' || echo '$(srcdir)/'`masterload_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-masterload_unittest.Tpo $(DEPDIR)/run_unittests-masterload_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='masterload_unittest.cc' object='run_unittests-masterload_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-masterload_unittest.o `test -f 'masterload_unittest.cc' || echo '$(srcdir)/'`masterload_unittest.cc
+
+run_unittests-masterload_unittest.obj: masterload_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-masterload_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-masterload_unittest.Tpo -c -o run_unittests-masterload_unittest.obj `if test -f 'masterload_unittest.cc'; then $(CYGPATH_W) 'masterload_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/masterload_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-masterload_unittest.Tpo $(DEPDIR)/run_unittests-masterload_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='masterload_unittest.cc' object='run_unittests-masterload_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-masterload_unittest.obj `if test -f 'masterload_unittest.cc'; then $(CYGPATH_W) 'masterload_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/masterload_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-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-rrset_collection_unittest.o: rrset_collection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrset_collection_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrset_collection_unittest.Tpo -c -o run_unittests-rrset_collection_unittest.o `test -f 'rrset_collection_unittest.cc' || echo '$(srcdir)/'`rrset_collection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrset_collection_unittest.Tpo $(DEPDIR)/run_unittests-rrset_collection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset_collection_unittest.cc' object='run_unittests-rrset_collection_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_collection_unittest.o `test -f 'rrset_collection_unittest.cc' || echo '$(srcdir)/'`rrset_collection_unittest.cc
+
+run_unittests-rrset_collection_unittest.obj: rrset_collection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrset_collection_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrset_collection_unittest.Tpo -c -o run_unittests-rrset_collection_unittest.obj `if test -f 'rrset_collection_unittest.cc'; then $(CYGPATH_W) 'rrset_collection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrset_collection_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrset_collection_unittest.Tpo $(DEPDIR)/run_unittests-rrset_collection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset_collection_unittest.cc' object='run_unittests-rrset_collection_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_collection_unittest.obj `if test -f 'rrset_collection_unittest.cc'; then $(CYGPATH_W) 'rrset_collection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrset_collection_unittest.cc'; fi`
+
+run_unittests-zone_checker_unittest.o: zone_checker_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-zone_checker_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-zone_checker_unittest.Tpo -c -o run_unittests-zone_checker_unittest.o `test -f 'zone_checker_unittest.cc' || echo '$(srcdir)/'`zone_checker_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-zone_checker_unittest.Tpo $(DEPDIR)/run_unittests-zone_checker_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='zone_checker_unittest.cc' object='run_unittests-zone_checker_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-zone_checker_unittest.o `test -f 'zone_checker_unittest.cc' || echo '$(srcdir)/'`zone_checker_unittest.cc
+
+run_unittests-zone_checker_unittest.obj: zone_checker_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-zone_checker_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-zone_checker_unittest.Tpo -c -o run_unittests-zone_checker_unittest.obj `if test -f 'zone_checker_unittest.cc'; then $(CYGPATH_W) 'zone_checker_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/zone_checker_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-zone_checker_unittest.Tpo $(DEPDIR)/run_unittests-zone_checker_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='zone_checker_unittest.cc' object='run_unittests-zone_checker_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-zone_checker_unittest.obj `if test -f 'zone_checker_unittest.cc'; then $(CYGPATH_W) 'zone_checker_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/zone_checker_unittest.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-masterload_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-nsec3hash_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-opcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-qid_gen_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-question_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_afsdb_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_caa_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_cname_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dname_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dnskey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ds_like_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_hinfo_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_minfo_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_mx_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_naptr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec3_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_rp_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_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_sshfp_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tlsa_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-rdatafields_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrclass_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrcollator_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrset_collection_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-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 ./$(DEPDIR)/run_unittests-zone_checker_unittest.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-masterload_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-nsec3hash_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-opcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-qid_gen_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-question_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rcode_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_afsdb_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_caa_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_cname_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dname_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_dnskey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ds_like_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_hinfo_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_minfo_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_mx_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_naptr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec3_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec3param_like_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec3param_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsec_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_nsecbitmap_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_pimpl_holder_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_rp_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_srv_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_sshfp_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rdata_tlsa_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-rdatafields_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrclass_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrcollator_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-rrset_collection_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-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 ./$(DEPDIR)/run_unittests-zone_checker_unittest.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..a8d2590
--- /dev/null
+++ b/src/lib/dns/tests/dns_exceptions_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2014-2015,2017 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..dfc68cc
--- /dev/null
+++ b/src/lib/dns/tests/edns_unittest.cc
@@ -0,0 +1,258 @@
+// 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 <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(NULL, 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..15650fa
--- /dev/null
+++ b/src/lib/dns/tests/labelsequence_unittest.cc
@@ -0,0 +1,1243 @@
+// Copyright (C) 2012-2020 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", NULL
+};
+
+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", NULL
+};
+const char* const cn_servers[] = {
+ "a.dns.cn", "b.dns.cn", "c.dns.cn", "d.dns.cn", "e.dns.cn",
+ "ns.cernet.net", NULL
+};
+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", NULL
+};
+
+// 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] != NULL; ++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(),
+ static_cast<const uint8_t*>(ob.getData()),
+ static_cast<const uint8_t*>(ob.getData()) +
+ ob.getLength());
+ 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(NULL), 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..c5f618a
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
@@ -0,0 +1,368 @@
+// Copyright (C) 2012-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/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..e810136
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_state_unittest.cc
@@ -0,0 +1,607 @@
+// Copyright (C) 2012-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/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(NULL),
+ 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*>(NULL), 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..2167a9f
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_token_unittest.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2012-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 <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..7bebb48
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -0,0 +1,521 @@
+// Copyright (C) 2012-2020 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(NULL), 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", NULL));
+ 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..9d23802
--- /dev/null
+++ b/src/lib/dns/tests/master_loader_callbacks_test.cc
@@ -0,0 +1,79 @@
+// Copyright (C) 2012-2020 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..74300d3
--- /dev/null
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -0,0 +1,1445 @@
+// Copyright (C) 2012-2021 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\"",
+ NULL
+ };
+ for (const char** include = includes; *include != NULL; ++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\"",
+ NULL
+ };
+ for (const char** origin = origins; *origin != NULL; ++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_F(MasterLoaderTest, generate) {
+ // Various forms of the directive
+ const char* generates[] = {
+ "$generate",
+ "$GENERATE",
+ "$Generate",
+ "$GeneratE",
+ "\"$GENERATE\"",
+ NULL
+ };
+ for (const char** generate = generates; *generate != NULL; ++generate) {
+ SCOPED_TRACE(*generate);
+
+ clear();
+ const string directive = *generate;
+ const string input =
+ "$ORIGIN example.org.\n"
+ "before.example.org. 3600 IN A 192.0.2.0\n" +
+ directive + " 3-5 host$ A 192.0.2.$\n" +
+ "after.example.org. 3600 IN A 192.0.2.255\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ // The "before" and "after" scaffolding below checks that no
+ // extra records are added by $GENERATE outside the requested
+ // range.
+ checkRR("before.example.org", RRType::A(), "192.0.2.0");
+ checkRR("host3.example.org", RRType::A(), "192.0.2.3");
+ checkRR("host4.example.org", RRType::A(), "192.0.2.4");
+ checkRR("host5.example.org", RRType::A(), "192.0.2.5");
+ checkRR("after.example.org", RRType::A(), "192.0.2.255");
+ }
+}
+
+TEST_F(MasterLoaderTest, generateRelativeLHS) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 1-2 @ 3600 NS ns$.example.org.\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("example.org", RRType::NS(), "ns1.example.org.");
+ checkRR("example.org", RRType::NS(), "ns2.example.org.");
+}
+
+TEST_F(MasterLoaderTest, generateInFront) {
+ // $ is in the front
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 9-10 $host 3600 TXT \"$ pomegranate\"\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("9host.example.org", RRType::TXT(), "9 pomegranate");
+ checkRR("10host.example.org", RRType::TXT(), "10 pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateInMiddle) {
+ // $ is in the middle
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 9-10 num$-host 3600 TXT \"This is $ pomegranate\"\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("num9-host.example.org", RRType::TXT(), "This is 9 pomegranate");
+ checkRR("num10-host.example.org", RRType::TXT(), "This is 10 pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateAtEnd) {
+ // $ is at the end
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 9-10 num$-host 3600 TXT Pomegranate$\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("num9-host.example.org", RRType::TXT(), "Pomegranate9");
+ checkRR("num10-host.example.org", RRType::TXT(), "Pomegranate10");
+}
+
+TEST_F(MasterLoaderTest, generateStripsQuotes) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 1-2 @ 3600 MX \"$ mx$.example.org.\"\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("example.org", RRType::MX(), "1 mx1.example.org.");
+ checkRR("example.org", RRType::MX(), "2 mx2.example.org.");
+}
+
+TEST_F(MasterLoaderTest, generateWithDoublePlaceholder) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 9-10 host$ 3600 TXT \"This is $$ pomegranate\"\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("host9.example.org", RRType::TXT(), "This is $ pomegranate");
+ checkRR("host10.example.org", RRType::TXT(), "This is $ pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateWithEscape) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 9-10 host$ 3600 TXT \"This is \\$\\pomegranate\"\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("host9.example.org", RRType::TXT(), "This is \\$\\pomegranate");
+ checkRR("host10.example.org", RRType::TXT(), "This is \\$\\pomegranate");
+}
+
+TEST_F(MasterLoaderTest, generateWithParams) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$TTL 3600\n"
+ "$GENERATE 2-3 host$ A 192.0.2.$\n"
+ "$GENERATE 5-6 host$ 3600 A 192.0.2.$\n"
+ "$GENERATE 8-9 host$ IN A 192.0.2.$\n"
+ "$GENERATE 11-12 host$ IN 3600 A 192.0.2.$\n"
+ "$GENERATE 14-15 host$ 3600 IN A 192.0.2.$\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("host2.example.org", RRType::A(), "192.0.2.2");
+ checkRR("host3.example.org", RRType::A(), "192.0.2.3");
+
+ checkRR("host5.example.org", RRType::A(), "192.0.2.5");
+ checkRR("host6.example.org", RRType::A(), "192.0.2.6");
+
+ checkRR("host8.example.org", RRType::A(), "192.0.2.8");
+ checkRR("host9.example.org", RRType::A(), "192.0.2.9");
+
+ checkRR("host11.example.org", RRType::A(), "192.0.2.11");
+ checkRR("host12.example.org", RRType::A(), "192.0.2.12");
+
+ checkRR("host14.example.org", RRType::A(), "192.0.2.14");
+ checkRR("host15.example.org", RRType::A(), "192.0.2.15");
+}
+
+TEST_F(MasterLoaderTest, generateWithStep) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 2-9/2 host$ 3600 A 192.0.2.$\n"
+ "$GENERATE 12-21/3 host$ 3600 A 192.0.2.$\n"
+ "$GENERATE 30-31/1 host$ 3600 A 192.0.2.$\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("host2.example.org", RRType::A(), "192.0.2.2");
+ checkRR("host4.example.org", RRType::A(), "192.0.2.4");
+ checkRR("host6.example.org", RRType::A(), "192.0.2.6");
+ checkRR("host8.example.org", RRType::A(), "192.0.2.8");
+
+ checkRR("host12.example.org", RRType::A(), "192.0.2.12");
+ checkRR("host15.example.org", RRType::A(), "192.0.2.15");
+ checkRR("host18.example.org", RRType::A(), "192.0.2.18");
+ checkRR("host21.example.org", RRType::A(), "192.0.2.21");
+
+ checkRR("host30.example.org", RRType::A(), "192.0.2.30");
+ checkRR("host31.example.org", RRType::A(), "192.0.2.31");
+}
+
+TEST_F(MasterLoaderTest, generateWithModifiers) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$TTL 3600\n"
+
+ // Use a positive delta of 1 in the LHS and a negative delta of
+ // -1 in the RHS
+ "$GENERATE 2-9/2 host${1} A 192.0.2.${-1}\n"
+
+ "$GENERATE 10-12 host${0,4} A 192.0.2.$\n"
+ "$GENERATE 14-15 host${0,4,d} A 192.0.2.$\n"
+
+ // Names are case-insensitive, so we use TXT's RDATA to check
+ // case with hex representation.
+ "$GENERATE 30-31 host$ TXT \"Value ${0,4,x}\"\n"
+ "$GENERATE 42-43 host$ TXT \"Value ${0,4,X}\"\n"
+
+ // Octal does not use any alphabets
+ "$GENERATE 45-46 host${0,4,o} A 192.0.2.$\n"
+
+ // Here, the LHS has a trailing dot (which would result in an
+ // out-of-zone name), but that should be handled as a relative
+ // name.
+ "$GENERATE 90-92 ${0,8,n} A 192.0.2.$\n"
+
+ // Here, the LHS has no trailing dot, and results in the same
+ // number of labels as width=8 above.
+ "$GENERATE 94-96 ${0,7,n} A 192.0.2.$\n"
+
+ // Names are case-insensitive, so we use TXT's RDATA to check
+ // case with nibble representation.
+ "$GENERATE 106-107 host$ TXT \"Value ${0,9,n}\"\n"
+ "$GENERATE 109-110 host$ TXT \"Value ${0,9,N}\"\n"
+
+ // Junk type will not parse and 'd' is assumed. No error is
+ // generated (this is to match BIND 9 behavior).
+ "$GENERATE 200-201 host${0,4,j} A 192.0.2.$\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSuccessfully());
+ EXPECT_TRUE(errors_.empty());
+
+ checkRR("host3.example.org", RRType::A(), "192.0.2.1");
+ checkRR("host5.example.org", RRType::A(), "192.0.2.3");
+ checkRR("host7.example.org", RRType::A(), "192.0.2.5");
+ checkRR("host9.example.org", RRType::A(), "192.0.2.7");
+
+ checkRR("host0010.example.org", RRType::A(), "192.0.2.10");
+ checkRR("host0011.example.org", RRType::A(), "192.0.2.11");
+ checkRR("host0012.example.org", RRType::A(), "192.0.2.12");
+
+ checkRR("host0014.example.org", RRType::A(), "192.0.2.14");
+ checkRR("host0015.example.org", RRType::A(), "192.0.2.15");
+
+ checkRR("host30.example.org", RRType::TXT(), "Value 001e");
+ checkRR("host31.example.org", RRType::TXT(), "Value 001f");
+
+ checkRR("host42.example.org", RRType::TXT(), "Value 002A");
+ checkRR("host43.example.org", RRType::TXT(), "Value 002B");
+
+ checkRR("host0055.example.org", RRType::A(), "192.0.2.45");
+ checkRR("host0056.example.org", RRType::A(), "192.0.2.46");
+
+ checkRR("a.5.0.0.example.org", RRType::A(), "192.0.2.90");
+ checkRR("b.5.0.0.example.org", RRType::A(), "192.0.2.91");
+ checkRR("c.5.0.0.example.org", RRType::A(), "192.0.2.92");
+
+ checkRR("e.5.0.0.example.org", RRType::A(), "192.0.2.94");
+ checkRR("f.5.0.0.example.org", RRType::A(), "192.0.2.95");
+ checkRR("0.6.0.0.example.org", RRType::A(), "192.0.2.96");
+
+ checkRR("host106.example.org", RRType::TXT(), "Value a.6.0.0.0");
+ checkRR("host107.example.org", RRType::TXT(), "Value b.6.0.0.0");
+ checkRR("host109.example.org", RRType::TXT(), "Value D.6.0.0.0");
+ checkRR("host110.example.org", RRType::TXT(), "Value E.6.0.0.0");
+
+ checkRR("host0200.example.org", RRType::A(), "192.0.2.200");
+ checkRR("host0201.example.org", RRType::A(), "192.0.2.201");
+}
+
+TEST_F(MasterLoaderTest, generateWithNoModifiers) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$TTL 3600\n"
+ "$GENERATE 10-12 host${} A 192.0.2.$\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "Invalid $GENERATE format modifiers", 3);
+ checkCallbackMessage(errors_.at(1),
+ "$GENERATE error", 3);
+}
+
+TEST_F(MasterLoaderTest, generateWithBadModifiers) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$TTL 3600\n"
+ "$GENERATE 10-12 host${GARBAGE} A 192.0.2.$\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSuccessfully());
+ ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "Invalid $GENERATE format modifiers", 3);
+ checkCallbackMessage(errors_.at(1),
+ "$GENERATE error", 3);
+}
+
+TEST_F(MasterLoaderTest, generateMissingRange) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingLHS) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 2-4\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingType) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 2-4 host$\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateMissingRHS) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 2-4 host$ A\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "unexpected end of input", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithBadRangeSyntax) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE ABCD host$ 3600 A 192.0.2.$\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "$GENERATE: invalid range: ABCD", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithInvalidRange) {
+ // start > stop
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 2-1 host$ 3600 A 192.0.2.$\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "$GENERATE: invalid range: 2-1", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithInvalidClass) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 1-2 host$ 3600 CH A 192.0.2.$\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "Class mismatch: CH vs. IN", 2);
+}
+
+TEST_F(MasterLoaderTest, generateWithNoAvailableTTL) {
+ const string input =
+ "$ORIGIN example.org.\n"
+ "$GENERATE 1-2 host$ A 192.0.2.$\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()); // For the broken GENERATE
+ EXPECT_TRUE(warnings_.empty());
+
+ checkCallbackMessage(errors_.at(0),
+ "no TTL specified; load rejected", 2);
+}
+
+// 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", NULL, "Invalid name" },
+ { "www FORTNIGHT IN A 192.0.2.1", NULL, "Invalid TTL" },
+ { "www 3600 XX A 192.0.2.1", NULL, "Invalid class" },
+ { "www 3600 IN A bad_ip", NULL, "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", NULL, "Unexpected EOLN" },
+ { "www 3600 CH TXT nothing", "Class mismatch: CH vs. IN",
+ "Class mismatch" },
+ { "www \"3600\" IN A 192.0.2.1", NULL, "Quoted TTL" },
+ { "www 3600 \"IN\" A 192.0.2.1", NULL, "Quoted class" },
+ { "www 3600 IN \"A\" 192.0.2.1", NULL, "Quoted type" },
+ { "unbalanced)paren 3600 IN A 192.0.2.1", NULL, "Token error 1" },
+ { "www 3600 unbalanced)paren A 192.0.2.1", NULL,
+ "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", NULL, "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", NULL, "Include file not found" },
+ { "$INCLUDE /file/not/found example.org. and here goes bunch of garbage",
+ NULL, "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" },
+ { NULL, NULL, NULL }
+};
+
+// 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 != NULL; ++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 != NULL) {
+ 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/masterload_unittest.cc b/src/lib/dns/tests/masterload_unittest.cc
new file mode 100644
index 0000000..e412de0
--- /dev/null
+++ b/src/lib/dns/tests/masterload_unittest.cc
@@ -0,0 +1,397 @@
+// Copyright (C) 2010-2022 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 <ios>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <dns/masterload.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+using namespace std;
+using namespace isc::dns;
+namespace ph = std::placeholders;
+
+namespace {
+// A callback functor for masterLoad() commonly used for the following tests.
+class TestCallback {
+public:
+ TestCallback(vector<ConstRRsetPtr>& rrsets) : rrsets_(rrsets) {}
+ void operator()(ConstRRsetPtr rrset) {
+ rrsets_.push_back(rrset);
+ }
+private:
+ vector<ConstRRsetPtr>& rrsets_;
+};
+
+// A function version of TestCallback.
+void
+testCallback(ConstRRsetPtr rrset, vector<ConstRRsetPtr>* rrsets) {
+ rrsets->push_back(rrset);
+}
+
+class MasterLoadTest : public ::testing::Test {
+protected:
+ MasterLoadTest() : origin("example.com"), zclass(RRClass::IN()),
+ callback(results) {}
+public:
+ void rrsetCallback(ConstRRsetPtr rrset) {
+ results.push_back(rrset);
+ }
+protected:
+ Name origin;
+ RRClass zclass;
+ stringstream rr_stream;
+ vector<ConstRRsetPtr> results;
+ TestCallback callback;
+};
+
+// Commonly used test RRs
+const char* const txt_rr = "example.com. 3600 IN TXT \"test data\"\n";
+const char* const a_rr1 = "www.example.com. 60 IN A 192.0.2.1\n";
+const char* const a_rr2 = "www.example.com. 60 IN A 192.0.2.2\n";
+const char* const a_rr3 = "ftp.example.com. 60 IN A 192.0.2.3\n";
+// multi-field RR case
+const char* const soa_rr = "example.com. 7200 IN SOA . . 0 0 0 0 0\n";
+// A couple of RRSIGs, different type covered
+const char* const rrsig_rr1 =
+ "www.example.com. 60 IN RRSIG A 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE\n";
+const char* const rrsig_rr2 =
+ "www.example.com. 60 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKEFAKEFAKE\n";
+
+// Commonly used for some tests to check the constructed RR content.
+const char* const dnskey_rdata =
+ "256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=\n";
+
+TEST_F(MasterLoadTest, loadRRs) {
+ // a simple case: loading 3 RRs, each consists of a single RRset.
+ rr_stream << txt_rr << a_rr1 << soa_rr;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(a_rr1, results[1]->toText());
+ EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithFunctionCallback) {
+ // The same test as loadRRs but using a normal function (not a functor
+ // object)
+ rr_stream << txt_rr << a_rr1 << soa_rr;
+ masterLoad(rr_stream, origin, zclass,
+ std::bind(&testCallback, ph::_1, &results));
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(a_rr1, results[1]->toText());
+ EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadWithMemFunctionCallback) {
+ // The same test as loadRRs but using a class member function (with a
+ // help of std.bind)
+ rr_stream << txt_rr << a_rr1 << soa_rr;
+ masterLoad(rr_stream, origin, zclass,
+ std::bind(&MasterLoadTest::rrsetCallback, this, ph::_1));
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(a_rr1, results[1]->toText());
+ EXPECT_EQ(soa_rr, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadComments) {
+ rr_stream << ";; comment line, should be skipped\n"
+ << "\n" // blank line (should be skipped)
+ << txt_rr;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRset) {
+ // load an RRset containing two RRs
+ rr_stream << a_rr1 << a_rr2;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(string(a_rr1) + string(a_rr2), results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRsetsOfSameType) {
+ // load two RRsets with the same RR type and different owner names.
+ // the loader must distinguish them as separate RRsets.
+ rr_stream << a_rr1 << a_rr3;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(2, results.size());
+ EXPECT_EQ(a_rr1, results[0]->toText());
+ EXPECT_EQ(a_rr3, results[1]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRsetsInterleaved) {
+ // two RRs that belongs to the same RRset (rr1 and rr2) are interleaved
+ // by another. This is an unexpected case for this loader, but it's
+ // not considered an error. The loader will simply treat them separate
+ // RRsets.
+ rr_stream << a_rr1 << a_rr3 << a_rr2;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(3, results.size());
+ EXPECT_EQ(a_rr1, results[0]->toText());
+ EXPECT_EQ(a_rr3, results[1]->toText());
+ EXPECT_EQ(a_rr2, results[2]->toText());
+}
+
+TEST_F(MasterLoadTest, loadRRsigs) {
+ // RRSIGs of different types covered should be separated
+ rr_stream << rrsig_rr1 << rrsig_rr2;
+ masterLoad(rr_stream, origin, zclass, callback);
+ EXPECT_EQ(2, results.size());
+}
+
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithComment) {
+ // Comment at the end of line should be ignored and the RR should be
+ // accepted.
+ rr_stream << "example.com. 3600 IN DNSKEY\t256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE= ; key id = 40430\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentNoSpace) {
+ // Similar to the previous one, but there's no space before comments.
+ // It should still work.
+ rr_stream << "example.com. 3600 IN DNSKEY\t256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=; key id = 40430\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentEmptyComment) {
+ // Similar to the previous one, but there's no data after the ;
+ // It should still work.
+ rr_stream << "example.com. 3600 IN DNSKEY\t256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE= ;\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentEmptyCommentNoSpace) {
+ // Similar to the previous one, but there's no space before or after ;
+ // It should still work.
+ rr_stream << "example.com. 3600 IN DNSKEY\t256 3 7 "
+ "AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH "
+ "zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=;\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::DNSKEY(), zclass,
+ dnskey_rdata)));
+}
+
+TEST_F(MasterLoadTest, loadRRWithEOLWhitespace) {
+ // Test with whitespace after rdata
+ // It should still work.
+ rr_stream << "example.com. 3600 IN NSEC3PARAM 1 0 1 beef \n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::NSEC3PARAM(), zclass,
+ "1 0 1 beef")));
+}
+
+TEST_F(MasterLoadTest, loadRRWithEOLWhitespaceTab) {
+ // Similar to the previous one, tab instead of space.
+ // It should still work.
+ rr_stream << "example.com. 3600 IN NSEC3PARAM 1 0 1 beef\t\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::NSEC3PARAM(), zclass,
+ "1 0 1 beef")));
+}
+
+TEST_F(MasterLoadTest, loadRRNoComment) {
+ // A semicolon in a character-string shouldn't confuse the parser.
+ rr_stream << "example.com. 3600 IN TXT \"aaa;bbb\"\n";
+ masterLoad(rr_stream, origin, zclass, callback);
+ EXPECT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::TXT(), zclass,
+ "\"aaa;bbb\"")));
+}
+
+TEST_F(MasterLoadTest, nonAbsoluteOwner) {
+ // If the owner name is not absolute, the zone origin is assumed to be
+ // its origin.
+ rr_stream << "example.com 3600 IN A 192.0.2.1";
+ masterLoad(rr_stream, origin, zclass, callback);
+ EXPECT_EQ(1, results.size());
+ EXPECT_EQ(results[0]->getName(), Name("example.com.example.com"));
+}
+
+TEST_F(MasterLoadTest, loadRREmptyAndComment) {
+ // There's no RDATA (invalid in this case) but a comment. This position
+ // shouldn't cause any disruption and should be treated as a normal error.
+ rr_stream << "example.com. 3600 IN A ;\n";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadWithNoEOF) {
+ // the input stream doesn't end with a new line (and the following blank
+ // line). It should be accepted.
+ string rr_string(a_rr1);
+ rr_string.erase(rr_string.end() - 1);
+ rr_stream << rr_string;
+ masterLoad(rr_stream, origin, zclass, callback);
+ ASSERT_EQ(1, results.size());
+ EXPECT_EQ(a_rr1, results[0]->toText());
+}
+
+TEST_F(MasterLoadTest, loadEmpty) {
+ // an unusual case: empty input. load must succeed with an empty result.
+ masterLoad(rr_stream, origin, zclass, callback);
+ EXPECT_EQ(0, results.size());
+}
+
+TEST_F(MasterLoadTest, loadWithBeginningSpace) {
+ rr_stream << " " << a_rr1;
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadWithBeginningTab) {
+ rr_stream << "\t" << a_rr1;
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadInvalidRRClass) {
+ rr_stream << "example.com. 3600 CH TXT \"test text\"";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadOutOfZoneData) {
+ rr_stream << "example.org. 3600 IN A 192.0.2.255";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadNonAtopSOA) {
+ // SOA's owner name must be zone's origin.
+ rr_stream << "soa.example.com. 3600 IN SOA . . 0 0 0 0 0";
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+}
+
+// Load TTL with units
+TEST_F(MasterLoadTest, loadUnitTTL) {
+ stringstream rr_stream2("example.com. 1D IN A 192.0.2.1");
+ masterLoad(rr_stream2, origin, zclass, callback);
+ EXPECT_EQ(1, results.size());
+ EXPECT_EQ(0, results[0]->getRdataIterator()->getCurrent().compare(
+ *rdata::createRdata(RRType::A(), zclass, "192.0.2.1")));
+}
+
+TEST_F(MasterLoadTest, loadBadRRText) {
+ rr_stream << "example..com. 3600 IN A 192.0.2.1"; // bad owner name
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
+ MasterLoadError);
+
+ // bad RR class text
+ stringstream rr_stream3("example.com. 3600 BAD A 192.0.2.1");
+ EXPECT_THROW(masterLoad(rr_stream3, origin, zclass, callback),
+ MasterLoadError);
+
+ // bad RR type text
+ stringstream rr_stream4("example.com. 3600 IN BAD 192.0.2.1");
+ EXPECT_THROW(masterLoad(rr_stream4, origin, zclass, callback),
+ MasterLoadError);
+
+ // bad RDATA text
+ stringstream rr_stream5("example.com. 3600 IN A 2001:db8::1");
+ EXPECT_THROW(masterLoad(rr_stream5, origin, zclass, callback),
+ MasterLoadError);
+
+ // incomplete RR text
+ stringstream rr_stream6("example.com. 3600 IN A");
+ EXPECT_THROW(masterLoad(rr_stream6, origin, zclass, callback),
+ MasterLoadError);
+}
+
+// This is a helper callback to test the case the input stream becomes bad
+// in the middle of processing.
+class StreamInvalidator {
+public:
+ StreamInvalidator(stringstream& ss) : ss_(ss) {}
+ void operator()(ConstRRsetPtr) {
+ ss_.setstate(ios::badbit);
+ }
+private:
+ stringstream& ss_;
+};
+
+TEST_F(MasterLoadTest, loadBadStream) {
+ rr_stream << txt_rr << a_rr1;
+ StreamInvalidator invalidator(rr_stream);
+ EXPECT_THROW(masterLoad(rr_stream, origin, zclass, invalidator),
+ MasterLoadError);
+}
+
+TEST_F(MasterLoadTest, loadFromFile) {
+ // The main parser is shared with the stream version, so we simply test
+ // file I/O specific parts.
+ masterLoad(TEST_DATA_SRCDIR "/masterload.txt", origin, zclass, callback);
+ ASSERT_EQ(2, results.size());
+ EXPECT_EQ(txt_rr, results[0]->toText());
+ EXPECT_EQ(string(a_rr1) + string(a_rr2), results[1]->toText());
+
+ // NULL file name. Should result in exception.
+ EXPECT_THROW(masterLoad(NULL, origin, zclass, callback), MasterLoadError);
+
+ // Non existent file name. Ditto.
+ EXPECT_THROW(masterLoad(TEST_DATA_BUILDDIR "/nonexistent.txt", origin,
+ zclass, callback), MasterLoadError);
+}
+} // end namespace
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
new file mode 100644
index 0000000..8b6832b
--- /dev/null
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -0,0 +1,1162 @@
+// 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 <fstream>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/time_utilities.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_EQ(static_cast<void*>(NULL), 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_NE(static_cast<void*>(NULL), 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_EQ(static_cast<void*>(NULL), tsig_rr->getRdata().getOtherData());
+
+ // If we clear the message for reuse, the recorded TSIG will be cleared.
+ message_parse.clear(Message::PARSE);
+ EXPECT_EQ(static_cast<void*>(NULL), 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_NE(static_cast<void*>(NULL), 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), 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), 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()),
+ 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), 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), 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), 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), OutOfRange);
+}
+
+TEST_F(MessageTest, appendSection) {
+ Message target(Message::RENDER);
+
+ // Section check
+ EXPECT_THROW(target.appendSection(bogus_section, message_render),
+ 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), InvalidBufferPosition);
+}
+
+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 = NULL)
+{
+ 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 != NULL) {
+ RRsetPtr ans_rrset(new RRset(Name("www.example.com"), RRClass::IN(),
+ qtype, RRTTL(86400)));
+ for (vector<const char*>::const_iterator it = answer_data->begin();
+ it != answer_data->end();
+ ++it) {
+ 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() != NULL);
+}
+
+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..c3a53eb
--- /dev/null
+++ b/src/lib/dns/tests/messagerenderer_unittest.cc
@@ -0,0 +1,292 @@
+// Copyright (C) 2009-2017 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(NULL);
+ 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(NULL);
+ EXPECT_THROW(renderer.setBuffer(NULL), isc::InvalidParameter);
+
+ // It's okay to reset a temporary buffer without using it.
+ renderer.setBuffer(&new_buffer);
+ EXPECT_NO_THROW(renderer.setBuffer(NULL));
+}
+
+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..caf1f12
--- /dev/null
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -0,0 +1,797 @@
+// Copyright (C) 2009-2019 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");
+
+ vector<string>::const_iterator it;
+ for (it = strnames.begin(); it != strnames.end(); ++it) {
+ 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, NULL), 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, NULL));
+}
+
+// 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, NULL), MissingNameOrigin);
+}
+
+// When we don't provide the data, it throws
+TEST_F(NameTest, noDataProvided) {
+ EXPECT_THROW(Name(NULL, 10, NULL), isc::InvalidParameter);
+ EXPECT_THROW(Name(NULL, 10, &origin_name), isc::InvalidParameter);
+ EXPECT_THROW(Name("www", 0, NULL), 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, NULL), 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, NULL).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));
+
+ vector<CompareParameters>::const_iterator it;
+ for (it = params.begin(); it != params.end(); ++it) {
+ 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/nsec3hash_unittest.cc b/src/lib/dns/tests/nsec3hash_unittest.cc
new file mode 100644
index 0000000..b47bb49
--- /dev/null
+++ b/src/lib/dns/tests/nsec3hash_unittest.cc
@@ -0,0 +1,269 @@
+// Copyright (C) 2012-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 <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/nsec3hash.h>
+#include <dns/labelsequence.h>
+#include <dns/rdataclass.h>
+#include <util/encode/hex.h>
+
+using boost::scoped_ptr;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+using namespace isc::util::encode;
+
+namespace {
+typedef scoped_ptr<NSEC3Hash> NSEC3HashPtr;
+
+// Commonly used NSEC3 suffix, defined to reduce the amount of typing
+const char* const nsec3_common = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
+
+class NSEC3HashTest : public ::testing::Test {
+protected:
+ NSEC3HashTest() :
+ test_hash(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd"))),
+ test_hash_nsec3(NSEC3Hash::create(generic::NSEC3
+ ("1 0 12 aabbccdd " +
+ string(nsec3_common))))
+ {
+ const uint8_t salt[] = {0xaa, 0xbb, 0xcc, 0xdd};
+ test_hash_args.reset(NSEC3Hash::create(1, 12, salt, sizeof(salt)));
+ }
+
+ ~NSEC3HashTest() {
+ // Make sure we reset the hash creator to the default
+ setNSEC3HashCreator(NULL);
+ }
+
+ // An NSEC3Hash object commonly used in tests. Parameters are borrowed
+ // from the RFC5155 example. Construction of this object implicitly
+ // checks a successful case of the creation.
+ NSEC3HashPtr test_hash;
+
+ // Similar to test_hash, but created from NSEC3 RR.
+ NSEC3HashPtr test_hash_nsec3;
+
+ // Similar to test_hash, but created from passed args.
+ NSEC3HashPtr test_hash_args;
+};
+
+TEST_F(NSEC3HashTest, unknownAlgorithm) {
+ EXPECT_THROW(NSEC3HashPtr(
+ NSEC3Hash::create(
+ generic::NSEC3PARAM("2 0 12 aabbccdd"))),
+ UnknownNSEC3HashAlgorithm);
+ EXPECT_THROW(NSEC3HashPtr(
+ NSEC3Hash::create(
+ generic::NSEC3("2 0 12 aabbccdd " +
+ string(nsec3_common)))),
+ UnknownNSEC3HashAlgorithm);
+
+ const uint8_t salt[] = {0xaa, 0xbb, 0xcc, 0xdd};
+ EXPECT_THROW(NSEC3HashPtr(NSEC3Hash::create(2, 12, salt, sizeof(salt))),
+ UnknownNSEC3HashAlgorithm);
+}
+
+// Common checks for NSEC3 hash calculation
+void
+calculateCheck(NSEC3Hash& hash) {
+ // A couple of normal cases from the RFC5155 example.
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(Name("example")));
+ EXPECT_EQ("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+ hash.calculate(Name("a.example")));
+
+ // Check case-insensitiveness
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(Name("EXAMPLE")));
+
+ // Repeat for the LabelSequence variant.
+
+ // A couple of normal cases from the RFC5155 example.
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(LabelSequence(Name("example"))));
+ EXPECT_EQ("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+ hash.calculate(LabelSequence(Name("a.example"))));
+
+ // Check case-insensitiveness
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ hash.calculate(LabelSequence(Name("EXAMPLE"))));
+}
+
+TEST_F(NSEC3HashTest, calculate) {
+ {
+ SCOPED_TRACE("calculate check with NSEC3PARAM based hash");
+ calculateCheck(*test_hash);
+ }
+ {
+ SCOPED_TRACE("calculate check with NSEC3 based hash");
+ calculateCheck(*test_hash_nsec3);
+ }
+ {
+ SCOPED_TRACE("calculate check with args based hash");
+ calculateCheck(*test_hash_args);
+ }
+
+ // Some boundary cases: 0-iteration and empty salt. Borrowed from the
+ // .com zone data.
+ EXPECT_EQ("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+ NSEC3HashPtr(NSEC3Hash::create(generic::NSEC3PARAM("1 0 0 -")))
+ ->calculate(Name("com")));
+ EXPECT_EQ("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+ NSEC3HashPtr(NSEC3Hash::create(generic::NSEC3PARAM("1 0 0 -")))
+ ->calculate(LabelSequence(Name("com"))));
+
+ // Using unusually large iterations, something larger than the 8-bit range.
+ // (expected hash value generated by BIND 9's dnssec-signzone)
+ EXPECT_EQ("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
+ NSEC3HashPtr(NSEC3Hash::create(
+ generic::NSEC3PARAM("1 0 256 AABBCCDD")))
+ ->calculate(LabelSequence(Name("example.org"))));
+}
+
+// Common checks for match cases
+template <typename RDATAType>
+void
+matchCheck(NSEC3Hash& hash, const string& postfix) {
+ // If all parameters match, it's considered to be matched.
+ EXPECT_TRUE(hash.match(RDATAType("1 0 12 aabbccdd" + postfix)));
+
+ // Algorithm doesn't match
+ EXPECT_FALSE(hash.match(RDATAType("2 0 12 aabbccdd" + postfix)));
+ // Iterations doesn't match
+ EXPECT_FALSE(hash.match(RDATAType("1 0 1 aabbccdd" + postfix)));
+ // Salt doesn't match
+ EXPECT_FALSE(hash.match(RDATAType("1 0 12 aabbccde" + postfix)));
+ // Salt doesn't match: the other has an empty salt
+ EXPECT_FALSE(hash.match(RDATAType("1 0 12 -" + postfix)));
+ // Flags don't matter
+ EXPECT_TRUE(hash.match(RDATAType("1 1 12 aabbccdd" + postfix)));
+}
+
+TEST_F(NSEC3HashTest, matchWithNSEC3) {
+ {
+ SCOPED_TRACE("match NSEC3PARAM based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3>(*test_hash, " " + string(nsec3_common));
+ }
+ {
+ SCOPED_TRACE("match NSEC3 based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3>(*test_hash_nsec3,
+ " " + string(nsec3_common));
+ }
+}
+
+TEST_F(NSEC3HashTest, matchWithNSEC3PARAM) {
+ {
+ SCOPED_TRACE("match NSEC3PARAM based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3PARAM>(*test_hash, "");
+ }
+ {
+ SCOPED_TRACE("match NSEC3 based hash against NSEC3 parameters");
+ matchCheck<generic::NSEC3PARAM>(*test_hash_nsec3, "");
+ }
+}
+
+// A simple faked hash calculator and a dedicated creator for it.
+class TestNSEC3Hash : public NSEC3Hash {
+ virtual string calculate(const Name&) const {
+ return ("00000000000000000000000000000000");
+ }
+ virtual string calculate(const LabelSequence&) const {
+ return ("00000000000000000000000000000000");
+ }
+ virtual bool match(const generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const generic::NSEC3&) const {
+ return (true);
+ }
+};
+
+// This faked creator basically creates the faked calculator regardless of
+// the passed NSEC3PARAM or NSEC3. But if the most significant bit of flags
+// is set, it will behave like the default creator.
+class TestNSEC3HashCreator : public NSEC3HashCreator {
+public:
+ virtual NSEC3Hash* create(const generic::NSEC3PARAM& param) const {
+ if ((param.getFlags() & 0x80) != 0) {
+ return (default_creator_.create(param));
+ }
+ return (new TestNSEC3Hash);
+ }
+ virtual NSEC3Hash* create(const generic::NSEC3& nsec3) const {
+ if ((nsec3.getFlags() & 0x80) != 0) {
+ return (default_creator_.create(nsec3));
+ }
+ return (new TestNSEC3Hash);
+ }
+ virtual NSEC3Hash* create(uint8_t, uint16_t,
+ const uint8_t*, size_t) const {
+ isc_throw(isc::Unexpected,
+ "This method is not implemented here.");
+ }
+private:
+ DefaultNSEC3HashCreator default_creator_;
+};
+
+TEST_F(NSEC3HashTest, setCreator) {
+ // Re-check an existing case using the default creator/hash implementation
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(LabelSequence(Name("example"))));
+
+ // Replace the creator, and confirm the hash values are faked
+ TestNSEC3HashCreator test_creator;
+ setNSEC3HashCreator(&test_creator);
+ // Re-create the hash object with the new creator
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+ EXPECT_EQ("00000000000000000000000000000000",
+ test_hash->calculate(Name("example")));
+ EXPECT_EQ("00000000000000000000000000000000",
+ test_hash->calculate(LabelSequence(Name("example"))));
+ // Same for hash from NSEC3 RDATA
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3
+ ("1 0 12 aabbccdd " +
+ string(nsec3_common))));
+ EXPECT_EQ("00000000000000000000000000000000",
+ test_hash->calculate(Name("example")));
+ EXPECT_EQ("00000000000000000000000000000000",
+ test_hash->calculate(LabelSequence(Name("example"))));
+
+ // If we set a special flag big (0x80) on creation, it will act like the
+ // default creator.
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM(
+ "1 128 12 aabbccdd")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(LabelSequence(Name("example"))));
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3
+ ("1 128 12 aabbccdd " +
+ string(nsec3_common))));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(LabelSequence(Name("example"))));
+
+ // Reset the creator to default, and confirm that
+ setNSEC3HashCreator(NULL);
+ test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(Name("example")));
+ EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+ test_hash->calculate(LabelSequence(Name("example"))));
+}
+
+} // end namespace
diff --git a/src/lib/dns/tests/opcode_unittest.cc b/src/lib/dns/tests/opcode_unittest.cc
new file mode 100644
index 0000000..9bc60b5
--- /dev/null
+++ b/src/lib/dns/tests/opcode_unittest.cc
@@ -0,0 +1,100 @@
+// 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 <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/qid_gen_unittest.cc b/src/lib/dns/tests/qid_gen_unittest.cc
new file mode 100644
index 0000000..20a2dfa
--- /dev/null
+++ b/src/lib/dns/tests/qid_gen_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright (C) 2011-2021 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/.
+
+/// \brief Test of QidGenerator
+///
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/qid_gen.h>
+
+using namespace isc::dns;
+
+// Tests the operation of the Qid generator
+
+// Check that getInstance returns a singleton
+TEST(QidGenerator, singleton) {
+ QidGenerator& g1 = QidGenerator::getInstance();
+ QidGenerator& g2 = QidGenerator::getInstance();
+
+ EXPECT_TRUE(&g1 == &g2);
+}
+
+TEST(QidGenerator, generate) {
+ // We'll assume that cryptolink's generator is 'good enough', and won't
+ // do full statistical checking here. Let's just call it the xkcd
+ // test (http://xkcd.com/221/), and check if three consecutive
+ // generates are not all the same.
+ uint16_t one, two, three;
+ QidGenerator& gen = QidGenerator::getInstance();
+ one = gen.generateQid();
+ two = gen.generateQid();
+ three = gen.generateQid();
+ ASSERT_FALSE((one == two) && (one == three));
+}
diff --git a/src/lib/dns/tests/question_unittest.cc b/src/lib/dns/tests/question_unittest.cc
new file mode 100644
index 0000000..03d31b1
--- /dev/null
+++ b/src/lib/dns/tests/question_unittest.cc
@@ -0,0 +1,196 @@
+// 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 <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"), b("b");
+ const RRClass in(RRClass::IN()), ch(RRClass::CH());
+ const RRType ns(RRType::NS()), 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..220248f
--- /dev/null
+++ b/src/lib/dns/tests/rcode_unittest.cc
@@ -0,0 +1,126 @@
+// 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 <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_afsdb_unittest.cc b/src/lib/dns/tests/rdata_afsdb_unittest.cc
new file mode 100644
index 0000000..26c3135
--- /dev/null
+++ b/src/lib/dns/tests/rdata_afsdb_unittest.cc
@@ -0,0 +1,235 @@
+// Copyright (C) 2011-2019 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 std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+const char* const afsdb_text = "1 afsdb.example.com.";
+const char* const afsdb_text2 = "0 root.example.com.";
+const char* const too_long_label("012345678901234567890123456789"
+ "0123456789012345678901234567890123.");
+
+namespace {
+class Rdata_AFSDB_Test : public RdataTest {
+protected:
+ Rdata_AFSDB_Test() :
+ rdata_afsdb(string(afsdb_text)), rdata_afsdb2(string(afsdb_text2))
+ {}
+
+ const generic::AFSDB rdata_afsdb;
+ const generic::AFSDB rdata_afsdb2;
+ vector<uint8_t> expected_wire;
+};
+
+
+TEST_F(Rdata_AFSDB_Test, createFromText) {
+ EXPECT_EQ(1, rdata_afsdb.getSubtype());
+ EXPECT_EQ(Name("afsdb.example.com."), rdata_afsdb.getServer());
+
+ EXPECT_EQ(0, rdata_afsdb2.getSubtype());
+ EXPECT_EQ(Name("root.example.com."), rdata_afsdb2.getServer());
+}
+
+TEST_F(Rdata_AFSDB_Test, badText) {
+ // subtype is too large
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("99999999 afsdb.example.com."),
+ InvalidRdataText);
+ // incomplete text
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("10"), InvalidRdataText);
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("SPOON"), InvalidRdataText);
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("1root.example.com."), InvalidRdataText);
+ // number of fields (must be 2) is incorrect
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("10 afsdb. example.com."),
+ InvalidRdataText);
+ // No origin and relative
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("1 afsdb.example.com"),
+ MissingNameOrigin);
+ // bad name
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("1 afsdb.example.com." +
+ string(too_long_label)), TooLongLabel);
+}
+
+TEST_F(Rdata_AFSDB_Test, copy) {
+ const generic::AFSDB rdata_afsdb2(rdata_afsdb);
+ EXPECT_EQ(0, rdata_afsdb.compare(rdata_afsdb2));
+}
+
+TEST_F(Rdata_AFSDB_Test, assignment) {
+ generic::AFSDB copy((string(afsdb_text2)));
+ copy = rdata_afsdb;
+ EXPECT_EQ(0, copy.compare(rdata_afsdb));
+
+ // Check if the copied data is valid even after the original is deleted
+ generic::AFSDB* copy2 = new generic::AFSDB(rdata_afsdb);
+ generic::AFSDB copy3((string(afsdb_text2)));
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_afsdb));
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(0, copy.compare(rdata_afsdb));
+}
+
+TEST_F(Rdata_AFSDB_Test, createFromWire) {
+ // uncompressed names
+ EXPECT_EQ(0, rdata_afsdb.compare(
+ *rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+ "rdata_afsdb_fromWire1.wire")));
+ // compressed name
+ EXPECT_EQ(0, rdata_afsdb.compare(
+ *rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+ "rdata_afsdb_fromWire2.wire", 13)));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+ "rdata_afsdb_fromWire3.wire"),
+ InvalidRdataLength);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+ "rdata_afsdb_fromWire4.wire"),
+ InvalidRdataLength);
+ // bogus server name, the error should be detected in the name
+ // constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType::AFSDB(), RRClass::IN(),
+ "rdata_afsdb_fromWire5.wire"),
+ DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_AFSDB_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_afsdb.compare(
+ *test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ afsdb_text)));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ generic::AFSDB tmp = generic::AFSDB("1 afsdb2.example.org.");
+ EXPECT_EQ(0, tmp.compare(
+ *test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1 afsdb2")));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1root.example.com."));
+
+ // 65536 is larger than maximum possible subtype
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "65536 afsdb.example.com."));
+
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1 afsdb.example.com. extra."));
+}
+
+TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
+ // construct actual data
+ rdata_afsdb.toWire(obuffer);
+
+ // construct expected data
+ UnitTestUtil::readWireData("rdata_afsdb_toWire1.wire", expected_wire);
+
+ // then compare them
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ obuffer.getData(), obuffer.getLength());
+
+ // clear buffer for the next test
+ obuffer.clear();
+
+ // construct actual data
+ Name("example.com.").toWire(obuffer);
+ rdata_afsdb2.toWire(obuffer);
+
+ // construct expected data
+ UnitTestUtil::readWireData("rdata_afsdb_toWire2.wire", expected_wire);
+
+ // then compare them
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_AFSDB_Test, toWireRenderer) {
+ // similar to toWireBuffer, but names in RDATA could be compressed due to
+ // preceding names. Actually they must not be compressed according to
+ // RFC3597, and this test checks that.
+
+ // construct actual data
+ rdata_afsdb.toWire(renderer);
+
+ // construct expected data
+ UnitTestUtil::readWireData("rdata_afsdb_toWire1.wire", expected_wire);
+
+ // then compare them
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ renderer.getData(), renderer.getLength());
+
+ // clear renderer for the next test
+ renderer.clear();
+
+ // construct actual data
+ renderer.writeName(Name("example.com."));
+ rdata_afsdb2.toWire(renderer);
+
+ // construct expected data
+ UnitTestUtil::readWireData("rdata_afsdb_toWire2.wire", expected_wire);
+
+ // then compare them
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_AFSDB_Test, toText) {
+ EXPECT_EQ(afsdb_text, rdata_afsdb.toText());
+ EXPECT_EQ(afsdb_text2, rdata_afsdb2.toText());
+}
+
+TEST_F(Rdata_AFSDB_Test, compare) {
+ // check reflexivity
+ EXPECT_EQ(0, rdata_afsdb.compare(rdata_afsdb));
+
+ // name must be compared in case-insensitive manner
+ EXPECT_EQ(0, rdata_afsdb.compare(generic::AFSDB("1 "
+ "AFSDB.example.com.")));
+
+ const generic::AFSDB small1("10 afsdb.example.com.");
+ const generic::AFSDB large1("65535 afsdb.example.com.");
+ const generic::AFSDB large2("256 afsdb.example.com.");
+
+ // confirm these are compared as unsigned values
+ EXPECT_GT(0, rdata_afsdb.compare(large1));
+ EXPECT_LT(0, large1.compare(rdata_afsdb));
+
+ // confirm these are compared in network byte order
+ EXPECT_GT(0, small1.compare(large2));
+ EXPECT_LT(0, large2.compare(small1));
+
+ // another AFSDB whose server name is larger than that of rdata_afsdb.
+ const generic::AFSDB large3("256 zzzzz.example.com.");
+ EXPECT_GT(0, large2.compare(large3));
+ EXPECT_LT(0, large3.compare(large2));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_afsdb.compare(*rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_caa_unittest.cc b/src/lib/dns/tests/rdata_caa_unittest.cc
new file mode 100644
index 0000000..48b9076
--- /dev/null
+++ b/src/lib/dns/tests/rdata_caa_unittest.cc
@@ -0,0 +1,322 @@
+// Copyright (C) 2014-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 <algorithm>
+#include <string>
+
+#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/algorithm/string.hpp>
+
+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_CAA_Test : public RdataTest {
+protected:
+ Rdata_CAA_Test() :
+ caa_txt("0 issue \"ca.example.net\""),
+ rdata_caa(caa_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::CAA, isc::Exception, isc::Exception>(
+ rdata_str, rdata_caa, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::CAA, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_caa, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::CAA, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_caa, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::CAA, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_caa, true, false);
+ }
+
+ const string caa_txt;
+ const generic::CAA rdata_caa;
+};
+
+const uint8_t rdata_caa_wiredata[] = {
+ // flags
+ 0x00,
+ // tag length
+ 0x5,
+ // tag
+ 'i', 's', 's', 'u', 'e',
+ // value
+ 'c', 'a', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e',
+ '.', 'n', 'e', 't'
+};
+
+TEST_F(Rdata_CAA_Test, createFromText) {
+ // Basic test
+ checkFromText_None(caa_txt);
+
+ // With different spacing
+ checkFromText_None("0 issue \"ca.example.net\"");
+
+ // Combination of lowercase and uppercase
+ checkFromText_None("0 IssUE \"ca.example.net\"");
+
+ // string constructor throws if there's extra text,
+ // but lexer constructor doesn't
+ checkFromText_BadString(caa_txt + "\n" + caa_txt);
+
+ // Missing value field
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
+}
+
+TEST_F(Rdata_CAA_Test, fields) {
+ // Some of these may not be RFC conformant, but we relax the check
+ // in our code to work with other field values that may show up in
+ // the future.
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("1 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("2 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("3 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("128 issue \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("255 issue \"ca.example.net\""));
+
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 foo \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 bar \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 12345 \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 w0x1y2z3 \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 relaxed-too \"ca.example.net\""));
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 RELAXED.too \"ca.example.net\""));
+
+ // No value (this is redundant to the last test case in the
+ // .createFromText test
+ EXPECT_NO_THROW(const generic::CAA rdata_caa2("0 issue"));
+
+ // > 255 would be broken
+ EXPECT_THROW(const generic::CAA rdata_caa2("256 issue \"ca.example.net\""),
+ InvalidRdataText);
+
+ // Missing tag causes the value to be parsed as the tag field. As
+ // the tag field does not allow quoted strings, this throws.
+ EXPECT_THROW(const generic::CAA rdata_caa2("0 \"ca.example.net\""),
+ InvalidRdataText);
+
+ // Tag is too long
+ const std::string tag(256, 'a');
+ const std::string rdata_txt("0 " + tag + " \"ca.example.net\"");
+ EXPECT_THROW(const generic::CAA rdata_caa2(rdata_txt), InvalidRdataText);
+}
+
+TEST_F(Rdata_CAA_Test, characterStringValue) {
+ const generic::CAA rdata_caa_unquoted("0 issue ca.example.net");
+ EXPECT_EQ(0, rdata_caa_unquoted.compare(rdata_caa));
+
+ const generic::CAA rdata_caa_escape_X("0 issue ca.e\\xample.net");
+ EXPECT_EQ(0, rdata_caa_escape_X.compare(rdata_caa));
+
+ const generic::CAA rdata_caa_escape_DDD("0 issue ca.e\\120ample.net");
+ EXPECT_EQ(0, rdata_caa_escape_DDD.compare(rdata_caa));
+
+ const generic::CAA rdata_caa_multiline("0 issue (\nca.example.net)");
+ EXPECT_EQ(0, rdata_caa_multiline.compare(rdata_caa));
+}
+
+TEST_F(Rdata_CAA_Test, badText) {
+ checkFromText_LexerError("0");
+ checkFromText_LexerError("ZERO issue \"ca.example.net\"");
+ EXPECT_THROW(const generic::CAA rdata_caa2(caa_txt + " extra text"),
+ InvalidRdataText);
+
+ // Yes, this is redundant to the last test cases in the .fields test
+ checkFromText_InvalidText("2345 issue \"ca.example.net\"");
+
+ // negative values are trapped in the lexer rather than the
+ // constructor
+ checkFromText_LexerError("-2 issue \"ca.example.net\"");
+}
+
+TEST_F(Rdata_CAA_Test, copyAndAssign) {
+ // Copy construct
+ generic::CAA rdata_caa2(rdata_caa);
+ EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
+
+ // Assignment, mainly to confirm it doesn't cause disruption.
+ rdata_caa2 = rdata_caa;
+ EXPECT_EQ(0, rdata_caa.compare(rdata_caa2));
+}
+
+TEST_F(Rdata_CAA_Test, createFromWire) {
+ // Basic test
+ EXPECT_EQ(0, rdata_caa.compare(
+ *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire1.wire")));
+
+ // Combination of lowercase and uppercase
+ EXPECT_EQ(0, rdata_caa.compare(
+ *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire2.wire")));
+
+ // Value field is empty
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire3.wire"));
+
+ // Tag field is empty
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire4.wire"),
+ InvalidRdataText);
+
+ // Value field is shorter than rdata len
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire5"),
+ InvalidBufferPosition);
+
+ // all RDATA is missing
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire6"),
+ InvalidBufferPosition);
+}
+
+TEST_F(Rdata_CAA_Test, createFromParams) {
+ const generic::CAA rdata_caa2(0, "issue", "ca.example.net");
+ EXPECT_EQ(0, rdata_caa2.compare(rdata_caa));
+
+ const generic::CAA rdata_caa4(0, "issue", "ca.e\\xample.net");
+ EXPECT_EQ(0, rdata_caa4.compare(rdata_caa));
+
+ const generic::CAA rdata_caa5(0, "issue", "ca.e\\120ample.net");
+ EXPECT_EQ(0, rdata_caa5.compare(rdata_caa));
+
+ // Tag is empty
+ EXPECT_THROW(const generic::CAA rdata_caa3(0, "", "ca.example.net"),
+ isc::InvalidParameter);
+
+ // Tag is too long
+ const std::string tag(256, 'a');
+ EXPECT_THROW(const generic::CAA rdata_caa3(0, tag, "ca.example.net"),
+ isc::InvalidParameter);
+
+ // Value is too long
+ const std::string value(65536, 'a');
+ EXPECT_THROW(const generic::CAA rdata_caa3(0, "issue", value),
+ InvalidRdataLength);
+}
+
+TEST_F(Rdata_CAA_Test, toText) {
+ EXPECT_TRUE(boost::iequals(caa_txt, rdata_caa.toText()));
+
+ const string caa_txt2("1 issue \"\"");
+ const generic::CAA rdata_caa2(caa_txt2);
+ EXPECT_TRUE(boost::iequals(caa_txt2, rdata_caa2.toText()));
+}
+
+TEST_F(Rdata_CAA_Test, toWire) {
+ obuffer.clear();
+ rdata_caa.toWire(obuffer);
+
+ matchWireData(rdata_caa_wiredata, sizeof(rdata_caa_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_CAA_Test, compare) {
+ // Equality test is repeated from createFromWire tests above.
+ EXPECT_EQ(0, rdata_caa.compare(
+ *rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire1.wire")));
+
+ const generic::CAA rdata_caa2("1 issue \"ca.example.net\"");
+
+ EXPECT_EQ(1, rdata_caa2.compare(rdata_caa));
+ EXPECT_EQ(-1, rdata_caa.compare(rdata_caa2));
+}
+
+TEST_F(Rdata_CAA_Test, getFlags) {
+ EXPECT_EQ(0, rdata_caa.getFlags());
+}
+
+TEST_F(Rdata_CAA_Test, getTag) {
+ EXPECT_EQ("issue", rdata_caa.getTag());
+}
+
+TEST_F(Rdata_CAA_Test, getValue) {
+ const uint8_t value_data[] = {
+ 'c', 'a', '.',
+ 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.',
+ 'n', 'e', 't'
+ };
+
+ const std::vector<uint8_t>& value = rdata_caa.getValue();
+ matchWireData(value_data, sizeof(value_data),
+ &value[0], value.size());
+}
+
+TEST_F(Rdata_CAA_Test, emptyValueFromWire) {
+ const uint8_t rdf_wiredata[] = {
+ // flags
+ 0x00,
+ // tag length
+ 0x5,
+ // tag
+ 'i', 's', 's', 'u', 'e'
+ };
+
+ const generic::CAA rdf =
+ dynamic_cast<const generic::CAA&>
+ (*rdataFactoryFromFile(RRType("CAA"), RRClass("IN"),
+ "rdata_caa_fromWire3.wire"));
+
+ EXPECT_EQ(0, rdf.getFlags());
+ EXPECT_EQ("issue", rdf.getTag());
+
+ obuffer.clear();
+ rdf.toWire(obuffer);
+
+ matchWireData(rdf_wiredata, sizeof(rdf_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_CAA_Test, emptyValueFromString) {
+ const generic::CAA rdata_caa2("0 issue");
+ const uint8_t rdata_caa2_wiredata[] = {
+ // flags
+ 0x00,
+ // tag length
+ 0x5,
+ // tag
+ 'i', 's', 's', 'u', 'e'
+ };
+
+ EXPECT_EQ(0, rdata_caa2.getFlags());
+ EXPECT_EQ("issue", rdata_caa2.getTag());
+
+ obuffer.clear();
+ rdata_caa2.toWire(obuffer);
+
+ matchWireData(rdata_caa2_wiredata, sizeof(rdata_caa2_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+}
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..4ffeadb
--- /dev/null
+++ b/src/lib/dns/tests/rdata_char_string_data_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright (C) 2014-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 <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/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"},
+ {NULL, NULL}
+};
+
+TEST_F(CharStringDataTest, charStringDataToString) {
+ for (const TestData* cur = conversion_data; cur->data != NULL; ++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..f1618b5
--- /dev/null
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (C) 2012-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 <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/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"},
+ {NULL, NULL}
+};
+
+TEST_F(CharStringTest, charStringToString) {
+ for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+ uint8_t idata[32];
+ size_t length = std::strlen(cur->data);
+ // length (1 byte) + string (length bytes)
+ assert(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_cname_unittest.cc b/src/lib/dns/tests/rdata_cname_unittest.cc
new file mode 100644
index 0000000..e666355
--- /dev/null
+++ b/src/lib/dns/tests/rdata_cname_unittest.cc
@@ -0,0 +1,135 @@
+// 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 <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 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_CNAME_Test : public RdataTest {
+public:
+ Rdata_CNAME_Test() :
+ rdata_cname("cn.example.com."),
+ rdata_cname2("cn2.example.com.")
+ {}
+
+ const generic::CNAME rdata_cname;
+ const generic::CNAME rdata_cname2;
+};
+
+const uint8_t wiredata_cname[] = {
+ 0x02, 0x63, 0x6e, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00 };
+const uint8_t wiredata_cname2[] = {
+ // first name: cn.example.com.
+ 0x02, 0x63, 0x6e, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00,
+ // second name: cn2.example.com. all labels except the first should be
+ // compressed.
+ 0x03, 0x63, 0x6e, 0x32, 0xc0, 0x03 };
+
+TEST_F(Rdata_CNAME_Test, createFromText) {
+ EXPECT_EQ(0, rdata_cname.compare(generic::CNAME("cn.example.com.")));
+ // explicitly add a trailing dot. should be the same RDATA.
+ EXPECT_EQ(0, rdata_cname.compare(generic::CNAME("cn.example.com.")));
+ // should be case sensitive.
+ EXPECT_EQ(0, rdata_cname.compare(generic::CNAME("CN.EXAMPLE.COM.")));
+ // RDATA of a class-independent type should be recognized for any
+ // "unknown" class.
+ EXPECT_EQ(0, rdata_cname.compare(*createRdata(RRType("CNAME"),
+ RRClass(65000),
+ "cn.example.com.")));
+}
+
+TEST_F(Rdata_CNAME_Test, badText) {
+ // Extra text at end of line
+ EXPECT_THROW(generic::CNAME("cname.example.com. extra."), InvalidRdataText);
+}
+
+TEST_F(Rdata_CNAME_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_cname.compare(
+ *rdataFactoryFromFile(RRType("CNAME"), RRClass("IN"),
+ "rdata_cname_fromWire")));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CNAME"), RRClass("IN"),
+ "rdata_cname_fromWire", 18),
+ InvalidRdataLength);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CNAME"), RRClass("IN"),
+ "rdata_cname_fromWire", 36),
+ InvalidRdataLength);
+ // incomplete name. the error should be detected in the name constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType("CNAME"), RRClass("IN"),
+ "rdata_cname_fromWire", 71),
+ DNSMessageFORMERR);
+
+ EXPECT_EQ(0, generic::CNAME("cn2.example.com.").compare(
+ *rdataFactoryFromFile(RRType("CNAME"), RRClass("IN"),
+ "rdata_cname_fromWire", 55)));
+ EXPECT_THROW(*rdataFactoryFromFile(RRType("CNAME"), RRClass("IN"),
+ "rdata_cname_fromWire", 63),
+ InvalidRdataLength);
+}
+
+TEST_F(Rdata_CNAME_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_cname.compare(
+ *test::createRdataUsingLexer(RRType::CNAME(), RRClass::IN(),
+ "cn.example.com.")));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ EXPECT_EQ(0, generic::CNAME("cname10.example.org.").compare(
+ *test::createRdataUsingLexer(RRType::CNAME(), RRClass::IN(),
+ "cname10")));
+
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::CNAME(), RRClass::IN(),
+ "cname.example.com. extra."));
+}
+
+TEST_F(Rdata_CNAME_Test, toWireBuffer) {
+ rdata_cname.toWire(obuffer);
+ matchWireData(wiredata_cname, sizeof(wiredata_cname),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_CNAME_Test, toWireRenderer) {
+ rdata_cname.toWire(renderer);
+ matchWireData(wiredata_cname, sizeof(wiredata_cname),
+ renderer.getData(), renderer.getLength());
+
+ rdata_cname2.toWire(renderer);
+ matchWireData(wiredata_cname2, sizeof(wiredata_cname2),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_CNAME_Test, toText) {
+ EXPECT_EQ("cn.example.com.", rdata_cname.toText());
+}
+
+TEST_F(Rdata_CNAME_Test, getCname) {
+ EXPECT_EQ(Name("cn.example.com."), rdata_cname.getCname());
+}
+}
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..a96c3e4
--- /dev/null
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -0,0 +1,165 @@
+// 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 <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/rdataclass.h>
+#include <util/encode/base64.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(NULL, 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_dname_unittest.cc b/src/lib/dns/tests/rdata_dname_unittest.cc
new file mode 100644
index 0000000..3bdfb23
--- /dev/null
+++ b/src/lib/dns/tests/rdata_dname_unittest.cc
@@ -0,0 +1,137 @@
+// 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 <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 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_DNAME_Test : public RdataTest {
+public:
+ Rdata_DNAME_Test() :
+ rdata_dname("dn.example.com."),
+ rdata_dname2("dn2.example.com.")
+ {}
+
+ const generic::DNAME rdata_dname;
+ const generic::DNAME rdata_dname2;
+};
+
+const uint8_t wiredata_dname[] = {
+ 0x02, 0x64, 0x6e, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00 };
+const uint8_t wiredata_dname2[] = {
+ // first name: dn.example.com.
+ 0x02, 0x64, 0x6e, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00,
+ // second name: dn2.example.com. The "example.com" shouldn't be compressed
+ // because DNAME is not a well know type per RFC3597.
+ 0x03, 0x64, 0x6e, 0x32,
+ 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
+ 0x63, 0x6f, 0x6d, 0x00 };
+
+TEST_F(Rdata_DNAME_Test, createFromText) {
+ EXPECT_EQ(0, rdata_dname.compare(generic::DNAME("dn.example.com.")));
+ // explicitly add a trailing dot. should be the same RDATA.
+ EXPECT_EQ(0, rdata_dname.compare(generic::DNAME("dn.example.com.")));
+ // should be case sensitive.
+ EXPECT_EQ(0, rdata_dname.compare(generic::DNAME("DN.EXAMPLE.COM.")));
+ // RDATA of a class-independent type should be recognized for any
+ // "unknown" class.
+ EXPECT_EQ(0, rdata_dname.compare(*createRdata(RRType("DNAME"),
+ RRClass(65000),
+ "dn.example.com.")));
+}
+
+TEST_F(Rdata_DNAME_Test, badText) {
+ // Extra text at end of line
+ EXPECT_THROW(generic::DNAME("dname.example.com. extra."), InvalidRdataText);
+}
+
+TEST_F(Rdata_DNAME_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_dname.compare(
+ *rdataFactoryFromFile(RRType("DNAME"), RRClass("IN"),
+ "rdata_dname_fromWire")));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType("DNAME"), RRClass("IN"),
+ "rdata_dname_fromWire", 18),
+ InvalidRdataLength);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType("DNAME"), RRClass("IN"),
+ "rdata_dname_fromWire", 36),
+ InvalidRdataLength);
+ // incomplete name. the error should be detected in the name constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType("DNAME"), RRClass("IN"),
+ "rdata_dname_fromWire", 71),
+ DNSMessageFORMERR);
+
+ EXPECT_EQ(0, generic::DNAME("dn2.example.com.").compare(
+ *rdataFactoryFromFile(RRType("DNAME"), RRClass("IN"),
+ "rdata_dname_fromWire", 55)));
+ EXPECT_THROW(*rdataFactoryFromFile(RRType("DNAME"), RRClass("IN"),
+ "rdata_dname_fromWire", 63),
+ InvalidRdataLength);
+}
+
+TEST_F(Rdata_DNAME_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_dname.compare(
+ *test::createRdataUsingLexer(RRType::DNAME(), RRClass::IN(),
+ "dn.example.com.")));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ EXPECT_EQ(0, generic::DNAME("dname8.example.org.").compare(
+ *test::createRdataUsingLexer(RRType::DNAME(), RRClass::IN(),
+ "dname8")));
+
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::DNAME(), RRClass::IN(),
+ "dname.example.com. extra."));
+}
+
+TEST_F(Rdata_DNAME_Test, toWireBuffer) {
+ rdata_dname.toWire(obuffer);
+ matchWireData(wiredata_dname, sizeof(wiredata_dname),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_DNAME_Test, toWireRenderer) {
+ rdata_dname.toWire(renderer);
+ matchWireData(wiredata_dname, sizeof(wiredata_dname),
+ renderer.getData(), renderer.getLength());
+
+ rdata_dname2.toWire(renderer);
+ matchWireData(wiredata_dname2, sizeof(wiredata_dname2),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_DNAME_Test, toText) {
+ EXPECT_EQ("dn.example.com.", rdata_dname.toText());
+}
+
+TEST_F(Rdata_DNAME_Test, getDname) {
+ EXPECT_EQ(Name("dn.example.com."), rdata_dname.getDname());
+}
+}
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
new file mode 100644
index 0000000..6f0866d
--- /dev/null
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -0,0 +1,200 @@
+// 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 <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 <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_DNSKEY_Test : public RdataTest {
+protected:
+ Rdata_DNSKEY_Test() :
+ dnskey_txt("257 3 5 BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMV"
+ "Fu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/x"
+ "ylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/"
+ "Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/"
+ "4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj"
+ "0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ"
+ "7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA"
+ "8lVUgEf/rzeC/bByBNsO70aEFTd"),
+ dnskey_txt2("257 3 5 YmluZDEwLmlzYy5vcmc="),
+ rdata_dnskey(dnskey_txt),
+ rdata_dnskey2(dnskey_txt2)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, isc::Exception, isc::Exception>(
+ rdata_str, rdata_dnskey2, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_InvalidLength(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, InvalidRdataLength, InvalidRdataLength>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, BadValue, BadValue>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::DNSKEY, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::DNSKEY, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_dnskey2, true, false);
+ }
+
+ const string dnskey_txt;
+ const string dnskey_txt2;
+ const generic::DNSKEY rdata_dnskey;
+ const generic::DNSKEY rdata_dnskey2;
+};
+
+TEST_F(Rdata_DNSKEY_Test, fromText) {
+ EXPECT_EQ(dnskey_txt, rdata_dnskey.toText());
+
+ // Space in key data is OK
+ checkFromText_None("257 3 5 YmluZDEw LmlzYy5vcmc=");
+
+ // Delimited number in key data is OK
+ checkFromText_None("257 3 5 YmluZDEwLmlzYy 5 vcmc=");
+
+ // Missing keydata is OK
+ EXPECT_NO_THROW(const generic::DNSKEY rdata_dnskey3("257 3 5"));
+
+ // Key data too short for RSA/MD5 algorithm is OK when
+ // constructing. But getTag() on this object would throw (see
+ // .getTag tests).
+ EXPECT_NO_THROW(const generic::DNSKEY rdata_dnskey4("1 1 1 YQ=="));
+
+ // Flags field out of range
+ checkFromText_InvalidText("65536 3 5 YmluZDEwLmlzYy5vcmc=");
+
+ // Protocol field out of range
+ checkFromText_InvalidText("257 256 5 YmluZDEwLmlzYy5vcmc=");
+
+ // Algorithm field out of range
+ checkFromText_InvalidText("257 3 256 YmluZDEwLmlzYy5vcmc=");
+
+ // Missing algorithm field
+ checkFromText_LexerError("257 3 YmluZDEwLmlzYy5vcmc=");
+
+ // Invalid key data field (not Base64)
+ checkFromText_BadValue("257 3 5 BAAAAAAAAAAAD");
+
+ // String instead of number
+ checkFromText_LexerError("foo 3 5 YmluZDEwLmlzYy5vcmc=");
+ checkFromText_LexerError("257 foo 5 YmluZDEwLmlzYy5vcmc=");
+ checkFromText_LexerError("257 3 foo YmluZDEwLmlzYy5vcmc=");
+
+ // 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("257 3 5 YmluZDEwLmlzYy5vcmc= ; comment\n"
+ "257 3 4 YmluZDEwLmlzYy5vcmc=");
+
+ // Unmatched parenthesis should cause a lexer error
+ checkFromText_LexerError("257 3 5 )YmluZDEwLmlzYy5vcmc=");
+}
+
+TEST_F(Rdata_DNSKEY_Test, assign) {
+ generic::DNSKEY rdata_dnskey2("257 3 5 YQ==");
+ rdata_dnskey2 = rdata_dnskey;
+ EXPECT_EQ(0, rdata_dnskey.compare(rdata_dnskey2));
+}
+
+TEST_F(Rdata_DNSKEY_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_dnskey.compare(
+ *test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
+ dnskey_txt)));
+}
+
+TEST_F(Rdata_DNSKEY_Test, toWireRenderer) {
+ renderer.skip(2);
+ rdata_dnskey.toWire(renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
+ matchWireData(&data[2], data.size() - 2,
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2);
+}
+
+TEST_F(Rdata_DNSKEY_Test, toWireBuffer) {
+ rdata_dnskey.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
+ matchWireData(&data[2], data.size() - 2,
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_DNSKEY_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_dnskey.compare(
+ *rdataFactoryFromFile(RRType("DNSKEY"), RRClass("IN"),
+ "rdata_dnskey_fromWire.wire")));
+
+ // Missing keydata is OK
+ const generic::DNSKEY rdata_dnskey_missing_keydata("257 3 5");
+ EXPECT_EQ(0, rdata_dnskey_missing_keydata.compare(
+ *rdataFactoryFromFile(RRType("DNSKEY"), RRClass("IN"),
+ "rdata_dnskey_empty_keydata_fromWire.wire")));
+}
+
+TEST_F(Rdata_DNSKEY_Test, getTag) {
+ EXPECT_EQ(12892, rdata_dnskey.getTag());
+
+ // Short keydata with algorithm RSA/MD5 must throw.
+ const generic::DNSKEY rdata_dnskey_short_keydata1("1 1 1 YQ==");
+ EXPECT_THROW(rdata_dnskey_short_keydata1.getTag(), isc::OutOfRange);
+
+ // Short keydata with algorithm not RSA/MD5 must not throw.
+ const generic::DNSKEY rdata_dnskey_short_keydata2("257 3 5 YQ==");
+ EXPECT_NO_THROW(rdata_dnskey_short_keydata2.getTag());
+}
+
+TEST_F(Rdata_DNSKEY_Test, getAlgorithm) {
+ EXPECT_EQ(5, rdata_dnskey.getAlgorithm());
+}
+
+TEST_F(Rdata_DNSKEY_Test, getFlags) {
+ EXPECT_EQ(257, rdata_dnskey.getFlags());
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
new file mode 100644
index 0000000..d4ea924
--- /dev/null
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -0,0 +1,229 @@
+// Copyright (C) 2011-2019 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 <algorithm>
+#include <string>
+
+#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 {
+// hacks to make templates work
+template <class T>
+class RRTYPE : public RRType {
+public:
+ RRTYPE();
+};
+
+template<> RRTYPE<generic::DS>::RRTYPE() : RRType(RRType::DS()) {}
+template<> RRTYPE<generic::DLV>::RRTYPE() : RRType(RRType::DLV()) {}
+
+template <class DS_LIKE>
+class Rdata_DS_LIKE_Test : public RdataTest {
+protected:
+ Rdata_DS_LIKE_Test() :
+ ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5"),
+ rdata_ds_like(ds_like_txt)
+ {}
+ const string ds_like_txt;
+ const DS_LIKE rdata_ds_like;
+};
+
+// The list of types we want to test.
+typedef testing::Types<generic::DS, generic::DLV> Implementations;
+
+#ifdef TYPED_TEST_SUITE
+TYPED_TEST_SUITE(Rdata_DS_LIKE_Test, Implementations);
+#else
+TYPED_TEST_CASE(Rdata_DS_LIKE_Test, Implementations);
+#endif
+
+TYPED_TEST(Rdata_DS_LIKE_Test, createFromText) {
+ // It's valid for the digest's presentation format to contain
+ // spaces. See RFC4034 section 5.3.
+ EXPECT_EQ(0, this->rdata_ds_like.compare(
+ TypeParam("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D5F0EB5"
+ "C777 58 6DE18 \t DA6B5")));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toText_DS_LIKE) {
+ EXPECT_EQ(this->ds_like_txt, this->rdata_ds_like.toText());
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, badText_DS_LIKE) {
+ EXPECT_THROW(const TypeParam ds_like2("99999 5 2 BEEF"), InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("11111 555 2 BEEF"),
+ InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("11111 5 22222 BEEF"),
+ InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("11111 5 2"), InvalidRdataText);
+ EXPECT_THROW(const TypeParam ds_like2("GARBAGE IN"), InvalidRdataText);
+ // no space between the digest type and the digest.
+ EXPECT_THROW(const TypeParam ds_like2(
+ "12892 5 2F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5"), InvalidRdataText);
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
+ EXPECT_EQ(0, this->rdata_ds_like.compare(
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass::IN(),
+ "rdata_ds_fromWire")));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
+ EXPECT_EQ(0, this->rdata_ds_like.compare(
+ *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ this->ds_like_txt)));
+
+ // Whitespace is okay
+ EXPECT_EQ(0, this->rdata_ds_like.compare(
+ *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ "12892 5 2 F1E184C0E1D615D20EB3C223ACED3B0"
+ "3C773DD952D5F0EB5C777 58 6DE18 \t DA6B5"
+ )));
+
+ // Exceptions cause NULL to be returned.
+
+ // Bad tag
+ EXPECT_FALSE(test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ "65536 5 2 BEEF"));
+
+ // Bad algorithm
+ EXPECT_FALSE(test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ "1024 256 2 BEEF"));
+
+ // Bad digest type
+ EXPECT_FALSE(test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ "2048 2 256 BEEF"));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
+ TypeParam copy(this->ds_like_txt);
+ copy = this->rdata_ds_like;
+ EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
+
+ // Check if the copied data is valid even after the original is deleted
+ TypeParam* copy2 = new TypeParam(this->rdata_ds_like);
+ TypeParam copy3(this->ds_like_txt);
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(this->rdata_ds_like));
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, getTag_DS_LIKE) {
+ EXPECT_EQ(12892, this->rdata_ds_like.getTag());
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
+ Rdata_DS_LIKE_Test<TypeParam>::renderer.skip(2);
+ TypeParam rdata_ds_like(this->ds_like_txt);
+ rdata_ds_like.toWire(this->renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_ds_fromWire", data);
+ matchWireData(&data[2], data.size() - 2,
+ static_cast<const uint8_t*>(this->renderer.getData()) + 2,
+ this->renderer.getLength() - 2);
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toWireBuffer) {
+ TypeParam rdata_ds_like(this->ds_like_txt);
+ rdata_ds_like.toWire(this->obuffer);
+}
+
+string ds_like_txt1("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different tag
+string ds_like_txt2("12893 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different algorithm
+string ds_like_txt3("12892 6 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different digest type
+string ds_like_txt4("12892 5 3 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different digest
+string ds_like_txt5("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+// different digest length
+string ds_like_txt6("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B555");
+
+TYPED_TEST(Rdata_DS_LIKE_Test, compare) {
+ const string ds_like_txt1(
+ "12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different tag
+ const string ds_like_txt2(
+ "12893 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different algorithm
+ const string ds_like_txt3(
+ "12892 6 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest type
+ const string ds_like_txt4(
+ "12892 5 3 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest
+ const string ds_like_txt5(
+ "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest length
+ const string ds_like_txt6(
+ "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B555");
+
+ // trivial case: self equivalence
+ EXPECT_EQ(0, TypeParam(this->ds_like_txt).
+ compare(TypeParam(this->ds_like_txt)));
+
+ // non-equivalence tests
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt2)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt2).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt3)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt3).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt4)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt4).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt5)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt5).compare(TypeParam(ds_like_txt1)), 0);
+
+ EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt6)), 0);
+ EXPECT_GT(TypeParam(ds_like_txt6).compare(TypeParam(ds_like_txt1)), 0);
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(this->rdata_ds_like.compare(*this->rdata_nomatch),
+ bad_cast);
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
new file mode 100644
index 0000000..1830f05
--- /dev/null
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -0,0 +1,154 @@
+// 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 <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 std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using namespace isc::dns::rdata::generic;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_HINFO_Test : public RdataTest {
+};
+
+static uint8_t hinfo_rdata[] = {0x07,0x50,0x65,0x6e,0x74,0x69,0x75,0x6d,0x05,
+ 0x4c,0x69,0x6e,0x75,0x78};
+static const char *hinfo_str = "\"Pentium\" \"Linux\"";
+static const char *hinfo_str1 = "\"Pen\\\"tium\" \"Linux\"";
+
+static const char *hinfo_str_equal = "\"Pentium\"\"Linux\"";
+static const char *hinfo_str_small1 = "\"Lentium\" \"Linux\"";
+static const char *hinfo_str_small2 = "\"Pentium\" \"Kinux\"";
+static const char *hinfo_str_large1 = "\"Qentium\" \"Linux\"";
+static const char *hinfo_str_large2 = "\"Pentium\" \"UNIX\"";
+
+TEST_F(Rdata_HINFO_Test, createFromText) {
+ HINFO hinfo(hinfo_str);
+ EXPECT_EQ(string("Pentium"), hinfo.getCPU());
+ EXPECT_EQ(string("Linux"), hinfo.getOS());
+ // Test the text with double quotes in the middle of string
+ HINFO hinfo1(hinfo_str1);
+ EXPECT_EQ(string("Pen\\\"tium"), hinfo1.getCPU());
+}
+
+TEST_F(Rdata_HINFO_Test, badText) {
+ // Only 2 fields must exist
+ EXPECT_THROW(const HINFO hinfo("\"Pentium\"\"Linux\"\"Computer\""),
+ InvalidRdataText);
+ EXPECT_THROW(const HINFO hinfo("\"Pentium\" \"Linux\" \"Computer\""),
+ InvalidRdataText);
+ // Field cannot be missing
+ EXPECT_THROW(const HINFO hinfo("Pentium"), InvalidRdataText);
+ // The <character-string> cannot exceed 255 characters
+ string hinfo_str;
+ for (int i = 0; i < 257; ++i) {
+ hinfo_str += 'A';
+ }
+ hinfo_str += " Linux";
+ EXPECT_THROW(const HINFO hinfo(hinfo_str), CharStringTooLong);
+}
+
+TEST_F(Rdata_HINFO_Test, createFromWire) {
+ InputBuffer input_buffer(hinfo_rdata, sizeof(hinfo_rdata));
+ HINFO hinfo(input_buffer, sizeof(hinfo_rdata));
+ EXPECT_EQ(string("Pentium"), hinfo.getCPU());
+ EXPECT_EQ(string("Linux"), hinfo.getOS());
+}
+
+TEST_F(Rdata_HINFO_Test, createFromLexer) {
+ HINFO rdata_hinfo(hinfo_str);
+ EXPECT_EQ(0, rdata_hinfo.compare(
+ *test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ hinfo_str)));
+ EXPECT_EQ(0, rdata_hinfo.compare(
+ *test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\"\"Linux\"")));
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\"\"Linux\""
+ "\"Computer\""));
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\" \"Linux\" "
+ "\"Computer\""));
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\""));
+}
+
+TEST_F(Rdata_HINFO_Test, toText) {
+ HINFO hinfo(hinfo_str);
+ EXPECT_EQ(hinfo_str, hinfo.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("\"a\" \"b\"", HINFO("a b").toText());
+ // will not add additional quotes
+ EXPECT_EQ("\"a\" \"b\"", HINFO("\"a\" \"b\"").toText());
+ // And make sure escaped quotes and spaces are left intact
+ EXPECT_EQ("\"a\\\"\" \"b c\"", HINFO("\"a\\\"\" \"b c\"").toText());
+}
+
+TEST_F(Rdata_HINFO_Test, toWire) {
+ HINFO hinfo(hinfo_str);
+
+ hinfo.toWire(obuffer);
+ matchWireData(hinfo_rdata, sizeof (hinfo_rdata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_HINFO_Test, toWireRenderer) {
+ HINFO hinfo(hinfo_str);
+
+ hinfo.toWire(renderer);
+ matchWireData(hinfo_rdata, sizeof (hinfo_rdata),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_HINFO_Test, compare) {
+ HINFO hinfo(hinfo_str);
+ HINFO hinfo_small1(hinfo_str_small1);
+ HINFO hinfo_small2(hinfo_str_small2);
+ HINFO hinfo_large1(hinfo_str_large1);
+ HINFO hinfo_large2(hinfo_str_large2);
+
+ EXPECT_EQ(0, hinfo.compare(HINFO(hinfo_str)));
+ EXPECT_EQ(0, hinfo.compare(HINFO(hinfo_str_equal)));
+ EXPECT_EQ(1, hinfo.compare(HINFO(hinfo_str_small1)));
+ EXPECT_EQ(1, hinfo.compare(HINFO(hinfo_str_small2)));
+ EXPECT_EQ(-1, hinfo.compare(HINFO(hinfo_str_large1)));
+ EXPECT_EQ(-1, hinfo.compare(HINFO(hinfo_str_large2)));
+}
+
+// Copy/assign test
+TEST_F(Rdata_HINFO_Test, copy) {
+ HINFO hinfo(hinfo_str);
+ HINFO hinfo2(hinfo);
+ HINFO hinfo3 = hinfo;
+
+ EXPECT_EQ(0, hinfo.compare(hinfo2));
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+
+ hinfo3 = hinfo;
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+}
+
+}
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..809cfb5
--- /dev/null
+++ b/src/lib/dns/tests/rdata_in_a_unittest.cc
@@ -0,0 +1,159 @@
+// 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/rdataclass.h>
+
+#include <util/buffer.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 <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.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..cc7b07d
--- /dev/null
+++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
@@ -0,0 +1,151 @@
+// 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 <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 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_minfo_unittest.cc b/src/lib/dns/tests/rdata_minfo_unittest.cc
new file mode 100644
index 0000000..35ff55c
--- /dev/null
+++ b/src/lib/dns/tests/rdata_minfo_unittest.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2011-2019 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 <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 std;
+using namespace isc::util;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_MINFO_Test : public RdataTest {
+protected:
+ Rdata_MINFO_Test():
+ minfo_txt("rmailbox.example.com. emailbox.example.com."),
+ minfo_txt2("root.example.com. emailbox.example.com."),
+ too_long_label("01234567890123456789012345678901234567"
+ "89012345678901234567890123."),
+ rdata_minfo(minfo_txt),
+ rdata_minfo2(minfo_txt2)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::MINFO, isc::Exception, isc::Exception>(
+ rdata_str, rdata_minfo, false, false);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::MINFO, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_TooLongLabel(const string& rdata_str) {
+ checkFromText<generic::MINFO, TooLongLabel, TooLongLabel>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText<generic::MINFO, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_minfo, true, false);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<generic::MINFO, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::MINFO, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_Origin(const string& rdata_str, const Name* origin) {
+ checkFromText<generic::MINFO, MissingNameOrigin, isc::Exception>(
+ rdata_str, rdata_minfo, true, false, origin);
+ }
+
+ const string minfo_txt;
+ const string minfo_txt2;
+ const string too_long_label;
+ const generic::MINFO rdata_minfo;
+ const generic::MINFO rdata_minfo2;
+};
+
+
+TEST_F(Rdata_MINFO_Test, createFromText) {
+ EXPECT_EQ(Name("rmailbox.example.com."), rdata_minfo.getRmailbox());
+ EXPECT_EQ(Name("emailbox.example.com."), rdata_minfo.getEmailbox());
+
+ EXPECT_EQ(Name("root.example.com."), rdata_minfo2.getRmailbox());
+ EXPECT_EQ(Name("emailbox.example.com."), rdata_minfo2.getEmailbox());
+
+ checkFromText_None(minfo_txt);
+
+ // origin defined for lexer constructor, but not string constructor
+ const Name origin("example.com");
+ checkFromText_Origin("rmailbox emailbox", &origin);
+
+ // lexer constructor accepts extra text, but string constructor doesn't
+ checkFromText_BadString("rmailbox.example.com. emailbox.example.com. "
+ "extra.example.com.");
+}
+
+TEST_F(Rdata_MINFO_Test, badText) {
+ // too long names
+ checkFromText_TooLongLabel("root.example.com." + too_long_label +
+ " emailbox.example.com.");
+ checkFromText_TooLongLabel("root.example.com. emailbox.example.com." +
+ too_long_label);
+
+ // invalid names
+ checkFromText_EmptyLabel("root..example.com. emailbox.example.com.");
+ checkFromText_EmptyLabel("root.example.com. emailbox..example.com.");
+
+ // missing name
+ checkFromText_LexerError("root.example.com.");
+
+ // missing origin
+ checkFromText_MissingOrigin("root.example.com emailbox.example.com.");
+ checkFromText_MissingOrigin("root.example.com. emailbox.example.com");
+}
+
+TEST_F(Rdata_MINFO_Test, createFromWire) {
+ // uncompressed names
+ EXPECT_EQ(0, rdata_minfo.compare(
+ *rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+ "rdata_minfo_fromWire1.wire")));
+ // compressed names
+ EXPECT_EQ(0, rdata_minfo.compare(
+ *rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+ "rdata_minfo_fromWire2.wire", 15)));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+ "rdata_minfo_fromWire3.wire"),
+ InvalidRdataLength);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+ "rdata_minfo_fromWire4.wire"),
+ InvalidRdataLength);
+ // bogus rmailbox name, the error should be detected in the name
+ // constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+ "rdata_minfo_fromWire5.wire"),
+ DNSMessageFORMERR);
+ // bogus emailbox name, the error should be detected in the name
+ // constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType::MINFO(), RRClass::IN(),
+ "rdata_minfo_fromWire6.wire"),
+ DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_MINFO_Test, assignment) {
+ generic::MINFO copy((string(minfo_txt2)));
+ copy = rdata_minfo;
+ EXPECT_EQ(0, copy.compare(rdata_minfo));
+
+ // Check if the copied data is valid even after the original is deleted
+ generic::MINFO* copy2 = new generic::MINFO(rdata_minfo);
+ generic::MINFO copy3((string(minfo_txt2)));
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_minfo));
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(0, copy.compare(rdata_minfo));
+}
+
+TEST_F(Rdata_MINFO_Test, toWireBuffer) {
+ rdata_minfo.toWire(obuffer);
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_minfo_toWireUncompressed1.wire", data);
+ matchWireData(&data[0], data.size(),
+ obuffer.getData(), obuffer.getLength());
+
+ obuffer.clear();
+ rdata_minfo2.toWire(obuffer);
+ vector<unsigned char> data2;
+ UnitTestUtil::readWireData("rdata_minfo_toWireUncompressed2.wire", data2);
+ matchWireData(&data2[0], data2.size(),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_MINFO_Test, toWireRenderer) {
+ rdata_minfo.toWire(renderer);
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_minfo_toWire1.wire", data);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+
+ renderer.clear();
+ rdata_minfo2.toWire(renderer);
+ vector<unsigned char> data2;
+ UnitTestUtil::readWireData("rdata_minfo_toWire2.wire", data2);
+ matchWireData(&data2[0], data2.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_MINFO_Test, toText) {
+ EXPECT_EQ(minfo_txt, rdata_minfo.toText());
+ EXPECT_EQ(minfo_txt2, rdata_minfo2.toText());
+}
+
+TEST_F(Rdata_MINFO_Test, compare) {
+ // check reflexivity
+ EXPECT_EQ(0, rdata_minfo.compare(rdata_minfo));
+
+ // names must be compared in case-insensitive manner
+ EXPECT_EQ(0, rdata_minfo.compare(generic::MINFO("RMAILBOX.example.com. "
+ "emailbox.EXAMPLE.com.")));
+
+ // another MINFO whose rmailbox name is larger than that of rdata_minfo.
+ const generic::MINFO large1_minfo("zzzzzzzz.example.com. "
+ "emailbox.example.com.");
+ EXPECT_GT(0, rdata_minfo.compare(large1_minfo));
+ EXPECT_LT(0, large1_minfo.compare(rdata_minfo));
+
+ // another MINFO whose emailbox name is larger than that of rdata_minfo.
+ const generic::MINFO large2_minfo("rmailbox.example.com. "
+ "zzzzzzzzzzz.example.com.");
+ EXPECT_GT(0, rdata_minfo.compare(large2_minfo));
+ EXPECT_LT(0, large2_minfo.compare(rdata_minfo));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_minfo.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_mx_unittest.cc b/src/lib/dns/tests/rdata_mx_unittest.cc
new file mode 100644
index 0000000..b11411f
--- /dev/null
+++ b/src/lib/dns/tests/rdata_mx_unittest.cc
@@ -0,0 +1,147 @@
+// 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 <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_MX_Test : public RdataTest {
+public:
+ Rdata_MX_Test() :
+ rdata_mx(10, Name("mx.example.com"))
+ {}
+
+ const generic::MX rdata_mx;
+};
+
+TEST_F(Rdata_MX_Test, createFromText) {
+ const generic::MX rdata_mx2("10 mx.example.com.");
+ EXPECT_EQ(0, rdata_mx2.compare(rdata_mx));
+}
+
+TEST_F(Rdata_MX_Test, badText) {
+ EXPECT_THROW(const generic::MX rdata_mx("99999999 mx."), InvalidRdataText);
+ EXPECT_THROW(const generic::MX rdata_mx("10"), InvalidRdataText);
+ EXPECT_THROW(const generic::MX rdata_mx("SPOON"), InvalidRdataText);
+ EXPECT_THROW(const generic::MX rdata_mx("10 mx. example.com."),
+ InvalidRdataText);
+ // No origin and relative
+ EXPECT_THROW(const generic::MX rdata_mx("10 mx.example.com"),
+ MissingNameOrigin);
+ // Extra text at end of line
+ EXPECT_THROW(const generic::MX rdata_mx("10 mx.example.com. extra."),
+ InvalidRdataText);
+}
+
+TEST_F(Rdata_MX_Test, copy) {
+ const generic::MX rdata_mx2(rdata_mx);
+ EXPECT_EQ(0, rdata_mx.compare(rdata_mx2));
+}
+
+TEST_F(Rdata_MX_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_mx.compare(
+ *rdataFactoryFromFile(RRType("MX"), RRClass("IN"),
+ "rdata_mx_fromWire")));
+ // TBD: more tests
+}
+
+TEST_F(Rdata_MX_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_mx.compare(
+ *test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+ "10 mx.example.com.")));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ EXPECT_EQ(0, generic::MX("10 mx2.example.org.").compare(
+ *test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+ "10 mx2")));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+ "10 mx. example.com."));
+
+ // 65536 is larger than maximum possible preference
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+ "65536 mx.example.com."));
+
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+ "10 mx.example.com. extra."));
+}
+
+TEST_F(Rdata_MX_Test, toWireRenderer) {
+ renderer.writeName(Name("example.com"));
+ rdata_mx.toWire(renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_mx_toWire1", data);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_MX_Test, toWireBuffer) {
+ Name("example.com").toWire(obuffer);
+ rdata_mx.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_mx_toWire2", data);
+ matchWireData(&data[0], data.size(),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_MX_Test, toText) {
+ EXPECT_EQ("10 mx.example.com.", rdata_mx.toText());
+}
+
+TEST_F(Rdata_MX_Test, getMXName) {
+ EXPECT_EQ(Name("mx.example.com."), rdata_mx.getMXName());
+}
+
+TEST_F(Rdata_MX_Test, getMXPref) {
+ EXPECT_EQ(10, rdata_mx.getMXPref());
+}
+
+TEST_F(Rdata_MX_Test, compare) {
+ generic::MX small1(1, Name("mx.example.com"));
+ generic::MX small2(10, Name("mx.example.com"));
+ generic::MX large1(65535, Name("mx.example.com"));
+ generic::MX large2(256, Name("mx.example.com"));
+
+ // 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_mx.compare(*rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
new file mode 100644
index 0000000..4ce008e
--- /dev/null
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -0,0 +1,239 @@
+// Copyright (C) 2011-2015,2021 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 std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using namespace isc::dns::rdata::generic;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_NAPTR_Test : public RdataTest {
+};
+
+// 10 100 "S" "SIP+D2U" "" _sip._udp.example.com.
+static uint8_t naptr_rdata[] = {0x00,0x0a,0x00,0x64,0x01,0x53,0x07,0x53,0x49,
+ 0x50,0x2b,0x44,0x32,0x55,0x00,0x04,0x5f,0x73,0x69,0x70,0x04,0x5f,0x75,0x64,
+ 0x70,0x07,0x65,0x78,0x61,0x6d,0x70,0x6c,0x65,0x03,0x63,0x6f,0x6d,0x00};
+
+static const char *naptr_str =
+ "10 100 \"S\" \"SIP+D2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str2 =
+ "10 100 S SIP+D2U \"\" _sip._udp.example.com.";
+
+static const char *naptr_str_small1 =
+ "9 100 \"S\" \"SIP+D2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_small2 =
+ "10 90 \"S\" \"SIP+D2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_small3 =
+ "10 100 \"R\" \"SIP+D2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_small4 =
+ "10 100 \"S\" \"SIP+C2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_small5 =
+ "10 100 \"S\" \"SIP+D2U\" \"\" _rip._udp.example.com.";
+
+static const char *naptr_str_large1 =
+ "11 100 \"S\" \"SIP+D2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_large2 =
+ "10 110 \"S\" \"SIP+D2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_large3 =
+ "10 100 \"T\" \"SIP+D2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_large4 =
+ "10 100 \"S\" \"SIP+E2U\" \"\" _sip._udp.example.com.";
+static const char *naptr_str_large5 =
+ "10 100 \"S\" \"SIP+D2U\" \"\" _tip._udp.example.com.";
+
+TEST_F(Rdata_NAPTR_Test, createFromText) {
+ NAPTR naptr(naptr_str);
+ EXPECT_EQ(10, naptr.getOrder());
+ EXPECT_EQ(100, naptr.getPreference());
+ EXPECT_EQ(string("S"), naptr.getFlags());
+ EXPECT_EQ(string("SIP+D2U"), naptr.getServices());
+ EXPECT_EQ(string(""), naptr.getRegexp());
+ EXPECT_EQ(Name("_sip._udp.example.com."), naptr.getReplacement());
+
+ // Test <char-string> that separated by space
+ NAPTR naptr2(naptr_str2);
+ EXPECT_EQ(string("S"), naptr2.getFlags());
+ EXPECT_EQ(string("SIP+D2U"), naptr2.getServices());
+}
+
+TEST_F(Rdata_NAPTR_Test, badText) {
+ // Order number cannot exceed 65535
+ EXPECT_THROW(const NAPTR naptr("65536 10 S SIP \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ // Preference number cannot exceed 65535
+ EXPECT_THROW(const NAPTR naptr("100 65536 S SIP \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ // No regexp given
+ EXPECT_THROW(const NAPTR naptr("100 10 S SIP _sip._udp.example.com."),
+ InvalidRdataText);
+ // The double quotes seperator must match
+ EXPECT_THROW(const NAPTR naptr("100 10 \"S SIP \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ // Order or preference cannot be missed
+ EXPECT_THROW(const NAPTR naptr("10 \"S\" SIP \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ // Unquoted fields must be separated by spaces
+ EXPECT_THROW(const NAPTR naptr("100 10S SIP \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ EXPECT_THROW(const NAPTR naptr("10010 \"S\" \"SIP\" \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ EXPECT_THROW(const NAPTR naptr("100 10 SSIP \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ // Field cannot be missing
+ EXPECT_THROW(const NAPTR naptr("100 10 \"S\""), InvalidRdataText);
+
+ // The <character-string> cannot exceed 255 characters
+ string naptr_str;
+ naptr_str += "100 10 ";
+ for (int i = 0; i < 257; ++i) {
+ naptr_str += 'A';
+ }
+ naptr_str += " SIP \"\" _sip._udp.example.com.";
+ EXPECT_THROW(const NAPTR naptr(naptr_str), CharStringTooLong);
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromWire) {
+ InputBuffer input_buffer(naptr_rdata, sizeof(naptr_rdata));
+ NAPTR naptr(input_buffer, sizeof(naptr_rdata));
+ EXPECT_EQ(10, naptr.getOrder());
+ EXPECT_EQ(100, naptr.getPreference());
+ EXPECT_EQ(string("S"), naptr.getFlags());
+ EXPECT_EQ(string("SIP+D2U"), naptr.getServices());
+ EXPECT_EQ(string(""), naptr.getRegexp());
+ EXPECT_EQ(Name("_sip._udp.example.com."), naptr.getReplacement());
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromWireTooLongDataLen) {
+ static uint8_t naptr_rdata_long[] = {
+ 0x00,0x0a,0x00,0x64,0x01,0x53,0x07,0x53,0x49,0x50,0x2b,0x44,0x32,0x55,
+ 0x00,0x04,0x5f,0x73,0x69,0x70,0x04,0x5f,0x75,0x64,0x70,0x07,0x65,0x78,
+ 0x61,0x6d,0x70,0x6c,0x65,0x03,0x63,0x6f,0x6d,0x00,0xff,0xff,0xff,0xff};
+ InputBuffer input_buffer(naptr_rdata_long, sizeof(naptr_rdata_long));
+ EXPECT_THROW(NAPTR naptr(input_buffer, sizeof(naptr_rdata_long)),
+ isc::dns::DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromWireTooShortDataLen) {
+ // missing data (just set rdata_len too low)
+ for (size_t i = 0; i < sizeof(naptr_rdata); ++i) {
+ // Just use existing correct buffer but set rdata_len too low
+ InputBuffer input_buffer(naptr_rdata, sizeof(naptr_rdata));
+ EXPECT_THROW(NAPTR naptr(input_buffer, i),
+ isc::dns::DNSMessageFORMERR);
+ }
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromLexer) {
+ const NAPTR rdata_naptr(naptr_str);
+
+ EXPECT_EQ(0, rdata_naptr.compare(
+ *test::createRdataUsingLexer(RRType::NAPTR(), RRClass::IN(),
+ naptr_str)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NAPTR(), RRClass::IN(),
+ "65536 10 S SIP \"\" "
+ "_sip._udp.example.com."));
+}
+
+TEST_F(Rdata_NAPTR_Test, toWire) {
+ NAPTR naptr(naptr_str);
+
+ naptr.toWire(obuffer);
+ matchWireData(naptr_rdata, sizeof(naptr_rdata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_NAPTR_Test, toWireRenderer) {
+ NAPTR naptr(naptr_str);
+
+ naptr.toWire(renderer);
+ matchWireData(naptr_rdata, sizeof(naptr_rdata),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_NAPTR_Test, toText) {
+ NAPTR naptr(naptr_str);
+ EXPECT_EQ(naptr_str, naptr.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 S SIP+D2U .* _sip._udp.example.com.").toText());
+ // will not add additional quotes
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.")
+ .toText());
+}
+
+TEST_F(Rdata_NAPTR_Test, compare) {
+ NAPTR naptr(naptr_str);
+ NAPTR naptr_small1(naptr_str_small1);
+ NAPTR naptr_small2(naptr_str_small2);
+ NAPTR naptr_small3(naptr_str_small3);
+ NAPTR naptr_small4(naptr_str_small4);
+ NAPTR naptr_small5(naptr_str_small5);
+ NAPTR naptr_large1(naptr_str_large1);
+ NAPTR naptr_large2(naptr_str_large2);
+ NAPTR naptr_large3(naptr_str_large3);
+ NAPTR naptr_large4(naptr_str_large4);
+ NAPTR naptr_large5(naptr_str_large5);
+
+ EXPECT_EQ(0, naptr.compare(NAPTR(naptr_str)));
+ EXPECT_EQ(1, naptr.compare(naptr_small1));
+ EXPECT_EQ(1, naptr.compare(naptr_small2));
+ EXPECT_EQ(1, naptr.compare(naptr_small3));
+ EXPECT_EQ(1, naptr.compare(naptr_small4));
+ EXPECT_EQ(1, naptr.compare(naptr_small5));
+ EXPECT_EQ(-1, naptr.compare(naptr_large1));
+ EXPECT_EQ(-1, naptr.compare(naptr_large2));
+ EXPECT_EQ(-1, naptr.compare(naptr_large3));
+ EXPECT_EQ(-1, naptr.compare(naptr_large4));
+ EXPECT_EQ(-1, naptr.compare(naptr_large5));
+ EXPECT_EQ(-1, naptr_small1.compare(naptr));
+ EXPECT_EQ(-1, naptr_small2.compare(naptr));
+ EXPECT_EQ(-1, naptr_small3.compare(naptr));
+ EXPECT_EQ(-1, naptr_small4.compare(naptr));
+ EXPECT_EQ(-1, naptr_small5.compare(naptr));
+ EXPECT_EQ(1, naptr_large1.compare(naptr));
+ EXPECT_EQ(1, naptr_large2.compare(naptr));
+ EXPECT_EQ(1, naptr_large3.compare(naptr));
+ EXPECT_EQ(1, naptr_large4.compare(naptr));
+ EXPECT_EQ(1, naptr_large5.compare(naptr));
+}
+
+TEST_F(Rdata_NAPTR_Test, copy) {
+ NAPTR naptr(naptr_str);
+ NAPTR naptr2(naptr);
+ NAPTR naptr3 = naptr;
+
+ EXPECT_EQ(0, naptr.compare(naptr2));
+ EXPECT_EQ(0, naptr.compare(naptr3));
+
+ naptr3 = naptr;
+ EXPECT_EQ(0, naptr.compare(naptr3));
+}
+
+}
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..31bb508
--- /dev/null
+++ b/src/lib/dns/tests/rdata_ns_unittest.cc
@@ -0,0 +1,145 @@
+// 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 <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 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_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_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
new file mode 100644
index 0000000..c6d3a07
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -0,0 +1,226 @@
+// Copyright (C) 2010-2017 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 <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+
+// Note: some tests can be shared with NSEC3PARAM. They are unified as
+// typed tests defined in nsec3param_like_unittest.
+class Rdata_NSEC3_Test : public RdataTest {
+protected:
+ Rdata_NSEC3_Test() :
+ nsec3_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "A NS SOA"),
+ nsec3_nosalt_txt("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA"),
+ nsec3_notype_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"),
+ rdata_nsec3(nsec3_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::NSEC3, isc::Exception, isc::Exception>(
+ rdata_str, rdata_nsec3, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::NSEC3, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::NSEC3, BadValue, BadValue>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_nsec3, true, false);
+ }
+
+ const string nsec3_txt;
+ const string nsec3_nosalt_txt;
+ const string nsec3_notype_txt;
+ const generic::NSEC3 rdata_nsec3;
+};
+
+TEST_F(Rdata_NSEC3_Test, fromText) {
+ // Hash that has the possible max length
+ EXPECT_EQ(255, generic::NSEC3("1 1 1 D399EAAB " +
+ string((255 * 8) / 5, '0') +
+ " NS").getNext().size());
+
+ // Hash is too long. Max = 255 bytes, base32-hex converts each 5 bytes
+ // of the original to 8 characters, so 260 * 8 / 5 is the smallest length
+ // of the encoded string that exceeds the max and doesn't require padding.
+ checkFromText_InvalidText("1 1 1 D399EAAB " + string((260 * 8) / 5, '0') +
+ " A NS SOA");
+
+ // Type bitmap is empty. it's possible and allowed for NSEC3.
+ EXPECT_NO_THROW(const generic::NSEC3 rdata_notype_nsec3(nsec3_notype_txt));
+
+ // Empty salt is also okay.
+ EXPECT_NO_THROW(const generic::NSEC3 rdata_nosalt_nsec3(nsec3_nosalt_txt));
+
+ // Bad type mnemonics
+ checkFromText_InvalidText("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"
+ " BIFF POW SPOON");
+
+ // Bad base32hex
+ checkFromText_BadValue("1 1 1 D399EAAB "
+ "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA");
+
+ // Hash algorithm out of range
+ checkFromText_InvalidText("256 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Flags out of range
+ checkFromText_InvalidText("1 256 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Iterations out of range
+ checkFromText_InvalidText("1 1 65536 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Space is not allowed in salt or the next hash. This actually
+ // causes the Base32 decoder that parses the next hash that comes
+ // afterwards, to throw.
+ checkFromText_BadValue("1 1 1 D399 EAAB H9RSFB7FPF2L8"
+ "HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Next hash must not contain padding (trailing '=' characters)
+ checkFromText_InvalidText("1 1 1 D399EAAB "
+ "AAECAwQFBgcICQoLDA0ODw== A NS SOA");
+
+ // String instead of number
+ checkFromText_LexerError("foo 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+ checkFromText_LexerError("1 foo 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+ checkFromText_LexerError("1 1 foo D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // 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(
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA ;comment\n"
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Unmatched parenthesis should cause a lexer error
+ checkFromText_LexerError("1 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A ) NS SOA");
+}
+
+TEST_F(Rdata_NSEC3_Test, createFromWire) {
+ // A valid NSEC3 RR with empty type bitmap.
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire15.wire"));
+
+ // Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
+
+ // hash length is too large
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire12.wire"),
+ DNSMessageFORMERR);
+
+ // empty hash. invalid.
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire14.wire"),
+ DNSMessageFORMERR);
+
+ // RDLEN is too short to hold the hash length field
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+ "rdata_nsec3_fromWire17.wire"),
+ DNSMessageFORMERR);
+
+ // Short buffer cases. The data is valid NSEC3 RDATA, but the buffer
+ // is trimmed at the end. All cases should result in an exception from
+ // the buffer class.
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData("rdata_nsec3_fromWire1", data);
+ const uint16_t rdlen = (data.at(0) << 8) + data.at(1);
+ for (int i = 0; i < rdlen; ++i) {
+ // intentionally construct a short buffer
+ InputBuffer b(&data[0] + 2, i);
+ EXPECT_THROW(createRdata(RRType::NSEC3(), RRClass::IN(), b, 39),
+ InvalidBufferPosition);
+ }
+}
+
+TEST_F(Rdata_NSEC3_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_nsec3.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+ nsec3_txt)));
+
+ // empty salt is also okay.
+ const generic::NSEC3 rdata_nosalt_nsec3(nsec3_nosalt_txt);
+ EXPECT_EQ(0, rdata_nosalt_nsec3.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+ nsec3_nosalt_txt)));
+}
+
+TEST_F(Rdata_NSEC3_Test, assign) {
+ generic::NSEC3 other_nsec3 = rdata_nsec3;
+ EXPECT_EQ(0, rdata_nsec3.compare(other_nsec3));
+}
+
+TEST_F(Rdata_NSEC3_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, generic::NSEC3(nsec3_txt).compare(generic::NSEC3(nsec3_txt)));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(generic::NSEC3(nsec3_txt).compare(*rdata_nomatch),
+ bad_cast);
+
+ // test RDATAs, sorted in the ascending order. We only check comparison
+ // on NSEC3-specific fields. Bitmap comparison is tested in the bitmap
+ // tests. Common cases for NSEC3 and NSECPARAM3 are in their shared tests.
+ vector<generic::NSEC3> compare_set;
+ compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ38"));
+ compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ0000000000"));
+ compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ00UUUUUUUU"));
+
+ vector<generic::NSEC3>::const_iterator it;
+ const vector<generic::NSEC3>::const_iterator it_end = compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " + (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
new file mode 100644
index 0000000..914a6f4
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
@@ -0,0 +1,272 @@
+// Copyright (C) 2012-2019 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/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 <string>
+#include <vector>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+
+// Template for shared tests for NSEC3 and NSEC3PARAM
+template <typename RDATA_TYPE>
+class NSEC3PARAMLikeTest : public RdataTest {
+protected:
+ NSEC3PARAMLikeTest() :
+ salt_txt("1 1 1 D399EAAB" + getCommonText()),
+ nosalt_txt("1 1 1 -" + getCommonText()),
+ obuffer(0)
+ {}
+
+ RDATA_TYPE fromText(const string& rdata_text) {
+ return (RDATA_TYPE(rdata_text));
+ }
+
+ void compareCheck() const {
+ typename vector<RDATA_TYPE>::const_iterator it;
+ typename vector<RDATA_TYPE>::const_iterator const it_end =
+ compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " +
+ (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+ }
+
+ const string salt_txt; // RDATA text with salt
+ const string nosalt_txt; // RDATA text without salt
+ OutputBuffer obuffer; // used in toWire() tests
+ MessageRenderer renderer; // ditto
+ vector<RDATA_TYPE> compare_set; // used in compare() tests
+
+ // Convert generic Rdata to the corresponding derived Rdata class object.
+ // Defined here because it depends on the template parameter.
+ static const RDATA_TYPE& convert(const Rdata& rdata) {
+ return (dynamic_cast<const RDATA_TYPE&>(rdata));
+ }
+
+ // These depend on the specific RR type. We use specialized methods
+ // for them.
+ static RRType getType(); // return either RRType::NSEC3() or NSEC3PARAM()
+ static string getWireFilePrefix();
+ static string getCommonText(); // commonly used part of textual form
+};
+
+// Instantiate specific typed tests
+typedef ::testing::Types<generic::NSEC3, generic::NSEC3PARAM> TestRdataTypes;
+#ifdef TYPED_TEST_SUITE
+TYPED_TEST_SUITE(NSEC3PARAMLikeTest, TestRdataTypes);
+#else
+TYPED_TEST_CASE(NSEC3PARAMLikeTest, TestRdataTypes);
+#endif
+
+template <>
+RRType
+NSEC3PARAMLikeTest<generic::NSEC3>::getType() {
+ return (RRType::NSEC3());
+}
+
+template <>
+RRType
+NSEC3PARAMLikeTest<generic::NSEC3PARAM>::getType() {
+ return (RRType::NSEC3PARAM());
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3>::getWireFilePrefix() {
+ return ("rdata_nsec3_");
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3PARAM>::getWireFilePrefix() {
+ return ("rdata_nsec3param_");
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3>::getCommonText() {
+ // next hash + RR type bitmap
+ return (" H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+ "NS SOA RRSIG DNSKEY NSEC3PARAM");
+}
+
+template <>
+string
+NSEC3PARAMLikeTest<generic::NSEC3PARAM>::getCommonText() {
+ // there's no more text for NSEC3PARAM
+ return ("");
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, fromText) {
+ // Numeric parameters have possible maximum values. Unusual, but must
+ // be accepted.
+ EXPECT_NO_THROW(this->fromText("255 255 65535 D399EAAB" +
+ this->getCommonText()));
+
+ // 0-length salt
+ EXPECT_EQ(0, this->fromText(this->nosalt_txt).getSalt().size());
+
+ // salt that has the possible max length
+ EXPECT_EQ(255, this->fromText("1 1 1 " + string(255 * 2, '0') +
+ this->getCommonText()).getSalt().size());
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, badText) {
+ // Bad salt hex
+ EXPECT_THROW(this->fromText("1 1 1 SPORK0" + this->getCommonText()),
+ isc::BadValue);
+ EXPECT_THROW(this->fromText("1 1 1 ADDAFEE" + this->getCommonText()),
+ isc::BadValue);
+
+ // Space within salt
+ EXPECT_THROW(this->fromText("1 1 1 ADDAFE ADDAFEEE" +
+ this->getCommonText()),
+ InvalidRdataText);
+
+ // Similar to empty salt, but not really. This shouldn't cause confusion.
+ EXPECT_THROW(this->fromText("1 1 1 --" + this->getCommonText()),
+ isc::BadValue);
+
+ // Too large algorithm
+ EXPECT_THROW(this->fromText("1000000 1 1 ADDAFEEE" + this->getCommonText()),
+ InvalidRdataText);
+
+ // Too large flags
+ EXPECT_THROW(this->fromText("1 1000000 1 ADDAFEEE" + this->getCommonText()),
+ InvalidRdataText);
+
+ // Too large iterations
+ EXPECT_THROW(this->fromText("1 1 65536 ADDAFEEE" + this->getCommonText()),
+ InvalidRdataText);
+
+ // There should be a space between "1" and "D399EAAB" (salt)
+ EXPECT_THROW(this->fromText("1 1 1D399EAAB" + this->getCommonText()),
+ InvalidRdataText);
+
+ // Salt is too long (possible max + 1 bytes)
+ EXPECT_THROW(this->fromText("1 1 1 " + string(256 * 2, '0') +
+ this->getCommonText()),
+ InvalidRdataText);
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, toText) {
+ // normal case
+ EXPECT_EQ(this->salt_txt, this->fromText(this->salt_txt).toText());
+
+ // empty salt case
+ EXPECT_EQ(this->nosalt_txt, this->fromText(this->nosalt_txt).toText());
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, createFromWire) {
+ // Normal case
+ EXPECT_EQ(0, this->fromText(this->salt_txt).compare(
+ *this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire1").c_str())));
+
+ // Too short RDLENGTH: it doesn't even contain the first 5 octets.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire2.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // salt length is too large
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire11.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // empty salt. not so usual, but valid.
+ ConstRdataPtr rdata =
+ this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire13.wire").c_str());
+ EXPECT_EQ(0, this->convert(*rdata).getSalt().size());
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, createFromLexer) {
+ EXPECT_EQ(0, this->fromText(this->salt_txt).compare(
+ *test::createRdataUsingLexer(this->getType(), RRClass::IN(),
+ this->salt_txt)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(this->getType(), RRClass::IN(),
+ "1000000 1 1 ADDAFEEE" +
+ this->getCommonText()));
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireCheck(RRType rrtype, OUTPUT_TYPE& output, const string& data_file) {
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData(data_file.c_str(), data);
+ InputBuffer buffer(&data[0], data.size());
+ const uint16_t rdlen = buffer.readUint16();
+
+ output.clear();
+ output.writeUint16(rdlen);
+ createRdata(rrtype, RRClass::IN(), buffer, rdlen)->toWire(output);
+ matchWireData(&data[0], data.size(),
+ output.getData(), output.getLength());
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, toWire) {
+ // normal case
+ toWireCheck(this->getType(), this->renderer,
+ this->getWireFilePrefix() + "fromWire1");
+ toWireCheck(this->getType(), this->obuffer,
+ this->getWireFilePrefix() + "fromWire1");
+
+ // empty salt
+ toWireCheck(this->getType(), this->renderer,
+ this->getWireFilePrefix() + "fromWire13.wire");
+ toWireCheck(this->getType(), this->obuffer,
+ this->getWireFilePrefix() + "fromWire13.wire");
+}
+
+TYPED_TEST(NSEC3PARAMLikeTest, compare) {
+ // test RDATAs, sorted in the ascending order.
+ this->compare_set.push_back(this->fromText("0 0 0 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 0 0 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 0 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 -" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 D399EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 FF99EAAB" +
+ this->getCommonText()));
+ this->compare_set.push_back(this->fromText("1 1 1 FF99EA0000" +
+ this->getCommonText()));
+
+ this->compareCheck();
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
new file mode 100644
index 0000000..9a677be
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -0,0 +1,209 @@
+// 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 <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 <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_NSEC3PARAM_Test : public RdataTest {
+protected:
+ Rdata_NSEC3PARAM_Test() :
+ nsec3param_txt("1 1 1 D399EAAB"),
+ nsec3param_nosalt_txt("1 1 1 -"),
+ rdata_nsec3param(nsec3param_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, isc::Exception, isc::Exception>(
+ rdata_str, rdata_nsec3param, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, BadValue, BadValue>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3PARAM, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str,
+ const generic::NSEC3PARAM& rdata)
+ {
+ checkFromText
+ <generic::NSEC3PARAM, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata, true, false);
+ }
+
+ const string nsec3param_txt;
+ const string nsec3param_nosalt_txt;
+ const generic::NSEC3PARAM rdata_nsec3param;
+};
+
+TEST_F(Rdata_NSEC3PARAM_Test, fromText) {
+ // Empty salt is okay.
+ EXPECT_EQ(0, generic::NSEC3PARAM(nsec3param_nosalt_txt).getSalt().size());
+
+ // Salt is missing.
+ checkFromText_LexerError("1 1 1");
+
+ // Salt has whitespace within. This only fails in the string
+ // constructor, as the lexer constructor stops reading at the end of
+ // its RDATA.
+ const generic::NSEC3PARAM rdata_nsec3param2("1 1 1 D399");
+ checkFromText_BadString("1 1 1 D399 EAAB", rdata_nsec3param2);
+
+ // Hash algorithm out of range.
+ checkFromText_InvalidText("256 1 1 D399EAAB");
+
+ // Flags out of range.
+ checkFromText_InvalidText("1 256 1 D399EAAB");
+
+ // Iterations out of range.
+ checkFromText_InvalidText("1 1 65536 D399EAAB");
+
+ // Bad hex sequence
+ checkFromText_BadValue("1 1 256 D399EAABZOO");
+
+ // String instead of number
+ checkFromText_LexerError("foo 1 256 D399EAAB");
+ checkFromText_LexerError("1 foo 256 D399EAAB");
+ checkFromText_LexerError("1 1 foo D399EAAB");
+
+ // 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("1 1 1 D399EAAB ; comment\n"
+ "1 1 1 D399EAAB", rdata_nsec3param);
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, toText) {
+ EXPECT_EQ(nsec3param_txt, rdata_nsec3param.toText());
+
+ // Garbage space at the end should be ok. RFC5155 only forbids
+ // whitespace within the salt field, but any whitespace afterwards
+ // should be fine.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 1 1 D399EAAB "));
+
+ // Hash algorithm in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("255 1 1 D399EAAB"));
+
+ // Flags in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 255 1 D399EAAB"));
+
+ // Iterations in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 1 65535 D399EAAB"));
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_nsec3param.compare(
+ *rdataFactoryFromFile(RRType::NSEC3PARAM(), RRClass::IN(),
+ "rdata_nsec3param_fromWire1")));
+
+ // Short buffer cases. The data is valid NSEC3PARAM RDATA, but the buffer
+ // is trimmed at the end. All cases should result in an exception from
+ // the buffer class.
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
+ const uint16_t rdlen = (data.at(0) << 8) + data.at(1);
+ for (int i = 0; i < rdlen; ++i) {
+ // intentionally construct a short buffer
+ InputBuffer b(&data[0] + 2, i);
+ EXPECT_THROW(createRdata(RRType::NSEC3PARAM(), RRClass::IN(), b, 9),
+ InvalidBufferPosition);
+ }
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_nsec3param.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
+ nsec3param_txt)));
+
+ // empty salt is also okay.
+ const generic::NSEC3PARAM rdata_nosalt_nsec3param(nsec3param_nosalt_txt);
+ EXPECT_EQ(0, rdata_nosalt_nsec3param.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
+ nsec3param_nosalt_txt)));
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
+ renderer.skip(2);
+ rdata_nsec3param.toWire(renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
+ matchWireData(&data[2], data.size() - 2,
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2);
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, toWireBuffer) {
+ rdata_nsec3param.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
+ matchWireData(&data[2], data.size() - 2,
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, getHashAlg) {
+ EXPECT_EQ(1, rdata_nsec3param.getHashalg());
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, getFlags) {
+ EXPECT_EQ(1, rdata_nsec3param.getFlags());
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, assign) {
+ generic::NSEC3PARAM other_nsec3param("1 1 1 -");
+ other_nsec3param = rdata_nsec3param;
+ EXPECT_EQ(0, rdata_nsec3param.compare(other_nsec3param));
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, generic::NSEC3PARAM(nsec3param_txt).
+ compare(generic::NSEC3PARAM(nsec3param_txt)));
+ EXPECT_EQ(0, generic::NSEC3PARAM("1 1 1 -").
+ compare(generic::NSEC3PARAM("1 1 1 -")));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(generic::NSEC3PARAM(nsec3param_txt).compare(*rdata_nomatch),
+ bad_cast);
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
new file mode 100644
index 0000000..570a2ab
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright (C) 2010-2017 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 <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 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_NSEC_Test : public RdataTest {
+ // there's nothing to specialize
+};
+
+const char* const nsec_txt = "www2.isc.org. CNAME RRSIG NSEC";
+
+TEST_F(Rdata_NSEC_Test, toText_NSEC) {
+ const generic::NSEC rdata_nsec(nsec_txt);
+ EXPECT_EQ(nsec_txt, rdata_nsec.toText());
+}
+
+TEST_F(Rdata_NSEC_Test, badText_NSEC) {
+ EXPECT_THROW(generic::NSEC rdata_nsec("www.isc.org. BIFF POW SPOON"),
+ InvalidRdataText);
+ EXPECT_THROW(generic::NSEC rdata_nsec("www.isc.org."),
+ InvalidRdataText);
+}
+
+TEST_F(Rdata_NSEC_Test, createFromWire_NSEC) {
+ const generic::NSEC rdata_nsec(nsec_txt);
+ EXPECT_EQ(0, rdata_nsec.compare(
+ *rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire1")));
+
+ // Too short RDLENGTH
+ EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
+ "rdata_nsec_fromWire2"),
+ DNSMessageFORMERR);
+
+ // Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
+}
+
+TEST_F(Rdata_NSEC_Test, createFromLexer_NSEC) {
+ const generic::NSEC rdata_nsec(nsec_txt);
+ EXPECT_EQ(0, rdata_nsec.compare(
+ *test::createRdataUsingLexer(RRType::NSEC(), RRClass::IN(),
+ nsec_txt)));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ EXPECT_EQ(0, generic::NSEC("www2.example.org. CNAME RRSIG NSEC").compare(
+ *test::createRdataUsingLexer(RRType::NSEC(), RRClass::IN(),
+ "www2 CNAME RRSIG NSEC")));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NSEC(), RRClass::IN(),
+ "www.isc.org."));
+}
+
+TEST_F(Rdata_NSEC_Test, toWireRenderer_NSEC) {
+ renderer.skip(2);
+ const generic::NSEC rdata_nsec(nsec_txt);
+ rdata_nsec.toWire(renderer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_nsec_fromWire1", data);
+ matchWireData(&data[2], data.size() - 2,
+ static_cast<const uint8_t *>(renderer.getData()) + 2,
+ renderer.getLength() - 2);
+}
+
+TEST_F(Rdata_NSEC_Test, toWireBuffer_NSEC) {
+ const generic::NSEC rdata_nsec(nsec_txt);
+ rdata_nsec.toWire(obuffer);
+}
+
+TEST_F(Rdata_NSEC_Test, assign) {
+ generic::NSEC rdata_nsec(nsec_txt);
+ generic::NSEC rdata_nsec2 = rdata_nsec;
+ EXPECT_EQ(0, rdata_nsec.compare(rdata_nsec2));
+}
+
+TEST_F(Rdata_NSEC_Test, getNextName) {
+ // The implementation is quite trivial, so we simply check it's actually
+ // defined and does work as intended in a simple case.
+ EXPECT_EQ(Name("www2.isc.org"), generic::NSEC((nsec_txt)).getNextName());
+}
+
+TEST_F(Rdata_NSEC_Test, compare) {
+ // trivial case: self equivalence
+ EXPECT_EQ(0, generic::NSEC("example. A").
+ compare(generic::NSEC("example. A")));
+ EXPECT_EQ(0, generic::NSEC("EXAMPLE. A"). // should be case insensitive
+ compare(generic::NSEC("example. A")));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(generic::NSEC(nsec_txt).compare(*rdata_nomatch),
+ bad_cast);
+
+ // test RDATAs, sorted in the ascending order. We only compare the
+ // next name here. Bitmap comparison is tested in the bitmap tests.
+ // Note that names are compared as wire-format data, not based on the
+ // domain name comparison.
+ vector<generic::NSEC> compare_set;
+ compare_set.push_back(generic::NSEC("a.example. A"));
+ compare_set.push_back(generic::NSEC("example. A"));
+ vector<generic::NSEC>::const_iterator it;
+ const vector<generic::NSEC>::const_iterator it_end = compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " + (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
new file mode 100644
index 0000000..f18e9df
--- /dev/null
+++ b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
@@ -0,0 +1,269 @@
+// Copyright (C) 2011-2019 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/tests/unittest_util.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/rdata_unittest.h>
+#include <util/unittests/wiredata.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+using boost::lexical_cast;
+
+namespace {
+
+// Template for shared tests for NSEC and NSEC3 bitmaps
+template <typename RDATA_TYPE>
+class NSECLikeBitmapTest : public RdataTest {
+protected:
+ RDATA_TYPE fromText(const string& rdata_text) {
+ return (RDATA_TYPE(rdata_text));
+ }
+
+ vector<RDATA_TYPE> compare_set; // used in compare() tests
+
+ void compareCheck() const {
+ typename vector<RDATA_TYPE>::const_iterator it;
+ typename vector<RDATA_TYPE>::const_iterator const it_end =
+ compare_set.end();
+ for (it = compare_set.begin(); it != it_end - 1; ++it) {
+ SCOPED_TRACE("compare " + it->toText() + " to " +
+ (it + 1)->toText());
+ EXPECT_GT(0, (*it).compare(*(it + 1)));
+ EXPECT_LT(0, (*(it + 1)).compare(*it));
+ }
+ }
+
+ // These depend on the specific RR type. We use specialized methods
+ // for them.
+ static RRType getType(); // return either RRType::NSEC() or NSEC3()
+ static string getWireFilePrefix();
+ static string getCommonText(); // commonly used part of textual form
+};
+
+// Instantiate specific typed tests
+typedef ::testing::Types<generic::NSEC, generic::NSEC3> TestRdataTypes;
+#ifdef TYPED_TEST_SUITE
+TYPED_TEST_SUITE(NSECLikeBitmapTest, TestRdataTypes);
+#else
+TYPED_TEST_CASE(NSECLikeBitmapTest, TestRdataTypes);
+#endif
+
+// NSEC and NSEC3 bitmaps have some subtle differences, in which case we
+// need to test them separately. Using these typedef type names with TEST_F
+// will do the trick.
+typedef NSECLikeBitmapTest<generic::NSEC3> NSEC3BitmapTest;
+typedef NSECLikeBitmapTest<generic::NSEC> NSECBitmapTest;
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC>::getWireFilePrefix() {
+ return ("rdata_nsec_");
+}
+
+template <>
+RRType
+NSECLikeBitmapTest<generic::NSEC>::getType() {
+ return (RRType::NSEC());
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC3>::getWireFilePrefix() {
+ return ("rdata_nsec3_");
+}
+
+template <>
+RRType
+NSECLikeBitmapTest<generic::NSEC3>::getType() {
+ return (RRType::NSEC3());
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC>::getCommonText() {
+ return ("next. ");
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC3>::getCommonText() {
+ return ("1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR ");
+}
+
+// Tests against various types of bogus NSEC/NSEC3 type bitmaps.
+// The syntax and semantics are common for both RR types, and our
+// implementation of that part is shared, so in theory it should be sufficient
+// to test for only one RR type. But we check for both just in case.
+TYPED_TEST(NSECLikeBitmapTest, createFromWire) {
+ // A malformed NSEC bitmap length field that could cause overflow.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire4.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // The bitmap field is incomplete (only the first byte is included)
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire5.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // Bitmap length is 0, which is invalid.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire6.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // Too large bitmap length with a short buffer.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire3").c_str()),
+ DNSMessageFORMERR);
+
+ // A boundary case: longest possible bitmaps (32 maps). This should be
+ // accepted.
+ EXPECT_NO_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire7.wire").c_str()));
+
+ // Another boundary condition: 33 bitmaps, which should be rejected.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire8.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // Disordered bitmap window blocks.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire9.wire").c_str()),
+ DNSMessageFORMERR);
+
+ // Bitmap ending with all-zero bytes. Not necessarily harmful except
+ // the additional overhead of parsing, but invalid according to the
+ // spec anyway.
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire10.wire").c_str()),
+ DNSMessageFORMERR);
+}
+
+// This tests the result of toText() with various kinds of NSEC/NSEC3 bitmaps.
+// It also tests the "from text" constructor as a result.
+TYPED_TEST(NSECLikeBitmapTest, toText) {
+ // A simple case (some commonly seen RR types in NSEC(3) bitmaps)
+ string rdata_text = this->getCommonText() + "NS SOA RRSIG DNSKEY";
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+
+ // Similar to above, but involves more than one bitmap window blocks.
+ rdata_text = this->getCommonText() + "NS DLV";
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+
+ // Make sure all possible bits in a one-octet bitmap field are handled
+ // correctly.
+ // We use the range around 1024 (reasonably higher number) so it's
+ // unlikely that they have predefined mnemonic and can be safely converted
+ // to TYPEnnnn by toText().
+ for (unsigned int i = 1024; i < 1032; ++i) {
+ rdata_text = this->getCommonText() + "TYPE" + lexical_cast<string>(i);
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+ }
+
+ // Make sure all possible 32 octets in a longest possible block are
+ // handled correctly.
+ for (unsigned int i = 1024; i < 1024 + 256; i += 8) {
+ rdata_text = this->getCommonText() + "TYPE" + lexical_cast<string>(i);
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+ }
+
+ // Check for the highest window block.
+ rdata_text = this->getCommonText() + "TYPE65535";
+ EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+}
+
+TYPED_TEST(NSECLikeBitmapTest, compare) {
+ // Bit map: [win=0][len=1] 00000010
+ this->compare_set.push_back(this->fromText(this->getCommonText() + "SOA"));
+ // Bit map: [win=0][len=1] 00000010, [win=4][len=1] 10000000
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "SOA TYPE1024"));
+ // Bit map: [win=0][len=1] 00100000
+ this->compare_set.push_back(this->fromText(this->getCommonText() + "NS"));
+ // Bit map: [win=0][len=1] 00100010
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "NS SOA"));
+ // Bit map: [win=0][len=2] 00100000, 00000001
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "NS MX"));
+ // Bit map: [win=4][len=1] 10000000
+ this->compare_set.push_back(this->fromText(this->getCommonText() +
+ "TYPE1024"));
+
+ this->compareCheck();
+}
+
+// NSEC bitmaps must not be empty
+TEST_F(NSECBitmapTest, emptyMap) {
+ EXPECT_THROW(this->fromText("next.example.").toText(), InvalidRdataText);
+
+ EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+ (this->getWireFilePrefix() +
+ "fromWire16.wire").c_str()),
+ DNSMessageFORMERR);
+}
+
+// NSEC3 bitmaps can be empty
+TEST_F(NSEC3BitmapTest, emptyMap) {
+ // Read wire data wit an empty NSEC3 bitmap. This should succeed.
+ vector<uint8_t> data;
+ UnitTestUtil::readWireData((this->getWireFilePrefix() +
+ "fromWire16.wire").c_str(), data);
+ InputBuffer buffer(&data[0], data.size());
+ const uint16_t rdlen = buffer.readUint16();
+ const generic::NSEC3 empty_nsec3 =
+ dynamic_cast<const generic::NSEC3&>(*createRdata(
+ RRType::NSEC3(), RRClass::IN(),
+ buffer, rdlen));
+
+ // Check the toText() result.
+ EXPECT_EQ("1 0 1 7373737373 D1K6GQ38D1K6GQ38D1K6GQ38D1K6GQ38",
+ empty_nsec3.toText());
+
+ // Check the toWire() result.
+ OutputBuffer obuffer(0);
+ obuffer.writeUint16(rdlen);
+ empty_nsec3.toWire(obuffer);
+ matchWireData(&data[0], data.size(),
+ obuffer.getData(), obuffer.getLength());
+
+ // Same for MessageRenderer.
+ obuffer.clear();
+ MessageRenderer renderer;
+ renderer.writeUint16(rdlen);
+ empty_nsec3.toWire(renderer);
+ matchWireData(&data[0], data.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+}
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..a235183
--- /dev/null
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -0,0 +1,198 @@
+// 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 <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),
+ InvalidBufferPosition);
+}
+
+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, NULL, 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_pimpl_holder_unittest.cc b/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
new file mode 100644
index 0000000..d639541
--- /dev/null
+++ b/src/lib/dns/tests/rdata_pimpl_holder_unittest.cc
@@ -0,0 +1,56 @@
+// Copyright (C) 2014-2017 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/rdata_pimpl_holder.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns::rdata;
+
+namespace {
+
+TEST(RdataPimplHolderTest, all) {
+ // Let's check with an integer
+ int* i1 = new int(42);
+ RdataPimplHolder<int> holder1(i1);
+ // The same pointer must be returned.
+ EXPECT_EQ(i1, holder1.get());
+ // Obviously the value should match too.
+ EXPECT_EQ(42, *holder1.get());
+ // We don't explicitly delete i or holder1, so it should not leak
+ // anything when the test is done (checked by Valgrind).
+
+ // The following cases are similar:
+
+ // Test no-argument reset()
+ int* i2 = new int(43);
+ RdataPimplHolder<int> holder2(i2);
+ holder2.reset();
+ EXPECT_EQ(NULL, holder2.get());
+
+ // Test reset() with argument
+ int* i3 = new int(44);
+ int* i4 = new int(45);
+ RdataPimplHolder<int> holder3(i3);
+ EXPECT_EQ(i3, holder3.get());
+ holder3.reset(i4);
+ EXPECT_EQ(i4, holder3.get());
+ EXPECT_EQ(45, *holder3.get());
+
+ // Test release()
+ RdataPimplHolder<int> holder4(new int(46));
+ EXPECT_NE(static_cast<void*>(NULL), holder4.get());
+ EXPECT_EQ(46, *holder4.get());
+ int* i5 = holder4.release();
+ EXPECT_EQ(NULL, holder4.get());
+ EXPECT_NE(static_cast<void*>(NULL), i5);
+ EXPECT_EQ(46, *i5);
+ delete i5;
+}
+
+}
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..5ed3bce
--- /dev/null
+++ b/src/lib/dns/tests/rdata_ptr_unittest.cc
@@ -0,0 +1,145 @@
+// 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 <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 std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+//
+// 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_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
new file mode 100644
index 0000000..e752f13
--- /dev/null
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -0,0 +1,200 @@
+// 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 <string>
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/rdataclass.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::util;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class Rdata_RP_Test : public RdataTest {
+protected:
+ Rdata_RP_Test() :
+ mailbox_name("root.example.com."),
+ text_name("rp-text.example.com."),
+ // this also serves as a test for "from text" constructor in a normal
+ // case.
+ rdata_rp("root.example.com. rp-text.example.com."),
+ obuffer(0)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::RP, isc::Exception, isc::Exception>(
+ rdata_str, rdata_rp, false, false);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::RP, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText<generic::RP, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_rp, true, false);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<generic::RP, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::RP, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_Origin(const string& rdata_str, const Name* origin) {
+ checkFromText<generic::RP, MissingNameOrigin, isc::Exception>(
+ rdata_str, rdata_rp, true, false, origin);
+ }
+
+ const Name mailbox_name, text_name;
+ const generic::RP rdata_rp; // commonly used test RDATA
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+ vector<uint8_t> expected_wire;
+};
+
+TEST_F(Rdata_RP_Test, createFromText) {
+ EXPECT_EQ(mailbox_name, rdata_rp.getMailbox());
+ EXPECT_EQ(text_name, rdata_rp.getText());
+
+ checkFromText_None("root.example.com. rp-text.example.com.");
+
+ // origin defined for lexer constructor, but not string constructor
+ const Name origin("example.com");
+ checkFromText_Origin("root rp-text", &origin);
+
+ // lexer constructor accepts extra text, but string constructor doesn't
+ checkFromText_BadString("root.example.com. rp-text.example.com. "
+ "extra.example.com.");
+}
+
+TEST_F(Rdata_RP_Test, badText) {
+ // invalid names
+ checkFromText_EmptyLabel("root..example.com. rp-text.example.com.");
+ checkFromText_EmptyLabel("root.example.com. rp-text..example.com.");
+
+ // missing field
+ checkFromText_LexerError("root.example.com.");
+
+ // missing origin
+ checkFromText_MissingOrigin("root.example.com rp-text.example.com.");
+ checkFromText_MissingOrigin("root.example.com. rp-text.example.com");
+}
+
+TEST_F(Rdata_RP_Test, createFromWire) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire1.wire"));
+ EXPECT_EQ(mailbox_name, dynamic_cast<generic::RP&>(*rdata).getMailbox());
+ EXPECT_EQ(text_name, dynamic_cast<generic::RP&>(*rdata).getText());
+
+ // a similar test with names being compressed
+ rdata = rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire2.wire", 30);
+ EXPECT_EQ(mailbox_name, dynamic_cast<generic::RP&>(*rdata).getMailbox());
+ EXPECT_EQ(Name("rp-text.example.net"),
+ dynamic_cast<generic::RP&>(*rdata).getText());
+}
+
+TEST_F(Rdata_RP_Test, badFromWire) {
+ // RDLEN is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire3.wire"),
+ InvalidRdataLength);
+
+ // RDLEN is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire4.wire"),
+ InvalidRdataLength);
+
+ // bogus mailbox name
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire5.wire"),
+ DNSMessageFORMERR);
+
+ // bogus text name
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire6.wire"),
+ DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_RP_Test, createFromParams) {
+ EXPECT_EQ(mailbox_name, generic::RP(mailbox_name, text_name).getMailbox());
+ EXPECT_EQ(text_name, generic::RP(mailbox_name, text_name).getText());
+}
+
+TEST_F(Rdata_RP_Test, toWireBuffer) {
+ // construct expected data
+ UnitTestUtil::readWireData("rdata_rp_toWire1.wire", expected_wire);
+
+ // construct actual data
+ rdata_rp.toWire(obuffer);
+
+ // then compare them
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_RP_Test, toWireRenderer) {
+ // similar to toWireBuffer, but names in RDATA could be compressed due to
+ // preceding names. Actually they must not be compressed according to
+ // RFC3597, and this test checks that.
+
+ UnitTestUtil::readWireData("rdata_rp_toWire2.wire", expected_wire);
+
+ renderer.writeName(Name("a.example.com"));
+ renderer.writeName(Name("b.example.net"));
+ generic::RP(mailbox_name, Name("rp-text.example.net")).toWire(renderer);
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_RP_Test, toText) {
+ // there's not much to test for this method. Only checking a simple case.
+ EXPECT_EQ("root.example.com. rp-text.example.com.", rdata_rp.toText());
+}
+
+TEST_F(Rdata_RP_Test, compare) {
+ // check reflexivity
+ EXPECT_EQ(0, rdata_rp.compare(rdata_rp));
+
+ // names must be compared in case-insensitive manner
+ EXPECT_EQ(0, rdata_rp.compare(generic::RP("ROOT.example.com. "
+ "rp-text.EXAMPLE.com.")));
+
+ // another RP whose mailbox name is larger than that of rdata_rp.
+ const generic::RP large1_rp("zzzz.example.com. rp-text.example.com.");
+ EXPECT_GT(0, rdata_rp.compare(large1_rp));
+ EXPECT_LT(0, large1_rp.compare(rdata_rp));
+
+ // yet another RP whose text name is larger than that of rdata_rp.
+ const generic::RP large2_rp("root.example.com. zzzzzzz.example.com.");
+ EXPECT_GT(0, rdata_rp.compare(large2_rp));
+ EXPECT_LT(0, large2_rp.compare(rdata_rp));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_rp.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
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..b198d15
--- /dev/null
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -0,0 +1,369 @@
+// 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 <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/time_utilities.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 <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..21f4dc4
--- /dev/null
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -0,0 +1,249 @@
+// Copyright (C) 2010-2017 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 = NULL,
+ 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",
+ NULL, false, false);
+
+ // Beginning and trailing space are ignored.
+ checkFromTextSOA<isc::Exception, isc::Exception>(
+ " ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200 ", NULL, 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",
+ NULL, false, false);
+
+ // multi-line.
+ checkFromTextSOA<isc::Exception, isc::Exception>(
+ "ns.example.com. (root.example.com.\n"
+ "2010012601 1H 5M 1000H) 20M", NULL, 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",
+ NULL, true, false);
+ checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>(
+ "\nns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M",
+ NULL, 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_srv_unittest.cc b/src/lib/dns/tests/rdata_srv_unittest.cc
new file mode 100644
index 0000000..a3296f2
--- /dev/null
+++ b/src/lib/dns/tests/rdata_srv_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright (C) 2011-2019 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 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_SRV_Test : public RdataTest {
+public:
+ Rdata_SRV_Test() :
+ srv_txt("1 5 1500 a.example.com."),
+ srv_txt2("1 5 1400 example.com."),
+ too_long_label("012345678901234567890123456789"
+ "0123456789012345678901234567890123."),
+ rdata_srv(srv_txt),
+ rdata_srv2(srv_txt2)
+ {}
+
+ const string srv_txt;
+ const string srv_txt2;
+ const string too_long_label;
+ const in::SRV rdata_srv;
+ const in::SRV rdata_srv2;
+};
+
+// 1 5 1500 a.example.com.
+const uint8_t wiredata_srv[] = {
+ 0x00, 0x01, 0x00, 0x05, 0x05, 0xdc, 0x01, 0x61, 0x07, 0x65, 0x78,
+ 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00};
+// 1 5 1400 example.com.
+const uint8_t wiredata_srv2[] = {
+ 0x00, 0x01, 0x00, 0x05, 0x05, 0x78, 0x07, 0x65, 0x78, 0x61, 0x6d,
+ 0x70, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00};
+
+TEST_F(Rdata_SRV_Test, createFromText) {
+ EXPECT_EQ(1, rdata_srv.getPriority());
+ EXPECT_EQ(5, rdata_srv.getWeight());
+ EXPECT_EQ(1500, rdata_srv.getPort());
+ EXPECT_EQ(Name("a.example.com."), rdata_srv.getTarget());
+}
+
+TEST_F(Rdata_SRV_Test, badText) {
+ // priority is too large (2814...6 is 2^48)
+ EXPECT_THROW(in::SRV("281474976710656 5 1500 a.example.com."),
+ InvalidRdataText);
+ // weight is too large
+ EXPECT_THROW(in::SRV("1 281474976710656 1500 a.example.com."),
+ InvalidRdataText);
+ // port is too large
+ EXPECT_THROW(in::SRV("1 5 281474976710656 a.example.com."),
+ InvalidRdataText);
+ // incomplete text
+ EXPECT_THROW(in::SRV("1 5 a.example.com."),
+ InvalidRdataText);
+ EXPECT_THROW(in::SRV("1 5 1500a.example.com."),
+ InvalidRdataText);
+ // bad name
+ EXPECT_THROW(in::SRV("1 5 1500 a.example.com." + too_long_label),
+ TooLongLabel);
+ // Extra text at end of line
+ EXPECT_THROW(in::SRV("1 5 1500 a.example.com. extra."), InvalidRdataText);
+}
+
+TEST_F(Rdata_SRV_Test, assignment) {
+ in::SRV copy((string(srv_txt2)));
+ copy = rdata_srv;
+ EXPECT_EQ(0, copy.compare(rdata_srv));
+
+ // Check if the copied data is valid even after the original is deleted
+ in::SRV* copy2 = new in::SRV(rdata_srv);
+ in::SRV copy3((string(srv_txt2)));
+ copy3 = *copy2;
+ delete copy2;
+ EXPECT_EQ(0, copy3.compare(rdata_srv));
+
+ // Self assignment
+ copy = *&copy;
+ EXPECT_EQ(0, copy.compare(rdata_srv));
+}
+
+TEST_F(Rdata_SRV_Test, createFromWire) {
+ EXPECT_EQ(0, rdata_srv.compare(
+ *rdataFactoryFromFile(RRType("SRV"), RRClass("IN"),
+ "rdata_srv_fromWire")));
+ // RDLENGTH is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType("SRV"), RRClass("IN"),
+ "rdata_srv_fromWire", 23),
+ InvalidRdataLength);
+ // RDLENGTH is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType("SRV"), RRClass("IN"),
+ "rdata_srv_fromWire", 46),
+ InvalidRdataLength);
+ // incomplete name. the error should be detected in the name constructor
+ EXPECT_THROW(rdataFactoryFromFile(RRType("SRV"), RRClass("IN"),
+ "rdata_cname_fromWire", 69),
+ DNSMessageFORMERR);
+ // parse compressed target name
+ EXPECT_EQ(0, rdata_srv.compare(
+ *rdataFactoryFromFile(RRType("SRV"), RRClass("IN"),
+ "rdata_srv_fromWire", 89)));
+}
+
+TEST_F(Rdata_SRV_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_srv.compare(
+ *test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "1 5 1500 a.example.com.")));
+
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ EXPECT_EQ(0, in::SRV("1 5 1500 server16.example.org.").compare(
+ *test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "1 5 1500 server16")));
+
+ // Exceptions cause NULL to be returned.
+
+ // Bad priority
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "65536 5 1500 "
+ "a.example.com."));
+ // Bad weight
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "1 65536 1500 "
+ "a.example.com."));
+ // Bad port
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "1 5 281474976710656 "
+ "a.example.com."));
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "1 5 1500 a.example.com. extra."));
+}
+
+TEST_F(Rdata_SRV_Test, toWireBuffer) {
+ rdata_srv.toWire(obuffer);
+ matchWireData(wiredata_srv, sizeof(wiredata_srv),
+ obuffer.getData(), obuffer.getLength());
+
+ obuffer.clear();
+ rdata_srv2.toWire(obuffer);
+ matchWireData(wiredata_srv2, sizeof(wiredata_srv2),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_SRV_Test, toWireRenderer) {
+ rdata_srv.toWire(renderer);
+ matchWireData(wiredata_srv, sizeof(wiredata_srv),
+ renderer.getData(), renderer.getLength());
+
+ renderer.clear();
+ rdata_srv2.toWire(renderer);
+ matchWireData(wiredata_srv2, sizeof(wiredata_srv2),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_SRV_Test, toText) {
+ EXPECT_EQ(srv_txt, rdata_srv.toText());
+ EXPECT_EQ(srv_txt2, rdata_srv2.toText());
+}
+
+TEST_F(Rdata_SRV_Test, compare) {
+ // test RDATAs, sorted in the ascending order.
+ vector<in::SRV> compare_set;
+ compare_set.push_back(in::SRV("1 5 1500 a.example.com."));
+ compare_set.push_back(in::SRV("2 5 1500 a.example.com."));
+ compare_set.push_back(in::SRV("2 6 1500 a.example.com."));
+ compare_set.push_back(in::SRV("2 6 1600 a.example.com."));
+ compare_set.push_back(in::SRV("2 6 1600 example.com."));
+
+ EXPECT_EQ(0, compare_set[0].compare(
+ in::SRV("1 5 1500 a.example.com.")));
+
+ vector<in::SRV>::const_iterator it;
+ vector<in::SRV>::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_srv.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
new file mode 100644
index 0000000..2bf88f3
--- /dev/null
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -0,0 +1,311 @@
+// Copyright (C) 2012-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 <algorithm>
+#include <string>
+
+#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/algorithm/string.hpp>
+
+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_SSHFP_Test : public RdataTest {
+protected:
+ Rdata_SSHFP_Test() :
+ sshfp_txt("2 1 123456789abcdef67890123456789abcdef67890"),
+ rdata_sshfp(sshfp_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::SSHFP, isc::Exception, isc::Exception>(
+ rdata_str, rdata_sshfp, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::SSHFP, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::SSHFP, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::SSHFP, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_sshfp, true, false);
+ }
+
+ const string sshfp_txt;
+ const generic::SSHFP rdata_sshfp;
+};
+
+const uint8_t rdata_sshfp_wiredata[] = {
+ // algorithm
+ 0x02,
+ // fingerprint type
+ 0x01,
+ // fingerprint
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf6,
+ 0x78, 0x90, 0x12, 0x34,
+ 0x56, 0x78, 0x9a, 0xbc,
+ 0xde, 0xf6, 0x78, 0x90
+};
+
+TEST_F(Rdata_SSHFP_Test, createFromText) {
+ // Basic test
+ checkFromText_None(sshfp_txt);
+
+ // With different spacing
+ checkFromText_None("2 1 123456789abcdef67890123456789abcdef67890");
+
+ // Combination of lowercase and uppercase
+ checkFromText_None("2 1 123456789ABCDEF67890123456789abcdef67890");
+
+ // spacing in the fingerprint field
+ checkFromText_None("2 1 123456789abcdef67890 123456789abcdef67890");
+
+ // multi-line fingerprint field
+ checkFromText_None("2 1 ( 123456789abcdef67890\n 123456789abcdef67890 )");
+
+ // string constructor throws if there's extra text,
+ // but lexer constructor doesn't
+ checkFromText_BadString(sshfp_txt + "\n" + sshfp_txt);
+}
+
+TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
+ // Some of these may not be RFC conformant, but we relax the check
+ // in our code to work with algorithm and fingerprint types that may
+ // show up in the future.
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("1 1 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("2 1 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("3 1 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("128 1 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("255 1 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("1 1 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("1 2 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("1 3 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("1 128 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("1 255 12ab"));
+
+ // 0 is reserved, but we allow that too
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("0 1 12ab"));
+ EXPECT_NO_THROW(const generic::SSHFP rdata_sshfp("1 0 12ab"));
+
+ // > 255 would be broken
+ EXPECT_THROW(const generic::SSHFP rdata_sshfp("256 1 12ab"),
+ InvalidRdataText);
+ EXPECT_THROW(const generic::SSHFP rdata_sshfp("2 256 12ab"),
+ InvalidRdataText);
+}
+
+TEST_F(Rdata_SSHFP_Test, badText) {
+ checkFromText_LexerError("1");
+ checkFromText_LexerError("ONE 2 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("1 TWO 123456789abcdef67890123456789abcdef67890");
+ checkFromText_InvalidText("1 2 BUCKLEMYSHOE");
+ checkFromText_InvalidText(sshfp_txt + " extra text");
+
+ // yes, these are redundant to the last test cases in algorithmTypes
+ checkFromText_InvalidText(
+ "2345 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_InvalidText(
+ "2 1234 123456789abcdef67890123456789abcdef67890");
+
+ // negative values are trapped in the lexer rather than the constructor
+ checkFromText_LexerError("-2 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("2 -1 123456789abcdef67890123456789abcdef67890");
+}
+
+TEST_F(Rdata_SSHFP_Test, copyAndAssign) {
+ // Copy construct
+ generic::SSHFP rdata_sshfp2(rdata_sshfp);
+ EXPECT_EQ(0, rdata_sshfp.compare(rdata_sshfp2));
+
+ // Assignment, mainly to confirm it doesn't cause disruption.
+ rdata_sshfp2 = rdata_sshfp;
+ EXPECT_EQ(0, rdata_sshfp.compare(rdata_sshfp2));
+}
+
+TEST_F(Rdata_SSHFP_Test, createFromWire) {
+ // Basic test
+ EXPECT_EQ(0, rdata_sshfp.compare(
+ *rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire")));
+ // Combination of lowercase and uppercase
+ EXPECT_EQ(0, rdata_sshfp.compare(
+ *rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire2")));
+ // algorithm=1, fingerprint=1
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire3.wire"));
+
+ // algorithm=255, fingerprint=1
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire4.wire"));
+
+ // algorithm=0, fingerprint=1
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire5.wire"));
+
+ // algorithm=5, fingerprint=0
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire6.wire"));
+
+ // algorithm=255, fingerprint=255
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire7.wire"));
+
+ // short fingerprint data
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire8.wire"));
+
+ // fingerprint is shorter than rdata len
+ EXPECT_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire9"),
+ InvalidBufferPosition);
+
+ // fingerprint is missing
+ EXPECT_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire10"),
+ InvalidBufferPosition);
+
+ // all rdata is missing
+ EXPECT_THROW(rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire11"),
+ InvalidBufferPosition);
+}
+
+TEST_F(Rdata_SSHFP_Test, createFromParams) {
+ const generic::SSHFP rdata_sshfp2(
+ 2, 1, "123456789abcdef67890123456789abcdef67890");
+ EXPECT_EQ(0, rdata_sshfp2.compare(rdata_sshfp));
+}
+
+TEST_F(Rdata_SSHFP_Test, toText) {
+ EXPECT_TRUE(boost::iequals(sshfp_txt, rdata_sshfp.toText()));
+
+ const string sshfp_txt2("2 1");
+ const generic::SSHFP rdata_sshfp2(sshfp_txt2);
+ EXPECT_TRUE(boost::iequals(sshfp_txt2, rdata_sshfp2.toText()));
+
+ const generic::SSHFP rdata_sshfp3("2 1 ");
+ EXPECT_TRUE(boost::iequals(sshfp_txt2, rdata_sshfp3.toText()));
+}
+
+TEST_F(Rdata_SSHFP_Test, toWire) {
+ this->obuffer.clear();
+ rdata_sshfp.toWire(this->obuffer);
+
+ EXPECT_EQ(sizeof (rdata_sshfp_wiredata),
+ this->obuffer.getLength());
+
+ matchWireData(rdata_sshfp_wiredata, sizeof(rdata_sshfp_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_SSHFP_Test, compare) {
+ const generic::SSHFP rdata_sshfp2("2 1");
+ EXPECT_EQ(-1, rdata_sshfp2.compare(rdata_sshfp));
+ EXPECT_EQ(1, rdata_sshfp.compare(rdata_sshfp2));
+}
+
+TEST_F(Rdata_SSHFP_Test, getAlgorithmNumber) {
+ EXPECT_EQ(2, rdata_sshfp.getAlgorithmNumber());
+}
+
+TEST_F(Rdata_SSHFP_Test, getFingerprintType) {
+ EXPECT_EQ(1, rdata_sshfp.getFingerprintType());
+}
+
+TEST_F(Rdata_SSHFP_Test, getFingerprint) {
+ const std::vector<uint8_t>& fingerprint =
+ rdata_sshfp.getFingerprint();
+
+ EXPECT_EQ(rdata_sshfp.getFingerprintLength(),
+ fingerprint.size());
+ for (size_t i = 0; i < fingerprint.size(); ++i) {
+ EXPECT_EQ(rdata_sshfp_wiredata[i + 2],
+ fingerprint.at(i));
+ }
+}
+
+TEST_F(Rdata_SSHFP_Test, getFingerprintLength) {
+ EXPECT_EQ(20, rdata_sshfp.getFingerprintLength());
+}
+
+TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromWire) {
+ const uint8_t rdf_wiredata[] = {
+ // algorithm
+ 0x04,
+ // fingerprint type
+ 0x09
+ };
+
+ const generic::SSHFP rdf =
+ dynamic_cast<const generic::SSHFP&>
+ (*rdataFactoryFromFile(RRType("SSHFP"), RRClass("IN"),
+ "rdata_sshfp_fromWire12"));
+
+ EXPECT_EQ(4, rdf.getAlgorithmNumber());
+ EXPECT_EQ(9, rdf.getFingerprintType());
+ EXPECT_EQ(0, rdf.getFingerprintLength());
+
+ this->obuffer.clear();
+ rdf.toWire(this->obuffer);
+
+ EXPECT_EQ(2, this->obuffer.getLength());
+
+ matchWireData(rdf_wiredata, sizeof(rdf_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_SSHFP_Test, emptyFingerprintFromString) {
+ const generic::SSHFP rdata_sshfp2("5 6");
+ const uint8_t rdata_sshfp2_wiredata[] = {
+ // algorithm
+ 0x05,
+ // fingerprint type
+ 0x06
+ };
+
+ EXPECT_EQ(5, rdata_sshfp2.getAlgorithmNumber());
+ EXPECT_EQ(6, rdata_sshfp2.getFingerprintType());
+ EXPECT_EQ(0, rdata_sshfp2.getFingerprintLength());
+
+ this->obuffer.clear();
+ rdata_sshfp2.toWire(this->obuffer);
+
+ EXPECT_EQ(2, this->obuffer.getLength());
+
+ matchWireData(rdata_sshfp2_wiredata, sizeof(rdata_sshfp2_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+}
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..8592a27
--- /dev/null
+++ b/src/lib/dns/tests/rdata_tkey_unittest.cc
@@ -0,0 +1,450 @@
+// Copyright (C) 2021 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 <util/time_utilities.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 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_EQ(static_cast<void*>(0), rdata_tkey.getKey());
+ EXPECT_EQ(0, rdata_tkey.getOtherLen());
+ EXPECT_EQ(static_cast<void*>(0), 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_EQ(static_cast<const void*>(0), 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_EQ(static_cast<const void*>(0), 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"),
+ InvalidBufferPosition);
+ // Other-data length is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(),
+ "rdata_tkey_fromWire9.wire"),
+ InvalidBufferPosition);
+}
+
+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_tlsa_unittest.cc b/src/lib/dns/tests/rdata_tlsa_unittest.cc
new file mode 100644
index 0000000..92970cb
--- /dev/null
+++ b/src/lib/dns/tests/rdata_tlsa_unittest.cc
@@ -0,0 +1,276 @@
+// Copyright (C) 2014-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 <algorithm>
+#include <string>
+
+#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/algorithm/string.hpp>
+
+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_TLSA_Test : public RdataTest {
+protected:
+ Rdata_TLSA_Test() :
+ tlsa_txt("0 0 1 d2abde240d7cd3ee6b4b28c54df034b9"
+ "7983a1d16e8a410e4561cb106618e971"),
+ rdata_tlsa(tlsa_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::TLSA, isc::Exception, isc::Exception>(
+ rdata_str, rdata_tlsa, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::TLSA, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_tlsa, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::TLSA, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_tlsa, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::TLSA, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_tlsa, true, false);
+ }
+
+ const string tlsa_txt;
+ const generic::TLSA rdata_tlsa;
+};
+
+const uint8_t rdata_tlsa_wiredata[] = {
+ // certificate usage
+ 0x00,
+ // selector
+ 0x00,
+ // matching type
+ 0x01,
+ // certificate association data
+ 0xd2, 0xab, 0xde, 0x24, 0x0d, 0x7c, 0xd3, 0xee,
+ 0x6b, 0x4b, 0x28, 0xc5, 0x4d, 0xf0, 0x34, 0xb9,
+ 0x79, 0x83, 0xa1, 0xd1, 0x6e, 0x8a, 0x41, 0x0e,
+ 0x45, 0x61, 0xcb, 0x10, 0x66, 0x18, 0xe9, 0x71
+};
+
+TEST_F(Rdata_TLSA_Test, createFromText) {
+ // Basic test
+ checkFromText_None(tlsa_txt);
+
+ // With different spacing
+ checkFromText_None("0 0 1 d2abde240d7cd3ee6b4b28c54df034b9"
+ "7983a1d16e8a410e4561cb106618e971");
+
+ // Combination of lowercase and uppercase
+ checkFromText_None("0 0 1 D2ABDE240D7CD3EE6B4B28C54DF034B9"
+ "7983a1d16e8a410e4561cb106618e971");
+
+ // spacing in the certificate association data field
+ checkFromText_None("0 0 1 d2abde240d7cd3ee6b4b28c54df034b9"
+ " 7983a1d16e8a410e4561cb106618e971");
+
+ // multi-line certificate association data field
+ checkFromText_None("0 0 1 ( d2abde240d7cd3ee6b4b28c54df034b9\n"
+ " 7983a1d16e8a410e4561cb106618e971 )");
+
+ // string constructor throws if there's extra text,
+ // but lexer constructor doesn't
+ checkFromText_BadString(tlsa_txt + "\n" + tlsa_txt);
+}
+
+TEST_F(Rdata_TLSA_Test, fields) {
+ // Some of these may not be RFC conformant, but we relax the check
+ // in our code to work with other field values that may show up in
+ // the future.
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("1 0 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("2 0 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("3 0 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("128 0 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("255 0 1 12ab"));
+
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 1 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 2 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 3 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 128 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 255 1 12ab"));
+
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 1 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 2 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 3 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 128 12ab"));
+ EXPECT_NO_THROW(const generic::TLSA rdata_tlsa("0 0 255 12ab"));
+
+ // > 255 would be broken
+ EXPECT_THROW(const generic::TLSA rdata_tlsa("256 0 1 12ab"),
+ InvalidRdataText);
+ EXPECT_THROW(const generic::TLSA rdata_tlsa("0 256 1 12ab"),
+ InvalidRdataText);
+ EXPECT_THROW(const generic::TLSA rdata_tlsa("0 0 256 12ab"),
+ InvalidRdataText);
+}
+
+TEST_F(Rdata_TLSA_Test, badText) {
+ checkFromText_LexerError("1");
+ checkFromText_LexerError("ONE 2 3 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("1 TWO 3 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("1 2 THREE 123456789abcdef67890123456789abcdef67890");
+ checkFromText_InvalidText("1 2 3 BAABAABLACKSHEEP");
+ checkFromText_InvalidText(tlsa_txt + " extra text");
+
+ // yes, these are redundant to the last test cases in the .fields
+ // test
+ checkFromText_InvalidText(
+ "2345 1 2 123456789abcdef67890123456789abcdef67890");
+ checkFromText_InvalidText(
+ "3 1234 4 123456789abcdef67890123456789abcdef67890");
+ checkFromText_InvalidText(
+ "5 6 1234 123456789abcdef67890123456789abcdef67890");
+
+ // negative values are trapped in the lexer rather than the
+ // constructor
+ checkFromText_LexerError("-2 0 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("0 -2 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("0 0 -2 123456789abcdef67890123456789abcdef67890");
+}
+
+TEST_F(Rdata_TLSA_Test, copyAndAssign) {
+ // Copy construct
+ generic::TLSA rdata_tlsa2(rdata_tlsa);
+ EXPECT_EQ(0, rdata_tlsa.compare(rdata_tlsa2));
+
+ // Assignment, mainly to confirm it doesn't cause disruption.
+ rdata_tlsa2 = rdata_tlsa;
+ EXPECT_EQ(0, rdata_tlsa.compare(rdata_tlsa2));
+}
+
+TEST_F(Rdata_TLSA_Test, createFromWire) {
+ // Basic test
+ EXPECT_EQ(0, rdata_tlsa.compare(
+ *rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire")));
+ // Combination of lowercase and uppercase
+ EXPECT_EQ(0, rdata_tlsa.compare(
+ *rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire2")));
+ // certificate_usage=0, selector=0, matching_type=1
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire3.wire"));
+
+ // certificate_usage=255, selector=0, matching_type=1
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire4.wire"));
+
+ // certificate_usage=0, selector=255, matching_type=1
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire5.wire"));
+
+ // certificate_usage=0, selector=0, matching_type=255
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire6.wire"));
+
+ // certificate_usage=3, selector=1, matching_type=2
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire7.wire"));
+
+ // short certificate association data
+ EXPECT_NO_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire8.wire"));
+
+ // certificate association data is shorter than rdata len
+ EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire9"),
+ InvalidBufferPosition);
+
+ // certificate association data is missing
+ EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire10"),
+ InvalidBufferPosition);
+
+ // certificate association data is empty
+ EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire12"),
+ InvalidRdataLength);
+
+ // all RDATA is missing
+ EXPECT_THROW(rdataFactoryFromFile(RRType("TLSA"), RRClass("IN"),
+ "rdata_tlsa_fromWire11"),
+ InvalidBufferPosition);
+}
+
+TEST_F(Rdata_TLSA_Test, createFromParams) {
+ const generic::TLSA rdata_tlsa2(
+ 0, 0, 1, "d2abde240d7cd3ee6b4b28c54df034b9"
+ "7983a1d16e8a410e4561cb106618e971");
+ EXPECT_EQ(0, rdata_tlsa2.compare(rdata_tlsa));
+
+ // empty certificate association data should throw
+ EXPECT_THROW(const generic::TLSA rdata_tlsa2(0, 0, 1, ""),
+ InvalidRdataText);
+}
+
+TEST_F(Rdata_TLSA_Test, toText) {
+ EXPECT_TRUE(boost::iequals(tlsa_txt, rdata_tlsa.toText()));
+}
+
+TEST_F(Rdata_TLSA_Test, toWire) {
+ this->obuffer.clear();
+ rdata_tlsa.toWire(this->obuffer);
+
+ EXPECT_EQ(sizeof (rdata_tlsa_wiredata),
+ this->obuffer.getLength());
+
+ matchWireData(rdata_tlsa_wiredata, sizeof(rdata_tlsa_wiredata),
+ obuffer.getData(), obuffer.getLength());
+}
+
+TEST_F(Rdata_TLSA_Test, compare) {
+ const generic::TLSA rdata_tlsa2("0 0 0 d2abde240d7cd3ee6b4b28c54df034b9"
+ "7983a1d16e8a410e4561cb106618e971");
+ EXPECT_EQ(-1, rdata_tlsa2.compare(rdata_tlsa));
+ EXPECT_EQ(1, rdata_tlsa.compare(rdata_tlsa2));
+}
+
+TEST_F(Rdata_TLSA_Test, getCertificateUsage) {
+ EXPECT_EQ(0, rdata_tlsa.getCertificateUsage());
+}
+
+TEST_F(Rdata_TLSA_Test, getSelector) {
+ EXPECT_EQ(0, rdata_tlsa.getSelector());
+}
+
+TEST_F(Rdata_TLSA_Test, getMatchingType) {
+ EXPECT_EQ(1, rdata_tlsa.getMatchingType());
+}
+
+TEST_F(Rdata_TLSA_Test, getDataLength) {
+ EXPECT_EQ(32, rdata_tlsa.getDataLength());
+}
+}
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..9d3bd89
--- /dev/null
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -0,0 +1,423 @@
+// Copyright (C) 2010-2023 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_EQ(static_cast<void*>(NULL), 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_EQ(static_cast<void*>(NULL), 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_EQ(static_cast<const void*>(NULL), 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_EQ(static_cast<const void*>(NULL), 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"),
+ InvalidBufferPosition);
+ // Other-data length is bogus:
+ EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+ "rdata_tsig_fromWire9.wire"),
+ InvalidBufferPosition);
+}
+
+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, NULL, 16020, 17, 0, NULL)));
+
+ 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, NULL)));
+
+ 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, NULL),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 12, NULL, 16020, 18, 0, NULL),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, NULL, 16020, 18, 0, fake_data),
+ isc::InvalidParameter);
+ EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, NULL, 16020, 18, 6, NULL),
+ 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..e2828c2
--- /dev/null
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -0,0 +1,397 @@
+// Copyright (C) 2011-2020 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 and SPF 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()) {}
+template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}
+
+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, generic::SPF> 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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, NULL, 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 (std::vector<std::string >::const_iterator it = texts.begin();
+ it != texts.end(); ++it) {
+ 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 (std::vector<std::string >::const_iterator it = texts.begin();
+ it != texts.end(); ++it) {
+ SCOPED_TRACE(*it);
+ EXPECT_EQ(0, TypeParam(*it).compare(*rdata));
+
+ EXPECT_EQ(0, TypeParam(this->lexer, NULL, 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(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..afc16a1
--- /dev/null
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -0,0 +1,467 @@
+// Copyright (C) 2010-2023 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 {
+RdataTest::RdataTest() :
+ obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0")),
+ loader_cb(MasterLoaderCallbacks::getNullCallbacks())
+{}
+
+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 =
+ MasterLoaderCallbacks::getNullCallbacks();
+ 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,
+ NULL, 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, NULL,
+ MasterLoader::MANY_ERRORS, callbacks);
+ EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+ EXPECT_FALSE(callback.isCalled());
+
+ // Broken RDATA text: extra token. createRdata() returns NULL, error
+ // callback is called.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+ 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, NULL,
+ 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, NULL,
+ 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, NULL,
+ 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, NULL,
+ 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),
+ InvalidBufferPosition);
+
+ // 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..0c9178e
--- /dev/null
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -0,0 +1,92 @@
+// 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/.
+
+#ifndef RDATA_UNITTEST_H
+#define RDATA_UNITTEST_H 1
+
+#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 = NULL)
+ {
+ 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/tests/rdatafields_unittest.cc b/src/lib/dns/tests/rdatafields_unittest.cc
new file mode 100644
index 0000000..0531439
--- /dev/null
+++ b/src/lib/dns/tests/rdatafields_unittest.cc
@@ -0,0 +1,366 @@
+// 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 <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatafields.h>
+#include <dns/tests/unittest_util.h>
+
+#include <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::UnitTestUtil;
+using isc::util::OutputBuffer;
+using isc::util::InputBuffer;
+using isc::util::unittests::matchWireData;
+
+namespace {
+class RdataFieldsTest : public ::testing::Test {
+protected:
+ RdataFieldsTest() : obuffer(0), ns_name("example.com"),
+ other_name("www.example.com")
+ {}
+ void constructCommonTests(const RdataFields& fields,
+ const uint8_t* const expected_data,
+ const size_t expected_data_len);
+ void constructCommonTestsNS(const RdataFields& fields);
+ void constructCommonTestsTXT(const RdataFields& fields);
+ void constructCommonTestsRRSIG(const RdataFields& fields);
+ void constructCommonTestsOPT(const RdataFields& fields);
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+ const Name ns_name;
+ const Name other_name;
+ vector<unsigned char> expected_wire;
+ vector<unsigned char> fields_wire;
+};
+
+const uint8_t in_a_data[] = { 192, 0, 2, 1 };
+// binary representation of example.com.
+const uint8_t ns_data[] = { 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00 };
+
+//
+// IN/A RDATA: fixed length, single data field
+//
+void
+RdataFieldsTest::constructCommonTests(const RdataFields& fields,
+ const uint8_t* const expected_data,
+ const size_t expected_data_len)
+{
+ matchWireData(expected_data, expected_data_len,
+ fields.getData(), fields.getDataLength());
+
+ EXPECT_EQ(sizeof(RdataFields::FieldSpec), fields.getFieldSpecDataSize());
+ EXPECT_EQ(1, fields.getFieldCount());
+ EXPECT_EQ(RdataFields::DATA, fields.getFieldSpec(0).type);
+ EXPECT_EQ(4, fields.getFieldSpec(0).len);
+
+ fields.toWire(obuffer);
+ matchWireData(expected_data, expected_data_len,
+ obuffer.getData(), obuffer.getLength());
+
+ fields.toWire(renderer);
+ matchWireData(expected_data, expected_data_len,
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(RdataFieldsTest, constructFromRdata) {
+ const RdataFields fields(in::A("192.0.2.1"));
+ constructCommonTests(fields, in_a_data, sizeof(in_a_data));
+}
+
+TEST_F(RdataFieldsTest, constructFromParams) {
+ const RdataFields::FieldSpec spec(RdataFields::DATA, 4);
+ const RdataFields fields(&spec, sizeof(spec), in_a_data,
+ sizeof(in_a_data));
+ constructCommonTests(fields, in_a_data, sizeof(in_a_data));
+}
+
+//
+// NS RDATA: containing a compressible name.
+//
+void
+RdataFieldsTest::constructCommonTestsNS(const RdataFields& fields) {
+ EXPECT_EQ(sizeof(RdataFields::FieldSpec), fields.getFieldSpecDataSize());
+ EXPECT_EQ(1, fields.getFieldCount());
+ EXPECT_EQ(RdataFields::COMPRESSIBLE_NAME, fields.getFieldSpec(0).type);
+ EXPECT_EQ(ns_name.getLength(), fields.getFieldSpec(0).len);
+
+ expected_wire.clear();
+ UnitTestUtil::readWireData("rdatafields1.wire", expected_wire);
+ other_name.toWire(obuffer);
+ fields.toWire(obuffer);
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ obuffer.getData(), obuffer.getLength());
+
+ expected_wire.clear();
+ UnitTestUtil::readWireData("rdatafields2.wire", expected_wire);
+ other_name.toWire(renderer);
+ fields.toWire(renderer);
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(RdataFieldsTest, constructFromRdataNS) {
+ const RdataFields fields_ns((generic::NS(ns_name)));
+ constructCommonTestsNS(fields_ns);
+}
+
+TEST_F(RdataFieldsTest, constructFromParamsNS) {
+ const RdataFields::FieldSpec spec(RdataFields::COMPRESSIBLE_NAME,
+ sizeof(ns_data));
+ const RdataFields fields_ns(&spec, sizeof(spec), ns_data, sizeof(ns_data));
+ constructCommonTestsNS(fields_ns);
+}
+
+//
+// TXT RDATA: multiple fields, lengths vary
+//
+void
+RdataFieldsTest::constructCommonTestsTXT(const RdataFields& fields) {
+ // Since all fields are plain data, they are handled as a single data
+ // field.
+ EXPECT_EQ(sizeof(RdataFields::FieldSpec), fields.getFieldSpecDataSize());
+ EXPECT_EQ(1, fields.getFieldCount());
+ EXPECT_EQ(RdataFields::DATA, fields.getFieldSpec(0).type);
+ EXPECT_EQ(expected_wire.size(), fields.getFieldSpec(0).len);
+
+ fields.toWire(obuffer);
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ obuffer.getData(), obuffer.getLength());
+
+ fields.toWire(renderer);
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(RdataFieldsTest, constructFromRdataTXT) {
+ UnitTestUtil::readWireData("rdatafields3.wire", expected_wire);
+ InputBuffer ibuffer(&expected_wire[0], expected_wire.size());
+ const uint16_t rdlen = ibuffer.readUint16();
+ const RdataFields fields(generic::TXT(ibuffer, rdlen));
+
+ // drop the RDLEN part
+ expected_wire.erase(expected_wire.begin(), expected_wire.begin() + 2);
+
+ constructCommonTestsTXT(fields);
+}
+
+TEST_F(RdataFieldsTest, constructFromParamsTXT) {
+ UnitTestUtil::readWireData("rdatafields3.wire", expected_wire);
+ expected_wire.erase(expected_wire.begin(), expected_wire.begin() + 2);
+ const RdataFields::FieldSpec spec(RdataFields::DATA, expected_wire.size());
+ const RdataFields fields(&spec, sizeof(spec), &expected_wire[0],
+ expected_wire.size());
+ constructCommonTestsTXT(fields);
+}
+
+//
+// RRSIG: multiple fields, with an incompressible name
+//
+void
+RdataFieldsTest::constructCommonTestsRRSIG(const RdataFields& fields) {
+ // In terms of RdataFields RRSIG RDATA consists of 3 fields:
+ // - 18-byte data field (from the "type covered" field to "key tag" field)
+ // - an incompressible name field (for the signer's name field).
+ // this is a variable length field. In this test it's a 13-byte field.
+ // - a variable-length data field for the signature. In this tests
+ // it's a 15-byte field.
+ EXPECT_EQ(3 * sizeof(RdataFields::FieldSpec),
+ fields.getFieldSpecDataSize());
+ EXPECT_EQ(3, fields.getFieldCount());
+ EXPECT_EQ(RdataFields::DATA, fields.getFieldSpec(0).type);
+ EXPECT_EQ(18, fields.getFieldSpec(0).len);
+ EXPECT_EQ(RdataFields::INCOMPRESSIBLE_NAME, fields.getFieldSpec(1).type);
+ EXPECT_EQ(13, fields.getFieldSpec(1).len);
+ EXPECT_EQ(RdataFields::DATA, fields.getFieldSpec(2).type);
+ EXPECT_EQ(15, fields.getFieldSpec(2).len);
+
+ expected_wire.clear();
+ UnitTestUtil::readWireData("rdatafields5.wire", expected_wire);
+ Name("com").toWire(obuffer);
+ obuffer.writeUint16(fields.getDataLength());
+ fields.toWire(obuffer);
+ other_name.toWire(obuffer);
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ obuffer.getData(), obuffer.getLength());
+
+ expected_wire.clear();
+ UnitTestUtil::readWireData("rdatafields6.wire", expected_wire);
+ Name("com").toWire(renderer);
+ renderer.writeUint16(fields.getDataLength());
+ fields.toWire(renderer); // the signer field won't be compressed
+ other_name.toWire(renderer); // but will be used as a compression target
+ matchWireData(&expected_wire[0], expected_wire.size(),
+ renderer.getData(), renderer.getLength());
+}
+
+TEST_F(RdataFieldsTest, constructFromRdataRRSIG) {
+ UnitTestUtil::readWireData("rdatafields4.wire", expected_wire);
+ InputBuffer ibuffer(&expected_wire[0], expected_wire.size());
+ const uint16_t rdlen = ibuffer.readUint16();
+ const RdataFields fields(generic::RRSIG(ibuffer, rdlen));
+
+ // drop the RDLEN part
+ expected_wire.erase(expected_wire.begin(), expected_wire.begin() + 2);
+
+ constructCommonTestsRRSIG(fields);
+}
+
+TEST_F(RdataFieldsTest, constructFromParamsRRSIG) {
+ UnitTestUtil::readWireData("rdatafields4.wire", fields_wire);
+ fields_wire.erase(fields_wire.begin(), fields_wire.begin() + 2);
+
+ const RdataFields::FieldSpec specs[] = {
+ RdataFields::FieldSpec(RdataFields::DATA, 18),
+ RdataFields::FieldSpec(RdataFields::INCOMPRESSIBLE_NAME, 13),
+ RdataFields::FieldSpec(RdataFields::DATA, 15)
+ };
+ const RdataFields fields(specs, sizeof(specs), &fields_wire[0],
+ fields_wire.size());
+ constructCommonTestsRRSIG(fields);
+}
+
+TEST_F(RdataFieldsTest, convertRdatatoParams) {
+ // Confirm we can restore the original data from the serialized data.
+ // We use RRSIG as a relatively complicated field structure.
+ UnitTestUtil::readWireData("rdatafields4.wire", expected_wire);
+ InputBuffer ibuffer(&expected_wire[0], expected_wire.size());
+ const uint16_t rdlen = ibuffer.readUint16();
+ const RdataFields fields(generic::RRSIG(ibuffer, rdlen));
+
+ expected_wire.erase(expected_wire.begin(), expected_wire.begin() + 2);
+
+ // Copy the data in separate storage
+ vector<uint8_t> spec_store(fields.getFieldSpecDataSize());
+ void* cp_spec = &spec_store[0];
+ memcpy(cp_spec, fields.getFieldSpecData(), spec_store.size());
+ vector<uint8_t> data_store(fields.getDataLength());
+ memcpy(&data_store[0], fields.getData(), fields.getDataLength());
+
+ // Restore the data in the form of RdataFields
+ const RdataFields fields_byparams(cp_spec, fields.getFieldSpecDataSize(),
+ &data_store[0], fields.getDataLength());
+
+ // Check it's valid
+ constructCommonTestsRRSIG(fields_byparams);
+}
+
+//
+// OPT: an empty RDATA
+//
+void
+RdataFieldsTest::constructCommonTestsOPT(const RdataFields& fields) {
+ EXPECT_EQ(0, fields.getFieldSpecDataSize());
+ EXPECT_EQ(0, fields.getFieldCount());
+ EXPECT_EQ(0, fields.getDataLength());
+ EXPECT_EQ((const uint8_t*) NULL, fields.getData());
+ fields.toWire(obuffer);
+ EXPECT_EQ(0, obuffer.getLength());
+ fields.toWire(renderer);
+ EXPECT_EQ(0, renderer.getLength());
+}
+
+TEST_F(RdataFieldsTest, constructFromRdataOPT) {
+ InputBuffer ibuffer(NULL, 0);
+ const RdataFields fields(generic::OPT(ibuffer, 0));
+ constructCommonTestsOPT(fields);
+}
+
+TEST_F(RdataFieldsTest, constructFromParamsOPT) {
+ const RdataFields fields(NULL, 0, NULL, 0);
+ constructCommonTestsOPT(fields);
+}
+
+// Invalid input to the "from parameter" constructor: sum of the field lengths
+// is not equal to the data length.
+TEST_F(RdataFieldsTest, invalidFieldLength) {
+ UnitTestUtil::readWireData("rdatafields4.wire", fields_wire);
+ fields_wire.erase(fields_wire.begin(), fields_wire.begin() + 2);
+
+ const RdataFields::FieldSpec specs[] = {
+ RdataFields::FieldSpec(RdataFields::DATA, 18),
+ RdataFields::FieldSpec(RdataFields::INCOMPRESSIBLE_NAME, 13),
+ RdataFields::FieldSpec(RdataFields::DATA, 14)
+ };
+ // sum of field len < data len
+ EXPECT_THROW(RdataFields(specs, 3, &fields_wire[0], fields_wire.size()),
+ isc::InvalidParameter);
+ // sum of field len > data len
+ EXPECT_THROW(RdataFields(specs, 3, &fields_wire[0],
+ fields_wire.size() - 2),
+ isc::InvalidParameter);
+}
+
+// Invalid input to the "from parameter" constructor: NULL vs length mismatch
+TEST_F(RdataFieldsTest, mismatchFieldLengthAndData) {
+ const unsigned char dummy_data = 0;
+ const RdataFields::FieldSpec dummy_spec(RdataFields::DATA, 1);
+
+ EXPECT_THROW(RdataFields(NULL, 1, &dummy_data, 1), isc::InvalidParameter);
+ EXPECT_THROW(RdataFields(&dummy_spec, 0, NULL, 0), isc::InvalidParameter);
+ EXPECT_THROW(RdataFields(&dummy_spec, 1, NULL, 1), isc::InvalidParameter);
+ EXPECT_THROW(RdataFields(NULL, 0, &dummy_data, 0), isc::InvalidParameter);
+}
+
+// Bogus input to getFieldSpec()
+TEST_F(RdataFieldsTest, getFieldSpecWithBadFieldId) {
+ const RdataFields fields_in_a(in::A("192.0.2.1"));
+ EXPECT_THROW(fields_in_a.getFieldSpec(1), isc::OutOfRange);
+}
+
+// Tests for unexpected methods in RdataFieldComposerTest. Confirm
+// a call to these methods triggers an exception. Expected methods are
+// tested via other tests above.
+class DummyRdata : public Rdata {
+public:
+ enum Mode { CLEAR, SKIP, TRIM };
+ explicit DummyRdata(Mode mode) : mode_(mode) {}
+ DummyRdata(const DummyRdata& source) : Rdata(), mode_(source.mode_) {}
+ virtual ~DummyRdata() {}
+ virtual void toWire(AbstractMessageRenderer& renderer) const {
+ // call the unexpected method corresponding to the test mode.
+ // method parameters don't matter.
+ switch (mode_) {
+ case CLEAR:
+ renderer.clear();
+ break;
+ case SKIP:
+ renderer.skip(2);
+ break;
+ case TRIM:
+ renderer.trim(2);
+ break;
+ }
+ }
+
+ // These are defined only to make the compiler happy. We don't use them
+ // for the test.
+ virtual string toText() const { return (""); }
+ virtual void toWire(OutputBuffer&) const {}
+ virtual int compare(const Rdata&) const { return (0); }
+private:
+ const int mode_;
+};
+
+TEST(RdataFieldComposerTest, unusedMethods) {
+ EXPECT_THROW(RdataFields(DummyRdata(DummyRdata::CLEAR)), isc::Unexpected);
+}
+}
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
new file mode 100644
index 0000000..ae85f01
--- /dev/null
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -0,0 +1,174 @@
+// Copyright (C) 2010-2017 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/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 ClassParam {
+ const char* const txt; // "IN", "CH", etc
+ const uint16_t code; // 1, 3,
+ const RRClass& (*obj)(); // RRClass::IN(), etc
+} known_classes[] = {
+ {"IN", 1, RRClass::IN}, {"CH", 3, RRClass::CH}, {"HS", 4, RRClass::HS},
+ {"NONE", 254, RRClass::NONE}, {"ANY", 255, RRClass::ANY},
+ {NULL, 0, NULL}
+};
+
+TEST(RRClassConstTest, wellKnowns) {
+ for (int i = 0; known_classes[i].txt; ++i) {
+ SCOPED_TRACE("Checking well known RRClass: " +
+ string(known_classes[i].txt));
+ EXPECT_EQ(known_classes[i].code,
+ RRClass(known_classes[i].txt).getCode());
+ EXPECT_EQ(known_classes[i].code,
+ (*known_classes[i].obj)().getCode());
+ }
+}
+}
diff --git a/src/lib/dns/tests/rrcollator_unittest.cc b/src/lib/dns/tests/rrcollator_unittest.cc
new file mode 100644
index 0000000..4247911
--- /dev/null
+++ b/src/lib/dns/tests/rrcollator_unittest.cc
@@ -0,0 +1,208 @@
+// Copyright (C) 2012-2020 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/name.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/rrclass.h>
+#include <dns/rrcollator.h>
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <sstream>
+#include <vector>
+
+using std::vector;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+namespace ph = std::placeholders;
+
+namespace {
+
+typedef RRCollator::AddRRsetCallback AddRRsetCallback;
+
+void
+addRRset(const RRsetPtr& rrset, vector<ConstRRsetPtr>* to_append,
+ const bool* do_throw) {
+ if (*do_throw) {
+ isc_throw(isc::Unexpected, "faked failure");
+ }
+ to_append->push_back(rrset);
+}
+
+class RRCollatorTest : public ::testing::Test {
+protected:
+ RRCollatorTest() :
+ origin_("example.com"), rrclass_(RRClass::IN()), rrttl_(3600),
+ throw_from_callback_(false),
+ collator_(std::bind(addRRset, ph::_1, &rrsets_, &throw_from_callback_)),
+ rr_callback_(collator_.getCallback()),
+ a_rdata1_(createRdata(RRType::A(), rrclass_, "192.0.2.1")),
+ a_rdata2_(createRdata(RRType::A(), rrclass_, "192.0.2.2")),
+ txt_rdata_(createRdata(RRType::TXT(), rrclass_, "test")),
+ sig_rdata1_(createRdata(RRType::RRSIG(), rrclass_,
+ "A 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKE")),
+ sig_rdata2_(createRdata(RRType::RRSIG(), rrclass_,
+ "NS 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKE"))
+ {}
+
+ void checkRRset(const Name& expected_name, const RRClass& expected_class,
+ const RRType& expected_type, const RRTTL& expected_ttl,
+ const vector<ConstRdataPtr>& expected_rdatas) {
+ SCOPED_TRACE(expected_name.toText(true) + "/" +
+ expected_class.toText() + "/" + expected_type.toText());
+
+ // This test always clears rrsets_ to confirm RRsets are added
+ // one-by-one
+ ASSERT_EQ(1, rrsets_.size());
+
+ ConstRRsetPtr actual = rrsets_[0];
+ EXPECT_EQ(expected_name, actual->getName());
+ EXPECT_EQ(expected_class, actual->getClass());
+ EXPECT_EQ(expected_type, actual->getType());
+ EXPECT_EQ(expected_ttl, actual->getTTL());
+ ASSERT_EQ(expected_rdatas.size(), actual->getRdataCount());
+ vector<ConstRdataPtr>::const_iterator it = expected_rdatas.begin();
+ for (RdataIteratorPtr rit = actual->getRdataIterator();
+ !rit->isLast();
+ rit->next()) {
+ EXPECT_EQ(0, rit->getCurrent().compare(**it));
+ ++it;
+ }
+
+ rrsets_.clear();
+ }
+
+ const Name origin_;
+ const RRClass rrclass_;
+ const RRTTL rrttl_;
+ vector<ConstRRsetPtr> rrsets_;
+ bool throw_from_callback_;
+ RRCollator collator_;
+ AddRRCallback rr_callback_;
+ const RdataPtr a_rdata1_, a_rdata2_, txt_rdata_, sig_rdata1_, sig_rdata2_;
+ vector<ConstRdataPtr> rdatas_; // placeholder for expected data
+};
+
+TEST_F(RRCollatorTest, basicCases) {
+ // Add two RRs belonging to the same RRset. These will be buffered.
+ rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata1_);
+ EXPECT_TRUE(rrsets_.empty()); // not yet given as an RRset
+ rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata2_);
+ EXPECT_TRUE(rrsets_.empty()); // still not given
+
+ // Add another type of RR. This completes the construction of the A RRset,
+ // which will be given via the callback.
+ rr_callback_(origin_, rrclass_, RRType::TXT(), rrttl_, txt_rdata_);
+ rdatas_.push_back(a_rdata1_);
+ rdatas_.push_back(a_rdata2_);
+ checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
+
+ // Add the same type of RR but of different name. This should make another
+ // callback for the previous TXT RR.
+ rr_callback_(Name("txt.example.com"), rrclass_, RRType::TXT(), rrttl_,
+ txt_rdata_);
+ rdatas_.clear();
+ rdatas_.push_back(txt_rdata_);
+ checkRRset(origin_, rrclass_, RRType::TXT(), rrttl_, rdatas_);
+
+ // Add the same type and name of RR but of different class (rare case
+ // in practice)
+ rr_callback_(Name("txt.example.com"), RRClass::CH(), RRType::TXT(), rrttl_,
+ txt_rdata_);
+ rdatas_.clear();
+ rdatas_.push_back(txt_rdata_);
+ checkRRset(Name("txt.example.com"), rrclass_, RRType::TXT(), rrttl_,
+ rdatas_);
+
+ // Tell the collator we are done, then we'll see the last RR as an RRset.
+ collator_.flush();
+ checkRRset(Name("txt.example.com"), RRClass::CH(), RRType::TXT(), rrttl_,
+ rdatas_);
+
+ // Redundant flush() will be no-op.
+ collator_.flush();
+ EXPECT_TRUE(rrsets_.empty());
+}
+
+TEST_F(RRCollatorTest, minTTLFirst) {
+ // RRs of the same RRset but has different TTLs. The first RR has
+ // the smaller TTL, which should be used for the TTL of the RRset.
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(10), a_rdata1_);
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(20), a_rdata2_);
+ rdatas_.push_back(a_rdata1_);
+ rdatas_.push_back(a_rdata2_);
+ collator_.flush();
+ checkRRset(origin_, rrclass_, RRType::A(), RRTTL(10), rdatas_);
+}
+
+TEST_F(RRCollatorTest, maxTTLFirst) {
+ // RRs of the same RRset but has different TTLs. The second RR has
+ // the smaller TTL, which should be used for the TTL of the RRset.
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(20), a_rdata1_);
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(10), a_rdata2_);
+ rdatas_.push_back(a_rdata1_);
+ rdatas_.push_back(a_rdata2_);
+ collator_.flush();
+ checkRRset(origin_, rrclass_, RRType::A(), RRTTL(10), rdatas_);
+}
+
+TEST_F(RRCollatorTest, addRRSIGs) {
+ // RRSIG is special; they are also distinguished by their covered types.
+ rr_callback_(origin_, rrclass_, RRType::RRSIG(), rrttl_, sig_rdata1_);
+ rr_callback_(origin_, rrclass_, RRType::RRSIG(), rrttl_, sig_rdata2_);
+
+ rdatas_.push_back(sig_rdata1_);
+ checkRRset(origin_, rrclass_, RRType::RRSIG(), rrttl_, rdatas_);
+}
+
+TEST_F(RRCollatorTest, emptyFlush) {
+ collator_.flush();
+ EXPECT_TRUE(rrsets_.empty());
+}
+
+TEST_F(RRCollatorTest, throwFromCallback) {
+ // Adding an A RR
+ rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata1_);
+
+ // Adding a TXT RR, which would trigger RRset callback, but in this test
+ // it throws. The added TXT RR will be effectively lost.
+ throw_from_callback_ = true;
+ EXPECT_THROW(rr_callback_(origin_, rrclass_, RRType::TXT(), rrttl_,
+ txt_rdata_), isc::Unexpected);
+
+ // We'll only see the A RR.
+ throw_from_callback_ = false;
+ collator_.flush();
+ rdatas_.push_back(a_rdata1_);
+ checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
+}
+
+TEST_F(RRCollatorTest, withMasterLoader) {
+ // Test a simple case with MasterLoader. There shouldn't be anything
+ // special, but that's the mainly intended usage of the collator, so we
+ // check it explicitly.
+ std::istringstream ss("example.com. 3600 IN A 192.0.2.1\n");
+ MasterLoader loader(ss, origin_, rrclass_,
+ MasterLoaderCallbacks::getNullCallbacks(),
+ collator_.getCallback());
+ loader.load();
+ collator_.flush();
+ rdatas_.push_back(a_rdata1_);
+ checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
+}
+
+}
diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc
new file mode 100644
index 0000000..90574d0
--- /dev/null
+++ b/src/lib/dns/tests/rrparamregistry_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright (C) 2010-2020 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::util;
+using namespace isc::dns::rdata;
+
+namespace {
+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(MasterLoaderCallbacks::getNullCallbacks());
+ 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_collection_unittest.cc b/src/lib/dns/tests/rrset_collection_unittest.cc
new file mode 100644
index 0000000..be16ab5
--- /dev/null
+++ b/src/lib/dns/tests/rrset_collection_unittest.cc
@@ -0,0 +1,240 @@
+// Copyright (C) 2012-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/rrset_collection.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <gtest/gtest.h>
+
+#include <list>
+#include <fstream>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace std;
+
+namespace {
+
+class RRsetCollectionTest : public ::testing::Test {
+public:
+ RRsetCollectionTest() :
+ rrclass("IN"),
+ origin("example.org"),
+ collection(TEST_DATA_SRCDIR "/example.org", origin, rrclass)
+ {}
+
+ const RRClass rrclass;
+ const Name origin;
+ RRsetCollection collection;
+};
+
+TEST_F(RRsetCollectionTest, istreamConstructor) {
+ std::ifstream fs(TEST_DATA_SRCDIR "/example.org");
+ RRsetCollection collection2(fs, origin, rrclass);
+
+ RRsetCollectionBase::Iterator iter = collection.begin();
+ RRsetCollectionBase::Iterator iter2 = collection2.begin();
+ while (iter != collection.end()) {
+ ASSERT_TRUE(iter2 != collection2.end());
+ EXPECT_EQ((*iter).toText(), (*iter2).toText());
+ ++iter;
+ ++iter2;
+ }
+ ASSERT_TRUE(iter2 == collection2.end());
+}
+
+template <typename T, typename TP>
+void doFind(T& collection, const RRClass& rrclass) {
+ // Test the find() that returns ConstRRsetPtr
+ TP rrset = collection.find(Name("www.example.org"), rrclass, RRType::A());
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset->getClass());
+ EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+ // foo.example.org doesn't exist
+ rrset = collection.find(Name("foo.example.org"), rrclass, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, but not with MX
+ rrset = collection.find(Name("www.example.org"), rrclass, RRType::MX());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, with AAAA
+ rrset = collection.find(Name("www.example.org"), rrclass, RRType::AAAA());
+ EXPECT_TRUE(rrset);
+
+ // www.example.org with AAAA does not exist in RRClass::CH()
+ rrset = collection.find(Name("www.example.org"), RRClass::CH(),
+ RRType::AAAA());
+ EXPECT_FALSE(rrset);
+}
+
+TEST_F(RRsetCollectionTest, findConst) {
+ // Test the find() that returns ConstRRsetPtr
+ const RRsetCollection& ccln = collection;
+ doFind<const RRsetCollection, ConstRRsetPtr>(ccln, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, find) {
+ // Test the find() that returns RRsetPtr
+ doFind<RRsetCollection, RRsetPtr>(collection, rrclass);
+}
+
+void
+doAddAndRemove(RRsetCollection& collection, const RRClass& rrclass) {
+ // foo.example.org/A doesn't exist
+ RRsetPtr rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_FALSE(rrset_found);
+
+ // Add foo.example.org/A
+ RRsetPtr rrset(new BasicRRset(Name("foo.example.org"), rrclass, RRType::A(),
+ RRTTL(7200)));
+ rrset->addRdata(in::A("192.0.2.1"));
+ collection.addRRset(rrset);
+
+ // foo.example.org/A should now exist
+ rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_TRUE(rrset_found);
+ EXPECT_EQ(RRType::A(), rrset_found->getType());
+ EXPECT_EQ(RRTTL(7200), rrset_found->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset_found->getClass());
+ EXPECT_EQ(Name("foo.example.org"), rrset_found->getName());
+
+ // The collection must not be empty.
+ EXPECT_TRUE(collection.end() != collection.begin());
+
+ // Adding a duplicate RRset must throw.
+ EXPECT_THROW({
+ collection.addRRset(rrset);
+ }, isc::InvalidParameter);
+
+ // Remove foo.example.org/A, which should pass
+ EXPECT_TRUE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
+ // foo.example.org/A should not exist now
+ rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_FALSE(rrset_found);
+
+ // Removing foo.example.org/A should fail now
+ EXPECT_FALSE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
+}
+
+TEST_F(RRsetCollectionTest, addAndRemove) {
+ doAddAndRemove(collection, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, empty) {
+ RRsetCollection cln;
+
+ // Here, cln is empty.
+ EXPECT_TRUE(cln.end() == cln.begin());
+
+ doAddAndRemove(cln, rrclass);
+
+ // cln should be empty again here, after the add and remove
+ // operations.
+ EXPECT_TRUE(cln.end() == cln.begin());
+}
+
+TEST_F(RRsetCollectionTest, iteratorTest) {
+ // The collection must not be empty.
+ EXPECT_TRUE(collection.end() != collection.begin());
+
+ // Here, we just count the records and do some basic tests on them.
+ size_t count = 0;
+ for (RRsetCollection::Iterator it = collection.begin();
+ it != collection.end(); ++it) {
+ ++count;
+ const AbstractRRset& rrset = *it;
+ EXPECT_EQ(rrclass, rrset.getClass());
+ EXPECT_EQ(RRTTL(3600), rrset.getTTL());
+ }
+
+ // example.org master file has SOA, NS, A, AAAA
+ EXPECT_EQ(4, count);
+}
+
+// This is a dummy class which is used in iteratorCompareDifferent test
+// to compare iterators from different RRsetCollectionBase
+// implementations.
+class MyRRsetCollection : public RRsetCollectionBase {
+public:
+ MyRRsetCollection()
+ {}
+
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name&,
+ const isc::dns::RRClass&,
+ const isc::dns::RRType&) const {
+ return (ConstRRsetPtr());
+ }
+
+ typedef std::list<isc::dns::RRset> MyCollection;
+
+protected:
+ class MyIter : public RRsetCollectionBase::Iter {
+ public:
+ MyIter(MyCollection::iterator& iter) :
+ iter_(iter)
+ {}
+
+ virtual const isc::dns::AbstractRRset& getValue() {
+ return (*iter_);
+ }
+
+ virtual IterPtr getNext() {
+ MyCollection::iterator it = iter_;
+ ++it;
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+ virtual bool equals(Iter& other) {
+ const MyIter* other_real = dynamic_cast<MyIter*>(&other);
+ if (other_real == NULL) {
+ return (false);
+ }
+ return (iter_ == other_real->iter_);
+ }
+
+ private:
+ MyCollection::iterator iter_;
+ };
+
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ MyCollection::iterator it = dummy_list_.begin();
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ MyCollection::iterator it = dummy_list_.end();
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+private:
+ MyCollection dummy_list_;
+};
+
+TEST_F(RRsetCollectionTest, iteratorCompareDifferent) {
+ // Create objects of two different RRsetCollectionBase
+ // implementations.
+ RRsetCollection cln1;
+ MyRRsetCollection cln2;
+
+ // Comparing two iterators from different RRsetCollectionBase
+ // implementations must not throw.
+ EXPECT_TRUE(cln2.begin() != cln1.begin());
+ EXPECT_TRUE(cln1.end() != cln2.end());
+}
+
+} // namespace
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
new file mode 100644
index 0000000..0425812
--- /dev/null
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -0,0 +1,442 @@
+// 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 <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_EQ(static_cast<void*>(NULL), sp.get());
+
+ sp = rrset_aaaa->getRRsig();
+ EXPECT_NE(static_cast<void*>(NULL), sp.get());
+}
+
+TEST_F(RRsetRRSIGTest, addRRsig) {
+ RRsetPtr sp = rrset_a->getRRsig();
+ EXPECT_EQ(static_cast<void*>(NULL), sp.get());
+
+ 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_NE(static_cast<void*>(NULL), sp.get());
+ 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..3cada14
--- /dev/null
+++ b/src/lib/dns/tests/rrttl_unittest.cc
@@ -0,0 +1,279 @@
+// 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 <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..a2f8ba5
--- /dev/null
+++ b/src/lib/dns/tests/rrtype_unittest.cc
@@ -0,0 +1,195 @@
+// 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 <gtest/gtest.h>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.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 TypeParam {
+ const char* const txt; // "A", "AAAA", "NS", etc
+ const uint16_t code; // 1, 28, 2, etc
+ const RRType& (*obj)(); // RRType::A(), etc
+} known_types[] = {
+ {"A", 1, RRType::A}, {"NS", 2, RRType::NS}, {"MD", 3, RRType::MD},
+ {"MF", 4, RRType::MF}, {"CNAME", 5, RRType::CNAME},
+ {"SOA", 6, RRType::SOA}, {"MB", 7, RRType::MB}, {"MG", 8, RRType::MG},
+ {"MR", 9, RRType::MR}, {"NULL", 10, RRType::Null},
+ {"WKS", 11, RRType::WKS}, {"PTR", 12, RRType::PTR},
+ {"HINFO", 13, RRType::HINFO}, {"MINFO", 14, RRType::MINFO},
+ {"MX", 15, RRType::MX}, {"TXT", 16, RRType::TXT}, {"RP", 17, RRType::RP},
+ {"AFSDB", 18, RRType::AFSDB}, {"X25", 19, RRType::X25},
+ {"ISDN", 20, RRType::ISDN}, {"RT", 21, RRType::RT},
+ {"NSAP", 22, RRType::NSAP}, {"NSAP-PTR", 23, RRType::NSAP_PTR},
+ {"SIG", 24, RRType::SIG}, {"KEY", 25, RRType::KEY},
+ {"PX", 26, RRType::PX}, {"GPOS", 27, RRType::GPOS},
+ {"AAAA", 28, RRType::AAAA}, {"LOC", 29, RRType::LOC},
+ {"NXT", 30, RRType::NXT}, {"SRV", 33, RRType::SRV},
+ {"NAPTR", 35, RRType::NAPTR}, {"KX", 36, RRType::KX},
+ {"CERT", 37, RRType::CERT}, {"A6", 38, RRType::A6},
+ {"DNAME", 39, RRType::DNAME}, {"OPT", 41, RRType::OPT},
+ {"APL", 42, RRType::APL}, {"DS", 43, RRType::DS},
+ {"SSHFP", 44, RRType::SSHFP}, {"IPSECKEY", 45, RRType::IPSECKEY},
+ {"RRSIG", 46, RRType::RRSIG}, {"NSEC", 47, RRType::NSEC},
+ {"DNSKEY", 48, RRType::DNSKEY}, {"DHCID", 49, RRType::DHCID},
+ {"NSEC3", 50, RRType::NSEC3}, {"NSEC3PARAM", 51, RRType::NSEC3PARAM},
+ {"TLSA", 52, RRType::TLSA}, {"HIP", 55, RRType::HIP},
+ {"SPF", 99, RRType::SPF}, {"UNSPEC", 103, RRType::UNSPEC},
+ {"NID", 104, RRType::NID}, {"L32", 105, RRType::L32},
+ {"L64", 106, RRType::L64}, {"LP", 107, RRType::LP},
+ {"TKEY", 249, RRType::TKEY}, {"TSIG", 250, RRType::TSIG},
+ {"IXFR", 251, RRType::IXFR}, {"AXFR", 252, RRType::AXFR},
+ {"MAILB", 253, RRType::MAILB}, {"MAILA", 254, RRType::MAILA},
+ {"ANY", 255, RRType::ANY}, {"URI", 256, RRType::URI},
+ {"CAA", 257, RRType::CAA}, {"DLV", 32769, RRType::DLV},
+ {NULL, 0, NULL}
+};
+
+TEST(RRTypeConstTest, wellKnowns) {
+ for (int i = 0; known_types[i].txt; ++i) {
+ SCOPED_TRACE("Checking well known RRType: " +
+ string(known_types[i].txt));
+ EXPECT_EQ(known_types[i].code, RRType(known_types[i].txt).getCode());
+ EXPECT_EQ(known_types[i].code,
+ (*known_types[i].obj)().getCode());
+ }
+}
+}
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..305b0b0
--- /dev/null
+++ b/src/lib/dns/tests/serial_unittest.cc
@@ -0,0 +1,173 @@
+// 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 <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..e5c8081
--- /dev/null
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -0,0 +1,219 @@
+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 += rdatafields1.spec rdatafields2.spec rdatafields3.spec
+EXTRA_DIST += rdatafields4.spec rdatafields5.spec rdatafields6.spec
+EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire
+EXTRA_DIST += rdata_dnskey_fromWire.spec rdata_dnskey_empty_keydata_fromWire.spec
+EXTRA_DIST += rdata_dhcid_fromWire rdata_dhcid_toWire
+EXTRA_DIST += rdata_ds_fromWire rdata_in_a_fromWire rdata_in_aaaa_fromWire
+EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_mx_toWire2
+EXTRA_DIST += rdata_ns_fromWire
+EXTRA_DIST += rdata_nsec_fromWire1 rdata_nsec_fromWire2 rdata_nsec_fromWire3
+EXTRA_DIST += rdata_nsec_fromWire4.spec rdata_nsec_fromWire5.spec
+EXTRA_DIST += rdata_nsec_fromWire6.spec rdata_nsec_fromWire7.spec
+EXTRA_DIST += rdata_nsec_fromWire8.spec rdata_nsec_fromWire9.spec
+EXTRA_DIST += rdata_nsec_fromWire10.spec
+EXTRA_DIST += rdata_nsec_fromWire16.spec
+EXTRA_DIST += rdata_nsec3param_fromWire1
+EXTRA_DIST += rdata_nsec3param_fromWire2.spec
+EXTRA_DIST += rdata_nsec3param_fromWire11.spec
+EXTRA_DIST += rdata_nsec3param_fromWire13.spec
+EXTRA_DIST += rdata_nsec3_fromWire1 rdata_nsec3_fromWire1.spec
+EXTRA_DIST += rdata_nsec3_fromWire2.spec rdata_nsec3_fromWire3
+EXTRA_DIST += rdata_nsec3_fromWire4.spec rdata_nsec3_fromWire5.spec
+EXTRA_DIST += rdata_nsec3_fromWire6.spec rdata_nsec3_fromWire7.spec
+EXTRA_DIST += rdata_nsec3_fromWire8.spec rdata_nsec3_fromWire9.spec
+EXTRA_DIST += rdata_nsec3_fromWire10.spec rdata_nsec3_fromWire11.spec
+EXTRA_DIST += rdata_nsec3_fromWire12.spec rdata_nsec3_fromWire13.spec
+EXTRA_DIST += rdata_nsec3_fromWire14.spec rdata_nsec3_fromWire15.spec
+EXTRA_DIST += rdata_nsec3_fromWire16.spec rdata_nsec3_fromWire17.spec
+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_rp_fromWire1.spec rdata_rp_fromWire2.spec
+EXTRA_DIST += rdata_rp_fromWire3.spec rdata_rp_fromWire4.spec
+EXTRA_DIST += rdata_rp_fromWire5.spec rdata_rp_fromWire6.spec
+EXTRA_DIST += rdata_rp_toWire1.spec rdata_rp_toWire2.spec
+EXTRA_DIST += rdata_sshfp_fromWire rdata_sshfp_fromWire2
+EXTRA_DIST += rdata_sshfp_fromWire1.spec rdata_sshfp_fromWire2.spec
+EXTRA_DIST += rdata_sshfp_fromWire3.spec rdata_sshfp_fromWire4.spec
+EXTRA_DIST += rdata_sshfp_fromWire5.spec rdata_sshfp_fromWire6.spec
+EXTRA_DIST += rdata_sshfp_fromWire7.spec rdata_sshfp_fromWire8.spec
+EXTRA_DIST += rdata_sshfp_fromWire9 rdata_sshfp_fromWire10
+EXTRA_DIST += rdata_sshfp_fromWire11 rdata_sshfp_fromWire12
+EXTRA_DIST += rdata_afsdb_fromWire1.spec rdata_afsdb_fromWire2.spec
+EXTRA_DIST += rdata_afsdb_fromWire3.spec rdata_afsdb_fromWire4.spec
+EXTRA_DIST += rdata_afsdb_fromWire5.spec
+EXTRA_DIST += rdata_afsdb_toWire1.spec rdata_afsdb_toWire2.spec
+EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.spec
+EXTRA_DIST += rdata_srv_fromWire
+EXTRA_DIST += rdata_minfo_fromWire1.spec rdata_minfo_fromWire2.spec
+EXTRA_DIST += rdata_minfo_fromWire3.spec rdata_minfo_fromWire4.spec
+EXTRA_DIST += rdata_minfo_fromWire5.spec rdata_minfo_fromWire6.spec
+EXTRA_DIST += rdata_minfo_toWire1.spec rdata_minfo_toWire2.spec
+EXTRA_DIST += rdata_minfo_toWireUncompressed1.spec
+EXTRA_DIST += rdata_minfo_toWireUncompressed2.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_tlsa_fromWire rdata_tlsa_fromWire2
+EXTRA_DIST += rdata_tlsa_fromWire3.spec rdata_tlsa_fromWire4.spec
+EXTRA_DIST += rdata_tlsa_fromWire5.spec rdata_tlsa_fromWire6.spec
+EXTRA_DIST += rdata_tlsa_fromWire7.spec rdata_tlsa_fromWire8.spec
+EXTRA_DIST += rdata_tlsa_fromWire9 rdata_tlsa_fromWire10
+EXTRA_DIST += rdata_tlsa_fromWire11 rdata_tlsa_fromWire12
+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 += rdata_caa_fromWire1.spec rdata_caa_fromWire2.spec
+EXTRA_DIST += rdata_caa_fromWire3.spec rdata_caa_fromWire4.spec
+EXTRA_DIST += rdata_caa_fromWire5 rdata_caa_fromWire6
+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 += rdatafields1.wire rdatafields2.wire rdatafields3.wire
+EXTRA_DIST += rdatafields4.wire rdatafields5.wire rdatafields6.wire
+EXTRA_DIST += rdata_dnskey_fromWire.wire rdata_dnskey_empty_keydata_fromWire.wire
+EXTRA_DIST += rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire
+EXTRA_DIST += rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire
+EXTRA_DIST += rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire
+EXTRA_DIST += rdata_nsec_fromWire10.wire
+EXTRA_DIST += rdata_nsec_fromWire16.wire
+EXTRA_DIST += rdata_nsec3param_fromWire2.wire
+EXTRA_DIST += rdata_nsec3param_fromWire11.wire
+EXTRA_DIST += rdata_nsec3param_fromWire13.wire
+EXTRA_DIST += rdata_nsec3_fromWire2.wire rdata_nsec3_fromWire3
+EXTRA_DIST += rdata_nsec3_fromWire4.wire rdata_nsec3_fromWire5.wire
+EXTRA_DIST += rdata_nsec3_fromWire6.wire rdata_nsec3_fromWire7.wire
+EXTRA_DIST += rdata_nsec3_fromWire8.wire rdata_nsec3_fromWire9.wire
+EXTRA_DIST += rdata_nsec3_fromWire10.wire rdata_nsec3_fromWire11.wire
+EXTRA_DIST += rdata_nsec3_fromWire12.wire rdata_nsec3_fromWire13.wire
+EXTRA_DIST += rdata_nsec3_fromWire14.wire rdata_nsec3_fromWire15.wire
+EXTRA_DIST += rdata_nsec3_fromWire16.wire rdata_nsec3_fromWire17.wire
+EXTRA_DIST += rdata_rrsig_fromWire2.wire
+EXTRA_DIST += rdata_rp_fromWire1.wire rdata_rp_fromWire2.wire
+EXTRA_DIST += rdata_rp_fromWire3.wire rdata_rp_fromWire4.wire
+EXTRA_DIST += rdata_rp_fromWire5.wire rdata_rp_fromWire6.wire
+EXTRA_DIST += rdata_rp_toWire1.wire rdata_rp_toWire2.wire
+EXTRA_DIST += rdata_sshfp_fromWire1.wire rdata_sshfp_fromWire2.wire
+EXTRA_DIST += rdata_sshfp_fromWire3.wire rdata_sshfp_fromWire4.wire
+EXTRA_DIST += rdata_sshfp_fromWire5.wire rdata_sshfp_fromWire6.wire
+EXTRA_DIST += rdata_sshfp_fromWire7.wire rdata_sshfp_fromWire8.wire
+EXTRA_DIST += rdata_afsdb_fromWire1.wire rdata_afsdb_fromWire2.wire
+EXTRA_DIST += rdata_afsdb_fromWire3.wire rdata_afsdb_fromWire4.wire
+EXTRA_DIST += rdata_afsdb_fromWire5.wire
+EXTRA_DIST += rdata_afsdb_toWire1.wire rdata_afsdb_toWire2.wire
+EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.wire
+EXTRA_DIST += rdata_minfo_fromWire1.wire rdata_minfo_fromWire2.wire
+EXTRA_DIST += rdata_minfo_fromWire3.wire rdata_minfo_fromWire4.wire
+EXTRA_DIST += rdata_minfo_fromWire5.wire rdata_minfo_fromWire6.wire
+EXTRA_DIST += rdata_minfo_toWire1.wire rdata_minfo_toWire2.wire
+EXTRA_DIST += rdata_minfo_toWireUncompressed1.wire
+EXTRA_DIST += rdata_minfo_toWireUncompressed2.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_tlsa_fromWire3.wire rdata_tlsa_fromWire4.wire
+EXTRA_DIST += rdata_tlsa_fromWire5.wire rdata_tlsa_fromWire6.wire
+EXTRA_DIST += rdata_tlsa_fromWire7.wire rdata_tlsa_fromWire8.wire
+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_caa_fromWire1.wire rdata_caa_fromWire2.wire
+EXTRA_DIST += rdata_caa_fromWire3.wire rdata_caa_fromWire4.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..0a311f3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/Makefile.in
@@ -0,0 +1,740 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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_cpp11.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 rdatafields1.spec \
+ rdatafields2.spec rdatafields3.spec rdatafields4.spec \
+ rdatafields5.spec rdatafields6.spec rdata_cname_fromWire \
+ rdata_dname_fromWire rdata_dnskey_fromWire.spec \
+ rdata_dnskey_empty_keydata_fromWire.spec rdata_dhcid_fromWire \
+ rdata_dhcid_toWire rdata_ds_fromWire rdata_in_a_fromWire \
+ rdata_in_aaaa_fromWire rdata_mx_fromWire rdata_mx_toWire1 \
+ rdata_mx_toWire2 rdata_ns_fromWire rdata_nsec_fromWire1 \
+ rdata_nsec_fromWire2 rdata_nsec_fromWire3 \
+ rdata_nsec_fromWire4.spec rdata_nsec_fromWire5.spec \
+ rdata_nsec_fromWire6.spec rdata_nsec_fromWire7.spec \
+ rdata_nsec_fromWire8.spec rdata_nsec_fromWire9.spec \
+ rdata_nsec_fromWire10.spec rdata_nsec_fromWire16.spec \
+ rdata_nsec3param_fromWire1 rdata_nsec3param_fromWire2.spec \
+ rdata_nsec3param_fromWire11.spec \
+ rdata_nsec3param_fromWire13.spec rdata_nsec3_fromWire1 \
+ rdata_nsec3_fromWire1.spec rdata_nsec3_fromWire2.spec \
+ rdata_nsec3_fromWire3 rdata_nsec3_fromWire4.spec \
+ rdata_nsec3_fromWire5.spec rdata_nsec3_fromWire6.spec \
+ rdata_nsec3_fromWire7.spec rdata_nsec3_fromWire8.spec \
+ rdata_nsec3_fromWire9.spec rdata_nsec3_fromWire10.spec \
+ rdata_nsec3_fromWire11.spec rdata_nsec3_fromWire12.spec \
+ rdata_nsec3_fromWire13.spec rdata_nsec3_fromWire14.spec \
+ rdata_nsec3_fromWire15.spec rdata_nsec3_fromWire16.spec \
+ rdata_nsec3_fromWire17.spec rdata_opt_fromWire1 \
+ rdata_opt_fromWire2 rdata_opt_fromWire3 rdata_opt_fromWire4 \
+ rdata_rrsig_fromWire1 rdata_rrsig_fromWire2.spec \
+ rdata_rp_fromWire1.spec rdata_rp_fromWire2.spec \
+ rdata_rp_fromWire3.spec rdata_rp_fromWire4.spec \
+ rdata_rp_fromWire5.spec rdata_rp_fromWire6.spec \
+ rdata_rp_toWire1.spec rdata_rp_toWire2.spec \
+ rdata_sshfp_fromWire rdata_sshfp_fromWire2 \
+ rdata_sshfp_fromWire1.spec rdata_sshfp_fromWire2.spec \
+ rdata_sshfp_fromWire3.spec rdata_sshfp_fromWire4.spec \
+ rdata_sshfp_fromWire5.spec rdata_sshfp_fromWire6.spec \
+ rdata_sshfp_fromWire7.spec rdata_sshfp_fromWire8.spec \
+ rdata_sshfp_fromWire9 rdata_sshfp_fromWire10 \
+ rdata_sshfp_fromWire11 rdata_sshfp_fromWire12 \
+ rdata_afsdb_fromWire1.spec rdata_afsdb_fromWire2.spec \
+ rdata_afsdb_fromWire3.spec rdata_afsdb_fromWire4.spec \
+ rdata_afsdb_fromWire5.spec rdata_afsdb_toWire1.spec \
+ rdata_afsdb_toWire2.spec rdata_soa_fromWire \
+ rdata_soa_toWireUncompressed.spec rdata_srv_fromWire \
+ rdata_minfo_fromWire1.spec rdata_minfo_fromWire2.spec \
+ rdata_minfo_fromWire3.spec rdata_minfo_fromWire4.spec \
+ rdata_minfo_fromWire5.spec rdata_minfo_fromWire6.spec \
+ rdata_minfo_toWire1.spec rdata_minfo_toWire2.spec \
+ rdata_minfo_toWireUncompressed1.spec \
+ rdata_minfo_toWireUncompressed2.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_tlsa_fromWire rdata_tlsa_fromWire2 \
+ rdata_tlsa_fromWire3.spec rdata_tlsa_fromWire4.spec \
+ rdata_tlsa_fromWire5.spec rdata_tlsa_fromWire6.spec \
+ rdata_tlsa_fromWire7.spec rdata_tlsa_fromWire8.spec \
+ rdata_tlsa_fromWire9 rdata_tlsa_fromWire10 \
+ rdata_tlsa_fromWire11 rdata_tlsa_fromWire12 \
+ 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 \
+ rdata_caa_fromWire1.spec rdata_caa_fromWire2.spec \
+ rdata_caa_fromWire3.spec rdata_caa_fromWire4.spec \
+ rdata_caa_fromWire5 rdata_caa_fromWire6 \
+ 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 rdatafields1.wire \
+ rdatafields2.wire rdatafields3.wire rdatafields4.wire \
+ rdatafields5.wire rdatafields6.wire rdata_dnskey_fromWire.wire \
+ rdata_dnskey_empty_keydata_fromWire.wire \
+ rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire \
+ rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire \
+ rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire \
+ rdata_nsec_fromWire10.wire rdata_nsec_fromWire16.wire \
+ rdata_nsec3param_fromWire2.wire \
+ rdata_nsec3param_fromWire11.wire \
+ rdata_nsec3param_fromWire13.wire rdata_nsec3_fromWire2.wire \
+ rdata_nsec3_fromWire3 rdata_nsec3_fromWire4.wire \
+ rdata_nsec3_fromWire5.wire rdata_nsec3_fromWire6.wire \
+ rdata_nsec3_fromWire7.wire rdata_nsec3_fromWire8.wire \
+ rdata_nsec3_fromWire9.wire rdata_nsec3_fromWire10.wire \
+ rdata_nsec3_fromWire11.wire rdata_nsec3_fromWire12.wire \
+ rdata_nsec3_fromWire13.wire rdata_nsec3_fromWire14.wire \
+ rdata_nsec3_fromWire15.wire rdata_nsec3_fromWire16.wire \
+ rdata_nsec3_fromWire17.wire rdata_rrsig_fromWire2.wire \
+ rdata_rp_fromWire1.wire rdata_rp_fromWire2.wire \
+ rdata_rp_fromWire3.wire rdata_rp_fromWire4.wire \
+ rdata_rp_fromWire5.wire rdata_rp_fromWire6.wire \
+ rdata_rp_toWire1.wire rdata_rp_toWire2.wire \
+ rdata_sshfp_fromWire1.wire rdata_sshfp_fromWire2.wire \
+ rdata_sshfp_fromWire3.wire rdata_sshfp_fromWire4.wire \
+ rdata_sshfp_fromWire5.wire rdata_sshfp_fromWire6.wire \
+ rdata_sshfp_fromWire7.wire rdata_sshfp_fromWire8.wire \
+ rdata_afsdb_fromWire1.wire rdata_afsdb_fromWire2.wire \
+ rdata_afsdb_fromWire3.wire rdata_afsdb_fromWire4.wire \
+ rdata_afsdb_fromWire5.wire rdata_afsdb_toWire1.wire \
+ rdata_afsdb_toWire2.wire rdata_soa_fromWire \
+ rdata_soa_toWireUncompressed.wire rdata_minfo_fromWire1.wire \
+ rdata_minfo_fromWire2.wire rdata_minfo_fromWire3.wire \
+ rdata_minfo_fromWire4.wire rdata_minfo_fromWire5.wire \
+ rdata_minfo_fromWire6.wire rdata_minfo_toWire1.wire \
+ rdata_minfo_toWire2.wire rdata_minfo_toWireUncompressed1.wire \
+ rdata_minfo_toWireUncompressed2.wire rdata_txt_fromWire1 \
+ rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire \
+ rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire \
+ rdata_unknown_fromWire rdata_tlsa_fromWire3.wire \
+ rdata_tlsa_fromWire4.wire rdata_tlsa_fromWire5.wire \
+ rdata_tlsa_fromWire6.wire rdata_tlsa_fromWire7.wire \
+ rdata_tlsa_fromWire8.wire 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_caa_fromWire1.wire \
+ rdata_caa_fromWire2.wire rdata_caa_fromWire3.wire \
+ rdata_caa_fromWire4.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_afsdb_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire1.spec
new file mode 100644
index 0000000..f831313
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire1.spec
@@ -0,0 +1,3 @@
+[custom]
+sections: afsdb
+[afsdb]
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire1.wire
new file mode 100644
index 0000000..b32776b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_afsdb_fromWire1.spec
+###
+
+# AFSDB RDATA, RDLEN=21
+0015
+# SUBTYPE=1 SERVER=afsdb.example.com
+0001 056166736462076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.spec
new file mode 100644
index 0000000..f33e768
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.spec
@@ -0,0 +1,6 @@
+[custom]
+sections: name:afsdb
+[name]
+name: example.com
+[afsdb]
+server: afsdb.ptr=0
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.wire
new file mode 100644
index 0000000..fdc53c5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire2.wire
@@ -0,0 +1,11 @@
+###
+### This data file was auto-generated from rdata_afsdb_fromWire2.spec
+###
+
+# DNS Name: example.com
+076578616d706c6503636f6d00
+
+# AFSDB RDATA, RDLEN=10
+000a
+# SUBTYPE=1 SERVER=afsdb.ptr=0
+0001 056166736462c000
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.spec
new file mode 100644
index 0000000..993032f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.spec
@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+rdlen: 3
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.wire
new file mode 100644
index 0000000..7ea4342
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire3.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_afsdb_fromWire3.spec
+###
+
+# AFSDB RDATA, RDLEN=3
+0003
+# SUBTYPE=1 SERVER=afsdb.example.com
+0001 056166736462076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.spec
new file mode 100644
index 0000000..37abf13
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.spec
@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+rdlen: 80
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.wire
new file mode 100644
index 0000000..79ff6c2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire4.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_afsdb_fromWire4.spec
+###
+
+# AFSDB RDATA, RDLEN=80
+0050
+# SUBTYPE=1 SERVER=afsdb.example.com
+0001 056166736462076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.spec
new file mode 100644
index 0000000..0ea79dd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.spec
@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+server: "01234567890123456789012345678901234567890123456789012345678901234"
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.wire
new file mode 100644
index 0000000..13eb016
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_fromWire5.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_afsdb_fromWire5.spec
+###
+
+# AFSDB RDATA, RDLEN=71
+0047
+# SUBTYPE=1 SERVER="01234567890123456789012345678901234567890123456789012345678901234"
+0001 432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_toWire1.spec b/src/lib/dns/tests/testdata/rdata_afsdb_toWire1.spec
new file mode 100644
index 0000000..1946458
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_toWire1.spec
@@ -0,0 +1,4 @@
+[custom]
+sections: afsdb
+[afsdb]
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_toWire1.wire b/src/lib/dns/tests/testdata/rdata_afsdb_toWire1.wire
new file mode 100644
index 0000000..7746fa6
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_toWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_afsdb_toWire1.spec
+###
+
+# AFSDB RDATA
+
+# SUBTYPE=1 SERVER=afsdb.example.com
+0001 056166736462076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_toWire2.spec b/src/lib/dns/tests/testdata/rdata_afsdb_toWire2.spec
new file mode 100644
index 0000000..c80011a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_toWire2.spec
@@ -0,0 +1,8 @@
+[custom]
+sections: name:afsdb
+[name]
+name: example.com.
+[afsdb]
+subtype: 0
+server: root.example.com
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_afsdb_toWire2.wire b/src/lib/dns/tests/testdata/rdata_afsdb_toWire2.wire
new file mode 100644
index 0000000..7f01512
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_afsdb_toWire2.wire
@@ -0,0 +1,11 @@
+###
+### This data file was auto-generated from rdata_afsdb_toWire2.spec
+###
+
+# DNS Name: example.com.
+076578616d706c6503636f6d00
+
+# AFSDB RDATA
+
+# SUBTYPE=0 SERVER=root.example.com
+0000 04726f6f74076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
new file mode 100644
index 0000000..d21987c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# The simplest form of CAA: all default parameters
+#
+[custom]
+sections: caa
+[caa]
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.wire
new file mode 100644
index 0000000..780bd8d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_caa_fromWire1.spec
+###
+
+# CAA RDATA, RDLEN=21
+0015
+# FLAGS=0 TAG=issue VALUE=ca.example.net
+00 05 697373756563612e6578616d706c652e6e6574
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
new file mode 100644
index 0000000..867e43d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.spec
@@ -0,0 +1,7 @@
+#
+# Mixed case CAA tag field.
+#
+[custom]
+sections: caa
+[caa]
+tag: 'ISSue'
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.wire
new file mode 100644
index 0000000..09ca24d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire2.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_caa_fromWire2.spec
+###
+
+# CAA RDATA, RDLEN=21
+0015
+# FLAGS=0 TAG=ISSue VALUE=ca.example.net
+00 05 495353756563612e6578616d706c652e6e6574
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
new file mode 100644
index 0000000..9297151
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.spec
@@ -0,0 +1,7 @@
+#
+# Missing CAA value field.
+#
+[custom]
+sections: caa
+[caa]
+value: ''
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.wire
new file mode 100644
index 0000000..16b5c43
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire3.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_caa_fromWire3.spec
+###
+
+# CAA RDATA, RDLEN=7
+0007
+# FLAGS=0 TAG=issue VALUE=
+00 05 6973737565
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
new file mode 100644
index 0000000..53e16b1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.spec
@@ -0,0 +1,7 @@
+#
+# Missing CAA value field.
+#
+[custom]
+sections: caa
+[caa]
+tag: ''
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.wire
new file mode 100644
index 0000000..3225e60
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire4.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_caa_fromWire4.spec
+###
+
+# CAA RDATA, RDLEN=16
+0010
+# FLAGS=0 TAG= VALUE=ca.example.net
+00 00 63612e6578616d706c652e6e6574
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire5 b/src/lib/dns/tests/testdata/rdata_caa_fromWire5
new file mode 100644
index 0000000..123011f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire5
@@ -0,0 +1,6 @@
+# Test where CAA value field is shorter than the RDATA length
+
+# CAA RDATA, RDLEN=32
+0020
+# FLAGS=0 TAG=c VALUE=ca.example.net
+00 01 63 63612e6578616d706c652e6e6574
diff --git a/src/lib/dns/tests/testdata/rdata_caa_fromWire6 b/src/lib/dns/tests/testdata/rdata_caa_fromWire6
new file mode 100644
index 0000000..1a35a1a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_caa_fromWire6
@@ -0,0 +1,4 @@
+# Test where RDATA is completely missing
+
+# CAA RDATA, RDLEN=32
+0020
diff --git a/src/lib/dns/tests/testdata/rdata_cname_fromWire b/src/lib/dns/tests/testdata/rdata_cname_fromWire
new file mode 100644
index 0000000..57eae13
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_cname_fromWire
@@ -0,0 +1,44 @@
+#
+# various kinds of CNAME RDATA stored in an input buffer
+#
+# Valid non-compressed RDATA for cn.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) c n (7) e x a m p l e (3) c o m .
+ 02 63 6e 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 63 6e 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 63 6e 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00
+#
+# Valid compressed CNAME name: 'cn2' + pointer
+# 5 6
+ 00 06
+# 7 8 9 60 1 2
+#(3) c n 2 ptr=5
+ 03 63 6e 32 c0 05
+#
+# Valid compressed CNAME 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 63 6e 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 63 6e 07 65 78 61 6d 70 6c 65 03 63 6f 6d
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_dname_fromWire b/src/lib/dns/tests/testdata/rdata_dname_fromWire
new file mode 100644
index 0000000..1c899bf
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dname_fromWire
@@ -0,0 +1,44 @@
+#
+# various kinds of DNAME RDATA stored in an input buffer
+#
+# Valid non-compressed RDATA for dn.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) d n (7) e x a m p l e (3) c o m .
+ 02 64 6e 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 64 6e 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 64 6e 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00
+#
+# Valid compressed DNAME name: 'dn2' + pointer
+# 5 6
+ 00 06
+# 7 8 9 60 1 2
+#(3) d n 2 ptr=5
+ 03 64 6e 32 c0 05
+#
+# Valid compressed DNAME 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 64 6e 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 64 6e 07 65 78 61 6d 70 6c 65 03 63 6f 6d
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
new file mode 100644
index 0000000..b65271d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
@@ -0,0 +1,7 @@
+# DNSKEY test data with empty digest
+
+[custom]
+sections: dnskey
+
+[dnskey]
+digest:
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.wire b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.wire
new file mode 100644
index 0000000..35388b1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_dnskey_empty_keydata_fromWire.spec
+###
+
+# DNSKEY RDATA, RDLEN=4
+0004
+# FLAGS=257
+0101
+# PROTOCOL=3
+03
+# ALGORITHM=5
+05
+# DIGEST=
+
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
new file mode 100644
index 0000000..87e66db
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
@@ -0,0 +1,7 @@
+# DNSKEY test data
+
+[custom]
+sections: dnskey
+
+[dnskey]
+digest: BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMVFu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/xylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA8lVUgEf/rzeC/bByBNsO70aEFTd
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.wire b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.wire
new file mode 100644
index 0000000..0a2e41f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_dnskey_fromWire.spec
+###
+
+# DNSKEY RDATA, RDLEN=265
+0109
+# FLAGS=257
+0101
+# PROTOCOL=3
+03
+# ALGORITHM=5
+05
+# DIGEST=BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMVFu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/xylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA8lVUgEf/rzeC/bByBNsO70aEFTd
+0440000003a11d00c1ae141bb69860ab6c10529110e6de03b541f1a0c545bb68562c332fa0e3115e31ab86109e16f0198a1ef22477fc6467d6ea1777f215c6ff1ca56023ba2aba5b7688f0c7c60c5cb039fe403ebb9d1620bf1947547a2936ec61531ffd0c7946235b3c2970faf4fe53c79710998edb48c84b550b82acb7e3b701075ccc9e7cffe0b26903475af426ca8f7036e784f9d79b0d20c730b01f3fdbed84eb7ff366b4330648f406b37ff417b18e98a4b378d18596ad12c5e7ddd4f2e3b474f548b1e56709b7ec73a99efecacc8b28e39e752dfd67b4839ac9f6780d052ad429c00e8b5de1b6c3e8f19b0de803c95552011ffebcde0bf6c1c8136c3bbd1a1054dd
diff --git a/src/lib/dns/tests/testdata/rdata_ds_fromWire b/src/lib/dns/tests/testdata/rdata_ds_fromWire
new file mode 100644
index 0000000..81c412c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_ds_fromWire
@@ -0,0 +1,6 @@
+# RDLENGTH 36 bytes
+00 24
+# DS record, keyid 12892
+32 5c 05 02 f1 e1 84 c0 e1 d6 15 d2 0e b3 c2 23
+ac ed 3b 03 c7 73 dd 95 2d 5f 0e b5 c7 77 58 6d
+e1 8d a6 b5
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_minfo_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_minfo_fromWire1.spec
new file mode 100644
index 0000000..2c43db0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire1.spec
@@ -0,0 +1,3 @@
+[custom]
+sections: minfo
+[minfo]
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_minfo_fromWire1.wire
new file mode 100644
index 0000000..3483e9b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_fromWire1.spec
+###
+
+# MINFO RDATA, RDLEN=44
+002c
+# RMAILBOX=rmailbox.example.com EMAILBOX=emailbox.example.com
+08726d61696c626f78076578616d706c6503636f6d00 08656d61696c626f78076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_minfo_fromWire2.spec
new file mode 100644
index 0000000..d781cac
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire2.spec
@@ -0,0 +1,7 @@
+[custom]
+sections: name:minfo
+[name]
+name: a.example.com.
+[minfo]
+rmailbox: rmailbox.ptr=02
+emailbox: emailbox.ptr=02
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_minfo_fromWire2.wire
new file mode 100644
index 0000000..d79e936
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire2.wire
@@ -0,0 +1,11 @@
+###
+### This data file was auto-generated from rdata_minfo_fromWire2.spec
+###
+
+# DNS Name: a.example.com.
+0161076578616d706c6503636f6d00
+
+# MINFO RDATA, RDLEN=22
+0016
+# RMAILBOX=rmailbox.ptr=02 EMAILBOX=emailbox.ptr=02
+08726d61696c626f78c002 08656d61696c626f78c002
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_minfo_fromWire3.spec
new file mode 100644
index 0000000..a1d4b76
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire3.spec
@@ -0,0 +1,6 @@
+[custom]
+sections: minfo
+# rdlength too short
+[minfo]
+emailbox: emailbox.ptr=11
+rdlen: 3
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_minfo_fromWire3.wire
new file mode 100644
index 0000000..55df1a2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire3.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_fromWire3.spec
+###
+
+# MINFO RDATA, RDLEN=3
+0003
+# RMAILBOX=rmailbox.example.com EMAILBOX=emailbox.ptr=11
+08726d61696c626f78076578616d706c6503636f6d00 08656d61696c626f78c00b
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_minfo_fromWire4.spec
new file mode 100644
index 0000000..269a6ce
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire4.spec
@@ -0,0 +1,6 @@
+[custom]
+sections: minfo
+# rdlength too long
+[minfo]
+emailbox: emailbox.ptr=11
+rdlen: 80
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_minfo_fromWire4.wire
new file mode 100644
index 0000000..746f55a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire4.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_fromWire4.spec
+###
+
+# MINFO RDATA, RDLEN=80
+0050
+# RMAILBOX=rmailbox.example.com EMAILBOX=emailbox.ptr=11
+08726d61696c626f78076578616d706c6503636f6d00 08656d61696c626f78c00b
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_minfo_fromWire5.spec
new file mode 100644
index 0000000..3a888e3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire5.spec
@@ -0,0 +1,5 @@
+[custom]
+sections: minfo
+# bogus rmailbox name
+[minfo]
+rmailbox: "01234567890123456789012345678901234567890123456789012345678901234"
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_minfo_fromWire5.wire
new file mode 100644
index 0000000..dacd9a0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire5.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_fromWire5.spec
+###
+
+# MINFO RDATA, RDLEN=91
+005b
+# RMAILBOX="01234567890123456789012345678901234567890123456789012345678901234" EMAILBOX=emailbox.example.com
+432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200 08656d61696c626f78076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_minfo_fromWire6.spec
new file mode 100644
index 0000000..c75ed8e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire6.spec
@@ -0,0 +1,5 @@
+[custom]
+sections: minfo
+# bogus emailbox name
+[minfo]
+emailbox: "01234567890123456789012345678901234567890123456789012345678901234"
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_minfo_fromWire6.wire
new file mode 100644
index 0000000..4199fee
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_fromWire6.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_fromWire6.spec
+###
+
+# MINFO RDATA, RDLEN=91
+005b
+# RMAILBOX=rmailbox.example.com EMAILBOX="01234567890123456789012345678901234567890123456789012345678901234"
+08726d61696c626f78076578616d706c6503636f6d00 432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWire1.spec b/src/lib/dns/tests/testdata/rdata_minfo_toWire1.spec
new file mode 100644
index 0000000..7b340a3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWire1.spec
@@ -0,0 +1,5 @@
+[custom]
+sections: minfo
+[minfo]
+emailbox: emailbox.ptr=09
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWire1.wire b/src/lib/dns/tests/testdata/rdata_minfo_toWire1.wire
new file mode 100644
index 0000000..8ac4afe
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_toWire1.spec
+###
+
+# MINFO RDATA
+
+# RMAILBOX=rmailbox.example.com EMAILBOX=emailbox.ptr=09
+08726d61696c626f78076578616d706c6503636f6d00 08656d61696c626f78c009
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWire2.spec b/src/lib/dns/tests/testdata/rdata_minfo_toWire2.spec
new file mode 100644
index 0000000..132f118
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWire2.spec
@@ -0,0 +1,6 @@
+[custom]
+sections: minfo
+[minfo]
+rmailbox: root.example.com.
+emailbox: emailbox.ptr=05
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWire2.wire b/src/lib/dns/tests/testdata/rdata_minfo_toWire2.wire
new file mode 100644
index 0000000..7fb847c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWire2.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_toWire2.spec
+###
+
+# MINFO RDATA
+
+# RMAILBOX=root.example.com. EMAILBOX=emailbox.ptr=05
+04726f6f74076578616d706c6503636f6d00 08656d61696c626f78c005
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.spec b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.spec
new file mode 100644
index 0000000..d99a381
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.spec
@@ -0,0 +1,7 @@
+#
+# A simplest form of MINFO: all default parameters
+#
+[custom]
+sections: minfo
+[minfo]
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.wire b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.wire
new file mode 100644
index 0000000..6de233e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_toWireUncompressed1.spec
+###
+
+# MINFO RDATA
+
+# RMAILBOX=rmailbox.example.com EMAILBOX=emailbox.example.com
+08726d61696c626f78076578616d706c6503636f6d00 08656d61696c626f78076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.spec b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.spec
new file mode 100644
index 0000000..0f78fcc
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.spec
@@ -0,0 +1,8 @@
+#
+# A simplest form of MINFO: custom rmailbox and default emailbox
+#
+[custom]
+sections: minfo
+[minfo]
+rmailbox: root.example.com.
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.wire b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.wire
new file mode 100644
index 0000000..51e9e35
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_minfo_toWireUncompressed2.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_minfo_toWireUncompressed2.spec
+###
+
+# MINFO RDATA
+
+# RMAILBOX=root.example.com. EMAILBOX=emailbox.example.com
+04726f6f74076578616d706c6503636f6d00 08656d61696c626f78076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_mx_fromWire b/src/lib/dns/tests/testdata/rdata_mx_fromWire
new file mode 100644
index 0000000..8e09154
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_mx_fromWire
@@ -0,0 +1,15 @@
+#
+# various kinds of MX RDATA stored in an input buffer
+#
+# Valid RDATA for "10 mail.example.com"
+#
+# RDLENGTH=18 bytes
+# 0 1
+ 00 12
+# 2 3
+# PREFERENCE: 10
+ 00 0a
+# EXCHANGE: non compressed
+# 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 (bytes)
+#(2) m x (7) e x a m p l e (3) c o m .
+ 02 6d 78 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
diff --git a/src/lib/dns/tests/testdata/rdata_mx_toWire1 b/src/lib/dns/tests/testdata/rdata_mx_toWire1
new file mode 100644
index 0000000..3049373
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_mx_toWire1
@@ -0,0 +1,12 @@
+#
+# compressed MX RDATA stored in an output buffer
+#
+# sentinel name: example.com.
+# 0 1 2 3 4 5 6 7 8 9 10 1 2 (bytes)
+#(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
+# PREFERENCE: 10
+ 00 0a
+# EXCHANGE: compressed
+#(4) m x ptr=0
+ 02 6d 78 c0 00
diff --git a/src/lib/dns/tests/testdata/rdata_mx_toWire2 b/src/lib/dns/tests/testdata/rdata_mx_toWire2
new file mode 100644
index 0000000..ebd2f27
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_mx_toWire2
@@ -0,0 +1,12 @@
+#
+# compressed MX RDATA stored in an output buffer
+#
+# sentinel name: example.com.
+# 0 1 2 3 4 5 6 7 8 9 10 1 2 (bytes)
+#(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
+# PREFERENCE: 10
+ 00 0a
+# EXCHANGE: not compressed
+#(4) m x ptr=0
+ 02 6d 78 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
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_nsec3_fromWire1 b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1
new file mode 100644
index 0000000..1dd5f52
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1
@@ -0,0 +1,7 @@
+# RDLENGTH, 39 bytes
+00 27
+# NSEC3 record:
+# 1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 NS SOA RRSIG DNSKEY NSEC3PARAM
+01 01 00 01 04 d3 99 ea ab 14 8a 77 c7 ac ef cb
+c5 54 46 03 2b 2d 96 1c c5 eb 68 21 ef 26 00 07
+22 00 00 00 00 02 90
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
new file mode 100644
index 0000000..39a78d7
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire1.spec
@@ -0,0 +1,7 @@
+#
+# A malformed NSEC3 RDATA: bit map length is too large, causing overflow
+#
+
+[custom]
+sections: nsec3
+[nsec3]
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
new file mode 100644
index 0000000..30417f5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: a bitmap block containing empty bytes
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+bitmap: '01000000'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.wire
new file mode 100644
index 0000000..99c5afd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire10.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire10.spec
+###
+
+# NSEC3 RDATA, RDLEN=37
+0025
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=4
+00 04 01000000
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
new file mode 100644
index 0000000..80ec59f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: Saltlen is too large
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 7
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.wire
new file mode 100644
index 0000000..48dc589
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire11.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire11.spec
+###
+
+# NSEC3 RDATA, RDLEN=7
+0007
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=6
+00 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
new file mode 100644
index 0000000..1e01655
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA: Hash length is too large
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+# only contains the first byte of hash
+rdlen: 12
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.wire
new file mode 100644
index 0000000..e880d55
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire12.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire12.spec
+###
+
+# NSEC3 RDATA, RDLEN=12
+000c
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=6
+00 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
new file mode 100644
index 0000000..fcc9d53
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.spec
@@ -0,0 +1,9 @@
+#
+# A valid (but unusual) NSEC3 RDATA: salt is empty.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+saltlen: 0
+salt: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.wire
new file mode 100644
index 0000000..86a06c5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire13.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire13.spec
+###
+
+# NSEC3 RDATA, RDLEN=34
+0022
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=0, Salt=''
+00
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=6
+00 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
new file mode 100644
index 0000000..a0550d5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA: empty hash
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+hashlen: 0
+hash: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.wire
new file mode 100644
index 0000000..9d76b5a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire14.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire14.spec
+###
+
+# NSEC3 RDATA, RDLEN=19
+0013
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=0, Hash=''
+00
+# Bitmap: Block=0, Length=6
+00 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
new file mode 100644
index 0000000..4993e03
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.spec
@@ -0,0 +1,10 @@
+#
+# NSEC3 RDATA with empty type bitmap. It's okay.
+# The test data includes bytes for a bitmap field, but RDLEN indicates
+# it's not part of the RDATA and so it will be ignored.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 31
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.wire
new file mode 100644
index 0000000..bd636a7
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire15.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire15.spec
+###
+
+# NSEC3 RDATA, RDLEN=31
+001f
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=6
+00 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
new file mode 100644
index 0000000..dac14ea
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
@@ -0,0 +1,8 @@
+#
+# NSEC3 RDATA with an empty bitmap
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+nbitmap: 0
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.wire
new file mode 100644
index 0000000..bab957e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.wire
@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire16.spec
+###
+
+# NSEC3 RDATA, RDLEN=31
+001f
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
new file mode 100644
index 0000000..4253349
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: RDLEN is too short to include the hash len field.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 10
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.wire
new file mode 100644
index 0000000..f7972a0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire17.spec
+###
+
+# NSEC3 RDATA, RDLEN=10
+000a
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=6
+00 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
new file mode 100644
index 0000000..f35de83
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3 RDATA: RDLEN indicates it doesn't even contain the fixed
+# 5 octets
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 4
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.wire
new file mode 100644
index 0000000..ec14a58
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire2.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire2.spec
+###
+
+# NSEC3 RDATA, RDLEN=4
+0004
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=6
+00 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire3 b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire3
new file mode 100644
index 0000000..a0c8f59
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire3
@@ -0,0 +1,6 @@
+# RDLENGTH, 39 bytes
+00 27
+# NSEC3 record with a broken type bitmap
+01 01 00 01 04 d3 99 ea ab 14 8a 77 c7 ac ef cb
+c5 54 46 03 2b 2d 96 1c c5 eb 68 21 ef 26 00 ff
+22 00 00 00 00 02 90
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
new file mode 100644
index 0000000..06d6eb4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3 RDATA: bit map length is too large, causing overflow
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 31
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.wire
new file mode 100644
index 0000000..527860c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire4.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire4.spec
+###
+
+# NSEC3 RDATA, RDLEN=34
+0022
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=31
+00 1f 01
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
new file mode 100644
index 0000000..2d5713c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.spec
@@ -0,0 +1,13 @@
+#
+# A malformed NSEC3 RDATA: incomplete bit map field
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+# only containing the block field of the bitmap
+rdlen: 32
+#dummy data
+maplen: 31
+#dummy data
+bitmap: '00'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.wire
new file mode 100644
index 0000000..97b798b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire5.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire5.spec
+###
+
+# NSEC3 RDATA, RDLEN=32
+0020
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=31
+00 1f 00
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
new file mode 100644
index 0000000..36e9e59
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.spec
@@ -0,0 +1,11 @@
+#
+# A malformed NSEC3 RDATA: bit map length being 0
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 33
+maplen: 0
+# dummy data:
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.wire
new file mode 100644
index 0000000..4cf1189
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire6.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire6.spec
+###
+
+# NSEC3 RDATA, RDLEN=33
+0021
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=0
+00 00 01
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
new file mode 100644
index 0000000..338c0c9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.spec
@@ -0,0 +1,9 @@
+#
+# NSEC3 RDATA with a longest bitmap field (32 bitmap bytes)
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 32
+bitmap: '0101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.wire
new file mode 100644
index 0000000..1fddd58
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire7.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire7.spec
+###
+
+# NSEC3 RDATA, RDLEN=65
+0041
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=32
+00 20 0101010101010101010101010101010101010101010101010101010101010101
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
new file mode 100644
index 0000000..041714e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC3 RDATA with an oversized bitmap field (33 bitmap bytes)
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+maplen: 33
+bitmap: '010101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.wire
new file mode 100644
index 0000000..9569094
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire8.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire8.spec
+###
+
+# NSEC3 RDATA, RDLEN=66
+0042
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=0, Length=33
+00 21 010101010101010101010101010101010101010101010101010101010101010101
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
new file mode 100644
index 0000000..b04c84f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.spec
@@ -0,0 +1,10 @@
+#
+# An invalid NSEC3 RDATA: disordered bitmap blocks
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+nbitmap: 2
+block0: 2
+block1: 1
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.wire b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.wire
new file mode 100644
index 0000000..9c0de10
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire9.wire
@@ -0,0 +1,16 @@
+###
+### This data file was auto-generated from rdata_nsec3_fromWire9.spec
+###
+
+# NSEC3 RDATA, RDLEN=47
+002f
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
+# Hash Len=20, Hash='hhhhhhhhhhhhhhhhhhhh'
+14 6868686868686868686868686868686868686868
+# Bitmap: Block=2, Length=6
+02 06 040000000003
+# Bitmap: Block=1, Length=6
+01 06 040000000003
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire1 b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire1
new file mode 100644
index 0000000..1b8697f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire1
@@ -0,0 +1,5 @@
+# RDLENGTH, 9 bytes
+00 09
+# NSEC3PARAM record
+# 1 1 1 D399EAAB
+01 01 00 01 04 d3 99 ea ab
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec
new file mode 100644
index 0000000..41e1784
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3PARAM RDATA: Saltlen is too large
+#
+
+[custom]
+sections: nsec3param
+[nsec3param]
+rdlen: 7
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.wire b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.wire
new file mode 100644
index 0000000..e9ffb46
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire11.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec3param_fromWire11.spec
+###
+
+# NSEC3PARAM RDATA, RDLEN=7
+0007
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec
new file mode 100644
index 0000000..311b2dd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.spec
@@ -0,0 +1,9 @@
+#
+# A valid (but unusual) NSEC3PARAM RDATA: salt is empty.
+#
+
+[custom]
+sections: nsec3param
+[nsec3param]
+saltlen: 0
+salt: ''
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.wire b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.wire
new file mode 100644
index 0000000..511da87
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire13.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec3param_fromWire13.spec
+###
+
+# NSEC3PARAM RDATA, RDLEN=5
+0005
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=0, Salt=''
+00
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
new file mode 100644
index 0000000..e04b818
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC3PARAM RDATA: RDLEN indicates it doesn't even contain the
+# fixed 5 octets
+#
+
+[custom]
+sections: nsec3param
+[nsec3param]
+rdlen: 4
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.wire
new file mode 100644
index 0000000..adc5ea9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3param_fromWire2.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec3param_fromWire2.spec
+###
+
+# NSEC3PARAM RDATA, RDLEN=4
+0004
+# Hash Alg=SHA1(1), Opt-Out=0, Other Flags=0, Iterations=1
+01 00 0001
+# Salt Len=5, Salt='sssss'
+05 7373737373
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire1 b/src/lib/dns/tests/testdata/rdata_nsec_fromWire1
new file mode 100644
index 0000000..9c34394
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire1
@@ -0,0 +1,6 @@
+# RDLENGTH, 22 bytes
+00 16
+# NSEC record
+# www2.isc.org. CNAME RRSIG NSEC
+04 77 77 77 32 03 69 73 63 03 6f 72 67 00 00 06
+04 00 00 00 00 03
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire10.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire10.spec
new file mode 100644
index 0000000..39d19ae
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire10.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC RDATA: a bitmap block containing empty bytes
+#
+
+[custom]
+sections: nsec
+[nsec]
+bitmap: '01000000'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire10.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire10.wire
new file mode 100644
index 0000000..1145dbd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire10.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire10.spec
+###
+
+# NSEC RDATA, RDLEN=24
+0018
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
+# Bitmap: Block=0, Length=4
+00 04 01000000
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
new file mode 100644
index 0000000..d7faeed
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC RDATA: with an empty bitmap
+#
+
+[custom]
+sections: nsec
+[nsec]
+nbitmap: 0
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.wire
new file mode 100644
index 0000000..943601a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire16.spec
+###
+
+# NSEC RDATA, RDLEN=18
+0012
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire2 b/src/lib/dns/tests/testdata/rdata_nsec_fromWire2
new file mode 100644
index 0000000..e9b41e2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire2
@@ -0,0 +1,10 @@
+#
+# NSEC RDATA with a bogus RDLEN (too short)
+#
+
+# RDLENGTH, 13 bytes (should be 22)
+00 0d
+# NSEC record
+# www2.isc.org. CNAME RRSIG NSEC
+04 77 77 77 32 03 69 73 63 03 6f 72 67 00 00 06
+04 00 00 00 00 03
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire3 b/src/lib/dns/tests/testdata/rdata_nsec_fromWire3
new file mode 100644
index 0000000..67e6b3f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire3
@@ -0,0 +1,10 @@
+#
+# NSEC RDATA with an invalid type bitmap
+#
+
+# RDLENGTH, 22 bytes
+00 16
+# NSEC record
+# www2.isc.org. followed by a broken bitmap
+04 77 77 77 32 03 69 73 63 03 6f 72 67 00 00 ff
+00 00 00 00 00 00
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire4.spec
new file mode 100644
index 0000000..a5744c1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire4.spec
@@ -0,0 +1,9 @@
+#
+# A malformed NSEC RDATA: bit map length is too large, causing overflow
+#
+
+[custom]
+sections: nsec
+[nsec]
+maplen: 31
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire4.wire
new file mode 100644
index 0000000..1c4d4a4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire4.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire4.spec
+###
+
+# NSEC RDATA, RDLEN=21
+0015
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
+# Bitmap: Block=0, Length=31
+00 1f 01
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire5.spec
new file mode 100644
index 0000000..795d1e0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire5.spec
@@ -0,0 +1,13 @@
+#
+# A malformed NSEC RDATA: incomplete bit map field
+#
+
+[custom]
+sections: nsec
+[nsec]
+# only containing the block field of the bitmap
+rdlen: 19
+#dummy data
+maplen: 31
+#dummy data
+bitmap: '00'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire5.wire
new file mode 100644
index 0000000..104a2b9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire5.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire5.spec
+###
+
+# NSEC RDATA, RDLEN=19
+0013
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
+# Bitmap: Block=0, Length=31
+00 1f 00
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire6.spec
new file mode 100644
index 0000000..cb864ab
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire6.spec
@@ -0,0 +1,11 @@
+#
+# A malformed NSEC RDATA: bit map length being 0
+#
+
+[custom]
+sections: nsec
+[nsec]
+rdlen: 20
+maplen: 0
+# dummy data:
+bitmap: '01'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire6.wire
new file mode 100644
index 0000000..3108ed7
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire6.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire6.spec
+###
+
+# NSEC RDATA, RDLEN=20
+0014
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
+# Bitmap: Block=0, Length=0
+00 00 01
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire7.spec
new file mode 100644
index 0000000..46b521b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire7.spec
@@ -0,0 +1,9 @@
+#
+# NSEC RDATA with a longest bitmap field (32 bitmap bytes)
+#
+
+[custom]
+sections: nsec
+[nsec]
+maplen: 32
+bitmap: '0101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire7.wire
new file mode 100644
index 0000000..e265a3b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire7.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire7.spec
+###
+
+# NSEC RDATA, RDLEN=52
+0034
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
+# Bitmap: Block=0, Length=32
+00 20 0101010101010101010101010101010101010101010101010101010101010101
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire8.spec
new file mode 100644
index 0000000..3f957a3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire8.spec
@@ -0,0 +1,9 @@
+#
+# An invalid NSEC RDATA with an oversized bitmap field (33 bitmap bytes)
+#
+
+[custom]
+sections: nsec
+[nsec]
+maplen: 33
+bitmap: '010101010101010101010101010101010101010101010101010101010101010101'
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire8.wire
new file mode 100644
index 0000000..066cd70
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire8.wire
@@ -0,0 +1,10 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire8.spec
+###
+
+# NSEC RDATA, RDLEN=53
+0035
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
+# Bitmap: Block=0, Length=33
+00 21 010101010101010101010101010101010101010101010101010101010101010101
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire9.spec
new file mode 100644
index 0000000..a3bd0c9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire9.spec
@@ -0,0 +1,10 @@
+#
+# An invalid NSEC RDATA: disordered bitmap blocks
+#
+
+[custom]
+sections: nsec
+[nsec]
+nbitmap: 2
+block0: 2
+block1: 1
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire9.wire b/src/lib/dns/tests/testdata/rdata_nsec_fromWire9.wire
new file mode 100644
index 0000000..53ad6cc
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire9.wire
@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from rdata_nsec_fromWire9.spec
+###
+
+# NSEC RDATA, RDLEN=34
+0022
+# Next Name=next.example.com (18 bytes)
+046e657874076578616d706c6503636f6d00
+# Bitmap: Block=2, Length=6
+02 06 040000000003
+# Bitmap: Block=1, Length=6
+01 06 040000000003
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_rp_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire1.spec
new file mode 100644
index 0000000..edb9f34
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# A simplest form of RP: all default parameters
+#
+[custom]
+sections: rp
+[rp]
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_rp_fromWire1.wire
new file mode 100644
index 0000000..aa93986
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_rp_fromWire1.spec
+###
+
+# RP RDATA, RDLEN=39
+0027
+# MAILBOX=root.example.com TEXT=rp-text.example.com
+04726f6f74076578616d706c6503636f6d00 0772702d74657874076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec
new file mode 100644
index 0000000..57adb5a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec
@@ -0,0 +1,12 @@
+#
+# A simplest form of RP: names are compressed.
+#
+[custom]
+sections: name/1:name/2:rp
+[name/1]
+name: a.example.com
+[name/2]
+name: b.example.net
+[rp]
+mailbox: root.ptr=2
+text: rp-text.ptr=17
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_rp_fromWire2.wire
new file mode 100644
index 0000000..507bb1a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire2.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_rp_fromWire2.spec
+###
+
+# DNS Name: a.example.com
+0161076578616d706c6503636f6d00
+
+# DNS Name: b.example.net
+0162076578616d706c65036e657400
+
+# RP RDATA, RDLEN=17
+0011
+# MAILBOX=root.ptr=2 TEXT=rp-text.ptr=17
+04726f6f74c002 0772702d74657874c011
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec
new file mode 100644
index 0000000..a238b7e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but RDLEN is too short.
+#
+[custom]
+sections: rp
+[rp]
+rdlen: 38
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_rp_fromWire3.wire
new file mode 100644
index 0000000..c2a7086
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire3.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_rp_fromWire3.spec
+###
+
+# RP RDATA, RDLEN=38
+0026
+# MAILBOX=root.example.com TEXT=rp-text.example.com
+04726f6f74076578616d706c6503636f6d00 0772702d74657874076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec
new file mode 100644
index 0000000..6f3abd1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but RDLEN is too long.
+#
+[custom]
+sections: rp
+[rp]
+rdlen: 40
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_rp_fromWire4.wire
new file mode 100644
index 0000000..56c643e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire4.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_rp_fromWire4.spec
+###
+
+# RP RDATA, RDLEN=40
+0028
+# MAILBOX=root.example.com TEXT=rp-text.example.com
+04726f6f74076578616d706c6503636f6d00 0772702d74657874076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec
new file mode 100644
index 0000000..b8d5e29
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but mailbox name is broken.
+#
+[custom]
+sections: rp
+[rp]
+mailbox: "01234567890123456789012345678901234567890123456789012345678901234"
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_rp_fromWire5.wire
new file mode 100644
index 0000000..1127047
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire5.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_rp_fromWire5.spec
+###
+
+# RP RDATA, RDLEN=90
+005a
+# MAILBOX="01234567890123456789012345678901234567890123456789012345678901234" TEXT=rp-text.example.com
+432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200 0772702d74657874076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec
new file mode 100644
index 0000000..e9e79f3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but text name is broken.
+#
+[custom]
+sections: rp
+[rp]
+text: "01234567890123456789012345678901234567890123456789012345678901234"
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_rp_fromWire6.wire
new file mode 100644
index 0000000..49aedc6
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire6.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_rp_fromWire6.spec
+###
+
+# RP RDATA, RDLEN=87
+0057
+# MAILBOX=root.example.com TEXT="01234567890123456789012345678901234567890123456789012345678901234"
+04726f6f74076578616d706c6503636f6d00 432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200
diff --git a/src/lib/dns/tests/testdata/rdata_rp_toWire1.spec b/src/lib/dns/tests/testdata/rdata_rp_toWire1.spec
new file mode 100644
index 0000000..948bd1a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_toWire1.spec
@@ -0,0 +1,8 @@
+#
+# A simplest form of RP for toWire test: all default parameters except rdlen,
+# which is to be omitted.
+#
+[custom]
+sections: rp
+[rp]
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_rp_toWire1.wire b/src/lib/dns/tests/testdata/rdata_rp_toWire1.wire
new file mode 100644
index 0000000..dcadb15
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_toWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_rp_toWire1.spec
+###
+
+# RP RDATA
+
+# MAILBOX=root.example.com TEXT=rp-text.example.com
+04726f6f74076578616d706c6503636f6d00 0772702d74657874076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdata_rp_toWire2.spec b/src/lib/dns/tests/testdata/rdata_rp_toWire2.spec
new file mode 100644
index 0000000..09a7ddc
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_toWire2.spec
@@ -0,0 +1,14 @@
+#
+# A simple form of RP: names could be compressed (but MUST NOT).
+# rdlen is omitted for the "to wire" test.
+#
+[custom]
+sections: name/1:name/2:rp
+[name/1]
+name: a.example.com
+[name/2]
+name: b.example.net
+[rp]
+rdlen: -1
+mailbox: root.example.com
+text: rp-text.example.net
diff --git a/src/lib/dns/tests/testdata/rdata_rp_toWire2.wire b/src/lib/dns/tests/testdata/rdata_rp_toWire2.wire
new file mode 100644
index 0000000..38057ef
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_toWire2.wire
@@ -0,0 +1,14 @@
+###
+### This data file was auto-generated from rdata_rp_toWire2.spec
+###
+
+# DNS Name: a.example.com
+0161076578616d706c6503636f6d00
+
+# DNS Name: b.example.net
+0162076578616d706c65036e657400
+
+# RP RDATA
+
+# MAILBOX=root.example.com TEXT=rp-text.example.net
+04726f6f74076578616d706c6503636f6d00 0772702d74657874076578616d706c65036e657400
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_srv_fromWire b/src/lib/dns/tests/testdata/rdata_srv_fromWire
new file mode 100644
index 0000000..0f1e4ec
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_srv_fromWire
@@ -0,0 +1,36 @@
+#
+# various kinds of SRV RDATA stored in an input buffer
+#
+# RDLENGTH=21 bytes
+# 0 1
+ 00 15
+# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 20 1 2(bytes)
+ 00 01 00 05 05 dc 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#
+# short length
+# 3 4
+ 00 12
+# 5 6 7 8 9 30 1 2 3 4 5 6 7 8 9 40 1 2 3 4 5
+ 00 01 00 05 05 dc 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#
+# length too long
+# 6 7
+ 00 19
+#
+# 8 9 50 1 2 3 4 5 6 7 8 9 60 1 2 3 4 5 6 7 8
+ 00 01 00 05 05 dc 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+#
+#
+# incomplete target name
+# 9 70
+ 00 12
+# 1 2 3 4 5 6 7 8 9 80 1 2 3 4 5 6 7 8
+ 00 01 00 05 05 dc 01 61 07 65 78 61 6d 70 6c 65 03 63
+#
+#
+# Valid compressed target name: 'a' + pointer
+# 9 90
+ 00 0a
+#
+# 1 2 3 4 5 6 7 8 9 100
+ 00 01 00 05 05 dc 01 61 c0 0a
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire
new file mode 100644
index 0000000..added40
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire
@@ -0,0 +1,4 @@
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+02 01 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec
new file mode 100644
index 0000000..e28a62f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# A simplest form of SSHFP: all default parameters
+#
+[custom]
+sections: sshfp
+[sshfp]
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.wire
new file mode 100644
index 0000000..c7059e1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire1.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire1.spec
+###
+
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+02 01 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire10 b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire10
new file mode 100644
index 0000000..220e570
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire10
@@ -0,0 +1,6 @@
+# Test where fingerprint is missing
+
+# SSHFP RDATA, RDLEN=32
+0020
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=(none)
+02 01
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire11 b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire11
new file mode 100644
index 0000000..a2f8636
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire11
@@ -0,0 +1,4 @@
+# Test where RDATA is completely missing
+
+# SSHFP RDATA, RDLEN=32
+0020
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire12 b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire12
new file mode 100644
index 0000000..eabd06b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire12
@@ -0,0 +1,4 @@
+# SSHFP RDATA, RDLEN=02
+0002
+# ALGORITHM=4 FINGERPRINT_TYPE=9
+04 09
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2 b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2
new file mode 100644
index 0000000..a695548
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2
@@ -0,0 +1,4 @@
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=123456789ABCDEF67890123456789abcdef67890
+02 01 123456789ABCDEF67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec
new file mode 100644
index 0000000..59a336e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.spec
@@ -0,0 +1,7 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+fingerprint: 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.wire
new file mode 100644
index 0000000..e492316
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire2.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire2.spec
+###
+
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+02 01 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.spec
new file mode 100644
index 0000000..d111afd
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.spec
@@ -0,0 +1,8 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+algorithm: 1
+fingerprint_type: 1
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.wire
new file mode 100644
index 0000000..daa102f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire3.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire3.spec
+###
+
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=1 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+01 01 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.spec
new file mode 100644
index 0000000..b9b2658
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.spec
@@ -0,0 +1,8 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+algorithm: 255
+fingerprint_type: 1
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.wire
new file mode 100644
index 0000000..f05faad
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire4.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire4.spec
+###
+
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=255 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+ff 01 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.spec
new file mode 100644
index 0000000..b3a19fa
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.spec
@@ -0,0 +1,8 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+algorithm: 0
+fingerprint_type: 1
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.wire
new file mode 100644
index 0000000..ddc8edb
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire5.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire5.spec
+###
+
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=0 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+00 01 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.spec
new file mode 100644
index 0000000..437e282
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.spec
@@ -0,0 +1,8 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+algorithm: 5
+fingerprint_type: 0
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.wire
new file mode 100644
index 0000000..d4e591e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire6.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire6.spec
+###
+
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=5 FINGERPRINT_TYPE=0 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+05 00 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.spec
new file mode 100644
index 0000000..8e21d11
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.spec
@@ -0,0 +1,8 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+algorithm: 255
+fingerprint_type: 255
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.wire
new file mode 100644
index 0000000..c8b9d8b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire7.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire7.spec
+###
+
+# SSHFP RDATA, RDLEN=22
+0016
+# ALGORITHM=255 FINGERPRINT_TYPE=255 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+ff ff 123456789abcdef67890123456789abcdef67890
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.spec
new file mode 100644
index 0000000..98aa00f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.spec
@@ -0,0 +1,9 @@
+#
+# SSHFP RDATA
+#
+[custom]
+sections: sshfp
+[sshfp]
+fingerprint: 082359342fd9
+algorithm: 255
+fingerprint_type: 255
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.wire
new file mode 100644
index 0000000..32cede8
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire8.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_sshfp_fromWire8.spec
+###
+
+# SSHFP RDATA, RDLEN=8
+0008
+# ALGORITHM=255 FINGERPRINT_TYPE=255 FINGERPRINT=082359342fd9
+ff ff 082359342fd9
diff --git a/src/lib/dns/tests/testdata/rdata_sshfp_fromWire9 b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire9
new file mode 100644
index 0000000..05fc806
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_sshfp_fromWire9
@@ -0,0 +1,6 @@
+# Test where fingerprint length is smaller than what RDATA len indicates
+
+# SSHFP RDATA, RDLEN=32
+0020
+# ALGORITHM=2 FINGERPRINT_TYPE=1 FINGERPRINT=123456789abcdef67890123456789abcdef67890
+02 01 123456789abcdef67890123456789abcdef67890
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_tlsa_fromWire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire
new file mode 100644
index 0000000..38e279c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire
@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=...
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire10 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire10
new file mode 100644
index 0000000..67cecb1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire10
@@ -0,0 +1,6 @@
+# Test where certificate association data is missing.
+
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(missing)
+00 00 01
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire11 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire11
new file mode 100644
index 0000000..4b8ec93
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire11
@@ -0,0 +1,4 @@
+# Test where RDATA is completely missing
+
+# TLSA RDATA, RDLEN=35
+0023
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire12 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire12
new file mode 100644
index 0000000..71c7b9c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire12
@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=3
+0003
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(none)
+03 01 02
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire2 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire2
new file mode 100644
index 0000000..36ce278
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire2
@@ -0,0 +1,4 @@
+# TLSA RDATA, RDLEN=35
+0023
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=...
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983A1D16E8A410E4561CB106618E971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec
new file mode 100644
index 0000000..39c8057
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 0
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.wire
new file mode 100644
index 0000000..6a8b552
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire3.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_tlsa_fromWire3.spec
+###
+
+# TLSA RDATA, RDLEN=34
+0022
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec
new file mode 100644
index 0000000..d97ae6a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 255
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.wire
new file mode 100644
index 0000000..b5a39c8
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire4.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_tlsa_fromWire4.spec
+###
+
+# TLSA RDATA, RDLEN=34
+0022
+# CERTIFICATE_USAGE=255 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
+ff 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec
new file mode 100644
index 0000000..cc3e296
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+selector: 255
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.wire
new file mode 100644
index 0000000..b79f97d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire5.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_tlsa_fromWire5.spec
+###
+
+# TLSA RDATA, RDLEN=34
+0022
+# CERTIFICATE_USAGE=0 SELECTOR=255 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
+00 ff 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec
new file mode 100644
index 0000000..eed0ab9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+matching_type: 255
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.wire
new file mode 100644
index 0000000..02afe27
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire6.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_tlsa_fromWire6.spec
+###
+
+# TLSA RDATA, RDLEN=34
+0022
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=255 CERTIFICATE_ASSOCIATION_DATA=d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
+00 00 ff d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec
new file mode 100644
index 0000000..576df1e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.spec
@@ -0,0 +1,9 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_usage: 3
+selector: 1
+matching_type: 2
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.wire
new file mode 100644
index 0000000..e5c23a4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire7.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_tlsa_fromWire7.spec
+###
+
+# TLSA RDATA, RDLEN=34
+0022
+# CERTIFICATE_USAGE=3 SELECTOR=1 MATCHING_TYPE=2 CERTIFICATE_ASSOCIATION_DATA=d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
+03 01 02 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec
new file mode 100644
index 0000000..ef5c108
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.spec
@@ -0,0 +1,7 @@
+#
+# TLSA RDATA
+#
+[custom]
+sections: tlsa
+[tlsa]
+certificate_association_data: '0123'
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.wire
new file mode 100644
index 0000000..dc29a0b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire8.wire
@@ -0,0 +1,8 @@
+###
+### This data file was auto-generated from rdata_tlsa_fromWire8.spec
+###
+
+# TLSA RDATA, RDLEN=4
+0004
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=0123
+00 00 01 0123
diff --git a/src/lib/dns/tests/testdata/rdata_tlsa_fromWire9 b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire9
new file mode 100644
index 0000000..fc4560a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_tlsa_fromWire9
@@ -0,0 +1,7 @@
+# Test where certificate association data length is smaller than what
+# RDATA length indicates.
+
+# TLSA RDATA, RDLEN=64
+0040
+# CERTIFICATE_USAGE=0 SELECTOR=0 MATCHING_TYPE=1 CERTIFICATE_ASSOCIATION_DATA=(32 bytes)
+00 00 01 d2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971
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/rdatafields1.spec b/src/lib/dns/tests/testdata/rdatafields1.spec
new file mode 100644
index 0000000..6e105fb
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields1.spec
@@ -0,0 +1,10 @@
+#
+# A sequence of names that could be compressed (but not compressed)
+#
+
+[custom]
+sections: name/1:name/2
+[name/1]
+name: www.example.com
+[name/2]
+name: example.com
diff --git a/src/lib/dns/tests/testdata/rdatafields1.wire b/src/lib/dns/tests/testdata/rdatafields1.wire
new file mode 100644
index 0000000..42028c1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields1.wire
@@ -0,0 +1,9 @@
+###
+### This data file was auto-generated from rdatafields1.spec
+###
+
+# DNS Name: www.example.com
+03777777076578616d706c6503636f6d00
+
+# DNS Name: example.com
+076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdatafields2.spec b/src/lib/dns/tests/testdata/rdatafields2.spec
new file mode 100644
index 0000000..920dc95
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields2.spec
@@ -0,0 +1,11 @@
+#
+# A sequence of names that can be compressed.
+#
+
+[custom]
+sections: name/1:name/2
+[name/1]
+name: www.example.com
+[name/2]
+name: ''
+pointer: 4
diff --git a/src/lib/dns/tests/testdata/rdatafields2.wire b/src/lib/dns/tests/testdata/rdatafields2.wire
new file mode 100644
index 0000000..cbb0ce0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields2.wire
@@ -0,0 +1,9 @@
+###
+### This data file was auto-generated from rdatafields2.spec
+###
+
+# DNS Name: www.example.com
+03777777076578616d706c6503636f6d00
+
+# DNS Name: + compression pointer: 4
+c004
diff --git a/src/lib/dns/tests/testdata/rdatafields3.spec b/src/lib/dns/tests/testdata/rdatafields3.spec
new file mode 100644
index 0000000..b37fca3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields3.spec
@@ -0,0 +1,11 @@
+#
+# TXT RDATA with multiple character-strings.
+#
+
+[custom]
+sections: txt
+[txt]
+nstring: 3
+string0: 'first string'
+string1: 'second string'
+string2: 'last string'
diff --git a/src/lib/dns/tests/testdata/rdatafields3.wire b/src/lib/dns/tests/testdata/rdatafields3.wire
new file mode 100644
index 0000000..3ff32f1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields3.wire
@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from rdatafields3.spec
+###
+
+# TXT RDATA, RDLEN=39
+0027
+# String Len=12, String="first string"
+0c 666972737420737472696e67
+# String Len=13, String="second string"
+0d 7365636f6e6420737472696e67
+# String Len=11, String="last string"
+0b 6c61737420737472696e67
diff --git a/src/lib/dns/tests/testdata/rdatafields4.spec b/src/lib/dns/tests/testdata/rdatafields4.spec
new file mode 100644
index 0000000..24b59aa
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields4.spec
@@ -0,0 +1,7 @@
+#
+# Simple form of RRSIG (all fields use the default of generator script)
+#
+
+[custom]
+sections: rrsig
+[rrsig]
diff --git a/src/lib/dns/tests/testdata/rdatafields4.wire b/src/lib/dns/tests/testdata/rdatafields4.wire
new file mode 100644
index 0000000..e574426
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields4.wire
@@ -0,0 +1,12 @@
+###
+### This data file was auto-generated from rdatafields4.spec
+###
+
+# RRSIG RDATA, RDLEN=46
+002e
+# 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/rdatafields5.spec b/src/lib/dns/tests/testdata/rdatafields5.spec
new file mode 100644
index 0000000..2c78282
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields5.spec
@@ -0,0 +1,12 @@
+#
+# Names and RDATA (RRSIG) with an incompressible name. All names are
+# rendered without compression.
+#
+
+[custom]
+sections: name/1:rrsig:name/2
+[name/1]
+name: com
+[rrsig]
+[name/2]
+name: www.example.com
diff --git a/src/lib/dns/tests/testdata/rdatafields5.wire b/src/lib/dns/tests/testdata/rdatafields5.wire
new file mode 100644
index 0000000..d0cebc9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields5.wire
@@ -0,0 +1,18 @@
+###
+### This data file was auto-generated from rdatafields5.spec
+###
+
+# DNS Name: com
+03636f6d00
+
+# RRSIG RDATA, RDLEN=46
+002e
+# 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
+
+# DNS Name: www.example.com
+03777777076578616d706c6503636f6d00
diff --git a/src/lib/dns/tests/testdata/rdatafields6.spec b/src/lib/dns/tests/testdata/rdatafields6.spec
new file mode 100644
index 0000000..f9f0da1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields6.spec
@@ -0,0 +1,13 @@
+#
+# Names and RDATA (RRSIG) with an incompressible name. The name in RRSIG
+# isn't compressed, but it's used as the compression target.
+#
+
+[custom]
+sections: name/1:rrsig:name/2
+[name/1]
+name: com
+[rrsig]
+[name/2]
+name: www
+pointer: 25
diff --git a/src/lib/dns/tests/testdata/rdatafields6.wire b/src/lib/dns/tests/testdata/rdatafields6.wire
new file mode 100644
index 0000000..0e8b3d9
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdatafields6.wire
@@ -0,0 +1,18 @@
+###
+### This data file was auto-generated from rdatafields6.spec
+###
+
+# DNS Name: com
+03636f6d00
+
+# RRSIG RDATA, RDLEN=46
+002e
+# 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
+
+# DNS Name: www + compression pointer: 25
+03777777c019
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/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc
new file mode 100644
index 0000000..fde67bc
--- /dev/null
+++ b/src/lib/dns/tests/tsig_unittest.cc
@@ -0,0 +1,1185 @@
+// Copyright (C) 2011-2023 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 <time.h>
+#include <string>
+#include <stdexcept>
+#include <vector>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base64.h>
+#include <util/unittests/newhook.h>
+#include <util/time_utilities.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/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 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(NULL), 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, NULL, qid, 0, 0, NULL))
+ {
+ // 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 = NULL;
+
+ 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 = NULL;
+ }
+
+ // 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 = NULL,
+ const RRType* answer_type = NULL,
+ 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 != NULL) {
+ if (answer_type == NULL) {
+ 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 = NULL,
+ const Name& expected_algorithm = TSIGKey::HMACMD5_NAME())
+{
+ ASSERT_TRUE(tsig != NULL);
+ 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 != NULL, 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(), NULL, 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(NULL));
+
+ {
+ 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, NULL, 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, NULL,
+ 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, NULL,
+ 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, NULL,
+ 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(), NULL, NULL,
+ 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, NULL, 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, NULL, 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, NULL, qid, 0, 0, NULL));
+ {
+ 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, NULL, &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, NULL,
+ 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, NULL,
+ 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..e50f076
--- /dev/null
+++ b/src/lib/dns/tests/tsigerror_unittest.cc
@@ -0,0 +1,126 @@
+// 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 <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..90c59ac
--- /dev/null
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -0,0 +1,350 @@
+// Copyright (C) 2021-2019 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(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::MD5,
+ TSIGKey(key_name, TSIGKey::HMACMD5_SHORT_NAME(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA1, TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA256, TSIGKey(key_name,
+ TSIGKey::HMACSHA256_NAME(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA224, TSIGKey(key_name,
+ TSIGKey::HMACSHA224_NAME(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA384, TSIGKey(key_name,
+ TSIGKey::HMACSHA384_NAME(),
+ NULL, 0).getAlgorithm());
+ EXPECT_EQ(isc::cryptolink::SHA512, TSIGKey(key_name,
+ TSIGKey::HMACSHA512_NAME(),
+ NULL, 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"), NULL, 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(), NULL, 16),
+ isc::InvalidParameter);
+
+ // Empty secret
+ TSIGKey keye = TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), NULL, 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_EQ(static_cast<const TSIGKey*>(NULL),
+ 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_EQ(static_cast<const TSIGKey*>(NULL), result2.key);
+
+ const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name);
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL), 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_EQ(static_cast<const TSIGKey*>(NULL),
+ keyring.find(Name("noexist.example"), sha256_name).key);
+
+ EXPECT_EQ(TSIGKeyRing::NOTFOUND,
+ keyring.find(Name("another.example"), sha1_name).code);
+ EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
+ 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."), NULL, 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..624f03b
--- /dev/null
+++ b/src/lib/dns/tests/tsigrecord_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2011-2023 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, NULL)),
+ 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..b234c1c
--- /dev/null
+++ b/src/lib/dns/tests/unittest_util.cc
@@ -0,0 +1,176 @@
+// 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 <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());
+}
+
+void
+UnitTestUtil::createRequestMessage(Message& message,
+ const Opcode& opcode,
+ const uint16_t qid,
+ const Name& name,
+ const RRClass& rrclass,
+ const RRType& rrtype)
+{
+ message.clear(Message::RENDER);
+ message.setOpcode(opcode);
+ message.setRcode(Rcode::NOERROR());
+ message.setQid(qid);
+ message.addQuestion(Question(name, rrclass, rrtype));
+}
+
+void
+UnitTestUtil::createDNSSECRequestMessage(Message& message,
+ const Opcode& opcode,
+ const uint16_t qid,
+ const Name& name,
+ const RRClass& rrclass,
+ const RRType& rrtype)
+{
+ message.clear(Message::RENDER);
+ message.setOpcode(opcode);
+ message.setRcode(Rcode::NOERROR());
+ message.setQid(qid);
+ message.addQuestion(Question(name, rrclass, rrtype));
+ EDNSPtr edns(new EDNS());
+ edns->setUDPSize(4096);
+ edns->setDNSSECAwareness(true);
+ message.setEDNS(edns);
+}
diff --git a/src/lib/dns/tests/unittest_util.h b/src/lib/dns/tests/unittest_util.h
new file mode 100644
index 0000000..8c6b8f5
--- /dev/null
+++ b/src/lib/dns/tests/unittest_util.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2009-2017 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 1
+
+#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);
+
+ ///
+ /// Populate a request message
+ ///
+ /// Create a request message in 'request_message' using the
+ /// opcode 'opcode' and the name/class/type query tuple specified in
+ /// 'name', 'rrclass' and 'rrtype.
+ static void
+ createRequestMessage(isc::dns::Message& request_message,
+ const isc::dns::Opcode& opcode,
+ const uint16_t qid,
+ const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+ ///
+ /// Populate a DNSSEC request message
+ ///
+ /// Create a request message in 'request_message' using the
+ /// opcode 'opcode' and the name/class/type query tuple specified in
+ /// 'name', 'rrclass' and 'rrtype.
+ /// EDNS will be added with DO=1 and bufsize 4096
+ static void
+ createDNSSECRequestMessage(isc::dns::Message& request_message,
+ const isc::dns::Opcode& opcode,
+ const uint16_t qid,
+ const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+};
+}
+#endif // UNITTEST_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/tests/zone_checker_unittest.cc b/src/lib/dns/tests/zone_checker_unittest.cc
new file mode 100644
index 0000000..863b439
--- /dev/null
+++ b/src/lib/dns/tests/zone_checker_unittest.cc
@@ -0,0 +1,349 @@
+// Copyright (C) 2012-2020 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/zone_checker.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrset_collection.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <functional>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using isc::Unexpected;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+namespace ph = std::placeholders;
+
+namespace {
+
+const char* const soa_txt = "ns.example.com. root.example.com. 0 0 0 0 0";
+const char* const ns_txt1 = "ns.example.com.";
+const char* const ns_a_txt1 = "192.0.2.1";
+const char* const ns_txt2 = "ns2.example.com.";
+const char* const ns_a_txt2 = "192.0.2.2";
+
+class ZoneCheckerTest : public ::testing::Test {
+protected:
+ ZoneCheckerTest() :
+ zname_("example.com"), zclass_(RRClass::IN()),
+ soa_(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60))),
+ ns_(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60))),
+ callbacks_(std::bind(&ZoneCheckerTest::callback, this, ph::_1, true),
+ std::bind(&ZoneCheckerTest::callback, this, ph::_1, false))
+ {
+ std::stringstream ss;
+ ss << "example.com. 60 IN SOA " << soa_txt << "\n";
+ ss << "example.com. 60 IN NS " << ns_txt1 << "\n";
+ ss << "ns.example.com. 60 IN A " << ns_a_txt1 << "\n";
+ ss << "ns2.example.com. 60 IN A " << ns_a_txt2 << "\n";
+ rrsets_.reset(new RRsetCollection(ss, zname_, zclass_));
+ }
+
+public:
+ // This one is passed to std::bind. Some compilers seem to require
+ // it be public.
+ void callback(const std::string& reason, bool is_error) {
+ if (is_error) {
+ errors_.push_back(reason);
+ } else {
+ warns_.push_back(reason);
+ }
+ }
+
+protected:
+ // Check stored issue messages with expected ones. Clear vectors so
+ // the caller can check other cases.
+ void checkIssues() {
+ EXPECT_EQ(expected_errors_.size(), errors_.size());
+ for (size_t i = 0;
+ i < std::min(expected_errors_.size(), errors_.size());
+ ++i) {
+ // The actual message should begin with the expected message.
+ EXPECT_EQ(0, errors_[0].find(expected_errors_[0]))
+ << "actual message: " << errors_[0] << " expected: " <<
+ expected_errors_[0];
+ }
+ EXPECT_EQ(expected_warns_.size(), warns_.size());
+ for (size_t i = 0;
+ i < std::min(expected_warns_.size(), warns_.size());
+ ++i) {
+ EXPECT_EQ(0, warns_[0].find(expected_warns_[0]))
+ << "actual message: " << warns_[0] << " expected: " <<
+ expected_warns_[0];
+ }
+
+ errors_.clear();
+ expected_errors_.clear();
+ warns_.clear();
+ expected_warns_.clear();
+ }
+
+ const Name zname_;
+ const RRClass zclass_;
+ boost::scoped_ptr<RRsetCollection> rrsets_;
+ RRsetPtr soa_;
+ RRsetPtr ns_;
+ std::vector<std::string> errors_;
+ std::vector<std::string> warns_;
+ std::vector<std::string> expected_errors_;
+ std::vector<std::string> expected_warns_;
+ ZoneCheckerCallbacks callbacks_;
+};
+
+TEST_F(ZoneCheckerTest, checkGood) {
+ // Checking a valid case. No errors or warnings should be reported.
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Multiple NS RRs are okay.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_->addRdata(generic::NS(ns_txt1));
+ ns_->addRdata(generic::NS(ns_txt2));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkSOA) {
+ // If the zone has no SOA it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 0 SOA records");
+ checkIssues();
+
+ // If null callback is specified, checkZone() only returns the final
+ // result.
+ ZoneCheckerCallbacks noerror_callbacks(
+ 0, std::bind(&ZoneCheckerTest::callback, this, ph::_1, false));
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, noerror_callbacks));
+ checkIssues();
+
+ // If there are more than 1 SOA RR, it's also an error.
+ errors_.clear();
+ soa_->addRdata(generic::SOA(soa_txt));
+ soa_->addRdata(generic::SOA("ns2.example.com. . 0 0 0 0 0"));
+ rrsets_->addRRset(soa_);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 2 SOA records");
+ checkIssues();
+
+ // If the SOA RRset is "empty", it's treated as an implementation
+ // (rather than operational) error and results in an exception.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ // Likewise, if the SOA RRset contains non SOA Rdata, it should be a bug.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ soa_->addRdata(createRdata(RRType::NS(), zclass_, "ns.example.com."));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNS) {
+ // If the zone has no NS at origin it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has no NS records");
+ checkIssues();
+
+ // Check two buggy cases like the SOA tests
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(createRdata(RRType::TXT(), zclass_, "ns.example.com"));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNSData) {
+ const Name ns_name("ns.example.com");
+
+ // If a ("in-bailiwick") NS name doesn't have an address record, it's
+ // reported as a warning.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::A());
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // Same check, but disabling warning callback. Same result, but without
+ // the warning.
+ ZoneCheckerCallbacks nowarn_callbacks(
+ std::bind(&ZoneCheckerTest::callback, this, ph::_1, true), 0);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, nowarn_callbacks));
+ checkIssues();
+
+ // A tricky case: if the name matches a wildcard, it should technically
+ // be considered valid, but this checker doesn't check that far and still
+ // warns.
+ RRsetPtr wild(new RRset(Name("*.example.com"), zclass_, RRType::A(),
+ RRTTL(0)));
+ wild->addRdata(in::A("192.0.2.255"));
+ rrsets_->addRRset(wild);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // If there's a CNAME at the name instead, it's an error.
+ rrsets_->removeRRset(Name("*.example.com"), zclass_, RRType::A());
+ RRsetPtr cname(new RRset(ns_name, zclass_, RRType::CNAME(), RRTTL(60)));
+ cname->addRdata(generic::CNAME("cname.example.com."));
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't have to be A. An AAAA is enough.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ RRsetPtr aaaa(new RRset(ns_name, zclass_, RRType::AAAA(), RRTTL(60)));
+ aaaa->addRdata(in::AAAA("2001:db8::1"));
+ rrsets_->addRRset(aaaa);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Coexisting CNAME makes it error (CNAME with other record is itself
+ // invalid, but it's a different issue in this context)
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't matter if the NS name is "out of bailiwick".
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org."));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Note that if the NS name is the origin name, it should be checked
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(zname_));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDelegation) {
+ // Tests various cases where there's a zone cut due to delegation between
+ // the zone origin and the NS name. In each case the NS name doesn't have
+ // an address record.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to delegation in the middle; the check for the address
+ // record should be skipped.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr child_ns(new RRset(Name("child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org."));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut at the NS name. Same result.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(ns_name, zclass_, RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org."));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut below the NS name. The check applies.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(Name("another.ns.child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org."));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDNAME) {
+ // Similar to the above case, but the zone cut is due to DNAME. This is
+ // an invalid configuration.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to DNAME at the zone origin. This is an invalid case.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr dname(new RRset(zname_, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org."));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'example.com'");
+ checkIssues();
+
+ // Zone cut due to DNAME in the middle. Same result.
+ rrsets_->removeRRset(zname_, zclass_, RRType::DNAME());
+ dname.reset(new RRset(Name("child.example.com"), zclass_, RRType::DNAME(),
+ RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org."));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'child.example.com'");
+ checkIssues();
+
+ // A tricky case: there's also an NS at the name that has DNAME. It's
+ // prohibited per RFC6672 so we could say it's "undefined". Nevertheless,
+ // this implementation prefers the NS and skips further checks.
+ ns_.reset(new RRset(Name("child.example.com"), zclass_, RRType::NS(),
+ RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org."));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut due to DNAME at the NS name. In this case DNAME doesn't
+ // affect the NS name, so it should result in "no address record" warning.
+ rrsets_->removeRRset(dname->getName(), zclass_, RRType::DNAME());
+ rrsets_->removeRRset(ns_->getName(), zclass_, RRType::NS());
+ dname.reset(new RRset(ns_name, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org."));
+ rrsets_->addRRset(dname);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+}
diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc
new file mode 100644
index 0000000..d7c85de
--- /dev/null
+++ b/src/lib/dns/tsig.cc
@@ -0,0 +1,581 @@
+// Copyright (C) 2011-2021 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 <sys/time.h>
+
+#include <stdint.h>
+
+#include <cassert>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/time_utilities.h>
+
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/tsig.h>
+#include <dns/tsigerror.h>
+#include <dns/tsigkey.h>
+
+#include <cryptolink/cryptolink.h>
+#include <cryptolink/crypto_hmac.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::cryptolink;
+using namespace isc::dns::rdata;
+
+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 != NULL) {
+ 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.
+ 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_(NULL) {
+ 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_ = new TSIGContextImpl(TSIGKey(key_name, algorithm_name,
+ NULL, 0), TSIGError::BAD_KEY());
+ } else {
+ impl_ = new TSIGContextImpl(*result.key);
+ }
+}
+
+TSIGContext::~TSIGContext() {
+ delete impl_;
+}
+
+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 == NULL || 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, NULL,
+ qid, error.getCode(), 0, NULL)));
+ 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) ? NULL : 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_);
+ 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 == NULL) {
+ 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(), NULL, 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(), NULL, 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 == NULL) {
+ 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(), NULL, 0));
+ }
+ if (impl_->key_.getKeyName() != record->getName() ||
+ impl_->key_.getAlgorithmName() != tsig_rdata.getAlgorithm()) {
+ return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), NULL, 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 = NULL;
+ 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, NULL, 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(), NULL, 0));
+ }
+ if ((tsig_rdata.getMACSize() < 10) ||
+ (tsig_rdata.getMACSize() < (hmac->getOutputLength() / 2))) {
+ // signature length below minimum
+ return (impl_->postVerifyUpdate(TSIGError::FORMERR(), NULL, 0));
+ }
+ if (tsig_rdata.getMACSize() < impl_->digest_len_) {
+ // (truncated) signature length too small
+ return (impl_->postVerifyUpdate(TSIGError::BAD_TRUNC(), NULL, 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(), NULL, 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..ed74e77
--- /dev/null
+++ b/src/lib/dns/tsig.h
@@ -0,0 +1,445 @@
+// Copyright (C) 2011-2021 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 1
+
+#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;
+ TSIGContextImpl* impl_;
+};
+
+typedef boost::shared_ptr<TSIGContext> TSIGContextPtr;
+typedef boost::shared_ptr<TSIGKey> TSIGKeyPtr;
+
+}
+}
+
+#endif // TSIG_H
+
+// Local Variables:
+// mode: c++
+// End:
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..f7727d7
--- /dev/null
+++ b/src/lib/dns/tsigerror.h
@@ -0,0 +1,374 @@
+// 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/.
+
+#ifndef TSIGERROR_H
+#define TSIGERROR_H 1
+
+#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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
new file mode 100644
index 0000000..ad94b62
--- /dev/null
+++ b/src/lib/dns/tsigkey.cc
@@ -0,0 +1,364 @@
+// Copyright (C) 2010-2021 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/base64.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_(NULL) {
+ const HashAlgorithm algorithm = convertAlgorithmName(algorithm_name);
+ if ((secret != NULL && secret_len == 0) ||
+ (secret == NULL && 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 == NULL) {
+ impl_ = new TSIGKeyImpl(key_name, algorithm_name, algorithm,
+ digestbits);
+ } else {
+ impl_ = new TSIGKeyImpl(key_name, algorithm_name, algorithm,
+ digestbits, secret, secret_len);
+ }
+}
+
+TSIGKey::TSIGKey(const std::string& str) : impl_(NULL) {
+ 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_ = new TSIGKeyImpl(Name(keyname_str), algo_name, algorithm,
+ digestbits);
+ } else {
+ impl_ = new 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);
+ }
+
+ TSIGKeyImpl* newimpl = new TSIGKeyImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+TSIGKey::~TSIGKey() {
+ delete impl_;
+}
+
+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] : NULL);
+}
+
+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() {
+ delete impl_;
+}
+
+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, NULL));
+ }
+ 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, NULL));
+ }
+ 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..ead33e1
--- /dev/null
+++ b/src/lib/dns/tsigkey.h
@@ -0,0 +1,391 @@
+// Copyright (C) 2010-2021 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 1
+
+#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 \c 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 \c 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 \c 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;
+ const 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 \c 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 \c 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 \c 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;
+ TSIGKeyRingImpl* impl_;
+};
+}
+}
+
+#endif // TSIGKEY_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc
new file mode 100644
index 0000000..483296d
--- /dev/null
+++ b/src/lib/dns/tsigrecord.cc
@@ -0,0 +1,142 @@
+// 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 <util/buffer.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/tsigrecord.h>
+
+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);
+}
+}
+
+int
+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);
+}
+
+int
+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..0d5a375
--- /dev/null
+++ b/src/lib/dns/tsigrecord.h
@@ -0,0 +1,300 @@
+// 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/.
+
+#ifndef TSIGRECORD_H
+#define TSIGRECORD_H 1
+
+#include <ostream>
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include <util/buffer.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+
+namespace isc {
+namespace util {
+class OutputBuffer;
+}
+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.
+ int 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.
+ int 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
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/zone_checker.cc b/src/lib/dns/zone_checker.cc
new file mode 100644
index 0000000..def9896
--- /dev/null
+++ b/src/lib/dns/zone_checker.cc
@@ -0,0 +1,191 @@
+// Copyright (C) 2012-2020 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/zone_checker.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <functional>
+#include <string>
+
+using boost::lexical_cast;
+using std::string;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace dns {
+
+namespace {
+std::string
+zoneText(const Name& zone_name, const RRClass& zone_class) {
+ return (zone_name.toText(true) + "/" + zone_class.toText());
+}
+
+void
+checkSOA(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callback) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::SOA());
+ size_t count = 0;
+ if (rrset) {
+ for (RdataIteratorPtr rit = rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next(), ++count) {
+ if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
+ NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
+ }
+ }
+ if (count == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
+ }
+ }
+ if (count != 1) {
+ callback.error("zone " + zoneText(zone_name, zone_class) + ": has " +
+ lexical_cast<string>(count) + " SOA records");
+ }
+}
+
+// Check if a target name is beyond zone cut, either due to delegation or
+// DNAME. Note that DNAME works on the origin but not on the name itself,
+// while delegation works on the name itself (but the NS at the origin is not
+// delegation).
+ConstRRsetPtr
+findZoneCut(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets, const Name& target_name) {
+ const unsigned int origin_count = zone_name.getLabelCount();
+ const unsigned int target_count = target_name.getLabelCount();
+ assert(origin_count <= target_count);
+
+ for (unsigned int l = origin_count; l <= target_count; ++l) {
+ const Name& mid_name = (l == target_count) ? target_name :
+ target_name.split(target_count - l);
+
+ ConstRRsetPtr found;
+ if (l != origin_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::NS())) !=
+ NULL) {
+ return (found);
+ }
+ if (l != target_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::DNAME()))
+ != NULL) {
+ return (found);
+ }
+ }
+ return (ConstRRsetPtr());
+}
+
+// Check if each "in-zone" NS name has an address record, identifying some
+// error cases.
+void
+checkNSNames(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ConstRRsetPtr ns_rrset, ZoneCheckerCallbacks& callbacks) {
+ if (ns_rrset->getRdataCount() == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty NS RRset");
+ }
+
+ for (RdataIteratorPtr rit = ns_rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next()) {
+ const rdata::generic::NS* ns_data =
+ dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent());
+ if (ns_data == NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
+ }
+ const Name& ns_name = ns_data->getNSName();
+ const NameComparisonResult::NameRelation reln =
+ ns_name.compare(zone_name).getRelation();
+ if (reln != NameComparisonResult::EQUAL &&
+ reln != NameComparisonResult::SUBDOMAIN) {
+ continue; // not in the zone. we can ignore it.
+ }
+
+ // Check if there's a zone cut between the origin and the NS name.
+ ConstRRsetPtr cut_rrset = findZoneCut(zone_name, zone_class,
+ zone_rrsets, ns_name);
+ if (cut_rrset) {
+ if (cut_rrset->getType() == RRType::NS()) {
+ continue; // delegation; making the NS name "out of zone".
+ } else if (cut_rrset->getType() == RRType::DNAME()) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is " +
+ "below a DNAME '" +
+ cut_rrset->getName().toText(true) +
+ "' (illegal per RFC6672)");
+ continue;
+ } else {
+ assert(false);
+ }
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::CNAME()) != NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is a CNAME " +
+ "(illegal per RFC2181)");
+ continue;
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::A()) == NULL &&
+ zone_rrsets.find(ns_name, zone_class, RRType::AAAA()) == NULL) {
+ callbacks.warn("zone " + zoneText(zone_name, zone_class) +
+ ": NS has no address records (A or AAAA)");
+ }
+ }
+}
+
+void
+checkNS(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callbacks) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::NS());
+ if (rrset == NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": has no NS records");
+ return;
+ }
+ checkNSNames(zone_name, zone_class, zone_rrsets, rrset, callbacks);
+}
+
+// The following is a simple wrapper of checker callback so checkZone()
+// can also remember any critical errors.
+void
+errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
+ bool* had_error) {
+ *had_error = true;
+ callbacks->error(reason);
+}
+}
+
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks) {
+ bool had_error = false;
+ ZoneCheckerCallbacks my_callbacks(
+ std::bind(errorWrapper, ph::_1, &callbacks, &had_error),
+ std::bind(&ZoneCheckerCallbacks::warn, &callbacks, ph::_1));
+
+ checkSOA(zone_name, zone_class, zone_rrsets, my_callbacks);
+ checkNS(zone_name, zone_class, zone_rrsets, my_callbacks);
+
+ return (!had_error);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/zone_checker.h b/src/lib/dns/zone_checker.h
new file mode 100644
index 0000000..be36a32
--- /dev/null
+++ b/src/lib/dns/zone_checker.h
@@ -0,0 +1,153 @@
+// Copyright (C) 2012-2020 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 ZONE_CHECKER_H
+#define ZONE_CHECKER_H 1
+
+#include <dns/dns_fwd.h>
+
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace dns {
+
+/// \brief Set of callbacks used in zone checks.
+///
+/// Objects of this class are expected to be passed to \c checkZone().
+class ZoneCheckerCallbacks {
+public:
+ /// \brief Functor type of the callback on some issue (error or warning).
+ ///
+ /// Its parameter indicates the reason for the corresponding issue.
+ typedef std::function<void(const std::string& reason)> IssueCallback;
+
+ /// \brief Constructor.
+ ///
+ /// Either or both of the callbacks can be empty, in which case the
+ /// corresponding callback will be effectively no-operation. This can be
+ /// used, for example, when the caller of \c checkZone() is only
+ /// interested in the final result. Note that a \c NULL pointer will be
+ /// implicitly converted to an empty functor object, so passing \c NULL
+ /// suffices.
+ ///
+ /// \throw none
+ ///
+ /// \param error_callback Callback functor to be called on critical errors.
+ /// \param warn_callback Callback functor to be called on non critical
+ /// issues.
+ ZoneCheckerCallbacks(const IssueCallback& error_callback,
+ const IssueCallback& warn_callback) :
+ error_callback_(error_callback), warn_callback_(warn_callback)
+ {}
+
+ /// \brief Call the callback for a critical error.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the error.
+ void error(const std::string& reason) const {
+ if (error_callback_) {
+ error_callback_(reason);
+ }
+ }
+
+ /// \brief Call the callback for a non critical issue.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the issue.
+ void warn(const std::string& reason) const {
+ if (warn_callback_)
+ warn_callback_(reason);
+ }
+
+private:
+ IssueCallback error_callback_;
+ IssueCallback warn_callback_;
+};
+
+/// \brief Perform basic integrity checks on zone RRsets.
+///
+/// This function performs some lightweight checks on zone's SOA and (apex)
+/// NS records. Here, lightweight means it doesn't require traversing
+/// the entire zone, and should be expected to complete reasonably quickly
+/// regardless of the size of the zone.
+///
+/// It distinguishes "critical" errors and other undesirable issues:
+/// the former should be interpreted as the resulting zone shouldn't be used
+/// further, e.g, by an authoritative server implementation; the latter means
+/// the issues are better to be addressed but are not necessarily considered
+/// to make the zone invalid. Critical errors are reported via the
+/// \c error() method of \c callbacks, and non critical issues are reported
+/// via its \c warn() method.
+///
+/// Specific checks performed by this function is as follows. Failure of
+/// a check is considered a critical error unless noted otherwise:
+/// - There is exactly one SOA RR at the zone apex.
+/// - There is at least one NS RR at the zone apex.
+/// - For each apex NS record, if the NS name (the RDATA of the record) is
+/// in the zone (i.e., it's a subdomain of the zone origin and above any
+/// zone cut due to delegation), check the following:
+/// - the NS name should have an address record (AAAA or A). Failure of
+/// this check is considered a non critical issue.
+/// - the NS name does not have a CNAME. This is prohibited by Section
+/// 10.3 of RFC 2181.
+/// - the NS name is not subject to DNAME substitution. This is prohibited
+/// by Section 4 of RFC 6672.
+/// .
+///
+/// In addition, when the check is completed without any critical error, this
+/// function guarantees that RRsets for the SOA and (apex) NS stored in the
+/// passed RRset collection have the expected type of Rdata objects,
+/// i.e., generic::SOA and generic::NS, respectively. (This is normally
+/// expected to be the case, but not guaranteed by the API).
+///
+/// As for the check on the existence of AAAA or A records for NS names,
+/// it should be noted that BIND 9 treats this as a critical error.
+/// It's not clear whether it's an implementation dependent behavior or
+/// based on the protocol standard (it looks like the former), but to make
+/// it sure we need to confirm there is even no wildcard match for the names.
+/// This should be a very rare configuration, and more expensive to detect,
+/// so we do not check this condition, and treat this case as a non critical
+/// issue.
+///
+/// This function indicates the result of the checks (whether there is a
+/// critical error) via the return value: It returns \c true if there is no
+/// critical error and returns \c false otherwise. It doesn't throw an
+/// exception on encountering an error so that it can report as many errors
+/// as possible in a single call. If an exception is a better way to signal
+/// the error, the caller can pass a callback object that throws from its
+/// \c error() method.
+///
+/// This function can still throw an exception if it finds a really bogus
+/// condition that is most likely to be an implementation bug of the caller.
+/// Such cases include when an RRset contained in the RRset collection is
+/// empty.
+///
+/// \throw Unexpected Conditions that suggest a caller's bug (see the
+/// description)
+///
+/// \param zone_name The name of the zone to be checked
+/// \param zone_class The RR class of the zone to be checked
+/// \param zone_rrsets The collection of RRsets of the zone
+/// \param callbacks Callback object used to report errors and issues
+///
+/// \return \c true if no critical errors are found; \c false otherwise.
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks);
+
+} // namespace dns
+} // namespace isc
+#endif // ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/eval/Makefile.am b/src/lib/eval/Makefile.am
new file mode 100644
index 0000000..ded89f2
--- /dev/null
+++ b/src/lib/eval/Makefile.am
@@ -0,0 +1,139 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# GCC 4.4 emits warning about breaking strict aliasing rule.
+# This warning is a result of a GCC bug:
+# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41874
+# The warning is raised in the generated code in parser.h.
+# Disabling the strict aliasing rule suppresses this warning.
+AM_CXXFLAGS += $(WARNING_GCC_44_STRICT_ALIASING_CFLAG)
+
+lib_LTLIBRARIES = libkea-eval.la
+libkea_eval_la_SOURCES =
+libkea_eval_la_SOURCES += dependency.cc dependency.h
+libkea_eval_la_SOURCES += eval_log.cc eval_log.h
+libkea_eval_la_SOURCES += evaluate.cc evaluate.h
+libkea_eval_la_SOURCES += token.cc token.h
+
+libkea_eval_la_SOURCES += parser.cc parser.h
+libkea_eval_la_SOURCES += lexer.cc
+libkea_eval_la_SOURCES += location.hh
+libkea_eval_la_SOURCES += eval_context.cc eval_context.h eval_context_decl.h
+libkea_eval_la_SOURCES += eval_messages.h eval_messages.cc
+
+libkea_eval_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_eval_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_eval_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_eval_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_eval_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_eval_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libkea_eval_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libkea_eval_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_eval_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_eval_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_eval_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+libkea_eval_la_LDFLAGS = -no-undefined -version-info 52:0:0
+libkea_eval_la_LDFLAGS += $(CRYPTO_LDFLAGS)
+
+EXTRA_DIST = eval.dox
+EXTRA_DIST += eval_messages.mes
+EXTRA_DIST += lexer.ll parser.yy
+
+CLEANFILES = *.gcno *.gcda
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean:
+ rm -f eval_messages.h eval_messages.cc
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: eval_messages.h eval_messages.cc
+ @echo Message files regenerated
+
+eval_messages.h eval_messages.cc: eval_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/eval/eval_messages.mes
+
+else
+
+messages eval_messages.h eval_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+#
+# If we want to get rid of all flex/bison generated files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild flex/bison without going through
+# reconfigure, a new target parser-clean has been added.
+maintainer-clean-local:
+ rm -f eval_messages.h eval_messages.cc
+ rm -f location.hh lexer.cc parser.cc parser.h
+
+# To regenerate flex/bison files, one can do:
+#
+# make parser-clean
+# make parser
+#
+# This is needed only when the lexer.ll or parser.yy files are modified.
+# Make sure you have both flex and bison installed.
+parser-clean:
+ rm -f location.hh lexer.cc parser.cc parser.h
+
+if GENERATE_PARSER
+
+# Generate parser first.
+all-recursive: lexer.cc location.hh parser.cc parser.h
+
+parser: lexer.cc location.hh parser.cc parser.h
+ @echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+location.hh parser.cc parser.h: parser.yy
+ $(YACC) -Wno-yacc --defines=parser.h -o parser.cc parser.yy
+
+lexer.cc: lexer.ll
+ $(LEX) --prefix eval -o lexer.cc lexer.ll
+
+else
+
+parser location.hh parser.cc parser.h lexer.cc:
+ @echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+endif
+
+
+# Specify the headers for copying into the installation directory tree.
+libkea_eval_includedir = $(pkgincludedir)/eval
+libkea_eval_include_HEADERS = \
+ dependency.h \
+ eval_context.h \
+ eval_context_decl.h \
+ eval_log.h \
+ eval_messages.h \
+ evaluate.h \
+ parser.h \
+ token.h
+# does not include *.hh generated headers as they come with lexer and parser.
diff --git a/src/lib/eval/Makefile.in b/src/lib/eval/Makefile.in
new file mode 100644
index 0000000..6b8edb5
--- /dev/null
+++ b/src/lib/eval/Makefile.in
@@ -0,0 +1,1134 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/eval
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_eval_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)$(libkea_eval_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_eval_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_eval_la_OBJECTS = libkea_eval_la-dependency.lo \
+ libkea_eval_la-eval_log.lo libkea_eval_la-evaluate.lo \
+ libkea_eval_la-token.lo libkea_eval_la-parser.lo \
+ libkea_eval_la-lexer.lo libkea_eval_la-eval_context.lo \
+ libkea_eval_la-eval_messages.lo
+libkea_eval_la_OBJECTS = $(am_libkea_eval_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_eval_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_eval_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_eval_la-dependency.Plo \
+ ./$(DEPDIR)/libkea_eval_la-eval_context.Plo \
+ ./$(DEPDIR)/libkea_eval_la-eval_log.Plo \
+ ./$(DEPDIR)/libkea_eval_la-eval_messages.Plo \
+ ./$(DEPDIR)/libkea_eval_la-evaluate.Plo \
+ ./$(DEPDIR)/libkea_eval_la-lexer.Plo \
+ ./$(DEPDIR)/libkea_eval_la-parser.Plo \
+ ./$(DEPDIR)/libkea_eval_la-token.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_eval_la_SOURCES)
+DIST_SOURCES = $(libkea_eval_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 = $(libkea_eval_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+
+# GCC 4.4 emits warning about breaking strict aliasing rule.
+# This warning is a result of a GCC bug:
+# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41874
+# The warning is raised in the generated code in parser.h.
+# Disabling the strict aliasing rule suppresses this warning.
+AM_CXXFLAGS = $(KEA_CXXFLAGS) $(WARNING_GCC_44_STRICT_ALIASING_CFLAG)
+lib_LTLIBRARIES = libkea-eval.la
+libkea_eval_la_SOURCES = dependency.cc dependency.h eval_log.cc \
+ eval_log.h evaluate.cc evaluate.h token.cc token.h parser.cc \
+ parser.h lexer.cc location.hh eval_context.cc eval_context.h \
+ eval_context_decl.h eval_messages.h eval_messages.cc
+libkea_eval_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_eval_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_eval_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+libkea_eval_la_LDFLAGS = -no-undefined -version-info 52:0:0 \
+ $(CRYPTO_LDFLAGS)
+EXTRA_DIST = eval.dox eval_messages.mes lexer.ll parser.yy
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_eval_includedir = $(pkgincludedir)/eval
+libkea_eval_include_HEADERS = \
+ dependency.h \
+ eval_context.h \
+ eval_context_decl.h \
+ eval_log.h \
+ eval_messages.h \
+ evaluate.h \
+ parser.h \
+ token.h
+
+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/eval/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/eval/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-eval.la: $(libkea_eval_la_OBJECTS) $(libkea_eval_la_DEPENDENCIES) $(EXTRA_libkea_eval_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_eval_la_LINK) -rpath $(libdir) $(libkea_eval_la_OBJECTS) $(libkea_eval_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-dependency.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-eval_context.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-eval_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-eval_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-evaluate.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-lexer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-token.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)$(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 $@ $<
+
+libkea_eval_la-dependency.lo: dependency.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-dependency.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-dependency.Tpo -c -o libkea_eval_la-dependency.lo `test -f 'dependency.cc' || echo '$(srcdir)/'`dependency.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-dependency.Tpo $(DEPDIR)/libkea_eval_la-dependency.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dependency.cc' object='libkea_eval_la-dependency.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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-dependency.lo `test -f 'dependency.cc' || echo '$(srcdir)/'`dependency.cc
+
+libkea_eval_la-eval_log.lo: eval_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-eval_log.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-eval_log.Tpo -c -o libkea_eval_la-eval_log.lo `test -f 'eval_log.cc' || echo '$(srcdir)/'`eval_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-eval_log.Tpo $(DEPDIR)/libkea_eval_la-eval_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='eval_log.cc' object='libkea_eval_la-eval_log.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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-eval_log.lo `test -f 'eval_log.cc' || echo '$(srcdir)/'`eval_log.cc
+
+libkea_eval_la-evaluate.lo: evaluate.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-evaluate.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-evaluate.Tpo -c -o libkea_eval_la-evaluate.lo `test -f 'evaluate.cc' || echo '$(srcdir)/'`evaluate.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-evaluate.Tpo $(DEPDIR)/libkea_eval_la-evaluate.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='evaluate.cc' object='libkea_eval_la-evaluate.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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-evaluate.lo `test -f 'evaluate.cc' || echo '$(srcdir)/'`evaluate.cc
+
+libkea_eval_la-token.lo: token.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-token.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-token.Tpo -c -o libkea_eval_la-token.lo `test -f 'token.cc' || echo '$(srcdir)/'`token.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-token.Tpo $(DEPDIR)/libkea_eval_la-token.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='token.cc' object='libkea_eval_la-token.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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-token.lo `test -f 'token.cc' || echo '$(srcdir)/'`token.cc
+
+libkea_eval_la-parser.lo: parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-parser.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-parser.Tpo -c -o libkea_eval_la-parser.lo `test -f 'parser.cc' || echo '$(srcdir)/'`parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-parser.Tpo $(DEPDIR)/libkea_eval_la-parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser.cc' object='libkea_eval_la-parser.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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-parser.lo `test -f 'parser.cc' || echo '$(srcdir)/'`parser.cc
+
+libkea_eval_la-lexer.lo: 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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-lexer.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-lexer.Tpo -c -o libkea_eval_la-lexer.lo `test -f 'lexer.cc' || echo '$(srcdir)/'`lexer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-lexer.Tpo $(DEPDIR)/libkea_eval_la-lexer.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lexer.cc' object='libkea_eval_la-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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-lexer.lo `test -f 'lexer.cc' || echo '$(srcdir)/'`lexer.cc
+
+libkea_eval_la-eval_context.lo: eval_context.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-eval_context.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-eval_context.Tpo -c -o libkea_eval_la-eval_context.lo `test -f 'eval_context.cc' || echo '$(srcdir)/'`eval_context.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-eval_context.Tpo $(DEPDIR)/libkea_eval_la-eval_context.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='eval_context.cc' object='libkea_eval_la-eval_context.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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-eval_context.lo `test -f 'eval_context.cc' || echo '$(srcdir)/'`eval_context.cc
+
+libkea_eval_la-eval_messages.lo: eval_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-eval_messages.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-eval_messages.Tpo -c -o libkea_eval_la-eval_messages.lo `test -f 'eval_messages.cc' || echo '$(srcdir)/'`eval_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-eval_messages.Tpo $(DEPDIR)/libkea_eval_la-eval_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='eval_messages.cc' object='libkea_eval_la-eval_messages.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_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-eval_messages.lo `test -f 'eval_messages.cc' || echo '$(srcdir)/'`eval_messages.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_eval_includeHEADERS: $(libkea_eval_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_eval_include_HEADERS)'; test -n "$(libkea_eval_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_eval_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_eval_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)$(libkea_eval_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_eval_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_eval_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_eval_include_HEADERS)'; test -n "$(libkea_eval_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_eval_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_eval_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_eval_la-dependency.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-eval_context.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-eval_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-eval_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-evaluate.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-lexer.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-token.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-libkea_eval_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_eval_la-dependency.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-eval_context.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-eval_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-eval_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-evaluate.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-lexer.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_eval_la-token.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_eval_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_eval_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_eval_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean:
+ rm -f eval_messages.h eval_messages.cc
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: eval_messages.h eval_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@eval_messages.h eval_messages.cc: eval_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/eval/eval_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages eval_messages.h eval_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+#
+# If we want to get rid of all flex/bison generated files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild flex/bison without going through
+# reconfigure, a new target parser-clean has been added.
+maintainer-clean-local:
+ rm -f eval_messages.h eval_messages.cc
+ rm -f location.hh lexer.cc parser.cc parser.h
+
+# To regenerate flex/bison files, one can do:
+#
+# make parser-clean
+# make parser
+#
+# This is needed only when the lexer.ll or parser.yy files are modified.
+# Make sure you have both flex and bison installed.
+parser-clean:
+ rm -f location.hh lexer.cc parser.cc parser.h
+
+# Generate parser first.
+@GENERATE_PARSER_TRUE@all-recursive: lexer.cc location.hh parser.cc parser.h
+
+@GENERATE_PARSER_TRUE@parser: lexer.cc location.hh parser.cc parser.h
+@GENERATE_PARSER_TRUE@ @echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+@GENERATE_PARSER_TRUE@location.hh parser.cc parser.h: parser.yy
+@GENERATE_PARSER_TRUE@ $(YACC) -Wno-yacc --defines=parser.h -o parser.cc parser.yy
+
+@GENERATE_PARSER_TRUE@lexer.cc: lexer.ll
+@GENERATE_PARSER_TRUE@ $(LEX) --prefix eval -o lexer.cc lexer.ll
+
+@GENERATE_PARSER_FALSE@parser location.hh parser.cc parser.h lexer.cc:
+@GENERATE_PARSER_FALSE@ @echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+# does not include *.hh generated headers as they come with lexer and parser.
+
+# 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/eval/dependency.cc b/src/lib/eval/dependency.cc
new file mode 100644
index 0000000..66e3600
--- /dev/null
+++ b/src/lib/eval/dependency.cc
@@ -0,0 +1,37 @@
+// Copyright (C) 2018 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 <eval/dependency.h>
+#include <boost/pointer_cast.hpp>
+
+namespace isc {
+namespace dhcp {
+
+bool dependOnClass(const TokenPtr& token, const std::string& name) {
+ boost::shared_ptr<TokenMember> member;
+ member = boost::dynamic_pointer_cast<TokenMember>(token);
+ if (!member) {
+ return (false);
+ }
+ return (member->getClientClass() == name);
+}
+
+bool dependOnClass(const ExpressionPtr& expr, const std::string& name) {
+ if (!expr) {
+ return (false);
+ }
+ for (auto it = expr->cbegin(); it != expr->cend(); ++it) {
+ if (dependOnClass(*it, name)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/eval/dependency.h b/src/lib/eval/dependency.h
new file mode 100644
index 0000000..001bed7
--- /dev/null
+++ b/src/lib/eval/dependency.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2018 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 DEPENDENCY_H
+#define DEPENDENCY_H
+
+#include <eval/token.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Checks dependency on a token.
+///
+/// It checks if the token is a TokenMember for the given class.
+///
+/// @param token A pointer to the token.
+/// @param name The client class name.
+/// @return true if token points to a TokenMember of name, false if not.
+bool dependOnClass(const TokenPtr& token, const std::string& name);
+
+/// @brief Checks dependency on an expression.
+///
+/// It checks if a member of the expression depends on the given class.
+///
+/// @param expr An expression.
+/// @param name The client class name.
+/// @return true if a member of expr depends on name, false if not.
+bool dependOnClass(const ExpressionPtr& expr, const std::string& name);
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/eval/eval.dox b/src/lib/eval/eval.dox
new file mode 100644
index 0000000..e728006
--- /dev/null
+++ b/src/lib/eval/eval.dox
@@ -0,0 +1,198 @@
+// Copyright (C) 2015-2021 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/.
+
+/**
+ @page libeval libkea-eval - Expression Evaluation and Client Classification Library
+
+ @section dhcpEvalIntroduction Introduction
+
+ The core of the libeval library is a parser that is able to parse an
+ expression (e.g. option[123].text == 'APC'). This is currently used for
+ client classification, but in the future may be also used for other
+ applications.
+
+ The external interface to the library is the @ref isc::eval::EvalContext
+ class. Once instantiated, it offers a major method:
+ @ref isc::eval::EvalContext::parseString, which parses the specified
+ string. Once the expression is parsed, it is converted to a collection of
+ tokens that are stored in Reverse Polish Notation in
+ EvalContext::expression.
+
+ Parameters to the @ref isc::eval::EvalContext class constructor are
+ the universe to choose between DHCPv4 and DHCPv6 for DHCP version
+ dependent expressions, and a function used
+ by the parser to accept only already defined or built-in client
+ class names in client class membership expressions. This function defaults
+ to accept all client class names.
+
+ Internally, the parser code is generated by flex and bison. These two
+ tools convert lexer.ll and parser.yy files into a number of .cc and .hh files.
+ To avoid a build of Kea depending on the presence of flex and bison, the
+ result of the generation is checked into the github repository and is
+ distributed in the tarballs.
+
+ @section dhcpEvalLexer Lexer generation using flex
+
+ Flex is used to generate the lexer, a piece of code that converts input
+ data into a series of tokens. It contains a small number of directives,
+ but the majority of the code consists of the definitions of tokens. These
+ definitions are regular expressions that define various tokens, e.g. strings,
+ numbers, parentheses, etc. Once the expression is matched, the associated
+ action is executed. In the majority of the cases a generator method from
+ @ref isc::eval::EvalParser is called, which returns returns a newly created
+ bison token. The purpose of the lexer is to generate a stream
+ of tokens that are consumed by the parser.
+
+ lexer.cc and lexer.hh must not be edited. If there is a need
+ to introduce changes, lexer.ll must be updated and the .cc and .hh files
+ regenerated.
+
+ @section dhcpEvalParser Parser generation using bison
+
+ Bison is used to generate the parser, a piece of code that consumes a
+ stream of tokens and attempts to match it against a defined grammar.
+ The bison parser is created from parser.yy. It contains
+ a number of directives, but the two most important sections are:
+ a list of tokens (for each token defined here, bison will generate the
+ make_NAMEOFTOKEN method in the @ref isc::eval::EvalParser class) and
+ the grammar. The Grammar is a tree like structure with possible loops.
+
+ Here is an over-simplified version of the grammar:
+
+@code
+01. %start expression;
+02.
+03. expression : token EQUAL token
+04. | token
+05. ;
+06.
+07. token : STRING
+08. {
+09. TokenPtr str(new TokenString($1));
+10. ctx.expression.push_back(str);
+11. }
+12. | HEXSTRING
+13. {
+14. TokenPtr hex(new TokenHexString($1));
+15. ctx.expression.push_back(hex);
+16. }
+17. | OPTION '[' INTEGER ']' DOT TEXT
+18. {
+19. TokenPtr opt(new TokenOption($3, TokenOption::TEXTUAL));
+20. ctx.expression.push_back(opt);
+21. }
+22. | OPTION '[' INTEGER ']' DOT HEX
+23. {
+24. TokenPtr opt(new TokenOption($3, TokenOption::HEXADECIMAL));
+25. ctx.expression.push_back(opt);
+26. }
+27. ;
+@endcode
+
+This code determines that the grammar starts from expression (line 1).
+The actual definition of expression (lines 3-5) may either be a
+single token or an expression "token == token" (EQUAL has been defined as
+"==" elsewhere). Token is further
+defined in lines 7-22: it may either be a string (lines 7-11),
+a hex string (lines 12-16), option in the textual format (lines 17-21)
+or option in a hexadecimal format (lines 22-26).
+When the actual case is determined, the respective C++ action
+is executed. For example, if the token is a string, the TokenString class is
+instantiated with the appropriate value and put onto the expression vector.
+
+@section dhcpEvalMakefile Generating parser files
+
+ In the general case, we want to avoid generating parser files, so an
+ average user interested in just compiling Kea would not need flex or
+ bison. Therefore the generated files are already included in the
+ git repository and will be included in the tarball releases.
+
+ However, there will be cases when one of the developers would want
+ to tweak the lexer.ll and parser.yy files and then regenerate
+ the code. For this purpose, two makefile targets are defined:
+ @code
+ make parser
+ @endcode
+ will generate the parsers and
+ @code
+ make parser-clean
+ @endcode
+ will remove the files. Generated files removal was also hooked
+ into the maintainer-clean target.
+
+@section dhcpEvalConfigure Configure options
+
+ Since the flex/bison tools are not necessary for a regular compilation,
+ checks are conducted during the configure script, but the lack of flex or
+ bison tools does not stop the process. There is a flag
+ (--enable-generate-parser) that tells configure script that the
+ parser will be generated. With this flag, the checks for flex/bison
+ are mandatory. If either tool is missing or at too early a version, the
+ configure process will terminate with an error.
+
+@section dhcpEvalToken Supported tokens
+
+ There are a number of tokens implemented. Each token is derived from
+ isc::eval::Token class and represents a certain expression primitive.
+ Currently supported tokens are:
+
+ - isc::dhcp::TokenString -- represents a constant string, e.g. "MSFT".
+ - isc::dhcp::TokenHexString -- represents a constant string, encoded as
+ hex string, e.g. 0x666f6f which is actually "foo".
+ - isc::dhcp::TokenIpAddress -- represents a constant IP address, encoded as
+ a 4 or 16 byte binary string, e.g., 10.0.0.1 is 0x10000001.
+ - isc::dhcp::TokenIpAddressToText -- represents an IP address in text format.
+ - isc::dhcp::TokenOption -- represents an option in a packet, e.g.
+ option[123].text.
+ - isc::dhcp::TokenRelay4Option -- represents a sub-option inserted by the
+ DHCPv4 relay, e.g. relay[123].text or relay[123].hex
+ - isc::dhcp::TokenRelay6Option -- represents a sub-option inserted by
+ a DHCPv6 relay
+ - isc::dhcp::TokenPkt -- represents a DHCP packet meta data (incoming
+ interface name, source/remote or destination/local IP address, length).
+ - isc::dhcp::TokenPkt4 -- represents a DHCPv4 packet field.
+ - isc::dhcp::TokenPkt6 -- represents a DHCPv6 packet field (message type
+ or transaction id).
+ - isc::dhcp::TokenRelay6Field -- represents a DHCPv6 relay information field.
+ - isc::dhcp::TokenEqual -- represents the equal (==) operator.
+ - isc::dhcp::TokenSubstring -- represents the substring(text, start, length) operator.
+ - isc::dhcp::TokenConcat -- represents the concat operator which
+ concatenate two other tokens.
+ - isc::dhcp::TokenIfElse -- represents the ifelse(cond, iftrue, ifelse) operator.
+ - isc::dhcp::TokenToHexString -- represents the hexstring operator which
+ converts a binary value to its hexadecimal string representation.
+ - isc::dhcp::TokenInt8ToText -- represents the signed 8 bit integer in string
+ representation.
+ - isc::dhcp::TokenInt16ToText -- represents the signed 16 bit integer in string
+ representation.
+ - isc::dhcp::TokenInt32ToText -- represents the signed 32 bit integer in string
+ representation.
+ - isc::dhcp::TokenUInt8ToText -- represents the unsigned 8 bit integer in string
+ representation.
+ - isc::dhcp::TokenUInt16ToText -- represents the unsigned 16 bit integer in string
+ representation.
+ - isc::dhcp::TokenUInt32ToText -- represents the unsigned 32 bit integer in string
+ representation.
+ - isc::dhcp::TokenNot -- the logical not operator.
+ - isc::dhcp::TokenAnd -- the logical and (strict) operator.
+ - isc::dhcp::TokenOr -- the logical or (strict) operator (strict means
+ it always evaluates its operands).
+ - isc::dhcp::TokenVendor -- represents vendor information option's existence,
+ enterprise-id field and possible sub-options. (e.g. vendor[1234].exists,
+ vendor[*].enterprise-id, vendor[1234].option[1].exists, vendor[1234].option[1].hex)
+ - isc::dhcp::TokenVendorClass -- represents vendor information option's existence,
+ enterprise-id and included data chunks. (e.g. vendor-class[1234].exists,
+ vendor-class[*].enterprise-id, vendor-class[*].data[3])
+
+More operators are expected to be implemented in upcoming releases.
+
+@section dhcpEvalMTConsiderations Multi-Threading Consideration for Expression Evaluation Library
+
+This library is not thread safe, for instance @ref isc::dhcp::evaluateBool
+or @ref isc::dhcp::evaluateString must not be called in different threads
+on the same packet.
+
+*/
diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc
new file mode 100644
index 0000000..d91fbb0
--- /dev/null
+++ b/src/lib/eval/eval_context.cc
@@ -0,0 +1,260 @@
+// Copyright (C) 2015-2021 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 <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_space.h>
+#include <eval/eval_context.h>
+#include <eval/parser.h>
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <limits>
+
+EvalContext::EvalContext(const Option::Universe& option_universe,
+ CheckDefined check_defined)
+ : trace_scanning_(false), trace_parsing_(false),
+ option_universe_(option_universe), check_defined_(check_defined)
+{
+}
+
+EvalContext::~EvalContext() {
+}
+
+bool
+EvalContext::acceptAll(const ClientClass&) {
+ return (true);
+}
+
+bool
+EvalContext::parseString(const std::string& str, ParserType type) {
+ file_ = "<string>";
+ string_ = str;
+ scanStringBegin(type);
+ int res = -1;
+ try {
+ isc::eval::EvalParser parser(*this);
+ parser.set_debug_level(trace_parsing_);
+ res = parser.parse();
+ } catch (...) {
+ scanStringEnd();
+ throw;
+ }
+ scanStringEnd();
+ return (res == 0);
+}
+
+void
+EvalContext::error(const isc::eval::location& loc, const std::string& what) {
+ isc_throw(EvalParseError, loc << ": " << what);
+}
+
+void
+EvalContext::error (const std::string& what) {
+ isc_throw(EvalParseError, what);
+}
+
+uint16_t
+EvalContext::convertOptionCode(const std::string& option_code,
+ const isc::eval::location& loc) {
+ int n = 0;
+ try {
+ n = boost::lexical_cast<int>(option_code);
+ } catch (const boost::bad_lexical_cast &) {
+ // This can't happen...
+ error(loc, "Option code has invalid value in " + option_code);
+ }
+ if (option_universe_ == Option::V6) {
+ if (n < 0 || n > 65535) {
+ error(loc, "Option code has invalid value in "
+ + option_code + ". Allowed range: 0..65535");
+ }
+ } else {
+ if (n < 0 || n > 255) {
+ error(loc, "Option code has invalid value in "
+ + option_code + ". Allowed range: 0..255");
+ }
+ }
+ return (static_cast<uint16_t>(n));
+}
+
+uint16_t
+EvalContext::convertOptionName(const std::string& option_name,
+ const isc::eval::location& loc) {
+ const std::string global_space = (option_universe_ == Option::V4) ?
+ DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE;
+
+ OptionDefinitionPtr option_def = LibDHCP::getOptionDef(global_space,
+ option_name);
+ if (!option_def) {
+ option_def = LibDHCP::getRuntimeOptionDef(global_space, option_name);
+ }
+
+ if (!option_def) {
+ option_def = LibDHCP::getLastResortOptionDef(global_space, option_name);
+ }
+
+ if (!option_def) {
+ error(loc, "option '" + option_name + "' is not defined");
+ }
+
+ return (option_def->getCode());
+}
+
+int8_t
+EvalContext::convertNestLevelNumber(const std::string& nest_level,
+ const isc::eval::location& loc) {
+ int8_t n = convertInt8(nest_level, loc);
+ if (option_universe_ == Option::V6) {
+ if ((n < - HOP_COUNT_LIMIT) || (n >= HOP_COUNT_LIMIT)) {
+ error(loc, "Nest level has invalid value in "
+ + nest_level + ". Allowed range: -32..31");
+ }
+ } else {
+ error(loc, "Nest level invalid for DHCPv4 packets");
+ }
+
+ return (n);
+}
+
+uint8_t
+EvalContext::convertUint8(const std::string& number,
+ const isc::eval::location& loc) {
+ int64_t n = 0;
+ try {
+ n = boost::lexical_cast<int64_t>(number);
+ } catch (const boost::bad_lexical_cast &) {
+ error(loc, "Invalid integer value in " + number);
+ }
+ if (n < 0 || n > std::numeric_limits<uint8_t>::max()) {
+ error(loc, "Invalid value in "
+ + number + ". Allowed range: 0..255");
+ }
+
+ return (static_cast<uint8_t>(n));
+}
+
+int8_t
+EvalContext::convertInt8(const std::string& number,
+ const isc::eval::location& loc) {
+ int64_t n = 0;
+ try {
+ n = boost::lexical_cast<int64_t>(number);
+ } catch (const boost::bad_lexical_cast &) {
+ error(loc, "Invalid integer value in " + number);
+ }
+ if (n < std::numeric_limits<int8_t>::min() ||
+ n > std::numeric_limits<int8_t>::max()) {
+ error(loc, "Invalid value in "
+ + number + ". Allowed range: -128..127");
+ }
+
+ return (static_cast<int8_t>(n));
+}
+
+uint16_t
+EvalContext::convertUint16(const std::string& number,
+ const isc::eval::location& loc) {
+ int64_t n = 0;
+ try {
+ n = boost::lexical_cast<int64_t>(number);
+ } catch (const boost::bad_lexical_cast &) {
+ error(loc, "Invalid value in " + number);
+ }
+ if (n < 0 || n > std::numeric_limits<uint16_t>::max()) {
+ error(loc, "Invalid value in "
+ + number + ". Allowed range: 0..65535");
+ }
+
+ return (static_cast<uint16_t>(n));
+}
+
+int16_t
+EvalContext::convertInt16(const std::string& number,
+ const isc::eval::location& loc) {
+ uint64_t n = 0;
+ try {
+ n = boost::lexical_cast<int64_t>(number);
+ } catch (const boost::bad_lexical_cast &) {
+ error(loc, "Invalid value in " + number);
+ }
+ if (n > std::numeric_limits<int16_t>::max() ||
+ n < std::numeric_limits<int16_t>::max()) {
+ error(loc, "Invalid value in "
+ + number + ". Allowed range: -32768..32767");
+ }
+
+ return (static_cast<int16_t>(n));
+}
+
+uint32_t
+EvalContext::convertUint32(const std::string& number,
+ const isc::eval::location& loc) {
+ int64_t n = 0;
+ try {
+ n = boost::lexical_cast<int64_t>(number);
+ } catch (const boost::bad_lexical_cast &) {
+ error(loc, "Invalid value in " + number);
+ }
+ if (n < 0 || n > std::numeric_limits<uint32_t>::max()) {
+ error(loc, "Invalid value in "
+ + number + ". Allowed range: 0..4294967295");
+ }
+
+ return (static_cast<uint32_t>(n));
+}
+
+int32_t
+EvalContext::convertInt32(const std::string& number,
+ const isc::eval::location& loc) {
+ int64_t n = 0;
+ try {
+ n = boost::lexical_cast<int64_t>(number);
+ } catch (const boost::bad_lexical_cast &) {
+ error(loc, "Invalid value in " + number);
+ }
+ if (n > std::numeric_limits<int32_t>::max() ||
+ n < std::numeric_limits<int32_t>::max()) {
+ error(loc, "Invalid value in "
+ + number + ". Allowed range: -2147483648..2147483647");
+ }
+
+ return (static_cast<int32_t>(n));
+}
+
+std::string
+EvalContext::fromUint32(const uint32_t integer) {
+ std::string tmp(4, 0);
+ tmp[0] = (integer >> 24) & 0xff;
+ tmp[1] = (integer >> 16) & 0xff;
+ tmp[2] = (integer >> 8) & 0xff;
+ tmp[3] = integer & 0xff;
+
+ return (tmp);
+}
+
+std::string
+EvalContext::fromUint16(const uint16_t integer) {
+ std::string tmp(2, 0);
+ tmp[0] = (integer >> 8) & 0xff;
+ tmp[1] = integer & 0xff;
+
+ return (tmp);
+}
+
+bool
+EvalContext::isClientClassDefined(const ClientClass& client_class) {
+ return (check_defined_(client_class));
+}
+
+void
+EvalContext::fatal(const std::string& what) {
+ isc_throw(Unexpected, what);
+}
diff --git a/src/lib/eval/eval_context.h b/src/lib/eval/eval_context.h
new file mode 100644
index 0000000..8050925
--- /dev/null
+++ b/src/lib/eval/eval_context.h
@@ -0,0 +1,249 @@
+// Copyright (C) 2015-2022 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 EVAL_CONTEXT_H
+#define EVAL_CONTEXT_H
+#include <string>
+#include <map>
+#include <eval/parser.h>
+#include <eval/eval_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// Tell Flex the lexer's prototype ...
+#define YY_DECL \
+ isc::eval::EvalParser::symbol_type evallex (EvalContext& driver)
+
+// ... and declare it for the parser's sake.
+YY_DECL;
+
+namespace isc {
+namespace eval {
+
+/// @brief Evaluation error exception raised when trying to parse an exceptions.
+class EvalParseError : public isc::Exception {
+public:
+ EvalParseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Evaluation context, an interface to the expression evaluation.
+class EvalContext {
+public:
+
+ /// @brief Specifies what type of expression the parser is expected to see
+ typedef enum {
+ PARSER_BOOL, ///< expression is expected to evaluate to bool
+ PARSER_STRING ///< expression is expected to evaluate to string
+ } ParserType;
+
+ /// @brief Type of the check defined function.
+ typedef std::function<bool(const ClientClass&)> CheckDefined;
+
+ /// @brief Default constructor.
+ ///
+ /// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used
+ /// by the parser to determine which option definitions set should be used
+ /// to map option names to option codes.
+ /// @param check_defined A function called to check if a client class
+ /// used for membership is already defined. If it is not the parser
+ /// will fail: only backward or built-in references are accepted.
+ EvalContext(const Option::Universe& option_universe,
+ CheckDefined check_defined = acceptAll);
+
+ /// @brief destructor
+ virtual ~EvalContext();
+
+ /// @brief Accept all client class names
+ ///
+ /// @param client_class (unused)
+ /// @return true
+ static bool acceptAll(const ClientClass& client_class);
+
+ /// @brief Parsed expression (output tokens are stored here)
+ isc::dhcp::Expression expression;
+
+ /// @brief Method called before scanning starts on a string.
+ ///
+ /// @param type specifies type of the expression to be parsed
+ void scanStringBegin(ParserType type);
+
+ /// @brief Method called after the last tokens are scanned from a string.
+ void scanStringEnd();
+
+ /// @brief Run the parser on the string specified.
+ ///
+ /// @param str string to be parsed
+ /// @param type type of the expression expected/parser type to be created
+ /// @return true on success.
+ bool parseString(const std::string& str, ParserType type = PARSER_BOOL);
+
+ /// @brief The name of the file being parsed.
+ /// Used later to pass the file name to the location tracker.
+ std::string file_;
+
+ /// @brief The string being parsed.
+ std::string string_;
+
+ /// @brief Error handler
+ ///
+ /// @param loc location within the parsed file where the problem was experienced.
+ /// @param what string explaining the nature of the error.
+ static void error(const isc::eval::location& loc, const std::string& what);
+
+ /// @brief Error handler
+ ///
+ /// This is a simplified error reporting tool for possible future
+ /// cases when the EvalParser is not able to handle the packet.
+ static void error(const std::string& what);
+
+ /// @brief Fatal error handler
+ ///
+ /// This is for should not happen but fatal errors
+ static void fatal(const std::string& what);
+
+ /// @brief Option code conversion
+ ///
+ /// @param option_code a string representing the integer code
+ /// @param loc the location of the token
+ /// @result the option code
+ /// @throw calls the syntax error function if the value is not in
+ /// the range 0..255 or 0..65535
+ uint16_t convertOptionCode(const std::string& option_code,
+ const isc::eval::location& loc);
+
+ /// @brief Option name conversion
+ ///
+ /// @param option_name the option name
+ /// @param loc the location of the token
+ /// @return the option code
+ /// @throw calls the syntax error function if the name cannot be resolved
+ uint16_t convertOptionName(const std::string& option_name,
+ const isc::eval::location& loc);
+
+ /// @brief Attempts to convert string to unsigned 32bit integer
+ ///
+ /// For reverse conversion, see @ref fromUint32
+ ///
+ /// @param number string to be converted
+ /// @param loc the location of the token
+ /// @return the integer value
+ /// @throw EvalParseError if conversion fails or the value is out of range.
+ static uint32_t convertUint32(const std::string& number,
+ const isc::eval::location& loc);
+
+ /// @brief Attempts to convert string to signed 32bit integer
+ ///
+ /// @param number string to be converted
+ /// @param loc the location of the token
+ /// @return the integer value
+ /// @throw EvalParseError if conversion fails or the value is out of range.
+ static int32_t convertInt32(const std::string& number,
+ const isc::eval::location& loc);
+
+ /// @brief Attempts to convert string to unsigned 16bit integer
+ ///
+ /// For reverse conversion, see @ref fromUint16
+ ///
+ /// @param number string to be converted
+ /// @param loc the location of the token
+ /// @return the integer value
+ /// @throw EvalParseError if conversion fails or the value is out of range.
+ static uint16_t convertUint16(const std::string& number,
+ const isc::eval::location& loc);
+
+ /// @brief Attempts to convert string to signed 16bit integer
+ ///
+ /// @param number string to be converted
+ /// @param loc the location of the token
+ /// @return the integer value
+ /// @throw EvalParseError if conversion fails or the value is out of range.
+ static int16_t convertInt16(const std::string& number,
+ const isc::eval::location& loc);
+
+ /// @brief Attempts to convert string to unsigned 8bit integer
+ ///
+ /// @param number string to be converted
+ /// @param loc the location of the token
+ /// @return the integer value
+ /// @throw EvalParseError if conversion fails or the value is out of range.
+ static uint8_t convertUint8(const std::string& number,
+ const isc::eval::location& loc);
+
+ /// @brief Attempts to convert string to signed 8bit integer
+ ///
+ /// @param number string to be converted
+ /// @param loc the location of the token
+ /// @return the integer value
+ /// @throw EvalParseError if conversion fails or the value is out of range.
+ static int8_t convertInt8(const std::string& number,
+ const isc::eval::location& loc);
+
+ /// @brief Nest level conversion
+ ///
+ /// @param nest_level a string representing the integer nesting level
+ /// @param loc the location of the token
+ /// @return the nesting level
+ /// @throw calls the syntax error function if the value is not in
+ /// the range -32..31
+ int8_t convertNestLevelNumber(const std::string& nest_level,
+ const isc::eval::location& loc);
+
+ /// @brief Converts unsigned 32bit integer to string representation
+ ///
+ /// The integer is coded as a 4 byte long string in network order, e.g.
+ /// 6 is represented as 00000006. For reverse conversion, see
+ /// @ref convertUint32.
+ ///
+ /// @param integer value to be converted
+ /// @return 4 byte long string that encodes the value.
+ static std::string fromUint32(const uint32_t integer);
+
+ /// @brief Converts unsigned 16bit integer to string representation
+ ///
+ /// The integer is coded as a 2 byte long string in network order, e.g.
+ /// 6 is represented as 0006. For reverse conversion, see
+ /// @ref convertUint16.
+ ///
+ /// @param integer value to be converted
+ /// @return 2 byte long string that encodes the value.
+ static std::string fromUint16(const uint16_t integer);
+
+ /// @brief Returns the universe (v4 or v6)
+ ///
+ /// @return universe
+ Option::Universe getUniverse() {
+ return (option_universe_);
+ }
+
+ /// @brief Check if a client class is already defined
+ ///
+ /// @param client_class the client class name to check
+ /// @return true if the client class is defined, false if not
+ bool isClientClassDefined(const ClientClass& client_class);
+
+private:
+ /// @brief Flag determining scanner debugging.
+ bool trace_scanning_;
+
+ /// @brief Flag determining parser debugging.
+ bool trace_parsing_;
+
+ /// @brief Option universe: DHCPv4 or DHCPv6.
+ ///
+ /// This is used by the parser to determine which option definitions
+ /// set should be used to map option name to option code.
+ Option::Universe option_universe_;
+
+ /// @brief Function to check if a client class is already defined.
+ CheckDefined check_defined_;
+
+};
+
+} // end of isc::eval namespace
+} // end of isc namespace
+
+#endif
diff --git a/src/lib/eval/eval_context_decl.h b/src/lib/eval/eval_context_decl.h
new file mode 100644
index 0000000..c838011
--- /dev/null
+++ b/src/lib/eval/eval_context_decl.h
@@ -0,0 +1,20 @@
+// Copyright (C) 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/.
+
+#ifndef EVAL_CONTEXT_DECL_H
+#define EVAL_CONTEXT_DECL_H
+
+/// @file eval_context_decl.h Forward declaration of the EvalContext class
+
+namespace isc {
+namespace eval {
+
+class EvalContext;
+
+}; // end of isc::eval namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/eval/eval_log.cc b/src/lib/eval/eval_log.cc
new file mode 100644
index 0000000..d9aeffa
--- /dev/null
+++ b/src/lib/eval/eval_log.cc
@@ -0,0 +1,23 @@
+// Copyright (C) 2015-2018 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/.
+
+/// Defines the logger used by the Eval (classification) code
+
+#include <config.h>
+
+#include <eval/eval_log.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const int EVAL_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int EVAL_DBG_STACK = isc::log::DBGLVL_TRACE_DETAIL_DATA;
+
+isc::log::Logger eval_logger("eval");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/eval/eval_log.h b/src/lib/eval/eval_log.h
new file mode 100644
index 0000000..5667025
--- /dev/null
+++ b/src/lib/eval/eval_log.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2015-2018 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 EVAL_LOG_H
+#define EVAL_LOG_H
+
+#include <log/macros.h>
+#include <eval/eval_messages.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Eval debug Logging levels
+///
+/// Defines the levels used to output debug messages in the eval (classification) code.
+/// Note that higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations,
+extern const int EVAL_DBG_TRACE;
+
+// Additional information on the calls. Report the values that were
+// popped from or pushed to the value stack.
+extern const int EVAL_DBG_STACK;
+
+/// @brief Eval Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger eval_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // EVAL_LOG_H
diff --git a/src/lib/eval/eval_messages.cc b/src/lib/eval/eval_messages.cc
new file mode 100644
index 0000000..7dfeba1
--- /dev/null
+++ b/src/lib/eval/eval_messages.cc
@@ -0,0 +1,113 @@
+// File created from ../../../src/lib/eval/eval_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID EVAL_DEBUG_AND = "EVAL_DEBUG_AND";
+extern const isc::log::MessageID EVAL_DEBUG_CONCAT = "EVAL_DEBUG_CONCAT";
+extern const isc::log::MessageID EVAL_DEBUG_EQUAL = "EVAL_DEBUG_EQUAL";
+extern const isc::log::MessageID EVAL_DEBUG_HEXSTRING = "EVAL_DEBUG_HEXSTRING";
+extern const isc::log::MessageID EVAL_DEBUG_IFELSE_FALSE = "EVAL_DEBUG_IFELSE_FALSE";
+extern const isc::log::MessageID EVAL_DEBUG_IFELSE_TRUE = "EVAL_DEBUG_IFELSE_TRUE";
+extern const isc::log::MessageID EVAL_DEBUG_INT16TOTEXT = "EVAL_DEBUG_INT16TOTEXT";
+extern const isc::log::MessageID EVAL_DEBUG_INT32TOTEXT = "EVAL_DEBUG_INT32TOTEXT";
+extern const isc::log::MessageID EVAL_DEBUG_INT8TOTEXT = "EVAL_DEBUG_INT8TOTEXT";
+extern const isc::log::MessageID EVAL_DEBUG_IPADDRESS = "EVAL_DEBUG_IPADDRESS";
+extern const isc::log::MessageID EVAL_DEBUG_IPADDRESSTOTEXT = "EVAL_DEBUG_IPADDRESSTOTEXT";
+extern const isc::log::MessageID EVAL_DEBUG_MEMBER = "EVAL_DEBUG_MEMBER";
+extern const isc::log::MessageID EVAL_DEBUG_NOT = "EVAL_DEBUG_NOT";
+extern const isc::log::MessageID EVAL_DEBUG_OPTION = "EVAL_DEBUG_OPTION";
+extern const isc::log::MessageID EVAL_DEBUG_OR = "EVAL_DEBUG_OR";
+extern const isc::log::MessageID EVAL_DEBUG_PKT = "EVAL_DEBUG_PKT";
+extern const isc::log::MessageID EVAL_DEBUG_PKT4 = "EVAL_DEBUG_PKT4";
+extern const isc::log::MessageID EVAL_DEBUG_PKT6 = "EVAL_DEBUG_PKT6";
+extern const isc::log::MessageID EVAL_DEBUG_RELAY6 = "EVAL_DEBUG_RELAY6";
+extern const isc::log::MessageID EVAL_DEBUG_RELAY6_RANGE = "EVAL_DEBUG_RELAY6_RANGE";
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT = "EVAL_DEBUG_SPLIT";
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT_DELIM_EMPTY = "EVAL_DEBUG_SPLIT_DELIM_EMPTY";
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT_EMPTY = "EVAL_DEBUG_SPLIT_EMPTY";
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE = "EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE";
+extern const isc::log::MessageID EVAL_DEBUG_STRING = "EVAL_DEBUG_STRING";
+extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING = "EVAL_DEBUG_SUBSTRING";
+extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_EMPTY = "EVAL_DEBUG_SUBSTRING_EMPTY";
+extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_RANGE = "EVAL_DEBUG_SUBSTRING_RANGE";
+extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION = "EVAL_DEBUG_SUB_OPTION";
+extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION_NO_OPTION = "EVAL_DEBUG_SUB_OPTION_NO_OPTION";
+extern const isc::log::MessageID EVAL_DEBUG_TOHEXSTRING = "EVAL_DEBUG_TOHEXSTRING";
+extern const isc::log::MessageID EVAL_DEBUG_UINT16TOTEXT = "EVAL_DEBUG_UINT16TOTEXT";
+extern const isc::log::MessageID EVAL_DEBUG_UINT32TOTEXT = "EVAL_DEBUG_UINT32TOTEXT";
+extern const isc::log::MessageID EVAL_DEBUG_UINT8TOTEXT = "EVAL_DEBUG_UINT8TOTEXT";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA = "EVAL_DEBUG_VENDOR_CLASS_DATA";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND = "EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID = "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH = "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_EXISTS = "EVAL_DEBUG_VENDOR_CLASS_EXISTS";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_NO_OPTION = "EVAL_DEBUG_VENDOR_CLASS_NO_OPTION";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID = "EVAL_DEBUG_VENDOR_ENTERPRISE_ID";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH = "EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_EXISTS = "EVAL_DEBUG_VENDOR_EXISTS";
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_NO_OPTION = "EVAL_DEBUG_VENDOR_NO_OPTION";
+extern const isc::log::MessageID EVAL_RESULT = "EVAL_RESULT";
+
+} // namespace dhcp
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "EVAL_DEBUG_AND", "Popping %1 and %2 pushing %3",
+ "EVAL_DEBUG_CONCAT", "Popping %1 and %2 pushing %3",
+ "EVAL_DEBUG_EQUAL", "Popping %1 and %2 pushing result %3",
+ "EVAL_DEBUG_HEXSTRING", "Pushing hex string %1",
+ "EVAL_DEBUG_IFELSE_FALSE", "Popping %1 (false) and %2, leaving %3",
+ "EVAL_DEBUG_IFELSE_TRUE", "Popping %1 (true) and %2, leaving %3",
+ "EVAL_DEBUG_INT16TOTEXT", "Pushing Int16 %1",
+ "EVAL_DEBUG_INT32TOTEXT", "Pushing Int32 %1",
+ "EVAL_DEBUG_INT8TOTEXT", "Pushing Int8 %1",
+ "EVAL_DEBUG_IPADDRESS", "Pushing IPAddress %1",
+ "EVAL_DEBUG_IPADDRESSTOTEXT", "Pushing IPAddress %1",
+ "EVAL_DEBUG_MEMBER", "Checking membership of '%1', pushing result %2",
+ "EVAL_DEBUG_NOT", "Popping %1 pushing %2",
+ "EVAL_DEBUG_OPTION", "Pushing option %1 with value %2",
+ "EVAL_DEBUG_OR", "Popping %1 and %2 pushing %3",
+ "EVAL_DEBUG_PKT", "Pushing PKT meta data %1 with value %2",
+ "EVAL_DEBUG_PKT4", "Pushing PKT4 field %1 with value %2",
+ "EVAL_DEBUG_PKT6", "Pushing PKT6 field %1 with value %2",
+ "EVAL_DEBUG_RELAY6", "Pushing PKT6 relay field %1 nest %2 with value %3",
+ "EVAL_DEBUG_RELAY6_RANGE", "Pushing PKT6 relay field %1 nest %2 with value %3",
+ "EVAL_DEBUG_SPLIT", "Popping field %1, delimiters %2, string %3, pushing result %4",
+ "EVAL_DEBUG_SPLIT_DELIM_EMPTY", "Popping field %1, delimiters %2, string %3, pushing result %4",
+ "EVAL_DEBUG_SPLIT_EMPTY", "Popping field %1, delimiters %2, string %3, pushing result %4",
+ "EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE", "Popping field %1, delimiters %2, string %3, pushing result %4",
+ "EVAL_DEBUG_STRING", "Pushing text string %1",
+ "EVAL_DEBUG_SUBSTRING", "Popping length %1, start %2, string %3 pushing result %4",
+ "EVAL_DEBUG_SUBSTRING_EMPTY", "Popping length %1, start %2, string %3 pushing result %4",
+ "EVAL_DEBUG_SUBSTRING_RANGE", "Popping length %1, start %2, string %3 pushing result %4",
+ "EVAL_DEBUG_SUB_OPTION", "Pushing option %1 sub-option %2 with value %3",
+ "EVAL_DEBUG_SUB_OPTION_NO_OPTION", "Requested option %1 sub-option %2, but the parent option is not present, pushing result %3",
+ "EVAL_DEBUG_TOHEXSTRING", "Popping binary value %1 and separator %2, pushing result %3",
+ "EVAL_DEBUG_UINT16TOTEXT", "Pushing UInt16 %1",
+ "EVAL_DEBUG_UINT32TOTEXT", "Pushing UInt32 %1",
+ "EVAL_DEBUG_UINT8TOTEXT", "Pushing UInt8 %1",
+ "EVAL_DEBUG_VENDOR_CLASS_DATA", "Data %1 (out of %2 received) in vendor class found, pushing result '%3'",
+ "EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND", "Requested data index %1, but option with enterprise-id %2 has only %3 data tuple(s), pushing result '%4'",
+ "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID", "Pushing enterprise-id %1 as result 0x%2",
+ "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH", "Was looking for %1, option had %2, pushing result '%3'",
+ "EVAL_DEBUG_VENDOR_CLASS_EXISTS", "Option with enterprise-id %1 found, pushing result '%2'",
+ "EVAL_DEBUG_VENDOR_CLASS_NO_OPTION", "Option with code %1 missing, pushing result '%2'",
+ "EVAL_DEBUG_VENDOR_ENTERPRISE_ID", "Pushing enterprise-id %1 as result 0x%2",
+ "EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH", "Was looking for %1, option had %2, pushing result '%3'",
+ "EVAL_DEBUG_VENDOR_EXISTS", "Option with enterprise-id %1 found, pushing result '%2'",
+ "EVAL_DEBUG_VENDOR_NO_OPTION", "Option with code %1 missing, pushing result '%2'",
+ "EVAL_RESULT", "Expression %1 evaluated to %2",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/eval/eval_messages.h b/src/lib/eval/eval_messages.h
new file mode 100644
index 0000000..6353ab2
--- /dev/null
+++ b/src/lib/eval/eval_messages.h
@@ -0,0 +1,60 @@
+// File created from ../../../src/lib/eval/eval_messages.mes
+
+#ifndef EVAL_MESSAGES_H
+#define EVAL_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace dhcp {
+
+extern const isc::log::MessageID EVAL_DEBUG_AND;
+extern const isc::log::MessageID EVAL_DEBUG_CONCAT;
+extern const isc::log::MessageID EVAL_DEBUG_EQUAL;
+extern const isc::log::MessageID EVAL_DEBUG_HEXSTRING;
+extern const isc::log::MessageID EVAL_DEBUG_IFELSE_FALSE;
+extern const isc::log::MessageID EVAL_DEBUG_IFELSE_TRUE;
+extern const isc::log::MessageID EVAL_DEBUG_INT16TOTEXT;
+extern const isc::log::MessageID EVAL_DEBUG_INT32TOTEXT;
+extern const isc::log::MessageID EVAL_DEBUG_INT8TOTEXT;
+extern const isc::log::MessageID EVAL_DEBUG_IPADDRESS;
+extern const isc::log::MessageID EVAL_DEBUG_IPADDRESSTOTEXT;
+extern const isc::log::MessageID EVAL_DEBUG_MEMBER;
+extern const isc::log::MessageID EVAL_DEBUG_NOT;
+extern const isc::log::MessageID EVAL_DEBUG_OPTION;
+extern const isc::log::MessageID EVAL_DEBUG_OR;
+extern const isc::log::MessageID EVAL_DEBUG_PKT;
+extern const isc::log::MessageID EVAL_DEBUG_PKT4;
+extern const isc::log::MessageID EVAL_DEBUG_PKT6;
+extern const isc::log::MessageID EVAL_DEBUG_RELAY6;
+extern const isc::log::MessageID EVAL_DEBUG_RELAY6_RANGE;
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT;
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT_DELIM_EMPTY;
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT_EMPTY;
+extern const isc::log::MessageID EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE;
+extern const isc::log::MessageID EVAL_DEBUG_STRING;
+extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING;
+extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_EMPTY;
+extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_RANGE;
+extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION;
+extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION_NO_OPTION;
+extern const isc::log::MessageID EVAL_DEBUG_TOHEXSTRING;
+extern const isc::log::MessageID EVAL_DEBUG_UINT16TOTEXT;
+extern const isc::log::MessageID EVAL_DEBUG_UINT32TOTEXT;
+extern const isc::log::MessageID EVAL_DEBUG_UINT8TOTEXT;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_EXISTS;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_NO_OPTION;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_EXISTS;
+extern const isc::log::MessageID EVAL_DEBUG_VENDOR_NO_OPTION;
+extern const isc::log::MessageID EVAL_RESULT;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // EVAL_MESSAGES_H
diff --git a/src/lib/eval/eval_messages.mes b/src/lib/eval/eval_messages.mes
new file mode 100644
index 0000000..1cace9a
--- /dev/null
+++ b/src/lib/eval/eval_messages.mes
@@ -0,0 +1,285 @@
+# Copyright (C) 2015-2022 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/.
+
+$NAMESPACE isc::dhcp
+
+
+# For use with TokenAnd
+% EVAL_DEBUG_AND Popping %1 and %2 pushing %3
+This debug message indicates that two values are popped from
+the value stack. Then are then combined via logical and and
+the result is pushed onto the value stack.
+
+# For use with TokenConcat
+
+% EVAL_DEBUG_CONCAT Popping %1 and %2 pushing %3
+This debug message indicates that the two strings are being popped off
+of the stack. They are then concatenated and the resulting string is
+pushed onto the stack. The strings are displayed in hex.
+
+# For use with TokenEqual
+# Start with binary for the inputs for now, we may add text in the future.
+
+% EVAL_DEBUG_EQUAL Popping %1 and %2 pushing result %3
+This debug message indicates that the two strings are being popped off
+of the value stack and the result of comparing them is being pushed onto
+the value stack. The strings are displayed in hex.
+
+# For use with TokenHexString
+
+% EVAL_DEBUG_HEXSTRING Pushing hex string %1
+This debug message indicates that the given binary string is being pushed
+onto the value stack. The string is displayed in hex.
+
+# For use with TokenIfElse
+
+% EVAL_DEBUG_IFELSE_FALSE Popping %1 (false) and %2, leaving %3
+This debug message indicates that the condition is false so
+the iftrue branch value is removed and the ifelse branch value
+is left on the value stack.
+
+% EVAL_DEBUG_IFELSE_TRUE Popping %1 (true) and %2, leaving %3
+This debug message indicates that the condition is true so
+the ifelse branch value is removed and the iftrue branch value
+is left on the value stack.
+
+# For use with TokenIpAddress
+
+% EVAL_DEBUG_INT16TOTEXT Pushing Int16 %1
+This debug message indicates that the given address string representation is
+being pushed onto the value stack. This represents a 16 bit integer.
+
+# For use with TokenInt32ToText
+
+% EVAL_DEBUG_INT32TOTEXT Pushing Int32 %1
+This debug message indicates that the given address string representation is
+being pushed onto the value stack. This represents a 32 bit integer.
+
+# For use with TokenUInt8ToText
+
+% EVAL_DEBUG_INT8TOTEXT Pushing Int8 %1
+This debug message indicates that the given address string representation is
+being pushed onto the value stack. This represents an 8 bit integer.
+
+# For use with TokenInt16ToText
+
+% EVAL_DEBUG_IPADDRESS Pushing IPAddress %1
+This debug message indicates that the given binary string is being pushed
+onto the value stack. This represents either an IPv4 or IPv6 address.
+The string is displayed in hex.
+
+# For use with TokenIpAddressToText
+
+% EVAL_DEBUG_IPADDRESSTOTEXT Pushing IPAddress %1
+This debug message indicates that the given address string representation is
+being pushed onto the value stack. This represents either an IPv4 or IPv6
+address.
+
+# For use with TokenInt8ToText
+
+% EVAL_DEBUG_MEMBER Checking membership of '%1', pushing result %2
+This debug message indicates that the membership of the packet for
+the client class was checked.
+
+# For use with TokenNot
+
+% EVAL_DEBUG_NOT Popping %1 pushing %2
+This debug message indicates that the first value is popped from
+the value stack, negated and then pushed onto the value stack.
+The string is displayed in text.
+
+# For use with TokenOption based classes. These include TokenOption,
+# TokenRelay4Option and TokenRelay6Option.
+
+% EVAL_DEBUG_OPTION Pushing option %1 with value %2
+This debug message indicates that the given string representing the
+value of the requested option is being pushed onto the value stack.
+The string may be the text or binary value of the string based on the
+representation type requested (.text or .hex) or "true" or "false" if
+the requested type is .exists. The option code may be for either an
+option or a sub-option as requested in the classification statement.
+
+# For use with TokenOr
+
+% EVAL_DEBUG_OR Popping %1 and %2 pushing %3
+This debug message indicates that two values are popped from
+the value stack. Then are then combined via logical or and
+the result is pushed onto the value stack. The string is displayed
+in text.
+
+# For use with TokenPkt
+
+% EVAL_DEBUG_PKT Pushing PKT meta data %1 with value %2
+This debug message indicates that the given binary string representing
+the value of the requested meta data is being pushed onto the value stack.
+The string is displayed in hex at the exception of interface name.
+
+# For use with TokenPkt4
+
+% EVAL_DEBUG_PKT4 Pushing PKT4 field %1 with value %2
+This debug message indicates that the given binary string representing
+the value of the requested field is being pushed onto the value stack.
+The string is displayed in hex.
+
+# For use with TokenPkt6
+
+% EVAL_DEBUG_PKT6 Pushing PKT6 field %1 with value %2
+This debug message indicates that the given binary string representing
+the value of the requested field is being pushed onto the value stack.
+The string is displayed in hex.
+
+# For use with TokenRelay6Field
+
+% EVAL_DEBUG_RELAY6 Pushing PKT6 relay field %1 nest %2 with value %3
+This debug message indicates that the given binary string representing
+the value of the requested field is being pushed onto the value stack.
+The string is displayed in hex.
+
+% EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field %1 nest %2 with value %3
+This debug message is generated if the nest field is out of range. The
+empty string will always be the value pushed onto the stack.
+
+# For use with TokenString
+
+% EVAL_DEBUG_SPLIT Popping field %1, delimiters %2, string %3, pushing result %4
+This debug message indicates that three values are being popped from the stack
+and a result is being pushed onto the stack. The values being popped are the
+field, delimiter and string. The result is the extracted field which is pushed
+onto the stack. The strings are displayed in hex.
+
+% EVAL_DEBUG_SPLIT_DELIM_EMPTY Popping field %1, delimiters %2, string %3, pushing result %4
+This debug message indicates that the delimiter popped from the stack was empty
+and so the result will be the entire string. The field, delimiter and string
+are still popped from the stack and the result is still pushed.
+
+% EVAL_DEBUG_SPLIT_EMPTY Popping field %1, delimiters %2, string %3, pushing result %4
+This debug message indicates that the string popped from the stack was empty
+and so the result will also be empty. The field, delimiter and string are
+still popped from the stack and the result is still pushed.
+
+% EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field %1, delimiters %2, string %3, pushing result %4
+This debug message indicates that the field is either less than one or larger
+than the number of fields in the string popped from the stack. The result will
+be empty. The field, delimiter and string are still popped from the stack and
+the result is still pushed.
+
+% EVAL_DEBUG_STRING Pushing text string %1
+This debug message indicates that the given text string is being pushed
+onto the value stack. The string is displayed in text.
+
+# For use with TokenSubstring
+# Start with binary for the strings for now, we may add text in the future.
+
+% EVAL_DEBUG_SUBSTRING Popping length %1, start %2, string %3 pushing result %4
+This debug message indicates that three values are being popped from
+the value stack and a result is being pushed onto the value stack. The
+values being popped are the starting point and length of a substring to
+extract from the given string. The resulting string is pushed onto
+the stack. The strings are displayed in hex.
+
+% EVAL_DEBUG_SUBSTRING_EMPTY Popping length %1, start %2, string %3 pushing result %4
+This debug message indicates that the string popped from the stack was empty
+and so the result will also be empty. The start, length and string are
+still popped from the stack and the result is still pushed.
+
+% EVAL_DEBUG_SUBSTRING_RANGE Popping length %1, start %2, string %3 pushing result %4
+This debug message indicates that the value of start is outside of the
+string and an empty result will be pushed onto the stack. The start,
+length and string are still popped from the stack and the result is
+still pushed. The strings are displayed in hex.
+
+# For use with TokenSubOption
+
+% EVAL_DEBUG_SUB_OPTION Pushing option %1 sub-option %2 with value %3
+This debug message indicates that the given string representing the
+value of the requested sub-option of the requested parent option is
+being pushed onto the value stack. The string may be the text or
+binary value of the string based on the representation type requested
+(.text or .hex) or "true" or "false" if the requested type is .exists.
+The codes are the parent option and the sub-option codes as requested
+in the classification statement.
+
+% EVAL_DEBUG_SUB_OPTION_NO_OPTION Requested option %1 sub-option %2, but the parent option is not present, pushing result %3
+This debug message indicates that the parent option was not found.
+The codes are the parent option and the sub-option codes as requested
+in the classification statement.
+
+# For use with TokenToHexString
+
+% EVAL_DEBUG_TOHEXSTRING Popping binary value %1 and separator %2, pushing result %3
+This debug message indicates that two values are being popped from
+the value stack and a result is being pushed onto the value stack.
+The values being popped are the binary value to convert and the separator.
+The binary value is converted to its hexadecimal string representation
+and pushed onto the stack. The binary value is displayed in hex.
+
+% EVAL_DEBUG_UINT16TOTEXT Pushing UInt16 %1
+This debug message indicates that the given address string representation is
+being pushed onto the value stack. This represents a 16 bit unsigned integer.
+
+# For use with TokenUInt32ToText
+
+% EVAL_DEBUG_UINT32TOTEXT Pushing UInt32 %1
+This debug message indicates that the given address string representation is
+being pushed onto the value stack. This represents a 32 bit unsigned integer.
+
+# For use with TokenMember
+
+% EVAL_DEBUG_UINT8TOTEXT Pushing UInt8 %1
+This debug message indicates that the given address string representation is
+being pushed onto the value stack. This represents an 8 bit unsigned integer.
+
+# For use with TokenUInt16ToText
+
+% EVAL_DEBUG_VENDOR_CLASS_DATA Data %1 (out of %2 received) in vendor class found, pushing result '%3'
+This debug message indicates that vendor class option was found and passed
+enterprise-id checks and has sufficient number of data chunks. The total number
+of chunks and value pushed are reported as debugging aid.
+
+% EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index %1, but option with enterprise-id %2 has only %3 data tuple(s), pushing result '%4'
+This debug message indicates that vendor class option was found and passed
+enterprise-id checks, but does not have sufficient number of data chunks.
+Note that the index starts at 0, so there has to be at least (index + 1)
+data chunks.
+
+% EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id %1 as result 0x%2
+This debug message indicates that the expression has been evaluated and vendor
+class option was found and its enterprise-id is being reported.
+
+% EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for %1, option had %2, pushing result '%3'
+This debug message indicates that the expression has been evaluated
+and vendor class option was found, but has different enterprise-id than specified
+in the expression.
+
+% EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id %1 found, pushing result '%2'
+This debug message indicates that the expression has been evaluated and vendor
+class option was found.
+
+% EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code %1 missing, pushing result '%2'
+This debug message indicates that the expression has been evaluated
+and vendor class option was not found.
+
+% EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id %1 as result 0x%2
+This debug message indicates that the expression has been evaluated and vendor
+option was found and its enterprise-id is being reported.
+
+% EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for %1, option had %2, pushing result '%3'
+This debug message indicates that the expression has been evaluated
+and vendor option was found, but has different enterprise-id than specified
+in the expression.
+
+% EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id %1 found, pushing result '%2'
+This debug message indicates that the expression has been evaluated and vendor
+option was found.
+
+% EVAL_DEBUG_VENDOR_NO_OPTION Option with code %1 missing, pushing result '%2'
+This debug message indicates that the expression has been evaluated
+and vendor option was not found.
+
+% EVAL_RESULT Expression %1 evaluated to %2
+This debug message indicates that the expression has been evaluated
+to said value. This message is mostly useful during debugging of the
+client classification expressions.
diff --git a/src/lib/eval/evaluate.cc b/src/lib/eval/evaluate.cc
new file mode 100644
index 0000000..f2cadce
--- /dev/null
+++ b/src/lib/eval/evaluate.cc
@@ -0,0 +1,42 @@
+// Copyright (C) 2015-2017 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 <eval/evaluate.h>
+
+namespace isc {
+namespace dhcp {
+
+bool evaluateBool(const Expression& expr, Pkt& pkt) {
+ ValueStack values;
+ for (Expression::const_iterator it = expr.begin();
+ it != expr.end(); ++it) {
+ (*it)->evaluate(pkt, values);
+ }
+ if (values.size() != 1) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly "
+ "1 value at the end of evaluation, got " << values.size());
+ }
+ return (Token::toBool(values.top()));
+}
+
+std::string
+evaluateString(const Expression& expr, Pkt& pkt) {
+ ValueStack values;
+ for (auto it = expr.begin(); it != expr.end(); ++it) {
+ (*it)->evaluate(pkt, values);
+ }
+ if (values.size() != 1) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly "
+ "1 value at the end of evaluation, got " << values.size());
+ }
+ return (values.top());
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/eval/evaluate.h b/src/lib/eval/evaluate.h
new file mode 100644
index 0000000..b2f6eb2
--- /dev/null
+++ b/src/lib/eval/evaluate.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2015-2017 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 EVALUATE_H
+#define EVALUATE_H
+
+#include <eval/token.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Evaluate a RPN expression for a v4 or v6 packet and return
+/// a true or false decision
+///
+/// @param expr the RPN expression, i.e., a vector of parsed tokens
+/// @param pkt The v4 or v6 packet
+/// @return the boolean decision
+/// @throw EvalStackError if there is not exactly one element on the value
+/// stack at the end of the evaluation
+/// @throw EvalTypeError if the value at the top of the stack at the
+/// end of the evaluation is not "false" or "true"
+bool evaluateBool(const Expression& expr, Pkt& pkt);
+
+
+std::string evaluateString(const Expression& expr, Pkt& pkt);
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/eval/lexer.cc b/src/lib/eval/lexer.cc
new file mode 100644
index 0000000..5855ede
--- /dev/null
+++ b/src/lib/eval/lexer.cc
@@ -0,0 +1,2996 @@
+#line 1 "lexer.cc"
+
+#line 3 "lexer.cc"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+/* %not-for-header */
+/* %if-c-only */
+/* %if-not-reentrant */
+#define yy_create_buffer eval_create_buffer
+#define yy_delete_buffer eval_delete_buffer
+#define yy_scan_buffer eval_scan_buffer
+#define yy_scan_string eval_scan_string
+#define yy_scan_bytes eval_scan_bytes
+#define yy_init_buffer eval_init_buffer
+#define yy_flush_buffer eval_flush_buffer
+#define yy_load_buffer_state eval_load_buffer_state
+#define yy_switch_to_buffer eval_switch_to_buffer
+#define yypush_buffer_state evalpush_buffer_state
+#define yypop_buffer_state evalpop_buffer_state
+#define yyensure_buffer_stack evalensure_buffer_stack
+#define yy_flex_debug eval_flex_debug
+#define yyin evalin
+#define yyleng evalleng
+#define yylex evallex
+#define yylineno evallineno
+#define yyout evalout
+#define yyrestart evalrestart
+#define yytext evaltext
+#define yywrap evalwrap
+#define yyalloc evalalloc
+#define yyrealloc evalrealloc
+#define yyfree evalfree
+
+/* %endif */
+/* %endif */
+/* %ok-for-header */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 6
+#define YY_FLEX_SUBMINOR_VERSION 4
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* %if-c++-only */
+/* %endif */
+
+/* %if-c-only */
+#ifdef yy_create_buffer
+#define eval_create_buffer_ALREADY_DEFINED
+#else
+#define yy_create_buffer eval_create_buffer
+#endif
+
+#ifdef yy_delete_buffer
+#define eval_delete_buffer_ALREADY_DEFINED
+#else
+#define yy_delete_buffer eval_delete_buffer
+#endif
+
+#ifdef yy_scan_buffer
+#define eval_scan_buffer_ALREADY_DEFINED
+#else
+#define yy_scan_buffer eval_scan_buffer
+#endif
+
+#ifdef yy_scan_string
+#define eval_scan_string_ALREADY_DEFINED
+#else
+#define yy_scan_string eval_scan_string
+#endif
+
+#ifdef yy_scan_bytes
+#define eval_scan_bytes_ALREADY_DEFINED
+#else
+#define yy_scan_bytes eval_scan_bytes
+#endif
+
+#ifdef yy_init_buffer
+#define eval_init_buffer_ALREADY_DEFINED
+#else
+#define yy_init_buffer eval_init_buffer
+#endif
+
+#ifdef yy_flush_buffer
+#define eval_flush_buffer_ALREADY_DEFINED
+#else
+#define yy_flush_buffer eval_flush_buffer
+#endif
+
+#ifdef yy_load_buffer_state
+#define eval_load_buffer_state_ALREADY_DEFINED
+#else
+#define yy_load_buffer_state eval_load_buffer_state
+#endif
+
+#ifdef yy_switch_to_buffer
+#define eval_switch_to_buffer_ALREADY_DEFINED
+#else
+#define yy_switch_to_buffer eval_switch_to_buffer
+#endif
+
+#ifdef yypush_buffer_state
+#define evalpush_buffer_state_ALREADY_DEFINED
+#else
+#define yypush_buffer_state evalpush_buffer_state
+#endif
+
+#ifdef yypop_buffer_state
+#define evalpop_buffer_state_ALREADY_DEFINED
+#else
+#define yypop_buffer_state evalpop_buffer_state
+#endif
+
+#ifdef yyensure_buffer_stack
+#define evalensure_buffer_stack_ALREADY_DEFINED
+#else
+#define yyensure_buffer_stack evalensure_buffer_stack
+#endif
+
+#ifdef yylex
+#define evallex_ALREADY_DEFINED
+#else
+#define yylex evallex
+#endif
+
+#ifdef yyrestart
+#define evalrestart_ALREADY_DEFINED
+#else
+#define yyrestart evalrestart
+#endif
+
+#ifdef yylex_init
+#define evallex_init_ALREADY_DEFINED
+#else
+#define yylex_init evallex_init
+#endif
+
+#ifdef yylex_init_extra
+#define evallex_init_extra_ALREADY_DEFINED
+#else
+#define yylex_init_extra evallex_init_extra
+#endif
+
+#ifdef yylex_destroy
+#define evallex_destroy_ALREADY_DEFINED
+#else
+#define yylex_destroy evallex_destroy
+#endif
+
+#ifdef yyget_debug
+#define evalget_debug_ALREADY_DEFINED
+#else
+#define yyget_debug evalget_debug
+#endif
+
+#ifdef yyset_debug
+#define evalset_debug_ALREADY_DEFINED
+#else
+#define yyset_debug evalset_debug
+#endif
+
+#ifdef yyget_extra
+#define evalget_extra_ALREADY_DEFINED
+#else
+#define yyget_extra evalget_extra
+#endif
+
+#ifdef yyset_extra
+#define evalset_extra_ALREADY_DEFINED
+#else
+#define yyset_extra evalset_extra
+#endif
+
+#ifdef yyget_in
+#define evalget_in_ALREADY_DEFINED
+#else
+#define yyget_in evalget_in
+#endif
+
+#ifdef yyset_in
+#define evalset_in_ALREADY_DEFINED
+#else
+#define yyset_in evalset_in
+#endif
+
+#ifdef yyget_out
+#define evalget_out_ALREADY_DEFINED
+#else
+#define yyget_out evalget_out
+#endif
+
+#ifdef yyset_out
+#define evalset_out_ALREADY_DEFINED
+#else
+#define yyset_out evalset_out
+#endif
+
+#ifdef yyget_leng
+#define evalget_leng_ALREADY_DEFINED
+#else
+#define yyget_leng evalget_leng
+#endif
+
+#ifdef yyget_text
+#define evalget_text_ALREADY_DEFINED
+#else
+#define yyget_text evalget_text
+#endif
+
+#ifdef yyget_lineno
+#define evalget_lineno_ALREADY_DEFINED
+#else
+#define yyget_lineno evalget_lineno
+#endif
+
+#ifdef yyset_lineno
+#define evalset_lineno_ALREADY_DEFINED
+#else
+#define yyset_lineno evalset_lineno
+#endif
+
+#ifdef yywrap
+#define evalwrap_ALREADY_DEFINED
+#else
+#define yywrap evalwrap
+#endif
+
+/* %endif */
+
+#ifdef yyalloc
+#define evalalloc_ALREADY_DEFINED
+#else
+#define yyalloc evalalloc
+#endif
+
+#ifdef yyrealloc
+#define evalrealloc_ALREADY_DEFINED
+#else
+#define yyrealloc evalrealloc
+#endif
+
+#ifdef yyfree
+#define evalfree_ALREADY_DEFINED
+#else
+#define yyfree evalfree
+#endif
+
+/* %if-c-only */
+
+#ifdef yytext
+#define evaltext_ALREADY_DEFINED
+#else
+#define yytext evaltext
+#endif
+
+#ifdef yyleng
+#define evalleng_ALREADY_DEFINED
+#else
+#define yyleng evalleng
+#endif
+
+#ifdef yyin
+#define evalin_ALREADY_DEFINED
+#else
+#define yyin evalin
+#endif
+
+#ifdef yyout
+#define evalout_ALREADY_DEFINED
+#else
+#define yyout evalout
+#endif
+
+#ifdef yy_flex_debug
+#define eval_flex_debug_ALREADY_DEFINED
+#else
+#define yy_flex_debug eval_flex_debug
+#endif
+
+#ifdef yylineno
+#define evallineno_ALREADY_DEFINED
+#else
+#define yylineno evallineno
+#endif
+
+/* %endif */
+
+/* First, we deal with platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+/* %if-c-only */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+/* %endif */
+
+/* %if-tables-serialization */
+/* %endif */
+/* end standard C headers. */
+
+/* %if-c-or-c++ */
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX (~(size_t)0)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+/* %endif */
+
+/* begin standard C++ headers. */
+/* %if-c++-only */
+/* %endif */
+
+/* TODO: this is always defined, so inline it */
+#define yyconst const
+
+#if defined(__GNUC__) && __GNUC__ >= 3
+#define yynoreturn __attribute__((__noreturn__))
+#else
+#define yynoreturn
+#endif
+
+/* %not-for-header */
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+/* %ok-for-header */
+
+/* %not-for-header */
+/* Promotes a possibly negative, possibly signed char to an
+ * integer in range [0..255] for use as an array index.
+ */
+#define YY_SC_TO_UI(c) ((YY_CHAR) (c))
+/* %ok-for-header */
+
+/* %if-reentrant */
+/* %endif */
+
+/* %if-not-reentrant */
+
+/* %endif */
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin )
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
+#define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+/* %if-not-reentrant */
+extern int yyleng;
+/* %endif */
+
+/* %if-c-only */
+/* %if-not-reentrant */
+extern FILE *yyin, *yyout;
+/* %endif */
+/* %endif */
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+ /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires
+ * access to the local variable yy_act. Since yyless() is a macro, it would break
+ * existing scanners that call yyless() from OUTSIDE yylex.
+ * One obvious solution it to make yy_act a global. I tried that, and saw
+ * a 5% performance hit in a non-yylineno scanner, because yy_act is
+ * normally declared as a register variable-- so it is not worth it.
+ */
+ #define YY_LESS_LINENO(n) \
+ do { \
+ int yyl;\
+ for ( yyl = n; yyl < yyleng; ++yyl )\
+ if ( yytext[yyl] == '\n' )\
+ --yylineno;\
+ }while(0)
+ #define YY_LINENO_REWIND_TO(dst) \
+ do {\
+ const char *p;\
+ for ( p = yy_cp-1; p >= (dst); --p)\
+ if ( *p == '\n' )\
+ --yylineno;\
+ }while(0)
+
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ *yy_cp = (yy_hold_char); \
+ YY_RESTORE_YY_MORE_OFFSET \
+ (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+#define unput(c) yyunput( c, (yytext_ptr) )
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+ {
+/* %if-c-only */
+ FILE *yy_input_file;
+/* %endif */
+
+/* %if-c++-only */
+/* %endif */
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ int yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ int yy_bs_lineno; /**< The line count. */
+ int yy_bs_column; /**< The column count. */
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+
+ };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* %if-c-only Standard (non-C++) definition */
+/* %not-for-header */
+/* %if-not-reentrant */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */
+/* %endif */
+/* %ok-for-header */
+
+/* %endif */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+ : NULL)
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* %if-c-only Standard (non-C++) definition */
+
+/* %if-not-reentrant */
+/* %not-for-header */
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = NULL;
+static int yy_init = 0; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+/* %ok-for-header */
+
+/* %endif */
+
+void yyrestart ( FILE *input_file );
+void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer );
+YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size );
+void yy_delete_buffer ( YY_BUFFER_STATE b );
+void yy_flush_buffer ( YY_BUFFER_STATE b );
+void yypush_buffer_state ( YY_BUFFER_STATE new_buffer );
+void yypop_buffer_state ( void );
+
+static void yyensure_buffer_stack ( void );
+static void yy_load_buffer_state ( void );
+static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file );
+#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size );
+YY_BUFFER_STATE yy_scan_string ( const char *yy_str );
+YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len );
+
+/* %endif */
+
+void *yyalloc ( yy_size_t );
+void *yyrealloc ( void *, yy_size_t );
+void yyfree ( void * );
+
+#define yy_new_buffer yy_create_buffer
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){ \
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+ }
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){\
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+ }
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* %% [1.0] yytext/yyin/yyout/yy_state_type/yylineno etc. def's & init go here */
+/* Begin user sect3 */
+
+#define evalwrap() (/*CONSTCOND*/1)
+#define YY_SKIP_YYWRAP
+
+#define FLEX_DEBUG
+typedef flex_uint8_t YY_CHAR;
+
+FILE *yyin = NULL, *yyout = NULL;
+
+typedef int yy_state_type;
+
+extern int yylineno;
+int yylineno = 1;
+
+extern char *yytext;
+#ifdef yytext_ptr
+#undef yytext_ptr
+#endif
+#define yytext_ptr yytext
+
+/* %% [1.5] DFA */
+
+/* %if-c-only Standard (non-C++) definition */
+
+static yy_state_type yy_get_previous_state ( void );
+static yy_state_type yy_try_NUL_trans ( yy_state_type current_state );
+static int yy_get_next_buffer ( void );
+static void yynoreturn yy_fatal_error ( const char* msg );
+
+/* %endif */
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ (yytext_ptr) = yy_bp; \
+/* %% [2.0] code to fiddle yytext and yyleng for yymore() goes here \ */\
+ yyleng = (int) (yy_cp - yy_bp); \
+ (yy_hold_char) = *yy_cp; \
+ *yy_cp = '\0'; \
+/* %% [3.0] code to copy yytext_ptr to yytext[] goes here, if %array \ */\
+ (yy_c_buf_p) = yy_cp;
+/* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */
+#define YY_NUM_RULES 63
+#define YY_END_OF_BUFFER 64
+/* This struct is not used in this scanner,
+ but its presence is necessary. */
+struct yy_trans_info
+ {
+ flex_int32_t yy_verify;
+ flex_int32_t yy_nxt;
+ };
+static const flex_int16_t yy_accept[279] =
+ { 0,
+ 0, 0, 64, 62, 1, 2, 62, 55, 56, 60,
+ 61, 59, 62, 54, 5, 5, 62, 62, 62, 62,
+ 57, 58, 62, 62, 62, 62, 62, 62, 62, 62,
+ 62, 62, 62, 62, 62, 62, 62, 62, 62, 62,
+ 1, 2, 0, 3, 5, 0, 5, 0, 0, 0,
+ 0, 7, 8, 0, 0, 0, 0, 6, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 52, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 7, 0, 39, 51, 0,
+
+ 0, 0, 20, 0, 0, 0, 15, 0, 0, 0,
+ 0, 0, 21, 0, 23, 0, 0, 50, 0, 0,
+ 17, 0, 0, 0, 19, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 35, 0, 0, 0, 0,
+ 24, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 22, 30, 0, 0, 0, 0, 14, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 25, 18, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 38, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 26, 40, 0, 16, 27, 0,
+
+ 41, 0, 0, 0, 0, 53, 0, 9, 0, 10,
+ 11, 29, 0, 0, 0, 0, 0, 33, 28, 7,
+ 0, 0, 0, 0, 0, 0, 0, 31, 0, 0,
+ 32, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 13, 12, 0, 0, 0, 0, 0, 0, 0,
+ 42, 0, 0, 0, 37, 0, 0, 0, 0, 43,
+ 36, 0, 0, 44, 0, 0, 0, 0, 45, 46,
+ 0, 0, 47, 0, 48, 49, 34, 0
+ } ;
+
+static const YY_CHAR yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 1, 1, 1, 1, 1, 1, 4, 5,
+ 6, 7, 8, 9, 10, 11, 1, 12, 13, 14,
+ 15, 16, 17, 18, 17, 19, 17, 20, 1, 1,
+ 21, 1, 1, 1, 22, 22, 22, 22, 22, 22,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 24, 23, 23,
+ 25, 1, 26, 1, 27, 1, 28, 29, 30, 31,
+
+ 32, 33, 34, 35, 36, 23, 37, 38, 39, 40,
+ 41, 42, 23, 43, 44, 45, 46, 47, 23, 48,
+ 49, 23, 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,
+ 1, 1, 1, 1, 1
+ } ;
+
+static const YY_CHAR yy_meta[50] =
+ { 0,
+ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1,
+ 3, 4, 4, 4, 4, 4, 4, 4, 4, 5,
+ 1, 6, 1, 1, 1, 1, 1, 6, 6, 6,
+ 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1
+ } ;
+
+static const flex_int16_t yy_base[288] =
+ { 0,
+ 0, 0, 383, 384, 380, 378, 376, 384, 384, 384,
+ 384, 384, 0, 384, 39, 36, 359, 357, 86, 124,
+ 384, 384, 35, 38, 34, 37, 341, 48, 43, 58,
+ 63, 335, 22, 60, 343, 122, 116, 338, 341, 336,
+ 369, 367, 365, 384, 0, 0, 121, 348, 347, 0,
+ 346, 0, 384, 147, 158, 0, 0, 384, 334, 326,
+ 332, 334, 321, 315, 314, 313, 321, 328, 307, 322,
+ 304, 119, 307, 311, 310, 319, 309, 313, 301, 300,
+ 0, 312, 298, 304, 313, 302, 309, 309, 289, 308,
+ 295, 294, 305, 321, 0, 0, 288, 0, 0, 299,
+
+ 299, 300, 0, 295, 282, 294, 280, 283, 280, 291,
+ 282, 157, 0, 282, 0, 289, 272, 0, 280, 272,
+ 159, 286, 282, 276, 0, 267, 265, 269, 263, 276,
+ 275, 0, 260, 273, 275, 0, 259, 256, 269, 254,
+ 0, 266, 265, 252, 277, 280, 248, 264, 259, 241,
+ 248, 260, 0, 0, 238, 255, 240, 239, 0, 239,
+ 166, 241, 250, 269, 238, 235, 232, 234, 231, 231,
+ 230, 0, 0, 240, 226, 225, 228, 237, 224, 224,
+ 225, 233, 162, 220, 0, 219, 225, 242, 245, 213,
+ 214, 213, 0, 210, 0, 0, 211, 0, 0, 217,
+
+ 0, 211, 210, 205, 218, 0, 216, 0, 216, 0,
+ 0, 0, 210, 214, 199, 198, 201, 231, 0, 0,
+ 208, 203, 198, 192, 191, 203, 191, 0, 190, 192,
+ 0, 190, 189, 184, 160, 180, 183, 192, 193, 192,
+ 175, 0, 0, 188, 176, 175, 187, 180, 172, 184,
+ 0, 167, 166, 168, 0, 180, 179, 162, 176, 0,
+ 0, 147, 144, 0, 140, 139, 138, 138, 0, 0,
+ 129, 126, 0, 123, 0, 0, 0, 384, 190, 162,
+ 193, 107, 196, 199, 203, 78, 77
+ } ;
+
+static const flex_int16_t yy_def[288] =
+ { 0,
+ 278, 1, 278, 278, 278, 278, 279, 278, 278, 278,
+ 278, 278, 280, 278, 278, 15, 281, 278, 278, 19,
+ 278, 278, 19, 19, 19, 19, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 278, 278, 279, 278, 280, 282, 15, 281, 283, 284,
+ 281, 285, 278, 278, 20, 19, 20, 278, 19, 20,
+ 20, 20, 20, 19, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 282, 284, 285, 19, 20, 20, 20,
+
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 286, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 286, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 287, 20, 20, 20, 20, 20, 20, 20,
+
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 287,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 0, 278, 278,
+ 278, 278, 278, 278, 278, 278, 278
+ } ;
+
+static const flex_int16_t yy_nxt[434] =
+ { 0,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 16, 16, 16, 16, 16, 16, 17,
+ 18, 19, 20, 20, 21, 22, 4, 23, 19, 24,
+ 25, 26, 19, 27, 28, 29, 20, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 20, 40, 46,
+ 47, 47, 47, 47, 47, 47, 47, 47, 48, 278,
+ 49, 64, 50, 80, 81, 59, 49, 49, 49, 49,
+ 49, 49, 60, 62, 61, 72, 66, 65, 63, 69,
+ 220, 164, 73, 278, 67, 70, 50, 54, 54, 74,
+ 76, 82, 71, 75, 77, 55, 83, 56, 56, 56,
+
+ 56, 56, 56, 56, 56, 48, 78, 56, 57, 57,
+ 94, 58, 55, 56, 56, 56, 56, 56, 56, 57,
+ 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+ 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
+ 57, 57, 57, 278, 278, 57, 110, 89, 54, 54,
+ 111, 57, 57, 57, 57, 57, 57, 85, 90, 278,
+ 278, 278, 278, 86, 87, 45, 277, 88, 278, 145,
+ 276, 146, 58, 275, 153, 147, 154, 210, 188, 211,
+ 189, 274, 273, 278, 190, 278, 272, 271, 270, 248,
+ 43, 269, 43, 43, 43, 43, 51, 51, 51, 49,
+
+ 49, 49, 95, 268, 95, 96, 96, 96, 96, 267,
+ 266, 265, 264, 263, 262, 261, 260, 259, 258, 257,
+ 256, 255, 254, 253, 252, 251, 250, 249, 247, 246,
+ 245, 244, 243, 242, 241, 240, 239, 238, 237, 236,
+ 235, 234, 233, 232, 231, 230, 229, 228, 227, 226,
+ 225, 224, 223, 222, 221, 219, 218, 217, 216, 215,
+ 214, 213, 212, 209, 208, 207, 206, 205, 204, 203,
+ 202, 201, 200, 199, 198, 197, 196, 195, 194, 193,
+ 192, 191, 187, 186, 185, 184, 183, 182, 181, 180,
+ 179, 178, 177, 176, 175, 174, 173, 172, 171, 170,
+
+ 169, 168, 167, 166, 165, 163, 162, 161, 160, 159,
+ 158, 157, 156, 155, 152, 151, 150, 149, 148, 144,
+ 143, 142, 141, 140, 139, 138, 137, 136, 135, 134,
+ 133, 132, 131, 130, 129, 128, 127, 126, 125, 124,
+ 123, 122, 121, 120, 119, 118, 117, 116, 115, 114,
+ 113, 112, 109, 108, 107, 106, 105, 104, 103, 102,
+ 101, 100, 99, 98, 97, 52, 48, 52, 44, 42,
+ 41, 93, 92, 91, 84, 79, 68, 53, 52, 44,
+ 42, 41, 278, 3, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278
+ } ;
+
+static const flex_int16_t yy_chk[434] =
+ { 0,
+ 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, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 16,
+ 15, 25, 15, 33, 33, 23, 15, 15, 15, 15,
+ 15, 15, 23, 24, 23, 29, 26, 25, 24, 28,
+ 287, 286, 29, 16, 26, 28, 15, 19, 19, 30,
+ 31, 34, 28, 30, 31, 19, 34, 19, 19, 19,
+
+ 19, 19, 19, 19, 19, 19, 31, 19, 19, 19,
+ 282, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 47, 20, 72, 37, 54, 54,
+ 72, 20, 20, 20, 20, 20, 20, 36, 37, 55,
+ 55, 235, 235, 36, 36, 280, 274, 36, 47, 112,
+ 272, 112, 54, 271, 121, 112, 121, 183, 161, 183,
+ 161, 268, 267, 55, 161, 235, 266, 265, 263, 235,
+ 279, 262, 279, 279, 279, 279, 281, 281, 281, 283,
+
+ 283, 283, 284, 259, 284, 285, 285, 285, 285, 258,
+ 257, 256, 254, 253, 252, 250, 249, 248, 247, 246,
+ 245, 244, 241, 240, 239, 238, 237, 236, 234, 233,
+ 232, 230, 229, 227, 226, 225, 224, 223, 222, 221,
+ 218, 217, 216, 215, 214, 213, 209, 207, 205, 204,
+ 203, 202, 200, 197, 194, 192, 191, 190, 189, 188,
+ 187, 186, 184, 182, 181, 180, 179, 178, 177, 176,
+ 175, 174, 171, 170, 169, 168, 167, 166, 165, 164,
+ 163, 162, 160, 158, 157, 156, 155, 152, 151, 150,
+ 149, 148, 147, 146, 145, 144, 143, 142, 140, 139,
+
+ 138, 137, 135, 134, 133, 131, 130, 129, 128, 127,
+ 126, 124, 123, 122, 120, 119, 117, 116, 114, 111,
+ 110, 109, 108, 107, 106, 105, 104, 102, 101, 100,
+ 97, 94, 93, 92, 91, 90, 89, 88, 87, 86,
+ 85, 84, 83, 82, 80, 79, 78, 77, 76, 75,
+ 74, 73, 71, 70, 69, 68, 67, 66, 65, 64,
+ 63, 62, 61, 60, 59, 51, 49, 48, 43, 42,
+ 41, 40, 39, 38, 35, 32, 27, 18, 17, 7,
+ 6, 5, 3, 278, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278, 278, 278, 278, 278, 278, 278, 278,
+ 278, 278, 278
+ } ;
+
+/* Table of booleans, true if rule could match eol. */
+static const flex_int32_t yy_rule_can_match_eol[64] =
+ { 0,
+0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, };
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int yy_flex_debug;
+int yy_flex_debug = 1;
+
+static const flex_int16_t yy_rule_linenum[63] =
+ { 0,
+ 106, 111, 117, 127, 133, 151, 175, 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
+ } ;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "lexer.ll"
+/* Copyright (C) 2015-2022 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/. */
+#line 8 "lexer.ll"
+
+/* Generated files do not make clang static analyser so happy */
+#ifndef __clang_analyzer__
+
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <eval/eval_context.h>
+#include <eval/parser.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
+
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
+# undef yywrap
+# define yywrap() 1
+
+/* The location of the current token. The lexer will keep updating it. This
+ variable will be useful for logging errors. */
+static isc::eval::location loc;
+
+namespace {
+ bool start_token_flag = false;
+ isc::eval::EvalContext::ParserType start_token_value;
+};
+
+/* To avoid the call to exit... oops! */
+#define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg)
+#line 1044 "lexer.cc"
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+ always parse only a single string, there's no need to do any wraps. And
+ using yywrap requires linking with -lfl, which provides the default yywrap
+ implementation that always returns 1 anyway. */
+/* nounput simplifies the lexer, by removing support for putting a character
+ back into the input stream. We never use such capability anyway. */
+/* batch means that we'll never use the generated lexer interactively. */
+/* Enables debug mode. To see the debug messages, one needs to also set
+ eval_flex_debug to 1, then the debug messages will be printed on stderr. */
+/* I have no idea what this option does, except it was specified in the bison
+ examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+ be on the safe side and keep it. */
+#define YY_NO_INPUT 1
+/* This line tells flex to track the line numbers. It's not really that
+ useful for client classes, which typically are one-liners, but it may be
+ useful in more complex cases. */
+/* These are not token expressions yet, just convenience expressions that
+ can be used during actual token definitions. Note some can match
+ incorrect inputs (e.g., IP addresses) which must be checked. */
+#line 80 "lexer.ll"
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
+#define YY_USER_ACTION loc.columns(evalleng);
+#line 1069 "lexer.cc"
+#line 1070 "lexer.cc"
+
+#define INITIAL 0
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+/* %if-c-only */
+#include <unistd.h>
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* %if-c-only Reentrant structure and macros (non-C++). */
+/* %if-reentrant */
+/* %if-c-only */
+
+static int yy_init_globals ( void );
+
+/* %endif */
+/* %if-reentrant */
+/* %endif */
+/* %endif End reentrant structures and macros. */
+
+/* Accessor methods to globals.
+ These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy ( void );
+
+int yyget_debug ( void );
+
+void yyset_debug ( int debug_flag );
+
+YY_EXTRA_TYPE yyget_extra ( void );
+
+void yyset_extra ( YY_EXTRA_TYPE user_defined );
+
+FILE *yyget_in ( void );
+
+void yyset_in ( FILE * _in_str );
+
+FILE *yyget_out ( void );
+
+void yyset_out ( FILE * _out_str );
+
+ int yyget_leng ( void );
+
+char *yyget_text ( void );
+
+int yyget_lineno ( void );
+
+void yyset_lineno ( int _line_number );
+
+/* %if-bison-bridge */
+/* %endif */
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap ( void );
+#else
+extern int yywrap ( void );
+#endif
+#endif
+
+/* %not-for-header */
+#ifndef YY_NO_UNPUT
+
+#endif
+/* %ok-for-header */
+
+/* %endif */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy ( char *, const char *, int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen ( const char * );
+#endif
+
+#ifndef YY_NO_INPUT
+/* %if-c-only Standard (non-C++) definition */
+/* %not-for-header */
+#ifdef __cplusplus
+static int yyinput ( void );
+#else
+static int input ( void );
+#endif
+/* %ok-for-header */
+
+/* %endif */
+#endif
+
+/* %if-c-only */
+
+/* %endif */
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
+#define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* %if-c-only Standard (non-C++) definition */
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0)
+/* %endif */
+/* %if-c++-only C++ definition */
+/* %endif */
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+/* %% [5.0] fread()/read() definition of YY_INPUT goes here unless we're doing C++ \ */\
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+ { \
+ int c = '*'; \
+ int n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }\
+\
+/* %if-c++-only C++ definition \ */\
+/* %endif */
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+/* %if-c-only */
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+#endif
+
+/* %if-tables-serialization structures and prototypes */
+/* %not-for-header */
+/* %ok-for-header */
+
+/* %not-for-header */
+/* %tables-yydmap generated elements */
+/* %endif */
+/* end tables serialization structures and prototypes */
+
+/* %ok-for-header */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+/* %if-c-only Standard (non-C++) definition */
+
+extern int yylex (void);
+
+#define YY_DECL int yylex (void)
+/* %endif */
+/* %if-c++-only C++ definition */
+/* %endif */
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK /*LINTED*/break;
+#endif
+
+/* %% [6.0] YY_RULE_SETUP definition goes here */
+#define YY_RULE_SETUP \
+ YY_USER_ACTION
+
+/* %not-for-header */
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+ yy_state_type yy_current_state;
+ char *yy_cp, *yy_bp;
+ int yy_act;
+
+ if ( !(yy_init) )
+ {
+ (yy_init) = 1;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! (yy_start) )
+ (yy_start) = 1; /* first start state */
+
+ if ( ! yyin )
+/* %if-c-only */
+ yyin = stdin;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+ if ( ! yyout )
+/* %if-c-only */
+ yyout = stdout;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+ if ( ! YY_CURRENT_BUFFER ) {
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_load_buffer_state( );
+ }
+
+ {
+/* %% [7.0] user's declarations go here */
+#line 86 "lexer.ll"
+
+
+
+#line 90 "lexer.ll"
+ /* Code run each time evallex is called. */
+ loc.step();
+
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case EvalContext::PARSER_BOOL:
+ return isc::eval::EvalParser::make_TOPLEVEL_BOOL(loc);
+ default:
+ case EvalContext::PARSER_STRING:
+ return isc::eval::EvalParser::make_TOPLEVEL_STRING(loc);
+ }
+ }
+
+
+
+#line 1369 "lexer.cc"
+
+ while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
+ {
+/* %% [8.0] yymore()-related code goes here */
+ yy_cp = (yy_c_buf_p);
+
+ /* Support of yytext. */
+ *yy_cp = (yy_hold_char);
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+/* %% [9.0] code to set up and find next match goes here */
+ yy_current_state = (yy_start);
+yy_match:
+ do
+ {
+ YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 279 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ ++yy_cp;
+ }
+ while ( yy_current_state != 278 );
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+
+yy_find_action:
+/* %% [10.0] code to find the action number goes here */
+ yy_act = yy_accept[yy_current_state];
+
+ YY_DO_BEFORE_ACTION;
+
+/* %% [11.0] code for yylineno update goes here */
+
+ if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] )
+ {
+ int yyl;
+ for ( yyl = 0; yyl < yyleng; ++yyl )
+ if ( yytext[yyl] == '\n' )
+
+ yylineno++;
+;
+ }
+
+do_action: /* This label is used only to access EOF actions. */
+
+/* %% [12.0] debug code goes here */
+ if ( yy_flex_debug )
+ {
+ if ( yy_act == 0 )
+ fprintf( stderr, "--scanner backing up\n" );
+ else if ( yy_act < 63 )
+ fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n",
+ (long)yy_rule_linenum[yy_act], yytext );
+ else if ( yy_act == 63 )
+ fprintf( stderr, "--accepting default rule (\"%s\")\n",
+ yytext );
+ else if ( yy_act == 64 )
+ fprintf( stderr, "--(end of buffer or a NUL)\n" );
+ else
+ fprintf( stderr, "--EOF (start condition %d)\n", YY_START );
+ }
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+/* %% [13.0] actions go here */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = (yy_hold_char);
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 106 "lexer.ll"
+{
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
+ loc.step();
+}
+ YY_BREAK
+case 2:
+/* rule 2 can match eol */
+YY_RULE_SETUP
+#line 111 "lexer.ll"
+{
+ /* Newline found. Let's update the location and continue. */
+ loc.lines(evalleng);
+ loc.step();
+}
+ YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 117 "lexer.ll"
+{
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
+ std::string tmp(evaltext+1);
+ tmp.resize(tmp.size() - 1);
+
+ return isc::eval::EvalParser::make_STRING(tmp, loc);
+}
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 127 "lexer.ll"
+{
+ /* A hex string has been matched. It contains the '0x' or '0X' header
+ followed by at least one hexadecimal digit. */
+ return isc::eval::EvalParser::make_HEXSTRING(evaltext, loc);
+}
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 133 "lexer.ll"
+{
+ /* An integer was found. */
+ std::string tmp(evaltext);
+
+ try {
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
+ static_cast<void>(boost::lexical_cast<int64_t>(tmp));
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(loc, "Failed to convert " + tmp + " to an integer.");
+ }
+
+ /* The parser needs the string form as double conversion is no lossless */
+ return isc::eval::EvalParser::make_INTEGER(tmp, loc);
+}
+ YY_BREAK
+case 6:
+/* rule 6 can match eol */
+*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */
+YY_LINENO_REWIND_TO(yy_cp - 1);
+(yy_c_buf_p) = yy_cp -= 1;
+YY_DO_BEFORE_ACTION; /* set up yytext again */
+YY_RULE_SETUP
+#line 151 "lexer.ll"
+{
+ /* This string specifies option name starting with a letter
+ and further containing letters, digits, hyphens and
+ underscores and finishing by letters or digits. */
+ /* Moved from a variable trailing context to C++ code as it was too slow */
+ std::string tmp(evaltext);
+ /* remove possible trailing blanks or newlines */
+ while (tmp.size() > 1) {
+ char last = tmp[tmp.size() - 1];
+ if ((last != ' ') && (last != '\t') && (last != '\n')) {
+ break;
+ }
+ if (last == '\n') {
+ /* Take embedded newlines into account */
+ /* Can make it more complex to handle spaces after the last
+ newline but currently keep it simple... */
+ loc.lines();
+ loc.step();
+ }
+ tmp.resize(tmp.size() - 1);
+ }
+ return isc::eval::EvalParser::make_OPTION_NAME(tmp, loc);
+}
+ YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 175 "lexer.ll"
+{
+ /* IPv4 or IPv6 address */
+ std::string tmp(evaltext);
+
+ /* Some incorrect addresses can match so we have to check. */
+ try {
+ isc::asiolink::IOAddress ip(tmp);
+ } catch (...) {
+ driver.error(loc, "Failed to convert " + tmp + " to an IP address.");
+ }
+
+ return isc::eval::EvalParser::make_IP_ADDRESS(evaltext, loc);
+}
+ YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 189 "lexer.ll"
+return isc::eval::EvalParser::make_EQUAL(loc);
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 190 "lexer.ll"
+return isc::eval::EvalParser::make_OPTION(loc);
+ YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 191 "lexer.ll"
+return isc::eval::EvalParser::make_RELAY4(loc);
+ YY_BREAK
+case 11:
+YY_RULE_SETUP
+#line 192 "lexer.ll"
+return isc::eval::EvalParser::make_RELAY6(loc);
+ YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 193 "lexer.ll"
+return isc::eval::EvalParser::make_PEERADDR(loc);
+ YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 194 "lexer.ll"
+return isc::eval::EvalParser::make_LINKADDR(loc);
+ YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 195 "lexer.ll"
+return isc::eval::EvalParser::make_TEXT(loc);
+ YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 196 "lexer.ll"
+return isc::eval::EvalParser::make_HEX(loc);
+ YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 197 "lexer.ll"
+return isc::eval::EvalParser::make_EXISTS(loc);
+ YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 198 "lexer.ll"
+return isc::eval::EvalParser::make_PKT(loc);
+ YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 199 "lexer.ll"
+return isc::eval::EvalParser::make_IFACE(loc);
+ YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 200 "lexer.ll"
+return isc::eval::EvalParser::make_SRC(loc);
+ YY_BREAK
+case 20:
+YY_RULE_SETUP
+#line 201 "lexer.ll"
+return isc::eval::EvalParser::make_DST(loc);
+ YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 202 "lexer.ll"
+return isc::eval::EvalParser::make_LEN(loc);
+ YY_BREAK
+case 22:
+YY_RULE_SETUP
+#line 203 "lexer.ll"
+return isc::eval::EvalParser::make_PKT4(loc);
+ YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 204 "lexer.ll"
+return isc::eval::EvalParser::make_CHADDR(loc);
+ YY_BREAK
+case 24:
+YY_RULE_SETUP
+#line 205 "lexer.ll"
+return isc::eval::EvalParser::make_HLEN(loc);
+ YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 206 "lexer.ll"
+return isc::eval::EvalParser::make_HTYPE(loc);
+ YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 207 "lexer.ll"
+return isc::eval::EvalParser::make_CIADDR(loc);
+ YY_BREAK
+case 27:
+YY_RULE_SETUP
+#line 208 "lexer.ll"
+return isc::eval::EvalParser::make_GIADDR(loc);
+ YY_BREAK
+case 28:
+YY_RULE_SETUP
+#line 209 "lexer.ll"
+return isc::eval::EvalParser::make_YIADDR(loc);
+ YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 210 "lexer.ll"
+return isc::eval::EvalParser::make_SIADDR(loc);
+ YY_BREAK
+case 30:
+YY_RULE_SETUP
+#line 211 "lexer.ll"
+return isc::eval::EvalParser::make_PKT6(loc);
+ YY_BREAK
+case 31:
+YY_RULE_SETUP
+#line 212 "lexer.ll"
+return isc::eval::EvalParser::make_MSGTYPE(loc);
+ YY_BREAK
+case 32:
+YY_RULE_SETUP
+#line 213 "lexer.ll"
+return isc::eval::EvalParser::make_TRANSID(loc);
+ YY_BREAK
+case 33:
+YY_RULE_SETUP
+#line 214 "lexer.ll"
+return isc::eval::EvalParser::make_VENDOR(loc);
+ YY_BREAK
+case 34:
+YY_RULE_SETUP
+#line 215 "lexer.ll"
+return isc::eval::EvalParser::make_VENDOR_CLASS(loc);
+ YY_BREAK
+case 35:
+YY_RULE_SETUP
+#line 216 "lexer.ll"
+return isc::eval::EvalParser::make_DATA(loc);
+ YY_BREAK
+case 36:
+YY_RULE_SETUP
+#line 217 "lexer.ll"
+return isc::eval::EvalParser::make_ENTERPRISE(loc);
+ YY_BREAK
+case 37:
+YY_RULE_SETUP
+#line 218 "lexer.ll"
+return isc::eval::EvalParser::make_SUBSTRING(loc);
+ YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 219 "lexer.ll"
+return isc::eval::EvalParser::make_SPLIT(loc);
+ YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 220 "lexer.ll"
+return isc::eval::EvalParser::make_ALL(loc);
+ YY_BREAK
+case 40:
+YY_RULE_SETUP
+#line 221 "lexer.ll"
+return isc::eval::EvalParser::make_CONCAT(loc);
+ YY_BREAK
+case 41:
+YY_RULE_SETUP
+#line 222 "lexer.ll"
+return isc::eval::EvalParser::make_IFELSE(loc);
+ YY_BREAK
+case 42:
+YY_RULE_SETUP
+#line 223 "lexer.ll"
+return isc::eval::EvalParser::make_TOHEXSTRING(loc);
+ YY_BREAK
+case 43:
+YY_RULE_SETUP
+#line 224 "lexer.ll"
+return isc::eval::EvalParser::make_ADDRTOTEXT(loc);
+ YY_BREAK
+case 44:
+YY_RULE_SETUP
+#line 225 "lexer.ll"
+return isc::eval::EvalParser::make_INT8TOTEXT(loc);
+ YY_BREAK
+case 45:
+YY_RULE_SETUP
+#line 226 "lexer.ll"
+return isc::eval::EvalParser::make_INT16TOTEXT(loc);
+ YY_BREAK
+case 46:
+YY_RULE_SETUP
+#line 227 "lexer.ll"
+return isc::eval::EvalParser::make_INT32TOTEXT(loc);
+ YY_BREAK
+case 47:
+YY_RULE_SETUP
+#line 228 "lexer.ll"
+return isc::eval::EvalParser::make_UINT8TOTEXT(loc);
+ YY_BREAK
+case 48:
+YY_RULE_SETUP
+#line 229 "lexer.ll"
+return isc::eval::EvalParser::make_UINT16TOTEXT(loc);
+ YY_BREAK
+case 49:
+YY_RULE_SETUP
+#line 230 "lexer.ll"
+return isc::eval::EvalParser::make_UINT32TOTEXT(loc);
+ YY_BREAK
+case 50:
+YY_RULE_SETUP
+#line 231 "lexer.ll"
+return isc::eval::EvalParser::make_NOT(loc);
+ YY_BREAK
+case 51:
+YY_RULE_SETUP
+#line 232 "lexer.ll"
+return isc::eval::EvalParser::make_AND(loc);
+ YY_BREAK
+case 52:
+YY_RULE_SETUP
+#line 233 "lexer.ll"
+return isc::eval::EvalParser::make_OR(loc);
+ YY_BREAK
+case 53:
+YY_RULE_SETUP
+#line 234 "lexer.ll"
+return isc::eval::EvalParser::make_MEMBER(loc);
+ YY_BREAK
+case 54:
+YY_RULE_SETUP
+#line 235 "lexer.ll"
+return isc::eval::EvalParser::make_DOT(loc);
+ YY_BREAK
+case 55:
+YY_RULE_SETUP
+#line 236 "lexer.ll"
+return isc::eval::EvalParser::make_LPAREN(loc);
+ YY_BREAK
+case 56:
+YY_RULE_SETUP
+#line 237 "lexer.ll"
+return isc::eval::EvalParser::make_RPAREN(loc);
+ YY_BREAK
+case 57:
+YY_RULE_SETUP
+#line 238 "lexer.ll"
+return isc::eval::EvalParser::make_LBRACKET(loc);
+ YY_BREAK
+case 58:
+YY_RULE_SETUP
+#line 239 "lexer.ll"
+return isc::eval::EvalParser::make_RBRACKET(loc);
+ YY_BREAK
+case 59:
+YY_RULE_SETUP
+#line 240 "lexer.ll"
+return isc::eval::EvalParser::make_COMA(loc);
+ YY_BREAK
+case 60:
+YY_RULE_SETUP
+#line 241 "lexer.ll"
+return isc::eval::EvalParser::make_ANY(loc);
+ YY_BREAK
+case 61:
+YY_RULE_SETUP
+#line 242 "lexer.ll"
+return isc::eval::EvalParser::make_PLUS(loc);
+ YY_BREAK
+case 62:
+YY_RULE_SETUP
+#line 243 "lexer.ll"
+driver.error (loc, "Invalid character: " + std::string(evaltext));
+ YY_BREAK
+case YY_STATE_EOF(INITIAL):
+#line 244 "lexer.ll"
+return isc::eval::EvalParser::make_END(loc);
+ YY_BREAK
+case 63:
+YY_RULE_SETUP
+#line 245 "lexer.ll"
+ECHO;
+ YY_BREAK
+#line 1849 "lexer.cc"
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = (yy_hold_char);
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
+ * consistency between YY_CURRENT_BUFFER and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+/* %if-c-only */
+ YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++(yy_c_buf_p);
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+/* %% [14.0] code to do back-up for compressed tables and set up yy_cp goes here */
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ (yy_did_buffer_switch_on_eof) = 0;
+
+ if ( yywrap( ) )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) =
+ (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ (yy_c_buf_p) =
+ &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+ } /* end of user's declarations */
+} /* end of yylex */
+/* %ok-for-header */
+
+/* %if-c++-only */
+/* %not-for-header */
+/* %ok-for-header */
+
+/* %endif */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+/* %if-c-only */
+static int yy_get_next_buffer (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+ char *source = (yytext_ptr);
+ int number_to_move, i;
+ int ret_val;
+
+ if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1);
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+ else
+ {
+ int num_to_read =
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
+
+ int yy_c_buf_p_offset =
+ (int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ yyrealloc( (void *) b->yy_ch_buf,
+ (yy_size_t) (b->yy_buf_size + 2) );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = NULL;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+ number_to_move - 1;
+
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+ (yy_n_chars), num_to_read );
+
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ if ( (yy_n_chars) == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart( yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+ /* Extend the array by 50%, plus the number we really need. */
+ int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1);
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc(
+ (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size );
+ if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+ /* "- 2" to take care of EOB's */
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2);
+ }
+
+ (yy_n_chars) += number_to_move;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+ (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+ return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+/* %if-c-only */
+/* %not-for-header */
+ static yy_state_type yy_get_previous_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ yy_state_type yy_current_state;
+ char *yy_cp;
+
+/* %% [15.0] code to get the start state into yy_current_state goes here */
+ yy_current_state = (yy_start);
+
+ for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+ {
+/* %% [16.0] code to find the next state goes here */
+ YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 279 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ }
+
+ return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+/* %if-c-only */
+ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ int yy_is_jam;
+ /* %% [17.0] code to find the next state, and perhaps do backing up, goes here */
+ char *yy_cp = (yy_c_buf_p);
+
+ YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 279 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ yy_is_jam = (yy_current_state == 278);
+
+ return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_UNPUT
+/* %if-c-only */
+
+/* %endif */
+#endif
+
+/* %if-c-only */
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+ static int yyinput (void)
+#else
+ static int input (void)
+#endif
+
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ int c;
+
+ *(yy_c_buf_p) = (yy_hold_char);
+
+ if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ /* This was really a NUL. */
+ *(yy_c_buf_p) = '\0';
+
+ else
+ { /* need more input */
+ int offset = (int) ((yy_c_buf_p) - (yytext_ptr));
+ ++(yy_c_buf_p);
+
+ switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ yyrestart( yyin );
+
+ /*FALLTHROUGH*/
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap( ) )
+ return 0;
+
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) = (yytext_ptr) + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */
+ *(yy_c_buf_p) = '\0'; /* preserve yytext */
+ (yy_hold_char) = *++(yy_c_buf_p);
+
+/* %% [19.0] update BOL and yylineno */
+ if ( c == '\n' )
+
+ yylineno++;
+;
+
+ return c;
+}
+/* %if-c-only */
+#endif /* ifndef YY_NO_INPUT */
+/* %endif */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ *
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+/* %if-c-only */
+ void yyrestart (FILE * input_file )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ if ( ! YY_CURRENT_BUFFER ){
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_init_buffer( YY_CURRENT_BUFFER, input_file );
+ yy_load_buffer_state( );
+}
+
+/* %if-c++-only */
+/* %endif */
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ *
+ */
+/* %if-c-only */
+ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ /* TODO. We should be able to replace this entire function body
+ * with
+ * yypop_buffer_state();
+ * yypush_buffer_state(new_buffer);
+ */
+ yyensure_buffer_stack ();
+ if ( YY_CURRENT_BUFFER == new_buffer )
+ return;
+
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+ yy_load_buffer_state( );
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+
+/* %if-c-only */
+static void yy_load_buffer_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+/* %if-c-only */
+ yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ (yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ *
+ * @return the allocated buffer state.
+ */
+/* %if-c-only */
+ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ yy_init_buffer( b, file );
+
+ return b;
+}
+
+/* %if-c++-only */
+/* %endif */
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ *
+ */
+/* %if-c-only */
+ void yy_delete_buffer (YY_BUFFER_STATE b )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ if ( ! b )
+ return;
+
+ if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ yyfree( (void *) b->yy_ch_buf );
+
+ yyfree( (void *) b );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+/* %if-c-only */
+ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+{
+ int oerrno = errno;
+
+ yy_flush_buffer( b );
+
+/* %if-c-only */
+ b->yy_input_file = file;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ b->yy_fill_buffer = 1;
+
+ /* If b is the current buffer, then yy_init_buffer was _probably_
+ * called from yyrestart() or through yy_get_next_buffer.
+ * In that case, we don't want to reset the lineno or column.
+ */
+ if (b != YY_CURRENT_BUFFER){
+ b->yy_bs_lineno = 1;
+ b->yy_bs_column = 0;
+ }
+
+/* %if-c-only */
+
+ b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ *
+ */
+/* %if-c-only */
+ void yy_flush_buffer (YY_BUFFER_STATE b )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == YY_CURRENT_BUFFER )
+ yy_load_buffer_state( );
+}
+
+/* %if-c-or-c++ */
+/** Pushes the new state onto the stack. The new state becomes
+ * the current state. This function will allocate the stack
+ * if necessary.
+ * @param new_buffer The new state.
+ *
+ */
+/* %if-c-only */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if (new_buffer == NULL)
+ return;
+
+ yyensure_buffer_stack();
+
+ /* This block is copied from yy_switch_to_buffer. */
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ /* Only push if top exists. Otherwise, replace top. */
+ if (YY_CURRENT_BUFFER)
+ (yy_buffer_stack_top)++;
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+ /* copied from yy_switch_to_buffer. */
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+/* %endif */
+
+/* %if-c-or-c++ */
+/** Removes and deletes the top of the stack, if present.
+ * The next element becomes the new top.
+ *
+ */
+/* %if-c-only */
+void yypop_buffer_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if (!YY_CURRENT_BUFFER)
+ return;
+
+ yy_delete_buffer(YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ if ((yy_buffer_stack_top) > 0)
+ --(yy_buffer_stack_top);
+
+ if (YY_CURRENT_BUFFER) {
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+ }
+}
+/* %endif */
+
+/* %if-c-or-c++ */
+/* Allocates the stack if it does not exist.
+ * Guarantees space for at least one push.
+ */
+/* %if-c-only */
+static void yyensure_buffer_stack (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ yy_size_t num_to_alloc;
+
+ if (!(yy_buffer_stack)) {
+
+ /* First allocation is just for 2 elements, since we don't know if this
+ * scanner will even need a stack. We use 2 instead of 1 to avoid an
+ * immediate realloc on the next call.
+ */
+ num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
+ (num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+
+ (yy_buffer_stack_max) = num_to_alloc;
+ (yy_buffer_stack_top) = 0;
+ return;
+ }
+
+ if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+ /* Increase the buffer to prepare for a possible push. */
+ yy_size_t grow_size = 8 /* arbitrary grow size */;
+
+ num_to_alloc = (yy_buffer_stack_max) + grow_size;
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
+ ((yy_buffer_stack),
+ num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ /* zero only the new slots.*/
+ memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+ (yy_buffer_stack_max) = num_to_alloc;
+ }
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size )
+{
+ YY_BUFFER_STATE b;
+
+ if ( size < 2 ||
+ base[size-2] != YY_END_OF_BUFFER_CHAR ||
+ base[size-1] != YY_END_OF_BUFFER_CHAR )
+ /* They forgot to leave room for the EOB's. */
+ return NULL;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+ b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */
+ b->yy_buf_pos = b->yy_ch_buf = base;
+ b->yy_is_our_buffer = 0;
+ b->yy_input_file = NULL;
+ b->yy_n_chars = b->yy_buf_size;
+ b->yy_is_interactive = 0;
+ b->yy_at_bol = 1;
+ b->yy_fill_buffer = 0;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ yy_switch_to_buffer( b );
+
+ return b;
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ *
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ * yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (const char * yystr )
+{
+
+ return yy_scan_bytes( yystr, (int) strlen(yystr) );
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param yybytes the byte buffer to scan
+ * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len )
+{
+ YY_BUFFER_STATE b;
+ char *buf;
+ yy_size_t n;
+ int i;
+
+ /* Get memory for full buffer, including space for trailing EOB's. */
+ n = (yy_size_t) (_yybytes_len + 2);
+ buf = (char *) yyalloc( n );
+ if ( ! buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+ for ( i = 0; i < _yybytes_len; ++i )
+ buf[i] = yybytes[i];
+
+ buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+ b = yy_scan_buffer( buf, n );
+ if ( ! b )
+ YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+ /* It's okay to grow etc. this buffer, and we should throw it
+ * away when we're done.
+ */
+ b->yy_is_our_buffer = 1;
+
+ return b;
+}
+/* %endif */
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+/* %if-c-only */
+static void yynoreturn yy_fatal_error (const char* msg )
+{
+ fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+}
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ yytext[yyleng] = (yy_hold_char); \
+ (yy_c_buf_p) = yytext + yyless_macro_arg; \
+ (yy_hold_char) = *(yy_c_buf_p); \
+ *(yy_c_buf_p) = '\0'; \
+ yyleng = yyless_macro_arg; \
+ } \
+ while ( 0 )
+
+/* Accessor methods (get/set functions) to struct members. */
+
+/* %if-c-only */
+/* %if-reentrant */
+/* %endif */
+
+/** Get the current line number.
+ *
+ */
+int yyget_lineno (void)
+{
+
+ return yylineno;
+}
+
+/** Get the input stream.
+ *
+ */
+FILE *yyget_in (void)
+{
+ return yyin;
+}
+
+/** Get the output stream.
+ *
+ */
+FILE *yyget_out (void)
+{
+ return yyout;
+}
+
+/** Get the length of the current token.
+ *
+ */
+int yyget_leng (void)
+{
+ return yyleng;
+}
+
+/** Get the current token.
+ *
+ */
+
+char *yyget_text (void)
+{
+ return yytext;
+}
+
+/* %if-reentrant */
+/* %endif */
+
+/** Set the current line number.
+ * @param _line_number line number
+ *
+ */
+void yyset_lineno (int _line_number )
+{
+
+ yylineno = _line_number;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param _in_str A readable stream.
+ *
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE * _in_str )
+{
+ yyin = _in_str ;
+}
+
+void yyset_out (FILE * _out_str )
+{
+ yyout = _out_str ;
+}
+
+int yyget_debug (void)
+{
+ return yy_flex_debug;
+}
+
+void yyset_debug (int _bdebug )
+{
+ yy_flex_debug = _bdebug ;
+}
+
+/* %endif */
+
+/* %if-reentrant */
+/* %if-bison-bridge */
+/* %endif */
+/* %endif if-c-only */
+
+/* %if-c-only */
+static int yy_init_globals (void)
+{
+ /* Initialization is the same as for the non-reentrant scanner.
+ * This function is called from yylex_destroy(), so don't allocate here.
+ */
+
+ /* We do not touch yylineno unless the option is enabled. */
+ yylineno = 1;
+
+ (yy_buffer_stack) = NULL;
+ (yy_buffer_stack_top) = 0;
+ (yy_buffer_stack_max) = 0;
+ (yy_c_buf_p) = NULL;
+ (yy_init) = 0;
+ (yy_start) = 0;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+ yyin = stdin;
+ yyout = stdout;
+#else
+ yyin = NULL;
+ yyout = NULL;
+#endif
+
+ /* For future reference: Set errno on error, since we are called by
+ * yylex_init()
+ */
+ return 0;
+}
+/* %endif */
+
+/* %if-c-only SNIP! this currently causes conflicts with the c++ scanner */
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy (void)
+{
+
+ /* Pop the buffer stack, destroying each element. */
+ while(YY_CURRENT_BUFFER){
+ yy_delete_buffer( YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ yypop_buffer_state();
+ }
+
+ /* Destroy the stack itself. */
+ yyfree((yy_buffer_stack) );
+ (yy_buffer_stack) = NULL;
+
+ /* Reset the globals. This is important in a non-reentrant scanner so the next time
+ * yylex() is called, initialization will occur. */
+ yy_init_globals( );
+
+/* %if-reentrant */
+/* %endif */
+ return 0;
+}
+/* %endif */
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, const char * s2, int n )
+{
+
+ int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (const char * s )
+{
+ int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+}
+#endif
+
+void *yyalloc (yy_size_t size )
+{
+ return malloc(size);
+}
+
+void *yyrealloc (void * ptr, yy_size_t size )
+{
+
+ /* The cast to (char *) in the following accommodates both
+ * implementations that use char* generic pointers, and those
+ * that use void* generic pointers. It works with the latter
+ * because both ANSI C and C++ allow castless assignment from
+ * any pointer type to void*, and deal with argument conversions
+ * as though doing an assignment.
+ */
+ return realloc(ptr, size);
+}
+
+void yyfree (void * ptr )
+{
+ free( (char *) ptr ); /* see yyrealloc() for (char *) cast */
+}
+
+/* %if-tables-serialization definitions */
+/* %define-yytables The name for this specific scanner's tables. */
+#define YYTABLES_NAME "yytables"
+/* %endif */
+
+/* %ok-for-header */
+
+#line 245 "lexer.ll"
+
+
+using namespace isc::eval;
+
+void
+EvalContext::scanStringBegin(ParserType type)
+{
+ start_token_flag = true;
+ start_token_value = type;
+
+ loc.initialize(&file_);
+ eval_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+ buffer = eval_scan_bytes(string_.c_str(), string_.size());
+ if (!buffer) {
+ fatal("cannot scan string");
+ /* fatal() throws an exception so this can't be reached */
+ }
+}
+
+void
+EvalContext::scanStringEnd()
+{
+ eval_delete_buffer(YY_CURRENT_BUFFER);
+}
+
+namespace {
+/** To avoid unused function error */
+class Dummy {
+ /* cppcheck-suppress unusedPrivateFunction */
+ void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}
+#endif /* !__clang_analyzer__ */
+
diff --git a/src/lib/eval/lexer.ll b/src/lib/eval/lexer.ll
new file mode 100644
index 0000000..3c376c2
--- /dev/null
+++ b/src/lib/eval/lexer.ll
@@ -0,0 +1,277 @@
+/* Copyright (C) 2015-2022 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/. */
+
+%{ /* -*- C++ -*- */
+
+/* Generated files do not make clang static analyser so happy */
+#ifndef __clang_analyzer__
+
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <eval/eval_context.h>
+#include <eval/parser.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
+
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
+# undef yywrap
+# define yywrap() 1
+
+/* The location of the current token. The lexer will keep updating it. This
+ variable will be useful for logging errors. */
+static isc::eval::location loc;
+
+namespace {
+ bool start_token_flag = false;
+ isc::eval::EvalContext::ParserType start_token_value;
+};
+
+/* To avoid the call to exit... oops! */
+#define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg)
+%}
+
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+ always parse only a single string, there's no need to do any wraps. And
+ using yywrap requires linking with -lfl, which provides the default yywrap
+ implementation that always returns 1 anyway. */
+%option noyywrap
+
+/* nounput simplifies the lexer, by removing support for putting a character
+ back into the input stream. We never use such capability anyway. */
+%option nounput
+
+/* batch means that we'll never use the generated lexer interactively. */
+%option batch
+
+/* Enables debug mode. To see the debug messages, one needs to also set
+ eval_flex_debug to 1, then the debug messages will be printed on stderr. */
+%option debug
+
+/* I have no idea what this option does, except it was specified in the bison
+ examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+ be on the safe side and keep it. */
+%option noinput
+
+/* This line tells flex to track the line numbers. It's not really that
+ useful for client classes, which typically are one-liners, but it may be
+ useful in more complex cases. */
+%option yylineno
+
+/* These are not token expressions yet, just convenience expressions that
+ can be used during actual token definitions. Note some can match
+ incorrect inputs (e.g., IP addresses) which must be checked. */
+int \-?[0-9]+
+hex [0-9a-fA-F]+
+blank [ \t]
+addr4 [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+
+addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
+
+%{
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
+#define YY_USER_ACTION loc.columns(evalleng);
+%}
+
+%%
+
+%{
+ /* Code run each time evallex is called. */
+ loc.step();
+
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case EvalContext::PARSER_BOOL:
+ return isc::eval::EvalParser::make_TOPLEVEL_BOOL(loc);
+ default:
+ case EvalContext::PARSER_STRING:
+ return isc::eval::EvalParser::make_TOPLEVEL_STRING(loc);
+ }
+ }
+
+%}
+
+{blank}+ {
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
+ loc.step();
+}
+
+[\n]+ {
+ /* Newline found. Let's update the location and continue. */
+ loc.lines(evalleng);
+ loc.step();
+}
+
+\'[^\'\n]*\' {
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
+ std::string tmp(evaltext+1);
+ tmp.resize(tmp.size() - 1);
+
+ return isc::eval::EvalParser::make_STRING(tmp, loc);
+}
+
+0[xX]{hex} {
+ /* A hex string has been matched. It contains the '0x' or '0X' header
+ followed by at least one hexadecimal digit. */
+ return isc::eval::EvalParser::make_HEXSTRING(evaltext, loc);
+}
+
+{int} {
+ /* An integer was found. */
+ std::string tmp(evaltext);
+
+ try {
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
+ static_cast<void>(boost::lexical_cast<int64_t>(tmp));
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(loc, "Failed to convert " + tmp + " to an integer.");
+ }
+
+ /* The parser needs the string form as double conversion is no lossless */
+ return isc::eval::EvalParser::make_INTEGER(tmp, loc);
+}
+
+[A-Za-z]([-_A-Za-z0-9]*[A-Za-z0-9])?({blank}|\n)*/] {
+ /* This string specifies option name starting with a letter
+ and further containing letters, digits, hyphens and
+ underscores and finishing by letters or digits. */
+ /* Moved from a variable trailing context to C++ code as it was too slow */
+ std::string tmp(evaltext);
+ /* remove possible trailing blanks or newlines */
+ while (tmp.size() > 1) {
+ char last = tmp[tmp.size() - 1];
+ if ((last != ' ') && (last != '\t') && (last != '\n')) {
+ break;
+ }
+ if (last == '\n') {
+ /* Take embedded newlines into account */
+ /* Can make it more complex to handle spaces after the last
+ newline but currently keep it simple... */
+ loc.lines();
+ loc.step();
+ }
+ tmp.resize(tmp.size() - 1);
+ }
+ return isc::eval::EvalParser::make_OPTION_NAME(tmp, loc);
+}
+
+{addr4}|{addr6} {
+ /* IPv4 or IPv6 address */
+ std::string tmp(evaltext);
+
+ /* Some incorrect addresses can match so we have to check. */
+ try {
+ isc::asiolink::IOAddress ip(tmp);
+ } catch (...) {
+ driver.error(loc, "Failed to convert " + tmp + " to an IP address.");
+ }
+
+ return isc::eval::EvalParser::make_IP_ADDRESS(evaltext, loc);
+}
+
+"==" return isc::eval::EvalParser::make_EQUAL(loc);
+"option" return isc::eval::EvalParser::make_OPTION(loc);
+"relay4" return isc::eval::EvalParser::make_RELAY4(loc);
+"relay6" return isc::eval::EvalParser::make_RELAY6(loc);
+"peeraddr" return isc::eval::EvalParser::make_PEERADDR(loc);
+"linkaddr" return isc::eval::EvalParser::make_LINKADDR(loc);
+"text" return isc::eval::EvalParser::make_TEXT(loc);
+"hex" return isc::eval::EvalParser::make_HEX(loc);
+"exists" return isc::eval::EvalParser::make_EXISTS(loc);
+"pkt" return isc::eval::EvalParser::make_PKT(loc);
+"iface" return isc::eval::EvalParser::make_IFACE(loc);
+"src" return isc::eval::EvalParser::make_SRC(loc);
+"dst" return isc::eval::EvalParser::make_DST(loc);
+"len" return isc::eval::EvalParser::make_LEN(loc);
+"pkt4" return isc::eval::EvalParser::make_PKT4(loc);
+"mac" return isc::eval::EvalParser::make_CHADDR(loc);
+"hlen" return isc::eval::EvalParser::make_HLEN(loc);
+"htype" return isc::eval::EvalParser::make_HTYPE(loc);
+"ciaddr" return isc::eval::EvalParser::make_CIADDR(loc);
+"giaddr" return isc::eval::EvalParser::make_GIADDR(loc);
+"yiaddr" return isc::eval::EvalParser::make_YIADDR(loc);
+"siaddr" return isc::eval::EvalParser::make_SIADDR(loc);
+"pkt6" return isc::eval::EvalParser::make_PKT6(loc);
+"msgtype" return isc::eval::EvalParser::make_MSGTYPE(loc);
+"transid" return isc::eval::EvalParser::make_TRANSID(loc);
+"vendor" return isc::eval::EvalParser::make_VENDOR(loc);
+"vendor-class" return isc::eval::EvalParser::make_VENDOR_CLASS(loc);
+"data" return isc::eval::EvalParser::make_DATA(loc);
+"enterprise" return isc::eval::EvalParser::make_ENTERPRISE(loc);
+"substring" return isc::eval::EvalParser::make_SUBSTRING(loc);
+"split" return isc::eval::EvalParser::make_SPLIT(loc);
+"all" return isc::eval::EvalParser::make_ALL(loc);
+"concat" return isc::eval::EvalParser::make_CONCAT(loc);
+"ifelse" return isc::eval::EvalParser::make_IFELSE(loc);
+"hexstring" return isc::eval::EvalParser::make_TOHEXSTRING(loc);
+"addrtotext" return isc::eval::EvalParser::make_ADDRTOTEXT(loc);
+"int8totext" return isc::eval::EvalParser::make_INT8TOTEXT(loc);
+"int16totext" return isc::eval::EvalParser::make_INT16TOTEXT(loc);
+"int32totext" return isc::eval::EvalParser::make_INT32TOTEXT(loc);
+"uint8totext" return isc::eval::EvalParser::make_UINT8TOTEXT(loc);
+"uint16totext" return isc::eval::EvalParser::make_UINT16TOTEXT(loc);
+"uint32totext" return isc::eval::EvalParser::make_UINT32TOTEXT(loc);
+"not" return isc::eval::EvalParser::make_NOT(loc);
+"and" return isc::eval::EvalParser::make_AND(loc);
+"or" return isc::eval::EvalParser::make_OR(loc);
+"member" return isc::eval::EvalParser::make_MEMBER(loc);
+"." return isc::eval::EvalParser::make_DOT(loc);
+"(" return isc::eval::EvalParser::make_LPAREN(loc);
+")" return isc::eval::EvalParser::make_RPAREN(loc);
+"[" return isc::eval::EvalParser::make_LBRACKET(loc);
+"]" return isc::eval::EvalParser::make_RBRACKET(loc);
+"," return isc::eval::EvalParser::make_COMA(loc);
+"*" return isc::eval::EvalParser::make_ANY(loc);
+"+" return isc::eval::EvalParser::make_PLUS(loc);
+. driver.error (loc, "Invalid character: " + std::string(evaltext));
+<<EOF>> return isc::eval::EvalParser::make_END(loc);
+%%
+
+using namespace isc::eval;
+
+void
+EvalContext::scanStringBegin(ParserType type)
+{
+ start_token_flag = true;
+ start_token_value = type;
+
+ loc.initialize(&file_);
+ eval_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+ buffer = eval_scan_bytes(string_.c_str(), string_.size());
+ if (!buffer) {
+ fatal("cannot scan string");
+ /* fatal() throws an exception so this can't be reached */
+ }
+}
+
+void
+EvalContext::scanStringEnd()
+{
+ eval_delete_buffer(YY_CURRENT_BUFFER);
+}
+
+namespace {
+/** To avoid unused function error */
+class Dummy {
+ /* cppcheck-suppress unusedPrivateFunction */
+ void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}
+#endif /* !__clang_analyzer__ */
diff --git a/src/lib/eval/location.hh b/src/lib/eval/location.hh
new file mode 100644
index 0000000..5bde479
--- /dev/null
+++ b/src/lib/eval/location.hh
@@ -0,0 +1,306 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Locations for Bison parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file location.hh
+ ** Define the isc::eval::location class.
+ */
+
+#ifndef YY_EVAL_LOCATION_HH_INCLUDED
+# define YY_EVAL_LOCATION_HH_INCLUDED
+
+# include <iostream>
+# include <string>
+
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+#line 14 "parser.yy"
+namespace isc { namespace eval {
+#line 59 "location.hh"
+
+ /// A point in a source file.
+ class position
+ {
+ public:
+ /// Type for file name.
+ typedef const std::string filename_type;
+ /// Type for line and column numbers.
+ typedef int counter_type;
+
+ /// Construct a position.
+ explicit position (filename_type* f = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ : filename (f)
+ , line (l)
+ , column (c)
+ {}
+
+
+ /// Initialization.
+ void initialize (filename_type* fn = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ {
+ filename = fn;
+ line = l;
+ column = c;
+ }
+
+ /** \name Line and Column related manipulators
+ ** \{ */
+ /// (line related) Advance to the COUNT next lines.
+ void lines (counter_type count = 1)
+ {
+ if (count)
+ {
+ column = 1;
+ line = add_ (line, count, 1);
+ }
+ }
+
+ /// (column related) Advance to the COUNT next columns.
+ void columns (counter_type count = 1)
+ {
+ column = add_ (column, count, 1);
+ }
+ /** \} */
+
+ /// File name to which this position refers.
+ filename_type* filename;
+ /// Current line number.
+ counter_type line;
+ /// Current column number.
+ counter_type column;
+
+ private:
+ /// Compute max (min, lhs+rhs).
+ static counter_type add_ (counter_type lhs, counter_type rhs, counter_type min)
+ {
+ return lhs + rhs < min ? min : lhs + rhs;
+ }
+ };
+
+ /// Add \a width columns, in place.
+ inline position&
+ operator+= (position& res, position::counter_type width)
+ {
+ res.columns (width);
+ return res;
+ }
+
+ /// Add \a width columns.
+ inline position
+ operator+ (position res, position::counter_type width)
+ {
+ return res += width;
+ }
+
+ /// Subtract \a width columns, in place.
+ inline position&
+ operator-= (position& res, position::counter_type width)
+ {
+ return res += -width;
+ }
+
+ /// Subtract \a width columns.
+ inline position
+ operator- (position res, position::counter_type width)
+ {
+ return res -= width;
+ }
+
+ /** \brief Intercept output stream redirection.
+ ** \param ostr the destination output stream
+ ** \param pos a reference to the position to redirect
+ */
+ template <typename YYChar>
+ std::basic_ostream<YYChar>&
+ operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
+ {
+ if (pos.filename)
+ ostr << *pos.filename << ':';
+ return ostr << pos.line << '.' << pos.column;
+ }
+
+ /// Two points in a source file.
+ class location
+ {
+ public:
+ /// Type for file name.
+ typedef position::filename_type filename_type;
+ /// Type for line and column numbers.
+ typedef position::counter_type counter_type;
+
+ /// Construct a location from \a b to \a e.
+ location (const position& b, const position& e)
+ : begin (b)
+ , end (e)
+ {}
+
+ /// Construct a 0-width location in \a p.
+ explicit location (const position& p = position ())
+ : begin (p)
+ , end (p)
+ {}
+
+ /// Construct a 0-width location in \a f, \a l, \a c.
+ explicit location (filename_type* f,
+ counter_type l = 1,
+ counter_type c = 1)
+ : begin (f, l, c)
+ , end (f, l, c)
+ {}
+
+
+ /// Initialization.
+ void initialize (filename_type* f = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ {
+ begin.initialize (f, l, c);
+ end = begin;
+ }
+
+ /** \name Line and Column related manipulators
+ ** \{ */
+ public:
+ /// Reset initial location to final location.
+ void step ()
+ {
+ begin = end;
+ }
+
+ /// Extend the current location to the COUNT next columns.
+ void columns (counter_type count = 1)
+ {
+ end += count;
+ }
+
+ /// Extend the current location to the COUNT next lines.
+ void lines (counter_type count = 1)
+ {
+ end.lines (count);
+ }
+ /** \} */
+
+
+ public:
+ /// Beginning of the located region.
+ position begin;
+ /// End of the located region.
+ position end;
+ };
+
+ /// Join two locations, in place.
+ inline location&
+ operator+= (location& res, const location& end)
+ {
+ res.end = end.end;
+ return res;
+ }
+
+ /// Join two locations.
+ inline location
+ operator+ (location res, const location& end)
+ {
+ return res += end;
+ }
+
+ /// Add \a width columns to the end position, in place.
+ inline location&
+ operator+= (location& res, location::counter_type width)
+ {
+ res.columns (width);
+ return res;
+ }
+
+ /// Add \a width columns to the end position.
+ inline location
+ operator+ (location res, location::counter_type width)
+ {
+ return res += width;
+ }
+
+ /// Subtract \a width columns to the end position, in place.
+ inline location&
+ operator-= (location& res, location::counter_type width)
+ {
+ return res += -width;
+ }
+
+ /// Subtract \a width columns to the end position.
+ inline location
+ operator- (location res, location::counter_type width)
+ {
+ return res -= width;
+ }
+
+ /** \brief Intercept output stream redirection.
+ ** \param ostr the destination output stream
+ ** \param loc a reference to the location to redirect
+ **
+ ** Avoid duplicate information.
+ */
+ template <typename YYChar>
+ std::basic_ostream<YYChar>&
+ operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
+ {
+ location::counter_type end_col
+ = 0 < loc.end.column ? loc.end.column - 1 : 0;
+ ostr << loc.begin;
+ if (loc.end.filename
+ && (!loc.begin.filename
+ || *loc.begin.filename != *loc.end.filename))
+ ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
+ else if (loc.begin.line < loc.end.line)
+ ostr << '-' << loc.end.line << '.' << end_col;
+ else if (loc.begin.column < end_col)
+ ostr << '-' << end_col;
+ return ostr;
+ }
+
+#line 14 "parser.yy"
+} } // isc::eval
+#line 305 "location.hh"
+
+#endif // !YY_EVAL_LOCATION_HH_INCLUDED
diff --git a/src/lib/eval/parser.cc b/src/lib/eval/parser.cc
new file mode 100644
index 0000000..e73d694
--- /dev/null
+++ b/src/lib/eval/parser.cc
@@ -0,0 +1,2227 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Skeleton implementation for Bison LALR(1) parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+// especially those whose name start with YY_ or yy_. They are
+// private implementation details that can be changed or removed.
+
+
+// Take the name prefix into account.
+#define yylex evallex
+
+
+
+#include "parser.h"
+
+
+// Unqualified %code blocks.
+#line 33 "parser.yy"
+
+# include "eval_context.h"
+
+// Avoid warnings with the error counter.
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+#endif
+
+#line 57 "parser.cc"
+
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> // FIXME: INFRINGES ON USER NAME SPACE.
+# define YY_(msgid) dgettext ("bison-runtime", msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(msgid) msgid
+# endif
+#endif
+
+
+// Whether we are compiled with exception support.
+#ifndef YY_EXCEPTIONS
+# if defined __GNUC__ && !defined __EXCEPTIONS
+# define YY_EXCEPTIONS 0
+# else
+# define YY_EXCEPTIONS 1
+# endif
+#endif
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K].location)
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+ If N is 0, then set CURRENT to the empty location which ends
+ the previous symbol: RHS[0] (always defined). */
+
+# ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do \
+ if (N) \
+ { \
+ (Current).begin = YYRHSLOC (Rhs, 1).begin; \
+ (Current).end = YYRHSLOC (Rhs, N).end; \
+ } \
+ else \
+ { \
+ (Current).begin = (Current).end = YYRHSLOC (Rhs, 0).end; \
+ } \
+ while (false)
+# endif
+
+
+// Enable debugging if requested.
+#if EVALDEBUG
+
+// A pseudo ostream that takes yydebug_ into account.
+# define YYCDEBUG if (yydebug_) (*yycdebug_)
+
+# define YY_SYMBOL_PRINT(Title, Symbol) \
+ do { \
+ if (yydebug_) \
+ { \
+ *yycdebug_ << Title << ' '; \
+ yy_print_ (*yycdebug_, Symbol); \
+ *yycdebug_ << '\n'; \
+ } \
+ } while (false)
+
+# define YY_REDUCE_PRINT(Rule) \
+ do { \
+ if (yydebug_) \
+ yy_reduce_print_ (Rule); \
+ } while (false)
+
+# define YY_STACK_PRINT() \
+ do { \
+ if (yydebug_) \
+ yy_stack_print_ (); \
+ } while (false)
+
+#else // !EVALDEBUG
+
+# define YYCDEBUG if (false) std::cerr
+# define YY_SYMBOL_PRINT(Title, Symbol) YY_USE (Symbol)
+# define YY_REDUCE_PRINT(Rule) static_cast<void> (0)
+# define YY_STACK_PRINT() static_cast<void> (0)
+
+#endif // !EVALDEBUG
+
+#define yyerrok (yyerrstatus_ = 0)
+#define yyclearin (yyla.clear ())
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYRECOVERING() (!!yyerrstatus_)
+
+#line 14 "parser.yy"
+namespace isc { namespace eval {
+#line 150 "parser.cc"
+
+ /// Build a parser object.
+ EvalParser::EvalParser (EvalContext& ctx_yyarg)
+#if EVALDEBUG
+ : yydebug_ (false),
+ yycdebug_ (&std::cerr),
+#else
+ :
+#endif
+ ctx (ctx_yyarg)
+ {}
+
+ EvalParser::~EvalParser ()
+ {}
+
+ EvalParser::syntax_error::~syntax_error () YY_NOEXCEPT YY_NOTHROW
+ {}
+
+ /*---------.
+ | symbol. |
+ `---------*/
+
+
+
+ // by_state.
+ EvalParser::by_state::by_state () YY_NOEXCEPT
+ : state (empty_state)
+ {}
+
+ EvalParser::by_state::by_state (const by_state& that) YY_NOEXCEPT
+ : state (that.state)
+ {}
+
+ void
+ EvalParser::by_state::clear () YY_NOEXCEPT
+ {
+ state = empty_state;
+ }
+
+ void
+ EvalParser::by_state::move (by_state& that)
+ {
+ state = that.state;
+ that.clear ();
+ }
+
+ EvalParser::by_state::by_state (state_type s) YY_NOEXCEPT
+ : state (s)
+ {}
+
+ EvalParser::symbol_kind_type
+ EvalParser::by_state::kind () const YY_NOEXCEPT
+ {
+ if (state == empty_state)
+ return symbol_kind::S_YYEMPTY;
+ else
+ return YY_CAST (symbol_kind_type, yystos_[+state]);
+ }
+
+ EvalParser::stack_symbol_type::stack_symbol_type ()
+ {}
+
+ EvalParser::stack_symbol_type::stack_symbol_type (YY_RVREF (stack_symbol_type) that)
+ : super_type (YY_MOVE (that.state), YY_MOVE (that.location))
+ {
+ switch (that.kind ())
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.YY_MOVE_OR_COPY< TokenOption::RepresentationType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.YY_MOVE_OR_COPY< TokenPkt4::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.YY_MOVE_OR_COPY< TokenPkt6::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.YY_MOVE_OR_COPY< TokenPkt::MetadataType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.YY_MOVE_OR_COPY< TokenRelay6Field::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.YY_MOVE_OR_COPY< int8_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.YY_MOVE_OR_COPY< std::string > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.YY_MOVE_OR_COPY< uint16_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.YY_MOVE_OR_COPY< uint32_t > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+#if 201103L <= YY_CPLUSPLUS
+ // that is emptied.
+ that.state = empty_state;
+#endif
+ }
+
+ EvalParser::stack_symbol_type::stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) that)
+ : super_type (s, YY_MOVE (that.location))
+ {
+ switch (that.kind ())
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.move< TokenOption::RepresentationType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.move< TokenPkt4::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.move< TokenPkt6::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.move< TokenPkt::MetadataType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.move< TokenRelay6Field::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.move< int8_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.move< std::string > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.move< uint16_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.move< uint32_t > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ // that is emptied.
+ that.kind_ = symbol_kind::S_YYEMPTY;
+ }
+
+#if YY_CPLUSPLUS < 201103L
+ EvalParser::stack_symbol_type&
+ EvalParser::stack_symbol_type::operator= (const stack_symbol_type& that)
+ {
+ state = that.state;
+ switch (that.kind ())
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.copy< TokenOption::RepresentationType > (that.value);
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.copy< TokenPkt4::FieldType > (that.value);
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.copy< TokenPkt6::FieldType > (that.value);
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.copy< TokenPkt::MetadataType > (that.value);
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.copy< TokenRelay6Field::FieldType > (that.value);
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.copy< int8_t > (that.value);
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.copy< std::string > (that.value);
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.copy< uint16_t > (that.value);
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.copy< uint32_t > (that.value);
+ break;
+
+ default:
+ break;
+ }
+
+ location = that.location;
+ return *this;
+ }
+
+ EvalParser::stack_symbol_type&
+ EvalParser::stack_symbol_type::operator= (stack_symbol_type& that)
+ {
+ state = that.state;
+ switch (that.kind ())
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.move< TokenOption::RepresentationType > (that.value);
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.move< TokenPkt4::FieldType > (that.value);
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.move< TokenPkt6::FieldType > (that.value);
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.move< TokenPkt::MetadataType > (that.value);
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.move< TokenRelay6Field::FieldType > (that.value);
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.move< int8_t > (that.value);
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.move< std::string > (that.value);
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.move< uint16_t > (that.value);
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.move< uint32_t > (that.value);
+ break;
+
+ default:
+ break;
+ }
+
+ location = that.location;
+ // that is emptied.
+ that.state = empty_state;
+ return *this;
+ }
+#endif
+
+ template <typename Base>
+ void
+ EvalParser::yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const
+ {
+ if (yymsg)
+ YY_SYMBOL_PRINT (yymsg, yysym);
+ }
+
+#if EVALDEBUG
+ template <typename Base>
+ void
+ EvalParser::yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const
+ {
+ std::ostream& yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (yysym.empty ())
+ yyo << "empty symbol";
+ else
+ {
+ symbol_kind_type yykind = yysym.kind ();
+ yyo << (yykind < YYNTOKENS ? "token" : "nterm")
+ << ' ' << yysym.name () << " ("
+ << yysym.location << ": ";
+ switch (yykind)
+ {
+ case symbol_kind::S_STRING: // "constant string"
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < std::string > (); }
+#line 469 "parser.cc"
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < std::string > (); }
+#line 475 "parser.cc"
+ break;
+
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < std::string > (); }
+#line 481 "parser.cc"
+ break;
+
+ case symbol_kind::S_OPTION_NAME: // "option name"
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < std::string > (); }
+#line 487 "parser.cc"
+ break;
+
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < std::string > (); }
+#line 493 "parser.cc"
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < uint32_t > (); }
+#line 499 "parser.cc"
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < uint16_t > (); }
+#line 505 "parser.cc"
+ break;
+
+ case symbol_kind::S_sub_option_code: // sub_option_code
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < uint16_t > (); }
+#line 511 "parser.cc"
+ break;
+
+ case symbol_kind::S_option_repr_type: // option_repr_type
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < TokenOption::RepresentationType > (); }
+#line 517 "parser.cc"
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < int8_t > (); }
+#line 523 "parser.cc"
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < TokenPkt::MetadataType > (); }
+#line 529 "parser.cc"
+ break;
+
+ case symbol_kind::S_enterprise_id: // enterprise_id
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < uint32_t > (); }
+#line 535 "parser.cc"
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < TokenPkt4::FieldType > (); }
+#line 541 "parser.cc"
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < TokenPkt6::FieldType > (); }
+#line 547 "parser.cc"
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+#line 127 "parser.yy"
+ { yyoutput << yysym.value.template as < TokenRelay6Field::FieldType > (); }
+#line 553 "parser.cc"
+ break;
+
+ default:
+ break;
+ }
+ yyo << ')';
+ }
+ }
+#endif
+
+ void
+ EvalParser::yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym)
+ {
+ if (m)
+ YY_SYMBOL_PRINT (m, sym);
+ yystack_.push (YY_MOVE (sym));
+ }
+
+ void
+ EvalParser::yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym)
+ {
+#if 201103L <= YY_CPLUSPLUS
+ yypush_ (m, stack_symbol_type (s, std::move (sym)));
+#else
+ stack_symbol_type ss (s, sym);
+ yypush_ (m, ss);
+#endif
+ }
+
+ void
+ EvalParser::yypop_ (int n) YY_NOEXCEPT
+ {
+ yystack_.pop (n);
+ }
+
+#if EVALDEBUG
+ std::ostream&
+ EvalParser::debug_stream () const
+ {
+ return *yycdebug_;
+ }
+
+ void
+ EvalParser::set_debug_stream (std::ostream& o)
+ {
+ yycdebug_ = &o;
+ }
+
+
+ EvalParser::debug_level_type
+ EvalParser::debug_level () const
+ {
+ return yydebug_;
+ }
+
+ void
+ EvalParser::set_debug_level (debug_level_type l)
+ {
+ yydebug_ = l;
+ }
+#endif // EVALDEBUG
+
+ EvalParser::state_type
+ EvalParser::yy_lr_goto_state_ (state_type yystate, int yysym)
+ {
+ int yyr = yypgoto_[yysym - YYNTOKENS] + yystate;
+ if (0 <= yyr && yyr <= yylast_ && yycheck_[yyr] == yystate)
+ return yytable_[yyr];
+ else
+ return yydefgoto_[yysym - YYNTOKENS];
+ }
+
+ bool
+ EvalParser::yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT
+ {
+ return yyvalue == yypact_ninf_;
+ }
+
+ bool
+ EvalParser::yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT
+ {
+ return yyvalue == yytable_ninf_;
+ }
+
+ int
+ EvalParser::operator() ()
+ {
+ return parse ();
+ }
+
+ int
+ EvalParser::parse ()
+ {
+ int yyn;
+ /// Length of the RHS of the rule being reduced.
+ int yylen = 0;
+
+ // Error handling.
+ int yynerrs_ = 0;
+ int yyerrstatus_ = 0;
+
+ /// The lookahead symbol.
+ symbol_type yyla;
+
+ /// The locations where the error started and ended.
+ stack_symbol_type yyerror_range[3];
+
+ /// The return value of parse ().
+ int yyresult;
+
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ YYCDEBUG << "Starting parse\n";
+
+
+ /* Initialize the stack. The initial state will be set in
+ yynewstate, since the latter expects the semantical and the
+ location values to have been already stored, initialize these
+ stacks with a primary value. */
+ yystack_.clear ();
+ yypush_ (YY_NULLPTR, 0, YY_MOVE (yyla));
+
+ /*-----------------------------------------------.
+ | yynewstate -- push a new symbol on the stack. |
+ `-----------------------------------------------*/
+ yynewstate:
+ YYCDEBUG << "Entering state " << int (yystack_[0].state) << '\n';
+ YY_STACK_PRINT ();
+
+ // Accept?
+ if (yystack_[0].state == yyfinal_)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+ /*-----------.
+ | yybackup. |
+ `-----------*/
+ yybackup:
+ // Try to take a decision without lookahead.
+ yyn = yypact_[+yystack_[0].state];
+ if (yy_pact_value_is_default_ (yyn))
+ goto yydefault;
+
+ // Read a lookahead token.
+ if (yyla.empty ())
+ {
+ YYCDEBUG << "Reading a token\n";
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ symbol_type yylookahead (yylex (ctx));
+ yyla.move (yylookahead);
+ }
+#if YY_EXCEPTIONS
+ catch (const syntax_error& yyexc)
+ {
+ YYCDEBUG << "Caught exception: " << yyexc.what() << '\n';
+ error (yyexc);
+ goto yyerrlab1;
+ }
+#endif // YY_EXCEPTIONS
+ }
+ YY_SYMBOL_PRINT ("Next token is", yyla);
+
+ if (yyla.kind () == symbol_kind::S_YYerror)
+ {
+ // The scanner already issued an error message, process directly
+ // to error recovery. But do not keep the error token as
+ // lookahead, it is too special and may lead us to an endless
+ // loop in error recovery. */
+ yyla.kind_ = symbol_kind::S_YYUNDEF;
+ goto yyerrlab1;
+ }
+
+ /* If the proper action on seeing token YYLA.TYPE is to reduce or
+ to detect an error, take that action. */
+ yyn += yyla.kind ();
+ if (yyn < 0 || yylast_ < yyn || yycheck_[yyn] != yyla.kind ())
+ {
+ goto yydefault;
+ }
+
+ // Reduce or error.
+ yyn = yytable_[yyn];
+ if (yyn <= 0)
+ {
+ if (yy_table_value_is_error_ (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ // Count tokens shifted since error; after three, turn off error status.
+ if (yyerrstatus_)
+ --yyerrstatus_;
+
+ // Shift the lookahead token.
+ yypush_ ("Shifting", state_type (yyn), YY_MOVE (yyla));
+ goto yynewstate;
+
+
+ /*-----------------------------------------------------------.
+ | yydefault -- do the default action for the current state. |
+ `-----------------------------------------------------------*/
+ yydefault:
+ yyn = yydefact_[+yystack_[0].state];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+ /*-----------------------------.
+ | yyreduce -- do a reduction. |
+ `-----------------------------*/
+ yyreduce:
+ yylen = yyr2_[yyn];
+ {
+ stack_symbol_type yylhs;
+ yylhs.state = yy_lr_goto_state_ (yystack_[yylen].state, yyr1_[yyn]);
+ /* Variants are always initialized to an empty instance of the
+ correct type. The default '$$ = $1' action is NOT applied
+ when using variants. */
+ switch (yyr1_[yyn])
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ yylhs.value.emplace< TokenOption::RepresentationType > ();
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ yylhs.value.emplace< TokenPkt4::FieldType > ();
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ yylhs.value.emplace< TokenPkt6::FieldType > ();
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ yylhs.value.emplace< TokenPkt::MetadataType > ();
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ yylhs.value.emplace< TokenRelay6Field::FieldType > ();
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ yylhs.value.emplace< int8_t > ();
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ yylhs.value.emplace< std::string > ();
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ yylhs.value.emplace< uint16_t > ();
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ yylhs.value.emplace< uint32_t > ();
+ break;
+
+ default:
+ break;
+ }
+
+
+ // Default location.
+ {
+ stack_type::slice range (yystack_, yylen);
+ YYLLOC_DEFAULT (yylhs.location, range, yylen);
+ yyerror_range[1].location = yylhs.location;
+ }
+
+ // Perform the reduction.
+ YY_REDUCE_PRINT (yyn);
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ switch (yyn)
+ {
+ case 6: // bool_expr: "not" bool_expr
+#line 147 "parser.yy"
+ {
+ TokenPtr neg(new TokenNot());
+ ctx.expression.push_back(neg);
+ }
+#line 851 "parser.cc"
+ break;
+
+ case 7: // bool_expr: bool_expr "and" bool_expr
+#line 152 "parser.yy"
+ {
+ TokenPtr neg(new TokenAnd());
+ ctx.expression.push_back(neg);
+ }
+#line 860 "parser.cc"
+ break;
+
+ case 8: // bool_expr: bool_expr "or" bool_expr
+#line 157 "parser.yy"
+ {
+ TokenPtr neg(new TokenOr());
+ ctx.expression.push_back(neg);
+ }
+#line 869 "parser.cc"
+ break;
+
+ case 9: // bool_expr: string_expr "==" string_expr
+#line 162 "parser.yy"
+ {
+ TokenPtr eq(new TokenEqual());
+ ctx.expression.push_back(eq);
+ }
+#line 878 "parser.cc"
+ break;
+
+ case 10: // bool_expr: "option" "[" option_code "]" "." "exists"
+#line 167 "parser.yy"
+ {
+ TokenPtr opt(new TokenOption(yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ }
+#line 887 "parser.cc"
+ break;
+
+ case 11: // bool_expr: "option" "[" option_code "]" "." "option" "[" sub_option_code "]" "." "exists"
+#line 172 "parser.yy"
+ {
+ TokenPtr opt(new TokenSubOption(yystack_[8].value.as < uint16_t > (), yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ }
+#line 896 "parser.cc"
+ break;
+
+ case 12: // bool_expr: "relay4" "[" sub_option_code "]" "." "exists"
+#line 177 "parser.yy"
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V4:
+ {
+ TokenPtr opt(new TokenRelay4Option(yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V6:
+ // We will have relay6[123] for the DHCPv6.
+ // In a very distant future we'll possibly be able
+ // to mix both if we have DHCPv4-over-DHCPv6, so it
+ // has some sense to make it explicit whether we
+ // talk about DHCPv4 relay or DHCPv6 relay. However,
+ // for the time being relay4 can be used in DHCPv4
+ // only.
+ error(yystack_[5].location, "relay4 can only be used in DHCPv4.");
+ }
+ }
+#line 920 "parser.cc"
+ break;
+
+ case 13: // bool_expr: "relay6" "[" nest_level "]" "." "option" "[" sub_option_code "]" "." "exists"
+#line 197 "parser.yy"
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as < int8_t > (), yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V4:
+ // For now we only use relay6 in DHCPv6.
+ error(yystack_[10].location, "relay6 can only be used in DHCPv6.");
+ }
+ }
+#line 938 "parser.cc"
+ break;
+
+ case 14: // bool_expr: "vendor-class" "[" enterprise_id "]" "." "exists"
+#line 211 "parser.yy"
+ {
+ // Expression: vendor-class[1234].exists
+ //
+ // This token will find option 124 (DHCPv4) or 16 (DHCPv6),
+ // and will check if enterprise-id equals specified value.
+ TokenPtr exist(new TokenVendorClass(ctx.getUniverse(), yystack_[3].value.as < uint32_t > (), TokenOption::EXISTS));
+ ctx.expression.push_back(exist);
+ }
+#line 951 "parser.cc"
+ break;
+
+ case 15: // bool_expr: "vendor" "[" enterprise_id "]" "." "exists"
+#line 220 "parser.yy"
+ {
+ // Expression: vendor[1234].exists
+ //
+ // This token will find option 125 (DHCPv4) or 17 (DHCPv6),
+ // and will check if enterprise-id equals specified value.
+ TokenPtr exist(new TokenVendor(ctx.getUniverse(), yystack_[3].value.as < uint32_t > (), TokenOption::EXISTS));
+ ctx.expression.push_back(exist);
+ }
+#line 964 "parser.cc"
+ break;
+
+ case 16: // bool_expr: "vendor" "[" enterprise_id "]" "." "option" "[" sub_option_code "]" "." "exists"
+#line 229 "parser.yy"
+ {
+ // Expression vendor[1234].option[123].exists
+ //
+ // This token will check if specified vendor option
+ // exists, has specified enterprise-id and if has
+ // specified suboption.
+ TokenPtr exist(new TokenVendor(ctx.getUniverse(), yystack_[8].value.as < uint32_t > (), TokenOption::EXISTS, yystack_[3].value.as < uint16_t > ()));
+ ctx.expression.push_back(exist);
+ }
+#line 978 "parser.cc"
+ break;
+
+ case 17: // bool_expr: "member" "(" "constant string" ")"
+#line 239 "parser.yy"
+ {
+ // Expression member('foo')
+ //
+ // This token will check if the packet is a member of
+ // the specified client class.
+ // To avoid loops at evaluation only already defined and
+ // built-in classes are allowed.
+ std::string cc = yystack_[1].value.as < std::string > ();
+ if (!ctx.isClientClassDefined(cc)) {
+ error(yystack_[1].location, "Not defined client class '" + cc + "'");
+ }
+ TokenPtr member(new TokenMember(cc));
+ ctx.expression.push_back(member);
+ }
+#line 997 "parser.cc"
+ break;
+
+ case 18: // string_expr: "constant string"
+#line 256 "parser.yy"
+ {
+ TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ()));
+ ctx.expression.push_back(str);
+ }
+#line 1006 "parser.cc"
+ break;
+
+ case 19: // string_expr: "constant hexstring"
+#line 261 "parser.yy"
+ {
+ TokenPtr hex(new TokenHexString(yystack_[0].value.as < std::string > ()));
+ ctx.expression.push_back(hex);
+ }
+#line 1015 "parser.cc"
+ break;
+
+ case 20: // string_expr: "ip address"
+#line 266 "parser.yy"
+ {
+ TokenPtr ip(new TokenIpAddress(yystack_[0].value.as < std::string > ()));
+ ctx.expression.push_back(ip);
+ }
+#line 1024 "parser.cc"
+ break;
+
+ case 21: // string_expr: "option" "[" option_code "]" "." option_repr_type
+#line 271 "parser.yy"
+ {
+ TokenPtr opt(new TokenOption(yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ()));
+ ctx.expression.push_back(opt);
+ }
+#line 1033 "parser.cc"
+ break;
+
+ case 22: // string_expr: "option" "[" option_code "]" "." "option" "[" sub_option_code "]" "." option_repr_type
+#line 276 "parser.yy"
+ {
+ TokenPtr opt(new TokenSubOption(yystack_[8].value.as < uint16_t > (), yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ()));
+ ctx.expression.push_back(opt);
+ }
+#line 1042 "parser.cc"
+ break;
+
+ case 23: // string_expr: "relay4" "[" sub_option_code "]" "." option_repr_type
+#line 281 "parser.yy"
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V4:
+ {
+ TokenPtr opt(new TokenRelay4Option(yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ()));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V6:
+ // We will have relay6[123] for the DHCPv6.
+ // In a very distant future we'll possibly be able
+ // to mix both if we have DHCPv4-over-DHCPv6, so it
+ // has some sense to make it explicit whether we
+ // talk about DHCPv4 relay or DHCPv6 relay. However,
+ // for the time being relay4 can be used in DHCPv4
+ // only.
+ error(yystack_[5].location, "relay4 can only be used in DHCPv4.");
+ }
+ }
+#line 1066 "parser.cc"
+ break;
+
+ case 24: // string_expr: "relay6" "[" nest_level "]" "." "option" "[" sub_option_code "]" "." option_repr_type
+#line 302 "parser.yy"
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as < int8_t > (), yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ()));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V4:
+ // For now we only use relay6 in DHCPv6.
+ error(yystack_[10].location, "relay6 can only be used in DHCPv6.");
+ }
+ }
+#line 1084 "parser.cc"
+ break;
+
+ case 25: // string_expr: "pkt" "." pkt_metadata
+#line 317 "parser.yy"
+ {
+ TokenPtr pkt_metadata(new TokenPkt(yystack_[0].value.as < TokenPkt::MetadataType > ()));
+ ctx.expression.push_back(pkt_metadata);
+ }
+#line 1093 "parser.cc"
+ break;
+
+ case 26: // string_expr: "pkt4" "." pkt4_field
+#line 322 "parser.yy"
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V4:
+ {
+ TokenPtr pkt4_field(new TokenPkt4(yystack_[0].value.as < TokenPkt4::FieldType > ()));
+ ctx.expression.push_back(pkt4_field);
+ break;
+ }
+ case Option::V6:
+ // For now we only use pkt4 in DHCPv4.
+ error(yystack_[2].location, "pkt4 can only be used in DHCPv4.");
+ }
+ }
+#line 1111 "parser.cc"
+ break;
+
+ case 27: // string_expr: "pkt6" "." pkt6_field
+#line 336 "parser.yy"
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr pkt6_field(new TokenPkt6(yystack_[0].value.as < TokenPkt6::FieldType > ()));
+ ctx.expression.push_back(pkt6_field);
+ break;
+ }
+ case Option::V4:
+ // For now we only use pkt6 in DHCPv6.
+ error(yystack_[2].location, "pkt6 can only be used in DHCPv6.");
+ }
+ }
+#line 1129 "parser.cc"
+ break;
+
+ case 28: // string_expr: "relay6" "[" nest_level "]" "." relay6_field
+#line 350 "parser.yy"
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr relay6field(new TokenRelay6Field(yystack_[3].value.as < int8_t > (), yystack_[0].value.as < TokenRelay6Field::FieldType > ()));
+ ctx.expression.push_back(relay6field);
+ break;
+ }
+ case Option::V4:
+ // For now we only use relay6 in DHCPv6.
+ error(yystack_[5].location, "relay6 can only be used in DHCPv6.");
+ }
+ }
+#line 1147 "parser.cc"
+ break;
+
+ case 29: // string_expr: "substring" "(" string_expr "," start_expr "," length_expr ")"
+#line 365 "parser.yy"
+ {
+ TokenPtr sub(new TokenSubstring());
+ ctx.expression.push_back(sub);
+ }
+#line 1156 "parser.cc"
+ break;
+
+ case 30: // string_expr: "split" "(" string_expr "," string_expr "," int_expr ")"
+#line 370 "parser.yy"
+ {
+ TokenPtr split(new TokenSplit());
+ ctx.expression.push_back(split);
+ }
+#line 1165 "parser.cc"
+ break;
+
+ case 31: // string_expr: "concat" "(" string_expr "," string_expr ")"
+#line 375 "parser.yy"
+ {
+ TokenPtr conc(new TokenConcat());
+ ctx.expression.push_back(conc);
+ }
+#line 1174 "parser.cc"
+ break;
+
+ case 32: // string_expr: string_expr "+" string_expr
+#line 380 "parser.yy"
+ {
+ TokenPtr conc(new TokenConcat());
+ ctx.expression.push_back(conc);
+ }
+#line 1183 "parser.cc"
+ break;
+
+ case 33: // string_expr: "ifelse" "(" bool_expr "," string_expr "," string_expr ")"
+#line 385 "parser.yy"
+ {
+ TokenPtr cond(new TokenIfElse());
+ ctx.expression.push_back(cond);
+ }
+#line 1192 "parser.cc"
+ break;
+
+ case 34: // string_expr: "hexstring" "(" string_expr "," string_expr ")"
+#line 390 "parser.yy"
+ {
+ TokenPtr tohex(new TokenToHexString());
+ ctx.expression.push_back(tohex);
+ }
+#line 1201 "parser.cc"
+ break;
+
+ case 35: // string_expr: "addrtotext" "(" string_expr ")"
+#line 395 "parser.yy"
+ {
+ TokenPtr addrtotext(new TokenIpAddressToText());
+ ctx.expression.push_back(addrtotext);
+ }
+#line 1210 "parser.cc"
+ break;
+
+ case 36: // string_expr: "int8totext" "(" string_expr ")"
+#line 400 "parser.yy"
+ {
+ TokenPtr int8totext(new TokenInt8ToText());
+ ctx.expression.push_back(int8totext);
+ }
+#line 1219 "parser.cc"
+ break;
+
+ case 37: // string_expr: "int16totext" "(" string_expr ")"
+#line 405 "parser.yy"
+ {
+ TokenPtr int16totext(new TokenInt16ToText());
+ ctx.expression.push_back(int16totext);
+ }
+#line 1228 "parser.cc"
+ break;
+
+ case 38: // string_expr: "int32totext" "(" string_expr ")"
+#line 410 "parser.yy"
+ {
+ TokenPtr int32totext(new TokenInt32ToText());
+ ctx.expression.push_back(int32totext);
+ }
+#line 1237 "parser.cc"
+ break;
+
+ case 39: // string_expr: "uint8totext" "(" string_expr ")"
+#line 415 "parser.yy"
+ {
+ TokenPtr uint8totext(new TokenUInt8ToText());
+ ctx.expression.push_back(uint8totext);
+ }
+#line 1246 "parser.cc"
+ break;
+
+ case 40: // string_expr: "uint16totext" "(" string_expr ")"
+#line 420 "parser.yy"
+ {
+ TokenPtr uint16totext(new TokenUInt16ToText());
+ ctx.expression.push_back(uint16totext);
+ }
+#line 1255 "parser.cc"
+ break;
+
+ case 41: // string_expr: "uint32totext" "(" string_expr ")"
+#line 425 "parser.yy"
+ {
+ TokenPtr uint32totext(new TokenUInt32ToText());
+ ctx.expression.push_back(uint32totext);
+ }
+#line 1264 "parser.cc"
+ break;
+
+ case 42: // string_expr: "vendor" "." "enterprise"
+#line 430 "parser.yy"
+ {
+ // expression: vendor.enterprise
+ //
+ // This token will return enterprise-id number of
+ // received vendor option.
+ TokenPtr vendor(new TokenVendor(ctx.getUniverse(), 0, TokenVendor::ENTERPRISE_ID));
+ ctx.expression.push_back(vendor);
+ }
+#line 1277 "parser.cc"
+ break;
+
+ case 43: // string_expr: "vendor-class" "." "enterprise"
+#line 439 "parser.yy"
+ {
+ // expression: vendor-class.enterprise
+ //
+ // This token will return enterprise-id number of
+ // received vendor class option.
+ TokenPtr vendor(new TokenVendorClass(ctx.getUniverse(), 0,
+ TokenVendor::ENTERPRISE_ID));
+ ctx.expression.push_back(vendor);
+ }
+#line 1291 "parser.cc"
+ break;
+
+ case 44: // string_expr: "vendor" "[" enterprise_id "]" "." "option" "[" sub_option_code "]" "." option_repr_type
+#line 449 "parser.yy"
+ {
+ // This token will search for vendor option with
+ // specified enterprise-id. If found, will search
+ // for specified suboption and finally will return
+ // its content.
+ TokenPtr opt(new TokenVendor(ctx.getUniverse(), yystack_[8].value.as < uint32_t > (), yystack_[0].value.as < TokenOption::RepresentationType > (), yystack_[3].value.as < uint16_t > ()));
+ ctx.expression.push_back(opt);
+ }
+#line 1304 "parser.cc"
+ break;
+
+ case 45: // string_expr: "vendor-class" "[" enterprise_id "]" "." "data"
+#line 458 "parser.yy"
+ {
+ // expression: vendor-class[1234].data
+ //
+ // Vendor class option does not have suboptions,
+ // but chunks of data (typically 1, but the option
+ // structure allows multiple of them). If chunk
+ // offset is not specified, we assume the first (0th)
+ // is requested.
+ TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), yystack_[3].value.as < uint32_t > (),
+ TokenVendor::DATA, 0));
+ ctx.expression.push_back(vendor_class);
+ }
+#line 1321 "parser.cc"
+ break;
+
+ case 46: // string_expr: "vendor-class" "[" enterprise_id "]" "." "data" "[" "integer" "]"
+#line 471 "parser.yy"
+ {
+ // expression: vendor-class[1234].data[5]
+ //
+ // Vendor class option does not have suboptions,
+ // but chunks of data (typically 1, but the option
+ // structure allows multiple of them). This syntax
+ // specifies which data chunk (tuple) we want.
+ uint8_t index = ctx.convertUint8(yystack_[1].value.as < std::string > (), yystack_[1].location);
+ TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), yystack_[6].value.as < uint32_t > (),
+ TokenVendor::DATA, index));
+ ctx.expression.push_back(vendor_class);
+ }
+#line 1338 "parser.cc"
+ break;
+
+ case 47: // string_expr: integer_expr
+#line 484 "parser.yy"
+ {
+ TokenPtr integer(new TokenInteger(yystack_[0].value.as < uint32_t > ()));
+ ctx.expression.push_back(integer);
+ }
+#line 1347 "parser.cc"
+ break;
+
+ case 49: // integer_expr: "integer"
+#line 492 "parser.yy"
+ {
+ yylhs.value.as < uint32_t > () = ctx.convertUint32(yystack_[0].value.as < std::string > (), yystack_[0].location);
+ }
+#line 1355 "parser.cc"
+ break;
+
+ case 50: // option_code: "integer"
+#line 498 "parser.yy"
+ {
+ yylhs.value.as < uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as < std::string > (), yystack_[0].location);
+ }
+#line 1363 "parser.cc"
+ break;
+
+ case 51: // option_code: "option name"
+#line 502 "parser.yy"
+ {
+ yylhs.value.as < uint16_t > () = ctx.convertOptionName(yystack_[0].value.as < std::string > (), yystack_[0].location);
+ }
+#line 1371 "parser.cc"
+ break;
+
+ case 52: // sub_option_code: "integer"
+#line 508 "parser.yy"
+ {
+ yylhs.value.as < uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as < std::string > (), yystack_[0].location);
+ }
+#line 1379 "parser.cc"
+ break;
+
+ case 53: // option_repr_type: "text"
+#line 514 "parser.yy"
+ {
+ yylhs.value.as < TokenOption::RepresentationType > () = TokenOption::TEXTUAL;
+ }
+#line 1387 "parser.cc"
+ break;
+
+ case 54: // option_repr_type: "hex"
+#line 518 "parser.yy"
+ {
+ yylhs.value.as < TokenOption::RepresentationType > () = TokenOption::HEXADECIMAL;
+ }
+#line 1395 "parser.cc"
+ break;
+
+ case 55: // nest_level: "integer"
+#line 524 "parser.yy"
+ {
+ yylhs.value.as < int8_t > () = ctx.convertNestLevelNumber(yystack_[0].value.as < std::string > (), yystack_[0].location);
+ }
+#line 1403 "parser.cc"
+ break;
+
+ case 56: // pkt_metadata: "iface"
+#line 533 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::IFACE;
+ }
+#line 1411 "parser.cc"
+ break;
+
+ case 57: // pkt_metadata: "src"
+#line 537 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::SRC;
+ }
+#line 1419 "parser.cc"
+ break;
+
+ case 58: // pkt_metadata: "dst"
+#line 541 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::DST;
+ }
+#line 1427 "parser.cc"
+ break;
+
+ case 59: // pkt_metadata: "len"
+#line 545 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::LEN;
+ }
+#line 1435 "parser.cc"
+ break;
+
+ case 60: // enterprise_id: "integer"
+#line 551 "parser.yy"
+ {
+ yylhs.value.as < uint32_t > () = ctx.convertUint32(yystack_[0].value.as < std::string > (), yystack_[0].location);
+ }
+#line 1443 "parser.cc"
+ break;
+
+ case 61: // enterprise_id: "*"
+#line 555 "parser.yy"
+ {
+ yylhs.value.as < uint32_t > () = 0;
+ }
+#line 1451 "parser.cc"
+ break;
+
+ case 62: // pkt4_field: "mac"
+#line 561 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::CHADDR;
+ }
+#line 1459 "parser.cc"
+ break;
+
+ case 63: // pkt4_field: "hlen"
+#line 565 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::HLEN;
+ }
+#line 1467 "parser.cc"
+ break;
+
+ case 64: // pkt4_field: "htype"
+#line 569 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::HTYPE;
+ }
+#line 1475 "parser.cc"
+ break;
+
+ case 65: // pkt4_field: "ciaddr"
+#line 573 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::CIADDR;
+ }
+#line 1483 "parser.cc"
+ break;
+
+ case 66: // pkt4_field: "giaddr"
+#line 577 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::GIADDR;
+ }
+#line 1491 "parser.cc"
+ break;
+
+ case 67: // pkt4_field: "yiaddr"
+#line 581 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::YIADDR;
+ }
+#line 1499 "parser.cc"
+ break;
+
+ case 68: // pkt4_field: "siaddr"
+#line 585 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::SIADDR;
+ }
+#line 1507 "parser.cc"
+ break;
+
+ case 69: // pkt4_field: "msgtype"
+#line 589 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::MSGTYPE;
+ }
+#line 1515 "parser.cc"
+ break;
+
+ case 70: // pkt4_field: "transid"
+#line 593 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::TRANSID;
+ }
+#line 1523 "parser.cc"
+ break;
+
+ case 71: // pkt6_field: "msgtype"
+#line 599 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt6::FieldType > () = TokenPkt6::MSGTYPE;
+ }
+#line 1531 "parser.cc"
+ break;
+
+ case 72: // pkt6_field: "transid"
+#line 603 "parser.yy"
+ {
+ yylhs.value.as < TokenPkt6::FieldType > () = TokenPkt6::TRANSID;
+ }
+#line 1539 "parser.cc"
+ break;
+
+ case 73: // relay6_field: "peeraddr"
+#line 609 "parser.yy"
+ {
+ yylhs.value.as < TokenRelay6Field::FieldType > () = TokenRelay6Field::PEERADDR;
+ }
+#line 1547 "parser.cc"
+ break;
+
+ case 74: // relay6_field: "linkaddr"
+#line 613 "parser.yy"
+ {
+ yylhs.value.as < TokenRelay6Field::FieldType > () = TokenRelay6Field::LINKADDR;
+ }
+#line 1555 "parser.cc"
+ break;
+
+ case 75: // start_expr: "integer"
+#line 619 "parser.yy"
+ {
+ TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ()));
+ ctx.expression.push_back(str);
+ }
+#line 1564 "parser.cc"
+ break;
+
+ case 76: // length_expr: "integer"
+#line 626 "parser.yy"
+ {
+ TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ()));
+ ctx.expression.push_back(str);
+ }
+#line 1573 "parser.cc"
+ break;
+
+ case 77: // length_expr: "all"
+#line 631 "parser.yy"
+ {
+ TokenPtr str(new TokenString("all"));
+ ctx.expression.push_back(str);
+ }
+#line 1582 "parser.cc"
+ break;
+
+ case 78: // int_expr: "integer"
+#line 637 "parser.yy"
+ {
+ TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ()));
+ ctx.expression.push_back(str);
+ }
+#line 1591 "parser.cc"
+ break;
+
+
+#line 1595 "parser.cc"
+
+ default:
+ break;
+ }
+ }
+#if YY_EXCEPTIONS
+ catch (const syntax_error& yyexc)
+ {
+ YYCDEBUG << "Caught exception: " << yyexc.what() << '\n';
+ error (yyexc);
+ YYERROR;
+ }
+#endif // YY_EXCEPTIONS
+ YY_SYMBOL_PRINT ("-> $$ =", yylhs);
+ yypop_ (yylen);
+ yylen = 0;
+
+ // Shift the result of the reduction.
+ yypush_ (YY_NULLPTR, YY_MOVE (yylhs));
+ }
+ goto yynewstate;
+
+
+ /*--------------------------------------.
+ | yyerrlab -- here on detecting error. |
+ `--------------------------------------*/
+ yyerrlab:
+ // If not already recovering from an error, report this error.
+ if (!yyerrstatus_)
+ {
+ ++yynerrs_;
+ context yyctx (*this, yyla);
+ std::string msg = yysyntax_error_ (yyctx);
+ error (yyla.location, YY_MOVE (msg));
+ }
+
+
+ yyerror_range[1].location = yyla.location;
+ if (yyerrstatus_ == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ // Return failure if at end of input.
+ if (yyla.kind () == symbol_kind::S_YYEOF)
+ YYABORT;
+ else if (!yyla.empty ())
+ {
+ yy_destroy_ ("Error: discarding", yyla);
+ yyla.clear ();
+ }
+ }
+
+ // Else will try to reuse lookahead token after shifting the error token.
+ goto yyerrlab1;
+
+
+ /*---------------------------------------------------.
+ | yyerrorlab -- error raised explicitly by YYERROR. |
+ `---------------------------------------------------*/
+ yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and
+ the label yyerrorlab therefore never appears in user code. */
+ if (false)
+ YYERROR;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ yypop_ (yylen);
+ yylen = 0;
+ YY_STACK_PRINT ();
+ goto yyerrlab1;
+
+
+ /*-------------------------------------------------------------.
+ | yyerrlab1 -- common code for both syntax error and YYERROR. |
+ `-------------------------------------------------------------*/
+ yyerrlab1:
+ yyerrstatus_ = 3; // Each real token shifted decrements this.
+ // Pop stack until we find a state that shifts the error token.
+ for (;;)
+ {
+ yyn = yypact_[+yystack_[0].state];
+ if (!yy_pact_value_is_default_ (yyn))
+ {
+ yyn += symbol_kind::S_YYerror;
+ if (0 <= yyn && yyn <= yylast_
+ && yycheck_[yyn] == symbol_kind::S_YYerror)
+ {
+ yyn = yytable_[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ // Pop the current state because it cannot handle the error token.
+ if (yystack_.size () == 1)
+ YYABORT;
+
+ yyerror_range[1].location = yystack_[0].location;
+ yy_destroy_ ("Error: popping", yystack_[0]);
+ yypop_ ();
+ YY_STACK_PRINT ();
+ }
+ {
+ stack_symbol_type error_token;
+
+ yyerror_range[2].location = yyla.location;
+ YYLLOC_DEFAULT (error_token.location, yyerror_range, 2);
+
+ // Shift the error token.
+ error_token.state = state_type (yyn);
+ yypush_ ("Shifting", YY_MOVE (error_token));
+ }
+ goto yynewstate;
+
+
+ /*-------------------------------------.
+ | yyacceptlab -- YYACCEPT comes here. |
+ `-------------------------------------*/
+ yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+
+ /*-----------------------------------.
+ | yyabortlab -- YYABORT comes here. |
+ `-----------------------------------*/
+ yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+
+ /*-----------------------------------------------------.
+ | yyreturn -- parsing is finished, return the result. |
+ `-----------------------------------------------------*/
+ yyreturn:
+ if (!yyla.empty ())
+ yy_destroy_ ("Cleanup: discarding lookahead", yyla);
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ yypop_ (yylen);
+ YY_STACK_PRINT ();
+ while (1 < yystack_.size ())
+ {
+ yy_destroy_ ("Cleanup: popping", yystack_[0]);
+ yypop_ ();
+ }
+
+ return yyresult;
+ }
+#if YY_EXCEPTIONS
+ catch (...)
+ {
+ YYCDEBUG << "Exception caught: cleaning lookahead and stack\n";
+ // Do not try to display the values of the reclaimed symbols,
+ // as their printers might throw an exception.
+ if (!yyla.empty ())
+ yy_destroy_ (YY_NULLPTR, yyla);
+
+ while (1 < yystack_.size ())
+ {
+ yy_destroy_ (YY_NULLPTR, yystack_[0]);
+ yypop_ ();
+ }
+ throw;
+ }
+#endif // YY_EXCEPTIONS
+ }
+
+ void
+ EvalParser::error (const syntax_error& yyexc)
+ {
+ error (yyexc.location, yyexc.what ());
+ }
+
+ /* Return YYSTR after stripping away unnecessary quotes and
+ backslashes, so that it's suitable for yyerror. The heuristic is
+ that double-quoting is unnecessary unless the string contains an
+ apostrophe, a comma, or backslash (other than backslash-backslash).
+ YYSTR is taken from yytname. */
+ std::string
+ EvalParser::yytnamerr_ (const char *yystr)
+ {
+ if (*yystr == '"')
+ {
+ std::string yyr;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ else
+ goto append;
+
+ append:
+ default:
+ yyr += *yyp;
+ break;
+
+ case '"':
+ return yyr;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ return yystr;
+ }
+
+ std::string
+ EvalParser::symbol_name (symbol_kind_type yysymbol)
+ {
+ return yytnamerr_ (yytname_[yysymbol]);
+ }
+
+
+
+ // EvalParser::context.
+ EvalParser::context::context (const EvalParser& yyparser, const symbol_type& yyla)
+ : yyparser_ (yyparser)
+ , yyla_ (yyla)
+ {}
+
+ int
+ EvalParser::context::expected_tokens (symbol_kind_type yyarg[], int yyargn) const
+ {
+ // Actual number of expected tokens
+ int yycount = 0;
+
+ const int yyn = yypact_[+yyparser_.yystack_[0].state];
+ if (!yy_pact_value_is_default_ (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ const int yyxbegin = yyn < 0 ? -yyn : 0;
+ // Stay within bounds of both yycheck and yytname.
+ const int yychecklim = yylast_ - yyn + 1;
+ const int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ for (int yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck_[yyx + yyn] == yyx && yyx != symbol_kind::S_YYerror
+ && !yy_table_value_is_error_ (yytable_[yyx + yyn]))
+ {
+ if (!yyarg)
+ ++yycount;
+ else if (yycount == yyargn)
+ return 0;
+ else
+ yyarg[yycount++] = YY_CAST (symbol_kind_type, yyx);
+ }
+ }
+
+ if (yyarg && yycount == 0 && 0 < yyargn)
+ yyarg[0] = symbol_kind::S_YYEMPTY;
+ return yycount;
+ }
+
+
+
+
+
+
+ int
+ EvalParser::yy_syntax_error_arguments_ (const context& yyctx,
+ symbol_kind_type yyarg[], int yyargn) const
+ {
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yyla) is
+ if this state is a consistent state with a default action.
+ Thus, detecting the absence of a lookahead is sufficient to
+ determine that there is no unexpected or expected token to
+ report. In that case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is
+ a consistent state with a default action. There might have
+ been a previous inconsistent state, consistent state with a
+ non-default action, or user semantic action that manipulated
+ yyla. (However, yyla is currently not documented for users.)
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+
+ if (!yyctx.lookahead ().empty ())
+ {
+ if (yyarg)
+ yyarg[0] = yyctx.token ();
+ int yyn = yyctx.expected_tokens (yyarg ? yyarg + 1 : yyarg, yyargn - 1);
+ return yyn + 1;
+ }
+ return 0;
+ }
+
+ // Generate an error message.
+ std::string
+ EvalParser::yysyntax_error_ (const context& yyctx) const
+ {
+ // Its maximum.
+ enum { YYARGS_MAX = 5 };
+ // Arguments of yyformat.
+ symbol_kind_type yyarg[YYARGS_MAX];
+ int yycount = yy_syntax_error_arguments_ (yyctx, yyarg, YYARGS_MAX);
+
+ char const* yyformat = YY_NULLPTR;
+ switch (yycount)
+ {
+#define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ default: // Avoid compiler warnings.
+ YYCASE_ (0, YY_("syntax error"));
+ YYCASE_ (1, YY_("syntax error, unexpected %s"));
+ YYCASE_ (2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_ (3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_ (4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_ (5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+#undef YYCASE_
+ }
+
+ std::string yyres;
+ // Argument number.
+ std::ptrdiff_t yyi = 0;
+ for (char const* yyp = yyformat; *yyp; ++yyp)
+ if (yyp[0] == '%' && yyp[1] == 's' && yyi < yycount)
+ {
+ yyres += symbol_name (yyarg[yyi++]);
+ ++yyp;
+ }
+ else
+ yyres += *yyp;
+ return yyres;
+ }
+
+
+ const short EvalParser::yypact_ninf_ = -156;
+
+ const signed char EvalParser::yytable_ninf_ = -1;
+
+ const short
+ EvalParser::yypact_[] =
+ {
+ 43, 109, 156, 20, 109, 109, 73, 94, 98, 90,
+ 79, 111, 133, 142, 157, 168, 177, 180, 185, 189,
+ 190, 192, 211, 219, 207, 112, 131, -156, -156, -156,
+ -156, -156, 135, 29, -156, 156, 210, 212, 213, 161,
+ 164, 187, -156, 104, 0, -156, -34, 169, 170, 172,
+ 82, 52, 156, 156, 156, 109, 156, 156, 156, 156,
+ 156, 156, 156, 156, 113, -39, 176, -39, 178, 109,
+ 109, 156, 156, 1, -34, 169, 170, -39, -39, -156,
+ -156, -156, -156, 217, -156, 220, -156, 221, 231, -156,
+ -156, -156, -156, -156, -156, -156, -156, -156, -156, -156,
+ -156, -156, -156, -156, 147, 150, 173, 10, 174, 2,
+ 3, 5, 6, 7, 14, 23, -156, -156, -156, -156,
+ -156, 222, -156, 223, -156, -156, 234, 187, -156, 225,
+ 226, 227, 228, 229, 230, 232, 233, -156, 186, 156,
+ 156, 156, 156, -156, -156, -156, -156, -156, -156, -156,
+ 235, 236, 237, 238, 239, 240, 241, 16, 4, 78,
+ -156, 214, 181, 25, 184, 26, 11, 77, 80, 188,
+ 81, 193, 250, 245, -156, -156, -156, -156, -156, -156,
+ 246, -156, -156, -156, -17, 202, -156, 156, -156, -156,
+ 248, 249, -156, 251, 252, 253, 169, 169, -156, -156,
+ 261, -156, 265, 28, 215, 169, 169, 169, 169, 254,
+ 255, -156, -156, -156, 256, 257, 258, 260, 262, 263,
+ 264, -156, 266, 267, 268, 269, 97, 106, 155, 188,
+ 188, 188, -156, -156, -156, -156, -156, -156
+ };
+
+ const signed char
+ EvalParser::yydefact_[] =
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 18, 49, 19,
+ 20, 2, 4, 0, 47, 0, 0, 0, 0, 0,
+ 0, 3, 1, 0, 0, 6, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 5,
+ 48, 50, 51, 0, 52, 0, 55, 0, 0, 56,
+ 57, 58, 59, 25, 62, 63, 64, 65, 66, 67,
+ 68, 69, 70, 26, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 71, 72, 27, 61,
+ 60, 0, 43, 0, 42, 7, 8, 9, 32, 0,
+ 0, 0, 0, 0, 0, 0, 0, 17, 0, 0,
+ 0, 0, 0, 35, 36, 37, 38, 39, 40, 41,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 75, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 53, 54, 10, 21, 12, 23,
+ 0, 73, 74, 28, 0, 0, 31, 0, 34, 14,
+ 45, 0, 15, 0, 0, 0, 0, 0, 77, 76,
+ 0, 78, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 29, 30, 33, 0, 0, 0, 0, 0, 0,
+ 0, 46, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 11, 22, 13, 24, 16, 44
+ };
+
+ const short
+ EvalParser::yypgoto_[] =
+ {
+ -156, -156, -156, 8, -2, -156, 203, -74, -155, 206,
+ -156, -29, -156, -156, -156, -156, -156, -156
+ };
+
+ const unsigned char
+ EvalParser::yydefgoto_[] =
+ {
+ 0, 3, 31, 32, 33, 34, 83, 85, 177, 87,
+ 93, 121, 103, 118, 183, 161, 200, 202
+ };
+
+ const unsigned char
+ EvalParser::yytable_[] =
+ {
+ 41, 130, 44, 179, 80, 80, 143, 144, 71, 145,
+ 146, 147, 43, 45, 179, 119, 69, 70, 148, 198,
+ 42, 120, 174, 175, 178, 173, 81, 149, 82, 186,
+ 188, 189, 213, 73, 174, 175, 176, 71, 123, 72,
+ 72, 72, 72, 199, 72, 72, 72, 141, 132, 133,
+ 104, 105, 106, 72, 108, 109, 110, 111, 112, 113,
+ 114, 115, 72, 107, 72, 72, 190, 72, 72, 127,
+ 128, 233, 235, 237, 233, 235, 237, 125, 126, 94,
+ 95, 96, 97, 98, 99, 100, 191, 180, 46, 193,
+ 194, 181, 182, 49, 181, 182, 50, 192, 174, 175,
+ 1, 2, 101, 102, 89, 90, 91, 92, 79, 47,
+ 69, 70, 4, 48, 5, 174, 175, 232, 6, 7,
+ 8, 9, 209, 210, 174, 175, 234, 65, 51, 66,
+ 10, 215, 216, 217, 218, 11, 52, 162, 163, 164,
+ 165, 69, 70, 12, 13, 53, 67, 14, 68, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 35,
+ 54, 25, 26, 116, 117, 36, 37, 38, 27, 28,
+ 29, 55, 30, 174, 175, 236, 77, 10, 66, 78,
+ 56, 68, 11, 57, 138, 203, 72, 139, 58, 72,
+ 12, 13, 59, 60, 14, 61, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 174, 175, 39, 40,
+ 140, 142, 72, 72, 62, 27, 28, 29, 185, 30,
+ 72, 187, 63, 72, 64, 74, 72, 75, 76, 84,
+ 86, 88, 122, 134, 124, 137, 135, 136, 150, 151,
+ 69, 152, 153, 154, 155, 156, 160, 157, 190, 158,
+ 159, 184, 166, 167, 168, 169, 170, 171, 172, 195,
+ 196, 197, 201, 204, 205, 211, 206, 207, 208, 212,
+ 219, 220, 221, 222, 223, 214, 224, 129, 225, 0,
+ 226, 227, 131, 228, 229, 230, 231
+ };
+
+ const short
+ EvalParser::yycheck_[] =
+ {
+ 2, 75, 4, 158, 4, 4, 4, 4, 8, 4,
+ 4, 4, 4, 5, 169, 54, 6, 7, 4, 36,
+ 0, 60, 18, 19, 20, 9, 60, 4, 62, 4,
+ 4, 20, 4, 35, 18, 19, 20, 8, 67, 39,
+ 39, 39, 39, 60, 39, 39, 39, 37, 77, 78,
+ 52, 53, 54, 39, 56, 57, 58, 59, 60, 61,
+ 62, 63, 39, 55, 39, 39, 55, 39, 39, 71,
+ 72, 226, 227, 228, 229, 230, 231, 69, 70, 27,
+ 28, 29, 30, 31, 32, 33, 9, 9, 15, 9,
+ 9, 13, 14, 3, 13, 14, 17, 20, 18, 19,
+ 57, 58, 50, 51, 22, 23, 24, 25, 4, 15,
+ 6, 7, 3, 15, 5, 18, 19, 20, 9, 10,
+ 11, 12, 196, 197, 18, 19, 20, 15, 17, 17,
+ 21, 205, 206, 207, 208, 26, 3, 139, 140, 141,
+ 142, 6, 7, 34, 35, 3, 15, 38, 17, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 3,
+ 3, 52, 53, 50, 51, 9, 10, 11, 59, 60,
+ 61, 3, 63, 18, 19, 20, 15, 21, 17, 15,
+ 3, 17, 26, 3, 37, 187, 39, 37, 3, 39,
+ 34, 35, 3, 3, 38, 3, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 18, 19, 52, 53,
+ 37, 37, 39, 39, 3, 59, 60, 61, 37, 63,
+ 39, 37, 3, 39, 17, 15, 39, 15, 15, 60,
+ 60, 59, 56, 16, 56, 4, 16, 16, 16, 16,
+ 6, 16, 16, 16, 16, 16, 60, 17, 55, 17,
+ 17, 37, 17, 17, 17, 17, 17, 17, 17, 9,
+ 15, 15, 60, 15, 15, 4, 15, 15, 15, 4,
+ 16, 16, 16, 16, 16, 60, 16, 74, 16, -1,
+ 17, 17, 76, 17, 17, 17, 17
+ };
+
+ const signed char
+ EvalParser::yystos_[] =
+ {
+ 0, 57, 58, 65, 3, 5, 9, 10, 11, 12,
+ 21, 26, 34, 35, 38, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 52, 53, 59, 60, 61,
+ 63, 66, 67, 68, 69, 3, 9, 10, 11, 52,
+ 53, 68, 0, 67, 68, 67, 15, 15, 15, 3,
+ 17, 17, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 17, 15, 17, 15, 17, 6,
+ 7, 8, 39, 68, 15, 15, 15, 15, 15, 4,
+ 4, 60, 62, 70, 60, 71, 60, 73, 59, 22,
+ 23, 24, 25, 74, 27, 28, 29, 30, 31, 32,
+ 33, 50, 51, 76, 68, 68, 68, 67, 68, 68,
+ 68, 68, 68, 68, 68, 68, 50, 51, 77, 54,
+ 60, 75, 56, 75, 56, 67, 67, 68, 68, 70,
+ 71, 73, 75, 75, 16, 16, 16, 4, 37, 37,
+ 37, 37, 37, 4, 4, 4, 4, 4, 4, 4,
+ 16, 16, 16, 16, 16, 16, 16, 17, 17, 17,
+ 60, 79, 68, 68, 68, 68, 17, 17, 17, 17,
+ 17, 17, 17, 9, 18, 19, 20, 72, 20, 72,
+ 9, 13, 14, 78, 37, 37, 4, 37, 4, 20,
+ 55, 9, 20, 9, 9, 9, 15, 15, 36, 60,
+ 80, 60, 81, 68, 15, 15, 15, 15, 15, 71,
+ 71, 4, 4, 4, 60, 71, 71, 71, 71, 16,
+ 16, 16, 16, 16, 16, 16, 17, 17, 17, 17,
+ 17, 17, 20, 72, 20, 72, 20, 72
+ };
+
+ const signed char
+ EvalParser::yyr1_[] =
+ {
+ 0, 64, 65, 65, 66, 67, 67, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 69,
+ 70, 70, 71, 72, 72, 73, 74, 74, 74, 74,
+ 75, 75, 76, 76, 76, 76, 76, 76, 76, 76,
+ 76, 77, 77, 78, 78, 79, 80, 80, 81
+ };
+
+ const signed char
+ EvalParser::yyr2_[] =
+ {
+ 0, 2, 2, 2, 1, 3, 2, 3, 3, 3,
+ 6, 11, 6, 11, 6, 6, 11, 4, 1, 1,
+ 1, 6, 11, 6, 11, 3, 3, 3, 6, 8,
+ 8, 6, 3, 8, 6, 4, 4, 4, 4, 4,
+ 4, 4, 3, 3, 11, 6, 9, 1, 3, 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
+ };
+
+
+#if EVALDEBUG || 1
+ // YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ // First, the terminals, then, starting at \a YYNTOKENS, nonterminals.
+ const char*
+ const EvalParser::yytname_[] =
+ {
+ "\"end of file\"", "error", "\"invalid token\"", "\"(\"", "\")\"",
+ "\"not\"", "\"and\"", "\"or\"", "\"==\"", "\"option\"", "\"relay4\"",
+ "\"relay6\"", "\"member\"", "\"peeraddr\"", "\"linkaddr\"", "\"[\"",
+ "\"]\"", "\".\"", "\"text\"", "\"hex\"", "\"exists\"", "\"pkt\"",
+ "\"iface\"", "\"src\"", "\"dst\"", "\"len\"", "\"pkt4\"", "\"mac\"",
+ "\"hlen\"", "\"htype\"", "\"ciaddr\"", "\"giaddr\"", "\"yiaddr\"",
+ "\"siaddr\"", "\"substring\"", "\"split\"", "\"all\"", "\",\"",
+ "\"concat\"", "\"+\"", "\"ifelse\"", "\"hexstring\"", "\"addrtotext\"",
+ "\"int8totext\"", "\"int16totext\"", "\"int32totext\"",
+ "\"uint8totext\"", "\"uint16totext\"", "\"uint32totext\"", "\"pkt6\"",
+ "\"msgtype\"", "\"transid\"", "\"vendor-class\"", "\"vendor\"", "\"*\"",
+ "\"data\"", "\"enterprise\"", "\"top-level bool\"",
+ "\"top-level string\"", "\"constant string\"", "\"integer\"",
+ "\"constant hexstring\"", "\"option name\"", "\"ip address\"", "$accept",
+ "start", "expression", "bool_expr", "string_expr", "integer_expr",
+ "option_code", "sub_option_code", "option_repr_type", "nest_level",
+ "pkt_metadata", "enterprise_id", "pkt4_field", "pkt6_field",
+ "relay6_field", "start_expr", "length_expr", "int_expr", YY_NULLPTR
+ };
+#endif
+
+
+#if EVALDEBUG
+ const short
+ EvalParser::yyrline_[] =
+ {
+ 0, 136, 136, 137, 142, 145, 146, 151, 156, 161,
+ 166, 171, 176, 196, 210, 219, 228, 238, 255, 260,
+ 265, 270, 275, 280, 301, 316, 321, 335, 349, 364,
+ 369, 374, 379, 384, 389, 394, 399, 404, 409, 414,
+ 419, 424, 429, 438, 448, 457, 470, 483, 488, 491,
+ 497, 501, 507, 513, 517, 523, 532, 536, 540, 544,
+ 550, 554, 560, 564, 568, 572, 576, 580, 584, 588,
+ 592, 598, 602, 608, 612, 618, 625, 630, 636
+ };
+
+ void
+ EvalParser::yy_stack_print_ () const
+ {
+ *yycdebug_ << "Stack now";
+ for (stack_type::const_iterator
+ i = yystack_.begin (),
+ i_end = yystack_.end ();
+ i != i_end; ++i)
+ *yycdebug_ << ' ' << int (i->state);
+ *yycdebug_ << '\n';
+ }
+
+ void
+ EvalParser::yy_reduce_print_ (int yyrule) const
+ {
+ int yylno = yyrline_[yyrule];
+ int yynrhs = yyr2_[yyrule];
+ // Print the symbols being reduced, and their result.
+ *yycdebug_ << "Reducing stack by rule " << yyrule - 1
+ << " (line " << yylno << "):\n";
+ // The symbols being reduced.
+ for (int yyi = 0; yyi < yynrhs; yyi++)
+ YY_SYMBOL_PRINT (" $" << yyi + 1 << " =",
+ yystack_[(yynrhs) - (yyi + 1)]);
+ }
+#endif // EVALDEBUG
+
+
+#line 14 "parser.yy"
+} } // isc::eval
+#line 2219 "parser.cc"
+
+#line 643 "parser.yy"
+
+void
+isc::eval::EvalParser::error(const location_type& loc,
+ const std::string& what)
+{
+ ctx.error(loc, what);
+}
diff --git a/src/lib/eval/parser.h b/src/lib/eval/parser.h
new file mode 100644
index 0000000..597cf62
--- /dev/null
+++ b/src/lib/eval/parser.h
@@ -0,0 +1,2677 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Skeleton interface for Bison LALR(1) parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+
+/**
+ ** \file parser.h
+ ** Define the isc::eval::parser class.
+ */
+
+// C++ LALR(1) parser skeleton written by Akim Demaille.
+
+// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+// especially those whose name start with YY_ or yy_. They are
+// private implementation details that can be changed or removed.
+
+#ifndef YY_EVAL_PARSER_H_INCLUDED
+# define YY_EVAL_PARSER_H_INCLUDED
+// "%code requires" blocks.
+#line 17 "parser.yy"
+
+#include <string>
+#include <eval/token.h>
+#include <eval/eval_context_decl.h>
+#include <dhcp/option.h>
+#include <boost/lexical_cast.hpp>
+
+using namespace isc::dhcp;
+using namespace isc::eval;
+
+#line 60 "parser.h"
+
+# include <cassert>
+# include <cstdlib> // std::abort
+# include <iostream>
+# include <stdexcept>
+# include <string>
+# include <vector>
+
+#if defined __cplusplus
+# define YY_CPLUSPLUS __cplusplus
+#else
+# define YY_CPLUSPLUS 199711L
+#endif
+
+// Support move semantics when possible.
+#if 201103L <= YY_CPLUSPLUS
+# define YY_MOVE std::move
+# define YY_MOVE_OR_COPY move
+# define YY_MOVE_REF(Type) Type&&
+# define YY_RVREF(Type) Type&&
+# define YY_COPY(Type) Type
+#else
+# define YY_MOVE
+# define YY_MOVE_OR_COPY copy
+# define YY_MOVE_REF(Type) Type&
+# define YY_RVREF(Type) const Type&
+# define YY_COPY(Type) const Type&
+#endif
+
+// Support noexcept when possible.
+#if 201103L <= YY_CPLUSPLUS
+# define YY_NOEXCEPT noexcept
+# define YY_NOTHROW
+#else
+# define YY_NOEXCEPT
+# define YY_NOTHROW throw ()
+#endif
+
+// Support constexpr when possible.
+#if 201703 <= YY_CPLUSPLUS
+# define YY_CONSTEXPR constexpr
+#else
+# define YY_CONSTEXPR
+#endif
+# include "location.hh"
+#include <typeinfo>
+#ifndef EVAL_ASSERT
+# include <cassert>
+# define EVAL_ASSERT assert
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Debug traces. */
+#ifndef EVALDEBUG
+# if defined YYDEBUG
+#if YYDEBUG
+# define EVALDEBUG 1
+# else
+# define EVALDEBUG 0
+# endif
+# else /* ! defined YYDEBUG */
+# define EVALDEBUG 1
+# endif /* ! defined YYDEBUG */
+#endif /* ! defined EVALDEBUG */
+
+#line 14 "parser.yy"
+namespace isc { namespace eval {
+#line 209 "parser.h"
+
+
+
+
+ /// A Bison parser.
+ class EvalParser
+ {
+ public:
+#ifdef EVALSTYPE
+# ifdef __GNUC__
+# pragma GCC message "bison: do not #define EVALSTYPE in C++, use %define api.value.type"
+# endif
+ typedef EVALSTYPE value_type;
+#else
+ /// A buffer to store and retrieve objects.
+ ///
+ /// Sort of a variant, but does not keep track of the nature
+ /// of the stored data, since that knowledge is available
+ /// via the current parser state.
+ class value_type
+ {
+ public:
+ /// Type of *this.
+ typedef value_type self_type;
+
+ /// Empty construction.
+ value_type () YY_NOEXCEPT
+ : yyraw_ ()
+ , yytypeid_ (YY_NULLPTR)
+ {}
+
+ /// Construct and fill.
+ template <typename T>
+ value_type (YY_RVREF (T) t)
+ : yytypeid_ (&typeid (T))
+ {
+ EVAL_ASSERT (sizeof (T) <= size);
+ new (yyas_<T> ()) T (YY_MOVE (t));
+ }
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ value_type (const self_type&) = delete;
+ /// Non copyable.
+ self_type& operator= (const self_type&) = delete;
+#endif
+
+ /// Destruction, allowed only if empty.
+ ~value_type () YY_NOEXCEPT
+ {
+ EVAL_ASSERT (!yytypeid_);
+ }
+
+# if 201103L <= YY_CPLUSPLUS
+ /// Instantiate a \a T in here from \a t.
+ template <typename T, typename... U>
+ T&
+ emplace (U&&... u)
+ {
+ EVAL_ASSERT (!yytypeid_);
+ EVAL_ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T (std::forward <U>(u)...);
+ }
+# else
+ /// Instantiate an empty \a T in here.
+ template <typename T>
+ T&
+ emplace ()
+ {
+ EVAL_ASSERT (!yytypeid_);
+ EVAL_ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T ();
+ }
+
+ /// Instantiate a \a T in here from \a t.
+ template <typename T>
+ T&
+ emplace (const T& t)
+ {
+ EVAL_ASSERT (!yytypeid_);
+ EVAL_ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T (t);
+ }
+# endif
+
+ /// Instantiate an empty \a T in here.
+ /// Obsolete, use emplace.
+ template <typename T>
+ T&
+ build ()
+ {
+ return emplace<T> ();
+ }
+
+ /// Instantiate a \a T in here from \a t.
+ /// Obsolete, use emplace.
+ template <typename T>
+ T&
+ build (const T& t)
+ {
+ return emplace<T> (t);
+ }
+
+ /// Accessor to a built \a T.
+ template <typename T>
+ T&
+ as () YY_NOEXCEPT
+ {
+ EVAL_ASSERT (yytypeid_);
+ EVAL_ASSERT (*yytypeid_ == typeid (T));
+ EVAL_ASSERT (sizeof (T) <= size);
+ return *yyas_<T> ();
+ }
+
+ /// Const accessor to a built \a T (for %printer).
+ template <typename T>
+ const T&
+ as () const YY_NOEXCEPT
+ {
+ EVAL_ASSERT (yytypeid_);
+ EVAL_ASSERT (*yytypeid_ == typeid (T));
+ EVAL_ASSERT (sizeof (T) <= size);
+ return *yyas_<T> ();
+ }
+
+ /// Swap the content with \a that, of same type.
+ ///
+ /// Both variants must be built beforehand, because swapping the actual
+ /// data requires reading it (with as()), and this is not possible on
+ /// unconstructed variants: it would require some dynamic testing, which
+ /// should not be the variant's responsibility.
+ /// Swapping between built and (possibly) non-built is done with
+ /// self_type::move ().
+ template <typename T>
+ void
+ swap (self_type& that) YY_NOEXCEPT
+ {
+ EVAL_ASSERT (yytypeid_);
+ EVAL_ASSERT (*yytypeid_ == *that.yytypeid_);
+ std::swap (as<T> (), that.as<T> ());
+ }
+
+ /// Move the content of \a that to this.
+ ///
+ /// Destroys \a that.
+ template <typename T>
+ void
+ move (self_type& that)
+ {
+# if 201103L <= YY_CPLUSPLUS
+ emplace<T> (std::move (that.as<T> ()));
+# else
+ emplace<T> ();
+ swap<T> (that);
+# endif
+ that.destroy<T> ();
+ }
+
+# if 201103L <= YY_CPLUSPLUS
+ /// Move the content of \a that to this.
+ template <typename T>
+ void
+ move (self_type&& that)
+ {
+ emplace<T> (std::move (that.as<T> ()));
+ that.destroy<T> ();
+ }
+#endif
+
+ /// Copy the content of \a that to this.
+ template <typename T>
+ void
+ copy (const self_type& that)
+ {
+ emplace<T> (that.as<T> ());
+ }
+
+ /// Destroy the stored \a T.
+ template <typename T>
+ void
+ destroy ()
+ {
+ as<T> ().~T ();
+ yytypeid_ = YY_NULLPTR;
+ }
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ value_type (const self_type&);
+ /// Non copyable.
+ self_type& operator= (const self_type&);
+#endif
+
+ /// Accessor to raw memory as \a T.
+ template <typename T>
+ T*
+ yyas_ () YY_NOEXCEPT
+ {
+ void *yyp = yyraw_;
+ return static_cast<T*> (yyp);
+ }
+
+ /// Const accessor to raw memory as \a T.
+ template <typename T>
+ const T*
+ yyas_ () const YY_NOEXCEPT
+ {
+ const void *yyp = yyraw_;
+ return static_cast<const T*> (yyp);
+ }
+
+ /// An auxiliary type to compute the largest semantic type.
+ union union_type
+ {
+ // option_repr_type
+ char dummy1[sizeof (TokenOption::RepresentationType)];
+
+ // pkt4_field
+ char dummy2[sizeof (TokenPkt4::FieldType)];
+
+ // pkt6_field
+ char dummy3[sizeof (TokenPkt6::FieldType)];
+
+ // pkt_metadata
+ char dummy4[sizeof (TokenPkt::MetadataType)];
+
+ // relay6_field
+ char dummy5[sizeof (TokenRelay6Field::FieldType)];
+
+ // nest_level
+ char dummy6[sizeof (int8_t)];
+
+ // "constant string"
+ // "integer"
+ // "constant hexstring"
+ // "option name"
+ // "ip address"
+ char dummy7[sizeof (std::string)];
+
+ // option_code
+ // sub_option_code
+ char dummy8[sizeof (uint16_t)];
+
+ // integer_expr
+ // enterprise_id
+ char dummy9[sizeof (uint32_t)];
+ };
+
+ /// The size of the largest semantic type.
+ enum { size = sizeof (union_type) };
+
+ /// A buffer to store semantic values.
+ union
+ {
+ /// Strongest alignment constraints.
+ long double yyalign_me_;
+ /// A buffer large enough to store any of the semantic values.
+ char yyraw_[size];
+ };
+
+ /// Whether the content is built: if defined, the name of the stored type.
+ const std::type_info *yytypeid_;
+ };
+
+#endif
+ /// Backward compatibility (Bison 3.8).
+ typedef value_type semantic_type;
+
+ /// Symbol locations.
+ typedef location location_type;
+
+ /// Syntax errors thrown from user actions.
+ struct syntax_error : std::runtime_error
+ {
+ syntax_error (const location_type& l, const std::string& m)
+ : std::runtime_error (m)
+ , location (l)
+ {}
+
+ syntax_error (const syntax_error& s)
+ : std::runtime_error (s.what ())
+ , location (s.location)
+ {}
+
+ ~syntax_error () YY_NOEXCEPT YY_NOTHROW;
+
+ location_type location;
+ };
+
+ /// Token kinds.
+ struct token
+ {
+ enum token_kind_type
+ {
+ TOKEN_EVALEMPTY = -2,
+ TOKEN_END = 0, // "end of file"
+ TOKEN_EVALerror = 256, // error
+ TOKEN_EVALUNDEF = 257, // "invalid token"
+ TOKEN_LPAREN = 258, // "("
+ TOKEN_RPAREN = 259, // ")"
+ TOKEN_NOT = 260, // "not"
+ TOKEN_AND = 261, // "and"
+ TOKEN_OR = 262, // "or"
+ TOKEN_EQUAL = 263, // "=="
+ TOKEN_OPTION = 264, // "option"
+ TOKEN_RELAY4 = 265, // "relay4"
+ TOKEN_RELAY6 = 266, // "relay6"
+ TOKEN_MEMBER = 267, // "member"
+ TOKEN_PEERADDR = 268, // "peeraddr"
+ TOKEN_LINKADDR = 269, // "linkaddr"
+ TOKEN_LBRACKET = 270, // "["
+ TOKEN_RBRACKET = 271, // "]"
+ TOKEN_DOT = 272, // "."
+ TOKEN_TEXT = 273, // "text"
+ TOKEN_HEX = 274, // "hex"
+ TOKEN_EXISTS = 275, // "exists"
+ TOKEN_PKT = 276, // "pkt"
+ TOKEN_IFACE = 277, // "iface"
+ TOKEN_SRC = 278, // "src"
+ TOKEN_DST = 279, // "dst"
+ TOKEN_LEN = 280, // "len"
+ TOKEN_PKT4 = 281, // "pkt4"
+ TOKEN_CHADDR = 282, // "mac"
+ TOKEN_HLEN = 283, // "hlen"
+ TOKEN_HTYPE = 284, // "htype"
+ TOKEN_CIADDR = 285, // "ciaddr"
+ TOKEN_GIADDR = 286, // "giaddr"
+ TOKEN_YIADDR = 287, // "yiaddr"
+ TOKEN_SIADDR = 288, // "siaddr"
+ TOKEN_SUBSTRING = 289, // "substring"
+ TOKEN_SPLIT = 290, // "split"
+ TOKEN_ALL = 291, // "all"
+ TOKEN_COMA = 292, // ","
+ TOKEN_CONCAT = 293, // "concat"
+ TOKEN_PLUS = 294, // "+"
+ TOKEN_IFELSE = 295, // "ifelse"
+ TOKEN_TOHEXSTRING = 296, // "hexstring"
+ TOKEN_ADDRTOTEXT = 297, // "addrtotext"
+ TOKEN_INT8TOTEXT = 298, // "int8totext"
+ TOKEN_INT16TOTEXT = 299, // "int16totext"
+ TOKEN_INT32TOTEXT = 300, // "int32totext"
+ TOKEN_UINT8TOTEXT = 301, // "uint8totext"
+ TOKEN_UINT16TOTEXT = 302, // "uint16totext"
+ TOKEN_UINT32TOTEXT = 303, // "uint32totext"
+ TOKEN_PKT6 = 304, // "pkt6"
+ TOKEN_MSGTYPE = 305, // "msgtype"
+ TOKEN_TRANSID = 306, // "transid"
+ TOKEN_VENDOR_CLASS = 307, // "vendor-class"
+ TOKEN_VENDOR = 308, // "vendor"
+ TOKEN_ANY = 309, // "*"
+ TOKEN_DATA = 310, // "data"
+ TOKEN_ENTERPRISE = 311, // "enterprise"
+ TOKEN_TOPLEVEL_BOOL = 312, // "top-level bool"
+ TOKEN_TOPLEVEL_STRING = 313, // "top-level string"
+ TOKEN_STRING = 314, // "constant string"
+ TOKEN_INTEGER = 315, // "integer"
+ TOKEN_HEXSTRING = 316, // "constant hexstring"
+ TOKEN_OPTION_NAME = 317, // "option name"
+ TOKEN_IP_ADDRESS = 318 // "ip address"
+ };
+ /// Backward compatibility alias (Bison 3.6).
+ typedef token_kind_type yytokentype;
+ };
+
+ /// Token kind, as returned by yylex.
+ typedef token::token_kind_type token_kind_type;
+
+ /// Backward compatibility alias (Bison 3.6).
+ typedef token_kind_type token_type;
+
+ /// Symbol kinds.
+ struct symbol_kind
+ {
+ enum symbol_kind_type
+ {
+ YYNTOKENS = 64, ///< Number of tokens.
+ S_YYEMPTY = -2,
+ S_YYEOF = 0, // "end of file"
+ S_YYerror = 1, // error
+ S_YYUNDEF = 2, // "invalid token"
+ S_LPAREN = 3, // "("
+ S_RPAREN = 4, // ")"
+ S_NOT = 5, // "not"
+ S_AND = 6, // "and"
+ S_OR = 7, // "or"
+ S_EQUAL = 8, // "=="
+ S_OPTION = 9, // "option"
+ S_RELAY4 = 10, // "relay4"
+ S_RELAY6 = 11, // "relay6"
+ S_MEMBER = 12, // "member"
+ S_PEERADDR = 13, // "peeraddr"
+ S_LINKADDR = 14, // "linkaddr"
+ S_LBRACKET = 15, // "["
+ S_RBRACKET = 16, // "]"
+ S_DOT = 17, // "."
+ S_TEXT = 18, // "text"
+ S_HEX = 19, // "hex"
+ S_EXISTS = 20, // "exists"
+ S_PKT = 21, // "pkt"
+ S_IFACE = 22, // "iface"
+ S_SRC = 23, // "src"
+ S_DST = 24, // "dst"
+ S_LEN = 25, // "len"
+ S_PKT4 = 26, // "pkt4"
+ S_CHADDR = 27, // "mac"
+ S_HLEN = 28, // "hlen"
+ S_HTYPE = 29, // "htype"
+ S_CIADDR = 30, // "ciaddr"
+ S_GIADDR = 31, // "giaddr"
+ S_YIADDR = 32, // "yiaddr"
+ S_SIADDR = 33, // "siaddr"
+ S_SUBSTRING = 34, // "substring"
+ S_SPLIT = 35, // "split"
+ S_ALL = 36, // "all"
+ S_COMA = 37, // ","
+ S_CONCAT = 38, // "concat"
+ S_PLUS = 39, // "+"
+ S_IFELSE = 40, // "ifelse"
+ S_TOHEXSTRING = 41, // "hexstring"
+ S_ADDRTOTEXT = 42, // "addrtotext"
+ S_INT8TOTEXT = 43, // "int8totext"
+ S_INT16TOTEXT = 44, // "int16totext"
+ S_INT32TOTEXT = 45, // "int32totext"
+ S_UINT8TOTEXT = 46, // "uint8totext"
+ S_UINT16TOTEXT = 47, // "uint16totext"
+ S_UINT32TOTEXT = 48, // "uint32totext"
+ S_PKT6 = 49, // "pkt6"
+ S_MSGTYPE = 50, // "msgtype"
+ S_TRANSID = 51, // "transid"
+ S_VENDOR_CLASS = 52, // "vendor-class"
+ S_VENDOR = 53, // "vendor"
+ S_ANY = 54, // "*"
+ S_DATA = 55, // "data"
+ S_ENTERPRISE = 56, // "enterprise"
+ S_TOPLEVEL_BOOL = 57, // "top-level bool"
+ S_TOPLEVEL_STRING = 58, // "top-level string"
+ S_STRING = 59, // "constant string"
+ S_INTEGER = 60, // "integer"
+ S_HEXSTRING = 61, // "constant hexstring"
+ S_OPTION_NAME = 62, // "option name"
+ S_IP_ADDRESS = 63, // "ip address"
+ S_YYACCEPT = 64, // $accept
+ S_start = 65, // start
+ S_expression = 66, // expression
+ S_bool_expr = 67, // bool_expr
+ S_string_expr = 68, // string_expr
+ S_integer_expr = 69, // integer_expr
+ S_option_code = 70, // option_code
+ S_sub_option_code = 71, // sub_option_code
+ S_option_repr_type = 72, // option_repr_type
+ S_nest_level = 73, // nest_level
+ S_pkt_metadata = 74, // pkt_metadata
+ S_enterprise_id = 75, // enterprise_id
+ S_pkt4_field = 76, // pkt4_field
+ S_pkt6_field = 77, // pkt6_field
+ S_relay6_field = 78, // relay6_field
+ S_start_expr = 79, // start_expr
+ S_length_expr = 80, // length_expr
+ S_int_expr = 81 // int_expr
+ };
+ };
+
+ /// (Internal) symbol kind.
+ typedef symbol_kind::symbol_kind_type symbol_kind_type;
+
+ /// The number of tokens.
+ static const symbol_kind_type YYNTOKENS = symbol_kind::YYNTOKENS;
+
+ /// A complete symbol.
+ ///
+ /// Expects its Base type to provide access to the symbol kind
+ /// via kind ().
+ ///
+ /// Provide access to semantic value and location.
+ template <typename Base>
+ struct basic_symbol : Base
+ {
+ /// Alias to Base.
+ typedef Base super_type;
+
+ /// Default constructor.
+ basic_symbol () YY_NOEXCEPT
+ : value ()
+ , location ()
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Move constructor.
+ basic_symbol (basic_symbol&& that)
+ : Base (std::move (that))
+ , value ()
+ , location (std::move (that.location))
+ {
+ switch (this->kind ())
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.move< TokenOption::RepresentationType > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.move< TokenPkt4::FieldType > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.move< TokenPkt6::FieldType > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.move< TokenPkt::MetadataType > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.move< TokenRelay6Field::FieldType > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.move< int8_t > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.move< std::string > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.move< uint16_t > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.move< uint32_t > (std::move (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ }
+#endif
+
+ /// Copy constructor.
+ basic_symbol (const basic_symbol& that);
+
+ /// Constructors for typed symbols.
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, location_type&& l)
+ : Base (t)
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const location_type& l)
+ : Base (t)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, TokenOption::RepresentationType&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const TokenOption::RepresentationType& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, TokenPkt4::FieldType&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const TokenPkt4::FieldType& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, TokenPkt6::FieldType&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const TokenPkt6::FieldType& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, TokenPkt::MetadataType&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const TokenPkt::MetadataType& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, TokenRelay6Field::FieldType&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const TokenRelay6Field::FieldType& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, int8_t&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const int8_t& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, std::string&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const std::string& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, uint16_t&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const uint16_t& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, uint32_t&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const uint32_t& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+ /// Destroy the symbol.
+ ~basic_symbol ()
+ {
+ clear ();
+ }
+
+
+
+ /// Destroy contents, and record that is empty.
+ void clear () YY_NOEXCEPT
+ {
+ // User destructor.
+ symbol_kind_type yykind = this->kind ();
+ basic_symbol<Base>& yysym = *this;
+ (void) yysym;
+ switch (yykind)
+ {
+ default:
+ break;
+ }
+
+ // Value type destructor.
+switch (yykind)
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.template destroy< TokenOption::RepresentationType > ();
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.template destroy< TokenPkt4::FieldType > ();
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.template destroy< TokenPkt6::FieldType > ();
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.template destroy< TokenPkt::MetadataType > ();
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.template destroy< TokenRelay6Field::FieldType > ();
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.template destroy< int8_t > ();
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.template destroy< std::string > ();
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.template destroy< uint16_t > ();
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.template destroy< uint32_t > ();
+ break;
+
+ default:
+ break;
+ }
+
+ Base::clear ();
+ }
+
+ /// The user-facing name of this symbol.
+ std::string name () const YY_NOEXCEPT
+ {
+ return EvalParser::symbol_name (this->kind ());
+ }
+
+ /// Backward compatibility (Bison 3.6).
+ symbol_kind_type type_get () const YY_NOEXCEPT;
+
+ /// Whether empty.
+ bool empty () const YY_NOEXCEPT;
+
+ /// Destructive move, \a s is emptied into this.
+ void move (basic_symbol& s);
+
+ /// The semantic value.
+ value_type value;
+
+ /// The location.
+ location_type location;
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Assignment operator.
+ basic_symbol& operator= (const basic_symbol& that);
+#endif
+ };
+
+ /// Type access provider for token (enum) based symbols.
+ struct by_kind
+ {
+ /// The symbol kind as needed by the constructor.
+ typedef token_kind_type kind_type;
+
+ /// Default constructor.
+ by_kind () YY_NOEXCEPT;
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Move constructor.
+ by_kind (by_kind&& that) YY_NOEXCEPT;
+#endif
+
+ /// Copy constructor.
+ by_kind (const by_kind& that) YY_NOEXCEPT;
+
+ /// Constructor from (external) token numbers.
+ by_kind (kind_type t) YY_NOEXCEPT;
+
+
+
+ /// Record that this symbol is empty.
+ void clear () YY_NOEXCEPT;
+
+ /// Steal the symbol kind from \a that.
+ void move (by_kind& that);
+
+ /// The (internal) type number (corresponding to \a type).
+ /// \a empty when empty.
+ symbol_kind_type kind () const YY_NOEXCEPT;
+
+ /// Backward compatibility (Bison 3.6).
+ symbol_kind_type type_get () const YY_NOEXCEPT;
+
+ /// The symbol kind.
+ /// \a S_YYEMPTY when empty.
+ symbol_kind_type kind_;
+ };
+
+ /// Backward compatibility for a private implementation detail (Bison 3.6).
+ typedef by_kind by_type;
+
+ /// "External" symbols: returned by the scanner.
+ struct symbol_type : basic_symbol<by_kind>
+ {
+ /// Superclass.
+ typedef basic_symbol<by_kind> super_type;
+
+ /// Empty symbol.
+ symbol_type () YY_NOEXCEPT {}
+
+ /// Constructor for valueless symbols, and symbols from each type.
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, location_type l)
+ : super_type (token_kind_type (tok), std::move (l))
+#else
+ symbol_type (int tok, const location_type& l)
+ : super_type (token_kind_type (tok), l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ EVAL_ASSERT (tok == token::TOKEN_END
+ || (token::TOKEN_EVALerror <= tok && tok <= token::TOKEN_TOPLEVEL_STRING));
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, std::string v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const std::string& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ EVAL_ASSERT ((token::TOKEN_STRING <= tok && tok <= token::TOKEN_IP_ADDRESS));
+#endif
+ }
+ };
+
+ /// Build a parser object.
+ EvalParser (EvalContext& ctx_yyarg);
+ virtual ~EvalParser ();
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ EvalParser (const EvalParser&) = delete;
+ /// Non copyable.
+ EvalParser& operator= (const EvalParser&) = delete;
+#endif
+
+ /// Parse. An alias for parse ().
+ /// \returns 0 iff parsing succeeded.
+ int operator() ();
+
+ /// Parse.
+ /// \returns 0 iff parsing succeeded.
+ virtual int parse ();
+
+#if EVALDEBUG
+ /// The current debugging stream.
+ std::ostream& debug_stream () const YY_ATTRIBUTE_PURE;
+ /// Set the current debugging stream.
+ void set_debug_stream (std::ostream &);
+
+ /// Type for debugging levels.
+ typedef int debug_level_type;
+ /// The current debugging level.
+ debug_level_type debug_level () const YY_ATTRIBUTE_PURE;
+ /// Set the current debugging level.
+ void set_debug_level (debug_level_type l);
+#endif
+
+ /// Report a syntax error.
+ /// \param loc where the syntax error is found.
+ /// \param msg a description of the syntax error.
+ virtual void error (const location_type& loc, const std::string& msg);
+
+ /// Report a syntax error.
+ void error (const syntax_error& err);
+
+ /// The user-facing name of the symbol whose (internal) number is
+ /// YYSYMBOL. No bounds checking.
+ static std::string symbol_name (symbol_kind_type yysymbol);
+
+ // Implementation of make_symbol for each token kind.
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_END (location_type l)
+ {
+ return symbol_type (token::TOKEN_END, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_END (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_END, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_EVALerror (location_type l)
+ {
+ return symbol_type (token::TOKEN_EVALerror, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_EVALerror (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_EVALerror, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_EVALUNDEF (location_type l)
+ {
+ return symbol_type (token::TOKEN_EVALUNDEF, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_EVALUNDEF (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_EVALUNDEF, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LPAREN (location_type l)
+ {
+ return symbol_type (token::TOKEN_LPAREN, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LPAREN (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LPAREN, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RPAREN (location_type l)
+ {
+ return symbol_type (token::TOKEN_RPAREN, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RPAREN (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RPAREN, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NOT (location_type l)
+ {
+ return symbol_type (token::TOKEN_NOT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NOT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NOT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_AND (location_type l)
+ {
+ return symbol_type (token::TOKEN_AND, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_AND (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_AND, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OR (location_type l)
+ {
+ return symbol_type (token::TOKEN_OR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_EQUAL (location_type l)
+ {
+ return symbol_type (token::TOKEN_EQUAL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_EQUAL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_EQUAL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OPTION (location_type l)
+ {
+ return symbol_type (token::TOKEN_OPTION, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OPTION (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OPTION, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RELAY4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_RELAY4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RELAY4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RELAY4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RELAY6 (location_type l)
+ {
+ return symbol_type (token::TOKEN_RELAY6, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RELAY6 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RELAY6, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MEMBER (location_type l)
+ {
+ return symbol_type (token::TOKEN_MEMBER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MEMBER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MEMBER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PEERADDR (location_type l)
+ {
+ return symbol_type (token::TOKEN_PEERADDR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PEERADDR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PEERADDR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LINKADDR (location_type l)
+ {
+ return symbol_type (token::TOKEN_LINKADDR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LINKADDR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LINKADDR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LBRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_LBRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LBRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LBRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RBRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_RBRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RBRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RBRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DOT (location_type l)
+ {
+ return symbol_type (token::TOKEN_DOT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DOT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DOT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_TEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HEX (location_type l)
+ {
+ return symbol_type (token::TOKEN_HEX, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HEX (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HEX, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_EXISTS (location_type l)
+ {
+ return symbol_type (token::TOKEN_EXISTS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_EXISTS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_EXISTS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PKT (location_type l)
+ {
+ return symbol_type (token::TOKEN_PKT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PKT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PKT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_IFACE (location_type l)
+ {
+ return symbol_type (token::TOKEN_IFACE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_IFACE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_IFACE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SRC (location_type l)
+ {
+ return symbol_type (token::TOKEN_SRC, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SRC (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SRC, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DST (location_type l)
+ {
+ return symbol_type (token::TOKEN_DST, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DST (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DST, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LEN (location_type l)
+ {
+ return symbol_type (token::TOKEN_LEN, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LEN (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LEN, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PKT4 (location_type l)
+ {
+ return symbol_type (token::TOKEN_PKT4, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PKT4 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PKT4, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CHADDR (location_type l)
+ {
+ return symbol_type (token::TOKEN_CHADDR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CHADDR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CHADDR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HLEN (location_type l)
+ {
+ return symbol_type (token::TOKEN_HLEN, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HLEN (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HLEN, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HTYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_HTYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HTYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HTYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CIADDR (location_type l)
+ {
+ return symbol_type (token::TOKEN_CIADDR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CIADDR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CIADDR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_GIADDR (location_type l)
+ {
+ return symbol_type (token::TOKEN_GIADDR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_GIADDR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_GIADDR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_YIADDR (location_type l)
+ {
+ return symbol_type (token::TOKEN_YIADDR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_YIADDR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_YIADDR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SIADDR (location_type l)
+ {
+ return symbol_type (token::TOKEN_SIADDR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SIADDR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SIADDR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUBSTRING (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUBSTRING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUBSTRING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUBSTRING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SPLIT (location_type l)
+ {
+ return symbol_type (token::TOKEN_SPLIT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SPLIT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SPLIT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ALL (location_type l)
+ {
+ return symbol_type (token::TOKEN_ALL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ALL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ALL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COMA (location_type l)
+ {
+ return symbol_type (token::TOKEN_COMA, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COMA (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COMA, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CONCAT (location_type l)
+ {
+ return symbol_type (token::TOKEN_CONCAT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CONCAT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CONCAT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PLUS (location_type l)
+ {
+ return symbol_type (token::TOKEN_PLUS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PLUS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PLUS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_IFELSE (location_type l)
+ {
+ return symbol_type (token::TOKEN_IFELSE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_IFELSE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_IFELSE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TOHEXSTRING (location_type l)
+ {
+ return symbol_type (token::TOKEN_TOHEXSTRING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TOHEXSTRING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOHEXSTRING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ADDRTOTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_ADDRTOTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ADDRTOTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ADDRTOTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INT8TOTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_INT8TOTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INT8TOTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INT8TOTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INT16TOTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_INT16TOTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INT16TOTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INT16TOTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INT32TOTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_INT32TOTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INT32TOTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INT32TOTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_UINT8TOTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_UINT8TOTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_UINT8TOTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_UINT8TOTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_UINT16TOTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_UINT16TOTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_UINT16TOTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_UINT16TOTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_UINT32TOTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_UINT32TOTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_UINT32TOTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_UINT32TOTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PKT6 (location_type l)
+ {
+ return symbol_type (token::TOKEN_PKT6, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PKT6 (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PKT6, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MSGTYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_MSGTYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MSGTYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MSGTYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TRANSID (location_type l)
+ {
+ return symbol_type (token::TOKEN_TRANSID, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TRANSID (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TRANSID, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_VENDOR_CLASS (location_type l)
+ {
+ return symbol_type (token::TOKEN_VENDOR_CLASS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_VENDOR_CLASS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_VENDOR_CLASS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_VENDOR (location_type l)
+ {
+ return symbol_type (token::TOKEN_VENDOR, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_VENDOR (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_VENDOR, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ANY (location_type l)
+ {
+ return symbol_type (token::TOKEN_ANY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ANY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ANY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DATA (location_type l)
+ {
+ return symbol_type (token::TOKEN_DATA, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DATA (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DATA, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ENTERPRISE (location_type l)
+ {
+ return symbol_type (token::TOKEN_ENTERPRISE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ENTERPRISE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ENTERPRISE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TOPLEVEL_BOOL (location_type l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_BOOL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TOPLEVEL_BOOL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_BOOL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TOPLEVEL_STRING (location_type l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_STRING, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TOPLEVEL_STRING (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_STRING, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_STRING (std::string v, location_type l)
+ {
+ return symbol_type (token::TOKEN_STRING, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_STRING (const std::string& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_STRING, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INTEGER (std::string v, location_type l)
+ {
+ return symbol_type (token::TOKEN_INTEGER, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INTEGER (const std::string& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INTEGER, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HEXSTRING (std::string v, location_type l)
+ {
+ return symbol_type (token::TOKEN_HEXSTRING, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HEXSTRING (const std::string& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HEXSTRING, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OPTION_NAME (std::string v, location_type l)
+ {
+ return symbol_type (token::TOKEN_OPTION_NAME, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OPTION_NAME (const std::string& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OPTION_NAME, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_IP_ADDRESS (std::string v, location_type l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESS, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_IP_ADDRESS (const std::string& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESS, v, l);
+ }
+#endif
+
+
+ class context
+ {
+ public:
+ context (const EvalParser& yyparser, const symbol_type& yyla);
+ const symbol_type& lookahead () const YY_NOEXCEPT { return yyla_; }
+ symbol_kind_type token () const YY_NOEXCEPT { return yyla_.kind (); }
+ const location_type& location () const YY_NOEXCEPT { return yyla_.location; }
+
+ /// Put in YYARG at most YYARGN of the expected tokens, and return the
+ /// number of tokens stored in YYARG. If YYARG is null, return the
+ /// number of expected tokens (guaranteed to be less than YYNTOKENS).
+ int expected_tokens (symbol_kind_type yyarg[], int yyargn) const;
+
+ private:
+ const EvalParser& yyparser_;
+ const symbol_type& yyla_;
+ };
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ EvalParser (const EvalParser&);
+ /// Non copyable.
+ EvalParser& operator= (const EvalParser&);
+#endif
+
+
+ /// Stored state numbers (used for stacks).
+ typedef unsigned char state_type;
+
+ /// The arguments of the error message.
+ int yy_syntax_error_arguments_ (const context& yyctx,
+ symbol_kind_type yyarg[], int yyargn) const;
+
+ /// Generate an error message.
+ /// \param yyctx the context in which the error occurred.
+ virtual std::string yysyntax_error_ (const context& yyctx) const;
+ /// Compute post-reduction state.
+ /// \param yystate the current state
+ /// \param yysym the nonterminal to push on the stack
+ static state_type yy_lr_goto_state_ (state_type yystate, int yysym);
+
+ /// Whether the given \c yypact_ value indicates a defaulted state.
+ /// \param yyvalue the value to check
+ static bool yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT;
+
+ /// Whether the given \c yytable_ value indicates a syntax error.
+ /// \param yyvalue the value to check
+ static bool yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT;
+
+ static const short yypact_ninf_;
+ static const signed char yytable_ninf_;
+
+ /// Convert a scanner token kind \a t to a symbol kind.
+ /// In theory \a t should be a token_kind_type, but character literals
+ /// are valid, yet not members of the token_kind_type enum.
+ static symbol_kind_type yytranslate_ (int t) YY_NOEXCEPT;
+
+ /// Convert the symbol name \a n to a form suitable for a diagnostic.
+ static std::string yytnamerr_ (const char *yystr);
+
+ /// For a symbol, its name in clear.
+ static const char* const yytname_[];
+
+
+ // Tables.
+ // YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ // STATE-NUM.
+ static const short yypact_[];
+
+ // YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ // Performed when YYTABLE does not specify something else to do. Zero
+ // means the default is an error.
+ static const signed char yydefact_[];
+
+ // YYPGOTO[NTERM-NUM].
+ static const short yypgoto_[];
+
+ // YYDEFGOTO[NTERM-NUM].
+ static const unsigned char yydefgoto_[];
+
+ // YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ // positive, shift that token. If negative, reduce the rule whose
+ // number is the opposite. If YYTABLE_NINF, syntax error.
+ static const unsigned char yytable_[];
+
+ static const short yycheck_[];
+
+ // YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ // state STATE-NUM.
+ static const signed char yystos_[];
+
+ // YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM.
+ static const signed char yyr1_[];
+
+ // YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM.
+ static const signed char yyr2_[];
+
+
+#if EVALDEBUG
+ // YYRLINE[YYN] -- Source line where rule number YYN was defined.
+ static const short yyrline_[];
+ /// Report on the debug stream that the rule \a r is going to be reduced.
+ virtual void yy_reduce_print_ (int r) const;
+ /// Print the state stack on the debug stream.
+ virtual void yy_stack_print_ () const;
+
+ /// Debugging level.
+ int yydebug_;
+ /// Debug stream.
+ std::ostream* yycdebug_;
+
+ /// \brief Display a symbol kind, value and location.
+ /// \param yyo The output stream.
+ /// \param yysym The symbol.
+ template <typename Base>
+ void yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const;
+#endif
+
+ /// \brief Reclaim the memory associated to a symbol.
+ /// \param yymsg Why this token is reclaimed.
+ /// If null, print nothing.
+ /// \param yysym The symbol.
+ template <typename Base>
+ void yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const;
+
+ private:
+ /// Type access provider for state based symbols.
+ struct by_state
+ {
+ /// Default constructor.
+ by_state () YY_NOEXCEPT;
+
+ /// The symbol kind as needed by the constructor.
+ typedef state_type kind_type;
+
+ /// Constructor.
+ by_state (kind_type s) YY_NOEXCEPT;
+
+ /// Copy constructor.
+ by_state (const by_state& that) YY_NOEXCEPT;
+
+ /// Record that this symbol is empty.
+ void clear () YY_NOEXCEPT;
+
+ /// Steal the symbol kind from \a that.
+ void move (by_state& that);
+
+ /// The symbol kind (corresponding to \a state).
+ /// \a symbol_kind::S_YYEMPTY when empty.
+ symbol_kind_type kind () const YY_NOEXCEPT;
+
+ /// The state number used to denote an empty symbol.
+ /// We use the initial state, as it does not have a value.
+ enum { empty_state = 0 };
+
+ /// The state.
+ /// \a empty when empty.
+ state_type state;
+ };
+
+ /// "Internal" symbol: element of the stack.
+ struct stack_symbol_type : basic_symbol<by_state>
+ {
+ /// Superclass.
+ typedef basic_symbol<by_state> super_type;
+ /// Construct an empty symbol.
+ stack_symbol_type ();
+ /// Move or copy construction.
+ stack_symbol_type (YY_RVREF (stack_symbol_type) that);
+ /// Steal the contents from \a sym to build this.
+ stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) sym);
+#if YY_CPLUSPLUS < 201103L
+ /// Assignment, needed by push_back by some old implementations.
+ /// Moves the contents of that.
+ stack_symbol_type& operator= (stack_symbol_type& that);
+
+ /// Assignment, needed by push_back by other implementations.
+ /// Needed by some other old implementations.
+ stack_symbol_type& operator= (const stack_symbol_type& that);
+#endif
+ };
+
+ /// A stack with random access from its top.
+ template <typename T, typename S = std::vector<T> >
+ class stack
+ {
+ public:
+ // Hide our reversed order.
+ typedef typename S::iterator iterator;
+ typedef typename S::const_iterator const_iterator;
+ typedef typename S::size_type size_type;
+ typedef typename std::ptrdiff_t index_type;
+
+ stack (size_type n = 200) YY_NOEXCEPT
+ : seq_ (n)
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ stack (const stack&) = delete;
+ /// Non copyable.
+ stack& operator= (const stack&) = delete;
+#endif
+
+ /// Random access.
+ ///
+ /// Index 0 returns the topmost element.
+ const T&
+ operator[] (index_type i) const
+ {
+ return seq_[size_type (size () - 1 - i)];
+ }
+
+ /// Random access.
+ ///
+ /// Index 0 returns the topmost element.
+ T&
+ operator[] (index_type i)
+ {
+ return seq_[size_type (size () - 1 - i)];
+ }
+
+ /// Steal the contents of \a t.
+ ///
+ /// Close to move-semantics.
+ void
+ push (YY_MOVE_REF (T) t)
+ {
+ seq_.push_back (T ());
+ operator[] (0).move (t);
+ }
+
+ /// Pop elements from the stack.
+ void
+ pop (std::ptrdiff_t n = 1) YY_NOEXCEPT
+ {
+ for (; 0 < n; --n)
+ seq_.pop_back ();
+ }
+
+ /// Pop all elements from the stack.
+ void
+ clear () YY_NOEXCEPT
+ {
+ seq_.clear ();
+ }
+
+ /// Number of elements on the stack.
+ index_type
+ size () const YY_NOEXCEPT
+ {
+ return index_type (seq_.size ());
+ }
+
+ /// Iterator on top of the stack (going downwards).
+ const_iterator
+ begin () const YY_NOEXCEPT
+ {
+ return seq_.begin ();
+ }
+
+ /// Bottom of the stack.
+ const_iterator
+ end () const YY_NOEXCEPT
+ {
+ return seq_.end ();
+ }
+
+ /// Present a slice of the top of a stack.
+ class slice
+ {
+ public:
+ slice (const stack& stack, index_type range) YY_NOEXCEPT
+ : stack_ (stack)
+ , range_ (range)
+ {}
+
+ const T&
+ operator[] (index_type i) const
+ {
+ return stack_[range_ - i];
+ }
+
+ private:
+ const stack& stack_;
+ index_type range_;
+ };
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ stack (const stack&);
+ /// Non copyable.
+ stack& operator= (const stack&);
+#endif
+ /// The wrapped container.
+ S seq_;
+ };
+
+
+ /// Stack type.
+ typedef stack<stack_symbol_type> stack_type;
+
+ /// The stack.
+ stack_type yystack_;
+
+ /// Push a new state on the stack.
+ /// \param m a debug message to display
+ /// if null, no trace is output.
+ /// \param sym the symbol
+ /// \warning the contents of \a s.value is stolen.
+ void yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym);
+
+ /// Push a new look ahead token on the state on the stack.
+ /// \param m a debug message to display
+ /// if null, no trace is output.
+ /// \param s the state
+ /// \param sym the symbol (for its value and location).
+ /// \warning the contents of \a sym.value is stolen.
+ void yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym);
+
+ /// Pop \a n symbols from the stack.
+ void yypop_ (int n = 1) YY_NOEXCEPT;
+
+ /// Constants.
+ enum
+ {
+ yylast_ = 286, ///< Last index in yytable_.
+ yynnts_ = 18, ///< Number of nonterminal symbols.
+ yyfinal_ = 42 ///< Termination state number.
+ };
+
+
+ // User arguments.
+ EvalContext& ctx;
+
+ };
+
+ inline
+ EvalParser::symbol_kind_type
+ EvalParser::yytranslate_ (int t) YY_NOEXCEPT
+ {
+ // YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to
+ // TOKEN-NUM as returned by yylex.
+ static
+ const signed char
+ translate_table[] =
+ {
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 62, 63
+ };
+ // Last valid token kind.
+ const int code_max = 318;
+
+ if (t <= 0)
+ return symbol_kind::S_YYEOF;
+ else if (t <= code_max)
+ return static_cast <symbol_kind_type> (translate_table[t]);
+ else
+ return symbol_kind::S_YYUNDEF;
+ }
+
+ // basic_symbol.
+ template <typename Base>
+ EvalParser::basic_symbol<Base>::basic_symbol (const basic_symbol& that)
+ : Base (that)
+ , value ()
+ , location (that.location)
+ {
+ switch (this->kind ())
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.copy< TokenOption::RepresentationType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.copy< TokenPkt4::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.copy< TokenPkt6::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.copy< TokenPkt::MetadataType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.copy< TokenRelay6Field::FieldType > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.copy< int8_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.copy< std::string > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.copy< uint16_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.copy< uint32_t > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ }
+
+
+
+
+ template <typename Base>
+ EvalParser::symbol_kind_type
+ EvalParser::basic_symbol<Base>::type_get () const YY_NOEXCEPT
+ {
+ return this->kind ();
+ }
+
+
+ template <typename Base>
+ bool
+ EvalParser::basic_symbol<Base>::empty () const YY_NOEXCEPT
+ {
+ return this->kind () == symbol_kind::S_YYEMPTY;
+ }
+
+ template <typename Base>
+ void
+ EvalParser::basic_symbol<Base>::move (basic_symbol& s)
+ {
+ super_type::move (s);
+ switch (this->kind ())
+ {
+ case symbol_kind::S_option_repr_type: // option_repr_type
+ value.move< TokenOption::RepresentationType > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_pkt4_field: // pkt4_field
+ value.move< TokenPkt4::FieldType > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_pkt6_field: // pkt6_field
+ value.move< TokenPkt6::FieldType > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_pkt_metadata: // pkt_metadata
+ value.move< TokenPkt::MetadataType > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_relay6_field: // relay6_field
+ value.move< TokenRelay6Field::FieldType > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_nest_level: // nest_level
+ value.move< int8_t > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ case symbol_kind::S_INTEGER: // "integer"
+ case symbol_kind::S_HEXSTRING: // "constant hexstring"
+ case symbol_kind::S_OPTION_NAME: // "option name"
+ case symbol_kind::S_IP_ADDRESS: // "ip address"
+ value.move< std::string > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_option_code: // option_code
+ case symbol_kind::S_sub_option_code: // sub_option_code
+ value.move< uint16_t > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_integer_expr: // integer_expr
+ case symbol_kind::S_enterprise_id: // enterprise_id
+ value.move< uint32_t > (YY_MOVE (s.value));
+ break;
+
+ default:
+ break;
+ }
+
+ location = YY_MOVE (s.location);
+ }
+
+ // by_kind.
+ inline
+ EvalParser::by_kind::by_kind () YY_NOEXCEPT
+ : kind_ (symbol_kind::S_YYEMPTY)
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ inline
+ EvalParser::by_kind::by_kind (by_kind&& that) YY_NOEXCEPT
+ : kind_ (that.kind_)
+ {
+ that.clear ();
+ }
+#endif
+
+ inline
+ EvalParser::by_kind::by_kind (const by_kind& that) YY_NOEXCEPT
+ : kind_ (that.kind_)
+ {}
+
+ inline
+ EvalParser::by_kind::by_kind (token_kind_type t) YY_NOEXCEPT
+ : kind_ (yytranslate_ (t))
+ {}
+
+
+
+ inline
+ void
+ EvalParser::by_kind::clear () YY_NOEXCEPT
+ {
+ kind_ = symbol_kind::S_YYEMPTY;
+ }
+
+ inline
+ void
+ EvalParser::by_kind::move (by_kind& that)
+ {
+ kind_ = that.kind_;
+ that.clear ();
+ }
+
+ inline
+ EvalParser::symbol_kind_type
+ EvalParser::by_kind::kind () const YY_NOEXCEPT
+ {
+ return kind_;
+ }
+
+
+ inline
+ EvalParser::symbol_kind_type
+ EvalParser::by_kind::type_get () const YY_NOEXCEPT
+ {
+ return this->kind ();
+ }
+
+
+#line 14 "parser.yy"
+} } // isc::eval
+#line 2673 "parser.h"
+
+
+
+
+#endif // !YY_EVAL_PARSER_H_INCLUDED
diff --git a/src/lib/eval/parser.yy b/src/lib/eval/parser.yy
new file mode 100644
index 0000000..2015e83
--- /dev/null
+++ b/src/lib/eval/parser.yy
@@ -0,0 +1,649 @@
+/* Copyright (C) 2015-2023 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/. */
+
+%skeleton "lalr1.cc" /* -*- C++ -*- */
+%require "3.3.0"
+%defines
+%define api.parser.class {EvalParser}
+%define api.prefix {eval}
+%define api.token.constructor
+%define api.value.type variant
+%define api.namespace {isc::eval}
+%define parse.assert
+%code requires
+{
+#include <string>
+#include <eval/token.h>
+#include <eval/eval_context_decl.h>
+#include <dhcp/option.h>
+#include <boost/lexical_cast.hpp>
+
+using namespace isc::dhcp;
+using namespace isc::eval;
+}
+// The parsing context.
+%param { EvalContext& ctx }
+%locations
+%define parse.trace
+%define parse.error verbose
+%code
+{
+# include "eval_context.h"
+
+// Avoid warnings with the error counter.
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+#endif
+}
+
+%define api.token.prefix {TOKEN_}
+// Tokens in an order which makes sense and related to the intended use.
+%token
+ END 0 "end of file"
+ LPAREN "("
+ RPAREN ")"
+ NOT "not"
+ AND "and"
+ OR "or"
+ EQUAL "=="
+ OPTION "option"
+ RELAY4 "relay4"
+ RELAY6 "relay6"
+ MEMBER "member"
+ PEERADDR "peeraddr"
+ LINKADDR "linkaddr"
+ LBRACKET "["
+ RBRACKET "]"
+ DOT "."
+ TEXT "text"
+ HEX "hex"
+ EXISTS "exists"
+ PKT "pkt"
+ IFACE "iface"
+ SRC "src"
+ DST "dst"
+ LEN "len"
+ PKT4 "pkt4"
+ CHADDR "mac"
+ HLEN "hlen"
+ HTYPE "htype"
+ CIADDR "ciaddr"
+ GIADDR "giaddr"
+ YIADDR "yiaddr"
+ SIADDR "siaddr"
+ SUBSTRING "substring"
+ SPLIT "split"
+ ALL "all"
+ COMA ","
+ CONCAT "concat"
+ PLUS "+"
+ IFELSE "ifelse"
+ TOHEXSTRING "hexstring"
+ ADDRTOTEXT "addrtotext"
+ INT8TOTEXT "int8totext"
+ INT16TOTEXT "int16totext"
+ INT32TOTEXT "int32totext"
+ UINT8TOTEXT "uint8totext"
+ UINT16TOTEXT "uint16totext"
+ UINT32TOTEXT "uint32totext"
+ PKT6 "pkt6"
+ MSGTYPE "msgtype"
+ TRANSID "transid"
+ VENDOR_CLASS "vendor-class"
+ VENDOR "vendor"
+ ANY "*"
+ DATA "data"
+ ENTERPRISE "enterprise"
+
+ TOPLEVEL_BOOL "top-level bool"
+ TOPLEVEL_STRING "top-level string"
+;
+
+%token <std::string> STRING "constant string"
+%token <std::string> INTEGER "integer"
+%token <std::string> HEXSTRING "constant hexstring"
+%token <std::string> OPTION_NAME "option name"
+%token <std::string> IP_ADDRESS "ip address"
+
+%type <uint16_t> option_code
+%type <uint16_t> sub_option_code
+%type <uint32_t> enterprise_id
+%type <uint32_t> integer_expr
+%type <TokenOption::RepresentationType> option_repr_type
+%type <TokenRelay6Field::FieldType> relay6_field
+%type <int8_t> nest_level
+%type <TokenPkt::MetadataType> pkt_metadata
+%type <TokenPkt4::FieldType> pkt4_field
+%type <TokenPkt6::FieldType> pkt6_field
+
+%left PLUS
+%left OR
+%left AND
+%precedence NOT
+
+%printer { yyoutput << $$; } <*>;
+
+%%
+
+// The whole grammar starts with a 'start' symbol...
+%start start;
+
+// ... that expects either TOPLEVEL_BOOL or TOPLEVEL_STRING. Depending on which
+// token appears first, it will determine what is allowed and what it not.
+start: TOPLEVEL_BOOL expression
+ | TOPLEVEL_STRING string_expr
+;
+
+// Expression can either be a single token or a (something == something) expression
+
+expression : bool_expr
+ ;
+
+bool_expr : "(" bool_expr ")"
+ | NOT bool_expr
+ {
+ TokenPtr neg(new TokenNot());
+ ctx.expression.push_back(neg);
+ }
+ | bool_expr AND bool_expr
+ {
+ TokenPtr neg(new TokenAnd());
+ ctx.expression.push_back(neg);
+ }
+ | bool_expr OR bool_expr
+ {
+ TokenPtr neg(new TokenOr());
+ ctx.expression.push_back(neg);
+ }
+ | string_expr EQUAL string_expr
+ {
+ TokenPtr eq(new TokenEqual());
+ ctx.expression.push_back(eq);
+ }
+ | OPTION "[" option_code "]" "." EXISTS
+ {
+ TokenPtr opt(new TokenOption($3, TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ }
+ | OPTION "[" option_code "]" "." OPTION "[" sub_option_code "]" "." EXISTS
+ {
+ TokenPtr opt(new TokenSubOption($3, $8, TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ }
+ | RELAY4 "[" sub_option_code "]" "." EXISTS
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V4:
+ {
+ TokenPtr opt(new TokenRelay4Option($3, TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V6:
+ // We will have relay6[123] for the DHCPv6.
+ // In a very distant future we'll possibly be able
+ // to mix both if we have DHCPv4-over-DHCPv6, so it
+ // has some sense to make it explicit whether we
+ // talk about DHCPv4 relay or DHCPv6 relay. However,
+ // for the time being relay4 can be used in DHCPv4
+ // only.
+ error(@1, "relay4 can only be used in DHCPv4.");
+ }
+ }
+ | RELAY6 "[" nest_level "]" "." OPTION "[" sub_option_code "]" "." EXISTS
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr opt(new TokenRelay6Option($3, $8, TokenOption::EXISTS));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V4:
+ // For now we only use relay6 in DHCPv6.
+ error(@1, "relay6 can only be used in DHCPv6.");
+ }
+ }
+ | VENDOR_CLASS "[" enterprise_id "]" "." EXISTS
+ {
+ // Expression: vendor-class[1234].exists
+ //
+ // This token will find option 124 (DHCPv4) or 16 (DHCPv6),
+ // and will check if enterprise-id equals specified value.
+ TokenPtr exist(new TokenVendorClass(ctx.getUniverse(), $3, TokenOption::EXISTS));
+ ctx.expression.push_back(exist);
+ }
+ | VENDOR "[" enterprise_id "]" "." EXISTS
+ {
+ // Expression: vendor[1234].exists
+ //
+ // This token will find option 125 (DHCPv4) or 17 (DHCPv6),
+ // and will check if enterprise-id equals specified value.
+ TokenPtr exist(new TokenVendor(ctx.getUniverse(), $3, TokenOption::EXISTS));
+ ctx.expression.push_back(exist);
+ }
+ | VENDOR "[" enterprise_id "]" "." OPTION "[" sub_option_code "]" "." EXISTS
+ {
+ // Expression vendor[1234].option[123].exists
+ //
+ // This token will check if specified vendor option
+ // exists, has specified enterprise-id and if has
+ // specified suboption.
+ TokenPtr exist(new TokenVendor(ctx.getUniverse(), $3, TokenOption::EXISTS, $8));
+ ctx.expression.push_back(exist);
+ }
+ | MEMBER "(" STRING ")"
+ {
+ // Expression member('foo')
+ //
+ // This token will check if the packet is a member of
+ // the specified client class.
+ // To avoid loops at evaluation only already defined and
+ // built-in classes are allowed.
+ std::string cc = $3;
+ if (!ctx.isClientClassDefined(cc)) {
+ error(@3, "Not defined client class '" + cc + "'");
+ }
+ TokenPtr member(new TokenMember(cc));
+ ctx.expression.push_back(member);
+ }
+ ;
+
+string_expr : STRING
+ {
+ TokenPtr str(new TokenString($1));
+ ctx.expression.push_back(str);
+ }
+ | HEXSTRING
+ {
+ TokenPtr hex(new TokenHexString($1));
+ ctx.expression.push_back(hex);
+ }
+ | IP_ADDRESS
+ {
+ TokenPtr ip(new TokenIpAddress($1));
+ ctx.expression.push_back(ip);
+ }
+ | OPTION "[" option_code "]" "." option_repr_type
+ {
+ TokenPtr opt(new TokenOption($3, $6));
+ ctx.expression.push_back(opt);
+ }
+ | OPTION "[" option_code "]" "." OPTION "[" sub_option_code "]" "." option_repr_type
+ {
+ TokenPtr opt(new TokenSubOption($3, $8, $11));
+ ctx.expression.push_back(opt);
+ }
+ | RELAY4 "[" sub_option_code "]" "." option_repr_type
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V4:
+ {
+ TokenPtr opt(new TokenRelay4Option($3, $6));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V6:
+ // We will have relay6[123] for the DHCPv6.
+ // In a very distant future we'll possibly be able
+ // to mix both if we have DHCPv4-over-DHCPv6, so it
+ // has some sense to make it explicit whether we
+ // talk about DHCPv4 relay or DHCPv6 relay. However,
+ // for the time being relay4 can be used in DHCPv4
+ // only.
+ error(@1, "relay4 can only be used in DHCPv4.");
+ }
+ }
+
+ | RELAY6 "[" nest_level "]" "." OPTION "[" sub_option_code "]" "." option_repr_type
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr opt(new TokenRelay6Option($3, $8, $11));
+ ctx.expression.push_back(opt);
+ break;
+ }
+ case Option::V4:
+ // For now we only use relay6 in DHCPv6.
+ error(@1, "relay6 can only be used in DHCPv6.");
+ }
+ }
+
+ | PKT "." pkt_metadata
+ {
+ TokenPtr pkt_metadata(new TokenPkt($3));
+ ctx.expression.push_back(pkt_metadata);
+ }
+ | PKT4 "." pkt4_field
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V4:
+ {
+ TokenPtr pkt4_field(new TokenPkt4($3));
+ ctx.expression.push_back(pkt4_field);
+ break;
+ }
+ case Option::V6:
+ // For now we only use pkt4 in DHCPv4.
+ error(@1, "pkt4 can only be used in DHCPv4.");
+ }
+ }
+ | PKT6 "." pkt6_field
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr pkt6_field(new TokenPkt6($3));
+ ctx.expression.push_back(pkt6_field);
+ break;
+ }
+ case Option::V4:
+ // For now we only use pkt6 in DHCPv6.
+ error(@1, "pkt6 can only be used in DHCPv6.");
+ }
+ }
+ | RELAY6 "[" nest_level "]" "." relay6_field
+ {
+ switch (ctx.getUniverse()) {
+ case Option::V6:
+ {
+ TokenPtr relay6field(new TokenRelay6Field($3, $6));
+ ctx.expression.push_back(relay6field);
+ break;
+ }
+ case Option::V4:
+ // For now we only use relay6 in DHCPv6.
+ error(@1, "relay6 can only be used in DHCPv6.");
+ }
+ }
+
+ | SUBSTRING "(" string_expr "," start_expr "," length_expr ")"
+ {
+ TokenPtr sub(new TokenSubstring());
+ ctx.expression.push_back(sub);
+ }
+ | SPLIT "(" string_expr "," string_expr "," int_expr ")"
+ {
+ TokenPtr split(new TokenSplit());
+ ctx.expression.push_back(split);
+ }
+ | CONCAT "(" string_expr "," string_expr ")"
+ {
+ TokenPtr conc(new TokenConcat());
+ ctx.expression.push_back(conc);
+ }
+ | string_expr PLUS string_expr
+ {
+ TokenPtr conc(new TokenConcat());
+ ctx.expression.push_back(conc);
+ }
+ | IFELSE "(" bool_expr "," string_expr "," string_expr ")"
+ {
+ TokenPtr cond(new TokenIfElse());
+ ctx.expression.push_back(cond);
+ }
+ | TOHEXSTRING "(" string_expr "," string_expr ")"
+ {
+ TokenPtr tohex(new TokenToHexString());
+ ctx.expression.push_back(tohex);
+ }
+ | ADDRTOTEXT "(" string_expr ")"
+ {
+ TokenPtr addrtotext(new TokenIpAddressToText());
+ ctx.expression.push_back(addrtotext);
+ }
+ | INT8TOTEXT "(" string_expr ")"
+ {
+ TokenPtr int8totext(new TokenInt8ToText());
+ ctx.expression.push_back(int8totext);
+ }
+ | INT16TOTEXT "(" string_expr ")"
+ {
+ TokenPtr int16totext(new TokenInt16ToText());
+ ctx.expression.push_back(int16totext);
+ }
+ | INT32TOTEXT "(" string_expr ")"
+ {
+ TokenPtr int32totext(new TokenInt32ToText());
+ ctx.expression.push_back(int32totext);
+ }
+ | UINT8TOTEXT "(" string_expr ")"
+ {
+ TokenPtr uint8totext(new TokenUInt8ToText());
+ ctx.expression.push_back(uint8totext);
+ }
+ | UINT16TOTEXT "(" string_expr ")"
+ {
+ TokenPtr uint16totext(new TokenUInt16ToText());
+ ctx.expression.push_back(uint16totext);
+ }
+ | UINT32TOTEXT "(" string_expr ")"
+ {
+ TokenPtr uint32totext(new TokenUInt32ToText());
+ ctx.expression.push_back(uint32totext);
+ }
+ | VENDOR "." ENTERPRISE
+ {
+ // expression: vendor.enterprise
+ //
+ // This token will return enterprise-id number of
+ // received vendor option.
+ TokenPtr vendor(new TokenVendor(ctx.getUniverse(), 0, TokenVendor::ENTERPRISE_ID));
+ ctx.expression.push_back(vendor);
+ }
+ | VENDOR_CLASS "." ENTERPRISE
+ {
+ // expression: vendor-class.enterprise
+ //
+ // This token will return enterprise-id number of
+ // received vendor class option.
+ TokenPtr vendor(new TokenVendorClass(ctx.getUniverse(), 0,
+ TokenVendor::ENTERPRISE_ID));
+ ctx.expression.push_back(vendor);
+ }
+ | VENDOR "[" enterprise_id "]" "." OPTION "[" sub_option_code "]" "." option_repr_type
+ {
+ // This token will search for vendor option with
+ // specified enterprise-id. If found, will search
+ // for specified suboption and finally will return
+ // its content.
+ TokenPtr opt(new TokenVendor(ctx.getUniverse(), $3, $11, $8));
+ ctx.expression.push_back(opt);
+ }
+ | VENDOR_CLASS "[" enterprise_id "]" "." DATA
+ {
+ // expression: vendor-class[1234].data
+ //
+ // Vendor class option does not have suboptions,
+ // but chunks of data (typically 1, but the option
+ // structure allows multiple of them). If chunk
+ // offset is not specified, we assume the first (0th)
+ // is requested.
+ TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), $3,
+ TokenVendor::DATA, 0));
+ ctx.expression.push_back(vendor_class);
+ }
+ | VENDOR_CLASS "[" enterprise_id "]" "." DATA "[" INTEGER "]"
+ {
+ // expression: vendor-class[1234].data[5]
+ //
+ // Vendor class option does not have suboptions,
+ // but chunks of data (typically 1, but the option
+ // structure allows multiple of them). This syntax
+ // specifies which data chunk (tuple) we want.
+ uint8_t index = ctx.convertUint8($8, @8);
+ TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), $3,
+ TokenVendor::DATA, index));
+ ctx.expression.push_back(vendor_class);
+ }
+ | integer_expr
+ {
+ TokenPtr integer(new TokenInteger($1));
+ ctx.expression.push_back(integer);
+ }
+ | "(" string_expr ")"
+ ;
+
+integer_expr : INTEGER
+ {
+ $$ = ctx.convertUint32($1, @1);
+ }
+ ;
+
+option_code : INTEGER
+ {
+ $$ = ctx.convertOptionCode($1, @1);
+ }
+ | OPTION_NAME
+ {
+ $$ = ctx.convertOptionName($1, @1);
+ }
+ ;
+
+sub_option_code : INTEGER
+ {
+ $$ = ctx.convertOptionCode($1, @1);
+ }
+ ;
+
+option_repr_type : TEXT
+ {
+ $$ = TokenOption::TEXTUAL;
+ }
+ | HEX
+ {
+ $$ = TokenOption::HEXADECIMAL;
+ }
+ ;
+
+nest_level : INTEGER
+ {
+ $$ = ctx.convertNestLevelNumber($1, @1);
+ }
+ // Eventually we may add strings to handle different
+ // ways of choosing from which relay we want to extract
+ // an option or field.
+ ;
+
+pkt_metadata : IFACE
+ {
+ $$ = TokenPkt::IFACE;
+ }
+ | SRC
+ {
+ $$ = TokenPkt::SRC;
+ }
+ | DST
+ {
+ $$ = TokenPkt::DST;
+ }
+ | LEN
+ {
+ $$ = TokenPkt::LEN;
+ }
+ ;
+
+enterprise_id : INTEGER
+ {
+ $$ = ctx.convertUint32($1, @1);
+ }
+ | "*"
+ {
+ $$ = 0;
+ }
+ ;
+
+pkt4_field : CHADDR
+ {
+ $$ = TokenPkt4::CHADDR;
+ }
+ | HLEN
+ {
+ $$ = TokenPkt4::HLEN;
+ }
+ | HTYPE
+ {
+ $$ = TokenPkt4::HTYPE;
+ }
+ | CIADDR
+ {
+ $$ = TokenPkt4::CIADDR;
+ }
+ | GIADDR
+ {
+ $$ = TokenPkt4::GIADDR;
+ }
+ | YIADDR
+ {
+ $$ = TokenPkt4::YIADDR;
+ }
+ | SIADDR
+ {
+ $$ = TokenPkt4::SIADDR;
+ }
+ | MSGTYPE
+ {
+ $$ = TokenPkt4::MSGTYPE;
+ }
+ | TRANSID
+ {
+ $$ = TokenPkt4::TRANSID;
+ }
+ ;
+
+pkt6_field : MSGTYPE
+ {
+ $$ = TokenPkt6::MSGTYPE;
+ }
+ | TRANSID
+ {
+ $$ = TokenPkt6::TRANSID;
+ }
+ ;
+
+relay6_field : PEERADDR
+ {
+ $$ = TokenRelay6Field::PEERADDR;
+ }
+ | LINKADDR
+ {
+ $$ = TokenRelay6Field::LINKADDR;
+ }
+ ;
+
+start_expr : INTEGER
+ {
+ TokenPtr str(new TokenString($1));
+ ctx.expression.push_back(str);
+ }
+ ;
+
+length_expr : INTEGER
+ {
+ TokenPtr str(new TokenString($1));
+ ctx.expression.push_back(str);
+ }
+ | ALL
+ {
+ TokenPtr str(new TokenString("all"));
+ ctx.expression.push_back(str);
+ }
+ ;
+int_expr : INTEGER
+ {
+ TokenPtr str(new TokenString($1));
+ ctx.expression.push_back(str);
+ }
+ ;
+
+%%
+void
+isc::eval::EvalParser::error(const location_type& loc,
+ const std::string& what)
+{
+ ctx.error(loc, what);
+}
diff --git a/src/lib/eval/tests/Makefile.am b/src/lib/eval/tests/Makefile.am
new file mode 100644
index 0000000..13ba097
--- /dev/null
+++ b/src/lib/eval/tests/Makefile.am
@@ -0,0 +1,46 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DLOGGING_SPEC_FILE=\"$(abs_top_srcdir)/src/lib/dhcpsrv/logging.spec\"
+
+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 += libeval_unittests
+
+libeval_unittests_SOURCES = boolean_unittest.cc
+libeval_unittests_SOURCES += context_unittest.cc
+libeval_unittests_SOURCES += dependency_unittest.cc
+libeval_unittests_SOURCES += evaluate_unittest.cc
+libeval_unittests_SOURCES += token_unittest.cc
+libeval_unittests_SOURCES += run_unittests.cc
+libeval_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+libeval_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libeval_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+libeval_unittests_LDADD = $(top_builddir)/src/lib/eval/libkea-eval.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libeval_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libeval_unittests_LDADD += $(CRYPTO_LIBS) $(LOG4CPLUS_LIBS)
+libeval_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/eval/tests/Makefile.in b/src/lib/eval/tests/Makefile.in
new file mode 100644
index 0000000..6aa01a2
--- /dev/null
+++ b/src/lib/eval/tests/Makefile.in
@@ -0,0 +1,1098 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libeval_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/eval/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libeval_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libeval_unittests_SOURCES_DIST = boolean_unittest.cc \
+ context_unittest.cc dependency_unittest.cc \
+ evaluate_unittest.cc token_unittest.cc run_unittests.cc
+@HAVE_GTEST_TRUE@am_libeval_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libeval_unittests-boolean_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libeval_unittests-context_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libeval_unittests-dependency_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libeval_unittests-evaluate_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libeval_unittests-token_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libeval_unittests-run_unittests.$(OBJEXT)
+libeval_unittests_OBJECTS = $(am_libeval_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libeval_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) \
+@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 =
+libeval_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libeval_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)/libeval_unittests-boolean_unittest.Po \
+ ./$(DEPDIR)/libeval_unittests-context_unittest.Po \
+ ./$(DEPDIR)/libeval_unittests-dependency_unittest.Po \
+ ./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po \
+ ./$(DEPDIR)/libeval_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libeval_unittests-token_unittest.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 =
+SOURCES = $(libeval_unittests_SOURCES)
+DIST_SOURCES = $(am__libeval_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DLOGGING_SPEC_FILE=\"$(abs_top_srcdir)/src/lib/dhcpsrv/logging.spec\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libeval_unittests_SOURCES = boolean_unittest.cc \
+@HAVE_GTEST_TRUE@ context_unittest.cc dependency_unittest.cc \
+@HAVE_GTEST_TRUE@ evaluate_unittest.cc token_unittest.cc \
+@HAVE_GTEST_TRUE@ run_unittests.cc
+@HAVE_GTEST_TRUE@libeval_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libeval_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libeval_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libeval_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@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/log/libkea-log.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) $(LOG4CPLUS_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_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/eval/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/eval/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
+
+libeval_unittests$(EXEEXT): $(libeval_unittests_OBJECTS) $(libeval_unittests_DEPENDENCIES) $(EXTRA_libeval_unittests_DEPENDENCIES)
+ @rm -f libeval_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libeval_unittests_LINK) $(libeval_unittests_OBJECTS) $(libeval_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-boolean_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-context_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-dependency_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-token_unittest.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 $@ $<
+
+libeval_unittests-boolean_unittest.o: boolean_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-boolean_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo -c -o libeval_unittests-boolean_unittest.o `test -f 'boolean_unittest.cc' || echo '$(srcdir)/'`boolean_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo $(DEPDIR)/libeval_unittests-boolean_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boolean_unittest.cc' object='libeval_unittests-boolean_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-boolean_unittest.o `test -f 'boolean_unittest.cc' || echo '$(srcdir)/'`boolean_unittest.cc
+
+libeval_unittests-boolean_unittest.obj: boolean_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-boolean_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo -c -o libeval_unittests-boolean_unittest.obj `if test -f 'boolean_unittest.cc'; then $(CYGPATH_W) 'boolean_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boolean_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo $(DEPDIR)/libeval_unittests-boolean_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boolean_unittest.cc' object='libeval_unittests-boolean_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-boolean_unittest.obj `if test -f 'boolean_unittest.cc'; then $(CYGPATH_W) 'boolean_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boolean_unittest.cc'; fi`
+
+libeval_unittests-context_unittest.o: context_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-context_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-context_unittest.Tpo -c -o libeval_unittests-context_unittest.o `test -f 'context_unittest.cc' || echo '$(srcdir)/'`context_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-context_unittest.Tpo $(DEPDIR)/libeval_unittests-context_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='context_unittest.cc' object='libeval_unittests-context_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-context_unittest.o `test -f 'context_unittest.cc' || echo '$(srcdir)/'`context_unittest.cc
+
+libeval_unittests-context_unittest.obj: context_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-context_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-context_unittest.Tpo -c -o libeval_unittests-context_unittest.obj `if test -f 'context_unittest.cc'; then $(CYGPATH_W) 'context_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/context_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-context_unittest.Tpo $(DEPDIR)/libeval_unittests-context_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='context_unittest.cc' object='libeval_unittests-context_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-context_unittest.obj `if test -f 'context_unittest.cc'; then $(CYGPATH_W) 'context_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/context_unittest.cc'; fi`
+
+libeval_unittests-dependency_unittest.o: dependency_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-dependency_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo -c -o libeval_unittests-dependency_unittest.o `test -f 'dependency_unittest.cc' || echo '$(srcdir)/'`dependency_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo $(DEPDIR)/libeval_unittests-dependency_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dependency_unittest.cc' object='libeval_unittests-dependency_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-dependency_unittest.o `test -f 'dependency_unittest.cc' || echo '$(srcdir)/'`dependency_unittest.cc
+
+libeval_unittests-dependency_unittest.obj: dependency_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-dependency_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo -c -o libeval_unittests-dependency_unittest.obj `if test -f 'dependency_unittest.cc'; then $(CYGPATH_W) 'dependency_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dependency_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo $(DEPDIR)/libeval_unittests-dependency_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dependency_unittest.cc' object='libeval_unittests-dependency_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-dependency_unittest.obj `if test -f 'dependency_unittest.cc'; then $(CYGPATH_W) 'dependency_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dependency_unittest.cc'; fi`
+
+libeval_unittests-evaluate_unittest.o: evaluate_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-evaluate_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo -c -o libeval_unittests-evaluate_unittest.o `test -f 'evaluate_unittest.cc' || echo '$(srcdir)/'`evaluate_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo $(DEPDIR)/libeval_unittests-evaluate_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='evaluate_unittest.cc' object='libeval_unittests-evaluate_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-evaluate_unittest.o `test -f 'evaluate_unittest.cc' || echo '$(srcdir)/'`evaluate_unittest.cc
+
+libeval_unittests-evaluate_unittest.obj: evaluate_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-evaluate_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo -c -o libeval_unittests-evaluate_unittest.obj `if test -f 'evaluate_unittest.cc'; then $(CYGPATH_W) 'evaluate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/evaluate_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo $(DEPDIR)/libeval_unittests-evaluate_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='evaluate_unittest.cc' object='libeval_unittests-evaluate_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-evaluate_unittest.obj `if test -f 'evaluate_unittest.cc'; then $(CYGPATH_W) 'evaluate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/evaluate_unittest.cc'; fi`
+
+libeval_unittests-token_unittest.o: token_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-token_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-token_unittest.Tpo -c -o libeval_unittests-token_unittest.o `test -f 'token_unittest.cc' || echo '$(srcdir)/'`token_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-token_unittest.Tpo $(DEPDIR)/libeval_unittests-token_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='token_unittest.cc' object='libeval_unittests-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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-token_unittest.o `test -f 'token_unittest.cc' || echo '$(srcdir)/'`token_unittest.cc
+
+libeval_unittests-token_unittest.obj: token_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-token_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-token_unittest.Tpo -c -o libeval_unittests-token_unittest.obj `if test -f 'token_unittest.cc'; then $(CYGPATH_W) 'token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/token_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-token_unittest.Tpo $(DEPDIR)/libeval_unittests-token_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='token_unittest.cc' object='libeval_unittests-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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-token_unittest.obj `if test -f 'token_unittest.cc'; then $(CYGPATH_W) 'token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/token_unittest.cc'; fi`
+
+libeval_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libeval_unittests-run_unittests.Tpo -c -o libeval_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-run_unittests.Tpo $(DEPDIR)/libeval_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libeval_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libeval_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-run_unittests.Tpo -c -o libeval_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)/libeval_unittests-run_unittests.Tpo $(DEPDIR)/libeval_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libeval_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_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)/libeval_unittests-boolean_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-context_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-dependency_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-token_unittest.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)/libeval_unittests-boolean_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-context_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-dependency_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libeval_unittests-token_unittest.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/eval/tests/boolean_unittest.cc b/src/lib/eval/tests/boolean_unittest.cc
new file mode 100644
index 0000000..f620dae
--- /dev/null
+++ b/src/lib/eval/tests/boolean_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright (C) 2016-2017 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 <eval/eval_context.h>
+#include <eval/evaluate.h>
+#include <eval/token.h>
+#include <dhcp/pkt4.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture for testing booleans.
+class BooleanTest : public ::testing::Test {
+public:
+ void check(const string& expr, bool expected) {
+ EvalContext eval(Option::V4);
+ ASSERT_TRUE(eval.parseString(expr));
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+ if (expected) {
+ EXPECT_TRUE(evaluateBool(eval.expression, *pkt4));
+ } else {
+ EXPECT_FALSE(evaluateBool(eval.expression, *pkt4));
+ }
+ }
+};
+
+// A group of tests
+TEST_F(BooleanTest, tests) {
+ // true and (false or false)
+ check("('a' == 'a') and (('a' == 'b') or ('b' == 'a'))", false);
+ // (true and false) or false
+ check("(('a' == 'a') and ('a' == 'b')) or ('b' == 'a')", false);
+ // not true
+ check("not ('a' == 'a')", false);
+ // not false
+ check("not ('a' == 'b')", true);
+ // true and true and true and false
+ check("('a' == 'a') and ('b' == 'b') and ('c' == 'c') and ('a' == 'c')",
+ false);
+ // false or false or false or true
+ check("('a' == 'b') or ('a' == 'c') or ('b' == 'c') or ('b' == 'b')",
+ true);
+ // true or false or false or false
+ check("('a' == 'a') or ('a' == 'b') or ('a' == 'c') or ('b' == 'c')",
+ true);
+ // not (true or false)
+ check("not (('a' == 'a') or ('a' == 'b'))", false);
+ // not (true and false)
+ check("not (('a' == 'a') and ('a' == 'b'))", true);
+ // (not true) and false
+ check("(not ('a' == 'a')) and ('a' == 'b')",false);
+}
+
+};
diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc
new file mode 100644
index 0000000..26ef533
--- /dev/null
+++ b/src/lib/eval/tests/context_unittest.cc
@@ -0,0 +1,2118 @@
+// Copyright (C) 2015-2021 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 <eval/eval_context.h>
+#include <eval/token.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt4.h>
+#include <asiolink/io_address.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test class for testing EvalContext aka class test parsing
+class EvalContextTest : public ::testing::Test {
+public:
+ /// @brief constructor to initialize members
+ EvalContextTest() : ::testing::Test(),
+ universe_(Option::V4), parsed_(false)
+ { }
+
+ /// @brief checks if the given token is a string with the expected value
+ void checkTokenString(const TokenPtr& token, const std::string& expected) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenString> str =
+ boost::dynamic_pointer_cast<TokenString>(token);
+ ASSERT_TRUE(str);
+
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+ ValueStack values;
+
+ EXPECT_NO_THROW(token->evaluate(*pkt4, values));
+
+ ASSERT_EQ(1, values.size());
+
+ EXPECT_EQ(expected, values.top());
+ }
+
+ /// @brief checks if the given token is a hex string with the expected value
+ void checkTokenHexString(const TokenPtr& token,
+ const std::string& expected) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenHexString> hex =
+ boost::dynamic_pointer_cast<TokenHexString>(token);
+ ASSERT_TRUE(hex);
+
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+ ValueStack values;
+
+ EXPECT_NO_THROW(token->evaluate(*pkt4, values));
+
+ ASSERT_EQ(1, values.size());
+
+ EXPECT_EQ(expected, values.top());
+ }
+
+ /// @brief checks if the given token is an IP address with the expected value
+ void checkTokenIpAddress(const TokenPtr& token,
+ const std::string& expected) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenIpAddress> ipaddr =
+ boost::dynamic_pointer_cast<TokenIpAddress>(token);
+ ASSERT_TRUE(ipaddr);
+
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+ ValueStack values;
+
+ EXPECT_NO_THROW(token->evaluate(*pkt4, values));
+
+ ASSERT_EQ(1, values.size());
+ string value = values.top();
+
+ boost::scoped_ptr<IOAddress> exp_ip;
+ ASSERT_NO_THROW(exp_ip.reset(new IOAddress(expected)));
+ vector<uint8_t> exp_addr = exp_ip->toBytes();
+ ASSERT_EQ(exp_addr.size(), value.size());
+ EXPECT_EQ(0, memcmp(&exp_addr[0], &value[0], value.size()));
+ }
+
+ /// @brief checks if the given token is an equal operator
+ void checkTokenEq(const TokenPtr& token) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenEqual> eq =
+ boost::dynamic_pointer_cast<TokenEqual>(token);
+ EXPECT_TRUE(eq);
+ }
+
+ /// @brief Checks if the given token is integer with expected value
+ ///
+ /// @param token token to be inspected
+ /// @param exp_value expected integer value of the token
+ void checkTokenInteger(const TokenPtr& token, uint32_t exp_value) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenInteger> integer =
+ boost::dynamic_pointer_cast<TokenInteger>(token);
+ ASSERT_TRUE(integer);
+ EXPECT_EQ(exp_value, integer->getInteger());
+ }
+
+ /// @brief checks if the given token is an option with the expected code
+ /// and representation type
+ /// @param token token to be checked
+ /// @param expected_code expected option code
+ /// @param expected_repr expected representation (text, hex, exists)
+ void checkTokenOption(const TokenPtr& token,
+ uint16_t expected_code,
+ TokenOption::RepresentationType expected_repr) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenOption> opt =
+ boost::dynamic_pointer_cast<TokenOption>(token);
+ ASSERT_TRUE(opt);
+
+ EXPECT_EQ(expected_code, opt->getCode());
+ EXPECT_EQ(expected_repr, opt->getRepresentation());
+ }
+
+ /// @brief check if the given token is relay4 with the expected code
+ /// and representation type
+ /// @param token token to be checked
+ /// @param expected_code expected option code
+ /// @param expected_repr expected representation (text, hex, exists)
+ void checkTokenRelay4(const TokenPtr& token,
+ uint16_t expected_code,
+ TokenOption::RepresentationType expected_repr) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenRelay4Option> relay4 =
+ boost::dynamic_pointer_cast<TokenRelay4Option>(token);
+ EXPECT_TRUE(relay4);
+
+ if (relay4) {
+ EXPECT_EQ(expected_code, relay4->getCode());
+ EXPECT_EQ(expected_repr, relay4->getRepresentation());
+ }
+ }
+
+ /// @brief checks if the given token is a TokenRelay6Option with
+ /// the correct nesting level, option code and representation.
+ /// @param token token to be checked
+ /// @param expected_level expected nesting level
+ /// @param expected_code expected option code
+ /// @param expected_repr expected representation (text, hex, exists)
+ void checkTokenRelay6Option(const TokenPtr& token,
+ int8_t expected_level,
+ uint16_t expected_code,
+ TokenOption::RepresentationType expected_repr) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenRelay6Option> opt =
+ boost::dynamic_pointer_cast<TokenRelay6Option>(token);
+ ASSERT_TRUE(opt);
+
+ EXPECT_EQ(expected_level, opt->getNest());
+ EXPECT_EQ(expected_code, opt->getCode());
+ EXPECT_EQ(expected_repr, opt->getRepresentation());
+ }
+
+ /// @brief This tests attempts to parse the expression then checks
+ /// if the number of tokens is correct and the TokenRelay6Option
+ /// is as expected.
+ ///
+ /// @param expr expression to be parsed
+ /// @param exp_level expected level to be parsed
+ /// @param exp_code expected option code to be parsed
+ /// @param exp_repr expected representation to be parsed
+ /// @param exp_tokens expected number of tokens
+ void testRelay6Option(const std::string& expr,
+ int8_t exp_level,
+ uint16_t exp_code,
+ TokenOption::RepresentationType exp_repr,
+ int exp_tokens) {
+ EvalContext eval(Option::V6);
+
+ // parse the expression
+ try {
+ parsed_ = eval.parseString(expr);
+ }
+ catch (const EvalParseError& ex) {
+ FAIL() <<"Exception thrown: " << ex.what();
+ return;
+ }
+
+ // Parsing should succeed and return a token.
+ EXPECT_TRUE(parsed_);
+
+ // There should be the expected number of tokens.
+ ASSERT_EQ(exp_tokens, eval.expression.size());
+
+ // checked that the first token is TokenRelay6Option and that
+ // is has the correct attributes
+ checkTokenRelay6Option(eval.expression.at(0), exp_level, exp_code, exp_repr);
+ }
+
+ /// @brief check if the given token is a Pkt of specified type
+ /// @param token token to be checked
+ /// @param type expected type of the Pkt metadata
+ void checkTokenPkt(const TokenPtr& token, TokenPkt::MetadataType type) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenPkt> pkt =
+ boost::dynamic_pointer_cast<TokenPkt>(token);
+ ASSERT_TRUE(pkt);
+
+ EXPECT_EQ(type, pkt->getType());
+ }
+
+ /// @brief Test that verifies access to the DHCP packet metadata.
+ ///
+ /// This test attempts to parse the expression, will check if the number
+ /// of tokens is exactly as expected and then will try to verify if the
+ /// first token represents the expected metadata in DHCP packet.
+ ///
+ /// @param expr expression to be parsed
+ /// @param exp_type expected metadata type to be parsed
+ /// @param exp_tokens expected number of tokens
+ void testPktMetadata(const std::string& expr,
+ TokenPkt::MetadataType exp_type,
+ int exp_tokens) {
+ EvalContext eval(Option::V6);
+
+ // Parse the expression.
+ try {
+ parsed_ = eval.parseString(expr);
+ }
+ catch (const EvalParseError& ex) {
+ FAIL() << "Exception thrown: " << ex.what();
+ return;
+ }
+
+ // Parsing should succeed and return a token.
+ EXPECT_TRUE(parsed_);
+
+ // There should be exactly the expected number of tokens.
+ ASSERT_EQ(exp_tokens, eval.expression.size());
+
+ // Check that the first token is TokenPkt instance and has correct type.
+ checkTokenPkt(eval.expression.at(0), exp_type);
+ }
+
+ /// @brief checks if the given token is Pkt4 of specified type
+ /// @param token token to be checked
+ /// @param type expected type of the Pkt4 field
+ void checkTokenPkt4(const TokenPtr& token, TokenPkt4::FieldType type) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenPkt4> pkt =
+ boost::dynamic_pointer_cast<TokenPkt4>(token);
+ ASSERT_TRUE(pkt);
+
+ EXPECT_EQ(type, pkt->getType());
+ }
+
+ /// @brief Test that verifies access to the DHCPv4 packet fields.
+ ///
+ /// This test attempts to parse the expression, will check if the number
+ /// of tokens is exactly as expected and then will try to verify if the
+ /// first token represents the expected field in DHCPv4 packet.
+ ///
+ /// @param expr expression to be parsed
+ /// @param exp_type expected field type to be parsed
+ /// @param exp_tokens expected number of tokens
+ void testPkt4Field(const std::string& expr,
+ TokenPkt4::FieldType exp_type,
+ int exp_tokens) {
+ EvalContext eval(Option::V4);
+
+ // Parse the expression.
+ try {
+ parsed_ = eval.parseString(expr);
+ }
+ catch (const EvalParseError& ex) {
+ FAIL() << "Exception thrown: " << ex.what();
+ return;
+ }
+
+ // Parsing should succeed and return a token.
+ EXPECT_TRUE(parsed_);
+
+ // There should be exactly the expected number of tokens.
+ ASSERT_EQ(exp_tokens, eval.expression.size());
+
+ // Check that the first token is TokenPkt4 instance and has correct type.
+ checkTokenPkt4(eval.expression.at(0), exp_type);
+ }
+
+ /// @brief checks if the given token is Pkt6 of specified type
+ /// @param token token to be checked
+ /// @param exp_type expected type of the Pkt6 field
+ void checkTokenPkt6(const TokenPtr& token,
+ TokenPkt6::FieldType exp_type) {
+ ASSERT_TRUE(token);
+
+ boost::shared_ptr<TokenPkt6> pkt =
+ boost::dynamic_pointer_cast<TokenPkt6>(token);
+
+ ASSERT_TRUE(pkt);
+
+ EXPECT_EQ(exp_type, pkt->getType());
+ }
+
+ /// @brief Test that verifies access to the DHCPv6 packet fields.
+ ///
+ /// This test attempts to parse the expression, will check if the number
+ /// of tokens is exactly as planned and then will try to verify if the
+ /// first token represents expected the field in DHCPv6 packet.
+ ///
+ /// @param expr expression to be parsed
+ /// @param exp_type expected field type to be parsed
+ /// @param exp_tokens expected number of tokens
+ void testPkt6Field(const std::string& expr,
+ TokenPkt6::FieldType exp_type,
+ int exp_tokens) {
+ EvalContext eval(Option::V6);
+
+ // Parse the expression.
+ try {
+ parsed_ = eval.parseString(expr);
+ }
+ catch (const EvalParseError& ex) {
+ FAIL() << "Exception thrown: " << ex.what();
+ return;
+ }
+
+ // Parsing should succeed and return a token.
+ EXPECT_TRUE(parsed_);
+
+ // There should be the requested number of tokens
+ ASSERT_EQ(exp_tokens, eval.expression.size());
+
+ // Check that the first token is TokenPkt6 instance and has correct type.
+ checkTokenPkt6(eval.expression.at(0), exp_type);
+ }
+
+ /// @brief checks if the given token is a TokenRelay with the
+ /// correct nesting level and field type.
+ /// @param token token to be checked
+ /// @param expected_level expected nesting level
+ /// @param expected_code expected option code
+ /// @param expected_repr expected representation (text, hex, exists)
+ void checkTokenRelay6Field(const TokenPtr& token,
+ int8_t expected_level,
+ TokenRelay6Field::FieldType expected_type) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenRelay6Field> opt =
+ boost::dynamic_pointer_cast<TokenRelay6Field>(token);
+ ASSERT_TRUE(opt);
+
+ EXPECT_EQ(expected_level, opt->getNest());
+ EXPECT_EQ(expected_type, opt->getType());
+ }
+
+ /// @brief This tests attempts to parse the expression then checks
+ /// if the number of tokens is correct and the TokenRelay6Field is as
+ /// expected.
+ ///
+ /// @param expr expression to be parsed
+ /// @param exp_level expected level to be parsed
+ /// @param exp_type expected field type to be parsed
+ /// @param exp_tokens expected number of tokens
+ void testRelay6Field(const std::string& expr,
+ int8_t exp_level,
+ TokenRelay6Field::FieldType exp_type,
+ int exp_tokens) {
+ EvalContext eval(Option::V6);
+
+ // parse the expression
+ try {
+ parsed_ = eval.parseString(expr);
+ }
+ catch (const EvalParseError& ex) {
+ FAIL() <<"Exception thrown: " << ex.what();
+ return;
+ }
+
+ // Parsing should succeed and return a token.
+ EXPECT_TRUE(parsed_);
+
+ // There should be the expected number of tokens.
+ ASSERT_EQ(exp_tokens, eval.expression.size());
+
+ // checked that the first token is TokenRelay6Field and that
+ // is has the correct attributes
+ checkTokenRelay6Field(eval.expression.at(0), exp_level, exp_type);
+ }
+
+ /// @brief checks if the given token is a TokenMember with the
+ /// correct client class name.
+ /// @param token token to be checked
+ /// @param expected_client_class expected client class name
+ void checkTokenMember(const TokenPtr& token,
+ const std::string& expected_client_class) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenMember> member =
+ boost::dynamic_pointer_cast<TokenMember>(token);
+ ASSERT_TRUE(member);
+
+ EXPECT_EQ(expected_client_class, member->getClientClass());
+ }
+
+ /// @brief This tests attempts to parse the expression then checks
+ /// if the number of tokens is correct and the TokenMember is as
+ /// expected.
+ ///
+ /// @param expr expression to be parsed
+ /// @param check_defined closure checking if the client class is defined
+ /// @param exp_client_class expected client class name to be parsed
+ /// @param exp_tokens expected number of tokens
+ void testMember(const std::string& expr,
+ EvalContext::CheckDefined check_defined,
+ const std::string& exp_client_class,
+ int exp_tokens) {
+ EvalContext eval(Option::V6, check_defined);
+
+ // parse the expression
+ try {
+ parsed_ = eval.parseString(expr);
+ }
+ catch (const EvalParseError& ex) {
+ FAIL() <<"Exception thrown: " << ex.what();
+ return;
+ }
+
+ // Parsing should succeed and return a token.
+ EXPECT_TRUE(parsed_);
+
+ // There should be the expected number of tokens.
+ ASSERT_EQ(exp_tokens, eval.expression.size());
+
+ // checked that the first token is TokenRelay6Field and that
+ // is has the correct attributes
+ checkTokenMember(eval.expression.at(0), exp_client_class);
+ }
+
+ /// @brief checks if the given token is a substring operator
+ void checkTokenSubstring(const TokenPtr& token) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenSubstring> sub =
+ boost::dynamic_pointer_cast<TokenSubstring>(token);
+ EXPECT_TRUE(sub);
+ }
+
+ /// @brief checks if the given token is a concat operator
+ void checkTokenConcat(const TokenPtr& token) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenConcat> conc =
+ boost::dynamic_pointer_cast<TokenConcat>(token);
+ EXPECT_TRUE(conc);
+ }
+
+ /// @brief checks if the given token is an ifelse operator
+ void checkTokenIfElse(const TokenPtr& token) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenIfElse> alt =
+ boost::dynamic_pointer_cast<TokenIfElse>(token);
+ EXPECT_TRUE(alt);
+ }
+
+ /// @brief checks if the given token is a hexstring operator
+ void checkTokenToHexString(const TokenPtr& token) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenToHexString> tohex =
+ boost::dynamic_pointer_cast<TokenToHexString>(token);
+ EXPECT_TRUE(tohex);
+ }
+
+ /// @brief checks if the given token is an addrtotext operator
+ void checkTokenIpAddressToText(const TokenPtr& token,
+ const std::string& expected) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenIpAddressToText> addrtotext =
+ boost::dynamic_pointer_cast<TokenIpAddressToText>(token);
+ EXPECT_TRUE(addrtotext);
+
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+ ValueStack values;
+
+ std::vector<uint8_t> bytes = IOAddress(expected).toBytes();
+ values.push(std::string(bytes.begin(), bytes.end()));
+
+ EXPECT_NO_THROW(token->evaluate(*pkt4, values));
+
+ ASSERT_EQ(1, values.size());
+ string value = values.top();
+
+ EXPECT_EQ(value, expected);
+ }
+
+ /// @brief checks if the given token is a inttotext operator
+ template <typename IntegerType, typename TokenIntegerType>
+ void checkTokenIntToText(const TokenPtr& token,
+ const std::string& expected) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenIntegerType> inttotext =
+ boost::dynamic_pointer_cast<TokenIntegerType>(token);
+ EXPECT_TRUE(inttotext);
+
+ Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345));
+ ValueStack values;
+
+ IntegerType n;
+
+ try {
+ if (is_signed<IntegerType>()) {
+ n = static_cast<IntegerType>(boost::lexical_cast<int32_t>(expected));
+ } else {
+ n = static_cast<IntegerType>(boost::lexical_cast<uint32_t>(expected));
+ }
+ } catch (const boost::bad_lexical_cast& e) {
+ FAIL() << "invalid value " << expected << ", error: " << e.what();
+ }
+
+ values.push(std::string(const_cast<const char*>(reinterpret_cast<char*>(&n)), sizeof(IntegerType)));
+
+ EXPECT_NO_THROW(token->evaluate(*pkt4, values));
+
+ ASSERT_EQ(1, values.size());
+ string value = values.top();
+
+ EXPECT_EQ(value, expected);
+ }
+
+ /// @brief checks if the given expression raises the expected message
+ /// when it is parsed.
+ void checkError(const string& expr, const string& msg) {
+ EvalContext eval(universe_);
+ parsed_ = false;
+ try {
+ parsed_ = eval.parseString(expr);
+ FAIL() << "Expected EvalParseError but nothing was raised";
+ }
+ catch (const EvalParseError& ex) {
+ EXPECT_EQ(msg, ex.what());
+ EXPECT_FALSE(parsed_);
+ }
+ catch (...) {
+ FAIL() << "Expected EvalParseError but something else was raised";
+ }
+ }
+
+ /// @brief sets the universe
+ /// @note the default universe is DHCPv4
+ void setUniverse(const Option::Universe& universe) {
+ universe_ = universe;
+ }
+
+ /// @brief Checks if the given token is TokenVendor and has expected characteristics
+ /// @param token token to be checked
+ /// @param exp_vendor_id expected vendor-id (aka enterprise number)
+ /// @param exp_repr expected representation (either 'exists' or 'hex')
+ /// @param exp_option_code expected option code (ignored if 0)
+ void checkTokenVendor(const TokenPtr& token, uint32_t exp_vendor_id,
+ uint16_t exp_option_code,
+ TokenOption::RepresentationType exp_repr) {
+ ASSERT_TRUE(token);
+
+ boost::shared_ptr<TokenVendor> vendor =
+ boost::dynamic_pointer_cast<TokenVendor>(token);
+
+ ASSERT_TRUE(vendor);
+
+ EXPECT_EQ(exp_vendor_id, vendor->getVendorId());
+ EXPECT_EQ(exp_repr, vendor->getRepresentation());
+ EXPECT_EQ(exp_option_code, vendor->getCode());
+ }
+
+ /// @brief Tests if specified token vendor expression can be parsed
+ ///
+ /// This test assumes the first token will be token vendor. Any additional
+ /// tokens are ignored. Tests expressions:
+ /// vendor[1234].option[234].hex
+ /// vendor[1234].option[234].exists
+ ///
+ /// @param expr expression to be parsed
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id expected vendor-id (aka enterprise number)
+ /// @param option_code expected option code (ignored if 0)
+ /// @param expected_repr expected representation (either 'exists' or 'hex')
+ void testVendor(const std::string& expr, Option::Universe u,
+ uint32_t vendor_id, uint16_t option_code,
+ TokenOption::RepresentationType expected_repr) {
+ EvalContext eval(u);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+ EXPECT_TRUE(parsed_);
+
+ // We need at least one token, we will evaluate the first one.
+ ASSERT_FALSE(eval.expression.empty());
+
+ checkTokenVendor(eval.expression.at(0), vendor_id, option_code, expected_repr);
+ }
+
+ /// @brief Checks if token is really a TokenVendor, that the vendor_id was
+ /// stored properly and that it has expected representation
+ ///
+ /// This test is able to handle expressions similar to:
+ /// vendor[4491].option[1].hex
+ /// vendor[4491].option[1].exists
+ /// vendor[4491].exists
+ /// vendor[*].exists
+ ///
+ /// @param expr expression to be parsed
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id expected vendor-id (aka enterprise number)
+ /// @param expected_repr expected representation (either 'exists' or 'hex')
+ void testVendor(const std::string& expr, Option::Universe u,
+ uint32_t vendor_id,
+ TokenOption::RepresentationType expected_repr) {
+ testVendor(expr, u, vendor_id, 0, expected_repr);
+ }
+
+ /// @brief Tests if the expression parses into token vendor that returns enterprise-id
+ ///
+ /// This test is able to handle expressions similar to:
+ /// vendor.enterprise
+ ///
+ /// @param expr expression to be parsed
+ /// @param u universe (V4 or V6)
+ void testVendorEnterprise(const std::string& expr,
+ Option::Universe u) {
+ EvalContext eval(u);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_FALSE(eval.expression.empty());
+
+ boost::shared_ptr<TokenVendor> vendor =
+ boost::dynamic_pointer_cast<TokenVendor>(eval.expression.at(0));
+
+ ASSERT_TRUE(vendor);
+ EXPECT_EQ(TokenVendor::ENTERPRISE_ID, vendor->getField());
+ }
+
+ /// @brief This test checks if vendor-class token is correct
+ ///
+ /// This test checks if EXISTS representation is set correctly.
+ /// It covers cases like:
+ /// - vendor-class[4491].exists
+ /// - vendor-class[*].exists
+ ///
+ /// @param expr expression to be parsed
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id expected vendor-id (aka enterprise number)
+ void testVendorClass(const std::string& expr,
+ Option::Universe u, uint32_t vendor_id) {
+ EvalContext eval(u);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(1, eval.expression.size());
+ checkTokenVendorClass(eval.expression.at(0), vendor_id, 0, TokenOption::EXISTS,
+ TokenVendor::EXISTS);
+ }
+
+ /// @brief Tests if specified token vendor class expression can be parsed
+ ///
+ /// This test assumes the first token will be token vendor-class.
+ /// Any additional tokens are ignored. Tests expressions:
+ /// - vendor-class[4491].exists
+ /// - vendor-class[*].exists
+ /// - vendor-class[4491].data
+ /// - vendor-class[4491].data[3]
+ ///
+ /// @param expr expression to be parsed
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id expected vendor-id (aka enterprise number)
+ /// @param index expected data index
+ void testVendorClass(const std::string& expr, Option::Universe u,
+ uint32_t vendor_id, uint16_t index) {
+ EvalContext eval(u);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+ EXPECT_TRUE(parsed_);
+
+ // Make sure there's at least one token
+ ASSERT_FALSE(eval.expression.empty());
+
+ // The first token should be TokenVendorClass, let's take a closer look.
+ checkTokenVendorClass(eval.expression.at(0), vendor_id, index,
+ TokenOption::HEXADECIMAL, TokenVendor::DATA);
+
+ }
+
+ /// @brief Tests if the expression parses into vendor class token that
+ /// returns enterprise-id.
+ ///
+ /// This test is able to handle expressions similar to:
+ /// - vendor-class.enterprise
+ ///
+ /// @param expr expression to be parsed
+ /// @param u universe (V4 or V6)
+ void testVendorClassEnterprise(const std::string& expr,
+ Option::Universe u) {
+ EvalContext eval(u);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString(expr));
+ EXPECT_TRUE(parsed_);
+
+ // Make sure there's at least one token
+ ASSERT_FALSE(eval.expression.empty());
+
+ // The first token should be TokenVendorClass, let's take a closer look.
+ checkTokenVendorClass(eval.expression.at(0), 0, 0, TokenOption::HEXADECIMAL,
+ TokenVendor::ENTERPRISE_ID);
+ }
+
+ /// @brief Checks if the given token is TokenVendorClass and has expected characteristics
+ ///
+ /// @param token token to be checked
+ /// @param vendor_id expected vendor-id (aka enterprise number)
+ /// @param index expected index (used for data field only)
+ /// @param repr expected representation (either 'exists' or 'hex')
+ /// @param field expected field (none, enterprise or data)
+ void checkTokenVendorClass(const TokenPtr& token, uint32_t vendor_id,
+ uint16_t index, TokenOption::RepresentationType repr,
+ TokenVendor::FieldType field) {
+ ASSERT_TRUE(token);
+
+ boost::shared_ptr<TokenVendorClass> vendor =
+ boost::dynamic_pointer_cast<TokenVendorClass>(token);
+
+ ASSERT_TRUE(vendor);
+
+ EXPECT_EQ(vendor_id, vendor->getVendorId());
+ EXPECT_EQ(index, vendor->getDataIndex());
+ EXPECT_EQ(repr, vendor->getRepresentation());
+ EXPECT_EQ(field, vendor->getField());
+ }
+
+ /// @brief checks if the given token is a sub-option with the expected
+ /// parent option and sub-option codes and representation type
+ /// @param token token to be checked
+ /// @param expected_code expected option code
+ /// @param expected_sub_code expected sub-option code
+ /// @param expected_repr expected representation (text, hex, exists)
+ void checkTokenSubOption(const TokenPtr& token,
+ uint16_t expected_code,
+ uint16_t expected_sub_code,
+ TokenOption::RepresentationType expected_repr) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenSubOption> sub =
+ boost::dynamic_pointer_cast<TokenSubOption>(token);
+ ASSERT_TRUE(sub);
+
+ EXPECT_EQ(expected_code, sub->getCode());
+ EXPECT_EQ(expected_sub_code, sub->getSubCode());
+ EXPECT_EQ(expected_repr, sub->getRepresentation());
+ }
+
+ Option::Universe universe_; ///< Universe (V4 or V6)
+ bool parsed_; ///< Parsing status
+
+};
+
+// Test the error method without location
+TEST_F(EvalContextTest, error) {
+
+ EvalContext eval(Option::V4);
+
+ EXPECT_THROW(eval.error("an error"), EvalParseError);
+}
+
+// Test the fatal method
+TEST_F(EvalContextTest, fatal) {
+
+ EvalContext eval(Option::V4);
+
+ EXPECT_THROW(eval.fatal("a fatal error"), isc::Unexpected);
+}
+
+// Test the convertOptionCode method with an illegal input
+TEST_F(EvalContextTest, badOptionCode) {
+
+ EvalContext eval(Option::V4);
+
+ // the option code must be a number
+ EXPECT_THROW(eval.convertOptionCode("bad", location(position())),
+ EvalParseError);
+}
+
+// Test the convertNestLevelNumber method with an illegal input
+TEST_F(EvalContextTest, badNestLevelNumber) {
+
+ EvalContext eval(Option::V4);
+
+ // the nest level number must be a number
+ EXPECT_THROW(eval.convertNestLevelNumber("bad", location(position())),
+ EvalParseError);
+}
+
+// Test the parsing of a basic expression
+TEST_F(EvalContextTest, basic) {
+
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'MSFT'"));
+ EXPECT_TRUE(parsed_);
+}
+
+// Test the parsing of a string terminal
+TEST_F(EvalContextTest, string) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+
+ checkTokenString(tmp1, "foo");
+ checkTokenString(tmp2, "bar");
+}
+
+// Test the parsing of a basic expression using integers
+TEST_F(EvalContextTest, integer) {
+
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("substring(option[123].text, 0, 2) == '42'"));
+ EXPECT_TRUE(parsed_);
+}
+
+// Test the parsing of a hexstring terminal
+TEST_F(EvalContextTest, hexstring) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("0x666f6f == 'foo'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+
+ checkTokenHexString(tmp, "foo");
+}
+
+// Test the parsing of a hexstring terminal with an odd number of
+// hexadecimal digits
+TEST_F(EvalContextTest, oddHexstring) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("0X7 == 'foo'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+
+ checkTokenHexString(tmp, "\a");
+}
+
+// Test the parsing of an IPv4 address
+TEST_F(EvalContextTest, ipaddress4) {
+ EvalContext eval(Option::V6);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("10.0.0.1 == 'foo'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+
+ checkTokenIpAddress(tmp, "10.0.0.1");
+}
+
+// Test the parsing of an IPv6 address
+TEST_F(EvalContextTest, ipaddress6) {
+ EvalContext eval(Option::V6);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("2001:db8::1 == 'foo'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+
+ checkTokenIpAddress(tmp, "2001:db8::1");
+}
+
+// Test the parsing of an IPv4 compatible IPv6 address
+TEST_F(EvalContextTest, ipaddress46) {
+ EvalContext eval(Option::V6);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("::10.0.0.1 == 'foo'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+
+ checkTokenIpAddress(tmp, "::10.0.0.1");
+}
+
+// Test the parsing of the unspecified IPv6 address
+TEST_F(EvalContextTest, ipaddress6unspec) {
+ EvalContext eval(Option::V6);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString(":: == 'foo'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+
+ checkTokenIpAddress(tmp, "::");
+}
+
+// Test the parsing of an IPv6 prefix
+TEST_F(EvalContextTest, ipaddress6prefix) {
+ EvalContext eval(Option::V6);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("2001:db8:: == 'foo'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+
+ checkTokenIpAddress(tmp, "2001:db8::");
+}
+
+// Test the parsing of an equal expression
+TEST_F(EvalContextTest, equal) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+
+ checkTokenString(tmp1, "foo");
+ checkTokenString(tmp2, "bar");
+ checkTokenEq(tmp3);
+}
+
+// Test the parsing of an option terminal
+TEST_F(EvalContextTest, option) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'foo'"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenOption(eval.expression.at(0), 123, TokenOption::TEXTUAL);
+}
+
+// Test parsing of an option identified by name.
+TEST_F(EvalContextTest, optionWithName) {
+ EvalContext eval(Option::V4);
+
+ // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].text == 'foo'"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenOption(eval.expression.at(0), 12, TokenOption::TEXTUAL);
+}
+
+// Test parsing of an option existence
+TEST_F(EvalContextTest, optionExists) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[100].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(1, eval.expression.size());
+ checkTokenOption(eval.expression.at(0), 100, TokenOption::EXISTS);
+}
+
+// Test checking that whitespace can surround option name.
+TEST_F(EvalContextTest, optionWithNameAndWhitespace) {
+ EvalContext eval(Option::V4);
+
+ // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[ host-name ].text == 'foo'"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenOption(eval.expression.at(0), 12, TokenOption::TEXTUAL);
+}
+
+// Test checking that newlines can surround option name.
+TEST_F(EvalContextTest, optionWithNameAndNewline) {
+ EvalContext eval(Option::V4);
+
+ // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++.
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("option[\n host-name \n ].text == \n'foo'"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenOption(eval.expression.at(0), 12, TokenOption::TEXTUAL);
+}
+
+// Test parsing of an option represented as hexadecimal string.
+TEST_F(EvalContextTest, optionHex) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].hex == 0x666F6F"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenOption(eval.expression.at(0), 123, TokenOption::HEXADECIMAL);
+}
+
+// This test checks that the relay4[code].hex can be used in expressions.
+TEST_F(EvalContextTest, relay4Option) {
+
+ EvalContext eval(Option::V4);
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("relay4[13].hex == 'thirteen'"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+
+ checkTokenRelay4(tmp1, 13, TokenOption::HEXADECIMAL);
+ checkTokenString(tmp2, "thirteen");
+ checkTokenEq(tmp3);
+}
+
+// This test check the relay4[code].exists is supported.
+TEST_F(EvalContextTest, relay4Exists) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("relay4[13].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(1, eval.expression.size());
+ checkTokenRelay4(eval.expression.at(0), 13, TokenOption::EXISTS);
+}
+
+// Verify that relay4[13] is not usable in v6
+// There will be a separate relay accessor for v6.
+TEST_F(EvalContextTest, relay4Error) {
+ universe_ = Option::V6;
+
+ checkError("relay4[13].hex == 'thirteen'",
+ "<string>:1.1-6: relay4 can only be used in DHCPv4.");
+}
+
+// Test the parsing of a relay6 option
+TEST_F(EvalContextTest, relay6Option) {
+ EvalContext eval(Option::V6);
+
+ testRelay6Option("relay6[0].option[123].text == 'foo'",
+ 0, 123, TokenOption::TEXTUAL, 3);
+}
+
+// Test the parsing of existence for a relay6 option
+TEST_F(EvalContextTest, relay6OptionExists) {
+ EvalContext eval(Option::V6);
+
+ testRelay6Option("relay6[1].option[75].exists",
+ 1, 75, TokenOption::EXISTS, 1);
+}
+
+// Test the parsing of hex for a relay6 option
+TEST_F(EvalContextTest, relay6OptionHex) {
+ EvalContext eval(Option::V6);
+
+ testRelay6Option("relay6[2].option[85].hex == 'foo'",
+ 2, 85, TokenOption::HEXADECIMAL, 3);
+}
+
+// Test the parsing of a relay6 option in reverse order
+TEST_F(EvalContextTest, relay6OptionReverse) {
+ EvalContext eval(Option::V6);
+
+ testRelay6Option("relay6[-1].option[123].text == 'foo'",
+ -1, 123, TokenOption::TEXTUAL, 3);
+}
+
+// Test the nest level of a relay6 option should be in [-32..32[
+TEST_F(EvalContextTest, relay6OptionLimits) {
+ EvalContext eval(Option::V6);
+
+ // max nest level is hop count limit minus one so 31
+ testRelay6Option("relay6[31].option[123].text == 'foo'",
+ 31, 123, TokenOption::TEXTUAL, 3);
+
+ universe_ = Option::V6;
+
+ checkError("relay6[32].option[123].text == 'foo'",
+ "<string>:1.8-9: Nest level has invalid value in 32. "
+ "Allowed range: -32..31");
+
+ // min nest level is minus hop count limit
+ testRelay6Option("relay6[-32].option[123].text == 'foo'",
+ -32, 123, TokenOption::TEXTUAL, 3);
+
+ checkError("relay6[-33].option[123].text == 'foo'",
+ "<string>:1.8-10: Nest level has invalid value in -33. Allowed range: -32..31");
+}
+
+// Verify that relay6[13].option is not usable in v4
+TEST_F(EvalContextTest, relay6OptionError) {
+ universe_ = Option::V4;
+
+ // nest_level is reduced first so raises the error
+ // (if we'd like to get a relay6 error we have to insert an
+ // intermediate action to check the universe)
+ checkError("relay6[0].option[123].text == 'foo'",
+ "<string>:1.8: Nest level invalid for DHCPv4 packets");
+}
+
+// Tests whether iface metadata in DHCP can be accessed.
+TEST_F(EvalContextTest, pktMetadataIface) {
+ testPktMetadata("pkt.iface == 'eth0'", TokenPkt::IFACE, 3);
+}
+
+// Tests whether src metadata in DHCP can be accessed.
+TEST_F(EvalContextTest, pktMetadataSrc) {
+ testPktMetadata("pkt.src == fe80::1", TokenPkt::SRC, 3);
+}
+
+// Tests whether dst metadata in DHCP can be accessed.
+TEST_F(EvalContextTest, pktMetadataDst) {
+ testPktMetadata("pkt.dst == fe80::2", TokenPkt::DST, 3);
+}
+
+// Tests whether len metadata in DHCP can be accessed.
+TEST_F(EvalContextTest, pktMetadataLen) {
+ testPktMetadata("pkt.len == 0x00000100", TokenPkt::LEN, 3);
+}
+
+// Tests whether chaddr field in DHCPv4 can be accessed.
+TEST_F(EvalContextTest, pkt4FieldChaddr) {
+ testPkt4Field("pkt4.mac == 0x000102030405", TokenPkt4::CHADDR, 3);
+}
+
+// Tests whether chaddr field in DHCPv4 can be accessed and converted.
+TEST_F(EvalContextTest, pkt4FieldChaddrHexa) {
+ testPkt4Field("hexstring(pkt4.mac, ':') == '00:01:02:03:04:05'",
+ TokenPkt4::CHADDR, 5);
+}
+
+// Tests whether hlen field in DHCPv4 can be accessed.
+TEST_F(EvalContextTest, pkt4FieldHlen) {
+ testPkt4Field("pkt4.hlen == 0x6", TokenPkt4::HLEN, 3);
+}
+
+// Tests whether htype field in DHCPv4 can be accessed.
+TEST_F(EvalContextTest, pkt4FieldHtype) {
+ testPkt4Field("pkt4.htype == 0x1", TokenPkt4::HTYPE, 3);
+}
+
+// Tests whether ciaddr field in DHCPv4 can be accessed.
+TEST_F(EvalContextTest, pkt4FieldCiaddr) {
+ testPkt4Field("pkt4.ciaddr == 192.0.2.1", TokenPkt4::CIADDR, 3);
+}
+
+// Tests whether giaddr field in DHCPv4 can be accessed.
+TEST_F(EvalContextTest, pkt4FieldGiaddr) {
+ testPkt4Field("pkt4.giaddr == 192.0.2.1", TokenPkt4::GIADDR, 3);
+}
+
+// Tests whether yiaddr field in DHCPv4 can be accessed.
+TEST_F(EvalContextTest, pkt4FieldYiaddr) {
+ testPkt4Field("pkt4.yiaddr == 192.0.2.1", TokenPkt4::YIADDR, 3);
+}
+
+// Tests whether siaddr field in DHCPv4 can be accessed.
+TEST_F(EvalContextTest, pkt4FieldSiaddr) {
+ testPkt4Field("pkt4.siaddr == 192.0.2.1", TokenPkt4::SIADDR, 3);
+}
+
+// Tests whether message type field in DHCPv6 can be accessed.
+TEST_F(EvalContextTest, pkt6FieldMsgtype) {
+ testPkt6Field("pkt6.msgtype == 1", TokenPkt6::MSGTYPE, 3);
+}
+
+// Tests whether transaction id field in DHCPv6 can be accessed.
+TEST_F(EvalContextTest, pkt6FieldTransid) {
+ testPkt6Field("pkt6.transid == 1", TokenPkt6::TRANSID, 3);
+}
+
+// Tests if the linkaddr field in a Relay6 encapsulation can be accessed.
+TEST_F(EvalContextTest, relay6FieldLinkAddr) {
+ testRelay6Field("relay6[0].linkaddr == ::",
+ 0, TokenRelay6Field::LINKADDR, 3);
+}
+
+// Tests if the peeraddr field in a Relay6 encapsulation can be accessed.
+TEST_F(EvalContextTest, relay6FieldPeerAddr) {
+ testRelay6Field("relay6[1].peeraddr == ::",
+ 1, TokenRelay6Field::PEERADDR, 3);
+}
+
+// Verify that relay6[0].<field> is not usable in v4
+TEST_F(EvalContextTest, relay6FieldError) {
+ universe_ = Option::V4;
+
+ // nest_level is reduced first so raises the error
+ // (if we'd like to get a relay6 error we have to insert an
+ // intermediate action to check the universe)
+ checkError("relay6[0].linkaddr == ::",
+ "<string>:1.8: Nest level invalid for DHCPv4 packets");
+}
+
+// Tests parsing of member with defined class
+TEST_F(EvalContextTest, member) {
+ auto check_defined = [](const ClientClass& cc) { return (cc == "foo"); };
+ testMember("member('foo')", check_defined, "foo", 1);
+}
+
+// Test parsing of member with not defined class
+TEST_F(EvalContextTest, memberError) {
+ auto check_defined = [](const ClientClass& cc) { return (cc == "foo"); };
+ EvalContext eval(Option::V6, check_defined);
+ parsed_ = false;
+ try {
+ parsed_ = eval.parseString("member('bar')");
+ FAIL() << "Expected EvalParseError but nothing was raised";
+ }
+ catch (const EvalParseError& ex) {
+ EXPECT_EQ("<string>:1.8-12: Not defined client class 'bar'",
+ std::string(ex.what()));
+ EXPECT_FALSE(parsed_);
+ }
+ catch (...) {
+ FAIL() << "Expected EvalParseError but something else was raised";
+ }
+}
+
+// Test parsing of logical operators
+TEST_F(EvalContextTest, logicalOps) {
+ // option.exists
+ EvalContext eval0(Option::V4);
+ EXPECT_NO_THROW(parsed_ = eval0.parseString("option[123].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(1, eval0.expression.size());
+ TokenPtr token = eval0.expression.at(0);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenOption> opt =
+ boost::dynamic_pointer_cast<TokenOption>(token);
+ EXPECT_TRUE(opt);
+
+ // not
+ EvalContext evaln(Option::V4);
+ EXPECT_NO_THROW(parsed_ = evaln.parseString("not option[123].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(2, evaln.expression.size());
+ token = evaln.expression.at(1);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenNot> tnot =
+ boost::dynamic_pointer_cast<TokenNot>(token);
+ EXPECT_TRUE(tnot);
+
+ // and
+ EvalContext evala(Option::V4);
+ EXPECT_NO_THROW(parsed_ =
+ evala.parseString("option[123].exists and option[123].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, evala.expression.size());
+ token = evala.expression.at(2);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenAnd> tand =
+ boost::dynamic_pointer_cast<TokenAnd>(token);
+ EXPECT_TRUE(tand);
+
+ // or
+ EvalContext evalo(Option::V4);
+ EXPECT_NO_THROW(parsed_ =
+ evalo.parseString("option[123].exists or option[123].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, evalo.expression.size());
+ token = evalo.expression.at(2);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenOr> tor =
+ boost::dynamic_pointer_cast<TokenOr>(token);
+ EXPECT_TRUE(tor);
+}
+
+// Test parsing of logical operators with precedence
+TEST_F(EvalContextTest, logicalPrecedence) {
+ // not precedence > and precedence
+ EvalContext evalna(Option::V4);
+ EXPECT_NO_THROW(parsed_ =
+ evalna.parseString("not option[123].exists and option[123].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(4, evalna.expression.size());
+ TokenPtr token = evalna.expression.at(3);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenAnd> tand =
+ boost::dynamic_pointer_cast<TokenAnd>(token);
+ EXPECT_TRUE(tand);
+
+ // and precedence > or precedence
+ EvalContext evaloa(Option::V4);
+ EXPECT_NO_THROW(parsed_ =
+ evaloa.parseString("option[123].exists or option[123].exists "
+ "and option[123].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(5, evaloa.expression.size());
+ token = evaloa.expression.at(4);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenOr> tor =
+ boost::dynamic_pointer_cast<TokenOr>(token);
+ EXPECT_TRUE(tor);
+}
+
+// Test parsing of logical operators with parentheses (same than
+// with precedence but using parentheses to overwrite precedence)
+TEST_F(EvalContextTest, logicalParentheses) {
+ // not precedence > and precedence
+ EvalContext evalna(Option::V4);
+ EXPECT_NO_THROW(parsed_ =
+ evalna.parseString("not (option[123].exists and option[123].exists)"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(4, evalna.expression.size());
+ TokenPtr token = evalna.expression.at(3);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenNot> tnot =
+ boost::dynamic_pointer_cast<TokenNot>(token);
+ EXPECT_TRUE(tnot);
+
+ // and precedence > or precedence
+ EvalContext evaloa(Option::V4);
+ EXPECT_NO_THROW(parsed_ =
+ evaloa.parseString("(option[123].exists or option[123].exists) "
+ "and option[123].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(5, evaloa.expression.size());
+ token = evaloa.expression.at(4);
+ ASSERT_TRUE(token);
+ boost::shared_ptr<TokenAnd> tand =
+ boost::dynamic_pointer_cast<TokenAnd>(token);
+ EXPECT_TRUE(tand);
+}
+
+// Test the parsing of a substring expression
+TEST_F(EvalContextTest, substring) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("substring('foobar',2,all) == 'obar'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(6, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenString(tmp1, "foobar");
+ checkTokenString(tmp2, "2");
+ checkTokenString(tmp3, "all");
+ checkTokenSubstring(tmp4);
+}
+
+// Test the parsing of a concat expression
+TEST_F(EvalContextTest, concat) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("concat('foo','bar') == 'foobar'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(5, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+
+ checkTokenString(tmp1, "foo");
+ checkTokenString(tmp2, "bar");
+ checkTokenConcat(tmp3);
+}
+
+// Test the parsing of a plus expression
+TEST_F(EvalContextTest, plus) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("'foo' + 'bar' == 'foobar'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(5, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+
+ checkTokenString(tmp1, "foo");
+ checkTokenString(tmp2, "bar");
+ checkTokenConcat(tmp3);
+}
+
+// Test the parsing of plus expressions
+TEST_F(EvalContextTest, assocPlus) {
+ EvalContext eval(Option::V4);
+
+ // Operator '+' is (left) associative
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("'a' + 'b' + 'c' == 'abc'"));
+
+ ASSERT_EQ(7, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+ TokenPtr tmp5 = eval.expression.at(4);
+
+ checkTokenString(tmp1, "a");
+ checkTokenString(tmp2, "b");
+ checkTokenConcat(tmp3);
+ checkTokenString(tmp4, "c");
+ checkTokenConcat(tmp5);
+}
+
+// Test the parsing of plus expressions with enforced associativity
+TEST_F(EvalContextTest, assocRightPlus) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("'a' + ('b' + 'c') == 'abc'"));
+
+ ASSERT_EQ(7, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+ TokenPtr tmp5 = eval.expression.at(4);
+
+ checkTokenString(tmp1, "a");
+ checkTokenString(tmp2, "b");
+ checkTokenString(tmp3, "c");
+ checkTokenConcat(tmp4);
+ checkTokenConcat(tmp5);
+}
+
+// Test the parsing of an ifelse expression
+TEST_F(EvalContextTest, ifElse) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("ifelse('foo' == 'bar', 'us', 'them') == 'you'"));
+
+ ASSERT_EQ(8, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(2);
+ TokenPtr tmp2 = eval.expression.at(3);
+ TokenPtr tmp3 = eval.expression.at(4);
+ TokenPtr tmp4 = eval.expression.at(5);
+
+ checkTokenEq(tmp1);
+ checkTokenString(tmp2, "us");
+ checkTokenString(tmp3, "them");
+ checkTokenIfElse(tmp4);
+}
+
+// Test the parsing of a plus operator and ifelse expression
+TEST_F(EvalContextTest, plusIfElse) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("'foo' + ifelse('a' == 'a', 'bar', '') == 'foobar'"));
+
+ ASSERT_EQ(10, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+ TokenPtr tmp5 = eval.expression.at(4);
+ TokenPtr tmp6 = eval.expression.at(5);
+ TokenPtr tmp7 = eval.expression.at(6);
+ TokenPtr tmp8 = eval.expression.at(7);
+
+ checkTokenString(tmp1, "foo");
+ checkTokenString(tmp2, "a");
+ checkTokenString(tmp3, "a");
+ checkTokenEq(tmp4);
+ checkTokenString(tmp5, "bar");
+ checkTokenString(tmp6, "");
+ checkTokenIfElse(tmp7);
+ checkTokenConcat(tmp8);
+}
+
+// Test the parsing of a hexstring expression
+TEST_F(EvalContextTest, toHexString) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ =
+ eval.parseString("hexstring(0x666f,'-') == '66-6f'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(5, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+
+ checkTokenHexString(tmp1, "fo");
+ checkTokenString(tmp2, "-");
+ checkTokenToHexString(tmp3);
+}
+
+// Test the parsing of an addrtotext expression
+TEST_F(EvalContextTest, addressToText) {
+ {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("addrtotext(10.0.0.1) == '10.0.0.1'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenIpAddress(tmp1, "10.0.0.1");
+ checkTokenIpAddressToText(tmp2, "10.0.0.1");
+ checkTokenString(tmp3, "10.0.0.1");
+ checkTokenEq(tmp4);
+ }
+
+ {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("addrtotext(2001:db8::1) == '2001:db8::1'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenIpAddress(tmp1, "2001:db8::1");
+ checkTokenIpAddressToText(tmp2, "2001:db8::1");
+ checkTokenString(tmp3, "2001:db8::1");
+ checkTokenEq(tmp4);
+ }
+}
+
+// Test the parsing of a int8_t expression
+TEST_F(EvalContextTest, int8ToText) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("int8totext(255) == '-1'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenInteger(tmp1, 255);
+ checkTokenIntToText<int8_t, TokenInt8ToText>(tmp2, "-1");
+ checkTokenString(tmp3, "-1");
+ checkTokenEq(tmp4);
+}
+
+// Test the parsing of a int16_t expression
+TEST_F(EvalContextTest, int16ToText) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("int16totext(65535) == '-1'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenInteger(tmp1, 65535);
+ checkTokenIntToText<int16_t, TokenInt16ToText>(tmp2, "-1");
+ checkTokenString(tmp3, "-1");
+ checkTokenEq(tmp4);
+}
+
+// Test the parsing of a int32_t expression
+TEST_F(EvalContextTest, int32ToText) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("int32totext(4294967295) == '-1'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenInteger(tmp1, 4294967295);
+ checkTokenIntToText<int32_t, TokenInt32ToText>(tmp2, "-1");
+ checkTokenString(tmp3, "-1");
+ checkTokenEq(tmp4);
+}
+
+// Test the parsing of a uint8_t expression
+TEST_F(EvalContextTest, uint8ToText) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("uint8totext(255) == '255'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenInteger(tmp1, 255);
+ checkTokenIntToText<uint8_t, TokenUInt8ToText>(tmp2, "255");
+ checkTokenString(tmp3, "255");
+ checkTokenEq(tmp4);
+}
+
+// Test the parsing of a uint16_t expression
+TEST_F(EvalContextTest, uint16ToText) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("uint16totext(65535) == '65535'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenInteger(tmp1, 65535);
+ checkTokenIntToText<uint16_t, TokenUInt16ToText>(tmp2, "65535");
+ checkTokenString(tmp3, "65535");
+ checkTokenEq(tmp4);
+}
+
+// Test the parsing of a uint32_t expression
+TEST_F(EvalContextTest, uint32ToText) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("uint32totext(4294967295) == '4294967295'"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(4, eval.expression.size());
+
+ TokenPtr tmp1 = eval.expression.at(0);
+ TokenPtr tmp2 = eval.expression.at(1);
+ TokenPtr tmp3 = eval.expression.at(2);
+ TokenPtr tmp4 = eval.expression.at(3);
+
+ checkTokenInteger(tmp1, 4294967295);
+ checkTokenIntToText<uint32_t, TokenUInt32ToText>(tmp2, "4294967295");
+ checkTokenString(tmp3, "4294967295");
+ checkTokenEq(tmp4);
+}
+
+//
+// Test some scanner error cases
+TEST_F(EvalContextTest, scanErrors) {
+ checkError("'", "<string>:1.1: Invalid character: '");
+ checkError("'\''", "<string>:1.3: Invalid character: '");
+ checkError("'\n'", "<string>:1.1: Invalid character: '");
+ checkError("0x123h", "<string>:1.6: Invalid character: h");
+ checkError(":1", "<string>:1.1: Invalid character: :");
+ checkError("=", "<string>:1.1: Invalid character: =");
+
+ // Typo should be handled as well.
+ checkError("subtring", "<string>:1.1: Invalid character: s");
+ checkError("foo", "<string>:1.1: Invalid character: f");
+ checkError(" bar", "<string>:1.2: Invalid character: b");
+ checkError("relay[12].hex == 'foo'", "<string>:1.1: Invalid character: r");
+ checkError("pkt4.ziaddr", "<string>:1.6: Invalid character: z");
+ checkError("members('foo'", "<string>:1.7: Invalid character: s");
+}
+
+// Tests some scanner/parser error cases
+TEST_F(EvalContextTest, scanParseErrors) {
+ checkError("", "<string>:1.1: syntax error, unexpected end of file");
+ checkError(" ", "<string>:1.2: syntax error, unexpected end of file");
+ checkError("0x", "<string>:1.2: Invalid character: x");
+ checkError("0abc",
+ "<string>:1.2: Invalid character: a");
+
+ // This one is a little bid odd. This is a truncated address, so it's not
+ // recognized as an address. Instead, the first token (10) is recognized as
+ // an integer. The only thing we can do with integers right now is to
+ // apply equality or concat operators, so the only possible next token
+ // are == and +. There's a dot instead, so an error is reported.
+ checkError("10.0.1", "<string>:1.3: syntax error, unexpected ., "
+ "expecting == or +");
+
+ checkError("10.256.0.1",
+ "<string>:1.1-10: Failed to convert 10.256.0.1 to "
+ "an IP address.");
+ checkError(":::",
+ "<string>:1.1-3: Failed to convert ::: to an IP address.");
+ checkError("===", "<string>:1.1-2: syntax error, unexpected ==");
+ checkError("option[-1].text",
+ "<string>:1.8-9: Option code has invalid "
+ "value in -1. Allowed range: 0..255");
+ checkError("option[256].text",
+ "<string>:1.8-10: Option code has invalid "
+ "value in 256. Allowed range: 0..255");
+ setUniverse(Option::V6);
+ checkError("option[65536].text",
+ "<string>:1.8-12: Option code has invalid "
+ "value in 65536. Allowed range: 0..65535");
+ setUniverse(Option::V4);
+ checkError("option[12345678901234567890].text",
+ "<string>:1.8-27: Failed to convert 12345678901234567890 "
+ "to an integer.");
+ checkError("option[123]",
+ "<string>:1.12: syntax error, unexpected end of file,"
+ " expecting .");
+ checkError("option[123].text < 'foo'", "<string>:1.18: Invalid"
+ " character: <");
+ checkError("option[-ab].text", "<string>:1.8: Invalid character: -");
+ checkError("option[0ab].text",
+ "<string>:1.9-10: syntax error, unexpected option name, "
+ "expecting ]");
+ checkError("option[ab_].hex", "<string>:1.8: Invalid character: a");
+ checkError("option[\nhost-name\n].hex =\n= 'foo'",
+ "<string>:3.7: Invalid character: =");
+ checkError("substring('foo',12345678901234567890,1)",
+ "<string>:1.17-36: Failed to convert 12345678901234567890 "
+ "to an integer.");
+}
+
+// Tests some parser error cases
+TEST_F(EvalContextTest, parseErrors) {
+ checkError("'foo''bar'",
+ "<string>:1.6-10: syntax error, unexpected constant string, "
+ "expecting == or +");
+ checkError("'foo' (",
+ "<string>:1.7: syntax error, unexpected (, expecting == or +");
+ checkError("== 'ab'", "<string>:1.1-2: syntax error, unexpected ==");
+ checkError("'foo' ==",
+ "<string>:1.9: syntax error, unexpected end of file");
+ checkError("('foo' == 'bar'",
+ "<string>:1.16: syntax error, unexpected end of file, "
+ "expecting ) or and or or");
+ checkError("('foo' == 'bar') ''",
+ "<string>:1.18-19: syntax error, unexpected constant string, "
+ "expecting end of file");
+ checkError("not",
+ "<string>:1.4: syntax error, unexpected end of file");
+ checkError("not 'foo'",
+ "<string>:1.10: syntax error, unexpected end of file, "
+ "expecting == or +");
+ checkError("not()",
+ "<string>:1.5: syntax error, unexpected )");
+ checkError("(not('foo' 'bar')",
+ "<string>:1.12-16: syntax error, unexpected constant string, "
+ "expecting ) or == or +");
+ checkError("and",
+ "<string>:1.1-3: syntax error, unexpected and");
+ checkError("'foo' and",
+ "<string>:1.7-9: syntax error, unexpected and, "
+ "expecting == or +");
+ checkError("'foo' == 'bar' and",
+ "<string>:1.19: syntax error, unexpected end of file");
+ checkError("'foo' == 'bar' and ''",
+ "<string>:1.22: syntax error, unexpected end of file, "
+ "expecting == or +");
+ checkError("or",
+ "<string>:1.1-2: syntax error, unexpected or");
+ checkError("'foo' or",
+ "<string>:1.7-8: syntax error, unexpected or, "
+ "expecting == or +");
+ checkError("'foo' == 'bar' or",
+ "<string>:1.18: syntax error, unexpected end of file");
+ checkError("'foo' == 'bar' or ''",
+ "<string>:1.21: syntax error, unexpected end of file, "
+ "expecting == or +");
+ checkError("option 'ab'",
+ "<string>:1.8-11: syntax error, unexpected "
+ "constant string, expecting [");
+ checkError("option(10) == 'ab'",
+ "<string>:1.7: syntax error, "
+ "unexpected (, expecting [");
+ checkError("option['ab'].text == 'foo'",
+ "<string>:1.8-11: syntax error, "
+ "unexpected constant string, "
+ "expecting integer or option name");
+ checkError("option[ab].text == 'foo'",
+ "<string>:1.8-9: option 'ab' is not defined");
+ checkError("option[0xa].text == 'ab'",
+ "<string>:1.8-10: syntax error, "
+ "unexpected constant hexstring, "
+ "expecting integer or option name");
+ checkError("option[10].bin", "<string>:1.12: Invalid character: b");
+ checkError("option[boot-size].bin", "<string>:1.19: Invalid character: b");
+ checkError("option[10].exists == 'foo'",
+ "<string>:1.19-20: syntax error, unexpected ==, "
+ "expecting end of file");
+ checkError("substring('foobar') == 'f'",
+ "<string>:1.19: syntax error, unexpected ), "
+ "expecting \",\" or +");
+ checkError("substring('foobar',3) == 'bar'",
+ "<string>:1.21: syntax error, unexpected ), expecting \",\"");
+ checkError("substring('foobar','3',3) == 'bar'",
+ "<string>:1.20-22: syntax error, unexpected constant string, "
+ "expecting integer");
+ checkError("substring('foobar',1,a) == 'foo'",
+ "<string>:1.22: Invalid character: a");
+ string long_text = "substring('foobar',1,65535) == ";
+ for (int i = 0; i < (1 << 16); ++i) {
+ long_text += "0";
+ }
+ long_text += "'";
+ checkError(long_text,
+ "<string>:1.65568: Invalid character: '");
+ checkError("concat('foobar') == 'f'",
+ "<string>:1.16: syntax error, unexpected ), "
+ "expecting \",\" or +");
+ checkError("concat('foo','bar','') == 'foobar'",
+ "<string>:1.19: syntax error, unexpected \",\", "
+ "expecting ) or +");
+ checkError("ifelse('foo'=='bar','foo')",
+ "<string>:1.26: syntax error, unexpected ), "
+ "expecting \",\" or +");
+ checkError("ifelse('foo'=='bar','foo','bar','')",
+ "<string>:1.32: syntax error, unexpected \",\", "
+ "expecting ) or +");
+ checkError("+ 'a' = 'a'", "<string>:1.1: syntax error, unexpected +");
+ checkError("'a' + == 'a'", "<string>:1.7-8: syntax error, unexpected ==");
+ checkError("'a' ++ 'b' == 'ab'",
+ "<string>:1.6: syntax error, unexpected +");
+ checkError("addrtotext(10.0.0.1, 10.0.0.2)",
+ "<string>:1.20: syntax error, unexpected \",\", expecting ) or +");
+ checkError("addrtotext('cafebabecafebabe')",
+ "<string>:1.31: syntax error, unexpected end of file, expecting == or +");
+ checkError("addrtotext('')",
+ "<string>:1.15: syntax error, unexpected end of file, expecting == or +");
+ checkError("int8totext('01', '01')",
+ "<string>:1.16: syntax error, unexpected \",\", expecting ) or +");
+ checkError("int8totext('0123')",
+ "<string>:1.19: syntax error, unexpected end of file, expecting == or +");
+ checkError("int8totext('')",
+ "<string>:1.15: syntax error, unexpected end of file, expecting == or +");
+ checkError("int16totext('0123', '0123')",
+ "<string>:1.19: syntax error, unexpected \",\", expecting ) or +");
+ checkError("int16totext('01')",
+ "<string>:1.18: syntax error, unexpected end of file, expecting == or +");
+ checkError("int16totext('')",
+ "<string>:1.16: syntax error, unexpected end of file, expecting == or +");
+ checkError("int32totext('01234567', '01234567')",
+ "<string>:1.23: syntax error, unexpected \",\", expecting ) or +");
+ checkError("int32totext('01')",
+ "<string>:1.18: syntax error, unexpected end of file, expecting == or +");
+ checkError("int32totext('')",
+ "<string>:1.16: syntax error, unexpected end of file, expecting == or +");
+ checkError("uint8totext('01', '01')",
+ "<string>:1.17: syntax error, unexpected \",\", expecting ) or +");
+ checkError("uint8totext('0123')",
+ "<string>:1.20: syntax error, unexpected end of file, expecting == or +");
+ checkError("uint8totext('')",
+ "<string>:1.16: syntax error, unexpected end of file, expecting == or +");
+ checkError("uint16totext('0123', '0123')",
+ "<string>:1.20: syntax error, unexpected \",\", expecting ) or +");
+ checkError("uint16totext('01')",
+ "<string>:1.19: syntax error, unexpected end of file, expecting == or +");
+ checkError("uint16totext('')",
+ "<string>:1.17: syntax error, unexpected end of file, expecting == or +");
+ checkError("uint32totext('01234567', '01234567')",
+ "<string>:1.24: syntax error, unexpected \",\", expecting ) or +");
+ checkError("uint32totext('01')",
+ "<string>:1.19: syntax error, unexpected end of file, expecting == or +");
+ checkError("uint32totext('')",
+ "<string>:1.17: syntax error, unexpected end of file, expecting == or +");
+}
+
+// Tests some type error cases
+TEST_F(EvalContextTest, typeErrors) {
+ checkError("'foobar'",
+ "<string>:1.9: syntax error, unexpected end of file, "
+ "expecting == or +");
+ checkError("substring('foobar',all,1) == 'foo'",
+ "<string>:1.20-22: syntax error, unexpected all, "
+ "expecting integer");
+ checkError("substring('foobar',0x32,1) == 'foo'",
+ "<string>:1.20-23: syntax error, unexpected constant "
+ "hexstring, expecting integer");
+
+ // With the #4483 addition, all integers are treated as 4 byte strings,
+ // so those checks no longer makes sense. Commenting it out.
+ // checkError("concat('foo',3) == 'foo3'",
+ // "<string>:1.14: syntax error, unexpected integer");
+ // checkError("concat(3,'foo') == '3foo'",
+ // "<string>:1.8: syntax error, unexpected integer");
+ checkError("('foo' == 'bar') == 'false'",
+ "<string>:1.18-19: syntax error, unexpected ==, "
+ "expecting end of file");
+ checkError("not 'true'",
+ "<string>:1.11: syntax error, unexpected end of file, "
+ "expecting == or +");
+ checkError("'true' and 'false'",
+ "<string>:1.8-10: syntax error, unexpected and, "
+ "expecting == or +");
+ checkError("'true' or 'false'",
+ "<string>:1.8-9: syntax error, unexpected or, "
+ "expecting == or +");
+
+ // Ifelse requires a boolean condition and string branches.
+ checkError("ifelse('foobar','foo','bar')",
+ "<string>:1.16: syntax error, unexpected \",\", "
+ "expecting == or +");
+ checkError("ifelse('foo'=='bar','foo'=='foo','bar')",
+ "<string>:1.26-27: syntax error, unexpected ==, "
+ "expecting \",\" or +");
+ checkError("ifelse('foo'=='bar','foo','bar'=='bar')",
+ "<string>:1.32-33: syntax error, unexpected ==, "
+ "expecting ) or +");
+
+ // Member uses quotes around the client class name.
+ checkError("member(foo)", "<string>:1.8: Invalid character: f");
+
+ // sub-option by name is not supported.
+ checkError("option[123].option[host-name].exists",
+ "<string>:1.20-28: syntax error, unexpected option name, "
+ "expecting integer");
+
+ // Addrtotext requires string storing the binary representation of the address.
+ checkError("addrtotext('192.100.1.1')",
+ "<string>:1.26: syntax error, unexpected end of file, expecting == or +");
+
+ // Int8totext requires string storing the binary representation of the 8 bit integer.
+ checkError("int8totext('0123')",
+ "<string>:1.19: syntax error, unexpected end of file, expecting == or +");
+
+ // Int16totext requires string storing the binary representation of the 16 bit integer.
+ checkError("int16totext('01')",
+ "<string>:1.18: syntax error, unexpected end of file, expecting == or +");
+
+ // Int32totext requires string storing the binary representation of the 32 bit integer.
+ checkError("int32totext('01')",
+ "<string>:1.18: syntax error, unexpected end of file, expecting == or +");
+
+ // Uint8totext requires string storing the binary representation of the 8 bit unsigned integer.
+ checkError("uint8totext('0123')",
+ "<string>:1.20: syntax error, unexpected end of file, expecting == or +");
+
+ // Uint16totext requires string storing the binary representation of the 16 bit unsigned integer.
+ checkError("uint16totext('01')",
+ "<string>:1.19: syntax error, unexpected end of file, expecting == or +");
+
+ // Uint32totext requires string storing the binary representation of the 32 bit unsigned integer.
+ checkError("uint32totext('01')",
+ "<string>:1.19: syntax error, unexpected end of file, expecting == or +");
+}
+
+TEST_F(EvalContextTest, vendor4SpecificVendorExists) {
+ testVendor("vendor[4491].exists", Option::V4, 4491, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor6SpecificVendorExists) {
+ testVendor("vendor[4491].exists", Option::V6, 4491, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor4AnyVendorExists) {
+ testVendor("vendor[*].exists", Option::V4, 0, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor6AnyVendorExists) {
+ testVendor("vendor[*].exists", Option::V6, 0, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor4enterprise) {
+ testVendorEnterprise("vendor.enterprise == 0x1234", Option::V4);
+}
+
+TEST_F(EvalContextTest, vendor6enterprise) {
+ testVendorEnterprise("vendor.enterprise == 0x1234", Option::V6);
+}
+
+TEST_F(EvalContextTest, vendor4SuboptionExists) {
+ testVendor("vendor[4491].option[1].exists", Option::V4, 4491, 1, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor6SuboptionExists) {
+ testVendor("vendor[4491].option[1].exists", Option::V6, 4491, 1, TokenOption::EXISTS);
+}
+
+TEST_F(EvalContextTest, vendor4SuboptionHex) {
+ testVendor("vendor[4491].option[1].hex == 0x1234", Option::V4, 4491, 1,
+ TokenOption::HEXADECIMAL);
+}
+
+TEST_F(EvalContextTest, vendor6SuboptionHex) {
+ testVendor("vendor[4491].option[1].hex == 0x1234", Option::V6, 4491, 1,
+ TokenOption::HEXADECIMAL);
+}
+
+TEST_F(EvalContextTest, vendorClass4SpecificVendorExists) {
+ testVendorClass("vendor-class[4491].exists", Option::V4, 4491);
+}
+
+TEST_F(EvalContextTest, vendorClass6SpecificVendorExists) {
+ testVendorClass("vendor-class[4491].exists", Option::V6, 4491);
+}
+
+TEST_F(EvalContextTest, vendorClass4AnyVendorExists) {
+ testVendorClass("vendor-class[*].exists", Option::V4, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass6AnyVendorExists) {
+ testVendorClass("vendor-class[*].exists", Option::V6, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass4enterprise) {
+ testVendorClassEnterprise("vendor-class.enterprise == 0x1234", Option::V4);
+}
+
+TEST_F(EvalContextTest, vendorClass6enterprise) {
+ testVendorClassEnterprise("vendor-class.enterprise == 0x1234", Option::V6);
+}
+
+TEST_F(EvalContextTest, vendorClass4SpecificVendorData) {
+ testVendorClass("vendor-class[4491].data == 0x1234", Option::V4, 4491, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass6SpecificVendorData) {
+ testVendorClass("vendor-class[4491].data == 0x1234", Option::V6, 4491, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass4AnyVendorData) {
+ testVendorClass("vendor-class[*].data == 0x1234", Option::V4, 0, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass6AnyVendorData) {
+ testVendorClass("vendor-class[*].data == 0x1234", Option::V6, 0, 0);
+}
+
+TEST_F(EvalContextTest, vendorClass4DataIndex) {
+ testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V4, 4491, 3);
+}
+
+TEST_F(EvalContextTest, vendorClass6DataIndex) {
+ testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V6, 4491, 3);
+}
+
+// Test the parsing of a sub-option with parent by code.
+TEST_F(EvalContextTest, subOptionWithCode) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].option[234].text == 'foo'"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenSubOption(eval.expression.at(0), 123, 234, TokenOption::TEXTUAL);
+}
+
+// Test the parsing of a sub-option with parent by name.
+TEST_F(EvalContextTest, subOptionWithName) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].option[123].text == 'foo'"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenSubOption(eval.expression.at(0), 12, 123, TokenOption::TEXTUAL);
+}
+
+// Test the parsing of a sub-option existence
+TEST_F(EvalContextTest, subOptionExists) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[100].option[200].exists"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(1, eval.expression.size());
+ checkTokenSubOption(eval.expression.at(0), 100, 200, TokenOption::EXISTS);
+}
+
+// Test parsing of a sub-option represented as hexadecimal string.
+TEST_F(EvalContextTest, subOptionHex) {
+ EvalContext eval(Option::V4);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].option[234].hex == 0x666F6F"));
+ EXPECT_TRUE(parsed_);
+ ASSERT_EQ(3, eval.expression.size());
+ checkTokenSubOption(eval.expression.at(0), 123, 234, TokenOption::HEXADECIMAL);
+}
+
+// Checks if integer expressions can be parsed and checked for equality.
+TEST_F(EvalContextTest, integer1) {
+
+ EvalContext eval(Option::V6);
+
+ EXPECT_NO_THROW(parsed_ = eval.parseString("1 == 2"));
+ EXPECT_TRUE(parsed_);
+
+ ASSERT_EQ(3, eval.expression.size());
+
+ TokenPtr tmp = eval.expression.at(0);
+ ASSERT_TRUE(tmp);
+ checkTokenInteger(tmp, 1);
+ tmp = eval.expression.at(1);
+
+ ASSERT_TRUE(tmp);
+ checkTokenInteger(tmp, 2);
+}
+
+}
diff --git a/src/lib/eval/tests/dependency_unittest.cc b/src/lib/eval/tests/dependency_unittest.cc
new file mode 100644
index 0000000..19f9121
--- /dev/null
+++ b/src/lib/eval/tests/dependency_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2018,2021 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 <eval/dependency.h>
+#include <eval/eval_context.h>
+#include <eval/token.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_string.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture for testing dependency.
+///
+/// This class provides several convenience objects to be used during testing
+/// of the dependency of classification expressions.
+class DependencyTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ DependencyTest() : result_(true) {
+ }
+
+ /// @brief Destructor
+ ///
+ /// Reset expression and result.
+ ~DependencyTest() {
+ e_.reset();
+ result_ = false;
+ }
+
+ ExpressionPtr e_; ///< An expression
+
+ bool result_; ///< A decision
+};
+
+// This checks the null expression: it should return false.
+TEST_F(DependencyTest, nullExpr) {
+ TokenPtr token;
+ ASSERT_NO_THROW(result_ = dependOnClass(token, "foobar"));
+ EXPECT_FALSE(result_);
+ ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar"));
+ EXPECT_FALSE(result_);
+}
+
+// This checks the empty expression: it should return false.
+TEST_F(DependencyTest, emptyExpr) {
+ e_.reset(new Expression());
+ ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar"));
+ EXPECT_FALSE(result_);
+}
+
+// This checks the { "true" } expression: it should return false.
+TEST_F(DependencyTest, trueExpr) {
+ TokenPtr ttrue;
+ ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
+ ASSERT_NO_THROW(result_ = dependOnClass(ttrue, "foobar"));
+ EXPECT_FALSE(result_);
+ e_.reset(new Expression());
+ e_->push_back(ttrue);
+ ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar"));
+ EXPECT_FALSE(result_);
+}
+
+// This checks the { member('not-matching') } expression:
+// it should return false.
+TEST_F(DependencyTest, notMatching) {
+ TokenPtr notmatching;
+ ASSERT_NO_THROW(notmatching.reset(new TokenMember("not-matching")));
+ ASSERT_NO_THROW(result_ = dependOnClass(notmatching, "foobar"));
+ EXPECT_FALSE(result_);
+ e_.reset(new Expression());
+ e_->push_back(notmatching);
+ ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar"));
+ EXPECT_FALSE(result_);
+}
+
+// This checks the { member('foobar') } expression: it should return true.
+TEST_F(DependencyTest, matching) {
+ TokenPtr matching;
+ ASSERT_NO_THROW(matching.reset(new TokenMember("foobar")));
+ ASSERT_NO_THROW(result_ = dependOnClass(matching, "foobar"));
+ EXPECT_TRUE(result_);
+ e_.reset(new Expression());
+ e_->push_back(matching);
+ result_ = false;
+ ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar"));
+ EXPECT_TRUE(result_);
+}
+
+};
diff --git a/src/lib/eval/tests/evaluate_unittest.cc b/src/lib/eval/tests/evaluate_unittest.cc
new file mode 100644
index 0000000..37c4f83
--- /dev/null
+++ b/src/lib/eval/tests/evaluate_unittest.cc
@@ -0,0 +1,515 @@
+// Copyright (C) 2015-2018,2021 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 <eval/evaluate.h>
+#include <eval/eval_context.h>
+#include <eval/token.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_string.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture for testing evaluation.
+///
+/// This class provides several convenience objects to be used during testing
+/// of the evaluation of classification expressions.
+class EvaluateTest : public ::testing::Test {
+public:
+
+ /// @brief Initializes Pkt4,Pkt6 and options that can be useful for
+ /// evaluation tests.
+ EvaluateTest() {
+ e_.clear();
+
+ result_ = false;
+
+ pkt4_.reset(new Pkt4(DHCPDISCOVER, 12345));
+ pkt6_.reset(new Pkt6(DHCPV6_SOLICIT, 12345));
+
+ // Add options with easily identifiable strings in them
+ option_str4_.reset(new OptionString(Option::V4, 100, "hundred4"));
+ option_str6_.reset(new OptionString(Option::V6, 100, "hundred6"));
+
+ pkt4_->addOption(option_str4_);
+ pkt6_->addOption(option_str6_);
+ }
+
+ Expression e_; ///< An expression
+
+ bool result_; ///< A decision
+
+ Pkt4Ptr pkt4_; ///< A stub DHCPv4 packet
+ Pkt6Ptr pkt6_; ///< A stub DHCPv6 packet
+
+ OptionPtr option_str4_; ///< A string option for DHCPv4
+ OptionPtr option_str6_; ///< A string option for DHCPv6
+
+ /// @todo: Add more option types here
+};
+
+// This checks the empty expression: it should raise EvalBadStack
+// when evaluated with a Pkt4. (The actual packet is not used)
+TEST_F(EvaluateTest, empty4) {
+ ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack);
+}
+
+// This checks the empty expression: it should raise EvalBadStack
+// when evaluated with a Pkt6. (The actual packet is not used)
+TEST_F(EvaluateTest, empty6) {
+ ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack);
+}
+
+// This checks the { "false" } expression: it should return false
+// when evaluated with a Pkt4. (The actual packet is not used)
+TEST_F(EvaluateTest, false4) {
+ TokenPtr tfalse;
+ ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
+ e_.push_back(tfalse);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_FALSE(result_);
+}
+
+// This checks the { "false" } expression: it should return false
+// when evaluated with a Pkt6. (The actual packet is not used)
+TEST_F(EvaluateTest, false6) {
+ TokenPtr tfalse;
+ ASSERT_NO_THROW(tfalse.reset(new TokenString("false")));
+ e_.push_back(tfalse);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_FALSE(result_);
+}
+
+// This checks the { "true" } expression: it should return true
+// when evaluated with a Pkt4. (The actual packet is not used)
+TEST_F(EvaluateTest, true4) {
+ TokenPtr ttrue;
+ ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
+ e_.push_back(ttrue);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_TRUE(result_);
+}
+
+// This checks the { "true" } expression: it should return true
+// when evaluated with a Pkt6. (The actual packet is not used)
+TEST_F(EvaluateTest, true6) {
+ TokenPtr ttrue;
+ ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
+ e_.push_back(ttrue);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_TRUE(result_);
+}
+
+// This checks the evaluation must lead to "false" or "true"
+// with a Pkt4. (The actual packet is not used)
+TEST_F(EvaluateTest, bad4) {
+ TokenPtr bad;
+ ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
+ e_.push_back(bad);
+ ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalTypeError);
+}
+
+// This checks the evaluation must lead to "false" or "true"
+// with a Pkt6. (The actual packet is not used)
+TEST_F(EvaluateTest, bad6) {
+ TokenPtr bad;
+ ASSERT_NO_THROW(bad.reset(new TokenString("bad")));
+ e_.push_back(bad);
+ ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalTypeError);
+}
+
+// This checks the evaluation must leave only one value on the stack
+// with a Pkt4. (The actual packet is not used)
+TEST_F(EvaluateTest, two4) {
+ TokenPtr ttrue;
+ ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
+ e_.push_back(ttrue);
+ e_.push_back(ttrue);
+ ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack);
+}
+
+// This checks the evaluation must leave only one value on the stack
+// with a Pkt6. (The actual packet is not used)
+TEST_F(EvaluateTest, two6) {
+ TokenPtr ttrue;
+ ASSERT_NO_THROW(ttrue.reset(new TokenString("true")));
+ e_.push_back(ttrue);
+ e_.push_back(ttrue);
+ ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack);
+}
+
+// A more complex test evaluated with a Pkt4. (The actual packet is not used)
+TEST_F(EvaluateTest, compare4) {
+ TokenPtr tfoo;
+ TokenPtr tbar;
+ TokenPtr tequal;
+
+ ASSERT_NO_THROW(tfoo.reset(new TokenString("foo")));
+ e_.push_back(tfoo);
+ ASSERT_NO_THROW(tbar.reset(new TokenString("bar")));
+ e_.push_back(tbar);
+ ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+ e_.push_back(tequal);
+
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_FALSE(result_);
+}
+
+// A more complex test evaluated with a Pkt6. (The actual packet is not used)
+TEST_F(EvaluateTest, compare6) {
+ TokenPtr tfoo;
+ TokenPtr tbar;
+ TokenPtr tequal;
+
+ ASSERT_NO_THROW(tfoo.reset(new TokenString("foo")));
+ e_.push_back(tfoo);
+ ASSERT_NO_THROW(tbar.reset(new TokenString("bar")));
+ e_.push_back(tbar);
+ ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+ e_.push_back(tequal);
+
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_FALSE(result_);
+}
+
+// A test using option existence
+TEST_F(EvaluateTest, exists) {
+ TokenPtr toption;
+
+ ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::EXISTS)));
+ e_.push_back(toption);
+
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_TRUE(result_);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_TRUE(result_);
+}
+
+// A test using option non-existence
+TEST_F(EvaluateTest, dontExists) {
+ TokenPtr toption;
+
+ ASSERT_NO_THROW(toption.reset(new TokenOption(101, TokenOption::EXISTS)));
+ e_.push_back(toption);
+
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_FALSE(result_);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_FALSE(result_);
+}
+
+// A test using packets.
+TEST_F(EvaluateTest, packet) {
+ TokenPtr toption;
+ TokenPtr tstring;
+ TokenPtr tequal;
+
+ ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::TEXTUAL)));
+ e_.push_back(toption);
+ ASSERT_NO_THROW(tstring.reset(new TokenString("hundred4")));
+ e_.push_back(tstring);
+ ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+ e_.push_back(tequal);
+
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_TRUE(result_);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_FALSE(result_);
+}
+
+// A test which compares option value represented in hexadecimal format.
+TEST_F(EvaluateTest, optionHex) {
+ TokenPtr toption;
+ TokenPtr tstring;
+ TokenPtr tequal;
+
+ ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::HEXADECIMAL)));
+ e_.push_back(toption);
+ ASSERT_NO_THROW(tstring.reset(new TokenString("hundred4")));
+ e_.push_back(tstring);
+ ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+ e_.push_back(tequal);
+
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_TRUE(result_);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_FALSE(result_);
+}
+
+// A test using substring on an option.
+TEST_F(EvaluateTest, complex) {
+ TokenPtr toption;
+ TokenPtr tstart;
+ TokenPtr tlength;
+ TokenPtr tsubstring;
+ TokenPtr tstring;
+ TokenPtr tequal;
+
+ // Get the option, i.e., "hundred[46]"
+ ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::TEXTUAL)));
+ e_.push_back(toption);
+
+ // Get substring("hundred[46]", 0, 7), i.e., "hundred"
+ ASSERT_NO_THROW(tstart.reset(new TokenString("0")));
+ e_.push_back(tstart);
+ ASSERT_NO_THROW(tlength.reset(new TokenString("7")));
+ e_.push_back(tlength);
+ ASSERT_NO_THROW(tsubstring.reset(new TokenSubstring()));
+ e_.push_back(tsubstring);
+
+ // Compare with "hundred"
+ ASSERT_NO_THROW(tstring.reset(new TokenString("hundred")));
+ e_.push_back(tstring);
+ ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+ e_.push_back(tequal);
+
+ // Should return true for v4 and v6 packets
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_));
+ EXPECT_TRUE(result_);
+ ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_));
+ EXPECT_TRUE(result_);
+}
+
+/// @brief Generic class for parsing expressions and evaluating them.
+///
+/// The main purpose of this class is to provide a generic interface to the
+/// eval library, so everything (expression parsing and then evaluation for
+/// given packets) can be done in one simple call.
+///
+/// These tests may be somewhat redundant to other more specialized tests, but
+/// the idea here is to mass produce tests that are trivial to write.
+class ExpressionsTest : public EvaluateTest {
+public:
+
+ /// @brief Checks if expression can be parsed and evaluated to bool
+ ///
+ /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you
+ /// tweak them as needed before calling this method.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param expr expression to be parsed
+ /// @param exp_result expected result (true or false)
+ void testExpression(const Option::Universe& u, const std::string& expr,
+ const bool exp_result) {
+
+ EvalContext eval(u);
+ bool result = false;
+ bool parsed = false;
+
+ EXPECT_NO_THROW(parsed = eval.parseString(expr))
+ << " while parsing expression " << expr;
+ EXPECT_TRUE(parsed) << " for expression " << expr;
+
+ switch (u) {
+ case Option::V4:
+ ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt4_))
+ << " for expression " << expr;
+ break;
+ case Option::V6:
+ ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt6_))
+ << " for expression " << expr;
+ break;
+ }
+
+ EXPECT_EQ(exp_result, result) << " for expression " << expr;
+ }
+
+ /// @brief Checks if expression can be parsed and evaluated to string
+ ///
+ /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you
+ /// tweak them as needed before calling this method.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param expr expression to be parsed
+ /// @param exp_result expected result (string)
+ void testExpressionString(const Option::Universe& u, const std::string& expr,
+ const std::string& exp_result) {
+
+ EvalContext eval(u);
+ string result;
+ bool parsed = false;
+
+ EXPECT_NO_THROW(parsed = eval.parseString(expr, EvalContext::PARSER_STRING))
+ << " while parsing expression " << expr;
+ EXPECT_TRUE(parsed) << " for expression " << expr;
+
+ switch (u) {
+ case Option::V4:
+ ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt4_))
+ << " for expression " << expr;
+ break;
+ case Option::V6:
+ ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt6_))
+ << " for expression " << expr;
+ break;
+ }
+
+ EXPECT_EQ(exp_result, result) << " for expression " << expr;
+ }
+
+ /// @brief Checks that specified expression throws expected exception.
+ ///
+ /// @tparam ex exception type expected to be thrown
+ /// @param expr expression to be evaluated
+ template<typename ex>
+ void testExpressionNegative(const std::string& expr,
+ const Option::Universe& u = Option::V4,
+ EvalContext::ParserType type = EvalContext::PARSER_BOOL) {
+ EvalContext eval(u);
+
+ EXPECT_THROW(eval.parseString(expr, type), ex) << "while parsing expression "
+ << expr;
+ }
+};
+
+// This is a quick way to check if certain expressions are valid or not and
+// whether the whole expression makes sense. This particular test checks if
+// integers can be used properly in expressions. There are many places where
+// integers are used. This particular test checks if pkt6.msgtype returns
+// something that can be compared with integers.
+//
+// For basic things we can take advantage of the skeleton packets created in
+// EvaluateTest constructors: The packet type is DISCOVER in DHCPv4 and
+// SOLICIT in DHCPv6. There is one option added with code 100 and content
+// being either "hundred4" or "hundred6" depending on the universe.
+
+// Tests if pkt6.msgtype returns something that can be compared with integers.
+TEST_F(ExpressionsTest, expressionsInteger1) {
+ testExpression(Option::V6, "pkt6.msgtype == 1", true);
+ testExpression(Option::V6, "pkt6.msgtype == 2", false);
+
+ testExpression(Option::V6, "pkt6.msgtype == 0x00000001", true);
+ testExpression(Option::V6, "pkt6.msgtype == 0x00000002", false);
+}
+
+// Tests if pkt6.transid returns something that can be compared with integers.
+TEST_F(ExpressionsTest, expressionsInteger2) {
+ testExpression(Option::V6, "pkt6.transid == 0", false);
+ testExpression(Option::V6, "pkt6.transid == 12345", true);
+ testExpression(Option::V6, "pkt6.transid == 12346", false);
+}
+
+// Tests if pkt4.transid returns something that can be compared with integers.
+TEST_F(ExpressionsTest, expressionsInteger3) {
+ testExpression(Option::V4, "pkt4.transid == 0", false);
+ testExpression(Option::V4, "pkt4.transid == 12345", true);
+ testExpression(Option::V4, "pkt4.transid == 12346", false);
+}
+
+// Tests if integers can be compared with integers.
+TEST_F(ExpressionsTest, expressionsInteger4) {
+ testExpression(Option::V6, "0 == 0", true);
+ testExpression(Option::V6, "2 == 3", false);
+}
+
+// Tests if pkt4.hlen and pkt4.htype return values that can be compared with integers.
+TEST_F(ExpressionsTest, expressionsPkt4Hlen) {
+
+ // By default there's no hardware set up. The default Pkt4 constructor
+ // creates HWAddr(), which has hlen=0 and htype set to HTYPE_ETHER.
+ testExpression(Option::V4, "pkt4.hlen == 0", true);
+ testExpression(Option::V4, "pkt4.htype == 1", true);
+
+ // Ok, let's initialize the hardware address to something plausible.
+ const size_t hwaddr_len = 6;
+ const uint16_t expected_htype = 123;
+ std::vector<uint8_t> hw(hwaddr_len,0);
+ for (int i = 0; i < hwaddr_len; i++) {
+ hw[i] = i + 1;
+ }
+ pkt4_->setHWAddr(expected_htype, hwaddr_len, hw);
+
+ testExpression(Option::V4, "pkt4.hlen == 0", false);
+ testExpression(Option::V4, "pkt4.hlen == 5", false);
+ testExpression(Option::V4, "pkt4.hlen == 6", true);
+ testExpression(Option::V4, "pkt4.hlen == 7", false);
+
+ testExpression(Option::V4, "pkt4.htype == 0", false);
+ testExpression(Option::V4, "pkt4.htype == 122", false);
+ testExpression(Option::V4, "pkt4.htype == 123", true);
+ testExpression(Option::V4, "pkt4.htype == 124", false);
+
+ testExpression(Option::V4, "pkt4.mac == 0x010203040506", true);
+}
+
+// Test if expressions message type can be detected in Pkt4.
+// It also doubles as a check for integer comparison here.
+TEST_F(ExpressionsTest, expressionsPkt4type) {
+
+ // We can inspect the option content directly, but
+ // it requires knowledge of the option type and its format.
+ testExpression(Option::V4, "option[53].hex == 0x0", false);
+ testExpression(Option::V4, "option[53].hex == 0x1", true);
+ testExpression(Option::V4, "option[53].hex == 0x2", false);
+
+ // It's easier to simply use the pkt4.msgtype
+ testExpression(Option::V4, "pkt4.msgtype == 0", false);
+ testExpression(Option::V4, "pkt4.msgtype == 1", true);
+ testExpression(Option::V4, "pkt4.msgtype == 2", false);
+}
+
+// This tests if inappropriate values (negative, too large) are
+// rejected, but extreme values still allowed for uint32_t are ok.
+TEST_F(ExpressionsTest, invalidIntegers) {
+
+ // These are the extreme uint32_t values that still should be accepted.
+ testExpression(Option::V4, "4294967295 == 0", false);
+
+ // Negative integers should be rejected.
+ testExpressionNegative<EvalParseError>("4294967295 == -1");
+
+ // Oops, one too much.
+ testExpressionNegative<EvalParseError>("4294967296 == 0");
+}
+
+// Tests whether expressions can be evaluated to a string.
+TEST_F(ExpressionsTest, evaluateString) {
+
+ // Check that content of the options is returned properly.
+ testExpressionString(Option::V4, "option[100].hex", "hundred4");
+ testExpressionString(Option::V6, "option[100].hex", "hundred6");
+
+ // Check that content of non-existing option returns empty string.
+ testExpressionString(Option::V4, "option[200].hex", "");
+ testExpressionString(Option::V6, "option[200].hex", "");
+
+ testExpressionNegative<EvalParseError>("pkt4.msgtype == 1", Option::V4,
+ EvalContext::PARSER_STRING);
+ testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6,
+ EvalContext::PARSER_STRING);
+
+ // Check that ifelse works as expecting (it was added explicitly for
+ // the string evaluation).
+ testExpressionString(Option::V4,
+ "ifelse(option[100].exists,'foo','bar')", "foo");
+ testExpressionString(Option::V4,
+ "ifelse(option[200].exists,'foo','bar')", "bar");
+
+ // Check that ifelse can be chained.
+ testExpressionString(Option::V4,
+ "ifelse(option[200].exists,option[200].hex,"
+ "ifelse(option[100].exists,"
+ "option[100].hex,'none?'))",
+ "hundred4");
+
+ // Check that hexstring works as expecting.
+ testExpressionString(Option::V4, "hexstring(0x1234,':')", "12:34");
+ testExpressionString(Option::V4, "hexstring(0x56789a,'-')", "56-78-9a");
+ testExpressionString(Option::V4, "hexstring(0xbcde,'')", "bcde");
+ testExpressionString(Option::V4, "hexstring(0xf01234,'..')", "f0..12..34");
+}
+
+};
diff --git a/src/lib/eval/tests/run_unittests.cc b/src/lib/eval/tests/run_unittests.cc
new file mode 100644
index 0000000..08f04d1
--- /dev/null
+++ b/src/lib/eval/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 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 <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc
new file mode 100644
index 0000000..bc70927
--- /dev/null
+++ b/src/lib/eval/tests/token_unittest.cc
@@ -0,0 +1,3487 @@
+// Copyright (C) 2015-2023 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 <eval/token.h>
+#include <eval/eval_context.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <log/logger_support.h>
+#include <testutils/log_utils.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <arpa/inet.h>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::log;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture for testing Tokens.
+///
+/// This class provides several convenience objects to be used during testing
+/// of the Token family of classes.
+
+class TokenTest : public LogContentTest {
+public:
+
+ /// @brief Initializes Pkt4, Pkt6 and options that can be useful for
+ /// evaluation tests.
+ TokenTest() {
+ pkt4_.reset(new Pkt4(DHCPDISCOVER, 12345));
+ pkt6_.reset(new Pkt6(DHCPV6_SOLICIT, 12345));
+
+ // Add options with easily identifiable strings in them
+ option_str4_.reset(new OptionString(Option::V4, 100, "hundred4"));
+ option_str6_.reset(new OptionString(Option::V6, 100, "hundred6"));
+
+ pkt4_->addOption(option_str4_);
+ pkt6_->addOption(option_str6_);
+
+ // Change this to true if you need extra information about logging
+ // checks to be printed.
+ logCheckVerbose(false);
+ }
+
+ /// @brief Inserts RAI option with several suboptions
+ ///
+ /// The structure inserted is:
+ /// - RAI (option 82)
+ /// - option 1 (containing string "one")
+ /// - option 13 (containing string "thirteen")
+ void insertRelay4Option() {
+
+ // RAI (Relay Agent Information) option
+ OptionPtr rai(new Option(Option::V4, DHO_DHCP_AGENT_OPTIONS));
+ OptionPtr sub1(new OptionString(Option::V4, 1, "one"));
+ OptionPtr sub13(new OptionString(Option::V4, 13, "thirteen"));
+
+ rai->addOption(sub1);
+ rai->addOption(sub13);
+ pkt4_->addOption(rai);
+ }
+
+ /// @brief Adds relay encapsulations with some suboptions
+ ///
+ /// This will add 2 relay encapsulations all will have
+ /// msg_type of RELAY_FORW
+ /// Relay 0 (closest to server) will have
+ /// linkaddr = peeraddr = 0, hop-count = 1
+ /// option 100 "hundred.zero", option 101 "hundredone.zero"
+ /// Relay 1 (closest to client) will have
+ /// linkaddr 1::1= peeraddr = 1::2, hop-count = 0
+ /// option 100 "hundred.one", option 102 "hundredtwo.one"
+ void addRelay6Encapsulations() {
+ // First relay
+ Pkt6::RelayInfo relay0;
+ relay0.msg_type_ = DHCPV6_RELAY_FORW;
+ relay0.hop_count_ = 1;
+ relay0.linkaddr_ = isc::asiolink::IOAddress("::");
+ relay0.peeraddr_ = isc::asiolink::IOAddress("::");
+ OptionPtr optRelay01(new OptionString(Option::V6, 100,
+ "hundred.zero"));
+ OptionPtr optRelay02(new OptionString(Option::V6, 101,
+ "hundredone.zero"));
+
+ relay0.options_.insert(make_pair(optRelay01->getType(), optRelay01));
+ relay0.options_.insert(make_pair(optRelay02->getType(), optRelay02));
+
+ pkt6_->addRelayInfo(relay0);
+ // Second relay
+ Pkt6::RelayInfo relay1;
+ relay1.msg_type_ = DHCPV6_RELAY_FORW;
+ relay1.hop_count_ = 0;
+ relay1.linkaddr_ = isc::asiolink::IOAddress("1::1");
+ relay1.peeraddr_ = isc::asiolink::IOAddress("1::2");
+ OptionPtr optRelay11(new OptionString(Option::V6, 100,
+ "hundred.one"));
+ OptionPtr optRelay12(new OptionString(Option::V6, 102,
+ "hundredtwo.one"));
+
+ relay1.options_.insert(make_pair(optRelay11->getType(), optRelay11));
+ relay1.options_.insert(make_pair(optRelay12->getType(), optRelay12));
+ pkt6_->addRelayInfo(relay1);
+ }
+
+ /// @brief Verify that the relay6 option evaluations work properly
+ ///
+ /// Given the nesting level and option code extract the option
+ /// and compare it to the expected string.
+ ///
+ /// @param test_level The nesting level
+ /// @param test_code The code of the option to extract
+ /// @param result_addr The expected result of the address as a string
+ void verifyRelay6Option(const int8_t test_level,
+ const uint16_t test_code,
+ const TokenOption::RepresentationType& test_rep,
+ const std::string& result_string) {
+ // Create the token
+ ASSERT_NO_THROW(t_.reset(new TokenRelay6Option(test_level,
+ test_code,
+ test_rep)));
+
+ // We should be able to evaluate it
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+
+ // We should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // And it should match the expected result
+ // Invalid nesting levels result in a 0 length string
+ EXPECT_EQ(result_string, values_.top());
+
+ // Then we clear the stack
+ clearStack();
+ }
+
+ /// @brief Verify that the relay6 field evaluations work properly
+ ///
+ /// Given the nesting level, the field to extract and the expected
+ /// address create a token and evaluate it then compare the addresses
+ ///
+ /// @param test_level The nesting level
+ /// @param test_field The type of the field to extract
+ /// @param result_addr The expected result of the address as a string
+ void verifyRelay6Eval(const int8_t test_level,
+ const TokenRelay6Field::FieldType test_field,
+ const int result_len,
+ const uint8_t result_addr[]) {
+ // Create the token
+ ASSERT_NO_THROW(t_.reset(new TokenRelay6Field(test_level, test_field)));
+
+ // We should be able to evaluate it
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+
+ // We should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // And it should match the expected result
+ // Invalid nesting levels result in a 0 length string
+ EXPECT_EQ(result_len, values_.top().size());
+ if (result_len != 0) {
+ EXPECT_EQ(0, memcmp(result_addr, &values_.top()[0], result_len));
+ }
+
+ // Then we clear the stack
+ clearStack();
+ }
+
+ /// @brief Convenience function. Removes token and values stacks.
+ /// @param token specifies if the convenience token should be removed or not
+ void clearStack(bool token = true) {
+ while (!values_.empty()) {
+ values_.pop();
+ }
+ if (token) {
+ t_.reset();
+ }
+ }
+
+ /// @brief Aux. function that stores integer values as 4 byte string.
+ ///
+ /// @param value integer value to be stored
+ /// @return 4 byte long string with encoded value.
+ string encode(uint32_t value) {
+ return EvalContext::fromUint32(value);
+ }
+
+ TokenPtr t_; ///< Just a convenience pointer
+
+ ValueStack values_; ///< evaluated values will be stored here
+
+ Pkt4Ptr pkt4_; ///< A stub DHCPv4 packet
+ Pkt6Ptr pkt6_; ///< A stub DHCPv6 packet
+
+ OptionPtr option_str4_; ///< A string option for DHCPv4
+ OptionPtr option_str6_; ///< A string option for DHCPv6
+
+ OptionVendorPtr vendor_; ///< Vendor option used during tests
+ OptionVendorClassPtr vendor_class_; ///< Vendor class option used during tests
+
+ /// @brief Verify that the substring eval works properly
+ ///
+ /// This function takes the parameters and sets up the value
+ /// stack then executes the eval and checks the results.
+ ///
+ /// @param test_string The string to operate on
+ /// @param test_start The position to start when getting a substring
+ /// @param test_length The length of the substring to get
+ /// @param result_string The expected result of the eval
+ /// @param should_throw The eval will throw
+ void verifySubstringEval(const std::string& test_string,
+ const std::string& test_start,
+ const std::string& test_length,
+ const std::string& result_string,
+ bool should_throw = false) {
+
+ // create the token
+ ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
+
+ // push values on stack
+ values_.push(test_string);
+ values_.push(test_start);
+ values_.push(test_length);
+
+ // evaluate the token
+ if (should_throw) {
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+ ASSERT_EQ(0, values_.size());
+ } else {
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // verify results
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ(result_string, values_.top());
+
+ // remove result
+ values_.pop();
+ }
+ }
+
+ /// @brief Verify that the split eval works properly
+ ///
+ /// This function takes the parameters and sets up the value
+ /// stack then executes the eval and checks the results.
+ ///
+ /// @param test_string The string to operate on
+ /// @param test_delimiters The string of delimiter characters to split upon
+ /// @param test_field The field number of the desired field
+ /// @param result_string The expected result of the eval
+ /// @param should_throw The eval will throw
+ void verifySplitEval(const std::string& test_string,
+ const std::string& test_delimiters,
+ const std::string& test_field,
+ const std::string& result_string,
+ bool should_throw = false) {
+ // create the token
+ ASSERT_NO_THROW(t_.reset(new TokenSplit()));
+
+ // push values on stack
+ values_.push(test_string);
+ values_.push(test_delimiters);
+ values_.push(test_field);
+
+ // evaluate the token
+ if (should_throw) {
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+ ASSERT_EQ(0, values_.size());
+ } else {
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // verify results
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ(result_string, values_.top());
+
+ // remove result
+ values_.pop();
+ }
+ }
+
+ /// @brief Creates vendor-option with specified value and adds it to packet
+ ///
+ /// This method creates specified vendor option, removes any existing
+ /// vendor options and adds the new one to v4 or v6 packet.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id specifies enterprise-id value.
+ void setVendorOption(Option::Universe u, uint32_t vendor_id) {
+ vendor_.reset(new OptionVendor(u, vendor_id));
+ switch (u) {
+ case Option::V4:
+ pkt4_->delOption(DHO_VIVSO_SUBOPTIONS);
+ pkt4_->addOption(vendor_);
+ break;
+ case Option::V6:
+ pkt6_->delOption(D6O_VENDOR_OPTS);
+ pkt6_->addOption(vendor_);
+ break;
+ }
+ }
+
+ /// @brief Creates vendor-class option with specified values and adds it to packet
+ ///
+ /// This method creates specified vendor-class option, removes any existing
+ /// vendor class options and adds the new one to v4 or v6 packet.
+ /// It also creates data tuples with greek alphabet names.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id specifies enterprise-id value.
+ /// @param tuples_size number of data tuples to create.
+ void setVendorClassOption(Option::Universe u, uint32_t vendor_id,
+ size_t tuples_size = 0) {
+ // Create the option first.
+ vendor_class_.reset(new OptionVendorClass(u, vendor_id));
+
+ // Now let's add specified number of data tuples
+ OpaqueDataTuple::LengthFieldType len = (u == Option::V4?OpaqueDataTuple::LENGTH_1_BYTE:
+ OpaqueDataTuple::LENGTH_2_BYTES);
+ const char* content[] = { "alpha", "beta", "delta", "gamma", "epsilon",
+ "zeta", "eta", "theta", "iota", "kappa" };
+ const size_t nb_content = sizeof(content) / sizeof(char*);
+ ASSERT_TRUE(tuples_size < nb_content);
+ for (size_t i = 0; i < tuples_size; ++i) {
+ OpaqueDataTuple tuple(len);
+ tuple.assign(string(content[i]));
+ if (u == Option::V4 && i == 0) {
+ // vendor-class for v4 has a peculiar quirk. The first tuple is being
+ // added, even if there's no data at all.
+ vendor_class_->setTuple(0, tuple);
+ } else {
+ vendor_class_->addTuple(tuple);
+ }
+ }
+
+ switch (u) {
+ case Option::V4:
+ pkt4_->delOption(DHO_VIVCO_SUBOPTIONS);
+ pkt4_->addOption(vendor_class_);
+ break;
+ case Option::V6:
+ pkt6_->delOption(D6O_VENDOR_CLASS);
+ pkt6_->addOption(vendor_class_);
+ break;
+ }
+ }
+
+ /// @brief Auxiliary function that evaluates tokens and checks result
+ ///
+ /// Depending on the universe, either pkt4_ or pkt6_ are supposed to have
+ /// all the necessary values and options set. The result is checked
+ /// on the values_ stack.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param expected_result text representation of the expected outcome
+ void evaluate(Option::Universe u, std::string expected_result) {
+ switch (u) {
+ case Option::V4:
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ break;
+ case Option::V6:
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+ break;
+ default:
+ ADD_FAILURE() << "Invalid universe specified.";
+ }
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ(expected_result, values_.top());
+ }
+
+ /// @brief Tests if vendor token behaves properly.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param token_vendor_id enterprise-id used in the token
+ /// @param option_vendor_id enterprise-id used in option (0 means don't
+ /// create the option)
+ /// @param expected_result text representation of the expected outcome
+ void testVendorExists(Option::Universe u, uint32_t token_vendor_id,
+ uint32_t option_vendor_id,
+ const std::string& expected_result) {
+ // Let's clear any old values, so we can run multiple cases in each test
+ clearStack();
+
+ // Create the token
+ ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id,
+ TokenOption::EXISTS)));
+
+ // If specified option is non-zero, create it.
+ if (option_vendor_id) {
+ setVendorOption(u, option_vendor_id);
+ }
+
+ evaluate(u, expected_result);
+ }
+
+ /// @brief Tests if vendor token properly returns enterprise-id.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param option_vendor_id enterprise-id used in option (0 means don't
+ /// create the option)
+ /// @param expected_result text representation of the expected outcome
+ void testVendorEnterprise(Option::Universe u, uint32_t option_vendor_id,
+ const std::string& expected_result) {
+ // Let's clear any old values, so we can run multiple cases in each test
+ clearStack();
+
+ ASSERT_NO_THROW(t_.reset(new TokenVendor(u, 0, TokenVendor::ENTERPRISE_ID)));
+ if (option_vendor_id) {
+ setVendorOption(u, option_vendor_id);
+ }
+
+ evaluate(u, expected_result);
+ }
+
+ /// @brief Tests if vendor class token properly returns enterprise-id.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param option_vendor_id enterprise-id used in option (0 means don't
+ /// create the option)
+ /// @param expected_result text representation of the expected outcome
+ void testVendorClassEnterprise(Option::Universe u, uint32_t option_vendor_id,
+ const std::string& expected_result) {
+ // Let's clear any old values, so we can run multiple cases in each test
+ clearStack();
+
+ ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, 0, TokenVendor::ENTERPRISE_ID)));
+ if (option_vendor_id) {
+ setVendorClassOption(u, option_vendor_id);
+ }
+
+ evaluate(u, expected_result);
+ }
+
+ /// @brief Tests if vendor class token can report existence properly.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param token_vendor_id enterprise-id used in the token
+ /// @param option_vendor_id enterprise-id used in option (0 means don't
+ /// create the option)
+ /// @param expected_result text representation of the expected outcome
+ void testVendorClassExists(Option::Universe u, uint32_t token_vendor_id,
+ uint32_t option_vendor_id,
+ const std::string& expected_result) {
+ // Let's clear any old values, so we can run multiple cases in each test
+ clearStack();
+
+ ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id,
+ TokenOption::EXISTS)));
+
+ if (option_vendor_id) {
+ setVendorClassOption(u, option_vendor_id);
+ }
+
+ evaluate(u, expected_result);
+ }
+
+ /// @brief Tests if vendor token can handle sub-options properly.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param token_vendor_id enterprise-id used in the token
+ /// @param token_option_code option code in the token
+ /// @param option_vendor_id enterprise-id used in option (0 means don't
+ /// create the option)
+ /// @param option_code sub-option code (0 means don't create suboption)
+ /// @param repr representation (TokenOption::EXISTS or HEXADECIMAL)
+ /// @param expected_result text representation of the expected outcome
+ void testVendorSuboption(Option::Universe u,
+ uint32_t token_vendor_id, uint16_t token_option_code,
+ uint32_t option_vendor_id, uint16_t option_code,
+ TokenOption::RepresentationType repr,
+ const std::string& expected) {
+ // Let's clear any old values, so we can run multiple cases in each test
+ clearStack();
+
+ ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id, repr,
+ token_option_code)));
+ if (option_vendor_id) {
+ setVendorOption(u, option_vendor_id);
+ if (option_code) {
+ ASSERT_TRUE(vendor_);
+ OptionPtr subopt(new OptionString(u, option_code, "alpha"));
+ vendor_->addOption(subopt);
+ }
+ }
+
+ evaluate(u, expected);
+ }
+
+ /// @brief Tests if vendor class token can handle data chunks properly.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param token_vendor_id enterprise-id used in the token
+ /// @param token_index data index used in the token
+ /// @param option_vendor_id enterprise-id used in option (0 means don't
+ /// create the option)
+ /// @param data_tuples number of data tuples in the option
+ /// @param expected_result text representation of the expected outcome
+ void testVendorClassData(Option::Universe u,
+ uint32_t token_vendor_id, uint16_t token_index,
+ uint32_t option_vendor_id, uint16_t data_tuples,
+ const std::string& expected) {
+ // Let's clear any old values, so we can run multiple cases in each test
+ clearStack();
+
+ ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id,
+ TokenVendor::DATA, token_index)));
+ if (option_vendor_id) {
+ setVendorClassOption(u, option_vendor_id, data_tuples);
+ }
+
+ evaluate(u, expected);
+ }
+
+ /// @brief Tests if TokenInteger evaluates to the proper value
+ ///
+ /// @param expected expected string representation on stack after evaluation
+ /// @param value integer value passed to constructor
+ void testInteger(const std::string& expected, uint32_t value) {
+
+ clearStack();
+
+ ASSERT_NO_THROW(t_.reset(new TokenInteger(value)));
+
+ // The universe (v4 or v6) shouldn't have any impact on this,
+ // but let's check it anyway.
+ evaluate(Option::V4, expected);
+
+ clearStack(false);
+ evaluate(Option::V6, expected);
+
+ clearStack(true);
+ }
+};
+
+// This tests the toBool() conversions
+TEST_F(TokenTest, toBool) {
+
+ ASSERT_NO_THROW(Token::toBool("true"));
+ EXPECT_TRUE(Token::toBool("true"));
+ ASSERT_NO_THROW(Token::toBool("false"));
+ EXPECT_FALSE(Token::toBool("false"));
+
+ // Token::toBool() is case-sensitive
+ EXPECT_THROW(Token::toBool("True"), EvalTypeError);
+ EXPECT_THROW(Token::toBool("TRUE"), EvalTypeError);
+
+ // Proposed aliases
+ EXPECT_THROW(Token::toBool("1"), EvalTypeError);
+ EXPECT_THROW(Token::toBool("0"), EvalTypeError);
+ EXPECT_THROW(Token::toBool(""), EvalTypeError);
+}
+
+// This simple test checks that a TokenString, representing a constant string,
+// can be used in Pkt4 evaluation. (The actual packet is not used)
+TEST_F(TokenTest, string4) {
+
+ // Store constant string "foo" in the TokenString object.
+ ASSERT_NO_THROW(t_.reset(new TokenString("foo")));
+
+ // Make sure that the token can be evaluated without exceptions.
+ ASSERT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("foo", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_STRING Pushing text string 'foo'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This simple test checks that a TokenString, representing a constant string,
+// can be used in Pkt6 evaluation. (The actual packet is not used)
+TEST_F(TokenTest, string6) {
+
+ // Store constant string "foo" in the TokenString object.
+ ASSERT_NO_THROW(t_.reset(new TokenString("foo")));
+
+ // Make sure that the token can be evaluated without exceptions.
+ ASSERT_NO_THROW(t_->evaluate(*pkt6_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("foo", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_STRING Pushing text string 'foo'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This simple test checks that a TokenHexString, representing a constant
+// string coded in hexadecimal, can be used in Pkt4 evaluation.
+// (The actual packet is not used)
+TEST_F(TokenTest, hexstring4) {
+ TokenPtr empty;
+ TokenPtr bad;
+ TokenPtr nodigit;
+ TokenPtr baddigit;
+ TokenPtr bell;
+ TokenPtr foo;
+ TokenPtr cookie;
+
+ // Store constant empty hexstring "" ("") in the TokenHexString object.
+ ASSERT_NO_THROW(empty.reset(new TokenHexString("")));
+ // Store bad encoded hexstring "0abc" ("").
+ ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc")));
+ // Store hexstring with no digits "0x" ("").
+ ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x")));
+ // Store hexstring with a bad hexdigit "0xxabc" ("").
+ ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc")));
+ // Store hexstring with an odd number of hexdigits "0x7" ("\a").
+ ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7")));
+ // Store constant hexstring "0x666f6f" ("foo").
+ ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f")));
+ // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE).
+ ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363")));
+
+ // Make sure that tokens can be evaluated without exceptions,
+ // and verify the debug output
+ ASSERT_NO_THROW(empty->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(bad->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(nodigit->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(baddigit->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(bell->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(foo->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(cookie->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(7, values_.size());
+ uint32_t expected = htonl(DHCP_OPTIONS_COOKIE);
+ EXPECT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
+ values_.pop();
+ EXPECT_EQ("foo", values_.top());
+ values_.pop();
+ EXPECT_EQ("\a", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x07");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x666F6F");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x63825363");
+ EXPECT_TRUE(checkFile());
+}
+
+// This simple test checks that a TokenHexString, representing a constant
+// string coded in hexadecimal, can be used in Pkt6 evaluation.
+// (The actual packet is not used)
+TEST_F(TokenTest, hexstring6) {
+ TokenPtr empty;
+ TokenPtr bad;
+ TokenPtr nodigit;
+ TokenPtr baddigit;
+ TokenPtr bell;
+ TokenPtr foo;
+ TokenPtr cookie;
+
+ // Store constant empty hexstring "" ("") in the TokenHexString object.
+ ASSERT_NO_THROW(empty.reset(new TokenHexString("")));
+ // Store bad encoded hexstring "0abc" ("").
+ ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc")));
+ // Store hexstring with no digits "0x" ("").
+ ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x")));
+ // Store hexstring with a bad hexdigit "0xxabc" ("").
+ ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc")));
+ // Store hexstring with an odd number of hexdigits "0x7" ("\a").
+ ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7")));
+ // Store constant hexstring "0x666f6f" ("foo").
+ ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f")));
+ // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE).
+ ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363")));
+
+ // Make sure that tokens can be evaluated without exceptions.
+ ASSERT_NO_THROW(empty->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(bad->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(nodigit->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(baddigit->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(bell->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(foo->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(cookie->evaluate(*pkt6_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(7, values_.size());
+ uint32_t expected = htonl(DHCP_OPTIONS_COOKIE);
+ EXPECT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
+ values_.pop();
+ EXPECT_EQ("foo", values_.top());
+ values_.pop();
+ EXPECT_EQ("\a", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+ EXPECT_EQ("", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x07");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x666F6F");
+ addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x63825363");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that a TokenIpAddress, representing an IP address as
+// a constant string, can be used in Pkt4/Pkt6 evaluation.
+// (The actual packet is not used)
+TEST_F(TokenTest, ipaddress) {
+ TokenPtr bad4;
+ TokenPtr bad6;
+ TokenPtr ip4;
+ TokenPtr ip6;
+
+ // Bad IP addresses
+ ASSERT_NO_THROW(bad4.reset(new TokenIpAddress("10.0.0.0.1")));
+ ASSERT_NO_THROW(bad6.reset(new TokenIpAddress(":::")));
+
+ // IP addresses
+ ASSERT_NO_THROW(ip4.reset(new TokenIpAddress("10.0.0.1")));
+ ASSERT_NO_THROW(ip6.reset(new TokenIpAddress("2001:db8::1")));
+
+ // Make sure that tokens can be evaluated without exceptions.
+ ASSERT_NO_THROW(ip4->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(ip6->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(bad4->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(bad6->evaluate(*pkt6_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(4, values_.size());
+
+ // Check bad addresses (they pushed '' on the value stack)
+ EXPECT_EQ(0, values_.top().size());
+ values_.pop();
+ EXPECT_EQ(0, values_.top().size());
+ values_.pop();
+
+ // Check IPv6 address
+ uint8_t expected6[] = { 0x20, 1, 0xd, 0xb8, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1 };
+ EXPECT_EQ(16, values_.top().size());
+ EXPECT_EQ(0, memcmp(expected6, &values_.top()[0], 16));
+ values_.pop();
+
+ // Check IPv4 address
+ uint8_t expected4[] = { 10, 0, 0, 1 };
+ EXPECT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(expected4, &values_.top()[0], 4));
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x0A000001");
+ addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress "
+ "0x20010DB8000000000000000000000001");
+ addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x");
+ addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that a TokenIpAddressToText, representing an IP address as
+// a string, can be used in Pkt4/Pkt6 evaluation.
+// (The actual packet is not used)
+TEST_F(TokenTest, addressToText) {
+ TokenPtr address((new TokenIpAddressToText()));
+ std::vector<uint8_t> bytes;
+
+ std::string value = "10.0.0.1";
+ values_.push(value);
+
+ // Invalid data size fails.
+ EXPECT_THROW(address->evaluate(*pkt4_, values_), EvalTypeError);
+
+ bytes = IOAddress(value).toBytes();
+ values_.push(std::string(bytes.begin(), bytes.end()));
+
+ EXPECT_NO_THROW(address->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(1, values_.size());
+
+ value = "2001:db8::1";
+ bytes = IOAddress(value).toBytes();
+ values_.push(std::string(bytes.begin(), bytes.end()));
+
+ EXPECT_NO_THROW(address->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(2, values_.size());
+
+ values_.push(std::string());
+ EXPECT_NO_THROW(address->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(3, values_.size());
+
+ // Check empty data
+ EXPECT_EQ(0, values_.top().size());
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Check IPv6 address
+ EXPECT_EQ(11, values_.top().size());
+ EXPECT_EQ("2001:db8::1", values_.top());
+ values_.pop();
+
+ // Check IPv4 address
+ EXPECT_EQ(8, values_.top().size());
+ EXPECT_EQ("10.0.0.1", values_.top());
+ values_.pop();
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_IPADDRESSTOTEXT Pushing IPAddress 10.0.0.1");
+ addString("EVAL_DEBUG_IPADDRESSTOTEXT Pushing IPAddress 2001:db8::1");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that a TokenIntToText, representing an integer as a string,
+// can be used in Pkt4/Pkt6 evaluation.
+// (The actual packet is not used)
+TEST_F(TokenTest, integerToText) {
+ TokenPtr int8token((new TokenInt8ToText()));
+ TokenPtr int16token((new TokenInt16ToText()));
+ TokenPtr int32token((new TokenInt32ToText()));
+ TokenPtr uint8token((new TokenUInt8ToText()));
+ TokenPtr uint16token((new TokenUInt16ToText()));
+ TokenPtr uint32token((new TokenUInt32ToText()));
+
+ std::vector<uint8_t> bytes;
+ std::string value = "0123456789";
+
+ // Invalid data size fails.
+ values_.push(value);
+ EXPECT_THROW(int8token->evaluate(*pkt4_, values_), EvalTypeError);
+ values_.push(value);
+ EXPECT_THROW(int16token->evaluate(*pkt4_, values_), EvalTypeError);
+ values_.push(value);
+ EXPECT_THROW(int32token->evaluate(*pkt4_, values_), EvalTypeError);
+ values_.push(value);
+ EXPECT_THROW(uint8token->evaluate(*pkt4_, values_), EvalTypeError);
+ values_.push(value);
+ EXPECT_THROW(uint16token->evaluate(*pkt4_, values_), EvalTypeError);
+ values_.push(value);
+ EXPECT_THROW(uint32token->evaluate(*pkt4_, values_), EvalTypeError);
+
+ uint64_t data = -1;
+
+ values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&data)), sizeof(int8_t)));
+
+ EXPECT_NO_THROW(int8token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(1, values_.size());
+
+ int16_t i16 = 0;
+ memcpy(&i16, &data, sizeof(int16_t));
+ values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&i16)), sizeof(int16_t)));
+
+ EXPECT_NO_THROW(int16token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(2, values_.size());
+
+ int32_t i32 = 0;
+ memcpy(&i32, &data, sizeof(int32_t));
+ values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&i32)), sizeof(int32_t)));
+
+ EXPECT_NO_THROW(int32token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(3, values_.size());
+
+ values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&data)), sizeof(uint8_t)));
+
+ EXPECT_NO_THROW(uint8token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(4, values_.size());
+
+ uint16_t ui16 = 0;
+ memcpy(&ui16, &data, sizeof(uint16_t));
+ values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&ui16)), sizeof(uint16_t)));
+
+ EXPECT_NO_THROW(uint16token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(5, values_.size());
+
+ uint32_t ui32 = 0;
+ memcpy(&ui32, &data, sizeof(uint32_t));
+ values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&ui32)), sizeof(uint32_t)));
+
+ EXPECT_NO_THROW(uint32token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(6, values_.size());
+
+ value = "";
+
+ values_.push(value);
+ EXPECT_NO_THROW(int8token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(7, values_.size());
+
+ values_.push(value);
+ EXPECT_NO_THROW(int16token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(8, values_.size());
+
+ values_.push(value);
+ EXPECT_NO_THROW(int32token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(9, values_.size());
+
+ values_.push(value);
+ EXPECT_NO_THROW(uint8token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(10, values_.size());
+
+ values_.push(value);
+ EXPECT_NO_THROW(uint16token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(11, values_.size());
+
+ values_.push(value);
+ EXPECT_NO_THROW(uint32token->evaluate(*pkt4_, values_));
+
+ // Check that the evaluation put its value on the values stack.
+ ASSERT_EQ(12, values_.size());
+
+ // Check empty data
+ EXPECT_EQ(0, values_.top().size());
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Check empty data
+ EXPECT_EQ(0, values_.top().size());
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Check empty data
+ EXPECT_EQ(0, values_.top().size());
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Check empty data
+ EXPECT_EQ(0, values_.top().size());
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Check empty data
+ EXPECT_EQ(0, values_.top().size());
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Check empty data
+ EXPECT_EQ(0, values_.top().size());
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Check uint32
+ EXPECT_EQ(10, values_.top().size());
+ EXPECT_EQ("4294967295", values_.top());
+ values_.pop();
+
+ // Check uint16
+ EXPECT_EQ(5, values_.top().size());
+ EXPECT_EQ("65535", values_.top());
+ values_.pop();
+
+ // Check uint8
+ EXPECT_EQ(3, values_.top().size());
+ EXPECT_EQ("255", values_.top());
+ values_.pop();
+
+ // Check int32
+ EXPECT_EQ(2, values_.top().size());
+ EXPECT_EQ("-1", values_.top());
+ values_.pop();
+
+ // Check int16
+ EXPECT_EQ(2, values_.top().size());
+ EXPECT_EQ("-1", values_.top());
+ values_.pop();
+
+ // Check int8
+ EXPECT_EQ(2, values_.top().size());
+ EXPECT_EQ("-1", values_.top());
+ values_.pop();
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_INT8TOTEXT Pushing Int8 -1");
+ addString("EVAL_DEBUG_INT16TOTEXT Pushing Int16 -1");
+ addString("EVAL_DEBUG_INT32TOTEXT Pushing Int32 -1");
+ addString("EVAL_DEBUG_UINT8TOTEXT Pushing UInt8 255");
+ addString("EVAL_DEBUG_UINT16TOTEXT Pushing UInt16 65535");
+ addString("EVAL_DEBUG_UINT32TOTEXT Pushing UInt32 4294967295");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an option value is able to extract
+// the option from an IPv4 packet and properly store the option's value.
+TEST_F(TokenTest, optionString4) {
+ TokenPtr found;
+ TokenPtr not_found;
+
+ // The packets we use have option 100 with a string in them.
+ ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::TEXTUAL)));
+ ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::TEXTUAL)));
+
+ // This should evaluate to the content of the option 100 (i.e. "hundred4")
+ ASSERT_NO_THROW(found->evaluate(*pkt4_, values_));
+
+ // This should evaluate to "" as there is no option 101.
+ ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_));
+
+ // There should be 2 values evaluated.
+ ASSERT_EQ(2, values_.size());
+
+ // This is a stack, so the pop order is inversed. We should get the empty
+ // string first.
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Then the content of the option 100.
+ EXPECT_EQ("hundred4", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred4'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing option value is able to extract
+// the option from an IPv4 packet and properly store its value in a
+// hexadecimal format.
+TEST_F(TokenTest, optionHexString4) {
+ TokenPtr found;
+ TokenPtr not_found;
+
+ // The packets we use have option 100 with a string in them.
+ ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::HEXADECIMAL)));
+ ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::HEXADECIMAL)));
+
+ // This should evaluate to the content of the option 100 (i.e. "hundred4")
+ ASSERT_NO_THROW(found->evaluate(*pkt4_, values_));
+
+ // This should evaluate to "" as there is no option 101.
+ ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_));
+
+ // There should be 2 values evaluated.
+ ASSERT_EQ(2, values_.size());
+
+ // This is a stack, so the pop order is inversed. We should get the empty
+ // string first.
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Then the content of the option 100.
+ EXPECT_EQ("hundred4", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 0x68756E6472656434");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value 0x");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an option value is able to check
+// the existence of the option from an IPv4 packet.
+TEST_F(TokenTest, optionExistsString4) {
+ TokenPtr found;
+ TokenPtr not_found;
+
+ // The packets we use have option 100 with a string in them.
+ ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::EXISTS)));
+ ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::EXISTS)));
+
+ ASSERT_NO_THROW(found->evaluate(*pkt4_, values_));
+ ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_));
+
+ // There should be 2 values evaluated.
+ ASSERT_EQ(2, values_.size());
+
+ // This is a stack, so the pop order is inversed.
+ EXPECT_EQ("false", values_.top());
+ values_.pop();
+ EXPECT_EQ("true", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an option value is able to extract
+// the option from an IPv6 packet and properly store the option's value.
+TEST_F(TokenTest, optionString6) {
+ TokenPtr found;
+ TokenPtr not_found;
+
+ // The packets we use have option 100 with a string in them.
+ ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::TEXTUAL)));
+ ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::TEXTUAL)));
+
+ // This should evaluate to the content of the option 100 (i.e. "hundred6")
+ ASSERT_NO_THROW(found->evaluate(*pkt6_, values_));
+
+ // This should evaluate to "" as there is no option 101.
+ ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_));
+
+ // There should be 2 values evaluated.
+ ASSERT_EQ(2, values_.size());
+
+ // This is a stack, so the pop order is inversed. We should get the empty
+ // string first.
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Then the content of the option 100.
+ EXPECT_EQ("hundred6", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred6'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an option value is able to extract
+// the option from an IPv6 packet and properly store its value in hexadecimal
+// format.
+TEST_F(TokenTest, optionHexString6) {
+ TokenPtr found;
+ TokenPtr not_found;
+
+ // The packets we use have option 100 with a string in them.
+ ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::HEXADECIMAL)));
+ ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::HEXADECIMAL)));
+
+ // This should evaluate to the content of the option 100 (i.e. "hundred6")
+ ASSERT_NO_THROW(found->evaluate(*pkt6_, values_));
+
+ // This should evaluate to "" as there is no option 101.
+ ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_));
+
+ // There should be 2 values evaluated.
+ ASSERT_EQ(2, values_.size());
+
+ // This is a stack, so the pop order is inversed. We should get the empty
+ // string first.
+ EXPECT_EQ("", values_.top());
+ values_.pop();
+
+ // Then the content of the option 100.
+ EXPECT_EQ("hundred6", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 0x68756E6472656436");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value 0x");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an option value is able to check
+// the existence of the option from an IPv6 packet.
+TEST_F(TokenTest, optionExistsString6) {
+ TokenPtr found;
+ TokenPtr not_found;
+
+ // The packets we use have option 100 with a string in them.
+ ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::EXISTS)));
+ ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::EXISTS)));
+
+ ASSERT_NO_THROW(found->evaluate(*pkt6_, values_));
+ ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_));
+
+ // There should be 2 values evaluated.
+ ASSERT_EQ(2, values_.size());
+
+ // This is a stack, so the pop order is inversed.
+ EXPECT_EQ("false", values_.top());
+ values_.pop();
+ EXPECT_EQ("true", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that the existing relay4 option can be found.
+TEST_F(TokenTest, relay4Option) {
+
+ // Insert relay option with sub-options 1 and 13
+ insertRelay4Option();
+
+ // Creating the token should be safe.
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
+
+ // We should be able to evaluate it.
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // The option should be found and relay4[13] should evaluate to the
+ // content of that sub-option, i.e. "thirteen"
+ EXPECT_EQ("thirteen", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 13 with value 'thirteen'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that the code properly handles cases when
+// there is a RAI option, but there's no requested sub-option.
+TEST_F(TokenTest, relay4OptionNoSuboption) {
+
+ // Insert relay option with sub-options 1 and 13
+ insertRelay4Option();
+
+ // Creating the token should be safe.
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(15, TokenOption::TEXTUAL)));
+
+ // We should be able to evaluate it.
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // The option should NOT be found (there is no sub-option 15),
+ // so the expression should evaluate to ""
+ EXPECT_EQ("", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 15 with value ''");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that the code properly handles cases when
+// there's no RAI option at all.
+TEST_F(TokenTest, relay4OptionNoRai) {
+
+ // We didn't call insertRelay4Option(), so there's no RAI option.
+
+ // Creating the token should be safe.
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
+
+ // We should be able to evaluate it.
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // The option should NOT be found (there is no sub-option 13),
+ // so the expression should evaluate to ""
+ EXPECT_EQ("", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 13 with value ''");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that only the RAI is searched for the requested
+// sub-option.
+TEST_F(TokenTest, relay4RAIOnly) {
+
+ // Insert relay option with sub-options 1 and 13
+ insertRelay4Option();
+
+ // Add options 13 and 70 to the packet.
+ OptionPtr opt13(new OptionString(Option::V4, 13, "THIRTEEN"));
+ OptionPtr opt70(new OptionString(Option::V4, 70, "SEVENTY"));
+ pkt4_->addOption(opt13);
+ pkt4_->addOption(opt70);
+
+ // The situation is as follows:
+ // Packet:
+ // - option 13 (containing "THIRTEEN")
+ // - option 82 (rai)
+ // - option 1 (containing "one")
+ // - option 13 (containing "thirteen")
+
+ // Let's try to get option 13. It should get the one from RAI
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("thirteen", values_.top());
+
+ // Try to get option 1. It should get the one from RAI
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(1, TokenOption::TEXTUAL)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("one", values_.top());
+
+ // Try to get option 70. It should fail, as there's no such
+ // sub option in RAI.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(70, TokenOption::TEXTUAL)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("", values_.top());
+
+ // Try to check option 1. It should return "true"
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(1, TokenOption::EXISTS)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // Try to check option 70. It should return "false"
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(70, TokenOption::EXISTS)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 13 with value 'thirteen'");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'one'");
+ addString("EVAL_DEBUG_OPTION Pushing option 70 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'true'");
+ addString("EVAL_DEBUG_OPTION Pushing option 70 with value 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if we can properly extract an option
+// from relay encapsulations. Our packet has two relay
+// encapsulations. Both include a common option with the
+// original message (option 100) and both include their
+// own option (101 and 102). We attempt to extract the
+// options and compare them to expected values. We also
+// try to extract an option from an encapsulation
+// that doesn't exist (level 2), this should result in an empty
+// string.
+TEST_F(TokenTest, relay6Option) {
+ // We start by adding a set of relay encapsulations to the
+ // basic v6 packet.
+ addRelay6Encapsulations();
+
+ // Then we work our way through the set of choices
+ // Level 0 both options it has and the check that
+ // the checking for an option it doesn't have results
+ // in an empty string.
+ verifyRelay6Option(0, 100, TokenOption::TEXTUAL, "hundred.zero");
+ verifyRelay6Option(0, 100, TokenOption::EXISTS, "true");
+ verifyRelay6Option(0, 101, TokenOption::TEXTUAL, "hundredone.zero");
+ verifyRelay6Option(0, 102, TokenOption::TEXTUAL, "");
+ verifyRelay6Option(0, 102, TokenOption::EXISTS, "false");
+
+ // Level 1, again both options it has and the one for level 0
+ verifyRelay6Option(1, 100, TokenOption::TEXTUAL, "hundred.one");
+ verifyRelay6Option(1, 101, TokenOption::TEXTUAL, "");
+ verifyRelay6Option(1, 102, TokenOption::TEXTUAL, "hundredtwo.one");
+
+ // Level 2, no encapsulation so no options
+ verifyRelay6Option(2, 100, TokenOption::TEXTUAL, "");
+
+ // Level -1, the same as level 1
+ verifyRelay6Option(-1, 100, TokenOption::TEXTUAL, "hundred.one");
+ verifyRelay6Option(-1, 101, TokenOption::TEXTUAL, "");
+ verifyRelay6Option(-1, 102, TokenOption::TEXTUAL, "hundredtwo.one");
+
+ // Level -2, the same as level 0
+ verifyRelay6Option(-2, 100, TokenOption::TEXTUAL, "hundred.zero");
+ verifyRelay6Option(-2, 100, TokenOption::EXISTS, "true");
+ verifyRelay6Option(-2, 101, TokenOption::TEXTUAL, "hundredone.zero");
+ verifyRelay6Option(-2, 102, TokenOption::TEXTUAL, "");
+ verifyRelay6Option(-2, 102, TokenOption::EXISTS, "false");
+
+ // Level -3, no encapsulation so no options
+ verifyRelay6Option(-3, 100, TokenOption::TEXTUAL, "");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.zero'");
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'hundredone.zero'");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'false'");
+
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.one'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'hundredtwo.one'");
+
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value ''");
+
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.one'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'hundredtwo.one'");
+
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.zero'");
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'");
+ addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'hundredone.zero'");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'false'");
+
+ addString("EVAL_DEBUG_OPTION Pushing option 100 with value ''");
+
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies that relay6 option requires DHCPv6
+TEST_F(TokenTest, relay6OptionError) {
+ // Create a relay6 option token
+ ASSERT_NO_THROW(t_.reset(new TokenRelay6Option(0, 13, TokenOption::TEXTUAL)));
+
+ // A DHCPv6 packet is required
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+}
+
+// Verifies that DHCPv4 packet metadata can be extracted.
+TEST_F(TokenTest, pkt4MetaData) {
+ pkt4_->setIface("eth0");
+ pkt4_->setLocalAddr(IOAddress("10.0.0.1"));
+ pkt4_->setRemoteAddr(IOAddress("10.0.0.2"));
+
+ // Check interface (expect eth0)
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::IFACE)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ ASSERT_EQ("eth0", values_.top());
+
+ // Check source (expect 10.0.0.2)
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::SRC)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ vector<uint8_t> a2 = IOAddress("10.0.0.2").toBytes();
+ ASSERT_EQ(a2.size(), values_.top().size());
+ EXPECT_EQ(0, memcmp(&a2[0], &values_.top()[0], a2.size()));
+
+ // Check destination (expect 10.0.0.1)
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::DST)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ vector<uint8_t> a1 = IOAddress("10.0.0.1").toBytes();
+ ASSERT_EQ(a1.size(), values_.top().size());
+ EXPECT_EQ(0, memcmp(&a1[0], &values_.top()[0], a1.size()));
+
+ // Check length (expect 249)
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::LEN)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ uint32_t length = htonl(static_cast<uint32_t>(pkt4_->len()));
+ ASSERT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(&length, &values_.top()[0], 4));
+
+ // Unknown metadata type fails
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::MetadataType(100))));
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data iface with value eth0");
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data src with value 0x0A000002");
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data dst with value 0x0A000001");
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data len with value 0x000000F9");
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies that DHCPv6 packet metadata can be extracted.
+TEST_F(TokenTest, pkt6MetaData) {
+ pkt6_->setIface("eth0");
+ pkt6_->setLocalAddr(IOAddress("ff02::1:2"));
+ pkt6_->setRemoteAddr(IOAddress("fe80::1234"));
+
+ // Check interface (expect eth0)
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::IFACE)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+ ASSERT_EQ(1, values_.size());
+ ASSERT_EQ("eth0", values_.top());
+
+ // Check source (expect fe80::1234)
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::SRC)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+ ASSERT_EQ(1, values_.size());
+ ASSERT_EQ(16, values_.top().size());
+ EXPECT_EQ(0xfe, static_cast<uint8_t>(values_.top()[0]));
+ EXPECT_EQ(0x80, static_cast<uint8_t>(values_.top()[1]));
+ for (unsigned i = 2; i < 14; ++i) {
+ EXPECT_EQ(0, values_.top()[i]);
+ }
+ EXPECT_EQ(0x12, values_.top()[14]);
+ EXPECT_EQ(0x34, values_.top()[15]);
+
+ // Check destination (expect ff02::1:2)
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::DST)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+ ASSERT_EQ(1, values_.size());
+ vector<uint8_t> ma = IOAddress("ff02::1:2").toBytes();
+ ASSERT_EQ(ma.size(), values_.top().size());
+ EXPECT_EQ(0, memcmp(&ma[0], &values_.top()[0], ma.size()));
+
+ // Check length (expect 16)
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::LEN)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+ ASSERT_EQ(1, values_.size());
+ uint32_t length = htonl(static_cast<uint32_t>(pkt6_->len()));
+ ASSERT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(&length, &values_.top()[0], 4));
+
+ // Unknown meta data type fails
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::MetadataType(100))));
+ EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError);
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data iface with value eth0");
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data src with value "
+ "0xFE800000000000000000000000001234");
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data dst with value "
+ "0xFF020000000000000000000000010002");
+ addString("EVAL_DEBUG_PKT Pushing PKT meta data len with value 0x00000010");
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies if the DHCPv4 packet fields can be extracted.
+TEST_F(TokenTest, pkt4Fields) {
+ pkt4_->setGiaddr(IOAddress("192.0.2.1"));
+ pkt4_->setCiaddr(IOAddress("192.0.2.2"));
+ pkt4_->setYiaddr(IOAddress("192.0.2.3"));
+ pkt4_->setSiaddr(IOAddress("192.0.2.4"));
+
+ // We're setting hardware address to uncommon (7 bytes rather than 6 and
+ // hardware type 123) HW address. We'll use it in hlen and htype checks.
+ HWAddrPtr hw(new HWAddr(HWAddr::fromText("01:02:03:04:05:06:07", 123)));
+ pkt4_->setHWAddr(hw);
+
+ // Check hardware address field.
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::CHADDR)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ uint8_t expected_hw[] = { 1, 2, 3, 4, 5, 6, 7 };
+ ASSERT_EQ(7, values_.top().size());
+ EXPECT_EQ(0, memcmp(expected_hw, &values_.top()[0], 7));
+
+ // Check hlen value field.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HLEN)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ ASSERT_EQ(4, values_.top().size());
+ uint32_t expected_hlen = htonl(7);
+ EXPECT_EQ(0, memcmp(&expected_hlen, &values_.top()[0], 4));
+
+ // Check htype value.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HTYPE)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ ASSERT_EQ(4, values_.top().size());
+ uint32_t expected_htype = htonl(123);
+ EXPECT_EQ(0, memcmp(&expected_htype, &values_.top()[0], 4));
+
+ // Check giaddr value.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::GIADDR)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ uint8_t expected_addr[] = { 192, 0, 2, 1 };
+ ASSERT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4));
+
+ // Check ciaddr value.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::CIADDR)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ expected_addr[3] = 2;
+ ASSERT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4));
+
+ // Check yiaddr value.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::YIADDR)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ expected_addr[3] = 3;
+ ASSERT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4));
+
+ // Check siaddr value.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::SIADDR)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ expected_addr[3] = 4;
+ ASSERT_EQ(4, values_.top().size());
+ EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4));
+
+ // Check msgtype.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::MSGTYPE)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ ASSERT_EQ(4, values_.top().size());
+ string exp_msgtype = encode(DHCPDISCOVER);
+ EXPECT_EQ(0, memcmp(&exp_msgtype[0], &values_.top()[0], 4));
+
+ // Check transaction-id
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::TRANSID)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ ASSERT_EQ(4, values_.top().size());
+ string exp_transid = encode(12345);
+ EXPECT_EQ(0, memcmp(&exp_transid[0], &values_.top()[0], 4));
+
+ // Check a DHCPv6 packet throws.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HLEN)));
+ EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError);
+
+ // Unknown field fails
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt4(static_cast<TokenPkt4::FieldType>(100))));
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field mac with value 0x01020304050607");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field hlen with value 0x00000007");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field htype with value 0x0000007B");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field giaddr with value 0xC0000201");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field ciaddr with value 0xC0000202");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field yiaddr with value 0xC0000203");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field siaddr with value 0xC0000204");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field msgtype with value 0x00000001");
+ addString("EVAL_DEBUG_PKT4 Pushing PKT4 field transid with value 0x00003039");
+ EXPECT_TRUE(checkFile());
+}
+
+// Verifies if the DHCPv6 packet fields can be extracted.
+TEST_F(TokenTest, pkt6Fields) {
+ // The default test creates a v6 DHCPV6_SOLICIT packet with a
+ // transaction id of 12345.
+
+ // Check the message type
+ ASSERT_NO_THROW(t_.reset(new TokenPkt6(TokenPkt6::MSGTYPE)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+ ASSERT_EQ(1, values_.size());
+ uint32_t expected = htonl(1);
+ EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
+
+ // Check the transaction id field
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt6(TokenPkt6::TRANSID)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_));
+ ASSERT_EQ(1, values_.size());
+ expected = htonl(12345);
+ EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
+
+ // Check that working with a v4 packet generates an error
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt6(TokenPkt6::TRANSID)));
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // Unknown field fails
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenPkt6(static_cast<TokenPkt6::FieldType>(100))));
+ EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError);
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_PKT6 Pushing PKT6 field msgtype with value 0x00000001");
+ addString("EVAL_DEBUG_PKT6 Pushing PKT6 field transid with value 0x00003039");
+
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if we can properly extract the link and peer
+// address fields from relay encapsulations. Our packet has
+// two relay encapsulations. We attempt to extract the two
+// fields from both of the encapsulations and compare them.
+// We also try to extract one of the fields from an encapsulation
+// that doesn't exist (level 2), this should result in an empty
+// string.
+TEST_F(TokenTest, relay6Field) {
+ // Values for the address results
+ uint8_t zeroaddr[] = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+ uint8_t linkaddr[] = { 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1 };
+ uint8_t peeraddr[] = { 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 2 };
+
+ // We start by adding a set of relay encapsulations to the
+ // basic v6 packet.
+ addRelay6Encapsulations();
+
+ // Then we work our way through the set of choices
+ // Level 0 both link and peer address should be 0::0
+ verifyRelay6Eval(0, TokenRelay6Field::LINKADDR, 16, zeroaddr);
+ verifyRelay6Eval(0, TokenRelay6Field::PEERADDR, 16, zeroaddr);
+
+ // Level 1 link and peer should have different non-zero addresses
+ verifyRelay6Eval(1, TokenRelay6Field::LINKADDR, 16, linkaddr);
+ verifyRelay6Eval(1, TokenRelay6Field::PEERADDR, 16, peeraddr);
+
+ // Level 2 has no encapsulation so the address should be zero length
+ verifyRelay6Eval(2, TokenRelay6Field::LINKADDR, 0, zeroaddr);
+
+ // Level -1 is the same as level 1
+ verifyRelay6Eval(-1, TokenRelay6Field::LINKADDR, 16, linkaddr);
+ verifyRelay6Eval(-1, TokenRelay6Field::PEERADDR, 16, peeraddr);
+
+ // Level -2 is the same as level 0
+ verifyRelay6Eval(-2, TokenRelay6Field::LINKADDR, 16, zeroaddr);
+ verifyRelay6Eval(-2, TokenRelay6Field::PEERADDR, 16, zeroaddr);
+
+ // Level -3 has no encapsulation so the address should be zero length
+ verifyRelay6Eval(-3, TokenRelay6Field::LINKADDR, 0, zeroaddr);
+
+ // Lets check that the layout of the address returned by the
+ // token matches that of the TokenIpAddress
+ TokenPtr trelay;
+ TokenPtr taddr;
+ TokenPtr tequal;
+ ASSERT_NO_THROW(trelay.reset(new TokenRelay6Field(1, TokenRelay6Field::LINKADDR)));
+ ASSERT_NO_THROW(taddr.reset(new TokenIpAddress("1::1")));
+ ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+
+ EXPECT_NO_THROW(trelay->evaluate(*pkt6_, values_));
+ EXPECT_NO_THROW(taddr->evaluate(*pkt6_, values_));
+ EXPECT_NO_THROW(tequal->evaluate(*pkt6_, values_));
+
+ // We should have a single value on the stack and it should be "true"
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // be tidy
+ clearStack();
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest 0 "
+ "with value 0x00000000000000000000000000000000");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest 0 "
+ "with value 0x00000000000000000000000000000000");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest 1 "
+ "with value 0x00010000000000000000000000000001");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest 1 "
+ "with value 0x00010000000000000000000000000002");
+ addString("EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field linkaddr "
+ "nest 2 with value 0x");
+
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest -1 "
+ "with value 0x00010000000000000000000000000001");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest -1 "
+ "with value 0x00010000000000000000000000000002");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest -2 "
+ "with value 0x00000000000000000000000000000000");
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest -2 "
+ "with value 0x00000000000000000000000000000000");
+ addString("EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field linkaddr "
+ "nest -3 with value 0x");
+
+ addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest 1 "
+ "with value 0x00010000000000000000000000000001");
+ addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress "
+ "0x00010000000000000000000000000001");
+ addString("EVAL_DEBUG_EQUAL Popping 0x00010000000000000000000000000001 "
+ "and 0x00010000000000000000000000000001 pushing result 'true'");
+
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks some error cases for a relay6 field token
+TEST_F(TokenTest, relay6FieldError) {
+ // Create a valid relay6 field token
+ ASSERT_NO_THROW(t_.reset(new TokenRelay6Field(0, TokenRelay6Field::LINKADDR)));
+
+ // a DHCPv6 packet is required
+ ASSERT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // No test for unknown field as it is not (yet) checked?!
+}
+
+// This test checks if a token representing an == operator is able to
+// compare two values (with incorrectly built stack).
+TEST_F(TokenTest, optionEqualInvalid) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenEqual()));
+
+ // CASE 1: There's not enough values on the stack. == is an operator that
+ // takes two parameters. There are 0 on the stack.
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // CASE 2: One value is still not enough.
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+}
+
+// This test checks if a token representing an == operator is able to
+// compare two different values.
+TEST_F(TokenTest, optionEqualFalse) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenEqual()));
+
+ values_.push("foo");
+ values_.push("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // After evaluation there should be a single value that represents
+ // result of "foo" == "bar" comparison.
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_EQUAL Popping 0x626172 and 0x666F6F "
+ "pushing result 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an == operator is able to
+// compare two identical values.
+TEST_F(TokenTest, optionEqualTrue) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenEqual()));
+
+ values_.push("foo");
+ values_.push("foo");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // After evaluation there should be a single value that represents
+ // result of "foo" == "foo" comparison.
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_EQUAL Popping 0x666F6F and 0x666F6F "
+ "pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing a substring request
+// throws an exception if there aren't enough values on the stack.
+// The stack from the top is: length, start, string.
+// The actual packet is not used.
+TEST_F(TokenTest, substringNotEnoughValues) {
+ ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
+
+ // Substring requires three values on the stack, try
+ // with 0, 1 and 2 all should throw an exception
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("0");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // Three should work
+ values_.push("0");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // As we had an empty string to start with we should have an empty
+ // one after the evaluate
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING_EMPTY Popping length 0, start 0, "
+ "string 0x pushing result 0x");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test getting the whole string in different ways
+TEST_F(TokenTest, substringWholeString) {
+ // Get the whole string
+ verifySubstringEval("foobar", "0", "6", "foobar");
+
+ // Get the whole string with "all"
+ verifySubstringEval("foobar", "0", "all", "foobar");
+
+ // Get the whole string with an extra long number
+ verifySubstringEval("foobar", "0", "123456", "foobar");
+
+ // Get the whole string counting from the back
+ verifySubstringEval("foobar", "-6", "all", "foobar");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING Popping length 6, start 0, "
+ "string 0x666F6F626172 pushing result 0x666F6F626172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length all, start 0, "
+ "string 0x666F6F626172 pushing result 0x666F6F626172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 123456, start 0, "
+ "string 0x666F6F626172 pushing result 0x666F6F626172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length all, start -6, "
+ "string 0x666F6F626172 pushing result 0x666F6F626172");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test getting a suffix, in this case the last 3 characters
+TEST_F(TokenTest, substringTrailer) {
+ verifySubstringEval("foobar", "3", "3", "bar");
+ verifySubstringEval("foobar", "3", "all", "bar");
+ verifySubstringEval("foobar", "-3", "all", "bar");
+ verifySubstringEval("foobar", "-3", "123", "bar");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING Popping length 3, start 3, "
+ "string 0x666F6F626172 pushing result 0x626172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length all, start 3, "
+ "string 0x666F6F626172 pushing result 0x626172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length all, start -3, "
+ "string 0x666F6F626172 pushing result 0x626172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 123, start -3, "
+ "string 0x666F6F626172 pushing result 0x626172");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test getting the middle of the string in different ways
+TEST_F(TokenTest, substringMiddle) {
+ verifySubstringEval("foobar", "1", "4", "ooba");
+ verifySubstringEval("foobar", "-5", "4", "ooba");
+ verifySubstringEval("foobar", "-1", "-4", "ooba");
+ verifySubstringEval("foobar", "5", "-4", "ooba");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 1, "
+ "string 0x666F6F626172 pushing result 0x6F6F6261");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 4, start -5, "
+ "string 0x666F6F626172 pushing result 0x6F6F6261");
+ addString("EVAL_DEBUG_SUBSTRING Popping length -4, start -1, "
+ "string 0x666F6F626172 pushing result 0x6F6F6261");
+ addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 5, "
+ "string 0x666F6F626172 pushing result 0x6F6F6261");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test getting the last letter in different ways
+TEST_F(TokenTest, substringLastLetter) {
+ verifySubstringEval("foobar", "5", "all", "r");
+ verifySubstringEval("foobar", "5", "1", "r");
+ verifySubstringEval("foobar", "5", "5", "r");
+ verifySubstringEval("foobar", "-1", "all", "r");
+ verifySubstringEval("foobar", "-1", "1", "r");
+ verifySubstringEval("foobar", "-1", "5", "r");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING Popping length all, start 5, "
+ "string 0x666F6F626172 pushing result 0x72");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 1, start 5, "
+ "string 0x666F6F626172 pushing result 0x72");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 5, start 5, "
+ "string 0x666F6F626172 pushing result 0x72");
+ addString("EVAL_DEBUG_SUBSTRING Popping length all, start -1, "
+ "string 0x666F6F626172 pushing result 0x72");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 1, start -1, "
+ "string 0x666F6F626172 pushing result 0x72");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 5, start -1, "
+ "string 0x666F6F626172 pushing result 0x72");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test we get only what is available if we ask for a longer string
+TEST_F(TokenTest, substringLength) {
+ // Test off the front
+ verifySubstringEval("foobar", "0", "-4", "");
+ verifySubstringEval("foobar", "1", "-4", "f");
+ verifySubstringEval("foobar", "2", "-4", "fo");
+ verifySubstringEval("foobar", "3", "-4", "foo");
+
+ // and the back
+ verifySubstringEval("foobar", "3", "4", "bar");
+ verifySubstringEval("foobar", "4", "4", "ar");
+ verifySubstringEval("foobar", "5", "4", "r");
+ verifySubstringEval("foobar", "6", "4", "");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 0, "
+ "string 0x666F6F626172 pushing result 0x");
+ addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 1, "
+ "string 0x666F6F626172 pushing result 0x66");
+ addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 2, "
+ "string 0x666F6F626172 pushing result 0x666F");
+ addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 3, "
+ "string 0x666F6F626172 pushing result 0x666F6F");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 3, "
+ "string 0x666F6F626172 pushing result 0x626172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 4, "
+ "string 0x666F6F626172 pushing result 0x6172");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 5, "
+ "string 0x666F6F626172 pushing result 0x72");
+ addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length 4, start 6, "
+ "string 0x666F6F626172 pushing result 0x");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test that we get nothing if the starting position is out of the string
+TEST_F(TokenTest, substringStartingPosition) {
+ // Off the front
+ verifySubstringEval("foobar", "-7", "1", "");
+ verifySubstringEval("foobar", "-7", "-11", "");
+ verifySubstringEval("foobar", "-7", "all", "");
+
+ // and the back
+ verifySubstringEval("foobar", "6", "1", "");
+ verifySubstringEval("foobar", "6", "-11", "");
+ verifySubstringEval("foobar", "6", "all", "");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length 1, start -7, "
+ "string 0x666F6F626172 pushing result 0x");
+ addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length -11, start -7, "
+ "string 0x666F6F626172 pushing result 0x");
+ addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length all, start -7, "
+ "string 0x666F6F626172 pushing result 0x");
+ addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length 1, start 6, "
+ "string 0x666F6F626172 pushing result 0x");
+ addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length -11, start 6, "
+ "string 0x666F6F626172 pushing result 0x");
+ addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length all, start 6, "
+ "string 0x666F6F626172 pushing result 0x");
+ EXPECT_TRUE(checkFile());
+}
+
+// Check what happens if we use strings that aren't numbers for start or length
+// We should return the empty string
+TEST_F(TokenTest, substringBadParams) {
+ verifySubstringEval("foobar", "0ick", "all", "", true);
+ verifySubstringEval("foobar", "ick0", "all", "", true);
+ verifySubstringEval("foobar", "ick", "all", "", true);
+ verifySubstringEval("foobar", "0", "ick", "", true);
+ verifySubstringEval("foobar", "0", "0ick", "", true);
+ verifySubstringEval("foobar", "0", "ick0", "", true);
+ verifySubstringEval("foobar", "0", "allaboard", "", true);
+
+ // These should result in a throw which should generate it's own
+ // log entry
+}
+
+// lastly check that we don't get anything if the string is empty or
+// we don't ask for any characters from it.
+TEST_F(TokenTest, substringReturnEmpty) {
+ verifySubstringEval("", "0", "all", "");
+ verifySubstringEval("foobar", "0", "0", "");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING_EMPTY Popping length all, start 0, "
+ "string 0x pushing result 0x");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 0, start 0, "
+ "string 0x666F6F626172 pushing result 0x");
+ EXPECT_TRUE(checkFile());
+}
+
+// Check if we can use the substring and equal tokens together
+// We put the result on the stack first then the substring values
+// then evaluate the substring which should leave the original
+// result on the bottom with the substring result on next.
+// Evaluating the equals should produce true for the first
+// and false for the second.
+// throws an exception if there aren't enough values on the stack.
+// The stack from the top is: length, start, string.
+// The actual packet is not used.
+TEST_F(TokenTest, substringEquals) {
+ TokenPtr tequal;
+
+ ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
+ ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+
+ // The final expected value
+ values_.push("ooba");
+
+ // The substring values
+ values_.push("foobar");
+ values_.push("1");
+ values_.push("4");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have two values on the stack
+ ASSERT_EQ(2, values_.size());
+
+ // next the equals eval
+ EXPECT_NO_THROW(tequal->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // get rid of the result
+ values_.pop();
+
+ // and try it again but with a bad final value
+ // The final expected value
+ values_.push("foob");
+
+ // The substring values
+ values_.push("foobar");
+ values_.push("1");
+ values_.push("4");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have two values on the stack
+ ASSERT_EQ(2, values_.size());
+
+ // next the equals eval
+ EXPECT_NO_THROW(tequal->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 1, "
+ "string 0x666F6F626172 pushing result 0x6F6F6261");
+ addString("EVAL_DEBUG_EQUAL Popping 0x6F6F6261 and 0x6F6F6261 "
+ "pushing result 'true'");
+ addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 1, "
+ "string 0x666F6F626172 pushing result 0x6F6F6261");
+ addString("EVAL_DEBUG_EQUAL Popping 0x6F6F6261 and 0x666F6F62 "
+ "pushing result 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing a concat request
+// throws an exception if there aren't enough values on the stack.
+// The actual packet is not used.
+TEST_F(TokenTest, concat) {
+ ASSERT_NO_THROW(t_.reset(new TokenConcat()));
+
+ // Concat requires two values on the stack, try
+ // with 0 and 1 both should throw an exception
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // Two should work
+ values_.push("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // Check the result
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("foobar", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_CONCAT Popping 0x626172 and 0x666F6F "
+ "pushing 0x666F6F626172");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing a hexstring request
+// throws an exception if there aren't enough values on the stack.
+// The actual packet is not used.
+TEST_F(TokenTest, tohexstring) {
+ ASSERT_NO_THROW(t_.reset(new TokenToHexString()));
+
+ // Hexstring requires two values on the stack, try
+ // with 0 and 1 both should throw an exception
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // Two should work
+ values_.push("-");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // Check the result
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("66-6f-6f", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_TOHEXSTRING Popping binary value 0x666F6F and "
+ "separator -, pushing result 66-6f-6f");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an ifelse is able
+// to select the branch following the condition.
+TEST_F(TokenTest, ifElse) {
+ ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
+
+ // Ifelse requires three values on the stack, try
+ // with 0, 1 and 2 all should throw an exception
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("bar");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // The condition must be a boolean
+ values_.push("bar");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // Check if what it returns
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
+ values_.push("true");
+ values_.push("foo");
+ values_.push("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("foo", values_.top());
+
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenIfElse()));
+ values_.push("false");
+ values_.push("foo");
+ values_.push("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("bar", values_.top());
+}
+
+// This test checks if a token representing a not is able to
+// negate a boolean value (with incorrectly built stack).
+TEST_F(TokenTest, operatorNotInvalid) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenNot()));
+
+ // CASE 1: The stack is empty.
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // CASE 2: The top value is not a boolean
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+}
+
+// This test checks if a token representing a not operator is able to
+// negate a boolean value.
+TEST_F(TokenTest, operatorNot) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenNot()));
+
+ values_.push("true");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // After evaluation there should be the negation of the value.
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // Double negation is identity.
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_NOT Popping 'true' pushing 'false'");
+ addString("EVAL_DEBUG_NOT Popping 'false' pushing 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an and is able to
+// conjugate two values (with incorrectly built stack).
+TEST_F(TokenTest, operatorAndInvalid) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenAnd()));
+
+ // CASE 1: There's not enough values on the stack. and is an operator that
+ // takes two parameters. There are 0 on the stack.
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // CASE 2: One value is still not enough.
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // CASE 3: The two values must be logical
+ values_.push("true");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // Swap the 2 values
+ values_.push("true");
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+}
+
+// This test checks if a token representing an and operator is able to
+// conjugate false with another logical
+TEST_F(TokenTest, operatorAndFalse) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenAnd()));
+
+ values_.push("true");
+ values_.push("false");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // After evaluation there should be a single "false" value
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // After true and false, check false and true
+ values_.push("true");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // And false and false
+ values_.push("false");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_AND Popping 'false' and 'true' pushing 'false'");
+ addString("EVAL_DEBUG_AND Popping 'true' and 'false' pushing 'false'");
+ addString("EVAL_DEBUG_AND Popping 'false' and 'false' pushing 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an and is able to
+// conjugate two true values.
+TEST_F(TokenTest, operatorAndTrue) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenAnd()));
+
+ values_.push("true");
+ values_.push("true");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // After evaluation there should be a single "true" value
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_AND Popping 'true' and 'true' pushing 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an or is able to
+// combine two values (with incorrectly built stack).
+TEST_F(TokenTest, operatorOrInvalid) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenOr()));
+
+ // CASE 1: There's not enough values on the stack. or is an operator that
+ // takes two parameters. There are 0 on the stack.
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // CASE 2: One value is still not enough.
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+ // CASE 3: The two values must be logical
+ values_.push("true");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+
+ // Swap the 2 values
+ values_.push("true");
+ values_.push("foo");
+ EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError);
+}
+
+// This test checks if a token representing an or is able to
+// conjugate two false values.
+TEST_F(TokenTest, operatorOrFalse) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenOr()));
+
+ values_.push("false");
+ values_.push("false");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // After evaluation there should be a single "false" value
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OR Popping 'false' and 'false' pushing 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks if a token representing an == operator is able to
+// conjugate true with another logical
+TEST_F(TokenTest, operatorOrTrue) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenOr()));
+
+ values_.push("false");
+ values_.push("true");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // After evaluation there should be a single "true" value
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // After false or true, checks true or false
+ values_.push("false");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // And true or true
+ values_.push("true");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_OR Popping 'true' and 'false' pushing 'true'");
+ addString("EVAL_DEBUG_OR Popping 'false' and 'true' pushing 'true'");
+ addString("EVAL_DEBUG_OR Popping 'true' and 'true' pushing 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies client class membership
+TEST_F(TokenTest, member) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenMember("foo")));
+
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // the packet has no classes so false was left on the stack
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+ values_.pop();
+
+ // Add bar and retry
+ pkt4_->addClass("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // the packet has a class but it is not foo
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+ values_.pop();
+
+ // Add foo and retry
+ pkt4_->addClass("foo");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // Now the packet is in the foo class
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+}
+
+// This test verifies if expression vendor[4491].exists works properly in DHCPv4.
+TEST_F(TokenTest, vendor4SpecificVendorExists) {
+ // Case 1: no option, should evaluate to false
+ testVendorExists(Option::V4, 4491, 0, "false");
+
+ // Case 2: option present, but uses different enterprise-id, should fail
+ testVendorExists(Option::V4, 4491, 1234, "false");
+
+ // Case 3: option present and has matching enterprise-id, should succeed
+ testVendorExists(Option::V4, 4491, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, "
+ "pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies if expression vendor[4491].exists works properly in DHCPv6.
+TEST_F(TokenTest, vendor6SpecificVendorExists) {
+ // Case 1: no option, should evaluate to false
+ testVendorExists(Option::V6, 4491, 0, "false");
+
+ // Case 2: option present, but uses different enterprise-id, should fail
+ testVendorExists(Option::V6, 4491, 1234, "false");
+
+ // Case 3: option present and has matching enterprise-id, should succeed
+ testVendorExists(Option::V6, 4491, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, "
+ "pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+/// Test if expression vendor[*].exists works properly for DHCPv4.
+TEST_F(TokenTest, vendor4AnyVendorExists) {
+ // Case 1: no option, should evaluate to false
+ testVendorExists(Option::V4, 0, 0, "false");
+
+ // Case 2: option present with vendor-id 1234, should succeed
+ testVendorExists(Option::V4, 0, 1234, "true");
+
+ // Case 3: option present with vendor-id 4491, should succeed
+ testVendorExists(Option::V4, 0, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, "
+ "pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 1234 "
+ "found, pushing result 'true'");
+ addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test if expression vendor[*].exists works properly for DHCPv6.
+TEST_F(TokenTest, vendor6AnyVendorExists) {
+ // Case 1: no option, should evaluate to false
+ testVendorExists(Option::V6, 0, 0, "false");
+
+ // Case 2: option present with vendor-id 1234, should succeed
+ testVendorExists(Option::V6, 0, 1234, "true");
+
+ // Case 3: option present with vendor-id 4491, should succeed
+ testVendorExists(Option::V6, 0, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, "
+ "pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 1234 "
+ "found, pushing result 'true'");
+ addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test if expression vendor[*].enterprise works properly for DHCPv4.
+TEST_F(TokenTest, vendor4enterprise) {
+ // Case 1: No option present, should return empty string
+ testVendorEnterprise(Option::V4, 0, "");
+
+ // Case 2: Option with vendor-id 1234, should return "1234"
+ testVendorEnterprise(Option::V4, 1234, encode(1234));
+
+ // Case 3: Option with vendor-id set to maximum value, should still
+ // be able to handle it
+ testVendorEnterprise(Option::V4, 4294967295, encode(4294967295));
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, pushing"
+ " result ''");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 1234 as "
+ "result 0x000004D2");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 4294967295"
+ " as result 0xFFFFFFFF");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test if expression vendor[*].enterprise works properly for DHCPv6.
+TEST_F(TokenTest, vendor6enterprise) {
+ // Case 1: No option present, should return empty string
+ testVendorEnterprise(Option::V6, 0, "");
+
+ // Case 2: Option with vendor-id 1234, should return "1234"
+ testVendorEnterprise(Option::V6, 1234, encode(1234));
+
+ // Case 3: Option with vendor-id set to maximum value, should still
+ // be able to handle it
+ testVendorEnterprise(Option::V6, 4294967295, encode(4294967295));
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, pushing"
+ " result ''");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 1234 as "
+ "result 0x000004D2");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 4294967295 "
+ "as result 0xFFFFFFFF");
+ EXPECT_TRUE(checkFile());
+}
+
+// This one tests "vendor[4491].option[1].exists" expression. There are so many
+// wonderful ways in which this could fail: the option could not be there,
+// it could have different enterprise-id, may not have suboption 1. Or may
+// have the suboption with valid type, but enterprise may be different.
+TEST_F(TokenTest, vendor4SuboptionExists) {
+ // Case 1: expression vendor[4491].option[1].exists, no option present
+ testVendorSuboption(Option::V4, 4491, 1, 0, 0, TokenOption::EXISTS, "false");
+
+ // Case 2: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+ // no suboptions, expected result "false"
+ testVendorSuboption(Option::V4, 4491, 1, 1234, 0, TokenOption::EXISTS, "false");
+
+ // Case 3: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+ // suboption 1, expected result "false"
+ testVendorSuboption(Option::V4, 4491, 1, 1234, 1, TokenOption::EXISTS, "false");
+
+ // Case 4: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+ // suboption 2, expected result "false"
+ testVendorSuboption(Option::V4, 4491, 1, 4491, 2, TokenOption::EXISTS, "false");
+
+ // Case 5: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+ // suboption 1, expected result "true"
+ testVendorSuboption(Option::V4, 4491, 1, 4491, 1, TokenOption::EXISTS, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, pushing "
+ "result 'false'");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'false'");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This is similar to the previous one, but tests vendor[4491].option[1].exists
+// for DHCPv6.
+TEST_F(TokenTest, vendor6SuboptionExists) {
+ // Case 1: expression vendor[4491].option[1].exists, no option present
+ testVendorSuboption(Option::V6, 4491, 1, 0, 0, TokenOption::EXISTS, "false");
+
+ // Case 2: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+ // no suboptions, expected result "false"
+ testVendorSuboption(Option::V6, 4491, 1, 1234, 0, TokenOption::EXISTS, "false");
+
+ // Case 3: expression vendor[4491].option[1].exists, option with vendor-id = 1234,
+ // suboption 1, expected result "false"
+ testVendorSuboption(Option::V6, 4491, 1, 1234, 1, TokenOption::EXISTS, "false");
+
+ // Case 4: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+ // suboption 2, expected result "false"
+ testVendorSuboption(Option::V6, 4491, 1, 4491, 2, TokenOption::EXISTS, "false");
+
+ // Case 5: expression vendor[4491].option[1].exists, option with vendor-id = 4491,
+ // suboption 1, expected result "true"
+ testVendorSuboption(Option::V6, 4491, 1, 4491, 1, TokenOption::EXISTS, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, pushing "
+ "result 'false'");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'false'");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies if vendor[4491].option[1].hex expression properly returns
+// value of said sub-option or empty string if desired option is not present.
+// This test is for DHCPv4.
+TEST_F(TokenTest, vendor4SuboptionHex) {
+ // Case 1: no option present, should return empty string
+ testVendorSuboption(Option::V4, 4491, 1, 0, 0, TokenOption::HEXADECIMAL, "");
+
+ // Case 2: option with vendor-id = 1234, no suboptions, expected result ""
+ testVendorSuboption(Option::V4, 4491, 1, 1234, 0, TokenOption::HEXADECIMAL, "");
+
+ // Case 3: option with vendor-id = 1234, suboption 1, expected result ""
+ testVendorSuboption(Option::V4, 4491, 1, 1234, 1, TokenOption::HEXADECIMAL, "");
+
+ // Case 4: option with vendor-id = 4491, suboption 2, expected result ""
+ testVendorSuboption(Option::V4, 4491, 1, 4491, 2, TokenOption::HEXADECIMAL, "");
+
+ // Case 5: option with vendor-id = 4491, suboption 1, expected result content
+ // of the option
+ testVendorSuboption(Option::V4, 4491, 1, 4491, 1, TokenOption::HEXADECIMAL, "alpha");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, pushing "
+ "result ''");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x616C706861");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies if vendor[4491].option[1].hex expression properly returns
+// value of said sub-option or empty string if desired option is not present.
+// This test is for DHCPv4.
+TEST_F(TokenTest, vendor6SuboptionHex) {
+ // Case 1: no option present, should return empty string
+ testVendorSuboption(Option::V6, 4491, 1, 0, 0, TokenOption::HEXADECIMAL, "");
+
+ // Case 2: option with vendor-id = 1234, no suboptions, expected result ""
+ testVendorSuboption(Option::V6, 4491, 1, 1234, 0, TokenOption::HEXADECIMAL, "");
+
+ // Case 3: option with vendor-id = 1234, suboption 1, expected result ""
+ testVendorSuboption(Option::V6, 4491, 1, 1234, 1, TokenOption::HEXADECIMAL, "");
+
+ // Case 4: option with vendor-id = 4491, suboption 2, expected result ""
+ testVendorSuboption(Option::V6, 4491, 1, 4491, 2, TokenOption::HEXADECIMAL, "");
+
+ // Case 5: option with vendor-id = 4491, suboption 1, expected result content
+ // of the option
+ testVendorSuboption(Option::V6, 4491, 1, 4491, 1, TokenOption::HEXADECIMAL, "alpha");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, pushing "
+ "result ''");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, "
+ "option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x");
+ addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x616C706861");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that "vendor-class[4491].exists" expression can be used
+// in DHCPv4.
+TEST_F(TokenTest, vendorClass4SpecificVendorExists) {
+ // Case 1: no option present, should fail
+ testVendorClassExists(Option::V4, 4491, 0, "false");
+
+ // Case 2: option exists, but has different vendor-id (1234), should fail
+ testVendorClassExists(Option::V4, 4491, 1234, "false");
+
+ // Case 3: option exists and has matching vendor-id, should succeed
+ testVendorClassExists(Option::V4, 4491, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, "
+ "pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that "vendor-class[4491].exists" expression can be used
+// in DHCPv6.
+TEST_F(TokenTest, vendorClass6SpecificVendorExists) {
+ // Case 1: no option present, should fail
+ testVendorClassExists(Option::V6, 4491, 0, "false");
+
+ // Case 2: option exists, but has different vendor-id (1234), should fail
+ testVendorClassExists(Option::V6, 4491, 1234, "false");
+
+ // Case 3: option exists and has matching vendor-id, should succeed
+ testVendorClassExists(Option::V6, 4491, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, pushing "
+ "result 'false'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that "vendor-class[*].exists" can be used in DHCPv4
+// and it matches a vendor class option with any vendor-id.
+TEST_F(TokenTest, vendorClass4AnyVendorExists) {
+ // Case 1: no option present, should fail
+ testVendorClassExists(Option::V4, 0, 0, "false");
+
+ // Case 2: option exists, should succeed, regardless of the vendor-id
+ testVendorClassExists(Option::V4, 0, 1234, "true");
+
+ // Case 3: option exists, should succeed, regardless of the vendor-id
+ testVendorClassExists(Option::V4, 0, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, "
+ "pushing result 'false'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 1234 "
+ "found, pushing result 'true'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that "vendor-class[*].exists" can be used in DHCPv6
+// and it matches a vendor class option with any vendor-id.
+TEST_F(TokenTest, vendorClass6AnyVendorExists) {
+ // Case 1: no option present, should fail
+ testVendorClassExists(Option::V6, 0, 0, "false");
+
+ // Case 2: option exists, should succeed, regardless of the vendor-id
+ testVendorClassExists(Option::V6, 0, 1234, "true");
+
+ // Case 3: option exists, should succeed, regardless of the vendor-id
+ testVendorClassExists(Option::V6, 0, 4491, "true");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, pushing "
+ "result 'false'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 1234 "
+ "found, pushing result 'true'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 "
+ "found, pushing result 'true'");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test if expression "vendor-class.enterprise" works properly for DHCPv4.
+TEST_F(TokenTest, vendorClass4enterprise) {
+ // Case 1: No option present, should return empty string
+ testVendorClassEnterprise(Option::V4, 0, "");
+
+ // Case 2: Option with vendor-id 1234, should return "1234"
+ testVendorClassEnterprise(Option::V4, 1234, encode(1234));
+
+ // Case 3: Option with vendor-id set to maximum value, should still
+ // be able to handle it
+ testVendorClassEnterprise(Option::V4, 4294967295, encode(4294967295));
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, pushing "
+ "result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id "
+ "1234 as result 0x000004D2");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id "
+ "4294967295 as result 0xFFFFFFFF");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test if expression "vendor-class.enterprise" works properly for DHCPv6.
+TEST_F(TokenTest, vendorClass6enterprise) {
+ // Case 1: No option present, should return empty string
+ testVendorClassEnterprise(Option::V6, 0, "");
+
+ // Case 2: Option with vendor-id 1234, should return "1234"
+ testVendorClassEnterprise(Option::V6, 1234, encode(1234));
+
+ // Case 3: Option with vendor-id set to maximum value, should still
+ // be able to handle it.
+ testVendorClassEnterprise(Option::V6, 4294967295, encode(4294967295));
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, pushing "
+ "result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id "
+ "1234 as result 0x000004D2");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id "
+ "4294967295 as result 0xFFFFFFFF");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test that expression "vendor-class[4491].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv4.
+TEST_F(TokenTest, vendorClass4SpecificVendorData) {
+ // Case 1: Expression looks for vendor-id 4491, data[0], there is no
+ // vendor-class option at all, expected result is empty string.
+ testVendorClassData(Option::V4, 4491, 0, 0, 0, "");
+
+ // Case 2: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+ testVendorClassData(Option::V4, 4491, 0, 1234, 0, "");
+
+ // Case 3: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 4491 and no data, expected result is empty string.
+ // Note that vendor option in v4 always have at least one data chunk, even though
+ // it may be empty. The OptionVendor code was told to not create any special
+ // tuples, but it creates one empty on its own. So the code finds that one
+ // tuple and extracts its content (an empty string).
+ testVendorClassData(Option::V4, 4491, 0, 4491, 0, "");
+
+ // Case 4: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string
+ testVendorClassData(Option::V4, 4491, 0, 1234, 1, "");
+
+ // Case 5: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+ // content of that data ("alpha")
+ testVendorClassData(Option::V4, 4491, 0, 4491, 1, "alpha");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor "
+ "class found, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor "
+ "class found, pushing result 'alpha'");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test that expression "vendor-class[4491].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv6.
+TEST_F(TokenTest, vendorClass6SpecificVendorData) {
+ // Case 1: Expression looks for vendor-id 4491, data[0], there is no
+ // vendor-class option at all, expected result is empty string.
+ testVendorClassData(Option::V6, 4491, 0, 0, 0, "");
+
+ // Case 2: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+ testVendorClassData(Option::V6, 4491, 0, 1234, 0, "");
+
+ // Case 3: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 4491 and no data, expected result is empty string
+ testVendorClassData(Option::V6, 4491, 0, 4491, 0, "");
+
+ // Case 4: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string
+ testVendorClassData(Option::V6, 4491, 0, 1234, 1, "");
+
+ // Case 5: Expression looks for vendor-id 4491, data[0], there is
+ // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+ // content of that data ("alpha")
+ testVendorClassData(Option::V6, 4491, 0, 4491, 1, "alpha");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 0, "
+ "but option with enterprise-id 4491 has only 0 data tuple(s), "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor "
+ "class found, pushing result 'alpha'");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test that expression "vendor-class[*].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv4.
+TEST_F(TokenTest, vendorClass4AnyVendorData) {
+ // Case 1: Expression looks for any vendor-id (0), data[0], there is no
+ // vendor-class option at all, expected result is empty string.
+ testVendorClassData(Option::V4, 0, 0, 0, 0, "");
+
+ // Case 2: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 1234 and no data (one empty tuple), expected
+ // result is empty string.
+ testVendorClassData(Option::V4, 0, 0, 1234, 0, "");
+
+ // Case 3: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 4491 and no data (one empty tuple), expected
+ // result is empty string.
+ testVendorClassData(Option::V4, 0, 0, 4491, 0, "");
+
+ // Case 4: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 1234 and 1 data tuple, expected result is
+ // content of that data ("alpha")
+ testVendorClassData(Option::V4, 0, 0, 1234, 1, "alpha");
+
+ // Case 5: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+ // content of that data ("alpha")
+ testVendorClassData(Option::V4, 0, 0, 4491, 1, "alpha");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in "
+ "vendor class found, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in "
+ "vendor class found, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in "
+ "vendor class found, pushing result 'alpha'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in "
+ "vendor class found, pushing result 'alpha'");
+ EXPECT_TRUE(checkFile());
+}
+
+// Test that expression "vendor-class[*].data" is able to retrieve content
+// of the first tuple of the vendor-class option in DHCPv6.
+TEST_F(TokenTest, vendorClass6AnyVendorData) {
+ // Case 1: Expression looks for any vendor-id (0), data[0], there is no
+ // vendor-class option at all, expected result is empty string.
+ testVendorClassData(Option::V6, 0, 0, 0, 0, "");
+
+ // Case 2: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+ testVendorClassData(Option::V6, 0, 0, 1234, 0, "");
+
+ // Case 3: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 4491 and no data, expected result is empty string
+ testVendorClassData(Option::V6, 0, 0, 4491, 0, "");
+
+ // Case 4: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 1234 and 1 data tuple, expected result is
+ // content of that data ("alpha")
+ testVendorClassData(Option::V6, 0, 0, 1234, 1, "alpha");
+
+ // Case 5: Expression looks for any vendor-id (0), data[0], there is
+ // vendor-class with vendor-id 4491 and 1 data tuple, expected result is
+ // content of that data ("alpha")
+ testVendorClassData(Option::V6, 0, 0, 4491, 1, "alpha");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 0, "
+ "but option with enterprise-id 1234 has only 0 data tuple(s), "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 0, "
+ "but option with enterprise-id 4491 has only 0 data tuple(s), "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor "
+ "class found, pushing result 'alpha'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor "
+ "class found, pushing result 'alpha'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies if expression vendor-class[4491].data[3] is able to access
+// the tuple specified by index. This is a DHCPv4 test.
+TEST_F(TokenTest, vendorClass4DataIndex) {
+ // Case 1: Expression looks for vendor-id 4491, data[3], there is no
+ // vendor-class option at all, expected result is empty string.
+ testVendorClassData(Option::V4, 4491, 3, 0, 0, "");
+
+ // Case 2: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+ testVendorClassData(Option::V4, 4491, 3, 1234, 0, "");
+
+ // Case 3: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 4491 and no data, expected result is empty string
+ testVendorClassData(Option::V4, 4491, 3, 4491, 0, "");
+
+ // Case 4: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string.
+ testVendorClassData(Option::V4, 4491, 3, 1234, 1, "");
+
+ // Case 5: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 4491, but has only 3 data tuples, expected
+ // result is empty string.
+ testVendorClassData(Option::V4, 4491, 3, 4491, 3, "");
+
+ // Case 6: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 4491 and 5 data tuples, expected result is
+ // content of that tuple ("gamma")
+ testVendorClassData(Option::V4, 4491, 3, 4491, 5, "gamma");
+
+ // Case 6: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 1234 and 5 data tuples, expected result is
+ // empty string, because vendor-id does not match.
+ testVendorClassData(Option::V4, 4491, 3, 1234, 5, "");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, "
+ "but option with enterprise-id 4491 has only 1 data tuple(s), "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, "
+ "but option with enterprise-id 4491 has only 3 data tuple(s), "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 3 (out of 5 received) in vendor "
+ "class found, pushing result 'gamma'");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies if expression vendor-class[4491].data[3] is able to access
+// the tuple specified by index. This is a DHCPv6 test.
+TEST_F(TokenTest, vendorClass6DataIndex) {
+ // Case 1: Expression looks for vendor-id 4491, data[3], there is no
+ // vendor-class option at all, expected result is empty string.
+ testVendorClassData(Option::V6, 4491, 3, 0, 0, "");
+
+ // Case 2: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 1234 and no data, expected result is empty string.
+ testVendorClassData(Option::V6, 4491, 3, 1234, 0, "");
+
+ // Case 3: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 4491 and no data, expected result is empty string
+ testVendorClassData(Option::V6, 4491, 3, 4491, 0, "");
+
+ // Case 4: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 1234 and 5 data tuples, expected result is empty string.
+ testVendorClassData(Option::V6, 4491, 3, 1234, 5, "");
+
+ // Case 5: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 4491, but has only 3 data tuples, expected
+ // result is empty string.
+ testVendorClassData(Option::V6, 4491, 3, 4491, 3, "");
+
+ // Case 6: Expression looks for vendor-id 4491, data[3], there is
+ // vendor-class with vendor-id 4491 and 5 data tuples, expected result is
+ // content of that tuple ("gamma")
+ testVendorClassData(Option::V6, 4491, 3, 4491, 5, "gamma");
+
+ // Check if the logged messages are correct.
+ addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, "
+ "but option with enterprise-id 4491 has only 0 data tuple(s), "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for "
+ "4491, option had 1234, pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, "
+ "but option with enterprise-id 4491 has only 3 data tuple(s), "
+ "pushing result ''");
+ addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 3 (out of 5 received) in vendor"
+ " class found, pushing result 'gamma'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that the existing RAI -sunoption can be found.
+TEST_F(TokenTest, subOption) {
+
+ // Insert relay option with sub-options 1 and 13
+ insertRelay4Option();
+
+ // Creating the token should be safe.
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 13, TokenOption::TEXTUAL)));
+
+ // We should be able to evaluate it.
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // The option should be found and option[82].option[13] should evaluate
+ // to thecontent of that sub-option, i.e. "thirteen"
+ EXPECT_EQ("thirteen", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 "
+ "sub-option 13 with value 'thirteen'");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that the code properly handles cases when
+// there is a RAI option, but there's no requested sub-option.
+TEST_F(TokenTest, subOptionNoSubOption) {
+
+ // Insert relay option with sub-options 1 and 13
+ insertRelay4Option();
+
+
+ // Creating the token should be safe.
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 15, TokenOption::TEXTUAL)));
+
+ // We should be able to evaluate it.
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // The option should NOT be found (there is no sub-option 15),
+ // so the expression should evaluate to ""
+ EXPECT_EQ("", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 "
+ "sub-option 15 with value ''");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that the code properly handles cases when
+// there's no RAI option at all.
+TEST_F(TokenTest, subOptionNoOption) {
+
+ // We didn't call insertRelay4Option(), so there's no RAI option.
+
+ // Creating the token should be safe.
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 13, TokenOption::TEXTUAL)));
+
+ // We should be able to evaluate it.
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // we should have one value on the stack
+ ASSERT_EQ(1, values_.size());
+
+ // The option should NOT be found (there is no option 82),
+ // so the expression should evaluate to ""
+ EXPECT_EQ("", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUB_OPTION_NO_OPTION Requested option 82 "
+ "sub-option 13, but the parent option is not present, "
+ "pushing result ''");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test checks that only the requested parent is searched for
+// the requested sub-option.
+TEST_F(TokenTest, subOptionOptionOnly) {
+
+ // Insert relay option with sub-options 1 and 13
+ insertRelay4Option();
+
+ // Add options 13 and 70 to the packet.
+ OptionPtr opt13(new OptionString(Option::V4, 13, "THIRTEEN"));
+ OptionPtr opt70(new OptionString(Option::V4, 70, "SEVENTY"));
+ pkt4_->addOption(opt13);
+ pkt4_->addOption(opt70);
+
+ // The situation is as follows:
+ // Packet:
+ // - option 13 (containing "THIRTEEN")
+ // - option 82 (rai)
+ // - option 1 (containing "one")
+ // - option 13 (containing "thirteen")
+
+ // Let's try to get option 13. It should get the one from RAI
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 13, TokenOption::TEXTUAL)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("thirteen", values_.top());
+
+ // Try to get option 1. It should get the one from RAI
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 1, TokenOption::TEXTUAL)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("one", values_.top());
+
+ // Try to get option 70. It should fail, as there's no such
+ // sub option in RAI.
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 70, TokenOption::TEXTUAL)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("", values_.top());
+
+ // Try to check option 1. It should return "true"
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 1, TokenOption::EXISTS)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+
+ // Try to check option 70. It should return "false"
+ clearStack();
+ ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS,
+ 70, TokenOption::EXISTS)));
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 "
+ "sub-option 13 with value 'thirteen'");
+ addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 "
+ "sub-option 1 with value 'one'");
+ addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 "
+ "sub-option 70 with value ''");
+ addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 "
+ "sub-option 1 with value 'true'");
+ addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 "
+ "sub-option 70 with value 'false'");
+ EXPECT_TRUE(checkFile());
+}
+
+// Checks if various values can be represented as integer tokens
+TEST_F(TokenTest, integer) {
+ testInteger(encode(0), 0);
+ testInteger(encode(6), 6);
+ testInteger(encode(255), 255);
+ testInteger(encode(256), 256);
+ testInteger(encode(1410), 1410);
+ testInteger(encode(4294967295), 4294967295);
+}
+
+// Verify TokenSplit::eval, single delimiter.
+TEST_F(TokenTest, split) {
+ // Get the whole string
+ std::string input(".two.three..five.");
+ std::string delims(".");
+
+ // Empty input string should yield empty result.
+ verifySplitEval("", delims, "1", "");
+
+ // Empty delimiters string should yield original string result.
+ verifySplitEval(input, "", "1", input);
+
+ // Field number less than one should yield empty result.
+ verifySplitEval(input, delims, "0", "");
+
+ // Now get each field in succession.
+ verifySplitEval(input, delims, "1", "");
+ verifySplitEval(input, delims, "2", "two");
+ verifySplitEval(input, delims, "3", "three");
+ verifySplitEval(input, delims, "4", "");
+ verifySplitEval(input, delims, "5", "five");
+ verifySplitEval(input, delims, "6", "");
+
+ // Too large of a field should yield empty result.
+ verifySplitEval(input, delims, "7", "");
+
+ // A string without delimiters returns as field 1.
+ verifySplitEval("just_one", delims, "1", "just_one");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+ addString("EVAL_DEBUG_SPLIT_EMPTY Popping field 1, delimiters .,"
+ " string , pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT_DELIM_EMPTY Popping field 1, delimiters ,"
+ " string .two.three..five., pushing result 0x2E74776F2E74687265652E2E666976652E");
+ addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 0, delimiters .,"
+ " string .two.three..five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .,"
+ " string .two.three..five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 2, delimiters .,"
+ " string .two.three..five., pushing result 0x74776F");
+ addString("EVAL_DEBUG_SPLIT Popping field 3, delimiters .,"
+ " string .two.three..five., pushing result 0x7468726565");
+ addString("EVAL_DEBUG_SPLIT Popping field 4, delimiters .,"
+ " string .two.three..five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 5, delimiters .,"
+ " string .two.three..five., pushing result 0x66697665");
+ addString("EVAL_DEBUG_SPLIT Popping field 6, delimiters .,"
+ " string .two.three..five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 7, delimiters .,"
+ " string .two.three..five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .,"
+ " string just_one, pushing result 0x6A7573745F6F6E65");
+ EXPECT_TRUE(checkFile());
+}
+
+// Verify TokenSplit::eval with more than one delimiter.
+TEST_F(TokenTest, splitMultipleDelims) {
+ // Get the whole string
+ std::string input(".two:three.:five.");
+ std::string delims(".:");
+
+ // Empty input string should yield empty result.
+ verifySplitEval("", delims, "1", "");
+
+ // Too small of a field should yield empty result.
+ verifySplitEval(input, delims, "0", "");
+
+ // Now get each field in succession.
+ verifySplitEval(input, delims, "1", "");
+ verifySplitEval(input, delims, "2", "two");
+ verifySplitEval(input, delims, "3", "three");
+ verifySplitEval(input, delims, "4", "");
+ verifySplitEval(input, delims, "5", "five");
+ verifySplitEval(input, delims, "6", "");
+
+ // Too large of a field should yield empty result.
+ verifySplitEval(input, delims, "7", "");
+
+ // A string without delimiters returns as field 1.
+ verifySplitEval("just_one", delims, "1", "just_one");
+
+ // Check that the debug output was correct. Add the strings
+ // to the test vector in the class and then call checkFile
+ // for comparison
+
+ addString("EVAL_DEBUG_SPLIT_EMPTY Popping field 1, delimiters .:,"
+ " string , pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 0, delimiters .:,"
+ " string .two:three.:five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .:,"
+ " string .two:three.:five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 2, delimiters .:,"
+ " string .two:three.:five., pushing result 0x74776F");
+ addString("EVAL_DEBUG_SPLIT Popping field 3, delimiters .:,"
+ " string .two:three.:five., pushing result 0x7468726565");
+ addString("EVAL_DEBUG_SPLIT Popping field 4, delimiters .:,"
+ " string .two:three.:five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 5, delimiters .:,"
+ " string .two:three.:five., pushing result 0x66697665");
+ addString("EVAL_DEBUG_SPLIT Popping field 6, delimiters .:,"
+ " string .two:three.:five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 7, delimiters .:,"
+ " string .two:three.:five., pushing result 0x");
+ addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .:,"
+ " string just_one, pushing result 0x6A7573745F6F6E65");
+ EXPECT_TRUE(checkFile());
+}
+
+};
diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc
new file mode 100644
index 0000000..017f22d
--- /dev/null
+++ b/src/lib/eval/token.cc
@@ -0,0 +1,1319 @@
+// Copyright (C) 2015-2023 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 <eval/token.h>
+#include <eval/eval_log.h>
+#include <eval/eval_context.h>
+#include <util/encode/hex.h>
+#include <util/io_utilities.h>
+#include <asiolink/io_address.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <boost/lexical_cast.hpp>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+#include <cstring>
+#include <string>
+#include <iomanip>
+#include <sstream>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
+
+using isc::util::encode::toHex;
+
+void
+TokenString::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ // Literals only push, nothing to pop
+ values.push(value_);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_STRING)
+ .arg('\'' + value_ + '\'');
+}
+
+TokenHexString::TokenHexString(const string& str) : value_("") {
+ // Check string starts "0x" or "0x" and has at least one additional character.
+ if ((str.size() < 3) ||
+ (str[0] != '0') ||
+ ((str[1] != 'x') && (str[1] != 'X'))) {
+ return;
+ }
+ string digits = str.substr(2);
+
+ // Transform string of hexadecimal digits into binary format
+ vector<uint8_t> binary;
+ try {
+ // The decodeHex function expects that the string contains an
+ // even number of digits. If we don't meet this requirement,
+ // we have to insert a leading 0.
+ if ((digits.length() % 2) != 0) {
+ digits = digits.insert(0, "0");
+ }
+ util::encode::decodeHex(digits, binary);
+ } catch (...) {
+ return;
+ }
+
+ // Convert to a string (note that binary.size() cannot be 0)
+ value_.resize(binary.size());
+ memmove(&value_[0], &binary[0], binary.size());
+}
+
+void
+TokenHexString::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ // Literals only push, nothing to pop
+ values.push(value_);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_HEXSTRING)
+ .arg(toHex(value_));
+}
+
+TokenIpAddress::TokenIpAddress(const string& addr) : value_("") {
+ // Transform IP address into binary format
+ vector<uint8_t> binary;
+ try {
+ asiolink::IOAddress ip(addr);
+ binary = ip.toBytes();
+ } catch (...) {
+ return;
+ }
+
+ // Convert to a string (note that binary.size() is 4 or 16, so not 0)
+ value_.resize(binary.size());
+ memmove(&value_[0], &binary[0], binary.size());
+}
+
+void
+TokenIpAddress::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ // Literals only push, nothing to pop
+ values.push(value_);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IPADDRESS)
+ .arg(toHex(value_));
+}
+
+void
+TokenIpAddressToText::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ size_t size = op.size();
+
+ if (!size) {
+ return;
+ }
+
+ values.pop();
+
+ if ((size != V4ADDRESS_LEN) && (size != V6ADDRESS_LEN)) {
+ isc_throw(EvalTypeError, "Can not convert to valid address.");
+ }
+
+ std::vector<uint8_t> binary(op.begin(), op.end());
+
+ if (size == V4ADDRESS_LEN) {
+ op = asiolink::IOAddress::fromBytes(AF_INET, binary.data()).toText();
+ } else {
+ op = asiolink::IOAddress::fromBytes(AF_INET6, binary.data()).toText();
+ }
+
+ values.push(op);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IPADDRESSTOTEXT)
+ .arg(op);
+}
+
+void
+TokenInt8ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ size_t size = op.size();
+
+ if (!size) {
+ return;
+ }
+
+ values.pop();
+
+ if (size != sizeof(int8_t)) {
+ isc_throw(EvalTypeError, "Can not convert to valid int8.");
+ }
+
+ stringstream tmp;
+ tmp << static_cast<int32_t>(*(reinterpret_cast<const int8_t*>(op.data())));
+ op = tmp.str();
+ values.push(op);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_INT8TOTEXT)
+ .arg(op);
+}
+
+void
+TokenInt16ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ size_t size = op.size();
+
+ if (!size) {
+ return;
+ }
+
+ values.pop();
+
+ if (size != sizeof(int16_t)) {
+ isc_throw(EvalTypeError, "Can not convert to valid int16.");
+ }
+
+ stringstream tmp;
+ int16_t value = static_cast<int16_t>(readUint16(const_cast<const char*>(op.data()), size));
+ tmp << value;
+ op = tmp.str();
+ values.push(op);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_INT16TOTEXT)
+ .arg(op);
+}
+
+void
+TokenInt32ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ size_t size = op.size();
+
+ if (!size) {
+ return;
+ }
+
+ values.pop();
+
+ if (size != sizeof(int32_t)) {
+ isc_throw(EvalTypeError, "Can not convert to valid int32.");
+ }
+
+ stringstream tmp;
+ int32_t value = static_cast<int32_t>(readUint32(const_cast<const char*>(op.data()), size));
+ tmp << value;
+ op = tmp.str();
+ values.push(op);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_INT32TOTEXT)
+ .arg(op);
+}
+
+void
+TokenUInt8ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ size_t size = op.size();
+
+ if (!size) {
+ return;
+ }
+
+ values.pop();
+
+ if (size != sizeof(uint8_t)) {
+ isc_throw(EvalTypeError, "Can not convert to valid uint8.");
+ }
+
+ stringstream tmp;
+ tmp << static_cast<uint32_t>(*(reinterpret_cast<const uint8_t*>(op.data())));
+ op = tmp.str();
+ values.push(op);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_UINT8TOTEXT)
+ .arg(op);
+}
+
+void
+TokenUInt16ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ size_t size = op.size();
+
+ if (!size) {
+ return;
+ }
+
+ values.pop();
+
+ if (size != sizeof(uint16_t)) {
+ isc_throw(EvalTypeError, "Can not convert to valid uint16.");
+ }
+
+ stringstream tmp;
+ uint16_t value = readUint16(const_cast<const char*>(op.data()), size);
+ tmp << value;
+ op = tmp.str();
+ values.push(op);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_UINT16TOTEXT)
+ .arg(op);
+}
+
+void
+TokenUInt32ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ size_t size = op.size();
+
+ if (!size) {
+ return;
+ }
+
+ values.pop();
+
+ if (size != sizeof(uint32_t)) {
+ isc_throw(EvalTypeError, "Can not convert to valid uint32.");
+ }
+
+ stringstream tmp;
+ uint32_t value = readUint32(const_cast<const char*>(op.data()), size);
+ tmp << value;
+ op = tmp.str();
+ values.push(op);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_UINT32TOTEXT)
+ .arg(op);
+}
+
+OptionPtr
+TokenOption::getOption(Pkt& pkt) {
+ return (pkt.getOption(option_code_));
+}
+
+void
+TokenOption::evaluate(Pkt& pkt, ValueStack& values) {
+ OptionPtr opt = getOption(pkt);
+ std::string opt_str;
+ if (opt) {
+ if (representation_type_ == TEXTUAL) {
+ opt_str = opt->toString();
+ } else if (representation_type_ == HEXADECIMAL) {
+ std::vector<uint8_t> binary = opt->toBinary();
+ opt_str.resize(binary.size());
+ if (!binary.empty()) {
+ memmove(&opt_str[0], &binary[0], binary.size());
+ }
+ } else {
+ opt_str = "true";
+ }
+ } else if (representation_type_ == EXISTS) {
+ opt_str = "false";
+ }
+
+ // Push value of the option or empty string if there was no such option
+ // in the packet.
+ values.push(opt_str);
+
+ // Log what we pushed, both exists and textual are simple text
+ // and can be output directly. We also include the code number
+ // of the requested option.
+ if (representation_type_ == HEXADECIMAL) {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OPTION)
+ .arg(option_code_)
+ .arg(toHex(opt_str));
+ } else {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OPTION)
+ .arg(option_code_)
+ .arg('\'' + opt_str + '\'');
+ }
+}
+
+std::string
+TokenOption::pushFailure(ValueStack& values) {
+ std::string txt;
+ if (representation_type_ == EXISTS) {
+ txt = "false";
+ }
+ values.push(txt);
+ return (txt);
+}
+
+TokenRelay4Option::TokenRelay4Option(const uint16_t option_code,
+ const RepresentationType& rep_type)
+ : TokenOption(option_code, rep_type) {
+}
+
+OptionPtr TokenRelay4Option::getOption(Pkt& pkt) {
+ // Check if there is Relay Agent Option.
+ OptionPtr rai = pkt.getOption(DHO_DHCP_AGENT_OPTIONS);
+ if (!rai) {
+ return (OptionPtr());
+ }
+
+ // If there is, try to return its suboption
+ return (rai->getOption(option_code_));
+}
+
+OptionPtr TokenRelay6Option::getOption(Pkt& pkt) {
+ try {
+ // Check if it's a Pkt6. If it's not the dynamic_cast will
+ // throw std::bad_cast.
+ Pkt6& pkt6 = dynamic_cast<Pkt6&>(pkt);
+
+ try {
+ // Now that we have the right type of packet we can
+ // get the option and return it.
+ if (nest_level_ >= 0) {
+ uint8_t nesting_level = static_cast<uint8_t>(nest_level_);
+ return(pkt6.getRelayOption(option_code_, nesting_level));
+ } else {
+ int nesting_level = pkt6.relay_info_.size() + nest_level_;
+ if (nesting_level < 0) {
+ return (OptionPtr());
+ }
+ return(pkt6.getRelayOption(option_code_,
+ static_cast<uint8_t>(nesting_level)));
+ }
+ }
+ catch (const isc::OutOfRange&) {
+ // The only exception we expect is OutOfRange if the nest
+ // level is out of range of the encapsulations, for example
+ // if nest_level_ is 4 and there are only 2 encapsulations.
+ // We return a NULL in that case.
+ return (OptionPtr());
+ }
+
+ } catch (const std::bad_cast&) {
+ isc_throw(EvalTypeError, "Specified packet is not Pkt6");
+ }
+
+}
+
+void
+TokenPkt::evaluate(Pkt& pkt, ValueStack& values) {
+ string value;
+ vector<uint8_t> binary;
+ string type_str;
+ bool is_binary = true;
+ bool print_hex = true;
+ switch (type_) {
+ case IFACE:
+ is_binary = false;
+ print_hex = false;
+ value = pkt.getIface();
+ type_str = "iface";
+ break;
+ case SRC:
+ binary = pkt.getRemoteAddr().toBytes();
+ type_str = "src";
+ break;
+ case DST:
+ binary = pkt.getLocalAddr().toBytes();
+ type_str = "dst";
+ break;
+ case LEN:
+ // len() returns a size_t but in fact it can't be very large
+ // (with UDP transport it fits in 16 bits)
+ // the len() method is not const because of DHCPv6 relays.
+ // We assume here it has no bad side effects...
+ value = EvalContext::fromUint32(static_cast<uint32_t>(const_cast<Pkt&>(pkt).len()));
+ is_binary = false;
+ type_str = "len";
+ break;
+
+ default:
+ isc_throw(EvalTypeError, "Bad meta data specified: " << static_cast<int>(type_));
+ }
+
+ if (is_binary) {
+ value.resize(binary.size());
+ if (!binary.empty()) {
+ memmove(&value[0], &binary[0], binary.size());
+ }
+ }
+ values.push(value);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT)
+ .arg(type_str)
+ .arg(print_hex ? toHex(value) : value);
+}
+
+void
+TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) {
+ vector<uint8_t> binary;
+ string value;
+ string type_str;
+ try {
+ // Check if it's a Pkt4. If it's not, the dynamic_cast will throw
+ // std::bad_cast (failed dynamic_cast returns NULL for pointers and
+ // throws for references).
+ const Pkt4& pkt4 = dynamic_cast<const Pkt4&>(pkt);
+
+ switch (type_) {
+ case CHADDR: {
+ HWAddrPtr hwaddr = pkt4.getHWAddr();
+ if (!hwaddr) {
+ // This should never happen. Every Pkt4 should always have
+ // a hardware address.
+ isc_throw(EvalTypeError,
+ "Packet does not have hardware address");
+ }
+ binary = hwaddr->hwaddr_;
+ type_str = "mac";
+ break;
+ }
+ case GIADDR:
+ binary = pkt4.getGiaddr().toBytes();
+ type_str = "giaddr";
+ break;
+ case CIADDR:
+ binary = pkt4.getCiaddr().toBytes();
+ type_str = "ciaddr";
+ break;
+ case YIADDR:
+ binary = pkt4.getYiaddr().toBytes();
+ type_str = "yiaddr";
+ break;
+ case SIADDR:
+ binary = pkt4.getSiaddr().toBytes();
+ type_str = "siaddr";
+ break;
+ case HLEN:
+ // Pad the uint8_t field to 4 bytes.
+ value = EvalContext::fromUint32(pkt4.getHlen());
+ type_str = "hlen";
+ break;
+ case HTYPE:
+ // Pad the uint8_t field to 4 bytes.
+ value = EvalContext::fromUint32(pkt4.getHtype());
+ type_str = "htype";
+ break;
+ case MSGTYPE:
+ value = EvalContext::fromUint32(pkt4.getType());
+ type_str = "msgtype";
+ break;
+ case TRANSID:
+ value = EvalContext::fromUint32(pkt4.getTransid());
+ type_str = "transid";
+ break;
+ default:
+ isc_throw(EvalTypeError, "Bad field specified: " << static_cast<int>(type_));
+ }
+
+ } catch (const std::bad_cast&) {
+ isc_throw(EvalTypeError, "Specified packet is not a Pkt4");
+ }
+
+ if (!binary.empty()) {
+ value.resize(binary.size());
+ memmove(&value[0], &binary[0], binary.size());
+ }
+ values.push(value);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT4)
+ .arg(type_str)
+ .arg(toHex(value));
+}
+
+void
+TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) {
+ string value;
+ string type_str;
+ try {
+ // Check if it's a Pkt6. If it's not the dynamic_cast will throw
+ // std::bad_cast (failed dynamic_cast returns NULL for pointers and
+ // throws for references).
+ const Pkt6& pkt6 = dynamic_cast<const Pkt6&>(pkt);
+
+ switch (type_) {
+ case MSGTYPE: {
+ // msg type is an uint8_t integer. We want a 4 byte string so 0 pad.
+ value = EvalContext::fromUint32(pkt6.getType());
+ type_str = "msgtype";
+ break;
+ }
+ case TRANSID: {
+ // transaction id is an uint32_t integer. We want a 4 byte string so copy
+ value = EvalContext::fromUint32(pkt6.getTransid());
+ type_str = "transid";
+ break;
+ }
+ default:
+ isc_throw(EvalTypeError, "Bad field specified: " << static_cast<int>(type_));
+ }
+
+ } catch (const std::bad_cast&) {
+ isc_throw(EvalTypeError, "Specified packet is not Pkt6");
+ }
+
+ values.push(value);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT6)
+ .arg(type_str)
+ .arg(toHex(value));
+}
+
+void
+TokenRelay6Field::evaluate(Pkt& pkt, ValueStack& values) {
+ vector<uint8_t> binary;
+ string type_str;
+ try {
+ // Check if it's a Pkt6. If it's not the dynamic_cast will
+ // throw std::bad_cast.
+ const Pkt6& pkt6 = dynamic_cast<const Pkt6&>(pkt);
+ uint8_t relay_level;
+
+ try {
+ if (nest_level_ >= 0) {
+ relay_level = static_cast<uint8_t>(nest_level_);
+ } else {
+ int nesting_level = pkt6.relay_info_.size() + nest_level_;
+ if (nesting_level < 0) {
+ // Don't throw OutOfRange here
+ nesting_level = 32;
+ }
+ relay_level = static_cast<uint8_t>(nesting_level);
+ }
+ switch (type_) {
+ // Now that we have the right type of packet we can
+ // get the option and return it.
+ case LINKADDR:
+ type_str = "linkaddr";
+ binary = pkt6.getRelay6LinkAddress(relay_level).toBytes();
+ break;
+ case PEERADDR:
+ type_str = "peeraddr";
+ binary = pkt6.getRelay6PeerAddress(relay_level).toBytes();
+ break;
+ }
+ } catch (const isc::OutOfRange&) {
+ // The only exception we expect is OutOfRange if the nest
+ // level is invalid. We push "" in that case.
+ values.push("");
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6_RANGE)
+ .arg(type_str)
+ .arg(int(nest_level_))
+ .arg("0x");
+ return;
+ }
+ } catch (const std::bad_cast&) {
+ isc_throw(EvalTypeError, "Specified packet is not Pkt6");
+ }
+
+ string value;
+ value.resize(binary.size());
+ if (!binary.empty()) {
+ memmove(&value[0], &binary[0], binary.size());
+ }
+ values.push(value);
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6)
+ .arg(type_str)
+ .arg(int(nest_level_))
+ .arg(toHex(value));
+}
+
+void
+TokenEqual::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 2) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "2 values for == operator, got " << values.size());
+ }
+
+ string op1 = values.top();
+ values.pop();
+ string op2 = values.top();
+ values.pop(); // Dammit, std::stack interface is awkward.
+
+ if (op1 == op2)
+ values.push("true");
+ else
+ values.push("false");
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_EQUAL)
+ .arg(toHex(op1))
+ .arg(toHex(op2))
+ .arg('\'' + values.top() + '\'');
+}
+
+void
+TokenSubstring::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 3) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "3 values for substring operator, got " << values.size());
+ }
+
+ string len_str = values.top();
+ values.pop();
+ string start_str = values.top();
+ values.pop();
+ string string_str = values.top();
+ values.pop();
+
+ // If we have no string to start with we push an empty string and leave
+ if (string_str.empty()) {
+ values.push("");
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING_EMPTY)
+ .arg(len_str)
+ .arg(start_str)
+ .arg("0x")
+ .arg("0x");
+ return;
+ }
+
+ // Convert the starting position and length from strings to numbers
+ // the length may also be "all" in which case simply make it the
+ // length of the string.
+ // If we have a problem push an empty string and leave
+ int start_pos;
+ int length;
+ try {
+ start_pos = boost::lexical_cast<int>(start_str);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(EvalTypeError, "the parameter '" << start_str
+ << "' for the starting position of the substring "
+ << "couldn't be converted to an integer.");
+ }
+ try {
+ if (len_str == "all") {
+ length = string_str.length();
+ } else {
+ length = boost::lexical_cast<int>(len_str);
+ }
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(EvalTypeError, "the parameter '" << len_str
+ << "' for the length of the substring "
+ << "couldn't be converted to an integer.");
+ }
+
+ const int string_length = string_str.length();
+ // If the starting position is outside of the string push an
+ // empty string and leave
+ if ((start_pos < -string_length) || (start_pos >= string_length)) {
+ values.push("");
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING_RANGE)
+ .arg(len_str)
+ .arg(start_str)
+ .arg(toHex(string_str))
+ .arg("0x");
+ return;
+ }
+
+ // Adjust the values to be something for substr. We first figure out
+ // the starting position, then update it and the length to get the
+ // characters before or after it depending on the sign of length
+ if (start_pos < 0) {
+ start_pos = string_length + start_pos;
+ }
+
+ if (length < 0) {
+ length = -length;
+ if (length <= start_pos){
+ start_pos -= length;
+ } else {
+ length = start_pos;
+ start_pos = 0;
+ }
+ }
+
+ // and finally get the substring
+ values.push(string_str.substr(start_pos, length));
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING)
+ .arg(len_str)
+ .arg(start_str)
+ .arg(toHex(string_str))
+ .arg(toHex(values.top()));
+}
+
+void
+TokenSplit::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 3) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "3 values for split operator, got " << values.size());
+ }
+
+ // Pop the parameters.
+ string field_str = values.top();
+ values.pop();
+ string delim_str = values.top();
+ values.pop();
+ string string_str = values.top();
+ values.pop();
+
+ // If we have no string to start with we push an empty string and leave
+ if (string_str.empty()) {
+ values.push("");
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT_EMPTY)
+ .arg(field_str)
+ .arg(delim_str)
+ .arg(string_str)
+ .arg("0x");
+ return;
+ }
+
+ // Convert the field position from string to number
+ // If we have a problem push an empty string and leave
+ int field;
+ try {
+ field = boost::lexical_cast<int>(field_str);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(EvalTypeError, "the parameter '" << field_str
+ << "' for the field field for split "
+ << "couldn't be converted to an integer.");
+ }
+
+ // If we have no delimiter to start with we push the input string and leave
+ if (delim_str.empty()) {
+ values.push(string_str);
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT_DELIM_EMPTY)
+ .arg(field_str)
+ .arg(delim_str)
+ .arg(string_str)
+ .arg(toHex(values.top()));
+ return;
+ }
+
+ // Split the string into fields.
+ std::vector<std::string> fields;
+ boost::split(fields, string_str, boost::is_any_of(delim_str),
+ boost::algorithm::token_compress_off);
+
+ // Range check the field.
+ if (field < 1 || field > fields.size()) {
+ // Push an empty string if field is out of range.
+ values.push("");
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE)
+ .arg(field_str)
+ .arg(delim_str)
+ .arg(string_str)
+ .arg("0x");
+ return;
+ }
+
+ // Push the desired field.
+ values.push(fields[field - 1]);
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT)
+ .arg(field_str)
+ .arg(delim_str)
+ .arg(string_str)
+ .arg(toHex(values.top()));
+}
+
+void
+TokenConcat::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 2) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "2 values for concat, got " << values.size());
+ }
+
+ string op1 = values.top();
+ values.pop();
+ string op2 = values.top();
+ values.pop(); // Dammit, std::stack interface is awkward.
+
+ // The top of the stack was evaluated last so this is the right order
+ values.push(op2 + op1);
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_CONCAT)
+ .arg(toHex(op1))
+ .arg(toHex(op2))
+ .arg(toHex(values.top()));
+}
+
+void
+TokenIfElse::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 3) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "3 values for ifelse, got " << values.size());
+ }
+
+ string iffalse = values.top();
+ values.pop();
+ string iftrue = values.top();
+ values.pop();
+ string cond = values.top();
+ values.pop();
+ bool val = toBool(cond);
+
+ if (val) {
+ values.push(iftrue);
+ } else {
+ values.push(iffalse);
+ }
+
+ // Log what we popped and pushed
+ if (val) {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_TRUE)
+ .arg('\'' + cond + '\'')
+ .arg(toHex(iffalse))
+ .arg(toHex(iftrue));
+ } else {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_FALSE)
+ .arg('\'' +cond + '\'')
+ .arg(toHex(iftrue))
+ .arg(toHex(iffalse));
+ }
+}
+
+void
+TokenToHexString::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 2) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "2 values for hexstring, got " << values.size());
+ }
+
+ string separator = values.top();
+ values.pop();
+ string binary = values.top();
+ values.pop();
+
+ bool first = true;
+ stringstream tmp;
+ tmp << hex;
+ for (size_t i = 0; i < binary.size(); ++i) {
+ if (!first) {
+ tmp << separator;
+ } else {
+ first = false;
+ }
+ tmp << setw(2) << setfill('0')
+ << (static_cast<unsigned>(binary[i]) & 0xff);
+ }
+ values.push(tmp.str());
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_TOHEXSTRING)
+ .arg(toHex(binary))
+ .arg(separator)
+ .arg(tmp.str());
+}
+
+void
+TokenNot::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() == 0) {
+ isc_throw(EvalBadStack, "Incorrect empty stack.");
+ }
+
+ string op = values.top();
+ values.pop();
+ bool val = toBool(op);
+
+ if (!val) {
+ values.push("true");
+ } else {
+ values.push("false");
+ }
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_NOT)
+ .arg('\'' + op + '\'')
+ .arg('\'' + values.top() + '\'');
+}
+
+void
+TokenAnd::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 2) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "2 values for and operator, got " << values.size());
+ }
+
+ string op1 = values.top();
+ values.pop();
+ bool val1 = toBool(op1);
+ string op2 = values.top();
+ values.pop(); // Dammit, std::stack interface is awkward.
+ bool val2 = toBool(op2);
+
+ if (val1 && val2) {
+ values.push("true");
+ } else {
+ values.push("false");
+ }
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_AND)
+ .arg('\'' + op1 + '\'')
+ .arg('\'' + op2 + '\'')
+ .arg('\'' + values.top() + '\'');
+}
+
+void
+TokenOr::evaluate(Pkt& /*pkt*/, ValueStack& values) {
+ if (values.size() < 2) {
+ isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+ "2 values for or operator, got " << values.size());
+ }
+
+ string op1 = values.top();
+ values.pop();
+ bool val1 = toBool(op1);
+ string op2 = values.top();
+ values.pop(); // Dammit, std::stack interface is awkward.
+ bool val2 = toBool(op2);
+
+ if (val1 || val2) {
+ values.push("true");
+ } else {
+ values.push("false");
+ }
+
+ // Log what we popped and pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OR)
+ .arg('\'' + op1 + '\'')
+ .arg('\'' + op2 + '\'')
+ .arg('\'' + values.top() + '\'');
+}
+
+void
+TokenMember::evaluate(Pkt& pkt, ValueStack& values) {
+ if (pkt.inClass(client_class_)) {
+ values.push("true");
+ } else {
+ values.push("false");
+ }
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_MEMBER)
+ .arg(client_class_)
+ .arg('\'' + values.top() + '\'');
+}
+
+TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr,
+ uint16_t option_code)
+ : TokenOption(option_code, repr), universe_(u), vendor_id_(vendor_id),
+ field_(option_code ? SUBOPTION : EXISTS) {
+}
+
+TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field)
+ : TokenOption(0, TokenOption::HEXADECIMAL), universe_(u), vendor_id_(vendor_id),
+ field_(field) {
+ if (field_ == EXISTS) {
+ representation_type_ = TokenOption::EXISTS;
+ }
+}
+
+uint32_t TokenVendor::getVendorId() const {
+ return (vendor_id_);
+}
+
+TokenVendor::FieldType TokenVendor::getField() const {
+ return (field_);
+}
+
+void TokenVendor::evaluate(Pkt& pkt, ValueStack& values) {
+ // Get the option first.
+ uint16_t code = 0;
+ switch (universe_) {
+ case Option::V4:
+ code = DHO_VIVSO_SUBOPTIONS;
+ break;
+ case Option::V6:
+ code = D6O_VENDOR_OPTS;
+ break;
+ }
+
+ OptionPtr opt = pkt.getOption(code);
+ OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ if (!vendor) {
+ // There's no vendor option, give up.
+ std::string txt = pushFailure(values);
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_NO_OPTION)
+ .arg(code)
+ .arg(txt);
+ return;
+ }
+
+ if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) {
+ // There is vendor option, but it has other vendor-id value
+ // than we're looking for. (0 means accept any vendor-id)
+ std::string txt = pushFailure(values);
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH)
+ .arg(vendor_id_)
+ .arg(vendor->getVendorId())
+ .arg(txt);
+ return;
+ }
+
+ switch (field_) {
+ case ENTERPRISE_ID:
+ {
+ // Extract enterprise-id
+ string txt(sizeof(uint32_t), 0);
+ uint32_t value = htonl(vendor->getVendorId());
+ memcpy(&txt[0], &value, sizeof(uint32_t));
+ values.push(txt);
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_ENTERPRISE_ID)
+ .arg(vendor->getVendorId())
+ .arg(util::encode::encodeHex(std::vector<uint8_t>(txt.begin(),
+ txt.end())));
+ return;
+ }
+ case SUBOPTION:
+ /// This is vendor[X].option[Y].exists, let's try to
+ /// extract the option
+ TokenOption::evaluate(pkt, values);
+ return;
+ case EXISTS:
+ // We already passed all the checks: the option is there and has specified
+ // enterprise-id.
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_EXISTS)
+ .arg(vendor->getVendorId())
+ .arg("true");
+ values.push("true");
+ return;
+ case DATA:
+ // This is for vendor-class option, we can skip it here.
+ isc_throw(EvalTypeError, "Field None is not valid for vendor-class");
+ return;
+ }
+}
+
+OptionPtr TokenVendor::getOption(Pkt& pkt) {
+ uint16_t code = 0;
+ switch (universe_) {
+ case Option::V4:
+ code = DHO_VIVSO_SUBOPTIONS;
+ break;
+ case Option::V6:
+ code = D6O_VENDOR_OPTS;
+ break;
+ }
+
+ OptionPtr opt = pkt.getOption(code);
+ if (!opt) {
+ // If vendor option is not found, return NULL
+ return (opt);
+ }
+
+ // If vendor option is found, try to return its
+ // encapsulated option.
+ return (opt->getOption(option_code_));
+}
+
+TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id,
+ RepresentationType repr)
+ : TokenVendor(u, vendor_id, repr, 0), index_(0) {
+}
+
+TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id,
+ FieldType field, uint16_t index)
+ : TokenVendor(u, vendor_id, TokenOption::HEXADECIMAL, 0), index_(index) {
+ field_ = field;
+}
+
+uint16_t TokenVendorClass::getDataIndex() const {
+ return (index_);
+}
+
+void TokenVendorClass::evaluate(Pkt& pkt, ValueStack& values) {
+ // Get the option first.
+ uint16_t code = 0;
+ switch (universe_) {
+ case Option::V4:
+ code = DHO_VIVCO_SUBOPTIONS;
+ break;
+ case Option::V6:
+ code = D6O_VENDOR_CLASS;
+ break;
+ }
+
+ OptionPtr opt = pkt.getOption(code);
+ OptionVendorClassPtr vendor = boost::dynamic_pointer_cast<OptionVendorClass>(opt);
+ if (!vendor) {
+ // There's no vendor class option, give up.
+ std::string txt = pushFailure(values);
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_NO_OPTION)
+ .arg(code)
+ .arg(txt);
+ return;
+ }
+
+ if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) {
+ // There is vendor option, but it has other vendor-id value
+ // than we're looking for. (0 means accept any vendor-id)
+ std::string txt = pushFailure(values);
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH)
+ .arg(vendor_id_)
+ .arg(vendor->getVendorId())
+ .arg(txt);
+ return;
+ }
+
+ switch (field_) {
+ case ENTERPRISE_ID:
+ {
+ // Extract enterprise-id
+ string txt(sizeof(uint32_t), 0);
+ uint32_t value = htonl(vendor->getVendorId());
+ memcpy(&txt[0], &value, sizeof(uint32_t));
+ values.push(txt);
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID)
+ .arg(vendor->getVendorId())
+ .arg(util::encode::encodeHex(std::vector<uint8_t>(txt.begin(),
+ txt.end())));
+ return;
+ }
+ case SUBOPTION:
+ // Extract sub-options
+ isc_throw(EvalTypeError, "Field None is not valid for vendor-class");
+ return;
+ case EXISTS:
+ // We already passed all the checks: the option is there and has specified
+ // enterprise-id.
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_EXISTS)
+ .arg(vendor->getVendorId())
+ .arg("true");
+ values.push("true");
+ return;
+ case DATA:
+ {
+ size_t max = vendor->getTuplesNum();
+ if (index_ + 1 > max) {
+ // The index specified is out of bounds, e.g. there are only
+ // 2 tuples and index specified is 5.
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND)
+ .arg(index_)
+ .arg(vendor->getVendorId())
+ .arg(max)
+ .arg("");
+ values.push("");
+ return;
+ }
+
+ OpaqueDataTuple tuple = vendor->getTuple(index_);
+ OpaqueDataTuple::Buffer buf = tuple.getData();
+ string txt(buf.begin(), buf.end());
+
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_DATA)
+ .arg(index_)
+ .arg(max)
+ .arg(txt);
+
+ values.push(txt);
+ return;
+ }
+ default:
+ isc_throw(EvalTypeError, "Invalid field specified." << field_);
+ }
+}
+
+TokenInteger::TokenInteger(const uint32_t value)
+ : TokenString(EvalContext::fromUint32(value)), int_value_(value) {
+}
+
+OptionPtr
+TokenSubOption::getSubOption(const OptionPtr& parent) {
+ if (!parent) {
+ return (OptionPtr());
+ }
+ return (parent->getOption(sub_option_code_));
+}
+
+void
+TokenSubOption::evaluate(Pkt& pkt, ValueStack& values) {
+ OptionPtr parent = getOption(pkt);
+ std::string txt;
+ isc::log::MessageID msgid = EVAL_DEBUG_SUB_OPTION;
+ if (!parent) {
+ // There's no parent option, notify that.
+ msgid = EVAL_DEBUG_SUB_OPTION_NO_OPTION;
+ if (representation_type_ == EXISTS) {
+ txt = "false";
+ }
+ } else {
+ OptionPtr sub = getSubOption(parent);
+ if (!sub) {
+ // Failed to find the sub-option
+ if (representation_type_ == EXISTS) {
+ txt = "false";
+ }
+ } else {
+ if (representation_type_ == TEXTUAL) {
+ txt = sub->toString();
+ } else if (representation_type_ == HEXADECIMAL) {
+ std::vector<uint8_t> binary = sub->toBinary();
+ txt.resize(binary.size());
+ if (!binary.empty()) {
+ memmove(&txt[0], &binary[0], binary.size());
+ }
+ } else {
+ txt = "true";
+ }
+ }
+ }
+
+ // Push value of the sub-option or empty string if there was no
+ // such parent option in the packet or sub-option in the parent.
+ values.push(txt);
+
+ // Log what we pushed, both exists and textual are simple text
+ // and can be output directly. We also include the code numbers
+ // of the requested parent option and sub-option.
+ if (representation_type_ == HEXADECIMAL) {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, msgid)
+ .arg(option_code_)
+ .arg(sub_option_code_)
+ .arg(toHex(txt));
+ } else {
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, msgid)
+ .arg(option_code_)
+ .arg(sub_option_code_)
+ .arg('\'' + txt + '\'');
+ }
+}
diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h
new file mode 100644
index 0000000..83833af
--- /dev/null
+++ b/src/lib/eval/token.h
@@ -0,0 +1,1300 @@
+// Copyright (C) 2015-2023 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 TOKEN_H
+#define TOKEN_H
+
+#include <exceptions/exceptions.h>
+#include <dhcp/pkt.h>
+#include <stack>
+
+namespace isc {
+namespace dhcp {
+
+class Token;
+
+/// @brief Pointer to a single Token
+typedef boost::shared_ptr<Token> TokenPtr;
+
+/// This is a structure that holds an expression converted to RPN
+///
+/// For example expression: option[123].text == 'foo' will be converted to:
+/// [0] = option[123].text (TokenOption object)
+/// [1] = 'foo' (TokenString object)
+/// [2] = == operator (TokenEqual object)
+typedef std::vector<TokenPtr> Expression;
+
+typedef boost::shared_ptr<Expression> ExpressionPtr;
+
+/// Evaluated values are stored as a stack of strings
+typedef std::stack<std::string> ValueStack;
+
+/// @brief EvalBadStack is thrown when more or less parameters are on the
+/// stack than expected.
+class EvalBadStack : public Exception {
+public:
+ EvalBadStack(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief EvalTypeError is thrown when a value on the stack has a content
+/// with an unexpected type.
+class EvalTypeError : public Exception {
+public:
+ EvalTypeError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Base class for all tokens
+///
+/// It provides an interface for all tokens and storage for string representation
+/// (all tokens evaluate to string).
+///
+/// This class represents a single token. Examples of a token are:
+/// - "foo" (a constant string)
+/// - option[123].text (a token that extracts textual value of option 123)
+/// - == (an operator that compares two other tokens)
+/// - substring(a,b,c) (an operator that takes three arguments: a string,
+/// first character and length)
+class Token {
+public:
+
+ /// @brief This is a generic method for evaluating a packet.
+ ///
+ /// We need to pass the packet being evaluated and possibly previously
+ /// evaluated values. Specific implementations may ignore the packet altogether
+ /// and just put their own value on the stack (constant tokens), look at the
+ /// packet and put some data extracted from it on the stack (option tokens),
+ /// or pop arguments from the stack and put back the result (operators).
+ ///
+ /// The parameters passed will be:
+ ///
+ /// @param pkt - packet being classified
+ /// @param values - stack of values with previously evaluated tokens
+ virtual void evaluate(Pkt& pkt, ValueStack& values) = 0;
+
+ /// @brief Virtual destructor
+ virtual ~Token() {}
+
+ /// @brief Coverts a (string) value to a boolean
+ ///
+ /// Only "true" and "false" are expected.
+ ///
+ /// @param value the (string) value
+ /// @return the boolean represented by the value
+ /// @throw EvalTypeError when the value is not either "true" or "false".
+ static inline bool toBool(std::string value) {
+ if (value == "true") {
+ return (true);
+ } else if (value == "false") {
+ return (false);
+ } else {
+ isc_throw(EvalTypeError, "Incorrect boolean. Expected exactly "
+ "\"false\" or \"true\", got \"" << value << "\"");
+ }
+ }
+};
+
+/// The order where Token subtypes are declared should be:
+/// - literal terminals
+/// - option & co
+/// - pkt field & co
+/// - ==
+/// - substring & co
+/// - not, and, or
+
+/// @brief Token representing a constant string
+///
+/// This token holds value of a constant string, e.g. it represents
+/// "MSFT" in expression option[vendor-class].text == "MSFT"
+class TokenString : public Token {
+public:
+ /// Value is set during token construction.
+ ///
+ /// @param str constant string to be represented.
+ TokenString(const std::string& str)
+ : value_(str){
+ }
+
+ /// @brief Token evaluation (puts value of the constant string on the stack)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented string will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ std::string value_; ///< Constant value
+};
+
+/// @brief Token representing a constant string in hexadecimal format
+///
+/// This token holds value of a constant string giving in an hexadecimal
+/// format, for instance 0x666f6f is "foo"
+class TokenHexString : public Token {
+public:
+ /// Value is set during token construction.
+ ///
+ /// @param str constant string to be represented
+ /// (must be "0x" or "0X" followed by a string of hexadecimal digits
+ /// or decoding will fail)
+ TokenHexString(const std::string& str);
+
+ /// @brief Token evaluation (puts value of the constant string on
+ /// the stack after decoding or an empty string if decoding fails
+ /// (note it should not if the parser is correct)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented string will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ std::string value_; ///< Constant value
+};
+
+/// @brief Token representing an unsigned 32 bit integer
+///
+/// For performance reasons, the constant integer value is converted to a string
+/// just once (in the constructor). Afterwards, this effectively works as a constant
+/// 4 byte long string. Hence this class is derived from TokenString and
+/// does not even need its own evaluate() method.
+class TokenInteger : public TokenString {
+public:
+ /// @brief Integer value set during construction.
+ ///
+ /// The value is converted to string and stored in value_ provided by the
+ /// base class.
+ ///
+ /// @param value integer value to be stored.
+ TokenInteger(const uint32_t value);
+
+ /// @brief Returns integer value
+ ///
+ /// Used in tests only.
+ ///
+ /// @return integer value
+ uint32_t getInteger() const {
+ return (int_value_);
+ }
+
+protected:
+ uint32_t int_value_; ///< value as integer (stored for testing only)
+};
+
+/// @brief Token representing an IP address as a constant string
+///
+/// This token holds the value of an IP address as a constant string,
+/// for instance 10.0.0.1 is 0x10000001
+class TokenIpAddress : public Token {
+public:
+ /// Value is set during token construction.
+ ///
+ /// @param addr IP address to be represented as a constant string
+ TokenIpAddress(const std::string& addr);
+
+ /// @brief Token evaluation (puts value of the constant string on
+ /// the stack after decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented IP address will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ ///< Constant value (empty string if the IP address cannot be converted)
+ std::string value_;
+};
+
+/// @brief Token representing an IP address as a string
+///
+/// This token holds the value of an IP address as a string, for instance
+/// 10.0.0.1 is '10.0.0.1'
+class TokenIpAddressToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenIpAddressToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented IP address as a string will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing an 8 bit integer as a string
+///
+/// This token holds the value of an 8 bit integer as a string, for instance
+/// 0xff is '-1'
+class TokenInt8ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenInt8ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 8 bit integer as a string will be pushed
+ /// here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 16 bit integer as a string
+///
+/// This token holds the value of a 16 bit integer as a string, for instance
+/// 0xffff is '-1'
+class TokenInt16ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenInt16ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 16 bit integer as a string will be pushed
+ /// here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 32 bit integer as a string
+///
+/// This token holds the value of a 32 bit integer as a string, for instance
+/// 0xffffffff is '-1'
+class TokenInt32ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenInt32ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 32 bit integer as a string will be pushed
+ /// here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing an 8 bit unsigned integer as a string
+///
+/// This token holds the value of an 8 bit unsigned integer as a string, for
+/// instance 0xff is '255'
+class TokenUInt8ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenUInt8ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 8 bit unsigned integer as a string will be
+ /// pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 16 bit unsigned integer as a string
+///
+/// This token holds the value of a 16 bit unsigned integer as a string, for
+/// instance 0xffff is '65535'
+class TokenUInt16ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenUInt16ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 16 bit unsigned integer as a string will be
+ /// pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 32 bit unsigned integer as a string
+///
+/// This token holds the value of a 32 bit unsigned integer as a string, for
+/// instance 0xffffffff is '4294967295'
+class TokenUInt32ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenUInt32ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 32 bit unsigned integer as a string will be
+ /// pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents a value of an option
+///
+/// This represents a reference to a given option, e.g. in the expression
+/// option[vendor-class].text == "MSFT", it represents
+/// option[vendor-class].text
+///
+/// During the evaluation it tries to extract the value of the specified
+/// option. If the option is not found, an empty string ("") is returned
+/// (or "false" when the representation is EXISTS).
+class TokenOption : public Token {
+public:
+
+ /// @brief Token representation type.
+ ///
+ /// There are many possible ways in which option can be presented.
+ /// Currently the textual, hexadecimal and exists representations are
+ /// supported. The type of representation is specified in the
+ /// constructor and it affects the value generated by the
+ /// @c TokenOption::evaluate function.
+ enum RepresentationType {
+ TEXTUAL,
+ HEXADECIMAL,
+ EXISTS
+ };
+
+ /// @brief Constructor that takes an option code as a parameter
+ ///
+ /// Note: There is no constructor that takes option_name, as it would
+ /// introduce complex dependency of the libkea-eval on libdhcpsrv.
+ ///
+ /// @param option_code code of the option to be represented.
+ /// @param rep_type Token representation type.
+ TokenOption(const uint16_t option_code, const RepresentationType& rep_type)
+ : option_code_(option_code), representation_type_(rep_type) {}
+
+ /// @brief Evaluates the values of the option
+ ///
+ /// This token represents a value of the option, so this method attempts
+ /// to extract the option from the packet and put its value on the stack.
+ /// If the option is not there, an empty string ("") is put on the stack.
+ ///
+ /// @param pkt specified option will be extracted from this packet (if present)
+ /// @param values value of the option will be pushed here (or "")
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns option-code
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenOption with correct parameters.
+ ///
+ /// @return option-code of the option this token expects to extract.
+ uint16_t getCode() const {
+ return (option_code_);
+ }
+
+ /// @brief Returns representation-type
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenOption with correct parameters.
+ ///
+ /// @return representation-type of the option this token expects to use.
+ RepresentationType getRepresentation() const {
+ return (representation_type_);
+ }
+
+protected:
+ /// @brief Attempts to retrieve an option
+ ///
+ /// For this class it simply attempts to retrieve the option from the packet,
+ /// but there may be derived classes that would attempt to extract it from
+ /// other places (e.g. relay option, or as a suboption of other specific option).
+ ///
+ ///
+ /// @param pkt the option will be retrieved from here
+ /// @return option instance (or NULL if not found)
+ virtual OptionPtr getOption(Pkt& pkt);
+
+ /// @brief Auxiliary method that puts string representing a failure
+ ///
+ /// Depending on the representation type, this is either "" or "false".
+ ///
+ /// @param values a string representing failure will be pushed here.
+ /// @return value pushed
+ virtual std::string pushFailure(ValueStack& values);
+
+ uint16_t option_code_; ///< Code of the option to be extracted
+ RepresentationType representation_type_; ///< Representation type.
+};
+
+/// @brief Represents a sub-option inserted by the DHCPv4 relay.
+///
+/// DHCPv4 relays insert sub-options in option 82. This token attempts to extract
+/// such sub-options. Note in DHCPv6 it is radically different (possibly
+/// many encapsulation levels), thus there are separate classes for v4 and v6.
+///
+/// This token can represent the following expressions:
+/// relay[13].text - Textual representation of sub-option 13 in RAI (option 82)
+/// relay[13].hex - Binary representation of sub-option 13 in RAI (option 82)
+/// relay[vendor-class].text - Text representation of sub-option X in RAI (option 82)
+/// relay[vendor-class].hex - Binary representation of sub-option X in RAI (option 82)
+class TokenRelay4Option : public TokenOption {
+public:
+
+ /// @brief Constructor for extracting sub-option from RAI (option 82)
+ ///
+ /// @param option_code code of the requested sub-option
+ /// @param rep_type code representation (currently .hex and .text are supported)
+ TokenRelay4Option(const uint16_t option_code,
+ const RepresentationType& rep_type);
+
+protected:
+ /// @brief Attempts to obtain specified sub-option of option 82 from the packet
+ /// @param pkt DHCPv4 packet (that hopefully contains option 82)
+ /// @return found sub-option from option 82
+ virtual OptionPtr getOption(Pkt& pkt);
+};
+
+/// @brief Token that represents a value of an option within a DHCPv6 relay
+/// encapsulation
+///
+/// This represents a reference to a given option similar to TokenOption
+/// but from within the information from a relay. In the expression
+/// relay6[nest-level].option[option-code], nest-level indicates which
+/// of the relays to examine and option-code which option to extract.
+///
+/// During the evaluation it tries to extract the value of the specified
+/// option from the requested relay block. If the relay block doesn't
+/// exist or the option is not found an empty string ("") is returned
+/// (or "false" when the representation is EXISTS).
+///
+/// The nesting level can go from 0 (closest to the server) to 31,
+/// or from -1 (closest to the client) to -32
+class TokenRelay6Option : public TokenOption {
+public:
+ /// @brief Constructor that takes a nesting level and an option
+ /// code as parameters.
+ ///
+ /// @param nest_level the nesting for which relay to examine.
+ /// @param option_code code of the option.
+ /// @param rep_type Token representation type.
+ TokenRelay6Option(const int8_t nest_level, const uint16_t option_code,
+ const RepresentationType& rep_type)
+ : TokenOption(option_code, rep_type), nest_level_(nest_level) {}
+
+ /// @brief Returns nest-level
+ ///
+ /// This method is used in testing to determine if the parser has
+ /// instantiated TokenRelay6Option with correct parameters.
+ ///
+ /// @return nest-level of the relay block this token expects to use
+ /// for extraction.
+ int8_t getNest() const {
+ return (nest_level_);
+ }
+
+protected:
+ /// @brief Attempts to obtain specified option from the specified relay block
+ /// @param pkt DHCPv6 packet that hopefully contains the proper relay block
+ /// @return option instance if available
+ virtual OptionPtr getOption(Pkt& pkt);
+
+ int8_t nest_level_; ///< nesting level of the relay block to use
+};
+
+/// @brief Token that represents meta data of a DHCP packet.
+///
+/// For example in the expression pkt.iface == 'eth0'
+/// this token represents the pkt.iface expression.
+///
+/// Currently supported meta datas are:
+/// - iface (incoming/outgoinginterface name)
+/// - src (source IP address, 4 or 16 octets)
+/// - dst (destination IP address, 4 or 16 octets)
+/// - len (length field in the UDP header, padded to 4 octets)
+class TokenPkt : public Token {
+public:
+
+ /// @brief enum value that determines the field.
+ enum MetadataType {
+ IFACE, ///< interface name (string)
+ SRC, ///< source (IP address)
+ DST, ///< destination (IP address)
+ LEN ///< length (4 octets)
+ };
+
+ /// @brief Constructor (does nothing)
+ TokenPkt(const MetadataType type)
+ : type_(type) {}
+
+ /// @brief Gets a value from the specified packet.
+ ///
+ /// Evaluation uses metadata available in the packet. It does not
+ /// require any values to be present on the stack.
+ ///
+ /// @param pkt - metadata will be extracted from here
+ /// @param values - stack of values (1 result will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns metadata type
+ ///
+ /// This method is used only in tests.
+ /// @return type of the metadata.
+ MetadataType getType() {
+ return (type_);
+ }
+
+private:
+ /// @brief Specifies metadata of the DHCP packet
+ MetadataType type_;
+};
+
+/// @brief Token that represents fields of a DHCPv4 packet.
+///
+/// For example in the expression pkt4.chaddr == 0x0102030405
+/// this token represents the pkt4.chaddr expression.
+///
+/// Currently supported fields are:
+/// - chaddr (client hardware address, hlen [0..16] octets)
+/// - giaddr (relay agent IP address, 4 octets)
+/// - ciaddr (client IP address, 4 octets)
+/// - yiaddr ('your' (client) IP address, 4 octets)
+/// - siaddr (next server IP address, 4 octets)
+/// - hlen (hardware address length, padded to 4 octets)
+/// - htype (hardware address type, padded to 4 octets)
+class TokenPkt4 : public Token {
+public:
+
+ /// @brief enum value that determines the field.
+ enum FieldType {
+ CHADDR, ///< chaddr field (up to 16 bytes link-layer address)
+ GIADDR, ///< giaddr (IPv4 address)
+ CIADDR, ///< ciaddr (IPv4 address)
+ YIADDR, ///< yiaddr (IPv4 address)
+ SIADDR, ///< siaddr (IPv4 address)
+ HLEN, ///< hlen (hardware address length)
+ HTYPE, ///< htype (hardware address type)
+ MSGTYPE, ///< message type (not really a field, content of option 53)
+ TRANSID, ///< transaction-id (xid)
+ };
+
+ /// @brief Constructor (does nothing)
+ TokenPkt4(const FieldType type)
+ : type_(type) {}
+
+ /// @brief Gets a value from the specified packet.
+ ///
+ /// Evaluation uses fields available in the packet. It does not require
+ /// any values to be present on the stack.
+ ///
+ /// @throw EvalTypeError when called for DHCPv6 packet
+ ///
+ /// @param pkt - fields will be extracted from here
+ /// @param values - stack of values (1 result will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns field type
+ ///
+ /// This method is used only in tests.
+ /// @return type of the field.
+ FieldType getType() {
+ return (type_);
+ }
+
+private:
+ /// @brief Specifies field of the DHCPv4 packet
+ FieldType type_;
+};
+
+/// @brief Token that represents fields of DHCPv6 packet.
+///
+/// For example in the expression pkt6.msgtype == 1
+/// this token represents the message type of the DHCPv6 packet.
+/// The integer values are placed on the value stack as 4 byte
+/// strings.
+///
+/// Currently supported fields are:
+/// - msgtype
+/// - transid
+class TokenPkt6 : public Token {
+public:
+ /// @brief enum value that determines the field.
+ enum FieldType {
+ MSGTYPE, ///< msg type
+ TRANSID ///< transaction id (integer but manipulated as a string)
+ };
+
+ /// @brief Constructor (does nothing)
+ TokenPkt6(const FieldType type)
+ : type_(type) {}
+
+ /// @brief Gets a value of the specified packet.
+ ///
+ /// The evaluation uses fields that are available in the packet. It does not
+ /// require any values to be present on the stack.
+ ///
+ /// @throw EvalTypeError when called for a DHCPv4 packet
+ ///
+ /// @param pkt - packet from which to extract the fields
+ /// @param values - stack of values, 1 result will be pushed
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns field type
+ ///
+ /// This method is used only in tests.
+ /// @return type of the field.
+ FieldType getType() {
+ return (type_);
+ }
+
+private:
+ /// @brief Specifies field of the DHCPv6 packet to get
+ FieldType type_;
+};
+
+/// @brief Token that represents a value of a field within a DHCPv6 relay
+/// encapsulation
+///
+/// This represents a reference to a field with a given DHCPv6 relay encapsulation.
+/// In the expression relay6[nest-level].field-name, nest-level indicates which of
+/// the relays to examine and field-name which of the fields to extract.
+///
+/// During the evaluation it tries to extract the value of the specified
+/// field from the requested relay block. If the relay block doesn't exist
+/// an empty string ("") is returned. If the relay block does exist the field
+/// is always returned as a 16 byte IPv6 address. As the relay may not have
+/// set the field it may be 0s.
+///
+/// The nesting level can go from 0 (closest to the server) to 31,
+/// or from -1 (closest to the client) to -32
+class TokenRelay6Field : public Token {
+public:
+
+ /// @brief enum value that determines the field.
+ enum FieldType {
+ PEERADDR, ///< Peer address field (IPv6 address)
+ LINKADDR ///< Link address field (IPv6 address)
+ };
+
+ /// @brief Constructor that takes a nesting level and field type
+ /// as parameters.
+ ///
+ /// @param nest_level the nesting level for which relay to examine.
+ /// @param type which field to extract.
+ TokenRelay6Field(const int8_t nest_level, const FieldType type)
+ : nest_level_(nest_level), type_(type) {}
+
+ /// @brief Extracts the specified field from the requested relay
+ ///
+ /// Evaluation uses fields available in the packet. It does not require
+ /// any values to be present on the stack.
+ ///
+ /// @param pkt fields will be extracted from here
+ /// @param values - stack of values (1 result will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns nest-level
+ ///
+ /// This method is used in testing to determine if the parser has
+ /// instantiated TokenRelay6Field with correct parameters.
+ ///
+ /// @return nest-level of the relay block this token expects to use
+ /// for extraction.
+ int8_t getNest() const {
+ return (nest_level_);
+ }
+
+ /// @brief Returns field type
+ ///
+ /// This method is used only in testing to determine if the parser has
+ /// instantiated TokenRelay6Field with correct parameters.
+ ///
+ /// @return type of the field.
+ FieldType getType() {
+ return (type_);
+ }
+
+protected:
+ /// @brief Specifies field of the DHCPv6 relay option to get
+ int8_t nest_level_; ///< nesting level of the relay block to use
+ FieldType type_; ///< field to get
+};
+
+/// @brief Token that represents equality operator (compares two other tokens)
+///
+/// For example in the expression option[vendor-class].text == "MSFT"
+/// this token represents the equal (==) sign.
+class TokenEqual : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenEqual() {}
+
+ /// @brief Compare two values.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It does a simple string comparison and sets the value to
+ /// either "true" or "false". It requires at least two parameters to be
+ /// present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents the substring operator (returns a portion
+/// of the supplied string)
+///
+/// This token represents substring(str, start, len) An operator that takes three
+/// arguments: a string, the first character and the length.
+class TokenSubstring : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenSubstring() {}
+
+ /// @brief Extract a substring from a string
+ ///
+ /// Evaluation does not use packet information. It requires at least
+ /// three values to be present on the stack. It will consume the top
+ /// three values on the stack as parameters and push the resulting substring
+ /// onto the stack. From the top it expects the values on the stack as:
+ /// - len
+ /// - start
+ /// - str
+ ///
+ /// str is the string to extract a substring from. If it is empty, an empty
+ /// string is pushed onto the value stack.
+ ///
+ /// start is the position from which the code starts extracting the substring.
+ /// 0 is the first character and a negative number starts from the end, with
+ /// -1 being the last character. If the starting point is outside of the
+ /// original string an empty string is pushed onto the value stack.
+ ///
+ /// length is the number of characters from the string to extract.
+ /// "all" means all remaining characters from start to the end of string.
+ /// A negative number means to go from start towards the beginning of
+ /// the string, but doesn't include start.
+ /// If length is longer than the remaining portion of string
+ /// then the entire remaining portion is placed on the value stack.
+ ///
+ /// The following examples all use the base string "foobar", the first number
+ /// is the starting position and the second is the length. Note that
+ /// a negative length only selects which characters to extract it does not
+ /// indicate an attempt to reverse the string.
+ /// - 0, all => "foobar"
+ /// - 0, 6 => "foobar"
+ /// - 0, 4 => "foob"
+ /// - 2, all => "obar"
+ /// - 2, 6 => "obar"
+ /// - -1, all => "r"
+ /// - -1, -4 => "ooba"
+ ///
+ /// @throw EvalBadStack if there are less than 3 values on stack
+ /// @throw EvalTypeError if start is not a number or length a number or
+ /// the special value "all".
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (3 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+class TokenSplit : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenSplit() {}
+
+ /// @brief Extract a field from a delimited string
+ ///
+ /// Evaluation does not use packet information. It requires at least
+ /// three values to be present on the stack. It will consume the top
+ /// three values on the stack as parameters and push the resulting substring
+ /// onto the stack. From the top it expects the values on the stack as:
+ /// - field
+ /// - delims
+ /// - str
+ ///
+ /// str is the string to split. If it is empty, an empty
+ /// string is pushed onto the value stack.
+ /// delims is string of character delimiters by which to split str. If it is
+ /// empty the entire value of str will be pushed on onto the value stack.
+ /// field is the field number (starting at 1) of the desired field. If it is
+ /// out of range an empty string is pushed on the value stack.
+ ///
+ /// The following examples all use the base string "one.two..four" and shows
+ /// the value returned for a given field:
+ /// ```
+ /// field => value
+ /// --------------
+ /// - 0 => ""
+ /// - 1 => "one"
+ /// - 2 => "two"
+ /// - 3 => ""
+ /// - 4 => "four"
+ /// - 5 => ""
+ /// ```
+ ///
+ /// @throw EvalBadStack if there are less than 3 values on stack
+ /// @throw EvalTypeError if field is not a number
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (3 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents concat operator (concatenates two other tokens)
+///
+/// For example in the sub-expression "concat('foo','bar')" the result
+/// of the evaluation is "foobar"
+/// For user convenience the "'foo' + 'bar'" alternative does the same.
+class TokenConcat : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenConcat() {}
+
+ /// @brief Concatenate two values.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It does a simple string concatenation. It requires
+ /// at least two parameters to be present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents an alternative
+///
+/// For example in the sub-expression "ifelse(cond, iftrue, iffalse)"
+/// the boolean "cond" expression is evaluated, if it is true then
+/// the "iftrue" value is returned else the "iffalse" value is returned.
+/// Please note that "iftrue" and "iffalse" must be plain string (vs. boolean)
+/// expressions and they are always evaluated. If you want a similar
+/// operator on boolean expressions it can be built from "and", "or" and
+/// "not" boolean operators.
+class TokenIfElse : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenIfElse() { }
+
+ /// @brief Alternative.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the
+ /// last three results. It does a simple string comparison on the
+ /// condition (third value on the stack) which is required to be
+ /// either "true" or "false", and leaves the second and first
+ /// value if the condition is "true" or "false".
+ ///
+ /// @throw EvalBadStack if there are less than 3 values on stack
+ /// @throw EvalTypeError if the third value (the condition) is not
+ /// either "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (two items are removed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that converts to hexadecimal string
+///
+/// For example in the sub-expression "hexstring(pkt4.mac, ':')"
+/// the binary MAC address is converted to its usual hexadecimal
+/// representation as a list of (6) pairs of hexadecimal digits
+/// separated by colons (':').
+/// Please note the token is named TokenToHexString when the syntax
+/// use the hexstring name without a leading "to".
+class TokenToHexString : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenToHexString() { }
+
+ /// @brief Convert a binary value to its hexadecimal string representation
+ ///
+ /// Evaluation does not use packet information. It requires at least
+ /// two values to be present on the stack. It will consume the top
+ /// two values on the stack as parameters and push the resulting
+ /// hexadecimal string onto the stack.
+ /// From the top it expects the values on the stack as:
+ /// - separator
+ /// - binary
+ ///
+ /// binary is the binary value (note it can be any value, i.e.
+ /// it is not checked to really be not printable).
+ /// separator is literal for instance '-' or ':'. The empty separator
+ /// means no separator.
+ ///
+ /// The following example use a binary MAC address 06:ce:8f:55:b3:33:
+ /// - mac, '-' => "06-ce-8f-55-b3-33"
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents logical negation operator
+///
+/// For example in the expression "not(option[vendor-class].text == 'MSF')"
+/// this token represents the leading "not"
+class TokenNot : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenNot() {}
+
+ /// @brief Logical negation.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// result. It does a simple string comparison and sets the value to
+ /// either "true" or "false". It requires at least one value to be
+ /// present on stack and to be either "true" or "false".
+ ///
+ /// @throw EvalBadStack if there are less than 1 value on stack
+ /// @throw EvalTypeError if the top value on the stack is not either
+ /// "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (logical top value negated)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents logical and operator
+///
+/// For example "option[10].exists and option[11].exists"
+class TokenAnd : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenAnd() {}
+
+ /// @brief Logical and.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It returns "true" if and only if both are "true".
+ /// It requires at least two logical (i.e., "true" or "false') values
+ /// present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ /// @throw EvalTypeError if one of the 2 values on stack is not
+ /// "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents logical or operator
+///
+/// For example "option[10].exists or option[11].exists"
+class TokenOr : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenOr() {}
+
+ /// @brief Logical or.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It returns "false" if and only if both are "false".
+ /// It requires at least two logical (i.e., "true" or "false') values
+ /// present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ /// @throw EvalTypeError if one of the 2 values on stack is not
+ /// "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents client class membership
+///
+/// For example "not member('foo')" is the complement of class foo
+class TokenMember : public Token {
+public:
+ /// @brief Constructor
+ ///
+ /// @param client_class client class name
+ TokenMember(const std::string& client_class)
+ : client_class_(client_class){
+ }
+
+ /// @brief Token evaluation (check if client_class_ was added to
+ /// packet client classes)
+ ///
+ /// @param pkt the class name will be check from this packet's client classes
+ /// @param values true (if found) or false (if not found) will be pushed here
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns client class name
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenMember with correct parameters.
+ ///
+ /// @return client class name the token expects to check membership.
+ const ClientClass& getClientClass() const {
+ return (client_class_);
+ }
+
+protected:
+ /// @brief The client class name
+ ClientClass client_class_;
+};
+
+/// @brief Token that represents vendor options in DHCPv4 and DHCPv6.
+///
+/// It covers vendor independent vendor information option (125, DHCPv4)
+/// and vendor option (17, DHCPv6). Since both of those options may have
+/// suboptions, this class is derived from TokenOption and leverages its
+/// ability to operate on sub-options. It also adds additional capabilities.
+/// In particular, it allows retrieving enterprise-id.
+///
+/// It can represent the following expressions:
+/// vendor[4491].exists - if vendor option with enterprise-id = 4491 exists
+/// vendor[*].exists - if any vendor option exists
+/// vendor.enterprise - returns enterprise-id from vendor option
+/// vendor[4491].option[1].exists - check if suboption 1 exists for vendor 4491
+/// vendor[4491].option[1].hex - return content of suboption 1 for vendor 4491
+class TokenVendor : public TokenOption {
+public:
+
+ /// @brief Specifies a field of the vendor option
+ enum FieldType {
+ SUBOPTION, ///< If this token fetches a suboption, not a field.
+ ENTERPRISE_ID, ///< enterprise-id field (vendor-info, vendor-class)
+ EXISTS, ///< vendor[123].exists
+ DATA ///< data chunk, used in derived vendor-class only
+ };
+
+ /// @brief Constructor used for accessing a field
+ ///
+ /// @param u universe (either V4 or V6)
+ /// @param vendor_id specifies enterprise-id (0 means any)
+ /// @param field specifies which field should be returned
+ TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field);
+
+
+ /// @brief Constructor used for accessing an option
+ ///
+ /// This constructor is used for accessing suboptions. In general
+ /// option_code is mandatory, except when repr is EXISTS. For
+ /// option_code = 0 and repr = EXISTS, the token will return true
+ /// if the whole option exists, not suboptions.
+ ///
+ /// @param u universe (either V4 or V6)
+ /// @param vendor_id specifies enterprise-id (0 means any)
+ /// @param repr representation type (hex or exists)
+ /// @param option_code sub-option code
+ TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr,
+ uint16_t option_code = 0);
+
+ /// @brief Returns enterprise-id
+ ///
+ /// Used in tests only.
+ ///
+ /// @return enterprise-id
+ uint32_t getVendorId() const;
+
+ /// @brief Returns field.
+ ///
+ /// Used in tests only.
+ ///
+ /// @return field type.
+ FieldType getField() const;
+
+ /// @brief This is a method for evaluating a packet.
+ ///
+ /// Depending on the value of vendor_id, field type, representation and
+ /// option code, it will attempt to return specified characteristic of the
+ /// vendor option
+ ///
+ /// If vendor-id is specified, check only option with that particular
+ /// enterprise-id. If vendor-id is 0, check any vendor option, regardless
+ /// of its enterprise-id value.
+ ///
+ /// If FieldType is NONE, get specified suboption represented by option_code
+ /// and represent it as specified by repr.
+ ///
+ /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field
+ /// or "" if there's no vendor option.
+ ///
+ /// @throw EvalTypeError for any other FieldType values.
+ ///
+ /// The parameters passed are:
+ ///
+ /// @param pkt - vendor options will be searched for here.
+ /// @param values - the evaluated value will be pushed here.
+ virtual void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ /// @brief Attempts to get a suboption.
+ ///
+ /// This method overrides behavior of TokenOption method. It attempts to retrieve
+ /// the sub-option of the vendor option. Using derived method allows usage of
+ /// TokenOption routines.
+ ///
+ /// @param pkt vendor option will be searched here.
+ /// @return suboption of the vendor option (if exists)
+ virtual OptionPtr getOption(Pkt& pkt);
+
+ /// @brief Universe (V4 or V6)
+ ///
+ /// We need to remember it, because depending on the universe, the code needs
+ /// to retrieve either option 125 (DHCPv4) or 17 (DHCPv6).
+ Option::Universe universe_;
+
+ /// @brief Enterprise-id value
+ ///
+ /// Yeah, I know it technically should be called enterprise-id, but that's
+ /// too long and everyone calls it vendor-id.
+ uint32_t vendor_id_;
+
+ /// @brief Specifies which field should be accessed.
+ FieldType field_;
+};
+
+/// @brief Token that represents vendor class options in DHCPv4 and DHCPv6.
+///
+/// It covers vendor independent vendor information option (124, DHCPv4)
+/// and vendor option (16, DHCPv6). Contrary to vendor options, vendor class
+/// options don't have suboptions, but have data chunks (tuples) instead.
+/// Therefore they're not referenced by option codes, but by indexes.
+/// The first data chunk is data[0], the second is data[1] etc.
+///
+/// This class is derived from OptionVendor to take advantage of the
+/// enterprise handling field and field type.
+///
+/// It can represent the following expressions:
+/// vendor-class[4491].exists
+/// vendor-class[*].exists
+/// vendor-class[*].enterprise
+/// vendor-class[4491].data - content of the opaque-data of the first tuple
+/// vendor-class[4491].data[3] - content of the opaque-data of the 4th tuple
+class TokenVendorClass : public TokenVendor {
+public:
+
+ /// @brief This constructor is used to access fields.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id value of enterprise-id field (0 means any)
+ /// @param repr representation type (EXISTS or HEX)
+ TokenVendorClass(Option::Universe u, uint32_t vendor_id, RepresentationType repr);
+
+ /// @brief This constructor is used to access data chunks.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id value of enterprise-id field (0 means any)
+ /// @param field type of the field (usually DATA or ENTERPRISE)
+ /// @param index specifies which data chunk to retrieve
+ TokenVendorClass(Option::Universe u, uint32_t vendor_id, FieldType field,
+ uint16_t index = 0);
+
+ /// @brief Returns data index.
+ ///
+ /// Used in testing.
+ /// @return data index (specifies which data chunk to retrieve)
+ uint16_t getDataIndex() const;
+
+protected:
+
+ /// @brief This is a method for evaluating a packet.
+ ///
+ /// Depending on the value of vendor_id, field type, representation and
+ /// option code, it will attempt to return specified characteristic of the
+ /// vendor option
+ ///
+ /// If vendor-id is specified, check only option with that particular
+ /// enterprise-id. If vendor-id is 0, check any vendor option, regardless
+ /// of its enterprise-id value.
+ ///
+ /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field
+ /// or "" if there's no vendor option.
+ ///
+ /// If FieldType is DATA, get specified data chunk represented by index_.
+ ///
+ /// If FieldType is EXISTS, return true if vendor-id matches.
+ ///
+ /// @throw EvalTypeError for any other FieldType values.
+ ///
+ /// The parameters passed are:
+ ///
+ /// @param pkt - vendor options will be searched for here.
+ /// @param values - the evaluated value will be pushed here.
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Data chunk index.
+ uint16_t index_;
+};
+
+/// @brief Token that represents sub-options in DHCPv4 and DHCPv6.
+///
+/// It covers any options which encapsulate sub-options, for instance
+/// dhcp-agent-options (82, DHCPv4) or rsoo (66, DHCPv6).
+/// This class is derived from TokenOption and leverages its ability
+/// to operate on sub-options. It also adds additional capabilities.
+///
+/// Note: @c TokenSubOption virtually derives @c TokenOption because both
+/// classes are inherited together in more complex classes in other parts of
+/// the code. This makes the base class @c TokenOption to exist only once in
+/// such complex classes.
+///
+/// It can represent the following expressions:
+/// option[149].exists - check if option 149 exists
+/// option[149].option[1].exists - check if suboption 1 exists in the option 149
+/// option[149].option[1].hex - return content of suboption 1 for option 149
+class TokenSubOption : public virtual TokenOption {
+public:
+
+ /// @note Does not define its own representation type:
+ /// simply use the @c TokenOption::RepresentationType
+
+ /// @brief Constructor that takes an option and sub-option codes as parameter
+ ///
+ /// Note: There is no constructor that takes names.
+ ///
+ /// @param option_code code of the parent option.
+ /// @param sub_option_code code of the sub-option to be represented.
+ /// @param rep_type Token representation type.
+ TokenSubOption(const uint16_t option_code,
+ const uint16_t sub_option_code,
+ const RepresentationType& rep_type)
+ : TokenOption(option_code, rep_type), sub_option_code_(sub_option_code) {}
+
+ /// @brief This is a method for evaluating a packet.
+ ///
+ /// This token represents a value of the sub-option, so this method
+ /// attempts to extract the parent option from the packet and when
+ /// it succeeds to extract the sub-option from the option and
+ /// its value on the stack.
+ /// If the parent option or the sub-option is not there, an empty
+ /// string ("") is put on the stack.
+ ///
+ /// @param pkt specified parent option will be extracted from this packet
+ /// @param values value of the sub-option will be pushed here (or "")
+ virtual void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns sub-option-code
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenSubOption with correct parameters.
+ ///
+ /// @return option-code of the sub-option this token expects to extract.
+ uint16_t getSubCode() const {
+ return (sub_option_code_);
+ }
+
+protected:
+ /// @brief Attempts to retrieve a sub-option.
+ ///
+ /// @param parent the sub-option will be retrieved from here
+ /// @return sub-option instance (or NULL if not found)
+ virtual OptionPtr getSubOption(const OptionPtr& parent);
+
+ uint16_t sub_option_code_; ///< Code of the sub-option to be extracted
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif
diff --git a/src/lib/exceptions/Makefile.am b/src/lib/exceptions/Makefile.am
new file mode 100644
index 0000000..a6e7b90
--- /dev/null
+++ b/src/lib/exceptions/Makefile.am
@@ -0,0 +1,16 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS=$(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-exceptions.la
+
+libkea_exceptions_la_SOURCES = exceptions.h exceptions.cc
+libkea_exceptions_la_SOURCES += isc_assert.h
+libkea_exceptions_la_LDFLAGS = -no-undefined -version-info 23:0:0
+
+CLEANFILES = *.gcno *.gcda
+
+libkea_exceptions_includedir = $(pkgincludedir)/exceptions
+libkea_exceptions_include_HEADERS = exceptions.h
+libkea_exceptions_include_HEADERS += isc_assert.h
diff --git a/src/lib/exceptions/Makefile.in b/src/lib/exceptions/Makefile.in
new file mode 100644
index 0000000..ac1ee43
--- /dev/null
+++ b/src/lib/exceptions/Makefile.in
@@ -0,0 +1,932 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/exceptions
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 \
+ $(libkea_exceptions_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)$(libkea_exceptions_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libkea_exceptions_la_LIBADD =
+am_libkea_exceptions_la_OBJECTS = exceptions.lo
+libkea_exceptions_la_OBJECTS = $(am_libkea_exceptions_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_exceptions_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_exceptions_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)/exceptions.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_exceptions_la_SOURCES)
+DIST_SOURCES = $(libkea_exceptions_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 = $(libkea_exceptions_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-exceptions.la
+libkea_exceptions_la_SOURCES = exceptions.h exceptions.cc isc_assert.h
+libkea_exceptions_la_LDFLAGS = -no-undefined -version-info 23:0:0
+CLEANFILES = *.gcno *.gcda
+libkea_exceptions_includedir = $(pkgincludedir)/exceptions
+libkea_exceptions_include_HEADERS = exceptions.h isc_assert.h
+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/exceptions/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/exceptions/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-exceptions.la: $(libkea_exceptions_la_OBJECTS) $(libkea_exceptions_la_DEPENDENCIES) $(EXTRA_libkea_exceptions_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_exceptions_la_LINK) -rpath $(libdir) $(libkea_exceptions_la_OBJECTS) $(libkea_exceptions_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exceptions.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_exceptions_includeHEADERS: $(libkea_exceptions_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_exceptions_include_HEADERS)'; test -n "$(libkea_exceptions_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_exceptions_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_exceptions_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)$(libkea_exceptions_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_exceptions_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_exceptions_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_exceptions_include_HEADERS)'; test -n "$(libkea_exceptions_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_exceptions_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_exceptions_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/exceptions.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-libkea_exceptions_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)/exceptions.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-libkea_exceptions_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_exceptions_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-libkea_exceptions_includeHEADERS
+
+.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/exceptions/exceptions.cc b/src/lib/exceptions/exceptions.cc
new file mode 100644
index 0000000..bc0d83a
--- /dev/null
+++ b/src/lib/exceptions/exceptions.cc
@@ -0,0 +1,55 @@
+// 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 <string>
+#include <sstream>
+#include <exceptions/exceptions.h>
+
+using isc::Exception;
+
+namespace isc {
+
+Exception::Exception(const char* file, size_t line, const char* what)
+: file_(file), line_(line), what_(what) {
+ std::stringstream location;
+ location << what_ << "[" << file_ << ":" << line_ << "]";
+ verbose_what_ = location.str();
+}
+
+Exception::Exception(const char* file, size_t line, const std::string& what)
+ : file_(file), line_(line), what_(what) {
+ std::stringstream location;
+ location << what_ << "[" << file_ << ":" << line_ << "]";
+ verbose_what_ = location.str();
+}
+
+const char*
+Exception::what() const throw() {
+ return (what(false));
+}
+
+const char*
+Exception::what(bool verbose) const throw() {
+
+ // Even though it's very unlikely that c_str() throws an exception,
+ // it's still not 100% guaranteed. To meet the exception specification
+ // of this function, we catch any unexpected exception and fall back to
+ // the pre-defined constant.
+ try {
+ if (verbose) {
+ return (verbose_what_.c_str());
+ } else {
+ return (what_.c_str());
+ }
+ } catch (...) {
+ // no exception handling is necessary. just have to catch exceptions.
+ }
+ return ("isc::Exception");
+}
+
+}
diff --git a/src/lib/exceptions/exceptions.h b/src/lib/exceptions/exceptions.h
new file mode 100644
index 0000000..0f89159
--- /dev/null
+++ b/src/lib/exceptions/exceptions.h
@@ -0,0 +1,264 @@
+// Copyright (C) 2009-2021 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 EXCEPTIONS_H
+#define EXCEPTIONS_H 1
+
+#include <stdexcept>
+#include <string>
+#include <sstream>
+
+namespace isc {
+
+///
+/// This is a base class for exceptions thrown from the DNS library module.
+/// Normally, the exceptions are thrown via a convenient shortcut macro,
+/// @ref isc_throw, which automatically gives trivial parameters for the
+/// exception such as the file name and line number where the exception is
+/// triggered.
+///
+class Exception : public std::exception {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+ /// \brief Constructor for a given type for exceptions with file name and
+ /// file line number.
+ ///
+ /// @param file the file name where the exception was thrown.
+ /// @param line the line in \a file where the exception was thrown.
+ /// @param what a description (type) of the exception.
+ Exception(const char* file, size_t line, const char* what);
+
+ /// \brief Constructor for a given type for exceptions with file name and
+ /// file line number.
+ ///
+ /// @param file the file name where the exception was thrown.
+ /// @param line the line in \a file where the exception was thrown.
+ /// @param what a description (type) of the exception.
+ Exception(const char* file, size_t line, const std::string& what);
+
+ /// The destructor
+ virtual ~Exception() throw() {}
+ //@}
+private:
+ ///
+ /// The assignment operator is intentionally disabled.
+ ///
+ void operator=(const Exception& src);
+
+public:
+ ///
+ /// \name Methods Reimplemented against the Standard Exception Class
+ ///
+ //@{
+ /// \brief Returns a C-style character string of the cause of the exception.
+ ///
+ /// Note: we normally don't use exception specifications, but this is an
+ /// "exception" to that policy as it's enforced by the base class.
+ ///
+ /// @return A C-style character string of the exception cause.
+ virtual const char* what() const throw();
+
+ /// \brief Returns a C-style character string of the cause of exception.
+ ///
+ /// With verbose set to true, also returns file name and line numbers.
+ /// Note that we can't simply define a single what() method with parameters,
+ /// as the compiler would complain that it shadows the base class method.
+ ///
+ /// \param verbose if set to true, filename and line number will be added.
+ /// \return A C-style character string of the exception cause.
+ virtual const char* what(bool verbose) const throw();
+ //@}
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Gets a string describing the cause of the exception.
+ ///
+ /// @return the cause string.
+ const std::string& getMessage() const { return (what_); }
+
+ /// \brief Gets the file name where the exception was thrown.
+ ///
+ /// @return a C-style string of the file name.
+ const char* getFile() const { return (file_); }
+
+ /// \brief Gets the line number of the file where the exception was thrown.
+ ///
+ /// @return an integer specifying the line number.
+ size_t getLine() const { return (line_); }
+ //@}
+
+private:
+
+ /// Specifies the filename where this exception was raised
+ const char* const file_;
+
+ /// Specifies the line number where this exception was raised
+ size_t line_;
+
+ /// Specifies actual content of the exception
+ const std::string what_;
+
+ /// Specifies actual context of the exception (with file:line added)
+ std::string verbose_what_;
+};
+
+/// \brief A generic exception that is thrown if a parameter given
+/// to a method would refer to or modify out-of-range data.
+class OutOfRange : public Exception {
+public:
+ OutOfRange(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief A generic exception that is thrown if a parameter given
+/// to a method or function is considered invalid and no other specific
+/// exceptions are suitable to describe the error.
+class InvalidParameter : public Exception {
+public:
+ InvalidParameter(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief A generic exception that is thrown if a parameter given
+/// to a method is considered invalid in that context.
+class BadValue : public Exception {
+public:
+ BadValue(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief A generic exception that is thrown if a function is called
+/// in a prohibited way.
+///
+/// For example, this can happen if a class method is called when the object's
+/// state does not allow that particular method.
+class InvalidOperation : public Exception {
+public:
+ InvalidOperation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
+/// \brief A generic exception that is thrown when an unexpected
+/// error condition occurs.
+///
+class Unexpected : public Exception {
+public:
+ Unexpected(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
+/// \brief A generic exception that is thrown when a function is
+/// not implemented.
+///
+/// This may be due to unfinished implementation or in case the
+/// function isn't even planned to be provided for that situation,
+/// i.e. not yet implemented or not supported.
+class NotImplemented : public Exception {
+public:
+ NotImplemented(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///
+/// \brief A generic exception that is thrown when an object can
+/// not be found.
+class NotFound : public Exception {
+public:
+ NotFound(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief Exception thrown when a worker thread is trying to stop or pause the
+/// respective thread pool (which would result in a dead-lock).
+class MultiThreadingInvalidOperation : public Exception {
+public:
+ MultiThreadingInvalidOperation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {};
+};
+
+///
+/// A shortcut macro to insert known values into exception arguments.
+///
+/// It allows the \c stream argument to be part of a statement using an
+/// \c ostream object and its \c operator<<. For example,
+/// \code int x = 10;
+/// isc_throw(SomeException, "Error happened, parameter: " << x);
+/// \endcode
+/// will throw an exception of class \c SomeException whose \c what string
+/// will be <code>"Error happened, parameter: 10"</code>.
+///
+/// Note: the stream related operations or creation of the exception object
+/// may itself throw an exception (specifically \c std::bad_alloc).
+/// Even though it should be very rare, we may have to address this issue later.
+///
+/// Note: in general we hate macros and avoid using it in the code. This is
+/// one of few exceptions to that policy. inline functions cannot be used
+/// for embedding \c __FILE__ and \c __LINE__. This is the main reason why
+/// this is defined as a macro. The convenience for the ostream is a secondary
+/// purpose (if that were the only possible reason we should rather avoid
+/// using a macro).
+#define isc_throw(type, stream) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str()); \
+ } while (1)
+
+///
+/// Similar as isc_throw, but allows the exception to have one additional
+/// parameter (the stream/text goes first)
+#define isc_throw_1(type, stream, param1) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1); \
+ } while (1)
+
+///
+/// Similar as isc_throw, but allows the exception to have two additional
+/// parameters (the stream/text goes first)
+#define isc_throw_2(type, stream, param1, param2) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2); \
+ } while (1)
+
+///
+/// Similar as isc_throw, but allows the exception to have three additional
+/// parameters (the stream/text goes first)
+#define isc_throw_3(type, stream, param1, param2, param3) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3); \
+ } while (1)
+
+///
+/// Similar as isc_throw, but allows the exception to have four additional
+/// parameters (the stream/text goes first)
+#define isc_throw_4(type, stream, param1, param2, param3, param4) \
+ do { \
+ std::ostringstream oss__; \
+ oss__ << stream; \
+ throw type(__FILE__, __LINE__, oss__.str().c_str(), param1, param2,\
+ param3, param4); \
+ } while (1)
+
+}
+#endif // EXCEPTIONS_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/exceptions/isc_assert.h b/src/lib/exceptions/isc_assert.h
new file mode 100644
index 0000000..6b5bf95
--- /dev/null
+++ b/src/lib/exceptions/isc_assert.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 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 ISC_ASSERT_H
+#define ISC_ASSERT_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+
+/// @brief Replacement for assert() that throws if the expression is false.
+/// This macro exists because some of the original code uses assert() and we
+/// decided it would be better to replace them with exception throws rather
+/// than compiling out all asserts.
+#define isc_throw_assert(expr) \
+{\
+ if(!(static_cast<bool>(expr))) \
+ {\
+ isc_throw(isc::Unexpected, __FILE__ << ":" << __LINE__ << " (" << #expr << ") failed");\
+ }\
+}
+
+}
+
+#endif // ISC_ASSERT_H
diff --git a/src/lib/exceptions/tests/Makefile.am b/src/lib/exceptions/tests/Makefile.am
new file mode 100644
index 0000000..5df69bd
--- /dev/null
+++ b/src/lib/exceptions/tests/Makefile.am
@@ -0,0 +1,24 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+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 = run_unittests.cc
+run_unittests_SOURCES += exceptions_unittest.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+run_unittests_LDADD += $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/exceptions/tests/Makefile.in b/src/lib/exceptions/tests/Makefile.in
new file mode 100644
index 0000000..f4f2791
--- /dev/null
+++ b/src/lib/exceptions/tests/Makefile.in
@@ -0,0 +1,875 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/exceptions/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = run_unittests.cc \
+ exceptions_unittest.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-exceptions_unittest.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(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-exceptions_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 =
+SOURCES = $(run_unittests_SOURCES)
+DIST_SOURCES = $(am__run_unittests_SOURCES_DIST)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+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; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+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 = run_unittests.cc \
+@HAVE_GTEST_TRUE@ exceptions_unittest.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(GTEST_LDADD)
+all: all-am
+
+.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/exceptions/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/exceptions/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-exceptions_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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-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`
+
+run_unittests-exceptions_unittest.o: exceptions_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-exceptions_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-exceptions_unittest.Tpo -c -o run_unittests-exceptions_unittest.o `test -f 'exceptions_unittest.cc' || echo '$(srcdir)/'`exceptions_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-exceptions_unittest.Tpo $(DEPDIR)/run_unittests-exceptions_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='exceptions_unittest.cc' object='run_unittests-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-exceptions_unittest.o `test -f 'exceptions_unittest.cc' || echo '$(srcdir)/'`exceptions_unittest.cc
+
+run_unittests-exceptions_unittest.obj: exceptions_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-exceptions_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-exceptions_unittest.Tpo -c -o run_unittests-exceptions_unittest.obj `if test -f 'exceptions_unittest.cc'; then $(CYGPATH_W) 'exceptions_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/exceptions_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-exceptions_unittest.Tpo $(DEPDIR)/run_unittests-exceptions_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='exceptions_unittest.cc' object='run_unittests-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-exceptions_unittest.obj `if test -f 'exceptions_unittest.cc'; then $(CYGPATH_W) 'exceptions_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/exceptions_unittest.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS)
+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 clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/run_unittests-exceptions_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/run_unittests-exceptions_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: 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 \
+ 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/exceptions/tests/exceptions_unittest.cc b/src/lib/exceptions/tests/exceptions_unittest.cc
new file mode 100644
index 0000000..c4b0528
--- /dev/null
+++ b/src/lib/exceptions/tests/exceptions_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2009-2020 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 <stdexcept>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+using isc::Exception;
+
+namespace {
+
+class ExceptionTest : public ::testing::Test {
+protected:
+ ExceptionTest() : teststring("test") {}
+ const char* teststring;
+};
+
+void raise_foobar() {
+ isc_throw(isc::BadValue, "foobar");
+}
+
+TEST_F(ExceptionTest, basicMethods) {
+ try {
+ isc_throw(Exception, teststring);
+ } catch (const Exception& ex) {
+ EXPECT_EQ(ex.getMessage(), std::string(teststring));
+ EXPECT_EQ(ex.getFile(), std::string(__FILE__));
+ EXPECT_EQ(ex.getLine(), __LINE__ - 4);
+ }
+}
+
+// Same than basicMethods but using a string (vs char *)
+TEST_F(ExceptionTest, string) {
+ try {
+ isc_throw(Exception, std::string(teststring));
+ } catch (const Exception& ex) {
+ EXPECT_EQ(ex.getMessage(), std::string(teststring));
+ EXPECT_EQ(ex.getFile(), std::string(__FILE__));
+ EXPECT_EQ(ex.getLine(), __LINE__ - 4);
+ }
+}
+
+// Test to see if it works as a proper derived class of std::exception.
+TEST_F(ExceptionTest, stdInheritance) {
+ try {
+ isc_throw(Exception, teststring);
+ } catch (const std::exception& ex) {
+ EXPECT_EQ(std::string(ex.what()), std::string(teststring));
+ }
+}
+
+// Tests whether verbose is disabled by default and can be enabled, if
+// needed.
+TEST_F(ExceptionTest, verbose) {
+
+ // This code is line numbers sensitive. Make sure no edits are done between
+ // this line and isc_throw below. Update that +3 offset, if needed.
+ std::stringstream expected;
+ expected << teststring << "[" << std::string(__FILE__)
+ << ":" << int(__LINE__ + 3) << "]";
+
+ try {
+ isc_throw(Exception, teststring);
+ } catch (const isc::Exception& ex) {
+ EXPECT_EQ(std::string(ex.what()), std::string(teststring));
+ EXPECT_EQ(std::string(ex.what(false)), std::string(teststring));
+ EXPECT_EQ(expected.str(), std::string(ex.what(true)));
+ }
+
+}
+
+// A full example of how to check both the exception (e.g., EXPECT_THROW)
+// and its associated message (something no gtest macros do).
+TEST_F(ExceptionTest, message) {
+ try {
+ raise_foobar();
+ ADD_FAILURE() << "Expected " "raise_foobar()" \
+ " throws an exception of type " "BadValue" \
+ ".\n Actual: it throws nothing.";
+ } catch (const isc::BadValue& ex) {
+ EXPECT_EQ(std::string(ex.getMessage()), "foobar");
+ } catch (...) {
+ ADD_FAILURE() << "Expected " "raise_foobar()" \
+ " throws an exception of type " "BadValue" \
+ ".\n Actual: it throws a different type.";
+ }
+}
+
+// Sanity check that 'isc_throw_assert' macro operates correctly.
+TEST(IscThrowAssert, checkMessage) {
+ int m = 5;
+ int n = 7;
+
+ ASSERT_NO_THROW(isc_throw_assert(m == m));
+
+ int line_no;
+ try {
+ line_no = __LINE__; isc_throw_assert(m == n);
+ } catch (const std::exception& ex) {
+ std::string msg = ex.what();
+ std::ostringstream os;
+ os << __FILE__ << ":" << line_no << " (m == n) failed";
+ EXPECT_EQ(os.str(), msg);
+ }
+}
+
+}
diff --git a/src/lib/exceptions/tests/run_unittests.cc b/src/lib/exceptions/tests/run_unittests.cc
new file mode 100644
index 0000000..92e2240
--- /dev/null
+++ b/src/lib/exceptions/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// 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>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // Unlike other tests we cannot use our wrapper for RUN_ALL_TESTS()
+ // due to dependency.
+ return (RUN_ALL_TESTS());
+}
diff --git a/src/lib/hooks/Makefile.am b/src/lib/hooks/Makefile.am
new file mode 100644
index 0000000..5b9bddb
--- /dev/null
+++ b/src/lib/hooks/Makefile.am
@@ -0,0 +1,103 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = hooks_messages.mes
+
+# Include developer's guide files
+EXTRA_DIST += hooks_user.dox hooks_maintenance.dox hooks_component_developer.dox
+
+# Include images used in Developer's guide
+EXTRA_DIST += images/DataScopeArgument.dia images/DataScopeArgument.png
+EXTRA_DIST += images/DataScopeContext.dia images/DataScopeContext.png
+EXTRA_DIST += images/HooksUml.dia images/HooksUml.png
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-hooks.la
+libkea_hooks_la_SOURCES =
+libkea_hooks_la_SOURCES += callout_handle.cc callout_handle.h
+libkea_hooks_la_SOURCES += callout_handle_associate.cc callout_handle_associate.h
+libkea_hooks_la_SOURCES += callout_manager.cc callout_manager.h
+libkea_hooks_la_SOURCES += hooks.h
+libkea_hooks_la_SOURCES += hooks_log.cc hooks_log.h
+libkea_hooks_la_SOURCES += hooks_manager.cc hooks_manager.h
+libkea_hooks_la_SOURCES += hooks_config.cc hooks_config.h
+libkea_hooks_la_SOURCES += hooks_parser.cc hooks_parser.h
+libkea_hooks_la_SOURCES += libinfo.cc libinfo.h
+libkea_hooks_la_SOURCES += library_handle.cc library_handle.h
+libkea_hooks_la_SOURCES += library_manager.cc library_manager.h
+libkea_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h
+libkea_hooks_la_SOURCES += parking_lots.h
+libkea_hooks_la_SOURCES += pointer_converter.h
+libkea_hooks_la_SOURCES += server_hooks.cc server_hooks.h
+libkea_hooks_la_SOURCES += hooks_messages.cc hooks_messages.h
+
+libkea_hooks_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_hooks_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_hooks_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 78:0:0
+libkea_hooks_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_hooks_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_hooks_la_LIBADD += $(BOOST_LIBS)
+libkea_hooks_la_LIBADD += $(LOG4CPLUS_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f hooks_messages.h hooks_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: hooks_messages.h hooks_messages.cc
+ @echo Message files regenerated
+
+hooks_messages.h hooks_messages.cc: hooks_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/hooks/hooks_messages.mes
+
+else
+
+messages hooks_messages.h hooks_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_hooks_includedir = $(pkgincludedir)/hooks
+libkea_hooks_include_HEADERS = \
+ callout_handle.h \
+ callout_handle_associate.h \
+ callout_manager.h \
+ hooks.h \
+ hooks_config.h \
+ hooks_log.h \
+ hooks_manager.h \
+ hooks_messages.h \
+ hooks_parser.h \
+ libinfo.h \
+ library_handle.h \
+ library_manager.h \
+ library_manager_collection.h \
+ parking_lots.h \
+ pointer_converter.h \
+ server_hooks.h
+
+
diff --git a/src/lib/hooks/Makefile.in b/src/lib/hooks/Makefile.in
new file mode 100644
index 0000000..94466d5
--- /dev/null
+++ b/src/lib/hooks/Makefile.in
@@ -0,0 +1,1164 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/hooks
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_hooks_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)$(libkea_hooks_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_hooks_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_hooks_la_OBJECTS = libkea_hooks_la-callout_handle.lo \
+ libkea_hooks_la-callout_handle_associate.lo \
+ libkea_hooks_la-callout_manager.lo \
+ libkea_hooks_la-hooks_log.lo libkea_hooks_la-hooks_manager.lo \
+ libkea_hooks_la-hooks_config.lo \
+ libkea_hooks_la-hooks_parser.lo libkea_hooks_la-libinfo.lo \
+ libkea_hooks_la-library_handle.lo \
+ libkea_hooks_la-library_manager.lo \
+ libkea_hooks_la-library_manager_collection.lo \
+ libkea_hooks_la-server_hooks.lo \
+ libkea_hooks_la-hooks_messages.lo
+libkea_hooks_la_OBJECTS = $(am_libkea_hooks_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_hooks_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_hooks_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_hooks_la-callout_handle.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-libinfo.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-library_handle.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-library_manager.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo \
+ ./$(DEPDIR)/libkea_hooks_la-server_hooks.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_hooks_la_SOURCES)
+DIST_SOURCES = $(libkea_hooks_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 = $(libkea_hooks_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+
+# Include developer's guide files
+
+# Include images used in Developer's guide
+EXTRA_DIST = hooks_messages.mes hooks_user.dox hooks_maintenance.dox \
+ hooks_component_developer.dox images/DataScopeArgument.dia \
+ images/DataScopeArgument.png images/DataScopeContext.dia \
+ images/DataScopeContext.png images/HooksUml.dia \
+ images/HooksUml.png
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-hooks.la
+libkea_hooks_la_SOURCES = callout_handle.cc callout_handle.h \
+ callout_handle_associate.cc callout_handle_associate.h \
+ callout_manager.cc callout_manager.h hooks.h hooks_log.cc \
+ hooks_log.h hooks_manager.cc hooks_manager.h hooks_config.cc \
+ hooks_config.h hooks_parser.cc hooks_parser.h libinfo.cc \
+ libinfo.h library_handle.cc library_handle.h \
+ library_manager.cc library_manager.h \
+ library_manager_collection.cc library_manager_collection.h \
+ parking_lots.h pointer_converter.h server_hooks.cc \
+ server_hooks.h hooks_messages.cc hooks_messages.h
+libkea_hooks_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_hooks_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_hooks_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 78:0:0
+libkea_hooks_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(BOOST_LIBS) $(LOG4CPLUS_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_hooks_includedir = $(pkgincludedir)/hooks
+libkea_hooks_include_HEADERS = \
+ callout_handle.h \
+ callout_handle_associate.h \
+ callout_manager.h \
+ hooks.h \
+ hooks_config.h \
+ hooks_log.h \
+ hooks_manager.h \
+ hooks_messages.h \
+ hooks_parser.h \
+ libinfo.h \
+ library_handle.h \
+ library_manager.h \
+ library_manager_collection.h \
+ parking_lots.h \
+ pointer_converter.h \
+ server_hooks.h
+
+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/hooks/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/hooks/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-hooks.la: $(libkea_hooks_la_OBJECTS) $(libkea_hooks_la_DEPENDENCIES) $(EXTRA_libkea_hooks_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_hooks_la_LINK) -rpath $(libdir) $(libkea_hooks_la_OBJECTS) $(libkea_hooks_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-callout_handle.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-libinfo.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-library_handle.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-library_manager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_hooks_la-server_hooks.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)$(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 $@ $<
+
+libkea_hooks_la-callout_handle.lo: callout_handle.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-callout_handle.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-callout_handle.Tpo -c -o libkea_hooks_la-callout_handle.lo `test -f 'callout_handle.cc' || echo '$(srcdir)/'`callout_handle.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-callout_handle.Tpo $(DEPDIR)/libkea_hooks_la-callout_handle.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle.cc' object='libkea_hooks_la-callout_handle.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-callout_handle.lo `test -f 'callout_handle.cc' || echo '$(srcdir)/'`callout_handle.cc
+
+libkea_hooks_la-callout_handle_associate.lo: callout_handle_associate.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-callout_handle_associate.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-callout_handle_associate.Tpo -c -o libkea_hooks_la-callout_handle_associate.lo `test -f 'callout_handle_associate.cc' || echo '$(srcdir)/'`callout_handle_associate.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-callout_handle_associate.Tpo $(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_associate.cc' object='libkea_hooks_la-callout_handle_associate.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-callout_handle_associate.lo `test -f 'callout_handle_associate.cc' || echo '$(srcdir)/'`callout_handle_associate.cc
+
+libkea_hooks_la-callout_manager.lo: callout_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-callout_manager.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-callout_manager.Tpo -c -o libkea_hooks_la-callout_manager.lo `test -f 'callout_manager.cc' || echo '$(srcdir)/'`callout_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-callout_manager.Tpo $(DEPDIR)/libkea_hooks_la-callout_manager.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_manager.cc' object='libkea_hooks_la-callout_manager.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-callout_manager.lo `test -f 'callout_manager.cc' || echo '$(srcdir)/'`callout_manager.cc
+
+libkea_hooks_la-hooks_log.lo: hooks_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_log.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_log.Tpo -c -o libkea_hooks_la-hooks_log.lo `test -f 'hooks_log.cc' || echo '$(srcdir)/'`hooks_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_log.Tpo $(DEPDIR)/libkea_hooks_la-hooks_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_log.cc' object='libkea_hooks_la-hooks_log.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_log.lo `test -f 'hooks_log.cc' || echo '$(srcdir)/'`hooks_log.cc
+
+libkea_hooks_la-hooks_manager.lo: hooks_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_manager.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_manager.Tpo -c -o libkea_hooks_la-hooks_manager.lo `test -f 'hooks_manager.cc' || echo '$(srcdir)/'`hooks_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_manager.Tpo $(DEPDIR)/libkea_hooks_la-hooks_manager.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_manager.cc' object='libkea_hooks_la-hooks_manager.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_manager.lo `test -f 'hooks_manager.cc' || echo '$(srcdir)/'`hooks_manager.cc
+
+libkea_hooks_la-hooks_config.lo: hooks_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_config.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_config.Tpo -c -o libkea_hooks_la-hooks_config.lo `test -f 'hooks_config.cc' || echo '$(srcdir)/'`hooks_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_config.Tpo $(DEPDIR)/libkea_hooks_la-hooks_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_config.cc' object='libkea_hooks_la-hooks_config.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_config.lo `test -f 'hooks_config.cc' || echo '$(srcdir)/'`hooks_config.cc
+
+libkea_hooks_la-hooks_parser.lo: hooks_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_parser.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_parser.Tpo -c -o libkea_hooks_la-hooks_parser.lo `test -f 'hooks_parser.cc' || echo '$(srcdir)/'`hooks_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_parser.Tpo $(DEPDIR)/libkea_hooks_la-hooks_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_parser.cc' object='libkea_hooks_la-hooks_parser.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_parser.lo `test -f 'hooks_parser.cc' || echo '$(srcdir)/'`hooks_parser.cc
+
+libkea_hooks_la-libinfo.lo: libinfo.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-libinfo.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-libinfo.Tpo -c -o libkea_hooks_la-libinfo.lo `test -f 'libinfo.cc' || echo '$(srcdir)/'`libinfo.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-libinfo.Tpo $(DEPDIR)/libkea_hooks_la-libinfo.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='libinfo.cc' object='libkea_hooks_la-libinfo.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-libinfo.lo `test -f 'libinfo.cc' || echo '$(srcdir)/'`libinfo.cc
+
+libkea_hooks_la-library_handle.lo: library_handle.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-library_handle.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-library_handle.Tpo -c -o libkea_hooks_la-library_handle.lo `test -f 'library_handle.cc' || echo '$(srcdir)/'`library_handle.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-library_handle.Tpo $(DEPDIR)/libkea_hooks_la-library_handle.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_handle.cc' object='libkea_hooks_la-library_handle.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-library_handle.lo `test -f 'library_handle.cc' || echo '$(srcdir)/'`library_handle.cc
+
+libkea_hooks_la-library_manager.lo: library_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-library_manager.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-library_manager.Tpo -c -o libkea_hooks_la-library_manager.lo `test -f 'library_manager.cc' || echo '$(srcdir)/'`library_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-library_manager.Tpo $(DEPDIR)/libkea_hooks_la-library_manager.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager.cc' object='libkea_hooks_la-library_manager.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-library_manager.lo `test -f 'library_manager.cc' || echo '$(srcdir)/'`library_manager.cc
+
+libkea_hooks_la-library_manager_collection.lo: library_manager_collection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-library_manager_collection.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-library_manager_collection.Tpo -c -o libkea_hooks_la-library_manager_collection.lo `test -f 'library_manager_collection.cc' || echo '$(srcdir)/'`library_manager_collection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-library_manager_collection.Tpo $(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_collection.cc' object='libkea_hooks_la-library_manager_collection.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-library_manager_collection.lo `test -f 'library_manager_collection.cc' || echo '$(srcdir)/'`library_manager_collection.cc
+
+libkea_hooks_la-server_hooks.lo: server_hooks.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-server_hooks.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-server_hooks.Tpo -c -o libkea_hooks_la-server_hooks.lo `test -f 'server_hooks.cc' || echo '$(srcdir)/'`server_hooks.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-server_hooks.Tpo $(DEPDIR)/libkea_hooks_la-server_hooks.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_hooks.cc' object='libkea_hooks_la-server_hooks.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-server_hooks.lo `test -f 'server_hooks.cc' || echo '$(srcdir)/'`server_hooks.cc
+
+libkea_hooks_la-hooks_messages.lo: hooks_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_hooks_la-hooks_messages.lo -MD -MP -MF $(DEPDIR)/libkea_hooks_la-hooks_messages.Tpo -c -o libkea_hooks_la-hooks_messages.lo `test -f 'hooks_messages.cc' || echo '$(srcdir)/'`hooks_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_hooks_la-hooks_messages.Tpo $(DEPDIR)/libkea_hooks_la-hooks_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_messages.cc' object='libkea_hooks_la-hooks_messages.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_hooks_la_CPPFLAGS) $(CPPFLAGS) $(libkea_hooks_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_hooks_la-hooks_messages.lo `test -f 'hooks_messages.cc' || echo '$(srcdir)/'`hooks_messages.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_hooks_includeHEADERS: $(libkea_hooks_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_hooks_include_HEADERS)'; test -n "$(libkea_hooks_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_hooks_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_hooks_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)$(libkea_hooks_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_hooks_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_hooks_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_hooks_include_HEADERS)'; test -n "$(libkea_hooks_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_hooks_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_hooks_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_handle.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-libinfo.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-library_handle.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-server_hooks.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-libkea_hooks_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_hooks_la-callout_handle.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_handle_associate.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-callout_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-hooks_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-libinfo.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-library_handle.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-library_manager_collection.Plo
+ -rm -f ./$(DEPDIR)/libkea_hooks_la-server_hooks.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_hooks_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_hooks_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_hooks_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f hooks_messages.h hooks_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: hooks_messages.h hooks_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@hooks_messages.h hooks_messages.cc: hooks_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/hooks/hooks_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages hooks_messages.h hooks_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/hooks/callout_handle.cc b/src/lib/hooks/callout_handle.cc
new file mode 100644
index 0000000..6877b65
--- /dev/null
+++ b/src/lib/hooks/callout_handle.cc
@@ -0,0 +1,167 @@
+// Copyright (C) 2013-2022 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor.
+CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+ const boost::shared_ptr<LibraryManagerCollection>& lmcoll)
+ : lm_collection_(lmcoll), arguments_(), context_collection_(),
+ manager_(manager), server_hooks_(ServerHooks::getServerHooks()),
+ current_library_(-1), current_hook_(-1), next_step_(NEXT_STEP_CONTINUE) {
+
+ // Call the "context_create" hook. We should be OK doing this - although
+ // the constructor has not finished running, all the member variables
+ // have been created.
+ manager_->callCallouts(ServerHooks::CONTEXT_CREATE, *this);
+}
+
+// Destructor
+CalloutHandle::~CalloutHandle() {
+ // Call the "context_destroy" hook. We should be OK doing this - although
+ // the destructor is being called, all the member variables are still in
+ // existence.
+ manager_->callCallouts(ServerHooks::CONTEXT_DESTROY, *this);
+
+ // Explicitly clear the argument and context objects. This should free up
+ // all memory that could have been allocated by libraries that were loaded.
+ arguments_.clear();
+ context_collection_.clear();
+
+ // Normal destruction of the remaining variables will include the
+ // destruction of lm_collection_, an action that decrements the reference
+ // count on the library manager collection (which holds the libraries that
+ // could have allocated memory in the argument and context members.) When
+ // that goes to zero, the libraries will be unloaded: at that point nothing
+ // in the hooks framework will be pointing to memory in the libraries'
+ // address space.
+ //
+ // It is possible that some other data structure in the server (the program
+ // using the hooks library) still references the address space and attempts
+ // to access it causing a segmentation fault. That issue is outside the
+ // scope of this framework and is not addressed by it.
+}
+
+// Return the name of all argument items.
+
+vector<string>
+CalloutHandle::getArgumentNames() const {
+ vector<string> names;
+ for (ElementCollection::const_iterator i = arguments_.begin();
+ i != arguments_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+ParkingLotHandlePtr
+CalloutHandle::getParkingLotHandlePtr() const {
+ return (boost::make_shared<ParkingLotHandle>(server_hooks_.getParkingLotPtr(current_hook_)));
+}
+
+// Return the context for the currently pointed-to library. This version is
+// used by the "setContext()" method and creates a context for the current
+// library if it does not exist.
+
+CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() {
+ // Access a reference to the element collection for the given index,
+ // creating a new element collection if necessary, and return it.
+ return (context_collection_[current_library_]);
+}
+
+// The "const" version of the above, used by the "getContext()" method. If
+// the context for the current library doesn't exist, throw an exception.
+
+const CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() const {
+ auto libcontext = context_collection_.find(current_library_);
+ if (libcontext == context_collection_.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "associated with the current library index (" << current_library_ <<
+ ")");
+ }
+
+ // Return a reference to the context's element collection.
+ return (libcontext->second);
+}
+
+// Return the name of all items in the context associated with the current]
+// library.
+
+vector<string>
+CalloutHandle::getContextNames() const {
+ vector<string> names;
+ const ElementCollection& elements = getContextForLibrary();
+ for (ElementCollection::const_iterator i = elements.begin();
+ i != elements.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return name of current hook (the hook to which the current callout is
+// attached) or the empty string if not called within the context of a
+// callout.
+
+string
+CalloutHandle::getHookName() const {
+ string hook = "";
+ try {
+ hook = server_hooks_.getName(current_hook_);
+ } catch (const NoSuchHook&) {
+ // Hook index is invalid, so this methods probably called from outside
+ // a callout being executed via a call to CalloutManager::callCallouts.
+ // In this case, the empty string is returned.
+ }
+
+ return (hook);
+}
+
+ScopedCalloutHandleState::
+ScopedCalloutHandleState(const CalloutHandlePtr& callout_handle)
+ : callout_handle_(callout_handle) {
+ if (!callout_handle_) {
+ isc_throw(BadValue, "callout_handle argument must not be null");
+ }
+
+ resetState();
+}
+
+ScopedCalloutHandleState::~ScopedCalloutHandleState() {
+ resetState();
+
+ if (on_completion_) {
+ on_completion_();
+ }
+}
+
+void
+ScopedCalloutHandleState::resetState() {
+ // No need to check if the handle is null because the constructor
+ // already checked that.
+ callout_handle_->deleteAllArguments();
+ callout_handle_->setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+}
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/callout_handle.h b/src/lib/hooks/callout_handle.h
new file mode 100644
index 0000000..fb4b30a
--- /dev/null
+++ b/src/lib/hooks/callout_handle.h
@@ -0,0 +1,512 @@
+// Copyright (C) 2013-2022 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 CALLOUT_HANDLE_H
+#define CALLOUT_HANDLE_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/library_handle.h>
+#include <hooks/parking_lots.h>
+#include <util/dhcp_space.h>
+
+#include <boost/any.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+class ServerHooks;
+
+/// @brief No such argument
+///
+/// Thrown if an attempt is made access an argument that does not exist.
+
+class NoSuchArgument : public Exception {
+public:
+ NoSuchArgument(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief No such callout context item
+///
+/// Thrown if an attempt is made to get an item of data from this callout's
+/// context and either the context or an item in the context with that name
+/// does not exist.
+
+class NoSuchCalloutContext : public Exception {
+public:
+ NoSuchCalloutContext(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+// Forward declaration of the library handle and related collection classes.
+
+class CalloutManager;
+class LibraryManagerCollection;
+
+/// @brief Per-packet callout handle
+///
+/// An object of this class is associated with every packet (or request)
+/// processed by the server. It forms the principle means of passing data
+/// between the server and the user-library callouts.
+///
+/// The class allows access to the following information:
+///
+/// - Arguments. When the callouts associated with a hook are called, they
+/// are passed information by the server (and can return information to it)
+/// through name/value pairs. Each of these pairs is an argument and the
+/// information is accessed through the {get,set}Argument() methods.
+///
+/// - Per-packet context. Each packet has a context associated with it, this
+/// context being on a per-library basis. In other words, As a packet passes
+/// through the callouts associated with a given library, the callouts can
+/// associate and retrieve information with the packet. The per-library
+/// nature of the context means that the callouts within a given library can
+/// pass packet-specific information between one another, but they cannot pass
+/// information to callous within another library. Typically such context
+/// is created in the "context_create" callout and destroyed in the
+/// "context_destroy" callout. The information is accessed through the
+/// {get,set}Context() methods.
+
+class CalloutHandle {
+public:
+
+ /// @brief Specifies allowed next steps
+ ///
+ /// Those values are used to designate the next step in packet processing.
+ /// They are set by hook callouts and read by the Kea server. See
+ /// @ref setStatus for detailed description of each value.
+ enum CalloutNextStep {
+ NEXT_STEP_CONTINUE = 0, ///< continue normally
+ NEXT_STEP_SKIP = 1, ///< skip the next processing step
+ NEXT_STEP_DROP = 2, ///< drop the packet
+ NEXT_STEP_PARK = 3 ///< park the packet
+ };
+
+
+ /// Typedef to allow abbreviation of iterator specification in methods.
+ /// The std::string is the argument name and the "boost::any" is the
+ /// corresponding value associated with it.
+ typedef std::map<std::string, boost::any> ElementCollection;
+
+ /// Typedef to allow abbreviations in specifications when accessing
+ /// context. The ElementCollection is the name/value collection for
+ /// a particular context. The "int" corresponds to the index of an
+ /// associated library - there is a 1:1 correspondence between libraries
+ /// and a name.value collection.
+ ///
+ /// The collection of contexts is stored in a map, as not every library
+ /// will require creation of a context associated with each packet. In
+ /// addition, the structure is more flexible in that the size does not
+ /// need to be set when the CalloutHandle is constructed.
+ typedef std::map<int, ElementCollection> ContextCollection;
+
+ /// @brief Constructor
+ ///
+ /// Creates the object and calls the callouts on the "context_create"
+ /// hook.
+ ///
+ /// Of the two arguments passed, only the pointer to the callout manager is
+ /// actively used. The second argument, the pointer to the library manager
+ /// collection, is used for lifetime control: after use, the callout handle
+ /// may contain pointers to memory allocated by the loaded libraries. The
+ /// used of a shared pointer to the collection of library managers means
+ /// that the libraries that could have allocated memory in a callout handle
+ /// will not be unloaded until all such handles have been destroyed. This
+ /// issue is discussed in more detail in the documentation for
+ /// isc::hooks::LibraryManager.
+ ///
+ /// @param manager Pointer to the callout manager object.
+ /// @param lmcoll Pointer to the library manager collection. This has a
+ /// null default for testing purposes.
+ CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+ const boost::shared_ptr<LibraryManagerCollection>& lmcoll =
+ boost::shared_ptr<LibraryManagerCollection>());
+
+ /// @brief Destructor
+ ///
+ /// Calls the context_destroy callback to release any per-packet context.
+ /// It also clears stored data to avoid problems during member destruction.
+ ~CalloutHandle();
+
+ /// @brief Set argument
+ ///
+ /// Sets the value of an argument. The argument is created if it does not
+ /// already exist.
+ ///
+ /// @param name Name of the argument.
+ /// @param value Value to set. That can be of any data type.
+ template <typename T>
+ void setArgument(const std::string& name, T value) {
+ arguments_[name] = value;
+ }
+
+ /// @brief Get argument
+ ///
+ /// Gets the value of an argument.
+ ///
+ /// @param name Name of the element in the argument list to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchArgument No argument with the given name is present.
+ /// @throw boost::bad_any_cast An argument with the given name is present,
+ /// but the data type of the value is not the same as the type of
+ /// the variable provided to receive the value.
+ template <typename T>
+ void getArgument(const std::string& name, T& value) const {
+ ElementCollection::const_iterator element_ptr = arguments_.find(name);
+ if (element_ptr == arguments_.end()) {
+ isc_throw(NoSuchArgument, "unable to find argument with name " <<
+ name);
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get argument names
+ ///
+ /// Returns a vector holding the names of arguments in the argument
+ /// vector.
+ ///
+ /// @return Vector of strings reflecting argument names.
+ std::vector<std::string> getArgumentNames() const;
+
+ /// @brief Delete argument
+ ///
+ /// Deletes an argument of the given name. If an argument of that name
+ /// does not exist, the method is a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this method.
+ ///
+ /// @param name Name of the element in the argument list to set.
+ void deleteArgument(const std::string& name) {
+ static_cast<void>(arguments_.erase(name));
+ }
+
+ /// @brief Delete all arguments
+ ///
+ /// Deletes all arguments associated with this context.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this method.
+ void deleteAllArguments() {
+ arguments_.clear();
+ }
+
+ /// @brief Sets the next processing step.
+ ///
+ /// This method is used by the callouts to determine the next step
+ /// in processing. This method replaces former setSkip() method
+ /// that allowed only two values.
+ ///
+ /// Currently there are three possible value allowed:
+ /// NEXT_STEP_CONTINUE - tells the server to continue processing as usual
+ /// (equivalent of previous setSkip(false) )
+ ///
+ /// NEXT_STEP_SKIP - tells the server to skip the processing. Exact meaning
+ /// is hook specific. See hook documentation for details.
+ /// (equivalent of previous setSkip(true))
+ ///
+ /// NEXT_STEP_DROP - tells the server to unconditionally drop the packet
+ /// and do not process it further.
+ ///
+ /// NEXT_STEP_PARK - tells the server to "park" the packet. The packet will
+ /// wait in the queue for being unparked, e.g. as a result
+ /// of completion of the asynchronous performed by the
+ /// hooks library operation.
+ ///
+ /// This variable is interrogated by the server to see if the remaining
+ /// callouts associated with the current hook should be bypassed.
+ ///
+ /// @param next New value of the next step status.
+ void setStatus(const CalloutNextStep next) {
+ next_step_ = next;
+ }
+
+ /// @brief Returns the next processing step.
+ ///
+ /// Gets the current value of the next step. See @ref setStatus for detailed
+ /// definition.
+ ///
+ /// @return Current value of the skip flag.
+ CalloutNextStep getStatus() const {
+ return (next_step_);
+ }
+
+ /// @brief Set context
+ ///
+ /// Sets an element in the context associated with the current library. If
+ /// an element of the name is already present, it is replaced.
+ ///
+ /// @param name Name of the element in the context to set.
+ /// @param value Value to set.
+ template <typename T>
+ void setContext(const std::string& name, T value) {
+ getContextForLibrary()[name] = value;
+ }
+
+ /// @brief Get context
+ ///
+ /// Gets an element from the context associated with the current library.
+ ///
+ /// @param name Name of the element in the context to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if no context element with the name
+ /// "name" is present.
+ /// @throw boost::bad_any_cast Thrown if the context element is present
+ /// but the type of the data is not the same as the type of the
+ /// variable provided to receive its value.
+ template <typename T>
+ void getContext(const std::string& name, T& value) const {
+ const ElementCollection& lib_context = getContextForLibrary();
+
+ ElementCollection::const_iterator element_ptr = lib_context.find(name);
+ if (element_ptr == lib_context.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "item " << name << " in the context associated with "
+ "current library");
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get context names
+ ///
+ /// Returns a vector holding the names of items in the context associated
+ /// with the current library.
+ ///
+ /// @return Vector of strings reflecting the names of items in the callout
+ /// context associated with the current library.
+ std::vector<std::string> getContextNames() const;
+
+ /// @brief Delete context element
+ ///
+ /// Deletes an item of the given name from the context associated with the
+ /// current library. If an item of that name does not exist, the method is
+ /// a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this.
+ ///
+ /// @param name Name of the context item to delete.
+ void deleteContext(const std::string& name) {
+ static_cast<void>(getContextForLibrary().erase(name));
+ }
+
+ /// @brief Delete all context items
+ ///
+ /// Deletes all items from the context associated with the current library.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this.
+ void deleteAllContext() {
+ getContextForLibrary().clear();
+ }
+
+ /// @brief Get hook name
+ ///
+ /// Get the name of the hook to which the current callout is attached.
+ /// This can be the null string if the CalloutHandle is being accessed
+ /// outside of the CalloutManager's "callCallouts" method.
+ ///
+ /// @return Name of the current hook or the empty string if none.
+ std::string getHookName() const;
+
+ /// @brief Returns pointer to the parking lot handle for this hook point.
+ ///
+ /// @return pointer to the parking lot handle
+ ParkingLotHandlePtr getParkingLotHandlePtr() const;
+
+ /// @brief Get current library index
+ ///
+ /// @return The current library index
+ int getCurrentLibrary() const {
+ return (current_library_);
+ }
+
+ /// @brief Set current library index
+ ///
+ /// @param library_index The library index
+ void setCurrentLibrary(int library_index) {
+ current_library_ = library_index;
+ }
+
+ /// @brief Get current hook index
+ ///
+ /// @return The current hook index
+ int getCurrentHook() const {
+ return (current_hook_);
+ }
+
+ /// @brief Set current hook index
+ ///
+ /// @param hook_index The hook index
+ void setCurrentHook(int hook_index) {
+ current_hook_ = hook_index;
+ }
+
+private:
+
+ /// @brief Check index
+ ///
+ /// Gets the current library index, throwing an exception if it is not set
+ /// or is invalid for the current library collection.
+ ///
+ /// @return Current library index, valid for this library collection.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ int getLibraryIndex() const;
+
+ /// @brief Return reference to context for current library
+ ///
+ /// Called by all context-setting functions, this returns a reference to
+ /// the callout context for the current library, creating a context if it
+ /// does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ ElementCollection& getContextForLibrary();
+
+ /// @brief Return reference to context for current library (const version)
+ ///
+ /// Called by all context-accessing functions, this a reference to the
+ /// callout context for the current library. An exception is thrown if
+ /// it does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection
+ /// associated with the current library.
+ const ElementCollection& getContextForLibrary() const;
+
+ // Member variables
+
+ /// Pointer to the collection of libraries for which this handle has been
+ /// created.
+ boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
+ /// Collection of arguments passed to the callouts
+ ElementCollection arguments_;
+
+ /// Context collection - there is one entry per library context.
+ ContextCollection context_collection_;
+
+ /// Callout manager.
+ boost::shared_ptr<CalloutManager> manager_;
+
+ /// Reference to the singleton ServerHooks object. See the
+ /// @ref hooksmgMaintenanceGuide for information as to why the class holds
+ /// a reference instead of accessing the singleton within the code.
+ ServerHooks& server_hooks_;
+
+ /// @brief Current library.
+ ///
+ /// When a call is made to @ref CalloutManager::callCallouts, this holds
+ /// the index of the current library. It is set to an invalid value (-1)
+ /// otherwise.
+ int current_library_;
+
+ /// @brief Current hook.
+ ///
+ /// When a call is made to @ref CalloutManager::callCallouts, this holds
+ /// the index of the current hook. It is set to an invalid value (-1)
+ /// otherwise.
+ int current_hook_;
+
+ /// Next processing step, indicating what the server should do next.
+ CalloutNextStep next_step_;
+};
+
+/// A shared pointer to a CalloutHandle object.
+typedef boost::shared_ptr<CalloutHandle> CalloutHandlePtr;
+
+/// @brief Wrapper class around callout handle which automatically
+/// resets handle's state.
+///
+/// The Kea servers often require to associate processed packets with
+/// @c CalloutHandle instances. This is to facilitate the case when the
+/// hooks library passes information between the callouts using the
+/// 'context' stored in the callout handle. The callouts invoked throughout
+/// the packet lifetime have access to the context information for the
+/// given packet.
+///
+/// The association between the packets and the callout handles is
+/// achieved by giving the ownership of the @c CalloutHandle objects to
+/// the @c Pkt objects. When the @c Pkt object goes out of scope, it should
+/// also release the pointer to the owned @c CalloutHandle object.
+/// However, this causes a risk of circular dependency between the shared
+/// pointer to the @c Pkt object and the shared pointer to the
+/// @c CalloutHandle it owns, because the pointer to the packet is often
+/// set as an argument of the callout handle prior to invoking a callout.
+///
+/// In order to break the circular dependency, the arguments of the
+/// callout handle must be deleted as soon as they are not needed
+/// anymore. This class is a wrapper around the callout handle object,
+/// which resets its state during construction and destruction. All
+/// Kea hook points must use this class within the scope where the
+/// @c HooksManager::callCallouts is invoked to reset the state of the
+/// callout handle. The state is reset when this object goes out of
+/// scope.
+///
+/// Currently, the following operations are performed during the reset:
+/// - all arguments of the callout handle are deleted,
+/// - the next step status is set to @c CalloutHandle::NEXT_STEP CONTINUE
+///
+/// This class must never be modified to also delete the context
+/// information from the callout handle. The context is intended
+/// to be used to share stateful data across callouts and hook points
+/// and its contents must exist for the duration of the packet lifecycle.
+/// Otherwise, we could simply re-create the callout handle for
+/// each hook point and we wouldn't need this RAII class.
+class ScopedCalloutHandleState {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Resets state of the callout handle.
+ ///
+ /// @param callout_handle reference to the pointer to the callout
+ /// handle which state should be reset.
+ /// @throw isc::BadValue if the callout handle is null.
+ explicit ScopedCalloutHandleState(const CalloutHandlePtr& callout_handle);
+
+ /// @brief Destructor.
+ ///
+ /// Resets state of the callout handle.
+ ~ScopedCalloutHandleState();
+
+ /// @brief Continuation callback.
+ std::function<void()> on_completion_;
+
+private:
+
+ /// @brief Resets the callout handle state.
+ ///
+ /// It is used internally by the constructor and destructor.
+ void resetState();
+
+ /// @brief Holds pointer to the wrapped callout handle.
+ CalloutHandlePtr callout_handle_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // CALLOUT_HANDLE_H
diff --git a/src/lib/hooks/callout_handle_associate.cc b/src/lib/hooks/callout_handle_associate.cc
new file mode 100644
index 0000000..dc6a3dc
--- /dev/null
+++ b/src/lib/hooks/callout_handle_associate.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2018-2022 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 <hooks/callout_handle_associate.h>
+#include <hooks/hooks_manager.h>
+
+namespace isc {
+namespace hooks {
+
+CalloutHandleAssociate::CalloutHandleAssociate()
+ : callout_handle_() {
+}
+
+CalloutHandlePtr
+CalloutHandleAssociate::getCalloutHandle() {
+ if (!callout_handle_) {
+ callout_handle_ = HooksManager::createCalloutHandle();
+ }
+
+ return (callout_handle_);
+}
+
+void
+CalloutHandleAssociate::resetCalloutHandle() {
+ callout_handle_.reset();
+}
+
+} // end of namespace isc::hooks
+} // end of namespace isc
diff --git a/src/lib/hooks/callout_handle_associate.h b/src/lib/hooks/callout_handle_associate.h
new file mode 100644
index 0000000..c222590
--- /dev/null
+++ b/src/lib/hooks/callout_handle_associate.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2018-2022 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 CALLOUT_HANDLE_ASSOCIATE_H
+#define CALLOUT_HANDLE_ASSOCIATE_H
+
+#include <hooks/callout_handle.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Base class for classes which need to be associated with
+/// a @c CalloutHandle object.
+///
+/// The @c CalloutHandle is an object used to pass various parameters
+/// between Kea and the callouts. The Kea servers usually invoke
+/// multiple different callouts for a single packet such as DHCP
+/// packet, control command etc. Therefore, it is required to
+/// associate this packet with an instance of the callout handle, so
+/// this instance can be used for all callouts invoked for this
+/// packet.
+///
+/// Previously this association was made by the @c CalloutHandleStore
+/// class. However, with the introduction of parallel processing
+/// of packets (DHCP packets parking) it became awkward to use.
+/// Attempts to extend this class to hold a map of associations
+/// failed because of no easy way to garbage collect unused handles.
+///
+/// The easiest way to deal with this is to provide ownership of the
+/// @c CalloutHandle to the object with which it is associated. The
+/// class of this object needs to derive from this class. When the
+/// object (e.g. DHCP packet) goes out of scope and is destroyed
+/// this instance is destroyed as well.
+class CalloutHandleAssociate {
+public:
+
+ /// @brief Constructor.
+ CalloutHandleAssociate();
+
+ /// @brief Returns callout handle.
+ ///
+ /// The callout handle is created if it doesn't exist. Subsequent
+ /// calls to this method always return the same handle.
+ ///
+ /// @return Pointer to the callout handle.
+ CalloutHandlePtr getCalloutHandle();
+
+ /// @brief Reset callout handle.
+ void resetCalloutHandle();
+
+protected:
+
+ /// @brief Callout handle stored.
+ CalloutHandlePtr callout_handle_;
+};
+
+} // end of isc::hooks
+} // end of isc
+
+#endif
diff --git a/src/lib/hooks/callout_manager.cc b/src/lib/hooks/callout_manager.cc
new file mode 100644
index 0000000..575d147
--- /dev/null
+++ b/src/lib/hooks/callout_manager.cc
@@ -0,0 +1,351 @@
+// Copyright (C) 2013-2023 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_log.h>
+#include <hooks/pointer_converter.h>
+#include <util/stopwatch.h>
+
+#include <boost/static_assert.hpp>
+
+#include <algorithm>
+#include <climits>
+#include <functional>
+#include <utility>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor
+CalloutManager::CalloutManager(int num_libraries)
+ : server_hooks_(ServerHooks::getServerHooks()), current_library_(-1),
+ hook_vector_(ServerHooks::getServerHooks().getCount()),
+ library_handle_(*this), pre_library_handle_(*this, 0),
+ post_library_handle_(*this, INT_MAX), num_libraries_(num_libraries) {
+ if (num_libraries < 0) {
+ isc_throw(isc::BadValue, "number of libraries passed to the "
+ "CalloutManager must be >= 0");
+ }
+}
+
+// Check that the index of a library is valid. It can range from 1 - n
+// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
+// (post-user library callouts). It can also be -1 to indicate an invalid
+// value.
+void
+CalloutManager::checkLibraryIndex(int library_index) const {
+ if (((library_index >= -1) && (library_index <= num_libraries_)) ||
+ (library_index == INT_MAX)) {
+ return;
+ }
+
+ isc_throw(NoSuchLibrary, "library index " << library_index <<
+ " is not valid for the number of loaded libraries (" <<
+ num_libraries_ << ")");
+}
+
+// Register a callout for the current library.
+void
+CalloutManager::registerCallout(const std::string& name,
+ CalloutPtr callout,
+ int library_index) {
+ // Note the registration.
+ LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
+ .arg(library_index).arg(name);
+
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(library_index);
+
+ // New hooks could have been registered since the manager was constructed.
+ ensureHookLibsVectorSize();
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = server_hooks_.getIndex(name);
+
+ // Iterate through the callout vector for the hook from start to end,
+ // looking for the first entry where the library index is greater than
+ // the present index.
+ for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
+ i != hook_vector_[hook_index].end(); ++i) {
+ if (i->first > library_index) {
+ // Found an element whose library index number is greater than the
+ // current index, so insert the new element ahead of this one.
+ hook_vector_[hook_index].insert(i, make_pair(library_index,
+ callout));
+ return;
+ }
+ }
+
+ // Reached the end of the vector, so there is no element in the (possibly
+ // empty) set of callouts with a library index greater than the current
+ // library index. Inset the callout at the end of the list.
+ hook_vector_[hook_index].push_back(make_pair(library_index, callout));
+}
+
+// Check if callouts are present for a given hook index.
+bool
+CalloutManager::calloutsPresent(int hook_index) const {
+ // Validate the hook index.
+ if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
+ isc_throw(NoSuchHook, "hook index " << hook_index <<
+ " is not valid for the list of registered hooks");
+ }
+
+ // Valid, so are there any callouts associated with that hook?
+ return (!hook_vector_[hook_index].empty());
+}
+
+bool
+CalloutManager::commandHandlersPresent(const std::string& command_name) const {
+ // Check if the hook point for the specified command exists.
+ int index = ServerHooks::getServerHooks().findIndex(
+ ServerHooks::commandToHookName(command_name));
+ if (index >= 0) {
+ // The hook point exits but it is possible that there are no
+ // callouts/command handlers. This is possible if there was a
+ // hook library supporting this command attached, but it was
+ // later unloaded. The hook points are not deregistered in
+ // this case. Only callouts are deregistered.
+ // Let's check if callouts are present for this hook point.
+ return (calloutsPresent(index));
+ }
+
+ // Hook point not created, so we don't support this command in
+ // any of the hooks libraries.
+ return (false);
+}
+
+// Call all the callouts for a given hook.
+void
+CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
+ // Clear the "skip" flag so we don't carry state from a previous call.
+ // This is done regardless of whether callouts are present to avoid passing
+ // any state from the previous call of callCallouts().
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+
+ // Only initialize and iterate if there are callouts present. This check
+ // also catches the case of an invalid index.
+ if (calloutsPresent(hook_index)) {
+
+ // Set the current hook index. This is used should a callout wish to
+ // determine to what hook it is attached.
+ callout_handle.setCurrentHook(hook_index);
+
+ // This object will be used to measure execution time of each callout
+ // and the total time spent in callouts for this hook point.
+ util::Stopwatch stopwatch;
+
+ // Mark that the callouts begin for the hook.
+ LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_BEGIN)
+ .arg(server_hooks_.getName(callout_handle.getCurrentHook()));
+
+ // Call all the callouts.
+ for (CalloutVector::const_iterator i = hook_vector_[hook_index].begin();
+ i != hook_vector_[hook_index].end(); ++i) {
+ // In case the callout requires access to the context associated
+ // with the library, set the current library index to the index
+ // associated with the library that registered the callout being
+ // called.
+ callout_handle.setCurrentLibrary(i->first);
+
+ // Call the callout
+ try {
+ stopwatch.start();
+ int status = (*i->second)(callout_handle);
+ stopwatch.stop();
+ if (status == 0) {
+ LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_CALLOUT_CALLED)
+ .arg(callout_handle.getCurrentLibrary())
+ .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
+ .arg(PointerConverter(i->second).dlsymPtr())
+ .arg(stopwatch.logFormatLastDuration());
+ } else {
+ LOG_ERROR(callouts_logger, HOOKS_CALLOUT_ERROR)
+ .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
+ .arg(callout_handle.getCurrentLibrary())
+ .arg(PointerConverter(i->second).dlsymPtr())
+ .arg(stopwatch.logFormatLastDuration());
+ }
+ } catch (const std::exception& e) {
+ // If an exception occurred, the stopwatch.stop() hasn't been
+ // called, so we have to call it here.
+ stopwatch.stop();
+ // Any exception, just ones based on isc::Exception
+ LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
+ .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
+ .arg(callout_handle.getCurrentLibrary())
+ .arg(PointerConverter(i->second).dlsymPtr())
+ .arg(e.what())
+ .arg(stopwatch.logFormatLastDuration());
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+ } catch (...) {
+ // If an exception occurred, the stopwatch.stop() hasn't been
+ // called, so we have to call it here.
+ stopwatch.stop();
+ // Any exception, not just ones based on isc::Exception
+ LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
+ .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
+ .arg(callout_handle.getCurrentLibrary())
+ .arg(PointerConverter(i->second).dlsymPtr())
+ .arg("Unspecified exception")
+ .arg(stopwatch.logFormatLastDuration());
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+ }
+ }
+
+ // Mark end of callout execution. Include the total execution
+ // time for callouts.
+ LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_COMPLETE)
+ .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
+ .arg(stopwatch.logFormatTotalDuration());
+
+ // Reset the current hook and library indexes to an invalid value to
+ // catch any programming errors.
+ callout_handle.setCurrentHook(-1);
+ callout_handle.setCurrentLibrary(-1);
+ }
+}
+
+void
+CalloutManager::callCommandHandlers(const std::string& command_name,
+ CalloutHandle& callout_handle) {
+ // Get the index of the hook point for the specified command.
+ // This will throw an exception if the hook point doesn't exist.
+ // The caller should check if the hook point exists by calling
+ // commandHandlersPresent.
+ int index = ServerHooks::getServerHooks().getIndex(ServerHooks::commandToHookName(command_name));
+ // Call the handlers for this command.
+ callCallouts(index, callout_handle);
+}
+
+// Deregister a callout registered by the current library on a particular hook.
+bool
+CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout,
+ int library_index) {
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(library_index);
+
+ // New hooks could have been registered since the manager was constructed.
+ ensureHookLibsVectorSize();
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = server_hooks_.getIndex(name);
+
+ // New hooks can have been registered since the manager was constructed.
+ if (hook_index >= hook_vector_.size()) {
+ return (false);
+ }
+
+ /// Construct a CalloutEntry matching the current library and the callout
+ /// we want to remove.
+ CalloutEntry target(library_index, callout);
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // The next bit is standard STL (see "Item 33" in "Effective STL" by
+ // Scott Meyers).
+ //
+ // remove_if reorders the hook vector so that all items not matching
+ // the predicate are at the start of the vector and returns a pointer
+ // to the next element. (In this case, the predicate is that the item
+ // is equal to the value of the passed callout.) The erase() call
+ // removes everything from that element to the end of the vector, i.e.
+ // all the matching elements.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ [&target] (CalloutEntry x) {
+ return (x == target); }),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ bool removed = initial_size != hook_vector_[hook_index].size();
+ if (removed) {
+ LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_CALLOUT_DEREGISTERED).arg(library_index).arg(name);
+ }
+
+ return (removed);
+}
+
+// Deregister all callouts on a given hook.
+bool
+CalloutManager::deregisterAllCallouts(const std::string& name,
+ int library_index) {
+ // New hooks could have been registered since the manager was constructed.
+ ensureHookLibsVectorSize();
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = server_hooks_.getIndex(name);
+
+ /// Construct a CalloutEntry matching the current library (the callout
+ /// pointer is NULL as we are not checking that).
+ CalloutEntry target(library_index, static_cast<CalloutPtr>(0));
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // Remove all callouts matching this library.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ [&target] (CalloutEntry x) {
+ return (x.first == target.first);
+ }),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ bool removed = initial_size != hook_vector_[hook_index].size();
+ if (removed) {
+ LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name);
+ }
+
+ return (removed);
+}
+
+void
+CalloutManager::registerCommandHook(const std::string& command_name) {
+ // New hooks could have been registered since the manager was constructed.
+ ensureHookLibsVectorSize();
+
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
+ if (hook_index < 0) {
+ // Hook for this command doesn't exist. Let's create one.
+ hooks.registerHook(ServerHooks::commandToHookName(command_name));
+ // Callout Manager's vector of hooks have to be resized to hold the
+ // information about callouts for this new hook point. This should
+ // add new element at the end of the hook_vector_. The index of this
+ // element will match the index of the hook point in the ServerHooks
+ // because ServerHooks allocates indexes incrementally.
+ hook_vector_.resize(server_hooks_.getCount());
+ }
+}
+
+void
+CalloutManager::ensureHookLibsVectorSize() {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ if (hooks.getCount() > hook_vector_.size()) {
+ // Uh oh, there are more hook points that our vector allows.
+ hook_vector_.resize(hooks.getCount());
+ }
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h
new file mode 100644
index 0000000..3ee1efd
--- /dev/null
+++ b/src/lib/hooks/callout_manager.h
@@ -0,0 +1,438 @@
+// Copyright (C) 2013-2021 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 CALLOUT_MANAGER_H
+#define CALLOUT_MANAGER_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <climits>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+/// @brief No such library
+///
+/// Thrown if an attempt is made to set the current library index to a value
+/// that is invalid for the number of loaded libraries.
+class NoSuchLibrary : public Exception {
+public:
+ NoSuchLibrary(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Callout Manager
+///
+/// This class manages the registration, deregistration and execution of the
+/// library callouts. It is part of the hooks framework used by the Kea
+/// server, and is not for use by user-written code in a hooks library.
+///
+/// In operation, the class needs to know two items of data:
+///
+/// - The list of server hooks, which is used in two ways. Firstly, when a
+/// library registers or deregisters a hook, it does so by name: the
+/// @ref isc::hooks::ServerHooks object supplies the names of registered
+/// hooks. Secondly, when the callouts associated with a hook are called by
+/// the server, the server supplies the index of the relevant hook: this is
+/// validated by reference to the list of hooks.
+///
+/// - The number of loaded libraries. Each callout registered by a user
+/// library is associated with that library, the callout manager storing both
+/// a pointer to the callout and the index of the library in the list of
+/// loaded libraries. When calling a callout, the callout manager maintains
+/// the idea of a "current library index": this is used to access the context
+/// associated with the library.
+///
+/// These two items of data are supplied when an object of this class is
+/// constructed. The latter (number of libraries) can be updated after the
+/// class is constructed. (Such an update is used during library loading where
+/// the CalloutManager has to be constructed before the libraries are loaded,
+/// but one of the libraries subsequently fails to load.)
+///
+/// The library index is important because it determines in what order callouts
+/// on a particular hook are called. For each hook, the CalloutManager
+/// maintains a vector of callouts ordered by library index. When a callout
+/// is added to the list, it is added at the end of the callouts associated
+/// with the current library. To clarify this further, suppose that three
+/// libraries are loaded, A (assigned an index 1), B (assigned an index 2) and
+/// C (assigned an index 3). Suppose A registers two callouts on a given hook,
+/// A1 and A2 (in that order) B registers B1 and B2 (in that order) and C
+/// registers C1 and C2 (in that order). Internally, the callouts are stored
+/// in the order A1, A2, B1, B2, C1, and C2: this is also the order in which
+/// they are called.
+///
+/// Indexes range between 1 and n (where n is the number of the libraries
+/// loaded) and are assigned to libraries based on the order the libraries
+/// presented to the hooks framework for loading (something that occurs in the
+/// isc::hooks::HooksManager) class. However, two other indexes are recognized,
+/// 0 and INT_MAX. These are used when the server itself registers callouts -
+/// the server is able to register callouts that get called before any
+/// user-library callouts, and ones that get called after user-library callouts.
+/// In other words, assuming the callouts on a hook are A1, A2, B1, B2, B3, C2,
+/// C2 as before, and that the server registers S1 (to run before the
+/// user-registered callouts) and S2 (to run after them), the callouts are
+/// stored (and executed) in the order S1, A1, A2, B1, B2, B3, C2, C2, S2. In
+/// summary, the recognized index values are:
+///
+/// - < 0: invalid.
+/// - 0: used for server-registered callouts that are called before
+/// user-registered callouts.
+/// - 1 - n: callouts from user libraries.
+/// - INT_MAX: used for server-registered callouts called after
+/// user-registered callouts.
+///
+/// Since Kea 1.3.0 release hook libraries can register callouts as control
+/// command handlers. Such handlers are associated with dynamically created
+/// hook points which names are created after command names. For example,
+/// if a command name is 'foo-bar', the name of the hook point to which
+/// callouts/command handlers are registered is '$foo_bar'. Prefixing the
+/// hook point name with the dollar sign eliminates potential conflicts
+/// between hook points dedicated to commands handling and other (fixed)
+/// hook points.
+///
+/// Prefixing hook names for command handlers with a dollar sign precludes
+/// auto registration of command handlers, i.e. hooks framework is unable
+/// to match hook points with names of functions implementing command
+/// handlers, because the dollar sign is not legal in C++ function names.
+/// This is intended because we want hook libraries to explicitly register
+/// commands handlers for supported commands and not rely on Kea to register
+/// hook points for them. Should we find use cases for auto registration of
+/// command handlers, we may modify the
+/// @ref ServerHooks::commandToHookName to use an encoding of hook
+/// point names for command handlers that would only contain characters
+/// allowed in function names.
+///
+/// The @ref CalloutManager::registerCommandHook has been added to allow for
+/// dynamically creating hook points for which command handlers are registered.
+/// This method is called from the @ref LibraryHandle::registerCommandCallout
+/// as a result of registering the command handlers by the hook library in
+/// its @c load() function. If the hook point for the given command already
+/// exists, this function doesn't do anything. The
+/// @ref LibraryHandle::registerCommandCallout can install callouts on this
+/// hook point.
+///
+/// Note that the callout functions do not access the CalloutManager: instead,
+/// they use a LibraryHandle object. This contains an internal pointer to
+/// the CalloutManager, but provides a restricted interface. In that way,
+/// callouts are unable to affect callouts supplied by other libraries.
+
+class CalloutManager {
+private:
+
+ // Private typedefs
+
+ /// Element in the vector of callouts. The elements in the pair are the
+ /// index of the library from which this callout was registered, and a#
+ /// pointer to the callout itself.
+ typedef std::pair<int, CalloutPtr> CalloutEntry;
+
+ /// An element in the hook vector. Each element is a vector of callouts
+ /// associated with a given hook.
+ typedef std::vector<CalloutEntry> CalloutVector;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initializes member variables, in particular sizing the hook vector
+ /// (the vector of callout vectors) to the appropriate size.
+ ///
+ /// @param num_libraries Number of loaded libraries.
+ ///
+ /// @throw isc::BadValue if the number of libraries is less than 0,
+ CalloutManager(int num_libraries = 0);
+
+ /// @brief Register a callout on a hook for the current library
+ ///
+ /// Registers a callout function for the current library with a given hook.
+ /// The callout is added to the end of the callouts for this library that
+ /// are associated with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ /// @param library_index Library index used for registering the callout.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognized.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name,
+ CalloutPtr callout,
+ int library_index);
+
+ /// @brief De-Register a callout on a hook for the current library
+ ///
+ /// Searches through the functions registered by the current library
+ /// with the named hook and removes all entries matching the
+ /// callout.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ /// @param library_index Library index used for deregistering the callout.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognized.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name,
+ CalloutPtr callout,
+ int library_index);
+
+ /// @brief Removes all callouts on a hook for the current library
+ ///
+ /// Removes all callouts associated with a given hook that were registered
+ /// by the current library.
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ /// @param library_index Library index used for deregistering all callouts.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognized.
+ bool deregisterAllCallouts(const std::string& name, int library_index);
+
+ /// @brief Checks if callouts are present on a hook
+ ///
+ /// Checks all loaded libraries and returns true if at least one callout
+ /// has been registered by any of them for the given hook.
+ ///
+ /// @param hook_index Hook index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ ///
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ bool calloutsPresent(int hook_index) const;
+
+ /// @brief Checks if control command handlers are present for the
+ /// specified command.
+ ///
+ /// @param command_name Command name for which handlers' presence should
+ /// be checked.
+ ///
+ /// @return true if there is a hook point associated with the specified
+ /// command and callouts/command handlers are installed for this hook
+ /// point, false otherwise.
+ bool commandHandlersPresent(const std::string& command_name) const;
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// Iterates through the library handles and calls the callouts associated
+ /// with the given hook index.
+ ///
+ /// @note This method invalidates the current library index set with
+ /// setLibraryIndex().
+ ///
+ /// @param hook_index Index of the hook to call.
+ /// @param callout_handle Reference to the CalloutHandle object for the
+ /// current object being processed.
+ void callCallouts(int hook_index, CalloutHandle& callout_handle);
+
+ /// @brief Calls the callouts/command handlers for a given command name.
+ ///
+ /// Iterates through the library handles and calls the command handlers
+ /// associated with the given command. It expects that the hook point
+ /// for this command exists (with a name being a command_name prefixed
+ /// with a dollar sign and with hyphens replaced with underscores).
+ ///
+ /// @param command_name Command name for which handlers should be called.
+ /// @param callout_handle Reference to the CalloutHandle object for the
+ /// current object being processed.
+ ///
+ /// @throw NoSuchHook if the hook point for the specified command does
+ /// not exist.
+ void callCommandHandlers(const std::string& command_name,
+ CalloutHandle& callout_handle);
+
+ /// @brief Registers a hook point for the specified command name.
+ ///
+ /// If the hook point for such command already exists, this function
+ /// doesn't do anything. The registered hook point name is created
+ /// after command_name by prefixing it with a dollar sign and replacing
+ /// all hyphens with underscores, e.g. for the 'foo-bar' command the
+ /// following hook point name will be generated: '$foo_bar'.
+ ///
+ /// @param command_name Command name for which the hook point should be
+ /// registered.
+ void registerCommandHook(const std::string& command_name);
+
+ /// @brief Get number of libraries
+ ///
+ /// Returns the number of libraries that this CalloutManager is expected
+ /// to serve. This is the number passed to its constructor.
+ ///
+ /// @return Number of libraries served by this CalloutManager.
+ int getNumLibraries() const {
+ return (num_libraries_);
+ }
+
+ /// @brief Get current library index
+ ///
+ /// Returns the index of the "current" library. This the index associated
+ /// with the currently executing callout when callCallouts is executing.
+ /// When callCallouts() is not executing (as is the case when the load()
+ /// function in a user-library is called during the library load process),
+ /// the index can be set by setLibraryIndex().
+ ///
+ /// @note The value set by this method is lost after a call to
+ /// callCallouts.
+ ///
+ /// @return Current library index.
+ int getLibraryIndex() const {
+ return (current_library_);
+ }
+
+ /// @brief Set current library index
+ ///
+ /// Sets the current library index. This has the following valid values:
+ ///
+ /// - -1: invalidate current index.
+ /// - 0: pre-user library callout.
+ /// - 1 - numlib: user-library callout (where "numlib" is the number of
+ /// libraries loaded in the system, this figure being passed to this
+ /// object at construction time).
+ /// - INT_MAX: post-user library callout.
+ ///
+ /// @param library_index New library index.
+ ///
+ /// @throw NoSuchLibrary if the index is not valid.
+ void setLibraryIndex(int library_index) {
+ checkLibraryIndex(library_index);
+ current_library_ = library_index;
+ }
+
+ /// @defgroup calloutManagerLibraryHandles Callout manager library handles
+ ///
+ /// The CalloutManager offers three library handles:
+ ///
+ /// - a "standard" one, used to register and deregister callouts for
+ /// the library index that is marked as current in the CalloutManager.
+ /// When a callout is called, it is passed this one.
+ /// - a pre-library callout handle, used by the server to register
+ // callouts to run prior to user-library callouts.
+ /// - a post-library callout handle, used by the server to register
+ /// callouts to run after the user-library callouts.
+ //@{
+
+ /// @brief Return library handle
+ ///
+ /// The library handle is available to the user callout via the callout
+ /// handle object. It provides a cut-down view of the CalloutManager,
+ /// allowing the callout to register and deregister callouts in the
+ /// library of which it is part, whilst denying access to anything that
+ /// may affect other libraries.
+ ///
+ /// @return Reference to library handle for this manager
+ LibraryHandle& getLibraryHandle() {
+ return (library_handle_);
+ }
+
+ /// @brief Return pre-user callouts library handle
+ ///
+ /// The LibraryHandle to affect callouts that will run before the
+ /// user-library callouts.
+ ///
+ /// @return Reference to pre-user library handle for this manager
+ LibraryHandle& getPreLibraryHandle() {
+ return (pre_library_handle_);
+ }
+
+ /// @brief Return post-user callouts library handle
+ ///
+ /// The LibraryHandle to affect callouts that will run before the
+ /// user-library callouts.
+ ///
+ /// @return Reference to post-user library handle for this manager
+ LibraryHandle& getPostLibraryHandle() {
+ return (post_library_handle_);
+ }
+
+ //@}
+
+ /// @brief Return number of currently available hooks
+ size_t getHookLibsVectorSize() const {
+ return (hook_vector_.size());
+ }
+
+private:
+
+ /// @brief This method checks whether the hook_vector_ size is sufficient
+ /// and extends it if necessary.
+ ///
+ /// The problem is as follows: some hooks are initialized statically
+ /// from global static objects. ServerHooks object creates hooks_ collection
+ /// and CalloutManager creates its own hook_vector_ and both are initialized
+ /// to the same size. All works well so far. However, if some code at a
+ /// later time (e.g. a hook library) registers new hook point, then
+ /// ServerHooks::registerHook() will extend its hooks_ collection, but
+ /// the CalloutManager will keep the old hook_vector_ that is too small by
+ /// one. Now when the library is unloaded, deregisterAllCallouts will
+ /// go through all hook points and will eventually hit the one that
+ /// will return index greater than the hook_vector_ size.
+ ///
+ /// To solve this problem, ensureVectorSize was implemented. It should
+ /// be called (presumably from ServerHooks) every time a new hook point
+ /// is registered. It checks whether the vector size is sufficient and
+ /// extends it if necessary. It is safe to call it multiple times. It
+ /// may grow the vector size, but will never shrink it.
+ void ensureHookLibsVectorSize();
+
+ /// @brief Check library index
+ ///
+ /// Ensures that the current library index is valid. This is called by
+ /// the hook registration functions.
+ ///
+ /// @param library_index Value to check for validity as a library index.
+ /// Valid values are 0 -> numlib + 1 and -1: see @ref setLibraryIndex
+ /// for the meaning of the various values.
+ ///
+ /// @throw NoSuchLibrary Library index is not valid.
+ void checkLibraryIndex(int library_index) const;
+
+ // Member variables
+
+ /// Reference to the singleton ServerHooks object. See the
+ /// @ref hooksmgMaintenanceGuide for information as to why the class holds
+ /// a reference instead of accessing the singleton within the code.
+ ServerHooks& server_hooks_;
+
+ /// Current library index. When a call is made to any of the callout
+ /// registration methods, this variable indicates the index of the user
+ /// library that should be associated with the call.
+ int current_library_;
+
+ /// Vector of callout vectors. There is one entry in this outer vector for
+ /// each hook. Each element is itself a vector, with one entry for each
+ /// callout registered for that hook.
+ std::vector<CalloutVector> hook_vector_;
+
+ /// LibraryHandle object user by the callout to access the callout
+ /// registration methods on this CalloutManager object. The object is set
+ /// such that the index of the library associated with any operation is
+ /// whatever is currently set in the CalloutManager.
+ LibraryHandle library_handle_;
+
+ /// LibraryHandle for callouts to be registered as being called before
+ /// the user-registered callouts.
+ LibraryHandle pre_library_handle_;
+
+ /// LibraryHandle for callouts to be registered as being called after
+ /// the user-registered callouts.
+ LibraryHandle post_library_handle_;
+
+ /// Number of libraries.
+ int num_libraries_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // CALLOUT_MANAGER_H
diff --git a/src/lib/hooks/hooks.h b/src/lib/hooks/hooks.h
new file mode 100644
index 0000000..e92cee2
--- /dev/null
+++ b/src/lib/hooks/hooks.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2013-2023 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 HOOKS_H
+#define HOOKS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+
+namespace {
+
+// Version 20401 of the hooks framework, set for Kea 2.4.1
+const int KEA_HOOKS_VERSION = 20401;
+
+// Names of the framework functions.
+const char* const LOAD_FUNCTION_NAME = "load";
+const char* const UNLOAD_FUNCTION_NAME = "unload";
+const char* const VERSION_FUNCTION_NAME = "version";
+const char* const MULTI_THREADING_COMPATIBLE_FUNCTION_NAME =
+ "multi_threading_compatible";
+
+// Typedefs for pointers to the framework functions.
+typedef int (*version_function_ptr)();
+typedef int (*load_function_ptr)(isc::hooks::LibraryHandle&);
+typedef int (*unload_function_ptr)();
+typedef int (*multi_threading_compatible_function_ptr)();
+
+} // Anonymous namespace
+
+namespace isc {
+namespace hooks {
+
+/// @brief User-Library Initialization for Statically-Linked Kea
+///
+/// If Kea is statically-linked, a user-created hooks library will not be
+/// able to access symbols in it. In particular, it will not be able to access
+/// singleton objects.
+///
+/// The hooks framework handles some of this. For example, although there is
+/// a singleton ServerHooks object, hooks framework objects store a reference
+/// to it when they are created. When the user library needs to register a
+/// callout (which requires access to the ServerHooks information), it accesses
+/// the ServerHooks object through a pointer passed from the Kea image.
+///
+/// The logging framework is more problematical. Here the code is partly
+/// statically linked (the Kea logging library) and partly shared (the
+/// log4cplus). The state of the former is not accessible to the user library,
+/// but the state of the latter is. So within the user library, we need to
+/// initialize the Kea logging library but not initialize the log4cplus
+/// code. Some of the initialization is done when the library is loaded, but
+/// other parts are done at run-time.
+///
+/// This function - to be called by the user library code in its load() function
+/// when running against a statically linked Kea - initializes the Kea
+/// logging library. In particular, it loads the message dictionary with the
+/// text of the Kea messages.
+///
+/// @note This means that the virtual address space is loaded with two copies
+/// of the message dictionary. Depending on how the user libraries are linked,
+/// loading multiple user libraries may involve loading one message dictionary
+/// per library.
+
+void hooksStaticLinkInit();
+
+} // namespace hooks
+} // namespace isc
+
+#endif // HOOKS_H
diff --git a/src/lib/hooks/hooks_component_developer.dox b/src/lib/hooks/hooks_component_developer.dox
new file mode 100644
index 0000000..baeb5ef
--- /dev/null
+++ b/src/lib/hooks/hooks_component_developer.dox
@@ -0,0 +1,520 @@
+// Copyright (C) 2013-2020 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/.
+
+/**
+@page hooksComponentDeveloperGuide Guide to Hooks for the Kea Component Developer
+
+@section hooksComponentIntroduction Introduction
+
+The hooks framework is a Kea system that simplifies the way that
+users can write code to modify the behavior of Kea. Instead of
+altering the Kea source code, they write functions that are compiled
+and linked into one or more dynamic shared objects, called here (for
+historical reasons), shared libraries. The library is specified in the Kea
+configuration and at run time Kea dynamically loads the library
+into its address space. At various points in the processing, the component
+"calls out" to functions in the library, passing to them the data is it
+currently working on. They can examine and modify the data as required.
+
+This guide is aimed at Kea developers who want to write or modify a
+Kea component to use hooks. It shows how the component should be written
+to load a shared library at run-time and how to call functions in it.
+
+For information about writing a hooks library containing functions called by Kea
+during its execution, see the document @ref hooksdgDevelopersGuide.
+
+
+@subsection hooksComponentTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Component - a Kea process, e.g. the DHCPv4 or DHCPv6 server.
+
+- Hook/Hook Point - used interchangeably, this is a point in the code at
+which a call to user-written functions is made. Each hook has a name and
+each hook can have any number (including 0) of user-written functions
+attached to it.
+
+- Callout - a user-written function called by the component at a hook
+point. This is so-named because the component "calls out" to the library
+to execute a user-written function.
+
+- User code/user library - non-Kea code that is compiled into a
+shared library and loaded by Kea into its address space. Multiple
+user libraries can be loaded at the same time, each containing callouts for
+the same hooks. The hooks framework calls these libraries one after the
+other. (See the document @ref hooksdgDevelopersGuide for more details.)
+
+
+@subsection hooksComponentLanguages Languages
+
+The core of Kea is written in C++ with some remaining legacy parts in Python.
+While it is the intention to provide the hooks framework for all languages,
+the initial version is for C++. All examples in this guide are in that language.
+
+
+@section hooksComponentBasicIdeas Basic Ideas
+
+From the point of view of the component author, the basic ideas of the hooks
+framework are quite simple:
+
+- The location of hook points in the code need to be determined.
+
+- Name the hook points and register them.
+
+- At each hook point, the component needs to complete the following steps to
+ execute callouts registered by the user-library:
+ -# copy data into the object used to pass information to the callout.
+ -# call the callout.
+ -# copy data back from the object used to exchange information.
+ -# take action based on information returned.
+
+Of course, to set up the system the libraries need to be loaded in the first
+place. The component also needs to:
+
+- Define the configuration item that specifies the user libraries for this
+component.
+
+- Handle configuration changes and load/unload the user libraries.
+
+The following sections will describe these tasks in more detail.
+
+
+@section hooksComponentDefinition Determining the Hook Points
+
+Before any other action takes place, the location of the hook points
+in the code need to be determined. This, of course, depends on the
+component but, as a general guideline, hook locations should be located
+where a callout is able to obtain useful information from Kea and/or
+affect processing. Typically this means at the start or end of a major
+step in the processing of a request, at a point where either useful
+information can be passed to a callout and/or the callout can affect
+the processing of the component. The latter is achieved in either or both
+of the following ways:
+
+- Setting the nest step status. This is an enum that the callout can set
+ and is a quick way of passing information back to the component. It is used
+ to indicate that the component should perform certain actions. Currently
+ there are three statuses defined: CONTINUE (this is the default, the server
+ is expected to continue as usual), SKIP (the server is expected to skip the
+ next processing step, but otherwise continue as usual) and DROP (the server
+ is expected to drop the packet or request completely. The exact action is up
+ to the component.
+
+- Modifying data passed to it. The component should be prepared to continue
+ processing with the data returned by the callout. It is up to the component
+ author whether the data is validated before being used, but doing so will
+ have performance implications.
+
+
+@section hooksComponentRegistration Naming and Registering the Hooks Points
+
+Once the location of the hook point has been determined, it should be
+given a name. This name should be unique amongst all hook points and is
+subject to certain restrictions (see below).
+
+Before the callouts at any hook point are called and any user libraries
+loaded - so typically during component initialization - the component must
+register the names of all the hooks. The registration is done using
+the static method @c isc::hooks::HooksManager::registerHook():
+
+@code
+
+#include <hooks/hooks_manager.h>
+ :
+ int example_index = HooksManager::registerHook("lease_allocate");
+@endcode
+
+The name of the hook is passed as the sole argument to the @c registerHook()
+method. The value returned is the index of that hook point and should
+be retained - it is needed to call the callouts attached to that hook.
+
+Note that a hook only needs to be registered once. There is no mechanism for
+unregistering a hook and there is no need to do so.
+
+
+@subsection hooksComponentAutomaticRegistration Automatic Registration of Hooks
+
+In some components, it may be convenient to set up a single initialization
+function that registers all hooks. For others, it may be more convenient
+for each module within the component to perform its own initialization.
+Since the @c isc::hooks::HooksManager object is a singleton and is created when first
+accessed, a useful trick is to automatically register the hooks when
+the module is loaded.
+
+This technique involves declaring an object outside of any execution
+unit in the module. When the module is loaded, the object's constructor
+is run. By placing the hook registration calls in the constructor,
+the hooks in the module are defined at load time, before any function in
+the module is run. The code for such an initialization sequence would
+be similar to:
+
+@code
+#include <hooks/hooks_manager.h>
+
+namespace {
+
+// Declare structure to perform initialization and store the hook indexes.
+//
+struct MyHooks {
+ int pkt_rcvd; // Index of "packet received" hook
+ int pkt_sent; // Index of "packet sent" hook
+
+ // Constructor
+ MyHooks() {
+ pkt_rcvd = HooksManager::registerHook("pkt_rcvd");
+ pkt_sent = HooksManager::registerHook("pkt_sent");
+ }
+};
+
+// Declare a "MyHooks" object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+MyHooks my_hooks;
+
+} // Anonymous namespace
+
+void Someclass::someFunction() {
+ :
+ // Check if any callouts are defined on the pkt_rcvd hook.
+ if (HooksManager::calloutPresent(my_hooks.pkt_rcvd)) {
+ :
+ }
+ :
+}
+@endcode
+
+
+@subsection hooksComponentHookNames Hook Names
+
+Hook names are strings and in principle, any string can be used as the
+name of a hook, even one containing spaces and non-printable characters.
+However, the following guidelines should be observed:
+
+- The names <b>context_create</b> and <b>context_destroy</b> are reserved to
+the hooks system and are automatically registered: an attempt to register
+one of these will lead to a @c isc::hooks::DuplicateHook exception being thrown.
+
+- The hook name should be a valid "C" function name. If a user gives a
+callout the same name as one of the hooks, the hooks framework will
+automatically load that callout and attach it to the hook: the user does not
+have to explicitly register it.
+
+- The hook name should not conflict with the name of a function in any of
+the system libraries (e.g. naming a hook "sqrt" could lead to the
+square-root function in the system's maths library being attached to the hook
+as a callout).
+
+- Although hook names can be in any case (including mixed case), the Kea
+convention is that they are lower-case.
+
+
+@section hooksComponentCallingCallouts Calling Callouts on a Hook
+
+
+@subsection hooksComponentArgument The Callout Handle
+
+Before describing how to call user code at a hook point, we must first consider
+how to pass data to it.
+
+Each user callout has the signature:
+@code
+int callout_name(isc::hooks::CalloutHandle& handle);
+@endcode
+
+The @c isc::hooks::CalloutHandle object is the object used to pass data to
+and from the callout. This holds the data as a set of name/value pairs,
+each pair being considered an argument to the callout. If there are
+multiple callouts attached to a hook, the @c CalloutHandle is passed to
+each in turn. Should a callout modify an argument, the updated data is
+passed subsequent callouts (each of which could also modify it) before
+being returned to the component.
+
+Two methods are provided to get and set the arguments passed to
+the callout called (naturally enough) @c getArgument and @c setArgument.
+Their usage is illustrated by the following code snippets.
+
+@code
+ int count = 10;
+ boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+ // Assume that "handle_ptr" has been created and is a pointer to a
+ // CalloutHandle.
+ handle_ptr->setArgument("data_count", count);
+ handle_ptr->setArgument("inpacket", pktptr);
+
+ // Call the hook code. lease_assigned_index is the value returned from
+ // HooksManager::registerHook() when the hook was registered.
+ HooksManager::callCallouts(lease_assigned_index, *handle_ptr);
+
+ // Retrieve the modified values
+ handle_ptr->getArgument("data_count", count);
+ handle_ptr->getArgument("inpacket", pktptr);
+@endcode
+
+As can be seen @c getArgument is used to retrieve data from the
+@c CalloutHandle, and @c setArgument used to put data into it. If a callout
+wishes to alter data and pass it back to the component, it should retrieve
+the data with getArgument, modify it, and call setArgument to send
+it back.
+
+There are a couple points to be aware of:
+
+- The data type of the variable in the call to @c getArgument must
+match the data type of the variable passed to the corresponding
+@c setArgument <B>exactly</B>: using what would normally be considered
+to be a "compatible" type is not enough. For example, if the callout
+passed an argument back to the component as an @c int and the component
+attempted to retrieve it as a @c long, an exception would be thrown even
+though any value that can be stored in an @c int will fit into a @c long.
+This restriction also applies the "const" attribute but only as applied to
+data pointed to by pointers, e.g. if an argument is defined as a @c char*,
+an exception will be thrown if an attempt is made to retrieve it into
+a variable of type @c const @c char*. (However, if an argument is set as a
+@c const @c int, it can be retrieved into an @c int.) The documentation of
+a hook point should detail the exact data type of each argument.
+
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the component even if the callout makes no call to setArgument.
+This can be avoided by passing a pointer to a "const" object.
+
+
+@subsection hooksComponentSkipFlag The Skip Flag (obsolete)
+
+
+@subsection hooksComponentNextStep The next step status
+
+Although information is passed back to the component from callouts through
+@c CalloutHandle arguments, a common action for callouts is to inform the component
+that its flow of control should be altered. For example:
+
+- In the DHCP servers, there is a hook at the point at which a lease is
+ about to be assigned. Callouts attached to this hooks may handle the
+ lease assignment in special cases, in which case they set the next step
+ status to SKIP to indicate that the server should not perform lease assignment
+ in this case.
+- A server may define a hook just after a packet is received. A callout
+ attached to the hook might inspect the source address and compare it
+ against a blacklist. If the address is on the list, the callout could set
+ the DROP flag to indicate to the server that the packet should be dropped.
+
+For ease of processing, the @c CalloutHandle contains
+two methods, @c isc::hooks::CalloutHandle::getStatus() and
+@c isc::hooks::CalloutHandle::setStatus(). It is only meaningful for the
+component to use the "get" method. The next step status is cleared (set to
+the default value of CONTINUE) by the hooks framework when the component
+requests that callouts be executed, so any
+value set by the component is lost. Callouts can both inspect the status (it
+might have been set by callouts earlier in the callout list for the hook)
+and set it. Note that the setting of the status by a callout does not
+prevent callouts later in the list from being called: the next step status is
+just an enum value - the only significance comes from its interpretation
+by the component.
+
+An example of use could be:
+@code
+// Set up arguments for DHCP lease assignment.
+handle->setArgument("query", query);
+handle->setArgument("response", response);
+HooksManager::callCallouts(lease_hook_index, *handle_ptr);
+if (handle_ptr->getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
+ // Skip flag not set, do the address allocation
+ :
+}
+@endcode
+
+
+@subsection hooksComponentGettingHandle Getting the Callout Handle
+
+The @c CalloutHandle object is linked to the loaded libraries
+for lifetime reasons (described below). Components
+should retrieve a @c isc::hooks::CalloutHandle using
+@c isc::hooks::HooksManager::createCalloutHandle():
+@code
+ CalloutHandlePtr handle_ptr = HooksManager::createCalloutHandle();
+@endcode
+(@c isc::hooks::CalloutHandlePtr is a typedef for a Boost shared pointer to a
+CalloutHandle.) The CalloutHandle so retrieved may be used for as
+long as the libraries are loaded.
+
+The handle is deleted by resetting the pointer:
+@code
+ handle_ptr.reset();
+@endcode
+... or by letting the handle pointer go out of scope. The actual deletion
+occurs when the CallHandle's reference count goes to zero. (The
+current version of the hooks framework does not maintain any other
+pointer to the returned CalloutHandle, so it gets destroyed when the
+shared pointer to it is cleared or destroyed. However, this may change
+in a future version.)
+
+When the handle is not created locally it is not destroyed so it can
+keep ownership on arguments. In such case the code must call @c
+isc::hooks::CalloutHandle::deleteAllArguments or simply use the RAII
+helper @c isc::hooks::ScopedCalloutHandleState as in:
+@code
+ CalloutHandlePtr handle_ptr = getCalloutHandle(query);
+ ScopedCalloutHandleState state(handle_ptr);
+@endcode
+
+@subsection hooksComponentCallingCallout Calling the Callout
+
+Calling the callout is a simple matter of executing the
+@c isc::hooks::HooksManager::callCallouts() method for the hook index in
+question. For example, with the hook index "pkt_sent" defined as above,
+the hook can be executed by:
+@code
+ HooksManager::callCallouts(pkt_sent, *handle_ptr);
+@endcode
+... where "*handle_ptr" is a reference (note: not a pointer) to the
+@c isc::hooks::CalloutHandle object holding the arguments. No status code
+is returned. If a component needs to get data returned (other than that
+provided by the next step status), it should define an argument through which
+the callout can do so.
+
+@subsubsection hooksComponentConditionalCallout Conditionally Calling Hook Callouts
+
+Most hooks in a component will not have callouts attached to them. To
+avoid the overhead of setting up arguments in the @c CalloutHandle, a
+component can check for callouts before doing that processing using
+@c isc::hooks::HooksManager::calloutsPresent(). Taking the index of a
+hook as its sole argument, the function returns true if there are any
+callouts attached to the hook and false otherwise.
+
+With this check, the code in the component for calling a hook would look
+something like:
+@code
+if (HooksManager::calloutsPresent(lease_hook_index)) {
+ // Set up arguments for lease assignment
+ handle->setArgument("query", query);
+ handle->setArgument("response", response);
+ HooksManager::callCallouts(lease_hook_index, *handle);
+ if (handle->getStatus() != CalloutHandle::NEXT_STEP_DROP) {
+ // Next step allows us to continue, do the address allocation
+ :
+ }
+}
+@endcode
+
+@section hooksComponentLoadLibraries Loading the User Libraries
+
+Once hooks are defined, all the hooks code described above will
+work, even if no libraries are loaded (and even if the library
+loading method is not called). The @c CalloutHandle returned by
+@c isc::hooks::HooksManager::createCalloutHandle() will be valid,
+@c isc::hooks::HooksManager::calloutsPresent() will return false for every
+index, and @c isc::hooks::HooksManager::callCallouts() will be a no-op.
+
+However, if user libraries are specified in the Kea configuration,
+the component should load them. (Note the term "libraries": the hooks
+framework allows multiple user libraries to be loaded.) This should take
+place after the component's configuration has been read, and is achieved
+by the @c isc::hooks::HooksManager::loadLibraries() method. The method is
+passed a vector of strings, each giving the full file specification of
+a user library:
+@code
+ std::vector<std::string> libraries = ... // Get array of libraries
+ bool success = HooksManager::loadLibraries(libraries);
+@endcode
+@c loadLibraries() returns a boolean status which is true if all libraries
+loaded successfully or false if one or more failed to load. Appropriate
+error messages will have been logged in the latter case, the status
+being more to allow the developer to decide whether the execution
+should proceed in such circumstances.
+
+Before @c loadLibraries() can be called a second or subsequent time
+(as a result of a reconfiguration), all existing libraries must be
+successfully unloaded. If a library stays in memory from a programming
+bug in Kea (for instance when no libraries were loaded) or in a
+library (@ref hooksMemoryManagement) @c loadLibraries() throws a not
+recoverable error.
+
+Unloading is done in two phases since Kea version 1.7.10:
+
+- call to @c isc::hooks::HooksManager::prepareUnloadLibraries() which
+calls all unload() entry points and deregisters callout points.
+
+- call to @c isc::hooks::HooksManager::unloadLibraries() even when
+the prepare failed.
+
+If a failure of @c unloadLibraries() is ignored any call to @c loadLibraries()
+will throw.
+
+
+@subsection hooksComponentUnloadIssues Unload and Reload Issues
+
+Unloading a shared library works by unmapping the part of the process's
+virtual address space in which the library lies. This may lead to
+problems if there are still references to that address space elsewhere
+in the process.
+
+In many operating systems, heap storage allowed by a shared library will
+lie in the virtual address allocated to the library. This has implications
+in the hooks framework because:
+
+- Argument information stored in a @c CalloutHandle by a callout in a library
+may lie in the library's address space.
+- Data modified in objects passed as arguments may lie in the address
+space. For example, it is common for a DHCP callout to add "options"
+to a packet: the memory allocated for those options will most likely
+lie in library address space.
+
+The problem really arises because of the extensive use by Kea of boost
+smart pointers. When the pointer is destroyed, the pointed-to memory is
+deallocated. If the pointer points to address space that is unmapped because
+a library has been unloaded, the deletion causes a segmentation fault.
+
+The hooks framework addresses the issue for CalloutHandles by keeping in
+that object a shared pointer to the object controlling library unloading.
+Although a library can be unloaded at any time, it is only when all
+CalloutHandles that could possibly reference address space in the library
+have been deleted that the library will actually be unloaded and the
+address space unmapped.
+
+The hooks framework cannot solve the second issue as the objects in
+question are under control of the component. It is up to the component
+developer to ensure that all such objects have been destroyed before
+libraries are reloaded. In extreme cases this may mean the component
+suspending all processing of incoming requests until all currently
+executing requests have completed and data object destroyed, reloading
+the libraries, then resuming processing.
+
+Since Kea 1.7.10 the unload() entry point is called as the first phase
+of unloading. This gives more chance to hooks writer to perform
+necessary cleanup actions so the second phase, memory unmapping
+can safely happen. The @c isc::hooks::unloadLibraries() function
+was updated too to return false when at least one active callout
+handle remained.
+
+
+@section hooksComponentCallouts Component-Defined Callouts
+
+Previous sections have discussed callout registration by user libraries.
+It is possible for a component to register its own functions (i.e. within
+its own address space) as hook callouts. These functions are called
+in exactly the same way as user callouts, being passed their arguments
+though a CalloutHandle object. (Guidelines for writing callouts can be
+found in @ref hooksdgDevelopersGuide.)
+
+A component can associate with a hook callouts that run either before
+user-registered callouts or after them. Registration is done via a
+@c isc::hooks::LibraryHandle object, a reference to one being obtained
+through the methods @c isc::hooks::HooksManager::preCalloutLibraryHandle()
+(for a handle to register callouts to run before the user library
+callouts) or @c isc::hooks::HooksManager::postCalloutLibraryHandle() (for
+a handle to register callouts to run after the user callouts). Use of
+the @c LibraryHandle to register and deregister callouts is described in
+@ref hooksdgLibraryHandle.
+
+Finally, it should be noted that callouts registered in this way only
+remain registered until the next call to @c isc::hooks::loadLibraries().
+It is up to the component to re-register the callouts after this
+method has been called.
+
+*/
diff --git a/src/lib/hooks/hooks_config.cc b/src/lib/hooks/hooks_config.cc
new file mode 100644
index 0000000..e8e0f05
--- /dev/null
+++ b/src/lib/hooks/hooks_config.cc
@@ -0,0 +1,129 @@
+// Copyright (C) 2017-2023 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 <hooks/hooks_config.h>
+#include <hooks/hooks_manager.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+
+namespace isc {
+namespace hooks {
+
+void
+HooksConfig::verifyLibraries(const Element::Position& position,
+ bool multi_threading_enabled) const {
+ // The code used to follow this logic:
+ //
+ // Check if the list of libraries has changed. If not, nothing is done
+ // - the command "DhcpN libreload" is required to reload the same
+ // libraries (this prevents needless reloads when anything else in the
+ // configuration is changed).
+ //
+ // We no longer rely on this. Parameters can change. And even if the
+ // parameters stay the same, they could point to files that could
+ // change. We can skip loading routines only if there were and there still
+ // are no libraries specified.
+ vector<string> current_libraries = HooksManager::getLibraryNames();
+ if (current_libraries.empty() && libraries_.empty()) {
+ return;
+ }
+
+ // Library list has changed, validate each of the libraries specified.
+ vector<string> lib_names = isc::hooks::extractNames(libraries_);
+ vector<string> error_libs = HooksManager::validateLibraries(lib_names,
+ multi_threading_enabled);
+ if (!error_libs.empty()) {
+
+ // Construct the list of libraries in error for the message.
+ string error_list = error_libs[0];
+ for (size_t i = 1; i < error_libs.size(); ++i) {
+ error_list += (string(", ") + error_libs[i]);
+ }
+ isc_throw(InvalidHooksLibraries,
+ "hooks libraries failed to validate - "
+ "library or libraries in error are: "
+ << error_list << " (" << position << ")");
+ }
+}
+
+void
+HooksConfig::loadLibraries(bool multi_threading_enabled) const {
+ /// Commits the list of libraries to the configuration manager storage if
+ /// the list of libraries has changed.
+ /// @todo: Delete any stored CalloutHandles before reloading the
+ /// libraries
+ if (!HooksManager::loadLibraries(libraries_, multi_threading_enabled)) {
+ isc_throw(InvalidHooksLibraries,
+ "One or more hook libraries failed to load");
+ }
+}
+
+bool
+HooksConfig::equal(const HooksConfig& other) const {
+
+ /// @todo: This comparision assumes that the library order is not relevant,
+ /// so [ lib1, lib2 ] is equal to [ lib2, lib1 ]. However, this is not strictly
+ /// true, because callouts execution is called in other they're loaded. Therefore
+ /// changing the libraries order may change the server behavior.
+ ///
+ /// We don't have any libraries that are interacting (or would change their behavior
+ /// depending on the order in which their callouts are executed), so the code is
+ /// ok for now.
+ for (isc::hooks::HookLibsCollection::const_iterator this_it = libraries_.begin();
+ this_it != libraries_.end(); ++this_it) {
+ bool match = false;
+ for (isc::hooks::HookLibsCollection::const_iterator other_it =
+ other.libraries_.begin(); other_it != other.libraries_.end(); ++other_it) {
+ if (this_it->first != other_it->first) {
+ continue;
+ }
+ if (isNull(this_it->second) && isNull(other_it->second)) {
+ match = true;
+ break;
+ }
+ if (isNull(this_it->second) || isNull(other_it->second)) {
+ continue;
+ }
+ if (this_it->second->equals(*other_it->second)) {
+ match = true;
+ break;
+ }
+ }
+ // No match found for the particular hooks library so return false.
+ if (!match) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+ElementPtr
+HooksConfig::toElement() const {
+ // hooks-libraries is a list of maps
+ ElementPtr result = Element::createList();
+ // Iterate through libraries
+ for (HookLibsCollection::const_iterator hl = libraries_.begin();
+ hl != libraries_.end(); ++hl) {
+ // Entries are maps
+ ElementPtr map = Element::createMap();
+ // Set the library name
+ map->set("library", Element::create(hl->first));
+ // Set parameters (not set vs set empty map)
+ if (!isNull(hl->second)) {
+ map->set("parameters", hl->second);
+ }
+ // Push to the list
+ result->add(map);
+ }
+ return (result);
+}
+
+};
+};
diff --git a/src/lib/hooks/hooks_config.h b/src/lib/hooks/hooks_config.h
new file mode 100644
index 0000000..62c3fbb
--- /dev/null
+++ b/src/lib/hooks/hooks_config.h
@@ -0,0 +1,115 @@
+// Copyright (C) 2017-2023 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 HOOKS_CONFIG_H
+#define HOOKS_CONFIG_H
+
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <cc/cfg_to_element.h>
+#include <hooks/libinfo.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Exception thrown when a library failed to validate
+class InvalidHooksLibraries : public Exception {
+ public:
+ InvalidHooksLibraries(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Wrapper class that holds hooks libraries configuration
+///
+/// This was moved from HooksLibrariesParser
+///
+/// However, this class does more than just check the list of library names.
+/// It does two other things:
+/// # validate libraries
+/// # load libraries
+/// and it provides a toElement() method to unparse a configuration.
+///
+/// @todo add toElement() unit tests
+class HooksConfig : public isc::data::CfgToElement {
+public:
+ /// @brief Default constructor.
+ ///
+ HooksConfig() : libraries_() { }
+
+ /// @brief Adds additional hooks libraries.
+ ///
+ /// @param libname full filename with path to the library.
+ /// @param parameters map of parameters that configure the library.
+ void add(std::string libname, isc::data::ConstElementPtr parameters) {
+ libraries_.push_back(make_pair(libname, parameters));
+ }
+
+ /// @brief Provides access to the configured hooks libraries.
+ ///
+ /// @note The const reference returned is only valid as long as the
+ /// object that returned it.
+ const isc::hooks::HookLibsCollection& get() const {
+ return libraries_;
+ }
+
+ /// @brief Removes all configured hooks libraries.
+ void clear() {
+ libraries_.clear();
+ }
+
+ /// @brief Compares two Hooks Config classes for equality
+ ///
+ /// @param other other hooksconfig to compare with
+ bool equal(const HooksConfig& other) const;
+
+ /// @brief Verifies that libraries stored in libraries_ are valid.
+ ///
+ /// This method is a smart wrapper around @ref
+ /// isc::hooks::HooksManager::validateLibraries().
+ /// It tries to validate all the libraries stored in libraries_.
+ ///
+ /// @param position position of the hooks-library map for error reporting
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @throw InvalidHooksLibraries if any issue is discovered.
+ void verifyLibraries(const isc::data::Element::Position& position,
+ bool multi_threading_enabled) const;
+
+ /// @brief Commits hooks libraries configuration.
+ ///
+ /// This method calls necessary methods in HooksManager that will unload
+ /// any libraries that may be currently loaded and will load the actual
+ /// libraries. Providing that the specified libraries are valid and are
+ /// different to those already loaded, this method loads the new set of
+ /// libraries (and unloads the existing set).
+ ///
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @throw InvalidHooksLibraries if the call to HooksManager fails.
+ void loadLibraries(bool multi_threading_enabled) const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// Returns an element which must parse into the same object, i.e.
+ /// @code
+ /// for all valid config C parse(parse(C)->toElement()) == parse(C)
+ /// @endcode
+ ///
+ /// @return a pointer to a configuration which can be parsed into
+ /// the initial configuration object
+ isc::data::ElementPtr toElement() const;
+
+private:
+ /// @brief List of hooks libraries with their configuration parameters
+ isc::hooks::HookLibsCollection libraries_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+#endif // HOOKS_CONFIG_H
diff --git a/src/lib/hooks/hooks_log.cc b/src/lib/hooks/hooks_log.cc
new file mode 100644
index 0000000..141ab63
--- /dev/null
+++ b/src/lib/hooks/hooks_log.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2011-2018 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/.
+
+/// Defines the logger used by the Hooks
+
+#include <config.h>
+
+#include <hooks/hooks_log.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace hooks {
+
+isc::log::Logger hooks_logger("hooks");
+
+isc::log::Logger callouts_logger("callouts");
+
+extern const int HOOKS_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC;
+extern const int HOOKS_DBG_CALLS = isc::log::DBGLVL_TRACE_BASIC_DATA;
+extern const int HOOKS_DBG_EXTENDED_CALLS = isc::log::DBGLVL_TRACE_DETAIL_DATA;
+
+
+} // namespace hooks
+} // namespace isc
+
diff --git a/src/lib/hooks/hooks_log.h b/src/lib/hooks/hooks_log.h
new file mode 100644
index 0000000..6b567c9
--- /dev/null
+++ b/src/lib/hooks/hooks_log.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2013-2015,2017 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 HOOKS_LOG_H
+#define HOOKS_LOG_H
+
+#include <log/macros.h>
+#include <hooks/hooks_messages.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Hooks debug Logging levels
+///
+/// Defines the levels used to output debug messages in the Hooks framework.
+/// Note that higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations,
+extern const int HOOKS_DBG_TRACE;
+
+// The next level traces each call to hook code.
+extern const int HOOKS_DBG_CALLS;
+
+// Additional information on the calls. Report each call to a callout (even
+// if there are multiple callouts on a hook) and each status return.
+extern const int HOOKS_DBG_EXTENDED_CALLS;
+
+
+/// @brief Hooks Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger hooks_logger;
+
+/// @brief Callouts logger
+///
+/// This is the specialized logger used to log messages pertaining to the
+/// callouts execution. In particular, it logs when the callout is invoked
+/// and when it ends. It also logs the callout execution times.
+extern isc::log::Logger callouts_logger;
+
+} // namespace hooks
+} // namespace isc
+
+#endif // HOOKS_LOG_H
diff --git a/src/lib/hooks/hooks_maintenance.dox b/src/lib/hooks/hooks_maintenance.dox
new file mode 100644
index 0000000..75e1227
--- /dev/null
+++ b/src/lib/hooks/hooks_maintenance.dox
@@ -0,0 +1,381 @@
+// Copyright (C) 2013-2020 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/.
+
+// Note: the prefix "hooksmg" to all labels is an abbreviation for "Hooks
+// Maintenance Guide" and is used to prevent a clash with symbols in any
+// other Doxygen file.
+
+/**
+ @page hooksmgMaintenanceGuide Hooks Maintenance Guide
+
+ @section hooksmgIntroduction Introduction
+
+ This document is aimed at Kea maintainers responsible for the hooks
+ system. It provides an overview of the classes that make up the hooks
+ framework and notes important aspects of processing. More detailed
+ information can be found in the source code.
+
+ It is assumed that the reader is familiar with the contents of the @ref
+ hooksdgDevelopersGuide and the @ref hooksComponentDeveloperGuide.
+
+ @section hooksmgObjects Hooks Framework Objects
+
+ The relationships between the various objects in the hooks framework
+ is shown below:
+
+ @image html HooksUml.png "High-Level Class Diagram of the Hooks Framework"
+
+ (To avoid clutter, the @ref hooksmgServerHooks object, used to pass
+ information about registered hooks to the components, is not shown on
+ the diagram.)
+
+ The hooks framework objects can be split into user-side objects and
+ server-side objects. The former are those objects used or referenced
+ by user-written hooks libraries. The latter are those objects used in
+ the hooks framework.
+
+ @subsection hooksmgUserObjects User-Side Objects
+
+ The user-side code is able to access two objects in the framework,
+ the @ref hooksmgCalloutHandle and the @ref hooksmgLibraryHandle.
+ The @ref hooksmgCalloutHandle is used to pass data between the Kea
+ component and the loaded library; the @ref hooksmgLibraryHandle is used
+ for registering callouts.
+
+ @subsubsection hooksmgCalloutHandle Callout Handle
+
+ The @ref isc::hooks::CalloutHandle has two functions: passing arguments
+ between the Kea component and the user-written library, and storing
+ per-request context between library calls. In both cases the data is
+ stored in a @c std::map structure, keyed by argument (or context item) name.
+ The actual data is stored in a @c boost::any object, which allows any
+ data type to be stored, although a penalty for this flexibility is
+ the restriction (mentioned in the @ref hooksdgDevelopersGuide) that
+ the type of data retrieved must be identical (and not just compatible)
+ with that stored.
+
+ The storage of context data is slightly complex because there is
+ separate context for each user library. For this reason, the @ref
+ hooksmgCalloutHandle has multiple maps, one for each library loaded.
+ The maps are stored in another map, the appropriate map being identified
+ by the "current library index" (this index is explained further below).
+ The reason for the second map (rather than a structure such as a vector)
+ is to avoid creating individual context maps unless needed; given the
+ key to the map (in this case the current library index) accessing an
+ element in a map using the operator[] method returns the element in
+ question if it exists, or creates a new one (and stores it in the map)
+ if its doesn't.
+
+ @subsubsection hooksmgLibraryHandle Library Handle
+
+ Little more than a restricted interface to the @ref
+ hooksmgCalloutManager, the @ref isc::hooks::LibraryHandle allows a
+ callout to register and deregister callouts. However, there are some
+ quirks to callout registration which, although the processing involved
+ is in the @ref hooksmgCalloutManager, are best described here.
+
+ Firstly, a callout can be deregistered by a function within a user
+ library only if it was registered by a function within that library. That
+ is to say, if library A registers the callout A_func() on hook "alpha"
+ and library B registers B_func(), functions within library A are only
+ able to remove A_func() (and functions in library B remove B_func()).
+ The restriction - here to prevent one library interfering with the
+ callouts of another - is enforced by means of the current library index.
+ As described below, each entry in the vector of callouts associated with
+ a hook is a pair object, comprising a pointer to the callout and
+ the index of the library with which it is associated. A callout
+ can only modify entries in that vector where the current library index
+ matches the index element of the pair.
+
+ A second quirk is that when dynamically modifying the list of callouts,
+ the change only takes effect when the current call out from the server
+ completes. To clarify this, suppose that functions A_func(), B_func()
+ and C_func() are registered on a hook, and the server executes a callout
+ on the hook. Suppose also during this call, A_func() removes the callout
+ C_func() and that B_func() adds D_func(). As changes only take effect
+ when the current call out completes, the user callouts executed will be
+ A_func(), B_func() then C_func(). When the server calls the hook callouts
+ again, the functions executed will be A_func(), B_func() and D_func().
+
+ This restriction is down to implementation. When a set of callouts on a hook
+ is being called, the @ref hooksmgCalloutManager iterates through a
+ vector (the "callout vector") of (index, callout pointer) pairs. Since
+ registration or deregistration of a callout on that hook would change the
+ vector (and so potentially invalidate the iterators used to access the it),
+ a copy of the vector is taken before the iteration starts. The @ref
+ hooksmgCalloutManager iterates over this copy while any changes made
+ by the callout registration functions affect the relevant callout vector.
+ Such approach was chosen because of performance considerations.
+
+ @subsection hooksmgServerObjects Server-Side Objects
+
+ Those objects are not accessible by user libraries. Please do not
+ attempt to use them if you are developing user callouts.
+
+ @subsubsection hooksmgServerHooks Server Hooks
+
+ The singleton @ref isc::hooks::ServerHooks object is used to register
+ hooks. It is little more than a wrapper around a map of (hook index,
+ hook name), generating a unique number (the hook index) for each
+ hook registered. It also handles the registration of the pre-defined
+ context_create and context_destroy hooks.
+
+ In operation, the @ref hooksmgHooksManager provides a thin wrapper
+ around it, so that the Kea component developer does not have to
+ worry about another object.
+
+ @subsubsection hooksmgLibraryManager Library Manager
+
+ An @ref isc::hooks::LibraryManager is created by the @ref
+ hooksmgHooksManager object for each shared library loaded. It
+ controls the loading and unloading of the library and in essence
+ represents the library in the hooks framework. It also handles the
+ registration of the standard callouts (functions in the library with
+ the same name as the hook name).
+
+ Of particular importance is the "library's index", a number associated
+ with the library. This is passed to the LibraryManager at creation
+ time and is used to tag the callout pointers. It is discussed
+ further below.
+
+ As the @c LibraryManager provides all the methods needed to manage the
+ shared library, it is the natural home for the static @c validateLibrary()
+ method. The function called the parsing of the Kea configuration, when
+ the "hooks-libraries" element is processed. It checks that shared library
+ exists, that it can be opened, that it contains the @c version() function
+ and that that function returns a valid value. It then closes the shared
+ library and returns an appropriate indication as to the library status.
+
+ @subsubsection hooksmgLibraryManagerCollection Library Manager Collection
+
+ The hooks framework can handle multiple libraries and as
+ a result will create a @ref hooksmgLibraryManager for each
+ of them. The collection of LibraryManagers is managed by the
+ @ref isc::hooks::LibraryManagerCollection object which, in most
+ cases has a method corresponding to a @ref hooksmgLibraryManager
+ method, e.g. it has a @c loadLibraries() that corresponds to the @ref
+ hooksmgLibraryManager's loadLibrary() call. As would be expected, methods
+ on the @c LibraryManagerCollection iterate through all specified libraries,
+ calling the corresponding LibraryManager method for each library.
+
+ One point of note is that @c LibraryManagerCollection operates on an "all
+ or none" principle. When @c loadLibraries() is called, on exit either all
+ libraries have been successfully opened or none of them have. There
+ is no use-case in Kea where, after a user has specified the shared
+ libraries they want to load, the system will operate with only some of
+ them loaded.
+
+ The @c LibraryManagerCollection is the place where each library's index is set.
+ Each library is assigned a number ranging from 1 through to the number
+ of libraries being loaded. As mentioned in the previous section, this
+ index is used to tag callout pointers, something that is discussed
+ in the next section.
+
+ (Whilst on the subject of library index numbers, two additional
+ numbers - 0 and @c INT_MAX - are also valid as "current library index".
+ For flexibility, the Kea component is able to register its own
+ functions as hook callouts. It does this by obtaining a suitable @ref
+ hooksmgLibraryHandle from the @ref hooksmgHooksManager. A choice
+ of two is available: one @ref hooksmgLibraryHandle (with an index
+ of 0) can be used to register a callout on a hook to execute before
+ any user-supplied callouts. The second (with an index of @c INT_MAX)
+ is used to register a callout to run after user-specified callouts.
+ Apart from the index number, the hooks framework does not treat these
+ callouts any differently from user-supplied ones.)
+
+ @subsubsection hooksmgCalloutManager Callout Manager
+
+ The @ref isc::hooks::CalloutManager is the core of the framework insofar
+ as the registration and calling of callouts is concerned.
+
+ It maintains a "hook vector" - a vector with one element for
+ each registered hook. Each element in this vector is itself a
+ vector (the callout vector), each element of which is a pair of
+ (library index, callback pointer). When a callout is registered, the
+ CalloutManager's current library index is used to supply the "library
+ index" part of the pair. The library index is set explicitly by the
+ @ref hooksmgLibraryManager prior to calling the user library's load()
+ function (and prior to registering the standard callbacks).
+
+ The situation is slightly more complex when a callout is executing. In
+ order to execute a callout, the CalloutManager's @c callCallouts()
+ method must be called. This iterates through the callout vector for
+ a hook and for each element in the vector, uses the "library index"
+ part of the pair to set the "current library index" before calling the
+ callout function recorded in the second part of the pair. In most cases,
+ the setting of the library index has no effect on the callout. However,
+ if the callout wishes to dynamically register or deregister a callout,
+ the @ref hooksmgLibraryHandle (see above) calls a method on the
+ @ref hooksmgCalloutManager which in turn uses that information.
+
+ @subsubsection hooksmgHooksManager Hooks Manager
+
+ The @ref isc::hooks::HooksManager is the main object insofar as the
+ server is concerned. It controls the creation of the library-related
+ objects and provides the framework in which they interact. It also
+ provides a shell around objects such as @ref hooksmgServerHooks so that all
+ interaction with the hooks framework by the server is through the
+ HooksManager object. Apart from this, it supplies no functionality to
+ the hooks framework.
+
+ @section hooksmgOtherIssues Other Issues
+
+ @subsection hooksmgMemoryAllocation Memory Allocation
+
+ Unloading a shared library works by unmapping the part of the process's
+ virtual address space in which the library lies. This may lead to
+ problems if there are still references to that address space elsewhere
+ in the process.
+
+ In many operating systems, heap storage allowed by a shared library
+ will lie in the virtual address allocated to the library. This has
+ implications in the hooks framework because:
+
+ - Argument information stored in a @ref hooksmgCalloutHandle by a
+ callout in a library may lie in the library's address space.
+
+ - Data modified in objects passed as arguments may lie in the address
+ space. For example, it is common for a DHCP callout to add "options"
+ to a packet: the memory allocated for those options will most likely
+ lie in library address space.
+
+ The problem really arises because of the extensive use by Kea of
+ boost smart pointers. When the pointer is destroyed, the pointed-to
+ memory is deallocated. If the pointer points to address space that is
+ unmapped because a library has been unloaded, the deletion causes a
+ segmentation fault.
+
+ The hooks framework addresses the issue for the @ref hooksmgCalloutHandle
+ by keeping in that object a shared pointer to the object controlling
+ library unloading (the @ref hooksmgLibraryManagerCollection). Although
+ the libraries can be unloaded at any time, it is only when every
+ @ref hooksmgCalloutHandle that could possibly reference address space in the
+ library have been deleted that the library will actually be unloaded
+ and the address space unmapped.
+
+ The hooks framework cannot solve the second issue as the objects in
+ question are under control of the Kea server incorporating the
+ hooks. It is up to the server developer to ensure that all such objects
+ have been destroyed before libraries are reloaded. In extreme cases
+ this may mean the server suspending all processing of incoming requests
+ until all currently executing requests have completed and data object
+ destroyed, reloading the libraries, then resuming processing.
+
+ Since Kea 1.7.10 the unload() entry point is called as the first phase
+ of unloading. This gives more chance to hooks writer to perform
+ necessary cleanup actions so the second phase, memory unmapping
+ can safely happen. The @c isc::hooks::unloadLibraries() function
+ was updated too to return false when at least one active callout
+ handle remained.
+
+ @subsection hooksmgStaticLinking Hooks and Statically-Linked Kea
+
+ Kea has the configuration option to allow static linking. What this
+ means is that it links against the static Kea libraries and not
+ the sharable ones - although it links against the sharable system
+ libraries like "libc" and "libstdc++" and well as the sharable libraries
+ for third-party packages such as log4cplus and MySql.
+
+ Static linking poses a problem for dynamically-loaded hooks libraries
+ as some of the code in them - in particular the hooks framework and
+ the logging code - depend on global objects created within the Kea
+ libraries. In the normal course of events (Kea linked against
+ shared libraries), when Kea is run and the operating system loads
+ a Kea shared library containing a global object, address space
+ is assigned for it. When the hooks framework loads a user-library
+ linked against the same Kea shared library, the operating system
+ recognizes that the library is already loaded (and initialized) and
+ uses its definition of the global object. Thus both the code in the
+ Kea image and the code in the user-written shared library
+ reference the same object.
+
+ If Kea is statically linked, the linker allocates address space
+ in the Kea image for the global object and does not include any
+ reference to the shared library containing it. When Kea now loads
+ the user-written shared library - and so loads the Kea library code
+ containing the global object - the operating system does not know that
+ the object already exists. Instead, it allocates new address space.
+ The version of Kea in memory therefore has two copies of the object:
+ one referenced by code in the Kea image, and one referenced by code
+ in the user-written hooks library. This causes problems - information
+ put in one copy is not available to the other.
+
+ Particular problems were encountered with global objects the hooks library
+ and in the logging library, so some code to alleviate the problem has been
+ included.
+
+ The issue in the hooks library is the singleton @ref
+ isc::hooks::ServerHooks object, used by the user-written hooks library
+ if it attempts to register or deregister callouts. The contents of the
+ singleton - the names of the hook points and their index - are set by
+ the relevant Kea server; this information is not available in the
+ singleton created in the user's hooks library.
+
+ Within the code users by the user's hooks library, the @c ServerHooks
+ object is used by @ref isc::hooks::CalloutHandle and @ref
+ isc::hooks::CalloutManager objects. Both these objects are passed to the
+ hooks library code when a callout is called: the former directly through
+ the callout argument list, the latter indirectly as a pointer to it is
+ stored in the CalloutHandle. This allows a solution to the problem:
+ instead of accessing the singleton via @c ServerHooks::getServerHooks(),
+ the constructors of these objects store a reference to the singleton
+ @c ServerHooks when they are created and use that reference to access
+ @c ServerHooks data. Since both @c CalloutHandle and @c CalloutManager are
+ created in the statically-linked Kea server, use of the reference
+ means that it is the singleton within the server - and not the one
+ within the user's hooks library - that is referenced.
+
+ The solution of the logging problem is not so straightforward. Within
+ Kea, there are two logging components, the Kea logging framework
+ and the log4cplus libraries. Owing to static linking, there are two
+ instances of the former; but as static linking uses shared libraries of
+ third-party products, there is one instance of the latter. What further
+ complicates matters is that initialization of the logging framework is
+ in two parts: static initialization and run-time initialization.
+
+ The logging initialization comprises the following:
+
+ -# Static initialization of the log4cplus global variables.
+ -# Static initialization of messages in the various Kea libraries.
+ -# Static initialization of logging framework.
+ -# Run-time initialization of the logging framework.
+ -# Run-time initialization of log4cplus
+
+ As both the Kea server and the user-written hooks libraries use the
+ log4cplus shared library, item 1 - the static initialization of the log4cplus
+ global variables is performed once.
+
+ The next two tasks - static initialization of the messages in the Kea
+ libraries and the static initialization of the logging framework -
+ are performed twice, once in the context of the Kea server and
+ once in the context of the hooks library. For this reason, run-time
+ initialization of the logging framework needs to be performed twice,
+ once in the context of the Kea server and once in the context of the
+ user-written hooks library. However, the standard logging framework
+ initialization code also performs the last task, initialization of
+ log4cplus, something that causes problems if executed more than once.
+
+ To get round this, the function @ref isc::hooks::hooksStaticLinkInit()
+ has been written. It executes the only part of the logging framework
+ run-time initialization that actually pertains to the logging framework
+ and not log4cplus, namely loading the message dictionary with the
+ statically-initialized messages in the Kea libraries.
+ This should be executed by any hooks library linking against a statically
+ initialized Kea. (In fact, running it against a dynamically-linked
+ Kea should have no effect, as the load operation discards any duplicate
+ message entries.) The hooks library tests do this, the code being
+ conditionally compiled within a test of the @c USE_STATIC_LINK macro, set
+ by the configure script.
+
+ @note Not everything is completely rosy with logging and static linking.
+ In particular, there appears to be an issue with the scenario where a
+ user-written hooks library is run by a statically-linked Kea and then
+ unloaded. As far as can be determined, on unload the system attempts to
+ delete the same logger twice. This is alleviated by explicitly clearing
+ the loggerptr_ variable in the @ref isc::log::Logger destructor, but there
+ is a suspicion that some memory might be lost in these circumstances.
+ This is still under investigation.
+*/
diff --git a/src/lib/hooks/hooks_manager.cc b/src/lib/hooks/hooks_manager.cc
new file mode 100644
index 0000000..67ec80a
--- /dev/null
+++ b/src/lib/hooks/hooks_manager.cc
@@ -0,0 +1,280 @@
+// Copyright (C) 2013-2023 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/library_manager_collection.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor
+
+HooksManager::HooksManager() : test_mode_(false) {
+ // Nothing present, so create the collection with any empty set of
+ // libraries, and get the CalloutManager.
+ HookLibsCollection libraries;
+ lm_collection_.reset(new LibraryManagerCollection(libraries));
+ lm_collection_->loadLibraries(false);
+ callout_manager_ = lm_collection_->getCalloutManager();
+}
+
+// Return reference to singleton hooks manager.
+
+HooksManager&
+HooksManager::getHooksManager() {
+ static HooksManager manager;
+ return (manager);
+}
+
+// Are callouts present?
+
+bool
+HooksManager::calloutsPresentInternal(int index) {
+ return (callout_manager_->calloutsPresent(index));
+}
+
+bool
+HooksManager::calloutsPresent(int index) {
+ return (getHooksManager().calloutsPresentInternal(index));
+}
+
+bool
+HooksManager::commandHandlersPresentInternal(const std::string& command_name) {
+ return (callout_manager_->commandHandlersPresent(command_name));
+}
+
+bool
+HooksManager::commandHandlersPresent(const std::string& command_name) {
+ return (getHooksManager().commandHandlersPresentInternal(command_name));
+}
+
+// Call the callouts
+
+void
+HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) {
+ callout_manager_->callCallouts(index, handle);
+}
+
+void
+HooksManager::callCallouts(int index, CalloutHandle& handle) {
+ getHooksManager().callCalloutsInternal(index, handle);
+}
+
+void
+HooksManager::callCommandHandlersInternal(const std::string& command_name,
+ CalloutHandle& handle) {
+ callout_manager_->callCommandHandlers(command_name, handle);
+}
+
+void
+HooksManager::callCommandHandlers(const std::string& command_name,
+ CalloutHandle& handle) {
+ getHooksManager().callCommandHandlersInternal(command_name, handle);
+}
+
+// Load the libraries. This will delete the previously-loaded libraries
+// (if present) and load new ones. If loading libraries fails, initialize with
+// empty list.
+
+bool
+HooksManager::loadLibrariesInternal(const HookLibsCollection& libraries,
+ bool multi_threading_enabled) {
+ if (test_mode_) {
+ return (true);
+ }
+
+ ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
+
+ // Keep a weak pointer on the existing library manager collection.
+ boost::weak_ptr<LibraryManagerCollection> weak_lmc(lm_collection_);
+
+ // Create the library manager collection.
+ lm_collection_.reset(new LibraryManagerCollection(libraries));
+
+ // If there was another owner the previous library manager collection
+ // was not destroyed and libraries not closed.
+ if (!weak_lmc.expired()) {
+ isc_throw(LibrariesStillOpened, "some libraries are still opened");
+ }
+
+ // Load the libraries.
+ bool status = lm_collection_->loadLibraries(multi_threading_enabled);
+
+ if (status) {
+ // ... and obtain the callout manager for them if successful.
+ callout_manager_ = lm_collection_->getCalloutManager();
+ } else {
+ // Unable to load libraries, reset to state before this function was
+ // called.
+ static_cast<void>(unloadLibrariesInternal());
+ }
+
+ return (status);
+}
+
+bool
+HooksManager::loadLibraries(const HookLibsCollection& libraries,
+ bool multi_threading_enabled) {
+ return (getHooksManager().loadLibrariesInternal(libraries,
+ multi_threading_enabled));
+}
+
+// Unload the libraries. This just deletes all internal objects (which will
+// cause the libraries to be unloaded) and initializes them with empty list if
+// requested.
+
+bool
+HooksManager::unloadLibrariesInternal() {
+ if (test_mode_) {
+ return (true);
+ }
+
+ ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
+
+ // Keep a weak pointer on the existing library manager collection.
+ boost::weak_ptr<LibraryManagerCollection> weak_lmc(lm_collection_);
+
+ // Create the collection with any empty set of libraries.
+ HookLibsCollection libraries;
+ lm_collection_.reset(new LibraryManagerCollection(libraries));
+
+ // If there was another owner the previous library manager collection
+ // was not destroyed and libraries not closed.
+ boost::shared_ptr<LibraryManagerCollection> still_here = weak_lmc.lock();
+ if (still_here) {
+ // Restore the library manager collection.
+ lm_collection_ = still_here;
+ return (false);
+ }
+
+ // Load the empty set of libraries.
+ lm_collection_->loadLibraries(false);
+
+ // Get the CalloutManager.
+ callout_manager_ = lm_collection_->getCalloutManager();
+
+ return (true);
+}
+
+bool
+HooksManager::unloadLibraries() {
+ return (getHooksManager().unloadLibrariesInternal());
+}
+
+void
+HooksManager::prepareUnloadLibrariesInternal() {
+ if (test_mode_) {
+ return;
+ }
+
+ static_cast<void>(lm_collection_->prepareUnloadLibraries());
+}
+
+void
+HooksManager::prepareUnloadLibraries() {
+ getHooksManager().prepareUnloadLibrariesInternal();
+}
+
+// Create a callout handle
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandleInternal() {
+ return (boost::make_shared<CalloutHandle>(callout_manager_, lm_collection_));
+}
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandle() {
+ return (getHooksManager().createCalloutHandleInternal());
+}
+
+// Get the list of the names of loaded libraries.
+
+std::vector<std::string>
+HooksManager::getLibraryNamesInternal() const {
+ return (lm_collection_->getLibraryNames());
+}
+
+HookLibsCollection
+HooksManager::getLibraryInfoInternal() const {
+ return (lm_collection_->getLibraryInfo());
+}
+
+std::vector<std::string>
+HooksManager::getLibraryNames() {
+ return (getHooksManager().getLibraryNamesInternal());
+}
+
+HookLibsCollection
+HooksManager::getLibraryInfo() {
+ return (getHooksManager().getLibraryInfoInternal());
+}
+
+// Shell around ServerHooks::registerHook()
+
+int
+HooksManager::registerHook(const std::string& name) {
+ return (ServerHooks::getServerHooks().registerHook(name));
+}
+
+// Return pre- and post- library handles.
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandleInternal() {
+ return (callout_manager_->getPreLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandle() {
+ return (getHooksManager().preCalloutsLibraryHandleInternal());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandleInternal() {
+ return (callout_manager_->getPostLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandle() {
+ return (getHooksManager().postCalloutsLibraryHandleInternal());
+}
+
+// Validate libraries
+
+std::vector<std::string>
+HooksManager::validateLibraries(const std::vector<std::string>& libraries,
+ bool multi_threading_enabled) {
+ return (LibraryManagerCollection::validateLibraries(libraries,
+ multi_threading_enabled));
+}
+
+// Test mode
+
+void
+HooksManager::setTestMode(bool mode) {
+ getHooksManager().test_mode_ = mode;
+}
+
+bool
+HooksManager::getTestMode() {
+ return (getHooksManager().test_mode_);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/hooks_manager.h b/src/lib/hooks/hooks_manager.h
new file mode 100644
index 0000000..c3957e8
--- /dev/null
+++ b/src/lib/hooks/hooks_manager.h
@@ -0,0 +1,496 @@
+// Copyright (C) 2013-2023 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 HOOKS_MANAGER_H
+#define HOOKS_MANAGER_H
+
+#include <hooks/server_hooks.h>
+#include <hooks/libinfo.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Libraries still opened.
+///
+/// Thrown if an attempt is made to load libraries when some are still
+/// in memory likely because they were not unloaded (logic error in Kea)
+/// or they have some visible dangling pointers (logic error in a hook
+/// library).
+class LibrariesStillOpened : public Exception {
+public:
+ LibrariesStillOpened(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+class LibraryHandle;
+class LibraryManagerCollection;
+
+/// @brief Hooks Manager
+///
+/// This is the overall manager of the hooks framework and is the main class
+/// used by a Kea module when handling hooks. It is responsible for the
+/// loading and unloading of user libraries, and for calling the callouts on
+/// each hook point.
+///
+/// The class is a singleton, the single instance of the object being accessed
+/// through the static getHooksManager() method.
+
+class HooksManager : boost::noncopyable {
+public:
+
+ /// @brief Load and reload libraries
+ ///
+ /// Loads the list of libraries into the server address space. For each
+ /// library, the "standard" functions (ones with the same names as the
+ /// hook points) are configured and the libraries' "load" function
+ /// called.
+ ///
+ /// @note this method now requires the libraries are unloaded before
+ /// being called.
+ ///
+ /// If any library fails to load, an error message will be logged. The
+ /// remaining libraries will be loaded if possible.
+ ///
+ /// @param libraries List of libraries to be loaded. The order is
+ /// important, as it determines the order that callouts on the same
+ /// hook will be called.
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return true if all libraries loaded without a problem, false if one or
+ /// more libraries failed to load. In the latter case, message will
+ /// be logged that give the reason.
+ /// @throw LibrariesStillOpened when some libraries are already loaded.
+ static bool loadLibraries(const HookLibsCollection& libraries,
+ bool multi_threading_enabled = false);
+
+ /// @brief Unload libraries
+ ///
+ /// Unloads the loaded libraries and leaves the hooks subsystem in the
+ /// state it was after construction but before loadLibraries() is called.
+ ///
+ /// @note: This method should be called after @ref prepareUnloadLibraries
+ /// in order to destroy appropriate objects. See notes for
+ /// the class LibraryManager for pitfalls.
+ /// @note: if even after @ref prepareUnloadLibraries there are still
+ /// visible pointers (i.e. callout handles owning the
+ /// library manager collection) the method will fail to close
+ /// libraries and returns false. It is a fatal error as there
+ /// is no possible recovery. It is a logic error in the hook
+ /// code too so the solution is to fix it and to restart
+ /// the server with a correct hook library binary.
+ ///
+ /// @return true if all libraries unloaded successfully, false if they
+ /// are still in memory.
+ static bool unloadLibraries();
+
+ /// @brief Prepare the unloading of libraries
+ ///
+ /// Calls the unload functions when they exist and removes callouts.
+ ///
+ /// @note: after the call to this method there should be no visible
+ /// dangling pointers (i.e. callout handles owning the library
+ /// manager collection) nor invisible dangling pointers.
+ /// In the first case it will be impossible to close libraries
+ /// so they will remain in memory, in the second case a crash
+ /// is possible in particular at exit time during global
+ /// object finalization. In both cases the hook library code
+ /// causing the problem is incorrect and must be fixed.
+ /// @note: it is a logic error to not call this method before
+ /// @ref unloadLibraries even it hurts only with particular
+ /// hooks libraries.
+ static void prepareUnloadLibraries();
+
+ /// @brief Are callouts present?
+ ///
+ /// Checks loaded libraries and returns true if at lease one callout
+ /// has been registered by them for the given hook.
+ ///
+ /// @param index Hooks index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ static bool calloutsPresent(int index);
+
+ /// @brief Checks if control command handlers are present for the
+ /// specified command.
+ ///
+ /// @param command_name Command name for which handlers' presence should
+ /// be checked.
+ ///
+ /// @return true if there is a hook point associated with the specified
+ /// command and callouts/command handlers are installed for this hook
+ /// point, false otherwise.
+ static bool commandHandlersPresent(const std::string& command_name);
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// Iterates through the library handles and calls the callouts associated
+ /// with the given hook index.
+ ///
+ /// @note This method invalidates the current library index set with
+ /// setLibraryIndex().
+ ///
+ /// @param index Index of the hook to call.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ static void callCallouts(int index, CalloutHandle& handle);
+
+ /// @brief Calls the callouts/command handlers for a given command name.
+ ///
+ /// Iterates through the library handles and calls the command handlers
+ /// associated with the given command. It expects that the hook point
+ /// for this command exists (with a name being a command_name prefixed
+ /// with a dollar sign and with hyphens replaced with underscores).
+ ///
+ /// @param command_name Command name for which handlers should be called.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ ///
+ /// @throw NoSuchHook if the hook point for the specified command does
+ /// not exist.
+ static void callCommandHandlers(const std::string& command_name,
+ CalloutHandle& handle);
+
+ /// @brief Return pre-callouts library handle
+ ///
+ /// Returns a library handle that can be used by the server to register
+ /// callouts on a hook that are called _before_ any callouts belonging
+ /// to a library.
+ ///
+ /// @note Both the reference returned and the callouts registered with
+ /// this handle only remain valid until the next loadLibraries() or
+ /// unloadLibraries() call. If the callouts are to remain registered
+ /// after this time, a new handle will need to be obtained and the
+ /// callouts re-registered.
+ ///
+ /// @return Reference to library handle associated with pre-library callout
+ /// registration.
+ static LibraryHandle& preCalloutsLibraryHandle();
+
+ /// @brief Return post-callouts library handle
+ ///
+ /// Returns a library handle that can be used by the server to register
+ /// callouts on a hook that are called _after any callouts belonging
+ /// to a library.
+ ///
+ /// @note Both the reference returned and the callouts registered with
+ /// this handle only remain valid until the next loadLibraries() or
+ /// unloadLibraries() call. If the callouts are to remain registered
+ /// after this time, a new handle will need to be obtained and the
+ /// callouts re-registered.
+ ///
+ /// @return Reference to library handle associated with post-library callout
+ /// registration.
+ static LibraryHandle& postCalloutsLibraryHandle();
+
+ /// @brief Return callout handle
+ ///
+ /// Returns a callout handle to be associated with a request passed round
+ /// the system.
+ ///
+ /// @note This handle is valid only after a loadLibraries() call and then
+ /// only up to the next loadLibraries() call.
+ ///
+ /// @return Shared pointer to a CalloutHandle object.
+ static boost::shared_ptr<CalloutHandle> createCalloutHandle();
+
+ /// @brief Register Hook
+ ///
+ /// This is just a convenience shell around the ServerHooks::registerHook()
+ /// method. It - along with the definitions of the two hook indexes for
+ /// the context_create and context_destroy methods - means that server
+ /// authors only need to deal with HooksManager and CalloutHandle, and not
+ /// include any other hooks framework classes.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent hook-related calls.
+ /// This will be greater than or equal to zero (so allowing a
+ /// negative value to indicate an invalid index).
+ ///
+ /// @throws DuplicateHook A hook with the same name has already been
+ /// registered.
+ static int registerHook(const std::string& name);
+
+ /// @brief Return list of loaded libraries
+ ///
+ /// Returns the names of the loaded libraries.
+ ///
+ /// @return List of loaded library names.
+ static std::vector<std::string> getLibraryNames();
+
+ /// @brief Return list of loaded libraries with its parameters.
+ ///
+ /// Returns the names of the loaded libraries and their parameters.
+ ///
+ /// @return List of loaded libraries (names + parameters)
+ static HookLibsCollection getLibraryInfo();
+
+ /// @brief Validate library list
+ ///
+ /// For each library passed to it, checks that the library can be opened
+ /// and that the "version" function is present and gives the right answer.
+ /// Each library is closed afterwards.
+ ///
+ /// This is used during the configuration parsing - when the list of hooks
+ /// libraries is changed, each of the new libraries is checked before the
+ /// change is committed.
+ ///
+ /// @param libraries List of libraries to be validated.
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return An empty vector if all libraries validated. Otherwise it
+ /// holds the names of the libraries that failed validation.
+ static std::vector<std::string> validateLibraries(const std::vector<std::string>& libraries,
+ bool multi_threading_enabled = false);
+
+ /// Index numbers for pre-defined hooks.
+ static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE;
+ static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY;
+
+ /// @brief Park an object (packet).
+ ///
+ /// The typical use case for parking an object is when the server needs to
+ /// suspend processing of a packet to perform an asynchronous operation,
+ /// before the response is sent to a client. In this case, the object type
+ /// is a pointer to the processed packet. Therefore, further in this
+ /// description we're going to refer to the parked objects as "parked
+ /// packets". However, any other object can be parked if necessary.
+ ///
+ /// The following is the typical flow when packets are parked. The callouts
+ /// responsible for performing an asynchronous operation signal this need
+ /// to the server by returning the status @c NEXT_STEP_PARK, which instructs
+ /// the server to call this function. This function stops processing the
+ /// packet and puts it in, so called, parking lot. In order to be able to
+ /// resume the packet processing when instructed by the hooks library, the
+ /// parked packet is associated with the callback which, when called, will
+ /// resume packet processing.
+ ///
+ /// The hook library must increase a reference count on the parked object
+ /// by calling @c ParkingLotHandle::reference prior to returning the
+ /// @c NEXT_STEP_PARK status. This is important when multiple callouts
+ /// are installed on the same hook point and each of them schedules an
+ /// asynchronous operation. In this case, the packet must not be unparked
+ /// until all hook libraries call @c ParkingLotHandle::unpark to mark
+ /// that respective asynchronous operations are completed.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param hook_name name of the hook point for which the packet is parked.
+ /// @param parked_object packet to be parked.
+ /// @param unpark_callback callback invoked when the packet is unparked.
+ template<typename T>
+ static void park(const std::string& hook_name, T parked_object,
+ std::function<void()> unpark_callback) {
+ ServerHooks::getServerHooks().
+ getParkingLotPtr(hook_name)->park(parked_object, unpark_callback);
+ }
+
+ /// @brief Forces unparking the object (packet).
+ ///
+ /// This method unparks the object regardless of the reference counting
+ /// value. This is used in the situations when the callouts fail to unpark
+ /// the packet for some reason.
+ ///
+ /// @tparam T type of the parked object.
+ /// @param hook_name name of the hook point for which the packet is parked.
+ /// @param parked_object parked object to be unparked.
+ /// @return true if the specified object has been found, false otherwise.
+ template<typename T>
+ static bool unpark(const std::string& hook_name, T parked_object) {
+ return (ServerHooks::getServerHooks().
+ getParkingLotPtr(hook_name)->unpark(parked_object, true));
+ }
+
+ /// @brief Removes parked object without calling a callback.
+ ///
+ /// @tparam T type of the parked object.
+ /// @param hook_name name of the hook point for which the packet is parked.
+ /// @param parked_object parked object to be removed.
+ /// @return true if the specified object has been found false otherwise.
+ template<typename T>
+ static bool drop(const std::string& hook_name, T parked_object) {
+ return (ServerHooks::getServerHooks().
+ getParkingLotPtr(hook_name)->drop(parked_object));
+ }
+
+ /// @brief Increases reference counter for the parked object.
+ ///
+ /// Reference counter must be increased at least to 1 before the @c park()
+ /// method can be called.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param hook_name name of the hook point for which the packet is parked.
+ /// @param parked_object parked object for which reference counter should
+ /// be increased.
+ template<typename T>
+ static void reference(const std::string& hook_name, T parked_object) {
+ ServerHooks::getServerHooks().
+ getParkingLotPtr(hook_name)->reference(parked_object);
+ }
+
+ /// @brief Clears any parking packets.
+ ///
+ /// This method should be called during reconfiguration to ensure there
+ /// are no dangling pointers that could possibly prevent the library
+ /// from being unloaded.
+ static void clearParkingLots() {
+ ServerHooks::getServerHooks().getParkingLotsPtr()->clear();
+ }
+
+ /// @brief Set test mode
+ ///
+ /// If enabled by unit tests will permit to register callouts before calling
+ /// @ref loadLibraries which will return immediately without changing
+ /// current internal state.
+ ///
+ /// @param mode the test mode flag which enables or disabled the
+ /// functionality.
+ static void setTestMode(bool mode);
+
+ /// @brief Get test mode
+ ///
+ /// @return the test mode flag.
+ static bool getTestMode();
+
+private:
+
+ /// @brief Constructor
+ ///
+ /// This is private as the object is a singleton and can only be addressed
+ /// through the getHooksManager() static method.
+ HooksManager();
+
+ /// @brief Get singleton hooks manager
+ ///
+ /// @return Reference to the singleton hooks manager.
+ static HooksManager& getHooksManager();
+
+ //@{
+ /// The following methods correspond to similarly-named static methods,
+ /// but actually do the work on the singleton instance of the HooksManager.
+ /// See the descriptions of the static methods for more details.
+
+ /// @brief Load and reload libraries
+ ///
+ /// @param libraries List of libraries to be loaded. The order is
+ /// important, as it determines the order that callouts on the same
+ /// hook will be called.
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return true if all libraries loaded without a problem, false if one or
+ /// more libraries failed to load. In the latter case, message will
+ /// be logged that give the reason.
+ bool loadLibrariesInternal(const HookLibsCollection& libraries,
+ bool multi_threading_enabled);
+
+ /// @brief Unload libraries
+ ///
+ /// @return true if all libraries unloaded successfully, false on an error.
+ /// In the latter case, an error message will have been output.
+ bool unloadLibrariesInternal();
+
+ /// @brief Prepare the unloading of libraries
+ void prepareUnloadLibrariesInternal();
+
+ /// @brief Are callouts present?
+ ///
+ /// @param index Hooks index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ bool calloutsPresentInternal(int index);
+
+ /// @brief Checks if control command handlers are present for the
+ /// specified command.
+ ///
+ /// @param command_name Command name for which handlers' presence should
+ /// be checked.
+ ///
+ /// @return true if there is a hook point associated with the specified
+ /// command and callouts/command handlers are installed for this hook
+ /// point, false otherwise.
+ bool commandHandlersPresentInternal(const std::string& command_name);
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// @param index Index of the hook to call.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ void callCalloutsInternal(int index, CalloutHandle& handle);
+
+ /// @brief Calls the callouts/command handlers for a given command name.
+ ///
+ /// @param command_name Command name for which handlers should be called.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ ///
+ /// @throw NoSuchHook if the hook point for the specified command does
+ /// not exist.
+ void callCommandHandlersInternal(const std::string& command_name,
+ CalloutHandle& handle);
+
+ /// @brief Return callout handle
+ ///
+ /// @return Shared pointer to a CalloutHandle object.
+ boost::shared_ptr<CalloutHandle> createCalloutHandleInternal();
+
+ /// @brief Return pre-callouts library handle
+ ///
+ /// @return Reference to library handle associated with pre-library callout
+ /// registration.
+ LibraryHandle& preCalloutsLibraryHandleInternal();
+
+ /// @brief Return post-callouts library handle
+ ///
+ /// @return Reference to library handle associated with post-library callout
+ /// registration.
+ LibraryHandle& postCalloutsLibraryHandleInternal();
+
+ /// @brief Return list of loaded libraries
+ ///
+ /// @return List of loaded library names.
+ std::vector<std::string> getLibraryNamesInternal() const;
+
+ /// @brief Return a collection of library names with parameters.
+ HookLibsCollection getLibraryInfoInternal() const;
+
+ //@}
+
+ // Members
+
+ /// Set of library managers.
+ ///
+ /// @note: This should never be null.
+ boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
+ /// Callout manager for the set of library managers.
+ ///
+ /// @note: This should never be null.
+ boost::shared_ptr<CalloutManager> callout_manager_;
+
+ /// Test flag to keep @ref callout_manager_ when calling @ref loadLibraries
+ /// from unit tests (called by @ref configureDhcp[46]Server).
+ ///
+ /// @note: This will effectively make @ref loadLibraries return immediately.
+ bool test_mode_;
+};
+
+} // namespace util
+} // namespace hooks
+
+#endif // HOOKS_MANAGER_H
diff --git a/src/lib/hooks/hooks_messages.cc b/src/lib/hooks/hooks_messages.cc
new file mode 100644
index 0000000..dc75fb4
--- /dev/null
+++ b/src/lib/hooks/hooks_messages.cc
@@ -0,0 +1,93 @@
+// File created from ../../../src/lib/hooks/hooks_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace hooks {
+
+extern const isc::log::MessageID HOOKS_ALL_CALLOUTS_DEREGISTERED = "HOOKS_ALL_CALLOUTS_DEREGISTERED";
+extern const isc::log::MessageID HOOKS_CALLOUTS_BEGIN = "HOOKS_CALLOUTS_BEGIN";
+extern const isc::log::MessageID HOOKS_CALLOUTS_COMPLETE = "HOOKS_CALLOUTS_COMPLETE";
+extern const isc::log::MessageID HOOKS_CALLOUTS_REMOVED = "HOOKS_CALLOUTS_REMOVED";
+extern const isc::log::MessageID HOOKS_CALLOUT_CALLED = "HOOKS_CALLOUT_CALLED";
+extern const isc::log::MessageID HOOKS_CALLOUT_DEREGISTERED = "HOOKS_CALLOUT_DEREGISTERED";
+extern const isc::log::MessageID HOOKS_CALLOUT_ERROR = "HOOKS_CALLOUT_ERROR";
+extern const isc::log::MessageID HOOKS_CALLOUT_EXCEPTION = "HOOKS_CALLOUT_EXCEPTION";
+extern const isc::log::MessageID HOOKS_CALLOUT_REGISTRATION = "HOOKS_CALLOUT_REGISTRATION";
+extern const isc::log::MessageID HOOKS_CLOSE_ERROR = "HOOKS_CLOSE_ERROR";
+extern const isc::log::MessageID HOOKS_HOOK_LIST_RESET = "HOOKS_HOOK_LIST_RESET";
+extern const isc::log::MessageID HOOKS_INCORRECT_VERSION = "HOOKS_INCORRECT_VERSION";
+extern const isc::log::MessageID HOOKS_LIBRARY_CLOSED = "HOOKS_LIBRARY_CLOSED";
+extern const isc::log::MessageID HOOKS_LIBRARY_LOADED = "HOOKS_LIBRARY_LOADED";
+extern const isc::log::MessageID HOOKS_LIBRARY_LOADING = "HOOKS_LIBRARY_LOADING";
+extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE = "HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE";
+extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE = "HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE";
+extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADED = "HOOKS_LIBRARY_UNLOADED";
+extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADING = "HOOKS_LIBRARY_UNLOADING";
+extern const isc::log::MessageID HOOKS_LIBRARY_VERSION = "HOOKS_LIBRARY_VERSION";
+extern const isc::log::MessageID HOOKS_LOAD_ERROR = "HOOKS_LOAD_ERROR";
+extern const isc::log::MessageID HOOKS_LOAD_EXCEPTION = "HOOKS_LOAD_EXCEPTION";
+extern const isc::log::MessageID HOOKS_LOAD_FRAMEWORK_EXCEPTION = "HOOKS_LOAD_FRAMEWORK_EXCEPTION";
+extern const isc::log::MessageID HOOKS_LOAD_SUCCESS = "HOOKS_LOAD_SUCCESS";
+extern const isc::log::MessageID HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION = "HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION";
+extern const isc::log::MessageID HOOKS_NO_LOAD = "HOOKS_NO_LOAD";
+extern const isc::log::MessageID HOOKS_NO_UNLOAD = "HOOKS_NO_UNLOAD";
+extern const isc::log::MessageID HOOKS_NO_VERSION = "HOOKS_NO_VERSION";
+extern const isc::log::MessageID HOOKS_OPEN_ERROR = "HOOKS_OPEN_ERROR";
+extern const isc::log::MessageID HOOKS_STD_CALLOUT_REGISTERED = "HOOKS_STD_CALLOUT_REGISTERED";
+extern const isc::log::MessageID HOOKS_UNLOAD_ERROR = "HOOKS_UNLOAD_ERROR";
+extern const isc::log::MessageID HOOKS_UNLOAD_EXCEPTION = "HOOKS_UNLOAD_EXCEPTION";
+extern const isc::log::MessageID HOOKS_UNLOAD_FRAMEWORK_EXCEPTION = "HOOKS_UNLOAD_FRAMEWORK_EXCEPTION";
+extern const isc::log::MessageID HOOKS_UNLOAD_SUCCESS = "HOOKS_UNLOAD_SUCCESS";
+extern const isc::log::MessageID HOOKS_VERSION_EXCEPTION = "HOOKS_VERSION_EXCEPTION";
+
+} // namespace hooks
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "HOOKS_ALL_CALLOUTS_DEREGISTERED", "hook library at index %1 removed all callouts on hook %2",
+ "HOOKS_CALLOUTS_BEGIN", "begin all callouts for hook %1",
+ "HOOKS_CALLOUTS_COMPLETE", "completed callouts for hook %1 (total callouts duration: %2)",
+ "HOOKS_CALLOUTS_REMOVED", "callouts removed from hook %1 for library %2",
+ "HOOKS_CALLOUT_CALLED", "hooks library with index %1 has called a callout on hook %2 that has address %3 (callout duration: %4)",
+ "HOOKS_CALLOUT_DEREGISTERED", "hook library at index %1 deregistered a callout on hook %2",
+ "HOOKS_CALLOUT_ERROR", "error returned by callout on hook %1 registered by library with index %2 (callout address %3) (callout duration %4)",
+ "HOOKS_CALLOUT_EXCEPTION", "exception thrown by callout on hook %1 registered by library with index %2 (callout address %3): %4 (callout duration: %5)",
+ "HOOKS_CALLOUT_REGISTRATION", "hooks library with index %1 registering callout for hook '%2'",
+ "HOOKS_CLOSE_ERROR", "failed to close hook library %1: %2",
+ "HOOKS_HOOK_LIST_RESET", "the list of hooks has been reset",
+ "HOOKS_INCORRECT_VERSION", "hook library %1 is at version %2, require version %3",
+ "HOOKS_LIBRARY_CLOSED", "hooks library %1 successfully closed",
+ "HOOKS_LIBRARY_LOADED", "hooks library %1 successfully loaded",
+ "HOOKS_LIBRARY_LOADING", "loading hooks library %1",
+ "HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE", "hooks library %1 reports its multi-threading compatibility as %2",
+ "HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE", "hooks library %1 is not compatible with multi-threading",
+ "HOOKS_LIBRARY_UNLOADED", "hooks library %1 successfully unloaded",
+ "HOOKS_LIBRARY_UNLOADING", "unloading library %1",
+ "HOOKS_LIBRARY_VERSION", "hooks library %1 reports its version as %2",
+ "HOOKS_LOAD_ERROR", "'load' function in hook library %1 returned error %2",
+ "HOOKS_LOAD_EXCEPTION", "'load' function in hook library %1 threw an exception",
+ "HOOKS_LOAD_FRAMEWORK_EXCEPTION", "'load' function in hook library %1 threw an exception: reason %2",
+ "HOOKS_LOAD_SUCCESS", "'load' function in hook library %1 returned success",
+ "HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION", "'multi_threading_compatible' function in hook library %1 threw an exception",
+ "HOOKS_NO_LOAD", "no 'load' function found in hook library %1",
+ "HOOKS_NO_UNLOAD", "no 'unload' function found in hook library %1",
+ "HOOKS_NO_VERSION", "no 'version' function found in hook library %1",
+ "HOOKS_OPEN_ERROR", "failed to open hook library %1: %2",
+ "HOOKS_STD_CALLOUT_REGISTERED", "hooks library %1 registered standard callout for hook %2 at address %3",
+ "HOOKS_UNLOAD_ERROR", "'unload' function in hook library %1 returned error %2",
+ "HOOKS_UNLOAD_EXCEPTION", "'unload' function in hook library %1 threw an exception",
+ "HOOKS_UNLOAD_FRAMEWORK_EXCEPTION", "'unload' function in hook library %1 threw an exception, reason %2",
+ "HOOKS_UNLOAD_SUCCESS", "'unload' function in hook library %1 returned success",
+ "HOOKS_VERSION_EXCEPTION", "'version' function in hook library %1 threw an exception",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/hooks/hooks_messages.h b/src/lib/hooks/hooks_messages.h
new file mode 100644
index 0000000..a80626c
--- /dev/null
+++ b/src/lib/hooks/hooks_messages.h
@@ -0,0 +1,50 @@
+// File created from ../../../src/lib/hooks/hooks_messages.mes
+
+#ifndef HOOKS_MESSAGES_H
+#define HOOKS_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace hooks {
+
+extern const isc::log::MessageID HOOKS_ALL_CALLOUTS_DEREGISTERED;
+extern const isc::log::MessageID HOOKS_CALLOUTS_BEGIN;
+extern const isc::log::MessageID HOOKS_CALLOUTS_COMPLETE;
+extern const isc::log::MessageID HOOKS_CALLOUTS_REMOVED;
+extern const isc::log::MessageID HOOKS_CALLOUT_CALLED;
+extern const isc::log::MessageID HOOKS_CALLOUT_DEREGISTERED;
+extern const isc::log::MessageID HOOKS_CALLOUT_ERROR;
+extern const isc::log::MessageID HOOKS_CALLOUT_EXCEPTION;
+extern const isc::log::MessageID HOOKS_CALLOUT_REGISTRATION;
+extern const isc::log::MessageID HOOKS_CLOSE_ERROR;
+extern const isc::log::MessageID HOOKS_HOOK_LIST_RESET;
+extern const isc::log::MessageID HOOKS_INCORRECT_VERSION;
+extern const isc::log::MessageID HOOKS_LIBRARY_CLOSED;
+extern const isc::log::MessageID HOOKS_LIBRARY_LOADED;
+extern const isc::log::MessageID HOOKS_LIBRARY_LOADING;
+extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE;
+extern const isc::log::MessageID HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE;
+extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADED;
+extern const isc::log::MessageID HOOKS_LIBRARY_UNLOADING;
+extern const isc::log::MessageID HOOKS_LIBRARY_VERSION;
+extern const isc::log::MessageID HOOKS_LOAD_ERROR;
+extern const isc::log::MessageID HOOKS_LOAD_EXCEPTION;
+extern const isc::log::MessageID HOOKS_LOAD_FRAMEWORK_EXCEPTION;
+extern const isc::log::MessageID HOOKS_LOAD_SUCCESS;
+extern const isc::log::MessageID HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION;
+extern const isc::log::MessageID HOOKS_NO_LOAD;
+extern const isc::log::MessageID HOOKS_NO_UNLOAD;
+extern const isc::log::MessageID HOOKS_NO_VERSION;
+extern const isc::log::MessageID HOOKS_OPEN_ERROR;
+extern const isc::log::MessageID HOOKS_STD_CALLOUT_REGISTERED;
+extern const isc::log::MessageID HOOKS_UNLOAD_ERROR;
+extern const isc::log::MessageID HOOKS_UNLOAD_EXCEPTION;
+extern const isc::log::MessageID HOOKS_UNLOAD_FRAMEWORK_EXCEPTION;
+extern const isc::log::MessageID HOOKS_UNLOAD_SUCCESS;
+extern const isc::log::MessageID HOOKS_VERSION_EXCEPTION;
+
+} // namespace hooks
+} // namespace isc
+
+#endif // HOOKS_MESSAGES_H
diff --git a/src/lib/hooks/hooks_messages.mes b/src/lib/hooks/hooks_messages.mes
new file mode 100644
index 0000000..e33e7e4
--- /dev/null
+++ b/src/lib/hooks/hooks_messages.mes
@@ -0,0 +1,201 @@
+# Copyright (C) 2013-2020 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/.
+
+$NAMESPACE isc::hooks
+
+% HOOKS_ALL_CALLOUTS_DEREGISTERED hook library at index %1 removed all callouts on hook %2
+A debug message issued when all callouts on the specified hook registered
+by the library with the given index were removed. This is similar to
+the HOOKS_CALLOUTS_REMOVED message (and the two are likely to be seen
+together), but is issued at a lower-level in the hook framework.
+
+% HOOKS_CALLOUTS_BEGIN begin all callouts for hook %1
+This debug message is issued when callout manager begins to invoke callouts
+for the hook. The argument specifies the hook name.
+
+% HOOKS_CALLOUTS_COMPLETE completed callouts for hook %1 (total callouts duration: %2)
+This debug message is issued when callout manager has completed execution
+of all callouts for the particular hook. The arguments specify the hook
+name and total execution time for all callouts in milliseconds.
+
+% HOOKS_CALLOUTS_REMOVED callouts removed from hook %1 for library %2
+This is a debug message issued during library unloading. It notes that
+one of more callouts registered by that library have been removed from
+the specified hook. This is similar to the HOOKS_DEREGISTER_ALL_CALLOUTS
+message (and the two are likely to be seen together), but is issued at a
+higher-level in the hook framework.
+
+% HOOKS_CALLOUT_CALLED hooks library with index %1 has called a callout on hook %2 that has address %3 (callout duration: %4)
+Only output at a high debugging level, this message indicates that
+a callout on the named hook registered by the library with the given
+index (in the list of loaded libraries) has been called and returned a
+success state. The address of the callout is given in the message.
+The message includes the callout execution time in milliseconds.
+
+% HOOKS_CALLOUT_DEREGISTERED hook library at index %1 deregistered a callout on hook %2
+A debug message issued when all instances of a particular callouts on
+the hook identified in the message that were registered by the library
+with the given index have been removed.
+
+% HOOKS_CALLOUT_ERROR error returned by callout on hook %1 registered by library with index %2 (callout address %3) (callout duration %4)
+If a callout returns an error status when called, this error message
+is issued. It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout. The error is otherwise ignored.
+The error message includes the callout execution time in milliseconds.
+
+% HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3): %4 (callout duration: %5)
+If a callout throws an exception when called, this error message is
+issued. It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout. The error is otherwise ignored.
+The error message includes the callout execution time in milliseconds.
+
+% HOOKS_CALLOUT_REGISTRATION hooks library with index %1 registering callout for hook '%2'
+This is a debug message, output when a library (whose index in the list
+of libraries (being) loaded is given) registers a callout.
+
+% HOOKS_CLOSE_ERROR failed to close hook library %1: %2
+Kea has failed to close the named hook library for the stated reason.
+Although this is an error, this should not affect the running system
+other than as a loss of resources. If this error persists, you should
+restart Kea.
+
+% HOOKS_HOOK_LIST_RESET the list of hooks has been reset
+This is a message indicating that the list of hooks has been reset.
+While this is usual when running the Kea test suite, it should not be
+seen when running Kea in a production environment. If this appears,
+please report a bug through the usual channels.
+
+% HOOKS_INCORRECT_VERSION hook library %1 is at version %2, require version %3
+Kea has detected that the named hook library has been built against
+a version of Kea that is incompatible with the version of Kea
+running on your system. It has not loaded the library.
+
+This is most likely due to the installation of a new version of Kea
+without rebuilding the hook library. A rebuild and re-install of the
+library should fix the problem in most cases.
+
+% HOOKS_LIBRARY_CLOSED hooks library %1 successfully closed
+This information message is issued when a user-supplied hooks library
+has been successfully closed.
+
+% HOOKS_LIBRARY_LOADED hooks library %1 successfully loaded
+This information message is issued when a user-supplied hooks library
+has been successfully loaded.
+
+% HOOKS_LIBRARY_LOADING loading hooks library %1
+This is a debug message output just before the specified library is loaded.
+If the action is successfully, it will be followed by the
+HOOKS_LIBRARY_LOADED informational message.
+
+% HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE hooks library %1 reports its multi-threading compatibility as %2
+A debug message issued when the "multi_threading_compatible" function was
+called. The returned value (0 means not compatible, others compatible)
+is displayed.
+
+% HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE hooks library %1 is not compatible with multi-threading
+When multi-threading is enabled and the library is not compatible (either
+because the "multi_threading_compatible" function returned 0 or was not
+implemented) this error message is issued. The library must be removed
+from the configuration or the multi-threading disabled.
+
+% HOOKS_LIBRARY_UNLOADED hooks library %1 successfully unloaded
+This information message is issued when a user-supplied hooks library
+has been successfully unloaded.
+
+% HOOKS_LIBRARY_UNLOADING unloading library %1
+This is a debug message called when the specified library is
+being unloaded. If all is successful, it will be followed by the
+HOOKS_LIBRARY_UNLOADED informational message.
+
+% HOOKS_LIBRARY_VERSION hooks library %1 reports its version as %2
+A debug message issued when the version check on the hooks library
+has succeeded.
+
+% HOOKS_LOAD_ERROR 'load' function in hook library %1 returned error %2
+A "load" function was found in the library named in the message and
+was called. The function returned a non-zero status (also given in
+the message) which was interpreted as an error. The library has been
+unloaded and no callouts from it will be installed.
+
+% HOOKS_LOAD_EXCEPTION 'load' function in hook library %1 threw an exception
+A "load" function was found in the library named in the message and
+was called. The function threw an exception (an error indication)
+during execution, which is an error condition. The library has been
+unloaded and no callouts from it will be installed.
+
+% HOOKS_LOAD_FRAMEWORK_EXCEPTION 'load' function in hook library %1 threw an exception: reason %2
+A "load" function was found in the library named in the message and
+was called. Either the hooks framework or the function threw an
+exception (an error indication) during execution, which is an error
+condition; the cause of the exception is recorded in the message.
+The library has been unloaded and no callouts from it will be
+installed.
+
+% HOOKS_LOAD_SUCCESS 'load' function in hook library %1 returned success
+This is a debug message issued when the "load" function has been found
+in a hook library and has been successfully called.
+
+% HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION 'multi_threading_compatible' function in hook library %1 threw an exception
+This error message is issued if the multi_threading_compatible()
+function in the specified hooks library was called and generated an
+exception. The library is considered unusable and will not be loaded.
+
+% HOOKS_NO_LOAD no 'load' function found in hook library %1
+This is a debug message saying that the specified library was loaded
+but no function called "load" was found in it. Providing the library
+contained some "standard" functions (i.e. functions with the names of
+the hooks for the given server), this is not an issue.
+
+% HOOKS_NO_UNLOAD no 'unload' function found in hook library %1
+This is a debug message issued when the library is being unloaded.
+It merely states that the library did not contain an "unload" function.
+
+% HOOKS_NO_VERSION no 'version' function found in hook library %1
+The shared library named in the message was found and successfully loaded,
+but Kea did not find a function named "version" in it. This function
+is required and should return the version of Kea against which the
+library was built. The value is used to check that the library was built
+against a compatible version of Kea. The library has not been loaded.
+
+% HOOKS_OPEN_ERROR failed to open hook library %1: %2
+Kea failed to open the specified hook library for the stated
+reason. The library has not been loaded. Kea will continue to
+function, but without the services offered by the library.
+
+% HOOKS_STD_CALLOUT_REGISTERED hooks library %1 registered standard callout for hook %2 at address %3
+This is a debug message, output when the library loading function has
+located a standard callout (a callout with the same name as a hook point)
+and registered it. The address of the callout is indicated.
+
+% HOOKS_UNLOAD_ERROR 'unload' function in hook library %1 returned error %2
+During the unloading of a library, an "unload" function was found.
+It was called, but returned an error (non-zero) status, resulting in
+the issuing of this message. The unload process continued after this
+message and the library has been unloaded.
+
+% HOOKS_UNLOAD_EXCEPTION 'unload' function in hook library %1 threw an exception
+During the unloading of a library, an "unload" function was found. It was
+called, but in the process generated an exception (an error indication).
+The unload process continued after this message and the library has
+been unloaded.
+
+% HOOKS_UNLOAD_FRAMEWORK_EXCEPTION 'unload' function in hook library %1 threw an exception, reason %2
+During the unloading of a library, an "unload" function was found.
+It was called, but in the process either it or the hooks framework
+generated an exception (an error indication); the cause of the error
+is recorded in the message. The unload process continued after
+this message and the library has been unloaded.
+
+% HOOKS_UNLOAD_SUCCESS 'unload' function in hook library %1 returned success
+This is a debug message issued when an "unload" function has been found
+in a hook library during the unload process, called, and returned success.
+
+% HOOKS_VERSION_EXCEPTION 'version' function in hook library %1 threw an exception
+This error message is issued if the version() function in the specified
+hooks library was called and generated an exception. The library is
+considered unusable and will not be loaded.
diff --git a/src/lib/hooks/hooks_parser.cc b/src/lib/hooks/hooks_parser.cc
new file mode 100644
index 0000000..cfd4a7e
--- /dev/null
+++ b/src/lib/hooks/hooks_parser.cc
@@ -0,0 +1,110 @@
+// Copyright (C) 2017 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 <cc/data.h>
+#include <cc/dhcp_config_error.h>
+#include <hooks/hooks_parser.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <util/strutil.h>
+#include <vector>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::dhcp;
+
+namespace isc {
+namespace hooks {
+
+// @todo use the flat style, split into list and item
+
+void
+HooksLibrariesParser::parse(HooksConfig& libraries, ConstElementPtr value) {
+ // Initialize.
+ libraries.clear();
+
+ if (!value) {
+ isc_throw(DhcpConfigError, "Tried to parse null hooks libraries");
+ }
+
+ // This is the new syntax. Iterate through it and get each map.
+ BOOST_FOREACH(ConstElementPtr library_entry, value->listValue()) {
+ ConstElementPtr parameters;
+
+ // Is it a map?
+ if (library_entry->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "hooks library configuration error:"
+ " one or more entries in the hooks-libraries list is not"
+ " a map (" << library_entry->getPosition() << ")");
+ }
+
+ // Iterate through each element in the map. We check
+ // whether we have found a library element.
+ bool lib_found = false;
+
+ string libname = "";
+
+ // Let's explicitly reset the parameters, so we won't cover old
+ // values from the previous loop round.
+ parameters.reset();
+
+ BOOST_FOREACH(auto entry_item, library_entry->mapValue()) {
+ if (entry_item.first == "library") {
+ if (entry_item.second->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "hooks library configuration"
+ " error: value of 'library' element is not a string"
+ " giving the path to a hooks library (" <<
+ entry_item.second->getPosition() << ")");
+ }
+
+ // Get the name of the library and add it to the list after
+ // removing quotes.
+ libname = (entry_item.second)->stringValue();
+
+ // Remove leading/trailing quotes and any leading/trailing
+ // spaces.
+ boost::erase_all(libname, "\"");
+ libname = isc::util::str::trim(libname);
+ if (libname.empty()) {
+ isc_throw(DhcpConfigError, "hooks library configuration"
+ " error: value of 'library' element must not be"
+ " blank (" <<
+ entry_item.second->getPosition() << ")");
+ }
+
+ // Note we have found the library name.
+ lib_found = true;
+ continue;
+ }
+
+ // If there are parameters, let's remember them.
+ if (entry_item.first == "parameters") {
+ parameters = entry_item.second;
+ continue;
+ }
+
+ // For all other parameters we will throw.
+ isc_throw(DhcpConfigError, "unknown hooks library parameter: "
+ << entry_item.first << "("
+ << library_entry->getPosition() << ")");
+ }
+
+ if (! lib_found) {
+ isc_throw(DhcpConfigError, "hooks library configuration error:"
+ " one or more hooks-libraries elements are missing the"
+ " name of the library" <<
+ " (" << library_entry->getPosition() << ")");
+ }
+
+ libraries.add(libname, parameters);
+ }
+}
+
+}
+}
diff --git a/src/lib/hooks/hooks_parser.h b/src/lib/hooks/hooks_parser.h
new file mode 100644
index 0000000..4bec9a6
--- /dev/null
+++ b/src/lib/hooks/hooks_parser.h
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 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 HOOKS_PARSER_H
+#define HOOKS_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <hooks/hooks_config.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Parser for hooks library list
+///
+/// This parser handles the list of hooks libraries. This is an optional list,
+/// which may be empty, and is encapsulated into a @ref HooksConfig object.
+class HooksLibrariesParser : public isc::data::SimpleParser {
+public:
+
+ /// @brief Parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the hooks libraries list. The method also checks whether the
+ /// list of libraries is the same as that already loaded. If not, it
+ /// checks each of the libraries in the list for validity (they exist and
+ /// have a "version" function that returns the correct value).
+ ///
+ /// The syntax for specifying hooks libraries allow for library-specific
+ /// parameters to be specified along with the library, e.g.
+ ///
+ /// @code
+ /// "hooks-libraries": [
+ /// {
+ /// "library": "hook-lib-1.so",
+ /// "parameters": {
+ /// "alpha": "a string",
+ /// "beta": 42
+ /// }
+ /// },
+ /// :
+ /// ]
+ /// @endcode
+ ///
+ /// The parsing code only checks that:
+ ///
+ /// -# Each element in the hooks-libraries list is a map
+ /// -# The map contains an element "library" whose value is a not blank string
+ /// -# That there is an optional 'parameters' element.
+ /// -# That there are no other element.
+ ///
+ /// This method stores parsed libraries in libraries.
+ ///
+ /// @param libraries parsed libraries information will be stored here
+ /// @param value pointer to the content to be parsed
+ void parse(HooksConfig& libraries, isc::data::ConstElementPtr value);
+};
+
+}; // namespace isc::hooks
+}; // namespace isc
+
+#endif
diff --git a/src/lib/hooks/hooks_user.dox b/src/lib/hooks/hooks_user.dox
new file mode 100644
index 0000000..2a51b93
--- /dev/null
+++ b/src/lib/hooks/hooks_user.dox
@@ -0,0 +1,1670 @@
+// Copyright (C) 2013-2021 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/.
+
+// Note: the prefix "hooksdg" to all labels is an abbreviation for "Hooks
+// Developer's Guide" and is used to prevent a clash with symbols in any
+// other Doxygen file.
+
+/**
+ @page hooksdgDevelopersGuide Hooks Developer's Guide
+
+ @section hooksdgIntroduction Introduction
+
+Although the Kea framework and its DHCP programs
+provide comprehensive functionality, there will be times when it does
+not quite do what you require: the processing has to be extended in some
+way to solve your problem.
+
+Since the Kea source code is freely available (Kea being an
+open-source project), one option is to modify it to do what
+you want. Whilst perfectly feasible, there are drawbacks:
+
+- Although well-documented, Kea is a large program. Just
+understanding how it works will take a significant amount of time. In
+addition, despite the fact that its object-oriented design keeps the
+coupling between modules to a minimum, an inappropriate change to one
+part of the program during the extension could cause another to
+behave oddly or to stop working altogether.
+
+- The change may need to be re-applied or re-written with every new
+version of Kea. As new functionality is added or bugs are fixed,
+the code or algorithms in the core software may change - and may change
+significantly.
+
+To overcome these problems, Kea provides the "Hooks" interface -
+a defined interface for third-party or user-written code. (For ease of
+reference in the rest of this document, all such code will be referred
+to as "user code".) At specific points in its processing
+("hook points") Kea will make a call to this code. The call passes
+data that the user code can examine and, if required, modify.
+Kea uses the modified data in the remainder of its processing.
+
+In order to minimize the interaction between Kea and the user code,
+the latter is built independently of Kea in the form of one or more
+dynamic shared objects, called here (for historical reasons), shared
+libraries. These are made known to Kea through its configuration
+mechanism, and Kea loads the library at run time. Libraries can be
+unloaded and reloaded as needed while Kea is running.
+
+Use of a defined API and the Kea configuration mechanism means that
+as new versions of Kea are released, there is no need to modify
+the user code. Unless there is a major change in an interface
+(which will be clearly documented), all that will be required is a rebuild
+of the libraries.
+
+@note Although the defined interface should not change, the internals
+of some of the classes and structures referenced by the user code may
+change between versions of Kea. These changes have to be reflected
+in the compiled version of the software, hence the need for a rebuild.
+
+
+@subsection hooksdgLanguages Languages
+
+The core of Kea is written in C++. While it is the intention to
+provide interfaces into user code written in other languages, the initial
+versions of the Hooks system required that user code be written in C++.
+It is no longer the case and there are examples of hooks written for
+instance in Python but this guide does not document how to do that.
+All examples in this guide are in C++.
+
+
+@subsection hooksdgTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Hook/Hook Point - used interchangeably, this is a point in the code at
+which a call to user functions is made. Each hook has a name and
+each hook can have any number (including 0) of user functions
+attached to it.
+
+- Callout - a user function called by the server at a hook
+point. This is so-named because the server "calls out" to the library
+to execute a user function.
+
+- Framework function - the functions that a user library needs to
+supply in order for the hooks framework to load and unload the library.
+
+- User code/user library - non-Kea code that is compiled into a
+shared library and loaded by Kea into its address space.
+
+
+@section hooksdgTutorial Tutorial
+
+To illustrate how to write code that integrates with Kea, we will
+use the following (rather contrived) example:
+
+<i>The Kea DHCPv4 server is used to allocate IPv4 addresses to clients
+(as well as to pass them other information such as the address of DNS
+servers). We will suppose that we need to classify clients requesting
+IPv4 addresses according to their hardware address, and want to log both
+the hardware address and allocated IP address for the clients of interest.</i>
+
+The following sections describe how to implement these requirements.
+The code presented here is not efficient and there are better ways of
+doing the task. The aim however, is to illustrate the main features of
+user hooks code, not to provide an optimal solution.
+
+
+@subsection hooksdgFrameworkFunctions Framework Functions
+
+Loading and initializing a library holding user code makes use
+of three (user-supplied) functions:
+
+- version - defines the version of Kea code with which the user-library
+is built
+- load - called when the library is loaded by the server.
+- unload - called when the library is unloaded by the server.
+- multi_threading_compatible - defines the compatibility (or not) of
+the user-library and a multi-threaded DHCP service.
+
+Of these, only "version" is mandatory, although in our example, all four
+are used.
+
+@subsubsection hooksdgVersionFunction The "version" Function
+
+"version" is used by the hooks framework to check that the libraries
+it is loading are compatible with the version of Kea being run.
+Although the hooks system allows Kea and user code to interface
+through a defined API, the relationship is somewhat tight in that the
+user code will depend on the internal structures of Kea. If these
+change - as they can between Kea releases - and Kea is run with
+a version of user code built against an earlier version of Kea, a program
+crash could result.
+
+To guard against this, the "version" function must be provided in every
+library. It returns a constant defined in header files of the version
+of Kea against which it was built. The hooks framework checks this
+for compatibility with the running version of Kea before loading
+the library.
+
+In this tutorial, we'll put "version" in its own file, version.cc. The
+contents are:
+
+@code
+// version.cc
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+}
+@endcode
+
+The file "hooks/hooks.h" is specified relative to the Kea libraries
+source directory - this is covered later in the section @ref hooksdgBuild.
+It defines the symbol KEA_HOOKS_VERSION, which has a value that changes
+on every release of Kea: this is the value that needs to be returned
+to the hooks framework.
+
+A final point to note is that the definition of "version" is enclosed
+within 'extern "C"' braces. All functions accessed by the hooks
+framework use C linkage, mainly to avoid the name mangling that
+accompanies use of the C++ compiler, but also to avoid issues related
+to namespaces.
+
+@subsubsection hooksdgLoadUnloadFunctions The "load" and "unload" Functions
+
+As the names suggest, "load" is called when a library is loaded and
+"unload" called when it is unloaded. (It is always guaranteed that
+"load" is called: "unload" may not be called in some circumstances,
+e.g., if the system shuts down abnormally.) These functions are the
+places where any library-wide resources are allocated and deallocated.
+"load" is also the place where any callouts with non-standard names
+(names that are not hook point names) can be registered:
+this is covered further in the section @ref hooksdgCalloutRegistration.
+
+The example does not make any use callouts with non-standard names. However,
+as our design requires that the log file be open while Kea is active
+and the library loaded, we'll open the file in the "load" function and close
+it in "unload".
+
+We create two files, one for the file handle declaration:
+
+@code
+// library_common.h
+
+#ifndef LIBRARY_COMMON_H
+#define LIBRARY_COMMON_H
+
+#include <fstream>
+
+// "Interesting clients" log file handle declaration.
+extern std::fstream interesting;
+
+#endif // LIBRARY_COMMON_H
+@endcode
+
+... and one to hold the "load" and "unload" functions:
+
+@code
+// load_unload.cc
+
+#include <hooks/hooks.h>
+#include "library_common.h"
+
+using namespace isc::hooks;
+
+// "Interesting clients" log file handle definition.
+std::fstream interesting;
+
+extern "C" {
+
+int load(LibraryHandle&) {
+ interesting.open("/data/clients/interesting.log",
+ std::fstream::out | std::fstream::app);
+ return (interesting ? 0 : 1);
+}
+
+int unload() {
+ if (interesting) {
+ interesting.close();
+ }
+ return (0);
+}
+
+}
+@endcode
+
+Notes:
+- The file handle ("interesting") is declared in a header file and defined
+outside of any function. This means it can be accessed by any function
+within the user library. For convenience, the definition is in the
+load_unload.cc file.
+- "load" is called with a LibraryHandle argument, this being used in
+the registration of functions. As no functions are being registered
+in this example, the argument specification omits the variable name
+(whilst retaining the type) to avoid an "unused variable" compiler
+warning. (The LibraryHandle and its use is discussed in the section
+@ref hooksdgLibraryHandle.)
+- In the initial version of the hooks framework, it was not possible to pass
+any configuration information to the "load" function. The name of the log
+file had therefore to be hard-coded as an absolute path name or communicated
+to the user code by some other means.
+- "load" must return 0 on success and non-zero on error. The hooks framework
+will abandon the loading of the library if "load" returns an error status.
+(In this example, "interesting" can be tested as a boolean value,
+returning "true" if the file opened successfully.)
+- "unload" closes the log file if it is open and is a no-op otherwise. As
+with "load", a zero value must be returned on success and a non-zero value
+on an error. The hooks framework will record a non-zero status return
+as an error in the current Kea log but otherwise ignore it.
+- As before, the function definitions are enclosed in 'extern "C"' braces.
+
+In some cases to restrict the library loading to DHCP servers so it
+cannot be loaded by the DDNS server or the Control Agent.
+The best way to perform this is to check the process name returned
+by @c isc::dhcp::Daemon::getProcName() static / class method declared
+in process/daemon.h header against "kea-dhcp4" and "kea-dhcp6"
+(other values are "kea-dhcp-ddns", "kea-ctrl-agent" and "kea-netconf").
+If you'd like to check the address family too it is returned in DHCP servers
+by isc::dhcp::CfgMgr::instance().getFamily() declared in dhcpsrv/cfgmgr.h
+with AF_INET and AF_INET6 values.
+
+@subsubsection hooksdgMultiThreadingCompatibleFunction The "multi_threading_compatible" function
+
+"multi_threading_compatible" is used by the hooks framework to check
+if the libraries it is loading are compatible with the DHCPv4 or DHCPv6
+server multi-threading configuration. The value 0 means not compatible
+and is the default when the function is not implemented. Non 0 values
+mean compatible.
+
+If your code implements it and returns the value 0 it is recommended
+to document the reason so someone revisiting the code will not by
+accident change the code.
+
+To be compatible means:
+- the code associated with DHCP packet processing callouts e.g.
+pkt4_receive or pkt6_send must be thread safe so the multi-threaded
+DHCP service can simultaneously call more than once one of these callouts.
+- commands registered by a library are not required to be thread safe because
+commands are executed by the main thread. Now it is a good idea to make
+them thread safe and to document cases where they are not.
+- when a library implements a thread safe backend API (e.g. host data
+source) the service methods must be thread safe.
+- a library which modifies the internal configuration of the server,
+e.g. creates or deletes a subnet, it must enter a critical section using
+the @c isc::util::MultiThreadingCriticalSection RAII class.
+
+In the tutorial, we'll put "multi_threading_compatible" in its own file,
+multi_threading_compatible.cc. The contents are:
+
+@code
+// multi_threading_compatible.cc
+
+extern "C" {
+
+int multi_threading_compatible() {
+ return (1);
+}
+
+}
+@endcode
+
+and for a command creating a new subnet:
+
+@code
+#include <util/multi_threading_mgr.h>
+
+int commandHandler(CalloutHandle& handle) {
+ ...
+ {
+ // Enter the critical section.
+ isc::util::MultiThreadingCriticalSection ct;
+ <add the subnet>
+ // Leave the critical section.
+ }
+ ...
+}
+@endcode
+
+@ref hooksMultiThreading provides more details about thread safety
+requirements.
+
+@subsection hooksdgCallouts Callouts
+
+Having sorted out the framework, we now come to the functions that
+actually do something. These functions are known as "callouts" because
+the Kea code "calls out" to them. Each Kea server has a number of
+hooks to which callouts can be attached: server-specific documentation
+describes in detail the points in the server at which the hooks are
+present together with the data passed to callouts attached to them.
+
+Before we continue with the example, we'll discuss how arguments are
+passed to callouts and information is returned to the server. We will
+also discuss how information can be moved between callouts.
+
+@subsubsection hooksdgCalloutSignature The Callout Signature
+
+All callouts are declared with the signature:
+@code
+extern "C" {
+int callout(CalloutHandle& handle);
+}
+@endcode
+
+(As before, the callout is declared with "C" linkage.) Information is passed
+between Kea and the callout through name/value pairs in the @c CalloutHandle
+object. The object is also used to pass information between callouts on a
+per-request basis. (Both of these concepts are explained below.)
+
+A callout returns an @c int as a status return. A value of 0 indicates
+success, anything else signifies an error. The status return has no
+effect on server processing; the only difference between a success
+and error code is that if the latter is returned, the server will
+log an error, specifying both the library and hook that generated it.
+Effectively the return status provides a quick way for a callout to log
+error information to the Kea logging system.
+
+@subsubsection hooksdgArguments Callout Arguments
+
+The @c CalloutHandle object provides two methods to get and set the
+arguments passed to the callout. These methods are called (naturally
+enough) getArgument and setArgument. Their usage is illustrated by the
+following code snippets.
+
+@code
+ // Server-side code snippet to show the setting of arguments
+
+ int count = 10;
+ boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+ // Assume that "handle" has been created
+ handle.setArgument("data_count", count);
+ handle.setArgument("inpacket", pktptr);
+
+ // Call the callouts attached to the hook
+ ...
+
+ // Retrieve the modified values
+ handle.getArgument("data_count", count);
+ handle.getArgument("inpacket", pktptr);
+@endcode
+
+In the callout
+
+@code
+ int number;
+ boost::shared_ptr<Pkt4> packet;
+
+ // Retrieve data set by the server.
+ handle.getArgument("data_count", number);
+ handle.getArgument("inpacket", packet);
+
+ // Modify "number"
+ number = ...;
+
+ // Update the arguments to send the value back to the server.
+ handle.setArgument("data_count", number);
+@endcode
+
+As can be seen @c getArgument is used to retrieve data from the
+@c CalloutHandle, and @c setArgument used to put data into it. If a callout
+wishes to alter data and pass it back to the server, it should retrieve
+the data with @c getArgument, modify it, and call @c setArgument to send
+it back.
+
+There are several points to be aware of:
+
+- the data type of the variable in the call to @c getArgument must match
+the data type of the variable passed to the corresponding @c setArgument
+<B>exactly</B>: using what would normally be considered to be a
+"compatible" type is not enough. For example, if the server passed
+an argument as an @c int and the callout attempted to retrieve it as a
+@c long, an exception would be thrown even though any value that can
+be stored in an @c int will fit into a @c long. This restriction also
+applies the "const" attribute but only as applied to data pointed to by
+pointers, e.g., if an argument is defined as a @c char*, an exception will
+be thrown if an attempt is made to retrieve it into a variable of type
+@c const @c char*. (However, if an argument is set as a @c const @c int,
+it can be retrieved into an @c int.) The documentation of each hook
+point will detail the data type of each argument.
+- Although all arguments can be modified, some altered values may not
+be read by the server. (These would be ones that the server considers
+"read-only".) Consult the documentation of each hook to see whether an
+argument can be used to transfer data back to the server.
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the server even if no call is made to setArgument.
+
+In all cases, consult the documentation for the particular hook to see whether
+parameters can be modified. As a general rule:
+
+- Do not alter arguments unless you mean the change to be reflected in
+the server.
+- If you alter an argument, call @c CalloutHandle::setArgument to update the
+value in the @c CalloutHandle object.
+
+@subsubsection hooksdgNextStep The Next step status
+
+Note: This functionality used to be provided in Kea 0.9.2 and earlier using
+boolean skip flag. See @ref hooksdgSkipFlag for explanation and tips how
+to migrate your hooks code to this new API.
+
+When a to callouts attached to a hook returns, the server will usually continue
+its processing. However, a callout might have done something that means that
+the server should follow another path. Possible actions a server could take
+include:
+
+- Continue as usual. This is the default value. Unless callouts explicitly
+change the status, the server will continue processing. There is no need
+to set the status, unless one callout wants to override the status set
+by another callout. This action is represented by CalloutHandle::NEXT_STEP_CONTINUE.
+
+- Skip the next stage of processing because the callout has already
+done it. For example, a hook is located just before the DHCP server
+allocates an address to the client. A callout may decide to allocate
+special addresses for certain clients, in which case it needs to tell
+the server not to allocate an address in this case. This action is
+hook specific and is represented by CalloutHandle::NEXT_STEP_SKIP.
+
+- Drop the packet and continue with the next request. A possible scenario
+is a server where a callout inspects the hardware address of the client
+sending the packet and compares it against a black list; if the address
+is on it, the callout notifies the server to drop the packet. This
+action is represented by CalloutHandle::NEXT_STEP_DROP.
+
+To handle these common cases, the @c CalloutHandle has a setStatus method.
+This is set by a callout when it wishes the server to change the normal
+processing. Exact meaning is hook specific. Please consult hook API
+documentation for details. For historic reasons (Kea 0.9.2 used a single
+boolean flag called skip that also doubled in some cases as an indicator
+to drop the packet) several hooks use SKIP status to drop the packet.
+
+The methods to get and set the "skip" or "drop" state are getStatus and
+setStatus. Their usage is intuitive:
+
+@code
+ // Get the current setting of the next step status.
+ auto status = handle.getStatus();
+
+ if (status == CalloutHandle::NEXT_STEP_DROP)
+ // Do something...
+ :
+
+ if (status == CalloutHandle::NEXT_STEP_SKIP)
+ // Do something...
+ :
+
+ // Do some processing...
+ :
+ if (lease_allocated) {
+ // Flag the server to skip the next step of the processing as we
+ // already have an address.
+ handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+ }
+ return;
+
+@endcode
+
+Like arguments, the next step status is passed to all callouts on a hook. Callouts
+later in the list are able to examine (and modify) the settings of earlier ones.
+
+If using multiple libraries, when the library wants to drop the current packet,
+the DROP status must be used instead of the SKIP status so that the packet
+processing ends at that specific hook point.
+
+It is recommended for all callouts to check the status before doing any
+processing. As callouts can modify the status, it is recommended to take good
+care when doing so, because this will have impact on all remaining hooks as well.
+It is highly recommended to not reset the SKIP or DROP status to CONTINUE, even
+though possible, so that the rest of the loaded hooks and the server can check
+and perform the proper action.
+
+Some hook points handle special functionality for the server, like pkt4_receive,
+pkt6_receive, which handle unpacking of the received packet, pkt4_send, pkt6_send,
+which handle packing of the response packet.
+
+If the hook handles these actions and sets the next step flag to SKIP, it should
+also perform a check for the SKIP flag before anything else. If it is already
+set, do not pack/unpack the packet (other library, or even the same library, if
+loaded multiple times, has done it already). Some libraries might also need to
+throw exceptions in such cases because they need to perform specific actions before
+pack/unpack (eg. addOption/delOption before pack action), which have no effect if
+pack/unpack action is done previously by some other library.
+
+@code
+ // Check if other library has already set SKIP flag and performed unpack
+ // so that unpack is skipped
+ if (handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
+ query->unpack();
+ }
+@endcode
+
+@code
+ // Check the status state.
+ auto status = handle.getStatus();
+ if (status == CalloutHandle::NEXT_STEP_SKIP) {
+ isc_throw(InvalidOperation, "packet pack already handled");
+ }
+ ...
+ response->delOption(DEL_OPTION_CODE);
+ ...
+ response->addOption(ADD_OPTION_CODE);
+ ...
+ response->pack();
+@endcode
+
+As stated before, the order of loading libraries is critical in achieving the
+desired behavior, so please read @ref hooksdgMultipleLibraries when configuring
+multiple libraries.
+
+@subsubsection hooksdgSkipFlag The "Skip" Flag (deprecated)
+
+In releases 0.9.2 and earlier, the functionality currently offered by next step
+status (see @ref hooksdgNextStep) was provided by
+a boolean flag called "Skip". However, since it only allowed to either continue
+or skip the next processing step and was not extensible to other decisions,
+setSkip(bool) call was replaced with a setStatus(enum) in Kea 1.0. This
+new approach is extensible. If we decide to add new results (e.g., WAIT
+or RATELIMIT), we will be able to do so without changing the API again.
+
+If you have your hooks libraries that take advantage of skip flag, migrating
+to the next step status is very easy. See @ref hooksdgNextStep for detailed
+explanation of the new status field.
+
+To migrate, replace this old code:
+@code
+handle.setSkip(false); // This is the default.
+
+handle.setSkip(true); // Tell the server to skip the next processing step.
+
+bool skip = hangle.getSkip(); // Check the skip flag state.
+if (skip) {
+ ...
+}
+@endcode
+
+with this:
+
+@code
+// This is the default.
+handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+
+// Tell the server to skip the next processing step.
+handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+
+// Check the status state.
+auto status = handle.getStatus();
+if (status == CalloutHandle::NEXT_STEP_SKIP) {
+ ...
+}
+@endcode
+
+@subsubsection hooksdgCalloutContext Per-Request Context
+
+Although the Kea modules can be characterized as handling a single
+packet at a time - e.g., the DHCPv4 server receives a DHCPDISCOVER packet,
+processes it and responds with an DHCPOFFER, this may not always be true.
+Future developments may have the server processing multiple packets
+simultaneously, or to suspend processing on a packet and resume it at
+a later time after other packets have been processed.
+
+As well as argument information, the @c CalloutHandle object can be used by
+callouts to attach information to a packet being handled by the server.
+This information (known as "context") is not used by the server: its purpose
+is to allow callouts to pass information between one another on a
+per-packet basis.
+
+Context associated with a packet only exists only for the duration of the
+processing of that packet: when processing is completed, the context is
+destroyed. A new packet starts with a new (empty) context. Context is
+particularly useful in servers that may be processing multiple packets
+simultaneously: callouts can effectively attach data to a packet that
+follows the packet around the system.
+
+Context information is held as name/value pairs in the same way
+as arguments, being accessed by the pair of methods @c setContext and
+@c getContext. They have the same restrictions as the @c setArgument and
+@c getArgument methods - the type of data retrieved from context must
+<B>exactly</B> match the type of the data set.
+
+The example in the next section illustrates their use.
+
+
+@subsection hooksdgExampleCallouts Example Callouts
+
+Continuing with the tutorial, the requirements need us to retrieve the
+hardware address of the incoming packet, classify it, and write it,
+together with the assigned IP address, to a log file. Although we could
+do this in one callout, for this example we'll use two:
+
+- pkt4_receive - a callout on this hook is invoked when a packet has been
+received and has been parsed. It is passed a single argument, "query4"
+which is an isc::dhcp::Pkt4Ptr object, holding a pointer to the
+isc::dhcp::Pkt4 object (representing a DHCPv4 packet). We will do the
+classification here.
+
+- pkt4_send - called when a response is just about to be sent back to
+the client. It is passed a single argument "response4". This is the
+point at which the example code will write the hardware and IP addresses
+to the log file.
+
+The standard for naming callouts is to give them the same name as
+the hook. If this is done, the callouts will be automatically found
+by the Hooks system (this is discussed further in section @ref
+hooksdgCalloutRegistration). For our example, we will assume this is the
+case, so the code for the first callout (used to classify the client's
+hardware address) is:
+
+@code
+// pkt_receive4.cc
+
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include "library_common.h"
+
+#include <string>
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+// This callout is called at the "pkt4_receive" hook.
+int pkt4_receive(CalloutHandle& handle) {
+
+ // A pointer to the packet is passed to the callout via a "boost" smart
+ // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4
+ // object as Pkt4Ptr. Retrieve a pointer to the object.
+ Pkt4Ptr query4_ptr;
+ handle.getArgument("query4", query4_ptr);
+
+ // Point to the hardware address.
+ HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr();
+
+ // The hardware address is held in a public member variable. We'll classify
+ // it as interesting if the sum of all the bytes in it is divisible by 4.
+ // (This is a contrived example after all!)
+ long sum = 0;
+ for (int i = 0; i < hwaddr_ptr->hwaddr_.size(); ++i) {
+ sum += hwaddr_ptr->hwaddr_[i];
+ }
+
+ // Classify it.
+ if (sum % 4 == 0) {
+ // Store the text form of the hardware address in the context to pass
+ // to the next callout.
+ string hwaddr = hwaddr_ptr->toText();
+ handle.setContext("hwaddr", hwaddr);
+ }
+
+ return (0);
+};
+
+}
+@endcode
+
+The "pkt4_receive" callout placed the hardware address of an interesting client in
+the "hwaddr" context for the packet. Turning now to the callout that will
+write this information to the log file:
+
+@code
+// pkt4_send.cc
+
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include "library_common.h"
+
+#include <string>
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+// This callout is called at the "pkt4_send" hook.
+int pkt4_send(CalloutHandle& handle) {
+
+ // Obtain the hardware address of the "interesting" client. We have to
+ // use a try...catch block here because if the client was not interesting,
+ // no information would be set and getArgument would thrown an exception.
+ string hwaddr;
+ try {
+ handle.getContext("hwaddr", hwaddr);
+
+ // getContext didn't throw so the client is interesting. Get a pointer
+ // to the reply.
+ Pkt4Ptr response4_ptr;
+ handle.getArgument("response4", response4_ptr);
+
+ // Get the string form of the IP address.
+ string ipaddr = response4_ptr->getYiaddr().toText();
+
+ // Write the information to the log file.
+ interesting << hwaddr << " " << ipaddr << "\n";
+
+ // ... and to guard against a crash, we'll flush the output stream.
+ flush(interesting);
+
+ } catch (const NoSuchCalloutContext&) {
+ // No such element in the per-request context with the name "hwaddr".
+ // This means that the request was not an interesting, so do nothing
+ // and dismiss the exception.
+ }
+
+ return (0);
+}
+
+}
+@endcode
+
+
+@subsection hooksdgLogging Logging in the Hooks Library
+
+Hooks libraries take part in the DHCP message processing. They also often
+modify the server's behavior by taking responsibility for processing
+the DHCP message at certain stages and instructing the server to skip
+the default processing for that stage. Thus, hooks libraries play an
+important role in the DHCP server operation and, depending on their
+purpose, they may have high complexity, which increases likelihood of the
+defects in the libraries.
+
+All hooks libraries should use Kea logging system to facilitate diagnostics
+of the defects in the libraries and issues with the DHCP server's operation.
+Even if the issue doesn't originate in the hooks library itself, the use
+of the library may uncover issues in the Kea code that only
+manifest themselves in some special circumstances.
+
+Hooks libraries use the Kea logging system in the same way as any other
+standard Kea library. A hooks library should have at least one logger
+defined, but may have multiple loggers if it is desired
+to separate log messages from different functional parts of the library.
+
+Assuming that it has been decided to use logging in the hooks library, the
+implementor must select a unique name for the logger. Ideally the name
+should have some relationship with the name of the library so that it is
+easy to distinguish messages logged from this library. For example,
+if the hooks library is used to capture incoming and outgoing DHCP
+messages, and the name of the library is "libkea-packet-capture",
+a suitable logger name could be "packet-capture".
+
+In order to use a logger within the library, the logger should be declared
+in a header file, which must be included in all files using
+the logger:
+
+@code
+#ifndef PACKET_CAPTURE_LOG_H
+#define PACKET_CAPTURE_LOG_H
+
+#include <log/message_initializer.h>
+#include <log/macros.h>
+#include <user_chk_messages.h>
+
+namespace packet_capture {
+
+extern isc::log::Logger packet_capture_logger;
+
+}
+
+#endif
+@endcode
+
+The logger should be defined and initialized in the implementation file,
+as illustrated below:
+
+@code
+#include <packet_capture_log.h>
+
+namespace packet_capture {
+
+isc::log::Logger packet_capture_logger("packet-capture");
+
+}
+@endcode
+
+These files may contain multiple logger declarations and initializations
+when the use of more than one logger is desired.
+
+The next step is to add the appropriate message file as described in the
+@ref logMessageFiles.
+
+The implementor must make sure that log messages appear in the right
+places and that they are logged at the appropriate level. The choice
+of the place where the message should appear is not always obvious:
+it depends if the particular function being called already logs enough
+information and whether adding log message before and/or after the
+call to this function would simply duplicate some messages. Sometimes
+the choice whether the log message should appear within the function or
+outside of it depends on the level of details available for logging. For
+example, in many cases it is desirable to include the client identifier
+or transaction id of the DHCP packet being processed in logging message.
+If this information is available at the higher level but not in the
+function being called, it is often better to place the log message at
+higher level. However, the function parameters list could be extended
+to include the additional information, and to be logged and the logging
+call made from within the function.
+
+Ideally, the hooks library should contain debug log messages (traces)
+in all significant decision points in the code, with the information as to
+how the code hit this decision point, how it will proceed and why.
+However, care should be taken when selecting the log level for those
+messages, because selecting too high logging level may impact the
+performance of the system. For this reason, traces (messages of
+the debug severity) should use different debug levels for the
+messages of different importance or having different performance
+requirements to generate the log message. For example, generation of
+a log message, which prints full details of a packet, usually requires
+more CPU bandwidth than the generation of the message which only prints
+the packet type and length. Thus, the former should be logged at
+lower debug level (see @ref logSeverity for details of using
+various debug levels using "dbglevel" parameter).
+
+All loggers defined within the hooks libraries derive the default
+configuration from the root logger. For example, when the hooks
+library is attached to the DHCPv4 server, the root logger name is
+"kea-dhcp4", and the library by default uses configuration of this
+logger. The configuration of the library's logger can
+be modified by adding a configuration entry for it
+to the configuration file. In case of the "packet-capture"
+logger declared above, the full name of the logger in the
+configuration file will be "kea-dhcp4.packet-capture". The
+configuration specified for this logger will override the default
+configuration derived from the root logger.
+
+
+@subsection hooksdgBuild Building the Library
+
+Building the code requires building a sharable library. This requires
+the the code be compiled as position-independent code (using the
+compiler's "-fpic" switch) and linked as a shared library (with the
+linker's "-shared" switch). The build command also needs to point to
+the Kea include directory and link in the appropriate libraries.
+
+Assuming that Kea has been installed in the default location, the
+command line needed to create the library using the Gnu C++ compiler on a
+Linux system is:
+
+@code
+g++ -I <install-dir>/include/kea -L <install-dir>/lib -fpic -shared -o example.so \
+ load_unload.cc pkt4_receive.cc pkt4_send.cc version.cc \
+ -lkea-dhcpsrv -lkea-dhcp++ -lkea-hooks -lkea-log -lkea-util -lkea-exceptions
+@endcode
+
+Notes:
+- Replace "<install-dir>" with the location in which you installed Kea. Unless
+you specified the "--prefix" switch on the "configure" command line when
+building Kea, it will be installed in the default location, usually /usr/local.
+- The compilation command and switches required may vary depending on
+your operating system and compiler - consult the relevant documentation
+for details.
+- The list of libraries that need to be included in the command line
+depends on the functionality used by the hook code and the module to
+which they are attached. Depending on operating system, you may also need
+to explicitly list libraries on which the Kea libraries you link against depend.
+
+
+@subsection hooksdgConfiguration Configuring the Hooks Library
+
+The final step is to make the library known to Kea. The configuration
+keywords of all Kea modules to which hooks can be added contain the
+"hooks-libraries" element and user libraries are added to this. (The Kea
+hooks system can handle multiple libraries - this is discussed below.)
+
+To add the example library (assumed to be in /usr/local/lib) to the
+DHCPv4 module, it must be listed in the "hooks-libraries" element of the
+"Dhcp4" part of the configuration file:
+
+@code
+"Dhcp4": {
+ :
+ "hooks-libraries": [
+ {
+ "library": "/usr/local/lib/example.so"
+ }
+ ]
+ :
+}
+@endcode
+(Note that "hooks" is plural.)
+
+Each entry in the "hooks-libraries" list is a structure (a "map" in JSON
+parlance) that holds the following element:
+- library - the name of the library to load. This must be a string.
+
+@note The syntax of the hooks-libraries configuration element has changed
+since kea 0.9.2 (in that version, "hooks-libraries" was just a list of
+libraries). This change is in preparation for the introduction of
+library-specific parameters, which will be added to Kea in a version after 1.0.
+
+The DHCPv4 server will load the library and execute the callouts each time a
+request is received.
+
+@note All the above assumes that the hooks library will be used with a
+version of Kea that is dynamically-linked. For information regarding
+running hooks libraries against a statically-linked Kea, see @ref
+hooksdgStaticallyLinkedKea.
+
+@section hooksdgAdvancedTopics Advanced Topics
+
+
+@subsection hooksdgContextCreateDestroy Context Creation and Destruction
+
+As well as the hooks defined by the server, the hooks framework defines
+two hooks of its own, "context_create" and "context_destroy". The first
+is called when a request is created in the server, before any of the
+server-specific hooks gets called. It's purpose it to allow a library
+to initialize per-request context. The second is called after all
+server-defined hooks have been processed, and is to allow a library to
+tidy up.
+
+As an example, the "pkt4_send" example above required that the code
+check for an exception being thrown when accessing the "hwaddr" context
+item in case it was not set. An alternative strategy would have been to
+provide a callout for the "context_create" hook and set the context item
+"hwaddr" to an empty string. Instead of needing to handle an exception,
+"pkt4_send" would be guaranteed to get something when looking for
+the hwaddr item and so could write or not write the output depending on
+the value.
+
+In most cases, "context_destroy" is not needed as the Hooks system
+automatically deletes context. An example where it could be required
+is where memory has been allocated by a callout during the processing
+of a request and a raw pointer to it stored in the context object. On
+destruction of the context, that memory will not be automatically
+released. Freeing in the memory in the "context_destroy" callout will solve
+that problem.
+
+Actually, when the context is destroyed, the destructor
+associated with any objects stored in it are run. Rather than point to
+allocated memory with a raw pointer, a better idea would be to point to
+it with a boost "smart" pointer and store that pointer in the context.
+When the context is destroyed, the smart pointer's destructor is run,
+which will automatically delete the pointed-to object.
+
+These approaches are illustrated in the following examples.
+Here it is assumed that the hooks library is performing some form of
+security checking on the packet and needs to maintain information in
+a user-specified "SecurityInformation" object. (The details of this
+fictitious object are of no concern here.) The object is created in
+the "context_create" callout and used in both the "pkt4_receive" and the
+"pkt4_send" callouts.
+
+@code
+// Storing information in a "raw" pointer. Assume that the
+
+#include <hooks/hooks.h>
+ :
+
+extern "C" {
+
+// context_create callout - called when the request is created.
+int context_create(CalloutHandle& handle) {
+ // Create the security information and store it in the context
+ // for this packet.
+ SecurityInformation* si = new SecurityInformation();
+ handle.setContext("security_information", si);
+}
+
+// Callouts that use the context
+int pkt4_receive(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ SecurityInformation* si;
+ handle.getContext("security_information", si);
+ :
+ :
+ // Set the security information
+ si->setSomething(...);
+
+ // The pointed-to information has been updated but the pointer has not been
+ // altered, so there is no need to call setContext() again.
+}
+
+int pkt4_send(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ SecurityInformation* si;
+ handle.getContext("security_information", si);
+ :
+ :
+ // Retrieve security information
+ bool active = si->getSomething(...);
+ :
+}
+
+// Context destruction. We need to delete the pointed-to SecurityInformation
+// object because we will lose the pointer to it when the @c CalloutHandle is
+// destroyed.
+int context_destroy(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ SecurityInformation* si;
+ handle.getContext("security_information", si);
+
+ // Delete the pointed-to memory.
+ delete si;
+}
+@endcode
+
+The requirement for the "context_destroy" callout can be eliminated if
+a Boost shared ptr is used to point to the allocated memory:
+
+@code
+// Storing information in a "raw" pointer. Assume that the
+
+#include <hooks/hooks.h>
+#include <boost/shared_ptr.hpp>
+ :
+
+extern "C" {
+
+// context_create callout - called when the request is created.
+
+int context_create(CalloutHandle& handle) {
+ // Create the security information and store it in the context for this
+ // packet.
+ boost::shared_ptr<SecurityInformation> si(new SecurityInformation());
+ handle.setContext("security_information", si);
+}
+
+// Other than the data type, a shared pointer has similar semantics to a "raw"
+// pointer. Only the code from "pkt4_receive" is shown here.
+
+int pkt4_receive(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ boost::shared_ptr<SecurityInformation> si;
+ handle.setContext("security_information", si);
+ :
+ :
+ // Modify the security information
+ si->setSomething(...);
+
+ // The pointed-to information has been updated but the pointer has not
+ // altered, so there is no need to reset the context.
+}
+
+// No context_destroy callout is needed to delete the allocated
+// SecurityInformation object. When the @c CalloutHandle is destroyed, the shared
+// pointer object will be destroyed. If that is the last shared pointer to the
+// allocated memory, then it too will be deleted.
+@endcode
+
+(Note that a Boost shared pointer - rather than any other Boost smart pointer -
+should be used, as the pointer objects are copied within the hooks framework and
+only shared pointers have the correct behavior for the copy operation.)
+
+
+@subsection hooksdgCalloutRegistration Registering Callouts
+
+As briefly mentioned in @ref hooksdgExampleCallouts, the standard is for
+callouts in the user library to have the same name as the name of the
+hook to which they are being attached. This convention was followed
+in the tutorial, e.g., the callout that needed to be attached to the
+"pkt4_receive" hook was named pkt4_receive.
+
+The reason for this convention is that when the library is loaded, the
+hook framework automatically searches the library for functions with
+the same names as the server hooks. When it finds one, it attaches it
+to the appropriate hook point. This simplifies the loading process and
+bookkeeping required to create a library of callouts.
+
+However, the hooks system is flexible in this area: callouts can have
+non-standard names, and multiple callouts can be registered on a hook.
+
+
+@subsubsection hooksdgLibraryHandle The LibraryHandle Object
+
+The way into the part of the hooks framework that allows callout
+registration is through the LibraryHandle object. This was briefly
+introduced in the discussion of the framework functions, in that
+an object of this type is pass to the "load" function. A LibraryHandle
+can also be obtained from within a callout by calling the CalloutHandle's
+@c getLibraryHandle() method.
+
+The LibraryHandle provides three methods to manipulate callouts:
+
+- @c registerCallout - register a callout on a hook.
+- @c deregisterCallout - deregister a callout from a hook.
+- @c deregisterAllCallouts - deregister all callouts on a hook.
+
+The following sections cover some of the ways in which these can be used.
+
+@subsubsection hooksdgNonstandardCalloutNames Non-Standard Callout Names
+
+The example in the tutorial used standard names for the callouts. As noted
+above, it is possible to use non-standard names. Suppose, instead of the
+callout names "pkt4_receive" and "pkt4_send", we had named our callouts
+"classify" and "write_data". The hooks framework would not have registered
+these callouts, so we would have needed to do it ourself. The place to
+do this is the "load" framework function, and its code would have had to
+been modified to:
+
+@code
+int load(LibraryHandle& libhandle) {
+ // Register the callouts on the hooks. We assume that a header file
+ // declares the "classify" and "write_data" functions.
+ libhandle.registerCallout("pkt4_receive", classify);
+ libhandle.registerCallout("pkt4_send", write_data);
+
+ // Open the log file
+ interesting.open("/data/clients/interesting.log",
+ std::fstream::out | std::fstream::app);
+ return (interesting ? 0 : 1);
+}
+@endcode
+
+It is possible for a library to contain callouts with both standard and
+non-standard names: ones with standard names will be registered automatically,
+ones with non-standard names need to be registered manually.
+
+@subsubsection hooksdgCommandHandlers Using Callouts as Command handlers
+
+Kea servers natively support a set of control commands to retrieve and update
+runtime information, e.g. server configuration, basic statistics etc. In
+many cases, however, DHCP deployments require support for additional commands
+or the natively supported commands don't exactly fulfill one's requirements.
+
+Taking advantage of Kea's modularity and hooks framework, it is now possible
+to easily extend the pool of supported commands by implementing additional
+(non-standard) commands within hook libraries.
+
+A hook library needs to register command handlers for control commands within
+its @c load function as follows:
+
+@code
+int load(LibraryHandle& handle) {
+ handle.registerCommandCallout("diagnostics-enable", diagnostics_enable);
+ handle.registerCommandCallout("diagnostics-dump", diagnostics_dump);
+ return (0);
+}
+@endcode
+
+Internally, the @c LibraryHandle associates command handlers @c diagnostics_enable
+and @c diagnostics_dump with dedicated hook points. These hook points are
+given names after the command names, i.e. "$diagnostics_enable" and
+"$diagnostics_dump". The dollar sign before the hook point name indicates
+that the hook point is dedicated for a command handler, i.e. is not one of
+the standard hook points used by the Kea servers. This is just a naming convention,
+usually invisible to the hook library implementation and is mainly aimed at
+minimizing a risk of collision between names of the hook points registered with
+command handlers and standard hook points.
+
+Once the hook library is loaded and the command handlers supported by the
+library are registered, the Kea servers will be able to recognize that those
+specific commands are supported and will dispatch commands with the corresponding
+names to the hook library (or multiple hook libraries) for processing. See the
+documentation of the @ref isc::config::HookedCommandMgr for more details how
+it uses @c HooksManager::commandHandlersPresent to determine if the received
+command should be dispatched to a hook library for processing.
+
+The @c diagnostics_enable and @c diagnostics_dump command
+handlers must be implemented within the hook library in analogous way to
+regular callouts:
+
+@code
+int diagnostics_enable(CalloutHandle& handle) {
+ ConstElementPtr response;
+
+ try {
+ ConstElementPtr command;
+ handle.getArgument("command", command);
+ ConstElementPtr args;
+ static_cast<void>(isc::config::parseCommand(args, command));
+
+ // ...
+ // handle command here.
+ // ...
+
+ response = createAnswer(CONTROL_RESULT_SUCCESS, "successful");
+
+ } catch (const std::exception& ex) {
+ response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
+ }
+
+ handle.setArgument("response", response);
+
+ return (0);
+}
+@endcode
+
+The sample code above retrieves the "command" argument which is always provided.
+It represents the control command as sent by the controlling client. It includes
+command name and command specific arguments. The generic @ref isc::config::parseCommand
+can be used to retrieve arguments included in the command. The callout then interprets
+these arguments, takes appropriate action and creates a response to the client.
+Care should be taken to catch any non-fatal exceptions that may arise during the callout
+that should be reported as a failure to the controlling client. In such case, the response
+with @c CONTROL_RESULT_ERROR is returned and the callout should return the value of 0.
+The non-zero result should only be returned by the callout in case of fatal errors, i.e.
+errors which result in inability to generate a response to the client. If the response
+is generated, the command handler must set it as "response" argument prior to return.
+
+It is uncommon but valid scenario to have multiple hook libraries providing command
+handlers for the same command. They are invoked sequentially and each of them
+can freely modify a response set by a previous callout. This includes entirely
+replacing the response provided by previous callouts, if necessary.
+
+@subsubsection hooksdgMultipleCallouts Multiple Callouts on a Hook
+
+The Kea hooks framework allows multiple callouts to be attached to
+a hook point. Although it is likely to be rare for user code to need to
+do this, there may be instances where it make sense.
+
+To register multiple callouts on a hook, just call
+@c LibraryHandle::registerCallout multiple times on the same hook, e.g.,
+
+@code
+ libhandle.registerCallout("pkt4_receive", classify);
+ libhandle.registerCallout("pkt4_receive", write_data);
+@endcode
+
+The hooks framework will call the callouts in the order they are
+registered. The same @c CalloutHandle is passed between them, so any
+change made to the CalloutHandle's arguments, "skip" flag, or per-request
+context by the first is visible to the second.
+
+
+@subsection hooksdgMultipleLibraries Multiple User Libraries
+
+As alluded to in the section @ref hooksdgConfiguration, Kea can load
+multiple libraries. The libraries are loaded in the order specified in
+the configuration, and the callouts attached to the hooks in the order
+presented by the libraries.
+
+The following picture illustrates this, and also illustrates the scope of
+data passed around the system.
+
+@image html DataScopeArgument.png "Scope of Arguments"
+
+In this illustration, a server has three hook points, alpha, beta
+and gamma. Two libraries are configured, library 1 and library 2.
+Library 1 registers the callout "authorize" for hook alpha, "check" for
+hook beta and "add_option" for hook gamma. Library 2 registers "logpkt",
+"validate" and "putopt"
+
+The horizontal red lines represent arguments to callouts. When the server
+calls hook alpha, it creates an argument list and calls the
+first callout for the hook, "authorize". When that callout returns, the
+same (but possibly modified) argument list is passed to the next callout
+in the chain, "logpkt". Another, separate argument list is created for
+hook beta and passed to the callouts "check" and "validate" in
+that order. A similar sequence occurs for hook gamma.
+
+The next picture shows the scope of the context associated with a
+request.
+
+@image html DataScopeContext.png "Illustration of per-library context"
+
+The vertical blue lines represent callout context. Context is
+per-packet but also per-library. When the server calls "authorize",
+the CalloutHandle's @c getContext and @c setContext methods access a context
+created purely for library 1. The next callout on the hook will access
+context created for library 2. These contexts are passed to the callouts
+associated with the next hook. So when "check" is called, it gets the
+context data that was set by "authorize", when "validate" is called,
+it gets the context data set by "logpkt".
+
+It is stressed that the context for callouts associated with different
+libraries is entirely separate. For example, suppose "authorize" sets
+the CalloutHandle's context item "foo" to 2 and "logpkt" sets an item of
+the same name to the string "bar". When "check" accesses the context
+item "foo", it gets a value of 2; when "validate" accesses an item of
+the same name, it gets the value "bar".
+
+It is also stressed that all this context exists only for the life of the
+request being processed. When that request is complete, all the
+context associated with that request - for all libraries - is destroyed,
+and new context created for the next request.
+
+This structure means that library authors can use per-request context
+without worrying about the presence of other libraries. Other libraries
+may be present, but will not affect the context values set by a library's
+callouts.
+
+Configuring multiple libraries just requires listing the libraries
+as separate elements of the hooks-libraries configuration element, e.g.,
+
+@code
+"Dhcp4": {
+ :
+ "hooks-libraries": [
+ {
+ "library": "/usr/lib/library1.so"
+ },
+ {
+ "library": "/opt/library2.so"
+ }
+ :
+ ]
+}
+@endcode
+
+
+@subsection hooksdgInterLibraryData Passing Data Between Libraries
+
+In rare cases, it is possible that one library may want to pass
+data to another. This can be done in a limited way by means of the
+CalloutHandle's @c setArgument and @c getArgument calls. For example, in the
+above diagram, the callout "add_option" can pass a value to "putopt"
+by setting a name.value pair in the hook's argument list. "putopt"
+would be able to read this, but would not be able to return information
+back to "add_option".
+
+All argument names used by Kea will be a combination of letters
+(both upper- and lower-case), digits, hyphens and underscores: no
+other characters will be used. As argument names are simple strings,
+it is suggested that if such a mechanism be used, the names of the data
+values passed between the libraries include a special character such as
+the dollar symbol or percent sign. In this way there is no danger that
+a name will conflict with any existing or future Kea argument names.
+
+
+@subsection hooksdgStaticallyLinkedKea Running Against a Statically-Linked Kea
+
+If Kea is built with the --enable-static-link switch (set when
+running the "configure" script), no shared Kea libraries are built;
+instead, archive libraries are created and Kea is linked to them.
+If you create a hooks library also linked against these archive libraries,
+when the library is loaded you end up with two copies of the library code,
+one in Kea and one in your library.
+
+To run successfully, your library needs to perform run-time initialization
+of the Kea code in your library (something performed by Kea
+in the case of shared libraries). To do this, call the function
+isc::hooks::hooksStaticLinkInit() as the first statement of the load()
+function. (If your library does not include a load() function, you need
+to add one.) For example:
+
+@code
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+int load() {
+ isc::hooks::hooksStaticLinkInit();
+ :
+}
+
+// Other callout functions
+ :
+
+}
+@endcode
+
+
+@subsection hooksdgHooksConfig Configuring Hooks Libraries
+
+Sometimes it is useful for the hook library to have some configuration parameters.
+This capability was introduced in Kea 1.1. This is often convenient to follow
+generic Kea configuration approach rather than invent your own configuration
+logic. Consider the following example:
+
+@code
+"hooks-libraries": [
+ {
+ "library": "/opt/first.so"
+ },
+ {
+ "library": "/opt/second.so",
+ "parameters": {
+ }
+ },
+ {
+ "library": "/opt/third.so",
+ "parameters": {
+ "mail": "spam@example.com",
+ "floor": 13,
+ "debug": false,
+ "users": [ "alice", "bob", "charlie" ],
+ "languages": {
+ "french": "bonjour",
+ "klingon": "yl'el"
+ }
+ }
+ }
+]
+@endcode
+
+This example has three hook libraries configured. The first and second have
+no parameters. Note that parameters map is optional, but it's perfectly okay to
+specify it as an empty map. The third library is more interesting. It has five
+parameters specified. The first one called 'mail' is a string. The second one
+is an integer and the third one is boolean. Fourth and fifth parameters are
+slightly more complicated as they are a list and a map respectively. JSON
+structures can be nested if necessary, e.g., you can have a list, which contains
+maps, maps that contain maps that contain other maps etc. Any valid JSON
+structure can be represented. One important limitation here is that the top
+level "parameters" structure must either be a map or not present at all.
+
+Those parameters can be accessed in load() method. Passed isc::hooks::LibraryHandle
+object has a method called getParameter that returns an instance of
+isc::data::ConstElementPtr or null pointer if there was no parameter specified. This pointer
+will point to an object derived from isc::data::Element class. For detailed
+explanation how to use those objects, see isc::data::Element class.
+
+Here's a brief overview of how to use those elements:
+
+ - x = getParameter("mail") will return instance of isc::data::StringElement. The content
+ can be accessed with x->stringValue() and will return std::string.
+ - x = getParameter("floor") will return an instance of isc::data::IntElement.
+ The content can be accessed with x->intValue() and will return int.
+ - x = getParameter("debug") will return an instance of isc::data::BoolElement.
+ Its value can be accessed with x->boolValue() and will return bool.
+ - x = getParameter("users") will return an instance of isc::data::ListElement.
+ Its content can be accessed with the following methods:
+ x->size(), x->get(index)
+ - x = getParameter("watch-list") will return an instance of isc::data::MapElement.
+ Its content can be accessed with the following methods:
+ x->find("klingon"), x->contains("french"), x->size()
+
+Keep in mind that the user can structure his config file incorrectly.
+Remember to check if the structure has the expected type before using type specific
+method. For example calling stringValue on IntElement will throw an exception.
+You can do this by calling isc::data::Element::getType.
+
+Here's an example that obtains all of the parameters specified above.
+If you want to get nested elements, Element::get(index) and Element::find(name)
+will return ElementPtr, which can be iterated in similar manner.
+
+@code
+int load(LibraryHandle& handle) {
+ ConstElementPtr mail = handle.getParameter("mail");
+ ConstElementPtr floor = handle.getParameter("floor");
+ ConstElementPtr debug = handle.getParameter("debug");
+ ConstElementPtr users = handle.getParameter("users");
+ ConstElementPtr lang = handle.getParameter("languages");
+
+ // String handling example
+ if (!mail) {
+ // Handle missing 'mail' parameter here.
+ return (1);
+ }
+ if (mail->getType() != Element::string) {
+ // Handle incorrect 'mail' parameter here.
+ return (1);
+ }
+ std::string mail_str = mail->stringValue();
+
+ // In the following examples safety checks are omitted for clarity.
+ // Make sure you do it properly similar to mail example above
+ // or you risk dereferencing null pointer or at least throwing
+ // an exception!
+
+ // Integer handling example
+ int floor_num = floor->intValue();
+
+ // Boolean handling example
+ bool debug_flag = debug->boolValue();
+
+ // List handling example
+ std::cout << "There are " << users->size() << " users defined." << std::endl;
+ for (int i = 0; i < users->size(); i++) {
+ ConstElementPtr user = users->get(i);
+ std::cout << "User " << user->stringValue() << std::endl;
+ }
+
+ // Map handling example
+ std::cout << "There are " << lang->size() << " languages defined." << std::endl;
+ if (lang->contains("french")) {
+ std::cout << "One of them is French!" << std::endl;
+ }
+ ConstElementPtr greeting = lang->find("klingon");
+ if (greeting) {
+ std::cout << "Lt. Worf says " << greeting->stringValue() << std::endl;
+ }
+
+ // All validation steps were successful. The library has all the parameters
+ // it needs, so we should report a success.
+ return (0);
+}
+@endcode
+
+A good sources of examples could be unit-tests in file src/lib/cc/tests/data_unittests.cc
+which are dedicated to isc::data::Element testing and src/lib/hooks/tests/callout_params_library.cc,
+which is an example library used in testing. This library expects exactly 3 parameters:
+svalue (which is a string), ivalue (which is an integer) and bvalue (which is a boolean).
+
+@subsection hooksMemoryManagement Memory Management Considerations for Hooks Writer
+
+Both Kea server memory space and hook library memory space share a common
+address space between the opening of the hook (call to dlopen() as the first
+phase of the hook library loading) and the closing of the hook (call to
+dlclose() as the last phase of the hook library unloading). There are
+pointers between the two memory spaces with at least two bad consequences
+when they are not correctly managed:
+
+- Kea uses shared pointers for its objects. If the hook ownership keeps
+ownership of an object, this object will never be destroyed, leading to
+a trivial memory leak. Some care is recommended when the hook library
+uses a garbage collector to not postpone releases of no longer used
+objects. Cycles should be avoided too, for instance using weak pointers.
+Of course at the opposite, if a Kea object is needed ownership on, it must
+be kept in order to not get a dangling pointer when it will be destroyed
+at the end of its last reference lifetime.
+
+- Kea can take some pointers to the hook library memory space, for instance
+when a hook object is registered. If these pointers are not destroyed
+before the hook library memory space is unmapped by dlclose() this likely
+leads to a crash.
+
+Communication between Kea code and hook library code is provided by
+callout handles. For callout points related to a packet, the callout
+handle is associated with the packet allowing to get the same callout handle
+for all callout points called during processing of a query.
+
+Hook libraries are closed i.e. hook library memory spaces are unmapped
+only when there is no active callout handles. This enforces a correct
+behavior at two conditions:
+
+- there is no "wild" dangling pointers, for instance no registered
+objects.
+
+- this can happen i.e. the hook library does not keep a shared pointer
+to a query packet.
+
+To allow hook writers to fulfill these two conditions the unload() entry
+point is called in the first phase of the unloading process since Kea
+version 1.7.10. For instance if the hook library uses the PIMPL code
+pattern the unload() entry point must reset the pointer to the
+hook library implementation.
+
+@subsection hooksMultiThreading Multi-Threading Considerations for Hooks Writers
+
+Multi-threading programming in C++ is not easy. For instance STL containers
+do not support simultaneous read and write accesses. Kea is written in C++
+so a priori for all Kea APIs one should never assume thread safety.
+
+When a hook library is internally multi-threaded, its code and any Kea API
+used simultaneously by different threads must be thread safe. To mark
+the difference between this and the other thread safety requirement this
+is called "generic thread safe".
+
+When multi-threaded packet processing is enabled, Kea servers perform
+some actions by the main thread and packet processing by members of
+a thread pool. The multi-threading mode is returned by:
+@code
+isc::util::MultiThreadingMgr::instance().getMode()
+@endcode
+When it is false, Kea is single threaded and there is no thread safety
+requirement, when it is true, the requirement is named Kea packet processing
+thread safe shorten into "Kea thread safe".
+
+A typical Kea thread safe looks like:
+@code
+int Foo() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (FooInternal());
+ } else {
+ return (FooInternal());
+ }
+}
+@endcode
+
+The overhead of mutexes and other synchronization tools is far greater
+than a test and branch so it is the recommended way to implement Kea
+thread safety.
+
+When a hook library entry point can be called from a packet processing
+thread, typically from a packet processing callout but also when
+implementing a lease or host backend API, the entry point code must
+be Kea thread safe. If it is not possible the hook library must
+be marked as not multi-threading compatible (i.e. return 0 from
+multi_threading_compatible).
+
+At the opposite during (re)configuration including libload command
+and config backend, only the main thread runs, so version, load, unload,
+multi_threading_compatible, dhcp4_srv_configured, dhcp6_srv_configured,
+cb4_updated and cb6_updated have no thread safety requirements.
+
+Other hook library entry points are called by the main thread:
+ - io service (io context is recent boost versions) is polled by the main
+ thread
+ - external socket callbacks are executed by the main thread
+ - commands including command_process
+
+The packet processing threads are not stopped so either the entry
+point code is Kea thread safe or it uses a critical section
+(@c isc::util::MultiThreadingCriticalSection) to stop the packet
+processing threads during the execution of the not Kea thread safe code.
+Of course critical sections have an impact on performance so they should
+be used only for particular cases where no better choice is available.
+
+Some Kea APIs were made thread safe mainly because they are used by the
+packet processing:
+ - logging is generic thread safe and even multi process safe i.e.
+ messages logged into a file or by syslog from multiple processes
+ do not mix.
+ - statistics update is Kea thread safe.
+ - lease and host database backends are Kea thread safe. Note if you need to
+ perform a direct MySQL or PostgreSQL query you must use the connection pool.
+ - state model and watched thread are generic thread safe (libkea-util)
+ - interval timer setup and cancel are generic thread safe (libkea-asiolink)
+ - parking lots are generic thread safe (libkea-hooks)
+ - external sockets are generic thread safe (libkea-dhcp++)
+ - http client is Kea thread safe (libkea-http)
+
+Some other Kea APIs are intrinsically thread safe because they do not
+involve a shared structure so for instance despite of its name the
+interface manager send methods are generic thread safe.
+
+Per library documentation details thread safety to help hooks writers
+and to provide an exhaustive list of Kea thread safe APIs:
+ - @ref utilMTConsiderations
+ - @ref logMTConsiderations
+ - @ref asiolinkMTConsiderations
+ - @ref ccMTConsiderations
+ - @ref databaseMTConsiderations
+ - @ref ctrlSocketMTConsiderations
+ - @ref libdhcpMTConsiderations
+ - @ref statsMTConsiderations
+ - @ref yangMTConsiderations
+ - @ref libdhcp_ddnsMTConsiderations
+ - @ref dhcpEvalMTConsiderations
+ - @ref cplMTConsiderations
+ - @ref dhcpDatabaseBackendsMTConsiderations
+ - @ref libdhcpsrvMTConsiderations
+ - @ref httpMTConsiderations
+
+*/
diff --git a/src/lib/hooks/images/DataScopeArgument.dia b/src/lib/hooks/images/DataScopeArgument.dia
new file mode 100644
index 0000000..02a4f17
--- /dev/null
+++ b/src/lib/hooks/images/DataScopeArgument.dia
Binary files differ
diff --git a/src/lib/hooks/images/DataScopeArgument.png b/src/lib/hooks/images/DataScopeArgument.png
new file mode 100644
index 0000000..34a5bd1
--- /dev/null
+++ b/src/lib/hooks/images/DataScopeArgument.png
Binary files differ
diff --git a/src/lib/hooks/images/DataScopeContext.dia b/src/lib/hooks/images/DataScopeContext.dia
new file mode 100644
index 0000000..1e39f5b
--- /dev/null
+++ b/src/lib/hooks/images/DataScopeContext.dia
Binary files differ
diff --git a/src/lib/hooks/images/DataScopeContext.png b/src/lib/hooks/images/DataScopeContext.png
new file mode 100644
index 0000000..ba18875
--- /dev/null
+++ b/src/lib/hooks/images/DataScopeContext.png
Binary files differ
diff --git a/src/lib/hooks/images/HooksUml.dia b/src/lib/hooks/images/HooksUml.dia
new file mode 100644
index 0000000..0972fca
--- /dev/null
+++ b/src/lib/hooks/images/HooksUml.dia
Binary files differ
diff --git a/src/lib/hooks/images/HooksUml.png b/src/lib/hooks/images/HooksUml.png
new file mode 100644
index 0000000..3859e6a
--- /dev/null
+++ b/src/lib/hooks/images/HooksUml.png
Binary files differ
diff --git a/src/lib/hooks/libinfo.cc b/src/lib/hooks/libinfo.cc
new file mode 100644
index 0000000..2abf3a1
--- /dev/null
+++ b/src/lib/hooks/libinfo.cc
@@ -0,0 +1,30 @@
+// Copyright (C) 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 <hooks/libinfo.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Extracts names from HookLibsCollection
+///
+/// @param libraries Hook libraries collection
+/// @return vector of strings with library names
+std::vector<std::string>
+extractNames(const isc::hooks::HookLibsCollection& libraries) {
+ std::vector<std::string> names;
+
+ for (isc::hooks::HookLibsCollection::const_iterator it = libraries.begin();
+ it != libraries.end(); ++it) {
+ names.push_back(it->first);
+ }
+ return (names);
+}
+
+};
+};
diff --git a/src/lib/hooks/libinfo.h b/src/lib/hooks/libinfo.h
new file mode 100644
index 0000000..42b0441
--- /dev/null
+++ b/src/lib/hooks/libinfo.h
@@ -0,0 +1,42 @@
+// Copyright (C) 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/.
+
+#ifndef HOOKS_LIBINFO_H
+#define HOOKS_LIBINFO_H
+
+#include <cc/data.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+#include <utility>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Entity that holds information about hook libraries and their
+/// parameters.
+///
+/// The first parameter is a full filename with path to the library.
+/// The second parameter is a map of parameters that configure the
+/// library. There's always at least one parameter: "library", which
+/// contains the library name.
+typedef std::pair<std::string, data::ConstElementPtr> HookLibInfo;
+
+/// @brief A storage for information about hook libraries.
+typedef std::vector<HookLibInfo> HookLibsCollection;
+
+/// @brief Shared pointer to collection of hooks libraries.
+typedef boost::shared_ptr<HookLibsCollection> HookLibsCollectionPtr;
+
+/// @brief Extracts library names from full library information structure
+std::vector<std::string> extractNames(const HookLibsCollection& libinfo);
+
+};
+};
+
+#endif
diff --git a/src/lib/hooks/library_handle.cc b/src/lib/hooks/library_handle.cc
new file mode 100644
index 0000000..b070448
--- /dev/null
+++ b/src/lib/hooks/library_handle.cc
@@ -0,0 +1,131 @@
+// Copyright (C) 2013-2020 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 <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/hooks_manager.h>
+
+#include <iostream>
+
+namespace isc {
+namespace hooks {
+
+// Callout manipulation - all deferred to the CalloutManager.
+
+void
+LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
+ int index = index_;
+
+ if (index_ == -1) {
+ // -1 means that current index is stored in CalloutManager.
+ // So let's get the index from there. See comment for
+ // LibraryHandle::index_.
+ index = callout_manager_.getLibraryIndex();
+ }
+
+ // Register the callout.
+ callout_manager_.registerCallout(name, callout, index);
+}
+
+void
+LibraryHandle::registerCommandCallout(const std::string& command_name,
+ CalloutPtr callout) {
+ // Register hook point for this command, if one doesn't exist.
+ callout_manager_.registerCommandHook(command_name);
+ // Register the command handler as a callout.
+ registerCallout(ServerHooks::commandToHookName(command_name), callout);
+}
+
+
+bool
+LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
+ int index = index_;
+
+ if (index_ == -1) {
+ // -1 means that current index is stored in CalloutManager.
+ // So let's get the index from there. See comment for
+ // LibraryHandle::index_.
+ index = callout_manager_.getLibraryIndex();
+ }
+
+ return (callout_manager_.deregisterCallout(name, callout, index));
+}
+
+bool
+LibraryHandle::deregisterAllCallouts(const std::string& name) {
+ int index = index_;
+
+ if (index_ == -1) {
+ // -1 means that current index is stored in CalloutManager.
+ // So let's get the index from there. See comment for
+ // LibraryHandle::index_.
+ index = callout_manager_.getLibraryIndex();
+ }
+
+ return (callout_manager_.deregisterAllCallouts(name, index));
+}
+
+isc::data::ConstElementPtr
+LibraryHandle::getParameters() {
+ HookLibsCollection libinfo = HooksManager::getLibraryInfo();
+
+ int index = index_;
+
+ if (index == -1) {
+ // -1 means that current index is stored in CalloutManager.
+ // So let's get the index from there. See comment for
+ // LibraryHandle::index_.
+ index = callout_manager_.getLibraryIndex();
+ }
+
+ if ((index > libinfo.size()) || (index <= 0)) {
+ // Something is very wrong here. The library index is out of bounds.
+ // However, this is user facing interface, so we should not throw here.
+ return (isc::data::ConstElementPtr());
+ }
+
+ // Some indexes have special meaning:
+ // * 0 - pre-user library callout
+ // * 1 -> numlib - indexes for actual libraries
+ // * INT_MAX - post-user library callout
+
+ return (libinfo[index - 1].second);
+}
+
+isc::data::ConstElementPtr
+LibraryHandle::getParameter(const std::string& name) {
+ // Try to find appropriate parameter. May return null pointer
+ isc::data::ConstElementPtr params = getParameters();
+ if (!params || (params->getType() != isc::data::Element::map)) {
+ return (isc::data::ConstElementPtr());
+ }
+
+ // May return null pointer if there's no parameter.
+ return (params->get(name));
+}
+
+std::vector<std::string>
+LibraryHandle::getParameterNames() {
+ std::vector<std::string> names;
+ // Find all parameter names.
+ isc::data::ConstElementPtr params = getParameters();
+ if (!params ||
+ (params->getType() != isc::data::Element::map) ||
+ (params->size() == 0)) {
+ return (names);
+ }
+ auto map = params->mapValue();
+ for (auto elem = map.begin(); elem != map.end(); ++elem) {
+ names.push_back(elem->first);
+ }
+ return (names);
+}
+
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/library_handle.h b/src/lib/hooks/library_handle.h
new file mode 100644
index 0000000..14af40c
--- /dev/null
+++ b/src/lib/hooks/library_handle.h
@@ -0,0 +1,242 @@
+// Copyright (C) 2013-2020 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 LIBRARY_HANDLE_H
+#define LIBRARY_HANDLE_H
+
+#include <string>
+#include <cc/data.h>
+
+namespace isc {
+namespace hooks {
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+
+/// Typedef for a callout pointer. (Callouts must have "C" linkage.)
+extern "C" {
+ typedef int (*CalloutPtr)(CalloutHandle&);
+};
+
+/// @brief Library handle
+///
+/// This class is accessed by the user library when registering callouts,
+/// either by the library's load() function, or by one of the callouts
+/// themselves.
+///
+/// It is really little more than a shell around the CalloutManager. By
+/// presenting this object to the user-library callouts, callouts can manage
+/// the callout list for their own library, but cannot affect the callouts
+/// registered by other libraries.
+///
+/// (This restriction is achieved by the CalloutManager maintaining the concept
+/// of the "current library". When a callout is registered - either by the
+/// library's load() function, or by a callout in the library - the registration
+/// information includes the library active at the time. When that callout is
+/// called, the CalloutManager uses that information to set the "current
+/// library": the registration functions only operator on data whose
+/// associated library is equal to the "current library".)
+///
+/// As of Kea 1.3.0 release, the @ref LibraryHandle can be used by the hook
+/// libraries to install control command handlers and dynamically register
+/// hook points with which the handlers are associated. For example, if the
+/// hook library supports control-command 'foo-bar' it should register its
+/// handler similarly to this:
+/// @code
+/// int load(LibraryHandle& libhandle) {
+/// libhandle.registerCommandCallout("foo-bar", foo_bar_handler);
+/// return (0);
+/// }
+/// @endcode
+///
+/// which will result in automatic creation of the hook point for the command
+/// (if one doesn't exist) and associating the callout 'foo_bar_handler' with
+/// this hook point as a handler for the command.
+
+class LibraryHandle {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param manager Back reference to the containing CalloutManager.
+ /// This reference is used to access appropriate methods in that
+ /// object. Note that the reference is safe - the only instance
+ /// of the LibraryHandle in the system is as a member of the
+ /// CalloutManager to which it points.
+ ///
+ /// @param index Index of the library to which the LibraryHandle applies.
+ /// If negative, the library index as set in the CalloutManager is
+ /// used. Note: although -1 is a valid argument value for
+ /// @ref isc::hooks::CalloutManager::setLibraryIndex(), in this class
+ /// it is used as a sentinel to indicate that the library index in
+ /// @ref isc::hooks::CalloutManager should not be set or reset.
+ LibraryHandle(CalloutManager& manager, int index = -1)
+ : callout_manager_(manager), index_(index) {}
+
+ /// @brief Register a callout on a hook
+ ///
+ /// Registers a callout function with a given hook. The callout is added
+ /// to the end of the callouts for the current library that are associated
+ /// with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognized.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Register control command handler
+ ///
+ /// Registers control command handler by creating a hook point for this
+ /// command (if it doesn't exist) and associating the callout as a command
+ /// handler. It is possible to register multiple command handlers for the
+ /// same control command because command handlers are implemented as callouts.
+ ///
+ /// @param command_name Command name for which handler should be installed.
+ /// @param callout Pointer to the command handler implemented as a callout.
+ void registerCommandCallout(const std::string& command_name, CalloutPtr callout);
+
+ /// @brief De-Register a callout on a hook
+ ///
+ /// Searches through the functions registered by the current library with
+ /// the named hook and removes all entries matching the callout. It does
+ /// not affect callouts registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognized.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Removes all callouts on a hook
+ ///
+ /// Removes all callouts associated with a given hook that were registered.
+ /// by the current library. It does not affect callouts that were
+ /// registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognized.
+ bool deregisterAllCallouts(const std::string& name);
+
+
+ /// @brief Returns configuration parameter for the library.
+ ///
+ /// This method returns configuration parameters specified in the
+ /// configuration file. Here's the example. Let's assume that there
+ /// are two hook libraries configured:
+ ///
+ /// "hooks-libraries": [
+ /// {
+ /// "library": "/opt/charging.so",
+ /// "parameters": {}
+ /// },
+ /// {
+ /// "library": "/opt/local/notification.so",
+ /// "parameters": {
+ /// "mail": "alarm@example.com",
+ /// "floor": 42,
+ /// "debug": false,
+ /// "users": [ "alice", "bob", "charlie" ],
+ /// "header": {
+ /// "french": "bonjour",
+ /// "klingon": "yl'el"
+ /// }
+ /// }
+ /// }
+ ///]
+ ///
+ /// The first library has no parameters, so regardless of the name
+ /// specified, for that library getParameter will always return NULL.
+ ///
+ /// For the second parameter, depending the following calls will return:
+ /// - x = getParameter("mail") will return instance of
+ /// isc::data::StringElement. The content can be accessed with
+ /// x->stringValue() and will return std::string.
+ /// - x = getParameter("floor") will return an instance of isc::data::IntElement.
+ /// The content can be accessed with x->intValue() and will return int.
+ /// - x = getParameter("debug") will return an instance of isc::data::BoolElement.
+ /// Its value can be accessed with x->boolValue() and will return bool.
+ /// - x = getParameter("users") will return an instance of ListElement.
+ /// Its content can be accessed with the following methods:
+ /// x->size(), x->get(index)
+ /// - x = getParameter("header") will return an instance of isc::data::MapElement.
+ /// Its content can be accessed with the following methods:
+ /// x->find("klingon"), x->contains("french"), x->size()
+ ///
+ /// For more examples and complete API, see documentation for
+ /// @ref isc::data::Element class and its derivatives:
+ /// - @ref isc::data::IntElement
+ /// - @ref isc::data::DoubleElement
+ /// - @ref isc::data::BoolElement
+ /// - @ref isc::data::StringElement
+ /// - @ref isc::data::ListElement
+ /// - @ref isc::data::MapElement
+ ///
+ /// Another good way to learn how to use Element interface is to look at the
+ /// unittests in data_unittests.cc.
+ ///
+ /// @param name text name of the parameter.
+ /// @return ElementPtr representing requested parameter (may be null, if
+ /// there is no such parameter.)
+ isc::data::ConstElementPtr
+ getParameter(const std::string& name);
+
+ /// @brief Get configuration parameter common code.
+ ///
+ /// @return configuration parameters.
+ isc::data::ConstElementPtr getParameters();
+
+ /// @brief Returns names of configuration parameters for the library.
+ ///
+ /// This method returns a vector of strings reflecting names of
+ /// configuration parameters specified in the configuration file.
+ ///
+ /// @note: kept for backward compatibility.
+ /// @return a vector with parameter entry names.
+ std::vector<std::string> getParameterNames();
+
+private:
+ /// @brief Copy constructor
+ ///
+ /// Private (with no implementation) as it makes no sense to copy an object
+ /// of this type. All code receives a reference to an existing handle which
+ /// is tied to a particular CalloutManager. Creating a copy of that handle
+ /// runs the risk of a "dangling pointer" to the original handle's callout
+ /// manager.
+ ///
+ /// @param Unused - should be the object to copy.
+ LibraryHandle(const LibraryHandle&);
+
+ /// @brief Assignment operator
+ ///
+ /// Declared private like the copy constructor for the same reasons. It too
+ /// has no implementation.
+ ///
+ /// @param Unused - should be the object to copy.
+ LibraryHandle& operator=(const LibraryHandle&);
+
+ /// Back pointer to the collection object for the library
+ CalloutManager& callout_manager_;
+
+ /// Library index to which this handle applies. -1 indicates that it
+ /// applies to whatever index is current in the CalloutManager.
+ int index_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // LIBRARY_HANDLE_H
diff --git a/src/lib/hooks/library_manager.cc b/src/lib/hooks/library_manager.cc
new file mode 100644
index 0000000..b8fb809
--- /dev/null
+++ b/src/lib/hooks/library_manager.cc
@@ -0,0 +1,435 @@
+// Copyright (C) 2013-2023 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 <hooks/hooks.h>
+#include <hooks/hooks_log.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/library_manager.h>
+#include <hooks/pointer_converter.h>
+#include <hooks/server_hooks.h>
+#include <log/logger_manager.h>
+#include <log/logger_support.h>
+#include <log/message_initializer.h>
+#include <util/multi_threading_mgr.h>
+
+#include <string>
+#include <vector>
+
+#include <dlfcn.h>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor (used by external agency)
+LibraryManager::LibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager)
+ : dl_handle_(NULL), index_(index), manager_(manager),
+ library_name_(name),
+ server_hooks_(ServerHooks::getServerHooksPtr())
+{
+ if (!manager) {
+ isc_throw(NoCalloutManager, "must specify a CalloutManager when "
+ "instantiating a LibraryManager object");
+ }
+}
+
+// Constructor (used by "validate" for library validation). Note that this
+// sets "manager_" to not point to anything, which means that methods such as
+// registerStandardCallout() will fail, probably with a segmentation fault.
+// There are no checks for this condition in those methods: this constructor
+// is declared "private", so can only be executed by a method in this class.
+// The only method to do so is "validateLibrary", which takes care not to call
+// methods requiring a non-NULL manager.
+LibraryManager::LibraryManager(const std::string& name)
+ : dl_handle_(NULL), index_(-1), manager_(), library_name_(name)
+{}
+
+// Destructor.
+LibraryManager::~LibraryManager() {
+ if (index_ >= 0) {
+ // LibraryManager instantiated to load a library, so ensure that
+ // it is unloaded before exiting.
+ static_cast<void>(prepareUnloadLibrary());
+ }
+
+ // LibraryManager instantiated to validate a library, so just ensure
+ // that it is closed before exiting.
+ static_cast<void>(closeLibrary());
+}
+
+// Open the library
+
+bool
+LibraryManager::openLibrary() {
+
+ // Open the library. We'll resolve names now, so that if there are any
+ // issues we don't bugcheck in the middle of apparently unrelated code.
+ dl_handle_ = dlopen(library_name_.c_str(), RTLD_NOW | RTLD_LOCAL);
+ if (dl_handle_ == NULL) {
+ LOG_ERROR(hooks_logger, HOOKS_OPEN_ERROR).arg(library_name_)
+ .arg(dlerror());
+ }
+
+ return (dl_handle_ != NULL);
+}
+
+// Close the library if not already open
+
+bool
+LibraryManager::closeLibrary() {
+
+ // Close the library if it is open. (If not, this is a no-op.)
+ int status = 0;
+ if (dl_handle_ != NULL) {
+ status = dlclose(dl_handle_);
+ dl_handle_ = NULL;
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_CLOSE_ERROR).arg(library_name_)
+ .arg(dlerror());
+ } else {
+ LOG_INFO(hooks_logger, HOOKS_LIBRARY_CLOSED).arg(library_name_);
+ }
+ }
+
+ return (status == 0);
+}
+
+// Check the version of the library
+
+bool
+LibraryManager::checkVersion() const {
+
+ // Get the pointer to the "version" function.
+ PointerConverter pc(dlsym(dl_handle_, VERSION_FUNCTION_NAME));
+ if (pc.versionPtr() != NULL) {
+ int version = KEA_HOOKS_VERSION - 1; // This is an invalid value
+ try {
+ version = (*pc.versionPtr())();
+ } catch (...) {
+ LOG_ERROR(hooks_logger, HOOKS_VERSION_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (version == KEA_HOOKS_VERSION) {
+ // All OK, version checks out
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_LIBRARY_VERSION)
+ .arg(library_name_).arg(version);
+ return (true);
+
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_INCORRECT_VERSION).arg(library_name_)
+ .arg(version).arg(KEA_HOOKS_VERSION);
+ }
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_);
+ }
+
+ return (false);
+}
+
+// Check the multi-threading compatibility of the library
+
+bool
+LibraryManager::checkMultiThreadingCompatible(bool multi_threading_enabled) const {
+
+ // Compatible with single-threaded.
+ if (!multi_threading_enabled) {
+ return (true);
+ }
+
+ // Get the pointer to the "multi_threading_compatible" function.
+ PointerConverter pc(dlsym(dl_handle_, MULTI_THREADING_COMPATIBLE_FUNCTION_NAME));
+ int compatible = 0;
+ if (pc.multiThreadingCompatiblePtr()) {
+ try {
+ compatible = (*pc.multiThreadingCompatiblePtr())();
+ } catch (...) {
+ LOG_ERROR(hooks_logger, HOOKS_MULTI_THREADING_COMPATIBLE_EXCEPTION)
+ .arg(library_name_);
+ return (false);
+ }
+
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS,
+ HOOKS_LIBRARY_MULTI_THREADING_COMPATIBLE)
+ .arg(library_name_)
+ .arg(compatible);
+ }
+ if (compatible == 0) {
+ LOG_ERROR(hooks_logger, HOOKS_LIBRARY_MULTI_THREADING_NOT_COMPATIBLE)
+ .arg(library_name_);
+ }
+ return (compatible != 0);
+}
+
+// Register the standard callouts
+
+void
+LibraryManager::registerStandardCallouts() {
+ // Set the library index for doing the registration. This is picked up
+ // when the library handle is created.
+ manager_->setLibraryIndex(index_);
+
+ // Iterate through the list of known hooks
+ vector<string> hook_names = ServerHooks::getServerHooks().getHookNames();
+ for (size_t i = 0; i < hook_names.size(); ++i) {
+
+ // Look up the symbol
+ void* dlsym_ptr = dlsym(dl_handle_, hook_names[i].c_str());
+ PointerConverter pc(dlsym_ptr);
+ if (pc.calloutPtr() != NULL) {
+ // Found a symbol, so register it.
+ manager_->getLibraryHandle().registerCallout(hook_names[i],
+ pc.calloutPtr());
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS,
+ HOOKS_STD_CALLOUT_REGISTERED).arg(library_name_)
+ .arg(hook_names[i]).arg(dlsym_ptr);
+
+ }
+ }
+}
+
+// Run the "load" function if present.
+
+bool
+LibraryManager::runLoad() {
+
+ // Get the pointer to the "load" function.
+ PointerConverter pc(dlsym(dl_handle_, LOAD_FUNCTION_NAME));
+ if (pc.loadPtr() != NULL) {
+
+ // Call the load() function with the library handle. We need to set
+ // the CalloutManager's index appropriately. We'll invalidate it
+ // afterwards.
+
+ int status = -1;
+ try {
+ manager_->setLibraryIndex(index_);
+ status = (*pc.loadPtr())(manager_->getLibraryHandle());
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(hooks_logger, HOOKS_LOAD_FRAMEWORK_EXCEPTION)
+ .arg(library_name_).arg(ex.what());
+ return (false);
+ } catch (...) {
+ LOG_ERROR(hooks_logger, HOOKS_LOAD_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_LOAD_ERROR).arg(library_name_)
+ .arg(status);
+ return (false);
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LOAD_SUCCESS)
+ .arg(library_name_);
+ }
+
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_LOAD)
+ .arg(library_name_);
+ }
+
+ return (true);
+}
+
+
+// Run the "unload" function if present.
+
+bool
+LibraryManager::prepareUnloadLibrary() {
+
+ // Nothing to do.
+ if (dl_handle_ == NULL) {
+ return (true);
+ }
+
+ // Call once.
+ if (index_ < 0) {
+ return (true);
+ }
+
+ // Get the pointer to the "load" function.
+ bool result = false;
+ PointerConverter pc(dlsym(dl_handle_, UNLOAD_FUNCTION_NAME));
+ if (pc.unloadPtr() != NULL) {
+
+ // Call the load() function with the library handle. We need to set
+ // the CalloutManager's index appropriately. We'll invalidate it
+ // afterwards.
+ int status = -1;
+ try {
+ status = (*pc.unloadPtr())();
+ result = true;
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(hooks_logger, HOOKS_UNLOAD_FRAMEWORK_EXCEPTION)
+ .arg(library_name_).arg(ex.what());
+ } catch (...) {
+ // Exception generated. Note a warning as the unload will occur
+ // anyway.
+ LOG_WARN(hooks_logger, HOOKS_UNLOAD_EXCEPTION).arg(library_name_);
+ }
+
+ if (result) {
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_UNLOAD_ERROR).arg(library_name_)
+ .arg(status);
+ result = false;
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_UNLOAD_SUCCESS)
+ .arg(library_name_);
+ }
+ }
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_UNLOAD)
+ .arg(library_name_);
+ result = true;
+ }
+
+ // Regardless of status, remove all callouts associated with this
+ // library on all hooks.
+ vector<string> hooks = ServerHooks::getServerHooks().getHookNames();
+ manager_->setLibraryIndex(index_);
+ for (size_t i = 0; i < hooks.size(); ++i) {
+ bool removed = manager_->deregisterAllCallouts(hooks[i], index_);
+ if (removed) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED)
+ .arg(hooks[i]).arg(library_name_);
+ }
+ }
+
+ // Mark as unload() ran.
+ index_ = -1;
+
+ return (result);
+}
+
+// The main library loading function.
+
+bool
+LibraryManager::loadLibrary(bool multi_threading_enabled) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_LOADING)
+ .arg(library_name_);
+
+ // In the following, if a method such as openLibrary() fails, it will
+ // have issued an error message so there is no need to issue another one
+ // here.
+
+ // Open the library (which is a check that it exists and is accessible).
+ if (openLibrary()) {
+
+ // The hook libraries provide their own log messages and logger
+ // instances. This step is required to register log messages for
+ // the library being loaded in the global dictionary. Ideally, this
+ // should be called after all libraries have been loaded but we're
+ // going to call the version() and load() functions here and these
+ // functions may already contain logging statements.
+ isc::log::MessageInitializer::loadDictionary();
+
+ // The log messages registered by the new hook library may duplicate
+ // some of the existing messages. Log warning for each duplicated
+ // message now.
+ isc::log::LoggerManager::logDuplicatedMessages();
+
+ // Library opened OK, see if a version function is present and if so,
+ // check what value it returns. Check multi-threading compatibility.
+ if (checkVersion() && checkMultiThreadingCompatible(multi_threading_enabled)) {
+ // Version OK, so now register the standard callouts and call the
+ // library's load() function if present.
+ registerStandardCallouts();
+ if (runLoad()) {
+
+ // Success - the library has been successfully loaded.
+ LOG_INFO(hooks_logger, HOOKS_LIBRARY_LOADED).arg(library_name_);
+ return (true);
+
+ } else {
+
+ // The load function failed, so back out. We can't just close
+ // the library as (a) we need to call the library's "unload"
+ // function (if present) in case "load" allocated resources that
+ // need to be freed and (b) we need to remove any callouts that
+ // have been installed.
+ static_cast<void>(prepareUnloadLibrary());
+ }
+ }
+
+ // Either the version check or call to load() failed, so close the
+ // library and free up resources. Ignore the status return here - we
+ // already know there's an error and will have output a message.
+ static_cast<void>(closeLibrary());
+ }
+
+ return (false);
+}
+
+// The library unloading function. Call the unload() function (if present),
+// remove callouts from the callout manager, then close the library. This is
+// only run if the library is still loaded and is a no-op if the library is
+// not open.
+
+bool
+LibraryManager::unloadLibrary() {
+ bool result = true;
+ if (dl_handle_ != NULL) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING)
+ .arg(library_name_);
+
+ // Call the unload() function if present. Note that this is done first
+ // - operations take place in the reverse order to which they were done
+ // when the library was loaded.
+ if (index_ >= 0) {
+ result = prepareUnloadLibrary();
+ }
+
+ // ... and close the library.
+ result = closeLibrary() && result;
+ if (result) {
+
+ // Issue the informational message only if the library was unloaded
+ // with no problems. If there was an issue, an error message would
+ // have been issued.
+ LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_);
+ }
+ }
+ return (result);
+}
+
+// Validate the library. We must be able to open it, and the version function
+// must both exist and return the right number. Note that this is a static
+// method.
+
+bool
+LibraryManager::validateLibrary(const std::string& name, bool multi_threading_enabled) {
+ // Instantiate a library manager for the validation. We use the private
+ // constructor as we don't supply a CalloutManager.
+ LibraryManager manager(name);
+
+ // Try to open it and, if we succeed, check the version.
+ bool validated = manager.openLibrary() && manager.checkVersion() &&
+ manager.checkMultiThreadingCompatible(multi_threading_enabled);
+
+ // Regardless of whether the version checked out, close the library. (This
+ // is a no-op if the library failed to open.)
+ static_cast<void>(manager.closeLibrary());
+
+ return (validated);
+}
+
+// @note Moved from its own hooks.cc file to avoid undefined reference
+// with static link.
+void hooksStaticLinkInit() {
+ if (!isc::log::isLoggingInitialized()) {
+ isc::log::initLogger(std::string("userlib"));
+ }
+}
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/library_manager.h b/src/lib/hooks/library_manager.h
new file mode 100644
index 0000000..84c5d82
--- /dev/null
+++ b/src/lib/hooks/library_manager.h
@@ -0,0 +1,253 @@
+// Copyright (C) 2013-2023 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 LIBRARY_MANAGER_H
+#define LIBRARY_MANAGER_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/server_hooks.h>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+/// @brief No Callout Manager
+///
+/// Thrown if a library manager is instantiated by an external agency without
+/// specifying a CalloutManager object.
+class NoCalloutManager : public Exception {
+public:
+ NoCalloutManager(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+class CalloutManager;
+class LibraryHandle;
+class LibraryManager;
+
+/// @brief Library manager
+///
+/// This class handles the loading and unloading of a specific library. It also
+/// provides a static method for checking that a library is valid (this is used
+/// in configuration parsing).
+///
+/// On loading, it opens the library using dlopen and checks the version (set
+/// with the "version" method. If all is OK, it iterates through the list of
+/// known hooks and locates their symbols, registering each callout as it does
+/// so. Finally it locates the "load" function (if present) and calls it.
+///
+/// On unload, it clears the callouts on all hooks, calls the "unload"
+/// method if present, clears the callouts on all hooks, and
+/// closes the library.
+///
+/// @note Caution needs to be exercised when using the close method. During
+/// normal use, data will pass between the server and the library. In
+/// this process, the library may allocate memory and pass it back to the
+/// server. This could happen by the server setting arguments or context
+/// in the CalloutHandle object, or by the library modifying the content
+/// of pointed-to data. If the library is closed, this memory may lie
+/// in the virtual address space deleted in that process. (The word "may"
+/// is used, as this could be operating-system specific.) Should this
+/// happen, any reference to the memory will cause a segmentation fault.
+/// This can occur in a quite obscure place, for example in the middle of
+/// a destructor of an STL class when it is deleting memory allocated
+/// when the data structure was extended by a function in the library.
+///
+/// @note The only safe way to run the "close" function is to ensure that all
+/// possible references to it are removed first. This means that all
+/// CalloutHandles must be destroyed, as must any data items that were
+/// passed to the callouts. In practice, it could mean that a server
+/// suspends processing of new requests until all existing ones have
+/// been serviced and all packet/context structures destroyed before
+/// reloading the libraries.
+///
+/// When validating a library, only the fact that the library can be opened and
+/// version() exists and returns the correct number is checked. The library
+/// is closed after the validation.
+
+class LibraryManager {
+public:
+ /// @brief Constructor
+ ///
+ /// This constructor is used by external agencies (i.e. the
+ /// LibraryManagerCollection) when instantiating a LibraryManager. It
+ /// stores the library name - the actual actual loading is done in
+ /// loadLibrary().
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ /// @param index Index of this library
+ /// @param manager CalloutManager object
+ ///
+ /// @throw NoCalloutManager Thrown if the manager argument is NULL.
+ LibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager);
+
+ /// @brief Destructor
+ ///
+ /// If the library is open, closes it. This is principally a safety
+ /// feature to ensure closure in the case of an exception destroying this
+ /// object. However, see the caveat in the class header about when it is
+ /// safe to close libraries.
+ ~LibraryManager();
+
+ /// @brief Validate library
+ ///
+ /// A static method that is used to validate a library. Validation checks
+ /// that the library can be opened, that "version" exists, and that it
+ /// returns the right number, and the multi-threading compatibility.
+ ///
+ /// @param name Name of the library to validate
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return true if the library validated, false if not. If the library
+ /// fails to validate, the reason for the failure is logged.
+ static bool validateLibrary(const std::string& name,
+ bool multi_threading_enabled = false);
+
+ /// @brief Loads a library
+ ///
+ /// Open the library, check the version and the multi-threading
+ /// compatibility. If all is OK, load all standard symbols then
+ /// call "load" if present.
+ ///
+ /// It also calls the @c isc::log::MessageInitializer::loadDictionary,
+ /// prior to invoking the @c version function of the library, to
+ /// update the global logging dictionary with the log messages
+ /// registered by the loaded library.
+ ///
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return true if the library loaded successfully, false otherwise.
+ /// In the latter case, the library will be unloaded if possible.
+ bool loadLibrary(bool multi_threading_enabled = false);
+
+ /// @brief Prepares library unloading
+ ///
+ /// Searches for the "unload" framework function and, if present, runs it.
+ /// Regardless of status, remove all callouts associated with this
+ /// library on all hooks.
+ ///
+ /// @return bool true if not found or found and run successfully,
+ /// false on an error. In this case, an error message will
+ /// have been output.
+ bool prepareUnloadLibrary();
+
+ /// @brief Return library name
+ ///
+ /// @return Name of this library
+ std::string getName() const {
+ return (library_name_);
+ }
+
+protected:
+ // The following methods are protected as they are accessed in testing.
+
+ /// @brief Unloads a library
+ ///
+ /// Calls the libraries "unload" function if present, the closes the
+ /// library.
+ ///
+ /// However, see the caveat in the class header about when it is safe to
+ /// unload libraries.
+ ///
+ /// @return true if the library unloaded successfully, false if an error
+ /// occurred in the process (most likely the unload() function
+ /// (if present) returned an error). Even if an error did occur,
+ /// the library is closed if possible.
+ bool unloadLibrary();
+
+ /// @brief Open library
+ ///
+ /// Opens the library associated with this LibraryManager. A message is
+ /// logged on an error.
+ ///
+ /// @return true if the library opened successfully, false otherwise.
+ bool openLibrary();
+
+ /// @brief Close library
+ ///
+ /// Closes the library associated with this LibraryManager. A message is
+ /// logged on an error.
+ ///
+ /// @return true if the library closed successfully, false otherwise. "true"
+ /// is also returned if the library were already closed when this
+ /// method was called.
+ bool closeLibrary();
+
+ /// @brief Check library version
+ ///
+ /// With the library open, accesses the "version()" function and, if
+ /// present, checks the returned value against the hooks version symbol
+ /// for the currently running Kea. The "version()" function is
+ /// mandatory and must be present (and return the correct value) for the
+ /// library to load.
+ ///
+ /// If there is no version() function, or if there is a mismatch in
+ /// version number, a message logged.
+ ///
+ /// @return bool true if the check succeeded
+ bool checkVersion() const;
+
+ /// @brief Check multi-threading compatibility
+ ///
+ /// If the multi-threading mode is disabled returns true, else with
+ /// the library open, accesses the "multi_threading_compatible()"
+ /// function and returns false if not exists or has value 0, returns
+ /// true otherwise.
+ ///
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return bool true if the check succeeded
+ bool checkMultiThreadingCompatible(bool multi_threading_enabled) const;
+
+ /// @brief Register standard callouts
+ ///
+ /// Loops through the list of hook names and searches the library for
+ /// functions with those names. Any that are found are registered as
+ /// callouts for that hook.
+ void registerStandardCallouts();
+
+ /// @brief Run the load function if present
+ ///
+ /// Searches for the "load" framework function and, if present, runs it.
+ ///
+ /// @return bool true if not found or found and run successfully,
+ /// false on an error. In this case, an error message will
+ /// have been output.
+ bool runLoad();
+
+private:
+ /// @brief Validating constructor
+ ///
+ /// Constructor used when the LibraryManager is instantiated to validate
+ /// a library (i.e. by the "validateLibrary" static method).
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ LibraryManager(const std::string& name);
+
+ // Member variables
+
+ void* dl_handle_; ///< Handle returned by dlopen
+ int index_; ///< Index associated with this library
+ boost::shared_ptr<CalloutManager> manager_;
+ ///< Callout manager for registration
+ std::string library_name_; ///< Name of the library
+
+ ServerHooksPtr server_hooks_; ///< Stores a pointer to ServerHooks.
+
+};
+
+} // namespace hooks
+} // namespace isc
+
+#endif // LIBRARY_MANAGER_H
diff --git a/src/lib/hooks/library_manager_collection.cc b/src/lib/hooks/library_manager_collection.cc
new file mode 100644
index 0000000..09b7912
--- /dev/null
+++ b/src/lib/hooks/library_manager_collection.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2013-2023 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 <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+
+namespace isc {
+namespace hooks {
+
+// Return callout manager for the loaded libraries. This call is only valid
+// after one has been created for the loaded libraries (which includes the
+// case of no loaded libraries).
+//
+// Note that there is no real connection between the callout manager and the
+// libraries, other than it knows the number of libraries so can do sanity
+// checks on values passed to it. However, this may change in the future,
+// so the hooks framework is written such that a callout manager is used only
+// with the LibraryManagerCollection that created it. It is also the reason
+// why each LibraryManager contains a pointer to this CalloutManager.
+
+boost::shared_ptr<CalloutManager>
+LibraryManagerCollection::getCalloutManager() const {
+
+ // Only return a pointer if we have a CalloutManager created.
+ if (!callout_manager_) {
+ isc_throw(LoadLibrariesNotCalled, "must load hooks libraries before "
+ "attempting to retrieve a CalloutManager for them");
+ }
+
+ return (callout_manager_);
+}
+
+LibraryManagerCollection::LibraryManagerCollection(const HookLibsCollection& libraries)
+ : library_info_(libraries) {
+
+ // We need to split hook libs into library names and library parameters.
+ for (HookLibsCollection::const_iterator it = libraries.begin();
+ it != libraries.end(); ++it) {
+ library_names_.push_back(it->first);
+ }
+}
+
+// Load a set of libraries
+
+bool
+LibraryManagerCollection::loadLibraries(bool multi_threading_enabled) {
+
+ // There must be no libraries still in memory.
+ if (!lib_managers_.empty()) {
+ isc_throw(LibrariesStillOpened, "some libraries are still opened");
+ }
+
+ // Access the callout manager, (re)creating it if required.
+ //
+ // A pointer to the callout manager is maintained by each as well as by
+ // the HooksManager itself. Note that the callout manager does not hold any
+ // memory allocated by a library: although a library registers a callout
+ // (and so causes the creation of an entry in the CalloutManager's callout
+ // list), that creation is done by the CalloutManager itself. The
+ // CalloutManager is created within the server. The upshot of this is that
+ // it is therefore safe for the CalloutManager to be deleted after all
+ // associated libraries are deleted, hence this link (LibraryManager ->
+ // CalloutManager) is safe.
+ //
+ // The call of this function will result in re-creating the callout manager.
+ // This deletes all callouts (including the pre-library and post-
+ // library) ones. It is up to the libraries to re-register their callouts.
+ // The pre-library and post-library callouts will also need to be
+ // re-registered.
+ callout_manager_.reset(new CalloutManager(library_names_.size()));
+
+ // Now iterate through the libraries are load them one by one. We'll
+ for (size_t i = 0; i < library_names_.size(); ++i) {
+ // Create a pointer to the new library manager. The index of this
+ // library is determined by the number of library managers currently
+ // loaded: note that the library indexes run from 1 to (number of loaded
+ // libraries).
+ boost::shared_ptr<LibraryManager> manager(
+ new LibraryManager(library_names_[i], lib_managers_.size() + 1,
+ callout_manager_));
+
+ // Load the library. On success, add it to the list of loaded
+ // libraries. On failure, unload all currently loaded libraries,
+ // leaving the object in the state it was in before loadLibraries was
+ // called.
+ if (manager->loadLibrary(multi_threading_enabled)) {
+ lib_managers_.push_back(manager);
+ } else {
+ static_cast<void>(unloadLibraries());
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+// Unload the libraries.
+
+void
+LibraryManagerCollection::unloadLibraries() {
+
+ // Delete the library managers in the reverse order to which they were
+ // created, then clear the library manager vector.
+ while (!lib_managers_.empty()) {
+ lib_managers_.pop_back();
+ }
+
+ // Get rid of the callout manager. (The other member, the list of library
+ // names, was cleared when the libraries were loaded.)
+ callout_manager_.reset();
+}
+
+// Prepare the unloading of libraries.
+bool
+LibraryManagerCollection::prepareUnloadLibraries() {
+ bool result = true;
+ // Iterate on library managers in reverse order.
+ for (auto lm = lib_managers_.rbegin(); lm != lib_managers_.rend(); ++lm) {
+ result = (*lm)->prepareUnloadLibrary() && result;
+ }
+ return (result);
+}
+
+// Return number of loaded libraries.
+int
+LibraryManagerCollection::getLoadedLibraryCount() const {
+ return (lib_managers_.size());
+}
+
+// Validate the libraries.
+std::vector<std::string>
+LibraryManagerCollection::validateLibraries(const std::vector<std::string>& libraries,
+ bool multi_threading_enabled) {
+
+ std::vector<std::string> failures;
+ for (size_t i = 0; i < libraries.size(); ++i) {
+ if (!LibraryManager::validateLibrary(libraries[i], multi_threading_enabled)) {
+ failures.push_back(libraries[i]);
+ }
+ }
+
+ return (failures);
+}
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/library_manager_collection.h b/src/lib/hooks/library_manager_collection.h
new file mode 100644
index 0000000..e602e9f
--- /dev/null
+++ b/src/lib/hooks/library_manager_collection.h
@@ -0,0 +1,192 @@
+// Copyright (C) 2013-2023 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 LIBRARY_MANAGER_COLLECTION_H
+#define LIBRARY_MANAGER_COLLECTION_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+#include <hooks/libinfo.h>
+
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief LoadLibraries not called
+///
+/// Thrown if an attempt is made get a CalloutManager before the libraries
+/// have been loaded.
+class LoadLibrariesNotCalled : public Exception {
+public:
+ LoadLibrariesNotCalled(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+// Forward declarations
+class CalloutManager;
+class LibraryManager;
+
+/// @brief Library manager collection
+///
+/// The LibraryManagerCollection class, as the name implies, is responsible for
+/// managing the collection of LibraryManager objects that describe the loaded
+/// libraries. As such, it converts a single operation (e.g load libraries)
+/// into multiple operations, one per library. However, the class does more
+/// than that - it provides a single object with which to manage lifetimes.
+///
+/// As described in the LibraryManager documentation, a CalloutHandle may end
+/// up with pointers to memory within the address space of a loaded library.
+/// If the library is closed before this address space is deleted, the
+/// deletion of the CalloutHandle may attempt to free memory into the newly-
+/// unmapped address space and cause a segmentation fault.
+///
+/// To prevent this, each CalloutHandle maintains a shared pointer to the
+/// LibraryManagerCollection current when it was created. In addition, the
+/// containing HooksManager object also maintains a shared pointer to it.
+/// A LibraryManagerCollection is never explicitly deleted: when a new set
+/// of libraries is loaded, the HooksManager clears its pointer to the
+/// collection. The LibraryManagerCollection is only destroyed when all
+/// CallHandle objects referencing it are destroyed.
+///
+/// Note that this does not completely solve the problem - a hook function may
+/// have modified a packet being processed by the server and that packet may
+/// hold a pointer to memory in the library's virtual address space. To avoid
+/// a segmentation fault, that packet needs to free the memory before the
+/// LibraryManagerCollection is destroyed and this places demands on the server
+/// code. However, the link with the CalloutHandle does at least mean that
+/// authors of server code do not need to be so careful about when they destroy
+/// CalloutHandles.
+///
+/// The collection object also provides a utility function to validate a set
+/// of libraries. The function checks that each library exists, can be opened,
+/// that the "version" function exists and return the right number.
+
+class LibraryManagerCollection {
+public:
+ /// @brief Constructor
+ ///
+ /// @param libraries List of libraries that this collection will manage.
+ /// The order of the libraries is important. It holds the library
+ /// names and its configuration parameters.
+ LibraryManagerCollection(const HookLibsCollection& libraries);
+
+ /// @brief Destructor
+ ///
+ /// Unloads all loaded libraries.
+ ~LibraryManagerCollection() {
+ static_cast<void>(unloadLibraries());
+ }
+
+ /// @brief Load libraries
+ ///
+ /// Loads the libraries. This creates the LibraryManager associated with
+ /// each library and calls its loadLibrary() method. If a library fails
+ /// to load, the loading is abandoned and all libraries loaded so far
+ /// are unloaded.
+ ///
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return true if all libraries loaded, false if one or more failed t
+ //// load.
+ bool loadLibraries(bool multi_threading_enabled = false);
+
+ /// @brief Get callout manager
+ ///
+ /// Returns a callout manager that can be used with this set of loaded
+ /// libraries (even if the number of loaded libraries is zero). This
+ /// method may only be called after loadLibraries() has been called.
+ ///
+ /// @return Pointer to a callout manager for this set of libraries.
+ ///
+ /// @throw LoadLibrariesNotCalled Thrown if this method is called between
+ /// construction and the time loadLibraries() is called.
+ boost::shared_ptr<CalloutManager> getCalloutManager() const;
+
+ /// @brief Get library names
+ ///
+ /// Returns the list of library names. If called before loadLibraries(),
+ /// the list is the list of names to be loaded; if called afterwards, it
+ /// is the list of libraries that have been loaded.
+ std::vector<std::string> getLibraryNames() const {
+ return (library_names_);
+ }
+
+ /// @brief Returns library info
+ ///
+ /// Returns a collection of libraries, each entry consisting of a library
+ /// name + all its parameters.
+ HookLibsCollection getLibraryInfo() const {
+ return (library_info_);
+ }
+
+ /// @brief Get number of loaded libraries
+ ///
+ /// Mainly for testing, this returns the number of libraries that are
+ /// loaded.
+ ///
+ /// @return Number of libraries that are loaded.
+ int getLoadedLibraryCount() const;
+
+ /// @brief Validate libraries
+ ///
+ /// Utility function to validate libraries. It checks that the libraries
+ /// exist, can be opened, that a "version" function is present in them, and
+ /// that it returns the right number. All errors are logged.
+ ///
+ /// @param libraries List of libraries to validate.
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled
+ /// (used to check hook libraries compatibility with MT).
+ ///
+ /// @return Vector of libraries that failed to validate, or an empty vector
+ /// if all validated.
+ static std::vector<std::string> validateLibraries(const std::vector<std::string>& libraries,
+ bool multi_threading_enabled = false);
+
+ /// @brief Prepare libaries unloading
+ ///
+ /// Utility function to call before closing libraries. It runs the
+ /// unload() function when it exists and removes associated callout.
+ /// When this function returns either there is only one owner
+ /// (the hook manager) or some visible dangling pointers so
+ /// libraries are not closed to lower the probability of a crash.
+ /// See @ref LibraryManager::prepareUnloadLibrary.
+ ///
+ /// @return true if all libraries unload were not found or run
+ /// successfully, false on an error.
+ bool prepareUnloadLibraries();
+
+protected:
+ /// @brief Unload libraries
+ ///
+ /// Unloads and closes all loaded libraries. They are unloaded in the
+ /// reverse order to the order in which they were loaded.
+ void unloadLibraries();
+
+private:
+
+ /// Vector of library names
+ std::vector<std::string> library_names_;
+
+ /// Vector of library managers
+ std::vector<boost::shared_ptr<LibraryManager> > lib_managers_;
+
+ /// Vector of library information. Each piece of information
+ /// consists of a pair of (library name, library parameters)
+ HookLibsCollection library_info_;
+
+ /// Callout manager to be associated with the libraries
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // LIBRARY_MANAGER_COLLECTION_H
diff --git a/src/lib/hooks/parking_lots.h b/src/lib/hooks/parking_lots.h
new file mode 100644
index 0000000..e4e82bd
--- /dev/null
+++ b/src/lib/hooks/parking_lots.h
@@ -0,0 +1,426 @@
+// Copyright (C) 2018-2021 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 PARKING_LOTS_H
+#define PARKING_LOTS_H
+
+#include <exceptions/exceptions.h>
+#include <boost/any.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <functional>
+#include <iostream>
+#include <sstream>
+#include <list>
+#include <unordered_map>
+#include <mutex>
+#include <thread>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Parking lot for objects, e.g. packets, for a hook point.
+///
+/// Callouts may instruct the servers to "park" processed packets, i.e. suspend
+/// their processing until explicitly unparked. This is useful in cases when
+/// callouts need to perform asynchronous operations related to the packet
+/// processing and the packet must not be further processed until the
+/// asynchronous operations are completed. While the packet is parked, the
+/// new packets can be processed, so the server remains responsive to the
+/// new requests.
+///
+/// Parking lots are created per hook point, so the callouts installed on the
+/// particular hook point only have access to the parking lots dedicated to
+/// them.
+///
+/// The parking lot object supports 5 actions: "park", "reference",
+/// "dereference", "unpark", and "drop".
+///
+/// In the typical case, the server parks the object and the callouts reference
+/// and unpark the objects. Therefore, the @ref ParkingLot object is not passed
+/// directly to the callouts. Instead, a ParkingLotHandle object is provided
+/// to the callout, which only provides access to "reference", "dereference",
+/// and "unpark" operations.
+///
+/// Parking an object is performed, proactively by the server, before callouts
+/// are invoked. Referencing (and dereferencing) an object is performed by the
+/// callouts before the @c CalloutHandle::NEXT_STEP_PARK is returned to the
+/// server.
+///
+/// Trying to reference (or deference) and unparked object will result
+/// in error. Referencing (reference counting) is an important part of the
+/// parking mechanism, which allows multiple callouts, installed on the same
+/// hook point, to perform asynchronous operations and guarantees that the
+/// object remains parked until all those asynchronous operations complete.
+/// Each such callout must call @c unpark() when it desires the object to
+/// be unparked, but the object will only be unparked when all callouts call
+/// this function, i.e. when all callouts signal completion of their respective
+/// asynchronous operations.
+///
+/// Dereferencing, decrements the reference count without invoking the unpark
+/// callback. This allows hook callouts to proactively reference the object
+/// in a callout and then cancel the reference should further processing
+/// deem it the reference unnecessary.
+///
+/// The types of the parked objects provided as T parameter of respective
+/// functions are most often shared pointers. One should not use references
+/// to parked objects nor references to shared pointers to avoid premature
+/// destruction of the parked objects.
+class ParkingLot {
+public:
+ /// @brief Parks an object.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object object to be parked, e.g. pointer to a packet.
+ /// @param unpark_callback callback function to be invoked when the object
+ /// is unparked.
+ /// @throw InvalidOperation if this object has already been parked.
+ template<typename T>
+ void park(T parked_object, std::function<void()> unpark_callback) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ auto it = find(parked_object);
+ if (it != parking_.end()) {
+ isc_throw(InvalidOperation, "object is already parked!");
+ }
+
+ // Add the object to the parking lot. At this point refcount = 0.
+ ParkingInfo pinfo(parked_object, unpark_callback);
+ parking_[makeKey(parked_object)] = pinfo;
+ }
+
+ /// @brief Increases reference counter for the parked object.
+ ///
+ /// This method is called by the callouts to increase a reference count
+ /// on the object to be parked. It may only be called after the object
+ /// has been parked
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object object which will be parked.
+ /// @return the integer number of references for this object.
+ template<typename T>
+ int reference(T parked_object) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ auto it = find(parked_object);
+ if (it == parking_.end()) {
+ isc_throw(InvalidOperation, "cannot reference an object"
+ " that has not been parked.");
+ }
+
+ // Bump and return the reference count
+ return (++it->second.refcount_);
+ }
+
+ /// @brief Decreases the reference counter for the parked object.
+ ///
+ /// This method is called by the callouts to decrease the reference count
+ /// on a parked object.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object parked object whose count should be reduced.
+ /// @return the integer number of references for this object.
+ template<typename T>
+ int dereference(T parked_object) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ auto it = find(parked_object);
+ if (it == parking_.end()) {
+ isc_throw(InvalidOperation, "cannot dereference an object"
+ " that has not been parked.");
+ }
+
+ // Decrement and return the reference count.
+ return (--it->second.refcount_);
+ }
+
+ /// @brief Signals that the object should be unparked.
+ ///
+ /// If the specified object is parked in this parking lot, the reference
+ /// count is decreased as a result of this method. If the reference count
+ /// is 0, the object is unparked and the callback is invoked. Typically, the
+ /// callback points to a function which resumes processing of a packet.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object parked object to be unparked.
+ /// @param force boolean value indicating if the reference counting should
+ /// be ignored and the object should be unparked immediately.
+ /// @return false if the object couldn't be unparked because there is
+ /// no such object, true otherwise.
+ template<typename T>
+ bool unpark(T parked_object, bool force = false) {
+ // Initialize as the empty function.
+ std::function<void()> cb;
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ auto it = find(parked_object);
+ if (it == parking_.end()) {
+ // No such parked object.
+ return (false);
+ }
+
+ if (force) {
+ it->second.refcount_ = 0;
+ } else {
+ --it->second.refcount_;
+ }
+
+ if (it->second.refcount_ <= 0) {
+ // Unpark the packet and set the callback.
+ cb = it->second.unpark_callback_;
+ parking_.erase(it);
+ }
+ }
+
+ // Invoke the callback if not empty.
+ if (cb) {
+ cb();
+ }
+
+ // Parked object found, so return true to indicate that the
+ // operation was successful. It doesn't necessarily mean
+ // that the object was unparked, but at least the reference
+ // count was decreased.
+ return (true);
+ }
+
+ /// @brief Removes parked object without calling a callback.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object parked object to be removed.
+ /// @return false if the object couldn't be removed because there is
+ /// no such object, true otherwise.
+ template<typename T>
+ bool drop(T parked_object) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ auto it = find(parked_object);
+ if (it != parking_.end()) {
+ // Parked object found.
+ parking_.erase(it);
+ return (true);
+ }
+
+ // No such object.
+ return (false);
+ }
+
+ /// @brief Returns the current number of objects.
+ size_t size() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (parking_.size());
+ }
+
+public:
+
+ /// @brief Holds information about parked object.
+ struct ParkingInfo {
+ /// @brief The parked object.
+ boost::any parked_object_;
+
+ /// @brief The pointer to callback.
+ std::function<void()> unpark_callback_;
+
+ /// @brief The current reference count.
+ int refcount_;
+
+ /// @brief Constructor.
+ ///
+ /// Default constructor.
+ ParkingInfo() : refcount_(0) {}
+
+ /// @brief Constructor.
+ ///
+ /// @param parked_object object being parked.
+ /// @param callback pointer to the callback.
+ ParkingInfo(const boost::any& parked_object,
+ std::function<void()> callback = 0)
+ : parked_object_(parked_object), unpark_callback_(callback),
+ refcount_(0) {}
+
+ /// @brief Update parking information.
+ ///
+ /// @param parked_object parked object.
+ /// @param callback pointer to the callback.
+ void update(const boost::any& parked_object,
+ std::function<void()> callback) {
+ parked_object_ = parked_object;
+ unpark_callback_ = callback;
+ }
+ };
+
+private:
+
+ /// @brief Map which stores parked objects.
+ typedef std::unordered_map<std::string, ParkingInfo> ParkingInfoList;
+
+ /// @brief Type of the iterator in the list of parked objects.
+ typedef ParkingInfoList::iterator ParkingInfoListIterator;
+
+ /// @brief Container holding parked objects for this parking lot.
+ ParkingInfoList parking_;
+
+ /// @brief Construct the key for a given parked object.
+ ///
+ /// @tparam T parked object type.
+ /// @param parked_object object from which the key should be constructed.
+ /// @return string containing the object's key.
+ template<typename T>
+ std::string makeKey(T parked_object) {
+ std::stringstream ss;
+ ss << boost::any_cast<T>(parked_object);
+ return (ss.str());
+ }
+
+ /// @brief Search for the information about the parked object.
+ ///
+ /// @tparam T parked object type.
+ /// @param parked_object object for which to search.
+ /// @return Iterator pointing to the parked object, or @c parking_.end()
+ /// if no such object found.
+ template<typename T>
+ ParkingInfoListIterator find(T parked_object) {
+ return (parking_.find(makeKey(parked_object)));
+ }
+
+ /// @brief The mutex to protect parking lot internal state.
+ ///
+ /// All public methods must enter of lock guard with the mutex
+ /// before any access to the @c parking_ member.
+ std::mutex mutex_;
+};
+
+/// @brief Type of the pointer to the parking lot.
+typedef boost::shared_ptr<ParkingLot> ParkingLotPtr;
+
+/// @brief Provides a limited view to the @c ParkingLot.
+///
+/// The handle is provided to the callouts which can reference and unpark
+/// parked objects. The callouts should not park objects, therefore this
+/// operation is not available.
+///
+/// The types of the parked objects provided as T parameter of respective
+/// functions are most often shared pointers. One should not use references
+/// to parked objects nor references to shared pointers to avoid premature
+/// destruction of the parked objects.
+class ParkingLotHandle {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param parking_lot pointer to the parking lot for which the handle is
+ /// created.
+ ParkingLotHandle(const ParkingLotPtr& parking_lot)
+ : parking_lot_(parking_lot) {
+ }
+
+ /// @brief Increases reference counter for the parked object.
+ ///
+ /// This method is called by the callouts to increase a reference count
+ /// on the object to be parked. It must be called before the object is
+ /// actually parked.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object object which will be parked.
+ /// @return new reference count as an integer
+ template<typename T>
+ int reference(T parked_object) {
+ return (parking_lot_->reference(parked_object));
+ }
+
+ /// @brief Decreases the reference counter for the parked object.
+ ///
+ /// This method is called by the callouts to decrease the reference count
+ /// of a parked object.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object object which will be parked.
+ /// @return new reference count as an integer
+ template<typename T>
+ int dereference(T parked_object) {
+ return (parking_lot_->dereference(parked_object));
+ }
+
+ /// @brief Signals that the object should be unparked.
+ ///
+ /// If the specified object is parked in this parking lot, the reference
+ /// count is decreased as a result of this method. If the reference count
+ /// is 0, the object is unparked and the callback is invoked. Typically, the
+ /// callback points to a function which resumes processing of a packet.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object parked object to be unparked.
+ /// @return false if the object couldn't be unparked because there is
+ /// no such object, true otherwise.
+ template<typename T>
+ bool unpark(T parked_object) {
+ return (parking_lot_->unpark(parked_object));
+ }
+
+ /// @brief Removes parked object without calling a callback.
+ ///
+ /// It ignores any reference counts on the parked object.
+ ///
+ /// @tparam Type of the parked object.
+ /// @param parked_object parked object to be removed.
+ /// @return false if the object couldn't be removed because there is
+ /// no such object, true otherwise.
+ template<typename T>
+ bool drop(T parked_object) {
+ return (parking_lot_->drop(parked_object));
+ }
+
+private:
+
+ /// @brief Parking lot to which this handle points.
+ ParkingLotPtr parking_lot_;
+
+};
+
+/// @brief Pointer to the parking lot handle.
+typedef boost::shared_ptr<ParkingLotHandle> ParkingLotHandlePtr;
+
+/// @brief Collection of parking lots for various hook points.
+class ParkingLots {
+public:
+
+ /// @brief Removes all parked objects.
+ ///
+ /// It doesn't invoke callbacks associated with the removed objects.
+ void clear() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ parking_lots_.clear();
+ }
+
+ /// @brief Returns pointer to the parking lot for a hook points.
+ ///
+ /// If the parking lot for the specified hook point doesn't exist, it is
+ /// created.
+ ///
+ /// @param hook_index index of the hook point with which the parking
+ /// lot is associated.
+ /// @return Pointer to the parking lot.
+ ParkingLotPtr getParkingLotPtr(const int hook_index) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (parking_lots_.count(hook_index) == 0) {
+ parking_lots_[hook_index] = boost::make_shared<ParkingLot>();
+ }
+ return (parking_lots_[hook_index]);
+ }
+
+private:
+
+ /// @brief Container holding parking lots for various hook points.
+ std::unordered_map<int, ParkingLotPtr> parking_lots_;
+
+ /// @brief The mutex to protect parking lots internal state.
+ std::mutex mutex_;
+};
+
+/// @brief Type of the pointer to the parking lots.
+typedef boost::shared_ptr<ParkingLots> ParkingLotsPtr;
+
+} // end of namespace hooks
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/hooks/pointer_converter.h b/src/lib/hooks/pointer_converter.h
new file mode 100644
index 0000000..5f51d8d
--- /dev/null
+++ b/src/lib/hooks/pointer_converter.h
@@ -0,0 +1,122 @@
+// Copyright (C) 2013-2020 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 POINTER_CONVERTER_H
+#define POINTER_CONVERTER_H
+
+#include <hooks/hooks.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Local class for conversion of void pointers to function pointers
+///
+/// Converting between void* and function pointers in C++ is fraught with
+/// difficulty and pitfalls, e.g. see
+/// https://groups.google.com/forum/?hl=en&fromgroups#!topic/comp.lang.c++/37o0l8rtEE0
+///
+/// The method given in that article - convert using a union is used here. A
+/// union is declared (and zeroed) and the appropriate member extracted when
+/// needed.
+
+class PointerConverter {
+public:
+ /// @brief Constructor
+ ///
+ /// Zeroes the union and stores the void* pointer we wish to convert (the
+ /// one returned by dlsym).
+ ///
+ /// @param dlsym_ptr void* pointer returned by call to dlsym()
+ PointerConverter(void* dlsym_ptr) {
+ memset(&pointers_, 0, sizeof(pointers_));
+ pointers_.dlsym_ptr = dlsym_ptr;
+ }
+
+ /// @brief Constructor
+ ///
+ /// Zeroes the union and stores the CalloutPtr pointer we wish to convert.
+ /// This constructor is used in debug messages; output of a pointer to
+ /// an object (including to a function) is, on some compilers, printed as
+ /// "1".
+ ///
+ /// @param callout_ptr Pointer to callout function
+ PointerConverter(CalloutPtr callout_ptr) {
+ memset(&pointers_, 0, sizeof(pointers_));
+ pointers_.callout_ptr = callout_ptr;
+ }
+
+ /// @name Pointer accessor functions
+ ///
+ /// It is up to the caller to ensure that the correct member is called so
+ /// that the correct type of pointer is returned.
+ ///
+ ///@{
+
+ /// @brief Return pointer returned by dlsym call
+ ///
+ /// @return void* pointer returned by the call to dlsym(). This can be
+ /// used in statements that print the hexadecimal value of the
+ /// symbol.
+ void* dlsymPtr() const {
+ return (pointers_.dlsym_ptr);
+ }
+
+ /// @brief Return pointer to callout function
+ ///
+ /// @return Pointer to the callout function
+ CalloutPtr calloutPtr() const {
+ return (pointers_.callout_ptr);
+ }
+
+ /// @brief Return pointer to load function
+ ///
+ /// @return Pointer to the load function
+ load_function_ptr loadPtr() const {
+ return (pointers_.load_ptr);
+ }
+
+ /// @brief Return pointer to unload function
+ ///
+ /// @return Pointer to the unload function
+ unload_function_ptr unloadPtr() const {
+ return (pointers_.unload_ptr);
+ }
+
+ /// @brief Return pointer to version function
+ ///
+ /// @return Pointer to the version function
+ version_function_ptr versionPtr() const {
+ return (pointers_.version_ptr);
+ }
+
+ /// @brief Return pointer to multi_threading_compatible function
+ ///
+ /// @return Pointer to the multi_threading_compatible function
+ multi_threading_compatible_function_ptr multiThreadingCompatiblePtr() const {
+ return (pointers_.multi_threading_compatible_ptr);
+ }
+
+ ///@}
+
+private:
+
+ /// @brief Union linking void* and pointers to functions.
+ union {
+ void* dlsym_ptr; // void* returned by dlsym
+ CalloutPtr callout_ptr; // Pointer to callout
+ load_function_ptr load_ptr; // Pointer to load function
+ unload_function_ptr unload_ptr; // Pointer to unload function
+ version_function_ptr version_ptr; // Pointer to version function
+ multi_threading_compatible_function_ptr multi_threading_compatible_ptr;
+ // Pointer to multi_threading_compatible function
+ } pointers_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // POINTER_CONVERTER_H
diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc
new file mode 100644
index 0000000..5ca1a50
--- /dev/null
+++ b/src/lib/hooks/server_hooks.cc
@@ -0,0 +1,221 @@
+// Copyright (C) 2013-2018 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 <hooks/hooks_log.h>
+#include <hooks/server_hooks.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+
+namespace isc {
+namespace hooks {
+
+// Constructor - register the pre-defined hooks and check that the indexes
+// assigned to them are as expected.
+//
+// Note that there are no logging messages here or in registerHooks(). The
+// recommended way to initialize hook names is to use static initialization.
+// Here, a static object is declared in a file outside of any function or
+// method. As a result, it is instantiated and its constructor run before the
+// program starts. By putting calls to ServerHooks::registerHook() in there,
+// hooks names are already registered when the program runs. However, at that
+// point, the logging system is not initialized, so messages are unable to
+// be output.
+
+ServerHooks::ServerHooks() {
+ initialize();
+}
+
+// Register a hook. The index assigned to the hook is the current number
+// of entries in the collection, so ensuring that hook indexes are unique
+// and non-negative.
+
+int
+ServerHooks::registerHook(const string& name) {
+
+ // Determine index for the new element and insert.
+ int index = hooks_.size();
+ pair<HookCollection::iterator, bool> result =
+ hooks_.insert(make_pair(name, index));
+
+ /// @todo: We also need to call CalloutManager::ensureVectorSize(), so it
+ /// adjusts its vector. Since CalloutManager is not a singleton, there's
+ /// no getInstance() or similar. Also, CalloutManager uses ServerHooks,
+ /// so such a call would induce circular dependencies. Ugh.
+
+ if (!result.second) {
+
+ // There's a problem with hook libraries that need to be linked with
+ // libdhcpsrv. For example host_cmds hook library requires host
+ // parser, so it needs to be linked with libdhcpsrv. However, when
+ // unit-tests are started, the hook points are not registered.
+ // When the library is loaded new hook points are registered.
+ // This causes issues in the hooks framework, especially when
+ // LibraryManager::unloadLibrary() iterates through all hooks
+ // and then calls deregisterAllCallouts. This method gets
+ // hook_index that is greater than number of elements in
+ // hook_vector_ and then we have a read past the array boundary.
+ /// @todo: See ticket 5251 and 5208 for details.
+ return (getIndex(name));
+
+ // New element was not inserted because an element with the same name
+ // already existed.
+ //isc_throw(DuplicateHook, "hook with name " << name <<
+ // " is already registered");
+ }
+
+ // Element was inserted, so add to the inverse hooks collection.
+ inverse_hooks_[index] = name;
+
+ // ... and return numeric index.
+ return (index);
+}
+
+// Set ServerHooks object to initial state.
+
+void
+ServerHooks::initialize() {
+
+ // Clear out the name->index and index->name maps.
+ hooks_.clear();
+ inverse_hooks_.clear();
+ parking_lots_.reset(new ParkingLots());
+
+ // Register the pre-defined hooks.
+ int create = registerHook("context_create");
+ int destroy = registerHook("context_destroy");
+
+ // Check registration went as expected.
+ if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) {
+ isc_throw(Unexpected, "pre-defined hook indexes are not as expected. "
+ "context_create: expected = " << CONTEXT_CREATE <<
+ ", actual = " << create <<
+ ". context_destroy: expected = " << CONTEXT_DESTROY <<
+ ", actual = " << destroy);
+ }
+}
+
+// Reset ServerHooks object to initial state.
+
+void
+ServerHooks::reset() {
+
+ // Clear all hooks then initialize the pre-defined ones.
+ initialize();
+
+ // Log a warning - although this is done during testing, it should never be
+ // seen in a production system.
+ LOG_WARN(hooks_logger, HOOKS_HOOK_LIST_RESET);
+}
+
+// Find the name associated with a hook index.
+
+std::string
+ServerHooks::getName(int index) const {
+
+ // Get iterator to matching element.
+ InverseHookCollection::const_iterator i = inverse_hooks_.find(index);
+ if (i == inverse_hooks_.end()) {
+ isc_throw(NoSuchHook, "hook index " << index << " is not recognized");
+ }
+
+ return (i->second);
+}
+
+// Find the index associated with a hook name.
+
+int
+ServerHooks::getIndex(const string& name) const {
+
+ // Get iterator to matching element.
+ HookCollection::const_iterator i = hooks_.find(name);
+ if (i == hooks_.end()) {
+ isc_throw(NoSuchHook, "hook name " << name << " is not recognized");
+ }
+
+ return (i->second);
+}
+
+int
+ServerHooks::findIndex(const std::string& name) const {
+ // Get iterator to matching element.
+ auto i = hooks_.find(name);
+ return ((i == hooks_.end()) ? -1 : i->second);
+}
+
+// Return vector of hook names. The names are not sorted - it is up to the
+// caller to perform sorting if required.
+
+vector<string>
+ServerHooks::getHookNames() const {
+
+ vector<string> names;
+ HookCollection::const_iterator i;
+ for (i = hooks_.begin(); i != hooks_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return global ServerHooks object
+
+ServerHooks&
+ServerHooks::getServerHooks() {
+ return (*getServerHooksPtr());
+}
+
+ServerHooksPtr
+ServerHooks::getServerHooksPtr() {
+ static ServerHooksPtr hooks(new ServerHooks());
+ return (hooks);
+}
+
+ParkingLotsPtr
+ServerHooks::getParkingLotsPtr() const {
+ return (parking_lots_);
+}
+
+ParkingLotPtr
+ServerHooks::getParkingLotPtr(const int hook_index) {
+ return (parking_lots_->getParkingLotPtr(hook_index));
+}
+
+ParkingLotPtr
+ServerHooks::getParkingLotPtr(const std::string& hook_name) {
+ return (parking_lots_->getParkingLotPtr(getServerHooks().getIndex(hook_name)));
+}
+
+std::string
+ServerHooks::commandToHookName(const std::string& command_name) {
+ // Prefix the command name with a dollar sign.
+ std::string hook_name = std::string("$") + command_name;
+ // Replace all hyphens with underscores.
+ std::replace(hook_name.begin(), hook_name.end(), '-', '_');
+ return (hook_name);
+}
+
+std::string
+ServerHooks::hookToCommandName(const std::string& hook_name) {
+ if (!hook_name.empty() && hook_name.front() == '$') {
+ std::string command_name = hook_name.substr(1);
+ std::replace(command_name.begin(), command_name.end(), '_', '-');
+ return (command_name);
+ }
+ return ("");
+}
+
+
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h
new file mode 100644
index 0000000..2c9df97
--- /dev/null
+++ b/src/lib/hooks/server_hooks.h
@@ -0,0 +1,246 @@
+// Copyright (C) 2013-2018 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 SERVER_HOOKS_H
+#define SERVER_HOOKS_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/parking_lots.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Duplicate hook
+///
+/// Thrown if an attempt is made to register a hook with the same name as a
+/// previously-registered hook.
+class DuplicateHook : public Exception {
+public:
+ DuplicateHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid hook
+///
+/// Thrown if an attempt is made to get the index for an invalid hook.
+class NoSuchHook : public Exception {
+public:
+ NoSuchHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+class ServerHooks;
+typedef boost::shared_ptr<ServerHooks> ServerHooksPtr;
+
+/// @brief Server hook collection
+///
+/// This class is used by the server-side code to register hooks - points in the
+/// server processing at which libraries can register functions (callouts) that
+/// the server will call. These functions can modify data and so affect the
+/// processing of the server.
+///
+/// The ServerHooks class is little more than a wrapper around the std::map
+/// class. It stores a hook, assigning to it a unique index number. This
+/// number is then used by the server code to identify the hook being called.
+/// (Although it would be feasible to use a name as an index, using an integer
+/// will speed up the time taken to locate the callouts, which may make a
+/// difference in a frequently-executed piece of code.)
+///
+/// ServerHooks is a singleton object and is only accessible by the static
+/// method getServerHooks().
+
+class ServerHooks : public boost::noncopyable {
+public:
+
+ /// Index numbers for pre-defined hooks.
+ static const int CONTEXT_CREATE = 0;
+ static const int CONTEXT_DESTROY = 1;
+
+ /// @brief Reset to Initial State
+ ///
+ /// Resets the collection of hooks to the initial state, with just the
+ /// context_create and context_destroy hooks set. This used during
+ /// testing to reset the global ServerHooks object; it should never be
+ /// used in production.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ void reset();
+
+ /// @brief Register a hook
+ ///
+ /// Registers a hook and returns the hook index.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent hook-related calls.
+ /// This will be greater than or equal to zero (so allowing a
+ /// negative value to indicate an invalid index).
+ ///
+ /// @throws DuplicateHook A hook with the same name has already been
+ /// registered.
+ int registerHook(const std::string& name);
+
+ /// @brief Get hook name
+ ///
+ /// Returns the name of a hook given the index. This is most likely to be
+ /// used in log messages.
+ ///
+ /// @param index Index of the hook
+ ///
+ /// @return Name of the hook.
+ ///
+ /// @throw NoSuchHook if the hook index is invalid.
+ std::string getName(int index) const;
+
+ /// @brief Get hook index
+ ///
+ /// Returns the index of a hook.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent calls.
+ ///
+ /// @throw NoSuchHook if the hook name is unknown to the caller.
+ int getIndex(const std::string& name) const;
+
+ /// @brief Find hook index
+ ///
+ /// Provides exception safe method of retrieving an index of the
+ /// specified hook.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook if the hook point exists, or -1 if the
+ /// hook point doesn't exist.
+ int findIndex(const std::string& name) const;
+
+ /// @brief Return number of hooks
+ ///
+ /// Returns the total number of hooks registered.
+ ///
+ /// @return Number of hooks registered.
+ int getCount() const {
+ return (hooks_.size());
+ }
+
+ /// @brief Get hook names
+ ///
+ /// Return list of hooks registered in the object.
+ ///
+ /// @return Vector of strings holding hook names.
+ std::vector<std::string> getHookNames() const;
+
+ /// @brief Return ServerHooks object
+ ///
+ /// Returns the global ServerHooks object.
+ ///
+ /// @return Reference to the global ServerHooks object.
+ static ServerHooks& getServerHooks();
+
+ /// @brief Returns pointer to ServerHooks object.
+ ///
+ /// @return Pointer to the global ServerHooks object.
+ static ServerHooksPtr getServerHooksPtr();
+
+ /// @brief Returns pointer to all parking lots.
+ ///
+ /// @return pointer to all parking lots.
+ ParkingLotsPtr getParkingLotsPtr() const;
+
+ /// @brief Returns pointer to the ParkingLot for the specified hook index.
+ ///
+ /// @param hook_index index of the hook point for which the parking lot
+ /// should be returned.
+ /// @return Pointer to the ParkingLot object.
+ ParkingLotPtr getParkingLotPtr(const int hook_index);
+
+ /// @brief Returns pointer to the ParkingLot for the specified hook name.
+ ///
+ /// @param hook_name name of the hook point for which the parking lot
+ /// should be returned.
+ /// @return Pointer to the ParkingLot object.
+ ParkingLotPtr getParkingLotPtr(const std::string& hook_name);
+
+ /// @brief Generates hook point name for the given control command name.
+ ///
+ /// This function is called to generate the name of the hook point
+ /// when the hook point is used to install command handlers for the
+ /// given control command.
+ ///
+ /// The name of the hook point is generated as follows:
+ /// - command name is prefixed with a dollar sign,
+ /// - all hyphens are replaced with underscores.
+ ///
+ /// For example, if the command_name is 'foo-bar', the resulting hook
+ /// point name will be '$foo_bar'.
+ ///
+ /// @param command_name Command name for which the hook point name is
+ /// to be generated.
+ ///
+ /// @return Hook point name, or an empty string if the command name
+ /// can't be converted to a hook name (e.g. when it lacks dollar sign).
+ static std::string commandToHookName(const std::string& command_name);
+
+ /// @brief Returns command name for a specified hook name.
+ ///
+ /// This function removes leading dollar sign and replaces underscores
+ /// with hyphens.
+ ///
+ /// @param hook_name Hook name for which command name should be returned.
+ ///
+ /// @return Command name.
+ static std::string hookToCommandName(const std::string& hook_name);
+
+private:
+ /// @brief Constructor
+ ///
+ /// This pre-registers two hooks, context_create and context_destroy, which
+ /// are called by the server before processing a packet and after processing
+ /// for the packet has completed. They allow the server code to allocate
+ /// and destroy per-packet context.
+ ///
+ /// The constructor is declared private to enforce the singleton nature of
+ /// the object. A reference to the singleton is obtainable through the
+ /// getServerHooks() static method.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ ServerHooks();
+
+ /// @brief Initialize hooks
+ ///
+ /// Sets the collection of hooks to the initial state, with just the
+ /// context_create and context_destroy hooks set. This is used during
+ /// construction.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ void initialize();
+
+ /// Useful typedefs.
+ typedef std::map<std::string, int> HookCollection;
+ typedef std::map<int, std::string> InverseHookCollection;
+
+ /// Two maps, one for name->index, the other for index->name. (This is
+ /// simpler than using a multi-indexed container.)
+ HookCollection hooks_; ///< Hook name/index collection
+ InverseHookCollection inverse_hooks_; ///< Hook index/name collection
+
+ ParkingLotsPtr parking_lots_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // SERVER_HOOKS_H
diff --git a/src/lib/hooks/tests/Makefile.am b/src/lib/hooks/tests/Makefile.am
new file mode 100644
index 0000000..8cbe413
--- /dev/null
+++ b/src/lib/hooks/tests/Makefile.am
@@ -0,0 +1,152 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Kea libraries against which the test user libraries are linked.
+HOOKS_LIB = $(top_builddir)/src/lib/hooks/libkea-hooks.la
+LOG_LIB = $(top_builddir)/src/lib/log/libkea-log.la
+UTIL_LIB = $(top_builddir)/src/lib/util/libkea-util.la
+EXCEPTIONS_LIB = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+
+ALL_LIBS = $(HOOKS_LIB) $(LOG_LIB) $(UTIL_LIB) $(EXCEPTIONS_LIB) $(LOG4CPLUS_LIBS)
+
+# Files to clean include the file created by testing.
+CLEANFILES = *.gcno *.gcda $(builddir)/marker_file.dat
+
+# Files generated by configure
+DISTCLEANFILES = marker_file.h test_libraries.h
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+# Build shared libraries for testing. The libtool way to create a shared library
+# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS
+# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html).
+# Use of these switches will guarantee that the .so files are created in the
+# .libs folder and they can be dlopened.
+#
+# Note that the shared libraries with callouts should not be used together with
+# the --enable-static-link option. With this option, the bind10 libraries are
+# statically linked with the program and if the callout invokes the methods
+# which belong to these libraries, the library with the callout will get its
+# own copy of the static objects (e.g. logger, ServerHooks) and that will lead
+# to unexpected errors. For this reason, the --enable-static-link option is
+# ignored for unit tests built here.
+
+noinst_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la
+noinst_LTLIBRARIES += liblecl.la libucl.la libfcl.la libpcl.la libacl.la
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+
+# No version function
+libnvl_la_SOURCES = no_version_library.cc
+libnvl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libnvl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libnvl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# Incorrect version function
+libivl_la_SOURCES = incorrect_version_library.cc
+libivl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libivl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libivl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# All framework functions throw an exception
+libfxl_la_SOURCES = framework_exception_library.cc
+libfxl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfxl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libfxl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The basic callout library - contains standard callouts
+libbcl_la_SOURCES = basic_callout_library.cc
+libbcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libbcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libbcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The load callout library - contains a load function
+liblcl_la_SOURCES = load_callout_library.cc
+liblcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+liblcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+liblcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The load error callout library - contains a load function that returns
+# an error.
+liblecl_la_SOURCES = load_error_callout_library.cc
+liblecl_la_CXXFLAGS = $(AM_CXXFLAGS)
+liblecl_la_CPPFLAGS = $(AM_CPPFLAGS)
+liblecl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The unload callout library - contains an unload function that
+# creates a marker file.
+libucl_la_SOURCES = unload_callout_library.cc
+libucl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libucl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libucl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The full callout library - contains all three framework functions.
+libfcl_la_SOURCES = full_callout_library.cc
+libfcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libfcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The parameters checking callout library - expects
+libpcl_la_SOURCES = callout_params_library.cc
+libpcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libpcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libpcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+libpcl_la_LDFLAGS += $(top_builddir)/src/lib/util/libkea-util.la
+
+# The async callout library - parks object for asynchronous task
+libacl_la_SOURCES = async_callout_library.cc
+libacl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libacl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libacl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += callout_handle_unittest.cc
+run_unittests_SOURCES += callout_handle_associate_unittest.cc
+run_unittests_SOURCES += callout_manager_unittest.cc
+run_unittests_SOURCES += common_test_class.h
+run_unittests_SOURCES += handles_unittest.cc
+run_unittests_SOURCES += hooks_manager_unittest.cc
+run_unittests_SOURCES += library_manager_collection_unittest.cc
+run_unittests_SOURCES += library_manager_unittest.cc
+run_unittests_SOURCES += parking_lots_unittest.cc
+run_unittests_SOURCES += server_hooks_unittest.cc
+
+nodist_run_unittests_SOURCES = marker_file.h
+nodist_run_unittests_SOURCES += test_libraries.h
+
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+if USE_STATIC_LINK
+run_unittests_LDFLAGS += -static -export-dynamic
+endif
+
+run_unittests_LDADD = $(AM_LDADD)
+run_unittests_LDADD += $(ALL_LIBS)
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(GTEST_LDADD) $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+# As noted in configure.ac, libtool doesn't work perfectly with Darwin: it
+# embeds the final install path in dynamic libraries and loadable modules refer
+# to that path even if its loaded within the source tree, so preventing tests
+# from working - but only when linking statically. The solution used in other
+# Makefiles (setting the path to the dynamic libraries via an environment
+# variable) don't seem to work. What does work is to run the unit test using
+# libtool and specifying paths via -dlopen switches. So... If running in an
+# environment where we have to set the library path AND if linking statically,
+# override the "check" target and run the unit tests ourselves.
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = marker_file.h.in test_libraries.h.in
diff --git a/src/lib/hooks/tests/Makefile.in b/src/lib/hooks/tests/Makefile.in
new file mode 100644
index 0000000..688124d
--- /dev/null
+++ b/src/lib/hooks/tests/Makefile.in
@@ -0,0 +1,1547 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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
+@HAVE_GTEST_TRUE@@USE_STATIC_LINK_TRUE@am__append_2 = -static -export-dynamic
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/hooks/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = marker_file.h test_libraries.h
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = run_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libacl_la_LIBADD =
+am__libacl_la_SOURCES_DIST = async_callout_library.cc
+@HAVE_GTEST_TRUE@am_libacl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libacl_la-async_callout_library.lo
+libacl_la_OBJECTS = $(am_libacl_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 =
+libacl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libacl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libacl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libacl_la_rpath =
+libbcl_la_LIBADD =
+am__libbcl_la_SOURCES_DIST = basic_callout_library.cc
+@HAVE_GTEST_TRUE@am_libbcl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libbcl_la-basic_callout_library.lo
+libbcl_la_OBJECTS = $(am_libbcl_la_OBJECTS)
+libbcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libbcl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libbcl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libbcl_la_rpath =
+libfcl_la_LIBADD =
+am__libfcl_la_SOURCES_DIST = full_callout_library.cc
+@HAVE_GTEST_TRUE@am_libfcl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libfcl_la-full_callout_library.lo
+libfcl_la_OBJECTS = $(am_libfcl_la_OBJECTS)
+libfcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libfcl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libfcl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libfcl_la_rpath =
+libfxl_la_LIBADD =
+am__libfxl_la_SOURCES_DIST = framework_exception_library.cc
+@HAVE_GTEST_TRUE@am_libfxl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libfxl_la-framework_exception_library.lo
+libfxl_la_OBJECTS = $(am_libfxl_la_OBJECTS)
+libfxl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libfxl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libfxl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libfxl_la_rpath =
+libivl_la_LIBADD =
+am__libivl_la_SOURCES_DIST = incorrect_version_library.cc
+@HAVE_GTEST_TRUE@am_libivl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libivl_la-incorrect_version_library.lo
+libivl_la_OBJECTS = $(am_libivl_la_OBJECTS)
+libivl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libivl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libivl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libivl_la_rpath =
+liblcl_la_LIBADD =
+am__liblcl_la_SOURCES_DIST = load_callout_library.cc
+@HAVE_GTEST_TRUE@am_liblcl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ liblcl_la-load_callout_library.lo
+liblcl_la_OBJECTS = $(am_liblcl_la_OBJECTS)
+liblcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(liblcl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(liblcl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_liblcl_la_rpath =
+liblecl_la_LIBADD =
+am__liblecl_la_SOURCES_DIST = load_error_callout_library.cc
+@HAVE_GTEST_TRUE@am_liblecl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ liblecl_la-load_error_callout_library.lo
+liblecl_la_OBJECTS = $(am_liblecl_la_OBJECTS)
+liblecl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(liblecl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(liblecl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_liblecl_la_rpath =
+libnvl_la_LIBADD =
+am__libnvl_la_SOURCES_DIST = no_version_library.cc
+@HAVE_GTEST_TRUE@am_libnvl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libnvl_la-no_version_library.lo
+libnvl_la_OBJECTS = $(am_libnvl_la_OBJECTS)
+libnvl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libnvl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libnvl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libnvl_la_rpath =
+libpcl_la_LIBADD =
+am__libpcl_la_SOURCES_DIST = callout_params_library.cc
+@HAVE_GTEST_TRUE@am_libpcl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libpcl_la-callout_params_library.lo
+libpcl_la_OBJECTS = $(am_libpcl_la_OBJECTS)
+libpcl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libpcl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libpcl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libpcl_la_rpath =
+libucl_la_LIBADD =
+am__libucl_la_SOURCES_DIST = unload_callout_library.cc
+@HAVE_GTEST_TRUE@am_libucl_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libucl_la-unload_callout_library.lo
+libucl_la_OBJECTS = $(am_libucl_la_OBJECTS)
+libucl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libucl_la_CXXFLAGS) \
+ $(CXXFLAGS) $(libucl_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libucl_la_rpath =
+am__run_unittests_SOURCES_DIST = run_unittests.cc \
+ callout_handle_unittest.cc \
+ callout_handle_associate_unittest.cc \
+ callout_manager_unittest.cc common_test_class.h \
+ handles_unittest.cc hooks_manager_unittest.cc \
+ library_manager_collection_unittest.cc \
+ library_manager_unittest.cc parking_lots_unittest.cc \
+ server_hooks_unittest.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-callout_handle_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-callout_handle_associate_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-callout_manager_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-handles_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hooks_manager_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-library_manager_collection_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-library_manager_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-parking_lots_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-server_hooks_unittest.$(OBJEXT)
+nodist_run_unittests_OBJECTS =
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS) \
+ $(nodist_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(HOOKS_LIB) $(LOG_LIB) $(UTIL_LIB) \
+ $(EXCEPTIONS_LIB) $(am__DEPENDENCIES_1)
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(am__DEPENDENCIES_2) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1)
+run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(run_unittests_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)/libacl_la-async_callout_library.Plo \
+ ./$(DEPDIR)/libbcl_la-basic_callout_library.Plo \
+ ./$(DEPDIR)/libfcl_la-full_callout_library.Plo \
+ ./$(DEPDIR)/libfxl_la-framework_exception_library.Plo \
+ ./$(DEPDIR)/libivl_la-incorrect_version_library.Plo \
+ ./$(DEPDIR)/liblcl_la-load_callout_library.Plo \
+ ./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo \
+ ./$(DEPDIR)/libnvl_la-no_version_library.Plo \
+ ./$(DEPDIR)/libpcl_la-callout_params_library.Plo \
+ ./$(DEPDIR)/libucl_la-unload_callout_library.Plo \
+ ./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po \
+ ./$(DEPDIR)/run_unittests-callout_handle_unittest.Po \
+ ./$(DEPDIR)/run_unittests-callout_manager_unittest.Po \
+ ./$(DEPDIR)/run_unittests-handles_unittest.Po \
+ ./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po \
+ ./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po \
+ ./$(DEPDIR)/run_unittests-library_manager_unittest.Po \
+ ./$(DEPDIR)/run_unittests-parking_lots_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-server_hooks_unittest.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 = $(libacl_la_SOURCES) $(libbcl_la_SOURCES) \
+ $(libfcl_la_SOURCES) $(libfxl_la_SOURCES) $(libivl_la_SOURCES) \
+ $(liblcl_la_SOURCES) $(liblecl_la_SOURCES) \
+ $(libnvl_la_SOURCES) $(libpcl_la_SOURCES) $(libucl_la_SOURCES) \
+ $(run_unittests_SOURCES) $(nodist_run_unittests_SOURCES)
+DIST_SOURCES = $(am__libacl_la_SOURCES_DIST) \
+ $(am__libbcl_la_SOURCES_DIST) $(am__libfcl_la_SOURCES_DIST) \
+ $(am__libfxl_la_SOURCES_DIST) $(am__libivl_la_SOURCES_DIST) \
+ $(am__liblcl_la_SOURCES_DIST) $(am__liblecl_la_SOURCES_DIST) \
+ $(am__libnvl_la_SOURCES_DIST) $(am__libpcl_la_SOURCES_DIST) \
+ $(am__libucl_la_SOURCES_DIST) \
+ $(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)`
+ETAGS = etags
+CTAGS = ctags
+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 $(srcdir)/marker_file.h.in \
+ $(srcdir)/test_libraries.h.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Kea libraries against which the test user libraries are linked.
+HOOKS_LIB = $(top_builddir)/src/lib/hooks/libkea-hooks.la
+LOG_LIB = $(top_builddir)/src/lib/log/libkea-log.la
+UTIL_LIB = $(top_builddir)/src/lib/util/libkea-util.la
+EXCEPTIONS_LIB = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+ALL_LIBS = $(HOOKS_LIB) $(LOG_LIB) $(UTIL_LIB) $(EXCEPTIONS_LIB) $(LOG4CPLUS_LIBS)
+
+# Files to clean include the file created by testing.
+CLEANFILES = *.gcno *.gcda $(builddir)/marker_file.dat
+
+# Files generated by configure
+DISTCLEANFILES = marker_file.h test_libraries.h
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+# Build shared libraries for testing. The libtool way to create a shared library
+# is to specify "-avoid-version -export-dynamic -module" in the library LDFLAGS
+# (see http://www.gnu.org/software/libtool/manual/html_node/Link-mode.html).
+# Use of these switches will guarantee that the .so files are created in the
+# .libs folder and they can be dlopened.
+#
+# Note that the shared libraries with callouts should not be used together with
+# the --enable-static-link option. With this option, the bind10 libraries are
+# statically linked with the program and if the callout invokes the methods
+# which belong to these libraries, the library with the callout will get its
+# own copy of the static objects (e.g. logger, ServerHooks) and that will lead
+# to unexpected errors. For this reason, the --enable-static-link option is
+# ignored for unit tests built here.
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libnvl.la libivl.la libfxl.la \
+@HAVE_GTEST_TRUE@ libbcl.la liblcl.la liblecl.la libucl.la \
+@HAVE_GTEST_TRUE@ libfcl.la libpcl.la libacl.la
+
+# -rpath /nowhere is a hack to trigger libtool to not create a
+# convenience archive, resulting in shared modules
+
+# No version function
+@HAVE_GTEST_TRUE@libnvl_la_SOURCES = no_version_library.cc
+@HAVE_GTEST_TRUE@libnvl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libnvl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libnvl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# Incorrect version function
+@HAVE_GTEST_TRUE@libivl_la_SOURCES = incorrect_version_library.cc
+@HAVE_GTEST_TRUE@libivl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libivl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libivl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# All framework functions throw an exception
+@HAVE_GTEST_TRUE@libfxl_la_SOURCES = framework_exception_library.cc
+@HAVE_GTEST_TRUE@libfxl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libfxl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libfxl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The basic callout library - contains standard callouts
+@HAVE_GTEST_TRUE@libbcl_la_SOURCES = basic_callout_library.cc
+@HAVE_GTEST_TRUE@libbcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libbcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libbcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The load callout library - contains a load function
+@HAVE_GTEST_TRUE@liblcl_la_SOURCES = load_callout_library.cc
+@HAVE_GTEST_TRUE@liblcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@liblcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@liblcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The load error callout library - contains a load function that returns
+# an error.
+@HAVE_GTEST_TRUE@liblecl_la_SOURCES = load_error_callout_library.cc
+@HAVE_GTEST_TRUE@liblecl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@liblecl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@liblecl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The unload callout library - contains an unload function that
+# creates a marker file.
+@HAVE_GTEST_TRUE@libucl_la_SOURCES = unload_callout_library.cc
+@HAVE_GTEST_TRUE@libucl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libucl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libucl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The full callout library - contains all three framework functions.
+@HAVE_GTEST_TRUE@libfcl_la_SOURCES = full_callout_library.cc
+@HAVE_GTEST_TRUE@libfcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libfcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libfcl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The parameters checking callout library - expects
+@HAVE_GTEST_TRUE@libpcl_la_SOURCES = callout_params_library.cc
+@HAVE_GTEST_TRUE@libpcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libpcl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libpcl_la_LDFLAGS = -avoid-version -export-dynamic \
+@HAVE_GTEST_TRUE@ -module -rpath /nowhere \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la
+
+# The async callout library - parks object for asynchronous task
+@HAVE_GTEST_TRUE@libacl_la_SOURCES = async_callout_library.cc
+@HAVE_GTEST_TRUE@libacl_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libacl_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libacl_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ callout_handle_unittest.cc \
+@HAVE_GTEST_TRUE@ callout_handle_associate_unittest.cc \
+@HAVE_GTEST_TRUE@ callout_manager_unittest.cc \
+@HAVE_GTEST_TRUE@ common_test_class.h handles_unittest.cc \
+@HAVE_GTEST_TRUE@ hooks_manager_unittest.cc \
+@HAVE_GTEST_TRUE@ library_manager_collection_unittest.cc \
+@HAVE_GTEST_TRUE@ library_manager_unittest.cc \
+@HAVE_GTEST_TRUE@ parking_lots_unittest.cc \
+@HAVE_GTEST_TRUE@ server_hooks_unittest.cc
+@HAVE_GTEST_TRUE@nodist_run_unittests_SOURCES = marker_file.h \
+@HAVE_GTEST_TRUE@ test_libraries.h
+@HAVE_GTEST_TRUE@run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(GTEST_LDFLAGS) $(am__append_2)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(AM_LDADD) $(ALL_LIBS) \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(GTEST_LDADD) $(LOG4CPLUS_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS)
+EXTRA_DIST = marker_file.h.in test_libraries.h.in
+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/hooks/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/hooks/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):
+marker_file.h: $(top_builddir)/config.status $(srcdir)/marker_file.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_libraries.h: $(top_builddir)/config.status $(srcdir)/test_libraries.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libacl.la: $(libacl_la_OBJECTS) $(libacl_la_DEPENDENCIES) $(EXTRA_libacl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libacl_la_LINK) $(am_libacl_la_rpath) $(libacl_la_OBJECTS) $(libacl_la_LIBADD) $(LIBS)
+
+libbcl.la: $(libbcl_la_OBJECTS) $(libbcl_la_DEPENDENCIES) $(EXTRA_libbcl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libbcl_la_LINK) $(am_libbcl_la_rpath) $(libbcl_la_OBJECTS) $(libbcl_la_LIBADD) $(LIBS)
+
+libfcl.la: $(libfcl_la_OBJECTS) $(libfcl_la_DEPENDENCIES) $(EXTRA_libfcl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libfcl_la_LINK) $(am_libfcl_la_rpath) $(libfcl_la_OBJECTS) $(libfcl_la_LIBADD) $(LIBS)
+
+libfxl.la: $(libfxl_la_OBJECTS) $(libfxl_la_DEPENDENCIES) $(EXTRA_libfxl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libfxl_la_LINK) $(am_libfxl_la_rpath) $(libfxl_la_OBJECTS) $(libfxl_la_LIBADD) $(LIBS)
+
+libivl.la: $(libivl_la_OBJECTS) $(libivl_la_DEPENDENCIES) $(EXTRA_libivl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libivl_la_LINK) $(am_libivl_la_rpath) $(libivl_la_OBJECTS) $(libivl_la_LIBADD) $(LIBS)
+
+liblcl.la: $(liblcl_la_OBJECTS) $(liblcl_la_DEPENDENCIES) $(EXTRA_liblcl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(liblcl_la_LINK) $(am_liblcl_la_rpath) $(liblcl_la_OBJECTS) $(liblcl_la_LIBADD) $(LIBS)
+
+liblecl.la: $(liblecl_la_OBJECTS) $(liblecl_la_DEPENDENCIES) $(EXTRA_liblecl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(liblecl_la_LINK) $(am_liblecl_la_rpath) $(liblecl_la_OBJECTS) $(liblecl_la_LIBADD) $(LIBS)
+
+libnvl.la: $(libnvl_la_OBJECTS) $(libnvl_la_DEPENDENCIES) $(EXTRA_libnvl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libnvl_la_LINK) $(am_libnvl_la_rpath) $(libnvl_la_OBJECTS) $(libnvl_la_LIBADD) $(LIBS)
+
+libpcl.la: $(libpcl_la_OBJECTS) $(libpcl_la_DEPENDENCIES) $(EXTRA_libpcl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libpcl_la_LINK) $(am_libpcl_la_rpath) $(libpcl_la_OBJECTS) $(libpcl_la_LIBADD) $(LIBS)
+
+libucl.la: $(libucl_la_OBJECTS) $(libucl_la_DEPENDENCIES) $(EXTRA_libucl_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libucl_la_LINK) $(am_libucl_la_rpath) $(libucl_la_OBJECTS) $(libucl_la_LIBADD) $(LIBS)
+
+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)/libacl_la-async_callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libbcl_la-basic_callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfcl_la-full_callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libfxl_la-framework_exception_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libivl_la-incorrect_version_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblcl_la-load_callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libnvl_la-no_version_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpcl_la-callout_params_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libucl_la-unload_callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-callout_handle_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-callout_manager_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-handles_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-library_manager_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-parking_lots_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-server_hooks_unittest.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 $@ $<
+
+libacl_la-async_callout_library.lo: async_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libacl_la_CPPFLAGS) $(CPPFLAGS) $(libacl_la_CXXFLAGS) $(CXXFLAGS) -MT libacl_la-async_callout_library.lo -MD -MP -MF $(DEPDIR)/libacl_la-async_callout_library.Tpo -c -o libacl_la-async_callout_library.lo `test -f 'async_callout_library.cc' || echo '$(srcdir)/'`async_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libacl_la-async_callout_library.Tpo $(DEPDIR)/libacl_la-async_callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='async_callout_library.cc' object='libacl_la-async_callout_library.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) $(libacl_la_CPPFLAGS) $(CPPFLAGS) $(libacl_la_CXXFLAGS) $(CXXFLAGS) -c -o libacl_la-async_callout_library.lo `test -f 'async_callout_library.cc' || echo '$(srcdir)/'`async_callout_library.cc
+
+libbcl_la-basic_callout_library.lo: basic_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libbcl_la_CPPFLAGS) $(CPPFLAGS) $(libbcl_la_CXXFLAGS) $(CXXFLAGS) -MT libbcl_la-basic_callout_library.lo -MD -MP -MF $(DEPDIR)/libbcl_la-basic_callout_library.Tpo -c -o libbcl_la-basic_callout_library.lo `test -f 'basic_callout_library.cc' || echo '$(srcdir)/'`basic_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libbcl_la-basic_callout_library.Tpo $(DEPDIR)/libbcl_la-basic_callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_callout_library.cc' object='libbcl_la-basic_callout_library.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) $(libbcl_la_CPPFLAGS) $(CPPFLAGS) $(libbcl_la_CXXFLAGS) $(CXXFLAGS) -c -o libbcl_la-basic_callout_library.lo `test -f 'basic_callout_library.cc' || echo '$(srcdir)/'`basic_callout_library.cc
+
+libfcl_la-full_callout_library.lo: full_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfcl_la_CPPFLAGS) $(CPPFLAGS) $(libfcl_la_CXXFLAGS) $(CXXFLAGS) -MT libfcl_la-full_callout_library.lo -MD -MP -MF $(DEPDIR)/libfcl_la-full_callout_library.Tpo -c -o libfcl_la-full_callout_library.lo `test -f 'full_callout_library.cc' || echo '$(srcdir)/'`full_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfcl_la-full_callout_library.Tpo $(DEPDIR)/libfcl_la-full_callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='full_callout_library.cc' object='libfcl_la-full_callout_library.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) $(libfcl_la_CPPFLAGS) $(CPPFLAGS) $(libfcl_la_CXXFLAGS) $(CXXFLAGS) -c -o libfcl_la-full_callout_library.lo `test -f 'full_callout_library.cc' || echo '$(srcdir)/'`full_callout_library.cc
+
+libfxl_la-framework_exception_library.lo: framework_exception_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libfxl_la_CPPFLAGS) $(CPPFLAGS) $(libfxl_la_CXXFLAGS) $(CXXFLAGS) -MT libfxl_la-framework_exception_library.lo -MD -MP -MF $(DEPDIR)/libfxl_la-framework_exception_library.Tpo -c -o libfxl_la-framework_exception_library.lo `test -f 'framework_exception_library.cc' || echo '$(srcdir)/'`framework_exception_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libfxl_la-framework_exception_library.Tpo $(DEPDIR)/libfxl_la-framework_exception_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='framework_exception_library.cc' object='libfxl_la-framework_exception_library.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) $(libfxl_la_CPPFLAGS) $(CPPFLAGS) $(libfxl_la_CXXFLAGS) $(CXXFLAGS) -c -o libfxl_la-framework_exception_library.lo `test -f 'framework_exception_library.cc' || echo '$(srcdir)/'`framework_exception_library.cc
+
+libivl_la-incorrect_version_library.lo: incorrect_version_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libivl_la_CPPFLAGS) $(CPPFLAGS) $(libivl_la_CXXFLAGS) $(CXXFLAGS) -MT libivl_la-incorrect_version_library.lo -MD -MP -MF $(DEPDIR)/libivl_la-incorrect_version_library.Tpo -c -o libivl_la-incorrect_version_library.lo `test -f 'incorrect_version_library.cc' || echo '$(srcdir)/'`incorrect_version_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libivl_la-incorrect_version_library.Tpo $(DEPDIR)/libivl_la-incorrect_version_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='incorrect_version_library.cc' object='libivl_la-incorrect_version_library.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) $(libivl_la_CPPFLAGS) $(CPPFLAGS) $(libivl_la_CXXFLAGS) $(CXXFLAGS) -c -o libivl_la-incorrect_version_library.lo `test -f 'incorrect_version_library.cc' || echo '$(srcdir)/'`incorrect_version_library.cc
+
+liblcl_la-load_callout_library.lo: load_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblcl_la_CPPFLAGS) $(CPPFLAGS) $(liblcl_la_CXXFLAGS) $(CXXFLAGS) -MT liblcl_la-load_callout_library.lo -MD -MP -MF $(DEPDIR)/liblcl_la-load_callout_library.Tpo -c -o liblcl_la-load_callout_library.lo `test -f 'load_callout_library.cc' || echo '$(srcdir)/'`load_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblcl_la-load_callout_library.Tpo $(DEPDIR)/liblcl_la-load_callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='load_callout_library.cc' object='liblcl_la-load_callout_library.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) $(liblcl_la_CPPFLAGS) $(CPPFLAGS) $(liblcl_la_CXXFLAGS) $(CXXFLAGS) -c -o liblcl_la-load_callout_library.lo `test -f 'load_callout_library.cc' || echo '$(srcdir)/'`load_callout_library.cc
+
+liblecl_la-load_error_callout_library.lo: load_error_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(liblecl_la_CPPFLAGS) $(CPPFLAGS) $(liblecl_la_CXXFLAGS) $(CXXFLAGS) -MT liblecl_la-load_error_callout_library.lo -MD -MP -MF $(DEPDIR)/liblecl_la-load_error_callout_library.Tpo -c -o liblecl_la-load_error_callout_library.lo `test -f 'load_error_callout_library.cc' || echo '$(srcdir)/'`load_error_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/liblecl_la-load_error_callout_library.Tpo $(DEPDIR)/liblecl_la-load_error_callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='load_error_callout_library.cc' object='liblecl_la-load_error_callout_library.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) $(liblecl_la_CPPFLAGS) $(CPPFLAGS) $(liblecl_la_CXXFLAGS) $(CXXFLAGS) -c -o liblecl_la-load_error_callout_library.lo `test -f 'load_error_callout_library.cc' || echo '$(srcdir)/'`load_error_callout_library.cc
+
+libnvl_la-no_version_library.lo: no_version_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libnvl_la_CPPFLAGS) $(CPPFLAGS) $(libnvl_la_CXXFLAGS) $(CXXFLAGS) -MT libnvl_la-no_version_library.lo -MD -MP -MF $(DEPDIR)/libnvl_la-no_version_library.Tpo -c -o libnvl_la-no_version_library.lo `test -f 'no_version_library.cc' || echo '$(srcdir)/'`no_version_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libnvl_la-no_version_library.Tpo $(DEPDIR)/libnvl_la-no_version_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='no_version_library.cc' object='libnvl_la-no_version_library.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) $(libnvl_la_CPPFLAGS) $(CPPFLAGS) $(libnvl_la_CXXFLAGS) $(CXXFLAGS) -c -o libnvl_la-no_version_library.lo `test -f 'no_version_library.cc' || echo '$(srcdir)/'`no_version_library.cc
+
+libpcl_la-callout_params_library.lo: callout_params_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpcl_la_CPPFLAGS) $(CPPFLAGS) $(libpcl_la_CXXFLAGS) $(CXXFLAGS) -MT libpcl_la-callout_params_library.lo -MD -MP -MF $(DEPDIR)/libpcl_la-callout_params_library.Tpo -c -o libpcl_la-callout_params_library.lo `test -f 'callout_params_library.cc' || echo '$(srcdir)/'`callout_params_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpcl_la-callout_params_library.Tpo $(DEPDIR)/libpcl_la-callout_params_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_params_library.cc' object='libpcl_la-callout_params_library.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) $(libpcl_la_CPPFLAGS) $(CPPFLAGS) $(libpcl_la_CXXFLAGS) $(CXXFLAGS) -c -o libpcl_la-callout_params_library.lo `test -f 'callout_params_library.cc' || echo '$(srcdir)/'`callout_params_library.cc
+
+libucl_la-unload_callout_library.lo: unload_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libucl_la_CPPFLAGS) $(CPPFLAGS) $(libucl_la_CXXFLAGS) $(CXXFLAGS) -MT libucl_la-unload_callout_library.lo -MD -MP -MF $(DEPDIR)/libucl_la-unload_callout_library.Tpo -c -o libucl_la-unload_callout_library.lo `test -f 'unload_callout_library.cc' || echo '$(srcdir)/'`unload_callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libucl_la-unload_callout_library.Tpo $(DEPDIR)/libucl_la-unload_callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unload_callout_library.cc' object='libucl_la-unload_callout_library.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) $(libucl_la_CPPFLAGS) $(CPPFLAGS) $(libucl_la_CXXFLAGS) $(CXXFLAGS) -c -o libucl_la-unload_callout_library.lo `test -f 'unload_callout_library.cc' || echo '$(srcdir)/'`unload_callout_library.cc
+
+run_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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`
+
+run_unittests-callout_handle_unittest.o: callout_handle_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo -c -o run_unittests-callout_handle_unittest.o `test -f 'callout_handle_unittest.cc' || echo '$(srcdir)/'`callout_handle_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_unittest.cc' object='run_unittests-callout_handle_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_unittest.o `test -f 'callout_handle_unittest.cc' || echo '$(srcdir)/'`callout_handle_unittest.cc
+
+run_unittests-callout_handle_unittest.obj: callout_handle_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo -c -o run_unittests-callout_handle_unittest.obj `if test -f 'callout_handle_unittest.cc'; then $(CYGPATH_W) 'callout_handle_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_unittest.cc' object='run_unittests-callout_handle_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_unittest.obj `if test -f 'callout_handle_unittest.cc'; then $(CYGPATH_W) 'callout_handle_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_unittest.cc'; fi`
+
+run_unittests-callout_handle_associate_unittest.o: callout_handle_associate_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_associate_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo -c -o run_unittests-callout_handle_associate_unittest.o `test -f 'callout_handle_associate_unittest.cc' || echo '$(srcdir)/'`callout_handle_associate_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_associate_unittest.cc' object='run_unittests-callout_handle_associate_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_associate_unittest.o `test -f 'callout_handle_associate_unittest.cc' || echo '$(srcdir)/'`callout_handle_associate_unittest.cc
+
+run_unittests-callout_handle_associate_unittest.obj: callout_handle_associate_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_handle_associate_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo -c -o run_unittests-callout_handle_associate_unittest.obj `if test -f 'callout_handle_associate_unittest.cc'; then $(CYGPATH_W) 'callout_handle_associate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_associate_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Tpo $(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_handle_associate_unittest.cc' object='run_unittests-callout_handle_associate_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_handle_associate_unittest.obj `if test -f 'callout_handle_associate_unittest.cc'; then $(CYGPATH_W) 'callout_handle_associate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_handle_associate_unittest.cc'; fi`
+
+run_unittests-callout_manager_unittest.o: callout_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_manager_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo -c -o run_unittests-callout_manager_unittest.o `test -f 'callout_manager_unittest.cc' || echo '$(srcdir)/'`callout_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo $(DEPDIR)/run_unittests-callout_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_manager_unittest.cc' object='run_unittests-callout_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_manager_unittest.o `test -f 'callout_manager_unittest.cc' || echo '$(srcdir)/'`callout_manager_unittest.cc
+
+run_unittests-callout_manager_unittest.obj: callout_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-callout_manager_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo -c -o run_unittests-callout_manager_unittest.obj `if test -f 'callout_manager_unittest.cc'; then $(CYGPATH_W) 'callout_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_manager_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-callout_manager_unittest.Tpo $(DEPDIR)/run_unittests-callout_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_manager_unittest.cc' object='run_unittests-callout_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-callout_manager_unittest.obj `if test -f 'callout_manager_unittest.cc'; then $(CYGPATH_W) 'callout_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/callout_manager_unittest.cc'; fi`
+
+run_unittests-handles_unittest.o: handles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-handles_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-handles_unittest.Tpo -c -o run_unittests-handles_unittest.o `test -f 'handles_unittest.cc' || echo '$(srcdir)/'`handles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-handles_unittest.Tpo $(DEPDIR)/run_unittests-handles_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='handles_unittest.cc' object='run_unittests-handles_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-handles_unittest.o `test -f 'handles_unittest.cc' || echo '$(srcdir)/'`handles_unittest.cc
+
+run_unittests-handles_unittest.obj: handles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-handles_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-handles_unittest.Tpo -c -o run_unittests-handles_unittest.obj `if test -f 'handles_unittest.cc'; then $(CYGPATH_W) 'handles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/handles_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-handles_unittest.Tpo $(DEPDIR)/run_unittests-handles_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='handles_unittest.cc' object='run_unittests-handles_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-handles_unittest.obj `if test -f 'handles_unittest.cc'; then $(CYGPATH_W) 'handles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/handles_unittest.cc'; fi`
+
+run_unittests-hooks_manager_unittest.o: hooks_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hooks_manager_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo -c -o run_unittests-hooks_manager_unittest.o `test -f 'hooks_manager_unittest.cc' || echo '$(srcdir)/'`hooks_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo $(DEPDIR)/run_unittests-hooks_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_manager_unittest.cc' object='run_unittests-hooks_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hooks_manager_unittest.o `test -f 'hooks_manager_unittest.cc' || echo '$(srcdir)/'`hooks_manager_unittest.cc
+
+run_unittests-hooks_manager_unittest.obj: hooks_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hooks_manager_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo -c -o run_unittests-hooks_manager_unittest.obj `if test -f 'hooks_manager_unittest.cc'; then $(CYGPATH_W) 'hooks_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hooks_manager_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hooks_manager_unittest.Tpo $(DEPDIR)/run_unittests-hooks_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hooks_manager_unittest.cc' object='run_unittests-hooks_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-hooks_manager_unittest.obj `if test -f 'hooks_manager_unittest.cc'; then $(CYGPATH_W) 'hooks_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hooks_manager_unittest.cc'; fi`
+
+run_unittests-library_manager_collection_unittest.o: library_manager_collection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_collection_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo -c -o run_unittests-library_manager_collection_unittest.o `test -f 'library_manager_collection_unittest.cc' || echo '$(srcdir)/'`library_manager_collection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_collection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_collection_unittest.cc' object='run_unittests-library_manager_collection_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_collection_unittest.o `test -f 'library_manager_collection_unittest.cc' || echo '$(srcdir)/'`library_manager_collection_unittest.cc
+
+run_unittests-library_manager_collection_unittest.obj: library_manager_collection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_collection_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo -c -o run_unittests-library_manager_collection_unittest.obj `if test -f 'library_manager_collection_unittest.cc'; then $(CYGPATH_W) 'library_manager_collection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_collection_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_collection_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_collection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_collection_unittest.cc' object='run_unittests-library_manager_collection_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_collection_unittest.obj `if test -f 'library_manager_collection_unittest.cc'; then $(CYGPATH_W) 'library_manager_collection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_collection_unittest.cc'; fi`
+
+run_unittests-library_manager_unittest.o: library_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_unittest.Tpo -c -o run_unittests-library_manager_unittest.o `test -f 'library_manager_unittest.cc' || echo '$(srcdir)/'`library_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_unittest.cc' object='run_unittests-library_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_unittest.o `test -f 'library_manager_unittest.cc' || echo '$(srcdir)/'`library_manager_unittest.cc
+
+run_unittests-library_manager_unittest.obj: library_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-library_manager_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-library_manager_unittest.Tpo -c -o run_unittests-library_manager_unittest.obj `if test -f 'library_manager_unittest.cc'; then $(CYGPATH_W) 'library_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-library_manager_unittest.Tpo $(DEPDIR)/run_unittests-library_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='library_manager_unittest.cc' object='run_unittests-library_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-library_manager_unittest.obj `if test -f 'library_manager_unittest.cc'; then $(CYGPATH_W) 'library_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/library_manager_unittest.cc'; fi`
+
+run_unittests-parking_lots_unittest.o: parking_lots_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-parking_lots_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo -c -o run_unittests-parking_lots_unittest.o `test -f 'parking_lots_unittest.cc' || echo '$(srcdir)/'`parking_lots_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo $(DEPDIR)/run_unittests-parking_lots_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parking_lots_unittest.cc' object='run_unittests-parking_lots_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-parking_lots_unittest.o `test -f 'parking_lots_unittest.cc' || echo '$(srcdir)/'`parking_lots_unittest.cc
+
+run_unittests-parking_lots_unittest.obj: parking_lots_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-parking_lots_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo -c -o run_unittests-parking_lots_unittest.obj `if test -f 'parking_lots_unittest.cc'; then $(CYGPATH_W) 'parking_lots_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parking_lots_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-parking_lots_unittest.Tpo $(DEPDIR)/run_unittests-parking_lots_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parking_lots_unittest.cc' object='run_unittests-parking_lots_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-parking_lots_unittest.obj `if test -f 'parking_lots_unittest.cc'; then $(CYGPATH_W) 'parking_lots_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parking_lots_unittest.cc'; fi`
+
+run_unittests-server_hooks_unittest.o: server_hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_hooks_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo -c -o run_unittests-server_hooks_unittest.o `test -f 'server_hooks_unittest.cc' || echo '$(srcdir)/'`server_hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo $(DEPDIR)/run_unittests-server_hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_hooks_unittest.cc' object='run_unittests-server_hooks_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-server_hooks_unittest.o `test -f 'server_hooks_unittest.cc' || echo '$(srcdir)/'`server_hooks_unittest.cc
+
+run_unittests-server_hooks_unittest.obj: server_hooks_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_hooks_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo -c -o run_unittests-server_hooks_unittest.obj `if test -f 'server_hooks_unittest.cc'; then $(CYGPATH_W) 'server_hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_hooks_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_hooks_unittest.Tpo $(DEPDIR)/run_unittests-server_hooks_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_hooks_unittest.cc' object='run_unittests-server_hooks_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-server_hooks_unittest.obj `if test -f 'server_hooks_unittest.cc'; then $(CYGPATH_W) 'server_hooks_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_hooks_unittest.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) $(LTLIBRARIES)
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libacl_la-async_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libbcl_la-basic_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libfcl_la-full_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libfxl_la-framework_exception_library.Plo
+ -rm -f ./$(DEPDIR)/libivl_la-incorrect_version_library.Plo
+ -rm -f ./$(DEPDIR)/liblcl_la-load_callout_library.Plo
+ -rm -f ./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libnvl_la-no_version_library.Plo
+ -rm -f ./$(DEPDIR)/libpcl_la-callout_params_library.Plo
+ -rm -f ./$(DEPDIR)/libucl_la-unload_callout_library.Plo
+ -rm -f ./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-callout_handle_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-callout_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-handles_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-library_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-parking_lots_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-server_hooks_unittest.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)/libacl_la-async_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libbcl_la-basic_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libfcl_la-full_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libfxl_la-framework_exception_library.Plo
+ -rm -f ./$(DEPDIR)/libivl_la-incorrect_version_library.Plo
+ -rm -f ./$(DEPDIR)/liblcl_la-load_callout_library.Plo
+ -rm -f ./$(DEPDIR)/liblecl_la-load_error_callout_library.Plo
+ -rm -f ./$(DEPDIR)/libnvl_la-no_version_library.Plo
+ -rm -f ./$(DEPDIR)/libpcl_la-callout_params_library.Plo
+ -rm -f ./$(DEPDIR)/libucl_la-unload_callout_library.Plo
+ -rm -f ./$(DEPDIR)/run_unittests-callout_handle_associate_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-callout_handle_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-callout_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-handles_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hooks_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-library_manager_collection_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-library_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-parking_lots_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-server_hooks_unittest.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-noinstLTLIBRARIES 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/hooks/tests/async_callout_library.cc b/src/lib/hooks/tests/async_callout_library.cc
new file mode 100644
index 0000000..5f40e52
--- /dev/null
+++ b/src/lib/hooks/tests/async_callout_library.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2018 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/.
+
+/// @file
+/// @brief Async callout library
+///
+/// This is source of a test library for testing a "parking" feature, i.e.
+/// the callouts can schedule asynchronous operation and indicate that the
+/// packet should be parked until the asynchronous operation completes and
+/// the hooks library indicates that packet processing should be resumed.
+
+#include <config.h>
+#include <hooks/hooks.h>
+#include <hooks/parking_lots.h>
+#include <log/logger.h>
+#include <log/macros.h>
+#include <log/message_initializer.h>
+#include <algorithm>
+#include <functional>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc::hooks;
+using namespace isc::log;
+using namespace std;
+
+namespace {
+
+/// @brief Logger used by the library.
+isc::log::Logger logger("acl");
+
+/// @brief Log messages.
+const char* log_messages[] = {
+ "ACL_LOAD_START", "async callout load %1",
+ "ACL_LOAD_END", "async callout load end",
+ "ACL_LOAD_END", "duplicate of async callout load end",
+ NULL
+};
+
+/// @brief Initializer for log messages.
+const MessageInitializer message_initializer(log_messages);
+
+/// @brief Simple callback which unparks parked object.
+///
+/// @param parking_lot parking lot where the object is parked.
+/// @param parked_object parked object.
+void unpark(ParkingLotHandlePtr parking_lot, const std::string& parked_object) {
+ parking_lot->unpark(parked_object);
+}
+
+} // end of anonymous namespace
+
+extern "C" {
+
+/// @brief Callout scheduling object parking and providing function to unpark
+/// it.
+///
+/// This callout is crafted to test the following scenario. The callout returns
+/// status "park" to indicate that the packet should be parked. The callout
+/// performs asynchronous operation and indicates that the packet should be
+/// unparked when this operation completes. Unparking the packet triggers a
+/// function associated with the parked packet, e.g. a function which continues
+/// processing of this packet.
+///
+/// This test callout "parks" a string object instead of a packet. It assumes
+/// that there might be multiple callouts installed on this hook point, which
+/// all trigger asynchronous operation. The object must be unparked when the
+/// last asynchronous operation completes. Therefore, it calls the @c reference
+/// function on the parking lot object to increase the reference count. The
+/// object remains parked as long as the reference counter is greater than
+/// 0.
+///
+/// The callout returns 1 or more pointers to the functions which should be
+/// called by the unit tests to simulate completion of the asynchronous tasks.
+/// When the test calls those functions, @c unpark function is called, which
+/// decreases reference count on the parked object, or actually unparks the
+/// object when the reference count reaches 0.
+///
+/// @param handle Reference to callout handle used to set/get arguments.
+int
+hookpt_one(CalloutHandle& handle) {
+ // Using a string as "parked" object.
+ std::string parked_object;
+ handle.getArgument("parked_object", parked_object);
+
+ // Retrieve the parking lot handle for this hook point. It allows for
+ // increasing a reference count on the parked object and also for
+ // scheduling packet unparking.
+ ParkingLotHandlePtr parking_lot = handle.getParkingLotHandlePtr();
+
+ // Increase the reference count to indicate that this callout needs the
+ // object to remain parked until the asynchronous operation completes.
+ // Otherwise, other callouts could potentially call unpark and cause the
+ // packet processing to continue before the asynchronous operation
+ // completes.
+ parking_lot->reference(parked_object);
+
+ // Create pointer to the function that the test should call to simulate
+ // completion of the asynchronous operation scheduled by this callout.
+ std::function<void()> unpark_trigger_func =
+ std::bind(unpark, parking_lot, parked_object);
+
+ // Every callout (if multiple callouts installed on this hook point) should
+ // return the function pointer under unique name. The base name is
+ // "unpark_trigger" and the callouts append consecutive numbers to this
+ // base name, e.g. "unpark_trigger1", "unpark_trigger2" etc.
+
+ std::string fun_name;
+ std::vector<std::string> args = handle.getArgumentNames();
+ unsigned i = 1;
+ do {
+ std::ostringstream candidate_name;
+ candidate_name << "unpark_trigger" << i;
+ if (std::find(args.begin(), args.end(), candidate_name.str()) ==
+ args.end()) {
+ fun_name = candidate_name.str();
+
+ } else {
+ ++i;
+ }
+ } while (fun_name.empty());
+
+ handle.setArgument(fun_name, unpark_trigger_func);
+
+ handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
+
+ return (0);
+}
+
+// Framework functions.
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+// load() initializes the user library if the main image was statically linked.
+int
+load(isc::hooks::LibraryHandle&) {
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ LOG_INFO(logger, "ACL_LOAD_START").arg("argument");
+ LOG_INFO(logger, "ACL_LOAD_END");
+ return (0);
+}
+
+}
+
diff --git a/src/lib/hooks/tests/basic_callout_library.cc b/src/lib/hooks/tests/basic_callout_library.cc
new file mode 100644
index 0000000..bd80555
--- /dev/null
+++ b/src/lib/hooks/tests/basic_callout_library.cc
@@ -0,0 +1,145 @@
+// Copyright (C) 2013-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/.
+
+/// @file
+/// @brief Basic callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - Only the "version" framework function is supplied.
+///
+/// - A context_create callout is supplied.
+///
+/// - Three "standard" callouts are supplied corresponding to the hooks
+/// "hookpt_one", "hookpt_two", "hookpt_three". All do some trivial
+/// calculations on the arguments supplied to it and the context variables,
+/// returning intermediate results through the "result" argument. The result
+/// of executing all four callouts in order is:
+///
+/// @f[ (10 + data_1) * data_2 - data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2
+/// to hookpt_two etc.) and the result is returned in the argument "result".
+///
+/// - The logger instance is created and some log messages are defined. Some
+/// log messages are duplicated purposely, to check that the logger handles
+/// the duplicates correctly.
+
+#include <config.h>
+#include <hooks/hooks.h>
+#include <fstream>
+#include <log/logger.h>
+#include <log/macros.h>
+#include <log/message_initializer.h>
+
+using namespace isc::hooks;
+using namespace isc::log;
+using namespace std;
+
+namespace {
+
+/// @brief Logger used by the library.
+isc::log::Logger logger("bcl");
+
+/// @brief Log messages.
+const char* log_messages[] = {
+ "BCL_LOAD_START", "basic callout load %1",
+ "BCL_LOAD_END", "basic callout load end",
+ "BCL_LOAD_END", "duplicate of basic callout load end",
+ NULL
+};
+
+/// @brief Initializer for log messages.
+const MessageInitializer message_initializer(log_messages);
+
+}
+
+extern "C" {
+
+// Callouts. All return their result through the "result" argument.
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(10));
+ handle.setArgument("result", static_cast<int>(10));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 10. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+int
+hookpt_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout subtracts the result in "data_3".
+
+int
+hookpt_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result -= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions.
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+// load() initializes the user library if the main image was statically linked.
+int
+load(isc::hooks::LibraryHandle&) {
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ LOG_INFO(logger, "BCL_LOAD_START").arg("argument");
+ LOG_INFO(logger, "BCL_LOAD_END");
+ return (0);
+}
+
+}
+
diff --git a/src/lib/hooks/tests/callout_handle_associate_unittest.cc b/src/lib/hooks/tests/callout_handle_associate_unittest.cc
new file mode 100644
index 0000000..cf09388
--- /dev/null
+++ b/src/lib/hooks/tests/callout_handle_associate_unittest.cc
@@ -0,0 +1,35 @@
+// Copyright (C) 2018 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 <hooks/callout_handle.h>
+#include <hooks/callout_handle_associate.h>
+#include <gtest/gtest.h>
+
+using namespace isc::hooks;
+
+namespace {
+
+// This test verifies that the callout handle can be created and
+// retrieved from the CalloutHandleAssociate.
+TEST(CalloutHandleAssociate, getCalloutHandle) {
+ CalloutHandleAssociate associate;
+ // The handle should be initialized and returned.
+ CalloutHandlePtr callout_handle = associate.getCalloutHandle();
+ ASSERT_TRUE(callout_handle);
+
+ // When calling the second time, the same handle should be returned.
+ CalloutHandlePtr callout_handle2 = associate.getCalloutHandle();
+ EXPECT_TRUE(callout_handle == callout_handle2);
+
+ // A different associate should produce a different handle.
+ CalloutHandleAssociate associate2;
+ callout_handle2 = associate2.getCalloutHandle();
+ EXPECT_FALSE(callout_handle == callout_handle2);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/hooks/tests/callout_handle_unittest.cc b/src/lib/hooks/tests/callout_handle_unittest.cc
new file mode 100644
index 0000000..bb20e0c
--- /dev/null
+++ b/src/lib/hooks/tests/callout_handle_unittest.cc
@@ -0,0 +1,384 @@
+// Copyright (C) 2013-2020 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @file
+/// @brief Holds the CalloutHandle argument tests
+///
+/// Additional testing of the CalloutHandle - together with the interaction
+/// of the LibraryHandle - is done in the handles_unittests set of tests.
+
+class CalloutHandleTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Sets up a callout manager to be referenced by the CalloutHandle in
+ /// these tests. (The "4" for the number of libraries in the
+ /// CalloutManager is arbitrary - it is not used in these tests.)
+ CalloutHandleTest() : manager_(new CalloutManager(4))
+ {}
+
+ /// Obtain hook manager
+ boost::shared_ptr<CalloutManager>& getCalloutManager() {
+ return (manager_);
+ }
+
+private:
+ /// Callout manager accessed by this CalloutHandle.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+// *** Argument Tests ***
+//
+// The first set of tests check that the CalloutHandle can store and retrieve
+// arguments. These are very similar to the LibraryHandle context tests.
+
+// Test that we can store multiple values of the same type and that they
+// are distinct.
+
+TEST_F(CalloutHandleTest, ArgumentDistinctSimpleType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Store and retrieve an int (random value).
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Add another integer (another random value).
+ int c = 142;
+ handle.setArgument("integer2", c);
+ EXPECT_EQ(142, c);
+
+ int d = 0;
+ handle.getArgument("integer2", d);
+ EXPECT_EQ(142, d);
+
+ // Add a short (random value).
+ short e = -81;
+ handle.setArgument("short", e);
+ EXPECT_EQ(-81, e);
+
+ short f = 0;
+ handle.getArgument("short", f);
+ EXPECT_EQ(-81, f);
+}
+
+// Test that trying to get an unknown argument throws an exception.
+
+TEST_F(CalloutHandleTest, ArgumentUnknownName) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Check that getting an unknown name throws an exception.
+ int c = 0;
+ EXPECT_THROW(handle.getArgument("unknown", c), NoSuchArgument);
+}
+
+// Test that trying to get an argument with an incorrect type throws an
+// exception.
+
+TEST_F(CalloutHandleTest, ArgumentIncorrectType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ long b = 0;
+ EXPECT_THROW(handle.getArgument("integer1", b), boost::bad_any_cast);
+}
+
+// Now try with some very complex types. The types cannot be defined within
+// the function and they should contain a copy constructor. For this reason,
+// a simple "struct" is used.
+
+struct Alpha {
+ int a;
+ int b;
+ Alpha(int first = 0, int second = 0) : a(first), b(second) {}
+};
+
+struct Beta {
+ int c;
+ int d;
+ Beta(int first = 0, int second = 0) : c(first), d(second) {}
+};
+
+TEST_F(CalloutHandleTest, ComplexTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare two variables of different (complex) types. (Note as to the
+ // variable names: aleph and beth are the first two letters of the Hebrew
+ // alphabet.)
+ Alpha aleph(1, 2);
+ EXPECT_EQ(1, aleph.a);
+ EXPECT_EQ(2, aleph.b);
+ handle.setArgument("aleph", aleph);
+
+ Beta beth(11, 22);
+ EXPECT_EQ(11, beth.c);
+ EXPECT_EQ(22, beth.d);
+ handle.setArgument("beth", beth);
+
+ // Ensure we can extract the data correctly.
+ Alpha aleph2;
+ EXPECT_EQ(0, aleph2.a);
+ EXPECT_EQ(0, aleph2.b);
+ handle.getArgument("aleph", aleph2);
+ EXPECT_EQ(1, aleph2.a);
+ EXPECT_EQ(2, aleph2.b);
+
+ Beta beth2;
+ EXPECT_EQ(0, beth2.c);
+ EXPECT_EQ(0, beth2.d);
+ handle.getArgument("beth", beth2);
+ EXPECT_EQ(11, beth2.c);
+ EXPECT_EQ(22, beth2.d);
+
+ // Ensure that complex types also thrown an exception if we attempt to
+ // get a context element of the wrong type.
+ EXPECT_THROW(handle.getArgument("aleph", beth), boost::bad_any_cast);
+}
+
+// Check that the context can store pointers. And also check that it respects
+// that a "pointer to X" is not the same as a "pointer to const X".
+
+TEST_F(CalloutHandleTest, PointerTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare a couple of variables, const and non-const.
+ Alpha aleph(5, 10);
+ const Beta beth(15, 20);
+
+ Alpha* pa = &aleph;
+ const Beta* pcb = &beth;
+
+ // Check pointers can be set and retrieved OK.
+ handle.setArgument("non_const_pointer", pa);
+ handle.setArgument("const_pointer", pcb);
+
+ Alpha* pa2 = 0;
+ handle.getArgument("non_const_pointer", pa2);
+ EXPECT_TRUE(pa == pa2);
+
+ const Beta* pcb2 = 0;
+ handle.getArgument("const_pointer", pcb2);
+ EXPECT_TRUE(pcb == pcb2);
+
+ // Check that the "const" is protected in the context.
+ const Alpha* pca3;
+ EXPECT_THROW(handle.getArgument("non_const_pointer", pca3),
+ boost::bad_any_cast);
+
+ Beta* pb3;
+ EXPECT_THROW(handle.getArgument("const_pointer", pb3),
+ boost::bad_any_cast);
+}
+
+// Check that we can get the names of the arguments.
+
+TEST_F(CalloutHandleTest, ContextItemNames) {
+ CalloutHandle handle(getCalloutManager());
+
+ vector<string> expected_names;
+
+ expected_names.push_back("faith");
+ handle.setArgument("faith", 42);
+ expected_names.push_back("hope");
+ handle.setArgument("hope", 43);
+ expected_names.push_back("charity");
+ handle.setArgument("charity", 44);
+
+ // Get the names and check against the expected names. We'll sort
+ // both arrays to simplify the checking.
+ vector<string> actual_names = handle.getArgumentNames();
+
+ sort(actual_names.begin(), actual_names.end());
+ sort(expected_names.begin(), expected_names.end());
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Test that we can delete an argument.
+
+TEST_F(CalloutHandleTest, DeleteArgument) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete "one".
+ handle.getArgument("one", value);
+ EXPECT_EQ(1, value);
+ handle.deleteArgument("one");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+
+ // Delete "three".
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.deleteArgument("three");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+}
+
+// Test that we can delete all arguments.
+
+TEST_F(CalloutHandleTest, DeleteAllArguments) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ // Set the arguments. The previous test verifies that this works.
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete all arguments...
+ handle.deleteAllArguments();
+
+ // ... and check that none are left.
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("two", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("four", value), NoSuchArgument);
+}
+
+// Test the "status" field.
+TEST_F(CalloutHandleTest, StatusField) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Should be continue on construction.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle.getStatus());
+
+ handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, handle.getStatus());
+
+ handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, handle.getStatus());
+
+ handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle.getStatus());
+}
+
+// Tests that ScopedCalloutHandleState object resets CalloutHandle state
+// during construction and destruction.
+TEST_F(CalloutHandleTest, scopedState) {
+ // Create pointer to the handle to be wrapped.
+ CalloutHandlePtr handle(new CalloutHandle(getCalloutManager()));
+
+ // Set two arguments and the non-default status.
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ handle->setArgument("one", one);
+ handle->setArgument("two", two);
+ handle->setContext("three", three);
+ handle->setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+
+ int value = 0;
+ EXPECT_NO_THROW(handle->getArgument("one", value));
+ EXPECT_NO_THROW(handle->getArgument("two", value));
+ EXPECT_NO_THROW(handle->getContext("three", value));
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, handle->getStatus());
+
+ {
+ // Wrap the callout handle with the scoped state object, which should
+ // reset the state of the handle.
+ ScopedCalloutHandleState scoped_state(handle);
+
+ // When state is reset, all arguments should be removed and the
+ // default status should be set.
+ EXPECT_THROW(handle->getArgument("one", value), NoSuchArgument);
+ EXPECT_THROW(handle->getArgument("two", value), NoSuchArgument);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle->getStatus());
+
+ // Context should be intact.
+ ASSERT_NO_THROW(handle->getContext("three", value));
+ EXPECT_EQ(three, value);
+
+ // Set the arguments and status again prior to the destruction of
+ // the wrapper.
+ handle->setArgument("one", one);
+ handle->setArgument("two", two);
+ handle->setStatus(CalloutHandle::NEXT_STEP_DROP);
+
+ EXPECT_NO_THROW(handle->getArgument("one", value));
+ EXPECT_NO_THROW(handle->getArgument("two", value));
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_DROP, handle->getStatus());
+ }
+
+ // Arguments should be gone again and the status should be set to
+ // a default value.
+ EXPECT_THROW(handle->getArgument("one", value), NoSuchArgument);
+ EXPECT_THROW(handle->getArgument("two", value), NoSuchArgument);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, handle->getStatus());
+
+ // Context should be intact.
+ ASSERT_NO_THROW(handle->getContext("three", value));
+ EXPECT_EQ(three, value);
+}
+
+// Further tests of the "skip" flag and tests of getting the name of the
+// hook to which the current callout is attached is in the "handles_unittest"
+// module.
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/callout_manager_unittest.cc b/src/lib/hooks/tests/callout_manager_unittest.cc
new file mode 100644
index 0000000..67ba83b
--- /dev/null
+++ b/src/lib/hooks/tests/callout_manager_unittest.cc
@@ -0,0 +1,944 @@
+// Copyright (C) 2013-2023 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <climits>
+#include <string>
+#include <vector>
+
+/// @file
+/// @brief CalloutManager and LibraryHandle tests
+///
+/// These set of tests check the CalloutManager and LibraryHandle. They are
+/// together in the same file because the LibraryHandle is little more than a
+/// restricted interface to the CalloutManager, and a lot of the support
+/// structure for the tests is common.
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+class CalloutManagerTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up a collection of three LibraryHandle objects to use in the test.
+ CalloutManagerTest() {
+
+ // Set up the server hooks. There is one singleton for all tests,
+ // so reset it and explicitly set up the hooks for the test.
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ alpha_index_ = hooks.registerHook("alpha");
+ beta_index_ = hooks.registerHook("beta");
+ gamma_index_ = hooks.registerHook("gamma");
+ delta_index_ = hooks.registerHook("delta");
+
+ // Set up the callout manager with these hooks. Assume a maximum of
+ // four libraries.
+ callout_manager_.reset(new CalloutManager(10));
+
+ // Set up the callout handle.
+ callout_handle_.reset(new CalloutHandle(callout_manager_));
+
+ // Initialize the static variable.
+ callout_value_ = 0;
+ }
+
+ /// @brief Return the callout handle
+ CalloutHandle& getCalloutHandle() {
+ return (*callout_handle_);
+ }
+
+ /// @brief Return the callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (callout_manager_);
+ }
+
+ /// Static variable used for accumulating information
+ static int callout_value_;
+
+ /// Hook indexes. These are somewhat ubiquitous, so are made public for
+ /// ease of reference instead of being accessible by a function.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+private:
+ /// Callout handle used in calls
+ boost::shared_ptr<CalloutHandle> callout_handle_;
+
+ /// Callout manager used for the test
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+// Definition of the static variable.
+int CalloutManagerTest::callout_value_ = 0;
+
+// Callout definitions
+//
+// The callouts defined here are structured in such a way that it is possible
+// to determine the order in which they are called and whether they are called
+// at all. The method used is simple - after a sequence of callouts, the digits
+// in the value, reading left to right, determines the order of the callouts
+// called. For example, callout one followed by two followed by three followed
+// by two followed by one results in a value of 12321.
+//
+// Functions return a zero to indicate success.
+
+extern "C" {
+int callout_general(int number) {
+ CalloutManagerTest::callout_value_ =
+ 10 * CalloutManagerTest::callout_value_ + number;
+ return (0);
+}
+
+int callout_one(CalloutHandle&) {
+ return (callout_general(1));
+}
+
+int callout_two(CalloutHandle&) {
+ return (callout_general(2));
+}
+
+int callout_three(CalloutHandle&) {
+ return (callout_general(3));
+}
+
+int callout_four(CalloutHandle&) {
+ return (callout_general(4));
+}
+
+int callout_five(CalloutHandle&) {
+ return (callout_general(5));
+}
+
+int callout_six(CalloutHandle&) {
+ return (callout_general(6));
+}
+
+int callout_seven(CalloutHandle&) {
+ return (callout_general(7));
+}
+
+// The next functions are duplicates of some of the above, but return an error.
+int callout_one_error(CalloutHandle& handle) {
+ (void) callout_one(handle);
+ return (1);
+}
+
+int callout_two_error(CalloutHandle& handle) {
+ (void) callout_two(handle);
+ return (1);
+}
+
+int callout_three_error(CalloutHandle& handle) {
+ (void) callout_three(handle);
+ return (1);
+}
+
+int callout_four_error(CalloutHandle& handle) {
+ (void) callout_four(handle);
+ return (1);
+}
+
+// The next functions are duplicates of some of the above, but throw exceptions.
+int callout_one_exception(CalloutHandle& handle) {
+ (void) callout_one(handle);
+ throw 0;
+ return (1);
+}
+
+int callout_two_exception(CalloutHandle& handle) {
+ (void) callout_two(handle);
+ throw 0;
+ return (1);
+}
+
+int callout_three_exception(CalloutHandle& handle) {
+ (void) callout_three(handle);
+ throw 0;
+ return (1);
+}
+
+int callout_four_exception(CalloutHandle& handle) {
+ (void) callout_four(handle);
+ throw 0;
+ return (1);
+}
+
+} // extern "C"
+
+// *** Callout Tests ***
+//
+// The next set of tests check that callouts can be called.
+
+// Constructor - check that we trap bad parameters.
+TEST_F(CalloutManagerTest, BadConstructorParameters) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Invalid number of libraries
+ EXPECT_THROW(cm.reset(new CalloutManager(-1)), BadValue);
+}
+
+// Check the number of libraries is reported successfully.
+TEST_F(CalloutManagerTest, NumberOfLibraries) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Check two valid values of number of libraries to ensure that the
+ // GetNumLibraries() returns the value set.
+ EXPECT_NO_THROW(cm.reset(new CalloutManager()));
+ EXPECT_EQ(0, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(0)));
+ EXPECT_EQ(0, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(4)));
+ EXPECT_EQ(4, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(42)));
+ EXPECT_EQ(42, cm->getNumLibraries());
+}
+
+// Check that we can only set the current library index to the correct values.
+TEST_F(CalloutManagerTest, CheckLibraryIndex) {
+ // Check valid indexes. As the callout manager is sized for 10 libraries,
+ // we expect:
+ //
+ // -1 to be valid as it is the standard "invalid" value.
+ // 0 to be valid for the pre-user library callouts
+ // 1-10 to be valid for the user-library callouts
+ // INT_MAX to be valid for the post-user library callouts
+ //
+ // All other values to be invalid.
+ for (int i = -1; i < 11; ++i) {
+ EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(i));
+ EXPECT_EQ(i, getCalloutManager()->getLibraryIndex());
+ }
+ EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(INT_MAX));
+ EXPECT_EQ(INT_MAX, getCalloutManager()->getLibraryIndex());
+
+ // Check invalid ones
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(-2), NoSuchLibrary);
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(11), NoSuchLibrary);
+}
+
+// Check that we can only register callouts on valid hook names.
+TEST_F(CalloutManagerTest, ValidHookNames) {
+ EXPECT_NO_THROW(getCalloutManager()->registerCallout("alpha", callout_one, 0));
+ EXPECT_THROW(getCalloutManager()->registerCallout("unknown", callout_one, 0),
+ NoSuchHook);
+}
+
+// Check we can register callouts appropriately.
+TEST_F(CalloutManagerTest, RegisterCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("beta", callout_two, 1);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Register some more callouts from different libraries on hook "alpha".
+ getCalloutManager()->registerCallout("alpha", callout_three, 2);
+ getCalloutManager()->registerCallout("alpha", callout_four, 2);
+ getCalloutManager()->registerCallout("alpha", callout_five, 3);
+
+ // Check it is as expected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1345, callout_value_);
+
+ // ... and check the additional callouts were not registered on the "beta"
+ // hook.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Add another callout to hook "alpha" from library index 2 - this should
+ // appear at the end of the callout list for that library.
+ getCalloutManager()->registerCallout("alpha", callout_six, 2);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13465, callout_value_);
+
+ // Add a callout from library index 1 - this should appear between the
+ // callouts from library index 0 and library index 2.
+ getCalloutManager()->registerCallout("alpha", callout_seven, 1);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(173465, callout_value_);
+}
+
+// Check the "calloutsPresent()" method.
+TEST_F(CalloutManagerTest, CalloutsPresent) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Set up so that hooks "alpha", "beta" and "delta" have callouts attached
+ // to them, and callout "gamma" does not. (In the statements below, the
+ // exact callouts attached to a hook are not relevant - only the fact
+ // that some callouts are). Chose the libraries for which the callouts
+ // are registered randomly.
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 1);
+ getCalloutManager()->registerCallout("beta", callout_two, 1);
+ getCalloutManager()->registerCallout("alpha", callout_three, 3);
+ getCalloutManager()->registerCallout("delta", callout_four, 3);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check we fail on an invalid hook index.
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(42), NoSuchHook);
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(-1), NoSuchHook);
+}
+
+// Test that calling a hook with no callouts on it returns success.
+TEST_F(CalloutManagerTest, CallNoCallouts) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Call the callouts on an arbitrary hook and ensure that nothing happens.
+ callout_value_ = 475;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(475, callout_value_); // Unchanged
+}
+
+// Test that the callouts are called in the correct order (i.e. the callouts
+// from the first library in the order they were registered, then the callouts
+// from the second library in the order they were registered etc.)
+TEST_F(CalloutManagerTest, CallCalloutsSuccess) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one, 1);
+ getCalloutManager()->registerCallout("alpha", callout_two, 1);
+ getCalloutManager()->registerCallout("alpha", callout_three, 2);
+ getCalloutManager()->registerCallout("alpha", callout_four, 3);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Do a random selection of callouts on hook "beta".
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("beta", callout_one, 0);
+ getCalloutManager()->registerCallout("beta", callout_three, 0);
+ getCalloutManager()->registerCallout("beta", callout_two, 1);
+ getCalloutManager()->registerCallout("beta", callout_four, 3);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(1324, callout_value_);
+
+ // Ensure that calling the callouts on a hook with no callouts works.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+}
+
+// Test that the callouts are called in order, but that callouts occurring
+// after a callout that returns an error are called.
+//
+// (Note: in this test, the callouts that return an error set the value of
+// callout_value_ before they return the error code.)
+TEST_F(CalloutManagerTest, CallCalloutsError) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributing one callout on hook "alpha". The first callout
+ // returns an error (after adding its value to the result).
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one_error, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 1);
+ getCalloutManager()->registerCallout("alpha", callout_three, 2);
+ getCalloutManager()->registerCallout("alpha", callout_four, 3);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Each library contributing multiple callouts on hook "beta". The last
+ // callout on the first library returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("beta", callout_one, 0);
+ getCalloutManager()->registerCallout("beta", callout_one_error, 0);
+ getCalloutManager()->registerCallout("beta", callout_two, 1);
+ getCalloutManager()->registerCallout("beta", callout_two, 1);
+ getCalloutManager()->registerCallout("beta", callout_three, 1);
+ getCalloutManager()->registerCallout("beta", callout_three, 1);
+ getCalloutManager()->registerCallout("beta", callout_four, 3);
+ getCalloutManager()->registerCallout("beta", callout_four, 3);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+
+ // A callout in a random position in the callout list returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("gamma", callout_one, 0);
+ getCalloutManager()->registerCallout("gamma", callout_one, 0);
+ getCalloutManager()->registerCallout("gamma", callout_two, 1);
+ getCalloutManager()->registerCallout("gamma", callout_two, 1);
+ getCalloutManager()->registerCallout("gamma", callout_four_error, 3);
+ getCalloutManager()->registerCallout("gamma", callout_four, 3);
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(112244, callout_value_);
+
+ // The last callout on a hook returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("delta", callout_one, 0);
+ getCalloutManager()->registerCallout("delta", callout_one, 0);
+ getCalloutManager()->registerCallout("delta", callout_two, 1);
+ getCalloutManager()->registerCallout("delta", callout_two, 1);
+ getCalloutManager()->registerCallout("delta", callout_three, 2);
+ getCalloutManager()->registerCallout("delta", callout_three, 2);
+ getCalloutManager()->registerCallout("delta", callout_four, 3);
+ getCalloutManager()->registerCallout("delta", callout_four_error, 3);
+ getCalloutManager()->callCallouts(delta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+}
+
+// Test that the callouts are called in order, but that callouts occurring
+// after a callout that throws exception are called.
+//
+// (Note: in this test, the callouts that return an error set the value of
+// callout_value_ before they throw exception.)
+TEST_F(CalloutManagerTest, CallCalloutsException) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributing one callout on hook "alpha". The first callout
+ // returns an error (after adding its value to the result).
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one_exception, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 1);
+ getCalloutManager()->registerCallout("alpha", callout_three, 2);
+ getCalloutManager()->registerCallout("alpha", callout_four, 3);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+ EXPECT_EQ(getCalloutHandle().getStatus(), CalloutHandle::NEXT_STEP_DROP);
+
+ // Each library contributing multiple callouts on hook "beta". The last
+ // callout on the first library returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("beta", callout_one, 0);
+ getCalloutManager()->registerCallout("beta", callout_one_exception, 0);
+ getCalloutManager()->registerCallout("beta", callout_two, 1);
+ getCalloutManager()->registerCallout("beta", callout_two, 1);
+ getCalloutManager()->registerCallout("beta", callout_three, 1);
+ getCalloutManager()->registerCallout("beta", callout_three, 1);
+ getCalloutManager()->registerCallout("beta", callout_four, 3);
+ getCalloutManager()->registerCallout("beta", callout_four, 3);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+ EXPECT_EQ(getCalloutHandle().getStatus(), CalloutHandle::NEXT_STEP_DROP);
+
+ // A callout in a random position in the callout list returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("gamma", callout_one, 0);
+ getCalloutManager()->registerCallout("gamma", callout_one, 0);
+ getCalloutManager()->registerCallout("gamma", callout_two, 1);
+ getCalloutManager()->registerCallout("gamma", callout_two, 1);
+ getCalloutManager()->registerCallout("gamma", callout_four_exception, 3);
+ getCalloutManager()->registerCallout("gamma", callout_four, 3);
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(112244, callout_value_);
+ EXPECT_EQ(getCalloutHandle().getStatus(), CalloutHandle::NEXT_STEP_DROP);
+
+ // The last callout on a hook returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("delta", callout_one, 0);
+ getCalloutManager()->registerCallout("delta", callout_one, 0);
+ getCalloutManager()->registerCallout("delta", callout_two, 1);
+ getCalloutManager()->registerCallout("delta", callout_two, 1);
+ getCalloutManager()->registerCallout("delta", callout_three, 2);
+ getCalloutManager()->registerCallout("delta", callout_three, 2);
+ getCalloutManager()->registerCallout("delta", callout_four, 3);
+ getCalloutManager()->registerCallout("delta", callout_four_exception, 3);
+ getCalloutManager()->callCallouts(delta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+ EXPECT_EQ(getCalloutHandle().getStatus(), CalloutHandle::NEXT_STEP_DROP);
+}
+
+// Now test that we can deregister a single callout on a hook.
+TEST_F(CalloutManagerTest, DeregisterSingleCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add a callout to hook "alpha" and check it is added correctly.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_two, 0);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Remove it and check that the no callouts are present. We have to reset
+ // the current library index here as it was invalidated by the call
+ // to callCallouts().
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Now test that we can deregister a single callout on a hook that has multiple
+// callouts from the same library.
+TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add multiple callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 0);
+ getCalloutManager()->registerCallout("alpha", callout_three, 0);
+ getCalloutManager()->registerCallout("alpha", callout_four, 0);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Remove the callout_two callout. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Try removing it again.
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+}
+
+// Check we can deregister multiple callouts from the same library.
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 0);
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 0);
+ getCalloutManager()->registerCallout("alpha", callout_three, 0);
+ getCalloutManager()->registerCallout("alpha", callout_four, 0);
+ getCalloutManager()->registerCallout("alpha", callout_three, 0);
+ getCalloutManager()->registerCallout("alpha", callout_four, 0);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12123434, callout_value_);
+
+ // Remove the callout_two callouts. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(113434, callout_value_);
+
+ // Try removing multiple callouts that includes one at the end of the
+ // list of callouts.
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_four, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1133, callout_value_);
+
+ // ... and from the start.
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_one, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(33, callout_value_);
+
+ // ... and the remaining callouts.
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_three, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Check we can deregister multiple callouts from multiple libraries.
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 0);
+ getCalloutManager()->registerCallout("alpha", callout_three, 1);
+ getCalloutManager()->registerCallout("alpha", callout_four, 1);
+ getCalloutManager()->registerCallout("alpha", callout_five, 2);
+ getCalloutManager()->registerCallout("alpha", callout_two, 2);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove the callout_two callout from library 0. It should not affect
+ // the second callout_two callout registered by library 2.
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13452, callout_value_);
+}
+
+// Check we can deregister all callouts from a single library.
+TEST_F(CalloutManagerTest, DeregisterAllCallouts) {
+ // Ensure that no callouts are attached to hook one.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 0);
+ getCalloutManager()->registerCallout("alpha", callout_three, 1);
+ getCalloutManager()->registerCallout("alpha", callout_four, 1);
+ getCalloutManager()->registerCallout("alpha", callout_five, 2);
+ getCalloutManager()->registerCallout("alpha", callout_six, 2);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123456, callout_value_);
+
+ // Remove all callouts from library index 1.
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha", 1));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1256, callout_value_);
+
+ // Remove all callouts from library index 2.
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha", 2));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12, callout_value_);
+}
+
+// Check that we can register/deregister callouts on different libraries
+// and different hooks, and that the callout instances are regarded as
+// unique and do not affect one another.
+TEST_F(CalloutManagerTest, MultipleCalloutsLibrariesHooks) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Register callouts on the alpha hook.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("alpha", callout_one, 0);
+ getCalloutManager()->registerCallout("alpha", callout_two, 0);
+ getCalloutManager()->registerCallout("alpha", callout_three, 1);
+ getCalloutManager()->registerCallout("alpha", callout_four, 1);
+ getCalloutManager()->registerCallout("alpha", callout_five, 2);
+ getCalloutManager()->registerCallout("alpha", callout_two, 2);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Register the same callouts on the beta hook, and check that those
+ // on the alpha hook are not affected.
+ callout_value_ = 0;
+ getCalloutManager()->registerCallout("beta", callout_five, 0);
+ getCalloutManager()->registerCallout("beta", callout_one, 0);
+ getCalloutManager()->registerCallout("beta", callout_four, 2);
+ getCalloutManager()->registerCallout("beta", callout_three, 2);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(5143, callout_value_);
+
+ // Check that the order of callouts on the alpha hook has not been affected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove callout four from beta and check that alpha is not affected.
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("beta", callout_four, 2));
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(513, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+}
+
+// Library handle tests. As by inspection the LibraryHandle can be seen to be
+// little more than shell around CalloutManager, only a basic set of tests
+// is done concerning registration and deregistration of functions.
+//
+// More extensive tests (i.e. checking that when a callout is called it can
+// only register and deregister callouts within its library) require that
+// the CalloutHandle object pass the appropriate LibraryHandle to the
+// callout. These tests are done in the handles_unittest tests.
+TEST_F(CalloutManagerTest, LibraryHandleRegistration) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_one);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_three);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha", callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Deregister a callout on library index 0 (after we check we can't
+ // deregister it through library index 1).
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two, 1));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two, 0));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Deregister all callouts on library index 1.
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha", 1));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+}
+
+// A repeat of the test above, but using the alternate constructor for the
+// LibraryHandle.
+TEST_F(CalloutManagerTest, LibraryHandleAlternateConstructor) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ LibraryHandle lh0(*getCalloutManager().get(), 0);
+ lh0.registerCallout("alpha", callout_one);
+ lh0.registerCallout("alpha", callout_two);
+
+ LibraryHandle lh1(*getCalloutManager().get(), 1);
+ lh1.registerCallout("alpha", callout_three);
+ lh1.registerCallout("alpha", callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Deregister a callout on library index 0 (after we check we can't
+ // deregister it through library index 1).
+ EXPECT_FALSE(lh1.deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ EXPECT_TRUE(lh0.deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Deregister all callouts on library index 1.
+ EXPECT_TRUE(lh1.deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+}
+
+// Check that the pre- and post- user callout library handles work
+// appropriately with no user libraries.
+TEST_F(CalloutManagerTest, LibraryHandlePrePostNoLibraries) {
+ // Create a local callout manager and callout handle to reflect no libraries
+ // being loaded.
+ boost::shared_ptr<CalloutManager> manager(new CalloutManager(0));
+ CalloutHandle handle(manager);
+
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(manager->calloutsPresent(alpha_index_));
+
+ // Setup the pre-and post callouts.
+ manager->getPostLibraryHandle().registerCallout("alpha", callout_four);
+ manager->getPreLibraryHandle().registerCallout("alpha", callout_one);
+ // Check all is as expected.
+ EXPECT_TRUE(manager->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(manager->calloutsPresent(beta_index_));
+ EXPECT_FALSE(manager->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(manager->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected.
+ callout_value_ = 0;
+ manager->callCallouts(alpha_index_, handle);
+ EXPECT_EQ(14, callout_value_);
+
+ // Deregister the pre- library callout.
+ EXPECT_TRUE(manager->getPreLibraryHandle().deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ manager->callCallouts(alpha_index_, handle);
+ EXPECT_EQ(4, callout_value_);
+}
+
+// Repeat the tests with one user library.
+TEST_F(CalloutManagerTest, LibraryHandlePrePostUserLibrary) {
+
+ // Setup the pre-, library and post callouts.
+ getCalloutManager()->getPostLibraryHandle().registerCallout("alpha",
+ callout_four);
+ getCalloutManager()->getPreLibraryHandle().registerCallout("alpha",
+ callout_one);
+
+ // ... and set up a callout in between, on library number 2.
+ LibraryHandle lh1(*getCalloutManager().get(), 2);
+ lh1.registerCallout("alpha", callout_five);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(154, callout_value_);
+}
+
+// Test that control command handlers can be installed as callouts.
+TEST_F(CalloutManagerTest, LibraryHandleRegisterCommandHandler) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Simulate creation of the two hook libraries. Fist library implements two
+ // handlers for the control command 'command-one'. Second library implements
+ // two control command handlers: one for the 'command-one', another one for
+ // 'command-two'. Each of the handlers for the 'command-one' must be called
+ // and they must be called in the appropriate order. Command handler for
+ // 'command-two' should also be called.
+
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one", callout_one);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one", callout_four);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-one", callout_two);
+ getCalloutManager()->getLibraryHandle().registerCommandCallout("command-two", callout_three);
+
+ // Command handlers are installed for commands: 'command-one' and 'command-two'.
+ EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-one"));
+ EXPECT_TRUE(getCalloutManager()->commandHandlersPresent("command-two"));
+ // There should be no handlers installed for 'command-three' and 'command-four'.
+ EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-three"));
+ EXPECT_FALSE(getCalloutManager()->commandHandlersPresent("command-four"));
+
+ // Call handlers for 'command-one'. There should be three handlers called in
+ // the following order: 1, 4, 2.
+ callout_value_ = 0;
+ ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-one", handle));
+ EXPECT_EQ(142, callout_value_);
+
+ // There should be one handler invoked for the 'command-two'. This handler has
+ // index of 3.
+ callout_value_ = 0;
+ ASSERT_NO_THROW(getCalloutManager()->callCommandHandlers("command-two", handle));
+ EXPECT_EQ(3, callout_value_);
+
+ // An attempt to call handlers for the commands for which no hook points
+ // were created should result in exception.
+ EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-three", handle),
+ NoSuchHook);
+ EXPECT_THROW(getCalloutManager()->callCommandHandlers("command-four", handle),
+ NoSuchHook);
+}
+
+// This test checks if the CalloutManager can adjust its own hook_vector_ size.
+TEST_F(CalloutManagerTest, VectorSize) {
+
+ size_t s = getCalloutManager()->getHookLibsVectorSize();
+
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+
+ EXPECT_NO_THROW(hooks.registerHook("a_new_one"));
+
+ // Now load a callout. Name of the hook point the new callout is installed
+ // on doesn't matter. CM should do sanity checks and adjust anyway.
+ getCalloutManager()->getPostLibraryHandle().registerCallout("alpha",
+ callout_four);
+
+ // The vector size should have been increased by one, because there's
+ // one new hook point now.
+ EXPECT_EQ(s + 1, getCalloutManager()->getHookLibsVectorSize());
+}
+
+// The setting of the hook index is checked in the handles_unittest
+// set of tests, as access restrictions mean it is not easily tested
+// on its own.
+
+} // namespace
diff --git a/src/lib/hooks/tests/callout_params_library.cc b/src/lib/hooks/tests/callout_params_library.cc
new file mode 100644
index 0000000..d35fc00
--- /dev/null
+++ b/src/lib/hooks/tests/callout_params_library.cc
@@ -0,0 +1,129 @@
+// Copyright (C) 2016-2020 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/.
+
+/// @file
+/// @brief Callout Library
+///
+/// This is the source of a test library for the DHCP parser tests that
+/// specify parameters. It will attempt to obtain its own parameters.
+
+#include <config.h>
+#include <hooks/hooks.h>
+#include <iostream>
+
+using namespace std;
+using namespace isc::hooks;
+using namespace isc::data;
+
+extern "C" {
+
+// Framework functions
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+/// @brief This method will be called when the hook library is loaded
+///
+/// While its primary usage is for unit-testing, it also doubles as an
+/// illustration referred to from Hooks Developer's Guide. As such, please
+/// keep it simple, tidy and try to avoid referencing unnecessary code.
+/// Parts of it can be used as copy-paste examples.
+///
+/// @param handle passed by the hooks framework
+/// @return 0 if load was successful, non-zero for errors
+int load(LibraryHandle& handle) {
+ ConstElementPtr elems = handle.getParameters();
+ ConstElementPtr string_elem = handle.getParameter("svalue");
+ ConstElementPtr int_elem = handle.getParameter("ivalue");
+ ConstElementPtr bool_elem = handle.getParameter("bvalue");
+ ConstElementPtr nonexistent = handle.getParameter("nonexistent");
+ vector<string> names = handle.getParameterNames();
+
+ // String handling example.
+ if (!string_elem) {
+ // Parameter was not specified at all.
+ return (1);
+ }
+
+ if (string_elem->getType() != Element::string) {
+ // Parameter is specified, but it's not a string.
+ return (2);
+ }
+
+ string str = string_elem->stringValue();
+ if (str != "string value") {
+ // Parameter is specified, is a string, but has unexpected value.
+ //
+ // This library is used for testing, so it expects exact value of the
+ // parameter. Normal library would likely use whatever value user
+ // specified.
+ return (3);
+ }
+
+ // Integer handling example
+ if (!int_elem) {
+ // Parameter was not specified at all.
+ return (4);
+ }
+
+ if (int_elem->getType() != Element::integer) {
+ // Parameter is specified, but it's not an integer.
+ return (5);
+ }
+
+ int int_value = int_elem->intValue();
+ if (int_value != 42) {
+ // Parameter specified, is an integer, but has a value different than
+ // expected.
+ return (6);
+ }
+
+ // Boolean handling example
+ if (!bool_elem) {
+ // Parameter was not specified at all.
+ return (7);
+ }
+
+ if (bool_elem->getType() != Element::boolean) {
+ // Parameter is specified, but it's not a boolean.
+ return (8);
+ }
+
+ bool flag = bool_elem->boolValue();
+ if (flag != true) {
+ // Parameter specified, is a boolean, but has a value different than
+ // expected.
+ return (9);
+ }
+
+ // Check names. As a side effect of what maps using strings are
+ // implemented names are sorted in alphabetical order.
+ if ((names.size() != 3) || (names[0] != "bvalue") ||
+ (names[1] != "ivalue") || (names[2] != "svalue")) {
+ // Expect 3 names: bvalue, ivalue, svalue.
+ return (10);
+ }
+
+ // Check elems map.
+ if (!elems) {
+ return (11);
+ }
+ string expected_str = "{ "
+ "\"bvalue\": true, "
+ "\"ivalue\": 42, "
+ "\"svalue\": \"string value\""
+ " }";
+ if (expected_str != elems->str()) {
+ return (12);
+ }
+
+ // All validation steps were successful. The library has all the parameters
+ // it needs, so we should report a success.
+ return (0);
+}
+
+}
diff --git a/src/lib/hooks/tests/common_test_class.h b/src/lib/hooks/tests/common_test_class.h
new file mode 100644
index 0000000..aa34bcf
--- /dev/null
+++ b/src/lib/hooks/tests/common_test_class.h
@@ -0,0 +1,174 @@
+// Copyright (C) 2013-2021 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 COMMON_HOOKS_TEST_CLASS_H
+#define COMMON_HOOKS_TEST_CLASS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/server_hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+/// @brief Common hooks test class
+///
+/// This class is a shared parent of the test fixture class in the tests of the
+/// higher-level hooks classes (LibraryManager, LibraryManagerCollection and
+/// HooksManager). It
+///
+/// - sets the ServerHooks object with three hooks and stores their
+/// indexes.
+/// - executes the callouts (which are assumed to perform a calculation)
+/// and checks the results.
+
+class HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ HooksCommonTestClass() {
+
+ // Set up the server hooks. ServerHooks is a singleton, so we reset it
+ // between each test.
+ isc::hooks::ServerHooks& hooks =
+ isc::hooks::ServerHooks::getServerHooks();
+ hooks.reset();
+ hookpt_one_index_ = hooks.registerHook("hookpt_one");
+ hookpt_two_index_ = hooks.registerHook("hookpt_two");
+ hookpt_three_index_ = hooks.registerHook("hookpt_three");
+ }
+
+ /// @brief Call callouts test
+ ///
+ /// All of the loaded libraries for which callouts are called register four
+ /// callouts: a context_create callout and three callouts that are attached
+ /// to hooks hookpt_one, hookpt_two and hookpt_three. These four callouts,
+ /// executed in sequence, perform a series of calculations. Data is passed
+ /// between callouts in the argument list, in a variable named "result".
+ ///
+ /// context_create initializes the calculation by setting a seed
+ /// value, called r0 here. This value is dependent on the library being
+ /// loaded. Prior to that, the argument "result" is initialized to -1,
+ /// the purpose being to avoid exceptions when running this test with no
+ /// libraries loaded.
+ ///
+ /// Callout hookpt_one is passed a value d1 and performs a simple arithmetic
+ /// operation on it and r0 yielding a result r1. Hence we can say that
+ /// @f[ r1 = hookpt_one(r0, d1) @f]
+ ///
+ /// Callout hookpt_two is passed a value d2 and performs another simple
+ /// arithmetic operation on it and d2, yielding r2, i.e.
+ /// @f[ r2 = hookpt_two(d1, d2) @f]
+ ///
+ /// hookpt_three does a similar operation giving
+ /// @f[ r3 = hookpt_three(r2, d3) @f].
+ ///
+ /// The details of the operations hookpt_one, hookpt_two and hookpt_three
+ /// depend on the library, so the results obtained not only depend on
+ /// the data, but also on the library loaded. This method is passed both
+ /// data and expected results. It executes the three callouts in sequence,
+ /// checking the intermediate and final results. Only if the expected
+ /// library has been loaded correctly and the callouts in it registered
+ /// correctly will be the results be as expected.
+ ///
+ /// It is assumed that callout_manager_ has been set up appropriately.
+ ///
+ /// @note The CalloutHandle used in the calls is declared locally here.
+ /// The advantage of this (apart from scope reduction) is that on
+ /// exit, it is destroyed. This removes any references to memory
+ /// allocated by loaded libraries while they are still loaded.
+ ///
+ /// @param manager CalloutManager to use for the test
+ /// @param r0...r3, d1..d3 Data (dN) and expected results (rN) - both
+ /// intermediate and final. The arguments are ordered so that they
+ /// appear in the argument list in the order they are used.
+ void executeCallCallouts(
+ const boost::shared_ptr<isc::hooks::CalloutManager>& manager,
+ int r0, int d1, int r1, int d2, int r2, int d3, int r3) {
+ static const char* COMMON_TEXT = " callout returned the wrong value";
+ static const char* RESULT = "result";
+
+ int result;
+
+ // Set up a callout handle for the calls.
+ isc::hooks::CalloutHandle handle(manager);
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ handle.setArgument(RESULT, -1);
+
+ // Seed the calculation.
+ manager->callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+ // Perform the first calculation.
+ handle.setArgument("data_1", d1);
+ manager->callCallouts(hookpt_one_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+ // ... the second ...
+ handle.setArgument("data_2", d2);
+ manager->callCallouts(hookpt_two_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+ // ... and the third.
+ handle.setArgument("data_3", d3);
+ manager->callCallouts(hookpt_three_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+ }
+
+ /// @brief Call command handlers test.
+ ///
+ /// This test is similar to @c executeCallCallouts but it uses
+ /// @ref CalloutManager::callCommandHandlers to execute the command
+ /// handlers for the following commands: 'command-one' and 'command-two'.
+ ///
+ /// @param manager CalloutManager to use for the test
+ /// @param r1..r2, d1..d2 Data (dN) and expected results (rN).
+ void executeCallCommandHandlers(
+ const boost::shared_ptr<isc::hooks::CalloutManager>& manager,
+ int d1, int r1, int d2, int r2) {
+ static const char* COMMON_TEXT = " command handler returned the wrong value";
+ static const char* RESULT = "result";
+
+ int result;
+
+ // Set up a callout handle for the calls.
+ isc::hooks::CalloutHandle handle(manager);
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ handle.setArgument(RESULT, -1);
+
+ // Perform the first calculation: it should assign the data to the
+ // result.
+ handle.setArgument("data_1", d1);
+ manager->callCommandHandlers("command-one", handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "command-one" << COMMON_TEXT;
+
+ // Perform the second calculation: it should multiply the data by 10
+ // and return in the result.
+ handle.setArgument("data_2", d2);
+ manager->callCommandHandlers("command-two", handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "command-two" << COMMON_TEXT;
+ }
+
+
+ /// Hook indexes. These are are made public for ease of reference.
+ int hookpt_one_index_;
+ int hookpt_two_index_;
+ int hookpt_three_index_;
+};
+
+#endif // COMMON_HOOKS_TEST_CLASS_H
diff --git a/src/lib/hooks/tests/framework_exception_library.cc b/src/lib/hooks/tests/framework_exception_library.cc
new file mode 100644
index 0000000..c60c3f8
--- /dev/null
+++ b/src/lib/hooks/tests/framework_exception_library.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2013-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/.
+
+/// @file
+/// @brief Framework exception library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - All three framework functions are supplied (version(), load() and
+/// unload()) and all generate an exception.
+
+#include <config.h>
+
+#include <hooks/hooks.h>
+
+#include <exception>
+
+extern "C" {
+
+int
+version() {
+ throw std::exception();
+}
+
+int
+load(isc::hooks::LibraryHandle& /*handle*/) {
+ throw std::exception();
+}
+
+int
+unload() {
+ throw std::exception();
+}
+
+};
+
diff --git a/src/lib/hooks/tests/full_callout_library.cc b/src/lib/hooks/tests/full_callout_library.cc
new file mode 100644
index 0000000..58ea8c7
--- /dev/null
+++ b/src/lib/hooks/tests/full_callout_library.cc
@@ -0,0 +1,177 @@
+// Copyright (C) 2013-2020 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/.
+
+/// @file
+/// @brief Full callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// The characteristics of this library are:
+///
+/// - All four framework functions are supplied (version(), load(),
+/// unload() and multi_threading_compatible()), with unload()
+/// creating a marker file. The test code checks for the presence
+/// of this file, so verifying that unload() has been run.
+///
+/// - One standard and two non-standard callouts are supplied, with the latter
+/// being registered by the load() function.
+///
+/// All callouts do trivial calculations, the result of all being called in
+/// sequence being
+///
+/// @f[ ((7 * data_1) - data_2) * data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2
+/// to hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <config.h>
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(7));
+ handle.setArgument("result", static_cast<int>(7));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 7. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout subtracts the passed value of data_2 from the current
+// running total.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result -= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout multiplies the current running total by data_3.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// First command handler assigns data to a result.
+
+static int
+command_handler_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result = data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second command handler multiples the result by data by 10.
+
+static int
+command_handler_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data * 10;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle& handle) {
+ // Initialize if the main image was statically linked
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ // Register the non-standard functions
+ handle.registerCallout("hookpt_two", hook_nonstandard_two);
+ handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+ // Register command_handler_one as control command handler.
+ handle.registerCommandCallout("command-one", command_handler_one);
+ handle.registerCommandCallout("command-two", command_handler_two);
+
+ return (0);
+}
+
+int
+unload() {
+ // Create the marker file.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::out);
+ marker.close();
+
+ return (0);
+}
+
+int
+multi_threading_compatible() {
+ return (1);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/handles_unittest.cc b/src/lib/hooks/tests/handles_unittest.cc
new file mode 100644
index 0000000..a9292d8
--- /dev/null
+++ b/src/lib/hooks/tests/handles_unittest.cc
@@ -0,0 +1,777 @@
+// Copyright (C) 2013-2020 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+/// @file
+/// CalloutHandle/LibraryHandle interaction tests
+///
+/// This file holds unit tests checking the interaction between the
+/// CalloutHandle/LibraryHandle and CalloutManager classes. In particular,
+/// they check that:
+///
+/// - A CalloutHandle's context is shared between callouts from the same
+/// library, but there is a separate context for each library.
+///
+/// - The various methods manipulating the items in the CalloutHandle's context
+/// work correctly.
+///
+/// - An active callout can only modify the registration of callouts registered
+/// by its own library.
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+class HandlesTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up the various elements used in each test.
+ HandlesTest() {
+ // Set up four hooks, although through gamma
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ alpha_index_ = hooks.registerHook("alpha");
+ beta_index_ = hooks.registerHook("beta");
+ gamma_index_ = hooks.registerHook("gamma");
+ delta_index_ = hooks.registerHook("delta");
+
+ // Set up for three libraries.
+ manager_.reset(new CalloutManager(3));
+
+ // Initialize remaining variables.
+ common_string_ = "";
+ }
+
+ /// @brief Return callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (manager_);
+ }
+
+ /// Hook indexes - these are frequently accessed, so are accessed directly.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+ /// String accessible by all callouts whatever the library
+ static std::string common_string_;
+
+private:
+ /// Callout manager. Declared static so that the callout functions can
+ /// access it.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+/// Define the common string
+std::string HandlesTest::common_string_;
+
+
+// The next set of functions define the callouts used by the tests. They
+// manipulate the data in such a way that callouts called - and the order in
+// which they were called - can be determined. The functions also check that
+// the "callout context" data areas are separate.
+//
+// Three libraries are assumed, and each supplies four callouts. All callouts
+// manipulate two context elements the CalloutHandle, the elements being called
+// "string" and "int" (which describe the type of data manipulated).
+//
+// For the string item, each callout shifts data to the left and inserts its own
+// data. The data is a string of the form "nmc", where "n" is the number of
+// the library, "m" is the callout number and "y" is the indication of what
+// callout handle was passed as an argument ("1" or "2": "0" is used when no
+// identification has been set in the callout handle).
+//
+// For simplicity, and to cut down the number of functions actually written,
+// the callout indicator ("1" or "2") ) used in the in the CalloutHandle
+// functions is passed via a CalloutArgument. The argument is named "string":
+// use of a name the same as that of one of the context elements serves as a
+// check that the argument name space and argument context space are separate.
+//
+// For integer data, the value starts at zero and an increment is added on each
+// call. This increment is equal to:
+//
+// 100 * library number + 10 * callout number + callout handle
+//
+// Although this gives less information than the string value, the reasons for
+// using it are:
+//
+// - It is a separate item in the context, so checks that the context can
+// handle multiple items.
+// - It provides an item that can be deleted by the context deletion
+// methods.
+
+
+// Values set in the CalloutHandle context. There are three libraries, so
+// there are three contexts for the callout, one for each library.
+
+std::string& resultCalloutString(int index) {
+ static std::string result_callout_string[3];
+ return (result_callout_string[index]);
+}
+
+int& resultCalloutInt(int index) {
+ static int result_callout_int[3];
+ return (result_callout_int[index]);
+}
+
+// A simple function to zero the results.
+
+static void zero_results() {
+ for (int i = 0; i < 3; ++i) {
+ resultCalloutString(i) = "";
+ resultCalloutInt(i) = 0;
+ }
+}
+
+
+// Library callouts.
+
+// Common code for setting the callout context values.
+
+int
+execute(CalloutHandle& callout_handle, int library_num, int callout_num) {
+
+ // Obtain the callout handle number
+ int handle_num = 0;
+ try {
+ callout_handle.getArgument("handle_num", handle_num);
+ } catch (const NoSuchArgument&) {
+ // handle_num argument not set: this is the case in the tests where
+ // the context_create hook check is tested.
+ handle_num = 0;
+ }
+
+ // Create the basic data to be appended to the context value.
+ int idata = 100 * library_num + 10 * callout_num + handle_num;
+ string sdata = boost::lexical_cast<string>(idata);
+
+ // Get the context data. As before, this will not exist for the first
+ // callout called. (In real life, the library should create it when the
+ // "context_create" hook gets called before any packet processing takes
+ // place.)
+ int int_value = 0;
+ try {
+ callout_handle.getContext("int", int_value);
+ } catch (const NoSuchCalloutContext&) {
+ int_value = 0;
+ }
+
+ string string_value = "";
+ try {
+ callout_handle.getContext("string", string_value);
+ } catch (const NoSuchCalloutContext&) {
+ string_value = "";
+ }
+
+ // Update the values and set them back in the callout context.
+ int_value += idata;
+ callout_handle.setContext("int", int_value);
+
+ string_value += sdata;
+ callout_handle.setContext("string", string_value);
+
+ return (0);
+}
+
+// The following functions are the actual callouts - the name is of the
+// form "callout_<library number>_<callout number>"
+
+int
+callout11(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 1));
+}
+
+int
+callout12(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 2));
+}
+
+int
+callout13(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 3));
+}
+
+int
+callout21(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 1));
+}
+
+int
+callout22(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 2));
+}
+
+int
+callout23(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 3));
+}
+
+int
+callout31(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 1));
+}
+
+int
+callout32(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 2));
+}
+
+int
+callout33(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 3));
+}
+
+// Common callout code for the fourth hook (which makes the data available for
+// checking). It copies the library and callout context data to the global
+// variables.
+
+int printExecute(CalloutHandle& callout_handle, int library_num) {
+ callout_handle.getContext("string", resultCalloutString(library_num - 1));
+ callout_handle.getContext("int", resultCalloutInt(library_num - 1));
+
+ return (0);
+}
+
+// These are the actual callouts.
+
+int
+print1(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 1));
+}
+
+int
+print2(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 2));
+}
+
+int
+print3(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 3));
+}
+
+// This test checks the many-faced nature of the context for the CalloutContext.
+
+TEST_F(HandlesTest, ContextAccessCheck) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ getCalloutManager()->registerCallout("alpha", callout11, 0);
+ getCalloutManager()->registerCallout("beta", callout12, 0);
+ getCalloutManager()->registerCallout("gamma", callout13, 0);
+ getCalloutManager()->registerCallout("delta", print1, 0);
+
+ getCalloutManager()->registerCallout("alpha", callout21, 1);
+ getCalloutManager()->registerCallout("beta", callout22, 1);
+ getCalloutManager()->registerCallout("gamma", callout23, 1);
+ getCalloutManager()->registerCallout("delta", print2, 1);
+
+ getCalloutManager()->registerCallout("alpha", callout31, 2);
+ getCalloutManager()->registerCallout("beta", callout32, 2);
+ getCalloutManager()->registerCallout("gamma", callout33, 2);
+ getCalloutManager()->registerCallout("delta", print3, 2);
+
+ // Create the callout handles and distinguish them by setting the
+ // "handle_num" argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout (the callout on hook "delta" copies
+ // the context values into a location the test can access). Explicitly
+ // zero the variables before getting the results so we are certain that
+ // the values are the results of the callouts.
+
+ zero_results();
+
+ // To explain the expected callout context results.
+ //
+ // Each callout handle maintains a separate context for each library. When
+ // the first call to callCallouts() is made, "111" gets appended to
+ // the context for library 1 maintained by the first callout handle, "211"
+ // gets appended to the context maintained for library 2, and "311" to
+ // the context maintained for library 3. In each case, the first digit
+ // corresponds to the library number, the second to the callout number and
+ // the third to the "handle_num" of the callout handle. For the first call
+ // to callCallouts, handle 1 is used, so the last digit is always 1.
+ //
+ // The next call to callCallouts() calls the same callouts but for the
+ // second callout handle. It also maintains three contexts (one for
+ // each library) and they will get "112", "212", "312" appended to
+ // them. The explanation for the digits is the same as before, except that
+ // in this case, the callout handle is number 2, so the third digit is
+ // always 2. These additions don't affect the contexts maintained by
+ // callout handle 1.
+ //
+ // The process is then repeated for hooks "beta" and "gamma" which, for
+ // callout handle 1, append "121", "221" and "321" for hook "beta" and
+ // "311", "321" and "331" for hook "gamma".
+ //
+ // The expected integer values can be found by summing up the values
+ // corresponding to the elements of the strings.
+
+ // At this point, we have only called the "print" function for callout
+ // handle "1", so the following results are checking the context values
+ // maintained in that callout handle.
+
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ("311321331", resultCalloutString(2));
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ((211 + 221 + 231), resultCalloutInt(1));
+ EXPECT_EQ((311 + 321 + 331), resultCalloutInt(2));
+
+ // Repeat the checks for callout 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ((212 + 222 + 232), resultCalloutInt(1));
+ EXPECT_EQ((312 + 322 + 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ("312322332", resultCalloutString(2));
+}
+
+// Now repeat the test, but add a deletion callout to the list. The "beta"
+// hook of library 2 will have an additional callout to delete the "int"
+// element: the same hook for library 3 will delete both elements. In
+// addition, the names of context elements for the libraries at this point
+// will be printed.
+
+// List of context item names.
+
+vector<string>&
+getItemNames(int index) {
+ static vector<string> context_items[3];
+ return (context_items[index]);
+}
+
+// Context item deletion functions.
+
+int
+deleteIntContextItem(CalloutHandle& handle) {
+ handle.deleteContext("int");
+ return (0);
+}
+
+int
+deleteAllContextItems(CalloutHandle& handle) {
+ handle.deleteAllContext();
+ return (0);
+}
+
+// Generic print function - copy names in sorted order.
+
+int
+printContextNamesExecute(CalloutHandle& handle, int library_num) {
+ const int index = library_num - 1;
+ getItemNames(index) = handle.getContextNames();
+ sort(getItemNames(index).begin(), getItemNames(index).end());
+ return (0);
+}
+
+int
+printContextNames1(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 1));
+}
+
+int
+printContextNames2(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 2));
+}
+
+int
+printContextNames3(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 3));
+}
+
+// Perform the test including deletion of context items.
+
+TEST_F(HandlesTest, ContextDeletionCheck) {
+ getCalloutManager()->registerCallout("alpha", callout11, 0);
+ getCalloutManager()->registerCallout("beta", callout12, 0);
+ getCalloutManager()->registerCallout("beta", printContextNames1, 0);
+ getCalloutManager()->registerCallout("gamma", callout13, 0);
+ getCalloutManager()->registerCallout("delta", print1, 0);
+
+ getCalloutManager()->registerCallout("alpha", callout21, 1);
+ getCalloutManager()->registerCallout("beta", callout22, 1);
+ getCalloutManager()->registerCallout("beta", deleteIntContextItem, 1);
+ getCalloutManager()->registerCallout("beta", printContextNames2, 1);
+ getCalloutManager()->registerCallout("gamma", callout23, 1);
+ getCalloutManager()->registerCallout("delta", print2, 1);
+
+ getCalloutManager()->registerCallout("alpha", callout31, 2);
+ getCalloutManager()->registerCallout("beta", callout32, 2);
+ getCalloutManager()->registerCallout("beta", deleteAllContextItems, 2);
+ getCalloutManager()->registerCallout("beta", printContextNames3, 2);
+ getCalloutManager()->registerCallout("gamma", callout33, 2);
+ getCalloutManager()->registerCallout("delta", print3, 2);
+
+ // Create the callout handles and distinguish them by setting the "long"
+ // argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout. Explicitly zero the variables before
+ // getting the results so we are certain that the values are the results
+ // of the callouts.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+
+ // The logic by which the expected results are arrived at is described
+ // in the ContextAccessCheck test. The results here are different
+ // because context items have been modified along the way.
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ(( 231), resultCalloutInt(1));
+ EXPECT_EQ(( 331), resultCalloutInt(2));
+
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ( "331", resultCalloutString(2));
+
+ // Repeat the checks for callout handle 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ(( 232), resultCalloutInt(1));
+ EXPECT_EQ(( 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ( "332", resultCalloutString(2));
+
+ // ... and check what the names of the context items are after the callouts
+ // for hook "beta". We know they are in sorted order.
+
+ EXPECT_EQ(2, getItemNames(0).size());
+ EXPECT_EQ(string("int"), getItemNames(0)[0]);
+ EXPECT_EQ(string("string"), getItemNames(0)[1]);
+
+ EXPECT_EQ(1, getItemNames(1).size());
+ EXPECT_EQ(string("string"), getItemNames(1)[0]);
+
+ EXPECT_EQ(0, getItemNames(2).size());
+}
+
+// Tests that the CalloutHandle's constructor and destructor call the
+// context_create and context_destroy callbacks (if registered). For
+// simplicity, we'll use the same callout functions as used above.
+
+TEST_F(HandlesTest, ConstructionDestructionCallouts) {
+
+ // Register context callouts.
+ getCalloutManager()->registerCallout("context_create", callout11, 0);
+ getCalloutManager()->registerCallout("context_create", print1, 0);
+ getCalloutManager()->registerCallout("context_destroy", callout12, 0);
+ getCalloutManager()->registerCallout("context_destroy", print1, 0);
+
+ // Create the CalloutHandle and check that the constructor callout
+ // has run.
+ zero_results();
+ boost::scoped_ptr<CalloutHandle>
+ callout_handle(new CalloutHandle(getCalloutManager()));
+ EXPECT_EQ("110", resultCalloutString(0));
+ EXPECT_EQ(110, resultCalloutInt(0));
+
+ // Check that the destructor callout runs. Note that the "print1" callout
+ // didn't destroy the library context - it only copied it to where it
+ // could be examined. As a result, the destructor callout appends its
+ // elements to the constructor's values and the result is printed.
+ zero_results();
+ callout_handle.reset();
+
+ EXPECT_EQ("110120", resultCalloutString(0));
+ EXPECT_EQ((110 + 120), resultCalloutInt(0));
+}
+
+// Testing the operation of the "skip" flag. Callouts print the value
+// they see in the flag and either leave it unchanged, set it or clear it.
+int
+calloutPrintSkip(CalloutHandle& handle) {
+ static const std::string YES("Y");
+ static const std::string NO("N");
+ static const std::string DROP("D");
+ static const std::string PARK("P");
+
+ switch (handle.getStatus()) {
+ case CalloutHandle::NEXT_STEP_CONTINUE:
+ HandlesTest::common_string_ += NO; // skip = no
+ break;
+ case CalloutHandle::NEXT_STEP_SKIP:
+ HandlesTest::common_string_ += YES; // skip = yes
+ break;
+ case CalloutHandle::NEXT_STEP_DROP:
+ HandlesTest::common_string_ += DROP; // drop
+ break;
+ case CalloutHandle::NEXT_STEP_PARK:
+ HandlesTest::common_string_ += PARK; // park
+ break;
+ }
+ return (0);
+}
+
+int
+calloutSetSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+ return (0);
+}
+
+int
+calloutClearSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+ return (0);
+}
+
+// Do a series of tests, returning with the skip flag set "true".
+
+TEST_F(HandlesTest, ReturnSkipSet) {
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 0);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 0);
+
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1);
+
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 2);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For ease of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NNYY" "NNYYN" "NNYN"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_SKIP, callout_handle.getStatus());
+}
+
+// Repeat the test, returning with the skip flag clear.
+TEST_F(HandlesTest, ReturnSkipClear) {
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 0);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 0);
+
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 1);
+
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip, 2);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For ease of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NYY" "NNYNYN" "NNNY"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle.getStatus());
+}
+
+// Check that the skip flag is cleared when callouts are called - even if
+// there are no callouts.
+
+TEST_F(HandlesTest, NoCalloutsSkipTest) {
+ // Note - no callouts are registered on any hook.
+ CalloutHandle callout_handle(getCalloutManager());
+
+ // Clear the skip flag and call a hook with no callouts.
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle.getStatus());
+
+ // Set the skip flag and call a hook with no callouts.
+ callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+ EXPECT_EQ(CalloutHandle::NEXT_STEP_CONTINUE, callout_handle.getStatus());
+}
+
+// The next set of callouts do a similar thing to the above "skip" tests,
+// but alter the value of a string argument. This is for testing that the
+// a callout is able to change an argument and return it to the caller.
+
+const char* MODIFIED_ARG = "modified_arg";
+
+int
+calloutSetArgumentCommon(CalloutHandle& handle, const char* what) {
+ std::string modified_arg = "";
+
+ handle.getArgument(MODIFIED_ARG, modified_arg);
+ modified_arg = modified_arg + std::string(what);
+ handle.setArgument(MODIFIED_ARG, modified_arg);
+ return (0);
+}
+
+int
+calloutSetArgumentSkip(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "S"));
+}
+
+int
+calloutSetArgumentContinue(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "C"));
+}
+
+int
+calloutSetArgumentDrop(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "D"));
+}
+
+int
+calloutSetArgumentPark(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "P"));
+}
+
+// ... and a callout to just copy the argument to the "common_string_" variable
+// but otherwise not alter it.
+
+int
+calloutPrintArgument(CalloutHandle& handle) {
+ handle.getArgument(MODIFIED_ARG, HandlesTest::common_string_);
+ return (0);
+}
+
+// This test verifies that the next step status is processed appropriately.
+// The test checks the following next step statuses: CONTINUE, SKIP, DROP.
+TEST_F(HandlesTest, CheckModifiedArgument) {
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 0);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 0);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 0);
+
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 1);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentDrop, 1);
+ getCalloutManager()->registerCallout("alpha", calloutPrintArgument, 1);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentDrop, 1);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 1);
+
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentContinue, 2);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark, 2);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentSkip, 2);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentPark, 2);
+
+ // Create the argument with an initial empty string value. Then call the
+ // sequence of callouts above.
+ CalloutHandle callout_handle(getCalloutManager());
+ std::string modified_arg = "";
+ callout_handle.setArgument(MODIFIED_ARG, modified_arg);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check the intermediate and results. For visual checking, the expected
+ // string is divided into sections corresponding to the blocks of callouts
+ // above.
+ EXPECT_EQ(std::string("SCC" "SD"), common_string_);
+
+ callout_handle.getArgument(MODIFIED_ARG, modified_arg);
+ EXPECT_EQ(std::string("SCC" "SDDC" "SCPSP"), modified_arg);
+}
+
+// Test that the CalloutHandle provides the name of the hook to which the
+// callout is attached.
+
+int
+callout_hook_name(CalloutHandle& callout_handle) {
+ HandlesTest::common_string_ = callout_handle.getHookName();
+ return (0);
+}
+
+int
+callout_hook_dummy(CalloutHandle&) {
+ return (0);
+}
+
+TEST_F(HandlesTest, HookName) {
+ getCalloutManager()->registerCallout("alpha", callout_hook_name, 0);
+ getCalloutManager()->registerCallout("beta", callout_hook_name, 0);
+
+ // Call alpha and beta callouts and check the hook to which they belong.
+ CalloutHandle callout_handle(getCalloutManager());
+
+ EXPECT_EQ(std::string(""), HandlesTest::common_string_);
+
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+ EXPECT_EQ(std::string("alpha"), HandlesTest::common_string_);
+
+ getCalloutManager()->callCallouts(beta_index_, callout_handle);
+ EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+
+ // Make sure that the callout accesses the name even if it is not the
+ // only callout in the list.
+ getCalloutManager()->registerCallout("gamma", callout_hook_dummy, 1);
+ getCalloutManager()->registerCallout("gamma", callout_hook_name, 1);
+ getCalloutManager()->registerCallout("gamma", callout_hook_dummy, 1);
+
+ EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle);
+ EXPECT_EQ(std::string("gamma"), HandlesTest::common_string_);
+}
+
+} // Anonymous namespace
+
diff --git a/src/lib/hooks/tests/hooks_manager_unittest.cc b/src/lib/hooks/tests/hooks_manager_unittest.cc
new file mode 100644
index 0000000..a36d42c
--- /dev/null
+++ b/src/lib/hooks/tests/hooks_manager_unittest.cc
@@ -0,0 +1,1081 @@
+// Copyright (C) 2013-2021 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 <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#define TEST_ASYNC_CALLOUT
+#include <hooks/tests/test_libraries.h>
+#include <cc/data.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <fstream>
+#include <string>
+
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+/// @brief Hooks manager collection test class
+
+class HooksManagerTest : public ::testing::Test,
+ public HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ ///
+ /// Reset the hooks manager. The hooks manager is a singleton, so needs
+ /// to be reset for each test.
+ HooksManagerTest() {
+ HooksManager::setTestMode(false);
+ HooksManager::prepareUnloadLibraries();
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture ctor) unloadLibraries failed" << endl;
+ }
+ // Ensure the marker file is not present at the start of a test.
+ static_cast<void>(remove(MARKER_FILE));
+ }
+
+ /// @brief Destructor
+ ///
+ /// Unload all libraries.
+ ~HooksManagerTest() {
+ static_cast<void>(remove(MARKER_FILE));
+ HooksManager::setTestMode(false);
+ HooksManager::prepareUnloadLibraries();
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ cerr << "(fixture dtor) unloadLibraries failed" << endl;
+ }
+ }
+
+ /// @brief Marker file present
+ ///
+ /// Convenience function to check whether a marker file is present. It
+ /// does this by opening the file.
+ ///
+ /// @return true if the marker file is present.
+ bool markerFilePresent() const {
+
+ // Try to open it.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::in);
+
+ // Check if it is open and close it if so.
+ bool exists = marker.is_open();
+ if (exists) {
+ marker.close();
+ }
+
+ return (exists);
+ }
+
+ /// @brief Call callouts test
+ ///
+ /// See the header for HooksCommonTestClass::execute for details.
+ ///
+ /// @param r0...r3, d1..d3 Values and intermediate values expected. They
+ /// are ordered so that the variables appear in the argument list in
+ /// the order they are used.
+ void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+ int r3) {
+ static const char* COMMON_TEXT = " callout returned the wrong value";
+ static const char* RESULT = "result";
+
+ // Get a CalloutHandle for the calculation.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ int result = -1;
+ handle->setArgument(RESULT, result);
+
+ // Seed the calculation.
+ HooksManager::callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE,
+ *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+ // Perform the first calculation.
+ handle->setArgument("data_1", d1);
+ HooksManager::callCallouts(hookpt_one_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+ // ... the second ...
+ handle->setArgument("data_2", d2);
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+ // ... and the third.
+ handle->setArgument("data_3", d3);
+ HooksManager::callCallouts(hookpt_three_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+ }
+
+ /// @brief Call command handlers test.
+ ///
+ /// This test is similar to @c executeCallCallouts but it uses
+ /// @ref HooksManager::callCommandHandlers to execute the command
+ /// handlers for the following commands: 'command-one' and 'command-two'.
+ ///
+ /// @param r1..r2, d1..d2 Data (dN) and expected results (rN).
+ void executeCallCommandHandlers(int d1, int r1, int d2, int r2) {
+ static const char* COMMON_TEXT = " command handler returned the wrong value";
+ static const char* RESULT = "result";
+
+ int result;
+
+ // Set up a callout handle for the calls.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ handle->setArgument(RESULT, -1);
+
+ // Perform the first calculation: it should assign the data to the
+ // result.
+ handle->setArgument("data_1", d1);
+ HooksManager::callCommandHandlers("command-one", *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "command-one" << COMMON_TEXT;
+
+ // Perform the second calculation: it should multiply the data by 10
+ // and return in the result.
+ handle->setArgument("data_2", d2);
+ HooksManager::callCommandHandlers("command-two", *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "command-two" << COMMON_TEXT;
+ }
+
+private:
+ /// To avoid unused variable errors
+ std::string dummy(int i) {
+ if (i == 0) {
+ return (LOAD_CALLOUT_LIBRARY);
+ } else {
+ return (LOAD_ERROR_CALLOUT_LIBRARY);
+ }
+ }
+};
+
+// This is effectively the same test as for LibraryManager, but using the
+// HooksManager object.
+
+TEST_F(HooksManagerTest, LoadLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // r2 = 5 * 7 * 10
+ {
+ SCOPED_TRACE("Calculation using command handlers");
+ executeCallCommandHandlers(5, 5, 7, 350);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Calculation with libraries not loaded");
+ executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that the failing library will not be
+// loaded, but others will be.
+
+TEST_F(HooksManagerTest, LoadLibrariesWithError) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(INCORRECT_VERSION_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the libraries. We expect a failure return because one of the
+ // libraries fails to load.
+ EXPECT_FALSE(HooksManager::loadLibraries(library_names));
+}
+
+// Test that we can unload a set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleUnloadLibrary) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. This library implements:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ {
+ SCOPED_TRACE("Calculation with full callout library loaded");
+ executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+ }
+
+ // Get an outstanding callout handle on this library.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Execute once of the callouts again to ensure that the handle contains
+ // memory allocated by the library.
+ HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+ // Unload the libraries.
+ HooksManager::prepareUnloadLibraries();
+ EXPECT_FALSE(HooksManager::unloadLibraries());
+
+ // Deleting the callout handle should not cause a segmentation fault.
+ handle.reset();
+
+ // And allows unload.
+ EXPECT_TRUE(HooksManager::unloadLibraries());
+}
+
+// Test that we can load a new set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleLoadLibrary) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. This library implements:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ {
+ SCOPED_TRACE("Calculation with full callout library loaded");
+ executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+ }
+
+ // Get an outstanding callout handle on this library and execute one of
+ // the callouts again to ensure that the handle contains memory allocated
+ // by the library.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+ // Load a new library that implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ HookLibsCollection new_library_names;
+ new_library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the libraries.
+ EXPECT_THROW(HooksManager::loadLibraries(new_library_names),
+ LibrariesStillOpened);
+
+ // Deleting the old callout handle should not cause a segmentation fault.
+ handle.reset();
+
+ // But it allows the load of the new library.
+ EXPECT_TRUE(HooksManager::loadLibraries(new_library_names));
+
+ // Execute the calculation.
+ {
+ SCOPED_TRACE("Calculation with basic callout library loaded");
+ executeCallCallouts(10, 7, 17, 3, 51, 16, 35);
+ }
+}
+
+// This is effectively the same test as the LoadLibraries test.
+
+TEST_F(HooksManagerTest, ReloadSameLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. See the LoadLibraries test for an explanation of
+ // the calculation.
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try reloading the libraries and re-execute the calculation - we should
+ // get the same results.
+ EXPECT_NO_THROW(HooksManager::loadLibraries(library_names));
+ {
+ SCOPED_TRACE("Calculation with libraries reloaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+}
+
+TEST_F(HooksManagerTest, ReloadLibrariesReverseOrder) {
+
+ // Set up the list of libraries to be loaded and load them.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the given order
+ // gives.
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Reload the libraries in the reverse order.
+ std::reverse(library_names.begin(), library_names.end());
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // The calculation in the reverse order gives:
+ //
+ // r3 = ((((7 + d1) * d1) * d2 - d2) - d3) * d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded in reverse order");
+ executeCallCallouts(7, 3, 30, 3, 87, 7, 560);
+ }
+}
+
+// Local callouts for the test of server-registered callouts.
+
+namespace {
+
+ int
+testPreCallout(CalloutHandle& handle) {
+ handle.setArgument("result", static_cast<int>(1027));
+ return (0);
+}
+
+int
+testPostCallout(CalloutHandle& handle) {
+ int result;
+ handle.getArgument("result", result);
+ result *= 2;
+ handle.setArgument("result", result);
+ return (0);
+}
+
+}
+
+// The next test registers the pre and post- callouts above for hook hookpt_two,
+// and checks they are called.
+
+TEST_F(HooksManagerTest, PrePostCalloutTest) {
+
+ // Load a single library.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ // Execute the callouts. hookpt_two implements the calculation:
+ //
+ // "result - data_2"
+ //
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // (1027 - data_2) * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2024, result);
+
+ // Reset the handle to allow a reload.
+ handle.reset();
+
+ // ... and check that the pre- and post- callout functions don't survive a
+ // reload.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(-15, result);
+}
+
+// Test with test mode enabled and the pre- and post- callout functions survive
+// a reload
+
+TEST_F(HooksManagerTest, TestModeEnabledPrePostSurviveLoad) {
+
+ // Load a single library.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ HooksManager::setTestMode(true);
+
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // 1027 * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+
+ // Reset the handle to allow a reload.
+ handle.reset();
+
+ // ... and check that the pre- and post- callout functions survive a
+ // reload.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ // Expect same value i.e. 1027 * 2
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+}
+
+// Test with test mode disabled and the pre- and post- callout functions do not
+// survive a reload
+
+TEST_F(HooksManagerTest, TestModeDisabledPrePostDoNotSurviveLoad) {
+
+ // Load a single library.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ HooksManager::setTestMode(false);
+
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // 1027 * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+
+ // Reset the handle to allow a reload.
+ handle.reset();
+
+ // ... and check that the pre- and post- callout functions don't survive a
+ // reload.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(-15, result);
+}
+
+// Test with test mode enabled and the pre- and post- callout functions do not
+// survive a reload if the test mode is set too late.
+
+TEST_F(HooksManagerTest, TestModeEnabledTooLatePrePostDoNotSurvive) {
+
+ // Load a single library.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // 1027 * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2054, result);
+
+ // Reset the handle to allow a reload.
+ handle.reset();
+
+ // ... and check that the pre- and post- callout functions don't survive a
+ // reload.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ HooksManager::setTestMode(true);
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(-15, result);
+}
+
+// Check that everything works even with no libraries loaded. First that
+// calloutsPresent() always returns false.
+
+TEST_F(HooksManagerTest, NoLibrariesCalloutsPresent) {
+ // No callouts should be present on any hooks.
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(HooksManager::commandHandlersPresent("command-one"));
+ EXPECT_FALSE(HooksManager::commandHandlersPresent("command-two"));
+}
+
+TEST_F(HooksManagerTest, NoLibrariesCallCallouts) {
+ executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+}
+
+// Test the encapsulation of the ServerHooks::registerHook() method.
+
+TEST_F(HooksManagerTest, RegisterHooks) {
+ ServerHooks::getServerHooks().reset();
+ EXPECT_EQ(2, ServerHooks::getServerHooks().getCount());
+
+ // Check that the hook indexes are as expected. (Use temporary variables
+ // as it appears that Google test can't access the constants.)
+ int sh_cc = ServerHooks::CONTEXT_CREATE;
+ int hm_cc = HooksManager::CONTEXT_CREATE;
+ EXPECT_EQ(sh_cc, hm_cc);
+
+ int sh_cd = ServerHooks::CONTEXT_DESTROY;
+ int hm_cd = HooksManager::CONTEXT_DESTROY;
+ EXPECT_EQ(sh_cd, hm_cd);
+
+ // Register a few hooks and check we have the indexes as expected.
+ EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
+ EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
+ EXPECT_EQ(4, HooksManager::registerHook(string("gamma")));
+
+
+ // The code used to throw, but it now allows to register the same
+ // hook several times. It simply returns existing index.
+ //EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
+ // DuplicateHook);
+ EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
+
+ // ... an check the hooks are as we expect.
+ EXPECT_EQ(5, ServerHooks::getServerHooks().getCount());
+ vector<string> names = ServerHooks::getServerHooks().getHookNames();
+ sort(names.begin(), names.end());
+
+ EXPECT_EQ(string("alpha"), names[0]);
+ EXPECT_EQ(string("beta"), names[1]);
+ EXPECT_EQ(string("context_create"), names[2]);
+ EXPECT_EQ(string("context_destroy"), names[3]);
+ EXPECT_EQ(string("gamma"), names[4]);
+}
+
+// Check that we can get the names of the libraries.
+
+TEST_F(HooksManagerTest, LibraryNames) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Check the names before the libraries are loaded.
+ std::vector<std::string> loaded_names = HooksManager::getLibraryNames();
+ EXPECT_TRUE(loaded_names.empty());
+
+ // Load the libraries and check the names again.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ loaded_names = HooksManager::getLibraryNames();
+ EXPECT_TRUE(extractNames(library_names) == loaded_names);
+
+ // Unload the libraries and check again.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+ loaded_names = HooksManager::getLibraryNames();
+ EXPECT_TRUE(loaded_names.empty());
+}
+
+// Test the library validation function.
+
+TEST_F(HooksManagerTest, validateLibraries) {
+ // Vector of libraries that failed validation
+ std::vector<std::string> failed;
+
+ // Test different vectors of libraries.
+
+ // No libraries should return a success.
+ std::vector<std::string> libraries;
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single valid library should validate.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Multiple valid libraries should succeed.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(UNLOAD_CALLOUT_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single invalid library should fail.
+ libraries.clear();
+ libraries.push_back(NOT_PRESENT_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Multiple invalid libraries should fail.
+ libraries.clear();
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Combination of valid and invalid (first one valid) should fail.
+ libraries.clear();
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+
+ std::vector<std::string> expected_failures;
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+
+ // Combination of valid and invalid (first one invalid) should fail.
+ libraries.clear();
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+
+ expected_failures.clear();
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+}
+
+// This test verifies that unload is called by the prepare method.
+TEST_F(HooksManagerTest, prepareUnload) {
+
+ // Set up the list of libraries to be loaded and load them.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(UNLOAD_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Check that the marker file is not present.
+ EXPECT_FALSE(markerFilePresent());
+
+ // Prepare unload libraries runs unload functions.
+ HooksManager::prepareUnloadLibraries();
+
+ // Now the marker file is present.
+ EXPECT_TRUE(markerFilePresent());
+}
+
+// This test verifies that the specified parameters are accessed properly.
+TEST_F(HooksManagerTest, LibraryParameters) {
+
+ // Prepare parameters for the callout parameters library.
+ ElementPtr params = Element::createMap();
+ params->set("svalue", Element::create("string value"));
+ params->set("ivalue", Element::create(42));
+ params->set("bvalue", Element::create(true));
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(CALLOUT_PARAMS_LIBRARY),
+ params));
+
+ // Load the libraries. Note that callout params library checks if
+ // all mandatory parameters are there, so if anything is missing, its
+ // load() function will return error, thus causing the library to not
+ // load.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+}
+
+// This test verifies that an object can be parked in two different
+// callouts and that it is unparked when the last callout calls the
+// unpark function.
+TEST_F(HooksManagerTest, Parking) {
+ // Load the same library twice. Both installed callouts will trigger
+ // asynchronous operation.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // We could be parked any object. Typically it will be a pointer to the
+ // packet. In this case, however, it is simpler to just use a string.
+ std::string parked_object = "foo";
+ handle->setArgument("parked_object", parked_object);
+
+ // This boolean value will be set to true when the packet gets unparked.
+ bool unparked = false;
+
+ // The callouts instruct us to park the object. We associated the callback
+ // function with the parked object, which sets "unparked" flag to true. We
+ // can later test the value of this flag to verify when exactly the packet
+ // got unparked.
+ ASSERT_NO_THROW(
+ HooksManager::park<std::string>("hookpt_one", "foo",
+ [&unparked] {
+ unparked = true;
+ })
+ );
+
+ // Call both installed callouts.
+ HooksManager::callCallouts(hookpt_one_index_, *handle);
+
+ // We have two callouts which should have returned pointers to the
+ // functions which we can call to simulate completion of asynchronous
+ // tasks.
+ std::function<void()> unpark_trigger_func1;
+ handle->getArgument("unpark_trigger1", unpark_trigger_func1);
+ // Call the first function. It should cause the hook library to call the
+ // "unpark" function. However, the object should not be unparked yet,
+ // because the other callout hasn't completed its scheduled asynchronous
+ // operation (keeps a reference on the parked object).
+ unpark_trigger_func1();
+ EXPECT_FALSE(unparked);
+
+ // Call the second function. This should decrease the reference count to
+ // 0 and the packet should be unparked.
+ std::function<void()> unpark_trigger_func2;
+ handle->getArgument("unpark_trigger2", unpark_trigger_func2);
+ unpark_trigger_func2();
+ EXPECT_TRUE(unparked);
+
+ // Resetting the handle makes return from test body to crash.
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ // Handle is still active.
+ EXPECT_FALSE(status);
+}
+
+// This test verifies that the server can also unpark the packet.
+TEST_F(HooksManagerTest, ServerUnpark) {
+ // Load the same library twice. Both installed callouts will trigger
+ // asynchronous operation.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ // Load libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // We could be parked any object. Typically it will be a pointer to the
+ // packet. In this case, however, it is simpler to just use a string.
+ std::string parked_object = "foo";
+ handle->setArgument("parked_object", parked_object);
+
+ // This boolean value will be set to true when the packet gets unparked.
+ bool unparked = false;
+
+ // The callouts instruct us to park the object. We associated the callback
+ // function with the parked object, which sets "unparked" flag to true. We
+ // can later test the value of this flag to verify when exactly the packet
+ // got unparked.
+ HooksManager::park<std::string>("hookpt_one", "foo",
+ [&unparked] {
+ unparked = true;
+ });
+
+ // It should be possible for the server to increase reference counter.
+ ASSERT_NO_THROW(HooksManager::reference<std::string>("hookpt_one", "foo"));
+
+ // Call installed callout.
+ HooksManager::callCallouts(hookpt_one_index_, *handle);
+
+ // Server can force unparking the object.
+ EXPECT_TRUE(HooksManager::unpark<std::string>("hookpt_one", "foo"));
+
+ EXPECT_TRUE(unparked);
+
+ // Reset the handle to allow unload.
+ handle.reset();
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+}
+
+// This test verifies that the server can drop parked packet.
+TEST_F(HooksManagerTest, ServerDropParked) {
+ // Load library.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // We could be parked any object. Typically it will be a pointer to the
+ // packet. In this case, however, it is simpler to just use a string.
+ std::string parked_object = "foo";
+ handle->setArgument("parked_object", parked_object);
+
+ // This boolean value will be set to true when the packet gets unparked.
+ bool unparked = false;
+
+ // The callouts instruct us to park the object. We associated the callback
+ // function with the parked object, which sets "unparked" flag to true. We
+ // can later test the value of this flag to verify when exactly the packet
+ // got unparked.
+ HooksManager::park<std::string>("hookpt_one", "foo",
+ [&unparked] {
+ unparked = true;
+ });
+
+ // It should be possible for the server to increase reference counter.
+ ASSERT_NO_THROW(HooksManager::reference<std::string>("hookpt_one", "foo"));
+
+ // Call installed callout.
+ HooksManager::callCallouts(hookpt_one_index_, *handle);
+
+ // Drop the parked packet. The callback should not be called.
+ EXPECT_TRUE(HooksManager::drop<std::string>("hookpt_one", "foo"));
+
+ EXPECT_FALSE(unparked);
+
+ // An attempt to unpark the packet should return false, as this packet
+ // is not parked anymore.
+ EXPECT_FALSE(HooksManager::unpark<std::string>("hookpt_one", "foo"));
+
+ // Reset the handle to allow unload.
+ handle.reset();
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+}
+
+// This test verifies that parked objects are removed when libraries are
+// unloaded.
+TEST_F(HooksManagerTest, UnloadBeforeUnpark) {
+ // Load the same library twice. Both installed callouts will trigger
+ // asynchronous operation.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(ASYNC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ // Load libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // We could be parked any object. Typically it will be a pointer to the
+ // packet. In this case, however, it is simpler to just use a string.
+ std::string parked_object = "foo";
+ handle->setArgument("parked_object", parked_object);
+
+ // This boolean value will be set to true when the packet gets unparked.
+ bool unparked = false;
+
+ // The callouts instruct us to park the object. We associated the callback
+ // function with the parked object, which sets "unparked" flag to true. We
+ // can later test the value of this flag to verify when exactly the packet
+ // got unparked.
+ HooksManager::park<std::string>("hookpt_one", "foo",
+ [&unparked] {
+ unparked = true;
+ });
+
+ // Call installed callout.
+ HooksManager::callCallouts(hookpt_one_index_, *handle);
+
+ // Reset the handle to allow a reload.
+ handle.reset();
+
+ // Try reloading the libraries.
+ EXPECT_NO_THROW(HooksManager::prepareUnloadLibraries());
+ bool status = false;
+ EXPECT_NO_THROW(status = HooksManager::unloadLibraries());
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Parked object should have been removed.
+ EXPECT_FALSE(HooksManager::unpark<std::string>("hookpt_one", "foo"));
+
+ // Callback should not be called.
+ EXPECT_FALSE(unparked);
+}
+
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/incorrect_version_library.cc b/src/lib/hooks/tests/incorrect_version_library.cc
new file mode 100644
index 0000000..f4d7850
--- /dev/null
+++ b/src/lib/hooks/tests/incorrect_version_library.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2013-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/.
+
+/// @file
+/// @brief Incorrect version function test
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - It contains the version() framework function only, which returns an
+/// incorrect version number.
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+ return (KEA_HOOKS_VERSION + 1);
+}
+
+};
diff --git a/src/lib/hooks/tests/library_manager_collection_unittest.cc b/src/lib/hooks/tests/library_manager_collection_unittest.cc
new file mode 100644
index 0000000..9b30cb3
--- /dev/null
+++ b/src/lib/hooks/tests/library_manager_collection_unittest.cc
@@ -0,0 +1,314 @@
+// Copyright (C) 2013-2020 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+#include <hooks/libinfo.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager collection test class
+
+class LibraryManagerCollectionTest : public ::testing::Test,
+ public HooksCommonTestClass {
+private:
+
+ /// To avoid unused variable errors
+ std::string dummy(int i) {
+ if (i == 0) {
+ return (MARKER_FILE);
+ } else if (i > 0) {
+ return (LOAD_CALLOUT_LIBRARY);
+ } else {
+ return (LOAD_ERROR_CALLOUT_LIBRARY);
+ }
+ }
+};
+
+} // namespace
+
+namespace isc {
+namespace hooks {
+/// @brief Public library manager collection class
+///
+/// This is an instance of the LibraryManagerCollection class but with the
+/// protected methods made public for test purposes.
+
+class PublicLibraryManagerCollection : public LibraryManagerCollection {
+public:
+ /// @brief Constructor
+ ///
+ /// @param List of libraries that this collection will manage. The order
+ /// of the libraries is important.
+ PublicLibraryManagerCollection(const HookLibsCollection& libraries)
+ : LibraryManagerCollection(libraries) {
+ }
+
+ /// Public methods that call protected methods on the superclass.
+ using LibraryManagerCollection::unloadLibraries;
+};
+
+} // namespace hooks
+} // namespace isc
+
+namespace {
+// This is effectively the same test as for LibraryManager, but using the
+// LibraryManagerCollection object.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(library_names);
+
+ // Load the libraries.
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ EXPECT_EQ(2, lm_collection.getLoadedLibraryCount());
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ boost::shared_ptr<CalloutManager> manager =
+ lm_collection.getCalloutManager();
+ {
+ SCOPED_TRACE("Doing calculation with libraries loaded");
+ executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(lm_collection.unloadLibraries());
+ EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Doing calculation with libraries not loaded");
+ executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that no libraries will be loaded.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibrariesWithError) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+ library_names.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(INCORRECT_VERSION_LIBRARY),
+ data::ConstElementPtr()));
+ library_names.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(library_names);
+
+ // Load the libraries. We expect a failure status to be returned as
+ // one of the libraries failed to load.
+ EXPECT_FALSE(lm_collection.loadLibraries());
+
+ // Expect no libraries were loaded.
+ EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
+}
+
+// Check that everything works even with no libraries loaded.
+
+TEST_F(LibraryManagerCollectionTest, NoLibrariesLoaded) {
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection library_names;
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ LibraryManagerCollection lm_collection(library_names);
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
+ boost::shared_ptr<CalloutManager> manager =
+ lm_collection.getCalloutManager();
+
+ // Execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+}
+
+// Check that we can get the names of the libraries.
+
+TEST_F(LibraryManagerCollectionTest, LibraryNames) {
+
+ // Set up the list of libraries to be loaded.
+ HookLibsCollection libraries;
+ libraries.push_back(make_pair(std::string(FULL_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+ libraries.push_back(make_pair(std::string(BASIC_CALLOUT_LIBRARY),
+ data::ConstElementPtr()));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(libraries);
+
+ // Check the names before the libraries are loaded.
+ std::vector<std::string> collection_names = lm_collection.getLibraryNames();
+ EXPECT_TRUE(extractNames(libraries) == collection_names);
+
+ // Load the libraries and check the names again.
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ EXPECT_EQ(2, lm_collection.getLoadedLibraryCount());
+ collection_names = lm_collection.getLibraryNames();
+ EXPECT_TRUE(extractNames(libraries) == collection_names);
+}
+
+// Test the library validation function.
+
+TEST_F(LibraryManagerCollectionTest, validateLibraries) {
+ // Vector of libraries that failed validation
+ std::vector<std::string> failed;
+
+ // Test different vectors of libraries.
+
+ // No libraries should return a success.
+ std::vector<std::string> libraries;
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single valid library should validate.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Multiple valid libraries should succeed.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(UNLOAD_CALLOUT_LIBRARY);
+ libraries.push_back(CALLOUT_PARAMS_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single invalid library should fail.
+ libraries.clear();
+ libraries.push_back(NOT_PRESENT_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Multiple invalid libraries should fail.
+ libraries.clear();
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Combination of valid and invalid (first one valid) should fail.
+ libraries.clear();
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+
+ std::vector<std::string> expected_failures;
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+
+ // Combination of valid and invalid (first one invalid) should fail.
+ libraries.clear();
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+
+ expected_failures.clear();
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+}
+
+// This test verifies if getLibraryNames and getLibraryInfo are returning
+// expected values if there are no libraries configured.
+TEST_F(LibraryManagerCollectionTest, libraryGetEmpty) {
+
+ HookLibsCollection empty;
+ boost::shared_ptr<LibraryManagerCollection> mgr;
+
+ // Instantiate library manager collection with no libraries
+ EXPECT_NO_THROW(mgr.reset(new LibraryManagerCollection(empty)));
+
+ // Check that getLibraryInfo returns empty list properly.
+ HookLibsCollection returned = mgr->getLibraryInfo();
+ EXPECT_TRUE(returned.empty());
+
+ // Check that getLibraryNames return empty list, too.
+ vector<string> names(3, "rubbish"); // just put something in it.
+ EXPECT_NO_THROW(names = mgr->getLibraryNames());
+ EXPECT_TRUE(names.empty());
+}
+
+// This test verifies if getLibraryNames and getLibraryInfo are returning
+// expected values when there are libraries configured.
+TEST_F(LibraryManagerCollectionTest, libraryGet) {
+ using namespace data;
+
+ HookLibsCollection libs;
+ ElementPtr param1(Element::fromJSON("{ \"param1\": \"foo\" }"));
+ ElementPtr param2(Element::fromJSON("{ \"param2\": \"bar\" }"));
+ libs.push_back(make_pair("libone", param1));
+ libs.push_back(make_pair("libtwo", param2));
+
+ boost::shared_ptr<LibraryManagerCollection> mgr;
+ EXPECT_NO_THROW(mgr.reset(new LibraryManagerCollection(libs)));
+ EXPECT_TRUE(libs == mgr->getLibraryInfo());
+
+ vector<string> exp_names;
+ exp_names.push_back("libone");
+ exp_names.push_back("libtwo");
+
+ EXPECT_TRUE(exp_names == mgr->getLibraryNames());
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/library_manager_unittest.cc b/src/lib/hooks/tests/library_manager_unittest.cc
new file mode 100644
index 0000000..52dea20
--- /dev/null
+++ b/src/lib/hooks/tests/library_manager_unittest.cc
@@ -0,0 +1,735 @@
+// Copyright (C) 2013-2023 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/marker_file.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+
+#include <util/multi_threading_mgr.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <fstream>
+#include <string>
+
+#include <unistd.h>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace isc::log;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager test class
+
+class LibraryManagerTest : public ::testing::Test,
+ public HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ ///
+ /// Initializes the CalloutManager object used in the tests. It sets it
+ /// up with the hooks initialized in the HooksCommonTestClass object and
+ /// with four libraries.
+ LibraryManagerTest() {
+ callout_manager_.reset(new CalloutManager(4));
+
+ // Ensure the marker file is not present at the start of a test.
+ static_cast<void>(remove(MARKER_FILE));
+
+ // Disable multi-threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor
+ ///
+ /// Ensures a marker file is removed after each test.
+ ~LibraryManagerTest() {
+ static_cast<void>(remove(MARKER_FILE));
+
+ // Disable multi-threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Marker file present
+ ///
+ /// Convenience function to check whether a marker file is present. It
+ /// does this by opening the file.
+ ///
+ /// @return true if the marker file is present.
+ bool markerFilePresent() const {
+
+ // Try to open it.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::in);
+
+ // Check if it is open and close it if so.
+ bool exists = marker.is_open();
+ if (exists) {
+ marker.close();
+ }
+
+ return (exists);
+ }
+
+ /// @brief Call callouts test
+ ///
+ /// A wrapper around the method of the same name in the HooksCommonTestClass
+ /// object, this passes this class's CalloutManager to that method.
+ ///
+ /// @param r0...r3, d1..d3 Values and intermediate values expected. They
+ /// are ordered so that the variables appear in the argument list in
+ /// the order they are used. See HooksCommonTestClass::execute for
+ /// a full description. (rN is used to indicate an expected result,
+ /// dN is data to be passed to the calculation.)
+ void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+ int r3) {
+ HooksCommonTestClass::executeCallCallouts(callout_manager_, r0, d1,
+ r1, d2, r2, d3, r3);
+ }
+
+ /// @brief Call command handlers test.
+ ///
+ /// A wrapper around the method of the same name in the HooksCommonTestClass
+ /// object, this passes this class's CalloutManager to that method.
+ ///
+ /// @param r1..r2, d1..d2 Values and intermediate values expected.
+ void executeCallCommandHandlers(int d1, int r1, int d2, int r2) {
+ HooksCommonTestClass::executeCallCommandHandlers(callout_manager_,
+ d1, r1, d2, r2);
+ }
+
+ /// Callout manager used for the test.
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+/// @brief Library manager class
+///
+/// This is an instance of the LibraryManager class but with the protected
+/// methods made public for test purposes.
+
+class PublicLibraryManager : public isc::hooks::LibraryManager {
+public:
+ /// @brief Constructor
+ ///
+ /// Stores the library name. The actual loading is done in loadLibrary().
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ /// @param index Index of this library. For all these tests, it will be
+ /// zero, as we are only using one library.
+ /// @param manager CalloutManager object
+ PublicLibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager)
+ : LibraryManager(name, index, manager)
+ {}
+
+ /// Public methods that call protected methods on the superclass.
+ using LibraryManager::unloadLibrary;
+ using LibraryManager::openLibrary;
+ using LibraryManager::closeLibrary;
+ using LibraryManager::checkVersion;
+ using LibraryManager::checkMultiThreadingCompatible;
+ using LibraryManager::registerStandardCallouts;
+ using LibraryManager::runLoad;
+ using LibraryManager::prepareUnloadLibrary;
+};
+
+
+// Check that LibraryManager constructor requires a not null manager
+
+TEST_F(LibraryManagerTest, NullManager) {
+ boost::shared_ptr<CalloutManager> null_manager;
+ EXPECT_THROW(PublicLibraryManager(std::string("foo"), 0, null_manager),
+ NoCalloutManager);
+}
+
+// Check that openLibrary() reports an error when it can't find the specified
+// library.
+
+TEST_F(LibraryManagerTest, NoLibrary) {
+ PublicLibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_FALSE(lib_manager.openLibrary());
+}
+
+// Check that the openLibrary() and closeLibrary() methods work.
+
+TEST_F(LibraryManagerTest, OpenClose) {
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+
+ // Open and close the library
+ EXPECT_TRUE(lib_manager.openLibrary());
+ EXPECT_TRUE(lib_manager.closeLibrary());
+
+ // Check that a second close on an already closed library does not report
+ // an error.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, NoVersion) {
+ PublicLibraryManager lib_manager(std::string(NO_VERSION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, WrongVersion) {
+ PublicLibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library where the version function
+// throws an exception.
+
+TEST_F(LibraryManagerTest, VersionException) {
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Tests that checkVersion() function succeeds in the case of a library with a
+// version function that returns the correct version number.
+
+TEST_F(LibraryManagerTest, CorrectVersionReturned) {
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should succeed.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks that the code handles the case of a library with no
+// multi_threading_compatible function.
+
+TEST_F(LibraryManagerTest, NoMultiThreadingCompatible) {
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Not multi-threading compatible: does not matter without MT.
+ EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible(false));
+
+ // Not multi-threading compatible: does matter with MT.
+ EXPECT_FALSE(lib_manager.checkMultiThreadingCompatible(true));
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks that the code handles the case of a library with a
+// multi_threading_compatible function returning 0 (not compatible).
+
+TEST_F(LibraryManagerTest, multiThreadingNotCompatible) {
+ PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Not multi-threading compatible: does not matter without MT.
+ EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible(false));
+
+ // Not multi-threading compatible: does matter with MT.
+ EXPECT_FALSE(lib_manager.checkMultiThreadingCompatible(true));
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks that the code handles the case of a library with a
+// multi_threading_compatible function returning 1 (compatible)
+
+TEST_F(LibraryManagerTest, multiThreadingCompatible) {
+ PublicLibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Multi-threading compatible: does not matter without MT.
+ EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible(false));
+
+ // Multi-threading compatible: does matter with MT.
+ EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible(true));
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks that the code handles the case of a library with a
+// multi_threading_compatible function returning 1 (compatible)
+
+TEST_F(LibraryManagerTest, multiThreadingCompatibleException) {
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Throw exception: does not matter without MT.
+ EXPECT_TRUE(lib_manager.checkMultiThreadingCompatible(false));
+
+ // Throw exception: does matter with MT.
+ EXPECT_FALSE(lib_manager.checkMultiThreadingCompatible(true));
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks the registration of standard callouts.
+
+TEST_F(LibraryManagerTest, RegisterStandardCallouts) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Load the standard callouts
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (10 + d1) * d2 - d3
+ executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Test that the "load" function is called correctly.
+
+TEST_F(LibraryManagerTest, CheckLoadCalled) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Load the standard callouts
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+
+ // Check that only context_create and hookpt_one have callouts registered.
+ EXPECT_TRUE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_CREATE));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_DESTROY));
+
+ // Call the runLoad() method to run the load() function.
+ EXPECT_TRUE(lib_manager.runLoad());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_CREATE));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-two"));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_DESTROY));
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (5 * d1 + d2) * d3
+ executeCallCallouts(5, 5, 25, 7, 32, 10, 320);
+
+ // Execute command handlers for 'command-one' and 'command-two'.
+ //
+ // r2 = d1 * d2 * 10;
+ executeCallCommandHandlers(5, 5, 7, 350);
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check handling of a "load" function that throws an exception
+
+TEST_F(LibraryManagerTest, CheckLoadException) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Running the load function should fail.
+ EXPECT_FALSE(lib_manager.runLoad());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check handling of a "load" function that returns an error.
+
+TEST_F(LibraryManagerTest, CheckLoadError) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that we catch a load error
+ EXPECT_FALSE(lib_manager.runLoad());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// No unload function
+
+TEST_F(LibraryManagerTest, CheckNoUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that no unload function returns true.
+ EXPECT_TRUE(lib_manager.prepareUnloadLibrary());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function returns an error
+
+TEST_F(LibraryManagerTest, CheckUnloadError) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that unload function returning an error returns false.
+ EXPECT_FALSE(lib_manager.prepareUnloadLibrary());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function throws an exception.
+
+TEST_F(LibraryManagerTest, CheckUnloadException) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that we detect that the unload function throws an exception.
+ EXPECT_FALSE(lib_manager.prepareUnloadLibrary());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the case of the library's unload() function returning a
+// success is handled correctly.
+
+TEST_F(LibraryManagerTest, CheckUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(UNLOAD_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+
+ // Check that the marker file is not present (at least that the file
+ // open fails).
+ EXPECT_FALSE(markerFilePresent());
+
+ // Check that unload function runs and returns a success
+ EXPECT_TRUE(lib_manager.prepareUnloadLibrary());
+
+ // Check that the marker file was created.
+ EXPECT_TRUE(markerFilePresent());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Test the operation of unloadLibrary(). We load a library with a set
+// of callouts then unload it. We need to check that the callouts have been
+// removed. We'll also check that the library's unload() function was called
+// as well.
+
+TEST_F(LibraryManagerTest, LibUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // No callouts should be registered at the moment.
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
+
+ // Load the single standard callout and check it is registered correctly.
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
+
+ // Call the load function to load the other callouts.
+ EXPECT_TRUE(lib_manager.runLoad());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_TRUE(callout_manager_->commandHandlersPresent("command-two"));
+
+ // Unload the library and check that the callouts have been removed from
+ // the CalloutManager.
+ lib_manager.unloadLibrary();
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-one"));
+ EXPECT_FALSE(callout_manager_->commandHandlersPresent("command-two"));
+}
+
+// Now come the loadLibrary() tests that make use of all the methods tested
+// above. These tests are really to make sure that the methods have been
+// tied together correctly.
+
+// First test the basic error cases - no library, no version function, version
+// function returning an error.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoLibrary) {
+ LibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoVersion) {
+ LibraryManager lib_manager(std::string(NO_VERSION_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, LoadLibraryWrongVersion) {
+ LibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the full loadLibrary call works.
+
+TEST_F(LibraryManagerTest, LoadLibrary) {
+ PublicLibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_TRUE(lib_manager.loadLibrary());
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ executeCallCallouts(7, 5, 35, 9, 26, 3, 78);
+
+ EXPECT_TRUE(lib_manager.unloadLibrary());
+
+ // Check that the callouts have been removed from the callout manager.
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+}
+
+// Now test for multiple libraries. We'll load the full callout library
+// first, then load some of the libraries with missing framework functions.
+// This will check that when searching for framework functions, only the
+// specified library is checked, not other loaded libraries. We will
+// load a second library with suitable callouts and check that the callouts
+// are added correctly. Finally, we'll unload one of the libraries and
+// check that only the callouts belonging to that library were removed.
+
+TEST_F(LibraryManagerTest, LoadMultipleLibraries) {
+ // Load a library with all framework functions.
+ PublicLibraryManager lib_manager_1(std::string(FULL_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager_1.loadLibrary());
+
+ // Attempt to load a library with no version() function. We should detect
+ // this and not end up calling the function from the already loaded
+ // library.
+ PublicLibraryManager lib_manager_2(std::string(NO_VERSION_LIBRARY),
+ 1, callout_manager_);
+ EXPECT_FALSE(lib_manager_2.loadLibrary());
+
+ // Attempt to load the library with an incorrect version. This should
+ // be detected.
+ PublicLibraryManager lib_manager_3(std::string(INCORRECT_VERSION_LIBRARY),
+ 1, callout_manager_);
+ EXPECT_FALSE(lib_manager_3.loadLibrary());
+
+ // Load the basic callout library. This only has standard callouts so,
+ // if the first library's load() function gets called, some callouts
+ // will be registered twice and lead to incorrect results.
+ PublicLibraryManager lib_manager_4(std::string(BASIC_CALLOUT_LIBRARY),
+ 1, callout_manager_);
+ EXPECT_TRUE(lib_manager_4.loadLibrary());
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+
+ // All done, so unload the first library.
+ EXPECT_TRUE(lib_manager_1.unloadLibrary());
+
+ // Now execute the callouts again and check that the results are as
+ // expected for the new calculation.
+ executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+ // ... and tidy up.
+ EXPECT_TRUE(lib_manager_4.unloadLibrary());
+}
+
+// Check that libraries can be validated.
+
+TEST_F(LibraryManagerTest, validateLibraries) {
+ EXPECT_TRUE(LibraryManager::validateLibrary(BASIC_CALLOUT_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(FULL_CALLOUT_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(FRAMEWORK_EXCEPTION_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(INCORRECT_VERSION_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_CALLOUT_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_ERROR_CALLOUT_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(NOT_PRESENT_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(NO_VERSION_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(UNLOAD_CALLOUT_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(CALLOUT_PARAMS_LIBRARY));
+
+ EXPECT_FALSE(LibraryManager::validateLibrary(BASIC_CALLOUT_LIBRARY, true));
+ EXPECT_TRUE(LibraryManager::validateLibrary(FULL_CALLOUT_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(FRAMEWORK_EXCEPTION_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(INCORRECT_VERSION_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(LOAD_CALLOUT_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(LOAD_ERROR_CALLOUT_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(NOT_PRESENT_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(NO_VERSION_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(UNLOAD_CALLOUT_LIBRARY, true));
+ EXPECT_FALSE(LibraryManager::validateLibrary(CALLOUT_PARAMS_LIBRARY, true));
+}
+
+// Check that log messages are properly registered and unregistered.
+
+TEST_F(LibraryManagerTest, libraryLoggerSetup) {
+ // Load a library with all framework functions.
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.loadLibrary());
+
+ // After loading the library, the global logging dictionary should
+ // contain log messages registered for this library.
+ const MessageDictionaryPtr& dict = MessageDictionary::globalDictionary();
+ EXPECT_EQ("basic callout load %1", dict->getText("BCL_LOAD_START"));
+ EXPECT_EQ("basic callout load end", dict->getText("BCL_LOAD_END"));
+ // Some of the messages defined by the hook library are duplicates. But,
+ // the loadLibrary function should have logged the duplicates and clear
+ // the duplicates list. By checking that the list of duplicates is empty
+ // we test that the LibraryManager handles the duplicates (logs and
+ // clears them).
+ EXPECT_TRUE(MessageInitializer::getDuplicates().empty());
+
+ // After unloading the library, the messages should be unregistered.
+ EXPECT_TRUE(lib_manager.unloadLibrary());
+ // The musl libc does not implement dlclose
+#ifndef LIBC_MUSL
+ EXPECT_TRUE(dict->getText("BCL_LOAD_START").empty());
+ EXPECT_TRUE(dict->getText("BCL_LOAD_END").empty());
+#endif
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/load_callout_library.cc b/src/lib/hooks/tests/load_callout_library.cc
new file mode 100644
index 0000000..613ec2c
--- /dev/null
+++ b/src/lib/hooks/tests/load_callout_library.cc
@@ -0,0 +1,152 @@
+// Copyright (C) 2013-2017 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/.
+
+/// @file
+/// @brief Basic library with load() function
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "load" framework functions are supplied. One "standard"
+/// callout is supplied ("hookpt_one") and two non-standard ones which are
+/// registered during the call to "load" on the hooks "hookpt_two" and
+/// "hookpt_three".
+///
+/// All callouts do trivial calculations, the result of all being called in
+/// sequence being
+///
+/// @f[ ((5 * data_1) + data_2) * data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2
+/// to hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(5));
+ handle.setArgument("result", static_cast<int>(5));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 5. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getContext("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Third callout adds "data_3" to the result.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// First command handler assigns data to a result.
+
+static int
+command_handler_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result = data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second command handler multiples the result by data by 10.
+
+static int
+command_handler_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data * 10;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+int load(LibraryHandle& handle) {
+ // Initialize the user library if the main image was statically linked
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ // Register the non-standard functions
+ handle.registerCallout("hookpt_two", hook_nonstandard_two);
+ handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+ // Register command_handler_one as control command handler.
+ handle.registerCommandCallout("command-one", command_handler_one);
+ handle.registerCommandCallout("command-two", command_handler_two);
+
+ return (0);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/load_error_callout_library.cc b/src/lib/hooks/tests/load_error_callout_library.cc
new file mode 100644
index 0000000..66b1359
--- /dev/null
+++ b/src/lib/hooks/tests/load_error_callout_library.cc
@@ -0,0 +1,47 @@
+// Copyright (C) 2013-2020 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/.
+
+/// @file
+/// @brief Error load library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - All framework functions are supplied. "version" returns the correct
+/// value, but "load" and unload return an error.
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+ return (1);
+}
+
+int
+unload() {
+ return (1);
+}
+
+int
+multi_threading_compatible() {
+ return (0);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/marker_file.h.in b/src/lib/hooks/tests/marker_file.h.in
new file mode 100644
index 0000000..368ad37
--- /dev/null
+++ b/src/lib/hooks/tests/marker_file.h.in
@@ -0,0 +1,19 @@
+// Copyright (C) 2013-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/.
+
+#ifndef MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called.
+
+namespace {
+const char* MARKER_FILE = "@abs_builddir@/marker_file.dat";
+}
+
+#endif // MARKER_FILE_H
+
diff --git a/src/lib/hooks/tests/no_version_library.cc b/src/lib/hooks/tests/no_version_library.cc
new file mode 100644
index 0000000..a4c4bb5
--- /dev/null
+++ b/src/lib/hooks/tests/no_version_library.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 2013-2020 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>
+
+/// @file
+/// @brief No version function library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - No version() function is present.
+
+extern "C" {
+
+int no_version() {
+ return (0);
+}
+
+};
diff --git a/src/lib/hooks/tests/parking_lots_unittest.cc b/src/lib/hooks/tests/parking_lots_unittest.cc
new file mode 100644
index 0000000..96f8814
--- /dev/null
+++ b/src/lib/hooks/tests/parking_lots_unittest.cc
@@ -0,0 +1,339 @@
+// Copyright (C) 2018-2021 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 <hooks/parking_lots.h>
+#include <boost/weak_ptr.hpp>
+#include <testutils/gtest_utils.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::hooks;
+
+namespace {
+
+// Defines a pointer to a string. The tests use shared pointers
+// as parked objects to ensure key matching works correctly with
+// them. We're doing this because real-world use parked objects
+// are typically pointers to packets.
+typedef boost::shared_ptr<std::string> StringPtr;
+
+// Test that it is possible to create and retrieve parking lots for
+// specified hook points.
+TEST(ParkingLotsTest, createGetParkingLot) {
+ ParkingLots parking_lots;
+
+ ParkingLotPtr parking_lot0 = parking_lots.getParkingLotPtr(1);
+ ParkingLotPtr parking_lot1 = parking_lots.getParkingLotPtr(2);
+ ParkingLotPtr parking_lot2 = parking_lots.getParkingLotPtr(1);
+
+ ASSERT_TRUE(parking_lot0);
+ ASSERT_TRUE(parking_lot1);
+ ASSERT_TRUE(parking_lot2);
+
+ EXPECT_EQ(0, parking_lot0->size());
+ EXPECT_EQ(0, parking_lot1->size());
+ EXPECT_EQ(0, parking_lot2->size());
+
+ EXPECT_FALSE(parking_lot0 == parking_lot1);
+ EXPECT_TRUE(parking_lot0 == parking_lot2);
+
+ ASSERT_NO_THROW(parking_lots.clear());
+
+ ParkingLotPtr parking_lot3 = parking_lots.getParkingLotPtr(1);
+ ASSERT_TRUE(parking_lot3);
+
+ EXPECT_FALSE(parking_lot3 == parking_lot0);
+}
+
+// Verify that an object can be parked.
+TEST(ParkingLotsTest, park) {
+ ParkingLot parking_lot;
+
+ // Create object to park.
+ StringPtr parked_object(new std::string("foo"));
+
+ // Verify that we can park an object that has not been parked.
+ ASSERT_NO_THROW(parking_lot.park(parked_object, [] {}));
+
+ EXPECT_EQ(1, parking_lot.size());
+
+ // Verify that we cannot park an object that has been parked
+ EXPECT_THROW(parking_lot.park(parked_object, [] {}),
+ InvalidOperation);
+}
+
+// Verify that an object can be referenced.
+TEST(ParkingLotsTest, reference) {
+ ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>();
+ ParkingLotHandlePtr parking_lot_handle =
+ boost::make_shared<ParkingLotHandle>(parking_lot);
+
+ // Create an object.
+ StringPtr parked_object(new std::string("foo"));
+
+ // Cannot reference an object that has not been parked.
+ ASSERT_THROW(parking_lot_handle->reference(parked_object),
+ InvalidOperation);
+
+ // Park the object.
+ ASSERT_NO_THROW(parking_lot->park(parked_object, [] {}));
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Reference the object. Reference count should one.
+ int ref_count = 0;
+ ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object));
+ ASSERT_EQ(1, ref_count);
+
+ // Reference the object again. Reference count should two.
+ ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object));
+ ASSERT_EQ(2, ref_count);
+
+ EXPECT_EQ(1, parking_lot->size());
+}
+
+// Test that object can be parked and then unparked.
+TEST(ParkingLotsTest, unpark) {
+ ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>();
+ ParkingLotHandlePtr parking_lot_handle =
+ boost::make_shared<ParkingLotHandle>(parking_lot);
+
+ StringPtr parked_object(new std::string("foo"));
+
+ // Unparking should return false if the object isn't parked.
+ EXPECT_FALSE(parking_lot->unpark(parked_object));
+
+ EXPECT_EQ(0, parking_lot->size());
+
+ // This flag will indicate if the callback has been called.
+ bool unparked = false;
+
+ ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] {
+ unparked = true;
+ }));
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Reference the parked object twice because we're going to test that
+ // reference counting works fine.
+ ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
+ ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Try to unpark the object. It should decrease the reference count, but not
+ // unpark the packet yet.
+ EXPECT_TRUE(parking_lot_handle->unpark(parked_object));
+ EXPECT_FALSE(unparked);
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Try to unpark the object. This time it should be successful, because the
+ // reference count goes to 0.
+ EXPECT_TRUE(parking_lot_handle->unpark(parked_object));
+ EXPECT_TRUE(unparked);
+
+ EXPECT_EQ(0, parking_lot->size());
+
+ // Calling unpark again should return false to indicate that the object is
+ // not parked.
+ EXPECT_FALSE(parking_lot_handle->unpark(parked_object));
+
+ EXPECT_EQ(0, parking_lot->size());
+}
+
+// Test that parked object can be dropped.
+TEST(ParkingLotsTest, drop) {
+ ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>();
+ ParkingLotHandlePtr parking_lot_handle =
+ boost::make_shared<ParkingLotHandle>(parking_lot);
+
+ StringPtr parked_object(new std::string("foo"));
+
+ // This flag will indicate if the callback has been called.
+ bool unparked = false;
+ ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] {
+ unparked = true;
+ }));
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Reference object twice to test that dropping the packet ignores
+ // reference counting.
+ ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
+ ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
+
+ // Drop parked object. The callback should not be invoked.
+ EXPECT_TRUE(parking_lot_handle->drop(parked_object));
+ EXPECT_FALSE(unparked);
+
+ EXPECT_EQ(0, parking_lot->size());
+
+ // Expect that an attempt to unpark return false, as the object
+ // has been dropped.
+ EXPECT_FALSE(parking_lot_handle->unpark(parked_object));
+}
+
+// Test that parked lots can be cleared.
+TEST(ParkingLotsTest, clear) {
+ ParkingLotsPtr parking_lots = boost::make_shared<ParkingLots>();
+ ParkingLotPtr parking_lot = parking_lots->getParkingLotPtr(1234);
+ ASSERT_TRUE(parking_lot);
+ ParkingLotHandlePtr parking_lot_handle =
+ boost::make_shared<ParkingLotHandle>(parking_lot);
+
+ boost::shared_ptr<std::string> parked_object =
+ boost::make_shared<std::string>("foo");
+ boost::weak_ptr<std::string> weak_parked_object(parked_object);
+
+ // This flag will indicate if the callback has been called.
+ bool unparked = false;
+ ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] {
+ unparked = true;
+ }));
+
+ // Reference object twice to test that clearing the parking lots
+ // ignores reference counting.
+ ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
+ ASSERT_NO_THROW(parking_lot_handle->reference(parked_object));
+
+ // Drop reference on objects.
+ parking_lot.reset();
+ parking_lot_handle.reset();
+ parked_object.reset();
+
+ // The parked object is still alive.
+ EXPECT_FALSE(weak_parked_object.expired());
+
+ // Clear the parking lots.
+ ASSERT_NO_THROW(parking_lots->clear());
+
+ // The callback should not be invoked.
+ EXPECT_FALSE(unparked);
+
+ // The parked object was destroyed.
+ EXPECT_TRUE(weak_parked_object.expired());
+}
+
+// Verify that an object can be dereferenced.
+TEST(ParkingLotsTest, dereference) {
+ ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>();
+ ParkingLotHandlePtr parking_lot_handle =
+ boost::make_shared<ParkingLotHandle>(parking_lot);
+
+ // Create an object.
+ StringPtr parked_object(new std::string("foo"));
+
+ // Verify that an object that hasn't been parked, cannot be
+ // dereferenced.
+ ASSERT_THROW(parking_lot_handle->dereference(parked_object),
+ InvalidOperation);
+
+ // Park the object.
+ // This flag will indicate if the callback has been called.
+ bool unparked = false;
+ ASSERT_NO_THROW(parking_lot->park(parked_object, [&unparked] {
+ unparked = true;
+ }));
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Reference the parked object twice.
+ int ref_count = 0;
+ ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object));
+ ASSERT_EQ(1, ref_count);
+ ASSERT_NO_THROW(ref_count = parking_lot_handle->reference(parked_object));
+ ASSERT_EQ(2, ref_count);
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Try to dereference the object. It should decrease the reference count,
+ // but not unpark the packet or invoke the callback.
+ ASSERT_NO_THROW(ref_count = parking_lot_handle->dereference(parked_object));
+ ASSERT_EQ(1, ref_count);
+ EXPECT_FALSE(unparked);
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Try to dereference the object. It should decrease the reference count,
+ // but not unpark the packet or invoke the callback.
+ ASSERT_NO_THROW(ref_count = parking_lot_handle->dereference(parked_object));
+ ASSERT_EQ(0, ref_count);
+ EXPECT_FALSE(unparked);
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Try to dereference the object. It should decrement to -1
+ // but not unpark the packet or invoke the callback.
+ ASSERT_NO_THROW(ref_count = parking_lot_handle->dereference(parked_object));
+ EXPECT_EQ(-1, ref_count);
+ EXPECT_FALSE(unparked);
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Calling unpark should invoke the callback.
+ ASSERT_TRUE(parking_lot_handle->unpark(parked_object));
+ EXPECT_TRUE(unparked);
+
+ EXPECT_EQ(0, parking_lot->size());
+}
+
+// Verify that parked objects are correctly distinguished from
+// one another.
+TEST(ParkingLotsTest, multipleObjects) {
+ ParkingLotPtr parking_lot = boost::make_shared<ParkingLot>();
+ ParkingLotHandlePtr parking_lot_handle =
+ boost::make_shared<ParkingLotHandle>(parking_lot);
+
+ // Create an object and park it.
+ StringPtr object_one(new std::string("one"));
+ int unparked_one = 0;
+ ASSERT_NO_THROW(parking_lot->park(object_one, [&unparked_one] {
+ ++unparked_one;
+ }));
+
+ // Create a second object and park it.
+ StringPtr object_two(new std::string("two"));
+ int unparked_two = 0;
+ ASSERT_NO_THROW(parking_lot->park(object_two, [&unparked_two] {
+ ++unparked_two;
+ }));
+
+ EXPECT_EQ(2, parking_lot->size());
+
+ // Create a third object but don't park it.
+ StringPtr object_three(new std::string("three"));
+
+ // Try to unpark object_three. It should fail, and no callbacks
+ // should get invoked.
+ EXPECT_FALSE(parking_lot_handle->unpark(object_three));
+ EXPECT_EQ(unparked_one, 0);
+ EXPECT_EQ(unparked_two, 0);
+
+ EXPECT_EQ(2, parking_lot->size());
+
+ // Unpark object one. It should succeed and its callback should
+ // get invoked.
+ EXPECT_TRUE(parking_lot_handle->unpark(object_one));
+ EXPECT_EQ(unparked_one, 1);
+ EXPECT_EQ(unparked_two, 0);
+
+ EXPECT_EQ(1, parking_lot->size());
+
+ // Unpark object two. It should succeed and its callback should
+ // get invoked.
+ EXPECT_TRUE(parking_lot_handle->unpark(object_two));
+ EXPECT_EQ(unparked_one, 1);
+ EXPECT_EQ(unparked_two, 1);
+
+ EXPECT_EQ(0, parking_lot->size());
+}
+
+}
diff --git a/src/lib/hooks/tests/run_unittests.cc b/src/lib/hooks/tests/run_unittests.cc
new file mode 100644
index 0000000..cd6fc2b
--- /dev/null
+++ b/src/lib/hooks/tests/run_unittests.cc
@@ -0,0 +1,19 @@
+// 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 <log/logger_support.h>
+#include <util/unittests/run_all.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/hooks/tests/server_hooks_unittest.cc b/src/lib/hooks/tests/server_hooks_unittest.cc
new file mode 100644
index 0000000..f9b20cb
--- /dev/null
+++ b/src/lib/hooks/tests/server_hooks_unittest.cc
@@ -0,0 +1,232 @@
+// Copyright (C) 2013-2018 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 <hooks/server_hooks.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+// Checks the registration of hooks and the interrogation methods. As the
+// constructor registers two hooks, this is also a test of the constructor.
+
+TEST(ServerHooksTest, RegisterHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // There should be two hooks already registered, with indexes 0 and 1.
+ EXPECT_EQ(2, hooks.getCount());
+ EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(0, hooks.findIndex("context_create"));
+ EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+ EXPECT_EQ(1, hooks.findIndex("context_destroy"));
+
+ // Check that the constants are as expected. (The intermediate variables
+ // are used because of problems with g++ 4.6.1/Ubuntu 11.10 when resolving
+ // the value of the ServerHooks constants when they appeared within the
+ // gtest macro.)
+ const int create_value = ServerHooks::CONTEXT_CREATE;
+ const int destroy_value = ServerHooks::CONTEXT_DESTROY;
+ EXPECT_EQ(0, create_value);
+ EXPECT_EQ(1, destroy_value);
+
+ // Register another couple of hooks. The test on returned index is based
+ // on knowledge that the hook indexes are assigned in ascending order.
+ int alpha = hooks.registerHook("alpha");
+ EXPECT_EQ(2, alpha);
+ EXPECT_EQ(2, hooks.getIndex("alpha"));
+
+ int beta = hooks.registerHook("beta");
+ EXPECT_EQ(3, beta);
+ EXPECT_EQ(3, hooks.getIndex("beta"));
+
+ // Should be four hooks now
+ EXPECT_EQ(4, hooks.getCount());
+}
+
+// Check that duplicate names cannot be registered.
+// This test has been updated. See #5251 for details. The old
+// code is retained in case we decide to get back to it.
+TEST(ServerHooksTest, DISABLED_OldDuplicateHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // Ensure we can't duplicate one of the existing names.
+ EXPECT_THROW(hooks.registerHook("context_create"), DuplicateHook);
+
+ // Check we can't duplicate a newly registered hook.
+ int gamma = hooks.registerHook("gamma");
+ EXPECT_EQ(2, gamma);
+ EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
+}
+
+// Check that duplicate names are handled properly. The code used to throw,
+// but it now returns the existing index. See #5251 for details.
+TEST(ServerHooksTest, NewDuplicateHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int index = hooks.getIndex("context_create");
+
+ // Ensure we can duplicate one of the existing names.
+ // Instead of throwing, we just check that a reasonable
+ // index has been returned.
+ EXPECT_EQ(index, hooks.registerHook("context_create"));
+
+ // Check that mutiple attempts to register the same hook will return
+ // existing index.
+ int gamma = hooks.registerHook("gamma");
+ EXPECT_EQ(2, gamma);
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+ EXPECT_EQ(gamma, hooks.registerHook("gamma"));
+}
+
+// Checks that we can get the name of the hooks.
+
+TEST(ServerHooksTest, GetHookNames) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ vector<string> expected_names;
+
+ // Add names into the hooks object and to the set of expected names.
+ expected_names.push_back("alpha");
+ expected_names.push_back("beta");
+ expected_names.push_back("gamma");
+ expected_names.push_back("delta");
+ for (size_t i = 0; i < expected_names.size(); ++i) {
+ hooks.registerHook(expected_names[i].c_str());
+ };
+
+ // Update the expected names to include the pre-defined hook names.
+ expected_names.push_back("context_create");
+ expected_names.push_back("context_destroy");
+
+ // Get the actual hook names
+ vector<string> actual_names = hooks.getHookNames();
+
+ // For comparison, sort the names into alphabetical order and do a straight
+ // vector comparison.
+ sort(expected_names.begin(), expected_names.end());
+ sort(actual_names.begin(), actual_names.end());
+
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Test the inverse hooks functionality (i.e. given an index, get the name).
+
+TEST(ServerHooksTest, GetHookIndexes) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int alpha = hooks.registerHook("alpha");
+ int beta = hooks.registerHook("beta");
+ int gamma = hooks.registerHook("gamma");
+
+ EXPECT_EQ(std::string("context_create"),
+ hooks.getName(ServerHooks::CONTEXT_CREATE));
+ EXPECT_EQ(std::string("context_destroy"),
+ hooks.getName(ServerHooks::CONTEXT_DESTROY));
+ EXPECT_EQ(std::string("alpha"), hooks.getName(alpha));
+ EXPECT_EQ(std::string("beta"), hooks.getName(beta));
+ EXPECT_EQ(std::string("gamma"), hooks.getName(gamma));
+
+ // Check for an invalid index
+ EXPECT_THROW(hooks.getName(-1), NoSuchHook);
+ EXPECT_THROW(hooks.getName(42), NoSuchHook);
+}
+
+// Test the reset functionality.
+
+TEST(ServerHooksTest, Reset) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int alpha = hooks.registerHook("alpha");
+ int beta = hooks.registerHook("beta");
+ int gamma = hooks.registerHook("gamma");
+
+ EXPECT_EQ(std::string("alpha"), hooks.getName(alpha));
+ EXPECT_EQ(std::string("beta"), hooks.getName(beta));
+ EXPECT_EQ(std::string("gamma"), hooks.getName(gamma));
+
+ // Check the counts before and after a reset.
+ EXPECT_EQ(5, hooks.getCount());
+ hooks.reset();
+ EXPECT_EQ(2, hooks.getCount());
+
+ // ... and check that the hooks are as expected.
+ EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+}
+
+// Check that getting an unknown name throws an exception.
+
+TEST(ServerHooksTest, UnknownHookName) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
+ EXPECT_EQ(-1, hooks.findIndex("unknown"));
+}
+
+// Check that the count of hooks is correct.
+
+TEST(ServerHooksTest, HookCount) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // Insert the names into the hooks object
+ hooks.registerHook("alpha");
+ hooks.registerHook("beta");
+ hooks.registerHook("gamma");
+ hooks.registerHook("delta");
+
+ // Should be two more hooks that the number we have registered.
+ EXPECT_EQ(6, hooks.getCount());
+}
+
+// Check that the hook name is correctly generated for a control command name
+// and vice versa.
+
+TEST(ServerHooksTest, CommandToHookName) {
+ EXPECT_EQ("$x_y_z", ServerHooks::commandToHookName("x-y-z"));
+ EXPECT_EQ("$foo_bar_foo", ServerHooks::commandToHookName("foo-bar_foo"));
+ EXPECT_EQ("$", ServerHooks::commandToHookName(""));
+}
+
+TEST(ServerHooksTest, HookToCommandName) {
+ // Underscores replaced by hyphens.
+ EXPECT_EQ("x-y-z", ServerHooks::hookToCommandName("$x_y_z"));
+ EXPECT_EQ("foo-bar-foo", ServerHooks::hookToCommandName("$foo_bar-foo"));
+ // Single dollar is converted to empty string.
+ EXPECT_TRUE(ServerHooks::hookToCommandName("$").empty());
+ // If no dollar, it is not a hook name. Return empty string.
+ EXPECT_TRUE(ServerHooks::hookToCommandName("abc").empty());
+}
+
+TEST(ServerHooksTest, getParkingLots) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ int alpha_hook = hooks.registerHook("alpha");
+
+ ASSERT_TRUE(hooks.getParkingLotsPtr());
+ ASSERT_TRUE(hooks.getParkingLotPtr(alpha_hook));
+ ASSERT_TRUE(hooks.getParkingLotPtr("alpha"));
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/test_libraries.h.in b/src/lib/hooks/tests/test_libraries.h.in
new file mode 100644
index 0000000..afad62f
--- /dev/null
+++ b/src/lib/hooks/tests/test_libraries.h.in
@@ -0,0 +1,61 @@
+// Copyright (C) 2013-2018 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 TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file. Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Basic library with context_create and three "standard" callouts.
+static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbcl.so";
+
+// Library with context_create and three "standard" callouts, as well as
+// load() and unload() functions.
+static const char* FULL_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libfcl.so";
+
+// Library where the all framework functions throw an exception
+static const char* FRAMEWORK_EXCEPTION_LIBRARY = "@abs_builddir@/.libs/libfxl.so";
+
+// Library where the version() function returns an incorrect result.
+static const char* INCORRECT_VERSION_LIBRARY = "@abs_builddir@/.libs/libivl.so";
+
+// Library where some of the callout registration is done with the load()
+// function.
+static const char* LOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/liblcl.so";
+
+// Library where the load() function returns an error.
+static const char* LOAD_ERROR_CALLOUT_LIBRARY =
+ "@abs_builddir@/.libs/liblecl.so";
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere.so";
+
+// Library that does not include a version function.
+static const char* NO_VERSION_LIBRARY = "@abs_builddir@/.libs/libnvl.so";
+
+// Library where there is an unload() function.
+static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl.so";
+
+// Library where parameters are checked.
+static const char* CALLOUT_PARAMS_LIBRARY = "@abs_builddir@/.libs/libpcl.so";
+
+// Library which tests objects parking.
+// Used only by hooks_manager_unittest.cc.
+#ifdef TEST_ASYNC_CALLOUT
+static const char* ASYNC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libacl.so";
+#endif
+
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/lib/hooks/tests/unload_callout_library.cc b/src/lib/hooks/tests/unload_callout_library.cc
new file mode 100644
index 0000000..a15b870
--- /dev/null
+++ b/src/lib/hooks/tests/unload_callout_library.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2013-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/.
+
+/// @file
+/// @brief Basic unload library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "unload" framework functions are supplied. "version"
+/// returns a valid value and "unload" creates a marker file and returns
+/// success.
+
+#include <config.h>
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+int
+unload() {
+ // Create the marker file.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::out);
+ marker.close();
+
+ return (0);
+}
+
+};
diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am
new file mode 100644
index 0000000..d6b428e
--- /dev/null
+++ b/src/lib/http/Makefile.am
@@ -0,0 +1,131 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST = http.dox
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST += auth_messages.mes http_messages.mes
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-http.la
+libkea_http_la_SOURCES = client.cc client.h
+libkea_http_la_SOURCES += connection.cc connection.h
+libkea_http_la_SOURCES += connection_pool.cc connection_pool.h
+libkea_http_la_SOURCES += date_time.cc date_time.h
+libkea_http_la_SOURCES += http_log.cc http_log.h
+libkea_http_la_SOURCES += header_context.h
+libkea_http_la_SOURCES += http_acceptor.h
+libkea_http_la_SOURCES += http_header.cc http_header.h
+libkea_http_la_SOURCES += http_message.cc http_message.h
+libkea_http_la_SOURCES += http_message_parser_base.cc http_message_parser_base.h
+libkea_http_la_SOURCES += http_messages.cc http_messages.h
+libkea_http_la_SOURCES += http_types.h
+libkea_http_la_SOURCES += listener.cc listener.h
+libkea_http_la_SOURCES += listener_impl.cc listener_impl.h
+libkea_http_la_SOURCES += post_request.cc post_request.h
+libkea_http_la_SOURCES += post_request_json.cc post_request_json.h
+libkea_http_la_SOURCES += request.cc request.h
+libkea_http_la_SOURCES += request_context.h
+libkea_http_la_SOURCES += request_parser.cc request_parser.h
+libkea_http_la_SOURCES += response.cc response.h
+libkea_http_la_SOURCES += response_parser.cc response_parser.h
+libkea_http_la_SOURCES += response_context.h
+libkea_http_la_SOURCES += response_creator.cc response_creator.h
+libkea_http_la_SOURCES += response_creator_factory.h
+libkea_http_la_SOURCES += response_json.cc response_json.h
+libkea_http_la_SOURCES += url.cc url.h
+libkea_http_la_SOURCES += auth_config.h
+libkea_http_la_SOURCES += auth_log.cc auth_log.h
+libkea_http_la_SOURCES += auth_messages.cc auth_messages.h
+libkea_http_la_SOURCES += basic_auth_config.cc basic_auth_config.h
+libkea_http_la_SOURCES += basic_auth.cc basic_auth.h
+
+libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_http_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_http_la_LDFLAGS += -no-undefined -version-info 56:0:0
+
+libkea_http_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_http_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_http_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f auth_messages.cc auth_messages.h
+ rm -f http_messages.h http_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: auth_messages.cc auth_messages.h http_messages.h http_messages.cc
+ @echo Message files regenerated
+
+auth_messages.cc auth_messages.h: auth_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/auth_messages.mes
+
+http_messages.h http_messages.cc: http_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/http_messages.mes
+
+else
+
+messages http_messages.h http_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_http_includedir = $(pkgincludedir)/http
+libkea_http_include_HEADERS = \
+ auth_config.h \
+ auth_log.h \
+ auth_messages.h \
+ basic_auth.h \
+ basic_auth_config.h \
+ client.h \
+ connection.h \
+ connection_pool.h \
+ date_time.h \
+ header_context.h \
+ http_acceptor.h \
+ http_header.h \
+ http_log.h \
+ http_message.h \
+ http_message_parser_base.h \
+ http_messages.h \
+ http_types.h \
+ listener.h \
+ listener_impl.h \
+ post_request.h \
+ post_request_json.h \
+ request.h \
+ request_context.h \
+ request_parser.h \
+ response.h \
+ response_context.h \
+ response_creator.h \
+ response_creator_factory.h \
+ response_json.h \
+ response_parser.h \
+ url.h
+
diff --git a/src/lib/http/Makefile.in b/src/lib/http/Makefile.in
new file mode 100644
index 0000000..b4a6f1a
--- /dev/null
+++ b/src/lib/http/Makefile.in
@@ -0,0 +1,1310 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/http
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_http_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)$(libkea_http_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_http_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_http_la_OBJECTS = libkea_http_la-client.lo \
+ libkea_http_la-connection.lo libkea_http_la-connection_pool.lo \
+ libkea_http_la-date_time.lo libkea_http_la-http_log.lo \
+ libkea_http_la-http_header.lo libkea_http_la-http_message.lo \
+ libkea_http_la-http_message_parser_base.lo \
+ libkea_http_la-http_messages.lo libkea_http_la-listener.lo \
+ libkea_http_la-listener_impl.lo libkea_http_la-post_request.lo \
+ libkea_http_la-post_request_json.lo libkea_http_la-request.lo \
+ libkea_http_la-request_parser.lo libkea_http_la-response.lo \
+ libkea_http_la-response_parser.lo \
+ libkea_http_la-response_creator.lo \
+ libkea_http_la-response_json.lo libkea_http_la-url.lo \
+ libkea_http_la-auth_log.lo libkea_http_la-auth_messages.lo \
+ libkea_http_la-basic_auth_config.lo \
+ libkea_http_la-basic_auth.lo
+libkea_http_la_OBJECTS = $(am_libkea_http_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_http_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_http_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_http_la-auth_log.Plo \
+ ./$(DEPDIR)/libkea_http_la-auth_messages.Plo \
+ ./$(DEPDIR)/libkea_http_la-basic_auth.Plo \
+ ./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo \
+ ./$(DEPDIR)/libkea_http_la-client.Plo \
+ ./$(DEPDIR)/libkea_http_la-connection.Plo \
+ ./$(DEPDIR)/libkea_http_la-connection_pool.Plo \
+ ./$(DEPDIR)/libkea_http_la-date_time.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_header.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_log.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_message.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo \
+ ./$(DEPDIR)/libkea_http_la-http_messages.Plo \
+ ./$(DEPDIR)/libkea_http_la-listener.Plo \
+ ./$(DEPDIR)/libkea_http_la-listener_impl.Plo \
+ ./$(DEPDIR)/libkea_http_la-post_request.Plo \
+ ./$(DEPDIR)/libkea_http_la-post_request_json.Plo \
+ ./$(DEPDIR)/libkea_http_la-request.Plo \
+ ./$(DEPDIR)/libkea_http_la-request_parser.Plo \
+ ./$(DEPDIR)/libkea_http_la-response.Plo \
+ ./$(DEPDIR)/libkea_http_la-response_creator.Plo \
+ ./$(DEPDIR)/libkea_http_la-response_json.Plo \
+ ./$(DEPDIR)/libkea_http_la-response_parser.Plo \
+ ./$(DEPDIR)/libkea_http_la-url.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_http_la_SOURCES)
+DIST_SOURCES = $(libkea_http_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 = $(libkea_http_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = http.dox auth_messages.mes http_messages.mes
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-http.la
+libkea_http_la_SOURCES = client.cc client.h connection.cc connection.h \
+ connection_pool.cc connection_pool.h date_time.cc date_time.h \
+ http_log.cc http_log.h header_context.h http_acceptor.h \
+ http_header.cc http_header.h http_message.cc http_message.h \
+ http_message_parser_base.cc http_message_parser_base.h \
+ http_messages.cc http_messages.h http_types.h listener.cc \
+ listener.h listener_impl.cc listener_impl.h post_request.cc \
+ post_request.h post_request_json.cc post_request_json.h \
+ request.cc request.h request_context.h request_parser.cc \
+ request_parser.h response.cc response.h response_parser.cc \
+ response_parser.h response_context.h response_creator.cc \
+ response_creator.h response_creator_factory.h response_json.cc \
+ response_json.h url.cc url.h auth_config.h auth_log.cc \
+ auth_log.h auth_messages.cc auth_messages.h \
+ basic_auth_config.cc basic_auth_config.h basic_auth.cc \
+ basic_auth.h
+libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_http_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info \
+ 56:0:0
+libkea_http_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_http_includedir = $(pkgincludedir)/http
+libkea_http_include_HEADERS = \
+ auth_config.h \
+ auth_log.h \
+ auth_messages.h \
+ basic_auth.h \
+ basic_auth_config.h \
+ client.h \
+ connection.h \
+ connection_pool.h \
+ date_time.h \
+ header_context.h \
+ http_acceptor.h \
+ http_header.h \
+ http_log.h \
+ http_message.h \
+ http_message_parser_base.h \
+ http_messages.h \
+ http_types.h \
+ listener.h \
+ listener_impl.h \
+ post_request.h \
+ post_request_json.h \
+ request.h \
+ request_context.h \
+ request_parser.h \
+ response.h \
+ response_context.h \
+ response_creator.h \
+ response_creator_factory.h \
+ response_json.h \
+ response_parser.h \
+ url.h
+
+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/http/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/http/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-http.la: $(libkea_http_la_OBJECTS) $(libkea_http_la_DEPENDENCIES) $(EXTRA_libkea_http_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_http_la_LINK) -rpath $(libdir) $(libkea_http_la_OBJECTS) $(libkea_http_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-auth_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-auth_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-basic_auth.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-connection_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-date_time.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_header.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_message.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-http_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-listener.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-listener_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-post_request.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-post_request_json.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-request.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-request_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response_creator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response_json.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-response_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_http_la-url.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)$(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 $@ $<
+
+libkea_http_la-client.lo: client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-client.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-client.Tpo -c -o libkea_http_la-client.lo `test -f 'client.cc' || echo '$(srcdir)/'`client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-client.Tpo $(DEPDIR)/libkea_http_la-client.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client.cc' object='libkea_http_la-client.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-client.lo `test -f 'client.cc' || echo '$(srcdir)/'`client.cc
+
+libkea_http_la-connection.lo: connection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-connection.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-connection.Tpo -c -o libkea_http_la-connection.lo `test -f 'connection.cc' || echo '$(srcdir)/'`connection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-connection.Tpo $(DEPDIR)/libkea_http_la-connection.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection.cc' object='libkea_http_la-connection.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-connection.lo `test -f 'connection.cc' || echo '$(srcdir)/'`connection.cc
+
+libkea_http_la-connection_pool.lo: connection_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-connection_pool.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-connection_pool.Tpo -c -o libkea_http_la-connection_pool.lo `test -f 'connection_pool.cc' || echo '$(srcdir)/'`connection_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-connection_pool.Tpo $(DEPDIR)/libkea_http_la-connection_pool.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection_pool.cc' object='libkea_http_la-connection_pool.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-connection_pool.lo `test -f 'connection_pool.cc' || echo '$(srcdir)/'`connection_pool.cc
+
+libkea_http_la-date_time.lo: date_time.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-date_time.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-date_time.Tpo -c -o libkea_http_la-date_time.lo `test -f 'date_time.cc' || echo '$(srcdir)/'`date_time.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-date_time.Tpo $(DEPDIR)/libkea_http_la-date_time.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='date_time.cc' object='libkea_http_la-date_time.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-date_time.lo `test -f 'date_time.cc' || echo '$(srcdir)/'`date_time.cc
+
+libkea_http_la-http_log.lo: http_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_log.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_log.Tpo -c -o libkea_http_la-http_log.lo `test -f 'http_log.cc' || echo '$(srcdir)/'`http_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_log.Tpo $(DEPDIR)/libkea_http_la-http_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_log.cc' object='libkea_http_la-http_log.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_log.lo `test -f 'http_log.cc' || echo '$(srcdir)/'`http_log.cc
+
+libkea_http_la-http_header.lo: http_header.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_header.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_header.Tpo -c -o libkea_http_la-http_header.lo `test -f 'http_header.cc' || echo '$(srcdir)/'`http_header.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_header.Tpo $(DEPDIR)/libkea_http_la-http_header.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_header.cc' object='libkea_http_la-http_header.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_header.lo `test -f 'http_header.cc' || echo '$(srcdir)/'`http_header.cc
+
+libkea_http_la-http_message.lo: http_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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_message.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_message.Tpo -c -o libkea_http_la-http_message.lo `test -f 'http_message.cc' || echo '$(srcdir)/'`http_message.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_message.Tpo $(DEPDIR)/libkea_http_la-http_message.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_message.cc' object='libkea_http_la-http_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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_message.lo `test -f 'http_message.cc' || echo '$(srcdir)/'`http_message.cc
+
+libkea_http_la-http_message_parser_base.lo: http_message_parser_base.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_message_parser_base.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_message_parser_base.Tpo -c -o libkea_http_la-http_message_parser_base.lo `test -f 'http_message_parser_base.cc' || echo '$(srcdir)/'`http_message_parser_base.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_message_parser_base.Tpo $(DEPDIR)/libkea_http_la-http_message_parser_base.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_message_parser_base.cc' object='libkea_http_la-http_message_parser_base.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_message_parser_base.lo `test -f 'http_message_parser_base.cc' || echo '$(srcdir)/'`http_message_parser_base.cc
+
+libkea_http_la-http_messages.lo: http_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-http_messages.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-http_messages.Tpo -c -o libkea_http_la-http_messages.lo `test -f 'http_messages.cc' || echo '$(srcdir)/'`http_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-http_messages.Tpo $(DEPDIR)/libkea_http_la-http_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_messages.cc' object='libkea_http_la-http_messages.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-http_messages.lo `test -f 'http_messages.cc' || echo '$(srcdir)/'`http_messages.cc
+
+libkea_http_la-listener.lo: listener.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-listener.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-listener.Tpo -c -o libkea_http_la-listener.lo `test -f 'listener.cc' || echo '$(srcdir)/'`listener.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-listener.Tpo $(DEPDIR)/libkea_http_la-listener.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='listener.cc' object='libkea_http_la-listener.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-listener.lo `test -f 'listener.cc' || echo '$(srcdir)/'`listener.cc
+
+libkea_http_la-listener_impl.lo: listener_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-listener_impl.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-listener_impl.Tpo -c -o libkea_http_la-listener_impl.lo `test -f 'listener_impl.cc' || echo '$(srcdir)/'`listener_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-listener_impl.Tpo $(DEPDIR)/libkea_http_la-listener_impl.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='listener_impl.cc' object='libkea_http_la-listener_impl.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-listener_impl.lo `test -f 'listener_impl.cc' || echo '$(srcdir)/'`listener_impl.cc
+
+libkea_http_la-post_request.lo: post_request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-post_request.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-post_request.Tpo -c -o libkea_http_la-post_request.lo `test -f 'post_request.cc' || echo '$(srcdir)/'`post_request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-post_request.Tpo $(DEPDIR)/libkea_http_la-post_request.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request.cc' object='libkea_http_la-post_request.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-post_request.lo `test -f 'post_request.cc' || echo '$(srcdir)/'`post_request.cc
+
+libkea_http_la-post_request_json.lo: post_request_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-post_request_json.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-post_request_json.Tpo -c -o libkea_http_la-post_request_json.lo `test -f 'post_request_json.cc' || echo '$(srcdir)/'`post_request_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-post_request_json.Tpo $(DEPDIR)/libkea_http_la-post_request_json.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_json.cc' object='libkea_http_la-post_request_json.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-post_request_json.lo `test -f 'post_request_json.cc' || echo '$(srcdir)/'`post_request_json.cc
+
+libkea_http_la-request.lo: request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-request.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-request.Tpo -c -o libkea_http_la-request.lo `test -f 'request.cc' || echo '$(srcdir)/'`request.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-request.Tpo $(DEPDIR)/libkea_http_la-request.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request.cc' object='libkea_http_la-request.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-request.lo `test -f 'request.cc' || echo '$(srcdir)/'`request.cc
+
+libkea_http_la-request_parser.lo: request_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-request_parser.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-request_parser.Tpo -c -o libkea_http_la-request_parser.lo `test -f 'request_parser.cc' || echo '$(srcdir)/'`request_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-request_parser.Tpo $(DEPDIR)/libkea_http_la-request_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_parser.cc' object='libkea_http_la-request_parser.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-request_parser.lo `test -f 'request_parser.cc' || echo '$(srcdir)/'`request_parser.cc
+
+libkea_http_la-response.lo: response.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response.Tpo -c -o libkea_http_la-response.lo `test -f 'response.cc' || echo '$(srcdir)/'`response.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response.Tpo $(DEPDIR)/libkea_http_la-response.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response.cc' object='libkea_http_la-response.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response.lo `test -f 'response.cc' || echo '$(srcdir)/'`response.cc
+
+libkea_http_la-response_parser.lo: response_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response_parser.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response_parser.Tpo -c -o libkea_http_la-response_parser.lo `test -f 'response_parser.cc' || echo '$(srcdir)/'`response_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response_parser.Tpo $(DEPDIR)/libkea_http_la-response_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_parser.cc' object='libkea_http_la-response_parser.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response_parser.lo `test -f 'response_parser.cc' || echo '$(srcdir)/'`response_parser.cc
+
+libkea_http_la-response_creator.lo: response_creator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response_creator.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response_creator.Tpo -c -o libkea_http_la-response_creator.lo `test -f 'response_creator.cc' || echo '$(srcdir)/'`response_creator.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response_creator.Tpo $(DEPDIR)/libkea_http_la-response_creator.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_creator.cc' object='libkea_http_la-response_creator.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response_creator.lo `test -f 'response_creator.cc' || echo '$(srcdir)/'`response_creator.cc
+
+libkea_http_la-response_json.lo: response_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-response_json.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-response_json.Tpo -c -o libkea_http_la-response_json.lo `test -f 'response_json.cc' || echo '$(srcdir)/'`response_json.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-response_json.Tpo $(DEPDIR)/libkea_http_la-response_json.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_json.cc' object='libkea_http_la-response_json.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-response_json.lo `test -f 'response_json.cc' || echo '$(srcdir)/'`response_json.cc
+
+libkea_http_la-url.lo: url.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-url.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-url.Tpo -c -o libkea_http_la-url.lo `test -f 'url.cc' || echo '$(srcdir)/'`url.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-url.Tpo $(DEPDIR)/libkea_http_la-url.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='url.cc' object='libkea_http_la-url.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-url.lo `test -f 'url.cc' || echo '$(srcdir)/'`url.cc
+
+libkea_http_la-auth_log.lo: auth_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-auth_log.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-auth_log.Tpo -c -o libkea_http_la-auth_log.lo `test -f 'auth_log.cc' || echo '$(srcdir)/'`auth_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-auth_log.Tpo $(DEPDIR)/libkea_http_la-auth_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='auth_log.cc' object='libkea_http_la-auth_log.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-auth_log.lo `test -f 'auth_log.cc' || echo '$(srcdir)/'`auth_log.cc
+
+libkea_http_la-auth_messages.lo: auth_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-auth_messages.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-auth_messages.Tpo -c -o libkea_http_la-auth_messages.lo `test -f 'auth_messages.cc' || echo '$(srcdir)/'`auth_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-auth_messages.Tpo $(DEPDIR)/libkea_http_la-auth_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='auth_messages.cc' object='libkea_http_la-auth_messages.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-auth_messages.lo `test -f 'auth_messages.cc' || echo '$(srcdir)/'`auth_messages.cc
+
+libkea_http_la-basic_auth_config.lo: basic_auth_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-basic_auth_config.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-basic_auth_config.Tpo -c -o libkea_http_la-basic_auth_config.lo `test -f 'basic_auth_config.cc' || echo '$(srcdir)/'`basic_auth_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-basic_auth_config.Tpo $(DEPDIR)/libkea_http_la-basic_auth_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_config.cc' object='libkea_http_la-basic_auth_config.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-basic_auth_config.lo `test -f 'basic_auth_config.cc' || echo '$(srcdir)/'`basic_auth_config.cc
+
+libkea_http_la-basic_auth.lo: basic_auth.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_http_la-basic_auth.lo -MD -MP -MF $(DEPDIR)/libkea_http_la-basic_auth.Tpo -c -o libkea_http_la-basic_auth.lo `test -f 'basic_auth.cc' || echo '$(srcdir)/'`basic_auth.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_http_la-basic_auth.Tpo $(DEPDIR)/libkea_http_la-basic_auth.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth.cc' object='libkea_http_la-basic_auth.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_http_la_CPPFLAGS) $(CPPFLAGS) $(libkea_http_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_http_la-basic_auth.lo `test -f 'basic_auth.cc' || echo '$(srcdir)/'`basic_auth.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_http_includeHEADERS: $(libkea_http_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_http_include_HEADERS)'; test -n "$(libkea_http_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_http_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_http_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)$(libkea_http_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_http_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_http_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_http_include_HEADERS)'; test -n "$(libkea_http_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_http_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_http_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_http_la-auth_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-auth_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-client.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-date_time.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_header.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_creator.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-url.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-libkea_http_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_http_la-auth_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-auth_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-basic_auth_config.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-client.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-connection_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-date_time.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_header.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_message_parser_base.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-http_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-listener_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-post_request_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-request_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_creator.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_json.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-response_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_http_la-url.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_http_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_http_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_http_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f auth_messages.cc auth_messages.h
+ rm -f http_messages.h http_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: auth_messages.cc auth_messages.h http_messages.h http_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@auth_messages.cc auth_messages.h: auth_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/auth_messages.mes
+
+@GENERATE_MESSAGES_TRUE@http_messages.h http_messages.cc: http_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/http/http_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages http_messages.h http_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/http/auth_config.h b/src/lib/http/auth_config.h
new file mode 100644
index 0000000..d8f74eb
--- /dev/null
+++ b/src/lib/http/auth_config.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2020-2022 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 HTTP_AUTH_CONFIG_H
+#define HTTP_AUTH_CONFIG_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/user_context.h>
+#include <http/request.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Base type of HTTP authentication configuration.
+class HttpAuthConfig : public isc::data::UserContext,
+ public isc::data::CfgToElement {
+public:
+
+ /// @brief Destructor.
+ virtual ~HttpAuthConfig() { }
+
+ /// @brief Set the realm.
+ ///
+ /// @param realm New realm.
+ void setRealm(const std::string& realm) {
+ realm_ = realm;
+ }
+
+ /// @brief Returns the realm.
+ ///
+ /// @return The HTTP authentication realm.
+ const std::string& getRealm() const {
+ return (realm_);
+ }
+
+ /// @brief Set the common part for file paths (usually a directory).
+ ///
+ /// @param directory New directory.
+ void setDirectory(const std::string& directory) {
+ directory_ = directory;
+ }
+
+ /// @brief Returns the common part for file paths (usually a directory).
+ ///
+ /// @return The common part for file paths (usually a directory).
+ const std::string& getDirectory() const {
+ return (directory_);
+ }
+
+ /// @brief Empty predicate.
+ ///
+ /// @return true if the configuration is empty so authentication
+ /// is not required.
+ virtual bool empty() const = 0;
+
+ /// @brief Clear configuration.
+ virtual void clear() = 0;
+
+ /// @brief Parses HTTP authentication configuration.
+ ///
+ /// @param config Element holding the basic HTTP authentication
+ /// configuration to be parsed.
+ /// @throw DhcpConfigError when the configuration is invalid.
+ virtual void parse(const isc::data::ConstElementPtr& config) = 0;
+
+ /// @brief Unparses HTTP authentication configuration.
+ ///
+ /// @return A pointer to unparsed HTTP authentication configuration.
+ virtual isc::data::ElementPtr toElement() const = 0;
+
+ /// @brief Validate HTTP request.
+ ///
+ /// @param creator The HTTP response creator.
+ /// @param request The HTTP request to validate.
+ /// @return Error HTTP response if validation failed, null otherwise.
+ virtual isc::http::HttpResponseJsonPtr
+ checkAuth(const isc::http::HttpResponseCreator& creator,
+ const isc::http::HttpRequestPtr& request) const = 0;
+
+private:
+
+ /// @brief The realm.
+ std::string realm_;
+
+ /// @brief Common part for file paths (usually a directory).
+ std::string directory_;
+};
+
+/// @brief Type of shared pointers to HTTP authentication configuration.
+typedef boost::shared_ptr<HttpAuthConfig> HttpAuthConfigPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif HTTP_AUTH_CONFIG_H
diff --git a/src/lib/http/auth_log.cc b/src/lib/http/auth_log.cc
new file mode 100644
index 0000000..d2c805c
--- /dev/null
+++ b/src/lib/http/auth_log.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2021 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/.
+
+/// Defines the logger used by the HTTP authentication.
+
+#include <config.h>
+
+#include <http/auth_log.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Defines the logger used by the HTTP authentication.
+isc::log::Logger auth_logger("auth");
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/auth_log.h b/src/lib/http/auth_log.h
new file mode 100644
index 0000000..8ebf5c3
--- /dev/null
+++ b/src/lib/http/auth_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 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 AUTH_LOG_H
+#define AUTH_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <http/auth_messages.h>
+
+namespace isc {
+namespace http {
+
+/// Define the HTTP authentication logger.
+extern isc::log::Logger auth_logger;
+
+} // namespace http
+} // namespace isc
+
+#endif // AUTH_LOG_H
diff --git a/src/lib/http/auth_messages.cc b/src/lib/http/auth_messages.cc
new file mode 100644
index 0000000..ebf9da5
--- /dev/null
+++ b/src/lib/http/auth_messages.cc
@@ -0,0 +1,31 @@
+// File created from ../../../src/lib/http/auth_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_AUTHORIZED = "HTTP_CLIENT_REQUEST_AUTHORIZED";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER = "HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NOT_AUTHORIZED = "HTTP_CLIENT_REQUEST_NOT_AUTHORIZED";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NO_AUTH_HEADER = "HTTP_CLIENT_REQUEST_NO_AUTH_HEADER";
+
+} // namespace http
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "HTTP_CLIENT_REQUEST_AUTHORIZED", "received HTTP request authorized for '%1'",
+ "HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER", "received HTTP request with malformed authentication header: %1",
+ "HTTP_CLIENT_REQUEST_NOT_AUTHORIZED", "received HTTP request with not matching authentication header",
+ "HTTP_CLIENT_REQUEST_NO_AUTH_HEADER", "received HTTP request without required authentication header",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/http/auth_messages.h b/src/lib/http/auth_messages.h
new file mode 100644
index 0000000..ff02ef5
--- /dev/null
+++ b/src/lib/http/auth_messages.h
@@ -0,0 +1,19 @@
+// File created from ../../../src/lib/http/auth_messages.mes
+
+#ifndef AUTH_MESSAGES_H
+#define AUTH_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_AUTHORIZED;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NOT_AUTHORIZED;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_NO_AUTH_HEADER;
+
+} // namespace http
+} // namespace isc
+
+#endif // AUTH_MESSAGES_H
diff --git a/src/lib/http/auth_messages.mes b/src/lib/http/auth_messages.mes
new file mode 100644
index 0000000..685bdb3
--- /dev/null
+++ b/src/lib/http/auth_messages.mes
@@ -0,0 +1,24 @@
+# Copyright (C) 2021 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/.
+
+$NAMESPACE isc::http
+
+% HTTP_CLIENT_REQUEST_AUTHORIZED received HTTP request authorized for '%1'
+This information message is issued when the server receives with a matching
+authentication header. The argument provides the user id.
+
+% HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request with malformed authentication header: %1
+This information message is issued when the server receives a request with
+a malformed authentication header. The argument explains the problem.
+
+% HTTP_CLIENT_REQUEST_NOT_AUTHORIZED received HTTP request with not matching authentication header
+This information message is issued when the server receives a request with
+authentication header carrying not recognized credential: the user
+provided incorrect user id and/or password.
+
+% HTTP_CLIENT_REQUEST_NO_AUTH_HEADER received HTTP request without required authentication header
+This information message is issued when the server receives a request without
+a required authentication header.
diff --git a/src/lib/http/basic_auth.cc b/src/lib/http/basic_auth.cc
new file mode 100644
index 0000000..f3a1d87
--- /dev/null
+++ b/src/lib/http/basic_auth.cc
@@ -0,0 +1,45 @@
+// Copyright (C) 2020 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 <http/basic_auth.h>
+#include <util/encode/base64.h>
+#include <util/encode/utf8.h>
+
+using namespace isc::util::encode;
+using namespace std;
+
+namespace isc {
+namespace http {
+
+BasicHttpAuth::BasicHttpAuth(const std::string& user,
+ const std::string& password)
+ : user_(user), password_(password) {
+ if (user.find(':') != string::npos) {
+ isc_throw(BadValue, "user '" << user << "' must not contain a ':'");
+ }
+ buildSecret();
+ buildCredential();
+}
+
+BasicHttpAuth::BasicHttpAuth(const std::string& secret) : secret_(secret) {
+ if (secret.find(':') == string::npos) {
+ isc_throw(BadValue, "secret '" << secret << "' must contain a ':");
+ }
+ buildCredential();
+}
+
+void BasicHttpAuth::buildSecret() {
+ secret_ = user_ + ":" + password_;
+}
+
+void BasicHttpAuth::buildCredential() {
+ credential_ = encodeBase64(encodeUtf8(secret_));
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/basic_auth.h b/src/lib/http/basic_auth.h
new file mode 100644
index 0000000..45301af
--- /dev/null
+++ b/src/lib/http/basic_auth.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2020-2022 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 BASIC_HTTP_AUTH_H
+#define BASIC_HTTP_AUTH_H
+
+#include <http/header_context.h>
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <unordered_set>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents a basic HTTP authentication.
+///
+/// It computes the credential from user and password.
+class BasicHttpAuth {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param user User id
+ /// @param password Password
+ /// @throw BadValue if user contains the ':' character.
+ BasicHttpAuth(const std::string& user, const std::string& password);
+
+ /// @brief Constructor.
+ ///
+ /// @param secret user:password string
+ /// @throw BadValue if secret does not contain the ':' character.
+ BasicHttpAuth(const std::string& secret);
+
+ /// @brief Returns the secret.
+ const std::string& getSecret() const {
+ return (secret_);
+ }
+
+ /// @brief Returns the credential (base64 of the UTF-8 secret).
+ const std::string& getCredential() const {
+ return (credential_);
+ }
+
+private:
+
+ /// @brief Build the secret from user and password.
+ void buildSecret();
+
+ /// @brief Build the credential from the secret.
+ void buildCredential();
+
+ /// @brief User id e.g. johndoe.
+ std::string user_;
+
+ /// @brief Password e.g. secret1.
+ std::string password_;
+
+ /// @brief Secret e.g. johndoe:secret1.
+ std::string secret_;
+
+ /// @brief Credential: base64 encoding of UTF-8 secret,
+ /// e.g. am9obmRvZTpzZWNyZXQx.
+ std::string credential_;
+};
+
+/// @brief Type of pointers to basic HTTP authentication objects.
+typedef boost::shared_ptr<BasicHttpAuth> BasicHttpAuthPtr;
+
+/// @brief Represents basic HTTP authentication header.
+struct BasicAuthHttpHeaderContext : public HttpHeaderContext {
+
+ /// @brief Constructor.
+ ///
+ /// @param basic_auth Basic HTTP authentication object.
+ explicit BasicAuthHttpHeaderContext(const BasicHttpAuth& basic_auth)
+ : HttpHeaderContext("Authorization",
+ "Basic " + basic_auth.getCredential()) {
+ }
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif BASIC_HTTP_AUTH_H
diff --git a/src/lib/http/basic_auth_config.cc b/src/lib/http/basic_auth_config.cc
new file mode 100644
index 0000000..0c98a4a
--- /dev/null
+++ b/src/lib/http/basic_auth_config.cc
@@ -0,0 +1,399 @@
+// Copyright (C) 2020-2022 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 <http/auth_log.h>
+#include <http/basic_auth_config.h>
+#include <util/file_utilities.h>
+#include <util/strutil.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace http {
+
+BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user,
+ const std::string& password,
+ const isc::data::ConstElementPtr& user_context)
+ : user_(user), user_file_(""), password_(password),
+ password_file_(""), password_file_only_(false) {
+ if (user_context) {
+ setContext(user_context);
+ }
+}
+
+BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only,
+ const isc::data::ConstElementPtr& user_context)
+ : user_(user), user_file_(user_file), password_(password),
+ password_file_(password_file), password_file_only_(password_file_only) {
+ if (user_context) {
+ setContext(user_context);
+ }
+}
+
+ElementPtr
+BasicHttpAuthClient::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set user-context
+ contextToElement(result);
+
+ // Set password file or password.
+ if (!password_file_.empty()) {
+ result->set("password-file", Element::create(password_file_));
+ } else {
+ result->set("password", Element::create(password_));
+ }
+
+ // Set user-file or user.
+ if (!password_file_only_) {
+ if (!user_file_.empty()) {
+ result->set("user-file", Element::create(user_file_));
+ } else {
+ result->set("user", Element::create(user_));
+ }
+ }
+
+ return (result);
+}
+
+void
+BasicHttpAuthConfig::add(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only,
+ const ConstElementPtr& user_context) {
+ BasicHttpAuth basic_auth(user, password);
+ list_.push_back(BasicHttpAuthClient(user, user_file, password,
+ password_file, password_file_only,
+ user_context));
+ map_[basic_auth.getCredential()] = user;
+}
+
+void
+BasicHttpAuthConfig::clear() {
+ list_.clear();
+ map_.clear();
+}
+
+bool
+BasicHttpAuthConfig::empty() const {
+ return (map_.empty());
+}
+
+string
+BasicHttpAuthConfig::getFileContent(const std::string& file_name) const {
+ // Build path.
+ string path = getDirectory();
+ // Add a trailing '/' if the last character is not already a '/'.
+ if (path.empty() || (path[path.size() - 1] != '/')) {
+ path += "/";
+ }
+ // Don't add a second '/'.
+ if (file_name.empty() || (file_name[0] != '/')) {
+ path += file_name;
+ } else {
+ path += file_name.substr(1);
+ }
+
+ try {
+ return (file::getContent(path));
+ } catch (const isc::BadValue& ex) {
+ isc_throw(DhcpConfigError, ex.what());
+ }
+}
+
+ElementPtr
+BasicHttpAuthConfig::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set user-context
+ contextToElement(result);
+
+ // Set type
+ result->set("type", Element::create(string("basic")));
+
+ // Set realm
+ result->set("realm", Element::create(getRealm()));
+
+ // Set directory.
+ result->set("directory", Element::create(getDirectory()));
+
+ // Set clients
+ ElementPtr clients = Element::createList();
+ for (auto client : list_) {
+ clients->add(client.toElement());
+ }
+ result->set("clients", clients);
+
+ return (result);
+}
+
+void
+BasicHttpAuthConfig::parse(const ConstElementPtr& config) {
+ if (!config) {
+ return;
+ }
+ if (config->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "authentication must be a map ("
+ << config->getPosition() << ")");
+ }
+
+ // Get and verify the type.
+ ConstElementPtr type = config->get("type");
+ if (!type) {
+ isc_throw(DhcpConfigError, "type is required in authentication ("
+ << config->getPosition() << ")");
+ }
+ if (type->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "type must be a string ("
+ << type->getPosition() << ")");
+ }
+ if (type->stringValue() != "basic") {
+ isc_throw(DhcpConfigError, "only basic HTTP authentication is "
+ << "supported: type is '" << type->stringValue()
+ << "' not 'basic' (" << type->getPosition() << ")");
+ }
+
+ // Get the realm.
+ ConstElementPtr realm = config->get("realm");
+ if (realm) {
+ if (realm->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "realm must be a string ("
+ << realm->getPosition() << ")");
+ }
+ setRealm(realm->stringValue());
+ }
+
+ // Get the directory.
+ ConstElementPtr directory = config->get("directory");
+ if (directory) {
+ if (directory->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "directory must be a string ("
+ << directory->getPosition() << ")");
+ }
+ setDirectory(directory->stringValue());
+ }
+
+ // Get user context.
+ ConstElementPtr user_context_cfg = config->get("user-context");
+ if (user_context_cfg) {
+ if (user_context_cfg->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "user-context must be a map ("
+ << user_context_cfg->getPosition() << ")");
+ }
+ setContext(user_context_cfg);
+ }
+
+ // Get clients.
+ ConstElementPtr clients = config->get("clients");
+ if (!clients) {
+ return;
+ }
+ if (clients->getType() != Element::list) {
+ isc_throw(DhcpConfigError, "clients must be a list ("
+ << clients->getPosition() << ")");
+ }
+
+ // Iterate on clients.
+ for (auto client : clients->listValue()) {
+ if (client->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "clients items must be maps ("
+ << client->getPosition() << ")");
+ }
+
+ // password.
+ string password;
+ ConstElementPtr password_cfg = client->get("password");
+ if (password_cfg) {
+ if (password_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "password must be a string ("
+ << password_cfg->getPosition() << ")");
+ }
+ password = password_cfg->stringValue();
+ }
+
+ // password file.
+ string password_file;
+ ConstElementPtr password_file_cfg = client->get("password-file");
+ if (password_file_cfg) {
+ if (password_cfg) {
+ isc_throw(DhcpConfigError, "password ("
+ << password_cfg->getPosition()
+ << ") and password-file ("
+ << password_file_cfg->getPosition()
+ << ") are mutually exclusive");
+ }
+ if (password_file_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "password-file must be a string ("
+ << password_file_cfg->getPosition() << ")");
+ }
+ password_file = password_file_cfg->stringValue();
+ }
+
+ ConstElementPtr user_cfg = client->get("user");
+ ConstElementPtr user_file_cfg = client->get("user-file");
+ bool password_file_only = false;
+ if (!user_cfg && !user_file_cfg) {
+ if (password_file_cfg) {
+ password_file_only = true;
+ } else {
+ isc_throw(DhcpConfigError, "user is required in clients "
+ << "items (" << client->getPosition() << ")");
+ }
+ }
+
+ // user.
+ string user;
+ if (user_cfg) {
+ if (user_file_cfg) {
+ isc_throw(DhcpConfigError, "user (" << user_cfg->getPosition()
+ << ") and user-file ("
+ << user_file_cfg->getPosition()
+ << ") are mutually exclusive");
+ }
+ if (user_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "user must be a string ("
+ << user_cfg->getPosition() << ")");
+ }
+ user = user_cfg->stringValue();
+ if (user.empty()) {
+ isc_throw(DhcpConfigError, "user must not be empty ("
+ << user_cfg->getPosition() << ")");
+ }
+ if (user.find(':') != string::npos) {
+ isc_throw(DhcpConfigError, "user must not contain a ':': '"
+ << user << "' (" << user_cfg->getPosition() << ")");
+ }
+ }
+
+ // user file.
+ string user_file;
+ if (user_file_cfg) {
+ if (user_file_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "user-file must be a string ("
+ << user_file_cfg->getPosition() << ")");
+ }
+ user_file = user_file_cfg->stringValue();
+ user = getFileContent(user_file);
+ if (user.empty()) {
+ isc_throw(DhcpConfigError, "user must not be empty "
+ << "from user-file '" << user_file << "' ("
+ << user_file_cfg->getPosition() << ")");
+ }
+ if (user.find(':') != string::npos) {
+ isc_throw(DhcpConfigError, "user must not contain a ':' "
+ << "from user-file '" << user_file << "' ("
+ << user_file_cfg->getPosition() << ")");
+ }
+ }
+
+ // Solve password file.
+ if (password_file_cfg) {
+ if (password_file_only) {
+ string content = getFileContent(password_file);
+ auto pos = content.find(':');
+ if (pos == string::npos) {
+ isc_throw(DhcpConfigError, "can't find the user id part "
+ << "in password-file '" << password_file << "' ("
+ << password_file_cfg->getPosition() << ")");
+ }
+ user = content.substr(0, pos);
+ password = content.substr(pos + 1);
+ } else {
+ password = getFileContent(password_file);
+ }
+ }
+
+ // user context.
+ ConstElementPtr user_context = client->get("user-context");
+ if (user_context) {
+ if (user_context->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "user-context must be a map ("
+ << user_context->getPosition() << ")");
+ }
+ }
+
+ // add it.
+ try {
+ add(user, user_file, password, password_file, password_file_only,
+ user_context);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << client->getPosition() << ")");
+ }
+ }
+}
+
+HttpResponseJsonPtr
+BasicHttpAuthConfig::checkAuth(const HttpResponseCreator& creator,
+ const HttpRequestPtr& request) const {
+ const BasicHttpAuthMap& credentials = getCredentialMap();
+ bool authentic = false;
+ if (credentials.empty()) {
+ authentic = true;
+ } else try {
+ string value = request->getHeaderValue("Authorization");
+ // Trim space characters.
+ value = str::trim(value);
+ if (value.size() < 8) {
+ isc_throw(BadValue, "header content is too short");
+ }
+ // Get the authentication scheme which must be "basic".
+ string scheme = value.substr(0, 5);
+ str::lowercase(scheme);
+ if (scheme != "basic") {
+ isc_throw(BadValue, "not basic authentication");
+ }
+ // Skip the authentication scheme name and space characters.
+ value = value.substr(5);
+ value = str::trim(value);
+ // Verify the credential is in the list.
+ const auto it = credentials.find(value);
+ if (it != credentials.end()) {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_AUTHORIZED)
+ .arg(it->second);
+ if (HttpRequest::recordBasicAuth_) {
+ request->setBasicAuth(it->second);
+ }
+ authentic = true;
+ } else {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_NOT_AUTHORIZED);
+ authentic = false;
+ }
+ } catch (const HttpMessageNonExistingHeader&) {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_NO_AUTH_HEADER);
+ } catch (const BadValue& ex) {
+ LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER)
+ .arg(ex.what());
+ }
+ if (authentic) {
+ return (HttpResponseJsonPtr());
+ }
+ const string& realm = getRealm();
+ const string& scheme = "Basic";
+ HttpResponsePtr response =
+ creator.createStockHttpResponse(request, HttpStatusCode::UNAUTHORIZED);
+ response->reset();
+ response->context()->headers_.push_back(
+ HttpHeaderContext("WWW-Authenticate",
+ scheme + " realm=\"" + realm + "\""));
+ response->finalize();
+ return (boost::dynamic_pointer_cast<HttpResponseJson>(response));
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/basic_auth_config.h b/src/lib/http/basic_auth_config.h
new file mode 100644
index 0000000..413dd22
--- /dev/null
+++ b/src/lib/http/basic_auth_config.h
@@ -0,0 +1,204 @@
+// Copyright (C) 2020-2022 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 HTTP_BASIC_AUTH_CONFIG_H
+#define HTTP_BASIC_AUTH_CONFIG_H
+
+#include <http/auth_config.h>
+#include <http/basic_auth.h>
+#include <list>
+#include <unordered_map>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of basic HTTP authentication credential and user id map,
+/// e.g. map["am9obmRvZTpzZWNyZXQx"] = "johndoe".
+///
+/// The map is used to verify a received credential: if it is not in it
+/// the authentication fails, if it is in it the user id is logged.
+typedef std::unordered_map<std::string, std::string> BasicHttpAuthMap;
+
+/// @brief Basic HTTP authentication client configuration.
+class BasicHttpAuthClient : public isc::data::UserContext,
+ public isc::data::CfgToElement {
+public:
+
+ /// @brief Constructor (legacy).
+ ///
+ /// @param user User id.
+ /// @param password Password.
+ /// @param user_context Optional user context.
+ BasicHttpAuthClient(const std::string& user,
+ const std::string& password,
+ const isc::data::ConstElementPtr& user_context);
+
+ /// @brief Constructor.
+ ///
+ /// @param user User id.
+ /// @param user_file File with the user id.
+ /// @param password Password.
+ /// @param password_file File with the password.
+ /// @param password_file_only Flag true if the password file includes
+ /// the user id too.
+ /// @param user_context Optional user context.
+ BasicHttpAuthClient(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only,
+ const isc::data::ConstElementPtr& user_context);
+
+ /// @brief Returns the user id.
+ ///
+ /// @return The user id.
+ const std::string& getUser() const {
+ return (user_);
+ }
+
+ /// @brief Returns the user id file.
+ ///
+ /// @return The user id file.
+ const std::string& getUserFile() const {
+ return (user_file_);
+ }
+
+ /// @brief Returns the password.
+ ///
+ /// @return The password.
+ const std::string& getPassword() const {
+ return (password_);
+ }
+
+ /// @brief Returns the password file.
+ ///
+ /// @return The password file.
+ const std::string& getPasswordFile() const {
+ return (password_file_);
+ }
+
+ /// @brief Returns the password file only flag.
+ ///
+ /// @return The password file only flag.
+ bool getPasswordFileOnly() const {
+ return (password_file_only_);
+ }
+
+ /// @brief Unparses basic HTTP authentication client configuration.
+ ///
+ /// @return A pointer to unparsed client configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief The user id e.g. johndoe.
+ std::string user_;
+
+ /// @brief The user id file.
+ std::string user_file_;
+
+ /// @brief The password e.g. secret1.
+ std::string password_;
+
+ /// @brief The password file.
+ std::string password_file_;
+
+ /// @brief The password file only flag.
+ bool password_file_only_;
+};
+
+/// @brief Type of basic HTTP authentication client configuration list.
+typedef std::list<BasicHttpAuthClient> BasicHttpAuthClientList;
+
+/// @brief Basic HTTP authentication configuration.
+class BasicHttpAuthConfig : public HttpAuthConfig {
+public:
+ /// @brief Destructor.
+ virtual ~BasicHttpAuthConfig() { }
+
+ /// @brief Add a client configuration.
+ ///
+ /// @param user User id.
+ /// @param user_file File with the user id.
+ /// @param password Password.
+ /// @param password_file File with the password.
+ /// @param password_file_only Flag true if the password file includes
+ /// the user id too.
+ /// @param user_context Optional user context.
+ /// @throw BadValue if the user id contains the ':' character.
+ void add(const std::string& user,
+ const std::string& user_file,
+ const std::string& password,
+ const std::string& password_file,
+ bool password_file_only = false,
+ const isc::data::ConstElementPtr& user_context = isc::data::ConstElementPtr());
+
+ /// @brief Empty predicate.
+ ///
+ /// @return true if the configuration is empty so authentication
+ /// is not required.
+ virtual bool empty() const;
+
+ /// @brief Clear configuration.
+ virtual void clear();
+
+ /// @brief Get the content of {directory}/{file-name} regular file.
+ ///
+ /// @param file_name The file name.
+ /// @return The content of the {directory}/{file-name} regular file.
+ std::string getFileContent(const std::string& file_name) const;
+
+ /// @brief Returns the list of client configuration.
+ ///
+ /// @return List of basic HTTP authentication client configuration.
+ const BasicHttpAuthClientList& getClientList() const {
+ return (list_);
+ }
+
+ /// @brief Returns the credential and user id map.
+ ///
+ /// @return The basic HTTP authentication credential and user id map.
+ const BasicHttpAuthMap& getCredentialMap() const {
+ return (map_);
+ }
+
+ /// @brief Parses basic HTTP authentication configuration.
+ ///
+ /// @param config Element holding the basic HTTP authentication
+ /// configuration to be parsed.
+ /// @throw DhcpConfigError when the configuration is invalid.
+ void parse(const isc::data::ConstElementPtr& config);
+
+ /// @brief Unparses basic HTTP authentication configuration.
+ ///
+ /// @return A pointer to unparsed basic HTTP authentication configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Validate HTTP request.
+ ///
+ /// @param creator The HTTP response creator.
+ /// @param request The HTTP request to validate.
+ /// @return Error HTTP response if validation failed, null otherwise.
+ virtual isc::http::HttpResponseJsonPtr
+ checkAuth(const isc::http::HttpResponseCreator& creator,
+ const isc::http::HttpRequestPtr& request) const;
+
+private:
+
+ /// @brief The list of basic HTTP authentication client configuration.
+ BasicHttpAuthClientList list_;
+
+ /// @brief The basic HTTP authentication credential and user id map.
+ BasicHttpAuthMap map_;
+};
+
+/// @brief Type of shared pointers to basic HTTP authentication configuration.
+typedef boost::shared_ptr<BasicHttpAuthConfig> BasicHttpAuthConfigPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif HTTP_BASIC_AUTH_CONFIG_H
diff --git a/src/lib/http/client.cc b/src/lib/http/client.cc
new file mode 100644
index 0000000..1f139a3
--- /dev/null
+++ b/src/lib/http/client.cc
@@ -0,0 +1,2062 @@
+// Copyright (C) 2018-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service_thread_pool.h>
+#include <asiolink/tls_socket.h>
+#include <http/client.h>
+#include <http/http_log.h>
+#include <http/http_messages.h>
+#include <http/response_json.h>
+#include <http/response_parser.h>
+#include <util/boost_time_utils.h>
+#include <util/multi_threading_mgr.h>
+#include <util/unlock_guard.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <atomic>
+#include <array>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <mutex>
+#include <queue>
+#include <thread>
+
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::util;
+using namespace boost::posix_time;
+
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the HTTP message that can be logged.
+///
+/// The part of the HTTP message beyond this value is truncated.
+constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
+
+/// @brief TCP / TLS socket callback function type.
+typedef std::function<void(boost::system::error_code ec, size_t length)>
+SocketCallbackFunction;
+
+/// @brief Socket callback class required by the TCPSocket and TLSSocket APIs.
+///
+/// Its function call operator ignores callbacks invoked with "operation aborted"
+/// error codes. Such status codes are generated when the posted IO operations
+/// are canceled.
+class SocketCallback {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Stores pointer to a callback function provided by a caller.
+ ///
+ /// @param socket_callback Pointer to a callback function.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Function call operator.
+ ///
+ /// Invokes the callback for all error codes except "operation aborted".
+ ///
+ /// @param ec Error code.
+ /// @param length Length of the data transmitted over the socket.
+ void operator()(boost::system::error_code ec, size_t length = 0) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ callback_(ec, length);
+ }
+
+private:
+
+ /// @brief Holds pointer to a supplied callback.
+ SocketCallbackFunction callback_;
+
+};
+
+class ConnectionPool;
+
+/// @brief Shared pointer to a connection pool.
+typedef boost::shared_ptr<ConnectionPool> ConnectionPoolPtr;
+
+/// @brief Client side HTTP connection to the server.
+///
+/// Each connection is established with a unique destination identified by the
+/// specified URL and TLS context. Multiple requests to the same destination
+/// can be sent over the same connection, if the connection is persistent.
+/// If the server closes the TCP connection (e.g. after sending a response),
+/// the connection is closed.
+///
+/// If new request is created while the previous request is still in progress,
+/// the new request is stored in the FIFO queue. The queued requests to the
+/// particular URL are sent to the server when the current transaction ends.
+///
+/// The communication over the transport socket is asynchronous. The caller is
+/// notified about the completion of the transaction via a callback that the
+/// caller supplies when initiating the transaction.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used for the connection.
+ /// @param tls_context TLS context to be used for the connection.
+ /// @param conn_pool Back pointer to the connection pool to which this
+ /// connection belongs.
+ /// @param url URL associated with this connection.
+ explicit Connection(IOService& io_service,
+ const TlsContextPtr& tls_context,
+ const ConnectionPoolPtr& conn_pool,
+ const Url& url);
+
+ /// @brief Destructor.
+ ~Connection();
+
+ /// @brief Starts new asynchronous transaction (HTTP request and response).
+ ///
+ /// This method expects that all pointers provided as argument are non-null.
+ ///
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response is stored.
+ /// The caller should create a response object of the type which matches the
+ /// content type expected by the caller, e.g. HttpResponseJson when JSON
+ /// content type is expected to be received.
+ /// @param request_timeout Request timeout in milliseconds.
+ /// @param callback Pointer to the callback function to be invoked when the
+ /// transaction completes.
+ /// @param connect_callback Pointer to the callback function to be invoked
+ /// when the client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the callback function to be invoked
+ /// when the client closes the socket to the server.
+ void doTransaction(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback);
+
+ /// @brief Closes the socket and cancels the request timer.
+ void close();
+
+ /// @brief Checks if a transaction has been initiated over this connection.
+ ///
+ /// @return true if transaction has been initiated, false otherwise.
+ bool isTransactionOngoing() const {
+ return (started_);
+ }
+
+ /// @brief Checks if the socket has been closed.
+ ///
+ /// @return true if the socket has been closed.
+ bool isClosed() const {
+ return (closed_);
+ }
+
+ /// @brief Checks if the peer has closed the idle socket at its side.
+ ///
+ /// If the socket is open but is not usable the peer has closed
+ /// the socket at its side so we close it.
+ void isClosedByPeer();
+
+ /// @brief Checks if a socket descriptor belongs to this connection.
+ ///
+ /// @param socket_fd socket descriptor to check
+ ///
+ /// @return True if the socket fd belongs to this connection.
+ bool isMySocket(int socket_fd) const;
+
+ /// @brief Checks and logs if premature transaction timeout is suspected.
+ ///
+ /// There are cases when the premature timeout occurs, e.g. as a result of
+ /// moving system clock, during the transaction. In such case, the
+ /// @c terminate function is called which resets the transaction state but
+ /// the transaction handlers may be already waiting for the execution.
+ /// Each such handler should call this function to check if the transaction
+ /// it is participating in is still alive. If it is not, it should simply
+ /// return. This method also logs such situation.
+ ///
+ /// @param transid identifier of the transaction for which the handler
+ /// is being invoked. It is compared against the current transaction
+ /// id for this connection.
+ ///
+ /// @return true if the premature timeout is suspected, false otherwise.
+ bool checkPrematureTimeout(const uint64_t transid);
+
+private:
+
+ /// @brief Starts new asynchronous transaction (HTTP request and response).
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// This method expects that all pointers provided as argument are non-null.
+ ///
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response is stored.
+ /// The caller should create a response object of the type which matches the
+ /// content type expected by the caller, e.g. HttpResponseJson when JSON
+ /// content type is expected to be received.
+ /// @param request_timeout Request timeout in milliseconds.
+ /// @param callback Pointer to the callback function to be invoked when the
+ /// transaction completes.
+ /// @param connect_callback Pointer to the callback function to be invoked
+ /// when the client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the callback function to be invoked
+ /// when the client closes the socket to the server.
+ void doTransactionInternal(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback);
+
+ /// @brief Closes the socket and cancels the request timer.
+ ///
+ /// Should be called in a thread safe context.
+ void closeInternal();
+
+ /// @brief Checks if the peer has closed the socket at its side.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// If the socket is open but is not usable the peer has closed
+ /// the socket at its side so we close it.
+ void isClosedByPeerInternal();
+
+ /// @brief Checks and logs if premature transaction timeout is suspected.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// There are cases when the premature timeout occurs, e.g. as a result of
+ /// moving system clock, during the transaction. In such case, the
+ /// @c terminate function is called which resets the transaction state but
+ /// the transaction handlers may be already waiting for the execution.
+ /// Each such handler should call this function to check if the transaction
+ /// it is participating in is still alive. If it is not, it should simply
+ /// return. This method also logs such situation.
+ ///
+ /// @param transid identifier of the transaction for which the handler
+ /// is being invoked. It is compared against the current transaction
+ /// id for this connection.
+ ///
+ /// @return true if the premature timeout is suspected, false otherwise.
+ bool checkPrematureTimeoutInternal(const uint64_t transid);
+
+ /// @brief Resets the state of the object.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// In particular, it removes instances of objects provided for the previous
+ /// transaction by a caller. It doesn't close the socket, though.
+ void resetState();
+
+ /// @brief Performs tasks required after receiving a response or after an
+ /// error.
+ ///
+ /// This method triggers user's callback, resets the state of the connection
+ /// and initiates next transaction if there is any transaction queued for the
+ /// URL associated with this connection.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param parsing_error Message parsing error.
+ void terminate(const boost::system::error_code& ec,
+ const std::string& parsing_error = "");
+
+ /// @brief Performs tasks required after receiving a response or after an
+ /// error.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// This method triggers user's callback, resets the state of the connection
+ /// and initiates next transaction if there is any transaction queued for the
+ /// URL associated with this connection.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param parsing_error Message parsing error.
+ void terminateInternal(const boost::system::error_code& ec,
+ const std::string& parsing_error = "");
+
+ /// @brief Run parser and check if more data is needed.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param length Number of bytes received.
+ ///
+ /// @return true if more data is needed, false otherwise.
+ bool runParser(const boost::system::error_code& ec, size_t length);
+
+ /// @brief Run parser and check if more data is needed.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param ec Error code received as a result of the IO operation.
+ /// @param length Number of bytes received.
+ ///
+ /// @return true if more data is needed, false otherwise.
+ bool runParserInternal(const boost::system::error_code& ec, size_t length);
+
+ /// @brief This method schedules timer or reschedules existing timer.
+ ///
+ /// @param request_timeout New timer interval in milliseconds.
+ void scheduleTimer(const long request_timeout);
+
+ /// @brief Asynchronously performs the TLS handshake.
+ ///
+ /// The TLS handshake is performed once on TLS sockets.
+ ///
+ /// @param transid Current transaction id.
+ void doHandshake(const uint64_t transid);
+
+ /// @brief Asynchronously sends data over the socket.
+ ///
+ /// The data sent over the socket are stored in the @c buf_.
+ ///
+ /// @param transid Current transaction id.
+ void doSend(const uint64_t transid);
+
+ /// @brief Asynchronously receives data over the socket.
+ ///
+ /// The data received over the socket are store into the @c input_buf_.
+ ///
+ /// @param transid Current transaction id.
+ void doReceive(const uint64_t transid);
+
+ /// @brief Local callback invoked when the connection is established.
+ ///
+ /// If the connection is successfully established, this callback will start
+ /// to asynchronously send the request over the socket or perform the
+ /// TLS handshake with the server.
+ ///
+ /// @param Pointer to the callback to be invoked when client connects to
+ /// the server.
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of the connection attempt.
+ void connectCallback(HttpClient::ConnectHandler connect_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec);
+
+ /// @brief Local callback invoked when the handshake is performed.
+ ///
+ /// If the handshake is successfully performed, this callback will start
+ /// to asynchronously send the request over the socket.
+ ///
+ /// @param Pointer to the callback to be invoked when client performs
+ /// the TLS handshake with the server.
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of the connection attempt.
+ void handshakeCallback(HttpClient::HandshakeHandler handshake_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec);
+
+ /// @brief Local callback invoked when an attempt to send a portion of data
+ /// over the socket has ended.
+ ///
+ /// The portion of data that has been sent is removed from the buffer. If all
+ /// data from the buffer were sent, the callback will start to asynchronously
+ /// receive a response from the server.
+ ///
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of sending the data.
+ /// @param length Number of bytes sent.
+ void sendCallback(const uint64_t transid, const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Local callback invoked when an attempt to receive a portion of data
+ /// over the socket has ended.
+ ///
+ /// @param transid Current transaction id.
+ /// @param ec Error code being a result of receiving the data.
+ /// @param length Number of bytes received.
+ void receiveCallback(const uint64_t transid, const boost::system::error_code& ec,
+ size_t length);
+
+ /// @brief Local callback invoked when request timeout occurs.
+ void timerCallback();
+
+ /// @brief Local callback invoked when the connection is closed.
+ ///
+ /// Invokes the close callback (if one), passing in the socket's
+ /// descriptor, when the connection's socket about to be closed.
+ /// The callback invocation is wrapped in a try-catch to ensure
+ /// exception safety.
+ ///
+ /// @param clear dictates whether or not the callback is discarded
+ /// after invocation. Defaults to false.
+ void closeCallback(const bool clear = false);
+
+ /// @brief Pointer to the connection pool owning this connection.
+ ///
+ /// This is a weak pointer to avoid circular dependency between the
+ /// Connection and ConnectionPool.
+ boost::weak_ptr<ConnectionPool> conn_pool_;
+
+ /// @brief URL for this connection.
+ Url url_;
+
+ /// @brief TLS context for this connection.
+ TlsContextPtr tls_context_;
+
+ /// @brief TCP socket to be used for this connection.
+ std::unique_ptr<TCPSocket<SocketCallback> > tcp_socket_;
+
+ /// @brief TLS socket to be used for this connection.
+ std::unique_ptr<TLSSocket<SocketCallback> > tls_socket_;
+
+ /// @brief Interval timer used for detecting request timeouts.
+ IntervalTimer timer_;
+
+ /// @brief Holds currently sent request.
+ HttpRequestPtr current_request_;
+
+ /// @brief Holds pointer to an object where response is to be stored.
+ HttpResponsePtr current_response_;
+
+ /// @brief Pointer to the HTTP response parser.
+ HttpResponseParserPtr parser_;
+
+ /// @brief User supplied callback.
+ HttpClient::RequestHandler current_callback_;
+
+ /// @brief Output buffer.
+ std::string buf_;
+
+ /// @brief Input buffer.
+ std::array<char, 32768> input_buf_;
+
+ /// @brief Identifier of the current transaction.
+ uint64_t current_transid_;
+
+ /// @brief User supplied handshake callback.
+ HttpClient::HandshakeHandler handshake_callback_;
+
+ /// @brief User supplied close callback.
+ HttpClient::CloseHandler close_callback_;
+
+ /// @brief Flag to indicate that a transaction is running.
+ std::atomic<bool> started_;
+
+ /// @brief Flag to indicate that the TLS handshake has to be performed.
+ std::atomic<bool> need_handshake_;
+
+ /// @brief Flag to indicate that the socket was closed.
+ std::atomic<bool> closed_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+};
+
+/// @brief Shared pointer to the connection.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Connection pool for managing multiple connections.
+///
+/// Connection pool creates and destroys URL destinations. It manages
+/// connections to and requests for URLs. Each time a request is
+/// submitted for a URL, it assigns it to an available idle connection,
+/// or if no idle connections are available, pushes the request on the queue
+/// for that URL.
+class ConnectionPool : public boost::enable_shared_from_this<ConnectionPool> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Reference to the IO service to be used by the
+ /// connections.
+ /// @param max_url_connections maximum number of concurrent
+ /// connections allowed per URL.
+ explicit ConnectionPool(IOService& io_service, size_t max_url_connections)
+ : io_service_(io_service), destinations_(), pool_mutex_(),
+ max_url_connections_(max_url_connections) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes all connections.
+ ~ConnectionPool() {
+ closeAll();
+ }
+
+ /// @brief Process next queued request for the given URL and TLS context.
+ ///
+ /// @param url URL for which next queued request should be processed.
+ /// @param tls_context TLS context for which next queued request
+ /// should be processed.
+ void processNextRequest(const Url& url, const TlsContextPtr& tls_context) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ return (processNextRequestInternal(url, tls_context));
+ } else {
+ return (processNextRequestInternal(url, tls_context));
+ }
+ }
+
+ /// @brief Schedule processing of next queued request.
+ ///
+ /// @param url URL for which next queued request should be processed.
+ /// @param tls_context TLS context for which next queued request
+ /// should be processed.
+ void postProcessNextRequest(const Url& url,
+ const TlsContextPtr& tls_context) {
+ io_service_.post(std::bind(&ConnectionPool::processNextRequest,
+ shared_from_this(), url, tls_context));
+ }
+
+ /// @brief Queue next request for sending to the server.
+ ///
+ /// A new transaction is started immediately, if there is no other request
+ /// in progress for the given URL. Otherwise, the request is queued.
+ ///
+ /// @param url Destination where the request should be sent.
+ /// @param tls_context TLS context to be used for the connection.
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response should be
+ /// stored.
+ /// @param request_timeout Requested timeout for the transaction in
+ /// milliseconds.
+ /// @param request_callback Pointer to the user callback to be invoked when the
+ /// transaction ends.
+ /// @param connect_callback Pointer to the user callback to be invoked when the
+ /// client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the user callback to be invoked when the
+ /// client closes the connection to the server.
+ void queueRequest(const Url& url,
+ const TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ return (queueRequestInternal(url, tls_context, request, response,
+ request_timeout, request_callback,
+ connect_callback, handshake_callback,
+ close_callback));
+ } else {
+ return (queueRequestInternal(url, tls_context, request, response,
+ request_timeout, request_callback,
+ connect_callback, handshake_callback,
+ close_callback));
+ }
+ }
+
+ /// @brief Closes all URLs and removes associated information from
+ /// the connection pool.
+ void closeAll() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ closeAllInternal();
+ } else {
+ closeAllInternal();
+ }
+ }
+
+ /// @brief Closes a connection if it has an out-of-band socket event
+ ///
+ /// If the pool contains a connection using the given socket and that
+ /// connection is currently in a transaction the method returns as this
+ /// indicates a normal ready event. If the connection is not in an
+ /// ongoing transaction, then the connection is closed.
+ ///
+ /// This is method is intended to be used to detect and clean up then
+ /// sockets that are marked ready outside of transactions. The most common
+ /// case is the other end of the socket being closed.
+ ///
+ /// @param socket_fd socket descriptor to check
+ void closeIfOutOfBand(int socket_fd) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(pool_mutex_);
+ closeIfOutOfBandInternal(socket_fd);
+ } else {
+ closeIfOutOfBandInternal(socket_fd);
+ }
+ }
+
+private:
+
+ /// @brief Process next queued request for the given URL and TLS context.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param url URL for which next queued request should be retrieved.
+ /// @param tls_context TLS context for which next queued request
+ /// should be processed.
+ void processNextRequestInternal(const Url& url,
+ const TlsContextPtr& tls_context) {
+ // Check if there is a queue for this URL. If there is no queue, there
+ // is no request queued either.
+ DestinationPtr destination = findDestination(url, tls_context);
+ if (destination) {
+ // Remove closed connections.
+ destination->garbageCollectConnections();
+ if (!destination->queueEmpty()) {
+ // We have at least one queued request. Do we have an
+ // idle connection?
+ ConnectionPtr connection = destination->getIdleConnection();
+ if (!connection) {
+ // No idle connections.
+ if (destination->connectionsFull()) {
+ return;
+ }
+ // Room to make another connection with this destination,
+ // so make one.
+ connection.reset(new Connection(io_service_, tls_context,
+ shared_from_this(), url));
+ destination->addConnection(connection);
+ }
+
+ // Dequeue the oldest request and start a transaction for it using
+ // the idle connection.
+ RequestDescriptor desc = destination->popNextRequest();
+ connection->doTransaction(desc.request_, desc.response_,
+ desc.request_timeout_, desc.callback_,
+ desc.connect_callback_,
+ desc.handshake_callback_,
+ desc.close_callback_);
+ }
+ }
+ }
+
+ /// @brief Queue next request for sending to the server.
+ ///
+ /// A new transaction is started immediately, if there is no other request
+ /// in progress for the given URL. Otherwise, the request is queued.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param url Destination where the request should be sent.
+ /// @param tls_context TLS context to be used for the connection.
+ /// @param request Pointer to the request to be sent to the server.
+ /// @param response Pointer to the object into which the response should be
+ /// stored.
+ /// @param request_timeout Requested timeout for the transaction in
+ /// milliseconds.
+ /// @param request_callback Pointer to the user callback to be invoked when the
+ /// transaction ends.
+ /// @param connect_callback Pointer to the user callback to be invoked when the
+ /// client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Pointer to the user callback to be invoked when the
+ /// client closes the connection to the server.
+ void queueRequestInternal(const Url& url,
+ const TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ ConnectionPtr connection;
+ // Find the destination for the requested URL.
+ DestinationPtr destination = findDestination(url, tls_context);
+ if (destination) {
+ // Remove closed connections.
+ destination->garbageCollectConnections();
+ // Found it, look for an idle connection.
+ connection = destination->getIdleConnection();
+ } else {
+ // Doesn't exist yet so it's a new destination.
+ destination = addDestination(url, tls_context);
+ }
+
+ if (!connection) {
+ if (destination->connectionsFull()) {
+ // All connections busy, queue it.
+ destination->pushRequest(RequestDescriptor(request, response,
+ request_timeout,
+ request_callback,
+ connect_callback,
+ handshake_callback,
+ close_callback));
+ return;
+ }
+
+ // Room to make another connection with this destination, so make one.
+ connection.reset(new Connection(io_service_, tls_context,
+ shared_from_this(), url));
+ destination->addConnection(connection);
+ }
+
+ // Use the connection to start the transaction.
+ connection->doTransaction(request, response, request_timeout, request_callback,
+ connect_callback, handshake_callback, close_callback);
+ }
+
+ /// @brief Closes all connections for all URLs and removes associated
+ /// information from the connection pool.
+ ///
+ /// This method should be called in a thread safe context.
+ void closeAllInternal() {
+ for (auto const& destination : destinations_) {
+ destination.second->closeAllConnections();
+ }
+
+ destinations_.clear();
+ }
+
+ /// @brief Closes a connection if it has an out-of-band socket event
+ ///
+ /// If the pool contains a connection using the given socket and that
+ /// connection is currently in a transaction the method returns as this
+ /// indicates a normal ready event. If the connection is not in an
+ /// ongoing transaction, then the connection is closed.
+ ///
+ /// This is method is intended to be used to detect and clean up then
+ /// sockets that are marked ready outside of transactions. The most common
+ /// case is the other end of the socket being closed.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param socket_fd socket descriptor to check
+ void closeIfOutOfBandInternal(int socket_fd) {
+ for (auto const& destination : destinations_) {
+ // First we look for a connection with the socket.
+ ConnectionPtr connection = destination.second->findBySocketFd(socket_fd);
+ if (connection) {
+ if (!connection->isTransactionOngoing()) {
+ // Socket has no transaction, so any ready event is
+ // out-of-band (other end probably closed), so
+ // let's close it. Note we do not remove any queued
+ // requests, as this might somehow be occurring in
+ // between them.
+ destination.second->closeConnection(connection);
+ }
+
+ return;
+ }
+ }
+ }
+
+ /// @brief Request descriptor holds parameters associated with the
+ /// particular request.
+ struct RequestDescriptor {
+ /// @brief Constructor.
+ ///
+ /// @param request Pointer to the request to be sent.
+ /// @param response Pointer to the object into which the response will
+ /// be stored.
+ /// @param request_timeout Requested timeout for the transaction.
+ /// @param callback Pointer to the user callback.
+ /// @param connect_callback pointer to the user callback to be invoked
+ /// when the client connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback pointer to the user callback to be invoked
+ /// when the client closes the connection to the server.
+ RequestDescriptor(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long& request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback)
+ : request_(request), response_(response),
+ request_timeout_(request_timeout), callback_(callback),
+ connect_callback_(connect_callback),
+ handshake_callback_(handshake_callback),
+ close_callback_(close_callback) {
+ }
+
+ /// @brief Holds pointer to the request.
+ HttpRequestPtr request_;
+
+ /// @brief Holds pointer to the response.
+ HttpResponsePtr response_;
+
+ /// @brief Holds requested timeout value.
+ long request_timeout_;
+
+ /// @brief Holds pointer to the user callback.
+ HttpClient::RequestHandler callback_;
+
+ /// @brief Holds pointer to the user callback for connect.
+ HttpClient::ConnectHandler connect_callback_;
+
+ /// @brief Holds pointer to the user callback for handshake.
+ HttpClient::HandshakeHandler handshake_callback_;
+
+ /// @brief Holds pointer to the user callback for close.
+ HttpClient::CloseHandler close_callback_;
+ };
+
+ /// @brief Type of URL and TLS context pairs.
+ typedef std::pair<Url, TlsContextPtr> DestinationDescriptor;
+
+ /// @brief Encapsulates connections and requests for a given URL
+ class Destination {
+ public:
+ /// @brief Number of queued requests allowed without warnings being emitted.
+ const size_t QUEUE_SIZE_THRESHOLD = 2048;
+ /// @brief Interval between queue size warnings.
+ const int QUEUE_WARN_SECS = 5;
+
+ /// @brief Constructor
+ ///
+ /// @param url server URL of this destination
+ /// @param tls_context server TLS context of this destination
+ /// @param max_connections maximum number of concurrent connections
+ /// allowed for in the list URL
+ Destination(Url const& url, TlsContextPtr tls_context, size_t max_connections)
+ : url_(url), tls_context_(tls_context),
+ max_connections_(max_connections), connections_(), queue_(),
+ last_queue_warn_time_(min_date_time), last_queue_size_(0) {
+ }
+
+ /// @brief Destructor
+ ~Destination() {
+ closeAllConnections();
+ }
+
+ /// @brief Adds a new connection
+ ///
+ /// @param connection the connection to add
+ ///
+ /// @throw BadValue if the maximum number of connections already
+ /// exist.
+ /// @note This should be called in a thread safe context.
+ void addConnection(ConnectionPtr connection) {
+ if (connectionsFull()) {
+ isc_throw(BadValue, "URL: " << url_.toText()
+ << ", already at maximum connections: "
+ << max_connections_);
+ }
+
+ connections_.push_back(connection);
+ }
+
+ /// @brief Closes a connection and removes it from the list.
+ ///
+ /// @param connection the connection to remove
+ /// @note This should be called in a thread safe context.
+ void closeConnection(ConnectionPtr connection) {
+ for (auto it = connections_.begin(); it != connections_.end(); ++it) {
+ if (*it == connection) {
+ (*it)->close();
+ connections_.erase(it);
+ break;
+ }
+ }
+ }
+
+ /// @brief Closes all connections and clears the list.
+ /// @note This should be called in a thread safe context.
+ void closeAllConnections() {
+ // Flush the queue.
+ while (!queue_.empty()) {
+ queue_.pop();
+ }
+
+ for (auto const& connection : connections_) {
+ connection->close();
+ }
+
+ connections_.clear();
+ }
+
+ /// @brief Removes closed connections.
+ ///
+ /// This method should be called before @ref getIdleConnection.
+ ///
+ /// In a first step it closes not usable idle connections
+ /// (idle means no current transaction and not closed,
+ /// usable means the peer side did not close it at that time).
+ /// In a second step it removes (collects) closed connections.
+ ///
+ /// @note a connection is closed when the transaction is finished
+ /// and the connection is persistent, or when the connection was
+ /// idle and the first step of the garbage collector detects that
+ /// it was closed by peer, so is not usable.
+ ///
+ /// @note there are two races here:
+ /// - the peer side closes the connection after the first step
+ /// - a not persistent connection finishes its transaction and
+ /// closes
+ /// The second race is avoided by setting the closed flag before
+ /// the started flag and by unconditionally posting a process next
+ /// request action.
+ ///
+ /// @note This should be called in a thread safe context.
+ void garbageCollectConnections() {
+ for (auto it = connections_.begin(); it != connections_.end();) {
+ (*it)->isClosedByPeer();
+ if (!(*it)->isClosed()) {
+ ++it;
+ } else {
+ it = connections_.erase(it);
+ }
+ }
+ }
+
+ /// @brief Finds the first idle connection.
+ ///
+ /// Iterates over the existing connections and returns the
+ /// first connection which is not currently in a transaction and
+ /// is not closed.
+ ///
+ /// @note @ref garbageCollectConnections should be called before.
+ /// This removes connections which were closed at that time.
+ ///
+ /// @return The first idle connection or an empty pointer if
+ /// all connections are busy or closed.
+ ConnectionPtr getIdleConnection() {
+ for (auto const& connection : connections_) {
+ if (!connection->isTransactionOngoing() &&
+ !connection->isClosed()) {
+ return (connection);
+ }
+ }
+
+ return (ConnectionPtr());
+ }
+
+ /// @brief Find a connection by its socket descriptor.
+ ///
+ /// @param socket_fd socket descriptor to find
+ ///
+ /// @return The connection or an empty pointer if no matching
+ /// connection exists.
+ ConnectionPtr findBySocketFd(int socket_fd) {
+ for (auto const& connection : connections_) {
+ if (connection->isMySocket(socket_fd)) {
+ return (connection);
+ }
+ }
+
+ return (ConnectionPtr());
+ }
+
+ /// @brief Indicates if there are no connections in the list.
+ ///
+ /// @return true if the list is empty.
+ bool connectionsEmpty() {
+ return (connections_.empty());
+ }
+
+ /// @brief Indicates if list contains the maximum number.
+ ///
+ /// @return true if the list is full.
+ bool connectionsFull() {
+ return (connections_.size() >= max_connections_);
+ }
+
+ /// @brief Fetches the number of connections in the list.
+ ///
+ /// @return the number of connections in the list.
+ size_t connectionCount() {
+ return (connections_.size());
+ }
+
+ /// @brief Fetches the maximum number of connections.
+ ///
+ /// @return the maxim number of connections.
+ size_t getMaxConnections() const {
+ return (max_connections_);
+ }
+
+ /// @brief Indicates if request queue is empty.
+ ///
+ /// @return true if there are no requests queued.
+ bool queueEmpty() const {
+ return (queue_.empty());
+ }
+
+ /// @brief Adds a request to the end of the request queue.
+ ///
+ /// If the size of the queue exceeds a threshold and appears
+ /// to be growing it will emit a warning log.
+ ///
+ /// @param desc RequestDescriptor to queue.
+ void pushRequest(RequestDescriptor const& desc) {
+ queue_.push(desc);
+ size_t size = queue_.size();
+ // If the queue size is larger than the threshold and growing, issue a
+ // periodic warning.
+ if ((size > QUEUE_SIZE_THRESHOLD) && (size > last_queue_size_)) {
+ ptime now = microsec_clock::universal_time();
+ if ((now - last_queue_warn_time_) > seconds(QUEUE_WARN_SECS)) {
+ LOG_WARN(http_logger, HTTP_CLIENT_QUEUE_SIZE_GROWING)
+ .arg(url_.toText())
+ .arg(size);
+ // Remember the last time we warned.
+ last_queue_warn_time_ = now;
+ }
+ }
+
+ // Remember the previous size.
+ last_queue_size_ = size;
+ }
+
+ /// @brief Removes a request from the front of the request queue.
+ ///
+ /// @return desc RequestDescriptor of the removed request.
+ RequestDescriptor popNextRequest() {
+ if (queue_.empty()) {
+ isc_throw(InvalidOperation, "cannot pop, queue is empty");
+ }
+
+ RequestDescriptor desc = queue_.front();
+ queue_.pop();
+ return (desc);
+ }
+
+ private:
+ /// @brief URL supported by this destination.
+ Url url_;
+
+ /// @brief TLS context to use with this destination.
+ TlsContextPtr tls_context_;
+
+ /// @brief Maximum number of concurrent connections for this destination.
+ size_t max_connections_;
+
+ /// @brief List of concurrent connections.
+ std::list<ConnectionPtr> connections_;
+
+ /// @brief Holds the queue of request for this destination.
+ std::queue<RequestDescriptor> queue_;
+
+ /// @brief Time the last queue size warning was issued.
+ ptime last_queue_warn_time_;
+
+ /// @brief Size of the queue after last push.
+ size_t last_queue_size_;
+ };
+
+ /// @brief Pointer to a Destination.
+ typedef boost::shared_ptr<Destination> DestinationPtr;
+
+ /// @brief Creates a new destination for the given URL and TLS context.
+ ///
+ /// @param url URL of the new destination.
+ /// @param tls_context TLS context for the new destination.
+ ///
+ /// @return Pointer to the newly created destination.
+ /// @note Must be called from within a thread-safe context.
+ DestinationPtr addDestination(const Url& url,
+ const TlsContextPtr& tls_context) {
+ const DestinationDescriptor& desc = std::make_pair(url, tls_context);
+ DestinationPtr destination(new Destination(url, tls_context,
+ max_url_connections_));
+ destinations_[desc] = destination;
+ return (destination);
+ }
+
+ /// @brief Fetches a destination by URL and TLS context.
+ ///
+ /// @param url URL of the destination desired.
+ /// @param tls_context TLS context for the destination desired.
+ ///
+ /// @return pointer the desired destination, empty pointer
+ /// if the destination does not exist.
+ /// @note Must be called from within a thread-safe context.
+ DestinationPtr findDestination(const Url& url,
+ const TlsContextPtr& tls_context) const {
+ const DestinationDescriptor& desc = std::make_pair(url, tls_context);
+ auto it = destinations_.find(desc);
+ if (it != destinations_.end()) {
+ return (it->second);
+ }
+
+ return (DestinationPtr());
+ }
+
+ /// @brief Removes a destination by URL and TLS context.
+ ///
+ /// Closes all of the destination's connections and
+ /// discards all of its queued requests while removing
+ /// the destination from the list of known destinations.
+ ///
+ /// @note not used yet.
+ ///
+ /// @param url URL of the destination to be removed.
+ /// @param tls_context TLS context for the destination to be removed.
+ /// @note Must be called from within a thread-safe context.
+ void removeDestination(const Url& url,
+ const TlsContextPtr& tls_context) {
+ const DestinationDescriptor& desc = std::make_pair(url, tls_context);
+ auto it = destinations_.find(desc);
+ if (it != destinations_.end()) {
+ it->second->closeAllConnections();
+ destinations_.erase(it);
+ }
+ }
+
+ /// @brief A reference to the IOService that drives socket IO.
+ IOService& io_service_;
+
+ /// @brief Map of Destinations by URL and TLS context.
+ std::map<DestinationDescriptor, DestinationPtr> destinations_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex pool_mutex_;
+
+ /// @brief Maximum number of connections per URL and TLS context.
+ size_t max_url_connections_;
+};
+
+Connection::Connection(IOService& io_service,
+ const TlsContextPtr& tls_context,
+ const ConnectionPoolPtr& conn_pool,
+ const Url& url)
+ : conn_pool_(conn_pool), url_(url), tls_context_(tls_context),
+ tcp_socket_(), tls_socket_(), timer_(io_service),
+ current_request_(), current_response_(), parser_(),
+ current_callback_(), buf_(), input_buf_(), current_transid_(0),
+ close_callback_(), started_(false), need_handshake_(false),
+ closed_(false) {
+ if (!tls_context) {
+ tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
+ } else {
+ tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
+ tls_context));
+ need_handshake_ = true;
+ }
+}
+
+Connection::~Connection() {
+ close();
+}
+
+void
+Connection::resetState() {
+ started_ = false;
+ current_request_.reset();
+ current_response_.reset();
+ parser_.reset();
+ current_callback_ = HttpClient::RequestHandler();
+}
+
+void
+Connection::closeCallback(const bool clear) {
+ if (close_callback_) {
+ try {
+ if (tcp_socket_) {
+ close_callback_(tcp_socket_->getNative());
+ } else if (tls_socket_) {
+ close_callback_(tls_socket_->getNative());
+ } else {
+ isc_throw(Unexpected,
+ "internal error: can't find a socket to close");
+ }
+ } catch (...) {
+ LOG_ERROR(http_logger, HTTP_CONNECTION_CLOSE_CALLBACK_FAILED);
+ }
+ }
+
+ if (clear) {
+ close_callback_ = HttpClient::CloseHandler();
+ }
+}
+
+void
+Connection::isClosedByPeer() {
+ // This method applies only to idle connections.
+ if (started_ || closed_) {
+ return;
+ }
+ // This code was guarded by a lock so keep this.
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ isClosedByPeerInternal();
+ } else {
+ isClosedByPeerInternal();
+ }
+}
+
+void
+Connection::isClosedByPeerInternal() {
+ // If the socket is open we check if it is possible to transmit
+ // the data over this socket by reading from it with message
+ // peeking. If the socket is not usable, we close it and then
+ // re-open it. There is a narrow window of time between checking
+ // the socket usability and actually transmitting the data over
+ // this socket, when the peer may close the connection. In this
+ // case we'll need to re-transmit but we don't handle it here.
+ if (tcp_socket_) {
+ if (tcp_socket_->getASIOSocket().is_open() &&
+ !tcp_socket_->isUsable()) {
+ closeCallback();
+ closed_ = true;
+ tcp_socket_->close();
+ }
+ } else if (tls_socket_) {
+ if (tls_socket_->getASIOSocket().is_open() &&
+ !tls_socket_->isUsable()) {
+ closeCallback();
+ closed_ = true;
+ tls_socket_->close();
+ }
+ } else {
+ isc_throw(Unexpected, "internal error: can't find the sending socket");
+ }
+}
+
+void
+Connection::doTransaction(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ doTransactionInternal(request, response, request_timeout,
+ callback, connect_callback, handshake_callback,
+ close_callback);
+ } else {
+ doTransactionInternal(request, response, request_timeout,
+ callback, connect_callback, handshake_callback,
+ close_callback);
+ }
+}
+
+void
+Connection::doTransactionInternal(const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const long request_timeout,
+ const HttpClient::RequestHandler& callback,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ try {
+ started_ = true;
+ current_request_ = request;
+ current_response_ = response;
+ parser_.reset(new HttpResponseParser(*current_response_));
+ parser_->initModel();
+ current_callback_ = callback;
+ handshake_callback_ = handshake_callback;
+ close_callback_ = close_callback;
+
+ // Starting new transaction. Generate new transaction id.
+ ++current_transid_;
+
+ buf_ = request->toString();
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_CLIENT_REQUEST_SEND)
+ .arg(request->toBriefString())
+ .arg(url_.toText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ HTTP_CLIENT_REQUEST_SEND_DETAILS)
+ .arg(url_.toText())
+ .arg(HttpMessageParserBase::logFormatHttpMessage(request->toString(),
+ MAX_LOGGED_MESSAGE_SIZE));
+
+ // Setup request timer.
+ scheduleTimer(request_timeout);
+
+ /// @todo We're getting a hostname but in fact it is expected to be an IP address.
+ /// We should extend the TCPEndpoint to also accept names. Currently, it will fall
+ /// over for names.
+ TCPEndpoint endpoint(url_.getStrippedHostname(),
+ static_cast<unsigned short>(url_.getPort()));
+ SocketCallback socket_cb(std::bind(&Connection::connectCallback, shared_from_this(),
+ connect_callback, current_transid_,
+ ph::_1));
+
+ // Establish new connection or use existing connection.
+ if (tcp_socket_) {
+ tcp_socket_->open(&endpoint, socket_cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->open(&endpoint, socket_cb);
+ return;
+ }
+
+ // Should never reach this point.
+ isc_throw(Unexpected, "internal error: can't find a socket to open");
+
+ } catch (const std::exception& ex) {
+ // Re-throw with the expected exception type.
+ isc_throw(HttpClientError, ex.what());
+ }
+}
+
+void
+Connection::close() {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (closeInternal());
+ } else {
+ return (closeInternal());
+ }
+}
+
+void
+Connection::closeInternal() {
+ // Pass in true to discard the callback.
+ closeCallback(true);
+
+ closed_ = true;
+ timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ }
+ if (tls_socket_) {
+ tls_socket_->close();
+ }
+
+ resetState();
+}
+
+bool
+Connection::isMySocket(int socket_fd) const {
+ if (tcp_socket_) {
+ return (tcp_socket_->getNative() == socket_fd);
+ } else if (tls_socket_) {
+ return (tls_socket_->getNative() == socket_fd);
+ }
+ // Should never reach this point.
+ std::cerr << "internal error: can't find my socket\n";
+ return (false);
+}
+
+bool
+Connection::checkPrematureTimeout(const uint64_t transid) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (checkPrematureTimeoutInternal(transid));
+ } else {
+ return (checkPrematureTimeoutInternal(transid));
+ }
+}
+
+bool
+Connection::checkPrematureTimeoutInternal(const uint64_t transid) {
+ // If there is no transaction but the handlers are invoked it means
+ // that the last transaction in the queue timed out prematurely.
+ // Also, if there is a transaction in progress but the ID of that
+ // transaction doesn't match the one associated with the handler it,
+ // also means that the transaction timed out prematurely.
+ if (!isTransactionOngoing() || (transid != current_transid_)) {
+ LOG_WARN(http_logger, HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED)
+ .arg(isTransactionOngoing())
+ .arg(transid)
+ .arg(current_transid_);
+ return (true);
+ }
+
+ return (false);
+}
+
+void
+Connection::terminate(const boost::system::error_code& ec,
+ const std::string& parsing_error) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ terminateInternal(ec, parsing_error);
+ } else {
+ terminateInternal(ec, parsing_error);
+ }
+}
+
+void
+Connection::terminateInternal(const boost::system::error_code& ec,
+ const std::string& parsing_error) {
+ HttpResponsePtr response;
+ if (isTransactionOngoing()) {
+
+ timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->cancel();
+ }
+ if (tls_socket_) {
+ tls_socket_->cancel();
+ }
+
+ if (!ec && current_response_->isFinalized()) {
+ response = current_response_;
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_SERVER_RESPONSE_RECEIVED)
+ .arg(url_.toText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_SERVER_RESPONSE_RECEIVED_DETAILS)
+ .arg(url_.toText())
+ .arg(parser_ ?
+ parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
+ "[HttpResponseParser is null]");
+
+ } else {
+ std::string err = parsing_error.empty() ? ec.message() :
+ parsing_error;
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_BAD_SERVER_RESPONSE_RECEIVED)
+ .arg(url_.toText())
+ .arg(err);
+
+ // Only log the details if we have received anything and tried
+ // to parse it.
+ if (!parsing_error.empty()) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS)
+ .arg(url_.toText())
+ .arg(parser_ ?
+ parser_->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE) :
+ "[HttpResponseParser is null]");
+ }
+ }
+
+ try {
+ // The callback should take care of its own exceptions but one
+ // never knows.
+ if (MultiThreadingMgr::instance().getMode()) {
+ UnlockGuard<std::mutex> lock(mutex_);
+ current_callback_(ec, response, parsing_error);
+ } else {
+ current_callback_(ec, response, parsing_error);
+ }
+ } catch (...) {
+ }
+
+ // If we're not requesting connection persistence or the
+ // connection has timed out, we should close the socket.
+ if (!closed_ &&
+ (!current_request_->isPersistent() ||
+ (ec == boost::asio::error::timed_out))) {
+ closeInternal();
+ }
+
+ resetState();
+ }
+
+ // Check if there are any requests queued for this destination and start
+ // another transaction if there is at least one.
+ ConnectionPoolPtr conn_pool = conn_pool_.lock();
+ if (conn_pool) {
+ conn_pool->postProcessNextRequest(url_, tls_context_);
+ }
+}
+
+void
+Connection::scheduleTimer(const long request_timeout) {
+ if (request_timeout > 0) {
+ timer_.setup(std::bind(&Connection::timerCallback, this), request_timeout,
+ IntervalTimer::ONE_SHOT);
+ }
+}
+
+void
+Connection::doHandshake(const uint64_t transid) {
+ // Skip the handshake if it is not needed.
+ if (!need_handshake_) {
+ doSend(transid);
+ return;
+ }
+
+ SocketCallback socket_cb(std::bind(&Connection::handshakeCallback,
+ shared_from_this(),
+ handshake_callback_,
+ transid,
+ ph::_1));
+ try {
+ tls_socket_->handshake(socket_cb);
+
+ } catch (...) {
+ terminate(boost::asio::error::not_connected);
+ }
+}
+
+void
+Connection::doSend(const uint64_t transid) {
+ SocketCallback socket_cb(std::bind(&Connection::sendCallback,
+ shared_from_this(),
+ transid,
+ ph::_1,
+ ph::_2));
+ try {
+ if (tcp_socket_) {
+ tcp_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
+ return;
+ }
+
+ if (tls_socket_) {
+ tls_socket_->asyncSend(&buf_[0], buf_.size(), socket_cb);
+ return;
+ }
+
+ // Should never reach this point.
+ std::cerr << "internal error: can't find a socket to send to\n";
+ isc_throw(Unexpected,
+ "internal error: can't find a socket to send to");
+ } catch (...) {
+ terminate(boost::asio::error::not_connected);
+ }
+}
+
+void
+Connection::doReceive(const uint64_t transid) {
+ TCPEndpoint endpoint;
+ SocketCallback socket_cb(std::bind(&Connection::receiveCallback,
+ shared_from_this(),
+ transid,
+ ph::_1,
+ ph::_2));
+ try {
+ if (tcp_socket_) {
+ tcp_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
+ input_buf_.size(), 0,
+ &endpoint, socket_cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncReceive(static_cast<void*>(input_buf_.data()),
+ input_buf_.size(), 0,
+ &endpoint, socket_cb);
+ return;
+ }
+ // Should never reach this point.
+ std::cerr << "internal error: can't find a socket to receive from\n";
+ isc_throw(Unexpected,
+ "internal error: can't find a socket to receive from");
+
+ } catch (...) {
+ terminate(boost::asio::error::not_connected);
+ }
+}
+
+void
+Connection::connectCallback(HttpClient::ConnectHandler connect_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec) {
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ // Run user defined connect callback if specified.
+ if (connect_callback) {
+ // If the user defined callback indicates that the connection
+ // should not be continued.
+ if (tcp_socket_) {
+ if (!connect_callback(ec, tcp_socket_->getNative())) {
+ return;
+ }
+ } else if (tls_socket_) {
+ if (!connect_callback(ec, tls_socket_->getNative())) {
+ return;
+ }
+ } else {
+ // Should never reach this point.
+ std::cerr << "internal error: can't find a socket to connect\n";
+ }
+ }
+
+ if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
+ return;
+
+ // In some cases the "in progress" status code may be returned. It doesn't
+ // indicate an error. Sending the request over the socket is expected to
+ // be successful. Getting such status appears to be highly dependent on
+ // the operating system.
+ } else if (ec &&
+ (ec.value() != boost::asio::error::in_progress) &&
+ (ec.value() != boost::asio::error::already_connected)) {
+ terminate(ec);
+
+ } else {
+ // Start the TLS handshake asynchronously.
+ doHandshake(transid);
+ }
+}
+
+void
+Connection::handshakeCallback(HttpClient::ConnectHandler handshake_callback,
+ const uint64_t transid,
+ const boost::system::error_code& ec) {
+ need_handshake_ = false;
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ // Run user defined handshake callback if specified.
+ if (handshake_callback) {
+ // If the user defined callback indicates that the connection
+ // should not be continued.
+ if (tls_socket_) {
+ if (!handshake_callback(ec, tls_socket_->getNative())) {
+ return;
+ }
+ } else {
+ // Should never reach this point.
+ std::cerr << "internal error: can't find TLS socket\n";
+ }
+ }
+
+ if (ec && (ec.value() == boost::asio::error::operation_aborted)) {
+ return;
+ } else if (ec) {
+ terminate(ec);
+
+ } else {
+ // Start sending the request asynchronously.
+ doSend(transid);
+ }
+}
+
+void
+Connection::sendCallback(const uint64_t transid,
+ const boost::system::error_code& ec,
+ size_t length) {
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EAGAIN and EWOULDBLOCK don't really indicate an error. The length
+ // should be 0 in this case but let's be sure.
+ } else if ((ec.value() == boost::asio::error::would_block) ||
+ (ec.value() == boost::asio::error::try_again)) {
+ length = 0;
+
+ } else {
+ // Any other error should cause the transaction to terminate.
+ terminate(ec);
+ return;
+ }
+ }
+
+ // Sending is in progress, so push back the timeout.
+ scheduleTimer(timer_.getInterval());
+
+ // If any data have been sent, remove it from the buffer and only leave the
+ // portion that still has to be sent.
+ if (length > 0) {
+ buf_.erase(0, length);
+ }
+
+ // If there is no more data to be sent, start receiving a response. Otherwise,
+ // continue sending.
+ if (buf_.empty()) {
+ doReceive(transid);
+
+ } else {
+ doSend(transid);
+ }
+}
+
+void
+Connection::receiveCallback(const uint64_t transid,
+ const boost::system::error_code& ec,
+ size_t length) {
+ if (checkPrematureTimeout(transid)) {
+ return;
+ }
+
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EAGAIN and EWOULDBLOCK don't indicate an error in this case. All
+ // other errors should terminate the transaction.
+ }
+ if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ terminate(ec);
+ return;
+
+ } else {
+ // For EAGAIN and EWOULDBLOCK the length should be 0 anyway, but let's
+ // make sure.
+ length = 0;
+ }
+ }
+
+ // Receiving is in progress, so push back the timeout.
+ scheduleTimer(timer_.getInterval());
+
+ if (runParser(ec, length)) {
+ doReceive(transid);
+ }
+}
+
+bool
+Connection::runParser(const boost::system::error_code& ec, size_t length) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (runParserInternal(ec, length));
+ } else {
+ return (runParserInternal(ec, length));
+ }
+}
+
+bool
+Connection::runParserInternal(const boost::system::error_code& ec,
+ size_t length) {
+ // If we have received any data, let's feed the parser with it.
+ if (length != 0) {
+ parser_->postBuffer(static_cast<void*>(input_buf_.data()), length);
+ parser_->poll();
+ }
+
+ // If the parser still needs data, let's schedule another receive.
+ if (parser_->needData()) {
+ return (true);
+
+ } else if (parser_->httpParseOk()) {
+ // No more data needed and parsing has been successful so far. Let's
+ // try to finalize the response parsing.
+ try {
+ current_response_->finalize();
+ terminateInternal(ec);
+
+ } catch (const std::exception& ex) {
+ // If there is an error here, we need to return the error message.
+ terminateInternal(ec, ex.what());
+ }
+
+ } else {
+ // Parsing was unsuccessful. Let's pass the error message held in the
+ // parser.
+ terminateInternal(ec, parser_->getErrorMessage());
+ }
+
+ return (false);
+}
+
+void
+Connection::timerCallback() {
+ // Request timeout occurred.
+ terminate(boost::asio::error::timed_out);
+}
+
+}
+
+namespace isc {
+namespace http {
+
+/// @brief HttpClient implementation.
+class HttpClientImpl {
+public:
+ /// @brief Constructor.
+ ///
+ /// If single-threading:
+ /// - Creates the connection pool passing in the caller's IOService
+ /// and a maximum number of connections per URL value of 1.
+ /// If multi-threading:
+ /// - Creates a private IOService
+ /// - Creates a thread pool with the thread_pool_size threads
+ /// - Creates the connection pool passing the private IOService
+ /// and the thread_pool_size as the maximum number of connections
+ /// per URL.
+ ///
+ /// @param io_service IOService that will drive connection IO in single
+ /// threaded mode. (Currently ignored in multi-threaded mode)
+ /// @param thread_pool_size maximum number of concurrent threads
+ /// Internally this also sets the maximum number of concurrent connections
+ /// per URL.
+ /// @param defer_thread_start When true, starting of the pool threads is
+ /// deferred until a subsequent call to @ref start(). In this case the
+ /// pool's operational state after construction is STOPPED. Otherwise,
+ /// the thread pool threads will be created and started, with the
+ /// operational state being RUNNING. Applicable only when thread-pool size
+ /// is greater than zero.
+ HttpClientImpl(IOService& io_service, size_t thread_pool_size = 0,
+ bool defer_thread_start = false)
+ : thread_pool_size_(thread_pool_size), thread_pool_() {
+ if (thread_pool_size_ > 0) {
+ // Create our own private IOService.
+ thread_io_service_.reset(new IOService());
+
+ // Create the connection pool. Note that we use the thread_pool_size
+ // as the maximum connections per URL value.
+ conn_pool_.reset(new ConnectionPool(*thread_io_service_, thread_pool_size_));
+
+ // Create the thread pool.
+ thread_pool_.reset(new IoServiceThreadPool(thread_io_service_, thread_pool_size_,
+ defer_thread_start));
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC, HTTP_CLIENT_MT_STARTED)
+ .arg(thread_pool_size_);
+ } else {
+ // Single-threaded mode: use the caller's IOService,
+ // one connection per URL.
+ conn_pool_.reset(new ConnectionPool(io_service, 1));
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// Calls stop().
+ ~HttpClientImpl() {
+ stop();
+ }
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions() {
+ if (thread_pool_) {
+ thread_pool_->checkPausePermissions();
+ }
+ }
+
+ /// @brief Starts running the client's thread pool, if multi-threaded.
+ void start() {
+ if (thread_pool_) {
+ thread_pool_->run();
+ }
+ }
+
+ /// @brief Close all connections, and if multi-threaded, stops the client's
+ /// thread pool.
+ void stop() {
+ // Close all the connections.
+ conn_pool_->closeAll();
+
+ // Stop the thread pool.
+ if (thread_pool_) {
+ thread_pool_->stop();
+ }
+ }
+
+ /// @brief Pauses the client's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void pause() {
+ if (!thread_pool_) {
+ isc_throw(InvalidOperation, "HttpClient::pause - no thread pool");
+ }
+
+ // Pause the thread pool.
+ thread_pool_->pause();
+ }
+
+ /// @brief Resumes running the client's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void resume() {
+ if (!thread_pool_) {
+ isc_throw(InvalidOperation, "HttpClient::resume - no thread pool");
+ }
+
+ // Resume running the thread pool.
+ thread_pool_->run();
+ }
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning() {
+ if (thread_pool_) {
+ return (thread_pool_->isRunning());
+ }
+
+ return (false);
+ }
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool exists and it is in the STOPPED state,
+ /// false otherwise.
+ bool isStopped() {
+ if (thread_pool_) {
+ return (thread_pool_->isStopped());
+ }
+
+ return (false);
+ }
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused() {
+ if (thread_pool_) {
+ return (thread_pool_->isPaused());
+ }
+
+ return (false);
+ }
+
+ /// @brief Fetches the internal IOService used in multi-threaded mode.
+ ///
+ /// @return A pointer to the IOService, or an empty pointer when
+ /// in single-threaded mode.
+ asiolink::IOServicePtr getThreadIOService() {
+ return (thread_io_service_);
+ };
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() {
+ return (thread_pool_size_);
+ }
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return the number of running threads.
+ uint16_t getThreadCount() {
+ if (!thread_pool_) {
+ return (0);
+ }
+ return (thread_pool_->getThreadCount());
+ }
+
+ /// @brief Holds a pointer to the connection pool.
+ ConnectionPoolPtr conn_pool_;
+
+private:
+
+ /// @brief Maxim number of threads in the thread pool.
+ size_t thread_pool_size_;
+
+ /// @brief Pointer to private IOService used in multi-threaded mode.
+ asiolink::IOServicePtr thread_io_service_;
+
+ /// @brief Pool of threads used to service connections in multi-threaded
+ /// mode.
+ IoServiceThreadPoolPtr thread_pool_;
+};
+
+HttpClient::HttpClient(IOService& io_service, bool multi_threading_enabled,
+ size_t thread_pool_size, bool defer_thread_start) {
+ if (!multi_threading_enabled && thread_pool_size) {
+ isc_throw(InvalidOperation,
+ "HttpClient thread_pool_size must be zero "
+ "when Kea core multi-threading is disabled");
+ }
+
+ impl_.reset(new HttpClientImpl(io_service, thread_pool_size,
+ defer_thread_start));
+}
+
+HttpClient::~HttpClient() {
+}
+
+void
+HttpClient::asyncSendRequest(const Url& url,
+ const TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const HttpClient::RequestHandler& request_callback,
+ const HttpClient::RequestTimeout& request_timeout,
+ const HttpClient::ConnectHandler& connect_callback,
+ const HttpClient::HandshakeHandler& handshake_callback,
+ const HttpClient::CloseHandler& close_callback) {
+ if (!url.isValid()) {
+ isc_throw(HttpClientError, "invalid URL specified for the HTTP client");
+ }
+
+ if ((url.getScheme() == Url::Scheme::HTTPS) && !tls_context) {
+ isc_throw(HttpClientError, "HTTPS URL scheme but no TLS context");
+ }
+
+ if (!request) {
+ isc_throw(HttpClientError, "HTTP request must not be null");
+ }
+
+ if (!response) {
+ isc_throw(HttpClientError, "HTTP response must not be null");
+ }
+
+ if (!request_callback) {
+ isc_throw(HttpClientError, "callback for HTTP transaction must not be null");
+ }
+
+ impl_->conn_pool_->queueRequest(url, tls_context, request, response,
+ request_timeout.value_,
+ request_callback, connect_callback,
+ handshake_callback, close_callback);
+}
+
+void
+HttpClient::closeIfOutOfBand(int socket_fd) {
+ return (impl_->conn_pool_->closeIfOutOfBand(socket_fd));
+}
+
+void
+HttpClient::start() {
+ impl_->start();
+}
+
+void
+HttpClient::checkPermissions() {
+ impl_->checkPermissions();
+}
+
+void
+HttpClient::pause() {
+ impl_->pause();
+}
+
+void
+HttpClient::resume() {
+ impl_->resume();
+}
+
+void
+HttpClient::stop() {
+ impl_->stop();
+}
+
+const IOServicePtr
+HttpClient::getThreadIOService() const {
+ return (impl_->getThreadIOService());
+}
+
+uint16_t
+HttpClient::getThreadPoolSize() const {
+ return (impl_->getThreadPoolSize());
+}
+
+uint16_t
+HttpClient::getThreadCount() const {
+ return (impl_->getThreadCount());
+}
+
+bool
+HttpClient::isRunning() {
+ return (impl_->isRunning());
+}
+
+bool
+HttpClient::isStopped() {
+ return (impl_->isStopped());
+}
+
+bool
+HttpClient::isPaused() {
+ return (impl_->isPaused());
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/client.h b/src/lib/http/client.h
new file mode 100644
index 0000000..bea9057
--- /dev/null
+++ b/src/lib/http/client.h
@@ -0,0 +1,344 @@
+// Copyright (C) 2018-2023 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 HTTP_CLIENT_H
+#define HTTP_CLIENT_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/tls_socket.h>
+#include <exceptions/exceptions.h>
+#include <http/url.h>
+#include <http/request.h>
+#include <http/response.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <string>
+#include <thread>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief A generic error raised by the @ref HttpClient class.
+class HttpClientError : public Exception {
+public:
+ HttpClientError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class HttpClientImpl;
+
+/// @brief HTTP client class.
+///
+/// This class implements an asynchronous HTTP client. The caller can schedule
+/// transmission of the HTTP request using @ref HttpClient::asyncSendRequest
+/// method. The caller specifies target URL for each request. The caller also
+/// specifies a pointer to the @ref HttpRequest or derived class, holding a
+/// request that should be transmitted to the destination. Such request must
+/// be finalized, i.e. @ref HttpRequest::finalize method must be called prior
+/// to sending it. The caller must also provide a pointer to the
+/// @ref HttpResponse object or an object derived from it. The type of the
+/// response object must match the expected content type to be returned in the
+/// server's response. The last argument specified in this call is the pointer
+/// to the callback function, which should be launched when the response is
+/// received, an error occurs or when a timeout in the transmission is
+/// signaled.
+///
+/// The HTTP client supports multiple simultaneous and persistent connections
+/// with different destinations. The client determines if the connection is
+/// persistent by looking into the Connection header and HTTP version of the
+/// request. If the connection should be persistent the client doesn't
+/// close the connection after sending a request and receiving a response from
+/// the server. If the client is provided with the request to be sent to the
+/// particular destination, but there is an ongoing communication with this
+/// destination, e.g. as a result of sending previous request, the new
+/// request is queued in the FIFO queue. When the previous request completes,
+/// the next request in the queue for the particular URL will be initiated.
+///
+/// Furthermore, the class supports two modes of operation: single-threaded
+/// and multi-threaded mode. In single-threaded mode, all IO is driven by
+/// an external IOService passed into the class constructor, and ultimately
+/// only a single connection per URL can be open at any given time.
+///
+/// In multi-threaded mode an internal thread pool driven by a private
+/// IOService instance is used to support multiple concurrent connections
+/// per URL. Currently, the number of connections per URL is set to the
+/// number of threads in the thread pool.
+///
+/// The client tests the persistent connection for usability before sending
+/// a request by trying to read from the socket (with message peeking). If
+/// the socket is usable the client uses it to transmit the request.
+///
+/// This classes exposes the underlying transport socket's descriptor for
+/// each connection via connect, handshake and close callbacks.
+/// This is done to permit the sockets to be monitored for IO readiness
+/// by external code that's something other than boost::asio
+/// (e.g.select() or epoll()), and would thus otherwise starve the
+/// client's IOService and cause a backlog of ready event handlers.
+///
+/// All errors are reported to the caller via the callback function supplied
+/// to the @ref HttpClient::asyncSendRequest. The IO errors are communicated
+/// via the @c boost::system::error code value. The response parsing errors
+/// are returned via the 3rd parameter of the callback.
+class HttpClient {
+public:
+ /// @brief HTTP request/response timeout value.
+ struct RequestTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Request/response timeout value in milliseconds.
+ explicit RequestTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Timeout value specified.
+ };
+
+ /// @brief Callback type used in call to @ref HttpClient::asyncSendRequest.
+ typedef std::function<void(const boost::system::error_code&,
+ const HttpResponsePtr&,
+ const std::string&)> RequestHandler;
+
+ /// @brief Optional handler invoked when client connects to the server.
+ ///
+ /// Returned boolean value indicates whether the client should continue
+ /// connecting to the server (if true) or not (false).
+ /// It is passed the IO error code along with the native socket handle of
+ /// the connection's TCP socket. The passed socket descriptor may be used
+ /// to monitor the readiness of the events via select() or epoll().
+ ///
+ /// @note Beware that the IO error code can be set to "in progress"
+ /// so a not null error code does not always mean the connect failed.
+ typedef std::function<bool(const boost::system::error_code&, const int)> ConnectHandler;
+
+ /// @brief Optional handler invoked when client performs the TLS handshake
+ /// with the server.
+ ///
+ /// It is called when the TLS handshake completes:
+ /// - if the handshake succeeds it is called with error code 0
+ /// - if the handshake fails it is called with error code != 0
+ /// - if TLS is not enabled, it is not called at all
+ ///
+ /// Returned boolean value indicates whether the client should continue
+ /// connecting to the server (if true) or not (false).
+ /// @note The second argument is not used.
+ typedef std::function<bool(const boost::system::error_code&, const int)> HandshakeHandler;
+
+ /// @brief Optional handler invoked when client closes the connection to the server.
+ ///
+ /// It is passed the native socket handler of the connection's TCP socket.
+ typedef std::function<void(const int)> CloseHandler;
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the HTTP client.
+ /// @param multi_threading_enabled The flag which indicates if MT is enabled.
+ /// @param thread_pool_size maximum number of threads in the thread pool.
+ /// A value greater than zero enables multi-threaded mode and sets the
+ /// maximum number of concurrent connections per URL. A value of zero
+ /// (default) enables single-threaded mode with one connection per URL.
+ /// @param defer_thread_start When true, starting of the pool threads is
+ /// deferred until a subsequent call to @ref start(). In this case the
+ /// pool's operational state after construction is STOPPED. Otherwise,
+ /// the thread pool threads will be created and started, with the
+ /// operational state being RUNNING. Applicable only when thread-pool size
+ /// is greater than zero.
+ explicit HttpClient(asiolink::IOService& io_service,
+ bool multi_threading_enabled,
+ size_t thread_pool_size = 0,
+ bool defer_thread_start = false);
+
+ /// @brief Destructor.
+ ~HttpClient();
+
+ /// @brief Queues new asynchronous HTTP request for a given URL.
+ ///
+ /// The client maintains an internal connection pool which manages lists
+ /// of connections per URL. In single-threaded mode, each URL is limited
+ /// to a single connection. In multi-threaded mode, each URL may have
+ /// more than one open connection per URL, enabling the client to carry
+ /// on multiple concurrent requests per URL.
+ ///
+ /// The client will search the pool for an open, idle connection for the
+ /// given URL. If there are no idle connections, the client will open
+ /// a new connection up to the maximum number of connections allowed by the
+ /// thread mode. If all possible connections are busy, the request is
+ /// pushed on to back of a URL-specific FIFO queue of pending requests.
+ ///
+ /// If however, there is an idle connection available than a new transaction
+ /// for the request will be initiated immediately upon that connection.
+ ///
+ /// Note that when a connection completes a transaction, and its URL
+ /// queue is not empty, it will pop a pending request from the front of
+ /// the queue and begin a new transaction for that request. The net effect
+ /// is that requests are always pulled from the front of the queue unless
+ /// the queue is empty.
+ ///
+ /// The existing connection is tested before it is used for the new
+ /// transaction by attempting to read (with message peeking) from
+ /// the open transport socket. If the read attempt is successful,
+ /// the client will transmit the HTTP request to the server using
+ /// this connection. It is possible that the server closes the
+ /// connection between the connection test and sending the request.
+ /// In such case, an error will be returned and the caller will
+ /// need to try re-sending the request.
+ ///
+ /// If the connection test fails, the client will close the socket and
+ /// reconnect to the server prior to sending the request.
+ ///
+ /// Pointers to the request and response objects are provided as arguments
+ /// of this method. These pointers should have appropriate types derived
+ /// from the @ref HttpRequest and @ref HttpResponse classes. For example,
+ /// if the request has content type "application/json", a pointer to the
+ /// @ref HttpResponseJson should be passed. In this case, the response type
+ /// should be @ref HttpResponseJson. These types are used to validate both
+ /// the request provided by the caller and the response received from the
+ /// server.
+ ///
+ /// The callback function provided by the caller is invoked when the
+ /// transaction terminates, i.e. when the server has responded or when an
+ /// error occurred. The callback is expected to be exception safe, but the
+ /// client internally guards against exceptions thrown by the callback.
+ ///
+ /// The first argument of the callback indicates an IO error during
+ /// communication with the server. If the communication is successful the
+ /// error code of 0 is returned. However, in this case it is still possible
+ /// that the transaction is unsuccessful due to HTTP response parsing error,
+ /// e.g. invalid content type, malformed response etc. Such errors are
+ /// indicated via third argument.
+ ///
+ /// If message parsing was successful the second argument of the callback
+ /// contains a pointer to the parsed response (the same pointer as provided
+ /// by the caller as the argument). If parsing was unsuccessful, the null
+ /// pointer is returned.
+ ///
+ /// The default timeout for the transaction is set to 10 seconds
+ /// (10 000 ms). If the timeout occurs, the callback is invoked with the
+ /// error code of @c boost::asio::error::timed_out.
+ /// The timeout covers both the connect and the transaction phases
+ /// so when connecting to the server takes too long (e.g. with a
+ /// misconfigured firewall) the timeout is triggered. The connect
+ /// callback can be used to recognize this condition.
+ ///
+ /// @param url URL where the request should be send.
+ /// @param tls_context TLS context.
+ /// @param request Pointer to the object holding a request.
+ /// @param response Pointer to the object where response should be stored.
+ /// @param request_callback Pointer to the user callback function invoked
+ /// when transaction ends.
+ /// @param request_timeout Timeout for the transaction in milliseconds.
+ /// @param connect_callback Optional callback invoked when the client
+ /// connects to the server.
+ /// @param handshake_callback Optional callback invoked when the client
+ /// performs the TLS handshake with the server.
+ /// @param close_callback Optional callback invoked when the client
+ /// closes the connection to the server.
+ ///
+ /// @throw HttpClientError If invalid arguments were provided.
+ void asyncSendRequest(const Url& url,
+ const asiolink::TlsContextPtr& tls_context,
+ const HttpRequestPtr& request,
+ const HttpResponsePtr& response,
+ const RequestHandler& request_callback,
+ const RequestTimeout& request_timeout =
+ RequestTimeout(10000),
+ const ConnectHandler& connect_callback =
+ ConnectHandler(),
+ const HandshakeHandler& handshake_callback =
+ HandshakeHandler(),
+ const CloseHandler& close_callback =
+ CloseHandler());
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions();
+
+ /// @brief Starts running the client's thread pool, if multi-threaded.
+ void start();
+
+ /// @brief Pauses the client's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void pause();
+
+ /// @brief Resumes running the client's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ /// @throw InvalidOperation if the thread pool does not exist.
+ void resume();
+
+ /// @brief Halts client-side IO activity.
+ ///
+ /// Closes all connections, discards any queued requests, and in
+ /// multi-threaded mode discards the thread-pool and the internal
+ /// IOService.
+ void stop();
+
+ /// @brief Closes a connection if it has an out-of-band socket event
+ ///
+ /// If the client owns a connection using the given socket and that
+ /// connection is currently in a transaction the method returns as this
+ /// indicates a normal ready event. If the connection is not in an
+ /// ongoing transaction, then the connection is closed.
+ ///
+ /// This is method is intended to be used to detect and clean up then
+ /// sockets that are marked ready outside of transactions. The most common
+ /// case is the other end of the socket being closed.
+ ///
+ /// @param socket_fd socket descriptor to check
+ void closeIfOutOfBand(int socket_fd);
+
+ /// @brief Fetches a pointer to the internal IOService used to
+ /// drive the thread-pool in multi-threaded mode.
+ ///
+ /// @return pointer to the IOService instance, or an empty pointer
+ /// in single-threaded mode.
+ const asiolink::IOServicePtr getThreadIOService() const;
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() const;
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return the number of running threads.
+ uint16_t getThreadCount() const;
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning();
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool exists and it is in the STOPPED state,
+ /// false otherwise.
+ bool isStopped();
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused();
+
+private:
+
+ /// @brief Pointer to the HTTP client implementation.
+ boost::shared_ptr<HttpClientImpl> impl_;
+};
+
+/// @brief Defines a pointer to an HttpClient instance.
+typedef boost::shared_ptr<HttpClient> HttpClientPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/connection.cc b/src/lib/http/connection.cc
new file mode 100644
index 0000000..b1e57bd
--- /dev/null
+++ b/src/lib/http/connection.cc
@@ -0,0 +1,600 @@
+// Copyright (C) 2017-2022 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 <asiolink/asio_wrapper.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/http_log.h>
+#include <http/http_messages.h>
+#include <boost/make_shared.hpp>
+#include <functional>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the HTTP message that can be logged.
+///
+/// The part of the HTTP message beyond this value is truncated.
+constexpr size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
+
+}
+
+namespace isc {
+namespace http {
+
+HttpConnection::Transaction::Transaction(const HttpResponseCreatorPtr& response_creator,
+ const HttpRequestPtr& request)
+ : request_(request ? request : response_creator->createNewHttpRequest()),
+ parser_(new HttpRequestParser(*request_)),
+ input_buf_(),
+ output_buf_() {
+ parser_->initModel();
+}
+
+HttpConnection::TransactionPtr
+HttpConnection::Transaction::create(const HttpResponseCreatorPtr& response_creator) {
+ return (boost::make_shared<Transaction>(response_creator));
+}
+
+HttpConnection::TransactionPtr
+HttpConnection::Transaction::spawn(const HttpResponseCreatorPtr& response_creator,
+ const TransactionPtr& transaction) {
+ if (transaction) {
+ return (boost::make_shared<Transaction>(response_creator,
+ transaction->getRequest()));
+ }
+ return (create(response_creator));
+}
+
+void
+HttpConnection::
+SocketCallback::operator()(boost::system::error_code ec, size_t length) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ callback_(ec, length);
+}
+
+HttpConnection::HttpConnection(asiolink::IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : request_timer_(io_service),
+ request_timeout_(request_timeout),
+ tls_context_(tls_context),
+ idle_timeout_(idle_timeout),
+ tcp_socket_(),
+ tls_socket_(),
+ acceptor_(acceptor),
+ connection_pool_(connection_pool),
+ response_creator_(response_creator),
+ acceptor_callback_(callback) {
+ if (!tls_context) {
+ tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
+ } else {
+ tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
+ tls_context));
+ }
+}
+
+HttpConnection::~HttpConnection() {
+ close();
+}
+
+void
+HttpConnection::recordParameters(const HttpRequestPtr& request) const {
+ if (!request) {
+ // Should never happen.
+ return;
+ }
+
+ // Record the remote address.
+ request->setRemote(getRemoteEndpointAddressAsText());
+
+ // Record TLS parameters.
+ if (!tls_socket_) {
+ return;
+ }
+
+ // The connection uses HTTPS aka HTTP over TLS.
+ request->setTls(true);
+
+ // Record the first commonName of the subjectName of the client
+ // certificate when wanted.
+ if (HttpRequest::recordSubject_) {
+ request->setSubject(tls_socket_->getTlsStream().getSubject());
+ }
+
+ // Record the first commonName of the issuerName of the client
+ // certificate when wanted.
+ if (HttpRequest::recordIssuer_) {
+ request->setIssuer(tls_socket_->getTlsStream().getIssuer());
+ }
+}
+
+void
+HttpConnection::shutdownCallback(const boost::system::error_code&) {
+ tls_socket_->close();
+}
+
+void
+HttpConnection::shutdown() {
+ request_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+ if (tls_socket_) {
+ // Create instance of the callback to close the socket.
+ SocketCallback cb(std::bind(&HttpConnection::shutdownCallback,
+ shared_from_this(),
+ ph::_1)); // error_code
+ tls_socket_->shutdown(cb);
+ return;
+ }
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to shutdown the socket");
+}
+
+void
+HttpConnection::close() {
+ request_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->close();
+ return;
+ }
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to close the socket");
+}
+
+void
+HttpConnection::shutdownConnection() {
+ try {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_CONNECTION_SHUTDOWN)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.shutdown(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(http_logger, HTTP_CONNECTION_SHUTDOWN_FAILED);
+ }
+}
+
+void
+HttpConnection::stopThisConnection() {
+ try {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_CONNECTION_STOP)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.stop(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(http_logger, HTTP_CONNECTION_STOP_FAILED);
+ }
+}
+
+void
+HttpConnection::asyncAccept() {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ HttpAcceptorCallback cb = std::bind(&HttpConnection::acceptorCallback,
+ shared_from_this(),
+ ph::_1); // error
+ try {
+ HttpsAcceptorPtr tls_acceptor =
+ boost::dynamic_pointer_cast<HttpsAcceptor>(acceptor_);
+ if (!tls_acceptor) {
+ if (!tcp_socket_) {
+ isc_throw(Unexpected, "internal error: TCP socket is null");
+ }
+ acceptor_->asyncAccept(*tcp_socket_, cb);
+ } else {
+ if (!tls_socket_) {
+ isc_throw(Unexpected, "internal error: TLS socket is null");
+ }
+ tls_acceptor->asyncAccept(*tls_socket_, cb);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(HttpConnectionError, "unable to start accepting TCP "
+ "connections: " << ex.what());
+ }
+}
+
+void
+HttpConnection::doHandshake() {
+ // Skip the handshake if the socket is not a TLS one.
+ if (!tls_socket_) {
+ doRead();
+ return;
+ }
+
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&HttpConnection::handshakeCallback,
+ shared_from_this(),
+ ph::_1)); // error
+ try {
+ tls_socket_->handshake(cb);
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpConnectionError, "unable to perform TLS handshake: "
+ << ex.what());
+ }
+}
+
+void
+HttpConnection::doRead(TransactionPtr transaction) {
+ try {
+ TCPEndpoint endpoint;
+
+ // Transaction hasn't been created if we are starting to read the
+ // new request.
+ if (!transaction) {
+ transaction = Transaction::create(response_creator_);
+ recordParameters(transaction->getRequest());
+ }
+
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying std functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&HttpConnection::socketReadCallback,
+ shared_from_this(),
+ transaction,
+ ph::_1, // error
+ ph::_2)); //bytes_transferred
+ if (tcp_socket_) {
+ tcp_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
+ transaction->getInputBufSize(),
+ 0, &endpoint, cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncReceive(static_cast<void*>(transaction->getInputBufData()),
+ transaction->getInputBufSize(),
+ 0, &endpoint, cb);
+ return;
+ }
+ } catch (...) {
+ stopThisConnection();
+ }
+}
+
+void
+HttpConnection::doWrite(HttpConnection::TransactionPtr transaction) {
+ try {
+ if (transaction->outputDataAvail()) {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying std functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&HttpConnection::socketWriteCallback,
+ shared_from_this(),
+ transaction,
+ ph::_1, // error
+ ph::_2)); // bytes_transferred
+ if (tcp_socket_) {
+ tcp_socket_->asyncSend(transaction->getOutputBufData(),
+ transaction->getOutputBufSize(),
+ cb);
+ return;
+ }
+ if (tls_socket_) {
+ tls_socket_->asyncSend(transaction->getOutputBufData(),
+ transaction->getOutputBufSize(),
+ cb);
+ return;
+ }
+ } else {
+ // The isPersistent() function may throw if the request hasn't
+ // been created, i.e. the HTTP headers weren't parsed. We catch
+ // this exception below and close the connection since we're
+ // unable to tell if the connection should remain persistent
+ // or not. The default is to close it.
+ if (!transaction->getRequest()->isPersistent()) {
+ stopThisConnection();
+
+ } else {
+ // The connection is persistent and we are done sending
+ // the previous response. Start listening for the next
+ // requests.
+ setupIdleTimer();
+ doRead();
+ }
+ }
+ } catch (...) {
+ stopThisConnection();
+ }
+}
+
+void
+HttpConnection::asyncSendResponse(const ConstHttpResponsePtr& response,
+ TransactionPtr transaction) {
+ transaction->setOutputBuf(response->toString());
+ doWrite(transaction);
+}
+
+
+void
+HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
+ if (!acceptor_->isOpen()) {
+ return;
+ }
+
+ if (ec) {
+ stopThisConnection();
+ }
+
+ acceptor_callback_(ec);
+
+ if (!ec) {
+ if (!tls_context_) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(request_timeout_/1000));
+ } else {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_CONNECTION_HANDSHAKE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(request_timeout_/1000));
+ }
+
+ setupRequestTimer();
+ doHandshake();
+ }
+}
+
+void
+HttpConnection::handshakeCallback(const boost::system::error_code& ec) {
+ if (ec) {
+ LOG_INFO(http_logger, HTTP_CONNECTION_HANDSHAKE_FAILED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ec.message());
+ stopThisConnection();
+ } else {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTPS_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText());
+
+ doRead();
+ }
+}
+
+void
+HttpConnection::socketReadCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt. Just make sure
+ // we don't try to read anything now in case there is any garbage
+ // passed in length.
+ } else {
+ length = 0;
+ }
+ }
+
+ // Receiving is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+
+ if (length != 0) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ HTTP_DATA_RECEIVED)
+ .arg(length)
+ .arg(getRemoteEndpointAddressAsText());
+
+ transaction->getParser()->postBuffer(static_cast<void*>(transaction->getInputBufData()),
+ length);
+ transaction->getParser()->poll();
+ }
+
+ if (transaction->getParser()->needData()) {
+ // The parser indicates that the some part of the message being
+ // received is still missing, so continue to read.
+ doRead(transaction);
+
+ } else {
+ try {
+ // The whole message has been received, so let's finalize it.
+ transaction->getRequest()->finalize();
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_CLIENT_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_CLIENT_REQUEST_RECEIVED_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+
+ } catch (const std::exception& ex) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_BAD_CLIENT_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ex.what());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(transaction->getParser()->getBufferAsString(MAX_LOGGED_MESSAGE_SIZE));
+ }
+
+ // Don't want to timeout if creation of the response takes long.
+ request_timer_.cancel();
+
+ // Create the response from the received request using the custom
+ // response creator.
+ HttpResponsePtr response = response_creator_->createHttpResponse(transaction->getRequest());
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC,
+ HTTP_SERVER_RESPONSE_SEND)
+ .arg(response->toBriefString())
+ .arg(getRemoteEndpointAddressAsText());
+
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_BASIC_DATA,
+ HTTP_SERVER_RESPONSE_SEND_DETAILS)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(HttpMessageParserBase::logFormatHttpMessage(response->toString(),
+ MAX_LOGGED_MESSAGE_SIZE));
+
+ // Response created. Activate the timer again.
+ setupRequestTimer(transaction);
+
+ // Start sending the response.
+ asyncSendResponse(response, transaction);
+ }
+}
+
+void
+HttpConnection::socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt.
+ } else {
+ // Sending is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+
+ doWrite(transaction);
+ }
+ }
+
+ // Since each transaction has its own output buffer, it is not really
+ // possible that the number of bytes written is larger than the size
+ // of the buffer. But, let's be safe and set the length to the size
+ // of the buffer if that unexpected condition occurs.
+ if (length > transaction->getOutputBufSize()) {
+ length = transaction->getOutputBufSize();
+ }
+
+ if (length <= transaction->getOutputBufSize()) {
+ // Sending is in progress, so push back the timeout.
+ setupRequestTimer(transaction);
+ }
+
+ // Eat the 'length' number of bytes from the output buffer and only
+ // leave the part of the response that hasn't been sent.
+ transaction->consumeOutputBuf(length);
+
+ // Schedule the write of the unsent data.
+ doWrite(transaction);
+}
+
+void
+HttpConnection::setupRequestTimer(TransactionPtr transaction) {
+ // Pass raw pointer rather than shared_ptr to this object,
+ // because IntervalTimer already passes shared pointer to the
+ // IntervalTimerImpl to make sure that the callback remains
+ // valid.
+ request_timer_.setup(std::bind(&HttpConnection::requestTimeoutCallback,
+ this, transaction),
+ request_timeout_, IntervalTimer::ONE_SHOT);
+}
+
+void
+HttpConnection::setupIdleTimer() {
+ request_timer_.setup(std::bind(&HttpConnection::idleTimeoutCallback,
+ this),
+ idle_timeout_, IntervalTimer::ONE_SHOT);
+}
+
+void
+HttpConnection::requestTimeoutCallback(TransactionPtr transaction) {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+
+ // We need to differentiate the transactions between a normal response and the
+ // timeout. We create new transaction from the current transaction. It is
+ // to preserve the request we're responding to.
+ auto spawned_transaction = Transaction::spawn(response_creator_, transaction);
+
+ // The new transaction inherits the request from the original transaction
+ // if such transaction exists.
+ auto request = spawned_transaction->getRequest();
+
+ // Depending on when the timeout occurred, the HTTP version of the request
+ // may or may not be available. Therefore we check if the HTTP version is
+ // set in the request. If it is not available, we need to create a dummy
+ // request with the default HTTP/1.0 version. This version will be used
+ // in the response.
+ if (request->context()->http_version_major_ == 0) {
+ request.reset(new HttpRequest(HttpRequest::Method::HTTP_POST, "/",
+ HttpVersion::HTTP_10(),
+ HostHttpHeader("dummy")));
+ request->finalize();
+ }
+
+ // Create the timeout response.
+ HttpResponsePtr response =
+ response_creator_->createStockHttpResponse(request,
+ HttpStatusCode::REQUEST_TIMEOUT);
+
+ // Send the HTTP 408 status.
+ asyncSendResponse(response, spawned_transaction);
+}
+
+void
+HttpConnection::idleTimeoutCallback() {
+ LOG_DEBUG(http_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+ // In theory we should shutdown first and stop/close after but
+ // it is better to put the connection management responsibility
+ // on the client... so simply drop idle connections.
+ stopThisConnection();
+}
+
+std::string
+HttpConnection::getRemoteEndpointAddressAsText() const {
+ try {
+ if (tcp_socket_) {
+ if (tcp_socket_->getASIOSocket().is_open()) {
+ return (tcp_socket_->getASIOSocket().remote_endpoint().address().to_string());
+ }
+ } else if (tls_socket_) {
+ if (tls_socket_->getASIOSocket().is_open()) {
+ return (tls_socket_->getASIOSocket().remote_endpoint().address().to_string());
+ }
+ }
+ } catch (...) {
+ }
+ return ("(unknown address)");
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/connection.h b/src/lib/http/connection.h
new file mode 100644
index 0000000..70109de
--- /dev/null
+++ b/src/lib/http/connection.h
@@ -0,0 +1,432 @@
+// Copyright (C) 2017-2022 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 HTTP_CONNECTION_H
+#define HTTP_CONNECTION_H
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/request_parser.h>
+#include <http/response_creator_factory.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/shared_ptr.hpp>
+#include <array>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic error reported within @ref HttpConnection class.
+class HttpConnectionError : public Exception {
+public:
+ HttpConnectionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the @ref HttpConnectionPool.
+///
+/// This declaration is needed because we don't include the header file
+/// declaring @ref HttpConnectionPool to avoid circular inclusion.
+class HttpConnectionPool;
+
+class HttpConnection;
+/// @brief Pointer to the @ref HttpConnection.
+typedef boost::shared_ptr<HttpConnection> HttpConnectionPtr;
+
+/// @brief Accepts and handles a single HTTP connection.
+class HttpConnection : public boost::enable_shared_from_this<HttpConnection> {
+private:
+
+ /// @brief Type of the function implementing a callback invoked by the
+ /// @c SocketCallback functor.
+ typedef std::function<void(boost::system::error_code ec, size_t length)>
+ SocketCallbackFunction;
+
+ /// @brief Functor associated with the socket object.
+ ///
+ /// This functor calls a callback function specified in the constructor.
+ class SocketCallback {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param socket_callback Callback to be invoked by the functor upon
+ /// an event associated with the socket.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Operator called when event associated with a socket occurs.
+ ///
+ /// This operator returns immediately when received error code is
+ /// @c boost::system::error_code is equal to
+ /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+ /// invoked.
+ ///
+ /// @param ec Error code.
+ /// @param length Data length.
+ void operator()(boost::system::error_code ec, size_t length = 0);
+
+ private:
+ /// @brief Supplied callback.
+ SocketCallbackFunction callback_;
+ };
+
+protected:
+
+ class Transaction;
+
+ /// @brief Shared pointer to the @c Transaction.
+ typedef boost::shared_ptr<Transaction> TransactionPtr;
+
+ /// @brief Represents a single exchange of the HTTP messages.
+ ///
+ /// In HTTP/1.1 multiple HTTP message exchanges may be conducted
+ /// over the same persistent connection before the connection is
+ /// closed. Since ASIO handlers for these exchanges may be sometimes
+ /// executed out of order, there is a need to associate the states of
+ /// the exchanges with the appropriate ASIO handlers. This object
+ /// represents such state and includes: received request, request
+ /// parser (being in the particular state of parsing), input buffer
+ /// and the output buffer.
+ ///
+ /// The new @c Transaction instance is created when the connection
+ /// is established and the server starts receiving the HTTP request.
+ /// The shared pointer to the created transaction is passed between
+ /// the asynchronous handlers. Therefore, as long as the asynchronous
+ /// communication is conducted the instance of the transaction is
+ /// held by the IO service which runs the handlers. The transaction
+ /// instance exists as long as the asynchronous handlers for the
+ /// given request/response exchange are executed. When the server
+ /// responds to the client and all corresponding IO handlers are
+ /// invoked the transaction is automatically destroyed.
+ ///
+ /// The timeout may occur anytime during the transaction. In such
+ /// cases, a new transaction instance is created to send the
+ /// HTTP 408 (timeout) response to the client. Creation of the
+ /// new transaction for the timeout response is necessary because
+ /// there may be some asynchronous handlers scheduled by the
+ /// original transaction which rely on the original transaction's
+ /// state. The timeout response's state is held within the new
+ /// transaction spawned from the original transaction.
+ class Transaction {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param response_creator Pointer to the response creator being
+ /// used for generating a response from the request.
+ /// @param request Pointer to the HTTP request. If the request is
+ /// null, the constructor creates new request instance using the
+ /// provided response creator.
+ Transaction(const HttpResponseCreatorPtr& response_creator,
+ const HttpRequestPtr& request = HttpRequestPtr());
+
+ /// @brief Creates new transaction instance.
+ ///
+ /// It is called when the HTTP server has just scheduled asynchronous
+ /// reading of the HTTP message.
+ ///
+ /// @param response_creator Pointer to the response creator to be passed
+ /// to the transaction's constructor.
+ ///
+ /// @return Pointer to the created transaction instance.
+ static TransactionPtr create(const HttpResponseCreatorPtr& response_creator);
+
+ /// @brief Creates new transaction from the current transaction.
+ ///
+ /// This method creates new transaction and inherits the request
+ /// from the existing transaction. This is used when the timeout
+ /// occurs during the messages exchange. The server creates the new
+ /// transaction to handle the timeout but this new transaction must
+ /// include the request instance so as HTTP version information can
+ /// be inferred from it while sending the timeout response. The
+ /// HTTP version information should match between the request and
+ /// the response.
+ ///
+ /// @param response_creator Pointer to the response creator.
+ /// @param transaction Existing transaction from which the request
+ /// should be inherited. If the transaction is null, the new (dummy)
+ /// request is created for the new transaction.
+ static TransactionPtr spawn(const HttpResponseCreatorPtr& response_creator,
+ const TransactionPtr& transaction);
+
+ /// @brief Returns request instance associated with the transaction.
+ HttpRequestPtr getRequest() const {
+ return (request_);
+ }
+
+ /// @brief Returns parser instance associated with the transaction.
+ HttpRequestParserPtr getParser() const {
+ return (parser_);
+ }
+
+ /// @brief Returns pointer to the first byte of the input buffer.
+ char* getInputBufData() {
+ return (input_buf_.data());
+ }
+
+ /// @brief Returns input buffer size.
+ size_t getInputBufSize() const {
+ return (input_buf_.size());
+ }
+
+ /// @brief Checks if the output buffer contains some data to be
+ /// sent.
+ ///
+ /// @return true if the output buffer contains data to be sent,
+ /// false otherwise.
+ bool outputDataAvail() const {
+ return (!output_buf_.empty());
+ }
+
+ /// @brief Returns pointer to the first byte of the output buffer.
+ const char* getOutputBufData() const {
+ return (output_buf_.data());
+ }
+
+ /// @brief Returns size of the output buffer.
+ size_t getOutputBufSize() const {
+ return (output_buf_.size());
+ }
+
+ /// @brief Replaces output buffer contents with new contents.
+ ///
+ /// @param response New contents for the output buffer.
+ void setOutputBuf(const std::string& response) {
+ output_buf_ = response;
+ }
+
+ /// @brief Erases n bytes from the beginning of the output buffer.
+ ///
+ /// @param length Number of bytes to be erased.
+ void consumeOutputBuf(const size_t length) {
+ output_buf_.erase(0, length);
+ }
+
+ private:
+
+ /// @brief Pointer to the request received over this connection.
+ HttpRequestPtr request_;
+
+ /// @brief Pointer to the HTTP request parser.
+ HttpRequestParserPtr parser_;
+
+ /// @brief Buffer for received data.
+ std::array<char, 32768> input_buf_;
+
+ /// @brief Buffer used for outbound data.
+ std::string output_buf_;
+ };
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnection(asiolink::IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const asiolink::TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout);
+
+ /// @brief Destructor.
+ ///
+ /// Closes current connection.
+ virtual ~HttpConnection();
+
+ /// @brief Asynchronously accepts new connection.
+ ///
+ /// When the connection is established successfully, the timeout timer is
+ /// setup and the asynchronous handshake with client is performed.
+ void asyncAccept();
+
+ /// @brief Shutdown the socket.
+ void shutdown();
+
+ /// @brief Closes the socket.
+ void close();
+
+ /// @brief Records connection parameters into the HTTP request.
+ ///
+ /// @param request Pointer to the HTTP request.
+ void recordParameters(const HttpRequestPtr& request) const;
+
+ /// @brief Asynchronously performs TLS handshake.
+ ///
+ /// When the handshake is performed successfully or skipped because TLS
+ /// was not enabled, the asynchronous read from the socket is started.
+ void doHandshake();
+
+ /// @brief Starts asynchronous read from the socket.
+ ///
+ /// The data received over the socket are supplied to the HTTP parser until
+ /// the parser signals that the entire request has been received or until
+ /// the parser signals an error. In the former case the server creates an
+ /// HTTP response using supplied response creator object.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param transaction Pointer to the transaction for which the read
+ /// operation should be performed. It defaults to null pointer which
+ /// indicates that this function should create new transaction.
+ void doRead(TransactionPtr transaction = TransactionPtr());
+
+protected:
+
+ /// @brief Starts asynchronous write to the socket.
+ ///
+ /// The @c output_buf_ must contain the data to be sent.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param transaction Pointer to the transaction for which the write
+ /// operation should be performed.
+ void doWrite(TransactionPtr transaction);
+
+ /// @brief Sends HTTP response asynchronously.
+ ///
+ /// Internally it calls @ref HttpConnection::doWrite to send the data.
+ ///
+ /// @param response Pointer to the HTTP response to be sent.
+ /// @param transaction Pointer to the transaction.
+ void asyncSendResponse(const ConstHttpResponsePtr& response,
+ TransactionPtr transaction);
+
+ /// @brief Local callback invoked when new connection is accepted.
+ ///
+ /// It invokes external (supplied via constructor) acceptor callback. If
+ /// the acceptor is not opened it returns immediately. If the connection
+ /// is accepted successfully the @ref HttpConnection::doRead or
+ /// @ref HttpConnection::doHandshake is called.
+ ///
+ /// @param ec Error code.
+ void acceptorCallback(const boost::system::error_code& ec);
+
+ /// @brief Local callback invoked when TLS handshake is performed.
+ ///
+ /// If the handshake is performed successfully the @ref
+ /// HttpConnection::doRead is called.
+ ///
+ /// @param ec Error code.
+ void handshakeCallback(const boost::system::error_code& ec);
+
+ /// @brief Callback invoked when new data is received over the socket.
+ ///
+ /// This callback supplies the data to the HTTP parser and continues
+ /// parsing. When the parser signals end of the HTTP request the callback
+ /// prepares a response and starts asynchronous send over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the received data.
+ void socketReadCallback(TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when TLS shutdown is performed.
+ ///
+ /// The TLS socket is unconditionally closed but the callback is called
+ /// only when the peer has answered so the connection should be
+ /// explicitly closed in all cases, i.e. do not rely on this handler.
+ ///
+ /// @param ec Error code (ignored).
+ void shutdownCallback(const boost::system::error_code& ec);
+
+ /// @brief Reset timer for detecting request timeouts.
+ ///
+ /// @param transaction Pointer to the transaction to be guarded by the timeout.
+ void setupRequestTimer(TransactionPtr transaction = TransactionPtr());
+
+ /// @brief Reset timer for detecting idle timeout in persistent connections.
+ void setupIdleTimer();
+
+ /// @brief Callback invoked when the HTTP Request Timeout occurs.
+ ///
+ /// This callback creates HTTP response with Request Timeout error code
+ /// and sends it to the client.
+ ///
+ /// @param transaction Pointer to the transaction for which timeout occurs.
+ void requestTimeoutCallback(TransactionPtr transaction);
+
+ void idleTimeoutCallback();
+
+ /// @brief Shuts down current connection.
+ ///
+ /// Copied from the next method @ref stopThisConnection
+ void shutdownConnection();
+
+ /// @brief Stops current connection.
+ void stopThisConnection();
+
+ /// @brief returns remote address in textual form
+ std::string getRemoteEndpointAddressAsText() const;
+
+ /// @brief Timer used to detect Request Timeout.
+ asiolink::IntervalTimer request_timer_;
+
+ /// @brief Configured Request Timeout in milliseconds.
+ long request_timeout_;
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Timeout after which the persistent HTTP connection is shut
+ /// down by the server.
+ long idle_timeout_;
+
+ /// @brief TCP socket used by this connection.
+ std::unique_ptr<asiolink::TCPSocket<SocketCallback> > tcp_socket_;
+
+ /// @brief TLS socket used by this connection.
+ std::unique_ptr<asiolink::TLSSocket<SocketCallback> > tls_socket_;
+
+ /// @brief Pointer to the TCP acceptor used to accept new connections.
+ HttpAcceptorPtr acceptor_;
+
+ /// @brief Connection pool holding this connection.
+ HttpConnectionPool& connection_pool_;
+
+ /// @brief Pointer to the @ref HttpResponseCreator object used to create
+ /// HTTP responses.
+ HttpResponseCreatorPtr response_creator_;
+
+ /// @brief External TCP acceptor callback.
+ HttpAcceptorCallback acceptor_callback_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/connection_pool.cc b/src/lib/http/connection_pool.cc
new file mode 100644
index 0000000..de92eb2
--- /dev/null
+++ b/src/lib/http/connection_pool.cc
@@ -0,0 +1,74 @@
+// Copyright (C) 2017-2021 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 <asiolink/asio_wrapper.h>
+#include <http/connection_pool.h>
+#include <util/multi_threading_mgr.h>
+
+namespace isc {
+namespace http {
+
+void
+HttpConnectionPool::start(const HttpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.insert(connections_.end(), connection);
+ } else {
+ connections_.insert(connections_.end(), connection);
+ }
+
+ connection->asyncAccept();
+}
+
+void
+HttpConnectionPool::stop(const HttpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.remove(connection);
+ } else {
+ connections_.remove(connection);
+ }
+
+ connection->close();
+}
+
+void
+HttpConnectionPool::shutdown(const HttpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.remove(connection);
+ } else {
+ connections_.remove(connection);
+ }
+
+ connection->shutdown();
+}
+
+void
+HttpConnectionPool::stopAll() {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ stopAllInternal();
+ } else {
+ stopAllInternal();
+ }
+}
+
+void
+HttpConnectionPool::stopAllInternal() {
+ for (auto connection = connections_.begin();
+ connection != connections_.end();
+ ++connection) {
+ (*connection)->close();
+ }
+
+ connections_.clear();
+}
+
+}
+}
diff --git a/src/lib/http/connection_pool.h b/src/lib/http/connection_pool.h
new file mode 100644
index 0000000..a9110bc
--- /dev/null
+++ b/src/lib/http/connection_pool.h
@@ -0,0 +1,78 @@
+// Copyright (C) 2017-2021 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 HTTP_CONNECTION_POOL_H
+#define HTTP_CONNECTION_POOL_H
+
+#include <http/connection.h>
+
+#include <list>
+#include <mutex>
+
+namespace isc {
+namespace http {
+
+/// @brief Pool of active HTTP connections.
+///
+/// The HTTP server is designed to handle many connections simultaneously.
+/// The communication between the client and the server may take long time
+/// and the server must be able to react on other events while the communication
+/// with the clients is in progress. Thus, the server must track active
+/// connections and gracefully close them when needed. An obvious case when the
+/// connections must be terminated by the server is when the shutdown signal
+/// is received.
+///
+/// This object is a simple container for the server connections which provides
+/// means to terminate them on request.
+class HttpConnectionPool {
+public:
+
+ /// @brief Start new connection.
+ ///
+ /// The connection is inserted to the pool and the
+ /// @ref HttpConnection::asyncAccept is invoked.
+ ///
+ /// @param connection Pointer to the new connection.
+ void start(const HttpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and shutdown it.
+ ///
+ /// Shutdown is specific to TLS and is a first part of graceful close (note it is
+ /// NOT the same as TCP shutdown system call).
+ ///
+ /// @note if the TLS connection stalls e.g. the peer does not try I/O
+ /// on it the connection has to be explicitly stopped.
+ ///
+ /// @param connection Pointer to the connection.
+ void shutdown(const HttpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and stops it.
+ ///
+ /// @param connection Pointer to the connection.
+ void stop(const HttpConnectionPtr& connection);
+
+ /// @brief Stops all connections and removes them from the pool.
+ void stopAll();
+
+protected:
+
+ /// @brief Stops all connections and removes them from the pool.
+ ///
+ /// Must be called from with a thread-safe context.
+ void stopAllInternal();
+
+ /// @brief Set of connections.
+ std::list<HttpConnectionPtr> connections_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+};
+
+}
+}
+
+#endif
+
diff --git a/src/lib/http/date_time.cc b/src/lib/http/date_time.cc
new file mode 100644
index 0000000..cd7824a
--- /dev/null
+++ b/src/lib/http/date_time.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2016-2019 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 <http/date_time.h>
+#include <boost/date_time/time_facet.hpp>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <locale>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace http {
+
+HttpDateTime::HttpDateTime()
+ : time_(boost::posix_time::second_clock::universal_time()) {
+}
+
+HttpDateTime::HttpDateTime(const boost::posix_time::ptime& t)
+ : time_(t) {
+}
+
+std::string
+HttpDateTime::rfc1123Format() const {
+ return (toString("%a, %d %b %Y %H:%M:%S GMT", "RFC 1123"));
+}
+
+std::string
+HttpDateTime::rfc850Format() const {
+ return (toString("%A, %d-%b-%y %H:%M:%S GMT", "RFC 850"));
+}
+
+std::string
+HttpDateTime::asctimeFormat() const {
+ return (toString("%a %b %e %H:%M:%S %Y", "asctime"));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc1123(const std::string& time_string) {
+ return (HttpDateTime(fromString(time_string,
+ "%a, %d %b %Y %H:%M:%S %ZP",
+ "RFC 1123")));
+}
+
+HttpDateTime
+HttpDateTime::fromRfc850(const std::string& time_string) {
+ return (HttpDateTime(fromString(time_string,
+ "%A, %d-%b-%y %H:%M:%S %ZP",
+ "RFC 850")));
+}
+
+HttpDateTime
+HttpDateTime::fromAsctime(const std::string& time_string) {
+ // The asctime() puts space instead of leading 0 in days of
+ // month. The %e # formatter of time_input_facet doesn't deal
+ // with this. To deal with this, we make a copy of the string
+ // holding formatted time and replace a space preceding day
+ // number with 0. Thanks to this workaround we can use the
+ // %d formatter which seems to work fine. This has a side
+ // effect of accepting timestamps such as Sun Nov 06 08:49:37 1994,
+ // but it should be ok to be liberal in this case.
+ std::string time_string_copy(time_string);
+ boost::replace_all(time_string_copy, " ", " 0");
+ return (HttpDateTime(fromString(time_string_copy,
+ "%a %b %d %H:%M:%S %Y",
+ "asctime",
+ false)));
+}
+
+HttpDateTime
+HttpDateTime::fromAny(const std::string& time_string) {
+ HttpDateTime date_time;
+ // Try to parse as a timestamp specified in RFC 1123 format.
+ try {
+ date_time = fromRfc1123(time_string);
+ return (date_time);
+ } catch (...) {
+ // Ignore errors, simply try different format.
+ }
+
+ // Try to parse as a timestamp specified in RFC 850 format.
+ try {
+ date_time = fromRfc850(time_string);
+ return (date_time);
+ } catch (...) {
+ // Ignore errors, simply try different format.
+ }
+
+ // Try to parse as a timestamp output by asctime() function.
+ try {
+ date_time = fromAsctime(time_string);
+ } catch (...) {
+ isc_throw(HttpTimeConversionError,
+ "unsupported time format of the '" << time_string
+ << "'");
+ }
+
+ return (date_time);
+
+}
+
+std::string
+HttpDateTime::toString(const std::string& format,
+ const std::string& method_name) const {
+ std::ostringstream s;
+ // Create raw pointer. The output stream will take responsibility for
+ // deleting the object.
+ time_facet* df(new time_facet(format.c_str()));
+ s.imbue(std::locale(std::locale::classic(), df));
+
+ // Convert time value to a string.
+ s << time_;
+ if (s.fail()) {
+ isc_throw(HttpTimeConversionError, "unable to convert "
+ << "time value of '" << time_ << "'"
+ << " to " << method_name << " format");
+ }
+ return (s.str());
+}
+
+
+ptime
+HttpDateTime::fromString(const std::string& time_string,
+ const std::string& format,
+ const std::string& method_name,
+ const bool zone_check) {
+ std::istringstream s(time_string);
+ // Create raw pointer. The input stream will take responsibility for
+ // deleting the object.
+ time_input_facet* tif(new time_input_facet(format));
+ s.imbue(std::locale(std::locale::classic(), tif));
+
+ time_zone_ptr zone(new posix_time_zone("GMT"));
+ local_date_time ldt = local_microsec_clock::local_time(zone);
+
+ // Parse the time value. The stream will not automatically detect whether
+ // the zone is GMT. We need to check it on our own.
+ s >> ldt;
+ if (s.fail() ||
+ (zone_check && (!ldt.zone() ||
+ ldt.zone()->std_zone_abbrev() != "GMT"))) {
+ isc_throw(HttpTimeConversionError, "unable to parse "
+ << method_name << " time value of '"
+ << time_string << "'");
+ }
+
+ return (ldt.local_time());
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/date_time.h b/src/lib/http/date_time.h
new file mode 100644
index 0000000..ba96ba3
--- /dev/null
+++ b/src/lib/http/date_time.h
@@ -0,0 +1,161 @@
+// Copyright (C) 2016-2019 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 HTTP_DATE_TIME_H
+#define HTTP_DATE_TIME_H
+
+#include <exceptions/exceptions.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when there is an error during time conversion.
+///
+/// The most common reason for this exception is that the unsupported time
+/// format was used as an input to the time parsing functions.
+class HttpTimeConversionError : public Exception {
+public:
+ HttpTimeConversionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief This class parses and generates time values used in HTTP.
+///
+/// The HTTP protocol have historically allowed 3 different date/time formats
+/// (see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html). These are:
+/// - Sun, 06 Nov 1994 08:49:37 GMT
+/// - Sunday, 06-Nov-94 08:49:37 GMT
+/// - Sun Nov 6 08:49:37 1994
+///
+/// The first format is preferred but implementations must also support
+/// remaining two obsolete formats for compatibility. This class implements
+/// parsers and generators for all three formats. It uses @c boost::posix_time
+/// to represent time and date. It uses @c boost::date_time::time_facet
+/// and @c boost::date_time::time_input_facet to generate and parse the
+/// timestamps.
+class HttpDateTime {
+public:
+
+ /// @brief Default constructor.
+ ///
+ /// Sets current universal time as time value.
+ /// Time resolution is to seconds (i.e no fractional seconds).
+ HttpDateTime();
+
+ /// @brief Construct from @c boost::posix_time::ptime object.
+ ///
+ /// @param t time value to be set.
+ explicit HttpDateTime(const boost::posix_time::ptime& t);
+
+ /// @brief Returns time encapsulated by this class.
+ ///
+ /// @return @c boost::posix_time::ptime value encapsulated by the instance
+ /// of this class.
+ boost::posix_time::ptime getPtime() const {
+ return (time_);
+ }
+
+ /// @brief Returns time value formatted as specified in RFC 1123.
+ ///
+ /// @return A string containing time value formatted as
+ /// Sun, 06 Nov 1994 08:49:37 GMT.
+ std::string rfc1123Format() const;
+
+ /// @brief Returns time value formatted as specified in RFC 850.
+ ///
+ /// @return A string containing time value formatted as
+ /// Sunday, 06-Nov-94 08:49:37 GMT.
+ std::string rfc850Format() const;
+
+ /// @brief Returns time value formatted as output of ANSI C's
+ /// asctime().
+ ///
+ /// @return A string containing time value formatted as
+ /// Sun Nov 6 08:49:37 1994.
+ std::string asctimeFormat() const;
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as specified in RFC 1123.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromRfc1123(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as specified in RFC 850.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromRfc850(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted as output from asctime() function.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided timestamp has invalid
+ /// format.
+ static HttpDateTime fromAsctime(const std::string& time_string);
+
+ /// @brief Creates an instance from a string containing time value
+ /// formatted in one of the supported formats.
+ ///
+ /// This method will detect the format of the time value and parse it.
+ /// It tries parsing the value in the following order:
+ /// - a format specified in RFC 1123,
+ /// - a format specified in RFC 850,
+ /// - a format of asctime output.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @return Instance of @ref HttpDateTime.
+ /// @throw HttpTimeConversionError if provided value doesn't match any
+ /// of the supported formats.
+ static HttpDateTime fromAny(const std::string& time_string);
+
+private:
+
+ /// @brief Generic method formatting a time value to a specified format.
+ ////
+ /// @param format Time format as accepted by the
+ /// @c boost::date_time::time_facet.
+ std::string toString(const std::string& format,
+ const std::string& method_name) const;
+
+ /// @brief Generic method parsing time value and converting it to the
+ /// instance of @c boost::posix_time::ptime.
+ ///
+ /// @param time_string Input string holding formatted time value.
+ /// @param format Time format as accepted by the
+ /// @c boost::date_time::time_input_facet.
+ /// @param method_name Name of the expected format to appear in the error
+ /// message if parsing fails, e.g. RFC 1123, RFC 850 or asctime.
+ /// @param zone_check Indicates if the time zone name should be validated
+ /// during parsing. This should be set to false for the formats which
+ /// lack time zones (e.g. asctime).
+ ///
+ /// @return Instance of the @ref boost::posix_time::ptime created from the
+ /// input string.
+ /// @throw HttpTimeConversionError if provided value doesn't match the
+ /// specified format.
+ static boost::posix_time::ptime
+ fromString(const std::string& time_string, const std::string& format,
+ const std::string& method_name, const bool zone_check = true);
+
+ /// @brief Time value encapsulated by this class instance.
+ boost::posix_time::ptime time_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/header_context.h b/src/lib/http/header_context.h
new file mode 100644
index 0000000..623e263
--- /dev/null
+++ b/src/lib/http/header_context.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2016-2017 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 HTTP_HEADER_CONTEXT_H
+#define HTTP_HEADER_CONTEXT_H
+
+#include <boost/lexical_cast.hpp>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP header context.
+struct HttpHeaderContext {
+ std::string name_;
+ std::string value_;
+
+ /// @brief Constructor.
+ ///
+ /// Sets header name and value to empty strings.
+ HttpHeaderContext()
+ : name_(), value_() {
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param name Header name.
+ /// @param value Header value.
+ HttpHeaderContext(const std::string& name, const std::string& value)
+ : name_(name), value_(value) {
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param name Header name.
+ /// @param value Numeric value for the header.
+ HttpHeaderContext(const std::string& name, const int64_t value)
+ : name_(name), value_(boost::lexical_cast<std::string>(value)) {
+ }
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/http.dox b/src/lib/http/http.dox
new file mode 100644
index 0000000..c6e4933
--- /dev/null
+++ b/src/lib/http/http.dox
@@ -0,0 +1,24 @@
+// Copyright (C) 2020 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/.
+
+/**
+ @page libhttp libkea-http - Kea HTTP Library
+
+@section httpMTConsiderations Multi-Threading Consideration for HTTP Library
+
+By default this library is not thread safe, for instance HTTP listeners
+and HTTP messages are not thread safe. Exceptions are:
+
+ - HTTP client is Kea thread safe (i.e. is thread safe when the
+ multi-threading mode is true).
+
+ - date time is thread safe (mainly because its encapsulated POSIX time
+ is private and read-only, or because all methods are instance const methods
+ or class methods).
+
+ - URL is thread safe (all public methods are const methods).
+
+*/
diff --git a/src/lib/http/http_acceptor.h b/src/lib/http/http_acceptor.h
new file mode 100644
index 0000000..eb6f55d
--- /dev/null
+++ b/src/lib/http/http_acceptor.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2017-2021 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 HTTP_ACCEPTOR_H
+#define HTTP_ACCEPTOR_H
+
+#include <asiolink/tls_acceptor.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/system/system_error.hpp>
+#include <functional>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of the callback for the TCP acceptor used in this library.
+typedef std::function<void(const boost::system::error_code&)>
+HttpAcceptorCallback;
+
+/// @brief Type of the TCP acceptor used in this library.
+typedef asiolink::TCPAcceptor<HttpAcceptorCallback> HttpAcceptor;
+
+/// @brief Type of the TLS acceptor used in this library.
+///
+/// @note It is a derived type of HttpAcceptor.
+typedef asiolink::TLSAcceptor<HttpAcceptorCallback> HttpsAcceptor;
+
+/// @brief Type of shared pointer to TCP acceptors.
+typedef boost::shared_ptr<HttpAcceptor> HttpAcceptorPtr;
+
+/// @brief Type of shared pointer to TLS acceptors.
+typedef boost::shared_ptr<HttpsAcceptor> HttpsAcceptorPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/http_header.cc b/src/lib/http/http_header.cc
new file mode 100644
index 0000000..a543e66
--- /dev/null
+++ b/src/lib/http/http_header.cc
@@ -0,0 +1,56 @@
+// Copyright (C) 2017-2018 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 <http/http_header.h>
+#include <util/strutil.h>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace http {
+
+HttpHeader::HttpHeader(const std::string& header_name,
+ const std::string& header_value)
+ : header_name_(header_name), header_value_(header_value) {
+}
+
+uint64_t
+HttpHeader::getUint64Value() const {
+ try {
+ return (boost::lexical_cast<uint64_t>(header_value_));
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(BadValue, header_name_ << " HTTP header value "
+ << header_value_ << " is not a valid number");
+ }
+}
+
+std::string
+HttpHeader::getLowerCaseName() const {
+ std::string ln = header_name_;
+ util::str::lowercase(ln);
+ return (ln);
+}
+
+std::string
+HttpHeader::getLowerCaseValue() const {
+ std::string lc = header_value_;
+ util::str::lowercase(lc);
+ return (lc);
+}
+
+bool
+HttpHeader::isValueEqual(const std::string& v) const {
+ std::string lcv = v;
+ util::str::lowercase(lcv);
+ return (lcv == getLowerCaseValue());
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/http_header.h b/src/lib/http/http_header.h
new file mode 100644
index 0000000..35d4c17
--- /dev/null
+++ b/src/lib/http/http_header.h
@@ -0,0 +1,86 @@
+// Copyright (C) 2017-2019 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 HTTP_HEADER_H
+#define HTTP_HEADER_H
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents HTTP header including a header name and value.
+///
+/// It includes methods for retrieving header name and value in lower case
+/// and for case insensitive comparison of header values.
+class HttpHeader {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param header_name Header name.
+ /// @param header_value Header value.
+ explicit HttpHeader(const std::string& header_name,
+ const std::string& header_value = "");
+
+ /// @brief Returns header name.
+ std::string getName() const {
+ return (header_name_);
+ }
+
+ /// @brief Returns header value.
+ std::string getValue() const {
+ return (header_value_);
+ }
+
+ /// @brief Returns header value as unsigned integer.
+ ///
+ /// @throw BadValue if the header value is not a valid number.
+ uint64_t getUint64Value() const;
+
+ /// @brief Returns lower case header name.
+ std::string getLowerCaseName() const;
+
+ /// @brief Returns lower case header value.
+ std::string getLowerCaseValue() const;
+
+ /// @brief Case insensitive comparison of header value.
+ ///
+ /// @param v Value to be compared.
+ ///
+ /// @return true if header value is equal, false otherwise.
+ bool isValueEqual(const std::string& v) const;
+
+private:
+
+ std::string header_name_; ///< Header name.
+ std::string header_value_; ///< Header value.
+};
+
+/// @brief Pointer to the @c HttpHeader class.
+typedef boost::shared_ptr<HttpHeader> HttpHeaderPtr;
+
+/// @brief Represents HTTP Host header.
+class HostHttpHeader : public HttpHeader {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param header_value Host header value. The default is empty
+ /// string.
+ explicit HostHttpHeader(const std::string& header_value = "")
+ : HttpHeader("Host", header_value) {
+ }
+};
+
+/// @brief Pointer to the HTTP host header.
+typedef boost::shared_ptr<HostHttpHeader> HostHttpHeaderPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_HEADER_H
diff --git a/src/lib/http/http_log.cc b/src/lib/http/http_log.cc
new file mode 100644
index 0000000..8e1994d
--- /dev/null
+++ b/src/lib/http/http_log.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2016-2021 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/.
+
+/// Defines the logger used by the libkea-http library.
+
+#include <config.h>
+
+#include <http/http_log.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Defines the logger used within libkea-http library.
+isc::log::Logger http_logger("http");
+
+} // namespace http
+} // namespace isc
+
diff --git a/src/lib/http/http_log.h b/src/lib/http/http_log.h
new file mode 100644
index 0000000..0b7d8ad
--- /dev/null
+++ b/src/lib/http/http_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2016-2021 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 HTTP_LOG_H
+#define HTTP_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <http/http_messages.h>
+
+namespace isc {
+namespace http {
+
+/// Define the logger used within libkea-http library.
+extern isc::log::Logger http_logger;
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_LOG_H
diff --git a/src/lib/http/http_message.cc b/src/lib/http/http_message.cc
new file mode 100644
index 0000000..ce012ca
--- /dev/null
+++ b/src/lib/http/http_message.cc
@@ -0,0 +1,108 @@
+// Copyright (C) 2017-2018 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 <http/http_message.h>
+
+namespace isc {
+namespace http {
+
+HttpMessage::HttpMessage(const HttpMessage::Direction& direction)
+ : direction_(direction), required_versions_(),
+ http_version_(HttpVersion::HTTP_10()), required_headers_(),
+ created_(false), finalized_(false), headers_() {
+}
+
+HttpMessage::~HttpMessage() {
+}
+
+void
+HttpMessage::requireHttpVersion(const HttpVersion& version) {
+ required_versions_.insert(version);
+}
+
+void
+HttpMessage::requireHeader(const std::string& header_name) {
+ // Empty value denotes that the header is required but no specific
+ // value is expected.
+ HttpHeaderPtr hdr(new HttpHeader(header_name));
+ required_headers_[hdr->getLowerCaseName()] = hdr;
+}
+
+void
+HttpMessage::requireHeaderValue(const std::string& header_name,
+ const std::string& header_value) {
+ HttpHeaderPtr hdr(new HttpHeader(header_name, header_value));
+ required_headers_[hdr->getLowerCaseName()] = hdr;
+}
+
+bool
+HttpMessage::requiresBody() const {
+ // If Content-Length is required the body must exist too. There may
+ // be probably some cases when Content-Length is not provided but
+ // the body is provided. But, probably not in our use cases.
+ // Use lower case header name because this is how it is indexed in
+ // the storage.
+ return (required_headers_.find("content-length") != required_headers_.end());
+}
+
+HttpVersion
+HttpMessage::getHttpVersion() const {
+ checkCreated();
+ return (http_version_);
+}
+
+HttpHeaderPtr
+HttpMessage::getHeader(const std::string& header_name) const {
+ checkCreated();
+
+ HttpHeader hdr(header_name);
+ auto header_it = headers_.find(hdr.getLowerCaseName());
+ if (header_it != headers_.end()) {
+ return (header_it->second);
+ }
+
+ isc_throw(HttpMessageNonExistingHeader, header_name << " HTTP header"
+ " not found in the request");
+}
+
+std::string
+HttpMessage::getHeaderValue(const std::string& header_name) const {
+ return (getHeader(header_name)->getValue());
+}
+
+uint64_t
+HttpMessage::getHeaderValueAsUint64(const std::string& header_name) const {
+ try {
+ return (getHeader(header_name)->getUint64Value());
+
+ } catch (const std::exception& ex) {
+ // The specified header does exist, but the value is not a number.
+ isc_throw(HttpMessageError, ex.what());
+ }
+}
+
+void
+HttpMessage::checkCreated() const {
+ if (!created_) {
+ isc_throw(HttpMessageError, "unable to retrieve values of HTTP"
+ " message because the HttpMessage::create() must be"
+ " called first. This is a programmatic error");
+ }
+}
+
+void
+HttpMessage::checkFinalized() const {
+ if (!finalized_) {
+ isc_throw(HttpMessageError, "unable to retrieve body of HTTP"
+ " message because the HttpMessage::finalize() must be"
+ " called first. This is a programmatic error");
+ }
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/http_message.h b/src/lib/http/http_message.h
new file mode 100644
index 0000000..3634c80
--- /dev/null
+++ b/src/lib/http/http_message.h
@@ -0,0 +1,265 @@
+// Copyright (C) 2017-2018 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 HTTP_MESSAGE_H
+#define HTTP_MESSAGE_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_header.h>
+#include <http/http_types.h>
+#include <map>
+#include <set>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpMessage class.
+class HttpMessageError : public Exception {
+public:
+ HttpMessageError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when attempt is made to retrieve a
+/// non-existing header.
+class HttpMessageNonExistingHeader : public HttpMessageError {
+public:
+ HttpMessageNonExistingHeader(const char* file, size_t line,
+ const char* what) :
+ HttpMessageError(file, line, what) { };
+};
+
+
+/// @brief Base class for @ref HttpRequest and @ref HttpResponse.
+///
+/// This abstract class provides a common functionality for the HTTP
+/// requests and responses. Each such message can be marked as outbound
+/// or inbound. An HTTP inbound request is the one received by the server
+/// and HTTP inbound response is the response received by the client.
+/// Conversely, an HTTP outbound request is the request created by the
+/// client and HTTP outbound response is the response created by the
+/// server. There are differences in how the inbound and outbound
+/// messages are created. The inbound messages are received over the
+/// TCP sockets and parsed by the parsers. The parsed information is
+/// stored in a context, i.e. structure holding raw information and
+/// associated with the given @c HttpMessage instance. Once the message
+/// is parsed and all required information is stored in the context,
+/// the @c create method is called to validate and fetch information
+/// from the context into the message. The @c finalize method is called
+/// to commit the HTTP message body into the message.
+///
+/// The outbound message is created locally from the known data, e.g.
+/// HTTP version number, URI, method etc. The headers can be then
+/// appended to the message via the context. In order to use this message
+/// the @c finalize method must be called to commit this information.
+/// Them, @c toString method can be called to generate the message in
+/// the textual form, which can be transferred via TCP socket.
+class HttpMessage {
+public:
+
+ /// @brief Specifies the direction of the HTTP message.
+ enum Direction {
+ INBOUND,
+ OUTBOUND
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param direction Direction of the message (inbound or outbound).
+ explicit HttpMessage(const Direction& direction);
+
+ /// @brief Destructor.
+ virtual ~HttpMessage();
+
+ /// @brief Returns HTTP message direction.
+ Direction getDirection() const {
+ return (direction_);
+ }
+
+ /// @brief Sets direction for the HTTP message.
+ ///
+ /// This is mostly useful in unit testing.
+ ///
+ /// @param direction New direction of the HTTP message.
+ void setDirection(const Direction& direction) {
+ direction_ = direction;
+ }
+
+ /// @brief Specifies HTTP version allowed.
+ ///
+ /// Allowed HTTP versions must be specified prior to calling @ref create
+ /// method. If no version is specified, all versions are allowed.
+ ///
+ /// @param version Version number allowed for the request.
+ void requireHttpVersion(const HttpVersion& version);
+
+ /// @brief Specifies a required HTTP header for the HTTP message.
+ ///
+ /// Required headers must be specified prior to calling @ref create method.
+ /// The specified header must exist in the received HTTP request. This puts
+ /// no requirement on the header value.
+ ///
+ /// @param header_name Required header name.
+ void requireHeader(const std::string& header_name);
+
+ /// @brief Specifies a required value of a header in the message.
+ ///
+ /// Required header values must be specified prior to calling @ref create
+ /// method. The specified header must exist and its value must be equal to
+ /// the value specified as second parameter.
+ ///
+ /// @param header_name HTTP header name.
+ /// @param header_value HTTP header value.
+ void requireHeaderValue(const std::string& header_name,
+ const std::string& header_value);
+
+ /// @brief Checks if the body is required for the HTTP message.
+ ///
+ /// Current implementation simply checks if the "Content-Length" header
+ /// is required.
+ ///
+ /// @return true if the body is required, false otherwise.
+ bool requiresBody() const;
+
+ /// @brief Reads parsed message from the context, validates the message and
+ /// stores parsed information.
+ ///
+ /// This method must be called before retrieving parsed data using accessors.
+ /// This method doesn't parse the HTTP request body.
+ virtual void create() = 0;
+
+ /// @brief Complete parsing HTTP message or creating an HTTP outbound message.
+ ///
+ /// This method is used in two situations: when a message has been received
+ /// into a context and may be fully parsed (including the body) or when the
+ /// data for the creation of the outbound message have been stored in a context
+ /// and the message can be now created from the context.
+ ///
+ /// This method should call @c create method if it hasn't been called yet and
+ /// then read the message body from the context and interpret it. If the body
+ /// doesn't adhere to the requirements for the message (in particular, when the
+ /// content type of the body is invalid) an exception should be thrown.
+ virtual void finalize() = 0;
+
+ /// @brief Reset the state of the object.
+ virtual void reset() = 0;
+
+ /// @brief Returns HTTP version number (major and minor).
+ HttpVersion getHttpVersion() const;
+
+ /// @brief Returns object encapsulating HTTP header.
+ ///
+ /// @param header_name HTTP header name.
+ ///
+ /// @return Non-null pointer to the header.
+ /// @throw HttpMessageNonExistingHeader if header with the specified name
+ /// doesn't exist.
+ /// @throw HttpMessageError if the request hasn't been created.
+ HttpHeaderPtr getHeader(const std::string& header_name) const;
+
+ /// @brief Returns a value of the specified HTTP header.
+ ///
+ /// @param header_name Name of the HTTP header.
+ ///
+ /// @throw HttpMessageError if the header doesn't exist.
+ std::string getHeaderValue(const std::string& header_name) const;
+
+ /// @brief Returns a value of the specified HTTP header as number.
+ ///
+ /// @param header_name Name of the HTTP header.
+ ///
+ /// @throw HttpMessageError if the header doesn't exist or if the
+ /// header value is not number.
+ uint64_t getHeaderValueAsUint64(const std::string& header_name) const;
+
+ /// @brief Returns HTTP message body as string.
+ virtual std::string getBody() const = 0;
+
+ /// @brief Returns HTTP message as text.
+ ///
+ /// This method is called to generate the outbound HTTP message. Make
+ /// sure to call @c finalize prior to calling this method.
+ virtual std::string toString() const = 0;
+
+ /// @brief Checks if the message has been successfully finalized.
+ ///
+ /// The message gets finalized on successful call to @c finalize.
+ ///
+ /// @return true if the message has been finalized, false otherwise.
+ bool isFinalized() const {
+ return (finalized_);
+ }
+
+protected:
+
+ /// @brief Checks if the @ref create was called.
+ ///
+ /// @throw HttpMessageError if @ref create wasn't called.
+ void checkCreated() const;
+
+ /// @brief Checks if the @ref finalize was called.
+ ///
+ /// @throw HttpMessageError if @ref finalize wasn't called.
+ void checkFinalized() const;
+
+ /// @brief Checks if the set is empty or the specified element belongs
+ /// to this set.
+ ///
+ /// This is a convenience method used by the class to verify that the
+ /// given HTTP method belongs to "required methods", HTTP version belongs
+ /// to "required versions" etc.
+ ///
+ /// @param element Reference to the element.
+ /// @param element_set Reference to the set of elements.
+ /// @tparam T Element type, @ref HttpVersion etc.
+ ///
+ /// @return true if the element set is empty or if the element belongs
+ /// to the set.
+ template<typename T>
+ bool inRequiredSet(const T& element,
+ const std::set<T>& element_set) const {
+ return (element_set.empty() || element_set.count(element) > 0);
+ }
+
+ /// @brief Message direction (inbound or outbound).
+ Direction direction_;
+
+ /// @brief Set of required HTTP versions.
+ ///
+ /// If the set is empty, all versions are allowed.
+ std::set<HttpVersion> required_versions_;
+
+ /// @brief HTTP version numbers.
+ HttpVersion http_version_;
+
+ /// @brief Map of HTTP headers indexed by lower case header names.
+ typedef std::map<std::string, HttpHeaderPtr> HttpHeaderMap;
+
+ /// @brief Map holding required HTTP headers.
+ ///
+ /// The key of this map specifies the lower case HTTP header name.
+ /// If the value of the HTTP header is empty, the header is required
+ /// but the value of the header is not checked. If the value is
+ /// non-empty, the value in the HTTP request must be equal (case
+ /// insensitive) to the value in the map.
+ HttpHeaderMap required_headers_;
+
+ /// @brief Flag indicating whether @ref create was called.
+ bool created_;
+
+ /// @brief Flag indicating whether @ref finalize was called.
+ bool finalized_;
+
+ /// @brief Parsed HTTP headers.
+ HttpHeaderMap headers_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_MESSAGE_H
diff --git a/src/lib/http/http_message_parser_base.cc b/src/lib/http/http_message_parser_base.cc
new file mode 100644
index 0000000..000e343
--- /dev/null
+++ b/src/lib/http/http_message_parser_base.cc
@@ -0,0 +1,307 @@
+// Copyright (C) 2017-2020 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 <http/http_message_parser_base.h>
+#include <functional>
+#include <sstream>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpMessageParserBase::HTTP_PARSE_OK_ST;
+const int HttpMessageParserBase::HTTP_PARSE_FAILED_ST;
+
+const int HttpMessageParserBase::DATA_READ_OK_EVT;
+const int HttpMessageParserBase::NEED_MORE_DATA_EVT;
+const int HttpMessageParserBase::MORE_DATA_PROVIDED_EVT;
+const int HttpMessageParserBase::HTTP_PARSE_OK_EVT;
+const int HttpMessageParserBase::HTTP_PARSE_FAILED_EVT;
+
+
+HttpMessageParserBase::HttpMessageParserBase(HttpMessage& message)
+ : StateModel(), message_(message), buffer_(), buffer_pos_(0),
+ error_message_() {
+}
+
+void
+HttpMessageParserBase::poll() {
+ try {
+ // Run the parser until it runs out of input data or until
+ // parsing completes.
+ do {
+ getState(getCurrState())->run();
+
+ } while (!isModelDone() && (getNextEvent() != NOP_EVT) &&
+ (getNextEvent() != NEED_MORE_DATA_EVT));
+ } catch (const std::exception& ex) {
+ abortModel(ex.what());
+ }
+}
+
+bool
+HttpMessageParserBase::needData() const {
+ return ((getNextEvent() == NEED_MORE_DATA_EVT) ||
+ (getNextEvent() == START_EVT));
+}
+
+bool
+HttpMessageParserBase::httpParseOk() const {
+ return ((getNextEvent() == END_EVT) &&
+ (getLastEvent() == HTTP_PARSE_OK_EVT));
+}
+
+void
+HttpMessageParserBase::postBuffer(const void* buf, const size_t buf_size) {
+ if (buf_size > 0) {
+ // The next event is NEED_MORE_DATA_EVT when the parser wants to
+ // signal that more data is needed. This method is called to supply
+ // more data and thus it should change the next event to
+ // MORE_DATA_PROVIDED_EVT.
+ if (getNextEvent() == NEED_MORE_DATA_EVT) {
+ transition(getCurrState(), MORE_DATA_PROVIDED_EVT);
+ }
+ buffer_.insert(buffer_.end(), static_cast<const char*>(buf),
+ static_cast<const char*>(buf) + buf_size);
+ }
+}
+
+std::string
+HttpMessageParserBase::getBufferAsString(const size_t limit) const {
+ std::string message(buffer_.begin(), buffer_.end());
+ return (logFormatHttpMessage(message, limit));
+}
+
+std::string
+HttpMessageParserBase::logFormatHttpMessage(const std::string& message,
+ const size_t limit) {
+ if ((limit > 0) && !message.empty()) {
+ if (limit < message.size()) {
+ std::ostringstream s;
+ s << message.substr(0, limit)
+ << ".........\n(truncating HTTP message larger than "
+ << limit << " characters)\n";
+ return (s.str());
+ }
+ }
+
+ // Return original message if it is empty or does not exceed the
+ // limit.
+ return (message);
+}
+
+
+void
+HttpMessageParserBase::defineEvents() {
+ StateModel::defineEvents();
+
+ // Define HTTP parser specific events.
+ defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT");
+ defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT");
+ defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT");
+ defineEvent(HTTP_PARSE_OK_EVT, "HTTP_PARSE_OK_EVT");
+ defineEvent(HTTP_PARSE_FAILED_EVT, "HTTP_PARSE_FAILED_EVT");
+}
+
+void
+HttpMessageParserBase::verifyEvents() {
+ StateModel::verifyEvents();
+
+ getEvent(DATA_READ_OK_EVT);
+ getEvent(NEED_MORE_DATA_EVT);
+ getEvent(MORE_DATA_PROVIDED_EVT);
+ getEvent(HTTP_PARSE_OK_EVT);
+ getEvent(HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpMessageParserBase::defineStates() {
+ // Call parent class implementation first.
+ StateModel::defineStates();
+
+ defineState(HTTP_PARSE_OK_ST, "HTTP_PARSE_OK_ST",
+ std::bind(&HttpMessageParserBase::parseEndedHandler, this));
+
+ defineState(HTTP_PARSE_FAILED_ST, "HTTP_PARSE_FAILED_ST",
+ std::bind(&HttpMessageParserBase::parseEndedHandler, this));
+}
+
+void
+HttpMessageParserBase::stateWithReadHandler(const std::string& handler_name,
+ std::function<void(const char c)>
+ after_read_logic) {
+ std::string bytes;
+ getNextFromBuffer(bytes);
+ // Do nothing if we reached the end of buffer.
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case DATA_READ_OK_EVT:
+ case MORE_DATA_PROVIDED_EVT:
+ after_read_logic(bytes[0]);
+ break;
+ default:
+ invalidEventError(handler_name, getNextEvent());
+ }
+ }
+}
+
+void
+HttpMessageParserBase::stateWithMultiReadHandler(const std::string& handler_name,
+ std::function<void(const std::string&)>
+ after_read_logic) {
+ std::string bytes;
+ getNextFromBuffer(bytes, 0);
+ // Do nothing if we reached the end of buffer.
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case DATA_READ_OK_EVT:
+ case MORE_DATA_PROVIDED_EVT:
+ after_read_logic(bytes);
+ break;
+ default:
+ invalidEventError(handler_name, getNextEvent());
+ }
+ }
+}
+
+void
+HttpMessageParserBase::parseFailure(const std::string& error_msg) {
+ error_message_ = error_msg + " : " + getContextStr();
+ transition(HTTP_PARSE_FAILED_ST, HTTP_PARSE_FAILED_EVT);
+}
+
+void
+HttpMessageParserBase::onModelFailure(const std::string& explanation) {
+ if (error_message_.empty()) {
+ error_message_ = explanation;
+ }
+}
+
+void
+HttpMessageParserBase::getNextFromBuffer(std::string& bytes, const size_t limit) {
+ unsigned int ev = getNextEvent();
+ bytes = "\0";
+ // The caller should always provide additional data when the
+ // NEED_MORE_DATA_EVT occurs. If the next event is still
+ // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided
+ // the data.
+ if (ev == NEED_MORE_DATA_EVT) {
+ isc_throw(HttpParseError,
+ "HTTP request parser requires new data to progress, but no data"
+ " have been provided. The transaction is aborted to avoid"
+ " a deadlock. This is a Kea HTTP server logic error!");
+
+ } else {
+ // Try to retrieve characters from the buffer.
+ const bool data_exist = popNextFromBuffer(bytes, limit);
+ if (!data_exist) {
+ // There is no more data so it is really not possible that we're
+ // at MORE_DATA_PROVIDED_EVT.
+ if (ev == MORE_DATA_PROVIDED_EVT) {
+ isc_throw(HttpParseError,
+ "HTTP server state indicates that new data have been"
+ " provided to be parsed, but the transaction buffer"
+ " contains no new data. This is a Kea HTTP server logic"
+ " error!");
+
+ } else {
+ // If there is no more data we should set NEED_MORE_DATA_EVT
+ // event to indicate that new data should be provided.
+ postNextEvent(NEED_MORE_DATA_EVT);
+ }
+ }
+ }
+}
+
+void
+HttpMessageParserBase::invalidEventError(const std::string& handler_name,
+ const unsigned int event) {
+ isc_throw(HttpParseError, handler_name << ": invalid event "
+ << getEventLabel(static_cast<int>(event)));
+}
+
+void
+HttpMessageParserBase::parseEndedHandler() {
+ switch(getNextEvent()) {
+ case HTTP_PARSE_OK_EVT:
+ message_.finalize();
+ transition(END_ST, END_EVT);
+ break;
+ case HTTP_PARSE_FAILED_EVT:
+ abortModel("HTTP message parsing failed");
+ break;
+
+ default:
+ invalidEventError("parseEndedHandler", getNextEvent());
+ }
+}
+
+bool
+HttpMessageParserBase::popNextFromBuffer(std::string& next, const size_t limit) {
+ // If there are any characters in the buffer, pop next.
+ if (buffer_pos_ < buffer_.size()) {
+ next = buffer_.substr(buffer_pos_, limit == 0 ? std::string::npos : limit);
+
+ if (limit > 0) {
+ buffer_pos_ += limit;
+ }
+
+ if ((buffer_pos_ > buffer_.size()) || (limit == 0)) {
+ buffer_pos_ = buffer_.size();
+ }
+ return (true);
+ }
+ return (false);
+}
+
+bool
+HttpMessageParserBase::isChar(const char c) const {
+ // was (c >= 0) && (c <= 127)
+ return (c >= 0);
+}
+
+bool
+HttpMessageParserBase::isCtl(const char c) const {
+ return (((c >= 0) && (c <= 31)) || (c == 127));
+}
+
+bool
+HttpMessageParserBase::isSpecial(const char c) const {
+ switch (c) {
+ case '(':
+ case ')':
+ case '<':
+ case '>':
+ case '@':
+ case ',':
+ case ';':
+ case ':':
+ case '\\':
+ case '"':
+ case '/':
+ case '[':
+ case ']':
+ case '?':
+ case '=':
+ case '{':
+ case '}':
+ case ' ':
+ case '\t':
+ return true;
+
+ default:
+ ;
+ }
+
+ return false;
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/http_message_parser_base.h b/src/lib/http/http_message_parser_base.h
new file mode 100644
index 0000000..01f9831
--- /dev/null
+++ b/src/lib/http/http_message_parser_base.h
@@ -0,0 +1,316 @@
+// Copyright (C) 2017-2020 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 HTTP_MESSAGE_PARSER_BASE_H
+#define HTTP_MESSAGE_PARSER_BASE_H
+
+#include <exceptions/exceptions.h>
+#include <http/http_message.h>
+#include <util/state_model.h>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when an error during parsing HTTP message
+/// has occurred.
+///
+/// The most common errors are due to receiving malformed requests.
+class HttpParseError : public Exception {
+public:
+ HttpParseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Base class for the HTTP message parsers.
+///
+/// This is a base class for @c HttpRequestParser and @c HttpResponseParser
+/// classes. It provides common states, events and functionality for processing
+/// the received HTTP messages.
+///
+/// This class must not be used directly. Instead, an instance of the
+/// derived class should be used.
+///
+/// HTTP uses TCP as a transport which is asynchronous in nature, i.e. the
+/// HTTP message is received in chunks and multiple TCP connections can be
+/// established at the same time. Multiplexing between these connections
+/// requires providing a separate state machine per connection to "remember"
+/// the state of each transaction when the parser is waiting for asynchronous
+/// data to be delivered. While the parser is waiting for the data, it can
+/// parse requests received over other connections. This class provides means
+/// for parsing partial data received over the specific connection and
+/// interrupting data parsing to switch to a different context.
+///
+/// A new method @ref HttpMessageParserBase::poll has been created to run the
+/// parser's state machine as long as there are unparsed data in the parser's
+/// internal buffer. This method returns control to the caller when the parser
+/// runs out of data in this buffer. The caller must feed the buffer by calling
+/// @ref HttpMessageParserBase::postBuffer and then run
+/// @ref HttpMessageParserBase::poll again.
+///
+/// In case, the caller provides more data than indicated by the "Content-Length"
+/// header the parser will return from @c poll() after parsing the data which
+/// constitute the HTTP request and not parse the extraneous data. The caller
+/// should test the @ref HttpMessageParserBase::needData and
+/// @ref HttpMessageParserBase::httpParseOk to determine whether parsing has
+/// completed.
+///
+/// The @ref util::StateModel::runModel must not be used to run the parser
+/// state machine, thus it is made private method.
+class HttpMessageParserBase : public util::StateModel {
+public:
+
+ /// @name States supported by the HttpMessageParserBase.
+ ///
+ //@{
+
+ /// @brief Parsing successfully completed.
+ static const int HTTP_PARSE_OK_ST = SM_DERIVED_STATE_MIN + 1000;
+
+ /// @brief Parsing failed.
+ static const int HTTP_PARSE_FAILED_ST = SM_DERIVED_STATE_MIN + 1001;
+
+ //@}
+
+ /// @name Events used during HTTP message parsing.
+ ///
+ //@{
+
+ /// @brief Chunk of data successfully read and parsed.
+ static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Unable to proceed with parsing until new data is provided.
+ static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ /// @brief New data provided and parsing should continue.
+ static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ /// @brief Parsing HTTP request successful.
+ static const int HTTP_PARSE_OK_EVT = SM_DERIVED_EVENT_MIN + 1000;
+
+ /// @brief Parsing HTTP request failed.
+ static const int HTTP_PARSE_FAILED_EVT = SM_DERIVED_EVENT_MIN + 1001;
+
+ //@}
+
+ /// @brief Constructor.
+ ///
+ /// @param message Reference to the HTTP request or response message.
+ HttpMessageParserBase(HttpMessage& message);
+
+ /// @brief Run the parser as long as the amount of data is sufficient.
+ ///
+ /// The data to be parsed should be provided by calling
+ /// @ref HttpMessageParserBase::postBuffer. When the parser reaches the end
+ /// of the data buffer the @ref HttpMessageParserBase::poll sets the next
+ /// event to @ref NEED_MORE_DATA_EVT and returns. The caller should then invoke
+ /// @ref HttpMessageParserBase::postBuffer again to provide more data to the
+ /// parser, and call @ref HttpMessageParserBase::poll to continue parsing.
+ ///
+ /// This method also returns when parsing completes or fails. The last
+ /// event can be examined to check whether parsing was successful or not.
+ void poll();
+
+ /// @brief Returns true if the parser needs more data to continue.
+ ///
+ /// @return true if the next event is NEED_MORE_DATA_EVT.
+ bool needData() const;
+
+ /// @brief Returns true if the message has been parsed successfully.
+ bool httpParseOk() const;
+
+ /// @brief Returns error message.
+ std::string getErrorMessage() const {
+ return (error_message_);
+ }
+
+ /// @brief Provides more input data to the parser.
+ ///
+ /// This method must be called prior to calling @ref HttpMessageParserBase::poll
+ /// to deliver data to be parsed. HTTP messages are received over TCP and
+ /// multiple reads may be necessary to retrieve the entire request. There is
+ /// no need to accumulate the entire request to start parsing it. A chunk
+ /// of data can be provided to the parser using this method and parsed right
+ /// away using @ref HttpMessageParserBase::poll.
+ ///
+ /// @param buf A pointer to the buffer holding the data.
+ /// @param buf_size Size of the data within the buffer.
+ void postBuffer(const void* buf, const size_t buf_size);
+
+ /// @brief Returns parser's input buffer as string.
+ ///
+ /// @param limit Maximum length of the buffer to be output. If the limit is 0,
+ /// the length of the output is unlimited.
+ /// @return Textual representation of the input buffer.
+ std::string getBufferAsString(const size_t limit = 0) const;
+
+ /// @brief Formats provided HTTP message for logging.
+ ///
+ /// This method is useful in cases when there is a need to log a HTTP message
+ /// (as text). If the @c limit is specified the message output is limited to
+ /// this size. If the @c limit is set to 0 (default), the whole message is
+ /// output. The @c getBufferAsString method calls this method internally.
+ ///
+ /// @param message HTTP message to be logged.
+ /// @param limit Maximum length of the buffer to be output. If the limit is 0,
+ /// the length of the output is unlimited.
+ /// @return HTTP message formatted for logging.
+ static std::string logFormatHttpMessage(const std::string& message,
+ const size_t limit = 0);
+
+private:
+
+ /// @brief Make @ref runModel private to make sure that the caller uses
+ /// @ref poll method instead.
+ using StateModel::runModel;
+
+protected:
+
+ /// @brief Define events used by the parser.
+ virtual void defineEvents();
+
+ /// @brief Verifies events used by the parser.
+ virtual void verifyEvents();
+
+ /// @brief Defines states of the parser.
+ virtual void defineStates();
+
+ /// @brief Generic parser handler which reads a single byte of data and
+ /// parses it using specified callback function.
+ ///
+ /// This generic handler is used in most of the parser states to parse a
+ /// single byte of input data. If there is no more data it simply returns.
+ /// Otherwise, if the next event is DATA_READ_OK_EVT or
+ /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to
+ /// parse the new byte of data. For all other states it throws an exception.
+ ///
+ /// @param handler_name Name of the handler function which called this
+ /// method.
+ /// @param after_read_logic Callback function to parse the byte of data.
+ /// This callback function implements state specific logic.
+ ///
+ /// @throw HttpRequestParserError when invalid event occurred.
+ void stateWithReadHandler(const std::string& handler_name,
+ std::function<void(const char c)>
+ after_read_logic);
+
+ /// @brief Generic parser handler which reads multiple bytes of data and
+ /// parses it using specified callback function.
+ ///
+ /// This handler is mostly used for parsing body of the HTTP message,
+ /// where we don't validate the content read. Reading multiple bytes
+ /// is the most efficient. If there is no more data it simply returns.
+ /// Otherwise, if the next event is DATA_READ_OK_EVT or
+ /// MORE_DATA_PROVIDED_EVT, it calls the provided callback function to
+ /// parse the new byte of data.
+ ///
+ /// @param handler_name Name of the handler function which called this
+ /// method.
+ /// @param after_read_logic Callback function to parse multiple bytes of
+ /// data. This callback function implements state specific logic.
+ ///
+ /// @throw HttpRequestParserError when invalid event occurred.
+ void stateWithMultiReadHandler(const std::string& handler_name,
+ std::function<void(const std::string&)>
+ after_read_logic);
+
+ /// @brief Transition parser to failure state.
+ ///
+ /// This method transitions the parser to @ref HTTP_PARSE_FAILED_ST and
+ /// sets next event to HTTP_PARSE_FAILED_EVT.
+ ///
+ /// @param error_msg Error message explaining the failure.
+ void parseFailure(const std::string& error_msg);
+
+ /// @brief A method called when parsing fails.
+ ///
+ /// @param explanation Error message explaining the reason for parsing
+ /// failure.
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Retrieves next bytes of data from the buffer.
+ ///
+ /// During normal operation, when there is no more data in the buffer,
+ /// the parser sets NEED_MORE_DATA_EVT as next event to signal the need for
+ /// calling @ref HttpMessageParserBase::postBuffer.
+ ///
+ /// @param [out] bytes Reference to the variable where read data should be stored.
+ /// @param limit Maximum number of bytes to be read.
+ ///
+ /// @throw HttpMessageParserBaseError If current event is already set to
+ /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it
+ /// indicates that the caller failed to provide new data using
+ /// @ref HttpMessageParserBase::postBuffer. The latter case is highly unlikely
+ /// as it indicates that no new data were provided but the state of the
+ /// parser was changed from NEED_MORE_DATA_EVT or the data were provided
+ /// but the data buffer is empty. In both cases, it is an internal server
+ /// error.
+ void getNextFromBuffer(std::string& bytes, const size_t limit = 1);
+
+ /// @brief This method is called when invalid event occurred in a particular
+ /// parser state.
+ ///
+ /// This method simply throws @ref HttpParseError informing about invalid
+ /// event occurring for the particular parser state. The error message
+ /// includes the name of the handler in which the exception has been
+ /// thrown. It also includes the event which caused the exception.
+ ///
+ /// @param handler_name Name of the handler in which the exception is
+ /// thrown.
+ /// @param event An event which caused the exception.
+ ///
+ /// @throw HttpMessageParserBaseError.
+ void invalidEventError(const std::string& handler_name,
+ const unsigned int event);
+
+ /// @brief Handler for HTTP_PARSE_OK_ST and HTTP_PARSE_FAILED_ST.
+ ///
+ /// If parsing is successful, it calls @ref HttpRequest::create to validate
+ /// the HTTP request. In both cases it transitions the parser to the END_ST.
+ void parseEndedHandler();
+
+ /// @brief Tries to read next byte from buffer.
+ ///
+ /// @param [out] next A reference to the variable where read data should be
+ /// stored.
+ /// @param limit Maximum number of characters to be read.
+ ///
+ /// @return true if data was successfully read, false otherwise.
+ bool popNextFromBuffer(std::string& next, const size_t limit = 1);
+
+ /// @brief Checks if specified value is a character.
+ ///
+ /// @return true, if specified value is a character.
+ bool isChar(const char c) const;
+
+ /// @brief Checks if specified value is a control value.
+ ///
+ /// @return true, if specified value is a control value.
+ bool isCtl(const char c) const;
+
+ /// @brief Checks if specified value is a special character.
+ ///
+ /// @return true, if specified value is a special character.
+ bool isSpecial(const char c) const;
+
+ /// @brief Reference to the parsed HTTP message.
+ HttpMessage& message_;
+
+ /// @brief Internal buffer from which parser reads data.
+ std::string buffer_;
+
+ /// @brief Position of the next character to read from the buffer.
+ size_t buffer_pos_;
+
+ /// @brief Error message set by @ref onModelFailure.
+ std::string error_message_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/http_messages.cc b/src/lib/http/http_messages.cc
new file mode 100644
index 0000000..292d2d8
--- /dev/null
+++ b/src/lib/http/http_messages.cc
@@ -0,0 +1,77 @@
+// File created from ../../../src/lib/http/http_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTPS_REQUEST_RECEIVE_START = "HTTPS_REQUEST_RECEIVE_START";
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED = "HTTP_BAD_CLIENT_REQUEST_RECEIVED";
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS = "HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED = "HTTP_BAD_SERVER_RESPONSE_RECEIVED";
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS = "HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_CLIENT_MT_STARTED = "HTTP_CLIENT_MT_STARTED";
+extern const isc::log::MessageID HTTP_CLIENT_QUEUE_SIZE_GROWING = "HTTP_CLIENT_QUEUE_SIZE_GROWING";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED = "HTTP_CLIENT_REQUEST_RECEIVED";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED_DETAILS = "HTTP_CLIENT_REQUEST_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND = "HTTP_CLIENT_REQUEST_SEND";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND_DETAILS = "HTTP_CLIENT_REQUEST_SEND_DETAILS";
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED = "HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID HTTP_CONNECTION_CLOSE_CALLBACK_FAILED = "HTTP_CONNECTION_CLOSE_CALLBACK_FAILED";
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_FAILED = "HTTP_CONNECTION_HANDSHAKE_FAILED";
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_START = "HTTP_CONNECTION_HANDSHAKE_START";
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN = "HTTP_CONNECTION_SHUTDOWN";
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN_FAILED = "HTTP_CONNECTION_SHUTDOWN_FAILED";
+extern const isc::log::MessageID HTTP_CONNECTION_STOP = "HTTP_CONNECTION_STOP";
+extern const isc::log::MessageID HTTP_CONNECTION_STOP_FAILED = "HTTP_CONNECTION_STOP_FAILED";
+extern const isc::log::MessageID HTTP_DATA_RECEIVED = "HTTP_DATA_RECEIVED";
+extern const isc::log::MessageID HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED = "HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED = "HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID HTTP_REQUEST_RECEIVE_START = "HTTP_REQUEST_RECEIVE_START";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED = "HTTP_SERVER_RESPONSE_RECEIVED";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED_DETAILS = "HTTP_SERVER_RESPONSE_RECEIVED_DETAILS";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND = "HTTP_SERVER_RESPONSE_SEND";
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND_DETAILS = "HTTP_SERVER_RESPONSE_SEND_DETAILS";
+
+} // namespace http
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "HTTPS_REQUEST_RECEIVE_START", "start receiving request from %1",
+ "HTTP_BAD_CLIENT_REQUEST_RECEIVED", "bad request received from %1: %2",
+ "HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS", "detailed information about bad request received from %1:\n%2",
+ "HTTP_BAD_SERVER_RESPONSE_RECEIVED", "bad response received when communicating with %1: %2",
+ "HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS", "detailed information about bad response received from %1:\n%2",
+ "HTTP_CLIENT_MT_STARTED", "HttpClient has been started in multi-threaded mode running %1 threads",
+ "HTTP_CLIENT_QUEUE_SIZE_GROWING", "queue for URL: %1, now has %2 entries and may be growing too quickly",
+ "HTTP_CLIENT_REQUEST_RECEIVED", "received HTTP request from %1",
+ "HTTP_CLIENT_REQUEST_RECEIVED_DETAILS", "detailed information about well-formed request received from %1:\n%2",
+ "HTTP_CLIENT_REQUEST_SEND", "sending HTTP request %1 to %2",
+ "HTTP_CLIENT_REQUEST_SEND_DETAILS", "detailed information about request sent to %1:\n%2",
+ "HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED", "HTTP request timeout occurred when communicating with %1",
+ "HTTP_CONNECTION_CLOSE_CALLBACK_FAILED", "Connection close callback threw an exception",
+ "HTTP_CONNECTION_HANDSHAKE_FAILED", "TLS handshake with %1 failed with %2",
+ "HTTP_CONNECTION_HANDSHAKE_START", "start TLS handshake with %1 with timeout %2",
+ "HTTP_CONNECTION_SHUTDOWN", "shutting down HTTP connection from %1",
+ "HTTP_CONNECTION_SHUTDOWN_FAILED", "shutting down HTTP connection failed",
+ "HTTP_CONNECTION_STOP", "stopping HTTP connection from %1",
+ "HTTP_CONNECTION_STOP_FAILED", "stopping HTTP connection failed",
+ "HTTP_DATA_RECEIVED", "received %1 bytes from %2",
+ "HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED", "closing persistent connection with %1 as a result of a timeout",
+ "HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED", "premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3",
+ "HTTP_REQUEST_RECEIVE_START", "start receiving request from %1 with timeout %2",
+ "HTTP_SERVER_RESPONSE_RECEIVED", "received HTTP response from %1",
+ "HTTP_SERVER_RESPONSE_RECEIVED_DETAILS", "detailed information about well-formed response received from %1:\n%2",
+ "HTTP_SERVER_RESPONSE_SEND", "sending HTTP response %1 to %2",
+ "HTTP_SERVER_RESPONSE_SEND_DETAILS", "detailed information about response sent to %1:\n%2",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/http/http_messages.h b/src/lib/http/http_messages.h
new file mode 100644
index 0000000..4ac3030
--- /dev/null
+++ b/src/lib/http/http_messages.h
@@ -0,0 +1,42 @@
+// File created from ../../../src/lib/http/http_messages.mes
+
+#ifndef HTTP_MESSAGES_H
+#define HTTP_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace http {
+
+extern const isc::log::MessageID HTTPS_REQUEST_RECEIVE_START;
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED;
+extern const isc::log::MessageID HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED;
+extern const isc::log::MessageID HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_CLIENT_MT_STARTED;
+extern const isc::log::MessageID HTTP_CLIENT_QUEUE_SIZE_GROWING;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_SEND_DETAILS;
+extern const isc::log::MessageID HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID HTTP_CONNECTION_CLOSE_CALLBACK_FAILED;
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_FAILED;
+extern const isc::log::MessageID HTTP_CONNECTION_HANDSHAKE_START;
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN;
+extern const isc::log::MessageID HTTP_CONNECTION_SHUTDOWN_FAILED;
+extern const isc::log::MessageID HTTP_CONNECTION_STOP;
+extern const isc::log::MessageID HTTP_CONNECTION_STOP_FAILED;
+extern const isc::log::MessageID HTTP_DATA_RECEIVED;
+extern const isc::log::MessageID HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID HTTP_REQUEST_RECEIVE_START;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_RECEIVED_DETAILS;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND;
+extern const isc::log::MessageID HTTP_SERVER_RESPONSE_SEND_DETAILS;
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_MESSAGES_H
diff --git a/src/lib/http/http_messages.mes b/src/lib/http/http_messages.mes
new file mode 100644
index 0000000..625c5cb
--- /dev/null
+++ b/src/lib/http/http_messages.mes
@@ -0,0 +1,164 @@
+# Copyright (C) 2016-2021 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/.
+
+$NAMESPACE isc::http
+
+% HTTPS_REQUEST_RECEIVE_START start receiving request from %1
+This debug message is issued when the server starts receiving new request
+over the established connection. The argument specifies the address
+of the remote endpoint.
+
+% HTTP_BAD_CLIENT_REQUEST_RECEIVED bad request received from %1: %2
+This debug message is issued when an HTTP client sends malformed request to
+the server. This includes HTTP requests using unexpected content types,
+including malformed JSON etc. The first argument specifies an address of
+the remote endpoint which sent the request. The second argument provides
+a detailed error message.
+
+% HTTP_BAD_CLIENT_REQUEST_RECEIVED_DETAILS detailed information about bad request received from %1:\n%2
+This debug message is issued when an HTTP client sends malformed request to
+the server. It includes detailed information about the received request
+rejected by the server. The first argument specifies an address of
+the remote endpoint which sent the request. The second argument provides
+a request in the textual format. The request is truncated by the logger
+if it is too large to be printed.
+
+% HTTP_BAD_SERVER_RESPONSE_RECEIVED bad response received when communicating with %1: %2
+This debug message is issued when an HTTP client fails to receive a response
+from the server or when this response is malformed. The first argument
+specifies the server URL. The second argument provides a detailed error
+message.
+
+% HTTP_BAD_SERVER_RESPONSE_RECEIVED_DETAILS detailed information about bad response received from %1:\n%2
+This debug message is issued when an HTTP client receives malformed response
+from the server. The first argument specifies an URL of the server. The
+second argument provides a response in the textual format. The request is
+truncated by the logger if it is too large to be printed.
+
+% HTTP_CLIENT_MT_STARTED HttpClient has been started in multi-threaded mode running %1 threads
+This debug message is issued when a multi-threaded HTTP client instance has
+been created. The argument specifies the maximum number of threads.
+
+% HTTP_CLIENT_QUEUE_SIZE_GROWING queue for URL: %1, now has %2 entries and may be growing too quickly
+This warning message is issued when the queue of pending requests for the
+given URL appears to be growing more quickly than the requests can be handled.
+It will be emitted periodically as long as the queue size continues to grow.
+This may occur with a surge of client traffic creating a momentary backlog
+which then subsides as the surge subsides. If it happens continually then
+it most likely indicates a deployment configuration that cannot sustain the
+client load.
+
+% HTTP_CLIENT_REQUEST_RECEIVED received HTTP request from %1
+This debug message is issued when the server finished receiving a HTTP
+request from the remote endpoint. The address of the remote endpoint is
+specified as an argument.
+
+% HTTP_CLIENT_REQUEST_RECEIVED_DETAILS detailed information about well-formed request received from %1:\n%2
+This debug message is issued when the HTTP server receives a well-formed
+request. It includes detailed information about the received request. The
+first argument specifies an address of the remote endpoint which sent the
+request. The second argument provides the request in the textual format.
+The request is truncated by the logger if it is too large to be printed.
+
+% HTTP_CLIENT_REQUEST_SEND sending HTTP request %1 to %2
+This debug message is issued when the client is starting to send a HTTP
+request to a server. The first argument holds basic information
+about the request (HTTP version number and status code). The second
+argument specifies a URL of the server.
+
+% HTTP_CLIENT_REQUEST_SEND_DETAILS detailed information about request sent to %1:\n%2
+This debug message is issued right before the client sends an HTTP request
+to the server. It includes detailed information about the request. The
+first argument specifies an URL of the server to which the request is
+being sent. The second argument provides the request in the textual form.
+The request is truncated by the logger if it is too large to be printed.
+
+% HTTP_CLIENT_REQUEST_TIMEOUT_OCCURRED HTTP request timeout occurred when communicating with %1
+This debug message is issued when the HTTP request timeout has occurred and
+the server is going to send a response with Http Request timeout status
+code.
+
+% HTTP_CONNECTION_CLOSE_CALLBACK_FAILED Connection close callback threw an exception
+This is an error message emitted when the close connection callback
+registered on the connection failed unexpectedly. This is a programmatic
+error that should be submitted as a bug.
+
+% HTTP_CONNECTION_HANDSHAKE_FAILED TLS handshake with %1 failed with %2
+This information message is issued when the TLS handshake failed at the
+server side. The client address and the error message are displayed.
+
+% HTTP_CONNECTION_HANDSHAKE_START start TLS handshake with %1 with timeout %2
+This debug message is issued when the server starts the TLS handshake
+with the remote endpoint. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% HTTP_CONNECTION_SHUTDOWN shutting down HTTP connection from %1
+This debug message is issued when one of the HTTP connections is shut down.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% HTTP_CONNECTION_SHUTDOWN_FAILED shutting down HTTP connection failed
+This error message is issued when an error occurred during shutting down
+a HTTP connection with a client.
+
+% HTTP_CONNECTION_STOP stopping HTTP connection from %1
+This debug message is issued when one of the HTTP connections is stopped.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% HTTP_CONNECTION_STOP_FAILED stopping HTTP connection failed
+This error message is issued when an error occurred during closing a
+HTTP connection with a client.
+
+% HTTP_DATA_RECEIVED received %1 bytes from %2
+This debug message is issued when the server receives a chunk of data from
+the remote endpoint. This may include the whole request or only a part
+of the request. The first argument specifies the amount of received data.
+The second argument specifies an address of the remote endpoint which
+produced the data.
+
+% HTTP_IDLE_CONNECTION_TIMEOUT_OCCURRED closing persistent connection with %1 as a result of a timeout
+This debug message is issued when the persistent HTTP connection is being
+closed as a result of being idle.
+
+% HTTP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3
+This warning message is issued when unexpected timeout occurred during the
+transaction. This is proven to occur when the system clock is moved manually
+or as a result of synchronization with a time server. Any ongoing transactions
+will be interrupted. New transactions should be conducted normally.
+
+% HTTP_REQUEST_RECEIVE_START start receiving request from %1 with timeout %2
+This debug message is issued when the server starts receiving new request
+over the established connection. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% HTTP_SERVER_RESPONSE_RECEIVED received HTTP response from %1
+This debug message is issued when the client finished receiving an HTTP
+response from the server. The URL of the server is specified as an argument.
+
+% HTTP_SERVER_RESPONSE_RECEIVED_DETAILS detailed information about well-formed response received from %1:\n%2
+This debug message is issued when the HTTP client receives a well-formed
+response from the server. It includes detailed information about the
+received response. The first argument specifies a URL of the server which
+sent the response. The second argument provides the response in the textual
+format. The response is truncated by the logger if it is too large to
+be printed.
+
+% HTTP_SERVER_RESPONSE_SEND sending HTTP response %1 to %2
+This debug message is issued when the server is starting to send a HTTP
+response to a remote endpoint. The first argument holds basic information
+about the response (HTTP version number and status code). The second
+argument specifies an address of the remote endpoint.
+
+% HTTP_SERVER_RESPONSE_SEND_DETAILS detailed information about response sent to %1:\n%2
+This debug message is issued right before the server sends a HTTP response
+to the client. It includes detailed information about the response. The
+first argument specifies an address of the remote endpoint to which the
+response is being sent. The second argument provides a response in the
+textual form. The response is truncated by the logger if it is too large
+to be printed.
diff --git a/src/lib/http/http_types.h b/src/lib/http/http_types.h
new file mode 100644
index 0000000..68d1c09
--- /dev/null
+++ b/src/lib/http/http_types.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2016-2017 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 HTTP_TYPES_H
+#define HTTP_TYPES_H
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP protocol version.
+struct HttpVersion {
+ unsigned major_; ///< Major HTTP version.
+ unsigned minor_; ///< Minor HTTP version.
+
+ /// @brief Constructor.
+ ///
+ /// @param major Major HTTP version.
+ /// @param minor Minor HTTP version.
+ explicit HttpVersion(const unsigned major, const unsigned minor)
+ : major_(major), minor_(minor) {
+ }
+
+ /// @brief Operator less.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator<(const HttpVersion& rhs) const {
+ return ((major_ < rhs.major_) ||
+ ((major_ == rhs.major_) && (minor_ < rhs.minor_)));
+ }
+
+ /// @brief Operator equal.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator==(const HttpVersion& rhs) const {
+ return ((major_ == rhs.major_) && (minor_ == rhs.minor_));
+ }
+
+ /// @brief Operator not equal.
+ ///
+ /// @param rhs Version to compare to.
+ bool operator!=(const HttpVersion& rhs) const {
+ return (!operator==(rhs));
+ }
+
+ /// @name Methods returning @c HttpVersion object encapsulating typical
+ /// HTTP version numbers.
+ //@{
+
+ /// @brief HTTP version 1.0.
+ static const HttpVersion& HTTP_10() {
+ static HttpVersion ver(1, 0);
+ return (ver);
+ };
+
+ /// @brief HTTP version 1.1.
+ static const HttpVersion& HTTP_11() {
+ static HttpVersion ver(1, 1);
+ return (ver);
+ }
+
+ /// @brief HTTP version 2.0.
+ static const HttpVersion& HTTP_20() {
+ static HttpVersion ver(2, 0);
+ return (ver);
+ }
+
+ //@}
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/listener.cc b/src/lib/http/listener.cc
new file mode 100644
index 0000000..2e6d2e1
--- /dev/null
+++ b/src/lib/http/listener.cc
@@ -0,0 +1,56 @@
+// Copyright (C) 2017-2021 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 <asiolink/asio_wrapper.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace http {
+
+HttpListener::HttpListener(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const HttpListener::RequestTimeout& request_timeout,
+ const HttpListener::IdleTimeout& idle_timeout)
+ : impl_(new HttpListenerImpl(io_service, server_address, server_port,
+ tls_context, creator_factory,
+ request_timeout.value_,
+ idle_timeout.value_)) {
+}
+
+HttpListener::~HttpListener() {
+ stop();
+}
+
+IOAddress
+HttpListener::getLocalAddress() const {
+ return (impl_->getEndpoint().getAddress());
+}
+
+uint16_t
+HttpListener::getLocalPort() const {
+ return (impl_->getEndpoint().getPort());
+}
+
+void
+HttpListener::start() {
+ impl_->start();
+}
+
+void
+HttpListener::stop() {
+ impl_->stop();
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/listener.h b/src/lib/http/listener.h
new file mode 100644
index 0000000..8965f29
--- /dev/null
+++ b/src/lib/http/listener.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2017-2021 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 HTTP_LISTENER_H
+#define HTTP_LISTENER_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/crypto_tls.h>
+#include <exceptions/exceptions.h>
+#include <http/response_creator_factory.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace http {
+
+/// @brief A generic error raised by the @ref HttpListener class.
+class HttpListenerError : public Exception {
+public:
+ HttpListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief HttpListener implementation.
+class HttpListenerImpl;
+
+/// @brief HTTP listener.
+///
+/// This class is an entry point to the use of HTTP services in Kea.
+/// It creates a transport acceptor service on the specified address and
+/// port and listens to the incoming HTTP connections. The constructor
+/// receives a pointer to the implementation of the
+/// @ref HttpResponseCreatorFactory, which is used by the @ref HttpListener
+/// to create/retrieve an instance of the @ref HttpResponseCreator when the
+/// new HTTP response needs to be generated. The @ref HttpResponseCreator
+/// creates an object derived from the @ref HttpResponse class, encapsulating
+/// a HTTP response following some specific rules, e.g. having
+/// "application/json" content type.
+///
+/// When the listener is started it creates an instance of a @ref HttpConnection
+/// and stores them in the pool of active connections. The @ref HttpConnection
+/// is responsible for managing the next connection received and receiving the
+/// HTTP request and sending appropriate response. The listener can handle
+/// many HTTP connections simultaneously.
+///
+/// When the @ref HttpListener::stop is invoked, all active connections are
+/// closed and the listener stops accepting new connections.
+class HttpListener {
+public:
+
+ /// @brief HTTP request timeout value.
+ struct RequestTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Request timeout value in milliseconds.
+ explicit RequestTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Request timeout value specified.
+ };
+
+ /// @brief Idle connection timeout.
+ struct IdleTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Connection idle timeout value in milliseconds.
+ explicit IdleTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Connection idle timeout value specified.
+ };
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListener(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const RequestTimeout& request_timeout,
+ const IdleTimeout& idle_timeout);
+
+ /// @brief Destructor.
+ ///
+ /// Stops all active connections and closes transport acceptor service.
+ ~HttpListener();
+
+ /// @brief Returns local address on which server is listening.
+ asiolink::IOAddress getLocalAddress() const;
+
+ /// @brief Returns local port on which server is listening.
+ uint16_t getLocalPort() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+protected:
+
+ /// @brief Pointer to the implementation of the @ref HttpListener.
+ boost::shared_ptr<HttpListenerImpl> impl_;
+};
+
+/// @brief Pointer to the @ref HttpListener.
+typedef boost::shared_ptr<HttpListener> HttpListenerPtr;
+
+/// @brief Pointer to the const @ref HttpListener.
+typedef boost::shared_ptr<const HttpListener> ConstHttpListenerPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/listener_impl.cc b/src/lib/http/listener_impl.cc
new file mode 100644
index 0000000..fdcbdd0
--- /dev/null
+++ b/src/lib/http/listener_impl.cc
@@ -0,0 +1,125 @@
+// Copyright (C) 2019-2021 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 <asiolink/asio_wrapper.h>
+#include <http/connection_pool.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace http {
+
+HttpListenerImpl::HttpListenerImpl(IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout)
+ : io_service_(io_service), tls_context_(tls_context), acceptor_(),
+ endpoint_(), connections_(),
+ creator_factory_(creator_factory),
+ request_timeout_(request_timeout), idle_timeout_(idle_timeout) {
+ // Create the TCP or TLS acceptor.
+ if (!tls_context) {
+ acceptor_.reset(new HttpAcceptor(io_service));
+ } else {
+ acceptor_.reset(new HttpsAcceptor(io_service));
+ }
+
+ // Try creating an endpoint. This may cause exceptions.
+ try {
+ endpoint_.reset(new TCPEndpoint(server_address, server_port));
+
+ } catch (...) {
+ isc_throw(HttpListenerError, "unable to create TCP endpoint for "
+ << server_address << ":" << server_port);
+ }
+
+ // The factory must not be null.
+ if (!creator_factory_) {
+ isc_throw(HttpListenerError, "HttpResponseCreatorFactory must not"
+ " be null");
+ }
+
+ // Request timeout is signed and must be greater than 0.
+ if (request_timeout_ <= 0) {
+ isc_throw(HttpListenerError, "Invalid desired HTTP request timeout "
+ << request_timeout_);
+ }
+
+ // Idle persistent connection timeout is signed and must be greater than 0.
+ if (idle_timeout_ <= 0) {
+ isc_throw(HttpListenerError, "Invalid desired HTTP idle persistent connection"
+ " timeout " << idle_timeout_);
+ }
+}
+
+const TCPEndpoint&
+HttpListenerImpl::getEndpoint() const {
+ return (*endpoint_);
+}
+
+void
+HttpListenerImpl::start() {
+ try {
+ acceptor_->open(*endpoint_);
+ acceptor_->setOption(HttpAcceptor::ReuseAddress(true));
+ acceptor_->bind(*endpoint_);
+ acceptor_->listen();
+
+ } catch (const boost::system::system_error& ex) {
+ stop();
+ isc_throw(HttpListenerError, "unable to setup TCP acceptor for "
+ "listening to the incoming HTTP requests: " << ex.what());
+ }
+
+ accept();
+}
+
+void
+HttpListenerImpl::stop() {
+ connections_.stopAll();
+ acceptor_->close();
+}
+
+void
+HttpListenerImpl::accept() {
+ // In some cases we may need HttpResponseCreator instance per connection.
+ // But, the factory may also return the same instance each time. It
+ // depends on the use case.
+ HttpResponseCreatorPtr response_creator = creator_factory_->create();
+ HttpAcceptorCallback acceptor_callback =
+ std::bind(&HttpListenerImpl::acceptHandler, this, ph::_1);
+ HttpConnectionPtr conn = createConnection(response_creator,
+ acceptor_callback);
+ // Add this new connection to the pool.
+ connections_.start(conn);
+}
+
+void
+HttpListenerImpl::acceptHandler(const boost::system::error_code&) {
+ // The new connection has arrived. Set the acceptor to continue
+ // accepting new connections.
+ accept();
+}
+
+HttpConnectionPtr
+HttpListenerImpl::createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback) {
+ HttpConnectionPtr
+ conn(new HttpConnection(io_service_, acceptor_, tls_context_,
+ connections_, response_creator, callback,
+ request_timeout_, idle_timeout_));
+ return (conn);
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/listener_impl.h b/src/lib/http/listener_impl.h
new file mode 100644
index 0000000..4ad1960
--- /dev/null
+++ b/src/lib/http/listener_impl.h
@@ -0,0 +1,136 @@
+// Copyright (C) 2019-2021 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 HTTP_LISTENER_IMPL_H
+#define HTTP_LISTENER_IMPL_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/response_creator_factory.h>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Implementation of the @ref HttpListener.
+class HttpListenerImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref HttpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerImpl(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout);
+
+ /// @brief Virtual destructor.
+ virtual ~HttpListenerImpl() {
+ }
+
+ /// @brief Returns reference to the current listener endpoint.
+ const asiolink::TCPEndpoint& getEndpoint() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new HTTP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref HttpListener::stop is called.
+ ///
+ /// @throw HttpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+protected:
+
+ /// @brief Creates @ref HttpConnection instance and adds it to the
+ /// pool of active connections.
+ ///
+ /// The next accepted connection will be handled by this instance.
+ void accept();
+
+ /// @brief Callback invoked when the new connection is accepted.
+ ///
+ /// It calls @c HttpListener::accept to create new @c HttpConnection
+ /// instance.
+ ///
+ /// @param ec Error code passed to the handler. This is currently ignored.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Creates an instance of the @c HttpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ ///
+ /// @return Pointer to the created connection.
+ virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback);
+
+ /// @brief Reference to the IO service.
+ asiolink::IOService& io_service_;
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Acceptor instance.
+ HttpAcceptorPtr acceptor_;
+
+ /// @brief Pointer to the endpoint representing IP address and port on
+ /// which the service is running.
+ boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
+
+ /// @brief Pool of active connections.
+ HttpConnectionPool connections_;
+
+ /// @brief Pointer to the @ref HttpResponseCreatorFactory.
+ HttpResponseCreatorFactoryPtr creator_factory_;
+
+ /// @brief Timeout for HTTP Request Timeout desired.
+ long request_timeout_;
+
+ /// @brief Timeout after which idle persistent connection is closed by
+ /// the server.
+ long idle_timeout_;
+};
+
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // HTTP_LISTENER_IMPL_H
diff --git a/src/lib/http/post_request.cc b/src/lib/http/post_request.cc
new file mode 100644
index 0000000..1658985
--- /dev/null
+++ b/src/lib/http/post_request.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2016-2020 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 <http/post_request.h>
+
+namespace isc {
+namespace http {
+
+PostHttpRequest::PostHttpRequest()
+ : HttpRequest() {
+ requireHttpMethod(Method::HTTP_POST);
+ requireHeader("Content-Length");
+ requireHeader("Content-Type");
+}
+
+PostHttpRequest::PostHttpRequest(const Method& method, const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header,
+ const BasicHttpAuthPtr& basic_auth)
+ : HttpRequest(method, uri, version, host_header, basic_auth) {
+ requireHttpMethod(Method::HTTP_POST);
+ requireHeader("Content-Length");
+ requireHeader("Content-Type");
+}
+
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/post_request.h b/src/lib/http/post_request.h
new file mode 100644
index 0000000..095fdab
--- /dev/null
+++ b/src/lib/http/post_request.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2016-2020 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 HTTP_POST_REQUEST_H
+#define HTTP_POST_REQUEST_H
+
+#include <http/request.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class PostHttpRequest;
+
+/// @brief Pointer to @ref PostHttpRequest.
+typedef boost::shared_ptr<PostHttpRequest> PostHttpRequestPtr;
+/// @brief Pointer to const @ref PostHttpRequest.
+typedef boost::shared_ptr<const PostHttpRequest> ConstPostHttpRequestPtr;
+
+/// @brief Represents HTTP POST request.
+///
+/// Instructs the parent class to require:
+/// - HTTP POST message type,
+/// - Content-Length header,
+/// - Content-Type header.
+class PostHttpRequest : public HttpRequest {
+public:
+
+ /// @brief Constructor for inbound HTTP request.
+ PostHttpRequest();
+
+ /// @brief Constructor for outbound HTTP request.
+ ///
+ /// @param method HTTP method, e.g. POST.
+ /// @param uri URI.
+ /// @param version HTTP version.
+ /// @param host_header Host header to be included in the request. The default
+ /// is the empty Host header.
+ /// @param basic_auth Basic HTTP authentication credential. The default
+ /// is no authentication.
+ PostHttpRequest(const Method& method, const std::string& uri, const HttpVersion& version,
+ const HostHttpHeader& host_header = HostHttpHeader(),
+ const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr());
+};
+
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/post_request_json.cc b/src/lib/http/post_request_json.cc
new file mode 100644
index 0000000..909a901
--- /dev/null
+++ b/src/lib/http/post_request_json.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2016-2022 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 <http/post_request_json.h>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+PostHttpRequestJson::PostHttpRequestJson()
+ : PostHttpRequest(), json_() {
+ requireHeaderValue("Content-Type", "application/json");
+}
+
+PostHttpRequestJson::PostHttpRequestJson(const Method& method, const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header,
+ const BasicHttpAuthPtr& basic_auth)
+ : PostHttpRequest(method, uri, version, host_header, basic_auth) {
+ requireHeaderValue("Content-Type", "application/json");
+ context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+}
+
+
+void
+PostHttpRequestJson::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // Parse JSON body and store.
+ parseBodyAsJson();
+ finalized_ = true;
+}
+
+void
+PostHttpRequestJson::reset() {
+ PostHttpRequest::reset();
+ json_.reset();
+}
+
+ConstElementPtr
+PostHttpRequestJson::getBodyAsJson() const {
+ checkFinalized();
+ return (json_);
+}
+
+void
+PostHttpRequestJson::setBodyAsJson(const data::ConstElementPtr& body) {
+ if (body) {
+ context_->body_ = body->str();
+ json_ = body;
+
+ } else {
+ context_->body_.clear();
+ }
+}
+
+ConstElementPtr
+PostHttpRequestJson::getJsonElement(const std::string& element_name) const {
+ try {
+ ConstElementPtr body = getBodyAsJson();
+ if (body) {
+ const std::map<std::string, ConstElementPtr>& map_value = body->mapValue();
+ auto map_element = map_value.find(element_name);
+ if (map_element != map_value.end()) {
+ return (map_element->second);
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpRequestJsonError, "unable to get JSON element "
+ << element_name << ": " << ex.what());
+ }
+ return (ConstElementPtr());
+}
+
+void
+PostHttpRequestJson::parseBodyAsJson() {
+ try {
+ // Only parse the body if it hasn't been parsed yet.
+ if (!json_ && !context_->body_.empty()) {
+ ElementPtr json = Element::fromJSON(context_->body_);
+ if (!remote_.empty() && (json->getType() == Element::map)) {
+ json->set("remote-address", Element::create(remote_));
+ }
+ json_ = json;
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(HttpRequestJsonError, "unable to parse the body of the HTTP"
+ " request: " << ex.what());
+ }
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/post_request_json.h b/src/lib/http/post_request_json.h
new file mode 100644
index 0000000..7a2faba
--- /dev/null
+++ b/src/lib/http/post_request_json.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2016-2020 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 HTTP_POST_REQUEST_JSON_H
+#define HTTP_POST_REQUEST_JSON_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <http/post_request.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when body of the HTTP message is not JSON.
+class HttpRequestJsonError : public HttpRequestError {
+public:
+ HttpRequestJsonError(const char* file, size_t line, const char* what) :
+ HttpRequestError(file, line, what) { };
+};
+
+class PostHttpRequestJson;
+
+/// @brief Pointer to @ref PostHttpRequestJson.
+typedef boost::shared_ptr<PostHttpRequestJson> PostHttpRequestJsonPtr;
+
+/// @brief Represents HTTP POST request with JSON body.
+///
+/// In addition to the requirements specified by the @ref PostHttpRequest
+/// this class requires that the "Content-Type" is "application/json".
+///
+/// This class provides methods to parse and retrieve JSON data structures.
+class PostHttpRequestJson : public PostHttpRequest {
+public:
+
+ /// @brief Constructor for inbound HTTP request.
+ explicit PostHttpRequestJson();
+
+ /// @brief Constructor for outbound HTTP request.
+ ///
+ /// This constructor adds "Content-Type" header with the value of
+ /// "application/json" to the context.
+ ///
+ /// @param method HTTP method, e.g. POST.
+ /// @param uri URI.
+ /// @param version HTTP version.
+ /// @param host_header Host header to be included in the request. The default
+ /// is the empty Host header.
+ /// @param basic_auth Basic HTTP authentication credential. The default
+ /// is no authentication.
+ explicit PostHttpRequestJson(const Method& method, const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header = HostHttpHeader(),
+ const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr());
+
+ /// @brief Complete parsing of the HTTP request.
+ ///
+ /// This method parses the JSON body into the structure of
+ /// @ref data::ConstElementPtr objects.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Retrieves JSON body.
+ ///
+ /// @return Pointer to the root element of the JSON structure.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getBodyAsJson() const;
+
+ /// @brief Sets JSON body for an outbound message.
+ ///
+ /// Note that this method copies the pointer to the body, rather than
+ /// the entire data structure. Thus, the original object should not be
+ /// modified after this method is called. If the specified pointer is
+ /// null, the empty body is set.
+ ///
+ /// @param body JSON structure to be used as a body.
+ void setBodyAsJson(const data::ConstElementPtr& body);
+
+ /// @brief Retrieves a single JSON element.
+ ///
+ /// The element must be at top level of the JSON structure.
+ ///
+ /// @param element_name Element name.
+ ///
+ /// @return Pointer to the specified element or NULL if such element
+ /// doesn't exist.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getJsonElement(const std::string& element_name) const;
+
+protected:
+
+ /// @brief Interprets body as JSON, which can be later retrieved using
+ /// data element objects.
+ void parseBodyAsJson();
+
+ /// @brief Pointer to the parsed JSON body.
+ data::ConstElementPtr json_;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/request.cc b/src/lib/http/request.cc
new file mode 100644
index 0000000..f753fea
--- /dev/null
+++ b/src/lib/http/request.cc
@@ -0,0 +1,285 @@
+// Copyright (C) 2016-2022 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 <http/request.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+namespace {
+
+/// @brief New line (CRLF).
+const std::string crlf = "\r\n";
+
+}
+
+namespace isc {
+namespace http {
+
+bool HttpRequest::recordSubject_ = false;
+
+bool HttpRequest::recordIssuer_ = false;
+
+bool HttpRequest::recordBasicAuth_ = false;
+
+HttpRequest::HttpRequest()
+ : HttpMessage(INBOUND), required_methods_(),
+ method_(Method::HTTP_METHOD_UNKNOWN),
+ context_(new HttpRequestContext()),
+ remote_(""), tls_(false), subject_(""), issuer_(""),
+ basic_auth_(""), custom_("") {
+}
+
+HttpRequest::HttpRequest(const Method& method,
+ const std::string& uri,
+ const HttpVersion& version,
+ const HostHttpHeader& host_header,
+ const BasicHttpAuthPtr& basic_auth)
+ : HttpMessage(OUTBOUND), required_methods_(),
+ method_(Method::HTTP_METHOD_UNKNOWN),
+ context_(new HttpRequestContext()),
+ remote_(""), tls_(false), subject_(""), issuer_(""),
+ basic_auth_(""), custom_("") {
+ context()->method_ = methodToString(method);
+ context()->uri_ = uri;
+ context()->http_version_major_ = version.major_;
+ context()->http_version_minor_ = version.minor_;
+ // The Host header is mandatory in HTTP/1.1 and should be placed before
+ // any other headers. We also include it for HTTP/1.0 as it doesn't
+ // harm to include it.
+ context()->headers_.push_back(HttpHeaderContext(host_header.getName(),
+ host_header.getValue()));
+ if (basic_auth) {
+ context()->headers_.push_back(BasicAuthHttpHeaderContext(*basic_auth));
+ }
+}
+
+void
+HttpRequest::requireHttpMethod(const HttpRequest::Method& method) {
+ required_methods_.insert(method);
+}
+
+void
+HttpRequest::create() {
+ try {
+ // The RequestParser doesn't validate the method name. Thus, this
+ // may throw an exception. But, we're fine with lower case names,
+ // e.g. get, post etc.
+ method_ = methodFromString(context_->method_);
+
+ // Check if the method is allowed for this request.
+ if (!inRequiredSet(method_, required_methods_)) {
+ isc_throw(BadValue, "use of HTTP " << methodToString(method_)
+ << " not allowed");
+ }
+
+ http_version_.major_ = context_->http_version_major_;
+ http_version_.minor_ = context_->http_version_minor_;
+
+ // Check if the HTTP version is allowed for this request.
+ if (!inRequiredSet(http_version_, required_versions_)) {
+ isc_throw(BadValue, "use of HTTP version "
+ << http_version_.major_ << "."
+ << http_version_.minor_
+ << " not allowed");
+ }
+
+ // Copy headers from the context.
+ for (auto header = context_->headers_.begin();
+ header != context_->headers_.end();
+ ++header) {
+ HttpHeaderPtr hdr(new HttpHeader(header->name_, header->value_));
+ headers_[hdr->getLowerCaseName()] = hdr;
+ }
+
+ if (getDirection() == HttpMessage::OUTBOUND) {
+ HttpHeaderPtr hdr(new HttpHeader("Content-Length",
+ boost::lexical_cast<std::string>(context_->body_.length())));
+ headers_["content-length"] = hdr;
+ }
+
+ // Iterate over required headers and check that they exist
+ // in the HTTP request.
+ for (auto req_header = required_headers_.begin();
+ req_header != required_headers_.end();
+ ++req_header) {
+ auto header = headers_.find(req_header->first);
+ if (header == headers_.end()) {
+ isc_throw(BadValue, "required header " << req_header->first
+ << " not found in the HTTP request");
+ } else if (!req_header->second->getValue().empty() &&
+ !header->second->isValueEqual(req_header->second->getValue())) {
+ // If specific value is required for the header, check
+ // that the value in the HTTP request matches it.
+ isc_throw(BadValue, "required header's " << header->first
+ << " value is " << req_header->second->getValue()
+ << ", but " << header->second->getValue() << " was found");
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ // Reset the state of the object if we failed at any point.
+ reset();
+ isc_throw(HttpRequestError, ex.what());
+ }
+
+ // All ok.
+ created_ = true;
+}
+
+void
+HttpRequest::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // Copy the body from the context. Derive classes may further
+ // interpret the body contents, e.g. against the Content-Type.
+ finalized_ = true;
+}
+
+void
+HttpRequest::reset() {
+ created_ = false;
+ finalized_ = false;
+ method_ = HttpRequest::Method::HTTP_METHOD_UNKNOWN;
+ headers_.clear();
+}
+
+HttpRequest::Method
+HttpRequest::getMethod() const {
+ checkCreated();
+ return (method_);
+}
+
+std::string
+HttpRequest::getUri() const {
+ checkCreated();
+ return (context_->uri_);
+}
+
+std::string
+HttpRequest::getBody() const {
+ checkFinalized();
+ return (context_->body_);
+}
+
+std::string
+HttpRequest::toBriefString() const {
+ checkFinalized();
+
+ std::ostringstream s;
+ s << methodToString(getMethod()) << " " << getUri() << " HTTP/" <<
+ getHttpVersion().major_ << "." << getHttpVersion().minor_;
+ return (s.str());
+}
+
+std::string
+HttpRequest::toString() const {
+ checkFinalized();
+
+ std::ostringstream s;
+ // HTTP method, URI and version number.
+ s << toBriefString() << crlf;
+
+ // Host header must go first.
+ HttpHeaderPtr host_header;
+ try {
+ host_header = getHeader("Host");
+ if (host_header) {
+ s << host_header->getName() << ": " << host_header->getValue() << crlf;
+ }
+
+ } catch (...) {
+ // impossible condition
+ }
+
+ // Add all other headers.
+ for (auto header_it = headers_.cbegin(); header_it != headers_.cend();
+ ++header_it) {
+ if (header_it->second->getName() != "Host") {
+ s << header_it->second->getName() << ": " << header_it->second->getValue()
+ << crlf;
+ }
+ }
+
+ s << crlf;
+
+ s << getBody();
+
+ return (s.str());
+}
+
+bool
+HttpRequest::isPersistent() const {
+ HttpHeaderPtr conn;
+
+ try {
+ conn = getHeader("connection");
+
+ } catch (...) {
+ // If there is an exception, it means that the header was not found.
+ }
+
+ std::string conn_value;
+ if (conn) {
+ conn_value = conn->getLowerCaseValue();
+ }
+
+ HttpVersion ver = getHttpVersion();
+
+ return (((ver == HttpVersion::HTTP_10()) && (conn_value == "keep-alive")) ||
+ ((HttpVersion::HTTP_10() < ver) && (conn_value.empty() || (conn_value != "close"))));
+}
+
+HttpRequest::Method
+HttpRequest::methodFromString(std::string method) const {
+ boost::to_upper(method);
+ if (method == "GET") {
+ return (Method::HTTP_GET);
+ } else if (method == "POST") {
+ return (Method::HTTP_POST);
+ } else if (method == "HEAD") {
+ return (Method::HTTP_HEAD);
+ } else if (method == "PUT") {
+ return (Method::HTTP_PUT);
+ } else if (method == "DELETE") {
+ return (Method::HTTP_DELETE);
+ } else if (method == "OPTIONS") {
+ return (Method::HTTP_OPTIONS);
+ } else if (method == "CONNECT") {
+ return (Method::HTTP_CONNECT);
+ } else {
+ isc_throw(HttpRequestError, "unknown HTTP method " << method);
+ }
+}
+
+std::string
+HttpRequest::methodToString(const HttpRequest::Method& method) const {
+ switch (method) {
+ case Method::HTTP_GET:
+ return ("GET");
+ case Method::HTTP_POST:
+ return ("POST");
+ case Method::HTTP_HEAD:
+ return ("HEAD");
+ case Method::HTTP_PUT:
+ return ("PUT");
+ case Method::HTTP_DELETE:
+ return ("DELETE");
+ case Method::HTTP_OPTIONS:
+ return ("OPTIONS");
+ case Method::HTTP_CONNECT:
+ return ("CONNECT");
+ default:
+ return ("unknown HTTP method");
+ }
+}
+
+}
+}
diff --git a/src/lib/http/request.h b/src/lib/http/request.h
new file mode 100644
index 0000000..014e6e4
--- /dev/null
+++ b/src/lib/http/request.h
@@ -0,0 +1,312 @@
+// Copyright (C) 2016-2022 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 HTTP_REQUEST_H
+#define HTTP_REQUEST_H
+
+#include <hooks/callout_handle_associate.h>
+#include <http/basic_auth.h>
+#include <http/http_message.h>
+#include <http/request_context.h>
+#include <boost/shared_ptr.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpRequest class.
+class HttpRequestError : public HttpMessageError {
+public:
+ HttpRequestError(const char* file, size_t line, const char* what) :
+ HttpMessageError(file, line, what) { };
+};
+
+class HttpRequest;
+
+/// @brief Pointer to the @ref HttpRequest object.
+typedef boost::shared_ptr<HttpRequest> HttpRequestPtr;
+
+/// @brief Represents HTTP request message.
+///
+/// This derivation of the @c HttpMessage class is specialized to represent
+/// HTTP requests. This class provides two constructors for creating an inbound
+/// and outbound request instance respectively. This class is associated with
+/// an instance of the @c HttpRequestContext, which is used to provide request
+/// specific values, such as: HTTP method, version, URI and headers.
+///
+/// The derivations of this class provide specializations and specify the
+/// HTTP methods, versions and headers supported/required in the specific use
+/// cases. For example, the @c PostHttpRequest class derives from @c HttpRequest
+/// and it requires that request uses POST method. The @c PostHttpRequestJson,
+/// which derives from @c PostHttpRequest requires that the POST message
+/// includes body holding a JSON structure and provides methods to parse the
+/// JSON body.
+///
+/// Objects of this class is used to record some parameters for access control:
+/// - the remote address
+/// - the use of TLS
+/// - the first commonName of the SubjectName of the client certificate
+/// - the first commonName of the IssuerName of the client certificate
+/// - the user ID of the basic HTTP authentication
+/// - a custom value
+///
+/// Callouts are associated to the request.
+class HttpRequest : public HttpMessage, public hooks::CalloutHandleAssociate {
+public:
+
+ /// @brief HTTP methods.
+ enum class Method {
+ HTTP_GET,
+ HTTP_POST,
+ HTTP_HEAD,
+ HTTP_PUT,
+ HTTP_DELETE,
+ HTTP_OPTIONS,
+ HTTP_CONNECT,
+ HTTP_METHOD_UNKNOWN
+ };
+
+ /// @brief Constructor for inbound HTTP request.
+ HttpRequest();
+
+ /// @brief Constructor for outbound HTTP request.
+ ///
+ /// The constructor always includes Host header in the request, regardless
+ /// of the HTTP version used.
+ ///
+ /// @param method HTTP method, e.g. POST.
+ /// @param uri URI.
+ /// @param version HTTP version.
+ /// @param host_header Host header to be included in the request. The default
+ /// is the empty Host header.
+ /// @param basic_auth Basic HTTP authentication credential. The default
+ /// is no authentication.
+ HttpRequest(const Method& method, const std::string& uri, const HttpVersion& version,
+ const HostHttpHeader& host_header = HostHttpHeader(),
+ const BasicHttpAuthPtr& basic_auth = BasicHttpAuthPtr());
+
+ /// @brief Returns pointer to the @ref HttpRequestContext.
+ ///
+ /// The context holds intermediate data for creating a request. The request
+ /// parser stores parsed raw data in the context. When parsing is finished,
+ /// the data are validated and committed into the @c HttpRequest.
+ ///
+ /// @return Pointer to the underlying @ref HttpRequestContext.
+ const HttpRequestContextPtr& context() const {
+ return (context_);
+ }
+
+ /// @brief Returns remote address.
+ ///
+ /// @return remote address from HTTP connection
+ /// getRemote method.
+ std::string getRemote() const {
+ return (remote_);
+ }
+
+ /// @brief Set remote address.
+ ///
+ /// @param remote Remote end-point address in textual form.
+ void setRemote(const std::string& remote) {
+ remote_ = remote;
+ }
+
+ /// @brief Specifies an HTTP method allowed for the request.
+ ///
+ /// Allowed methods must be specified prior to calling @ref create method.
+ /// If no method is specified, all methods are supported.
+ ///
+ /// @param method HTTP method allowed for the request.
+ void requireHttpMethod(const HttpRequest::Method& method);
+
+ /// @brief Commits information held in the context into the request.
+ ///
+ /// This function reads HTTP method, version and headers from the context
+ /// and validates their values. For the outbound messages, it automatically
+ /// appends Content-Length header to the request, based on the length of the
+ /// request body.
+ ///
+ /// @throw HttpRequestError if the parsed request doesn't meet the specified
+ /// requirements for it.
+ virtual void create();
+
+ /// @brief Completes creation of the HTTP request.
+ ///
+ /// This method marks the message as finalized. The outbound request may now be
+ /// sent over the TCP socket. The information from the inbound message may be
+ /// read, including the request body.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Returns HTTP method of the request.
+ Method getMethod() const;
+
+ /// @brief Returns HTTP request URI.
+ std::string getUri() const;
+
+
+ /// @brief Returns HTTP message body as string.
+ std::string getBody() const;
+
+ /// @brief Returns HTTP method, URI and HTTP version as a string.
+ std::string toBriefString() const;
+
+ /// @brief Returns HTTP message as string.
+ ///
+ /// This method is called to generate the outbound HTTP message. Make
+ /// sure to call @c finalize prior to calling this method.
+ virtual std::string toString() const;
+
+ /// @brief Checks if the client has requested persistent connection.
+ ///
+ /// For the HTTP/1.0 case, the connection is persistent if the client has
+ /// included Connection: keep-alive header. For the HTTP/1.1 case, the
+ /// connection is assumed to be persistent unless Connection: close header
+ /// has been included.
+ ///
+ /// @return true if the client has requested persistent connection, false
+ /// otherwise.
+ bool isPersistent() const;
+
+ /// Access control parameters: get/set methods.
+
+ /// @brief Returns recorded TLS usage.
+ ///
+ /// @return recorded TLS usage.
+ bool getTls() const {
+ return (tls_);
+ }
+
+ /// @brief Set (record) TLS usage.
+ ///
+ /// @param tls the TLS usage.
+ void setTls(bool tls) {
+ tls_ = tls;
+ }
+
+ /// @brief Returns recorded subject name.
+ ///
+ /// @return recorded subject name.
+ std::string getSubject() const {
+ return (subject_);
+ }
+
+ /// @brief Set (record) subject name.
+ ///
+ /// @param subject the subject name.
+ void setSubject(const std::string& subject) {
+ subject_ = subject;
+ }
+
+ /// @brief Returns recorded issuer name.
+ ///
+ /// @return recorded issuer name.
+ std::string getIssuer() const {
+ return (issuer_);
+ }
+
+ /// @brief Set (record) issuer name.
+ ///
+ /// @param issuer the issuer name.
+ void setIssuer(const std::string& issuer) {
+ issuer_ = issuer;
+ }
+
+ /// @brief Returns recorded basic auth.
+ ///
+ /// @return recorded basic auth.
+ std::string getBasicAuth() const {
+ return (basic_auth_);
+ }
+
+ /// @brief Set (record) basic auth.
+ ///
+ /// @param basic_auth the basic auth.
+ void setBasicAuth(const std::string& basic_auth) {
+ basic_auth_ = basic_auth;
+ }
+
+ /// @brief Returns recorded custom name.
+ ///
+ /// @return recorded custom name.
+ std::string getCustom() const {
+ return (custom_);
+ }
+
+ /// @brief Set (record) custom name.
+ ///
+ /// @param custom the custom name.
+ void setCustom(const std::string& custom) {
+ custom_ = custom;
+ }
+
+ /// Access control parameters: Flags which indicate what information to record.
+ /// Remote address and TLS usage are always recorded.
+
+ /// @brief Record subject name.
+ static bool recordSubject_;
+
+ /// @brief Record issuer name.
+ static bool recordIssuer_;
+
+ /// @brief Record basic auth.
+ static bool recordBasicAuth_;
+
+protected:
+
+ /// @brief Converts HTTP method specified in textual format to @ref Method.
+ ///
+ /// @param method HTTP method specified in the textual format. This value
+ /// is case insensitive.
+ ///
+ /// @return HTTP method as enum.
+ /// @throw HttpRequestError if unknown method specified.
+ Method methodFromString(std::string method) const;
+
+ /// @brief Converts HTTP method to string.
+ ///
+ /// @param method HTTP method specified as enum.
+ ///
+ /// @return HTTP method as string.
+ std::string methodToString(const HttpRequest::Method& method) const;
+
+ /// @brief Set of required HTTP methods.
+ ///
+ /// If the set is empty, all methods are allowed.
+ std::set<Method> required_methods_;
+
+ /// @brief HTTP method of the request.
+ Method method_;
+
+ /// @brief Pointer to the @ref HttpRequestContext holding parsed
+ /// data.
+ HttpRequestContextPtr context_;
+
+ /// @brief Remote address.
+ std::string remote_;
+
+ /// @brief TLS usage.
+ bool tls_;
+
+ /// @brief Subject name.
+ std::string subject_;
+
+ /// @brief Issuer name.
+ std::string issuer_;
+
+ /// @brief Basic auth.
+ std::string basic_auth_;
+
+ /// @brief Custom name.
+ std::string custom_;
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/http/request_context.h b/src/lib/http/request_context.h
new file mode 100644
index 0000000..bcbacda
--- /dev/null
+++ b/src/lib/http/request_context.h
@@ -0,0 +1,44 @@
+// Copyright (C) 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/.
+
+#ifndef HTTP_REQUEST_CONTEXT_H
+#define HTTP_REQUEST_CONTEXT_H
+
+#include <http/header_context.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP request context.
+///
+/// The context is used by the @ref HttpRequestParser to store parsed
+/// data. This data is later used to create an instance of the
+/// @ref HttpRequest or its derivation.
+struct HttpRequestContext {
+ /// @brief HTTP request method.
+ std::string method_;
+ /// @brief HTTP request URI.
+ std::string uri_;
+ /// @brief HTTP major version number.
+ unsigned int http_version_major_;
+ /// @brief HTTP minor version number.
+ unsigned int http_version_minor_;
+ /// @brief Collection of HTTP headers.
+ std::vector<HttpHeaderContext> headers_;
+ /// @brief HTTP request body.
+ std::string body_;
+};
+
+/// @brief Pointer to the @ref HttpRequestContext.
+typedef boost::shared_ptr<HttpRequestContext> HttpRequestContextPtr;
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/request_parser.cc b/src/lib/http/request_parser.cc
new file mode 100644
index 0000000..d774807
--- /dev/null
+++ b/src/lib/http/request_parser.cc
@@ -0,0 +1,458 @@
+// Copyright (C) 2016-2020 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 <http/request_parser.h>
+#include <functional>
+#include <iostream>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpRequestParser::RECEIVE_START_ST;
+const int HttpRequestParser::HTTP_METHOD_ST;
+const int HttpRequestParser::HTTP_URI_ST;
+const int HttpRequestParser::HTTP_VERSION_H_ST;
+const int HttpRequestParser::HTTP_VERSION_T1_ST;
+const int HttpRequestParser::HTTP_VERSION_T2_ST;
+const int HttpRequestParser::HTTP_VERSION_P_ST;
+const int HttpRequestParser::HTTP_VERSION_SLASH_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MAJOR_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_START_ST;
+const int HttpRequestParser::HTTP_VERSION_MINOR_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE1_ST;
+const int HttpRequestParser::HEADER_LINE_START_ST;
+const int HttpRequestParser::HEADER_LWS_ST;
+const int HttpRequestParser::HEADER_NAME_ST;
+const int HttpRequestParser::SPACE_BEFORE_HEADER_VALUE_ST;
+const int HttpRequestParser::HEADER_VALUE_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE2_ST;
+const int HttpRequestParser::EXPECTING_NEW_LINE3_ST;
+const int HttpRequestParser::HTTP_BODY_ST;
+
+HttpRequestParser::HttpRequestParser(HttpRequest& request)
+ : HttpMessageParserBase(request), request_(request),
+ context_(request_.context()) {
+}
+
+void
+HttpRequestParser::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+HttpRequestParser::defineStates() {
+ // Call parent class implementation first.
+ HttpMessageParserBase::defineStates();
+
+ // Define HTTP parser specific states.
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ std::bind(&HttpRequestParser::receiveStartHandler, this));
+
+ defineState(HTTP_METHOD_ST, "HTTP_METHOD_ST",
+ std::bind(&HttpRequestParser::httpMethodHandler, this));
+
+ defineState(HTTP_URI_ST, "HTTP_URI_ST",
+ std::bind(&HttpRequestParser::uriHandler, this));
+
+ defineState(HTTP_VERSION_H_ST, "HTTP_VERSION_H_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'H',
+ HTTP_VERSION_T1_ST));
+
+ defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_T2_ST));
+
+ defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_P_ST));
+
+ defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, 'P',
+ HTTP_VERSION_SLASH_ST));
+
+ defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
+ std::bind(&HttpRequestParser::versionHTTPHandler, this, '/',
+ HTTP_VERSION_MAJOR_ST));
+
+ defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
+ std::bind(&HttpRequestParser::versionNumberStartHandler, this,
+ HTTP_VERSION_MAJOR_ST,
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
+ std::bind(&HttpRequestParser::versionNumberHandler, this,
+ '.', HTTP_VERSION_MINOR_START_ST,
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
+ std::bind(&HttpRequestParser::versionNumberStartHandler, this,
+ HTTP_VERSION_MINOR_ST,
+ &context_->http_version_minor_));
+
+ defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
+ std::bind(&HttpRequestParser::versionNumberHandler, this,
+ '\r', EXPECTING_NEW_LINE1_ST,
+ &context_->http_version_minor_));
+
+ defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
+ std::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
+ std::bind(&HttpRequestParser::headerLineStartHandler, this));
+
+ defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
+ std::bind(&HttpRequestParser::headerLwsHandler, this));
+
+ defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
+ std::bind(&HttpRequestParser::headerNameHandler, this));
+
+ defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
+ std::bind(&HttpRequestParser::spaceBeforeHeaderValueHandler, this));
+
+ defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
+ std::bind(&HttpRequestParser::headerValueHandler, this));
+
+ defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
+ std::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
+ std::bind(&HttpRequestParser::expectingNewLineHandler, this,
+ HTTP_PARSE_OK_ST));
+
+ defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
+ std::bind(&HttpRequestParser::bodyHandler, this));
+}
+
+void
+HttpRequestParser::receiveStartHandler() {
+ std::string bytes;
+ getNextFromBuffer(bytes);
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case START_EVT:
+ // The first byte should contain a first character of the
+ // HTTP method name.
+ if (!isChar(bytes[0]) || isCtl(bytes[0]) || isSpecial(bytes[0])) {
+ parseFailure("invalid first character " + std::string(1, bytes[0]) +
+ " in HTTP method name");
+
+ } else {
+ context_->method_.push_back(bytes[0]);
+ transition(HTTP_METHOD_ST, DATA_READ_OK_EVT);
+ }
+ break;
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+HttpRequestParser::httpMethodHandler() {
+ stateWithReadHandler("httpMethodHandler", [this](const char c) {
+ // Space character terminates the HTTP method name. Next thing
+ // is the URI.
+ if (c == ' ') {
+ transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in HTTP method name");
+
+ } else {
+ // Still parsing the method. Append the next character to the
+ // method name.
+ context_->method_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::uriHandler() {
+ stateWithReadHandler("uriHandler", [this](const char c) {
+ // Space character terminates the URI.
+ if (c == ' ') {
+ transition(HTTP_VERSION_H_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in HTTP URI");
+
+ } else {
+ // Still parsing the URI. Append the next character to the
+ // method name.
+ context_->uri_.push_back(c);
+ transition(HTTP_URI_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state) {
+ stateWithReadHandler("versionHTTPHandler",
+ [this, expected_letter, next_state](const char c) {
+ // We're handling one of the letters: 'H', 'T' or 'P'.
+ if (c == expected_letter) {
+ // The HTTP version is specified as "HTTP/X.Y". If the current
+ // character is a slash we're starting to parse major HTTP version
+ // number. Let's reset the version numbers.
+ if (c == '/') {
+ context_->http_version_major_ = 0;
+ context_->http_version_minor_ = 0;
+ }
+ // In all cases, let's transition to next specified state.
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ // Unexpected character found. Parsing fails.
+ parseFailure("unexpected character " + std::string(1, c) +
+ " in HTTP version string");
+ }
+ });
+}
+
+void
+HttpRequestParser::versionNumberStartHandler(const unsigned int next_state,
+ unsigned int* storage) {
+ stateWithReadHandler("versionNumberStartHandler",
+ [this, next_state, storage](const char c) mutable {
+ // HTTP version number must be a digit.
+ if (isdigit(c)) {
+ // Update the version number using new digit being parsed.
+ *storage = *storage * 10 + c - '0';
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("expected digit in HTTP version, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::versionNumberHandler(const char following_character,
+ const unsigned int next_state,
+ unsigned int* const storage) {
+ stateWithReadHandler("versionNumberHandler",
+ [this, following_character, next_state, storage](const char c)
+ mutable {
+ // We're getting to the end of the version number, let's transition
+ // to next state.
+ if (c == following_character) {
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else if (isdigit(c)) {
+ // Current character is a digit, so update the version number.
+ *storage = *storage * 10 + c - '0';
+
+ } else {
+ parseFailure("expected digit in HTTP version, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::expectingNewLineHandler(const unsigned int next_state) {
+ stateWithReadHandler("expectingNewLineHandler", [this, next_state](const char c) {
+ // Only a new line character is allowed in this state.
+ if (c == '\n') {
+ // If next state is HTTP_PARSE_OK_ST it means that we're
+ // parsing 3rd new line in the HTTP request message. This
+ // terminates the HTTP request (if there is no body) or marks the
+ // beginning of the body.
+ if (next_state == HTTP_PARSE_OK_ST) {
+ // Whether there is a body in this message or not, we should
+ // parse the HTTP headers to validate it and to check if there
+ // is "Content-Length" specified. The "Content-Length" is
+ // required for parsing body.
+ request_.create();
+ try {
+ // This will throw exception if there is no Content-Length.
+ uint64_t content_length =
+ request_.getHeaderValueAsUint64("Content-Length");
+ if (content_length > 0) {
+ // There is body in this request, so let's parse it.
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+ } else {
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ } catch (const std::exception& ex) {
+ // There is no body in this message. If the body is required
+ // parsing fails.
+ if (request_.requiresBody()) {
+ parseFailure("HTTP message lacks a body");
+
+ } else {
+ // Body not required so simply terminate parsing.
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ }
+
+ } else {
+ // This is 1st or 2nd new line, so let's transition to the
+ // next state required by this handler.
+ transition(next_state, DATA_READ_OK_EVT);
+ }
+ } else {
+ parseFailure("expecting new line after CR, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpRequestParser::headerLineStartHandler() {
+ stateWithReadHandler("headerLineStartHandler", [this](const char c) {
+ // If we're parsing HTTP headers and we found CR it marks the
+ // end of headers section.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE3_ST, DATA_READ_OK_EVT);
+
+ } else if (!context_->headers_.empty() && ((c == ' ') || (c == '\t'))) {
+ // New line in headers section followed by space or tab is an LWS,
+ // a line break within header value.
+ transition(HEADER_LWS_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in header name");
+
+ } else {
+ // Update header name with the parse letter.
+ context_->headers_.push_back(HttpHeaderContext());
+ context_->headers_.back().name_.push_back(c);
+ transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerLwsHandler() {
+ stateWithReadHandler("headerLwsHandler", [this](const char c) {
+ if (c == '\r') {
+ // Found CR during parsing a header value. Next value
+ // should be new line.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if ((c == ' ') || (c == '\t')) {
+ // Space and tab is used to mark LWS. Simply swallow
+ // this character.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header " +
+ context_->headers_.back().name_);
+
+ } else {
+ // We're parsing header value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerNameHandler() {
+ stateWithReadHandler("headerNameHandler", [this](const char c) {
+ // Colon follows header name and it has its own state.
+ if (c == ':') {
+ transition(SPACE_BEFORE_HEADER_VALUE_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " found in the HTTP header name");
+
+ } else {
+ // Parsing a header name, so update it.
+ context_->headers_.back().name_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::spaceBeforeHeaderValueHandler() {
+ stateWithReadHandler("spaceBeforeHeaderValueHandler", [this](const char c) {
+ if (c == ' ') {
+ // Remove leading whitespace from the header value.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (c == '\r') {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::headerValueHandler() {
+ stateWithReadHandler("headerValueHandler", [this](const char c) {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpRequestParser::bodyHandler() {
+ stateWithMultiReadHandler("bodyHandler", [this](const std::string& body) {
+ // We don't validate the body at this stage. Simply record the
+ // number of characters specified within "Content-Length".
+ context_->body_ += body;
+ size_t content_length = request_.getHeaderValueAsUint64("Content-Length");
+ if (context_->body_.length() < content_length) {
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+
+ } else {
+ // If there was some extraneous data, ignore it.
+ if (context_->body_.length() > content_length) {
+ context_->body_.resize(content_length);
+ }
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ });
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/request_parser.h b/src/lib/http/request_parser.h
new file mode 100644
index 0000000..4f415d0
--- /dev/null
+++ b/src/lib/http/request_parser.h
@@ -0,0 +1,246 @@
+// Copyright (C) 2016-2018,2021 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 HTTP_REQUEST_PARSER_H
+#define HTTP_REQUEST_PARSER_H
+
+#include <http/http_message_parser_base.h>
+#include <http/request.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class HttpRequestParser;
+
+/// @brief Pointer to the @ref HttpRequestParser.
+typedef boost::shared_ptr<HttpRequestParser> HttpRequestParserPtr;
+
+/// @brief A generic parser for HTTP requests.
+///
+/// This class implements a parser for HTTP requests. The parser derives from
+/// @ref HttpMessageParserBase class and implements its own state machine on
+/// top of it. The states of the parser reflect various parts of the HTTP
+/// message being parsed, e.g. parsing HTTP method, parsing URI, parsing
+/// message body etc. The descriptions of all parser states are provided
+/// below together with the constants defining these states.
+///
+/// The request parser validates the syntax of the received message as it
+/// progresses with parsing the data. Though, it doesn't interpret the received
+/// data until the whole message is parsed. In most cases we want to apply some
+/// restrictions on the message content, e.g. Kea Control API requires that
+/// commands are sent using HTTP POST, with a JSON command being carried in a
+/// message body. The parser doesn't verify if the message meets these
+/// restrictions until the whole message is parsed, i.e. stored in the
+/// @ref HttpRequestContext object. This object is associated with a
+/// @ref HttpRequest object (or its derivation). When the parsing is completed,
+/// the @ref HttpRequest::create method is called to retrieve the data from
+/// the @ref HttpRequestContext and interpret the data. In particular, the
+/// @ref HttpRequest or its derivation checks if the received message meets
+/// desired restrictions.
+///
+/// Kea Control API uses @ref PostHttpRequestJson class (which derives from the
+/// @ref HttpRequest) to interpret received request. This class requires
+/// that the HTTP request uses POST method and contains the following headers:
+/// - Content-Type: application/json,
+/// - Content-Length
+///
+/// If any of these restrictions is not met in the received message, an
+/// exception will be thrown, thereby @ref HttpRequestParser will fail parsing
+/// the message.
+class HttpRequestParser : public HttpMessageParserBase {
+public:
+
+ /// @name States supported by the HttpRequestParser.
+ ///
+ //@{
+
+ /// @brief State indicating a beginning of parsing.
+ static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1;
+
+ /// @brief Parsing HTTP method, e.g. GET, POST etc.
+ static const int HTTP_METHOD_ST = SM_DERIVED_STATE_MIN + 2;
+
+ /// @brief Parsing URI.
+ static const int HTTP_URI_ST = SM_DERIVED_STATE_MIN + 3;
+
+ /// @brief Parsing letter "H" of "HTTP".
+ static const int HTTP_VERSION_H_ST = SM_DERIVED_STATE_MIN + 4;
+
+ /// @brief Parsing first occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T1_ST = SM_DERIVED_STATE_MIN + 5;
+
+ /// @brief Parsing second occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T2_ST = SM_DERIVED_STATE_MIN + 6;
+
+ /// @brief Parsing letter "P" in "HTTP".
+ static const int HTTP_VERSION_P_ST = SM_DERIVED_STATE_MIN + 7;
+
+ /// @brief Parsing slash character in "HTTP/Y.X"
+ static const int HTTP_VERSION_SLASH_ST = SM_DERIVED_STATE_MIN + 8;
+
+ /// @brief Starting to parse major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_START_ST = SM_DERIVED_STATE_MIN + 9;
+
+ /// @brief Parsing major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_ST = SM_DERIVED_STATE_MIN + 10;
+
+ /// @brief Starting to parse minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_START_ST = SM_DERIVED_STATE_MIN + 11;
+
+ /// @brief Parsing minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_ST = SM_DERIVED_STATE_MIN + 12;
+
+ /// @brief Parsing first new line (after HTTP version number).
+ static const int EXPECTING_NEW_LINE1_ST = SM_DERIVED_STATE_MIN + 13;
+
+ /// @brief Starting to parse a header line.
+ static const int HEADER_LINE_START_ST = SM_DERIVED_STATE_MIN + 14;
+
+ /// @brief Parsing LWS (Linear White Space), i.e. new line with a space
+ /// or tab character while parsing a HTTP header.
+ static const int HEADER_LWS_ST = SM_DERIVED_STATE_MIN + 15;
+
+ /// @brief Parsing header name.
+ static const int HEADER_NAME_ST = SM_DERIVED_STATE_MIN + 16;
+
+ /// @brief Parsing space before header value.
+ static const int SPACE_BEFORE_HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 17;
+
+ /// @brief Parsing header value.
+ static const int HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 18;
+
+ /// @brief Expecting new line after parsing header value.
+ static const int EXPECTING_NEW_LINE2_ST = SM_DERIVED_STATE_MIN + 19;
+
+ /// @brief Expecting second new line marking end of HTTP headers.
+ static const int EXPECTING_NEW_LINE3_ST = SM_DERIVED_STATE_MIN + 20;
+
+ /// @brief Parsing body of a HTTP message.
+ static const int HTTP_BODY_ST = SM_DERIVED_STATE_MIN + 21;
+
+ //@}
+
+
+ /// @brief Constructor.
+ ///
+ /// Creates new instance of the parser.
+ ///
+ /// @param request Reference to the @ref HttpRequest object or its
+ /// derivation that should be used to validate the parsed request and
+ /// to be used as a container for the parsed request.
+ explicit HttpRequestParser(HttpRequest& request);
+
+ /// @brief Initialize the state model for parsing.
+ ///
+ /// This method must be called before parsing the request, i.e. before
+ /// calling @ref HttpRequestParser::poll. It initializes dictionaries of
+ /// states and events, and sets the initial model state to RECEIVE_START_ST.
+ void initModel();
+
+private:
+
+ /// @brief Defines states of the parser.
+ virtual void defineStates();
+
+ /// @name State handlers.
+ ///
+ //@{
+
+ /// @brief Handler for RECEIVE_START_ST.
+ void receiveStartHandler();
+
+ /// @brief Handler for HTTP_METHOD_ST.
+ void httpMethodHandler();
+
+ /// @brief Handler for HTTP_URI_ST.
+ void uriHandler();
+
+ /// @brief Handler for states parsing "HTTP" string within the first line
+ /// of the HTTP request.
+ ///
+ /// @param expected_letter One of the 'H', 'T', 'P'.
+ /// @param next_state A state to which the parser should transition after
+ /// parsing the character.
+ void versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state);
+
+ /// @brief Handler for HTTP_VERSION_MAJOR_START_ST and
+ /// HTTP_VERSION_MINOR_START_ST.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param next_state State to which the parser should transition.
+ /// @param [out] storage Reference to a number holding current product of
+ /// parsing major or minor version number.
+ void versionNumberStartHandler(const unsigned int next_state,
+ unsigned int* storage);
+
+ /// @brief Handler for HTTP_VERSION_MAJOR_ST and HTTP_VERSION_MINOR_ST.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param following_character Character following the version number, i.e.
+ /// '.' for major version, \r for minor version.
+ /// @param next_state State to which the parser should transition.
+ /// @param [out] storage Pointer to a number holding current product of
+ /// parsing major or minor version number.
+ void versionNumberHandler(const char following_character,
+ const unsigned int next_state,
+ unsigned int* const storage);
+
+ /// @brief Handler for states related to new lines.
+ ///
+ /// If the next_state is HTTP_PARSE_OK_ST it indicates that the parsed
+ /// value is a 3rd new line within request HTTP message. In this case the
+ /// handler calls @ref HttpRequest::create to validate the received message
+ /// (excluding body). The handler then reads the "Content-Length" header to
+ /// check if the request contains a body. If the "Content-Length" is greater
+ /// than zero, the parser transitions to HTTP_BODY_ST. If the
+ /// "Content-Length" doesn't exist the parser transitions to
+ /// HTTP_PARSE_OK_ST.
+ ///
+ /// @param next_state A state to which parser should transition.
+ void expectingNewLineHandler(const unsigned int next_state);
+
+ /// @brief Handler for HEADER_LINE_START_ST.
+ void headerLineStartHandler();
+
+ /// @brief Handler for HEADER_LWS_ST.
+ void headerLwsHandler();
+
+ /// @brief Handler for HEADER_NAME_ST.
+ void headerNameHandler();
+
+ /// @brief Handler for SPACE_BEFORE_HEADER_VALUE_ST.
+ void spaceBeforeHeaderValueHandler();
+
+ /// @brief Handler for HEADER_VALUE_ST.
+ void headerValueHandler();
+
+ /// @brief Handler for HTTP_BODY_ST.
+ void bodyHandler();
+
+ //@}
+
+ /// @brief Reference to the request object specified in the constructor.
+ HttpRequest& request_;
+
+ /// @brief Pointer to the internal context of the @ref HttpRequest object.
+ HttpRequestContextPtr context_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif // HTTP_REQUEST_PARSER_H
+
diff --git a/src/lib/http/response.cc b/src/lib/http/response.cc
new file mode 100644
index 0000000..cdc419a
--- /dev/null
+++ b/src/lib/http/response.cc
@@ -0,0 +1,233 @@
+// Copyright (C) 2016-2018 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 <http/date_time.h>
+#include <http/response.h>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <boost/date_time/time_facet.hpp>
+#include <sstream>
+
+using namespace boost::local_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief A map of status codes to status names.
+const std::map<HttpStatusCode, std::string> status_code_to_description = {
+ { HttpStatusCode::OK, "OK" },
+ { HttpStatusCode::CREATED, "Created" },
+ { HttpStatusCode::ACCEPTED, "Accepted" },
+ { HttpStatusCode::NO_CONTENT, "No Content" },
+ { HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices" },
+ { HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently" },
+ { HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily" },
+ { HttpStatusCode::NOT_MODIFIED, "Not Modified" },
+ { HttpStatusCode::BAD_REQUEST, "Bad Request" },
+ { HttpStatusCode::UNAUTHORIZED, "Unauthorized" },
+ { HttpStatusCode::FORBIDDEN, "Forbidden" },
+ { HttpStatusCode::NOT_FOUND, "Not Found" },
+ { HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout" },
+ { HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error" },
+ { HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented" },
+ { HttpStatusCode::BAD_GATEWAY, "Bad Gateway" },
+ { HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable" }
+};
+
+/// @brief New line (CRLF).
+const std::string crlf = "\r\n";
+
+}
+
+namespace isc {
+namespace http {
+
+HttpResponse::HttpResponse()
+ : HttpMessage(INBOUND), context_(new HttpResponseContext()) {
+}
+
+HttpResponse::HttpResponse(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body)
+ : HttpMessage(OUTBOUND), context_(new HttpResponseContext()) {
+ context_->http_version_major_ = version.major_;
+ context_->http_version_minor_ = version.minor_;
+ context_->status_code_ = static_cast<unsigned int>(status_code);
+
+ if (generic_body.set_) {
+ // This currently does nothing, but it is useful to have it here as
+ // an example how to implement it in the derived classes.
+ setGenericBody(status_code);
+ }
+}
+
+void
+HttpResponse::create() {
+ try {
+ http_version_.major_ = context_->http_version_major_;
+ http_version_.minor_ = context_->http_version_minor_;
+
+ // Check if the HTTP version is allowed for this request.
+ if (!inRequiredSet(http_version_, required_versions_)) {
+ isc_throw(BadValue, "use of HTTP version "
+ << http_version_.major_ << "."
+ << http_version_.minor_
+ << " not allowed");
+ }
+
+ // Copy headers from the context.
+ for (auto header = context_->headers_.begin();
+ header != context_->headers_.end();
+ ++header) {
+ HttpHeaderPtr hdr(new HttpHeader(header->name_, header->value_));
+ headers_[hdr->getLowerCaseName()] = hdr;
+ }
+
+ if (getDirection() == HttpMessage::OUTBOUND) {
+ HttpHeaderPtr length_header(new HttpHeader("Content-Length", boost::lexical_cast<std::string>
+ (context_->body_.length())));
+ headers_["content-length"] = length_header;
+
+ HttpHeaderPtr date_header(new HttpHeader("Date", getDateHeaderValue()));;
+ headers_["date"] = date_header;
+ }
+
+ // Iterate over required headers and check that they exist
+ // in the HTTP response.
+ for (auto req_header = required_headers_.begin();
+ req_header != required_headers_.end();
+ ++req_header) {
+ auto header = headers_.find(req_header->first);
+ if (header == headers_.end()) {
+ isc_throw(BadValue, "required header " << req_header->first
+ << " not found in the HTTP response");
+ } else if (!req_header->second->getValue().empty() &&
+ !header->second->isValueEqual(req_header->second->getValue())) {
+ // If specific value is required for the header, check
+ // that the value in the HTTP response matches it.
+ isc_throw(BadValue, "required header's " << header->first
+ << " value is " << req_header->second->getValue()
+ << ", but " << header->second->getValue() << " was found");
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ // Reset the state of the object if we failed at any point.
+ reset();
+ isc_throw(HttpResponseError, ex.what());
+ }
+
+ // All ok.
+ created_ = true;
+}
+
+void
+HttpResponse::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ finalized_ = true;
+}
+
+void
+HttpResponse::reset() {
+ created_ = false;
+ finalized_ = false;
+ headers_.clear();
+}
+
+HttpStatusCode
+HttpResponse::getStatusCode() const {
+ checkCreated();
+ return (static_cast<HttpStatusCode>(context_->status_code_));
+}
+
+std::string
+HttpResponse::getStatusPhrase() const {
+ checkCreated();
+ return (context_->phrase_);
+}
+
+std::string
+HttpResponse::getBody() const {
+ checkFinalized();
+ return (context_->body_);
+}
+
+bool
+HttpResponse::isClientError(const HttpStatusCode& status_code) {
+ // Client errors have status codes of 4XX.
+ uint16_t c = statusCodeToNumber(status_code);
+ return ((c >= 400) && (c < 500));
+}
+
+bool
+HttpResponse::isServerError(const HttpStatusCode& status_code) {
+ // Server errors have status codes of 5XX.
+ uint16_t c = statusCodeToNumber(status_code);
+ return ((c >= 500) && (c < 600));
+}
+
+std::string
+HttpResponse::statusCodeToString(const HttpStatusCode& status_code) {
+ auto status_code_it = status_code_to_description.find(status_code);
+ if (status_code_it == status_code_to_description.end()) {
+ isc_throw(HttpResponseError, "internal server error: no HTTP status"
+ " description for the given status code "
+ << static_cast<uint16_t>(status_code));
+ }
+ return (status_code_it->second);
+}
+
+uint16_t
+HttpResponse::statusCodeToNumber(const HttpStatusCode& status_code) {
+ return (static_cast<uint16_t>(status_code));
+}
+
+std::string
+HttpResponse::getDateHeaderValue() const {
+ // This returns current time in the recommended format.
+ HttpDateTime date_time;
+ return (date_time.rfc1123Format());
+}
+
+std::string
+HttpResponse::toBriefString() const {
+ checkFinalized();
+
+ std::ostringstream s;
+ // HTTP version number and status code.
+ s << "HTTP/" << http_version_.major_ << "." << http_version_.minor_;
+ s << " " << context_->status_code_;
+ s << " " << statusCodeToString(static_cast<HttpStatusCode>(context_->status_code_));
+ return (s.str());
+}
+
+std::string
+HttpResponse::toString() const {
+
+ std::ostringstream s;
+ // HTTP version number and status code.
+ s << toBriefString() << crlf;
+
+ for (auto header_it = headers_.cbegin(); header_it != headers_.cend();
+ ++header_it) {
+ s << header_it->second->getName() << ": " << header_it->second->getValue()
+ << crlf;
+ }
+
+ s << crlf;
+
+ // Include message body.
+ s << getBody();
+
+ return (s.str());
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response.h b/src/lib/http/response.h
new file mode 100644
index 0000000..734d92f
--- /dev/null
+++ b/src/lib/http/response.h
@@ -0,0 +1,247 @@
+// Copyright (C) 2016-2022 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 HTTP_RESPONSE_H
+#define HTTP_RESPONSE_H
+
+#include <cc/data.h>
+#include <http/header_context.h>
+#include <http/http_message.h>
+#include <http/response_context.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief Generic exception thrown by @ref HttpResponse class.
+class HttpResponseError : public HttpMessageError {
+public:
+ HttpResponseError(const char* file, size_t line, const char* what) :
+ HttpMessageError(file, line, what) { };
+};
+
+/// @brief HTTP status codes (cf RFC 2068)
+enum class HttpStatusCode : std::uint16_t {
+ OK = 200,
+ CREATED = 201,
+ ACCEPTED = 202,
+ NO_CONTENT = 204,
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY = 301,
+ MOVED_TEMPORARILY = 302,
+ NOT_MODIFIED = 304,
+ BAD_REQUEST = 400,
+ UNAUTHORIZED = 401,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ REQUEST_TIMEOUT = 408,
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED = 501,
+ BAD_GATEWAY = 502,
+ SERVICE_UNAVAILABLE = 503
+};
+
+/// @brief Encapsulates the boolean value indicating if the @ref HttpResponse
+/// constructor should call its @c setGenericBody method during construction.
+struct CallSetGenericBody {
+
+ /// @brief Constructor.
+ ///
+ /// @param set A boolean value indicating if the method should be called
+ /// or not.
+ explicit CallSetGenericBody(const bool set)
+ : set_(set) {
+ }
+
+ /// @brief Returns encapsulated true.
+ static const CallSetGenericBody& yes() {
+ static CallSetGenericBody yes(true);
+ return (yes);
+ }
+
+ /// @brief Returns encapsulated false.
+ static const CallSetGenericBody& no() {
+ static CallSetGenericBody no(false);
+ return (no);
+ }
+
+ /// @brief A storage for the boolean flag.
+ bool set_;
+};
+
+class HttpResponse;
+
+/// @brief Pointer to the @ref HttpResponse object.
+typedef boost::shared_ptr<HttpResponse> HttpResponsePtr;
+
+/// @brief Pointer to the const @ref HttpResponse object.
+typedef boost::shared_ptr<const HttpResponse> ConstHttpResponsePtr;
+
+/// @brief Represents HTTP response message.
+///
+/// This derivation of the @c HttpMessage class is specialized to represent
+/// HTTP responses. This class provides two constructors for creating an inbound
+/// and outbound response instance respectively. This class is associated with
+/// an instance of the @c HttpResponseContext, which is used to provide response
+/// specific values, such as HTTP status and headers.
+///
+/// The derivations of this class provide specializations and specify the HTTP
+/// versions and headers supported/required in the specific use cases. For example,
+/// the @c HttpResponseJson class derives from the @c HttpResponse and it requires
+/// that response includes a body in the JSON format.
+class HttpResponse : public HttpMessage {
+public:
+
+ /// @brief Constructor for the inbound HTTP response.
+ explicit HttpResponse();
+
+
+ /// @brief Constructor for outbound HTTP response.
+ ///
+ /// Creates basic instance of the object. It sets the HTTP version and the
+ /// status code to be included in the response.
+ ///
+ /// @param version HTTP version.
+ /// @param status_code HTTP status code.
+ /// @param generic_body Indicates if the constructor should call
+ /// @c setGenericBody to create a generic content for the given
+ /// status code. This should be set to "no" when the constructor is
+ /// called by the derived class which provides its own implementation
+ /// of the @c setGenericBody method.
+ explicit HttpResponse(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body =
+ CallSetGenericBody::yes());
+
+ /// @brief Returns pointer to the @ref HttpResponseContext.
+ ///
+ /// The context holds intermediate data for creating a response. The response
+ /// parser stores parsed raw data in the context. When parsing is finished,
+ /// the data are validated and committed into the @c HttpResponse.
+ ///
+ /// @return Pointer to the underlying @ref HttpResponseContext.
+ const HttpResponseContextPtr& context() const {
+ return (context_);
+ }
+
+ /// @brief Commits information held in the context into the response.
+ ///
+ /// This function reads HTTP version, status code and headers from the
+ /// context and validates their values. For the outbound messages, it
+ /// automatically appends Content-Length and Date headers to the response.
+ /// The Content-Length is set to the body size. The Date is set to the
+ /// current date and time.
+ virtual void create();
+
+ /// @brief Completes creation of the HTTP response.
+ ///
+ /// This method marks the response as finalized. The outbound response may now
+ /// be sent over the TCP socket. The information from the inbound message may
+ /// be read, including the response body.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Returns HTTP status code.
+ HttpStatusCode getStatusCode() const;
+
+ /// @brief Returns HTTP status phrase.
+ std::string getStatusPhrase() const;
+
+ /// @brief Returns HTTP response body as string.
+ virtual std::string getBody() const;
+
+ /// @brief Retrieves a single JSON element.
+ ///
+ /// The element must be at top level of the JSON structure.
+ ///
+ /// @param element_name Element name.
+ ///
+ /// @return Pointer to the specified element or NULL if such element
+ /// doesn't exist.
+ /// @throw HttpResponseJsonError if an error occurred.
+ data::ConstElementPtr getJsonElement(const std::string& element_name) const;
+
+ /// @brief Checks if the status code indicates client error.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return true if the status code indicates client error.
+ static bool isClientError(const HttpStatusCode& status_code);
+
+ /// @brief Checks if the status code indicates server error.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return true if the status code indicates server error.
+ static bool isServerError(const HttpStatusCode& status_code);
+
+ /// @brief Convenience method converting status code to numeric value.
+ ///
+ /// @param status_code Status code represented as enum.
+ /// @return Numeric representation of the status code.
+ static uint16_t statusCodeToNumber(const HttpStatusCode& status_code);
+
+ /// @brief Converts status code to string.
+ ///
+ /// @param status_code HTTP status code.
+ /// @return Textual representation of the status code.
+ static std::string statusCodeToString(const HttpStatusCode& status_code);
+
+ /// @brief Returns HTTP version and HTTP status as a string.
+ std::string toBriefString() const;
+
+ /// @brief Returns HTTP response as string.
+ ///
+ /// This method is called to generate the outbound HTTP response. Make
+ /// sure to call @c finalize prior to calling this method.
+ virtual std::string toString() const;
+
+ /// @brief Returns current time formatted as required by RFC 1123.
+ ///
+ /// This method is virtual so as it can be overridden in unit tests
+ /// to return a "predictable" value of time, e.g. constant value.
+ ///
+ /// @return Current time formatted as required by RFC 1123.
+ virtual std::string getDateHeaderValue() const;
+
+private:
+
+ /// @brief Sets generic body for the given status code.
+ ///
+ /// Most of the classes derived from @ref HttpResponse will expect
+ /// a certain content type. Depending on the content type used they
+ /// will use different body formats for error messages. For example,
+ /// a response using text/html will use HTML within the response
+ /// body. The application/json will use JSON body etc. There is a
+ /// need to implement class specific way of generating the body
+ /// for error messages. Thus, each derivation of this class is
+ /// required to implement class specific @ref setGenericBody function
+ /// which should be called in the class constructor.
+ ///
+ /// This is also the case for this class, though the implementation
+ /// of @c setGenericBody is currently no-op.
+ ///
+ /// Note that this class can't be declared virtual because it is
+ /// meant to be called from the class constructor.
+ ///
+ /// @param status_code Status code for which the body should be
+ /// generated.
+ void setGenericBody(const HttpStatusCode& /*status_code*/) { };
+
+protected:
+
+ /// @brief Pointer to the @ref HttpResponseContext holding parsed
+ /// data.
+ HttpResponseContextPtr context_;
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/response_context.h b/src/lib/http/response_context.h
new file mode 100644
index 0000000..a821477
--- /dev/null
+++ b/src/lib/http/response_context.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 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 HTTP_RESPONSE_CONTEXT_H
+#define HTTP_RESPONSE_CONTEXT_H
+
+#include <http/header_context.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace http {
+
+/// @brief HTTP response context.
+///
+/// This context is used by the @c HttpResponseParser to store parsed
+/// data. This data is later used to create an instance of the
+/// @c HttpResponse or its derivation.
+struct HttpResponseContext {
+ /// @brief HTTP major version number.
+ unsigned int http_version_major_;
+ /// @brief HTTP minor version number.
+ unsigned int http_version_minor_;
+ /// @brief HTTP status code.
+ unsigned int status_code_;
+ /// @brief HTTP status phrase.
+ std::string phrase_;
+ /// @brief Collection of HTTP headers.
+ std::vector<HttpHeaderContext> headers_;
+ /// @brief HTTP request body.
+ std::string body_;
+};
+
+/// @brief Pointer to the @ref HttpResponseContext.
+typedef boost::shared_ptr<HttpResponseContext> HttpResponseContextPtr;
+
+} // end of namespace http
+} // end of namespace isc
+
+#endif // endif HTTP_RESPONSE_CONTEXT_H
diff --git a/src/lib/http/response_creator.cc b/src/lib/http/response_creator.cc
new file mode 100644
index 0000000..a060f6a
--- /dev/null
+++ b/src/lib/http/response_creator.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2016-2020 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 <http/response_creator.h>
+
+namespace isc {
+namespace http {
+
+HttpResponsePtr
+HttpResponseCreator::createHttpResponse(HttpRequestPtr request) {
+ // This should never happen. This method must only be called with a
+ // non null request, so we consider it unlikely internal server error.
+ if (!request) {
+ isc_throw(HttpResponseError, "internal server error: HTTP request is null");
+ }
+
+ // If not finalized, the request parsing failed. Generate HTTP 400.
+ if (!request->isFinalized()) {
+ return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
+ }
+
+ // Message has been successfully parsed. Create implementation specific
+ // response to this request.
+ return (createDynamicHttpResponse(request));
+}
+
+}
+}
diff --git a/src/lib/http/response_creator.h b/src/lib/http/response_creator.h
new file mode 100644
index 0000000..ac212fe
--- /dev/null
+++ b/src/lib/http/response_creator.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2016-2020 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 HTTP_RESPONSE_CREATOR_H
+#define HTTP_RESPONSE_CREATOR_H
+
+#include <http/request.h>
+#include <http/response.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class HttpResponseCreator;
+
+/// @brief Pointer to the @ref HttpResponseCreator object.
+typedef boost::shared_ptr<HttpResponseCreator> HttpResponseCreatorPtr;
+
+/// @brief Specifies an interface for classes creating HTTP responses
+/// from HTTP requests.
+///
+/// HTTP is designed to carry various content types. Most commonly
+/// this is text/html. In Kea, the application/json content type is used
+/// to carry control commands in JSON format. The libkea-http library is
+/// meant to be generic and provide means for transferring different types
+/// of content, depending on the use case.
+///
+/// This abstract class specifies a common interface for generating HTTP
+/// responses from HTTP requests using specific content type and being
+/// used in some specific context. Kea modules providing HTTP services need to
+/// implement their specific derivations of the @ref HttpResponseCreator
+/// class. These derivations use classes derived from @ref HttpRequest as
+/// an input and classes derived from @ref HttpResponse as an output of
+/// @c createHttpResponse method.
+class HttpResponseCreator {
+public:
+
+ /// @brief Destructor.
+ ///
+ /// Classes with virtual functions need virtual destructors.
+ virtual ~HttpResponseCreator() { };
+
+ /// @brief Create HTTP response from HTTP request received.
+ ///
+ /// This class implements a generic logic for creating a HTTP response.
+ /// Derived classes do not override this method. They merely implement
+ /// the methods it calls.
+ ///
+ /// The request processing may generally fail at one of the two stages:
+ /// parsing or interpretation of the parsed request. During the former
+ /// stage the request's syntax is checked, i.e. HTTP version, URI,
+ /// headers etc. During the latter stage the HTTP server checks if the
+ /// request is valid within the specific context, e.g. valid HTTP version
+ /// used, expected content type etc.
+ ///
+ /// In the @ref HttpRequest terms, the request has gone through the
+ /// first stage if it is "finalized" (see @ref HttpRequest::finalize).
+ /// This method accepts instances of both finalized and not finalized
+ /// requests. If the request isn't finalized it indicates that
+ /// the request parsing has failed. In such case, this method calls
+ /// @c createStockBadRequest to generate a response with HTTP 400 status
+ /// code. If the request is finalized, this method calls
+ /// @c createDynamicHttpResponse to generate implementation specific
+ /// response to the received request.
+ ///
+ /// This method is marked virtual final to prevent derived classes from
+ /// overriding this method. Instead, the derived classes must implement
+ /// protected methods which this method calls.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to the object encapsulating generated HTTP response.
+ /// @throw HttpResponseError if request is a NULL pointer.
+ virtual HttpResponsePtr
+ createHttpResponse(HttpRequestPtr request) final;
+
+ /// @brief Create a new request.
+ ///
+ /// This method creates an instance of the @ref HttpRequest or derived
+ /// class. The type of the object is compatible with the instance of
+ /// the @ref HttpResponseCreator implementation which creates it, i.e.
+ /// can be used as an argument in the call to @ref createHttpResponse.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const = 0;
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @param status_code Status code of the response.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const = 0;
+
+protected:
+
+ /// @brief Creates implementation specific HTTP response.
+ ///
+ /// @param request Pointer to an object representing HTTP request.
+ /// @return Pointer to an object representing HTTP response.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) = 0;
+
+};
+
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/response_creator_factory.h b/src/lib/http/response_creator_factory.h
new file mode 100644
index 0000000..c2fc917
--- /dev/null
+++ b/src/lib/http/response_creator_factory.h
@@ -0,0 +1,59 @@
+// Copyright (C) 2017 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 HTTP_RESPONSE_CREATOR_FACTORY_H
+#define HTTP_RESPONSE_CREATOR_FACTORY_H
+
+#include <http/response_creator.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+/// @brief Specifies the interface for implementing custom factory classes
+/// used to create instances of @ref HttpResponseCreator.
+///
+/// The @ref HttpResponseCreator defines an interface for the classes used
+/// to generate HTTP responses. Such classes are defined outside of this
+/// library and they are specific to the needs of the particular module.
+/// In some cases it may be desired to create new instance of the
+/// @ref HttpResponseCreator implementation for every request processed.
+/// The @ref HttpResponseCreatorFactory is an interface to the "factory"
+/// class which generates canned @ref HttpResponseCreator instances. The
+/// pointer to the factory class is passed to the @ref HttpListener and
+/// the listener propagates it down to other classes. These classes call
+/// @ref HttpResponseCreatorFactory::create to retrieve an instance of the
+/// appropriate @ref HttpResponseCreator, which is in turn used to generate
+/// HTTP response.
+///
+/// Note that an implementation of the @ref HttpResponseCreatorFactory::create
+/// may always return the same instance of the @ref HttpResponseCreator
+/// if creating new instance for each request is not required or undesired.
+class HttpResponseCreatorFactory {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~HttpResponseCreatorFactory() { }
+
+ /// @brief Returns an instance of the @ref HttpResponseCreator.
+ ///
+ /// The implementation may create new instance every time this method
+ /// is called, or it may always return the same instance.
+ ///
+ /// @return Pointer to the instance of the @ref HttpResponseCreator to
+ /// be used to generate HTTP response.
+ virtual HttpResponseCreatorPtr create() const = 0;
+
+};
+
+/// @brief Pointer to the @ref HttpResponseCreatorFactory.
+typedef boost::shared_ptr<HttpResponseCreatorFactory>
+HttpResponseCreatorFactoryPtr;
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/response_json.cc b/src/lib/http/response_json.cc
new file mode 100644
index 0000000..7543d76
--- /dev/null
+++ b/src/lib/http/response_json.cc
@@ -0,0 +1,122 @@
+// Copyright (C) 2016-2018 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 <http/response_json.h>
+#include <map>
+
+using namespace isc::data;
+
+namespace isc {
+namespace http {
+
+HttpResponseJson::HttpResponseJson()
+ : HttpResponse() {
+ context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+}
+
+
+HttpResponseJson::HttpResponseJson(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body)
+ : HttpResponse(version, status_code, CallSetGenericBody::no()) {
+ context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+ // This class provides its own implementation of the setGenericBody.
+ // We call it here unless the derived class calls this constructor
+ // from its own constructor and indicates that we shouldn't set the
+ // generic content in the body.
+ if (generic_body.set_) {
+ setGenericBody(status_code);
+ }
+}
+
+void
+HttpResponseJson::setGenericBody(const HttpStatusCode& status_code) {
+ // Only generate the content for the client or server errors. For
+ // other status codes (status OK in particular) the content should
+ // be created using setBodyAsJson or setBody.
+ if (isClientError(status_code) || isServerError(status_code)) {
+ std::map<std::string, ConstElementPtr> map_elements;
+ map_elements["result"] =
+ ConstElementPtr(new IntElement(statusCodeToNumber(status_code)));
+ map_elements["text"] =
+ ConstElementPtr(new StringElement(statusCodeToString(status_code)));
+ auto body = Element::createMap();
+ body->setValue(map_elements);
+ setBodyAsJson(body);
+ }
+}
+
+void
+HttpResponseJson::finalize() {
+ if (!created_) {
+ create();
+ }
+
+ // Parse JSON body and store.
+ parseBodyAsJson();
+ finalized_ = true;
+}
+
+void
+HttpResponseJson::reset() {
+ HttpResponse::reset();
+ json_.reset();
+}
+
+ConstElementPtr
+HttpResponseJson::getBodyAsJson() const {
+ checkFinalized();
+ return (json_);
+}
+
+void
+HttpResponseJson::setBodyAsJson(const ConstElementPtr& json_body) {
+ if (json_body) {
+ context()->body_ = json_body->str();
+
+ } else {
+ context()->body_.clear();
+ }
+
+ json_ = json_body;
+}
+
+ConstElementPtr
+HttpResponseJson::getJsonElement(const std::string& element_name) const {
+ try {
+ ConstElementPtr body = getBodyAsJson();
+ if (body) {
+ const std::map<std::string, ConstElementPtr>& map_value = body->mapValue();
+ auto map_element = map_value.find(element_name);
+ if (map_element != map_value.end()) {
+ return (map_element->second);
+ }
+ }
+
+ } catch (const std::exception& ex) {
+ isc_throw(HttpResponseJsonError, "unable to get JSON element "
+ << element_name << ": " << ex.what());
+ }
+ return (ConstElementPtr());
+}
+
+void
+HttpResponseJson::parseBodyAsJson() {
+ try {
+ // Only parse the body if it hasn't been parsed yet.
+ if (!json_ && !context_->body_.empty()) {
+ json_ = Element::fromJSON(context_->body_);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(HttpResponseJsonError, "unable to parse the body of the HTTP"
+ " response: " << ex.what());
+ }
+}
+
+} // namespace http
+} // namespace isc
diff --git a/src/lib/http/response_json.h b/src/lib/http/response_json.h
new file mode 100644
index 0000000..d8ed06e
--- /dev/null
+++ b/src/lib/http/response_json.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2016-2018 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 HTTP_RESPONSE_JSON_H
+#define HTTP_RESPONSE_JSON_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <http/response.h>
+
+namespace isc {
+namespace http {
+
+/// @brief Exception thrown when body of the HTTP message is not JSON.
+class HttpResponseJsonError : public HttpResponseError {
+public:
+ HttpResponseJsonError(const char* file, size_t line, const char* what) :
+ HttpResponseError(file, line, what) { };
+};
+
+class HttpResponseJson;
+
+/// @brief Pointer to the @ref HttpResponseJson object.
+typedef boost::shared_ptr<HttpResponseJson> HttpResponseJsonPtr;
+
+/// @brief Represents HTTP response with JSON content.
+///
+/// This is a specialization of the @ref HttpResponse class which
+/// includes "Content-Type" equal to "application/json". It also provides
+/// methods to create JSON content within HTTP responses.
+class HttpResponseJson : public HttpResponse {
+public:
+
+ /// @brief Constructor for the inbound HTTP response.
+ explicit HttpResponseJson();
+
+ /// @brief Constructor for the outbound HTTP response.
+ ///
+ /// @param version HTTP version.
+ /// @param status_code HTTP status code.
+ /// @param generic_body Indicates if the constructor should call
+ /// @c setGenericBody to create a generic content for the given
+ /// status code. This should be set to "no" when the constructor is
+ /// called by the derived class which provides its own implementation
+ /// of the @c setGenericBody method.
+ explicit HttpResponseJson(const HttpVersion& version,
+ const HttpStatusCode& status_code,
+ const CallSetGenericBody& generic_body =
+ CallSetGenericBody::yes());
+
+ /// @brief Completes creation of the HTTP response.
+ ///
+ /// This method marks the response as finalized. The JSON structure is
+ /// created and can be used to retrieve the parsed data. If this is the
+ /// outbound message, it can be transmitted over the wire as the body
+ /// for the message is now committed.
+ virtual void finalize();
+
+ /// @brief Reset the state of the object.
+ virtual void reset();
+
+ /// @brief Retrieves JSON body.
+ ///
+ /// @return Pointer to the root element of the JSON structure.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getBodyAsJson() const;
+
+ /// @brief Generates JSON content from the data structures represented
+ /// as @ref data::ConstElementPtr.
+ ///
+ /// @param json_body A data structure representing JSON content.
+ void setBodyAsJson(const data::ConstElementPtr& json_body);
+
+ /// @brief Retrieves a single JSON element.
+ ///
+ /// The element must be at top level of the JSON structure.
+ ///
+ /// @param element_name Element name.
+ ///
+ /// @return Pointer to the specified element or NULL if such element
+ /// doesn't exist.
+ /// @throw HttpRequestJsonError if an error occurred.
+ data::ConstElementPtr getJsonElement(const std::string& element_name) const;
+
+private:
+
+ /// @brief Sets generic body for the given status code.
+ ///
+ /// This method generates JSON content for the HTTP client and server
+ /// errors. The generated JSON structure is a map containing "result"
+ /// value holding HTTP status code (e.g. 400) and the "text" string
+ /// holding a status code description.
+ ///
+ /// @param status_code Status code for which the body should be
+ /// generated.
+ void setGenericBody(const HttpStatusCode& status_code);
+
+protected:
+
+ /// @brief Interprets body as JSON, which can be later retrieved using
+ /// data element objects.
+ void parseBodyAsJson();
+
+ /// @brief Pointer to the parsed JSON body.
+ data::ConstElementPtr json_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/response_parser.cc b/src/lib/http/response_parser.cc
new file mode 100644
index 0000000..b2ab797
--- /dev/null
+++ b/src/lib/http/response_parser.cc
@@ -0,0 +1,461 @@
+// Copyright (C) 2017-2020 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 <http/response_parser.h>
+#include <functional>
+
+using namespace isc::util;
+
+namespace isc {
+namespace http {
+
+const int HttpResponseParser::RECEIVE_START_ST;
+const int HttpResponseParser::HTTP_VERSION_H_ST;
+const int HttpResponseParser::HTTP_VERSION_T1_ST;
+const int HttpResponseParser::HTTP_VERSION_T2_ST;
+const int HttpResponseParser::HTTP_VERSION_P_ST;
+const int HttpResponseParser::HTTP_VERSION_SLASH_ST;
+const int HttpResponseParser::HTTP_VERSION_MAJOR_START_ST;
+const int HttpResponseParser::HTTP_VERSION_MAJOR_ST;
+const int HttpResponseParser::HTTP_VERSION_MINOR_START_ST;
+const int HttpResponseParser::HTTP_VERSION_MINOR_ST;
+const int HttpResponseParser::HTTP_STATUS_CODE_START_ST;
+const int HttpResponseParser::HTTP_STATUS_CODE_ST;
+const int HttpResponseParser::HTTP_PHRASE_START_ST;
+const int HttpResponseParser::HTTP_PHRASE_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE1_ST;
+const int HttpResponseParser::HEADER_LINE_START_ST;
+const int HttpResponseParser::HEADER_LWS_ST;
+const int HttpResponseParser::HEADER_NAME_ST;
+const int HttpResponseParser::SPACE_BEFORE_HEADER_VALUE_ST;
+const int HttpResponseParser::HEADER_VALUE_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE2_ST;
+const int HttpResponseParser::EXPECTING_NEW_LINE3_ST;
+const int HttpResponseParser::HTTP_BODY_ST;
+
+HttpResponseParser::HttpResponseParser(HttpResponse& response)
+ : HttpMessageParserBase(response), response_(response),
+ context_(response.context()) {
+}
+
+void
+HttpResponseParser::initModel() {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(RECEIVE_START_ST);
+
+ // Parsing starts from here.
+ postNextEvent(START_EVT);
+}
+
+void
+HttpResponseParser::defineStates() {
+ // Call parent class implementation first.
+ HttpMessageParserBase::defineStates();
+
+ // Define HTTP parser specific states.
+ defineState(RECEIVE_START_ST, "RECEIVE_START_ST",
+ std::bind(&HttpResponseParser::receiveStartHandler, this));
+
+ defineState(HTTP_VERSION_T1_ST, "HTTP_VERSION_T1_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_T2_ST));
+
+ defineState(HTTP_VERSION_T2_ST, "HTTP_VERSION_T2_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'T',
+ HTTP_VERSION_P_ST));
+
+ defineState(HTTP_VERSION_P_ST, "HTTP_VERSION_P_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, 'P',
+ HTTP_VERSION_SLASH_ST));
+
+ defineState(HTTP_VERSION_SLASH_ST, "HTTP_VERSION_SLASH_ST",
+ std::bind(&HttpResponseParser::versionHTTPHandler, this, '/',
+ HTTP_VERSION_MAJOR_ST));
+
+ defineState(HTTP_VERSION_MAJOR_START_ST, "HTTP_VERSION_MAJOR_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_VERSION_MAJOR_ST,
+ "HTTP version",
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MAJOR_ST, "HTTP_VERSION_MAJOR_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ '.', HTTP_VERSION_MINOR_START_ST,
+ "HTTP version",
+ &context_->http_version_major_));
+
+ defineState(HTTP_VERSION_MINOR_START_ST, "HTTP_VERSION_MINOR_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_VERSION_MINOR_ST,
+ "HTTP version",
+ &context_->http_version_minor_));
+
+ defineState(HTTP_VERSION_MINOR_ST, "HTTP_VERSION_MINOR_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ ' ', HTTP_STATUS_CODE_START_ST,
+ "HTTP version",
+ &context_->http_version_minor_));
+
+ defineState(HTTP_STATUS_CODE_START_ST, "HTTP_STATUS_CODE_START_ST",
+ std::bind(&HttpResponseParser::numberStartHandler, this,
+ HTTP_STATUS_CODE_ST,
+ "HTTP status code",
+ &context_->status_code_));
+
+ defineState(HTTP_STATUS_CODE_ST, "HTTP_STATUS_CODE_ST",
+ std::bind(&HttpResponseParser::numberHandler, this,
+ ' ', HTTP_PHRASE_START_ST,
+ "HTTP status code",
+ &context_->status_code_));
+
+ defineState(HTTP_PHRASE_START_ST, "HTTP_PHRASE_START_ST",
+ std::bind(&HttpResponseParser::phraseStartHandler, this));
+
+ defineState(HTTP_PHRASE_ST, "HTTP_PHRASE_ST",
+ std::bind(&HttpResponseParser::phraseHandler, this));
+
+ defineState(EXPECTING_NEW_LINE1_ST, "EXPECTING_NEW_LINE1_ST",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(HEADER_LINE_START_ST, "HEADER_LINE_START_ST",
+ std::bind(&HttpResponseParser::headerLineStartHandler, this));
+
+ defineState(HEADER_LWS_ST, "HEADER_LWS_ST",
+ std::bind(&HttpResponseParser::headerLwsHandler, this));
+
+ defineState(HEADER_NAME_ST, "HEADER_NAME_ST",
+ std::bind(&HttpResponseParser::headerNameHandler, this));
+
+ defineState(SPACE_BEFORE_HEADER_VALUE_ST, "SPACE_BEFORE_HEADER_VALUE_ST",
+ std::bind(&HttpResponseParser::spaceBeforeHeaderValueHandler, this));
+
+ defineState(HEADER_VALUE_ST, "HEADER_VALUE_ST",
+ std::bind(&HttpResponseParser::headerValueHandler, this));
+
+ defineState(EXPECTING_NEW_LINE2_ST, "EXPECTING_NEW_LINE2",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HEADER_LINE_START_ST));
+
+ defineState(EXPECTING_NEW_LINE3_ST, "EXPECTING_NEW_LINE3_ST",
+ std::bind(&HttpResponseParser::expectingNewLineHandler, this,
+ HTTP_PARSE_OK_ST));
+
+ defineState(HTTP_BODY_ST, "HTTP_BODY_ST",
+ std::bind(&HttpResponseParser::bodyHandler, this));
+}
+
+void
+HttpResponseParser::receiveStartHandler() {
+ std::string bytes;
+ getNextFromBuffer(bytes);
+ if (getNextEvent() != NEED_MORE_DATA_EVT) {
+ switch(getNextEvent()) {
+ case START_EVT:
+ if (bytes[0] == 'H') {
+ transition(HTTP_VERSION_T1_ST, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("unexpected first character " + std::string(1, bytes[0]) +
+ ": expected \'H\'");
+ }
+ break;
+
+ default:
+ invalidEventError("receiveStartHandler", getNextEvent());
+ }
+ }
+}
+
+void
+HttpResponseParser::versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state) {
+ stateWithReadHandler("versionHTTPHandler",
+ [this, expected_letter, next_state](const char c) {
+ // We're handling one of the letters: 'H', 'T' or 'P'.
+ if (c == expected_letter) {
+ // The HTTP version is specified as "HTTP/X.Y". If the current
+ // character is a slash we're starting to parse major HTTP version
+ // number. Let's reset the version numbers.
+ if (c == '/') {
+ context_->http_version_major_ = 0;
+ context_->http_version_minor_ = 0;
+ }
+ // In all cases, let's transition to next specified state.
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ // Unexpected character found. Parsing fails.
+ parseFailure("unexpected character " + std::string(1, c) +
+ " in HTTP version string");
+ }
+ });
+}
+
+void
+HttpResponseParser::numberStartHandler(const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* storage) {
+ stateWithReadHandler("numberStartHandler",
+ [this, next_state, number_name, storage](const char c) mutable {
+ // HTTP version number must be a digit.
+ if (isdigit(c)) {
+ // Update the version number using new digit being parsed.
+ *storage = *storage * 10 + c - '0';
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else {
+ parseFailure("expected digit in " + number_name + ", found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::numberHandler(const char following_character,
+ const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* const storage) {
+ stateWithReadHandler("numberHandler",
+ [this, following_character, number_name, next_state, storage](const char c)
+ mutable {
+ // We're getting to the end of the version number, let's transition
+ // to next state.
+ if (c == following_character) {
+ transition(next_state, DATA_READ_OK_EVT);
+
+ } else if (isdigit(c)) {
+ // Current character is a digit, so update the version number.
+ *storage = *storage * 10 + c - '0';
+
+ } else {
+ parseFailure("expected digit in " + number_name + ", found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::phraseStartHandler() {
+ stateWithReadHandler("phraseStartHandler", [this](const char c) {
+ if (!isChar(c) || isCtl(c)) {
+ parseFailure("invalid first character " + std::string(1, c) +
+ " in HTTP phrase");
+ } else {
+ context_->phrase_.push_back(c);
+ transition(HTTP_PHRASE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::phraseHandler() {
+ stateWithReadHandler("phraseHandler", [this](const char c) {
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE1_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in HTTP phrase");
+
+ } else {
+ context_->phrase_.push_back(c);
+ transition(HTTP_PHRASE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::expectingNewLineHandler(const unsigned int next_state) {
+ stateWithReadHandler("expectingNewLineHandler", [this, next_state](const char c) {
+ // Only a new line character is allowed in this state.
+ if (c == '\n') {
+ // If next state is HTTP_PARSE_OK_ST it means that we're
+ // parsing 3rd new line in the HTTP request message. This
+ // terminates the HTTP request (if there is no body) or marks the
+ // beginning of the body.
+ if (next_state == HTTP_PARSE_OK_ST) {
+ // Whether there is a body in this message or not, we should
+ // parse the HTTP headers to validate it and to check if there
+ // is "Content-Length" specified. The "Content-Length" is
+ // required for parsing body.
+ response_.create();
+ try {
+ // This will throw exception if there is no Content-Length.
+ uint64_t content_length =
+ response_.getHeaderValueAsUint64("Content-Length");
+ if (content_length > 0) {
+ // There is body in this request, so let's parse it.
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+ } else {
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ } catch (const std::exception& ex) {
+ // There is no body in this message. If the body is required
+ // parsing fails.
+ if (response_.requiresBody()) {
+ parseFailure("HTTP message lacks a body");
+
+ } else {
+ // Body not required so simply terminate parsing.
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ }
+
+ } else {
+ // This is 1st or 2nd new line, so let's transition to the
+ // next state required by this handler.
+ transition(next_state, DATA_READ_OK_EVT);
+ }
+ } else {
+ parseFailure("expecting new line after CR, found " +
+ std::string(1, c));
+ }
+ });
+}
+
+void
+HttpResponseParser::headerLineStartHandler() {
+ stateWithReadHandler("headerLineStartHandler", [this](const char c) {
+ // If we're parsing HTTP headers and we found CR it marks the
+ // end of headers section.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE3_ST, DATA_READ_OK_EVT);
+
+ } else if (!context_->headers_.empty() && ((c == ' ') || (c == '\t'))) {
+ // New line in headers section followed by space or tab is an LWS,
+ // a line break within header value.
+ transition(HEADER_LWS_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " in header name");
+
+ } else {
+ // Update header name with the parsed letter.
+ context_->headers_.push_back(HttpHeaderContext());
+ context_->headers_.back().name_.push_back(c);
+ transition(HEADER_NAME_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerLwsHandler() {
+ stateWithReadHandler("headerLwsHandler", [this](const char c) {
+ if (c == '\r') {
+ // Found CR during parsing a header value. Next value
+ // should be new line.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if ((c == ' ') || (c == '\t')) {
+ // Space and tab is used to mark LWS. Simply swallow
+ // this character.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header " +
+ context_->headers_.back().name_);
+
+ } else {
+ // We're parsing header value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerNameHandler() {
+ stateWithReadHandler("headerNameHandler", [this](const char c) {
+ // Colon follows header name and it has its own state.
+ if (c == ':') {
+ transition(SPACE_BEFORE_HEADER_VALUE_ST, DATA_READ_OK_EVT);
+
+ } else if (!isChar(c) || isCtl(c) || isSpecial(c)) {
+ parseFailure("invalid character " + std::string(1, c) +
+ " found in the HTTP header name");
+
+ } else {
+ // Parsing a header name, so update it.
+ context_->headers_.back().name_.push_back(c);
+ transition(getCurrState(), DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::spaceBeforeHeaderValueHandler() {
+ stateWithReadHandler("spaceBeforeHeaderValueHandler", [this](const char c) {
+ if (c == ' ') {
+ // Remove leading whitespace from the header value.
+ transition(getCurrState(), DATA_READ_OK_EVT);
+
+ } else if (c == '\r') {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::headerValueHandler() {
+ stateWithReadHandler("headerValueHandler", [this](const char c) {
+ // If CR found during parsing header value, it marks the end
+ // of this value.
+ if (c == '\r') {
+ transition(EXPECTING_NEW_LINE2_ST, DATA_READ_OK_EVT);
+
+ } else if (isCtl(c)) {
+ parseFailure("control character found in the HTTP header "
+ + context_->headers_.back().name_);
+
+ } else {
+ // Still parsing the value, so let's update it.
+ context_->headers_.back().value_.push_back(c);
+ transition(HEADER_VALUE_ST, DATA_READ_OK_EVT);
+ }
+ });
+}
+
+void
+HttpResponseParser::bodyHandler() {
+ stateWithMultiReadHandler("bodyHandler", [this](const std::string& body) {
+ // We don't validate the body at this stage. Simply record the
+ // number of characters specified within "Content-Length".
+ context_->body_ += body;
+ size_t content_length = response_.getHeaderValueAsUint64("Content-Length");
+ if (context_->body_.length() < content_length) {
+ transition(HTTP_BODY_ST, DATA_READ_OK_EVT);
+
+ } else {
+ // If there was some extraneous data, ignore it.
+ if (context_->body_.length() > content_length) {
+ context_->body_.resize(content_length);
+ }
+ transition(HTTP_PARSE_OK_ST, HTTP_PARSE_OK_EVT);
+ }
+ });
+}
+
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/response_parser.h b/src/lib/http/response_parser.h
new file mode 100644
index 0000000..fb83b13
--- /dev/null
+++ b/src/lib/http/response_parser.h
@@ -0,0 +1,243 @@
+// Copyright (C) 2017-2023 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 HTTP_RESPONSE_PARSER_H
+#define HTTP_RESPONSE_PARSER_H
+
+#include <http/http_message_parser_base.h>
+#include <http/response.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace http {
+
+class HttpResponseParser;
+
+/// @brief Pointer to the @ref HttpResponseParser.
+typedef boost::shared_ptr<HttpResponseParser> HttpResponseParserPtr;
+
+/// @brief A generic parser for HTTP responses.
+///
+/// This class implements a parser for HTTP responses. The parser derives
+/// from the @ref HttpMessageParserBase class and implements its own state
+/// machine on top of it. The states of the parser reflect various parts of
+/// the HTTP message being parsed, e.g. parsing HTTP version, status code,
+/// message body etc. The descriptions of all parser states are provided
+/// below together with the constants defining these states.
+///
+/// The response parser validates the syntax of the received message as it
+/// progresses with parsing the data. Though, it doesn't interpret the
+/// received data until the whole message is parsed. In most cases we want
+/// to apply some restrictions on the message content, e.g. responses to
+/// control commands include JSON body. The parser doesn't verify if the
+/// message meets such restrictions until the whole message is parsed,
+/// i.e. stored in the @ref HttpResponseContext object. This object is
+/// associated with @ref HttpResponse object (or its derivation). When
+/// the parsing is completed, the @ref HttpResponse::create method is
+/// called to retrieve and interpret the data from the context. In
+/// particular, the @ref HttpResponse or its derivation checks if the
+/// received message meets the desired restrictions.
+class HttpResponseParser : public HttpMessageParserBase {
+public:
+
+ /// @name States supported by the HttpResponseParser.
+ ///
+ //@{
+
+ /// @brief State indicating a beginning of parsing.
+ static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 101;
+
+ /// @brief Parsing letter "H" of "HTTP".
+ static const int HTTP_VERSION_H_ST = SM_DERIVED_STATE_MIN + 102;
+
+ /// @brief Parsing first occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T1_ST = SM_DERIVED_STATE_MIN + 103;
+
+ /// @brief Parsing second occurrence of "T" in "HTTP".
+ static const int HTTP_VERSION_T2_ST = SM_DERIVED_STATE_MIN + 104;
+
+ /// @brief Parsing letter "P" in "HTTP".
+ static const int HTTP_VERSION_P_ST = SM_DERIVED_STATE_MIN + 105;
+
+ /// @brief Parsing slash character in "HTTP/Y.X"
+ static const int HTTP_VERSION_SLASH_ST = SM_DERIVED_STATE_MIN + 106;
+
+ /// @brief Starting to parse major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_START_ST = SM_DERIVED_STATE_MIN + 107;
+
+ /// @brief Parsing major HTTP version number.
+ static const int HTTP_VERSION_MAJOR_ST = SM_DERIVED_STATE_MIN + 108;
+
+ /// @brief Starting to parse minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_START_ST = SM_DERIVED_STATE_MIN + 109;
+
+ /// @brief Parsing minor HTTP version number.
+ static const int HTTP_VERSION_MINOR_ST = SM_DERIVED_STATE_MIN + 110;
+
+ /// @brief Starting to parse HTTP status code.
+ static const int HTTP_STATUS_CODE_START_ST = SM_DERIVED_STATE_MIN + 111;
+
+ /// @brief Parsing HTTP status code.
+ static const int HTTP_STATUS_CODE_ST = SM_DERIVED_STATE_MIN + 112;
+
+ /// @brief Starting to parse HTTP status phrase.
+ static const int HTTP_PHRASE_START_ST = SM_DERIVED_STATE_MIN + 113;
+
+ /// @brief Parsing HTTP status phrase.
+ static const int HTTP_PHRASE_ST = SM_DERIVED_STATE_MIN + 114;
+
+ /// @brief Parsing first new line (after HTTP status phrase).
+ static const int EXPECTING_NEW_LINE1_ST = SM_DERIVED_STATE_MIN + 115;
+
+ static const int HEADER_LINE_START_ST = SM_DERIVED_STATE_MIN + 116;
+
+ /// @brief Parsing LWS (Linear White Space), i.e. new line with a space
+ /// or tab character while parsing a HTTP header.
+ static const int HEADER_LWS_ST = SM_DERIVED_STATE_MIN + 117;
+
+ /// @brief Parsing header name.
+ static const int HEADER_NAME_ST = SM_DERIVED_STATE_MIN + 118;
+
+ /// @brief Parsing space before header value.
+ static const int SPACE_BEFORE_HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 119;
+
+ /// @brief Parsing header value.
+ static const int HEADER_VALUE_ST = SM_DERIVED_STATE_MIN + 120;
+
+ /// @brief Expecting new line after parsing header value.
+ static const int EXPECTING_NEW_LINE2_ST = SM_DERIVED_STATE_MIN + 121;
+
+ /// @brief Expecting second new line marking end of HTTP headers.
+ static const int EXPECTING_NEW_LINE3_ST = SM_DERIVED_STATE_MIN + 122;
+
+ /// @brief Parsing body of a HTTP message.
+ static const int HTTP_BODY_ST = SM_DERIVED_STATE_MIN + 123;
+
+ //@}
+
+ /// @brief Constructor.
+ ///
+ /// Creates new instance of the parser.
+ ///
+ /// @param response Reference to the @ref HttpResponse object or its
+ /// derivation that should be used to validate the parsed response and
+ /// to be used as a container for the parsed response.
+ explicit HttpResponseParser(HttpResponse& response);
+
+ /// @brief Initialize the state model for parsing.
+ ///
+ /// This method must be called before parsing the response, i.e. before
+ /// calling @ref HttpResponseParser::poll. It initializes dictionaries of
+ /// states and events, and sets the initial model state to RECEIVE_START_ST.
+ void initModel();
+
+private:
+
+ /// @brief Defines states of the parser.
+ virtual void defineStates();
+
+ /// @name State handlers.
+ ///
+ //@{
+
+ /// @brief Handler for RECEIVE_START_ST.
+ void receiveStartHandler();
+
+ /// @brief Handler for states parsing "HTTP" string within the first line
+ /// of the HTTP request.
+ ///
+ /// @param expected_letter One of the 'H', 'T', 'P'.
+ /// @param next_state A state to which the parser should transition after
+ /// parsing the character.
+ void versionHTTPHandler(const char expected_letter,
+ const unsigned int next_state);
+
+ /// @brief Handler for states in which parser begins to read numeric values.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param next_state State to which the parser should transition.
+ /// @param number_name Name of the parsed numeric value, e.g. HTTP version or
+ /// HTTP status code (used for error logging).
+ /// @param [out] storage Reference to a number holding current product of
+ /// parsing major or minor version number.
+ void numberStartHandler(const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* const storage);
+
+ /// @brief Handler for states in which parser reads numeric values.
+ ///
+ /// This handler calculates version number using the following equation:
+ /// @code
+ /// storage = storage * 10 + c - '0';
+ /// @endcode
+ ///
+ /// @param following_character Character following the version number, i.e.
+ /// '.' for major version, \r for minor version.
+ /// @param next_state State to which the parser should transition.
+ /// @param number_name Name of the parsed numeric value, e.g. HTTP version or
+ /// HTTP status code (used for error logging).
+ /// @param [out] storage Pointer to a number holding current product of
+ /// parsing major or minor version number.
+ void numberHandler(const char following_character,
+ const unsigned int next_state,
+ const std::string& number_name,
+ unsigned int* const storage);
+
+ /// @brief Handler for HTTP_PHRASE_START_ST.
+ void phraseStartHandler();
+
+ /// @brief Handler for HTTP_PHRASE_ST.
+ void phraseHandler();
+
+ /// @brief Handler for states related to new lines.
+ ///
+ /// If the next_state is HTTP_PARSE_OK_ST it indicates that the parsed
+ /// value is a 3rd new line within request HTTP message. In this case the
+ /// handler calls @ref HttpRequest::create to validate the received message
+ /// (excluding body). The handler then reads the "Content-Length" header to
+ /// check if the request contains a body. If the "Content-Length" is greater
+ /// than zero, the parser transitions to HTTP_BODY_ST. If the
+ /// "Content-Length" doesn't exist the parser transitions to
+ /// HTTP_PARSE_OK_ST.
+ ///
+ /// @param next_state A state to which parser should transition.
+ void expectingNewLineHandler(const unsigned int next_state);
+
+ /// @brief Handler for HEADER_LINE_START_ST.
+ void headerLineStartHandler();
+
+ /// @brief Handler for HEADER_LWS_ST.
+ void headerLwsHandler();
+
+ /// @brief Handler for HEADER_NAME_ST.
+ void headerNameHandler();
+
+ /// @brief Handler for SPACE_BEFORE_HEADER_VALUE_ST.
+ void spaceBeforeHeaderValueHandler();
+
+ /// @brief Handler for HEADER_VALUE_ST.
+ void headerValueHandler();
+
+ /// @brief Handler for HTTP_BODY_ST.
+ void bodyHandler();
+
+ //@}
+
+ /// @brief Reference to the response object specified in the constructor.
+ HttpResponse& response_;
+
+ /// @brief Pointer to the internal context of the @ref HttpResponse object.
+ HttpResponseContextPtr context_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am
new file mode 100644
index 0000000..5b2b2a0
--- /dev/null
+++ b/src/lib/http/tests/Makefile.am
@@ -0,0 +1,76 @@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = testdata/empty
+EXTRA_DIST += testdata/hiddenp
+EXTRA_DIST += testdata/hiddens
+EXTRA_DIST += testdata/hiddenu
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+TEST_CA_DIR = $(srcdir)/../../asiolink/testutils/ca
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(TEST_CA_DIR)\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DDATA_DIR=\"$(abs_srcdir)/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 += libhttp_unittests
+
+libhttp_unittests_SOURCES = basic_auth_unittests.cc
+libhttp_unittests_SOURCES += basic_auth_config_unittests.cc
+libhttp_unittests_SOURCES += connection_pool_unittests.cc
+libhttp_unittests_SOURCES += date_time_unittests.cc
+libhttp_unittests_SOURCES += http_header_unittests.cc
+libhttp_unittests_SOURCES += post_request_unittests.cc
+libhttp_unittests_SOURCES += post_request_json_unittests.cc
+libhttp_unittests_SOURCES += request_parser_unittests.cc
+libhttp_unittests_SOURCES += request_test.h
+libhttp_unittests_SOURCES += response_creator_unittests.cc
+libhttp_unittests_SOURCES += response_parser_unittests.cc
+libhttp_unittests_SOURCES += response_test.h
+libhttp_unittests_SOURCES += request_unittests.cc
+libhttp_unittests_SOURCES += response_unittests.cc
+libhttp_unittests_SOURCES += response_json_unittests.cc
+libhttp_unittests_SOURCES += run_unittests.cc
+libhttp_unittests_SOURCES += server_client_unittests.cc
+if HAVE_OPENSSL
+libhttp_unittests_SOURCES += tls_server_unittests.cc
+libhttp_unittests_SOURCES += tls_client_unittests.cc
+endif
+if HAVE_BOTAN_BOOST
+libhttp_unittests_SOURCES += tls_server_unittests.cc
+libhttp_unittests_SOURCES += tls_client_unittests.cc
+endif
+libhttp_unittests_SOURCES += url_unittests.cc
+libhttp_unittests_SOURCES += test_http_client.h
+libhttp_unittests_SOURCES += client_mt_unittests.cc
+
+libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+libhttp_unittests_LDADD = $(top_builddir)/src/lib/http/libkea-http.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libhttp_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libhttp_unittests_LDADD += $(LOG4CPLUS_LIBS)
+libhttp_unittests_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/http/tests/Makefile.in b/src/lib/http/tests/Makefile.in
new file mode 100644
index 0000000..5c5bc7a
--- /dev/null
+++ b/src/lib/http/tests/Makefile.in
@@ -0,0 +1,1392 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libhttp_unittests
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__append_2 = \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ tls_server_unittests.cc \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ tls_client_unittests.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__append_3 = tls_server_unittests.cc \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ tls_client_unittests.cc
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/http/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libhttp_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libhttp_unittests_SOURCES_DIST = basic_auth_unittests.cc \
+ basic_auth_config_unittests.cc connection_pool_unittests.cc \
+ date_time_unittests.cc http_header_unittests.cc \
+ post_request_unittests.cc post_request_json_unittests.cc \
+ request_parser_unittests.cc request_test.h \
+ response_creator_unittests.cc response_parser_unittests.cc \
+ response_test.h request_unittests.cc response_unittests.cc \
+ response_json_unittests.cc run_unittests.cc \
+ server_client_unittests.cc tls_server_unittests.cc \
+ tls_client_unittests.cc url_unittests.cc test_http_client.h \
+ client_mt_unittests.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__objects_1 = libhttp_unittests-tls_server_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@ libhttp_unittests-tls_client_unittests.$(OBJEXT)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__objects_2 = libhttp_unittests-tls_server_unittests.$(OBJEXT) \
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@ libhttp_unittests-tls_client_unittests.$(OBJEXT)
+@HAVE_GTEST_TRUE@am_libhttp_unittests_OBJECTS = libhttp_unittests-basic_auth_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-basic_auth_config_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-connection_pool_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-date_time_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-http_header_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-post_request_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-post_request_json_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-request_parser_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_creator_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_parser_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-request_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-response_json_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-server_client_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-url_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libhttp_unittests-client_mt_unittests.$(OBJEXT)
+libhttp_unittests_OBJECTS = $(am_libhttp_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libhttp_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@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 =
+libhttp_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libhttp_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)/libhttp_unittests-basic_auth_config_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-request_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-response_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po \
+ ./$(DEPDIR)/libhttp_unittests-url_unittests.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 = $(libhttp_unittests_SOURCES)
+DIST_SOURCES = $(am__libhttp_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+
+# Add to the tarball:
+EXTRA_DIST = testdata/empty testdata/hiddenp testdata/hiddens \
+ testdata/hiddenu
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(TEST_CA_DIR)\" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" \
+ -DDATA_DIR=\"$(abs_srcdir)/testdata\"
+TEST_CA_DIR = $(srcdir)/../../asiolink/testutils/ca
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libhttp_unittests_SOURCES = basic_auth_unittests.cc \
+@HAVE_GTEST_TRUE@ basic_auth_config_unittests.cc \
+@HAVE_GTEST_TRUE@ connection_pool_unittests.cc \
+@HAVE_GTEST_TRUE@ date_time_unittests.cc \
+@HAVE_GTEST_TRUE@ http_header_unittests.cc \
+@HAVE_GTEST_TRUE@ post_request_unittests.cc \
+@HAVE_GTEST_TRUE@ post_request_json_unittests.cc \
+@HAVE_GTEST_TRUE@ request_parser_unittests.cc request_test.h \
+@HAVE_GTEST_TRUE@ response_creator_unittests.cc \
+@HAVE_GTEST_TRUE@ response_parser_unittests.cc response_test.h \
+@HAVE_GTEST_TRUE@ request_unittests.cc response_unittests.cc \
+@HAVE_GTEST_TRUE@ response_json_unittests.cc run_unittests.cc \
+@HAVE_GTEST_TRUE@ server_client_unittests.cc $(am__append_2) \
+@HAVE_GTEST_TRUE@ $(am__append_3) url_unittests.cc \
+@HAVE_GTEST_TRUE@ test_http_client.h client_mt_unittests.cc
+@HAVE_GTEST_TRUE@libhttp_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libhttp_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libhttp_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libhttp_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@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/http/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/http/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
+
+libhttp_unittests$(EXEEXT): $(libhttp_unittests_OBJECTS) $(libhttp_unittests_DEPENDENCIES) $(EXTRA_libhttp_unittests_DEPENDENCIES)
+ @rm -f libhttp_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libhttp_unittests_LINK) $(libhttp_unittests_OBJECTS) $(libhttp_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-request_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-response_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libhttp_unittests-url_unittests.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 $@ $<
+
+libhttp_unittests-basic_auth_unittests.o: basic_auth_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo -c -o libhttp_unittests-basic_auth_unittests.o `test -f 'basic_auth_unittests.cc' || echo '$(srcdir)/'`basic_auth_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_unittests.cc' object='libhttp_unittests-basic_auth_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_unittests.o `test -f 'basic_auth_unittests.cc' || echo '$(srcdir)/'`basic_auth_unittests.cc
+
+libhttp_unittests-basic_auth_unittests.obj: basic_auth_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo -c -o libhttp_unittests-basic_auth_unittests.obj `if test -f 'basic_auth_unittests.cc'; then $(CYGPATH_W) 'basic_auth_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_unittests.cc' object='libhttp_unittests-basic_auth_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_unittests.obj `if test -f 'basic_auth_unittests.cc'; then $(CYGPATH_W) 'basic_auth_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_unittests.cc'; fi`
+
+libhttp_unittests-basic_auth_config_unittests.o: basic_auth_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_config_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo -c -o libhttp_unittests-basic_auth_config_unittests.o `test -f 'basic_auth_config_unittests.cc' || echo '$(srcdir)/'`basic_auth_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_config_unittests.cc' object='libhttp_unittests-basic_auth_config_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_config_unittests.o `test -f 'basic_auth_config_unittests.cc' || echo '$(srcdir)/'`basic_auth_config_unittests.cc
+
+libhttp_unittests-basic_auth_config_unittests.obj: basic_auth_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-basic_auth_config_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo -c -o libhttp_unittests-basic_auth_config_unittests.obj `if test -f 'basic_auth_config_unittests.cc'; then $(CYGPATH_W) 'basic_auth_config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_config_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Tpo $(DEPDIR)/libhttp_unittests-basic_auth_config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='basic_auth_config_unittests.cc' object='libhttp_unittests-basic_auth_config_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-basic_auth_config_unittests.obj `if test -f 'basic_auth_config_unittests.cc'; then $(CYGPATH_W) 'basic_auth_config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/basic_auth_config_unittests.cc'; fi`
+
+libhttp_unittests-connection_pool_unittests.o: connection_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-connection_pool_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo -c -o libhttp_unittests-connection_pool_unittests.o `test -f 'connection_pool_unittests.cc' || echo '$(srcdir)/'`connection_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection_pool_unittests.cc' object='libhttp_unittests-connection_pool_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-connection_pool_unittests.o `test -f 'connection_pool_unittests.cc' || echo '$(srcdir)/'`connection_pool_unittests.cc
+
+libhttp_unittests-connection_pool_unittests.obj: connection_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-connection_pool_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo -c -o libhttp_unittests-connection_pool_unittests.obj `if test -f 'connection_pool_unittests.cc'; then $(CYGPATH_W) 'connection_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/connection_pool_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Tpo $(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='connection_pool_unittests.cc' object='libhttp_unittests-connection_pool_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-connection_pool_unittests.obj `if test -f 'connection_pool_unittests.cc'; then $(CYGPATH_W) 'connection_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/connection_pool_unittests.cc'; fi`
+
+libhttp_unittests-date_time_unittests.o: date_time_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-date_time_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo -c -o libhttp_unittests-date_time_unittests.o `test -f 'date_time_unittests.cc' || echo '$(srcdir)/'`date_time_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo $(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='date_time_unittests.cc' object='libhttp_unittests-date_time_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-date_time_unittests.o `test -f 'date_time_unittests.cc' || echo '$(srcdir)/'`date_time_unittests.cc
+
+libhttp_unittests-date_time_unittests.obj: date_time_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-date_time_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo -c -o libhttp_unittests-date_time_unittests.obj `if test -f 'date_time_unittests.cc'; then $(CYGPATH_W) 'date_time_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/date_time_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-date_time_unittests.Tpo $(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='date_time_unittests.cc' object='libhttp_unittests-date_time_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-date_time_unittests.obj `if test -f 'date_time_unittests.cc'; then $(CYGPATH_W) 'date_time_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/date_time_unittests.cc'; fi`
+
+libhttp_unittests-http_header_unittests.o: http_header_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-http_header_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo -c -o libhttp_unittests-http_header_unittests.o `test -f 'http_header_unittests.cc' || echo '$(srcdir)/'`http_header_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo $(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_header_unittests.cc' object='libhttp_unittests-http_header_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-http_header_unittests.o `test -f 'http_header_unittests.cc' || echo '$(srcdir)/'`http_header_unittests.cc
+
+libhttp_unittests-http_header_unittests.obj: http_header_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-http_header_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo -c -o libhttp_unittests-http_header_unittests.obj `if test -f 'http_header_unittests.cc'; then $(CYGPATH_W) 'http_header_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/http_header_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-http_header_unittests.Tpo $(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='http_header_unittests.cc' object='libhttp_unittests-http_header_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-http_header_unittests.obj `if test -f 'http_header_unittests.cc'; then $(CYGPATH_W) 'http_header_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/http_header_unittests.cc'; fi`
+
+libhttp_unittests-post_request_unittests.o: post_request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo -c -o libhttp_unittests-post_request_unittests.o `test -f 'post_request_unittests.cc' || echo '$(srcdir)/'`post_request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_unittests.cc' object='libhttp_unittests-post_request_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_unittests.o `test -f 'post_request_unittests.cc' || echo '$(srcdir)/'`post_request_unittests.cc
+
+libhttp_unittests-post_request_unittests.obj: post_request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo -c -o libhttp_unittests-post_request_unittests.obj `if test -f 'post_request_unittests.cc'; then $(CYGPATH_W) 'post_request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_unittests.cc' object='libhttp_unittests-post_request_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_unittests.obj `if test -f 'post_request_unittests.cc'; then $(CYGPATH_W) 'post_request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_unittests.cc'; fi`
+
+libhttp_unittests-post_request_json_unittests.o: post_request_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_json_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo -c -o libhttp_unittests-post_request_json_unittests.o `test -f 'post_request_json_unittests.cc' || echo '$(srcdir)/'`post_request_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_json_unittests.cc' object='libhttp_unittests-post_request_json_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_json_unittests.o `test -f 'post_request_json_unittests.cc' || echo '$(srcdir)/'`post_request_json_unittests.cc
+
+libhttp_unittests-post_request_json_unittests.obj: post_request_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-post_request_json_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo -c -o libhttp_unittests-post_request_json_unittests.obj `if test -f 'post_request_json_unittests.cc'; then $(CYGPATH_W) 'post_request_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_json_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='post_request_json_unittests.cc' object='libhttp_unittests-post_request_json_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-post_request_json_unittests.obj `if test -f 'post_request_json_unittests.cc'; then $(CYGPATH_W) 'post_request_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/post_request_json_unittests.cc'; fi`
+
+libhttp_unittests-request_parser_unittests.o: request_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_parser_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo -c -o libhttp_unittests-request_parser_unittests.o `test -f 'request_parser_unittests.cc' || echo '$(srcdir)/'`request_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_parser_unittests.cc' object='libhttp_unittests-request_parser_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_parser_unittests.o `test -f 'request_parser_unittests.cc' || echo '$(srcdir)/'`request_parser_unittests.cc
+
+libhttp_unittests-request_parser_unittests.obj: request_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_parser_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo -c -o libhttp_unittests-request_parser_unittests.obj `if test -f 'request_parser_unittests.cc'; then $(CYGPATH_W) 'request_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_parser_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_parser_unittests.cc' object='libhttp_unittests-request_parser_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_parser_unittests.obj `if test -f 'request_parser_unittests.cc'; then $(CYGPATH_W) 'request_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_parser_unittests.cc'; fi`
+
+libhttp_unittests-response_creator_unittests.o: response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_creator_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo -c -o libhttp_unittests-response_creator_unittests.o `test -f 'response_creator_unittests.cc' || echo '$(srcdir)/'`response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_creator_unittests.cc' object='libhttp_unittests-response_creator_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_creator_unittests.o `test -f 'response_creator_unittests.cc' || echo '$(srcdir)/'`response_creator_unittests.cc
+
+libhttp_unittests-response_creator_unittests.obj: response_creator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_creator_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo -c -o libhttp_unittests-response_creator_unittests.obj `if test -f 'response_creator_unittests.cc'; then $(CYGPATH_W) 'response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_creator_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_creator_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_creator_unittests.cc' object='libhttp_unittests-response_creator_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_creator_unittests.obj `if test -f 'response_creator_unittests.cc'; then $(CYGPATH_W) 'response_creator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_creator_unittests.cc'; fi`
+
+libhttp_unittests-response_parser_unittests.o: response_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_parser_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo -c -o libhttp_unittests-response_parser_unittests.o `test -f 'response_parser_unittests.cc' || echo '$(srcdir)/'`response_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_parser_unittests.cc' object='libhttp_unittests-response_parser_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_parser_unittests.o `test -f 'response_parser_unittests.cc' || echo '$(srcdir)/'`response_parser_unittests.cc
+
+libhttp_unittests-response_parser_unittests.obj: response_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_parser_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo -c -o libhttp_unittests-response_parser_unittests.obj `if test -f 'response_parser_unittests.cc'; then $(CYGPATH_W) 'response_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_parser_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_parser_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_parser_unittests.cc' object='libhttp_unittests-response_parser_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_parser_unittests.obj `if test -f 'response_parser_unittests.cc'; then $(CYGPATH_W) 'response_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_parser_unittests.cc'; fi`
+
+libhttp_unittests-request_unittests.o: request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_unittests.Tpo -c -o libhttp_unittests-request_unittests.o `test -f 'request_unittests.cc' || echo '$(srcdir)/'`request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_unittests.cc' object='libhttp_unittests-request_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_unittests.o `test -f 'request_unittests.cc' || echo '$(srcdir)/'`request_unittests.cc
+
+libhttp_unittests-request_unittests.obj: request_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-request_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-request_unittests.Tpo -c -o libhttp_unittests-request_unittests.obj `if test -f 'request_unittests.cc'; then $(CYGPATH_W) 'request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-request_unittests.Tpo $(DEPDIR)/libhttp_unittests-request_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='request_unittests.cc' object='libhttp_unittests-request_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-request_unittests.obj `if test -f 'request_unittests.cc'; then $(CYGPATH_W) 'request_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/request_unittests.cc'; fi`
+
+libhttp_unittests-response_unittests.o: response_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_unittests.Tpo -c -o libhttp_unittests-response_unittests.o `test -f 'response_unittests.cc' || echo '$(srcdir)/'`response_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_unittests.cc' object='libhttp_unittests-response_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_unittests.o `test -f 'response_unittests.cc' || echo '$(srcdir)/'`response_unittests.cc
+
+libhttp_unittests-response_unittests.obj: response_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_unittests.Tpo -c -o libhttp_unittests-response_unittests.obj `if test -f 'response_unittests.cc'; then $(CYGPATH_W) 'response_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_unittests.cc' object='libhttp_unittests-response_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_unittests.obj `if test -f 'response_unittests.cc'; then $(CYGPATH_W) 'response_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_unittests.cc'; fi`
+
+libhttp_unittests-response_json_unittests.o: response_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_json_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo -c -o libhttp_unittests-response_json_unittests.o `test -f 'response_json_unittests.cc' || echo '$(srcdir)/'`response_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_json_unittests.cc' object='libhttp_unittests-response_json_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_json_unittests.o `test -f 'response_json_unittests.cc' || echo '$(srcdir)/'`response_json_unittests.cc
+
+libhttp_unittests-response_json_unittests.obj: response_json_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-response_json_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo -c -o libhttp_unittests-response_json_unittests.obj `if test -f 'response_json_unittests.cc'; then $(CYGPATH_W) 'response_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_json_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-response_json_unittests.Tpo $(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='response_json_unittests.cc' object='libhttp_unittests-response_json_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-response_json_unittests.obj `if test -f 'response_json_unittests.cc'; then $(CYGPATH_W) 'response_json_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/response_json_unittests.cc'; fi`
+
+libhttp_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-run_unittests.Tpo -c -o libhttp_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-run_unittests.Tpo $(DEPDIR)/libhttp_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libhttp_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libhttp_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-run_unittests.Tpo -c -o libhttp_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)/libhttp_unittests-run_unittests.Tpo $(DEPDIR)/libhttp_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libhttp_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libhttp_unittests-server_client_unittests.o: server_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-server_client_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo -c -o libhttp_unittests-server_client_unittests.o `test -f 'server_client_unittests.cc' || echo '$(srcdir)/'`server_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_client_unittests.cc' object='libhttp_unittests-server_client_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-server_client_unittests.o `test -f 'server_client_unittests.cc' || echo '$(srcdir)/'`server_client_unittests.cc
+
+libhttp_unittests-server_client_unittests.obj: server_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-server_client_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo -c -o libhttp_unittests-server_client_unittests.obj `if test -f 'server_client_unittests.cc'; then $(CYGPATH_W) 'server_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/server_client_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-server_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_client_unittests.cc' object='libhttp_unittests-server_client_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-server_client_unittests.obj `if test -f 'server_client_unittests.cc'; then $(CYGPATH_W) 'server_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/server_client_unittests.cc'; fi`
+
+libhttp_unittests-tls_server_unittests.o: tls_server_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_server_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo -c -o libhttp_unittests-tls_server_unittests.o `test -f 'tls_server_unittests.cc' || echo '$(srcdir)/'`tls_server_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_server_unittests.cc' object='libhttp_unittests-tls_server_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_server_unittests.o `test -f 'tls_server_unittests.cc' || echo '$(srcdir)/'`tls_server_unittests.cc
+
+libhttp_unittests-tls_server_unittests.obj: tls_server_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_server_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo -c -o libhttp_unittests-tls_server_unittests.obj `if test -f 'tls_server_unittests.cc'; then $(CYGPATH_W) 'tls_server_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_server_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_server_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_server_unittests.cc' object='libhttp_unittests-tls_server_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_server_unittests.obj `if test -f 'tls_server_unittests.cc'; then $(CYGPATH_W) 'tls_server_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_server_unittests.cc'; fi`
+
+libhttp_unittests-tls_client_unittests.o: tls_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_client_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo -c -o libhttp_unittests-tls_client_unittests.o `test -f 'tls_client_unittests.cc' || echo '$(srcdir)/'`tls_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_client_unittests.cc' object='libhttp_unittests-tls_client_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_client_unittests.o `test -f 'tls_client_unittests.cc' || echo '$(srcdir)/'`tls_client_unittests.cc
+
+libhttp_unittests-tls_client_unittests.obj: tls_client_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-tls_client_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo -c -o libhttp_unittests-tls_client_unittests.obj `if test -f 'tls_client_unittests.cc'; then $(CYGPATH_W) 'tls_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_client_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-tls_client_unittests.Tpo $(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_client_unittests.cc' object='libhttp_unittests-tls_client_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-tls_client_unittests.obj `if test -f 'tls_client_unittests.cc'; then $(CYGPATH_W) 'tls_client_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_client_unittests.cc'; fi`
+
+libhttp_unittests-url_unittests.o: url_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-url_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-url_unittests.Tpo -c -o libhttp_unittests-url_unittests.o `test -f 'url_unittests.cc' || echo '$(srcdir)/'`url_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-url_unittests.Tpo $(DEPDIR)/libhttp_unittests-url_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='url_unittests.cc' object='libhttp_unittests-url_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-url_unittests.o `test -f 'url_unittests.cc' || echo '$(srcdir)/'`url_unittests.cc
+
+libhttp_unittests-url_unittests.obj: url_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-url_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-url_unittests.Tpo -c -o libhttp_unittests-url_unittests.obj `if test -f 'url_unittests.cc'; then $(CYGPATH_W) 'url_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/url_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-url_unittests.Tpo $(DEPDIR)/libhttp_unittests-url_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='url_unittests.cc' object='libhttp_unittests-url_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-url_unittests.obj `if test -f 'url_unittests.cc'; then $(CYGPATH_W) 'url_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/url_unittests.cc'; fi`
+
+libhttp_unittests-client_mt_unittests.o: client_mt_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-client_mt_unittests.o -MD -MP -MF $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo -c -o libhttp_unittests-client_mt_unittests.o `test -f 'client_mt_unittests.cc' || echo '$(srcdir)/'`client_mt_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo $(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_mt_unittests.cc' object='libhttp_unittests-client_mt_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-client_mt_unittests.o `test -f 'client_mt_unittests.cc' || echo '$(srcdir)/'`client_mt_unittests.cc
+
+libhttp_unittests-client_mt_unittests.obj: client_mt_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -MT libhttp_unittests-client_mt_unittests.obj -MD -MP -MF $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo -c -o libhttp_unittests-client_mt_unittests.obj `if test -f 'client_mt_unittests.cc'; then $(CYGPATH_W) 'client_mt_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_mt_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libhttp_unittests-client_mt_unittests.Tpo $(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='client_mt_unittests.cc' object='libhttp_unittests-client_mt_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) $(libhttp_unittests_CPPFLAGS) $(CPPFLAGS) $(libhttp_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libhttp_unittests-client_mt_unittests.obj `if test -f 'client_mt_unittests.cc'; then $(CYGPATH_W) 'client_mt_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/client_mt_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)/libhttp_unittests-basic_auth_config_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-url_unittests.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)/libhttp_unittests-basic_auth_config_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-basic_auth_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-client_mt_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-connection_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-date_time_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-http_header_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-post_request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-request_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_creator_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_json_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-response_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-server_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_client_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-tls_server_unittests.Po
+ -rm -f ./$(DEPDIR)/libhttp_unittests-url_unittests.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/http/tests/basic_auth_config_unittests.cc b/src/lib/http/tests/basic_auth_config_unittests.cc
new file mode 100644
index 0000000..5df2170
--- /dev/null
+++ b/src/lib/http/tests/basic_auth_config_unittests.cc
@@ -0,0 +1,540 @@
+// Copyright (C) 2020-2022 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 <http/basic_auth_config.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::http;
+using namespace isc::test;
+using namespace std;
+
+namespace {
+
+string data_dir(DATA_DIR);
+
+// Test that basic auth client works as expected.
+TEST(BasicHttpAuthClientTest, basic) {
+ // Create a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ BasicHttpAuthClient client("foo", "bar", ctx);
+
+ // Check it.
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("", client.getUserFile());
+ EXPECT_EQ("bar", client.getPassword());
+ EXPECT_EQ("", client.getPasswordFile());
+ EXPECT_FALSE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ expected->set("user", Element::create(string("foo")));
+ expected->set("password", Element::create(string("bar")));
+ expected->set("user-context", ctx);
+ runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth client with files works as expected.
+TEST(BasicHttpAuthClientTest, basicFiles) {
+ // Create a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ BasicHttpAuthClient client("", "foo", "", "bar", false, ctx);
+
+ // Check it.
+ EXPECT_EQ("", client.getUser());
+ EXPECT_EQ("foo", client.getUserFile());
+ EXPECT_EQ("", client.getPassword());
+ EXPECT_EQ("bar", client.getPasswordFile());
+ EXPECT_FALSE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ expected->set("user-file", Element::create(string("foo")));
+ expected->set("password-file", Element::create(string("bar")));
+ expected->set("user-context", ctx);
+ runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth client with one file works as expected.
+TEST(BasicHttpAuthClientTest, basicOneFile) {
+ // Create a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ BasicHttpAuthClient client("", "", "", "foobar", true, ctx);
+
+ // Check it.
+ EXPECT_EQ("", client.getUser());
+ EXPECT_EQ("", client.getUserFile());
+ EXPECT_EQ("", client.getPassword());
+ EXPECT_EQ("foobar", client.getPasswordFile());
+ EXPECT_TRUE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ expected->set("password-file", Element::create(string("foobar")));
+ expected->set("user-context", ctx);
+ runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth configuration works as expected.
+TEST(BasicHttpAuthConfigTest, basic) {
+ // Create a configuration.
+ BasicHttpAuthConfig config;
+
+ // Initial configuration is empty.
+ EXPECT_TRUE(config.empty());
+ EXPECT_TRUE(config.getRealm().empty());
+ EXPECT_TRUE(config.getDirectory().empty());
+ EXPECT_TRUE(config.getClientList().empty());
+ EXPECT_TRUE(config.getCredentialMap().empty());
+
+ // Set the realm, directory and user context.
+ EXPECT_NO_THROW(config.setRealm("my-realm"));
+ EXPECT_EQ("my-realm", config.getRealm());
+ EXPECT_NO_THROW(config.setDirectory("/tmp"));
+ EXPECT_EQ("/tmp", config.getDirectory());
+ ConstElementPtr horse = Element::fromJSON("{ \"value\": \"a horse\" }");
+ EXPECT_NO_THROW(config.setContext(horse));
+ EXPECT_TRUE(horse->equals(*config.getContext()));
+
+ // Add rejects user id with embedded ':'.
+ EXPECT_THROW(config.add("foo:", "", "bar", ""), BadValue);
+
+ // Add a client.
+ EXPECT_TRUE(config.empty());
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_NO_THROW(config.add("foo", "", "bar", "", false, ctx));
+ EXPECT_FALSE(config.empty());
+
+ // Check the client.
+ ASSERT_EQ(1, config.getClientList().size());
+ const BasicHttpAuthClient& client = config.getClientList().front();
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("bar", client.getPassword());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check the credential.
+ ASSERT_NE(0, config.getCredentialMap().count("Zm9vOmJhcg=="));
+ string user;
+ EXPECT_NO_THROW(user = config.getCredentialMap().at("Zm9vOmJhcg=="));
+ EXPECT_EQ("foo", user);
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ ElementPtr clients = Element::createList();
+ ElementPtr elem = Element::createMap();
+ elem->set("user", Element::create(string("foo")));
+ elem->set("password", Element::create(string("bar")));
+ elem->set("user-context", ctx);
+ clients->add(elem);
+ expected->set("type", Element::create(string("basic")));
+ expected->set("realm", Element::create(string("my-realm")));
+ expected->set("directory", Element::create(string("/tmp")));
+ expected->set("user-context", horse);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add a second client and test it.
+ EXPECT_NO_THROW(config.add("test", "", "123\xa3", ""));
+ ASSERT_EQ(2, config.getClientList().size());
+ EXPECT_EQ("foo", config.getClientList().front().getUser());
+ EXPECT_EQ("test", config.getClientList().back().getUser());
+ ASSERT_NE(0, config.getCredentialMap().count("dGVzdDoxMjPCow=="));
+
+ // Check clear.
+ config.clear();
+ EXPECT_TRUE(config.empty());
+ expected->set("clients", Element::createList());
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add clients again.
+ EXPECT_NO_THROW(config.add("test", "", "123\xa3", ""));
+ EXPECT_NO_THROW(config.add("foo", "", "bar", "", false, ctx));
+
+ // Check that toElement keeps add order.
+ ElementPtr elem0 = Element::createMap();
+ elem0->set("user", Element::create(string("test")));
+ elem0->set("password", Element::create(string("123\xa3")));
+ clients = Element::createList();
+ clients->add(elem0);
+ clients->add(elem);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+}
+
+// Test that basic auth configuration with files works as expected.
+TEST(BasicHttpAuthConfigTest, basicFiles) {
+ // Create a configuration.
+ BasicHttpAuthConfig config;
+
+ // Set the realm, directory and user context.
+ EXPECT_NO_THROW(config.setRealm("my-realm"));
+ EXPECT_EQ("my-realm", config.getRealm());
+ EXPECT_NO_THROW(config.setDirectory(data_dir));
+ EXPECT_EQ(data_dir, config.getDirectory());
+ ConstElementPtr horse = Element::fromJSON("{ \"value\": \"a horse\" }");
+ EXPECT_NO_THROW(config.setContext(horse));
+ EXPECT_TRUE(horse->equals(*config.getContext()));
+
+ // ':' in user id check is done during parsing
+
+ // Add a client.
+ EXPECT_TRUE(config.empty());
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_NO_THROW(config.add("foo", "", "", "hiddenp", false, ctx));
+ EXPECT_FALSE(config.empty());
+
+ // Check the client.
+ ASSERT_EQ(1, config.getClientList().size());
+ const BasicHttpAuthClient& client = config.getClientList().front();
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("", client.getUserFile());
+ EXPECT_EQ("", client.getPassword());
+ EXPECT_EQ("hiddenp", client.getPasswordFile());
+ EXPECT_FALSE(client.getPasswordFileOnly());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ ElementPtr clients = Element::createList();
+ ElementPtr elem = Element::createMap();
+ elem->set("user", Element::create(string("foo")));
+ elem->set("password-file", Element::create(string("hiddenp")));
+ elem->set("user-context", ctx);
+ clients->add(elem);
+ expected->set("type", Element::create(string("basic")));
+ expected->set("realm", Element::create(string("my-realm")));
+ expected->set("directory", Element::create(data_dir));
+ expected->set("user-context", horse);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add a second client and test it.
+ EXPECT_NO_THROW(config.add("", "hiddenu", "", "hiddenp"));
+ ASSERT_EQ(2, config.getClientList().size());
+ EXPECT_EQ("foo", config.getClientList().front().getUser());
+ EXPECT_EQ("hiddenu", config.getClientList().back().getUserFile());
+
+ // Check clear.
+ config.clear();
+ EXPECT_TRUE(config.empty());
+ expected->set("clients", Element::createList());
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add clients again.
+ EXPECT_NO_THROW(config.add("", "hiddenu", "", "hiddenp"));
+ EXPECT_NO_THROW(config.add("foo", "", "", "hiddenp", false, ctx));
+
+ // Check that toElement keeps add order.
+ ElementPtr elem0 = Element::createMap();
+ elem0->set("user-file", Element::create(string("hiddenu")));
+ elem0->set("password-file", Element::create(string("hiddenp")));
+ clients = Element::createList();
+ clients->add(elem0);
+ clients->add(elem);
+ expected->set("clients", clients);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+}
+
+// Test that basic auth configuration parses.
+TEST(BasicHttpAuthConfigTest, parse) {
+ BasicHttpAuthConfig config;
+ ElementPtr cfg;
+
+ // No config is accepted.
+ EXPECT_NO_THROW(config.parse(cfg));
+ EXPECT_TRUE(config.empty());
+ EXPECT_TRUE(config.getClientList().empty());
+ EXPECT_TRUE(config.getCredentialMap().empty());
+ ElementPtr expected = Element::createMap();
+ expected->set("type", Element::create(string("basic")));
+ expected->set("realm", Element::create(string("")));
+ expected->set("directory", Element::create(string("")));
+ expected->set("clients", Element::createList());
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // The config must be a map.
+ cfg = Element::createList();
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "authentication must be a map (:0:0)");
+
+ // The type must be present.
+ cfg = Element::createMap();
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "type is required in authentication (:0:0)");
+
+ // The type must be a string.
+ cfg->set("type", Element::create(true));
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "type must be a string (:0:0)");
+
+ // The type must be basic.
+ cfg->set("type", Element::create(string("foobar")));
+ string errmsg = "only basic HTTP authentication is supported: type is ";
+ errmsg += "'foobar' not 'basic' (:0:0)";
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, errmsg);
+ cfg->set("type", Element::create(string("basic")));
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // The realm must be a string.
+ cfg->set("realm", Element::createList());
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "realm must be a string (:0:0)");
+ cfg->set("realm", Element::create(string("my-realm")));
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // The directory must be a string.
+ cfg->set("directory", Element::createMap());
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "directory must be a string (:0:0)");
+ cfg->set("directory", Element::create(data_dir));
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // The user context must be a map.
+ ElementPtr ctx = Element::createList();
+ cfg->set("user-context", ctx);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user-context must be a map (:0:0)");
+ ctx = Element::fromJSON("{ \"value\": \"a horse\" }");
+ cfg->set("user-context", ctx);
+ EXPECT_NO_THROW(config.parse(cfg));
+
+ // Clients must be a list.
+ ElementPtr clients_cfg = Element::createMap();
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "clients must be a list (:0:0)");
+
+ // The client config must be a map.
+ clients_cfg = Element::createList();
+ ElementPtr client_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "clients items must be maps (:0:0)");
+
+ // The user parameter is mandatory in client config
+ // without a password file.
+ client_cfg = Element::createMap();
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user is required in clients items (:0:0)");
+
+ // The user parameter must be a string.
+ ElementPtr user_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must be a string (:0:0)");
+
+ // The user parameter must not be empty.
+ user_cfg = Element::create(string(""));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not be empty (:0:0)");
+
+ // The user parameter must not contain ':'.
+ user_cfg = Element::create(string("foo:bar"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not contain a ':': 'foo:bar' (:0:0)");
+
+ // The user-file parameter must be a string.
+ ElementPtr user_file_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user-file must be a string (:0:0)");
+
+ // The user and user-file parameters are incompatible.
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user (:0:0) and user-file (:0:0) are "
+ "mutually exclusive");
+
+ // The user-file parameter must not be empty.
+ user_file_cfg = Element::create(string("empty"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not be empty from user-file "
+ "'empty' (:0:0)");
+
+ // The user-file parameter must not contain ':'.
+ user_file_cfg = Element::create(string("hiddens"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user-file", user_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not contain a ':' from user-file "
+ "'hiddens' (:0:0)");
+
+ // Password is not required.
+ user_cfg = Element::create(string("foo"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // The password parameter must be a string.
+ ElementPtr password_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("password", password_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "password must be a string (:0:0)");
+
+ // Empty password is accepted.
+ password_cfg = Element::create(string(""));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // The password-file parameter must be a string.
+ ElementPtr password_file_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ // user is not required when password-file is here.
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "password-file must be a string (:0:0)");
+
+ // The password and password-file parameters are incompatible.
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "password (:0:0) and password-file (:0:0) are "
+ "mutually exclusive");
+
+ // Empty password-file is accepted.
+ password_file_cfg = Element::create(string("empty"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // password-file is enough.
+ password_file_cfg = Element::create(string("hiddens"));
+ client_cfg = Element::createMap();
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("test", config.getClientList().front().getPassword());
+ config.clear();
+
+ // password-file only requires a ':' in the content.
+ password_file_cfg = Element::create(string("hiddenp"));
+ client_cfg = Element::createMap();
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "can't find the user id part in password-file "
+ "'hiddenp' (:0:0)");
+
+ // User context must be a map.
+ password_cfg = Element::create(string("bar"));
+ ctx = Element::createList();
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("user-context", ctx);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user-context must be a map (:0:0)");
+
+ // Check a working not empty config.
+ ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("user-context", ctx);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ runToElementTest<BasicHttpAuthConfig>(cfg, config);
+
+ // Check a working not empty config with files.
+ config.clear();
+ client_cfg = Element::createMap();
+ user_file_cfg = Element::create(string("hiddenu"));
+ client_cfg->set("user-file", user_file_cfg);
+ client_cfg->set("password-file", password_file_cfg);
+ clients_cfg = Element::createList();
+ clients_cfg->add(client_cfg);
+ cfg->set("clients", clients_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ runToElementTest<BasicHttpAuthConfig>(cfg, config);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/basic_auth_unittests.cc b/src/lib/http/tests/basic_auth_unittests.cc
new file mode 100644
index 0000000..1c2693d
--- /dev/null
+++ b/src/lib/http/tests/basic_auth_unittests.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2020 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 <http/basic_auth.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::http;
+
+namespace {
+
+// Test that user name with a colon is rejected.
+TEST(BasicHttpAuthTest, userColon) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_THROW(basic_auth.reset(new BasicHttpAuth("foo:bar", "")), BadValue);
+}
+
+// Test that secret without a colon is rejected.
+TEST(BasicHttpAuthTest, secretNoColon) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_THROW(basic_auth.reset(new BasicHttpAuth("foo-bar")), BadValue);
+}
+
+// Test that valid user and password work.
+TEST(BasicHttpAuthTest, user) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo", "bar")));
+ ASSERT_TRUE(basic_auth);
+ EXPECT_EQ("foo:bar", basic_auth->getSecret());
+ EXPECT_EQ("Zm9vOmJhcg==", basic_auth->getCredential());
+}
+
+// Test that valid secret work.
+TEST(BasicHttpAuthTest, secret) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo:bar")));
+ ASSERT_TRUE(basic_auth);
+ EXPECT_EQ("foo:bar", basic_auth->getSecret());
+ EXPECT_EQ("Zm9vOmJhcg==", basic_auth->getCredential());
+}
+
+// Test that secret is encoded in UTF-8.
+TEST(BasicHttpAuthTest, utf8) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo\n", "b\ar")));
+ ASSERT_TRUE(basic_auth);
+ EXPECT_EQ("foo\n:b\ar", basic_auth->getSecret());
+ EXPECT_EQ("Zm9vCjpiB3I=", basic_auth->getCredential());
+}
+
+// Test that a header context for basic HTTP authentication can be created.
+TEST(BasicHttpAuthTest, headerContext) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo", "bar")));
+ ASSERT_TRUE(basic_auth);
+ BasicAuthHttpHeaderContext ctx(*basic_auth);
+ EXPECT_EQ("Authorization", ctx.name_);
+ EXPECT_EQ("Basic Zm9vOmJhcg==", ctx.value_);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/client_mt_unittests.cc b/src/lib/http/tests/client_mt_unittests.cc
new file mode 100644
index 0000000..ef04c28
--- /dev/null
+++ b/src/lib/http/tests/client_mt_unittests.cc
@@ -0,0 +1,1042 @@
+// Copyright (C) 2021-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/listener.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <sstream>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Container request/response pair handled by a given thread.
+struct ClientRR {
+ /// @brief Thread id of the client thread handling the request as a string.
+ std::string thread_id_;
+
+ /// @brief HTTP request submitted by the client thread.
+ PostHttpRequestJsonPtr request_;
+
+ /// @brief HTTP response received by the client thread.
+ HttpResponseJsonPtr response_;
+};
+
+/// @brief Pointer to a ClientRR instance.
+typedef boost::shared_ptr<ClientRR> ClientRRPtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+///
+/// Creates a response to a request containing body content
+/// as follows:
+///
+/// ```
+/// { "sequence" : nnnn }
+/// ```
+///
+/// The response will include the sequence number of the request
+/// as well as the server port passed into the creator's constructor:
+///
+/// ```
+/// { "sequence": nnnn, "server-port": xxxx }
+/// ```
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+ /// @brief Constructor
+ ///
+ /// @param server_port integer value the server listens upon, it is
+ /// echoed back in responses as "server-port".
+ TestHttpResponseCreator(uint16_t server_port)
+ : server_port_(server_port) { }
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @param status_code status code to include in the response.
+ ///
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed OK).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ HttpResponseJsonPtr response(new HttpResponseJson(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// Generates a response which echoes the requests sequence
+ /// number as well as the creator's server port value. Responses
+ /// should appear as follows:
+ ///
+ /// ```
+ /// { "sequence" : nnnn }
+ /// ```
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ if (!request_json) {
+ return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
+ }
+
+ // Extract the sequence from the request.
+ ConstElementPtr sequence = request_json->getJsonElement("sequence");
+ if (!sequence) {
+ return (createStockHttpResponse(request, HttpStatusCode::BAD_REQUEST));
+ }
+
+ // Create the response.
+ HttpResponseJsonPtr response(new HttpResponseJson(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // Construct the body.
+ ElementPtr body = Element::createMap();
+ body->set("server-port", Element::create(server_port_));
+ body->set("sequence", sequence);
+
+ // Echo request body back in the response.
+ response->setBodyAsJson(body);
+
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Port upon which this creator's server is listening.
+ ///
+ /// The intent is to use the value to determine which server generated
+ /// a given response.
+ uint16_t server_port_;
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param server_port port upon with the server is listening. This
+ /// value will be included in responses such that each response
+ /// can be attributed to a specific server.
+ TestHttpResponseCreatorFactory(uint16_t server_port)
+ : server_port_(server_port) {};
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator(server_port_));
+ return (response_creator);
+ }
+
+ /// @brief Port upon which this factory's server is listening.
+ ///
+ /// The intent is to use the value to determine which server generated
+ /// a given response.
+ uint16_t server_port_;
+};
+
+/// @brief Test fixture class for testing threading modes of HTTP client.
+class MultiThreadingHttpClientTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ MultiThreadingHttpClientTest()
+ : io_service_(), client_(), listener_(), factory_(), listeners_(), factories_(),
+ test_timer_(io_service_), num_threads_(0), num_batches_(0), num_listeners_(0),
+ expected_requests_(0), num_in_progress_(0), num_finished_(0), paused_(false),
+ pause_cnt_(0) {
+ test_timer_.setup(std::bind(&MultiThreadingHttpClientTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ MultiThreadingMgr::instance().setMode(true);
+ }
+
+ /// @brief Destructor.
+ ~MultiThreadingHttpClientTest() {
+ // Stop the client.
+ if (client_) {
+ client_->stop();
+ }
+
+ // Stop all listeners.
+ for (const auto& listener : listeners_) {
+ listener->stop();
+ }
+
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Callback function to invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs the test's IOService until the desired number of requests
+ /// have been carried out or the test fails.
+ void runIOService(size_t request_limit) {
+ while (getRRCount() < request_limit) {
+ // Always call reset() before we call run();
+ io_service_.restart();
+
+ // Run until a client stops the service.
+ io_service_.run();
+ }
+ }
+
+ /// @brief Creates an HTTP request with JSON body.
+ ///
+ /// It includes a JSON parameter with a specified value.
+ ///
+ /// @param parameter_name JSON parameter to be included.
+ /// @param value JSON parameter value.
+ /// @param version HTTP version to be used. Default is HTTP/1.1.
+ template<typename ValueType>
+ PostHttpRequestJsonPtr createRequest(const std::string& parameter_name,
+ const ValueType& value,
+ const HttpVersion& version = HttpVersion(1, 1)) {
+ // Create POST request with JSON body.
+ PostHttpRequestJsonPtr request(new PostHttpRequestJson(HttpRequest::Method::HTTP_POST,
+ "/boo", version));
+ // Body is a map with a specified parameter included.
+ ElementPtr body = Element::createMap();
+ body->set(parameter_name, Element::create(value));
+ request->setBodyAsJson(body);
+ try {
+ request->finalize();
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "failed to create request: " << ex.what();
+ }
+
+ return (request);
+ }
+
+ /// @brief Test that worker threads are not permitted to change thread pool
+ /// state.
+ void testIllegalThreadPoolActions() {
+ ASSERT_THROW(client_->start(), MultiThreadingInvalidOperation);
+ ASSERT_THROW(client_->pause(), MultiThreadingInvalidOperation);
+ ASSERT_THROW(client_->resume(), MultiThreadingInvalidOperation);
+ }
+
+ /// @brief Initiates a single HTTP request.
+ ///
+ /// Constructs an HTTP post whose body is a JSON map containing a
+ /// single integer element, "sequence".
+ ///
+ /// The request completion handler will block each requesting thread
+ /// until the number of in-progress threads reaches the number of
+ /// threads in the pool. At that point, the handler will unblock
+ /// until all threads have finished preparing their response and are
+ /// ready to return. The handler will then notify all pending threads
+ /// and invoke stop() on the test's main IO service thread.
+ ///
+ /// @param sequence value for the integer element, "sequence",
+ /// to send in the request.
+ void startRequest(int sequence, int port_offset = 0) {
+ // Create the URL on which the server can be reached.
+ std::stringstream ss;
+ ss << "http://" << SERVER_ADDRESS << ":" << (SERVER_PORT + port_offset);
+ Url url(ss.str());
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request_json = createRequest("sequence", sequence);
+ HttpResponseJsonPtr response_json = boost::make_shared<HttpResponseJson>();
+ ASSERT_NO_THROW(client_->asyncSendRequest(url, TlsContextPtr(),
+ request_json, response_json,
+ [this, request_json, response_json](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ // Bail on an error.
+ ASSERT_FALSE(ec) << "asyncSendRequest failed, ec: " << ec;
+
+ // Wait here until we have as many in progress as we have threads.
+ {
+ std::unique_lock<std::mutex> lck(test_mutex_);
+ ++num_in_progress_;
+ if (num_threads_ == 0 || num_in_progress_ == num_threads_) {
+ // Everybody has one, let's go.
+ num_finished_ = 0;
+ test_cv_.notify_all();
+ } else {
+ // I'm ready but others aren't wait here.
+ bool ret = test_cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_in_progress_ == num_threads_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to start work";
+ }
+ }
+ }
+
+ // If running on multiple threads, threads should be prohibited from
+ // changing the thread pool state.
+ if (num_threads_) {
+ testIllegalThreadPoolActions();
+ }
+
+ // Get stringified thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+
+ // Create the ClientRR.
+ ClientRRPtr clientRR(new ClientRR());
+ clientRR->thread_id_ = ss.str();
+ clientRR->request_ = request_json;
+ clientRR->response_ = response_json;
+
+ // Wait here until we have as many ready to finish as we have threads.
+ {
+ std::unique_lock<std::mutex> lck(test_mutex_);
+ ++num_finished_;
+ clientRRs_.push_back(clientRR);
+ if (num_threads_ == 0 || num_finished_ == num_threads_) {
+ // We're all done, notify the others and finish.
+ num_in_progress_ = 0;
+ test_cv_.notify_all();
+ // Stop the test's IOService.
+ io_service_.stop();
+ } else {
+ // I'm done but others aren't wait here.
+ bool ret = test_cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_finished_ == num_threads_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to finish work";
+ }
+ }
+ }
+ }));
+ }
+
+ /// @brief Initiates a single HTTP request.
+ ///
+ /// Constructs an HTTP post whose body is a JSON map containing a
+ /// single integer element, "sequence".
+ ///
+ /// The request completion handler simply constructs the response,
+ /// and adds it the list of completed request/responses. If the
+ /// number of completed requests has reached the expected number
+ /// it stops the test IOService.
+ ///
+ /// @param sequence value for the integer element, "sequence",
+ /// to send in the request.
+ void startRequestSimple(int sequence, int port_offset = 0) {
+ // Create the URL on which the server can be reached.
+ std::stringstream ss;
+ ss << "http://" << SERVER_ADDRESS << ":" << (SERVER_PORT + port_offset);
+ Url url(ss.str());
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request_json = createRequest("sequence", sequence);
+ HttpResponseJsonPtr response_json = boost::make_shared<HttpResponseJson>();
+ ASSERT_NO_THROW(client_->asyncSendRequest(url, TlsContextPtr(),
+ request_json, response_json,
+ [this, request_json, response_json](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ // Bail on an error.
+ ASSERT_FALSE(ec) << "asyncSendRequest failed, ec: " << ec;
+
+ // Get stringified thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+
+ // Create the ClientRR.
+ ClientRRPtr clientRR(new ClientRR());
+ clientRR->thread_id_ = ss.str();
+ clientRR->request_ = request_json;
+ clientRR->response_ = response_json;
+
+ {
+ std::unique_lock<std::mutex> lck(test_mutex_);
+ clientRRs_.push_back(clientRR);
+ ++num_finished_;
+ if ((num_finished_ >= expected_requests_) && !io_service_.stopped()) {
+ io_service_.stop();
+ }
+ }
+
+ }));
+ }
+
+ /// @brief Carries out HTTP requests via HttpClient to HTTP listener(s).
+ ///
+ /// This function creates one HttpClient with the given number
+ /// of threads and then the given number of HttpListeners. It then
+ /// initiates the given number of request batches where each batch
+ /// contains one request per thread per listener.
+ ///
+ /// Then it iteratively runs the test's IOService until all
+ /// the requests have been responded to, an error occurs, or the
+ /// test times out.
+ ///
+ /// Each request carries a single integer element, "sequence", which
+ /// uniquely identifies the request. Each response is expected to
+ /// contain this value echoed back along with the listener's server
+ /// port number. Thus each response can be matched to it's request
+ /// and to the listener that handled the request.
+ ///
+ /// After all requests have been conducted, the function verifies
+ /// that:
+ ///
+ /// 1. The number of requests conducted is correct
+ /// 2. The sequence numbers in request-response pairs match
+ /// 3. Each client thread handled the same number of requests
+ /// 4. Each listener handled the same number of requests
+ ///
+ /// @param num_threads number of threads the HttpClient should use.
+ /// A value of 0 puts the HttpClient in single-threaded mode.
+ /// @param num_batches number of batches of requests that should be
+ /// conducted.
+ /// @param num_listeners number of HttpListeners to create. Defaults
+ /// to 1.
+ void threadRequestAndReceive(size_t num_threads, size_t num_batches,
+ size_t num_listeners = 1) {
+ ASSERT_TRUE(num_batches);
+ ASSERT_TRUE(num_listeners);
+ num_threads_ = num_threads;
+ num_batches_ = num_batches;
+ num_listeners_ = num_listeners;
+
+ // Client in ST is, in effect, 1 thread.
+ size_t effective_threads = (num_threads_ == 0 ? 1 : num_threads_);
+
+ // Calculate the expected number of requests.
+ expected_requests_ = (num_batches_ * num_listeners_ * effective_threads);
+
+ for (auto i = 0; i < num_listeners_; ++i) {
+ // Make a factory
+ HttpResponseCreatorFactoryPtr factory(new TestHttpResponseCreatorFactory(SERVER_PORT + i));
+ factories_.push_back(factory);
+
+ // Need to create a Listener on
+ HttpListenerPtr listener(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS), (SERVER_PORT + i),
+ TlsContextPtr(), factory,
+ HttpListener::RequestTimeout(10000),
+ HttpListener::IdleTimeout(10000)));
+ listeners_.push_back(listener);
+
+ // Start the server.
+ ASSERT_NO_THROW(listener->start());
+ }
+
+ // Create an MT client with num_threads
+ ASSERT_NO_THROW_LOG(client_.reset(new HttpClient(io_service_,
+ num_threads ? true : false,
+ num_threads, true)));
+ ASSERT_TRUE(client_);
+
+ if (num_threads_ == 0) {
+ // If we single-threaded client should not have it's own IOService.
+ ASSERT_FALSE(client_->getThreadIOService());
+ } else {
+ // If we multi-threaded client should have it's own IOService.
+ ASSERT_TRUE(client_->getThreadIOService());
+ }
+
+ // Start the requisite number of requests:
+ // batch * listeners * threads.
+ int sequence = 0;
+ for (auto b = 0; b < num_batches; ++b) {
+ for (auto l = 0; l < num_listeners_; ++l) {
+ for (auto t = 0; t < effective_threads; ++t) {
+ startRequest(++sequence, l);
+ }
+ }
+ }
+
+ client_->start();
+
+ // Verify the pool size and number of threads are as expected.
+ ASSERT_EQ(client_->getThreadPoolSize(), num_threads);
+ ASSERT_EQ(client_->getThreadCount(), num_threads);
+
+ // Loop until the clients are done, an error occurs, or the time runs out.
+ runIOService(expected_requests_);
+
+ // Client should stop without issue.
+ ASSERT_NO_THROW(client_->stop());
+
+ // Listeners should stop without issue.
+ for (const auto& listener : listeners_) {
+ ASSERT_NO_THROW(listener->stop());
+ }
+
+ // We should have a response for each request.
+ ASSERT_EQ(getRRCount(), expected_requests_);
+
+ // Create a map to track number of responses for each client thread.
+ std::map<std::string, int> responses_per_thread;
+
+ // Create a map to track number of responses for each listener port.
+ std::map<uint16_t, int> responses_per_listener;
+
+ // Get the stringified thread-id of the test's main thread.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ std::string main_thread_id = ss.str();
+
+ // Iterate over the client request/response pairs.
+ for (auto const& clientRR : clientRRs_) {
+ // Make sure it's whole.
+ ASSERT_FALSE(clientRR->thread_id_.empty());
+ ASSERT_TRUE(clientRR->request_);
+ ASSERT_TRUE(clientRR->response_);
+
+ // Request should contain an integer sequence number.
+ int request_sequence;
+ ConstElementPtr sequence = clientRR->request_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(request_sequence = sequence->intValue());
+
+ // Response should contain an integer sequence number.
+ int response_sequence;
+ sequence = clientRR->response_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(response_sequence = sequence->intValue());
+
+ // Request and Response sequence numbers should match.
+ ASSERT_EQ(request_sequence, response_sequence);
+
+ ConstElementPtr server_port_elem = clientRR->response_->getJsonElement("server-port");
+ ASSERT_TRUE(server_port_elem);
+ uint16_t server_port = server_port_elem->intValue();
+
+ if (num_threads_ == 0) {
+ // For ST mode thread id should always be the main thread.
+ ASSERT_EQ(clientRR->thread_id_, main_thread_id);
+ } else {
+ // For MT mode the thread id should never be the main thread.
+ ASSERT_NE(clientRR->thread_id_, main_thread_id);
+ }
+
+ // Bump the response count for the given client thread-id.
+ auto rit = responses_per_thread.find(clientRR->thread_id_);
+ if (rit != responses_per_thread.end()) {
+ responses_per_thread[clientRR->thread_id_] = rit->second + 1;
+ } else {
+ responses_per_thread[clientRR->thread_id_] = 1;
+ }
+
+ // Bump the response count for the given server port.
+ auto lit = responses_per_listener.find(server_port);
+ if (lit != responses_per_listener.end()) {
+ responses_per_listener[server_port] = lit->second + 1;
+ } else {
+ responses_per_listener[server_port] = 1;
+ }
+ }
+
+ // Make sure that all client threads received responses.
+ ASSERT_EQ(responses_per_thread.size(), effective_threads);
+
+ // Make sure that each client thread received the same number of responses.
+ for (auto const& it : responses_per_thread) {
+ EXPECT_EQ(it.second, (num_batches_ * num_listeners_))
+ << "thread-id: " << it.first
+ << ", responses: " << it.second << std::endl;
+ }
+
+ // Make sure that all listeners generated responses.
+ ASSERT_EQ(responses_per_listener.size(), num_listeners_);
+
+ // Make sure Each listener generated the same number of responses.
+ for (auto const& it : responses_per_listener) {
+ EXPECT_EQ(it.second, (num_batches_ * effective_threads))
+ << "server-port: " << it.first
+ << ", responses: " << it.second << std::endl;
+ }
+ }
+
+ /// @brief Verifies the client can be paused and resumed repeatedly
+ /// while doing multi-threaded work.
+ ///
+ /// @param num_threads number of threads the HttpClient should use.
+ /// Must be greater than zero, this test does not make sense for a
+ /// single threaded client.
+ /// @param num_batches number of batches of requests that should be
+ /// conducted.
+ /// @param num_listeners number of HttpListeners to create.
+ /// @param num_pauses number of pauses to conduct.
+ void workPauseResumeShutdown(size_t num_threads, size_t num_batches,
+ size_t num_listeners, size_t num_pauses) {
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_batches);
+ ASSERT_TRUE(num_listeners);
+ num_threads_ = num_threads;
+ num_batches_ = num_batches;
+ num_listeners_ = num_listeners;
+
+ // Calculate the total expected number of requests.
+ size_t total_requests = (num_batches_ * num_listeners_ * num_threads_);
+
+ // Create the listeners.
+ for (auto i = 0; i < num_listeners_; ++i) {
+ // Make a factory
+ HttpResponseCreatorFactoryPtr factory(new TestHttpResponseCreatorFactory(SERVER_PORT + i));
+ factories_.push_back(factory);
+
+ // Need to create a Listener on
+ HttpListenerPtr listener(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS), (SERVER_PORT + i),
+ TlsContextPtr(), factory,
+ HttpListener::RequestTimeout(10000),
+ HttpListener::IdleTimeout(10000)));
+ listeners_.push_back(listener);
+
+ // Start the server.
+ ASSERT_NO_THROW(listener->start());
+ }
+
+ // Create an instant start, MT client with num_threads
+ ASSERT_NO_THROW_LOG(client_.reset(new HttpClient(io_service_, true, num_threads, true)));
+ ASSERT_TRUE(client_);
+
+ // Start the requisite number of requests:
+ // batch * listeners * threads.
+ int sequence = 0;
+ for (auto b = 0; b < num_batches; ++b) {
+ for (auto l = 0; l < num_listeners_; ++l) {
+ for (auto t = 0; t < num_threads_; ++t) {
+ startRequestSimple(++sequence, l);
+ }
+ }
+ }
+
+ client_->start();
+
+ // Client should be running. Check convenience functions.
+ ASSERT_TRUE(client_->isRunning());
+ ASSERT_FALSE(client_->isPaused());
+ ASSERT_FALSE(client_->isStopped());
+
+ // Verify the pool size and number of threads are as expected.
+ ASSERT_EQ(client_->getThreadPoolSize(), num_threads);
+ ASSERT_EQ(client_->getThreadCount(), num_threads);
+
+ size_t rr_count = 0;
+ while (rr_count < total_requests) {
+ size_t request_limit = (pause_cnt_ < num_pauses ?
+ (rr_count + ((total_requests - rr_count) / num_pauses))
+ : total_requests);
+
+ // Run test IOService until we hit the limit.
+ runIOService(request_limit);
+
+ // If we've done all our pauses we should be through.
+ if (pause_cnt_ == num_pauses) {
+ break;
+ }
+
+ // Pause the client.
+ ASSERT_NO_THROW(client_->pause());
+ ASSERT_TRUE(client_->isPaused());
+ ++pause_cnt_;
+
+ // Check our progress.
+ rr_count = getRRCount();
+ ASSERT_GE(rr_count, request_limit);
+
+ // Resume the client.
+ ASSERT_NO_THROW(client_->resume());
+ ASSERT_TRUE(client_->isRunning());
+ }
+
+ // Client should stop without issue.
+ ASSERT_NO_THROW(client_->stop());
+ ASSERT_TRUE(client_->isStopped());
+
+ // We should have finished all our requests.
+ ASSERT_EQ(getRRCount(), total_requests);
+
+ // Stopping again should be harmless.
+ ASSERT_NO_THROW(client_->stop());
+
+ // Listeners should stop without issue.
+ for (const auto& listener : listeners_) {
+ ASSERT_NO_THROW(listener->stop());
+ }
+
+ // Get the stringified thread-id of the test's main thread.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ std::string main_thread_id = ss.str();
+
+ // Tracks the number for requests fulfilled by main thread.
+ size_t worked_by_main = 0;
+
+ // Iterate over the client request/response pairs.
+ for (auto const& clientRR : clientRRs_) {
+ // Make sure it's whole.
+ ASSERT_FALSE(clientRR->thread_id_.empty());
+ ASSERT_TRUE(clientRR->request_);
+ ASSERT_TRUE(clientRR->response_);
+
+ // Request should contain an integer sequence number.
+ int request_sequence;
+ ConstElementPtr sequence = clientRR->request_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(request_sequence = sequence->intValue());
+
+ // Response should contain an integer sequence number.
+ int response_sequence;
+ sequence = clientRR->response_->getJsonElement("sequence");
+ ASSERT_TRUE(sequence);
+ ASSERT_NO_THROW(response_sequence = sequence->intValue());
+
+ // Request and Response sequence numbers should match.
+ ASSERT_EQ(request_sequence, response_sequence);
+
+ ConstElementPtr server_port_elem = clientRR->response_->getJsonElement("server-port");
+ ASSERT_TRUE(server_port_elem);
+
+ // Track how many requests were completed by the main thread.
+ // These can occur when pausing calls IOService::poll.
+ if (clientRR->thread_id_ == main_thread_id) {
+ ++worked_by_main;
+ }
+ }
+
+ // Make sure the majority of the requests were worked by
+ // worker threads. In theory, the number of calls to poll
+ // times the number of threads is the limit for responses
+ // built by the main thread.
+ ASSERT_LE(worked_by_main, num_pauses * num_threads);
+ }
+
+ /// @brief Fetch the number of completed requests.
+ ///
+ /// @return number of completed requests.
+ size_t getRRCount() {
+ std::lock_guard<std::mutex> lck(test_mutex_);
+ return (clientRRs_.size());
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Instance of the client used in the tests.
+ HttpClientPtr client_;
+
+ /// @brief Instance of the listener used in the tests.
+ HttpListenerPtr listener_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief List of listeners.
+ std::vector<HttpListenerPtr> listeners_;
+
+ /// @brief List of response factories.
+ std::vector<HttpResponseCreatorFactoryPtr> factories_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Number of threads HttpClient should use.
+ size_t num_threads_;
+
+ /// @brief Number of request batches to conduct.
+ size_t num_batches_;
+
+ /// @brief Number of listeners to start.
+ size_t num_listeners_;
+
+ /// @brief Number of expected requests to carry out.
+ size_t expected_requests_;
+
+ /// @brief Number of requests that are in progress.
+ size_t num_in_progress_;
+
+ /// @brief Number of requests that have been completed.
+ size_t num_finished_;
+
+ /// @brief a List of client request-response pairs.
+ std::vector<ClientRRPtr> clientRRs_;
+
+ /// @brief Mutex for locking.
+ std::mutex test_mutex_;
+
+ /// @brief Condition variable used to make client threads wait
+ /// until number of in-progress requests reaches the number
+ /// of client requests.
+ std::condition_variable test_cv_;
+
+ /// @brief Indicates if client threads are currently "paused".
+ bool paused_;
+
+ /// @brief Number of times client has been paused during the test.
+ size_t pause_cnt_;
+};
+
+// Verifies we can construct and destruct, in both single
+// and multi-threaded modes.
+TEST_F(MultiThreadingHttpClientTest, basics) {
+ HttpClientPtr client;
+
+ // Value of 0 for thread_pool_size means single-threaded.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, false)));
+ ASSERT_TRUE(client);
+
+ ASSERT_FALSE(client->getThreadIOService());
+ ASSERT_EQ(client->getThreadPoolSize(), 0);
+ ASSERT_EQ(client->getThreadCount(), 0);
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+
+ // Non-zero thread-pool-size means multi-threaded mode, should throw.
+ ASSERT_THROW_MSG(client.reset(new HttpClient(io_service_, false, 1)), InvalidOperation,
+ "HttpClient thread_pool_size must be zero "
+ "when Kea core multi-threading is disabled");
+ ASSERT_FALSE(client);
+
+ // Multi-threaded construction should work now.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, true, 3)));
+ ASSERT_TRUE(client);
+
+ // Verify that it has an internal IOService and that thread pool size
+ // and thread count match.
+ ASSERT_TRUE(client->getThreadIOService());
+ EXPECT_FALSE(client->getThreadIOService()->stopped());
+ ASSERT_EQ(client->getThreadPoolSize(), 3);
+ ASSERT_EQ(client->getThreadCount(), 3);
+
+ // Check convenience functions.
+ ASSERT_TRUE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_FALSE(client->isStopped());
+
+ // Verify stop doesn't throw.
+ ASSERT_NO_THROW_LOG(client->stop());
+
+ // Verify we're stopped.
+ ASSERT_TRUE(client->getThreadIOService());
+ EXPECT_TRUE(client->getThreadIOService()->stopped());
+ ASSERT_EQ(client->getThreadPoolSize(), 3);
+ ASSERT_EQ(client->getThreadCount(), 0);
+
+ // Check convenience functions.
+ ASSERT_FALSE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_TRUE(client->isStopped());
+
+ // Verify a second call to stop() doesn't throw.
+ ASSERT_NO_THROW_LOG(client->stop());
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+
+ // Create another multi-threaded instance.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, true, 3)));
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+}
+
+// Verifies we can construct with deferred start.
+TEST_F(MultiThreadingHttpClientTest, deferredStart) {
+ HttpClientPtr client;
+ size_t thread_pool_size = 3;
+
+ // Create MT client with deferred start.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, true, thread_pool_size, true)));
+ ASSERT_TRUE(client);
+
+ // Client should be STOPPED, with no threads.
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_EQ(client->getThreadPoolSize(), thread_pool_size);
+ ASSERT_EQ(client->getThreadCount(), 0);
+
+ // Check convenience functions.
+ ASSERT_FALSE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_TRUE(client->isStopped());
+
+ // We should be able to start it.
+ ASSERT_NO_THROW(client->start());
+
+ // Verify we have threads and run state is RUNNING.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_FALSE(client->getThreadIOService()->stopped());
+
+ // Check convenience functions.
+ ASSERT_TRUE(client->isRunning());
+ ASSERT_FALSE(client->isPaused());
+ ASSERT_FALSE(client->isStopped());
+
+ // Second call to start should be harmless.
+ ASSERT_NO_THROW_LOG(client->start());
+
+ // Verify we didn't break it.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->isRunning());
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+}
+
+// Verifies we can restart after stop.
+TEST_F(MultiThreadingHttpClientTest, restartAfterStop) {
+ HttpClientPtr client;
+ size_t thread_pool_size = 3;
+
+ // Create MT client with instant start.
+ ASSERT_NO_THROW_LOG(client.reset(new HttpClient(io_service_, true, thread_pool_size)));
+ ASSERT_TRUE(client);
+
+ // Verify we're started.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_FALSE(client->getThreadIOService()->stopped());
+ ASSERT_TRUE(client->isRunning());
+
+ // Stop should succeed.
+ ASSERT_NO_THROW_LOG(client->stop());
+
+ // Verify we're stopped.
+ ASSERT_EQ(client->getThreadCount(), 0);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_TRUE(client->getThreadIOService()->stopped());
+ ASSERT_TRUE(client->isStopped());
+
+ // Starting again should succeed.
+ ASSERT_NO_THROW_LOG(client->start());
+
+ // Verify we didn't break it.
+ ASSERT_EQ(client->getThreadCount(), 3);
+ ASSERT_TRUE(client->getThreadIOService());
+ ASSERT_FALSE(client->getThreadIOService()->stopped());
+ ASSERT_TRUE(client->isRunning());
+
+ // Make sure destruction doesn't throw.
+ ASSERT_NO_THROW_LOG(client.reset());
+}
+
+// Now we'll run some permutations of the number of client threads,
+// requests, and listeners.
+
+// Single-threaded, three batches, one listener.
+TEST_F(MultiThreadingHttpClientTest, zeroByThreeByOne) {
+ size_t num_threads = 0; // Zero threads = ST mode.
+ size_t num_batches = 3;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Single-threaded, three batches, three listeners.
+TEST_F(MultiThreadingHttpClientTest, zeroByThreeByThree) {
+ size_t num_threads = 0; // Zero threads = ST mode.
+ size_t num_batches = 3;
+ size_t num_listeners = 3;
+ threadRequestAndReceive(num_threads, num_batches, num_listeners);
+}
+
+// Multi-threaded with one thread, three batches, one listener
+TEST_F(MultiThreadingHttpClientTest, oneByThreeByOne) {
+ size_t num_threads = 1;
+ size_t num_batches = 3;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Multi-threaded with three threads, three batches, one listener
+TEST_F(MultiThreadingHttpClientTest, threeByThreeByOne) {
+ size_t num_threads = 3;
+ size_t num_batches = 3;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Multi-threaded with three threads, nine batches, one listener
+TEST_F(MultiThreadingHttpClientTest, threeByNineByOne) {
+ size_t num_threads = 3;
+ size_t num_batches = 9;
+ threadRequestAndReceive(num_threads, num_batches);
+}
+
+// Multi-threaded with two threads, four batches, two listeners
+TEST_F(MultiThreadingHttpClientTest, twoByFourByTwo) {
+ size_t num_threads = 2;
+ size_t num_batches = 4;
+ size_t num_listeners = 2;
+ threadRequestAndReceive(num_threads, num_batches, num_listeners);
+}
+
+// Multi-threaded with four threads, four batches, two listeners
+TEST_F(MultiThreadingHttpClientTest, fourByFourByTwo) {
+ size_t num_threads = 4;
+ size_t num_batches = 4;
+ size_t num_listeners = 2;
+ threadRequestAndReceive(num_threads, num_batches, num_listeners);
+}
+
+// Verifies that we can cleanly pause, resume, and shutdown while doing
+// multi-threaded work.
+TEST_F(MultiThreadingHttpClientTest, workPauseResumeShutdown) {
+ size_t num_threads = 4;
+ size_t num_batches = 4;
+ size_t num_listeners = 4;
+ size_t num_pauses = 3;
+ workPauseResumeShutdown(num_threads, num_batches, num_listeners, num_pauses);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/connection_pool_unittests.cc b/src/lib/http/tests/connection_pool_unittests.cc
new file mode 100644
index 0000000..b8337c3
--- /dev/null
+++ b/src/lib/http/tests/connection_pool_unittests.cc
@@ -0,0 +1,270 @@
+// Copyright (C) 2017-2021 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <http/http_acceptor.h>
+#include <http/connection.h>
+#include <http/connection_pool.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Request timeout used in tests.
+const long CONN_REQUEST_TIMEOUT = 1000;
+
+/// @brief Idle connection timeout used in tests.
+const long CONN_IDLE_TIMEOUT = 1000;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // The simplest thing is to create a response with no content.
+ // We don't need content to test our class.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ return (response);
+ }
+};
+
+/// @brief Derivation of @ref HttpConnectionPool exposing protected member.
+class TestHttpConnectionPool : public HttpConnectionPool {
+public:
+
+ using HttpConnectionPool::connections_;
+
+ /// @brief Checks if specified connection belongs to the pool.
+ bool hasConnection(const HttpConnectionPtr& conn) const {
+ return (std::find(connections_.begin(), connections_.end(), conn)
+ != connections_.end());
+ }
+
+};
+
+/// @brief Test fixture class for @ref HttpConnectionPool.
+class HttpConnectionPoolTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ HttpConnectionPoolTest()
+ : io_service_(),
+ acceptor_(new HttpAcceptor(io_service_)),
+ connection_pool_(),
+ response_creator_(new TestHttpResponseCreator()) {
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ ~HttpConnectionPoolTest() {
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Verifies that connections can be added to the pool and removed.
+ void startStopTest() {
+ // Create two distinct connections.
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+ // The pool should be initially empty.
+ TestHttpConnectionPool pool;
+ ASSERT_TRUE(pool.connections_.empty());
+
+ // Start first connection and check that it has been added to the pool.
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_EQ(1, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn1));
+
+ // Start second connection and check that it also has been added.
+ ASSERT_NO_THROW(pool.start(conn2));
+ ASSERT_EQ(2, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn2));
+
+ // Stop first connection.
+ ASSERT_NO_THROW(pool.stop(conn1));
+ ASSERT_EQ(1, pool.connections_.size());
+ // Check that it has been removed but the second connection is still
+ // there.
+ ASSERT_EQ(0, pool.hasConnection(conn1));
+ ASSERT_EQ(1, pool.hasConnection(conn2));
+
+ // Remove second connection and verify.
+ ASSERT_NO_THROW(pool.stop(conn2));
+ EXPECT_TRUE(pool.connections_.empty());
+ }
+
+ /// @brief Verifies that all connections can be remove with a single call.
+ void stopAllTest() {
+ // Create two distinct connections.
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+ TestHttpConnectionPool pool;
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_NO_THROW(pool.start(conn2));
+
+ // There are two distinct connections in the pool.
+ ASSERT_EQ(2, pool.connections_.size());
+
+ // This should remove all connections.
+ ASSERT_NO_THROW(pool.stopAll());
+ EXPECT_TRUE(pool.connections_.empty());
+ }
+
+ /// @brief Verifies that stopping a non-existing connection is no-op.
+ void stopInvalidTest() {
+ HttpConnectionPtr conn1(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+
+ HttpConnectionPtr conn2(new HttpConnection(io_service_, acceptor_,
+ TlsContextPtr(),
+ connection_pool_,
+ response_creator_,
+ HttpAcceptorCallback(),
+ CONN_REQUEST_TIMEOUT,
+ CONN_IDLE_TIMEOUT));
+ TestHttpConnectionPool pool;
+ ASSERT_NO_THROW(pool.start(conn1));
+ ASSERT_NO_THROW(pool.stop(conn2));
+ ASSERT_EQ(1, pool.connections_.size());
+ ASSERT_EQ(1, pool.hasConnection(conn1));
+ }
+
+ IOService io_service_; ///< IO service.
+ HttpAcceptorPtr acceptor_; ///< Test acceptor.
+ HttpConnectionPool connection_pool_; ///< Test connection pool.
+ HttpResponseCreatorPtr response_creator_; ///< Test response creator.
+
+};
+
+// Verifies that connections can be added to the pool and removed.
+// with MultiThreading disabled.
+TEST_F(HttpConnectionPoolTest, startStopTest) {
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ startStopTest();
+}
+
+// Verifies that connections can be added to the pool and removed
+// with MultiThreading enabled.
+TEST_F(HttpConnectionPoolTest, startStopTestMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ startStopTest();
+}
+
+// Check that all connections can be remove with a single call.
+// with MultiThreading disabled.
+TEST_F(HttpConnectionPoolTest, stopAll) {
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ stopAllTest();
+}
+
+// Check that all connections can be remove with a single call
+// with MultiThreading enabled.
+TEST_F(HttpConnectionPoolTest, stopAllMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_TRUE(MultiThreadingMgr::instance().getMode());
+ stopAllTest();
+}
+
+// Check that stopping non-existing connection is no-op.
+// with MultiThreading disabled.
+TEST_F(HttpConnectionPoolTest, stopInvalid) {
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ stopInvalidTest();
+}
+
+// Check that stopping non-existing connection is no-op.
+// with MultiThreading enabled.
+TEST_F(HttpConnectionPoolTest, stopInvalidMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_TRUE(MultiThreadingMgr::instance().getMode());
+ stopInvalidTest();
+}
+
+}
diff --git a/src/lib/http/tests/date_time_unittests.cc b/src/lib/http/tests/date_time_unittests.cc
new file mode 100644
index 0000000..59d75b1
--- /dev/null
+++ b/src/lib/http/tests/date_time_unittests.cc
@@ -0,0 +1,190 @@
+// Copyright (C) 2016-2017 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 <http/date_time.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::gregorian;
+using namespace boost::posix_time;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpDateTime.
+class HttpDateTimeTest : public ::testing::Test {
+public:
+
+ /// @brief Checks time value against expected values.
+ ///
+ /// This method uses value of @ref date_time_ for the test.
+ ///
+ /// @param exp_day_of_week Expected day of week.
+ /// @param exp_day Expected day of month.
+ /// @param exp_month Expected month.
+ /// @param exp_year Expected year.
+ /// @param exp_hours Expected hour value.
+ /// @param exp_minutes Expected minutes value.
+ /// @param exp_seconds Expected seconds value.
+ void testDateTime(const unsigned short exp_day_of_week,
+ const unsigned short exp_day,
+ const unsigned short exp_month,
+ const unsigned short exp_year,
+ const long exp_hours,
+ const long exp_minutes,
+ const long exp_seconds) {
+ // Retrieve @c boost::posix_time::ptime value.
+ ptime as_ptime = date_time_.getPtime();
+ // Date is contained within this object.
+ date date_part = as_ptime.date();
+
+ // Verify weekday.
+ greg_weekday day_of_week = date_part.day_of_week();
+ EXPECT_EQ(exp_day_of_week, day_of_week.as_number());
+
+ // Verify day of month.
+ greg_day day = date_part.day();
+ EXPECT_EQ(exp_day, day.as_number());
+
+ // Verify month.
+ greg_month month = date_part.month();
+ EXPECT_EQ(exp_month, month.as_number());
+
+ // Verify year.
+ greg_year year = date_part.year();
+ EXPECT_EQ(exp_year, static_cast<unsigned short>(year));
+
+ // Retrieve time of the day and verify hour, minute and second.
+ time_duration time_of_day = as_ptime.time_of_day();
+ EXPECT_EQ(exp_hours, time_of_day.hours());
+ EXPECT_EQ(exp_minutes, time_of_day.minutes());
+ EXPECT_EQ(exp_seconds, time_of_day.seconds());
+ }
+
+ /// @brief Date/time value which should be set by the tests.
+ HttpDateTime date_time_;
+
+};
+
+// Test formatting as specified in RFC 1123.
+TEST_F(HttpDateTimeTest, rfc1123Format) {
+ date gdate(greg_year(2002), greg_month(1), greg_day(20));
+ time_duration tm(23, 59, 59, 0);
+ ptime t = ptime(gdate, tm);
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.rfc1123Format());
+ EXPECT_EQ("Sun, 20 Jan 2002 23:59:59 GMT", formatted);
+}
+
+// Test formatting as specified in RFC 850.
+TEST_F(HttpDateTimeTest, rfc850Format) {
+ date gdate(greg_year(1994), greg_month(8), greg_day(6));
+ time_duration tm(11, 12, 13, 0);
+ ptime t = ptime(gdate, tm);
+
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.rfc850Format());
+ EXPECT_EQ("Saturday, 06-Aug-94 11:12:13 GMT", formatted);
+}
+
+// Test formatting as output of asctime().
+TEST_F(HttpDateTimeTest, asctimeFormat) {
+ date gdate(greg_year(1999), greg_month(11), greg_day(2));
+ time_duration tm(03, 57, 12, 0);
+ ptime t = ptime(gdate, tm);
+
+ HttpDateTime date_time(t);
+ std::string formatted;
+ ASSERT_NO_THROW(formatted = date_time.asctimeFormat());
+ EXPECT_EQ("Tue Nov 2 03:57:12 1999", formatted);
+}
+
+// Test parsing time in RFC 1123 format.
+TEST_F(HttpDateTimeTest, fromRfc1123) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45 GMT")
+ );
+ testDateTime(3, 21, 12, 2016, 18, 53, 45);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dex 2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 43 Dec 2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 18:53:45"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc1123("Wed, 21 Dec 2016 1853:45 GMT"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 850 format.
+TEST_F(HttpDateTimeTest, fromRfc850) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 18:53:45 GMT");
+ );
+ testDateTime(3, 21, 12, 2016, 18, 53, 45);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 55-Dec-16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dex-16 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-2016 18:53:45 GMT"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromRfc850("Wednesday, 21-Dec-16 1853:45 GMT"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in asctime() format.
+TEST_F(HttpDateTimeTest, fromRfcAsctime) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 2016");
+ );
+ testDateTime(3, 21, 12, 2016, 8, 49, 37);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dex 21 08:49:37 2016"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 55 08:49:37 2016"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:49:37 16"),
+ HttpTimeConversionError);
+ EXPECT_THROW(HttpDateTime::fromAsctime("Wed Dec 21 08:4937 2016"),
+ HttpTimeConversionError);
+}
+
+// Test parsing time in RFC 1123 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc1123) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Thu, 05 Jan 2017 09:15:06 GMT");
+ );
+ testDateTime(4, 5, 1, 2017, 9, 15, 06);
+}
+
+// Test parsing time in RFC 850 format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyRfc850) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Saturday, 18-Feb-17 01:02:10 GMT");
+ );
+ testDateTime(6, 18, 2, 2017, 1, 2, 10);
+}
+
+// Test parsing time in asctime() format using HttpDateTime::fromAny().
+TEST_F(HttpDateTimeTest, fromAnyAsctime) {
+ ASSERT_NO_THROW(
+ date_time_ = HttpDateTime::fromAny("Wed Mar 1 15:45:07 2017 GMT");
+ );
+ testDateTime(3, 1, 3, 2017, 15, 45, 7);
+}
+
+// Test that HttpDateTime::fromAny throws exception if unsupported format is
+// used.
+TEST_F(HttpDateTimeTest, fromAnyInvalidFormat) {
+ EXPECT_THROW(HttpDateTime::fromAsctime("20020131T235959"),
+ HttpTimeConversionError);
+}
+
+}
diff --git a/src/lib/http/tests/http_header_unittests.cc b/src/lib/http/tests/http_header_unittests.cc
new file mode 100644
index 0000000..df9d5bb
--- /dev/null
+++ b/src/lib/http/tests/http_header_unittests.cc
@@ -0,0 +1,54 @@
+// Copyright (C) 2017 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 <http/http_header.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::http;
+
+namespace {
+
+// Test that HTTP header can be created.
+TEST(HttpHeader, create) {
+ HttpHeader hdr("Content-Type", "application/json");
+ EXPECT_EQ("Content-Type", hdr.getName());
+ EXPECT_EQ("application/json", hdr.getValue());
+}
+
+// Test that the numeric value can be retrieved from a header and that
+// an exception is thrown if the header value is not a valid number.
+TEST(HttpHeader, getUint64Value) {
+ HttpHeader hdr64("Content-Length", "64");
+ EXPECT_EQ(64, hdr64.getUint64Value());
+
+ HttpHeader hdr_foo("Content-Length", "foo");
+ EXPECT_THROW(hdr_foo.getUint64Value(), isc::BadValue);
+}
+
+// Test that header name can be retrieved in lower case.
+TEST(HttpHeader, getLowerCaseName) {
+ HttpHeader hdr("ConnectioN", "Keep-Alive");
+ EXPECT_EQ("connection", hdr.getLowerCaseName());
+}
+
+// Test that header value can be retrieved in lower case.
+TEST(HttpHeader, getLowerCaseValue) {
+ HttpHeader hdr("Connection", "Keep-Alive");
+ EXPECT_EQ("keep-alive", hdr.getLowerCaseValue());
+}
+
+// Test that header value comparison is case insensitive.
+TEST(HttpHeader, equalsCaseInsensitive) {
+ HttpHeader hdr("Connection", "KeEp-ALIve");
+ EXPECT_TRUE(hdr.isValueEqual("keep-alive"));
+ EXPECT_TRUE(hdr.isValueEqual("KEEP-ALIVE"));
+ EXPECT_TRUE(hdr.isValueEqual("kEeP-AlIvE"));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/http/tests/post_request_json_unittests.cc b/src/lib/http/tests/post_request_json_unittests.cc
new file mode 100644
index 0000000..bb717cd
--- /dev/null
+++ b/src/lib/http/tests/post_request_json_unittests.cc
@@ -0,0 +1,197 @@
+// Copyright (C) 2016-2019 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 <cc/data.h>
+#include <http/http_types.h>
+#include <http/post_request_json.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+#include <map>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @ref PostHttpRequestJson.
+class PostHttpRequestJsonTest :
+ public HttpRequestTestBase<PostHttpRequestJson> {
+public:
+
+ /// @brief Constructor.
+ PostHttpRequestJsonTest()
+ : HttpRequestTestBase<PostHttpRequestJson>(),
+ json_body_("{ \"service\": \"dhcp4\", \"param1\": \"foo\" }") {
+ }
+
+ /// @brief Sets new JSON body for the HTTP request context.
+ ///
+ /// If the body parameter is empty, it will use the value of
+ /// @ref json_body_ member. Otherwise, it will assign the body
+ /// provided as parameter.
+ ///
+ /// @param body new body value.
+ void setBody(const std::string& body = "") {
+ request_->context()->body_ = body.empty() ? json_body_ : body;
+ }
+
+ /// @brief Default value of the JSON body.
+ std::string json_body_;
+};
+
+// This test verifies that PostHttpRequestJson class only accepts
+// POST messages.
+TEST_F(PostHttpRequestJsonTest, requiredPost) {
+ // Use a GET method that is not supported.
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // Now use POST. It should be accepted.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header equal to "application/json".
+TEST_F(PostHttpRequestJsonTest, requireContentTypeJson) {
+ // Specify "Content-Type" other than "application/json".
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // This time specify correct "Content-Type". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestJsonTest, requireContentLength) {
+ // "Content-Length" is not specified initially. It should fail.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // Specify "Content-Length". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+}
+
+// This test verifies that JSON body can be retrieved from the
+// HTTP request.
+TEST_F(PostHttpRequestJsonTest, getBodyAsJson) {
+ // Create HTTP POST request with JSON body.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ setBody();
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Try to retrieve pointer to the root element of the JSON body.
+ ConstElementPtr json = request_->getBodyAsJson();
+ ASSERT_TRUE(json);
+
+ // Iterate over JSON values and store them in a simple map.
+ std::map<std::string, std::string> config_values;
+ for (auto config_element = json->mapValue().begin();
+ config_element != json->mapValue().end();
+ ++config_element) {
+ ASSERT_FALSE(config_element->first.empty());
+ ASSERT_TRUE(config_element->second);
+ config_values[config_element->first] = config_element->second->stringValue();
+ }
+
+ // Verify the values.
+ EXPECT_EQ("dhcp4", config_values["service"]);
+ EXPECT_EQ("foo", config_values["param1"]);
+}
+
+// This test verifies that an attempt to parse/retrieve malformed
+// JSON structure will cause an exception.
+TEST_F(PostHttpRequestJsonTest, getBodyAsJsonMalformed) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ // No colon before 123.
+ setBody("{ \"command\" 123 }" );
+
+ EXPECT_THROW(request_->finalize(), HttpRequestJsonError);
+}
+
+// This test verifies that NULL pointer is returned when trying to
+// retrieve root element of the empty JSON structure.
+TEST_F(PostHttpRequestJsonTest, getEmptyJsonBody) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ ConstElementPtr json = request_->getBodyAsJson();
+ EXPECT_FALSE(json);
+}
+
+// This test verifies that the specific JSON element can be retrieved.
+TEST_F(PostHttpRequestJsonTest, getJsonElement) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+ setBody();
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ ConstElementPtr element;
+ ASSERT_NO_THROW(element = request_->getJsonElement("service"));
+ ASSERT_TRUE(element);
+ EXPECT_EQ("dhcp4", element->stringValue());
+
+ // An attempt to retrieve non-existing element should return NULL.
+ EXPECT_FALSE(request_->getJsonElement("bar"));
+}
+
+// This test verifies that it is possible to create client side request
+// containing JSON body.
+TEST_F(PostHttpRequestJsonTest, clientRequest) {
+ request_->setDirection(HttpMessage::OUTBOUND);
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "application/json");
+
+ ElementPtr json = Element::fromJSON(json_body_);
+ request_->setBodyAsJson(json);
+
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_->finalize());
+
+ std::ostringstream expected_request_text;
+ expected_request_text << "POST /isc/org HTTP/1.0\r\n"
+ "Content-Length: " << json->str().size() << "\r\n"
+ "Content-Type: application/json\r\n"
+ "\r\n"
+ << json->str();
+
+ EXPECT_EQ(expected_request_text.str(), request_->toString());
+}
+
+}
diff --git a/src/lib/http/tests/post_request_unittests.cc b/src/lib/http/tests/post_request_unittests.cc
new file mode 100644
index 0000000..18f2fda
--- /dev/null
+++ b/src/lib/http/tests/post_request_unittests.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2016-2019 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 <http/post_request.h>
+#include <http/tests/request_test.h>
+#include <gtest/gtest.h>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @ref PostHttpRequest.
+class PostHttpRequestTest : public HttpRequestTestBase<PostHttpRequest> {
+public:
+
+ /// @brief Constructor.
+ PostHttpRequestTest()
+ : HttpRequestTestBase<PostHttpRequest>(),
+ json_body_("{ \"service\": \"dhcp4\", \"param1\": \"foo\" }") {
+ }
+
+ /// @brief Default value of the JSON body.
+ std::string json_body_;
+};
+
+// This test verifies that PostHttpRequest class only accepts POST
+// messages.
+TEST_F(PostHttpRequestTest, requirePost) {
+ // Use a GET method that is not supported.
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // Now use POST. It should be accepted.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that PostHttpRequest requires "Content-Length"
+// header.
+TEST_F(PostHttpRequestTest, requireContentType) {
+ // No "Content-Type". It should fail.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // There is "Content-Type". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "text/html");
+
+ EXPECT_NO_THROW(request_->create());
+
+}
+
+// This test verifies that PostHttpRequest requires "Content-Type"
+// header.
+TEST_F(PostHttpRequestTest, requireContentLength) {
+ // No "Content-Length". It should fail.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ // There is "Content-Length". It should pass.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body_.length());
+ addHeaderToContext("Content-Type", "application/json");
+}
+
+}
diff --git a/src/lib/http/tests/request_parser_unittests.cc b/src/lib/http/tests/request_parser_unittests.cc
new file mode 100644
index 0000000..0756711
--- /dev/null
+++ b/src/lib/http/tests/request_parser_unittests.cc
@@ -0,0 +1,387 @@
+// Copyright (C) 2016-2019 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 <cc/data.h>
+#include <http/http_types.h>
+#include <http/request_parser.h>
+#include <http/post_request_json.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpRequestParser.
+class HttpRequestParserTest : public ::testing::Test {
+public:
+
+ /// @brief Creates HTTP request string.
+ ///
+ /// @param preamble A string including HTTP request's first line
+ /// and all headers except "Content-Length".
+ /// @param payload A string containing HTTP request payload.
+ std::string createRequestString(const std::string& preamble,
+ const std::string& payload) {
+ std::ostringstream s;
+ s << preamble;
+ s << "Content-Length: " << payload.length() << "\r\n\r\n"
+ << payload;
+ return (s.str());
+ }
+
+ /// @brief Parses the HTTP request and checks that parsing was
+ /// successful.
+ ///
+ /// @param http_req HTTP request string.
+ void doParse(const std::string& http_req) {
+ HttpRequestParser parser(request_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Tests that parsing fails when malformed HTTP request
+ /// is received.
+ ///
+ /// @param http_req HTTP request string.
+ void testInvalidHttpRequest(const std::string& http_req) {
+ HttpRequestParser parser(request_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ EXPECT_FALSE(parser.needData());
+ EXPECT_FALSE(parser.httpParseOk());
+ EXPECT_FALSE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Instance of the HttpRequest used by the unit tests.
+ HttpRequest request_;
+};
+
+// Test test verifies that an HTTP request including JSON body is parsed
+// successfully.
+TEST_F(HttpRequestParserTest, postHttpRequestWithJson) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ http_req = createRequestString(http_req, json);
+
+ // Create HTTP request which accepts POST method and JSON as a body.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Simulate receiving HTTP request in chunks.
+ for (size_t i = 0; i < http_req.size(); i += http_req.size() / 10) {
+ bool done = false;
+ // Get the size of the data chunk.
+ size_t chunk = http_req.size() / 10;
+ // When we're near the end of the data stream, the chunk length may
+ // vary.
+ if (i + chunk > http_req.size()) {
+ chunk = http_req.size() - i;
+ done = true;
+ }
+ // Feed the parser with a data chunk and parse it.
+ parser.postBuffer(&http_req[i], chunk);
+ parser.poll();
+ if (!done) {
+ ASSERT_TRUE(parser.needData());
+ }
+ }
+
+ // Parser should have parsed the request and should expect no more data.
+ ASSERT_FALSE(parser.needData());
+ // Parsing should be successful.
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Verify parsed headers etc.
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request.getMethod());
+ EXPECT_EQ("/foo/bar", request.getUri());
+ EXPECT_EQ("application/json", request.getHeaderValue("Content-Type"));
+ EXPECT_EQ(json.length(), request.getHeaderValueAsUint64("Content-Length"));
+ EXPECT_EQ(1, request.getHttpVersion().major_);
+ EXPECT_EQ(0, request.getHttpVersion().minor_);
+
+ // Try to retrieve values carried in JSON payload.
+ ConstElementPtr json_element;
+ ASSERT_NO_THROW(json_element = request.getJsonElement("service"));
+ EXPECT_EQ("dhcp4", json_element->stringValue());
+
+ ASSERT_NO_THROW(json_element = request.getJsonElement("command"));
+ EXPECT_EQ("shutdown", json_element->stringValue());
+}
+
+// This test verifies that extraneous data in the request will not cause
+// an error if "Content-Length" value refers to the length of the valid
+// part of the request.
+TEST_F(HttpRequestParserTest, extraneousDataInRequest) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ // Create valid request;
+ http_req = createRequestString(http_req, json);
+
+ // Add some garbage at the end.
+ http_req += "some stuff which, if parsed, will cause errors";
+
+ // Create HTTP request which accepts POST method and JSON as a body.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Feed the parser with the request containing some garbage at the end.
+ parser.postBuffer(&http_req[0], http_req.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ // The parser should only parse the valid part of the request as indicated
+ // by the Content-Length.
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Do another poll() to see if the parser will parse the garbage. We
+ // expect that it doesn't.
+ ASSERT_NO_THROW(parser.poll());
+ EXPECT_FALSE(parser.needData());
+ EXPECT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+}
+
+
+// This test verifies that LWS is parsed correctly. The LWS marks line breaks
+// in the HTTP header values.
+TEST_F(HttpRequestParserTest, getLWS) {
+ // "User-Agent" header contains line breaks with whitespaces in the new
+ // lines to mark continuation of the header value.
+ std::string http_req = "GET /foo/bar HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n"
+ "User-Agent: Kea/1.2 Command \r\n"
+ " Control \r\n"
+ "\tClient\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ // Verify parsed values.
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ("Kea/1.2 Command Control Client",
+ request_.getHeaderValue("User-Agent"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the HTTP request with no headers is
+// parsed correctly.
+TEST_F(HttpRequestParserTest, noHeaders) {
+ std::string http_req = "GET /foo/bar HTTP/1.1\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ // Verify the values.
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the HTTP method can be specified in lower
+// case.
+TEST_F(HttpRequestParserTest, getLowerCase) {
+ std::string http_req = "get /foo/bar HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that headers are case insensitive.
+TEST_F(HttpRequestParserTest, headersCaseInsensitive) {
+ std::string http_req = "get /foo/bar HTTP/1.1\r\n"
+ "Content-type: text/html\r\n"
+ "connection: keep-Alive\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeader("Content-Type")->getValue());
+ EXPECT_EQ("keep-alive", request_.getHeader("Connection")->getLowerCaseValue());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that other value of the HTTP version can be
+// specified in the request.
+TEST_F(HttpRequestParserTest, http20) {
+ std::string http_req = "get /foo/bar HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(2, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the header with no whitespace between the
+// colon and header value is accepted.
+TEST_F(HttpRequestParserTest, noHeaderWhitespace) {
+ std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+ "Content-Type:text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that the header value preceded with multiple
+// whitespaces is accepted.
+TEST_F(HttpRequestParserTest, multipleLeadingHeaderWhitespaces) {
+ std::string http_req = "get /foo/bar HTTP/1.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_.getMethod());
+ EXPECT_EQ("/foo/bar", request_.getUri());
+ EXPECT_EQ("text/html", request_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(0, request_.getHttpVersion().minor_);
+}
+
+// This test verifies that error is reported when unsupported HTTP
+// method is used.
+TEST_F(HttpRequestParserTest, unsupportedMethod) {
+ std::string http_req = "POSTX /foo/bar HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when URI contains
+// an invalid character.
+TEST_F(HttpRequestParserTest, invalidUri) {
+ std::string http_req = "POST /foo/\r HTTP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that the request containing a typo in the
+// HTTP version string causes parsing error.
+TEST_F(HttpRequestParserTest, invalidHTTPString) {
+ std::string http_req = "POST /foo/ HTLP/2.0\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when the HTTP version
+// string doesn't contain a slash character.
+TEST_F(HttpRequestParserTest, invalidHttpVersionNoSlash) {
+ std::string http_req = "POST /foo/ HTTP 1.1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP version string
+// doesn't contain the minor version number.
+TEST_F(HttpRequestParserTest, invalidHttpNoMinorVersion) {
+ std::string http_req = "POST /foo/ HTTP/1\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header name
+// contains an invalid character.
+TEST_F(HttpRequestParserTest, invalidHeaderName) {
+ std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+ "Content-;: text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that error is reported when HTTP header value
+// is not preceded with the colon character.
+TEST_F(HttpRequestParserTest, noColonInHttpHeader) {
+ std::string http_req = "POST /foo/ HTTP/1.1\r\n"
+ "Content-Type text/html\r\n\r\n";
+ testInvalidHttpRequest(http_req);
+}
+
+// This test verifies that the input buffer of the HTTP request can be
+// retrieved as text formatted for logging.
+TEST_F(HttpRequestParserTest, getBufferAsString) {
+ std::string http_req = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n";
+
+ // Create HTTP request.
+ PostHttpRequestJson request;
+
+ // Create a parser and make it use the request we created.
+ HttpRequestParser parser(request);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Insert data into the request.
+ ASSERT_NO_THROW(parser.postBuffer(&http_req[0], http_req.size()));
+
+ // limit = 0 means no limit
+ EXPECT_EQ(http_req, parser.getBufferAsString(0));
+
+ // large enough limit should not cause the truncation.
+ EXPECT_EQ(http_req, parser.getBufferAsString(1024));
+
+ // Only 3 characters requested. The request should be truncated.
+ EXPECT_EQ("POS.........\n(truncating HTTP message larger than 3 characters)\n",
+ parser.getBufferAsString(3));
+}
+
+TEST_F(HttpRequestParserTest, parseEmptyRequest) {
+ std::string http_req = "POST / HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "";
+
+ http_req = createRequestString(http_req, json);
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_req));
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request_.getMethod());
+ EXPECT_EQ("/", request_.getUri());
+ EXPECT_EQ("", request_.getBody());
+ EXPECT_EQ(1, request_.getHttpVersion().major_);
+ EXPECT_EQ(1, request_.getHttpVersion().minor_);
+}
+
+}
diff --git a/src/lib/http/tests/request_test.h b/src/lib/http/tests/request_test.h
new file mode 100644
index 0000000..f73b31f
--- /dev/null
+++ b/src/lib/http/tests/request_test.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2016-2019 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 HTTP_REQUEST_TEST_H
+#define HTTP_REQUEST_TEST_H
+
+#include <http/http_types.h>
+#include <http/request.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace http {
+namespace test {
+
+/// @brief Base test fixture class for testing @ref HttpRequest class and its
+/// derivations.
+///
+/// @tparam HttpRequestType Class under test.
+template<typename HttpRequestType>
+class HttpRequestTestBase : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates HTTP request to be used in unit tests.
+ HttpRequestTestBase()
+ : request_(new HttpRequestType()) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Does nothing.
+ virtual ~HttpRequestTestBase() {
+ }
+
+ /// @brief Initializes HTTP request context with basic information.
+ ///
+ /// It sets:
+ /// - HTTP method,
+ /// - URI,
+ /// - HTTP version number.
+ ///
+ /// @param method HTTP method as string.
+ /// @param uri URI.
+ /// @param version A pair of values of which the first is the major HTTP
+ /// version and the second is the minor HTTP version.
+ void setContextBasics(const std::string& method, const std::string& uri,
+ const HttpVersion& version) {
+ request_->context()->method_ = method;
+ request_->context()->uri_ = uri;
+ request_->context()->http_version_major_ = version.major_;
+ request_->context()->http_version_minor_ = version.minor_;
+ }
+
+ /// @brief Adds HTTP header to the context.
+ ///
+ /// @param header_name HTTP header name.
+ /// @param header_value HTTP header value. This value will be converted to
+ /// a string using @c boost::lexical_cast.
+ /// @tparam ValueType Header value type.
+ template<typename ValueType>
+ void addHeaderToContext(const std::string& header_name,
+ const ValueType& header_value) {
+ request_->context()->headers_.push_back(HttpHeaderContext(header_name, header_value));
+ }
+
+ /// @brief Instance of the @ref HttpRequest or its derivation.
+ boost::shared_ptr<HttpRequestType> request_;
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/tests/request_unittests.cc b/src/lib/http/tests/request_unittests.cc
new file mode 100644
index 0000000..2c18090
--- /dev/null
+++ b/src/lib/http/tests/request_unittests.cc
@@ -0,0 +1,422 @@
+// Copyright (C) 2016-2023 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 <http/request.h>
+#include <http/date_time.h>
+#include <http/http_header.h>
+#include <http/http_types.h>
+#include <http/tests/request_test.h>
+#include <boost/lexical_cast.hpp>
+#include <gtest/gtest.h>
+#include <utility>
+
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Test fixture class for @c HttpRequest class.
+class HttpRequestTest : public HttpRequestTestBase<HttpRequest> {
+public:
+
+ /// @brief Tests connection persistence for the given HTTP version
+ /// and header value.
+ ///
+ /// This method creates a dummy HTTP request and sets the specified
+ /// version and header. Next, it returns the value if @c isPersistent
+ /// method for this request. The unit test verifies this value for
+ /// correctness.
+ ///
+ /// @param http_version HTTP version.
+ /// @param http_header HTTP header to be included in the request. If
+ /// the header has an empty value, it is not included.
+ ///
+ /// @return true if request indicates that connection is to be
+ /// persistent.
+ bool isPersistent(const HttpVersion& http_version,
+ const HttpHeader& http_header = HttpHeader("Connection")) {
+ try {
+ // We need to add some JSON body.
+ std::string json_body = "{ \"param1\": \"foo\" }";
+
+ // Set method, path, version and content length.
+ setContextBasics("POST", "/isc/org", http_version);
+ addHeaderToContext("Content-Length", json_body.length());
+
+ // If additional header has been specified (typically "Connection"),
+ // include it.
+ if (!http_header.getValue().empty()) {
+ addHeaderToContext(http_header.getName(), http_header.getValue());
+ }
+ // Attach JSON body.
+ request_->context()->body_ = json_body;
+ request_->create();
+
+ } catch (...) {
+ ADD_FAILURE() << "failed to create HTTP request while testing"
+ " connection persistence";
+ }
+
+ return (request_->isPersistent());
+ }
+
+};
+
+// This test verifies that a minimal request can be created.
+TEST_F(HttpRequestTest, minimal) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ ASSERT_NO_THROW(request_->create());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(1, request_->getHttpVersion().minor_);
+ EXPECT_TRUE(request_->getRemote().empty());
+ request_->setRemote("127.0.0.1");
+ EXPECT_EQ("127.0.0.1", request_->getRemote());
+
+ EXPECT_THROW(request_->getHeaderValue("Content-Length"),
+ HttpMessageNonExistingHeader);
+}
+
+// This test verifies that empty Host header is included in the
+// request if it is not explicitly specified.
+TEST_F(HttpRequestTest, hostHeaderDefault) {
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 0))));
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(0, request_->getHttpVersion().minor_);
+
+ std::string host_hdr;
+ ASSERT_NO_THROW(host_hdr = request_->getHeaderValue("Host"));
+ EXPECT_TRUE(host_hdr.empty());
+}
+
+// This test verifies that it is possible to explicitly specify a
+// Host header value while creating a request.
+TEST_F(HttpRequestTest, hostHeaderCustom) {
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 1),
+ HostHttpHeader("www.example.org"))));
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_GET, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(1, request_->getHttpVersion().minor_);
+
+ std::string host_hdr;
+ ASSERT_NO_THROW(host_hdr = request_->getHeaderValue("Host"));
+ EXPECT_EQ("www.example.org", host_hdr);
+}
+
+// This test verifies that headers can be included in a request.
+TEST_F(HttpRequestTest, includeHeaders) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", "1024");
+ addHeaderToContext("Content-Type", "application/json");
+ ASSERT_NO_THROW(request_->create());
+
+ EXPECT_EQ(HttpRequest::Method::HTTP_POST, request_->getMethod());
+ EXPECT_EQ("/isc/org", request_->getUri());
+ EXPECT_EQ(1, request_->getHttpVersion().major_);
+ EXPECT_EQ(0, request_->getHttpVersion().minor_);
+
+ std::string content_type;
+ ASSERT_NO_THROW(content_type = request_->getHeaderValue("Content-Type"));
+ EXPECT_EQ("application/json", content_type);
+
+ uint64_t content_length = 0;
+ ASSERT_NO_THROW(
+ content_length = request_->getHeaderValueAsUint64("Content-Length")
+ );
+ EXPECT_EQ(1024, content_length);
+}
+
+// This test verifies that it is possible to specify required
+// methods for the request and that an error is thrown if the
+// selected method doesn't match.
+TEST_F(HttpRequestTest, requiredMethods) {
+ request_->requireHttpMethod(HttpRequest::Method::HTTP_GET);
+ request_->requireHttpMethod(HttpRequest::Method::HTTP_POST);
+
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+
+ ASSERT_NO_THROW(request_->create());
+
+ request_->context()->method_ = "POST";
+ ASSERT_NO_THROW(request_->create());
+
+ request_->context()->method_ = "PUT";
+ EXPECT_THROW(request_->create(), HttpRequestError);
+}
+
+// This test verifies that it is possible to specify required
+// HTTP version for the request and that an error is thrown if
+// the selected HTTP version doesn't match.
+TEST_F(HttpRequestTest, requiredHttpVersion) {
+ request_->requireHttpVersion(HttpVersion(1, 0));
+ request_->requireHttpVersion(HttpVersion(1, 1));
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ EXPECT_NO_THROW(request_->create());
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 1));
+ EXPECT_NO_THROW(request_->create());
+
+ setContextBasics("POST", "/isc/org", HttpVersion(2, 0));
+ EXPECT_THROW(request_->create(), HttpRequestError);
+}
+
+// This test verifies that it is possible to specify required
+// HTTP headers for the request and that an error is thrown if
+// the required header is not included.
+TEST_F(HttpRequestTest, requiredHeader) {
+ request_->requireHeader("Content-Length");
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ addHeaderToContext("Content-Type", "application/json");
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ addHeaderToContext("Content-Length", "2048");
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that it is possible to specify required
+// HTTP header value for the request and that an error is thrown
+// if the value doesn't match.
+TEST_F(HttpRequestTest, requiredHeaderValue) {
+ request_->requireHeaderValue("Content-Type", "application/json");
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+
+ ASSERT_THROW(request_->create(), HttpRequestError);
+
+ addHeaderToContext("Content-Type", "application/json");
+
+ EXPECT_NO_THROW(request_->create());
+}
+
+// This test verifies that an error is thrown upon an attempt to
+// fetch request properties before the request is finalized.
+TEST_F(HttpRequestTest, notCreated) {
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Type", "text/html");
+ addHeaderToContext("Content-Length", "1024");
+
+ EXPECT_THROW(static_cast<void>(request_->getMethod()), HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getHttpVersion()),
+ HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getUri()), HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getHeaderValue("Content-Type")),
+ HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getHeaderValueAsUint64("Content-Length")),
+ HttpMessageError);
+ EXPECT_THROW(static_cast<void>(request_->getBody()), HttpMessageError);
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_NO_THROW(static_cast<void>(request_->getMethod()));
+ EXPECT_NO_THROW(static_cast<void>(request_->getHttpVersion()));
+ EXPECT_NO_THROW(static_cast<void>(request_->getUri()));
+ EXPECT_NO_THROW(static_cast<void>(request_->getHeaderValue("Content-Type")));
+ EXPECT_NO_THROW(
+ static_cast<void>(request_->getHeaderValueAsUint64("Content-Length"))
+ );
+ EXPECT_NO_THROW(static_cast<void>(request_->getBody()));
+}
+
+// This test verifies that it is possible to fetch the request
+// body.
+TEST_F(HttpRequestTest, getBody) {
+ std::string json_body = "{ \"param1\": \"foo\" }";
+
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 0));
+ addHeaderToContext("Content-Length", json_body.length());
+
+ request_->context()->body_ = json_body;
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ EXPECT_EQ(json_body, request_->getBody());
+}
+
+// This test verifies the behavior of the requiresBody function.
+TEST_F(HttpRequestTest, requiresBody) {
+ ASSERT_FALSE(request_->requiresBody());
+ request_->requireHeader("Content-Length");
+ EXPECT_TRUE(request_->requiresBody());
+}
+
+// This test verifies that HTTP/1.0 connections are not persistent
+// by default.
+TEST_F(HttpRequestTest, isPersistentHttp10) {
+ // In HTTP 1.0 the connection is by default non-persistent.
+ EXPECT_FALSE(isPersistent(HttpVersion(1, 0)));
+}
+
+// This test verifies that HTTP/1.1 connections are persistent
+// by default.
+TEST_F(HttpRequestTest, isPersistentHttp11) {
+ // In HTTP 1.1 the connection is by default persistent.
+ EXPECT_TRUE(isPersistent(HttpVersion(1, 1)));
+}
+
+// This test verifies that HTTP/1.0 connection becomes persistent
+// when keep-alive value of the Connection header is included.
+TEST_F(HttpRequestTest, isPersistentHttp10KeepAlive) {
+ // In HTTP 1.0 the client indicates that the connection is desired to be
+ // persistent by including "Connection: keep-alive" header.
+ EXPECT_TRUE(
+ isPersistent(HttpVersion(1, 0), HttpHeader("Connection", "Keep-alive"))
+ );
+}
+
+// This test verifies that HTTP/1.1 connection is closed when the
+// close value of the Connection header is included.
+TEST_F(HttpRequestTest, isPersistentHttp11Close) {
+ // In HTTP 1.1 the client would include "Connection: close" header if it
+ // desires to close the connection.
+ EXPECT_FALSE(
+ isPersistent(HttpVersion(1, 1), HttpHeader("Connection", "close"))
+ );
+}
+
+// This test verifies the contents of the HTTP outbound request.
+TEST_F(HttpRequestTest, clientRequest) {
+ ASSERT_NO_THROW(
+ request_.reset(new HttpRequest(HttpRequest::Method::HTTP_POST,
+ "/isc/org",
+ HttpVersion(1, 0),
+ HostHttpHeader("www.example.org")));
+ );
+
+ // Capture current date and time.
+ HttpDateTime date_time;
+
+ // Add headers.
+ request_->context()->headers_.push_back(HttpHeaderContext("Date", date_time.rfc1123Format()));
+ request_->context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ request_->context()->headers_.push_back(HttpHeaderContext("Accept", "text/html"));
+ // Add a body.
+ request_->context()->body_ = "<html></html>";
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Check that the HTTP request in the textual format is correct. Note that
+ // it should include "Content-Length", even though we haven't explicitly set
+ // this header. It is dynamically computed from the body size.
+ EXPECT_EQ("POST /isc/org HTTP/1.0\r\n"
+ "Host: www.example.org\r\n"
+ "Accept: text/html\r\n"
+ "Content-Length: 13\r\n"
+ "Content-Type: text/html\r\n"
+ "Date: " + date_time.rfc1123Format() + "\r\n"
+ "\r\n"
+ "<html></html>",
+ request_->toString());
+}
+
+// This test verifies the contents of the HTTP outbound request
+// which lacks body.
+TEST_F(HttpRequestTest, clientRequestNoBody) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ // Add headers.
+ request_->context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ // Commit and validate the data.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Check that the HTTP request in the textual format is correct. Note that
+ // there should be no Content-Length included, because the body is empty.
+ EXPECT_EQ("GET /isc/org HTTP/1.1\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n",
+ request_->toString());
+}
+
+// This test verifies the first line of the HTTP request.
+TEST_F(HttpRequestTest, toBriefString) {
+ // Create the request.
+ setContextBasics("POST", "/isc/org", HttpVersion(1, 1));
+ // Add headers.
+ request_->context()->headers_.push_back(HttpHeaderContext("Content-Type", "application/json"));
+ // Must be finalized before can be used.
+ ASSERT_NO_THROW(request_->finalize());
+ // Check that the brief string is correct.
+ EXPECT_EQ("POST /isc/org HTTP/1.1", request_->toBriefString());
+}
+
+// This test verifies that no basic HTTP authentication is supported.
+TEST_F(HttpRequestTest, noBasicAuth) {
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 1),
+ HostHttpHeader("www.example.org"))));
+
+ ASSERT_NO_THROW(request_->finalize());
+ ASSERT_THROW(request_->getHeader("Authorization"),
+ HttpMessageNonExistingHeader);
+}
+
+// This test verifies that basic HTTP authentication works as expected.
+TEST_F(HttpRequestTest, basicAuth) {
+ BasicHttpAuthPtr basic_auth;
+ EXPECT_NO_THROW(basic_auth.reset(new BasicHttpAuth("foo", "bar")));
+ ASSERT_TRUE(basic_auth);
+
+ ASSERT_NO_THROW(request_.reset(new HttpRequest(HttpRequest::Method::HTTP_GET,
+ "/isc/org",
+ HttpVersion(1, 1),
+ HostHttpHeader("www.example.org"),
+ basic_auth)));
+
+ ASSERT_NO_THROW(request_->finalize());
+
+ std::string value;
+ EXPECT_NO_THROW(value = request_->getHeaderValue("Authorization"));
+ EXPECT_EQ(value, "Basic " + basic_auth->getCredential());
+}
+
+/// This test verifies that access parameters are handled as expected.
+TEST_F(HttpRequestTest, parameters) {
+ setContextBasics("GET", "/isc/org", HttpVersion(1, 1));
+ ASSERT_NO_THROW(request_->create());
+
+ EXPECT_TRUE(request_->getRemote().empty());
+ EXPECT_FALSE(request_->getTls());
+ EXPECT_TRUE(request_->getSubject().empty());
+ EXPECT_TRUE(request_->getIssuer().empty());
+ EXPECT_TRUE(request_->getBasicAuth().empty());
+ EXPECT_TRUE(request_->getCustom().empty());
+
+ request_->setRemote("my-remote");
+ request_->setTls(true);
+ request_->setSubject("my-subject");
+ request_->setIssuer("my-issuer");
+ request_->setBasicAuth("foo");
+ request_->setCustom("bar");
+
+ EXPECT_EQ("my-remote", request_->getRemote());
+ EXPECT_TRUE(request_->getTls());
+ EXPECT_EQ("my-subject", request_->getSubject());
+ EXPECT_EQ("my-issuer", request_->getIssuer());
+ EXPECT_EQ("foo", request_->getBasicAuth());
+ EXPECT_EQ("bar", request_->getCustom());
+}
+
+}
diff --git a/src/lib/http/tests/response_creator_unittests.cc b/src/lib/http/tests/response_creator_unittests.cc
new file mode 100644
index 0000000..c5370cf
--- /dev/null
+++ b/src/lib/http/tests/response_creator_unittests.cc
@@ -0,0 +1,340 @@
+// Copyright (C) 2016-2022 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 <http/basic_auth.h>
+#include <http/basic_auth_config.h>
+#include <http/http_types.h>
+#include <http/request.h>
+#include <http/response.h>
+#include <http/response_creator.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <testutils/log_utils.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp::test;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace std;
+
+namespace {
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new HttpRequest()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // The simplest thing is to create a response with no content.
+ // We don't need content to test our class.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Pointer to test HTTP response creator.
+typedef boost::shared_ptr<TestHttpResponseCreator> TestHttpResponseCreatorPtr;
+
+// This test verifies that Bad Request status is generated when the request
+// hasn't been finalized.
+TEST(HttpResponseCreatorTest, badRequest) {
+ HttpResponsePtr response;
+ // Create a request but do not finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+
+ // Use test specific implementation of Response Creator. It should
+ // generate HTTP error 400.
+ TestHttpResponseCreator creator;
+ ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ response->toString());
+}
+
+// This test verifies that response is generated successfully from the
+// finalized/parsed request.
+TEST(HttpResponseCreatorTest, goodRequest) {
+ // There is no credentials so it checks also what happens when
+ // authentication is not required.
+
+ HttpResponsePtr response;
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ ASSERT_NO_THROW(request->finalize());
+
+ // Use test specific implementation of the Response Creator to generate
+ // a response.
+ TestHttpResponseCreator creator;
+ ASSERT_NO_THROW(response = creator.createHttpResponse(request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 200 OK\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n\r\n",
+ response->toString());
+}
+
+/// @brief Test fixture for HTTP response creator authentication.
+class HttpResponseCreatorAuthTest : public LogContentTest { };
+
+// This test verifies that missing required authentication header gives
+// unauthorized error.
+TEST_F(HttpResponseCreatorAuthTest, noAuth) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_NO_AUTH_HEADER received HTTP request "
+ "without required authentication header");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that too short authentication header is rejected.
+TEST_F(HttpResponseCreatorAuthTest, authTooShort) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ HttpHeaderContext auth("Authorization", "Basic =");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request "
+ "with malformed authentication header: "
+ "header content is too short");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that another authentication schema is rejected.
+TEST_F(HttpResponseCreatorAuthTest, badScheme) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ HttpHeaderContext auth("Authorization", "Basis dGVzdDoxMjPCow==");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request "
+ "with malformed authentication header: "
+ "not basic authentication");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that not matching credential is rejected.
+TEST_F(HttpResponseCreatorAuthTest, notMatching) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ // Slightly different credential...
+ HttpHeaderContext auth("Authorization", "Basic dGvZdDoxMjPcOw==");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ ASSERT_TRUE(response);
+
+ EXPECT_EQ("HTTP/1.0 401 Unauthorized\r\n"
+ "Content-Length: 41\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "WWW-Authenticate: Basic realm=\"ISC.ORG\"\r\n\r\n"
+ "{ \"result\": 401, \"text\": \"Unauthorized\" }",
+ response->toString());
+
+ EXPECT_TRUE(request->getBasicAuth().empty());
+ addString("HTTP_CLIENT_REQUEST_NOT_AUTHORIZED received HTTP request "
+ "with not matching authentication header");
+ EXPECT_TRUE(checkFile());
+}
+
+// This test verifies that matching credential is accepted.
+TEST_F(HttpResponseCreatorAuthTest, matching) {
+ // Create basic HTTP authentication configuration.
+ BasicHttpAuthConfigPtr auth_config(new BasicHttpAuthConfig());
+ EXPECT_NO_THROW(auth_config->add("test", "", "123\xa3", ""));
+ const BasicHttpAuthMap& credentials = auth_config->getCredentialMap();
+ auto cred = credentials.find("dGVzdDoxMjPCow==");
+ EXPECT_NE(cred, credentials.end());
+ EXPECT_EQ(cred->second, "test");
+ auth_config->setRealm("ISC.ORG");
+
+ // Create request and finalize it.
+ HttpRequestPtr request(new HttpRequest());
+ request->context()->http_version_major_ = 1;
+ request->context()->http_version_minor_ = 0;
+ request->context()->method_ = "GET";
+ request->context()->uri_ = "/foo";
+ HttpHeaderContext auth("Authorization", "Basic dGVzdDoxMjPCow==");
+ request->context()->headers_.push_back(auth);
+ ASSERT_NO_THROW(request->finalize());
+ HttpRequest::recordBasicAuth_ = true;
+
+ HttpResponsePtr response;
+ TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());;
+ ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request));
+ EXPECT_FALSE(response);
+
+ EXPECT_EQ("test", request->getBasicAuth());
+ addString("HTTP_CLIENT_REQUEST_AUTHORIZED received HTTP request "
+ "authorized for 'test'");
+ EXPECT_TRUE(checkFile());
+ HttpRequest::recordBasicAuth_ = false;
+}
+
+}
diff --git a/src/lib/http/tests/response_json_unittests.cc b/src/lib/http/tests/response_json_unittests.cc
new file mode 100644
index 0000000..e3829b5
--- /dev/null
+++ b/src/lib/http/tests/response_json_unittests.cc
@@ -0,0 +1,151 @@
+// Copyright (C) 2016-2017 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 <cc/data.h>
+#include <http/http_types.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponseJson> TestHttpResponseJson;
+
+/// @brief Test fixture class for @ref HttpResponseJson.
+class HttpResponseJsonTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the following class members:
+ /// - json_string_ - which is a pretty formatted JSON content,
+ /// - json_ - A structure of Element objects representing the JSON,
+ /// - json_string_from_json_ - which is a JSON text converted back from
+ /// the json_ data structure. It is the same content as json_string_
+ /// but has different whitespaces.
+ HttpResponseJsonTest()
+ : json_(), json_string_(), json_string_from_json_() {
+ json_string_ =
+ "["
+ " {"
+ " \"pid\": 8080,"
+ " \"status\": 10,"
+ " \"comment\": \"Nice comment from 8080\""
+ " },"
+ " {"
+ " \"pid\": 8081,"
+ " \"status\": 12,"
+ " \"comment\": \"A comment from 8081\""
+ " }"
+ "]";
+
+ json_ = Element::fromJSON(json_string_);
+ json_string_from_json_ = json_->str();
+ }
+
+ /// @brief Test that the response format is correct.
+ ///
+ /// @param status_code HTTP status code for which the response should
+ /// be tested.
+ /// @param status_message HTTP status message.
+ void testGenericResponse(const HttpStatusCode& status_code,
+ const std::string& status_message) {
+ TestHttpResponseJson response(HttpVersion(1, 0), status_code);
+ ASSERT_NO_THROW(response.finalize());
+ std::ostringstream status_message_json;
+ // Build the expected content.
+ status_message_json << "{ \"result\": "
+ << static_cast<uint16_t>(status_code)
+ << ", \"text\": "
+ << "\"" << status_message << "\" }";
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.0 " << static_cast<uint16_t>(status_code) << " "
+ << status_message << "\r\n";
+
+ // The content must only be generated for error codes.
+ if (HttpResponse::isClientError(status_code) ||
+ HttpResponse::isServerError(status_code)) {
+ response_string << "Content-Length: " << status_message_json.str().size()
+ << "\r\n";
+ } else {
+ response_string << "Content-Length: 0\r\n";
+ }
+
+ // Content-Type and Date are automatically included.
+ response_string << "Content-Type: application/json\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+
+ if (HttpResponse::isClientError(status_code) ||
+ HttpResponse::isServerError(status_code)) {
+ response_string << status_message_json.str();
+ }
+
+ // Check that the output is as expected.
+ EXPECT_EQ(response_string.str(), response.toString());
+ }
+
+ /// @brief JSON content represented as structure of Element objects.
+ ConstElementPtr json_;
+
+ /// @brief Pretty formatted JSON content.
+ std::string json_string_;
+
+ /// @brief JSON content parsed back from json_ structure.
+ std::string json_string_from_json_;
+
+};
+
+// Test that the response with custom JSON content is generated properly.
+TEST_F(HttpResponseJsonTest, responseWithContent) {
+ TestHttpResponseJson response(HttpVersion(1, 1), HttpStatusCode::OK);
+ ASSERT_NO_THROW(response.setBodyAsJson(json_));
+ ASSERT_NO_THROW(response.finalize());
+
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Length: " << json_string_from_json_.length() << "\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n\r\n"
+ << json_string_from_json_;
+ EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test that generic responses are created properly.
+TEST_F(HttpResponseJsonTest, genericResponse) {
+ testGenericResponse(HttpStatusCode::OK, "OK");
+ testGenericResponse(HttpStatusCode::CREATED, "Created");
+ testGenericResponse(HttpStatusCode::ACCEPTED, "Accepted");
+ testGenericResponse(HttpStatusCode::NO_CONTENT, "No Content");
+ testGenericResponse(HttpStatusCode::MULTIPLE_CHOICES,
+ "Multiple Choices");
+ testGenericResponse(HttpStatusCode::MOVED_PERMANENTLY,
+ "Moved Permanently");
+ testGenericResponse(HttpStatusCode::MOVED_TEMPORARILY,
+ "Moved Temporarily");
+ testGenericResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+ testGenericResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+ testGenericResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+ testGenericResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+ testGenericResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+ testGenericResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
+ testGenericResponse(HttpStatusCode::INTERNAL_SERVER_ERROR,
+ "Internal Server Error");
+ testGenericResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+ testGenericResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+ testGenericResponse(HttpStatusCode::SERVICE_UNAVAILABLE,
+ "Service Unavailable");
+}
+
+}
diff --git a/src/lib/http/tests/response_parser_unittests.cc b/src/lib/http/tests/response_parser_unittests.cc
new file mode 100644
index 0000000..58d479d
--- /dev/null
+++ b/src/lib/http/tests/response_parser_unittests.cc
@@ -0,0 +1,351 @@
+// Copyright (C) 2017-2019 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 <cc/data.h>
+#include <http/response_json.h>
+#include <http/response_parser.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @ref HttpResponseParser.
+class HttpResponseParserTest : public ::testing::Test {
+public:
+
+ /// @brief Creates HTTP response string.
+ ///
+ /// @param preamble A string including HTTP response's first line
+ /// and all headers except "Content-Length".
+ /// @param payload A string containing HTTP response payload.
+ std::string createResponseString(const std::string& preamble,
+ const std::string& payload) {
+ std::ostringstream s;
+ s << preamble;
+ s << "Content-Length: " << payload.length() << "\r\n\r\n"
+ << payload;
+ return (s.str());
+ }
+
+ /// @brief Parses the HTTP response and checks that parsing was
+ /// successful.
+ ///
+ /// @param http_resp HTTP response string.
+ void doParse(const std::string& http_resp) {
+ HttpResponseParser parser(response_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_resp[0], http_resp.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Tests that parsing fails when malformed HTTP response
+ /// is received.
+ ///
+ /// @param http_resp HTTP response string.
+ void testInvalidHttpResponse(const std::string& http_resp) {
+ HttpResponseParser parser(response_);
+ ASSERT_NO_THROW(parser.initModel());
+
+ parser.postBuffer(&http_resp[0], http_resp.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ EXPECT_FALSE(parser.needData());
+ EXPECT_FALSE(parser.httpParseOk());
+ EXPECT_FALSE(parser.getErrorMessage().empty());
+ }
+
+ /// @brief Tests that the response specified with (header, body) can
+ /// be parsed properly.
+ ///
+ /// @param header specifies the header of the response to be parsed
+ /// @param json specifies the body of the response (JSON in text format) to be parsed
+ /// @param expect_success whether the parsing is expected to be successful
+ ///
+ /// @return a parser that parsed the response for further inspection
+ HttpResponseJson testResponseWithJson(const std::string& header,
+ const std::string& json,
+ bool expect_success = true) {
+ std::string http_resp = createResponseString(header, json);
+
+ // Create HTTP response which accepts JSON as a body.
+ HttpResponseJson response;
+
+ // Create a parser and make it use the response we created.
+ HttpResponseParser parser(response);
+ EXPECT_NO_THROW(parser.initModel());
+
+ // Simulate receiving HTTP response in chunks.
+ const unsigned chunk_size = 10;
+ while (!http_resp.empty()) {
+ size_t chunk = http_resp.size() % chunk_size;
+ if (chunk == 0) {
+ chunk = chunk_size;
+ }
+
+ parser.postBuffer(&http_resp[0], chunk);
+ http_resp.erase(0, chunk);
+ parser.poll();
+ if (chunk < chunk_size) {
+ EXPECT_TRUE(parser.needData());
+ if (!parser.needData()) {
+ ADD_FAILURE() << "Parser completed prematurely";
+ return (response);
+ }
+ }
+ }
+
+ if (expect_success) {
+ // Parser should have parsed the response and should expect no more data.
+ EXPECT_FALSE(parser.needData());
+ // Parsing should be successful.
+ EXPECT_TRUE(parser.httpParseOk()) << parser.getErrorMessage();
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+ }
+
+ return (response);
+ }
+
+ /// @brief Instance of the HttpResponse used by the unit tests.
+ HttpResponse response_;
+};
+
+// Test test verifies that an HTTP response including JSON body is parsed
+// successfully.
+TEST_F(HttpResponseParserTest, responseWithJson) {
+ std::string http_resp = "HTTP/1.1 408 Request Timeout\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"result\": 0, \"text\": \"All ok\" }";
+
+ HttpResponseJson response = testResponseWithJson(http_resp, json);
+
+ // Verify HTTP version, status code and phrase.
+ EXPECT_EQ(1, response.getHttpVersion().major_);
+ EXPECT_EQ(1, response.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::REQUEST_TIMEOUT, response.getStatusCode());
+ EXPECT_EQ("Request Timeout", response.getStatusPhrase());
+
+ // Try to retrieve values carried in JSON payload.
+ ConstElementPtr json_element;
+ ASSERT_NO_THROW(json_element = response.getJsonElement("result"));
+ EXPECT_EQ(0, json_element->intValue());
+
+ ASSERT_NO_THROW(json_element = response.getJsonElement("text"));
+ EXPECT_EQ("All ok", json_element->stringValue());
+}
+
+// This test verifies that extraneous data in the response will not cause
+// an error if "Content-Length" value refers to the length of the valid
+// part of the response.
+TEST_F(HttpResponseParserTest, extraneousDataInResponse) {
+ std::string http_resp = "HTTP/1.0 200 OK\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "{ \"service\": \"dhcp4\", \"command\": \"shutdown\" }";
+
+ // Create valid response.
+ http_resp = createResponseString(http_resp, json);
+
+ // Add some garbage at the end.
+ http_resp += "some stuff which, if parsed, will cause errors";
+
+ // Create HTTP response which accepts JSON as a body.
+ HttpResponseJson response;
+
+ // Create a parser and make it use the response we created.
+ HttpResponseParser parser(response);
+ ASSERT_NO_THROW(parser.initModel());
+
+ // Feed the parser with the response containing some garbage at the end.
+ parser.postBuffer(&http_resp[0], http_resp.size());
+ ASSERT_NO_THROW(parser.poll());
+
+ // The parser should only parse the valid part of the response as indicated
+ // by the Content-Length.
+ ASSERT_FALSE(parser.needData());
+ ASSERT_TRUE(parser.httpParseOk());
+ // There should be no error message.
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+
+ // Do another poll() to see if the parser will parse the garbage. We
+ // expect that it doesn't.
+ ASSERT_NO_THROW(parser.poll());
+ EXPECT_FALSE(parser.needData());
+ EXPECT_TRUE(parser.httpParseOk());
+ EXPECT_TRUE(parser.getErrorMessage().empty());
+}
+
+// This test verifies that LWS is parsed correctly. The LWS (linear white
+// space) marks line breaks in the HTTP header values.
+TEST_F(HttpResponseParserTest, getLWS) {
+ // "User-Agent" header contains line breaks with whitespaces in the new
+ // lines to mark continuation of the header value.
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html\r\n"
+ "User-Agent: Kea/1.2 Command \r\n"
+ " Control \r\n"
+ "\tClient\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ // Verify parsed values.
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+ EXPECT_EQ("text/html", response_.getHeaderValue("Content-Type"));
+ EXPECT_EQ("Kea/1.2 Command Control Client",
+ response_.getHeaderValue("User-Agent"));
+}
+
+// This test verifies that the HTTP response with no headers is
+// parsed correctly.
+TEST_F(HttpResponseParserTest, noHeaders) {
+ std::string http_resp = "HTTP/1.1 204 No Content\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ // Verify the values.
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::NO_CONTENT, response_.getStatusCode());
+}
+
+// This test verifies that headers are case insensitive.
+TEST_F(HttpResponseParserTest, headersCaseInsensitive) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-type: text/html\r\n"
+ "connection: clOSe\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ EXPECT_EQ("text/html", response_.getHeader("Content-Type")->getValue());
+ EXPECT_EQ("close", response_.getHeader("Connection")->getLowerCaseValue());
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+// This test verifies that the header with no whitespace between the
+// colon and header value is accepted.
+TEST_F(HttpResponseParserTest, noHeaderWhitespace) {
+ std::string http_resp = "HTTP/1.0 200 OK\r\n"
+ "Content-Type:text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ EXPECT_EQ("text/html", response_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(0, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+// This test verifies that the header value preceded with multiple
+// whitespaces is accepted.
+TEST_F(HttpResponseParserTest, multipleLeadingHeaderWhitespaces) {
+ std::string http_resp = "HTTP/1.0 200 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ EXPECT_EQ("text/html", response_.getHeaderValue("Content-Type"));
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(0, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+// This test verifies that the response containing a typo in the
+// HTTP version string causes parsing error.
+TEST_F(HttpResponseParserTest, invalidHTTPString) {
+ std::string http_resp = "HTLP/2.0 100 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when the HTTP version
+// string doesn't contain a slash character.
+TEST_F(HttpResponseParserTest, invalidHttpVersionNoSlash) {
+ std::string http_resp = "HTTP 1.1 100 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when HTTP version string
+// doesn't contain the minor version number.
+TEST_F(HttpResponseParserTest, invalidHttpNoMinorVersion) {
+ std::string http_resp = "HTTP/1 200 OK\r\n"
+ "Content-Type: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when HTTP header name
+// contains an invalid character.
+TEST_F(HttpResponseParserTest, invalidHeaderName) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-;: text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that error is reported when HTTP header value
+// is not preceded with the colon character.
+TEST_F(HttpResponseParserTest, noColonInHttpHeader) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-Type text/html\r\n\r\n";
+ testInvalidHttpResponse(http_resp);
+}
+
+// This test verifies that the HTTP response is formatted for logging.
+TEST_F(HttpResponseParserTest, logFormatHttpMessage) {
+ std::string message = "POST / HTTP/1.1\r\n"
+ "Host: 127.0.0.1:8080\r\n"
+ "User-Agent: curl/7.59.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 51\r\n\r\n"
+ "{ \"command\": \"config-get\", \"service\": [ \"dhcp4\" ] }";
+
+ // limit = 0 means no limit
+ EXPECT_EQ(message, HttpResponseParser::logFormatHttpMessage(message, 0));
+
+ // large enough limit should not cause the truncation.
+ EXPECT_EQ(message, HttpResponseParser::logFormatHttpMessage(message, 1024));
+
+ // Only 3 characters requested. The request should be truncated.
+ EXPECT_EQ("POS.........\n(truncating HTTP message larger than 3 characters)\n",
+ HttpResponseParser::logFormatHttpMessage(message, 3));
+}
+
+TEST_F(HttpResponseParserTest, parseEmptyResponse) {
+ std::string http_resp = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: application/json\r\n";
+ std::string json = "";
+
+ http_resp = createResponseString(http_resp, json);
+
+ ASSERT_NO_FATAL_FAILURE(doParse(http_resp));
+
+ HttpResponseJson response = testResponseWithJson(http_resp, json);
+
+ EXPECT_EQ("", response_.getBody());
+ EXPECT_EQ(1, response_.getHttpVersion().major_);
+ EXPECT_EQ(1, response_.getHttpVersion().minor_);
+ EXPECT_EQ(HttpStatusCode::OK, response_.getStatusCode());
+ EXPECT_EQ("OK", response_.getStatusPhrase());
+}
+
+}
diff --git a/src/lib/http/tests/response_test.h b/src/lib/http/tests/response_test.h
new file mode 100644
index 0000000..d342a64
--- /dev/null
+++ b/src/lib/http/tests/response_test.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2016-2018 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 HTTP_RESPONSE_TEST_H
+#define HTTP_RESPONSE_TEST_H
+
+#include <http/http_types.h>
+#include <http/response.h>
+#include <boost/lexical_cast.hpp>
+#include <cstdint>
+
+namespace isc {
+namespace http {
+namespace test {
+
+/// @brief Base class for test HTTP response.
+template<typename HttpResponseType>
+class TestHttpResponseBase : public HttpResponseType {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param version HTTP version for the response.
+ /// @param status_code HTTP status code.
+ TestHttpResponseBase(const HttpVersion& version,
+ const HttpStatusCode& status_code)
+ : HttpResponseType(version, status_code) {
+ }
+
+ /// @brief Returns fixed header value.
+ ///
+ /// Including fixed header value in the response makes the
+ /// response deterministic, which is critical for the unit
+ /// tests.
+ virtual std::string getDateHeaderValue() const {
+ return ("Tue, 19 Dec 2016 18:53:35 GMT");
+ }
+
+ /// @brief Returns date header value.
+ std::string generateDateHeaderValue() const {
+ return (HttpResponseType::getDateHeaderValue());
+ }
+
+ /// @brief Sets custom content length.
+ ///
+ /// @param content_length Content length value.
+ void setContentLength(const uint64_t content_length) {
+ HttpHeaderPtr length_header(new HttpHeader("Content-Length",
+ boost::lexical_cast<std::string>
+ (content_length)));
+ HttpResponseType::headers_["content-length"] = length_header;
+ }
+};
+
+} // namespace test
+} // namespace http
+} // namespace isc
+
+#endif
diff --git a/src/lib/http/tests/response_unittests.cc b/src/lib/http/tests/response_unittests.cc
new file mode 100644
index 0000000..f54e65d
--- /dev/null
+++ b/src/lib/http/tests/response_unittests.cc
@@ -0,0 +1,169 @@
+// Copyright (C) 2016-2018 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 <http/date_time.h>
+#include <http/http_types.h>
+#include <http/response.h>
+#include <http/tests/response_test.h>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace boost::posix_time;
+using namespace isc::http;
+using namespace isc::http::test;
+
+namespace {
+
+/// @brief Response type used in tests.
+typedef TestHttpResponseBase<HttpResponse> TestHttpResponse;
+
+/// @brief Test fixture class for @ref HttpResponse.
+class HttpResponseTest : public ::testing::Test {
+public:
+
+ /// @brief Checks if the format of the response is correct.
+ ///
+ /// @param status_code HTTP status code in the response.
+ /// @param status_message HTTP status message in the response.
+ void testResponse(const HttpStatusCode& status_code,
+ const std::string& status_message) {
+ // Create the response. Because we're using derived class
+ // it returns the fixed value of the Date header, which is
+ // very useful in unit tests.
+ TestHttpResponse response(HttpVersion(1, 0), status_code);
+ response.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ ASSERT_NO_THROW(response.finalize());
+ std::ostringstream response_string;
+ response_string << "HTTP/1.0 " << static_cast<uint16_t>(status_code)
+ << " " << status_message;
+ EXPECT_EQ(response_string.str(), response.toBriefString());
+
+ response_string
+ << "\r\nContent-Length: 0\r\n"
+ << "Content-Type: text/html\r\n"
+ << "Date: " << response.getDateHeaderValue() << "\r\n\r\n";
+ EXPECT_EQ(response_string.str(), response.toString());
+ }
+};
+
+// Test the case of HTTP OK message.
+TEST_F(HttpResponseTest, responseOK) {
+ // Include HTML body.
+ const std::string sample_body =
+ "<html>"
+ "<head><title>Kea page title</title></head>"
+ "<body><h1>Some header</h1></body>"
+ "</html>";
+
+ // Create the message and add some headers.
+ TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+ response.context()->headers_.push_back(HttpHeaderContext("Content-Type", "text/html"));
+ response.context()->headers_.push_back(HttpHeaderContext("Host", "kea.example.org"));
+ response.context()->body_ = sample_body;
+ ASSERT_NO_THROW(response.finalize());
+
+ // Create a string holding expected response. Note that the Date
+ // is a fixed value returned by the customized TestHttpResponse
+ // class.
+ std::ostringstream response_string;
+ response_string <<
+ "HTTP/1.0 200 OK\r\n"
+ "Content-Length: " << sample_body.length() << "\r\n"
+ "Content-Type: text/html\r\n"
+ "Date: " << response.getDateHeaderValue() << "\r\n"
+ "Host: kea.example.org\r\n\r\n" << sample_body;
+
+ EXPECT_EQ(response_string.str(), response.toString());
+}
+
+// Test generic responses for various status codes.
+TEST_F(HttpResponseTest, genericResponse) {
+ testResponse(HttpStatusCode::OK, "OK");
+ testResponse(HttpStatusCode::CREATED, "Created");
+ testResponse(HttpStatusCode::ACCEPTED, "Accepted");
+ testResponse(HttpStatusCode::NO_CONTENT, "No Content");
+ testResponse(HttpStatusCode::MULTIPLE_CHOICES, "Multiple Choices");
+ testResponse(HttpStatusCode::MOVED_PERMANENTLY, "Moved Permanently");
+ testResponse(HttpStatusCode::MOVED_TEMPORARILY, "Moved Temporarily");
+ testResponse(HttpStatusCode::NOT_MODIFIED, "Not Modified");
+ testResponse(HttpStatusCode::BAD_REQUEST, "Bad Request");
+ testResponse(HttpStatusCode::UNAUTHORIZED, "Unauthorized");
+ testResponse(HttpStatusCode::FORBIDDEN, "Forbidden");
+ testResponse(HttpStatusCode::NOT_FOUND, "Not Found");
+ testResponse(HttpStatusCode::REQUEST_TIMEOUT, "Request Timeout");
+ testResponse(HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error");
+ testResponse(HttpStatusCode::NOT_IMPLEMENTED, "Not Implemented");
+ testResponse(HttpStatusCode::BAD_GATEWAY, "Bad Gateway");
+ testResponse(HttpStatusCode::SERVICE_UNAVAILABLE, "Service Unavailable");
+}
+
+// Test if the class correctly identifies client errors.
+TEST_F(HttpResponseTest, isClientError) {
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::OK));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::CREATED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::ACCEPTED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NO_CONTENT));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MULTIPLE_CHOICES));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_PERMANENTLY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::MOVED_TEMPORARILY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_MODIFIED));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::BAD_REQUEST));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::UNAUTHORIZED));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::FORBIDDEN));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::NOT_FOUND));
+ EXPECT_TRUE(HttpResponse::isClientError(HttpStatusCode::REQUEST_TIMEOUT));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::NOT_IMPLEMENTED));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::BAD_GATEWAY));
+ EXPECT_FALSE(HttpResponse::isClientError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test if the class correctly identifies server errors.
+TEST_F(HttpResponseTest, isServerError) {
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::OK));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::CREATED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::ACCEPTED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NO_CONTENT));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MULTIPLE_CHOICES));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_PERMANENTLY));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::MOVED_TEMPORARILY));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_MODIFIED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::BAD_REQUEST));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::UNAUTHORIZED));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::FORBIDDEN));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::NOT_FOUND));
+ EXPECT_FALSE(HttpResponse::isServerError(HttpStatusCode::REQUEST_TIMEOUT));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::INTERNAL_SERVER_ERROR));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::NOT_IMPLEMENTED));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::BAD_GATEWAY));
+ EXPECT_TRUE(HttpResponse::isServerError(HttpStatusCode::SERVICE_UNAVAILABLE));
+}
+
+// Test that the generated time value, being included in the Date
+// header, is correct.
+TEST_F(HttpResponseTest, getDateHeaderValue) {
+ // Create a response and retrieve the value to be included in the
+ // Date header. This value should hold a current time in the
+ // RFC1123 format.
+ TestHttpResponse response(HttpVersion(1, 0), HttpStatusCode::OK);
+ std::string generated_date = response.generateDateHeaderValue();
+
+ // Use our date/time utilities to parse this value into the ptime.
+ HttpDateTime parsed_time = HttpDateTime::fromRfc1123(generated_date);
+
+ // Now that we have it converted back, we can check how far this
+ // value is from the current time. To be on the safe side, we check
+ // that it is not later than 10 seconds apart, rather than checking
+ // it for equality. In fact, checking it for equality would almost
+ // certainly cause an error. Especially on a virtual machine.
+ time_duration parsed_to_current =
+ microsec_clock::universal_time() - parsed_time.getPtime();
+ EXPECT_LT(parsed_to_current.seconds(), 10);
+}
+
+}
diff --git a/src/lib/http/tests/run_unittests.cc b/src/lib/http/tests/run_unittests.cc
new file mode 100644
index 0000000..17255dc
--- /dev/null
+++ b/src/lib/http/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2016-2018 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 <log/logger_support.h>
+#include <http/http_log.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/http/tests/server_client_unittests.cc b/src/lib/http/tests/server_client_unittests.cc
new file mode 100644
index 0000000..2ab76f3
--- /dev/null
+++ b/src/lib/http/tests/server_client_unittests.cc
@@ -0,0 +1,2041 @@
+// Copyright (C) 2017-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_acceptor.h>
+#include <cc/data.h>
+#include <test_http_client.h>
+#include <http/client.h>
+#include <http/http_types.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <list>
+#include <sstream>
+#include <string>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch HTTP service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// This method generates 3 types of responses:
+ /// - response with a requested content type,
+ /// - partial response with incomplete JSON body,
+ /// - response with JSON body copied from the request.
+ ///
+ /// The first one is useful to test situations when received response can't
+ /// be parsed because of the content type mismatch. The second one is useful
+ /// to test request timeouts. The third type is used by most of the unit tests
+ /// to test successful transactions.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ ConstElementPtr body;
+ if (request_json) {
+ body = request_json->getBodyAsJson();
+ if (body) {
+ // Check if the client requested one of the two first response
+ // types.
+ GenericResponsePtr response;
+ ConstElementPtr content_type = body->get("requested-content-type");
+ ConstElementPtr partial_response = body->get("partial-response");
+ if (content_type || partial_response) {
+ // The first two response types can only be generated using the
+ // generic response as we have to explicitly modify some of the
+ // values.
+ response.reset(new GenericResponse(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ HttpResponseContextPtr ctx = response->context();
+
+ if (content_type) {
+ // Provide requested content type.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ content_type->stringValue()));
+ // It doesn't matter what body is there.
+ ctx->body_ = "abcd";
+ response->finalize();
+
+ } else {
+ // Generate JSON response.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ "application/json"));
+ // The body lacks '}' so the client will be waiting for it and
+ // eventually should time out.
+ ctx->body_ = "{";
+ response->finalize();
+ // The auto generated Content-Length header would be based on the
+ // body size (so set to 1 byte). We have to override it to
+ // account for the missing '}' character.
+ response->setContentLength(2);
+ }
+ return (response);
+ }
+ }
+ }
+
+ // Third type of response is requested.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // If body was included in the request. Let's copy it.
+ if (body) {
+ response->setBodyAsJson(body);
+ }
+
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Implementation of the HTTP listener used in tests.
+///
+/// This implementation replaces the @c HttpConnection type with a custom
+/// implementation.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerImplCustom : public HttpListenerImpl {
+public:
+
+ HttpListenerImplCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpListenerImpl(io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout,
+ idle_timeout) {
+ }
+
+protected:
+
+ /// @brief Creates an instance of the @c HttpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ ///
+ /// @return Pointer to the created connection.
+ virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback) {
+ HttpConnectionPtr
+ conn(new HttpConnectionType(io_service_, acceptor_,
+ tls_context_, connections_,
+ response_creator, callback,
+ request_timeout_, idle_timeout_));
+ return (conn);
+ }
+};
+
+/// @brief Derivation of the @c HttpListener used in tests.
+///
+/// This class replaces the default implementation instance with the
+/// @c HttpListenerImplCustom using the customized connection type.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerCustom : public HttpListener {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const HttpListener::RequestTimeout& request_timeout,
+ const HttpListener::IdleTimeout& idle_timeout)
+ : HttpListener(io_service, server_address, server_port,
+ tls_context, creator_factory,
+ request_timeout, idle_timeout) {
+ // Replace the default implementation with the customized version
+ // using the custom derivation of the HttpConnection.
+ impl_.reset(new HttpListenerImplCustom<HttpConnectionType>
+ (io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout.value_,
+ idle_timeout.value_));
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which injects greater
+/// length value than the buffer size into the write socket callback.
+class HttpConnectionLongWriteBuffer : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionLongWriteBuffer(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Pass greater length of the data written. The callback should deal
+ // with this and adjust the data length.
+ HttpConnection::socketWriteCallback(transaction, ec, length + 1);
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which replaces
+/// transaction instance prior to calling write socket callback.
+class HttpConnectionTransactionChange : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param context TLS tls_context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionTransactionChange(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Replace the transaction. The socket callback should deal with this
+ // gracefully. It should detect that the output buffer is empty. Then
+ // try to see if the connection is persistent. This check should fail,
+ // because the request hasn't been created/finalized. The exception
+ // thrown upon checking the persistence should be caught and the
+ // connection closed.
+ transaction = HttpConnection::Transaction::create(response_creator_);
+ HttpConnection::socketWriteCallback(transaction, ec, length);
+ }
+};
+
+/// @brief Pointer to the TestHttpClient.
+typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ HttpListenerTest()
+ : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+ test_timer_(io_service_), run_io_service_timer_(io_service_), clients_() {
+ test_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes active HTTP clients.
+ virtual ~HttpListenerTest() {
+ for (auto client = clients_.begin(); client != clients_.end();
+ ++client) {
+ (*client)->close();
+ }
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TestHttpClient instance and retains it in the clients_
+ /// list.
+ ///
+ /// @param request String containing the HTTP request to be sent.
+ void startRequest(const std::string& request) {
+ TestHttpClientPtr client(new TestHttpClient(io_service_));
+ clients_.push_back(client);
+ clients_.back()->startRequest(request);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran.
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler,
+ this, false),
+ timeout, IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief Returns HTTP OK response expected by unit tests.
+ ///
+ /// @param http_version HTTP version.
+ ///
+ /// @return HTTP OK response expected by unit tests.
+ std::string httpOk(const HttpVersion& http_version) {
+ std::ostringstream s;
+ s << "HTTP/" << http_version.major_ << "." << http_version.minor_ << " 200 OK\r\n"
+ "Content-Length: 33\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"remote-address\": \"127.0.0.1\" }";
+ return (s.str());
+ }
+
+ /// @brief Tests that HTTP request timeout status is returned when the
+ /// server does not receive the entire request.
+ ///
+ /// @param request Partial request for which the parser will be waiting for
+ /// the next chunks of data.
+ /// @param expected_version HTTP version expected in the response.
+ void testRequestTimeout(const std::string& request,
+ const HttpVersion& expected_version) {
+ // Open the listener with the Request Timeout of 1 sec and post the
+ // partial request.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(),
+ factory_, HttpListener::RequestTimeout(1000),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+
+ // Build the reference response.
+ std::ostringstream expected_response;
+ expected_response
+ << "HTTP/" << expected_version.major_ << "." << expected_version.minor_
+ << " 408 Request Timeout\r\n"
+ "Content-Length: 44\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 408, \"text\": \"Request Timeout\" }";
+
+ // The server should wait for the missing part of the request for 1 second.
+ // The missing part never arrives so the server should respond with the
+ // HTTP Request Timeout status.
+ EXPECT_EQ(expected_response.str(), client->getResponse());
+ }
+
+ /// @brief Tests various cases when unexpected data is passed to the
+ /// socket write handler.
+ ///
+ /// This test uses the custom listener and the test specific derivations of
+ /// the @c HttpConnection class to enforce injection of the unexpected
+ /// data to the socket write callback. The two example applications of
+ /// this test are:
+ /// - injecting greater length value than the output buffer size,
+ /// - replacing the transaction with another transaction.
+ ///
+ /// It is expected that the socket write callback deals gracefully with
+ /// those situations.
+ ///
+ /// @tparam HttpConnectionType Test specific derivation of the
+ /// @c HttpConnection class.
+ template<typename HttpConnectionType>
+ void testWriteBufferIssues() {
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Use custom listener and the specialized connection object.
+ HttpListenerCustom<HttpConnectionType>
+ listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+
+ // Injecting unexpected data should not result in an exception.
+ ASSERT_NO_THROW(runIOService());
+
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TestHttpClientPtr> clients_;
+};
+
+// This test verifies that HTTP connection can be established and used to
+// transmit HTTP request and receive a response.
+TEST_F(HttpListenerTest, listen) {
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+
+// This test verifies that persistent HTTP connection can be established when
+// "Connection: Keep-Alive" header value is specified.
+TEST_F(HttpListenerTest, keepAlive) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it lacks the keep-alive header, so the server should close the connection
+ // after sending the response.
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent HTTP connection is established by default
+// when HTTP/1.1 is in use.
+TEST_F(HttpListenerTest, persistentConnection) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the first request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // HTTP/1.1 connection is persistent by default.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it includes the "Connection: close" header which instructs the server to
+ // close the connection after responding.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that "keep-alive" connection is closed by the server after
+// an idle time.
+TEST_F(HttpListenerTest, keepAliveTimeout) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent connection is closed by the server after
+// an idle time.
+TEST_F(HttpListenerTest, persistentConnectionTimeout) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that HTTP/1.1 connection remains open even if there is an
+// error in the message body.
+TEST_F(HttpListenerTest, persistentConnectionBadBody) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 12\r\n\r\n"
+ "{ \"a\": abc }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Make sure that we can send another request. This time we specify the
+ // "close" connection-token to force the connection to close.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that the HTTP listener can't be started twice.
+TEST_F(HttpListenerTest, startTwice) {
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that Bad Request status is returned when the request
+// is malformed.
+TEST_F(HttpListenerTest, badRequest) {
+ // Content-Type is wrong. This should result in Bad Request status.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: foo\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+}
+
+// This test verifies that NULL pointer can't be specified for the
+// HttpResponseCreatorFactory.
+TEST_F(HttpListenerTest, invalidFactory) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(),
+ HttpResponseCreatorFactoryPtr(),
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// Request Timeout.
+TEST_F(HttpListenerTest, invalidRequestTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(0),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// idle persistent connection timeout.
+TEST_F(HttpListenerTest, invalidIdleTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(0)),
+ HttpListenerError);
+}
+
+// This test verifies that listener can't be bound to the port to which
+// other server is bound.
+TEST_F(HttpListenerTest, addressInUse) {
+ tcp::acceptor acceptor(io_service_.get_io_service());
+ // Use other port than SERVER_PORT to make sure that this TCP connection
+ // doesn't affect subsequent tests.
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT + 1);
+ acceptor.open(endpoint.protocol());
+ acceptor.bind(endpoint);
+
+ // Listener should report an error when we try to start it because another
+ // acceptor is bound to that port and address.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT + 1, TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request contains the HTTP
+// version number. The timeout response should contain the same
+// HTTP version number as the partial request.
+TEST_F(HttpListenerTest, requestTimeoutHttpVersionFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length:";
+
+ testRequestTimeout(request, HttpVersion::HTTP_11());
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request does not contain
+// the HTTP version number. The timeout response should by default
+// contain HTTP/1.0 version number.
+TEST_F(HttpListenerTest, requestTimeoutHttpVersionNotFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP";
+
+ testRequestTimeout(request, HttpVersion::HTTP_10());
+}
+
+// This test verifies that injecting length value greater than the
+// output buffer length to the socket write callback does not cause
+// an exception.
+TEST_F(HttpListenerTest, tooLongWriteBuffer) {
+ testWriteBufferIssues<HttpConnectionLongWriteBuffer>();
+}
+
+// This test verifies that changing the transaction before calling
+// the socket write callback does not cause an exception.
+TEST_F(HttpListenerTest, transactionChangeDuringWrite) {
+ testWriteBufferIssues<HttpConnectionTransactionChange>();
+}
+
+/// @brief Test fixture class for testing HTTP client.
+class HttpClientTest : public HttpListenerTest {
+public:
+
+ /// @brief Constructor.
+ HttpClientTest()
+ : HttpListenerTest(),
+ listener_(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ listener2_(io_service_, IOAddress(IPV6_SERVER_ADDRESS), SERVER_PORT + 1,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ listener3_(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT + 2,
+ TlsContextPtr(), factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(SHORT_IDLE_TIMEOUT)) {
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ ~HttpClientTest() {
+ listener_.stop();
+ listener2_.stop();
+ listener3_.stop();
+ io_service_.poll();
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Creates HTTP request with JSON body.
+ ///
+ /// It includes a JSON parameter with a specified value.
+ ///
+ /// @param parameter_name JSON parameter to be included.
+ /// @param value JSON parameter value.
+ /// @param version HTTP version to be used. Default is HTTP/1.1.
+ template<typename ValueType>
+ PostHttpRequestJsonPtr createRequest(const std::string& parameter_name,
+ const ValueType& value,
+ const HttpVersion& version = HttpVersion(1, 1)) {
+ // Create POST request with JSON body.
+ PostHttpRequestJsonPtr request(new PostHttpRequestJson(HttpRequest::Method::HTTP_POST,
+ "/", version));
+ // Body is a map with a specified parameter included.
+ ElementPtr body = Element::createMap();
+ body->set(parameter_name, Element::create(value));
+ request->setBodyAsJson(body);
+ try {
+ request->finalize();
+
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "failed to create request: " << ex.what();
+ }
+
+ return (request);
+ }
+
+ /// @brief Test that two consecutive requests can be sent over the same
+ /// connection (if persistent, if not persistent two connections will
+ /// be used).
+ ///
+ /// @param version HTTP version to be used.
+ void testConsecutiveRequests(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_, false);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with two different
+ /// destinations simultaneously.
+ void testMultipleDestinations() {
+ // Start two servers running on different ports.
+ ASSERT_NO_THROW(listener_.start());
+ ASSERT_NO_THROW(listener2_.start());
+
+ // Create the client. It will be communicating with the two servers.
+ HttpClient client(io_service_, false);
+
+ // Specify the URLs on which the servers are available.
+ Url url1("http://127.0.0.1:18123");
+ Url url2("http://[::1]:18124");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url1, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Create a request to the second server.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url2, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with the same destination
+ /// address and port but with different TLS contexts so
+ void testMultipleTlsContexts() {
+ // Start only one server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL on which the server is available.
+ Url url("http://127.0.0.1:18123");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Create a request with the second TLS context.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that idle connection can be resumed for second request.
+ void testIdleConnection() {
+ // Start the server that has short idle timeout. It closes the idle
+ // connection after 200ms.
+ ASSERT_NO_THROW(listener3_.start());
+
+ // Create the client that will communicate with this server.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of this server.
+ Url url("http://127.0.0.1:18125");
+
+ // Create the first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ EXPECT_FALSE(ec);
+ }));
+
+ // Run the IO service until the response is received.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure the response has been received.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ // Delay the generation of the second request by 2x server idle timeout.
+ // This should be enough to cause the server to close the connection.
+ ASSERT_NO_THROW(runIOService(SHORT_IDLE_TIMEOUT * 2));
+
+ // Create another request.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ EXPECT_FALSE(ec);
+ }));
+
+ // Actually trigger the second request.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sire that the server has responded.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ // Make sure that two different responses have been received.
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief This test verifies that the client returns IO error code when the
+ /// server is unreachable.
+ void testUnreachable () {
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server. This server is down.
+ Url url("http://127.0.0.1:18123");
+
+ // Create the request.
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ // The server should have returned an IO error.
+ EXPECT_TRUE(ec);
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that an error is returned by the client if the server
+ /// response is malformed.
+ void testMalformedResponse () {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // The response is going to be malformed in such a way that it holds
+ // an invalid content type. We affect the content type by creating
+ // a request that holds a JSON parameter requesting a specific
+ // content type.
+ PostHttpRequestJsonPtr request = createRequest("requested-content-type",
+ "text/html");
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string& parsing_error) {
+ io_service_.stop();
+ // There should be no IO error (answer from the server is received).
+ EXPECT_FALSE(ec);
+ // The response object is NULL because it couldn't be finalized.
+ EXPECT_FALSE(response);
+ // The message parsing error should be returned.
+ EXPECT_FALSE(parsing_error.empty());
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that client times out when it doesn't receive the entire
+ /// response from the server within a desired time.
+ void testClientRequestTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ // Create the request which asks the server to generate a partial
+ // (although well formed) response. The client will be waiting for the
+ // rest of the response to be provided and will eventually time out.
+ PostHttpRequestJsonPtr request1 = createRequest("partial-response", true);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ // This value will be set to true if the connection close callback is
+ // invoked upon time out.
+ auto connection_closed = false;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(100),
+ HttpClient::ConnectHandler(),
+ HttpClient::HandshakeHandler(),
+ [&connection_closed](const int) {
+ // This callback is called when the connection gets closed
+ // by the client.
+ connection_closed = true;
+ })
+ );
+
+ // Create another request after the timeout. It should be handled ok.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ // Make sure that the client has closed the connection upon timeout.
+ EXPECT_TRUE(connection_closed);
+ }
+
+ /// @brief Test that client times out when connection takes too long.
+ void testClientConnectTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+
+ }, HttpClient::RequestTimeout(100),
+
+ // This callback is invoked upon an attempt to connect to the
+ // server. The false value indicates to the HttpClient to not
+ // try to send a request to the server. This simulates the
+ // case of connect() taking very long and should eventually
+ // cause the transaction to time out.
+ [](const boost::system::error_code& /*ec*/, int) {
+ return (false);
+ }));
+
+ // Create another request after the timeout. It should be handled ok.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request, response,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests the behavior of the HTTP client when the premature
+ /// timeout occurs.
+ ///
+ /// The premature timeout may occur when the system clock is moved
+ /// during the transaction. This test simulates this behavior by
+ /// starting new transaction and waiting for the timeout to occur
+ /// before the IO service is ran. The timeout handler is invoked
+ /// first and it resets the transaction state. This test verifies
+ /// that the started transaction tears down gracefully after the
+ /// transaction state is reset.
+ ///
+ /// There are two variants of this test. The first variant schedules
+ /// one transaction before running the IO service. The second variant
+ /// schedules two transactions prior to running the IO service. The
+ /// second transaction is queued, so it is expected that it doesn't
+ /// time out and it runs successfully.
+ ///
+ /// @param queue_two_requests Boolean value indicating if a single
+ /// transaction should be queued (false), or two (true).
+ void testClientRequestLateStart(const bool queue_two_requests) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // Specify the TLS context of the server.
+ TlsContextPtr tls_context;
+
+ // Generate first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+
+ // Use very short timeout to make sure that it occurs before we actually
+ // run the transaction.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, tls_context,
+ request1, response1,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ }, HttpClient::RequestTimeout(1)));
+
+ if (queue_two_requests) {
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, tls_context,
+ request2, response2,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // This second request should be successful.
+ EXPECT_TRUE(ec.value() == 0);
+ EXPECT_TRUE(response);
+ }));
+ }
+
+ // This waits for 3ms to make sure that the timeout occurs before we
+ // run the transaction. This leads to an unusual situation that the
+ // transaction state is reset as a result of the timeout but the
+ // transaction is alive. We want to make sure that the client can
+ // gracefully deal with this situation.
+ usleep(3000);
+
+ // Run the transaction and hope it will gracefully tear down.
+ ASSERT_NO_THROW(runIOService(100));
+
+ // Now try to send another request to make sure that the client
+ // is healthy.
+ PostHttpRequestJsonPtr request3 = createRequest("sequence", 3);
+ HttpResponseJsonPtr response3(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request3, response3,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+
+ // Everything should be ok.
+ EXPECT_TRUE(ec.value() == 0);
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests that underlying TCP socket can be registered and
+ /// unregistered via connection and close callbacks.
+ ///
+ /// It conducts to consecutive requests over the same client.
+ ///
+ /// @param version HTTP version to be used.
+ void testConnectCloseCallbacks(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_, false);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // We should have had 2 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 1 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Tests detection and handling out-of-band socket events
+ ///
+ /// It initiates a transaction and verifies that a mid-transaction call
+ /// to HttpClient::closeIfOutOfBand() has no affect on the connection.
+ /// After successful completion of the transaction, a second call to
+ /// HttpClient::closeIfOutOfBand() is made. This should result in the
+ /// connection being closed.
+ /// This step is repeated to verify that after an OOB closure, transactions
+ /// to the same destination can be processed.
+ ///
+ /// Lastly, we verify that HttpClient::stop() closes the connection correctly.
+ ///
+ /// @param version HTTP version to be used.
+ void testCloseIfOutOfBand(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_.start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_, false);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request1, response1,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ EXPECT_EQ(1, monitor.connect_cnt_); // We should have 1 connect.
+ EXPECT_EQ(0, monitor.close_cnt_); // We should have 0 closes
+ ASSERT_GT(monitor.registered_fd_, -1); // We should have a valid fd.
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(0, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received a response.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+ EXPECT_EQ(1, sequence1->intValue());
+
+ // We should have had 1 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(1, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+
+ // Now let's do another request to the destination to verify that
+ // we'll reopen the connection without issue.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, TlsContextPtr(),
+ request2, response2,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ EXPECT_EQ(2, monitor.connect_cnt_); // We should have 1 connect.
+ EXPECT_EQ(1, monitor.close_cnt_); // We should have 0 closes
+ ASSERT_GT(monitor.registered_fd_, -1); // We should have a valid fd.
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ EXPECT_FALSE(ec);
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received the second response.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_EQ(2, sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 2 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(2, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Simulates external registery of Connection TCP sockets
+ ///
+ /// Provides methods compatible with Connection callbacks for connect
+ /// and close operations.
+ class ExternalMonitor {
+ public:
+ /// @brief Constructor
+ ExternalMonitor()
+ : registered_fd_(-1), connect_cnt_(0), close_cnt_(0) {
+ }
+
+ /// @brief Connect callback handler
+ /// @param ec Error status of the ASIO connect
+ /// @param tcp_native_fd socket descriptor to register
+ bool connectHandler(const boost::system::error_code& ec, int tcp_native_fd) {
+ ++connect_cnt_;
+ if ((!ec || (ec.value() == boost::asio::error::in_progress))
+ && (tcp_native_fd >= 0)) {
+ registered_fd_ = tcp_native_fd;
+ return (true);
+ } else if ((ec.value() == boost::asio::error::already_connected)
+ && (registered_fd_ != tcp_native_fd)) {
+ return (false);
+ }
+
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Handshake callback handler
+ /// @param ec Error status of the ASIO connect
+ bool handshakeHandler(const boost::system::error_code&, int) {
+ ADD_FAILURE() << "handshake callback handler is called";
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Close callback handler
+ ///
+ /// @param tcp_native_fd socket descriptor to register
+ void closeHandler(int tcp_native_fd) {
+ ++close_cnt_;
+ EXPECT_EQ(tcp_native_fd, registered_fd_) << "closeHandler fd mismatch";
+ if (tcp_native_fd >= 0) {
+ registered_fd_ = -1;
+ }
+ }
+
+ /// @brief Keeps track of socket currently "registered" for external monitoring.
+ int registered_fd_;
+
+ /// @brief Tracks how many times the connect callback is invoked.
+ int connect_cnt_;
+
+ /// @brief Tracks how many times the close callback is invoked.
+ int close_cnt_;
+ };
+
+ /// @brief Instance of the listener used in the tests.
+ HttpListener listener_;
+
+ /// @brief Instance of the second listener used in the tests.
+ HttpListener listener2_;
+
+ /// @brief Instance of the third listener used in the tests (with short idle
+ /// timeout).
+ HttpListener listener3_;
+
+};
+
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpClientTest, consecutiveRequests) {
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpClientTest, consecutiveRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpClientTest, closeBetweenRequests) {
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpClientTest, closeBetweenRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpClientTest, multipleDestinations) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpClientTest, multipleDestinationsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpClientTest, multipleTlsContexts) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpClientTest, multipleTlsContextsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpClientTest, idleConnection) {
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpClientTest, idleConnectionMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpClientTest, unreachable) {
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpClientTest, unreachableMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpClientTest, malformedResponse) {
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpClientTest, malformedResponseMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpClientTest, clientRequestTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpClientTest, clientRequestTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpClientTest, clientRequestLateStartNoQueue) {
+ testClientRequestLateStart(false);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpClientTest, clientRequestLateStartNoQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(false);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpClientTest, clientRequestLateStartQueue) {
+ testClientRequestLateStart(true);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpClientTest, clientRequestLateStartQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(true);
+}
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpClientTest, clientConnectTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpClientTest, clientConnectTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpClientTest, connectCloseCallbacks) {
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpClientTest, connectCloseCallbacksMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpClientTest, closeIfOutOfBand) {
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpClientTest, closeIfOutOfBandMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+}
diff --git a/src/lib/http/tests/test_http_client.h b/src/lib/http/tests/test_http_client.h
new file mode 100644
index 0000000..f95b111
--- /dev/null
+++ b/src/lib/http/tests/test_http_client.h
@@ -0,0 +1,273 @@
+// Copyright (C) 2017-2021 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 TEST_HTTP_CLIENT_H
+#define TEST_HTTP_CLIENT_H
+
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/http_types.h>
+
+#include <boost/asio/read.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <gtest/gtest.h>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+
+/// @brief Entity which can connect to the HTTP server endpoint.
+class TestHttpClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error or completion.
+ /// @param server_address string containing the IP address of the server.
+ /// @param port port number of the server.
+ explicit TestHttpClient(IOService& io_service,
+ const std::string& server_address = "127.0.0.1",
+ uint16_t port = 18123)
+ : io_service_(io_service.get_io_service()), socket_(io_service_),
+ buf_(), response_(), server_address_(server_address),
+ server_port_(port), receive_done_(false) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TestHttpClient() {
+ close();
+ }
+
+ /// @brief Send HTTP request specified in textual format.
+ ///
+ /// @param request HTTP request in the textual format.
+ void startRequest(const std::string& request) {
+ tcp::endpoint endpoint(address::from_string(server_address_), server_port_);
+ socket_.async_connect(endpoint,
+ [this, request](const boost::system::error_code& ec) {
+ receive_done_ = false;
+ if (ec) {
+ // One would expect that async_connect wouldn't return
+ // EINPROGRESS error code, but simply wait for the connection
+ // to get established before the handler is invoked. It turns out,
+ // however, that on some OSes the connect handler may receive this
+ // error code which doesn't necessarily indicate a problem.
+ // Making an attempt to write and read from this socket will
+ // typically succeed. So, we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+ sendRequest(request);
+ });
+ }
+
+ /// @brief Send HTTP request.
+ ///
+ /// @param request HTTP request in the textual format.
+ void sendRequest(const std::string& request) {
+ sendPartialRequest(request);
+ }
+
+ /// @brief Send part of the HTTP request.
+ ///
+ /// @param request part of the HTTP request to be sent.
+ void sendPartialRequest(std::string request) {
+ socket_.async_send(boost::asio::buffer(request.data(), request.size()),
+ [this, request](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) mutable {
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again make sure there is no garbage in the
+ // bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+
+ // Remove the part of the request which has been sent.
+ if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
+ request.erase(0, bytes_transferred);
+ }
+
+ // Continue sending request data if there are still some data to be
+ // sent.
+ if (!request.empty()) {
+ sendPartialRequest(request);
+
+ } else {
+ // Request has been sent. Start receiving response.
+ response_.clear();
+ receivePartialResponse();
+ }
+ });
+ }
+
+ /// @brief Receive response from the server.
+ void receivePartialResponse() {
+ socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (ec) {
+ // IO service stopped so simply return.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again, make sure that there is no garbage
+ // in the bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ // Error occurred, bail...
+ ADD_FAILURE() << "error occurred while receiving HTTP"
+ " response from the server: " << ec.message();
+ io_service_.stop();
+ }
+ }
+
+ if (bytes_transferred > 0) {
+ response_.insert(response_.end(), buf_.data(),
+ buf_.data() + bytes_transferred);
+ }
+
+ // Two consecutive new lines end the part of the response we're
+ // expecting.
+ if (response_.find("\r\n\r\n", 0) != std::string::npos) {
+ receive_done_ = true;
+ io_service_.stop();
+ } else {
+ receivePartialResponse();
+ }
+ });
+ }
+
+ /// @brief Checks if the TCP connection is still open.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// Unfortunately expected failure depends on a race between the read
+ /// and the other side close so to check if the connection is closed
+ /// please use @c isConnectionClosed instead.
+ ///
+ /// @return true if the TCP connection is open.
+ bool isConnectionAlive() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = socket_.non_blocking();
+ // Set the socket to non blocking mode. We're going to test if the socket
+ // returns would_block status on the attempt to read from it.
+ socket_.non_blocking(true);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ socket_.non_blocking(non_blocking_orig);
+
+ // If the connection is alive we'd typically get would_block status code.
+ // If there are any data that haven't been read we may also get success
+ // status. We're guessing that try_again may also be returned by some
+ // implementations in some situations. Any other error code indicates a
+ // problem with the connection so we assume that the connection has been
+ // closed.
+ return (!ec || (ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block));
+ }
+
+ /// @brief Checks if the TCP connection is already closed.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// The read can block so this must be used to check if a connection
+ /// is alive so to check if the connection is alive please always
+ /// use @c isConnectionAlive.
+ ///
+ /// @return true if the TCP connection is closed.
+ bool isConnectionClosed() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = socket_.non_blocking();
+ // Set the socket to blocking mode. We're going to test if the socket
+ // returns eof status on the attempt to read from it.
+ socket_.non_blocking(false);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(socket_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ socket_.non_blocking(non_blocking_orig);
+
+ // If the connection is closed we'd typically get eof status code.
+ return (ec.value() == boost::asio::error::eof);
+ }
+
+ /// @brief Close connection.
+ void close() {
+ socket_.close();
+ }
+
+ /// @brief Returns the HTTP response string.
+ ///
+ /// @return string containing the response.
+ std::string getResponse() const {
+ return (response_);
+ }
+
+ /// @brief Returns true if the receive completed without error.
+ ///
+ /// @return True if the receive completed successfully, false
+ /// otherwise.
+ bool receiveDone() {
+ return (receive_done_);
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ boost::asio::ip::tcp::socket socket_;
+
+ /// @brief Buffer into which response is written.
+ std::array<char, 8192> buf_;
+
+ /// @brief Response in the textual format.
+ std::string response_;
+
+ /// @brief IP address of the server.
+ std::string server_address_;
+
+ /// @brief IP port of the server.
+ uint16_t server_port_;
+
+ /// @brief Set to true when the receive has completed successfully.
+ bool receive_done_;
+};
+
+/// @brief Pointer to the TestHttpClient.
+typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
+
+#endif
diff --git a/src/lib/http/tests/testdata/empty b/src/lib/http/tests/testdata/empty
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/lib/http/tests/testdata/empty
diff --git a/src/lib/http/tests/testdata/hiddenp b/src/lib/http/tests/testdata/hiddenp
new file mode 100644
index 0000000..e8b2395
--- /dev/null
+++ b/src/lib/http/tests/testdata/hiddenp
@@ -0,0 +1 @@
+KeaTest \ No newline at end of file
diff --git a/src/lib/http/tests/testdata/hiddens b/src/lib/http/tests/testdata/hiddens
new file mode 100644
index 0000000..f52fb83
--- /dev/null
+++ b/src/lib/http/tests/testdata/hiddens
@@ -0,0 +1 @@
+kea:test \ No newline at end of file
diff --git a/src/lib/http/tests/testdata/hiddenu b/src/lib/http/tests/testdata/hiddenu
new file mode 100644
index 0000000..801489a
--- /dev/null
+++ b/src/lib/http/tests/testdata/hiddenu
@@ -0,0 +1 @@
+keatest \ No newline at end of file
diff --git a/src/lib/http/tests/tls_client_unittests.cc b/src/lib/http/tests/tls_client_unittests.cc
new file mode 100644
index 0000000..fc999f5
--- /dev/null
+++ b/src/lib/http/tests/tls_client_unittests.cc
@@ -0,0 +1,1398 @@
+// Copyright (C) 2017-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_acceptor.h>
+#include <asiolink/testutils/test_tls.h>
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/http_types.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <list>
+#include <sstream>
+#include <string>
+
+#ifdef WITH_BOTAN
+#define DISABLE_SOME_TESTS
+#endif
+#ifdef WITH_OPENSSL
+#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#define DISABLE_SOME_TESTS
+#endif
+#endif
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+/// @todo: put the common part of client and server tests in its own file(s).
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch HTTP service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// This method generates 3 types of responses:
+ /// - response with a requested content type,
+ /// - partial response with incomplete JSON body,
+ /// - response with JSON body copied from the request.
+ ///
+ /// The first one is useful to test situations when received response can't
+ /// be parsed because of the content type mismatch. The second one is useful
+ /// to test request timeouts. The third type is used by most of the unit tests
+ /// to test successful transactions.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Check access parameters.
+ if (HttpRequest::recordSubject_) {
+ EXPECT_TRUE(request->getTls());
+ EXPECT_EQ("kea-client", request->getSubject());
+ }
+ if (HttpRequest::recordIssuer_) {
+ EXPECT_TRUE(request->getTls());
+ EXPECT_EQ("kea-ca", request->getIssuer());
+ }
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ ConstElementPtr body;
+ if (request_json) {
+ body = request_json->getBodyAsJson();
+ if (body) {
+ // Check if the client requested one of the two first response
+ // types.
+ GenericResponsePtr response;
+ ConstElementPtr content_type = body->get("requested-content-type");
+ ConstElementPtr partial_response = body->get("partial-response");
+ if (content_type || partial_response) {
+ // The first two response types can only be generated using the
+ // generic response as we have to explicitly modify some of the
+ // values.
+ response.reset(new GenericResponse(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ HttpResponseContextPtr ctx = response->context();
+
+ if (content_type) {
+ // Provide requested content type.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ content_type->stringValue()));
+ // It doesn't matter what body is there.
+ ctx->body_ = "abcd";
+ response->finalize();
+
+ } else {
+ // Generate JSON response.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ "application/json"));
+ // The body lacks '}' so the client will be waiting for it and
+ // eventually should time out.
+ ctx->body_ = "{";
+ response->finalize();
+ // The auto generated Content-Length header would be based on the
+ // body size (so set to 1 byte). We have to override it to
+ // account for the missing '}' character.
+ response->setContentLength(2);
+ }
+ return (response);
+ }
+ }
+ }
+
+ // Third type of response is requested.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // If body was included in the request. Let's copy it.
+ if (body) {
+ response->setBodyAsJson(body);
+ }
+
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ HttpListenerTest()
+ : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+ test_timer_(io_service_), run_io_service_timer_(io_service_) {
+ test_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran (ms).
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&HttpListenerTest::timeoutHandler,
+ this, false),
+ timeout, IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+};
+
+/// @brief Test fixture class for testing HTTP client.
+class HttpsClientTest : public HttpListenerTest {
+public:
+
+ /// @brief Constructor.
+ HttpsClientTest()
+ : HttpListenerTest(), listener_(), listener2_(), listener3_(),
+ server_context_(), client_context_() {
+ configServer(server_context_);
+ configClient(client_context_);
+ listener_.reset(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ server_context_,
+ factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)));
+ listener2_.reset(new HttpListener(io_service_,
+ IOAddress(IPV6_SERVER_ADDRESS),
+ SERVER_PORT + 1,
+ server_context_,
+ factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)));
+ listener3_.reset(new HttpListener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT + 2,
+ server_context_,
+ factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(SHORT_IDLE_TIMEOUT)));
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Destructor.
+ ~HttpsClientTest() {
+ listener_->stop();
+ listener2_->stop();
+ listener3_->stop();
+ io_service_.poll();
+ MultiThreadingMgr::instance().setMode(false);
+ HttpRequest::recordSubject_ = false;
+ HttpRequest::recordIssuer_ = false;
+ }
+
+ /// @brief Creates HTTP request with JSON body.
+ ///
+ /// It includes a JSON parameter with a specified value.
+ ///
+ /// @param parameter_name JSON parameter to be included.
+ /// @param value JSON parameter value.
+ /// @param version HTTP version to be used. Default is HTTP/1.1.
+ template<typename ValueType>
+ PostHttpRequestJsonPtr createRequest(const std::string& parameter_name,
+ const ValueType& value,
+ const HttpVersion& version = HttpVersion(1, 1)) {
+ // Create POST request with JSON body.
+ PostHttpRequestJsonPtr request(new PostHttpRequestJson(HttpRequest::Method::HTTP_POST,
+ "/", version));
+ // Body is a map with a specified parameter included.
+ ElementPtr body = Element::createMap();
+ body->set(parameter_name, Element::create(value));
+ request->setBodyAsJson(body);
+ try {
+ request->finalize();
+
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "failed to create request: " << ex.what();
+ }
+
+ return (request);
+ }
+
+ /// @brief Test that two consecutive requests can be sent over the same
+ /// connection (if persistent, if not persistent two connections will
+ /// be used).
+ ///
+ /// @param version HTTP version to be used.
+ void testConsecutiveRequests(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_, false);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with two different
+ /// destinations simultaneously.
+ void testMultipleDestinations() {
+ // Start two servers running on different ports.
+ ASSERT_NO_THROW(listener_->start());
+ ASSERT_NO_THROW(listener2_->start());
+
+ // Create the client. It will be communicating with the two servers.
+ HttpClient client(io_service_, false);
+
+ // Specify the URLs on which the servers are available.
+ Url url1("http://127.0.0.1:18123");
+ Url url2("http://[::1]:18124");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url1, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Create a request to the second server.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url2, client_context_,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that the client can communicate with the same destination
+ /// address and port but with different TLS contexts so
+ void testMultipleTlsContexts() {
+ // Start only one server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Create a second client context.
+ TlsContextPtr client_context2;
+ configClient(client_context2);
+
+ // Specify the URL on which the server is available.
+ Url url("http://127.0.0.1:18123");
+
+ // Create a request to the first server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Create a request with the second TLS context.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context2,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Record subject and issuer: they will be checked during response creation.
+ HttpRequest::recordSubject_ = true;
+ HttpRequest::recordIssuer_ = true;
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure we have received two different responses.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief Test that idle connection can be resumed for second request.
+ void testIdleConnection() {
+ // Start the server that has short idle timeout. It closes the idle
+ // connection after 200ms.
+ ASSERT_NO_THROW(listener3_->start());
+
+ // Create the client that will communicate with this server.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of this server.
+ Url url("http://127.0.0.1:18125");
+
+ // Create the first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Run the IO service until the response is received.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure the response has been received.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ // Delay the generation of the second request by 2x server idle timeout.
+ // This should be enough to cause the server to close the connection.
+ ASSERT_NO_THROW(runIOService(SHORT_IDLE_TIMEOUT * 2));
+
+ // Create another request.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this](const boost::system::error_code& ec, const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the second request.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sire that the server has responded.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+
+ // Make sure that two different responses have been received.
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+ }
+
+ /// @brief This test verifies that the client returns IO error code when the
+ /// server is unreachable.
+ void testUnreachable () {
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server. This server is down.
+ Url url("http://127.0.0.1:18123");
+
+ // Create the request.
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+ // The server should have returned an IO error.
+ if (!ec) {
+ ADD_FAILURE() << "asyncSendRequest didn't fail";
+ }
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that an error is returned by the client if the server
+ /// response is malformed.
+ void testMalformedResponse () {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // The response is going to be malformed in such a way that it holds
+ // an invalid content type. We affect the content type by creating
+ // a request that holds a JSON parameter requesting a specific
+ // content type.
+ PostHttpRequestJsonPtr request = createRequest("requested-content-type",
+ "text/html");
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string& parsing_error) {
+ io_service_.stop();
+ // There should be no IO error (answer from the server is received).
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ // The response object is NULL because it couldn't be finalized.
+ EXPECT_FALSE(response);
+ // The message parsing error should be returned.
+ EXPECT_FALSE(parsing_error.empty());
+ }));
+
+ // Actually trigger the request.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Test that client times out when it doesn't receive the entire
+ /// response from the server within a desired time.
+ void testClientRequestTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ // Create the request which asks the server to generate a partial
+ // (although well formed) response. The client will be waiting for the
+ // rest of the response to be provided and will eventually time out.
+ PostHttpRequestJsonPtr request1 = createRequest("partial-response", true);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ // This value will be set to true if the connection close callback is
+ // invoked upon time out.
+ auto connection_closed = false;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(100),
+ HttpClient::ConnectHandler(),
+ HttpClient::HandshakeHandler(),
+ [&connection_closed](const int) {
+ // This callback is called when the connection gets closed
+ // by the client.
+ connection_closed = true;
+ })
+ );
+
+ // Create another request after the timeout. It should be handled ok.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ // Make sure that the client has closed the connection upon timeout.
+ EXPECT_TRUE(connection_closed);
+ }
+
+ /// @brief Test that client times out when connection takes too long.
+ void testClientConnectTimeout() {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ unsigned cb_num = 0;
+
+ PostHttpRequestJsonPtr request = createRequest("sequence", 1);
+ HttpResponseJsonPtr response(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this, &cb_num](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(100),
+
+ // This callback is invoked upon an attempt to connect to the
+ // server. The false value indicates to the HttpClient to not
+ // try to send a request to the server. This simulates the
+ // case of connect() taking very long and should eventually
+ // cause the transaction to time out.
+ [](const boost::system::error_code& /*ec*/, int) {
+ return (false);
+ }));
+
+ // Create another request after the timeout. It should be handled ok.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request, response,
+ [this, &cb_num](const boost::system::error_code& /*ec*/,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++cb_num > 1) {
+ io_service_.stop();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests the behavior of the HTTP client when the premature
+ /// timeout occurs.
+ ///
+ /// The premature timeout may occur when the system clock is moved
+ /// during the transaction. This test simulates this behavior by
+ /// starting new transaction and waiting for the timeout to occur
+ /// before the IO service is ran. The timeout handler is invoked
+ /// first and it resets the transaction state. This test verifies
+ /// that the started transaction tears down gracefully after the
+ /// transaction state is reset.
+ ///
+ /// There are two variants of this test. The first variant schedules
+ /// one transaction before running the IO service. The second variant
+ /// schedules two transactions prior to running the IO service. The
+ /// second transaction is queued, so it is expected that it doesn't
+ /// time out and it runs successfully.
+ ///
+ /// @param queue_two_requests Boolean value indicating if a single
+ /// transaction should be queued (false), or two (true).
+ void testClientRequestLateStart(const bool queue_two_requests) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create the client.
+ HttpClient client(io_service_, false);
+
+ // Specify the URL of the server.
+ Url url("http://127.0.0.1:18123");
+
+ // Generate first request.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+
+ // Use very short timeout to make sure that it occurs before we actually
+ // run the transaction.
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // In this particular case we know exactly the type of the
+ // IO error returned, because the client explicitly sets this
+ // error code.
+ EXPECT_TRUE(ec.value() == boost::asio::error::timed_out);
+ // There should be no response returned.
+ EXPECT_FALSE(response);
+ },
+ HttpClient::RequestTimeout(1)));
+
+ if (queue_two_requests) {
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [](const boost::system::error_code& ec,
+ const HttpResponsePtr& response,
+ const std::string&) {
+
+ // This second request should be successful.
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ EXPECT_TRUE(response);
+ }));
+ }
+
+ // This waits for 3ms to make sure that the timeout occurs before we
+ // run the transaction. This leads to an unusual situation that the
+ // transaction state is reset as a result of the timeout but the
+ // transaction is alive. We want to make sure that the client can
+ // gracefully deal with this situation.
+ usleep(3000);
+
+ // Run the transaction and hope it will gracefully tear down.
+ ASSERT_NO_THROW(runIOService(100));
+
+ // Now try to send another request to make sure that the client
+ // is healthy.
+ PostHttpRequestJsonPtr request3 = createRequest("sequence", 3);
+ HttpResponseJsonPtr response3(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request3, response3,
+ [this](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ io_service_.stop();
+
+ // Everything should be ok.
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ }));
+
+ // Actually trigger the requests.
+ ASSERT_NO_THROW(runIOService());
+ }
+
+ /// @brief Tests that underlying TCP socket can be registered and
+ /// unregistered via connection and close callbacks.
+ ///
+ /// It conducts to consecutive requests over the same client.
+ ///
+ /// @param version HTTP version to be used.
+ void testConnectCloseCallbacks(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_, false);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Initiate another request to the destination.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &resp_num](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num > 1) {
+ io_service_.stop();
+ }
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // We should have had 2 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Make sure that the received responses are different. We check that by
+ // comparing value of the sequence parameters.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_NE(sequence1->intValue(), sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 1 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Tests detection and handling out-of-band socket events
+ ///
+ /// It initiates a transaction and verifies that a mid-transaction call
+ /// to HttpClient::closeIfOutOfBand() has no affect on the connection.
+ /// After successful completion of the transaction, a second call to
+ /// HttpClient::closeIfOutOfBand() is made. This should result in the
+ /// connection being closed.
+ /// This step is repeated to verify that after an OOB closure, transactions
+ /// to the same destination can be processed.
+ ///
+ /// Lastly, we verify that HttpClient::stop() closes the connection correctly.
+ ///
+ /// @param version HTTP version to be used.
+ void testCloseIfOutOfBand(const HttpVersion& version) {
+ // Start the server.
+ ASSERT_NO_THROW(listener_->start());
+
+ // Create a client and specify the URL on which the server can be reached.
+ HttpClient client(io_service_, false);
+ Url url("http://127.0.0.1:18123");
+
+ // Initiate request to the server.
+ PostHttpRequestJsonPtr request1 = createRequest("sequence", 1, version);
+ HttpResponseJsonPtr response1(new HttpResponseJson());
+ unsigned resp_num = 0;
+ ExternalMonitor monitor;
+
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request1, response1,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ // We should have 1 connect.
+ EXPECT_EQ(1, monitor.connect_cnt_);
+ // We should have 1 handshake.
+ EXPECT_EQ(1, monitor.handshake_cnt_);
+ // We should have 0 closes
+ EXPECT_EQ(0, monitor.close_cnt_);
+ // We should have a valid fd.
+ ASSERT_GT(monitor.registered_fd_, -1);
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(0, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received a response.
+ ASSERT_TRUE(response1);
+ ConstElementPtr sequence1 = response1->getJsonElement("sequence");
+ ASSERT_TRUE(sequence1);
+ EXPECT_EQ(1, sequence1->intValue());
+
+ // We should have had 1 connect invocations, no closes
+ // and a valid registered fd
+ EXPECT_EQ(1, monitor.connect_cnt_);
+ EXPECT_EQ(0, monitor.close_cnt_);
+ EXPECT_GT(monitor.registered_fd_, -1);
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+
+ // Now let's do another request to the destination to verify that
+ // we'll reopen the connection without issue.
+ PostHttpRequestJsonPtr request2 = createRequest("sequence", 2, version);
+ HttpResponseJsonPtr response2(new HttpResponseJson());
+ resp_num = 0;
+ ASSERT_NO_THROW(client.asyncSendRequest(url, client_context_,
+ request2, response2,
+ [this, &client, &resp_num, &monitor](const boost::system::error_code& ec,
+ const HttpResponsePtr&,
+ const std::string&) {
+ if (++resp_num == 1) {
+ io_service_.stop();
+ }
+
+ // We should have 1 connect.
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ // We should have 2 handshake.
+ EXPECT_EQ(2, monitor.handshake_cnt_);
+ // We should have 0 closes
+ EXPECT_EQ(1, monitor.close_cnt_);
+ // We should have a valid fd.
+ ASSERT_GT(monitor.registered_fd_, -1);
+ int orig_fd = monitor.registered_fd_;
+
+ // Test our socket for OOBness.
+ client.closeIfOutOfBand(monitor.registered_fd_);
+
+ // Since we're in a transaction, we should have no closes and
+ // the same valid fd.
+ EXPECT_EQ(1, monitor.close_cnt_);
+ ASSERT_EQ(monitor.registered_fd_, orig_fd);
+
+ if (ec) {
+ ADD_FAILURE() << "asyncSendRequest failed: " << ec.message();
+ }
+ },
+ HttpClient::RequestTimeout(10000),
+ std::bind(&ExternalMonitor::connectHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::handshakeHandler, &monitor, ph::_1, ph::_2),
+ std::bind(&ExternalMonitor::closeHandler, &monitor, ph::_1)
+ ));
+
+ // Actually trigger the requests. The requests should be handlded by the
+ // server one after another. While the first request is being processed
+ // the server should queue another one.
+ ASSERT_NO_THROW(runIOService());
+
+ // Make sure that we received the second response.
+ ASSERT_TRUE(response2);
+ ConstElementPtr sequence2 = response2->getJsonElement("sequence");
+ ASSERT_TRUE(sequence2);
+ EXPECT_EQ(2, sequence2->intValue());
+
+ // Stopping the client the close the connection.
+ client.stop();
+
+ // We should have had 2 connect invocations, 2 closes
+ // and an invalid registered fd
+ EXPECT_EQ(2, monitor.connect_cnt_);
+ EXPECT_EQ(2, monitor.close_cnt_);
+ EXPECT_EQ(-1, monitor.registered_fd_);
+ }
+
+ /// @brief Simulates external registry of Connection TCP sockets
+ ///
+ /// Provides methods compatible with Connection callbacks for connect
+ /// and close operations.
+ class ExternalMonitor {
+ public:
+ /// @brief Constructor
+ ExternalMonitor()
+ : registered_fd_(-1), connect_cnt_(0), handshake_cnt_(0),
+ close_cnt_(0) {
+ }
+
+ /// @brief Connect callback handler
+ /// @param ec Error status of the ASIO connect
+ /// @param tcp_native_fd socket descriptor to register
+ bool connectHandler(const boost::system::error_code& ec, int tcp_native_fd) {
+ ++connect_cnt_;
+ if ((!ec || (ec.value() == boost::asio::error::in_progress))
+ && (tcp_native_fd >= 0)) {
+ registered_fd_ = tcp_native_fd;
+ return (true);
+ } else if ((ec.value() == boost::asio::error::already_connected)
+ && (registered_fd_ != tcp_native_fd)) {
+ return (false);
+ }
+
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Handshake callback handler
+ /// @param ec Error status of the ASIO connect
+ bool handshakeHandler(const boost::system::error_code&, int) {
+ ++handshake_cnt_;
+ // ec indicates an error, return true, so that error can be handled
+ // by Connection logic.
+ return (true);
+ }
+
+ /// @brief Close callback handler
+ ///
+ /// @param tcp_native_fd socket descriptor to register
+ void closeHandler(int tcp_native_fd) {
+ ++close_cnt_;
+ EXPECT_EQ(tcp_native_fd, registered_fd_) << "closeHandler fd mismatch";
+ if (tcp_native_fd >= 0) {
+ registered_fd_ = -1;
+ }
+ }
+
+ /// @brief Keeps track of socket currently "registered" for external monitoring.
+ int registered_fd_;
+
+ /// @brief Tracks how many times the connect callback is invoked.
+ int connect_cnt_;
+
+ /// @brief Tracks how many times the handshake callback is invoked.
+ int handshake_cnt_;
+
+ /// @brief Tracks how many times the close callback is invoked.
+ int close_cnt_;
+ };
+
+ /// @brief Instance of the listener used in the tests.
+ std::unique_ptr<HttpListener> listener_;
+
+ /// @brief Instance of the second listener used in the tests.
+ std::unique_ptr<HttpListener> listener2_;
+
+ /// @brief Instance of the third listener used in the tests (with short idle
+ /// timeout).
+ std::unique_ptr<HttpListener> listener3_;
+
+ /// @brief Server TLS context.
+ TlsContextPtr server_context_;
+
+ /// @brief Client TLS context.
+ TlsContextPtr client_context_;
+};
+
+#ifndef DISABLE_SOME_TESTS
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpsClientTest, consecutiveRequests) {
+
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+
+// Test that two consecutive requests can be sent over the same (persistent)
+// connection.
+TEST_F(HttpsClientTest, consecutiveRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 1)));
+}
+#endif
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpsClientTest, closeBetweenRequests) {
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that two consecutive requests can be sent over non-persistent connection.
+// This is achieved by sending HTTP/1.0 requests, which are non-persistent by
+// default. The client should close the connection right after receiving a response
+// from the server.
+TEST_F(HttpsClientTest, closeBetweenRequestsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConsecutiveRequests(HttpVersion(1, 0)));
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpsClientTest, multipleDestinations) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can communicate with two different destinations
+// simultaneously.
+TEST_F(HttpsClientTest, multipleDestinationsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleDestinations());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpsClientTest, multipleTlsContexts) {
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that the client can use two different TLS contexts to the same
+// destination address and port simultaneously.
+TEST_F(HttpsClientTest, multipleTlsContextsMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMultipleTlsContexts());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpsClientTest, idleConnection) {
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// Test that idle connection can be resumed for second request.
+TEST_F(HttpsClientTest, idleConnectionMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testIdleConnection());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpsClientTest, unreachable) {
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// This test verifies that the client returns IO error code when the
+// server is unreachable.
+TEST_F(HttpsClientTest, unreachableMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testUnreachable());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpsClientTest, malformedResponse) {
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that an error is returned by the client if the server response is
+// malformed.
+TEST_F(HttpsClientTest, malformedResponseMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testMalformedResponse());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpsClientTest, clientRequestTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// Test that client times out when it doesn't receive the entire response
+// from the server within a desired time.
+TEST_F(HttpsClientTest, clientRequestTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientRequestTimeout());
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpsClientTest, DISABLED_clientRequestLateStartNoQueue) {
+ testClientRequestLateStart(false);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// (and unexpected) timeout occurs. The premature timeout may be caused
+// by the system clock move.
+TEST_F(HttpsClientTest, DISABLED_clientRequestLateStartNoQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(false);
+}
+
+#ifndef DISABLE_SOME_TESTS
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpsClientTest, clientRequestLateStartQueue) {
+
+ testClientRequestLateStart(true);
+}
+
+// This test verifies the behavior of the HTTP client when the premature
+// timeout occurs and there are requests queued after the request which
+// times out.
+TEST_F(HttpsClientTest, clientRequestLateStartQueueMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ testClientRequestLateStart(true);
+}
+#endif
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpsClientTest, clientConnectTimeout) {
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+// Test that client times out when connection takes too long.
+TEST_F(HttpsClientTest, clientConnectTimeoutMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testClientConnectTimeout());
+}
+
+#ifndef DISABLE_SOME_TESTS
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpsClientTest, connectCloseCallbacks) {
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+
+/// Tests that connect and close callbacks work correctly.
+TEST_F(HttpsClientTest, connectCloseCallbacksMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testConnectCloseCallbacks(HttpVersion(1, 1)));
+}
+#endif
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpsClientTest, closeIfOutOfBand) {
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+/// Tests that HttpClient::closeIfOutOfBand works correctly.
+TEST_F(HttpsClientTest, closeIfOutOfBandMultiThreading) {
+ MultiThreadingMgr::instance().setMode(true);
+ ASSERT_NO_FATAL_FAILURE(testCloseIfOutOfBand(HttpVersion(1, 1)));
+}
+
+}
diff --git a/src/lib/http/tests/tls_server_unittests.cc b/src/lib/http/tests/tls_server_unittests.cc
new file mode 100644
index 0000000..a2a6f9d
--- /dev/null
+++ b/src/lib/http/tests/tls_server_unittests.cc
@@ -0,0 +1,1253 @@
+// Copyright (C) 2017-2022 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/tls_acceptor.h>
+#include <asiolink/testutils/test_tls.h>
+#include <cc/data.h>
+#include <http/client.h>
+#include <http/http_types.h>
+#include <http/listener.h>
+#include <http/listener_impl.h>
+#include <http/post_request_json.h>
+#include <http/response_creator.h>
+#include <http/response_creator_factory.h>
+#include <http/response_json.h>
+#include <http/tests/response_test.h>
+#include <http/url.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <list>
+#include <sstream>
+#include <string>
+
+using namespace boost::asio;
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::data;
+using namespace isc::http;
+using namespace isc::http::test;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+/// @todo: put the common part of client and server tests in its own file(s).
+
+namespace {
+
+/// @brief IP address to which HTTP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch HTTP service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which HTTP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Persistent connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test HTTP response.
+typedef TestHttpResponseBase<HttpResponseJson> Response;
+
+/// @brief Pointer to test HTTP response.
+typedef boost::shared_ptr<Response> ResponsePtr;
+
+/// @brief Generic test HTTP response.
+typedef TestHttpResponseBase<HttpResponse> GenericResponse;
+
+/// @brief Pointer to generic test HTTP response.
+typedef boost::shared_ptr<GenericResponse> GenericResponsePtr;
+
+/// @brief Implementation of the @ref HttpResponseCreator.
+class TestHttpResponseCreator : public HttpResponseCreator {
+public:
+
+ /// @brief Create a new request.
+ ///
+ /// @return Pointer to the new instance of the @ref HttpRequest.
+ virtual HttpRequestPtr
+ createNewHttpRequest() const {
+ return (HttpRequestPtr(new PostHttpRequestJson()));
+ }
+
+private:
+
+ /// @brief Creates HTTP response.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP response.
+ virtual HttpResponsePtr
+ createStockHttpResponse(const HttpRequestPtr& request,
+ const HttpStatusCode& status_code) const {
+ // The request hasn't been finalized so the request object
+ // doesn't contain any information about the HTTP version number
+ // used. But, the context should have this data (assuming the
+ // HTTP version is parsed ok).
+ HttpVersion http_version(request->context()->http_version_major_,
+ request->context()->http_version_minor_);
+ // This will generate the response holding JSON content.
+ ResponsePtr response(new Response(http_version, status_code));
+ response->finalize();
+ return (response);
+ }
+
+ /// @brief Creates HTTP response.
+ ///
+ /// This method generates 3 types of responses:
+ /// - response with a requested content type,
+ /// - partial response with incomplete JSON body,
+ /// - response with JSON body copied from the request.
+ ///
+ /// The first one is useful to test situations when received response can't
+ /// be parsed because of the content type mismatch. The second one is useful
+ /// to test request timeouts. The third type is used by most of the unit tests
+ /// to test successful transactions.
+ ///
+ /// @param request Pointer to the HTTP request.
+ /// @return Pointer to the generated HTTP OK response with no content.
+ virtual HttpResponsePtr
+ createDynamicHttpResponse(HttpRequestPtr request) {
+ // Request must always be JSON.
+ PostHttpRequestJsonPtr request_json =
+ boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+ ConstElementPtr body;
+ if (request_json) {
+ body = request_json->getBodyAsJson();
+ if (body) {
+ // Check if the client requested one of the two first response
+ // types.
+ GenericResponsePtr response;
+ ConstElementPtr content_type = body->get("requested-content-type");
+ ConstElementPtr partial_response = body->get("partial-response");
+ if (content_type || partial_response) {
+ // The first two response types can only be generated using the
+ // generic response as we have to explicitly modify some of the
+ // values.
+ response.reset(new GenericResponse(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ HttpResponseContextPtr ctx = response->context();
+
+ if (content_type) {
+ // Provide requested content type.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ content_type->stringValue()));
+ // It doesn't matter what body is there.
+ ctx->body_ = "abcd";
+ response->finalize();
+
+ } else {
+ // Generate JSON response.
+ ctx->headers_.push_back(HttpHeaderContext("Content-Type",
+ "application/json"));
+ // The body lacks '}' so the client will be waiting for it and
+ // eventually should time out.
+ ctx->body_ = "{";
+ response->finalize();
+ // The auto generated Content-Length header would be based on the
+ // body size (so set to 1 byte). We have to override it to
+ // account for the missing '}' character.
+ response->setContentLength(2);
+ }
+ return (response);
+ }
+ }
+ }
+
+ // Third type of response is requested.
+ ResponsePtr response(new Response(request->getHttpVersion(),
+ HttpStatusCode::OK));
+ // If body was included in the request. Let's copy it.
+ if (body) {
+ response->setBodyAsJson(body);
+ }
+
+ response->finalize();
+ return (response);
+ }
+};
+
+/// @brief Implementation of the test @ref HttpResponseCreatorFactory.
+///
+/// This factory class creates @ref TestHttpResponseCreator instances.
+class TestHttpResponseCreatorFactory : public HttpResponseCreatorFactory {
+public:
+
+ /// @brief Creates @ref TestHttpResponseCreator instance.
+ virtual HttpResponseCreatorPtr create() const {
+ HttpResponseCreatorPtr response_creator(new TestHttpResponseCreator());
+ return (response_creator);
+ }
+};
+
+/// @brief Implementation of the HTTP listener used in tests.
+///
+/// This implementation replaces the @c HttpConnection type with a custom
+/// implementation.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerImplCustom : public HttpListenerImpl {
+public:
+
+ HttpListenerImplCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpListenerImpl(io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout,
+ idle_timeout) {
+ }
+
+protected:
+
+ /// @brief Creates an instance of the @c HttpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ ///
+ /// @return Pointer to the created connection.
+ virtual HttpConnectionPtr createConnection(const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback) {
+ TlsContextPtr tls_context;
+ configClient(tls_context);
+ HttpConnectionPtr
+ conn(new HttpConnectionType(io_service_, acceptor_,
+ tls_context_, connections_,
+ response_creator, callback,
+ request_timeout_, idle_timeout_));
+ return (conn);
+ }
+};
+
+/// @brief Derivation of the @c HttpListener used in tests.
+///
+/// This class replaces the default implementation instance with the
+/// @c HttpListenerImplCustom using the customized connection type.
+///
+/// @tparam HttpConnectionType Type of the connection object to be used by
+/// the listener implementation.
+template<typename HttpConnectionType>
+class HttpListenerCustom : public HttpListener {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the HTTP service should run.
+ /// @param server_port Port number on which the HTTP service should run.
+ /// @param tls_context TLS context.
+ /// @param creator_factory Pointer to the caller-defined
+ /// @ref HttpResponseCreatorFactory derivation which should be used to
+ /// create @ref HttpResponseCreator instances.
+ /// @param request_timeout Timeout after which the HTTP Request Timeout
+ /// is generated.
+ /// @param idle_timeout Timeout after which an idle persistent HTTP
+ /// connection is closed by the server.
+ ///
+ /// @throw HttpListenerError when any of the specified parameters is
+ /// invalid.
+ HttpListenerCustom(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const HttpResponseCreatorFactoryPtr& creator_factory,
+ const HttpListener::RequestTimeout& request_timeout,
+ const HttpListener::IdleTimeout& idle_timeout)
+ : HttpListener(io_service, server_address, server_port,
+ tls_context, creator_factory,
+ request_timeout, idle_timeout) {
+ // Replace the default implementation with the customized version
+ // using the custom derivation of the HttpConnection.
+ impl_.reset(new HttpListenerImplCustom<HttpConnectionType>
+ (io_service, server_address, server_port,
+ tls_context, creator_factory, request_timeout.value_,
+ idle_timeout.value_));
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which injects greater
+/// length value than the buffer size into the write socket callback.
+class HttpConnectionLongWriteBuffer : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionLongWriteBuffer(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Pass greater length of the data written. The callback should deal
+ // with this and adjust the data length.
+ HttpConnection::socketWriteCallback(transaction, ec, length + 1);
+ }
+};
+
+/// @brief Implementation of the @c HttpConnection which replaces
+/// transaction instance prior to calling write socket callback.
+class HttpConnectionTransactionChange : public HttpConnection {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new HTTP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param response_creator Pointer to the response creator object used to
+ /// create HTTP response from the HTTP request received.
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param request_timeout Configured timeout for a HTTP request.
+ /// @param idle_timeout Timeout after which persistent HTTP connection is
+ /// closed by the server.
+ HttpConnectionTransactionChange(IOService& io_service,
+ const HttpAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ HttpConnectionPool& connection_pool,
+ const HttpResponseCreatorPtr& response_creator,
+ const HttpAcceptorCallback& callback,
+ const long request_timeout,
+ const long idle_timeout)
+ : HttpConnection(io_service, acceptor, tls_context, connection_pool,
+ response_creator, callback, request_timeout,
+ idle_timeout) {
+ }
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param transaction Pointer to the transaction for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(HttpConnection::TransactionPtr transaction,
+ boost::system::error_code ec,
+ size_t length) {
+ // Replace the transaction. The socket callback should deal with this
+ // gracefully. It should detect that the output buffer is empty. Then
+ // try to see if the connection is persistent. This check should fail,
+ // because the request hasn't been created/finalized. The exception
+ // thrown upon checking the persistence should be caught and the
+ // connection closed.
+ transaction = HttpConnection::Transaction::create(response_creator_);
+ HttpConnection::socketWriteCallback(transaction, ec, length);
+ }
+};
+
+
+/// @brief Entity which can connect to the HTTP server endpoint.
+class TestHttpClient : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// connect() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error.
+ /// @param tls_context TLS context.
+ TestHttpClient(IOService& io_service, TlsContextPtr tls_context)
+ : io_service_(io_service.get_io_service()),
+ stream_(io_service_, tls_context->getContext()),
+ buf_(), response_() {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ ~TestHttpClient() {
+ close();
+ }
+
+ /// @brief Send HTTP request specified in textual format.
+ ///
+ /// @param request HTTP request in the textual format.
+ void startRequest(const std::string& request) {
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT);
+ stream_.lowest_layer().async_connect(endpoint,
+ [this, request](const boost::system::error_code& ec) {
+ if (ec) {
+ // One would expect that async_connect wouldn't return
+ // EINPROGRESS error code, but simply wait for the connection
+ // to get established before the handler is invoked. It turns out,
+ // however, that on some OSes the connect handler may receive this
+ // error code which doesn't necessarily indicate a problem.
+ // Making an attempt to write and read from this socket will
+ // typically succeed. So, we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+ stream_.async_handshake(roleToImpl(TlsRole::CLIENT),
+ [this, request](const boost::system::error_code& ec) {
+ if (ec) {
+ ADD_FAILURE() << "error occurred during handshake: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ sendRequest(request);
+ });
+ });
+ }
+
+ /// @brief Send HTTP request.
+ ///
+ /// @param request HTTP request in the textual format.
+ void sendRequest(const std::string& request) {
+ sendPartialRequest(request);
+ }
+
+ /// @brief Send part of the HTTP request.
+ ///
+ /// @param request part of the HTTP request to be sent.
+ void sendPartialRequest(std::string request) {
+ boost::asio::async_write(stream_,
+ boost::asio::buffer(request.data(), request.size()),
+ [this, request](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) mutable {
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again make sure there is no garbage in the
+ // bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ io_service_.stop();
+ return;
+ }
+ }
+
+ // Remove the part of the request which has been sent.
+ if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
+ request.erase(0, bytes_transferred);
+ }
+
+ // Continue sending request data if there are still some data to be
+ // sent.
+ if (!request.empty()) {
+ sendPartialRequest(request);
+
+ } else {
+ // Request has been sent. Start receiving response.
+ response_.clear();
+ receivePartialResponse();
+ }
+ });
+ }
+
+ /// @brief Receive response from the server.
+ void receivePartialResponse() {
+ stream_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (ec) {
+ // IO service stopped so simply return.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again, make sure that there is no garbage
+ // in the bytes_transferred.
+ bytes_transferred = 0;
+
+ } else {
+ // Error occurred, bail...
+ ADD_FAILURE() << "error occurred while receiving HTTP"
+ " response from the server: " << ec.message();
+ io_service_.stop();
+ }
+ }
+
+ if (bytes_transferred > 0) {
+ response_.insert(response_.end(), buf_.data(),
+ buf_.data() + bytes_transferred);
+ }
+
+ // Two consecutive new lines end the part of the response we're
+ // expecting.
+ if (response_.find("\r\n\r\n", 0) != std::string::npos) {
+ io_service_.stop();
+
+ } else {
+ receivePartialResponse();
+ }
+
+ });
+ }
+
+ /// @brief Checks if the TCP connection is still open.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// Unfortunately expected failure depends on a race between the read
+ /// and the other side close so to check if the connection is closed
+ /// please use @c isConnectionClosed instead.
+ ///
+ /// @return true if the TCP connection is open.
+ bool isConnectionAlive() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
+ // Set the socket to non blocking mode. We're going to test if the socket
+ // returns would_block status on the attempt to read from it.
+ stream_.lowest_layer().non_blocking(true);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ stream_.lowest_layer().non_blocking(non_blocking_orig);
+
+ // If the connection is alive we'd typically get would_block status code.
+ // If there are any data that haven't been read we may also get success
+ // status. We're guessing that try_again may also be returned by some
+ // implementations in some situations. Any other error code indicates a
+ // problem with the connection so we assume that the connection has been
+ // closed.
+ return (!ec || (ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block));
+ }
+
+ /// @brief Checks if the TCP connection is already closed.
+ ///
+ /// Tests the TCP connection by trying to read from the socket.
+ /// The read can block so this must be used to check if a connection
+ /// is alive so to check if the connection is alive please always
+ /// use @c isConnectionAlive.
+ ///
+ /// @return true if the TCP connection is closed.
+ bool isConnectionClosed() {
+ // Remember the current non blocking setting.
+ const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
+ // Set the socket to blocking mode. We're going to test if the socket
+ // returns eof status on the attempt to read from it.
+ stream_.lowest_layer().non_blocking(false);
+
+ // We need to provide a buffer for a call to read.
+ char data[2];
+ boost::system::error_code ec;
+ boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
+
+ // Revert the original non_blocking flag on the socket.
+ stream_.lowest_layer().non_blocking(non_blocking_orig);
+
+ // If the connection is closed we'd typically get eof or
+ // stream_truncated status code.
+ return ((ec.value() == boost::asio::error::eof) ||
+ (ec.value() == STREAM_TRUNCATED));
+ }
+
+ /// @brief Close connection.
+ void close() {
+ stream_.lowest_layer().close();
+ }
+
+ std::string getResponse() const {
+ return (response_);
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief A socket used for the connection.
+ TlsStreamImpl stream_;
+
+ /// @brief Buffer into which response is written.
+ std::array<char, 8192> buf_;
+
+ /// @brief Response in the textual format.
+ std::string response_;
+};
+
+/// @brief Pointer to the TestHttpClient.
+typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
+
+/// @brief Test fixture class for @ref HttpListener.
+class HttpsListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ HttpsListenerTest()
+ : io_service_(), factory_(new TestHttpResponseCreatorFactory()),
+ test_timer_(io_service_), run_io_service_timer_(io_service_),
+ clients_(), server_context_(), client_context_() {
+ configServer(server_context_);
+ configClient(client_context_);
+ test_timer_.setup(std::bind(&HttpsListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes active HTTP clients.
+ virtual ~HttpsListenerTest() {
+ for (auto client = clients_.begin(); client != clients_.end();
+ ++client) {
+ (*client)->close();
+ }
+ }
+
+ /// @brief Connect to the endpoint.
+ ///
+ /// This method creates TestHttpClient instance and retains it in the clients_
+ /// list.
+ ///
+ /// @param request String containing the HTTP request to be sent.
+ void startRequest(const std::string& request) {
+ TestHttpClientPtr client(new TestHttpClient(io_service_,
+ client_context_));
+ clients_.push_back(client);
+ clients_.back()->startRequest(request);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran.
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&HttpsListenerTest::timeoutHandler,
+ this, false),
+ timeout, IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief Returns HTTP OK response expected by unit tests.
+ ///
+ /// @param http_version HTTP version.
+ ///
+ /// @return HTTP OK response expected by unit tests.
+ std::string httpOk(const HttpVersion& http_version) {
+ std::ostringstream s;
+ s << "HTTP/" << http_version.major_ << "." << http_version.minor_ << " 200 OK\r\n"
+ "Content-Length: 33\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"remote-address\": \"127.0.0.1\" }";
+ return (s.str());
+ }
+
+ /// @brief Tests that HTTP request timeout status is returned when the
+ /// server does not receive the entire request.
+ ///
+ /// @param request Partial request for which the parser will be waiting for
+ /// the next chunks of data.
+ /// @param expected_version HTTP version expected in the response.
+ void testRequestTimeout(const std::string& request,
+ const HttpVersion& expected_version) {
+ // Open the listener with the Request Timeout of 1 sec and post the
+ // partial request.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_,
+ factory_, HttpListener::RequestTimeout(1000),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+
+ // Build the reference response.
+ std::ostringstream expected_response;
+ expected_response
+ << "HTTP/" << expected_version.major_ << "." << expected_version.minor_
+ << " 408 Request Timeout\r\n"
+ "Content-Length: 44\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 408, \"text\": \"Request Timeout\" }";
+
+ // The server should wait for the missing part of the request for 1 second.
+ // The missing part never arrives so the server should respond with the
+ // HTTP Request Timeout status.
+ EXPECT_EQ(expected_response.str(), client->getResponse());
+ }
+
+ /// @brief Tests various cases when unexpected data is passed to the
+ /// socket write handler.
+ ///
+ /// This test uses the custom listener and the test specific derivations of
+ /// the @c HttpConnection class to enforce injection of the unexpected
+ /// data to the socket write callback. The two example applications of
+ /// this test are:
+ /// - injecting greater length value than the output buffer size,
+ /// - replacing the transaction with another transaction.
+ ///
+ /// It is expected that the socket write callback deals gracefully with
+ /// those situations.
+ ///
+ /// @tparam HttpConnectionType Test specific derivation of the
+ /// @c HttpConnection class.
+ template<typename HttpConnectionType>
+ void testWriteBufferIssues() {
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Use custom listener and the specialized connection object.
+ HttpListenerCustom<HttpConnectionType>
+ listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+
+ // Injecting unexpected data should not result in an exception.
+ ASSERT_NO_THROW(runIOService());
+
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Pointer to the response creator factory.
+ HttpResponseCreatorFactoryPtr factory_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TestHttpClientPtr> clients_;
+
+ /// @brief Server TLS context.
+ TlsContextPtr server_context_;
+
+ /// @brief Client TLS context.
+ TlsContextPtr client_context_;
+};
+
+// This test verifies that HTTP connection can be established and used to
+// transmit HTTP request and receive a response.
+TEST_F(HttpsListenerTest, listen) {
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+
+// This test verifies that persistent HTTP connection can be established when
+// "Connection: Keep-Alive" header value is specified.
+TEST_F(HttpsListenerTest, keepAlive) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it lacks the keep-alive header, so the server should close the connection
+ // after sending the response.
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent HTTP connection is established by default
+// when HTTP/1.1 is in use.
+TEST_F(HttpsListenerTest, persistentConnection) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the first request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // HTTP/1.1 connection is persistent by default.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Test that we can send another request via the same connection. This time
+ // it includes the "Connection: close" header which instructs the server to
+ // close the connection after responding.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // Connection should have been closed by the server.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that "keep-alive" connection is closed by the server after
+// an idle time.
+TEST_F(HttpsListenerTest, keepAliveTimeout) {
+
+ // The first request contains the keep-alive header which instructs the server
+ // to maintain the TCP connection after sending a response.
+ std::string request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: Keep-Alive\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request with the keep-alive header.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ // We have sent keep-alive header so we expect that the connection with
+ // the server remains active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.0\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_10()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that persistent connection is closed by the server after
+// an idle time.
+TEST_F(HttpsListenerTest, persistentConnectionTimeout) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ // Specify the idle timeout of 500ms.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(500));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Run IO service for 1000ms. The idle time is set to 500ms, so the connection
+ // should be closed by the server while we wait here.
+ runIOService(1000);
+
+ // Make sure the connection has been closed.
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ // Check if we can re-establish the connection and send another request.
+ clients_.clear();
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that HTTP/1.1 connection remains open even if there is an
+// error in the message body.
+TEST_F(HttpsListenerTest, persistentConnectionBadBody) {
+
+ // The HTTP/1.1 requests are by default persistent.
+ std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 12\r\n\r\n"
+ "{ \"a\": abc }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+
+ // Send the request.
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+
+ // The connection should remain active.
+ ASSERT_TRUE(client->isConnectionAlive());
+
+ // Make sure that we can send another request. This time we specify the
+ // "close" connection-token to force the connection to close.
+ request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length: 3\r\n"
+ "Connection: close\r\n\r\n"
+ "{ }";
+
+ // Send request reusing the existing connection.
+ ASSERT_NO_THROW(client->sendRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ EXPECT_EQ(httpOk(HttpVersion::HTTP_11()), client->getResponse());
+
+ EXPECT_TRUE(client->isConnectionClosed());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that the HTTP listener can't be started twice.
+TEST_F(HttpsListenerTest, startTwice) {
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that Bad Request status is returned when the request
+// is malformed.
+TEST_F(HttpsListenerTest, badRequest) {
+ // Content-Type is wrong. This should result in Bad Request status.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: foo\r\n"
+ "Content-Length: 3\r\n\r\n"
+ "{ }";
+
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS), SERVER_PORT,
+ server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TestHttpClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_EQ("HTTP/1.1 400 Bad Request\r\n"
+ "Content-Length: 40\r\n"
+ "Content-Type: application/json\r\n"
+ "Date: Tue, 19 Dec 2016 18:53:35 GMT\r\n"
+ "\r\n"
+ "{ \"result\": 400, \"text\": \"Bad Request\" }",
+ client->getResponse());
+}
+
+// This test verifies that NULL pointer can't be specified for the
+// HttpResponseCreatorFactory.
+TEST_F(HttpsListenerTest, invalidFactory) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_,
+ HttpResponseCreatorFactoryPtr(),
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// Request Timeout.
+TEST_F(HttpsListenerTest, invalidRequestTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_, factory_,
+ HttpListener::RequestTimeout(0),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT)),
+ HttpListenerError);
+}
+
+// This test verifies that the timeout of 0 can't be specified for the
+// idle persistent connection timeout.
+TEST_F(HttpsListenerTest, invalidIdleTimeout) {
+ EXPECT_THROW(HttpListener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(0)),
+ HttpListenerError);
+}
+
+// This test verifies that listener can't be bound to the port to which
+// other server is bound.
+TEST_F(HttpsListenerTest, addressInUse) {
+ tcp::acceptor acceptor(io_service_.get_io_service());
+ // Use other port than SERVER_PORT to make sure that this TCP connection
+ // doesn't affect subsequent tests.
+ tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
+ SERVER_PORT + 1);
+ acceptor.open(endpoint.protocol());
+ acceptor.bind(endpoint);
+
+ // Listener should report an error when we try to start it because another
+ // acceptor is bound to that port and address.
+ HttpListener listener(io_service_, IOAddress(SERVER_ADDRESS),
+ SERVER_PORT + 1, server_context_, factory_,
+ HttpListener::RequestTimeout(REQUEST_TIMEOUT),
+ HttpListener::IdleTimeout(IDLE_TIMEOUT));
+ EXPECT_THROW(listener.start(), HttpListenerError);
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request contains the HTTP
+// version number. The timeout response should contain the same
+// HTTP version number as the partial request.
+TEST_F(HttpsListenerTest, requestTimeoutHttpVersionFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP/1.1\r\n"
+ "Content-Type: application/json\r\n"
+ "Content-Length:";
+
+ testRequestTimeout(request, HttpVersion::HTTP_11());
+}
+
+// This test verifies that HTTP Request Timeout status is returned as
+// expected when the read part of the request does not contain
+// the HTTP version number. The timeout response should by default
+// contain HTTP/1.0 version number.
+TEST_F(HttpsListenerTest, requestTimeoutHttpVersionNotFound) {
+ // The part of the request specified here is correct but it is not
+ // a complete request.
+ const std::string request = "POST /foo/bar HTTP";
+
+ testRequestTimeout(request, HttpVersion::HTTP_10());
+}
+
+// This test verifies that injecting length value greater than the
+// output buffer length to the socket write callback does not cause
+// an exception.
+TEST_F(HttpsListenerTest, tooLongWriteBuffer) {
+ testWriteBufferIssues<HttpConnectionLongWriteBuffer>();
+}
+
+// This test verifies that changing the transaction before calling
+// the socket write callback does not cause an exception.
+TEST_F(HttpsListenerTest, transactionChangeDuringWrite) {
+ testWriteBufferIssues<HttpConnectionTransactionChange>();
+}
+
+}
diff --git a/src/lib/http/tests/url_unittests.cc b/src/lib/http/tests/url_unittests.cc
new file mode 100644
index 0000000..f024e61
--- /dev/null
+++ b/src/lib/http/tests/url_unittests.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2017-2018 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 <http/url.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc::http;
+
+namespace {
+
+/// @brief Test fixture class for @c Url class.
+class UrlTest : public ::testing::Test {
+public:
+
+ /// @brief Test valid URL.
+ ///
+ /// @param text_url URL is the text form.
+ /// @param expected_scheme Expected scheme.
+ /// @param expected_hostname Expected hostname.
+ /// @param expected_port Expected port.
+ /// @param expected_path Expected path.
+ void testValidUrl(const std::string& text_url,
+ const Url::Scheme& expected_scheme,
+ const std::string& expected_hostname,
+ const unsigned expected_port,
+ const std::string& expected_path) {
+ Url url(text_url);
+ ASSERT_TRUE(url.isValid()) << url.getErrorMessage();
+ EXPECT_EQ(expected_scheme, url.getScheme());
+ EXPECT_EQ(expected_hostname, url.getStrippedHostname());
+ EXPECT_EQ(expected_port, url.getPort());
+ EXPECT_EQ(expected_path, url.getPath());
+ }
+
+ /// @brief Test invalid URL.
+ ///
+ /// @param text_url URL is the text form.
+ void testInvalidUrl(const std::string& text_url) {
+ Url url(text_url);
+ EXPECT_FALSE(url.isValid());
+ }
+};
+
+// URL contains scheme and hostname.
+TEST_F(UrlTest, schemeHostname) {
+ testValidUrl("http://example.org", Url::HTTP, "example.org", 0, "");
+}
+
+// URL contains scheme, hostname and slash.
+TEST_F(UrlTest, schemeHostnameSlash) {
+ testValidUrl("http://example.org/", Url::HTTP, "example.org", 0, "/");
+}
+
+// URL contains scheme, IPv6 address and slash.
+TEST_F(UrlTest, schemeIPv6AddressSlash) {
+ testValidUrl("http://[2001:db8:1::100]/", Url::HTTP, "2001:db8:1::100", 0, "/");
+}
+
+// URL contains scheme, IPv4 address and slash.
+TEST_F(UrlTest, schemeIPv4AddressSlash) {
+ testValidUrl("http://192.0.2.2/", Url::HTTP, "192.0.2.2", 0, "/");
+}
+
+// URL contains scheme, hostname and path.
+TEST_F(UrlTest, schemeHostnamePath) {
+ testValidUrl("http://example.org/some/path", Url::HTTP, "example.org", 0,
+ "/some/path");
+}
+
+// URL contains scheme, hostname and port.
+TEST_F(UrlTest, schemeHostnamePort) {
+ testValidUrl("http://example.org:8080/", Url::HTTP, "example.org", 8080, "/");
+}
+
+// URL contains scheme, hostname, port and slash.
+TEST_F(UrlTest, schemeHostnamePortSlash) {
+ testValidUrl("http://example.org:8080/", Url::HTTP, "example.org", 8080, "/");
+}
+
+// URL contains scheme, IPv6 address and port.
+TEST_F(UrlTest, schemeIPv6AddressPort) {
+ testValidUrl("http://[2001:db8:1::1]:8080/", Url::HTTP, "2001:db8:1::1", 8080, "/");
+}
+
+// URL contains scheme, hostname, port and path.
+TEST_F(UrlTest, schemeHostnamePortPath) {
+ testValidUrl("http://example.org:8080/path/", Url::HTTP, "example.org", 8080,
+ "/path/");
+}
+
+// URL contains https scheme, hostname, port and path.
+TEST_F(UrlTest, secureSchemeHostnamePortPath) {
+ testValidUrl("https://example.org:8080/path/", Url::HTTPS, "example.org", 8080,
+ "/path/");
+}
+
+// Tests various invalid URLS.
+TEST_F(UrlTest, invalidUrls) {
+ testInvalidUrl("example.org");
+ testInvalidUrl("file://example.org");
+ testInvalidUrl("http//example.org");
+ testInvalidUrl("http:/example.org");
+ testInvalidUrl("http://");
+ testInvalidUrl("http://[]");
+ testInvalidUrl("http://[2001:db8:1::1");
+ testInvalidUrl("http://example.org:");
+ testInvalidUrl("http://example.org:abc");
+}
+
+}
diff --git a/src/lib/http/url.cc b/src/lib/http/url.cc
new file mode 100644
index 0000000..427b11a
--- /dev/null
+++ b/src/lib/http/url.cc
@@ -0,0 +1,223 @@
+// Copyright (C) 2017-2021 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 <http/url.h>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+#include <iostream>
+
+namespace isc {
+namespace http {
+
+Url::Url(const std::string& url)
+ : url_(url), valid_(false), error_message_(), scheme_(Url::HTTPS),
+ hostname_(), port_(0), path_() {
+ parse();
+}
+
+bool
+Url::operator<(const Url& url) const {
+ return (url_ < url.rawUrl());
+}
+
+Url::Scheme
+Url::getScheme() const {
+ checkValid();
+ return (scheme_);
+}
+
+std::string
+Url::getHostname() const {
+ checkValid();
+ return (hostname_);
+}
+
+std::string
+Url::getStrippedHostname() const {
+ std::string hostname = getHostname();
+ if ((hostname.length() >= 2) && (hostname.at(0) == '[')) {
+ return (hostname.substr(1, hostname.length() - 2));
+ }
+
+ return (hostname);
+}
+
+unsigned
+Url::getPort() const {
+ checkValid();
+ return (port_);
+}
+
+std::string
+Url::getPath() const {
+ checkValid();
+ return (path_);
+}
+
+std::string
+Url::toText() const {
+ std::ostringstream s;
+ s << (getScheme() == HTTP ? "http" : "https");
+ s << "://" << getHostname();
+
+ if (getPort() != 0) {
+ s << ":" << getPort();
+ }
+
+ s << getPath();
+
+ return (s.str());
+}
+
+void
+Url::checkValid() const {
+ if (!isValid()) {
+ isc_throw(InvalidOperation, "invalid URL " << url_ << ": " << error_message_);
+ }
+}
+
+void
+Url::parse() {
+ valid_ = false;
+ error_message_.clear();
+ scheme_ = Url::HTTPS;
+ hostname_.clear();
+ port_ = 0;
+ path_.clear();
+
+ std::ostringstream error;
+
+ // Retrieve scheme
+ size_t offset = url_.find(":");
+ if ((offset == 0) || (offset == std::string::npos)) {
+ error << "url " << url_ << " lacks http or https scheme";
+ error_message_ = error.str();
+ return;
+ }
+
+ // Validate scheme.
+ std::string scheme = url_.substr(0, offset);
+ if (scheme == "http") {
+ scheme_ = Url::HTTP;
+
+ } else if (scheme == "https") {
+ scheme_ = Url::HTTPS;
+
+ } else {
+ error << "invalid scheme " << scheme << " in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Colon and two slashes should follow the scheme
+ if (url_.substr(offset, 3) != "://") {
+ error << "expected :// after scheme in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Move forward to hostname.
+ offset += 3;
+ if (offset >= url_.length()) {
+ error << "hostname missing in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ size_t offset2 = 0;
+
+ // IPv6 address is specified within [ ].
+ if (url_.at(offset) == '[') {
+ offset2 = url_.find(']', offset);
+ if (offset2 == std::string::npos) {
+ error << "expected ] after IPv6 address in " << url_;
+ error_message_ = error.str();
+ return;
+
+ } else if (offset2 == offset + 1) {
+ error << "expected IPv6 address within [] in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Move one character beyond the ].
+ ++offset2;
+
+ } else {
+ // There is a normal hostname or IPv4 address. It is terminated
+ // by the colon (for port number), a slash (if no port number) or
+ // goes up to the end of the URL.
+ offset2 = url_.find(":", offset);
+ if (offset2 == std::string::npos) {
+ offset2 = url_.find("/", offset);
+ if (offset2 == std::string::npos) {
+ // No port number and no slash.
+ offset2 = url_.length();
+ }
+ }
+ }
+
+ // Extract the hostname.
+ hostname_ = url_.substr(offset, offset2 - offset);
+
+ // If there is no port number and no path, simply return and mark the
+ // URL as valid.
+ if (offset2 == url_.length()) {
+ valid_ = true;
+ return;
+ }
+
+ // If there is a port number, we need to read it and convert to
+ // numeric value.
+ if (url_.at(offset2) == ':') {
+ if (offset2 == url_.length() - 1) {
+ error << "expected port number after : in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+ // Move to the port number.
+ ++offset2;
+
+ // Port number may be terminated by a slash or by the end of URL.
+ size_t slash_offset = url_.find('/', offset2);
+ std::string port_str;
+ if (slash_offset == std::string::npos) {
+ port_str = url_.substr(offset2);
+ } else {
+ port_str = url_.substr(offset2, slash_offset - offset2);
+ }
+
+ try {
+ // Try to convert the port number to numeric value.
+ port_ = boost::lexical_cast<unsigned>(port_str);
+
+ } catch (...) {
+ error << "invalid port number " << port_str << " in " << url_;
+ error_message_ = error.str();
+ return;
+ }
+
+ // Go to the end of the port section.
+ offset2 = slash_offset;
+ }
+
+ // If there is anything left in the URL, we consider it a path.
+ if (offset2 != std::string::npos) {
+ path_ = url_.substr(offset2);
+ if (path_.empty()) {
+ path_ = "/";
+ }
+ }
+
+ valid_ = true;
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
diff --git a/src/lib/http/url.h b/src/lib/http/url.h
new file mode 100644
index 0000000..d6ec9e2
--- /dev/null
+++ b/src/lib/http/url.h
@@ -0,0 +1,131 @@
+// Copyright (C) 2017-2022 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 KEA_URL_H
+#define KEA_URL_H
+
+#include <asiolink/io_address.h>
+#include <string>
+
+namespace isc {
+namespace http {
+
+/// @brief Represents an URL.
+///
+/// It parses the provided URL and allows for retrieving the parts
+/// of it after parsing.
+class Url {
+public:
+
+ /// @brief Scheme: https or http.
+ enum Scheme {
+ HTTP,
+ HTTPS
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Parses provided URL.
+ ///
+ /// @param url URL.
+ explicit Url(const std::string& url);
+
+ /// @brief compares URLs lexically.
+ ///
+ /// Both URLs are compared as text.
+ ///
+ /// @param url URL to be compared with
+ /// @return true if the other operator is larger (in lexical sense)
+ bool operator<(const Url& url) const;
+
+ /// @brief Checks if the URL is valid.
+ ///
+ /// @return true if the URL is valid, false otherwise.
+ bool isValid() const {
+ return (valid_);
+ }
+
+ /// @brief Returns parsing error message.
+ std::string getErrorMessage() const {
+ return (error_message_);
+ }
+
+ /// @brief Returns parsed scheme.
+ ///
+ /// @throw InvalidOperation if URL is invalid.
+ Scheme getScheme() const;
+
+ /// @brief Returns hostname stripped from [ ] characters surrounding
+ /// IPv6 address.
+ ///
+ /// @throw InvalidOperation if URL is invalid.
+ std::string getStrippedHostname() const;
+
+ /// @brief Returns port number.
+ ///
+ /// @return Port number or 0 if URL doesn't contain port number.
+ /// @throw InvalidOperation if URL is invalid.
+ unsigned getPort() const;
+
+ /// @brief Returns path.
+ ///
+ /// @return URL path
+ /// @throw InvalidOperation if URL is invalid.
+ std::string getPath() const;
+
+ /// @brief Returns textual representation of the URL.
+ ///
+ /// @return Text version of the URL.
+ std::string toText() const;
+
+ /// @brief Returns the raw, unparsed URL string.
+ ///
+ /// @return Unparsed URL string.
+ const std::string& rawUrl() const {
+ return (url_);
+ }
+
+private:
+ /// @brief Returns hostname.
+ ///
+ /// @throw InvalidOperation if URL is invalid.
+ std::string getHostname() const;
+
+ /// @brief Returns boolean value indicating if the URL is valid.
+ void checkValid() const;
+
+ /// @brief Parses URL.
+ ///
+ /// This method doesn't throw an exception. Call @c isValid to see
+ /// if the URL is valid.
+ void parse();
+
+ /// @brief Holds specified URL.
+ std::string url_;
+
+ /// @brief A flag indicating if the URL is valid.
+ bool valid_;
+
+ /// @brief Holds error message after parsing.
+ std::string error_message_;
+
+ /// @brief Parsed scheme.
+ Scheme scheme_;
+
+ /// @brief Parsed hostname.
+ std::string hostname_;
+
+ /// @brief Parsed port number.
+ unsigned port_;
+
+ /// @brief Parsed path.
+ std::string path_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
new file mode 100644
index 0000000..b7150e3
--- /dev/null
+++ b/src/lib/log/Makefile.am
@@ -0,0 +1,119 @@
+SUBDIRS = interprocess . compiler tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTOP_BUILDDIR=\"$(abs_top_builddir)\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-log.la
+libkea_log_la_SOURCES =
+libkea_log_la_SOURCES += logimpl_messages.cc logimpl_messages.h
+libkea_log_la_SOURCES += log_dbglevels.cc log_dbglevels.h
+libkea_log_la_SOURCES += log_formatter.h log_formatter.cc
+libkea_log_la_SOURCES += logger.cc logger.h
+libkea_log_la_SOURCES += logger_impl.cc logger_impl.h
+libkea_log_la_SOURCES += logger_level.cc logger_level.h
+libkea_log_la_SOURCES += logger_level_impl.cc logger_level_impl.h
+libkea_log_la_SOURCES += logger_manager.cc logger_manager.h
+libkea_log_la_SOURCES += logger_manager_impl.cc logger_manager_impl.h
+libkea_log_la_SOURCES += logger_name.cc logger_name.h
+libkea_log_la_SOURCES += logger_specification.h
+libkea_log_la_SOURCES += logger_support.cc logger_support.h
+libkea_log_la_SOURCES += logger_unittest_support.cc logger_unittest_support.h
+libkea_log_la_SOURCES += log_messages.cc log_messages.h
+libkea_log_la_SOURCES += macros.h
+libkea_log_la_SOURCES += message_dictionary.cc message_dictionary.h
+libkea_log_la_SOURCES += message_exception.h
+libkea_log_la_SOURCES += message_initializer.cc message_initializer.h
+libkea_log_la_SOURCES += message_reader.cc message_reader.h
+libkea_log_la_SOURCES += message_types.h
+libkea_log_la_SOURCES += output_option.cc output_option.h
+libkea_log_la_SOURCES += buffer_appender_impl.cc buffer_appender_impl.h
+
+EXTRA_DIST = logging.dox
+EXTRA_DIST += logimpl_messages.mes
+EXTRA_DIST += log_messages.mes
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+libkea_log_la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+libkea_log_la_CXXFLAGS += -Wno-unused-parameter -Wno-deprecated-declarations
+endif
+libkea_log_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libkea_log_la_LIBADD = $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.la
+libkea_log_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_log_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_log_la_LIBADD += $(LOG4CPLUS_LIBS)
+libkea_log_la_LDFLAGS = -no-undefined -version-info 48:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_log_includedir = $(pkgincludedir)/log
+libkea_log_include_HEADERS = \
+ buffer_appender_impl.h \
+ log_dbglevels.h \
+ log_formatter.h \
+ log_messages.h \
+ logger.h \
+ logger_impl.h \
+ logger_level.h \
+ logger_level_impl.h \
+ logger_manager.h \
+ logger_manager_impl.h \
+ logger_name.h \
+ logger_specification.h \
+ logger_support.h \
+ logger_unittest_support.h \
+ logimpl_messages.h \
+ macros.h \
+ message_dictionary.h \
+ message_exception.h \
+ message_initializer.h \
+ message_reader.h \
+ message_types.h \
+ output_option.h
+
+libkea_log_interprocess_includedir = $(pkgincludedir)/log/interprocess
+libkea_log_interprocess_include_HEADERS = \
+ interprocess/interprocess_sync.h \
+ interprocess/interprocess_sync_file.h \
+ interprocess/interprocess_sync_null.h
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f log_messages.cc log_messages.h logimpl_messages.cc logimpl_messages.h
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file.
+messages: log_messages.cc log_messages.h logimpl_messages.cc logimpl_messages.h
+ @echo Message files regenerated
+
+log_messages.cc log_messages.h: log_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/log/log_messages.mes
+
+logimpl_messages.cc logimpl_messages.h: logimpl_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/log/logimpl_messages.mes
+
+else
+
+messages log_messages.cc log_messages.h logimpl_messages.cc logimpl_messages.h:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
diff --git a/src/lib/log/Makefile.in b/src/lib/log/Makefile.in
new file mode 100644
index 0000000..1cce016
--- /dev/null
+++ b/src/lib/log/Makefile.in
@@ -0,0 +1,1258 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@USE_GXX_TRUE@am__append_1 = -Wno-unused-parameter -Wno-deprecated-declarations
+subdir = src/lib/log
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_log_include_HEADERS) \
+ $(libkea_log_interprocess_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)$(libkea_log_includedir)" \
+ "$(DESTDIR)$(libkea_log_interprocess_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_log_la_DEPENDENCIES = $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1)
+am_libkea_log_la_OBJECTS = libkea_log_la-logimpl_messages.lo \
+ libkea_log_la-log_dbglevels.lo libkea_log_la-log_formatter.lo \
+ libkea_log_la-logger.lo libkea_log_la-logger_impl.lo \
+ libkea_log_la-logger_level.lo \
+ libkea_log_la-logger_level_impl.lo \
+ libkea_log_la-logger_manager.lo \
+ libkea_log_la-logger_manager_impl.lo \
+ libkea_log_la-logger_name.lo libkea_log_la-logger_support.lo \
+ libkea_log_la-logger_unittest_support.lo \
+ libkea_log_la-log_messages.lo \
+ libkea_log_la-message_dictionary.lo \
+ libkea_log_la-message_initializer.lo \
+ libkea_log_la-message_reader.lo libkea_log_la-output_option.lo \
+ libkea_log_la-buffer_appender_impl.lo
+libkea_log_la_OBJECTS = $(am_libkea_log_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_log_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) $(libkea_log_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_log_la-buffer_appender_impl.Plo \
+ ./$(DEPDIR)/libkea_log_la-log_dbglevels.Plo \
+ ./$(DEPDIR)/libkea_log_la-log_formatter.Plo \
+ ./$(DEPDIR)/libkea_log_la-log_messages.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_impl.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_level.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_level_impl.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_manager.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_manager_impl.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_name.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_support.Plo \
+ ./$(DEPDIR)/libkea_log_la-logger_unittest_support.Plo \
+ ./$(DEPDIR)/libkea_log_la-logimpl_messages.Plo \
+ ./$(DEPDIR)/libkea_log_la-message_dictionary.Plo \
+ ./$(DEPDIR)/libkea_log_la-message_initializer.Plo \
+ ./$(DEPDIR)/libkea_log_la-message_reader.Plo \
+ ./$(DEPDIR)/libkea_log_la-output_option.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_log_la_SOURCES)
+DIST_SOURCES = $(libkea_log_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 = $(libkea_log_include_HEADERS) \
+ $(libkea_log_interprocess_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = interprocess . compiler tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) -DTOP_BUILDDIR=\"$(abs_top_builddir)\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-log.la
+libkea_log_la_SOURCES = logimpl_messages.cc logimpl_messages.h \
+ log_dbglevels.cc log_dbglevels.h log_formatter.h \
+ log_formatter.cc logger.cc logger.h logger_impl.cc \
+ logger_impl.h logger_level.cc logger_level.h \
+ logger_level_impl.cc logger_level_impl.h logger_manager.cc \
+ logger_manager.h logger_manager_impl.cc logger_manager_impl.h \
+ logger_name.cc logger_name.h logger_specification.h \
+ logger_support.cc logger_support.h logger_unittest_support.cc \
+ logger_unittest_support.h log_messages.cc log_messages.h \
+ macros.h message_dictionary.cc message_dictionary.h \
+ message_exception.h message_initializer.cc \
+ message_initializer.h message_reader.cc message_reader.h \
+ message_types.h output_option.cc output_option.h \
+ buffer_appender_impl.cc buffer_appender_impl.h
+EXTRA_DIST = logging.dox logimpl_messages.mes log_messages.mes
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+libkea_log_la_CXXFLAGS = $(AM_CXXFLAGS) $(am__append_1)
+libkea_log_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libkea_log_la_LIBADD = $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS)
+libkea_log_la_LDFLAGS = -no-undefined -version-info 48:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_log_includedir = $(pkgincludedir)/log
+libkea_log_include_HEADERS = \
+ buffer_appender_impl.h \
+ log_dbglevels.h \
+ log_formatter.h \
+ log_messages.h \
+ logger.h \
+ logger_impl.h \
+ logger_level.h \
+ logger_level_impl.h \
+ logger_manager.h \
+ logger_manager_impl.h \
+ logger_name.h \
+ logger_specification.h \
+ logger_support.h \
+ logger_unittest_support.h \
+ logimpl_messages.h \
+ macros.h \
+ message_dictionary.h \
+ message_exception.h \
+ message_initializer.h \
+ message_reader.h \
+ message_types.h \
+ output_option.h
+
+libkea_log_interprocess_includedir = $(pkgincludedir)/log/interprocess
+libkea_log_interprocess_include_HEADERS = \
+ interprocess/interprocess_sync.h \
+ interprocess/interprocess_sync_file.h \
+ interprocess/interprocess_sync_null.h
+
+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/log/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/log/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-log.la: $(libkea_log_la_OBJECTS) $(libkea_log_la_DEPENDENCIES) $(EXTRA_libkea_log_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_log_la_LINK) -rpath $(libdir) $(libkea_log_la_OBJECTS) $(libkea_log_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-buffer_appender_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-log_dbglevels.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-log_formatter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-log_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_level.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_level_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_manager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_manager_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_name.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_support.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logger_unittest_support.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-logimpl_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-message_dictionary.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-message_initializer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-message_reader.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_log_la-output_option.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)$(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 $@ $<
+
+libkea_log_la-logimpl_messages.lo: logimpl_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logimpl_messages.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logimpl_messages.Tpo -c -o libkea_log_la-logimpl_messages.lo `test -f 'logimpl_messages.cc' || echo '$(srcdir)/'`logimpl_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logimpl_messages.Tpo $(DEPDIR)/libkea_log_la-logimpl_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logimpl_messages.cc' object='libkea_log_la-logimpl_messages.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logimpl_messages.lo `test -f 'logimpl_messages.cc' || echo '$(srcdir)/'`logimpl_messages.cc
+
+libkea_log_la-log_dbglevels.lo: log_dbglevels.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-log_dbglevels.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-log_dbglevels.Tpo -c -o libkea_log_la-log_dbglevels.lo `test -f 'log_dbglevels.cc' || echo '$(srcdir)/'`log_dbglevels.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-log_dbglevels.Tpo $(DEPDIR)/libkea_log_la-log_dbglevels.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_dbglevels.cc' object='libkea_log_la-log_dbglevels.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-log_dbglevels.lo `test -f 'log_dbglevels.cc' || echo '$(srcdir)/'`log_dbglevels.cc
+
+libkea_log_la-log_formatter.lo: log_formatter.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-log_formatter.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-log_formatter.Tpo -c -o libkea_log_la-log_formatter.lo `test -f 'log_formatter.cc' || echo '$(srcdir)/'`log_formatter.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-log_formatter.Tpo $(DEPDIR)/libkea_log_la-log_formatter.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_formatter.cc' object='libkea_log_la-log_formatter.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-log_formatter.lo `test -f 'log_formatter.cc' || echo '$(srcdir)/'`log_formatter.cc
+
+libkea_log_la-logger.lo: logger.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger.Tpo -c -o libkea_log_la-logger.lo `test -f 'logger.cc' || echo '$(srcdir)/'`logger.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger.Tpo $(DEPDIR)/libkea_log_la-logger.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger.cc' object='libkea_log_la-logger.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger.lo `test -f 'logger.cc' || echo '$(srcdir)/'`logger.cc
+
+libkea_log_la-logger_impl.lo: logger_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_impl.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_impl.Tpo -c -o libkea_log_la-logger_impl.lo `test -f 'logger_impl.cc' || echo '$(srcdir)/'`logger_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_impl.Tpo $(DEPDIR)/libkea_log_la-logger_impl.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_impl.cc' object='libkea_log_la-logger_impl.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_impl.lo `test -f 'logger_impl.cc' || echo '$(srcdir)/'`logger_impl.cc
+
+libkea_log_la-logger_level.lo: logger_level.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_level.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_level.Tpo -c -o libkea_log_la-logger_level.lo `test -f 'logger_level.cc' || echo '$(srcdir)/'`logger_level.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_level.Tpo $(DEPDIR)/libkea_log_la-logger_level.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_level.cc' object='libkea_log_la-logger_level.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_level.lo `test -f 'logger_level.cc' || echo '$(srcdir)/'`logger_level.cc
+
+libkea_log_la-logger_level_impl.lo: logger_level_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_level_impl.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_level_impl.Tpo -c -o libkea_log_la-logger_level_impl.lo `test -f 'logger_level_impl.cc' || echo '$(srcdir)/'`logger_level_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_level_impl.Tpo $(DEPDIR)/libkea_log_la-logger_level_impl.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_level_impl.cc' object='libkea_log_la-logger_level_impl.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_level_impl.lo `test -f 'logger_level_impl.cc' || echo '$(srcdir)/'`logger_level_impl.cc
+
+libkea_log_la-logger_manager.lo: logger_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_manager.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_manager.Tpo -c -o libkea_log_la-logger_manager.lo `test -f 'logger_manager.cc' || echo '$(srcdir)/'`logger_manager.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_manager.Tpo $(DEPDIR)/libkea_log_la-logger_manager.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_manager.cc' object='libkea_log_la-logger_manager.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_manager.lo `test -f 'logger_manager.cc' || echo '$(srcdir)/'`logger_manager.cc
+
+libkea_log_la-logger_manager_impl.lo: logger_manager_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_manager_impl.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_manager_impl.Tpo -c -o libkea_log_la-logger_manager_impl.lo `test -f 'logger_manager_impl.cc' || echo '$(srcdir)/'`logger_manager_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_manager_impl.Tpo $(DEPDIR)/libkea_log_la-logger_manager_impl.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_manager_impl.cc' object='libkea_log_la-logger_manager_impl.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_manager_impl.lo `test -f 'logger_manager_impl.cc' || echo '$(srcdir)/'`logger_manager_impl.cc
+
+libkea_log_la-logger_name.lo: logger_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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_name.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_name.Tpo -c -o libkea_log_la-logger_name.lo `test -f 'logger_name.cc' || echo '$(srcdir)/'`logger_name.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_name.Tpo $(DEPDIR)/libkea_log_la-logger_name.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_name.cc' object='libkea_log_la-logger_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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_name.lo `test -f 'logger_name.cc' || echo '$(srcdir)/'`logger_name.cc
+
+libkea_log_la-logger_support.lo: logger_support.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_support.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_support.Tpo -c -o libkea_log_la-logger_support.lo `test -f 'logger_support.cc' || echo '$(srcdir)/'`logger_support.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_support.Tpo $(DEPDIR)/libkea_log_la-logger_support.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_support.cc' object='libkea_log_la-logger_support.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_support.lo `test -f 'logger_support.cc' || echo '$(srcdir)/'`logger_support.cc
+
+libkea_log_la-logger_unittest_support.lo: logger_unittest_support.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-logger_unittest_support.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-logger_unittest_support.Tpo -c -o libkea_log_la-logger_unittest_support.lo `test -f 'logger_unittest_support.cc' || echo '$(srcdir)/'`logger_unittest_support.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-logger_unittest_support.Tpo $(DEPDIR)/libkea_log_la-logger_unittest_support.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_unittest_support.cc' object='libkea_log_la-logger_unittest_support.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-logger_unittest_support.lo `test -f 'logger_unittest_support.cc' || echo '$(srcdir)/'`logger_unittest_support.cc
+
+libkea_log_la-log_messages.lo: log_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-log_messages.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-log_messages.Tpo -c -o libkea_log_la-log_messages.lo `test -f 'log_messages.cc' || echo '$(srcdir)/'`log_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-log_messages.Tpo $(DEPDIR)/libkea_log_la-log_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_messages.cc' object='libkea_log_la-log_messages.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-log_messages.lo `test -f 'log_messages.cc' || echo '$(srcdir)/'`log_messages.cc
+
+libkea_log_la-message_dictionary.lo: message_dictionary.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-message_dictionary.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-message_dictionary.Tpo -c -o libkea_log_la-message_dictionary.lo `test -f 'message_dictionary.cc' || echo '$(srcdir)/'`message_dictionary.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-message_dictionary.Tpo $(DEPDIR)/libkea_log_la-message_dictionary.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_dictionary.cc' object='libkea_log_la-message_dictionary.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-message_dictionary.lo `test -f 'message_dictionary.cc' || echo '$(srcdir)/'`message_dictionary.cc
+
+libkea_log_la-message_initializer.lo: message_initializer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-message_initializer.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-message_initializer.Tpo -c -o libkea_log_la-message_initializer.lo `test -f 'message_initializer.cc' || echo '$(srcdir)/'`message_initializer.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-message_initializer.Tpo $(DEPDIR)/libkea_log_la-message_initializer.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_initializer.cc' object='libkea_log_la-message_initializer.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-message_initializer.lo `test -f 'message_initializer.cc' || echo '$(srcdir)/'`message_initializer.cc
+
+libkea_log_la-message_reader.lo: message_reader.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-message_reader.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-message_reader.Tpo -c -o libkea_log_la-message_reader.lo `test -f 'message_reader.cc' || echo '$(srcdir)/'`message_reader.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-message_reader.Tpo $(DEPDIR)/libkea_log_la-message_reader.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_reader.cc' object='libkea_log_la-message_reader.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-message_reader.lo `test -f 'message_reader.cc' || echo '$(srcdir)/'`message_reader.cc
+
+libkea_log_la-output_option.lo: output_option.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-output_option.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-output_option.Tpo -c -o libkea_log_la-output_option.lo `test -f 'output_option.cc' || echo '$(srcdir)/'`output_option.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-output_option.Tpo $(DEPDIR)/libkea_log_la-output_option.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='output_option.cc' object='libkea_log_la-output_option.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-output_option.lo `test -f 'output_option.cc' || echo '$(srcdir)/'`output_option.cc
+
+libkea_log_la-buffer_appender_impl.lo: buffer_appender_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_log_la-buffer_appender_impl.lo -MD -MP -MF $(DEPDIR)/libkea_log_la-buffer_appender_impl.Tpo -c -o libkea_log_la-buffer_appender_impl.lo `test -f 'buffer_appender_impl.cc' || echo '$(srcdir)/'`buffer_appender_impl.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_log_la-buffer_appender_impl.Tpo $(DEPDIR)/libkea_log_la-buffer_appender_impl.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_appender_impl.cc' object='libkea_log_la-buffer_appender_impl.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_log_la_CPPFLAGS) $(CPPFLAGS) $(libkea_log_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_log_la-buffer_appender_impl.lo `test -f 'buffer_appender_impl.cc' || echo '$(srcdir)/'`buffer_appender_impl.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_log_includeHEADERS: $(libkea_log_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_log_include_HEADERS)'; test -n "$(libkea_log_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_log_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_log_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)$(libkea_log_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_log_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_log_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_log_include_HEADERS)'; test -n "$(libkea_log_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_log_includedir)'; $(am__uninstall_files_from_dir)
+install-libkea_log_interprocess_includeHEADERS: $(libkea_log_interprocess_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_log_interprocess_include_HEADERS)'; test -n "$(libkea_log_interprocess_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_log_interprocess_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_log_interprocess_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)$(libkea_log_interprocess_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_log_interprocess_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_log_interprocess_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_log_interprocess_include_HEADERS)'; test -n "$(libkea_log_interprocess_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_log_interprocess_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_log_includedir)" "$(DESTDIR)$(libkea_log_interprocess_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_log_la-buffer_appender_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-log_dbglevels.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-log_formatter.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-log_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_level.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_level_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_manager_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_name.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_support.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_unittest_support.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logimpl_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-message_dictionary.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-message_initializer.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-message_reader.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-output_option.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-libkea_log_includeHEADERS \
+ install-libkea_log_interprocess_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_log_la-buffer_appender_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-log_dbglevels.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-log_formatter.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-log_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_level.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_level_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_manager.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_manager_impl.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_name.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_support.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logger_unittest_support.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-logimpl_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-message_dictionary.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-message_initializer.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-message_reader.Plo
+ -rm -f ./$(DEPDIR)/libkea_log_la-output_option.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_log_includeHEADERS \
+ uninstall-libkea_log_interprocess_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_log_includeHEADERS \
+ install-libkea_log_interprocess_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_log_includeHEADERS \
+ uninstall-libkea_log_interprocess_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f log_messages.cc log_messages.h logimpl_messages.cc logimpl_messages.h
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file.
+@GENERATE_MESSAGES_TRUE@messages: log_messages.cc log_messages.h logimpl_messages.cc logimpl_messages.h
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@log_messages.cc log_messages.h: log_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/log/log_messages.mes
+
+@GENERATE_MESSAGES_TRUE@logimpl_messages.cc logimpl_messages.h: logimpl_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/log/logimpl_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages log_messages.cc log_messages.h logimpl_messages.cc logimpl_messages.h:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/log/buffer_appender_impl.cc b/src/lib/log/buffer_appender_impl.cc
new file mode 100644
index 0000000..6ce8d40
--- /dev/null
+++ b/src/lib/log/buffer_appender_impl.cc
@@ -0,0 +1,96 @@
+// Copyright (C) 2012-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 <log/buffer_appender_impl.h>
+
+#include <log4cplus/loglevel.h>
+#include <log4cplus/version.h>
+#include <boost/scoped_ptr.hpp>
+#include <cstdio>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+BufferAppender::~BufferAppender() {
+ // If there is anything left in the buffer,
+ // it means no reconfig has been done, and
+ // we can assume the logging system was either
+ // never setup, or broke while doing so.
+ // So dump all that is left to stdout
+ try {
+ flushStdout();
+ destructorImpl();
+ } catch (...) {
+ // Ok if we can't even seem to dump to stdout, never mind.
+ }
+}
+
+void
+BufferAppender::flushStdout() {
+ // This does not show a bit of information normal log messages
+ // do, so perhaps we should try and setup a new logger here
+ // However, as this is called from a destructor, it may not
+ // be a good idea; as we can't reliably know whether in what
+ // state the logger instance is now (or what the specific logger's
+ // settings were).
+ LogEventList::const_iterator it;
+ for (it = stored_.begin(); it != stored_.end(); ++it) {
+ const std::string level(it->first);
+ LogEventPtr event(it->second);
+ std::printf("%s [%s]: %s\n", level.c_str(),
+ event->getLoggerName().c_str(),
+ event->getMessage().c_str());
+ }
+ stored_.clear();
+}
+
+void
+BufferAppender::flush() {
+ LogEventList stored_copy;
+ stored_.swap(stored_copy);
+
+ LogEventList::const_iterator it;
+ for (it = stored_copy.begin(); it != stored_copy.end(); ++it) {
+ LogEventPtr event(it->second);
+ log4cplus::Logger logger =
+ log4cplus::Logger::getInstance(event->getLoggerName());
+
+ logger.log(event->getLogLevel(), event->getMessage());
+ }
+ flushed_ = true;
+}
+
+size_t
+BufferAppender::getBufferSize() const {
+ return (stored_.size());
+}
+
+void
+BufferAppender::append(const log4cplus::spi::InternalLoggingEvent& event) {
+ if (flushed_) {
+ isc_throw(LogBufferAddAfterFlush,
+ "Internal log buffer has been flushed already");
+ }
+ // get a clone, and put the pointer in a shared_ptr in the list
+#if LOG4CPLUS_VERSION < LOG4CPLUS_MAKE_VERSION(2, 0, 0)
+ std::auto_ptr<log4cplus::spi::InternalLoggingEvent>
+#else
+ std::unique_ptr<log4cplus::spi::InternalLoggingEvent>
+#endif
+ event_aptr = event.clone();
+ // Also store the string representation of the log level, to be
+ // used in flushStdout if necessary
+ stored_.push_back(LevelAndEvent(
+ log4cplus::LogLevelManager().toString(event.getLogLevel()),
+ LogEventPtr(event_aptr.release())));
+}
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
diff --git a/src/lib/log/buffer_appender_impl.h b/src/lib/log/buffer_appender_impl.h
new file mode 100644
index 0000000..10290b5
--- /dev/null
+++ b/src/lib/log/buffer_appender_impl.h
@@ -0,0 +1,110 @@
+// Copyright (C) 2012-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/.
+
+#ifndef LOG_BUFFER_H
+#define LOG_BUFFER_H
+
+#include <exceptions/exceptions.h>
+
+#include <log4cplus/logger.h>
+#include <log4cplus/spi/loggingevent.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+/// \brief Buffer add after flush
+///
+/// This exception is thrown if the log buffer's add() method
+/// is called after the log buffer has been flushed; the buffer
+/// is only supposed to be used once (until the first time a
+/// logger specification is processed)
+class LogBufferAddAfterFlush : public isc::Exception {
+public:
+ LogBufferAddAfterFlush(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// Convenience typedef for a pointer to a log event
+typedef boost::shared_ptr<log4cplus::spi::InternalLoggingEvent> LogEventPtr;
+
+/// Convenience typedef for a pair string/logeventptr, the string representing
+/// the logger level, as returned by LogLevelManager::toString() at the
+/// time of initial logging
+typedef std::pair<std::string, LogEventPtr> LevelAndEvent;
+
+/// Convenience typedef for a vector of LevelAndEvent instances
+typedef std::vector<LevelAndEvent> LogEventList;
+
+/// \brief Buffering Logger Appender
+///
+/// This class can be set as an Appender for log4cplus loggers,
+/// and is used to store logging events; it simply keeps any
+/// event that is passed to \c append(), and will replay them to the
+/// logger that they were originally created for when \c flush() is
+/// called.
+///
+/// The idea is that initially, a program may want to do some logging,
+/// but does not know where to yet (for instance because it has yet to
+/// read and parse its configuration). Any log messages before this time
+/// would normally go to some default (say, stdout), and be lost in the
+/// real logging destination. By buffering them (and flushing them once
+/// the logger has been configured), these log messages are kept in a
+/// consistent place, and are not lost.
+///
+/// Given this goal, this class has an extra check; it will raise
+/// an exception if \c append() is called after flush().
+///
+/// If the BufferAppender instance is destroyed before being flushed,
+/// it will dump any event it has left to stdout.
+class BufferAppender : public log4cplus::Appender {
+public:
+ /// \brief Constructor
+ ///
+ /// Constructs a BufferAppender that buffers log evens
+ BufferAppender() : flushed_(false) {}
+
+ /// \brief Destructor
+ ///
+ /// Any remaining events are flushed to stdout (there should
+ /// only be any events remaining if flush() was never called)
+ virtual ~BufferAppender();
+
+ /// \brief Close the appender
+ ///
+ /// This class has no specialized handling for this method
+ virtual void close() {}
+
+ /// \brief Flush the internal buffer
+ ///
+ /// Events that have been stored (after calls to \c append()
+ /// are replayed to the logger. Should only be called after
+ /// new appenders have been set to the logger.
+ void flush();
+
+ /// \brief Returns the number of stored logging events
+ ///
+ /// Mainly useful for testing
+ size_t getBufferSize() const;
+
+protected:
+ virtual void append(const log4cplus::spi::InternalLoggingEvent& event);
+private:
+ /// \brief Helper for the destructor, flush events to stdout
+ void flushStdout();
+
+ LogEventList stored_;
+ bool flushed_;
+};
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
+
+#endif // LOG_BUFFER_H
+
diff --git a/src/lib/log/compiler/Makefile.am b/src/lib/log/compiler/Makefile.am
new file mode 100644
index 0000000..ff414b8
--- /dev/null
+++ b/src/lib/log/compiler/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+if GENERATE_MESSAGES
+
+bin_PROGRAMS = kea-msg-compiler
+
+kea_msg_compiler_SOURCES = message.cc
+kea_msg_compiler_LDADD = $(top_builddir)/src/lib/log/libkea-log.la
+kea_msg_compiler_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+kea_msg_compiler_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+kea_msg_compiler_LDADD += $(LOG4CPLUS_LIBS)
+
+endif
diff --git a/src/lib/log/compiler/Makefile.in b/src/lib/log/compiler/Makefile.in
new file mode 100644
index 0000000..38c37b0
--- /dev/null
+++ b/src/lib/log/compiler/Makefile.in
@@ -0,0 +1,878 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@GENERATE_MESSAGES_TRUE@bin_PROGRAMS = kea-msg-compiler$(EXEEXT)
+subdir = src/lib/log/compiler
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__kea_msg_compiler_SOURCES_DIST = message.cc
+@GENERATE_MESSAGES_TRUE@am_kea_msg_compiler_OBJECTS = \
+@GENERATE_MESSAGES_TRUE@ message.$(OBJEXT)
+kea_msg_compiler_OBJECTS = $(am_kea_msg_compiler_OBJECTS)
+am__DEPENDENCIES_1 =
+@GENERATE_MESSAGES_TRUE@kea_msg_compiler_DEPENDENCIES = $(top_builddir)/src/lib/log/libkea-log.la \
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@GENERATE_MESSAGES_TRUE@ $(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 =
+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)/message.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 =
+SOURCES = $(kea_msg_compiler_SOURCES)
+DIST_SOURCES = $(am__kea_msg_compiler_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+@GENERATE_MESSAGES_TRUE@kea_msg_compiler_SOURCES = message.cc
+@GENERATE_MESSAGES_TRUE@kea_msg_compiler_LDADD = $(top_builddir)/src/lib/log/libkea-log.la \
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@GENERATE_MESSAGES_TRUE@ $(LOG4CPLUS_LIBS)
+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/log/compiler/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/log/compiler/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-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_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
+
+kea-msg-compiler$(EXEEXT): $(kea_msg_compiler_OBJECTS) $(kea_msg_compiler_DEPENDENCIES) $(EXTRA_kea_msg_compiler_DEPENDENCIES)
+ @rm -f kea-msg-compiler$(EXEEXT)
+ $(AM_V_CXXLD)$(CXXLINK) $(kea_msg_compiler_OBJECTS) $(kea_msg_compiler_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/message.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 $@ $<
+
+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
+
+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: check-recursive
+all-am: Makefile $(PROGRAMS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/message.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-binPROGRAMS
+
+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)/message.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: uninstall-binPROGRAMS
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-binPROGRAMS \
+ clean-generic 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-binPROGRAMS \
+ 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 \
+ uninstall-binPROGRAMS
+
+.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/log/compiler/message.cc b/src/lib/log/compiler/message.cc
new file mode 100644
index 0000000..8f19c5a
--- /dev/null
+++ b/src/lib/log/compiler/message.cc
@@ -0,0 +1,543 @@
+// Copyright (C) 2011-2021 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 <cctype>
+#include <cstddef>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/filename.h>
+#include <util/strutil.h>
+
+#include <log/log_messages.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+
+#include <log/logger.h>
+
+#include <boost/foreach.hpp>
+
+using namespace std;
+using namespace isc::log;
+using namespace isc::util;
+
+/// \file log/compiler/message.cc
+/// \brief Message Compiler
+///
+/// \b Overview<BR>
+/// This is the program that takes as input a message file and produces:
+///
+/// \li A .h file containing message definition
+/// \li A .cc file containing code that adds the messages to the program's
+/// message dictionary at start-up time.
+///
+/// \b Invocation<BR>
+/// The program is invoked with the command:
+///
+/// <tt>kea-msg-compiler [-v | -h | -d &lt;dir&gt; | <message-file>]</tt>
+///
+/// It reads the message file and writes out two files of the same
+/// name in the current working directory (unless -d is used) but
+/// with extensions of .h and .cc.
+///
+/// -v causes it to print the version number and exit. -h prints a
+/// help message (and exits). -d &lt;dir&gt; will make it write the
+/// output file(s) to dir instead of current working directory
+
+/// \brief Print Version
+///
+/// Prints the program's version number.
+
+void
+version() {
+ cout << VERSION << "\n";
+}
+
+/// \brief Print Usage
+///
+/// Prints program usage to stdout.
+
+void
+usage() {
+ cout <<
+ "Usage: kea-msg-compiler [-h] [-v] [-d dir] <message-file>\n" <<
+ "\n" <<
+ "-h Print this message and exit\n" <<
+ "-v Print the program version and exit\n" <<
+ "-d <dir> Place output files in given directory\n" <<
+ "\n" <<
+ "<message-file> is the name of the input message file.\n";
+}
+
+/// \brief Create Header Sentinel
+///
+/// Given the name of a file, create an \#ifdef sentinel name. The name is
+/// &lt;name&gt;_&lt;ext&gt;, where &lt;name&gt; is the name of the file, and &lt;ext&gt;
+/// is the extension less the leading period. The sentinel will be upper-case.
+///
+/// \param file Filename object representing the file.
+///
+/// \return Sentinel name
+
+string
+sentinel(Filename& file) {
+
+ string name = file.name();
+ string ext = file.extension();
+ string sentinel_text = name + "_" + ext.substr(1);
+ isc::util::str::uppercase(sentinel_text);
+ return (sentinel_text);
+}
+
+/// \brief Quote String
+///
+/// Inserts an escape character (a backslash) prior to any double quote
+/// characters. This is used to handle the fact that the input file does not
+/// contain quotes, yet the string will be included in a C++ literal string.
+
+string
+quoteString(const string& instring) {
+
+ // Create the output string and reserve the space needed to hold the input
+ // string. (Most input strings will not contain quotes, so this single
+ // reservation should be all that is needed.)
+ string outstring;
+ outstring.reserve(instring.size());
+
+ // Iterate through the input string, preceding quotes with a slash.
+ for (size_t i = 0; i < instring.size(); ++i) {
+ if (instring[i] == '"') {
+ outstring += '\\';
+ }
+ outstring += instring[i];
+ }
+
+ return (outstring);
+}
+
+/// \brief Sorted Identifiers
+///
+/// Given a dictionary, return a vector holding the message IDs in sorted
+/// order.
+///
+/// \param dictionary Dictionary to examine
+///
+/// \return Sorted list of message IDs
+
+vector<string>
+sortedIdentifiers(MessageDictionary& dictionary) {
+ vector<string> ident;
+
+ for (MessageDictionary::const_iterator i = dictionary.begin();
+ i != dictionary.end(); ++i) {
+ ident.push_back(i->first);
+ }
+ sort(ident.begin(), ident.end());
+
+ return (ident);
+}
+
+/// \brief Split Namespace
+///
+/// The $NAMESPACE directive may well specify a namespace in the form a::b.
+/// Unfortunately, the C++ "namespace" statement can only accept a single
+/// string - to set up the namespace of "a::b" requires two statements, one
+/// for "namespace a" and the other for "namespace b".
+///
+/// This function returns the set of namespace components as a vector of
+/// strings. A vector of one element, containing the empty string, is returned
+/// if the anonymous namespace is specified.
+///
+/// \param ns Argument to $NAMESPACE (passed by value, as we will be modifying
+/// it.)
+
+vector<string>
+splitNamespace(string ns) {
+
+ // Namespaces components are separated by double colon characters -
+ // convert to single colons.
+ size_t dcolon;
+ while ((dcolon = ns.find("::")) != string::npos) {
+ ns.replace(dcolon, 2, ":");
+ }
+
+ // ... and return the vector of namespace components split on the single
+ // colon.
+ return (isc::util::str::tokens(ns, ":"));
+}
+
+/// \brief Write Opening Namespace(s)
+///
+/// Writes the lines listing the namespaces in use.
+void
+writeOpeningNamespace(ostream& output, const vector<string>& ns) {
+ if (!ns.empty()) {
+
+ // Output namespaces in correct order
+ for (vector<string>::size_type i = 0; i < ns.size(); ++i) {
+ output << "namespace " << ns[i] << " {\n";
+ }
+ output << "\n";
+ }
+}
+
+/// \brief Write Closing Namespace(s)
+///
+/// Writes the lines listing the namespaces in use.
+void
+writeClosingNamespace(ostream& output, const vector<string>& ns) {
+ if (!ns.empty()) {
+ for (int i = ns.size() - 1; i >= 0; --i) {
+ output << "} // namespace " << ns[i] << "\n";
+ }
+ output << "\n";
+ }
+}
+
+/// \brief Write Header File
+///
+/// Writes the C++ header file containing the symbol definitions. These are
+/// "extern" references to definitions in the .cc file. As such, they should
+/// take up no space in the module in which they are included, and redundant
+/// references should be removed by the compiler.
+///
+/// \param file Name of the message file. The header file is written to a
+/// file of the same name but with a .h suffix.
+/// \param ns_components Namespace in which the definitions are to be placed.
+/// An empty string indicates no namespace.
+/// \param dictionary Dictionary holding the message definitions.
+/// \param output_directory if not null NULL, output files are written
+/// to the given directory. If NULL, they are written to the current
+/// working directory.
+void
+writeHeaderFile(const string& file,
+ const vector<string>& ns_components,
+ MessageDictionary& dictionary,
+ const char* output_directory) {
+ Filename message_file(file);
+ Filename header_file(Filename(message_file.name()).useAsDefault(".h"));
+ if (output_directory != NULL) {
+ header_file.setDirectory(output_directory);
+ }
+
+ // Text to use as the sentinels.
+ string sentinel_text = sentinel(header_file);
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ // Open the output file for writing
+ ofstream hfile(header_file.fullName().c_str());
+
+ if (hfile.fail()) {
+ isc_throw_4(MessageException, "Failed to open output file",
+ LOG_OPEN_OUTPUT_FAIL, header_file.fullName(),
+ strerror(errno), 0);
+ }
+
+ // Write the header preamble. If there is an error, we'll pick it up
+ // after the last write.
+
+ hfile <<
+ "// File created from " << message_file.fullName() << "\n" <<
+ "\n" <<
+ "#ifndef " << sentinel_text << "\n" <<
+ "#define " << sentinel_text << "\n" <<
+ "\n" <<
+ "#include <log/message_types.h>\n" <<
+ "\n";
+
+ // Write the message identifiers, bounded by a namespace declaration
+ writeOpeningNamespace(hfile, ns_components);
+
+ vector<string> idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator j = idents.begin();
+ j != idents.end(); ++j) {
+ hfile << "extern const isc::log::MessageID " << *j << ";\n";
+ }
+ hfile << "\n";
+
+ writeClosingNamespace(hfile, ns_components);
+
+ // ... and finally the postamble
+ hfile << "#endif // " << sentinel_text << "\n";
+
+ // Report errors (if any) and exit
+ if (hfile.fail()) {
+ isc_throw_4(MessageException, "Error writing to output file",
+ LOG_WRITE_ERROR, header_file.fullName(), strerror(errno),
+ 0);
+ }
+
+ hfile.close();
+}
+
+/// \brief Convert Non Alpha-Numeric Characters to Underscores
+///
+/// Simple function for use in a call to transform
+char
+replaceNonAlphaNum(char c) {
+ return (isalnum(c) ? c : '_');
+}
+
+/// \brief Write Program File
+///
+/// Writes the C++ source code file. This defines the text of the message
+/// symbols, as well as the initializer object that sets the entries in the
+/// global dictionary.
+///
+/// The construction of the initializer object loads the dictionary with the
+/// message text. However, nothing actually references it. If the initializer
+/// were in a file by itself, the lack of things referencing it would cause the
+/// linker to ignore it when pulling modules out of the logging library in a
+/// static link. By including it in the file with the symbol definitions, the
+/// module will get included in the link process to resolve the symbol
+/// definitions, and so the initializer object will be included in the final
+/// image. (Note that there are no such problems when the logging library is
+/// built as a dynamically-linked library: the whole library - including the
+/// initializer module - gets mapped into address space when the library is
+/// loaded, after which all the initializing code (including the constructors
+/// of objects declared outside functions) gets run.)
+///
+/// There _may_ be a problem when we come to port this to Windows. Microsoft
+/// Visual Studio contains a "Whole Program Optimization" option, where the
+/// optimization is done at link-time, not compiler-time. In this it _may_
+/// decide to remove the initializer object because of a lack of references
+/// to it. But until BIND-10 is ported to Windows, we won't know.
+///
+/// \param file Name of the message file. The header file is written to a
+/// file of the same name but with a .h suffix.
+/// \param ns_components Namespace in which the definitions are to be placed.
+/// An empty string indicates no namespace.
+/// \param dictionary Dictionary holding the message definitions.
+/// \param output_directory if not null NULL, output files are written
+/// to the given directory. If NULL, they are written to the current
+/// working directory.
+void
+writeProgramFile(const string& file,
+ const vector<string>& ns_components,
+ MessageDictionary& dictionary,
+ const char* output_directory) {
+ Filename message_file(file);
+ Filename program_file(Filename(message_file.name()).useAsDefault(".cc"));
+ if (output_directory) {
+ program_file.setDirectory(output_directory);
+ }
+
+ // zero out the errno to be safe
+ errno = 0;
+
+ // Open the output file for writing
+ ofstream ccfile(program_file.fullName().c_str());
+
+ if (ccfile.fail()) {
+ isc_throw_4(MessageException, "Error opening output file",
+ LOG_OPEN_OUTPUT_FAIL, program_file.fullName(),
+ strerror(errno), 0);
+ }
+
+ // Write the preamble. If there is an error, we'll pick it up after
+ // the last write.
+
+ ccfile <<
+ "// File created from " << message_file.fullName() << "\n" <<
+ "\n" <<
+ "#include <cstddef>\n" <<
+ "#include <log/message_types.h>\n" <<
+ "#include <log/message_initializer.h>\n" <<
+ "\n";
+
+ // Declare the message symbols themselves.
+
+ writeOpeningNamespace(ccfile, ns_components);
+
+ vector<string> idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator j = idents.begin();
+ j != idents.end(); ++j) {
+ ccfile << "extern const isc::log::MessageID " << *j <<
+ " = \"" << *j << "\";\n";
+ }
+ ccfile << "\n";
+
+ writeClosingNamespace(ccfile, ns_components);
+
+ // Now the code for the message initialization.
+
+ ccfile <<
+ "namespace {\n" <<
+ "\n" <<
+ "const char* values[] = {\n";
+
+ // Output the identifiers and the associated text.
+ idents = sortedIdentifiers(dictionary);
+ for (vector<string>::const_iterator i = idents.begin();
+ i != idents.end(); ++i) {
+ ccfile << " \"" << *i << "\", \"" <<
+ quoteString(dictionary.getText(*i)) << "\",\n";
+ }
+
+ // ... and the postamble
+ ccfile <<
+ " NULL\n" <<
+ "};\n" <<
+ "\n" <<
+ "const isc::log::MessageInitializer initializer(values);\n" <<
+ "\n" <<
+ "} // Anonymous namespace\n" <<
+ "\n";
+
+ // Report errors (if any) and exit
+ if (ccfile.fail()) {
+ isc_throw_4(MessageException, "Error writing to output file",
+ LOG_WRITE_ERROR, program_file.fullName(), strerror(errno),
+ 0);
+ }
+
+ ccfile.close();
+}
+
+/// \brief Error and exit if there are duplicate entries
+///
+/// If the input file contained duplicate message IDs, we print an
+/// error for each of them, then exit the program with a non-0 value.
+///
+/// \param reader Message Reader used to read the file
+void
+errorDuplicates(MessageReader& reader) {
+
+ // Get the duplicates (the overflow) and, if present, sort them into some
+ // order and remove those which occur more than once (which mean that they
+ // occur more than twice in the input file).
+ MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
+ if (!duplicates.empty()) {
+ cout << "Error: the following duplicate IDs were found:\n";
+
+ sort(duplicates.begin(), duplicates.end());
+ MessageReader::MessageIDCollection::iterator new_end =
+ unique(duplicates.begin(), duplicates.end());
+ for (MessageReader::MessageIDCollection::iterator i = duplicates.begin();
+ i != new_end; ++i) {
+ cout << " " << *i << "\n";
+ }
+ exit(1);
+ }
+}
+
+/// \brief Main Program
+///
+/// Parses the options then dispatches to the appropriate function. See the
+/// main file header for the invocation.
+int
+main(int argc, char* argv[]) {
+
+ const char* soptions = "hvpd:"; // Short options
+
+ optind = 1; // Ensure we start a new scan
+ int opt; // Value of the option
+
+ const char *output_directory = NULL;
+
+ while ((opt = getopt(argc, argv, soptions)) != -1) {
+ switch (opt) {
+ case 'd':
+ output_directory = optarg;
+ break;
+
+ case 'h':
+ usage();
+ return (0);
+
+ case 'v':
+ version();
+ return (0);
+
+ default:
+ // A message will have already been output about the error.
+ return (1);
+ }
+ }
+
+ // Do we have the message file?
+ if (optind < (argc - 1)) {
+ cout << "Error: excess arguments in command line\n";
+ usage();
+ return (1);
+ } else if (optind >= argc) {
+ cout << "Error: missing message file\n";
+ usage();
+ return (1);
+ }
+ string message_file = argv[optind];
+
+ try {
+ // Have identified the file, so process it. First create a local
+ // dictionary into which the data will be put.
+ MessageDictionary dictionary;
+
+ // Read the data into it.
+ MessageReader reader(&dictionary);
+ reader.readFile(message_file);
+
+ // Error (and quit) if there are of any duplicates encountered.
+ errorDuplicates(reader);
+
+ // Get the namespace into which the message definitions will be put and
+ // split it into components.
+ vector<string> ns_components =
+ splitNamespace(reader.getNamespace());
+
+ // Write the header file.
+ writeHeaderFile(message_file, ns_components, dictionary,
+ output_directory);
+
+ // Write the file that defines the message symbols and text
+ writeProgramFile(message_file, ns_components, dictionary,
+ output_directory);
+
+ } catch (const MessageException& e) {
+ // Create an error message from the ID and the text
+ const MessageDictionaryPtr& global = MessageDictionary::globalDictionary();
+ string text = e.id();
+ text += ", ";
+ text += global->getText(e.id());
+ // Format with arguments
+ vector<string> args(e.arguments());
+ for (size_t i(0); i < args.size(); ++ i) {
+ try {
+ replacePlaceholder(text, args[i], i + 1);
+ } catch (...) {
+ // Error in error handling: nothing right to do...
+ }
+ }
+
+ cerr << text << "\n";
+
+ return (1);
+ } catch (const std::exception& ex) {
+ cerr << "Fatal error: " << ex.what() << "\n";
+
+ return (1);
+ } catch (...) {
+ cerr << "Fatal error\n";
+
+ return (1);
+ }
+
+ return (0);
+}
diff --git a/src/lib/log/interprocess/Makefile.am b/src/lib/log/interprocess/Makefile.am
new file mode 100644
index 0000000..676af42
--- /dev/null
+++ b/src/lib/log/interprocess/Makefile.am
@@ -0,0 +1,21 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -DLOCKFILE_DIR=\"$(localstatedir)/run/$(PACKAGE_NAME)\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_LTLIBRARIES = libkea-log_interprocess.la
+
+libkea_log_interprocess_la_SOURCES = interprocess_sync.h
+libkea_log_interprocess_la_SOURCES += interprocess_sync_file.h
+libkea_log_interprocess_la_SOURCES += interprocess_sync_file.cc
+libkea_log_interprocess_la_SOURCES += interprocess_sync_null.h
+libkea_log_interprocess_la_SOURCES += interprocess_sync_null.cc
+
+libkea_log_interprocess_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+
+EXTRA_DIST = README
diff --git a/src/lib/log/interprocess/Makefile.in b/src/lib/log/interprocess/Makefile.in
new file mode 100644
index 0000000..95c3b6e
--- /dev/null
+++ b/src/lib/log/interprocess/Makefile.in
@@ -0,0 +1,854 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/log/interprocess
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libkea_log_interprocess_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+am_libkea_log_interprocess_la_OBJECTS = interprocess_sync_file.lo \
+ interprocess_sync_null.lo
+libkea_log_interprocess_la_OBJECTS = \
+ $(am_libkea_log_interprocess_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 =
+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)/interprocess_sync_file.Plo \
+ ./$(DEPDIR)/interprocess_sync_null.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_log_interprocess_la_SOURCES)
+DIST_SOURCES = $(libkea_log_interprocess_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
+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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ -DLOCKFILE_DIR=\"$(localstatedir)/run/$(PACKAGE_NAME)\" \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+noinst_LTLIBRARIES = libkea-log_interprocess.la
+libkea_log_interprocess_la_SOURCES = interprocess_sync.h \
+ interprocess_sync_file.h interprocess_sync_file.cc \
+ interprocess_sync_null.h interprocess_sync_null.cc
+libkea_log_interprocess_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+EXTRA_DIST = README
+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/log/interprocess/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/log/interprocess/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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-log_interprocess.la: $(libkea_log_interprocess_la_OBJECTS) $(libkea_log_interprocess_la_DEPENDENCIES) $(EXTRA_libkea_log_interprocess_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(libkea_log_interprocess_la_OBJECTS) $(libkea_log_interprocess_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/interprocess_sync_file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/interprocess_sync_null.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)$(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 $@ $<
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/interprocess_sync_file.Plo
+ -rm -f ./$(DEPDIR)/interprocess_sync_null.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-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)/interprocess_sync_file.Plo
+ -rm -f ./$(DEPDIR)/interprocess_sync_null.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/log/interprocess/README b/src/lib/log/interprocess/README
new file mode 100644
index 0000000..6f48dc6
--- /dev/null
+++ b/src/lib/log/interprocess/README
@@ -0,0 +1,13 @@
+The files in this directory implement a helper sub-library of the
+inter process locking for the log library. We use our own locks
+because such locks are only available in relatively recent versions of
+log4cplus. Also (against our usual practice) we somehow re-invented
+an in-house version of such a general purpose library rather than
+existing proven tools such as boost::interprocess. While we decided
+to go with the in-house version for the log library at least until we
+completely switch to log4cplus's native lock support, no other BIND 10
+module should use this; they should use existing external
+tools/libraries.
+
+This sub-library is therefore "hidden" here. As such, none of these
+files should be installed.
diff --git a/src/lib/log/interprocess/interprocess_sync.h b/src/lib/log/interprocess/interprocess_sync.h
new file mode 100644
index 0000000..0692ee1
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync.h
@@ -0,0 +1,143 @@
+// Copyright (C) 2012-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/.
+
+#ifndef INTERPROCESS_SYNC_H
+#define INTERPROCESS_SYNC_H
+
+#include <string>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+class InterprocessSyncLocker; // forward declaration
+
+/// \brief Interprocess Sync Class
+///
+/// This class specifies an interface for mutual exclusion among
+/// co-operating processes. This is an abstract class and a real
+/// implementation such as InterprocessSyncFile should be used
+/// in code. Usage is as follows:
+///
+/// 1. Client instantiates a sync object of an implementation (such as
+/// InterprocessSyncFile).
+/// 2. Client then creates an automatic (stack) object of
+/// InterprocessSyncLocker around the sync object. Such an object
+/// destroys itself and releases any acquired lock when it goes out of extent.
+/// 3. Client calls lock() method on the InterprocessSyncLocker.
+/// 4. Client performs task that needs mutual exclusion.
+/// 5. Client frees lock with unlock(), or simply returns from the basic
+/// block which forms the scope for the InterprocessSyncLocker.
+///
+/// NOTE: All implementations of InterprocessSync should keep the
+/// is_locked_ member variable updated whenever their
+/// lock()/tryLock()/unlock() implementations are called.
+class InterprocessSync {
+ // InterprocessSyncLocker is the only code outside this class that
+ // should be allowed to call the lock(), tryLock() and unlock()
+ // methods.
+ friend class InterprocessSyncLocker;
+
+public:
+ /// \brief Constructor
+ ///
+ /// Creates an interprocess synchronization object
+ ///
+ /// \param task_name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSync(const std::string& task_name) :
+ task_name_(task_name), is_locked_(false)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSync() {}
+
+protected:
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ virtual bool lock() = 0;
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ virtual bool tryLock() = 0;
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ virtual bool unlock() = 0;
+
+ const std::string task_name_; ///< The task name
+ bool is_locked_; ///< Is the lock taken?
+};
+
+/// \brief Interprocess Sync Locker Class
+///
+/// This class is used for making automatic stack objects to manage
+/// locks that are released automatically when the block is exited
+/// (RAII). It is meant to be used along with InterprocessSync objects. See
+/// the description of InterprocessSync.
+class InterprocessSyncLocker {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a lock manager around a interprocess synchronization object
+ ///
+ /// \param sync The sync object which has to be locked/unlocked by
+ /// this locker object.
+ InterprocessSyncLocker(InterprocessSync& sync) :
+ sync_(sync)
+ {}
+
+ /// \brief Destructor
+ ~InterprocessSyncLocker() {
+ if (isLocked())
+ unlock();
+ }
+
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool lock() {
+ return (sync_.lock());
+ }
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if a new lock could be acquired, false
+ /// otherwise.
+ bool tryLock() {
+ return (sync_.tryLock());
+ }
+
+ /// \brief Check if the lock is taken
+ ///
+ /// \return Returns true if a lock is currently acquired, false
+ /// otherwise.
+ bool isLocked() const {
+ return (sync_.is_locked_);
+ }
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ bool unlock() {
+ return (sync_.unlock());
+ }
+
+protected:
+ InterprocessSync& sync_; ///< Ref to underlying sync object
+};
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
+
+#endif // INTERPROCESS_SYNC_H
diff --git a/src/lib/log/interprocess/interprocess_sync_file.cc b/src/lib/log/interprocess/interprocess_sync_file.cc
new file mode 100644
index 0000000..ed6a5b7
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_file.cc
@@ -0,0 +1,132 @@
+// Copyright (C) 2012-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/.
+
+// This file requires LOCKFILE_DIR to be defined. It points to the default
+// directory where lockfile will be created.
+
+#include <config.h>
+
+#include <log/interprocess/interprocess_sync_file.h>
+
+#include <string>
+#include <cerrno>
+#include <cstring>
+#include <sstream>
+#include <iostream>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+InterprocessSyncFile::~InterprocessSyncFile() {
+ if (fd_ != -1) {
+ // This will also release any applied locks.
+ close(fd_);
+ // The lockfile will continue to exist, and we must not delete
+ // it.
+ }
+}
+
+bool
+InterprocessSyncFile::do_lock(int cmd, short l_type) {
+ // Open lock file only when necessary (i.e., here). This is so that
+ // if a default InterprocessSync object is replaced with another
+ // implementation, it doesn't attempt any opens.
+ if (fd_ == -1) {
+ std::string lockfile_path = LOCKFILE_DIR;
+
+ const char* const env = getenv("KEA_LOCKFILE_DIR");
+ if (env != NULL) {
+ lockfile_path = env;
+ }
+
+ lockfile_path += "/" + task_name_ + "_lockfile";
+
+ // Open the lockfile in the constructor so it doesn't do the access
+ // checks every time a message is logged.
+ const mode_t mode = umask(S_IXUSR | S_IXGRP | S_IXOTH); // 0111
+ fd_ = open(lockfile_path.c_str(), O_CREAT | O_RDWR,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); // 0660
+ umask(mode);
+
+ if (fd_ == -1) {
+ std::stringstream tmp;
+
+ // We failed to create a lockfile. This means that the logging
+ // system is unusable. We need to report the issue using plain
+ // print to stderr.
+ tmp << "Unable to use interprocess sync lockfile ("
+ << std::strerror(errno) << "): " << lockfile_path;
+ std::cerr << tmp.str() << std::endl;
+
+ // And then throw exception as usual.
+ isc_throw(InterprocessSyncFileError, tmp.str());
+ }
+ }
+
+ struct flock lock;
+
+ memset(&lock, 0, sizeof (lock));
+ lock.l_type = l_type;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+
+ return (fcntl(fd_, cmd, &lock) == 0);
+}
+
+bool
+InterprocessSyncFile::lock() {
+ if (is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLKW, F_WRLCK)) {
+ is_locked_ = true;
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+InterprocessSyncFile::tryLock() {
+ if (is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLK, F_WRLCK)) {
+ is_locked_ = true;
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+InterprocessSyncFile::unlock() {
+ if (!is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLKW, F_UNLCK)) {
+ is_locked_ = false;
+ return (true);
+ }
+
+ return (false);
+}
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/interprocess/interprocess_sync_file.h b/src/lib/log/interprocess/interprocess_sync_file.h
new file mode 100644
index 0000000..9613e7f
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_file.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2012-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/.
+
+#ifndef INTERPROCESS_SYNC_FILE_H
+#define INTERPROCESS_SYNC_FILE_H
+
+#include <log/interprocess/interprocess_sync.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+/// \brief InterprocessSyncFileError
+///
+/// Exception that is thrown if it's not possible to open the
+/// lock file.
+class InterprocessSyncFileError : public Exception {
+public:
+ InterprocessSyncFileError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief File-based Interprocess Sync Class
+///
+/// This class specifies a concrete implementation for a file-based
+/// interprocess synchronization mechanism. Please see the
+/// InterprocessSync class documentation for usage.
+///
+/// An InterprocessSyncFileError exception may be thrown if there is an
+/// issue opening the lock file.
+///
+/// Lock files are created typically in the local state directory
+/// (var). They are typically named like "<task_name>_lockfile".
+/// This implementation opens lock files lazily (only when
+/// necessary). It also leaves the lock files lying around as multiple
+/// processes may have locks on them.
+class InterprocessSyncFile : public InterprocessSync {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a file-based interprocess synchronization object
+ ///
+ /// \param task_name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSyncFile(const std::string& task_name) :
+ InterprocessSync(task_name), fd_(-1)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSyncFile();
+
+protected:
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool lock();
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool tryLock();
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ bool unlock();
+
+private:
+ bool do_lock(int cmd, short l_type);
+
+ int fd_; ///< The descriptor for the open file
+};
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
+
+#endif // INTERPROCESS_SYNC_FILE_H
diff --git a/src/lib/log/interprocess/interprocess_sync_null.cc b/src/lib/log/interprocess/interprocess_sync_null.cc
new file mode 100644
index 0000000..ccd9e32
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_null.cc
@@ -0,0 +1,38 @@
+// Copyright (C) 2012-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 <log/interprocess/interprocess_sync_null.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+InterprocessSyncNull::~InterprocessSyncNull() {
+}
+
+bool
+InterprocessSyncNull::lock() {
+ is_locked_ = true;
+ return (true);
+}
+
+bool
+InterprocessSyncNull::tryLock() {
+ is_locked_ = true;
+ return (true);
+}
+
+bool
+InterprocessSyncNull::unlock() {
+ is_locked_ = false;
+ return (true);
+}
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/interprocess/interprocess_sync_null.h b/src/lib/log/interprocess/interprocess_sync_null.h
new file mode 100644
index 0000000..8b0c57b
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_null.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2012-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/.
+
+#ifndef INTERPROCESS_SYNC_NULL_H
+#define INTERPROCESS_SYNC_NULL_H
+
+#include <log/interprocess/interprocess_sync.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+/// \brief Null Interprocess Sync Class
+///
+/// This class specifies a concrete implementation for a null (no effect)
+/// interprocess synchronization mechanism. Please see the
+/// InterprocessSync class documentation for usage.
+class InterprocessSyncNull : public InterprocessSync {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a null interprocess synchronization object
+ ///
+ /// \param task_name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSyncNull(const std::string& task_name) :
+ InterprocessSync(task_name)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSyncNull();
+
+protected:
+ /// \brief Acquire the lock (never blocks)
+ ///
+ /// \return Always returns true
+ bool lock();
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Always returns true
+ bool tryLock();
+
+ /// \brief Release the lock
+ ///
+ /// \return Always returns true
+ bool unlock();
+};
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
+
+#endif // INTERPROCESS_SYNC_NULL_H
diff --git a/src/lib/log/interprocess/tests/Makefile.am b/src/lib/log/interprocess/tests/Makefile.am
new file mode 100644
index 0000000..a51a4b3
--- /dev/null
+++ b/src/lib/log/interprocess/tests/Makefile.am
@@ -0,0 +1,36 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+# XXX: we'll pollute the top builddir for creating a temporary test file
+# used to bind a UNIX domain socket so we can minimize the risk of exceeding
+# the limit of file name path size.
+AM_CPPFLAGS += -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\"
+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 = run_unittests.cc
+run_unittests_SOURCES += interprocess_sync_file_unittest.cc
+run_unittests_SOURCES += interprocess_sync_null_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.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 += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/log/interprocess/tests/Makefile.in b/src/lib/log/interprocess/tests/Makefile.in
new file mode 100644
index 0000000..dcbb165
--- /dev/null
+++ b/src/lib/log/interprocess/tests/Makefile.in
@@ -0,0 +1,1024 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/log/interprocess/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = run_unittests.cc \
+ interprocess_sync_file_unittest.cc \
+ interprocess_sync_null_unittest.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-interprocess_sync_file_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-interprocess_sync_null_unittest.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.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_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-interprocess_sync_file_unittest.Po \
+ ./$(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 =
+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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+# XXX: we'll pollute the top builddir for creating a temporary test file
+# used to bind a UNIX domain socket so we can minimize the risk of exceeding
+# the limit of file name path size.
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\"
+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 = run_unittests.cc \
+@HAVE_GTEST_TRUE@ interprocess_sync_file_unittest.cc \
+@HAVE_GTEST_TRUE@ interprocess_sync_null_unittest.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/log/interprocess/libkea-log_interprocess.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@ $(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/log/interprocess/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/log/interprocess/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-interprocess_sync_file_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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-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`
+
+run_unittests-interprocess_sync_file_unittest.o: interprocess_sync_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-interprocess_sync_file_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-interprocess_sync_file_unittest.Tpo -c -o run_unittests-interprocess_sync_file_unittest.o `test -f 'interprocess_sync_file_unittest.cc' || echo '$(srcdir)/'`interprocess_sync_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-interprocess_sync_file_unittest.Tpo $(DEPDIR)/run_unittests-interprocess_sync_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interprocess_sync_file_unittest.cc' object='run_unittests-interprocess_sync_file_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-interprocess_sync_file_unittest.o `test -f 'interprocess_sync_file_unittest.cc' || echo '$(srcdir)/'`interprocess_sync_file_unittest.cc
+
+run_unittests-interprocess_sync_file_unittest.obj: interprocess_sync_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-interprocess_sync_file_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-interprocess_sync_file_unittest.Tpo -c -o run_unittests-interprocess_sync_file_unittest.obj `if test -f 'interprocess_sync_file_unittest.cc'; then $(CYGPATH_W) 'interprocess_sync_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/interprocess_sync_file_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-interprocess_sync_file_unittest.Tpo $(DEPDIR)/run_unittests-interprocess_sync_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interprocess_sync_file_unittest.cc' object='run_unittests-interprocess_sync_file_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-interprocess_sync_file_unittest.obj `if test -f 'interprocess_sync_file_unittest.cc'; then $(CYGPATH_W) 'interprocess_sync_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/interprocess_sync_file_unittest.cc'; fi`
+
+run_unittests-interprocess_sync_null_unittest.o: interprocess_sync_null_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-interprocess_sync_null_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Tpo -c -o run_unittests-interprocess_sync_null_unittest.o `test -f 'interprocess_sync_null_unittest.cc' || echo '$(srcdir)/'`interprocess_sync_null_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Tpo $(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interprocess_sync_null_unittest.cc' object='run_unittests-interprocess_sync_null_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-interprocess_sync_null_unittest.o `test -f 'interprocess_sync_null_unittest.cc' || echo '$(srcdir)/'`interprocess_sync_null_unittest.cc
+
+run_unittests-interprocess_sync_null_unittest.obj: interprocess_sync_null_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-interprocess_sync_null_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Tpo -c -o run_unittests-interprocess_sync_null_unittest.obj `if test -f 'interprocess_sync_null_unittest.cc'; then $(CYGPATH_W) 'interprocess_sync_null_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/interprocess_sync_null_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Tpo $(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interprocess_sync_null_unittest.cc' object='run_unittests-interprocess_sync_null_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-interprocess_sync_null_unittest.obj `if test -f 'interprocess_sync_null_unittest.cc'; then $(CYGPATH_W) 'interprocess_sync_null_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/interprocess_sync_null_unittest.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-interprocess_sync_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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-interprocess_sync_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-interprocess_sync_null_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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/log/interprocess/tests/interprocess_sync_file_unittest.cc b/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
new file mode 100644
index 0000000..d80f288
--- /dev/null
+++ b/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright (C) 2012-2015,2017 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 <log/interprocess/interprocess_sync_file.h>
+
+#include <util/unittests/check_valgrind.h>
+#include <util/unittests/interprocess_util.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc::log::interprocess;
+using isc::util::unittests::parentReadState;
+
+namespace {
+TEST(InterprocessSyncFileTest, BadFile) {
+ InterprocessSyncFile sync("/no-such--dir/or--file");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_FALSE(locker.isLocked());
+ ASSERT_THROW(locker.lock(), InterprocessSyncFileError);
+}
+
+TEST(InterprocessSyncFileTest, TestLock) {
+ InterprocessSyncFile sync("test");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.isLocked());
+
+ if (!isc::util::unittests::runningOnValgrind()) {
+
+ int fds[2];
+
+ // Here, we check that a lock has been taken by forking and
+ // checking from the child that a lock exists. This has to be
+ // done from a separate process as we test by trying to lock the
+ // range again on the lock file. The lock attempt would pass if
+ // done from the same process for the granted range. The lock
+ // attempt must fail to pass our check.
+
+ EXPECT_EQ(0, pipe(fds));
+
+ if (fork() == 0) {
+ unsigned char locked = 0;
+ // Child writes to pipe
+ close(fds[0]);
+
+ InterprocessSyncFile sync2("test");
+ InterprocessSyncLocker locker2(sync2);
+
+ if (!locker2.tryLock()) {
+ EXPECT_FALSE(locker2.isLocked());
+ locked = 1;
+ } else {
+ EXPECT_TRUE(locker2.isLocked());
+ }
+
+ ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
+ EXPECT_EQ(sizeof(locked), bytes_written);
+
+ close(fds[1]);
+ exit(0);
+ } else {
+ // Parent reads from pipe
+ close(fds[1]);
+
+ const unsigned char locked = parentReadState(fds[0]);
+
+ close(fds[0]);
+
+ EXPECT_EQ(1, locked);
+ }
+ }
+
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_FALSE(locker.isLocked());
+
+ EXPECT_EQ (0, remove(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
+}
+
+TEST(InterprocessSyncFileTest, TestMultipleFilesDirect) {
+ InterprocessSyncFile sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_TRUE(locker.lock());
+
+ InterprocessSyncFile sync2("test2");
+ InterprocessSyncLocker locker2(sync2);
+ EXPECT_TRUE(locker2.lock());
+ EXPECT_TRUE(locker2.unlock());
+
+ EXPECT_TRUE(locker.unlock());
+
+ EXPECT_EQ (0, remove(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
+ EXPECT_EQ (0, remove(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
+}
+
+TEST(InterprocessSyncFileTest, TestMultipleFilesForked) {
+ InterprocessSyncFile sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_TRUE(locker.lock());
+
+ if (!isc::util::unittests::runningOnValgrind()) {
+
+ int fds[2];
+
+ EXPECT_EQ(0, pipe(fds));
+
+ if (fork() == 0) {
+ unsigned char locked = 0xff;
+ // Child writes to pipe
+ close(fds[0]);
+
+ InterprocessSyncFile sync2("test2");
+ InterprocessSyncLocker locker2(sync2);
+
+ if (locker2.tryLock()) {
+ locked = 0;
+ }
+
+ ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
+ EXPECT_EQ(sizeof(locked), bytes_written);
+
+ close(fds[1]);
+ exit(0);
+ } else {
+ // Parent reads from pipe
+ close(fds[1]);
+
+ const unsigned char locked = parentReadState(fds[0]);
+
+ close(fds[0]);
+
+ EXPECT_EQ(0, locked);
+ }
+
+ EXPECT_EQ (0, remove(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
+ }
+
+ EXPECT_TRUE(locker.unlock());
+
+ EXPECT_EQ (0, remove(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
+}
+
+} // unnamed namespace
diff --git a/src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc b/src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc
new file mode 100644
index 0000000..4f12e37
--- /dev/null
+++ b/src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright (C) 2012-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 <log/interprocess/interprocess_sync_null.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::log::interprocess;
+
+namespace {
+
+TEST(InterprocessSyncNullTest, TestNull) {
+ InterprocessSyncNull sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ // Check if the is_locked_ flag is set correctly during lock().
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.isLocked());
+
+ // lock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+
+ // Check if the is_locked_ flag is set correctly during unlock().
+ EXPECT_TRUE(locker.isLocked());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_FALSE(locker.isLocked());
+
+ // unlock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+
+ // Check if the is_locked_ flag is set correctly during tryLock().
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.isLocked());
+
+ // tryLock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+
+ // Random order (should all return true)
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+}
+
+}
diff --git a/src/lib/log/interprocess/tests/run_unittests.cc b/src/lib/log/interprocess/tests/run_unittests.cc
new file mode 100644
index 0000000..0a12720
--- /dev/null
+++ b/src/lib/log/interprocess/tests/run_unittests.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2013-2017 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 <stdlib.h>
+
+// This file uses TEST_DATA_TOPBUILDDIR macro, which must point to a writable
+// directory. It will be used for creating a logger lockfile.
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ setenv("KEA_LOCKFILE_DIR", TEST_DATA_TOPBUILDDIR, 1);
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/log/log_dbglevels.cc b/src/lib/log/log_dbglevels.cc
new file mode 100644
index 0000000..0863d15
--- /dev/null
+++ b/src/lib/log/log_dbglevels.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2017-2021 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>
+
+namespace isc {
+namespace log {
+
+/// This is given a value of 0 as that is the level selected if debugging is
+/// enabled without giving a level.
+extern const int DBGLVL_START_SHUT = 0;
+extern const int DBGLVL_COMMAND = 10;
+extern const int DBGLVL_PKT_HANDLING = 15;
+extern const int DBGLVL_COMMAND_DATA = 20;
+
+extern const int DBGLVL_TRACE_BASIC = 40;
+extern const int DBGLVL_TRACE_BASIC_DATA = 45;
+extern const int DBGLVL_TRACE_DETAIL = 50;
+extern const int DBGLVL_TRACE_DETAIL_DATA = 55;
+
+}
+}
diff --git a/src/lib/log/log_dbglevels.h b/src/lib/log/log_dbglevels.h
new file mode 100644
index 0000000..2d449c7
--- /dev/null
+++ b/src/lib/log/log_dbglevels.h
@@ -0,0 +1,83 @@
+// Copyright (C) 2011-2021 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 LOG_DBGLVLS_H
+#define LOG_DBGLVLS_H
+
+/// \file
+///
+/// When a message is logged with DEBUG severity, the debug level associated
+/// with the message is also specified. This debug level is a number
+/// ranging from 0 to 99; the idea is that the higher the debug level, the
+/// more detailed the message.
+///
+/// If debug messages are being logged, the logging system allows them to be
+/// filtered by debug level - only messages logged with a level equal to or
+/// less than the set debug level will be output. (For example, if the
+/// filter is set to 30, only debug messages logged with levels in the range
+/// 0 to 30 will be output; messages logged with levels 31 to 99 will be
+/// suppressed.)
+///
+/// Levels of 30 or below are reserved for debug messages that are most
+/// likely to be useful for an administrator. Levels 31 to 99 are for use by
+/// someone familiar with the code. "Useful for an administrator" is,
+/// admittedly, a subjective term: it is loosely defined as messages helping
+/// someone diagnose a problem that they could solve without needing to dive
+/// into the code. So it covers things like start-up steps and configuration
+/// messages.
+///
+/// In practice, this means that levels of 30 and below are most-likely to
+/// be used by the top-level programs, and 31 and above by the various
+/// libraries.
+///
+/// This file defines a set of standard debug levels for use across all loggers.
+/// In this way users can have some expectation of what will be output when
+/// enabling debugging. Symbols are prefixed DBGLVL so as not to clash with
+/// DBG_ symbols in the various modules.
+
+namespace isc {
+namespace log {
+
+/// Process startup/shutdown debug messages. Note that these are _debug_
+/// messages, as other messages related to startup and shutdown may be output
+/// with another severity. For example, when the authoritative server starts
+/// up, the "server started" message could be output at a severity of INFO.
+/// "Server starting" and messages indicating the stages in startup should be
+/// debug messages output at this severity.
+extern const int DBGLVL_START_SHUT;
+
+/// This debug level is reserved for logging the exchange of messages/commands
+/// between processes, including configuration messages.
+extern const int DBGLVL_COMMAND;
+
+/// This debug level is reserved for logging the details of packet handling, such
+/// as dropping the packet for various reasons.
+extern const int DBGLVL_PKT_HANDLING;
+
+/// If the commands have associated data, this level is when they are printed.
+/// This includes configuration messages.
+extern const int DBGLVL_COMMAND_DATA;
+
+// The following constants are suggested values for common operations.
+// Depending on the exact nature of the code, modules may or may not use these
+// levels.
+
+/// Trace basic operations.
+extern const int DBGLVL_TRACE_BASIC;
+
+/// Trace data associated with the basic operations.
+extern const int DBGLVL_TRACE_BASIC_DATA;
+
+/// Trace detailed operations.
+extern const int DBGLVL_TRACE_DETAIL;
+
+/// Trace data associated with detailed operations.
+extern const int DBGLVL_TRACE_DETAIL_DATA;
+
+} // log namespace
+} // isc namespace
+
+#endif // LOG_DBGLVLS_H
diff --git a/src/lib/log/log_formatter.cc b/src/lib/log/log_formatter.cc
new file mode 100644
index 0000000..1ce33f4
--- /dev/null
+++ b/src/lib/log/log_formatter.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 2011-2022 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 <log/log_formatter.h>
+
+#include <cassert>
+
+#ifdef ENABLE_LOGGER_CHECKS
+#include <iostream>
+#endif
+
+using namespace std;
+using namespace boost;
+
+namespace isc {
+namespace log {
+
+void
+replacePlaceholder(std::string& message, const string& arg,
+ const unsigned placeholder) {
+ const string mark("%" + lexical_cast<string>(placeholder));
+ size_t pos(message.find(mark));
+ if (pos != string::npos) {
+ do {
+ message.replace(pos, mark.size(), arg);
+ pos = message.find(mark, pos + arg.size());
+ } while (pos != string::npos);
+ } else {
+#ifdef ENABLE_LOGGER_CHECKS
+ // We're missing the placeholder, so throw an exception
+ isc_throw(MismatchedPlaceholders, "Missing logger placeholder '" << mark << "' for value '"
+ << arg << "' in message '"
+ << message << "'");
+#else
+ // We're missing the placeholder, so add some complain
+ message.append(" @@Missing logger placeholder '" + mark + "' for value '" + arg + "'@@");
+#endif /* ENABLE_LOGGER_CHECKS */
+ }
+}
+
+void
+checkExcessPlaceholders(std::string& message,
+ unsigned int placeholder) {
+ const string mark("%" + lexical_cast<string>(placeholder));
+ const size_t pos(message.find(mark));
+ if (pos != string::npos) {
+ // Excess placeholders were found. If we enable the harsh check,
+ // abort it. Note: ideally we'd like to throw MismatchedPlaceholders,
+ // but we can't at least for now because this function is called from
+ // the Formatter's destructor.
+#ifdef ENABLE_LOGGER_CHECKS
+ // Also, make sure we print the message so we can identify which
+ // identifier has the problem.
+ cerr << "Excess logger placeholder '" << mark << "' still exists in message '" << message
+ << "'." << endl;
+ assert(false);
+#else
+ message.append(" @@Excess logger placeholder '" + mark + "' still exists@@");
+#endif /* ENABLE_LOGGER_CHECKS */
+ }
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/log_formatter.h b/src/lib/log/log_formatter.h
new file mode 100644
index 0000000..7fc67f1
--- /dev/null
+++ b/src/lib/log/log_formatter.h
@@ -0,0 +1,263 @@
+// Copyright (C) 2011-2020 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 LOG_FORMATTER_H
+#define LOG_FORMATTER_H
+
+#include <cstddef>
+#include <string>
+#include <iostream>
+
+#include <exceptions/exceptions.h>
+#include <log/logger_level.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace log {
+
+/// \brief Format Failure
+///
+/// This exception is used to wrap a bad_lexical_cast exception thrown during
+/// formatting an argument.
+
+class FormatFailure : public isc::Exception {
+public:
+ FormatFailure(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+
+/// \brief Mismatched Placeholders
+///
+/// This exception is used when the number of placeholders do not match
+/// the number of arguments passed to the formatter.
+
+class MismatchedPlaceholders : public isc::Exception {
+public:
+ MismatchedPlaceholders(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+
+///
+/// \brief Internal excess placeholder checker
+///
+/// This is used internally by the Formatter to check for excess
+/// placeholders (and fewer arguments).
+void
+checkExcessPlaceholders(std::string& message, unsigned int placeholder);
+
+///
+/// \brief The internal replacement routine
+///
+/// This is used internally by the Formatter. Replaces a placeholder
+/// in the message by replacement. If the placeholder is not found,
+/// it adds a complain at the end.
+void
+replacePlaceholder(std::string& message, const std::string& replacement,
+ const unsigned placeholder);
+
+///
+/// \brief The log message formatter
+///
+/// This class allows us to format logging messages conveniently. We
+/// call something like logger.warn(WARN_MSG).arg(15).arg(dnsMsg). This
+/// outputs some text with placeholders replaced by the arguments, if
+/// the logging verbosity is at WARN level or more.
+///
+/// To make this work, we use the Formatter. The warn (or whatever logging
+/// function) returns a Formatter object. That one holds the string to be
+/// output with the placeholders. It also remembers if there should be any
+/// output at all (eg. if the logging is enabled for this level). When there's
+/// no .arg call on the object, it is destroyed right away and we use the
+/// destructor to output the text (but only in case we should output anything).
+///
+/// If there's an .arg call, we return reference to the same object, so another
+/// .arg can be called on it. After the last .arg call is done, the object is
+/// destroyed and, again, we can produce the output.
+///
+/// Of course, if the logging is turned off, we don't bother with any replacing
+/// and just return.
+///
+/// User of logging code should not really care much about this class, only
+/// call the .arg method to generate the correct output.
+///
+/// The class is a template to allow easy testing. Also, we want everything
+/// here in the header anyway and it doesn't depend on the details of what
+/// Logger really is, so it doesn't hurt anything.
+///
+/// Also, if you are interested in the internals, you might find the copy
+/// constructor a bit strange. It deactivates the original formatter. We don't
+/// really want to support copying of the Formatter by user, but C++ needs a
+/// copy constructor when returning from the logging functions, so we need one.
+/// And if we did not deactivate the original Formatter, that one would get
+/// destroyed before any call to .arg, producing an output, and then the one
+/// the .arg calls are called on would get destroyed as well, producing output
+/// again. So, think of this behavior as soul moving from one to another.
+template<class Logger> class Formatter {
+private:
+ /// \brief The logger we will use to output the final message.
+ ///
+ /// If NULL, we are not active and should not produce anything.
+ mutable Logger* logger_;
+
+ /// \brief Message severity
+ Severity severity_;
+
+ /// \brief The messages with %1, %2... placeholders
+ boost::shared_ptr<std::string> message_;
+
+ /// \brief Which will be the next placeholder to replace
+ unsigned nextPlaceholder_;
+
+
+public:
+ /// \brief Constructor of "active" formatter
+ ///
+ /// This will create a formatter. If the arguments are set, it
+ /// will be active (will produce output). If you leave them all as NULL,
+ /// it will create an inactive Formatter -- one that'll produce no output.
+ ///
+ /// It is not expected to be called by user of logging system directly.
+ ///
+ /// \param severity The severity of the message (DEBUG, ERROR etc.)
+ /// \param message The message with placeholders. We take ownership of
+ /// it and we will modify the string. Must not be NULL unless
+ /// logger is also NULL, but it's not checked.
+ /// \param logger The logger where the final output will go, or NULL
+ /// if no output is wanted.
+ Formatter(const Severity& severity = NONE,
+ boost::shared_ptr<std::string> message = boost::make_shared<std::string>(),
+ Logger* logger = NULL) :
+ logger_(logger), severity_(severity), message_(message),
+ nextPlaceholder_(0) {
+ }
+
+ /// \brief Copy constructor
+ ///
+ /// "Control" is passed to the created object in that it is the created object
+ /// that will have responsibility for outputting the formatted message - the
+ /// object being copied relinquishes that responsibility.
+ Formatter(const Formatter& other) :
+ logger_(other.logger_), severity_(other.severity_),
+ message_(other.message_), nextPlaceholder_(other.nextPlaceholder_) {
+ other.logger_ = NULL;
+ }
+
+ /// \brief Destructor.
+ //
+ /// This is the place where output happens if the formatter is active.
+ ~Formatter() {
+ if (logger_) {
+ try {
+ checkExcessPlaceholders(*message_, ++nextPlaceholder_);
+ logger_->output(severity_, *message_);
+ } catch (...) {
+ // Catch and ignore all exceptions here.
+ }
+ }
+ }
+
+ /// \brief Assignment operator
+ ///
+ /// Essentially the same function as the assignment operator - the object being
+ /// assigned to takes responsibility for outputting the message.
+ Formatter& operator =(const Formatter& other) {
+ if (&other != this) {
+ logger_ = other.logger_;
+ severity_ = other.severity_;
+ message_ = other.message_;
+ nextPlaceholder_ = other.nextPlaceholder_;
+ other.logger_ = NULL;
+ }
+
+ return *this;
+ }
+
+ /// \brief Replaces another placeholder
+ ///
+ /// Replaces another placeholder and returns a new formatter with it.
+ /// Deactivates the current formatter. In case the formatter is not active,
+ /// only produces another inactive formatter.
+ ///
+ /// \param value The argument to place into the placeholder.
+ template<class Arg> Formatter& arg(const Arg& value) {
+ if (logger_) {
+ try {
+ return (arg(boost::lexical_cast<std::string>(value)));
+ } catch (const boost::bad_lexical_cast& ex) {
+ // The formatting of the log message got wrong, we don't want
+ // to output it.
+ deactivate();
+ // A bad_lexical_cast during a conversion to a string is
+ // *extremely* unlikely to fail. However, there is nothing
+ // in the documentation that rules it out, so we need to handle
+ // it. As it is a potentially very serious problem, throw the
+ // exception detailing the problem with as much information as
+ // we can. (Note that this does not include 'value' -
+ // boost::lexical_cast failed to convert it to a string, so an
+ // attempt to do so here would probably fail as well.)
+ isc_throw(FormatFailure, "bad_lexical_cast in call to "
+ "Formatter::arg(): " << ex.what());
+ }
+ } else {
+ return (*this);
+ }
+ }
+
+ /// \brief String version of arg.
+ ///
+ /// \param arg The text to place into the placeholder.
+ Formatter& arg(const std::string& arg) {
+ if (logger_) {
+ // Note that this method does a replacement and returns the
+ // modified string. If there are multiple invocations of arg() (e.g.
+ // logger.info(msgid).arg(xxx).arg(yyy)...), each invocation
+ // operates on the string returned by the previous one. This
+ // sequential operation means that if we had a message like "%1 %2",
+ // and called .arg("%2").arg(42), we would get "42 42"; the first
+ // call replaces the %1" with "%2" and the second replaces all
+ // occurrences of "%2" with 42. (Conversely, the sequence
+ // .arg(42).arg("%1") would return "42 %1" - there are no recursive
+ // replacements).
+ try {
+ replacePlaceholder(*message_, arg, ++nextPlaceholder_);
+ } catch (...) {
+ // Something went wrong here, the log message is broken, so
+ // we don't want to output it, nor we want to check all the
+ // placeholders were used (because they won't be).
+ deactivate();
+ throw;
+ }
+ }
+ return (*this);
+ }
+
+ /// \brief Turn off the output of this logger.
+ ///
+ /// If the logger would output anything at the end, now it won't.
+ /// Also, this turns off the strict checking of placeholders, if
+ /// it is compiled in.
+ ///
+ /// The expected use is when there was an exception processing
+ /// the arguments for the message.
+ void deactivate() {
+ if (logger_) {
+ message_.reset();
+ logger_ = NULL;
+ }
+ }
+};
+
+} // namespace log
+} // namespace isc
+
+#endif
diff --git a/src/lib/log/log_messages.cc b/src/lib/log/log_messages.cc
new file mode 100644
index 0000000..65d6bd8
--- /dev/null
+++ b/src/lib/log/log_messages.cc
@@ -0,0 +1,63 @@
+// File created from ../../../src/lib/log/log_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID LOG_BAD_DESTINATION = "LOG_BAD_DESTINATION";
+extern const isc::log::MessageID LOG_BAD_SEVERITY = "LOG_BAD_SEVERITY";
+extern const isc::log::MessageID LOG_BAD_STREAM = "LOG_BAD_STREAM";
+extern const isc::log::MessageID LOG_DUPLICATE_MESSAGE_ID = "LOG_DUPLICATE_MESSAGE_ID";
+extern const isc::log::MessageID LOG_DUPLICATE_NAMESPACE = "LOG_DUPLICATE_NAMESPACE";
+extern const isc::log::MessageID LOG_INPUT_OPEN_FAIL = "LOG_INPUT_OPEN_FAIL";
+extern const isc::log::MessageID LOG_INVALID_MESSAGE_ID = "LOG_INVALID_MESSAGE_ID";
+extern const isc::log::MessageID LOG_NAMESPACE_EXTRA_ARGS = "LOG_NAMESPACE_EXTRA_ARGS";
+extern const isc::log::MessageID LOG_NAMESPACE_INVALID_ARG = "LOG_NAMESPACE_INVALID_ARG";
+extern const isc::log::MessageID LOG_NAMESPACE_NO_ARGS = "LOG_NAMESPACE_NO_ARGS";
+extern const isc::log::MessageID LOG_NO_MESSAGE_ID = "LOG_NO_MESSAGE_ID";
+extern const isc::log::MessageID LOG_NO_MESSAGE_TEXT = "LOG_NO_MESSAGE_TEXT";
+extern const isc::log::MessageID LOG_NO_SUCH_MESSAGE = "LOG_NO_SUCH_MESSAGE";
+extern const isc::log::MessageID LOG_OPEN_OUTPUT_FAIL = "LOG_OPEN_OUTPUT_FAIL";
+extern const isc::log::MessageID LOG_PREFIX_EXTRA_ARGS = "LOG_PREFIX_EXTRA_ARGS";
+extern const isc::log::MessageID LOG_PREFIX_INVALID_ARG = "LOG_PREFIX_INVALID_ARG";
+extern const isc::log::MessageID LOG_READING_LOCAL_FILE = "LOG_READING_LOCAL_FILE";
+extern const isc::log::MessageID LOG_READ_ERROR = "LOG_READ_ERROR";
+extern const isc::log::MessageID LOG_UNRECOGNIZED_DIRECTIVE = "LOG_UNRECOGNIZED_DIRECTIVE";
+extern const isc::log::MessageID LOG_WRITE_ERROR = "LOG_WRITE_ERROR";
+
+} // namespace log
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "LOG_BAD_DESTINATION", "unrecognized log destination: %1",
+ "LOG_BAD_SEVERITY", "unrecognized log severity: %1",
+ "LOG_BAD_STREAM", "bad log console output stream: %1",
+ "LOG_DUPLICATE_MESSAGE_ID", "duplicate message ID (%1) in compiled code",
+ "LOG_DUPLICATE_NAMESPACE", "line %1: duplicate $NAMESPACE directive found",
+ "LOG_INPUT_OPEN_FAIL", "unable to open message file %1 for input: %2",
+ "LOG_INVALID_MESSAGE_ID", "line %1: invalid message identification '%2'",
+ "LOG_NAMESPACE_EXTRA_ARGS", "line %1: $NAMESPACE directive has too many arguments",
+ "LOG_NAMESPACE_INVALID_ARG", "line %1: $NAMESPACE directive has an invalid argument ('%2')",
+ "LOG_NAMESPACE_NO_ARGS", "line %1: no arguments were given to the $NAMESPACE directive",
+ "LOG_NO_MESSAGE_ID", "line %1: message definition line found without a message ID",
+ "LOG_NO_MESSAGE_TEXT", "line %1: line found containing a message ID ('%2') and no text",
+ "LOG_NO_SUCH_MESSAGE", "could not replace message text for '%1': no such message",
+ "LOG_OPEN_OUTPUT_FAIL", "unable to open %1 for output: %2",
+ "LOG_PREFIX_EXTRA_ARGS", "line %1: $PREFIX directive has too many arguments",
+ "LOG_PREFIX_INVALID_ARG", "line %1: $PREFIX directive has an invalid argument ('%2')",
+ "LOG_READING_LOCAL_FILE", "reading local message file %1",
+ "LOG_READ_ERROR", "error reading from message file %1: %2",
+ "LOG_UNRECOGNIZED_DIRECTIVE", "line %1: unrecognized directive '%2'",
+ "LOG_WRITE_ERROR", "error writing to %1: %2",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/log/log_messages.h b/src/lib/log/log_messages.h
new file mode 100644
index 0000000..d8af641
--- /dev/null
+++ b/src/lib/log/log_messages.h
@@ -0,0 +1,35 @@
+// File created from ../../../src/lib/log/log_messages.mes
+
+#ifndef LOG_MESSAGES_H
+#define LOG_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID LOG_BAD_DESTINATION;
+extern const isc::log::MessageID LOG_BAD_SEVERITY;
+extern const isc::log::MessageID LOG_BAD_STREAM;
+extern const isc::log::MessageID LOG_DUPLICATE_MESSAGE_ID;
+extern const isc::log::MessageID LOG_DUPLICATE_NAMESPACE;
+extern const isc::log::MessageID LOG_INPUT_OPEN_FAIL;
+extern const isc::log::MessageID LOG_INVALID_MESSAGE_ID;
+extern const isc::log::MessageID LOG_NAMESPACE_EXTRA_ARGS;
+extern const isc::log::MessageID LOG_NAMESPACE_INVALID_ARG;
+extern const isc::log::MessageID LOG_NAMESPACE_NO_ARGS;
+extern const isc::log::MessageID LOG_NO_MESSAGE_ID;
+extern const isc::log::MessageID LOG_NO_MESSAGE_TEXT;
+extern const isc::log::MessageID LOG_NO_SUCH_MESSAGE;
+extern const isc::log::MessageID LOG_OPEN_OUTPUT_FAIL;
+extern const isc::log::MessageID LOG_PREFIX_EXTRA_ARGS;
+extern const isc::log::MessageID LOG_PREFIX_INVALID_ARG;
+extern const isc::log::MessageID LOG_READING_LOCAL_FILE;
+extern const isc::log::MessageID LOG_READ_ERROR;
+extern const isc::log::MessageID LOG_UNRECOGNIZED_DIRECTIVE;
+extern const isc::log::MessageID LOG_WRITE_ERROR;
+
+} // namespace log
+} // namespace isc
+
+#endif // LOG_MESSAGES_H
diff --git a/src/lib/log/log_messages.mes b/src/lib/log/log_messages.mes
new file mode 100644
index 0000000..3c28d2c
--- /dev/null
+++ b/src/lib/log/log_messages.mes
@@ -0,0 +1,138 @@
+# Copyright (C) 2011-2020 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/.
+
+# \brief Message Utility Message File
+#
+# This is the source of the set of messages generated by the message and
+# logging components. The associated .h and .cc files are created by hand from
+# this file though and are not built during the build process; this is to avoid
+# the chicken-and-egg situation where we need the files to build the message
+# compiler, yet we need the compiler to build the files.
+
+$NAMESPACE isc::log
+
+% LOG_BAD_DESTINATION unrecognized log destination: %1
+A logger destination value was given that was not recognized. The
+destination should be one of "console", "file", or "syslog".
+
+% LOG_BAD_SEVERITY unrecognized log severity: %1
+A logger severity value was given that was not recognized. The severity
+should be one of "DEBUG", "INFO", "WARN", "ERROR", "FATAL" or "NONE".
+
+% LOG_BAD_STREAM bad log console output stream: %1
+Logging has been configured so that output is written to the terminal
+(console) but the stream on which it is to be written is not recognized.
+Allowed values are "stdout" and "stderr".
+
+% LOG_DUPLICATE_MESSAGE_ID duplicate message ID (%1) in compiled code
+During start-up, Kea detected that the given message identification
+had been defined multiple times in the Kea code. This indicates a
+programming error; please submit a bug report.
+
+% LOG_DUPLICATE_NAMESPACE line %1: duplicate $NAMESPACE directive found
+When reading a message file, more than one $NAMESPACE directive was found.
+(This directive is used to set a C++ namespace when generating header
+files during software development.) Such a condition is regarded as an
+error and the read will be abandoned.
+
+% LOG_INPUT_OPEN_FAIL unable to open message file %1 for input: %2
+The program was not able to open the specified input message file for
+the reason given.
+
+% LOG_INVALID_MESSAGE_ID line %1: invalid message identification '%2'
+An invalid message identification (ID) has been found during the read of
+a message file. Message IDs should comprise only alphanumeric characters
+and the underscore, and should not start with a digit.
+
+% LOG_NAMESPACE_EXTRA_ARGS line %1: $NAMESPACE directive has too many arguments
+The $NAMESPACE directive in a message file takes a single argument, a
+namespace in which all the generated symbol names are placed. This error
+is generated when the compiler finds a $NAMESPACE directive with more
+than one argument.
+
+% LOG_NAMESPACE_INVALID_ARG line %1: $NAMESPACE directive has an invalid argument ('%2')
+The $NAMESPACE argument in a message file should be a valid C++ namespace.
+This message is output if the simple check on the syntax of the string
+carried out by the reader fails.
+
+% LOG_NAMESPACE_NO_ARGS line %1: no arguments were given to the $NAMESPACE directive
+The $NAMESPACE directive in a message file takes a single argument,
+a C++ namespace in which all the generated symbol names are placed.
+This error is generated when the compiler finds a $NAMESPACE directive
+with no arguments.
+
+% LOG_NO_MESSAGE_ID line %1: message definition line found without a message ID
+Within a message file, message are defined by lines starting with a "%".
+The rest of the line should comprise the message ID and text describing
+the message. This error indicates the message compiler found a line in
+the message file comprising just the "%" and nothing else.
+
+% LOG_NO_MESSAGE_TEXT line %1: line found containing a message ID ('%2') and no text
+Within a message file, message are defined by lines starting with a "%".
+The rest of the line should comprise the message ID and text describing
+the message. This error indicates the message compiler found a line
+in the message file comprising just the "%" and message identification,
+but no text.
+
+% LOG_NO_SUCH_MESSAGE could not replace message text for '%1': no such message
+During start-up a local message file was read. A line with the listed
+message identification was found in the file, but the identification is
+not one contained in the compiled-in message dictionary. This message
+may appear a number of times in the file, once for every such unknown
+message identification.
+
+There are several reasons why this message may appear:
+
+- The message ID has been misspelled in the local message file.
+
+- The program outputting the message may not use that particular message
+(e.g. it originates in a module not used by the program).
+
+- The local file was written for an earlier version of the Kea software
+and the later version no longer generates that message.
+
+Whatever the reason, there is no impact on the operation of Kea.
+
+% LOG_OPEN_OUTPUT_FAIL unable to open %1 for output: %2
+Originating within the logging code, the program was not able to open
+the specified output file for the reason given.
+
+% LOG_PREFIX_EXTRA_ARGS line %1: $PREFIX directive has too many arguments
+Within a message file, the $PREFIX directive takes a single argument,
+a prefix to be added to the symbol names when a C++ file is created.
+This error is generated when the compiler finds a $PREFIX directive with
+more than one argument.
+
+Note: the $PREFIX directive is deprecated and will be removed in a future
+version of Kea.
+
+% LOG_PREFIX_INVALID_ARG line %1: $PREFIX directive has an invalid argument ('%2')
+Within a message file, the $PREFIX directive takes a single argument,
+a prefix to be added to the symbol names when a C++ file is created.
+As such, it must adhere to restrictions on C++ symbol names (e.g. may
+only contain alphanumeric characters or underscores, and may nor start
+with a digit). A $PREFIX directive was found with an argument (given
+in the message) that violates those restrictions.
+
+Note: the $PREFIX directive is deprecated and will be removed in a future
+version of Kea.
+
+% LOG_READING_LOCAL_FILE reading local message file %1
+This is an informational message output by Kea when it starts to read
+a local message file. (A local message file may replace the text of
+one or more messages; the ID of the message will not be changed though.)
+
+% LOG_READ_ERROR error reading from message file %1: %2
+The specified error was encountered reading from the named message file.
+
+% LOG_UNRECOGNIZED_DIRECTIVE line %1: unrecognized directive '%2'
+Within a message file, a line starting with a dollar symbol was found
+(indicating the presence of a directive) but the first word on the line
+(shown in the message) was not recognized.
+
+% LOG_WRITE_ERROR error writing to %1: %2
+The specified error was encountered by the message compiler when writing
+to the named output file.
diff --git a/src/lib/log/logger.cc b/src/lib/log/logger.cc
new file mode 100644
index 0000000..e036a09
--- /dev/null
+++ b/src/lib/log/logger.cc
@@ -0,0 +1,220 @@
+// Copyright (C) 2011-2022 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 <stdarg.h>
+#include <stdio.h>
+
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/logger_name.h>
+#include <log/logger_support.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+#include <util/strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+LoggerImpl*
+Logger::getLoggerPtr() {
+ if (!initialized_) {
+ lock_guard<mutex> lk(mutex_);
+ if (!initialized_) {
+ initLoggerImpl();
+ }
+ initialized_ = true;
+ }
+ return (loggerptr_);
+}
+
+// Initialize underlying logger, but only if logging has been initialized.
+void
+Logger::initLoggerImpl() {
+ if (isLoggingInitialized()) {
+ loggerptr_ = new LoggerImpl(name_);
+ } else {
+ isc_throw(LoggingNotInitialized, "attempt to access logging function "
+ "before logging has been initialized");
+ }
+}
+
+// Destructor.
+
+Logger::~Logger() {
+ delete loggerptr_;
+
+ // The next statement is required for the Kea hooks framework, where a
+ // statically-linked Kea loads and unloads multiple libraries. See the hooks
+ // documentation for more details.
+ loggerptr_ = 0;
+}
+
+// Get Version
+std::string
+Logger::getVersion() {
+ return (LoggerImpl::getVersion());
+}
+
+// Get Name of Logger
+
+std::string
+Logger::getName() {
+ return (getLoggerPtr()->getName());
+}
+
+// Set the severity for logging.
+
+void
+Logger::setSeverity(isc::log::Severity severity, int dbglevel) {
+ getLoggerPtr()->setSeverity(severity, dbglevel);
+}
+
+// Return the severity of the logger.
+
+isc::log::Severity
+Logger::getSeverity() {
+ return (getLoggerPtr()->getSeverity());
+}
+
+// Get Effective Severity Level for Logger
+
+isc::log::Severity
+Logger::getEffectiveSeverity() {
+ return (getLoggerPtr()->getEffectiveSeverity());
+}
+
+// Debug level (only relevant if messages of severity DEBUG are being logged).
+
+int
+Logger::getDebugLevel() {
+ return (getLoggerPtr()->getDebugLevel());
+}
+
+// Effective debug level (only relevant if messages of severity DEBUG are being
+// logged).
+
+int
+Logger::getEffectiveDebugLevel() {
+ return (getLoggerPtr()->getEffectiveDebugLevel());
+}
+
+// Check on the current severity settings
+
+bool
+Logger::isDebugEnabled(int dbglevel) {
+ return (getLoggerPtr()->isDebugEnabled(dbglevel));
+}
+
+bool
+Logger::isInfoEnabled() {
+ return (getLoggerPtr()->isInfoEnabled());
+}
+
+bool
+Logger::isWarnEnabled() {
+ return (getLoggerPtr()->isWarnEnabled());
+}
+
+bool
+Logger::isErrorEnabled() {
+ return (getLoggerPtr()->isErrorEnabled());
+}
+
+bool
+Logger::isFatalEnabled() {
+ return (getLoggerPtr()->isFatalEnabled());
+}
+
+// Format a message: looks up the message text in the dictionary and formats
+// it, replacing tokens with arguments.
+//
+// Owing to the use of variable arguments, this must be inline (hence the
+// definition of the macro). Also note that it expects that the message buffer
+// "message" is declared in the compilation unit.
+
+// Output methods
+
+void
+Logger::output(const Severity& severity, const std::string& message) {
+ getLoggerPtr()->outputRaw(severity, message);
+}
+
+Logger::Formatter
+Logger::debug(int dbglevel, const isc::log::MessageID& ident) {
+ if (isDebugEnabled(dbglevel)) {
+ return (Formatter(DEBUG, getLoggerPtr()->lookupMessage(ident),
+ this));
+ } else {
+ return (Formatter());
+ }
+}
+
+Logger::Formatter
+Logger::info(const isc::log::MessageID& ident) {
+ if (isInfoEnabled()) {
+ return (Formatter(INFO, getLoggerPtr()->lookupMessage(ident),
+ this));
+ } else {
+ return (Formatter());
+ }
+}
+
+Logger::Formatter
+Logger::warn(const isc::log::MessageID& ident) {
+ if (isWarnEnabled()) {
+ return (Formatter(WARN, getLoggerPtr()->lookupMessage(ident),
+ this));
+ } else {
+ return (Formatter());
+ }
+}
+
+Logger::Formatter
+Logger::error(const isc::log::MessageID& ident) {
+ if (isErrorEnabled()) {
+ return (Formatter(ERROR, getLoggerPtr()->lookupMessage(ident),
+ this));
+ } else {
+ return (Formatter());
+ }
+}
+
+Logger::Formatter
+Logger::fatal(const isc::log::MessageID& ident) {
+ if (isFatalEnabled()) {
+ return (Formatter(FATAL, getLoggerPtr()->lookupMessage(ident),
+ this));
+ } else {
+ return (Formatter());
+ }
+}
+
+// Replace the interprocess synchronization object
+
+void
+Logger::setInterprocessSync(isc::log::interprocess::InterprocessSync* sync) {
+ getLoggerPtr()->setInterprocessSync(sync);
+}
+
+bool
+Logger::hasAppender(OutputOption::Destination const destination) {
+ return getLoggerPtr()->hasAppender(destination);
+}
+
+// Comparison (testing only)
+
+bool
+Logger::operator==(Logger& other) {
+ return (*getLoggerPtr() == *other.getLoggerPtr());
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger.h b/src/lib/log/logger.h
new file mode 100644
index 0000000..78a82ff
--- /dev/null
+++ b/src/lib/log/logger.h
@@ -0,0 +1,369 @@
+// Copyright (C) 2011-2022 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 LOGGER_H
+#define LOGGER_H
+
+#include <atomic>
+#include <cstdlib>
+#include <cstring>
+#include <mutex>
+#include <string>
+
+#include <exceptions/exceptions.h>
+#include <log/logger_level.h>
+#include <log/message_types.h>
+#include <log/log_formatter.h>
+#include <log/output_option.h>
+
+namespace isc {
+namespace log {
+
+namespace interprocess {
+// Forward declaration to hide implementation details from normal
+// applications.
+class InterprocessSync;
+}
+
+/// \page LoggingApi Logging API
+/// \section LoggingApiOverview Overview
+/// Kea logging uses the concepts of the widely-used Java logging
+/// package log4j (https://logging.apache.org/log4j/), albeit implemented
+/// in C++ using an open-source port. Features of the system are:
+///
+/// - Within the code objects - known as loggers - can be created and
+/// used to log messages. These loggers have names; those with the
+/// same name share characteristics (such as output destination).
+/// - Loggers have a hierarchical relationship, with each logger being
+/// the child of another logger, except for the top of the hierarchy, the
+/// root logger. If a logger does not log a message, it is passed to the
+/// parent logger.
+/// - Messages can be logged at severity levels of FATAL, ERROR, WARN, INFO
+/// or DEBUG. The DEBUG level has further sub-levels numbered 0 (least
+/// informative) to 99 (most informative).
+/// - Each logger has a severity level set associated with it. When a
+/// message is logged, it is output only if it is logged at a level equal
+/// to the logger severity level or greater, e.g. if the logger's severity
+/// is WARN, only messages logged at WARN, ERROR or FATAL will be output.
+///
+/// \section LoggingApiLoggerNames Kea Logger Names
+/// Within Kea, the root logger root logger is given the name of the
+/// program (via the stand-alone function setRootLoggerName()). Other loggers
+/// are children of the root logger and are named "<program>.<sublogger>".
+/// This name appears in logging output, allowing users to identify both
+/// the Kea program and the component within the program that generated
+/// the message.
+///
+/// When creating a logger, the abbreviated name "<sublogger>" can be used;
+/// the program name will be prepended to it when the logger is created.
+/// In this way, individual libraries can have their own loggers without
+/// worrying about the program in which they are used, but:
+/// - The origin of the message will be clearly identified.
+/// - The same component can have different options (e.g. logging severity)
+/// in different programs at the same time.
+///
+/// \section LoggingApiLoggingMessages Logging Messages
+/// Instead of embedding the text of messages within the code, each message
+/// is referred to using a symbolic name. The logging code uses this name as
+/// a key in a dictionary from which the message text is obtained. Such a
+/// system allows for the optional replacement of message text at run time.
+/// More details about the message dictionary (and the compiler used to create
+/// the symbol definitions) can be found in other modules in the src/lib/log
+/// directory.
+///
+/// \section LoggingApiImplementationIssues Implementation Issues
+/// Owing to the way that the logging is implemented, notably that loggers can
+/// be declared as static external objects, there is a restriction on the
+/// length of the name of a logger component (i.e. the length of
+/// the string passed to the Logger constructor) to a maximum of 31 characters.
+/// There is no reason for this particular value other than limiting the amount
+/// of memory used. It is defined by the constant Logger::MAX_LOGGER_NAME_SIZE,
+/// and can be made larger (or smaller) if so desired.
+
+class LoggerImpl; // Forward declaration of the implementation class
+
+/// \brief Bad Interprocess Sync
+///
+/// Exception thrown if a bad InterprocessSync object (such as null) is
+/// used.
+class BadInterprocessSync : public isc::Exception {
+public:
+ BadInterprocessSync(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Logger Name Error
+///
+/// Exception thrown if a logger name is too short or too long.
+class LoggerNameError : public isc::Exception {
+public:
+ LoggerNameError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Logger Name is null
+///
+/// Exception thrown if a logger name is null
+class LoggerNameNull : public isc::Exception {
+public:
+ LoggerNameNull(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Logging Not Initialized
+///
+/// Exception thrown if an attempt is made to access a logging function
+/// if the logging system has not been initialized.
+class LoggingNotInitialized : public isc::Exception {
+public:
+ LoggingNotInitialized(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Logger Class
+///
+/// This class is the main class used for logging. Use comprises:
+///
+/// 1. Constructing a logger by instantiating it with a specific name. (If the
+/// same logger is in multiple functions within a file, overhead can be
+/// minimized by declaring it as a file-wide static variable.)
+/// 2. Using the error(), info() etc. methods to log an error. (However, it is
+/// recommended to use the LOG_ERROR, LOG_INFO etc. macros defined in macros.h.
+/// These will avoid the potentially-expensive evaluation of arguments if the
+/// severity is such that the message will be suppressed.)
+
+class Logger {
+public:
+ /// Maximum size of a logger name
+ static const size_t MAX_LOGGER_NAME_SIZE = 31;
+
+ /// \brief Constructor
+ ///
+ /// Creates/attaches to a logger of a specific name.
+ ///
+ /// \param name Name of the logger. If the name is that of the root name,
+ /// this creates an instance of the root logger; otherwise it creates a
+ /// child of the root logger.
+ ///
+ /// \note The name of the logger may be no longer than MAX_LOGGER_NAME_SIZE
+ /// else the program will throw an exception. This restriction allows
+ /// loggers to be declared statically: the name is stored in a fixed-size
+ /// array to avoid the need to allocate heap storage during program
+ /// initialization (which causes problems on some operating systems).
+ ///
+ /// \note Note also that there is no constructor taking a std::string. This
+ /// minimizes the possibility of initializing a static logger with a
+ /// string, so leading to problems mentioned above.
+ Logger(const char* name) : loggerptr_(0), initialized_(false) {
+
+ // Validate the name of the logger.
+ if (name) {
+ // Name not null, is it too short or too long?
+ size_t namelen = std::strlen(name);
+ if ((namelen == 0) || (namelen > MAX_LOGGER_NAME_SIZE)) {
+ isc_throw(LoggerNameError, "'" << name << "' is not a valid "
+ << "name for a logger: valid names must be between 1 "
+ << "and " << MAX_LOGGER_NAME_SIZE << " characters in "
+ << "length");
+ }
+ } else {
+ isc_throw(LoggerNameNull, "logger names may not be null");
+ }
+
+ // Do the copy, ensuring a trailing null in all cases.
+ std::strncpy(name_, name, MAX_LOGGER_NAME_SIZE);
+ name_[MAX_LOGGER_NAME_SIZE] = '\0';
+ }
+
+ /// \brief Destructor
+ virtual ~Logger();
+
+ /// \brief Version
+ static std::string getVersion();
+
+ /// \brief The formatter used to replace placeholders
+ typedef isc::log::Formatter<Logger> Formatter;
+
+ /// \brief Get Name of Logger
+ ///
+ /// \return The full name of the logger (including the root name)
+ virtual std::string getName();
+
+ /// \brief Set Severity Level for Logger
+ ///
+ /// Sets the level at which this logger will log messages. If none is set,
+ /// the level is inherited from the parent.
+ ///
+ /// \param severity Severity level to log
+ /// \param dbglevel If the severity is DEBUG, this is the debug level.
+ /// This can be in the range 1 to 100 and controls the verbosity. A value
+ /// outside these limits is silently coerced to the nearest boundary.
+ virtual void setSeverity(isc::log::Severity severity, int dbglevel = 1);
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// \return The current logging level of this logger. In most cases though,
+ /// the effective logging level is what is required.
+ virtual isc::log::Severity getSeverity();
+
+ /// \brief Get Effective Severity Level for Logger
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ virtual isc::log::Severity getEffectiveSeverity();
+
+ /// \brief Return DEBUG Level
+ ///
+ /// \return Current setting of debug level. This is returned regardless of
+ /// whether the severity is set to debug.
+ virtual int getDebugLevel();
+
+ /// \brief Get Effective Debug Level for Logger
+ ///
+ /// \return The effective debug level of the logger. This is the same
+ /// as getDebugLevel() if the logger has a debug level set, but otherwise
+ /// is the debug level of the parent.
+ virtual int getEffectiveDebugLevel();
+
+ /// \brief Returns if Debug Message Should Be Output
+ ///
+ /// \param dbglevel Level for which debugging is checked. Debugging is
+ /// enabled only if the logger has DEBUG enabled and if the dbglevel
+ /// checked is less than or equal to the debug level set for the logger.
+ virtual bool isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL);
+
+ /// \brief Is INFO Enabled?
+ virtual bool isInfoEnabled();
+
+ /// \brief Is WARNING Enabled?
+ virtual bool isWarnEnabled();
+
+ /// \brief Is ERROR Enabled?
+ virtual bool isErrorEnabled();
+
+ /// \brief Is FATAL Enabled?
+ virtual bool isFatalEnabled();
+
+ /// \brief Output Debug Message
+ ///
+ /// \param dbglevel Debug level, ranging between 0 and 99. Higher numbers
+ /// are used for more verbose output.
+ /// \param ident Message identification.
+ Formatter debug(int dbglevel, const MessageID& ident);
+
+ /// \brief Output Informational Message
+ ///
+ /// \param ident Message identification.
+ Formatter info(const MessageID& ident);
+
+ /// \brief Output Warning Message
+ ///
+ /// \param ident Message identification.
+ Formatter warn(const MessageID& ident);
+
+ /// \brief Output Error Message
+ ///
+ /// \param ident Message identification.
+ Formatter error(const MessageID& ident);
+
+ /// \brief Output Fatal Message
+ ///
+ /// \param ident Message identification.
+ Formatter fatal(const MessageID& ident);
+
+ /// \brief Replace the interprocess synchronization object
+ ///
+ /// If this method is called with null as the argument, it throws a
+ /// BadInterprocessSync exception.
+ ///
+ /// \note This method is intended to be used only within this log library
+ /// and its tests. Normal application shouldn't use it (in fact,
+ /// normal application shouldn't even be able to instantiate
+ /// InterprocessSync objects).
+ ///
+ /// \param sync The logger uses this synchronization object for
+ /// synchronizing output of log messages. It should be deletable and
+ /// the ownership is transferred to the logger. If null is passed,
+ /// a BadInterprocessSync exception is thrown.
+ void setInterprocessSync(isc::log::interprocess::InterprocessSync* sync);
+
+ /// @brief Check if this logger has an appender of the given type.
+ ///
+ /// @param destination the appender type to be checked: console, file or syslog
+ ///
+ /// @return true if an appender of the given type is found, false otherwise
+ bool hasAppender(OutputOption::Destination const destination);
+
+ /// \brief Equality
+ ///
+ /// Check if two instances of this logger refer to the same stream.
+ ///
+ /// \return true if the logger objects are instances of the same logger.
+ bool operator==(Logger& other);
+
+private:
+ friend class isc::log::Formatter<Logger>;
+
+ /// \brief Raw output function
+ ///
+ /// This is used by the formatter to output formatted output.
+ ///
+ /// \param severity Severity of the message being output.
+ /// \param message Text of the message to be output.
+ void output(const Severity& severity, const std::string& message);
+
+ /// \brief Copy Constructor
+ ///
+ /// Disabled (marked private) as it makes no sense to copy the logger -
+ /// just create another one of the same name.
+ Logger(const Logger&);
+
+ /// \brief Assignment Operator
+ ///
+ /// Disabled (marked private) as it makes no sense to copy the logger -
+ /// just create another one of the same name.
+ Logger& operator=(const Logger&);
+
+ /// \brief Initialize Implementation
+ ///
+ /// Returns the logger pointer. If not yet set, the implementation class is
+ /// initialized.
+ ///
+ /// The main reason for this is to allow loggers to be declared statically
+ /// before the underlying logging system is initialized. However, any
+ /// attempt to access a logging method on any logger before initialization -
+ /// regardless of whether is is statically or automatically declared - will
+ /// cause a "LoggingNotInitialized" exception to be thrown.
+ ///
+ /// \return Returns pointer to implementation
+ LoggerImpl* getLoggerPtr();
+
+ /// \brief Initialize Underlying Implementation and Set loggerptr_
+ void initLoggerImpl();
+
+ ///< Pointer to underlying logger
+ LoggerImpl* loggerptr_;
+
+ ///< Copy of the logger name
+ char name_[MAX_LOGGER_NAME_SIZE + 1];
+
+ ///< Mutex to protect the internal state
+ std::mutex mutex_;
+
+ ///< Flag which indicates if logger is initialized
+ std::atomic<bool> initialized_;
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // LOGGER_H
diff --git a/src/lib/log/logger_impl.cc b/src/lib/log/logger_impl.cc
new file mode 100644
index 0000000..f0e5298
--- /dev/null
+++ b/src/lib/log/logger_impl.cc
@@ -0,0 +1,231 @@
+// Copyright (C) 2011-2022 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 <algorithm>
+#include <cstring>
+#include <iomanip>
+#include <iostream>
+#include <stdarg.h>
+#include <stdio.h>
+#include <sstream>
+
+#include <boost/make_shared.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/static_assert.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <log4cplus/configurator.h>
+#include <log4cplus/consoleappender.h>
+#include <log4cplus/fileappender.h>
+#include <log4cplus/loggingmacros.h>
+#include <log4cplus/syslogappender.h>
+#include <log4cplus/version.h>
+
+#include <log/logger.h>
+#include <log/logger_impl.h>
+#include <log/logger_level.h>
+#include <log/logger_level_impl.h>
+#include <log/logger_name.h>
+#include <log/logger_manager.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/interprocess/interprocess_sync_file.h>
+#include <log/interprocess/interprocess_sync_null.h>
+
+#include <util/strutil.h>
+
+// Note: as log4cplus and the Kea logger have many concepts in common, and
+// thus many similar names, to disambiguate types we don't "use" the log4cplus
+// namespace: instead, all log4cplus types are explicitly qualified.
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+/// @brief detects whether file locking is enabled or disabled
+///
+/// The lockfile is enabled by default. The only way to disable it is to
+/// set KEA_LOCKFILE_DIR variable to 'none'.
+/// @return true if lockfile is enabled, false otherwise
+bool lockfileEnabled() {
+ const char* const env = getenv("KEA_LOCKFILE_DIR");
+ if (env && boost::iequals(string(env), string("none"))) {
+ return (false);
+ }
+
+ return (true);
+}
+
+// Constructor. The setting of logger_ must be done when the variable is
+// constructed (instead of being left to the body of the function); at least
+// one compiler requires that all member variables be constructed before the
+// constructor is run, but log4cplus::Logger (the type of logger_) has no
+// default constructor.
+LoggerImpl::LoggerImpl(const string& name) :
+ name_(expandLoggerName(name)),
+ logger_(log4cplus::Logger::getInstance(name_))
+{
+ if (lockfileEnabled()) {
+ sync_ = new interprocess::InterprocessSyncFile("logger");
+ } else {
+ sync_ = new interprocess::InterprocessSyncNull("logger");
+ }
+}
+
+// Destructor. (Here because of virtual declaration.)
+
+LoggerImpl::~LoggerImpl() {
+ delete sync_;
+}
+
+/// \brief Version
+std::string
+LoggerImpl::getVersion() {
+ std::ostringstream ver;
+ ver << "log4cplus ";
+ ver << log4cplus::versionStr;
+ return (ver.str());
+}
+
+// Set the severity for logging.
+void
+LoggerImpl::setSeverity(isc::log::Severity severity, int dbglevel) {
+ Level level(severity, dbglevel);
+ logger_.setLogLevel(LoggerLevelImpl::convertFromBindLevel(level));
+}
+
+// Return severity level
+isc::log::Severity
+LoggerImpl::getSeverity() {
+ Level level = LoggerLevelImpl::convertToBindLevel(logger_.getLogLevel());
+ return level.severity;
+}
+
+// Return current debug level (only valid if current severity level is DEBUG).
+int
+LoggerImpl::getDebugLevel() {
+ Level level = LoggerLevelImpl::convertToBindLevel(logger_.getLogLevel());
+ return level.dbglevel;
+}
+
+// Get effective severity. Either the current severity or, if not set, the
+// severity of the root level.
+isc::log::Severity
+LoggerImpl::getEffectiveSeverity() {
+ Level level = LoggerLevelImpl::convertToBindLevel(logger_.getChainedLogLevel());
+ return level.severity;
+}
+
+// Return effective debug level (only valid if current effective severity level
+// is DEBUG).
+int
+LoggerImpl::getEffectiveDebugLevel() {
+ Level level = LoggerLevelImpl::convertToBindLevel(logger_.getChainedLogLevel());
+ return level.dbglevel;
+}
+
+
+// Output a general message
+boost::shared_ptr<string>
+LoggerImpl::lookupMessage(const MessageID& ident) {
+ return (boost::make_shared<string>(string(ident) + " " +
+ MessageDictionary::globalDictionary()->getText(ident)));
+}
+
+// Replace the interprocess synchronization object
+
+void
+LoggerImpl::setInterprocessSync(interprocess::InterprocessSync* sync) {
+ if (sync == NULL) {
+ isc_throw(BadInterprocessSync,
+ "NULL was passed to setInterprocessSync()");
+ }
+
+ delete sync_;
+ sync_ = sync;
+}
+
+void
+LoggerImpl::outputRaw(const Severity& severity, const string& message) {
+ // Use a mutex locker for mutual exclusion from other threads in
+ // this process.
+ std::lock_guard<std::mutex> mutex_locker(LoggerManager::getMutex());
+
+ // Use an interprocess sync locker for mutual exclusion from other
+ // processes to avoid log messages getting interspersed.
+ interprocess::InterprocessSyncLocker locker(*sync_);
+
+ if (!locker.lock()) {
+ LOG4CPLUS_ERROR(logger_, "Unable to lock logger lockfile");
+ }
+
+ switch (severity) {
+ case DEBUG:
+ LOG4CPLUS_DEBUG(logger_, message);
+ break;
+
+ case INFO:
+ LOG4CPLUS_INFO(logger_, message);
+ break;
+
+ case WARN:
+ LOG4CPLUS_WARN(logger_, message);
+ break;
+
+ case ERROR:
+ LOG4CPLUS_ERROR(logger_, message);
+ break;
+
+ case FATAL:
+ LOG4CPLUS_FATAL(logger_, message);
+ break;
+
+ case NONE:
+ break;
+
+ default:
+ LOG4CPLUS_ERROR(logger_,
+ "Unsupported severity in LoggerImpl::outputRaw(): "
+ << severity);
+ }
+
+ if (!locker.unlock()) {
+ LOG4CPLUS_ERROR(logger_, "Unable to unlock logger lockfile");
+ }
+}
+
+bool
+LoggerImpl::hasAppender(OutputOption::Destination const destination) {
+ // Get the appender for the name under which this logger is registered.
+ log4cplus::SharedAppenderPtrList appenders(
+ log4cplus::Logger::getInstance(name_).getAllAppenders());
+
+ // If there are no appenders, they might be under the root name.
+ if (appenders.size() == 0) {
+ appenders = log4cplus::Logger::getInstance(getRootLoggerName()).getAllAppenders();
+ }
+
+ for (log4cplus::helpers::SharedObjectPtr<log4cplus::Appender> logger : appenders) {
+ if (destination == OutputOption::DEST_CONSOLE &&
+ dynamic_cast<log4cplus::ConsoleAppender*>(logger.get())) {
+ return true;
+ } else if (destination == OutputOption::DEST_FILE &&
+ dynamic_cast<log4cplus::FileAppender*>(logger.get())) {
+ return true;
+ } else if (destination == OutputOption::DEST_SYSLOG &&
+ dynamic_cast<log4cplus::SysLogAppender*>(logger.get())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_impl.h b/src/lib/log/logger_impl.h
new file mode 100644
index 0000000..d7dad45
--- /dev/null
+++ b/src/lib/log/logger_impl.h
@@ -0,0 +1,208 @@
+// Copyright (C) 2011-2022 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 LOGGER_IMPL_H
+#define LOGGER_IMPL_H
+
+#include <stdarg.h>
+#include <time.h>
+
+#include <iostream>
+#include <cstdlib>
+#include <string>
+#include <map>
+#include <utility>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+// log4cplus logger header file
+#include <log4cplus/logger.h>
+
+// Kea logger files
+#include <log/logger_level_impl.h>
+#include <log/message_types.h>
+#include <log/interprocess/interprocess_sync.h>
+#include <log/output_option.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Console Logger Implementation
+///
+/// The logger uses a "pimpl" idiom for implementation, where the base logger
+/// class contains little more than a pointer to the implementation class, and
+/// all actions are carried out by the latter.
+///
+/// This particular implementation is based on log4cplus (from sourceforge:
+/// http://log4cplus.sourceforge.net). Particular items of note:
+///
+/// a) Kea loggers have names of the form "program.sublogger". In other
+/// words, each of the loggers is a sub-logger of the main program logger.
+/// In log4cplus, there is a root logger (called "root" according to the
+/// documentation, but actually unnamed) and all loggers created are subloggers
+/// if it.
+///
+/// In this implementation, the log4cplus root logger is unused. Instead, the
+/// Kea root logger is created as a child of the log4cplus root logger,
+/// and all other loggers used in the program are created as sub-loggers of
+/// that. In this way, the logging system can just include the name of the
+/// logger in each message without the need to specially consider if the
+/// message is the root logger or not.
+///
+/// b) The idea of debug levels is implemented. See logger_level.h and
+/// logger_level_impl.h for more details on this.
+
+class LoggerImpl : public boost::noncopyable {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Creates a logger of the specific name.
+ ///
+ /// \param name Name of the logger.
+ LoggerImpl(const std::string& name);
+
+
+ /// \brief Destructor
+ virtual ~LoggerImpl();
+
+
+ /// \brief Version
+ static std::string getVersion();
+
+
+ /// \brief Get the full name of the logger (including the root name)
+ virtual std::string getName() {
+ return (name_);
+ }
+
+
+ /// \brief Set Severity Level for Logger
+ ///
+ /// Sets the level at which this logger will log messages. If none is set,
+ /// the level is inherited from the parent.
+ ///
+ /// \param severity Severity level to log
+ /// \param dbglevel If the severity is DEBUG, this is the debug level.
+ /// This can be in the range 0 to 99 and controls the verbosity. A value
+ /// outside these limits is silently coerced to the nearest boundary.
+ virtual void setSeverity(Severity severity, int dbglevel = 1);
+
+
+ /// \brief Get Severity Level for Logger
+ ///
+ /// \return The current logging level of this logger. In most cases though,
+ /// the effective logging level is what is required.
+ virtual Severity getSeverity();
+
+
+ /// \brief Get Effective Severity Level for Logger
+ ///
+ /// \return The effective severity level of the logger. This is the same
+ /// as getSeverity() if the logger has a severity level set, but otherwise
+ /// is the severity of the parent.
+ virtual Severity getEffectiveSeverity();
+
+
+ /// \brief Return debug level
+ ///
+ /// \return Current setting of debug level. This will be zero if the
+ /// the current severity level is not DEBUG.
+ virtual int getDebugLevel();
+
+
+ /// \brief Return effective debug level
+ ///
+ /// \return Current setting of effective debug level. This will be zero if
+ /// the current effective severity level is not DEBUG.
+ virtual int getEffectiveDebugLevel();
+
+
+ /// \brief Returns if Debug Message Should Be Output
+ ///
+ /// \param dbglevel Level for which debugging is checked. Debugging is
+ /// enabled only if the logger has DEBUG enabled and if the dbglevel
+ /// checked is less than or equal to the debug level set for the logger.
+ virtual bool isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL) {
+ Level level(DEBUG, dbglevel);
+ return logger_.isEnabledFor(LoggerLevelImpl::convertFromBindLevel(level));
+ }
+
+ /// \brief Is INFO Enabled?
+ virtual bool isInfoEnabled() {
+ return (logger_.isEnabledFor(log4cplus::INFO_LOG_LEVEL));
+ }
+
+ /// \brief Is WARNING Enabled?
+ virtual bool isWarnEnabled() {
+ return (logger_.isEnabledFor(log4cplus::WARN_LOG_LEVEL));
+ }
+
+ /// \brief Is ERROR Enabled?
+ virtual bool isErrorEnabled() {
+ return (logger_.isEnabledFor(log4cplus::ERROR_LOG_LEVEL));
+ }
+
+ /// \brief Is FATAL Enabled?
+ virtual bool isFatalEnabled() {
+ return (logger_.isEnabledFor(log4cplus::FATAL_LOG_LEVEL));
+ }
+
+ /// \brief Raw output
+ ///
+ /// Writes the message with time into the log. Used by the Formatter
+ /// to produce output.
+ ///
+ /// \param severity Severity of the message. (This controls the prefix
+ /// label output with the message text.)
+ /// \param message Text of the message.
+ void outputRaw(const Severity& severity, const std::string& message);
+
+ /// \brief Look up message text in dictionary
+ ///
+ /// This gets you the unformatted text of message for given ID.
+ boost::shared_ptr<std::string> lookupMessage(const MessageID& id);
+
+ /// \brief Replace the interprocess synchronization object
+ ///
+ /// If this method is called with NULL as the argument, it throws a
+ /// BadInterprocessSync exception.
+ ///
+ /// \param sync The logger uses this synchronization object for
+ /// synchronizing output of log messages. It should be deletable and
+ /// the ownership is transferred to the logger implementation.
+ /// If NULL is passed, a BadInterprocessSync exception is thrown.
+ void setInterprocessSync(isc::log::interprocess::InterprocessSync* sync);
+
+ /// @brief Check if this logger has an appender of the given type.
+ ///
+ /// @param destination the appender type to be checked: console, file or syslog
+ ///
+ /// @return true if an appender of the given type is found, false otherwise
+ bool hasAppender(OutputOption::Destination const destination);
+
+ /// \brief Equality
+ ///
+ /// Check if two instances of this logger refer to the same stream.
+ /// (This method is principally for testing.)
+ ///
+ /// \return true if the logger objects are instances of the same logger.
+ bool operator==(const LoggerImpl& other) const {
+ return (name_ == other.name_);
+ }
+
+private:
+ std::string name_; ///< Full name of this logger
+ log4cplus::Logger logger_; ///< Underlying log4cplus logger
+ isc::log::interprocess::InterprocessSync* sync_;
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // LOGGER_IMPL_H
diff --git a/src/lib/log/logger_level.cc b/src/lib/log/logger_level.cc
new file mode 100644
index 0000000..195d600
--- /dev/null
+++ b/src/lib/log/logger_level.cc
@@ -0,0 +1,42 @@
+// 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 <log/logger_level.h>
+#include <log/macros.h>
+#include <log/log_messages.h>
+
+#include <boost/algorithm/string.hpp>
+
+
+namespace isc {
+namespace log {
+
+isc::log::Severity
+getSeverity(const std::string& sev_str) {
+ if (boost::iequals(sev_str, "DEBUG")) {
+ return isc::log::DEBUG;
+ } else if (boost::iequals(sev_str, "INFO")) {
+ return isc::log::INFO;
+ } else if (boost::iequals(sev_str, "WARN")) {
+ return isc::log::WARN;
+ } else if (boost::iequals(sev_str, "ERROR")) {
+ return isc::log::ERROR;
+ } else if (boost::iequals(sev_str, "FATAL")) {
+ return isc::log::FATAL;
+ } else if (boost::iequals(sev_str, "NONE")) {
+ return isc::log::NONE;
+ } else {
+ Logger logger("log");
+ LOG_ERROR(logger, LOG_BAD_SEVERITY).arg(sev_str);
+ return isc::log::INFO;
+ }
+}
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_level.h b/src/lib/log/logger_level.h
new file mode 100644
index 0000000..7732868
--- /dev/null
+++ b/src/lib/log/logger_level.h
@@ -0,0 +1,68 @@
+// 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/.
+
+#ifndef LOGGER_LEVEL_H
+#define LOGGER_LEVEL_H
+
+#include <string>
+
+namespace isc {
+namespace log {
+
+/// \brief Severity Levels
+///
+/// Defines the severity levels for logging. This is shared between the logger
+/// and the implementations classes.
+///
+/// N.B. The order of the levels - DEBUG less than INFO less that WARN etc. is
+/// implicitly assumed in several implementations. They must not be changed.
+
+typedef enum {
+ DEFAULT = 0, // Default to logging level of the parent
+ DEBUG = 1,
+ INFO = 2,
+ WARN = 3,
+ ERROR = 4,
+ FATAL = 5,
+ NONE = 6 // Disable logging
+} Severity;
+
+/// Minimum/maximum debug levels.
+
+const int MIN_DEBUG_LEVEL = 0;
+const int MAX_DEBUG_LEVEL = 99;
+
+/// \brief Log level structure
+///
+/// A simple pair structure that provides suitable names for the members. It
+/// holds a combination of logging severity and debug level.
+struct Level {
+ Severity severity; ///< Logging severity
+ int dbglevel; ///< Debug level
+
+ Level(Severity sev = DEFAULT, int dbg = MIN_DEBUG_LEVEL) :
+ severity(sev), dbglevel(dbg)
+ {}
+
+ // Default assignment and copy constructor is appropriate
+};
+
+/// \brief Returns the isc::log::Severity value represented by the given string
+///
+/// This must be one of the strings "DEBUG", "INFO", "WARN", "ERROR", "FATAL" or
+/// "NONE". (Case is not important, but the string most not contain leading or
+/// trailing spaces.)
+///
+/// \param sev_str The string representing severity value
+///
+/// \return The severity. If the string is not recognized, an error will be
+/// logged and the string will return isc::log::INFO.
+isc::log::Severity getSeverity(const std::string& sev_str);
+
+} // namespace log
+} // namespace isc
+
+#endif // LOGGER_LEVEL_H
diff --git a/src/lib/log/logger_level_impl.cc b/src/lib/log/logger_level_impl.cc
new file mode 100644
index 0000000..a4aba73
--- /dev/null
+++ b/src/lib/log/logger_level_impl.cc
@@ -0,0 +1,215 @@
+// Copyright (C) 2011-2022 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 <algorithm>
+#include <string.h>
+#include <iostream>
+#include <boost/lexical_cast.hpp>
+
+#include <log4cplus/logger.h>
+
+#include <log/logger_level.h>
+#include <log/logger_level_impl.h>
+#include <log/logimpl_messages.h>
+#include <log/macros.h>
+
+using namespace log4cplus;
+using namespace std;
+
+namespace {
+isc::log::Logger logger("log");
+}
+
+namespace isc {
+namespace log {
+
+// Convert Kea level to a log4cplus logging level.
+log4cplus::LogLevel
+LoggerLevelImpl::convertFromBindLevel(const Level& level) {
+
+ // Kea logging levels are small integers so we can do a table lookup
+ static const log4cplus::LogLevel log4cplus_levels[] = {
+ log4cplus::NOT_SET_LOG_LEVEL,
+ log4cplus::DEBUG_LOG_LEVEL,
+ log4cplus::INFO_LOG_LEVEL,
+ log4cplus::WARN_LOG_LEVEL,
+ log4cplus::ERROR_LOG_LEVEL,
+ log4cplus::FATAL_LOG_LEVEL,
+ log4cplus::OFF_LOG_LEVEL
+ };
+
+ // ... with compile-time checks to ensure that table indexes are correct.
+ BOOST_STATIC_ASSERT(static_cast<int>(DEFAULT) == 0);
+ BOOST_STATIC_ASSERT(static_cast<int>(DEBUG) == 1);
+ BOOST_STATIC_ASSERT(static_cast<int>(INFO) == 2);
+ BOOST_STATIC_ASSERT(static_cast<int>(WARN) == 3);
+ BOOST_STATIC_ASSERT(static_cast<int>(ERROR) == 4);
+ BOOST_STATIC_ASSERT(static_cast<int>(FATAL) == 5);
+ BOOST_STATIC_ASSERT(static_cast<int>(NONE) == 6);
+
+ // Do the conversion
+ if (level.severity == DEBUG) {
+
+ // Debug severity, so the log4cplus level returned depends on the
+ // debug level. Silently limit the debug level to the range
+ // MIN_DEBUG_LEVEL to MAX_DEBUG_LEVEL (avoids the hassle of throwing
+ // and catching exceptions and besides, this is for debug information).
+ int limited = std::max(MIN_DEBUG_LEVEL,
+ std::min(level.dbglevel, MAX_DEBUG_LEVEL));
+ LogLevel newlevel = static_cast<int>(DEBUG_LOG_LEVEL -
+ (limited - MIN_DEBUG_LEVEL));
+ return (static_cast<log4cplus::LogLevel>(newlevel));
+
+ } else {
+
+ // Can do a table lookup to speed things up. There is no need to check
+ // that the index is out of range. That the variable is of type
+ // isc::log::Severity ensures that it must be one of the Severity enum
+ // members - conversion of a numeric value to an enum is not permitted.
+ return (log4cplus_levels[level.severity]);
+ }
+}
+
+// Convert log4cplus logging level to Kea debug level. It is up to the
+// caller to validate that the debug level is valid.
+Level
+LoggerLevelImpl::convertToBindLevel(const log4cplus::LogLevel loglevel) {
+
+ // Not easy to do a table lookup as the numerical values of log4cplus levels
+ // are quite high.
+ if (loglevel <= log4cplus::NOT_SET_LOG_LEVEL) {
+ return (Level(DEFAULT));
+
+ } else if (loglevel <= log4cplus::DEBUG_LOG_LEVEL) {
+
+ // Debug severity, so extract the debug level from the numeric value.
+ // If outside the limits, change the severity to the level above or
+ // below.
+ int dbglevel = MIN_DEBUG_LEVEL +
+ static_cast<int>(log4cplus::DEBUG_LOG_LEVEL) -
+ static_cast<int>(loglevel);
+ if (dbglevel > MAX_DEBUG_LEVEL) {
+ return (Level(DEFAULT));
+ } else if (dbglevel < MIN_DEBUG_LEVEL) {
+ return (Level(INFO));
+ }
+ return (Level(DEBUG, dbglevel));
+
+ } else if (loglevel <= log4cplus::INFO_LOG_LEVEL) {
+ return (Level(INFO));
+
+ } else if (loglevel <= log4cplus::WARN_LOG_LEVEL) {
+ return (Level(WARN));
+
+ } else if (loglevel <= log4cplus::ERROR_LOG_LEVEL) {
+ return (Level(ERROR));
+
+ } else if (loglevel <= log4cplus::FATAL_LOG_LEVEL) {
+ return (Level(FATAL));
+
+ }
+
+ return (Level(NONE));
+}
+
+
+// Convert string to appropriate logging level
+log4cplus::LogLevel
+LoggerLevelImpl::logLevelFromString(const log4cplus::tstring& level) {
+
+ std::string name = level; // Get to known type
+ size_t length = name.size(); // Length of the string
+
+ if (length < 5) {
+
+ // String can't possibly start DEBUG so we don't know what it is.
+ // As per documentation, return NOT_SET level.
+ return (NOT_SET_LOG_LEVEL);
+ } else {
+ if (strncasecmp(name.c_str(), "DEBUG", 5) == 0) {
+
+ // String starts "DEBUG" (or "debug" or any case mixture). The
+ // rest of the string - if any - should be a number.
+ if (length == 5) {
+
+ // It is plain "DEBUG". Take this as level 0.
+ return (DEBUG_LOG_LEVEL);
+ } else {
+
+ // Try converting the remainder to an integer. The "5" is
+ // the length of the string "DEBUG". Note that if the number
+ // is outside the range of debug levels, it is coerced to the
+ // nearest limit. Thus a level of DEBUG509 will end up as
+ // if DEBUG99 has been specified.
+ try {
+ int dbglevel = boost::lexical_cast<int>(name.substr(5));
+ if (dbglevel < MIN_DEBUG_LEVEL) {
+ LOG_WARN(logger, LOGIMPL_BELOW_MIN_DEBUG).arg(dbglevel)
+ .arg(MIN_DEBUG_LEVEL);
+ dbglevel = MIN_DEBUG_LEVEL;
+
+ } else if (dbglevel > MAX_DEBUG_LEVEL) {
+ LOG_WARN(logger, LOGIMPL_ABOVE_MAX_DEBUG).arg(dbglevel)
+ .arg(MAX_DEBUG_LEVEL);
+ dbglevel = MAX_DEBUG_LEVEL;
+
+ }
+ return convertFromBindLevel(Level(DEBUG, dbglevel));
+ }
+ catch (const boost::bad_lexical_cast&) {
+ LOG_ERROR(logger, LOGIMPL_BAD_DEBUG_STRING).arg(name);
+ return (NOT_SET_LOG_LEVEL);
+ }
+ }
+ } else {
+
+ // Unknown string - return default. Log4cplus will call any other
+ // registered conversion functions to interpret it.
+ return (NOT_SET_LOG_LEVEL);
+ }
+ }
+}
+
+// Convert logging level to string. If the level is a valid debug level,
+// return the string DEBUG, else return the empty string.
+#if LOG4CPLUS_VERSION < LOG4CPLUS_MAKE_VERSION(2, 0, 0)
+LoggerLevelImpl::LogLevelString
+#else
+const LoggerLevelImpl::LogLevelString&
+#endif
+LoggerLevelImpl::logLevelToString(log4cplus::LogLevel level) {
+ Level bindlevel = convertToBindLevel(level);
+ Severity& severity = bindlevel.severity;
+ int& dbglevel = bindlevel.dbglevel;
+ static LoggerLevelImpl::LogLevelString debug_ = tstring("DEBUG");
+ static LoggerLevelImpl::LogLevelString empty_ = tstring();
+
+ if ((severity == DEBUG) &&
+ ((dbglevel >= MIN_DEBUG_LEVEL) && (dbglevel <= MAX_DEBUG_LEVEL))) {
+ return (debug_);
+ }
+
+ // Unknown, so return empty string for log4cplus to try other conversion
+ // functions.
+ return (empty_);
+}
+
+// Initialization. Register the conversion functions with the LogLevelManager.
+void
+LoggerLevelImpl::init() {
+
+ // Get the singleton log-level manager.
+ LogLevelManager& manager = getLogLevelManager();
+
+ // Register the conversion functions
+ manager.pushFromStringMethod(LoggerLevelImpl::logLevelFromString);
+ manager.pushToStringMethod(LoggerLevelImpl::logLevelToString);
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_level_impl.h b/src/lib/log/logger_level_impl.h
new file mode 100644
index 0000000..77da5f7
--- /dev/null
+++ b/src/lib/log/logger_level_impl.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2011-2017 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 LOGGER_LEVEL_IMPL_H
+#define LOGGER_LEVEL_IMPL_H
+
+#include <log4cplus/logger.h>
+#include <log4cplus/version.h>
+#include <log/logger_level.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Implementation aspects of logging levels
+///
+/// This extends the log4cplus level set to allow 100 debug levels.
+///
+/// First some terminology, as the use of the term "level" gets confusing. The
+/// code and comments here use the term "level" in two contexts:
+///
+/// Logging level: The category of messages to log. By default log4cplus
+/// defines the following logging levels: OFF_LOG_LEVEL, FATAL_LOG_LEVEL,
+/// ERROR_LOG_LEVEL, WARN_LOG_LEVEL, INFO_LOG_LEVEL, DEBUG_LOG_LEVEL,
+/// TRACE_LOG_LEVEL, ALL_LOG_LEVEL (which here will be abbreviated OFF, FATAL
+/// etc.). Within the context of Kea, OFF, TRACE and ALL are not used
+/// and the idea of DEBUG has been extended, as will be seen below. In
+/// Kea terms, this is known as "severity"; the "logging level" usage will
+/// usually be used when talking about log4cplus aspects of the idea (as
+/// log4cplus uses that terminology).
+///
+/// Debug level: This is a number that ranges from 0 to 99 and is used by the
+/// application to control the detail of debug output. A value of 0 gives the
+/// highest-level debug output; a value of 99 gives the most verbose and most
+/// detailed. Debug messages (or whatever debug level) are only ever output
+/// when the logging level is set to DEBUG. (Note that the numbers 0 and 99
+/// are not hard-coded - they are the constants MIN_DEBUG_LEVEL and
+/// MAX_DEBUG_LEVEL.)
+///
+/// With log4cplus, the various logging levels have a numeric value associated
+/// with them, such that FATAL > ERROR > WARNING etc. This suggests that the
+/// idea of debug levels can be incorporated into the existing logging level
+/// scheme by assigning them appropriate numeric values, i.e.
+///
+/// WARNING > INFO > DEBUG > DEBUG - 1 > DEBUG - 2 > ... > DEBUG - 99
+///
+/// Setting a numeric level of DEBUG enables the basic messages; setting higher
+/// debug levels (corresponding to lower numeric logging levels) will enable
+/// progressively more messages. The lowest debug level (0) is chosen such that
+/// it corresponds to the default level of DEBUG.
+///
+/// This class comprises nothing more than static methods to aid the conversion
+/// of logging levels between log4cplus and Kea, and to register those
+/// levels with log4cplus.
+
+class LoggerLevelImpl {
+public:
+
+typedef log4cplus::tstring LogLevelString;
+
+ /// \brief Convert Kea level to log4cplus logging level
+ ///
+ /// Converts the Kea severity level into a log4cplus logging level.
+ /// If the severity is DEBUG, the current Kea debug level is taken
+ /// into account when doing the conversion.
+ ///
+ /// \param level Kea severity and debug level
+ ///
+ /// \return Equivalent log4cplus logging level.
+ static
+ log4cplus::LogLevel convertFromBindLevel(const isc::log::Level& level);
+
+ /// \brief Convert log4cplus logging level to Kea logging level
+ ///
+ /// Converts the log4cplus log level into a Kea severity level.
+ /// The log4cplus log level may be non-standard in which case it is
+ /// encoding a Kea debug level as well.
+ ///
+ /// \param loglevel log4cplus log level
+ ///
+ /// \return Equivalent Kea severity and debug level
+ static
+ isc::log::Level convertToBindLevel(const log4cplus::LogLevel loglevel);
+
+ /// \brief Convert string to log4cplus logging level
+ ///
+ /// Kea extends the set of logging levels in log4cplus with a group
+ /// of debug levels. These are given names DEBUG0 through DEBUG99 (with
+ /// DEBUG0 being equivalent to DEBUG, the standard log level. If the name
+ /// is DEBUGn but n lies outside the range of debug levels, debug level
+ /// specifies is coerced to the nearest valid value. If the string is just
+ /// not recognized, a NOT_SET_LOG_LEVEL is returned.
+ ///
+ /// \param level String representing the logging level.
+ ///
+ /// \return Corresponding log4cplus log level
+ static
+ log4cplus::LogLevel logLevelFromString(const log4cplus::tstring& level);
+
+ /// \brief Convert log level to string
+ ///
+ /// If the log level is one of the extended debug levels, the string DEBUG
+ /// is returned, otherwise the empty string.
+ ///
+ /// \param level Extended logging level
+ ///
+ /// \return Equivalent string.
+#if LOG4CPLUS_VERSION < LOG4CPLUS_MAKE_VERSION(2, 0, 0)
+ static LogLevelString logLevelToString(log4cplus::LogLevel level);
+#else
+ static const LogLevelString& logLevelToString(log4cplus::LogLevel level);
+#endif
+
+ /// \brief Initialize extended logging levels
+ ///
+ /// This must be called once, after log4cplus has been initialized. It
+ /// registers the level/string converter functions.
+ static void init();
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // LOGGER_LEVEL_IMPL_H
diff --git a/src/lib/log/logger_manager.cc b/src/lib/log/logger_manager.cc
new file mode 100644
index 0000000..1890706
--- /dev/null
+++ b/src/lib/log/logger_manager.cc
@@ -0,0 +1,216 @@
+// Copyright (C) 2011-2021 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 <algorithm>
+#include <vector>
+
+#include <log/logger.h>
+#include <log/logger_manager.h>
+#include <log/logger_manager_impl.h>
+#include <log/logger_name.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include <log/macros.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_initializer.h>
+#include <log/message_reader.h>
+#include <log/message_types.h>
+#include <log/interprocess/interprocess_sync_null.h>
+
+using namespace std;
+
+// Older log4cplus versions (1.2.0) don't have the initializer.h header that
+// would allow explicit initialization. Newer versions (2.0.4 for sure, possibly
+// older as well) have it and it's recommended to use it. We detect whether
+// it's present or not and do explicit initialization if possible.
+#ifdef LOG4CPLUS_INITIALIZER_H
+#include <log4cplus/initializer.h>
+namespace {
+log4cplus::Initializer initializer;
+}
+#endif
+
+namespace {
+
+// Logger used for logging messages within the logging code itself.
+isc::log::Logger logger("log");
+
+// Static stores for the initialization severity and debug level.
+// These are put in methods to avoid a "static initialization fiasco".
+
+isc::log::Severity& initSeverity() {
+ static isc::log::Severity severity = isc::log::INFO;
+ return (severity);
+}
+
+int& initDebugLevel() {
+ static int dbglevel = 0;
+ return (dbglevel);
+}
+
+std::string& initRootName() {
+ static std::string root(isc::log::getDefaultRootLoggerName());
+ return (root);
+}
+
+} // Anonymous namespace
+
+
+namespace isc {
+namespace log {
+
+// Constructor - create the implementation class.
+LoggerManager::LoggerManager() {
+ impl_ = new LoggerManagerImpl();
+}
+
+// Destructor - get rid of the implementation class
+LoggerManager::~LoggerManager() {
+ delete impl_;
+}
+
+// Initialize processing
+void
+LoggerManager::processInit() {
+ impl_->processInit();
+}
+
+// Process logging specification
+void
+LoggerManager::processSpecification(const LoggerSpecification& spec) {
+ impl_->processSpecification(spec);
+}
+
+// End Processing
+void
+LoggerManager::processEnd() {
+ impl_->processEnd();
+}
+
+
+/// Logging system initialization
+
+void
+LoggerManager::init(const std::string& root, isc::log::Severity severity,
+ int dbglevel, const char* file, bool buffer)
+{
+ // Load in the messages declared in the program and registered by
+ // statically-declared MessageInitializer objects.
+ MessageInitializer::loadDictionary();
+
+ // Save name, severity and debug level for later. No need to save the
+ // file name as once the local message file is read the messages will
+ // not be lost.
+ initRootName() = root;
+ initSeverity() = severity;
+ initDebugLevel() = dbglevel;
+
+ // Create the Kea root logger and set the default severity and
+ // debug level. This is the logger that has the name of the application.
+ // All other loggers created in this application will be its children.
+ setRootLoggerName(root);
+
+ // Initialize the implementation logging. After this point, some basic
+ // logging has been set up and messages can be logged.
+ // However, they will not appear until a logging specification has been
+ // processed (or the program exits), see TODO
+ LoggerManagerImpl::init(severity, dbglevel, buffer);
+ setLoggingInitialized();
+
+ // Check if there were any duplicate message IDs in the default dictionary
+ // and if so, log them. Log using the logging facility logger.
+ logDuplicatedMessages();
+
+ // Replace any messages with local ones (if given)
+ if (file) {
+ readLocalMessageFile(file);
+ }
+
+ // Ensure that the mutex is constructed and ready at this point.
+ (void) getMutex();
+}
+
+void
+LoggerManager::logDuplicatedMessages() {
+ const list<string>& duplicates = MessageInitializer::getDuplicates();
+ if (!duplicates.empty()) {
+
+ // There are duplicates present. This list itself may contain
+ // duplicates; if so, the message ID is listed as many times as
+ // there are duplicates.
+ for (list<string>::const_iterator i = duplicates.begin();
+ i != duplicates.end(); ++i) {
+ LOG_WARN(logger, LOG_DUPLICATE_MESSAGE_ID).arg(*i);
+ }
+ MessageInitializer::clearDuplicates();
+ }
+}
+
+
+// Read local message file
+// TODO This should be done after the configuration has been read so that
+// the file can be placed in the local configuration
+void
+LoggerManager::readLocalMessageFile(const char* file) {
+
+ const MessageDictionaryPtr& dictionary = MessageDictionary::globalDictionary();
+ MessageReader reader(dictionary.get());
+
+ // Turn off use of any lock files. This is because this logger can
+ // be used by standalone programs which may not have write access to
+ // the local state directory (to create lock files). So we switch to
+ // using a null interprocess sync object here.
+ logger.setInterprocessSync(
+ new isc::log::interprocess::InterprocessSyncNull("logger"));
+
+ try {
+
+ logger.info(LOG_READING_LOCAL_FILE).arg(file);
+ reader.readFile(file, MessageReader::REPLACE);
+
+ // File successfully read. As each message in the file is supposed to
+ // replace one in the dictionary, any ID read that can't be located in
+ // the dictionary will not be used. To aid problem diagnosis, the
+ // unknown message IDs are listed.
+ MessageReader::MessageIDCollection unknown = reader.getNotAdded();
+ for (MessageReader::MessageIDCollection::const_iterator
+ i = unknown.begin(); i != unknown.end(); ++i) {
+ string message_id = boost::lexical_cast<string>(*i);
+ logger.warn(LOG_NO_SUCH_MESSAGE).arg(message_id);
+ }
+ }
+ catch (const MessageException& e) {
+ MessageID ident = e.id();
+ vector<string> args = e.arguments();
+
+ // Log the variable number of arguments. The actual message will be
+ // logged when the error_message variable is destroyed.
+ Formatter<isc::log::Logger> error_message = logger.error(ident);
+ for (vector<string>::size_type i = 0; i < args.size(); ++i) {
+ error_message = error_message.arg(args[i]);
+ }
+ }
+}
+
+// Reset logging to settings passed to init()
+void
+LoggerManager::reset() {
+ setRootLoggerName(initRootName());
+ LoggerManagerImpl::reset(initSeverity(), initDebugLevel());
+}
+
+std::mutex&
+LoggerManager::getMutex() {
+ static std::mutex mutex;
+
+ return (mutex);
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_manager.h b/src/lib/log/logger_manager.h
new file mode 100644
index 0000000..1ddea6d
--- /dev/null
+++ b/src/lib/log/logger_manager.h
@@ -0,0 +1,173 @@
+// Copyright (C) 2011-2019 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 LOGGER_MANAGER_H
+#define LOGGER_MANAGER_H
+
+#include <exceptions/exceptions.h>
+#include <log/logger_specification.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <mutex>
+
+// Generated if, when updating the logging specification, an unknown
+// destination is encountered.
+class UnknownLoggingDestination : public isc::Exception {
+public:
+ UnknownLoggingDestination(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+namespace isc {
+namespace log {
+
+class LoggerManagerImpl;
+
+/// \brief Logger Manager
+///
+/// The logger manager class exists to process the set of logger specifications
+/// and use them to set up the logging in the program appropriately.
+///
+/// To isolate the underlying implementation from basic processing, the
+/// LoggerManager is implemented using the "pimpl" idiom.
+
+class LoggerManager : public boost::noncopyable {
+public:
+ /// \brief Constructor
+ LoggerManager();
+
+ /// \brief Destructor
+ ~LoggerManager();
+
+ /// \brief Process Specifications
+ ///
+ /// Replaces the current logging configuration by the one given.
+ ///
+ /// \param start Iterator pointing to the start of the collection of
+ /// logging specifications.
+ /// \param finish Iterator pointing to the end of the collection of
+ /// logging specification.
+ template <typename T>
+ void process(T start, T finish) {
+ processInit();
+ for (T i = start; i != finish; ++i) {
+ processSpecification(*i);
+ }
+ processEnd();
+ }
+
+ /// \brief Process a single specification
+ ///
+ /// A convenience function for a single specification.
+ ///
+ /// \param spec Specification to process
+ void process(const LoggerSpecification& spec) {
+ processInit();
+ processSpecification(spec);
+ processEnd();
+ }
+
+ /// \brief Process 'empty' specification
+ ///
+ /// This will disable any existing output options, and set
+ /// the logging to go to the built-in default (stdout).
+ /// If the logger has been initialized with buffering enabled,
+ /// all log messages up to now shall be printed to stdout.
+ ///
+ /// This is mainly useful in scenarios where buffering is needed,
+ /// but it turns out there are no logging specifications to
+ /// handle.
+ void process() {
+ processInit();
+ processEnd();
+ }
+
+ /// \brief Run-Time Initialization
+ ///
+ /// Performs run-time initialization of the logger system, in particular
+ /// supplying the root logger name and name of a replacement message file.
+ ///
+ /// This must be the first logging function called in the program. If
+ /// an attempt is made to log a message before this is function is called,
+ /// the results will be dependent on the underlying logging package.
+ ///
+ /// Any duplicate log IDs encountered are reported as warning, after which
+ /// the global duplicates vector is cleared
+ ///
+ /// \param root Name of the root logger. This should be set to the name of
+ /// the program.
+ /// \param severity Severity at which to log
+ /// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
+ /// \param file Name of the local message file. This must be NULL if there
+ /// is no local message file.
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
+ static void init(const std::string& root,
+ isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
+
+ /// \brief List duplicated log messages.
+ ///
+ /// Lists the duplicated log messages using warning severity. Then, it
+ /// clears the list of duplicated messages. This method is called by the
+ /// \c init method and by the \c isc::hooks::LibraryManager when the new
+ /// hooks library is loaded.
+ static void logDuplicatedMessages();
+
+ /// \brief Reset logging
+ ///
+ /// Resets logging to whatever was set in the call to init(), expect for
+ /// the buffer option.
+ static void reset();
+
+ /// \brief Read local message file
+ ///
+ /// Reads the local message file into the global dictionary, overwriting
+ /// existing messages. If the file contained any message IDs not in the
+ /// dictionary, they are listed in a warning message.
+ ///
+ /// \param file Name of the local message file
+ static void readLocalMessageFile(const char* file);
+
+ /// \brief Return a process-global mutex that's used for mutual
+ /// exclusion among threads of a single process during logging
+ /// calls.
+ static std::mutex& getMutex();
+
+private:
+ /// \brief Initialize Processing
+ ///
+ /// Initializes the processing of a list of specifications by resetting all
+ /// loggers to their defaults, which is to pass the message to their
+ /// parent logger. (Except for the root logger, where the default action is
+ /// to output the message.)
+ void processInit();
+
+ /// \brief Process Logging Specification
+ ///
+ /// Processes the given specification. It is assumed at this point that
+ /// either the logger does not exist or has been made inactive.
+ void processSpecification(const LoggerSpecification& spec);
+
+ /// \brief End Processing
+ ///
+ /// Place holder for finish processing.
+ /// TODO: Check that the root logger has something enabled
+ void processEnd();
+
+ // Members
+ LoggerManagerImpl* impl_; ///< Pointer to implementation
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // LOGGER_MANAGER_H
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
new file mode 100644
index 0000000..946e163
--- /dev/null
+++ b/src/lib/log/logger_manager_impl.cc
@@ -0,0 +1,322 @@
+// Copyright (C) 2011-2022 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 <algorithm>
+#include <iostream>
+#include <array>
+
+#include <log4cplus/logger.h>
+#include <log4cplus/configurator.h>
+#include <log4cplus/hierarchy.h>
+#include <log4cplus/consoleappender.h>
+#include <log4cplus/fileappender.h>
+#include <log4cplus/syslogappender.h>
+#include <log4cplus/helpers/loglog.h>
+#include <log4cplus/version.h>
+
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <log/logger_level_impl.h>
+#include <log/logger_manager.h>
+#include <log/logger_manager_impl.h>
+#include <log/log_messages.h>
+#include <log/logger_name.h>
+#include <log/logger_specification.h>
+#include <log/buffer_appender_impl.h>
+
+#include <exceptions/isc_assert.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using boost::lexical_cast;
+
+namespace isc {
+namespace log {
+
+// Reset hierarchy of loggers back to default settings. This removes all
+// appenders from loggers, sets their severity to NOT_SET (so that events are
+// passed back to the parent) and resets the root logger to logging
+// informational messages. (This last is not a log4cplus default, so we have to
+// explicitly reset the logging severity.)
+void
+LoggerManagerImpl::processInit() {
+ storeBufferAppenders();
+
+ log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
+ initRootLogger();
+}
+
+// Flush the BufferAppenders at the end of processing a new specification
+void
+LoggerManagerImpl::processEnd() {
+ flushBufferAppenders();
+}
+
+// Process logging specification. Set up the common states then dispatch to
+// add output specifications.
+void
+LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
+ string const& name(spec.getName());
+ string const& root_logger_name(getRootLoggerName());
+
+ log4cplus::Logger logger = log4cplus::Logger::getInstance(expandLoggerName(name));
+
+ // Set severity level according to specification entry.
+ logger.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
+ Level(spec.getSeverity(), spec.getDbglevel())));
+
+ // Set the additive flag.
+ logger.setAdditivity(spec.getAdditive());
+
+ // Replace all appenders for this logger.
+ logger.removeAllAppenders();
+
+ if (name == root_logger_name) {
+ // Store a copy of the root specification. It might be required later.
+ root_spec_ = spec;
+ }
+
+ // Output options given?
+ if (spec.optionCount() > 0) {
+ // If there are output options provided, continue with the given spec.
+ appenderFactory(logger, spec);
+ } else {
+ // If there are no output options, inherit them from the root logger.
+ // It's important that root_spec_.getName() is not used further since it
+ // may be different than the logger being configured here.
+ appenderFactory(logger, root_spec_);
+ }
+}
+
+void
+LoggerManagerImpl::appenderFactory(log4cplus::Logger& logger,
+ LoggerSpecification const& spec) {
+ for (OutputOption const& i : spec) {
+ switch (i.destination) {
+ case OutputOption::DEST_CONSOLE:
+ createConsoleAppender(logger, i);
+ break;
+
+ case OutputOption::DEST_FILE:
+ createFileAppender(logger, i);
+ break;
+
+ case OutputOption::DEST_SYSLOG:
+ createSyslogAppender(logger, i);
+ break;
+
+ default:
+ // Not a valid destination. As we are in the middle of updating
+ // logging destinations, we could be in the situation where
+ // there are no valid appenders. For this reason, throw an
+ // exception.
+ isc_throw(UnknownLoggingDestination,
+ "Unknown logging destination, code = " << i.destination);
+ }
+ }
+}
+
+// Console appender - log to either stdout or stderr.
+void
+LoggerManagerImpl::createConsoleAppender(log4cplus::Logger& logger,
+ const OutputOption& opt)
+{
+ log4cplus::SharedAppenderPtr console(
+ new log4cplus::ConsoleAppender(
+ (opt.stream == OutputOption::STR_STDERR), opt.flush));
+
+ setAppenderLayout(console, (opt.pattern.empty() ?
+ OutputOption::DEFAULT_CONSOLE_PATTERN : opt.pattern));
+ logger.addAppender(console);
+}
+
+// File appender. Depending on whether a maximum size is given, either
+// a standard file appender or a rolling file appender will be created.
+// In the case of the latter, we set "UseLockFile" to true so that
+// log4cplus internally avoids race in rolling over the files by multiple
+// processes. This feature isn't supported in log4cplus 1.0.x, but setting
+// the property unconditionally is okay as unknown properties are simply
+// ignored.
+void
+LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
+ const OutputOption& opt)
+{
+ // Append to existing file
+ const std::ios::openmode mode = std::ios::app;
+
+ log4cplus::SharedAppenderPtr fileapp;
+ if (opt.maxsize == 0) {
+ fileapp = log4cplus::SharedAppenderPtr(new log4cplus::FileAppender(
+ opt.filename, mode, opt.flush));
+ } else {
+ log4cplus::helpers::Properties properties;
+ properties.setProperty("File", opt.filename);
+
+ // log4cplus supports file sizes past INT_MAX only in suffixed format.
+ // Convert from integer.
+ uint64_t maxsize(opt.maxsize);
+ size_t i(0);
+ while (std::numeric_limits<int32_t>::max() < maxsize && i < 2) {
+ maxsize /= 1000;
+ ++i;
+ }
+ std::array<std::string, 3> const suffixes({"", "KB", "MB"});
+ std::string const max_file_size(to_string(maxsize) + suffixes[i]);
+
+ // If maxsize is still past INT_MAX, it will overflow in log4cplus,
+ // so stop here instead.
+ if (std::numeric_limits<int32_t>::max() < maxsize) {
+ isc_throw(BadValue, "expected maxsize < "
+ << std::numeric_limits<int32_t>::max()
+ << "MB, but instead got " << max_file_size);
+ }
+
+ properties.setProperty("MaxFileSize", max_file_size);
+ properties.setProperty("MaxBackupIndex",
+ lexical_cast<string>(opt.maxver));
+ properties.setProperty("ImmediateFlush", opt.flush ? "true" : "false");
+ properties.setProperty("UseLockFile", "true");
+ fileapp = log4cplus::SharedAppenderPtr(
+ new log4cplus::RollingFileAppender(properties));
+ }
+
+ setAppenderLayout(fileapp, (opt.pattern.empty() ?
+ OutputOption::DEFAULT_FILE_PATTERN : opt.pattern));
+ logger.addAppender(fileapp);
+}
+
+void
+LoggerManagerImpl::createBufferAppender(log4cplus::Logger& logger) {
+ log4cplus::SharedAppenderPtr bufferapp(new internal::BufferAppender());
+ bufferapp->setName("buffer");
+ logger.addAppender(bufferapp);
+ // Since we do not know at what level the loggers will end up
+ // running, set it to the highest for now
+ logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+}
+
+// Syslog appender.
+void
+LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
+ const OutputOption& opt)
+{
+ log4cplus::helpers::Properties properties;
+ properties.setProperty("ident", getRootLoggerName());
+ properties.setProperty("facility", opt.facility);
+ log4cplus::SharedAppenderPtr syslogapp(
+ new log4cplus::SysLogAppender(properties));
+ setAppenderLayout(syslogapp, (opt.pattern.empty() ?
+ OutputOption::DEFAULT_SYSLOG_PATTERN : opt.pattern));
+ logger.addAppender(syslogapp);
+}
+
+
+// One-time initialization of the log4cplus system
+void
+LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel,
+ bool buffer)
+{
+ // Set up basic configurator. This attaches a ConsoleAppender to the
+ // root logger with suitable output. This is used until we we have
+ // actually read the logging configuration, in which case the output
+ // may well be changed.
+ log4cplus::BasicConfigurator config;
+ config.configure();
+
+ // Add the additional debug levels
+ LoggerLevelImpl::init();
+
+ initRootLogger(severity, dbglevel, buffer);
+}
+
+// Reset logging to default configuration. This closes all appenders
+// and resets the root logger to output INFO messages to the console.
+// It is principally used in testing.
+void
+LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel)
+{
+ // Initialize the root logger
+ initRootLogger(severity, dbglevel);
+}
+
+// Initialize the root logger
+void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
+ int dbglevel, bool buffer)
+{
+ log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
+
+ // Disable log4cplus' own logging, unless --enable-debug was
+ // specified to configure. Note that this does not change
+ // LogLog's levels (that is still just INFO).
+#ifndef ENABLE_DEBUG
+ log4cplus::helpers::LogLog::getLogLog()->setQuietMode(true);
+#endif
+
+ // Set the log4cplus root to not output anything - effectively we are
+ // ignoring it.
+ log4cplus::Logger::getRoot().setLogLevel(log4cplus::OFF_LOG_LEVEL);
+
+ // Set the level for the Kea root logger to the given severity and
+ // debug level.
+ log4cplus::Logger kea_root = log4cplus::Logger::getInstance(
+ getRootLoggerName());
+ kea_root.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
+ Level(severity, dbglevel)));
+
+ if (buffer) {
+ createBufferAppender(kea_root);
+ } else {
+ OutputOption opt;
+ createConsoleAppender(kea_root, opt);
+ }
+}
+
+
+void LoggerManagerImpl::setAppenderLayout(
+ log4cplus::SharedAppenderPtr& appender,
+ std::string pattern)
+{
+ // Finally the text of the message
+ appender->setLayout(
+#if LOG4CPLUS_VERSION < LOG4CPLUS_MAKE_VERSION(2, 0, 0)
+ auto_ptr<log4cplus::Layout>
+#else
+ unique_ptr<log4cplus::Layout>
+#endif
+ (new log4cplus::PatternLayout(pattern)));
+}
+
+void LoggerManagerImpl::storeBufferAppenders() {
+ // Walk through all loggers, and find any buffer appenders there
+ log4cplus::LoggerList loggers = log4cplus::Logger::getCurrentLoggers();
+ log4cplus::LoggerList::iterator it;
+ for (it = loggers.begin(); it != loggers.end(); ++it) {
+ log4cplus::SharedAppenderPtr buffer_appender =
+ it->getAppender("buffer");
+ if (buffer_appender) {
+ buffer_appender_store_.push_back(buffer_appender);
+ }
+ }
+}
+
+void LoggerManagerImpl::flushBufferAppenders() {
+ std::vector<log4cplus::SharedAppenderPtr> copy;
+ buffer_appender_store_.swap(copy);
+
+ std::vector<log4cplus::SharedAppenderPtr>::iterator it;
+ for (it = copy.begin(); it != copy.end(); ++it) {
+ internal::BufferAppender* app =
+ dynamic_cast<internal::BufferAppender*>(it->get());
+ isc_throw_assert(app);
+ app->flush();
+ }
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_manager_impl.h b/src/lib/log/logger_manager_impl.h
new file mode 100644
index 0000000..c459cfe
--- /dev/null
+++ b/src/lib/log/logger_manager_impl.h
@@ -0,0 +1,197 @@
+// Copyright (C) 2011-2022 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 LOGGER_MANAGER_IMPL_H
+#define LOGGER_MANAGER_IMPL_H
+
+#include <string>
+
+#include <log4cplus/appender.h>
+#include <log/logger_level.h>
+#include <log/logger_specification.h>
+
+// Forward declaration to avoid need to include log4cplus header file here.
+namespace log4cplus {
+class Logger;
+class Appender;
+}
+
+namespace isc {
+namespace log {
+
+// Forward declarations
+struct OutputOption;
+
+/// \brief Logger Manager Implementation
+///
+/// This is the implementation of the logger manager for the log4cplus
+/// underlying logger.
+///
+/// As noted in logger_manager.h, the logger manager class exists to set up the
+/// logging given a set of specifications. This class handles the processing
+/// of those specifications.
+///
+/// Note: the logging has been implemented using a "pimpl" idiom to conceal
+/// the underlying implementation (log4cplus) from the Kea interface.
+/// This requires that there be an implementation class, even though in this
+/// case, all the implementation class methods can be declared static.
+
+class LoggerManagerImpl {
+public:
+
+ /// \brief Constructor
+ LoggerManagerImpl() {}
+
+ /// \brief Initialize Processing
+ ///
+ /// This resets the hierarchy of loggers back to their defaults. This means
+ /// that all non-root loggers (if they exist) are set to NOT_SET, and the
+ /// root logger reset to logging informational messages.
+ void processInit();
+
+ /// \brief Process Specification
+ ///
+ /// Processes the specification for a single logger.
+ ///
+ /// \param spec Logging specification for this logger
+ void processSpecification(const LoggerSpecification& spec);
+
+ /// \brief End Processing
+ ///
+ /// Terminates the processing of the logging specifications.
+ void processEnd();
+
+ /// \brief Implementation-specific initialization
+ ///
+ /// Sets the basic configuration for logging (the root logger has INFO and
+ /// more severe messages routed to stdout). Unless this function (or
+ /// process() with a valid specification for all loggers that will log
+ /// messages) is called before a message is logged, log4cplus will output
+ /// a message to stderr noting that logging has not been initialized.
+ ///
+ /// It is assumed here that the name of the Kea root logger can be
+ /// obtained from the global function getRootLoggerName().
+ ///
+ /// \param severity Severity to be associated with this logger
+ /// \param dbglevel Debug level associated with the root logger
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
+ static void init(isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0, bool buffer = false);
+
+ /// \brief Reset logging
+ ///
+ /// Resets to default configuration (root logger logging to the console
+ /// with INFO severity).
+ ///
+ /// \param severity Severity to be associated with this logger
+ /// \param dbglevel Debug level associated with the root logger
+ static void reset(isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0);
+
+private:
+ /// @brief Decides what appender to create.
+ ///
+ /// Delegates to the other functions that create appenders based on what's
+ /// in spec.
+ ///
+ /// @param logger log4cplus logger to which the appender must be attached
+ /// @param spec the configured specification consisting of output options
+ static void appenderFactory(log4cplus::Logger& logger,
+ LoggerSpecification const& spec);
+
+ /// \brief Create console appender
+ ///
+ /// Creates an object that, when attached to a logger, will log to one
+ /// of the output streams (stdout or stderr).
+ ///
+ /// \param logger Log4cplus logger to which the appender must be attached.
+ /// \param opt Output options for this appender.
+ static void createConsoleAppender(log4cplus::Logger& logger,
+ const OutputOption& opt);
+
+ /// \brief Create file appender
+ ///
+ /// Creates an object that, when attached to a logger, will log to a
+ /// specified file. This also includes the ability to "roll" files when
+ /// they reach a specified size.
+ ///
+ /// \param logger Log4cplus logger to which the appender must be attached.
+ /// \param opt Output options for this appender.
+ static void createFileAppender(log4cplus::Logger& logger,
+ const OutputOption& opt);
+
+ /// \brief Create syslog appender
+ ///
+ /// Creates an object that, when attached to a logger, will log to the
+ /// syslog file.
+ ///
+ /// \param logger Log4cplus logger to which the appender must be attached.
+ /// \param opt Output options for this appender.
+ static void createSyslogAppender(log4cplus::Logger& logger,
+ const OutputOption& opt);
+
+ /// \brief Create buffered appender
+ ///
+ /// Appends an object to the logger that will store the log events sent
+ /// to the logger. These log messages are replayed to the logger in
+ /// processEnd().
+ ///
+ /// \param logger Log4cplus logger to which the appender must be attached.
+ static void createBufferAppender(log4cplus::Logger& logger);
+
+ /// \brief Set default layout and severity for root logger
+ ///
+ /// Initializes the root logger to Kea defaults - console or buffered
+ /// output and the passed severity/debug level.
+ ///
+ /// \param severity Severity of messages that the logger should output.
+ /// \param dbglevel Debug level if severity = DEBUG
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
+ static void initRootLogger(isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0, bool buffer = false);
+
+ /// \brief Set layout for an appender
+ ///
+ /// Sets the layout of the specified appender to one suitable for file
+ /// or console output:
+ ///
+ /// \param appender Appender for which this pattern is to be set.
+ /// \param pattern Log message format pattern
+ static void setAppenderLayout(log4cplus::SharedAppenderPtr& appender,
+ std::string pattern);
+
+ /// \brief Store all buffer appenders
+ ///
+ /// When processing a new specification, this method can be used
+ /// to keep a list of the buffer appenders; the caller can then
+ /// process the specification, and call \c flushBufferAppenders()
+ /// to flush and clear the list
+ void storeBufferAppenders();
+
+ /// \brief Flush the stored buffer appenders
+ ///
+ /// This flushes the list of buffer appenders stored in
+ /// \c storeBufferAppenders(), and clears it
+ void flushBufferAppenders();
+
+ /// @brief Only used between processInit() and processEnd(), to temporarily
+ /// store the buffer appenders in order to flush them after
+ /// processSpecification() calls have been completed
+ std::vector<log4cplus::SharedAppenderPtr> buffer_appender_store_;
+
+ /// @brief A hard copy of the specification for the root logger used for
+ /// inheritance by child loggers.
+ LoggerSpecification root_spec_;
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // LOGGER_MANAGER_IMPL_H
diff --git a/src/lib/log/logger_name.cc b/src/lib/log/logger_name.cc
new file mode 100644
index 0000000..39a79cc
--- /dev/null
+++ b/src/lib/log/logger_name.cc
@@ -0,0 +1,58 @@
+// 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 <string>
+#include <log/logger_name.h>
+
+namespace isc {
+namespace log {
+
+namespace {
+
+// Obtain the root logger name in a way that is safe for statically-initialized
+// objects.
+
+std::string&
+getRootLoggerNameInternal() {
+ static std::string root_name;
+ return (root_name);
+}
+
+} // Anonymous namespace
+
+void
+setRootLoggerName(const std::string& name) {
+ getRootLoggerNameInternal() = name;
+}
+
+const std::string& getRootLoggerName() {
+ return (getRootLoggerNameInternal());
+}
+
+const std::string& getDefaultRootLoggerName() {
+ static std::string root_name("kea");
+ return (root_name);
+}
+
+std::string expandLoggerName(const std::string& name) {
+
+ // Are we the root logger, or does the logger name start with
+ // the string "<root_logger_name>.". If so, use a logger
+ // whose name is the one given.
+ if ((name == getRootLoggerName()) ||
+ (name.find(getRootLoggerName() + std::string(".")) == 0)) {
+ return (name);
+
+ }
+
+ // Anything else is assumed to be a sub-logger of the root logger.
+ return (getRootLoggerName() + "." + name);
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_name.h b/src/lib/log/logger_name.h
new file mode 100644
index 0000000..08090cb
--- /dev/null
+++ b/src/lib/log/logger_name.h
@@ -0,0 +1,55 @@
+// 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/.
+
+#ifndef LOGGER_NAME_H
+#define LOGGER_NAME_H
+
+#include <string>
+
+/// \brief Define Name of Root Logger
+///
+/// In BIND-10, the name root logger of a program is the name of the program
+/// itself (in contrast to packages such as log4cplus where the root logger name
+// is something like "root"). These trivial functions allow the setting and
+// getting of that name by the logger classes.
+
+namespace isc {
+namespace log {
+
+/// \brief Set root logger name
+///
+/// This function should be called by the program's initialization code before
+/// any logging functions are called.
+///
+/// \param name Name of the root logger. This should be the program name.
+void setRootLoggerName(const std::string& name);
+
+/// \brief Get root logger name
+///
+/// \return Name of the root logger.
+const std::string& getRootLoggerName();
+
+
+/// @brief Returns the default ('kea') root logger name
+///
+/// @return The default name of root logger.
+const std::string& getDefaultRootLoggerName();
+
+/// \brief Expand logger name
+///
+/// Given a logger name, returns the fully-expanded logger name. If the name
+/// starts with the root logger name, it is returned as-is. Otherwise it is
+/// prefixed with the root logger name.
+///
+/// \param name Name to expand.
+///
+/// \return Fully-expanded logger name.
+std::string expandLoggerName(const std::string& name);
+
+}
+}
+
+#endif // LOGGER_NAME_H
diff --git a/src/lib/log/logger_specification.h b/src/lib/log/logger_specification.h
new file mode 100644
index 0000000..906c2dc
--- /dev/null
+++ b/src/lib/log/logger_specification.h
@@ -0,0 +1,148 @@
+// 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/.
+
+#ifndef LOGGER_SPECIFICATION_H
+#define LOGGER_SPECIFICATION_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <log/logger_level.h>
+#include <log/output_option.h>
+
+/// \brief Logger Specification
+///
+/// The logging configuration options are a list of logger specifications, each
+/// of which represents a logger and the options for its appenders.
+///
+/// Unlike OutputOption (which is a struct), this contains a bit more
+/// structure and is concealed in a class.
+
+#include <vector>
+
+namespace isc {
+namespace log {
+
+class LoggerSpecification {
+public:
+ typedef std::vector<OutputOption>::iterator iterator;
+ typedef std::vector<OutputOption>::const_iterator const_iterator;
+
+ /// \brief Constructor
+ ///
+ /// \param name Name of the logger.
+ /// \param severity Severity at which this logger logs
+ /// \param dbglevel Debug level
+ /// \param additive true to cause message logged with this logger to be
+ /// passed to the parent for logging.
+ LoggerSpecification(const std::string& name = "",
+ isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0, bool additive = false) :
+ name_(name), severity_(severity), dbglevel_(dbglevel),
+ additive_(additive)
+ {}
+
+ /// \brief Set the name of the logger.
+ ///
+ /// \param name Name of the logger.
+ void setName(const std::string& name) {
+ name_ = name;
+ }
+
+ /// \return Return logger name.
+ std::string getName() const {
+ return name_;
+ }
+
+ /// \brief Set the severity.
+ ///
+ /// \param severity New severity of the logger.
+ void setSeverity(isc::log::Severity severity) {
+ severity_ = severity;
+ }
+
+ /// \return Return logger severity.
+ isc::log::Severity getSeverity() const {
+ return severity_;
+ }
+
+ /// \brief Set the debug level.
+ ///
+ /// \param dbglevel New debug level of the logger.
+ void setDbglevel(int dbglevel) {
+ dbglevel_ = dbglevel;
+ }
+
+ /// \return Return logger debug level
+ int getDbglevel() const {
+ return dbglevel_;
+ }
+
+ /// \brief Set the additive flag.
+ ///
+ /// \param additive New value of the additive flag.
+ void setAdditive(bool additive) {
+ additive_ = additive;
+ }
+
+ /// \return Return additive flag.
+ bool getAdditive() const {
+ return additive_;
+ }
+
+ /// \brief Add output option.
+ ///
+ /// \param option Option to add to the list.
+ void addOutputOption(const OutputOption& option) {
+ options_.push_back(option);
+ }
+
+ /// \return Iterator to start of output options.
+ iterator begin() {
+ return options_.begin();
+ }
+
+ /// \return Iterator to start of output options.
+ const_iterator begin() const {
+ return options_.begin();
+ }
+
+ /// \return Iterator to end of output options.
+ iterator end() {
+ return options_.end();
+ }
+
+ /// \return Iterator to end of output options.
+ const_iterator end() const {
+ return options_.end();
+ }
+
+ /// \return Number of output specification options.
+ size_t optionCount() const {
+ return options_.size();
+ }
+
+ /// \brief Reset back to defaults.
+ void reset() {
+ name_ = "";
+ severity_ = isc::log::INFO;
+ dbglevel_ = 0;
+ additive_ = false;
+ options_.clear();
+ }
+
+private:
+ std::string name_; ///< Logger name
+ isc::log::Severity severity_; ///< Severity for this logger
+ int dbglevel_; ///< Debug level
+ bool additive_; ///< Chaining output
+ std::vector<OutputOption> options_; ///< Logger options
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // LOGGER_SPECIFICATION_H
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
new file mode 100644
index 0000000..9431edd
--- /dev/null
+++ b/src/lib/log/logger_support.cc
@@ -0,0 +1,119 @@
+// 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 <string>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+
+using namespace std;
+
+namespace {
+
+// Flag to hold logging initialization state.
+bool logging_init_state = false;
+
+} // Anonymous namespace
+
+namespace isc {
+namespace log {
+
+// Return initialization state.
+bool
+isLoggingInitialized() {
+ return (logging_init_state);
+}
+
+// Set initialization state. (Note: as logging can be initialized via a direct
+// call to LoggerManager::init(), this function is called from there, not from
+// the initialization functions in this file.
+void
+setLoggingInitialized(bool state) {
+ logging_init_state = state;
+}
+
+// Logger Run-Time Initialization.
+
+void
+initLogger(const string& root, isc::log::Severity severity, int dbglevel,
+ const char* file, bool buffer) {
+ LoggerManager::init(root, severity, dbglevel, file, buffer);
+}
+
+// Reset characteristics of the root logger to that set by the environment
+// variables KEA_LOGGER_SEVERITY, KEA_LOGGER_DBGLEVEL and KEA_LOGGER_DESTINATION.
+
+void
+setDefaultLoggingOutput(bool verbose) {
+
+ using namespace isc::log;
+
+ // Constants: not declared static as this is function is expected to be
+ // called once only
+ const string DEVNULL = "/dev/null";
+ const string STDOUT = "stdout";
+ const string STDERR = "stderr";
+ const string SYSLOG = "syslog";
+ const string SYSLOG_COLON = "syslog:";
+
+ // Get the destination. If not specified, assume /dev/null. (The default
+ // severity for unit tests is DEBUG, which generates a lot of output.
+ // Routing the logging to /dev/null will suppress that, whilst still
+ // ensuring that the code paths are tested.)
+ const char* destination = getenv("KEA_LOGGER_DESTINATION");
+ const string dest((destination == NULL) ? DEVNULL : destination);
+
+ // Prepare the objects to define the logging specification
+ LoggerSpecification spec(getRootLoggerName(),
+ keaLoggerSeverity(verbose ? isc::log::DEBUG :
+ isc::log::INFO),
+ keaLoggerDbglevel(isc::log::MAX_DEBUG_LEVEL));
+ OutputOption option;
+
+ // Set up output option according to destination specification
+ if (dest == STDOUT) {
+ option.destination = OutputOption::DEST_CONSOLE;
+ option.stream = OutputOption::STR_STDOUT;
+
+ } else if (dest == STDERR) {
+ option.destination = OutputOption::DEST_CONSOLE;
+ option.stream = OutputOption::STR_STDERR;
+
+ } else if (dest == SYSLOG) {
+ option.destination = OutputOption::DEST_SYSLOG;
+ // Use default specified in OutputOption constructor for the
+ // syslog destination
+
+ } else if (dest.find(SYSLOG_COLON) == 0) {
+ option.destination = OutputOption::DEST_SYSLOG;
+ // Must take account of the string actually being "syslog:"
+ if (dest == SYSLOG_COLON) {
+ cerr << "**ERROR** value for KEA_LOGGER_DESTINATION of " <<
+ SYSLOG_COLON << " is invalid, " << SYSLOG <<
+ " will be used instead\n";
+ // Use default for logging facility
+
+ } else {
+ // Everything else in the string is the facility name
+ option.facility = dest.substr(SYSLOG_COLON.size());
+ }
+
+ } else {
+ // Not a recognized destination, assume a file.
+ option.destination = OutputOption::DEST_FILE;
+ option.filename = dest;
+ }
+
+ // ... and set the destination
+ spec.addOutputOption(option);
+ LoggerManager manager;
+ manager.process(spec);
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
new file mode 100644
index 0000000..515ab11
--- /dev/null
+++ b/src/lib/log/logger_support.h
@@ -0,0 +1,78 @@
+// 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/.
+
+#ifndef LOGGER_SUPPORT_H
+#define LOGGER_SUPPORT_H
+
+#include <unistd.h>
+
+#include <string>
+#include <log/logger.h>
+#include <log/logger_unittest_support.h>
+
+/// \file
+/// \brief Logging initialization functions
+///
+/// Contains a set of functions relating to logging initialization that are
+/// used by the production code.
+
+namespace isc {
+namespace log {
+
+/// \brief Is logging initialized?
+///
+/// As some underlying logging implementations can behave unpredictably if they
+/// have not been initialized when a logging function is called, their
+/// initialization state is tracked. The logger functions will check this flag
+/// and throw an exception if logging is not initialized at that point.
+///
+/// \return true if logging has been initialized, false if not
+bool isLoggingInitialized();
+
+/// \brief Set state of "logging initialized" flag
+///
+/// \param state State to set the flag to. (This is expected to be "true" - the
+/// default - for all code apart from specific unit tests.)
+void setLoggingInitialized(bool state = true);
+
+/// \brief Run-time initialization
+///
+/// Performs run-time initialization of the logger in particular supplying:
+///
+/// - Name of the root logger
+/// - The severity (and if applicable, debug level) for the root logger.
+/// - Name of a local message file, containing localization of message text.
+///
+/// This function is likely to change over time as more debugging options are
+/// held in the configuration database.
+///
+/// \param root Name of the root logger
+/// \param severity Severity at which to log
+/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
+/// \param file Name of the local message file.
+/// \param buffer If true, all log messages will be buffered until one of
+/// the \c process() methods is called. If false, initial logging
+/// shall go to the default output (i.e. stdout)
+void initLogger(const std::string& root,
+ isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
+
+/// \brief Reset root logger characteristics
+///
+/// This is a simplified interface into the resetting of the characteristics
+/// of the root logger. It is aimed for use in unit tests and initial
+/// phase of bring up before logging configuration is parsed and applied.
+/// It uses KEA_LOGGER_DESTINATION environment variable to specify
+/// logging destination.
+/// @param verbose defines whether logging should be verbose or not
+void setDefaultLoggingOutput(bool verbose = true);
+
+
+} // namespace log
+} // namespace isc
+
+#endif // LOGGER_SUPPORT_H
diff --git a/src/lib/log/logger_unittest_support.cc b/src/lib/log/logger_unittest_support.cc
new file mode 100644
index 0000000..fc01c6e
--- /dev/null
+++ b/src/lib/log/logger_unittest_support.cc
@@ -0,0 +1,101 @@
+// Copyright (C) 2011-2021 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 <algorithm>
+#include <string>
+
+#include <log/logger_level.h>
+#include <log/logger_name.h>
+#include <log/logger_manager.h>
+#include <log/logger_specification.h>
+#include <log/logger_unittest_support.h>
+#include <log/logger_support.h>
+#include <log/output_option.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Get the logging severity. This is defined by the environment variable
+// KEA_LOGGER_SEVERITY, and can be one of "DEBUG", "INFO", "WARN", "ERROR"
+// of "FATAL". (Note that the string must be in upper case with no leading
+// of trailing blanks.) If not present, the default severity passed to the
+// function is returned.
+isc::log::Severity
+keaLoggerSeverity(isc::log::Severity defseverity) {
+ const char* sev_char = getenv("KEA_LOGGER_SEVERITY");
+ if (sev_char) {
+ return (isc::log::getSeverity(sev_char));
+ }
+ return (defseverity);
+}
+
+// Get the debug level. This is defined by the environment variable
+// KEA_LOGGER_DBGLEVEL. If not defined, a default value passed to the function
+// is returned.
+int
+keaLoggerDbglevel(int defdbglevel) {
+ const char* dbg_char = getenv("KEA_LOGGER_DBGLEVEL");
+ if (dbg_char) {
+ int level = 0;
+ try {
+ level = boost::lexical_cast<int>(dbg_char);
+ if (level < MIN_DEBUG_LEVEL) {
+ std::cerr << "**ERROR** debug level of " << level
+ << " is invalid - a value of " << MIN_DEBUG_LEVEL
+ << " will be used\n";
+ level = MIN_DEBUG_LEVEL;
+ } else if (level > MAX_DEBUG_LEVEL) {
+ std::cerr << "**ERROR** debug level of " << level
+ << " is invalid - a value of " << MAX_DEBUG_LEVEL
+ << " will be used\n";
+ level = MAX_DEBUG_LEVEL;
+ }
+ } catch (...) {
+ // Error, but not fatal to the test
+ std::cerr << "**ERROR** Unable to translate "
+ "KEA_LOGGER_DBGLEVEL - a value of 0 will be used\n";
+ }
+ return (level);
+ }
+
+ return (defdbglevel);
+}
+
+// Logger Run-Time Initialization via Environment Variables
+void initLogger(isc::log::Severity severity, int dbglevel) {
+
+ // Root logger name is defined by the environment variable KEA_LOGGER_ROOT.
+ // If not present, the name is "kea".
+ const char* root = getenv("KEA_LOGGER_ROOT");
+ if (! root) {
+ // If not present, the name is "kea".
+ root = isc::log::getDefaultRootLoggerName().c_str();
+ }
+
+ // Set the local message file
+ const char* localfile = getenv("KEA_LOGGER_LOCALMSG");
+
+ // Set a directory for creating lockfiles when running tests
+ setenv("KEA_LOCKFILE_DIR", TOP_BUILDDIR, 0);
+
+ // Initialize logging
+ initLogger(root, severity, dbglevel, localfile);
+
+ // Now set reset the output destination of the root logger, overriding
+ // the default severity, debug level and destination with those specified
+ // in the environment variables. (The two-step approach is used as the
+ // setUnitTestRootLoggerCharacteristics() function is used in several
+ // places in the Kea tests, and it avoid duplicating code.)
+ isc::log::setDefaultLoggingOutput();
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/logger_unittest_support.h b/src/lib/log/logger_unittest_support.h
new file mode 100644
index 0000000..5b3f874
--- /dev/null
+++ b/src/lib/log/logger_unittest_support.h
@@ -0,0 +1,111 @@
+// 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/.
+
+#ifndef LOGGER_UNITTEST_SUPPORT_H
+#define LOGGER_UNITTEST_SUPPORT_H
+
+#include <string>
+#include <log/logger.h>
+
+/// \file
+/// \brief Miscellaneous logging functions used by the unit tests.
+///
+/// As the configuration database is usually unavailable during unit tests,
+/// the functions defined here allow a limited amount of logging configuration
+/// through the use of environment variables
+
+namespace isc {
+namespace log {
+
+/// \brief Run-Time Initialization for Unit Tests from Environment
+///
+/// Performs run-time initialization of the logger via the setting of
+/// environment variables. These are:
+///
+/// - KEA_LOGGER_ROOT\n
+/// Name of the root logger. If not given, the string "kea" will be used.
+///
+/// - KEA_LOGGER_SEVERITY\n
+/// Severity of messages that will be logged. This must be one of the strings
+/// "DEBUG", "INFO", "WARN", "ERROR", "FATAL" or "NONE". (Must be upper case
+/// and must not contain leading or trailing spaces.) If not specified (or if
+/// specified but incorrect), the default passed as argument to this function
+/// (currently DEBUG) will be used.
+///
+/// - KEA_LOGGER_DBGLEVEL\n
+/// Ignored if the level is not DEBUG, this should be a number between 0 and
+/// 99 indicating the logging severity. The default is 0. If outside these
+/// limits or if not a number, The value passed to this function (default
+/// of MAX_DEBUG_LEVEL) is used.
+///
+/// - KEA_LOGGER_LOCALMSG\n
+/// If defined, the path specification of a file that contains message
+/// definitions replacing ones in the default dictionary.
+///
+/// - KEA_LOGGER_DESTINATION\n
+/// If defined, the destination of the logging output. This can be one of:
+/// - \c stdout Send output to stdout.
+/// - \c stderr Send output to stderr
+/// - \c syslog Send output to syslog using the facility local0.
+/// - \c syslog:xxx Send output to syslog, using the facility xxx. ("xxx"
+/// should be one of the syslog facilities such as "local0".) There must
+/// be a colon between "syslog" and "xxx
+/// - \c other Anything else is interpreted as the name of a file to which
+/// output is appended. If the file does not exist, it is created.
+///
+/// Any errors in the settings cause messages to be output to stderr.
+///
+/// This function is aimed at test programs, allowing the default settings to
+/// be overridden by the tester. It is not intended for use in production
+/// code.
+///
+/// @note: Do NOT use this function in production code as it creates
+/// lockfile in the build dir. That's ok for tests, but not
+/// ok for production code.
+///
+/// @todo: Rename. This function overloads the initLogger() function that can
+/// be used to initialize production programs. This may lead to confusion.
+void initLogger(isc::log::Severity severity = isc::log::DEBUG,
+ int dbglevel = isc::log::MAX_DEBUG_LEVEL);
+
+
+/// \brief Obtains logging severity from KEA_LOGGER_SEVERITY
+///
+/// Support function called by the unit test logging initialization code.
+/// It returns the logging severity defined by KEA_LOGGER_SEVERITY. If
+/// not defined it returns the default passed to it.
+///
+/// \param defseverity Default severity used if KEA_LOGGER_SEVERITY is not
+// defined.
+///
+/// \return Severity to use for the logging.
+isc::log::Severity keaLoggerSeverity(isc::log::Severity defseverity);
+
+
+/// \brief Obtains logging debug level from KEA_LOGGER_DBGLEVEL
+///
+/// Support function called by the unit test logging initialization code.
+/// It returns the logging debug level defined by KEA_LOGGER_DBGLEVEL. If
+/// not defined, it returns the default passed to it.
+///
+/// N.B. If there is an error, a message is written to stderr and a value
+/// related to the error is used. (This is because (a) logging is not yet
+/// initialized, hence only the error stream is known to exist, and (b) this
+/// function is only used in unit test logging initialization, so incorrect
+/// selection of a level is not really an issue.)
+///
+/// \param defdbglevel Default debug level to be used if KEA_LOGGER_DBGLEVEL
+/// is not defined.
+///
+/// \return Debug level to use.
+int keaLoggerDbglevel(int defdbglevel);
+
+} // namespace log
+} // namespace isc
+
+
+
+#endif // LOGGER_UNITTEST_SUPPORT_H
diff --git a/src/lib/log/logging.dox b/src/lib/log/logging.dox
new file mode 100644
index 0000000..8dbc02f
--- /dev/null
+++ b/src/lib/log/logging.dox
@@ -0,0 +1,698 @@
+// Copyright (C) 2013-2021 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/.
+
+// Note: the prefix "log" to all labels is an abbreviation for "Logging"
+// and is used to prevent a clash with symbols in any other Doxygen file.
+
+/**
+@page logKeaLogging Kea Logging
+
+@section logBasicIdeas Basic Ideas
+
+The Kea logging system is based on the log4J logging system
+common in Java development, and includes the following ideas:
+
+- A set of severity levels.
+- A hierarchy of logging sources.
+- Separation of message use from message text.
+
+@subsection logSeverity Message Severity
+Each message logged by Kea has a severity associated with it, ranging
+from FATAL - the most severe - to DEBUG - messages output as the code
+executes to facilitate debugging. In order of decreasing severity,
+the levels are:
+
+<dl>
+<dt>FATAL</dt>
+<dd>The program has encountered an error that is so severe that it
+cannot continue (or there is no point in continuing). For example, an
+unhandled exception generated deep within the code has been caught by the
+top-level program. When a fatal error has been logged, the program will
+exit immediately (or shortly afterwards) after dumping some diagnostic
+information.</dd>
+
+<dt>ERROR</dt>
+<dd>Something has happened such that the program can continue but the
+results for the current (or future) operations cannot be guaranteed to
+be correct, or the results will be correct but the service is impaired.
+For example, the program started but attempts to open one or more network
+interfaces failed.</dd>
+
+<dt>WARN</dt>
+<dd>(Warning) An unusual event happened. Although the program will
+continue working normally, the event was sufficiently out of the ordinary
+to warrant drawing attention to it. For example, the authoritative
+server loaded a zone that contained no resource records.</dd>
+
+<dt>INFO</dt>
+<dd>(Information) A normal but significant event has occurred that should
+be recorded, e.g. the program has started or is just about to terminate,
+a new zone has been created, etc.</dd>
+
+<dt>DEBUG</dt>
+<dd>Debug messages are output at this severity. Each message also
+has a debug level associated with it, ranging from 0 (the default)
+for high-level messages and level 99 (the maximum) for the lowest
+level.</dd>
+</dl>
+
+When logging is enabled for a component, it is enabled for a particular
+severity level and all higher severities. So if logging is enabled
+for INFO messages, WARN, ERROR and FATAL messages will also be logged,
+but not DEBUG ones. If enabled for ERROR, only ERROR and FATAL messages
+will be logged.
+
+As noted above, DEBUG messages are also associated with a debug level.
+This allows the developer to control the amount of debugging information
+produced; the higher the debug level, the more information is output.
+For example, if debugging the NSAS (nameserver address store), debug
+levels might be assigned as follows: a level 0 debug message records
+the creation of a new zone, a level 10 logs every timeout when trying
+to get a nameserver address, a level of 50 records every query for an
+address and a level of 70 records every update of the round-trip time.
+
+Like severities, levels are cumulative; so if level 25 is set as the
+debug level, all debug messages associated levels 0 to 25 (inclusive)
+will be output. In fact, it is probably easier to visualize the debug
+levels as part of the severity system:
+@code
+FATAL Most severe
+ERROR
+WARN
+INFO
+DEBUG level 0
+DEBUG level 1
+ :
+DEBUG level 99 Least severe
+@endcode
+When a particular debug level is set, it - and all debug levels and
+severities above it - will be logged.
+
+To try to ensure that the information from different modules is roughly
+comparable for the same debug level, a set of standard debug levels has
+been defined for common types of debug output. (These can be found in
+@ref log_dbglevels.h) However, modules are free to set their own debug
+levels or define additional ones.
+
+@subsection logHierarchical Hierarchical Logging System
+
+When a program writes a message to the logging system, it does so using an
+instance of the @ref isc::log::Logger class. As well as performing the
+write of the message, the logger identifies the source of the message:
+different sources can write to different destinations and can log
+different severities of messages. For example, the logger associated
+with the resolver's cache code could write debugging and other messages
+to a file while all other components only write messages relating to
+errors to the syslog file.
+
+The loggers are hierarchical in that each logger is the child of another
+logger. The top of the hierarchy is the root logger; this does not
+have a parent. The reason for this hierarchy is that unless a logger
+explicitly assigns a value to an attribute (such as severity of messages
+it should log), it picks it up the value from the parent. In Kea,
+each component (kea-dhcp4, kea-dhcp-ddns etc.) has a root logger (named
+after the program) and every other logger in the component is a child
+of that. So in the example above, the error/syslog attributes could be
+associated with the kea-resolver logger while the logger associated with
+the cache sets its own values for the debug/file attributes.
+
+More information about the logging hierarchy can be found in the section
+on Logging configuration in the <a
+href="https://kea.readthedocs.io/">Kea Administrator
+Reference Manual</a>.
+
+@subsection logSeparationUseText Separation of Messages Use from Message Text
+
+Separating the use of the message from the text associated with it -
+in essence, defining message text in an external file - allows for the
+replacement the supplied text of the messages with a local language version.
+It also means that other attributes can be associated with the message,
+for example, an explanation of the meaning of the message and other
+information such as remedial action in the case of errors.
+
+Each message has an identifier such as "LOG_WRITE_ERROR".
+Within the program, this is the symbol passed to the logging system
+which uses the symbol as an index into a dictionary to retrieve the message
+associated with it (e.g. "unable to open %1 for input"), after which it
+substitutes any message parameters (in this example, the name of the file
+where the write operation failed) and logs the result to the destination.
+
+In Kea, the default text for each message is linked into the
+program. Each program is able to read a locally-defined message file
+when it starts, updating the stored definitions with site-specific text.
+When the message is logged, the updated text is output. However, the
+message identifier is always included in the output so that the origin
+of the message can be identified even if the text has been changed.
+
+@note Local message files have not yet been implemented in Kea.
+
+
+
+@section logDeveloperUse Using Logging in a Kea Component
+
+The steps in using the logging system in a Kea component (such as
+an executable or library) are:
+
+<ol>
+<li>Create a message file. This defines messages by an identification
+string and text that explains the message in more detail. Ideally the
+file should have a file type of ".mes".</li>
+
+<li>Run it through the message compiler to produce the files for your
+module. This step should be included in the build process. The message
+compiler is a Kea program and is one of the first programs built and
+linked in the build process. As a result, it should be available for
+compiling the message files of all Kea components and libraries.
+
+For C++ development, the message compiler produces two files in the
+default directory, having the same name as the input file but with file
+types of ".h" and ".cc".</li>
+
+<li>Include the resultant files in your source code to define message symbols,
+and compile the code and link it into your program.</li>
+
+<li>Declare loggers in your code and use them to log messages.</li>
+
+<li>Call the logger initialization function in the program's main module.</li>
+
+</ol>
+
+The following sections describe these steps in more detail.
+
+
+@subsection logMessageFiles Create a Message File
+
+A message file contains message definitions. Typically there
+will be one message file for each component that uses Kea logging.
+An example file could be:
+
+@code
+# Example message file
+
+$NAMESPACE isc::log
+
+% LOG_UNRECOGNIZED_DIRECTIVE line %1: unrecognized directive '%2'
+A line starting with a dollar symbol was found, but the first word on the line
+(shown in the message) was not a recognized message compiler directive.
+
+% LOG_WRITE_ERROR error writing to %1: %2
+The specified error was encountered by the message compiler when writing to
+the named output file.
+@endcode
+
+Points to note are:
+
+<ul>
+<li>Leading and trailing spaces are trimmed from each line before it
+is processed. Although the above example has every line starting at
+column 1, the lines could be indented if desired.</li>
+
+<li>Lines starting with "#" are comments are are ignored. Comments must
+be on a line by themselves; inline comments will be interpreted as part
+of the text of that line.</li>
+
+<li>Lines starting with "$" are directives. At present, just one
+directive is recognized:
+ <dl>
+ <dt>$NAMESPACE &lt;namespace-name&gt;</dt>
+ <dd>The sole argument is the name of the namespace in which the
+ symbols are created. In the absence of a $NAMESPACE directive,
+ symbols will be put in the anonymous namespace.</dd>
+ </dl>
+</li>
+
+<li>Lines starting with "%" are message definitions and comprise the message
+identification and the message text. For example:
+@code
+% LOG_WRITE_ERROR error writing to %1: %2
+@endcode
+
+There may be zero or more spaces between the leading "%" and the
+message identification (which, in the example above, is the string
+"LOG_WRITE_ERROR").</li>
+
+<li>The message identification can be any string of letters, digits and
+underscores, but must not start with a digit.</li>
+
+<li>The rest of the line - from the first non-space character to the
+last non- space character - is the text of the message. There are no
+restrictions on what characters may be in this text, other than they be
+printable (so both single-quote (') and double-quote (") characters are
+allowed). The message text may include replacement tokens (the strings
+"%1", "%2" etc.). When a message is logged, these are replaced with the
+arguments passed to the logging call: %1 refers to the first argument,
+%2 to the second etc. Within the message text, the placeholders can
+appear in any order and placeholders can be repeated. Otherwise, the
+message is printed unmodified.</li>
+
+<li>Remaining lines indicate an explanation for the preceding message.
+The explanation can comprise multiple paragraphs, the paragraphs being
+separated by blank lines. These lines are intended to be processed by a
+separate program to generate an error messages manual; they are ignored
+by the message compiler.</li>
+
+<li>Except when used to separate paragraphs in the message explanation,
+blank lines are ignored.</li>
+</ul>
+
+Although there are few restriction on what can be in the message
+identification and text, there are a number of conventions used by Kea,
+both in the contents of the message and in the usage. All code
+should adhere to these:
+
+<ul>
+<li>Message identifications should include at least one underscore.
+The component before the first underscore is a string indicating the
+origin of the message, and the remainder describes the condition.
+So in the example above, the LOG indicates that the error originated
+from the logging library and the "WRITE_ERROR" indicates that there was
+a problem in a write operation.</li>
+
+<li>The part of the message identification describing the error (e.g.
+"WRITE_ERROR" in the example above) should comprise no more than
+two or three words separated by underscores. An excessive number
+of words or overly long message identification should be avoided;
+such information should be put in the text of the message. For example,
+"RECEIVED_EMPTY_HOSTNAME_FROM_CLIENT" is excessively long,
+"EMPTY_HOSTNAME" being better.</li>
+
+<li>Similarly, the text of the message should be reasonably concise. It should
+include enough information (possibly supplied at run-time in the form of
+parameters) to allow further investigations to be undertaken if required.
+
+Taking the above example, a suitable error message to indicate that the
+resolver has failed to read a name from an upstream authoritative server
+could be:
+
+@code
+% RESOLVER_FETCH_ERROR fetch from %1 failed, error code %2 (%3)
+@endcode
+
+... where %1 indicates the name or IP address of the server to which the
+fetch was sent, %2 the errno value returned and %3 the message associated
+with that error number (retrieved via a call to "strerror()").
+
+</li>
+
+<li>The message should not have a comma after the message identification.
+The message text should neither start with a capital letter (unless
+the first word is a proper noun or is normally written in capitals)
+nor end with a period. The message reporting system takes care of such
+punctuation.</li>
+
+<li>The parameters substituted into the message text should not include
+line breaks. Messages are normally output to the syslog file which
+has the inbuilt assumption of one line per message. Splitting a message
+across multiple lines makes it awkward to search the file for messages
+and associated information.</li>
+
+<li>The message identifier should be unique across the entire Kea
+system. (An error will be reported at system start-up if an identifier
+is repeated.)</li>
+
+<li>A particular message identifier should only be used at one place in
+the Kea code. In this way, if the message indicates a problem, the
+code in question can be quickly identified.</li>
+
+<li>The explanation of the message - the free-form text following the
+message identification - appears in the Kea message manual. It
+should:
+
+<ul>
+<li>Describe the severity of the message (debug, informational etc.)</li>
+
+<li>Expand on the text of the message. In some cases, such as
+debug messages, the message text may provide more or less sufficient
+description. For warnings and errors, the explanation should provide
+sufficient background to the problem to allow a non-developer to
+understand the issue and to begin fault-finding. If possible, the
+explanation should also include suggested remedial action.</li>
+</ul>
+</ul>
+
+@subsection logSourceFiles Produce Source Files
+The message file created in the previous step is then run through the
+message compiler to produce source files that are included in the Kea
+programs.
+
+@subsubsection logMessageCompiler Message Compiler
+The message compiler is a program built in the src/log/compiler directory.
+It is invoked by the command:
+@code
+kea-msg-compiler [-h] [-v] [-d dir] <message-file>
+@endcode
+"-v" prints the version number and exits; "-h" prints brief help text.
+Finally, the "-d" switch directs the compiler to produce the output
+files in the specified directory (the default being the current
+working directory).
+
+<b>C++ Files</b><br/>
+
+<ol>
+<li>A C++ header file (called <message-file-name>.h) holding lines of
+the form:
+@code
+namespace <namespace-name> {
+ extern const isc::log::MessageID LOG_BAD_DESTINATION;
+ extern const isc::log::MessageID LOG_BAD_SEVERITY;
+ :
+}
+@endcode
+The symbols define keys in the global message dictionary, with
+the namespace enclosing the symbols set by the $NAMESPACE directive.
+(This is the reason for the restriction on message identifiers - they
+have to be valid C++ symbol names.)</li>
+
+<li>A C++ source file (called <message-file-name>.cc) that holds the definitions
+of the global symbols and code to insert the symbols and messages into
+an internal dictionary.
+
+Symbols are defined to be equal to strings equal to the identifier, e.g.
+@code
+extern const isc::log::MessageID LOG_BAD_DESTINATION = "LOG_BAD_DESTINATION";
+extern const isc::log::MessageID LOG_BAD_SEVERITY = "LOG_BAD_SEVERITY";
+ :
+@endcode
+(The current implementation allows symbols to be compared. However,
+use of strings should not be assumed - a future implementation may change
+this.) In addition, the file declares an array of identifiers/messages
+and an object to add them to the global dictionary, e.g.:
+@code
+namespace {
+ const char* values[] = {
+ "LOG_BAD_DESTINATION", "unrecognized log destination: %1",
+ "LOG_BAD_SEVERITY", "unrecognized log severity: %1",
+ :
+ NULL
+ };
+ const isc::log::MessageInitializer initializer(values);
+}
+@endcode
+
+The constructor of the @ref isc::log::MessageInitializer object retrieves
+the singleton global @ref isc::log::MessageDictionary object (created
+using standard methods to avoid the "static initialization fiasco") and
+adds each identifier and associated text to it. These constructors are run
+when the program starts; a check is made as each identifier is added and,
+if the identifier already exists in the dictionary, a warning message
+is printed to the main logging output when logging is finally enabled.
+The appearance of such a message indicates a programming error.
+</li>
+</ol>
+
+@subsubsection logMakefile Include Message Compilation in Makefile
+The source file for the messages is the ".mes" file, but the files
+used by the code which must be compiled and linked are the output of
+the message compiler. (The compiler is produced very early on in the
+Kea build sequence, so is available for use in the building of
+subsequent components.) To allow this, certain dependencies must be
+included in the Makefile.am for each component that uses logging.
+
+<b>Including Message files in C++ Component Builds</b><br/>
+The following segment from the "hooks" Makefile.am illustrates
+the entries needed.
+@code
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = hooks_messages.mes
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f hooks_messages.h hooks_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: hooks_messages.h hooks_messages.cc
+ @echo Message files regenerated
+
+hooks_messages.h hooks_messages.cc: hooks_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/hooks/hooks_messages.mes
+
+else
+
+messages hooks_messages.h hooks_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+@endcode
+The first rule adds a hook to make maintainer-clean which is the standard
+way to regenerate all messages files. The second rule adds the new
+messages-clean target to regenerate local messages files.
+
+The GENERATE_MESSAGES conditional part is the (re)generation of
+the messages files. When configured with --enable-generate-messages
+and the source (.mes file) is newer these files are generated using
+the Kea message compiler. When configured without --enable-generate-messages
+(the default) and the source (.mes file) is newer a message is emitted.
+
+Not shown are the Makefile.am lines where the .h and .cc file are used. These
+are the same as other lines specifying .h and .cc source files.
+
+And finally please note the message header (e.g. hooks_messages.h)
+should be installed, i.e. in the include_HEADERS list.
+
+@subsection logUsage Using Logging Files in Program Development
+
+@subsubsection logCppUsage Use in a C++ Program or Module
+To use logging in a C++ program or module:
+
+<ol>
+<li>Build the message header file and source file as described above.</li>
+
+<li>In each C++ file in which logging is to be used, declare a logger
+through which the message will be logged.
+
+@code
+isc::log::Logger logger("name");
+@endcode
+This declaration can be per-function, or it can be declared statically
+in file scope. The string passed to the constructor is the name of
+the logger (it can be any string) and is used when configuring it.
+(Remember though that the name of root logger for the program will be
+prepended to the name chosen. So if, for example, the name "cache"
+is chosen and the model is included in the "kea-resolver" program, the
+full name of the logger will be "kea-resolver.cache".) Loggers with
+the same name share the same configuration. For this reason if, as is
+usual, messages logged in different files in the same component (e.g.
+hooks module, nameserver address store, etc.) originate from loggers
+with the same name, the logger declaration can be placed into a header
+file.</li>
+
+<li>Issue logging calls using supplied macros in "log/macros.h", e.g.
+@code
+LOG_ERROR(logger, LOG_WRITE_ERROR).arg("output.txt");
+LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_LOOKUP_CANCEL).arg(zone);
+@endcode
+All macros (with the exception of LOG_DEBUG) take two arguments:
+the C++ logger object that will be used to log the message, and the
+identification of the message to be logged. LOG_DEBUG takes three
+arguments, the additional one being the debug level associated with
+the message. The .arg() call appended to the end of the LOG_XXX()
+macro handles the arguments to the message. A chain of these is used
+in cases where a message takes multiple arguments, e.g.
+@code
+LOG_DEBUG(nsas_logger, NSAS_DBG_RTT, NSAS_UPDATE_RTT)
+ .arg(addresses_[family][index].getAddress().toText())
+ .arg(old_rtt).arg(new_rtt);
+@endcode
+Using the macros is more efficient than direct calls to the methods on
+the logger class: they avoid the overhead of evaluating the parameters
+to arg() if the logging settings are such that the message is not going
+to be output (e.g. it is a DEBUG message and the logging is set to output
+messages of INFO severity or above).</li>
+
+<li>The main program unit must include a call to isc::log::initLogger()
+(described in more detail below) to set the initial logging severity, debug log
+level, and external message file.
+</ol>
+
+@subsection logInitialization Logging Initialization
+In all cases, if an attempt is made to use a logging method before
+the logging has been initialized, the program will terminate with a
+LoggingNotInitialized exception.
+
+@subsection logInitializationCpp C++ Initialization
+Logging Initialization is carried out by calling @ref
+isc::log::initLogger(). There are two variants to the call, one for
+use by production programs and one for use by unit tests.
+
+@subsubsection logInitializationCppVariant1 Variant #1, Used by Production Programs
+The call that should be used by all production programs is:
+@code
+void isc::log::initLogger(const std::string& root,
+ isc::log::Severity severity = isc::log::INFO,
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
+@endcode
+Arguments are:
+<dl>
+<dt><code>root</code></dt>
+<dd>Name of the root logger. This should be the name of the program
+(e.g. "kea-auth") and is used when configuring logging.</dd>
+
+<dt><code>severity</code></dt>
+<dd>Default severity that the program will start logging with. Although
+this may be overridden when the program obtains its configuration from
+the configuration database, this is the severity that it used until then.
+The logging severity is one of the enum defined in @ref logger.h, i.e.
+
+@code
+isc::log::DEBUG
+isc::log::INFO
+isc::log::WARN
+isc::log::ERROR
+isc::log::FATAL
+isc::log::NONE
+@endcode
+
+(The level NONE may be used to disable logging.)
+</dd>
+
+<dt><code>dbglevel</code></dt>
+<dd>The debug log level is only interpreted when the severity is
+isc::log::DEBUG and is an integer ranging from 0 to 99. 0 should be
+used for the highest-level debug messages and 99 for the lowest-level
+(and typically more verbose) messages.</dd>
+
+<dt><code>file</code></dt>
+<dd>The name of a local message file. This will be read and its
+definitions used to replace the compiled-in text of the messages.
+The default value of NULL indicates that no local message file is
+supplied.</dd>
+
+<dt><code>buffer</code></dt>
+<dd>If set to true, initial log messages will be internally buffered,
+until the first time a logger specification is processed. This
+way the program can use logging before even processing its logging
+configuration. As soon as any specification is processed (even an
+empty one), the buffered log messages will be flushed according to
+the specification. Note that if this option is used, the program
+SHOULD call one of the @ref isc::log::LoggerManager::process() calls.
+If the program exits before this is done, all log messages are dumped
+in a raw format to stdout (so that no messages get lost).</dd>
+</dl>
+
+@subsubsection logInitializationCppVariant2 Variant #2, Used by Unit Tests
+@code
+void isc::log::initLogger()
+@endcode
+This is the call that should be used by unit tests. In this variant,
+all the options are supplied by environment variables: it should not
+be used for production programs to avoid the chance that the program
+operation is affected by inadvertently-defined environment variables. The
+environment variables are:
+
+<dl>
+<dt>KEA_LOGGER_ROOT</dt>
+<dd>Sets the "root" for the unit test. If not defined, the name "kea"
+is used.</dd>
+
+<dt>KEA_LOGGER_SEVERITY</dt>
+<dd>The severity to set for the root logger in the unit test.
+Valid values are "DEBUG", "INFO", "WARN", "ERROR", "FATAL" and "NONE".
+If not defined, "INFO" is used.</dd>
+
+<dt>KEA_LOGGER_DBGLEVEL</dt>
+<dd>If KEA_LOGGER_SEVERITY is set to "DEBUG", the debug level. This can
+be a number between 0 and 99, and defaults to 0.</dd>
+
+<dt>KEA_LOGGER_LOCALMSG</dt>
+<dd>If defined, points to a local message file. The default is not to
+use a local message file.</dd>
+
+<dt>KEA_LOGGER_DESTINATION</dt>
+<dd>The location to which log message are written. This can be one of:
+<ul>
+<li><b>stdout</b> Message are written to stdout.</li>
+<li><b>stderr</b> Messages are written to stderr.</li>
+<li><b>syslog[:facility]</b> Messages are written to syslog. If the
+optional "facility" is used, the messages are written using that facility.
+(This defaults to "local0" if not specified.)</li>
+<li><b>Anything else</b> Interpreted as the name of a file to which
+output is appended. If the file does not exist, a new one is opened.</li>
+</ul>
+In the case of "stdout", "stderr" and "syslog", they must be written exactly
+as is - no leading or trailing spaces, and in lower-case.</dd>
+</dl>
+
+@subsection logInitializationHooks Hooks Specific Notes
+All hooks libraries should use Kea logging mechanisms. The loggers and the
+library specific log messages are created in the same way as for the core
+Kea modules. The loggers created within the hook library belong to the
+logging hierarchy of the Kea process and their configuration can be
+controlled from the Kea configuration file. If the configuration file doesn't
+contain the specific configuration for the logger used in the library,
+this logger is given the configuration of the root Kea logger.
+
+The hook libraries are loaded dynamically. This requires that the global log
+messages dictionary, holding the mapping of specific log message
+identifiers to the actual messages, is updated to include the messages
+specified in the hook library when the library is loaded. Conversely, the
+messages have to be removed from the dictionary when the library is unloaded.
+
+The new messages are added to the global dictionary using the
+@ref isc::log::MessageInitializer::loadDictionary static function. It is
+called by the @ref isc::hooks::LibraryManager::loadLibrary for each loaded
+library.
+
+When the library is unloaded, the instance of the
+@ref isc::log::MessageInitializer defined in the library is destroyed
+and its destructor removes the messages registered by the destroyed
+instance from the global dictionary.
+
+The hook library itself must not perform any action to register or
+unregister log messages in the global dictionary!
+
+@section logNotes Notes on the Use of Logging
+One thing that should always be kept in mind is whether the logging
+could be used as a means for a DOS attack. For example, if a warning
+message is logged every time an invalid packet is received, an attacker
+could simply send large numbers of invalid packets. Of course, warnings
+could be disabled (or just warnings for that that particular logger),
+but nevertheless the message is an attack vector. As a general rule,
+if the message can be triggered by a user action, it can be used as an
+attack vector.
+
+There are two approaches to get round this:
+<ol>
+<li>Log messages generated by such user actions as DEBUG messages. DEBUG
+is not enabled by default, so these events will not be recorded unless
+DEBUG is specifically enabled. Choosing a suitable debug level for
+such messages will select only those messages and not the more general
+debug messages.</li>
+
+<li>Record system-related and packet-related messages via different
+loggers. As the loggers are independent and the severity levels
+independent, fine-tuning of what and what is not recorded can be achieved.</li>
+
+</ol>
+
+@section logMTConsiderations Multi-Threading Consideration for Logging
+
+Logging is thread safe: messages emitted at the same time do not mix.
+
+When the KEA_LOCKFILE_DIR environment variable is not set to none
+Logging to files is multi-process safe too: for instance two servers
+can be configured to put log messages in the same file.
+
+The @c isc::log::Logger class initializes its implementation in a lazy
+(i.e. on demand) but thread safe way so it is always initialized at most
+once even in a multi-threaded environment.
+
+*/
diff --git a/src/lib/log/logimpl_messages.cc b/src/lib/log/logimpl_messages.cc
new file mode 100644
index 0000000..12a6442
--- /dev/null
+++ b/src/lib/log/logimpl_messages.cc
@@ -0,0 +1,29 @@
+// File created from ../../../src/lib/log/logimpl_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID LOGIMPL_ABOVE_MAX_DEBUG = "LOGIMPL_ABOVE_MAX_DEBUG";
+extern const isc::log::MessageID LOGIMPL_BAD_DEBUG_STRING = "LOGIMPL_BAD_DEBUG_STRING";
+extern const isc::log::MessageID LOGIMPL_BELOW_MIN_DEBUG = "LOGIMPL_BELOW_MIN_DEBUG";
+
+} // namespace log
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "LOGIMPL_ABOVE_MAX_DEBUG", "debug level of %1 is too high and will be set to the maximum of %2",
+ "LOGIMPL_BAD_DEBUG_STRING", "debug string '%1' has invalid format",
+ "LOGIMPL_BELOW_MIN_DEBUG", "debug level of %1 is too low and will be set to the minimum of %2",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/log/logimpl_messages.h b/src/lib/log/logimpl_messages.h
new file mode 100644
index 0000000..e0813d6
--- /dev/null
+++ b/src/lib/log/logimpl_messages.h
@@ -0,0 +1,18 @@
+// File created from ../../../src/lib/log/logimpl_messages.mes
+
+#ifndef LOGIMPL_MESSAGES_H
+#define LOGIMPL_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID LOGIMPL_ABOVE_MAX_DEBUG;
+extern const isc::log::MessageID LOGIMPL_BAD_DEBUG_STRING;
+extern const isc::log::MessageID LOGIMPL_BELOW_MIN_DEBUG;
+
+} // namespace log
+} // namespace isc
+
+#endif // LOGIMPL_MESSAGES_H
diff --git a/src/lib/log/logimpl_messages.mes b/src/lib/log/logimpl_messages.mes
new file mode 100644
index 0000000..8af43fc
--- /dev/null
+++ b/src/lib/log/logimpl_messages.mes
@@ -0,0 +1,35 @@
+# 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/.
+
+# \brief Logger Implementation Messages
+#
+# This holds messages generated by the underlying logger implementation. They
+# are likely to be specific to that implementation, and may well change if the
+# underlying implementation is changed. For that reason, they have been put
+# in a separate file.
+
+$NAMESPACE isc::log
+
+% LOGIMPL_ABOVE_MAX_DEBUG debug level of %1 is too high and will be set to the maximum of %2
+A message from the interface to the underlying logger implementation reporting
+that the debug level (as set by an internally-created string DEBUGn, where n
+is an integer, e.g. DEBUG22) is above the maximum allowed value and has
+been reduced to that value. The appearance of this message may indicate
+a programming error - please submit a bug report.
+
+% LOGIMPL_BAD_DEBUG_STRING debug string '%1' has invalid format
+A message from the interface to the underlying logger implementation
+reporting that an internally-created string used to set the debug level
+is not of the correct format (it should be of the form DEBUGn, where n
+is an integer, e.g. DEBUG22). The appearance of this message indicates
+a programming error - please submit a bug report.
+
+% LOGIMPL_BELOW_MIN_DEBUG debug level of %1 is too low and will be set to the minimum of %2
+A message from the interface to the underlying logger implementation reporting
+that the debug level (as set by an internally-created string DEBUGn, where n
+is an integer, e.g. DEBUG22) is below the minimum allowed value and has
+been increased to that value. The appearance of this message may indicate
+a programming error - please submit a bug report.
diff --git a/src/lib/log/macros.h b/src/lib/log/macros.h
new file mode 100644
index 0000000..f8336d6
--- /dev/null
+++ b/src/lib/log/macros.h
@@ -0,0 +1,43 @@
+// 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/.
+
+#ifndef LOG_MACROS_H
+#define LOG_MACROS_H
+
+#include <log/logger.h>
+#include <log/log_dbglevels.h>
+
+/// \brief Macro to conveniently test debug output and log it
+#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE) \
+ if (!(LOGGER).isDebugEnabled((LEVEL))) { \
+ } else \
+ (LOGGER).debug((LEVEL), (MESSAGE))
+
+/// \brief Macro to conveniently test info output and log it
+#define LOG_INFO(LOGGER, MESSAGE) \
+ if (!(LOGGER).isInfoEnabled()) { \
+ } else \
+ (LOGGER).info((MESSAGE))
+
+/// \brief Macro to conveniently test warn output and log it
+#define LOG_WARN(LOGGER, MESSAGE) \
+ if (!(LOGGER).isWarnEnabled()) { \
+ } else \
+ (LOGGER).warn((MESSAGE))
+
+/// \brief Macro to conveniently test error output and log it
+#define LOG_ERROR(LOGGER, MESSAGE) \
+ if (!(LOGGER).isErrorEnabled()) { \
+ } else \
+ (LOGGER).error((MESSAGE))
+
+/// \brief Macro to conveniently test fatal output and log it
+#define LOG_FATAL(LOGGER, MESSAGE) \
+ if (!(LOGGER).isFatalEnabled()) { \
+ } else \
+ (LOGGER).fatal((MESSAGE))
+
+#endif
diff --git a/src/lib/log/message_dictionary.cc b/src/lib/log/message_dictionary.cc
new file mode 100644
index 0000000..5271e46
--- /dev/null
+++ b/src/lib/log/message_dictionary.cc
@@ -0,0 +1,121 @@
+// Copyright (C) 2011-2022 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 <cstddef>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Constructor
+
+MessageDictionary::MessageDictionary() : dictionary_(), empty_("") {
+}
+
+// (Virtual) Destructor
+
+MessageDictionary::~MessageDictionary() {
+}
+
+// Add message and note if ID already exists
+
+bool
+MessageDictionary::add(const std::string& ident, const std::string& text) {
+ Dictionary::iterator i = dictionary_.find(ident);
+ bool not_found = (i == dictionary_.end());
+ if (not_found) {
+
+ // Message not already in the dictionary, so add it.
+ dictionary_[ident] = text;
+ }
+
+ return (not_found);
+}
+
+// Add message and note if ID does not already exist
+
+bool
+MessageDictionary::replace(const std::string& ident, const std::string& text) {
+ Dictionary::iterator i = dictionary_.find(ident);
+ bool found = (i != dictionary_.end());
+ if (found) {
+
+ // Exists, so replace it.
+ dictionary_[ident] = text;
+ }
+
+ return (found);
+}
+
+bool
+MessageDictionary::erase(const std::string& ident, const std::string& text) {
+ Dictionary::iterator mes = dictionary_.find(ident);
+ // Both the ID and the text must match.
+ bool found = (mes != dictionary_.end() && (mes->second == text));
+ if (found) {
+ dictionary_.erase(mes);
+ }
+ return (found);
+}
+
+// Load a set of messages
+
+vector<std::string>
+MessageDictionary::load(const char* messages[]) {
+ vector<std::string> duplicates;
+ int i = 0;
+ while (messages[i]) {
+ // ID present, so point to text.
+ ++i;
+ if (messages[i]) {
+ const MessageID ident(messages[i - 1]);
+ // Text not null, note it and point to next ident.
+ const std::string text(messages[i]);
+ ++i;
+
+ // Add ID and text to message dictionary, noting if the ID was
+ // already present.
+ bool added = add(ident, text);
+ if (!added) {
+ duplicates.push_back(boost::lexical_cast<string>(ident));
+ }
+ }
+ }
+ return (duplicates);
+}
+
+// Return message text or blank string. A reference is returned to a string
+// in the dictionary - this is fine, as the string is immediately used for
+// output.
+
+const string&
+MessageDictionary::getText(const std::string& ident) const {
+ Dictionary::const_iterator i = dictionary_.find(ident);
+ if (i == dictionary_.end()) {
+ return (empty_);
+ } else {
+ return (i->second);
+ }
+}
+
+// Return global dictionary
+
+const MessageDictionaryPtr&
+MessageDictionary::globalDictionary() {
+ static MessageDictionaryPtr global(new MessageDictionary());
+ return (global);
+}
+
+
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/message_dictionary.h b/src/lib/log/message_dictionary.h
new file mode 100644
index 0000000..6afc0f0
--- /dev/null
+++ b/src/lib/log/message_dictionary.h
@@ -0,0 +1,208 @@
+// Copyright (C) 2011-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/.
+
+#ifndef MESSAGE_DICTIONARY_H
+#define MESSAGE_DICTIONARY_H
+
+#include <cstddef>
+#include <string>
+#include <map>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Forward declaration of \c MessageDictionary
+class MessageDictionary;
+
+/// \brief Shared pointer to the \c MessageDictionary.
+typedef boost::shared_ptr<MessageDictionary> MessageDictionaryPtr;
+
+/// \brief Message Dictionary
+///
+/// The message dictionary is a wrapper around a std::map object, and allows
+/// message text to be retrieved given the string identification.
+///
+/// Adding text occurs in two modes:
+///
+/// Through the "Add" method, ID/text mappings are added to the dictionary
+/// unless the ID already exists. This is designed for use during program
+/// initialization, where a local message may supplant a compiled-in message.
+///
+/// Through the "Replace" method, ID/text mappings are added to the dictionary
+/// only if the ID already exists. This is for use when a message file is
+/// supplied to replace messages provided with the program.
+///
+/// Although the class can be used stand-alone, it does supply a static method
+/// to return a particular instance - the "global" dictionary.
+
+class MessageDictionary {
+public:
+
+ typedef std::map<std::string, std::string> Dictionary;
+ typedef Dictionary::const_iterator const_iterator;
+
+ /// \brief Constructor
+ MessageDictionary();
+
+ /// \brief Virtual Destructor
+ virtual ~MessageDictionary();
+
+ /// \brief Add Message
+ ///
+ /// Adds a message to the dictionary. If the ID already exists, the ID is
+ /// added to the overflow vector.
+ ///
+ /// \param ident Identification of the message to add
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message existed and it was not added.
+ virtual bool add(const MessageID& ident, const std::string& text) {
+ return (add(boost::lexical_cast<std::string>(ident), text));
+ }
+
+ /// \brief Add Message
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Identification of the message to add
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message existed and it was not added.
+ virtual bool add (const std::string& ident, const std::string& text);
+
+
+ /// \brief Replace Message
+ ///
+ /// Replaces a message in the dictionary. If the ID does not exist, it is
+ /// added to the overflow vector.
+ ///
+ /// \param ident Identification of the message to replace
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message did not exist and it was not added.
+ virtual bool replace(const MessageID& ident, const std::string& text) {
+ return (replace(boost::lexical_cast<std::string>(ident), text));
+ }
+
+ /// \brief Replace Message
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Identification of the message to replace
+ /// \param text Message text
+ ///
+ /// \return true if the message was added to the dictionary, false if the
+ /// message did not exist and it was not added.
+ virtual bool replace(const std::string& ident, const std::string& text);
+
+
+ /// \brief Removes the specified message from the dictionary.
+ ///
+ /// Checks if both the message identifier and the text match the message
+ /// in the dictionary before removal. If the text doesn't match it is
+ /// an indication that the message which removal is requested is a
+ /// duplicate of another message. This may occur when two Kea modules
+ /// register messages with the same identifier. When one of the modules
+ /// is unloaded and the relevant messages are unregistered, there is a
+ /// need to make sure that the message registered by the other module
+ /// is not accidentally removed. Hence, the additional check for the
+ /// text match is needed.
+ ///
+ /// \param ident Identification of the message to remove.
+ /// \param text Message text
+ ///
+ /// \return true of the message has been removed, false if the message
+ /// couldn't be found.
+ virtual bool erase(const std::string& ident, const std::string& text);
+
+ /// \brief Load Dictionary
+ ///
+ /// Designed to be used during the initialization of programs, this
+ /// accepts a set of (ID, text) pairs as a one-dimensional array of
+ /// const char* and adds them to the dictionary. The messages are added
+ /// using "Add".
+ ///
+ /// \param elements null-terminated array of const char* alternating ID and
+ /// message text. This should be an odd number of elements long, the last
+ /// element being NULL. If it is an even number of elements long, the
+ /// last ID is ignored.
+ ///
+ /// \return Vector of message IDs that were not loaded because an ID of the
+ /// same name already existing in the dictionary. This vector may be
+ /// empty.
+ virtual std::vector<std::string> load(const char* elements[]);
+
+ /// \brief Get Message Text
+ ///
+ /// Given an ID, retrieve associated message text.
+ ///
+ /// \param ident Message identification
+ ///
+ /// \return Text associated with message or empty string if the ID is not
+ /// recognized. (Note: this precludes an ID being associated with an empty
+ /// string.)
+ virtual const std::string& getText(const MessageID& ident) const {
+ return(getText(boost::lexical_cast<std::string>(ident)));
+ }
+
+
+ /// \brief Get Message Text
+ ///
+ /// Alternate signature.
+ ///
+ /// \param ident Message identification
+ ///
+ /// \return Text associated with message or empty string if the ID is not
+ /// recognized. (Note: this precludes an ID being associated with an empty
+ /// string.)
+ virtual const std::string& getText(const std::string& ident) const;
+
+
+ /// \brief Number of Items in Dictionary
+ ///
+ /// \return Number of items in the dictionary
+ virtual size_t size() const {
+ return (dictionary_.size());
+ }
+
+
+ /// \brief Return begin() iterator of internal map
+ const_iterator begin() const {
+ return (dictionary_.begin());
+ }
+
+
+ /// \brief Return end() iterator of internal map
+ const_iterator end() const {
+ return (dictionary_.end());
+ }
+
+
+ /// \brief Return Global Dictionary
+ ///
+ /// Returns a pointer to the singleton global dictionary.
+ ///
+ /// \return Pointer to global dictionary.
+ static const MessageDictionaryPtr& globalDictionary();
+
+private:
+ Dictionary dictionary_; ///< Holds the ID to text lookups
+ const std::string empty_; ///< Empty string
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // MESSAGE_DICTIONARY_H
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
new file mode 100644
index 0000000..f5c77cf
--- /dev/null
+++ b/src/lib/log/message_exception.h
@@ -0,0 +1,112 @@
+// Copyright (C) 2011-2017 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_EXCEPTION_H
+#define MESSAGE_EXCEPTION_H
+
+#include <exceptions/exceptions.h>
+#include <log/message_types.h>
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Exception
+///
+/// Used in the message reader, this simple exception class allows a message
+/// code and its arguments to be encapsulated in an exception and thrown
+/// up the stack.
+
+class MessageException : public isc::Exception {
+public:
+
+ /// \brief Constructor
+ ///
+ /// \param file Filename where the exception occurred.
+ /// \param line Line where exception occurred.
+ /// \param what Text description of the problem.
+ /// \param id Message identification.
+ /// \param lineno Line number on which error occurred (if > 0).
+ MessageException(const char* file, size_t line, const char* what,
+ MessageID id, int lineno)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
+ {
+ if (lineno_ > 0) {
+ args_.push_back(boost::lexical_cast<std::string>(lineno));
+ }
+ }
+
+ /// \brief Constructor
+ ///
+ /// \param file Filename where the exception occurred.
+ /// \param line Line where exception occurred.
+ /// \param what Text description of the problem.
+ /// \param id Message identification.
+ /// \param arg1 First message argument.
+ /// \param lineno Line number on which error occurred (if > 0).
+ MessageException(const char* file, size_t line, const char* what,
+ MessageID id, const std::string& arg1, int lineno)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
+ {
+ if (lineno > 0) {
+ args_.push_back(boost::lexical_cast<std::string>(lineno));
+ }
+ args_.push_back(arg1);
+ }
+
+ /// \brief Constructor
+ ///
+ /// \param file Filename where the exception occurred.
+ /// \param line Line where exception occurred.
+ /// \param what Text description of the problem.
+ /// \param id Message identification.
+ /// \param arg1 First message argument.
+ /// \param arg2 Second message argument.
+ /// \param lineno Line number on which error occurred (if > 0).
+ MessageException(const char* file, size_t line, const char *what,
+ MessageID id, const std::string& arg1,
+ const std::string& arg2, int lineno)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
+ {
+ if (lineno > 0) {
+ args_.push_back(boost::lexical_cast<std::string>(lineno));
+ }
+ args_.push_back(arg1);
+ args_.push_back(arg2);
+ }
+
+ /// \brief Destructor
+ ~MessageException() {}
+
+ /// \brief Return Message ID
+ ///
+ /// \return Message identification
+ MessageID id() const {
+ return id_;
+ }
+
+ /// \brief Return Arguments
+ ///
+ /// \return Exception Arguments
+ std::vector<std::string> arguments() const {
+ return (args_);
+ }
+
+private:
+ MessageID id_; // Exception ID
+ std::vector<std::string> args_; // Exception arguments
+ int lineno_;
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // MESSAGE_EXCEPTION_H
diff --git a/src/lib/log/message_initializer.cc b/src/lib/log/message_initializer.cc
new file mode 100644
index 0000000..a7c85ed
--- /dev/null
+++ b/src/lib/log/message_initializer.cc
@@ -0,0 +1,137 @@
+// 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 <log/message_dictionary.h>
+#include <log/message_initializer.h>
+#include <algorithm>
+#include <cassert>
+#include <cstdlib>
+
+
+namespace {
+
+using namespace isc::log;
+
+/// @brief Returns the shared pointer to the list of pointers to the
+/// log messages defined.
+///
+/// The returned pointer must be held in the \c MessageInitializer object
+/// throughout its lifetime to make sure that the object doesn't outlive
+/// the list and may still access it in the destructor. The returned
+/// pointer is shared between all \c MessageInitializer instances.
+LoggerValuesListPtr
+getNonConstLoggerValues() {
+ static LoggerValuesListPtr logger_values(new LoggerValuesList());
+ return (logger_values);
+}
+
+/// @brief Returns the pointer to the list of message duplicates.
+///
+/// The returned pointer must be held in the \c MessageInitializer object
+/// throughout its lifetime to make sure that the object doesn't outlive
+/// the list and may still access it in the destructor. The returned
+/// pointer is shared between all \c MessageInitializer instances.
+LoggerDuplicatesListPtr
+getNonConstDuplicates() {
+ static LoggerDuplicatesListPtr duplicates(new LoggerDuplicatesList());
+ return (duplicates);
+}
+} // end unnamed namespace
+
+
+namespace isc {
+namespace log {
+
+MessageInitializer::MessageInitializer(const char* values[])
+ : values_(values),
+ global_dictionary_(MessageDictionary::globalDictionary()),
+ global_logger_values_(getNonConstLoggerValues()),
+ global_logger_duplicates_(getNonConstDuplicates()) {
+ global_logger_values_->push_back(values);
+}
+
+MessageInitializer::~MessageInitializer() {
+ // Search for the pointer to pending messages belonging to our instance.
+ LoggerValuesList::iterator my_messages = std::find(global_logger_values_->begin(),
+ global_logger_values_->end(),
+ values_);
+ bool pending = (my_messages != global_logger_values_->end());
+ // Our messages are still pending, so let's remove them from the list
+ // of pending messages.
+ if (pending) {
+ global_logger_values_->erase(my_messages);
+
+ } else {
+ // Our messages are not pending, so they might have been loaded to
+ // the dictionary and/or duplicates.
+ int i = 0;
+ while (values_[i]) {
+ // Check if the unloaded message is registered as duplicate. If it is,
+ // remove it from the duplicates list.
+ LoggerDuplicatesList::iterator dup =
+ std::find(global_logger_duplicates_->begin(),
+ global_logger_duplicates_->end(),
+ values_[i]);
+ if (dup != global_logger_duplicates_->end()) {
+ global_logger_duplicates_->erase(dup);
+
+ } else {
+ global_dictionary_->erase(values_[i], values_[i + 1]);
+ }
+ i += 2;
+ }
+ }
+}
+
+// Return the number of arrays registered but not yet loaded.
+
+size_t
+MessageInitializer::getPendingCount() {
+ return (getNonConstLoggerValues()->size());
+}
+
+// Load the messages in the arrays registered in the logger_values array
+// into the global dictionary.
+
+void
+MessageInitializer::loadDictionary(bool ignore_duplicates) {
+ const MessageDictionaryPtr& global = MessageDictionary::globalDictionary();
+ const LoggerValuesListPtr& logger_values = getNonConstLoggerValues();
+
+ for (LoggerValuesList::const_iterator values = logger_values->begin();
+ values != logger_values->end(); ++values) {
+ std::vector<std::string> repeats = global->load(*values);
+
+ // Append the IDs in the list just loaded (the "repeats") to the
+ // global list of duplicate IDs.
+ if (!ignore_duplicates && !repeats.empty()) {
+ const LoggerDuplicatesListPtr& duplicates = getNonConstDuplicates();
+ duplicates->insert(duplicates->end(), repeats.begin(), repeats.end());
+ }
+ }
+
+ // ... and mark that the messages have been loaded. (This avoids a lot
+ // of "duplicate message ID" messages in some of the unit tests where the
+ // logging initialization code may be called multiple times.)
+ logger_values->clear();
+}
+
+// Return reference to duplicates vector
+const std::list<std::string>&
+MessageInitializer::getDuplicates() {
+ return (*getNonConstDuplicates());
+}
+
+// Clear the duplicates vector
+void
+MessageInitializer::clearDuplicates() {
+ getNonConstDuplicates()->clear();
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/message_initializer.h b/src/lib/log/message_initializer.h
new file mode 100644
index 0000000..7f40f9b
--- /dev/null
+++ b/src/lib/log/message_initializer.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2011-2022 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 MESSAGEINITIALIZER_H
+#define MESSAGEINITIALIZER_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <cstdlib>
+#include <list>
+#include <string>
+
+namespace isc {
+namespace log {
+
+// Declare the MessageDictionary class to allow a pointer to it to be defined.
+class MessageDictionary;
+
+/// @name Type definitions for containers shared among instances of the class.
+///
+//\{
+/// \brief List of pointers to the messages.
+typedef std::list<const char**> LoggerValuesList;
+/// \brief Shared pointer to the list of pointers to the messages.
+typedef boost::shared_ptr<LoggerValuesList> LoggerValuesListPtr;
+
+/// \brief List of duplicated messages.
+typedef std::list<std::string> LoggerDuplicatesList;
+/// \brief Shared pointer to the list of duplicated messages.
+typedef boost::shared_ptr<LoggerDuplicatesList> LoggerDuplicatesListPtr;
+//\}
+
+/// \brief Initialize Message Dictionary
+///
+/// This is a helper class to add a set of message IDs and associated text to
+/// the global dictionary.
+///
+/// It should be declared outside an execution unit and initialized with
+/// an array of values, alternating identifier, associated text and ending with
+/// a NULL, e.g.
+///
+/// static const char* values[] = {
+/// "IDENT1", "message for ident 1",
+/// "IDENT2", "message for ident 2",
+/// :
+/// NULL
+/// };
+/// MessageInitializer xyz(values);
+///
+/// All that needed is for the module containing the definitions to be
+/// included in the execution unit.
+///
+/// Dynamically loaded modules should call the initializer as well on the
+/// moment they are instantiated.
+///
+/// To avoid static initialization fiasco problems, the containers shared by
+/// all instances of this class are dynamically allocated on first use, and
+/// held in the smart pointers which are de-allocated only when all instances
+/// of the class are destructed. After the object has been created with the
+/// constructor, the \c MessageInitializer::loadDictionary static function is
+/// called to populate the messages defined in various instances of the
+/// \c MessageInitializer class to the global dictionary.
+///
+/// When messages are added to the dictionary, the are added via the
+/// MessageDictionary::add() method, so any duplicates are stored in the
+/// global dictionary's overflow lince whence they can be retrieved at
+/// run-time.
+
+class MessageInitializer : public boost::noncopyable {
+public:
+
+ /// \brief Constructor
+ ///
+ /// Adds a pointer to the array of messages to the global array of
+ /// pointers to message arrays.
+ ///
+ /// \param values NULL-terminated array of alternating identifier strings
+ /// and associated message text. N.B. This object stores a pointer to the
+ /// passed array; the array MUST remain valid at least until
+ /// loadDictionary() has been called.
+ MessageInitializer(const char* values[]);
+
+ /// \brief Destructor
+ ///
+ /// Removes pending messages from the array or loaded messages from the
+ /// global dictionary.
+ ///
+ /// If the messages initialized with the destructed have already been
+ /// loaded to the global dictionary the destructor will remove these
+ /// messages and preserve messages loaded by other instances of the
+ /// \c MessageInitializer. If there are any duplicates, only the instance
+ /// of the duplicated message initialized by the destructed object will
+ /// be removed.
+ ~MessageInitializer();
+
+ /// \brief Obtain pending load count
+ ///
+ /// Returns the number of message arrays that will be loaded by the next
+ /// call to loadDictionary().
+ ///
+ /// \return Number of registered message arrays. This is reset to zero
+ /// when loadDictionary() is called.
+ static size_t getPendingCount();
+
+ /// \brief Run-Time Initialization
+ ///
+ /// Loops through the internal array of pointers to message arrays
+ /// and adds the messages to the internal dictionary. This is called
+ /// during run-time initialization.
+ ///
+ /// \param ignore_duplicates If true, duplicate IDs, and IDs already
+ /// loaded, are ignored instead of stored in the global duplicates
+ /// list.
+ static void loadDictionary(bool ignore_duplicates = false);
+
+ /// \brief Return Duplicates
+ ///
+ /// When messages are added to the global dictionary, any duplicates are
+ /// recorded. They can later be output through the logging system.
+ ///
+ /// \return List of duplicate message IDs when the global dictionary was
+ /// loaded. Note that the duplicates list itself may contain duplicates.
+ static const std::list<std::string>& getDuplicates();
+
+ /// \brief Clear the static duplicates list
+ ///
+ /// Empties the list returned by getDuplicates()
+ static void clearDuplicates();
+
+private:
+
+ /// \brief Holds the pointer to the array of messages.
+ const char** values_;
+
+ /// \brief Holds the pointer to the global dictionary.
+ ///
+ /// One or more instances of \c MessageInitializer are created statically,
+ /// the \c MessageDictionary being created by the first one to run. As the
+ /// \c MessageDictionary is also accessed by the \c MessageInitializer
+ /// destructor, a smart pointer to it is kept. This avoids the possibility
+ /// that, during shutdown, the \c MessageDictionary is destroyed before all
+ /// instances of \c MessageInitializer.
+ boost::shared_ptr<MessageDictionary> global_dictionary_;
+
+ /// \brief Holds the shared pointer to the list of pointers to the
+ /// log messages defined by various instances of this class.
+ ///
+ /// This pointer must be initialized in the constructor and held
+ /// throughout the lifetime of the \c MessageInitializer object. This
+ /// prevents static deinitialization fiasco when trying to access the
+ /// values in the list from the destructor of this class.
+ LoggerValuesListPtr global_logger_values_;
+
+ /// \brief Holds the shared pointer to the collection od duplicated
+ /// messages.
+ ///
+ /// This pointer must be initialized in the constructor and held
+ /// throughout the lifetime of the \c MessageInitializer object. This
+ /// prevents static deinitialization fiasco when trying to access the
+ /// values in the list from the destructor of this class.
+ LoggerDuplicatesListPtr global_logger_duplicates_;
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // MESSAGEINITIALIZER_H
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
new file mode 100644
index 0000000..2b48608
--- /dev/null
+++ b/src/lib/log/message_reader.cc
@@ -0,0 +1,279 @@
+// Copyright (C) 2011-2022 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 <errno.h>
+#include <string.h>
+
+#include <iostream>
+#include <fstream>
+
+#include <exceptions/isc_assert.h>
+#include <log/log_messages.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+#include <util/strutil.h>
+
+using namespace std;
+
+namespace {
+const char DIRECTIVE_FLAG = '$'; // Starts each directive
+const char MESSAGE_FLAG = '%'; // Starts each message
+}
+
+
+namespace isc {
+namespace log {
+
+// Read the file.
+
+void
+MessageReader::readFile(const string& file, MessageReader::Mode mode) {
+
+ // Ensure the non-added collection is empty: we could be re-using this
+ // object.
+ not_added_.clear();
+
+ // Open the file.
+ ifstream infile(file.c_str());
+ if (infile.fail()) {
+ isc_throw_4(MessageException, "Failed to open message file",
+ LOG_INPUT_OPEN_FAIL, file, strerror(errno), 0);
+ }
+
+ // Loop round reading it. As we process the file one line at a time,
+ // keep a track of line number of aid diagnosis of problems.
+ string line;
+ getline(infile, line);
+ lineno_ = 0;
+
+ while (infile.good()) {
+ ++lineno_;
+ processLine(line, mode);
+ getline(infile, line);
+ }
+
+ // Why did the loop terminate?
+ if (!infile.eof()) {
+ isc_throw_4(MessageException, "Error reading message file",
+ LOG_READ_ERROR, file, strerror(errno), 0);
+ }
+ infile.close();
+}
+
+// Parse a line of the file.
+
+void
+MessageReader::processLine(const string& line, MessageReader::Mode mode) {
+
+ // Get rid of leading and trailing spaces
+ string text = isc::util::str::trim(line);
+
+ if (text.empty()) {
+ ; // Ignore blank lines
+
+ } else if (text[0] == DIRECTIVE_FLAG) {
+ parseDirective(text); // Process directives
+
+
+ } else if (text[0] == MESSAGE_FLAG) {
+ parseMessage(text, mode); // Process message definition line
+
+ } else {
+ ; // Other lines are extended message
+ // description so are ignored
+ }
+}
+
+// Process directive
+
+void
+MessageReader::parseDirective(const std::string& text) {
+
+
+ // Break into tokens
+ vector<string> tokens = isc::util::str::tokens(text);
+
+ // Uppercase directive and branch on valid ones
+ isc::util::str::uppercase(tokens[0]);
+ if (tokens[0] == string("$PREFIX")) {
+ parsePrefix(tokens);
+
+ } else if (tokens[0] == string("$NAMESPACE")) {
+ parseNamespace(tokens);
+
+ } else {
+
+ // Unrecognized directive
+ isc_throw_3(MessageException, "Unrecognized directive",
+ LOG_UNRECOGNIZED_DIRECTIVE, tokens[0],
+ lineno_);
+ }
+}
+
+// Process $PREFIX
+void
+MessageReader::parsePrefix(const vector<string>& tokens) {
+
+ // Should not get here unless there is something in the tokens array.
+ isc_throw_assert(!tokens.empty());
+
+ // Process $PREFIX. With no arguments, the prefix is set to the empty
+ // string. One argument sets the prefix to the to its value and more than
+ // one argument is invalid.
+ if (tokens.size() == 1) {
+ prefix_ = "";
+
+ } else if (tokens.size() == 2) {
+ prefix_ = tokens[1];
+
+ // Token is potentially valid providing it only contains alphabetic
+ // and numeric characters (and underscores) and does not start with a
+ // digit.
+ if (invalidSymbol(prefix_)) {
+ isc_throw_3(MessageException, "Invalid prefix",
+ LOG_PREFIX_INVALID_ARG, prefix_, lineno_);
+ }
+
+ } else {
+
+ // Too many arguments
+ isc_throw_2(MessageException, "Too many arguments",
+ LOG_PREFIX_EXTRA_ARGS, lineno_);
+ }
+}
+
+// Check if string is an invalid C++ symbol. It is valid if comprises only
+// alphanumeric characters and underscores, and does not start with a digit.
+// (Owing to the logic of the rest of the code, we check for its invalidity,
+// not its validity.)
+bool
+MessageReader::invalidSymbol(const string& symbol) {
+ static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789_";
+ return ( symbol.empty() ||
+ (symbol.find_first_not_of(valid_chars) != string::npos) ||
+ (std::isdigit(symbol[0])));
+}
+
+// Process $NAMESPACE. A lot of the processing is similar to that of $PREFIX,
+// except that only limited checks will be done on the namespace (to avoid a
+// lot of parsing and separating out of the namespace components.) Also, unlike
+// $PREFIX, there can only be one $NAMESPACE in a file.
+
+void
+MessageReader::parseNamespace(const vector<string>& tokens) {
+
+ // Check argument count
+ if (tokens.size() < 2) {
+ isc_throw_2(MessageException, "No arguments", LOG_NAMESPACE_NO_ARGS,
+ lineno_);
+
+ } else if (tokens.size() > 2) {
+ isc_throw_2(MessageException, "Too many arguments",
+ LOG_NAMESPACE_EXTRA_ARGS, lineno_);
+
+ }
+
+ // Token is potentially valid providing it only contains alphabetic
+ // and numeric characters (and underscores and colons). As noted above,
+ // we won't be exhaustive - after all, and code containing the resultant
+ // namespace will have to be compiled, and the compiler will catch errors.
+ static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789_:";
+ if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
+ isc_throw_3(MessageException, "Invalid argument",
+ LOG_NAMESPACE_INVALID_ARG, tokens[1], lineno_);
+ }
+
+ // All OK - unless the namespace has already been set.
+ if (ns_.size() != 0) {
+ isc_throw_2(MessageException, "Duplicate namespace",
+ LOG_DUPLICATE_NAMESPACE, lineno_);
+ }
+
+ // Prefix has not been set, so set it and return success.
+ ns_ = tokens[1];
+}
+
+// Process message. By the time this method is called, the line has been
+// stripped of leading and trailing spaces. The first character of the string
+// is the message introducer, so we can get rid of that. The remainder is
+// a line defining a message.
+//
+// The first token on the line, when concatenated to the prefix and converted to
+// upper-case, is the message ID. The first of the line from the next token
+// on is the message text.
+
+void
+MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
+
+ static string delimiters("\t\n "); // Delimiters
+
+ // The line passed should be at least one character long and start with the
+ // message introducer (else we should not have got here).
+ isc_throw_assert((text.size() >= 1) && (text[0] == MESSAGE_FLAG));
+
+ // A line comprising just the message introducer is not valid.
+ if (text.size() == 1) {
+ isc_throw_3(MessageException, "No message ID", LOG_NO_MESSAGE_ID,
+ text, lineno_);
+ }
+
+ // Strip off the introducer and any leading space after that.
+ string message_line = isc::util::str::trim(text.substr(1));
+
+ // Look for the first delimiter.
+ size_t first_delim = message_line.find_first_of(delimiters);
+ if (first_delim == string::npos) {
+
+ // Just a single token in the line - this is not valid
+ isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
+ message_line, lineno_);
+ }
+
+ // Extract the first token into the message ID, preceding it with the
+ // current prefix, then convert to upper-case. If the prefix is not set,
+ // perform the valid character check now - the string will become a C++
+ // symbol so we may as well identify problems early.
+ string ident = prefix_ + message_line.substr(0, first_delim);
+ if (prefix_.empty()) {
+ if (invalidSymbol(ident)) {
+ isc_throw_3(MessageException, "Invalid message ID",
+ LOG_INVALID_MESSAGE_ID, ident, lineno_);
+ }
+ }
+ isc::util::str::uppercase(ident);
+
+ // Locate the start of the message text
+ size_t first_text = message_line.find_first_not_of(delimiters, first_delim);
+ if (first_text == string::npos) {
+
+ // ?? This happens if there are trailing delimiters, which should not
+ // occur as we have stripped trailing spaces off the line. Just treat
+ // this as a single-token error for simplicity's sake.
+ isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
+ message_line, lineno_);
+ }
+
+ // Add the result to the dictionary and to the non-added list if the add to
+ // the dictionary fails.
+ bool added;
+ if (mode == ADD) {
+ added = dictionary_->add(ident, message_line.substr(first_text));
+ } else {
+ added = dictionary_->replace(ident, message_line.substr(first_text));
+ }
+ if (!added) {
+ not_added_.push_back(ident);
+ }
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/message_reader.h b/src/lib/log/message_reader.h
new file mode 100644
index 0000000..65fdb2b
--- /dev/null
+++ b/src/lib/log/message_reader.h
@@ -0,0 +1,207 @@
+// 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/.
+
+#ifndef MESSAGE_READER_H
+#define MESSAGE_READER_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Read Message File
+///
+/// Reads a message file and creates a map of identifier against the text of the
+/// message. This map can be retrieved for subsequent processing.
+
+class MessageReader {
+public:
+
+ /// \brief Read Mode
+ ///
+ /// If ADD, messages are added to the dictionary if the ID does not exist
+ /// there. If it does, the ID is added to the dictionary's overflow
+ /// vector.
+ ///
+ /// If REPLACE, the dictionary is only modified if the message ID already
+ /// exists in it. New message IDs are added to the overflow vector.
+ typedef enum {
+ ADD,
+ REPLACE
+ } Mode;
+
+ /// \brief Visible collection types
+ typedef std::vector<std::string> MessageIDCollection;
+
+ /// \brief Constructor
+ ///
+ /// Default constructor. All work is done in the main readFile code (so
+ /// that a status return can be returned instead of needing to throw an
+ /// exception).
+ ///
+ /// \param dictionary Dictionary to which messages read read from the file
+ /// are added. (This should be a local dictionary when the class is used in
+ /// the message compiler, and the global dictionary when used in a server.
+ /// The ownership of the dictionary object is not transferred - the caller
+ /// is responsible for managing the lifetime of the dictionary.
+ MessageReader(MessageDictionary* dictionary = NULL) :
+ dictionary_(dictionary), lineno_(0)
+ {}
+
+ /// \brief Virtual Destructor
+ virtual ~MessageReader()
+ {}
+
+ /// \brief Get Dictionary
+ ///
+ /// Returns the pointer to the dictionary object. Note that ownership is
+ /// not transferred - the caller should not delete it.
+ ///
+ /// \return Pointer to current dictionary object
+ MessageDictionary* getDictionary() const {
+ return (dictionary_);
+ }
+
+
+ /// \brief Set Dictionary
+ ///
+ /// Sets the current dictionary object.
+ ///
+ /// \param dictionary New dictionary object. The ownership of the dictionary
+ /// object is not transferred - the caller is responsible for managing the
+ /// lifetime of the dictionary.
+ void setDictionary(MessageDictionary* dictionary) {
+ dictionary_ = dictionary;
+ }
+
+
+ /// \brief Read File
+ ///
+ /// This is the main method of the class and reads in the file, parses it,
+ /// and stores the result in the message dictionary.
+ ///
+ /// \param file Name of the message file.
+ /// \param mode Addition mode. See the description of the "Mode" enum.
+ virtual void readFile(const std::string& file, Mode mode = ADD);
+
+
+ /// \brief Process Line
+ ///
+ /// Parses a text line and adds it to the message map. Although this is
+ /// for use in readFile, it can also be used to add individual messages
+ /// to the message map.
+ ///
+ /// \param line Line of text to process
+ /// \param mode If a message line, how to add the message to the dictionary.
+ virtual void processLine(const std::string& line, Mode mode = ADD);
+
+
+ /// \brief Get Namespace
+ ///
+ /// \return Argument to the $NAMESPACE directive (if present)
+ virtual std::string getNamespace() const {
+ return (ns_);
+ }
+
+
+ /// \brief Clear Namespace
+ ///
+ /// Clears the current namespace.
+ virtual void clearNamespace() {
+ ns_ = "";
+ }
+
+
+ /// \brief Get Prefix
+ ///
+ /// \return Argument to the $PREFIX directive (if present)
+ virtual std::string getPrefix() const {
+ return (prefix_);
+ }
+
+
+ /// \brief Clear Prefix
+ ///
+ /// Clears the current prefix.
+ virtual void clearPrefix() {
+ prefix_ = "";
+ }
+
+
+ /// \brief Get Not-Added List
+ ///
+ /// Returns the list of IDs that were not added during the last
+ /// read of the file.
+ ///
+ /// \return Collection of messages not added
+ MessageIDCollection getNotAdded() const {
+ return (not_added_);
+ }
+
+private:
+
+ /// \brief Handle a Message Definition
+ ///
+ /// Passed a line that should contain a message, this processes that line
+ /// and adds it to the dictionary according to the mode setting.
+ ///
+ /// \param line Line of text
+ /// \param ADD or REPLACE depending on how the reader is operating. (See
+ /// the description of the Mode typedef for details.)
+ void parseMessage(const std::string& line, Mode mode);
+
+
+ /// \brief Handle Directive
+ ///
+ /// Passed a line starting with a "$", this handles the processing of
+ /// directives.
+ ///
+ /// \param line Line of text that starts with "$",
+ void parseDirective(const std::string& line);
+
+
+ /// \brief Parse $PREFIX line
+ ///
+ /// \param tokens $PREFIX line split into tokens
+ void parsePrefix(const std::vector<std::string>& tokens);
+
+
+ /// \brief Parse $NAMESPACE line
+ ///
+ /// \param tokens $NAMESPACE line split into tokens
+ void parseNamespace(const std::vector<std::string>& tokens);
+
+ /// \brief Check for invalid C++ symbol name
+ ///
+ /// The message ID (or concatenation of prefix and message ID) will be used
+ /// as the name of a symbol in C++ code. This function checks if the name
+ /// is invalid (contains anything other than alphanumeric characters or
+ /// underscores, or starts with a digit).
+ ///
+ /// \param symbol name to check to see if it is an invalid C++ symbol.
+ ///
+ /// \return true if the name is invalid, false if it is valid.
+ bool invalidSymbol(const std::string& symbol);
+
+
+
+ /// Attributes
+ MessageDictionary* dictionary_; ///< Dictionary to add messages to
+ MessageIDCollection not_added_; ///< List of IDs not added
+ int lineno_; ///< Number of last line read
+ std::string prefix_; ///< Argument of $PREFIX statement
+ std::string ns_; ///< Argument of $NAMESPACE statement
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // MESSAGE_READER_H
diff --git a/src/lib/log/message_types.h b/src/lib/log/message_types.h
new file mode 100644
index 0000000..6991bd6
--- /dev/null
+++ b/src/lib/log/message_types.h
@@ -0,0 +1,29 @@
+// 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/.
+
+#ifndef MESSAGE_TYPES_H
+#define MESSAGE_TYPES_H
+
+#include <string.h>
+
+namespace isc {
+namespace log {
+
+typedef const char* MessageID;
+
+/// \brief Compare MessageID for Equality
+///
+/// \param m1 First message ID
+/// \param m2 Second message ID
+/// \return true if they are equal, false if not
+bool equalMessageID(const MessageID& m1, const MessageID& m2);
+
+} // namespace log
+} // namespace isc
+
+
+
+#endif // MESSAGE_TYPES_H
diff --git a/src/lib/log/output_option.cc b/src/lib/log/output_option.cc
new file mode 100644
index 0000000..0073c7f
--- /dev/null
+++ b/src/lib/log/output_option.cc
@@ -0,0 +1,58 @@
+// Copyright (C) 2011-2020 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 <boost/algorithm/string.hpp>
+
+#include <log/log_messages.h>
+#include <log/macros.h>
+#include <log/output_option.h>
+
+namespace isc {
+namespace log {
+
+/// Default layout pattern for console logs
+const std::string OutputOption::DEFAULT_CONSOLE_PATTERN = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i.%t] %m\n";
+
+/// Default layout pattern for file logs
+const std::string OutputOption::DEFAULT_FILE_PATTERN = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i.%t] %m\n";
+
+/// Default layout pattern for syslog logs
+const std::string OutputOption::DEFAULT_SYSLOG_PATTERN = "%-5p [%c.%t] %m\n";
+
+OutputOption::Destination
+getDestination(const std::string& dest_str) {
+ if (boost::iequals(dest_str, "console")) {
+ return OutputOption::DEST_CONSOLE;
+ } else if (boost::iequals(dest_str, "file")) {
+ return OutputOption::DEST_FILE;
+ } else if (boost::iequals(dest_str, "syslog")) {
+ return OutputOption::DEST_SYSLOG;
+ } else {
+ Logger logger("log");
+ LOG_ERROR(logger, LOG_BAD_DESTINATION).arg(dest_str);
+ return OutputOption::DEST_CONSOLE;
+ }
+}
+
+OutputOption::Stream
+getStream(const std::string& stream_str) {
+ if (boost::iequals(stream_str, "stderr")) {
+ return OutputOption::STR_STDERR;
+ } else if (boost::iequals(stream_str, "stdout")) {
+ return OutputOption::STR_STDOUT;
+ } else {
+ Logger logger("log");
+ LOG_ERROR(logger, LOG_BAD_STREAM).arg(stream_str);
+ return OutputOption::STR_STDOUT;
+ }
+}
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/output_option.h b/src/lib/log/output_option.h
new file mode 100644
index 0000000..09d9d3d
--- /dev/null
+++ b/src/lib/log/output_option.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2011-2021 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 OUTPUT_OPTION_H
+#define OUTPUT_OPTION_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string>
+
+/// \brief Logger Output Option
+///
+/// The logging configuration options are a list of logger specifications, each
+/// with one or more output options. This class represents an output option;
+/// one or more of these are attached to a LoggerSpecification object which is
+/// then passed to the LoggerManager to configure the logger.
+///
+/// Although there are three distinct output types (console, file, syslog) and
+/// the options for each do not really overlap. Although it is tempting to
+/// define a base OutputOption class and derive a class for each type
+/// (ConsoleOutputOptions etc.), it would be messy to use in practice. At
+/// some point the exact class would have to be known to get the class-specific
+/// options and the (pointer to) the base class cast to the appropriate type.
+/// Instead, this "struct" contains the union of all output options; it is up
+/// to the caller to cherry-pick the members it needs.
+///
+/// One final note: this object holds data and does no computation. For this
+/// reason, it is a "struct" and members are accessed directly instead of
+/// through methods.
+
+namespace isc {
+namespace log {
+
+struct OutputOption {
+
+ /// Default layout pattern for console logs
+ static const std::string DEFAULT_CONSOLE_PATTERN;
+ /// Default layout pattern for file logs
+ static const std::string DEFAULT_FILE_PATTERN;
+ /// Default layout pattern for syslog logs
+ static const std::string DEFAULT_SYSLOG_PATTERN;
+
+ /// Destinations. Prefixed "DEST_" to avoid problems with the C stdio.h
+ /// FILE type.
+ typedef enum {
+ DEST_CONSOLE = 0,
+ DEST_FILE = 1,
+ DEST_SYSLOG = 2
+ } Destination;
+
+ /// If console, stream on which messages are output
+ typedef enum {
+ STR_STDOUT = 1,
+ STR_STDERR = 2
+ } Stream;
+
+ /// \brief Constructor
+ OutputOption() : destination(DEST_CONSOLE), stream(STR_STDERR),
+ flush(true), facility("LOCAL0"), filename(""),
+ maxsize(0), maxver(0), pattern("")
+ {}
+
+ /// Members.
+
+ Destination destination; ///< Where the output should go
+ Stream stream; ///< stdout/stderr if console output
+ bool flush; ///< true to flush after each message
+ std::string facility; ///< syslog facility
+ std::string filename; ///< Filename if file output
+ uint64_t maxsize; ///< 0 if no maximum size
+ unsigned int maxver; ///< Maximum versions (none if <= 0)
+ std::string pattern; ///< log content pattern
+};
+
+OutputOption::Destination getDestination(const std::string& dest_str);
+OutputOption::Stream getStream(const std::string& stream_str);
+
+
+} // namespace log
+} // namespace isc
+
+#endif // OUTPUT_OPTION_H
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
new file mode 100644
index 0000000..55240aa
--- /dev/null
+++ b/src/lib/log/tests/Makefile.am
@@ -0,0 +1,158 @@
+SUBDIRS = .
+
+# Define the flags used in each set of tests.
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+AM_LDADD =
+AM_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+AM_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+AM_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+AM_LDADD += $(LOG4CPLUS_LIBS) $(GTEST_LDADD)
+
+AM_LDFLAGS =
+if USE_STATIC_LINK
+AM_LDFLAGS += -static
+endif
+
+CLEANFILES = *.gcno *.gcda *.lock
+
+EXTRA_DIST = log_test_messages.mes
+
+# Helper programs used in shell tests
+TEST_HELPERS = logger_example
+logger_example_SOURCES = logger_example.cc
+logger_example_CPPFLAGS = $(AM_CPPFLAGS)
+logger_example_CXXFLAGS = $(AM_CXXFLAGS)
+logger_example_LDADD = $(AM_LDADD)
+logger_example_LDFLAGS = $(AM_LDFLAGS)
+
+TEST_HELPERS += init_logger_test
+init_logger_test_SOURCES = init_logger_test.cc
+init_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+init_logger_test_CXXFLAGS = $(AM_CXXFLAGS)
+init_logger_test_LDADD = $(AM_LDADD)
+init_logger_test_LDFLAGS = $(AM_LDFLAGS)
+
+TEST_HELPERS += buffer_logger_test
+buffer_logger_test_SOURCES = buffer_logger_test.cc
+buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+buffer_logger_test_CXXFLAGS = $(AM_CXXFLAGS)
+buffer_logger_test_LDADD = $(AM_LDADD)
+buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
+
+TEST_HELPERS += logger_lock_test
+logger_lock_test_SOURCES = logger_lock_test.cc
+logger_lock_test_SOURCES += log_test_messages.cc log_test_messages.h
+logger_lock_test_CPPFLAGS = $(AM_CPPFLAGS)
+logger_lock_test_CXXFLAGS = $(AM_CXXFLAGS)
+logger_lock_test_LDADD = $(AM_LDADD)
+logger_lock_test_LDFLAGS = $(AM_LDFLAGS)
+
+# Don't install helper binaries.
+noinst_PROGRAMS = $(TEST_HELPERS)
+
+# Shell tests. These are principally tests where the global logging environment
+# is affected, and where the output needs to be compared with stored output
+# (where "cut" and "diff" are useful utilities).
+SHTESTS =
+SHTESTS += buffer_logger_test.sh
+SHTESTS += console_test.sh
+SHTESTS += destination_test.sh
+SHTESTS += init_logger_test.sh
+SHTESTS += local_file_test.sh
+SHTESTS += logger_lock_test.sh
+SHTESTS += severity_test.sh
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = tempdir.h
+DISTCLEANFILES += $(SHTESTS)
+
+if HAVE_GTEST
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Set of unit tests for the general logging classes
+PROGRAM_TESTS = run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += log_formatter_unittest.cc
+run_unittests_SOURCES += logger_level_impl_unittest.cc
+run_unittests_SOURCES += logger_level_unittest.cc
+run_unittests_SOURCES += logger_manager_unittest.cc
+run_unittests_SOURCES += logger_name_unittest.cc
+run_unittests_SOURCES += logger_support_unittest.cc
+run_unittests_SOURCES += logger_unittest.cc
+run_unittests_SOURCES += logger_specification_unittest.cc
+run_unittests_SOURCES += message_dictionary_unittest.cc
+run_unittests_SOURCES += message_reader_unittest.cc
+run_unittests_SOURCES += output_option_unittest.cc
+run_unittests_SOURCES += buffer_appender_unittest.cc
+run_unittests_SOURCES += log_test_messages.cc log_test_messages.h
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+run_unittests_LDADD = $(AM_LDADD)
+run_unittests_LDFLAGS = $(AM_LDFLAGS)
+
+# logging initialization tests. These are put in a separate program to
+# ensure that the initialization status at the start of each test is known,
+# and to prevent circumstances where the execution of one test affects the
+# starting conditions of the next.
+PROGRAM_TESTS += initializer_unittests_1
+initializer_unittests_1_SOURCES = run_initializer_unittests.cc
+initializer_unittests_1_SOURCES += message_initializer_1_unittest.cc
+initializer_unittests_1_SOURCES += message_initializer_1a_unittest.cc
+initializer_unittests_1_CPPFLAGS = $(AM_CPPFLAGS)
+initializer_unittests_1_CXXFLAGS = $(AM_CXXFLAGS)
+initializer_unittests_1_LDADD = $(AM_LDADD)
+initializer_unittests_1_LDFLAGS = $(AM_LDFLAGS)
+
+# Run C++ tests on "make check".
+TESTS = $(PROGRAM_TESTS)
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS += $(SHTESTS)
+
+# Don't install test binaries.
+noinst_PROGRAMS += $(PROGRAM_TESTS)
+
+endif
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f log_test_messages.h log_test_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: log_test_messages.h log_test_messages.cc
+ @echo Message files regenerated
+
+log_test_messages.h log_test_messages.cc: log_test_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/log/tests/log_test_messages.mes
+
+else
+
+messages log_test_messages.h log_test_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
diff --git a/src/lib/log/tests/Makefile.in b/src/lib/log/tests/Makefile.in
new file mode 100644
index 0000000..721a9e7
--- /dev/null
+++ b/src/lib/log/tests/Makefile.in
@@ -0,0 +1,1602 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@USE_STATIC_LINK_TRUE@am__append_1 = -static
+noinst_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_3)
+@HAVE_GTEST_TRUE@TESTS = $(am__EXEEXT_2) $(SHTESTS)
+
+# Don't install test binaries.
+@HAVE_GTEST_TRUE@am__append_2 = $(PROGRAM_TESTS)
+subdir = src/lib/log/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = buffer_logger_test.sh console_test.sh \
+ destination_test.sh init_logger_test.sh local_file_test.sh \
+ logger_lock_test.sh severity_test.sh tempdir.h
+CONFIG_CLEAN_VPATH_FILES =
+am__EXEEXT_1 = logger_example$(EXEEXT) init_logger_test$(EXEEXT) \
+ buffer_logger_test$(EXEEXT) logger_lock_test$(EXEEXT)
+@HAVE_GTEST_TRUE@am__EXEEXT_2 = run_unittests$(EXEEXT) \
+@HAVE_GTEST_TRUE@ initializer_unittests_1$(EXEEXT)
+@HAVE_GTEST_TRUE@am__EXEEXT_3 = $(am__EXEEXT_2)
+PROGRAMS = $(noinst_PROGRAMS)
+am_buffer_logger_test_OBJECTS = \
+ buffer_logger_test-buffer_logger_test.$(OBJEXT)
+buffer_logger_test_OBJECTS = $(am_buffer_logger_test_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+buffer_logger_test_DEPENDENCIES = $(am__DEPENDENCIES_2)
+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 =
+buffer_logger_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(buffer_logger_test_CXXFLAGS) $(CXXFLAGS) \
+ $(buffer_logger_test_LDFLAGS) $(LDFLAGS) -o $@
+am_init_logger_test_OBJECTS = \
+ init_logger_test-init_logger_test.$(OBJEXT)
+init_logger_test_OBJECTS = $(am_init_logger_test_OBJECTS)
+init_logger_test_DEPENDENCIES = $(am__DEPENDENCIES_2)
+init_logger_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(init_logger_test_CXXFLAGS) $(CXXFLAGS) \
+ $(init_logger_test_LDFLAGS) $(LDFLAGS) -o $@
+am__initializer_unittests_1_SOURCES_DIST = \
+ run_initializer_unittests.cc message_initializer_1_unittest.cc \
+ message_initializer_1a_unittest.cc
+@HAVE_GTEST_TRUE@am_initializer_unittests_1_OBJECTS = initializer_unittests_1-run_initializer_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ initializer_unittests_1-message_initializer_1_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ initializer_unittests_1-message_initializer_1a_unittest.$(OBJEXT)
+initializer_unittests_1_OBJECTS = \
+ $(am_initializer_unittests_1_OBJECTS)
+@HAVE_GTEST_TRUE@initializer_unittests_1_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_2)
+initializer_unittests_1_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) \
+ $(initializer_unittests_1_LDFLAGS) $(LDFLAGS) -o $@
+am_logger_example_OBJECTS = logger_example-logger_example.$(OBJEXT)
+logger_example_OBJECTS = $(am_logger_example_OBJECTS)
+logger_example_DEPENDENCIES = $(am__DEPENDENCIES_2)
+logger_example_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(logger_example_CXXFLAGS) $(CXXFLAGS) \
+ $(logger_example_LDFLAGS) $(LDFLAGS) -o $@
+am_logger_lock_test_OBJECTS = \
+ logger_lock_test-logger_lock_test.$(OBJEXT) \
+ logger_lock_test-log_test_messages.$(OBJEXT)
+logger_lock_test_OBJECTS = $(am_logger_lock_test_OBJECTS)
+logger_lock_test_DEPENDENCIES = $(am__DEPENDENCIES_2)
+logger_lock_test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) \
+ $(logger_lock_test_LDFLAGS) $(LDFLAGS) -o $@
+am__run_unittests_SOURCES_DIST = run_unittests.cc \
+ log_formatter_unittest.cc logger_level_impl_unittest.cc \
+ logger_level_unittest.cc logger_manager_unittest.cc \
+ logger_name_unittest.cc logger_support_unittest.cc \
+ logger_unittest.cc logger_specification_unittest.cc \
+ message_dictionary_unittest.cc message_reader_unittest.cc \
+ output_option_unittest.cc buffer_appender_unittest.cc \
+ log_test_messages.cc log_test_messages.h
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-log_formatter_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-logger_level_impl_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-logger_level_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-logger_manager_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-logger_name_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-logger_support_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-logger_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-logger_specification_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-message_dictionary_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-message_reader_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-output_option_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-buffer_appender_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-log_test_messages.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(am__DEPENDENCIES_2)
+run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(run_unittests_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SCRIPTS = $(noinst_SCRIPTS)
+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)/buffer_logger_test-buffer_logger_test.Po \
+ ./$(DEPDIR)/init_logger_test-init_logger_test.Po \
+ ./$(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Po \
+ ./$(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Po \
+ ./$(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Po \
+ ./$(DEPDIR)/logger_example-logger_example.Po \
+ ./$(DEPDIR)/logger_lock_test-log_test_messages.Po \
+ ./$(DEPDIR)/logger_lock_test-logger_lock_test.Po \
+ ./$(DEPDIR)/run_unittests-buffer_appender_unittest.Po \
+ ./$(DEPDIR)/run_unittests-log_formatter_unittest.Po \
+ ./$(DEPDIR)/run_unittests-log_test_messages.Po \
+ ./$(DEPDIR)/run_unittests-logger_level_impl_unittest.Po \
+ ./$(DEPDIR)/run_unittests-logger_level_unittest.Po \
+ ./$(DEPDIR)/run_unittests-logger_manager_unittest.Po \
+ ./$(DEPDIR)/run_unittests-logger_name_unittest.Po \
+ ./$(DEPDIR)/run_unittests-logger_specification_unittest.Po \
+ ./$(DEPDIR)/run_unittests-logger_support_unittest.Po \
+ ./$(DEPDIR)/run_unittests-logger_unittest.Po \
+ ./$(DEPDIR)/run_unittests-message_dictionary_unittest.Po \
+ ./$(DEPDIR)/run_unittests-message_reader_unittest.Po \
+ ./$(DEPDIR)/run_unittests-output_option_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 = $(buffer_logger_test_SOURCES) $(init_logger_test_SOURCES) \
+ $(initializer_unittests_1_SOURCES) $(logger_example_SOURCES) \
+ $(logger_lock_test_SOURCES) $(run_unittests_SOURCES)
+DIST_SOURCES = $(buffer_logger_test_SOURCES) \
+ $(init_logger_test_SOURCES) \
+ $(am__initializer_unittests_1_SOURCES_DIST) \
+ $(logger_example_SOURCES) $(logger_lock_test_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)`
+ETAGS = etags
+CTAGS = ctags
+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 \
+ $(srcdir)/buffer_logger_test.sh.in \
+ $(srcdir)/console_test.sh.in $(srcdir)/destination_test.sh.in \
+ $(srcdir)/init_logger_test.sh.in \
+ $(srcdir)/local_file_test.sh.in \
+ $(srcdir)/logger_lock_test.sh.in $(srcdir)/severity_test.sh.in \
+ $(srcdir)/tempdir.h.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+
+# Define the flags used in each set of tests.
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+AM_LDADD = $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(GTEST_LDADD)
+AM_LDFLAGS = $(am__append_1)
+CLEANFILES = *.gcno *.gcda *.lock
+EXTRA_DIST = log_test_messages.mes
+
+# Helper programs used in shell tests
+TEST_HELPERS = logger_example init_logger_test buffer_logger_test \
+ logger_lock_test
+logger_example_SOURCES = logger_example.cc
+logger_example_CPPFLAGS = $(AM_CPPFLAGS)
+logger_example_CXXFLAGS = $(AM_CXXFLAGS)
+logger_example_LDADD = $(AM_LDADD)
+logger_example_LDFLAGS = $(AM_LDFLAGS)
+init_logger_test_SOURCES = init_logger_test.cc
+init_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+init_logger_test_CXXFLAGS = $(AM_CXXFLAGS)
+init_logger_test_LDADD = $(AM_LDADD)
+init_logger_test_LDFLAGS = $(AM_LDFLAGS)
+buffer_logger_test_SOURCES = buffer_logger_test.cc
+buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+buffer_logger_test_CXXFLAGS = $(AM_CXXFLAGS)
+buffer_logger_test_LDADD = $(AM_LDADD)
+buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
+logger_lock_test_SOURCES = logger_lock_test.cc log_test_messages.cc \
+ log_test_messages.h
+logger_lock_test_CPPFLAGS = $(AM_CPPFLAGS)
+logger_lock_test_CXXFLAGS = $(AM_CXXFLAGS)
+logger_lock_test_LDADD = $(AM_LDADD)
+logger_lock_test_LDFLAGS = $(AM_LDFLAGS)
+
+# Shell tests. These are principally tests where the global logging environment
+# is affected, and where the output needs to be compared with stored output
+# (where "cut" and "diff" are useful utilities).
+SHTESTS = buffer_logger_test.sh console_test.sh destination_test.sh \
+ init_logger_test.sh local_file_test.sh logger_lock_test.sh \
+ severity_test.sh
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = tempdir.h $(SHTESTS)
+@HAVE_GTEST_TRUE@TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Set of unit tests for the general logging classes
+
+# logging initialization tests. These are put in a separate program to
+# ensure that the initialization status at the start of each test is known,
+# and to prevent circumstances where the execution of one test affects the
+# starting conditions of the next.
+@HAVE_GTEST_TRUE@PROGRAM_TESTS = run_unittests initializer_unittests_1
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ log_formatter_unittest.cc \
+@HAVE_GTEST_TRUE@ logger_level_impl_unittest.cc \
+@HAVE_GTEST_TRUE@ logger_level_unittest.cc \
+@HAVE_GTEST_TRUE@ logger_manager_unittest.cc \
+@HAVE_GTEST_TRUE@ logger_name_unittest.cc \
+@HAVE_GTEST_TRUE@ logger_support_unittest.cc logger_unittest.cc \
+@HAVE_GTEST_TRUE@ logger_specification_unittest.cc \
+@HAVE_GTEST_TRUE@ message_dictionary_unittest.cc \
+@HAVE_GTEST_TRUE@ message_reader_unittest.cc \
+@HAVE_GTEST_TRUE@ output_option_unittest.cc \
+@HAVE_GTEST_TRUE@ buffer_appender_unittest.cc \
+@HAVE_GTEST_TRUE@ log_test_messages.cc log_test_messages.h
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(AM_LDADD)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS)
+@HAVE_GTEST_TRUE@initializer_unittests_1_SOURCES = \
+@HAVE_GTEST_TRUE@ run_initializer_unittests.cc \
+@HAVE_GTEST_TRUE@ message_initializer_1_unittest.cc \
+@HAVE_GTEST_TRUE@ message_initializer_1a_unittest.cc
+@HAVE_GTEST_TRUE@initializer_unittests_1_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@initializer_unittests_1_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@initializer_unittests_1_LDADD = $(AM_LDADD)
+@HAVE_GTEST_TRUE@initializer_unittests_1_LDFLAGS = $(AM_LDFLAGS)
+
+# Run shell tests on "make check".
+@HAVE_GTEST_TRUE@check_SCRIPTS = $(SHTESTS)
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+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/log/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/log/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):
+buffer_logger_test.sh: $(top_builddir)/config.status $(srcdir)/buffer_logger_test.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+console_test.sh: $(top_builddir)/config.status $(srcdir)/console_test.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+destination_test.sh: $(top_builddir)/config.status $(srcdir)/destination_test.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+init_logger_test.sh: $(top_builddir)/config.status $(srcdir)/init_logger_test.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+local_file_test.sh: $(top_builddir)/config.status $(srcdir)/local_file_test.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+logger_lock_test.sh: $(top_builddir)/config.status $(srcdir)/logger_lock_test.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+severity_test.sh: $(top_builddir)/config.status $(srcdir)/severity_test.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+tempdir.h: $(top_builddir)/config.status $(srcdir)/tempdir.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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
+
+buffer_logger_test$(EXEEXT): $(buffer_logger_test_OBJECTS) $(buffer_logger_test_DEPENDENCIES) $(EXTRA_buffer_logger_test_DEPENDENCIES)
+ @rm -f buffer_logger_test$(EXEEXT)
+ $(AM_V_CXXLD)$(buffer_logger_test_LINK) $(buffer_logger_test_OBJECTS) $(buffer_logger_test_LDADD) $(LIBS)
+
+init_logger_test$(EXEEXT): $(init_logger_test_OBJECTS) $(init_logger_test_DEPENDENCIES) $(EXTRA_init_logger_test_DEPENDENCIES)
+ @rm -f init_logger_test$(EXEEXT)
+ $(AM_V_CXXLD)$(init_logger_test_LINK) $(init_logger_test_OBJECTS) $(init_logger_test_LDADD) $(LIBS)
+
+initializer_unittests_1$(EXEEXT): $(initializer_unittests_1_OBJECTS) $(initializer_unittests_1_DEPENDENCIES) $(EXTRA_initializer_unittests_1_DEPENDENCIES)
+ @rm -f initializer_unittests_1$(EXEEXT)
+ $(AM_V_CXXLD)$(initializer_unittests_1_LINK) $(initializer_unittests_1_OBJECTS) $(initializer_unittests_1_LDADD) $(LIBS)
+
+logger_example$(EXEEXT): $(logger_example_OBJECTS) $(logger_example_DEPENDENCIES) $(EXTRA_logger_example_DEPENDENCIES)
+ @rm -f logger_example$(EXEEXT)
+ $(AM_V_CXXLD)$(logger_example_LINK) $(logger_example_OBJECTS) $(logger_example_LDADD) $(LIBS)
+
+logger_lock_test$(EXEEXT): $(logger_lock_test_OBJECTS) $(logger_lock_test_DEPENDENCIES) $(EXTRA_logger_lock_test_DEPENDENCIES)
+ @rm -f logger_lock_test$(EXEEXT)
+ $(AM_V_CXXLD)$(logger_lock_test_LINK) $(logger_lock_test_OBJECTS) $(logger_lock_test_LDADD) $(LIBS)
+
+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)/buffer_logger_test-buffer_logger_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/init_logger_test-init_logger_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logger_example-logger_example.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logger_lock_test-log_test_messages.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logger_lock_test-logger_lock_test.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-buffer_appender_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-log_formatter_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-log_test_messages.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-logger_level_impl_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-logger_level_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-logger_manager_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-logger_name_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-logger_specification_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-logger_support_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-logger_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-message_dictionary_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-message_reader_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-output_option_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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 $@ $<
+
+buffer_logger_test-buffer_logger_test.o: buffer_logger_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(buffer_logger_test_CPPFLAGS) $(CPPFLAGS) $(buffer_logger_test_CXXFLAGS) $(CXXFLAGS) -MT buffer_logger_test-buffer_logger_test.o -MD -MP -MF $(DEPDIR)/buffer_logger_test-buffer_logger_test.Tpo -c -o buffer_logger_test-buffer_logger_test.o `test -f 'buffer_logger_test.cc' || echo '$(srcdir)/'`buffer_logger_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/buffer_logger_test-buffer_logger_test.Tpo $(DEPDIR)/buffer_logger_test-buffer_logger_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_logger_test.cc' object='buffer_logger_test-buffer_logger_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) $(buffer_logger_test_CPPFLAGS) $(CPPFLAGS) $(buffer_logger_test_CXXFLAGS) $(CXXFLAGS) -c -o buffer_logger_test-buffer_logger_test.o `test -f 'buffer_logger_test.cc' || echo '$(srcdir)/'`buffer_logger_test.cc
+
+buffer_logger_test-buffer_logger_test.obj: buffer_logger_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(buffer_logger_test_CPPFLAGS) $(CPPFLAGS) $(buffer_logger_test_CXXFLAGS) $(CXXFLAGS) -MT buffer_logger_test-buffer_logger_test.obj -MD -MP -MF $(DEPDIR)/buffer_logger_test-buffer_logger_test.Tpo -c -o buffer_logger_test-buffer_logger_test.obj `if test -f 'buffer_logger_test.cc'; then $(CYGPATH_W) 'buffer_logger_test.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_logger_test.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/buffer_logger_test-buffer_logger_test.Tpo $(DEPDIR)/buffer_logger_test-buffer_logger_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_logger_test.cc' object='buffer_logger_test-buffer_logger_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) $(buffer_logger_test_CPPFLAGS) $(CPPFLAGS) $(buffer_logger_test_CXXFLAGS) $(CXXFLAGS) -c -o buffer_logger_test-buffer_logger_test.obj `if test -f 'buffer_logger_test.cc'; then $(CYGPATH_W) 'buffer_logger_test.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_logger_test.cc'; fi`
+
+init_logger_test-init_logger_test.o: init_logger_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(init_logger_test_CPPFLAGS) $(CPPFLAGS) $(init_logger_test_CXXFLAGS) $(CXXFLAGS) -MT init_logger_test-init_logger_test.o -MD -MP -MF $(DEPDIR)/init_logger_test-init_logger_test.Tpo -c -o init_logger_test-init_logger_test.o `test -f 'init_logger_test.cc' || echo '$(srcdir)/'`init_logger_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/init_logger_test-init_logger_test.Tpo $(DEPDIR)/init_logger_test-init_logger_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='init_logger_test.cc' object='init_logger_test-init_logger_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) $(init_logger_test_CPPFLAGS) $(CPPFLAGS) $(init_logger_test_CXXFLAGS) $(CXXFLAGS) -c -o init_logger_test-init_logger_test.o `test -f 'init_logger_test.cc' || echo '$(srcdir)/'`init_logger_test.cc
+
+init_logger_test-init_logger_test.obj: init_logger_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(init_logger_test_CPPFLAGS) $(CPPFLAGS) $(init_logger_test_CXXFLAGS) $(CXXFLAGS) -MT init_logger_test-init_logger_test.obj -MD -MP -MF $(DEPDIR)/init_logger_test-init_logger_test.Tpo -c -o init_logger_test-init_logger_test.obj `if test -f 'init_logger_test.cc'; then $(CYGPATH_W) 'init_logger_test.cc'; else $(CYGPATH_W) '$(srcdir)/init_logger_test.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/init_logger_test-init_logger_test.Tpo $(DEPDIR)/init_logger_test-init_logger_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='init_logger_test.cc' object='init_logger_test-init_logger_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) $(init_logger_test_CPPFLAGS) $(CPPFLAGS) $(init_logger_test_CXXFLAGS) $(CXXFLAGS) -c -o init_logger_test-init_logger_test.obj `if test -f 'init_logger_test.cc'; then $(CYGPATH_W) 'init_logger_test.cc'; else $(CYGPATH_W) '$(srcdir)/init_logger_test.cc'; fi`
+
+initializer_unittests_1-run_initializer_unittests.o: run_initializer_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -MT initializer_unittests_1-run_initializer_unittests.o -MD -MP -MF $(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Tpo -c -o initializer_unittests_1-run_initializer_unittests.o `test -f 'run_initializer_unittests.cc' || echo '$(srcdir)/'`run_initializer_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Tpo $(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_initializer_unittests.cc' object='initializer_unittests_1-run_initializer_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) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -c -o initializer_unittests_1-run_initializer_unittests.o `test -f 'run_initializer_unittests.cc' || echo '$(srcdir)/'`run_initializer_unittests.cc
+
+initializer_unittests_1-run_initializer_unittests.obj: run_initializer_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -MT initializer_unittests_1-run_initializer_unittests.obj -MD -MP -MF $(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Tpo -c -o initializer_unittests_1-run_initializer_unittests.obj `if test -f 'run_initializer_unittests.cc'; then $(CYGPATH_W) 'run_initializer_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_initializer_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Tpo $(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_initializer_unittests.cc' object='initializer_unittests_1-run_initializer_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) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -c -o initializer_unittests_1-run_initializer_unittests.obj `if test -f 'run_initializer_unittests.cc'; then $(CYGPATH_W) 'run_initializer_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_initializer_unittests.cc'; fi`
+
+initializer_unittests_1-message_initializer_1_unittest.o: message_initializer_1_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -MT initializer_unittests_1-message_initializer_1_unittest.o -MD -MP -MF $(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Tpo -c -o initializer_unittests_1-message_initializer_1_unittest.o `test -f 'message_initializer_1_unittest.cc' || echo '$(srcdir)/'`message_initializer_1_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Tpo $(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_initializer_1_unittest.cc' object='initializer_unittests_1-message_initializer_1_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) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -c -o initializer_unittests_1-message_initializer_1_unittest.o `test -f 'message_initializer_1_unittest.cc' || echo '$(srcdir)/'`message_initializer_1_unittest.cc
+
+initializer_unittests_1-message_initializer_1_unittest.obj: message_initializer_1_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -MT initializer_unittests_1-message_initializer_1_unittest.obj -MD -MP -MF $(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Tpo -c -o initializer_unittests_1-message_initializer_1_unittest.obj `if test -f 'message_initializer_1_unittest.cc'; then $(CYGPATH_W) 'message_initializer_1_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_initializer_1_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Tpo $(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_initializer_1_unittest.cc' object='initializer_unittests_1-message_initializer_1_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) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -c -o initializer_unittests_1-message_initializer_1_unittest.obj `if test -f 'message_initializer_1_unittest.cc'; then $(CYGPATH_W) 'message_initializer_1_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_initializer_1_unittest.cc'; fi`
+
+initializer_unittests_1-message_initializer_1a_unittest.o: message_initializer_1a_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -MT initializer_unittests_1-message_initializer_1a_unittest.o -MD -MP -MF $(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Tpo -c -o initializer_unittests_1-message_initializer_1a_unittest.o `test -f 'message_initializer_1a_unittest.cc' || echo '$(srcdir)/'`message_initializer_1a_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Tpo $(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_initializer_1a_unittest.cc' object='initializer_unittests_1-message_initializer_1a_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) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -c -o initializer_unittests_1-message_initializer_1a_unittest.o `test -f 'message_initializer_1a_unittest.cc' || echo '$(srcdir)/'`message_initializer_1a_unittest.cc
+
+initializer_unittests_1-message_initializer_1a_unittest.obj: message_initializer_1a_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -MT initializer_unittests_1-message_initializer_1a_unittest.obj -MD -MP -MF $(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Tpo -c -o initializer_unittests_1-message_initializer_1a_unittest.obj `if test -f 'message_initializer_1a_unittest.cc'; then $(CYGPATH_W) 'message_initializer_1a_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_initializer_1a_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Tpo $(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_initializer_1a_unittest.cc' object='initializer_unittests_1-message_initializer_1a_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) $(initializer_unittests_1_CPPFLAGS) $(CPPFLAGS) $(initializer_unittests_1_CXXFLAGS) $(CXXFLAGS) -c -o initializer_unittests_1-message_initializer_1a_unittest.obj `if test -f 'message_initializer_1a_unittest.cc'; then $(CYGPATH_W) 'message_initializer_1a_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_initializer_1a_unittest.cc'; fi`
+
+logger_example-logger_example.o: logger_example.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(logger_example_CPPFLAGS) $(CPPFLAGS) $(logger_example_CXXFLAGS) $(CXXFLAGS) -MT logger_example-logger_example.o -MD -MP -MF $(DEPDIR)/logger_example-logger_example.Tpo -c -o logger_example-logger_example.o `test -f 'logger_example.cc' || echo '$(srcdir)/'`logger_example.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/logger_example-logger_example.Tpo $(DEPDIR)/logger_example-logger_example.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_example.cc' object='logger_example-logger_example.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) $(logger_example_CPPFLAGS) $(CPPFLAGS) $(logger_example_CXXFLAGS) $(CXXFLAGS) -c -o logger_example-logger_example.o `test -f 'logger_example.cc' || echo '$(srcdir)/'`logger_example.cc
+
+logger_example-logger_example.obj: logger_example.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(logger_example_CPPFLAGS) $(CPPFLAGS) $(logger_example_CXXFLAGS) $(CXXFLAGS) -MT logger_example-logger_example.obj -MD -MP -MF $(DEPDIR)/logger_example-logger_example.Tpo -c -o logger_example-logger_example.obj `if test -f 'logger_example.cc'; then $(CYGPATH_W) 'logger_example.cc'; else $(CYGPATH_W) '$(srcdir)/logger_example.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/logger_example-logger_example.Tpo $(DEPDIR)/logger_example-logger_example.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_example.cc' object='logger_example-logger_example.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) $(logger_example_CPPFLAGS) $(CPPFLAGS) $(logger_example_CXXFLAGS) $(CXXFLAGS) -c -o logger_example-logger_example.obj `if test -f 'logger_example.cc'; then $(CYGPATH_W) 'logger_example.cc'; else $(CYGPATH_W) '$(srcdir)/logger_example.cc'; fi`
+
+logger_lock_test-logger_lock_test.o: logger_lock_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -MT logger_lock_test-logger_lock_test.o -MD -MP -MF $(DEPDIR)/logger_lock_test-logger_lock_test.Tpo -c -o logger_lock_test-logger_lock_test.o `test -f 'logger_lock_test.cc' || echo '$(srcdir)/'`logger_lock_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/logger_lock_test-logger_lock_test.Tpo $(DEPDIR)/logger_lock_test-logger_lock_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_lock_test.cc' object='logger_lock_test-logger_lock_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) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -c -o logger_lock_test-logger_lock_test.o `test -f 'logger_lock_test.cc' || echo '$(srcdir)/'`logger_lock_test.cc
+
+logger_lock_test-logger_lock_test.obj: logger_lock_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -MT logger_lock_test-logger_lock_test.obj -MD -MP -MF $(DEPDIR)/logger_lock_test-logger_lock_test.Tpo -c -o logger_lock_test-logger_lock_test.obj `if test -f 'logger_lock_test.cc'; then $(CYGPATH_W) 'logger_lock_test.cc'; else $(CYGPATH_W) '$(srcdir)/logger_lock_test.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/logger_lock_test-logger_lock_test.Tpo $(DEPDIR)/logger_lock_test-logger_lock_test.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_lock_test.cc' object='logger_lock_test-logger_lock_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) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -c -o logger_lock_test-logger_lock_test.obj `if test -f 'logger_lock_test.cc'; then $(CYGPATH_W) 'logger_lock_test.cc'; else $(CYGPATH_W) '$(srcdir)/logger_lock_test.cc'; fi`
+
+logger_lock_test-log_test_messages.o: log_test_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -MT logger_lock_test-log_test_messages.o -MD -MP -MF $(DEPDIR)/logger_lock_test-log_test_messages.Tpo -c -o logger_lock_test-log_test_messages.o `test -f 'log_test_messages.cc' || echo '$(srcdir)/'`log_test_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/logger_lock_test-log_test_messages.Tpo $(DEPDIR)/logger_lock_test-log_test_messages.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_test_messages.cc' object='logger_lock_test-log_test_messages.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) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -c -o logger_lock_test-log_test_messages.o `test -f 'log_test_messages.cc' || echo '$(srcdir)/'`log_test_messages.cc
+
+logger_lock_test-log_test_messages.obj: log_test_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -MT logger_lock_test-log_test_messages.obj -MD -MP -MF $(DEPDIR)/logger_lock_test-log_test_messages.Tpo -c -o logger_lock_test-log_test_messages.obj `if test -f 'log_test_messages.cc'; then $(CYGPATH_W) 'log_test_messages.cc'; else $(CYGPATH_W) '$(srcdir)/log_test_messages.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/logger_lock_test-log_test_messages.Tpo $(DEPDIR)/logger_lock_test-log_test_messages.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_test_messages.cc' object='logger_lock_test-log_test_messages.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) $(logger_lock_test_CPPFLAGS) $(CPPFLAGS) $(logger_lock_test_CXXFLAGS) $(CXXFLAGS) -c -o logger_lock_test-log_test_messages.obj `if test -f 'log_test_messages.cc'; then $(CYGPATH_W) 'log_test_messages.cc'; else $(CYGPATH_W) '$(srcdir)/log_test_messages.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) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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`
+
+run_unittests-log_formatter_unittest.o: log_formatter_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-log_formatter_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-log_formatter_unittest.Tpo -c -o run_unittests-log_formatter_unittest.o `test -f 'log_formatter_unittest.cc' || echo '$(srcdir)/'`log_formatter_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-log_formatter_unittest.Tpo $(DEPDIR)/run_unittests-log_formatter_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_formatter_unittest.cc' object='run_unittests-log_formatter_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-log_formatter_unittest.o `test -f 'log_formatter_unittest.cc' || echo '$(srcdir)/'`log_formatter_unittest.cc
+
+run_unittests-log_formatter_unittest.obj: log_formatter_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-log_formatter_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-log_formatter_unittest.Tpo -c -o run_unittests-log_formatter_unittest.obj `if test -f 'log_formatter_unittest.cc'; then $(CYGPATH_W) 'log_formatter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/log_formatter_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-log_formatter_unittest.Tpo $(DEPDIR)/run_unittests-log_formatter_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_formatter_unittest.cc' object='run_unittests-log_formatter_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-log_formatter_unittest.obj `if test -f 'log_formatter_unittest.cc'; then $(CYGPATH_W) 'log_formatter_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/log_formatter_unittest.cc'; fi`
+
+run_unittests-logger_level_impl_unittest.o: logger_level_impl_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_level_impl_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-logger_level_impl_unittest.Tpo -c -o run_unittests-logger_level_impl_unittest.o `test -f 'logger_level_impl_unittest.cc' || echo '$(srcdir)/'`logger_level_impl_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_level_impl_unittest.Tpo $(DEPDIR)/run_unittests-logger_level_impl_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_level_impl_unittest.cc' object='run_unittests-logger_level_impl_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_level_impl_unittest.o `test -f 'logger_level_impl_unittest.cc' || echo '$(srcdir)/'`logger_level_impl_unittest.cc
+
+run_unittests-logger_level_impl_unittest.obj: logger_level_impl_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_level_impl_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-logger_level_impl_unittest.Tpo -c -o run_unittests-logger_level_impl_unittest.obj `if test -f 'logger_level_impl_unittest.cc'; then $(CYGPATH_W) 'logger_level_impl_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_level_impl_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_level_impl_unittest.Tpo $(DEPDIR)/run_unittests-logger_level_impl_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_level_impl_unittest.cc' object='run_unittests-logger_level_impl_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_level_impl_unittest.obj `if test -f 'logger_level_impl_unittest.cc'; then $(CYGPATH_W) 'logger_level_impl_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_level_impl_unittest.cc'; fi`
+
+run_unittests-logger_level_unittest.o: logger_level_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_level_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-logger_level_unittest.Tpo -c -o run_unittests-logger_level_unittest.o `test -f 'logger_level_unittest.cc' || echo '$(srcdir)/'`logger_level_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_level_unittest.Tpo $(DEPDIR)/run_unittests-logger_level_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_level_unittest.cc' object='run_unittests-logger_level_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_level_unittest.o `test -f 'logger_level_unittest.cc' || echo '$(srcdir)/'`logger_level_unittest.cc
+
+run_unittests-logger_level_unittest.obj: logger_level_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_level_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-logger_level_unittest.Tpo -c -o run_unittests-logger_level_unittest.obj `if test -f 'logger_level_unittest.cc'; then $(CYGPATH_W) 'logger_level_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_level_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_level_unittest.Tpo $(DEPDIR)/run_unittests-logger_level_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_level_unittest.cc' object='run_unittests-logger_level_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_level_unittest.obj `if test -f 'logger_level_unittest.cc'; then $(CYGPATH_W) 'logger_level_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_level_unittest.cc'; fi`
+
+run_unittests-logger_manager_unittest.o: logger_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_manager_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-logger_manager_unittest.Tpo -c -o run_unittests-logger_manager_unittest.o `test -f 'logger_manager_unittest.cc' || echo '$(srcdir)/'`logger_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_manager_unittest.Tpo $(DEPDIR)/run_unittests-logger_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_manager_unittest.cc' object='run_unittests-logger_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_manager_unittest.o `test -f 'logger_manager_unittest.cc' || echo '$(srcdir)/'`logger_manager_unittest.cc
+
+run_unittests-logger_manager_unittest.obj: logger_manager_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_manager_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-logger_manager_unittest.Tpo -c -o run_unittests-logger_manager_unittest.obj `if test -f 'logger_manager_unittest.cc'; then $(CYGPATH_W) 'logger_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_manager_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_manager_unittest.Tpo $(DEPDIR)/run_unittests-logger_manager_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_manager_unittest.cc' object='run_unittests-logger_manager_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_manager_unittest.obj `if test -f 'logger_manager_unittest.cc'; then $(CYGPATH_W) 'logger_manager_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_manager_unittest.cc'; fi`
+
+run_unittests-logger_name_unittest.o: logger_name_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_name_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-logger_name_unittest.Tpo -c -o run_unittests-logger_name_unittest.o `test -f 'logger_name_unittest.cc' || echo '$(srcdir)/'`logger_name_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_name_unittest.Tpo $(DEPDIR)/run_unittests-logger_name_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_name_unittest.cc' object='run_unittests-logger_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_name_unittest.o `test -f 'logger_name_unittest.cc' || echo '$(srcdir)/'`logger_name_unittest.cc
+
+run_unittests-logger_name_unittest.obj: logger_name_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_name_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-logger_name_unittest.Tpo -c -o run_unittests-logger_name_unittest.obj `if test -f 'logger_name_unittest.cc'; then $(CYGPATH_W) 'logger_name_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_name_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_name_unittest.Tpo $(DEPDIR)/run_unittests-logger_name_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_name_unittest.cc' object='run_unittests-logger_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_name_unittest.obj `if test -f 'logger_name_unittest.cc'; then $(CYGPATH_W) 'logger_name_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_name_unittest.cc'; fi`
+
+run_unittests-logger_support_unittest.o: logger_support_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_support_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-logger_support_unittest.Tpo -c -o run_unittests-logger_support_unittest.o `test -f 'logger_support_unittest.cc' || echo '$(srcdir)/'`logger_support_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_support_unittest.Tpo $(DEPDIR)/run_unittests-logger_support_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_support_unittest.cc' object='run_unittests-logger_support_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_support_unittest.o `test -f 'logger_support_unittest.cc' || echo '$(srcdir)/'`logger_support_unittest.cc
+
+run_unittests-logger_support_unittest.obj: logger_support_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_support_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-logger_support_unittest.Tpo -c -o run_unittests-logger_support_unittest.obj `if test -f 'logger_support_unittest.cc'; then $(CYGPATH_W) 'logger_support_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_support_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_support_unittest.Tpo $(DEPDIR)/run_unittests-logger_support_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_support_unittest.cc' object='run_unittests-logger_support_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_support_unittest.obj `if test -f 'logger_support_unittest.cc'; then $(CYGPATH_W) 'logger_support_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_support_unittest.cc'; fi`
+
+run_unittests-logger_unittest.o: logger_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-logger_unittest.Tpo -c -o run_unittests-logger_unittest.o `test -f 'logger_unittest.cc' || echo '$(srcdir)/'`logger_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_unittest.Tpo $(DEPDIR)/run_unittests-logger_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_unittest.cc' object='run_unittests-logger_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_unittest.o `test -f 'logger_unittest.cc' || echo '$(srcdir)/'`logger_unittest.cc
+
+run_unittests-logger_unittest.obj: logger_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-logger_unittest.Tpo -c -o run_unittests-logger_unittest.obj `if test -f 'logger_unittest.cc'; then $(CYGPATH_W) 'logger_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_unittest.Tpo $(DEPDIR)/run_unittests-logger_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_unittest.cc' object='run_unittests-logger_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_unittest.obj `if test -f 'logger_unittest.cc'; then $(CYGPATH_W) 'logger_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_unittest.cc'; fi`
+
+run_unittests-logger_specification_unittest.o: logger_specification_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_specification_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-logger_specification_unittest.Tpo -c -o run_unittests-logger_specification_unittest.o `test -f 'logger_specification_unittest.cc' || echo '$(srcdir)/'`logger_specification_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_specification_unittest.Tpo $(DEPDIR)/run_unittests-logger_specification_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_specification_unittest.cc' object='run_unittests-logger_specification_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_specification_unittest.o `test -f 'logger_specification_unittest.cc' || echo '$(srcdir)/'`logger_specification_unittest.cc
+
+run_unittests-logger_specification_unittest.obj: logger_specification_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-logger_specification_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-logger_specification_unittest.Tpo -c -o run_unittests-logger_specification_unittest.obj `if test -f 'logger_specification_unittest.cc'; then $(CYGPATH_W) 'logger_specification_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_specification_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-logger_specification_unittest.Tpo $(DEPDIR)/run_unittests-logger_specification_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logger_specification_unittest.cc' object='run_unittests-logger_specification_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-logger_specification_unittest.obj `if test -f 'logger_specification_unittest.cc'; then $(CYGPATH_W) 'logger_specification_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/logger_specification_unittest.cc'; fi`
+
+run_unittests-message_dictionary_unittest.o: message_dictionary_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_dictionary_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-message_dictionary_unittest.Tpo -c -o run_unittests-message_dictionary_unittest.o `test -f 'message_dictionary_unittest.cc' || echo '$(srcdir)/'`message_dictionary_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_dictionary_unittest.Tpo $(DEPDIR)/run_unittests-message_dictionary_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_dictionary_unittest.cc' object='run_unittests-message_dictionary_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_dictionary_unittest.o `test -f 'message_dictionary_unittest.cc' || echo '$(srcdir)/'`message_dictionary_unittest.cc
+
+run_unittests-message_dictionary_unittest.obj: message_dictionary_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_dictionary_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-message_dictionary_unittest.Tpo -c -o run_unittests-message_dictionary_unittest.obj `if test -f 'message_dictionary_unittest.cc'; then $(CYGPATH_W) 'message_dictionary_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_dictionary_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_dictionary_unittest.Tpo $(DEPDIR)/run_unittests-message_dictionary_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_dictionary_unittest.cc' object='run_unittests-message_dictionary_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_dictionary_unittest.obj `if test -f 'message_dictionary_unittest.cc'; then $(CYGPATH_W) 'message_dictionary_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_dictionary_unittest.cc'; fi`
+
+run_unittests-message_reader_unittest.o: message_reader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_reader_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-message_reader_unittest.Tpo -c -o run_unittests-message_reader_unittest.o `test -f 'message_reader_unittest.cc' || echo '$(srcdir)/'`message_reader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_reader_unittest.Tpo $(DEPDIR)/run_unittests-message_reader_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_reader_unittest.cc' object='run_unittests-message_reader_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_reader_unittest.o `test -f 'message_reader_unittest.cc' || echo '$(srcdir)/'`message_reader_unittest.cc
+
+run_unittests-message_reader_unittest.obj: message_reader_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_reader_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-message_reader_unittest.Tpo -c -o run_unittests-message_reader_unittest.obj `if test -f 'message_reader_unittest.cc'; then $(CYGPATH_W) 'message_reader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_reader_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_reader_unittest.Tpo $(DEPDIR)/run_unittests-message_reader_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_reader_unittest.cc' object='run_unittests-message_reader_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_reader_unittest.obj `if test -f 'message_reader_unittest.cc'; then $(CYGPATH_W) 'message_reader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_reader_unittest.cc'; fi`
+
+run_unittests-output_option_unittest.o: output_option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-output_option_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-output_option_unittest.Tpo -c -o run_unittests-output_option_unittest.o `test -f 'output_option_unittest.cc' || echo '$(srcdir)/'`output_option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-output_option_unittest.Tpo $(DEPDIR)/run_unittests-output_option_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='output_option_unittest.cc' object='run_unittests-output_option_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-output_option_unittest.o `test -f 'output_option_unittest.cc' || echo '$(srcdir)/'`output_option_unittest.cc
+
+run_unittests-output_option_unittest.obj: output_option_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-output_option_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-output_option_unittest.Tpo -c -o run_unittests-output_option_unittest.obj `if test -f 'output_option_unittest.cc'; then $(CYGPATH_W) 'output_option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/output_option_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-output_option_unittest.Tpo $(DEPDIR)/run_unittests-output_option_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='output_option_unittest.cc' object='run_unittests-output_option_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-output_option_unittest.obj `if test -f 'output_option_unittest.cc'; then $(CYGPATH_W) 'output_option_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/output_option_unittest.cc'; fi`
+
+run_unittests-buffer_appender_unittest.o: buffer_appender_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-buffer_appender_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-buffer_appender_unittest.Tpo -c -o run_unittests-buffer_appender_unittest.o `test -f 'buffer_appender_unittest.cc' || echo '$(srcdir)/'`buffer_appender_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-buffer_appender_unittest.Tpo $(DEPDIR)/run_unittests-buffer_appender_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_appender_unittest.cc' object='run_unittests-buffer_appender_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-buffer_appender_unittest.o `test -f 'buffer_appender_unittest.cc' || echo '$(srcdir)/'`buffer_appender_unittest.cc
+
+run_unittests-buffer_appender_unittest.obj: buffer_appender_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-buffer_appender_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-buffer_appender_unittest.Tpo -c -o run_unittests-buffer_appender_unittest.obj `if test -f 'buffer_appender_unittest.cc'; then $(CYGPATH_W) 'buffer_appender_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_appender_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-buffer_appender_unittest.Tpo $(DEPDIR)/run_unittests-buffer_appender_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_appender_unittest.cc' object='run_unittests-buffer_appender_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-buffer_appender_unittest.obj `if test -f 'buffer_appender_unittest.cc'; then $(CYGPATH_W) 'buffer_appender_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_appender_unittest.cc'; fi`
+
+run_unittests-log_test_messages.o: log_test_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-log_test_messages.o -MD -MP -MF $(DEPDIR)/run_unittests-log_test_messages.Tpo -c -o run_unittests-log_test_messages.o `test -f 'log_test_messages.cc' || echo '$(srcdir)/'`log_test_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-log_test_messages.Tpo $(DEPDIR)/run_unittests-log_test_messages.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_test_messages.cc' object='run_unittests-log_test_messages.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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-log_test_messages.o `test -f 'log_test_messages.cc' || echo '$(srcdir)/'`log_test_messages.cc
+
+run_unittests-log_test_messages.obj: log_test_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-log_test_messages.obj -MD -MP -MF $(DEPDIR)/run_unittests-log_test_messages.Tpo -c -o run_unittests-log_test_messages.obj `if test -f 'log_test_messages.cc'; then $(CYGPATH_W) 'log_test_messages.cc'; else $(CYGPATH_W) '$(srcdir)/log_test_messages.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-log_test_messages.Tpo $(DEPDIR)/run_unittests-log_test_messages.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_test_messages.cc' object='run_unittests-log_test_messages.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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-log_test_messages.obj `if test -f 'log_test_messages.cc'; then $(CYGPATH_W) 'log_test_messages.cc'; else $(CYGPATH_W) '$(srcdir)/log_test_messages.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_SCRIPTS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(SCRIPTS)
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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)/buffer_logger_test-buffer_logger_test.Po
+ -rm -f ./$(DEPDIR)/init_logger_test-init_logger_test.Po
+ -rm -f ./$(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Po
+ -rm -f ./$(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Po
+ -rm -f ./$(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Po
+ -rm -f ./$(DEPDIR)/logger_example-logger_example.Po
+ -rm -f ./$(DEPDIR)/logger_lock_test-log_test_messages.Po
+ -rm -f ./$(DEPDIR)/logger_lock_test-logger_lock_test.Po
+ -rm -f ./$(DEPDIR)/run_unittests-buffer_appender_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-log_formatter_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-log_test_messages.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_level_impl_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_level_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_name_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_specification_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_support_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-message_dictionary_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-message_reader_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-output_option_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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)/buffer_logger_test-buffer_logger_test.Po
+ -rm -f ./$(DEPDIR)/init_logger_test-init_logger_test.Po
+ -rm -f ./$(DEPDIR)/initializer_unittests_1-message_initializer_1_unittest.Po
+ -rm -f ./$(DEPDIR)/initializer_unittests_1-message_initializer_1a_unittest.Po
+ -rm -f ./$(DEPDIR)/initializer_unittests_1-run_initializer_unittests.Po
+ -rm -f ./$(DEPDIR)/logger_example-logger_example.Po
+ -rm -f ./$(DEPDIR)/logger_lock_test-log_test_messages.Po
+ -rm -f ./$(DEPDIR)/logger_lock_test-logger_lock_test.Po
+ -rm -f ./$(DEPDIR)/run_unittests-buffer_appender_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-log_formatter_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-log_test_messages.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_level_impl_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_level_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_manager_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_name_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_specification_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_support_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-logger_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-message_dictionary_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-message_reader_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-output_option_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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 maintainer-clean-local mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f log_test_messages.h log_test_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: log_test_messages.h log_test_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@log_test_messages.h log_test_messages.cc: log_test_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/log/tests/log_test_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages log_test_messages.h log_test_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/log/tests/buffer_appender_unittest.cc b/src/lib/log/tests/buffer_appender_unittest.cc
new file mode 100644
index 0000000..f52d833
--- /dev/null
+++ b/src/lib/log/tests/buffer_appender_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright (C) 2012-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 <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include <log/buffer_appender_impl.h>
+
+#include <log4cplus/loggingmacros.h>
+#include <log4cplus/logger.h>
+#include <log4cplus/nullappender.h>
+#include <log4cplus/spi/loggingevent.h>
+
+using namespace isc::log;
+using namespace isc::log::internal;
+
+namespace isc {
+namespace log {
+
+/// \brief Specialized test class
+///
+/// In order to test append() somewhat directly, this
+/// class implements one more method (addEvent)
+class TestBufferAppender : public BufferAppender {
+public:
+ void addEvent(const log4cplus::spi::InternalLoggingEvent& event) {
+ append(event);
+ }
+};
+
+class BufferAppenderTest : public ::testing::Test {
+protected:
+ BufferAppenderTest() : buffer_appender1(new TestBufferAppender()),
+ appender1(buffer_appender1),
+ buffer_appender2(new TestBufferAppender()),
+ appender2(buffer_appender2),
+ logger(log4cplus::Logger::getInstance("buffer"))
+ {
+ logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+ }
+
+ ~BufferAppenderTest() {
+ // If any log messages are left, we don't care, get rid of them,
+ // by flushing them to a null appender
+ // Given the 'messages should not get lost' approach of the logging
+ // system, not flushing them to a null appender would cause them
+ // to be dumped to stdout as the test is destroyed, making
+ // unnecessarily messy test output.
+ log4cplus::SharedAppenderPtr null_appender(
+ new log4cplus::NullAppender());
+ logger.removeAllAppenders();
+ logger.addAppender(null_appender);
+ buffer_appender1->flush();
+ buffer_appender2->flush();
+ }
+
+ TestBufferAppender* buffer_appender1;
+ log4cplus::SharedAppenderPtr appender1;
+ TestBufferAppender* buffer_appender2;
+ log4cplus::SharedAppenderPtr appender2;
+ log4cplus::Logger logger;
+};
+
+// Test that log events are indeed stored, and that they are
+// flushed to the new appenders of their logger
+TEST_F(BufferAppenderTest, flush) {
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+ // Create a Logger, log a few messages with the first appender
+ logger.addAppender(appender1);
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(1, buffer_appender1->getBufferSize());
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(2, buffer_appender1->getBufferSize());
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(3, buffer_appender1->getBufferSize());
+
+ // Second buffer should still be empty
+ ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+ // Replace the appender by the second one, and call flush;
+ // this should cause all events to be moved to the second buffer
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ buffer_appender1->flush();
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(3, buffer_appender2->getBufferSize());
+}
+
+// Once flushed, logging new messages with the same buffer should fail
+TEST_F(BufferAppenderTest, addAfterFlush) {
+ logger.addAppender(appender1);
+ buffer_appender1->flush();
+ EXPECT_THROW(LOG4CPLUS_INFO(logger, "Foo"), LogBufferAddAfterFlush);
+ // It should not have been added
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+
+ // But logging should work again as long as a different buffer is used
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(1, buffer_appender2->getBufferSize());
+}
+
+TEST_F(BufferAppenderTest, addDirectly) {
+ // A few direct calls
+ log4cplus::spi::InternalLoggingEvent event("buffer",
+ log4cplus::INFO_LOG_LEVEL,
+ "Bar", "file", 123);
+ buffer_appender1->addEvent(event);
+ ASSERT_EQ(1, buffer_appender1->getBufferSize());
+
+ // Do one from a smaller scope to make sure destruction doesn't harm
+ {
+ log4cplus::spi::InternalLoggingEvent event2("buffer",
+ log4cplus::INFO_LOG_LEVEL,
+ "Bar", "file", 123);
+ buffer_appender1->addEvent(event2);
+ }
+ ASSERT_EQ(2, buffer_appender1->getBufferSize());
+
+ // And flush them to the next
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ buffer_appender1->flush();
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(2, buffer_appender2->getBufferSize());
+}
+
+}
+}
diff --git a/src/lib/log/tests/buffer_logger_test.cc b/src/lib/log/tests/buffer_logger_test.cc
new file mode 100644
index 0000000..956e2f1
--- /dev/null
+++ b/src/lib/log/tests/buffer_logger_test.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2012-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 <log/macros.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <log/log_messages.h>
+#include <log/interprocess/interprocess_sync_null.h>
+
+using namespace isc::log;
+
+namespace {
+void usage() {
+ std::cout << "Usage: buffer_logger_test [-n]" << std::endl;
+}
+} // end unnamed namespace
+
+/// \brief Test InitLogger
+///
+/// A program used in testing the logger that initializes logging with
+/// buffering enabled, so that initial log messages are not immediately
+/// logged, but are not lost (they should be logged the moment process()
+/// is called.
+///
+/// If -n is given as an argument, process() is never called. In this
+/// case, upon exit, all leftover log messages should be printed to
+/// stdout, but without normal logging additions (such as time and
+/// logger name)
+int
+main(int argc, char** argv) {
+ bool do_process = true;
+ int opt;
+ while ((opt = getopt(argc, argv, "n")) != -1) {
+ switch (opt) {
+ case 'n':
+ do_process = false;
+ break;
+ default:
+ usage();
+ return (1);
+ }
+ }
+
+ // Note, level is INFO, so DEBUG should normally not show
+ // up. Unless process is never called (at which point it
+ // will end up in the dump at the end).
+ initLogger("buffertest", isc::log::INFO, 0, NULL, true);
+ Logger logger("log");
+ // No need for file interprocess locking in this test
+ logger.setInterprocessSync(
+ new isc::log::interprocess::InterprocessSyncNull("logger"));
+ LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+ LOG_DEBUG(logger, 50, LOG_BAD_DESTINATION).arg("debug-50");
+ LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+ // process should cause them to be logged
+ if (do_process) {
+ LoggerManager logger_manager;
+ logger_manager.process();
+ }
+ return (0);
+}
diff --git a/src/lib/log/tests/buffer_logger_test.sh.in b/src/lib/log/tests/buffer_logger_test.sh.in
new file mode 100644
index 0000000..975df97
--- /dev/null
+++ b/src/lib/log/tests/buffer_logger_test.sh.in
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# Copyright (C) 2012-2020 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/.
+
+# Checks that the initLogger() call uses for unit tests respects the setting of
+# the buffer value
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+tempfile="@abs_builddir@/buffer_logger_test_tempfile_$$"
+
+printf 'Checking that buffer initialization works:\n'
+
+test_start 'buffer-logger.buffer-including-process()-call'
+cat > $tempfile << .
+INFO [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+INFO [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test 2>&1 | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish 0
+
+test_start 'buffer-logger.buffer-excluding-process()-call'
+cat > $tempfile << .
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+DEBUG [buffertest.log]: LOG_BAD_DESTINATION unrecognized log destination: debug-50
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test -n 2>&1 | diff $tempfile -
+test_finish 0
+
+# Tidy up.
+rm -f $tempfile
diff --git a/src/lib/log/tests/console_test.sh.in b/src/lib/log/tests/console_test.sh.in
new file mode 100644
index 0000000..7c5ebeb
--- /dev/null
+++ b/src/lib/log/tests/console_test.sh.in
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# Copyright (C) 2011-2020 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/.
+
+# The logger supports the idea of a "console" logger than logs to either stdout
+# or stderr. This test checks that both these options work.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+tempfile="@abs_builddir@/console_test_tempfile_$$"
+
+# Look at tempfile and check that the count equals the expected count
+passfail() {
+ count=$(wc -l $tempfile | awk '{print $1}')
+ if [ "${count}" -eq "${1}" ]; then
+ test_finish 0
+ else
+ test_finish 1
+ fi
+}
+
+test_start 'console-output.stdout'
+rm -f $tempfile
+./logger_example -c stdout -s error 1> $tempfile 2> /dev/null
+passfail 4
+
+test_start 'console-output.stdout-not-stderr'
+rm -f $tempfile
+./logger_example -c stdout -s error 1> /dev/null 2> $tempfile
+passfail 0
+
+test_start 'console-output.stderr'
+rm -f $tempfile
+./logger_example -c stderr -s error 1> /dev/null 2> $tempfile
+passfail 4
+
+test_start 'console-output.stderr-not-stdout'
+rm -f $tempfile
+./logger_example -c stderr -s error 1> $tempfile 2> /dev/null
+passfail 0
+
+# Tidy up
+rm -f $tempfile
diff --git a/src/lib/log/tests/destination_test.sh.in b/src/lib/log/tests/destination_test.sh.in
new file mode 100644
index 0000000..86fc415
--- /dev/null
+++ b/src/lib/log/tests/destination_test.sh.in
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+# Copyright (C) 2011-2020 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/.
+
+# Checks that the logger will route messages to the chosen destination.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+tempfile="@abs_builddir@/destination_test_tempfile_$$"
+destfile1_tmp="@abs_builddir@/destination_test_destfile_1_tmp_$$"
+destfile2_tmp="@abs_builddir@/destination_test_destfile_2_tmp_$$"
+destfile1="@abs_builddir@/destination_test_destfile_1_$$"
+destfile2="@abs_builddir@/destination_test_destfile_2_$$"
+
+printf '1. One logger, multiple destinations:\n'
+
+cat > $tempfile << .
+FATAL [example] LOG_WRITE_ERROR error writing to test1: 42
+ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
+FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
+ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
+.
+rm -f $destfile1 $destfile2
+./logger_example -s error -f $destfile1_tmp -f $destfile2_tmp
+
+# strip the pids and thread ids
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
+# strip the thread ids
+
+test_start 'logger-destination.one-logger-file-1'
+cut -d' ' -f3- $destfile1 | diff $tempfile -
+test_finish $?
+
+test_start 'logger-destination.one-logger-file-2'
+cut -d' ' -f3- $destfile2 | diff $tempfile -
+test_finish $?
+
+# Tidy up.
+rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
+
+printf '2. Two loggers, different destinations and severities:\n'
+rm -f $destfile1 $destfile2
+./logger_example -l example -s info -f $destfile1_tmp -l alpha -s warn -f $destfile2_tmp
+
+# strip the pids and thread ids
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
+
+# All output for example and example.beta should have gone to destfile1.
+# Output for example.alpha should have done to destfile2.
+
+test_start 'logger-destination.multiples-loggers-file-1'
+cat > $tempfile << .
+FATAL [example] LOG_WRITE_ERROR error writing to test1: 42
+ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
+WARN [example] LOG_BAD_STREAM bad log console output stream: example
+FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
+ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
+WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
+INFO [example.beta] LOG_READ_ERROR error reading from message file beta: info
+.
+cut -d' ' -f3- $destfile1 | diff $tempfile -
+test_finish $?
+
+test_start 'logger-destination.multiples-loggers-file-2'
+cat > $tempfile << .
+WARN [example.alpha] LOG_READ_ERROR error reading from message file a.txt: dummy reason
+.
+cut -d' ' -f3- $destfile2 | diff $tempfile -
+test_finish $?
+
+# Tidy up.
+rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
diff --git a/src/lib/log/tests/init_logger_test.cc b/src/lib/log/tests/init_logger_test.cc
new file mode 100644
index 0000000..02862fd
--- /dev/null
+++ b/src/lib/log/tests/init_logger_test.cc
@@ -0,0 +1,36 @@
+// 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 <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+
+using namespace isc::log;
+
+/// \brief Test InitLogger
+///
+/// A program used in testing the logger that initializes logging using
+/// initLogger(), then outputs several messages at different severities and
+/// debug levels. An external script sets the environment variables and checks
+/// that they have the desired effect.
+
+int
+main(int, char**) {
+ initLogger();
+ Logger logger("log");
+
+ LOG_DEBUG(logger, 0, LOG_BAD_DESTINATION).arg("debug-0");
+ LOG_DEBUG(logger, 50, LOG_BAD_DESTINATION).arg("debug-50");
+ LOG_DEBUG(logger, 99, LOG_BAD_DESTINATION).arg("debug-99");
+ LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+ LOG_WARN(logger, LOG_BAD_STREAM).arg("warn");
+ LOG_ERROR(logger, LOG_DUPLICATE_MESSAGE_ID).arg("error");
+ LOG_FATAL(logger, LOG_NO_MESSAGE_ID).arg("fatal");
+
+ return (0);
+}
diff --git a/src/lib/log/tests/init_logger_test.sh.in b/src/lib/log/tests/init_logger_test.sh.in
new file mode 100644
index 0000000..348008d
--- /dev/null
+++ b/src/lib/log/tests/init_logger_test.sh.in
@@ -0,0 +1,96 @@
+#!/bin/sh
+
+# Copyright (C) 2011-2020 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/.
+
+# Checks that the initLogger() call uses for unit tests respects the setting of
+# the environment variables.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+tempfile="@abs_builddir@/init_logger_test_tempfile_$$"
+destfile_tmp="@abs_builddir@/init_logger_test_destfile_tmp_$$"
+destfile="@abs_builddir@/init_logger_test_destfile_$$"
+
+printf '1. Checking that KEA_LOGGER_SEVERITY/KEA_LOGGER_DBGLEVEL work:\n'
+
+test_start 'init-logger.severity=DEBUG,dbglevel=99'
+cat > $tempfile << .
+DEBUG [kea.log] LOG_BAD_DESTINATION unrecognized log destination: debug-0
+DEBUG [kea.log] LOG_BAD_DESTINATION unrecognized log destination: debug-50
+DEBUG [kea.log] LOG_BAD_DESTINATION unrecognized log destination: debug-99
+INFO [kea.log] LOG_BAD_SEVERITY unrecognized log severity: info
+WARN [kea.log] LOG_BAD_STREAM bad log console output stream: warn
+ERROR [kea.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in compiled code
+FATAL [kea.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
+.
+KEA_LOGGER_DESTINATION=stdout KEA_LOGGER_SEVERITY=DEBUG KEA_LOGGER_DBGLEVEL=99 ./init_logger_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+test_start 'init-logger.severity=DEBUG,dbglevel=50'
+cat > $tempfile << .
+DEBUG [kea.log] LOG_BAD_DESTINATION unrecognized log destination: debug-0
+DEBUG [kea.log] LOG_BAD_DESTINATION unrecognized log destination: debug-50
+INFO [kea.log] LOG_BAD_SEVERITY unrecognized log severity: info
+WARN [kea.log] LOG_BAD_STREAM bad log console output stream: warn
+ERROR [kea.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in compiled code
+FATAL [kea.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
+.
+KEA_LOGGER_DESTINATION=stdout KEA_LOGGER_SEVERITY=DEBUG KEA_LOGGER_DBGLEVEL=50 ./init_logger_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+test_start 'init-logger.severity=WARN'
+cat > $tempfile << .
+WARN [kea.log] LOG_BAD_STREAM bad log console output stream: warn
+ERROR [kea.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in compiled code
+FATAL [kea.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
+.
+KEA_LOGGER_DESTINATION=stdout KEA_LOGGER_SEVERITY=WARN ./init_logger_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+printf '2. Checking that KEA_LOGGER_DESTINATION works:\n'
+
+test_start 'init-logger.stdout'
+cat > $tempfile << .
+FATAL [kea.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
+.
+rm -f $destfile_tmp $destfile
+KEA_LOGGER_SEVERITY=FATAL KEA_LOGGER_DESTINATION=stdout ./init_logger_test 1> $destfile_tmp
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
+cut -d' ' -f3- $destfile | diff $tempfile -
+test_finish $?
+
+test_start 'init-logger.stderr'
+rm -f $destfile_tmp $destfile
+KEA_LOGGER_SEVERITY=FATAL KEA_LOGGER_DESTINATION=stderr ./init_logger_test 2> $destfile_tmp
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
+cut -d' ' -f3- $destfile | diff $tempfile -
+test_finish $?
+
+test_start 'init-logger.file'
+rm -f $destfile_tmp $destfile
+KEA_LOGGER_SEVERITY=FATAL KEA_LOGGER_DESTINATION=$destfile_tmp ./init_logger_test
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
+cut -d' ' -f3- $destfile | diff $tempfile -
+test_finish $?
+
+# Note: can't automatically test syslog output.
+
+# Tidy up.
+rm -f $tempfile $destfile_tmp $destfile
diff --git a/src/lib/log/tests/local_file_test.sh.in b/src/lib/log/tests/local_file_test.sh.in
new file mode 100644
index 0000000..c1e7a53
--- /dev/null
+++ b/src/lib/log/tests/local_file_test.sh.in
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+# Copyright (C) 2011-2020 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/.
+
+# Checks that a local message file can override the definitions in the message
+# dictionary.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+localmes="@abs_builddir@/localdef_mes_$$"
+tempfile="@abs_builddir@/run_time_init_test_tempfile_$$"
+
+# Create the local message file for testing
+
+cat > $localmes << .
+% LOG_NOTHERE this message is not in the global dictionary
+% LOG_READ_ERROR replacement read error, parameters: '%1' and '%2'
+% LOG_READING_LOCAL_FILE replacement read local message file, parameter is '%1'
+.
+
+test_start 'local-file.local-message-replacement'
+cat > $tempfile << .
+WARN [example.log] LOG_NO_SUCH_MESSAGE could not replace message text for 'LOG_NOTHERE': no such message
+FATAL [example] LOG_WRITE_ERROR error writing to test1: 42
+ERROR [example] LOG_READING_LOCAL_FILE replacement read local message file, parameter is 'dummy/file'
+WARN [example] LOG_BAD_STREAM bad log console output stream: example
+WARN [example.alpha] LOG_READ_ERROR replacement read error, parameters: 'a.txt' and 'dummy reason'
+FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
+ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
+WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
+.
+./logger_example -c stdout -s warn $localmes | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+test_start 'local-file.read-error-reporting'
+cat > $tempfile << .
+ERROR [example.log] LOG_INPUT_OPEN_FAIL unable to open message file $localmes for input: No such file or directory
+FATAL [example] LOG_WRITE_ERROR error writing to test1: 42
+ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
+WARN [example] LOG_BAD_STREAM bad log console output stream: example
+WARN [example.alpha] LOG_READ_ERROR error reading from message file a.txt: dummy reason
+FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
+ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
+WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
+.
+rm -f $localmes
+./logger_example -c stdout -s warn $localmes | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+# Tidy up.
+rm -f $tempfile
diff --git a/src/lib/log/tests/log_formatter_unittest.cc b/src/lib/log/tests/log_formatter_unittest.cc
new file mode 100644
index 0000000..4bd0405
--- /dev/null
+++ b/src/lib/log/tests/log_formatter_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright (C) 2011-2022 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/resource.h>
+#include <util/unittests/check_valgrind.h>
+
+#include <log/log_formatter.h>
+#include <log/logger_level.h>
+
+#include <vector>
+#include <string>
+
+using namespace std;
+
+namespace {
+
+class FormatterTest : public ::testing::Test {
+protected:
+ typedef pair<isc::log::Severity, string> Output;
+ typedef isc::log::Formatter<FormatterTest> Formatter;
+ vector<Output> outputs;
+public:
+ void output(const isc::log::Severity& prefix, const string& message) {
+ outputs.push_back(Output(prefix, message));
+ }
+ // Just shortcut for new string
+ boost::shared_ptr<string> s(const char* text) {
+ return (boost::make_shared<string>(text));
+ }
+};
+
+// Create an inactive formatter and check it doesn't produce any output
+TEST_F(FormatterTest, inactive) {
+ Formatter();
+ EXPECT_EQ(0, outputs.size());
+}
+
+// Create an active formatter and check it produces output. Does no arg
+// substitution yet
+TEST_F(FormatterTest, active) {
+ Formatter(isc::log::INFO, s("Text of message"), this);
+ ASSERT_EQ(1, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("Text of message", outputs[0].second);
+}
+
+// No output even when we have an arg on the inactive formatter
+TEST_F(FormatterTest, inactiveArg) {
+ Formatter().arg("Hello");
+ EXPECT_EQ(0, outputs.size());
+}
+
+// Create an active formatter and replace a placeholder with string
+TEST_F(FormatterTest, stringArg) {
+ {
+ SCOPED_TRACE("C++ string");
+ Formatter(isc::log::INFO, s("Hello %1"), this).arg(string("World"));
+ ASSERT_EQ(1, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("Hello World", outputs[0].second);
+ }
+ {
+ SCOPED_TRACE("C++ string");
+ Formatter(isc::log::INFO, s("Hello %1"), this).arg(string("Internet"));
+ ASSERT_EQ(2, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[1].first);
+ EXPECT_EQ("Hello Internet", outputs[1].second);
+ }
+}
+
+// Test the .deactivate() method
+TEST_F(FormatterTest, deactivate) {
+ Formatter(isc::log::INFO, s("Text of message"), this).deactivate();
+ // If there was no .deactivate, it should have output it.
+ // But not now.
+ ASSERT_EQ(0, outputs.size());
+}
+
+// Can convert to string
+TEST_F(FormatterTest, intArg) {
+ Formatter(isc::log::INFO, s("The answer is %1"), this).arg(42);
+ ASSERT_EQ(1, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("The answer is 42", outputs[0].second);
+}
+
+// Can use multiple arguments at different places
+TEST_F(FormatterTest, multiArg) {
+ Formatter(isc::log::INFO, s("The %2 are %1"), this).arg("switched").
+ arg("arguments");
+ ASSERT_EQ(1, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("The arguments are switched", outputs[0].second);
+}
+
+#ifdef ENABLE_LOGGER_CHECKS
+
+TEST_F(FormatterTest, mismatchedPlaceholders) {
+ // Throws MismatchedPlaceholders exception if the placeholder is missing
+ // for a supplied argument.
+ EXPECT_THROW(Formatter(isc::log::INFO, s("Missing the second %1"), this).
+ arg("argument").arg("missing"),
+ isc::log::MismatchedPlaceholders);
+
+#ifdef EXPECT_DEATH
+ // Likewise, if there's a redundant placeholder (or missing argument), the
+ // check detects it and aborts the program. Due to the restriction of the
+ // current implementation, it doesn't throw.
+ if (!isc::util::unittests::runningOnValgrind()) {
+ EXPECT_DEATH({
+ isc::util::unittests::dontCreateCoreDumps();
+ Formatter(isc::log::INFO, s("Too many arguments in %1 %2"), this).
+ arg("only one");
+ }, ".*");
+ }
+#endif /* EXPECT_DEATH */
+ // Mixed case of above two: the exception will be thrown due to the missing
+ // placeholder. The other check is disabled due to that.
+ EXPECT_THROW(Formatter(isc::log::INFO, s("Missing the first %2"), this).
+ arg("missing").arg("argument"),
+ isc::log::MismatchedPlaceholders);
+}
+
+#else
+
+// If logger checks are not enabled, nothing is thrown
+TEST_F(FormatterTest, mismatchedPlaceholders) {
+ Formatter(isc::log::INFO, s("Missing the second %1"), this).
+ arg("argument").arg("missing");
+ ASSERT_EQ(1, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("Missing the second argument "
+ "@@Missing logger placeholder '%2' for value 'missing'@@",
+ outputs[0].second);
+
+ EXPECT_NO_THROW(Formatter(isc::log::INFO,
+ s("Too many arguments in %1 %2"), this).
+ arg("only one"));
+ ASSERT_EQ(2, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[1].first);
+ EXPECT_EQ("Too many arguments in only one %2 "
+ "@@Excess logger placeholder '%2' still exists@@",
+ outputs[1].second);
+
+ EXPECT_NO_THROW(Formatter(isc::log::INFO, s("Missing the first %2"), this).
+ arg("missing").arg("argument"));
+ ASSERT_EQ(3, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[2].first);
+ EXPECT_EQ("Missing the first argument "
+ "@@Missing logger placeholder '%1' for value 'missing'@@",
+ outputs[2].second);
+}
+
+#endif /* ENABLE_LOGGER_CHECKS */
+
+// Can replace multiple placeholders
+TEST_F(FormatterTest, multiPlaceholder) {
+ Formatter(isc::log::INFO, s("The %1 is the %1"), this).
+ arg("first rule of tautology club");
+ ASSERT_EQ(1, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("The first rule of tautology club is "
+ "the first rule of tautology club", outputs[0].second);
+}
+
+// Test we can cope with replacement containing the placeholder
+TEST_F(FormatterTest, noRecurse) {
+ // If we recurse, this will probably eat all the memory and crash
+ Formatter(isc::log::INFO, s("%1"), this).arg("%1 %1");
+ ASSERT_EQ(1, outputs.size());
+ EXPECT_EQ(isc::log::INFO, outputs[0].first);
+ EXPECT_EQ("%1 %1", outputs[0].second);
+}
+
+}
diff --git a/src/lib/log/tests/log_test_messages.cc b/src/lib/log/tests/log_test_messages.cc
new file mode 100644
index 0000000..5cb25ad
--- /dev/null
+++ b/src/lib/log/tests/log_test_messages.cc
@@ -0,0 +1,25 @@
+// File created from ../../../../src/lib/log/tests/log_test_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID LOG_LOCK_TEST_MESSAGE = "LOG_LOCK_TEST_MESSAGE";
+
+} // namespace log
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "LOG_LOCK_TEST_MESSAGE", "this is a test message.",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/log/tests/log_test_messages.h b/src/lib/log/tests/log_test_messages.h
new file mode 100644
index 0000000..96553f5
--- /dev/null
+++ b/src/lib/log/tests/log_test_messages.h
@@ -0,0 +1,16 @@
+// File created from ../../../../src/lib/log/tests/log_test_messages.mes
+
+#ifndef LOG_TEST_MESSAGES_H
+#define LOG_TEST_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+extern const isc::log::MessageID LOG_LOCK_TEST_MESSAGE;
+
+} // namespace log
+} // namespace isc
+
+#endif // LOG_TEST_MESSAGES_H
diff --git a/src/lib/log/tests/log_test_messages.mes b/src/lib/log/tests/log_test_messages.mes
new file mode 100644
index 0000000..f7bee43
--- /dev/null
+++ b/src/lib/log/tests/log_test_messages.mes
@@ -0,0 +1,18 @@
+# Copyright (C) 2012-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/.
+
+# \brief Message Utility Message File
+#
+# This is the source of the set of messages generated by the message and
+# logging components. The associated .h and .cc files are created by hand from
+# this file though and are not built during the build process; this is to avoid
+# the chicken-and-egg situation where we need the files to build the message
+# compiler, yet we need the compiler to build the files.
+
+$NAMESPACE isc::log
+
+% LOG_LOCK_TEST_MESSAGE this is a test message.
+This is a log message used in testing.
diff --git a/src/lib/log/tests/logger_example.cc b/src/lib/log/tests/logger_example.cc
new file mode 100644
index 0000000..ff3d512
--- /dev/null
+++ b/src/lib/log/tests/logger_example.cc
@@ -0,0 +1,306 @@
+// Copyright (C) 2011-2020 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/.
+
+/// \brief Example Program
+///
+/// Simple example program showing how to use the logger. The various
+/// command-line options let most aspects of the logger be exercised, so
+/// making this a useful tool for testing.
+///
+/// See the usage() method for details of use.
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <util/strutil.h>
+
+#include <log/logger.h>
+#include <log/logger_level.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <log/logger_specification.h>
+#include <log/macros.h>
+
+// Include a set of message definitions.
+#include <log/log_messages.h>
+#include <log/interprocess/interprocess_sync_null.h>
+
+using namespace isc::log;
+using namespace std;
+using isc::log::interprocess::InterprocessSyncNull;
+
+// Print usage information
+
+void usage() {
+ cout <<
+"logger_support_test [-h | [logger_spec] [[logger_spec]...]]\n"
+"\n"
+" -h Print this message and exit\n"
+"\n"
+"The rest of the command line comprises the set of logger specifications.\n"
+"Each specification is of the form:\n"
+"\n"
+" -l logger [-s severity] [-d dbglevel] output_spec] [[output_spec] ...\n"
+"\n"
+"where:\n"
+"\n"
+" -l logger Give the name of the logger to which the following\n"
+" output specifications will apply.\n"
+"\n"
+"Each logger is followed by the indication of the severity it is logging\n"
+"and, if applicable, its debug level:\n"
+"\n"
+" -d dbglevel Debug level. Only interpreted if the severity is 'debug'\n"
+" this is a number between 0 and 99.\n"
+" -s severity Set the severity of messages output. 'severity' is one\n"
+" of 'debug', 'info', 'warn', 'error', 'fatal', the default\n"
+" being 'info'.\n"
+"\n"
+"The output specifications - there may be more than one per logger - detail\n"
+"the output streams attached to the logger. These are of the form:\n"
+"\n"
+" -c stream | -f file [-m maxver] [-z maxsize] | -y facility\n"
+"\n"
+"These are:\n"
+"\n"
+" -c stream Send output to the console. 'stream' is one of 'stdout'\n"
+" of 'stderr'.\n"
+" -f file Send output to specified file, appending to existing file\n"
+" if one exists.\n"
+" -y facility Send output to the syslog file with the given facility\n"
+" name (e.g. local1, cron etc.)\n"
+"\n"
+"The following can be specified for the file logger:\n"
+"\n"
+" -m maxver If file rolling is selected (by the maximum file size being\n"
+" non-zero), the maximum number of versions to keep (defaults\n"
+" to 0)\n"
+" -z maxsize Maximum size of the file before the file is closed and a\n"
+" new one opened. The default of 0 means no maximum size.\n"
+"\n"
+"If none of -c, -f or -y is given, by default, output is sent to stdout. If no\n"
+"logger is specified, the default is the program's root logger ('example').\n";
+
+}
+
+
+// The program sets the attributes on the root logger and logs a set of
+// messages. Looking at the output determines whether the program worked.
+
+int main(int argc, char** argv) {
+ const char* ROOT_NAME = "example";
+
+ bool sw_found = false; // Set true if switch found
+ bool c_found = false; // Set true if "-c" found
+ bool f_found = false; // Set true if "-f" found
+ bool y_found = false; // Set true if "-y" found
+ int option; // For getopt() processing
+ OutputOption def_opt; // Default output option - used
+ // for initialization
+ LoggerSpecification cur_spec(ROOT_NAME);// Current specification
+ OutputOption cur_opt; // Current output option
+ vector<LoggerSpecification> loggers; // Set of logger specifications
+ std::string severity; // Severity set for logger
+
+ // Initialize logging system - set the root logger name.
+ LoggerManager manager;
+ manager.init(ROOT_NAME);
+
+ // In the parsing loop that follows, the construction of the logging
+ // specification is always "one behind". In other words, the parsing of
+ // command-line options updates the current logging specification/output
+ // options. When the flag indicating a new logger or output specification
+ // is encountered, the previous one is added to the list.
+ //
+ // One complication is that there is deemed to be a default active when
+ // the parsing starts (console output for the Kea root logger). This
+ // is included in the logging specifications UNLESS the first switch on
+ // the command line is a "-l" flag starting a new logger. To track this,
+ // the "sw_found" flag is set when a switch is completely processed. The
+ // processing of "-l" will only add information for a previous logger to
+ // the list if this flag is set.
+ while ((option = getopt(argc, argv, "hc:d:f:l:m:s:y:z:")) != -1) {
+ switch (option) {
+ case 'c': // Console output
+ // New output spec. If one was currently active, add it to the
+ // list and reset the current output option to the defaults.
+ if (c_found || f_found || y_found) {
+ cur_spec.addOutputOption(cur_opt);
+ cur_opt = def_opt;
+ f_found = y_found = false;
+ }
+
+ // Set the output option for this switch.
+ c_found = true;
+ cur_opt.destination = OutputOption::DEST_CONSOLE;
+ if (strcmp(optarg, "stdout") == 0) {
+ cur_opt.stream = OutputOption::STR_STDOUT;
+
+ } else if (strcmp(optarg, "stderr") == 0) {
+ cur_opt.stream = OutputOption::STR_STDERR;
+
+ } else {
+ cerr << "Unrecognized console option: " << optarg << "\n";
+ return (1);
+ }
+ break;
+
+ case 'd': // Debug level
+ cur_spec.setDbglevel(boost::lexical_cast<int>(optarg));
+ break;
+
+ case 'f': // File output specification
+ // New output spec. If one was currently active, add it to the
+ // list and reset the current output option to the defaults.
+ if (c_found || f_found || y_found) {
+ cur_spec.addOutputOption(cur_opt);
+ cur_opt = def_opt;
+ c_found = y_found = false;
+ }
+
+ // Set the output option for this switch.
+ f_found = true;
+ cur_opt.destination = OutputOption::DEST_FILE;
+ cur_opt.filename = optarg;
+ break;
+
+ case 'h': // Help
+ usage();
+ return (0);
+
+ case 'l': // Logger
+ // If a current specification is active, add the last output option
+ // to it, add it to the list and reset. A specification is active
+ // if at least one switch has been previously found.
+ if (sw_found) {
+ cur_spec.addOutputOption(cur_opt);
+ loggers.push_back(cur_spec);
+ cur_spec.reset();
+ }
+
+ // Set the logger name
+ cur_spec.setName(std::string(optarg));
+
+ // Reset the output option to the default.
+ cur_opt = def_opt;
+
+ // Indicate nothing is found to prevent the console option (the
+ // default output option) being added to the output list if an
+ // output option is found.
+ c_found = f_found = y_found = false;
+ break;
+
+ case 'm': // Maximum file version
+ if (!f_found) {
+ std::cerr << "Attempt to set maximum version (-m) "
+ "outside of file output specification\n";
+ return (1);
+ }
+ try {
+ cur_opt.maxver = boost::lexical_cast<unsigned int>(optarg);
+ } catch (const boost::bad_lexical_cast&) {
+ std::cerr << "Maximum version (-m) argument must be a positive "
+ "integer\n";
+ return (1);
+ }
+ break;
+
+ case 's': // Severity
+ severity = optarg;
+ isc::util::str::uppercase(severity);
+ cur_spec.setSeverity(getSeverity(severity));
+ break;
+
+ case 'y': // Syslog output
+ // New output spec. If one was currently active, add it to the
+ // list and reset the current output option to the defaults.
+ if (c_found || f_found || y_found) {
+ cur_spec.addOutputOption(cur_opt);
+ cur_opt = def_opt;
+ c_found = f_found = false;
+ }
+ y_found = true;
+ cur_opt.destination = OutputOption::DEST_SYSLOG;
+ cur_opt.facility = optarg;
+ break;
+
+ case 'z': // Maximum size
+ if (! f_found) {
+ std::cerr << "Attempt to set file size (-z) "
+ "outside of file output specification\n";
+ return (1);
+ }
+ try {
+ cur_opt.maxsize = boost::lexical_cast<size_t>(optarg);
+ } catch (const boost::bad_lexical_cast&) {
+ std::cerr << "File size (-z) argument must be a positive "
+ "integer\n";
+ return (1);
+ }
+ break;
+
+
+ default:
+ std::cerr << "Unrecognized option: " <<
+ static_cast<char>(option) << "\n";
+ return (1);
+ }
+
+ // Have found at least one command-line switch, so note the fact.
+ sw_found = true;
+ }
+
+ // Add the current (unfinished specification) to the list.
+ cur_spec.addOutputOption(cur_opt);
+ loggers.push_back(cur_spec);
+
+ // Set the logging options.
+ manager.process(loggers.begin(), loggers.end());
+
+ // Set the local file
+ if (optind < argc) {
+ LoggerManager::readLocalMessageFile(argv[optind]);
+ }
+
+ // Log a few messages to different loggers. Here, we switch to using
+ // null interprocess sync objects for the loggers below as the
+ // logger example can be used as a standalone program (which may not
+ // have write access to a local state directory to create
+ // lockfiles).
+ isc::log::Logger logger_ex(ROOT_NAME);
+ logger_ex.setInterprocessSync(new InterprocessSyncNull("logger"));
+ isc::log::Logger logger_alpha("alpha");
+ logger_alpha.setInterprocessSync(new InterprocessSyncNull("logger"));
+ isc::log::Logger logger_beta("beta");
+ logger_beta.setInterprocessSync(new InterprocessSyncNull("logger"));
+
+ LOG_FATAL(logger_ex, LOG_WRITE_ERROR).arg("test1").arg("42");
+ LOG_ERROR(logger_ex, LOG_READING_LOCAL_FILE).arg("dummy/file");
+ LOG_WARN(logger_ex, LOG_BAD_STREAM).arg("example");
+ LOG_WARN(logger_alpha, LOG_READ_ERROR).arg("a.txt").arg("dummy reason");
+ LOG_INFO(logger_alpha, LOG_INPUT_OPEN_FAIL).arg("example.msg").arg("dummy reason");
+ LOG_DEBUG(logger_ex, 0, LOG_READING_LOCAL_FILE).arg("example/0");
+ LOG_DEBUG(logger_ex, 24, LOG_READING_LOCAL_FILE).arg("example/24");
+ LOG_DEBUG(logger_ex, 25, LOG_READING_LOCAL_FILE).arg("example/25");
+ LOG_DEBUG(logger_ex, 26, LOG_READING_LOCAL_FILE).arg("example/26");
+ LOG_FATAL(logger_beta, LOG_BAD_SEVERITY).arg("beta_fatal");
+ LOG_ERROR(logger_beta, LOG_BAD_DESTINATION).arg("beta_error");
+ LOG_WARN(logger_beta, LOG_BAD_STREAM).arg("beta_warn");
+ LOG_INFO(logger_beta, LOG_READ_ERROR).arg("beta").arg("info");
+ LOG_DEBUG(logger_beta, 25, LOG_BAD_SEVERITY).arg("beta/25");
+ LOG_DEBUG(logger_beta, 26, LOG_BAD_SEVERITY).arg("beta/26");
+
+ return (0);
+}
diff --git a/src/lib/log/tests/logger_level_impl_unittest.cc b/src/lib/log/tests/logger_level_impl_unittest.cc
new file mode 100644
index 0000000..c82be69
--- /dev/null
+++ b/src/lib/log/tests/logger_level_impl_unittest.cc
@@ -0,0 +1,168 @@
+// 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 <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <log/logger_level_impl.h>
+#include <log/logger_support.h>
+#include <log4cplus/logger.h>
+
+using namespace isc::log;
+using namespace std;
+
+class LoggerLevelImplTest : public ::testing::Test {
+protected:
+ LoggerLevelImplTest() {
+ // Ensure logging set to default for unit tests
+ setDefaultLoggingOutput();
+ }
+
+ ~LoggerLevelImplTest()
+ {}
+};
+
+
+// Checks that the log4cplus and Kea levels convert correctly
+TEST_F(LoggerLevelImplTest, DefaultConversionFromBind) {
+ log4cplus::LogLevel fatal =
+ LoggerLevelImpl::convertFromBindLevel(Level(FATAL));
+ EXPECT_EQ(log4cplus::FATAL_LOG_LEVEL, fatal);
+
+ log4cplus::LogLevel error =
+ LoggerLevelImpl::convertFromBindLevel(Level(ERROR));
+ EXPECT_EQ(log4cplus::ERROR_LOG_LEVEL, error);
+
+ log4cplus::LogLevel warn =
+ LoggerLevelImpl::convertFromBindLevel(Level(WARN));
+ EXPECT_EQ(log4cplus::WARN_LOG_LEVEL, warn);
+
+ log4cplus::LogLevel info =
+ LoggerLevelImpl::convertFromBindLevel(Level(INFO));
+ EXPECT_EQ(log4cplus::INFO_LOG_LEVEL, info);
+
+ log4cplus::LogLevel debug =
+ LoggerLevelImpl::convertFromBindLevel(Level(DEBUG));
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL, debug);
+}
+
+// Checks that the debug severity and level converts correctly
+TEST_F(LoggerLevelImplTest, DebugConversionFromBind) {
+ log4cplus::LogLevel debug0 =
+ LoggerLevelImpl::convertFromBindLevel(Level(DEBUG, 0));
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL - 0, debug0);
+
+ log4cplus::LogLevel debug1 =
+ LoggerLevelImpl::convertFromBindLevel(Level(DEBUG, 1));
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL - 1, debug1);
+
+ log4cplus::LogLevel debug99 =
+ LoggerLevelImpl::convertFromBindLevel(Level(DEBUG, 99));
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL - 99, debug99);
+
+ // Out of range should be coerced to the nearest boundary
+ log4cplus::LogLevel debug_1 =
+ LoggerLevelImpl::convertFromBindLevel(Level(DEBUG, MIN_DEBUG_LEVEL - 1));
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL, debug_1);
+
+ log4cplus::LogLevel debug100 =
+ LoggerLevelImpl::convertFromBindLevel(Level(DEBUG, MAX_DEBUG_LEVEL + 1));
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL - MAX_DEBUG_LEVEL, debug100);
+}
+
+// Do the checks the other way
+static void
+test_convert_to(const char* trace, isc::log::Severity severity, int dbglevel,
+ log4cplus::LogLevel level)
+{
+ SCOPED_TRACE(trace);
+ Level test = LoggerLevelImpl::convertToBindLevel(level);
+ EXPECT_EQ(severity, test.severity);
+ EXPECT_EQ(dbglevel, test.dbglevel);
+}
+
+TEST_F(LoggerLevelImplTest, ConversionToBind) {
+ test_convert_to("FATAL", FATAL, MIN_DEBUG_LEVEL, log4cplus::FATAL_LOG_LEVEL);
+ test_convert_to("ERROR", ERROR, MIN_DEBUG_LEVEL, log4cplus::ERROR_LOG_LEVEL);
+ test_convert_to("WARN", WARN , MIN_DEBUG_LEVEL, log4cplus::WARN_LOG_LEVEL);
+ test_convert_to("INFO", INFO , MIN_DEBUG_LEVEL, log4cplus::INFO_LOG_LEVEL);
+ test_convert_to("DEBUG", DEBUG, MIN_DEBUG_LEVEL, log4cplus::DEBUG_LOG_LEVEL);
+
+ test_convert_to("DEBUG0", DEBUG, MIN_DEBUG_LEVEL + 0,
+ (log4cplus::DEBUG_LOG_LEVEL));
+ test_convert_to("DEBUG1", DEBUG, MIN_DEBUG_LEVEL + 1,
+ (log4cplus::DEBUG_LOG_LEVEL - 1));
+ test_convert_to("DEBUG2", DEBUG, MIN_DEBUG_LEVEL + 2,
+ (log4cplus::DEBUG_LOG_LEVEL - 2));
+ test_convert_to("DEBUG99", DEBUG, MIN_DEBUG_LEVEL + 99,
+ (log4cplus::DEBUG_LOG_LEVEL - 99));
+
+ // ... and some invalid valid values
+ test_convert_to("DEBUG-1", INFO, MIN_DEBUG_LEVEL,
+ (log4cplus::DEBUG_LOG_LEVEL + 1));
+ BOOST_STATIC_ASSERT(MAX_DEBUG_LEVEL == 99);
+ test_convert_to("DEBUG+100", DEFAULT, 0,
+ (log4cplus::DEBUG_LOG_LEVEL - MAX_DEBUG_LEVEL - 1));
+}
+
+// Check that we can convert from a string to the new log4cplus levels
+TEST_F(LoggerLevelImplTest, FromString) {
+
+ // Test all valid values
+ for (int i = MIN_DEBUG_LEVEL; i <= MAX_DEBUG_LEVEL; ++i) {
+ std::string token = string("DEBUG") + boost::lexical_cast<std::string>(i);
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL - i,
+ LoggerLevelImpl::logLevelFromString(token));
+ }
+
+ // ... in lowercase too
+ for (int i = MIN_DEBUG_LEVEL; i <= MAX_DEBUG_LEVEL; ++i) {
+ std::string token = string("debug") + boost::lexical_cast<std::string>(i);
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL - i,
+ LoggerLevelImpl::logLevelFromString(token));
+ }
+
+ // A few below the minimum
+ for (int i = MIN_DEBUG_LEVEL - 5; i < MIN_DEBUG_LEVEL; ++i) {
+ std::string token = string("DEBUG") + boost::lexical_cast<std::string>(i);
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL, LoggerLevelImpl::logLevelFromString(token));
+ }
+
+ // ... and above the maximum
+ for (int i = MAX_DEBUG_LEVEL + 1; i < MAX_DEBUG_LEVEL + 5; ++i) {
+ std::string token = string("DEBUG") + boost::lexical_cast<std::string>(i);
+ EXPECT_EQ(log4cplus::DEBUG_LOG_LEVEL - MAX_DEBUG_LEVEL,
+ LoggerLevelImpl::logLevelFromString(token));
+ }
+
+ // Invalid strings.
+ EXPECT_EQ(log4cplus::NOT_SET_LOG_LEVEL,
+ LoggerLevelImpl::logLevelFromString("DEBU"));
+ EXPECT_EQ(log4cplus::NOT_SET_LOG_LEVEL,
+ LoggerLevelImpl::logLevelFromString("unrecognized"));
+}
+
+// ... and check the conversion back again. All levels should convert to "DEBUG".
+TEST_F(LoggerLevelImplTest, ToString) {
+
+ for (int i = MIN_DEBUG_LEVEL; i <= MAX_DEBUG_LEVEL; ++i) {
+ EXPECT_EQ(std::string("DEBUG"),
+ LoggerLevelImpl::logLevelToString(log4cplus::DEBUG_LOG_LEVEL - i));
+ }
+
+ // ... and that out of range stuff returns an empty string.
+ EXPECT_EQ(std::string(),
+ LoggerLevelImpl::logLevelToString(log4cplus::DEBUG_LOG_LEVEL + 1));
+ EXPECT_EQ(std::string(),
+ LoggerLevelImpl::logLevelToString(
+ log4cplus::DEBUG_LOG_LEVEL - MAX_DEBUG_LEVEL - 100));
+}
diff --git a/src/lib/log/tests/logger_level_unittest.cc b/src/lib/log/tests/logger_level_unittest.cc
new file mode 100644
index 0000000..ccaecd9
--- /dev/null
+++ b/src/lib/log/tests/logger_level_unittest.cc
@@ -0,0 +1,78 @@
+// 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 <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/logger.h>
+#include <log/logger_manager.h>
+#include <log/log_messages.h>
+#include <log/logger_support.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class LoggerLevelTest : public ::testing::Test {
+protected:
+ LoggerLevelTest() {
+ // Logger initialization is done in main(). As logging tests may
+ // alter the default logging output, it is reset here.
+ setDefaultLoggingOutput();
+ }
+ ~LoggerLevelTest() {
+ LoggerManager::reset();
+ }
+};
+
+
+// Checks that the logger is named correctly.
+
+TEST_F(LoggerLevelTest, Creation) {
+
+ // Default
+ isc::log::Level level1;
+ EXPECT_EQ(isc::log::DEFAULT, level1.severity);
+ EXPECT_EQ(isc::log::MIN_DEBUG_LEVEL, level1.dbglevel);
+
+ // Single argument constructor.
+ isc::log::Level level2(isc::log::FATAL);
+ EXPECT_EQ(isc::log::FATAL, level2.severity);
+ EXPECT_EQ(isc::log::MIN_DEBUG_LEVEL, level2.dbglevel);
+
+ // Two-argument constructor
+ isc::log::Level level3(isc::log::DEBUG, 42);
+ EXPECT_EQ(isc::log::DEBUG, level3.severity);
+ EXPECT_EQ(42, level3.dbglevel);
+}
+
+TEST_F(LoggerLevelTest, getSeverity) {
+ EXPECT_EQ(DEBUG, getSeverity("DEBUG"));
+ EXPECT_EQ(DEBUG, getSeverity("debug"));
+ EXPECT_EQ(DEBUG, getSeverity("DeBuG"));
+ EXPECT_EQ(INFO, getSeverity("INFO"));
+ EXPECT_EQ(INFO, getSeverity("info"));
+ EXPECT_EQ(INFO, getSeverity("iNfO"));
+ EXPECT_EQ(WARN, getSeverity("WARN"));
+ EXPECT_EQ(WARN, getSeverity("warn"));
+ EXPECT_EQ(WARN, getSeverity("wARn"));
+ EXPECT_EQ(ERROR, getSeverity("ERROR"));
+ EXPECT_EQ(ERROR, getSeverity("error"));
+ EXPECT_EQ(ERROR, getSeverity("ERRoR"));
+ EXPECT_EQ(FATAL, getSeverity("FATAL"));
+ EXPECT_EQ(FATAL, getSeverity("fatal"));
+ EXPECT_EQ(FATAL, getSeverity("FAtaL"));
+
+ // bad values should default to stdout
+ EXPECT_EQ(INFO, getSeverity("some bad value"));
+ EXPECT_EQ(INFO, getSeverity(""));
+
+ LoggerManager::reset();
+}
diff --git a/src/lib/log/tests/logger_lock_test.cc b/src/lib/log/tests/logger_lock_test.cc
new file mode 100644
index 0000000..9908800
--- /dev/null
+++ b/src/lib/log/tests/logger_lock_test.cc
@@ -0,0 +1,101 @@
+// Copyright (C) 2012-2019 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 <log/macros.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <log/log_messages.h>
+#include <log/interprocess/interprocess_sync.h>
+#include <log/tests/log_test_messages.h>
+
+#include <mutex>
+#include <iostream>
+
+#include <boost/noncopyable.hpp>
+
+using namespace std;
+using namespace isc::log;
+
+/// \brief RAII safe mutex checker.
+class CheckMutex : boost::noncopyable {
+public:
+ /// \brief Exception thrown when the mutex is already locked.
+ struct AlreadyLocked : public isc::InvalidParameter {
+ AlreadyLocked(const char* file, size_t line, const char* what) :
+ isc::InvalidParameter(file, line, what)
+ {}
+ };
+
+ /// \brief Constructor.
+ ///
+ /// \throw AlreadyLocked if the mutex is already locked.
+ CheckMutex(mutex& mutex) : mutex_(mutex) {
+ if (!mutex.try_lock()) {
+ isc_throw(AlreadyLocked, "The mutex is already locked");
+ }
+ }
+
+ /// \brief Destructor.
+ ///
+ /// Unlocks the mutex.
+ ~CheckMutex() {
+ mutex_.unlock();
+ }
+
+private:
+ mutex& mutex_;
+};
+
+class MockLoggingSync : public isc::log::interprocess::InterprocessSync {
+public:
+ /// \brief Constructor
+ MockLoggingSync(const std::string& component_name) :
+ InterprocessSync(component_name)
+ {}
+
+protected:
+ virtual bool lock() {
+ // We first check if the logger acquired a lock on the
+ // LoggerManager mutex.
+ try {
+ CheckMutex check(LoggerManager::getMutex());
+ } catch (const CheckMutex::AlreadyLocked&) {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: MUTEXLOCK\n";
+ }
+
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: LOCK\n";
+ return (true);
+ }
+
+ virtual bool tryLock() {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: TRYLOCK\n";
+ return (true);
+ }
+
+ virtual bool unlock() {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: UNLOCK\n";
+ return (true);
+ }
+};
+
+/// \brief Test logger lock sequence
+///
+/// A program used in testing the logger. It verifies that (1) an
+/// interprocess sync lock is first acquired by the logger, (2) the
+/// message is logged by the logger, and (3) the lock is released in
+/// that sequence.
+int
+main(int, char**) {
+ initLogger();
+ Logger logger("log");
+ logger.setInterprocessSync(new MockLoggingSync("log"));
+
+ LOG_INFO(logger, LOG_LOCK_TEST_MESSAGE);
+
+ return (0);
+}
diff --git a/src/lib/log/tests/logger_lock_test.sh.in b/src/lib/log/tests/logger_lock_test.sh.in
new file mode 100644
index 0000000..2cea0f0
--- /dev/null
+++ b/src/lib/log/tests/logger_lock_test.sh.in
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Copyright (C) 2012-2020 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/.
+
+# Checks that the locker interprocess sync locks are acquired and
+# released correctly.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+tempfile="@abs_builddir@/logger_lock_test_tempfile_$$"
+destfile="@abs_builddir@/logger_lock_test_destfile_$$"
+
+test_start 'logger_lock'
+cat > $tempfile << .
+LOGGER_LOCK_TEST: MUTEXLOCK
+LOGGER_LOCK_TEST: LOCK
+INFO [kea.log] LOG_LOCK_TEST_MESSAGE this is a test message.
+LOGGER_LOCK_TEST: UNLOCK
+.
+rm -f $destfile
+KEA_LOGGER_SEVERITY=INFO KEA_LOGGER_DESTINATION=stdout ./logger_lock_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' > $destfile
+cut -d' ' -f3- $destfile | diff $tempfile -
+test_finish $?
+
+# Tidy up.
+rm -f $tempfile $destfile
diff --git a/src/lib/log/tests/logger_manager_unittest.cc b/src/lib/log/tests/logger_manager_unittest.cc
new file mode 100644
index 0000000..6bde1db
--- /dev/null
+++ b/src/lib/log/tests/logger_manager_unittest.cc
@@ -0,0 +1,511 @@
+// Copyright (C) 2011-2022 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 <stdio.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_array.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <log/macros.h>
+#include <log/log_messages.h>
+#include <log/logger.h>
+#include <log/logger_level.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <log/logger_specification.h>
+#include <log/message_initializer.h>
+#include <log/output_option.h>
+#include <log/tests/tempdir.h>
+#include <testutils/gtest_utils.h>
+
+#include <sys/types.h>
+#include <regex.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+/// \brief LoggerManager Test
+class LoggerManagerTest : public ::testing::Test {
+public:
+ LoggerManagerTest() {
+ // Initialization of logging is done in main()
+ }
+
+ ~LoggerManagerTest() {
+ LoggerManager::reset();
+ }
+};
+
+// Convenience class to create the specification for the logger "filelogger",
+// which, as the name suggests, logs to a file. It remembers the file name and
+// deletes the file when instance of the class is destroyed.
+class SpecificationForFileLogger {
+public:
+
+ // Constructor - allocate file and create the specification object
+ SpecificationForFileLogger() : spec_(), name_(createTempFilename()),
+ logname_("filelogger") {
+
+ // Set the output to a temporary file.
+ OutputOption option;
+ option.destination = OutputOption::DEST_FILE;
+ option.filename = name_;
+
+ // Set target output to the file logger. The defaults indicate
+ // INFO severity.
+ spec_.setName(logname_);
+ spec_.addOutputOption(option);
+ }
+
+ // Destructor, remove the file. This is only a test, so ignore failures
+ ~SpecificationForFileLogger() {
+ if (! name_.empty()) {
+ static_cast<void>(remove(name_.c_str()));
+
+ // Depending on the log4cplus version, a lock file may also be
+ // created.
+ static_cast<void>(remove((name_ + ".lock").c_str()));
+ }
+ }
+
+ // Return reference to the logging specification for this loggger
+ LoggerSpecification& getSpecification() {
+ return spec_;
+ }
+
+ // Return name of the logger
+ string getLoggerName() const {
+ return logname_;
+ }
+
+ // Return name of the file
+ string getFileName() const {
+ return name_;
+ }
+
+ // Create temporary filename
+ //
+ // The compiler warns against tmpnam() and suggests mkstemp instead.
+ // Unfortunately, this creates the filename and opens it. So we need to
+ // close and delete the file before returning the name. Also, the name
+ // is based on the template supplied and the name of the temporary
+ // directory may vary between systems. So translate TMPDIR and if that
+ // does not exist, use /tmp.
+ //
+ // \return Temporary file name
+ static std::string createTempFilename() {
+ string filename = TEMP_DIR + "/kea_logger_manager_test_XXXXXX";
+
+ // Copy into writable storage for the call to mkstemp
+ boost::scoped_array<char> tname(new char[filename.size() + 1]);
+ strcpy(tname.get(), filename.c_str());
+
+ // Create file, close and delete it, and store the name for later.
+ // There is still a race condition here, albeit a small one.
+ int filenum = mkstemp(tname.get());
+ if (filenum == -1) {
+ isc_throw(Exception, "Unable to obtain unique filename");
+ }
+ close(filenum);
+
+ return (string(tname.get()));
+ }
+
+
+private:
+ LoggerSpecification spec_; // Specification for this file logger
+ string name_; // Name of the output file
+ string logname_; // Name of this logger
+};
+
+// Convenience function to read an output log file and check that each line
+// contains the expected message ID
+//
+// \param filename Name of the file to check
+// \param start Iterator pointing to first expected message ID
+// \param finish Iterator pointing to last expected message ID
+template <typename T>
+void checkFileContents(const std::string& filename, T start, T finish) {
+
+ // Access the file for input
+ ifstream infile(filename.c_str());
+ if (! infile.good()) {
+ FAIL() << "Unable to open the logging file " << filename;
+ }
+
+ // Iterate round the expected message IDs and check that they appear in
+ // the string.
+ string line; // Line read from the file
+
+ T i = start; // Iterator
+ getline(infile, line);
+ int lineno = 1;
+
+ while ((i != finish) && (infile.good())) {
+
+ // Check that the message ID appears in the line.
+ EXPECT_TRUE(line.find(string(*i)) != string::npos)
+ << "Expected to find " << string(*i) << " on line " << lineno
+ << " of logging file " << filename;
+
+ // Go for the next line
+ ++i;
+ getline(infile, line);
+ ++lineno;
+ }
+
+ // Why did the loop end?
+ EXPECT_TRUE(i == finish) << "Did not reach the end of the message ID list";
+ EXPECT_TRUE(infile.eof()) << "Did not reach the end of the logging file";
+
+ // File will close when the instream is deleted at the end of this
+ // function.
+}
+
+// Check that the logger correctly creates something logging to a file.
+TEST_F(LoggerManagerTest, FileLogger) {
+
+ // Create a specification for the file logger and use the manager to
+ // connect the "filelogger" logger to it.
+ SpecificationForFileLogger file_spec;
+
+ // For the first test, we want to check that the file is created
+ // if it does not already exist. So delete the temporary file before
+ // logging the first message.
+ static_cast<void>(remove(file_spec.getFileName().c_str()));
+
+ // Set up the file appenders.
+ LoggerManager manager;
+ manager.process(file_spec.getSpecification());
+
+ // Try logging to the file. Local scope is set to ensure that the logger
+ // is destroyed before we reset the global logging. We record what we
+ // put in the file for a later comparison.
+ vector<MessageID> ids;
+ {
+
+ // Scope-limit the logger to ensure it is destroyed after the brief
+ // check. This adds weight to the idea that the logger will not
+ // keep the file open.
+ Logger logger(file_spec.getLoggerName().c_str());
+
+ LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
+ ids.push_back(LOG_DUPLICATE_MESSAGE_ID);
+
+ LOG_FATAL(logger, LOG_DUPLICATE_NAMESPACE).arg("test");
+ ids.push_back(LOG_DUPLICATE_NAMESPACE);
+ }
+ LoggerManager::reset();
+
+ // At this point, the output file should contain two lines with messages
+ // LOG_DUPLICATE_MESSAGE_ID and LOG_DUPLICATE_NAMESPACE messages - test this.
+ checkFileContents(file_spec.getFileName(), ids.begin(), ids.end());
+
+ // Re-open the file (we have to assume that it was closed when we
+ // reset the logger - there is no easy way to check) and check that
+ // new messages are appended to it. We use the alternative
+ // invocation of process() here to check it works.
+ vector<LoggerSpecification> spec(1, file_spec.getSpecification());
+ manager.process(spec.begin(), spec.end());
+
+ // Create a new instance of the logger and log three more messages.
+ Logger logger(file_spec.getLoggerName().c_str());
+
+ LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg("test");
+ ids.push_back(LOG_NO_SUCH_MESSAGE);
+
+ LOG_FATAL(logger, LOG_INVALID_MESSAGE_ID).arg("test").arg("test2");
+ ids.push_back(LOG_INVALID_MESSAGE_ID);
+
+ LOG_FATAL(logger, LOG_NO_MESSAGE_ID).arg("42");
+ ids.push_back(LOG_NO_MESSAGE_ID);
+
+ // Close the file and check again
+ LoggerManager::reset();
+ checkFileContents(file_spec.getFileName(), ids.begin(), ids.end());
+}
+
+// Check if the file rolls over when it gets above a certain size.
+TEST_F(LoggerManagerTest, FileSizeRollover) {
+ // Set to a suitable minimum that log4cplus can cope with.
+ static const size_t SIZE_LIMIT = 204800;
+
+ // Set up the name of the file.
+ SpecificationForFileLogger file_spec;
+ LoggerSpecification& spec = file_spec.getSpecification();
+
+ // Expand the option to ensure that a maximum version size is set.
+ LoggerSpecification::iterator opt = spec.begin();
+ EXPECT_TRUE(opt != spec.end());
+ opt->maxsize = SIZE_LIMIT; // Bytes
+ opt->maxver = 2;
+
+ // The current output file does not exist (the creation of file_spec
+ // ensures that. Check that previous versions don't either.
+ vector<string> prev_name;
+ for (int i = 0; i < 3; ++i) {
+ prev_name.push_back(file_spec.getFileName() + "." +
+ boost::lexical_cast<string>(i + 1));
+ (void) remove(prev_name[i].c_str());
+ }
+
+ // Generate an argument for a message that ensures that the message when
+ // logged will be over that size.
+ string big_arg(SIZE_LIMIT, 'x');
+
+ // Set up the file logger
+ LoggerManager manager;
+ manager.process(spec);
+
+ // Log the message twice using different message IDs. This should generate
+ // three files as for the log4cplus implementation, the files appear to
+ // be rolled after the message is logged.
+ {
+ Logger logger(file_spec.getLoggerName().c_str());
+ LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg(big_arg);
+ LOG_FATAL(logger, LOG_DUPLICATE_NAMESPACE).arg(big_arg);
+ }
+
+ // Check them.
+ LoggerManager::reset(); // Ensure files are closed
+
+ vector<MessageID> ids;
+ ids.push_back(LOG_NO_SUCH_MESSAGE);
+ checkFileContents(prev_name[1], ids.begin(), ids.end());
+
+ ids.clear();
+ ids.push_back(LOG_DUPLICATE_NAMESPACE);
+ checkFileContents(prev_name[0], ids.begin(), ids.end());
+
+ // Log another message and check that the files have rotated and that
+ // a .3 version does not exist.
+ manager.process(spec);
+ {
+ Logger logger(file_spec.getLoggerName().c_str());
+ LOG_FATAL(logger, LOG_NO_MESSAGE_TEXT).arg("42").arg(big_arg);
+ }
+
+ LoggerManager::reset(); // Ensure files are closed
+
+ // Check that the files have moved.
+ ids.clear();
+ ids.push_back(LOG_DUPLICATE_NAMESPACE);
+ checkFileContents(prev_name[1], ids.begin(), ids.end());
+
+ ids.clear();
+ ids.push_back(LOG_NO_MESSAGE_TEXT);
+ checkFileContents(prev_name[0], ids.begin(), ids.end());
+
+ // ... and check that the .3 version does not exist.
+ ifstream file3(prev_name[2].c_str(), ifstream::in);
+ EXPECT_FALSE(file3.good());
+
+ // Tidy up
+ for (vector<string>::size_type i = 0; i < prev_name.size(); ++i) {
+ (void) remove(prev_name[i].c_str());
+ }
+}
+
+// Check if an exception is thrown if maxsize is too large.
+TEST_F(LoggerManagerTest, TooLargeMaxsize) {
+ // Set up the name of the file.
+ SpecificationForFileLogger file_spec;
+ LoggerSpecification& spec(file_spec.getSpecification());
+
+ // UINT64_MAX should be large enough.
+ LoggerSpecification::iterator opt = spec.begin();
+ EXPECT_TRUE(opt != spec.end());
+ opt->maxsize = std::numeric_limits<uint64_t>::max(); // bytes
+
+ // Set up the file logger.
+ LoggerManager manager;
+ EXPECT_THROW_MSG(manager.process(spec), BadValue,
+ "expected maxsize < 2147483647MB, but instead got "
+ "18446744073709MB");
+
+ opt->maxsize = 1000000LL * (std::numeric_limits<int32_t>::max() + 1LL); // bytes
+ EXPECT_THROW_MSG(manager.process(spec), BadValue,
+ "expected maxsize < 2147483647MB, but instead got "
+ "2147483648MB");
+}
+
+namespace { // begin unnamed namespace
+
+// When we begin to use C++11, we could replace use of POSIX API with
+// <regex>.
+
+class RegexHolder {
+public:
+ RegexHolder(const char* expr, const int flags = REG_EXTENDED) {
+ const int rc = regcomp(&regex_, expr, flags);
+ if (rc) {
+ regfree(&regex_);
+ throw;
+ }
+ }
+
+ ~RegexHolder() {
+ regfree(&regex_);
+ }
+
+ regex_t* operator*() {
+ return (&regex_);
+ }
+
+private:
+ regex_t regex_;
+};
+
+} // end of unnamed namespace
+
+// Check that the logger correctly outputs the full formatted layout
+// pattern.
+TEST_F(LoggerManagerTest, checkLayoutPattern) {
+ // Create a specification for the file logger and use the manager to
+ // connect the "filelogger" logger to it.
+ SpecificationForFileLogger file_spec;
+
+ // For the first test, we want to check that the file is created
+ // if it does not already exist. So delete the temporary file before
+ // logging the first message.
+ static_cast<void>(remove(file_spec.getFileName().c_str()));
+
+ // Set up the file appenders.
+ LoggerManager manager;
+ manager.process(file_spec.getSpecification());
+
+ // Try logging to the file. Local scope is set to ensure that the logger
+ // is destroyed before we reset the global logging.
+ {
+ Logger logger(file_spec.getLoggerName().c_str());
+ LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
+ }
+
+ LoggerManager::reset();
+
+ // Access the file for input
+ const std::string& filename = file_spec.getFileName();
+ ifstream infile(filename.c_str());
+ if (! infile.good()) {
+ FAIL() << "Unable to open the logging file " << filename;
+ }
+
+ std::string line;
+ std::getline(infile, line);
+
+ RegexHolder regex(// %D{%Y-%m-%d %H:%M:%S.%q}
+ "^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}[[:space:]]"
+ "[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\.[[:digit:]]+[[:space:]]"
+ // %-5p
+ "[[:alpha:]]{1,5}[[:space:]]"
+ // [%c/%i/%t]
+ "\\[[[:alnum:]\\-\\.]+/[[:digit:]]+\\.(0x)?[[:xdigit:]]+\\][[:space:]]"
+ );
+
+ const int re = regexec(*regex, line.c_str(), 0, NULL, 0);
+ ASSERT_EQ(0, re)
+ << "Logged message does not match expected layout pattern";
+}
+
+// Check that after calling the logDuplicatedMessages, the duplicated
+// messages are removed.
+TEST_F(LoggerManagerTest, logDuplicatedMessages) {
+ // Original set should not have duplicates.
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ // This just defines 1, but we'll add it a number of times.
+ const char* dupe[] = {
+ "DUPE", "dupe",
+ NULL
+ };
+ const MessageInitializer init_message_initializer_1(dupe);
+ const MessageInitializer init_message_initializer_2(dupe);
+
+ MessageInitializer::loadDictionary();
+ // Should have a duplicate now.
+ ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
+
+ // The logDuplicatedMessages, besides logging, should also remove the
+ // duplicates.
+ LoggerManager::logDuplicatedMessages();
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+}
+
+// Check that output options can be inherited.
+TEST_F(LoggerManagerTest, outputOptionsInheritance) {
+ LoggerManager manager;
+ SpecificationForFileLogger file_spec;
+ vector<LoggerSpecification> specs;
+
+ // Create the root logger configuration with a file output option.
+ string root_name(getRootLoggerName());
+ LoggerSpecification root_spec(root_name);
+ OutputOption root_option;
+ root_option.destination = OutputOption::DEST_FILE;
+ root_option.filename = file_spec.getFileName();
+ root_option.pattern = "%p %m\n";
+ root_spec.addOutputOption(root_option);
+ specs.push_back(root_spec);
+
+ // Create a child logger configuration without any output options.
+ // It should inherit the output option from the root logger.
+ string foo_name(root_name + ".foo");
+ LoggerSpecification foo_spec(foo_name);
+ specs.push_back(foo_spec);
+
+ // Create another child logger configuration with a console output option.
+ string bar_name(root_name + ".bar");
+ LoggerSpecification bar_spec(bar_name);
+ OutputOption bar_option;
+ bar_option.destination = OutputOption::DEST_CONSOLE;
+ bar_option.pattern = "%p %m\n";
+ bar_spec.addOutputOption(bar_option);
+ specs.push_back(bar_spec);
+
+ // Check the number of output options for each specification.
+ EXPECT_EQ(root_spec.optionCount(), 1);
+ EXPECT_EQ(foo_spec.optionCount(), 0);
+ EXPECT_EQ(bar_spec.optionCount(), 1);
+
+ // Process all the specifications.
+ manager.process(specs.begin(), specs.end());
+
+ // Log two messages each.
+ Logger root_logger(root_name.c_str());
+ Logger foo_logger(foo_name.c_str());
+ Logger bar_logger(bar_name.c_str());
+ LOG_INFO(root_logger, "from root logger 1");
+ LOG_INFO(foo_logger, "from foo logger 1");
+ LOG_INFO(bar_logger, "from bar logger 1");
+ LOG_INFO(root_logger, "from root logger 2");
+ LOG_INFO(foo_logger, "from foo logger 2");
+ LOG_INFO(bar_logger, "from bar logger 2");
+
+ // Check that root and foo were logged to file and that bar which
+ // had an explicit console configuration did not.
+ std::ifstream ifs(file_spec.getFileName());
+ std::stringstream s;
+ s << ifs.rdbuf();
+ std::string const result(s.str());
+ EXPECT_NE(result.find("INFO from root logger 1"), string::npos);
+ EXPECT_NE(result.find("INFO from foo logger 1"), string::npos);
+ EXPECT_EQ(result.find("INFO from bar logger 1"), string::npos);
+ EXPECT_NE(result.find("INFO from root logger 2"), string::npos);
+ EXPECT_NE(result.find("INFO from foo logger 2"), string::npos);
+ EXPECT_EQ(result.find("INFO from bar logger 2"), string::npos);
+}
diff --git a/src/lib/log/tests/logger_name_unittest.cc b/src/lib/log/tests/logger_name_unittest.cc
new file mode 100644
index 0000000..9048bf2
--- /dev/null
+++ b/src/lib/log/tests/logger_name_unittest.cc
@@ -0,0 +1,76 @@
+// 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 <string>
+
+#include <gtest/gtest.h>
+
+#include <log/logger_name.h>
+
+using namespace isc;
+using namespace isc::log;
+
+// Test class. To avoid disturbing the root logger configuration in other
+// tests in the suite, the root logger name is saved in the constructor and
+// restored in the destructor. However, this is a bit chicken and egg, as the
+// functions used to do the save and restore are those being tested...
+//
+// Note that the root name is originally set by the initialization of the
+// logging configuration done in main().
+
+class LoggerNameTest : public ::testing::Test {
+public:
+ LoggerNameTest() :
+ name_(getRootLoggerName())
+ {}
+ ~LoggerNameTest() {
+ setRootLoggerName(name_);
+ }
+
+private:
+ std::string name_; ///< Saved name
+};
+
+// Check setting and getting of root name
+
+TEST_F(LoggerNameTest, RootNameSetGet) {
+ const std::string name1 = "test1";
+ const std::string name2 = "test2";
+
+ // Check that Set/Get works
+ setRootLoggerName(name1);
+ EXPECT_EQ(name1, getRootLoggerName());
+
+ // We could not test that the root logger name is initialized
+ // correctly (as there is one instance of it and we don't know
+ // when this test will be run) so to check that setName() actually
+ // does change the name, run the test again with a different name.
+ //
+ // (There was always the outside chance that the root logger name
+ // was initialized with name1 and that setName() has no effect.)
+ setRootLoggerName(name2);
+ EXPECT_EQ(name2, getRootLoggerName());
+}
+
+// Check expansion of name
+
+TEST_F(LoggerNameTest, ExpandLoggerName) {
+ const std::string ROOT = "example";
+ const std::string NAME = "something";
+ const std::string FULL_NAME = ROOT + "." + NAME;
+
+ setRootLoggerName(ROOT);
+ EXPECT_EQ(ROOT, expandLoggerName(ROOT));
+ EXPECT_EQ(FULL_NAME, expandLoggerName(NAME));
+ EXPECT_EQ(FULL_NAME, expandLoggerName(FULL_NAME));
+}
+
+// Checks that the default logger name is returned properly.
+TEST_F(LoggerNameTest, default) {
+ EXPECT_EQ("kea", getDefaultRootLoggerName());
+}
diff --git a/src/lib/log/tests/logger_specification_unittest.cc b/src/lib/log/tests/logger_specification_unittest.cc
new file mode 100644
index 0000000..be9da6e
--- /dev/null
+++ b/src/lib/log/tests/logger_specification_unittest.cc
@@ -0,0 +1,90 @@
+// 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 <string>
+
+#include <gtest/gtest.h>
+
+#include <log/logger_specification.h>
+#include <log/output_option.h>
+
+using namespace isc::log;
+using namespace std;
+
+// Check default initialization.
+TEST(LoggerSpecificationTest, DefaultInitialization) {
+ LoggerSpecification spec;
+
+ EXPECT_EQ(string(""), spec.getName());
+ EXPECT_EQ(isc::log::INFO, spec.getSeverity());
+ EXPECT_EQ(0, spec.getDbglevel());
+ EXPECT_FALSE(spec.getAdditive());
+ EXPECT_EQ(0, spec.optionCount());
+}
+
+// Non-default initialization
+TEST(LoggerSpecificationTest, Initialization) {
+ LoggerSpecification spec("alpha", isc::log::ERROR, 42, true);
+
+ EXPECT_EQ(string("alpha"), spec.getName());
+ EXPECT_EQ(isc::log::ERROR, spec.getSeverity());
+ EXPECT_EQ(42, spec.getDbglevel());
+ EXPECT_TRUE(spec.getAdditive());
+ EXPECT_EQ(0, spec.optionCount());
+}
+
+// Get/Set tests
+TEST(LoggerSpecificationTest, SetGet) {
+ LoggerSpecification spec;
+
+ spec.setName("gamma");
+ EXPECT_EQ(string("gamma"), spec.getName());
+
+ spec.setSeverity(isc::log::FATAL);
+ EXPECT_EQ(isc::log::FATAL, spec.getSeverity());
+
+ spec.setDbglevel(7);
+ EXPECT_EQ(7, spec.getDbglevel());
+
+ spec.setAdditive(true);
+ EXPECT_TRUE(spec.getAdditive());
+
+ // Should not affect option count
+ EXPECT_EQ(0, spec.optionCount());
+}
+
+// Check option setting
+TEST(LoggerSpecificationTest, AddOption) {
+ OutputOption option1;
+ option1.destination = OutputOption::DEST_FILE;
+ option1.filename = "/tmp/example.log";
+ option1.maxsize = 123456;
+
+ OutputOption option2;
+ option2.destination = OutputOption::DEST_SYSLOG;
+ option2.facility = "LOCAL7";
+
+ LoggerSpecification spec;
+ spec.addOutputOption(option1);
+ spec.addOutputOption(option2);
+ EXPECT_EQ(2, spec.optionCount());
+
+ // Iterate through them
+ LoggerSpecification::const_iterator i = spec.begin();
+
+ EXPECT_EQ(OutputOption::DEST_FILE, i->destination);
+ EXPECT_EQ(string("/tmp/example.log"), i->filename);
+ EXPECT_EQ(123456, i->maxsize);
+
+ ++i;
+ EXPECT_EQ(OutputOption::DEST_SYSLOG, i->destination);
+ EXPECT_EQ(string("LOCAL7"), i->facility);
+
+ ++i;
+ EXPECT_TRUE(i == spec.end());
+}
diff --git a/src/lib/log/tests/logger_support_unittest.cc b/src/lib/log/tests/logger_support_unittest.cc
new file mode 100644
index 0000000..3f2943c
--- /dev/null
+++ b/src/lib/log/tests/logger_support_unittest.cc
@@ -0,0 +1,77 @@
+// 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 <gtest/gtest.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+
+using namespace isc::log;
+
+class LoggerSupportTest : public ::testing::Test {
+protected:
+ LoggerSupportTest() {
+ // Logger initialization is done in main(). As logging tests may
+ // alter the default logging output, it is reset here.
+ setDefaultLoggingOutput();
+ }
+ ~LoggerSupportTest() {
+ }
+};
+
+// Check that the initialized flag can be manipulated. This is a bit chicken-
+// -and-egg: we want to reset to the flag to the original value at the end
+// of the test, so use the functions to do that. But we are trying to check
+// that these functions in fact work.
+
+TEST_F(LoggerSupportTest, InitializedFlag) {
+ bool current_flag = isLoggingInitialized();
+
+ // check we can flip the flag.
+ setLoggingInitialized(!current_flag);
+ EXPECT_NE(current_flag, isLoggingInitialized());
+ setLoggingInitialized(!isLoggingInitialized());
+ EXPECT_EQ(current_flag, isLoggingInitialized());
+
+ // Check we can set it to explicit values (tests that a call to the "set"
+ // function does not just flip the flag).
+ setLoggingInitialized(false);
+ EXPECT_FALSE(isLoggingInitialized());
+ setLoggingInitialized(false);
+ EXPECT_FALSE(isLoggingInitialized());
+
+ setLoggingInitialized(true);
+ EXPECT_TRUE(isLoggingInitialized());
+ setLoggingInitialized(true);
+ EXPECT_TRUE(isLoggingInitialized());
+
+ // Reset to original value
+ setLoggingInitialized(current_flag);
+}
+
+// Check that a logger will throw an exception if logging has not been
+// initialized.
+
+TEST_F(LoggerSupportTest, LoggingInitializationCheck) {
+
+ // Assert that logging has been initialized (it should be in main()).
+ bool current_flag = isLoggingInitialized();
+ EXPECT_TRUE(current_flag);
+
+ // Flag that it has not been initialized and declare a logger. Any logging
+ // operation should then throw.
+ setLoggingInitialized(false);
+ isc::log::Logger test_logger("test");
+
+ EXPECT_THROW(test_logger.isDebugEnabled(), LoggingNotInitialized);
+ EXPECT_THROW(test_logger.info(LOG_INPUT_OPEN_FAIL), LoggingNotInitialized);
+
+ // ... and check that they work when logging is initialized.
+ setLoggingInitialized(true);
+ EXPECT_NO_THROW(test_logger.isDebugEnabled());
+ EXPECT_NO_THROW(test_logger.info(LOG_INPUT_OPEN_FAIL).arg("foo").arg("bar"));
+}
diff --git a/src/lib/log/tests/logger_unittest.cc b/src/lib/log/tests/logger_unittest.cc
new file mode 100644
index 0000000..cc2fb95
--- /dev/null
+++ b/src/lib/log/tests/logger_unittest.cc
@@ -0,0 +1,508 @@
+// Copyright (C) 2011-2022 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/resource.h>
+#include <util/unittests/check_valgrind.h>
+
+#include <log/logger.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include <log/interprocess/interprocess_sync_file.h>
+#include <log/output_option.h>
+#include <log/tests/log_test_messages.h>
+
+#include <iostream>
+#include <string>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+/// \brief Logger Test
+///
+/// As the logger is only a shell around the implementation, this tests also
+/// checks the logger implementation class as well.
+
+class LoggerTest : public ::testing::Test {
+public:
+ LoggerTest() {
+ // Initialize logging before each test, even if it is already done in main().
+ isc::log::initLogger();
+ }
+ ~LoggerTest() {
+ LoggerManager::reset();
+ }
+};
+
+// Check version
+
+TEST_F(LoggerTest, Version) {
+ EXPECT_NO_THROW(Logger::getVersion());
+}
+
+// Checks that the logger is named correctly.
+
+TEST_F(LoggerTest, Name) {
+
+ // Create a logger
+ Logger logger("alpha");
+
+ // ... and check the name
+ EXPECT_EQ(getRootLoggerName() + string(".alpha"), logger.getName());
+}
+
+// This test attempts to get two instances of a logger with the same name
+// and checks that they are in fact the same logger.
+
+TEST_F(LoggerTest, GetLogger) {
+
+ const char* name1 = "alpha";
+ const char* name2 = "beta";
+
+ // Instantiate two loggers that should be the same
+ Logger logger1(name1);
+ Logger logger2(name1);
+ // And check they equal
+ EXPECT_TRUE(logger1 == logger2);
+
+ // Instantiate another logger with another name and check that it
+ // is different to the previously instantiated ones.
+ Logger logger3(name2);
+ EXPECT_FALSE(logger1 == logger3);
+}
+
+// Check that the logger levels are get set properly.
+
+TEST_F(LoggerTest, Severity) {
+
+ // Create a logger
+ Logger logger("alpha");
+
+ // Now check the levels
+ logger.setSeverity(isc::log::NONE);
+ EXPECT_EQ(isc::log::NONE, logger.getSeverity());
+
+ logger.setSeverity(isc::log::FATAL);
+ EXPECT_EQ(isc::log::FATAL, logger.getSeverity());
+
+ logger.setSeverity(isc::log::ERROR);
+ EXPECT_EQ(isc::log::ERROR, logger.getSeverity());
+
+ logger.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, logger.getSeverity());
+
+ logger.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::INFO, logger.getSeverity());
+
+ logger.setSeverity(isc::log::DEBUG);
+ EXPECT_EQ(isc::log::DEBUG, logger.getSeverity());
+
+ logger.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::DEFAULT, logger.getSeverity());
+}
+
+// Check that the debug level is set correctly.
+
+TEST_F(LoggerTest, DebugLevels) {
+
+ // Create a logger
+ Logger logger("alpha");
+
+ // Debug level should be 0 if not at debug severity
+ logger.setSeverity(isc::log::NONE, 20);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::INFO, 42);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ // Should be the value set if the severity is set to DEBUG though.
+ logger.setSeverity(isc::log::DEBUG, 32);
+ EXPECT_EQ(32, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 97);
+ EXPECT_EQ(97, logger.getDebugLevel());
+
+ // Try the limits
+ logger.setSeverity(isc::log::DEBUG, -1);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 0);
+ EXPECT_EQ(0, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 1);
+ EXPECT_EQ(1, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 98);
+ EXPECT_EQ(98, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 99);
+ EXPECT_EQ(99, logger.getDebugLevel());
+
+ logger.setSeverity(isc::log::DEBUG, 100);
+ EXPECT_EQ(99, logger.getDebugLevel());
+}
+
+// Check that changing the parent and child severity does not affect the
+// other.
+
+TEST_F(LoggerTest, SeverityInheritance) {
+
+ // Create two loggers. We cheat here as we know that the underlying
+ // implementation will set a parent-child relationship if the loggers
+ // are named <parent> and <parent>.<child>.
+ Logger parent("alpha");
+ Logger child("alpha.beta");
+
+ // By default, newly created loggers should have a level of DEFAULT
+ // (i.e. default to parent)
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+
+ // Set the severity of the parent to debug and check what is
+ // reported by the child.
+ parent.setSeverity(isc::log::DEBUG, 42);
+ EXPECT_EQ(42, parent.getDebugLevel());
+ EXPECT_EQ(0, child.getDebugLevel());
+ EXPECT_EQ(42, child.getEffectiveDebugLevel());
+
+ // Setting the child to DEBUG severity should set its own
+ // debug level.
+ child.setSeverity(isc::log::DEBUG, 53);
+ EXPECT_EQ(53, child.getDebugLevel());
+ EXPECT_EQ(53, child.getEffectiveDebugLevel());
+
+ // If the child severity is set to something other than DEBUG,
+ // the debug level should be reported as 0.
+ child.setSeverity(isc::log::ERROR);
+ EXPECT_EQ(0, child.getDebugLevel());
+ EXPECT_EQ(0, child.getEffectiveDebugLevel());
+}
+
+// Check that changing the parent and child debug level does not affect
+// the other.
+
+TEST_F(LoggerTest, DebugLevelInheritance) {
+
+ // Create two loggers. We cheat here as we know that the underlying
+ // implementation will set a parent-child relationship if the loggers
+ // are named <parent> and <parent>.<child>.
+ Logger parent("alpha");
+ Logger child("alpha.beta");
+
+ // By default, newly created loggers should have a level of DEFAULT
+ // (i.e. default to parent)
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+
+ // Set the severity of the child to something other than the default -
+ // check it changes and that of the parent does not.
+ child.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getSeverity());
+
+ // Reset the child severity and set that of the parent
+ child.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::DEFAULT, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+ parent.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, parent.getSeverity());
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+}
+
+// Check that severity is inherited.
+
+TEST_F(LoggerTest, EffectiveSeverityInheritance) {
+
+ // Create two loggers. We cheat here as we know that the underlying
+ // implementation will set a parent-child relationship if the loggers
+ // are named <parent> and <parent>.<child>.
+ Logger parent("test6");
+ Logger child("test6.beta");
+
+ // By default, newly created loggers should have a level of DEFAULT
+ // (i.e. default to parent) and the root should have a default severity
+ // of INFO. However, the latter is only enforced when created by the
+ // RootLogger class, so explicitly set it for the parent for now.
+ parent.setSeverity(isc::log::INFO);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+
+ EXPECT_EQ(isc::log::DEFAULT, child.getSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());
+
+ // Set the severity of the child to something other than the default -
+ // check it changes and that of the parent does not.
+ child.setSeverity(isc::log::FATAL);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::FATAL, child.getEffectiveSeverity());
+
+ // Reset the child severity and check again.
+ child.setSeverity(isc::log::DEFAULT);
+ EXPECT_EQ(isc::log::INFO, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::INFO, child.getEffectiveSeverity());
+
+ // Change the parent's severity and check it is reflects in the child.
+ parent.setSeverity(isc::log::WARN);
+ EXPECT_EQ(isc::log::WARN, parent.getEffectiveSeverity());
+ EXPECT_EQ(isc::log::WARN, child.getEffectiveSeverity());
+}
+
+// Test the isXxxxEnabled methods.
+
+TEST_F(LoggerTest, IsXxxEnabled) {
+
+ Logger logger("test7");
+
+ logger.setSeverity(isc::log::INFO);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_TRUE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::WARN);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_FALSE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::ERROR);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_FALSE(logger.isInfoEnabled());
+ EXPECT_FALSE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::FATAL);
+ EXPECT_FALSE(logger.isDebugEnabled());
+ EXPECT_FALSE(logger.isInfoEnabled());
+ EXPECT_FALSE(logger.isWarnEnabled());
+ EXPECT_FALSE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ // Check various debug levels
+
+ logger.setSeverity(isc::log::DEBUG);
+ EXPECT_TRUE(logger.isDebugEnabled());
+ EXPECT_TRUE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ logger.setSeverity(isc::log::DEBUG, 45);
+ EXPECT_TRUE(logger.isDebugEnabled());
+ EXPECT_TRUE(logger.isInfoEnabled());
+ EXPECT_TRUE(logger.isWarnEnabled());
+ EXPECT_TRUE(logger.isErrorEnabled());
+ EXPECT_TRUE(logger.isFatalEnabled());
+
+ // Create a child logger with no severity set, and check that it reflects
+ // the severity of the parent logger.
+
+ Logger child("test7.child");
+ logger.setSeverity(isc::log::FATAL);
+ EXPECT_FALSE(child.isDebugEnabled());
+ EXPECT_FALSE(child.isInfoEnabled());
+ EXPECT_FALSE(child.isWarnEnabled());
+ EXPECT_FALSE(child.isErrorEnabled());
+ EXPECT_TRUE(child.isFatalEnabled());
+
+ logger.setSeverity(isc::log::INFO);
+ EXPECT_FALSE(child.isDebugEnabled());
+ EXPECT_TRUE(child.isInfoEnabled());
+ EXPECT_TRUE(child.isWarnEnabled());
+ EXPECT_TRUE(child.isErrorEnabled());
+ EXPECT_TRUE(child.isFatalEnabled());
+}
+
+// Within the Debug level there are 100 debug levels. Test that we know
+// when to issue a debug message.
+
+TEST_F(LoggerTest, IsDebugEnabledLevel) {
+
+ Logger logger("test8");
+
+ int MID_LEVEL = (MIN_DEBUG_LEVEL + MAX_DEBUG_LEVEL) / 2;
+
+ logger.setSeverity(isc::log::DEBUG);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+ logger.setSeverity(isc::log::DEBUG, MIN_DEBUG_LEVEL);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+ logger.setSeverity(isc::log::DEBUG, MID_LEVEL);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL - 1));
+ EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL + 1));
+ EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+ logger.setSeverity(isc::log::DEBUG, MAX_DEBUG_LEVEL);
+ EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+ EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
+ EXPECT_TRUE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+}
+
+// Check that loggers with invalid names give an error.
+
+TEST_F(LoggerTest, LoggerNameLength) {
+ // Null name
+ EXPECT_THROW(Logger(NULL), LoggerNameNull);
+
+ // Declare space for the logger name. The length of names checked
+ // will range from 0 through MAX_LOGGER_NAME_SIZE + 1: to allow for
+ // the trailing null, at least one more byte than the longest name size
+ // must be reserved.
+ char name[Logger::MAX_LOGGER_NAME_SIZE + 2];
+
+ // Zero-length name should throw an exception
+ name[0] = '\0';
+ EXPECT_THROW({
+ Logger dummy(name);
+ }, LoggerNameError);
+
+ // Work through all valid names.
+ for (size_t i = 0; i < Logger::MAX_LOGGER_NAME_SIZE; ++i) {
+
+ // Append a character to the name and check that a logger with that
+ // name can be created without throwing an exception.
+ name[i] = 'X';
+ name[i + 1] = '\0';
+ EXPECT_NO_THROW({
+ Logger dummy(name);
+ }) << "Size of logger name is " << (i + 1);
+ }
+
+ // ... and check that an overly long name throws an exception.
+ name[Logger::MAX_LOGGER_NAME_SIZE] = 'X';
+ name[Logger::MAX_LOGGER_NAME_SIZE + 1] = '\0';
+ EXPECT_THROW({
+ Logger dummy(name);
+ }, LoggerNameError);
+
+}
+
+TEST_F(LoggerTest, setInterprocessSync) {
+ // Create a logger
+ Logger logger("alpha");
+
+ EXPECT_THROW(logger.setInterprocessSync(NULL), BadInterprocessSync);
+}
+
+class MockSync : public isc::log::interprocess::InterprocessSync {
+public:
+ /// \brief Constructor
+ MockSync(const std::string& component_name) :
+ InterprocessSync(component_name), was_locked_(false),
+ was_unlocked_(false)
+ {}
+
+ bool wasLocked() const {
+ return (was_locked_);
+ }
+
+ bool wasUnlocked() const {
+ return (was_unlocked_);
+ }
+
+protected:
+ bool lock() {
+ was_locked_ = true;
+ return (true);
+ }
+
+ bool tryLock() {
+ return (true);
+ }
+
+ bool unlock() {
+ was_unlocked_ = true;
+ return (true);
+ }
+
+private:
+ bool was_locked_;
+ bool was_unlocked_;
+};
+
+// Checks that the logger logs exclusively and other Kea components
+// are locked out.
+
+TEST_F(LoggerTest, Lock) {
+ // Create a logger
+ Logger logger("alpha");
+
+ // Setup our own mock sync object so that we can intercept the lock
+ // call and check if a lock has been taken.
+ MockSync* sync = new MockSync("logger");
+ logger.setInterprocessSync(sync);
+
+ // Log a message and put things into play.
+ logger.setSeverity(isc::log::INFO, 100);
+ logger.info(LOG_LOCK_TEST_MESSAGE);
+
+ EXPECT_TRUE(sync->wasLocked());
+ EXPECT_TRUE(sync->wasUnlocked());
+}
+
+// Checks that hasAppender() reports
+TEST_F(LoggerTest, HasAppender) {
+ // Create a logger.
+ Logger logger("logger");
+
+ // By default, loggers have a file appender to /dev/null.
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_CONSOLE));
+ EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_FILE));
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_SYSLOG));
+
+ // -- Create some specifications. --
+
+ OutputOption console;
+ console.destination = OutputOption::DEST_CONSOLE;
+ LoggerSpecification spec_console("logger");
+ spec_console.addOutputOption(console);
+
+ OutputOption file;
+ file.destination = OutputOption::DEST_FILE;
+ file.filename = "/dev/null";
+ LoggerSpecification spec_file("logger");
+ spec_file.addOutputOption(file);
+
+ OutputOption syslog;
+ syslog.destination = OutputOption::DEST_SYSLOG;
+ LoggerSpecification spec_syslog("logger");
+ spec_syslog.addOutputOption(syslog);
+
+ LoggerManager manager;
+
+ // Check console.
+ manager.process(spec_console);
+ EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_CONSOLE));
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_FILE));
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_SYSLOG));
+
+ // Check file.
+ manager.process(spec_file);
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_CONSOLE));
+ EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_FILE));
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_SYSLOG));
+
+ // Check syslog.
+ manager.process(spec_syslog);
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_CONSOLE));
+ EXPECT_FALSE(logger.hasAppender(OutputOption::DEST_FILE));
+ EXPECT_TRUE(logger.hasAppender(OutputOption::DEST_SYSLOG));
+}
diff --git a/src/lib/log/tests/message_dictionary_unittest.cc b/src/lib/log/tests/message_dictionary_unittest.cc
new file mode 100644
index 0000000..8613393
--- /dev/null
+++ b/src/lib/log/tests/message_dictionary_unittest.cc
@@ -0,0 +1,219 @@
+// 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 <cstddef>
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+#include <log/message_types.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+// set up another message initializer. This will add a symbol found in the
+// logging library and a symbol not found in the logging library. When the
+// global dictionary is loaded, the former should be marked as a duplicate
+// and the latter should be present.
+
+namespace {
+const char* values[] = {
+ // This message for DUPLICATE_NAMESPACE must be copied from
+ // ../log_messages.mes; otherwise logger check might fail.
+ "LOG_DUPLICATE_NAMESPACE", "line %1: duplicate $NAMESPACE directive found",
+ "NEWSYM", "new symbol added",
+ NULL
+};
+
+MessageInitializer init(values);
+}
+
+class MessageDictionaryTest : public ::testing::Test {
+protected:
+ MessageDictionaryTest() :
+ alpha_id("ALPHA"), alpha_text("This is alpha"),
+ beta_id("BETA"), beta_text("This is beta"),
+ gamma_id("GAMMA"), gamma_text("This is gamma")
+ {
+ }
+
+ MessageID alpha_id;
+ std::string alpha_text;
+ MessageID beta_id;
+ std::string beta_text;
+ MessageID gamma_id;
+ std::string gamma_text;
+
+};
+
+// Check that adding messages works
+
+TEST_F(MessageDictionaryTest, Add) {
+ MessageDictionary dictionary;
+ EXPECT_EQ(0, dictionary.size());
+
+ // Add a few messages and check that we can look them up and that there is
+ // nothing in the overflow vector.
+ EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+ EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+ EXPECT_EQ(2, dictionary.size());
+
+ EXPECT_EQ(alpha_text, dictionary.getText(alpha_id));
+ EXPECT_EQ(beta_text, dictionary.getText(beta_id));
+ EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+
+ // Try adding a duplicate with different text. It should not replace the
+ // current text and the ID should be in the overflow section.
+ EXPECT_FALSE(dictionary.add(alpha_id, gamma_text));
+ EXPECT_EQ(2, dictionary.size());
+}
+
+// Check that replacing messages works.
+
+TEST_F(MessageDictionaryTest, Replace) {
+ MessageDictionary dictionary;
+ EXPECT_EQ(0, dictionary.size());
+
+ // Try to replace a non-existent message
+ EXPECT_FALSE(dictionary.replace(alpha_id, alpha_text));
+ EXPECT_EQ(0, dictionary.size());
+
+ // Add a couple of messages.
+ EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+ EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+ EXPECT_EQ(2, dictionary.size());
+
+ // Replace an existing message
+ EXPECT_TRUE(dictionary.replace(alpha_id, gamma_text));
+ EXPECT_EQ(2, dictionary.size());
+ EXPECT_EQ(gamma_text, dictionary.getText(alpha_id));
+
+ // ... and replace non-existent message (but now the dictionary has some
+ // items in it).
+ EXPECT_FALSE(dictionary.replace(gamma_id, alpha_text));
+ EXPECT_EQ(2, dictionary.size());
+ EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+}
+
+// Check that removing message works.
+
+TEST_F(MessageDictionaryTest, erase) {
+ MessageDictionary dictionary;
+ ASSERT_NO_THROW(dictionary.erase(alpha_id, alpha_text));
+ ASSERT_EQ(0, dictionary.size());
+
+ // Add a couple of messages.
+ EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+ EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+ // There is no sense to continue if messages haven't been added.
+ ASSERT_EQ(2, dictionary.size());
+
+ // Remove one with the existing ID, but non-matching text. It
+ // should not remove any message.
+ EXPECT_FALSE(dictionary.erase(beta_id, alpha_text));
+
+ // Now, remove the message with matching ID and text.
+ EXPECT_TRUE(dictionary.erase(beta_id, beta_text));
+ EXPECT_EQ(1, dictionary.size());
+ // The other entry should still exist.
+ EXPECT_EQ(alpha_text, dictionary.getText(alpha_id));
+
+ // And remove the other message.
+ EXPECT_TRUE(dictionary.erase(alpha_id, alpha_text));
+ EXPECT_EQ(0, dictionary.size());
+}
+
+// Load test
+
+TEST_F(MessageDictionaryTest, LoadTest) {
+ static const char* data1[] = {
+ "ALPHA", "This is alpha",
+ "BETA", "This is beta",
+ "GAMMA", "This is gamma",
+ NULL
+ };
+
+ static const char* data2[] = {
+ "DELTA", "This is delta",
+ "EPSILON", "This is epsilon",
+ "ETA", NULL
+ };
+
+ MessageDictionary dictionary1;
+ EXPECT_EQ(0, dictionary1.size());
+
+ // Load a dictionary1.
+ vector<string> duplicates = dictionary1.load(data1);
+ EXPECT_EQ(3, dictionary1.size());
+ EXPECT_EQ(string(data1[1]), dictionary1.getText(data1[0]));
+ EXPECT_EQ(string(data1[3]), dictionary1.getText(data1[2]));
+ EXPECT_EQ(string(data1[5]), dictionary1.getText(data1[4]));
+ EXPECT_EQ(0, duplicates.size());
+
+ // Attempt an overwrite
+ duplicates = dictionary1.load(data1);
+ EXPECT_EQ(3, dictionary1.size());
+ EXPECT_EQ(3, duplicates.size());
+
+ // Try a new dictionary but with an incorrect number of elements
+ MessageDictionary dictionary2;
+ EXPECT_EQ(0, dictionary2.size());
+
+ duplicates = dictionary2.load(data2);
+ EXPECT_EQ(2, dictionary2.size());
+ EXPECT_EQ(string(data2[1]), dictionary2.getText(data2[0]));
+ EXPECT_EQ(string(data2[3]), dictionary2.getText(data2[2]));
+ EXPECT_EQ(string(""), dictionary2.getText(data2[4]));
+ EXPECT_EQ(0, duplicates.size());
+}
+
+// Check for some non-existent items
+
+TEST_F(MessageDictionaryTest, Lookups) {
+ static const char* data[] = {
+ "ALPHA", "This is alpha",
+ "BETA", "This is beta",
+ "GAMMA", "This is gamma",
+ NULL
+ };
+
+ MessageDictionary dictionary;
+ vector<string> duplicates = dictionary.load(data);
+ EXPECT_EQ(3, dictionary.size());
+ EXPECT_EQ(0, duplicates.size());
+
+ // Valid lookups
+ EXPECT_EQ(string("This is alpha"), dictionary.getText("ALPHA"));
+ EXPECT_EQ(string("This is beta"), dictionary.getText("BETA"));
+ EXPECT_EQ(string("This is gamma"), dictionary.getText("GAMMA"));
+
+ // ... and invalid ones
+ EXPECT_EQ(string(""), dictionary.getText("XYZZY"));
+ EXPECT_EQ(string(""), dictionary.getText(""));
+ EXPECT_EQ(string(""), dictionary.getText("\n\n\n"));
+}
+
+// Check that the global dictionary is a singleton.
+
+TEST_F(MessageDictionaryTest, GlobalTest) {
+ const MessageDictionaryPtr& global = MessageDictionary::globalDictionary();
+ const MessageDictionaryPtr& global2 = MessageDictionary::globalDictionary();
+ EXPECT_TRUE(global2 == global);
+}
+
+// Check that the global dictionary has detected the duplicate and the
+// new symbol.
+
+TEST_F(MessageDictionaryTest, GlobalLoadTest) {
+ // There were duplicates but the vector should be cleared in init() now
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ string text = MessageDictionary::globalDictionary()->getText("NEWSYM");
+ EXPECT_EQ(string("new symbol added"), text);
+}
diff --git a/src/lib/log/tests/message_initializer_1_unittest.cc b/src/lib/log/tests/message_initializer_1_unittest.cc
new file mode 100644
index 0000000..c34139a
--- /dev/null
+++ b/src/lib/log/tests/message_initializer_1_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright (C) 2012-2020 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 <log/message_dictionary.h>
+#include <log/message_initializer.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+const char* values1[] = {
+ "GLOBAL1", "global message one",
+ "GLOBAL2", "global message two",
+ NULL
+};
+
+const char* values2[] = {
+ "GLOBAL3", "global message three",
+ "GLOBAL4", "global message four",
+ NULL
+};
+
+const char* values3[] = {
+ "GLOBAL7", "global message seven",
+ "GLOBAL8", "global message eight",
+ NULL
+};
+
+const char* values4[] = {
+ "GLOBAL8", "global message eight",
+ "GLOBAL9", "global message nine",
+ NULL
+};
+
+/// @brief Scoped pointer to the @c MessageInitializer object.
+typedef boost::scoped_ptr<MessageInitializer> MessageInitializerPtr;
+
+}
+
+// Statically initialize the global dictionary with those messages. Three sets
+// are used to check that the declaration of separate initializer objects
+// really does combine the messages. (The third set - declaring message IDs
+// GLOBAL5 and GLOBAL6) is declared in the separately-compiled file,
+// message_identifier_initializer_1a_unittest.cc.)
+
+const MessageInitializer init_message_initializer_unittest_1(values1);
+const MessageInitializer init_message_initializer_unittest_2(values2);
+
+// Check that the global dictionary is initialized with the specified
+// messages.
+
+namespace {
+void
+messageTest() {
+ static bool done = false;
+
+ // Execute once.
+ if (done) {
+ return;
+ } else {
+ done = true;
+ }
+
+ const MessageDictionaryPtr& global = MessageDictionary::globalDictionary();
+
+ // Pointers to the message arrays should have been stored, but none of the
+ // messages should yet be in the dictionary.
+ for (int i = 1; i <= 6; ++i) {
+ string symbol = string("GLOBAL") + boost::lexical_cast<std::string>(i);
+ EXPECT_EQ(string(""), global->getText(symbol));
+ }
+
+ // Load the dictionary - this should clear the message array pending count.
+ // (N.B. We do not check for a known value before the call, only that the
+ // value is not zero. This is because libraries against which the test
+ // is linked may have registered their own message arrays.)
+ EXPECT_NE(0, MessageInitializer::getPendingCount());
+ MessageInitializer::loadDictionary();
+ EXPECT_EQ(0, MessageInitializer::getPendingCount());
+
+ // ... and check the messages loaded.
+ EXPECT_EQ(string("global message one"), global->getText("GLOBAL1"));
+ EXPECT_EQ(string("global message two"), global->getText("GLOBAL2"));
+ EXPECT_EQ(string("global message three"), global->getText("GLOBAL3"));
+ EXPECT_EQ(string("global message four"), global->getText("GLOBAL4"));
+ EXPECT_EQ(string("global message five"), global->getText("GLOBAL5"));
+ EXPECT_EQ(string("global message six"), global->getText("GLOBAL6"));
+}
+}
+
+// Check that destroying the MessageInitializer causes the relevant
+// messages to be removed from the dictionary.
+
+TEST(MessageInitializerTest1, dynamicLoadUnload) {
+ // Try first messageTest.
+ messageTest();
+
+ // Obtain the instance of the global dictionary.
+ const MessageDictionaryPtr& global = MessageDictionary::globalDictionary();
+
+ // Dynamically create the first initializer.
+ MessageInitializerPtr init1(new MessageInitializer(values3));
+ EXPECT_EQ(1, MessageInitializer::getPendingCount());
+
+ // Dynamically create the second initializer.
+ MessageInitializerPtr init2(new MessageInitializer(values4));
+ EXPECT_EQ(2, MessageInitializer::getPendingCount());
+
+ // Load messages from both initializers to the global dictionary.
+ MessageInitializer::loadDictionary();
+ // There should be no pending messages.
+ EXPECT_EQ(0, MessageInitializer::getPendingCount());
+
+ // Make sure that the messages have been loaded.
+ EXPECT_EQ("global message seven", global->getText("GLOBAL7"));
+ EXPECT_EQ("global message eight", global->getText("GLOBAL8"));
+ EXPECT_EQ("global message nine", global->getText("GLOBAL9"));
+
+ // Destroy the first initializer. The first message should be removed.
+ // The second message should not be removed because it is also held
+ // by another object.
+ init1.reset();
+ EXPECT_TRUE(global->getText("GLOBAL7").empty());
+ EXPECT_EQ("global message eight", global->getText("GLOBAL8"));
+ EXPECT_EQ("global message nine", global->getText("GLOBAL9"));
+
+ // Destroy the second initializer. Now, all messages should be
+ // unregistered.
+ init2.reset();
+ EXPECT_TRUE(global->getText("GLOBAL7").empty());
+ EXPECT_TRUE(global->getText("GLOBAL8").empty());
+ EXPECT_TRUE(global->getText("GLOBAL9").empty());
+}
+
+// Check that destroying the MessageInitializer removes pending messages.
+
+TEST(MessageInitializerTest1, dynamicUnloadPending) {
+ // Try first messageTest.
+ messageTest();
+
+ // Obtain the instance of the global dictionary.
+ const MessageDictionaryPtr& global = MessageDictionary::globalDictionary();
+
+ // Dynamically create the first initializer.
+ MessageInitializerPtr init1(new MessageInitializer(values3));
+ ASSERT_EQ(1, MessageInitializer::getPendingCount());
+
+ // Create second initializer without committing the first set
+ // of messages to the dictionary.
+ MessageInitializerPtr init2(new MessageInitializer(values4));
+ ASSERT_EQ(2, MessageInitializer::getPendingCount());
+
+ // Destroy the first initializer and make sure that the number of
+ // pending message sets drops to 1.
+ init1.reset();
+ ASSERT_EQ(1, MessageInitializer::getPendingCount());
+
+ // Now destroy the second initializer and make sure that there are
+ // no pending messages.
+ init2.reset();
+ ASSERT_EQ(0, MessageInitializer::getPendingCount());
+
+ init1.reset(new MessageInitializer(values3));
+ ASSERT_EQ(1, MessageInitializer::getPendingCount());
+
+ // Load the messages to the dictionary and make sure there are no pending
+ // messages.
+ MessageInitializer::loadDictionary();
+ EXPECT_EQ(0, MessageInitializer::getPendingCount());
+
+ // Create the second initializer. There should be one pending set of
+ // messages.
+ init2.reset(new MessageInitializer(values4));
+ ASSERT_EQ(1, MessageInitializer::getPendingCount());
+
+ // Make sure that the messages defined by the first initializer
+ // are in the dictionary.
+ ASSERT_EQ("global message seven", global->getText("GLOBAL7"));
+ ASSERT_EQ("global message eight", global->getText("GLOBAL8"));
+ ASSERT_TRUE(global->getText("GLOBAL9").empty());
+
+ // Destroy the second initializer. There should be no pending messages
+ // now.
+ init2.reset();
+ ASSERT_EQ(0, MessageInitializer::getPendingCount());
+
+ // Loading the messages should be no-op.
+ MessageInitializer::loadDictionary();
+ ASSERT_EQ(0, MessageInitializer::getPendingCount());
+
+ // Make sure that the messages loaded from the first initializer
+ // are not affected.
+ ASSERT_EQ("global message seven", global->getText("GLOBAL7"));
+ ASSERT_EQ("global message eight", global->getText("GLOBAL8"));
+ ASSERT_TRUE(global->getText("GLOBAL9").empty());
+
+ // And remove them.
+ init1.reset();
+ EXPECT_TRUE(global->getText("GLOBAL7").empty());
+ EXPECT_TRUE(global->getText("GLOBAL8").empty());
+ EXPECT_TRUE(global->getText("GLOBAL9").empty());
+}
+
+TEST(MessageInitializerTest1, duplicates) {
+ // Try first messageTest.
+ messageTest();
+
+ // Original set should not have dupes
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ // This just defines 1, but we'll add it a number of times
+ const char* dupe[] = {
+ "DUPE", "dupe",
+ NULL
+ };
+ const MessageInitializer init_message_initializer_unittest_1(dupe);
+ const MessageInitializer init_message_initializer_unittest_2(dupe);
+
+ MessageInitializer::loadDictionary();
+ // Should be a dupe now
+ ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
+
+ // clear them
+ MessageInitializer::clearDuplicates();
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ // Do it again to make sure, let's explicitly provide false now
+ const MessageInitializer init_message_initializer_unittest_3(dupe);
+ MessageInitializer::loadDictionary(false);
+ ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
+
+ // Loading with ignore_duplicates=true should result in no (reported)
+ // dupes
+ MessageInitializer::clearDuplicates();
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+ const MessageInitializer init_message_initializer_unittest_4(dupe);
+ MessageInitializer::loadDictionary(true);
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+}
diff --git a/src/lib/log/tests/message_initializer_1a_unittest.cc b/src/lib/log/tests/message_initializer_1a_unittest.cc
new file mode 100644
index 0000000..3e2b86b
--- /dev/null
+++ b/src/lib/log/tests/message_initializer_1a_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2012-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/.
+
+// The sole purpose of this file is to provide a set of message definitions
+// in a separate compilation unit from the one in which their presence is
+// checked. This tests that merely declaring the MessageInitializer object
+// is enough to include the definitions in the global dictionary.
+
+#include <config.h>
+
+#include <log/message_initializer.h>
+
+using namespace isc::log;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+
+const char* values3[] = {
+ "GLOBAL5", "global message five",
+ "GLOBAL6", "global message six",
+ NULL
+};
+
+}
+
+// Register the messages for loading into the global dictionary
+const MessageInitializer init_message_initializer_unittest_3(values3);
diff --git a/src/lib/log/tests/message_reader_unittest.cc b/src/lib/log/tests/message_reader_unittest.cc
new file mode 100644
index 0000000..4dd6435
--- /dev/null
+++ b/src/lib/log/tests/message_reader_unittest.cc
@@ -0,0 +1,269 @@
+// Copyright (C) 2011-2020 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 <algorithm>
+#include <string>
+#include <gtest/gtest.h>
+
+#include <log/log_messages.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class MessageReaderTest : public ::testing::Test {
+protected:
+ MessageReaderTest() : dictionary_(), reader_()
+ {
+ dictionary_ = new MessageDictionary();
+ reader_.setDictionary(dictionary_);
+ }
+
+ ~MessageReaderTest() {
+ delete dictionary_;
+ }
+
+ MessageDictionary* dictionary_; // Dictionary to add messages to
+ MessageReader reader_; // Default reader object
+};
+
+
+// Check the get/set dictionary calls (using a local reader and dictionary).
+
+TEST_F(MessageReaderTest, GetSetDictionary) {
+ MessageReader reader;
+ EXPECT_TRUE(reader.getDictionary() == NULL);
+
+ MessageDictionary dictionary;
+ reader.setDictionary(&dictionary);
+ EXPECT_EQ(&dictionary, reader.getDictionary());
+}
+
+// Check for parsing blank lines and comments. These should not add to the
+// dictionary and each parse should return success.
+
+TEST_F(MessageReaderTest, BlanksAndComments) {
+
+ // Ensure that the dictionary is empty.
+ EXPECT_EQ(0, dictionary_->size());
+
+ // Add a number of blank lines and comments and check that (a) they are
+ // parsed successfully ...
+ EXPECT_NO_THROW(reader_.processLine(""));
+ EXPECT_NO_THROW(reader_.processLine(" "));
+ EXPECT_NO_THROW(reader_.processLine(" \n "));
+ EXPECT_NO_THROW(reader_.processLine("# This is a comment"));
+ EXPECT_NO_THROW(reader_.processLine("\t\t # Another comment"));
+ EXPECT_NO_THROW(reader_.processLine(" A description line"));
+ EXPECT_NO_THROW(reader_.processLine("# A comment"));
+ EXPECT_NO_THROW(reader_.processLine(" +# A description line"));
+
+ // ... and (b) nothing gets added to either the map or the not-added section.
+ EXPECT_EQ(0, dictionary_->size());
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+
+// Local test to check that processLine generates the right exception.
+
+void
+processLineException(MessageReader& reader, const char* what,
+ const MessageID& expected) {
+
+ try {
+ reader.processLine(what);
+ FAIL() << "MessageReader::processLine() should throw an exception " <<
+ " with message ID " << expected << " for '" << what << "'\n";
+ } catch (const MessageException& e) {
+ EXPECT_EQ(boost::lexical_cast<string>(expected),
+ boost::lexical_cast<string>(e.id()));
+ } catch (...) {
+ FAIL() << "Unknown exception thrown by MessageReader::processLine()\n";
+ }
+}
+
+// Check that it recognizes invalid directives
+
+TEST_F(MessageReaderTest, InvalidDirectives) {
+
+ // Check that a "$" with nothing else generates an error
+ processLineException(reader_, "$", LOG_UNRECOGNIZED_DIRECTIVE);
+ processLineException(reader_, "$xyz", LOG_UNRECOGNIZED_DIRECTIVE);
+}
+
+// Check that it can parse a prefix
+
+TEST_F(MessageReaderTest, Prefix) {
+
+ // Check that no $PREFIX is present
+ EXPECT_EQ(string(""), reader_.getPrefix());
+
+ // Check that a $PREFIX directive with no argument is OK
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX"));
+
+ // Check a $PREFIX with multiple arguments is invalid
+ processLineException(reader_, "$prefix A B", LOG_PREFIX_EXTRA_ARGS);
+
+ // Prefixes should be alphanumeric (with underscores) and not start
+ // with a number.
+ processLineException(reader_, "$prefix ab[cd", LOG_PREFIX_INVALID_ARG);
+ processLineException(reader_, "$prefix 123", LOG_PREFIX_INVALID_ARG);
+ processLineException(reader_, "$prefix 1ABC", LOG_PREFIX_INVALID_ARG);
+
+ // A valid prefix should be accepted
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX dlm__"));
+ EXPECT_EQ(string("dlm__"), reader_.getPrefix());
+
+ // And check that the parser fails on invalid prefixes...
+ processLineException(reader_, "$prefix 1ABC", LOG_PREFIX_INVALID_ARG);
+
+ // Check that we can clear the prefix as well
+ reader_.clearPrefix();
+ EXPECT_EQ(string(""), reader_.getPrefix());
+
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX dlm__"));
+ EXPECT_EQ(string("dlm__"), reader_.getPrefix());
+ EXPECT_NO_THROW(reader_.processLine("$PREFIX"));
+ EXPECT_EQ(string(""), reader_.getPrefix());
+}
+
+// Check that it can parse a namespace
+
+TEST_F(MessageReaderTest, Namespace) {
+
+ // Check that no $NAMESPACE is present
+ EXPECT_EQ(string(""), reader_.getNamespace());
+
+ // Check that a $NAMESPACE directive with no argument generates an error.
+ processLineException(reader_, "$NAMESPACE", LOG_NAMESPACE_NO_ARGS);
+
+ // Check a $NAMESPACE with multiple arguments is invalid
+ processLineException(reader_, "$namespace A B", LOG_NAMESPACE_EXTRA_ARGS);
+
+ // Namespaces should be alphanumeric (with underscores and colons)
+ processLineException(reader_, "$namespace ab[cd", LOG_NAMESPACE_INVALID_ARG);
+
+ // A valid $NAMESPACE should be accepted
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE isc"));
+ EXPECT_EQ(string("isc"), reader_.getNamespace());
+
+ // (Check that we can clear the namespace)
+ reader_.clearNamespace();
+ EXPECT_EQ(string(""), reader_.getNamespace());
+
+ // Check that a valid namespace can include colons
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE isc::log"));
+ EXPECT_EQ(string("isc::log"), reader_.getNamespace());
+
+ // Check that the indication of the anonymous namespace will be recognized.
+ reader_.clearNamespace();
+ EXPECT_NO_THROW(reader_.processLine("$NAMESPACE ::"));
+ EXPECT_EQ(string("::"), reader_.getNamespace());
+
+ // ... and that another $NAMESPACE is rejected
+ processLineException(reader_, "$NAMESPACE ABC", LOG_DUPLICATE_NAMESPACE);
+}
+
+// Check that it can parse a line
+
+TEST_F(MessageReaderTest, ValidMessageAddDefault) {
+
+ // Add a couple of valid messages
+ reader_.processLine("% GLOBAL1\t\tthis is message global one\n");
+ reader_.processLine("%GLOBAL2 this is message global two");
+
+ // ... and check them
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no messages were not added
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageAdd) {
+
+ // Add a couple of valid messages
+ reader_.processLine("%GLOBAL1\t\tthis is message global one\n",
+ MessageReader::ADD);
+ reader_.processLine("% GLOBAL2 this is message global two",
+ MessageReader::ADD);
+
+ // ... and check them
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no messages were not added
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageReplace) {
+
+ dictionary_->add("GLOBAL1", "original global1 message");
+ dictionary_->add("GLOBAL2", "original global2 message");
+
+ // Replace a couple of valid messages
+ reader_.processLine("% GLOBAL1\t\tthis is message global one\n",
+ MessageReader::REPLACE);
+ reader_.processLine("% GLOBAL2 this is message global two",
+ MessageReader::REPLACE);
+
+ // ... and check them
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no messages were not added
+ vector<string> not_added = reader_.getNotAdded();
+ EXPECT_EQ(0, not_added.size());
+}
+
+// Do checks on overflows, although this essentially duplicates the checks
+// in MessageDictionary.
+
+TEST_F(MessageReaderTest, Overflows) {
+
+ // Add a couple of valid messages
+ reader_.processLine("% GLOBAL1\t\tthis is message global one\n");
+ reader_.processLine("% GLOBAL2 this is message global two");
+
+ // Add a duplicate in ADD mode.
+ reader_.processLine("% GLOBAL1\t\tthis is a replacement for global one");
+
+ // Replace a non-existent one in REPLACE mode
+ reader_.processLine("% LOCAL\t\tthis is a new message",
+ MessageReader::REPLACE);
+
+ // Check what is in the dictionary.
+ EXPECT_EQ(string("this is message global one"),
+ dictionary_->getText("GLOBAL1"));
+ EXPECT_EQ(string("this is message global two"),
+ dictionary_->getText("GLOBAL2"));
+ EXPECT_EQ(2, dictionary_->size());
+
+ // ... and ensure no overflows
+ vector<string> not_added = reader_.getNotAdded();
+ ASSERT_EQ(2, not_added.size());
+
+ sort(not_added.begin(), not_added.end());
+ EXPECT_EQ(string("GLOBAL1"), not_added[0]);
+ EXPECT_EQ(string("LOCAL"), not_added[1]);
+}
diff --git a/src/lib/log/tests/output_option_unittest.cc b/src/lib/log/tests/output_option_unittest.cc
new file mode 100644
index 0000000..bf922a9
--- /dev/null
+++ b/src/lib/log/tests/output_option_unittest.cc
@@ -0,0 +1,60 @@
+// 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 <string>
+
+#include <gtest/gtest.h>
+
+#include <log/output_option.h>
+
+using namespace isc::log;
+using namespace std;
+
+// As OutputOption is a struct, the only meaningful test is to check that it
+// initializes correctly.
+
+TEST(OutputOptionTest, Initialization) {
+ OutputOption option;
+
+ EXPECT_EQ(OutputOption::DEST_CONSOLE, option.destination);
+ EXPECT_EQ(OutputOption::STR_STDERR, option.stream);
+ EXPECT_TRUE(option.flush);
+ EXPECT_EQ(string("LOCAL0"), option.facility);
+ EXPECT_EQ(string(""), option.filename);
+ EXPECT_EQ(0, option.maxsize);
+ EXPECT_EQ(0, option.maxver);
+}
+
+TEST(OutputOption, getDestination) {
+ EXPECT_EQ(OutputOption::DEST_CONSOLE, getDestination("console"));
+ EXPECT_EQ(OutputOption::DEST_CONSOLE, getDestination("CONSOLE"));
+ EXPECT_EQ(OutputOption::DEST_CONSOLE, getDestination("CoNSoLE"));
+ EXPECT_EQ(OutputOption::DEST_FILE, getDestination("file"));
+ EXPECT_EQ(OutputOption::DEST_FILE, getDestination("FILE"));
+ EXPECT_EQ(OutputOption::DEST_FILE, getDestination("fIlE"));
+ EXPECT_EQ(OutputOption::DEST_SYSLOG, getDestination("syslog"));
+ EXPECT_EQ(OutputOption::DEST_SYSLOG, getDestination("SYSLOG"));
+ EXPECT_EQ(OutputOption::DEST_SYSLOG, getDestination("SYSlog"));
+
+ // bad values should default to DEST_CONSOLE
+ EXPECT_EQ(OutputOption::DEST_CONSOLE, getDestination("SOME_BAD_VALUE"));
+}
+
+TEST(OutputOption, getStream) {
+ EXPECT_EQ(OutputOption::STR_STDOUT, getStream("stdout"));
+ EXPECT_EQ(OutputOption::STR_STDOUT, getStream("STDOUT"));
+ EXPECT_EQ(OutputOption::STR_STDOUT, getStream("STdouT"));
+ EXPECT_EQ(OutputOption::STR_STDERR, getStream("stderr"));
+ EXPECT_EQ(OutputOption::STR_STDERR, getStream("STDERR"));
+ EXPECT_EQ(OutputOption::STR_STDERR, getStream("StDeRR"));
+
+ // bad values should default to stdout
+ EXPECT_EQ(OutputOption::STR_STDOUT, getStream("some bad value"));
+ EXPECT_EQ(OutputOption::STR_STDOUT, getStream(""));
+}
+
diff --git a/src/lib/log/tests/run_initializer_unittests.cc b/src/lib/log/tests/run_initializer_unittests.cc
new file mode 100644
index 0000000..7765c14
--- /dev/null
+++ b/src/lib/log/tests/run_initializer_unittests.cc
@@ -0,0 +1,19 @@
+// 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 <stdlib.h>
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/log/tests/run_unittests.cc b/src/lib/log/tests/run_unittests.cc
new file mode 100644
index 0000000..2753b0b
--- /dev/null
+++ b/src/lib/log/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// 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 <stdlib.h>
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/log/tests/severity_test.sh.in b/src/lib/log/tests/severity_test.sh.in
new file mode 100644
index 0000000..02376f8
--- /dev/null
+++ b/src/lib/log/tests/severity_test.sh.in
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+# Copyright (C) 2011-2020 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/.
+
+# Checks that the logger will limit the output of messages less severe than
+# the severity/debug setting.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include common test library.
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+tempfile="@abs_builddir@/severity_test_tempfile_$$"
+
+test_start 'severity.default-parameters'
+cat > $tempfile << .
+FATAL [example] LOG_WRITE_ERROR error writing to test1: 42
+ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
+WARN [example] LOG_BAD_STREAM bad log console output stream: example
+WARN [example.alpha] LOG_READ_ERROR error reading from message file a.txt: dummy reason
+INFO [example.alpha] LOG_INPUT_OPEN_FAIL unable to open message file example.msg for input: dummy reason
+FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
+ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
+WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
+INFO [example.beta] LOG_READ_ERROR error reading from message file beta: info
+.
+./logger_example -c stdout | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+test_start 'severity.filter'
+cat > $tempfile << .
+FATAL [example] LOG_WRITE_ERROR error writing to test1: 42
+ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
+FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
+ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
+.
+./logger_example -c stdout -s error | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+test_start 'severity.debug-level'
+cat > $tempfile << .
+FATAL [example] LOG_WRITE_ERROR error writing to test1: 42
+ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
+WARN [example] LOG_BAD_STREAM bad log console output stream: example
+WARN [example.alpha] LOG_READ_ERROR error reading from message file a.txt: dummy reason
+INFO [example.alpha] LOG_INPUT_OPEN_FAIL unable to open message file example.msg for input: dummy reason
+DEBUG [example] LOG_READING_LOCAL_FILE reading local message file example/0
+DEBUG [example] LOG_READING_LOCAL_FILE reading local message file example/24
+DEBUG [example] LOG_READING_LOCAL_FILE reading local message file example/25
+FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
+ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
+WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
+INFO [example.beta] LOG_READ_ERROR error reading from message file beta: info
+DEBUG [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta/25
+.
+./logger_example -c stdout -s debug -d 25 | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\.\(0x\)\{0,1\}\([0-9A-Fa-f]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+test_finish $?
+
+# Tidy up
+rm -f $tempfile
diff --git a/src/lib/log/tests/tempdir.h.in b/src/lib/log/tests/tempdir.h.in
new file mode 100644
index 0000000..c805844
--- /dev/null
+++ b/src/lib/log/tests/tempdir.h.in
@@ -0,0 +1,21 @@
+// 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/.
+
+#ifndef TEMPDIR_H
+#define TEMPDIR_H
+
+/// \brief Define temporary directory
+///
+/// Defines the temporary directory in which temporary files used by the
+/// unit tests are created.
+
+#include <string>
+
+namespace {
+std::string TEMP_DIR("@builddir@");
+}
+
+#endif // TEMPDIR_H
diff --git a/src/lib/mysql/Makefile.am b/src/lib/mysql/Makefile.am
new file mode 100644
index 0000000..f3fa69b
--- /dev/null
+++ b/src/lib/mysql/Makefile.am
@@ -0,0 +1,32 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MYSQL_CPPFLAGS)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-mysql.la
+libkea_mysql_la_SOURCES = mysql_connection.cc mysql_connection.h
+libkea_mysql_la_SOURCES += mysql_binding.cc mysql_binding.h
+libkea_mysql_la_SOURCES += mysql_constants.h
+
+libkea_mysql_la_LIBADD = $(top_builddir)/src/lib/database/libkea-database.la
+libkea_mysql_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_mysql_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_mysql_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_mysql_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_mysql_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_mysql_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+libkea_mysql_la_LDFLAGS = -no-undefined -version-info 53:0:0
+
+libkea_mysql_la_LDFLAGS += $(MYSQL_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_mysql_includedir = $(pkgincludedir)/mysql
+libkea_mysql_include_HEADERS = \
+ mysql_binding.h \
+ mysql_connection.h \
+ mysql_constants.h
diff --git a/src/lib/mysql/Makefile.in b/src/lib/mysql/Makefile.in
new file mode 100644
index 0000000..a1675c7
--- /dev/null
+++ b/src/lib/mysql/Makefile.in
@@ -0,0 +1,960 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/mysql
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_mysql_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)$(libkea_mysql_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_mysql_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_mysql_la_OBJECTS = mysql_connection.lo mysql_binding.lo
+libkea_mysql_la_OBJECTS = $(am_libkea_mysql_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_mysql_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_mysql_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)/mysql_binding.Plo \
+ ./$(DEPDIR)/mysql_connection.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_mysql_la_SOURCES)
+DIST_SOURCES = $(libkea_mysql_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 = $(libkea_mysql_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . testutils tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(MYSQL_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-mysql.la
+libkea_mysql_la_SOURCES = mysql_connection.cc mysql_connection.h \
+ mysql_binding.cc mysql_binding.h mysql_constants.h
+libkea_mysql_la_LIBADD = \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libkea_mysql_la_LDFLAGS = -no-undefined -version-info 53:0:0 \
+ $(MYSQL_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_mysql_includedir = $(pkgincludedir)/mysql
+libkea_mysql_include_HEADERS = \
+ mysql_binding.h \
+ mysql_connection.h \
+ mysql_constants.h
+
+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/mysql/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/mysql/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-mysql.la: $(libkea_mysql_la_OBJECTS) $(libkea_mysql_la_DEPENDENCIES) $(EXTRA_libkea_mysql_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_mysql_la_LINK) -rpath $(libdir) $(libkea_mysql_la_OBJECTS) $(libkea_mysql_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mysql_binding.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mysql_connection.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_mysql_includeHEADERS: $(libkea_mysql_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_mysql_include_HEADERS)'; test -n "$(libkea_mysql_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_mysql_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_mysql_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)$(libkea_mysql_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_mysql_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_mysql_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_mysql_include_HEADERS)'; test -n "$(libkea_mysql_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_mysql_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_mysql_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/mysql_binding.Plo
+ -rm -f ./$(DEPDIR)/mysql_connection.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-libkea_mysql_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)/mysql_binding.Plo
+ -rm -f ./$(DEPDIR)/mysql_connection.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-libkea_mysql_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_mysql_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-libkea_mysql_includeHEADERS
+
+.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/mysql/mysql_binding.cc b/src/lib/mysql/mysql_binding.cc
new file mode 100644
index 0000000..67ad95d
--- /dev/null
+++ b/src/lib/mysql/mysql_binding.cc
@@ -0,0 +1,349 @@
+// Copyright (C) 2018-2022 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 <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <mysql/mysql_binding.h>
+
+using namespace boost::posix_time;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace db {
+
+std::string
+MySqlBinding::getString() const {
+ // Make sure the binding type is text.
+ validateAccess<std::string>();
+ if (length_ == 0) {
+ return (std::string());
+ }
+ return (std::string(buffer_.begin(), buffer_.begin() + length_));
+}
+
+std::string
+MySqlBinding::getStringOrDefault(const std::string& default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getString());
+}
+
+ElementPtr
+MySqlBinding::getJSON() const {
+ if (amNull()) {
+ return (ElementPtr());
+ }
+ std::string s = getString();
+ return (Element::fromJSON(s));
+}
+
+std::vector<uint8_t>
+MySqlBinding::getBlob() const {
+ // Make sure the binding type is blob.
+ validateAccess<std::vector<uint8_t> >();
+ if (length_ == 0) {
+ return (std::vector<uint8_t>());
+ }
+ return (std::vector<uint8_t>(buffer_.begin(), buffer_.begin() + length_));
+}
+
+std::vector<uint8_t>
+MySqlBinding::getBlobOrDefault(const std::vector<uint8_t>& default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getBlob());
+}
+
+float
+MySqlBinding::getFloat() const {
+ // It may seem a bit weird that we use getInteger template method
+ // for getting a floating point value. However, the getInteger method
+ // seems to be generic enough to support it. If we were to redo the
+ // API of this class we would probably introduce a getNumericValue
+ // method instead of getInteger. However, we already have getInteger
+ // used in many places so we should stick to it.
+ return (getInteger<float>());
+}
+
+ptime
+MySqlBinding::getTimestamp() const {
+ // Make sure the binding type is timestamp.
+ validateAccess<ptime>();
+ // Copy the buffer contents into native timestamp structure and
+ // then convert it to posix time.
+ const MYSQL_TIME* database_time = reinterpret_cast<const MYSQL_TIME*>(&buffer_[0]);
+ return (convertFromDatabaseTime(*database_time));
+}
+
+ptime
+MySqlBinding::getTimestampOrDefault(const ptime& default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getTimestamp());
+}
+
+MySqlBindingPtr
+MySqlBinding::createString(const unsigned long length) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::string>::column_type,
+ length));
+ return (binding);
+}
+
+MySqlBindingPtr
+MySqlBinding::createString(const std::string& value) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::string>::column_type,
+ value.size()));
+ binding->setBufferValue(value.begin(), value.end());
+ return (binding);
+}
+
+MySqlBindingPtr
+MySqlBinding::condCreateString(const Optional<std::string>& value) {
+ return (value.unspecified() ? MySqlBinding::createNull() : createString(value));
+}
+
+MySqlBindingPtr
+MySqlBinding::createBlob(const unsigned long length) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<std::vector<uint8_t> >::column_type,
+ length));
+ return (binding);
+}
+
+MySqlBindingPtr
+MySqlBinding::createFloat(const float value) {
+ // It may seem a bit weird that we use createInteger template method
+ // for setting a floating point value. However, the setInteger method
+ // seems to be generic enough to support it. If we were to redo the
+ // API of this class we would probably introduce a createNumericValue
+ // method instead of createInteger. However, we already have createInteger
+ // used in many places so we should stick to it.
+ return (createInteger<float>(value));
+}
+
+MySqlBindingPtr
+MySqlBinding::createBool() {
+ return (createInteger<uint8_t>(static_cast<uint8_t>(false)));
+}
+
+MySqlBindingPtr
+MySqlBinding::createBool(const bool value) {
+ return (createInteger<uint8_t>(static_cast<uint8_t>(value)));
+}
+
+MySqlBindingPtr
+MySqlBinding::condCreateBool(const util::Optional<bool>& value) {
+ if (value.unspecified()) {
+ return (MySqlBinding::createNull());
+ }
+
+ return (createInteger<uint8_t>(static_cast<uint8_t>(value.get())));
+}
+
+MySqlBindingPtr
+MySqlBinding::condCreateIPv4Address(const Optional<IOAddress>& value) {
+ // If the value is unspecified it doesn't matter what the value is.
+ if (value.unspecified()) {
+ return (MySqlBinding::createNull());
+ }
+
+ // Make sure it is an IPv4 address.
+ if (!value.get().isV4()) {
+ isc_throw(BadValue, "unable to create a MySQL binding: specified value '"
+ << value.get().toText() << "' is not an IPv4 address");
+ }
+
+ return (createInteger<uint32_t>(value.get().toUint32()));
+}
+
+MySqlBindingPtr
+MySqlBinding::createTimestamp(const boost::posix_time::ptime& timestamp) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<ptime>::column_type,
+ MySqlBindingTraits<ptime>::length));
+ binding->setTimestampValue(timestamp);
+ return (binding);
+}
+
+MySqlBindingPtr
+MySqlBinding::createTimestamp() {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<ptime>::column_type,
+ MySqlBindingTraits<ptime>::length));
+ return (binding);
+}
+
+MySqlBindingPtr
+MySqlBinding::createNull() {
+ MySqlBindingPtr binding(new MySqlBinding(MYSQL_TYPE_NULL, 0));
+ return (binding);
+}
+
+void
+MySqlBinding::convertToDatabaseTime(const time_t input_time,
+ MYSQL_TIME& output_time) {
+
+ // Clear output data.
+ memset(&output_time, 0, sizeof(MYSQL_TIME));
+
+ // Convert to broken-out time
+ struct tm time_tm;
+ (void) localtime_r(&input_time, &time_tm);
+
+ // Place in output expire structure.
+ output_time.year = time_tm.tm_year + 1900;
+ output_time.month = time_tm.tm_mon + 1; // Note different base
+ output_time.day = time_tm.tm_mday;
+ output_time.hour = time_tm.tm_hour;
+ output_time.minute = time_tm.tm_min;
+ output_time.second = time_tm.tm_sec;
+ output_time.second_part = 0; // No fractional seconds
+ output_time.neg = my_bool(0); // Not negative
+}
+
+void
+MySqlBinding::convertToDatabaseTime(const boost::posix_time::ptime& input_time,
+ MYSQL_TIME& output_time) {
+ if (input_time.is_not_a_date_time()) {
+ isc_throw(BadValue, "Time value is not a valid posix time");
+ }
+
+ // Clear output data.
+ memset(&output_time, 0, sizeof(MYSQL_TIME));
+
+ output_time.year = input_time.date().year();
+ output_time.month = input_time.date().month();
+ output_time.day = input_time.date().day();
+ output_time.hour = input_time.time_of_day().hours();
+ output_time.minute = input_time.time_of_day().minutes();
+ output_time.second = input_time.time_of_day().seconds();
+ /// @todo Use fractional seconds instead of 0 when minimum supported
+ /// MySQL version has it.
+ output_time.second_part = 0;
+/* output_time.second_part = input_time.time_of_day().fractional_seconds()
+ *1000000/time_duration::ticks_per_second(); */
+ output_time.neg = my_bool(0);
+}
+
+void
+MySqlBinding::convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime,
+ MYSQL_TIME& expire) {
+
+ // Calculate expiry time. Store it in the 64-bit value so as we can detect
+ // overflows.
+ int64_t expire_time_64 = static_cast<int64_t>(cltt) +
+ static_cast<int64_t>(valid_lifetime);
+
+ // Even on 64-bit systems MySQL doesn't seem to accept the timestamps
+ // beyond the max value of int32_t.
+ if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
+ isc_throw(BadValue, "Time value is too large: " << expire_time_64);
+ }
+
+ // Clear output data.
+ memset(&expire, 0, sizeof(MYSQL_TIME));
+
+ const time_t expire_time = static_cast<time_t>(expire_time_64);
+
+ // Convert to broken-out time
+ struct tm expire_tm;
+ (void) localtime_r(&expire_time, &expire_tm);
+
+ // Place in output expire structure.
+ expire.year = expire_tm.tm_year + 1900;
+ expire.month = expire_tm.tm_mon + 1; // Note different base
+ expire.day = expire_tm.tm_mday;
+ expire.hour = expire_tm.tm_hour;
+ expire.minute = expire_tm.tm_min;
+ expire.second = expire_tm.tm_sec;
+ expire.second_part = 0; // No fractional seconds
+ expire.neg = my_bool(0); // Not negative
+}
+
+void
+MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime,
+ time_t& cltt) {
+ // Copy across fields from MYSQL_TIME structure.
+ struct tm expire_tm;
+ memset(&expire_tm, 0, sizeof(expire_tm));
+
+ expire_tm.tm_year = expire.year - 1900;
+ expire_tm.tm_mon = expire.month - 1;
+ expire_tm.tm_mday = expire.day;
+ expire_tm.tm_hour = expire.hour;
+ expire_tm.tm_min = expire.minute;
+ expire_tm.tm_sec = expire.second;
+ expire_tm.tm_isdst = -1; // Let the system work out about DST
+
+ // Convert to local time
+ cltt = mktime(&expire_tm) - valid_lifetime;
+}
+
+ptime
+MySqlBinding::convertFromDatabaseTime(const MYSQL_TIME& database_time) {
+ /// @todo Use fractional seconds instead of 0 when minimum supported
+ /// MySQL version has it.
+ long fractional = 0;
+ // long fractional = database_time.second_part * time_duration::ticks_per_second()/1000000;
+ ptime pt(boost::gregorian::date(database_time.year,
+ boost::gregorian::greg_month(database_time.month),
+ database_time.day),
+ time_duration(database_time.hour, database_time.minute,
+ database_time.second, fractional));
+
+ return (pt);
+}
+
+MySqlBinding::MySqlBinding(enum_field_types buffer_type,
+ const size_t length)
+ // Make sure that the buffer has non-zero length in case we need to
+ // reference its first element to assign it to the MySQL binding.
+ : buffer_(length > 0 ? length : 1), length_(length),
+ null_value_(buffer_type == MYSQL_TYPE_NULL) {
+ memset(&bind_, 0, sizeof(MYSQL_BIND));
+ bind_.buffer_type = buffer_type;
+
+ if (buffer_type != MYSQL_TYPE_NULL) {
+ bind_.buffer = &buffer_[0];
+ bind_.buffer_length = length_;
+ bind_.length = &length_;
+ bind_.is_null = &null_value_;
+ }
+}
+
+void
+MySqlBinding::setBufferLength(const unsigned long length) {
+ length_ = length;
+ // It appears that the MySQL connectors sometimes require that the
+ // buffer is specified (set to a non-zero value), even if the buffer
+ // length is 0. We have found that setting the buffer to 0 value would
+ // cause the value inserted to the database be NULL. In order to avoid
+ // it, we simply make sure that the buffer length is at least 1 byte and
+ // provide the pointer to this byte within the binding.
+ buffer_.resize(length_ > 0 ? length_ : 1);
+ bind_.buffer = &buffer_[0];
+ bind_.buffer_length = length_;
+}
+
+void
+MySqlBinding::setTimestampValue(const ptime& timestamp) {
+ MYSQL_TIME database_time;
+ convertToDatabaseTime(timestamp, database_time);
+ // Copy database time into the buffer.
+ memcpy(static_cast<void*>(&buffer_[0]), reinterpret_cast<char*>(&database_time),
+ sizeof(MYSQL_TIME));
+ bind_.buffer = &buffer_[0];
+}
+
+} // end of namespace isc::db
+} // end of namespace isc
diff --git a/src/lib/mysql/mysql_binding.h b/src/lib/mysql/mysql_binding.h
new file mode 100644
index 0000000..57333f3
--- /dev/null
+++ b/src/lib/mysql/mysql_binding.h
@@ -0,0 +1,671 @@
+// Copyright (C) 2018-2022 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 MYSQL_BINDING_H
+#define MYSQL_BINDING_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <database/database_connection.h>
+#include <exceptions/exceptions.h>
+#include <util/optional.h>
+#include <boost/date_time/posix_time/conversion.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/shared_ptr.hpp>
+#include <mysql/mysql_constants.h>
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <cstdint>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace db {
+
+/// @brief Trait class for column types supported in MySQL.
+///
+/// This class is used to map C++ types to MySQL column types
+/// defined in MySQL C API and their sizes. Specializations of
+/// this class provide such mapping. The default is a BLOB type
+/// which can be used for various input types.
+template<typename T>
+struct MySqlBindingTraits {
+ /// @brief Column type represented in MySQL C API.
+ static const enum_field_types column_type = MYSQL_TYPE_BLOB;
+ /// @brief Length of data in this column.
+ ///
+ /// The value of 0 is used for variable size columns.
+ static const size_t length = 0;
+ /// @brief Boolean value indicating if the numeric value is
+ /// unsigned.
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL TEXT type.
+template<>
+struct MySqlBindingTraits<std::string> {
+ static const enum_field_types column_type = MYSQL_TYPE_STRING;
+ static const size_t length = 0;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL TIMESTAMP type.
+template<>
+struct MySqlBindingTraits<boost::posix_time::ptime> {
+ static const enum_field_types column_type = MYSQL_TYPE_TIMESTAMP;
+ static const size_t length = sizeof(MYSQL_TIME);
+ static const bool am_unsignged = false;
+};
+
+/// @brief Specialization for MySQL TINYINT type.
+template<>
+struct MySqlBindingTraits<int8_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_TINY;
+ static const size_t length = 1;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL TINYINT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint8_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_TINY;
+ static const size_t length = 1;
+ static const bool am_unsigned = true;
+};
+
+/// @brief Speclialization for MySQL SMALLINT type.
+template<>
+struct MySqlBindingTraits<int16_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_SHORT;
+ static const size_t length = 2;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL SMALLINT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint16_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_SHORT;
+ static const size_t length = 2;
+ static const bool am_unsigned = true;
+};
+
+/// @brief Specialization for MySQL INT type.
+template<>
+struct MySqlBindingTraits<int32_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONG;
+ static const size_t length = 4;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL INT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint32_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONG;
+ static const size_t length = 4;
+ static const bool am_unsigned = true;
+};
+
+/// @brief Specialization for MySQL BIGINT type.
+template<>
+struct MySqlBindingTraits<int64_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONGLONG;
+ static const size_t length = 8;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Specialization for MySQL BIGINT UNSIGNED type.
+template<>
+struct MySqlBindingTraits<uint64_t> {
+ static const enum_field_types column_type = MYSQL_TYPE_LONGLONG;
+ static const size_t length = 8;
+ static const bool am_unsigned = true;
+};
+
+template<>
+struct MySqlBindingTraits<float> {
+ static const enum_field_types column_type = MYSQL_TYPE_FLOAT;
+ static const size_t length = 4;
+ static const bool am_unsigned = false;
+};
+
+/// @brief Forward declaration of @c MySqlBinding class.
+class MySqlBinding;
+
+/// @brief Shared pointer to the @c Binding class.
+typedef boost::shared_ptr<MySqlBinding> MySqlBindingPtr;
+
+/// @brief MySQL binding used in prepared statements.
+///
+/// Kea uses prepared statements to execute queries in a database.
+/// Prepared statements include placeholders for the input parameters.
+/// These parameters are passed to the prepared statements via a
+/// collection of @c MYSQL_BIND structures. The same structures are
+/// used to receive the values from the database as a result of
+/// SELECT statements.
+///
+/// The @c MYSQL_BIND structure contains information about the
+/// data type and length. It also contains pointer to the buffer
+/// actually holding the data to be passed to the database, a
+/// flag indicating if the value is null etc.
+///
+/// The @c MySqlBinding is a C++ wrapper around this structure which
+/// is meant to ease management of the MySQL bindings. The major
+/// benefit is that the @c MySqlBinding class owns the buffer,
+/// holding the data as well as other variables which are assigned
+/// to the @c MYSQL_BIND structure. It also automatically detects
+/// the appropriate @c enum_field_types value based on the C++
+/// type used in the binding.
+class MySqlBinding {
+public:
+
+ /// @brief Returns MySQL column type for the binding.
+ ///
+ /// @return column type, e.g. MYSQL_TYPE_STRING.
+ enum_field_types getType() const {
+ return (bind_.buffer_type);
+ }
+
+ /// @brief Returns reference to the native binding.
+ ///
+ /// The returned reference is only valid for the lifetime of the
+ /// object. Make sure that the object is not destroyed as long
+ /// as the binding is required. In particular, do not destroy this
+ /// object before database query is complete.
+ ///
+ /// @return Reference to native MySQL binding.
+ MYSQL_BIND& getMySqlBinding() {
+ return (bind_);
+ }
+
+ /// @brief Returns value held in the binding as string.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type is not @c MYSQL_TYPE_STRING.
+ ///
+ /// @return String value.
+ std::string getString() const;
+
+ /// @brief Returns value held in the binding as string.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type is not @c MYSQL_TYPE_STRING.
+ ///
+ /// @return String value.
+ std::string getStringOrDefault(const std::string& default_value) const;
+
+ /// @brief Returns value held in the binding as JSON.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the binding is not @c MYSQL_TYPE_STRING.
+ /// @throw data::JSONError if the string value is not a valid JSON.
+ ///
+ /// @return JSON structure or NULL if the string is null.
+ data::ElementPtr getJSON() const;
+
+ /// @brief Returns value held in the binding as blob.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type is not @c MYSQL_TYPE_BLOB.
+ ///
+ /// @return Blob in a vector.
+ std::vector<uint8_t> getBlob() const;
+
+ /// @brief Returns value held in the binding as blob.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type is not @c MYSQL_TYPE_BLOB.
+ ///
+ /// @return Blob in a vector.
+ std::vector<uint8_t>
+ getBlobOrDefault(const std::vector<uint8_t>& default_value) const;
+
+ /// @brief Returns numeric value held in the binding.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type does not match the template parameter.
+ ///
+ /// @return Numeric value of a specified type.
+ template<typename T>
+ T getInteger() const {
+ // Make sure that the binding type is numeric.
+ validateAccess<T>();
+
+ // Convert the buffer to a numeric type and then return a copy.
+ const T* value = reinterpret_cast<const T*>(&buffer_[0]);
+ return (*value);
+ }
+
+ /// @brief Returns numeric value held in the binding.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type does not match the
+ /// template parameter.
+ ///
+ /// @return Numeric value of a specified type.
+ template<typename T>
+ T getIntegerOrDefault(T default_value) const {
+ if (amNull()) {
+ return (default_value);
+ }
+ return (getInteger<T>());
+ }
+
+ /// @brief Returns float value held in the binding.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type does not match the template parameter.
+ ///
+ /// @return Float value.
+ float getFloat() const;
+
+ /// @brief Returns boolean value held in the binding.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type is not uint8_t.
+ ///
+ /// @return Boolean value.
+ bool getBool() const {
+ return (static_cast<bool>(getInteger<uint8_t>()));
+ }
+
+ /// @brief Returns timestamp value held in the binding.
+ ///
+ /// Call @c MySqlBinding::amNull to verify that the value is not
+ /// null prior to calling this method.
+ ///
+ /// @throw InvalidOperation if the value is NULL or the binding
+ /// type is not @c MYSQL_TYPE_TIMESTAMP.
+ ///
+ /// @return Timestamp converted to POSIX time.
+ boost::posix_time::ptime getTimestamp() const;
+
+ /// @brief Returns timestamp value held in the binding.
+ ///
+ /// If the value to be returned is null, a default value is returned.
+ ///
+ /// @param default_value Default value.
+ ///
+ /// @throw InvalidOperation if the binding type is not @c MYSQL_TYPE_TIMESTAMP.
+ ///
+ /// @return Timestamp converted to POSIX time.
+ boost::posix_time::ptime
+ getTimestampOrDefault(const boost::posix_time::ptime& default_value) const;
+
+ /// @brief Checks if the bound value is NULL.
+ ///
+ /// @return true if the value in the binding is NULL, false otherwise.
+ bool amNull() const {
+ return (null_value_ == MLM_TRUE);
+ }
+
+ /// @brief Creates binding of text type for receiving data.
+ ///
+ /// @param length Length of the buffer into which received data will
+ /// be stored.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createString(const unsigned long length);
+
+ /// @brief Creates binding of text type for sending data.
+ ///
+ /// @param value String value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createString(const std::string& value);
+
+ /// @brief Conditionally creates binding of text type for sending
+ /// data if provided value is unspecified.
+ ///
+ /// @param value String value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr condCreateString(const util::Optional<std::string>& value);
+
+ /// @brief Creates binding of blob type for receiving data.
+ ///
+ /// @param length Length of the buffer into which received data will
+ /// be stored.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createBlob(const unsigned long length);
+
+ /// @brief Creates binding of blob type for sending data.
+ ///
+ /// @tparam Iterator Type of the iterator.
+ ///
+ /// @param begin Iterator pointing to the beginning of the input
+ /// buffer holding the data to be sent to the database.
+ /// @param end Iterator pointing to the end of the input buffer
+ /// holding the data to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename Iterator>
+ static MySqlBindingPtr createBlob(Iterator begin, Iterator end) {
+ MySqlBindingPtr binding(new MySqlBinding(MYSQL_TYPE_BLOB,
+ std::distance(begin, end)));
+ binding->setBufferValue(begin, end);
+ return (binding);
+ }
+
+ /// @brief Creates binding of numeric type for receiving data.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename T>
+ static MySqlBindingPtr createInteger() {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<T>::column_type,
+ MySqlBindingTraits<T>::length));
+ binding->setValue<T>(0);
+ return (binding);
+ }
+
+ /// @brief Creates binding of numeric type for sending data.
+ ///
+ /// @tparam Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @param value Numeric value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename T>
+ static MySqlBindingPtr createInteger(T value) {
+ MySqlBindingPtr binding(new MySqlBinding(MySqlBindingTraits<T>::column_type,
+ MySqlBindingTraits<T>::length));
+ binding->setValue(value);
+ return (binding);
+ }
+
+ /// @brief Conditionally creates binding of numeric type for sending
+ /// data if provided value is specified.
+ ///
+ /// @tparam T Numeric type corresponding to the binding type, e.g.
+ /// @c uint8_t, @c uint16_t etc.
+ ///
+ /// @param value Numeric value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename T>
+ static MySqlBindingPtr condCreateInteger(const util::Optional<T>& value) {
+ return (value.unspecified() ? createNull() : createInteger<T>(value.get()));
+ }
+
+ /// @brief Creates binding having a float type for sending data.
+ ///
+ /// @param value Float value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createFloat(const float value);
+
+ /// @brief Conditionally creates binding of float type for sending data if
+ /// provided value is specified.
+ ///
+ /// @tparam T Floating point type to be converted to float.
+ ///
+ /// @param value Value to be stored in the database as float.
+ ///
+ /// @return Pointer to the created binding.
+ template<typename T>
+ static MySqlBindingPtr condCreateFloat(const util::Optional<T>& value) {
+ return (value.unspecified() ? createNull() :
+ createInteger<float> (static_cast<float>(value.get())));
+ }
+
+ /// @brief Creates binding having a bool type for receiving data.
+ ///
+ /// @return Pointer to the created binding holding an @c uint8_t
+ /// value representing the boolean value.
+ static MySqlBindingPtr createBool();
+
+ /// @brief Creates binding having a bool type for sending data.
+ ///
+ /// @param value Boolean value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding holding an @c uint8_t
+ /// value representing the boolean value.
+ static MySqlBindingPtr createBool(const bool value);
+
+ /// @brief Conditionally creates binding of @c uint8_t type representing
+ /// a boolean value if provided value is specified.
+ ///
+ /// @param value Boolean value for which the binding should be created.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr condCreateBool(const util::Optional<bool>& value);
+
+ /// @brief Conditionally creates binding of @c uint32_t type representing
+ /// an IPv4 address if provided value is specified.
+ ///
+ /// @param value @c IOAddress encapsulating an IPv4 address.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr
+ condCreateIPv4Address(const util::Optional<asiolink::IOAddress>& value);
+
+ /// @brief Creates binding of timestamp type for receiving data.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createTimestamp();
+
+ /// @brief Creates binding of timestamp type for sending data.
+ ///
+ /// @param timestamp Timestamp value to be sent to the database.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createTimestamp(const boost::posix_time::ptime& timestamp);
+
+ /// @brief Creates binding encapsulating a NULL value.
+ ///
+ /// This method is used to create a binding encapsulating a NULL
+ /// value, which can be used to assign NULL to any type of column.
+ ///
+ /// @return Pointer to the created binding.
+ static MySqlBindingPtr createNull();
+
+ /// @brief Converts time_t value to database time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @param output_time Reference to MYSQL_TIME object where converted time
+ /// will be put.
+ static void convertToDatabaseTime(const time_t input_time,
+ MYSQL_TIME& output_time);
+
+ /// @brief Converts POSIX time value to database time.
+ ///
+ /// @param input_time A POSIX time value representing local time.
+ /// @param output_time Reference to MYSQL_TIME object where converted time
+ /// will be put.
+ static void convertToDatabaseTime(const boost::posix_time::ptime& input_time,
+ MYSQL_TIME& output_time);
+
+ /// @brief Converts Lease Time to Database Times
+ ///
+ /// Within the DHCP servers, times are stored as client last transmit time
+ /// and valid lifetime. In the database, the information is stored as
+ /// valid lifetime and "expire" (time of expiry of the lease). They are
+ /// related by the equation:
+ ///
+ /// - expire = client last transmit time + valid lifetime
+ ///
+ /// This method converts from the times in the lease object into times
+ /// able to be added to the database.
+ ///
+ /// @param cltt Client last transmit time
+ /// @param valid_lifetime Valid lifetime
+ /// @param expire Reference to MYSQL_TIME object where the expiry time of
+ /// the DHCP lease will be put.
+ ///
+ /// @throw isc::BadValue if the sum of the calculated expiration time is
+ /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+ static void convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime,
+ MYSQL_TIME& expire);
+
+ /// @brief Converts Database Time to Lease Times
+ ///
+ /// Within the database, time is stored as "expire" (time of expiry of the
+ /// lease) and valid lifetime. In the DHCP server, the information is
+ /// stored client last transmit time and valid lifetime. These are related
+ /// by the equation:
+ ///
+ /// - client last transmit time = expire - valid_lifetime
+ ///
+ /// This method converts from the times in the database into times
+ /// able to be inserted into the lease object.
+ ///
+ /// @param expire Reference to MYSQL_TIME object from where the expiry
+ /// time of the lease is taken.
+ /// @param valid_lifetime lifetime of the lease.
+ /// @param cltt Reference to location where client last transmit time
+ /// is put.
+ static void convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime,
+ time_t& cltt);
+
+ /// @brief Converts database time to POSIX time.
+ ///
+ /// @param database_time Reference to MYSQL_TIME object where database
+ /// time is stored.
+ ///
+ /// @return Database time converted to local POSIX time.
+ static boost::posix_time::ptime
+ convertFromDatabaseTime(const MYSQL_TIME& database_time);
+
+private:
+
+ /// @brief Private constructor.
+ ///
+ /// This constructor is private because MySQL bindings should only be
+ /// created using static factory functions.
+ ///
+ /// @param buffer_type MySQL buffer type as defined in MySQL C API.
+ /// @param length Buffer length.
+ MySqlBinding(enum_field_types buffer_type, const size_t length);
+
+ /// @brief Assigns new value to a buffer.
+ ///
+ /// @tparam Iterator Type of the iterators marking beginning and end
+ /// of the range to be assigned to the buffer.
+ ///
+ /// @param begin Iterator pointing to the beginning of the assigned
+ /// range.
+ /// @param end Iterator pointing to the end of the assigned range.
+ template<typename Iterator>
+ void setBufferValue(Iterator begin, Iterator end) {
+ length_ = std::distance(begin, end);
+ buffer_.assign(begin, end);
+ // It appears that the MySQL connectors sometimes require that the
+ // buffer is specified (set to a non-zero value), even if the buffer
+ // length is 0. We have found that setting the buffer to 0 value would
+ // cause the value inserted to the database be NULL. In order to avoid
+ // it, we simply make sure that the buffer length is at least 1 byte and
+ // provide the pointer to this byte within the binding.
+ if (buffer_.empty()) {
+ buffer_.resize(1);
+ }
+ bind_.buffer = &buffer_[0];
+ bind_.buffer_length = length_;
+ }
+
+ /// @brief Resizes the buffer and assigns new length to the binding.
+ ///
+ /// @param length New buffer length to be used.
+ void setBufferLength(const unsigned long length);
+
+ /// @brief Copies numeric value to a buffer and modifies "unsigned" flag
+ /// accoriding to the numeric type used.
+ ///
+ /// @tparam T Type of the numeric value.
+ ///
+ /// @param value Value to be copied to the buffer.
+ template<typename T>
+ void setValue(T value) {
+ memcpy(static_cast<void*>(&buffer_[0]), reinterpret_cast<char*>(&value),
+ sizeof(value));
+ bind_.buffer = &buffer_[0];
+ bind_.is_unsigned = (MySqlBindingTraits<T>::am_unsigned ? MLM_TRUE : MLM_FALSE);
+ }
+
+ /// @brief Converts timestamp to database time value and copies it to
+ /// the buffer.
+ ///
+ /// @param timestamp Timestamp value as POSIX time.
+ void setTimestampValue(const boost::posix_time::ptime& timestamp);
+
+ /// @brief Checks if the data accessor called is matching the type
+ /// of the data held in the binding.
+ ///
+ /// @tparam Data type requested, e.g. @c std::string.
+ ///
+ /// @throw InvalidOperation if the requested data type is not matching
+ /// the type of the binding, e.g. called @c getString but the binding
+ /// type is @c MYSQL_TYPE_LONG.
+ template<typename T>
+ void validateAccess() const {
+ // Can't retrieve null value.
+ if (amNull()) {
+ isc_throw(InvalidOperation, "retrieved value is null");
+ }
+ // The type of the accessor must match the stored data type.
+ if (MySqlBindingTraits<T>::column_type != getType()) {
+ isc_throw(InvalidOperation, "mismatched column type");
+ }
+ }
+
+ /// @brief Data buffer (both input and output).
+ std::vector<uint8_t> buffer_;
+
+ /// @brief Buffer length.
+ unsigned long length_;
+
+ /// @brief Flag indicating whether the value of the binding is NULL.
+ my_bool null_value_;
+
+ /// @brief Native MySQL binding wrapped by this class.
+ MYSQL_BIND bind_;
+};
+
+/// @brief Collection of bindings.
+typedef std::vector<MySqlBindingPtr> MySqlBindingCollection;
+
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/mysql/mysql_connection.cc b/src/lib/mysql/mysql_connection.cc
new file mode 100644
index 0000000..652793e
--- /dev/null
+++ b/src/lib/mysql/mysql_connection.cc
@@ -0,0 +1,534 @@
+// Copyright (C) 2012-2023 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 <database/db_log.h>
+#include <exceptions/exceptions.h>
+#include <mysql/mysql_connection.h>
+#include <util/file_utilities.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <algorithm>
+#include <stdint.h>
+#include <string>
+#include <limits>
+
+using namespace isc;
+using namespace std;
+
+namespace isc {
+namespace db {
+
+int MySqlHolder::atexit_ = [] {
+ return atexit([] { mysql_library_end(); });
+}();
+
+/// @todo: Migrate this default value to src/bin/dhcpX/simple_parserX.cc
+const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
+
+MySqlTransaction::MySqlTransaction(MySqlConnection& conn)
+ : conn_(conn), committed_(false) {
+ conn_.startTransaction();
+}
+
+MySqlTransaction::~MySqlTransaction() {
+ // Rollback if the MySqlTransaction::commit wasn't explicitly
+ // called.
+ if (!committed_) {
+ conn_.rollback();
+ }
+}
+
+void
+MySqlTransaction::commit() {
+ conn_.commit();
+ committed_ = true;
+}
+
+// Open the database using the parameters passed to the constructor.
+
+void
+MySqlConnection::openDatabase() {
+ // Set up the values of the parameters
+ const char* host = "localhost";
+ string shost;
+ try {
+ shost = getParameter("host");
+ host = shost.c_str();
+ } catch (...) {
+ // No host. Fine, we'll use "localhost"
+ }
+
+ unsigned int port = 0;
+ try {
+ setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidPort, ex.what());
+ }
+
+ const char* user = NULL;
+ string suser;
+ try {
+ suser = getParameter("user");
+ user = suser.c_str();
+ } catch (...) {
+ // No user. Fine, we'll use NULL
+ }
+
+ const char* password = NULL;
+ string spassword;
+ try {
+ spassword = getParameter("password");
+ password = spassword.c_str();
+ } catch (...) {
+ // No password. Fine, we'll use NULL
+ }
+
+ const char* name = NULL;
+ string sname;
+ try {
+ sname = getParameter("name");
+ name = sname.c_str();
+ } catch (...) {
+ // No database name. Throw a "NoName" exception
+ isc_throw(NoDatabaseName, "must specify a name for the database");
+ }
+
+ unsigned int connect_timeout = MYSQL_DEFAULT_CONNECTION_TIMEOUT;
+ unsigned int read_timeout = 0;
+ unsigned int write_timeout = 0;
+ try {
+ // The timeout is only valid if greater than zero, as depending on the
+ // database, a zero timeout might signify something like "wait
+ // indefinitely".
+ setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
+ // Other timeouts can be 0, meaning that the database client will follow a default
+ // behavior. Earlier MySQL versions didn't have these parameters, so we allow 0
+ // to skip setting them.
+ setIntParameterValue("read-timeout", 0, numeric_limits<int>::max(), read_timeout);
+ setIntParameterValue("write-timeout", 0, numeric_limits<int>::max(), write_timeout);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidTimeout, ex.what());
+ }
+
+ const char* ca_file(0);
+ const char* ca_dir(0);
+ string sca;
+ try {
+ sca = getParameter("trust-anchor");
+ tls_ = true;
+ if (util::file::isDir(sca)) {
+ ca_dir = sca.c_str();
+ } else {
+ ca_file = sca.c_str();
+ }
+ } catch (...) {
+ // No trust anchor
+ }
+
+ const char* cert_file(0);
+ string scert;
+ try {
+ scert = getParameter("cert-file");
+ tls_ = true;
+ cert_file = scert.c_str();
+ } catch (...) {
+ // No client certificate file
+ }
+
+ const char* key_file(0);
+ string skey;
+ try {
+ skey = getParameter("key-file");
+ tls_ = true;
+ key_file = skey.c_str();
+ } catch (...) {
+ // No private key file
+ }
+
+ const char* cipher_list(0);
+ string scipher;
+ try {
+ scipher = getParameter("cipher-list");
+ tls_ = true;
+ cipher_list = scipher.c_str();
+ } catch (...) {
+ // No cipher list
+ }
+
+ // Set options for the connection:
+ //
+ // Set options for the connection:
+ // Make sure auto_reconnect is OFF! Enabling it leaves us with an unusable
+ // connection after a reconnect as among other things, it drops all our
+ // pre-compiled statements.
+ my_bool auto_reconnect = MLM_FALSE;
+ int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
+ mysql_error(mysql_));
+ }
+
+ // Make sure we have a large idle time window ... say 30 days...
+ const char *wait_time = "SET SESSION wait_timeout = 30 * 86400";
+ result = mysql_options(mysql_, MYSQL_INIT_COMMAND, wait_time);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set wait_timeout " <<
+ mysql_error(mysql_));
+ }
+
+ // Set SQL mode options for the connection: SQL mode governs how what
+ // constitutes insertable data for a given column, and how to handle
+ // invalid data. We want to ensure we get the strictest behavior and
+ // to reject invalid data with an error.
+ const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
+ result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set SQL mode options: " <<
+ mysql_error(mysql_));
+ }
+
+ // Connection timeout, the amount of time taken for the client to drop
+ // the connection if the server is not responding.
+ result = mysql_options(mysql_, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set database connection timeout: " <<
+ mysql_error(mysql_));
+ }
+
+ // Set the read timeout if it has been specified. Otherwise, the timeout is
+ // not used.
+ if (read_timeout > 0) {
+ result = mysql_options(mysql_, MYSQL_OPT_READ_TIMEOUT, &read_timeout);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set database read timeout: " <<
+ mysql_error(mysql_));
+ }
+ }
+
+ // Set the write timeout if it has been specified. Otherwise, the timeout
+ // is not used.
+ if (write_timeout > 0) {
+ result = mysql_options(mysql_, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set database write timeout: " <<
+ mysql_error(mysql_));
+ }
+ }
+
+ // If TLS is enabled set it. If something should go wrong it will happen
+ // later at the mysql_real_connect call.
+ if (tls_) {
+ mysql_ssl_set(mysql_, key_file, cert_file, ca_file, ca_dir,
+ cipher_list);
+ }
+
+ // Open the database.
+ //
+ // The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
+ // the affected rows are the number of rows found that match the
+ // WHERE clause of the SQL statement, not the rows changed. The reason
+ // here is that MySQL apparently does not update a row if data has not
+ // changed and so the "affected rows" (retrievable from MySQL) is zero.
+ // This makes it hard to distinguish whether the UPDATE changed no rows
+ // because no row matching the WHERE clause was found, or because a
+ // row was found but no data was altered.
+ MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
+ port, NULL, CLIENT_FOUND_ROWS);
+ if (status != mysql_) {
+ isc_throw(DbOpenError, mysql_error(mysql_));
+ }
+
+ // Enable autocommit. In case transaction is explicitly used, this
+ // setting will be overwritten for the transaction. However, there are
+ // cases when lack of autocommit could cause transactions to hang
+ // until commit or rollback is explicitly called. This already
+ // caused issues for some unit tests which were unable to cleanup
+ // the database after the test because of pending transactions.
+ // Use of autocommit will eliminate this problem.
+ my_bool autocommit_result = mysql_autocommit(mysql_, 1);
+ if (autocommit_result != 0) {
+ isc_throw(DbOperationError, mysql_error(mysql_));
+ }
+
+ // To avoid a flush to disk on every commit, the global parameter
+ // innodb_flush_log_at_trx_commit should be set to 2. This will cause the
+ // changes to be written to the log, but flushed to disk in the background
+ // every second. Setting the parameter to that value will speed up the
+ // system, but at the risk of losing data if the system crashes.
+}
+
+// Get schema version.
+
+std::pair<uint32_t, uint32_t>
+MySqlConnection::getVersion(const ParameterMap& parameters) {
+ // Get a connection.
+ MySqlConnection conn(parameters);
+
+ // Open the database.
+ conn.openDatabase();
+
+ // Allocate a new statement.
+ MYSQL_STMT *stmt = mysql_stmt_init(conn.mysql_);
+ if (stmt == NULL) {
+ isc_throw(DbOperationError, "unable to allocate MySQL prepared "
+ "statement structure, reason: " << mysql_error(conn.mysql_));
+ }
+
+ try {
+
+ // Prepare the statement from SQL text.
+ const char* version_sql = "SELECT version, minor FROM schema_version";
+ int status = mysql_stmt_prepare(stmt, version_sql, strlen(version_sql));
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to prepare MySQL statement <"
+ << version_sql << ">, reason: "
+ << mysql_error(conn.mysql_));
+ }
+
+ // Execute the prepared statement.
+ if (MysqlExecuteStatement(stmt) != 0) {
+ isc_throw(DbOperationError, "cannot execute schema version query <"
+ << version_sql << ">, reason: "
+ << mysql_errno(conn.mysql_));
+ }
+
+ // Bind the output of the statement to the appropriate variables.
+ MYSQL_BIND bind[2];
+ memset(bind, 0, sizeof(bind));
+
+ uint32_t version;
+ bind[0].buffer_type = MYSQL_TYPE_LONG;
+ bind[0].is_unsigned = 1;
+ bind[0].buffer = &version;
+ bind[0].buffer_length = sizeof(version);
+
+ uint32_t minor;
+ bind[1].buffer_type = MYSQL_TYPE_LONG;
+ bind[1].is_unsigned = 1;
+ bind[1].buffer = &minor;
+ bind[1].buffer_length = sizeof(minor);
+
+ if (mysql_stmt_bind_result(stmt, bind)) {
+ isc_throw(DbOperationError, "unable to bind result set for <"
+ << version_sql << ">, reason: "
+ << mysql_errno(conn.mysql_));
+ }
+
+ // Fetch the data.
+ if (mysql_stmt_fetch(stmt)) {
+ isc_throw(DbOperationError, "unable to bind result set for <"
+ << version_sql << ">, reason: "
+ << mysql_errno(conn.mysql_));
+ }
+
+ // Discard the statement and its resources
+ mysql_stmt_close(stmt);
+ return (std::make_pair(version, minor));
+
+ } catch (const std::exception&) {
+ // Avoid a memory leak on error.
+ mysql_stmt_close(stmt);
+
+ // Send the exception to the caller.
+ throw;
+ }
+}
+
+// Prepared statement setup. The textual form of an SQL statement is stored
+// in a vector of strings (text_statements_) and is used in the output of
+// error messages. The SQL statement is also compiled into a "prepared
+// statement" (stored in statements_), which avoids the overhead of compilation
+// during use. As prepared statements have resources allocated to them, the
+// class destructor explicitly destroys them.
+
+void
+MySqlConnection::prepareStatement(uint32_t index, const char* text) {
+ // Validate that there is space for the statement in the statements array
+ // and that nothing has been placed there before.
+ if ((index >= statements_.size()) || (statements_[index] != NULL)) {
+ isc_throw(InvalidParameter, "invalid prepared statement index (" <<
+ static_cast<int>(index) << ") or indexed prepared " <<
+ "statement is not null");
+ }
+
+ // All OK, so prepare the statement
+ text_statements_[index] = std::string(text);
+ statements_[index] = mysql_stmt_init(mysql_);
+ if (statements_[index] == NULL) {
+ isc_throw(DbOperationError, "unable to allocate MySQL prepared "
+ "statement structure, reason: " << mysql_error(mysql_));
+ }
+
+ int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<
+ text << ">, reason: " << mysql_error(mysql_));
+ }
+}
+
+void
+MySqlConnection::prepareStatements(const TaggedStatement* start_statement,
+ const TaggedStatement* end_statement) {
+ // Created the MySQL prepared statements for each DML statement.
+ for (const TaggedStatement* tagged_statement = start_statement;
+ tagged_statement != end_statement; ++tagged_statement) {
+ if (tagged_statement->index >= statements_.size()) {
+ statements_.resize(tagged_statement->index + 1, NULL);
+ text_statements_.resize(tagged_statement->index + 1,
+ std::string(""));
+ }
+ prepareStatement(tagged_statement->index,
+ tagged_statement->text);
+ }
+}
+
+void MySqlConnection::clearStatements() {
+ statements_.clear();
+ text_statements_.clear();
+}
+
+/// @brief Destructor
+MySqlConnection::~MySqlConnection() {
+ // Free up the prepared statements, ignoring errors. (What would we do
+ // about them? We're destroying this object and are not really concerned
+ // with errors on a database connection that is about to go away.)
+ for (int i = 0; i < statements_.size(); ++i) {
+ if (statements_[i] != NULL) {
+ (void) mysql_stmt_close(statements_[i]);
+ statements_[i] = NULL;
+ }
+ }
+ statements_.clear();
+ text_statements_.clear();
+}
+
+// Time conversion methods.
+//
+// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
+// from the current timezone to UTC for storage, and from UTC to the current
+// timezone for retrieval.
+//
+// This causes no problems providing that:
+// a) cltt is given in local time
+// b) We let the system take care of timezone conversion when converting
+// from a time read from the database into a local time.
+void
+MySqlConnection::convertToDatabaseTime(const time_t input_time,
+ MYSQL_TIME& output_time) {
+ MySqlBinding::convertToDatabaseTime(input_time, output_time);
+}
+
+void
+MySqlConnection::convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime,
+ MYSQL_TIME& expire) {
+ MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire);
+}
+
+void
+MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime, time_t& cltt) {
+ MySqlBinding::convertFromDatabaseTime(expire, valid_lifetime, cltt);
+}
+
+void
+MySqlConnection::startTransaction() {
+ // If it is nested transaction, do nothing.
+ if (++transaction_ref_count_ > 1) {
+ return;
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_START_TRANSACTION);
+ checkUnusable();
+ // We create prepared statements for all other queries, but MySQL
+ // don't support prepared statements for START TRANSACTION.
+ int status = mysql_query(mysql_, "START TRANSACTION");
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to start transaction, "
+ "reason: " << mysql_error(mysql_));
+ }
+}
+
+bool
+MySqlConnection::isTransactionStarted() const {
+ return (transaction_ref_count_ > 0);
+}
+
+void
+MySqlConnection::commit() {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(Unexpected, "commit called for not started transaction - coding error");
+ }
+
+ // When committing nested transaction, do nothing.
+ if (--transaction_ref_count_ > 0) {
+ return;
+ }
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_COMMIT);
+ checkUnusable();
+ if (mysql_commit(mysql_) != 0) {
+ isc_throw(DbOperationError, "commit failed: "
+ << mysql_error(mysql_));
+ }
+}
+
+void
+MySqlConnection::rollback() {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(Unexpected, "rollback called for not started transaction - coding error");
+ }
+
+ // When rolling back nested transaction, do nothing.
+ if (--transaction_ref_count_ > 0) {
+ return;
+ }
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_ROLLBACK);
+ checkUnusable();
+ if (mysql_rollback(mysql_) != 0) {
+ isc_throw(DbOperationError, "rollback failed: "
+ << mysql_error(mysql_));
+ }
+}
+
+template<typename T>
+void
+MySqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) {
+ string svalue;
+ try {
+ svalue = getParameter(name);
+ } catch (...) {
+ // Do nothing if the parameter is not present.
+ }
+ if (svalue.empty()) {
+ return;
+ }
+ try {
+ // Try to convert the value.
+ auto parsed_value = boost::lexical_cast<T>(svalue);
+ // Check if the value is within the specified range.
+ if ((parsed_value < min) || (parsed_value > max)) {
+ isc_throw(BadValue, "bad " << svalue << " value");
+ }
+ // Everything is fine. Return the parsed value.
+ value = parsed_value;
+
+ } catch (...) {
+ // We may end up here when lexical_cast fails or when the
+ // parsed value is not within the desired range. In both
+ // cases let's throw the same general error.
+ isc_throw(BadValue, name << " parameter (" <<
+ svalue << ") must be an integer between "
+ << min << " and " << max);
+ }
+}
+
+} // namespace db
+} // namespace isc
diff --git a/src/lib/mysql/mysql_connection.h b/src/lib/mysql/mysql_connection.h
new file mode 100644
index 0000000..8db7c37
--- /dev/null
+++ b/src/lib/mysql/mysql_connection.h
@@ -0,0 +1,781 @@
+// Copyright (C) 2012-2023 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 MYSQL_CONNECTION_H
+#define MYSQL_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <database/database_connection.h>
+#include <database/db_exceptions.h>
+#include <database/db_log.h>
+#include <exceptions/exceptions.h>
+#include <mysql/mysql_binding.h>
+#include <mysql/mysql_constants.h>
+#include <boost/scoped_ptr.hpp>
+#include <mysql.h>
+#include <mysqld_error.h>
+#include <errmsg.h>
+#include <functional>
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace db {
+
+
+/// @brief Fetch and Release MySQL Results
+///
+/// When a MySQL statement is expected, to fetch the results the function
+/// mysql_stmt_fetch() must be called. As well as getting data, this
+/// allocates internal state. Subsequent calls to mysql_stmt_fetch can be
+/// made, but when all the data is retrieved, mysql_stmt_free_result must be
+/// called to free up the resources allocated.
+///
+/// Created prior to the first fetch, this class's destructor calls
+/// mysql_stmt_free_result, so eliminating the need for an explicit release
+/// in the method calling mysql_stmt_free_result. In this way, it guarantees
+/// that the resources are released even if the MySqlLeaseMgr method concerned
+/// exits via an exception.
+
+class MySqlFreeResult {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Store the pointer to the statement for which data is being fetched.
+ ///
+ /// Note that according to the MySQL documentation, mysql_stmt_free_result
+ /// only releases resources if a cursor has been allocated for the
+ /// statement. This implies that it is a no-op if none have been. Either
+ /// way, any error from mysql_stmt_free_result is ignored. (Generating
+ /// an exception is not much help, as it will only confuse things if the
+ /// method calling mysql_stmt_fetch is exiting via an exception.)
+ MySqlFreeResult(MYSQL_STMT* statement) : statement_(statement)
+ {}
+
+ /// @brief Destructor
+ ///
+ /// Frees up fetch context if a fetch has been successfully executed.
+ ~MySqlFreeResult() {
+ (void) mysql_stmt_free_result(statement_);
+ }
+
+private:
+ MYSQL_STMT* statement_; ///< Statement for which results are freed
+};
+
+/// @brief MySQL Selection Statements
+///
+/// Each statement is associated with an index, which is used to reference the
+/// associated prepared statement.
+struct TaggedStatement {
+ uint32_t index;
+ const char* text;
+};
+
+/// @brief Retry on InnoDB deadlock.
+///
+/// When f(args) returns ER_LOCK_DEADLOCK f(args) is called again up to 5 times.
+///
+/// @tparam Fun Type of the function which must return an int.
+/// @tparam Args Types of arguments.
+/// @param fun The function to call.
+/// @param args Arguments.
+/// @return status (can be ER_LOCK_DEADLOCK after 5 retries).
+template <typename Fun, typename... Args>
+int retryOnDeadlock(Fun& fun, Args... args) {
+ int status;
+ for (unsigned count = 0; count < 5; ++count) {
+ status = fun(args...);
+ if (status != ER_LOCK_DEADLOCK) {
+ break;
+ }
+ }
+ return (status);
+}
+
+/// @brief Execute a prepared statement.
+///
+/// Call mysql_stmt_execute and retry on ER_LOCK_DEADLOCK.
+///
+/// @param stmt Statement to execute.
+/// @return status (can be ER_LOCK_DEADLOCK after 5 retries).
+inline int MysqlExecuteStatement(MYSQL_STMT* stmt) {
+ return (retryOnDeadlock(mysql_stmt_execute, stmt));
+}
+
+/// @brief Execute a literal statement.
+///
+/// Call mysql_query and retry on ER_LOCK_DEADLOCK.
+///
+/// @param mysql MySQL context.
+/// @param stmt Statement to execute.
+/// @return status (can be ER_LOCK_DEADLOCK after 5 retries).
+inline int MysqlQuery(MYSQL* mysql, const char* stmt) {
+ return (retryOnDeadlock(mysql_query, mysql, stmt));
+}
+
+/// @brief MySQL Handle Holder
+///
+/// Small RAII object for safer initialization, will close the database
+/// connection upon destruction. This means that if an exception is thrown
+/// during database initialization, resources allocated to the database are
+/// guaranteed to be freed.
+///
+/// It makes no sense to copy an object of this class. After the copy, both
+/// objects would contain pointers to the same MySql context object. The
+/// destruction of one would invalid the context in the remaining object.
+/// For this reason, the class is declared noncopyable.
+class MySqlHolder : public boost::noncopyable {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initialize MySql and store the associated context object.
+ ///
+ /// @throw DbOpenError Unable to initialize MySql handle.
+ MySqlHolder() : mysql_(mysql_init(NULL)) {
+ if (mysql_ == NULL) {
+ isc_throw(db::DbOpenError, "unable to initialize MySQL");
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// Frees up resources allocated by the initialization of MySql.
+ ~MySqlHolder() {
+ if (mysql_ != NULL) {
+ mysql_close(mysql_);
+ }
+ }
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the MySqlHolder object to be passed as the context argument to
+ /// mysql_xxx functions.
+ operator MYSQL*() const {
+ return (mysql_);
+ }
+
+private:
+ /// @brief Variable used for its static property to call atexit() once.
+ static int atexit_;
+
+ /// @brief Initialization context
+ MYSQL* mysql_;
+};
+
+/// @brief Forward declaration to @ref MySqlConnection.
+class MySqlConnection;
+
+/// @brief RAII object representing MySQL transaction.
+///
+/// An instance of this class should be created in a scope where multiple
+/// INSERT statements should be executed within a single transaction. The
+/// transaction is started when the constructor of this class is invoked.
+/// The transaction is ended when the @ref MySqlTransaction::commit is
+/// explicitly called or when the instance of this class is destroyed.
+/// The @ref MySqlTransaction::commit commits changes to the database
+/// and the changes remain in the database when the instance of the
+/// class is destroyed. If the class instance is destroyed before the
+/// @ref MySqlTransaction::commit is called, the transaction is rolled
+/// back. The rollback on destruction guarantees that partial data is
+/// not stored in the database when there is an error during any
+/// of the operations belonging to a transaction.
+///
+/// The default MySQL backend configuration enables 'autocommit'.
+/// Starting a transaction overrides 'autocommit' setting for this
+/// particular transaction only. It does not affect the global 'autocommit'
+/// setting for the database connection, i.e. all modifications to the
+/// database which don't use transactions will still be auto committed.
+class MySqlTransaction : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts transaction by making a "START TRANSACTION" query.
+ ///
+ /// @param conn MySQL connection to use for the transaction. This
+ /// connection will be later used to commit or rollback changes.
+ ///
+ /// @throw DbOperationError if "START TRANSACTION" query fails.
+ MySqlTransaction(MySqlConnection& conn);
+
+ /// @brief Destructor.
+ ///
+ /// Rolls back the transaction if changes haven't been committed.
+ ~MySqlTransaction();
+
+ /// @brief Commits transaction.
+ void commit();
+
+private:
+
+ /// @brief Holds reference to the MySQL database connection.
+ MySqlConnection& conn_;
+
+ /// @brief Boolean flag indicating if the transaction has been committed.
+ ///
+ /// This flag is used in the class destructor to assess if the
+ /// transaction should be rolled back.
+ bool committed_;
+};
+
+
+/// @brief Common MySQL Connector Pool
+///
+/// This class provides common operations for MySQL database connection
+/// used by both MySqlLeaseMgr and MySqlHostDataSource. It manages connecting
+/// to the database and preparing compiled statements. Its fields are
+/// public, because they are used (both set and retrieved) in classes
+/// that use instances of MySqlConnection.
+class MySqlConnection : public db::DatabaseConnection {
+public:
+
+ /// @brief Function invoked to process fetched row.
+ typedef std::function<void(MySqlBindingCollection&)> ConsumeResultFun;
+
+ /// @brief Constructor
+ ///
+ /// Initialize MySqlConnection object with parameters needed for connection.
+ ///
+ /// @param parameters Specify the connection details.
+ /// @param io_accessor The IOService accessor function.
+ /// @param callback The connection recovery callback.
+ MySqlConnection(const ParameterMap& parameters,
+ IOServiceAccessorPtr io_accessor = IOServiceAccessorPtr(),
+ DbCallback callback = DbCallback())
+ : DatabaseConnection(parameters, callback),
+ io_service_accessor_(io_accessor), io_service_(),
+ transaction_ref_count_(0), tls_(false) {
+ }
+
+ /// @brief Destructor
+ virtual ~MySqlConnection();
+
+ /// @brief Get the schema version.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ static std::pair<uint32_t, uint32_t>
+ getVersion(const ParameterMap& parameters);
+
+ /// @brief Prepare Single Statement
+ ///
+ /// Creates a prepared statement from the text given and adds it to the
+ /// statements_ vector at the given index.
+ ///
+ /// @param index Index into the statements_ vector into which the text
+ /// should be placed. The vector must be big enough for the index
+ /// to be valid, else an exception will be thrown.
+ /// @param text Text of the SQL statement to be prepared.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::InvalidParameter 'index' is not valid for the vector.
+ void prepareStatement(uint32_t index, const char* text);
+
+ /// @brief Prepare statements
+ ///
+ /// Creates the prepared statements for all of the SQL statements used
+ /// by the MySQL backend.
+ ///
+ /// @param start_statement Pointer to the first statement in range of the
+ /// statements to be compiled.
+ /// @param end_statement Pointer to the statement marking end of the
+ /// range of statements to be compiled. This last statement is not compiled.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::InvalidParameter 'index' is not valid for the vector. This
+ /// represents an internal error within the code.
+ void prepareStatements(const TaggedStatement* start_statement,
+ const TaggedStatement* end_statement);
+
+ /// @brief Clears prepared statements and text statements.
+ void clearStatements();
+
+ /// @brief Open Database
+ ///
+ /// Opens the database using the information supplied in the parameters
+ /// passed to the constructor.
+ ///
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbOpenError Error opening the database
+ void openDatabase();
+
+ ///@{
+ /// The following methods are used to convert between times and time
+ /// intervals stored in the Lease object, and the times stored in the
+ /// database. The reason for the difference is because in the DHCP server,
+ /// the cltt (Client Time Since Last Transmission) is the natural data; in
+ /// the lease file - which may be read by the user - it is the expiry time
+ /// of the lease.
+
+ /// @brief Convert time_t value to database time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @param output_time Reference to MYSQL_TIME object where converted time
+ /// will be put.
+ static
+ void convertToDatabaseTime(const time_t input_time, MYSQL_TIME& output_time);
+
+ /// @brief Convert Lease Time to Database Times
+ ///
+ /// Within the DHCP servers, times are stored as client last transmit time
+ /// and valid lifetime. In the database, the information is stored as
+ /// valid lifetime and "expire" (time of expiry of the lease). They are
+ /// related by the equation:
+ ///
+ /// - expire = client last transmit time + valid lifetime
+ ///
+ /// This method converts from the times in the lease object into times
+ /// able to be added to the database.
+ ///
+ /// @param cltt Client last transmit time
+ /// @param valid_lifetime Valid lifetime
+ /// @param expire Reference to MYSQL_TIME object where the expiry time of
+ /// the lease will be put.
+ ///
+ /// @throw isc::BadValue if the sum of the calculated expiration time is
+ /// greater than the value of @c LeaseMgr::MAX_DB_TIME.
+ static
+ void convertToDatabaseTime(const time_t cltt, const uint32_t valid_lifetime,
+ MYSQL_TIME& expire);
+
+ /// @brief Convert Database Time to Lease Times
+ ///
+ /// Within the database, time is stored as "expire" (time of expiry of the
+ /// lease) and valid lifetime. In the DHCP server, the information is
+ /// stored client last transmit time and valid lifetime. These are related
+ /// by the equation:
+ ///
+ /// - client last transmit time = expire - valid_lifetime
+ ///
+ /// This method converts from the times in the database into times
+ /// able to be inserted into the lease object.
+ ///
+ /// @param expire Reference to MYSQL_TIME object from where the expiry
+ /// time of the lease is taken.
+ /// @param valid_lifetime lifetime of the lease.
+ /// @param cltt Reference to location where client last transmit time
+ /// is put.
+ static
+ void convertFromDatabaseTime(const MYSQL_TIME& expire,
+ uint32_t valid_lifetime, time_t& cltt);
+ ///@}
+
+ /// @brief Starts new transaction
+ ///
+ /// This function begins a new transaction by sending the START TRANSACTION
+ /// statement to the database. The transaction should be explicitly committed
+ /// by calling @c commit() or rolled back by calling @c rollback(). MySQL
+ /// does not support nested transactions, and it implicitly commits a
+ /// current transaction when the new transaction begins. Therefore, this
+ /// function checks if a transaction has already started and does not start
+ /// a new transaction. However, it increments a transaction reference counter
+ /// which is later decremented when @c commit() or @c rollback() is called.
+ /// When this mechanism is used properly, it guarantees that nested
+ /// transactions are not attempted, thus avoiding implicit (unexpected)
+ /// commits of the pending transaction.
+ void startTransaction();
+
+ /// @brief Checks if there is a transaction in progress.
+ ///
+ /// @return true if a transaction has been started, false otherwise.
+ bool isTransactionStarted() const;
+
+ /// @brief Executes SELECT query using prepared statement.
+ ///
+ /// The statement index must point to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement. The size of
+ /// the @c out_bindings must match the number of selected columns. The
+ /// output bindings must be created and must encapsulate values of
+ /// the appropriate type, e.g. string, uint32_t etc.
+ ///
+ /// This method executes prepared statement using provided bindings and
+ /// calls @c process_result function for each returned row. The
+ /// @c process_result function is implemented by the caller and should
+ /// gather and store each returned row in an external data structure prior
+ /// to returning because the values in the @c out_bindings will be
+ /// overwritten by the values of the next returned row when this function
+ /// is called again.
+ ///
+ /// @tparam StatementIndex Type of the statement index enum.
+ ///
+ /// @param index Index of the query to be executed.
+ /// @param in_bindings Input bindings holding values to substitue placeholders
+ /// in the query.
+ /// @param [out] out_bindings Output bindings where retrieved data will be
+ /// stored.
+ /// @param process_result Pointer to the function to be invoked for each
+ /// retrieved row. This function consumes the retrieved data from the
+ /// output bindings.
+ template<typename StatementIndex>
+ void selectQuery(const StatementIndex& index,
+ const MySqlBindingCollection& in_bindings,
+ MySqlBindingCollection& out_bindings,
+ ConsumeResultFun process_result) {
+ checkUnusable();
+ // Extract native input bindings.
+ std::vector<MYSQL_BIND> in_bind_vec;
+ for (MySqlBindingPtr in_binding : in_bindings) {
+ in_bind_vec.push_back(in_binding->getMySqlBinding());
+ }
+
+ int status = 0;
+ if (!in_bind_vec.empty()) {
+ // Bind parameters to the prepared statement.
+ status = mysql_stmt_bind_param(statements_[index],
+ in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
+ checkError(status, index, "unable to bind parameters for select");
+ }
+
+ // Bind variables that will receive results as well.
+ std::vector<MYSQL_BIND> out_bind_vec;
+ for (MySqlBindingPtr out_binding : out_bindings) {
+ out_bind_vec.push_back(out_binding->getMySqlBinding());
+ }
+ if (!out_bind_vec.empty()) {
+ status = mysql_stmt_bind_result(statements_[index], &out_bind_vec[0]);
+ checkError(status, index, "unable to bind result parameters for select");
+ }
+
+ // Execute query.
+ status = MysqlExecuteStatement(statements_[index]);
+ checkError(status, index, "unable to execute");
+
+ status = mysql_stmt_store_result(statements_[index]);
+ checkError(status, index, "unable to set up for storing all results");
+
+ // Fetch results.
+ MySqlFreeResult fetch_release(statements_[index]);
+ while ((status = mysql_stmt_fetch(statements_[index])) ==
+ MLM_MYSQL_FETCH_SUCCESS) {
+ try {
+ // For each returned row call user function which should
+ // consume the row and copy the data to a safe place.
+ process_result(out_bindings);
+
+ } catch (const std::exception& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ text_statements_[index] << ">");
+ }
+ }
+
+ // How did the fetch end?
+ // If mysql_stmt_fetch return value is equal to 1 an error occurred.
+ if (status == MLM_MYSQL_FETCH_FAILURE) {
+ // Error - unable to fetch results
+ checkError(status, index, "unable to fetch results");
+
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, text_statements_[index]
+ << " returned truncated data");
+ }
+ }
+
+ /// @brief Executes INSERT prepared statement.
+ ///
+ /// The statement index must point to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided bindings to
+ /// insert data into the database.
+ ///
+ /// @tparam StatementIndex Type of the statement index enum.
+ ///
+ /// @param index Index of the query to be executed.
+ /// @param in_bindings Input bindings holding values to substitue placeholders
+ /// in the query.
+ template<typename StatementIndex>
+ void insertQuery(const StatementIndex& index,
+ const MySqlBindingCollection& in_bindings) {
+ checkUnusable();
+ std::vector<MYSQL_BIND> in_bind_vec;
+ for (MySqlBindingPtr in_binding : in_bindings) {
+ in_bind_vec.push_back(in_binding->getMySqlBinding());
+ }
+
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[index],
+ in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
+ checkError(status, index, "unable to bind parameters");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(statements_[index]);
+
+ if (status != 0) {
+ // Failure: check for the special case of duplicate entry.
+ if (mysql_errno(mysql_) == ER_DUP_ENTRY) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+ // Failure: check for the special case of WHERE returning NULL.
+ if (mysql_errno(mysql_) == ER_BAD_NULL_ERROR) {
+ isc_throw(NullKeyError, "Database bad NULL error");
+ }
+ checkError(status, index, "unable to execute");
+ }
+ }
+
+ /// @brief Executes UPDATE or DELETE prepared statement and returns
+ /// the number of affected rows.
+ ///
+ /// The statement index must point to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// @tparam StatementIndex Type of the statement index enum.
+ ///
+ /// @param index Index of the query to be executed.
+ /// @param in_bindings Input bindings holding values to substitute placeholders
+ /// in the query.
+ ///
+ /// @return Number of affected rows.
+ template<typename StatementIndex>
+ uint64_t updateDeleteQuery(const StatementIndex& index,
+ const MySqlBindingCollection& in_bindings) {
+ checkUnusable();
+ std::vector<MYSQL_BIND> in_bind_vec;
+ for (MySqlBindingPtr in_binding : in_bindings) {
+ in_bind_vec.push_back(in_binding->getMySqlBinding());
+ }
+
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[index],
+ in_bind_vec.empty() ? 0 : &in_bind_vec[0]);
+ checkError(status, index, "unable to bind parameters");
+
+ // Execute the statement
+ status = MysqlExecuteStatement(statements_[index]);
+
+ if (status != 0) {
+ // Failure: check for the special case of duplicate entry.
+ if ((mysql_errno(mysql_) == ER_DUP_ENTRY)
+#ifdef ER_FOREIGN_DUPLICATE_KEY
+ || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY)
+#endif
+#ifdef ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO
+ || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO)
+#endif
+#ifdef ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO
+ || (mysql_errno(mysql_) == ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO)
+#endif
+ ) {
+ isc_throw(DuplicateEntry, "Database duplicate entry error");
+ }
+ checkError(status, index, "unable to execute");
+ }
+
+ // Let's return how many rows were affected.
+ return (static_cast<uint64_t>(mysql_stmt_affected_rows(statements_[index])));
+ }
+
+ /// @brief Commits current transaction
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the commit failed.
+ void commit();
+
+ /// @brief Rollbacks current transaction
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the rollback failed.
+ void rollback();
+
+ /// @brief Check Error and Throw Exception
+ ///
+ /// Virtually all MySQL functions return a status which, if non-zero,
+ /// indicates an error. This function centralizes the error checking
+ /// code.
+ ///
+ /// It is used to determine whether or not the function succeeded, and
+ /// in the event of failures, decide whether or not those failures are
+ /// recoverable.
+ ///
+ /// If the error is recoverable, the function will throw a DbOperationError.
+ /// If the error is deemed unrecoverable, such as a loss of connectivity
+ /// with the server, the function will call startRecoverDbConnection() which
+ /// will start the connection recovery.
+ ///
+ /// If the invocation returns true, this indicates the calling layer will
+ /// attempt recovery, and the function throws a DbOperationError to allow
+ /// the caller to error handle the failed db access attempt.
+ ///
+ /// @param status Status code: non-zero implies an error
+ /// @param index Index of statement that caused the error
+ /// @param what High-level description of the error
+ ///
+ /// @tparam Enumeration representing index of a statement to which an
+ /// error pertains.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ template<typename StatementIndex>
+ void checkError(const int status, const StatementIndex& index,
+ const char* what) {
+ if (status != 0) {
+ switch(mysql_errno(mysql_)) {
+ // These are the ones we consider fatal. Remember this method is
+ // used to check errors of API calls made subsequent to successfully
+ // connecting. Errors occurring while attempting to connect are
+ // checked in the connection code. An alternative would be to call
+ // mysql_ping() - assuming autoreconnect is off. If that fails
+ // then we know connection is toast.
+ case CR_SERVER_GONE_ERROR:
+ case CR_SERVER_LOST:
+ case CR_OUT_OF_MEMORY:
+ case CR_CONNECTION_ERROR: {
+ DB_LOG_ERROR(db::MYSQL_FATAL_ERROR)
+ .arg(what)
+ .arg(text_statements_[static_cast<int>(index)])
+ .arg(mysql_error(mysql_))
+ .arg(mysql_errno(mysql_));
+
+ // Mark this connection as no longer usable.
+ markUnusable();
+
+ // Start the connection recovery.
+ startRecoverDbConnection();
+
+ // We still need to throw so caller can error out of the current
+ // processing.
+ isc_throw(db::DbConnectionUnusable,
+ "fatal database error or connectivity lost");
+ }
+ default:
+ // Connection is ok, so it must be an SQL error
+ isc_throw(db::DbOperationError, what << " for <"
+ << text_statements_[static_cast<int>(index)]
+ << ">, reason: "
+ << mysql_error(mysql_) << " (error code "
+ << mysql_errno(mysql_) << ")");
+ }
+ }
+ }
+
+ /// @brief The recover connection
+ ///
+ /// This function starts the recover process of the connection.
+ ///
+ /// @note The recover function must be run on the IO Service thread.
+ void startRecoverDbConnection() {
+ if (callback_) {
+ if (!io_service_ && io_service_accessor_) {
+ io_service_ = (*io_service_accessor_)();
+ io_service_accessor_.reset();
+ }
+
+ if (io_service_) {
+ io_service_->post(std::bind(callback_, reconnectCtl()));
+ }
+ }
+ }
+
+ /// @brief Get the TLS flag.
+ ///
+ /// @return True if TLS was required, false otherwise.
+ bool getTls() const {
+ return (tls_);
+ }
+
+ /// @brief Get the TLS cipher.
+ ///
+ /// This method is used to check if required TLS was setup.
+ std::string getTlsCipher() {
+ const char* cipher = mysql_get_ssl_cipher(mysql_);
+ return (cipher ? std::string(cipher) : "");
+ }
+
+private:
+
+ /// @brief Convenience function parsing and setting an integer parameter,
+ /// if it exists.
+ ///
+ /// If the parameter is not present, this function doesn't change the @c value.
+ /// Otherwise, it tries to convert the parameter to the type @c T. Finally,
+ /// it checks if the converted number is within the specified range.
+ ///
+ /// @param name Parameter name.
+ /// @param min Expected minimal value.
+ /// @param max Expected maximal value.
+ /// @param [out] value Reference to a value returning the parsed parameter.
+ /// @tparam T Parameter type.
+ /// @throw BadValue if the parameter is not a valid number or if it is out
+ /// of range.
+ template<typename T>
+ void setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value);
+
+public:
+
+ /// @brief Prepared statements
+ ///
+ /// This field is public, because it is used heavily from MySqlConnection
+ /// and will be from MySqlHostDataSource.
+ std::vector<MYSQL_STMT*> statements_;
+
+ /// @brief Raw text of statements
+ ///
+ /// This field is public, because it is used heavily from MySqlConnection
+ /// and will be from MySqlHostDataSource.
+ std::vector<std::string> text_statements_;
+
+ /// @brief MySQL connection handle
+ ///
+ /// This field is public, because it is used heavily from MySqlConnection
+ /// and will be from MySqlHostDataSource.
+ MySqlHolder mysql_;
+
+ /// @brief Accessor function which returns the IOService that can be used to
+ /// recover the connection.
+ ///
+ /// This accessor is used to lazy retrieve the IOService when the connection
+ /// is lost. It is useful to retrieve it at a later time to support hook
+ /// libraries which create managers on load and set IOService later on by
+ /// using the dhcp4_srv_configured and dhcp6_srv_configured hooks.
+ IOServiceAccessorPtr io_service_accessor_;
+
+ /// @brief IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+
+ /// @brief Reference counter for transactions.
+ ///
+ /// It precludes starting and committing nested transactions. MySQL
+ /// implicitly commits current transaction when new transaction is
+ /// started. We want to not start new transactions when one is already
+ /// in progress.
+ int transaction_ref_count_;
+
+ /// @brief TLS flag (true when TLS was required, false otherwise).
+ bool tls_;
+};
+
+} // end of isc::db namespace
+} // end of isc namespace
+
+#endif // MYSQL_CONNECTION_H
diff --git a/src/lib/mysql/mysql_constants.h b/src/lib/mysql/mysql_constants.h
new file mode 100644
index 0000000..0f2cc1c
--- /dev/null
+++ b/src/lib/mysql/mysql_constants.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2018-2023 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 MYSQL_CONSTANTS_H
+#define MYSQL_CONSTANTS_H
+
+#include <mysql.h>
+
+namespace isc {
+namespace db {
+
+/// @name MySQL constants.
+///
+//@{
+
+/// @brief my_bools type for vectors.
+/// @note vector<bool> is specialized into a bitset, so vector<char>
+/// must be used instead
+typedef char my_bools;
+
+#ifdef HAVE_MYSQL_MY_BOOL
+/// @brief MySQL false value.
+const my_bool MLM_FALSE = 0;
+
+/// @brief MySQL true value.
+const my_bool MLM_TRUE = 1;
+
+#else
+/// @brief my_bool type in MySQL 8.x.
+typedef bool my_bool;
+
+/// @brief MySQL false value.
+const my_bool MLM_FALSE = false;
+
+/// @brief MySQL true value.
+const my_bool MLM_TRUE = true;
+#endif
+
+///@brief check for bool size
+static_assert(sizeof(my_bool) == sizeof(char), "unsupported bool size");
+
+/// @brief MySQL fetch success code.
+const int MLM_MYSQL_FETCH_SUCCESS = 0;
+
+/// @brief MySQL fetch failure code.
+const int MLM_MYSQL_FETCH_FAILURE = 0;
+
+//@}
+
+/// @name Current database schema version values.
+//@{
+const uint32_t MYSQL_SCHEMA_VERSION_MAJOR = 19;
+const uint32_t MYSQL_SCHEMA_VERSION_MINOR = 0;
+
+//@}
+
+
+} // end of namespace isc::db
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/mysql/tests/Makefile.am b/src/lib/mysql/tests/Makefile.am
new file mode 100644
index 0000000..b0945f2
--- /dev/null
+++ b/src/lib/mysql/tests/Makefile.am
@@ -0,0 +1,39 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MYSQL_CPPFLAGS)
+
+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 += libmysql_unittests
+
+libmysql_unittests_SOURCES = mysql_binding_unittest.cc
+libmysql_unittests_SOURCES += mysql_connection_unittest.cc
+libmysql_unittests_SOURCES += run_unittests.cc
+
+libmysql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libmysql_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(MYSQL_LIBS)
+
+libmysql_unittests_LDADD = $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/mysql/libkea-mysql.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libmysql_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libmysql_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/mysql/tests/Makefile.in b/src/lib/mysql/tests/Makefile.in
new file mode 100644
index 0000000..b4cb993
--- /dev/null
+++ b/src/lib/mysql/tests/Makefile.in
@@ -0,0 +1,1028 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libmysql_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/mysql/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libmysql_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libmysql_unittests_SOURCES_DIST = mysql_binding_unittest.cc \
+ mysql_connection_unittest.cc run_unittests.cc
+@HAVE_GTEST_TRUE@am_libmysql_unittests_OBJECTS = libmysql_unittests-mysql_binding_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libmysql_unittests-mysql_connection_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libmysql_unittests-run_unittests.$(OBJEXT)
+libmysql_unittests_OBJECTS = $(am_libmysql_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libmysql_unittests_DEPENDENCIES = $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/mysql/libkea-mysql.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(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 =
+libmysql_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libmysql_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)/libmysql_unittests-mysql_binding_unittest.Po \
+ ./$(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Po \
+ ./$(DEPDIR)/libmysql_unittests-run_unittests.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 =
+SOURCES = $(libmysql_unittests_SOURCES)
+DIST_SOURCES = $(am__libmysql_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(MYSQL_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libmysql_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ mysql_binding_unittest.cc \
+@HAVE_GTEST_TRUE@ mysql_connection_unittest.cc run_unittests.cc
+@HAVE_GTEST_TRUE@libmysql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libmysql_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@libmysql_unittests_LDADD = $(top_builddir)/src/lib/mysql/testutils/libmysqltest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/mysql/libkea-mysql.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(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/mysql/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/mysql/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
+
+libmysql_unittests$(EXEEXT): $(libmysql_unittests_OBJECTS) $(libmysql_unittests_DEPENDENCIES) $(EXTRA_libmysql_unittests_DEPENDENCIES)
+ @rm -f libmysql_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libmysql_unittests_LINK) $(libmysql_unittests_OBJECTS) $(libmysql_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmysql_unittests-mysql_binding_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmysql_unittests-run_unittests.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 $@ $<
+
+libmysql_unittests-mysql_binding_unittest.o: mysql_binding_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libmysql_unittests-mysql_binding_unittest.o -MD -MP -MF $(DEPDIR)/libmysql_unittests-mysql_binding_unittest.Tpo -c -o libmysql_unittests-mysql_binding_unittest.o `test -f 'mysql_binding_unittest.cc' || echo '$(srcdir)/'`mysql_binding_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmysql_unittests-mysql_binding_unittest.Tpo $(DEPDIR)/libmysql_unittests-mysql_binding_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_binding_unittest.cc' object='libmysql_unittests-mysql_binding_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) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libmysql_unittests-mysql_binding_unittest.o `test -f 'mysql_binding_unittest.cc' || echo '$(srcdir)/'`mysql_binding_unittest.cc
+
+libmysql_unittests-mysql_binding_unittest.obj: mysql_binding_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libmysql_unittests-mysql_binding_unittest.obj -MD -MP -MF $(DEPDIR)/libmysql_unittests-mysql_binding_unittest.Tpo -c -o libmysql_unittests-mysql_binding_unittest.obj `if test -f 'mysql_binding_unittest.cc'; then $(CYGPATH_W) 'mysql_binding_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_binding_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmysql_unittests-mysql_binding_unittest.Tpo $(DEPDIR)/libmysql_unittests-mysql_binding_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_binding_unittest.cc' object='libmysql_unittests-mysql_binding_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) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libmysql_unittests-mysql_binding_unittest.obj `if test -f 'mysql_binding_unittest.cc'; then $(CYGPATH_W) 'mysql_binding_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_binding_unittest.cc'; fi`
+
+libmysql_unittests-mysql_connection_unittest.o: mysql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libmysql_unittests-mysql_connection_unittest.o -MD -MP -MF $(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Tpo -c -o libmysql_unittests-mysql_connection_unittest.o `test -f 'mysql_connection_unittest.cc' || echo '$(srcdir)/'`mysql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Tpo $(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_connection_unittest.cc' object='libmysql_unittests-mysql_connection_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) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libmysql_unittests-mysql_connection_unittest.o `test -f 'mysql_connection_unittest.cc' || echo '$(srcdir)/'`mysql_connection_unittest.cc
+
+libmysql_unittests-mysql_connection_unittest.obj: mysql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libmysql_unittests-mysql_connection_unittest.obj -MD -MP -MF $(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Tpo -c -o libmysql_unittests-mysql_connection_unittest.obj `if test -f 'mysql_connection_unittest.cc'; then $(CYGPATH_W) 'mysql_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_connection_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Tpo $(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_connection_unittest.cc' object='libmysql_unittests-mysql_connection_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) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libmysql_unittests-mysql_connection_unittest.obj `if test -f 'mysql_connection_unittest.cc'; then $(CYGPATH_W) 'mysql_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/mysql_connection_unittest.cc'; fi`
+
+libmysql_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libmysql_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libmysql_unittests-run_unittests.Tpo -c -o libmysql_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmysql_unittests-run_unittests.Tpo $(DEPDIR)/libmysql_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libmysql_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) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libmysql_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libmysql_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libmysql_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libmysql_unittests-run_unittests.Tpo -c -o libmysql_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)/libmysql_unittests-run_unittests.Tpo $(DEPDIR)/libmysql_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libmysql_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) $(libmysql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libmysql_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)/libmysql_unittests-mysql_binding_unittest.Po
+ -rm -f ./$(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libmysql_unittests-run_unittests.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)/libmysql_unittests-mysql_binding_unittest.Po
+ -rm -f ./$(DEPDIR)/libmysql_unittests-mysql_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libmysql_unittests-run_unittests.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/mysql/tests/mysql_binding_unittest.cc b/src/lib/mysql/tests/mysql_binding_unittest.cc
new file mode 100644
index 0000000..331dfa8
--- /dev/null
+++ b/src/lib/mysql/tests/mysql_binding_unittest.cc
@@ -0,0 +1,249 @@
+// Copyright (C) 2018-2019 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 <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <mysql/mysql_binding.h>
+#include <util/optional.h>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::util;
+
+namespace {
+
+// This test verifies that default string is returned if binding is null.
+TEST(MySqlBindingTest, defaultString) {
+ auto binding = MySqlBinding::createNull();
+ EXPECT_EQ("foo", binding->getStringOrDefault("foo"));
+
+ binding = MySqlBinding::createString("bar");
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ("bar", binding->getStringOrDefault("foo"));
+}
+
+// This test verifies that null binding is created for unspecified string
+// and the string binding is created for a specified string.
+TEST(MySqlBindingTest, conditionalString) {
+ auto binding = MySqlBinding::condCreateString(Optional<std::string>());
+ EXPECT_TRUE(binding->amNull());
+
+ binding = MySqlBinding::condCreateString("foo");
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ("foo", binding->getString());
+}
+
+// This test verifies that empty string is stored in the database.
+TEST(MySqlBindingTest, emptyString) {
+ auto binding = MySqlBinding::condCreateString(Optional<std::string>(""));
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_TRUE(binding->getString().empty());
+}
+
+// This test verifies that an error is thrown upon an attempt to use
+// invalid accessor for a string binding.
+TEST(MySqlBindingTest, stringTypeMismatch) {
+ auto binding = MySqlBinding::createString("foo");
+ EXPECT_NO_THROW(static_cast<void>(binding->getString()));
+
+ EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
+}
+
+// This test verifies that null JSON is returned if the string binding
+// is null, JSON value is returned when string value is valid JSON and
+// that exception is thrown if the string is not a valid JSON.
+TEST(MySqlBindingTest, getJSON) {
+ auto binding = MySqlBinding::createNull();
+ EXPECT_FALSE(binding->getJSON());
+
+ binding = MySqlBinding::createString("{ \"foo\": \"bar\" }");
+ auto json = binding->getJSON();
+ ASSERT_TRUE(json);
+ ASSERT_EQ(Element::map, json->getType());
+ auto foo = json->get("foo");
+ ASSERT_TRUE(foo);
+ ASSERT_EQ(Element::string, foo->getType());
+ EXPECT_EQ("bar", foo->stringValue());
+}
+
+// This test verifies that default blob is returned if binding is null.
+TEST(MySqlBindingTest, defaultBlob) {
+ std::vector<uint8_t> blob(10, 1);
+ std::vector<uint8_t> default_blob(10, 5);
+ auto binding = MySqlBinding::createNull();
+ EXPECT_EQ(default_blob, binding->getBlobOrDefault(default_blob));
+
+ binding = MySqlBinding::createBlob(blob.begin(), blob.end());
+ EXPECT_EQ(blob, binding->getBlobOrDefault(default_blob));
+}
+
+// This test verifies that an error is thrown upon an attempt to use
+// invalid accessor for a blob binding.
+TEST(MySqlBindingTest, blobTypeMismatch) {
+ std::vector<uint8_t> blob(10, 1);
+ auto binding = MySqlBinding::createBlob(blob.begin(), blob.end());
+ EXPECT_NO_THROW(static_cast<void>(binding->getBlob()));
+
+ EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
+}
+
+// This test verifies that default number is returned if binding is null.
+TEST(MySqlBindingTest, defaultInteger) {
+ auto binding = MySqlBinding::createNull();
+ ASSERT_TRUE(binding->amNull());
+ EXPECT_EQ(123, binding->getIntegerOrDefault<uint32_t>(123));
+
+ binding = MySqlBinding::createInteger<uint32_t>(1024);
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ(1024, binding->getIntegerOrDefault<uint32_t>(123));
+}
+
+// This test verifies that null binding is created for unspecified number
+// and the integer binding is created for a specified number.
+TEST(MySqlBindingTest, conditionalInteger) {
+ auto binding = MySqlBinding::condCreateInteger<uint16_t>(Optional<uint16_t>());
+ EXPECT_TRUE(binding->amNull());
+
+ binding = MySqlBinding::condCreateInteger<uint16_t>(1);
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ(1, binding->getInteger<uint16_t>());
+}
+
+// This test verifies that an error is thrown upon an attempt to use
+// invalid accessor for an integer binding.
+TEST(MySqlBindingTest, integerTypeMismatch) {
+ auto binding = MySqlBinding::createInteger<uint32_t>(123);
+ EXPECT_NO_THROW(static_cast<void>(binding->getInteger<uint32_t>()));
+
+ EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint8_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
+}
+
+// This test verifies that null binding is created for unspecified floating
+// point value and the float binding is created for the specified value.
+TEST(MySqlBindingTest, conditionalFloat) {
+ auto binding = MySqlBinding::condCreateFloat(Optional<float>());
+ EXPECT_TRUE(binding->amNull());
+
+ binding = MySqlBinding::condCreateFloat<float>(1.567f);
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ(1.567f, binding->getFloat());
+}
+
+// This test verifies that an error is thrown upon an attempt to use
+// invalid accessor for a float binding.
+TEST(MySqlBindingTest, floatTypeMismatch) {
+ auto binding = MySqlBinding::createFloat(123.123f);
+ EXPECT_NO_THROW(static_cast<void>(binding->getFloat()));
+
+ EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint8_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint32_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getBool()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
+}
+
+// This test verifies that null binding is created for unspecified boolean
+// value and the uint8_t binding is created for a specified boolean
+// value.
+TEST(MySqlBindingTest, conditionalBoolean) {
+ auto binding = MySqlBinding::condCreateBool(Optional<bool>());
+ EXPECT_TRUE(binding->amNull());
+
+ binding = MySqlBinding::condCreateBool(false);
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_FALSE(binding->getBool());
+
+ binding = MySqlBinding::condCreateBool(true);
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_TRUE(binding->getBool());
+}
+
+// This test verifies that an error is thrown upon an attempt to use
+// invalid accessor for a float binding.
+TEST(MySqlBindingTest, booleanTypeMismatch) {
+ auto binding = MySqlBinding::createBool(false);
+ EXPECT_NO_THROW(static_cast<void>(binding->getBool()));
+ EXPECT_NO_THROW(static_cast<void>(binding->getInteger<uint8_t>()));
+
+ EXPECT_THROW(static_cast<void>(binding->getString()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getBlob()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint16_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getInteger<uint32_t>()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getFloat()), InvalidOperation);
+ EXPECT_THROW(static_cast<void>(binding->getTimestamp()), InvalidOperation);
+}
+
+// This test verifies that null binding is created for unspecified address
+// and the uint32_t binding is created for the specified address.
+TEST(MySqlBindingTest, conditionalIPv4Address) {
+ auto binding = MySqlBinding::condCreateIPv4Address(Optional<IOAddress>());
+ EXPECT_TRUE(binding->amNull());
+
+ binding = MySqlBinding::condCreateIPv4Address(IOAddress("192.0.2.1"));
+ ASSERT_FALSE(binding->amNull());
+ EXPECT_EQ(0xC0000201, binding->getInteger<uint32_t>());
+
+ EXPECT_THROW(MySqlBinding::condCreateIPv4Address(IOAddress("2001:db8:1::1")),
+ isc::BadValue);
+}
+
+// This test verifies that default timestamp is returned if binding is null.
+TEST(MySqlBindingTest, defaultTimestamp) {
+ boost::posix_time::ptime current_time = boost::posix_time::second_clock::local_time();
+ boost::posix_time::ptime past_time = current_time - boost::posix_time::hours(1);
+
+ auto binding = MySqlBinding::createNull();
+ EXPECT_TRUE(past_time == binding->getTimestampOrDefault(past_time));
+
+ binding = MySqlBinding::createTimestamp(current_time);
+ EXPECT_TRUE(current_time == binding->getTimestampOrDefault(past_time));
+}
+
+// This test verifies that the binding preserves fractional seconds in
+// millisecond precision.
+/// @todo This test is disabled until we decide that the minimum
+/// supported MySQL version has a fractional seconds precision.
+TEST(MySqlBindingTest, DISABLED_millisecondTimestampPrecision) {
+ // Set timestamp of 2019-01-28 01:12:10.123
+
+ // Fractional part depends on the clock resolution.
+ long fractional = 123*(boost::posix_time::time_duration::ticks_per_second()/1000);
+ boost::posix_time::ptime
+ test_time(boost::gregorian::date(2019, boost::gregorian::Jan, 28),
+ boost::posix_time::time_duration(1, 12, 10, fractional));
+
+ auto binding = MySqlBinding::createTimestamp(test_time);
+
+ boost::posix_time::ptime returned_test_time = binding->getTimestamp();
+
+ EXPECT_EQ(returned_test_time, test_time);
+}
+
+}
+
diff --git a/src/lib/mysql/tests/mysql_connection_unittest.cc b/src/lib/mysql/tests/mysql_connection_unittest.cc
new file mode 100644
index 0000000..cc2b21d
--- /dev/null
+++ b/src/lib/mysql/tests/mysql_connection_unittest.cc
@@ -0,0 +1,921 @@
+// Copyright (C) 2018-2023 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 <mysql/mysql_connection.h>
+#include <mysql/testutils/mysql_schema.h>
+#include <testutils/gtest_utils.h>
+
+#include <array>
+
+#include <gtest/gtest.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+using namespace isc::db;
+using namespace isc::db::test;
+
+namespace {
+
+/// @brief RAII wrapper over MYSQL_RES obtained from MySQL library functions like
+/// mysql_use_result().
+struct MySqlResult {
+ MySqlResult(MYSQL_RES* result) : result_(result) {
+ }
+
+ ~MySqlResult() {
+ mysql_free_result(result_);
+ }
+
+ MYSQL_RES* const result_;
+};
+
+/// @brief Test fixture class for @c MySqlConnection class.
+class MySqlConnectionTest : public ::testing::Test {
+public:
+
+ /// @brief Indexes of prepared statements used within the tests.
+ enum StatementIndex {
+ GET_BY_INT_VALUE,
+ DELETE_BY_INT_VALUE,
+ INSERT_VALUE,
+ NUM_STATEMENTS
+ };
+
+ /// @brief Array of tagged MySQL statements.
+ typedef std::array<TaggedStatement, NUM_STATEMENTS> TaggedStatementArray;
+
+ /// @brief Prepared MySQL statements used in the tests.
+ TaggedStatementArray tagged_statements = {{
+ { GET_BY_INT_VALUE,
+ "SELECT tinyint_value, int_value, bigint_value, string_value,"
+ " blob_value, timestamp_value"
+ " FROM mysql_connection_test WHERE int_value = ?" },
+
+ { DELETE_BY_INT_VALUE,
+ "DELETE FROM mysql_connection_test WHERE int_value = ?" },
+
+ { INSERT_VALUE,
+ "INSERT INTO mysql_connection_test (tinyint_value, int_value,"
+ "bigint_value, string_value, blob_value, timestamp_value)"
+ " VALUES (?, ?, ?, ?, ?, ?)" }
+ }};
+
+ /// @brief Constructor.
+ ///
+ /// Re-creates database schema, opens new database connection and creates
+ /// prepared statements used in tests. Created schema contains a test
+ /// table @c mysql_connection_test which includes 6 columns of various
+ /// types.
+ MySqlConnectionTest(bool const primary_key = false)
+ : conn_(DatabaseConnection::parse(validMySQLConnectionString())) {
+
+ try {
+ // Open new connection.
+ conn_.openDatabase();
+
+ // Create mysql_connection_test table.
+ createTestTable(primary_key);
+
+ // In Percona XtraDB cluster, you can't do much on tables with
+ // primary keys. So far the connection and the table creation have
+ // been tested. Continue only if:
+ // * we are in primary key mode
+ // * a MySQL database other than Percona is running
+ // * Percona's pxc_strict_mode is set to "DISABLED" or "PERMISSIVE"
+ // The last two checks are done with inverse logic against the two
+ // modes that restrict this: "ENFORCING" and "MASTER". This check is
+ // to be paired inside the tests without a primary key to disable
+ // those tests.
+ if (!primary_key &&
+ (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER")) {
+ return;
+ }
+
+ // Created prepared statements for basic queries to test table.
+ conn_.prepareStatements(tagged_statements.begin(),
+ tagged_statements.end());
+ } catch (...) {
+ std::cerr << "*** ERROR: unable to open database. The test\n"
+ "*** environment is broken and must be fixed before\n"
+ "*** the MySQL tests will run correctly.\n"
+ "*** The reason for the problem is described in the\n"
+ "*** accompanying exception output.\n";
+ throw;
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// Removes test table from the database.
+ virtual ~MySqlConnectionTest() {
+ if (conn_.isTransactionStarted()) {
+ conn_.rollback();
+ }
+ dropTestTable();
+ }
+
+ /// @brief Creates test table @c mysql_connection_test.
+ ///
+ /// The new table contains 6 columns of various data types. All of
+ /// the columns accept null values.
+ void createTestTable(bool const primary_key = false) {
+ /// @todo TIMESTAMP value lacks sub second precision because
+ /// it is supported since MySQL 5.6.4, which is still not a
+ /// default version on some OSes. When the subsecond precision
+ /// is available on all OSes that Kea supports, the timestamp
+ /// column should be turned to TIMESTAMP(6). Until then, it
+ /// must remain TIMESTAMP.
+ runQuery("CREATE TABLE IF NOT EXISTS mysql_connection_test ("
+ "tinyint_value TINYINT " +
+ std::string(primary_key ? "PRIMARY KEY NOT NULL," : "NULL,") +
+ "int_value INT NULL,"
+ "bigint_value BIGINT NULL,"
+ "string_value TEXT NULL,"
+ "blob_value BLOB NULL,"
+ "timestamp_value TIMESTAMP NULL"
+ ")");
+ }
+
+ /// @brief Drops test table.
+ void dropTestTable() {
+ runQuery("DROP TABLE IF EXISTS mysql_connection_test");
+ }
+
+ /// @brief Runs MySQL query on the opened connection.
+ ///
+ /// @param sql Query in the textual form.
+ void runQuery(const std::string& sql) {
+ MYSQL_STMT *stmt = mysql_stmt_init(conn_.mysql_);
+ if (stmt == NULL) {
+ isc_throw(DbOperationError, "unable to allocate MySQL prepared "
+ "statement structure, reason: " << mysql_error(conn_.mysql_));
+ }
+
+ int status = mysql_stmt_prepare(stmt, sql.c_str(), sql.length());
+ if (status != 0) {
+ isc_throw(DbOperationError, "unable to prepare MySQL statement <"
+ << sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ }
+
+ // Execute the prepared statement.
+ if (MysqlExecuteStatement(stmt) != 0) {
+ isc_throw(DbOperationError, "cannot execute MySQL query <"
+ << sql << ">, reason: " << mysql_errno(conn_.mysql_));
+ }
+
+ // Discard the statement and its resources
+ mysql_stmt_close(stmt);
+ }
+
+ /// @brief Tests inserting and retrieving data from the database.
+ ///
+ /// In this test data carried in the bindings is inserted into the database.
+ /// Then this data is retrieved from the database and compared with the
+ /// orginal.
+ ///
+ /// @param in_bindings Collection of bindings encapsulating the data to
+ /// be inserted into the database and then retrieved.
+ void testInsertSelect(const MySqlBindingCollection& in_bindings) {
+ // Expecting 6 bindings because we have 6 columns in our table.
+ ASSERT_EQ(6, in_bindings.size());
+ // We are going to select by int_value so this value must not be null.
+ ASSERT_FALSE(in_bindings[1]->amNull());
+
+ // Store data in the database.
+ ASSERT_NO_THROW_LOG(conn_.insertQuery(MySqlConnectionTest::INSERT_VALUE,
+ in_bindings));
+
+ // Create input binding for select query.
+ MySqlBindingCollection bindings =
+ { MySqlBinding::createInteger<uint32_t>(in_bindings[1]->getInteger<uint32_t>()) };
+
+ // Also, create output (placeholder) bindings for receiving data.
+ MySqlBindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint8_t>(),
+ MySqlBinding::createInteger<uint32_t>(),
+ MySqlBinding::createInteger<int64_t>(),
+ MySqlBinding::createString(512),
+ MySqlBinding::createBlob(512),
+ MySqlBinding::createTimestamp()
+ };
+
+ // Execute select statement. We expect one row to be returned. For this
+ // returned row the lambda provided as 4th argument should be executed.
+ ASSERT_NO_THROW_LOG(conn_.selectQuery(MySqlConnectionTest::GET_BY_INT_VALUE,
+ bindings, out_bindings,
+ [&](MySqlBindingCollection& out_bindings) {
+
+ // Compare received data with input data assuming they are both non-null.
+
+ if (!out_bindings[0]->amNull() && !in_bindings[0]->amNull()) {
+ EXPECT_EQ(static_cast<int>(in_bindings[0]->getInteger<uint8_t>()),
+ static_cast<int>(out_bindings[0]->getInteger<uint8_t>()));
+ }
+
+ if (!out_bindings[1]->amNull() && !in_bindings[1]->amNull()) {
+ EXPECT_EQ(in_bindings[1]->getInteger<uint32_t>(),
+ out_bindings[1]->getInteger<uint32_t>());
+ }
+
+ if (!out_bindings[2]->amNull() && !in_bindings[2]->amNull()) {
+ EXPECT_EQ(in_bindings[2]->getInteger<int64_t>(),
+ out_bindings[2]->getInteger<int64_t>());
+ }
+
+ if (!out_bindings[3]->amNull() && !in_bindings[3]->amNull()) {
+ EXPECT_EQ(in_bindings[3]->getString(),
+ out_bindings[3]->getString());
+ }
+
+ if (!out_bindings[4]->amNull() && !in_bindings[4]->amNull()) {
+ EXPECT_EQ(in_bindings[4]->getBlob(),
+ out_bindings[4]->getBlob());
+ }
+
+ if (!out_bindings[5]->amNull() && !in_bindings[5]->amNull()) {
+ EXPECT_TRUE(in_bindings[5]->getTimestamp() ==
+ out_bindings[5]->getTimestamp());
+ }
+ }));
+
+ // Make sure that null values were returned for columns for which null
+ // was set.
+ ASSERT_EQ(in_bindings.size(), out_bindings.size());
+ for (auto i = 0; i < in_bindings.size(); ++i) {
+ EXPECT_EQ(in_bindings[i]->amNull(), out_bindings[i]->amNull())
+ << "null value test failed for binding #" << i;
+ }
+ }
+
+ /// @brief Run a raw, unprepared statement and return the result.
+ ///
+ /// This is useful when running statements that can't be parameterized with a
+ /// question mark in place of a bound variable e.g. "SHOW GLOBAL VARIABLES"
+ /// and thus cannot be prepared beforehand. All the results are string, the
+ /// output should be the same as that which one would see in a mysql command
+ /// line client.
+ ///
+ /// @param statement the statement in string form
+ /// @throw DbOperationError if the statement could not be run
+ /// @return the list of rows, each row consisting of a list of values for
+ /// each column
+ std::vector<std::vector<std::string>>
+ rawStatement(std::string const& statement) const {
+ // Execute a SQL statement.
+ if (mysql_query(conn_.mysql_, statement.c_str())) {
+ isc_throw(DbOperationError,
+ statement << ": " << mysql_error(conn_.mysql_));
+ }
+
+ // Get a result set.
+ MySqlResult result(mysql_use_result(conn_.mysql_));
+
+ // Fetch a result set.
+ std::vector<std::vector<std::string>> output;
+ size_t r(0);
+ MYSQL_ROW row;
+ size_t const column_count(mysql_num_fields(result.result_));
+ while ((row = mysql_fetch_row(result.result_)) != NULL) {
+ output.push_back(std::vector<std::string>());
+ for (size_t i = 0; i < column_count; ++i) {
+ output[r].push_back(row[i]);
+ }
+ ++r;
+ }
+
+ return output;
+ }
+
+ /// @brief Get pxc_strict_mode global variable from the database.
+ /// For Percona, they can be: DISABLED, PERMISSIVE, ENFORCING, MASTER.
+ std::string showPxcStrictMode() {
+ // Store in a static variable so this only runs once per gtest binary
+ // invocation.
+ static std::string const pxc_strict_mode([&]() -> std::string {
+ // Execute select statement. We expect one row to be returned. For
+ // this returned row the lambda provided as 4th argument should be
+ // executed.
+ std::vector<std::vector<std::string>> const result(
+ rawStatement("SHOW GLOBAL VARIABLES LIKE 'pxc_strict_mode'"));
+ if (result.size() < 1 || result[0].size() < 2) {
+ // Not Percona
+ return "";
+ }
+
+ return result[0][1];
+ }());
+ return pxc_strict_mode;
+ }
+
+ /// *** Test definitions start here. Tests are invoked ***
+ /// *** multiple times further below in different test fixtures. ***
+
+ /// @brief Test that non-null values of various types can be inserted and
+ /// retrieved from the dataabse.
+ void select() {
+ std::string blob = "myblob";
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ /// @todo Change it to microsec_clock once we transition to
+ /// subsecond precision.
+ MySqlBinding::createTimestamp(
+ boost::posix_time::second_clock::local_time()),
+ };
+
+ testInsertSelect(in_bindings);
+ }
+
+ /// @brief Test that null value can be inserted to a column having numeric
+ /// type and retrieved.
+ void selectNullInteger() {
+ std::string blob = "myblob";
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createNull(),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ /// @todo Change it to microsec_clock once we transition to
+ /// subsecond precision.
+ MySqlBinding::createTimestamp(
+ boost::posix_time::second_clock::local_time()),
+ };
+
+ testInsertSelect(in_bindings);
+ }
+
+ /// @brief Test that null value can be inserted to a column having string
+ /// type and retrieved.
+ void selectNullString() {
+ std::string blob = "myblob";
+
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createNull(),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ /// @todo Change it to microsec_clock once we transition to
+ /// subsecond precision.
+ MySqlBinding::createTimestamp(
+ boost::posix_time::second_clock::local_time()),
+ };
+
+ testInsertSelect(in_bindings);
+ }
+
+ /// @brief Test that null value can be inserted to a column having blob type
+ /// and retrieved.
+ void selectNullBlob() {
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createNull(),
+ /// @todo Change it to microsec_clock once we transition to
+ /// subsecond precision.
+ MySqlBinding::createTimestamp(
+ boost::posix_time::second_clock::local_time()),
+ };
+
+ testInsertSelect(in_bindings);
+ }
+
+ /// @brief Test that null value can be inserted to a column having timestamp
+ /// type and retrieved.
+ void selectNullTimestamp() {
+ std::string blob = "myblob";
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString("shellfish"),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ MySqlBinding::createNull(),
+ };
+
+ testInsertSelect(in_bindings);
+ }
+
+ /// @brief Test that empty string and empty blob can be inserted to a
+ /// database.
+ void selectEmptyStringBlob() {
+ std::string blob = "";
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createString(""),
+ MySqlBinding::createBlob(blob.begin(), blob.end()),
+ /// @todo Change it to microsec_clock once we transition to
+ /// subsecond precision.
+ MySqlBinding::createTimestamp(
+ boost::posix_time::second_clock::local_time()),
+ };
+
+ testInsertSelect(in_bindings);
+ }
+
+ /// @brief Test that a row can be deleted from the database.
+ void deleteByValue() {
+ // Insert a row with numeric values.
+ MySqlBindingCollection in_bindings = {
+ MySqlBinding::createInteger<uint8_t>(123),
+ MySqlBinding::createInteger<uint32_t>(1024),
+ MySqlBinding::createInteger<int64_t>(-4096),
+ MySqlBinding::createNull(),
+ MySqlBinding::createNull(),
+ MySqlBinding::createNull(),
+ };
+
+ ASSERT_NO_THROW_LOG(
+ conn_.insertQuery(MySqlConnectionTest::INSERT_VALUE, in_bindings));
+
+ // This variable will be checked to see if the row has been deleted
+ // from the database.
+ bool deleted = false;
+
+ // Execute delete query but use int_value of non existing row.
+ // The row should not be deleted.
+ in_bindings = {MySqlBinding::createInteger<uint32_t>(1)};
+ ASSERT_NO_THROW_LOG(
+ deleted = conn_.updateDeleteQuery(
+ MySqlConnectionTest::DELETE_BY_INT_VALUE, in_bindings));
+ ASSERT_FALSE(deleted);
+
+ // This time use the correct value.
+ in_bindings = {MySqlBinding::createInteger<uint32_t>(1024)};
+ ASSERT_NO_THROW_LOG(
+ deleted = conn_.updateDeleteQuery(
+ MySqlConnectionTest::DELETE_BY_INT_VALUE, in_bindings));
+ // The row should have been deleted.
+ ASSERT_TRUE(deleted);
+
+ // Let's confirm that it has been deleted by issuing a select query.
+ MySqlBindingCollection out_bindings = {
+ MySqlBinding::createInteger<uint8_t>(),
+ MySqlBinding::createInteger<uint32_t>(),
+ MySqlBinding::createInteger<int64_t>(),
+ MySqlBinding::createString(512),
+ MySqlBinding::createBlob(512),
+ MySqlBinding::createTimestamp(),
+ };
+
+ ASSERT_NO_THROW_LOG(conn_.selectQuery(MySqlConnectionTest::GET_BY_INT_VALUE,
+ in_bindings, out_bindings,
+ [&deleted](MySqlBindingCollection&) {
+ // This will be executed if the
+ // row is returned as a result of
+ // select query. We expect that
+ // this is not executed.
+ deleted = false;
+ }));
+ // Make sure that select query returned nothing.
+ EXPECT_TRUE(deleted);
+ }
+
+ /// @brief Test MySQL connection.
+ MySqlConnection conn_;
+};
+
+struct MySqlConnectionWithPrimaryKeyTest : MySqlConnectionTest {
+ MySqlConnectionWithPrimaryKeyTest()
+ : MySqlConnectionTest(/* primary_key = */ true) {
+ }
+};
+
+TEST_F(MySqlConnectionTest, select) {
+ if (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER") {
+ return;
+ }
+ select();
+}
+
+TEST_F(MySqlConnectionTest, selectNullInteger) {
+ if (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER") {
+ return;
+ }
+ selectNullInteger();
+}
+
+TEST_F(MySqlConnectionTest, selectNullString) {
+ if (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER") {
+ return;
+ }
+ selectNullString();
+}
+
+TEST_F(MySqlConnectionTest, selectNullBlob) {
+ if (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER") {
+ return;
+ }
+ selectNullBlob();
+}
+
+TEST_F(MySqlConnectionTest, selectNullTimestamp) {
+ if (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER") {
+ return;
+ }
+ selectNullTimestamp();
+}
+
+TEST_F(MySqlConnectionTest, selectEmptyStringBlob) {
+ if (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER") {
+ return;
+ }
+ selectEmptyStringBlob();
+}
+
+TEST_F(MySqlConnectionTest, deleteByValue) {
+ if (showPxcStrictMode() == "ENFORCING" || showPxcStrictMode() == "MASTER") {
+ return;
+ }
+ deleteByValue();
+}
+
+TEST_F(MySqlConnectionTest, transactions) {
+ // Two inserts within a transaction and successful commit.
+ conn_.startTransaction();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ runQuery("INSERT INTO mysql_connection_test (tinyint_value) VALUES (1)");
+ runQuery("INSERT INTO mysql_connection_test (tinyint_value) VALUES (2)");
+ conn_.commit();
+ EXPECT_FALSE(conn_.isTransactionStarted());
+ auto result = rawStatement("SELECT COUNT(*) FROM mysql_connection_test");
+ ASSERT_EQ(1, result.size());
+ ASSERT_EQ(1, result[0].size());
+ EXPECT_EQ("2", result[0][0]);
+
+ // Add third row but roll back the transaction. We should still have
+ // two rows in the table.
+ conn_.startTransaction();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ runQuery("INSERT INTO mysql_connection_test (tinyint_value) VALUES (5)");
+ conn_.rollback();
+ EXPECT_FALSE(conn_.isTransactionStarted());
+ ASSERT_EQ(1, result.size());
+ ASSERT_EQ(1, result[0].size());
+ EXPECT_EQ("2", result[0][0]);
+
+ // Nested transaction. The inner transaction should be ignored and the outer
+ // transaction rolled back. We should still have two rows in the database.
+ conn_.startTransaction();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ runQuery("INSERT INTO mysql_connection_test (tinyint_value) VALUES (3)");
+ conn_.startTransaction();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ runQuery("INSERT INTO mysql_connection_test (tinyint_value) VALUES (4)");
+ conn_.commit();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ conn_.rollback();
+ EXPECT_FALSE(conn_.isTransactionStarted());
+ result = rawStatement("SELECT COUNT(*) FROM mysql_connection_test");
+ ASSERT_EQ(1, result.size());
+ ASSERT_EQ(1, result[0].size());
+ EXPECT_EQ("2", result[0][0]);
+
+ // Nested transaction. The inner transaction is rolled back but this should
+ // be ignored because nested transactions are not supported. We should
+ // have two new rows.
+ conn_.startTransaction();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ runQuery("INSERT INTO mysql_connection_test (tinyint_value) VALUES (5)");
+ conn_.startTransaction();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ runQuery("INSERT INTO mysql_connection_test (tinyint_value) VALUES (6)");
+ conn_.rollback();
+ EXPECT_TRUE(conn_.isTransactionStarted());
+ conn_.commit();
+ EXPECT_FALSE(conn_.isTransactionStarted());
+ result = rawStatement("SELECT COUNT(*) FROM mysql_connection_test");
+ ASSERT_EQ(1, result.size());
+ ASSERT_EQ(1, result[0].size());
+ EXPECT_EQ("4", result[0][0]);
+
+ // Committing or rolling back a not started transaction is a coding error.
+ EXPECT_THROW(conn_.commit(), isc::Unexpected);
+ EXPECT_THROW(conn_.rollback(), isc::Unexpected);
+}
+
+// Tests that invalid port value causes an error.
+TEST_F(MySqlConnectionTest, portInvalid) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_PORT_1);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidPort);
+}
+
+// Tests that invalid timeout value type causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_TIMEOUT_1);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a negative connection timeout value causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid2) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_TIMEOUT_2);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a zero connection timeout value causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid3) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_TIMEOUT_3);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that an invalid read timeout causes an error.
+TEST_F(MySqlConnectionTest, readTimeoutInvalid) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_READ_TIMEOUT_1);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that an invalid write timeout causes an error.
+TEST_F(MySqlConnectionTest, writeTimeoutInvalid) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, INVALID_WRITE_TIMEOUT_1);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+#ifdef HAVE_MYSQL_GET_OPTION
+
+// Tests that valid connection timeout is accepted.
+TEST_F(MySqlConnectionTest, connectionTimeout) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_TIMEOUT);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout));
+ EXPECT_EQ(10, timeout);
+}
+
+// Tests that a valid read timeout is accepted.
+TEST_F(MySqlConnectionTest, readTimeout) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_READ_TIMEOUT);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_READ_TIMEOUT, &timeout));
+ EXPECT_EQ(11, timeout);
+}
+
+// Tests that a zero read timeout is accepted.
+TEST_F(MySqlConnectionTest, readTimeoutZero) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_READ_TIMEOUT_ZERO);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_READ_TIMEOUT, &timeout));
+ EXPECT_EQ(0, timeout);
+}
+
+// Tests that a valid write timeout is accepted.
+TEST_F(MySqlConnectionTest, writeTimeout) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_WRITE_TIMEOUT);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout));
+ EXPECT_EQ(12, timeout);
+}
+
+// Tests that a zero write timeout is accepted.
+TEST_F(MySqlConnectionTest, writeTimeoutZero) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD, VALID_WRITE_TIMEOUT_ZERO);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+ auto mysql = static_cast<MYSQL*>(conn.mysql_);
+ ASSERT_TRUE(mysql);
+ unsigned int timeout = 123;
+ EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout));
+ EXPECT_EQ(0, timeout);
+}
+
+#endif // HAVE_MYSQL_GET_OPTION
+
+TEST_F(MySqlConnectionWithPrimaryKeyTest, select) {
+ select();
+}
+
+TEST_F(MySqlConnectionWithPrimaryKeyTest, selectNullInteger) {
+ selectNullInteger();
+}
+
+TEST_F(MySqlConnectionWithPrimaryKeyTest, selectNullString) {
+ selectNullString();
+}
+
+TEST_F(MySqlConnectionWithPrimaryKeyTest, selectNullBlob) {
+ selectNullBlob();
+}
+
+TEST_F(MySqlConnectionWithPrimaryKeyTest, selectNullTimestamp) {
+ selectNullTimestamp();
+}
+
+TEST_F(MySqlConnectionWithPrimaryKeyTest, selectEmptyStringBlob) {
+ selectEmptyStringBlob();
+}
+
+TEST_F(MySqlConnectionWithPrimaryKeyTest, deleteByValue) {
+ deleteByValue();
+}
+
+/// @brief Test fixture class for @c MySqlConnection class methods.
+class MySqlSchemaTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ MySqlSchemaTest() {
+ // Ensure we have the proper schema.
+ createMySQLSchema();
+ }
+
+ /// @brief Destructor.
+ virtual ~MySqlSchemaTest() {
+ destroyMySQLSchema();
+ }
+};
+
+/// @brief Check that getVersion() returns the expected version.
+TEST_F(MySqlSchemaTest, checkVersion) {
+ // Check version
+ auto parameters = DatabaseConnection::parse(validMySQLConnectionString());
+ std::pair<uint32_t, uint32_t> version;
+ ASSERT_NO_THROW_LOG(version = MySqlConnection::getVersion(parameters));
+ EXPECT_EQ(MYSQL_SCHEMA_VERSION_MAJOR, version.first);
+ EXPECT_EQ(MYSQL_SCHEMA_VERSION_MINOR, version.second);
+}
+
+/// @brief Test fixture class for secure connection.
+class MySqlSecureConnectionTest : public ::testing::Test {
+public:
+
+ /// @brief Check if SSL/TLS support is available and configured.
+ bool hasMySQLTls() {
+ std::string tls = getMySQLTlsEnv();
+ if (tls.empty()) {
+ tls = getMySQLTlsServer();
+ }
+ return (tls == "YES");
+ }
+};
+
+/// @brief Check that we can get the MySQL support status.
+TEST_F(MySqlSecureConnectionTest, getMySQLTls) {
+ std::string env;
+ try {
+ env = getMySQLTlsEnv();
+ std::cout << "getMySQLTlsEnv returns '" << env << "'\n";
+ } catch (const isc::Exception& ex) {
+ std::cerr << "getMySQLTlsEnv fails with " << ex.what() << "\n";
+ }
+ if (!env.empty()) {
+ return;
+ }
+ try {
+ std::cout << "getMySQLTlsServer returns '" << getMySQLTlsServer() << "'\n";
+ } catch (const isc::Exception& ex) {
+ std::cerr << "getMySQLTlsServer fails with " << ex.what() << "\n";
+ }
+}
+
+/// @brief Check the enforced TCP connection.
+TEST_F(MySqlSecureConnectionTest, Tcp) {
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_USER,
+ VALID_PASSWORD);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+}
+
+/// @brief Check the SSL/TLS protected connection.
+TEST_F(MySqlSecureConnectionTest, Tls) {
+ if (!hasMySQLTls()) {
+ std::cout << "SSL/TLS support is not available or configured: "
+ << "skipping this test\n";
+ return;
+ }
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_SECURE_USER,
+ VALID_PASSWORD, 0, 0,
+ VALID_CERT, VALID_KEY, VALID_CA,
+ VALID_CIPHER);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ ASSERT_NO_THROW_LOG(conn.openDatabase());
+ EXPECT_TRUE(conn.getTls());
+ std::string cipher = conn.getTlsCipher();
+ EXPECT_FALSE(cipher.empty());
+ std::cout << "TLS cipher is '" << cipher << "'\n";
+}
+
+/// @brief Check the SSL/TLS protected connection still requires the password.
+TEST_F(MySqlSecureConnectionTest, TlsInvalidPassword) {
+ if (!hasMySQLTls()) {
+ std::cout << "SSL/TLS support is not available or configured: "
+ << "skipping this test\n";
+ return;
+ }
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_SECURE_USER,
+ INVALID_PASSWORD, 0, 0,
+ VALID_CERT, VALID_KEY, VALID_CA,
+ VALID_CIPHER);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbOpenError);
+}
+
+/// @brief Check the SSL/TLS protected connection requires crypto parameters.
+TEST_F(MySqlSecureConnectionTest, TlsNoCrypto) {
+ if (!hasMySQLTls()) {
+ std::cout << "SSL/TLS support is not available or configured: "
+ << "skipping this test\n";
+ return;
+ }
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_SECURE_USER,
+ VALID_PASSWORD);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbOpenError);
+}
+
+/// @brief Check the SSL/TLS protected connection requires valid key.
+TEST_F(MySqlSecureConnectionTest, TlsInvalidKey) {
+ if (!hasMySQLTls()) {
+ std::cout << "SSL/TLS support is not available or configured: "
+ << "skipping this test\n";
+ return;
+ }
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_SECURE_USER,
+ VALID_PASSWORD, 0, 0,
+ VALID_CERT, INVALID_KEY, VALID_CA,
+ VALID_CIPHER);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbOpenError);
+}
+
+/// @brief Check the SSL/TLS protected connection requires a key.
+TEST_F(MySqlSecureConnectionTest, TlsNoKey) {
+ if (!hasMySQLTls()) {
+ std::cout << "SSL/TLS support is not available or configured: "
+ << "skipping this test\n";
+ return;
+ }
+ std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+ VALID_HOST_TCP, VALID_SECURE_USER,
+ VALID_PASSWORD, 0, 0,
+ VALID_CERT, 0, VALID_CA,
+ VALID_CIPHER);
+ MySqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.openDatabase(), DbOpenError);
+}
+
+} // namespace
diff --git a/src/lib/mysql/tests/run_unittests.cc b/src/lib/mysql/tests/run_unittests.cc
new file mode 100644
index 0000000..4e83d4b
--- /dev/null
+++ b/src/lib/mysql/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/mysql/testutils/Makefile.am b/src/lib/mysql/testutils/Makefile.am
new file mode 100644
index 0000000..b4c2349
--- /dev/null
+++ b/src/lib/mysql/testutils/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\"
+AM_CPPFLAGS += -DDATABASE_WIPE_DIR=\"$(abs_top_builddir)/src/share/database/scripts\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libmysqltest.la
+
+libmysqltest_la_SOURCES = mysql_schema.cc mysql_schema.h
+
+libmysqltest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libmysqltest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(MYSQL_CPPFLAGS)
+libmysqltest_la_LDFLAGS = $(AM_LDFLAGS) $(MYSQL_LIBS)
+
+libmysqltest_la_LIBADD = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+
+endif
diff --git a/src/lib/mysql/testutils/Makefile.in b/src/lib/mysql/testutils/Makefile.in
new file mode 100644
index 0000000..86c8a56
--- /dev/null
+++ b/src/lib/mysql/testutils/Makefile.in
@@ -0,0 +1,862 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/mysql/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libmysqltest_la_DEPENDENCIES = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+am__libmysqltest_la_SOURCES_DIST = mysql_schema.cc mysql_schema.h
+@HAVE_GTEST_TRUE@am_libmysqltest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libmysqltest_la-mysql_schema.lo
+libmysqltest_la_OBJECTS = $(am_libmysqltest_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 =
+libmysqltest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libmysqltest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libmysqltest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libmysqltest_la_rpath =
+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)/libmysqltest_la-mysql_schema.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 = $(libmysqltest_la_SOURCES)
+DIST_SOURCES = $(am__libmysqltest_la_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\" \
+ -DDATABASE_WIPE_DIR=\"$(abs_top_builddir)/src/share/database/scripts\" \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libmysqltest.la
+@HAVE_GTEST_TRUE@libmysqltest_la_SOURCES = mysql_schema.cc mysql_schema.h
+@HAVE_GTEST_TRUE@libmysqltest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libmysqltest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(MYSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@libmysqltest_la_LDFLAGS = $(AM_LDFLAGS) $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@libmysqltest_la_LIBADD = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+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/mysql/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/mysql/testutils/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libmysqltest.la: $(libmysqltest_la_OBJECTS) $(libmysqltest_la_DEPENDENCIES) $(EXTRA_libmysqltest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libmysqltest_la_LINK) $(am_libmysqltest_la_rpath) $(libmysqltest_la_OBJECTS) $(libmysqltest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libmysqltest_la-mysql_schema.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)$(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 $@ $<
+
+libmysqltest_la-mysql_schema.lo: mysql_schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libmysqltest_la_CPPFLAGS) $(CPPFLAGS) $(libmysqltest_la_CXXFLAGS) $(CXXFLAGS) -MT libmysqltest_la-mysql_schema.lo -MD -MP -MF $(DEPDIR)/libmysqltest_la-mysql_schema.Tpo -c -o libmysqltest_la-mysql_schema.lo `test -f 'mysql_schema.cc' || echo '$(srcdir)/'`mysql_schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libmysqltest_la-mysql_schema.Tpo $(DEPDIR)/libmysqltest_la-mysql_schema.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mysql_schema.cc' object='libmysqltest_la-mysql_schema.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) $(libmysqltest_la_CPPFLAGS) $(CPPFLAGS) $(libmysqltest_la_CXXFLAGS) $(CXXFLAGS) -c -o libmysqltest_la-mysql_schema.lo `test -f 'mysql_schema.cc' || echo '$(srcdir)/'`mysql_schema.cc
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libmysqltest_la-mysql_schema.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-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)/libmysqltest_la-mysql_schema.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/mysql/testutils/mysql_schema.cc b/src/lib/mysql/testutils/mysql_schema.cc
new file mode 100644
index 0000000..fd4f8b1
--- /dev/null
+++ b/src/lib/mysql/testutils/mysql_schema.cc
@@ -0,0 +1,163 @@
+// Copyright (C) 2015-2022 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 <mysql.h>
+#include <mysql/testutils/mysql_schema.h>
+#include <mysql/mysql_connection.h>
+#include <exceptions/exceptions.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <stdlib.h>
+
+using namespace std;
+
+namespace isc {
+namespace db {
+namespace test {
+
+const char* MYSQL_VALID_TYPE = "type=mysql";
+
+string
+validMySQLConnectionString() {
+ return (connectionString(MYSQL_VALID_TYPE, VALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+}
+
+void destroyMySQLSchema(bool show_err, bool force) {
+ // If force is true or wipeMySQLData() fails, destroy the schema.
+ if (force || (!softWipeEnabled()) || wipeMySQLData(show_err)) {
+ runMySQLScript(DATABASE_SCRIPTS_DIR, "mysql/dhcpdb_drop.mysql", show_err);
+ }
+}
+
+void createMySQLSchema(bool show_err, bool force) {
+ // If force is true or wipeMySQLData() fails, recreate the schema.
+ if (force || (!softWipeEnabled()) || wipeMySQLData(show_err)) {
+ destroyMySQLSchema(show_err, true);
+ runMySQLScript(DATABASE_SCRIPTS_DIR, "mysql/dhcpdb_create.mysql", show_err);
+ }
+}
+
+bool wipeMySQLData(bool show_err) {
+ std::ostringstream cmd;
+ cmd << "sh " << DATABASE_WIPE_DIR << "/";
+
+ std::ostringstream version;
+ version << MYSQL_SCHEMA_VERSION_MAJOR << "." << MYSQL_SCHEMA_VERSION_MINOR;
+
+ cmd << "mysql/wipe_data.sh " << version.str()
+ << " -N -B --user=keatest --password=keatest keatest";
+ if (!show_err) {
+ cmd << " 2>/dev/null ";
+ }
+
+ int retval = ::system(cmd.str().c_str());
+ if (retval) {
+ std::cerr << "wipeMySQLData failed:[" << cmd.str() << "]" << std::endl;
+ }
+
+ return(retval);
+}
+
+void runMySQLScript(const std::string& path, const std::string& script_name,
+ bool show_err) {
+ std::ostringstream cmd;
+ cmd << "mysql -N -B --user=keatest --password=keatest keatest";
+ if (!show_err) {
+ cmd << " 2>/dev/null ";
+ }
+
+ if (!path.empty()) {
+ cmd << " < " << path << "/";
+ }
+
+ cmd << script_name;
+
+ int retval = ::system(cmd.str().c_str());
+ if (retval) {
+ std::cerr << "runMySQLSchema failed: " << cmd.str() << std::endl;
+ isc_throw(Unexpected, "runMySQLSchema failed: " << cmd.str());
+ }
+}
+
+string getMySQLTlsEnv() {
+ const string name("KEA_MYSQL_HAVE_SSL");
+ const char* val = getenv(name.c_str());
+ return (val ? string(val) : "");
+}
+
+string getMySQLTlsServerVariable(string variable) {
+ MYSQL_RES* result(0);
+ try {
+ DatabaseConnection::ParameterMap parameters =
+ DatabaseConnection::parse(validMySQLConnectionString());
+ MySqlConnection conn(parameters);
+ conn.openDatabase();
+ string sql("SHOW GLOBAL VARIABLES LIKE '");
+ sql += variable;
+ sql += "'";
+ if (mysql_query(conn.mysql_, sql.c_str())) {
+ isc_throw(DbOperationError,
+ sql << ": " << mysql_error(conn.mysql_));
+ }
+ result = mysql_use_result(conn.mysql_);
+ size_t count = mysql_num_fields(result);
+ if (count != 2) {
+ isc_throw(DbOperationError,
+ sql << " returned " << count << " rows, expecting 2");
+ }
+ MYSQL_ROW row = mysql_fetch_row(result);
+ if (!row) {
+ isc_throw(DbOperationError, sql << " returned row is null");
+ }
+ // first column is 'have_ssl', second is the status.
+ string name(row[0]);
+ if (name != variable) {
+ isc_throw(DbOperationError,
+ sql << " returned a wrong name '" << name
+ << "', expected '" << variable << "'");
+ }
+ string value(row[1]);
+ mysql_free_result(result);
+ return (value);
+ } catch (...) {
+ if (result) {
+ mysql_free_result(result);
+ }
+ throw;
+ }
+}
+
+bool isMySQLTlsConfigured() {
+ if (getMySQLTlsServerVariable("ssl_ca").find("kea-ca.crt") == string::npos) {
+ return (false);
+ }
+ if (getMySQLTlsServerVariable("ssl_cert").find("kea-server.crt") == string::npos) {
+ return (false);
+ }
+ if (getMySQLTlsServerVariable("ssl_key").find("kea-server.key") == string::npos) {
+ return (false);
+ }
+ return (true);
+}
+
+string getMySQLTlsServer() {
+ string value = getMySQLTlsServerVariable("have_ssl");
+ if (value == "YES" && !isMySQLTlsConfigured()) {
+ value = "UNCONFIGURED";
+ }
+ const string env("KEA_MYSQL_HAVE_SSL");
+ static_cast<void>(setenv(env.c_str(), value.c_str(), 1));
+ return (value);
+}
+
+} // namespace test
+} // namespace db
+} // namespace isc
diff --git a/src/lib/mysql/testutils/mysql_schema.h b/src/lib/mysql/testutils/mysql_schema.h
new file mode 100644
index 0000000..9178bc5
--- /dev/null
+++ b/src/lib/mysql/testutils/mysql_schema.h
@@ -0,0 +1,123 @@
+// Copyright (C) 2015-2022 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 TEST_MYSQL_SCHEMA_H
+#define TEST_MYSQL_SCHEMA_H
+
+#include <config.h>
+#include <database/testutils/schema.h>
+#include <string>
+
+namespace isc {
+namespace db {
+namespace test {
+
+extern const char* MYSQL_VALID_TYPE;
+
+/// Return valid connection string
+///
+/// @return valid MySQL connection string.
+std::string validMySQLConnectionString();
+
+/// @brief Clear the unit test database
+///
+/// In order to reduce test execution time, this function
+/// defaults to first attempting to delete transient data
+/// from the database by calling @c wipeMySQLData. If that
+/// function fails it will then attempt to destroy the database
+/// schema by running the SQL script:
+///
+/// <DATABASE_SCRIPT_DIR>/mysql/dhcpdb_drop.mysql
+///
+/// The default behavior of wiping the data only may be overridden
+/// in one of two ways:
+///
+/// -# Setting the force parameter to true
+/// -# Defining the environment variable:
+/// KEA_TEST_DB_WIPE_DATA_ONLY="false"
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @param force if true, the function will skip deleting the data and
+/// destroy the schema.
+void destroyMySQLSchema(bool show_err = false, bool force = false);
+
+/// @brief Create the unit test MySQL Schema
+///
+/// Ensures the unit test database is a empty and version-correct.
+/// Unless, the force parameter is true, it will first attempt
+/// to wipe the data from the database by calling @c wipeMySQLData.
+/// If this call succeeds the function returns, otherwise it will
+/// will call @c destroyMySQLSchema to forcibly remove the
+/// existing schema and then submits the SQL script:
+///
+/// <DATABASE_SCRIPTS_DIR>/mysql/dhcpdb_create.mysql
+///
+/// to the unit test MySQL database.
+///
+/// The default behavior of wiping the data only may be overridden
+/// in one of two ways:
+///
+/// -# Setting the force parameter to true
+/// -# Defining the environment variable:
+/// KEA_TEST_DB_WIPE_DATA_ONLY="false"
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @param force flag when true, the function will recreate the database
+/// schema.
+void createMySQLSchema(bool show_err = false, bool force = false);
+
+/// @brief Attempts to wipe data from the MySQL unit test database
+///
+/// Runs the shell script
+///
+/// <DATABASE_WIPE_DIR>/mysql/wipe_data.sh
+///
+/// This will fail if there is no schema, if the existing schema
+/// version is incorrect (i.e. does not match MYSQL_SCHEMA_VERSION_MAJOR
+/// and MYSQL_SCHEMA_VERSION_MINOR), or a SQL error occurs. Otherwise,
+/// the script is should delete all transient data, leaving intact
+/// reference tables.
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+bool wipeMySQLData(bool show_err = false);
+
+/// @brief Run a MySQL SQL script against the MySQL unit test database
+///
+/// Submits the given SQL script to MySQL via mysql CLI. The output of
+/// stderr is suppressed unless the parameter, show_err is true. The is done
+/// to suppress warnings that might otherwise make test output needlessly
+/// noisy. An exception is thrown if the script fails to execute.
+///
+/// @param path - path (if not blank) of the script to execute
+/// @param script_name - file name of the path to execute
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @throw Unexpected when the script returns an error.
+void runMySQLScript(const std::string& path, const std::string& script_name,
+ bool show_err);
+
+/// @brief Get the SSL/TLS support status from the environment
+///
+/// The environment variable is KEA_MYSQL_HAVE_SSL
+std::string getMySQLTlsEnv();
+
+/// @brief Get the SSL/TLS support status from the server
+/// @note the returned value is set in the environment
+std::string getMySQLTlsServer();
+
+/// @brief Return true if the server has been configured with proper SSL/TLS
+/// credentials, false otherwise
+bool isMySQLTlsConfigured();
+
+/// @brief Get the server global variable value
+///
+/// @param variable The server global variable name
+std::string getMySQLTlsServerVariable(std::string variable);
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/pgsql/Makefile.am b/src/lib/pgsql/Makefile.am
new file mode 100644
index 0000000..fbefd8c
--- /dev/null
+++ b/src/lib/pgsql/Makefile.am
@@ -0,0 +1,31 @@
+SUBDIRS = . testutils tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-pgsql.la
+libkea_pgsql_la_SOURCES = pgsql_connection.cc pgsql_connection.h
+libkea_pgsql_la_SOURCES += pgsql_exchange.cc pgsql_exchange.h
+
+
+libkea_pgsql_la_LIBADD = $(top_builddir)/src/lib/database/libkea-database.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_pgsql_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_pgsql_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+libkea_pgsql_la_LDFLAGS = -no-undefined -version-info 53:0:0
+
+libkea_pgsql_la_LDFLAGS += $(PGSQL_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_pgsql_includedir = $(pkgincludedir)/pgsql
+libkea_pgsql_include_HEADERS = \
+ pgsql_connection.h \
+ pgsql_exchange.h
diff --git a/src/lib/pgsql/Makefile.in b/src/lib/pgsql/Makefile.in
new file mode 100644
index 0000000..d2aa868
--- /dev/null
+++ b/src/lib/pgsql/Makefile.in
@@ -0,0 +1,959 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/pgsql
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_pgsql_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)$(libkea_pgsql_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_pgsql_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_pgsql_la_OBJECTS = pgsql_connection.lo pgsql_exchange.lo
+libkea_pgsql_la_OBJECTS = $(am_libkea_pgsql_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_pgsql_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_pgsql_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)/pgsql_connection.Plo \
+ ./$(DEPDIR)/pgsql_exchange.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_pgsql_la_SOURCES)
+DIST_SOURCES = $(libkea_pgsql_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 = $(libkea_pgsql_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . testutils tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-pgsql.la
+libkea_pgsql_la_SOURCES = pgsql_connection.cc pgsql_connection.h \
+ pgsql_exchange.cc pgsql_exchange.h
+libkea_pgsql_la_LIBADD = \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libkea_pgsql_la_LDFLAGS = -no-undefined -version-info 53:0:0 \
+ $(PGSQL_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_pgsql_includedir = $(pkgincludedir)/pgsql
+libkea_pgsql_include_HEADERS = \
+ pgsql_connection.h \
+ pgsql_exchange.h
+
+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/pgsql/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/pgsql/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-pgsql.la: $(libkea_pgsql_la_OBJECTS) $(libkea_pgsql_la_DEPENDENCIES) $(EXTRA_libkea_pgsql_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_pgsql_la_LINK) -rpath $(libdir) $(libkea_pgsql_la_OBJECTS) $(libkea_pgsql_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pgsql_connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pgsql_exchange.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_pgsql_includeHEADERS: $(libkea_pgsql_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_pgsql_include_HEADERS)'; test -n "$(libkea_pgsql_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_pgsql_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_pgsql_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)$(libkea_pgsql_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_pgsql_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_pgsql_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_pgsql_include_HEADERS)'; test -n "$(libkea_pgsql_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_pgsql_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_pgsql_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/pgsql_connection.Plo
+ -rm -f ./$(DEPDIR)/pgsql_exchange.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-libkea_pgsql_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)/pgsql_connection.Plo
+ -rm -f ./$(DEPDIR)/pgsql_exchange.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-libkea_pgsql_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_pgsql_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-libkea_pgsql_includeHEADERS
+
+.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/pgsql/pgsql_connection.cc b/src/lib/pgsql/pgsql_connection.cc
new file mode 100644
index 0000000..6ec896d
--- /dev/null
+++ b/src/lib/pgsql/pgsql_connection.cc
@@ -0,0 +1,560 @@
+// Copyright (C) 2016-2023 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 <database/db_exceptions.h>
+#include <database/db_log.h>
+#include <pgsql/pgsql_connection.h>
+
+// PostgreSQL errors should be tested based on the SQL state code. Each state
+// code is 5 decimal, ASCII, digits, the first two define the category of
+// error, the last three are the specific error. PostgreSQL makes the state
+// code as a char[5]. Macros for each code are defined in PostgreSQL's
+// server/utils/errcodes.h, although they require a second macro,
+// MAKE_SQLSTATE for completion. For example, duplicate key error as:
+//
+// #define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
+//
+// PostgreSQL deliberately omits the MAKE_SQLSTATE macro so callers can/must
+// supply their own. We'll define it as an initialization list:
+#define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
+// So we can use it like this: const char some_error[] = ERRCODE_xxxx;
+#define PGSQL_STATECODE_LEN 5
+#include <utils/errcodes.h>
+
+#include <sstream>
+
+using namespace std;
+
+namespace isc {
+namespace db {
+
+// Default connection timeout
+
+/// @todo: migrate this default timeout to src/bin/dhcpX/simple_parserX.cc
+const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
+
+const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
+const char PgSqlConnection::NULL_KEY[] = ERRCODE_NOT_NULL_VIOLATION;
+
+bool PgSqlConnection::warned_about_tls = false;
+
+PgSqlResult::PgSqlResult(PGresult *result)
+ : result_(result), rows_(0), cols_(0) {
+ if (!result) {
+ // Certain failures, like a loss of connectivity, can return a
+ // null PGresult and we still need to be able to create a PgSqlResult.
+ // We'll set row and col counts to -1 to prevent anyone going off the
+ // rails.
+ rows_ = -1;
+ cols_ = -1;
+ } else {
+ rows_ = PQntuples(result);
+ cols_ = PQnfields(result);
+ }
+}
+
+void
+PgSqlResult::rowCheck(int row) const {
+ if (row < 0 || row >= rows_) {
+ isc_throw (db::DbOperationError, "row: " << row
+ << ", out of range: 0.." << rows_);
+ }
+}
+
+PgSqlResult::~PgSqlResult() {
+ if (result_) {
+ PQclear(result_);
+ }
+}
+
+void
+PgSqlResult::colCheck(int col) const {
+ if (col < 0 || col >= cols_) {
+ isc_throw (DbOperationError, "col: " << col
+ << ", out of range: 0.." << cols_);
+ }
+}
+
+void
+PgSqlResult::rowColCheck(int row, int col) const {
+ rowCheck(row);
+ colCheck(col);
+}
+
+std::string
+PgSqlResult::getColumnLabel(const int col) const {
+ const char* label = NULL;
+ try {
+ colCheck(col);
+ label = PQfname(result_, col);
+ } catch (...) {
+ std::ostringstream os;
+ os << "Unknown column:" << col;
+ return (os.str());
+ }
+
+ return (label);
+}
+
+PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
+ : conn_(conn), committed_(false) {
+ conn_.startTransaction();
+}
+
+PgSqlTransaction::~PgSqlTransaction() {
+ // If commit() wasn't explicitly called, rollback.
+ if (!committed_) {
+ conn_.rollback();
+ }
+}
+
+void
+PgSqlTransaction::commit() {
+ conn_.commit();
+ committed_ = true;
+}
+
+PgSqlConnection::~PgSqlConnection() {
+ if (conn_) {
+ // Deallocate the prepared queries.
+ if (PQstatus(conn_) == CONNECTION_OK) {
+ PgSqlResult r(PQexec(conn_, "DEALLOCATE all"));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ // Highly unlikely but we'll log it and go on.
+ DB_LOG_ERROR(PGSQL_DEALLOC_ERROR)
+ .arg(PQerrorMessage(conn_));
+ }
+ }
+ }
+}
+
+std::pair<uint32_t, uint32_t>
+PgSqlConnection::getVersion(const ParameterMap& parameters) {
+ // Get a connection.
+ PgSqlConnection conn(parameters);
+
+ // Open the database.
+ conn.openDatabaseInternal(false);
+
+ const char* version_sql = "SELECT version, minor FROM schema_version;";
+ PgSqlResult r(PQexec(conn.conn_, version_sql));
+ if (PQresultStatus(r) != PGRES_TUPLES_OK) {
+ isc_throw(DbOperationError, "unable to execute PostgreSQL statement <"
+ << version_sql << ", reason: " << PQerrorMessage(conn.conn_));
+ }
+
+ uint32_t version;
+ PgSqlExchange::getColumnValue(r, 0, 0, version);
+
+ uint32_t minor;
+ PgSqlExchange::getColumnValue(r, 0, 1, minor);
+
+ return (make_pair(version, minor));
+}
+
+void
+PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
+ // Prepare all statements queries with all known fields datatype
+ PgSqlResult r(PQprepare(conn_, statement.name, statement.text,
+ statement.nbparams, statement.types));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
+ << " name: " << statement.name
+ << ", reason: " << PQerrorMessage(conn_)
+ << ", text: " << statement.text);
+ }
+}
+
+void
+PgSqlConnection::prepareStatements(const PgSqlTaggedStatement* start_statement,
+ const PgSqlTaggedStatement* end_statement) {
+ // Created the PostgreSQL prepared statements.
+ for (const PgSqlTaggedStatement* tagged_statement = start_statement;
+ tagged_statement != end_statement; ++tagged_statement) {
+ prepareStatement(*tagged_statement);
+ }
+}
+
+std::string
+PgSqlConnection::getConnParameters() {
+ return (getConnParametersInternal(false));
+}
+
+std::string
+PgSqlConnection::getConnParametersInternal(bool logging) {
+ string dbconnparameters;
+ string shost = "localhost";
+ try {
+ shost = getParameter("host");
+ } catch(...) {
+ // No host. Fine, we'll use "localhost"
+ }
+
+ dbconnparameters += "host = '" + shost + "'" ;
+
+ unsigned int port = 0;
+ try {
+ setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidPort, ex.what());
+ }
+
+ // Add port to connection parameters when not default.
+ if (port > 0) {
+ std::ostringstream oss;
+ oss << port;
+ dbconnparameters += " port = " + oss.str();
+ }
+
+ string suser;
+ try {
+ suser = getParameter("user");
+ dbconnparameters += " user = '" + suser + "'";
+ } catch(...) {
+ // No user. Fine, we'll use NULL
+ }
+
+ string spassword;
+ try {
+ spassword = getParameter("password");
+ dbconnparameters += " password = '" + spassword + "'";
+ } catch(...) {
+ // No password. Fine, we'll use NULL
+ }
+
+ string sname;
+ try {
+ sname = getParameter("name");
+ dbconnparameters += " dbname = '" + sname + "'";
+ } catch(...) {
+ // No database name. Throw a "NoDatabaseName" exception
+ isc_throw(NoDatabaseName, "must specify a name for the database");
+ }
+
+ unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT;
+ unsigned int tcp_user_timeout = 0;
+ try {
+ // The timeout is only valid if greater than zero, as depending on the
+ // database, a zero timeout might signify something like "wait
+ // indefinitely".
+ setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
+ // This timeout value can be 0, meaning that the database client will
+ // follow a default behavior. Earlier Postgres versions didn't have
+ // this parameter, so we allow 0 to skip setting them for these
+ // earlier versions.
+ setIntParameterValue("tcp-user-timeout", 0, numeric_limits<int>::max(), tcp_user_timeout);
+
+ } catch (const std::exception& ex) {
+ isc_throw(DbInvalidTimeout, ex.what());
+ }
+
+ // Append connection timeout.
+ std::ostringstream oss;
+ oss << " connect_timeout = " << connect_timeout;
+
+ if (tcp_user_timeout > 0) {
+// tcp_user_timeout parameter is a PostgreSQL 12+ feature.
+#ifdef HAVE_PGSQL_TCP_USER_TIMEOUT
+ oss << " tcp_user_timeout = " << tcp_user_timeout * 1000;
+ static_cast<void>(logging);
+#else
+ if (logging) {
+ DB_LOG_WARN(PGSQL_TCP_USER_TIMEOUT_UNSUPPORTED).arg();
+ }
+#endif
+ }
+ dbconnparameters += oss.str();
+
+ return (dbconnparameters);
+}
+
+void
+PgSqlConnection::openDatabase() {
+ openDatabaseInternal(true);
+}
+
+void
+PgSqlConnection::openDatabaseInternal(bool logging) {
+ std::string dbconnparameters = getConnParametersInternal(logging);
+ // Connect to Postgres, saving the low level connection pointer
+ // in the holder object
+ PGconn* new_conn = PQconnectdb(dbconnparameters.c_str());
+ if (!new_conn) {
+ isc_throw(DbOpenError, "could not allocate connection object");
+ }
+
+ if (PQstatus(new_conn) != CONNECTION_OK) {
+ // If we have a connection object, we have to call finish
+ // to release it, but grab the error message first.
+ std::string error_message = PQerrorMessage(new_conn);
+ PQfinish(new_conn);
+ isc_throw(DbOpenError, error_message);
+ }
+
+ // We have a valid connection, so let's save it to our holder
+ conn_.setConnection(new_conn);
+}
+
+bool
+PgSqlConnection::compareError(const PgSqlResult& r, const char* error_state) {
+ const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
+ // PostgreSQL guarantees it will always be 5 characters long
+ return ((sqlstate != NULL) &&
+ (memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0));
+}
+
+void
+PgSqlConnection::checkStatementError(const PgSqlResult& r,
+ PgSqlTaggedStatement& statement) {
+ int s = PQresultStatus(r);
+ if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
+ // We're testing the first two chars of SQLSTATE, as this is the
+ // error class. Note, there is a severity field, but it can be
+ // misleadingly returned as fatal. However, a loss of connectivity
+ // can lead to a NULL sqlstate with a status of PGRES_FATAL_ERROR.
+ const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
+ if ((sqlstate == NULL) ||
+ ((memcmp(sqlstate, "08", 2) == 0) || // Connection Exception
+ (memcmp(sqlstate, "53", 2) == 0) || // Insufficient resources
+ (memcmp(sqlstate, "54", 2) == 0) || // Program Limit exceeded
+ (memcmp(sqlstate, "57", 2) == 0) || // Operator intervention
+ (memcmp(sqlstate, "58", 2) == 0))) { // System error
+ DB_LOG_ERROR(PGSQL_FATAL_ERROR)
+ .arg(statement.name)
+ .arg(PQerrorMessage(conn_))
+ .arg(sqlstate ? sqlstate : "<sqlstate null>");
+
+ // Mark this connection as no longer usable.
+ markUnusable();
+
+ // Start the connection recovery.
+ startRecoverDbConnection();
+
+ // We still need to throw so caller can error out of the current
+ // processing.
+ isc_throw(DbConnectionUnusable,
+ "fatal database error or connectivity lost");
+ }
+
+ // Failure: check for the special case of duplicate entry.
+ if (compareError(r, PgSqlConnection::DUPLICATE_KEY)) {
+ isc_throw(DuplicateEntry, "statement: " << statement.name
+ << ", reason: " << PQerrorMessage(conn_));
+ }
+
+ // Failure: check for the special case of null key violation.
+ if (compareError(r, PgSqlConnection::NULL_KEY)) {
+ isc_throw(NullKeyError, "statement: " << statement.name
+ << ", reason: " << PQerrorMessage(conn_));
+ }
+
+ // Apparently it wasn't fatal, so we throw with a helpful message.
+ const char* error_message = PQerrorMessage(conn_);
+ isc_throw(DbOperationError, "Statement exec failed for: "
+ << statement.name << ", status: " << s
+ << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")
+ << " ], reason: " << error_message);
+ }
+}
+
+void
+PgSqlConnection::startTransaction() {
+ // If it is nested transaction, do nothing.
+ if (++transaction_ref_count_ > 1) {
+ return;
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_START_TRANSACTION);
+ checkUnusable();
+ PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ const char* error_message = PQerrorMessage(conn_);
+ isc_throw(DbOperationError, "unable to start transaction"
+ << error_message);
+ }
+}
+
+bool
+PgSqlConnection::isTransactionStarted() const {
+ return (transaction_ref_count_ > 0);
+}
+
+void
+PgSqlConnection::commit() {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(Unexpected, "commit called for not started transaction - coding error");
+ }
+
+ // When committing nested transaction, do nothing.
+ if (--transaction_ref_count_ > 0) {
+ return;
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_COMMIT);
+ checkUnusable();
+ PgSqlResult r(PQexec(conn_, "COMMIT"));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ const char* error_message = PQerrorMessage(conn_);
+ isc_throw(DbOperationError, "commit failed: " << error_message);
+ }
+}
+
+void
+PgSqlConnection::rollback() {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(Unexpected, "rollback called for not started transaction - coding error");
+ }
+
+ // When rolling back nested transaction, do nothing.
+ if (--transaction_ref_count_ > 0) {
+ return;
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_ROLLBACK);
+ checkUnusable();
+ PgSqlResult r(PQexec(conn_, "ROLLBACK"));
+ if (PQresultStatus(r) != PGRES_COMMAND_OK) {
+ const char* error_message = PQerrorMessage(conn_);
+ isc_throw(DbOperationError, "rollback failed: " << error_message);
+ }
+}
+
+void
+PgSqlConnection::createSavepoint(const std::string& name) {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(InvalidOperation, "no transaction, cannot create savepoint: " << name);
+ }
+
+ DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, PGSQL_CREATE_SAVEPOINT).arg(name);
+ std::string sql("SAVEPOINT " + name);
+ executeSQL(sql);
+}
+
+void
+PgSqlConnection::rollbackToSavepoint(const std::string& name) {
+ if (transaction_ref_count_ <= 0) {
+ isc_throw(InvalidOperation, "no transaction, cannot rollback to savepoint: " << name);
+ }
+
+ std::string sql("ROLLBACK TO SAVEPOINT " + name);
+ executeSQL(sql);
+}
+
+void
+PgSqlConnection::executeSQL(const std::string& sql) {
+ // Use a TaggedStatement so we can call checkStatementError and ensure
+ // we detect connectivity issues properly.
+ PgSqlTaggedStatement statement({0, {OID_NONE}, "run-statement", sql.c_str()});
+ checkUnusable();
+ PgSqlResult r(PQexec(conn_, statement.text));
+ checkStatementError(r, statement);
+}
+
+PgSqlResultPtr
+PgSqlConnection::executePreparedStatement(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings) {
+ checkUnusable();
+
+ if (statement.nbparams != in_bindings.size()) {
+ isc_throw (InvalidOperation, "executePreparedStatement:"
+ << " expected: " << statement.nbparams
+ << " parameters, given: " << in_bindings.size()
+ << ", statement: " << statement.name
+ << ", SQL: " << statement.text);
+ }
+
+ const char* const* values = 0;
+ const int* lengths = 0;
+ const int* formats = 0;
+ if (statement.nbparams > 0) {
+ values = static_cast<const char* const*>(&in_bindings.values_[0]);
+ lengths = static_cast<const int *>(&in_bindings.lengths_[0]);
+ formats = static_cast<const int *>(&in_bindings.formats_[0]);
+ }
+
+ PgSqlResultPtr result_set;
+ result_set.reset(new PgSqlResult(PQexecPrepared(conn_, statement.name, statement.nbparams,
+ values, lengths, formats, 0)));
+
+ checkStatementError(*result_set, statement);
+ return (result_set);
+}
+
+void
+PgSqlConnection::selectQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings,
+ ConsumeResultRowFun process_result_row) {
+ // Execute the prepared statement.
+ PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
+
+ // Iterate over the returned rows and invoke the row consumption
+ // function on each one.
+ int rows = result_set->getRows();
+ for (int row = 0; row < rows; ++row) {
+ try {
+ process_result_row(*result_set, row);
+ } catch (const std::exception& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ statement.text << ">");
+ }
+ }
+}
+
+void
+PgSqlConnection::insertQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings) {
+ // Execute the prepared statement.
+ PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
+}
+
+uint64_t
+PgSqlConnection::updateDeleteQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings) {
+ // Execute the prepared statement.
+ PgSqlResultPtr result_set = executePreparedStatement(statement, in_bindings);
+
+ return (boost::lexical_cast<int>(PQcmdTuples(*result_set)));
+}
+
+template<typename T>
+void
+PgSqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) {
+ string svalue;
+ try {
+ svalue = getParameter(name);
+ } catch (...) {
+ // Do nothing if the parameter is not present.
+ }
+ if (svalue.empty()) {
+ return;
+ }
+ try {
+ // Try to convert the value.
+ auto parsed_value = boost::lexical_cast<T>(svalue);
+ // Check if the value is within the specified range.
+ if ((parsed_value < min) || (parsed_value > max)) {
+ isc_throw(BadValue, "bad " << svalue << " value");
+ }
+ // Everything is fine. Return the parsed value.
+ value = parsed_value;
+
+ } catch (...) {
+ // We may end up here when lexical_cast fails or when the
+ // parsed value is not within the desired range. In both
+ // cases let's throw the same general error.
+ isc_throw(BadValue, name << " parameter (" <<
+ svalue << ") must be an integer between "
+ << min << " and " << max);
+ }
+}
+
+
+} // end of isc::db namespace
+} // end of isc namespace
diff --git a/src/lib/pgsql/pgsql_connection.h b/src/lib/pgsql/pgsql_connection.h
new file mode 100644
index 0000000..4e8c21a
--- /dev/null
+++ b/src/lib/pgsql/pgsql_connection.h
@@ -0,0 +1,597 @@
+// Copyright (C) 2016-2023 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 PGSQL_CONNECTION_H
+#define PGSQL_CONNECTION_H
+
+#include <asiolink/io_service.h>
+#include <pgsql/pgsql_exchange.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace db {
+
+/// @brief Define the PostgreSQL backend version.
+const uint32_t PGSQL_SCHEMA_VERSION_MAJOR = 18;
+const uint32_t PGSQL_SCHEMA_VERSION_MINOR = 0;
+
+// Maximum number of parameters that can be used a statement
+// @todo This allows us to use an initializer list (since we can't
+// require C++11). It's unlikely we'd go past this many a single
+// statement.
+const size_t PGSQL_MAX_PARAMETERS_IN_QUERY = 128;
+
+/// @brief Define a PostgreSQL statement.
+///
+/// Each statement is associated with an index, which is used to reference the
+/// associated prepared statement.
+struct PgSqlTaggedStatement {
+ /// Number of parameters for a given query
+ int nbparams;
+
+ /// @brief OID types
+ ///
+ /// Specify parameter types. See /usr/include/postgresql/catalog/pg_type.h.
+ /// For some reason that header does not export those parameters.
+ /// Those OIDs must match both input and output parameters.
+ const Oid types[PGSQL_MAX_PARAMETERS_IN_QUERY];
+
+ /// Short name of the query.
+ const char* name;
+
+ /// Text representation of the actual query.
+ const char* text;
+};
+
+/// @{
+/// @brief Constants for PostgreSQL data types
+/// These are defined by PostgreSQL in <catalog/pg_type.h>, but including
+/// this file is extraordinarily convoluted, so we'll use these to fill-in.
+/// @{
+const size_t OID_NONE = 0; // PostgreSQL infers proper type
+const size_t OID_BOOL = 16;
+const size_t OID_BYTEA = 17;
+const size_t OID_INT8 = 20; // 8 byte int
+const size_t OID_INT2 = 21; // 2 byte int
+const size_t OID_INT4 = 23; // 4 byte int
+const size_t OID_TEXT = 25;
+const size_t OID_VARCHAR = 1043;
+const size_t OID_TIMESTAMP = 1114;
+/// @}
+
+/// @brief Postgresql connection handle Holder
+///
+/// Small RAII object for safer initialization, will close the database
+/// connection upon destruction. This means that if an exception is thrown
+/// during database initialization, resources allocated to the database are
+/// guaranteed to be freed.
+///
+/// It makes no sense to copy an object of this class. After the copy, both
+/// objects would contain pointers to the same PgSql context object. The
+/// destruction of one would invalid the context in the remaining object.
+/// For this reason, the class is declared noncopyable.
+class PgSqlHolder : public boost::noncopyable {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Sets the Postgresql API connector handle to NULL.
+ ///
+ PgSqlHolder() : pgconn_(NULL) {
+ }
+
+ /// @brief Destructor
+ ///
+ /// Frees up resources allocated by the connection.
+ ~PgSqlHolder() {
+ if (pgconn_ != NULL) {
+ PQfinish(pgconn_);
+ }
+ }
+
+ /// @brief Sets the connection to the value given
+ ///
+ /// @param connection - pointer to the Postgresql connection instance
+ void setConnection(PGconn* connection) {
+ if (pgconn_ != NULL) {
+ // Already set? Release the current connection first.
+ // Maybe this should be an error instead?
+ PQfinish(pgconn_);
+ }
+
+ pgconn_ = connection;
+ }
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the PgSqlHolder object to be passed as the context argument to
+ /// PQxxxx functions.
+ operator PGconn*() const {
+ return (pgconn_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the connection for emptiness: "if (holder)"
+ operator bool() const {
+ return (pgconn_);
+ }
+
+private:
+ PGconn* pgconn_; ///< Postgresql connection
+};
+
+/// @brief Forward declaration to @ref PgSqlConnection.
+class PgSqlConnection;
+
+/// @brief RAII object representing a PostgreSQL transaction.
+///
+/// An instance of this class should be created in a scope where multiple
+/// INSERT statements should be executed within a single transaction. The
+/// transaction is started when the constructor of this class is invoked.
+/// The transaction is ended when the @ref PgSqlTransaction::commit is
+/// explicitly called or when the instance of this class is destroyed.
+/// The @ref PgSqlTransaction::commit commits changes to the database.
+/// If the class instance is destroyed before @ref PgSqlTransaction::commit
+/// has been called, the transaction is rolled back. The rollback on
+/// destruction guarantees that partial data is not stored in the database
+/// when an error occurs during any of the operations within a transaction.
+///
+/// By default PostgreSQL performs a commit following each statement which
+/// alters the database (i.e. "autocommit"). Starting a transaction
+/// stops autocommit for the connection until the transaction is ended by
+/// either commit or rollback. Other connections are unaffected.
+class PgSqlTransaction : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts transaction by executing the SQL statement: "START TRANSACTION"
+ ///
+ /// @param conn PostgreSQL connection to use for the transaction. This
+ /// connection will be later used to commit or rollback changes.
+ ///
+ /// @throw DbOperationError if statement execution fails
+ PgSqlTransaction(PgSqlConnection& conn);
+
+ /// @brief Destructor.
+ ///
+ /// If the transaction has not been committed, it is rolled back
+ /// by executing the SQL statement: "ROLLBACK"
+ ///
+ /// @throw DbOperationError if statement execution fails
+ ~PgSqlTransaction();
+
+ /// @brief Commits transaction.
+ ///
+ /// Commits all changes made during the transaction by executing the
+ /// SQL statement: "COMMIT"
+ ///
+ /// @throw DbOperationError if statement execution fails
+ void commit();
+
+private:
+
+ /// @brief Holds reference to the PostgreSQL database connection.
+ PgSqlConnection& conn_;
+
+ /// @brief Boolean flag indicating if the transaction has been committed.
+ ///
+ /// This flag is used in the class destructor to assess if the
+ /// transaction should be rolled back.
+ bool committed_;
+};
+
+/// @brief Common PgSql Connector Pool
+///
+/// This class provides common operations for PgSql database connection
+/// used by both PgSqlLeaseMgr and PgSqlHostDataSource. It manages connecting
+/// to the database and preparing compiled statements. Its fields are
+/// public, because they are used (both set and retrieved) in classes
+/// that use instances of PgSqlConnection.
+class PgSqlConnection : public db::DatabaseConnection {
+public:
+ /// @brief Define the PgSql error state for a duplicate key error.
+ static const char DUPLICATE_KEY[];
+ /// @brief Define the PgSql error state for a null foreign key error.
+ static const char NULL_KEY[];
+
+ /// @brief Function invoked to process fetched row.
+ typedef std::function<void(PgSqlResult&, int)> ConsumeResultRowFun;
+
+ /// @brief Emit the TLS support warning only once.
+ static bool warned_about_tls;
+
+ /// @brief Constructor
+ ///
+ /// Initialize PgSqlConnection object with parameters needed for connection.
+ ///
+ /// @param parameters Specify the connection details.
+ /// @param io_accessor The IOService accessor function.
+ /// @param callback The connection recovery callback.
+ PgSqlConnection(const ParameterMap& parameters,
+ IOServiceAccessorPtr io_accessor = IOServiceAccessorPtr(),
+ DbCallback callback = DbCallback())
+ : DatabaseConnection(parameters, callback),
+ io_service_accessor_(io_accessor), io_service_(),
+ transaction_ref_count_(0) {
+ }
+
+ /// @brief Destructor
+ virtual ~PgSqlConnection();
+
+ /// @brief Get the schema version.
+ ///
+ /// @param parameters A data structure relating keywords and values
+ /// concerned with the database.
+ ///
+ /// @return Version number as a pair of unsigned integers. "first" is the
+ /// major version number, "second" the minor number.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ static std::pair<uint32_t, uint32_t>
+ getVersion(const ParameterMap& parameters);
+
+ /// @brief Prepare Single Statement
+ ///
+ /// Creates a prepared statement from the text given and adds it to the
+ /// statements_ vector at the given index.
+ ///
+ /// @param statement SQL statement to be prepared.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ void prepareStatement(const PgSqlTaggedStatement& statement);
+
+ /// @brief Prepare statements
+ ///
+ /// Creates the prepared statements for all of the SQL statements used
+ /// by the PostgreSQL backend.
+ ///
+ /// @param start_statement Pointer to the first statement in range of the
+ /// statements to be compiled.
+ /// @param end_statement Pointer to the statement marking end of the
+ /// range of statements to be compiled. This last statement is not compiled.
+ ///
+ /// @throw isc::db::DbOperationError An operation on the open database has
+ /// failed.
+ void prepareStatements(const PgSqlTaggedStatement* start_statement,
+ const PgSqlTaggedStatement* end_statement);
+
+ /// @brief Creates connection string from specified parameters.
+ ///
+ /// This function is called from the unit tests.
+ ///
+ /// @return connection string for @c openDatabase.
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbInvalidTimeout when the database timeout is wrong.
+ std::string getConnParameters();
+
+private:
+
+ /// @brief Creates connection string from the specified parameters.
+ ///
+ /// This is an internal implementation of the @c getConnParameters that
+ /// allows for controlling logging. In some cases, a caller can disable
+ /// logging warnings to avoid duplication of the log messages emitted
+ /// when the invocation is a result of calling @c getVersion before
+ /// opening the connection for the normal server operation.
+ ///
+ /// @param logging boolean parameter controlling if logging should be
+ /// enabled (when true) or disabled (when false).
+ ///
+ /// @return connection string for @c openDatabase.
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbInvalidTimeout when the database timeout is wrong.
+ std::string getConnParametersInternal(bool logging);
+
+public:
+
+ /// @brief Open database with logging.
+ ///
+ /// Opens the database using the information supplied in the parameters
+ /// passed to the constructor. It logs warnings resulting from the
+ /// @c getConnParameters.
+ ///
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbOpenError Error opening the database
+ void openDatabase();
+
+private:
+
+ /// @brief Internal implementation of the database opening.
+ ///
+ /// It allows for controlling if the @c getConnParameterInternal function
+ /// should log the warnings.
+ ///
+ /// @param logging boolean parameter controlling if logging should be
+ /// enabled (when true) or disabled (when false).
+ ///
+ /// @throw NoDatabaseName Mandatory database name not given
+ /// @throw DbOpenError Error opening the database
+ void openDatabaseInternal(bool logging);
+
+public:
+
+ /// @brief Starts new transaction
+ ///
+ /// This function begins a new transaction by sending the START TRANSACTION
+ /// statement to the database. The transaction should be explicitly committed
+ /// by calling @c commit() or rolled back by calling @c rollback().
+ ///
+ /// PostgreSQL does not support nested transactions directly. Issuing a
+ /// START TRANSACTION while already in a transaction will cause a warning to
+ /// be emitted but otherwise does not alter the state of the current transaction.
+ /// In other words, the transaction will still end upon the next COMMIT or
+ /// ROLLBACK statement.
+ ///
+ /// Therefore, this function checks if a transaction has already started and
+ /// does not start a new transaction. However, it increments a transaction
+ /// reference counter which is later decremented when @c commit() or @c
+ /// rollback() is called. When this mechanism is used properly, it
+ /// guarantees that nested transactions are not attempted, thus avoiding
+ /// unexpected commits or rollbacks of the pending transaction.
+ void startTransaction();
+
+ /// @brief Checks if there is a transaction in progress.
+ ///
+ /// @return true if a transaction has been started, false otherwise.
+ bool isTransactionStarted() const;
+
+ /// @brief Commits current transaction
+ ///
+ /// Commits all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the commit failed.
+ void commit();
+
+ /// @brief Rollbacks current transaction
+ ///
+ /// Rolls back all pending database operations. On databases that don't
+ /// support transactions, this is a no-op.
+ ///
+ /// When this method is called for a nested transaction it decrements the
+ /// transaction reference counter incremented during the call to
+ /// @c startTransaction.
+ ///
+ /// @throw DbOperationError If the rollback failed.
+ void rollback();
+
+ /// @brief Creates a savepoint within the current transaction
+ ///
+ /// Creates a named savepoint within the current transaction.
+ ///
+ /// @param name name of the savepoint to create.
+ ///
+ /// @throw InvalidOperation if called outside a transaction.
+ /// @throw DbOperationError If the savepoint cannot be created.
+ void createSavepoint(const std::string& name);
+
+ /// @brief Rollbacks to the given savepoint
+ ///
+ /// Rolls back all pending database operations made after the
+ /// named savepoint.
+ ///
+ /// @param name name of the savepoint to which to rollback.
+ ///
+ /// @throw InvalidOperation if called outside a transaction.
+ /// @throw DbOperationError if the rollback failed.
+ void rollbackToSavepoint(const std::string& name);
+
+ /// @brief Executes the an SQL statement.
+ ///
+ /// It executes the given SQL text after first checking the
+ /// connection for usability. After the statement is executed
+ /// @c checkStatementError() is invoked to ensure we detect
+ /// connectivity issues properly.
+ /// It is intended to be used to execute utility statements such
+ /// as commit, rollback et al, which have no parameters, return no
+ /// results, and are not pre-compiled.
+ ///
+ /// @param sql SQL statement to execute.
+ void executeSQL(const std::string& sql);
+
+ /// @brief Checks a result set's SQL state against an error state.
+ ///
+ /// @param r result set to check
+ /// @param error_state error state to compare against
+ ///
+ /// @return True if the result set's SQL state equals the error_state,
+ /// false otherwise.
+ bool compareError(const PgSqlResult& r, const char* error_state);
+
+ /// @brief Checks result of the r object
+ ///
+ /// This function is used to determine whether or not the SQL statement
+ /// execution succeeded, and in the event of failures, decide whether or
+ /// not the failures are recoverable.
+ ///
+ /// If the error is recoverable, the function will throw a DbOperationError.
+ /// If the error is deemed unrecoverable, such as a loss of connectivity
+ /// with the server, the function will call startRecoverDbConnection() which
+ /// will start the connection recovery.
+ ///
+ /// If the invocation returns true, this indicates the calling layer will
+ /// attempt recovery, and the function throws a DbOperationError to allow
+ /// the caller to error handle the failed db access attempt.
+ ///
+ /// @param r result of the last PostgreSQL operation
+ /// @param statement - tagged statement that was executed
+ ///
+ /// @throw isc::db::DbOperationError Detailed PostgreSQL failure
+ void checkStatementError(const PgSqlResult& r,
+ PgSqlTaggedStatement& statement);
+
+ /// @brief The recover connection
+ ///
+ /// This function starts the recover process of the connection.
+ ///
+ /// @note The recover function must be run on the IO Service thread.
+ void startRecoverDbConnection() {
+ if (callback_) {
+ if (!io_service_ && io_service_accessor_) {
+ io_service_ = (*io_service_accessor_)();
+ io_service_accessor_.reset();
+ }
+
+ if (io_service_) {
+ io_service_->post(std::bind(callback_, reconnectCtl()));
+ }
+ }
+ }
+
+ /// @brief Executes a prepared SQL statement.
+ ///
+ /// It executes the given prepared SQL statement, after checking
+ /// for usability and input parameter sanity. After the statement
+ /// is executed @c checkStatementError() is invoked to ensure we detect
+ /// connectivity issues properly. Upon successful execution, the
+ /// the result set is returned. It may be used for any form of
+ /// prepared SQL statement (e.g query, insert, update, delete...),
+ /// with or without input parameters.
+ ///
+ /// @param statement PgSqlTaggedStatement describing the prepared
+ /// statement to execute.
+ /// @param in_bindings array of input parameter bindings. If the SQL
+ /// statement requires no input arguments, this parameter should either
+ /// be omitted or an empty PsqlBindArray should be supplied.
+ /// @throw InvalidOperation if the number of parameters expected
+ /// by the statement does not match the size of the input bind array.
+ PgSqlResultPtr executePreparedStatement(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings
+ = PsqlBindArray());
+
+ /// @brief Executes SELECT query using prepared statement.
+ ///
+ /// The statement parameter refers to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided input bindings and
+ /// calls @c process_result_row function for each returned row. The
+ /// @c process_result_row function is implemented by the caller and should
+ /// gather and store each returned row in an external data structure prior.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings input bindings holding values to substitue placeholders
+ /// in the query.
+ /// @param process_result_row Pointer to the function to be invoked for each
+ /// retrieved row. This function consumes the retrieved data from the
+ /// result set.
+ void selectQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings,
+ ConsumeResultRowFun process_result_row);
+
+ /// @brief Executes INSERT prepared statement.
+ ///
+ /// The @c statement must refer to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// This method executes prepared statement using provided bindings to
+ /// insert data into the database.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings input bindings holding values to substitue placeholders
+ /// in the query.
+ void insertQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings);
+
+
+ /// @brief Executes UPDATE or DELETE prepared statement and returns
+ /// the number of affected rows.
+ ///
+ /// The @c statement must refer to an existing prepared statement
+ /// associated with the connection. The @c in_bindings size must match
+ /// the number of placeholders in the prepared statement.
+ ///
+ /// @param statement reference to the precompiled tagged statement to execute
+ /// @param in_bindings Input bindings holding values to substitute placeholders
+ /// in the query.
+ ///
+ /// @return Number of affected rows.
+ uint64_t updateDeleteQuery(PgSqlTaggedStatement& statement,
+ const PsqlBindArray& in_bindings);
+
+ /// @brief PgSql connection handle
+ ///
+ /// This field is public, because it is used heavily from PgSqlLeaseMgr
+ /// and from PgSqlHostDataSource.
+ PgSqlHolder conn_;
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the PgConnection object to be passed as the context argument to
+ /// PQxxxx functions.
+ operator PGconn*() const {
+ return (conn_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the PgConnection for initialized connection
+ operator bool() const {
+ return (conn_);
+ }
+
+private:
+
+ /// @brief Convenience function parsing and setting an integer parameter,
+ /// if it exists.
+ ///
+ /// If the parameter is not present, this function doesn't change the @c value.
+ /// Otherwise, it tries to convert the parameter to the type @c T. Finally,
+ /// it checks if the converted number is within the specified range.
+ ///
+ /// @param name Parameter name.
+ /// @param min Expected minimal value.
+ /// @param max Expected maximal value.
+ /// @param [out] value Reference to a value returning the parsed parameter.
+ /// @tparam T Parameter type.
+ /// @throw BadValue if the parameter is not a valid number or if it is out
+ /// of range.
+ template<typename T>
+ void setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value);
+
+public:
+
+ /// @brief Accessor function which returns the IOService that can be used to
+ /// recover the connection.
+ ///
+ /// This accessor is used to lazy retrieve the IOService when the connection
+ /// is lost. It is useful to retrieve it at a later time to support hook
+ /// libraries which create managers on load and set IOService later on by
+ /// using the dhcp4_srv_configured and dhcp6_srv_configured hooks.
+ IOServiceAccessorPtr io_service_accessor_;
+
+ /// @brief IOService object, used for all ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+
+ /// @brief Reference counter for transactions.
+ ///
+ /// It precludes starting and committing nested transactions. PostgreSQL
+ /// logs but ignores START TRANSACTIONs (or BEGINs) issued from within an
+ /// ongoing transaction. We do not want to start new transactions when one
+ /// is already in progress.
+ int transaction_ref_count_;
+};
+
+/// @brief Defines a pointer to a PgSqlConnection
+typedef boost::shared_ptr<PgSqlConnection> PgSqlConnectionPtr;
+
+} // end of isc::db namespace
+} // end of isc namespace
+
+#endif // PGSQL_CONNECTION_H
diff --git a/src/lib/pgsql/pgsql_exchange.cc b/src/lib/pgsql/pgsql_exchange.cc
new file mode 100644
index 0000000..d203714
--- /dev/null
+++ b/src/lib/pgsql/pgsql_exchange.cc
@@ -0,0 +1,767 @@
+// Copyright (C) 2016-2022 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 <pgsql/pgsql_exchange.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <vector>
+
+using namespace isc::util;
+using namespace isc::data;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace db {
+
+const int PsqlBindArray::TEXT_FMT = 0;
+const int PsqlBindArray::BINARY_FMT = 1;
+const char* PsqlBindArray::TRUE_STR = "TRUE";
+const char* PsqlBindArray::FALSE_STR = "FALSE";
+
+void PsqlBindArray::add(const char* value) {
+ if (!value) {
+ isc_throw(BadValue, "PsqlBindArray::add - char* value cannot be NULL");
+ }
+
+ values_.push_back(value);
+ lengths_.push_back(strlen(value));
+ formats_.push_back(TEXT_FMT);
+}
+
+void PsqlBindArray::add(const std::string& value) {
+ values_.push_back(value.c_str());
+ lengths_.push_back(value.size());
+ formats_.push_back(TEXT_FMT);
+}
+
+void PsqlBindArray::insert(const char* value, size_t index) {
+ if (index && index >= values_.size()) {
+ isc_throw(OutOfRange, "PsqlBindArray::insert - index: " << index
+ << ", is larger than the array size: " << values_.size());
+ }
+
+ values_.insert(values_.begin() + index, value);
+ lengths_.insert(lengths_.begin() + index, strlen(value));
+ formats_.insert(formats_.begin() + index, TEXT_FMT);
+}
+
+void PsqlBindArray::insert(const std::string& value, size_t index) {
+ if (index && index >= values_.size()) {
+ isc_throw(OutOfRange, "PsqlBindArray::insert - index: " << index
+ << ", is larger than the array size: " << values_.size());
+ }
+
+ bound_strs_.push_back(ConstStringPtr(new std::string(value)));
+
+ values_.insert(values_.begin() + index, bound_strs_.back()->c_str());
+ lengths_.insert(lengths_.begin() + index, value.size());
+ formats_.insert(formats_.begin() + index, TEXT_FMT);
+}
+
+void PsqlBindArray::popBack() {
+ if (values_.size() == 0) {
+ isc_throw(OutOfRange, "PsqlBindArray::pop_back - array empty");
+ }
+
+ values_.erase(values_.end() - 1);
+ lengths_.erase(lengths_.end() - 1);
+ formats_.erase(formats_.end() - 1);
+}
+
+void PsqlBindArray::add(const std::vector<uint8_t>& data) {
+ values_.push_back(reinterpret_cast<const char*>(&(data[0])));
+ lengths_.push_back(data.size());
+ formats_.push_back(BINARY_FMT);
+}
+
+void PsqlBindArray::addTempBinary(const std::vector<uint8_t>& data) {
+ bound_strs_.push_back(ConstStringPtr(new std::string(
+ reinterpret_cast<const char*>(data.data()), data.size())));
+
+ values_.push_back(reinterpret_cast<const char*>(bound_strs_.back()->data()));
+ lengths_.push_back(data.size());
+ formats_.push_back(BINARY_FMT);
+}
+
+void PsqlBindArray::add(const uint8_t* data, const size_t len) {
+ if (!data) {
+ isc_throw(BadValue, "PsqlBindArray::add - uint8_t data cannot be NULL");
+ }
+
+ values_.push_back(reinterpret_cast<const char*>(&(data[0])));
+ lengths_.push_back(len);
+ formats_.push_back(BINARY_FMT);
+}
+
+void PsqlBindArray::addTempBuffer(const uint8_t* data, const size_t len) {
+ if (!data) {
+ isc_throw(BadValue, "PsqlBindArray::addTempBuffer - uint8_t data cannot be NULL");
+ }
+
+ bound_strs_.push_back(ConstStringPtr(new std::string(
+ reinterpret_cast<const char*>(data), len)));
+
+ values_.push_back(bound_strs_.back()->data());
+ lengths_.push_back(len);
+ formats_.push_back(BINARY_FMT);
+}
+
+void PsqlBindArray::add(const bool& value) {
+ add(value ? TRUE_STR : FALSE_STR);
+}
+
+void PsqlBindArray::add(const uint8_t& byte) {
+ // We static_cast to an unsigned int, otherwise lexical_cast may to
+ // treat byte as a character, which yields "" for unprintable values
+ addTempString(boost::lexical_cast<std::string>
+ (static_cast<unsigned int>(byte)));
+}
+
+void PsqlBindArray::add(const isc::asiolink::IOAddress& addr) {
+ if (addr.isV4()) {
+ addTempString(boost::lexical_cast<std::string>
+ (addr.toUint32()));
+ } else {
+ addTempString(addr.toText());
+ }
+}
+
+void PsqlBindArray::addNull(const int format) {
+ values_.push_back(NULL);
+ lengths_.push_back(0);
+ formats_.push_back(format);
+}
+
+void
+PsqlBindArray::add(const Triplet<uint32_t>& triplet) {
+ if (triplet.unspecified()) {
+ addNull();
+ } else {
+ add<uint32_t>(triplet.get());
+ }
+}
+
+void
+PsqlBindArray::addMin(const Triplet<uint32_t>& triplet) {
+ if (triplet.unspecified() || (triplet.getMin() == triplet.get())) {
+ addNull();
+ } else {
+ add<uint32_t>(triplet.getMin());
+ }
+}
+
+void
+PsqlBindArray::addMax(const Triplet<uint32_t>& triplet) {
+ if (triplet.unspecified() || (triplet.getMax() == triplet.get())) {
+ addNull();
+ } else {
+ add<uint32_t>(triplet.getMax());
+ }
+}
+
+/// @todo Eventually this could replace add(std::string&)? This would mean
+/// all bound strings would be internally copies rather than perhaps belonging
+/// to the originating object such as Host::hostname_. One the one hand it
+/// would make all strings handled one-way only, on the other hand it would
+/// mean duplicating strings where it isn't strictly necessary.
+void PsqlBindArray::addTempString(const std::string& str) {
+ bound_strs_.push_back(ConstStringPtr(new std::string(str)));
+
+ PsqlBindArray::add((bound_strs_.back())->c_str());
+}
+
+void
+PsqlBindArray::addOptional(const util::Optional<std::string>& value) {
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ addTempString(value);
+ }
+}
+
+void
+PsqlBindArray::addInet4(const isc::asiolink::IOAddress& value) {
+ if (!value.isV4()) {
+ isc_throw(BadValue, "unable to add address to PsqlBindAray '"
+ << value.toText() << "' is not an IPv4 address");
+ }
+
+ // inet columns are inserted as string addresses.
+ addTempString(value.toText());
+}
+
+void
+PsqlBindArray::addOptionalInet4(const util::Optional<isc::asiolink::IOAddress>& value) {
+ // If the value is unspecified it doesn't matter what the value is.
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ addInet4(value);
+ }
+}
+
+void
+PsqlBindArray::addInet6(const isc::asiolink::IOAddress& value) {
+ if (!value.isV6()) {
+ isc_throw(BadValue, "unable to add address to PsqlBindAray '"
+ << value.toText() << "' is not an IPv6 address");
+ }
+
+ // inet columns are inserted as string addresses.
+ addTempString(value.toText());
+}
+
+void
+PsqlBindArray::addOptionalInet6(const util::Optional<isc::asiolink::IOAddress>& value) {
+ // If the value is unspecified it doesn't matter what the value is.
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ addInet6(value);
+ }
+}
+
+void
+PsqlBindArray::addTimestamp(const boost::posix_time::ptime& timestamp) {
+ // Convert the ptime to time_t, then use the existing conversion
+ // function to make db time.
+ //
+ // Sadly boost::posix_time::to_time_t() was not added until 1.58,
+ // so do it ourselves.
+ ptime epoch(boost::gregorian::date(1970, 1, 1));
+ if (timestamp < epoch) {
+ isc_throw(isc::BadValue, "Time value is before the epoch");
+ }
+ ptime max_db_time = boost::posix_time::from_time_t(DatabaseConnection::MAX_DB_TIME);
+ time_duration::sec_type since_epoch = (timestamp - epoch).total_seconds();
+ time_t input_time(since_epoch);
+ if (timestamp > max_db_time) {
+ isc_throw(isc::BadValue, "Time value is too large: " <<
+ (input_time < 0 ?
+ static_cast<int64_t>(static_cast<uint32_t>(input_time)) :
+ input_time));
+ }
+
+ // Converts to timestamp to local date/time string.
+ addTempString(PgSqlExchange::convertLocalToDatabaseTime(input_time));
+}
+
+void
+PsqlBindArray::addTimestamp() {
+ time_t now;
+ time(&now);
+ addTempString(PgSqlExchange::convertLocalToDatabaseTime(now));
+}
+
+void
+PsqlBindArray::add(const ElementPtr& value) {
+ if (!value) {
+ addNull();
+ return;
+ }
+
+ std::ostringstream ss;
+ value->toJSON(ss);
+ addTempString(ss.str());
+}
+
+void
+PsqlBindArray::add(const ConstElementPtr& value) {
+ if (!value) {
+ addNull();
+ return;
+ }
+
+ std::ostringstream ss;
+ value->toJSON(ss);
+ addTempString(ss.str());
+}
+
+std::string
+PsqlBindArray::toText() const {
+ std::ostringstream stream;
+
+ if (values_.size() == 0) {
+ return ("bindarray is empty");
+ }
+
+ for (int i = 0; i < values_.size(); ++i) {
+ stream << i << " : ";
+
+ if (lengths_[i] == 0) {
+ stream << "empty" << std::endl;
+ continue;
+ }
+
+ if (formats_[i] == TEXT_FMT) {
+ stream << "\"" << values_[i] << "\"" << std::endl;
+ } else {
+ const char *data = values_[i];
+ stream << "0x";
+ for (int x = 0; x < lengths_[i]; ++x) {
+ stream << std::setfill('0') << std::setw(2)
+ << std::setbase(16)
+ << static_cast<unsigned int>(data[x]);
+ }
+
+ stream << std::endl << std::setbase(10);
+ }
+ }
+
+ return (stream.str());
+}
+
+bool
+PsqlBindArray::amNull(size_t index) const {
+ if (values_.size() < index + 1) {
+ isc_throw(OutOfRange, "The index " << index << " is larger than the "
+ " array size " << values_.size());
+ }
+
+ // We assume lengths_.size() always equals values_.size(). If not, the
+ // at() operator will throw.
+ return ( (values_.at(index) == NULL) && (lengths_.at(index) == 0) );
+}
+
+std::string
+PgSqlExchange::convertToDatabaseTime(const time_t input_time) {
+ struct tm tinfo;
+ char buffer[20];
+
+ localtime_r(&input_time, &tinfo);
+
+ // PostgreSQL will assume the value is already in local time since we
+ // do not specify timezone in the string.
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
+ return (std::string(buffer));
+}
+
+std::string
+PgSqlExchange::convertLocalToDatabaseTime(const time_t input_time) {
+ struct tm tinfo;
+ char buffer[20];
+
+ // We use gmtime_r to avoid adjustment as time_t is already local.
+ gmtime_r(&input_time, &tinfo);
+
+ // PostgreSQL will assume the value is already in local time since we
+ // do not specify timezone in the string.
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
+ return (std::string(buffer));
+}
+
+
+std::string
+PgSqlExchange::convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime) {
+ // Calculate expiry time. Store it in the 64-bit value so as we can
+ // detect overflows.
+ int64_t expire_time_64 = static_cast<int64_t>(cltt)
+ + static_cast<int64_t>(valid_lifetime);
+
+ // It has been observed that the PostgreSQL doesn't deal well with the
+ // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
+ // beginning of the epoch (around year 2038). The value is often
+ // stored in the database but it is invalid when read back (overflow?).
+ // Hence, the maximum timestamp value is restricted here.
+ if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
+ isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
+ }
+
+ return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
+}
+
+time_t
+PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
+ // Convert string time value to time_t
+ time_t new_time;
+ try {
+ new_time = (boost::lexical_cast<time_t>(db_time_val));
+ } catch (const std::exception& ex) {
+ isc_throw(BadValue, "Database time value is invalid: " << db_time_val);
+ }
+
+ return (new_time);
+}
+
+void
+PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val,
+ boost::posix_time::ptime& conv_time) {
+ time_t tmp_time = convertFromDatabaseTime(db_time_val);
+ conv_time = boost::posix_time::from_time_t(tmp_time);
+}
+
+const char*
+PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
+ const size_t col) {
+ r.rowColCheck(row,col);
+ const char* value = PQgetvalue(r, row, col);
+ if (!value) {
+ isc_throw(DbOperationError, "getRawColumnValue no data for :"
+ << getColumnLabel(r, col) << " row:" << row);
+ }
+ return (value);
+}
+
+bool
+PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
+ const size_t col) {
+ r.rowColCheck(row,col);
+ return (PQgetisnull(r, row, col));
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, std::string& value) {
+ value = getRawColumnValue(r, row, col);
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, bool &value) {
+ const char* data = getRawColumnValue(r, row, col);
+ if (!strlen(data) || *data == 'f') {
+ value = false;
+ } else if (*data == 't') {
+ value = true;
+ } else {
+ isc_throw(DbOperationError, "Invalid boolean data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : must be 't' or 'f'");
+ }
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, uint8_t &value) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ // lexically casting as uint8_t doesn't convert from char
+ // so we use uint16_t and implicitly convert.
+ value = boost::lexical_cast<uint16_t>(data);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Invalid uint8_t data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, boost::posix_time::ptime& value) {
+ std::string db_time_val;
+ PgSqlExchange::getColumnValue(r, row, col, db_time_val );
+ PgSqlExchange::convertFromDatabaseTime(db_time_val, value);
+}
+
+void
+PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, ElementPtr& value) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ value = Element::fromJSON(data);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Cannot convert data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+isc::asiolink::IOAddress
+PgSqlExchange::getInetValue4(const PgSqlResult& r, const int row,
+ const size_t col) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ asiolink::IOAddress addr(data);
+ if (!addr.isV4()) {
+ isc_throw(BadValue, "not a v4 address");
+ }
+
+ return (addr);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Cannot convert data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+isc::asiolink::IOAddress
+PgSqlExchange::getInetValue6(const PgSqlResult& r, const int row,
+ const size_t col) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ asiolink::IOAddress addr(data);
+ if (!addr.isV6()) {
+ isc_throw(BadValue, "not a v6 address");
+ }
+
+ return (addr);
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Cannot convert data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+isc::asiolink::IOAddress
+PgSqlExchange::getIPv6Value(const PgSqlResult& r, const int row,
+ const size_t col) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ return (isc::asiolink::IOAddress(data));
+ } catch (const std::exception& ex) {
+ isc_throw(DbOperationError, "Cannot convert data: " << data
+ << " for: " << getColumnLabel(r, col) << " row:" << row
+ << " : " << ex.what());
+ }
+}
+
+void
+PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
+ const size_t col, uint8_t* buffer,
+ const size_t buffer_size,
+ size_t &bytes_converted) {
+ // Returns converted bytes in a dynamically allocated buffer, and
+ // sets bytes_converted.
+ unsigned char* bytes = PQunescapeBytea((const unsigned char*)
+ (getRawColumnValue(r, row, col)),
+ &bytes_converted);
+
+ // Unlikely it couldn't allocate it but you never know.
+ if (!bytes) {
+ isc_throw (DbOperationError, "PQunescapeBytea failed for:"
+ << getColumnLabel(r, col) << " row:" << row);
+ }
+
+ // Make sure it's not larger than expected.
+ if (bytes_converted > buffer_size) {
+ // Free the allocated buffer first!
+ PQfreemem(bytes);
+ isc_throw (DbOperationError, "Converted data size: "
+ << bytes_converted << " is too large for: "
+ << getColumnLabel(r, col) << " row:" << row);
+ }
+
+ // Copy from the allocated buffer to caller's buffer then free
+ // the allocated buffer.
+ memcpy(buffer, bytes, bytes_converted);
+ PQfreemem(bytes);
+}
+
+void
+PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row, const size_t col,
+ std::vector<uint8_t>& value) {
+ // Returns converted bytes in a dynamically allocated buffer, and
+ // sets bytes_converted.
+ size_t bytes_converted = 0;
+ unsigned char* bytes = PQunescapeBytea((const unsigned char*)
+ (getRawColumnValue(r, row, col)),
+ &bytes_converted);
+
+ // Unlikely it couldn't allocate it but you never know.
+ if (!bytes) {
+ isc_throw (DbOperationError, "PQunescapeBytea failed for:"
+ << getColumnLabel(r, col) << " row:" << row);
+ }
+
+ // Copy from the allocated buffer to caller's buffer then free
+ // the allocated buffer.
+ if (bytes_converted) {
+ value.assign(bytes, bytes + bytes_converted);
+ } else {
+ value.clear();
+ }
+
+ // Free the PostgreSQL buffer.
+ PQfreemem(bytes);
+}
+
+Triplet<uint32_t>
+PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row,
+ const size_t col) {
+ uint32_t col_value;
+ if (isColumnNull(r, row, col)) {
+ return (Triplet<uint32_t>());
+ }
+
+ getColumnValue(r, row, col, col_value);
+ return (Triplet<uint32_t>(col_value));
+}
+
+Triplet<uint32_t>
+PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row,
+ const size_t def_col, const size_t min_col,
+ const size_t max_col) {
+ if (isColumnNull(r, row, def_col)) {
+ return (Triplet<uint32_t>());
+ }
+
+ uint32_t value;
+ getColumnValue(r, row, def_col, value);
+
+ uint32_t min_value = value;
+ if (!isColumnNull(r, row, min_col)) {
+ getColumnValue(r, row, min_col, min_value);
+ }
+
+ uint32_t max_value = value;
+ if (!isColumnNull(r, row, max_col)) {
+ getColumnValue(r, row, max_col, max_value);
+ }
+
+ return (Triplet<uint32_t>(min_value, value, max_value));
+}
+
+std::string
+PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
+ return (r.getColumnLabel(column));
+}
+
+std::string
+PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
+ r.rowCheck(row);
+ std::ostringstream stream;
+ int columns = r.getCols();
+ for (int col = 0; col < columns; ++col) {
+ const char* val = getRawColumnValue(r, row, col);
+ std::string name = r.getColumnLabel(col);
+ int format = PQfformat(r, col);
+
+ stream << col << " " << name << " : " ;
+ if (format == PsqlBindArray::TEXT_FMT) {
+ stream << "\"" << val << "\"" << std::endl;
+ } else {
+ const char *data = val;
+ int length = PQfsize(r, col);
+ if (length == 0) {
+ stream << "empty" << std::endl;
+ } else {
+ stream << "0x";
+ for (int i = 0; i < length; ++i) {
+ stream << std::setfill('0') << std::setw(2)
+ << std::setbase(16)
+ << static_cast<unsigned int>(data[i]);
+ }
+ stream << std::endl;
+ }
+ }
+ }
+
+ return (stream.str());
+}
+
+PgSqlResultRowWorker::PgSqlResultRowWorker(const PgSqlResult& r, const int row)
+ : r_(r), row_(row) {
+ // Validate the desired row.
+ r.rowCheck(row);
+}
+
+bool
+PgSqlResultRowWorker::isColumnNull(const size_t col) {
+ return (PgSqlExchange::isColumnNull(r_, row_, col));
+}
+
+std::string
+PgSqlResultRowWorker::getString(const size_t col) {
+ std::string tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+bool
+PgSqlResultRowWorker::getBool(const size_t col) {
+ bool tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+double
+PgSqlResultRowWorker::getDouble(const size_t col) {
+ double tmp;
+ PgSqlExchange::getColumnValue(r_, row_, col, tmp);
+ return (tmp);
+}
+
+const char*
+PgSqlResultRowWorker::getRawColumnValue(const size_t col) {
+ return (PgSqlExchange::getRawColumnValue(r_, row_, col));
+}
+
+uint64_t
+PgSqlResultRowWorker::getBigInt(const size_t col) {
+ uint64_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+uint32_t
+PgSqlResultRowWorker::getInt(const size_t col) {
+ uint32_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+uint16_t
+PgSqlResultRowWorker::getSmallInt(const size_t col) {
+ uint16_t value;
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ return (value);
+}
+
+void
+PgSqlResultRowWorker::getBytes(const size_t col, std::vector<uint8_t>& value) {
+ PgSqlExchange::convertFromBytea(r_, row_, col, value);
+}
+
+isc::asiolink::IOAddress
+PgSqlResultRowWorker::getInet4(const size_t col) {
+ return (PgSqlExchange::getInetValue4(r_, row_, col));
+}
+
+isc::asiolink::IOAddress
+PgSqlResultRowWorker::getInet6(const size_t col) {
+ return (PgSqlExchange::getInetValue6(r_, row_, col));
+}
+
+boost::posix_time::ptime
+PgSqlResultRowWorker::getTimestamp(const size_t col) {
+ boost::posix_time::ptime value;
+ getColumnValue(col, value);
+ return (value);
+};
+
+data::ElementPtr
+PgSqlResultRowWorker::getJSON(const size_t col) {
+ data::ElementPtr value;
+ getColumnValue(col, value);
+ return (value);
+}
+
+isc::util::Triplet<uint32_t>
+PgSqlResultRowWorker::getTriplet(const size_t col) {
+ return (PgSqlExchange::getTripletValue(r_, row_, col));
+}
+
+isc::util::Triplet<uint32_t>
+PgSqlResultRowWorker::getTriplet(const size_t def_col, const size_t min_col,
+ const size_t max_col) {
+ return (PgSqlExchange::getTripletValue(r_, row_, def_col, min_col, max_col));
+}
+
+std::string
+PgSqlResultRowWorker::dumpRow() {
+ return (PgSqlExchange::dumpRow(r_, row_));
+}
+
+} // end of isc::db namespace
+} // end of isc namespace
diff --git a/src/lib/pgsql/pgsql_exchange.h b/src/lib/pgsql/pgsql_exchange.h
new file mode 100644
index 0000000..22ec7af
--- /dev/null
+++ b/src/lib/pgsql/pgsql_exchange.h
@@ -0,0 +1,1004 @@
+// Copyright (C) 2016-2022 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 PGSQL_EXCHANGE_H
+#define PGSQL_EXCHANGE_H
+
+#include <asiolink/io_address.h>
+#include <database/database_connection.h>
+#include <cc/data.h>
+#include <util/triplet.h>
+#include <util/boost_time_utils.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <libpq-fe.h>
+
+#include <stdint.h>
+#include <vector>
+#include <iostream>
+
+namespace isc {
+namespace db {
+
+/// @brief RAII wrapper for PostgreSQL Result sets
+///
+/// When a Postgresql statement is executed, the results are returned
+/// in a pointer allocated structure, PGresult*. Data and status information
+/// are accessed via calls to functions such as PQgetvalue() which require
+/// the results pointer. In order to ensure this structure is freed, any
+/// invocation of Psql function which returns a PGresult* (e.g. PQexec,
+/// PQExecPrepared, ...) must save the result to an instance of this
+/// class. Example:
+/// {{{
+/// PgSqlResult r(PQexec(conn_, "ROLLBACK"));
+/// }}}
+///
+/// This eliminates the need for an explicit release via, PQclear() and
+/// guarantees that the resources are released even if the an exception is
+/// thrown.
+
+class PgSqlResult : public boost::noncopyable {
+public:
+ /// @brief Constructor
+ ///
+ /// Store the pointer to the fetched result set. Set row and column
+ /// counts for convenience.
+ ///
+ /// @param result pointer to the Postgresql client layer result
+ /// If the value of is NULL, row and col values will be set to -1.
+ /// This allows PgSqlResult to be passed into statement error
+ /// checking.
+ PgSqlResult(PGresult *result);
+
+ /// @brief Destructor
+ ///
+ /// Frees the result set
+ ~PgSqlResult();
+
+ /// @brief Returns the number of rows in the result set.
+ int getRows() const {
+ return (rows_);
+ }
+
+ /// @brief Returns the number of columns in the result set.
+ int getCols() const {
+ return (cols_);
+ }
+
+ /// @brief Determines if a row index is valid
+ ///
+ /// @param row index to range check
+ ///
+ /// @throw DbOperationError if the row index is out of range
+ void rowCheck(int row) const;
+
+ /// @brief Determines if a column index is valid
+ ///
+ /// @param col index to range check
+ ///
+ /// @throw DbOperationError if the column index is out of range
+ void colCheck(int col) const;
+
+ /// @brief Determines if both a row and column index are valid
+ ///
+ /// @param row index to range check
+ /// @param col index to range check
+ ///
+ /// @throw DbOperationError if either the row or column index
+ /// is out of range
+ void rowColCheck(int row, int col) const;
+
+ /// @brief Fetches the name of the column in a result set
+ ///
+ /// Returns the column name of the column from the result set.
+ /// If the column index is out of range it will return the
+ /// string "Unknown column:<index>"
+ ///
+ /// @param col index of the column name to fetch
+ /// @return string containing the name of the column
+ /// This method is exception safe.
+ std::string getColumnLabel(const int col) const;
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the PgSqlResult object to be passed as the result set argument to
+ /// PQxxxx functions.
+ operator PGresult*() const {
+ return (result_);
+ }
+
+ /// @brief Boolean Operator
+ ///
+ /// Allows testing the PgSqlResult object for emptiness: "if (result)"
+ operator bool() const {
+ return (result_);
+ }
+
+private:
+ PGresult* result_; ///< Result set to be freed
+ int rows_; ///< Number of rows in the result set
+ int cols_; ///< Number of columns in the result set
+};
+
+typedef boost::shared_ptr<PgSqlResult> PgSqlResultPtr;
+
+/// @brief Structure used to bind C++ input values to dynamic SQL parameters
+/// The structure contains three vectors which store the input values,
+/// data lengths, and formats. These vectors are passed directly into the
+/// PostgreSQL execute call.
+///
+/// Note that the data values are stored as pointers. These pointers need to
+/// be valid for the duration of the PostgreSQL statement execution. In other
+/// words populating them with pointers to values that go out of scope before
+/// statement is executed is a bad idea.
+///
+/// Other than vectors or buffers of binary data, all other values are currently
+/// converted to their string representation prior to sending them to PostgreSQL.
+/// All of the add() method variants which accept a non-string value internally
+/// create the conversion string which is then retained in the bind array to ensure
+/// scope.
+///
+/// @brief smart pointer to const std::strings used by PsqlBindArray to ensure scope
+/// of strings supplying exchange values
+typedef boost::shared_ptr<const std::string> ConstStringPtr;
+
+struct PsqlBindArray {
+ /// @brief Constructor.
+ PsqlBindArray() : values_(0), lengths_(0), formats_(0) {}
+
+ /// @brief Vector of pointers to the data values.
+ std::vector<const char*> values_;
+
+ /// @brief Vector of data lengths for each value.
+ std::vector<int> lengths_;
+
+ /// @brief Vector of "format" for each value. A value of 0 means the
+ /// value is text, 1 means the value is binary.
+ std::vector<int> formats_;
+
+ /// @brief Format value for text data.
+ static const int TEXT_FMT;
+
+ /// @brief Format value for binary data.
+ static const int BINARY_FMT;
+
+ /// @brief Constant string passed to DB for boolean true values.
+ static const char* TRUE_STR;
+
+ /// @brief Constant string passed to DB for boolean false values.
+ static const char* FALSE_STR;
+
+ /// @brief Fetches the number of entries in the array.
+ /// @return Returns size_t containing the number of entries.
+ size_t size() const {
+ return (values_.size());
+ }
+
+ /// @brief Indicates it the array is empty.
+ /// @return Returns true if there are no entries in the array, false
+ /// otherwise.
+ bool empty() const {
+ return (values_.empty());
+ }
+
+ /// @brief Adds a char array to bind array based
+ ///
+ /// Adds a TEXT_FMT value to the end of the bind array, using the given
+ /// char* as the data source. The value is expected to be NULL
+ /// terminated. The caller is responsible for ensuring that value
+ /// remains in scope until the bind array has been discarded.
+ ///
+ /// @param value char array containing the null-terminated text to add.
+ /// @throw DbOperationError if value is NULL.
+ void add(const char* value);
+
+ /// @brief Adds a string value to the bind array
+ ///
+ /// Adds a TEXT formatted value to the end of the bind array using the
+ /// given string as the data source. The caller is responsible for
+ /// ensuring that string parameter remains in scope until the bind
+ /// array has been discarded.
+ ///
+ /// @param value std::string containing the value to add.
+ void add(const std::string& value);
+
+ /// @brief Inserts a string value to the bind array before the given index
+ ///
+ /// Inserts a TEXT_FMT value into the bind array before the element
+ /// position given by index, using the given char* as the data source.
+ /// The value is expected to be NULL terminated. The caller is responsible
+ /// for ensuring that value remains in scope until the bind array has been
+ /// discarded.
+ ///
+ /// @param value char array containing the null-terminated text to add.
+ /// @param index element position before which the string should be inserted.
+ ///
+ /// @throw DbOperationError if value is NULL.
+ /// @throw OutOfRange if the index is beyond the end of the array.
+ void insert(const char* value, size_t index);
+
+ /// @brief Inserts a string value to the bind array before the given index
+ ///
+ /// Inserts a TEXT_FMT value into the bind array before the element
+ /// position given by index, using the given given string as the data
+ /// source. This creates an internally scoped copy of the string.
+ ///
+ /// @param value char array containing the null-terminated text to add.
+ /// @param index element position before which the string should be
+ /// inserted.
+ ///
+ /// @throw DbOperationError if value is NULL.
+ /// @throw OutOfRange if the index is beyond the end of the array.
+ void insert(const std::string& value, size_t index);
+
+ /// @brief Removes the last entry in the bind array.
+ ///
+ /// @throw OutOfRange if array is empty.
+ void popBack();
+
+ /// @brief Adds a vector of binary data to the bind array.
+ ///
+ /// Adds a BINARY_FMT value to the end of the bind array using the
+ /// given vector as the data source. NOTE this does not replicate
+ /// the vector, so it must remain in scope until the bind array
+ /// is destroyed.
+ ///
+ /// @param data vector of binary bytes.
+ void add(const std::vector<uint8_t>& data);
+
+ /// @brief Adds a vector of binary data to the bind array.
+ ///
+ /// Adds a BINARY_FMT value to the end of the bind array using the
+ /// given vector as the data source. This creates an internally scoped
+ /// copy of the vector.
+ ///
+ /// @param data vector of binary bytes.
+ void addTempBinary(const std::vector<uint8_t>& data);
+
+ /// @brief Adds a buffer of binary data to the bind array.
+ ///
+ /// Adds a BINARY_FMT value to the end of the bind array using the
+ /// given vector as the data source. NOTE this does not replicate
+ /// the buffer, so it must remain in scope until the bind array
+ /// is destroyed.
+ ///
+ /// @param data buffer of binary data.
+ /// @param len number of bytes of data in buffer
+ /// @throw DbOperationError if data is NULL.
+ void add(const uint8_t* data, const size_t len);
+
+ /// @brief Adds a temporary buffer of binary data to the bind array.
+ ///
+ /// Adds a BINARY_FMT value to the end of the bind array using the
+ /// given vector as the data source.
+ /// Prior to adding the buffer, it is duplicated as a ConstStringPtr
+ /// and saved internally. This guarantees the buffer remains in scope
+ /// until the PsqlBindArray is destroyed, without the caller maintaining
+ /// the buffer values.
+ ///
+ /// @param data buffer of binary data.
+ /// @param len number of bytes of data in buffer
+ /// @throw DbOperationError if data is NULL.
+ void addTempBuffer(const uint8_t* data, const size_t len);
+
+ /// @brief Adds a boolean value to the bind array.
+ ///
+ /// Converts the given boolean value to its corresponding to PostgreSQL
+ /// string value and adds it as a TEXT_FMT value to the bind array.
+ /// This creates an internally scoped string.
+ ///
+ /// @param value the boolean value to add.
+ void add(const bool& value);
+
+ /// @brief Adds a uint8_t value to the bind array.
+ ///
+ /// Converts the given uint8_t value to its corresponding numeric string
+ /// literal and adds it as a TEXT_FMT value to the bind array.
+ /// This creates an internally scoped string.
+ ///
+ /// @param byte the one byte value to add.
+ void add(const uint8_t& byte);
+
+ /// @brief Adds the given IOAddress value to the bind array.
+ ///
+ /// Converts the IOAddress, based on its protocol family, to the
+ /// corresponding string literal and adds it as a TEXT_FMT value to
+ /// the bind array.
+ /// This creates an internally scoped string.
+ ///
+ /// @param addr IP address value to add.
+ void add(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Adds the given value to the bind array.
+ ///
+ /// Converts the given value to its corresponding string literal
+ /// boost::lexical_cast and adds it as a TEXT_FMT value to the bind array.
+ /// This is intended primarily for numeric types.
+ /// This creates an internally scoped string.
+ ///
+ /// @param value data value to add.
+ template<typename T>
+ void add(const T& value) {
+ addTempString(boost::lexical_cast<std::string>(value));
+ }
+
+ /// @brief Binds the given string to the bind array.
+ ///
+ /// Prior to add the given string the vector of exchange values,
+ /// it duplicated as a ConstStringPtr and saved internally. This guarantees
+ /// the string remains in scope until the PsqlBindArray is destroyed,
+ /// without the caller maintaining the string values.
+ ///
+ /// @param str string value to add.
+ void addTempString(const std::string& str);
+
+ /// @brief Adds a NULL value to the bind array
+ ///
+ /// This should be used whenever the value for a parameter specified
+ /// in the SQL statement should be NULL.
+ void addNull(const int format = PsqlBindArray::TEXT_FMT);
+
+ /// @brief Adds an integer Triplet's value to the bind array
+ ///
+ /// Stores the current value of a triplet to the bind array.
+ /// If it is unspecified it stores a NULL.
+ ///
+ /// @param triplet Triplet instance from which to get the value.
+ void add(const isc::util::Triplet<uint32_t>& triplet);
+
+ /// @brief Adds an integer Triplet's minimum value to the bind array
+ ///
+ /// Stores the minimum value of a triplet to the bind array.
+ /// If it is unspecified it stores a NULL.
+ ///
+ /// @param triplet Triplet instance from which to get the value.
+ void addMin(const isc::util::Triplet<uint32_t>& triplet);
+
+ /// @brief Adds an integer Triplet's maximum value to the bind array
+ ///
+ /// Stores the maximum value of a triplet to the bind array.
+ /// If it is unspecified it stores a NULL.
+ ///
+ /// @param triplet Triplet instance from which to get the value.
+ void addMax(const isc::util::Triplet<uint32_t>& triplet);
+
+ /// @brief Adds an @c Optional string to the bind array.
+ ///
+ /// Optional strings require adding a temp string to the
+ /// bind array, unlike other types which implicitly do so.
+ ///
+ /// @param value Optional string value to add
+ void addOptional(const util::Optional<std::string>& value);
+
+ /// @brief Adds an @c Optional<type> value to the bind array.
+ ///
+ /// @tparam T variable type corresponding to the binding type, e.g.
+ /// @c string, bool, uint8_t, @c uint16_t etc.
+ /// @param value Optional of type T.
+ template<typename T>
+ void addOptional(const util::Optional<T>& value) {
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ add(value);
+ }
+ }
+
+ /// @brief Adds an IPv4 address to the bind array.
+ ///
+ /// This is used for inet type columns.
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv4 address.
+ void addInet4(const isc::asiolink::IOAddress& value);
+
+ /// @brief Adds an IPv6 address to the bind array.
+ ///
+ /// This is used for inet type columns.
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv6 address.
+ void addInet6(const isc::asiolink::IOAddress& value);
+
+ /// @brief Adds an @c Optional IPv4 address to the bind array.
+ ///
+ /// This is used for inet type columns.
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv4 address.
+ void addOptionalInet4(const util::Optional<isc::asiolink::IOAddress>& value);
+
+ /// @brief Adds an @c Optional IPv6 address to the bind array.
+ ///
+ /// This is used for inet type columns which expect
+ /// v4 addresses to be inserted in string form:
+ /// '3001::1'
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv6 address.
+ void addOptionalInet6(const util::Optional<isc::asiolink::IOAddress>& value);
+
+
+ /// @brief Adds a timestamp from a ptime to the bind array.
+ ///
+ /// Precision is seconds.
+ ///
+ /// @param timestamp Timestamp value to be sent to the database.
+ /// @throw BadValue if the timestamp exceeds DatabaseConnection::MAX_DB_TIME.
+ void addTimestamp(const boost::posix_time::ptime& timestamp);
+
+ /// @brief Adds a timestamp of the current time to the bind array.
+ ///
+ /// Precision is seconds.
+ void addTimestamp();
+
+ /// @brief Adds an ElementPtr to the bind array
+ ///
+ /// Adds a TEXT_FMT value to the end of the bind array containing
+ /// the JSON text output by given ElementPtr::toJSON().
+ ///
+ /// @param value ElementPtr containing Element tree to add.
+ /// @throw DbOperationError if value is NULL.
+ void add(const data::ElementPtr& value);
+
+ /// @brief Adds a ConstElementPtr to the bind array
+ ///
+ /// Adds a TEXT_FMT value to the end of the bind array containing
+ /// the JSON text output by given ElementPtr::toJSON().
+ ///
+ /// @param value ElementPtr containing Element tree to add.
+ /// @throw DbOperationError if value is NULL.
+ void add(const data::ConstElementPtr& value);
+
+ /// @brief Dumps the contents of the array to a string.
+ /// @return std::string containing the dump
+ std::string toText() const;
+
+ // --- the following methods are mostly useful for testing -----
+
+ /// @brief Determines if specified value is null
+ /// @param index if array holds more than one value, this index determines
+ /// which column to use
+ /// @return true if the column is defined and is null
+ bool amNull(size_t index = 0) const;
+
+ /// @brief Returns the value as an integer.
+ /// @param index if array holds more than one value, this index determines
+ /// which column to use
+ /// @return value interpreted as specified integer type
+ /// @throw OutOfRange if the offset is too large
+ /// @throw BadValue if the data is null
+ /// @throw boost::bad_lexical_cast if value is not an integer
+ template<typename T>
+ T getInteger(size_t index = 0) {
+ if (values_.size() < index + 1) {
+ isc_throw(OutOfRange, "Invalid index " << index << ", the values array has "
+ << values_.size() << " element(s)");
+ }
+ auto x = values_.at(index);
+ if (!x) {
+ isc_throw(BadValue, "the data in column " << index << " is null");
+ }
+ return (boost::lexical_cast<T>(x));
+ }
+
+ /// @brief Returns the column type
+ /// @param index if array holds more than one value, this index determines
+ /// which column to use
+ /// @return the type of specified column
+ /// @throw BadValue if the offset is too large
+ int getType(size_t index = 0 ) {
+ if (formats_.size() < index + 1) {
+ isc_throw(OutOfRange, "Invalid index " << index << ", the formats_ array has "
+ << formats_.size() << " element(s)");
+ }
+ return (formats_.at(index));
+ }
+
+private:
+ /// @brief vector of strings which supplied the values
+ std::vector<ConstStringPtr> bound_strs_;
+};
+
+/// @brief Defines a smart pointer to PsqlBindArray
+typedef boost::shared_ptr<PsqlBindArray> PsqlBindArrayPtr;
+
+/// @brief Base class for marshalling data to and from PostgreSQL.
+///
+/// Provides the common functionality to set up binding information between
+/// application objects in the program and their representation in the
+/// database, and for retrieving column values from rows of a result set.
+class PgSqlExchange {
+public:
+ /// @brief Constructor
+ PgSqlExchange(const size_t num_columns = 0) : columns_(num_columns) {}
+
+ /// @brief Destructor
+ virtual ~PgSqlExchange() {}
+
+ /// @brief Converts UTC time_t value to a text representation in local time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @return std::string containing stringified time.
+ static std::string convertToDatabaseTime(const time_t input_time);
+
+ /// @brief Converts local time_t value to a text representation in local time.
+ ///
+ /// @param input_time A time_t value representing time.
+ /// @return std::string containing stringified time.
+ static std::string convertLocalToDatabaseTime(const time_t input_time);
+
+ /// @brief Converts lease expiration time to a text representation in
+ /// local time.
+ ///
+ /// The expiration time is calculated as a sum of the cltt (client last
+ /// transmit time) and the valid lifetime.
+ ///
+ /// The format of the output string is "%Y-%m-%d %H:%M:%S". Database
+ /// table columns using this value should be typed as TIMESTAMP WITH
+ /// TIME ZONE. For such columns PostgreSQL assumes input strings without
+ /// timezones should be treated as in local time and are converted to UTC
+ /// when stored. Likewise, these columns are automatically adjusted
+ /// upon retrieval unless fetched via "extract(epoch from <column>))".
+ ///
+ /// Unless we start using binary input, timestamp columns must be input as
+ /// date/time strings.
+ ///
+ /// @param cltt Client last transmit time
+ /// @param valid_lifetime Valid lifetime
+ ///
+ /// @return std::string containing the stringified time
+ /// @throw isc::BadValue if the sum of the calculated expiration time is
+ /// greater than the value of @c DataSource::MAX_DB_TIME.
+ static std::string convertToDatabaseTime(const time_t cltt,
+ const uint32_t valid_lifetime);
+
+ /// @brief Converts time stamp from the database to a time_t
+ ///
+ /// We're fetching timestamps as an integer string of seconds since the
+ /// epoch. This method converts such a string to a time_t.
+ ///
+ /// @param db_time_val timestamp to be converted. This value
+ /// is expected to be the number of seconds since the epoch
+ /// expressed as base-10 integer string.
+ /// @return Converted timestamp as time_t value.
+ static time_t convertFromDatabaseTime(const std::string& db_time_val);
+
+ /// @brief Converts time stamp from the database to a boost::posix::ptime
+ ///
+ /// We're fetching timestamps as an integer string of seconds since the
+ /// epoch. This method converts such a string to a boost::posix_time::ptime.
+ ///
+ /// @param db_time_val timestamp to be converted. This value
+ /// is expected to be the number of seconds since the epoch
+ /// expressed as base-10 integer string.
+ /// @param[out] conv_time resulting time as a ptime (UTC)
+ static void convertFromDatabaseTime(const std::string& db_time_val,
+ boost::posix_time::ptime& conv_time);
+
+ /// @brief Gets a pointer to the raw column value in a result set row
+ ///
+ /// Given a result set, row, and column return a const char* pointer to
+ /// the data value in the result set. The pointer is valid as long as
+ /// the result set has not been freed. It may point to text or binary
+ /// data depending on how query was structured. You should not attempt
+ /// to free this pointer.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ ///
+ /// @return a const char* pointer to the column's raw data
+ /// @throw DbOperationError if the value cannot be fetched.
+ static const char* getRawColumnValue(const PgSqlResult& r, const int row,
+ const size_t col);
+
+ /// @brief Fetches the name of the column in a result set
+ ///
+ /// Returns the column name of the column from the result set.
+ /// If the column index is out of range it will return the
+ /// string "Unknown column:<index>". Note this is NOT from the
+ /// list of columns defined in the exchange.
+ ///
+ /// @param r the result set containing the query results
+ /// @param col index of the column name to fetch
+ ///
+ /// @return string containing the name of the column
+ static std::string getColumnLabel(const PgSqlResult& r, const size_t col);
+
+ /// @brief Fetches text column value as a string
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value parameter to receive the string value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, std::string& value);
+
+ /// @brief Fetches boolean text ('t' or 'f') as a bool.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value parameter to receive the converted value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, bool &value);
+
+ /// @brief Fetches an integer text column as a uint8_t.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value parameter to receive the converted value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, uint8_t &value);
+
+ /// @brief Converts a column in a row in a result set into IPv4 address.
+ ///
+ /// This is used to fetch values from inet type columns.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv4 address.
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::asiolink::IOAddress getInetValue4(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Converts a column in a row in a result set into IPv6 address.
+ ///
+ /// This is used to fetch values from inet type columns.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv6 address.
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::asiolink::IOAddress getInetValue6(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Converts a column in a row in a result set into IPv6 address.
+ ///
+ /// This used for IPv6 columns stored as varchar.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv6 address.
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::asiolink::IOAddress getIPv6Value(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Returns true if a column within a row is null
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ ///
+ /// @return True if the column values in the row is NULL, false otherwise.
+ static bool isColumnNull(const PgSqlResult& r, const int row,
+ const size_t col);
+
+ /// @brief Fetches a text column as the given value type
+ ///
+ /// Uses boost::lexicalcast to convert the text column value into
+ /// a value of type T.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value parameter to receive the converted value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ template<typename T>
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, T& value) {
+ const char* data = getRawColumnValue(r, row, col);
+ try {
+ value = boost::lexical_cast<T>(data);
+ } catch (const std::exception& ex) {
+ isc_throw(db::DbOperationError, "Invalid data:[" << data
+ << "] for row: " << row << " col: " << col << ","
+ << getColumnLabel(r, col) << " : " << ex.what());
+ }
+ }
+
+ /// @brief Fetches a timestamp column as a ptime.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value ptime parameter to receive the converted timestamp
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, boost::posix_time::ptime& value);
+
+ /// @brief Fetches a JSON column as an ElementPtr.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value ElementPtr to receive the column data
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void getColumnValue(const PgSqlResult& r, const int row,
+ const size_t col, data::ElementPtr& value);
+
+ /// @brief Converts a column in a row in a result set to a binary bytes
+ ///
+ /// Method is used to convert columns stored as BYTEA into a buffer of
+ /// binary bytes, (uint8_t). It uses PQunescapeBytea to do the conversion.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] buffer pre-allocated buffer to which the converted bytes
+ /// will be stored.
+ /// @param buffer_size size of the output buffer
+ /// @param[out] bytes_converted number of bytes converted
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void convertFromBytea(const PgSqlResult& r, const int row,
+ const size_t col, uint8_t* buffer,
+ const size_t buffer_size,
+ size_t &bytes_converted);
+
+ /// @brief Converts a column in a row in a result set to a binary bytes
+ ///
+ /// Method is used to convert columns stored as BYTEA into a vector of
+ /// binary bytes, (uint8_t). It uses PQunescapeBytea to do the conversion.
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row
+ /// @param[out] value vector to receive the converted bytes
+ /// value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static void convertFromBytea(const PgSqlResult& r, const int row,
+ const size_t col, std::vector<uint8_t>& value);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a single
+ /// column value
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param col the column number within the row. If the column
+ /// is null, the Triplet is returned as unspecified.
+ /// @return Triplet to receive the column value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::util::Triplet<uint32_t> getTripletValue(const PgSqlResult& r,
+ const int row,
+ const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a three
+ /// column values: default, minimum, and maximum
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ /// @param def_col the column number within the row that contains the
+ /// default value. If this column is null, the Triplet is returned
+ /// as unspecified.
+ /// @param min_col the column number within the row that contains the
+ /// minimum value.
+ /// @param max_col the column number within the row that contains the
+ /// maximum value.
+ /// @return Triplet to receive the column value
+ ///
+ /// @throw DbOperationError if the value cannot be fetched or is
+ /// invalid.
+ static isc::util::Triplet<uint32_t> getTripletValue(const PgSqlResult& r,
+ const int row,
+ const size_t def_col,
+ const size_t min_col,
+ const size_t max_col);
+
+ /// @brief Diagnostic tool which dumps the Result row contents as a string
+ ///
+ /// @param r the result set containing the query results
+ /// @param row the row number within the result set
+ ///
+ /// @return A string depiction of the row contents.
+ static std::string dumpRow(const PgSqlResult& r, int row);
+
+protected:
+ /// @brief Stores text labels for columns, currently only used for
+ /// logging and errors.
+ std::vector<std::string> columns_;
+};
+
+/// @brief Convenience class which facilitates fetching column values
+/// from a result set row.
+class PgSqlResultRowWorker {
+public:
+ /// @brief Constructor
+ ///
+ /// @param r result set containing the fetched rows of data.
+ /// @param row zero-based index of the desired row, (e.g.
+ /// 0 .. n - 1 where n = number of rows in r)
+ /// @throw DbOperationError if row value is invalid.
+ PgSqlResultRowWorker(const PgSqlResult& r, const int row);
+
+ /// @brief Indicates whether or not the given column value is null.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return true if the value is null, false otherwise.
+ bool isColumnNull(const size_t col);
+
+ /// @brief Fetches the column value as a string.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return std::string containing the column value.
+ std::string getString(const size_t col);
+
+ /// @brief Fetches the boolean value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return bool containing the column value.
+ bool getBool(const size_t col);
+
+ /// @brief Fetches the floating point value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return double containing the column value.
+ double getDouble(const size_t col);
+
+ /// @brief Gets a pointer to the raw column value in a result set row
+ ///
+ /// Given a column return a const char* pointer to the data value in
+ /// the result set row. The pointer is valid as long as the underlying
+ /// result set has not been freed. It may point to text or binary
+ /// data depending on how query was structured. You should not attempt
+ /// to free this pointer.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return a const char* pointer to the column's raw data
+ const char* getRawColumnValue(const size_t col);
+
+ /// @brief Fetches the uint64_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint64_t containing the column value
+ uint64_t getBigInt(const size_t col);
+
+ /// @brief Fetches the uint32_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint32_t containing the column value
+ uint32_t getInt(const size_t col);
+
+ /// @brief Fetches the uint16_t value at the given column.
+ ///
+ /// @param col the column number within the row
+ ///
+ /// @return uint16_t containing the column value
+ uint16_t getSmallInt(const size_t col);
+
+ /// @brief Fetches binary data at the given column into a vector.
+ ///
+ /// @param col the column number within the row
+ /// @param[out] value vector to receive the fetched data.
+ void getBytes(const size_t col, std::vector<uint8_t>& value);
+
+ /// @brief Fetches the v4 IP address at the given column.
+ ///
+ /// This is used to fetch values from inet type columns.
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv4 address.
+ isc::asiolink::IOAddress getInet4(const size_t col);
+
+ /// @brief Fetches the v6 IP address at the given column.
+ ///
+ /// This is used to fetch values from inet type columns.
+ /// @param col the column number within the row
+ ///
+ /// @return isc::asiolink::IOAddress containing the IPv6 address.
+ isc::asiolink::IOAddress getInet6(const size_t col);
+
+ /// @brief Fetches a text column as the given value type
+ ///
+ /// Uses boost::lexicalcast to convert the text column value into
+ /// a value of type T.
+ ///
+ /// @param col the column number within the row
+ /// @param[out] value parameter to receive the converted value
+ template<typename T>
+ void getColumnValue(const size_t col, T& value) {
+ PgSqlExchange::getColumnValue(r_, row_, col, value);
+ }
+
+ /// @brief Fetches a timestamp column as a ptime.
+ ///
+ /// @param col the column number within the row
+ /// @return ptime parameter to receive the converted timestamp
+ boost::posix_time::ptime getTimestamp(const size_t col);
+
+ /// @brief Fetches a JSON column as an ElementPtr.
+ ///
+ /// @param col the column number within the row
+ /// @return ElementPtr parameter to receive the column value
+ data::ElementPtr getJSON(const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a single
+ /// column value
+ ///
+ /// @param col the column number within the row If the column
+ /// is null, the Triplet is returned as unspecified.
+ /// @return Triplet to receive the column value
+ isc::util::Triplet<uint32_t> getTriplet(const size_t col);
+
+ /// @brief Fetches a uint32_t value into a Triplet using a three
+ /// column values: default, minimum, and maximum
+ ///
+ /// @param def_col the column number within the row that contains the
+ /// default value. If this column is null, the Triplet is returned
+ /// as unspecified.
+ /// @param min_col the column number within the row that contains the
+ /// minimum value.
+ /// @param max_col the column number within the row that contains the
+ /// maximum value.
+ /// @return Triplet to receive the column value
+ isc::util::Triplet<uint32_t> getTriplet(const size_t def_col,
+ const size_t min_col,
+ const size_t max_col);
+
+ /// @brief Diagnostic tool which dumps the Result row contents as a string
+ ///
+ /// @return A string representation of the row contents.
+ std::string dumpRow();
+
+private:
+ /// @brief Result set containing the row.
+ const PgSqlResult& r_;
+
+ /// @brief Index of the desired row.
+ size_t row_;
+};
+
+/// @brief Pointer to a result row worker.
+typedef boost::shared_ptr<PgSqlResultRowWorker> PgSqlResultRowWorkerPtr;
+
+} // end of isc::db namespace
+} // end of isc namespace
+
+#endif // PGSQL_EXCHANGE_H
diff --git a/src/lib/pgsql/tests/Makefile.am b/src/lib/pgsql/tests/Makefile.am
new file mode 100644
index 0000000..425eea0
--- /dev/null
+++ b/src/lib/pgsql/tests/Makefile.am
@@ -0,0 +1,40 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+
+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 += libpgsql_unittests
+
+libpgsql_unittests_SOURCES = pgsql_basics.cc pgsql_basics.h
+libpgsql_unittests_SOURCES += pgsql_connection_unittest.cc
+libpgsql_unittests_SOURCES += pgsql_exchange_unittest.cc
+libpgsql_unittests_SOURCES += run_unittests.cc
+
+libpgsql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libpgsql_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(PGSQL_LIBS)
+
+libpgsql_unittests_LDADD = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/pgsql/libkea-pgsql.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libpgsql_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libpgsql_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/pgsql/tests/Makefile.in b/src/lib/pgsql/tests/Makefile.in
new file mode 100644
index 0000000..71f383d
--- /dev/null
+++ b/src/lib/pgsql/tests/Makefile.in
@@ -0,0 +1,1066 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libpgsql_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/pgsql/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libpgsql_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libpgsql_unittests_SOURCES_DIST = pgsql_basics.cc pgsql_basics.h \
+ pgsql_connection_unittest.cc pgsql_exchange_unittest.cc \
+ run_unittests.cc
+@HAVE_GTEST_TRUE@am_libpgsql_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-pgsql_basics.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-pgsql_connection_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-pgsql_exchange_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libpgsql_unittests-run_unittests.$(OBJEXT)
+libpgsql_unittests_OBJECTS = $(am_libpgsql_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libpgsql_unittests_DEPENDENCIES = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/pgsql/libkea-pgsql.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(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 =
+libpgsql_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libpgsql_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)/libpgsql_unittests-pgsql_basics.Po \
+ ./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po \
+ ./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po \
+ ./$(DEPDIR)/libpgsql_unittests-run_unittests.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 = $(libpgsql_unittests_SOURCES)
+DIST_SOURCES = $(am__libpgsql_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(PGSQL_CPPFLAGS)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libpgsql_unittests_SOURCES = pgsql_basics.cc \
+@HAVE_GTEST_TRUE@ pgsql_basics.h pgsql_connection_unittest.cc \
+@HAVE_GTEST_TRUE@ pgsql_exchange_unittest.cc run_unittests.cc
+@HAVE_GTEST_TRUE@libpgsql_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libpgsql_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(PGSQL_LIBS)
+@HAVE_GTEST_TRUE@libpgsql_unittests_LDADD = $(top_builddir)/src/lib/pgsql/testutils/libpgsqltest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/pgsql/libkea-pgsql.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(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/pgsql/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/pgsql/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
+
+libpgsql_unittests$(EXEEXT): $(libpgsql_unittests_OBJECTS) $(libpgsql_unittests_DEPENDENCIES) $(EXTRA_libpgsql_unittests_DEPENDENCIES)
+ @rm -f libpgsql_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libpgsql_unittests_LINK) $(libpgsql_unittests_OBJECTS) $(libpgsql_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-pgsql_basics.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsql_unittests-run_unittests.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 $@ $<
+
+libpgsql_unittests-pgsql_basics.o: pgsql_basics.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_basics.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo -c -o libpgsql_unittests-pgsql_basics.o `test -f 'pgsql_basics.cc' || echo '$(srcdir)/'`pgsql_basics.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_basics.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_basics.cc' object='libpgsql_unittests-pgsql_basics.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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_basics.o `test -f 'pgsql_basics.cc' || echo '$(srcdir)/'`pgsql_basics.cc
+
+libpgsql_unittests-pgsql_basics.obj: pgsql_basics.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_basics.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo -c -o libpgsql_unittests-pgsql_basics.obj `if test -f 'pgsql_basics.cc'; then $(CYGPATH_W) 'pgsql_basics.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_basics.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_basics.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_basics.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_basics.cc' object='libpgsql_unittests-pgsql_basics.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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_basics.obj `if test -f 'pgsql_basics.cc'; then $(CYGPATH_W) 'pgsql_basics.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_basics.cc'; fi`
+
+libpgsql_unittests-pgsql_connection_unittest.o: pgsql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_connection_unittest.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo -c -o libpgsql_unittests-pgsql_connection_unittest.o `test -f 'pgsql_connection_unittest.cc' || echo '$(srcdir)/'`pgsql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_connection_unittest.cc' object='libpgsql_unittests-pgsql_connection_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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_connection_unittest.o `test -f 'pgsql_connection_unittest.cc' || echo '$(srcdir)/'`pgsql_connection_unittest.cc
+
+libpgsql_unittests-pgsql_connection_unittest.obj: pgsql_connection_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_connection_unittest.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo -c -o libpgsql_unittests-pgsql_connection_unittest.obj `if test -f 'pgsql_connection_unittest.cc'; then $(CYGPATH_W) 'pgsql_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_connection_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_connection_unittest.cc' object='libpgsql_unittests-pgsql_connection_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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_connection_unittest.obj `if test -f 'pgsql_connection_unittest.cc'; then $(CYGPATH_W) 'pgsql_connection_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_connection_unittest.cc'; fi`
+
+libpgsql_unittests-pgsql_exchange_unittest.o: pgsql_exchange_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_exchange_unittest.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo -c -o libpgsql_unittests-pgsql_exchange_unittest.o `test -f 'pgsql_exchange_unittest.cc' || echo '$(srcdir)/'`pgsql_exchange_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_exchange_unittest.cc' object='libpgsql_unittests-pgsql_exchange_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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_exchange_unittest.o `test -f 'pgsql_exchange_unittest.cc' || echo '$(srcdir)/'`pgsql_exchange_unittest.cc
+
+libpgsql_unittests-pgsql_exchange_unittest.obj: pgsql_exchange_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-pgsql_exchange_unittest.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo -c -o libpgsql_unittests-pgsql_exchange_unittest.obj `if test -f 'pgsql_exchange_unittest.cc'; then $(CYGPATH_W) 'pgsql_exchange_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_exchange_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Tpo $(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_exchange_unittest.cc' object='libpgsql_unittests-pgsql_exchange_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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-pgsql_exchange_unittest.obj `if test -f 'pgsql_exchange_unittest.cc'; then $(CYGPATH_W) 'pgsql_exchange_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pgsql_exchange_unittest.cc'; fi`
+
+libpgsql_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libpgsql_unittests-run_unittests.Tpo -c -o libpgsql_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsql_unittests-run_unittests.Tpo $(DEPDIR)/libpgsql_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libpgsql_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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libpgsql_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libpgsql_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libpgsql_unittests-run_unittests.Tpo -c -o libpgsql_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)/libpgsql_unittests-run_unittests.Tpo $(DEPDIR)/libpgsql_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libpgsql_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) $(libpgsql_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libpgsql_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)/libpgsql_unittests-pgsql_basics.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-run_unittests.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)/libpgsql_unittests-pgsql_basics.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_connection_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-pgsql_exchange_unittest.Po
+ -rm -f ./$(DEPDIR)/libpgsql_unittests-run_unittests.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/pgsql/tests/pgsql_basics.cc b/src/lib/pgsql/tests/pgsql_basics.cc
new file mode 100644
index 0000000..e6d061a
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_basics.cc
@@ -0,0 +1,149 @@
+// Copyright (C) 2021-2022 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 <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <pgsql/tests/pgsql_basics.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::db;
+using namespace isc::db::test;
+
+PgSqlBasicsTest::PgSqlBasicsTest() : expected_col_names_(NUM_BASIC_COLS) {
+ // Create database connection parameter list
+ DatabaseConnection::ParameterMap params;
+ params["name"] = "keatest";
+ params["user"] = "keatest";
+ params["password"] = "keatest";
+
+ // Create and open the database connection
+ conn_.reset(new PgSqlConnection(params));
+ conn_->openDatabase();
+
+ // Create the list of expected column names
+ expected_col_names_[ID_COL] = "id";
+ expected_col_names_[BOOL_COL] = "bool_col";
+ expected_col_names_[BYTEA_COL] = "bytea_col";
+ expected_col_names_[BIGINT_COL] = "bigint_col";
+ expected_col_names_[SMALLINT_COL] = "smallint_col";
+ expected_col_names_[INT_COL] = "int_col";
+ expected_col_names_[TEXT_COL] = "text_col";
+ expected_col_names_[TIMESTAMP_COL] = "timestamp_col";
+ expected_col_names_[VARCHAR_COL] = "varchar_col";
+ expected_col_names_[INET4_COL] = "inet4_col";
+ expected_col_names_[FLOAT_COL] = "float_col";
+ expected_col_names_[JSON_COL] = "json_col";
+ expected_col_names_[MIN_INT_COL] = "min_int_col";
+ expected_col_names_[MAX_INT_COL] = "max_int_col";
+ expected_col_names_[INET6_COL] = "inet6_col";
+ expected_col_names_[LOCALTIME_COL] = "localtime_col";
+
+ destroySchema();
+ createSchema();
+}
+
+PgSqlBasicsTest::~PgSqlBasicsTest () {
+ destroySchema();
+}
+
+const std::string&
+PgSqlBasicsTest::expectedColumnName(int col) {
+ if (col < 0 || col >= NUM_BASIC_COLS) {
+ isc_throw(BadValue,
+ "definedColumnName: invalid column value" << col);
+ }
+
+ return (expected_col_names_[col]);
+}
+
+void
+PgSqlBasicsTest::createSchema() {
+ // One column for OID type, plus an auto-increment
+ const char* sql =
+ "CREATE TABLE basics ( "
+ " id SERIAL PRIMARY KEY NOT NULL, "
+ " bool_col BOOLEAN, "
+ " bytea_col BYTEA, "
+ " bigint_col BIGINT, "
+ " smallint_col SMALLINT, "
+ " int_col INT, "
+ " text_col TEXT, "
+ " timestamp_col TIMESTAMP WITH TIME ZONE, "
+ " varchar_col VARCHAR(255), "
+ " inet4_col INET, "
+ " float_col FLOAT, "
+ " json_col JSON,"
+ " min_int_col INT, "
+ " max_int_col INT, "
+ " inet6_col INET, "
+ " localtime_col TIMESTAMP WITH TIME ZONE "
+ "); ";
+
+ PgSqlResult r(PQexec(*conn_, sql));
+ ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
+ << " create basics table failed: " << PQerrorMessage(*conn_);
+}
+
+void
+PgSqlBasicsTest::destroySchema() {
+ if (conn_) {
+ PgSqlResult r(PQexec(*conn_, "DROP TABLE IF EXISTS basics;"));
+ ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
+ << " drop basics table failed: " << PQerrorMessage(*conn_);
+ }
+}
+
+void
+PgSqlBasicsTest::runSql(PgSqlResultPtr& r, const std::string& sql,
+ int exp_outcome, int lineno) {
+ r.reset(new PgSqlResult(PQexec(*conn_, sql.c_str())));
+ ASSERT_EQ(PQresultStatus(*r), exp_outcome)
+ << " runSql at line: " << lineno << " failed, sql:[" << sql
+ << "]\n reason: " << PQerrorMessage(*conn_);
+}
+
+void
+PgSqlBasicsTest::runPreparedStatement(PgSqlResultPtr& r,
+ PgSqlTaggedStatement& statement,
+ PsqlBindArrayPtr bind_array,
+ int exp_outcome, int lineno) {
+ r.reset(new PgSqlResult(PQexecPrepared(*conn_, statement.name,
+ statement.nbparams,
+ &bind_array->values_[0],
+ &bind_array->lengths_[0],
+ &bind_array->formats_[0], 0)));
+ ASSERT_EQ(PQresultStatus(*r), exp_outcome)
+ << " runPreparedStatement at line: " << lineno
+ << " statement name:[" << statement.name
+ << "]\n reason: " << PQerrorMessage(*conn_);
+}
+
+void
+PgSqlBasicsTest::fetchRows(PgSqlResultPtr& r, int exp_rows, int line) {
+ std::string sql =
+ "SELECT"
+ " id, bool_col, bytea_col, bigint_col, smallint_col, "
+ " int_col, text_col,"
+ " extract(epoch from timestamp_col)::bigint as timestamp_col,"
+ " varchar_col, inet4_col, float_col, json_col,"
+ " min_int_col, max_int_col, inet6_col,"
+ " (extract(epoch from localtime_col) + extract(timezone from localtime_col))::bigint as localtime_col"
+ " FROM basics";
+
+ runSql(r, sql, PGRES_TUPLES_OK, line);
+ ASSERT_EQ(r->getRows(), exp_rows) << "fetch at line: " << line
+ << " wrong row count, expected: " << exp_rows
+ << " , have: " << r->getRows();
+}
diff --git a/src/lib/pgsql/tests/pgsql_basics.h b/src/lib/pgsql/tests/pgsql_basics.h
new file mode 100644
index 0000000..5129cf9
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_basics.h
@@ -0,0 +1,161 @@
+// Copyright (C) 2021-2022 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 TEST_PGSQL_BASICS_H
+#define TEST_PGSQL_BASICS_H
+
+#include <config.h>
+
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace db {
+namespace test {
+
+/// @brief Fixture for exercising basic PostgreSQL operations and data types
+///
+/// This class is intended to be used to verify basic operations and to
+/// verify that each PostgreSQL data type currently used by Kea, can be
+/// correctly written to and read from PostgreSQL. Rather than use tables
+/// that belong to Kea the schema proper, it creates its own. Currently it
+/// consists of a single table, called "basics" which contains one column for
+/// each of the supported data types.
+///
+/// It creates the schema during construction, deletes it upon destruction, and
+/// provides functions for executing SQL statements, executing prepared
+/// statements, fetching all rows in the table, and deleting all the rows in
+/// the table.
+class PgSqlBasicsTest : public ::testing::Test {
+public:
+
+ /// @brief Column index for each column
+ enum BasicColIndex {
+ ID_COL,
+ BOOL_COL,
+ BYTEA_COL,
+ BIGINT_COL,
+ SMALLINT_COL,
+ INT_COL,
+ TEXT_COL,
+ TIMESTAMP_COL, // Used when epoch coming back is GMT (e.g. lease mgr)
+ VARCHAR_COL,
+ INET4_COL,
+ FLOAT_COL,
+ JSON_COL,
+ MIN_INT_COL,
+ MAX_INT_COL,
+ INET6_COL,
+ LOCALTIME_COL, // Used when epoch coming back is LOCAL (e.g. CB)
+ NUM_BASIC_COLS
+ };
+
+ /// @brief Constructor
+ ///
+ /// Creates the database connection, opens the database, and destroys
+ /// the table (if present) and then recreates it.
+ PgSqlBasicsTest();
+
+ /// @brief Destructor
+ ///
+ /// Destroys the table. The database resources are freed and the connection
+ /// closed by the destruction of conn_.
+ virtual ~PgSqlBasicsTest ();
+
+ /// @brief Gets the expected name of the column for a given column index
+ ///
+ /// Returns the name of column as we expect it to be when the column is
+ /// fetched from the database.
+ ///
+ /// @param col index of the desired column
+ ///
+ /// @return string containing the column name
+ ///
+ /// @throw BadValue if the index is out of range
+ const std::string& expectedColumnName(int col);
+
+ /// @brief Creates the basics table
+ /// Asserts if the creation step fails
+ void createSchema();
+
+ /// @brief Destroys the basics table
+ /// Asserts if the destruction fails
+ void destroySchema();
+
+ /// @brief Executes a SQL statement and tests for an expected outcome
+ ///
+ /// @param r pointer which will contain the result set returned by the
+ /// statement's execution.
+ /// @param sql string containing the SQL statement text. Note that
+ /// PostgreSQL supports executing text which contains more than one SQL
+ /// statement separated by semicolons.
+ /// @param exp_outcome expected status value returned with within the
+ /// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
+ /// @param lineno line number from where the call was invoked
+ ///
+ /// Asserts if the result set status does not equal the expected outcome.
+ void runSql(PgSqlResultPtr& r, const std::string& sql, int exp_outcome,
+ int lineno);
+
+ /// @brief Executes a SQL statement and tests for an expected outcome
+ ///
+ /// @param r pointer which will contain the result set returned by the
+ /// statement's execution.
+ /// @param statement statement descriptor of the prepared statement
+ /// to execute.
+ /// @param bind_array bind array containing the input values to submit
+ /// along with the statement
+ /// @param exp_outcome expected status value returned with within the
+ /// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
+ /// @param lineno line number from where the call was invoked
+ ///
+ /// Asserts if the result set status does not equal the expected outcome.
+ void runPreparedStatement(PgSqlResultPtr& r,
+ PgSqlTaggedStatement& statement,
+ PsqlBindArrayPtr bind_array, int exp_outcome,
+ int lineno);
+
+ /// @brief Fetches all of the rows currently in the table
+ ///
+ /// Executes a select statement which returns all of the rows in the
+ /// basics table, in their order of insertion. Each row contains all
+ /// of the defined columns, in the order they are defined.
+ ///
+ /// @param r pointer which will contain the result set returned by the
+ /// statement's execution.
+ /// @param exp_rows expected number of rows fetched. (This can be 0).
+ /// @param lineno line number from where the call was invoked
+ ///
+ /// Asserts if the result set status does not equal the expected outcome.
+ void fetchRows(PgSqlResultPtr& r, int exp_rows, int line);
+
+ /// @brief Database connection
+ PgSqlConnectionPtr conn_;
+
+ /// @brief List of column names as we expect them to be in fetched rows
+ std::vector<std::string> expected_col_names_;
+};
+
+// Macros defined to ease passing invocation line number for output tracing
+// (Yes I could have used scoped tracing but that's so ugly in code...)
+#define RUN_SQL(a,b,c) (runSql(a,b,c, __LINE__))
+#define RUN_PREP(a,b,c,d) (runPreparedStatement(a,b,c,d, __LINE__))
+#define FETCH_ROWS(a,b) (fetchRows(a,b,__LINE__))
+#define WIPE_ROWS(a) (RUN_SQL(a, "DELETE FROM BASICS", PGRES_COMMAND_OK))
+
+}
+}
+}
+
+#endif
diff --git a/src/lib/pgsql/tests/pgsql_connection_unittest.cc b/src/lib/pgsql/tests/pgsql_connection_unittest.cc
new file mode 100644
index 0000000..ab09009
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_connection_unittest.cc
@@ -0,0 +1,651 @@
+// Copyright (C) 2021-2023 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 <database/db_exceptions.h>
+#include <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <pgsql/tests/pgsql_basics.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::util;
+
+namespace {
+
+// A small extension of PgSqlBasicsTest that instantiates the actual Kea schema.
+// Those tests tend to be a bit heavy (especially with the CB and its tables),
+// so please consider adding your tests in PgSqlBasicsTest, unless you really need
+// the full schema.
+class PgSqlSchemaTest: public PgSqlBasicsTest {
+public:
+ PgSqlSchemaTest() : PgSqlBasicsTest() {
+ destroySchema(); // We don't need this fake schema with just "basics" table.
+
+ // Create the actual full Kea schema.
+ isc::db::test::createPgSQLSchema(true, true);
+ }
+
+ virtual ~PgSqlSchemaTest() {
+ // Clean up after ourselves.
+ isc::db::test::destroyPgSQLSchema(true, true);
+ }
+};
+
+/// @brief Checks if the schema version is really as expected.
+TEST_F(PgSqlSchemaTest, schemaVersion) {
+
+ PgSqlResultPtr r;
+ std::string sql = "SELECT version, minor FROM schema_version";
+ RUN_SQL(r, sql, PGRES_TUPLES_OK);
+ // There should be one row with 7,0 returned or whatever the latest schema is.
+ ASSERT_EQ(r->getRows(), 1) << "failed to check schema version, expected 1 row, have: "
+ << r->getRows();
+
+ int value = 0;
+ // Get row 0, column 0 (i.e. version field)
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, 0));
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, 0, 0, value));
+ EXPECT_EQ(value, PGSQL_SCHEMA_VERSION_MAJOR)
+ << "invalid schema version reported, major expected " << PGSQL_SCHEMA_VERSION_MAJOR
+ << ", actual:" << value;
+
+ // Get row 0, column 1 (i.e. minor field)
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, 1));
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, 0, 1, value));
+ EXPECT_EQ(value, PGSQL_SCHEMA_VERSION_MINOR)
+ << "invalid schema version reported, minor expected " << PGSQL_SCHEMA_VERSION_MINOR
+ << ", actual:" << value;
+
+}
+
+/// @brief Test fixture for exercising higher order PgSqlConnection functions
+/// selectQuery, insertQuery, updateDeleteQuery. These tests only use two of
+/// the columns in the BASICS table: int_col and text_col. Inserting rows with
+/// varying types and values are tested above. These tests focus on the higher
+/// order function mechanics.
+class PgSqlConnectionTest : public PgSqlBasicsTest {
+public:
+
+ /// @brief Indexes of prepared statements used within the tests.
+ enum StatementIndex {
+ GET_BY_INT_VALUE,
+ GET_BY_INT_RANGE,
+ DELETE_BY_INT_RANGE,
+ INSERT_VALUE,
+ UPDATE_BY_INT_VALUE,
+ GET_ALL_ROWS,
+ DELETE_ALL_ROWS,
+ NUM_STATEMENTS
+ };
+
+ /// @brief Array of tagged PgSql statements.
+ typedef std::array<PgSqlTaggedStatement, NUM_STATEMENTS> TaggedStatementArray;
+
+ /// @brief Prepared PgSql statements used in the tests.
+ TaggedStatementArray tagged_statements = {{
+ { 1, { OID_INT4 }, "GET_BY_INT_VALUE",
+ "SELECT int_col, text_col"
+ " FROM basics WHERE int_col = $1" },
+
+ { 2, { OID_INT4, OID_INT4 }, "GET_BY_INT_RANGE",
+ "SELECT int_col, text_col"
+ " FROM basics WHERE int_col >= $1 and int_col <= $2" },
+
+ { 2, { OID_INT4, OID_INT4 }, "DEL_BY_INT_RANGE",
+ "DELETE FROM basics WHERE int_col >= $1 and int_col <= $2" },
+
+ { 2, { OID_INT4, OID_TEXT }, "INSERT_INT_TEXT",
+ "INSERT INTO basics (int_col,text_col)"
+ " VALUES ($1, $2)" },
+
+ { 2, { OID_INT4, OID_TEXT }, "UPDATE_BY_INT_VALUE",
+ "UPDATE basics SET text_col = $2"
+ " WHERE int_col = $1" },
+
+ { 0, { OID_NONE }, "GET_ALL_ROWS",
+ "SELECT int_col, text_col FROM basics" },
+
+ { 0, { OID_NONE }, "DELETE_ALL_ROWS",
+ "DELETE FROM basics" }
+ }};
+
+ /// @brief Structure for holding data values describing a single
+ /// row. These tests only use two of the columns in the BASICS table:
+ /// int_col and text_col. Inserting rows with varying types and values
+ /// are tested above. These tests focus on the higher order mechanics.
+ struct TestRow {
+ int int_col;
+ std::string text_col;
+
+ bool operator==(const TestRow& other) const {
+ return (int_col == other.int_col &&
+ text_col == other.text_col);
+ }
+ };
+
+ /// @brief Defines a set of test rows.
+ typedef std::vector<TestRow> TestRowSet;
+
+ /// @brief Constructor.
+ PgSqlConnectionTest() : PgSqlBasicsTest() {};
+
+ /// @brief Destructor.
+ virtual ~PgSqlConnectionTest() {
+ if (conn_->isTransactionStarted()) {
+ conn_->rollback();
+ }
+ };
+
+ /// @brief SetUp function which prepares the tagged statements.
+ virtual void SetUp() {
+ ASSERT_NO_THROW_LOG(conn_->prepareStatements(tagged_statements.begin(),
+ tagged_statements.end()));
+ }
+
+ /// @brief Tests inserting data into the database.
+ ///
+ /// @param insert_rows Collection of rows of data to insert. Note that
+ /// each row is inserted as a separate statement execution.
+ void testInsert(const TestRowSet& insert_rows) {
+ for (auto row : insert_rows ) {
+ // Set the insert parameters based on the current insert row.
+ PsqlBindArray in_bindings;
+ in_bindings.add(row.int_col);
+ in_bindings.add(row.text_col);
+
+ // Insert the row into the database.
+ conn_->insertQuery(tagged_statements[INSERT_VALUE], in_bindings);
+ }
+ }
+
+ /// @brief Tests fetching data using PgSqlConnection::selectQuery()
+ ///
+ /// Selects rows from the BASICS table whose int_col value falls within
+ /// an inclusive range.
+ ///
+ /// @param expected_rows Collection of rows of data that we expect to be
+ /// fetched. Note the rows should be in the order you expect them to be
+ /// returned from the database.
+ /// @param begin_int beginning of the range to include.
+ /// @param end_int end fo the range to include.
+ void testSelect(const TestRowSet& expected_rows, const int begin_int, const int end_int) {
+ // Set the where clause parameters to the desired range values.
+ PsqlBindArray in_bindings;
+ in_bindings.add(begin_int);
+ in_bindings.add(end_int);
+
+ // Row set that will receive the fetched rows.
+ TestRowSet fetched_rows;
+
+ // Run the select. The row consumption lambda should populate
+ // fetched_rows based on the the result set returned by the select.
+ conn_->selectQuery(tagged_statements[GET_BY_INT_RANGE], in_bindings,
+ [&](PgSqlResult& r, int row) {
+ TestRow fetched_row;
+ if (row >= expected_rows.size()) {
+ // We have too many rows.
+ isc_throw(Unexpected, "row index exceeded expected row count of "
+ << expected_rows.size());
+ }
+
+ // First column should be int_col and not NULL.
+ if (PgSqlExchange::isColumnNull(r, row, 0)) {
+ isc_throw(Unexpected, "first column is null!");
+ }
+
+ // Fetch the int_col value.
+ PgSqlExchange::getColumnValue(r, row, 0, fetched_row.int_col);
+
+ // Second column should be text and not NULL.
+ if (PgSqlExchange::isColumnNull(r, row, 1)) {
+ isc_throw(Unexpected, "second column is null!");
+ }
+
+ // Fetch the text_col value.
+ PgSqlExchange::getColumnValue(r, row, 1, fetched_row.text_col);
+
+ // Add the fetched row into set of fetched rows.
+ fetched_rows.push_back(fetched_row);
+ });
+
+ // Make sure fetched rows match the expected rows.
+ ASSERT_EQ(fetched_rows, expected_rows);
+ }
+
+ /// @brief Tests updating data using PgSqlConnection::updateDeleteQuery()
+ ///
+ /// In this test, the input data is a set of rows that describe
+ /// which rows in the database to update and how. For each row
+ /// in the set we find the record in the database with matching
+ /// int_col value and replace its text_col value with the the
+ /// text value from the input the row.
+ ///
+ /// @param update_rows Collection of rows of data to update.
+ void testUpdate(const TestRowSet& update_rows) {
+ size_t update_count = 0;
+ for (auto row : update_rows ) {
+ // Set the text value and where clause parameters based on the
+ // this row's values.
+ PsqlBindArray in_bindings;
+ in_bindings.add(row.int_col);
+ in_bindings.add(row.text_col);
+
+ // Update the database.
+ update_count += conn_->updateDeleteQuery(tagged_statements[UPDATE_BY_INT_VALUE],
+ in_bindings);
+ }
+
+ // Number of rows updated should match rows we passed in.
+ ASSERT_EQ(update_count, update_rows.size());
+ }
+
+ /// @brief Tests deleting data using PgSqlConnection::updateDeleteQuery()
+ ///
+ /// Deletes rows from the BASICS table whose int_col value falls within
+ /// an inclusive range.
+ ///
+ /// @param begin_int beginning of the range to include.
+ /// @param end_int end of the range to include.
+ /// @param expected_delete_count number of rows of data we expect to be
+ /// deleted.
+ void testDelete(const int begin_int, const int end_int, size_t expected_delete_count) {
+ // Set the where clause parameters to the desired range values.
+ PsqlBindArray in_bindings;
+ in_bindings.add(begin_int);
+ in_bindings.add(end_int);
+
+ // Execute the delete statement.
+ size_t delete_count = 0;
+ delete_count = conn_->updateDeleteQuery(tagged_statements[DELETE_BY_INT_RANGE],
+ in_bindings);
+
+ // Verify the number of records deleted is as expected.
+ ASSERT_EQ(delete_count, expected_delete_count);
+ }
+};
+
+/// @brief Verifies basics of input parameter sanity checking and statement
+/// execution enforced by executePreparedStatement. Higher order tests
+/// verify actual data CRUD results.
+TEST_F(PgSqlConnectionTest, executePreparedStatement) {
+
+ // Executing with no parameters when they are required should throw.
+ // First we'll omit the bindings (defaults to empty).
+ PgSqlResultPtr r;
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[INSERT_VALUE]),
+ InvalidOperation,
+ "executePreparedStatement: expected: 2 parameters, given: 0,"
+ " statement: INSERT_INT_TEXT, SQL: INSERT INTO basics "
+ "(int_col,text_col) VALUES ($1, $2)");
+
+ // Now we'll pass in an empty array.
+ PsqlBindArray in_bindings;
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[INSERT_VALUE],
+ in_bindings),
+ InvalidOperation,
+ "executePreparedStatement: expected: 2 parameters, given: 0,"
+ " statement: INSERT_INT_TEXT, SQL: INSERT INTO basics "
+ "(int_col,text_col) VALUES ($1, $2)");
+
+ // Executing without parameters when none are expected should be fine.
+ // First we'll simply omit the array.
+ ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS]));
+
+ // Now with an empty array.
+ ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS], in_bindings));
+
+ // Executing with parameters when none are required should throw.
+ in_bindings.add(1);
+ in_bindings.add(2);
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS],
+ in_bindings),
+ InvalidOperation,
+ "executePreparedStatement: expected: 0 parameters, given: 2,"
+ " statement: GET_ALL_ROWS, SQL: SELECT int_col, text_col FROM basics");
+
+ // Executing with the correct number of parameters should work.
+ ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_BY_INT_RANGE],
+ in_bindings));
+
+ // Executing with too many parameters should fail.
+ in_bindings.add(3);
+ ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[GET_BY_INT_RANGE],
+ in_bindings),
+ InvalidOperation,
+ "executePreparedStatement: expected: 2 parameters, given: 3,"
+ " statement: GET_BY_INT_RANGE, SQL: SELECT int_col, text_col"
+ " FROM basics WHERE int_col >= $1 and int_col <= $2");
+}
+
+/// @brief Verify that we can insert rows with
+/// PgSqlConnection::insertQuery() and fetch
+/// them using PgSqlConnection::selectQuery().
+TEST_F(PgSqlConnectionTest, insertSelectTest) {
+
+ // Define the list of rows we want to insert.
+ TestRowSet insert_rows = {
+ { 7, "seven" },
+ { 8, "eight" },
+ { 9, "nine" },
+ };
+
+ // Insert the rows.
+ ASSERT_NO_THROW_LOG(testInsert(insert_rows));
+
+ // Make sure we can fetch a single row.
+ ASSERT_NO_THROW_LOG(testSelect(TestRowSet({{ 8, "eight" }}), 8, 8));
+
+ // Make sure we can fetch all the rows.
+ ASSERT_NO_THROW_LOG(testSelect(insert_rows, 7, 9));
+}
+
+/// @brief Verify that we can update rows with
+/// PgSqlConnection::updateDeleteQuery()
+TEST_F(PgSqlConnectionTest, updateTest) {
+
+ // Define the list of rows we want to insert.
+ TestRowSet insert_rows = {
+ { 7, "seven" },
+ { 8, "eight" },
+ { 9, "nine" },
+ };
+
+ // Insert the rows.
+ ASSERT_NO_THROW_LOG(testInsert(insert_rows));
+
+ // Define the list of updates.
+ TestRowSet update_rows = {
+ { 8, "ate" },
+ { 9, "mine" }
+ };
+
+ // Update the rows.
+ ASSERT_NO_THROW_LOG(testUpdate(update_rows));
+
+ // Fetch the updated rows.
+ ASSERT_NO_THROW_LOG(testSelect(update_rows, 8, 9));
+}
+
+/// @brief Verify that we can delete rows with
+/// PgSqlConnection::updateDeleteQuery()
+TEST_F(PgSqlConnectionTest, deleteTest) {
+
+ // Define the list of rows we want to insert.
+ TestRowSet insert_rows = {
+ { 6, "six" },
+ { 7, "seven" },
+ { 8, "eight" },
+ { 9, "nine" },
+ };
+
+ // Insert the rows.
+ ASSERT_NO_THROW_LOG(testInsert(insert_rows));
+
+ // Fetch the all rows.
+ ASSERT_NO_THROW_LOG(testSelect(insert_rows, 0, 10));
+
+ // Delete rows 7 and 8.
+ ASSERT_NO_THROW_LOG(testDelete(7, 8, 2));
+
+ // Fetch the all rows.
+ ASSERT_NO_THROW_LOG(testSelect(TestRowSet({{6, "six"}, {9, "nine"}}), 0, 10));
+}
+
+// Verifies that transaction nesting and operations: start, commit,
+// and rollback work correctly.
+TEST_F(PgSqlConnectionTest, transactions) {
+ ASSERT_FALSE(conn_->isTransactionStarted());
+
+ // Two inserts within a transaction and successful commit.
+ TestRowSet two_rows = {{1, "one"}, {2, "two"}};
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert(two_rows));
+ conn_->commit();
+
+ // Should not be in a transaction and we should have both
+ // rows we inserted.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Add third row but roll back the transaction. We should still have
+ // two rows in the table.
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert({{ 3, "three"}}));
+ conn_->rollback();
+
+ // We should not be in a transaction and should still have
+ // only the first two rows.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Nested transaction. The inner transaction should be ignored and the outer
+ // transaction rolled back. We should have only the original two rows in the
+ // database.
+ conn_->startTransaction();
+ EXPECT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert({{ 3, "three"}}));
+
+ conn_->startTransaction();
+ EXPECT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert({{ 4, "four"}}));
+
+ // First commit should do nothing other than decrement
+ // the transaction ref count.
+ conn_->commit();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+
+ // Rollback should end the transaction without committing changes.
+ conn_->rollback();
+ ASSERT_FALSE(conn_->isTransactionStarted());
+
+ // We should still have only the first two rows.
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Nested transaction. The inner transaction is rolled back but this should
+ // be ignored because nested transactions are not supported. We should
+ // have two new rows.
+
+ // Insert five row.
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ TestRow row_five({ 5, "five" });
+ ASSERT_NO_THROW_LOG(testInsert(TestRowSet({ row_five })));
+ two_rows.push_back(row_five);
+
+ // Insert six row.
+ conn_->startTransaction();
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ TestRow row_six({ 6, "six" });
+ ASSERT_NO_THROW_LOG(testInsert(TestRowSet({ row_six })));
+ two_rows.push_back(row_six);
+
+ // Rollback should do nothing other than decrement the
+ // reference count.
+ conn_->rollback();
+ EXPECT_TRUE(conn_->isTransactionStarted());
+
+ // Commit should complete the transaction and commit the inserts.
+ conn_->commit();
+ EXPECT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10));
+
+ // Committing or rolling back a not started transaction is a coding error.
+ EXPECT_THROW(conn_->commit(), isc::Unexpected);
+ EXPECT_THROW(conn_->rollback(), isc::Unexpected);
+}
+
+// Verifies that savepoints operate correctly.
+TEST_F(PgSqlConnectionTest, savepoints) {
+ // We want to trigger DuplicateEntry errors so let's
+ // add a unique constraint to the table.
+ ASSERT_NO_THROW(conn_->executeSQL("ALTER TABLE basics ADD CONSTRAINT"
+ " unique_int_col UNIQUE (int_col);"));
+ // Verify we are not in a transaction.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+
+ // Creating or rollback to savepoints outside of transactions
+ // should throw.
+ ASSERT_THROW_MSG(conn_->createSavepoint("rubbish"), InvalidOperation,
+ "no transaction, cannot create savepoint: rubbish");
+ ASSERT_THROW_MSG(conn_->rollbackToSavepoint("rubbish"), InvalidOperation,
+ "no transaction, cannot rollback to savepoint: rubbish");
+
+ // Test that we can create and rollback to a savepoint, then
+ // committing only the pre savepoint work.
+ TestRowSet first_row = {{1, "one"}};
+ ASSERT_NO_THROW_LOG(conn_->startTransaction());
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert(first_row));
+
+ // Create a savepoint.
+ ASSERT_NO_THROW_LOG(conn_->createSavepoint("sp_one"));
+
+ // Insert a second row, without committing it.
+ TestRowSet second_row = {{2, "two"}};
+ ASSERT_NO_THROW_LOG(testInsert(second_row));
+
+ // Rollback to the savepoint.
+ ASSERT_NO_THROW_LOG(conn_->rollbackToSavepoint("sp_one"));
+
+ // Commit the transaction.
+ conn_->commit();
+
+ // We should not be in a transaction but we should
+ // only have the first row.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testSelect(first_row, 0, 10));
+
+ // Now we'll test that we can create and rollback to a
+ // savepoint after Postgresql aborts an insert due to
+ // duplicate key error. We should still be able to
+ // commit the pre-savepoint and post rollback work.
+ ASSERT_NO_THROW_LOG(conn_->startTransaction());
+ ASSERT_TRUE(conn_->isTransactionStarted());
+ ASSERT_NO_THROW_LOG(testInsert(second_row));
+
+ // Create a savepoint.
+ ASSERT_NO_THROW_LOG(conn_->createSavepoint("sp_two"));
+
+ // Attempt to insert a duplicate first row.
+ ASSERT_THROW(testInsert(first_row), DuplicateEntry);
+
+ // Rollback to the savepoint.
+ ASSERT_NO_THROW_LOG(conn_->rollbackToSavepoint("sp_two"));
+
+ // Now insert a third row.
+ TestRowSet third_row = {{3, "three"}};
+ ASSERT_NO_THROW_LOG(testInsert(third_row));
+
+ // Commit the transaction.
+ conn_->commit();
+
+ // We should not be in a transaction and we should
+ // two rows.
+ ASSERT_FALSE(conn_->isTransactionStarted());
+ TestRowSet three_rows{{1, "one"}, {2, "two"}, {3, "three"}};
+ ASSERT_NO_THROW_LOG(testSelect(three_rows, 0, 10));
+}
+
+// Tests that invalid port value causes an error.
+TEST_F(PgSqlConnectionTest, portInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_PORT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidPort);
+}
+
+// Tests that valid connection timeout is accepted.
+TEST_F(PgSqlConnectionTest, connectionTimeout) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TIMEOUT);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_TRUE(parameters.find("connect_timeout = 10") != std::string::npos)
+ << "parameter not found in " << parameters;
+}
+
+// Tests that invalid timeout value type causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that a negative connection timeout value causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid2) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_2);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that a zero connection timeout value causes an error.
+TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid3) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_3);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+// Tests that valid tcp user timeout is accepted. This parameter is
+// supported by PostgreSQL 12 and later.
+#ifdef HAVE_PGSQL_TCP_USER_TIMEOUT
+TEST_F(PgSqlConnectionTest, tcpUserTimeout) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TCP_USER_TIMEOUT);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_TRUE(parameters.find("tcp_user_timeout = 8000") != std::string::npos)
+ << "parameter not found in " << parameters;
+}
+#endif
+
+// Tests that a zero tcp user timeout is accepted.
+TEST_F(PgSqlConnectionTest, tcpUserTimeoutZero) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ VALID_TCP_USER_TIMEOUT_ZERO);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ std::string parameters;
+ ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters());
+ EXPECT_FALSE(parameters.find("tcp_user_timeout") != std::string::npos)
+ << "parameter found in " << parameters << " but expected to be gone";
+}
+
+// Tests that an invalid tcp user timeout causes an error.
+TEST_F(PgSqlConnectionTest, tcpUserTimeoutInvalid) {
+ std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME,
+ VALID_USER, VALID_PASSWORD,
+ INVALID_TIMEOUT_1);
+ PgSqlConnection conn(DatabaseConnection::parse(conn_str));
+ EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout);
+}
+
+
+}; // namespace
diff --git a/src/lib/pgsql/tests/pgsql_exchange_unittest.cc b/src/lib/pgsql/tests/pgsql_exchange_unittest.cc
new file mode 100644
index 0000000..7bbf747
--- /dev/null
+++ b/src/lib/pgsql/tests/pgsql_exchange_unittest.cc
@@ -0,0 +1,1545 @@
+// Copyright (C) 2016-2022 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 <pgsql/pgsql_connection.h>
+#include <pgsql/pgsql_exchange.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <pgsql/tests/pgsql_basics.h>
+#include <testutils/gtest_utils.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::db;
+using namespace isc::db::test;
+using namespace isc::util;
+using namespace boost::posix_time;
+using namespace boost::gregorian;
+
+namespace {
+
+/// @brief Verifies the ability to add various data types to
+/// the bind array.
+TEST(PsqlBindArray, addDataTest) {
+
+ PsqlBindArray b;
+
+ // Declare a vector to add. Vectors are not currently duplicated
+ // So they will go out of scope, unless caller ensures it.
+ std::vector<uint8_t> bytes;
+ for (int i = 0; i < 10; i++) {
+ bytes.push_back(i+1);
+ }
+
+ // Declare a string
+ std::string not_temp_str("just a string");
+
+ // Now add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ // Add a const char*
+ b.add("booya!");
+
+ // Add the non temporary string
+ b.add(not_temp_str);
+
+ // Add a temporary string
+ b.addTempString("walah walah washington");
+
+ // Add a one byte int
+ uint8_t small_int = 25;
+ b.add(small_int);
+
+ // Add a four byte int
+ int reg_int = 376;
+ b.add(reg_int);
+
+ // Add a eight byte unsigned int
+ uint64_t big_int = 48786749032;
+ b.add(big_int);
+
+ // Add boolean true and false
+ b.add((bool)(1));
+ b.add((bool)(0));
+
+ // Add IP addresses
+ b.add(isc::asiolink::IOAddress("192.2.15.34"));
+ b.add(isc::asiolink::IOAddress("3001::1"));
+
+ // Add the vector
+ b.add(bytes);
+
+ // Add an empty string.
+ b.add(std::string(""));
+
+ // Add a v4 address.
+ asiolink::IOAddress addr4("192.168.1.1");
+ b.addInet4(addr4);
+
+ // Add a v6 address.
+ asiolink::IOAddress addr6("3001::1");
+ b.addInet6(addr6);
+
+ // Add a double. Not sure how portably reliable this test will be.
+ double dbl = 2.0;
+ b.add(dbl);
+
+ // Add a JSON.
+ ElementPtr elems = Element::fromJSON("{ \"foo\": \"bar\" }");
+ b.add(elems);
+
+ // Add a Temporary blob
+ std::vector<uint8_t> blob({0x0a,0x0b,0x0,0xc,0xd});
+ b.addTempBinary(blob);
+ }
+
+ // We've left bind scope, everything should be intact.
+ std::string expected =
+ "0 : \"booya!\"\n"
+ "1 : \"just a string\"\n"
+ "2 : \"walah walah washington\"\n"
+ "3 : \"25\"\n"
+ "4 : \"376\"\n"
+ "5 : \"48786749032\"\n"
+ "6 : \"TRUE\"\n"
+ "7 : \"FALSE\"\n"
+ "8 : \"3221360418\"\n"
+ "9 : \"3001::1\"\n"
+ "10 : 0x0102030405060708090a\n"
+ "11 : empty\n"
+ "12 : \"192.168.1.1\"\n"
+ "13 : \"3001::1\"\n"
+ "14 : \"2\"\n"
+ "15 : \"{ \"foo\": \"bar\" }\"\n"
+ "16 : 0x0a0b000c0d\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Triplets to
+/// the bind array.
+TEST(PsqlBindArray, addTriplet) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Triplet<uint32_t> empty;
+ Triplet<uint32_t> not_empty(1,2,3);
+
+ // Add triplets to the array.
+ b.add(empty);
+ b.add(not_empty);
+ b.addMin(empty);
+ b.addMin(not_empty);
+ b.addMax(empty);
+ b.addMax(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(6, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"2\"\n"
+ "2 : empty\n"
+ "3 : \"1\"\n"
+ "4 : empty\n"
+ "5 : \"3\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Optional strings to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalString) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<std::string> empty;
+ Optional<std::string> not_empty("whoopee!");
+
+ // Add strings to the array.
+ b.addOptional(empty);
+ b.addOptional(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"whoopee!\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Optional booleans to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalBool) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<bool> empty;
+ Optional<bool> am_false(false);
+ Optional<bool> am_true(true);
+
+ // Add booleans to the array.
+ b.addOptional(empty);
+ b.addOptional(am_false);
+ b.addOptional(am_true);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(3, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"0\"\n"
+ "2 : \"1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+
+/// @brief Verifies the ability to add OptionalIntegers to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalInteger) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<uint32_t> empty;
+ Optional<uint32_t> not_empty(123);
+
+ // Add the integers to the array..
+ b.addOptional(empty);
+ b.addOptional(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"123\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add Optional IPv4 addresses to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalInet4) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<asiolink::IOAddress> empty;
+ Optional<asiolink::IOAddress> not_empty(asiolink::IOAddress("192.16.1.1"));
+
+ // Verify we cannot add a v6 address.
+ Optional<asiolink::IOAddress> not_v4(asiolink::IOAddress("3001::1"));
+ ASSERT_THROW_MSG(b.addOptionalInet4(not_v4), BadValue,
+ "unable to add address to PsqlBindAray"
+ " '3001::1' is not an IPv4 address");
+
+ // Add addresses to bind array.
+ b.addOptionalInet4(empty);
+ b.addOptionalInet4(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"192.16.1.1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to add optional IPv6 addresses to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalInet6) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<asiolink::IOAddress> empty;
+ Optional<asiolink::IOAddress> not_empty(asiolink::IOAddress("3001::1"));
+
+ // Verify we cannot add a v6 address.
+ Optional<asiolink::IOAddress> not_v6(asiolink::IOAddress("192.168.1.1"));
+ ASSERT_THROW_MSG(b.addOptionalInet6(not_v6), BadValue,
+ "unable to add address to PsqlBindAray"
+ " '192.168.1.1' is not an IPv6 address");
+
+ // Add addresses to bind array.
+ b.addOptionalInet6(empty);
+ b.addOptionalInet6(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"3001::1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies that PgResultSet row and column meta-data is correct
+TEST_F(PgSqlBasicsTest, rowColumnBasics) {
+ // We fetch the table contents, which at this point should be no rows.
+ PgSqlResultPtr r;
+ FETCH_ROWS(r, 0);
+
+ // Column meta-data is determined by the select statement and is
+ // present whether or not any rows were returned.
+ EXPECT_EQ(r->getCols(), NUM_BASIC_COLS);
+
+ // Negative indexes should be out of range. We test negative values
+ // as PostgreSQL functions accept column values as type int.
+ EXPECT_THROW(r->colCheck(-1), DbOperationError);
+
+ // Iterate over the column indexes verifying:
+ // 1. the column is valid
+ // 2. the result set column name matches the expected column name
+ for (int i = 0; i < NUM_BASIC_COLS; i++) {
+ EXPECT_NO_THROW(r->colCheck(i));
+ EXPECT_EQ(r->getColumnLabel(i), expectedColumnName(i));
+ }
+
+ // Verify above range column value is detected.
+ EXPECT_THROW(r->colCheck(NUM_BASIC_COLS), DbOperationError);
+
+ // Verify the fetching a column label for out of range columns
+ // do NOT throw.
+ std::string label;
+ ASSERT_NO_THROW(label = r->getColumnLabel(-1));
+ EXPECT_EQ(label, "Unknown column:-1");
+ ASSERT_NO_THROW(label = r->getColumnLabel(NUM_BASIC_COLS));
+ std::ostringstream os;
+ os << "Unknown column:" << NUM_BASIC_COLS;
+ EXPECT_EQ(label, os.str());
+
+ // Verify row count and checking. With an empty result set all values of
+ // row are invalid.
+ EXPECT_EQ(r->getRows(), 0);
+ EXPECT_THROW(r->rowCheck(-1), DbOperationError);
+ EXPECT_THROW(r->rowCheck(0), DbOperationError);
+ EXPECT_THROW(r->rowCheck(1), DbOperationError);
+
+ // Verify Row-column check will always fail with an empty result set.
+ EXPECT_THROW(r->rowColCheck(-1, 1), DbOperationError);
+ EXPECT_THROW(r->rowColCheck(0, 1), DbOperationError);
+ EXPECT_THROW(r->rowColCheck(1, 1), DbOperationError);
+
+ // Insert three minimal rows. We don't really care about column content
+ // for this test.
+ int num_rows = 3;
+ for (int i = 0; i < num_rows; i++) {
+ RUN_SQL(r, "INSERT INTO basics (bool_col) VALUES ('t')",
+ PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly created rows.
+ FETCH_ROWS(r, num_rows);
+
+ // Verify we row count and checking
+ EXPECT_EQ(r->getRows(), num_rows);
+ EXPECT_THROW(r->rowCheck(-1), DbOperationError);
+
+ // Iterate over the row count, verifying that expected rows are valid
+ for (int i = 0; i < num_rows; i++) {
+ EXPECT_NO_THROW(r->rowCheck(i));
+ EXPECT_NO_THROW(r->rowColCheck(i, 0));
+ }
+
+ // Verify an above range row is detected.
+ EXPECT_THROW(r->rowCheck(num_rows), DbOperationError);
+}
+
+/// @brief Verify that we can read and write BOOL columns
+TEST_F(PgSqlBasicsTest, boolTest) {
+ // Create a prepared statement for inserting bool_col
+ const char* st_name = "bool_insert";
+ PgSqlTaggedStatement statement[] = {
+ {1, { OID_BOOL }, st_name,
+ "INSERT INTO BASICS (bool_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ bool bools[] = { true, false };
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+ PgSqlResultPtr r;
+
+ // Insert bool rows
+ for (int i = 0; i < 2; ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(bools[i]);
+ RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, 2);
+
+ // Verify the fetched bool values are what we expect.
+ bool fetched_bool;
+ int row = 0;
+ for ( ; row < 2; ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BOOL_COL));
+
+ // Fetch and verify the column value
+ fetched_bool = !bools[row];
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BOOL_COL,
+ fetched_bool));
+ EXPECT_EQ(fetched_bool, bools[row]);
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, 1, fetched_bool),
+ DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL boolean
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+
+ // Run the insert with the bind array.
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, 1));
+}
+
+/// @brief Verify that we can read and write BYTEA columns
+TEST_F(PgSqlBasicsTest, byteaTest) {
+ const char* st_name = "bytea_insert";
+ PgSqlTaggedStatement statement[] = {
+ {1, { OID_BYTEA }, st_name,
+ "INSERT INTO BASICS (bytea_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ const uint8_t bytes[] = {
+ 0x01, 0x02, 0x03, 0x04
+ };
+ std::vector<uint8_t> vbytes(bytes, bytes + sizeof(bytes));
+
+ // Verify we can insert bytea from a vector
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+ PgSqlResultPtr r;
+ bind_array->add(vbytes);
+ RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Verify we can insert bytea from a buffer.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(bytes, sizeof(bytes));
+ RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted rows.
+ int num_rows = 2;
+ FETCH_ROWS(r, num_rows);
+
+ uint8_t fetched_bytes[sizeof(bytes)];
+ size_t byte_count;
+ int row = 0;
+ for ( ; row < num_rows; ++row) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BYTEA_COL));
+
+ // Extract the data into a correctly sized buffer
+ memset(fetched_bytes, 0, sizeof(fetched_bytes));
+ ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
+ fetched_bytes,
+ sizeof(fetched_bytes),
+ byte_count));
+
+ // Verify the data is correct
+ ASSERT_EQ(byte_count, sizeof(bytes));
+ for (int i = 0; i < sizeof(bytes); i++) {
+ ASSERT_EQ(bytes[i], fetched_bytes[i]);
+ }
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
+ fetched_bytes,
+ sizeof(fetched_bytes),
+ byte_count), DbOperationError);
+
+ // Verify that too small of a buffer throws
+ ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
+ fetched_bytes,
+ sizeof(fetched_bytes) - 1,
+ byte_count), DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL for a bytea column
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull(PsqlBindArray::BINARY_FMT);
+ RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BYTEA_COL));
+
+ // Verify that fetching a NULL bytea, returns 0 byte count
+ ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
+ fetched_bytes,
+ sizeof(fetched_bytes),
+ byte_count));
+ EXPECT_EQ(byte_count, 0);
+}
+
+/// @brief Verify that we can read and write BIGINT columns
+TEST_F(PgSqlBasicsTest, bigIntTest) {
+ // Create a prepared statement for inserting BIGINT
+ const char* st_name = "bigint_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_INT8 }, st_name,
+ "INSERT INTO BASICS (bigint_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<int64_t> ints;
+ ints.push_back(-1);
+ ints.push_back(0);
+ ints.push_back(0x7fffffffffffffff);
+ ints.push_back(0xffffffffffffffff);
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < ints.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(ints[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, ints.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int64_t fetched_int;
+ int row = 0;
+ for ( ; row < ints.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BIGINT_COL));
+
+ // Fetch and verify the column value
+ fetched_int = 777;
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
+ fetched_int));
+ EXPECT_EQ(fetched_int, ints[row]);
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
+ fetched_int), DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BIGINT_COL));
+}
+
+/// @brief Verify that we can read and write SMALLINT columns
+TEST_F(PgSqlBasicsTest, smallIntTest) {
+ // Create a prepared statement for inserting a SMALLINT
+ const char* st_name = "smallint_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_INT2 }, st_name,
+ "INSERT INTO BASICS (smallint_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<int16_t>ints;
+ ints.push_back(-1);
+ ints.push_back(0);
+ ints.push_back(0x7fff);
+ ints.push_back(0xffff);
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < ints.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(ints[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, ints.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int16_t fetched_int;
+ int row = 0;
+ for ( ; row < ints.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, SMALLINT_COL));
+
+ // Fetch and verify the column value
+ fetched_int = 777;
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
+ fetched_int));
+ EXPECT_EQ(fetched_int, ints[row]);
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
+ fetched_int),
+ DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, SMALLINT_COL));
+}
+
+/// @brief Verify that we can read and write INT columns
+TEST_F(PgSqlBasicsTest, intTest) {
+ // Create a prepared statement for inserting an INT
+ const char* st_name = "int_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_INT4 }, st_name,
+ "INSERT INTO BASICS (int_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<int32_t> ints;
+ ints.push_back(-1);
+ ints.push_back(0);
+ ints.push_back(0x7fffffff);
+ ints.push_back(0xffffffff);
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < ints.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(ints[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, ints.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int32_t fetched_int;
+ int row = 0;
+ for ( ; row < ints.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INT_COL));
+
+ // Fetch and verify the column value
+ fetched_int = 777;
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL,
+ fetched_int));
+ EXPECT_EQ(fetched_int, ints[row]);
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL, fetched_int),
+ DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted rows
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INT_COL));
+}
+
+/// @brief Verify that we use the methods used for testing:
+/// amNull(), getInteger<T>(), getType()
+TEST_F(PgSqlBasicsTest, get) {
+ PsqlBindArray bind_array;
+
+ // The array itself is empty, its first column is not null
+ EXPECT_THROW(bind_array.amNull(), OutOfRange);
+ EXPECT_THROW(bind_array.getInteger<uint32_t>(), OutOfRange);
+ EXPECT_THROW(bind_array.getType(), OutOfRange);
+
+ // Now try again with proper values.
+ bind_array.add(123); // This will be converted to "123" string.
+ bind_array.addNull();
+ bind_array.add("sagittarius");
+ EXPECT_FALSE(bind_array.amNull(0)); // first column is not null
+ EXPECT_TRUE(bind_array.amNull(1)); // second column is null
+ EXPECT_FALSE(bind_array.amNull(2)); // third column is not null
+
+ EXPECT_EQ(123, bind_array.getInteger<uint32_t>(0)); // first column is 123
+ EXPECT_THROW(bind_array.getInteger<uint32_t>(1), BadValue);
+ EXPECT_THROW(bind_array.getInteger<uint32_t>(2), boost::bad_lexical_cast);
+
+ EXPECT_EQ(PsqlBindArray::TEXT_FMT, bind_array.getType(0));
+ EXPECT_EQ(PsqlBindArray::TEXT_FMT, bind_array.getType(1));
+ EXPECT_EQ(PsqlBindArray::TEXT_FMT, bind_array.getType(2));
+}
+
+/// @brief Verify that we can read and write TEXT columns
+TEST_F(PgSqlBasicsTest, textTest) {
+ // Create a prepared statement for inserting TEXT
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, "text_insert",
+ "INSERT INTO BASICS (text_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Our reference string.
+ std::string ref_string = "This is a text string";
+
+ // Insert the reference from std::string
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(ref_string);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Insert the reference from a buffer
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(ref_string.c_str());
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, 2);
+
+ // Iterate over the rows, verifying the value against the reference
+ std::string fetched_str;
+ int row = 0;
+ for ( ; row < 2; ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TEXT_COL));
+
+ // Fetch and verify the column value
+ fetched_str = "";
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL,
+ fetched_str));
+ EXPECT_EQ(fetched_str, ref_string);
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL, fetched_str),
+ DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TEXT_COL));
+}
+
+/// @brief Verify that we can read and write VARCHAR columns
+TEST_F(PgSqlBasicsTest, varcharTest) {
+ // Create a prepared statement for inserting a VARCHAR
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_VARCHAR }, "varchar_insert",
+ "INSERT INTO BASICS (varchar_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Our reference string.
+ std::string ref_string = "This is a varchar string";
+
+ // Insert the reference from std::string
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(ref_string);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Insert the reference from a buffer
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(ref_string.c_str());
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, 2);
+
+ // Iterate over the rows, verifying the value against the reference
+ std::string fetched_str;
+ int row = 0;
+ for ( ; row < 2; ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, VARCHAR_COL));
+
+ // Fetch and verify the column value
+ fetched_str = "";
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
+ fetched_str));
+ EXPECT_EQ(fetched_str, ref_string);
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
+ fetched_str), DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted rows
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, VARCHAR_COL));
+}
+
+/// @brief Verify that we can read and write TIMESTAMP columns
+TEST_F(PgSqlBasicsTest, timeStampTest) {
+ // Create a prepared statement for inserting a TIMESTAMP
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TIMESTAMP }, "timestamp_insert",
+ "INSERT INTO BASICS (timestamp_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Build our list of reference times
+ time_t now;
+ time(&now);
+ std::vector<time_t> times;
+ times.push_back(now);
+ times.push_back(DatabaseConnection::MAX_DB_TIME);
+ // Note on a 32-bit OS this value is really -1. PosgreSQL will store it
+ // and return it intact.
+ times.push_back(0xFFFFFFFF);
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ std::string time_str;
+ for (int i = 0; i < times.size(); ++i) {
+ // Timestamps are inserted as strings so convert them first
+ ASSERT_NO_THROW(time_str =
+ PgSqlExchange::convertToDatabaseTime(times[i]));
+
+ // Add it to the bind array and insert it
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(time_str);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Insert a row with ref time plus one day
+ times.push_back(now + 24*3600);
+ ASSERT_NO_THROW(time_str =
+ PgSqlExchange::convertToDatabaseTime(times[0], 24*3600));
+
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(time_str);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, times.size());
+
+ // Iterate over the rows, verifying the value against its reference
+ std::string fetched_str;
+ int row = 0;
+ for ( ; row < times.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TIMESTAMP_COL));
+
+ // Fetch and verify the column value
+ fetched_str = "";
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
+ fetched_str));
+
+ time_t fetched_time;
+ ASSERT_NO_THROW(fetched_time =
+ PgSqlExchange::convertFromDatabaseTime(fetched_str));
+ EXPECT_EQ(fetched_time, times[row]) << " row: " << row;
+ }
+
+ // While we here, verify that bad row throws
+ ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
+ fetched_str), DbOperationError);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted rows
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TIMESTAMP_COL));
+
+ // Verify exceeding max time throws
+ ASSERT_THROW(PgSqlExchange::convertToDatabaseTime(times[0],
+ DatabaseConnection::
+ MAX_DB_TIME), BadValue);
+}
+
+/// @brief Verify that we can read and write ptime using TIMESTAMP columns.
+TEST_F(PgSqlBasicsTest, ptimeTimestamp) {
+ // Create a prepared statement for inserting a TIMESTAMP
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TIMESTAMP }, "timestamp_insert",
+ "INSERT INTO BASICS (localtime_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Create an empty array.
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+
+ // Make sure we catch values before the epoch.
+ ptime christmas1969(date(1969, Dec, 25));
+ ASSERT_THROW_MSG(bind_array->addTimestamp(christmas1969), BadValue,
+ "Time value is before the epoch");
+
+ // Make sure we catch values that are too big.
+ time_duration duration = hours(10) + minutes(14) + seconds(15);
+ ptime day_too_far(date(2038, Jan, 21), duration);
+ ASSERT_THROW_MSG(bind_array->addTimestamp(day_too_far), BadValue,
+ "Time value is too large: 2147681655");
+
+ // Now add reasonable day, US National Ice Cream day.
+ ptime nice_day(date(2021, Jul, 18), duration);
+ bind_array->addTimestamp(nice_day);
+
+ PgSqlResultPtr r;
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Timestamp column should not be null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, LOCALTIME_COL));
+
+ // Convert fetched value into a ptime.
+ ptime fetched_time;
+ ASSERT_NO_THROW_LOG(PgSqlExchange::getColumnValue(*r, 0, LOCALTIME_COL,
+ fetched_time));
+
+ ASSERT_EQ(fetched_time, nice_day);
+}
+
+/// @brief Verifies the ability to insert a string into
+/// the bind array.
+TEST(PsqlBindArray, insertString) {
+ PsqlBindArray b;
+
+ // Make a non-temporary string to insert.
+ std::string one("one");
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ // Make sure you can "insert" at the front of an empty array.
+ b.insert("two", 0);
+
+ // Add a binding.
+ b.add("four");
+
+ // Verify an out of range index throws.
+ ASSERT_THROW_MSG(b.insert(std::string("too far"), 5), OutOfRange,
+ "PsqlBindArray::insert - index: 5, "
+ "is larger than the array size: 2");
+
+ // Insert a non-temporary string at the front.
+ b.insert(one, 0);
+
+ // Insert a temporary string.
+ b.insert("three", 2);
+
+ // Add another one.
+ b.add("five");
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(5, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : \"one\"\n"
+ "1 : \"two\"\n"
+ "2 : \"three\"\n"
+ "3 : \"four\"\n"
+ "4 : \"five\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verifies the ability to pop bindings off
+/// the end of a bind array.
+TEST(PsqlBindArray, popBackTest) {
+ PsqlBindArray b;
+
+ // Popping on an empty array should throw.
+ ASSERT_THROW_MSG(b.popBack(), OutOfRange,
+ "PsqlBindArray::pop_back - array empty");
+
+ // Add five integers.
+ for (int i = 1; i < 6; ++i) {
+ b.add(i);
+ }
+
+ // Verify size.
+ EXPECT_EQ(b.size(), 5);
+
+ // Pop one off.
+ ASSERT_NO_THROW_LOG(b.popBack());
+
+ // Verify size.
+ EXPECT_EQ(b.size(), 4);
+
+ // Pop another one off.
+ ASSERT_NO_THROW_LOG(b.popBack());
+
+ // Verify size.
+ EXPECT_EQ(b.size(), 3);
+
+ // This is what we should have left.
+ std::string expected =
+ "0 : \"1\"\n"
+ "1 : \"2\"\n"
+ "2 : \"3\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+/// @brief Verify that we can read and write IPv4 addresses
+/// using INET columns.
+TEST_F(PgSqlBasicsTest, inetTest4) {
+ // Create a prepared statement for inserting an IPv4 address
+ const char* st_name = "inet4_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (inet4_col) values (cast($1 as inet))" },
+ { 1, { OID_TEXT }, "check_where",
+ "select * from BASICS where inet4_col = cast($1 as inet)" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[1]));
+
+ // Build our reference list of reference values
+ std::vector<asiolink::IOAddress>inets;
+ inets.push_back(asiolink::IOAddress("0.0.0.0"));
+ inets.push_back(asiolink::IOAddress("192.168.1.9"));
+ inets.push_back(asiolink::IOAddress("192.168.1.1"));
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < inets.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet4(inets[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, inets.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ asiolink::IOAddress fetched_inet("0.0.0.0");
+ for ( ; row < inets.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET4_COL));
+
+ // Fetch and verify the column value
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, row, INET4_COL));
+ EXPECT_EQ(fetched_inet, inets[row]);
+ }
+
+ // Verify that casting from string to inet works in where clauses.
+ r.reset();
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet4(inets[1]);
+ RUN_PREP(r, statement[1], bind_array, PGRES_TUPLES_OK);
+ ASSERT_EQ(r->getRows(),1);
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue4(*r, 0, INET4_COL));
+ EXPECT_EQ(fetched_inet, inets[1]);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INET4_COL));
+}
+
+/// @brief Verify that we can read and write IPv6 addresses
+/// using INET columns.
+TEST_F(PgSqlBasicsTest, inetTest6) {
+ // Create a prepared statement for inserting an IPv6 address
+ const char* st_name = "inet6_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (inet6_col) values (cast($1 as inet))" },
+ { 1, { OID_TEXT }, "check_where",
+ "select * from BASICS where inet6_col = cast($1 as inet)" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[1]));
+
+ // Build our reference list of reference values
+ std::vector<asiolink::IOAddress>inets;
+ inets.push_back(asiolink::IOAddress("::"));
+ inets.push_back(asiolink::IOAddress("3001::1"));
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < inets.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet6(inets[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, inets.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ asiolink::IOAddress fetched_inet("::");
+ for ( ; row < inets.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INET6_COL));
+
+ // Fetch and verify the column value
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue6(*r, row, INET6_COL));
+ EXPECT_EQ(fetched_inet, inets[row]);
+ }
+
+ // Verify that casting from string to inet works in where clauses.
+ r.reset();
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addInet6(inets[1]);
+ RUN_PREP(r, statement[1], bind_array, PGRES_TUPLES_OK);
+ ASSERT_EQ(r->getRows(),1);
+ ASSERT_NO_THROW(fetched_inet = PgSqlExchange::getInetValue6(*r, 0, INET6_COL));
+ EXPECT_EQ(fetched_inet, inets[1]);
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INET6_COL));
+}
+
+/// @brief Verify that we can read and write floats
+TEST_F(PgSqlBasicsTest, floatTest) {
+ // Create a prepared statement for inserting a FLOAT
+ const char* st_name = "float_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (float_col) values (cast($1 as float))" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<double>floats;
+ floats.push_back(1.345);
+ floats.push_back(200);
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < floats.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(floats[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, floats.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ for ( ; row < floats.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, FLOAT_COL));
+
+ // Fetch and verify the column value
+ double fetched_float;
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, FLOAT_COL, fetched_float));
+ EXPECT_EQ(fetched_float, floats[row]);
+ }
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, FLOAT_COL));
+}
+
+/// @brief Verify that we can read and write JSON columns.
+TEST_F(PgSqlBasicsTest, jsonTest) {
+ // Create a prepared statement for inserting a JSON
+ const char* st_name = "json_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TEXT }, st_name,
+ "INSERT INTO BASICS (json_col) values (cast($1 as json))" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<ElementPtr>elem_ptrs;
+ ASSERT_NO_THROW_LOG(elem_ptrs.push_back(Element::fromJSON("{ \"one\":1 }")));
+ ASSERT_NO_THROW_LOG(elem_ptrs.push_back(Element::fromJSON("{ \"two\":\"two\" }")));
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (int i = 0; i < elem_ptrs.size(); ++i) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(elem_ptrs[i]);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, elem_ptrs.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ for ( ; row < elem_ptrs.size(); ++row ) {
+ // Verify the column is not null.
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, JSON_COL));
+
+ // Fetch and verify the column value
+ ElementPtr fetched_ptr;
+ ASSERT_NO_THROW_LOG(PgSqlExchange::getColumnValue(*r, row, JSON_COL, fetched_ptr));
+ EXPECT_TRUE(fetched_ptr->equals(*(elem_ptrs[row])));
+ }
+
+ // Clean out the table
+ WIPE_ROWS(r);
+
+ // Verify we can insert a NULL value.
+ bind_array.reset(new PsqlBindArray());
+ bind_array->addNull();
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Verify the column is null.
+ ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, JSON_COL));
+}
+
+/// @brief Verify that we can read and write integer Triplets.
+TEST_F(PgSqlBasicsTest, tripleTest) {
+ // Create a prepared statement for inserting a Triplet
+ const char* st_name = "triplets_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 3, { OID_INT4, OID_INT4, OID_INT4 }, st_name,
+ "INSERT INTO BASICS (int_col, min_int_col, max_int_col) values ($1, $2, $3)" }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Build our reference list of reference values
+ std::vector<Triplet<uint32_t>> triplets;
+ triplets.push_back(Triplet<uint32_t>()); // def column is null
+ triplets.push_back(Triplet<uint32_t>(10)); // only default column
+ triplets.push_back(Triplet<uint32_t>(5,10,15)); // all three columns
+ triplets.push_back(Triplet<uint32_t>(10,10,15)); // min column is null
+ triplets.push_back(Triplet<uint32_t>(5,10,10)); // max column is null
+
+ // Insert a row for each reference value
+ PsqlBindArrayPtr bind_array;
+ PgSqlResultPtr r;
+ for (auto triplet : triplets) {
+ bind_array.reset(new PsqlBindArray());
+ bind_array->add(triplet);
+ bind_array->addMin(triplet);
+ bind_array->addMax(triplet);
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+ }
+
+ // Fetch the newly inserted rows.
+ FETCH_ROWS(r, triplets.size());
+
+ // Iterate over the rows, verifying each value against its reference
+ int row = 0;
+ for (auto expected : triplets) {
+ Triplet<uint32_t> fetched;
+ // First we test making a triplet only with default value column.
+ ASSERT_NO_THROW_LOG(fetched = PgSqlExchange::getTripletValue(*r, row, INT_COL));
+ if (expected.unspecified()) {
+ EXPECT_TRUE(fetched.unspecified());
+ } else {
+ EXPECT_FALSE(fetched.unspecified());
+ EXPECT_EQ(fetched.get(), expected.get());
+ }
+
+ // Now test making a triplet with all three columns.
+ ASSERT_NO_THROW_LOG(
+ fetched = PgSqlExchange::getTripletValue(*r, row,
+ INT_COL, MIN_INT_COL, MAX_INT_COL));
+ if (expected.unspecified()) {
+ EXPECT_TRUE(fetched.unspecified());
+ } else {
+ EXPECT_FALSE(fetched.unspecified());
+ EXPECT_EQ(fetched.get(), expected.get());
+ EXPECT_EQ(fetched.getMin(), expected.getMin());
+ EXPECT_EQ(fetched.getMax(), expected.getMax());
+ }
+
+ ++row;
+ }
+
+ // Clean out the table
+ WIPE_ROWS(r);
+}
+
+/// @brief Verify PgResultRowWorker operations.
+TEST_F(PgSqlBasicsTest, resultRowWorker) {
+ // Create a prepared statement for inserting multiple types
+ const char* st_name = "row_insert";
+ PgSqlTaggedStatement statement[] = {
+ { 14,
+ {
+ OID_BOOL,
+ OID_BYTEA,
+ OID_INT8,
+ OID_INT2,
+ OID_INT4,
+ OID_TEXT,
+ OID_TIMESTAMP,
+ OID_TEXT,
+ OID_TEXT,
+ OID_TEXT,
+ OID_TEXT,
+ OID_INT4,
+ OID_INT4,
+ }, st_name,
+ "INSERT INTO BASICS ("
+ " bool_col, "
+ " bytea_col, "
+ " bigint_col, "
+ " smallint_col, "
+ " int_col, "
+ " text_col, "
+ " localtime_col, "
+ " varchar_col, "
+ " inet4_col, "
+ " float_col, "
+ " json_col, "
+ " min_int_col, "
+ " max_int_col, "
+ " inet6_col) "
+ " VALUES ($1, $2, $3, $4, $5, $6, $7, $8, cast($9 as inet), "
+ " cast($10 as float), cast($11 as json), $12, $13, $14)"
+ }
+ };
+
+ ASSERT_NO_THROW_LOG(conn_->prepareStatement(statement[0]));
+
+ // Create a bind array of input values.
+ PsqlBindArrayPtr b(new PsqlBindArray());
+
+ bool exp_bool(true);
+ b->add(exp_bool);
+
+ std::vector<uint8_t> exp_bytes({0x01, 0x02, 0x03, 0x04});
+ b->add(exp_bytes);
+
+ uint64_t exp_bigint = 9876;
+ b->add(exp_bigint);
+
+ uint16_t exp_smallint = 12;
+ b->add(exp_smallint);
+
+ uint32_t exp_int = 345;
+ b->add(exp_int);
+
+ std::string exp_text = "just some string";
+ b->add(exp_text);
+
+ time_duration duration = hours(7) + minutes(45) + seconds(9);
+ ptime exp_timestamp(date(2021, Jul, 18), duration);
+ b->addTimestamp(exp_timestamp);
+
+ const char* exp_varchar = "really just a string";
+ b->add(exp_varchar);
+
+ asiolink::IOAddress exp_inet4("192.168.1.35");
+ b->addInet4(exp_inet4);
+
+ double exp_double(2.5);
+ b->add(exp_double);
+
+ ElementPtr exp_elems = Element::fromJSON("{ \"foo\": \"bar\" }");
+ b->add(exp_elems);
+
+ uint32_t exp_min = 100;
+ b->add(exp_min);
+
+ uint32_t exp_max = 500;
+ b->add(exp_max);
+
+ asiolink::IOAddress exp_inet6("3001::77");
+ b->addInet6(exp_inet6);
+
+ PgSqlResultPtr r;
+ RUN_PREP(r, statement[0], b, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Create a row worker.
+ PgSqlResultRowWorkerPtr worker;
+
+ // Creating the row worker for the first (and only) row should succeed.
+ ASSERT_NO_THROW_LOG(worker.reset(new PgSqlResultRowWorker(*r, 0)));
+
+ // Now let's test all the getters.
+ EXPECT_EQ(exp_bool, worker->getBool(BOOL_COL));
+
+ std::vector<uint8_t> fetched_bytes;
+ ASSERT_NO_THROW_LOG(worker->getBytes(BYTEA_COL, fetched_bytes));
+ EXPECT_EQ(exp_bytes, fetched_bytes);
+
+ EXPECT_EQ(exp_bigint, worker->getBigInt(BIGINT_COL));
+ EXPECT_EQ(exp_smallint, worker->getSmallInt(SMALLINT_COL));
+ EXPECT_EQ(exp_int, worker->getInt(INT_COL));
+ EXPECT_EQ(exp_text, worker->getString(TEXT_COL));
+ EXPECT_EQ(exp_timestamp, worker->getTimestamp(LOCALTIME_COL));
+ EXPECT_EQ(exp_varchar, worker->getString(VARCHAR_COL));
+ EXPECT_EQ(exp_inet4, worker->getInet4(INET4_COL));
+ EXPECT_EQ(exp_double, worker->getDouble(FLOAT_COL));
+ EXPECT_EQ(*exp_elems, *(worker->getJSON(JSON_COL)));
+ EXPECT_EQ(exp_min, worker->getInt(MIN_INT_COL));
+ EXPECT_EQ(exp_max, worker->getInt(MAX_INT_COL));
+ EXPECT_EQ(exp_inet6, worker->getInet6(INET6_COL));
+
+ // Get a triplet using int_col as the sole value.
+ Triplet<uint32_t>fetched_triplet = worker->getTriplet(INT_COL);
+ EXPECT_EQ(exp_int, fetched_triplet.get());
+
+ // Get a triplet using int_col, min_col, and max_col values.
+ fetched_triplet = worker->getTriplet(INT_COL, MIN_INT_COL, MAX_INT_COL);
+ EXPECT_EQ(exp_int, fetched_triplet.get());
+ EXPECT_EQ(exp_min, fetched_triplet.getMin());
+ EXPECT_EQ(exp_max, fetched_triplet.getMax());
+
+ // Attempting to access an invalid row should throw.
+ ASSERT_THROW_MSG(worker.reset(new PgSqlResultRowWorker(*r, 1)),
+ DbOperationError, "row: 1, out of range: 0..1");
+}
+
+} // namespace
diff --git a/src/lib/pgsql/tests/run_unittests.cc b/src/lib/pgsql/tests/run_unittests.cc
new file mode 100644
index 0000000..4e83d4b
--- /dev/null
+++ b/src/lib/pgsql/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 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 <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/pgsql/testutils/Makefile.am b/src/lib/pgsql/testutils/Makefile.am
new file mode 100644
index 0000000..195f981
--- /dev/null
+++ b/src/lib/pgsql/testutils/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\"
+AM_CPPFLAGS += -DDATABASE_WIPE_DIR=\"$(abs_top_builddir)/src/share/database/scripts\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libpgsqltest.la
+
+libpgsqltest_la_SOURCES = pgsql_schema.cc pgsql_schema.h
+
+libpgsqltest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libpgsqltest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(PGSQL_CPPFLAGS)
+libpgsqltest_la_LDFLAGS = $(AM_LDFLAGS) $(PGSQL_LIBS)
+
+libpgsqltest_la_LIBADD = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+
+endif
diff --git a/src/lib/pgsql/testutils/Makefile.in b/src/lib/pgsql/testutils/Makefile.in
new file mode 100644
index 0000000..9b8bc29
--- /dev/null
+++ b/src/lib/pgsql/testutils/Makefile.in
@@ -0,0 +1,862 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/pgsql/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libpgsqltest_la_DEPENDENCIES = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+am__libpgsqltest_la_SOURCES_DIST = pgsql_schema.cc pgsql_schema.h
+@HAVE_GTEST_TRUE@am_libpgsqltest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libpgsqltest_la-pgsql_schema.lo
+libpgsqltest_la_OBJECTS = $(am_libpgsqltest_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 =
+libpgsqltest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libpgsqltest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libpgsqltest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libpgsqltest_la_rpath =
+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)/libpgsqltest_la-pgsql_schema.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 = $(libpgsqltest_la_SOURCES)
+DIST_SOURCES = $(am__libpgsqltest_la_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\" \
+ -DDATABASE_WIPE_DIR=\"$(abs_top_builddir)/src/share/database/scripts\" \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libpgsqltest.la
+@HAVE_GTEST_TRUE@libpgsqltest_la_SOURCES = pgsql_schema.cc pgsql_schema.h
+@HAVE_GTEST_TRUE@libpgsqltest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libpgsqltest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(PGSQL_CPPFLAGS)
+@HAVE_GTEST_TRUE@libpgsqltest_la_LDFLAGS = $(AM_LDFLAGS) $(PGSQL_LIBS)
+@HAVE_GTEST_TRUE@libpgsqltest_la_LIBADD = $(top_builddir)/src/lib/database/testutils/libdatabasetest.la
+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/pgsql/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/pgsql/testutils/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libpgsqltest.la: $(libpgsqltest_la_OBJECTS) $(libpgsqltest_la_DEPENDENCIES) $(EXTRA_libpgsqltest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libpgsqltest_la_LINK) $(am_libpgsqltest_la_rpath) $(libpgsqltest_la_OBJECTS) $(libpgsqltest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libpgsqltest_la-pgsql_schema.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)$(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 $@ $<
+
+libpgsqltest_la-pgsql_schema.lo: pgsql_schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libpgsqltest_la_CPPFLAGS) $(CPPFLAGS) $(libpgsqltest_la_CXXFLAGS) $(CXXFLAGS) -MT libpgsqltest_la-pgsql_schema.lo -MD -MP -MF $(DEPDIR)/libpgsqltest_la-pgsql_schema.Tpo -c -o libpgsqltest_la-pgsql_schema.lo `test -f 'pgsql_schema.cc' || echo '$(srcdir)/'`pgsql_schema.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libpgsqltest_la-pgsql_schema.Tpo $(DEPDIR)/libpgsqltest_la-pgsql_schema.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pgsql_schema.cc' object='libpgsqltest_la-pgsql_schema.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) $(libpgsqltest_la_CPPFLAGS) $(CPPFLAGS) $(libpgsqltest_la_CXXFLAGS) $(CXXFLAGS) -c -o libpgsqltest_la-pgsql_schema.lo `test -f 'pgsql_schema.cc' || echo '$(srcdir)/'`pgsql_schema.cc
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libpgsqltest_la-pgsql_schema.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-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)/libpgsqltest_la-pgsql_schema.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/pgsql/testutils/pgsql_schema.cc b/src/lib/pgsql/testutils/pgsql_schema.cc
new file mode 100644
index 0000000..1cf79a5
--- /dev/null
+++ b/src/lib/pgsql/testutils/pgsql_schema.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2016-2022 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 <pgsql//pgsql_connection.h>
+#include <pgsql/testutils/pgsql_schema.h>
+#include <exceptions/exceptions.h>
+
+#include <libpq-fe.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <stdlib.h>
+
+using namespace std;
+
+namespace isc {
+namespace db {
+namespace test {
+
+const char* PGSQL_VALID_TYPE = "type=postgresql";
+
+string
+validPgSQLConnectionString() {
+ return (connectionString(PGSQL_VALID_TYPE, VALID_NAME, VALID_HOST,
+ VALID_USER, VALID_PASSWORD));
+}
+
+void destroyPgSQLSchema(bool show_err, bool force) {
+ // If force is true or wipePgSQLData() fails, destroy the schema.
+ if (force || (!softWipeEnabled()) || wipePgSQLData(show_err)) {
+ runPgSQLScript(DATABASE_SCRIPTS_DIR, "pgsql/dhcpdb_drop.pgsql", show_err);
+ }
+}
+
+void createPgSQLSchema(bool show_err, bool force) {
+ // If force is true or wipePgSQLData() fails, recreate the schema.
+ if (force || (!softWipeEnabled()) || wipePgSQLData(show_err)) {
+ destroyPgSQLSchema(show_err, true);
+ runPgSQLScript(DATABASE_SCRIPTS_DIR, "pgsql/dhcpdb_create.pgsql", show_err);
+ }
+}
+
+bool wipePgSQLData(bool show_err) {
+ std::ostringstream cmd;
+
+ // Pass psql the password via environment variable.
+ cmd << "export PGPASSWORD=keatest;";
+
+ // Add in the wipe shell script invocation.
+ cmd << " sh " << DATABASE_WIPE_DIR << "/pgsql/wipe_data.sh";
+
+ // Add expected schema version as the wipe script's first argument.
+ cmd << " " << PGSQL_SCHEMA_VERSION_MAJOR << "." << PGSQL_SCHEMA_VERSION_MINOR;
+
+ // Now add command line arguments for psql.
+ cmd << " --set ON_ERROR_STOP=1 -A -t -h localhost -q -U keatest -d keatest";
+
+ // Suppress error output.
+ if (!show_err) {
+ cmd << " 2>/dev/null ";
+ }
+
+ // Execute the command string.
+ int retval = ::system(cmd.str().c_str());
+ if (retval) {
+ std::cerr << "wipePgSQLData failed:[" << cmd.str() << "]" << std::endl;
+ }
+
+ return(retval);
+}
+
+void runPgSQLScript(const std::string& path, const std::string& script_name,
+ bool show_err) {
+ std::ostringstream cmd;
+
+ cmd << "export PGPASSWORD=keatest; cat ";
+ if (!path.empty()) {
+ cmd << " < " << path << "/";
+ }
+
+ cmd << script_name
+ << " | psql --set ON_ERROR_STOP=1 -A -t -h localhost -q -U keatest -d keatest";
+
+ if (!show_err) {
+ cmd << " 2>/dev/null ";
+ }
+
+ int retval = ::system(cmd.str().c_str());
+ if (retval) {
+ std::cerr << "runPgSQLSchema failed: " << cmd.str() << std::endl;
+ isc_throw(Unexpected, "runPgSQLSchema failed: " << cmd.str());
+ }
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/pgsql/testutils/pgsql_schema.h b/src/lib/pgsql/testutils/pgsql_schema.h
new file mode 100644
index 0000000..5919670
--- /dev/null
+++ b/src/lib/pgsql/testutils/pgsql_schema.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2016-2022 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 TEST_PGSQL_SCHEMA_H
+#define TEST_PGSQL_SCHEMA_H
+
+#include <config.h>
+#include <database/testutils/schema.h>
+#include <string>
+
+namespace isc {
+namespace db {
+namespace test {
+
+extern const char* PGSQL_VALID_TYPE;
+
+/// Return valid connection string
+///
+/// @return valid PgSQL connection string.
+std::string validPgSQLConnectionString();
+
+/// @brief Clear the unit test database
+///
+/// In order to reduce test execution time, this function
+/// defaults to first attempting to delete transient data
+/// from the database by calling @c wipePgSQLData. If that
+/// function fails it will then attempt to destroy the database
+/// schema by running the SQL script:
+///
+/// <DATABASE_SCRIPTS_DIR>/pgsql/dhcpdb_drop.pgsql
+///
+/// The default behavior of wiping the data only may be overridden
+/// in one of two ways:
+///
+/// -# Setting the force parameter to true
+/// -# Defining the environment variable:
+/// KEA_TEST_DB_WIPE_DATA_ONLY="false"
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @param force if true, the function will skip deleting the data and
+/// destroy the schema.
+void destroyPgSQLSchema(bool show_err = false, bool force = false);
+
+/// @brief Create the unit test PgSQL Schema
+///
+/// Ensures the unit test database is empty and version-correct.
+/// Unless,the force parameter is true, it will first attempt
+/// to wipe the data from the database by calling @c wipePgSQLData.
+/// If this call succeeds the function returns, otherwise it will
+/// call @c destroyPgSQLSchema to forcibly remove the existing
+/// schema and then submits the SQL script:
+///
+/// <DATABASE_SCRIPTS_DIR>/pgsql/dhcpdb_create.pgsql
+///
+/// to the unit test PgSQL database.
+///
+/// The default behavior of wiping the data only may be overridden
+/// in one of two ways:
+///
+/// -# Setting the force parameter to true
+/// -# Defining the environment variable:
+/// KEA_TEST_DB_WIPE_DATA_ONLY="false"
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @param force flag when true, the function will recreate the database
+/// schema.
+void createPgSQLSchema(bool show_err = false, bool force = false);
+
+/// @brief Attempts to wipe data from the PgSQL unit test database
+///
+/// Runs the shell script
+///
+/// <DATABASE_WIPE_DIR>/pgsql/wipe_data.sh
+///
+/// This will fail if there is no schema, if the existing schema
+/// version is incorrect (i.e. does not match PGSQL_SCHEMA_VERSION_MAJOR
+/// and PGSQL_SCHEMA_VERSION_MINOR), or a SQL error occurs. Otherwise,
+/// the script is should delete all transient data, leaving intact
+/// reference tables.
+///
+/// @param show_err flag which governs whether or not stderr is suppressed.
+bool wipePgSQLData(bool show_err = false);
+
+/// @brief Run a PgSQL SQL script against the Postgresql unit test database
+///
+/// Submits the given SQL script to Postgresql via psql CLI. The output of
+/// stderr is suppressed unless the parameter, show_err is true. The is done
+/// to suppress warnings that might otherwise make test output needlessly
+/// noisy. An exception is thrown if the script fails to execute.
+///
+/// @param path - path (if not blank) of the script to execute
+/// @param script_name - file name of the path to execute
+/// @param show_err flag which governs whether or not stderr is suppressed.
+/// @throw Unexpected when the script returns an error.
+void runPgSQLScript(const std::string& path, const std::string& script_name,
+ bool show_err);
+
+};
+};
+};
+
+#endif
diff --git a/src/lib/process/Makefile.am b/src/lib/process/Makefile.am
new file mode 100644
index 0000000..cb20ab8
--- /dev/null
+++ b/src/lib/process/Makefile.am
@@ -0,0 +1,99 @@
+SUBDIRS = cfgrpt . testutils tests
+# DATA_DIR is the directory where to put PID files.
+dhcp_data_dir = @runstatedir@/@PACKAGE@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDATA_DIR="\"$(dhcp_data_dir)\""
+
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = process_messages.mes libprocess.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# Generated by configure files
+DISTCLEANFILES = spec_config.h.pre
+
+lib_LTLIBRARIES = libkea-process.la
+libkea_process_la_SOURCES = cb_ctl_base.h
+libkea_process_la_SOURCES += config_base.cc config_base.h
+libkea_process_la_SOURCES += config_ctl_info.cc config_ctl_info.h
+libkea_process_la_SOURCES += config_ctl_parser.cc config_ctl_parser.h
+libkea_process_la_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+libkea_process_la_SOURCES += d_controller.cc d_controller.h
+libkea_process_la_SOURCES += d_log.cc d_log.h
+libkea_process_la_SOURCES += d_process.h
+libkea_process_la_SOURCES += daemon.cc daemon.h
+libkea_process_la_SOURCES += log_parser.cc log_parser.h
+libkea_process_la_SOURCES += logging_info.cc logging_info.h
+libkea_process_la_SOURCES += process_messages.cc process_messages.h
+libkea_process_la_SOURCES += redact_config.cc redact_config.h
+
+libkea_process_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_process_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_process_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_process_la_LDFLAGS += -no-undefined -version-info 57:0:0
+
+libkea_process_la_LIBADD = $(top_builddir)/src/lib/process/cfgrpt/libcfgrpt.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/http/libkea-http.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/database/libkea-database.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_process_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_process_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f process_messages.h process_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: process_messages.h process_messages.cc
+ @echo Message files regenerated
+
+process_messages.h process_messages.cc: process_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/process/process_messages.mes
+
+else
+
+messages process_messages.h process_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_process_includedir = $(pkgincludedir)/process
+libkea_process_include_HEADERS = \
+ cb_ctl_base.h \
+ config_base.h \
+ config_ctl_info.h \
+ config_ctl_parser.h \
+ daemon.h \
+ d_cfg_mgr.h \
+ d_controller.h \
+ d_log.h \
+ d_process.h \
+ logging_info.h \
+ log_parser.h \
+ process_messages.h \
+ redact_config.h
diff --git a/src/lib/process/Makefile.in b/src/lib/process/Makefile.in
new file mode 100644
index 0000000..c0f22ae
--- /dev/null
+++ b/src/lib/process/Makefile.in
@@ -0,0 +1,1146 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/process
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_process_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)$(libkea_process_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_process_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/process/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_process_la_OBJECTS = libkea_process_la-config_base.lo \
+ libkea_process_la-config_ctl_info.lo \
+ libkea_process_la-config_ctl_parser.lo \
+ libkea_process_la-d_cfg_mgr.lo \
+ libkea_process_la-d_controller.lo libkea_process_la-d_log.lo \
+ libkea_process_la-daemon.lo libkea_process_la-log_parser.lo \
+ libkea_process_la-logging_info.lo \
+ libkea_process_la-process_messages.lo \
+ libkea_process_la-redact_config.lo
+libkea_process_la_OBJECTS = $(am_libkea_process_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_process_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libkea_process_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_process_la-config_base.Plo \
+ ./$(DEPDIR)/libkea_process_la-config_ctl_info.Plo \
+ ./$(DEPDIR)/libkea_process_la-config_ctl_parser.Plo \
+ ./$(DEPDIR)/libkea_process_la-d_cfg_mgr.Plo \
+ ./$(DEPDIR)/libkea_process_la-d_controller.Plo \
+ ./$(DEPDIR)/libkea_process_la-d_log.Plo \
+ ./$(DEPDIR)/libkea_process_la-daemon.Plo \
+ ./$(DEPDIR)/libkea_process_la-log_parser.Plo \
+ ./$(DEPDIR)/libkea_process_la-logging_info.Plo \
+ ./$(DEPDIR)/libkea_process_la-process_messages.Plo \
+ ./$(DEPDIR)/libkea_process_la-redact_config.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_process_la_SOURCES)
+DIST_SOURCES = $(libkea_process_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 = $(libkea_process_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = cfgrpt . testutils tests
+# DATA_DIR is the directory where to put PID files.
+dhcp_data_dir = @runstatedir@/@PACKAGE@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDATA_DIR="\"$(dhcp_data_dir)\"" $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = process_messages.mes libprocess.dox
+CLEANFILES = *.gcno *.gcda
+
+# Generated by configure files
+DISTCLEANFILES = spec_config.h.pre
+lib_LTLIBRARIES = libkea-process.la
+libkea_process_la_SOURCES = cb_ctl_base.h config_base.cc config_base.h \
+ config_ctl_info.cc config_ctl_info.h config_ctl_parser.cc \
+ config_ctl_parser.h d_cfg_mgr.cc d_cfg_mgr.h d_controller.cc \
+ d_controller.h d_log.cc d_log.h d_process.h daemon.cc daemon.h \
+ log_parser.cc log_parser.h logging_info.cc logging_info.h \
+ process_messages.cc process_messages.h redact_config.cc \
+ redact_config.h
+libkea_process_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_process_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_process_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info \
+ 57:0:0
+libkea_process_la_LIBADD = \
+ $(top_builddir)/src/lib/process/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_process_includedir = $(pkgincludedir)/process
+libkea_process_include_HEADERS = \
+ cb_ctl_base.h \
+ config_base.h \
+ config_ctl_info.h \
+ config_ctl_parser.h \
+ daemon.h \
+ d_cfg_mgr.h \
+ d_controller.h \
+ d_log.h \
+ d_process.h \
+ logging_info.h \
+ log_parser.h \
+ process_messages.h \
+ redact_config.h
+
+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/process/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/process/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-process.la: $(libkea_process_la_OBJECTS) $(libkea_process_la_DEPENDENCIES) $(EXTRA_libkea_process_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_process_la_LINK) -rpath $(libdir) $(libkea_process_la_OBJECTS) $(libkea_process_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-config_base.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-config_ctl_info.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-config_ctl_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-d_cfg_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-d_controller.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-d_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-daemon.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-log_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-logging_info.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-process_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_process_la-redact_config.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)$(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 $@ $<
+
+libkea_process_la-config_base.lo: config_base.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-config_base.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-config_base.Tpo -c -o libkea_process_la-config_base.lo `test -f 'config_base.cc' || echo '$(srcdir)/'`config_base.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-config_base.Tpo $(DEPDIR)/libkea_process_la-config_base.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_base.cc' object='libkea_process_la-config_base.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-config_base.lo `test -f 'config_base.cc' || echo '$(srcdir)/'`config_base.cc
+
+libkea_process_la-config_ctl_info.lo: config_ctl_info.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-config_ctl_info.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-config_ctl_info.Tpo -c -o libkea_process_la-config_ctl_info.lo `test -f 'config_ctl_info.cc' || echo '$(srcdir)/'`config_ctl_info.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-config_ctl_info.Tpo $(DEPDIR)/libkea_process_la-config_ctl_info.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_ctl_info.cc' object='libkea_process_la-config_ctl_info.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-config_ctl_info.lo `test -f 'config_ctl_info.cc' || echo '$(srcdir)/'`config_ctl_info.cc
+
+libkea_process_la-config_ctl_parser.lo: config_ctl_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-config_ctl_parser.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-config_ctl_parser.Tpo -c -o libkea_process_la-config_ctl_parser.lo `test -f 'config_ctl_parser.cc' || echo '$(srcdir)/'`config_ctl_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-config_ctl_parser.Tpo $(DEPDIR)/libkea_process_la-config_ctl_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_ctl_parser.cc' object='libkea_process_la-config_ctl_parser.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-config_ctl_parser.lo `test -f 'config_ctl_parser.cc' || echo '$(srcdir)/'`config_ctl_parser.cc
+
+libkea_process_la-d_cfg_mgr.lo: d_cfg_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-d_cfg_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-d_cfg_mgr.Tpo -c -o libkea_process_la-d_cfg_mgr.lo `test -f 'd_cfg_mgr.cc' || echo '$(srcdir)/'`d_cfg_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-d_cfg_mgr.Tpo $(DEPDIR)/libkea_process_la-d_cfg_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_cfg_mgr.cc' object='libkea_process_la-d_cfg_mgr.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-d_cfg_mgr.lo `test -f 'd_cfg_mgr.cc' || echo '$(srcdir)/'`d_cfg_mgr.cc
+
+libkea_process_la-d_controller.lo: d_controller.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-d_controller.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-d_controller.Tpo -c -o libkea_process_la-d_controller.lo `test -f 'd_controller.cc' || echo '$(srcdir)/'`d_controller.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-d_controller.Tpo $(DEPDIR)/libkea_process_la-d_controller.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_controller.cc' object='libkea_process_la-d_controller.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-d_controller.lo `test -f 'd_controller.cc' || echo '$(srcdir)/'`d_controller.cc
+
+libkea_process_la-d_log.lo: d_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-d_log.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-d_log.Tpo -c -o libkea_process_la-d_log.lo `test -f 'd_log.cc' || echo '$(srcdir)/'`d_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-d_log.Tpo $(DEPDIR)/libkea_process_la-d_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_log.cc' object='libkea_process_la-d_log.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-d_log.lo `test -f 'd_log.cc' || echo '$(srcdir)/'`d_log.cc
+
+libkea_process_la-daemon.lo: daemon.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-daemon.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-daemon.Tpo -c -o libkea_process_la-daemon.lo `test -f 'daemon.cc' || echo '$(srcdir)/'`daemon.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-daemon.Tpo $(DEPDIR)/libkea_process_la-daemon.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='daemon.cc' object='libkea_process_la-daemon.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-daemon.lo `test -f 'daemon.cc' || echo '$(srcdir)/'`daemon.cc
+
+libkea_process_la-log_parser.lo: log_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-log_parser.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-log_parser.Tpo -c -o libkea_process_la-log_parser.lo `test -f 'log_parser.cc' || echo '$(srcdir)/'`log_parser.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-log_parser.Tpo $(DEPDIR)/libkea_process_la-log_parser.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_parser.cc' object='libkea_process_la-log_parser.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-log_parser.lo `test -f 'log_parser.cc' || echo '$(srcdir)/'`log_parser.cc
+
+libkea_process_la-logging_info.lo: logging_info.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-logging_info.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-logging_info.Tpo -c -o libkea_process_la-logging_info.lo `test -f 'logging_info.cc' || echo '$(srcdir)/'`logging_info.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-logging_info.Tpo $(DEPDIR)/libkea_process_la-logging_info.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logging_info.cc' object='libkea_process_la-logging_info.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-logging_info.lo `test -f 'logging_info.cc' || echo '$(srcdir)/'`logging_info.cc
+
+libkea_process_la-process_messages.lo: process_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-process_messages.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-process_messages.Tpo -c -o libkea_process_la-process_messages.lo `test -f 'process_messages.cc' || echo '$(srcdir)/'`process_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-process_messages.Tpo $(DEPDIR)/libkea_process_la-process_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='process_messages.cc' object='libkea_process_la-process_messages.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-process_messages.lo `test -f 'process_messages.cc' || echo '$(srcdir)/'`process_messages.cc
+
+libkea_process_la-redact_config.lo: redact_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_process_la-redact_config.lo -MD -MP -MF $(DEPDIR)/libkea_process_la-redact_config.Tpo -c -o libkea_process_la-redact_config.lo `test -f 'redact_config.cc' || echo '$(srcdir)/'`redact_config.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_process_la-redact_config.Tpo $(DEPDIR)/libkea_process_la-redact_config.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='redact_config.cc' object='libkea_process_la-redact_config.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_process_la_CPPFLAGS) $(CPPFLAGS) $(libkea_process_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_process_la-redact_config.lo `test -f 'redact_config.cc' || echo '$(srcdir)/'`redact_config.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_process_includeHEADERS: $(libkea_process_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_process_include_HEADERS)'; test -n "$(libkea_process_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_process_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_process_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)$(libkea_process_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_process_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_process_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_process_include_HEADERS)'; test -n "$(libkea_process_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_process_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_process_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_process_la-config_base.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-config_ctl_info.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-config_ctl_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-d_cfg_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-d_controller.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-d_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-daemon.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-log_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-logging_info.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-process_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-redact_config.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-libkea_process_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_process_la-config_base.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-config_ctl_info.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-config_ctl_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-d_cfg_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-d_controller.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-d_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-daemon.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-log_parser.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-logging_info.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-process_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_process_la-redact_config.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_process_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_process_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_process_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f process_messages.h process_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: process_messages.h process_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@process_messages.h process_messages.cc: process_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/process/process_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages process_messages.h process_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/process/cb_ctl_base.h b/src/lib/process/cb_ctl_base.h
new file mode 100644
index 0000000..7baf8dd
--- /dev/null
+++ b/src/lib/process/cb_ctl_base.h
@@ -0,0 +1,373 @@
+// Copyright (C) 2019-2020 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 CB_CTL_BASE_H
+#define CB_CTL_BASE_H
+
+#include <database/audit_entry.h>
+#include <database/backend_selector.h>
+#include <database/server_selector.h>
+#include <process/config_base.h>
+#include <process/config_ctl_info.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <process/d_log.h>
+
+namespace isc {
+namespace process {
+
+
+/// @brief Base class for implementing server specific mechanisms to control
+/// the use of the Configuration Backends.
+///
+/// Every Kea server using Config Backend as a configuration repository
+/// fetches configuration available for this server during startup and then
+/// periodically while it is running. In both cases, the server has to
+/// take into account that there is some existing configuration that the
+/// server already knows, into which the configuration from the database
+/// has to be merged.
+///
+/// When the server starts up, the existing configuration is the one that
+/// the server reads from the configuration file. Usually, the configuration
+/// fetched from the file will be disjointed with the configuration in the
+/// database, e.g. all subnets should be specified either in the configuration
+/// file or a database, not in both. However, there may be other cases when
+/// option definitions are held in the configuration file, but the DHCP
+/// options using them are stored in the database. The typical configuration
+/// sequence upon the server startup will be to build the staging
+/// configuration from the data stored in the configuration file and then
+/// build another partial configuration from the data fetched from the
+/// database. Finally, both configurations should be merged and committed
+/// if they are deemed sane.
+///
+/// When the server is already running it may use "audit" (a.k.a. journal)
+/// to periodically check if there are any pending configuration updates.
+/// If changes are present, it will be fetched and used to create a new
+/// configuration object (derived from the @c ConfigBase) holding this
+/// partial configuration. This configuration has to be subsequently
+/// merged into the current configuration that the server is using.
+///
+/// Note the difference between these two use cases is that in the first
+/// case the fetched configuration is fetched into the staging configuration
+/// and then committed, and in the second case it has to be directly merged
+/// into the running configuration.
+///
+/// This class contains some common logic to facilitate both scenarios which
+/// will be used by all server types. It also contains some pure virtual
+/// methods to be implemented by specific servers. The common logic includes
+/// the following operations:
+/// - use the "config-control" specification to connect to the specified
+/// databases via the configuration backends,
+/// - fetch the audit trail to detect configuration updates,
+/// - store the timestamp and id of the most recent audit entry fetched
+/// from the database, so as next time it can fetch only the later updates.
+///
+/// The server specific part to be implemented in derived classes must
+/// correctly interpret the audit entries and make appropriate API calls
+/// to fetch the indicated configuration changes. It should also merge
+/// the fetched configuration into the staging or current configuration.
+/// Note that this class doesn't recognize whether it is a staging or
+/// current configuration it is merging into. It simply uses the instance
+/// provided by the caller.
+///
+/// @tparam ConfigBackendMgrType Type of the Config Backend Manager used
+/// by the server implementing this class. For example, for the DHCPv4
+/// server it will be @c ConfigBackendDHCPv4Mgr.
+template<typename ConfigBackendMgrType>
+class CBControlBase {
+public:
+
+ /// @brief Fetch mode used in invocations to @c databaseConfigFetch.
+ ///
+ /// One of the values indicates that the entire configuration should
+ /// be fetched. The other one indicates that only configuration updates
+ /// should be fetched.
+ enum class FetchMode {
+ FETCH_ALL,
+ FETCH_UPDATE
+ };
+
+ /// @brief Constructor.
+ ///
+ /// Sets the time of the last fetched audit entry to Jan 1st, 2000,
+ /// with id 0.
+ CBControlBase()
+ : last_audit_revision_time_(getInitialAuditRevisionTime()),
+ last_audit_revision_id_(0) {
+ }
+
+ /// @brief Virtual destructor.
+ ///
+ /// It is always needed when there are virtual methods.
+ virtual ~CBControlBase() {
+ databaseConfigDisconnect();
+ }
+
+ /// @brief Resets the state of this object.
+ ///
+ /// Disconnects the configuration backends resets the recorded last
+ /// audit revision time and id.
+ void reset() {
+ databaseConfigDisconnect();
+ last_audit_revision_time_ = getInitialAuditRevisionTime();
+ last_audit_revision_id_ = 0;
+ }
+
+ /// @brief (Re)connects to the specified configuration backends.
+ ///
+ /// This method disconnects from any existing configuration backends
+ /// and then connects to those listed in the @c ConfigControlInfo
+ /// structure within the @c srv_cfg argument. This method is called
+ /// when the server starts up. It is not called when it merely
+ /// fetches configuration updates.
+ ///
+ /// @param srv_cfg Pointer to the staging server configuration.
+ ///
+ /// @return true if the server found at least one backend to connect to,
+ /// false if there are no backends available.
+ bool databaseConfigConnect(const ConfigPtr& srv_cfg) {
+ // We need to get rid of any existing backends. These would be any
+ // opened by previous configuration cycle.
+ databaseConfigDisconnect();
+
+ // Fetch the config-control info.
+ ConstConfigControlInfoPtr config_ctl = srv_cfg->getConfigControlInfo();
+ if (!config_ctl || config_ctl->getConfigDatabases().empty()) {
+ // No config dbs, nothing to do.
+ return (false);
+ }
+
+ // Iterate over the configured DBs and instantiate them.
+ for (auto db : config_ctl->getConfigDatabases()) {
+ LOG_INFO(dctl_logger, DCTL_OPEN_CONFIG_DB)
+ .arg(db.redactedAccessString());
+ getMgr().addBackend(db.getAccessString());
+ }
+
+ // Let the caller know we have opened DBs.
+ return (true);
+ }
+
+ /// @brief Disconnects from the configuration backends.
+ void databaseConfigDisconnect() {
+ getMgr().delAllBackends();
+ }
+
+ /// @brief Fetches the entire or partial configuration from the database.
+ ///
+ /// This method is called by the starting up server to fetch and merge
+ /// the entire configuration from the database or to fetch configuration
+ /// updates periodically, e.g. as a result of triggering an interval
+ /// timer callback.
+ ///
+ /// @param srv_cfg pointer to the staging configuration that should
+ /// hold the config backends list and other partial configuration read
+ /// from the file in case the method is called upon the server's start
+ /// up. It is a pointer to the current server configuration if the
+ /// method is called to fetch configuration updates.
+ /// @param fetch_mode value indicating if the method is called upon the
+ /// server start up or it is called to fetch configuration updates.
+ virtual void databaseConfigFetch(const ConfigPtr& srv_cfg,
+ const FetchMode& fetch_mode = FetchMode::FETCH_ALL) {
+ // If the server starts up we need to connect to the database(s).
+ // If there are no databases available simply do nothing.
+ if ((fetch_mode == FetchMode::FETCH_ALL) && !databaseConfigConnect(srv_cfg)) {
+ // There are no CB databases so we're done
+ return;
+ }
+
+ LOG_INFO(dctl_logger, DCTL_CONFIG_FETCH);
+
+ // For now we find data based on first backend that has it.
+ db::BackendSelector backend_selector(db::BackendSelector::Type::UNSPEC);
+
+ // Use the server_tag if set, otherwise use ALL.
+ std::string server_tag = srv_cfg->getServerTag();
+ db::ServerSelector server_selector =
+ (server_tag.empty()? db::ServerSelector::ALL() : db::ServerSelector::ONE(server_tag));
+
+ // This collection will hold the audit entries since the last update if
+ // we're running this method to fetch the configuration updates.
+ db::AuditEntryCollection audit_entries;
+
+ // If we're fetching updates we need to retrieve audit entries to see
+ // which objects have to be updated. If we're performing full reconfiguration
+ // we also need audit entries to set the last_audit_revision_time_ to the
+ // time of the most recent audit entry.
+
+ /// @todo We need a separate API call for the latter case to only
+ /// fetch the last audit entry rather than all of them.
+
+ // Save the timestamp indicating last audit revision time.
+ auto lb_modification_time = last_audit_revision_time_;
+ // Save the identifier indicating last audit revision id.
+ auto lb_modification_id = last_audit_revision_id_;
+
+ audit_entries = getMgr().getPool()->getRecentAuditEntries(backend_selector,
+ server_selector,
+ lb_modification_time,
+ lb_modification_id);
+ // Store the last audit revision time. It should be set to the most recent
+ // audit entry fetched. If returned audit is empty we don't update.
+ updateLastAuditRevisionTimeId(audit_entries);
+
+ // If this is full reconfiguration we don't need the audit entries anymore.
+ // Let's remove them and proceed as if they don't exist.
+ if (fetch_mode == FetchMode::FETCH_ALL) {
+ audit_entries.clear();
+ }
+
+ // If we fetch the entire config or we're updating the config and there are
+ // audit entries indicating that there are some pending updates, let's
+ // execute the server specific function that fetches and merges the data
+ // into the given configuration.
+ if ((fetch_mode == FetchMode::FETCH_ALL) || !audit_entries.empty()) {
+ try {
+ databaseConfigApply(backend_selector, server_selector,
+ lb_modification_time, audit_entries);
+ } catch (...) {
+ // Revert last audit revision time and id so as we can retry
+ // from the last successful attempt.
+ /// @todo Consider reverting to the initial value to reload
+ /// the entire configuration if the update failed.
+ last_audit_revision_time_ = lb_modification_time;
+ last_audit_revision_id_ = lb_modification_id;
+ throw;
+ }
+ }
+ }
+
+protected:
+
+ /// @brief Returns audit entries for new or updated configuration
+ /// elements of specific type to be fetched from the database.
+ ///
+ /// This is convenience method invoked from the implementations of the
+ /// @c databaseConfigApply function. This method is invoked when the
+ /// server should fetch the updates to the existing configuration.
+ /// The collection of audit entries contains audit entries indicating
+ /// the updates to be fetched. This method returns audit entries for
+ /// updated configuration elements of the specific type the
+ /// @c databaseConfigApply should fetch. The returned collection od
+ /// audit entries contains CREATE or UPDATE entries of the specific type.
+ ///
+ /// @param audit_entries collection od audit entries to filter.
+ /// @param object_type object type to filter with.
+ /// @return audit entries collection with CREATE or UPDATE entries
+ /// of the specific type be fetched from the database.
+ db::AuditEntryCollection
+ fetchConfigElement(const db::AuditEntryCollection& audit_entries,
+ const std::string& object_type) const {
+ db::AuditEntryCollection result;
+ const auto& index = audit_entries.get<db::AuditEntryObjectTypeTag>();
+ auto range = index.equal_range(object_type);
+ for (auto it = range.first; it != range.second; ++it) {
+ if ((*it)->getModificationType() != db::AuditEntry::ModificationType::DELETE) {
+ result.insert(*it);
+ }
+ }
+
+ return (result);
+ }
+
+ /// @brief Server specific method to fetch and apply back end
+ /// configuration into the local configuration.
+ ///
+ /// This pure virtual method must be implemented in the derived classes
+ /// to provide server specific implementation of fetching and applying
+ /// the configuration. The implementations should perform the following
+ /// sequence of operations:
+ /// - Check if any audit entries exist. If none exist, assume that this
+ /// is the case of full server (re)configuration, otherwise assume
+ /// that configuration update is being performed.
+ /// - Select audit entries which indicate deletion of any configuration
+ /// elements. For each such audit entry delete the given object from
+ /// the local configuration.
+ /// - If the server is performing full reconfiguration, fetch the entire
+ /// configuration for the server. If the server is merely updating
+ /// the server configuration, fetch only those objects for which
+ /// (create/update) audit entries exist.
+ /// - Merge the fetched configuration into the local server's
+ /// configuration.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param lb_modification_time Lower bound modification time for the
+ /// configuration elements to be fetched.
+ /// @param audit_entries Audit entries fetched from the database since
+ /// the last configuration update. This collection is empty if there
+ /// were no updates.
+ virtual void databaseConfigApply(const db::BackendSelector& backend_selector,
+ const db::ServerSelector& server_selector,
+ const boost::posix_time::ptime& lb_modification_time,
+ const db::AuditEntryCollection& audit_entries) = 0;
+
+ /// @brief Returns the instance of the Config Backend Manager used by
+ /// this object.
+ ///
+ /// @return Reference to the CB Manager instance.
+ ConfigBackendMgrType& getMgr() const {
+ return (ConfigBackendMgrType::instance());
+ }
+
+ /// @brief Convenience method returning initial timestamp to set the
+ /// @c last_audit_revision_time_ to.
+ ///
+ /// @return Returns 2000-Jan-01 00:00:00 in local time.
+ static boost::posix_time::ptime getInitialAuditRevisionTime() {
+ static boost::posix_time::ptime
+ initial_time(boost::gregorian::date(2000, boost::gregorian::Jan, 1));
+ return (initial_time);
+ }
+
+ /// @brief Updates timestamp of the most recent audit entry fetched from
+ /// the database.
+ ///
+ /// If the collection of audit entries is empty, this method simply
+ /// returns without updating the timestamp.
+ ///
+ /// @param audit_entries reference to the collection of the fetched audit entries.
+ void updateLastAuditRevisionTimeId(const db::AuditEntryCollection& audit_entries) {
+ // Do nothing if there are no audit entries. It is the case if
+ // there were no updates to the configuration.
+ if (audit_entries.empty()) {
+ return;
+ }
+
+ // Get the audit entries sorted by modification time and id,
+ // and pick the latest entry.
+ const auto& index = audit_entries.get<db::AuditEntryModificationTimeIdTag>();
+ last_audit_revision_time_ = (*index.rbegin())->getModificationTime();
+ last_audit_revision_id_ = (*index.rbegin())->getRevisionId();
+ }
+
+ /// @brief Stores the most recent audit revision timestamp.
+ boost::posix_time::ptime last_audit_revision_time_;
+
+ /// @brief Stores the most recent audit revision identifier.
+ ///
+ /// The identifier is used when two (or more) audit entries have
+ /// the same timestamp. It is not used by itself because timestamps
+ /// are more user friendly. Unfortunately old versions of MySQL do not
+ /// support millisecond timestamps.
+ uint64_t last_audit_revision_id_;
+};
+
+/// @brief Checks if an object is in a collection od audit entries.
+///
+/// @param audit_entries collection od audit entries to search for.
+/// @param object_id object identifier.
+inline bool
+hasObjectId(const db::AuditEntryCollection& audit_entries,
+ const uint64_t& object_id) {
+ const auto& object_id_idx = audit_entries.get<db::AuditEntryObjectIdTag>();
+ return (object_id_idx.count(object_id) > 0);
+}
+
+} // end of namespace isc::process
+} // end of namespace isc
+
+#endif /* CB_CTL_BASE_H */
diff --git a/src/lib/process/cfgrpt/Makefile.am b/src/lib/process/cfgrpt/Makefile.am
new file mode 100644
index 0000000..8df9629
--- /dev/null
+++ b/src/lib/process/cfgrpt/Makefile.am
@@ -0,0 +1,29 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda config_report.cc
+
+# config_report.cc is generated below
+BUILT_SOURCES = config_report.cc
+
+# convenience archive
+noinst_LTLIBRARIES = libcfgrpt.la
+
+nodist_libcfgrpt_la_SOURCES = config_report.cc
+libcfgrpt_la_SOURCES = config_report.h cfgrpt.cc
+
+# set pathname to the input configuration report
+report_file = $(abs_top_builddir)/config.report
+
+# Generate config_report.cc
+config_report.cc: $(report_file)
+ ${SHELL} $(top_srcdir)/tools/mk_cfgrpt.sh $(report_file) $(top_builddir)/src/lib/process/cfgrpt/config_report.cc
+
+# Specify the headers for copying into the installation directory tree. The
+# following headers are anticipated to be useful for the user libraries.
+libkea_cfgrpt_includedir = $(pkgincludedir)/cfgrpt
+libkea_cfgrpt_include_HEADERS = \
+ config_report.h
diff --git a/src/lib/process/cfgrpt/Makefile.in b/src/lib/process/cfgrpt/Makefile.in
new file mode 100644
index 0000000..24a3183
--- /dev/null
+++ b/src/lib/process/cfgrpt/Makefile.in
@@ -0,0 +1,930 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/process/cfgrpt
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_cfgrpt_include_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libcfgrpt_la_LIBADD =
+am_libcfgrpt_la_OBJECTS = cfgrpt.lo
+nodist_libcfgrpt_la_OBJECTS = config_report.lo
+libcfgrpt_la_OBJECTS = $(am_libcfgrpt_la_OBJECTS) \
+ $(nodist_libcfgrpt_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 =
+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)/cfgrpt.Plo \
+ ./$(DEPDIR)/config_report.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 = $(libcfgrpt_la_SOURCES) $(nodist_libcfgrpt_la_SOURCES)
+DIST_SOURCES = $(libcfgrpt_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
+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)$(libkea_cfgrpt_includedir)"
+HEADERS = $(libkea_cfgrpt_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda config_report.cc
+
+# config_report.cc is generated below
+BUILT_SOURCES = config_report.cc
+
+# convenience archive
+noinst_LTLIBRARIES = libcfgrpt.la
+nodist_libcfgrpt_la_SOURCES = config_report.cc
+libcfgrpt_la_SOURCES = config_report.h cfgrpt.cc
+
+# set pathname to the input configuration report
+report_file = $(abs_top_builddir)/config.report
+
+# Specify the headers for copying into the installation directory tree. The
+# following headers are anticipated to be useful for the user libraries.
+libkea_cfgrpt_includedir = $(pkgincludedir)/cfgrpt
+libkea_cfgrpt_include_HEADERS = \
+ config_report.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/process/cfgrpt/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/process/cfgrpt/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libcfgrpt.la: $(libcfgrpt_la_OBJECTS) $(libcfgrpt_la_DEPENDENCIES) $(EXTRA_libcfgrpt_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(libcfgrpt_la_OBJECTS) $(libcfgrpt_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cfgrpt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config_report.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_cfgrpt_includeHEADERS: $(libkea_cfgrpt_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_cfgrpt_include_HEADERS)'; test -n "$(libkea_cfgrpt_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_cfgrpt_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_cfgrpt_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)$(libkea_cfgrpt_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_cfgrpt_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_cfgrpt_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_cfgrpt_include_HEADERS)'; test -n "$(libkea_cfgrpt_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_cfgrpt_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)$(libkea_cfgrpt_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) 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."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/cfgrpt.Plo
+ -rm -f ./$(DEPDIR)/config_report.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-libkea_cfgrpt_includeHEADERS
+
+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)/cfgrpt.Plo
+ -rm -f ./$(DEPDIR)/config_report.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-libkea_cfgrpt_includeHEADERS
+
+.MAKE: $(am__recursive_targets) all check install install-am \
+ install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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-libkea_cfgrpt_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-libkea_cfgrpt_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Generate config_report.cc
+config_report.cc: $(report_file)
+ ${SHELL} $(top_srcdir)/tools/mk_cfgrpt.sh $(report_file) $(top_builddir)/src/lib/process/cfgrpt/config_report.cc
+
+# 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/process/cfgrpt/cfgrpt.cc b/src/lib/process/cfgrpt/cfgrpt.cc
new file mode 100644
index 0000000..bf5d40a
--- /dev/null
+++ b/src/lib/process/cfgrpt/cfgrpt.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2015-2023 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 <process/cfgrpt/config_report.h>
+
+namespace isc {
+namespace detail {
+
+// The config_report array ends with an empty line ("")
+// Each line before this final one starts with four semicolons (;;;;)
+// in order to be easy to extract from binaries.
+std::string
+getConfigReport() {
+ std::stringstream tmp;
+
+ size_t linenum = 0;
+ for (;;) {
+ const char* const line = config_report[linenum++];
+ if (line[0] == '\0')
+ break;
+ tmp << line + 4 << std::endl;
+ }
+ return (tmp.str());
+}
+
+}
+}
diff --git a/src/lib/process/cfgrpt/config_report.h b/src/lib/process/cfgrpt/config_report.h
new file mode 100644
index 0000000..c92f1c1
--- /dev/null
+++ b/src/lib/process/cfgrpt/config_report.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2015-2023 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 CONFIG_REPORT_H
+#define CONFIG_REPORT_H
+
+#include <string>
+
+namespace isc {
+namespace detail {
+
+extern const char* const config_report[];
+
+// The config_report array ends with an empty line ("")
+// Each line before this final one starts with four semicolons (;;;;)
+// in order to be easy to extract from binaries.
+std::string getConfigReport();
+
+}
+}
+
+#endif // CONFIG_REPORT_H
diff --git a/src/lib/process/cfgrpt/tests/Makefile.am b/src/lib/process/cfgrpt/tests/Makefile.am
new file mode 100644
index 0000000..74bdc43
--- /dev/null
+++ b/src/lib/process/cfgrpt/tests/Makefile.am
@@ -0,0 +1,27 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+
+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 = config_report_unittests.cc run_unittests.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(top_builddir)/src/lib/process/cfgrpt/libcfgrpt.la
+run_unittests_LDADD += $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/process/cfgrpt/tests/Makefile.in b/src/lib/process/cfgrpt/tests/Makefile.in
new file mode 100644
index 0000000..6f9e5d9
--- /dev/null
+++ b/src/lib/process/cfgrpt/tests/Makefile.in
@@ -0,0 +1,990 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/process/cfgrpt/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = config_report_unittests.cc \
+ run_unittests.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = run_unittests-config_report_unittests.$(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 = $(top_builddir)/src/lib/process/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(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-config_report_unittests.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.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 =
+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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+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 = config_report_unittests.cc run_unittests.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/process/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(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/process/cfgrpt/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/process/cfgrpt/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-config_report_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.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-config_report_unittests.o: config_report_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-config_report_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-config_report_unittests.Tpo -c -o run_unittests-config_report_unittests.o `test -f 'config_report_unittests.cc' || echo '$(srcdir)/'`config_report_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-config_report_unittests.Tpo $(DEPDIR)/run_unittests-config_report_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_report_unittests.cc' object='run_unittests-config_report_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-config_report_unittests.o `test -f 'config_report_unittests.cc' || echo '$(srcdir)/'`config_report_unittests.cc
+
+run_unittests-config_report_unittests.obj: config_report_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-config_report_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-config_report_unittests.Tpo -c -o run_unittests-config_report_unittests.obj `if test -f 'config_report_unittests.cc'; then $(CYGPATH_W) 'config_report_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_report_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-config_report_unittests.Tpo $(DEPDIR)/run_unittests-config_report_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_report_unittests.cc' object='run_unittests-config_report_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-config_report_unittests.obj `if test -f 'config_report_unittests.cc'; then $(CYGPATH_W) 'config_report_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_report_unittests.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-config_report_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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-config_report_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.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/process/cfgrpt/tests/config_report_unittests.cc b/src/lib/process/cfgrpt/tests/config_report_unittests.cc
new file mode 100644
index 0000000..b5e8f5b
--- /dev/null
+++ b/src/lib/process/cfgrpt/tests/config_report_unittests.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2015-2023 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 <kea_version.h>
+#include <process/cfgrpt/config_report.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace std;
+
+// This test verifies that the getConfigReport() function
+// returns the actual config report.
+TEST(ConfigReportTest, getConfigReport) {
+
+ // Fetch the report string
+ std::string cfgReport = isc::detail::getConfigReport();
+
+ // Verify that it is not empty and does contain the
+ // extended version number
+ ASSERT_FALSE(cfgReport.empty());
+ EXPECT_NE(std::string::npos, cfgReport.find(EXTENDED_VERSION, 0));
+}
diff --git a/src/lib/process/cfgrpt/tests/run_unittests.cc b/src/lib/process/cfgrpt/tests/run_unittests.cc
new file mode 100644
index 0000000..feb0d8e
--- /dev/null
+++ b/src/lib/process/cfgrpt/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// Copyright (C) 2015-2023 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>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/process/config_base.cc b/src/lib/process/config_base.cc
new file mode 100644
index 0000000..1e02fad
--- /dev/null
+++ b/src/lib/process/config_base.cc
@@ -0,0 +1,143 @@
+// Copyright (C) 2018-2022 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 <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <log/logger_specification.h>
+#include <process/config_base.h>
+
+#include <list>
+
+using namespace isc::log;
+using namespace isc::data;
+
+namespace isc {
+namespace process {
+
+void
+ConfigBase::applyLoggingCfg() const {
+ std::list<LoggerSpecification> specs;
+ for (LoggingInfo const& logging_info : logging_info_) {
+ if (logging_info.name_ == getRootLoggerName()) {
+ // Root logger has to be processed first if we expect child loggers
+ // to inherit configuration from it.
+ specs.push_front(logging_info.toSpec());
+ } else {
+ specs.push_back(logging_info.toSpec());
+ }
+ }
+ LoggerManager manager;
+ manager.process(specs.begin(), specs.end());
+}
+
+bool
+ConfigBase::equals(const ConfigBase& other) const {
+ // If number of loggers is different, then configurations aren't equal.
+ if (logging_info_.size() != other.logging_info_.size()) {
+ return (false);
+ }
+ // Pass through all loggers and try to find the match for each of them
+ // with the loggers from the other configuration. The order doesn't
+ // matter so we can't simply compare the vectors.
+ for (LoggingInfoStorage::const_iterator this_it =
+ logging_info_.begin(); this_it != logging_info_.end();
+ ++this_it) {
+ bool match = false;
+ for (LoggingInfoStorage::const_iterator other_it =
+ other.logging_info_.begin();
+ other_it != other.logging_info_.end(); ++other_it) {
+ if (this_it->equals(*other_it)) {
+ match = true;
+ break;
+ }
+ }
+ // No match found for the particular logger so return false.
+ if (!match) {
+ return (false);
+ }
+ }
+
+ // Check config control info for equality.
+ if ((config_ctl_info_ && !other.config_ctl_info_) ||
+ (!config_ctl_info_ && other.config_ctl_info_) ||
+ ((config_ctl_info_ && other.config_ctl_info_) &&
+ (!config_ctl_info_->equals(*(other.config_ctl_info_))))) {
+ return (false);
+ }
+
+ return (true);
+}
+
+void
+ConfigBase::copy(ConfigBase& other) const {
+ // We will entirely replace loggers in the new configuration.
+ other.logging_info_.clear();
+ for (LoggingInfoStorage::const_iterator it = logging_info_.begin();
+ it != logging_info_.end(); ++it) {
+ other.addLoggingInfo(*it);
+ }
+
+ // Clone the config control info
+ if (config_ctl_info_) {
+ other.config_ctl_info_.reset(new ConfigControlInfo(*config_ctl_info_));
+ } else {
+ other.config_ctl_info_.reset();
+ }
+
+ // Clone server tag.
+ other.server_tag_ = server_tag_;
+}
+
+void
+ConfigBase::merge(ConfigBase& other) {
+ // Merge logging info.
+ if (!other.logging_info_.empty()) {
+ logging_info_ = other.logging_info_;
+ }
+
+ // Merge the config control info
+ if (other.config_ctl_info_) {
+ if (config_ctl_info_) {
+ config_ctl_info_->merge(*other.config_ctl_info_);
+ } else {
+ config_ctl_info_ = other.config_ctl_info_;
+ }
+ }
+
+ // Merge server tag.
+ if (!other.server_tag_.unspecified()) {
+ server_tag_ = other.server_tag_.get();
+ }
+}
+
+ElementPtr
+ConfigBase::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Was in the Logging global map.
+ if (!logging_info_.empty()) {
+ // Set loggers list
+ ElementPtr loggers = Element::createList();
+ for (LoggingInfoStorage::const_iterator logger =
+ logging_info_.cbegin();
+ logger != logging_info_.cend(); ++logger) {
+ loggers->add(logger->toElement());
+ }
+ result->set("loggers", loggers);
+ }
+
+ // server-tag
+ if (!server_tag_.unspecified()) {
+ result->set("server-tag", Element::create(server_tag_.get()));
+ }
+
+ return (result);
+}
+
+} // namespace process
+} // namespace isc
diff --git a/src/lib/process/config_base.h b/src/lib/process/config_base.h
new file mode 100644
index 0000000..1a99d4c
--- /dev/null
+++ b/src/lib/process/config_base.h
@@ -0,0 +1,181 @@
+// Copyright (C) 2018-2019 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 CONFIG_BASE_H
+#define CONFIG_BASE_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <process/config_ctl_info.h>
+#include <process/logging_info.h>
+#include <util/optional.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace isc {
+namespace process {
+
+/// @brief Base class for all configurations
+///
+/// This is a common base class that represents configurations.
+/// SrvConfig, D2CfgContext, CtrlAgentCfgContext and possibly other
+/// classes holding configurations are derived from this.
+///
+/// It should contain only those elements that are applicable to really
+/// every daemon we may have. Before adding anything here, please consider
+/// whether it would be usable by all of the following: DHCP servers,
+/// DDNS update daemon, Control Agent, Netconf daemon, DHCP relay,
+/// DHCP client.
+///
+/// This class currently holds information about common server configuration.
+class ConfigBase : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+ /// @name Modifiers and accesors for the configuration objects.
+ ///
+ /// @warning References to the objects returned by accessors are only
+ /// valid during the lifetime of the @c ConfigBase object which
+ /// returned them.
+ ///
+ //@{
+ /// @brief Returns logging specific configuration.
+ const process::LoggingInfoStorage& getLoggingInfo() const {
+ return (logging_info_);
+ }
+
+ /// @brief Sets logging specific configuration.
+ ///
+ /// @param logging_info New logging configuration.
+ void addLoggingInfo(const process::LoggingInfo& logging_info) {
+ logging_info_.push_back(logging_info);
+ }
+
+ /// @brief Apply logging configuration to log4cplus.
+ void applyLoggingCfg() const;
+
+ /// @brief Compares two configuration.
+ ///
+ /// @param other the other configuration to compare to
+ bool equals(const ConfigBase& other) const;
+
+ /// @brief Merges specified configuration into this configuration.
+ ///
+ /// This method merges logging and config control configuration into
+ /// this configuration. The new logging configuration replaces the
+ /// existing configuration if the new logging configuration is
+ /// non-empty. The new config control configuration replaces the
+ /// existing configuration if the new logging configuration is
+ /// non-null and non-empty.
+ ///
+ /// @warning The call to @c merge may modify the data in the @c other
+ /// object. Therefore, the caller must not rely on the data held
+ /// in the @c other object after the call to @c merge. Also, the
+ /// data held in @c other must not be modified after the call to
+ /// @c merge because it may affect the merged configuration.
+ ///
+ /// If a derivation of this class implements the @c merge method
+ /// it should call @c ConfigBase::merge.
+ ///
+ /// @param other the other configuration to be merged into this
+ /// configuration.
+ virtual void merge(ConfigBase& other);
+
+ /// @brief Converts to Element representation
+ ///
+ /// This creates a Map element with the following content (expressed
+ /// as JSON):
+ /// {{{
+ /// {
+ /// "Server": {
+ /// :
+ /// }
+ /// }
+ /// }}}
+ ///
+ /// Note that it will not contain the configuration control information
+ /// (i.e. "config-control"), as this is not a top-level element, rather
+ /// it belongs within the configured process element.
+ ///
+ /// @return Element representation.
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Fetches a read-only copy of the configuration control
+ /// information
+ /// @return pointer to the const ConfigControlInfo
+ process::ConstConfigControlInfoPtr getConfigControlInfo() const {
+ return (config_ctl_info_);
+ }
+
+ /// @brief Set the configuration control information
+ ///
+ /// Updates the internal pointer to the configuration control
+ /// information with the given pointer value. If the given pointer
+ /// is empty, the internal pointer will be reset.
+ ///
+ /// @param config_ctl_info pointer to the configuration value
+ /// to store.
+ void setConfigControlInfo(const process::ConfigControlInfoPtr&
+ config_ctl_info) {
+ config_ctl_info_ = config_ctl_info;
+ }
+
+ /// @brief Sets the server's logical name
+ ///
+ /// @param server_tag a unique string name which identifies this server
+ /// from any other configured servers
+ void setServerTag(const util::Optional<std::string>& server_tag) {
+ server_tag_ = server_tag;
+ }
+
+ /// @brief Returns the server's logical name
+ ///
+ /// @return string containing the server's tag
+ util::Optional<std::string> getServerTag() const {
+ return (server_tag_);
+ }
+
+ /// @brief Returns the last commit timestamp.
+ /// @return the last commit timestamp.
+ boost::posix_time::ptime getLastCommitTime() const {
+ return (last_commit_time_);
+ }
+
+ /// @brief Sets the last commit timestamp.
+ /// @param last_commit_time last commit timestamp.
+ void setLastCommitTime(const boost::posix_time::ptime& last_commit_time) {
+ last_commit_time_ = last_commit_time;
+ }
+
+protected:
+ /// @brief Copies the current configuration to a new configuration.
+ ///
+ /// This method copies only the parameters defined in this class.
+ /// Since derived classes are expected to provide their own
+ /// copy methods, this one is protected and can be used only
+ /// by descendant classes.
+ ///
+ /// @param new_config this configuration will be copied to new_config
+ void copy(ConfigBase& new_config) const;
+
+private:
+ /// @brief Logging specific information.
+ process::LoggingInfoStorage logging_info_;
+
+ /// @brief Configuration control information.
+ process::ConfigControlInfoPtr config_ctl_info_;
+
+ /// @brief Logical name of the server
+ util::Optional<std::string> server_tag_;
+
+ /// @brief Stores the last commit timestamp.
+ boost::posix_time::ptime last_commit_time_;
+};
+
+/// @brief Non-const pointer to the @c ConfigBase.
+typedef boost::shared_ptr<ConfigBase> ConfigPtr;
+
+};
+};
+
+#endif /* CONFIG_BASE_H */
diff --git a/src/lib/process/config_ctl_info.cc b/src/lib/process/config_ctl_info.cc
new file mode 100644
index 0000000..5c6aca7
--- /dev/null
+++ b/src/lib/process/config_ctl_info.cc
@@ -0,0 +1,130 @@
+// Copyright (C) 2018-2021 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 <process/config_ctl_info.h>
+
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace process {
+
+void
+ConfigDbInfo::setAccessString(const std::string& access_str, bool test_mode) {
+ access_str_ = access_str;
+ access_params_.clear();
+ if (!test_mode) {
+ access_params_ = db::DatabaseConnection::parse(access_str_);
+ }
+}
+
+bool
+ConfigDbInfo::equals(const ConfigDbInfo& other) const {
+ return (access_params_ == other.access_params_);
+}
+
+isc::data::ElementPtr
+ConfigDbInfo::toElement() const {
+ return (isc::db::DatabaseConnection::toElementDbAccessString(access_str_));
+}
+
+bool
+ConfigDbInfo::getParameterValue(const std::string& name, std::string& value) const {
+ auto param = access_params_.find(name);
+ if (param == access_params_.end()) {
+ return (false);
+ }
+
+ value = param->second;
+ return (true);
+}
+
+//******** ConfigControlInfo ********//
+
+ConfigControlInfo::ConfigControlInfo(const ConfigControlInfo& other)
+ : config_fetch_wait_time_(other.config_fetch_wait_time_) {
+ for (auto db : other.db_infos_) {
+ addConfigDatabase(db.getAccessString());
+ }
+}
+
+void
+ConfigControlInfo::addConfigDatabase(const std::string& access_str) {
+ ConfigDbInfo new_db;
+ new_db.setAccessString(access_str);
+
+ for (auto db : db_infos_) {
+ if (new_db == db) {
+ // we have a duplicate!
+ isc_throw(BadValue, "database with access parameters: "
+ << access_str << " already exists");
+ }
+ }
+
+ db_infos_.push_back(new_db);
+}
+
+const ConfigDbInfo&
+ConfigControlInfo::findConfigDb(const std::string& param_name,
+ const std::string& param_value) {
+ for (ConfigDbInfoList::iterator db = db_infos_.begin();
+ db != db_infos_.end(); ++db) {
+ std::string db_value;
+ if (db->getParameterValue(param_name, db_value) &&
+ (param_value == db_value)) {
+ return (*db);
+ }
+ }
+
+ return (EMPTY_DB());
+}
+
+const ConfigDbInfo&
+ConfigControlInfo::EMPTY_DB() {
+ static ConfigDbInfo empty;
+ return (empty);
+}
+
+void
+ConfigControlInfo::clear() {
+ db_infos_.clear();
+ config_fetch_wait_time_ = Optional<uint16_t>(30, true);
+}
+
+void
+ConfigControlInfo::merge(const ConfigControlInfo& other) {
+ if (!other.db_infos_.empty()) {
+ db_infos_ = other.db_infos_;
+ }
+}
+
+ElementPtr
+ConfigControlInfo::toElement() const {
+ ElementPtr result = Element::createMap();
+ ElementPtr db_list = Element::createList();
+ for (auto db_info : db_infos_) {
+ db_list->add(db_info.toElement());
+ }
+
+ result->set("config-databases", db_list);
+
+ if (!config_fetch_wait_time_.unspecified()) {
+ result->set("config-fetch-wait-time",
+ Element::create(static_cast<int>(config_fetch_wait_time_)));
+ }
+
+ return (result);
+}
+
+bool
+ConfigControlInfo::equals(const ConfigControlInfo& other) const {
+ return ((db_infos_ == other.db_infos_) &&
+ (config_fetch_wait_time_.get() == other.config_fetch_wait_time_.get()));
+}
+
+} // end of namespace isc::process
+} // end of namespace isc
diff --git a/src/lib/process/config_ctl_info.h b/src/lib/process/config_ctl_info.h
new file mode 100644
index 0000000..a4fbedb
--- /dev/null
+++ b/src/lib/process/config_ctl_info.h
@@ -0,0 +1,247 @@
+// Copyright (C) 2018-2022 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 PROCESS_CONFIG_CTL_INFO_H
+#define PROCESS_CONFIG_CTL_INFO_H
+
+#include <cc/cfg_to_element.h>
+#include <database/database_connection.h>
+#include <util/optional.h>
+
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+#include <vector>
+
+namespace isc {
+namespace process {
+
+/// @brief Provides configuration information used during a server's
+/// configuration process.
+///
+class ConfigDbInfo : public isc::data::CfgToElement {
+public:
+ /// @brief Constructor
+ ConfigDbInfo() {};
+
+ /// @brief Set the access string.
+ ///
+ /// Sets the db's access string to the given value and then parses it
+ /// into name-value pairs and storing them internally as a
+ /// DatabaseConnection::ParameterMap. It discards the existing content
+ /// of the map first. It does not validate the parameter names of values,
+ /// ensuring the validity of the string content is placed upon the caller.
+ ///
+ /// @param access_str string of name=value pairs separated by spaces.
+ /// @param test_mode flag used in unittests only to skip parsing the access
+ /// string and storing the parameters.
+ void setAccessString(const std::string& access_str, bool test_mode = false);
+
+ /// @brief Retrieves the database access string.
+ ///
+ /// @return database access string.
+ std::string getAccessString() const {
+ return (access_str_);
+ }
+
+ /// @brief Retrieves the database access string with password redacted.
+ ///
+ /// @return database access string with password redacted.
+ std::string redactedAccessString() const {
+ return (db::DatabaseConnection::redactedAccessString(access_params_));
+ }
+
+ /// @brief Retrieve the map of parameter values.
+ ///
+ /// @return Constant reference to the database's parameter map.
+ const db::DatabaseConnection::ParameterMap& getParameters() const {
+ return (access_params_);
+ }
+
+ /// @brief Fetch the value of a given parameter.
+ ///
+ /// @param name name of the parameter value to fetch.
+ /// @param[out] value string which will contain the value of the
+ /// parameter (if found).
+ ///
+ /// @return Boolean true if the parameter named is found in the map,
+ /// false otherwise.
+ bool getParameterValue(const std::string& name,
+ std::string& value) const;
+
+ /// @brief Unparse a configuration object.
+ ///
+ /// @return a pointer to unparsed configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Compares two objects for equality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool equals(const ConfigDbInfo& other) const;
+
+ /// @brief Compares two objects for equality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool operator==(const ConfigDbInfo& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Compares two objects for inequality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are not equal, false otherwise.
+ bool operator!=(const ConfigDbInfo& other) const {
+ return (!equals(other));
+ }
+
+private:
+ /// @brief Access string of parameters used to access this database.
+ std::string access_str_;
+
+ /// @brief Map of the access parameters and their values.
+ db::DatabaseConnection::ParameterMap access_params_;
+};
+
+typedef std::vector<ConfigDbInfo> ConfigDbInfoList;
+
+/// @brief Embodies configuration information used during a server's
+/// configuration process.
+///
+/// This is class conveys the configuration control information
+/// described by the following JSON text:
+///
+/// @code
+/// "config-control" :
+/// {
+/// "config-databases":
+/// [
+/// {
+/// # first config db
+/// # common database access parameters
+/// "type": <"mysql"|"postgresql">,
+/// "name": <"db name">,
+/// "host": <"db host name">,
+/// :
+/// },
+/// {
+/// # next config db
+/// }
+/// ]
+///
+/// }
+/// @endcode
+///
+class ConfigControlInfo : public isc::data::CfgToElement {
+public:
+
+ /// @brief Constructor.
+ ConfigControlInfo()
+ : config_fetch_wait_time_(30, true) {};
+
+ /// @brief Copy Constructor.
+ ConfigControlInfo(const ConfigControlInfo& other);
+
+ /// @brief Sets new value of the config-fetch-wait-time.
+ ///
+ /// @param config_fetch_wait_time New value of the parameter which
+ /// specifies a time period in seconds between the attempts to
+ /// fetch the server configuration updates. The value of 0 disables
+ /// the periodic attempts to fetch the updates.
+ void setConfigFetchWaitTime(const util::Optional<uint16_t>& config_fetch_wait_time) {
+ config_fetch_wait_time_ = config_fetch_wait_time;
+ }
+
+ /// @brief Returns configured config-fetch-wait-time value.
+ ///
+ /// This value specifies the time period in seconds between the
+ /// attempts to fetch the server configuration updates via the
+ /// configuration backends. The value of 0 means that the
+ /// mechanism to periodically fetch the configuration updates
+ /// is disabled.
+ ///
+ /// @return Time period between the subsequent attempts to
+ /// fetch server configuration updates in seconds.
+ const util::Optional<uint16_t>& getConfigFetchWaitTime() const {
+ return (config_fetch_wait_time_);
+ }
+
+ /// @brief Sets configuration database access string.
+ ///
+ /// @param access_str database access string.
+ ///
+ /// @throw BadValue if an entry exists that matches the parameters
+ /// in the given access string, or if the access string is invalid.
+ void addConfigDatabase(const std::string& access_str);
+
+ /// @brief Retrieves the list of databases.
+ ///
+ /// The entries in the list are stored in the order they were
+ /// added to it (FIFO).
+ ///
+ /// @return a reference to a const list of databases.
+ const ConfigDbInfoList& getConfigDatabases() const {
+ return (db_infos_);
+ }
+
+ /// @brief Retrieves the database with the given access parameter value.
+ ///
+ /// @return A reference to the matching database or the not-found value
+ /// available via @c EMPTY_DB().
+ const ConfigDbInfo& findConfigDb(const std::string& param_name,
+ const std::string& param_value);
+
+ /// @brief Empties the contents of the class, including the database list.
+ void clear();
+
+ /// @brief Merges specified configuration into this configuration.
+ ///
+ /// If the other configuration is non-empty it completely replaces
+ /// this configuration.
+ ///
+ /// @param other the other configuration to be merged into this
+ /// configuration.
+ void merge(const ConfigControlInfo& other);
+
+ /// @brief Unparse a configuration object.
+ ///
+ /// @return a pointer to unparsed configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+ /// @brief Fetches the not-found value returned by database list searches.
+ ///
+ /// @return a reference to the empty ConfigDBInfo.
+ static const ConfigDbInfo& EMPTY_DB();
+
+ /// @brief Compares two objects for equality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool equals(const ConfigControlInfo& other) const;
+
+private:
+
+ /// @brief Configured value of the config-fetch-wait-time.
+ util::Optional<uint16_t> config_fetch_wait_time_;
+
+ /// @brief List of configuration databases.
+ ConfigDbInfoList db_infos_;
+};
+
+/// @brief Defines a pointer to a ConfigControlInfo.
+typedef boost::shared_ptr<ConfigControlInfo> ConfigControlInfoPtr;
+
+/// @brief Defines a pointer to a const ConfigControlInfo.
+typedef boost::shared_ptr<const ConfigControlInfo> ConstConfigControlInfoPtr;
+
+} // namespace process
+} // end namespace isc
+
+#endif // PROCESS_CONFIG_CTL_INFO_H
diff --git a/src/lib/process/config_ctl_parser.cc b/src/lib/process/config_ctl_parser.cc
new file mode 100644
index 0000000..38f7ac0
--- /dev/null
+++ b/src/lib/process/config_ctl_parser.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2018-2019 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 <cc/dhcp_config_error.h>
+#include <process/config_ctl_parser.h>
+#include <database/dbaccess_parser.h>
+#include <cstdint>
+#include <string>
+
+using namespace isc;
+using namespace isc::data;
+
+namespace isc {
+namespace process {
+
+ConfigControlInfoPtr
+ConfigControlParser::parse(const data::ConstElementPtr& config_control) {
+ ConfigControlInfoPtr ctl_info(new ConfigControlInfo());
+
+ try {
+ if (config_control->contains("config-databases")) {
+
+ auto elem = config_control->get("config-databases");
+ if (elem->getType() != Element::list) {
+ isc_throw (ConfigError, "config-databases must be a list ("
+ << elem->getPosition() << ")");
+ }
+
+ const std::vector<data::ElementPtr>& db_list = elem->listValue();
+ for (auto db = db_list.cbegin(); db != db_list.end(); ++db) {
+ db::DbAccessParser parser;
+ std::string access_string;
+ parser.parse(access_string, *db);
+ /// @todo do we still need access_string for this at all?
+ /// can't we just use params directly and get rid of the
+ /// string now that DatabaseConnection::toElement(map) exists?
+ ctl_info->addConfigDatabase(access_string);
+ }
+ }
+
+ if (config_control->contains("config-fetch-wait-time")) {
+ auto config_fetch_wait_time = getInteger(config_control,
+ "config-fetch-wait-time",
+ 0, 65535);
+ ctl_info->setConfigFetchWaitTime(static_cast<uint16_t>(config_fetch_wait_time));
+ }
+
+ } catch (const isc::ConfigError&) {
+ // Position was already added
+ throw;
+ } catch (const std::exception& ex) {
+ isc_throw(ConfigError, ex.what() << " ("
+ << config_control->getPosition() << ")");
+ }
+
+ return (ctl_info);
+}
+
+} // end of namespace isc::process
+} // end of namespace isc
+
diff --git a/src/lib/process/config_ctl_parser.h b/src/lib/process/config_ctl_parser.h
new file mode 100644
index 0000000..4182a19
--- /dev/null
+++ b/src/lib/process/config_ctl_parser.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2018 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 PROCESS_CONFIG_CONTROL_PARSER_H
+#define PROCESS_CONFIG_CONTROL_PARSER_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <process/config_ctl_info.h>
+
+namespace isc {
+namespace process {
+
+/// @brief Implements parser for config control information, "config-control"
+class ConfigControlParser : isc::data::SimpleParser {
+public:
+
+ /// @brief Parses a configuration control Element
+ ///
+ /// @param config_control Element holding the config control content
+ /// to be parsed.
+ ///
+ /// @return Pointer to newly created ConfigControlInfo instance
+ /// @throw DhcpConfigError when config control content is invalid.
+ ConfigControlInfoPtr
+ parse(const data::ConstElementPtr& config_control);
+};
+
+} // enf of namespace isc::process
+} // end of namespace isc
+
+#endif // PROCESS_CONFIG_CONTROL_PARSER_H
diff --git a/src/lib/process/d_cfg_mgr.cc b/src/lib/process/d_cfg_mgr.cc
new file mode 100644
index 0000000..a08a903
--- /dev/null
+++ b/src/lib/process/d_cfg_mgr.cc
@@ -0,0 +1,164 @@
+// Copyright (C) 2013-2023 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 <cc/command_interpreter.h>
+#include <dhcp/libdhcp++.h>
+#include <process/d_log.h>
+#include <process/d_cfg_mgr.h>
+#include <process/daemon.h>
+#include <process/redact_config.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace process {
+
+// *********************** DCfgMgrBase *************************
+
+DCfgMgrBase::DCfgMgrBase(ConfigPtr context) {
+ setContext(context);
+}
+
+DCfgMgrBase::~DCfgMgrBase() {
+}
+
+void
+DCfgMgrBase::resetContext() {
+ ConfigPtr context = createNewContext();
+ setContext(context);
+}
+
+void
+DCfgMgrBase::setContext(ConfigPtr& context) {
+ if (!context) {
+ isc_throw(DCfgMgrBaseError, "DCfgMgrBase: context cannot be NULL");
+ }
+
+ context_ = context;
+}
+
+ConstElementPtr
+DCfgMgrBase::redactConfig(ConstElementPtr const& config) const {
+ ConstElementPtr result(config);
+ for (std::list<std::string>& json_path : jsonPathsToRedact()) {
+ result = isc::process::redactConfig(result, json_path);
+ }
+ return result;
+}
+
+list<list<string>> DCfgMgrBase::jsonPathsToRedact() const {
+ static list<list<string>> const list;
+ return list;
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::simpleParseConfig(isc::data::ConstElementPtr config_set,
+ bool check_only,
+ const std::function<void()>& post_config_cb) {
+ if (!config_set) {
+ return (isc::config::createAnswer(CONTROL_RESULT_ERROR,
+ std::string("Can't parse NULL config")));
+ }
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_COMMAND, DCTL_CONFIG_START)
+ .arg(redactConfig(config_set)->str());
+
+ // The parsers implement data inheritance by directly accessing
+ // configuration context. For this reason the data parsers must store
+ // the parsed data into context immediately. This may cause data
+ // inconsistency if the parsing operation fails after the context has been
+ // modified. We need to preserve the original context here
+ // so as we can rollback changes when an error occurs.
+ ConfigPtr original_context = context_;
+ resetContext();
+ bool rollback = false;
+
+ // Answer will hold the result returned to the caller.
+ ConstElementPtr answer;
+
+ try {
+ // Logging is common so factor it.
+ Daemon::configureLogger(config_set, context_);
+
+ // Let's call the actual implementation
+ answer = parse(config_set, check_only);
+
+ // and check the response returned.
+ int code = 0;
+ isc::config::parseAnswer(code, answer);
+
+ // Everything was fine. Configuration set processed successfully.
+ if (!check_only) {
+ if (code == 0) {
+ // Call the callback only when parsing was successful.
+ if (post_config_cb) {
+ post_config_cb();
+ }
+ LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
+ // Set the last commit timestamp.
+ auto now = boost::posix_time::second_clock::universal_time();
+ context_->setLastCommitTime(now);
+ } else {
+ rollback = true;
+ }
+
+ // Use the answer provided.
+ //answer = isc::config::createAnswer(CONTROL_RESULT_SUCCESS, "Configuration committed.");
+ } else {
+ LOG_INFO(dctl_logger, DCTL_CONFIG_CHECK_COMPLETE)
+ .arg(getConfigSummary(0))
+ .arg(config::answerToText(answer));
+ }
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
+ answer = isc::config::createAnswer(CONTROL_RESULT_ERROR, ex.what());
+ rollback = true;
+ }
+
+ if (check_only) {
+ // If this is a configuration check only, then don't actually apply
+ // the configuration and reverse to the previous one.
+ context_ = original_context;
+ }
+
+ if (rollback) {
+ // An error occurred, so make sure that we restore original context.
+ context_ = original_context;
+ }
+
+ return (answer);
+}
+
+
+void
+DCfgMgrBase::setCfgDefaults(isc::data::ElementPtr) {
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::parse(isc::data::ConstElementPtr, bool) {
+ isc_throw(DCfgMgrBaseError, "This class does not implement simple parser paradigm yet");
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/process/d_cfg_mgr.h b/src/lib/process/d_cfg_mgr.h
new file mode 100644
index 0000000..204f7b2
--- /dev/null
+++ b/src/lib/process/d_cfg_mgr.h
@@ -0,0 +1,252 @@
+// Copyright (C) 2013-2021 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 D_CFG_MGR_H
+#define D_CFG_MGR_H
+
+#include <cc/data.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <process/config_base.h>
+#include <exceptions/exceptions.h>
+
+#include <stdint.h>
+
+#include <functional>
+#include <list>
+#include <string>
+
+namespace isc {
+namespace process {
+
+/// @brief Defines a map of ConstElementPtrs keyed by name
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief Exception thrown if the configuration manager encounters an error.
+class DCfgMgrBaseError : public isc::Exception {
+public:
+ DCfgMgrBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Configuration Manager
+///
+/// DCfgMgrBase is an abstract class that provides the mechanisms for managing
+/// an application's configuration. This includes services for parsing sets of
+/// configuration values, storing the parsed information in its converted form,
+/// and retrieving the information on demand. It is intended to be the worker
+/// class which is handed a set of configuration values to process by upper
+/// application management layers.
+///
+/// This class allows two configuration methods:
+///
+/// 1. classic method
+///
+/// The class presents a public method for receiving new configurations,
+/// parseConfig. This method coordinates the parsing effort as follows:
+///
+/// @code
+/// make backup copy of configuration context
+/// Split top-level configuration elements into to sets:
+/// 1. Set of scalar elements (strings, booleans, ints, etc..)
+/// 2. Set of object elements (maps, lists, etc...)
+/// For each entry in the scalar set:
+/// get derivation-specific parser for element
+/// run parser
+/// update context with parsed results
+/// break on error
+///
+/// For each entry in the object set;
+/// get derivation-specific parser for element
+/// run parser
+/// update context with parsed results
+/// break on error
+///
+/// if an error occurred or this is only a check
+/// restore configuration context from backup
+/// @endcode
+///
+/// The above structuring ensures that global parameters are parsed first
+/// making them available during subsequent object element parsing. The order
+/// in which the object elements are processed is either:
+///
+/// 1. Natural order presented by the configuration set
+/// 2. Specific order determined by a list of element ids
+///
+/// This allows a derivation to specify the order in which its elements are
+/// parsed if there are dependencies between elements.
+///
+/// To parse a given element, its id along with the element itself,
+/// is passed into the virtual method, @c parseElement. Derivations are
+/// expected to converts the element into application specific object(s),
+/// thereby isolating the CPL from application details.
+///
+/// In the event that an error occurs, parsing is halted and the
+/// configuration context is restored from backup.
+///
+/// See @ref isc::d2::D2CfgMgr and @ref isc::d2::D2Process for example use of
+/// this approach.
+///
+/// 2. simple configuration method
+///
+/// This approach assumes usage of @ref isc::data::SimpleParser paradigm. It
+/// does not use any intermediate storage, does not use parser pointers, does
+/// not enforce parsing order.
+///
+/// Here's the expected control flow order:
+/// 1. implementation calls simpleParseConfig from its configure method.
+/// 2. simpleParseConfig makes a configuration context
+/// 3. parse method from the derived class is called
+/// 4. if the configuration was unsuccessful or this is only a check, the
+/// old context is reinstantiated. If not, the configuration is kept.
+///
+/// See @ref isc::agent::CtrlAgentCfgMgr and @ref isc::agent::CtrlAgentProcess
+/// for example use of this approach.
+class DCfgMgrBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param context is a pointer to the configuration context the manager
+ /// will use for storing parsed results.
+ ///
+ /// @throw throws DCfgMgrBaseError if context is null
+ DCfgMgrBase(ConfigPtr context);
+
+ /// @brief Destructor
+ virtual ~DCfgMgrBase();
+
+ /// @brief Acts as the receiver of new configurations.
+ ///
+ /// This method is similar to what parseConfig did, execept it employs
+ /// the simple parser paradigm: no intermediate storage, no parser pointers
+ /// no distinction between params_map and objects_map, parse order (if needed)
+ /// can be enforced in the actual implementation by calling specific
+ /// parsers first. See @ref isc::agent::CtrlAgentCfgMgr::parse for example.
+ ///
+ /// If check_only is true, the actual parsing is done to check if the configuration
+ /// is sane, but is then reverted.
+ ///
+ /// @param config set of configuration elements to be parsed
+ /// @param check_only true if the config is to be checked only, but not applied
+ /// @param post_config_cb Callback to be executed after the usual parsing stage.
+ /// This can be specified as a C++ lambda which configures other parts of the
+ /// system based on the parsed configuration information. The callback should
+ /// throw an exception to signal an error. This method will catch this
+ /// exception and place an exception string within the result returned.
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ isc::data::ConstElementPtr
+ simpleParseConfig(isc::data::ConstElementPtr config,
+ bool check_only = false,
+ const std::function<void()>& post_config_cb = nullptr);
+
+ /// @brief Fetches the configuration context.
+ ///
+ /// @return returns a pointer reference to the configuration context.
+ ConfigPtr& getContext() {
+ return (context_);
+ }
+
+ /// @brief Returns configuration summary in the textual format.
+ ///
+ /// This method returns the brief text describing the current configuration.
+ /// It may be used for logging purposes, e.g. whn the new configuration is
+ /// committed to notify a user about the changes in configuration.
+ ///
+ /// @param selection Bitfield which describes the parts of the configuration
+ /// to be returned.
+ ///
+ /// @return Summary of the configuration in the textual format.
+ virtual std::string getConfigSummary(const uint32_t selection) = 0;
+
+ /// @brief Redact the configuration.
+ ///
+ /// This method replaces passwords and secrets by asterisks. By
+ /// default it follows all subtrees at the exception of user
+ /// contexts. Please derive the method to allow a reasonable
+ /// performance by following only subtrees where the syntax allows
+ /// the presence of passwords and secrets.
+ ///
+ /// @param config the Element tree structure that describes the configuration.
+ /// @return unmodified config or a copy of the config where passwords were
+ /// replaced by asterisks so can be safely logged to an unprivileged place.
+ isc::data::ConstElementPtr
+ redactConfig(isc::data::ConstElementPtr const& config) const;
+
+protected:
+ /// @brief Adds default values to the given config
+ ///
+ /// Provides derivations a means to add defaults to a configuration
+ /// Element map prior to parsing it.
+ ///
+ /// @param mutable_config - configuration to which defaults should be added
+ virtual void setCfgDefaults(isc::data::ElementPtr mutable_config);
+
+ /// @brief Abstract factory which creates a context instance.
+ ///
+ /// This method is used at the beginning of configuration process to
+ /// create a fresh, empty copy of the derivation-specific context. This
+ /// new context will be populated during the configuration process
+ /// and will replace the existing context provided the configuration
+ /// process completes without error.
+ ///
+ /// @return Returns a ConfigPtr to the new context instance.
+ virtual ConfigPtr createNewContext() = 0;
+
+ /// @brief Replaces existing context with a new, empty context.
+ void resetContext();
+
+ /// @brief Update the current context.
+ ///
+ /// Replaces the existing context with the given context.
+ /// @param context Pointer to the new context.
+ /// @throw DCfgMgrBaseError if context is NULL.
+ void setContext(ConfigPtr& context);
+
+ /// @brief Parses actual configuration.
+ ///
+ /// This method is expected to be implemented in derived classes that employ
+ /// SimpleParser paradigm (i.e. they call simpleParseConfig rather than
+ /// parseConfig from their configure method).
+ ///
+ /// Implementations that do not employ this method may provide dummy
+ /// implementation.
+ ///
+ /// @param config the Element tree structure that describes the configuration.
+ /// @param check_only false for normal configuration, true when verifying only
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr parse(isc::data::ConstElementPtr config,
+ bool check_only);
+
+ /// @brief Return a list of all paths that contain passwords or secrets.
+ ///
+ /// Used in @ref isc::process::Daemon::redactConfig to only make copies and
+ /// only redact configuration subtrees that contain passwords or secrets.
+ ///
+ /// This method needs to be overridden in each process that has a distinct
+ /// configuration structure.
+ ///
+ /// @return the list of lists of sequential JSON map keys needed to reach
+ /// the passwords and secrets.
+ virtual std::list<std::list<std::string>> jsonPathsToRedact() const;
+
+private:
+ /// @brief Pointer to the configuration context instance.
+ ConfigPtr context_;
+};
+
+/// @brief Defines a shared pointer to DCfgMgrBase.
+typedef boost::shared_ptr<DCfgMgrBase> DCfgMgrBasePtr;
+
+} // end of isc::process namespace
+} // end of isc namespace
+
+#endif // D_CFG_MGR_H
diff --git a/src/lib/process/d_controller.cc b/src/lib/process/d_controller.cc
new file mode 100644
index 0000000..420442c
--- /dev/null
+++ b/src/lib/process/d_controller.cc
@@ -0,0 +1,860 @@
+// Copyright (C) 2013-2023 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 <cc/command_interpreter.h>
+#include <process/cfgrpt/config_report.h>
+#include <cryptolink/crypto_hash.h>
+#include <exceptions/exceptions.h>
+#include <config/command_mgr.h>
+#include <hooks/hooks_manager.h>
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <util/encode/hex.h>
+#include <process/daemon.h>
+#include <process/d_log.h>
+#include <process/d_controller.h>
+#include <process/config_base.h>
+#include <kea_version.h>
+#include <functional>
+#include <sstream>
+#include <string>
+#include <unistd.h>
+#include <signal.h>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace process {
+
+DControllerBasePtr DControllerBase::controller_;
+
+// Note that the constructor instantiates the controller's primary IOService.
+DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
+ : app_name_(app_name), bin_name_(bin_name),
+ verbose_(false), check_only_(false),
+ io_service_(new isc::asiolink::IOService()),
+ io_signal_set_() {
+}
+
+void
+DControllerBase::setController(const DControllerBasePtr& controller) {
+ if (controller_) {
+ // This shouldn't happen, but let's make sure it can't be done.
+ // It represents a programmatic error.
+ isc_throw (DControllerBaseError,
+ "Multiple controller instances attempted.");
+ }
+
+ controller_ = controller;
+}
+
+ConstElementPtr
+DControllerBase::parseFile(const std::string&) {
+ ConstElementPtr elements;
+ return (elements);
+}
+
+int
+DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
+
+ // Step 1 is to parse the command line arguments.
+ try {
+ parseArgs(argc, argv);
+ } catch (const InvalidUsage& ex) {
+ usage(ex.what());
+ // rethrow it with an empty message
+ isc_throw(InvalidUsage, "");
+ }
+
+ setProcName(bin_name_);
+
+ if (isCheckOnly()) {
+ checkConfigOnly();
+ return (EXIT_SUCCESS);
+ }
+
+ // It is important that we set a default logger name because this name
+ // will be used when the user doesn't provide the logging configuration
+ // in the Kea configuration file.
+ Daemon::setDefaultLoggerName(bin_name_);
+
+ // Logger's default configuration depends on whether we are in the
+ // verbose mode or not. CfgMgr manages the logger configuration so
+ // the verbose mode is set for CfgMgr.
+ Daemon::setVerbose(verbose_);
+
+ // Do not initialize logger here if we are running unit tests. It would
+ // replace an instance of unit test specific logger.
+ if (!test_mode) {
+ // Now that we know what the mode flags are, we can init logging.
+ Daemon::loggerInit(bin_name_.c_str(), verbose_);
+ }
+
+ try {
+ checkConfigFile();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
+ .arg(app_name_).arg(ex.what());
+ isc_throw (LaunchError, "Launch Failed: " << ex.what());
+ }
+
+ try {
+ createPIDFile();
+ } catch (const DaemonPIDExists& ex) {
+ LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
+ .arg(bin_name_).arg(ex.what());
+ isc_throw (LaunchError, "Launch Failed: " << ex.what());
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
+ .arg(app_name_).arg(ex.what());
+ isc_throw (LaunchError, "Launch failed: " << ex.what());
+ }
+
+ // Log the starting of the service.
+ LOG_INFO(dctl_logger, DCTL_STARTING)
+ .arg(app_name_)
+ .arg(getpid())
+ .arg(VERSION)
+ .arg(PACKAGE_VERSION_TYPE);
+ // When it is not a stable version dissuade use in production.
+ if (std::string(PACKAGE_VERSION_TYPE) == "development") {
+ LOG_WARN(dctl_logger, DCTL_DEVELOPMENT_VERSION);
+ }
+ try {
+ // Step 2 is to create and initialize the application process object.
+ initProcess();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
+ .arg(app_name_).arg(ex.what());
+ isc_throw (ProcessInitError,
+ "Application Process initialization failed: " << ex.what());
+ }
+
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_STANDALONE)
+ .arg(app_name_);
+
+ // Step 3 is to load configuration from file.
+ int rcode;
+ ConstElementPtr comment = parseAnswer(rcode, configFromFile());
+ if (rcode != 0) {
+ LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
+ .arg(app_name_).arg(comment->stringValue());
+ isc_throw (ProcessInitError, "Could Not load configuration file: "
+ << comment->stringValue());
+ }
+
+ // Note that the controller was started.
+ start_ = boost::posix_time::second_clock::universal_time();
+
+ // Everything is clear for launch, so start the application's
+ // event loop.
+ try {
+ // Now that we have a process, we can set up signal handling.
+ initSignalHandling();
+ runProcess();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
+ .arg(app_name_).arg(ex.what());
+ isc_throw (ProcessRunError,
+ "Application process event loop failed: " << ex.what());
+ }
+
+ // All done, so bail out.
+ LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
+ .arg(app_name_).arg(getpid()).arg(VERSION);
+
+ return (getExitValue());
+}
+
+void
+DControllerBase::checkConfigOnly() {
+ try {
+ // We need to initialize logging, in case any error
+ // messages are to be printed.
+ // This is just a test, so we don't care about lockfile.
+ setenv("KEA_LOCKFILE_DIR", "none", 0);
+ Daemon::setDefaultLoggerName(bin_name_);
+ Daemon::setVerbose(verbose_);
+ Daemon::loggerInit(bin_name_.c_str(), verbose_);
+
+ // Check the syntax first.
+ std::string config_file = getConfigFile();
+ if (config_file.empty()) {
+ // Basic sanity check: file name must not be empty.
+ isc_throw(InvalidUsage, "JSON configuration file not specified");
+ }
+ ConstElementPtr whole_config = parseFile(config_file);
+ if (!whole_config) {
+ // No fallback to fromJSONFile
+ isc_throw(InvalidUsage, "No configuration found");
+ }
+ if (verbose_) {
+ std::cerr << "Syntax check OK" << std::endl;
+ }
+
+ // Check the logic next.
+ ConstElementPtr module_config;
+ module_config = whole_config->get(getAppName());
+ if (!module_config) {
+ isc_throw(InvalidUsage, "Config file " << config_file <<
+ " does not include '" << getAppName() << "' entry");
+ }
+ if (module_config->getType() != Element::map) {
+ isc_throw(InvalidUsage, "Config file " << config_file <<
+ " includes not map '" << getAppName() << "' entry");
+ }
+
+ // Handle other (i.e. not application name) objects.
+ std::string errmsg = handleOtherObjects(whole_config);
+ if (!errmsg.empty()) {
+ isc_throw(InvalidUsage, "Config file " << config_file << errmsg);
+ }
+
+ // Get an application process object.
+ initProcess();
+
+ ConstElementPtr answer = checkConfig(module_config);
+ int rcode = 0;
+ answer = parseAnswer(rcode, answer);
+ if (rcode != 0) {
+ isc_throw(InvalidUsage, "Error encountered: "
+ << answer->stringValue());
+ }
+ } catch (const VersionMessage&) {
+ throw;
+ } catch (const InvalidUsage&) {
+ throw;
+ } catch (const std::exception& ex) {
+ isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
+ }
+ return;
+}
+
+void
+DControllerBase::parseArgs(int argc, char* argv[]) {
+
+ if (argc == 1) {
+ isc_throw(InvalidUsage, "");
+ }
+
+ // Iterate over the given command line options. If its a stock option
+ // ("c" or "d") handle it here. If its a valid custom option, then
+ // invoke customOption.
+ int ch;
+ opterr = 0;
+ optind = 1;
+ std::string opts("dvVWc:t:" + getCustomOpts());
+ while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
+ switch (ch) {
+ case 'd':
+ // Enables verbose logging.
+ verbose_ = true;
+ break;
+
+ case 'v':
+ // gather Kea version and throw so main() can catch and return
+ // rather than calling exit() here which disrupts gtest.
+ isc_throw(VersionMessage, getVersion(false));
+ break;
+
+ case 'V':
+ // gather Kea version and throw so main() can catch and return
+ // rather than calling exit() here which disrupts gtest.
+ isc_throw(VersionMessage, getVersion(true));
+ break;
+
+ case 'W':
+ // gather Kea config report and throw so main() can catch and
+ // return rather than calling exit() here which disrupts gtest.
+ isc_throw(VersionMessage, isc::detail::getConfigReport());
+ break;
+
+ case 'c':
+ case 't':
+ // config file name
+ if (optarg == NULL) {
+ isc_throw(InvalidUsage, "configuration file name missing");
+ }
+
+ setConfigFile(optarg);
+
+ if (ch == 't') {
+ check_only_ = true;
+ }
+ break;
+
+ case '?': {
+ // We hit an invalid option.
+ isc_throw(InvalidUsage, "unsupported option: ["
+ << static_cast<char>(optopt) << "] "
+ << (!optarg ? "" : optarg));
+
+ break;
+ }
+
+ default:
+ // We hit a valid custom option
+ if (!customOption(ch, optarg)) {
+ // This would be a programmatic error.
+ isc_throw(InvalidUsage, " Option listed but implemented?: ["
+ << static_cast<char>(ch) << "] "
+ << (!optarg ? "" : optarg));
+ }
+ break;
+ }
+ }
+
+ // There was too much information on the command line.
+ if (argc > optind) {
+ isc_throw(InvalidUsage, "extraneous command line information");
+ }
+}
+
+bool
+DControllerBase::customOption(int /* option */, char* /*optarg*/) {
+ // Default implementation returns false.
+ return (false);
+}
+
+void
+DControllerBase::initProcess() {
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_INIT_PROCESS)
+ .arg(app_name_);
+
+ // Invoke virtual method to instantiate the application process.
+ try {
+ process_.reset(createProcess());
+ } catch (const std::exception& ex) {
+ isc_throw(DControllerBaseError, std::string("createProcess failed: ")
+ + ex.what());
+ }
+
+ // This is pretty unlikely, but will test for it just to be safe..
+ if (!process_) {
+ isc_throw(DControllerBaseError, "createProcess returned NULL");
+ }
+
+ // Invoke application's init method (Note this call should throw
+ // DProcessBaseError if it fails).
+ process_->init();
+}
+
+ConstElementPtr
+DControllerBase::configFromFile() {
+ // Rollback any previous staging configuration. For D2, only a
+ // logger configuration is used here.
+ // We're not using cfgmgr to store logging configuration anymore.
+ // isc::dhcp::CfgMgr::instance().rollback();
+
+ // Will hold configuration.
+ ConstElementPtr module_config;
+ // Will receive configuration result.
+ ConstElementPtr answer;
+ try {
+ std::string config_file = getConfigFile();
+ if (config_file.empty()) {
+ // Basic sanity check: file name must not be empty.
+ isc_throw(BadValue, "JSON configuration file not specified. Please "
+ "use -c command line option.");
+ }
+
+ // If parseFile returns an empty pointer, then pass the file onto the
+ // original JSON parser.
+ ConstElementPtr whole_config = parseFile(config_file);
+ if (!whole_config) {
+ // Read contents of the file and parse it as JSON
+ whole_config = Element::fromJSONFile(config_file, true);
+ }
+
+ // Extract derivation-specific portion of the configuration.
+ module_config = whole_config->get(getAppName());
+ if (!module_config) {
+ isc_throw(BadValue, "Config file " << config_file <<
+ " does not include '" <<
+ getAppName() << "' entry.");
+ }
+ if (module_config->getType() != Element::map) {
+ isc_throw(InvalidUsage, "Config file " << config_file <<
+ " includes not map '" << getAppName() << "' entry");
+ }
+
+ // Handle other (i.e. not application name) objects.
+ std::string errmsg = handleOtherObjects(whole_config);
+ if (!errmsg.empty()) {
+ isc_throw(InvalidUsage, "Config file " << config_file << errmsg);
+ }
+
+ // Let's configure logging before applying the configuration,
+ // so we can log things during configuration process.
+
+ // Temporary storage for logging configuration
+ ConfigPtr storage(new ConfigBase());
+
+ // Configure logging to the temporary storage.
+ Daemon::configureLogger(module_config, storage);
+
+ // Let's apply the new logging. We do it early, so we'll be able
+ // to print out what exactly is wrong with the new config in
+ // case of problems.
+ storage->applyLoggingCfg();
+
+ answer = updateConfig(module_config);
+ // In all cases the right logging configuration is in the context.
+ process_->getCfgMgr()->getContext()->applyLoggingCfg();
+ } catch (const std::exception& ex) {
+ // Rollback logging configuration.
+ process_->getCfgMgr()->getContext()->applyLoggingCfg();
+
+ // build an error result
+ ConstElementPtr error = createAnswer(CONTROL_RESULT_ERROR,
+ std::string("Configuration parsing failed: ") + ex.what());
+ return (error);
+ }
+
+ return (answer);
+}
+
+void
+DControllerBase::runProcess() {
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_RUN_PROCESS)
+ .arg(app_name_);
+ if (!process_) {
+ // This should not be possible.
+ isc_throw(DControllerBaseError, "Process not initialized");
+ }
+
+ // Invoke the application process's run method. This may throw
+ // DProcessBaseError
+ process_->run();
+}
+
+// Instance method for handling new config
+ConstElementPtr
+DControllerBase::updateConfig(ConstElementPtr new_config) {
+ return (process_->configure(new_config, false));
+}
+
+// Instance method for checking new config
+ConstElementPtr
+DControllerBase::checkConfig(ConstElementPtr new_config) {
+ return (process_->configure(new_config, true));
+}
+
+ConstElementPtr
+DControllerBase::configGetHandler(const std::string&,
+ ConstElementPtr /*args*/) {
+ ElementPtr config = process_->getCfgMgr()->getContext()->toElement();
+ std::string hash = BaseCommandMgr::getHash(config);
+ config->set("hash", Element::create(hash));
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, config));
+}
+
+ConstElementPtr
+DControllerBase::configHashGetHandler(const std::string&,
+ ConstElementPtr /*args*/) {
+ ConstElementPtr config = process_->getCfgMgr()->getContext()->toElement();
+ std::string hash = BaseCommandMgr::getHash(config);
+ ElementPtr params = Element::createMap();
+ params->set("hash", Element::create(hash));
+ return (createAnswer(CONTROL_RESULT_SUCCESS, params));
+}
+
+ConstElementPtr
+DControllerBase::configWriteHandler(const std::string&,
+ ConstElementPtr args) {
+ std::string filename;
+
+ if (args) {
+ if (args->getType() != Element::map) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
+ }
+ ConstElementPtr filename_param = args->get("filename");
+ if (filename_param) {
+ if (filename_param->getType() != Element::string) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "passed parameter 'filename' "
+ "is not a string"));
+ }
+ filename = filename_param->stringValue();
+ }
+ }
+
+ if (filename.empty()) {
+ // filename parameter was not specified, so let's use
+ // whatever we remember
+ filename = getConfigFile();
+ if (filename.empty()) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Unable to determine filename."
+ "Please specify filename explicitly."));
+ }
+ }
+
+ // Ok, it's time to write the file.
+ size_t size = 0;
+ ElementPtr cfg = process_->getCfgMgr()->getContext()->toElement();
+
+ try {
+ size = writeConfigFile(filename, cfg);
+ } catch (const isc::Exception& ex) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ std::string("Error during write-config:")
+ + ex.what()));
+ }
+ if (size == 0) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "Error writing configuration to " + filename));
+ }
+
+ // Ok, it's time to return the successful response.
+ ElementPtr params = Element::createMap();
+ params->set("size", Element::create(static_cast<long long>(size)));
+ params->set("filename", Element::create(filename));
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
+ + filename + " successful", params));
+}
+
+std::string
+DControllerBase::handleOtherObjects(ConstElementPtr args) {
+ // Check obsolete or unknown (aka unsupported) objects.
+ const std::string& app_name = getAppName();
+ std::string errmsg;
+ for (auto obj : args->mapValue()) {
+ const std::string& obj_name = obj.first;
+ if (obj_name == app_name) {
+ continue;
+ }
+ LOG_ERROR(dctl_logger, DCTL_CONFIG_DEPRECATED)
+ .arg("'" + obj_name + "', defining anything in global level besides '"
+ + app_name + "' is no longer supported.");
+ if (errmsg.empty()) {
+ errmsg = " contains unsupported '" + obj_name + "' parameter";
+ } else {
+ errmsg += " (and '" + obj_name + "')";
+ }
+ }
+ return (errmsg);
+}
+
+ConstElementPtr
+DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
+ const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
+ ConstElementPtr module_config;
+ std::string app_name = getAppName();
+ std::string message;
+
+ // Command arguments are expected to be:
+ // { "Module": { ... } }
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ module_config = args->get(app_name);
+ if (!module_config) {
+ message = "Missing mandatory '" + app_name + "' parameter.";
+ } else if (module_config->getType() != Element::map) {
+ message = "'" + app_name + "' parameter expected to be a map.";
+ }
+ }
+
+ if (message.empty()) {
+ // Handle other (i.e. not application name) objects.
+ std::string errmsg = handleOtherObjects(args);
+ if (!errmsg.empty()) {
+ message = "'arguments' parameter" + errmsg;
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ // We're not using cfgmgr to store logging information anymore.
+ // isc::dhcp::CfgMgr::instance().rollback();
+
+ // Now we check the server proper.
+ return (checkConfig(module_config));
+}
+
+ConstElementPtr
+DControllerBase::configReloadHandler(const std::string&, ConstElementPtr) {
+ // Add reload in message?
+ return (configFromFile());
+}
+
+ConstElementPtr
+DControllerBase::configSetHandler(const std::string&, ConstElementPtr args) {
+ const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
+ ConstElementPtr module_config;
+ std::string app_name = getAppName();
+ std::string message;
+
+ // Command arguments are expected to be:
+ // { "Module": { ... } }
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ module_config = args->get(app_name);
+ if (!module_config) {
+ message = "Missing mandatory '" + app_name + "' parameter.";
+ } else if (module_config->getType() != Element::map) {
+ message = "'" + app_name + "' parameter expected to be a map.";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ try {
+
+ // Handle other (i.e. not application name) objects.
+ handleOtherObjects(args);
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ // We're not using cfgmgr to store logging information anymore.
+ // isc::dhcp::CfgMgr::instance().rollback();
+
+ // Temporary storage for logging configuration
+ ConfigPtr storage(new ConfigBase());
+
+ // Configure logging to the temporary storage.
+ Daemon::configureLogger(module_config, storage);
+
+ // Let's apply the new logging. We do it early, so we'll be able
+ // to print out what exactly is wrong with the new config in
+ // case of problems.
+ storage->applyLoggingCfg();
+
+ ConstElementPtr answer = updateConfig(module_config);
+ int rcode = 0;
+ parseAnswer(rcode, answer);
+ // In all cases the right logging configuration is in the context.
+ process_->getCfgMgr()->getContext()->applyLoggingCfg();
+ return (answer);
+ } catch (const std::exception& ex) {
+ // Rollback logging configuration.
+ process_->getCfgMgr()->getContext()->applyLoggingCfg();
+
+ // build an error result
+ ConstElementPtr error = createAnswer(CONTROL_RESULT_ERROR,
+ std::string("Configuration parsing failed: ") + ex.what());
+ return (error);
+ }
+}
+
+ConstElementPtr
+DControllerBase::serverTagGetHandler(const std::string&, ConstElementPtr) {
+ const std::string& tag = process_->getCfgMgr()->getContext()->getServerTag();
+ ElementPtr response = Element::createMap();
+ response->set("server-tag", Element::create(tag));
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, response));
+}
+
+ConstElementPtr
+DControllerBase::statusGetHandler(const std::string&, ConstElementPtr) {
+ ElementPtr status = Element::createMap();
+ status->set("pid", Element::create(static_cast<int>(getpid())));
+
+ auto now = boost::posix_time::second_clock::universal_time();
+ if (!start_.is_not_a_date_time()) {
+ auto uptime = now - start_;
+ status->set("uptime", Element::create(uptime.total_seconds()));
+ }
+
+ auto last_commit = process_->getCfgMgr()->getContext()->getLastCommitTime();
+ if (!last_commit.is_not_a_date_time()) {
+ auto reload = now - last_commit;
+ status->set("reload", Element::create(reload.total_seconds()));
+ }
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, status));
+}
+
+ConstElementPtr
+DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
+ ConstElementPtr answer;
+
+ // For version-get put the extended version in arguments
+ ElementPtr extended = Element::create(getVersion(true));
+ ElementPtr arguments = Element::createMap();
+ arguments->set("extended", extended);
+ answer = createAnswer(CONTROL_RESULT_SUCCESS, getVersion(false), arguments);
+ return (answer);
+}
+
+ConstElementPtr
+DControllerBase::buildReportHandler(const std::string&, ConstElementPtr) {
+ return (createAnswer(CONTROL_RESULT_SUCCESS, isc::detail::getConfigReport()));
+}
+
+ConstElementPtr
+DControllerBase::shutdownHandler(const std::string&, ConstElementPtr args) {
+ // Shutdown is universal. If its not that, then try it as
+ // a custom command supported by the derivation. If that
+ // doesn't pan out either, than send to it the application
+ // as it may be supported there.
+
+ int exit_value = EXIT_SUCCESS;
+ if (args) {
+ // @todo Should we go ahead and shutdown even if the args are invalid?
+ if (args->getType() != Element::map) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
+ }
+
+ ConstElementPtr param = args->get("exit-value");
+ if (param) {
+ if (param->getType() != Element::integer) {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "parameter 'exit-value' is not an integer"));
+ }
+
+ exit_value = param->intValue();
+ }
+ }
+
+ setExitValue(exit_value);
+ return (shutdownProcess(args));
+}
+
+ConstElementPtr
+DControllerBase::shutdownProcess(ConstElementPtr args) {
+ if (process_) {
+ return (process_->shutdown(args));
+ }
+
+ // Not really a failure, but this condition is worth noting. In reality
+ // it should be pretty hard to cause this.
+ LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "Process has not been initialized"));
+}
+
+void
+DControllerBase::initSignalHandling() {
+ /// @todo block everything we don't handle
+
+ // Create our signal set.
+ io_signal_set_.reset(new IOSignalSet(io_service_,
+ std::bind(&DControllerBase::
+ processSignal,
+ this, ph::_1)));
+ // Register for the signals we wish to handle.
+ io_signal_set_->add(SIGHUP);
+ io_signal_set_->add(SIGINT);
+ io_signal_set_->add(SIGTERM);
+}
+
+void
+DControllerBase::processSignal(int signum) {
+ switch (signum) {
+ case SIGHUP:
+ {
+ LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
+ .arg(signum).arg(getConfigFile());
+ int rcode;
+ ConstElementPtr comment = parseAnswer(rcode, configFromFile());
+ if (rcode != 0) {
+ LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
+ .arg(comment->stringValue());
+ }
+
+ break;
+ }
+
+ case SIGINT:
+ case SIGTERM:
+ {
+ LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT,
+ DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
+ ElementPtr arg_set;
+ shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
+ break;
+ }
+
+ default:
+ LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
+ break;
+ }
+}
+
+void
+DControllerBase::usage(const std::string & text) {
+ if (text != "") {
+ std::cerr << "Usage error: " << text << std::endl;
+ }
+
+ std::cerr << "Usage: " << bin_name_ << std::endl
+ << " -v: print version number and exit" << std::endl
+ << " -V: print extended version information and exit"
+ << std::endl
+ << " -W: display the configuration report and exit"
+ << std::endl
+ << " -d: optional, verbose output " << std::endl
+ << " -c <config file name> : mandatory,"
+ << " specify name of configuration file" << std::endl
+ << " -t <config file name> : check the"
+ << " configuration file and exit" << std::endl;
+
+ // add any derivation specific usage
+ std::cerr << getUsageText() << std::endl;
+}
+
+DControllerBase::~DControllerBase() {
+ // Explicitly unload hooks
+ HooksManager::prepareUnloadLibraries();
+ if (!HooksManager::unloadLibraries()) {
+ auto names = HooksManager::getLibraryNames();
+ std::string msg;
+ if (!names.empty()) {
+ msg = names[0];
+ for (size_t i = 1; i < names.size(); ++i) {
+ msg += std::string(", ") + names[i];
+ }
+ }
+ LOG_ERROR(dctl_logger, DCTL_UNLOAD_LIBRARIES_ERROR).arg(msg);
+ }
+
+ io_signal_set_.reset();
+ getIOService()->poll();
+}
+
+std::string
+DControllerBase::getVersion(bool extended) {
+ std::stringstream tmp;
+
+ tmp << VERSION;
+ if (extended) {
+ tmp << std::endl << EXTENDED_VERSION << std::endl;
+ tmp << "linked with:" << std::endl;
+ tmp << isc::log::Logger::getVersion() << std::endl;
+ tmp << getVersionAddendum();
+ }
+
+ return (tmp.str());
+}
+
+} // end of namespace isc::process
+
+} // end of namespace isc
diff --git a/src/lib/process/d_controller.h b/src/lib/process/d_controller.h
new file mode 100644
index 0000000..e16253e
--- /dev/null
+++ b/src/lib/process/d_controller.h
@@ -0,0 +1,663 @@
+// Copyright (C) 2013-2023 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 D_CONTROLLER_H
+#define D_CONTROLLER_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_service_signal.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+#include <process/daemon.h>
+#include <process/d_log.h>
+#include <process/d_process.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <string>
+#include <set>
+
+namespace isc {
+namespace process {
+
+/// @brief Exception thrown when the command line is invalid.
+/// Can be used to transmit negative messages too.
+class InvalidUsage : public isc::Exception {
+public:
+ InvalidUsage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception used to convey version info upwards.
+/// Since command line argument parsing is done as part of
+/// DControllerBase::launch(), it uses this exception to propagate
+/// version information up to main(), when command line argument
+/// -v, -V or -W is given. Can be used to transmit positive messages too.
+class VersionMessage : public isc::Exception {
+public:
+ VersionMessage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the controller launch fails.
+class LaunchError: public isc::Exception {
+public:
+ LaunchError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the application process fails.
+class ProcessInitError: public isc::Exception {
+public:
+ ProcessInitError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the application process encounters an
+/// operation in its event loop (i.e. run method).
+class ProcessRunError: public isc::Exception {
+public:
+ ProcessRunError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the controller encounters an operational error.
+class DControllerBaseError : public isc::Exception {
+public:
+ DControllerBaseError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines a shared pointer to DControllerBase.
+class DControllerBase;
+typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
+
+/// @brief Application Controller
+///
+/// DControllerBase is an abstract singleton which provides the framework and
+/// services for managing an application process that implements the
+/// DProcessBase interface. It runs the process like a stand-alone, command
+/// line driven executable, which must be supplied a configuration file at
+/// startup. It coordinates command line argument parsing, process
+/// instantiation and initialization, and runtime control through external
+/// command and configuration event handling.
+/// It creates the IOService instance which is used for runtime control
+/// events and passes the IOService into the application process at process
+/// creation.
+/// It provides the callback handlers for command and configuration events
+/// which could be triggered by an external source. Such sources are intended
+/// to be registered with and monitored by the controller's IOService such that
+/// the appropriate handler can be invoked.
+///
+/// DControllerBase provides dynamic configuration file reloading upon receipt
+/// of SIGHUP, and graceful shutdown upon receipt of either SIGINT or SIGTERM.
+///
+/// NOTE: Derivations must supply their own static singleton instance method(s)
+/// for creating and fetching the instance. The base class declares the instance
+/// member in order for it to be available for static callback functions.
+class DControllerBase : public Daemon {
+public:
+ /// @brief Constructor
+ ///
+ /// @param app_name is display name of the application under control. This
+ /// name appears in log statements.
+ /// @param bin_name is the name of the application executable.
+ DControllerBase(const char* app_name, const char* bin_name);
+
+ /// @brief Destructor
+ virtual ~DControllerBase();
+
+ /// @brief returns Kea version on stdout and exit.
+ /// redeclaration/redefinition. @ref isc::process::Daemon::getVersion()
+ std::string getVersion(bool extended);
+
+ /// @brief Acts as the primary entry point into the controller execution
+ /// and provides the outermost application control logic:
+ ///
+ /// 1. parse command line arguments
+ /// 2. instantiate and initialize the application process
+ /// 3. load the configuration file
+ /// 4. record the start timestamp
+ /// 5. initialize signal handling
+ /// 6. start and wait on the application process event loop
+ /// 7. exit to the caller
+ ///
+ /// It is intended to be called from main() and be given the command line
+ /// arguments.
+ ///
+ /// This function can be run in "test mode". It prevents initialization
+ /// of module logger. This is used in unit tests which initialize logger
+ /// in their main function. Such a logger uses environmental variables to
+ /// control severity, verbosity etc.
+ ///
+ /// @param argc is the number of command line arguments supplied
+ /// @param argv is the array of string (char *) command line arguments
+ /// @param test_mode is a bool value which indicates if
+ /// @c DControllerBase::launch should be run in the test mode (if true).
+ /// This parameter doesn't have default value to force test implementers to
+ /// enable test mode explicitly.
+ ///
+ /// @throw throws one of the following exceptions:
+ /// InvalidUsage - Indicates invalid command line.
+ /// ProcessInitError - Failed to create and initialize application
+ /// process object.
+ /// ProcessRunError - A fatal error occurred while in the application
+ /// process event loop.
+ /// @return The value from @c Daemon::getExitValue().
+ virtual int launch(int argc, char* argv[], const bool test_mode);
+
+ /// @brief Instance method invoked by the configuration event handler and
+ /// which processes the actual configuration update. Provides behavioral
+ /// path for both integrated and stand-alone modes. The current
+ /// implementation will merge the configuration update into the existing
+ /// configuration and then invoke the application process' configure method.
+ ///
+ /// @param new_config is the new configuration
+ ///
+ /// @return returns an Element that contains the results of configuration
+ /// update composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
+ new_config);
+
+ /// @brief Instance method invoked by the configuration event handler and
+ /// which processes the actual configuration check. Provides behavioral
+ /// path for both integrated and stand-alone modes. The current
+ /// implementation will merge the configuration update into the existing
+ /// configuration and then invoke the application process' configure method
+ /// with a final rollback.
+ ///
+ /// @param new_config is the new configuration
+ ///
+ /// @return returns an Element that contains the results of configuration
+ /// update composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr checkConfig(isc::data::ConstElementPtr
+ new_config);
+
+ /// @brief Reconfigures the process from a configuration file
+ ///
+ /// By default the file is assumed to be a JSON text file whose contents
+ /// include at least:
+ ///
+ /// @code
+ /// { "<module-name>": {<module-config>}
+ /// }
+ ///
+ /// where:
+ /// module-name : is a label which uniquely identifies the
+ /// configuration data for this controller's application
+ ///
+ /// module-config: a set of zero or more JSON elements which comprise
+ /// the application's configuration values
+ /// @endcode
+ ///
+ /// To translate the JSON content into Elements, @c parseFile() is called
+ /// first. This virtual method provides derivations a means to parse the
+ /// file content using an alternate parser. If it returns an empty pointer
+ /// than the JSON parsing providing by Element::fromJSONFile() is called.
+ ///
+ /// Once parsed, the method extracts the set of configuration
+ /// elements for the module-name that matches the controller's app_name_,
+ /// looks for the loggers entry and, if present uses it to configure
+ /// logging. It then passes that set into @c updateConfig() (or
+ /// @c checkConfig()).
+ ///
+ /// The file may contain an arbitrary number of other modules.
+ ///
+ /// @return returns an Element that contains the results of configuration
+ /// update composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configFromFile();
+
+ /// @brief Fetches the name of the application under control.
+ ///
+ /// @return returns the controller service name string
+ std::string getAppName() const {
+ return (app_name_);
+ }
+
+ /// @brief Fetches the name of the application executable.
+ ///
+ /// @return returns the controller logger name string
+ std::string getBinName() const {
+ return (bin_name_);
+ }
+
+ /// @brief handler for version-get command
+ ///
+ /// This method handles the version-get command. It returns the basic and
+ /// extended version.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return answer with version details.
+ isc::data::ConstElementPtr
+ versionGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for 'build-report' command
+ ///
+ /// This method handles build-report command. It returns the output printed
+ /// by configure script which contains most compilation parameters.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return answer with build report
+ isc::data::ConstElementPtr
+ buildReportHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-get command
+ ///
+ /// This method handles the config-get command, which retrieves
+ /// the current configuration and returns it in response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return current configuration wrapped in a response
+ isc::data::ConstElementPtr
+ configGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-hash-get command
+ ///
+ /// This method handles the config-hash-get command, which retrieves
+ /// the current configuration and returns it in response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return hash of current configuration wrapped in a response
+ isc::data::ConstElementPtr
+ configHashGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-write command
+ ///
+ /// This handle processes write-config command, which writes the
+ /// current configuration to disk. This command takes one optional
+ /// parameter called filename. If specified, the current configuration
+ /// will be written to that file. If not specified, the file used during
+ /// Kea start-up will be used. To avoid any exploits, the path is
+ /// always relative and .. is not allowed in the filename. This is
+ /// a security measure against exploiting file writes remotely.
+ ///
+ /// @param command (ignored)
+ /// @param args may contain optional string argument filename
+ /// @return status of the configuration file write
+ isc::data::ConstElementPtr
+ configWriteHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-test command
+ ///
+ /// This method handles the config-test command, which checks
+ /// configuration specified in args parameter.
+ ///
+ /// @param command (ignored)
+ /// @param args configuration to be checked.
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ configTestHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-reload command
+ ///
+ /// This method handles the config-reload command, which reloads
+ /// the configuration file.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ configReloadHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for config-set command
+ ///
+ /// This method handles the config-set command, which loads
+ /// configuration specified in args parameter.
+ ///
+ /// @param command (ignored)
+ /// @param args configuration to be checked.
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ configSetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for 'shutdown' command
+ ///
+ /// This method handles shutdown command. It initiates the shutdown procedure
+ /// using CPL methods.
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return answer confirming that the shutdown procedure is started
+ isc::data::ConstElementPtr
+ shutdownHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for server-tag-get command
+ ///
+ /// This method handles the server-tag-get command, which retrieves
+ /// the current server tag and returns it in response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return current configuration wrapped in a response
+ isc::data::ConstElementPtr
+ serverTagGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @brief handler for status-get command
+ ///
+ /// This method handles the status-get command, which retrieves
+ /// the server process information i.e. the pid and returns it in
+ /// response.
+ ///
+ /// @param command (ignored)
+ /// @param args (ignored)
+ /// @return process information wrapped in a response
+ isc::data::ConstElementPtr
+ statusGetHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+protected:
+ /// @brief Virtual method that provides derivations the opportunity to
+ /// support additional command line options. It is invoked during command
+ /// line argument parsing (see parseArgs method) if the option is not
+ /// recognized as a stock DControllerBase option.
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hyphen(s)
+ /// @param optarg is the argument value (if one) associated with the option
+ ///
+ /// @return must return true if the option was valid, false if it is
+ /// invalid. (Note the default implementation always returns false.)
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Abstract method that is responsible for instantiating the
+ /// application process object. It is invoked by the controller after
+ /// command line argument parsing as part of the process initialization
+ /// (see initProcess method).
+ ///
+ /// @return returns a pointer to the new process object (DProcessBase*)
+ /// or NULL if the create fails.
+ /// Note this value is subsequently wrapped in a smart pointer.
+ virtual DProcessBase* createProcess() = 0;
+
+ /// @brief Virtual method which can be used to contribute derivation
+ /// specific usage text. It is invoked by the usage() method under
+ /// invalid usage conditions.
+ ///
+ /// @return returns the desired text.
+ virtual const std::string getUsageText() const {
+ return ("");
+ }
+
+ /// @brief Virtual method which returns a string containing the option
+ /// letters for any custom command line options supported by the derivation.
+ /// These are added to the stock options of "c", "d", ..., during command
+ /// line interpretation.
+ ///
+ /// @return returns a string containing the custom option letters.
+ virtual const std::string getCustomOpts() const {
+ return ("");
+ }
+
+ /// @brief Check the configuration
+ ///
+ /// Called by @c launch() when @c check_only_ mode is enabled
+ /// @throw VersionMessage when successful but a message should be displayed
+ /// @throw InvalidUsage when an error was detected
+ void checkConfigOnly();
+
+ /// @brief Application-level signal processing method.
+ ///
+ /// This method is the last step in processing a OS signal occurrence.
+ /// It currently supports the following signals as follows:
+ /// -# SIGHUP - instigates reloading the configuration file
+ /// -# SIGINT - instigates a graceful shutdown
+ /// -# SIGTERM - instigates a graceful shutdown
+ /// If it receives any other signal, it will issue a debug statement and
+ /// discard it.
+ /// Derivations wishing to support additional signals could override this
+ /// method with one that: processes the signal if it is one of additional
+ /// signals, otherwise invoke this method (DControllerBase::processSignal())
+ /// with the signal value.
+ /// @todo Provide a convenient way for derivations to register additional
+ /// signals.
+ virtual void processSignal(int signum);
+
+ /// @brief Supplies whether or not verbose logging is enabled.
+ ///
+ /// @return returns true if verbose logging is enabled.
+ bool isVerbose() const {
+ return (verbose_);
+ }
+
+ /// @brief Method for enabling or disabling verbose logging.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setVerbose(bool value) {
+ verbose_ = value;
+ }
+
+ /// @brief Supplies whether or not check only mode is enabled.
+ ///
+ /// @return returns true if check only is enabled.
+ bool isCheckOnly() const {
+ return (check_only_);
+ }
+
+ /// @brief Method for enabling or disabling check only mode.
+ ///
+ /// @todo this method and @c setVerbose are currently not used.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setCheckOnly(bool value) {
+ check_only_ = value;
+ }
+
+ /// @brief Getter for fetching the controller's IOService
+ ///
+ /// @return returns a pointer reference to the IOService.
+ asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Static getter which returns the singleton instance.
+ ///
+ /// @return returns a pointer reference to the private singleton instance
+ /// member.
+ static DControllerBasePtr& getController() {
+ return (controller_);
+ }
+
+ /// @brief Static setter which sets the singleton instance.
+ ///
+ /// @param controller is a pointer to the singleton instance.
+ ///
+ /// @throw throws DControllerBase error if an attempt is made to set the
+ /// instance a second time.
+ static void setController(const DControllerBasePtr& controller);
+
+ /// @brief Processes the command line arguments. It is the first step
+ /// taken after the controller has been launched. It combines the stock
+ /// list of options with those returned by getCustomOpts(), and uses
+ /// cstdlib's getopt to loop through the command line.
+ /// It handles stock options directly, and passes any custom options into
+ /// the customOption method. Currently there are only some stock options
+ /// -c/t for specifying the configuration file, -d for verbose logging,
+ /// and -v/V/W for version reports.
+ ///
+ /// @param argc is the number of command line arguments supplied
+ /// @param argv is the array of string (char *) command line arguments
+ ///
+ /// @throw InvalidUsage when there are usage errors.
+ /// @throw VersionMessage if the -v, -V or -W arguments is given.
+ void parseArgs(int argc, char* argv[]);
+
+ ///@brief Parse a given file into Elements
+ ///
+ /// This method provides a means for deriving classes to use alternate
+ /// parsing mechanisms to parse configuration files into the corresponding
+ /// isc::data::Elements. The elements produced must be equivalent to those
+ /// which would be produced by the original JSON parsing. Implementations
+ /// should throw when encountering errors.
+ ///
+ /// The default implementation returns an empty pointer, signifying to
+ /// callers that they should submit the file to the original parser.
+ ///
+ /// @param file_name pathname of the file to parse
+ ///
+ /// @return pointer to the elements created
+ ///
+ virtual isc::data::ConstElementPtr parseFile(const std::string& file_name);
+
+ ///@brief Parse text into Elements
+ ///
+ /// This method provides a means for deriving classes to use alternate
+ /// parsing mechanisms to parse configuration text into the corresponding
+ /// isc::data::Elements. The elements produced must be equivalent to those
+ /// which would be produced by the original JSON parsing. Implementations
+ /// should throw when encountering errors.
+ ///
+ /// The default implementation returns an empty pointer, signifying to
+ /// callers that they should submit the text to the original parser.
+ ///
+ /// @param input text to parse
+ ///
+ /// @return pointer to the elements created
+ ///
+ virtual isc::data::ConstElementPtr parseText(const std::string& input) {
+ static_cast<void>(input); // just tu shut up the unused parameter warning
+ isc::data::ConstElementPtr elements;
+ return (elements);
+ }
+
+ /// @brief Instantiates the application process and then initializes it.
+ /// This is the second step taken during launch, following successful
+ /// command line parsing. It is used to invoke the derivation-specific
+ /// implementation of createProcess, following by an invoking of the
+ /// newly instantiated process's init method.
+ ///
+ /// @throw throws DControllerBaseError or indirectly DProcessBaseError
+ /// if there is a failure creating or initializing the application process.
+ void initProcess();
+
+ /// @brief Invokes the application process's event loop,(DBaseProcess::run).
+ /// It is called during launch only after successfully completing the
+ /// requested setup: command line parsing, application initialization,
+ /// and session establishment (if not stand-alone).
+ /// The process event loop is expected to only return upon application
+ /// shutdown either in response to the shutdown command or due to an
+ /// unrecoverable error.
+ ///
+ // @throw throws DControllerBaseError or indirectly DProcessBaseError
+ void runProcess();
+
+ /// @brief Initiates shutdown procedure. This method is invoked
+ /// by executeCommand in response to the shutdown command. It will invoke
+ /// the application process's shutdown method which causes the process to
+ /// to begin its shutdown process.
+ ///
+ /// Note, it is assumed that the process of shutting down is neither
+ /// instantaneous nor synchronous. This method does not "block" waiting
+ /// until the process has halted. Rather it is used to convey the
+ /// need to shutdown. A successful return indicates that the shutdown
+ /// has successfully commenced, but does not indicate that the process
+ /// has actually exited.
+ ///
+ /// @return returns an Element that contains the results of shutdown
+ /// command composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ ///
+ /// @param args is a set of derivation-specific arguments (if any)
+ /// for the shutdown command.
+ isc::data::ConstElementPtr shutdownProcess(isc::data::ConstElementPtr args);
+
+ /// @brief Initializes signal handling
+ ///
+ /// This method configures the controller to catch and handle signals.
+ /// It instantiates a IOSignalSet which listens for SIGHUP, SIGINT, and
+ /// SIGTERM.
+ void initSignalHandling();
+
+ /// @brief Fetches the current process
+ ///
+ /// @return a pointer to the current process instance.
+ DProcessBasePtr getProcess() {
+ return (process_);
+ }
+
+ /// @brief Prints the program usage text to std error.
+ ///
+ /// @param text is a string message which will preceded the usage text.
+ /// This is intended to be used for specific usage violation messages.
+ void usage(const std::string& text);
+
+ /// @brief Fetches text containing additional version specifics
+ ///
+ /// This method is provided so derivations can append any additional
+ /// desired information such as library dependencies to the extended
+ /// version text returned when DControllerBase::getVersion(true) is
+ /// invoked.
+ /// @return a string containing additional version info
+ virtual std::string getVersionAddendum() { return (""); }
+
+ /// @brief Deals with other (i.e. not application name) global objects.
+ ///
+ /// Code shared between configuration handlers:
+ /// - check obsolete or unknown (aka unsupported) objects.
+ ///
+ /// @param args Command arguments.
+ /// @return Error message or empty string.
+ std::string handleOtherObjects(isc::data::ConstElementPtr args);
+
+private:
+ /// @brief Name of the service under control.
+ /// This name is used as the configuration module name and appears in log
+ /// statements.
+ std::string app_name_;
+
+ /// @brief Name of the service executable.
+ /// By convention this matches the executable name. It is also used to
+ /// establish the logger name.
+ std::string bin_name_;
+
+ /// @brief Indicates if the verbose logging mode is enabled.
+ bool verbose_;
+
+ /// @brief Indicates if the check only mode for the configuration
+ /// is enabled (usually specified by the command line -t argument).
+ bool check_only_;
+
+ /// @brief Pointer to the instance of the process.
+ ///
+ /// This is required for config and command handlers to gain access to
+ /// the process
+ DProcessBasePtr process_;
+
+ /// @brief Shared pointer to an IOService object, used for ASIO operations.
+ isc::asiolink::IOServicePtr io_service_;
+
+ /// @brief ASIO signal set.
+ isc::asiolink::IOSignalSetPtr io_signal_set_;
+
+ /// @brief Singleton instance value.
+ static DControllerBasePtr controller_;
+
+// DControllerTest is named a friend class to facilitate unit testing while
+// leaving the intended member scopes intact.
+friend class DControllerTest;
+};
+
+} // namespace process
+} // namespace isc
+
+#endif
diff --git a/src/lib/process/d_log.cc b/src/lib/process/d_log.cc
new file mode 100644
index 0000000..a449996
--- /dev/null
+++ b/src/lib/process/d_log.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2013-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/.
+
+/// Defines the logger used by the top-level component of kea-dhcp-ddns.
+
+#include <config.h>
+
+#include <process/d_log.h>
+
+namespace isc {
+namespace process {
+
+/// @brief Defines the logger used within libkea-process library.
+isc::log::Logger dctl_logger("dctl");
+
+} // namespace process
+} // namespace isc
+
diff --git a/src/lib/process/d_log.h b/src/lib/process/d_log.h
new file mode 100644
index 0000000..5a20cf4
--- /dev/null
+++ b/src/lib/process/d_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2013-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/.
+
+#ifndef D_LOG_H
+#define D_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <process/process_messages.h>
+
+namespace isc {
+namespace process {
+
+/// Define the loggers used within libkea-process library.
+extern isc::log::Logger dctl_logger;
+
+} // namespace process
+} // namespace isc
+
+#endif // D_LOG_H
diff --git a/src/lib/process/d_process.h b/src/lib/process/d_process.h
new file mode 100644
index 0000000..e2b9309
--- /dev/null
+++ b/src/lib/process/d_process.h
@@ -0,0 +1,214 @@
+// Copyright (C) 2013-2023 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 D_PROCESS_H
+#define D_PROCESS_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <process/d_cfg_mgr.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <atomic>
+
+namespace isc {
+namespace process {
+
+/// @brief Exception thrown if the process encountered an operational error.
+class DProcessBaseError : public isc::Exception {
+public:
+ DProcessBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief String value for the version-get command.
+static const std::string VERSION_GET_COMMAND("version-get");
+
+/// @brief String value for the build-report command.
+static const std::string BUILD_REPORT_COMMAND("build-report");
+
+/// @brief String value for the config-get command.
+static const std::string CONFIG_GET_COMMAND("config-get");
+
+/// @brief String value for the config-hash-get command.
+static const std::string CONFIG_HASH_GET_COMMAND("config-hash-get");
+
+/// @brief String value for the config-write command.
+static const std::string CONFIG_WRITE_COMMAND("config-write");
+
+/// @brief String value for the config-test command.
+static const std::string CONFIG_TEST_COMMAND("config-test");
+
+/// @brief String value for the config-reload command.
+static const std::string CONFIG_RELOAD_COMMAND("config-reload");
+
+/// @brief String value for the config-set command.
+static const std::string CONFIG_SET_COMMAND("config-set");
+
+/// @brief String value for the server-tag-get command.
+static const std::string SERVER_TAG_GET_COMMAND("server-tag-get");
+
+/// @brief String value for the shutdown command.
+static const std::string SHUT_DOWN_COMMAND("shutdown");
+
+/// @brief String value for the status-get command.
+static const std::string STATUS_GET_COMMAND("status-get");
+
+/// @brief Application Process Interface
+///
+/// DProcessBase is an abstract class represents the primary "application"
+/// level object in a "managed" asynchronous application. It provides a uniform
+/// interface such that a managing layer can construct, initialize, and start
+/// the application's event loop. The event processing is centered around the
+/// use of isc::asiolink::io_service. The io_service is shared between the
+/// managing layer and the DProcessBase. This allows management layer IO such
+/// as directives to be sensed and handled, as well as processing IO activity
+/// specific to the application. In terms of management layer IO, there are
+/// methods shutdown, configuration updates, and commands unique to the
+/// application.
+class DProcessBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param app_name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ /// @param cfg_mgr the configuration manager instance that handles
+ /// configuration parsing.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ DProcessBase(const char* app_name, asiolink::IOServicePtr io_service,
+ DCfgMgrBasePtr cfg_mgr)
+ : app_name_(app_name), io_service_(io_service), shut_down_flag_(false),
+ cfg_mgr_(cfg_mgr) {
+ if (!io_service_) {
+ isc_throw (DProcessBaseError, "IO Service cannot be null");
+ }
+
+ if (!cfg_mgr_) {
+ isc_throw (DProcessBaseError, "CfgMgr cannot be null");
+ }
+ };
+
+ /// @brief May be used after instantiation to perform initialization unique
+ /// to application. It must be invoked prior to invoking run. This would
+ /// likely include the creation of additional IO sources and their
+ /// integration into the io_service.
+ /// @throw DProcessBaseError if the initialization fails.
+ virtual void init() = 0;
+
+ /// @brief Implements the process's event loop. In its simplest form it
+ /// would an invocation io_service_->run(). This method should not exit
+ /// until the process itself is exiting due to a request to shutdown or
+ /// some anomaly is forcing an exit.
+ /// @throw DProcessBaseError if an operational error is encountered.
+ virtual void run() = 0;
+
+ /// @brief Initiates the process's shutdown process.
+ ///
+ /// This is last step in the shutdown event callback chain, that is
+ /// intended to notify the process it is to begin its shutdown process.
+ ///
+ /// @param args an Element set of shutdown arguments (if any) that are
+ /// supported by the process derivation.
+ ///
+ /// @return an Element that contains the results of argument processing,
+ /// consisting of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ ///
+ /// @throw DProcessBaseError if an operational error is encountered.
+ virtual isc::data::ConstElementPtr
+ shutdown(isc::data::ConstElementPtr args) = 0;
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This method may be called multiple times during the process lifetime.
+ /// Certainly once during process startup, and possibly later if the user
+ /// alters configuration. This method must not throw, it should catch any
+ /// processing errors and return a success or failure answer as described
+ /// below. On success the last commit timestamp must be updated.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @param check_only true if configuration is to be verified only, not applied
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr
+ configure(isc::data::ConstElementPtr config_set,
+ bool check_only = false) = 0;
+
+ /// @brief Destructor
+ virtual ~DProcessBase(){};
+
+ /// @brief Checks if the process has been instructed to shut down.
+ ///
+ /// @return true if process shutdown flag is true.
+ bool shouldShutdown() const {
+ return (shut_down_flag_);
+ }
+
+ /// @brief Sets the process shut down flag to the given value.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setShutdownFlag(bool value) {
+ shut_down_flag_ = value;
+ }
+
+ /// @brief Fetches the application name.
+ ///
+ /// @return application name string.
+ const std::string getAppName() const {
+ return (app_name_);
+ }
+
+ /// @brief Fetches the controller's IOService.
+ ///
+ /// @return a reference to the controller's IOService.
+ asiolink::IOServicePtr& getIoService() {
+ return (io_service_);
+ }
+
+ /// @brief Convenience method for stopping IOservice processing.
+ /// Invoking this will cause the process to exit any blocking
+ /// IOService method such as run(). No further IO events will be
+ /// processed.
+ void stopIOService() {
+ io_service_->stop();
+ }
+
+ /// @brief Fetches the process's configuration manager.
+ ///
+ /// @return a reference to the configuration manager.
+ DCfgMgrBasePtr& getCfgMgr() {
+ return (cfg_mgr_);
+ }
+
+private:
+ /// @brief Text label for the process. Generally used in log statements,
+ /// but otherwise can be arbitrary.
+ std::string app_name_;
+
+ /// @brief The IOService to be used for asynchronous event handling.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Boolean flag set when shutdown has been requested.
+ std::atomic<bool> shut_down_flag_;
+
+ /// @brief Pointer to the configuration manager.
+ DCfgMgrBasePtr cfg_mgr_;
+};
+
+/// @brief Defines a shared pointer to DProcessBase.
+typedef boost::shared_ptr<DProcessBase> DProcessBasePtr;
+
+} // namespace process
+} // namespace isc
+
+#endif
diff --git a/src/lib/process/daemon.cc b/src/lib/process/daemon.cc
new file mode 100644
index 0000000..3ca8579
--- /dev/null
+++ b/src/lib/process/daemon.cc
@@ -0,0 +1,265 @@
+// Copyright (C) 2014-2022 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 <cc/data.h>
+#include <process/daemon.h>
+#include <process/log_parser.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_name.h>
+#include <log/logger_support.h>
+#include <process/config_base.h>
+#include <process/redact_config.h>
+#include <util/filename.h>
+
+#include <functional>
+#include <sstream>
+#include <fstream>
+
+#include <errno.h>
+
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+/// @brief provides default implementation for basic daemon operations
+///
+/// This file provides stub implementations that are expected to be redefined
+/// in derived classes (e.g. ControlledDhcpv6Srv)
+namespace isc {
+namespace process {
+
+bool Daemon::verbose_ = false;
+
+std::string Daemon::proc_name_("");
+
+std::string Daemon::default_logger_name_("kea");
+
+Daemon::Daemon()
+ : signal_set_(), config_file_(""),
+ pid_file_dir_(DATA_DIR), pid_file_(), am_file_author_(false),
+ exit_value_(EXIT_SUCCESS) {
+
+ // The pid_file_dir can be overridden via environment variable
+ // This is primarily intended to simplify testing
+ const char* const env = getenv("KEA_PIDFILE_DIR");
+ if (env) {
+ pid_file_dir_ = env;
+ }
+}
+
+Daemon::~Daemon() {
+ if (pid_file_ && am_file_author_) {
+ pid_file_->deleteFile();
+ }
+}
+
+void Daemon::cleanup() {
+}
+
+void Daemon::shutdown() {
+}
+
+void Daemon::configureLogger(const ConstElementPtr& log_config,
+ const ConfigPtr& storage) {
+
+ if (log_config) {
+ ConstElementPtr loggers = log_config->get("loggers");
+ if (loggers) {
+ LogConfigParser parser(storage);
+ parser.parseConfiguration(loggers, verbose_);
+ }
+ }
+}
+
+void
+Daemon::setVerbose(bool verbose) {
+ verbose_ = verbose;
+}
+
+bool
+Daemon::getVerbose() {
+ return (verbose_);
+}
+
+void Daemon::loggerInit(const char* name, bool verbose) {
+
+ setenv("KEA_LOGGER_DESTINATION", "stdout", 0);
+
+ // Initialize logger system
+ isc::log::initLogger(name, isc::log::DEBUG, isc::log::MAX_DEBUG_LEVEL, 0);
+
+ // Apply default configuration (log INFO or DEBUG to stdout)
+ isc::log::setDefaultLoggingOutput(verbose);
+}
+
+std::string Daemon::getVersion(bool /*extended*/) {
+ isc_throw(isc::NotImplemented, "Daemon::getVersion() called");
+}
+
+std::string
+Daemon::getConfigFile() const {
+ return (config_file_);
+}
+
+void
+Daemon::setConfigFile(const std::string& config_file) {
+ config_file_ = config_file;
+}
+
+void
+Daemon::checkConfigFile() const {
+ if (config_file_.empty()) {
+ isc_throw(isc::BadValue, "config file name is not set");
+ }
+
+ // Create Filename instance from the config_file_ pathname, and
+ // check the file name component.
+ isc::util::Filename file(config_file_);
+ if (file.name().empty()) {
+ isc_throw(isc::BadValue, "config file:" << config_file_
+ << " is missing file name");
+ }
+}
+
+std::string
+Daemon::getProcName() {
+ return (proc_name_);
+};
+
+void
+Daemon::setProcName(const std::string& proc_name) {
+ proc_name_ = proc_name;
+}
+
+std::string
+Daemon::getPIDFileDir() const {
+ return(pid_file_dir_);
+}
+
+void
+Daemon::setPIDFileDir(const std::string& pid_file_dir) {
+ pid_file_dir_ = pid_file_dir;
+}
+
+std::string
+Daemon::getPIDFileName() const {
+ if (pid_file_) {
+ return (pid_file_->getFilename());
+ }
+
+ return ("");
+};
+
+void
+Daemon::setPIDFileName(const std::string& pid_file_name) {
+ if (pid_file_) {
+ isc_throw(isc::InvalidOperation, "Daemon::setConfigFile"
+ " file name already set to:" << pid_file_->getFilename());
+ }
+
+ if (pid_file_name.empty()) {
+ isc_throw(isc::BadValue, "Daemon::setPIDFileName"
+ " file name may not be empty");
+ }
+
+ pid_file_.reset(new util::PIDFile(pid_file_name));
+};
+
+std::string
+Daemon::makePIDFileName() const {
+ if (config_file_.empty()) {
+ isc_throw(isc::InvalidOperation,
+ "Daemon::makePIDFileName config file name is not set");
+ }
+
+ // Create Filename instance from the config_file_ pathname, so we can
+ // extract the fname component.
+ isc::util::Filename file(config_file_);
+ if (file.name().empty()) {
+ isc_throw(isc::BadValue, "Daemon::makePIDFileName config file:"
+ << config_file_ << " is missing file name");
+ }
+
+ if (proc_name_.empty()) {
+ isc_throw(isc::InvalidOperation,
+ "Daemon::makePIDFileName process name is not set");
+ }
+
+ // Make the pathname for the PID file from the runtime directory,
+ // configuration name and process name.
+ std::ostringstream stream;
+ stream << pid_file_dir_ << "/" << file.name()
+ << "." << proc_name_ << ".pid";
+
+ return(stream.str());
+};
+
+void
+Daemon::createPIDFile(int pid) {
+ // If pid_file_ hasn't been instantiated explicitly, then do so
+ // using the default name.
+ if (!pid_file_) {
+ setPIDFileName(makePIDFileName());
+ }
+
+ // If we find a pre-existing file containing a live PID we bail.
+ int chk_pid = pid_file_->check();
+ if (chk_pid > 0) {
+ isc_throw(DaemonPIDExists, "Daemon::createPIDFile: PID: " << chk_pid
+ << " exists, PID file: " << getPIDFileName());
+ }
+
+ if (pid == 0) {
+ // Write the PID of the current process
+ pid_file_->write();
+ } else {
+ // Write the PID we were given
+ pid_file_->write(pid);
+ }
+
+ am_file_author_ = true;
+}
+
+size_t
+Daemon::writeConfigFile(const std::string& config_file,
+ ConstElementPtr cfg) const {
+ if (!cfg) {
+ isc_throw(Unexpected, "Can't write configuration: conversion to JSON failed");
+ }
+
+ std::ofstream out(config_file, std::ios::trunc);
+ if (!out.good()) {
+ isc_throw(Unexpected, "Unable to open file " + config_file + " for writing");
+ }
+
+ // Write the actual content using pretty printing.
+ prettyPrint(cfg, out);
+
+ size_t bytes = static_cast<size_t>(out.tellp());
+
+ out.close();
+
+ return (bytes);
+}
+
+std::list<std::list<std::string>>
+Daemon::jsonPathsToRedact() const {
+ static std::list<std::list<std::string>> const list;
+ return list;
+}
+
+isc::data::ConstElementPtr
+Daemon::redactConfig(isc::data::ConstElementPtr const& config) {
+ isc::data::ConstElementPtr result(config);
+ for (std::list<std::string>& json_path : jsonPathsToRedact()) {
+ result = isc::process::redactConfig(result, json_path);
+ }
+ return result;
+}
+
+} // namespace process
+} // namespace isc
diff --git a/src/lib/process/daemon.h b/src/lib/process/daemon.h
new file mode 100644
index 0000000..d64445a
--- /dev/null
+++ b/src/lib/process/daemon.h
@@ -0,0 +1,296 @@
+// Copyright (C) 2014-2021 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 DAEMON_H
+#define DAEMON_H
+
+#include <cc/data.h>
+#include <process/config_base.h>
+#include <util/pid_file.h>
+#include <asiolink/io_service_signal.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace process {
+
+/// @brief Exception thrown when the PID file points to a live PID
+class DaemonPIDExists : public Exception {
+public:
+ DaemonPIDExists(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Base class for all services
+///
+/// This is the base class that all daemons (DHCPv4, DHCPv6, D2 and possibly
+/// others) are derived from. It provides a standard interface for starting up,
+/// reconfiguring, shutting down and several other operations. It also covers
+/// some common operations.
+///
+/// This class is not expected to be instantiated directly, but rather daemon
+/// implementations should derive from it.
+///
+/// Methods are not pure virtual, as we need to instantiate basic daemons (e.g.
+/// Dhcpv4Srv and Dhcpv6Srv) in tests, without going through the hassles of
+/// implementing stub methods.
+///
+/// @note Only one instance of this class is instantiated as it encompasses
+/// the whole operation of the server. Nothing, however, enforces the
+/// singleton status of the object.
+class Daemon : public boost::noncopyable {
+
+public:
+ /// @brief Default constructor
+ ///
+ /// Initializes the object installing custom signal handlers for the
+ /// process to NULL.
+ Daemon();
+
+ /// @brief Destructor
+ ///
+ /// Having virtual destructor ensures that all derived classes will have
+ /// virtual destructor as well.
+ virtual ~Daemon();
+
+ /// @brief Performs final deconfiguration.
+ ///
+ /// Performs configuration backend specific final clean-up. This is called
+ /// shortly before the daemon terminates. Depending on backend, it may
+ /// terminate existing msgq session, close LDAP connection or similar.
+ ///
+ /// The daemon is not expected to receive any further commands or
+ /// configuration updates as it is in final stages of shutdown.
+ virtual void cleanup();
+
+ /// @brief Initiates shutdown procedure for the whole server.
+ virtual void shutdown();
+
+ /// @brief Initializes logger
+ ///
+ /// This method initializes logging system. It also sets the default
+ /// output to stdout. This is used in early stages of the startup
+ /// phase before config file and parsed and proper logging details
+ /// are known.
+ ///
+ /// @param log_name name used in logger initialization
+ /// @param verbose verbose mode (true usually enables DEBUG messages)
+ static void loggerInit(const char* log_name, bool verbose);
+
+ /// @brief Configures logger
+ ///
+ /// Applies configuration stored in a top-level structure in the
+ /// configuration file. This structure has a "loggers" array that
+ /// contains 0 or more entries, each configuring one logging source
+ /// (name, severity, debuglevel), each with zero or more outputs (file,
+ /// maxsize, maximum number of files).
+ ///
+ /// @param log_config JSON structures that describe logging
+ /// @param storage configuration will be stored here
+ static void configureLogger(const isc::data::ConstElementPtr& log_config,
+ const isc::process::ConfigPtr& storage);
+
+ /// @brief Sets or clears verbose mode
+ ///
+ /// Verbose mode (-v in command-line) triggers loggers to log everything
+ /// (sets severity to DEBUG and debuglevel to 99). Values specified in the
+ /// config file are ignored.
+ ///
+ /// @param verbose specifies if verbose should be set or not
+ static void setVerbose(const bool verbose);
+
+ /// @brief Returns if running in verbose mode
+ ///
+ /// @return verbose mode
+ static bool getVerbose();
+
+ /// @brief returns Kea version on stdout and exits.
+ ///
+ /// With extended == false, this method returns a simple string
+ /// containing version number. With extended == true, it returns
+ /// also additional information about sources. It is expected to
+ /// return extra information about dependencies and used DB backends.
+ ///
+ /// As there is no static virtual methods in C++ this class method
+ /// has to be redefined in derived classes and called with the
+ /// derived class name or a child name.
+ ///
+ /// @param extended print additional information?
+ /// @return text string
+ static std::string getVersion(bool extended);
+
+ /// @brief Returns config file name.
+ /// @return text string
+ std::string getConfigFile() const;
+
+ /// @brief Sets the configuration file name
+ ///
+ /// @param config_file pathname of the configuration file
+ void setConfigFile(const std::string& config_file);
+
+ /// @brief Checks the configuration file name.
+ /// @throw BadValue when the configuration file name is bad.
+ void checkConfigFile() const;
+
+ /// @brief Writes current configuration to specified file
+ ///
+ /// This method writes the current configuration to specified file.
+ /// @todo: this logically more belongs to CPL process file. Once
+ /// Daemon is merged with CPL architecture, it will be a better
+ /// fit.
+ ///
+ /// If cfg is not specified, the current config (as returned by
+ /// CfgMgr::instance().getCurrentCfg() will be returned.
+ ///
+ /// @param config_file name of the file to write the configuration to
+ /// @param cfg configuration to write (optional)
+ /// @return number of files written
+ /// @throw Unexpected if CfgMgr can't retrieve configuration or file cannot
+ /// be written
+ virtual size_t
+ writeConfigFile(const std::string& config_file,
+ isc::data::ConstElementPtr cfg = isc::data::ConstElementPtr()) const;
+
+ /// @brief returns the process name
+ /// This value is used as when forming the default PID file name
+ /// @return text string
+ static std::string getProcName();
+
+ /// @brief Sets the process name
+ /// @param proc_name name the process by which the process is recognized
+ static void setProcName(const std::string& proc_name);
+
+ /// @brief Returns the directory used when forming default PID file name
+ /// @return text string
+ std::string getPIDFileDir() const;
+
+ /// @brief Sets the PID file directory
+ /// @param pid_file_dir path into which the PID file should be written
+ /// Note the value should not include a trailing slash, '/'
+ void setPIDFileDir(const std::string& pid_file_dir);
+
+ /// @brief Returns the current PID file name
+ /// @return text string
+ std::string getPIDFileName() const;
+
+ /// @brief Sets PID file name
+ ///
+ /// If this method is called prior to calling createPIDFile,
+ /// the value passed in will be treated as the full file name
+ /// for the PID file. This provides a means to override the
+ /// default file name with an explicit value.
+ ///
+ /// @param pid_file_name file name to be used as the PID file
+ void setPIDFileName(const std::string& pid_file_name);
+
+ /// @brief Creates the PID file
+ ///
+ /// If the PID file name has not been previously set, the method
+ /// uses manufacturePIDFileName() to set it. If the PID file
+ /// name refers to an existing file whose contents are a PID whose
+ /// process is still alive, the method will throw a DaemonPIDExists
+ /// exception. Otherwise, the file created (or truncated) and
+ /// the given pid (if not zero) is written to the file.
+ ///
+ /// @param pid PID to write to the file if not zero, otherwise the
+ /// PID of the current process is used.
+ void createPIDFile(int pid = 0);
+
+ /// @brief Returns default logger name.
+ static std::string getDefaultLoggerName() {
+ return (default_logger_name_);
+ }
+
+ /// @brief Sets the default logger name.
+ ///
+ /// This name is used in cases when a user doesn't provide a configuration
+ /// for logger in the Kea configuration file.
+ static void setDefaultLoggerName(const std::string& logger) {
+ default_logger_name_ = logger;
+ }
+
+ /// @brief Fetches the exit value.
+ int getExitValue() {
+ return (exit_value_);
+ }
+
+ /// @brief Sets the exit value.
+ ///
+ /// @param value new exit value.
+ void setExitValue(int value) {
+ exit_value_ = value;
+ }
+
+ /// @brief Return a list of all paths that contain passwords or secrets.
+ ///
+ /// Used in @ref isc::process::Daemon::redactConfig to only make copies and
+ /// only redact configuration subtrees that contain passwords or secrets. To
+ /// be overridden by derived classes.
+ ///
+ /// @return the list of lists of sequential JSON map keys needed to reach
+ /// the passwords and secrets.
+ virtual std::list<std::list<std::string>> jsonPathsToRedact() const;
+
+ /// @brief Redact a configuration.
+ ///
+ /// Calls @ref isc::process::redactConfig
+ ///
+ /// @param config the Element tree structure that describes the configuration.
+ ///
+ /// @return the redacted configuration
+ isc::data::ConstElementPtr redactConfig(isc::data::ConstElementPtr const& config);
+
+protected:
+
+ /// @brief A pointer to the object installing custom signal handlers.
+ ///
+ /// This pointer needs to be initialized to point to the @c IOSignalSet
+ /// object in the derived classes which need to handle signals received
+ /// by the process.
+ isc::asiolink::IOSignalSetPtr signal_set_;
+
+ /// @brief Manufacture the pid file name
+ std::string makePIDFileName() const;
+
+ /// @brief Timestamp of the start of the daemon.
+ boost::posix_time::ptime start_;
+
+private:
+ /// @brief Config file name or empty if config file not used.
+ std::string config_file_;
+
+ /// @brief Name of this process, used when creating its pid file
+ static std::string proc_name_;
+
+ /// @brief Pointer to the directory where PID file(s) are written
+ /// It defaults to --localstatedir / run
+ std::string pid_file_dir_;
+
+ /// @brief Pointer to the PID file for this process
+ isc::util::PIDFilePtr pid_file_;
+
+ /// @brief Indicates whether verbose mode is turned on or not.
+ static bool verbose_;
+
+ /// @brief Stores default logger name
+ static std::string default_logger_name_;
+
+ /// @brief Flag indicating if this instance created the file
+ bool am_file_author_;
+
+ /// @brief Exit value the process should use. Typically set
+ /// by the application during a controlled shutdown.
+ int exit_value_;
+};
+
+} // namespace process
+} // namespace isc
+
+#endif
diff --git a/src/lib/process/libprocess.dox b/src/lib/process/libprocess.dox
new file mode 100644
index 0000000..2ea2e5c
--- /dev/null
+++ b/src/lib/process/libprocess.dox
@@ -0,0 +1,195 @@
+// Copyright (C) 2016-2021 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/.
+
+/**
+ @page libprocess libkea-process - Controllable Process Layer (CPL)
+
+@section cpl Controllable Process Layer (CPL)
+During the design and development of D2 (Kea's DHCP-DDNS process), an abstract
+layer for process control, called the Controllable Process Layer or CPL, was
+created. Kea's DHCP servers were initially developed prior to D2 and thus
+before CPL existed.
+
+Out of short term convenience and the fact that only D2 was using it, the CPL
+was initially developed as part of D2 in src/bin/d2. In order to use CPL for
+new Kea processes, it has since been moved into its own library, libkea-process.
+The following sections describe the architecture of CPL and how it can be used to implement new daemons in Kea.
+
+The CPL provides the essentials for a controllable, configurable,
+asynchronous process. They are the result of an effort to distill the
+common facets of process control currently duplicated in Kea's
+DHCP servers into a reusable construct. The classes which form this abstract
+base are shown in the following class diagram:
+
+@image html abstract_app_classes.svg "Controllable Process Layer Classes"
+
+- isc::process::DControllerBase - provides all of the services necessary to manage
+an application process class derived from isc::d2::DProcess. These services include:
+ - Command line argument handling
+ - Process instantiation and initialization
+ - Support for stand-alone execution
+ - Process event loop invocation and shutdown
+
+ It creates and manages an instance of isc::process::DProcessBase. The CPL is
+ designed for asynchronous event processing applications. It is constructed
+ to use ASIO library for IO processing. @c DControllerBase owns an
+ isc::asiolink::IOService instance and it passes this into the @c
+ DProcessBase constructor. It is this @c IOService that is used to drive the
+ process's event loop. The controller is designed to provide any interfaces
+ between the process it controls and the outside world.
+
+ @c DControllerBase provides configuration for its process via a JSON file
+ specified as a mandatory command line argument. The file structure is
+ expected be as follows:
+
+ { "<module-name>": {<module-config>} }
+
+ where:
+ - module-name : is a label which uniquely identifies the
+ configuration data for the (i.e. the controlled process.)
+ It is the value returned by @ref
+ isc::process::DControllerBase::getAppName()
+
+ - module-config: a set of zero or more JSON elements which comprise
+ application's configuration values. Element syntax is governed
+ by those elements supported in isc::cc.
+
+ The file may contain an arbitrary number of other modules.
+
+ @todo Eventually, some sort of secure socket interface which supports remote
+ control operations such as configuration changes or status reporting will
+ likely be implemented.
+
+- isc::process::DProcessBase - defines an asynchronous-event processor (i.e.
+application) which provides a uniform interface to:
+ - Instantiate and initialize a process instance
+ - "Run" the application by starting its event loop
+ - Inject events to control the process
+It owns an instance of @c DCfgMgrBase.
+
+- isc::process::DCfgMgrBase - provides the mechanisms for managing an application's
+configuration. This includes services for parsing sets of configuration
+values, storing the parsed information in its converted form, and retrieving
+the information on demand. It owns an instance of @c DCfgContextBase, which
+provides a "global" context for information that is accessible before, during,
+and after parsing.
+
+- isc::process::DCfgContextBase - implements a container for configuration
+information or "context". It provides a single enclosure for the storage of
+configuration parameters or any other information that needs to accessible
+within a given context.
+
+The following sequence diagram shows how a configuration from file moves
+through the CPL layer:
+
+@image html config_from_file_sequence.svg "CPL Configuration From File Sequence"
+
+The CPL classes will likely move into a common library.
+
+@section cplSignals CPL Signal Handling
+
+CPL supports interaction with the outside world via OS signals. The default
+implementation supports the following signal driven behavior:
+- SIGHUP receipt of this signal will cause a reloading of the configuration
+file.
+- SIGINT/SIGTERM receipt of either of these signals will initiate an
+orderly shutdown.
+
+CPL applications wait for for process asynchronous IO events through
+isc::asiolink::IOService::run() or its variants. These calls are not
+interrupted upon signal receipt as is the select() function and while
+boost::asio provides a signal mechanism it requires linking in additional
+libraries. Therefore, CPL provides its own signal handling mechanism to
+propagate an OS signal such as SIGHUP to an IOService as a ready event with a
+callback.
+
+isc::process::DControllerBase uses two mechanisms to carry out signal handling. It
+uses isc::util::SignalSet to catch OS signals, and isc::process::IOSignalQueue to
+propagate them to its isc::asiolink::IOService as instances of
+isc::process::IOSignal.
+
+This CPL signaling class hierarchy is illustrated in the following diagram:
+
+@image html cpl_signal_classes.svg "CPL Signal Classes"
+
+The mechanics of isc::process::IOSignal are straight forward. Upon construction it
+is given the target isc::asiolink::IOService, the value of the OS signal to
+send (e.g. SIGINT, SIGHUP...), and an isc::process::IOSignalHandler. This handler
+should contain the logic the caller would normally execute in its OS signal
+handler. Each isc::process::IOSignal instance has a unique identifier called its
+sequence_id.
+
+Internally, IOSignal creates a 1 ms, one-shot timer, on the given
+IOService. When the timer expires its event handler invokes the caller's
+IOSignalHandler passing it the sequence_id of the IOSignal.
+
+Sending IOSignals is done through an isc::process::IOSignalQueue. This class is
+used to create the signals, house them until they are delivered, and dequeue
+them so they can be been handled. To generate an IOSignal when an OS signal
+arrives, the process's OS signal handler need only call
+isc::process::IOSignalQueue::pushSignal() with the appropriate values.
+
+To dequeue the IOSignal inside the caller's IOSignalHandler, one simply
+invokes isc::process::IOSignalQueue::popSignal() passing it the sequence_id
+parameter passed to the handler. This method returns a pointer to
+instigating IOSignal from which the value of OS signal (i.e. SIGINT,
+SIGUSR1...) can be obtained. Note that calling popSignal() removes the
+IOSignalPtr from the queue, which should reduce its reference count to
+zero upon exiting the handler (unless a deliberate copy of it is made).
+
+A typical isc::process::IOSignalHandler might be structured as follows:
+@code
+
+ void processSignal(IOSignalId sequence_id) {
+ // Pop the signal instance off the queue.
+ IOSignalPtr signal = io_signal_queue_->popSignal(sequence_id);
+
+ int os_signal_value = signal->getSignum();
+ :
+ // logic based on the signal value
+ :
+ }
+
+@endcode
+
+IOSignal's handler invocation code will catch, log ,and then swallow any
+exceptions thrown by an IOSignalHandler. This is done to protect the integrity
+IOService context.
+
+The following sequence diagram depicts the initialization of signal handling
+during startup and the subsequent receipt of a SIGHUP:
+
+@image html cpl_signal_sequence.svg "CPL Signal Handling Sequence"
+
+@section redact Redact Passwords
+
+There are two tools to remove sensitive data as passwords or secrets from logs:
+ - redactedAccessString for database access strings
+ - redactConfig for full configurations
+
+The jsonPathsToRedact method must be defined in derived classes following this
+procedure:
+ - Get all possible JSON paths from the root of the configuration to leaves that
+ fulfill the role of map keys and which contain "password" or "secret".
+ - For each of these paths, remove the root node and the leaf node.
+ - Include all the paths in the method. Duplicate subpaths are expected in the
+ case of common subpaths to different leaves.
+
+There are two special syntaxes:
+ - "[]" suggests that the searched element is a list. This is required for all
+ lists and is for performance gain.
+ - "*" as a last element in a JSON path tells the redacter to look in all
+ elements that follow for elements that contain "password" and "secret". This is
+ when the particular configuration that is targeted by the "*" does not have a
+ well defined structure, such as is the case for "parameters" in the
+ "hooks-libraries" map in "Dhcp4" and "Dhcp6".
+
+@section cplMTConsiderations Multi-Threading Consideration for Controllable Process Layer
+
+By default this library is not thread safe and currently there is no known
+exception.
+
+*/
diff --git a/src/lib/process/log_parser.cc b/src/lib/process/log_parser.cc
new file mode 100644
index 0000000..258aa4e
--- /dev/null
+++ b/src/lib/process/log_parser.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2014-2022 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 <cc/data.h>
+#include <process/log_parser.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <log/logger_specification.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+
+using namespace isc::data;
+using namespace isc::log;
+
+namespace isc {
+namespace process {
+
+LogConfigParser::LogConfigParser(const ConfigPtr& storage)
+ :config_(storage), verbose_(false) {
+ if (!storage) {
+ isc_throw(BadValue, "LogConfigParser needs a pointer to the "
+ "configuration, so parsed data can be stored there");
+ }
+}
+
+void LogConfigParser::parseConfiguration(const isc::data::ConstElementPtr& loggers,
+ bool verbose) {
+ verbose_ = verbose;
+
+ // Iterate over all entries in "Server/loggers" list
+ BOOST_FOREACH(ConstElementPtr logger, loggers->listValue()) {
+ parseConfigEntry(logger);
+ }
+}
+
+void LogConfigParser::parseConfigEntry(isc::data::ConstElementPtr entry) {
+ if (!entry) {
+ // This should not happen, but let's be on the safe side and check
+ return;
+ }
+
+ if (!config_) {
+ isc_throw(BadValue, "configuration storage not set, can't parse logger config.");
+ }
+
+ LoggingInfo info;
+ // Remove default destinations as we are going to replace them.
+ info.clearDestinations();
+
+ // Get user context
+ isc::data::ConstElementPtr user_context = entry->get("user-context");
+ if (user_context) {
+ info.setContext(user_context);
+ }
+
+ // Get a name
+ isc::data::ConstElementPtr name_ptr = entry->get("name");
+ if (!name_ptr) {
+ isc_throw(BadValue, "loggers entry does not have a mandatory 'name' "
+ "element (" << entry->getPosition() << ")");
+ }
+ info.name_ = name_ptr->stringValue();
+
+ // Get the severity.
+ // If not configured, set it to DEFAULT to inherit it from the root logger later.
+ isc::data::ConstElementPtr severity_ptr = entry->get("severity");
+ if (severity_ptr) {
+ info.severity_ = getSeverity(severity_ptr->stringValue());
+ } else {
+ info.severity_ = DEFAULT;
+ }
+
+ // Get debug logging level
+ info.debuglevel_ = 0;
+ isc::data::ConstElementPtr debuglevel_ptr = entry->get("debuglevel");
+
+ // It's ok to not have debuglevel, we'll just assume its least verbose
+ // (0) level.
+ if (debuglevel_ptr) {
+ try {
+ info.debuglevel_ = boost::lexical_cast<int>(debuglevel_ptr->str());
+ if ( (info.debuglevel_ < 0) || (info.debuglevel_ > 99) ) {
+ // Comment doesn't matter, it is caught several lines below
+ isc_throw(BadValue, "");
+ }
+ } catch (...) {
+ isc_throw(BadValue, "Unsupported debuglevel value '"
+ << debuglevel_ptr->stringValue()
+ << "', expected 0-99 ("
+ << debuglevel_ptr->getPosition() << ")");
+ }
+ }
+
+ // We want to follow the normal path, so it could catch parsing errors even
+ // when verbose mode is enabled. If it is, just override whatever was parsed
+ // in the config file.
+ if (verbose_) {
+ info.severity_ = isc::log::DEBUG;
+ info.debuglevel_ = 99;
+ }
+
+ isc::data::ConstElementPtr output_options = entry->get("output_options");
+
+ if (output_options) {
+ parseOutputOptions(info.destinations_, output_options);
+ }
+
+ config_->addLoggingInfo(info);
+}
+
+void LogConfigParser::parseOutputOptions(std::vector<LoggingDestination>& destination,
+ isc::data::ConstElementPtr output_options) {
+ if (!output_options) {
+ isc_throw(BadValue, "Missing 'output_options' structure in 'loggers'");
+ }
+
+ BOOST_FOREACH(ConstElementPtr output_option, output_options->listValue()) {
+
+ LoggingDestination dest;
+
+ isc::data::ConstElementPtr output = output_option->get("output");
+ if (!output) {
+ isc_throw(BadValue, "output_options entry does not have a mandatory 'output' "
+ "element (" << output_option->getPosition() << ")");
+ }
+ dest.output_ = output->stringValue();
+
+ isc::data::ConstElementPtr maxver_ptr = output_option->get("maxver");
+ if (maxver_ptr) {
+ dest.maxver_ = boost::lexical_cast<int>(maxver_ptr->str());
+ }
+
+ isc::data::ConstElementPtr maxsize_ptr = output_option->get("maxsize");
+ if (maxsize_ptr) {
+ dest.maxsize_ = boost::lexical_cast<uint64_t>(maxsize_ptr->str());
+ }
+
+ isc::data::ConstElementPtr flush_ptr = output_option->get("flush");
+ if (flush_ptr) {
+ dest.flush_ = flush_ptr->boolValue();
+ }
+
+ isc::data::ConstElementPtr pattern = output_option->get("pattern");
+ if (pattern) {
+ dest.pattern_ = pattern->stringValue();
+ }
+
+ destination.push_back(dest);
+ }
+}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/process/log_parser.h b/src/lib/process/log_parser.h
new file mode 100644
index 0000000..92209bd
--- /dev/null
+++ b/src/lib/process/log_parser.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2014-2019 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 DHCPSRV_LOGGING_H
+#define DHCPSRV_LOGGING_H
+
+#include <cc/data.h>
+#include <process/logging_info.h>
+#include <process/config_base.h>
+#include <vector>
+
+namespace isc {
+namespace process {
+
+/// @brief Configures log4cplus by translating Kea configuration structures
+///
+/// This parser iterates over provided data elements and translates them
+/// into values applicable to log4cplus.
+///
+/// The data structures converted to JSON format have the following syntax:
+/// {
+/// "name": "kea",
+/// "output_options": [
+/// {
+/// "output": "/home/thomson/kea-inst/kea-warn.log",
+/// "maxver": 8,
+/// "maxsize": 204800,
+/// "flush": true
+/// }
+/// ],
+/// "severity": "WARN"
+/// }
+///
+/// This is only an example and actual values may be different.
+///
+/// The data structures don't have to originate from JSON. JSON is just a
+/// convenient presentation syntax.
+///
+/// This class uses @c ConfigBase object to store logging configuration.
+class LogConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param storage parsed logging configuration will be stored here
+ LogConfigParser(const ConfigPtr& storage);
+
+ /// @brief Parses specified configuration
+ ///
+ /// Walks over specified logging configuration JSON structures and store
+ /// parsed information in config_->logging_info_.
+ ///
+ /// @param log_config JSON structures to be parsed (loggers list)
+ /// @param verbose specifies verbose mode (true forces DEBUG, debuglevel = 99)
+ void parseConfiguration(const isc::data::ConstElementPtr& log_config,
+ bool verbose = false);
+
+private:
+
+ /// @brief Parses one JSON structure in Server/loggers" array
+ ///
+ /// @param entry JSON structure to be parsed
+ /// @brief parses one structure in Server/loggers.
+ void parseConfigEntry(isc::data::ConstElementPtr entry);
+
+ /// @brief Parses output_options structure
+ ///
+ /// @ref @c LogConfigParser for an example in JSON format.
+ ///
+ /// @param destination parsed parameters will be stored here
+ /// @param output_options element to be parsed
+ void parseOutputOptions(std::vector<LoggingDestination>& destination,
+ isc::data::ConstElementPtr output_options);
+
+ /// @brief Configuration is stored here
+ ///
+ /// LogConfigParser class uses only config_->logging_info_ field.
+ ConfigPtr config_;
+
+ /// @brief Verbose mode
+ ///
+ /// When verbose mode is enabled, logging severity is overridden to DEBUG,
+ /// and debuglevel is always 99.
+ bool verbose_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOGGING_H
diff --git a/src/lib/process/logging_info.cc b/src/lib/process/logging_info.cc
new file mode 100644
index 0000000..a32119d
--- /dev/null
+++ b/src/lib/process/logging_info.cc
@@ -0,0 +1,212 @@
+// Copyright (C) 2014-2022 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 <process/logging_info.h>
+#include <process/daemon.h>
+#include <log/logger_name.h>
+
+using namespace isc::log;
+using namespace isc::data;
+
+namespace isc {
+namespace process {
+
+static const std::string STDOUT = "stdout";
+static const std::string STDERR = "stderr";
+static const std::string SYSLOG = "syslog";
+static const std::string SYSLOG_COLON = "syslog:";
+
+bool
+LoggingDestination::equals(const LoggingDestination& other) const {
+ return (output_ == other.output_ &&
+ maxver_ == other.maxver_ &&
+ maxsize_ == other.maxsize_ &&
+ flush_ == other.flush_ &&
+ pattern_ == other.pattern_);
+}
+
+ElementPtr
+LoggingDestination::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set output
+ result->set("output", Element::create(output_));
+
+ // Set flush
+ result->set("flush", Element::create(flush_));
+
+ // Set pattern
+ result->set("pattern", Element::create(pattern_));
+
+ if ((output_ != STDOUT) && (output_ != STDERR) && (output_ != SYSLOG) &&
+ (output_.find(SYSLOG_COLON) == std::string::npos)) {
+ // Set maxver
+ result->set("maxver", Element::create(maxver_));
+
+ // Set maxsize
+ result->set("maxsize", Element::create(static_cast<long long>(maxsize_)));
+ }
+
+ return (result);
+}
+
+LoggingInfo::LoggingInfo()
+ : name_("kea"), severity_(isc::log::INFO), debuglevel_(0) {
+ // If configuration Manager is in the verbose mode, we need to modify the
+ // default settings.
+ if (Daemon::getVerbose()) {
+ severity_ = isc::log::DEBUG;
+ debuglevel_ = 99;
+ }
+
+ // If the process has set the non-empty name for the default logger,
+ // let's use this name.
+ std::string default_logger = Daemon::getDefaultLoggerName();
+ if (!default_logger.empty()) {
+ name_ = default_logger;
+ }
+
+ // Add a default logging destination in case use hasn't provided a
+ // logger specification.
+ LoggingDestination dest;
+ dest.output_ = "stdout";
+ destinations_.push_back(dest);
+}
+
+bool
+LoggingInfo::equals(const LoggingInfo& other) const {
+ // If number of destinations aren't equal, the objects are not equal.
+ if (destinations_.size() != other.destinations_.size()) {
+ return (false);
+ }
+ // If there is the same number of logging destinations verify that the
+ // destinations are equal. The order doesn't matter to we don't expect
+ // that they are at the same index of the vectors.
+ for (auto const& dest : destinations_) {
+ bool match = false;
+ for (auto const &dest_other : other.destinations_) {
+ if (dest.equals(dest_other)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ return (false);
+ }
+ }
+
+ // Logging destinations are equal. Check the rest of the parameters for
+ // equality.
+ return (name_ == other.name_ &&
+ severity_ == other.severity_ &&
+ debuglevel_ == other.debuglevel_);
+}
+
+LoggerSpecification
+LoggingInfo::toSpec() const {
+ LoggerSpecification spec(name_, severity_, debuglevel_);
+
+ // Go over logger destinations and create output options accordingly.
+ for (auto const& dest : destinations_) {
+ OutputOption option;
+ // Set up output option according to destination specification
+ if (dest.output_ == STDOUT) {
+ option.destination = OutputOption::DEST_CONSOLE;
+ option.stream = OutputOption::STR_STDOUT;
+
+ } else if (dest.output_ == STDERR) {
+ option.destination = OutputOption::DEST_CONSOLE;
+ option.stream = OutputOption::STR_STDERR;
+
+ } else if (dest.output_ == SYSLOG) {
+ option.destination = OutputOption::DEST_SYSLOG;
+ // Use default specified in OutputOption constructor for the
+ // syslog destination
+
+ } else if (dest.output_.find(SYSLOG_COLON) == 0) {
+ option.destination = OutputOption::DEST_SYSLOG;
+ // Must take account of the string actually being "syslog:"
+ if (dest.output_ == SYSLOG_COLON) {
+ // The expected syntax is syslog:facility. User skipped
+ // the logging name, so we'll just use the default ("kea")
+ option.facility = isc::log::getDefaultRootLoggerName();
+
+ } else {
+ // Everything else in the string is the facility name
+ option.facility = dest.output_.substr(SYSLOG_COLON.size());
+ }
+
+ } else {
+ // Not a recognized destination, assume a file.
+ option.destination = OutputOption::DEST_FILE;
+ option.filename = dest.output_;
+ option.maxsize = dest.maxsize_;
+ option.maxver = dest.maxver_;
+ }
+
+ // Copy the immediate flush flag
+ option.flush = dest.flush_;
+
+ // Copy the pattern
+ option.pattern = dest.pattern_;
+
+ // ... and set the destination
+ spec.addOutputOption(option);
+ }
+
+ return (spec);
+}
+
+ElementPtr
+LoggingInfo::toElement() const {
+ ElementPtr result = Element::createMap();
+ // Set user context
+ contextToElement(result);
+ // Set name
+ result->set("name", Element::create(name_));
+ // Set output_options if not empty
+ if (!destinations_.empty()) {
+ ElementPtr options = Element::createList();
+ for (auto const& dest : destinations_) {
+ options->add(dest.toElement());
+ }
+ result->set("output_options", options);
+ }
+ // Set severity
+ std::string severity;
+ switch (severity_) {
+ case isc::log::DEBUG:
+ severity = "DEBUG";
+ break;
+ case isc::log::INFO:
+ severity = "INFO";
+ break;
+ case isc::log::WARN:
+ severity = "WARN";
+ break;
+ case isc::log::ERROR:
+ severity = "ERROR";
+ break;
+ case isc::log::FATAL:
+ severity = "FATAL";
+ break;
+ case isc::log::NONE:
+ severity = "NONE";
+ break;
+ default:
+ isc_throw(ToElementError, "illegal severity: " << severity_);
+ break;
+ }
+ result->set("severity", Element::create(severity));
+ // Set debug level
+ result->set("debuglevel", Element::create(debuglevel_));
+
+ return (result);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
diff --git a/src/lib/process/logging_info.h b/src/lib/process/logging_info.h
new file mode 100644
index 0000000..7660fe0
--- /dev/null
+++ b/src/lib/process/logging_info.h
@@ -0,0 +1,146 @@
+// Copyright (C) 2014-2019 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 DHCPSRV_LOGGING_INFO_H
+#define DHCPSRV_LOGGING_INFO_H
+
+#include <log/logger_level.h>
+#include <log/logger_specification.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <stdint.h>
+#include <vector>
+
+namespace isc {
+namespace process {
+
+/// @brief Defines single logging destination
+///
+/// This structure is used to keep log4cplus configuration parameters.
+struct LoggingDestination : public isc::data::CfgToElement {
+public:
+
+ /// @brief defines logging destination output
+ ///
+ /// Values accepted are: stdout, stderr, syslog, syslog:name.
+ /// Any other destination will be considered a file name.
+ std::string output_;
+
+ /// @brief Maximum number of log files in rotation
+ int maxver_;
+
+ /// @brief Maximum log file size
+ uint64_t maxsize_;
+
+ /// @brief Immediate flush
+ bool flush_;
+
+ /// @brief defines the log format pattern
+ /// It dictates what additional elements are output
+ std::string pattern_;
+
+ /// @brief Compares two objects for equality.
+ ///
+ /// @param other Object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool equals(const LoggingDestination& other) const;
+
+ /// @brief Default constructor.
+ LoggingDestination()
+ : output_("stdout"), maxver_(1), maxsize_(10240000), flush_(true), pattern_("") {
+ }
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+};
+
+/// @brief structure that describes one logging entry
+///
+/// This is a structure that conveys one logger entry configuration.
+/// The structure in JSON form has the following syntax:
+/// {
+/// "name": "*",
+/// "output_options": [
+/// {
+/// "output": "/path/to/the/logfile.log",
+/// "maxver": 8,
+/// "maxsize": 204800,
+/// "flush": true
+/// "pattern": "%-5p [%c] %m\n"
+/// }
+/// ],
+/// "severity": "WARN",
+/// "debuglevel": 99
+/// },
+class LoggingInfo : public isc::data::UserContext, public isc::data::CfgToElement {
+public:
+
+ /// @brief logging name
+ std::string name_;
+
+ /// @brief describes logging severity
+ isc::log::Severity severity_;
+
+ /// @brief debuglevel (used when severity_ == DEBUG)
+ ///
+ /// We use range 0(least verbose)..99(most verbose)
+ int debuglevel_;
+
+ /// @brief specific logging destinations
+ std::vector<LoggingDestination> destinations_;
+
+ /// @brief Default constructor.
+ LoggingInfo();
+
+ /// @brief Removes logging destinations.
+ void clearDestinations() {
+ destinations_.clear();
+ }
+
+ /// @brief Compares two objects for equality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool equals(const LoggingInfo& other) const;
+
+ /// @brief Compares two objects for equality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are equal, false otherwise.
+ bool operator==(const LoggingInfo& other) const {
+ return (equals(other));
+ }
+
+ /// @brief Compares two objects for inequality.
+ ///
+ /// @param other An object to be compared with this object.
+ ///
+ /// @return true if objects are not equal, false otherwise.
+ bool operator!=(const LoggingInfo& other) const {
+ return (!equals(other));
+ }
+
+ /// @brief Converts logger configuration to a spec.
+ isc::log::LoggerSpecification toSpec() const;
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to unparsed configuration
+ virtual isc::data::ElementPtr toElement() const;
+};
+
+/// @brief storage for logging information in log4cplus format
+typedef std::vector<isc::process::LoggingInfo> LoggingInfoStorage;
+
+}
+}
+
+#endif // DHCPSRV_LOGGING_INFO_H
diff --git a/src/lib/process/process_messages.cc b/src/lib/process/process_messages.cc
new file mode 100644
index 0000000..a4ee304
--- /dev/null
+++ b/src/lib/process/process_messages.cc
@@ -0,0 +1,83 @@
+// File created from ../../../src/lib/process/process_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace process {
+
+extern const isc::log::MessageID DCTL_ALREADY_RUNNING = "DCTL_ALREADY_RUNNING";
+extern const isc::log::MessageID DCTL_CCSESSION_ENDING = "DCTL_CCSESSION_ENDING";
+extern const isc::log::MessageID DCTL_CFG_FILE_RELOAD_ERROR = "DCTL_CFG_FILE_RELOAD_ERROR";
+extern const isc::log::MessageID DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD = "DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD";
+extern const isc::log::MessageID DCTL_COMMAND_RECEIVED = "DCTL_COMMAND_RECEIVED";
+extern const isc::log::MessageID DCTL_CONFIG_CHECK_COMPLETE = "DCTL_CONFIG_CHECK_COMPLETE";
+extern const isc::log::MessageID DCTL_CONFIG_COMPLETE = "DCTL_CONFIG_COMPLETE";
+extern const isc::log::MessageID DCTL_CONFIG_DEPRECATED = "DCTL_CONFIG_DEPRECATED";
+extern const isc::log::MessageID DCTL_CONFIG_FETCH = "DCTL_CONFIG_FETCH";
+extern const isc::log::MessageID DCTL_CONFIG_FILE_LOAD_FAIL = "DCTL_CONFIG_FILE_LOAD_FAIL";
+extern const isc::log::MessageID DCTL_CONFIG_LOAD_FAIL = "DCTL_CONFIG_LOAD_FAIL";
+extern const isc::log::MessageID DCTL_CONFIG_START = "DCTL_CONFIG_START";
+extern const isc::log::MessageID DCTL_CONFIG_STUB = "DCTL_CONFIG_STUB";
+extern const isc::log::MessageID DCTL_CONFIG_UPDATE = "DCTL_CONFIG_UPDATE";
+extern const isc::log::MessageID DCTL_DEVELOPMENT_VERSION = "DCTL_DEVELOPMENT_VERSION";
+extern const isc::log::MessageID DCTL_INIT_PROCESS = "DCTL_INIT_PROCESS";
+extern const isc::log::MessageID DCTL_INIT_PROCESS_FAIL = "DCTL_INIT_PROCESS_FAIL";
+extern const isc::log::MessageID DCTL_NOT_RUNNING = "DCTL_NOT_RUNNING";
+extern const isc::log::MessageID DCTL_OPEN_CONFIG_DB = "DCTL_OPEN_CONFIG_DB";
+extern const isc::log::MessageID DCTL_PARSER_FAIL = "DCTL_PARSER_FAIL";
+extern const isc::log::MessageID DCTL_PID_FILE_ERROR = "DCTL_PID_FILE_ERROR";
+extern const isc::log::MessageID DCTL_PROCESS_FAILED = "DCTL_PROCESS_FAILED";
+extern const isc::log::MessageID DCTL_RUN_PROCESS = "DCTL_RUN_PROCESS";
+extern const isc::log::MessageID DCTL_SESSION_FAIL = "DCTL_SESSION_FAIL";
+extern const isc::log::MessageID DCTL_SHUTDOWN = "DCTL_SHUTDOWN";
+extern const isc::log::MessageID DCTL_SHUTDOWN_SIGNAL_RECVD = "DCTL_SHUTDOWN_SIGNAL_RECVD";
+extern const isc::log::MessageID DCTL_STANDALONE = "DCTL_STANDALONE";
+extern const isc::log::MessageID DCTL_STARTING = "DCTL_STARTING";
+extern const isc::log::MessageID DCTL_UNLOAD_LIBRARIES_ERROR = "DCTL_UNLOAD_LIBRARIES_ERROR";
+extern const isc::log::MessageID DCTL_UNSUPPORTED_SIGNAL = "DCTL_UNSUPPORTED_SIGNAL";
+
+} // namespace process
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "DCTL_ALREADY_RUNNING", "%1 already running? %2",
+ "DCTL_CCSESSION_ENDING", "%1 ending control channel session",
+ "DCTL_CFG_FILE_RELOAD_ERROR", "configuration reload failed: %1, reverting to current configuration.",
+ "DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD", "OS signal %1 received, reloading configuration from file: %2",
+ "DCTL_COMMAND_RECEIVED", "%1 received command: %2, arguments: %3",
+ "DCTL_CONFIG_CHECK_COMPLETE", "server has completed configuration check: %1, result: %2",
+ "DCTL_CONFIG_COMPLETE", "server has completed configuration: %1",
+ "DCTL_CONFIG_DEPRECATED", "server configuration includes a deprecated object: %1",
+ "DCTL_CONFIG_FETCH", "Fetching configuration data from config backends.",
+ "DCTL_CONFIG_FILE_LOAD_FAIL", "%1 reason: %2",
+ "DCTL_CONFIG_LOAD_FAIL", "%1 configuration failed to load: %2",
+ "DCTL_CONFIG_START", "parsing new configuration: %1",
+ "DCTL_CONFIG_STUB", "%1 configuration stub handler called",
+ "DCTL_CONFIG_UPDATE", "%1 updated configuration received: %2",
+ "DCTL_DEVELOPMENT_VERSION", "This software is a development branch of Kea. It is not recommended for production use.",
+ "DCTL_INIT_PROCESS", "%1 initializing the application",
+ "DCTL_INIT_PROCESS_FAIL", "%1 application initialization failed: %2",
+ "DCTL_NOT_RUNNING", "%1 application instance is not running",
+ "DCTL_OPEN_CONFIG_DB", "Opening configuration database: %1",
+ "DCTL_PARSER_FAIL", ": %1",
+ "DCTL_PID_FILE_ERROR", "%1 could not create a PID file: %2",
+ "DCTL_PROCESS_FAILED", "%1 application execution failed: %2",
+ "DCTL_RUN_PROCESS", "%1 starting application event loop",
+ "DCTL_SESSION_FAIL", "%1 controller failed to establish Kea session: %1",
+ "DCTL_SHUTDOWN", "%1 has shut down, pid: %2, version: %3",
+ "DCTL_SHUTDOWN_SIGNAL_RECVD", "OS signal %1 received, starting shutdown",
+ "DCTL_STANDALONE", "%1 skipping message queue, running standalone",
+ "DCTL_STARTING", "%1 starting, pid: %2, version: %3 (%4)",
+ "DCTL_UNLOAD_LIBRARIES_ERROR", "error unloading hooks libraries during shutdown: %1",
+ "DCTL_UNSUPPORTED_SIGNAL", "ignoring reception of unsupported signal: %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/process/process_messages.h b/src/lib/process/process_messages.h
new file mode 100644
index 0000000..0ae950a
--- /dev/null
+++ b/src/lib/process/process_messages.h
@@ -0,0 +1,45 @@
+// File created from ../../../src/lib/process/process_messages.mes
+
+#ifndef PROCESS_MESSAGES_H
+#define PROCESS_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace process {
+
+extern const isc::log::MessageID DCTL_ALREADY_RUNNING;
+extern const isc::log::MessageID DCTL_CCSESSION_ENDING;
+extern const isc::log::MessageID DCTL_CFG_FILE_RELOAD_ERROR;
+extern const isc::log::MessageID DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD;
+extern const isc::log::MessageID DCTL_COMMAND_RECEIVED;
+extern const isc::log::MessageID DCTL_CONFIG_CHECK_COMPLETE;
+extern const isc::log::MessageID DCTL_CONFIG_COMPLETE;
+extern const isc::log::MessageID DCTL_CONFIG_DEPRECATED;
+extern const isc::log::MessageID DCTL_CONFIG_FETCH;
+extern const isc::log::MessageID DCTL_CONFIG_FILE_LOAD_FAIL;
+extern const isc::log::MessageID DCTL_CONFIG_LOAD_FAIL;
+extern const isc::log::MessageID DCTL_CONFIG_START;
+extern const isc::log::MessageID DCTL_CONFIG_STUB;
+extern const isc::log::MessageID DCTL_CONFIG_UPDATE;
+extern const isc::log::MessageID DCTL_DEVELOPMENT_VERSION;
+extern const isc::log::MessageID DCTL_INIT_PROCESS;
+extern const isc::log::MessageID DCTL_INIT_PROCESS_FAIL;
+extern const isc::log::MessageID DCTL_NOT_RUNNING;
+extern const isc::log::MessageID DCTL_OPEN_CONFIG_DB;
+extern const isc::log::MessageID DCTL_PARSER_FAIL;
+extern const isc::log::MessageID DCTL_PID_FILE_ERROR;
+extern const isc::log::MessageID DCTL_PROCESS_FAILED;
+extern const isc::log::MessageID DCTL_RUN_PROCESS;
+extern const isc::log::MessageID DCTL_SESSION_FAIL;
+extern const isc::log::MessageID DCTL_SHUTDOWN;
+extern const isc::log::MessageID DCTL_SHUTDOWN_SIGNAL_RECVD;
+extern const isc::log::MessageID DCTL_STANDALONE;
+extern const isc::log::MessageID DCTL_STARTING;
+extern const isc::log::MessageID DCTL_UNLOAD_LIBRARIES_ERROR;
+extern const isc::log::MessageID DCTL_UNSUPPORTED_SIGNAL;
+
+} // namespace process
+} // namespace isc
+
+#endif // PROCESS_MESSAGES_H
diff --git a/src/lib/process/process_messages.mes b/src/lib/process/process_messages.mes
new file mode 100644
index 0000000..1d601ab
--- /dev/null
+++ b/src/lib/process/process_messages.mes
@@ -0,0 +1,156 @@
+# Copyright (C) 2016-2021 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/.
+
+$NAMESPACE isc::process
+
+% DCTL_ALREADY_RUNNING %1 already running? %2
+This is an error message that occurs when a module encounters a pre-existing
+PID file which contains the PID of a running process. This most likely
+indicates an attempt to start a second instance of a module using the
+same configuration file. It is possible, though unlikely, that the PID file
+is a remnant left behind by a server crash or power failure and the PID
+it contains refers to a process other than Kea process. In such an event,
+it would be necessary to manually remove the PID file. The first argument is
+the process name, the second contains the PID and PID file.
+
+% DCTL_CCSESSION_ENDING %1 ending control channel session
+This debug message is issued just before the controller attempts
+to disconnect from its session with the Kea control channel.
+
+% DCTL_CFG_FILE_RELOAD_ERROR configuration reload failed: %1, reverting to current configuration.
+This is an error message indicating that the application attempted to reload
+its configuration from file and encountered an error. This is likely due to
+invalid content in the configuration file. The application should continue
+to operate under its current configuration.
+
+% DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD OS signal %1 received, reloading configuration from file: %2
+This is an informational message indicating the application has received a signal
+instructing it to reload its configuration from file.
+
+% DCTL_COMMAND_RECEIVED %1 received command: %2, arguments: %3
+A debug message listing the command (and possible arguments) received
+from the Kea control system by the controller.
+
+% DCTL_CONFIG_CHECK_COMPLETE server has completed configuration check: %1, result: %2
+This is an informational message announcing the successful processing of a
+new configuration check is complete. The result of that check is printed.
+This informational message is printed when configuration check is requested.
+
+% DCTL_CONFIG_COMPLETE server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
+% DCTL_CONFIG_DEPRECATED server configuration includes a deprecated object: %1
+This error message is issued when the configuration includes a deprecated
+object (i.e. a top level element) which will be ignored.
+
+% DCTL_CONFIG_FETCH Fetching configuration data from config backends.
+This is an informational message emitted when the Kea server is about to begin
+retrieving configuration data from one or more configuration backends.
+
+% DCTL_CONFIG_FILE_LOAD_FAIL %1 reason: %2
+This fatal error message indicates that the application attempted to load its
+initial configuration from file and has failed. The service will exit.
+
+% DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
+This critical error message indicates that the initial application
+configuration has failed. The service will start, but will not
+process requests until the configuration has been corrected.
+
+% DCTL_CONFIG_START parsing new configuration: %1
+A debug message indicating that the application process has received an
+updated configuration and has passed it to its configuration manager
+for parsing.
+
+% DCTL_CONFIG_STUB %1 configuration stub handler called
+This debug message is issued when the dummy handler for configuration
+events is called. This only happens during initial startup.
+
+% DCTL_CONFIG_UPDATE %1 updated configuration received: %2
+A debug message indicating that the controller has received an
+updated configuration from the Kea configuration system.
+
+% DCTL_DEVELOPMENT_VERSION This software is a development branch of Kea. It is not recommended for production use.
+This warning message is displayed when the version is a development
+(vs stable) one: the second number of the version is odd.
+
+% DCTL_INIT_PROCESS %1 initializing the application
+This debug message is issued just before the controller attempts
+to create and initialize its application instance.
+
+% DCTL_INIT_PROCESS_FAIL %1 application initialization failed: %2
+This error message is issued if the controller could not initialize the
+application and will exit.
+
+% DCTL_NOT_RUNNING %1 application instance is not running
+A warning message is issued when an attempt is made to shut down the
+application when it is not running.
+
+% DCTL_OPEN_CONFIG_DB Opening configuration database: %1
+This message is printed when the Kea server is attempting to open a
+configuration database. The database access string with password redacted
+is logged.
+
+% DCTL_PARSER_FAIL : %1
+On receipt of a new configuration, the server failed to create a parser to
+decode the contents of the named configuration element, or the creation
+succeeded but the parsing actions and committal of changes failed.
+The reason for the failure is given in the message.
+
+% DCTL_PID_FILE_ERROR %1 could not create a PID file: %2
+This is an error message that occurs when the server is unable to create
+its PID file. The log message should contain details sufficient to
+determine the underlying cause. The most likely culprits are that
+some portion of the pathname does not exist or a permissions issue. The
+default path is determined by --localstatedir or --runstatedir configure
+parameters but may be overridden by setting environment variable,
+KEA_PIDFILE_DIR. The first argument is the process name.
+
+% DCTL_PROCESS_FAILED %1 application execution failed: %2
+The controller has encountered a fatal error while running the
+application and is terminating. The reason for the failure is
+included in the message.
+
+% DCTL_RUN_PROCESS %1 starting application event loop
+This debug message is issued just before the controller invokes
+the application run method.
+
+% DCTL_SESSION_FAIL %1 controller failed to establish Kea session: %1
+The controller has failed to establish communication with the rest of
+Kea and will exit.
+
+% DCTL_SHUTDOWN %1 has shut down, pid: %2, version: %3
+This is an informational message indicating that the service has shut
+down. The argument specifies a name of the service.
+
+% DCTL_SHUTDOWN_SIGNAL_RECVD OS signal %1 received, starting shutdown
+This is a debug message indicating the application has received a signal
+instructing it to shutdown.
+
+% DCTL_STANDALONE %1 skipping message queue, running standalone
+This is a debug message indicating that the controller is running in the
+application in standalone mode. This means it will not connected to the Kea
+message queue. Standalone mode is only useful during program development,
+and should not be used in a production environment.
+
+% DCTL_STARTING %1 starting, pid: %2, version: %3 (%4)
+This is an informational message issued when controller for the
+service first starts. Version is also reported.
+
+% DCTL_UNLOAD_LIBRARIES_ERROR error unloading hooks libraries during shutdown: %1
+This error message indicates that during shutdown, unloading hooks
+libraries failed to close them. If the list of libraries is empty it is
+a programmatic error in the server code. If it is not empty it could be
+a programmatic error in one of the hooks libraries which could lead to
+a crash during finalization.
+
+% DCTL_UNSUPPORTED_SIGNAL ignoring reception of unsupported signal: %1
+This is a debug message indicating that the application received an
+unsupported signal. This is a programming error indicating that the
+application has registered to receive the signal but no associated
+processing logic has been added.
diff --git a/src/lib/process/redact_config.cc b/src/lib/process/redact_config.cc
new file mode 100644
index 0000000..16bba0f
--- /dev/null
+++ b/src/lib/process/redact_config.cc
@@ -0,0 +1,97 @@
+// Copyright (C) 2021 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 <process/redact_config.h>
+
+#include <boost/algorithm/string.hpp>
+
+using namespace isc;
+using namespace isc::data;
+using namespace std;
+
+namespace {
+
+template <typename ElementPtrType>
+ElementPtrType
+redact(ElementPtrType const& element, list<string> json_path) {
+ if (!element) {
+ isc_throw(BadValue, "redact() got a null pointer");
+ }
+
+ string const next_key(json_path.empty() ? string() : json_path.front());
+ ElementPtr result;
+ if (element->getType() == Element::list) {
+ // If we are looking for a list...
+ if (next_key == "*" || next_key == "[]") {
+ // But if we are looking specifically for a list...
+ if (next_key == "[]") {
+ // Then advance in the path.
+ json_path.pop_front();
+ }
+ // Then redact all children.
+ result = Element::createList();
+ for (ElementPtr const& child : element->listValue()) {
+ result->add(redact(child, json_path));
+ }
+ return result;
+ }
+ } else if (element->getType() == Element::map) {
+ // If we are looking for anything or if we have reached the end of a
+ /// path...
+ if (next_key == "*" || json_path.empty()) {
+ // Then iterate through all the children.
+ result = Element::createMap();
+ for (auto kv : element->mapValue()) {
+ std::string const& key(kv.first);
+ ConstElementPtr const& value(kv.second);
+
+ if (boost::algorithm::ends_with(key, "password") ||
+ boost::algorithm::ends_with(key, "secret")) {
+ // Sensitive data
+ result->set(key, Element::create(string("*****")));
+ } else if (key == "user-context") {
+ // Skip user contexts.
+ result->set(key, value);
+ } else {
+ if (json_path.empty()) {
+ // End of path means no sensitive data expected in this
+ // subtree, so we stop here.
+ result->set(key, value);
+ } else {
+ // We are looking for anything '*' so redact further.
+ result->set(key, redact(value, json_path));
+ }
+ }
+ }
+ return result;
+ } else {
+ ConstElementPtr child(element->get(next_key));
+ if (child) {
+ result = isc::data::copy(element, 1);
+ json_path.pop_front();
+ result->set(next_key, redact(child, json_path));
+ return result;
+ }
+ }
+ }
+
+ return element;
+}
+
+} // namespace
+
+namespace isc {
+namespace process {
+
+ConstElementPtr
+redactConfig(ConstElementPtr const& element, list<string> const& json_path) {
+ return redact(element, json_path);
+}
+
+} // namespace process
+} // namespace isc
diff --git a/src/lib/process/redact_config.h b/src/lib/process/redact_config.h
new file mode 100644
index 0000000..a0d1d0a
--- /dev/null
+++ b/src/lib/process/redact_config.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2021 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 REDACT_CONFIG_H
+#define REDACT_CONFIG_H
+
+#include <cc/data.h>
+#include <list>
+
+namespace isc {
+namespace process {
+
+/// @brief Redact a configuration.
+///
+/// This method walks on the configuration tree:
+/// - it copies only subtrees where a change was done.
+/// - it replaces passwords and secrets by asterisks.
+/// - it skips user context.
+/// - if a not empty list of keywords is given it follows only them.
+///
+/// @param element initially the Element tree structure that describe the
+/// configuration and smaller subtrees in recursive calls.
+/// @param json_path JSON path to redact
+///
+/// @return a copy of the config where passwords and secrets were replaced by
+/// asterisks so it can be safely logged to an unprivileged place.
+isc::data::ConstElementPtr
+redactConfig(isc::data::ConstElementPtr const& element,
+ std::list<std::string> const& json_path = {"*"});
+
+} // namespace process
+} // namespace isc
+
+#endif // REDACT_CONFIG_H
diff --git a/src/lib/process/tests/Makefile.am b/src/lib/process/tests/Makefile.am
new file mode 100644
index 0000000..f556c76
--- /dev/null
+++ b/src/lib/process/tests/Makefile.am
@@ -0,0 +1,56 @@
+SUBDIRS = .
+dhcp_data_dir = @runstatedir@/@PACKAGE@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/process/tests\"
+AM_CPPFLAGS += -DDATA_DIR="\"$(dhcp_data_dir)\""
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+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 += libprocess_unittests
+
+libprocess_unittests_SOURCES = d_cfg_mgr_unittests.cc
+libprocess_unittests_SOURCES += cb_ctl_base_unittests.cc
+libprocess_unittests_SOURCES += config_base_unittests.cc
+libprocess_unittests_SOURCES += config_ctl_info_unittests.cc
+libprocess_unittests_SOURCES += config_ctl_parser_unittests.cc
+libprocess_unittests_SOURCES += d_controller_unittests.cc
+libprocess_unittests_SOURCES += daemon_unittest.cc
+libprocess_unittests_SOURCES += log_parser_unittests.cc
+libprocess_unittests_SOURCES += logging_info_unittests.cc
+libprocess_unittests_SOURCES += run_unittests.cc
+
+libprocess_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+libprocess_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+libprocess_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libprocess_unittests_LDADD = $(top_builddir)/src/lib/process/testutils/libprocesstest.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libprocess_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libprocess_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+libprocess_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/process/tests/Makefile.in b/src/lib/process/tests/Makefile.in
new file mode 100644
index 0000000..29101f1
--- /dev/null
+++ b/src/lib/process/tests/Makefile.in
@@ -0,0 +1,1184 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = libprocess_unittests
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/process/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libprocess_unittests$(EXEEXT)
+am__EXEEXT_2 = $(am__EXEEXT_1)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libprocess_unittests_SOURCES_DIST = d_cfg_mgr_unittests.cc \
+ cb_ctl_base_unittests.cc config_base_unittests.cc \
+ config_ctl_info_unittests.cc config_ctl_parser_unittests.cc \
+ d_controller_unittests.cc daemon_unittest.cc \
+ log_parser_unittests.cc logging_info_unittests.cc \
+ run_unittests.cc
+@HAVE_GTEST_TRUE@am_libprocess_unittests_OBJECTS = libprocess_unittests-d_cfg_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-cb_ctl_base_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-config_base_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-config_ctl_info_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-config_ctl_parser_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-d_controller_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-daemon_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-log_parser_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-logging_info_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libprocess_unittests-run_unittests.$(OBJEXT)
+libprocess_unittests_OBJECTS = $(am_libprocess_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libprocess_unittests_DEPENDENCIES = $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@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 =
+libprocess_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libprocess_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)/libprocess_unittests-cb_ctl_base_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-config_base_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-d_controller_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-daemon_unittest.Po \
+ ./$(DEPDIR)/libprocess_unittests-log_parser_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-logging_info_unittests.Po \
+ ./$(DEPDIR)/libprocess_unittests-run_unittests.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 =
+SOURCES = $(libprocess_unittests_SOURCES)
+DIST_SOURCES = $(am__libprocess_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+dhcp_data_dir = @runstatedir@/@PACKAGE@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) \
+ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/process/tests\" \
+ -DDATA_DIR="\"$(dhcp_data_dir)\"" \
+ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libprocess_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ d_cfg_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ cb_ctl_base_unittests.cc \
+@HAVE_GTEST_TRUE@ config_base_unittests.cc \
+@HAVE_GTEST_TRUE@ config_ctl_info_unittests.cc \
+@HAVE_GTEST_TRUE@ config_ctl_parser_unittests.cc \
+@HAVE_GTEST_TRUE@ d_controller_unittests.cc daemon_unittest.cc \
+@HAVE_GTEST_TRUE@ log_parser_unittests.cc \
+@HAVE_GTEST_TRUE@ logging_info_unittests.cc run_unittests.cc
+@HAVE_GTEST_TRUE@libprocess_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libprocess_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libprocess_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libprocess_unittests_LDADD = $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/http/libkea-http.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) \
+@HAVE_GTEST_TRUE@ $(BOOST_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/process/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/process/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
+
+libprocess_unittests$(EXEEXT): $(libprocess_unittests_OBJECTS) $(libprocess_unittests_DEPENDENCIES) $(EXTRA_libprocess_unittests_DEPENDENCIES)
+ @rm -f libprocess_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libprocess_unittests_LINK) $(libprocess_unittests_OBJECTS) $(libprocess_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-cb_ctl_base_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-config_base_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-d_controller_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-daemon_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-log_parser_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-logging_info_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocess_unittests-run_unittests.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 $@ $<
+
+libprocess_unittests-d_cfg_mgr_unittests.o: d_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-d_cfg_mgr_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Tpo -c -o libprocess_unittests-d_cfg_mgr_unittests.o `test -f 'd_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`d_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Tpo $(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_cfg_mgr_unittests.cc' object='libprocess_unittests-d_cfg_mgr_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-d_cfg_mgr_unittests.o `test -f 'd_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`d_cfg_mgr_unittests.cc
+
+libprocess_unittests-d_cfg_mgr_unittests.obj: d_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-d_cfg_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Tpo -c -o libprocess_unittests-d_cfg_mgr_unittests.obj `if test -f 'd_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'd_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d_cfg_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Tpo $(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_cfg_mgr_unittests.cc' object='libprocess_unittests-d_cfg_mgr_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-d_cfg_mgr_unittests.obj `if test -f 'd_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'd_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d_cfg_mgr_unittests.cc'; fi`
+
+libprocess_unittests-cb_ctl_base_unittests.o: cb_ctl_base_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-cb_ctl_base_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-cb_ctl_base_unittests.Tpo -c -o libprocess_unittests-cb_ctl_base_unittests.o `test -f 'cb_ctl_base_unittests.cc' || echo '$(srcdir)/'`cb_ctl_base_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-cb_ctl_base_unittests.Tpo $(DEPDIR)/libprocess_unittests-cb_ctl_base_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cb_ctl_base_unittests.cc' object='libprocess_unittests-cb_ctl_base_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-cb_ctl_base_unittests.o `test -f 'cb_ctl_base_unittests.cc' || echo '$(srcdir)/'`cb_ctl_base_unittests.cc
+
+libprocess_unittests-cb_ctl_base_unittests.obj: cb_ctl_base_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-cb_ctl_base_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-cb_ctl_base_unittests.Tpo -c -o libprocess_unittests-cb_ctl_base_unittests.obj `if test -f 'cb_ctl_base_unittests.cc'; then $(CYGPATH_W) 'cb_ctl_base_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cb_ctl_base_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-cb_ctl_base_unittests.Tpo $(DEPDIR)/libprocess_unittests-cb_ctl_base_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='cb_ctl_base_unittests.cc' object='libprocess_unittests-cb_ctl_base_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-cb_ctl_base_unittests.obj `if test -f 'cb_ctl_base_unittests.cc'; then $(CYGPATH_W) 'cb_ctl_base_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/cb_ctl_base_unittests.cc'; fi`
+
+libprocess_unittests-config_base_unittests.o: config_base_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-config_base_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-config_base_unittests.Tpo -c -o libprocess_unittests-config_base_unittests.o `test -f 'config_base_unittests.cc' || echo '$(srcdir)/'`config_base_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-config_base_unittests.Tpo $(DEPDIR)/libprocess_unittests-config_base_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_base_unittests.cc' object='libprocess_unittests-config_base_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-config_base_unittests.o `test -f 'config_base_unittests.cc' || echo '$(srcdir)/'`config_base_unittests.cc
+
+libprocess_unittests-config_base_unittests.obj: config_base_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-config_base_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-config_base_unittests.Tpo -c -o libprocess_unittests-config_base_unittests.obj `if test -f 'config_base_unittests.cc'; then $(CYGPATH_W) 'config_base_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_base_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-config_base_unittests.Tpo $(DEPDIR)/libprocess_unittests-config_base_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_base_unittests.cc' object='libprocess_unittests-config_base_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-config_base_unittests.obj `if test -f 'config_base_unittests.cc'; then $(CYGPATH_W) 'config_base_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_base_unittests.cc'; fi`
+
+libprocess_unittests-config_ctl_info_unittests.o: config_ctl_info_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-config_ctl_info_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Tpo -c -o libprocess_unittests-config_ctl_info_unittests.o `test -f 'config_ctl_info_unittests.cc' || echo '$(srcdir)/'`config_ctl_info_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Tpo $(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_ctl_info_unittests.cc' object='libprocess_unittests-config_ctl_info_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-config_ctl_info_unittests.o `test -f 'config_ctl_info_unittests.cc' || echo '$(srcdir)/'`config_ctl_info_unittests.cc
+
+libprocess_unittests-config_ctl_info_unittests.obj: config_ctl_info_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-config_ctl_info_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Tpo -c -o libprocess_unittests-config_ctl_info_unittests.obj `if test -f 'config_ctl_info_unittests.cc'; then $(CYGPATH_W) 'config_ctl_info_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_ctl_info_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Tpo $(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_ctl_info_unittests.cc' object='libprocess_unittests-config_ctl_info_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-config_ctl_info_unittests.obj `if test -f 'config_ctl_info_unittests.cc'; then $(CYGPATH_W) 'config_ctl_info_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_ctl_info_unittests.cc'; fi`
+
+libprocess_unittests-config_ctl_parser_unittests.o: config_ctl_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-config_ctl_parser_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Tpo -c -o libprocess_unittests-config_ctl_parser_unittests.o `test -f 'config_ctl_parser_unittests.cc' || echo '$(srcdir)/'`config_ctl_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Tpo $(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_ctl_parser_unittests.cc' object='libprocess_unittests-config_ctl_parser_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-config_ctl_parser_unittests.o `test -f 'config_ctl_parser_unittests.cc' || echo '$(srcdir)/'`config_ctl_parser_unittests.cc
+
+libprocess_unittests-config_ctl_parser_unittests.obj: config_ctl_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-config_ctl_parser_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Tpo -c -o libprocess_unittests-config_ctl_parser_unittests.obj `if test -f 'config_ctl_parser_unittests.cc'; then $(CYGPATH_W) 'config_ctl_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_ctl_parser_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Tpo $(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_ctl_parser_unittests.cc' object='libprocess_unittests-config_ctl_parser_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-config_ctl_parser_unittests.obj `if test -f 'config_ctl_parser_unittests.cc'; then $(CYGPATH_W) 'config_ctl_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_ctl_parser_unittests.cc'; fi`
+
+libprocess_unittests-d_controller_unittests.o: d_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-d_controller_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-d_controller_unittests.Tpo -c -o libprocess_unittests-d_controller_unittests.o `test -f 'd_controller_unittests.cc' || echo '$(srcdir)/'`d_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-d_controller_unittests.Tpo $(DEPDIR)/libprocess_unittests-d_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_controller_unittests.cc' object='libprocess_unittests-d_controller_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-d_controller_unittests.o `test -f 'd_controller_unittests.cc' || echo '$(srcdir)/'`d_controller_unittests.cc
+
+libprocess_unittests-d_controller_unittests.obj: d_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-d_controller_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-d_controller_unittests.Tpo -c -o libprocess_unittests-d_controller_unittests.obj `if test -f 'd_controller_unittests.cc'; then $(CYGPATH_W) 'd_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d_controller_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-d_controller_unittests.Tpo $(DEPDIR)/libprocess_unittests-d_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_controller_unittests.cc' object='libprocess_unittests-d_controller_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-d_controller_unittests.obj `if test -f 'd_controller_unittests.cc'; then $(CYGPATH_W) 'd_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d_controller_unittests.cc'; fi`
+
+libprocess_unittests-daemon_unittest.o: daemon_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-daemon_unittest.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-daemon_unittest.Tpo -c -o libprocess_unittests-daemon_unittest.o `test -f 'daemon_unittest.cc' || echo '$(srcdir)/'`daemon_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-daemon_unittest.Tpo $(DEPDIR)/libprocess_unittests-daemon_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='daemon_unittest.cc' object='libprocess_unittests-daemon_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-daemon_unittest.o `test -f 'daemon_unittest.cc' || echo '$(srcdir)/'`daemon_unittest.cc
+
+libprocess_unittests-daemon_unittest.obj: daemon_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-daemon_unittest.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-daemon_unittest.Tpo -c -o libprocess_unittests-daemon_unittest.obj `if test -f 'daemon_unittest.cc'; then $(CYGPATH_W) 'daemon_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/daemon_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-daemon_unittest.Tpo $(DEPDIR)/libprocess_unittests-daemon_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='daemon_unittest.cc' object='libprocess_unittests-daemon_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-daemon_unittest.obj `if test -f 'daemon_unittest.cc'; then $(CYGPATH_W) 'daemon_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/daemon_unittest.cc'; fi`
+
+libprocess_unittests-log_parser_unittests.o: log_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-log_parser_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-log_parser_unittests.Tpo -c -o libprocess_unittests-log_parser_unittests.o `test -f 'log_parser_unittests.cc' || echo '$(srcdir)/'`log_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-log_parser_unittests.Tpo $(DEPDIR)/libprocess_unittests-log_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_parser_unittests.cc' object='libprocess_unittests-log_parser_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-log_parser_unittests.o `test -f 'log_parser_unittests.cc' || echo '$(srcdir)/'`log_parser_unittests.cc
+
+libprocess_unittests-log_parser_unittests.obj: log_parser_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-log_parser_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-log_parser_unittests.Tpo -c -o libprocess_unittests-log_parser_unittests.obj `if test -f 'log_parser_unittests.cc'; then $(CYGPATH_W) 'log_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/log_parser_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-log_parser_unittests.Tpo $(DEPDIR)/libprocess_unittests-log_parser_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_parser_unittests.cc' object='libprocess_unittests-log_parser_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-log_parser_unittests.obj `if test -f 'log_parser_unittests.cc'; then $(CYGPATH_W) 'log_parser_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/log_parser_unittests.cc'; fi`
+
+libprocess_unittests-logging_info_unittests.o: logging_info_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-logging_info_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-logging_info_unittests.Tpo -c -o libprocess_unittests-logging_info_unittests.o `test -f 'logging_info_unittests.cc' || echo '$(srcdir)/'`logging_info_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-logging_info_unittests.Tpo $(DEPDIR)/libprocess_unittests-logging_info_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logging_info_unittests.cc' object='libprocess_unittests-logging_info_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-logging_info_unittests.o `test -f 'logging_info_unittests.cc' || echo '$(srcdir)/'`logging_info_unittests.cc
+
+libprocess_unittests-logging_info_unittests.obj: logging_info_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-logging_info_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-logging_info_unittests.Tpo -c -o libprocess_unittests-logging_info_unittests.obj `if test -f 'logging_info_unittests.cc'; then $(CYGPATH_W) 'logging_info_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/logging_info_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-logging_info_unittests.Tpo $(DEPDIR)/libprocess_unittests-logging_info_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='logging_info_unittests.cc' object='libprocess_unittests-logging_info_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-logging_info_unittests.obj `if test -f 'logging_info_unittests.cc'; then $(CYGPATH_W) 'logging_info_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/logging_info_unittests.cc'; fi`
+
+libprocess_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libprocess_unittests-run_unittests.Tpo -c -o libprocess_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocess_unittests-run_unittests.Tpo $(DEPDIR)/libprocess_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libprocess_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libprocess_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -MT libprocess_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libprocess_unittests-run_unittests.Tpo -c -o libprocess_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)/libprocess_unittests-run_unittests.Tpo $(DEPDIR)/libprocess_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libprocess_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) $(libprocess_unittests_CPPFLAGS) $(CPPFLAGS) $(libprocess_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libprocess_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)/libprocess_unittests-cb_ctl_base_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-config_base_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-d_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-daemon_unittest.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-log_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-logging_info_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-run_unittests.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)/libprocess_unittests-cb_ctl_base_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-config_base_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-config_ctl_info_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-config_ctl_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-d_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-d_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-daemon_unittest.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-log_parser_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-logging_info_unittests.Po
+ -rm -f ./$(DEPDIR)/libprocess_unittests-run_unittests.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/process/tests/cb_ctl_base_unittests.cc b/src/lib/process/tests/cb_ctl_base_unittests.cc
new file mode 100644
index 0000000..58d0aa0
--- /dev/null
+++ b/src/lib/process/tests/cb_ctl_base_unittests.cc
@@ -0,0 +1,718 @@
+// Copyright (C) 2019-2020 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 <config_backend/base_config_backend_mgr.h>
+#include <config_backend/base_config_backend_pool.h>
+#include <process/cb_ctl_base.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <map>
+#include <string>
+
+using namespace isc;
+using namespace isc::cb;
+using namespace isc::db;
+using namespace isc::process;
+
+namespace {
+
+/// @brief Implementation of the config backend for testing the
+/// @c CBControlBase template class.
+///
+/// This simple class allows for adding, retrieving and clearing audit
+/// entries. The @c CBControlBase unit tests use it to control the
+/// behavior of the @c CBControlBase class under test.
+class CBControlBackend : BaseConfigBackend {
+public:
+
+ /// @brief Constructor.
+ CBControlBackend(const db::DatabaseConnection::ParameterMap&) {
+ }
+
+ /// @brief Retrieves the audit entries later than specified time.
+ ///
+ /// @param modification_time The lower bound time for which audit
+ /// entries should be returned.
+ /// @param modification_id The lower bound id for which audit
+ /// entries should be returned.
+ ///
+ /// @return Collection of audit entries later than specified time.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const db::ServerSelector&,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t modification_id) const {
+ db::AuditEntryCollection filtered_entries;
+
+ // Use the index which orders the audit entries by timestamps.
+ const auto& index = audit_entries_.get<AuditEntryModificationTimeIdTag>();
+
+ // Locate the first audit entry after the last one having the
+ // specified modification time and id.
+ auto modification = boost::make_tuple(modification_time, modification_id);
+ auto first_entry = index.upper_bound(modification);
+
+ // If there are any entries found return them.
+ if (first_entry != index.end()) {
+ filtered_entries.insert(first_entry, index.end());
+ }
+
+ return (filtered_entries);
+ }
+
+ /// @brief Add audit entry to the backend.
+ ///
+ /// @param object_type Object type to be stored in the audit entry.
+ /// @param object_id Object id to be stored in the audit entry.
+ /// @param modification_time Audit entry modification time to be set.
+ /// @param modification_id Audit entry modification id to be set.
+ void addAuditEntry(const ServerSelector&,
+ const std::string& object_type,
+ const uint64_t object_id,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t modification_id) {
+ // Create new audit entry from the specified parameters.
+ AuditEntryPtr audit_entry(new AuditEntry(object_type,
+ object_id,
+ AuditEntry::ModificationType::CREATE,
+ modification_time,
+ modification_id,
+ "added audit entry"));
+
+ // The audit entries are held in the static variable so as they
+ // don't disappear when we diconnect from the backend. The
+ // audit entries are explicitly cleared during the unit tests
+ // setup.
+ audit_entries_.insert(audit_entry);
+ }
+
+ /// @brief Returns backend type in the textual format.
+ ///
+ /// @return Name of the storage for configurations, e.g. "mysql",
+ /// "pgsql" and so forth.
+ virtual std::string getType() const {
+ return ("memfile");
+ }
+
+ /// @brief Returns backend host
+ ///
+ /// This is used by the @c BaseConfigBackendPool to select backend
+ /// when @c BackendSelector is specified.
+ ///
+ /// @return host on which the database is located.
+ virtual std::string getHost() const {
+ return ("");
+ }
+
+ /// @brief Returns backend port number.
+ ///
+ /// This is used by the @c BaseConfigBackendPool to select backend
+ /// when @c BackendSelector is specified.
+ ///
+ /// @return Port number on which database service is available.
+ virtual uint16_t getPort() const {
+ return (0);
+ }
+
+ /// @brief Removes audit entries.
+ static void clearAuditEntries() {
+ audit_entries_.clear();
+ }
+
+private:
+
+ /// @brief Static collection of audit entries.
+ ///
+ /// Thanks to storing them in the static member they are preserved
+ /// when the unit tests "disconnect" from the backend.
+ static AuditEntryCollection audit_entries_;
+};
+
+/// @brief Pointer to the @c CBControlBackend object.
+typedef boost::shared_ptr<CBControlBackend> CBControlBackendPtr;
+
+AuditEntryCollection CBControlBackend::audit_entries_;
+
+/// @brief Implementation of the backends pool used in the
+/// @c CBControlBase template class unit tests.
+class CBControlBackendPool : public BaseConfigBackendPool<CBControlBackend> {
+public:
+
+ /// @brief Add audit entry to the backend.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param object_type Object type to be stored in the audit entry.
+ /// @param object_id Object id to be stored in the audit entry.
+ /// @param modification_time Audit entry modification time to be set.
+ /// @param modification_id Audit entry modification id to be set.
+ void addAuditEntry(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const std::string& object_type,
+ const uint64_t object_id,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t modification_id) {
+ createUpdateDeleteProperty<void, const std::string&, uint64_t,
+ const boost::posix_time::ptime&, uint64_t>
+ (&CBControlBackend::addAuditEntry, backend_selector, server_selector,
+ object_type, object_id, modification_time, modification_id);
+ }
+
+ /// @brief Retrieves the audit entries later than specified time.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param modification_time The lower bound time for which audit
+ /// entries should be returned.
+ /// @param modification_id The lower bound id for which audit
+ /// entries should be returned.
+ ///
+ /// @return Collection of audit entries later than specified time.
+ virtual db::AuditEntryCollection
+ getRecentAuditEntries(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime& modification_time,
+ const uint64_t modification_id) const {
+ AuditEntryCollection audit_entries;
+ getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
+ (&CBControlBackend::getRecentAuditEntries, backend_selector,
+ server_selector, audit_entries, modification_time,
+ modification_id);
+ return (audit_entries);
+ }
+};
+
+/// @brief Implementation of the config backends manager used
+/// in the @c CBControlBase template class unit tests.
+class CBControlBackendMgr : public BaseConfigBackendMgr<CBControlBackendPool> {
+public:
+
+ /// @brief Constructor.
+ CBControlBackendMgr()
+ : instance_id_(0) {
+ }
+
+ /// @brief Returns instance of the @c CBControlBackendMgr.
+ ///
+ /// @return Reference to the instance of the @c CBControlBackendMgr.
+ static CBControlBackendMgr& instance() {
+ static CBControlBackendMgr mgr;
+ return (mgr);
+ }
+
+ /// @brief Returns instance id.
+ ///
+ /// This value is used in tests which verify that the @c CBControlBase::getMgr
+ /// returns the right instance of the CB manager.
+ ///
+ /// @return Instance id.
+ uint32_t getInstanceId() const {
+ return (instance_id_);
+ }
+
+ /// @brief Sets new instance id.
+ ///
+ /// @param instance_id New instance id.
+ void setInstanceId(const uint32_t instance_id) {
+ instance_id_ = instance_id;
+ }
+
+ /// @brief Instance id.
+ uint32_t instance_id_;
+};
+
+/// @brief Implementation of the @c CBControlBase class used in
+/// the unit tests.
+///
+/// It makes some of the protected methods public. It also provides
+/// means to test the behavior of the @c CBControlBase template.
+class CBControl : public CBControlBase<CBControlBackendMgr> {
+public:
+
+ using CBControlBase<CBControlBackendMgr>::fetchConfigElement;
+ using CBControlBase<CBControlBackendMgr>::getMgr;
+ using CBControlBase<CBControlBackendMgr>::getInitialAuditRevisionTime;
+
+ /// @brief Constructor.
+ CBControl()
+ : CBControlBase<CBControlBackendMgr>(),
+ merges_num_(0),
+ backend_selector_(BackendSelector::Type::MYSQL),
+ server_selector_(ServerSelector::UNASSIGNED()),
+ audit_entries_num_(-1),
+ enable_throw_(false) {
+ }
+
+ /// @brief Implementation of the method called to fetch and apply
+ /// configuration from the database into the local configuration.
+ ///
+ /// This stub implementation doesn't attempt to merge any configurations
+ /// but merely records the values of the parameters called.
+ ///
+ /// @param backend_selector Backend selector.
+ /// @param server_selector Server selector.
+ /// @param audit_entries Collection of audit entries.
+ virtual void databaseConfigApply(const BackendSelector& backend_selector,
+ const ServerSelector& server_selector,
+ const boost::posix_time::ptime&,
+ const AuditEntryCollection& audit_entries) {
+ ++merges_num_;
+ backend_selector_ = backend_selector;
+ server_selector_ = server_selector;
+ audit_entries_num_ = static_cast<int>(audit_entries.size());
+
+ if (enable_throw_) {
+ isc_throw(Unexpected, "throwing from databaseConfigApply");
+ }
+ }
+
+ /// @brief Returns the number of times the @c databaseConfigApply was called.
+ size_t getMergesNum() const {
+ return (merges_num_);
+ }
+
+ /// @brief Returns backend selector used as an argument in a call to
+ /// @c databaseConfigApply.
+ const BackendSelector& getBackendSelector() const {
+ return (backend_selector_);
+ }
+
+ /// @brief Returns server selector used as an argument in a call to
+ /// @c databaseConfigApply.
+ const ServerSelector& getServerSelector() const {
+ return (server_selector_);
+ }
+
+ /// @brief Returns the number of audit entries in the collection passed
+ /// to @c databaseConfigApply
+ int getAuditEntriesNum() const {
+ return (audit_entries_num_);
+ }
+
+ /// @brief Returns the recorded time of last audit entry.
+ boost::posix_time::ptime getLastAuditRevisionTime() const {
+ return (last_audit_revision_time_);
+ }
+
+ /// @brief Returns the recorded id of last audit entry.
+ uint64_t getLastAuditRevisionId() const {
+ return (last_audit_revision_id_);
+ }
+
+ /// @brief Overwrites the last audit entry time.
+ ///
+ /// @param last_audit_revision_time New time to be set.
+ void setLastAuditRevisionTime(const boost::posix_time::ptime& last_audit_revision_time) {
+ last_audit_revision_time_ = last_audit_revision_time;
+ }
+
+ /// @brief Overwrites the last audit revision id.
+ ///
+ /// @param last_audit_revision_id New id to be set.
+ void setLastAuditRevisionId(const uint64_t& last_audit_revision_id) {
+ last_audit_revision_id_ = last_audit_revision_id;
+ }
+
+ /// @brief Enables the @c databaseConfigApply function to throw.
+ ///
+ /// This is useful to test scenarios when configuration merge fails.
+ void enableThrow() {
+ enable_throw_ = true;
+ }
+
+private:
+
+ /// @brief Recorded number of calls to @c databaseConfigApply.
+ size_t merges_num_;
+
+ /// @brief Recorded backend selector value.
+ BackendSelector backend_selector_;
+
+ /// @brief Recorded server selector value.
+ ServerSelector server_selector_;
+
+ /// @brief Recorded number of audit entries.
+ int audit_entries_num_;
+
+ /// @brief Boolean value indicating if the @c databaseConfigApply should throw.
+ bool enable_throw_;
+};
+
+/// @brief Out of the blue instance id used in tests.
+constexpr uint32_t TEST_INSTANCE_ID = 123;
+
+/// @brief Test fixture class for @c CBControlBase template class.
+class CBControlBaseTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ CBControlBaseTest()
+ : cb_ctl_(), mgr_(CBControlBackendMgr::instance()),
+ timestamps_() {
+ mgr_.registerBackendFactory("db1",
+ [](const DatabaseConnection::ParameterMap& params)
+ -> CBControlBackendPtr {
+ return (CBControlBackendPtr(new CBControlBackend(params)));
+ });
+ mgr_.setInstanceId(TEST_INSTANCE_ID);
+ initTimestamps();
+ CBControlBackend::clearAuditEntries();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes audit entries created in the test.
+ ~CBControlBaseTest() {
+ CBControlBackend::clearAuditEntries();
+ }
+
+ /// @brief Initialize posix time values used in tests.
+ void initTimestamps() {
+ // Current time minus 1 hour to make sure it is in the past.
+ timestamps_["today"] = boost::posix_time::second_clock::local_time()
+ - boost::posix_time::hours(1);
+ // Yesterday.
+ timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
+ // Two days ago.
+ timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
+ // Tomorrow.
+ timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
+ }
+
+ /// @brief Creates an instance of the configuration object.
+ ///
+ /// @param db1_access Database access string to be used to connect to
+ /// the test configuration backend. It doesn't connect if the string
+ /// is empty.
+ ConfigPtr makeConfigBase(const std::string& db1_access = "") const {
+ ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
+
+ if (!db1_access.empty()) {
+ config_ctl_info->addConfigDatabase(db1_access);
+ }
+
+ ConfigPtr config_base(new ConfigBase());
+
+ config_base->setConfigControlInfo(config_ctl_info);
+ return (config_base);
+ }
+
+ /// @brief Instance of the @c CBControl used in tests.
+ CBControl cb_ctl_;
+
+ /// @brief Instance of the Config Backend Manager.
+ CBControlBackendMgr& mgr_;
+
+ /// @brief Holds timestamp values used in tests.
+ std::map<std::string, boost::posix_time::ptime> timestamps_;
+};
+
+// This test verifies that the same instance of the Config
+// Backend Manager is returned all the time.
+TEST_F(CBControlBaseTest, getMgr) {
+ auto mgr = cb_ctl_.getMgr();
+ EXPECT_EQ(TEST_INSTANCE_ID, mgr.getInstanceId());
+}
+
+// This test verifies that the initial audit revision time is set to
+// local time of 2000-01-01.
+TEST_F(CBControlBaseTest, getInitialAuditRevisionTime) {
+ auto initial_time = cb_ctl_.getInitialAuditRevisionTime();
+ ASSERT_FALSE(initial_time.is_not_a_date_time());
+ auto tm = boost::posix_time::to_tm(initial_time);
+ EXPECT_EQ(100, tm.tm_year);
+ EXPECT_EQ(0, tm.tm_mon);
+ EXPECT_EQ(0, tm.tm_yday);
+ EXPECT_EQ(0, tm.tm_hour);
+ EXPECT_EQ(0, tm.tm_min);
+ EXPECT_EQ(0, tm.tm_sec);
+}
+
+// This test verifies that last audit entry time is reset upon the
+// call to CBControlBase::reset().
+TEST_F(CBControlBaseTest, reset) {
+ cb_ctl_.setLastAuditRevisionTime(timestamps_["tomorrow"]);
+ cb_ctl_.reset();
+ EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
+}
+
+// This test verifies that it is correctly determined what entries the
+// server should fetch for the particular configuration element.
+TEST_F(CBControlBaseTest, fetchConfigElement) {
+ db::AuditEntryCollection audit_entries;
+ db::AuditEntryCollection updated;
+ // When audit entries collection is empty any subset is empty too.
+ updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
+ EXPECT_TRUE(updated.empty());
+
+ // Now test the case that there is a DELETE audit entry. In this case
+ // our function should indicate that the configuration should not be
+ // fetched for the given object type. Note that when the configuration
+ // element is deleted, it no longer exists in database so there is
+ // no reason to fetch the data from the database.
+ AuditEntryPtr audit_entry(new AuditEntry("dhcp4_subnet", 1234 ,
+ AuditEntry::ModificationType::DELETE,
+ 2345, "added audit entry"));
+ audit_entries.insert(audit_entry);
+ updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
+ EXPECT_TRUE(updated.empty());
+ EXPECT_TRUE(hasObjectId(audit_entries, 1234));
+ EXPECT_FALSE(hasObjectId(audit_entries, 5678));
+ EXPECT_FALSE(hasObjectId(updated, 1234));
+
+ // Add another audit entry which indicates creation of the configuration element.
+ // This time we should get it.
+ audit_entry.reset(new AuditEntry("my_object_type", 5678,
+ AuditEntry::ModificationType::CREATE,
+ 6789, "added audit entry"));
+ audit_entries.insert(audit_entry);
+ updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
+ ASSERT_EQ(1, updated.size());
+ AuditEntryPtr updated_entry = (*updated.begin());
+ ASSERT_TRUE(updated_entry);
+ EXPECT_EQ("my_object_type", updated_entry->getObjectType());
+ EXPECT_EQ(5678, updated_entry->getObjectId());
+ EXPECT_EQ(AuditEntry::ModificationType::CREATE, updated_entry->getModificationType());
+ EXPECT_TRUE(hasObjectId(audit_entries, 5678));
+ EXPECT_TRUE(hasObjectId(updated, 5678));
+ EXPECT_FALSE(hasObjectId(updated, 1234));
+
+ // Also we should get 'true' for the UPDATE case.
+ audit_entry.reset(new AuditEntry("my_object_type",
+ 5678, AuditEntry::ModificationType::UPDATE,
+ 6790, "added audit entry"));
+ audit_entries.insert(audit_entry);
+ updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
+ EXPECT_EQ(2, updated.size());
+ bool saw_create = false;
+ bool saw_update = false;
+ for (auto entry : updated) {
+ EXPECT_EQ("my_object_type", entry->getObjectType());
+ EXPECT_EQ(5678, entry->getObjectId());
+ if (AuditEntry::ModificationType::CREATE == entry->getModificationType()) {
+ EXPECT_FALSE(saw_create);
+ saw_create = true;
+ } else if (AuditEntry::ModificationType::UPDATE == entry->getModificationType()) {
+ EXPECT_FALSE(saw_update);
+ saw_update = true;
+ }
+ }
+ EXPECT_TRUE(saw_create);
+ EXPECT_TRUE(saw_update);
+ EXPECT_TRUE(hasObjectId(updated, 5678));
+ EXPECT_FALSE(hasObjectId(updated, 1234));
+}
+
+// This test verifies that true is return when the server successfully
+// connects to the backend and false if there are no backends to connect
+// to.
+TEST_F(CBControlBaseTest, connect) {
+ EXPECT_TRUE(cb_ctl_.databaseConfigConnect(makeConfigBase("type=db1")));
+ EXPECT_FALSE(cb_ctl_.databaseConfigConnect(makeConfigBase()));
+}
+
+// This test verifies the scenario when the server fetches the entire
+// configuration from the database upon startup.
+TEST_F(CBControlBaseTest, fetchAll) {
+ auto config_base = makeConfigBase("type=db1");
+
+ // Add two audit entries to the database. The server should load
+ // the entire configuration from the database regardless of the
+ // existing audit entries. However, the last audit entry timestamp
+ // should be set to the most recent audit entry in the
+ // @c CBControlBase.
+ ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
+
+ cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ "sql_table_2",
+ 1234,
+ timestamps_["yesterday"],
+ 2345);
+
+ cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ "sql_table_1",
+ 3456,
+ timestamps_["today"],
+ 4567);
+
+ // Disconnect from the database in order to check that the
+ // databaseConfigFetch reconnects.
+ ASSERT_NO_THROW(cb_ctl_.databaseConfigDisconnect());
+
+ // Verify that various indicators are set to their initial values.
+ ASSERT_EQ(0, cb_ctl_.getMergesNum());
+ ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
+ ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
+ ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
+
+ // Connect to the database and fetch the configuration.
+ ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
+
+ // There should be one invocation of the databaseConfigApply.
+ ASSERT_EQ(1, cb_ctl_.getMergesNum());
+ // Since this is full reconfiguration the audit entry collection
+ // passed to the databaseConfigApply should be empty.
+ EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
+ EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
+ // Make sure that the internal timestamp is set to the most recent
+ // audit entry, so as the server will only later fetch config
+ // updates after this timestamp.
+ EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(4567, cb_ctl_.getLastAuditRevisionId());
+}
+
+// This test verifies that the configuration can be fetched for a
+// specified server tag.
+TEST_F(CBControlBaseTest, fetchFromServer) {
+ auto config_base = makeConfigBase("type=db1");
+ // Set a server tag.
+ config_base->setServerTag("a-tag");
+
+ // Verify that various indicators are set to their initial values.
+ ASSERT_EQ(0, cb_ctl_.getMergesNum());
+ ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
+ ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
+ ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
+
+ ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
+
+ ASSERT_EQ(1, cb_ctl_.getMergesNum());
+ EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
+ // An explicit server selector should have been used this time.
+ ASSERT_EQ(ServerSelector::Type::SUBSET, cb_ctl_.getServerSelector().getType());
+ EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
+
+ // Make sure that the server selector used in databaseConfigFetch is
+ // correct.
+ auto tags = cb_ctl_.getServerSelector().getTags();
+ ASSERT_EQ(1, tags.size());
+ EXPECT_EQ("a-tag", tags.begin()->get());
+}
+
+// This test verifies that incremental configuration changes can be
+// fetched.
+TEST_F(CBControlBaseTest, fetchUpdates) {
+ auto config_base = makeConfigBase("type=db1");
+
+ // Connect to the database and store an audit entry. Do not close
+ // the database connection to simulate the case when the server
+ // uses existing connection to fetch configuration updates.
+ ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
+ cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ "sql_table_1",
+ 3456,
+ timestamps_["today"],
+ 4567);
+
+ // Verify that various indicators are set to their initial values.
+ ASSERT_EQ(0, cb_ctl_.getMergesNum());
+ ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
+ ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
+ ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
+
+ ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base,
+ CBControl::FetchMode::FETCH_UPDATE));
+
+ // There should be one invocation to databaseConfigApply recorded.
+ ASSERT_EQ(1, cb_ctl_.getMergesNum());
+ // The number of audit entries passed to this function should be 1.
+ EXPECT_EQ(1, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
+ EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
+ // The last audit entry time should be set to the latest audit entry.
+ EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(4567, cb_ctl_.getLastAuditRevisionId());
+}
+
+// Check that the databaseConfigApply function is not called when there
+// are no more unprocessed audit entries.
+TEST_F(CBControlBaseTest, fetchNoUpdates) {
+ auto config_base = makeConfigBase("type=db1");
+
+ // Set last audit entry time to the timestamp of the audit
+ // entry we are going to add. That means that there will be
+ // no new audit entries to fetch.
+ cb_ctl_.setLastAuditRevisionTime(timestamps_["yesterday"]);
+ cb_ctl_.setLastAuditRevisionId(4567);
+
+ ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
+
+ cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ "sql_table_1",
+ 3456,
+ timestamps_["yesterday"],
+ 4567);
+
+ ASSERT_EQ(0, cb_ctl_.getMergesNum());
+
+ ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base,
+ CBControl::FetchMode::FETCH_UPDATE));
+
+ // The databaseConfigApply should not be called because there are
+ // no new audit entires to process.
+ ASSERT_EQ(0, cb_ctl_.getMergesNum());
+}
+
+// This test verifies that database config fetch failures are handled
+// gracefully.
+TEST_F(CBControlBaseTest, fetchFailure) {
+ auto config_base = makeConfigBase("type=db1");
+
+ // Connect to the database and store an audit entry. Do not close
+ // the database connection to simulate the case when the server
+ // uses existing connection to fetch configuration updates.
+ ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
+ cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ "sql_table_1",
+ 3456,
+ timestamps_["today"],
+ 4567);
+
+ // Configure the CBControl to always throw simulating the failure
+ // during configuration merge.
+ cb_ctl_.enableThrow();
+
+ // Verify that various indicators are set to their initial values.
+ ASSERT_EQ(0, cb_ctl_.getMergesNum());
+ ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
+ ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
+ ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
+
+ ASSERT_THROW(cb_ctl_.databaseConfigFetch(config_base, CBControl::FetchMode::FETCH_UPDATE),
+ isc::Unexpected);
+
+ // There should be one invocation to databaseConfigApply recorded.
+ ASSERT_EQ(1, cb_ctl_.getMergesNum());
+ // The number of audit entries passed to this function should be 1.
+ EXPECT_EQ(1, cb_ctl_.getAuditEntriesNum());
+ EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
+ EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
+ // The last audit entry time should not be modified because there was a merge
+ // error.
+ EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
+ EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
+}
+
+}
diff --git a/src/lib/process/tests/config_base_unittests.cc b/src/lib/process/tests/config_base_unittests.cc
new file mode 100644
index 0000000..cec4582
--- /dev/null
+++ b/src/lib/process/tests/config_base_unittests.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2018-2019 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 <process/config_base.h>
+#include <util/optional.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::process;
+using namespace isc::util;
+
+/// @brief Derived ConfigBase class
+/// We use this derivation to test the
+/// copy and equality functions.
+class ConfigBaseImpl : public ConfigBase {
+public:
+ void
+ copy(ConfigBaseImpl& other) const {
+ ConfigBase::copy(other);
+ }
+};
+
+// Verifies construction, copy, and equality of
+// ConfigBase with respect to ConfigControInfo.
+TEST(ConfigBase, configControlInfoTests) {
+
+ // Create a control info instance
+ ConfigControlInfoPtr ctl_info1(new ConfigControlInfo());
+ ctl_info1->addConfigDatabase("type=mysql host=example.com");
+ ctl_info1->addConfigDatabase("type=mysql host=example2.com");
+
+ // Create a ConfigBase
+ ConfigBaseImpl base1;
+ base1.setConfigControlInfo(ctl_info1);
+
+ // Clone the ConfigBase
+ ConfigBaseImpl base2;
+ base1.copy(base2);
+
+ // They should be equal.
+ EXPECT_TRUE(base1.equals(base2));
+
+ // Reset control info for one of them.
+ base1.setConfigControlInfo(ConfigControlInfoPtr());
+
+ // They should not be equal.
+ EXPECT_FALSE(base1.equals(base2));
+
+ // Reset control info for the other one.
+ base2.setConfigControlInfo(ConfigControlInfoPtr());
+
+ // They should be equal again.
+ EXPECT_TRUE(base1.equals(base2));
+}
+
+// Verifies that logging information can be merged to another.
+TEST(ConfigBase, mergeLoggingInfo) {
+ // Create first logging info.
+ LoggingInfo log_info1;
+ log_info1.name_ = "foo";
+
+ // Create second logging info.
+ LoggingInfo log_info2;
+ log_info2.name_ = "bar";
+
+ // Create first config base instance.
+ ConfigBaseImpl base1;
+ base1.addLoggingInfo(log_info1);
+
+ // Copy the first instance to keep it as reference.
+ ConfigBaseImpl base1_copy;
+ base1_copy.copy(base1);
+
+ // Create second config base instance.
+ ConfigBaseImpl base2;
+ ASSERT_NO_THROW(base1.merge(base2));
+ EXPECT_TRUE(base1.equals(base1_copy));
+
+ // Set some data for the second config.
+ base2.addLoggingInfo(log_info2);
+
+ // This time the merge should replace the original config.
+ ASSERT_NO_THROW(base1.merge(base2));
+ EXPECT_TRUE(base1.equals(base2));
+}
+
+// Verifies that config control can be merged to another.
+TEST(ConfigBase, mergeConfigControl) {
+ // Create first config control info.
+ ConfigControlInfoPtr ctl_info1(new ConfigControlInfo());
+ ctl_info1->addConfigDatabase("type=mysql host=example.com");
+ ctl_info1->addConfigDatabase("type=mysql host=example2.com");
+
+ // Create second config control info.
+ ConfigControlInfoPtr ctl_info2(new ConfigControlInfo());
+ ctl_info2->addConfigDatabase("type=pgsql host=example.com");
+ ctl_info2->addConfigDatabase("type=pgsql host=example2.com");
+
+ // Create first config base instance.
+ ConfigBaseImpl base1;
+ base1.setConfigControlInfo(ctl_info1);
+
+ // Copy the first instance to keep it as reference.
+ ConfigBaseImpl base1_copy;
+ base1_copy.copy(base1);
+
+ // Create second config base instance.
+ ConfigBaseImpl base2;
+
+ // Merged base is empty, so the original should be preserved.
+ ASSERT_NO_THROW(base1.merge(base2));
+ EXPECT_TRUE(base1.equals(base1_copy));
+
+ // Set some data for the second config.
+ base2.setConfigControlInfo(ctl_info2);
+
+ // This time the merge should replace the original config.
+ ASSERT_NO_THROW(base1.merge(base2));
+ EXPECT_TRUE(base1.equals(base2));
+}
+
+// Verifies that server-tag may be configured.
+TEST(ConfigBase, serverTag) {
+ ConfigBaseImpl conf;
+
+ // Check that the default is an unspecified and empty string.
+ EXPECT_TRUE(conf.getServerTag().unspecified());
+ EXPECT_TRUE(conf.getServerTag().empty());
+
+ // Check that it can be modified.
+ conf.setServerTag("boo");
+ EXPECT_FALSE(conf.getServerTag().unspecified());
+ EXPECT_EQ("boo", conf.getServerTag().get());
+}
+
+// Verifies that server tag can be merged to another config.
+TEST(ConfigBase, mergeServerTag) {
+ ConfigBaseImpl base1;
+ ConfigBaseImpl base2;
+
+ // Initially the server tags in both config should be
+ // unspecified.
+ EXPECT_TRUE(base1.getServerTag().unspecified());
+ EXPECT_TRUE(base2.getServerTag().unspecified());
+
+ // Merging the config with unspecified server tag should
+ // not modify the target config.
+ ASSERT_NO_THROW(base1.merge(base2));
+ EXPECT_TRUE(base1.getServerTag().unspecified());
+ EXPECT_TRUE(base2.getServerTag().unspecified());
+
+ // Set server tag for base2 and merge it.
+ base2.setServerTag(std::string("base2"));
+ ASSERT_NO_THROW(base1.merge(base2));
+
+ // The server tag should be copied into the base1. Both
+ // should now be unspecified.
+ EXPECT_FALSE(base1.getServerTag().unspecified());
+ EXPECT_FALSE(base2.getServerTag().unspecified());
+
+ // They should also hold the same value.
+ EXPECT_EQ("base2", base1.getServerTag().get());
+ EXPECT_EQ("base2", base2.getServerTag().get());
+
+ // Reset the server tag to unspecified.
+ base2.setServerTag(Optional<std::string>());
+ EXPECT_FALSE(base1.getServerTag().unspecified());
+ EXPECT_TRUE(base2.getServerTag().unspecified());
+
+ // Merging the config with unspecified server tag should
+ // result in no change in the target config.
+ ASSERT_NO_THROW(base1.merge(base2));
+ EXPECT_FALSE(base1.getServerTag().unspecified());
+ EXPECT_TRUE(base2.getServerTag().unspecified());
+
+ // The server tag should remain the same.
+ EXPECT_EQ("base2", base1.getServerTag().get());
+
+ // Set the explicit server tag in the source config.
+ base2.setServerTag("new-base2");
+
+ // Merge again.
+ ASSERT_NO_THROW(base1.merge(base2));
+
+ // The new value should be stored in the target config, so
+ // both should be specified and have the same value.
+ EXPECT_FALSE(base1.getServerTag().unspecified());
+ EXPECT_FALSE(base2.getServerTag().unspecified());
+ EXPECT_EQ("new-base2", base1.getServerTag().get());
+ EXPECT_EQ("new-base2", base2.getServerTag().get());
+}
+
+// Verifies that server tag can be copied to another config.
+TEST(ConfigBase, copyServerTag) {
+ ConfigBaseImpl base1;
+ ConfigBaseImpl base2;
+
+ // Set server tag for the base2.
+ base2.setServerTag(std::string("base2"));
+
+ // The base1 has server tag unspecified. Copying it to the
+ // base2 should result in unspecified server tag in base2.
+ ASSERT_NO_THROW(base1.copy(base2));
+ EXPECT_TRUE(base2.getServerTag().unspecified());
+
+ // Set server tag for base1 and copy it to base2.
+ base1.setServerTag(std::string("base1"));
+ ASSERT_NO_THROW(base1.copy(base2));
+
+ // The base2 should now hold the value from base1.
+ EXPECT_FALSE(base2.getServerTag().unspecified());
+ EXPECT_EQ("base1", base2.getServerTag().get());
+
+ // Set base1 value to a different value.
+ base1.setServerTag(std::string("new-base1"));
+
+ // Copy again.
+ ASSERT_NO_THROW(base1.copy(base2));
+
+ // It should override the value in the base2.
+ EXPECT_FALSE(base2.getServerTag().unspecified());
+ EXPECT_EQ("new-base1", base2.getServerTag().get());
+}
diff --git a/src/lib/process/tests/config_ctl_info_unittests.cc b/src/lib/process/tests/config_ctl_info_unittests.cc
new file mode 100644
index 0000000..7c78014
--- /dev/null
+++ b/src/lib/process/tests/config_ctl_info_unittests.cc
@@ -0,0 +1,180 @@
+// Copyright (C) 2018-2022 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 <process/config_ctl_info.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <iostream>
+
+using namespace isc::process;
+using namespace isc::data;
+using namespace isc::util;
+
+// Verifies initializing via an access string and unparsing into elements
+// We just test basic unparsing, as more rigorous testing is done in
+// libkea-db testing which ConfigDBInfo uses.
+TEST(ConfigDbInfo, basicOperation) {
+ ConfigDbInfo db;
+ std::string access = "type=mysql user=tom password=terrific";
+ std::string redacted_access = "password=***** type=mysql user=tom";
+ std::string access_json = "{\n"
+ " \"type\":\"mysql\", \n"
+ " \"user\":\"tom\", \n"
+ " \"password\":\"terrific\" \n"
+ "} \n";
+
+ // Convert the above configuration into Elements for comparison.
+ ElementPtr exp_elems;
+ ASSERT_NO_THROW(exp_elems = Element::fromJSON(access_json))
+ << "test is broken";
+
+ // Initialize the db from an the access string
+ db.setAccessString(access);
+ EXPECT_EQ(access, db.getAccessString());
+
+ EXPECT_EQ(redacted_access, db.redactedAccessString());
+
+ // Convert the db into Elements and make sure they are as expected.
+ ElementPtr db_elems;
+ ASSERT_NO_THROW(db_elems = db.toElement());
+ EXPECT_TRUE(db_elems->equals(*exp_elems));
+}
+
+// Verify that db parameter values may be retrieved.
+TEST(ConfigDbInfo, getParameterValue) {
+ ConfigDbInfo db1;
+ std::string access1 = "type=mysql name=keatest port=33 readonly=false";
+ db1.setAccessString(access1);
+
+ std::string value;
+ bool found = false;
+
+ // Should find "type"
+ ASSERT_NO_THROW(found = db1.getParameterValue("type", value));
+ EXPECT_TRUE(found);
+ EXPECT_EQ("mysql", value);
+
+ // Should find "name"
+ ASSERT_NO_THROW(found = db1.getParameterValue("name", value));
+ EXPECT_TRUE(found);
+ EXPECT_EQ("keatest", value);
+
+ // Should find "port"
+ ASSERT_NO_THROW(found = db1.getParameterValue("port", value));
+ EXPECT_TRUE(found);
+ EXPECT_EQ("33", value);
+
+ // Should find "readonly"
+ ASSERT_NO_THROW(found = db1.getParameterValue("readonly", value));
+ EXPECT_TRUE(found);
+ EXPECT_EQ("false", value);
+
+ // Should not find "bogus"
+ ASSERT_NO_THROW(found = db1.getParameterValue("bogus", value));
+ EXPECT_FALSE(found);
+}
+
+// Verify that db equality operators work correctly.
+TEST(ConfigDbInfo, equalityOperators) {
+ ConfigDbInfo db1;
+ std::string access1 = "type=mysql user=tom password=terrific";
+ ASSERT_NO_THROW(db1.setAccessString(access1));
+
+ ConfigDbInfo db2;
+ std::string access2 = "type=postgresql user=tom password=terrific";
+ ASSERT_NO_THROW(db2.setAccessString(access2));
+
+ // Verify that the two unequal dbs are in fact not equal.
+ EXPECT_FALSE(db1.equals(db2));
+ EXPECT_FALSE(db1 == db2);
+ EXPECT_TRUE(db1 != db2);
+
+ // Verify that the two equal dbs are in fact equal.
+ db2.setAccessString(access1);
+ EXPECT_TRUE(db1.equals(db2));
+ EXPECT_TRUE(db1 == db2);
+ EXPECT_FALSE(db1 != db2);
+}
+
+// Verifies the basic operations of ConfigControlInfo
+TEST(ConfigControlInfo, basicOperation) {
+
+ ConfigControlInfo ctl;
+ // We should have no dbs in the list.
+ EXPECT_EQ(0, ctl.getConfigDatabases().size());
+ // The default fetch time is 30 and it is unspecified.
+ EXPECT_TRUE(ctl.getConfigFetchWaitTime().unspecified());
+ EXPECT_EQ(30, ctl.getConfigFetchWaitTime().get());
+
+ // Override the default fetch time.
+ ctl.setConfigFetchWaitTime(Optional<uint16_t>(123));
+ EXPECT_EQ(123, ctl.getConfigFetchWaitTime().get());
+
+ // We should be able to add two distinct, valid dbs
+ std::string access_str1 = "type=mysql host=machine1.org";
+ ASSERT_NO_THROW(ctl.addConfigDatabase(access_str1));
+
+ std::string access_str2 = "type=postgresql host=machine2.org";
+ ASSERT_NO_THROW(ctl.addConfigDatabase(access_str2));
+
+ // We should fail on a duplicate db.
+ ASSERT_THROW(ctl.addConfigDatabase(access_str1), isc::BadValue);
+
+ // We should have two dbs in the list.
+ const ConfigDbInfoList& db_list = ctl.getConfigDatabases();
+ EXPECT_EQ(2, db_list.size());
+
+ // Verify the dbs in the list are as we expect them to be.
+ EXPECT_EQ (access_str1, db_list[0].getAccessString());
+ EXPECT_EQ (access_str2, db_list[1].getAccessString());
+
+ // Verify we can find dbs based on a property values.
+ const ConfigDbInfo& db_info = ctl.findConfigDb("type", "mysql");
+ EXPECT_FALSE(db_info == ConfigControlInfo::EMPTY_DB());
+ EXPECT_EQ (access_str1, db_info.getAccessString());
+
+ const ConfigDbInfo& db_info2 = ctl.findConfigDb("host", "machine2.org");
+ EXPECT_FALSE(db_info2 == ConfigControlInfo::EMPTY_DB());
+ EXPECT_EQ (access_str2, db_info2.getAccessString());
+
+ // Verify not finding a db returns EMPTY_DB().
+ const ConfigDbInfo& db_info3 = ctl.findConfigDb("type", "bogus");
+ EXPECT_TRUE(db_info3 == ConfigControlInfo::EMPTY_DB());
+
+ // Verify we can clear the list of dbs and the fetch time.
+ ctl.clear();
+ EXPECT_EQ(0, ctl.getConfigDatabases().size());
+ EXPECT_TRUE(ctl.getConfigFetchWaitTime().unspecified());
+ EXPECT_EQ(30, ctl.getConfigFetchWaitTime().get());
+}
+
+// Verifies the copy ctor and equality functions ConfigControlInfo
+TEST(ConfigControlInfo, copyAndEquality) {
+
+ // Make an instance with two dbs.
+ ConfigControlInfo ctl1;
+ ASSERT_NO_THROW(ctl1.addConfigDatabase("type=mysql host=mach1.org"));
+ ASSERT_NO_THROW(ctl1.addConfigDatabase("type=postgresql host=mach2.org"));
+ ctl1.setConfigFetchWaitTime(Optional<uint16_t>(123));
+
+ // Clone that instance.
+ ConfigControlInfo ctl2(ctl1);
+
+ // They should be equal.
+ EXPECT_TRUE(ctl1.equals(ctl2));
+
+ // Make a third instance with a different db.
+ ConfigControlInfo ctl3;
+ ASSERT_NO_THROW(ctl1.addConfigDatabase("type=mysql host=other.org"));
+
+ // They should not equal.
+ EXPECT_FALSE(ctl3.equals(ctl1));
+}
+
diff --git a/src/lib/process/tests/config_ctl_parser_unittests.cc b/src/lib/process/tests/config_ctl_parser_unittests.cc
new file mode 100644
index 0000000..8e5b328
--- /dev/null
+++ b/src/lib/process/tests/config_ctl_parser_unittests.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2018-2019 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 <cc/dhcp_config_error.h>
+#include <process/config_ctl_parser.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+#include <iostream>
+
+using namespace isc::process;
+using namespace isc::data;
+
+// Verifies valid configurations are parsed correctly. The test
+// uses round-trip comparison of configuration Elements to determine
+// if parsing was correct.
+TEST(ConfigCtlInfoParser, validConfigs) {
+ std::string configs[] = {
+ "{}",
+
+ "{ \"config-databases\": [], \n"
+ " \"config-fetch-wait-time\": 20 \n"
+ "}",
+
+ "{ \"config-databases\": [ \n"
+ " { \n"
+ " \"type\": \"mysql\", \n"
+ " \"user\":\"tom\", \n"
+ " \"password\":\"terrific\" \n"
+ " }, \n"
+ " { \n"
+ " \"type\": \"postgresql\",\n"
+ " \"user\":\"bob\", \n"
+ " \"password\":\"wonder\" \n"
+ " } \n"
+ "] } \n"
+ };
+
+ for (auto config : configs) {
+ ConfigControlParser parser;
+ ConfigControlInfoPtr ctl_info;
+
+ // Turn the JSON config into Elements.
+ ElementPtr source_elem;
+ ASSERT_NO_THROW (source_elem = Element::fromJSON(config)) <<
+ " JSON error, test is broken: " << config;
+
+ // Parse the Elements into a ConfigControlInfo.
+ ASSERT_NO_THROW(ctl_info = parser.parse(source_elem));
+ ASSERT_TRUE(ctl_info);
+
+ // Turn the newly constructed info instance back into elements.
+ ElementPtr parsed_elem;
+ ASSERT_NO_THROW(parsed_elem = ctl_info->toElement());
+
+ // When the config is empty, ControlConfigInfo::toElement still
+ // generates a map with an empty db list. Replace source for
+ // element comparison below.
+ if (source_elem->size() == 0) {
+ ASSERT_NO_THROW (source_elem = Element::fromJSON(
+ "{ \"config-databases\": [] }"));
+ }
+
+ // The parsed element should match the source element.
+ EXPECT_TRUE(parsed_elem->equals(*source_elem)) << "config: " << config;
+ }
+}
+
+// Verify that invalid configurations fail to parse gracefully.
+TEST(ConfigCtlInfoParser, invalidConfigs) {
+ // Note that configurations are must be valid JSON, but invalid logically.
+ std::string configs[] = {
+ "{ \"config-databases\": \"not_list\" }",
+ "{ \"config-databases\": [ \n"
+ " { \n"
+ " \"bogus\": \"param\" \n"
+ " } \n"
+ "] } \n",
+ "{ \"config-fetch-wait-time\": -1 }",
+ "{ \"config-fetch-wait-time\": 65537 }",
+ "{ \"config-fetch-wait-time\": \"a-string\" }",
+ };
+
+ for (auto config : configs) {
+ ConfigControlParser parser;
+
+ // Turn the JSON config into Elements.
+ ElementPtr source_elem;
+ ASSERT_NO_THROW (source_elem = Element::fromJSON(config)) <<
+ " JSON error, test is broken: " << config;
+
+ // Parse the Elements into a ConfigControlInfo.
+ ASSERT_THROW(parser.parse(source_elem), isc::ConfigError)
+ << "config: " << config;
+ }
+}
diff --git a/src/lib/process/tests/d_cfg_mgr_unittests.cc b/src/lib/process/tests/d_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..faa3e92
--- /dev/null
+++ b/src/lib/process/tests/d_cfg_mgr_unittests.cc
@@ -0,0 +1,375 @@
+// Copyright (C) 2013-2021 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 <cc/command_interpreter.h>
+#include <exceptions/exceptions.h>
+#include <process/testutils/d_test_stubs.h>
+#include <process/d_cfg_mgr.h>
+#include <process/redact_config.h>
+
+#include <boost/foreach.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::process;
+using namespace isc::data;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test Class for verifying that configuration context cannot be null
+/// during construction.
+class DCtorTestCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor - Note that is passes in an empty configuration
+ /// pointer to the base class constructor.
+ DCtorTestCfgMgr() : DCfgMgrBase(ConfigPtr()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DCtorTestCfgMgr() {
+ }
+
+ /// @brief Dummy implementation as this method is abstract.
+ virtual ConfigPtr createNewContext() {
+ return (ConfigPtr());
+ }
+
+ /// @brief Returns summary of configuration in the textual format.
+ virtual std::string getConfigSummary(const uint32_t) {
+ return ("");
+ }
+};
+
+/// @brief Test fixture class for testing DCfgMgrBase class.
+/// It maintains an member instance of DStubCfgMgr and derives from
+/// ConfigParseTest fixture, thus providing methods for converting JSON
+/// strings to configuration element sets, checking parse results,
+/// accessing the configuration context and trying to unparse.
+class DStubCfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DStubCfgMgrTest():cfg_mgr_(new DStubCfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~DStubCfgMgrTest() {
+ }
+
+ /// @brief Convenience method which returns a DStubContextPtr to the
+ /// configuration context.
+ ///
+ /// @return returns a DStubContextPtr.
+ DStubContextPtr getStubContext() {
+ return (boost::dynamic_pointer_cast<DStubContext>
+ (cfg_mgr_->getContext()));
+ }
+
+ /// @brief Configuration manager instance.
+ DStubCfgMgrPtr cfg_mgr_;
+};
+
+///@brief Tests basic construction/destruction of configuration manager.
+/// Verifies that:
+/// 1. Proper construction succeeds.
+/// 2. Configuration context is initialized by construction.
+/// 3. Destruction works properly.
+/// 4. Construction with a null context is not allowed.
+TEST(DCfgMgrBase, construction) {
+ DCfgMgrBasePtr cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr.reset(new DStubCfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ ConfigPtr context = cfg_mgr->getContext();
+ EXPECT_TRUE(context);
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+
+ // Verify that an attempt to construct a manger with a null context fails.
+ ASSERT_THROW(DCtorTestCfgMgr(), DCfgMgrBaseError);
+}
+
+///@brief Tests fundamental aspects of configuration parsing.
+/// Verifies that:
+/// 1. A correctly formed simple configuration parses without error.
+/// 2. An error building the element is handled.
+/// 3. An error committing the element is handled.
+/// 4. An unknown element error is handled.
+TEST_F(DStubCfgMgrTest, basicParseTest) {
+ // Create a simple configuration.
+ string config = "{ \"test-value\": [] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse a simple configuration.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that we can check a simple configuration.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, true);
+ EXPECT_TRUE(checkAnswer(0));
+}
+
+/// @brief Tests that element ids supported by the base class as well as those
+/// added by the derived class function properly.
+/// This test verifies that:
+/// 1. Boolean parameters can be parsed and retrieved.
+/// 2. Uint32 parameters can be parsed and retrieved.
+/// 3. String parameters can be parsed and retrieved.
+/// 4. Map elements can be parsed and retrieved.
+/// 5. List elements can be parsed and retrieved.
+/// 6. Parsing a second configuration, updates the existing context values
+/// correctly.
+TEST_F(DStubCfgMgrTest, simpleTypesTest) {
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"map_test\" : {} , "
+ " \"list_test\": [] }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ ASSERT_TRUE(checkAnswer(0));
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration which "updates" all of the parameter values.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"map_test2\" : {} , "
+ " \"list_test2\": [] }";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(0));
+ context = getStubContext();
+ ASSERT_TRUE(context);
+}
+
+/// @brief Tests that the configuration context is preserved after failure
+/// during parsing causes a rollback.
+/// 1. Verifies configuration context rollback.
+TEST_F(DStubCfgMgrTest, rollBackTest) {
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"map_test\" : {} , "
+ " \"list_test\": [] }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(0));
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration which "updates" all of the parameter values
+ // plus one unknown at the end.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"map_test2\" : {} , "
+ " \"list_test2\": [] , "
+ " \"zeta_unknown\": 33 } ";
+ ASSERT_TRUE(fromJSON(config2));
+}
+
+/// @brief Tests that the configuration context is preserved during
+/// check only parsing.
+TEST_F(DStubCfgMgrTest, checkOnly) {
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"map_test\" : {} , "
+ " \"list_test\": [] }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(0));
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+
+ // Create a configuration which "updates" all of the parameter values.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"map_test2\" : {} , "
+ " \"list_test2\": [] }";
+ ASSERT_TRUE(fromJSON(config2));
+
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, true);
+ EXPECT_TRUE(checkAnswer(0));
+ context = getStubContext();
+ ASSERT_TRUE(context);
+
+}
+
+// Tests that configuration element position is returned by getParam variants.
+TEST_F(DStubCfgMgrTest, paramPosition) {
+ // Create a configuration with one of each scalar types. We end them
+ // with line feeds so we can test position value.
+ string config = "{ \"bool_test\": true , \n"
+ " \"uint32_test\": 77 , \n"
+ " \"string_test\": \"hmmm chewy\" }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ ASSERT_TRUE(checkAnswer(0));
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+}
+
+// This tests if some aspects of simpleParseConfig are behaving properly.
+// Thorough testing is only possible for specific implementations. This
+// is done for control agent (see CtrlAgentControllerTest tests in
+// src/bin/agent/tests/ctrl_agent_controller_unittest.cc for example).
+// Also, shell tests in src/bin/agent/ctrl_agent_process_tests.sh test
+// the whole CA process that uses simpleParseConfig. The alternative
+// would be to implement whole parser that would set the context
+// properly. The ROI for this is not worth the effort.
+TEST_F(DStubCfgMgrTest, simpleParseConfig) {
+ using namespace isc::data;
+
+ // Passing just null pointer should result in error return code
+ answer_ = cfg_mgr_->simpleParseConfig(ConstElementPtr(), false);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Ok, now try with a dummy, but valid json code
+ string config = "{ \"bool_test\": true , \n"
+ " \"uint32_test\": 77 , \n"
+ " \"string_test\": \"hmmm chewy\" }";
+ ASSERT_NO_THROW(fromJSON(config));
+
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ EXPECT_TRUE(checkAnswer(0));
+}
+
+// This test checks that the post configuration callback function is
+// executed by the simpleParseConfig function.
+TEST_F(DStubCfgMgrTest, simpleParseConfigWithCallback) {
+ string config = "{ \"bool_test\": true , \n"
+ " \"uint32_test\": 77 , \n"
+ " \"string_test\": \"hmmm chewy\" }";
+ ASSERT_NO_THROW(fromJSON(config));
+
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false,
+ []() {
+ isc_throw(Unexpected, "unexpected configuration error");
+ });
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+// This test checks that redactConfig works as expected.
+TEST_F(DStubCfgMgrTest, redactConfig) {
+ // Basic case.
+ string config = "{ \"foo\": 1 }";
+ ConstElementPtr elem;
+ ASSERT_NO_THROW(elem = Element::fromJSON(config));
+ ConstElementPtr ret;
+ ASSERT_NO_THROW(ret = redactConfig(elem));
+ EXPECT_EQ(ret->str(), elem->str());
+
+ // Verify redaction.
+ config = "{ \"password\": \"foo\", \"secret\": \"bar\" }";
+ ASSERT_NO_THROW(elem = Element::fromJSON(config));
+ ASSERT_NO_THROW(ret = redactConfig(elem));
+ string expected = "{ \"password\": \"*****\", \"secret\": \"*****\" }";
+ EXPECT_EQ(expected, ret->str());
+
+ // Verify that user context are skipped.
+ config = "{ \"user-context\": { \"password\": \"foo\" } }";
+ ASSERT_NO_THROW(elem = Element::fromJSON(config));
+ ASSERT_NO_THROW(ret = redactConfig(elem));
+ EXPECT_EQ(ret->str(), elem->str());
+
+ // Verify that only given subtrees are handled.
+ list<string> keys = { "foo" };
+ config = "{ \"foo\": { \"password\": \"foo\" }, ";
+ config += "\"next\": { \"secret\": \"bar\" } }";
+ ASSERT_NO_THROW(elem = Element::fromJSON(config));
+ ASSERT_NO_THROW(ret = redactConfig(elem, keys));
+ expected = "{ \"foo\": { \"password\": \"*****\" }, ";
+ expected += "\"next\": { \"secret\": \"bar\" } }";
+ EXPECT_EQ(expected, ret->str());
+}
+
+// Test that user context is not touched when configuration is redacted.
+TEST(RedactConfig, userContext) {
+ ConstElementPtr const config(Element::fromJSON(R"(
+ {
+ "some-database": {
+ "password": "sensitive",
+ "secret": "sensitive",
+ "user": "keatest",
+ "nested-map": {
+ "password": "sensitive",
+ "secret": "sensitive",
+ "user": "keatest"
+ }
+ },
+ "user-context": {
+ "password": "keatest",
+ "secret": "keatest",
+ "user": "keatest",
+ "nested-map": {
+ "password": "keatest",
+ "secret": "keatest",
+ "user": "keatest"
+ }
+ }
+ }
+ )"));
+ ConstElementPtr const expected(Element::fromJSON(R"(
+ {
+ "some-database": {
+ "password": "*****",
+ "secret": "*****",
+ "user": "keatest",
+ "nested-map": {
+ "password": "*****",
+ "secret": "*****",
+ "user": "keatest"
+ }
+ },
+ "user-context": {
+ "password": "keatest",
+ "secret": "keatest",
+ "user": "keatest",
+ "nested-map": {
+ "password": "keatest",
+ "secret": "keatest",
+ "user": "keatest"
+ }
+ }
+ }
+ )"));
+ ConstElementPtr redacted(redactConfig(config));
+ EXPECT_TRUE(isEquivalent(redacted, expected))
+ << "Actual:\n" << prettyPrint(redacted) << "\n"
+ "Expected:\n" << prettyPrint(expected);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/process/tests/d_controller_unittests.cc b/src/lib/process/tests/d_controller_unittests.cc
new file mode 100644
index 0000000..4d7b73c
--- /dev/null
+++ b/src/lib/process/tests/d_controller_unittests.cc
@@ -0,0 +1,470 @@
+// Copyright (C) 2013-2021 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 <kea_version.h>
+
+#include <asiolink/testutils/timed_signal.h>
+#include <cc/command_interpreter.h>
+#include <process/testutils/d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace isc::asiolink::test;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace process {
+
+/// @brief Test fixture class for testing DControllerBase class. This class
+/// derives from DControllerTest and wraps a DStubController. DStubController
+/// has been constructed to exercise DControllerBase.
+class DStubControllerTest : public DControllerTest {
+public:
+ /// @brief Constructor.
+ /// Note the constructor passes in the static DStubController instance
+ /// method.
+ DStubControllerTest() : DControllerTest(DStubController::instance) {
+ controller_ = boost::dynamic_pointer_cast<DStubController>
+ (DControllerTest::
+ getController());
+ }
+
+ /// @brief The controller.
+ DStubControllerPtr controller_;
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verifies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(DStubControllerTest, basicInstanceTesting) {
+ // Verify that the singleton exists and it is the correct type.
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ ASSERT_NO_THROW(boost::dynamic_pointer_cast<DStubController>(controller));
+
+ // Verify that controller's app name is correct.
+ EXPECT_TRUE(checkAppName(DStubController::stub_app_name_));
+
+ // Verify that controller's bin name is correct.
+ EXPECT_TRUE(checkBinName(DStubController::stub_bin_name_));
+
+ // Verify that controller's IOService exists.
+ EXPECT_TRUE(checkIOService());
+
+ // Verify that the Process does NOT exist.
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verifies that:
+/// 1. Standard command line options are supported.
+/// 2. Custom command line options are supported.
+/// 3. Invalid options are detected.
+/// 4. Extraneous command line information is detected.
+TEST_F(DStubControllerTest, commandLineArgs) {
+
+ // Verify that verbose flag is false initially.
+ EXPECT_TRUE(checkVerbose(false));
+
+ // Verify that standard options can be parsed without error.
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>("cfgName"),
+ const_cast<char*>("-d") };
+ int argc = 4;
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ // Verify that verbose is true.
+ EXPECT_TRUE(checkVerbose(true));
+
+ // Verify configuration file name is correct
+ EXPECT_TRUE(checkConfigFileName("cfgName"));
+
+ // Verify that the custom command line option is parsed without error.
+ char xopt[3] = "- ";
+ xopt[1] = *DStubController::stub_option_x_;
+ char* argv1[] = { const_cast<char*>("progName"), xopt};
+ argc = 2;
+ EXPECT_NO_THROW (parseArgs(argc, argv1));
+
+ // Verify that an unknown option is detected.
+ char* argv2[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-bs") };
+ argc = 2;
+ EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
+
+ // Verify that extraneous information is detected.
+ char* argv3[] = { const_cast<char*>("progName"),
+ const_cast<char*>("extra"),
+ const_cast<char*>("information") };
+ argc = 3;
+ EXPECT_THROW (parseArgs(argc, argv3), InvalidUsage);
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that:
+/// 1. An error during process creation is handled.
+/// 2. A NULL returned by process creation is handled.
+/// 3. An error during process initialization is handled.
+/// 4. Process can be successfully created and initialized.
+TEST_F(DStubControllerTest, initProcessTesting) {
+ // Verify that a failure during process creation is caught.
+ SimFailure::set(SimFailure::ftCreateProcessException);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that a NULL returned by process creation is handled.
+ SimFailure::set(SimFailure::ftCreateProcessNull);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ // Re-create controller, verify that we are starting clean
+ resetController();
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that an error during process initialization is handled.
+ SimFailure::set(SimFailure::ftProcessInit);
+ EXPECT_THROW(initProcess(), DProcessBaseError);
+
+ // Re-create controller, verify that we are starting clean
+ resetController();
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that the application process can created and initialized.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Tests launch handling of invalid command line.
+/// This test launches with an invalid command line which should throw
+/// an InvalidUsage.
+TEST_F(DStubControllerTest, launchInvalidUsage) {
+ // Command line to run integrated
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-z") };
+ int argc = 2;
+
+ // Launch the controller in integrated mode.
+ EXPECT_THROW(launch(argc, argv), InvalidUsage);
+}
+
+/// @brief Tests launch handling of failure in application process
+/// initialization. This test launches with a valid command line but with
+/// SimFailure set to fail during process creation. Launch should throw
+/// ProcessInitError.
+TEST_F(DStubControllerTest, launchProcessInitError) {
+ // Command line to run integrated
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>(DControllerTest::CFG_TEST_FILE),
+ const_cast<char*>("-d") };
+ int argc = 4;
+
+ // Launch the controller in stand alone mode.
+ SimFailure::set(SimFailure::ftCreateProcessException);
+ EXPECT_THROW(launch(argc, argv), ProcessInitError);
+}
+
+/// @brief Tests launch and normal shutdown (stand alone mode).
+/// This creates an interval timer to generate a normal shutdown and then
+/// launches with a valid, command line, with a valid configuration file
+/// and no simulated errors.
+TEST_F(DStubControllerTest, launchNormalShutdown) {
+ // Write the valid, empty, config and then run launch() for 1000 ms
+ time_duration elapsed_time;
+ ASSERT_NO_THROW(runWithConfig("{}", 2000, elapsed_time));
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
+ elapsed_time.total_milliseconds() <= 2300);
+}
+
+/// @brief A variant of the launch and normal shutdown test using a callback.
+TEST_F(DStubControllerTest, launchNormalShutdownWithCallback) {
+ // Write the valid, empty, config and then run launch() for 1000 ms
+ // Access to the internal state.
+ auto callback = [&] { EXPECT_FALSE(getProcess()->shouldShutdown()); };
+ time_duration elapsed_time;
+ ASSERT_NO_THROW(runWithConfig("{}", 2000,
+ static_cast<const TestCallback&>(callback),
+ elapsed_time));
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
+ elapsed_time.total_milliseconds() <= 2300);
+}
+
+/// @brief Tests launch with an non-existing configuration file.
+TEST_F(DStubControllerTest, nonExistingConfigFile) {
+ // command line to run standalone
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>("bogus-file"),
+ const_cast<char*>("-d") };
+ int argc = 4;
+
+ // Record start time, and invoke launch().
+ EXPECT_THROW(launch(argc, argv), ProcessInitError);
+}
+
+/// @brief Tests launch with configuration file argument but no file name
+TEST_F(DStubControllerTest, missingConfigFileName) {
+ // command line to run standalone
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>("-d") };
+ int argc = 3;
+
+ // Record start time, and invoke launch().
+ EXPECT_THROW(launch(argc, argv), ProcessInitError);
+}
+
+/// @brief Tests launch with no configuration file argument
+TEST_F(DStubControllerTest, missingConfigFileArgument) {
+ // command line to run standalone
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-d") };
+ int argc = 2;
+
+ // Record start time, and invoke launch().
+ EXPECT_THROW(launch(argc, argv), LaunchError);
+}
+
+/// @brief Tests launch with an operational error during application execution.
+/// This test creates an interval timer to generate a runtime exception during
+/// the process event loop. It launches with a valid, stand-alone command line
+/// and no simulated errors. Launch should throw ProcessRunError.
+TEST_F(DStubControllerTest, launchRuntimeError) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genFatalErrorCallback, 2000);
+
+ // Write the valid, empty, config and then run launch() for 5000 ms
+ time_duration elapsed_time;
+ EXPECT_THROW(runWithConfig("{}", 5000, elapsed_time), ProcessRunError);
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() >= 1900 &&
+ elapsed_time.total_milliseconds() <= 2300);
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation.
+/// This test verifies that:
+/// 1. That a valid configuration update results in successful status return.
+/// 2. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(DStubControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set. Content is arbitrary, just needs to be
+ // valid JSON.
+ std::string config = "{ \"test-value\": 1000 } ";
+ isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+
+ // Verify that a valid config gets a successful update result.
+ answer = updateConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Verify that a valid config gets a successful check result.
+ answer = checkConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Verify that an error in process configure method is handled.
+ SimFailure::set(SimFailure::ftProcessConfigure);
+ answer = updateConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Verify that an error is handled too when the config is checked for.
+ SimFailure::set(SimFailure::ftProcessConfigure);
+ answer = checkConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+}
+
+// Tests that handleOtherObjects behaves as expected.
+TEST_F(DStubControllerTest, handleOtherObjects) {
+ using namespace isc::data;
+
+ // A bad config.
+ ElementPtr config = Element::createMap();
+ config->set(controller_->getAppName(), Element::create(1));
+ config->set("foo", Element::create(2));
+ config->set("bar", Element::create(3));
+
+ // Check the error message.
+ std::string errmsg;
+ EXPECT_NO_THROW(errmsg = controller_->handleOtherObjects(config));
+ EXPECT_EQ(" contains unsupported 'bar' parameter (and 'foo')", errmsg);
+
+ // Retry with no error.
+ config = Element::createMap();
+ config->set(controller_->getAppName(), Element::create(1));
+ EXPECT_NO_THROW(errmsg = controller_->handleOtherObjects(config));
+ EXPECT_TRUE(errmsg.empty());
+}
+
+// Tests that registered signals are caught and handled.
+TEST_F(DStubControllerTest, ioSignals) {
+ // Tell test controller just to record the signals, don't call the
+ // base class signal handler.
+ controller_->recordSignalOnly(true);
+
+ // Setup to raise SIGHUP in 10 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 10);
+ TimedSignal sigint(*getIOService(), SIGINT, 100);
+ TimedSignal sigterm(*getIOService(), SIGTERM, 200);
+
+ // Write the valid, empty, config and then run launch() for 500 ms
+ time_duration elapsed_time;
+ runWithConfig("{}", 500, elapsed_time);
+
+ // Verify that we caught the signals as expected.
+ std::vector<int>& signals = controller_->getProcessedSignals();
+ ASSERT_EQ(3, signals.size());
+ EXPECT_EQ(SIGHUP, signals[0]);
+ EXPECT_EQ(SIGINT, signals[1]);
+ EXPECT_EQ(SIGTERM, signals[2]);
+}
+
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(DStubControllerTest, invalidConfigReload) {
+ // Schedule to rewrite the configuration file after launch. This way the
+ // file is updated after we have done the initial configuration. The
+ // new content is invalid JSON which will cause the config parse to fail.
+ scheduleTimedWrite("{ \"string_test\": BOGUS JSON }", 100);
+
+ // Setup to raise SIGHUP in 200 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+ // Write the config and then run launch() for 500 ms
+ // After startup, which will load the initial configuration this enters
+ // the process's runIO() loop. We will first rewrite the config file.
+ // Next we process the SIGHUP signal which should cause us to reconfigure.
+ time_duration elapsed_time;
+ runWithConfig("{ \"string_test\": \"first value\" }", 500, elapsed_time);
+
+ // Verify that we saw the signal.
+ std::vector<int>& signals = controller_->getProcessedSignals();
+ ASSERT_EQ(1, signals.size());
+ EXPECT_EQ(SIGHUP, signals[0]);
+}
+
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(DStubControllerTest, alternateParsing) {
+ controller_->useAlternateParser(true);
+
+ // Setup to raise SIGHUP in 200 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+ // Write the config and then run launch() for 500 ms
+ // After startup, which will load the initial configuration this enters
+ // the process's runIO() loop. We will first rewrite the config file.
+ // Next we process the SIGHUP signal which should cause us to reconfigure.
+ time_duration elapsed_time;
+ runWithConfig("{ \"string_test\": \"first value\" }", 500, elapsed_time);
+
+ // Verify that we saw the signal.
+ std::vector<int>& signals = controller_->getProcessedSignals();
+ ASSERT_EQ(1, signals.size());
+ EXPECT_EQ(SIGHUP, signals[0]);
+}
+
+// Tests that the original configuration is replaced after a SIGHUP triggered
+// reconfiguration succeeds.
+TEST_F(DStubControllerTest, validConfigReload) {
+ // Schedule to rewrite the configuration file after launch. This way the
+ // file is updated after we have done the initial configuration.
+ scheduleTimedWrite("{ \"string_test\": \"second value\" }", 100);
+
+ // Setup to raise SIGHUP in 200 ms and another at 400 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+ TimedSignal sighup2(*getIOService(), SIGHUP, 400);
+
+ // Write the config and then run launch() for 800 ms
+ time_duration elapsed_time;
+ runWithConfig("{ \"string_test\": \"first value\" }", 800, elapsed_time);
+
+ // Verify that we saw two occurrences of the signal.
+ std::vector<int>& signals = controller_->getProcessedSignals();
+ ASSERT_EQ(2, signals.size());
+ EXPECT_EQ(SIGHUP, signals[0]);
+ EXPECT_EQ(SIGHUP, signals[1]);
+}
+
+// Tests that the SIGINT triggers a normal shutdown.
+TEST_F(DStubControllerTest, sigintShutdown) {
+ // Setup to raise SIGHUP in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGINT, 1);
+
+ // Write the config and then run launch() for 1000 ms
+ time_duration elapsed_time;
+ runWithConfig("{ \"string_test\": \"first value\" }", 1000, elapsed_time);
+
+ // Verify that we saw the signal.
+ std::vector<int>& signals = controller_->getProcessedSignals();
+ ASSERT_EQ(1, signals.size());
+ EXPECT_EQ(SIGINT, signals[0]);
+
+ // Duration should be significantly less than our max run time.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+}
+
+// Verifies that version and extended version information is correct
+TEST_F(DStubControllerTest, getVersion) {
+ std::string text = controller_->getVersion(false);
+ EXPECT_EQ(text,VERSION);
+
+ text = controller_->getVersion(true);
+ EXPECT_NE(std::string::npos, text.find(VERSION));
+ EXPECT_NE(std::string::npos, text.find(EXTENDED_VERSION));
+ EXPECT_NE(std::string::npos, text.find(controller_->getVersionAddendum()));
+}
+
+// Tests that the SIGTERM triggers a normal shutdown.
+TEST_F(DStubControllerTest, sigtermShutdown) {
+ // Setup to raise SIGHUP in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGTERM, 1);
+
+ // Write the config and then run launch() for 1000 ms
+ time_duration elapsed_time;
+ runWithConfig("{ \"string_test\": \"first value\" }", 1000, elapsed_time);
+
+ // Verify that we saw the signal.
+ std::vector<int>& signals = controller_->getProcessedSignals();
+ ASSERT_EQ(1, signals.size());
+ EXPECT_EQ(SIGTERM, signals[0]);
+
+ // Duration should be significantly less than our max run time.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+}
+
+}; // end of isc::process namespace
+}; // end of isc namespace
diff --git a/src/lib/process/tests/daemon_unittest.cc b/src/lib/process/tests/daemon_unittest.cc
new file mode 100644
index 0000000..bd80e25
--- /dev/null
+++ b/src/lib/process/tests/daemon_unittest.cc
@@ -0,0 +1,324 @@
+// Copyright (C) 2014-2020 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 <cc/data.h>
+#include <process/daemon.h>
+#include <process/config_base.h>
+#include <process/log_parser.h>
+#include <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/wait.h>
+
+using namespace isc;
+using namespace isc::process;
+using namespace isc::data;
+
+namespace isc {
+namespace process {
+
+// @brief Derived Daemon class
+class DaemonImpl : public Daemon {
+public:
+ static std::string getVersion(bool extended);
+
+ using Daemon::makePIDFileName;
+};
+
+std::string DaemonImpl::getVersion(bool extended) {
+ if (extended) {
+ return (std::string("EXTENDED"));
+ } else {
+ return (std::string("BASIC"));
+ }
+}
+
+};
+};
+
+namespace {
+
+/// @brief Daemon Test test fixture class
+class DaemonTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ DaemonTest() : env_copy_() {
+ // Take a copy of KEA_PIDFILE_DIR environment variable value
+ char *env_value = getenv("KEA_PIDFILE_DIR");
+ if (env_value) {
+ env_copy_ = std::string(env_value);
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// As some of the tests have the side-effect of altering the logging
+ /// settings (when configureLogger is called), the logging is reset to
+ /// the default after each test completes.
+ ~DaemonTest() {
+ isc::log::setDefaultLoggingOutput();
+ // Restore KEA_PIDFILE_DIR environment variable value
+ if (env_copy_.empty()) {
+ static_cast<void>(unsetenv("KEA_PIDFILE_DIR"));
+ } else {
+ static_cast<void>(setenv("KEA_PIDFILE_DIR", env_copy_.c_str(), 1));
+ }
+ }
+
+private:
+ /// @brief copy of KEA_PIDFILE_DIR environment variable value
+ std::string env_copy_;
+};
+
+
+// Very simple test. Checks whether Daemon can be instantiated and its
+// default parameters are sane
+TEST_F(DaemonTest, constructor) {
+ // Disable KEA_PIDFILE_DIR
+ EXPECT_EQ(0, unsetenv("KEA_PIDFILE_DIR"));
+
+ EXPECT_NO_THROW(Daemon instance1);
+
+ // Check only instance values.
+ Daemon instance2;
+ EXPECT_TRUE(instance2.getConfigFile().empty());
+ EXPECT_EQ(std::string(DATA_DIR), instance2.getPIDFileDir());
+ EXPECT_TRUE(instance2.getPIDFileName().empty());
+}
+
+// Verify config file accessors
+TEST_F(DaemonTest, getSetConfigFile) {
+ Daemon instance;
+
+ EXPECT_NO_THROW(instance.setConfigFile("test.txt"));
+ EXPECT_EQ("test.txt", instance.getConfigFile());
+ EXPECT_NO_THROW(instance.checkConfigFile());
+}
+
+// Verify config file checker.
+TEST_F(DaemonTest, checkConfigFile) {
+ Daemon instance;
+
+ EXPECT_THROW(instance.checkConfigFile(), BadValue);
+ EXPECT_NO_THROW(instance.setConfigFile("/tmp/"));
+ EXPECT_THROW(instance.checkConfigFile(), BadValue);
+ EXPECT_NO_THROW(instance.setConfigFile("/tmp/test.txt"));
+ EXPECT_NO_THROW(instance.checkConfigFile());
+}
+
+// Verify process name accessors
+TEST_F(DaemonTest, getSetProcName) {
+ Daemon instance;
+
+ EXPECT_NO_THROW(instance.setProcName("myproc"));
+ EXPECT_EQ("myproc", instance.getProcName());
+}
+
+// Verify PID file directory name accessors
+TEST_F(DaemonTest, getSetPIDFileDir) {
+ Daemon instance;
+
+ EXPECT_NO_THROW(instance.setPIDFileDir("/tmp"));
+ EXPECT_EQ("/tmp", instance.getPIDFileDir());
+}
+
+// Verify PID file name accessors.
+TEST_F(DaemonTest, setPIDFileName) {
+ Daemon instance;
+
+ // Verify that PID file name may not be set to empty
+ EXPECT_THROW(instance.setPIDFileName(""), BadValue);
+
+ EXPECT_NO_THROW(instance.setPIDFileName("myproc"));
+ EXPECT_EQ("myproc", instance.getPIDFileName());
+
+ // Verify that setPIDFileName cannot be called twice on the same instance.
+ EXPECT_THROW(instance.setPIDFileName("again"), InvalidOperation);
+}
+
+// Test the getVersion() redefinition
+TEST_F(DaemonTest, getVersion) {
+ EXPECT_THROW(Daemon::getVersion(false), NotImplemented);
+
+ ASSERT_NO_THROW(DaemonImpl::getVersion(false));
+
+ EXPECT_EQ(DaemonImpl::getVersion(false), "BASIC");
+
+ ASSERT_NO_THROW(DaemonImpl::getVersion(true));
+
+ EXPECT_EQ(DaemonImpl::getVersion(true), "EXTENDED");
+}
+
+// Verify makePIDFileName method
+TEST_F(DaemonTest, makePIDFileName) {
+ DaemonImpl instance;
+
+ // Verify that config file cannot be blank
+ instance.setProcName("notblank");
+ EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
+
+ // Verify that proc name cannot be blank
+ instance.setProcName("");
+ instance.setConfigFile("notblank");
+ EXPECT_THROW(instance.makePIDFileName(), InvalidOperation);
+
+ // Verify that config file must contain a file name
+ instance.setProcName("myproc");
+ instance.setConfigFile(".txt");
+ EXPECT_THROW(instance.makePIDFileName(), BadValue);
+ instance.setConfigFile("/tmp/");
+ EXPECT_THROW(instance.makePIDFileName(), BadValue);
+
+ // Given a valid config file name and proc name we should good to go
+ instance.setConfigFile("/tmp/test.conf");
+ std::string name;
+ EXPECT_NO_THROW(name = instance.makePIDFileName());
+
+ // Make sure the name is as we expect
+ std::ostringstream stream;
+ stream << instance.getPIDFileDir() << "/test.myproc.pid";
+ EXPECT_EQ(stream.str(), name);
+
+ // Verify that the default directory can be overridden
+ instance.setPIDFileDir("/tmp");
+ EXPECT_NO_THROW(name = instance.makePIDFileName());
+ EXPECT_EQ("/tmp/test.myproc.pid", name);
+}
+
+// Verifies the creation a PID file and that a pre-existing PID file
+// which points to a live PID causes a throw.
+TEST_F(DaemonTest, createPIDFile) {
+ DaemonImpl instance;
+
+ instance.setConfigFile("test.conf");
+ instance.setProcName("daemon_test");
+ instance.setPIDFileDir(TEST_DATA_BUILDDIR);
+
+ EXPECT_NO_THROW(instance.createPIDFile());
+
+ std::ostringstream stream;
+ stream << TEST_DATA_BUILDDIR << "/test.daemon_test.pid";
+ EXPECT_EQ(stream.str(), instance.getPIDFileName());
+
+ // If we try again, we should see our own PID file and fail
+ EXPECT_THROW(instance.createPIDFile(), DaemonPIDExists);
+}
+
+// Verifies that a pre-existing PID file which points to a dead PID
+// is overwritten.
+TEST_F(DaemonTest, createPIDFileOverwrite) {
+ DaemonImpl instance;
+
+ // We're going to use fork to generate a PID we KNOW is dead.
+ int pid = fork();
+ ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ // This is the child, die right away. Tragic, no?
+ _exit (0);
+ }
+
+ // Back in the parent test, we need to wait for the child to die
+ // As with debugging waitpid() can be interrupted ignore EINTR.
+ int stat;
+ int ret;
+ do {
+ ret = waitpid(pid, &stat, 0);
+ } while ((ret == -1) && (errno == EINTR));
+ ASSERT_EQ(ret, pid);
+
+ // Ok, so we should now have a PID that we know to be dead.
+ // Let's use it to create a PID file.
+ instance.setConfigFile("test.conf");
+ instance.setProcName("daemon_test");
+ instance.setPIDFileDir(TEST_DATA_BUILDDIR);
+ EXPECT_NO_THROW(instance.createPIDFile(pid));
+
+ // If we try to create the PID file again, this should work.
+ EXPECT_NO_THROW(instance.createPIDFile());
+}
+
+// Verifies that Daemon destruction deletes the PID file
+TEST_F(DaemonTest, PIDFileCleanup) {
+ boost::shared_ptr<DaemonImpl> instance;
+ instance.reset(new DaemonImpl);
+
+ instance->setConfigFile("test.conf");
+ instance->setProcName("daemon_test");
+ instance->setPIDFileDir(TEST_DATA_BUILDDIR);
+ EXPECT_NO_THROW(instance->createPIDFile());
+
+ // If we try again, we should see our own PID file
+ EXPECT_THROW(instance->createPIDFile(), DaemonPIDExists);
+
+ // Save the pid file name
+ std::string pid_file_name = instance->getPIDFileName();
+
+ // Now delete the Daemon instance. This should remove the
+ // PID file.
+ instance.reset();
+
+ struct stat stat_buf;
+ ASSERT_EQ(-1, stat(pid_file_name.c_str(), &stat_buf));
+ EXPECT_EQ(errno, ENOENT);
+}
+
+// Checks that configureLogger method is behaving properly.
+// More dedicated tests are available for LogConfigParser class.
+// See logger_unittest.cc
+TEST_F(DaemonTest, parsingConsoleOutput) {
+ Daemon::setVerbose(false);
+
+ // Storage - parsed configuration will be stored here
+ ConfigPtr storage(new ConfigBase());
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\""
+ " }"
+ " ],"
+ " \"debuglevel\": 99,"
+ " \"severity\": \"DEBUG\""
+ " }"
+ "]}";
+ ConstElementPtr config = Element::fromJSON(config_txt);
+
+ // Spawn a daemon and tell it to configure logger
+ Daemon x;
+ EXPECT_NO_THROW(x.configureLogger(config, storage));
+
+ // The parsed configuration should be processed by the daemon and
+ // stored in configuration storage.
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(99, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
+}
+
+TEST_F(DaemonTest, exitValue) {
+ DaemonImpl instance;
+
+ EXPECT_EQ(EXIT_SUCCESS, instance.getExitValue());
+ instance.setExitValue(77);
+ EXPECT_EQ(77, instance.getExitValue());
+}
+
+
+// More tests will appear here as we develop Daemon class.
+
+};
diff --git a/src/lib/process/tests/log_parser_unittests.cc b/src/lib/process/tests/log_parser_unittests.cc
new file mode 100644
index 0000000..0341dd7
--- /dev/null
+++ b/src/lib/process/tests/log_parser_unittests.cc
@@ -0,0 +1,530 @@
+// Copyright (C) 2014-2023 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 <cc/data.h>
+#include <process/log_parser.h>
+#include <process/process_messages.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+#include <process/d_log.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/io_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::process;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Logging Test Fixture Class
+///
+/// Trivial class that ensures that the logging is reset to its defaults after
+/// each test. Strictly speaking this only resets the testing root logger (which
+/// has the name "kea") but as the only other logger mentioned here ("wombat")
+/// is not used elsewhere, that is sufficient.
+class LoggingTest : public ::testing::Test {
+ public:
+ /// @brief Constructor
+ LoggingTest() {}
+
+ /// @brief Destructor
+ ///
+ /// Reset root logger back to defaults.
+ ~LoggingTest() {
+ isc::log::initLogger();
+ wipeFiles();
+ }
+
+ /// @brief Generates a log file name suffixed with a rotation number
+ /// @param rotation number to the append to the end of the file
+ std::string logName(int rotation) {
+ std::ostringstream os;
+ os << TEST_LOG_NAME << "." << rotation;
+ return (os.str());
+ }
+
+ /// @brief Removes the base log file name and 1 rotation
+ void wipeFiles() {
+ static_cast<void>(remove(TEST_LOG_NAME));
+ for (int i = 1; i < TEST_MAX_VERS + 1; ++i) {
+ static_cast<void>(remove(logName(i).c_str()));
+ }
+
+ // Remove the lock file
+ std::ostringstream os;
+ os << TEST_LOG_NAME << ".lock";
+ static_cast<void>(remove(os.str().c_str()));
+ }
+
+ /// @brief Name of the log file
+ static const char* TEST_LOG_NAME;
+
+ /// @brief Maximum log size
+ static const int TEST_MAX_SIZE;
+
+ /// @brief Maximum rotated log versions
+ static const int TEST_MAX_VERS;
+
+};
+
+const char* LoggingTest::TEST_LOG_NAME = "kea.test.log";
+const int LoggingTest::TEST_MAX_SIZE = 204800; // Smallest without disabling rotation
+const int LoggingTest::TEST_MAX_VERS = 2; // More than the default of 1
+
+// Checks that the constructor is able to process specified storage properly.
+TEST_F(LoggingTest, constructor) {
+
+ ConfigPtr null_ptr;
+ EXPECT_THROW(LogConfigParser parser(null_ptr), BadValue);
+
+ ConfigPtr nonnull(new ConfigBase());
+
+ EXPECT_NO_THROW(LogConfigParser parser(nonnull));
+}
+
+// Checks if the LogConfigParser class is able to transform JSON structures
+// into Configuration usable by log4cplus. This test checks for output
+// configured to stdout on debug level.
+TEST_F(LoggingTest, parsingConsoleOutput) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"flush\": true"
+ " }"
+ " ],"
+ " \"debuglevel\": 99,"
+ " \"severity\": \"DEBUG\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(99, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+}
+
+// Check that LogConfigParser can parse configuration that
+// lacks a severity entry.
+TEST_F(LoggingTest, parsingNoSeverity) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"flush\": true"
+ " }"
+ " ],"
+ " \"debuglevel\": 99"
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ // No exception should be thrown.
+ EXPECT_NO_THROW_LOG(parser.parseConfiguration(config));
+
+ // Entries should be the ones set.
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+ LoggingInfo const& logging_info(storage->getLoggingInfo()[0]);
+ EXPECT_EQ("kea", logging_info.name_);
+ EXPECT_EQ(99, logging_info.debuglevel_);
+ ASSERT_EQ(1, logging_info.destinations_.size());
+ EXPECT_EQ("stdout" , logging_info.destinations_[0].output_);
+ EXPECT_TRUE(logging_info.destinations_[0].flush_);
+
+ // Severity should default to DEFAULT.
+ EXPECT_EQ(isc::log::DEFAULT, logging_info.severity_);
+
+ // Pattern should default to empty string.
+ EXPECT_TRUE(logging_info.destinations_[0].pattern_.empty());
+}
+
+// Checks if the LogConfigParser class is able to transform JSON structures
+// into Configuration usable by log4cplus. This test checks for output
+// configured to a file on INFO level.
+TEST_F(LoggingTest, parsingFile) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile.txt\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ // Default for immediate flush is true
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+
+ // Pattern should default to empty string.
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].pattern_.empty());
+}
+
+// Checks if the LogConfigParser class is able to transform data structures
+// into Configuration usable by log4cplus. This test checks that more than
+// one logger can be configured.
+TEST_F(LoggingTest, multipleLoggers) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile.txt\","
+ " \"flush\": true"
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " },"
+ " {"
+ " \"name\": \"wombat\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile2.txt\","
+ " \"flush\": false"
+ " }"
+ " ],"
+ " \"severity\": \"DEBUG\","
+ " \"debuglevel\": 99"
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(2, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+
+ EXPECT_EQ("wombat", storage->getLoggingInfo()[1].name_);
+ EXPECT_EQ(99, storage->getLoggingInfo()[1].debuglevel_);
+ EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[1].severity_);
+ ASSERT_EQ(1, storage->getLoggingInfo()[1].destinations_.size());
+ EXPECT_EQ("logfile2.txt" , storage->getLoggingInfo()[1].destinations_[0].output_);
+ EXPECT_FALSE(storage->getLoggingInfo()[1].destinations_[0].flush_);
+}
+
+// Checks if the LogConfigParser class is able to transform data structures
+// into Configuration usable by log4cplus. This test checks that more than
+// one logging destination can be configured.
+TEST_F(LoggingTest, multipleLoggingDestinations) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile.txt\""
+ " },"
+ " {"
+ " \"output\": \"stdout\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+ ASSERT_EQ(2, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[1].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[1].flush_);
+}
+
+// Verifies that log rotation occurs when configured. We do not
+// worry about contents of the log files, only that rotation occurs.
+// Such details are tested in lib/log. This test verifies that
+// we can correctly configure logging such that rotation occurs as
+// expected.
+TEST_F(LoggingTest, logRotate) {
+ wipeFiles();
+
+ std::ostringstream os;
+ os <<
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \""
+ << TEST_LOG_NAME << "\"," <<
+ " \"flush\": true,"
+ " \"maxsize\":"
+ << TEST_MAX_SIZE << "," <<
+ " \"maxver\":"
+ << TEST_MAX_VERS <<
+ " }"
+ " ],"
+ " \"debuglevel\": 99,"
+ " \"severity\": \"DEBUG\""
+ " }"
+ "]}";
+
+ // Create our server config container.
+ ConfigPtr server_cfg(new ConfigBase());
+
+ // LogConfigParser expects a list of loggers, so parse
+ // the JSON text and extract the "loggers" element from it
+ ConstElementPtr config = Element::fromJSON(os.str());
+ config = config->get("loggers");
+
+ // Parse the config and then apply it.
+ LogConfigParser parser(server_cfg);
+ ASSERT_NO_THROW(parser.parseConfiguration(config));
+ ASSERT_NO_THROW(server_cfg->applyLoggingCfg());
+
+ EXPECT_EQ(TEST_MAX_SIZE, server_cfg->getLoggingInfo()[0].destinations_[0].maxsize_);
+ EXPECT_EQ(TEST_MAX_VERS, server_cfg->getLoggingInfo()[0].destinations_[0].maxver_);
+
+ // Make sure we have the initial log file.
+ ASSERT_TRUE(isc::test::fileExists(TEST_LOG_NAME));
+
+ // Now generate a log we know will be large enough to force a rotation.
+ // We borrow a one argument log message for the test.
+ std::string big_arg(TEST_MAX_SIZE, 'x');
+ isc::log::Logger logger("kea");
+
+ for (int i = 1; i < TEST_MAX_VERS + 1; i++) {
+ // Output the big log and make sure we get the expected rotation file.
+ LOG_INFO(logger, DCTL_CONFIG_COMPLETE).arg(big_arg);
+ EXPECT_TRUE(isc::test::fileExists(logName(i).c_str()));
+ }
+
+ // Clean up.
+ wipeFiles();
+}
+
+// Verifies that a valid output option,'pattern' parses correctly.
+TEST_F(LoggingTest, validPattern) {
+
+ // Note the backslash must be doubled in the pattern definition.
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"pattern\": \"mylog %m\\n\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_EQ(storage->getLoggingInfo()[0].destinations_[0].pattern_,
+ std::string("mylog %m\n"));
+}
+
+// Verifies that output option,'pattern', may be an empty string
+TEST_F(LoggingTest, emptyPattern) {
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"pattern\": \"\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].pattern_.empty());
+}
+
+void testMaxSize(uint64_t maxsize_candidate, uint64_t expected_maxsize) {
+ std::string const logger(R"(
+ {
+ "loggers": [
+ {
+
+ "debuglevel": 99,
+ "name": "kea",
+ "output_options": [
+ {
+ "output": "kea.test.log",
+ "flush": true,
+ "maxsize": )" + std::to_string(maxsize_candidate) + R"(,
+ "maxver": 2
+ }
+ ],
+ "severity": "DEBUG"
+ }
+ ]
+ }
+ )");
+
+ // Create our server config container.
+ ConfigPtr server_cfg(boost::make_shared<ConfigBase>());
+
+ // LogConfigParser expects a list of loggers, so parse
+ // the JSON text and extract the "loggers" element from it
+ ConstElementPtr config(Element::fromJSON(logger));
+ config = config->get("loggers");
+
+ // Parse the config and then apply it.
+ LogConfigParser parser(server_cfg);
+ ASSERT_NO_THROW(parser.parseConfiguration(config));
+ ASSERT_NO_THROW(server_cfg->applyLoggingCfg());
+
+ EXPECT_EQ(server_cfg->getLoggingInfo()[0].destinations_[0].maxsize_,
+ expected_maxsize);
+}
+
+// Test that maxsize can be configured with high values.
+TEST_F(LoggingTest, maxsize) {
+ testMaxSize(TEST_MAX_SIZE, TEST_MAX_SIZE);
+ testMaxSize(std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max());
+ testMaxSize(std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::max());
+ testMaxSize(1000LL * std::numeric_limits<int32_t>::max(), 1000LL * std::numeric_limits<int32_t>::max());
+ testMaxSize(1000000LL * std::numeric_limits<int32_t>::max(), 1000000LL * std::numeric_limits<int32_t>::max());
+}
+
+/// @todo Add tests for malformed logging configuration
+
+/// @todo There is no easy way to test applyConfiguration() and defaultLogging().
+/// To test them, it would require instrumenting log4cplus to actually fake
+/// the logging set up. Alternatively, we could develop set of test suites
+/// that check each logging destination separately (e.g. configure log file, then
+/// check if the file is indeed created or configure stdout destination, then
+/// swap console file descriptors and check that messages are really logged.
+
+} // namespace
diff --git a/src/lib/process/tests/logging_info_unittests.cc b/src/lib/process/tests/logging_info_unittests.cc
new file mode 100644
index 0000000..388a8a5
--- /dev/null
+++ b/src/lib/process/tests/logging_info_unittests.cc
@@ -0,0 +1,208 @@
+// Copyright (C) 2014-2022 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 <process/daemon.h>
+#include <process/logging_info.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc::process;
+using namespace isc::test;
+using namespace isc::data;
+
+namespace {
+
+// Checks if two destinations can be compared for equality.
+TEST(LoggingDestination, equals) {
+ LoggingDestination dest1;
+ LoggingDestination dest2;
+
+ EXPECT_TRUE(dest1.equals(dest2));
+
+ dest1.output_ = "stderr";
+ EXPECT_FALSE(dest1.equals(dest2));
+
+ dest2.output_ = "stdout";
+ EXPECT_FALSE(dest1.equals(dest2));
+
+ dest2.output_ = "stderr";
+ EXPECT_TRUE(dest1.equals(dest2));
+
+ dest1.maxver_ = 10;
+ dest2.maxver_ = 5;
+ EXPECT_FALSE(dest1.equals(dest2));
+
+ dest2.maxver_ = 10;
+ EXPECT_TRUE(dest1.equals(dest2));
+
+ dest1.maxsize_ = 64;
+ dest2.maxsize_ = 32;
+ EXPECT_FALSE(dest1.equals(dest2));
+
+ dest1.maxsize_ = 32;
+ EXPECT_TRUE(dest1.equals(dest2));
+}
+
+/// @brief Test fixture class for testing @c LoggingInfo.
+class LoggingInfoTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ LoggingInfoTest() = default;
+
+ /// @brief Destructor
+ virtual ~LoggingInfoTest() = default;
+
+ /// @brief Setup the test.
+ virtual void SetUp() {
+ Daemon::setVerbose(false);
+ }
+
+ /// @brief Clear after the test.
+ virtual void TearDown() {
+ Daemon::setVerbose(false);
+ }
+};
+
+// Checks if default logging configuration is correct.
+TEST_F(LoggingInfoTest, defaults) {
+
+ // We now need to set the default logger explicitly.
+ // Otherwise leftovers from previous tests that use DController
+ // would leave the default logger set to TestBin.
+ Daemon::setDefaultLoggerName("kea");
+
+ LoggingInfo info_non_verbose;
+
+ // The DStubTest framework sets up the default binary name to TestBin
+ EXPECT_EQ("kea", info_non_verbose.name_);
+ EXPECT_EQ(isc::log::INFO, info_non_verbose.severity_);
+ EXPECT_EQ(0, info_non_verbose.debuglevel_);
+
+ ASSERT_EQ(1, info_non_verbose.destinations_.size());
+ EXPECT_EQ("stdout", info_non_verbose.destinations_[0].output_);
+
+ std::string header = "{\n";
+ std::string begin =
+ "\"name\": \"kea\",\n"
+ "\"output_options\": [ {\n"
+ " \"output\": \"stdout\", \"flush\": true, \"pattern\": \"\" } ],\n"
+ "\"severity\": \"";
+ std::string dbglvl = "\",\n\"debuglevel\": ";
+ std::string trailer = "\n}\n";
+ std::string expected = header + begin + "INFO" + dbglvl + "0" + trailer;
+ runToElementTest<LoggingInfo>(expected, info_non_verbose);
+
+ // Add a user context
+ std::string comment = "\"comment\": \"foo\"";
+ std::string user_context = "{ " + comment + " }";
+ std::string user_context_nl = "{\n" + comment + "\n}";
+ EXPECT_FALSE(info_non_verbose.getContext());
+ info_non_verbose.setContext(Element::fromJSON(user_context));
+ ASSERT_TRUE(info_non_verbose.getContext());
+ EXPECT_EQ(user_context, info_non_verbose.getContext()->str());
+ expected = header;
+ expected += "\"user-context\": " + user_context_nl + ",\n";
+ expected += begin + "INFO" + dbglvl + "0" + trailer;
+ runToElementTest<LoggingInfo>(expected, info_non_verbose);
+
+ Daemon::setVerbose(true);
+ LoggingInfo info_verbose;
+ EXPECT_EQ("kea", info_verbose.name_);
+ EXPECT_EQ(isc::log::DEBUG, info_verbose.severity_);
+ EXPECT_EQ(99, info_verbose.debuglevel_);
+
+ ASSERT_EQ(1, info_verbose.destinations_.size());
+ EXPECT_EQ("stdout", info_verbose.destinations_[0].output_);
+
+ EXPECT_EQ(10240000, info_verbose.destinations_[0].maxsize_);
+ EXPECT_EQ(1, info_verbose.destinations_[0].maxver_);
+
+ expected = header + begin + "DEBUG" + dbglvl + "99" + trailer;
+ runToElementTest<LoggingInfo>(expected, info_verbose);
+
+ // User comment again
+ EXPECT_FALSE(info_verbose.getContext());
+ info_verbose.setContext(Element::fromJSON(user_context));
+ ASSERT_TRUE(info_verbose.getContext());
+ EXPECT_EQ(user_context, info_verbose.getContext()->str());
+ expected = header;
+ expected += "\"user-context\": " + user_context_nl + ",\n";
+ expected += begin + "DEBUG" + dbglvl + "99" + trailer;
+ runToElementTest<LoggingInfo>(expected, info_verbose);
+}
+
+// Checks if (in)equality operators work for LoggingInfo.
+TEST_F(LoggingInfoTest, equalityOperators) {
+ LoggingInfo info1;
+ LoggingInfo info2;
+
+ // Initially, both objects are the same.
+ EXPECT_TRUE(info1 == info2);
+
+ // Differ by name.
+ info1.name_ = "foo";
+ info2.name_ = "bar";
+ EXPECT_FALSE(info1 == info2);
+ EXPECT_TRUE(info1 != info2);
+
+ // Names equal.
+ info2.name_ = "foo";
+ EXPECT_TRUE(info1 == info2);
+ EXPECT_FALSE(info1 != info2);
+
+ // Differ by severity.
+ info1.severity_ = isc::log::DEBUG;
+ info2.severity_ = isc::log::INFO;
+ EXPECT_FALSE(info1 == info2);
+ EXPECT_TRUE(info1 != info2);
+
+ // Severities equal.
+ info2.severity_ = isc::log::DEBUG;
+ EXPECT_TRUE(info1 == info2);
+ EXPECT_FALSE(info1 != info2);
+
+ // Differ by debug level.
+ info1.debuglevel_ = 99;
+ info2.debuglevel_ = 1;
+ EXPECT_FALSE(info1 == info2);
+ EXPECT_TRUE(info1 != info2);
+
+ // Debug level equal.
+ info2.debuglevel_ = 99;
+ EXPECT_TRUE(info1 == info2);
+ EXPECT_FALSE(info1 != info2);
+
+ // Create two different destinations.
+ LoggingDestination dest1;
+ LoggingDestination dest2;
+ dest1.output_ = "foo";
+ dest2.output_ = "bar";
+
+ // Push destinations in some order to info1.
+ info1.destinations_.push_back(dest1);
+ info1.destinations_.push_back(dest2);
+
+ // Push in reverse order to info2. Order shouldn't matter.
+ info2.destinations_.push_back(dest2);
+ info2.destinations_.push_back(dest1);
+
+ EXPECT_TRUE(info1 == info2);
+ EXPECT_FALSE(info1 != info2);
+
+ // Change one of the destinations.
+ LoggingDestination dest3;
+ dest3.output_ = "foobar";
+
+ info2.destinations_[2] = dest3;
+
+ // The should now be unequal.
+ EXPECT_FALSE(info1 == info2);
+ EXPECT_TRUE(info1 != info2);
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/process/tests/run_unittests.cc b/src/lib/process/tests/run_unittests.cc
new file mode 100644
index 0000000..a7c1f68
--- /dev/null
+++ b/src/lib/process/tests/run_unittests.cc
@@ -0,0 +1,24 @@
+// Copyright (C) 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 <log/logger_support.h>
+#include <process/d_log.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ // Override --localstatedir value for PID files
+ setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/process/testutils/Makefile.am b/src/lib/process/testutils/Makefile.am
new file mode 100644
index 0000000..147957e
--- /dev/null
+++ b/src/lib/process/testutils/Makefile.am
@@ -0,0 +1,27 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libprocesstest.la
+
+libprocesstest_la_SOURCES = d_test_stubs.cc d_test_stubs.h
+
+libprocesstest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libprocesstest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libprocesstest_la_LDFLAGS = $(AM_LDFLAGS)
+
+libprocesstest_la_LIBADD = $(top_builddir)/src/lib/process/libkea-process.la
+libprocesstest_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libprocesstest_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libprocesstest_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libprocesstest_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+
+endif
diff --git a/src/lib/process/testutils/Makefile.in b/src/lib/process/testutils/Makefile.in
new file mode 100644
index 0000000..bca5d20
--- /dev/null
+++ b/src/lib/process/testutils/Makefile.in
@@ -0,0 +1,870 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/process/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libprocesstest_la_DEPENDENCIES = $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__libprocesstest_la_SOURCES_DIST = d_test_stubs.cc d_test_stubs.h
+@HAVE_GTEST_TRUE@am_libprocesstest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libprocesstest_la-d_test_stubs.lo
+libprocesstest_la_OBJECTS = $(am_libprocesstest_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 =
+libprocesstest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libprocesstest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libprocesstest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libprocesstest_la_rpath =
+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)/libprocesstest_la-d_test_stubs.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 = $(libprocesstest_la_SOURCES)
+DIST_SOURCES = $(am__libprocesstest_la_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ -DDATABASE_SCRIPTS_DIR=\"$(abs_top_srcdir)/src/share/database/scripts\" \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libprocesstest.la
+@HAVE_GTEST_TRUE@libprocesstest_la_SOURCES = d_test_stubs.cc d_test_stubs.h
+@HAVE_GTEST_TRUE@libprocesstest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libprocesstest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libprocesstest_la_LDFLAGS = $(AM_LDFLAGS)
+@HAVE_GTEST_TRUE@libprocesstest_la_LIBADD = $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \
+@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+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/process/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/process/testutils/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libprocesstest.la: $(libprocesstest_la_OBJECTS) $(libprocesstest_la_DEPENDENCIES) $(EXTRA_libprocesstest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libprocesstest_la_LINK) $(am_libprocesstest_la_rpath) $(libprocesstest_la_OBJECTS) $(libprocesstest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libprocesstest_la-d_test_stubs.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)$(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 $@ $<
+
+libprocesstest_la-d_test_stubs.lo: d_test_stubs.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libprocesstest_la_CPPFLAGS) $(CPPFLAGS) $(libprocesstest_la_CXXFLAGS) $(CXXFLAGS) -MT libprocesstest_la-d_test_stubs.lo -MD -MP -MF $(DEPDIR)/libprocesstest_la-d_test_stubs.Tpo -c -o libprocesstest_la-d_test_stubs.lo `test -f 'd_test_stubs.cc' || echo '$(srcdir)/'`d_test_stubs.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libprocesstest_la-d_test_stubs.Tpo $(DEPDIR)/libprocesstest_la-d_test_stubs.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d_test_stubs.cc' object='libprocesstest_la-d_test_stubs.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) $(libprocesstest_la_CPPFLAGS) $(CPPFLAGS) $(libprocesstest_la_CXXFLAGS) $(CXXFLAGS) -c -o libprocesstest_la-d_test_stubs.lo `test -f 'd_test_stubs.cc' || echo '$(srcdir)/'`d_test_stubs.cc
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libprocesstest_la-d_test_stubs.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-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)/libprocesstest_la-d_test_stubs.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/process/testutils/d_test_stubs.cc b/src/lib/process/testutils/d_test_stubs.cc
new file mode 100644
index 0000000..6500855
--- /dev/null
+++ b/src/lib/process/testutils/d_test_stubs.cc
@@ -0,0 +1,338 @@
+// Copyright (C) 2013-2023 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 <asiolink/io_service.h>
+#include <process/d_log.h>
+#include <process/testutils/d_test_stubs.h>
+#include <process/daemon.h>
+#include <cc/command_interpreter.h>
+#include <functional>
+
+using namespace boost::asio;
+
+namespace isc {
+namespace process {
+
+// Initialize the static failure flag.
+SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
+
+DStubProcess::DStubProcess(const char* name, asiolink::IOServicePtr io_service)
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
+};
+
+
+void
+DStubProcess::init() {
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessInit)) {
+ // Simulates a failure to instantiate the process.
+ isc_throw(DProcessBaseError, "DStubProcess simulated init() failure");
+ }
+};
+
+void
+DStubProcess::run() {
+ // Until shut down or an fatal error occurs, wait for and
+ // execute a single callback. This is a preliminary implementation
+ // that is likely to evolve as development progresses.
+ // To use run(), the "managing" layer must issue an io_service::stop
+ // or the call to run will continue to block, and shutdown will not
+ // occur.
+ asiolink::IOServicePtr& io_service = getIoService();
+ while (!shouldShutdown()) {
+ try {
+ io_service->run_one();
+ } catch (const std::exception& ex) {
+ isc_throw (DProcessBaseError,
+ std::string("Process run method failed: ") + ex.what());
+ }
+ }
+};
+
+isc::data::ConstElementPtr
+DStubProcess::shutdown(isc::data::ConstElementPtr /* args */) {
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessShutdown)) {
+ // Simulates a failure during shutdown process.
+ isc_throw(DProcessBaseError, "DStubProcess simulated shutdown failure");
+ }
+
+ setShutdownFlag(true);
+ stopIOService();
+ return (isc::config::createAnswer(isc::config::CONTROL_RESULT_SUCCESS,
+ "Shutdown initiated."));
+}
+
+isc::data::ConstElementPtr
+DStubProcess::configure(isc::data::ConstElementPtr config_set, bool check_only) {
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
+ // Simulates a process configure failure.
+ return (isc::config::createAnswer(isc::config::CONTROL_RESULT_ERROR,
+ "Simulated process configuration error."));
+ }
+
+ return (getCfgMgr()->simpleParseConfig(config_set, check_only));
+}
+
+DStubProcess::~DStubProcess() {
+ Daemon::setVerbose(false);
+};
+
+//************************** DStubController *************************
+
+// Define custom command line option command supported by DStubController.
+const char* DStubController::stub_option_x_ = "x";
+
+/// @brief Defines the app name used to construct the controller
+const char* DStubController::stub_app_name_ = "TestService";
+
+/// @brief Defines the bin name used to construct the controller
+const char* DStubController::stub_bin_name_ = "TestBin";
+
+
+DControllerBasePtr&
+DStubController::instance() {
+ // If the singleton hasn't been created, do it now.
+ if (!getController()) {
+ DControllerBasePtr p(new DStubController());
+ setController(p);
+ }
+
+ return (getController());
+}
+
+DStubController::DStubController()
+ : DControllerBase(stub_app_name_, stub_bin_name_),
+ processed_signals_(), record_signal_only_(false), use_alternate_parser_(false) {
+}
+
+bool
+DStubController::customOption(int option, char* /* optarg */)
+{
+ // Check for the custom option supported by DStubController.
+ if (static_cast<char>(option) == *stub_option_x_) {
+ return (true);
+ }
+
+ return (false);
+}
+
+DProcessBase* DStubController::createProcess() {
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessException)) {
+ // Simulates a failure to instantiate the process due to exception.
+ throw std::runtime_error("SimFailure::ftCreateProcess");
+ }
+
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessNull)) {
+ // Simulates a failure to instantiate the process.
+ return (NULL);
+ }
+
+ // This should be a successful instantiation.
+ return (new DStubProcess(getAppName().c_str(), getIOService()));
+}
+
+const std::string DStubController::getCustomOpts() const {
+ // Return the "list" of custom options supported by DStubController.
+ return (std::string(stub_option_x_));
+}
+
+void
+DStubController::processSignal(int signum){
+ processed_signals_.push_back(signum);
+ if (record_signal_only_) {
+ return;
+ }
+
+ DControllerBase::processSignal(signum);
+}
+
+isc::data::ConstElementPtr
+DStubController::parseFile(const std::string& /*file_name*/) {
+ isc::data::ConstElementPtr elements;
+ if (use_alternate_parser_) {
+ std::ostringstream os;
+
+ os << "{ \"" << getController()->getAppName()
+ << "\": " << std::endl;
+ os << "{ \"string_test\": \"alt value\" } ";
+ os << " } " << std::endl;
+ elements = isc::data::Element::fromJSON(os.str());
+ }
+
+ return (elements);
+}
+
+DStubController::~DStubController() {
+}
+
+//************************** DControllerTest *************************
+
+void
+DControllerTest::writeFile(const std::string& content,
+ const std::string& module_name) {
+ std::ofstream out(CFG_TEST_FILE, std::ios::trunc);
+ ASSERT_TRUE(out.is_open());
+
+ out << "{ \"" << (!module_name.empty() ? module_name
+ : getController()->getAppName())
+ << "\": " << std::endl;
+
+ out << content;
+ out << " } " << std::endl;
+ out.close();
+}
+
+void
+DControllerTest::timedWriteCallback() {
+ writeFile(new_cfg_content_);
+}
+
+void
+DControllerTest::scheduleTimedWrite(const std::string& config,
+ int write_time_ms) {
+ new_cfg_content_ = config;
+ write_timer_.reset(new asiolink::IntervalTimer(*getIOService()));
+ write_timer_->setup(std::bind(&DControllerTest::timedWriteCallback, this),
+ write_time_ms, asiolink::IntervalTimer::ONE_SHOT);
+}
+
+void
+DControllerTest::runWithConfig(const std::string& config, int run_time_ms,
+ time_duration& elapsed_time) {
+ // Create the config file.
+ writeFile(config);
+
+ // Shutdown (without error) after runtime.
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genShutdownCallback, run_time_ms);
+
+ // Record start time, and invoke launch().
+ // We catch and rethrow to allow testing error scenarios.
+ ptime start = microsec_clock::universal_time();
+ try {
+ // Set up valid command line arguments
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>(DControllerTest::CFG_TEST_FILE),
+ const_cast<char*>("-d") };
+ launch(4, argv);
+ } catch (...) {
+ // calculate elapsed time, then rethrow it
+ elapsed_time = microsec_clock::universal_time() - start;
+ throw;
+ }
+
+ elapsed_time = microsec_clock::universal_time() - start;
+}
+
+void
+DControllerTest::runWithConfig(const std::string& config, int run_time_ms,
+ const TestCallback& callback,
+ time_duration& elapsed_time) {
+ // Create the config file.
+ writeFile(config);
+
+ // Shutdown (without error) after runtime.
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup([&] { callback(); genShutdownCallback(); }, run_time_ms);
+
+ // Record start time, and invoke launch().
+ // We catch and rethrow to allow testing error scenarios.
+ ptime start = microsec_clock::universal_time();
+ try {
+ // Set up valid command line arguments
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>(DControllerTest::CFG_TEST_FILE),
+ const_cast<char*>("-d") };
+ launch(4, argv);
+ } catch (...) {
+ // calculate elapsed time, then rethrow it
+ elapsed_time = microsec_clock::universal_time() - start;
+ throw;
+ }
+
+ elapsed_time = microsec_clock::universal_time() - start;
+}
+
+DProcessBasePtr
+DControllerTest:: getProcess() {
+ DProcessBasePtr p;
+ if (getController()) {
+ p = getController()->getProcess();
+ }
+ return (p);
+}
+
+DCfgMgrBasePtr
+DControllerTest::getCfgMgr() {
+ DCfgMgrBasePtr p;
+ if (getProcess()) {
+ p = getProcess()->getCfgMgr();
+ }
+
+ return (p);
+}
+
+ConfigPtr
+DControllerTest::getContext() {
+ ConfigPtr p;
+ if (getCfgMgr()) {
+ p = getCfgMgr()->getContext();
+ }
+
+ return (p);
+}
+
+// Initialize controller wrapper's static instance getter member.
+DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
+
+/// @brief Defines the name of the configuration file to use
+const char* DControllerTest::CFG_TEST_FILE = "d2-test-config.json";
+
+//************************** DStubContext *************************
+
+DStubContext::DStubContext() {
+}
+
+DStubContext::~DStubContext() {
+}
+
+ConfigPtr
+DStubContext::clone() {
+ return (ConfigPtr(new DStubContext(*this)));
+}
+
+DStubContext::DStubContext(const DStubContext& rhs): ConfigBase(rhs) {
+}
+
+isc::data::ElementPtr
+DStubContext::toElement() const {
+ return (isc::data::Element::createMap());
+}
+
+//************************** DStubCfgMgr *************************
+
+DStubCfgMgr::DStubCfgMgr()
+ : DCfgMgrBase(ConfigPtr(new DStubContext())) {
+}
+
+DStubCfgMgr::~DStubCfgMgr() {
+}
+
+ConfigPtr
+DStubCfgMgr::createNewContext() {
+ return (ConfigPtr (new DStubContext()));
+}
+
+isc::data::ConstElementPtr
+DStubCfgMgr::parse(isc::data::ConstElementPtr /*config*/, bool /*check_only*/) {
+ return (isc::config::createAnswer(isc::config::CONTROL_RESULT_SUCCESS,
+ "It all went fine. I promise"));
+}
+
+} // namespace isc::process
+} // namespace isc
diff --git a/src/lib/process/testutils/d_test_stubs.h b/src/lib/process/testutils/d_test_stubs.h
new file mode 100644
index 0000000..9881ee4
--- /dev/null
+++ b/src/lib/process/testutils/d_test_stubs.h
@@ -0,0 +1,753 @@
+// Copyright (C) 2013-2021 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 D_TEST_STUBS_H
+#define D_TEST_STUBS_H
+
+#include <asiolink/interval_timer.h>
+
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+
+#include <log/logger_support.h>
+
+#include <process/d_controller.h>
+#include <process/d_cfg_mgr.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+using namespace boost::posix_time;
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+namespace isc {
+namespace process {
+
+/// @brief Class is used to set a globally accessible value that indicates
+/// a specific type of failure to simulate. Test derivations of base classes
+/// can exercise error handling code paths by testing for specific SimFailure
+/// values at the appropriate places and then causing the error to "occur".
+/// The class consists of an enumerated set of failures, and static methods
+/// for getting, setting, and testing the current value.
+class SimFailure {
+public:
+ enum FailureType {
+ ftUnknown = -1,
+ ftNoFailure = 0,
+ ftCreateProcessException,
+ ftCreateProcessNull,
+ ftProcessInit,
+ ftProcessConfigure,
+ ftProcessShutdown,
+ ftElementBuild,
+ ftElementCommit,
+ ftElementUnknown
+ };
+
+ /// @brief Sets the SimFailure value to the given value.
+ ///
+ /// @param value is the new value to assign to the global value.
+ static void set(enum FailureType value) {
+ failure_type_ = value;
+ }
+
+ /// @brief Gets the current global SimFailure value
+ ///
+ /// @return returns the current SimFailure value
+ static enum FailureType get() {
+ return (failure_type_);
+ }
+
+ /// @brief One-shot test of the SimFailure value. If the global
+ /// SimFailure value is equal to the given value, clear the global
+ /// value and return true. This makes it convenient for code to
+ /// test and react without having to explicitly clear the global
+ /// value.
+ ///
+ /// @param value is the value against which the global value is
+ /// to be compared.
+ ///
+ /// @return returns true if current SimFailure value matches the
+ /// given value.
+ static bool shouldFailOn(enum FailureType value) {
+ if (failure_type_ == value) {
+ clear();
+ return (true);
+ }
+
+ return (false);
+ }
+
+ /// @brief Resets the failure type to none.
+ static void clear() {
+ failure_type_ = ftNoFailure;
+ }
+
+ /// @brief Static value for holding the failure type to simulate.
+ static enum FailureType failure_type_;
+};
+
+/// @brief Test Derivation of the DProcessBase class.
+///
+/// This class is used primarily to server as a test process class for testing
+/// DControllerBase. It provides minimal, but sufficient implementation to
+/// test the majority of DControllerBase functionality.
+class DStubProcess : public DProcessBase {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ DStubProcess(const char* name, asiolink::IOServicePtr io_service);
+
+ /// @brief Invoked after process instantiation to perform initialization.
+ /// This implementation supports simulating an error initializing the
+ /// process by throwing a DProcessBaseError if SimFailure is set to
+ /// ftProcessInit.
+ virtual void init();
+
+ /// @brief Implements the process's event loop.
+ /// This implementation is quite basic, surrounding calls to
+ /// io_service->runOne() with a test of the shutdown flag. Once invoked,
+ /// the method will continue until the process itself is exiting due to a
+ /// request to shutdown or some anomaly forces an exit.
+ /// @return returns 0 upon a successful, "normal" termination, non-zero to
+ /// indicate an abnormal termination.
+ virtual void run();
+
+ /// @brief Implements the process shutdown procedure.
+ ///
+ /// This sets the instance shutdown flag monitored by run() and stops
+ /// the IO service.
+ virtual isc::data::ConstElementPtr shutdown(isc::data::ConstElementPtr);
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This implementation fails if SimFailure is set to ftProcessConfigure.
+ /// Otherwise it will complete successfully. It does not check the content
+ /// of the inbound configuration.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @param check_only true if configuration is to be verified only, not applied
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr
+ configure(isc::data::ConstElementPtr config_set, bool check_only);
+
+ /// @brief Returns configuration summary in the textual format.
+ ///
+ /// @return Always an empty string.
+ virtual std::string getConfigSummary(const uint32_t) {
+ return ("");
+ }
+
+ /// @brief Destructor
+ virtual ~DStubProcess();
+};
+
+
+/// @brief Test Derivation of the DControllerBase class.
+///
+/// DControllerBase is an abstract class and therefore requires a derivation
+/// for testing. It allows testing the majority of the base class code
+/// without polluting production derivations (e.g. D2Process). It uses
+/// DStubProcess as its application process class. It is a full enough
+/// implementation to support running both stand alone and integrated.
+class DStubController : public DControllerBase {
+public:
+ /// @brief Static singleton instance method. This method returns the
+ /// base class singleton instance member. It instantiates the singleton
+ /// and sets the base class instance member upon first invocation.
+ ///
+ /// @return returns a pointer reference to the singleton instance.
+ static DControllerBasePtr& instance();
+
+ /// @brief Defines a custom command line option supported by
+ /// DStubController.
+ static const char* stub_option_x_;
+
+ /// @brief Defines the app name used to construct the controller
+ static const char* stub_app_name_;
+
+ /// @brief Defines the executable name used to construct the controller
+ static const char* stub_bin_name_;
+
+ /// @brief Gets the list of signals that have been caught and processed.
+ std::vector<int>& getProcessedSignals() {
+ return (processed_signals_);
+ }
+
+ /// @brief Deals with other (i.e. not application name) global objects.
+ using DControllerBase::handleOtherObjects;
+
+ /// @brief Controls whether signals are processed in full or merely
+ /// recorded.
+ ///
+ /// If true, signal handling will stop after recording the signal.
+ /// Otherwise the base class signal handler,
+ /// DControllerBase::processSignals will also be invoked. This switch is
+ /// useful for ensuring that IOSignals are delivered as expected without
+ /// incurring the full impact such as reconfiguring or shutting down.
+ ///
+ /// @param value boolean which if true enables record-only behavior
+ void recordSignalOnly(bool value) {
+ record_signal_only_ = value;
+ }
+
+ /// @brief Determines if parseFile() implementation is used
+ ///
+ /// If true, parseFile() will return a Map of elements with fixed content,
+ /// mimicking a controller which is using alternate JSON parsing.
+ /// If false, parseFile() will return an empty pointer mimicking a
+ /// controller which is using original JSON parsing supplied by the
+ /// Element class.
+ ///
+ /// @param value boolean which if true enables record-only behavior
+ void useAlternateParser(bool value) {
+ use_alternate_parser_ = value;
+ }
+
+protected:
+ /// @brief Handles additional command line options that are supported
+ /// by DStubController. This implementation supports an option "-x".
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hyphen(s)
+ /// @optarg optarg is the argument value (if one) associated with the option
+ ///
+ /// @return returns true if the option is "x", otherwise ti returns false.
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Instantiates an instance of DStubProcess.
+ ///
+ /// This implementation will fail if SimFailure is set to
+ /// ftCreateProcessException OR ftCreateProcessNull.
+ ///
+ /// @return returns a pointer to the new process instance (DProcessBase*)
+ /// or NULL if SimFailure is set to ftCreateProcessNull.
+ /// @throw throws std::runtime_error if SimFailure is set to
+ /// ftCreateProcessException.
+ virtual DProcessBase* createProcess();
+
+ /// @brief Provides a string of the additional command line options
+ /// supported by DStubController. DStubController supports one
+ /// addition option, stub_option_x_.
+ ///
+ /// @return returns a string containing the option letters.
+ virtual const std::string getCustomOpts() const;
+
+ /// @brief Application-level "signal handler"
+ ///
+ /// Overrides the base class implementation such that this method
+ /// is invoked each time an IOSignal is processed. It records the
+ /// signal received and unless we are in record-only behavior, it
+ /// in invokes the base class implementation.
+ ///
+ /// @param signum OS signal value received
+ virtual void processSignal(int signum);
+
+ /// @brief Provides alternate parse file implementation
+ ///
+ /// Overrides the base class implementation to mimic controllers which
+ /// implement alternate file parsing. If enabled via useAlternateParser()
+ /// the method will return a fixed map of elements reflecting the following
+ /// JSON:
+ ///
+ /// @code
+ /// { "<name>getController()->getAppName()" :
+ /// { "string_test": "alt value" };
+ /// }
+ ///
+ /// @endcode
+ ///
+ /// where <name> is getController()->getAppName()
+ ///
+ /// otherwise it return an empty pointer.
+ virtual isc::data::ConstElementPtr parseFile(const std::string&);
+
+public:
+
+ /// @brief Provides additional extended version text
+ ///
+ /// Overrides the base class implementation so we can
+ /// verify the getting the extended version text
+ /// contains derivation specific contributions.
+ virtual std::string getVersionAddendum() {
+ return ("StubController Version Text");
+ }
+
+private:
+ /// @brief Constructor is private to protect singleton integrity.
+ DStubController();
+
+ /// @brief Vector to record the signal values received.
+ std::vector<int> processed_signals_;
+
+ /// @brief Boolean for controlling if signals are merely recorded.
+ bool record_signal_only_;
+
+ /// @brief Boolean for controlling if parseFile is "implemented"
+ bool use_alternate_parser_;
+
+public:
+ virtual ~DStubController();
+};
+
+/// @brief Defines a pointer to a DStubController.
+typedef boost::shared_ptr<DStubController> DStubControllerPtr;
+
+/// @brief Abstract Test fixture class that wraps a DControllerBase. This class
+/// is a friend class of DControllerBase which allows it access to class
+/// content to facilitate testing. It provides numerous wrapper methods for
+/// the protected and private methods and member of the base class.
+class DControllerTest : public ::testing::Test {
+public:
+
+ /// @brief Defines a function pointer for controller singleton fetchers.
+ typedef DControllerBasePtr& (*InstanceGetter)();
+
+ /// @brief Static storage of the controller class's singleton fetcher.
+ /// We need this this statically available for callbacks.
+ static InstanceGetter instanceGetter_;
+
+ /// @brief Constructor
+ ///
+ /// @param instance_getter is a function pointer to the static instance
+ /// method of the DControllerBase derivation under test.
+ DControllerTest(InstanceGetter instance_getter)
+ : write_timer_(), new_cfg_content_() {
+ // Set the static fetcher member, then invoke it via getController.
+ // This ensures the singleton is instantiated.
+ instanceGetter_ = instance_getter;
+ getController();
+ }
+
+ /// @brief Destructor
+ /// Note the controller singleton is destroyed. This is essential to ensure
+ /// a clean start between tests.
+ virtual ~DControllerTest() {
+ // Some unit tests update the logging configuration which has a side
+ // effect that all subsequent tests print the output to stdout. This
+ // is to ensure that the logging settings are back to default.
+ isc::log::setDefaultLoggingOutput();
+
+ if (write_timer_) {
+ write_timer_->cancel();
+ }
+
+ getController().reset();
+ static_cast<void>(remove(CFG_TEST_FILE));
+ }
+
+ /// @brief Convenience method that destructs and then recreates the
+ /// controller singleton under test. This is handy for tests within
+ /// tests.
+ void resetController() {
+ getController().reset();
+ getController();
+ }
+
+ /// @brief Static method which returns the instance of the controller
+ /// under test.
+ /// @return returns a reference to the controller instance.
+ static DControllerBasePtr& getController() {
+ return ((*instanceGetter_)());
+ }
+
+ /// @brief Returns true if the Controller's app name matches the
+ /// given value.
+ ///
+ /// @param should_be is the value to compare against.
+ ///
+ /// @return returns true if the values are equal.
+ bool checkAppName(const std::string& should_be) {
+ return (getController()->getAppName().compare(should_be) == 0);
+ }
+
+ /// @brief Returns true if the Controller's service name matches the
+ /// given value.
+ ///
+ /// @param should_be is the value to compare against.
+ ///
+ /// @return returns true if the values are equal.
+ bool checkBinName(const std::string& should_be) {
+ return (getController()->getBinName().compare(should_be) == 0);
+ }
+
+ /// @brief Tests the existence of the Controller's application process.
+ ///
+ /// @return returns true if the process instance exists.
+ bool checkProcess() {
+ return (getController()->process_.get() != 0);
+ }
+
+ /// @brief Tests the existence of the Controller's IOService.
+ ///
+ /// @return returns true if the IOService exists.
+ bool checkIOService() {
+ return (getController()->io_service_.get() != 0);
+ }
+
+ /// @brief Gets the Controller's IOService.
+ ///
+ /// @return returns a reference to the IOService
+ asiolink::IOServicePtr& getIOService() {
+ return (getController()->io_service_);
+ }
+
+ /// @brief Compares verbose flag with the given value.
+ ///
+ /// @param value
+ ///
+ /// @return returns true if the verbose flag is equal to the given value.
+ bool checkVerbose(bool value) {
+ return (getController()->isVerbose() == value);
+ }
+
+ /// @brief Compares configuration file name with the given value.
+ ///
+ /// @param value file name to compare against
+ ///
+ /// @return returns true if the verbose flag is equal to the given value.
+ bool checkConfigFileName(const std::string& value) {
+ return (getController()->getConfigFile() == value);
+ }
+
+ /// @Wrapper to invoke the Controller's parseArgs method. Please refer to
+ /// DControllerBase::parseArgs for details.
+ void parseArgs(int argc, char* argv[]) {
+ getController()->parseArgs(argc, argv);
+ }
+
+ /// @Wrapper to invoke the Controller's init method. Please refer to
+ /// DControllerBase::init for details.
+ void initProcess() {
+ getController()->initProcess();
+ }
+
+ /// @Wrapper to invoke the Controller's launch method. Please refer to
+ /// DControllerBase::launch for details.
+ void launch(int argc, char* argv[]) {
+ optind = 1;
+ getController()->launch(argc, argv, true);
+ }
+
+ /// @Wrapper to invoke the Controller's updateConfig method. Please
+ /// refer to DControllerBase::updateConfig for details.
+ isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
+ new_config) {
+ return (getController()->updateConfig(new_config));
+ }
+
+ /// @Wrapper to invoke the Controller's checkConfig method. Please
+ /// refer to DControllerBase::checkConfig for details.
+ isc::data::ConstElementPtr checkConfig(isc::data::ConstElementPtr
+ new_config) {
+ return (getController()->checkConfig(new_config));
+ }
+
+ /// @brief Callback that will generate shutdown command via the
+ /// command callback function.
+ static void genShutdownCallback() {
+ isc::data::ElementPtr arg_set;
+ getController()->shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
+ }
+
+ /// @brief Callback that throws an exception.
+ static void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+
+ /// @brief writes specified content to a well known file
+ ///
+ /// Writes given JSON content to CFG_TEST_FILE. It will wrap the
+ /// content within a JSON element whose name is equal to the controller's
+ /// app name or the given module name if not blank:
+ ///
+ /// @code
+ /// { "<app_name>" : <content> }
+ /// @endcode
+ ///
+ /// suffix the content within a JSON element with the given module
+ /// name or wrapped by a JSON
+ /// element . Tests will
+ /// attempt to read that file.
+ ///
+ /// @param content JSON text to be written to file
+ /// @param module_name content content to be written to file
+ void writeFile(const std::string& content,
+ const std::string& module_name = "");
+
+ /// @brief Method used as timer callback to invoke writeFile.
+ ///
+ /// Wraps a call to writeFile passing in new_cfg_content_. This allows
+ /// the method to be bound as an IntervalTimer callback.
+ virtual void timedWriteCallback();
+
+ /// @brief Schedules the given content to overwrite the config file.
+ ///
+ /// Creates a one-shot IntervalTimer whose callback will overwrite the
+ /// configuration with the given content. This allows the configuration
+ /// file to replaced write_time_ms after DControllerBase::launch() has
+ /// invoked runProcess().
+ ///
+ /// @param config JSON string containing the desired content for the config
+ /// file.
+ /// @param write_time_ms time in milliseconds to delay before writing the
+ /// file.
+ void scheduleTimedWrite(const std::string& config, int write_time_ms);
+
+ /// @brief Convenience method for invoking standard, valid launch
+ ///
+ /// This method sets up a timed run of the DController::launch. It does
+ /// the following:
+ /// - It creates command line argument variables argc/argv
+ /// - Invokes writeFile to create the config file with the given content
+ /// - Schedules a shutdown time timer to call DController::executeShutdown
+ /// after the interval
+ /// - Records the start time
+ /// - Invokes DController::launch() with the command line arguments
+ /// - After launch returns, it calculates the elapsed time and returns it
+ ///
+ /// @param config configuration file content to write before calling launch
+ /// @param run_time_ms maximum amount of time to allow runProcess() to
+ /// continue.
+ /// @param[out] elapsed_time the actual time in ms spent in launch().
+ void runWithConfig(const std::string& config, int run_time_ms,
+ time_duration& elapsed_time);
+
+ /// @brief Type of testing callbacks
+ typedef std::function<void()> TestCallback;
+
+ /// @brief Convenience method for invoking standard, valid launch
+ /// with a testing callback
+ ///
+ /// This method sets up a timed run of the DController::launch. It does
+ /// the following:
+ /// - It creates command line argument variables argc/argv
+ /// - Invokes writeFile to create the config file with the given content
+ /// - Schedules a shutdown time timer to call DController::executeShutdown
+ /// after the interval
+ /// - Records the start time
+ /// - Invokes DController::launch() with the command line arguments
+ /// - After launch returns, it calculates the elapsed time and returns it
+ ///
+ /// @note the callback is called before the shutdown and MUST NOT throw
+ /// @param config configuration file content to write before calling launch
+ /// @param run_time_ms maximum amount of time to allow runProcess() to
+ /// continue.
+ /// @param callback testing callback of TestCallback type
+ /// @param[out] elapsed_time the actual time in ms spent in launch().
+ void runWithConfig(const std::string& config, int run_time_ms,
+ const TestCallback& callback,
+ time_duration& elapsed_time);
+
+ /// @brief Fetches the controller's process
+ ///
+ /// @return A pointer to the process which may be null if it has not yet
+ /// been instantiated.
+ DProcessBasePtr getProcess();
+
+ /// @brief Fetches the process's configuration manager
+ ///
+ /// @return A pointer to the manager which may be null if it has not yet
+ /// been instantiated.
+ DCfgMgrBasePtr getCfgMgr();
+
+ /// @brief Fetches the configuration manager's context
+ ///
+ /// @return A pointer to the context which may be null if it has not yet
+ /// been instantiated.
+ ConfigPtr getContext();
+
+ /// @brief Timer used for delayed configuration file writing.
+ asiolink::IntervalTimerPtr write_timer_;
+
+ /// @brief String which contains the content delayed file writing will use.
+ std::string new_cfg_content_;
+
+ /// @brief Name of a config file used during tests
+ static const char* CFG_TEST_FILE;
+};
+
+/// @brief Test Derivation of the ConfigBase class.
+///
+/// This class is used to test basic functionality of configuration context.
+/// It adds an additional storage container "extra values" to mimic an
+/// application extension of configuration storage. This permits testing that
+/// both the base class content as well as the application content is
+/// correctly copied during cloning. This is vital to configuration backup
+/// and rollback during configuration parsing.
+class DStubContext : public ConfigBase {
+public:
+
+ /// @brief Constructor
+ DStubContext();
+
+ /// @brief Destructor
+ virtual ~DStubContext();
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual ConfigPtr clone();
+
+protected:
+ /// @brief Copy constructor
+ DStubContext(const DStubContext& rhs);
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DStubContext& operator=(const DStubContext& rhs);
+
+ /// @brief Unparse a configuration object
+ ///
+ /// @return a pointer to a configuration
+ virtual isc::data::ElementPtr toElement() const;
+};
+
+/// @brief Defines a pointer to DStubContext.
+typedef boost::shared_ptr<DStubContext> DStubContextPtr;
+
+/// @brief Test Derivation of the DCfgMgrBase class.
+///
+/// This class is used to test basic functionality of configuration management.
+/// It supports the following configuration elements:
+///
+/// "bool_test" - Boolean element, tests parsing and committing a boolean
+/// configuration parameter.
+/// "uint32_test" - Uint32 element, tests parsing and committing a uint32_t
+/// configuration parameter.
+/// "string_test" - String element, tests parsing and committing a string
+/// configuration parameter.
+/// "extra_test" - "Extra" element, tests parsing and committing an extra
+/// configuration parameter. (This is used to demonstrate
+/// derivation's addition of storage to configuration context.
+///
+/// It also keeps track of the element ids that are parsed in the order they
+/// are parsed. This is used to test ordered and non-ordered parsing.
+class DStubCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ DStubCfgMgr();
+
+ /// @brief Destructor
+ virtual ~DStubCfgMgr();
+
+ /// @brief Pretends to parse the config
+ ///
+ /// This method pretends to parse the configuration specified on input
+ /// and returns a positive answer. The check_only flag is currently ignored.
+ ///
+ /// @param config configuration specified
+ /// @param check_only whether it's real configuration (false) or just
+ /// configuration check (true)
+ /// @return always positive answer
+ ///
+ isc::data::ConstElementPtr
+ parse(isc::data::ConstElementPtr config, bool check_only);
+
+ /// @brief Returns a summary of the configuration in the textual format.
+ ///
+ /// @return Always an empty string.
+ virtual std::string getConfigSummary(const uint32_t) {
+ return ("");
+ }
+
+ /// @todo
+ virtual ConfigPtr createNewContext();
+};
+
+/// @brief Defines a pointer to DStubCfgMgr.
+typedef boost::shared_ptr<DStubCfgMgr> DStubCfgMgrPtr;
+
+/// @brief Test fixture base class for any fixtures which test parsing.
+/// It provides methods for converting JSON strings to configuration element
+/// sets and checking parse results
+class ConfigParseTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ConfigParseTest(){
+ }
+
+ /// @brief Destructor
+ ~ConfigParseTest() {
+ }
+
+ /// @brief Converts a given JSON string into an Element set and stores the
+ /// result the member variable, config_set_.
+ ///
+ /// @param json_text contains the configuration text in JSON format to
+ /// convert.
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult fromJSON(const std::string& json_text) {
+ try {
+ config_set_ = isc::data::Element::fromJSON(json_text);
+ } catch (const isc::Exception &ex) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "JSON text failed to parse:"
+ << ex.what()));
+ }
+
+ return (::testing::AssertionSuccess());
+ }
+
+ /// @brief Compares the status in the parse result stored in member
+ /// variable answer_ to a given value.
+ ///
+ /// @param should_be is an integer against which to compare the status.
+ ///
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult checkAnswer(int should_be) {
+ return (checkAnswer(answer_, should_be));
+ }
+
+ /// @brief Compares the status in the given parse result to a given value.
+ ///
+ /// @param answer Element set containing an integer response and string
+ /// comment.
+ /// @param should_be is an integer against which to compare the status.
+ ///
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult checkAnswer(isc::data::ConstElementPtr answer,
+ int should_be) {
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+ if (rcode == should_be) {
+ return (testing::AssertionSuccess());
+ }
+
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "checkAnswer rcode:" << rcode
+ << " comment: " << *comment));
+ }
+
+ /// @brief Configuration set being tested.
+ isc::data::ElementPtr config_set_;
+
+ /// @brief Results of most recent element parsing.
+ isc::data::ConstElementPtr answer_;
+};
+
+} // namespace isc::process
+} // namespace isc
+
+#endif
diff --git a/src/lib/stats/Makefile.am b/src/lib/stats/Makefile.am
new file mode 100644
index 0000000..018d787
--- /dev/null
+++ b/src/lib/stats/Makefile.am
@@ -0,0 +1,28 @@
+SUBDIRS = . tests testutils
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-stats.la
+libkea_stats_la_SOURCES = observation.h observation.cc
+libkea_stats_la_SOURCES += context.h context.cc
+libkea_stats_la_SOURCES += stats_mgr.h stats_mgr.cc
+
+libkea_stats_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_stats_la_LDFLAGS = -no-undefined -version-info 29:0:0
+
+libkea_stats_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_stats_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_stats_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_stats_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_stats_la_LIBADD += $(BOOST_LIBS)
+
+EXTRA_DIST = stats.dox
+
+libkea_stats_includedir = $(pkgincludedir)/stats
+libkea_stats_include_HEADERS = \
+ context.h \
+ observation.h \
+ stats_mgr.h
+
diff --git a/src/lib/stats/Makefile.in b/src/lib/stats/Makefile.in
new file mode 100644
index 0000000..8929292
--- /dev/null
+++ b/src/lib/stats/Makefile.in
@@ -0,0 +1,978 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/stats
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_stats_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)$(libkea_stats_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_stats_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1)
+am_libkea_stats_la_OBJECTS = libkea_stats_la-observation.lo \
+ libkea_stats_la-context.lo libkea_stats_la-stats_mgr.lo
+libkea_stats_la_OBJECTS = $(am_libkea_stats_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_stats_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_stats_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_stats_la-context.Plo \
+ ./$(DEPDIR)/libkea_stats_la-observation.Plo \
+ ./$(DEPDIR)/libkea_stats_la-stats_mgr.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_stats_la_SOURCES)
+DIST_SOURCES = $(libkea_stats_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 = $(libkea_stats_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests testutils
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-stats.la
+libkea_stats_la_SOURCES = observation.h observation.cc context.h \
+ context.cc stats_mgr.h stats_mgr.cc
+libkea_stats_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_stats_la_LDFLAGS = -no-undefined -version-info 29:0:0
+libkea_stats_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(BOOST_LIBS)
+EXTRA_DIST = stats.dox
+libkea_stats_includedir = $(pkgincludedir)/stats
+libkea_stats_include_HEADERS = \
+ context.h \
+ observation.h \
+ stats_mgr.h
+
+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/stats/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/stats/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-stats.la: $(libkea_stats_la_OBJECTS) $(libkea_stats_la_DEPENDENCIES) $(EXTRA_libkea_stats_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_stats_la_LINK) -rpath $(libdir) $(libkea_stats_la_OBJECTS) $(libkea_stats_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_stats_la-context.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_stats_la-observation.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_stats_la-stats_mgr.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)$(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 $@ $<
+
+libkea_stats_la-observation.lo: observation.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_stats_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_stats_la-observation.lo -MD -MP -MF $(DEPDIR)/libkea_stats_la-observation.Tpo -c -o libkea_stats_la-observation.lo `test -f 'observation.cc' || echo '$(srcdir)/'`observation.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_stats_la-observation.Tpo $(DEPDIR)/libkea_stats_la-observation.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='observation.cc' object='libkea_stats_la-observation.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_stats_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_stats_la-observation.lo `test -f 'observation.cc' || echo '$(srcdir)/'`observation.cc
+
+libkea_stats_la-context.lo: context.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_stats_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_stats_la-context.lo -MD -MP -MF $(DEPDIR)/libkea_stats_la-context.Tpo -c -o libkea_stats_la-context.lo `test -f 'context.cc' || echo '$(srcdir)/'`context.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_stats_la-context.Tpo $(DEPDIR)/libkea_stats_la-context.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='context.cc' object='libkea_stats_la-context.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_stats_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_stats_la-context.lo `test -f 'context.cc' || echo '$(srcdir)/'`context.cc
+
+libkea_stats_la-stats_mgr.lo: stats_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_stats_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_stats_la-stats_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_stats_la-stats_mgr.Tpo -c -o libkea_stats_la-stats_mgr.lo `test -f 'stats_mgr.cc' || echo '$(srcdir)/'`stats_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_stats_la-stats_mgr.Tpo $(DEPDIR)/libkea_stats_la-stats_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stats_mgr.cc' object='libkea_stats_la-stats_mgr.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_stats_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_stats_la-stats_mgr.lo `test -f 'stats_mgr.cc' || echo '$(srcdir)/'`stats_mgr.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_stats_includeHEADERS: $(libkea_stats_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_stats_include_HEADERS)'; test -n "$(libkea_stats_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_stats_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_stats_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)$(libkea_stats_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_stats_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_stats_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_stats_include_HEADERS)'; test -n "$(libkea_stats_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_stats_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_stats_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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:
+
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_stats_la-context.Plo
+ -rm -f ./$(DEPDIR)/libkea_stats_la-observation.Plo
+ -rm -f ./$(DEPDIR)/libkea_stats_la-stats_mgr.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-libkea_stats_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_stats_la-context.Plo
+ -rm -f ./$(DEPDIR)/libkea_stats_la-observation.Plo
+ -rm -f ./$(DEPDIR)/libkea_stats_la-stats_mgr.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-libkea_stats_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_stats_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-libkea_stats_includeHEADERS
+
+.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/stats/context.cc b/src/lib/stats/context.cc
new file mode 100644
index 0000000..697b3de
--- /dev/null
+++ b/src/lib/stats/context.cc
@@ -0,0 +1,99 @@
+// Copyright (C) 2015-2020 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 <stats/context.h>
+#include <util/multi_threading_mgr.h>
+#include <map>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace stats {
+
+ObservationPtr
+StatContext::get(const std::string& name) const {
+ auto obs = stats_.find(name);
+ if (obs != stats_.end()) {
+ return (obs->second);
+ }
+ return (ObservationPtr());
+}
+
+void
+StatContext::add(const ObservationPtr& obs) {
+ auto existing = stats_.find(obs->getName());
+ if (existing == stats_.end()) {
+ stats_.insert(make_pair(obs->getName() ,obs));
+ } else {
+ isc_throw(DuplicateStat, "Statistic named " << obs->getName()
+ << " already exists.");
+ }
+}
+
+bool
+StatContext::del(const std::string& name) {
+ auto obs = stats_.find(name);
+ if (obs != stats_.end()) {
+ stats_.erase(obs);
+ return (true);
+ }
+ return (false);
+}
+
+size_t
+StatContext::size() {
+ return (stats_.size());
+}
+
+void
+StatContext::clear() {
+ stats_.clear();
+}
+
+void
+StatContext::resetAll() {
+ // Let's iterate over all stored statistics...
+ for (auto s : stats_) {
+ // ... and reset each statistic.
+ s.second->reset();
+ }
+}
+
+ConstElementPtr
+StatContext::getAll() const {
+ ElementPtr map = Element::createMap(); // a map
+ // Let's iterate over all stored statistics...
+ for (auto s : stats_) {
+ // ... and add each of them to the map.
+ map->set(s.first, s.second->getJSON());
+ }
+ return (map);
+}
+
+void
+StatContext::setMaxSampleCountAll(uint32_t max_samples) {
+ // Let's iterate over all stored statistics...
+ for (auto s : stats_) {
+ // ... and set count limit for each statistic.
+ s.second->setMaxSampleCount(max_samples);
+ }
+}
+
+void
+StatContext::setMaxSampleAgeAll(const StatsDuration& duration) {
+ // Let's iterate over all stored statistics...
+ for (auto s : stats_) {
+ // ... and set duration limit for each statistic.
+ s.second->setMaxSampleAge(duration);
+ }
+}
+
+} // namespace stats
+} // namespace isc
diff --git a/src/lib/stats/context.h b/src/lib/stats/context.h
new file mode 100644
index 0000000..117872f
--- /dev/null
+++ b/src/lib/stats/context.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2015-2020 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 CONTEXT_H
+#define CONTEXT_H
+
+#include <stats/observation.h>
+#include <boost/shared_ptr.hpp>
+#include <mutex>
+#include <string>
+
+namespace isc {
+namespace stats {
+
+/// @brief Exception indicating that a given statistic is duplicated.
+class DuplicateStat : public Exception {
+public:
+ DuplicateStat(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Statistics context
+///
+/// Statistics context is essentially a container used to group statistics
+/// related to a given context together. Two examples of such contexts are
+/// all statistics related to a given subnet or all statistics related to a
+/// given network interface.
+struct StatContext {
+public:
+
+ /// @brief Attempts to get an observation
+ ///
+ /// @param name name of the statistic
+ /// @return appropriate Observation object (or NULL)
+ ObservationPtr get(const std::string& name) const;
+
+ /// @brief Adds a new observation
+ ///
+ /// @param obs observation to be added
+ /// @throw DuplicateStat if an observation with the same name exists already
+ void add(const ObservationPtr& obs);
+
+ /// @brief Attempts to delete an observation
+ ///
+ /// @param name name of the observation to be deleted
+ /// @return true if successful, false if no such statistic was found
+ bool del(const std::string& name);
+
+ /// @brief Returns the number of observations
+ ///
+ /// @return the number of observations
+ size_t size();
+
+ /// @brief Removes all observations
+ void clear();
+
+ /// @brief Resets all observations
+ void resetAll();
+
+ /// @brief Sets max sample count for all observations
+ ///
+ /// @param max_samples value to be set for all observations
+ void setMaxSampleCountAll(uint32_t max_samples);
+
+ /// @brief Sets duration for all observations
+ ///
+ /// @param duration value to be set for all observations
+ void setMaxSampleAgeAll(const StatsDuration& duration);
+
+ /// @brief Returns a map with all observations
+ ///
+ /// @return map with all observations
+ isc::data::ConstElementPtr getAll() const;
+
+private:
+
+ /// @brief Statistics container
+ std::map<std::string, ObservationPtr> stats_;
+};
+
+/// @brief Pointer to the statistics context
+typedef boost::shared_ptr<StatContext> StatContextPtr;
+
+};
+};
+
+#endif // CONTEXT_H
diff --git a/src/lib/stats/observation.cc b/src/lib/stats/observation.cc
new file mode 100644
index 0000000..034f2b9
--- /dev/null
+++ b/src/lib/stats/observation.cc
@@ -0,0 +1,568 @@
+// Copyright (C) 2015-2023 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 <stats/observation.h>
+#include <util/chrono_time_utils.h>
+#include <cc/data.h>
+
+#include <chrono>
+#include <utility>
+
+using namespace std;
+using namespace std::chrono;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace stats {
+
+std::pair<bool, uint32_t>
+Observation::default_max_sample_count_ = std::make_pair(true, 20);
+
+std::pair<bool, StatsDuration>
+Observation::default_max_sample_age_ =
+ std::make_pair(false, StatsDuration::zero());
+
+Observation::Observation(const std::string& name, const int64_t value) :
+ name_(name), type_(STAT_INTEGER),
+ max_sample_count_(default_max_sample_count_),
+ max_sample_age_(default_max_sample_age_) {
+ setValue(value);
+}
+
+Observation::Observation(const std::string& name, const int128_t& value) :
+ name_(name), type_(STAT_BIG_INTEGER),
+ max_sample_count_(default_max_sample_count_),
+ max_sample_age_(default_max_sample_age_) {
+ setValue(value);
+}
+
+Observation::Observation(const std::string& name, const double value) :
+ name_(name), type_(STAT_FLOAT),
+ max_sample_count_(default_max_sample_count_),
+ max_sample_age_(default_max_sample_age_) {
+ setValue(value);
+}
+
+Observation::Observation(const std::string& name, const StatsDuration& value) :
+ name_(name), type_(STAT_DURATION),
+ max_sample_count_(default_max_sample_count_),
+ max_sample_age_(default_max_sample_age_) {
+ setValue(value);
+}
+
+Observation::Observation(const std::string& name, const std::string& value) :
+ name_(name), type_(STAT_STRING),
+ max_sample_count_(default_max_sample_count_),
+ max_sample_age_(default_max_sample_age_) {
+ setValue(value);
+}
+
+void Observation::setMaxSampleAge(const StatsDuration& duration) {
+ switch(type_) {
+ case STAT_INTEGER: {
+ setMaxSampleAgeInternal(integer_samples_, duration, STAT_INTEGER);
+ return;
+ }
+ case STAT_BIG_INTEGER: {
+ setMaxSampleAgeInternal(big_integer_samples_, duration, STAT_BIG_INTEGER);
+ return;
+ }
+ case STAT_FLOAT: {
+ setMaxSampleAgeInternal(float_samples_, duration, STAT_FLOAT);
+ return;
+ }
+ case STAT_DURATION: {
+ setMaxSampleAgeInternal(duration_samples_, duration, STAT_DURATION);
+ return;
+ }
+ case STAT_STRING: {
+ setMaxSampleAgeInternal(string_samples_, duration, STAT_STRING);
+ return;
+ }
+ default:
+ isc_throw(InvalidStatType, "Unknown statistic type: "
+ << typeToText(type_));
+ };
+}
+
+void Observation::setMaxSampleCount(uint32_t max_samples) {
+ switch(type_) {
+ case STAT_INTEGER: {
+ setMaxSampleCountInternal(integer_samples_, max_samples, STAT_INTEGER);
+ return;
+ }
+ case STAT_BIG_INTEGER: {
+ setMaxSampleCountInternal(big_integer_samples_, max_samples, STAT_BIG_INTEGER);
+ return;
+ }
+ case STAT_FLOAT: {
+ setMaxSampleCountInternal(float_samples_, max_samples, STAT_FLOAT);
+ return;
+ }
+ case STAT_DURATION: {
+ setMaxSampleCountInternal(duration_samples_, max_samples, STAT_DURATION);
+ return;
+ }
+ case STAT_STRING: {
+ setMaxSampleCountInternal(string_samples_, max_samples, STAT_STRING);
+ return;
+ }
+ default:
+ isc_throw(InvalidStatType, "Unknown statistic type: "
+ << typeToText(type_));
+ };
+}
+
+void Observation::addValue(const int64_t value) {
+ IntegerSample current = getInteger();
+ setValue(current.first + value);
+}
+
+void Observation::addValue(const int128_t& value) {
+ BigIntegerSample current = getBigInteger();
+ setValue(current.first + value);
+}
+
+void Observation::addValue(const double value) {
+ FloatSample current = getFloat();
+ setValue(current.first + value);
+}
+
+void Observation::addValue(const StatsDuration& value) {
+ DurationSample current = getDuration();
+ setValue(current.first + value);
+}
+
+void Observation::addValue(const std::string& value) {
+ StringSample current = getString();
+ setValue(current.first + value);
+}
+
+void Observation::setValue(const int64_t value) {
+ setValueInternal(value, integer_samples_, STAT_INTEGER);
+}
+
+void Observation::setValue(const int128_t& value) {
+ setValueInternal(value, big_integer_samples_, STAT_BIG_INTEGER);
+}
+
+void Observation::setValue(const double value) {
+ setValueInternal(value, float_samples_, STAT_FLOAT);
+}
+
+void Observation::setValue(const StatsDuration& value) {
+ setValueInternal(value, duration_samples_, STAT_DURATION);
+}
+
+void Observation::setValue(const std::string& value) {
+ setValueInternal(value, string_samples_, STAT_STRING);
+}
+
+size_t Observation::getSize() const {
+ size_t size = 0;
+ switch(type_) {
+ case STAT_INTEGER: {
+ size = getSizeInternal(integer_samples_, STAT_INTEGER);
+ return (size);
+ }
+ case STAT_BIG_INTEGER: {
+ size = getSizeInternal(big_integer_samples_, STAT_BIG_INTEGER);
+ return (size);
+ }
+ case STAT_FLOAT: {
+ size = getSizeInternal(float_samples_, STAT_FLOAT);
+ return (size);
+ }
+ case STAT_DURATION: {
+ size = getSizeInternal(duration_samples_, STAT_DURATION);
+ return (size);
+ }
+ case STAT_STRING: {
+ size = getSizeInternal(string_samples_, STAT_STRING);
+ return (size);
+ }
+ default:
+ isc_throw(InvalidStatType, "Unknown statistic type: "
+ << typeToText(type_));
+ };
+ return (size);
+}
+
+std::pair<bool, StatsDuration> Observation::getMaxSampleAge() const {
+ return (max_sample_age_);
+}
+
+std::pair<bool, uint32_t> Observation::getMaxSampleCount() const {
+ return (max_sample_count_);
+}
+
+template<typename StorageType>
+size_t Observation::getSizeInternal(StorageType& storage, Type exp_type) const {
+ if (type_ != exp_type) {
+ isc_throw(InvalidStatType, "Invalid statistic type requested: "
+ << typeToText(exp_type) << ", but the actual type is "
+ << typeToText(type_));
+ } else {
+ return (storage.size());
+ }
+ return (0); // to avoid compilation error
+}
+
+template<typename SampleType, typename StorageType>
+void Observation::setValueInternal(SampleType value, StorageType& storage,
+ Type exp_type) {
+ if (type_ != exp_type) {
+ isc_throw(InvalidStatType, "Invalid statistic type requested: "
+ << typeToText(exp_type) << ", but the actual type is "
+ << typeToText(type_));
+ }
+
+ if (storage.empty()) {
+ storage.push_back(make_pair(value, SampleClock::now()));
+ } else {
+ // Storing of more than one sample
+ storage.push_front(make_pair(value, SampleClock::now()));
+
+ if (max_sample_count_.first) {
+ // if max_sample_count_ is set to true
+ // and size of storage is equal to max_sample_count_
+ if (storage.size() > max_sample_count_.second) {
+ storage.pop_back(); // removing the last element
+ }
+ } else {
+ StatsDuration range_of_storage =
+ storage.front().second - storage.back().second;
+ // removing samples until the range_of_storage
+ // stops exceeding the duration limit
+ while (range_of_storage > max_sample_age_.second) {
+ storage.pop_back();
+ range_of_storage =
+ storage.front().second - storage.back().second;
+ }
+ }
+ }
+}
+
+IntegerSample Observation::getInteger() const {
+ return (getValueInternal<IntegerSample>(integer_samples_, STAT_INTEGER));
+}
+
+BigIntegerSample Observation::getBigInteger() const {
+ return (getValueInternal<BigIntegerSample>(big_integer_samples_, STAT_BIG_INTEGER));
+}
+
+FloatSample Observation::getFloat() const {
+ return (getValueInternal<FloatSample>(float_samples_, STAT_FLOAT));
+}
+
+DurationSample Observation::getDuration() const {
+ return (getValueInternal<DurationSample>(duration_samples_, STAT_DURATION));
+}
+
+StringSample Observation::getString() const {
+ return (getValueInternal<StringSample>(string_samples_, STAT_STRING));
+}
+
+template<typename SampleType, typename Storage>
+SampleType Observation::getValueInternal(Storage& storage, Type exp_type) const {
+ if (type_ != exp_type) {
+ isc_throw(InvalidStatType, "Invalid statistic type requested: "
+ << typeToText(exp_type) << ", but the actual type is "
+ << typeToText(type_));
+ }
+
+ if (storage.empty()) {
+ // That should never happen. The first element is always initialized in
+ // the constructor. reset() sets its value to zero, but the element should
+ // still be there.
+ isc_throw(Unexpected, "Observation storage container empty");
+ }
+ return (*storage.begin());
+}
+
+std::list<IntegerSample> Observation::getIntegers() const {
+ return (getValuesInternal<IntegerSample>(integer_samples_, STAT_INTEGER));
+}
+
+std::list<BigIntegerSample> Observation::getBigIntegers() const {
+ return (getValuesInternal<BigIntegerSample>(big_integer_samples_, STAT_BIG_INTEGER));
+}
+
+std::list<FloatSample> Observation::getFloats() const {
+ return (getValuesInternal<FloatSample>(float_samples_, STAT_FLOAT));
+}
+
+std::list<DurationSample> Observation::getDurations() const {
+ return (getValuesInternal<DurationSample>(duration_samples_, STAT_DURATION));
+}
+
+std::list<StringSample> Observation::getStrings() const {
+ return (getValuesInternal<StringSample>(string_samples_, STAT_STRING));
+}
+
+template<typename SampleType, typename Storage>
+std::list<SampleType> Observation::getValuesInternal(Storage& storage,
+ Type exp_type) const {
+ if (type_ != exp_type) {
+ isc_throw(InvalidStatType, "Invalid statistic type requested: "
+ << typeToText(exp_type) << ", but the actual type is "
+ << typeToText(type_));
+ }
+
+ if (storage.empty()) {
+ // That should never happen. The first element is always initialized in
+ // the constructor. reset() sets its value to zero, but the element should
+ // still be there.
+ isc_throw(Unexpected, "Observation storage container empty");
+ }
+ return (storage);
+}
+
+template<typename StorageType>
+void Observation::setMaxSampleAgeInternal(StorageType& storage,
+ const StatsDuration& duration,
+ Type exp_type) {
+ if (type_ != exp_type) {
+ isc_throw(InvalidStatType, "Invalid statistic type requested: "
+ << typeToText(exp_type) << ", but the actual type is "
+ << typeToText(type_));
+ }
+ // setting new value of max_sample_age_
+ max_sample_age_.first = true;
+ max_sample_age_.second = duration;
+ // deactivating the max_sample_count_ limit
+ max_sample_count_.first = false;
+
+ StatsDuration range_of_storage =
+ storage.front().second - storage.back().second;
+
+ while (range_of_storage > duration) {
+ // deleting elements which are exceeding the duration limit
+ storage.pop_back();
+ range_of_storage = storage.front().second - storage.back().second;
+ }
+}
+
+template<typename StorageType>
+void Observation::setMaxSampleCountInternal(StorageType& storage,
+ uint32_t max_samples,
+ Type exp_type) {
+ if (type_ != exp_type) {
+ isc_throw(InvalidStatType, "Invalid statistic type requested: "
+ << typeToText(exp_type) << ", but the actual type is "
+ << typeToText(type_));
+ }
+ // Should we refuse the max_samples = 0 value here?
+ // setting new value of max_sample_count_
+ max_sample_count_.first = true;
+ max_sample_count_.second = max_samples;
+ // deactivating the max_sample_age_ limit
+ max_sample_age_.first = false;
+
+ while (storage.size() > max_samples) {
+ // deleting elements which are exceeding the max_samples limit
+ storage.pop_back();
+ }
+}
+
+void Observation::setMaxSampleAgeDefault(const StatsDuration& duration) {
+ // setting new value of default_max_sample_age_
+ default_max_sample_age_.second = duration;
+}
+
+void Observation::setMaxSampleCountDefault(uint32_t max_samples) {
+ if (max_samples == 0) {
+ // deactivating the default_max_sample_count_ limit
+ default_max_sample_count_.first = false;
+ default_max_sample_age_.first = true;
+ } else {
+ // setting new value of default_max_sample_count_
+ default_max_sample_count_.second = max_samples;
+ // deactivating the default_max_sample_age_ limit
+ default_max_sample_age_.first = false;
+ default_max_sample_count_.first = true;
+ }
+}
+
+const StatsDuration& Observation::getMaxSampleAgeDefault() {
+ return (default_max_sample_age_.second);
+}
+
+uint32_t Observation::getMaxSampleCountDefault() {
+ if (default_max_sample_count_.first) {
+ return (default_max_sample_count_.second);
+ } else {
+ return (0);
+ }
+}
+
+std::string Observation::typeToText(Type type) {
+ std::stringstream tmp;
+ switch (type) {
+ case STAT_INTEGER:
+ tmp << "integer";
+ break;
+ case STAT_BIG_INTEGER:
+ tmp << "big integer";
+ break;
+ case STAT_FLOAT:
+ tmp << "float";
+ break;
+ case STAT_DURATION:
+ tmp << "duration";
+ break;
+ case STAT_STRING:
+ tmp << "string";
+ break;
+ default:
+ tmp << "unknown";
+ break;
+ }
+ tmp << "(" << type << ")";
+ return (tmp.str());
+}
+
+isc::data::ConstElementPtr
+Observation::getJSON() const {
+ ElementPtr list = isc::data::Element::createList(); // multiple observations
+ ElementPtr entry;
+ ElementPtr value;
+ ElementPtr timestamp;
+
+ // Support for retrieving more than one sample
+ // retrieving all samples of indicated observation
+ switch (type_) {
+ case STAT_INTEGER: {
+ std::list<IntegerSample> s = getIntegers(); // List of all integer samples
+
+ // Iteration over all elements in the list
+ // and adding alternately value and timestamp to the entry
+ for (std::list<IntegerSample>::iterator it = s.begin(); it != s.end(); ++it) {
+ entry = isc::data::Element::createList();
+ value = isc::data::Element::create(static_cast<int64_t>((*it).first));
+ timestamp = isc::data::Element::create(isc::util::clockToText((*it).second));
+
+ entry->add(value);
+ entry->add(timestamp);
+
+ list->add(entry);
+ }
+ break;
+ }
+ case STAT_BIG_INTEGER: {
+ std::list<BigIntegerSample> const& samples(getBigIntegers());
+
+ // Iterate over all elements in the list and alternately add
+ // value and timestamp to the entry.
+ for (BigIntegerSample const& i : samples) {
+ entry = isc::data::Element::createList();
+ value = isc::data::Element::create(i.first);
+ timestamp = isc::data::Element::create(isc::util::clockToText(i.second));
+
+ entry->add(value);
+ entry->add(timestamp);
+
+ list->add(entry);
+ }
+ break;
+ }
+ case STAT_FLOAT: {
+ std::list<FloatSample> s = getFloats();
+
+ // Iteration over all elements in the list
+ // and adding alternately value and timestamp to the entry
+ for (std::list<FloatSample>::iterator it = s.begin(); it != s.end(); ++it) {
+ entry = isc::data::Element::createList();
+ value = isc::data::Element::create((*it).first);
+ timestamp = isc::data::Element::create(isc::util::clockToText((*it).second));
+
+ entry->add(value);
+ entry->add(timestamp);
+
+ list->add(entry);
+ }
+ break;
+ }
+ case STAT_DURATION: {
+ std::list<DurationSample> s = getDurations();
+
+ // Iteration over all elements in the list
+ // and adding alternately value and timestamp to the entry
+ for (std::list<DurationSample>::iterator it = s.begin(); it != s.end(); ++it) {
+ entry = isc::data::Element::createList();
+ value = isc::data::Element::create(isc::util::durationToText((*it).first));
+ timestamp = isc::data::Element::create(isc::util::clockToText((*it).second));
+
+ entry->add(value);
+ entry->add(timestamp);
+
+ list->add(entry);
+ }
+ break;
+ }
+ case STAT_STRING: {
+ std::list<StringSample> s = getStrings();
+
+ // Iteration over all elements in the list
+ // and adding alternately value and timestamp to the entry
+ for (std::list<StringSample>::iterator it = s.begin(); it != s.end(); ++it) {
+ entry = isc::data::Element::createList();
+ value = isc::data::Element::create((*it).first);
+ timestamp = isc::data::Element::create(isc::util::clockToText((*it).second));
+
+ entry->add(value);
+ entry->add(timestamp);
+
+ list->add(entry);
+ }
+ break;
+ }
+ default:
+ isc_throw(InvalidStatType, "Unknown statistic type: "
+ << typeToText(type_));
+ };
+
+ return (list);
+}
+
+void Observation::reset() {
+ switch(type_) {
+ case STAT_INTEGER: {
+ integer_samples_.clear();
+ setValue(static_cast<int64_t>(0));
+ return;
+ }
+ case STAT_BIG_INTEGER: {
+ big_integer_samples_.clear();
+ setValue(int128_t(0));
+ return;
+ }
+ case STAT_FLOAT: {
+ float_samples_.clear();
+ setValue(0.0);
+ return;
+ }
+ case STAT_DURATION: {
+ duration_samples_.clear();
+ setValue(StatsDuration::zero());
+ return;
+ }
+ case STAT_STRING: {
+ string_samples_.clear();
+ setValue(string(""));
+ return;
+ }
+ default:
+ isc_throw(InvalidStatType, "Unknown statistic type: "
+ << typeToText(type_));
+ };
+}
+
+} // end of namespace stats
+} // end of namespace isc
diff --git a/src/lib/stats/observation.h b/src/lib/stats/observation.h
new file mode 100644
index 0000000..df9a94c
--- /dev/null
+++ b/src/lib/stats/observation.h
@@ -0,0 +1,484 @@
+// Copyright (C) 2015-2023 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 OBSERVATION_H
+#define OBSERVATION_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <util/bigints.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <chrono>
+#include <list>
+
+#include <stdint.h>
+
+namespace isc {
+namespace stats {
+
+/// @brief Exception thrown if invalid statistic type is used
+///
+/// For example statistic is of type duration, but methods using
+/// it as integer are called.
+class InvalidStatType : public Exception {
+public:
+ InvalidStatType(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Define clock type.
+///
+/// @note: we use the system clock i.e. the wall clock because this
+/// clock can be converted from and to standard Unix time (time_t).
+typedef std::chrono::system_clock SampleClock;
+
+/// @brief Defines duration type.
+///
+/// @note: the precision depends on the system,
+typedef std::chrono::system_clock::duration StatsDuration;
+
+/// @brief Returns the number of seconds in a duration.
+///
+/// @param dur The duration.
+/// @return The number of seconds in the given duration.
+inline long toSeconds(const StatsDuration& dur) {
+ return ((std::chrono::duration_cast<std::chrono::seconds>(dur)).count());
+}
+
+/// @defgroup stat_samples Specifies supported observation types.
+///
+/// @brief The list covers all supported types of observations.
+///
+/// @{
+
+/// @brief Integer (implemented as signed 64-bit integer)
+typedef std::pair<int64_t, SampleClock::time_point> IntegerSample;
+
+/// @brief BigInteger (implemented as signed 128-bit integer)
+typedef std::pair<isc::util::int128_t, SampleClock::time_point> BigIntegerSample;
+
+/// @brief Float (implemented as double precision)
+typedef std::pair<double, SampleClock::time_point> FloatSample;
+
+/// @brief Time Duration
+typedef std::pair<StatsDuration, SampleClock::time_point> DurationSample;
+
+/// @brief String
+typedef std::pair<std::string, SampleClock::time_point> StringSample;
+
+/// @}
+
+/// @brief Represents a single observable characteristic (a 'statistic')
+///
+/// Currently it supports one of four types: integer (implemented as signed 64
+/// bit integer), float (implemented as double), time duration (implemented with
+/// millisecond precision) and string. Absolute (setValue) and
+/// incremental (addValue) modes are supported. Statistic type is determined
+/// during its first use. Once type is set, any additional observations recorded
+/// must be of the same type. Attempting to set or extract information about
+/// other types will result in InvalidStateType exception.
+///
+/// Observation can be retrieved in one of @ref getInteger, @ref getFloat,
+/// @ref getDuration, @ref getString (appropriate type must be used) or
+/// @ref getJSON, which is generic and can be used for all types.
+///
+/// Since Kea 1.6 multiple samples are stored for the same observation.
+class Observation {
+public:
+
+ /// @brief Type of available statistics
+ ///
+ /// Note that those will later be exposed using control socket. Therefore
+ /// an easy to understand names were chosen (integer instead of uint64).
+ /// To avoid confusion, we will support only one type of integer and only
+ /// one type of floating points. Initially, these are represented by
+ /// int64_t and double. If convincing use cases appear to change them
+ /// to something else, we may change the underlying type.
+ enum Type {
+ STAT_INTEGER, ///< this statistic is signed 64-bit integer value
+ STAT_BIG_INTEGER, ///< this statistic is signed 128-bit integer value
+ STAT_FLOAT, ///< this statistic is a floating point value
+ STAT_DURATION, ///< this statistic represents time duration
+ STAT_STRING ///< this statistic represents a string
+ };
+
+ /// @brief Constructor for integer observations
+ ///
+ /// @param name observation name
+ /// @param value integer value observed.
+ Observation(const std::string& name, const int64_t value);
+
+ /// @brief Constructor for big integer observations
+ ///
+ /// @param name observation name
+ /// @param value integer value observed.
+ Observation(const std::string& name, const isc::util::int128_t& value);
+
+ /// @brief Constructor for floating point observations
+ ///
+ /// @param name observation name
+ /// @param value floating point value observed.
+ Observation(const std::string& name, const double value);
+
+ /// @brief Constructor for duration observations
+ ///
+ /// @param name observation name
+ /// @param value duration observed.
+ Observation(const std::string& name, const StatsDuration& value);
+
+ /// @brief Constructor for string observations
+ ///
+ /// @param name observation name
+ /// @param value string observed.
+ Observation(const std::string& name, const std::string& value);
+
+ /// @brief Determines maximum age of samples.
+ ///
+ /// Specifies that statistic name should be stored not as a single value,
+ /// but rather as a set of values. The duration determines the timespan.
+ /// Samples older than duration will be discarded. This is time-constrained
+ /// approach. For sample count constrained approach, see @ref
+ /// setMaxSampleCount() below.
+ ///
+ ///
+ /// @param duration determines maximum age of samples
+ /// Example:
+ /// To set a statistic to keep observations for the last 5 minutes, call:
+ /// setMaxSampleAge(std::chrono::minutes(5));
+ /// To revert statistic to a single value, call:
+ /// setMaxSampleAge(StatsDuration::zero());
+ void setMaxSampleAge(const StatsDuration& duration);
+
+ /// @brief Determines how many samples of a given statistic should be kept.
+ ///
+ /// Specifies that statistic name should be stored not as a single value,
+ /// but rather as a set of values. In this form, at most max_samples will
+ /// be kept. When adding max_samples + 1 sample, the oldest sample will be
+ /// discarded.
+ ///
+ ///
+ /// @param max_samples how many samples of a given statistic should be kept
+ /// Example:
+ /// To set a statistic to keep the last 100 observations, call:
+ /// setMaxSampleCount(100);
+ void setMaxSampleCount(uint32_t max_samples);
+
+ /// @brief Determines default maximum age of samples.
+ ///
+ /// @param duration default maximum age of samples to keep.
+ static void setMaxSampleAgeDefault(const StatsDuration& duration);
+
+ /// @brief Determines default maximum count of samples.
+ ///
+ /// @param max_samples default maximum count of samples to keep.
+ /// (0 means to disable count limit and enable age limit)
+ static void setMaxSampleCountDefault(uint32_t max_samples);
+
+ /// @brief Get default maximum age of samples.
+ ///
+ /// @return default maximum age of samples to keep.
+ static const StatsDuration& getMaxSampleAgeDefault();
+
+ /// @brief Get default maximum count of samples.
+ ///
+ /// @return max_samples default maximum count of samples to keep.
+ /// (0 means that count limit was disabled)
+ static uint32_t getMaxSampleCountDefault();
+
+ /// @
+
+ /// @brief Records absolute integer observation
+ ///
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void setValue(const int64_t value);
+
+ /// @brief Records big integer observation
+ ///
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void setValue(const isc::util::int128_t& value);
+
+ /// @brief Records absolute floating point observation
+ ///
+ /// @param value floating point value observed
+ /// @throw InvalidStatType if statistic is not fp
+ void setValue(const double value);
+
+ /// @brief Records absolute duration observation
+ ///
+ /// @param value duration value observed
+ /// @throw InvalidStatType if statistic is not time duration
+ void setValue(const StatsDuration& value);
+
+ /// @brief Records absolute string observation
+ ///
+ /// @param value string value observed
+ /// @throw InvalidStatType if statistic is not a string
+ void setValue(const std::string& value);
+
+ /// @brief Records incremental integer observation
+ ///
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void addValue(const int64_t value);
+
+ /// @brief Records incremental integer observation
+ ///
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void addValue(const isc::util::int128_t& value);
+
+ /// @brief Records incremental floating point observation
+ ///
+ /// @param value floating point value observed
+ /// @throw InvalidStatType if statistic is not fp
+ void addValue(const double value);
+
+ /// @brief Records incremental duration observation
+ ///
+ /// @param value duration value observed
+ /// @throw InvalidStatType if statistic is not time duration
+ void addValue(const StatsDuration& value);
+
+ /// @brief Records incremental string observation.
+ ///
+ /// @param value string value observed
+ /// @throw InvalidStatType if statistic is not a string
+ void addValue(const std::string& value);
+
+ /// @brief Returns size of observed storage
+ ///
+ /// @return size of storage
+ size_t getSize() const;
+
+ /// @brief Returns both values of max_sample_age_ of statistic.
+ ///
+ /// @return max_sample_age_.
+ std::pair<bool, StatsDuration> getMaxSampleAge() const;
+
+ /// @brief Returns both values of max_sample_count_ of statistic.
+ ///
+ /// @return max_sample_count_.
+ std::pair<bool, uint32_t> getMaxSampleCount() const;
+
+ /// @brief Resets statistic.
+ ///
+ /// Sets statistic to a neutral (0, 0.0 or "") value and
+ /// clears the underlying storage.
+ void reset();
+
+ /// @brief Returns statistic type
+ /// @return statistic type
+ Type getType() const {
+ return (type_);
+ }
+
+ /// @brief Returns observed integer sample
+ /// @return observed sample (value + timestamp)
+ /// @throw InvalidStatType if statistic is not integer
+ IntegerSample getInteger() const;
+
+ /// @brief Returns observed integer sample
+ /// @return observed sample (value + timestamp)
+ /// @throw InvalidStatType if statistic is not integer
+ BigIntegerSample getBigInteger() const;
+
+ /// @brief Returns observed float sample
+ /// @return observed sample (value + timestamp)
+ /// @throw InvalidStatType if statistic is not fp
+ FloatSample getFloat() const;
+
+ /// @brief Returns observed duration sample
+ /// @return observed sample (value + timestamp)
+ /// @throw InvalidStatType if statistic is not time duration
+ DurationSample getDuration() const;
+
+ /// @brief Returns observed string sample
+ /// @return observed sample (value + timestamp)
+ /// @throw InvalidStatType if statistic is not a string
+ StringSample getString() const;
+
+ /// @brief Returns observed integer samples
+ /// @return list of observed samples (value + timestamp)
+ /// @throw InvalidStatType if statistic is not integer
+ std::list<IntegerSample> getIntegers() const;
+
+ /// @brief Returns observed big-integer samples
+ /// @return list of observed samples (value + timestamp)
+ /// @throw InvalidStatType if statistic is not integer
+ std::list<BigIntegerSample> getBigIntegers() const;
+
+ /// @brief Returns observed float samples
+ /// @return list of observed samples (value + timestamp)
+ /// @throw InvalidStatType if statistic is not fp
+ std::list<FloatSample> getFloats() const;
+
+ /// @brief Returns observed duration samples
+ /// @return list of observed samples (value + timestamp)
+ /// @throw InvalidStatType if statistic is not time duration
+ std::list<DurationSample> getDurations() const;
+
+ /// @brief Returns observed string samples
+ /// @return list of observed samples (value + timestamp)
+ /// @throw InvalidStatType if statistic is not a string
+ std::list<StringSample> getStrings() const;
+
+ /// @brief Returns as a JSON structure
+ /// @return JSON structures representing all observations
+ isc::data::ConstElementPtr getJSON() const;
+
+ /// @brief Converts statistic type to string
+ /// @return textual name of statistic type
+ static std::string typeToText(Type type);
+
+ /// @brief Returns observation name
+ std::string getName() const {
+ return (name_);
+ }
+
+private:
+
+ /// @brief Returns size of observed storage
+ ///
+ /// This method returns size of observed storage.
+ /// It is used by public methods to return size of
+ /// available storages.
+ /// @tparam Storage type of storage (e.g. list<IntegerSample>)
+ /// @param storage storage which size will be returned
+ /// @param exp_type expected observation type (used for sanity checking)
+ /// @return size of storage
+ template<typename StorageType>
+ size_t getSizeInternal(StorageType& storage, Type exp_type) const;
+
+ /// @brief Records absolute sample (internal version)
+ ///
+ /// This method records an absolute value of an observation.
+ /// It is used by public methods to add sample to one of
+ /// available storages.
+ ///
+ /// @tparam SampleType type of sample (e.g. IntegerSample)
+ /// @tparam StorageType type of storage (e.g. list<IntegerSample>)
+ /// @param value observation to be recorded
+ /// @param storage observation will be stored here
+ /// @param exp_type expected observation type (used for sanity checking)
+ /// @throw InvalidStatType if observation type mismatches
+ template<typename SampleType, typename StorageType>
+ void setValueInternal(SampleType value, StorageType& storage,
+ Type exp_type);
+
+ /// @brief Returns a sample (internal version)
+ ///
+ /// @tparam SampleType type of sample (e.g. IntegerSample)
+ /// @tparam StorageType type of storage (e.g. list<IntegerSample>)
+ /// @param observation storage
+ /// @param exp_type expected observation type (used for sanity checking)
+ /// @throw InvalidStatType if observation type mismatches
+ /// @return Observed sample
+ template<typename SampleType, typename Storage>
+ SampleType getValueInternal(Storage& storage, Type exp_type) const;
+
+ /// @brief Returns samples (internal version)
+ ///
+ /// @tparam SampleType type of samples (e.g. IntegerSample)
+ /// @tparam Storage type of storage (e.g. list<IntegerSample>)
+ /// @param observation storage
+ /// @param exp_type expected observation type (used for sanity checking)
+ /// @throw InvalidStatType if observation type mismatches
+ /// @return list of observed samples
+ template<typename SampleType, typename Storage>
+ std::list<SampleType> getValuesInternal(Storage& storage,
+ Type exp_type) const;
+
+ /// @brief Determines maximum age of samples.
+ ///
+ /// @tparam Storage type of storage (e.g. list<IntegerSample>)
+ /// @param storage storage on which limit will be set
+ /// @param duration determines maximum age of samples
+ /// @param exp_type expected observation type (used for sanity checking)
+ template<typename StorageType>
+ void setMaxSampleAgeInternal(StorageType& storage,
+ const StatsDuration& duration, Type exp_type);
+
+ /// @brief Determines how many samples of a given statistic should be kept.
+ ///
+ /// @tparam Storage type of storage (e.g. list<IntegerSample>)
+ /// @param storage storage on which limit will be set
+ /// @param max_samples determines maximum number of samples
+ /// @param exp_type expected observation type (used for sanity checking)
+ template<typename StorageType>
+ void setMaxSampleCountInternal(StorageType& storage,
+ uint32_t max_samples, Type exp_type);
+
+ /// @brief Observation (statistic) name
+ std::string name_;
+
+ /// @brief Observation (statistic) type)
+ Type type_;
+
+ /// @brief Maximum number of samples
+ /// The limit is represented as a pair
+ /// of bool value and uint32_t
+ /// Only one kind of limit can be active
+ /// The bool value informs which limit
+ /// is available
+ /// True means active limit, false means inactive limit
+ std::pair<bool, uint32_t> max_sample_count_;
+
+ /// @brief Default maximum number of samples
+ ///
+ /// By default the MaxSampleCount is set to 20
+ /// and MaxSampleAge is disabled
+ static std::pair<bool, uint32_t> default_max_sample_count_;
+
+ /// @brief Maximum timespan of samples
+ /// The limit is represented as a pair
+ /// of bool value and StatsDuration
+ /// Only one kind of limit can be active
+ /// The bool value informs which limit
+ /// is available
+ /// True means active limit, false means inactive limit
+ std::pair<bool, StatsDuration> max_sample_age_;
+
+ /// @brief Default maximum timespan of samples
+ ///
+ /// By default the MaxSampleCount is set to 20
+ /// and MaxSampleAge is disabled
+ static std::pair<bool, StatsDuration> default_max_sample_age_;
+
+ /// @defgroup samples_storage Storage for supported observations
+ ///
+ /// @brief The following containers serve as a storage for all supported
+ /// observation types.
+ ///
+ /// @{
+
+ /// @brief Storage for integer samples
+ std::list<IntegerSample> integer_samples_;
+
+ /// @brief Storage for big integer samples
+ std::list<BigIntegerSample> big_integer_samples_;
+
+ /// @brief Storage for floating point samples
+ std::list<FloatSample> float_samples_;
+
+ /// @brief Storage for time duration samples
+ std::list<DurationSample> duration_samples_;
+
+ /// @brief Storage for string samples
+ std::list<StringSample> string_samples_;
+ /// @}
+};
+
+/// @brief Observation pointer
+typedef boost::shared_ptr<Observation> ObservationPtr;
+
+}
+}
+
+#endif // OBSERVATION_H
diff --git a/src/lib/stats/stats.dox b/src/lib/stats/stats.dox
new file mode 100644
index 0000000..7d4d3b5
--- /dev/null
+++ b/src/lib/stats/stats.dox
@@ -0,0 +1,16 @@
+// Copyright (C) 2020 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/.
+
+/**
+ @page libstats libkea-stats - Kea Statistics Library
+
+@section statsMTConsiderations Multi-Threading Consideration for Statistics
+
+The statistic manager (@c isc::stats::StatsMgr singleton) is Kea thread safe
+i.e. it is thread safe when the multi-threading mode is true (when the
+multi-threading mode is false Kea main thread processes packets).
+
+*/
diff --git a/src/lib/stats/stats_mgr.cc b/src/lib/stats/stats_mgr.cc
new file mode 100644
index 0000000..b420cab
--- /dev/null
+++ b/src/lib/stats/stats_mgr.cc
@@ -0,0 +1,541 @@
+// Copyright (C) 2015-2023 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 <stats/stats_mgr.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <util/multi_threading_mgr.h>
+#include <util/bigints.h>
+
+#include <boost/make_shared.hpp>
+
+#include <chrono>
+
+using namespace std;
+using namespace std::chrono;
+using namespace isc::data;
+using namespace isc::config;
+using namespace isc::util;
+
+namespace isc {
+namespace stats {
+
+StatsMgr&
+StatsMgr::instance() {
+ static StatsMgr stats_mgr;
+ return (stats_mgr);
+}
+
+StatsMgr::StatsMgr() :
+ global_(boost::make_shared<StatContext>()), mutex_(new mutex) {
+}
+
+void
+StatsMgr::setValue(const string& name, const int64_t value) {
+ MultiThreadingLock lock(*mutex_);
+ setValueInternal(name, value);
+}
+
+void
+StatsMgr::setValue(const string& name, const int128_t& value) {
+ MultiThreadingLock lock(*mutex_);
+ setValueInternal(name, value);
+}
+
+void
+StatsMgr::setValue(const string& name, const double value) {
+ MultiThreadingLock lock(*mutex_);
+ setValueInternal(name, value);
+}
+
+void
+StatsMgr::setValue(const string& name, const StatsDuration& value) {
+ MultiThreadingLock lock(*mutex_);
+ setValueInternal(name, value);
+}
+
+void
+StatsMgr::setValue(const string& name, const string& value) {
+ MultiThreadingLock lock(*mutex_);
+ setValueInternal(name, value);
+}
+
+void
+StatsMgr::addValue(const string& name, const int64_t value) {
+ MultiThreadingLock lock(*mutex_);
+ addValueInternal(name, value);
+}
+
+void
+StatsMgr::addValue(const string& name, const int128_t& value) {
+ MultiThreadingLock lock(*mutex_);
+ addValueInternal(name, value);
+}
+
+void
+StatsMgr::addValue(const string& name, const double value) {
+ MultiThreadingLock lock(*mutex_);
+ addValueInternal(name, value);
+}
+
+void
+StatsMgr::addValue(const string& name, const StatsDuration& value) {
+ MultiThreadingLock lock(*mutex_);
+ addValueInternal(name, value);
+}
+
+void
+StatsMgr::addValue(const string& name, const string& value) {
+ MultiThreadingLock lock(*mutex_);
+ addValueInternal(name, value);
+}
+
+ObservationPtr
+StatsMgr::getObservation(const string& name) const {
+ MultiThreadingLock lock(*mutex_);
+ return (getObservationInternal(name));
+}
+
+ObservationPtr
+StatsMgr::getObservationInternal(const string& name) const {
+ /// @todo: Implement contexts.
+ // Currently we keep everything in a global context.
+ return (global_->get(name));
+}
+
+void
+StatsMgr::addObservation(const ObservationPtr& stat) {
+ MultiThreadingLock lock(*mutex_);
+ addObservationInternal(stat);
+}
+
+void
+StatsMgr::addObservationInternal(const ObservationPtr& stat) {
+ /// @todo: Implement contexts.
+ // Currently we keep everything in a global context.
+ global_->add(stat);
+}
+
+bool
+StatsMgr::deleteObservation(const string& name) {
+ MultiThreadingLock lock(*mutex_);
+ return (deleteObservationInternal(name));
+}
+
+bool
+StatsMgr::deleteObservationInternal(const string& name) {
+ /// @todo: Implement contexts.
+ // Currently we keep everything in a global context.
+ return (global_->del(name));
+}
+
+bool
+StatsMgr::setMaxSampleAge(const string& name, const StatsDuration& duration) {
+ MultiThreadingLock lock(*mutex_);
+ return (setMaxSampleAgeInternal(name, duration));
+}
+
+bool
+StatsMgr::setMaxSampleAgeInternal(const string& name,
+ const StatsDuration& duration) {
+ ObservationPtr obs = getObservationInternal(name);
+ if (obs) {
+ obs->setMaxSampleAge(duration);
+ return (true);
+ }
+ return (false);
+}
+
+bool
+StatsMgr::setMaxSampleCount(const string& name, uint32_t max_samples) {
+ MultiThreadingLock lock(*mutex_);
+ return (setMaxSampleCountInternal(name, max_samples));
+}
+
+bool
+StatsMgr::setMaxSampleCountInternal(const string& name,
+ uint32_t max_samples) {
+ ObservationPtr obs = getObservationInternal(name);
+ if (obs) {
+ obs->setMaxSampleCount(max_samples);
+ return (true);
+ }
+ return (false);
+}
+
+void
+StatsMgr::setMaxSampleAgeAll(const StatsDuration& duration) {
+ MultiThreadingLock lock(*mutex_);
+ setMaxSampleAgeAllInternal(duration);
+}
+
+void
+StatsMgr::setMaxSampleAgeAllInternal(const StatsDuration& duration) {
+ global_->setMaxSampleAgeAll(duration);
+}
+
+void
+StatsMgr::setMaxSampleCountAll(uint32_t max_samples) {
+ MultiThreadingLock lock(*mutex_);
+ setMaxSampleCountAllInternal(max_samples);
+}
+
+void
+StatsMgr::setMaxSampleCountAllInternal(uint32_t max_samples) {
+ global_->setMaxSampleCountAll(max_samples);
+}
+
+void
+StatsMgr::setMaxSampleAgeDefault(const StatsDuration& duration) {
+ MultiThreadingLock lock(*mutex_);
+ setMaxSampleAgeDefaultInternal(duration);
+}
+
+void
+StatsMgr::setMaxSampleAgeDefaultInternal(const StatsDuration& duration) {
+ Observation::setMaxSampleAgeDefault(duration);
+}
+
+void
+StatsMgr::setMaxSampleCountDefault(uint32_t max_samples) {
+ MultiThreadingLock lock(*mutex_);
+ setMaxSampleCountDefaultInternal(max_samples);
+}
+
+void
+StatsMgr::setMaxSampleCountDefaultInternal(uint32_t max_samples) {
+ Observation::setMaxSampleCountDefault(max_samples);
+}
+
+const StatsDuration&
+StatsMgr::getMaxSampleAgeDefault() const {
+ MultiThreadingLock lock(*mutex_);
+ return (getMaxSampleAgeDefaultInternal());
+}
+
+const StatsDuration&
+StatsMgr::getMaxSampleAgeDefaultInternal() const {
+ return (Observation::getMaxSampleAgeDefault());
+}
+
+uint32_t
+StatsMgr::getMaxSampleCountDefault() const {
+ MultiThreadingLock lock(*mutex_);
+ return (getMaxSampleCountDefaultInternal());
+}
+
+uint32_t
+StatsMgr::getMaxSampleCountDefaultInternal() const {
+ return (Observation::getMaxSampleCountDefault());
+}
+
+bool
+StatsMgr::reset(const string& name) {
+ MultiThreadingLock lock(*mutex_);
+ return (resetInternal(name));
+}
+
+bool
+StatsMgr::resetInternal(const string& name) {
+ ObservationPtr obs = getObservationInternal(name);
+ if (obs) {
+ obs->reset();
+ return (true);
+ }
+ return (false);
+}
+
+bool
+StatsMgr::del(const string& name) {
+ MultiThreadingLock lock(*mutex_);
+ return (delInternal(name));
+}
+
+bool
+StatsMgr::delInternal(const string& name) {
+ return (global_->del(name));
+}
+
+void
+StatsMgr::removeAll() {
+ MultiThreadingLock lock(*mutex_);
+ removeAllInternal();
+}
+
+void
+StatsMgr::removeAllInternal() {
+ global_->clear();
+}
+
+ConstElementPtr
+StatsMgr::get(const string& name) const {
+ MultiThreadingLock lock(*mutex_);
+ return (getInternal(name));
+}
+
+ConstElementPtr
+StatsMgr::getInternal(const string& name) const {
+ ElementPtr map = Element::createMap(); // a map
+ ObservationPtr obs = getObservationInternal(name);
+ if (obs) {
+ map->set(name, obs->getJSON()); // that contains observations
+ }
+ return (map);
+}
+
+ConstElementPtr
+StatsMgr::getAll() const {
+ MultiThreadingLock lock(*mutex_);
+ return (getAllInternal());
+}
+
+ConstElementPtr
+StatsMgr::getAllInternal() const {
+ return (global_->getAll());
+}
+
+void
+StatsMgr::resetAll() {
+ MultiThreadingLock lock(*mutex_);
+ resetAllInternal();
+}
+
+void
+StatsMgr::resetAllInternal() {
+ global_->resetAll();
+}
+
+size_t
+StatsMgr::getSize(const string& name) const {
+ MultiThreadingLock lock(*mutex_);
+ return (getSizeInternal(name));
+}
+
+size_t
+StatsMgr::getSizeInternal(const string& name) const {
+ ObservationPtr obs = getObservationInternal(name);
+ if (obs) {
+ return (obs->getSize());
+ }
+ return (0);
+}
+
+size_t
+StatsMgr::count() const {
+ MultiThreadingLock lock(*mutex_);
+ return (countInternal());
+}
+
+size_t
+StatsMgr::countInternal() const {
+ return (global_->size());
+}
+
+ConstElementPtr
+StatsMgr::statisticSetMaxSampleAgeHandler(const string& /*name*/,
+ const ConstElementPtr& params) {
+ string name, error;
+ StatsDuration duration;
+ if (!StatsMgr::getStatName(params, name, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ if (!StatsMgr::getStatDuration(params, duration, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ if (StatsMgr::instance().setMaxSampleAge(name, duration)) {
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "Statistic '" + name + "' duration limit is set."));
+ } else {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "No '" + name + "' statistic found"));
+ }
+}
+
+ConstElementPtr
+StatsMgr::statisticSetMaxSampleCountHandler(const string& /*name*/,
+ const ConstElementPtr& params) {
+ string name, error;
+ uint32_t max_samples;
+ if (!StatsMgr::getStatName(params, name, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ if (!StatsMgr::getStatMaxSamples(params, max_samples, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ if (StatsMgr::instance().setMaxSampleCount(name, max_samples)) {
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "Statistic '" + name + "' count limit is set."));
+ } else {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "No '" + name + "' statistic found"));
+ }
+}
+
+ConstElementPtr
+StatsMgr::statisticGetHandler(const string& /*name*/,
+ const ConstElementPtr& params) {
+ string name, error;
+ if (!StatsMgr::getStatName(params, name, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ StatsMgr::instance().get(name)));
+}
+
+ConstElementPtr
+StatsMgr::statisticResetHandler(const string& /*name*/,
+ const ConstElementPtr& params) {
+ string name, error;
+ if (!StatsMgr::getStatName(params, name, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ if (StatsMgr::instance().reset(name)) {
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "Statistic '" + name + "' reset."));
+ } else {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "No '" + name + "' statistic found"));
+ }
+}
+
+ConstElementPtr
+StatsMgr::statisticRemoveHandler(const string& /*name*/,
+ const ConstElementPtr& params) {
+ string name, error;
+ if (!StatsMgr::getStatName(params, name, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ if (StatsMgr::instance().del(name)) {
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "Statistic '" + name + "' removed."));
+ } else {
+ return (createAnswer(CONTROL_RESULT_ERROR,
+ "No '" + name + "' statistic found"));
+ }
+
+}
+
+ConstElementPtr
+StatsMgr::statisticRemoveAllHandler(const string& /*name*/,
+ const ConstElementPtr& /*params*/) {
+ StatsMgr::instance().removeAll();
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "Warning: statistic-remove-all command is deprecated."
+ " All statistics removed."));
+}
+
+ConstElementPtr
+StatsMgr::statisticGetAllHandler(const string& /*name*/,
+ const ConstElementPtr& /*params*/) {
+ ConstElementPtr all_stats = StatsMgr::instance().getAll();
+ return (createAnswer(CONTROL_RESULT_SUCCESS, all_stats));
+}
+
+ConstElementPtr
+StatsMgr::statisticResetAllHandler(const string& /*name*/,
+ const ConstElementPtr& /*params*/) {
+ StatsMgr::instance().resetAll();
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "All statistics reset to neutral values."));
+}
+
+ConstElementPtr
+StatsMgr::statisticSetMaxSampleAgeAllHandler(const ConstElementPtr& params) {
+ string error;
+ StatsDuration duration;
+ if (!StatsMgr::getStatDuration(params, duration, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ MultiThreadingLock lock(*mutex_);
+ StatsMgr::instance().setMaxSampleCountDefaultInternal(0);
+ StatsMgr::instance().setMaxSampleAgeDefaultInternal(duration);
+ StatsMgr::instance().setMaxSampleAgeAllInternal(duration);
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "All statistics duration limit are set."));
+}
+
+ConstElementPtr
+StatsMgr::statisticSetMaxSampleCountAllHandler(const ConstElementPtr& params) {
+ string error;
+ uint32_t max_samples;
+ if (!StatsMgr::getStatMaxSamples(params, max_samples, error)) {
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ if (max_samples == 0) {
+ error = "'max-samples' parameter must not be zero";
+ return (createAnswer(CONTROL_RESULT_ERROR, error));
+ }
+ MultiThreadingLock lock(*mutex_);
+ StatsMgr::instance().setMaxSampleCountDefaultInternal(max_samples);
+ StatsMgr::instance().setMaxSampleCountAllInternal(max_samples);
+ return (createAnswer(CONTROL_RESULT_SUCCESS,
+ "All statistics count limit are set."));
+}
+
+bool
+StatsMgr::getStatName(const ConstElementPtr& params,
+ string& name,
+ string& reason) {
+ if (!params) {
+ reason = "Missing mandatory 'name' parameter.";
+ return (false);
+ }
+ ConstElementPtr stat_name = params->get("name");
+ if (!stat_name) {
+ reason = "Missing mandatory 'name' parameter.";
+ return (false);
+ }
+ if (stat_name->getType() != Element::string) {
+ reason = "'name' parameter expected to be a string.";
+ return (false);
+ }
+ name = stat_name->stringValue();
+ return (true);
+}
+
+bool
+StatsMgr::getStatDuration(const ConstElementPtr& params,
+ StatsDuration& duration,
+ string& reason) {
+ if (!params) {
+ reason = "Missing mandatory 'duration' parameter.";
+ return (false);
+ }
+ ConstElementPtr stat_duration = params->get("duration");
+ if (!stat_duration) {
+ reason = "Missing mandatory 'duration' parameter.";
+ return (false);
+ }
+ duration = std::chrono::seconds(stat_duration->intValue());
+ return (true);
+}
+
+bool
+StatsMgr::getStatMaxSamples(const ConstElementPtr& params,
+ uint32_t& max_samples,
+ string& reason) {
+ if (!params) {
+ reason = "Missing mandatory 'max-samples' parameter.";
+ return (false);
+ }
+ ConstElementPtr stat_max_samples = params->get("max-samples");
+ if (!stat_max_samples) {
+ reason = "Missing mandatory 'max-samples' parameter.";
+ return (false);
+ }
+ if (stat_max_samples->getType() != Element::integer) {
+ reason = "'max-samples' parameter expected to be an integer.";
+ return (false);
+ }
+ max_samples = stat_max_samples->intValue();
+ return (true);
+}
+
+} // end of namespace stats
+} // end of namespace isc
diff --git a/src/lib/stats/stats_mgr.h b/src/lib/stats/stats_mgr.h
new file mode 100644
index 0000000..f49ab8a
--- /dev/null
+++ b/src/lib/stats/stats_mgr.h
@@ -0,0 +1,805 @@
+// Copyright (C) 2015-2023 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 STATSMGR_H
+#define STATSMGR_H
+
+#include <stats/observation.h>
+#include <stats/context.h>
+#include <util/bigints.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <map>
+#include <mutex>
+#include <string>
+#include <vector>
+#include <sstream>
+
+namespace isc {
+namespace stats {
+
+/// @brief Statistics Manager class
+///
+/// StatsMgr is a singleton class that represents a subsystem that manages
+/// collection, storage and reporting of various types of statistics.
+/// It is also the intended API for both core code and hooks.
+///
+/// As of May 2015, Tomek ran performance benchmarks (see unit-tests in
+/// stats_mgr_unittest.cc with performance in their names) and it seems
+/// the code is able to register ~2.5-3 million observations per second, even
+/// with 1000 different statistics recorded. That seems sufficient for now,
+/// so there is no immediate need to develop any multi-threading solutions
+/// for now. However, should this decision be revised in the future, the
+/// best place for it would to be modify @ref addObservation method here.
+/// It's the common code point that all new observations must pass through.
+/// One possible way to enable multi-threading would be to run a separate
+/// thread handling collection. The main thread would call @ref addValue and
+/// @ref setValue methods that would end up calling @ref addObservation.
+/// That method would pass the data to separate thread to be collected and
+/// would immediately return. Further processing would be mostly as it
+/// is today, except happening in a separate thread. One unsolved issue in
+/// this approach is how to extract data, but that will remain unsolvable
+/// until we get the control socket implementation.
+///
+/// Statistics Manager does not use logging by design. The reasons are:
+/// - performance impact (logging every observation would degrade performance
+/// significantly. While it's possible to log on sufficiently high debug
+/// level, such a log would be not that useful)
+/// - dependency (statistics are intended to be a lightweight library, adding
+/// dependency on libkea-log, which has its own dependencies, including
+/// external log4cplus, is against 'lightweight' design)
+/// - if logging of specific statistics is warranted, it is recommended to
+/// add log entries in the code that calls StatsMgr.
+/// - enabling logging in StatsMgr does not offer fine tuning. It would be
+/// either all or nothing. Adding logging entries only when necessary
+/// in the code that uses StatsMgr gives better granularity.
+///
+/// If this decision is revisited in the future, the most universal places
+/// for adding logging have been marked in @ref addValueInternal and
+/// @ref setValueInternal.
+class StatsMgr : public boost::noncopyable {
+public:
+
+ /// @brief Statistics Manager accessor method.
+ static StatsMgr& instance();
+
+ /// @defgroup producer_methods Methods are used by data producers.
+ ///
+ /// @brief The following methods are used by data producers:
+ ///
+ /// @{
+
+ /// @brief Records absolute integer observation.
+ ///
+ /// @param name name of the observation
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void setValue(const std::string& name, const int64_t value);
+
+ /// @brief Records an absolute big integer observation.
+ ///
+ /// @param name name of the observation
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void setValue(const std::string& name, const isc::util::int128_t& value);
+
+ /// @brief Records absolute floating point observation.
+ ///
+ /// @param name name of the observation
+ /// @param value floating point value observed
+ /// @throw InvalidStatType if statistic is not fp
+ void setValue(const std::string& name, const double value);
+
+ /// @brief Records absolute duration observation.
+ ///
+ /// @param name name of the observation
+ /// @param value duration value observed
+ /// @throw InvalidStatType if statistic is not time duration
+ void setValue(const std::string& name, const StatsDuration& value);
+
+ /// @brief Records absolute string observation.
+ ///
+ /// @param name name of the observation
+ /// @param value string value observed
+ /// @throw InvalidStatType if statistic is not a string
+ void setValue(const std::string& name, const std::string& value);
+
+ /// @brief Records incremental integer observation.
+ ///
+ /// @param name name of the observation
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void addValue(const std::string& name, const int64_t value);
+
+ /// @brief Records an incremental big integer observation.
+ ///
+ /// @param name name of the observation
+ /// @param value integer value observed
+ /// @throw InvalidStatType if statistic is not integer
+ void addValue(const std::string& name, const isc::util::int128_t& value);
+
+ /// @brief Records incremental floating point observation.
+ ///
+ /// @param name name of the observation
+ /// @param value floating point value observed
+ /// @throw InvalidStatType if statistic is not fp
+ void addValue(const std::string& name, const double value);
+
+ /// @brief Records incremental duration observation.
+ ///
+ /// @param name name of the observation
+ /// @param value duration value observed
+ /// @throw InvalidStatType if statistic is not time duration
+ void addValue(const std::string& name, const StatsDuration& value);
+
+ /// @brief Records incremental string observation.
+ ///
+ /// @param name name of the observation
+ /// @param value string value observed
+ /// @throw InvalidStatType if statistic is not a string
+ void addValue(const std::string& name, const std::string& value);
+
+ /// @brief Determines maximum age of samples.
+ ///
+ /// Specifies that statistic name should be stored not as a single value,
+ /// but rather as a set of values. duration determines the timespan.
+ /// Samples older than duration will be discarded. This is time-constrained
+ /// approach. For sample count constrained approach, see @ref
+ /// setMaxSampleCount() below.
+ /// Example:
+ /// To set a statistic to keep observations for the last 5 minutes, call:
+ /// setMaxSampleAge("incoming-packets", StatsDuration::minutes(5));
+ /// to revert statistic to a single value, call:
+ /// setMaxSampleAge("incoming-packets", StatsDuration:zero());
+ ///
+ /// @param name name of the observation
+ /// @param duration determines maximum age of samples
+ /// @return true if successful, false if there's no such statistic
+ bool setMaxSampleAge(const std::string& name, const StatsDuration& duration);
+
+ /// @brief Determines how many samples of a given statistic should be kept.
+ ///
+ /// Specifies that statistic name should be stored not as single value, but
+ /// rather as a set of values. In this form, at most max_samples will be kept.
+ /// When adding max_samples + 1 sample, the oldest sample will be discarded.
+ /// Example:
+ /// To set a statistic to keep the last 100 observations, call:
+ /// setMaxSampleCount("incoming-packets", 100);
+ ///
+ /// @param name name of the observation
+ /// @param max_samples how many samples of a given statistic should be kept
+ /// @return true if successful, false if there's no such statistic
+ bool setMaxSampleCount(const std::string& name, uint32_t max_samples);
+
+ /// @brief Set duration limit for all collected statistics.
+ ///
+ /// @param duration determines maximum age of samples
+ void setMaxSampleAgeAll(const StatsDuration& duration);
+
+ /// @brief Set count limit for all collected statistics.
+ ///
+ /// @param max_samples how many samples of a given statistic should be kept
+ void setMaxSampleCountAll(uint32_t max_samples);
+
+ /// @brief Set default duration limit.
+ ///
+ /// @param duration default maximum age of samples to keep
+ void setMaxSampleAgeDefault(const StatsDuration& duration);
+
+ /// @brief Set default count limit.
+ ///
+ /// @param max_samples default maximum number of samples to keep
+ /// (0 means to disable count limit and enable age limit)
+ void setMaxSampleCountDefault(uint32_t max_samples);
+
+ /// @brief Get default duration limit.
+ ///
+ /// @return default maximum age of samples to keep.
+ const StatsDuration& getMaxSampleAgeDefault() const;
+
+ /// @brief Get default count limit.
+ ///
+ /// @return default maximum number of samples to keep.
+ /// (0 means that count limit was disabled)
+ uint32_t getMaxSampleCountDefault() const;
+
+ /// @}
+
+ /// @defgroup consumer_methods Methods are used by data consumers.
+ ///
+ /// @brief The following methods are used by data consumers:
+ ///
+ /// @{
+
+ /// @brief Resets specified statistic.
+ ///
+ /// This is a convenience function and is equivalent to setValue(name,
+ /// neutral_value), where neutral_value is 0, 0.0 or "".
+ ///
+ /// @param name name of the statistic to be reset.
+ /// @return true if successful, false if there's no such statistic
+ bool reset(const std::string& name);
+
+ /// @brief Removes specified statistic.
+ ///
+ /// @param name name of the statistic to be removed.
+ /// @return true if successful, false if there's no such statistic
+ bool del(const std::string& name);
+
+ /// @brief Resets all collected statistics back to zero.
+ void resetAll();
+
+ /// @brief Removes all collected statistics.
+ /// @note This command was deprecated.
+ void removeAll();
+
+ /// @brief Returns size of specified statistic.
+ ///
+ /// @param name name of the statistic which size should be return.
+ /// @return size of specified statistic, 0 means lack of given statistic.
+ size_t getSize(const std::string& name) const;
+
+ /// @brief Returns number of available statistics.
+ ///
+ /// @return number of recorded statistics.
+ size_t count() const;
+
+ /// @brief Returns a single statistic as a JSON structure.
+ ///
+ /// @return JSON structures representing a single statistic
+ isc::data::ConstElementPtr get(const std::string& name) const;
+
+ /// @brief Returns all statistics as a JSON structure.
+ ///
+ /// @return JSON structures representing all statistics
+ isc::data::ConstElementPtr getAll() const;
+
+ /// @}
+
+ /// @brief Returns an observation.
+ ///
+ /// Used in testing only. Production code should use @ref get() method
+ /// when the value is dereferenced.
+ /// Calls @ref getObservationInternal() method in a thread safe context.
+ ///
+ /// @param name name of the statistic
+ /// @return Pointer to the Observation object
+ ObservationPtr getObservation(const std::string& name) const;
+
+ /// @brief Returns an observation in a thread safe context.
+ ///
+ /// Used in testing only. Production code should use @ref get() method
+ /// when the value is dereferenced. Should be called in a thread safe context.
+ ///
+ /// @param name name of the statistic
+ /// @return Pointer to the Observation object
+ ObservationPtr getObservationInternal(const std::string& name) const;
+
+ /// @brief Generates statistic name in a given context
+ ///
+ /// Example:
+ /// @code
+ /// generateName("subnet", 123, "received-packets");
+ /// @endcode
+ /// will return subnet[123].received-packets. Any printable type
+ /// can be used as index.
+ ///
+ /// @tparam Type any type that can be used to index contexts
+ /// @param context name of the context (e.g. 'subnet')
+ /// @param index value used for indexing contexts (e.g. subnet_id)
+ /// @param stat_name name of the statistic
+ /// @return returns full statistic name in form context[index].stat_name
+ template<typename Type>
+ static std::string generateName(const std::string& context, Type index,
+ const std::string& stat_name) {
+ std::stringstream name;
+ name << context << "[" << index << "]." << stat_name;
+ return (name.str());
+ }
+
+ /// @defgroup command_methods Methods are used to handle commands.
+ ///
+ /// @brief The following methods are used to handle commands:
+ ///
+ /// @{
+
+ /// @brief Handles statistic-get command
+ ///
+ /// This method handles statistic-get command, which returns value
+ /// of a given statistic). It expects one parameter stored in params map:
+ /// name: name of the statistic
+ ///
+ /// Example params structure:
+ /// {
+ /// "name": "packets-received"
+ /// }
+ ///
+ /// @param name name of the command (ignored, should be "statistic-get")
+ /// @param params structure containing a map that contains "name"
+ /// @return answer containing details of specified statistic
+ static isc::data::ConstElementPtr
+ statisticGetHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-reset command
+ ///
+ /// This method handles statistic-reset command, which resets value
+ /// of a given statistic. It expects one parameter stored in params map:
+ /// name: name of the statistic
+ ///
+ /// Example params structure:
+ /// {
+ /// "name": "packets-received"
+ /// }
+ ///
+ /// @param name name of the command (ignored, should be "statistic-reset")
+ /// @param params structure containing a map that contains "name"
+ /// @return answer containing confirmation
+ static isc::data::ConstElementPtr
+ statisticResetHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-remove command
+ ///
+ /// This method handles statistic-reset command, which removes a given
+ /// statistic completely. It expects one parameter stored in params map:
+ /// name: name of the statistic
+ ///
+ /// Example params structure:
+ /// {
+ /// "name": "packets-received"
+ /// }
+ ///
+ /// @param name name of the command (ignored, should be "statistic-remove")
+ /// @param params structure containing a map that contains "name" element
+ /// @return answer containing confirmation
+ static isc::data::ConstElementPtr
+ statisticRemoveHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-sample-age-set command
+ ///
+ /// This method handles statistic-sample-age-set command,
+ /// which sets max_sample_age_ limit of a given statistic
+ /// and leaves max_sample_count_ disabled.
+ /// It expects two parameters stored in params map:
+ /// name: name of the statistic
+ /// duration: time limit expressed as a number of seconds
+ ///
+ /// Example params structure:
+ /// {
+ /// "name": "packets-received",
+ /// "duration": 1245
+ /// }
+ ///
+ /// @param name name of the command (ignored, should be "statistic-sample-age-set")
+ /// @param params structure containing a map that contains "name" and "duration"
+ /// @return answer containing information about successfully setup limit of statistic
+ static isc::data::ConstElementPtr
+ statisticSetMaxSampleAgeHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-sample-count-set command
+ ///
+ /// This method handles statistic-sample-count-set command,
+ /// which sets max_sample_count_ limit of a given statistic
+ /// and leaves max_sample_age_ disabled.
+ /// It expects two parameters stored in params map:
+ /// name: name of the statistic
+ /// max-samples: count limit
+ ///
+ /// Example params structure:
+ /// {
+ /// "name": "packets-received",
+ /// "max-samples": 15
+ /// }
+ ///
+ /// @param name name of the command (ignored, should be "statistic-sample-count-set")
+ /// @param params structure containing a map that contains "name" and "max-samples"
+ /// @return answer containing information about successfully setup limit of statistic
+ static isc::data::ConstElementPtr
+ statisticSetMaxSampleCountHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-get-all command
+ ///
+ /// This method handles statistic-get-all command, which returns values
+ /// of all statistics. Params parameter is ignored.
+ ///
+ /// @param name name of the command (ignored, should be "statistic-get-all")
+ /// @param params ignored
+ /// @return answer containing values of all statistic
+ static isc::data::ConstElementPtr
+ statisticGetAllHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-reset-all command
+ ///
+ /// This method handles statistic-reset-all command, which sets values of
+ /// all statistics back to zero. Params parameter is ignored.
+ ///
+ /// @param name name of the command (ignored, should be "statistic-reset-all")
+ /// @param params ignored
+ /// @return answer confirming success of this operation
+ static isc::data::ConstElementPtr
+ statisticResetAllHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-remove-all command
+ ///
+ /// @note The statistic-remove-all command was deprecated.
+ ///
+ /// This method handles statistic-remove-all command, which removes all
+ /// statistics. Params parameter is ignored.
+ ///
+ /// @param name name of the command (ignored, should be "statistic-remove-all")
+ /// @param params ignored
+ /// @return answer confirming success of this operation
+ static isc::data::ConstElementPtr
+ statisticRemoveAllHandler(const std::string& name,
+ const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-sample-age-set-all command
+ ///
+ /// This method handles statistic-sample-age-set-all command,
+ /// which sets max_sample_age_ limit to all statistics and the default.
+ /// It expects one parameter stored in params map:
+ /// duration: limit expressed as a number of seconds
+ ///
+ /// Example params structure:
+ /// {
+ /// "duration": 1245
+ /// }
+ ///
+ /// @param params structure containing a map that contains "duration"
+ /// @return answer confirming success of this operation
+ isc::data::ConstElementPtr
+ statisticSetMaxSampleAgeAllHandler(const isc::data::ConstElementPtr& params);
+
+ /// @brief Handles statistic-sample-count-set-all command
+ ///
+ /// This method handles statistic-sample-count-set-all command,
+ /// which sets max_sample_count_ limit of all statistics and the default.
+ /// It expects one parameter stored in params map:
+ /// max-samples: count limit
+ /// The value 0 is out of range.
+ ///
+ /// Example params structure:
+ /// {
+ /// "max-samples": 15
+ /// }
+ ///
+ /// @param params structure containing a map that contains "max-samples"
+ /// @return answer confirming success of this operation
+ isc::data::ConstElementPtr
+ statisticSetMaxSampleCountAllHandler(const isc::data::ConstElementPtr& params);
+
+ /// @}
+
+private:
+
+ /// @private
+
+ /// @brief Private constructor.
+ ///
+ /// StatsMgr is a singleton. It should be accessed using @ref instance
+ /// method.
+ StatsMgr();
+
+ /// @public
+
+ /// @brief Sets a given statistic to specified value (internal version).
+ ///
+ /// This template method sets statistic identified by name to a value
+ /// specified by value. This internal method is used by public @ref setValue
+ /// methods.
+ ///
+ /// @tparam DataType one of int64_t, double, StatsDuration or string
+ /// @param name name of the statistic
+ /// @param value specified statistic will be set to this value
+ /// @throw InvalidStatType is statistic exists and has a different type.
+ template<typename DataType>
+ void setValueInternal(const std::string& name, DataType value) {
+ // If we want to log each observation, here would be the best place for it.
+ ObservationPtr stat = getObservationInternal(name);
+ if (stat) {
+ stat->setValue(value);
+ } else {
+ stat.reset(new Observation(name, value));
+ addObservationInternal(stat);
+ }
+ }
+
+ /// @public
+
+ /// @brief Adds specified value to a given statistic (internal version).
+ ///
+ /// This template method adds specified value to a given statistic (identified
+ /// by name to a value). This internal method is used by public @ref setValue
+ /// methods.
+ ///
+ /// @tparam DataType one of int64_t, double, StatsDuration or string
+ /// @param name name of the statistic
+ /// @param value specified statistic will be set to this value
+ /// @throw InvalidStatType is statistic exists and has a different type.
+ template<typename DataType>
+ void addValueInternal(const std::string& name, DataType value) {
+ // If we want to log each observation, here would be the best place for it.
+ ObservationPtr existing = getObservationInternal(name);
+ if (!existing) {
+ // We tried to add to a non-existing statistic. We can recover from
+ // that. Simply add the new incremental value as a new statistic and
+ // we're done.
+ setValueInternal(name, value);
+ return;
+ } else {
+ // Let's hope it is of correct type. If not, the underlying
+ // addValue() method will throw.
+ existing->addValue(value);
+ }
+ }
+
+ /// @public
+
+ /// @brief Adds a new observation.
+ ///
+ /// That's an utility method used by public @ref setValue() and
+ /// @ref addValue() methods.
+ /// Calls @ref addObservationInternal() method in a thread safe context.
+ ///
+ /// @param stat observation
+ void addObservation(const ObservationPtr& stat);
+
+ /// @public
+
+ /// @brief Adds a new observation in a thread safe context.
+ ///
+ /// That's an utility method used by public @ref setValue() and
+ /// @ref addValue() methods.
+ /// Should be called in a thread safe context.
+ ///
+ /// @param stat observation
+ void addObservationInternal(const ObservationPtr& stat);
+
+ /// @private
+
+ /// @brief Tries to delete an observation.
+ ///
+ /// Calls @ref deleteObservationInternal() method in a thread safe context.
+ ///
+ /// @param name of the statistic to be deleted
+ /// @return true if deleted, false if not found
+ bool deleteObservation(const std::string& name);
+
+ /// @private
+
+ /// @brief Tries to delete an observation in a thread safe context.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param name of the statistic to be deleted
+ /// @return true if deleted, false if not found
+ bool deleteObservationInternal(const std::string& name);
+
+ /// @private
+
+ /// @brief Determines maximum age of samples.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param name name of the observation
+ /// @param duration determines maximum age of samples
+ /// @return true if successful, false if there's no such statistic
+ bool setMaxSampleAgeInternal(const std::string& name, const StatsDuration& duration);
+
+ /// @private
+
+ /// @brief Determines how many samples of a given statistic should be kept.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param name name of the observation
+ /// @param max_samples how many samples of a given statistic should be kept
+ /// @return true if successful, false if there's no such statistic
+ bool setMaxSampleCountInternal(const std::string& name, uint32_t max_samples);
+
+ /// @private
+
+ /// @brief Set duration limit for all collected statistics.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param duration determines maximum age of samples
+ void setMaxSampleAgeAllInternal(const StatsDuration& duration);
+
+ /// @private
+
+ /// @brief Set count limit for all collected statistics.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param max_samples how many samples of a given statistic should be kept
+ void setMaxSampleCountAllInternal(uint32_t max_samples);
+
+ /// @private
+
+ /// @brief Set default duration limit.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param duration default maximum age of samples to keep.
+ void setMaxSampleAgeDefaultInternal(const StatsDuration& duration);
+
+ /// @brief Set default count limit.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param max_samples default maximum number of samples to keep.
+ /// (0 means to disable count limit and enable age limit)
+ void setMaxSampleCountDefaultInternal(uint32_t max_samples);
+
+ /// @private
+
+ /// @brief Get default duration limit.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @return default maximum age of samples to keep.
+ const StatsDuration& getMaxSampleAgeDefaultInternal() const;
+
+ /// @brief Get default count limit.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @return default maximum number of samples to keep.
+ /// (0 means that count limit was disabled)
+ uint32_t getMaxSampleCountDefaultInternal() const;
+
+ /// @private
+
+ /// @brief Resets specified statistic.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param name name of the statistic to be reset.
+ /// @return true if successful, false if there's no such statistic
+ bool resetInternal(const std::string& name);
+
+ /// @private
+
+ /// @brief Removes specified statistic.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param name name of the statistic to be removed.
+ /// @return true if successful, false if there's no such statistic
+ bool delInternal(const std::string& name);
+
+ /// @private
+
+ /// @brief Resets all collected statistics back to zero.
+ ///
+ /// Should be called in a thread safe context.
+ void resetAllInternal();
+
+ /// @private
+
+ /// @brief Removes all collected statistics.
+ ///
+ /// Should be called in a thread safe context.
+ void removeAllInternal();
+
+ /// @private
+
+ /// @brief Returns size of specified statistic.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @param name name of the statistic which size should be return.
+ /// @return size of specified statistic, 0 means lack of given statistic.
+ size_t getSizeInternal(const std::string& name) const;
+
+ /// @private
+
+ /// @brief Returns number of available statistics.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @return number of recorded statistics.
+ size_t countInternal() const;
+
+ /// @private
+
+ /// @brief Returns a single statistic as a JSON structure.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @return JSON structures representing a single statistic
+ isc::data::ConstElementPtr getInternal(const std::string& name) const;
+
+ /// @private
+
+ /// @brief Returns all statistics as a JSON structure.
+ ///
+ /// Should be called in a thread safe context.
+ ///
+ /// @return JSON structures representing all statistics
+ isc::data::ConstElementPtr getAllInternal() const;
+
+ /// @private
+
+ /// @brief Utility method that attempts to extract statistic name
+ ///
+ /// This method attempts to extract statistic name from the params
+ /// structure. It is expected to be a map that contains 'name' element,
+ /// that is of type string. If present as expected, statistic name
+ /// set and true is returned. If missing or is of incorrect type, the reason
+ /// is specified in reason parameter and false is returned.
+ ///
+ /// @param params parameters structure received in command
+ /// @param name [out] name of the statistic (if no error detected)
+ /// @param reason [out] failure reason (if error is detected)
+ /// @return true (if everything is ok), false otherwise
+ static bool getStatName(const isc::data::ConstElementPtr& params,
+ std::string& name,
+ std::string& reason);
+
+ /// @private
+
+ /// @brief Utility method that attempts to extract duration limit for
+ /// a given statistic
+ ///
+ /// This method attempts to extract duration limit for a given statistic
+ /// from the params structure.
+ /// It is expected to be a map that contains four 'duration' elements: 'hours',
+ /// 'minutes', 'seconds' and 'milliseconds'
+ /// all are of type int. If present as expected, statistic duration
+ /// limit is set and true is returned.
+ /// If any of these four parameters is missing or is of incorrect type,
+ /// the reason is specified in reason parameter and false is returned.
+ ///
+ /// @param params parameters structure received in command
+ /// @param duration [out] duration limit for the statistic (if no error detected)
+ /// @param reason [out] failure reason (if error is detected)
+ /// @return true (if everything is ok), false otherwise
+ static bool getStatDuration(const isc::data::ConstElementPtr& params,
+ StatsDuration& duration,
+ std::string& reason);
+
+ /// @private
+
+ /// @brief Utility method that attempts to extract count limit for
+ /// a given statistic
+ ///
+ /// This method attempts to extract count limit for a given statistic
+ /// from the params structure.
+ /// It is expected to be a map that contains 'max-samples' element,
+ /// that is of type int. If present as expected, statistic count
+ /// limit (max_samples) is set and true is returned.
+ /// If missing or is of incorrect type, the reason is specified in reason
+ /// parameter and false is returned.
+ ///
+ /// @param params parameters structure received in command
+ /// @param max_samples [out] count limit for the statistic (if no error detected)
+ /// @param reason [out] failure reason (if error is detected)
+ /// @return true (if everything is ok), false otherwise
+ static bool getStatMaxSamples(const isc::data::ConstElementPtr& params,
+ uint32_t& max_samples,
+ std::string& reason);
+
+ /// @brief This is a global context. All statistics will initially be stored here.
+ StatContextPtr global_;
+
+ /// @brief The mutex used to protect internal state.
+ const boost::scoped_ptr<std::mutex> mutex_;
+};
+
+} // namespace stats
+} // namespace isc
+
+#endif // STATS_MGR
diff --git a/src/lib/stats/tests/Makefile.am b/src/lib/stats/tests/Makefile.am
new file mode 100644
index 0000000..138a657
--- /dev/null
+++ b/src/lib/stats/tests/Makefile.am
@@ -0,0 +1,37 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+if HAVE_GTEST
+
+TESTS = libstats_unittests
+
+libstats_unittests_SOURCES = run_unittests.cc
+libstats_unittests_SOURCES += observation_unittest.cc
+libstats_unittests_SOURCES += context_unittest.cc
+libstats_unittests_SOURCES += stats_mgr_unittest.cc
+
+libstats_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libstats_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+libstats_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+
+libstats_unittests_LDADD = $(top_builddir)/src/lib/stats/libkea-stats.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+libstats_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libstats_unittests_LDADD += $(LOG4CPLUS_LIBS) $(GTEST_LDADD) $(BOOST_LIBS)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/stats/tests/Makefile.in b/src/lib/stats/tests/Makefile.in
new file mode 100644
index 0000000..beee81b
--- /dev/null
+++ b/src/lib/stats/tests/Makefile.in
@@ -0,0 +1,1042 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_GTEST_TRUE@TESTS = libstats_unittests$(EXEEXT)
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/lib/stats/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = libstats_unittests$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am__libstats_unittests_SOURCES_DIST = run_unittests.cc \
+ observation_unittest.cc context_unittest.cc \
+ stats_mgr_unittest.cc
+@HAVE_GTEST_TRUE@am_libstats_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ libstats_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libstats_unittests-observation_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libstats_unittests-context_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ libstats_unittests-stats_mgr_unittest.$(OBJEXT)
+libstats_unittests_OBJECTS = $(am_libstats_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libstats_unittests_DEPENDENCIES = $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(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 =
+libstats_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) \
+ $(libstats_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)/libstats_unittests-context_unittest.Po \
+ ./$(DEPDIR)/libstats_unittests-observation_unittest.Po \
+ ./$(DEPDIR)/libstats_unittests-run_unittests.Po \
+ ./$(DEPDIR)/libstats_unittests-stats_mgr_unittest.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 =
+SOURCES = $(libstats_unittests_SOURCES)
+DIST_SOURCES = $(am__libstats_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@libstats_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ observation_unittest.cc context_unittest.cc \
+@HAVE_GTEST_TRUE@ stats_mgr_unittest.cc
+@HAVE_GTEST_TRUE@libstats_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libstats_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@libstats_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libstats_unittests_LDADD = $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(GTEST_LDADD) \
+@HAVE_GTEST_TRUE@ $(BOOST_LIBS)
+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/stats/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/stats/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
+
+libstats_unittests$(EXEEXT): $(libstats_unittests_OBJECTS) $(libstats_unittests_DEPENDENCIES) $(EXTRA_libstats_unittests_DEPENDENCIES)
+ @rm -f libstats_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(libstats_unittests_LINK) $(libstats_unittests_OBJECTS) $(libstats_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libstats_unittests-context_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libstats_unittests-observation_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libstats_unittests-run_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libstats_unittests-stats_mgr_unittest.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 $@ $<
+
+libstats_unittests-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libstats_unittests-run_unittests.Tpo -c -o libstats_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libstats_unittests-run_unittests.Tpo $(DEPDIR)/libstats_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libstats_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc
+
+libstats_unittests-run_unittests.obj: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libstats_unittests-run_unittests.Tpo -c -o libstats_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)/libstats_unittests-run_unittests.Tpo $(DEPDIR)/libstats_unittests-run_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libstats_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi`
+
+libstats_unittests-observation_unittest.o: observation_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-observation_unittest.o -MD -MP -MF $(DEPDIR)/libstats_unittests-observation_unittest.Tpo -c -o libstats_unittests-observation_unittest.o `test -f 'observation_unittest.cc' || echo '$(srcdir)/'`observation_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libstats_unittests-observation_unittest.Tpo $(DEPDIR)/libstats_unittests-observation_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='observation_unittest.cc' object='libstats_unittests-observation_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-observation_unittest.o `test -f 'observation_unittest.cc' || echo '$(srcdir)/'`observation_unittest.cc
+
+libstats_unittests-observation_unittest.obj: observation_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-observation_unittest.obj -MD -MP -MF $(DEPDIR)/libstats_unittests-observation_unittest.Tpo -c -o libstats_unittests-observation_unittest.obj `if test -f 'observation_unittest.cc'; then $(CYGPATH_W) 'observation_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/observation_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libstats_unittests-observation_unittest.Tpo $(DEPDIR)/libstats_unittests-observation_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='observation_unittest.cc' object='libstats_unittests-observation_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-observation_unittest.obj `if test -f 'observation_unittest.cc'; then $(CYGPATH_W) 'observation_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/observation_unittest.cc'; fi`
+
+libstats_unittests-context_unittest.o: context_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-context_unittest.o -MD -MP -MF $(DEPDIR)/libstats_unittests-context_unittest.Tpo -c -o libstats_unittests-context_unittest.o `test -f 'context_unittest.cc' || echo '$(srcdir)/'`context_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libstats_unittests-context_unittest.Tpo $(DEPDIR)/libstats_unittests-context_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='context_unittest.cc' object='libstats_unittests-context_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-context_unittest.o `test -f 'context_unittest.cc' || echo '$(srcdir)/'`context_unittest.cc
+
+libstats_unittests-context_unittest.obj: context_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-context_unittest.obj -MD -MP -MF $(DEPDIR)/libstats_unittests-context_unittest.Tpo -c -o libstats_unittests-context_unittest.obj `if test -f 'context_unittest.cc'; then $(CYGPATH_W) 'context_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/context_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libstats_unittests-context_unittest.Tpo $(DEPDIR)/libstats_unittests-context_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='context_unittest.cc' object='libstats_unittests-context_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-context_unittest.obj `if test -f 'context_unittest.cc'; then $(CYGPATH_W) 'context_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/context_unittest.cc'; fi`
+
+libstats_unittests-stats_mgr_unittest.o: stats_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-stats_mgr_unittest.o -MD -MP -MF $(DEPDIR)/libstats_unittests-stats_mgr_unittest.Tpo -c -o libstats_unittests-stats_mgr_unittest.o `test -f 'stats_mgr_unittest.cc' || echo '$(srcdir)/'`stats_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libstats_unittests-stats_mgr_unittest.Tpo $(DEPDIR)/libstats_unittests-stats_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stats_mgr_unittest.cc' object='libstats_unittests-stats_mgr_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-stats_mgr_unittest.o `test -f 'stats_mgr_unittest.cc' || echo '$(srcdir)/'`stats_mgr_unittest.cc
+
+libstats_unittests-stats_mgr_unittest.obj: stats_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -MT libstats_unittests-stats_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/libstats_unittests-stats_mgr_unittest.Tpo -c -o libstats_unittests-stats_mgr_unittest.obj `if test -f 'stats_mgr_unittest.cc'; then $(CYGPATH_W) 'stats_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stats_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libstats_unittests-stats_mgr_unittest.Tpo $(DEPDIR)/libstats_unittests-stats_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stats_mgr_unittest.cc' object='libstats_unittests-stats_mgr_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) $(libstats_unittests_CPPFLAGS) $(CPPFLAGS) $(libstats_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libstats_unittests-stats_mgr_unittest.obj `if test -f 'stats_mgr_unittest.cc'; then $(CYGPATH_W) 'stats_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stats_mgr_unittest.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:
+
+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)/libstats_unittests-context_unittest.Po
+ -rm -f ./$(DEPDIR)/libstats_unittests-observation_unittest.Po
+ -rm -f ./$(DEPDIR)/libstats_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libstats_unittests-stats_mgr_unittest.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)/libstats_unittests-context_unittest.Po
+ -rm -f ./$(DEPDIR)/libstats_unittests-observation_unittest.Po
+ -rm -f ./$(DEPDIR)/libstats_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/libstats_unittests-stats_mgr_unittest.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/stats/tests/context_unittest.cc b/src/lib/stats/tests/context_unittest.cc
new file mode 100644
index 0000000..92c754c
--- /dev/null
+++ b/src/lib/stats/tests/context_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (C) 2015-2020 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 <stats/context.h>
+#include <gtest/gtest.h>
+#include <util/chrono_time_utils.h>
+#include <string>
+
+using namespace isc::data;
+using namespace isc::stats;
+using namespace std;
+using namespace std::chrono;
+
+// Basic test that checks get, add, del methods
+TEST(ContextTest, basic) {
+
+ // Let's create a couple observations. Using floating point,
+ // as they're easiest to initialize.
+ ObservationPtr a(new Observation("alpha", 1.11));
+ ObservationPtr b(new Observation("beta", 2.22));
+ ObservationPtr c(new Observation("gamma", 3.33));
+ string expected_a = a->getJSON()->str();
+ string expected_b = b->getJSON()->str();
+ string expected_c = c->getJSON()->str();
+
+
+ // Context where we will store the observations.
+ StatContext ctx;
+
+ // By default the context does not hold any statistics.
+ EXPECT_EQ(0, ctx.size());
+
+ // It should be possible to add 'a' statistic
+ EXPECT_NO_THROW(ctx.add(a));
+
+ // We can't add a duplicate.
+ EXPECT_THROW(ctx.add(a), DuplicateStat);
+
+ // It should be ok to add other statistics
+ EXPECT_NO_THROW(ctx.add(b));
+ EXPECT_NO_THROW(ctx.add(c));
+
+ // By now we should have 3 statistics recorded
+ EXPECT_EQ(3, ctx.size());
+
+ // Let's try to retrieve them
+ ObservationPtr from_ctx;
+ EXPECT_NO_THROW(from_ctx = ctx.get("alpha"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_EQ(expected_a, from_ctx->getJSON()->str());
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("beta"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_EQ(expected_b, from_ctx->getJSON()->str());
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("gamma"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_EQ(expected_c, from_ctx->getJSON()->str());
+
+ // Let's try to retrieve non-existing stat
+ EXPECT_NO_THROW(from_ctx = ctx.get("delta"));
+ EXPECT_FALSE(from_ctx);
+
+ // Now delete one of the stats...
+ EXPECT_TRUE(ctx.del("beta"));
+
+ // ... and check that it's really gone.
+ EXPECT_FALSE(ctx.get("beta"));
+
+ // Attempt to delete non-existing stat should fail.
+ EXPECT_FALSE(ctx.del("beta"));
+
+ ConstElementPtr result;
+ EXPECT_NO_THROW(result = ctx.getAll());
+
+ ASSERT_TRUE(result);
+ ElementPtr expected_result = Element::createMap();
+ expected_result->set("alpha", a->getJSON());
+ expected_result->set("gamma", c->getJSON());
+ EXPECT_EQ(result->str(), expected_result->str());
+
+ // Reset all statistics.
+ EXPECT_NO_THROW(ctx.resetAll());
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("alpha"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_NE(expected_a, from_ctx->getJSON()->str());
+ EXPECT_EQ(0.0, a->getFloat().first);
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("gamma"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_NE(expected_c, from_ctx->getJSON()->str());
+ EXPECT_EQ(0.0, c->getFloat().first);
+
+ // Set sample count for all statistics
+ EXPECT_NO_THROW(ctx.setMaxSampleCountAll(50));
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("alpha"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_EQ(from_ctx->getMaxSampleCount().second, 50);
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("gamma"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_EQ(from_ctx->getMaxSampleCount().second, 50);
+
+ // Set sample age for all statistics
+ const StatsDuration& dur(minutes(4) + seconds(5) + milliseconds(3));
+ EXPECT_NO_THROW(ctx.setMaxSampleAgeAll(dur));
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("alpha"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_EQ(from_ctx->getMaxSampleAge().second, dur);
+
+ EXPECT_NO_THROW(from_ctx = ctx.get("gamma"));
+ ASSERT_TRUE(from_ctx);
+ EXPECT_EQ(from_ctx->getMaxSampleAge().second, dur);
+
+ // Clear all statistics.
+ EXPECT_NO_THROW(ctx.clear());
+ EXPECT_EQ(0, ctx.size());
+}
diff --git a/src/lib/stats/tests/observation_unittest.cc b/src/lib/stats/tests/observation_unittest.cc
new file mode 100644
index 0000000..63fbece
--- /dev/null
+++ b/src/lib/stats/tests/observation_unittest.cc
@@ -0,0 +1,717 @@
+// Copyright (C) 2015-2023 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 <stats/observation.h>
+#include <util/chrono_time_utils.h>
+#include <util/bigints.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+#include <unistd.h>
+
+using namespace isc;
+using namespace isc::stats;
+using namespace isc::util;
+using namespace std::chrono;
+
+namespace {
+
+static const StatsDuration& dur1234(hours(1) + minutes(2) + seconds(3) +
+ milliseconds(4));
+static const StatsDuration& dur5678(hours(5) + minutes(6) + seconds(7) +
+ milliseconds(8));
+static const StatsDuration& dur681012(hours(6) + minutes(8) + seconds(10) +
+ milliseconds(12));
+static const StatsDuration& dur453(minutes(4) + seconds(5) + milliseconds(3));
+
+// This test verifies that the number of seconds can be retrieved.
+TEST(StatsDurationTest, toSeconds) {
+ StatsDuration dur = StatsDuration::zero();
+ dur += hours(1) + minutes(1) + seconds(1) + milliseconds(1);
+ EXPECT_EQ(3661, toSeconds(dur));
+}
+
+/// @brief Test class for Observation
+///
+/// This simple fixture class initializes four observations:
+/// a (integer), b (float), c(time duration) and d (string).
+class ObservationTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ /// Initializes four observations.
+ ObservationTest() :
+ a("alpha", static_cast<int64_t>(1234)), // integer
+ b("beta", 12.34), // float
+ c("gamma", dur1234), // duration
+ d("delta", "1234"), // string
+ e("epsilon", int128_t(12e34)) { // big integer
+ }
+
+ Observation a;
+ Observation b;
+ Observation c;
+ Observation d;
+ Observation e;
+};
+
+// Basic tests for the Observation constructors. This test checks whether
+// parameters passed to the constructor initialize the object properly.
+TEST_F(ObservationTest, constructor) {
+ EXPECT_EQ(Observation::STAT_INTEGER, a.getType());
+ EXPECT_EQ(Observation::STAT_FLOAT, b.getType());
+ EXPECT_EQ(Observation::STAT_DURATION, c.getType());
+ EXPECT_EQ(Observation::STAT_STRING, d.getType());
+ EXPECT_EQ(Observation::STAT_BIG_INTEGER, e.getType());
+
+ EXPECT_EQ(1234, a.getInteger().first);
+ EXPECT_EQ(12.34, b.getFloat().first);
+ EXPECT_EQ(dur1234, c.getDuration().first);
+ EXPECT_EQ("1234", d.getString().first);
+ EXPECT_EQ(int128_t(12e34), e.getBigInteger().first);
+
+ // Let's check that attempting to get a different type
+ // than used will cause an exception.
+ EXPECT_THROW(a.getFloat(), InvalidStatType);
+ EXPECT_THROW(a.getDuration(), InvalidStatType);
+ EXPECT_THROW(a.getString(), InvalidStatType);
+ EXPECT_THROW(a.getBigInteger(), InvalidStatType);
+
+ EXPECT_THROW(b.getInteger(), InvalidStatType);
+ EXPECT_THROW(b.getDuration(), InvalidStatType);
+ EXPECT_THROW(b.getString(), InvalidStatType);
+ EXPECT_THROW(b.getBigInteger(), InvalidStatType);
+
+ EXPECT_THROW(c.getInteger(), InvalidStatType);
+ EXPECT_THROW(c.getFloat(), InvalidStatType);
+ EXPECT_THROW(c.getString(), InvalidStatType);
+ EXPECT_THROW(c.getBigInteger(), InvalidStatType);
+
+ EXPECT_THROW(d.getInteger(), InvalidStatType);
+ EXPECT_THROW(d.getFloat(), InvalidStatType);
+ EXPECT_THROW(d.getDuration(), InvalidStatType);
+ EXPECT_THROW(d.getBigInteger(), InvalidStatType);
+
+ EXPECT_THROW(e.getInteger(), InvalidStatType);
+ EXPECT_THROW(e.getFloat(), InvalidStatType);
+ EXPECT_THROW(e.getDuration(), InvalidStatType);
+ EXPECT_THROW(e.getString(), InvalidStatType);
+}
+
+// This test checks whether it is possible to set to an absolute value for all
+// given types.
+TEST_F(ObservationTest, setValue) {
+ EXPECT_NO_THROW(a.setValue(static_cast<int64_t>(5678)));
+ EXPECT_NO_THROW(b.setValue(56e+78));
+ EXPECT_NO_THROW(c.setValue(dur5678));
+ EXPECT_NO_THROW(d.setValue("fiveSixSevenEight"));
+ EXPECT_NO_THROW(e.setValue(int128_t(43e21)));
+
+ EXPECT_EQ(5678, a.getInteger().first);
+ EXPECT_EQ(56e+78, b.getFloat().first);
+ EXPECT_EQ(dur5678, c.getDuration().first);
+ EXPECT_EQ("fiveSixSevenEight", d.getString().first);
+ EXPECT_EQ(int128_t(43e21), e.getBigInteger().first);
+
+ // Now check whether setting value to a different type does
+ // throw an exception
+ EXPECT_THROW(a.setValue(56e+78), InvalidStatType);
+ EXPECT_THROW(a.setValue(dur5678), InvalidStatType);
+ EXPECT_THROW(a.setValue("fiveSixSevenEight"), InvalidStatType);
+ EXPECT_THROW(a.setValue(int128_t(43e21)), InvalidStatType);
+
+ EXPECT_THROW(b.setValue(static_cast<int64_t>(5678)), InvalidStatType);
+ EXPECT_THROW(b.setValue(dur5678), InvalidStatType);
+ EXPECT_THROW(b.setValue("fiveSixSevenEight"), InvalidStatType);
+ EXPECT_THROW(b.setValue(int128_t(43e21)), InvalidStatType);
+
+ EXPECT_THROW(c.setValue(static_cast<int64_t>(5678)), InvalidStatType);
+ EXPECT_THROW(c.setValue(56e+78), InvalidStatType);
+ EXPECT_THROW(c.setValue("fiveSixSevenEight"), InvalidStatType);
+ EXPECT_THROW(c.setValue(int128_t(43e21)), InvalidStatType);
+
+ EXPECT_THROW(d.setValue(static_cast<int64_t>(5678)), InvalidStatType);
+ EXPECT_THROW(d.setValue(56e+78), InvalidStatType);
+ EXPECT_THROW(d.setValue(dur5678), InvalidStatType);
+ EXPECT_THROW(d.setValue(int128_t(43e21)), InvalidStatType);
+
+ EXPECT_THROW(e.setValue(int64_t(5678)), InvalidStatType);
+ EXPECT_THROW(e.setValue(56e+78), InvalidStatType);
+ EXPECT_THROW(e.setValue(dur5678), InvalidStatType);
+ EXPECT_THROW(e.setValue("fiveSixSevenEight"), InvalidStatType);
+}
+
+// This test checks whether it is possible to add value to existing
+// counter.
+TEST_F(ObservationTest, addValue) {
+ // Note: all Observations were set to 1234, 12.34 or similar in
+ // ObservationTest constructor.
+
+ EXPECT_NO_THROW(a.addValue(static_cast<int64_t>(5678)));
+ EXPECT_NO_THROW(b.addValue(56.78));
+ EXPECT_NO_THROW(c.addValue(dur5678));
+ EXPECT_NO_THROW(d.addValue("fiveSixSevenEight"));
+ EXPECT_NO_THROW(e.addValue(int128_t(43e21)));
+
+ EXPECT_EQ(6912, a.getInteger().first);
+ EXPECT_EQ(69.12, b.getFloat().first);
+ EXPECT_EQ(dur681012, c.getDuration().first);
+ EXPECT_EQ("1234fiveSixSevenEight", d.getString().first);
+ EXPECT_EQ(int128_t(12e34) + int128_t(43e21), e.getBigInteger().first);
+
+ ASSERT_EQ(a.getSize(), 2);
+ ASSERT_EQ(b.getSize(), 2);
+ ASSERT_EQ(c.getSize(), 2);
+ ASSERT_EQ(d.getSize(), 2);
+ ASSERT_EQ(e.getSize(), 2);
+}
+
+// This test checks if collecting more than one sample
+// works well.
+TEST_F(ObservationTest, moreThanOne) {
+ // Arrays of 4 types of samples
+ int64_t int_samples[3] = {1234, 6912, 5678};
+ double float_samples[3] = {12.34, 69.12, 56e+78};
+ StatsDuration duration_samples[3] = {dur1234, dur681012, dur5678};
+ std::string string_samples[3] = {"1234", "1234fiveSixSevenEight", "fiveSixSevenEight"};
+ int128_t bigint_samples[3] = {int128_t(12e34), int128_t(12e34) + int128_t(43e21),
+ int128_t(43e21)};
+
+ EXPECT_NO_THROW(a.addValue(static_cast<int64_t>(5678)));
+ EXPECT_NO_THROW(b.addValue(56.78));
+ EXPECT_NO_THROW(c.addValue(dur5678));
+ EXPECT_NO_THROW(d.addValue("fiveSixSevenEight"));
+ EXPECT_NO_THROW(e.addValue(int128_t(43e21)));
+
+ EXPECT_NO_THROW(a.setValue(static_cast<int64_t>(5678)));
+ EXPECT_NO_THROW(b.setValue(56e+78));
+ EXPECT_NO_THROW(c.setValue(dur5678));
+ EXPECT_NO_THROW(d.setValue("fiveSixSevenEight"));
+ EXPECT_NO_THROW(e.setValue(int128_t(43e21)));
+
+ ASSERT_EQ(a.getSize(), 3);
+ ASSERT_EQ(b.getSize(), 3);
+ ASSERT_EQ(c.getSize(), 3);
+ ASSERT_EQ(d.getSize(), 3);
+ ASSERT_EQ(e.getSize(), 3);
+
+ ASSERT_NO_THROW(a.getIntegers());
+ ASSERT_NO_THROW(b.getFloats());
+ ASSERT_NO_THROW(c.getDurations());
+ ASSERT_NO_THROW(d.getStrings());
+ ASSERT_NO_THROW(e.getBigIntegers());
+
+ std::list<IntegerSample> samples_int = a.getIntegers(); // List of all integer samples
+ std::list<FloatSample> samples_float = b.getFloats(); // List of all float samples
+ std::list<DurationSample> samples_dur = c.getDurations(); // List of all duration samples
+ std::list<StringSample> samples_str = d.getStrings(); // List of all string samples
+ std::list<BigIntegerSample> samples_bigint = e.getBigIntegers(); // List of all big integer samples
+
+ uint32_t i = 2; // Index pointed to the end of array of samples
+ for (std::list<IntegerSample>::iterator it = samples_int.begin(); it != samples_int.end(); ++it) {
+ EXPECT_EQ(int_samples[i], static_cast<int64_t>((*it).first));
+ --i;
+ }
+ i = 2;
+ for (std::list<FloatSample>::iterator it = samples_float.begin(); it != samples_float.end(); ++it) {
+ EXPECT_EQ(float_samples[i], (*it).first);
+ --i;
+ }
+ i = 2;
+ for (std::list<DurationSample>::iterator it = samples_dur.begin(); it != samples_dur.end(); ++it) {
+ EXPECT_EQ(duration_samples[i], (*it).first);
+ --i;
+ }
+ i = 2;
+ for (std::list<StringSample>::iterator it = samples_str.begin(); it != samples_str.end(); ++it) {
+ EXPECT_EQ(string_samples[i], (*it).first);
+ --i;
+ }
+ i = 2;
+ for (BigIntegerSample const& sample : samples_bigint) {
+ EXPECT_EQ(bigint_samples[i], sample.first);
+ --i;
+ }
+}
+
+// This test checks whether the size of storage
+// is equal to the true value
+TEST_F(ObservationTest, getSize) {
+ // Check if size of storages is equal to 1
+ ASSERT_EQ(a.getSize(), 1);
+ ASSERT_EQ(b.getSize(), 1);
+ ASSERT_EQ(c.getSize(), 1);
+ ASSERT_EQ(d.getSize(), 1);
+ ASSERT_EQ(e.getSize(), 1);
+
+ a.addValue(static_cast<int64_t>(5678));
+ b.addValue(56.78);
+ c.addValue(dur5678);
+ d.addValue("fiveSixSevenEight");
+ e.addValue(int128_t(43e21));
+
+ EXPECT_NO_THROW(a.getSize());
+ EXPECT_NO_THROW(b.getSize());
+ EXPECT_NO_THROW(c.getSize());
+ EXPECT_NO_THROW(d.getSize());
+ EXPECT_NO_THROW(e.getSize());
+
+ // Check if size of storages is equal to 2
+ ASSERT_EQ(a.getSize(), 2);
+ ASSERT_EQ(b.getSize(), 2);
+ ASSERT_EQ(c.getSize(), 2);
+ ASSERT_EQ(d.getSize(), 2);
+ ASSERT_EQ(e.getSize(), 2);
+
+ a.setValue(static_cast<int64_t>(5678));
+ b.setValue(56e+78);
+ c.setValue(dur5678);
+ d.setValue("fiveSixSevenEight");
+ e.setValue(int128_t(43e21));
+
+ EXPECT_NO_THROW(a.getSize());
+ EXPECT_NO_THROW(b.getSize());
+ EXPECT_NO_THROW(c.getSize());
+ EXPECT_NO_THROW(d.getSize());
+
+ // Check if size of storages is equal to 3
+ ASSERT_EQ(a.getSize(), 3);
+ ASSERT_EQ(b.getSize(), 3);
+ ASSERT_EQ(c.getSize(), 3);
+ ASSERT_EQ(d.getSize(), 3);
+ ASSERT_EQ(e.getSize(), 3);
+}
+
+// Checks whether setting amount limits works properly
+TEST_F(ObservationTest, setCountLimit) {
+ // Preparing of 21 test's samples for each type of storage
+ int64_t int_samples[22] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21};
+ double float_samples[22] = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
+ 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0,
+ 20.0, 21.0};
+ std::string string_samples[22] = {"a", "b", "c", "d", "e", "f", "g", "h",
+ "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
+ "v"};
+ StatsDuration duration_samples[22];
+
+ for (uint32_t i = 0; i < 22; ++i) {
+ duration_samples[i] = milliseconds(i);
+ }
+
+ // By default the max_sample_count is set to 20 and max_sample_age
+ // is deactivated
+ // Adding 21 samples to each type of Observation
+ for (uint32_t i = 0; i < 21; ++i) {
+ a.setValue(int_samples[i]);
+ }
+ for (uint32_t i = 0; i < 21; ++i) {
+ b.setValue(float_samples[i]);
+ }
+ for (uint32_t i = 0; i < 21; ++i) {
+ c.setValue(duration_samples[i]);
+ }
+ for (uint32_t i = 0; i < 21; ++i) {
+ d.setValue(string_samples[i]);
+ }
+ for (uint32_t i = 0; i < 21; ++i) {
+ e.setValue(int128_t(int_samples[i]));
+ }
+
+ // Getting all 4 types of samples after inserting 21 values
+ std::list<IntegerSample> samples_int = a.getIntegers();
+ std::list<FloatSample> samples_float = b.getFloats();
+ std::list<DurationSample> samples_duration = c.getDurations();
+ std::list<StringSample> samples_string = d.getStrings();
+ std::list<BigIntegerSample> samples_bigint = e.getBigIntegers();
+
+ // Check if size of storages is equal to 20
+ ASSERT_EQ(a.getSize(), 20);
+ ASSERT_EQ(b.getSize(), 20);
+ ASSERT_EQ(c.getSize(), 20);
+ ASSERT_EQ(d.getSize(), 20);
+ ASSERT_EQ(e.getSize(), 20);
+
+ // And whether stored values are correct
+ uint32_t i = 20; // index of the last element in array of test's samples
+ for (std::list<IntegerSample>::iterator it = samples_int.begin(); it != samples_int.end(); ++it) {
+ EXPECT_EQ((*it).first, int_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (std::list<FloatSample>::iterator it = samples_float.begin(); it != samples_float.end(); ++it) {
+ EXPECT_EQ((*it).first, float_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (std::list<DurationSample>::iterator it = samples_duration.begin(); it != samples_duration.end(); ++it) {
+ EXPECT_EQ((*it).first, duration_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (std::list<StringSample>::iterator it = samples_string.begin(); it != samples_string.end(); ++it) {
+ EXPECT_EQ((*it).first, string_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (BigIntegerSample const& sample : samples_bigint) {
+ EXPECT_EQ(sample.first, int_samples[i]);
+ --i;
+ }
+
+ // Change size of storage to smaller one
+ ASSERT_NO_THROW(a.setMaxSampleCount(10));
+ ASSERT_NO_THROW(b.setMaxSampleCount(10));
+ ASSERT_NO_THROW(c.setMaxSampleCount(10));
+ ASSERT_NO_THROW(d.setMaxSampleCount(10));
+ ASSERT_NO_THROW(e.setMaxSampleCount(10));
+
+ samples_int = a.getIntegers();
+ samples_float = b.getFloats();
+ samples_duration = c.getDurations();
+ samples_string = d.getStrings();
+ samples_bigint = e.getBigIntegers();
+
+ // Check if size of storages is equal to 10
+ ASSERT_EQ(a.getSize(), 10);
+ ASSERT_EQ(b.getSize(), 10);
+ ASSERT_EQ(c.getSize(), 10);
+ ASSERT_EQ(d.getSize(), 10);
+ ASSERT_EQ(e.getSize(), 10);
+
+ // And whether storages contain only the 10 newest values
+ i = 20; // index of last element in array of test's samples
+ for (std::list<IntegerSample>::iterator it = samples_int.begin(); it != samples_int.end(); ++it) {
+ EXPECT_EQ((*it).first, int_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (std::list<FloatSample>::iterator it = samples_float.begin(); it != samples_float.end(); ++it) {
+ EXPECT_EQ((*it).first, float_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (std::list<DurationSample>::iterator it = samples_duration.begin(); it != samples_duration.end(); ++it) {
+ EXPECT_EQ((*it).first, duration_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (std::list<StringSample>::iterator it = samples_string.begin(); it != samples_string.end(); ++it) {
+ EXPECT_EQ((*it).first, string_samples[i]);
+ --i;
+ }
+ i = 20; // index of last element in array of test's samples
+ for (BigIntegerSample const& sample : samples_bigint) {
+ EXPECT_EQ(sample.first, int_samples[i]);
+ --i;
+ }
+
+ // Resize max_sample_count to greater
+ ASSERT_NO_THROW(a.setMaxSampleCount(50));
+ ASSERT_NO_THROW(b.setMaxSampleCount(50));
+ ASSERT_NO_THROW(c.setMaxSampleCount(50));
+ ASSERT_NO_THROW(d.setMaxSampleCount(50));
+ ASSERT_NO_THROW(e.setMaxSampleCount(50));
+
+ // Check if size of storages did not change without adding new value
+ ASSERT_EQ(a.getSize(), 10);
+ ASSERT_EQ(b.getSize(), 10);
+ ASSERT_EQ(c.getSize(), 10);
+ ASSERT_EQ(d.getSize(), 10);
+ ASSERT_EQ(e.getSize(), 10);
+
+ // Add new values to each type of Observation
+ a.setValue(static_cast<int64_t>(21));
+ b.setValue(21.0);
+ c.setValue(milliseconds(21));
+ d.setValue("v");
+ e.setValue(int128_t(21));
+
+ samples_int = a.getIntegers();
+ samples_float = b.getFloats();
+ samples_duration = c.getDurations();
+ samples_string = d.getStrings();
+ samples_bigint = e.getBigIntegers();
+
+ ASSERT_EQ(a.getSize(), 11);
+ ASSERT_EQ(b.getSize(), 11);
+ ASSERT_EQ(c.getSize(), 11);
+ ASSERT_EQ(d.getSize(), 11);
+ ASSERT_EQ(e.getSize(), 11);
+
+ i = 21; // index of last element in array of test's samples
+ for (std::list<IntegerSample>::iterator it = samples_int.begin(); it != samples_int.end(); ++it) {
+ EXPECT_EQ((*it).first, int_samples[i]);
+ --i;
+ }
+ i = 21; // index of last element in array of test's samples
+ for (std::list<FloatSample>::iterator it = samples_float.begin(); it != samples_float.end(); ++it) {
+ EXPECT_EQ((*it).first, float_samples[i]);
+ --i;
+ }
+ i = 21; // index of last element in array of test's samples
+ for (std::list<DurationSample>::iterator it = samples_duration.begin(); it != samples_duration.end(); ++it) {
+ EXPECT_EQ((*it).first, duration_samples[i]);
+ --i;
+ }
+ i = 21; // index of last element in array of test's samples
+ for (std::list<StringSample>::iterator it = samples_string.begin(); it != samples_string.end(); ++it) {
+ EXPECT_EQ((*it).first, string_samples[i]);
+ --i;
+ }
+ i = 21; // index of last element in array of test's samples
+ for (BigIntegerSample const& sample : samples_bigint) {
+ EXPECT_EQ(sample.first, int_samples[i]);
+ --i;
+ }
+}
+
+// Checks whether setting age limits works properly
+TEST_F(ObservationTest, setAgeLimit) {
+ // Set max_sample_age to 1 second
+ ASSERT_NO_THROW(c.setMaxSampleAge(seconds(1)));
+ // Add some value
+ c.setValue(milliseconds(5));
+ // Wait 1 second
+ sleep(1);
+ // and add new value
+ c.setValue(milliseconds(3));
+
+ // get the list of all samples
+ std::list<DurationSample> samples_duration = c.getDurations();
+ // check whether the size of samples is equal to 1
+ ASSERT_EQ(c.getSize(), 1);
+ // and whether it contains an expected value
+ EXPECT_EQ((*samples_duration.begin()).first, milliseconds(3));
+
+ // Wait 1 second to ensure removing previously set value
+ sleep(1);
+ // add 10 new values
+ for (uint32_t i = 0; i < 10; ++i) {
+ c.setValue(milliseconds(i));
+ }
+ // change the max_sample_age to smaller
+ ASSERT_NO_THROW(c.setMaxSampleAge(milliseconds(300)));
+
+ samples_duration = c.getDurations();
+ // check whether the size of samples is equal to 10
+ ASSERT_EQ(c.getSize(), 10);
+
+ // and whether it contains expected values
+ uint32_t i = 9;
+ for (std::list<DurationSample>::iterator it = samples_duration.begin(); it != samples_duration.end(); ++it) {
+ EXPECT_EQ((*it).first, milliseconds(i));
+ --i;
+ }
+}
+
+// Test checks whether we can get max_sample_age_ and max_sample_count_
+// properly.
+TEST_F(ObservationTest, getLimits) {
+ // First checks whether getting default values works properly
+ EXPECT_EQ(a.getMaxSampleAge().first, false);
+ EXPECT_EQ(b.getMaxSampleAge().first, false);
+ EXPECT_EQ(c.getMaxSampleAge().first, false);
+ EXPECT_EQ(d.getMaxSampleAge().first, false);
+ EXPECT_EQ(e.getMaxSampleAge().first, false);
+
+ EXPECT_EQ(a.getMaxSampleCount().first, true);
+ EXPECT_EQ(b.getMaxSampleCount().first, true);
+ EXPECT_EQ(c.getMaxSampleCount().first, true);
+ EXPECT_EQ(d.getMaxSampleCount().first, true);
+ EXPECT_EQ(e.getMaxSampleCount().first, true);
+
+ EXPECT_EQ(a.getMaxSampleCount().second, 20);
+ EXPECT_EQ(b.getMaxSampleCount().second, 20);
+ EXPECT_EQ(c.getMaxSampleCount().second, 20);
+ EXPECT_EQ(d.getMaxSampleCount().second, 20);
+ EXPECT_EQ(e.getMaxSampleCount().second, 20);
+
+ // change limit to time duration
+ ASSERT_NO_THROW(a.setMaxSampleAge(dur453));
+ ASSERT_NO_THROW(b.setMaxSampleAge(dur453));
+ ASSERT_NO_THROW(c.setMaxSampleAge(dur453));
+ ASSERT_NO_THROW(d.setMaxSampleAge(dur453));
+ ASSERT_NO_THROW(e.setMaxSampleAge(dur453));
+
+ EXPECT_EQ(a.getMaxSampleAge().first, true);
+ EXPECT_EQ(b.getMaxSampleAge().first, true);
+ EXPECT_EQ(c.getMaxSampleAge().first, true);
+ EXPECT_EQ(d.getMaxSampleAge().first, true);
+ EXPECT_EQ(e.getMaxSampleAge().first, true);
+
+ EXPECT_EQ(a.getMaxSampleAge().second, dur453);
+ EXPECT_EQ(b.getMaxSampleAge().second, dur453);
+ EXPECT_EQ(c.getMaxSampleAge().second, dur453);
+ EXPECT_EQ(d.getMaxSampleAge().second, dur453);
+ EXPECT_EQ(e.getMaxSampleAge().second, dur453);
+
+ EXPECT_EQ(a.getMaxSampleCount().first, false);
+ EXPECT_EQ(b.getMaxSampleCount().first, false);
+ EXPECT_EQ(c.getMaxSampleCount().first, false);
+ EXPECT_EQ(d.getMaxSampleCount().first, false);
+ EXPECT_EQ(e.getMaxSampleCount().first, false);
+
+ EXPECT_EQ(a.getMaxSampleCount().second, 20);
+ EXPECT_EQ(b.getMaxSampleCount().second, 20);
+ EXPECT_EQ(c.getMaxSampleCount().second, 20);
+ EXPECT_EQ(d.getMaxSampleCount().second, 20);
+ EXPECT_EQ(e.getMaxSampleCount().second, 20);
+}
+
+// limit defaults are tested with StatsMgr.
+
+// Test checks whether timing is reported properly.
+TEST_F(ObservationTest, timers) {
+ auto before = SampleClock::now();
+ b.setValue(123.0); // Set it to a random value and record the time.
+
+ // Allow a bit of imprecision. This test allows 500ms. That should be ok,
+ // when running on virtual machines.
+ auto after = before + milliseconds(500);
+
+ // Now wait some time. We want to confirm that the timestamp recorded is the
+ // time the observation took place, not current time.
+ sleep(1);
+
+ FloatSample sample = b.getFloat();
+
+ // Let's check that the timestamp is within (before, after) range:
+ // before < sample-time < after
+ EXPECT_TRUE(before <= sample.second);
+ EXPECT_TRUE(sample.second <= after);
+}
+
+// Checks whether an integer statistic can generate proper JSON structures.
+// See https://gitlab.isc.org/isc-projects/kea/wikis/designs/Stats-design
+/// for details.
+TEST_F(ObservationTest, integerToJSON) {
+ // String which contains first added sample
+ std::string first_sample = ", [ 1234, \"" +
+ isc::util::clockToText(a.getInteger().second) + "\" ] ]";
+
+ a.setValue(static_cast<int64_t>(1234));
+
+ std::string exp = "[ [ 1234, \"" +
+ isc::util::clockToText(a.getInteger().second) + "\" ]" + first_sample;
+
+ EXPECT_EQ(exp, a.getJSON()->str());
+}
+
+// Checks whether a floating point statistic can generate proper JSON
+// structures. See
+/// https://gitlab.isc.org/isc-projects/kea/wikis/designs/Stats-design
+/// for details.
+TEST_F(ObservationTest, floatToJSON) {
+ // String which contains first added sample
+ std::string first_sample = ", [ 12.34, \"" +
+ isc::util::clockToText(b.getFloat().second) + "\" ] ]";
+
+ // Let's use a value that converts easily to floating point.
+ // No need to deal with infinite fractions in binary systems.
+
+ b.setValue(1234.5);
+
+ std::string exp = "[ [ 1234.5, \"" +
+ isc::util::clockToText(b.getFloat().second) + "\" ]" + first_sample;
+
+ EXPECT_EQ(exp, b.getJSON()->str());
+}
+
+// Checks whether a time duration statistic can generate proper JSON structures.
+// See https://gitlab.isc.org/isc-projects/kea/wikis/designs/Stats-design for
+// details.
+TEST_F(ObservationTest, durationToJSON) {
+ // String which contains first added sample
+ std::string first_sample = ", [ \"01:02:03.004000\", \"" +
+ isc::util::clockToText(c.getDuration().second) + "\" ] ]";
+
+ // 1 hour 2 minutes 3 seconds and 4 milliseconds
+ c.setValue(dur1234);
+
+ std::string exp = "[ [ \"01:02:03.004000\", \"" +
+ isc::util::clockToText(c.getDuration().second) + "\" ]" + first_sample;
+
+ EXPECT_EQ(exp, c.getJSON()->str());
+}
+
+// Checks whether a string statistic can generate proper JSON structures.
+// See https://gitlab.isc.org/isc-projects/kea/wikis/designs/Stats-design
+// for details.
+TEST_F(ObservationTest, stringToJSON) {
+ // String which contains first added sample
+ std::string first_sample = ", [ \"1234\", \"" +
+ isc::util::clockToText(d.getString().second) + "\" ] ]";
+
+ d.setValue("Lorem ipsum dolor sit amet");
+
+ std::string exp = "[ [ \"Lorem ipsum dolor sit amet\", \"" +
+ isc::util::clockToText(d.getString().second) + "\" ]" + first_sample;
+
+ EXPECT_EQ(exp, d.getJSON()->str());
+}
+
+// Checks whether a big integer statistic can generate proper JSON structures.
+// See https://gitlab.isc.org/isc-projects/kea/wikis/designs/Stats-design
+// for details.
+TEST_F(ObservationTest, bigIntegerToJSON) {
+ // String which contains first added sample
+ std::string first_sample = ", [ 120000000000000007304085773727301632, \"" +
+ isc::util::clockToText(e.getBigInteger().second) + "\" ] ]";
+
+ e.setValue(int128_t(43e21));
+
+ std::string exp = "[ [ 43000000000000002097152, \"" +
+ isc::util::clockToText(e.getBigInteger().second) + "\" ]" + first_sample;
+
+ EXPECT_EQ(exp, e.getJSON()->str());
+}
+
+// Checks whether reset() resets the statistics properly.
+TEST_F(ObservationTest, reset) {
+ EXPECT_NO_THROW(a.addValue(static_cast<int64_t>(5678)));
+ EXPECT_NO_THROW(b.addValue(56.78));
+ EXPECT_NO_THROW(c.addValue(dur5678));
+ EXPECT_NO_THROW(d.addValue("fiveSixSevenEight"));
+ EXPECT_NO_THROW(e.addValue(int128_t(43e21)));
+
+ a.reset(); // integer
+ b.reset(); // float
+ c.reset(); // duration
+ d.reset(); // string
+ e.reset(); // big integer
+
+ EXPECT_EQ(0, a.getInteger().first);
+ EXPECT_EQ(0.0, b.getFloat().first);
+ EXPECT_EQ(StatsDuration::zero(), c.getDuration().first);
+ EXPECT_EQ("", d.getString().first);
+ EXPECT_EQ(0, e.getBigInteger().first);
+
+ ASSERT_EQ(a.getSize(), 1);
+ ASSERT_EQ(b.getSize(), 1);
+ ASSERT_EQ(c.getSize(), 1);
+ ASSERT_EQ(d.getSize(), 1);
+ ASSERT_EQ(e.getSize(), 1);
+}
+
+// Checks whether an observation can keep its name.
+TEST_F(ObservationTest, names) {
+ EXPECT_EQ("alpha", a.getName());
+ EXPECT_EQ("beta", b.getName());
+ EXPECT_EQ("gamma", c.getName());
+ EXPECT_EQ("delta", d.getName());
+ EXPECT_EQ("epsilon", e.getName());
+}
+
+} // namespace
diff --git a/src/lib/stats/tests/run_unittests.cc b/src/lib/stats/tests/run_unittests.cc
new file mode 100644
index 0000000..9d621ae
--- /dev/null
+++ b/src/lib/stats/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// Copyright (C) 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>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/stats/tests/stats_mgr_unittest.cc b/src/lib/stats/tests/stats_mgr_unittest.cc
new file mode 100644
index 0000000..734d134
--- /dev/null
+++ b/src/lib/stats/tests/stats_mgr_unittest.cc
@@ -0,0 +1,1117 @@
+// Copyright (C) 2015-2020 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 <stats/stats_mgr.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <util/chrono_time_utils.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::stats;
+using namespace isc::config;
+using namespace std::chrono;
+
+namespace {
+
+static const StatsDuration& dur1234(hours(1) + minutes(2) + seconds(3) +
+ milliseconds(4));
+static const StatsDuration& dur5678(hours(5) + minutes(6) + seconds(7) +
+ milliseconds(8));
+static const StatsDuration& dur1245(hours(1) + minutes(2) + seconds(45));
+
+/// @brief Fixture class for StatsMgr testing
+///
+/// Very simple class that makes sure that StatsMgr is indeed instantiated
+/// before the test and any statistics are wiped out after it.
+class StatsMgrTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ /// Makes sure that the Statistics Manager is instantiated.
+ StatsMgrTest() {
+ StatsMgr::instance();
+ StatsMgr::instance().removeAll();
+ }
+
+ /// @brief Destructor
+ /// Removes all statistics and restores class defaults.
+ ~StatsMgrTest() {
+ StatsMgr::instance().removeAll();
+ StatsMgr::instance().setMaxSampleAgeDefault(StatsDuration::zero());
+ StatsMgr::instance().setMaxSampleCountDefault(20);
+ }
+};
+
+// Basic test for statistics manager interface.
+TEST_F(StatsMgrTest, basic) {
+ // Getting an instance
+ EXPECT_NO_THROW(StatsMgr::instance());
+
+ // Check that there are no statistics recorded by default.
+ EXPECT_EQ(0, StatsMgr::instance().count());
+}
+
+// Test checks whether it's possible to record and later report
+// an integer statistic.
+TEST_F(StatsMgrTest, integerStat) {
+ EXPECT_NO_THROW(StatsMgr::instance().setValue("alpha",
+ static_cast<int64_t>(1234)));
+
+ ObservationPtr alpha;
+ EXPECT_NO_THROW(alpha = StatsMgr::instance().getObservation("alpha"));
+ ASSERT_TRUE(alpha);
+
+ std::string exp = "{ \"alpha\": [ [ 1234, \"" +
+ isc::util::clockToText(alpha->getInteger().second) + "\" ] ] }";
+
+ EXPECT_EQ(exp, StatsMgr::instance().get("alpha")->str());
+}
+
+// Test checks whether it's possible to record and later report
+// a floating point statistic.
+TEST_F(StatsMgrTest, floatStat) {
+ EXPECT_NO_THROW(StatsMgr::instance().setValue("beta", 12.34));
+
+ ObservationPtr beta;
+ EXPECT_NO_THROW(beta = StatsMgr::instance().getObservation("beta"));
+ ASSERT_TRUE(beta);
+
+ std::string exp = "{ \"beta\": [ [ 12.34, \"" +
+ isc::util::clockToText(beta->getFloat().second) + "\" ] ] }";
+
+ EXPECT_EQ(exp, StatsMgr::instance().get("beta")->str());
+}
+
+// Test checks whether it's possible to record and later report
+// a duration statistic.
+TEST_F(StatsMgrTest, durationStat) {
+ EXPECT_NO_THROW(StatsMgr::instance().setValue("gamma", dur1234));
+
+ ObservationPtr gamma;
+ EXPECT_NO_THROW(gamma = StatsMgr::instance().getObservation("gamma"));
+ ASSERT_TRUE(gamma);
+
+ std::string exp = "{ \"gamma\": [ [ \"01:02:03.004000\", \"" +
+ isc::util::clockToText(gamma->getDuration().second) + "\" ] ] }";
+
+ EXPECT_EQ(exp, StatsMgr::instance().get("gamma")->str());
+}
+
+// Test checks whether it's possible to record and later report
+// a string statistic.
+TEST_F(StatsMgrTest, stringStat) {
+ EXPECT_NO_THROW(StatsMgr::instance().setValue("delta",
+ "Lorem ipsum"));
+
+ ObservationPtr delta;
+ EXPECT_NO_THROW(delta = StatsMgr::instance().getObservation("delta"));
+ ASSERT_TRUE(delta);
+
+ std::string exp = "{ \"delta\": [ [ \"Lorem ipsum\", \"" +
+ isc::util::clockToText(delta->getString().second) + "\" ] ] }";
+
+ EXPECT_EQ(exp, StatsMgr::instance().get("delta")->str());
+}
+
+// Basic test of getSize function.
+TEST_F(StatsMgrTest, getSize) {
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ EXPECT_NO_THROW(StatsMgr::instance().getSize("alpha"));
+ EXPECT_NO_THROW(StatsMgr::instance().getSize("beta"));
+ EXPECT_NO_THROW(StatsMgr::instance().getSize("gamma"));
+ EXPECT_NO_THROW(StatsMgr::instance().getSize("delta"));
+
+ EXPECT_EQ(StatsMgr::instance().getSize("alpha"), 1);
+ EXPECT_EQ(StatsMgr::instance().getSize("beta"), 1);
+ EXPECT_EQ(StatsMgr::instance().getSize("gamma"), 1);
+ EXPECT_EQ(StatsMgr::instance().getSize("delta"), 1);
+}
+
+// Test checks whether setting age limit and count limit works properly.
+TEST_F(StatsMgrTest, setLimits) {
+ // Initializing of an integer type observation
+ StatsMgr::instance().setValue("foo", static_cast<int64_t>(1));
+
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleAge("foo",
+ seconds(1)));
+
+ for (uint32_t i = 0; i < 10; ++i) {
+ if (i == 5) {
+ sleep(1); // wait one second to force exceeding the time limit
+ }
+ StatsMgr::instance().setValue("foo", static_cast<int64_t>(i));
+ }
+
+ EXPECT_EQ(StatsMgr::instance().getSize("foo"), 5);
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleCount("foo", 100));
+
+ for (int64_t i = 0; i < 200; ++i) {
+ StatsMgr::instance().setValue("foo", i);
+ }
+
+ EXPECT_EQ(StatsMgr::instance().getSize("foo"), 100);
+}
+
+// Test checks whether setting age limit and count limit to existing
+// statistics works properly.
+TEST_F(StatsMgrTest, setLimitsAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // check the setting of time limit to existing statistics
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleAgeAll(seconds(1)));
+
+ // check if time limit was set properly and whether count limit is disabled
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second,
+ seconds(1));
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().second,
+ seconds(1));
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().second,
+ seconds(1));
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().second,
+ seconds(1));
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, false);
+
+ // check the setting of count limit to existing statistics
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleCountAll(1200));
+
+ // check if count limit was set properly and whether count limit is disabled
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 1200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().second, 1200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().second, 1200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().second, 1200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, false);
+}
+
+// Test checks whether setting default age limit and count limit works
+// properly.
+TEST_F(StatsMgrTest, setLimitsDefault) {
+ ASSERT_EQ(StatsMgr::instance().getMaxSampleCountDefault(), 20);
+ ASSERT_EQ(StatsMgr::instance().getMaxSampleAgeDefault(), StatsDuration::zero());
+
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", seconds(1234));
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // check what default applied
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second, StatsDuration::zero());
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().second, StatsDuration::zero());
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().second, StatsDuration::zero());
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().second, StatsDuration::zero());
+
+ // Retry with another default count limits.
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleCountDefault(10));
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleAgeDefault(seconds(5)));
+ ASSERT_EQ(StatsMgr::instance().getMaxSampleCountDefault(), 10);
+ ASSERT_EQ(StatsMgr::instance().getMaxSampleAgeDefault(), seconds(5));
+
+ // Check the existing statistics were not updated.
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second, StatsDuration::zero());
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().second, StatsDuration::zero());
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().second, StatsDuration::zero());
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().second, 20);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().second, StatsDuration::zero());
+
+ // Remove all test statistics.
+ EXPECT_NO_THROW(StatsMgr::instance().removeAll());
+
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", seconds(1234));
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().second, seconds(5));
+
+ // Retry with count limit disable.
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleCountDefault(0));
+ ASSERT_EQ(StatsMgr::instance().getMaxSampleCountDefault(), 0);
+
+ // Check the existing statistics were not updated.
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().second, seconds(5));
+
+ // Remove all test statistics.
+ EXPECT_NO_THROW(StatsMgr::instance().removeAll());
+
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", seconds(1234));
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().second, seconds(5));
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, false);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().second, 10);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().second, seconds(5));
+
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleCountDefault(20));
+ EXPECT_NO_THROW(StatsMgr::instance().setMaxSampleAgeDefault(StatsDuration::zero()));
+}
+
+// This test checks whether a single (get("foo")) and all (getAll())
+// statistics are reported properly.
+TEST_F(StatsMgrTest, getGetAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem");
+
+ // The string's representation of firstly added statistics
+ std::string alpha_first = ", [ 1234, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("alpha")
+ ->getInteger().second) + "\" ] ]";
+ std::string beta_first = ", [ 12.34, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("beta")
+ ->getFloat().second) + "\" ] ]";
+ std::string gamma_first = ", [ \"01:02:03.004000\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("gamma")
+ ->getDuration().second) + "\" ] ]";
+ std::string delta_first = ", [ \"Lorem\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("delta")
+ ->getString().second) + "\" ] ]";
+
+ // Now add some values to them
+ StatsMgr::instance().addValue("alpha", static_cast<int64_t>(5678));
+ StatsMgr::instance().addValue("beta", 56.78);
+ StatsMgr::instance().addValue("gamma", dur5678);
+ StatsMgr::instance().addValue("delta", " ipsum");
+
+ // There should be 4 statistics reported
+ EXPECT_EQ(4, StatsMgr::instance().count());
+
+ // Now check whether they can be reported back
+ ConstElementPtr rep_alpha = StatsMgr::instance().get("alpha");
+ ConstElementPtr rep_beta = StatsMgr::instance().get("beta");
+ ConstElementPtr rep_gamma = StatsMgr::instance().get("gamma");
+ ConstElementPtr rep_delta = StatsMgr::instance().get("delta");
+
+ ASSERT_TRUE(rep_alpha);
+ ASSERT_TRUE(rep_beta);
+ ASSERT_TRUE(rep_gamma);
+ ASSERT_TRUE(rep_delta);
+
+ std::string exp_str_alpha = "[ [ 6912, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("alpha")
+ ->getInteger().second) + "\" ]" + alpha_first;
+ std::string exp_str_beta = "[ [ 69.12, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("beta")
+ ->getFloat().second) + "\" ]" + beta_first;
+ std::string exp_str_gamma = "[ [ \"06:08:10.012000\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("gamma")
+ ->getDuration().second) + "\" ]" + gamma_first;
+ std::string exp_str_delta = "[ [ \"Lorem ipsum\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("delta")
+ ->getString().second) + "\" ]" + delta_first;
+
+ // Check that individual stats are reported properly
+ EXPECT_EQ("{ \"alpha\": " + exp_str_alpha + " }", rep_alpha->str());
+ EXPECT_EQ("{ \"beta\": " + exp_str_beta + " }", rep_beta->str());
+ EXPECT_EQ("{ \"gamma\": " + exp_str_gamma + " }", rep_gamma->str());
+ EXPECT_EQ("{ \"delta\": " + exp_str_delta + " }", rep_delta->str());
+
+ // Check that non-existent metric is not reported.
+ EXPECT_EQ("{ }", StatsMgr::instance().get("epsilon")->str());
+
+ // Check that all of them can be reported at once
+ ConstElementPtr rep_all = StatsMgr::instance().getAll();
+ ASSERT_TRUE(rep_all);
+
+ // Verifying this is a bit more involved, as we don't know whether the
+ // order would be preserved or not.
+ EXPECT_EQ(4, rep_all->size());
+ ASSERT_TRUE(rep_all->get("alpha"));
+ ASSERT_TRUE(rep_all->get("beta"));
+ ASSERT_TRUE(rep_all->get("delta"));
+ ASSERT_TRUE(rep_all->get("gamma"));
+ EXPECT_FALSE(rep_all->get("epsilon"));
+
+ EXPECT_EQ(exp_str_alpha, rep_all->get("alpha")->str());
+ EXPECT_EQ(exp_str_beta, rep_all->get("beta")->str());
+ EXPECT_EQ(exp_str_gamma, rep_all->get("gamma")->str());
+ EXPECT_EQ(exp_str_delta, rep_all->get("delta")->str());
+}
+
+// This test checks whether existing statistics can be reset.
+TEST_F(StatsMgrTest, reset) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // This should reset alpha to 0
+ EXPECT_NO_THROW(StatsMgr::instance().reset("alpha"));
+ EXPECT_EQ(0,
+ StatsMgr::instance().getObservation("alpha")->getInteger().first);
+
+ // The other stats should remain untouched
+ EXPECT_EQ(12.34,
+ StatsMgr::instance().getObservation("beta")->getFloat().first);
+ EXPECT_EQ(dur1234,
+ StatsMgr::instance().getObservation("gamma")->getDuration().first);
+ EXPECT_EQ("Lorem ipsum",
+ StatsMgr::instance().getObservation("delta")->getString().first);
+
+ // Now let's wipe them, too.
+ EXPECT_NO_THROW(StatsMgr::instance().reset("beta"));
+ EXPECT_NO_THROW(StatsMgr::instance().reset("gamma"));
+ EXPECT_NO_THROW(StatsMgr::instance().reset("delta"));
+ EXPECT_EQ(0.0,
+ StatsMgr::instance().getObservation("beta")->getFloat().first);
+ EXPECT_EQ(StatsDuration::zero(),
+ StatsMgr::instance().getObservation("gamma")->getDuration().first);
+ EXPECT_EQ("",
+ StatsMgr::instance().getObservation("delta")->getString().first);
+
+ // Resetting statistics should not remove them
+ EXPECT_EQ(4, StatsMgr::instance().count());
+}
+
+// This test checks whether existing statistics can be reset.
+TEST_F(StatsMgrTest, resetAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // This should reset alpha to 0
+ EXPECT_NO_THROW(StatsMgr::instance().resetAll());
+ EXPECT_EQ(0,
+ StatsMgr::instance().getObservation("alpha")->getInteger().first);
+ EXPECT_EQ(0.0,
+ StatsMgr::instance().getObservation("beta")->getFloat().first);
+ EXPECT_EQ(StatsDuration::zero(),
+ StatsMgr::instance().getObservation("gamma")->getDuration().first);
+ EXPECT_EQ("",
+ StatsMgr::instance().getObservation("delta")->getString().first);
+
+ // Resetting all statistics should not remove them
+ EXPECT_EQ(4, StatsMgr::instance().count());
+}
+
+// This test checks whether statistics can be removed.
+TEST_F(StatsMgrTest, removeAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // This should reset alpha to 0
+ EXPECT_NO_THROW(StatsMgr::instance().removeAll());
+
+ // Resetting all statistics should not remove them
+ EXPECT_EQ(0, StatsMgr::instance().count());
+
+ // There should be no such statistics anymore
+ EXPECT_EQ("{ }", StatsMgr::instance().get("alpha")->str());
+ EXPECT_EQ("{ }", StatsMgr::instance().get("beta")->str());
+ EXPECT_EQ("{ }", StatsMgr::instance().get("gamma")->str());
+ EXPECT_EQ("{ }", StatsMgr::instance().get("delta")->str());
+
+ // There should be no such statistics anymore
+ EXPECT_FALSE(StatsMgr::instance().getObservation("alpha"));
+ EXPECT_FALSE(StatsMgr::instance().getObservation("beta"));
+ EXPECT_FALSE(StatsMgr::instance().getObservation("gamma"));
+ EXPECT_FALSE(StatsMgr::instance().getObservation("delta"));
+}
+
+// This is a performance benchmark that checks how long does it take
+// to increment a single statistic million times.
+//
+// Data points:
+// It took 00:00:00.363709 (363ms) on late 2013 Mac with Mac OS X 10.9.5.
+TEST_F(StatsMgrTest, DISABLED_performanceSingleAdd) {
+ StatsMgr::instance().removeAll();
+
+ uint32_t cycles = 1000000;
+
+ auto before = SampleClock::now();
+ for (uint32_t i = 0; i < cycles; ++i) {
+ StatsMgr::instance().addValue("metric1", 0.1 * i);
+ }
+ auto after = SampleClock::now();
+
+ auto dur = after - before;
+
+ std::cout << "Incrementing a single statistic " << cycles << " times took: "
+ << isc::util::durationToText(dur) << std::endl;
+}
+
+// This is a performance benchmark that checks how long does it take
+// to set absolute value of a single statistic million times.
+//
+// Data points:
+// It took 00:00:00.361003 (361ms) on late 2013 Mac with Mac OS X 10.9.5.
+TEST_F(StatsMgrTest, DISABLED_performanceSingleSet) {
+ StatsMgr::instance().removeAll();
+
+ uint32_t cycles = 1000000;
+
+ auto before = SampleClock::now();
+ for (uint32_t i = 0; i < cycles; ++i) {
+ StatsMgr::instance().setValue("metric1", 0.1 * i);
+ }
+ auto after = SampleClock::now();
+
+ auto dur = after - before;
+
+ std::cout << "Setting a single statistic " << cycles << " times took: "
+ << isc::util::durationToText(dur) << std::endl;
+}
+
+// This is a performance benchmark that checks how long does it take to
+// increment one statistic a million times, when there is 1000 other statistics
+// present.
+//
+// Data points:
+// 00:00:00.436943 (436ms) on late 2013 Mac with Mac OS X 10.9.5
+TEST_F(StatsMgrTest, DISABLED_performanceMultipleAdd) {
+ StatsMgr::instance().removeAll();
+
+ uint32_t cycles = 1000000;
+ uint32_t stats = 1000;
+
+ for (uint32_t i = 0; i < stats; ++i) {
+ std::stringstream tmp;
+ tmp << "statistic" << i;
+ StatsMgr::instance().setValue(tmp.str(), static_cast<int64_t>(i));
+ }
+
+ auto before = SampleClock::now();
+ for (uint32_t i = 0; i < cycles; ++i) {
+ StatsMgr::instance().addValue("metric1", static_cast<int64_t>(i));
+ }
+ auto after = SampleClock::now();
+
+ auto dur = after - before;
+
+ std::cout << "Incrementing one of " << stats << " statistics " << cycles
+ << " times took: " << isc::util::durationToText(dur) << std::endl;
+}
+
+// This is a performance benchmark that checks how long does it take to
+// set one statistic to a given value a million times, when there is 1000 other
+// statistics present.
+//
+// Data points:
+// 00:00:00.424518 (424ms) on late 2013 Mac with Mac OS X 10.9.5
+TEST_F(StatsMgrTest, DISABLED_performanceMultipleSet) {
+ StatsMgr::instance().removeAll();
+
+ uint32_t cycles = 1000000;
+ uint32_t stats = 1000;
+
+ for (uint32_t i = 0; i < stats; ++i) {
+ std::stringstream tmp;
+ tmp << "statistic" << i;
+ StatsMgr::instance().setValue(tmp.str(), static_cast<int64_t>(i));
+ }
+
+ auto before = SampleClock::now();
+ for (uint32_t i = 0; i < cycles; ++i) {
+ StatsMgr::instance().setValue("metric1", static_cast<int64_t>(i));
+ }
+ auto after = SampleClock::now();
+
+ auto dur = after - before;
+
+ std::cout << "Setting one of " << stats << " statistics " << cycles
+ << " times took: " << isc::util::durationToText(dur) << std::endl;
+}
+
+// Test checks whether statistics name can be generated using various
+// indexes.
+TEST_F(StatsMgrTest, generateName) {
+ // generateName is a templated method, so in principle anything printable
+ // to stream can be used as index. However, in practice only integers
+ // and possibly strings will be used.
+
+ // Let's text integer as index.
+ EXPECT_EQ("subnet[123].pkt4-received",
+ StatsMgr::generateName("subnet", 123, "pkt4-received"));
+
+ // Lets' test string as index.
+ EXPECT_EQ("subnet[foo].pkt4-received",
+ StatsMgr::generateName("subnet", "foo", "pkt4-received"));
+}
+
+// Test checks if statistic-get handler is able to return specified statistic.
+TEST_F(StatsMgrTest, commandStatisticGet) {
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+
+ ElementPtr params = Element::createMap();
+ params->set("name", Element::create("alpha"));
+
+ ConstElementPtr rsp = StatsMgr::instance().statisticGetHandler("statistic-get",
+ params);
+
+ ObservationPtr alpha;
+ EXPECT_NO_THROW(alpha = StatsMgr::instance().getObservation("alpha"));
+ ASSERT_TRUE(alpha);
+
+ std::string exp = "{ \"alpha\": [ [ 1234, \"" +
+ isc::util::clockToText(alpha->getInteger().second) + "\" ] ] }";
+
+ EXPECT_EQ("{ \"arguments\": " + exp + ", \"result\": 0 }", rsp->str());
+}
+
+// Test checks if statistic-get is able to handle:
+// - a request without parameters
+// - a request with missing statistic name
+// - a request for non-existing statistic.
+TEST_F(StatsMgrTest, commandStatisticGetNegative) {
+ // Case 1: a request without parameters
+ ConstElementPtr rsp = StatsMgr::instance().statisticGetHandler("statistic-get",
+ ElementPtr());
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 2: a request with missing statistic name
+ ElementPtr params = Element::createMap();
+ rsp = StatsMgr::instance().statisticGetHandler("statistic-get", params);
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 3: a request for non-existing statistic
+ params->set("name", Element::create("alpha"));
+ rsp = StatsMgr::instance().statisticGetHandler("statistic-get", params);
+ EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", rsp->str());
+}
+
+// This test checks whether statistic-get-all command returns all statistics
+// correctly.
+TEST_F(StatsMgrTest, commandGetAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // Now get them. They're used to generate expected output
+ ConstElementPtr rep_alpha = StatsMgr::instance().get("alpha");
+ ConstElementPtr rep_beta = StatsMgr::instance().get("beta");
+ ConstElementPtr rep_gamma = StatsMgr::instance().get("gamma");
+ ConstElementPtr rep_delta = StatsMgr::instance().get("delta");
+
+ ASSERT_TRUE(rep_alpha);
+ ASSERT_TRUE(rep_beta);
+ ASSERT_TRUE(rep_gamma);
+ ASSERT_TRUE(rep_delta);
+
+ std::string exp_str_alpha = "[ [ 1234, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("alpha")
+ ->getInteger().second) + "\" ] ]";
+ std::string exp_str_beta = "[ [ 12.34, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("beta")
+ ->getFloat().second) + "\" ] ]";
+ std::string exp_str_gamma = "[ [ \"01:02:03.004000\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("gamma")
+ ->getDuration().second) + "\" ] ]";
+ std::string exp_str_delta = "[ [ \"Lorem ipsum\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("delta")
+ ->getString().second) + "\" ] ]";
+
+ // Check that all of them can be reported at once
+ ConstElementPtr rsp = StatsMgr::instance().statisticGetAllHandler(
+ "statistic-get-all", ElementPtr());
+ ASSERT_TRUE(rsp);
+ int status_code;
+ ConstElementPtr rep_all = parseAnswer(status_code, rsp);
+ ASSERT_EQ(0, status_code);
+ ASSERT_TRUE(rep_all);
+
+ // Verifying this is a bit more involved, as we don't know whether the
+ // order would be preserved or not.
+ EXPECT_EQ(4, rep_all->size());
+ ASSERT_TRUE(rep_all->get("alpha"));
+ ASSERT_TRUE(rep_all->get("beta"));
+ ASSERT_TRUE(rep_all->get("delta"));
+ ASSERT_TRUE(rep_all->get("gamma"));
+ EXPECT_FALSE(rep_all->get("epsilon"));
+
+ EXPECT_EQ(exp_str_alpha, rep_all->get("alpha")->str());
+ EXPECT_EQ(exp_str_beta, rep_all->get("beta")->str());
+ EXPECT_EQ(exp_str_gamma, rep_all->get("gamma")->str());
+ EXPECT_EQ(exp_str_delta, rep_all->get("delta")->str());
+}
+
+// Test checks if statistic-reset handler is able to reset specified statistic.
+TEST_F(StatsMgrTest, commandStatisticReset) {
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+
+ ElementPtr params = Element::createMap();
+ params->set("name", Element::create("alpha"));
+
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticResetHandler("statistic-reset", params);
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+
+ ObservationPtr alpha;
+ EXPECT_NO_THROW(alpha = StatsMgr::instance().getObservation("alpha"));
+ ASSERT_TRUE(alpha);
+
+ // Check that it was indeed reset
+ EXPECT_EQ(0, alpha->getInteger().first);
+}
+
+// Test checks if statistic-reset is able to handle:
+// - a request without parameters
+// - a request with missing statistic name
+// - a request for non-existing statistic.
+TEST_F(StatsMgrTest, commandStatisticResetNegative) {
+ // Case 1: a request without parameters
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticResetHandler("statistic-reset", ElementPtr());
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 2: a request with missing statistic name
+ ElementPtr params = Element::createMap();
+ rsp = StatsMgr::instance().statisticResetHandler("statistic-reset", params);
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 3: a request for non-existing statistic
+ params->set("name", Element::create("alpha"));
+ rsp = StatsMgr::instance().statisticResetHandler("statistic-reset", params);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'alpha' statistic found\" }",
+ rsp->str());
+}
+
+// This test checks whether statistic-reset-all command really resets all
+// statistics correctly.
+TEST_F(StatsMgrTest, commandResetAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // Now get them. They're used to generate expected output
+ ConstElementPtr rep_alpha = StatsMgr::instance().get("alpha");
+ ConstElementPtr rep_beta = StatsMgr::instance().get("beta");
+ ConstElementPtr rep_gamma = StatsMgr::instance().get("gamma");
+ ConstElementPtr rep_delta = StatsMgr::instance().get("delta");
+
+ ASSERT_TRUE(rep_alpha);
+ ASSERT_TRUE(rep_beta);
+ ASSERT_TRUE(rep_gamma);
+ ASSERT_TRUE(rep_delta);
+
+ std::string exp_str_alpha = "[ [ 1234, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("alpha")
+ ->getInteger().second) + "\" ] ]";
+ std::string exp_str_beta = "[ [ 12.34, \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("beta")
+ ->getFloat().second) + "\" ] ]";
+ std::string exp_str_gamma = "[ [ \"01:02:03.004000\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("gamma")
+ ->getDuration().second) + "\" ] ]";
+ std::string exp_str_delta = "[ [ \"Lorem ipsum\", \"" +
+ isc::util::clockToText(StatsMgr::instance().getObservation("delta")
+ ->getString().second) + "\" ] ]";
+
+ // Check that all of them can be reset at once
+ ConstElementPtr rsp = StatsMgr::instance().statisticResetAllHandler(
+ "statistic-reset-all", ElementPtr());
+ ASSERT_TRUE(rsp);
+ int status_code;
+ ConstElementPtr rep_all = parseAnswer(status_code, rsp);
+ ASSERT_EQ(0, status_code);
+ ASSERT_TRUE(rep_all);
+
+ // Check that they're indeed reset
+ EXPECT_EQ(0,
+ StatsMgr::instance().getObservation("alpha")->getInteger().first);
+ EXPECT_EQ(0.0f,
+ StatsMgr::instance().getObservation("beta")->getFloat().first);
+ EXPECT_EQ(StatsDuration::zero(),
+ StatsMgr::instance().getObservation("gamma")->getDuration().first);
+ EXPECT_EQ("",
+ StatsMgr::instance().getObservation("delta")->getString().first);
+}
+
+// Test checks if statistic-remove handler is able to remove a statistic.
+TEST_F(StatsMgrTest, commandStatisticRemove) {
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+
+ ElementPtr params = Element::createMap();
+ params->set("name", Element::create("alpha"));
+
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticRemoveHandler("statistic-remove", params);
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+
+ // It should be gone.
+ EXPECT_FALSE(StatsMgr::instance().getObservation("alpha"));
+ EXPECT_EQ(0, StatsMgr::instance().count());
+}
+
+// Test checks if statistic-remove is able to handle:
+// - a request without parameters
+// - a request with missing statistic name
+// - a request for non-existing statistic.
+TEST_F(StatsMgrTest, commandStatisticRemoveNegative) {
+ // Case 1: a request without parameters
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticRemoveHandler("statistic-remove", ElementPtr());
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 2: a request with missing statistic name
+ ElementPtr params = Element::createMap();
+ rsp = StatsMgr::instance().statisticRemoveHandler("statistic-remove", params);
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 3: a request for non-existing statistic
+ params->set("name", Element::create("alpha"));
+ rsp = StatsMgr::instance().statisticRemoveHandler("statistic-remove", params);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'alpha' statistic found\" }",
+ rsp->str());
+}
+
+// This test checks whether statistic-remove-all command really resets all
+// statistics correctly.
+TEST_F(StatsMgrTest, commandRemoveAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ // Check that all of them can be reset at once
+ ConstElementPtr rsp = StatsMgr::instance().statisticRemoveAllHandler(
+ "statistic-remove-all", ElementPtr());
+ ASSERT_TRUE(rsp);
+ int status_code;
+ ConstElementPtr rep_all = parseAnswer(status_code, rsp);
+ ASSERT_EQ(0, status_code);
+ ASSERT_TRUE(rep_all);
+ std::string exp = "\"Warning: statistic-remove-all command is deprecated.";
+ exp += " All statistics removed.\"";
+ EXPECT_EQ(exp, rep_all->str());
+
+ EXPECT_FALSE(StatsMgr::instance().getObservation("alpha"));
+ EXPECT_FALSE(StatsMgr::instance().getObservation("beta"));
+ EXPECT_FALSE(StatsMgr::instance().getObservation("gamma"));
+ EXPECT_FALSE(StatsMgr::instance().getObservation("delta"));
+ EXPECT_EQ(0, StatsMgr::instance().count());
+}
+
+// This test checks whether statistic-sample-age-set command really set
+// max_sample_age_ limit correctly.
+TEST_F(StatsMgrTest, commandSetMaxSampleAge) {
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+
+ ElementPtr params = Element::createMap();
+ params->set("name", Element::create("alpha"));
+ params->set("duration", Element::create(1245)); // minutes(20) + seconds(45)
+
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticSetMaxSampleAgeHandler("statistic-sample-age-set", params);
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+
+ // check if time limit was set properly and whether count limit is disabled
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second,
+ minutes(20) + seconds(45));
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, false);
+}
+
+// Test checks if statistic-sample-age-set is able to handle:
+// - a request without parameters
+// - a request without duration parameter
+// - a request with missing statistic name
+// - a request for non-existing statistic.
+TEST_F(StatsMgrTest, commandSetMaxSampleAgeNegative) {
+ // Case 1: a request without parameters
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticSetMaxSampleAgeHandler("statistic-sample-age-set", ElementPtr());
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 2: a request without duration parameter
+ ElementPtr params = Element::createMap();
+ params->set("name", Element::create("alpha"));
+ rsp = StatsMgr::instance().statisticSetMaxSampleAgeHandler("statistic-sample-age-set", params);
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 3: a request with missing statistic name
+ params = Element::createMap();
+ params->set("duration", Element::create(100));
+ rsp = StatsMgr::instance().statisticSetMaxSampleAgeHandler("statistic-sample-age-set", params);
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 4: a request for non-existing statistic
+ params->set("name", Element::create("alpha"));
+ rsp = StatsMgr::instance().statisticSetMaxSampleAgeHandler("statistic-sample-age-set", params);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'alpha' statistic found\" }",
+ rsp->str());
+}
+
+TEST_F(StatsMgrTest, commandSetMaxSampleAgeAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ ElementPtr params = Element::createMap();
+ params->set("duration", Element::create(3765)); // set duration to 3765 seconds
+
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticSetMaxSampleAgeAllHandler(params);
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+
+ // check defaults
+ EXPECT_EQ(StatsMgr::instance().getMaxSampleAgeDefault(), seconds(3765));
+ EXPECT_EQ(StatsMgr::instance().getMaxSampleCountDefault(), 0);
+
+ // check if time limit was set properly and whether count limit is disabled
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().second,
+ dur1245);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().second,
+ dur1245);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().second,
+ dur1245);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().second,
+ dur1245);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, false);
+}
+
+// This test checks whether statistic-sample-count-set command really set
+// max_sample_count_ limit correctly.
+TEST_F(StatsMgrTest, commandSetMaxSampleCount) {
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+
+ ElementPtr params = Element::createMap();
+ params->set("name", Element::create("alpha"));
+ params->set("max-samples", Element::create(15));
+
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticSetMaxSampleCountHandler("statistic-sample-count-set", params);
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+
+ // check if time limit was set properly and whether duration limit is disabled
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 15);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, false);
+}
+
+// Test checks if statistic-sample-count-set is able to handle:
+// - a request without parameters
+// - a request without max-samples parameter
+// - a request with missing statistic name
+// - a request for non-existing statistic.
+TEST_F(StatsMgrTest, commandSetMaxSampleCountNegative) {
+ // Case 1: a request without parameters
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticSetMaxSampleCountHandler("statistic-sample-count-set", ElementPtr());
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 2: a request without max-samples parameter
+ ElementPtr params = Element::createMap();
+ params->set("name", Element::create("alpha"));
+ rsp = StatsMgr::instance().statisticSetMaxSampleCountHandler("statistic-sample-count-set", params);
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 3: a request with missing statistic name
+ params = Element::createMap();
+ params->set("max-samples", Element::create(10));
+ rsp = StatsMgr::instance().statisticSetMaxSampleCountHandler("statistic-sample-count-set", params);
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+
+ // Case 4: a request for non-existing statistic
+ params->set("name", Element::create("alpha"));
+ rsp = StatsMgr::instance().statisticSetMaxSampleCountHandler("statistic-sample-count-set", params);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'alpha' statistic found\" }",
+ rsp->str());
+}
+
+TEST_F(StatsMgrTest, commandSetMaxSampleCountAll) {
+ // Set a couple of statistics
+ StatsMgr::instance().setValue("alpha", static_cast<int64_t>(1234));
+ StatsMgr::instance().setValue("beta", 12.34);
+ StatsMgr::instance().setValue("gamma", dur1234);
+ StatsMgr::instance().setValue("delta", "Lorem ipsum");
+
+ ElementPtr params = Element::createMap();
+ params->set("max-samples", Element::create(200));
+
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticSetMaxSampleCountAllHandler(params);
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+
+ // check default
+ EXPECT_EQ(StatsMgr::instance().getMaxSampleCountDefault(), 200);
+
+ // check if count limit was set properly and whether count limit is disabled
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleCount().second, 200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("alpha")->getMaxSampleAge().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleCount().second, 200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("beta")->getMaxSampleAge().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleCount().second, 200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("gamma")->getMaxSampleAge().first, false);
+
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().first, true);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleCount().second, 200);
+ EXPECT_EQ(StatsMgr::instance().getObservation("delta")->getMaxSampleAge().first, false);
+}
+
+// Test checks if statistics-sample-count-set-all fails on zero.
+TEST_F(StatsMgrTest, commandSetMaxSampleCountAllZero) {
+ ElementPtr params = Element::createMap();
+ params->set("max-samples", Element::create(0));
+
+ ConstElementPtr rsp =
+ StatsMgr::instance().statisticSetMaxSampleCountAllHandler(params);
+ int status_code;
+ ASSERT_NO_THROW(parseAnswer(status_code, rsp));
+ EXPECT_EQ(status_code, CONTROL_RESULT_ERROR);
+}
+
+}
diff --git a/src/lib/stats/testutils/Makefile.am b/src/lib/stats/testutils/Makefile.am
new file mode 100644
index 0000000..009257f
--- /dev/null
+++ b/src/lib/stats/testutils/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST = stats_test_utils.h
diff --git a/src/lib/stats/testutils/Makefile.in b/src/lib/stats/testutils/Makefile.in
new file mode 100644
index 0000000..e608be7
--- /dev/null
+++ b/src/lib/stats/testutils/Makefile.in
@@ -0,0 +1,548 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/stats/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+EXTRA_DIST = stats_test_utils.h
+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/stats/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/stats/testutils/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:
+
+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
+
+
+# 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/stats/testutils/stats_test_utils.h b/src/lib/stats/testutils/stats_test_utils.h
new file mode 100644
index 0000000..3a3786a
--- /dev/null
+++ b/src/lib/stats/testutils/stats_test_utils.h
@@ -0,0 +1,73 @@
+// Copyright (C) 2020-2023 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 STATS_TEST_UTILS_H
+#define STATS_TEST_UTILS_H
+
+#include <cc/data.h>
+#include <stats/stats_mgr.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace stats {
+namespace test {
+
+/// @brief Type of name x value for statistics.
+typedef std::map<std::string, int64_t> StatMap;
+
+/// @brief Compares a statistic to an expected value.
+///
+/// Attempt to fetch the named statistic from the StatsMgr and if
+/// found, compare its observed value to the given value.
+/// Fails if the stat is not found or if the values do not match.
+///
+/// @param name StatsMgr name for the statistic to check.
+/// @param expected_value expected value of the statistic.
+inline void checkStat(const std::string& name,
+ const isc::util::int128_t expected_value) {
+ ObservationPtr const obs(isc::stats::StatsMgr::instance().getObservation(name));
+
+ ASSERT_TRUE(obs) << "stat " << name << " not found ";
+ if (obs->getType() == Observation::STAT_INTEGER) {
+ EXPECT_EQ(expected_value, obs->getInteger().first)
+ << " wrong value for stat " << name;
+ } else if (obs->getType() == Observation::STAT_BIG_INTEGER) {
+ EXPECT_EQ(expected_value, obs->getBigInteger().first)
+ << " wrong value for stat " << name;
+ } else {
+ GTEST_FAIL() << "unexpected statistic type: "
+ << Observation::typeToText(obs->getType());
+ }
+}
+
+/// @brief Check if a statistic does not exists.
+///
+/// @param name StatsMgr name for the statistic to check.
+inline void checkNoStat(const std::string& name) {
+ using namespace isc::stats;
+ EXPECT_FALSE(StatsMgr::instance().getObservation(name));
+}
+
+/// @brief Compares StatsMgr statistics against expected values.
+///
+/// Iterates over a list of statistic names and expected values, attempting
+/// to fetch each from the StatsMgr and if found, compare its observed
+/// value to the expected value. Fails if any of the expected stats are not
+/// found or if the values do not match.
+///
+/// @param expected_stats Map of expected static names and values.
+inline void checkStats(const StatMap& expected_stats) {
+ for (const auto& it : expected_stats) {
+ checkStat(it.first, it.second);
+ }
+}
+
+}
+}
+}
+
+#endif // STATS_TEST_UTILS_H
diff --git a/src/lib/tcp/Makefile.am b/src/lib/tcp/Makefile.am
new file mode 100644
index 0000000..be1c209
--- /dev/null
+++ b/src/lib/tcp/Makefile.am
@@ -0,0 +1,80 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+EXTRA_DIST = libkea_tcp.dox
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST += tcp_messages.mes
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libkea-tcp.la
+
+libkea_tcp_la_SOURCES = tcp_connection.cc tcp_connection.h
+libkea_tcp_la_SOURCES += tcp_connection_pool.cc tcp_connection_pool.h
+libkea_tcp_la_SOURCES += tcp_listener.cc tcp_listener.h
+libkea_tcp_la_SOURCES += mt_tcp_listener_mgr.cc mt_tcp_listener_mgr.h
+libkea_tcp_la_SOURCES += tcp_log.cc tcp_log.h
+libkea_tcp_la_SOURCES += tcp_messages.cc tcp_messages.h
+libkea_tcp_la_SOURCES += tcp_stream_msg.cc tcp_stream_msg.h
+
+libkea_tcp_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_tcp_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_tcp_la_LDFLAGS = $(AM_LDFLAGS)
+libkea_tcp_la_LDFLAGS += -no-undefined -version-info 5:0:0
+
+libkea_tcp_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_tcp_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_tcp_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f tcp_messages.h tcp_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+if GENERATE_MESSAGES
+
+# Define rule to build logging source files from message file
+messages: tcp_messages.h tcp_messages.cc
+ @echo Message files regenerated
+
+tcp_messages.h tcp_messages.cc: tcp_messages.mes
+ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/tcp/tcp_messages.mes
+
+else
+
+messages tcp_messages.h tcp_messages.cc:
+ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+endif
+
+# Specify the headers for copying into the installation directory tree.
+libkea_tcp_includedir = $(pkgincludedir)/tcp
+libkea_tcp_include_HEADERS = \
+ mt_tcp_listener_mgr.h \
+ tcp_connection_acceptor.h \
+ tcp_connection.h \
+ tcp_connection_pool.h \
+ tcp_listener.h \
+ tcp_log.h \
+ tcp_messages.h \
+ tcp_stream_msg.h
diff --git a/src/lib/tcp/Makefile.in b/src/lib/tcp/Makefile.in
new file mode 100644
index 0000000..f268519
--- /dev/null
+++ b/src/lib/tcp/Makefile.in
@@ -0,0 +1,1076 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/tcp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_tcp_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)$(libkea_tcp_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_tcp_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_libkea_tcp_la_OBJECTS = libkea_tcp_la-tcp_connection.lo \
+ libkea_tcp_la-tcp_connection_pool.lo \
+ libkea_tcp_la-tcp_listener.lo \
+ libkea_tcp_la-mt_tcp_listener_mgr.lo libkea_tcp_la-tcp_log.lo \
+ libkea_tcp_la-tcp_messages.lo libkea_tcp_la-tcp_stream_msg.lo
+libkea_tcp_la_OBJECTS = $(am_libkea_tcp_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_tcp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) $(libkea_tcp_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_tcp_la-mt_tcp_listener_mgr.Plo \
+ ./$(DEPDIR)/libkea_tcp_la-tcp_connection.Plo \
+ ./$(DEPDIR)/libkea_tcp_la-tcp_connection_pool.Plo \
+ ./$(DEPDIR)/libkea_tcp_la-tcp_listener.Plo \
+ ./$(DEPDIR)/libkea_tcp_la-tcp_log.Plo \
+ ./$(DEPDIR)/libkea_tcp_la-tcp_messages.Plo \
+ ./$(DEPDIR)/libkea_tcp_la-tcp_stream_msg.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_tcp_la_SOURCES)
+DIST_SOURCES = $(libkea_tcp_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 = $(libkea_tcp_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)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . tests
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = libkea_tcp.dox tcp_messages.mes
+CLEANFILES = *.gcno *.gcda
+lib_LTLIBRARIES = libkea-tcp.la
+libkea_tcp_la_SOURCES = tcp_connection.cc tcp_connection.h \
+ tcp_connection_pool.cc tcp_connection_pool.h tcp_listener.cc \
+ tcp_listener.h mt_tcp_listener_mgr.cc mt_tcp_listener_mgr.h \
+ tcp_log.cc tcp_log.h tcp_messages.cc tcp_messages.h \
+ tcp_stream_msg.cc tcp_stream_msg.h
+libkea_tcp_la_CXXFLAGS = $(AM_CXXFLAGS)
+libkea_tcp_la_CPPFLAGS = $(AM_CPPFLAGS)
+libkea_tcp_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info \
+ 5:0:0
+libkea_tcp_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+
+# Specify the headers for copying into the installation directory tree.
+libkea_tcp_includedir = $(pkgincludedir)/tcp
+libkea_tcp_include_HEADERS = \
+ mt_tcp_listener_mgr.h \
+ tcp_connection_acceptor.h \
+ tcp_connection.h \
+ tcp_connection_pool.h \
+ tcp_listener.h \
+ tcp_log.h \
+ tcp_messages.h \
+ tcp_stream_msg.h
+
+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/tcp/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/tcp/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-tcp.la: $(libkea_tcp_la_OBJECTS) $(libkea_tcp_la_DEPENDENCIES) $(EXTRA_libkea_tcp_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_tcp_la_LINK) -rpath $(libdir) $(libkea_tcp_la_OBJECTS) $(libkea_tcp_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_tcp_la-mt_tcp_listener_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_tcp_la-tcp_connection.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_tcp_la-tcp_connection_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_tcp_la-tcp_listener.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_tcp_la-tcp_log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_tcp_la-tcp_messages.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_tcp_la-tcp_stream_msg.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)$(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 $@ $<
+
+libkea_tcp_la-tcp_connection.lo: tcp_connection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_tcp_la-tcp_connection.lo -MD -MP -MF $(DEPDIR)/libkea_tcp_la-tcp_connection.Tpo -c -o libkea_tcp_la-tcp_connection.lo `test -f 'tcp_connection.cc' || echo '$(srcdir)/'`tcp_connection.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_tcp_la-tcp_connection.Tpo $(DEPDIR)/libkea_tcp_la-tcp_connection.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_connection.cc' object='libkea_tcp_la-tcp_connection.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_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_tcp_la-tcp_connection.lo `test -f 'tcp_connection.cc' || echo '$(srcdir)/'`tcp_connection.cc
+
+libkea_tcp_la-tcp_connection_pool.lo: tcp_connection_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_tcp_la-tcp_connection_pool.lo -MD -MP -MF $(DEPDIR)/libkea_tcp_la-tcp_connection_pool.Tpo -c -o libkea_tcp_la-tcp_connection_pool.lo `test -f 'tcp_connection_pool.cc' || echo '$(srcdir)/'`tcp_connection_pool.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_tcp_la-tcp_connection_pool.Tpo $(DEPDIR)/libkea_tcp_la-tcp_connection_pool.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_connection_pool.cc' object='libkea_tcp_la-tcp_connection_pool.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_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_tcp_la-tcp_connection_pool.lo `test -f 'tcp_connection_pool.cc' || echo '$(srcdir)/'`tcp_connection_pool.cc
+
+libkea_tcp_la-tcp_listener.lo: tcp_listener.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_tcp_la-tcp_listener.lo -MD -MP -MF $(DEPDIR)/libkea_tcp_la-tcp_listener.Tpo -c -o libkea_tcp_la-tcp_listener.lo `test -f 'tcp_listener.cc' || echo '$(srcdir)/'`tcp_listener.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_tcp_la-tcp_listener.Tpo $(DEPDIR)/libkea_tcp_la-tcp_listener.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_listener.cc' object='libkea_tcp_la-tcp_listener.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_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_tcp_la-tcp_listener.lo `test -f 'tcp_listener.cc' || echo '$(srcdir)/'`tcp_listener.cc
+
+libkea_tcp_la-mt_tcp_listener_mgr.lo: mt_tcp_listener_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_tcp_la-mt_tcp_listener_mgr.lo -MD -MP -MF $(DEPDIR)/libkea_tcp_la-mt_tcp_listener_mgr.Tpo -c -o libkea_tcp_la-mt_tcp_listener_mgr.lo `test -f 'mt_tcp_listener_mgr.cc' || echo '$(srcdir)/'`mt_tcp_listener_mgr.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_tcp_la-mt_tcp_listener_mgr.Tpo $(DEPDIR)/libkea_tcp_la-mt_tcp_listener_mgr.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mt_tcp_listener_mgr.cc' object='libkea_tcp_la-mt_tcp_listener_mgr.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_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_tcp_la-mt_tcp_listener_mgr.lo `test -f 'mt_tcp_listener_mgr.cc' || echo '$(srcdir)/'`mt_tcp_listener_mgr.cc
+
+libkea_tcp_la-tcp_log.lo: tcp_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_tcp_la-tcp_log.lo -MD -MP -MF $(DEPDIR)/libkea_tcp_la-tcp_log.Tpo -c -o libkea_tcp_la-tcp_log.lo `test -f 'tcp_log.cc' || echo '$(srcdir)/'`tcp_log.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_tcp_la-tcp_log.Tpo $(DEPDIR)/libkea_tcp_la-tcp_log.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_log.cc' object='libkea_tcp_la-tcp_log.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_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_tcp_la-tcp_log.lo `test -f 'tcp_log.cc' || echo '$(srcdir)/'`tcp_log.cc
+
+libkea_tcp_la-tcp_messages.lo: tcp_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_tcp_la-tcp_messages.lo -MD -MP -MF $(DEPDIR)/libkea_tcp_la-tcp_messages.Tpo -c -o libkea_tcp_la-tcp_messages.lo `test -f 'tcp_messages.cc' || echo '$(srcdir)/'`tcp_messages.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_tcp_la-tcp_messages.Tpo $(DEPDIR)/libkea_tcp_la-tcp_messages.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_messages.cc' object='libkea_tcp_la-tcp_messages.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_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_tcp_la-tcp_messages.lo `test -f 'tcp_messages.cc' || echo '$(srcdir)/'`tcp_messages.cc
+
+libkea_tcp_la-tcp_stream_msg.lo: tcp_stream_msg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_tcp_la-tcp_stream_msg.lo -MD -MP -MF $(DEPDIR)/libkea_tcp_la-tcp_stream_msg.Tpo -c -o libkea_tcp_la-tcp_stream_msg.lo `test -f 'tcp_stream_msg.cc' || echo '$(srcdir)/'`tcp_stream_msg.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_tcp_la-tcp_stream_msg.Tpo $(DEPDIR)/libkea_tcp_la-tcp_stream_msg.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_stream_msg.cc' object='libkea_tcp_la-tcp_stream_msg.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_tcp_la_CPPFLAGS) $(CPPFLAGS) $(libkea_tcp_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_tcp_la-tcp_stream_msg.lo `test -f 'tcp_stream_msg.cc' || echo '$(srcdir)/'`tcp_stream_msg.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_tcp_includeHEADERS: $(libkea_tcp_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_tcp_include_HEADERS)'; test -n "$(libkea_tcp_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_tcp_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_tcp_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)$(libkea_tcp_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_tcp_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_tcp_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_tcp_include_HEADERS)'; test -n "$(libkea_tcp_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_tcp_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_tcp_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-mt_tcp_listener_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_connection.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_connection_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_listener.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_stream_msg.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-libkea_tcp_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_tcp_la-mt_tcp_listener_mgr.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_connection.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_connection_pool.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_listener.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_log.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_messages.Plo
+ -rm -f ./$(DEPDIR)/libkea_tcp_la-tcp_stream_msg.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic \
+ maintainer-clean-local
+
+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-libkea_tcp_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_tcp_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 \
+ maintainer-clean-local mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \
+ uninstall-libkea_tcp_includeHEADERS
+
+.PRECIOUS: Makefile
+
+
+# If we want to get rid of all generated messages files, we need to use
+# make maintainer-clean. The proper way to introduce custom commands for
+# that operation is to define maintainer-clean-local target. However,
+# make maintainer-clean also removes Makefile, so running configure script
+# is required. To make it easy to rebuild messages without going through
+# reconfigure, a new target messages-clean has been added.
+maintainer-clean-local:
+ rm -f tcp_messages.h tcp_messages.cc
+
+# To regenerate messages files, one can do:
+#
+# make messages-clean
+# make messages
+#
+# This is needed only when a .mes file is modified.
+messages-clean: maintainer-clean-local
+
+# Define rule to build logging source files from message file
+@GENERATE_MESSAGES_TRUE@messages: tcp_messages.h tcp_messages.cc
+@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated
+
+@GENERATE_MESSAGES_TRUE@tcp_messages.h tcp_messages.cc: tcp_messages.mes
+@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/tcp/tcp_messages.mes
+
+@GENERATE_MESSAGES_FALSE@messages tcp_messages.h tcp_messages.cc:
+@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it.
+
+# 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/tcp/README b/src/lib/tcp/README
new file mode 100644
index 0000000..68ef83f
--- /dev/null
+++ b/src/lib/tcp/README
@@ -0,0 +1 @@
+The tcp library is intended to provide support for TCP server/listeners.
diff --git a/src/lib/tcp/libkea_tcp.dox b/src/lib/tcp/libkea_tcp.dox
new file mode 100644
index 0000000..8dce873
--- /dev/null
+++ b/src/lib/tcp/libkea_tcp.dox
@@ -0,0 +1,19 @@
+// Copyright (C) 2022 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/.
+
+/**
+@page libkea_tcp libkea-kea_tcp - TCP Listener Library
+
+@section libkea_tcpIntro Introduction
+
+This is a library of classes (in the isc::kea_tcp namespace) that provide
+the ability to accept connections, listen for and respond to TCP messages.
+
+@section tcpMTConsiderations Multi-Threading Consideration for TCP Library
+
+This library is thread safe.
+
+*/
diff --git a/src/lib/tcp/mt_tcp_listener_mgr.cc b/src/lib/tcp/mt_tcp_listener_mgr.cc
new file mode 100644
index 0000000..a8afa6d
--- /dev/null
+++ b/src/lib/tcp/mt_tcp_listener_mgr.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2022-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <asiolink/io_service.h>
+#include <mt_tcp_listener_mgr.h>
+#include <tcp_log.h>
+#include <util/multi_threading_mgr.h>
+
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::asiolink;
+using namespace isc::tcp;
+using namespace isc::util;
+
+namespace isc {
+namespace tcp {
+
+MtTcpListenerMgr::MtTcpListenerMgr(TcpListenerFactory listener_factory,
+ const IOAddress& address,
+ const uint16_t port,
+ const uint16_t thread_pool_size /* = 1 */,
+ TlsContextPtr context /* = () */,
+ TcpConnectionFilterCallback connection_filter /* = 0 */)
+ : listener_factory_(listener_factory),
+ address_(address), port_(port), thread_io_service_(), tcp_listener_(),
+ thread_pool_size_(thread_pool_size), thread_pool_(),
+ tls_context_(context), connection_filter_(connection_filter),
+ idle_timeout_(TCP_IDLE_CONNECTION_TIMEOUT) {
+}
+
+MtTcpListenerMgr::~MtTcpListenerMgr() {
+ stop();
+}
+
+void
+MtTcpListenerMgr::start() {
+ // We must be in multi-threading mode.
+ if (!MultiThreadingMgr::instance().getMode()) {
+ isc_throw(InvalidOperation, "MtTcpListenerMgr cannot be started"
+ " when multi-threading is disabled");
+ }
+
+ // Punt if we're already started.
+ if (!isStopped()) {
+ isc_throw(InvalidOperation, "MtTcpListenerMgr already started!");
+ }
+
+ try {
+ // Create a new IOService.
+ thread_io_service_.reset(new IOService());
+
+ // Create a new TCPListener derivation using the factory.
+ tcp_listener_ = listener_factory_(*thread_io_service_,
+ address_,
+ port_,
+ tls_context_,
+ idle_timeout_,
+ connection_filter_);
+
+ // Instruct the HTTP listener to actually open socket, install
+ // callback and start listening.
+ tcp_listener_->start();
+
+ // Create the thread pool with immediate start.
+ thread_pool_.reset(new IoServiceThreadPool(thread_io_service_, thread_pool_size_));
+
+ // OK, seems like we're good to go.
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC, MT_TCP_LISTENER_MGR_STARTED)
+ .arg(thread_pool_size_)
+ .arg(address_)
+ .arg(port_)
+ .arg(tls_context_ ? "true" : "false");
+ } catch (const std::exception& ex) {
+ thread_io_service_.reset();
+ tcp_listener_.reset();
+ thread_pool_.reset();
+ isc_throw(Unexpected, "MtTcpListenerMgr::start failed:" << ex.what());
+ }
+}
+
+void
+MtTcpListenerMgr::checkPermissions() {
+ if (thread_pool_) {
+ thread_pool_->checkPausePermissions();
+ }
+}
+
+void
+MtTcpListenerMgr::pause() {
+ if (thread_pool_) {
+ thread_pool_->pause();
+ }
+}
+
+void
+MtTcpListenerMgr::resume() {
+ if (thread_pool_) {
+ thread_pool_->run();
+ }
+}
+
+void
+MtTcpListenerMgr::stop() {
+ // Nothing to do.
+ if (!thread_io_service_) {
+ return;
+ }
+
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC, MT_TCP_LISTENER_MGR_STOPPING)
+ .arg(address_)
+ .arg(port_);
+
+ // Stop the thread pool.
+ thread_pool_->stop();
+
+ // Get rid of the listener.
+ tcp_listener_.reset();
+
+ // Ditch the IOService.
+ thread_io_service_.reset();
+
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC, MT_TCP_LISTENER_MGR_STOPPED)
+ .arg(address_)
+ .arg(port_);
+}
+
+bool
+MtTcpListenerMgr::isRunning() {
+ if (thread_pool_) {
+ return (thread_pool_->isRunning());
+ }
+
+ return (false);
+}
+
+bool
+MtTcpListenerMgr::isStopped() {
+ if (thread_pool_) {
+ return (thread_pool_->isStopped());
+ }
+
+ return (true);
+}
+
+bool
+MtTcpListenerMgr::isPaused() {
+ if (thread_pool_) {
+ return (thread_pool_->isPaused());
+ }
+
+ return (false);
+}
+
+} // namespace isc::config
+} // namespace isc
diff --git a/src/lib/tcp/mt_tcp_listener_mgr.h b/src/lib/tcp/mt_tcp_listener_mgr.h
new file mode 100644
index 0000000..6fc2600
--- /dev/null
+++ b/src/lib/tcp/mt_tcp_listener_mgr.h
@@ -0,0 +1,211 @@
+// Copyright (C) 2022-2023 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 MT_TCP_LISTENER_MGR_H
+#define MT_TCP_LISTENER_MGR_H
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_service_thread_pool.h>
+#include <tcp/tcp_listener.h>
+#include <thread>
+#include <vector>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Default connection idle timeout in milliseconds.
+const long TCP_IDLE_CONNECTION_TIMEOUT = 300 * 1000;
+
+/// @brief Defines a factory function for creating TcpListeners.
+typedef std::function<
+ TcpListenerPtr(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const TcpListener::IdleTimeout& idle_timeout,
+ const TcpConnectionFilterCallback& connection_filter)
+ > TcpListenerFactory;
+
+/// @brief Manages a thread-pool that is used to drive a TcpListener
+///
+/// This class manages an IOServiceThreadPool which in turn is used to
+/// drive an internal instance of TcpListener. This allows the listener connections
+/// to each run on their own thread within the pool. The pool can be started, paused,
+/// resumed, and stopped.
+///
+/// @note This class is NOT compatible with Kea core single-threading.
+/// It is incumbent upon the owner to ensure the Kea core multi-threading
+/// is (or will be) enabled when creating instances of this class.
+class MtTcpListenerMgr {
+public:
+ /// @brief Constructor
+ ///
+ /// @param listener_factory Function for instantiating the internal
+ /// TcpListener
+ /// @param address Ip address to listen on for connections
+ /// @param port Ip port to listen on for connections
+ /// @param thread_pool_size Maximum Number of threads in the thread pool.
+ /// This implicit dictates the maximum number of connections.
+ /// @param context TLS context for authenticating connections. Defaults
+ /// to empty.
+ /// @param connection_filter Callback connections may use to filter
+ /// connections by their remote endpoint characteristics (e.g. ip address)
+ MtTcpListenerMgr(TcpListenerFactory listener_factory,
+ const asiolink::IOAddress& address,
+ const uint16_t port,
+ const uint16_t thread_pool_size = 1,
+ asiolink::TlsContextPtr context = asiolink::TlsContextPtr(),
+ TcpConnectionFilterCallback connection_filter = 0);
+
+ /// @brief Destructor
+ virtual ~MtTcpListenerMgr();
+
+ /// @brief Check if the current thread can perform thread pool state
+ /// transition.
+ ///
+ /// @throw MultiThreadingInvalidOperation if the state transition is done on
+ /// any of the worker threads.
+ void checkPermissions();
+
+ /// @brief Starts running the listener's thread pool.
+ void start();
+
+ /// @brief Pauses the listener's thread pool.
+ ///
+ /// Suspends thread pool event processing.
+ void pause();
+
+ /// @brief Resumes running the listener's thread pool.
+ ///
+ /// Resumes thread pool event processing.
+ void resume();
+
+ /// @brief Stops the listener's thread pool.
+ void stop();
+
+ /// @brief Indicates if the thread pool is running.
+ ///
+ /// @return True if the thread pool exists and it is in the RUNNING state,
+ /// false otherwise.
+ bool isRunning();
+
+ /// @brief Indicates if the thread pool is stopped.
+ ///
+ /// @return True if the thread pool does not exist or it is in the STOPPED
+ /// state, false otherwise.
+ bool isStopped();
+
+ /// @brief Indicates if the thread pool is paused.
+ ///
+ /// @return True if the thread pool exists and it is in the PAUSED state,
+ /// false otherwise.
+ bool isPaused();
+
+ /// @brief Fetches the IP address on which to listen.
+ ///
+ /// @return IOAddress containing the address on which to listen.
+ isc::asiolink::IOAddress getAddress() const {
+ return (address_);
+ }
+
+ /// @brief Fetches the port number on which to listen.
+ ///
+ /// @return uint16_t containing the port number on which to listen.
+ uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Fetches the maximum size of the thread pool.
+ ///
+ /// @return uint16_t containing the maximum size of the thread pool.
+ uint16_t getThreadPoolSize() const {
+ return (thread_pool_size_);
+ }
+
+ /// @brief Fetches the TLS context.
+ ///
+ /// @return TLS context.
+ asiolink::TlsContextPtr getTlsContext() const {
+ return (tls_context_);
+ }
+
+ /// @brief Fetches the number of threads in the pool.
+ ///
+ /// @return uint16_t containing the number of running threads.
+ uint16_t getThreadCount() const {
+ if (!thread_pool_) {
+ return (0);
+ }
+
+ return (thread_pool_->getThreadCount());
+ }
+
+ /// @brief Fetches a pointer to the internal IOService used to
+ /// drive the thread-pool in multi-threaded mode.
+ ///
+ /// @return pointer to the IOService instance, or an empty pointer
+ /// in single-threaded mode.
+ asiolink::IOServicePtr getThreadIOService() const {
+ return (thread_io_service_);
+ }
+
+ /// @brief Fetch a pointer to the listener.
+ const TcpListenerPtr getTcpListener() {
+ return(tcp_listener_);
+ }
+
+ /// @brief Sets the idle time per connection.
+ ///
+ /// @param milliseconds Amount of time in milliseconds
+ void setIdleTimeout(long milliseconds) {
+ idle_timeout_ = TcpListener::IdleTimeout(milliseconds);
+ }
+
+ long getIdleTimeout() {
+ return (idle_timeout_.value_);
+ }
+
+private:
+ /// @brief Factory for creating TcpListener instances.
+ TcpListenerFactory listener_factory_;
+
+ /// @brief IP address on which to listen.
+ isc::asiolink::IOAddress address_;
+
+ /// @brief Port on which to listen.
+ uint16_t port_;
+
+ /// @brief IOService instance that drives our IO.
+ isc::asiolink::IOServicePtr thread_io_service_;
+
+ /// @brief The TcpListener instance
+ TcpListenerPtr tcp_listener_;
+
+ /// @brief The number of threads that will call IOService_context::run().
+ std::size_t thread_pool_size_;
+
+ /// @brief The pool of threads that do IO work.
+ asiolink::IoServiceThreadPoolPtr thread_pool_;
+
+ /// @brief The TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Callback the listener may use to reject connections during acceptance.
+ TcpConnectionFilterCallback connection_filter_;
+
+ /// @brief Time in milliseconds that a connection can remain idle before
+ /// it is closed.
+ TcpListener::IdleTimeout idle_timeout_;
+};
+
+/// @brief Defines a shared pointer to MtTcpListenerMgr.
+typedef boost::shared_ptr<MtTcpListenerMgr> MtTcpListenerMgrPtr;
+
+} // namespace isc::tcp
+} // namespace isc
+
+#endif // MT_TCP_LISTENER_MGR_H
diff --git a/src/lib/tcp/tcp_connection.cc b/src/lib/tcp/tcp_connection.cc
new file mode 100644
index 0000000..d0e5393
--- /dev/null
+++ b/src/lib/tcp/tcp_connection.cc
@@ -0,0 +1,529 @@
+// Copyright (C) 2022-2023 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 <asiolink/asio_wrapper.h>
+#include <tcp/tcp_connection.h>
+#include <tcp/tcp_connection_pool.h>
+#include <tcp/tcp_log.h>
+#include <tcp/tcp_messages.h>
+#include <util/strutil.h>
+#include <boost/make_shared.hpp>
+
+#include <iomanip>
+#include <sstream>
+#include <functional>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of a message that can be logged.
+///
+/// The part of the message beyond this value is truncated.
+const size_t MAX_LOGGED_MESSAGE_SIZE = 1024;
+
+}
+
+namespace isc {
+namespace tcp {
+
+void
+TcpResponse::consumeWireData(const size_t length) {
+ send_in_progress_ = true;
+ wire_data_.erase(wire_data_.begin(), wire_data_.begin() + length);
+}
+
+void
+TcpConnection::
+SocketCallback::operator()(boost::system::error_code ec, size_t length) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+ callback_(ec, length);
+}
+
+TcpConnection::TcpConnection(asiolink::IOService& io_service,
+ const TcpConnectionAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ TcpConnectionPool& connection_pool,
+ const TcpConnectionAcceptorCallback& acceptor_callback,
+ const TcpConnectionFilterCallback& connection_filter,
+ const long idle_timeout,
+ const size_t read_max /* = 32768 */)
+ : tls_context_(tls_context),
+ idle_timeout_(idle_timeout),
+ idle_timer_(io_service),
+ tcp_socket_(),
+ tls_socket_(),
+ acceptor_(acceptor),
+ connection_pool_(connection_pool),
+ acceptor_callback_(acceptor_callback),
+ connection_filter_(connection_filter),
+ read_max_(read_max),
+ input_buf_(read_max) {
+ if (!tls_context) {
+ tcp_socket_.reset(new asiolink::TCPSocket<SocketCallback>(io_service));
+ } else {
+ tls_socket_.reset(new asiolink::TLSSocket<SocketCallback>(io_service,
+ tls_context));
+ }
+}
+
+TcpConnection::~TcpConnection() {
+ close();
+}
+
+void
+TcpConnection::shutdownCallback(const boost::system::error_code&) {
+ tls_socket_->close();
+}
+
+void
+TcpConnection::shutdown() {
+ idle_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+
+ if (tls_socket_) {
+ // Create instance of the callback to close the socket.
+ SocketCallback cb(std::bind(&TcpConnection::shutdownCallback,
+ shared_from_this(),
+ ph::_1)); // error_code
+ tls_socket_->shutdown(cb);
+ return;
+ }
+
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to shutdown the socket");
+}
+
+void
+TcpConnection::close() {
+ idle_timer_.cancel();
+ if (tcp_socket_) {
+ tcp_socket_->close();
+ return;
+ }
+
+ if (tls_socket_) {
+ tls_socket_->close();
+ return;
+ }
+
+ // Not reachable?
+ isc_throw(Unexpected, "internal error: unable to close the socket");
+}
+
+void
+TcpConnection::shutdownConnection() {
+ try {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_CONNECTION_SHUTDOWN)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.shutdown(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(tcp_logger, TCP_CONNECTION_SHUTDOWN_FAILED);
+ }
+}
+
+void
+TcpConnection::stopThisConnection() {
+ try {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_CONNECTION_STOP)
+ .arg(getRemoteEndpointAddressAsText());
+ connection_pool_.stop(shared_from_this());
+ } catch (...) {
+ LOG_ERROR(tcp_logger, TCP_CONNECTION_STOP_FAILED);
+ }
+}
+
+void
+TcpConnection::asyncAccept() {
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ TcpConnectionAcceptorCallback cb = std::bind(&TcpConnection::acceptorCallback,
+ shared_from_this(),
+ ph::_1);
+ try {
+ TlsConnectionAcceptorPtr tls_acceptor =
+ boost::dynamic_pointer_cast<TlsConnectionAcceptor>(acceptor_);
+ if (!tls_acceptor) {
+ if (!tcp_socket_) {
+ isc_throw(Unexpected, "internal error: TCP socket is null");
+ }
+ acceptor_->asyncAccept(*tcp_socket_, cb);
+ } else {
+ if (!tls_socket_) {
+ isc_throw(Unexpected, "internal error: TLS socket is null");
+ }
+ tls_acceptor->asyncAccept(*tls_socket_, cb);
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(TcpConnectionError, "unable to start accepting TCP "
+ "connections: " << ex.what());
+ }
+}
+
+void
+TcpConnection::doHandshake() {
+ // Skip the handshake if the socket is not a TLS one.
+ if (!tls_socket_) {
+ doRead();
+ return;
+ }
+
+ setupIdleTimer();
+
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying boost functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&TcpConnection::handshakeCallback,
+ shared_from_this(),
+ ph::_1)); // error
+ try {
+ tls_socket_->handshake(cb);
+
+ } catch (const std::exception& ex) {
+ isc_throw(TcpConnectionError, "unable to perform TLS handshake: "
+ << ex.what());
+ }
+}
+
+void
+TcpConnection::doRead(TcpRequestPtr request) {
+ try {
+ TCPEndpoint endpoint;
+
+ setupIdleTimer();
+
+ // Request hasn't been created if we are starting to read the
+ // new request.
+ if (!request) {
+ request = createRequest();
+ }
+
+ // Create instance of the callback. It is safe to pass the local instance
+ // of the callback, because the underlying std functions make copies
+ // as needed.
+ SocketCallback cb(std::bind(&TcpConnection::socketReadCallback,
+ shared_from_this(),
+ request,
+ ph::_1, // error
+ ph::_2)); // bytes_transferred
+ if (tcp_socket_) {
+ tcp_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
+ getInputBufSize(), 0, &endpoint, cb);
+ return;
+ }
+
+ if (tls_socket_) {
+ tls_socket_->asyncReceive(static_cast<void*>(getInputBufData()),
+ getInputBufSize(), 0, &endpoint, cb);
+ return;
+ }
+ } catch (...) {
+ stopThisConnection();
+ }
+}
+
+void
+TcpConnection::doWrite(TcpResponsePtr response) {
+ try {
+ if (response->wireDataAvail()) {
+ // Create instance of the callback. It is safe to pass the
+ // local instance of the callback, because the underlying
+ // std functions make copies as needed.
+ SocketCallback cb(std::bind(&TcpConnection::socketWriteCallback,
+ shared_from_this(),
+ response,
+ ph::_1, // error
+ ph::_2)); // bytes_transferred
+ if (tcp_socket_) {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_SERVER_RESPONSE_SEND)
+ .arg(getRemoteEndpointAddressAsText());
+ tcp_socket_->asyncSend(response->getWireData(),
+ response->getWireDataSize(),
+ cb);
+ return;
+ }
+ if (tls_socket_) {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TLS_SERVER_RESPONSE_SEND)
+ .arg(getRemoteEndpointAddressAsText());
+ tls_socket_->asyncSend(response->getWireData(),
+ response->getWireDataSize(),
+ cb);
+ return;
+ }
+ } else {
+ // The connection remains open and we are done sending the response.
+ // If the response sent handler returns true then we should start the
+ // idle timer.
+ if (responseSent(response)) {
+ setupIdleTimer();
+ }
+ }
+ } catch (...) {
+ // The connection is dead and there can't be a pending write as
+ // they are in sequence.
+ TcpConnection::stopThisConnection();
+ }
+}
+
+void
+TcpConnection::asyncSendResponse(TcpResponsePtr response) {
+ doWrite(response);
+}
+
+
+void
+TcpConnection::acceptorCallback(const boost::system::error_code& ec) {
+ if (!acceptor_->isOpen()) {
+ return;
+ }
+
+ if (ec) {
+ stopThisConnection();
+ }
+
+ // Stage a new connection to listen for next client.
+ acceptor_callback_(ec);
+
+ if (!ec) {
+ try {
+ if (tcp_socket_ && tcp_socket_->getASIOSocket().is_open()) {
+ remote_endpoint_ =
+ tcp_socket_->getASIOSocket().remote_endpoint();
+ } else if (tls_socket_ && tls_socket_->getASIOSocket().is_open()) {
+ remote_endpoint_ =
+ tls_socket_->getASIOSocket().remote_endpoint();
+ }
+ } catch (...) {
+ // Let's it to fail later.
+ }
+
+ // In theory, we should not get here with an unopened socket
+ // but just in case, we'll check for NO_ENDPOINT.
+ if ((remote_endpoint_ == NO_ENDPOINT()) ||
+ (connection_filter_ && !connection_filter_(remote_endpoint_))) {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TCP_CONNECTION_REJECTED_BY_FILTER)
+ .arg(getRemoteEndpointAddressAsText());
+ TcpConnectionPool::rejected_counter_ += 1;
+ stopThisConnection();
+ return;
+ }
+
+ if (!tls_context_) {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TCP_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(idle_timeout_/1000));
+ } else {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TLS_CONNECTION_HANDSHAKE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(idle_timeout_/1000));
+ }
+
+ doHandshake();
+ }
+}
+
+void
+TcpConnection::handshakeCallback(const boost::system::error_code& ec) {
+ if (ec) {
+ LOG_INFO(tcp_logger, TLS_CONNECTION_HANDSHAKE_FAILED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ec.message());
+ stopThisConnection();
+ } else {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TLS_REQUEST_RECEIVE_START)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(static_cast<unsigned>(idle_timeout_/1000));
+ doRead();
+ }
+}
+
+void
+TcpConnection::socketReadCallback(TcpRequestPtr request,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ stopThisConnection();
+ return;
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt. Just make sure
+ // we don't try to read anything now in case there is any garbage
+ // passed in length.
+ } else {
+ length = 0;
+ }
+ }
+
+ // Data received, Restart the request timer.
+ setupIdleTimer();
+
+ TcpRequestPtr next_request = request;
+ if (length) {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ TCP_DATA_RECEIVED)
+ .arg(length)
+ .arg(getRemoteEndpointAddressAsText());
+ WireData input_data(input_buf_.begin(), input_buf_.begin() + length);
+ next_request = postData(request, input_data);
+ }
+
+ // Start next read.
+ doRead(next_request);
+}
+
+TcpRequestPtr
+TcpConnection::postData(TcpRequestPtr request, WireData& input_data) {
+ size_t bytes_left = 0;
+ size_t length = input_data.size();
+ if (length) {
+ // Add data to the current request.
+ size_t bytes_used = request->postBuffer(static_cast<void*>(input_data.data()), length);
+ // Remove bytes used.
+ bytes_left = length - bytes_used;
+ input_data.erase(input_data.begin(), input_data.begin() + length);
+ }
+
+ if (request->needData()) {
+ // Current request is incomplete and we're out of data
+ // return the incomplete request and we'll read again.
+ return (request);
+ }
+
+ try {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_BASIC,
+ TCP_CLIENT_REQUEST_RECEIVED)
+ .arg(getRemoteEndpointAddressAsText());
+
+ // Request complete, stop the timer.
+ idle_timer_.cancel();
+
+ // Process the completed request.
+ requestReceived(request);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(tcp_logger, TCP_REQUEST_RECEIVED_FAILED)
+ .arg(getRemoteEndpointAddressAsText())
+ .arg(ex.what());
+ }
+
+ // Create a new, empty request.
+ request = createRequest();
+ if (bytes_left) {
+ // The input buffer spanned messages. Recurse to post the remainder to the
+ // new request.
+ request = postData(request, input_data);
+ }
+
+ return (request);
+}
+
+void
+TcpConnection::socketWriteCallback(TcpResponsePtr response,
+ boost::system::error_code ec, size_t length) {
+ if (ec) {
+ // IO service has been stopped and the connection is probably
+ // going to be shutting down.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ // EWOULDBLOCK and EAGAIN are special cases. Everything else is
+ // treated as fatal error.
+ } else if ((ec.value() != boost::asio::error::try_again) &&
+ (ec.value() != boost::asio::error::would_block)) {
+ // The connection is dead and there can't be a pending write as
+ // they are in sequence.
+ TcpConnection::stopThisConnection();
+ return;
+
+ // We got EWOULDBLOCK or EAGAIN which indicate that we may be able to
+ // read something from the socket on the next attempt.
+ } else {
+ doWrite(response);
+ }
+ }
+
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA, TCP_DATA_SENT)
+ .arg(length)
+ .arg(getRemoteEndpointAddressAsText());
+
+ // Since each response has its own wire data, it is not really
+ // possible that the number of bytes written is larger than the size
+ // of the buffer. But, let's be safe and set the length to the size
+ // of the buffer if that unexpected condition occurs.
+ if (length > response->getWireDataSize()) {
+ length = response->getWireDataSize();
+ }
+
+ // Eat the 'length' number of bytes from the output buffer and only
+ // leave the part of the response that hasn't been sent.
+ response->consumeWireData(length);
+
+ // Schedule the write of the unsent data.
+ doWrite(response);
+}
+
+void
+TcpConnection::setupIdleTimer() {
+ idle_timer_.setup(std::bind(&TcpConnection::idleTimeoutCallback, this),
+ idle_timeout_, IntervalTimer::ONE_SHOT);
+}
+
+void
+TcpConnection::idleTimeoutCallback() {
+ LOG_DEBUG(tcp_logger, isc::log::DBGLVL_TRACE_DETAIL,
+ TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED)
+ .arg(getRemoteEndpointAddressAsText());
+ // In theory we should shutdown first and stop/close after but
+ // it is better to put the connection management responsibility
+ // on the client... so simply drop idle connections.
+ stopThisConnection();
+}
+
+std::string
+TcpConnection::getRemoteEndpointAddressAsText() const {
+ if (remote_endpoint_ != NO_ENDPOINT()) {
+ return (remote_endpoint_.address().to_string());
+ }
+
+ return ("(unknown address)");
+}
+
+void
+TcpConnection::setReadMax(const size_t read_max) {
+ if (!read_max) {
+ isc_throw(BadValue, "TcpConnection read_max must be > 0");
+ }
+
+ read_max_ = read_max;
+ input_buf_.resize(read_max);
+}
+
+} // end of namespace isc::tcp
+} // end of namespace isc
diff --git a/src/lib/tcp/tcp_connection.h b/src/lib/tcp/tcp_connection.h
new file mode 100644
index 0000000..8b71d07
--- /dev/null
+++ b/src/lib/tcp/tcp_connection.h
@@ -0,0 +1,469 @@
+// Copyright (C) 2022 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 TCP_CONNECTION_H
+#define TCP_CONNECTION_H
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <tcp/tcp_connection_acceptor.h>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/system/error_code.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <array>
+#include <functional>
+#include <string>
+#include <iostream>
+
+#include <mutex>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Defines a data structure for storing raw bytes of data on the wire.
+typedef std::vector<uint8_t> WireData;
+typedef boost::shared_ptr<WireData> WireDataPtr;
+
+/// @brief Base class for TCP messages.
+class TcpMessage {
+public:
+ /// @brief Destructor
+ virtual ~TcpMessage(){
+ };
+
+ /// @brief Returns pointer to the first byte of the wire data.
+ /// @throw InvalidOperation if wire data is empty (i.e. getWireDataSize() == 0).
+ /// @return Constant raw pointer to the data.
+ const uint8_t* getWireData() const {
+ if (wire_data_.empty()) {
+ isc_throw(InvalidOperation, "TcpMessage::getWireData() - cannot access empty wire data");
+ }
+
+ return (wire_data_.data());
+ }
+
+ /// @brief Returns current size of the wire data.
+ size_t getWireDataSize() const {
+ return (wire_data_.size());
+ }
+
+protected:
+ /// @brief Buffer used for data in wire format data.
+ WireData wire_data_;
+};
+
+/// @brief Abstract class used to receive an inbound message.
+class TcpRequest : public TcpMessage {
+public:
+ /// @brief Destructor
+ virtual ~TcpRequest(){};
+
+ /// @brief Adds data to an incomplete request
+ ///
+ /// @param buf A pointer to the buffer holding the data.
+ /// @param nbytes Size of the data within the buffer.
+ /// @return number of bytes posted (consumed)
+ virtual size_t postBuffer(const void* buf, const size_t nbytes) = 0;
+
+ /// @brief Returns true if the request is incomplete.
+ ///
+ /// @return true if the request is incomplete.
+ virtual bool needData() const = 0;
+
+ /// @brief Returns request contents formatted for log output
+ ///
+ /// @param limit Maximum length of the buffer to be output. If the limit is 0,
+ /// the length of the output is unlimited.
+ /// @return Textual representation of the input buffer.
+ virtual std::string logFormatRequest(const size_t limit = 0) const = 0;
+
+ /// @brief Unpacks wire data once the message has been completely received.
+ virtual void unpack() = 0;
+
+private:
+
+ /// @brief Exception safe wrapper around logForamteRequest
+ ///
+ /// @param limit Maximum length of the buffer to be output. If the limit is 0,
+ /// the length of the output is unlimited.
+ /// @return Textual representation of the input buffer.
+ std::string logFormatRequestSafe(const size_t limit = 0) const;
+};
+
+/// @brief Defines a smart pointer to a TcpRequest.
+typedef boost::shared_ptr<TcpRequest> TcpRequestPtr;
+
+/// @brief Abstract class used to create and send an outbound response.
+class TcpResponse : public TcpMessage {
+public:
+ /// @brief Constructor
+ TcpResponse()
+ : send_in_progress_(false) {};
+
+ /// @brief Destructor
+ virtual ~TcpResponse() {};
+
+ /// @brief Checks if the output buffer contains some data to be
+ /// sent.
+ ///
+ /// @return true if the output buffer contains data to be sent,
+ /// false otherwise.
+ bool wireDataAvail() const {
+ return (!wire_data_.empty());
+ }
+
+ /// @brief Prepares the wire data content for writing.
+ virtual void pack() = 0;
+
+ /// @brief Erases n bytes from the beginning of the wire data.
+ ///
+ /// @param length Number of bytes to be erased.
+ virtual void consumeWireData(const size_t length);
+
+ bool sendInProgress() {
+ return (send_in_progress_);
+ }
+
+private:
+ /// @brief Returns true once wire data consumption has begun.
+ bool send_in_progress_;
+};
+
+typedef boost::shared_ptr<TcpResponse> TcpResponsePtr;
+
+/// @brief Generic error reported within @ref TcpConnection class.
+class TcpConnectionError : public Exception {
+public:
+ TcpConnectionError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Forward declaration to the @ref TcpConnectionPool.
+///
+/// This declaration is needed because we don't include the header file
+/// declaring @ref TcpConnectionPool to avoid circular inclusion.
+class TcpConnectionPool;
+
+/// @brief Type of the callback for filtering new connections by ip address.
+typedef std::function<bool(const boost::asio::ip::tcp::endpoint&)> TcpConnectionFilterCallback;
+
+/// @brief Accepts and handles a single TCP connection.
+class TcpConnection : public boost::enable_shared_from_this<TcpConnection> {
+private:
+
+ /// @brief Type of the function implementing a callback invoked by the
+ /// @c SocketCallback functor.
+ typedef std::function<void(boost::system::error_code ec, size_t length)>
+ SocketCallbackFunction;
+
+ /// @brief Functor associated with the socket object.
+ ///
+ /// This functor calls a callback function specified in the constructor.
+ class SocketCallback {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param socket_callback Callback to be invoked by the functor upon
+ /// an event associated with the socket.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Operator called when event associated with a socket occurs.
+ ///
+ /// This operator returns immediately when received error code is
+ /// @c boost::system::error_code is equal to
+ /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+ /// invoked.
+ ///
+ /// @param ec Error code.
+ /// @param length Data length.
+ void operator()(boost::system::error_code ec, size_t length = 0);
+
+ private:
+ /// @brief Supplied callback.
+ SocketCallbackFunction callback_;
+ };
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service IO service to be used by the connection.
+ /// @param acceptor Pointer to the TCP acceptor object used to listen for
+ /// new TCP connections.
+ /// @param tls_context TLS context.
+ /// @param connection_pool Connection pool in which this connection is
+ /// stored.
+ /// @param acceptor_callback Callback invoked when new connection is accepted.
+ /// @param connection_filter Callback invoked prior to handshake which can be
+ /// used to qualify and reject connections
+ /// @param idle_timeout Timeout after which a TCP connection is
+ /// closed by the server.
+ /// @param read_max maximum size of a single socket read. Defaults to 32K.
+ TcpConnection(asiolink::IOService& io_service,
+ const TcpConnectionAcceptorPtr& acceptor,
+ const asiolink::TlsContextPtr& tls_context,
+ TcpConnectionPool& connection_pool,
+ const TcpConnectionAcceptorCallback& acceptor_callback,
+ const TcpConnectionFilterCallback& connection_filter,
+ const long idle_timeout,
+ const size_t read_max = 32768);
+
+ /// @brief Destructor.
+ ///
+ /// Closes current connection.
+ virtual ~TcpConnection();
+
+ /// @brief Asynchronously accepts new connection.
+ ///
+ /// When the connection is established successfully, the timeout timer is
+ /// setup and the asynchronous handshake with client is performed.
+ void asyncAccept();
+
+ /// @brief Shutdown the socket.
+ virtual void shutdown();
+
+ /// @brief Closes the socket.
+ virtual void close();
+
+ /// @brief Asynchronously performs TLS handshake.
+ ///
+ /// When the handshake is performed successfully or skipped because TLS
+ /// was not enabled, the asynchronous read from the socket is started.
+ void doHandshake();
+
+ /// @brief Starts asynchronous read from the socket.
+ ///
+ /// The data received over the socket are supplied to the TCP parser until
+ /// the parser signals that the entire request has been received or until
+ /// the parser signals an error. In the former case the server creates an
+ /// TCP response using supplied response creator object.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param request Pointer to the request for which the read
+ /// operation should be performed. It defaults to null pointer which
+ /// indicates that this function should create new request.
+ void doRead(TcpRequestPtr request = TcpRequestPtr());
+
+ /// @brief Appends newly received raw data to the given request.
+ ///
+ /// The input data is passed into the current request's postBuffer method.
+ /// If the request is still incomplete, we return it and wait for more
+ /// data to post. Otherwise, the request is complete and it is passed into
+ /// @ref TcpConnection::requestReceived() to be processed. Upon return from
+ /// that, a new request is created and returned to be used for the next
+ /// read cycle.
+ ///
+ /// @param request request to which data should be posted.
+ /// @param input_data raw data to post.
+ ///
+ /// @return Pointer to the request to use for the next read.
+ TcpRequestPtr postData(TcpRequestPtr request, WireData& input_data);
+
+ /// @brief Processes a request once it has been completely received.
+ ///
+ /// This function is called by @c postData() if the post results
+ /// in a completion (i.e. request's needData() returns false).
+ virtual void requestReceived(TcpRequestPtr request) = 0;
+
+ /// @brief Creates a new, empty request.
+ ///
+ /// This function is called by @c postData(), following the completion
+ /// of the current request, to create a new request for accepting the
+ /// next data read.
+ ///
+ /// @return Pointer to the new request.
+ virtual TcpRequestPtr createRequest() = 0;
+
+ /// @brief Fetches the maximum number of bytes read during single socket
+ /// read.
+ /// @return Maximum number of bytes to read.
+ size_t getReadMax() const {
+ return (read_max_);
+ }
+
+ /// @brief Sets the maximum number of bytes read during single socket read.
+ ///
+ /// @param read_max maximum number of bytes to read.
+ /// @throw BadValue if the parameter is not greater than zero.
+ void setReadMax(const size_t read_max);
+
+ /// @brief Determines behavior after a response has been sent.
+ ///
+ /// @param response Pointer to the response sent.
+ /// @return True if the idle timer should be started.
+ virtual bool responseSent(TcpResponsePtr response) = 0;
+
+ /// @brief Returns an empty end point.
+ ///
+ /// @return an uninitialized endpoint.
+ static const boost::asio::ip::tcp::endpoint& NO_ENDPOINT() {
+ static boost::asio::ip::tcp::endpoint endpoint;
+ return (endpoint);
+ }
+
+ /// @brief Fetches the remote endpoint for the connection's socket.
+ ///
+ /// @return A reference to the endpoint if the socket is open, otherwise
+ /// NO_ENDPOINT.
+ const boost::asio::ip::tcp::endpoint getRemoteEndpoint() const {
+ return (remote_endpoint_);
+ }
+
+protected:
+
+ /// @brief Starts asynchronous write to the socket.
+ ///
+ /// The @c output_buf_ must contain the data to be sent.
+ ///
+ /// In case of error the connection is stopped.
+ ///
+ /// @param response Pointer to the response to write
+ /// operation should be performed.
+ void doWrite(TcpResponsePtr response);
+
+ /// @brief Sends TCP response asynchronously.
+ ///
+ /// Internally it calls @ref TcpConnection::doWrite to send the data.
+ ///
+ /// @param response Pointer to the TCP response to be sent.
+ void asyncSendResponse(TcpResponsePtr response);
+
+ /// @brief Local callback invoked when new connection is accepted.
+ ///
+ /// It invokes external (supplied via constructor) acceptor callback. If
+ /// the acceptor is not opened it returns immediately. If the connection
+ /// is accepted successfully the @ref TcpConnection::doRead or
+ /// @ref TcpConnection::doHandshake is called.
+ ///
+ /// @param ec Error code.
+ void acceptorCallback(const boost::system::error_code& ec);
+
+ /// @brief Local callback invoked when TLS handshake is performed.
+ ///
+ /// If the handshake is performed successfully the @ref
+ /// TcpConnection::doRead is called.
+ ///
+ /// @param ec Error code.
+ void handshakeCallback(const boost::system::error_code& ec);
+
+ /// @brief Callback invoked when new data is received over the socket.
+ ///
+ /// This callback supplies the data to the TCP parser and continues
+ /// parsing. When the parser signals end of the TCP request the callback
+ /// prepares a response and starts asynchronous send over the socket.
+ ///
+ /// @param request Pointer to the request for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the received data.
+ void socketReadCallback(TcpRequestPtr request,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when data is sent over the socket.
+ ///
+ /// @param request Pointer to the request for which the callback
+ /// is invoked.
+ /// @param ec Error code.
+ /// @param length Length of the data sent.
+ virtual void socketWriteCallback(TcpResponsePtr request,
+ boost::system::error_code ec,
+ size_t length);
+
+ /// @brief Callback invoked when TLS shutdown is performed.
+ ///
+ /// The TLS socket is unconditionally closed but the callback is called
+ /// only when the peer has answered so the connection should be
+ /// explicitly closed in all cases, i.e. do not rely on this handler.
+ ///
+ /// @param ec Error code (ignored).
+ void shutdownCallback(const boost::system::error_code& ec);
+
+ /// @brief Reset timer for detecting idle timeout in connections.
+ void setupIdleTimer();
+
+ /// @brief Callback invoked when the client has been idle.
+ void idleTimeoutCallback();
+
+ /// @brief Shuts down current connection.
+ ///
+ /// Copied from the next method @ref stopThisConnection
+ virtual void shutdownConnection();
+
+ /// @brief Stops current connection.
+ virtual void stopThisConnection();
+
+ /// @brief returns remote address in textual form
+ std::string getRemoteEndpointAddressAsText() const;
+
+ /// @brief Returns pointer to the first byte of the input buffer.
+ ///
+ /// @throw InvalidOperation if called when the buffer is empty.
+ unsigned char* getInputBufData() {
+ if (input_buf_.empty()) {
+ isc_throw(InvalidOperation, "TcpConnection::getInputBufData() - cannot access empty buffer");
+ }
+
+ return (input_buf_.data());
+ }
+
+ /// @brief Returns input buffer size.
+ size_t getInputBufSize() const {
+ return (input_buf_.size());
+ }
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Timeout after which the a TCP connection is shut
+ /// down by the server.
+ long idle_timeout_;
+
+ /// @brief Timer used to detect idle Timeout.
+ asiolink::IntervalTimer idle_timer_;
+
+ /// @brief TCP socket used by this connection.
+ std::unique_ptr<asiolink::TCPSocket<SocketCallback> > tcp_socket_;
+
+ /// @brief TLS socket used by this connection.
+ std::unique_ptr<asiolink::TLSSocket<SocketCallback> > tls_socket_;
+
+ /// @brief Pointer to the TCP acceptor used to accept new connections.
+ TcpConnectionAcceptorPtr acceptor_;
+
+ /// @brief Connection pool holding this connection.
+ TcpConnectionPool& connection_pool_;
+
+ /// @brief External TCP acceptor callback.
+ TcpConnectionAcceptorCallback acceptor_callback_;
+
+ /// @brief External callback for filtering connections by IP address.
+ TcpConnectionFilterCallback connection_filter_;
+
+ /// @brief Maximum bytes to read in a single socket read.
+ size_t read_max_;
+
+ /// @brief Buffer for a single socket read.
+ WireData input_buf_;
+
+ /// @brief Remote endpoint.
+ boost::asio::ip::tcp::endpoint remote_endpoint_;
+};
+
+/// @brief Pointer to the @ref TcpConnection.
+typedef boost::shared_ptr<TcpConnection> TcpConnectionPtr;
+
+} // end of namespace isc::tcp
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/tcp/tcp_connection_acceptor.h b/src/lib/tcp/tcp_connection_acceptor.h
new file mode 100644
index 0000000..028d999
--- /dev/null
+++ b/src/lib/tcp/tcp_connection_acceptor.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 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 TCP_CONNECTION_ACCEPTOR_H
+#define TCP_CONNECTION_ACCEPTOR_H
+
+#include <asiolink/tcp_acceptor.h>
+#include <asiolink/tls_acceptor.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/system/system_error.hpp>
+#include <functional>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Type of the callback for the TCP acceptor used in this library.
+typedef std::function<void(const boost::system::error_code&)> TcpConnectionAcceptorCallback;
+
+/// @brief Type of the TCP acceptor used in this library.
+typedef asiolink::TCPAcceptor<TcpConnectionAcceptorCallback> TcpConnectionAcceptor;
+
+/// @brief Type of shared pointer to TCP acceptors.
+typedef boost::shared_ptr<TcpConnectionAcceptor> TcpConnectionAcceptorPtr;
+
+/// @brief Type of the TLS acceptor used in this library.
+typedef asiolink::TLSAcceptor<TcpConnectionAcceptorCallback> TlsConnectionAcceptor;
+
+/// @brief Type of shared pointer to TLS acceptors.
+typedef boost::shared_ptr<TlsConnectionAcceptor> TlsConnectionAcceptorPtr;
+
+} // end of namespace isc::tcp
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/tcp/tcp_connection_pool.cc b/src/lib/tcp/tcp_connection_pool.cc
new file mode 100644
index 0000000..d5fe610
--- /dev/null
+++ b/src/lib/tcp/tcp_connection_pool.cc
@@ -0,0 +1,127 @@
+// Copyright (C) 2022 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 <asiolink/asio_wrapper.h>
+#include <tcp/tcp_connection_pool.h>
+#include <util/multi_threading_mgr.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace tcp {
+
+std::atomic<uint64_t>
+TcpConnectionPool::started_counter_(0);
+
+std::atomic<uint64_t>
+TcpConnectionPool::stopped_counter_(0);
+
+std::atomic<uint64_t>
+TcpConnectionPool::rejected_counter_(0);
+
+void
+TcpConnectionPool::start(const TcpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ connections_.insert(connections_.end(), connection);
+ started_counter_ += 1;
+ } else {
+ connections_.insert(connections_.end(), connection);
+ started_counter_ += 1;
+ }
+
+ connection->asyncAccept();
+}
+
+void
+TcpConnectionPool::stop(const TcpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ size_t before = connections_.size();
+ connections_.remove(connection);
+ size_t after = connections_.size();
+ stopped_counter_ += before - after;
+ } else {
+ size_t before = connections_.size();
+ connections_.remove(connection);
+ size_t after = connections_.size();
+ stopped_counter_ += before - after;
+ }
+
+ connection->close();
+}
+
+void
+TcpConnectionPool::shutdown(const TcpConnectionPtr& connection) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ size_t before = connections_.size();
+ connections_.remove(connection);
+ size_t after = connections_.size();
+ stopped_counter_ += before - after;
+ } else {
+ size_t before = connections_.size();
+ connections_.remove(connection);
+ size_t after = connections_.size();
+ stopped_counter_ += before - after;
+ }
+
+ connection->shutdown();
+}
+
+void
+TcpConnectionPool::stopAll() {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ stopAllInternal();
+ } else {
+ stopAllInternal();
+ }
+}
+
+void
+TcpConnectionPool::stopAllInternal() {
+ for (auto connection = connections_.begin();
+ connection != connections_.end();
+ ++connection) {
+ (*connection)->close();
+ }
+
+ size_t cnt = connections_.size();
+ connections_.clear();
+ stopped_counter_ += cnt;
+}
+
+size_t
+TcpConnectionPool::usedByRemoteIp(const IOAddress& remote_ip,
+ size_t& total_connections) {
+ if (util::MultiThreadingMgr::instance().getMode()) {
+ std::lock_guard<std::mutex> lk(mutex_);
+ return (usedByRemoteIpInternal(remote_ip, total_connections));
+ } else {
+ return (usedByRemoteIpInternal(remote_ip, total_connections));
+ }
+}
+
+size_t
+TcpConnectionPool::usedByRemoteIpInternal(const IOAddress& remote_ip,
+ size_t& total_connections) {
+ total_connections = connections_.size();
+ size_t cnt = 0;
+ for (const auto& conn : connections_) {
+ const auto& ep = conn->getRemoteEndpoint();
+ if ((ep != TcpConnection::NO_ENDPOINT()) &&
+ (IOAddress(ep.address()) == remote_ip)) {
+ ++cnt;
+ }
+ }
+ return (cnt);
+}
+
+}
+}
diff --git a/src/lib/tcp/tcp_connection_pool.h b/src/lib/tcp/tcp_connection_pool.h
new file mode 100644
index 0000000..2bd8800
--- /dev/null
+++ b/src/lib/tcp/tcp_connection_pool.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2022 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 TCP_CONNECTION_POOL_H
+#define TCP_CONNECTION_POOL_H
+
+#include <tcp/tcp_connection.h>
+
+#include <list>
+#include <mutex>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Type of TCP connection pointer list.
+typedef std::list<TcpConnectionPtr> TcpConnectionList;
+
+/// @brief Pool of active TCP connections.
+///
+/// The TCP server is designed to handle many connections simultaneously.
+/// The communication between the client and the server may take long time
+/// and the server must be able to react on other events while the communication
+/// with the clients is in progress. Thus, the server must track active
+/// connections and gracefully close them when needed. An obvious case when the
+/// connections must be terminated by the server is when the shutdown signal
+/// is received.
+///
+/// This object is a simple container for the server connections which provides
+/// means to terminate them on request.
+class TcpConnectionPool {
+public:
+
+ /// @brief Start new connection.
+ ///
+ /// The connection is inserted to the pool and the
+ /// @ref TcpConnection::asyncAccept is invoked.
+ ///
+ /// @param connection Pointer to the new connection.
+ void start(const TcpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and shutdown it.
+ ///
+ /// Shutdown is specific to TLS and is a first part of graceful close
+ /// (note it is NOT the same as TCP shutdown system call).
+ ///
+ /// @note if the TLS connection stalls e.g. the peer does not try I/O
+ /// on it the connection has to be explicitly stopped.
+ ///
+ /// @param connection Pointer to the connection.
+ void shutdown(const TcpConnectionPtr& connection);
+
+ /// @brief Removes a connection from the pool and stops it.
+ ///
+ /// @param connection Pointer to the connection.
+ void stop(const TcpConnectionPtr& connection);
+
+ /// @brief Stops all connections and removes them from the pool.
+ ///
+ /// @note This function is not thread-safe so should be called
+ /// when the thread pool is stopped.
+ void stopAll();
+
+ /// @brief Returns the number of connections using a given remote IP address.
+ ///
+ /// Used to limit the number of connections when accepting a new one.
+ ///
+ /// @param remote_ip The remote IP address.
+ /// @param[out] total_connections Size of the connection pool.
+ /// @return The number of connections using a given remote IP address.
+ size_t usedByRemoteIp(const asiolink::IOAddress& remote_ip,
+ size_t& total_connections);
+
+ /// @brief Class/static started (i.e. added to pool) connection counter.
+ static std::atomic<uint64_t> started_counter_;
+
+ /// @brief Class/static stopped (i.e. removed from pool) connection counter.
+ static std::atomic<uint64_t> stopped_counter_;
+
+ /// @brief Class/static rejected (by the accept filter) connection counter.
+ static std::atomic<uint64_t> rejected_counter_;
+
+protected:
+
+ /// @brief Stops all connections and removes them from the pool.
+ ///
+ /// Must be called from with a thread-safe context.
+ void stopAllInternal();
+
+ /// @brief Returns the number of connections using a given remote IP address.
+ ///
+ /// Used to limit the number of connections when accepting a new one.
+ /// Must be called from with a thread-safe context.
+ ///
+ /// @param remote_ip The remote IP address.
+ /// @param[out] total_connections Size of the connection pool.
+ /// @return The number of connections using a given remote IP address.
+ size_t usedByRemoteIpInternal(const asiolink::IOAddress& remote_ip,
+ size_t& total_connections);
+
+ /// @brief Set of connections.
+ TcpConnectionList connections_;
+
+ /// @brief Mutex to protect the internal state.
+ std::mutex mutex_;
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/tcp/tcp_listener.cc b/src/lib/tcp/tcp_listener.cc
new file mode 100644
index 0000000..dd327ae
--- /dev/null
+++ b/src/lib/tcp/tcp_listener.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2022 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 <asiolink/asio_wrapper.h>
+#include <tcp/tcp_listener.h>
+
+using namespace isc::asiolink;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace tcp {
+
+TcpListener::TcpListener(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const IdleTimeout& idle_timeout,
+ const TcpConnectionFilterCallback& connection_filter)
+ : io_service_(io_service), tls_context_(tls_context), acceptor_(),
+ endpoint_(), connections_(), idle_timeout_(idle_timeout.value_),
+ connection_filter_(connection_filter) {
+ // Create the TCP or TLS acceptor.
+ if (!tls_context) {
+ acceptor_.reset(new TcpConnectionAcceptor(io_service));
+ } else {
+ acceptor_.reset(new TlsConnectionAcceptor(io_service));
+ }
+
+ // Try creating an endpoint. This may cause exceptions.
+ try {
+ endpoint_.reset(new TCPEndpoint(server_address, server_port));
+ } catch (...) {
+ isc_throw(TcpListenerError, "unable to create TCP endpoint for "
+ << server_address << ":" << server_port);
+ }
+
+ // Idle connection timeout is signed and must be greater than 0.
+ if (idle_timeout_ <= 0) {
+ isc_throw(TcpListenerError, "Invalid desired TCP idle connection"
+ " timeout " << idle_timeout_);
+ }
+}
+
+TcpListener::~TcpListener() {
+ stop();
+}
+
+const TCPEndpoint&
+TcpListener::getEndpoint() const {
+ return (*endpoint_);
+}
+
+void
+TcpListener::start() {
+ try {
+ acceptor_->open(*endpoint_);
+ acceptor_->setOption(TcpConnectionAcceptor::ReuseAddress(true));
+ acceptor_->bind(*endpoint_);
+ acceptor_->listen();
+
+ } catch (const boost::system::system_error& ex) {
+ stop();
+ isc_throw(TcpListenerError, "unable to setup TCP acceptor for "
+ "listening for incoming TCP clients: " << ex.what());
+ }
+
+ accept();
+}
+
+void
+TcpListener::stop() {
+ connections_.stopAll();
+ acceptor_->close();
+}
+
+void
+TcpListener::accept() {
+ TcpConnectionAcceptorCallback acceptor_callback =
+ std::bind(&TcpListener::acceptHandler, this, ph::_1);
+
+ TcpConnectionPtr conn = createConnection(acceptor_callback, connection_filter_);
+
+ // Add this new connection to the pool.
+ connections_.start(conn);
+}
+
+void
+TcpListener::acceptHandler(const boost::system::error_code&) {
+ // The new connection has arrived. Set the acceptor to continue
+ // accepting new connections.
+ accept();
+}
+
+TcpConnectionPtr
+TcpListener::createConnection(const TcpConnectionAcceptorCallback&,
+ const TcpConnectionFilterCallback&) {
+ isc_throw(NotImplemented, "TcpListener::createConnection:");
+}
+
+IOAddress
+TcpListener::getLocalAddress() const {
+ return (getEndpoint().getAddress());
+}
+
+uint16_t
+TcpListener::getLocalPort() const {
+ return (getEndpoint().getPort());
+}
+
+} // end of namespace isc::tcp
+} // end of namespace isc
diff --git a/src/lib/tcp/tcp_listener.h b/src/lib/tcp/tcp_listener.h
new file mode 100644
index 0000000..26f74c5
--- /dev/null
+++ b/src/lib/tcp/tcp_listener.h
@@ -0,0 +1,169 @@
+// Copyright (C) 2022 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 TCP_LISTENER_H
+#define TCP_LISTENER_H
+
+#include <asiolink/io_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+#include <tcp/tcp_connection_pool.h>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace tcp {
+
+/// @brief A generic error raised by the @ref TcpListener class.
+class TcpListenerError : public Exception {
+public:
+ TcpListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Implements a class that listens for, accepts, and manages
+/// TCP connections.
+class TcpListener {
+public:
+ /// @brief Idle connection timeout.
+ struct IdleTimeout {
+ /// @brief Constructor.
+ ///
+ /// @param value Connection idle timeout value in milliseconds.
+ explicit IdleTimeout(long value)
+ : value_(value) {
+ }
+ long value_; ///< Connection idle timeout value specified.
+ };
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new server endpoint using the specified IP
+ /// address and port. It also validates other specified parameters.
+ ///
+ /// This constructor does not start accepting new connections! To start
+ /// accepting connections run @ref TcpListener::start.
+ ///
+ /// @param io_service IO service to be used by the listener.
+ /// @param server_address Address on which the TCP service should run.
+ /// @param server_port Port number on which the TCP service should run.
+ /// @param tls_context TLS context.
+ /// @param idle_timeout Timeout after which an idle TCP connection is
+ /// @param connection_filter Callback invoked during connection acceptance
+ /// that can allow or deny connections based on the remote endpoint.
+ ///
+ /// @throw TcpListenerError when any of the specified parameters is
+ /// invalid.
+ TcpListener(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const IdleTimeout& idle_timeout,
+ const TcpConnectionFilterCallback& connection_filter = 0);
+
+ /// @brief Virtual destructor.
+ virtual ~TcpListener();
+
+ /// @brief Returns reference to the current listener endpoint.
+ const asiolink::TCPEndpoint& getEndpoint() const;
+
+ /// @brief Starts accepting new connections.
+ ///
+ /// This method starts accepting and handling new TCP connections on
+ /// the IP address and port number specified in the constructor.
+ ///
+ /// If the method is invoked successfully, it must not be invoked again
+ /// until @ref TcpListener::stop is called.
+ ///
+ /// @throw TcpListenerError if an error occurred.
+ void start();
+
+ /// @brief Stops all active connections and shuts down the service.
+ void stop();
+
+ /// @brief Returns local address on which server is listening.
+ asiolink::IOAddress getLocalAddress() const;
+
+ /// @brief Returns local port on which server is listening.
+ uint16_t getLocalPort() const;
+
+ /// @brief Returns the idle timeout (in milliseconds).
+ long getIdleTimeout() const {
+ return (idle_timeout_);
+ }
+
+ /// @brief Returns the number of connections using a given remote IP address.
+ ///
+ /// Used to limit the number of connections when accepting a new one.
+ ///
+ /// @param remote_ip The remote IP address.
+ /// @param[out] total_connections Size of the connection pool.
+ /// @return The number of connections using a given remote IP address.
+ size_t usedByRemoteIp(const asiolink::IOAddress& remote_ip,
+ size_t& total_connections) {
+ return (connections_.usedByRemoteIp(remote_ip, total_connections));
+ }
+
+protected:
+
+ /// @brief Creates @ref TcpConnection instance and adds it to the
+ /// pool of active connections.
+ ///
+ /// The next accepted connection will be handled by this instance.
+ void accept();
+
+ /// @brief Callback invoked when the new connection is accepted.
+ ///
+ /// It calls @c TcpListener::accept to create new @c TcpConnection
+ /// instance.
+ ///
+ /// @param ec Error code passed to the handler. This is currently ignored.
+ void acceptHandler(const boost::system::error_code& ec);
+
+ /// @brief Creates an instance of the @c TcpConnection.
+ ///
+ /// This method is virtual so as it can be overridden when customized
+ /// connections are to be used, e.g. in case of unit testing.
+ ///
+ /// @param acceptor_callback Callback invoked when new connection is accepted.
+ /// @param connection_filter Callback invoked during acceptance which may
+ /// allow of deny connections based on their remote address.
+ /// @return Pointer to the created connection.
+ virtual TcpConnectionPtr createConnection(
+ const TcpConnectionAcceptorCallback& acceptor_callback,
+ const TcpConnectionFilterCallback& connection_filter);
+
+ /// @brief Reference to the IO service.
+ asiolink::IOService& io_service_;
+
+ /// @brief TLS context.
+ asiolink::TlsContextPtr tls_context_;
+
+ /// @brief Acceptor instance.
+ TcpConnectionAcceptorPtr acceptor_;
+
+ /// @brief Pointer to the endpoint representing IP address and port on
+ /// which the service is running.
+ boost::scoped_ptr<asiolink::TCPEndpoint> endpoint_;
+
+ /// @brief Pool of active connections.
+ TcpConnectionPool connections_;
+
+ /// @brief Timeout after which idle connection is closed by
+ /// the server.
+ long idle_timeout_;
+
+ /// @brief Callback invoked during acceptance which may
+ /// reject connections.
+ TcpConnectionFilterCallback connection_filter_;
+};
+
+/// @brief Pointer to a TcpListener.
+typedef boost::shared_ptr<TcpListener> TcpListenerPtr;
+
+} // end of namespace isc::asiolink
+} // end of namespace isc
+
+#endif // TCP_LISTENER_H
diff --git a/src/lib/tcp/tcp_log.cc b/src/lib/tcp/tcp_log.cc
new file mode 100644
index 0000000..959ae0c
--- /dev/null
+++ b/src/lib/tcp/tcp_log.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2022 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 tcp://mozilla.org/MPL/2.0/.
+
+/// Defines the logger used by the libkea-tcp library.
+
+#include <config.h>
+
+#include <tcp/tcp_log.h>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Defines the logger used within libkea-tcp library.
+isc::log::Logger tcp_logger("tcp");
+
+} // namespace tcp
+} // namespace isc
+
diff --git a/src/lib/tcp/tcp_log.h b/src/lib/tcp/tcp_log.h
new file mode 100644
index 0000000..ec3dce3
--- /dev/null
+++ b/src/lib/tcp/tcp_log.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2022 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 tcp://mozilla.org/MPL/2.0/.
+
+#ifndef TCP_LOG_H
+#define TCP_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <tcp/tcp_messages.h>
+
+namespace isc {
+namespace tcp {
+
+/// Define the logger used within libkea-tcp library.
+extern isc::log::Logger tcp_logger;
+
+} // namespace tcp
+} // namespace isc
+
+#endif // TCP_LOG_H
diff --git a/src/lib/tcp/tcp_messages.cc b/src/lib/tcp/tcp_messages.cc
new file mode 100644
index 0000000..5dc70ea
--- /dev/null
+++ b/src/lib/tcp/tcp_messages.cc
@@ -0,0 +1,67 @@
+// File created from ../../../src/lib/tcp/tcp_messages.mes
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace tcp {
+
+extern const isc::log::MessageID MT_TCP_LISTENER_MGR_STARTED = "MT_TCP_LISTENER_MGR_STARTED";
+extern const isc::log::MessageID MT_TCP_LISTENER_MGR_STOPPED = "MT_TCP_LISTENER_MGR_STOPPED";
+extern const isc::log::MessageID MT_TCP_LISTENER_MGR_STOPPING = "MT_TCP_LISTENER_MGR_STOPPING";
+extern const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED = "TCP_CLIENT_REQUEST_RECEIVED";
+extern const isc::log::MessageID TCP_CONNECTION_CLOSE_CALLBACK_FAILED = "TCP_CONNECTION_CLOSE_CALLBACK_FAILED";
+extern const isc::log::MessageID TCP_CONNECTION_REJECTED_BY_FILTER = "TCP_CONNECTION_REJECTED_BY_FILTER";
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN = "TCP_CONNECTION_SHUTDOWN";
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN_FAILED = "TCP_CONNECTION_SHUTDOWN_FAILED";
+extern const isc::log::MessageID TCP_CONNECTION_STOP = "TCP_CONNECTION_STOP";
+extern const isc::log::MessageID TCP_CONNECTION_STOP_FAILED = "TCP_CONNECTION_STOP_FAILED";
+extern const isc::log::MessageID TCP_DATA_RECEIVED = "TCP_DATA_RECEIVED";
+extern const isc::log::MessageID TCP_DATA_SENT = "TCP_DATA_SENT";
+extern const isc::log::MessageID TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED = "TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED = "TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED";
+extern const isc::log::MessageID TCP_REQUEST_RECEIVED_FAILED = "TCP_REQUEST_RECEIVED_FAILED";
+extern const isc::log::MessageID TCP_REQUEST_RECEIVE_START = "TCP_REQUEST_RECEIVE_START";
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND = "TCP_SERVER_RESPONSE_SEND";
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND_DETAILS = "TCP_SERVER_RESPONSE_SEND_DETAILS";
+extern const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_FAILED = "TLS_CONNECTION_HANDSHAKE_FAILED";
+extern const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_START = "TLS_CONNECTION_HANDSHAKE_START";
+extern const isc::log::MessageID TLS_REQUEST_RECEIVE_START = "TLS_REQUEST_RECEIVE_START";
+extern const isc::log::MessageID TLS_SERVER_RESPONSE_SEND = "TLS_SERVER_RESPONSE_SEND";
+
+} // namespace tcp
+} // namespace isc
+
+namespace {
+
+const char* values[] = {
+ "MT_TCP_LISTENER_MGR_STARTED", "MtTcpListenerMgr started with %1 threads, listening on %2:%3, use TLS: %4",
+ "MT_TCP_LISTENER_MGR_STOPPED", "MtTcpListenerMgr for %1:%2 stopped.",
+ "MT_TCP_LISTENER_MGR_STOPPING", "Stopping MtTcpListenerMgr for %1:%2",
+ "TCP_CLIENT_REQUEST_RECEIVED", "received TCP request from %1",
+ "TCP_CONNECTION_CLOSE_CALLBACK_FAILED", "Connection close callback threw an exception",
+ "TCP_CONNECTION_REJECTED_BY_FILTER", "connection from %1 has been denied by the connection filter.",
+ "TCP_CONNECTION_SHUTDOWN", "shutting down TCP connection from %1",
+ "TCP_CONNECTION_SHUTDOWN_FAILED", "shutting down TCP connection failed",
+ "TCP_CONNECTION_STOP", "stopping TCP connection from %1",
+ "TCP_CONNECTION_STOP_FAILED", "stopping TCP connection failed",
+ "TCP_DATA_RECEIVED", "received %1 bytes from %2",
+ "TCP_DATA_SENT", "send %1 bytes to %2",
+ "TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED", "closing connection with %1 as a result of a timeout",
+ "TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED", "premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3",
+ "TCP_REQUEST_RECEIVED_FAILED", "An unexpected error occurred processing a request from %1, error: %2",
+ "TCP_REQUEST_RECEIVE_START", "start receiving request from %1 with timeout %2",
+ "TCP_SERVER_RESPONSE_SEND", "sending TCP response to %1",
+ "TCP_SERVER_RESPONSE_SEND_DETAILS", "detailed information about response sent to %1:\n%2",
+ "TLS_CONNECTION_HANDSHAKE_FAILED", "TLS handshake with %1 failed with %2",
+ "TLS_CONNECTION_HANDSHAKE_START", "start TLS handshake with %1 with timeout %2",
+ "TLS_REQUEST_RECEIVE_START", "start receiving request from %1 with timeout %2",
+ "TLS_SERVER_RESPONSE_SEND", "sending TLS response to %1",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/tcp/tcp_messages.h b/src/lib/tcp/tcp_messages.h
new file mode 100644
index 0000000..7dc458c
--- /dev/null
+++ b/src/lib/tcp/tcp_messages.h
@@ -0,0 +1,37 @@
+// File created from ../../../src/lib/tcp/tcp_messages.mes
+
+#ifndef TCP_MESSAGES_H
+#define TCP_MESSAGES_H
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace tcp {
+
+extern const isc::log::MessageID MT_TCP_LISTENER_MGR_STARTED;
+extern const isc::log::MessageID MT_TCP_LISTENER_MGR_STOPPED;
+extern const isc::log::MessageID MT_TCP_LISTENER_MGR_STOPPING;
+extern const isc::log::MessageID TCP_CLIENT_REQUEST_RECEIVED;
+extern const isc::log::MessageID TCP_CONNECTION_CLOSE_CALLBACK_FAILED;
+extern const isc::log::MessageID TCP_CONNECTION_REJECTED_BY_FILTER;
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN;
+extern const isc::log::MessageID TCP_CONNECTION_SHUTDOWN_FAILED;
+extern const isc::log::MessageID TCP_CONNECTION_STOP;
+extern const isc::log::MessageID TCP_CONNECTION_STOP_FAILED;
+extern const isc::log::MessageID TCP_DATA_RECEIVED;
+extern const isc::log::MessageID TCP_DATA_SENT;
+extern const isc::log::MessageID TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED;
+extern const isc::log::MessageID TCP_REQUEST_RECEIVED_FAILED;
+extern const isc::log::MessageID TCP_REQUEST_RECEIVE_START;
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND;
+extern const isc::log::MessageID TCP_SERVER_RESPONSE_SEND_DETAILS;
+extern const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_FAILED;
+extern const isc::log::MessageID TLS_CONNECTION_HANDSHAKE_START;
+extern const isc::log::MessageID TLS_REQUEST_RECEIVE_START;
+extern const isc::log::MessageID TLS_SERVER_RESPONSE_SEND;
+
+} // namespace tcp
+} // namespace isc
+
+#endif // TCP_MESSAGES_H
diff --git a/src/lib/tcp/tcp_messages.mes b/src/lib/tcp/tcp_messages.mes
new file mode 100644
index 0000000..3deb0a5
--- /dev/null
+++ b/src/lib/tcp/tcp_messages.mes
@@ -0,0 +1,122 @@
+# Copyright (C) 2022-2023 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/.
+
+$NAMESPACE isc::tcp
+
+% MT_TCP_LISTENER_MGR_STARTED MtTcpListenerMgr started with %1 threads, listening on %2:%3, use TLS: %4
+This debug messages is issued when an MtTcpListenerMgr has been started to
+accept connections. Arguments detail the number of threads that the listener
+is using, the address and port at which it is listening, and if TLS is used
+or not.
+
+% MT_TCP_LISTENER_MGR_STOPPED MtTcpListenerMgr for %1:%2 stopped.
+This debug messages is issued when the MtTcpListenerMgr, listening
+at the given address and port, has completed shutdown.
+
+% MT_TCP_LISTENER_MGR_STOPPING Stopping MtTcpListenerMgr for %1:%2
+This debug messages is issued when the MtTcpListenerMgr, listening
+at the given address and port, has begun to shutdown.
+
+% TCP_CLIENT_REQUEST_RECEIVED received TCP request from %1
+This debug message is issued when the server finished receiving a TCP
+request from the remote endpoint. The address of the remote endpoint is
+specified as an argument.
+
+% TCP_CONNECTION_CLOSE_CALLBACK_FAILED Connection close callback threw an exception
+This is an error message emitted when the close connection callback
+registered on the connection failed unexpectedly. This is a programmatic
+error that should be submitted as a bug.
+
+% TCP_CONNECTION_REJECTED_BY_FILTER connection from %1 has been denied by the connection filter.
+This debug message is issued when the server's connection filter rejects
+a new connection based on the client's ip address.
+
+% TCP_CONNECTION_SHUTDOWN shutting down TCP connection from %1
+This debug message is issued when one of the TCP connections is shut down.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% TCP_CONNECTION_SHUTDOWN_FAILED shutting down TCP connection failed
+This error message is issued when an error occurred during shutting down
+a TCP connection with a client.
+
+% TCP_CONNECTION_STOP stopping TCP connection from %1
+This debug message is issued when one of the TCP connections is stopped.
+The connection can be stopped as a result of an error or after the
+successful message exchange with a client.
+
+% TCP_CONNECTION_STOP_FAILED stopping TCP connection failed
+This error message is issued when an error occurred during closing a
+TCP connection with a client.
+
+% TCP_DATA_RECEIVED received %1 bytes from %2
+This debug message is issued when the server receives a chunk of data from
+the remote endpoint. This may include the whole request or only a part
+of the request. The first argument specifies the amount of received data.
+The second argument specifies an address of the remote endpoint which
+produced the data.
+
+% TCP_DATA_SENT send %1 bytes to %2
+This debug message is issued when the server sends a chunk of data to
+the remote endpoint. This may include the whole response or only a part
+of the response. The first argument specifies the amount of sent data.
+The second argument specifies an address of the remote endpoint.
+
+% TCP_IDLE_CONNECTION_TIMEOUT_OCCURRED closing connection with %1 as a result of a timeout
+This debug message is issued when the TCP connection is being closed as a
+result of being idle.
+
+% TCP_PREMATURE_CONNECTION_TIMEOUT_OCCURRED premature connection timeout occurred: in transaction ? %1, transid: %2, current_transid: %3
+This warning message is issued when unexpected timeout occurred during the
+transaction. This is proven to occur when the system clock is moved manually
+or as a result of synchronization with a time server. Any ongoing transactions
+will be interrupted. New transactions should be conducted normally.
+
+% TCP_REQUEST_RECEIVED_FAILED An unexpected error occurred processing a request from %1, error: %2
+This error message is issued when an unexpected error occurred while the
+server attempted to process a received request. The first argument specifies
+the address of the remote endpoint. The second argument describes the nature
+error.
+
+% TCP_REQUEST_RECEIVE_START start receiving request from %1 with timeout %2
+This debug message is issued when the server starts receiving new request
+over the established connection. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% TCP_SERVER_RESPONSE_SEND sending TCP response to %1
+This debug message is issued when the server is starting to send a TCP
+response to a remote endpoint. The argument specifies an address of
+the remote endpoint.
+
+% TCP_SERVER_RESPONSE_SEND_DETAILS detailed information about response sent to %1:\n%2
+This debug message is issued right before the server sends a TCP response
+to the client. It includes detailed information about the response. The
+first argument specifies an address of the remote endpoint to which the
+response is being sent. The second argument provides a response in the
+textual form. The response is truncated by the logger if it is too large
+to be printed.
+
+% TLS_CONNECTION_HANDSHAKE_FAILED TLS handshake with %1 failed with %2
+This information message is issued when the TLS handshake failed at the
+server side. The client address and the error message are displayed.
+
+% TLS_CONNECTION_HANDSHAKE_START start TLS handshake with %1 with timeout %2
+This debug message is issued when the server starts the TLS handshake
+with the remote endpoint. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% TLS_REQUEST_RECEIVE_START start receiving request from %1 with timeout %2
+This debug message is issued when the server starts receiving new request
+over the established connection. The first argument specifies the address
+of the remote endpoint. The second argument specifies request timeout in
+seconds.
+
+% TLS_SERVER_RESPONSE_SEND sending TLS response to %1
+This debug message is issued when the server is starting to send a TLS
+response to a remote endpoint. The argument specifies an address of
+the remote endpoint.
diff --git a/src/lib/tcp/tcp_stream_msg.cc b/src/lib/tcp/tcp_stream_msg.cc
new file mode 100644
index 0000000..89fd5e8
--- /dev/null
+++ b/src/lib/tcp/tcp_stream_msg.cc
@@ -0,0 +1,127 @@
+// Copyright (C) 2022-2023 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 <tcp/tcp_stream_msg.h>
+#include <util/strutil.h>
+
+#include <iomanip>
+#include <sstream>
+#include <functional>
+
+namespace isc {
+namespace tcp {
+
+bool
+TcpStreamRequest::needData() const {
+ return (!expected_size_ || (wire_data_.size() < expected_size_));
+}
+
+size_t
+TcpStreamRequest::postBuffer(const void* buf, const size_t nbytes) {
+ if (!nbytes) {
+ // Nothing to do.
+ return (0);
+ }
+
+ const char* bufptr = static_cast<const char*>(buf);
+ size_t bytes_left = nbytes;
+ size_t wire_size = wire_data_.size();
+ size_t bytes_used = 0;
+ while (bytes_left) {
+ if (expected_size_) {
+ // We have the length, copy as much of what we still need as we can.
+ size_t need_bytes = expected_size_ - wire_size;
+ size_t copy_bytes = (need_bytes <= bytes_left ? need_bytes : bytes_left);
+ wire_data_.insert(wire_data_.end(), bufptr, bufptr + copy_bytes);
+ // bytes_left -= copy_bytes; // Since we break, we don't need to do this anymore.
+ bytes_used += copy_bytes;
+ break;
+ }
+
+ // Otherwise we don't know the length yet.
+ while (wire_size < 2 && bytes_left) {
+ wire_data_.push_back(*bufptr);
+ ++bufptr;
+ --bytes_left;
+ ++bytes_used;
+ ++wire_size;
+ }
+
+ // If we have enough to do it, calculate the expected length.
+ if (wire_size == 2 ) {
+ const uint8_t* cp = static_cast<const uint8_t*>(wire_data_.data());
+ uint16_t len = static_cast<unsigned int>(cp[0]) << 8;
+ len |= static_cast<unsigned int>(cp[1]);
+ expected_size_ = len + sizeof(len);
+ }
+ }
+
+ // Return how much we used.
+ return (bytes_used);
+}
+
+std::string
+TcpStreamRequest::logFormatRequest(const size_t limit) const {
+ std::stringstream output;
+ try {
+ size_t max = (limit && (limit < wire_data_.size()) ? limit : wire_data_.size());
+ output << "expected_size_: " << expected_size_ << ", current size: " << wire_data_.size()
+ << ", data: "
+ << isc::util::str::dumpAsHex(wire_data_.data(), max);
+ } catch (const std::exception& ex) {
+ std::stringstream output;
+ output << "logFormatRequest error: " << ex.what();
+ }
+
+ return (output.str());
+}
+
+void TcpStreamRequest::unpack() {
+ if (needData()) {
+ isc_throw(InvalidOperation, "Cannot unpack an incomplete request");
+ }
+
+ if (wire_data_.size() < sizeof(uint16_t)) {
+ isc_throw(Unexpected, "Request is malformed, too short");
+ }
+
+ request_ = std::vector<uint8_t>(wire_data_.begin() + sizeof(uint16_t), wire_data_.end());
+}
+
+void
+TcpStreamResponse::setResponseData(const uint8_t* data, size_t length) {
+ response_.assign(data, data + length);
+}
+
+void
+TcpStreamResponse::appendResponseData(const uint8_t* data, size_t length) {
+ response_.insert(response_.end(), data, data + length);
+}
+
+void
+TcpStreamResponse::setResponseData(const std::string& str) {
+ response_.assign(str.begin(), str.end());
+}
+
+void
+TcpStreamResponse::appendResponseData(const std::string& str) {
+ response_.insert(response_.end(), str.begin(), str.end());
+}
+
+void
+TcpStreamResponse::pack() {
+ wire_data_.clear();
+ // Prepend the length of the request.
+ uint16_t size = static_cast<uint16_t>(response_.size());
+ wire_data_.push_back(static_cast<uint8_t>((size & 0xff00U) >> 8));
+ wire_data_.push_back(static_cast<uint8_t>(size & 0x00ffU));
+ wire_data_.insert(wire_data_.end(), response_.begin(), response_.end());
+}
+
+} // end of namespace isc::tcp
+} // end of namespace isc
diff --git a/src/lib/tcp/tcp_stream_msg.h b/src/lib/tcp/tcp_stream_msg.h
new file mode 100644
index 0000000..9953749
--- /dev/null
+++ b/src/lib/tcp/tcp_stream_msg.h
@@ -0,0 +1,151 @@
+// Copyright (C) 2022 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 TCP_STREAM_MSG_H
+#define TCP_STREAM_MSG_H
+
+#include <tcp/tcp_connection.h>
+#include <boost/shared_ptr.hpp>
+
+#include <array>
+#include <functional>
+#include <string>
+
+namespace isc {
+namespace tcp {
+
+/// @brief Implement a simple length:data input stream message.
+///
+/// This class can be used to receive a single message from a TCP
+/// stream where the message consists of a 16-bit unsigned length (in
+/// network order), followed by that number of bytes of data.
+class TcpStreamRequest : public TcpRequest {
+public:
+ /// @brief Constructor.
+ TcpStreamRequest() : expected_size_(0) {
+ }
+
+ /// @brief Destructor
+ virtual ~TcpStreamRequest() {
+ }
+
+ /// @brief Adds data to an incomplete request
+ ///
+ /// @param buf A pointer to the buffer holding the data.
+ /// @param nbytes Size of the data within the buffer.
+ /// @return number of bytes posted (consumed)
+ virtual size_t postBuffer(const void* buf, const size_t nbytes);
+
+ /// @brief Returns true if the request is incomplete.
+ ///
+ /// @return true if the request is incomplete.
+ virtual bool needData() const;
+
+ /// @brief Returns request contents formatted for log output
+ ///
+ /// @param limit Maximum length of the buffer to be output. If the limit
+ /// is 0, the length of the output is unlimited.
+ /// @return Textual representation of the input buffer.
+ virtual std::string logFormatRequest(const size_t limit = 0) const;
+
+ /// @brief Unpacks the wire data into a string request.
+ virtual void unpack();
+
+ /// @brief Returns size of the unpacked request.
+ size_t getRequestSize() const {
+ return (request_.size());
+ }
+
+ /// @brief Returns pointer to the first byte of the unpacked request data.
+ ///
+ /// @return Constant raw pointer to the data.
+ /// @throw InvalidOperation if request data is empty (i.e. getRequestSize() == 0).
+ const uint8_t* getRequest() const {
+ if (request_.empty()) {
+ isc_throw(InvalidOperation, "TcpStreamRequest::getRequest()"
+ " - cannot access empty request");
+ }
+
+ return (request_.data());
+ }
+
+ /// @brief Fetches the unpacked request as a string.
+ ///
+ /// @return String containing the unpacked contents.
+ std::string getRequestString() const {
+ return (std::string(request_.begin(), request_.end()));
+ };
+
+protected:
+ /// @brief Unpacked request content
+ std::vector<uint8_t> request_;
+
+private:
+ /// @brief Expected size of the current message.
+ size_t expected_size_;
+};
+
+/// @brief Pointer to a TcpStreamRequest.
+typedef boost::shared_ptr<TcpStreamRequest> TcpStreamRequestPtr;
+
+/// @brief Implements a simple length:data output stream message.
+///
+/// This class can be used to send a single message on a TCP
+/// stream where the message consists of a 16-bit unsigned length (in
+/// network order), followed by that number of bytes of data.
+class TcpStreamResponse : public TcpResponse {
+public:
+ /// @brief Constructor.
+ TcpStreamResponse() {};
+
+ /// @brief Destructor.
+ virtual ~TcpStreamResponse() {};
+
+ /// @brief Replaces the response content .
+ ///
+ /// @param data New contents for the output buffer.
+ /// @param length Length of the contents to add.
+ virtual void setResponseData(const uint8_t* data, size_t length);
+
+ /// @brief Appends a data to the response content.
+ ///
+ /// @param data Data to append to the response.
+ /// @param length Length of the contents to add.
+ virtual void appendResponseData(const uint8_t* data, size_t length);
+
+ /// @brief Replaces the response content from a string.
+ ///
+ /// @param str New contents for the output buffer.
+ virtual void setResponseData(const std::string& str);
+
+ /// @brief Appends a string to the response content.
+ ///
+ /// @param str contents to add to the output buffer.
+ virtual void appendResponseData(const std::string& str);
+
+ /// @brief Packs the response content into wire data buffer.
+ virtual void pack();
+
+ /// @brief Fetches the unpacked response as a string.
+ ///
+ /// @return String containing the unpacked contents.
+ std::string getResponseString() const {
+ return (std::string(response_.begin(), response_.end()));
+ };
+
+private:
+ /// @brief Unpacked response data to send.
+ std::vector<uint8_t> response_;
+
+};
+
+/// @brief Pointer to a TcpStreamResponse.
+typedef boost::shared_ptr<TcpStreamResponse> TcpStreamResponsePtr;
+
+} // end of namespace isc::tcp
+} // end of namespace isc
+
+#endif // TCP_STREAM_MSG_H
diff --git a/src/lib/tcp/tests/Makefile.am b/src/lib/tcp/tests/Makefile.am
new file mode 100644
index 0000000..fd06ef4
--- /dev/null
+++ b/src/lib/tcp/tests/Makefile.am
@@ -0,0 +1,57 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+AM_CPPFLAGS += -DTEST_CA_DIR=\"$(abs_srcdir)/../../asiolink/testutils/ca\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda test-socket
+
+DISTCLEANFILES =
+
+noinst_SCRIPTS =
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += tcp_test_client.h tcp_test_listener.h
+run_unittests_SOURCES += tcp_listener_unittests.cc
+run_unittests_SOURCES += mt_tcp_listener_mgr_unittests.cc
+
+if HAVE_OPENSSL
+run_unittests_SOURCES += tls_listener_unittests.cc
+endif
+if HAVE_BOTAN_BOOST
+run_unittests_SOURCES += tls_listener_unittests.cc
+endif
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/tcp/libkea-tcp.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.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 += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(CRYPTO_LIBS)
+run_unittests_LDADD += $(GTEST_LDADD)
+
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+run_unittests_CXXFLAGS += -Wno-unused-parameter -Wno-unused-private-field
+endif
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/tcp/tests/Makefile.in b/src/lib/tcp/tests/Makefile.in
new file mode 100644
index 0000000..2713f70
--- /dev/null
+++ b/src/lib/tcp/tests/Makefile.in
@@ -0,0 +1,970 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__append_2 = tls_listener_unittests.cc
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__append_3 = tls_listener_unittests.cc
+@HAVE_GTEST_TRUE@@USE_GXX_TRUE@am__append_4 = -Wno-unused-parameter -Wno-unused-private-field
+noinst_PROGRAMS = $(am__EXEEXT_2)
+subdir = src/lib/tcp/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = run_unittests.cc tcp_test_client.h \
+ tcp_test_listener.h tcp_listener_unittests.cc \
+ mt_tcp_listener_mgr_unittests.cc tls_listener_unittests.cc
+@HAVE_GTEST_TRUE@@HAVE_OPENSSL_TRUE@am__objects_1 = run_unittests-tls_listener_unittests.$(OBJEXT)
+@HAVE_BOTAN_BOOST_TRUE@@HAVE_GTEST_TRUE@am__objects_2 = run_unittests-tls_listener_unittests.$(OBJEXT)
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-tcp_listener_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-mt_tcp_listener_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ $(am__objects_1) $(am__objects_2)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/tcp/libkea-tcp.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@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) \
+ $(run_unittests_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) \
+ $(LDFLAGS) -o $@
+SCRIPTS = $(noinst_SCRIPTS)
+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-mt_tcp_listener_mgr_unittests.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-tcp_listener_unittests.Po \
+ ./$(DEPDIR)/run_unittests-tls_listener_unittests.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)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+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; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES) \
+ -DTEST_CA_DIR=\"$(abs_srcdir)/../../asiolink/testutils/ca\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda test-socket
+DISTCLEANFILES =
+noinst_SCRIPTS =
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ tcp_test_client.h tcp_test_listener.h \
+@HAVE_GTEST_TRUE@ tcp_listener_unittests.cc \
+@HAVE_GTEST_TRUE@ mt_tcp_listener_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ $(am__append_2) $(am__append_3)
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/tcp/libkea-tcp.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/testutils/libasiolinktest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LIBS) $(GTEST_LDADD)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# KEA_CXXFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_CXXFLAGS = $(AM_CXXFLAGS) \
+@HAVE_GTEST_TRUE@ $(am__append_4)
+all: all-am
+
+.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/tcp/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/tcp/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-mt_tcp_listener_mgr_unittests.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-tcp_listener_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tls_listener_unittests.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-run_unittests.o: run_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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) $(run_unittests_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`
+
+run_unittests-tcp_listener_unittests.o: tcp_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_listener_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-tcp_listener_unittests.Tpo -c -o run_unittests-tcp_listener_unittests.o `test -f 'tcp_listener_unittests.cc' || echo '$(srcdir)/'`tcp_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_listener_unittests.Tpo $(DEPDIR)/run_unittests-tcp_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_listener_unittests.cc' object='run_unittests-tcp_listener_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_listener_unittests.o `test -f 'tcp_listener_unittests.cc' || echo '$(srcdir)/'`tcp_listener_unittests.cc
+
+run_unittests-tcp_listener_unittests.obj: tcp_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tcp_listener_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-tcp_listener_unittests.Tpo -c -o run_unittests-tcp_listener_unittests.obj `if test -f 'tcp_listener_unittests.cc'; then $(CYGPATH_W) 'tcp_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_listener_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tcp_listener_unittests.Tpo $(DEPDIR)/run_unittests-tcp_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tcp_listener_unittests.cc' object='run_unittests-tcp_listener_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tcp_listener_unittests.obj `if test -f 'tcp_listener_unittests.cc'; then $(CYGPATH_W) 'tcp_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tcp_listener_unittests.cc'; fi`
+
+run_unittests-mt_tcp_listener_mgr_unittests.o: mt_tcp_listener_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-mt_tcp_listener_mgr_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Tpo -c -o run_unittests-mt_tcp_listener_mgr_unittests.o `test -f 'mt_tcp_listener_mgr_unittests.cc' || echo '$(srcdir)/'`mt_tcp_listener_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Tpo $(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mt_tcp_listener_mgr_unittests.cc' object='run_unittests-mt_tcp_listener_mgr_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-mt_tcp_listener_mgr_unittests.o `test -f 'mt_tcp_listener_mgr_unittests.cc' || echo '$(srcdir)/'`mt_tcp_listener_mgr_unittests.cc
+
+run_unittests-mt_tcp_listener_mgr_unittests.obj: mt_tcp_listener_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-mt_tcp_listener_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Tpo -c -o run_unittests-mt_tcp_listener_mgr_unittests.obj `if test -f 'mt_tcp_listener_mgr_unittests.cc'; then $(CYGPATH_W) 'mt_tcp_listener_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/mt_tcp_listener_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Tpo $(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='mt_tcp_listener_mgr_unittests.cc' object='run_unittests-mt_tcp_listener_mgr_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-mt_tcp_listener_mgr_unittests.obj `if test -f 'mt_tcp_listener_mgr_unittests.cc'; then $(CYGPATH_W) 'mt_tcp_listener_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/mt_tcp_listener_mgr_unittests.cc'; fi`
+
+run_unittests-tls_listener_unittests.o: tls_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_listener_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-tls_listener_unittests.Tpo -c -o run_unittests-tls_listener_unittests.o `test -f 'tls_listener_unittests.cc' || echo '$(srcdir)/'`tls_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_listener_unittests.Tpo $(DEPDIR)/run_unittests-tls_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_listener_unittests.cc' object='run_unittests-tls_listener_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_listener_unittests.o `test -f 'tls_listener_unittests.cc' || echo '$(srcdir)/'`tls_listener_unittests.cc
+
+run_unittests-tls_listener_unittests.obj: tls_listener_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tls_listener_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-tls_listener_unittests.Tpo -c -o run_unittests-tls_listener_unittests.obj `if test -f 'tls_listener_unittests.cc'; then $(CYGPATH_W) 'tls_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_listener_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tls_listener_unittests.Tpo $(DEPDIR)/run_unittests-tls_listener_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tls_listener_unittests.cc' object='run_unittests-tls_listener_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) $(run_unittests_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tls_listener_unittests.obj `if test -f 'tls_listener_unittests.cc'; then $(CYGPATH_W) 'tls_listener_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/tls_listener_unittests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS) $(SCRIPTS)
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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 clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_listener_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_listener_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/run_unittests-mt_tcp_listener_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tcp_listener_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-tls_listener_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: 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 \
+ 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/tcp/tests/mt_tcp_listener_mgr_unittests.cc b/src/lib/tcp/tests/mt_tcp_listener_mgr_unittests.cc
new file mode 100644
index 0000000..ab561c6
--- /dev/null
+++ b/src/lib/tcp/tests/mt_tcp_listener_mgr_unittests.cc
@@ -0,0 +1,984 @@
+// Copyright (C) 2022-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/testutils/test_tls.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <tcp/mt_tcp_listener_mgr.h>
+#include <tcp_test_listener.h>
+#include <tcp_test_client.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <thread>
+#include <list>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::config;
+using namespace isc::data;
+using namespace boost::asio::ip;
+using namespace isc::tcp;
+using namespace isc::util;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which TCP service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief Port number to which TCP service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref MtTcpListenerMgr.
+class MtTcpListenerMgrTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts, and enables multi-threading mode.
+ MtTcpListenerMgrTest()
+ : mt_listener_mgr_(), io_service_(), test_timer_(io_service_),
+ run_io_service_timer_(io_service_), clients_(), num_threads_(),
+ num_clients_(), num_in_progress_(0), num_finished_(0), chunk_size_(0),
+ pause_cnt_(0), response_handler_(0) {
+ test_timer_.setup(std::bind(&MtTcpListenerMgrTest::timeoutHandler, this, true),
+ TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
+
+ // Enable multi-threading.
+ MultiThreadingMgr::instance().setMode(true);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes TCP clients and disables MT.
+ virtual ~MtTcpListenerMgrTest() {
+ // Wipe out the listener.
+ mt_listener_mgr_.reset();
+
+ // Destroy all remaining clients.
+ for (auto const& client : clients_) {
+ client->close();
+ }
+
+ // Disable multi-threading.
+ MultiThreadingMgr::instance().setMode(false);
+ }
+
+ /// @brief Replaces the test's listener with a new listener
+ ///
+ /// @param num_threads Number of threads the listener should use.
+ /// @param response_handler Response handler connections should use
+ void createMtTcpListenerMgr(size_t num_threads,
+ TcpTestConnection::ResponseHandler response_handler = 0) {
+ // Create a listener with prescribed number of threads.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_.reset(new MtTcpListenerMgr(
+ std::bind(&MtTcpListenerMgrTest::listenerFactory, this,
+ ph::_1, ph::_2, ph::_3, ph::_4, ph::_5, ph::_6),
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT, num_threads)));
+
+ ASSERT_TRUE(mt_listener_mgr_);
+ setResponseHandler(response_handler);
+ }
+
+ /// @brief Return the inner TcpTestListener's audit trail
+ AuditTrailPtr getAuditTrail() {
+ TcpListenerPtr l = mt_listener_mgr_->getTcpListener();
+ if (!l) {
+ isc_throw(Unexpected, "Test is broken? Listener is null?");
+ }
+
+ TcpTestListenerPtr listener = boost::dynamic_pointer_cast<TcpTestListener>(
+ mt_listener_mgr_->getTcpListener());
+ if (!listener) {
+ isc_throw(Unexpected, "Test is broken? Listener is not a TcpTestListener");
+ }
+
+ return (listener->audit_trail_);
+ }
+
+ /// @brief TcpListener factory for MtTcpListener to instantiate new listeners.
+ TcpListenerPtr listenerFactory(asiolink::IOService& io_service,
+ const asiolink::IOAddress& server_address,
+ const unsigned short server_port,
+ const asiolink::TlsContextPtr& tls_context,
+ const TcpListener::IdleTimeout& idle_timeout,
+ const TcpConnectionFilterCallback& connection_filter) {
+ TcpTestListenerPtr listener(new TcpTestListener(io_service,
+ server_address,
+ server_port,
+ tls_context,
+ idle_timeout,
+ connection_filter));
+ // Set the response handler the listener will pass into each connection.
+ listener->setResponseHandler(response_handler_);
+ return (listener);
+ }
+
+ /// @brief Callback function each client invokes when done.
+ ///
+ /// It stops the IO service
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void clientDone() {
+ io_service_.stop();
+ }
+
+ /// @brief Initiates a command via a new TCP client.
+ ///
+ /// This method creates a TcpTestClient instance, adds the
+ /// client to the list of clients, and starts a request.
+ /// The client will run on the main thread and be driven by
+ /// the test's IOService instance.
+ ///
+ /// @param request_str String containing the request
+ /// to be sent.
+ void startRequest(const std::string& request_str) {
+ // Instantiate the client.
+ TcpTestClientPtr client(new TcpTestClient(io_service_,
+ std::bind(&MtTcpListenerMgrTest::clientDone, this),
+ TlsContextPtr(),
+ SERVER_ADDRESS, SERVER_PORT));
+ // Add it to the list of clients.
+ clients_.push_back(client);
+
+ // Start the request. Note, nothing happens until the IOService runs.
+ client->startRequest(request_str);
+ }
+
+ /// @brief Initiates a "thread" command via a new TCP client.
+ ///
+ /// This method creates a TcpTestClient instance, adds the
+ /// client to the list of clients, and starts a request based
+ /// on the given command. The client will run on the main
+ /// thread and be driven by the test's IOService instance.
+ ///
+ /// The command has a single argument, "client-ptr". The function creates
+ /// the value for this argument from the pointer address of client instance
+ /// it creates. This argument should be echoed back in the response, along
+ /// with the thread-id of the MtTcpListener thread which handled the
+ /// command. The response body should look this:
+ ///
+ /// ```
+ /// [ { "arguments": { "client-ptr": "xxxxx", "thread-id": "zzzzz" }, "result": 0} ]
+ /// ```
+ void startThreadCommand(std::string request_str) {
+ // Create a new client.
+ TcpTestClientPtr client(new TcpTestClient(io_service_,
+ std::bind(&MtTcpListenerMgrTest::clientDone, this),
+ TlsContextPtr(),
+ SERVER_ADDRESS, SERVER_PORT));
+
+ // Construct the "thread" command post including the argument,
+ // "client-ptr", whose value is the stringified pointer to the
+ // newly created client.
+ std::stringstream request_body;
+ request_body << "{\"command\": \"thread\", \"arguments\": { \"client-ptr\": \""
+ << client << "\", \"request\": \"" << request_str << "\" } }";
+
+ // Add it to the list of clients.
+ clients_.push_back(client);
+
+ // Start the request. Note, nothing happens until the IOService runs.
+ ASSERT_NO_THROW_LOG(client->startRequest(request_body.str()));
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// We iterate over calls to asio::io_service.run(), until
+ /// all the clients have completed their requests. We do it this way
+ /// because the test clients stop the io_service when they're
+ /// through with a request.
+ ///
+ /// @param request_limit Desired number of requests the function should wait
+ /// to be processed before returning.
+ void runIOService(size_t request_limit = 0) {
+ if (!request_limit) {
+ request_limit = clients_.size();
+ }
+
+ // Loop until the clients are done, an error occurs, or the time runs out.
+ size_t num_done = 0;
+ while (num_done != request_limit) {
+ // Always call restart() before we call run();
+ io_service_.restart();
+
+ // Run until a client stops the service.
+ io_service_.run();
+
+ // If all the clients are done receiving, the test is done.
+ num_done = 0;
+ for (auto const& client : clients_) {
+ if (client->receiveDone()) {
+ ++num_done;
+ }
+ }
+ }
+ }
+
+ /// @brief Set the response handler use by each connection.
+ ///
+ /// Sets the response handler invoked by requestReceived.
+ ///
+ /// @param response_handler Handler function to invoke
+ void setResponseHandler(TcpTestConnection::ResponseHandler response_handler) {
+ response_handler_ = response_handler;
+ };
+
+ /// @brief Response handler for the 'thread' command.
+ ///
+ /// @param request JSON text containing thread command and arguments
+ /// which should contain one string element, "client-ptr", whose value is
+ /// the stringified pointer to the client that issued the command.
+ ///
+ /// @return Returns JSON text containing the response which should include
+ /// a string value 'thread-id': <thread id>
+ std::string synchronizedCommandHandler(const std::string& request) {
+ // If the number of in progress commands is less than the number
+ // of threads, then wait here until we're notified. Otherwise,
+ // notify everyone and finish. The idea is to force each thread
+ // to handle the same number of requests over the course of the
+ // test, making verification reliable.
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ ++num_in_progress_;
+ if (num_in_progress_ == chunk_size_) {
+ num_finished_ = 0;
+ cv_.notify_all();
+ } else {
+ bool ret = cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_in_progress_ == chunk_size_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to start work";
+ }
+ }
+ }
+
+ // Create the map of response arguments.
+ ElementPtr arguments = Element::createMap();
+
+ // Parse the command.
+ ConstElementPtr command = Element::fromJSON(request);
+ ConstElementPtr command_arguments;
+ std::string command_str = parseCommand(command_arguments, command);
+
+ // First we echo the client-ptr command argument.
+ ConstElementPtr client_ptr = command_arguments->get("client-ptr");
+ if (!client_ptr) {
+ return (createAnswerString(CONTROL_RESULT_ERROR, "missing client-ptr"));
+ }
+
+ arguments->set("client-ptr", client_ptr);
+
+ // Now we add the thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ arguments->set("thread-id", Element::create(ss.str()));
+ arguments->set("sign-off", Element::create("good bye"));
+
+ {
+ std::unique_lock<std::mutex> lck(mutex_);
+ num_finished_++;
+ if (num_finished_ == chunk_size_) {
+ // We're all done, notify the others and finish.
+ num_in_progress_ = 0;
+ cv_.notify_all();
+ } else {
+ // I'm done but others aren't wait here.
+ bool ret = cv_.wait_for(lck, std::chrono::seconds(10),
+ [&]() { return (num_finished_ == chunk_size_); });
+ if (!ret) {
+ ADD_FAILURE() << "clients failed to finish work";
+ }
+ }
+ }
+
+ EXPECT_THROW(mt_listener_mgr_->start(), InvalidOperation);
+ EXPECT_THROW(mt_listener_mgr_->pause(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(mt_listener_mgr_->resume(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(mt_listener_mgr_->stop(), MultiThreadingInvalidOperation);
+
+ // We're done, ship it!
+ std::string str = createAnswerString(CONTROL_RESULT_SUCCESS, arguments);
+ return (str);
+ }
+
+ /// @brief Create a response string of JSON
+ ///
+ /// @param status_code Indicates outcome of the command
+ /// @param arguments Element tree of response arguments
+ ///
+ /// @return JSON text containing the response
+ std::string createAnswerString(const int status_code, std::string text) {
+ ConstElementPtr answer = createAnswer(status_code, text);
+ std::stringstream os;
+ answer->toJSON(os);
+ return(os.str());
+ }
+
+ /// @brief Create a response string of JSON
+ ///
+ /// @param status_code Indicates outcome of the command
+ /// @param arguments Element tree of response arguments
+ ///
+ /// @return JSON text containing the response
+ std::string createAnswerString(const int status_code,
+ ConstElementPtr arguments) {
+ ConstElementPtr answer = createAnswer(status_code, arguments);
+ std::stringstream os;
+ answer->toJSON(os);
+ return(os.str());
+ }
+
+ /// @brief Simple response handler for the 'thread' command.
+ ///
+ /// @param command_name Command name, i.e. 'thread'.
+ /// @param command_arguments Command arguments should contain
+ /// one string element, "client-ptr", whose value is the stringified
+ /// pointer to the client that issued the command.
+ ///
+ /// @return Returns response with map of arguments containing
+ /// a string value 'thread-id': <thread id>
+ std::string simpleCommandHandler(const std::string& request) {
+ // Create the map of response arguments.
+ ElementPtr arguments = Element::createMap();
+
+ // Parse the command.
+ ConstElementPtr command = Element::fromJSON(request);
+ ConstElementPtr command_arguments;
+ std::string command_str = parseCommand(command_arguments, command);
+
+ // First we echo the client-ptr command argument.
+ ConstElementPtr client_ptr = command_arguments->get("client-ptr");
+ if (!client_ptr) {
+ return (createAnswerString(CONTROL_RESULT_ERROR, "missing client-ptr"));
+ }
+
+ arguments->set("client-ptr", client_ptr);
+
+ // Now we add the thread-id.
+ std::stringstream ss;
+ ss << std::this_thread::get_id();
+ arguments->set("thread-id", Element::create(ss.str()));
+ arguments->set("sign-off", Element::create("good bye"));
+
+ // We're done, ship it!
+ std::string str = createAnswerString(CONTROL_RESULT_SUCCESS, arguments);
+ return (str);
+ }
+
+ /// @brief Submits one or more thread commands to a MtTcpListener.
+ ///
+ /// This function command will create a MtTcpListener
+ /// with the given number of threads, initiates the given
+ /// number of clients, each requesting the "thread" command,
+ /// and then iteratively runs the test's IOService until all
+ /// the clients have received their responses or an error occurs.
+ ///
+ /// It requires that the number of clients, when greater than the
+ /// number of threads, be a multiple of the number of threads. The
+ /// thread command handler is structured in such a way as to ensure
+ /// (we hope) that each thread handles the same number of commands.
+ ///
+ /// @param num_threads - the number of threads the MtTcpListener
+ /// should use. Must be greater than 0.
+ /// @param num_clients - the number of clients that should issue the
+ /// thread command. Each client is used to carry out a single thread
+ /// command request. Must be greater than 0 and a multiple of num_threads
+ /// if it is greater than num_threads.
+ void threadListenAndRespond(size_t num_threads, size_t num_clients) {
+ // First we makes sure the parameter rules apply.
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_clients);
+ ASSERT_TRUE((num_clients < num_threads) || (num_clients % num_threads == 0));
+
+ num_threads_ = num_threads;
+ num_clients_ = num_clients;
+ chunk_size_ = num_threads_;
+ if (num_clients_ < chunk_size_) {
+ chunk_size_ = num_clients_;
+ }
+
+ // Create an MtTcpListenerMgr with prescribed number of threads.
+ createMtTcpListenerMgr(num_threads,
+ std::bind(&MtTcpListenerMgrTest::synchronizedCommandHandler,
+ this, ph::_1));
+
+ // Start it and verify it is running.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->start());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), num_threads);
+
+ // Maps the number of clients served by a given thread-id.
+ std::map<std::string, int> clients_per_thread;
+
+ // Initiate the prescribed number of command requests.
+ num_in_progress_ = 0;
+ while (clients_.size() < num_clients) {
+ ASSERT_NO_THROW_LOG(startThreadCommand("I am done"));
+ }
+
+ // Now we run the client-side IOService until all requests are done,
+ // errors occur or the test times out.
+ ASSERT_NO_FATAL_FAILURE(runIOService());
+
+ // Stop the listener and then verify it has stopped.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->stop());
+ ASSERT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+
+ // Iterate over the clients, checking their outcomes.
+ size_t total_responses = 0;
+ for (auto const& client : clients_) {
+ // Client should have completed its receive successfully.
+ ASSERT_TRUE(client->receiveDone());
+
+ // Now we walk the element tree to get the response data. It should look
+ // this:
+ //
+ // {
+ // "arguments": { "client-ptr": "xxxxx",
+ // "thread-id": "zzzzz" },
+ // "result": 0
+ // ]
+ //
+ // We expect 1 response.
+ auto responses = client->getResponses();
+ ASSERT_EQ(responses.size(), 1);
+
+ // First we turn it into an Element tree.
+ ConstElementPtr answer;
+ ASSERT_NO_THROW_LOG(answer = Element::fromJSON(responses.front()));
+
+ // Answer should be a map containing "arguments" and "results".
+ ASSERT_EQ(answer->getType(), Element::map);
+
+ // "result" should be 0.
+ ConstElementPtr result = answer->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result->getType(), Element::integer);
+ ASSERT_EQ(result->intValue(), 0);
+
+ // "arguments" is a map containing "client-ptr" and "thread-id".
+ ConstElementPtr arguments = answer->get("arguments");
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(arguments->getType(), Element::map);
+
+ // "client-ptr" is a string.
+ ConstElementPtr client_ptr = arguments->get("client-ptr");
+ ASSERT_TRUE(client_ptr);
+ ASSERT_EQ(client_ptr->getType(), Element::string);
+
+ // "thread-id" is a string.
+ ConstElementPtr thread_id = arguments->get("thread-id");
+ ASSERT_TRUE(thread_id);
+ ASSERT_EQ(thread_id->getType(), Element::string);
+ std::string thread_id_str = thread_id->stringValue();
+
+ // Make sure the response received was for this client.
+ std::stringstream ss;
+ ss << client;
+ ASSERT_EQ(client_ptr->stringValue(), ss.str());
+
+ // Bump the client count for the given thread-id.
+ auto it = clients_per_thread.find(thread_id_str);
+ if (it != clients_per_thread.end()) {
+ clients_per_thread[thread_id_str] = it->second + 1;
+ } else {
+ clients_per_thread[thread_id_str] = 1;
+ }
+ ++total_responses;
+ }
+
+ // We should have responses for all our clients.
+ EXPECT_EQ(total_responses, num_clients);
+
+ // Verify we have the expected number of entries in our map.
+ size_t expected_thread_count = (num_clients < num_threads ?
+ num_clients : num_threads);
+
+ ASSERT_EQ(clients_per_thread.size(), expected_thread_count);
+
+ // Each thread-id ought to have handled the same number of clients.
+ for (auto const& it : clients_per_thread) {
+ EXPECT_EQ(it.second, num_clients / clients_per_thread.size())
+ << "thread-id: " << it.first
+ << ", clients: " << it.second << std::endl;
+ }
+ }
+
+ /// @brief Pauses and resumes a MtTcpListener while it processes command
+ /// requests.
+ ///
+ /// This function command will create a MtTcpListenerMgr
+ /// with the given number of threads, initiates the given
+ /// number of clients, each requesting the "thread" command,
+ /// and then iteratively runs the test's IOService until all
+ /// the clients have received their responses or an error occurs.
+ /// It will pause and resume the listener at intervals governed
+ /// by the given number of pauses.
+ ///
+ /// @param num_threads - the number of threads the MtTcpListener
+ /// should use. Must be greater than 0.
+ /// @param num_clients - the number of clients that should issue the
+ /// thread command. Each client is used to carry out a single thread
+ /// command request. Must be greater than 0.
+ /// @param num_pauses Desired number of times the listener should be
+ /// paused during the test. Must be greater than 0.
+ void workPauseAndResume(size_t num_threads, size_t num_clients,
+ size_t num_pauses) {
+ // First we makes sure the parameter rules apply.
+ ASSERT_TRUE(num_threads);
+ ASSERT_TRUE(num_clients);
+ ASSERT_TRUE(num_pauses);
+ num_threads_ = num_threads;
+ num_clients_ = num_clients;
+
+ // Create an MtTcpListenerMgr with prescribed number of threads and the
+ // simple handler.
+ createMtTcpListenerMgr(num_threads,
+ std::bind(&MtTcpListenerMgrTest::simpleCommandHandler,
+ this, ph::_1));
+
+ ASSERT_TRUE(mt_listener_mgr_);
+
+ // Start it and verify it is running.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->start());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), num_threads);
+
+ // Initiate the prescribed number of command requests.
+ num_in_progress_ = 0;
+ while (clients_.size() < num_clients) {
+ ASSERT_NO_THROW_LOG(startThreadCommand("I am done"));
+ }
+
+ // Now we run the client-side IOService until all requests are done,
+ // errors occur or the test times out. We'll pause and resume the
+ // number of times given by num_pauses.
+ size_t num_done = 0;
+ size_t total_requests = clients_.size();
+ while (num_done < total_requests) {
+ // Calculate how many more requests to process before we pause again.
+ // We divide the number of outstanding requests by the number of pauses
+ // and stop after we've done at least that many more requests.
+ size_t request_limit = (pause_cnt_ < num_pauses ?
+ (num_done + ((total_requests - num_done) / num_pauses))
+ : total_requests);
+
+ // Run test IOService until we hit the limit.
+ runIOService(request_limit);
+
+ // If we've done all our pauses we should be through.
+ if (pause_cnt_ == num_pauses) {
+ break;
+ }
+
+ // Pause the client.
+ ASSERT_NO_THROW(mt_listener_mgr_->pause());
+ ASSERT_TRUE(mt_listener_mgr_->isPaused());
+ ++pause_cnt_;
+
+ // Check our progress.
+ num_done = 0;
+ for (auto const& client : clients_) {
+ if (client->receiveDone()) {
+ ++num_done;
+ }
+ }
+
+ // We should completed at least as many as our
+ // target limit.
+ ASSERT_GE(num_done, request_limit);
+
+ // Resume the listener.
+ ASSERT_NO_THROW(mt_listener_mgr_->resume());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ }
+
+ // Stop the listener and then verify it has stopped.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->stop());
+ ASSERT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+
+ // Iterate over the clients, checking their outcomes.
+ size_t total_responses = 0;
+ for (auto const& client : clients_) {
+ // Client should have completed its receive successfully.
+ ASSERT_TRUE(client->receiveDone());
+
+ // Now we walk the element tree to get the response data. It should look
+ // this:
+ //
+ // {
+ // "arguments": { "client-ptr": "xxxxx",
+ // "sign-off": "good bye",
+ // "thread-id": "zzzzz" },
+ // "result": 0
+ // }
+ //
+ // We expect one response.
+ auto responses = client->getResponses();
+ ASSERT_EQ(responses.size(), 1);
+
+ // First we turn it into an Element tree.
+ ConstElementPtr answer;
+ ASSERT_NO_THROW_LOG(answer = Element::fromJSON(responses.front()));
+
+ // Answer should be a map containing "arguments" and "results".
+ ASSERT_EQ(answer->getType(), Element::map);
+
+ // "result" should be 0.
+ ConstElementPtr result = answer->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(result->getType(), Element::integer);
+ ASSERT_EQ(result->intValue(), 0);
+
+ // "arguments" is a map containing "client-ptr" and "thread-id".
+ ConstElementPtr arguments = answer->get("arguments");
+ ASSERT_TRUE(arguments);
+ ASSERT_EQ(arguments->getType(), Element::map);
+
+ // "client-ptr" is a string.
+ ConstElementPtr client_ptr = arguments->get("client-ptr");
+ ASSERT_TRUE(client_ptr);
+ ASSERT_EQ(client_ptr->getType(), Element::string);
+
+ // "thread-id" is a string.
+ ConstElementPtr thread_id = arguments->get("thread-id");
+ ASSERT_TRUE(thread_id);
+ ASSERT_EQ(thread_id->getType(), Element::string);
+ std::string thread_id_str = thread_id->stringValue();
+
+ // Make sure the response received was for this client.
+ std::stringstream ss;
+ ss << client;
+ ASSERT_EQ(client_ptr->stringValue(), ss.str());
+
+ ++total_responses;
+ }
+
+ // We should have responses for all our clients.
+ EXPECT_EQ(total_responses, num_clients);
+
+ // We should have had the expected number of pauses.
+ if (!num_pauses) {
+ ASSERT_EQ(pause_cnt_, 0);
+ } else {
+ // We allow a range on pauses of +-1.
+ ASSERT_TRUE((num_pauses - 1) <= pause_cnt_ &&
+ (pause_cnt_ <= (num_pauses + 1)))
+ << " num_pauses: " << num_pauses
+ << ", pause_cnt_" << pause_cnt_;
+ }
+ }
+
+ /// @brief MtTcpListenerMgr instance under test.
+ MtTcpListenerMgrPtr mt_listener_mgr_;
+
+ /// @brief IO service used in drive the test and test clients.
+ IOService io_service_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TcpTestClientPtr> clients_;
+
+ /// @brief Number of threads the listener should use for the test.
+ size_t num_threads_;
+
+ /// @brief Number of client requests to make during the test.
+ size_t num_clients_;
+
+ /// @brief Number of requests currently in progress.
+ size_t num_in_progress_;
+
+ /// @brief Number of requests that have finished.
+ size_t num_finished_;
+
+ /// @brief Chunk size of requests that need to be processed in parallel.
+ ///
+ /// This can either be the number of threads (if the number of requests is
+ /// greater than the number of threads) or the number of requests (if the
+ /// number of threads is greater than the number of requests).
+ size_t chunk_size_;
+
+ /// @brief Mutex used to lock during thread coordination.
+ std::mutex mutex_;
+
+ /// @brief Condition variable used to coordinate threads.
+ std::condition_variable cv_;
+
+ /// @brief Number of times client has been paused during the test.
+ size_t pause_cnt_;
+
+ /// @brief Number of clients that have completed their assignment or
+ /// failed
+ size_t clients_done_;
+
+ /// @brief Response Handler passed down to each connection.
+ TcpTestConnection::ResponseHandler response_handler_;
+};
+
+/// Verifies the construction, starting, stopping, pausing, resuming,
+/// and destruction of MtTcpListener.
+TEST_F(MtTcpListenerMgrTest, basics) {
+ // Make sure multi-threading is off.
+ MultiThreadingMgr::instance().setMode(false);
+ IOAddress address(SERVER_ADDRESS);
+ uint16_t port = SERVER_PORT;
+
+ // Make sure we can create one.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_.reset(
+ new MtTcpListenerMgr(
+ std::bind(&MtTcpListenerMgrTest::listenerFactory, this,
+ ph::_1, ph::_2, ph::_3, ph::_4, ph::_5, ph::_6),
+ address, port)));
+
+ ASSERT_TRUE(mt_listener_mgr_);
+
+ // Verify the getters do what we expect.
+ EXPECT_EQ(mt_listener_mgr_->getAddress(), address);
+ EXPECT_EQ(mt_listener_mgr_->getPort(), port);
+ EXPECT_EQ(mt_listener_mgr_->getThreadPoolSize(), 1);
+ EXPECT_FALSE(mt_listener_mgr_->getTlsContext());
+
+ // It should not have an IOService, should not be listening and
+ // should have no threads.
+ ASSERT_FALSE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+
+ // Verify that we cannot start it when multi-threading is disabled.
+ ASSERT_FALSE(MultiThreadingMgr::instance().getMode());
+ ASSERT_THROW_MSG(mt_listener_mgr_->start(), InvalidOperation,
+ "MtTcpListenerMgr cannot be started"
+ " when multi-threading is disabled");
+
+ // It should still not be listening and have no threads.
+ EXPECT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+
+ // Enable multi-threading.
+ MultiThreadingMgr::instance().setMode(true);
+
+ // Make sure we can start it and it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->start());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 1);
+ ASSERT_TRUE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_FALSE(mt_listener_mgr_->getThreadIOService()->stopped());
+
+ // Trying to start it again should fail.
+ ASSERT_THROW_MSG(mt_listener_mgr_->start(), InvalidOperation,
+ "MtTcpListenerMgr already started!");
+
+ // Stop it and verify we're no longer listening.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->stop());
+ ASSERT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+ ASSERT_FALSE(mt_listener_mgr_->getThreadIOService());
+
+ // Make sure we can call stop again without problems.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->stop());
+
+ // We should be able to restart it.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->start());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 1);
+ ASSERT_TRUE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_FALSE(mt_listener_mgr_->getThreadIOService()->stopped());
+
+ // Destroying it should also stop it.
+ // If the test timeouts we know it didn't!
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_.reset());
+
+ // Verify we can construct with more than one thread.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_.reset(
+ new MtTcpListenerMgr(
+ std::bind(&MtTcpListenerMgrTest::listenerFactory, this,
+ ph::_1, ph::_2, ph::_3, ph::_4, ph::_5, ph::_6),
+ address, port, 4)));
+
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->start());
+ EXPECT_EQ(mt_listener_mgr_->getAddress(), address);
+ EXPECT_EQ(mt_listener_mgr_->getPort(), port);
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 4);
+ EXPECT_EQ(mt_listener_mgr_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ ASSERT_TRUE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_FALSE(mt_listener_mgr_->getThreadIOService()->stopped());
+
+ // Verify we can pause it. We should still be listening, threads intact,
+ // IOService stopped, state set to PAUSED.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->pause());
+ ASSERT_TRUE(mt_listener_mgr_->isPaused());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 4);
+ EXPECT_EQ(mt_listener_mgr_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_TRUE(mt_listener_mgr_->getThreadIOService()->stopped());
+
+ // Verify we can resume it.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->resume());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 4);
+ EXPECT_EQ(mt_listener_mgr_->getThreadPoolSize(), 4);
+ ASSERT_TRUE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_FALSE(mt_listener_mgr_->getThreadIOService()->stopped());
+
+ // Stop it and verify we're no longer listening.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->stop());
+ ASSERT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+ EXPECT_EQ(mt_listener_mgr_->getThreadPoolSize(), 4);
+ ASSERT_FALSE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_TRUE(mt_listener_mgr_->isStopped());
+}
+
+// Now we'll run some permutations of the number of listener threads
+// and the number of client requests.
+
+// One thread, one client.
+TEST_F(MtTcpListenerMgrTest, oneByOne) {
+ size_t num_threads = 1;
+ size_t num_clients = 1;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// One thread, four clients.
+TEST_F(MtTcpListenerMgrTest, oneByFour) {
+ size_t num_threads = 1;
+ size_t num_clients = 4;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, one clients.
+TEST_F(MtTcpListenerMgrTest, fourByOne) {
+ size_t num_threads = 4;
+ size_t num_clients = 1;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, four clients.
+TEST_F(MtTcpListenerMgrTest, fourByFour) {
+ size_t num_threads = 4;
+ size_t num_clients = 4;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Four threads, eight clients.
+TEST_F(MtTcpListenerMgrTest, fourByEight) {
+ size_t num_threads = 4;
+ size_t num_clients = 8;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Six threads, eighteen clients.
+TEST_F(MtTcpListenerMgrTest, sixByEighteen) {
+ size_t num_threads = 6;
+ size_t num_clients = 18;
+ threadListenAndRespond(num_threads, num_clients);
+}
+
+// Pauses and resumes the listener while it is processing
+// requests.
+TEST_F(MtTcpListenerMgrTest, pauseAndResume) {
+ size_t num_threads = 6;
+ size_t num_clients = 18;
+ size_t num_pauses = 3;
+ workPauseAndResume(num_threads, num_clients, num_pauses);
+}
+
+// Check if a TLS listener can be created.
+TEST_F(MtTcpListenerMgrTest, tls) {
+ IOAddress address(SERVER_ADDRESS);
+ uint16_t port = SERVER_PORT;
+ TlsContextPtr context;
+ configServer(context);
+
+ // Make sure we can create the listener.
+ ASSERT_NO_THROW_LOG(
+ mt_listener_mgr_.reset(new MtTcpListenerMgr(
+ std::bind(&MtTcpListenerMgrTest::listenerFactory,
+ this,
+ ph::_1, ph::_2, ph::_3, ph::_4, ph::_5, ph::_6),
+ IOAddress(SERVER_ADDRESS), SERVER_PORT, 1, context))
+ );
+
+ EXPECT_EQ(mt_listener_mgr_->getAddress(), address);
+ EXPECT_EQ(mt_listener_mgr_->getPort(), port);
+ EXPECT_EQ(mt_listener_mgr_->getThreadPoolSize(), 1);
+ EXPECT_EQ(mt_listener_mgr_->getTlsContext(), context);
+ EXPECT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+
+ // Make sure we can start it and it's listening with 1 thread.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->start());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 1);
+ ASSERT_TRUE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_FALSE(mt_listener_mgr_->getThreadIOService()->stopped());
+
+ // Stop it.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->stop());
+ ASSERT_TRUE(mt_listener_mgr_->isStopped());
+ EXPECT_EQ(mt_listener_mgr_->getThreadCount(), 0);
+ EXPECT_EQ(mt_listener_mgr_->getThreadPoolSize(), 1);
+ ASSERT_FALSE(mt_listener_mgr_->getThreadIOService());
+ EXPECT_TRUE(mt_listener_mgr_->isStopped());
+}
+
+/// Verifies that idle timeout can be passed down to the internal listener.
+TEST_F(MtTcpListenerMgrTest, idleTimeout) {
+ // Create an MtTcpListenerMgr.
+ createMtTcpListenerMgr(1, std::bind(&MtTcpListenerMgrTest::synchronizedCommandHandler,
+ this, ph::_1));
+ // Verify the default timeout value.
+ EXPECT_EQ(TCP_IDLE_CONNECTION_TIMEOUT, mt_listener_mgr_->getIdleTimeout());
+
+ // Set a new timeout value.
+ mt_listener_mgr_->setIdleTimeout(200);
+ EXPECT_EQ(200, mt_listener_mgr_->getIdleTimeout());
+
+ // Start the listener, which should instantiate the internal listener.
+ ASSERT_NO_THROW_LOG(mt_listener_mgr_->start());
+ ASSERT_TRUE(mt_listener_mgr_->isRunning());
+
+ // Verify the internal listener's timeout value.
+ auto tcp_listener = mt_listener_mgr_->getTcpListener();
+ ASSERT_TRUE(tcp_listener);
+ EXPECT_EQ(200, tcp_listener->getIdleTimeout());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/tcp/tests/run_unittests.cc b/src/lib/tcp/tests/run_unittests.cc
new file mode 100644
index 0000000..55589a6
--- /dev/null
+++ b/src/lib/tcp/tests/run_unittests.cc
@@ -0,0 +1,20 @@
+// Copyright (C) 2022 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 <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/tcp/tests/tcp_listener_unittests.cc b/src/lib/tcp/tests/tcp_listener_unittests.cc
new file mode 100644
index 0000000..90be50a
--- /dev/null
+++ b/src/lib/tcp/tests/tcp_listener_unittests.cc
@@ -0,0 +1,603 @@
+// Copyright (C) 2022-2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <tcp_test_listener.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::tcp;
+
+namespace ph = std::placeholders;
+
+std::ostream&
+operator<<(std::ostream& os, const AuditEntry& entry) {
+ os << "{ " << entry.connection_id_ << ", "
+ << (entry.direction_ == AuditEntry::INBOUND ? "I" : "O") << ", "
+ << entry.data_ << " }";
+ return (os);
+}
+
+namespace {
+
+/// @brief IP address to which service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_REQUEST_TIMEOUT = 200;
+
+/// @brief Connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref TcpListener.
+class TcpListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ TcpListenerTest()
+ : io_service_(), test_timer_(io_service_),
+ run_io_service_timer_(io_service_),
+ clients_(), clients_done_(0) {
+ test_timer_.setup(std::bind(&TcpListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT,
+ IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes active clients.
+ virtual ~TcpListenerTest() {
+ for (auto client : clients_) {
+ client->close();
+ }
+ }
+
+ /// @brief Create a new client.
+ ///
+ /// This method creates TcpTestClient instance and retains it in
+ /// the clients_ list.
+ /// @param tls_context TLS context to assign to the client.
+ TcpTestClientPtr createClient(TlsContextPtr tls_context = TlsContextPtr()) {
+ TcpTestClientPtr client(new TcpTestClient(io_service_,
+ std::bind(&TcpListenerTest::clientDone, this),
+ tls_context));
+ clients_.push_back(client);
+ return (client);
+ }
+
+ /// @brief Connect to the endpoint and send a request.
+ ///
+ /// This method creates TcpTestClient instance and retains it in
+ /// the clients_ list.
+ ///
+ /// @param request String containing the request to be sent.
+ /// @param tls_context TLS context to assign to the client.
+ void startRequest(const std::string& request,
+ TlsContextPtr tls_context = TlsContextPtr()) {
+ TcpTestClientPtr client = createClient(tls_context);
+ client->startRequest(request);
+ }
+
+ /// @brief Connect to the endpoint and send a list of requests.
+ ///
+ /// This method creates a TcpTestClient instance and initiates a
+ /// series of requests.
+ ///
+ /// @param request String containing the request to be sent.
+ /// @param tls_context TLS context to assign to the client.
+ void startRequests(const std::list<std::string>& requests,
+ TlsContextPtr tls_context = TlsContextPtr()) {
+ TcpTestClientPtr client = createClient(tls_context);
+ client->startRequests(requests);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Callback function each client invokes when done.
+ ///
+ /// It stops the IO service when all clients are done.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void clientDone() {
+ ++clients_done_;
+ if (clients_done_ >= clients_.size()) {
+ // They're all done or dead. Stop the service.
+ io_service_.stop();
+ }
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran.
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&TcpListenerTest::timeoutHandler,
+ this, false),
+ timeout,
+ IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief Filter that denies every other connection.
+ ///
+ /// @param remote_endpoint_address ip address of the remote end of
+ /// a connection.
+ bool connectionFilter(const boost::asio::ip::tcp::endpoint& remote_endpoint) {
+ static size_t count = 0;
+ // If the address doesn't match, something hinky is going on, so
+ // we'll reject them all. If it does match, then cool, it works
+ // as expected.
+ if ((count++ % 2) ||
+ (remote_endpoint.address().to_string() != SERVER_ADDRESS)) {
+ // Reject every other connection;
+ return (false);
+ }
+
+ return (true);
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TcpTestClientPtr> clients_;
+
+ /// @brief Counts the number of clients that have reported as done.
+ size_t clients_done_;
+};
+
+// This test verifies that a TCP connection can be established and used to
+// transmit a streamed request and receive a streamed response.
+TEST_F(TcpListenerTest, listen) {
+ const std::string request = "I am done";
+
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ TlsContextPtr(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TcpTestClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+
+ // Verify the audit trail for the connection.
+ // Sanity check to make sure we don't have more entries than we expect.
+ ASSERT_EQ(listener.audit_trail_->entries_.size(), 2);
+
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { 1, AuditEntry::INBOUND, "I am done" },
+ { 1, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ // Verify the audit trail.
+ ASSERT_EQ(expected_entries, listener.audit_trail_->getConnectionTrail(1));
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that a TCP connection can receive a complete
+// message that spans multiple socket reads.
+TEST_F(TcpListenerTest, splitReads) {
+ const std::string request = "I am done";
+
+ // Read at most one byte at a time.
+ size_t read_max = 1;
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ TlsContextPtr(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT),
+ 0,
+ read_max);
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request));
+ ASSERT_NO_THROW(runIOService());
+
+ // Fetch the client.
+ ASSERT_EQ(1, clients_.size());
+ TcpTestClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that a TCP connection can be established and used to
+// transmit a streamed request and receive a streamed response.
+TEST_F(TcpListenerTest, idleTimeoutTest) {
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ TlsContextPtr(),
+ TcpListener::IdleTimeout(SHORT_IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ // Start a client with an empty request. Empty requests tell the client to read
+ // without sending anything and expect the read to fail when the listener idle
+ // times out the socket.
+ ASSERT_NO_THROW(startRequest(""));
+
+ // Run until idle timer expires.
+ ASSERT_NO_THROW(runIOService());
+
+ ASSERT_EQ(1, clients_.size());
+ TcpTestClientPtr client = *clients_.begin();
+ EXPECT_FALSE(client->receiveDone());
+ EXPECT_TRUE(client->expectedEof());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+TEST_F(TcpListenerTest, multipleClientsListen) {
+ const std::string request = "I am done";
+
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ TlsContextPtr(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ size_t num_clients = 5;
+ for (auto i = 0; i < num_clients; ++i) {
+ ASSERT_NO_THROW(startRequest(request));
+ }
+
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(num_clients, clients_.size());
+
+ size_t connection_id = 1;
+ for (auto client : clients_) {
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { connection_id, AuditEntry::INBOUND, "I am done" },
+ { connection_id, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ // Fetch the entries for this connection.
+ auto entries = listener.audit_trail_->getConnectionTrail(connection_id);
+ ASSERT_EQ(expected_entries, entries);
+ ++connection_id;
+ }
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// Verify that the listener handles multiple requests for multiple
+// clients.
+TEST_F(TcpListenerTest, multipleRequetsPerClients) {
+ std::list<std::string>requests{ "one", "two", "three", "I am done"};
+
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ TlsContextPtr(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ size_t num_clients = 5;
+ for (auto i = 0; i < num_clients; ++i) {
+ ASSERT_NO_THROW(startRequests(requests));
+ }
+
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(num_clients, clients_.size());
+
+ std::list<std::string>expected_responses{ "echo one", "echo two",
+ "echo three", "good bye"};
+ size_t connection_id = 1;
+ for (auto client : clients_) {
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+ EXPECT_EQ(expected_responses, client->getResponses());
+
+ // Verify the connection's audit trail.
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { connection_id, AuditEntry::INBOUND, "one" },
+ { connection_id, AuditEntry::OUTBOUND, "echo one" },
+ { connection_id, AuditEntry::INBOUND, "two" },
+ { connection_id, AuditEntry::OUTBOUND, "echo two" },
+ { connection_id, AuditEntry::INBOUND, "three" },
+ { connection_id, AuditEntry::OUTBOUND, "echo three" },
+ { connection_id, AuditEntry::INBOUND, "I am done" },
+ { connection_id, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ // Fetch the entries for this connection.
+ auto entries = listener.audit_trail_->getConnectionTrail(connection_id);
+ ASSERT_EQ(expected_entries, entries);
+ ++connection_id;
+ }
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// Verify that connection filtering can eliminate specific connections.
+TEST_F(TcpListenerTest, filterClientsTest) {
+ const std::string request = "I am done";
+
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ TlsContextPtr(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT),
+ std::bind(&TcpListenerTest::connectionFilter, this, ph::_1));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ size_t num_clients = 5;
+ for (auto i = 0; i < num_clients; ++i) {
+ // Every other client sends nothing (i.e. waits for EOF) as
+ // we expect the filter to reject them.
+ if (i % 2 == 0) {
+ ASSERT_NO_THROW(startRequest("I am done"));
+ } else {
+ ASSERT_NO_THROW(startRequest(""));
+ }
+ }
+
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(num_clients, clients_.size());
+
+ size_t i = 0;
+ for (auto client : clients_) {
+ if (i % 2 == 0) {
+ // These clients should have been accepted and received responses.
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+
+ // Now verify the AuditTrail.
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { i+1, AuditEntry::INBOUND, "I am done" },
+ { i+1, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ auto entries = listener.audit_trail_->getConnectionTrail(i+1);
+ ASSERT_EQ(expected_entries, entries);
+
+ } else {
+ // These clients should have been rejected and gotten EOF'd.
+ EXPECT_FALSE(client->receiveDone());
+ EXPECT_TRUE(client->expectedEof());
+
+ // Verify connection recorded no audit entries.
+ auto entries = listener.audit_trail_->getConnectionTrail(i+1);
+ ASSERT_EQ(entries.size(), 0);
+ }
+
+ ++i;
+ }
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// Exercises TcpStreamRequest::postBuffer() through various
+// data permutations.
+TEST(TcpStreamRequst, postBufferTest) {
+ // Struct describing a test scenario.
+ struct Scenario {
+ const std::string desc_;
+ // List of input buffers to submit to post.
+ std::list<std::vector<uint8_t>> input_buffers_;
+ // List of expected "request" strings conveyed.
+ std::list<std::string> expected_strings_;
+ };
+
+ std::list<Scenario> scenarios{
+ {
+ "1. Two complete messages in their own buffers",
+ {
+ { 0x00, 0x04, 0x31, 0x32, 0x33, 0x34 },
+ { 0x00, 0x03, 0x35, 0x36, 0x37 },
+ },
+ { "1234", "567" }
+ },
+ {
+ "2. Three messages: first two are in the same buffer",
+ {
+ { 0x00, 0x04, 0x31, 0x32, 0x33, 0x34, 0x00, 0x02, 0x35, 0x36 },
+ { 0x00, 0x03, 0x37, 0x38, 0x39 },
+ },
+ { "1234", "56", "789" }
+ },
+ {
+ "3. One message across three buffers",
+ {
+ { 0x00, 0x09, 0x31, 0x32, 0x33 },
+ { 0x34, 0x35, 0x36, 0x37 },
+ { 0x38, 0x39 },
+ },
+ { "123456789" }
+
+ },
+ {
+ "4. One message, length and data split across buffers",
+ {
+ { 0x00 },
+ { 0x09, 0x31, 0x32, 0x33 },
+ { 0x34, 0x35, 0x36, 0x37 },
+ { 0x38, 0x39 },
+ },
+ { "123456789" }
+ }
+ };
+
+ // Extend the second case with 3 messages to all possible splits
+ // into one to four chunks.
+ std::string desc = "N. Three messages";
+ std::vector<uint8_t> buffer = {
+ 0x00, 0x04, 0x31, 0x32, 0x33, 0x34,
+ 0x00, 0x02, 0x35, 0x36,
+ 0x00, 0x03, 0x37, 0x38, 0x39
+ };
+ std::list<std::string> expected = { "1234", "56", "789" };
+ // No cut.
+ scenarios.push_back(Scenario{ desc, { buffer }, expected });
+ // One cut.
+ for (size_t i = 1; i < buffer.size() - 1; ++i) {
+ std::ostringstream sdesc;
+ sdesc << desc << " cut at " << i;
+ std::list<std::vector<uint8_t>> buffers;
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin(),
+ buffer.cbegin() + i));
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin() + i,
+ buffer.cend()));
+ scenarios.push_back(Scenario{ sdesc.str(), buffers, expected });
+ }
+ // Two cuts.
+ for (size_t i = 1; i < buffer.size() - 2; ++i) {
+ for (size_t j = i + 1; j < buffer.size() - 1; ++j) {
+ std::ostringstream sdesc;
+ sdesc << desc << " cut at " << i << " and " << j;
+ std::list<std::vector<uint8_t>> buffers;
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin(),
+ buffer.cbegin() + i));
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin() + i,
+ buffer.cbegin() + j));
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin() + j,
+ buffer.cend()));
+ scenarios.push_back(Scenario{ sdesc.str(), buffers, expected });
+ }
+ }
+ // Three cuts.
+ for (size_t i = 1; i < buffer.size() - 3; ++i) {
+ for (size_t j = i + 1; j < buffer.size() - 2; ++j) {
+ for (size_t k = j + 1; k < buffer.size() - 1; ++k) {
+ std::ostringstream sdesc;
+ sdesc << desc << " cut at " << i << ", " << j << " and " << k;
+ std::list<std::vector<uint8_t>> buffers;
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin(),
+ buffer.cbegin() + i));
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin() + i,
+ buffer.cbegin() + j));
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin() + j,
+ buffer.cbegin() + k));
+ buffers.push_back(std::vector<uint8_t>(buffer.cbegin() + k,
+ buffer.cend()));
+ scenarios.push_back(Scenario{ sdesc.str(), buffers, expected });
+ }
+ }
+ }
+
+ for (auto scenario : scenarios ) {
+ SCOPED_TRACE(scenario.desc_);
+ std::list<TcpStreamRequestPtr> requests;
+ TcpStreamRequestPtr request;
+ for (auto input_buf : scenario.input_buffers_) {
+ // Copy the input buffer.
+ std::vector<uint8_t> buf = input_buf;
+
+ // While there is data left to use, use it.
+ while (buf.size()) {
+ // If we need a new request make one.
+ if (!request) {
+ request.reset(new TcpStreamRequest());
+ }
+
+ size_t bytes_used = request->postBuffer(buf.data(),
+ buf.size());
+ if (!request->needData()) {
+ // Request is complete, save it.
+ requests.push_back(request);
+ request.reset();
+ }
+
+ // Consume bytes used.
+ if (bytes_used) {
+ buf.erase(buf.begin(), buf.begin() + bytes_used);
+ }
+ }
+ }
+
+ ASSERT_EQ(requests.size(), scenario.expected_strings_.size());
+ auto exp_string = scenario.expected_strings_.begin();
+ for (auto recvd_request : requests) {
+ ASSERT_NO_THROW(recvd_request->unpack());
+ EXPECT_EQ(*exp_string++, recvd_request->getRequestString());
+ }
+ }
+}
+
+}
diff --git a/src/lib/tcp/tests/tcp_test_client.h b/src/lib/tcp/tests/tcp_test_client.h
new file mode 100644
index 0000000..2e1b30b
--- /dev/null
+++ b/src/lib/tcp/tests/tcp_test_client.h
@@ -0,0 +1,437 @@
+// Copyright (C) 2022-2023 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 TCP_TEST_CLIENT_H
+#define TCP_TEST_CLIENT_H
+
+#include <cc/data.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/tls_socket.h>
+#include <asiolink/testutils/test_tls.h>
+#include <tcp/tcp_connection.h>
+#include <tcp/tcp_stream_msg.h>
+#include <boost/asio/read.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <gtest/gtest.h>
+
+/// @brief Entity which can connect to the TCP server endpoint with or
+/// or without TLS.
+class TcpTestClient : public boost::noncopyable {
+
+private:
+ /// @brief Type of the function implementing a callback invoked by the
+ /// @c SocketCallback functor.
+ typedef std::function<void(boost::system::error_code ec, size_t length)>
+ SocketCallbackFunction;
+
+ /// @brief Functor associated with the socket object.
+ ///
+ /// This functor calls a callback function specified in the constructor.
+ class SocketCallback {
+ public:
+
+ /// @brief Constructor.
+ ///
+ /// @param socket_callback Callback to be invoked by the functor upon
+ /// an event associated with the socket.
+ SocketCallback(SocketCallbackFunction socket_callback)
+ : callback_(socket_callback) {
+ }
+
+ /// @brief Operator called when event associated with a socket occurs.
+ ///
+ /// This operator returns immediately when received error code is
+ /// @c boost::system::error_code is equal to
+ /// @c boost::asio::error::operation_aborted, i.e. the callback is not
+ /// invoked.
+ ///
+ /// @param ec Error code.
+ /// @param length Data length.
+ void operator()(boost::system::error_code ec, size_t length = 0) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ }
+
+ callback_(ec, length);
+ }
+
+ private:
+ /// @brief Supplied callback.
+ SocketCallbackFunction callback_;
+ };
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates new socket instance. It doesn't connect. Call
+ /// start() to connect to the server.
+ ///
+ /// @param io_service IO service to be stopped on error or completion.
+ /// @param done_callback Function cient should invoke when it has finished
+ /// all its requests or failed.
+ /// @param tls_context
+ /// @param server_address string containing the IP address of the server.
+ /// @param port port number of the server.
+ explicit TcpTestClient(isc::asiolink::IOService& io_service,
+ std::function<void()> done_callback,
+ isc::asiolink::TlsContextPtr tls_context =
+ isc::asiolink::TlsContextPtr(),
+ const std::string& server_address = "127.0.0.1",
+ uint16_t port = 18123)
+ : io_service_(io_service.get_io_service()),
+ tls_context_(tls_context),
+ tcp_socket_(), tls_socket_(),
+ done_callback_(done_callback),
+ server_address_(server_address), server_port_(port),
+ buf_(), response_(),
+ receive_done_(false), expected_eof_(false), handshake_failed_(false) {
+ if (!tls_context_) {
+ tcp_socket_.reset(new isc::asiolink::TCPSocket<SocketCallback>(io_service));
+ } else {
+ tls_socket_.reset(new isc::asiolink::TLSSocket<SocketCallback>(io_service,
+ tls_context));
+ }
+ }
+
+ bool useTls() {
+ return (!!tls_context_);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Closes the underlying socket if it is open.
+ virtual ~TcpTestClient() {
+ close();
+ }
+
+ /// @brief Connect to the listener and initiate request processing.
+ ///
+ /// Upon successful connection, carry out the TLS handshake. If the handshake
+ /// completes successful start sending requests.
+ void start() {
+ isc::asiolink::TCPEndpoint endpoint(boost::asio::ip::address::from_string(server_address_), server_port_);
+ SocketCallback socket_cb(
+ [this](boost::system::error_code ec, size_t /*length */) {
+ receive_done_ = false;
+ expected_eof_ = false;
+ handshake_failed_ = false;
+ if (ec) {
+ // One would expect that open wouldn't return
+ // EINPROGRESS error code, but simply wait for the connection
+ // to get established before the handler is invoked. It turns out,
+ // however, that on some OSes the connect handler may receive this
+ // error code which doesn't necessarily indicate a problem.
+ // Making an attempt to write and read from this socket will
+ // typically succeed. So, we ignore this error.
+ if (ec.value() != boost::asio::error::in_progress) {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ done_callback_();
+ }
+ }
+
+ if (useTls()) {
+ SocketCallback socket_cb(
+ [this](boost::system::error_code ec, size_t /*length */) {
+ if (ec) {
+ handshake_failed_ = true;
+ done_callback_();
+ } else {
+ sendNextRequest();
+ }
+ });
+
+ tls_socket_->handshake(socket_cb);
+ } else {
+ sendNextRequest();
+ }
+ });
+
+ if (useTls()) {
+ tls_socket_->open(&endpoint, socket_cb);
+ } else {
+ tcp_socket_->open(&endpoint, socket_cb);
+ }
+ }
+
+ /// @brief Send request specified in textual format.
+ ///
+ /// @param request request in the textual format.
+ void startRequest(const std::string& request) {
+ requests_to_send_.push_back(request);
+ start();
+ }
+
+ /// @brief Send request specified in textual format.
+ ///
+ /// @param request request in the textual format.
+ void startRequests(const std::list<std::string>& requests) {
+ requests_to_send_ = requests;
+ start();
+ }
+
+ /// @brief Sends the next request from the list of requests to send.
+ void sendNextRequest() {
+ // If there are any requests left to send, send them.
+ if (!requests_to_send_.empty()) {
+ std::string request = requests_to_send_.front();
+ requests_to_send_.pop_front();
+ if (request.empty()) {
+ waitForEof();
+ } else {
+ sendRequest(request);
+ }
+ }
+ }
+
+ /// @brief Send a stream request.
+ ///
+ /// @param request request data to send textual format.
+ /// @param send_length number of bytes to send. If not zero, can be used
+ /// to truncate the amount of data sent.
+ void sendRequest(const std::string& request, const size_t send_length = 0) {
+ // Prepend the length of the request.
+ uint16_t size = static_cast<uint16_t>(request.size());
+ isc::tcp::WireData wire_request;
+ if (!request.empty()) {
+ wire_request.push_back(static_cast<uint8_t>((size & 0xff00U) >> 8));
+ wire_request.push_back(static_cast<uint8_t>(size & 0x00ffU));
+ wire_request.insert(wire_request.end(), request.begin(), request.end());
+ }
+
+ sendPartialRequest(wire_request, send_length);
+ }
+
+ /// @brief Wait for a server to close the connection.
+ void waitForEof() {
+ stream_response_.reset(new isc::tcp::TcpStreamRequest());
+ receivePartialResponse(true);
+ }
+
+ /// @brief Send part of the request.
+ ///
+ /// @param request part of the request to be sent.
+ /// @param send_length number of bytes to send. If not zero, can be used
+ /// to truncate the amount of data sent.
+ void sendPartialRequest(isc::tcp::WireData& wire_request, size_t send_length = 0) {
+ if (!send_length) {
+ send_length = wire_request.size();
+ } else {
+ ASSERT_LE(send_length, wire_request.size())
+ << "broken test, send_length exceeds wire size";
+ }
+
+ SocketCallback socket_cb(
+ [this, wire_request](boost::system::error_code ec, size_t bytes_transferred) mutable {
+ if (ec) {
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again make sure there is no garbage in the
+ // bytes_transferred.
+ bytes_transferred = 0;
+ } else {
+ ADD_FAILURE() << "error occurred while connecting: "
+ << ec.message();
+ done_callback_();
+ return;
+ }
+ }
+
+ // Remove the part of the request which has been sent.
+ if (bytes_transferred > 0 && (wire_request.size() <= bytes_transferred)) {
+ wire_request.erase(wire_request.begin(),
+ (wire_request.begin() + bytes_transferred));
+ }
+
+ // Continue sending request data if there are still some data to be
+ // sent.
+ if (!wire_request.empty()) {
+ sendPartialRequest(wire_request);
+ } else {
+ // Request has been sent. Start receiving response.
+ receivePartialResponse();
+ }
+ });
+
+ if (useTls()) {
+ tls_socket_->asyncSend(static_cast<const void *>(wire_request.data()),
+ send_length, socket_cb);
+ } else {
+ tcp_socket_->asyncSend(static_cast<const void *>(wire_request.data()),
+ send_length, socket_cb);
+ }
+ }
+
+ /// @brief Receive response from the server.
+ void receivePartialResponse(bool expect_eof = false) {
+ SocketCallback socket_cb(
+ [this, expect_eof](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (!stream_response_) {
+ stream_response_.reset(new isc::tcp::TcpStreamRequest());
+ }
+
+ if (ec) {
+ // IO service stopped so simply return.
+ if (ec.value() == boost::asio::error::operation_aborted) {
+ return;
+ } else if ((ec.value() == boost::asio::error::try_again) ||
+ (ec.value() == boost::asio::error::would_block)) {
+ // If we should try again, make sure that there is no garbage
+ // in the bytes_transferred.
+ bytes_transferred = 0;
+ } else if (expect_eof) {
+ expected_eof_ = true;
+ done_callback_();
+ return;
+ } else {
+ // Error occurred, bail...
+ ADD_FAILURE() << "client: " << this
+ << " error occurred while receiving TCP"
+ << " response from the server: " << ec.message();
+ done_callback_();
+ return;
+ }
+ }
+
+ // Post received data to the current response.
+ if (bytes_transferred > 0) {
+ stream_response_->postBuffer(buf_.data(), bytes_transferred);
+ }
+
+ if (stream_response_->needData()) {
+ // Response is incomplete, keep reading.
+ receivePartialResponse();
+ } else {
+ // Response is complete, process it.
+ responseReceived();
+ }
+ });
+
+ isc::asiolink::TCPEndpoint from;
+ if (useTls()) {
+ tls_socket_->asyncReceive(static_cast<void*>(buf_.data()), buf_.size(), 0,
+ &from, socket_cb);
+ } else {
+ tcp_socket_->asyncReceive(static_cast<void*>(buf_.data()), buf_.size(), 0,
+ &from, socket_cb);
+ }
+ }
+
+ /// @brief Process a completed response received from the server.
+ virtual void responseReceived() {
+ /// Unpack wire data into a string.
+ ASSERT_NO_THROW(stream_response_->unpack());
+ std::string response = stream_response_->getRequestString();
+ responses_received_.push_back(response);
+
+ // Quit if server tells us "good bye".
+ if (response.find("good bye", 0) != std::string::npos) {
+ receive_done_ = true;
+ done_callback_();
+ return;
+ }
+
+ // Clear out for the next one.
+ stream_response_.reset();
+ sendNextRequest();
+ }
+
+ /// @brief Close connection.
+ void close() {
+ if (useTls()) {
+ tls_socket_->close();
+ } else {
+ tcp_socket_->close();
+ }
+ }
+
+ /// @brief Returns true if the receive completed without error.
+ ///
+ /// @return True if the receive completed successfully, false
+ /// otherwise.
+ bool receiveDone() {
+ return (receive_done_);
+ }
+
+ /// @brief Returns true if the receive ended with expected EOF
+ ///
+ /// @return True if the receive ended with EOF, false otherwise
+ bool expectedEof() {
+ return (expected_eof_);
+ }
+
+ /// @brief Returns the list of received responses.
+ ///
+ /// @return list of string responses.
+ const std::list<std::string>& getResponses() {
+ return (responses_received_);
+ }
+
+ bool handshakeFailed() {
+ return(handshake_failed_);
+ }
+
+private:
+
+ /// @brief Holds reference to the IO service.
+ boost::asio::io_service& io_service_;
+
+ /// @brief TLS context.
+ isc::asiolink::TlsContextPtr tls_context_;
+
+ /// @brief TCP socket used by this connection.
+ std::unique_ptr<isc::asiolink::TCPSocket<SocketCallback> > tcp_socket_;
+
+ /// @brief TLS socket used by this connection.
+ std::unique_ptr<isc::asiolink::TLSSocket<SocketCallback> > tls_socket_;
+
+ /// @brief Callback to invoke when the client has finished its work or
+ /// failed.
+ std::function<void()> done_callback_;
+
+ /// @brief IP address of the server.
+ std::string server_address_;
+
+ /// @brief IP port of the server.
+ uint16_t server_port_;
+
+ /// @brief Buffer into which response is written.
+ std::array<char, 8192> buf_;
+
+ /// @brief Response in the textual format.
+ std::string response_;
+
+ /// @brief Set to true when the receive has completed successfully.
+ bool receive_done_;
+
+ /// @brief Set to true when the receive ended in EOF as expected. In other
+ /// words, the server closed the connection while we were reading as we
+ /// expected it to do.
+ bool expected_eof_;
+
+ /// @brief Set to true if the TLS handshake failed.
+ bool handshake_failed_;
+
+ /// @brief Pointer to the server response currently being received.
+ isc::tcp::TcpStreamRequestPtr stream_response_;
+
+ /// @brief List of string requests to send.
+ std::list<std::string> requests_to_send_;
+
+ /// @brief List of string responses received.
+ std::list<std::string> responses_received_;
+};
+
+/// @brief Pointer to the TcpTestClient.
+typedef boost::shared_ptr<TcpTestClient> TcpTestClientPtr;
+
+#endif
diff --git a/src/lib/tcp/tests/tcp_test_listener.h b/src/lib/tcp/tests/tcp_test_listener.h
new file mode 100644
index 0000000..5522484
--- /dev/null
+++ b/src/lib/tcp/tests/tcp_test_listener.h
@@ -0,0 +1,315 @@
+// Copyright (C) 2022 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 TCP_TEST_LISTENER_H
+#define TCP_TEST_LISTENER_H
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <tcp/tcp_listener.h>
+#include <tcp_test_client.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::tcp;
+
+/// @brief Describes stream message sent over a connection.
+class AuditEntry {
+public:
+ enum Direction {
+ INBOUND, // data received
+ OUTBOUND // data sent
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param connection_id Id of the client to whom the entry pertains
+ /// @param direction INBOUND for data received, OUTBOUND for data sent
+ /// @param data string form of the data involved
+ AuditEntry(size_t connection_id,
+ const AuditEntry::Direction& direction,
+ const std::string& data)
+ : connection_id_(connection_id), direction_(direction), data_(data) {
+ }
+
+ /// @brief Equality operator.
+ ///
+ /// @param other value to be compared.
+ bool operator==(const AuditEntry& other) const {
+ return ((connection_id_ == other.connection_id_) &&
+ (direction_ == other.direction_) &&
+ (data_ == other.data_));
+ }
+
+ /// @brief Unique client identifier.
+ size_t connection_id_;
+
+ /// @brief Indicates which direction the data traveled
+ Direction direction_;
+
+ /// @brief Contains the data sent or received.
+ std::string data_;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const AuditEntry& entry);
+
+/// @brief Contains the data receipt/transmission history for an arbitrary
+/// number of connections.
+class AuditTrail {
+public:
+ /// @brief Adds an entry to the audit trail.
+ ///
+ /// @param connection_id Id of the client to whom the entry pertains
+ /// @param direction INBOUND for data received, OUTBOUND for data sent
+ /// @param data string form of the data involved
+ void addEntry(size_t connection_id,
+ const AuditEntry::Direction& direction,
+ const std::string& data) {
+ std::unique_lock<std::mutex> lck(mutex_);
+ entries_.push_back(AuditEntry(connection_id, direction, data));
+ }
+
+ /// @brief Returns a list of AuditEntry(s) for a given connection.
+ ///
+ /// @param connection_id Id of the desired connection
+ /// @return A list of entries for the connection or an empty list if none are found.
+ std::list<AuditEntry> getConnectionTrail(size_t connection_id) {
+ std::unique_lock<std::mutex> lck(mutex_);
+ std::list<AuditEntry> conn_entries;
+ for (auto entry : entries_) {
+ if (entry.connection_id_ == connection_id) {
+ conn_entries.push_back(entry);
+ }
+ }
+
+ return (conn_entries);
+ }
+
+ /// @brief Dumps the audit trail as a string.
+ std::string dump() {
+ std::unique_lock<std::mutex> lck(mutex_);
+ std::stringstream ss;
+ for (auto entry : entries_) {
+ ss << entry << std::endl;
+ }
+
+ return (ss.str());
+ }
+
+ /// @brief Contains the audit entries.
+ std::list<AuditEntry> entries_;
+
+ /// @brief Mutex used to lock during access.
+ std::mutex mutex_;
+};
+
+/// @brief Defines a pointer to an AuditTrail
+typedef boost::shared_ptr<AuditTrail> AuditTrailPtr;
+
+/// @brief Derivation of TcpConnection used for testing.
+class TcpTestConnection : public TcpConnection {
+public:
+ typedef std::function<std::string(const std::string&)> ResponseHandler;
+
+ /// @brief Constructor
+ TcpTestConnection(IOService& io_service,
+ const TcpConnectionAcceptorPtr& acceptor,
+ const TlsContextPtr& tls_context,
+ TcpConnectionPool& connection_pool,
+ const TcpConnectionAcceptorCallback& acceptor_callback,
+ const TcpConnectionFilterCallback& filter_callback,
+ const long idle_timeout,
+ size_t connection_id,
+ AuditTrailPtr audit_trail,
+ ResponseHandler response_handler)
+ : TcpConnection(io_service, acceptor, tls_context, connection_pool,
+ acceptor_callback, filter_callback, idle_timeout),
+ connection_id_(connection_id), audit_trail_(audit_trail),
+ response_handler_(response_handler) {
+ }
+
+ /// @brief Creates a new empty request ready to receive data.
+ virtual TcpRequestPtr createRequest() {
+ return (TcpStreamRequestPtr(new TcpStreamRequest()));
+ }
+
+ /// @brief Processes a completely received request.
+ ///
+ /// Adds the request to the audit trail, then forms and sends a response.
+ /// If the request is "I am done", the response is "good bye" which should
+ /// instruct the client to disconnect.
+ ///
+ /// @param request Request to process.
+ virtual void requestReceived(TcpRequestPtr request) {
+ TcpStreamRequestPtr stream_req = boost::dynamic_pointer_cast<TcpStreamRequest>(request);
+ if (!stream_req) {
+ isc_throw(isc::Unexpected, "request not a TcpStreamRequest");
+ }
+
+ // Unpack the request and add it to the audit trail.
+ stream_req->unpack();
+ auto request_str = stream_req->getRequestString();
+ audit_trail_->addEntry(connection_id_, AuditEntry::INBOUND, request_str);
+
+ // Create the response.
+ std::string response_str;
+
+ // If there's a response handler, use it
+ if (response_handler_) {
+ response_str = response_handler_(request_str);
+ } else {
+ std::ostringstream os;
+ if (request_str.find("I am done", 0) != std::string::npos) {
+ os << "good bye";
+ } else {
+ os << "echo " << request_str;
+ }
+
+ response_str = os.str();
+ }
+
+ // Ship the response if it's not empty.
+ TcpStreamResponsePtr response;
+ if (!response_str.empty()) {
+ response.reset(new TcpStreamResponse());
+ response->setResponseData(response_str);
+ response->pack();
+ asyncSendResponse(response);
+ }
+ }
+
+ /// @brief Processes a response once it has been sent.
+ ///
+ /// Adds the response to the audit trail and returns true, signifying
+ /// that the connection should start the idle timer.
+ ///
+ /// @param response Response that was sent to the remote endpoint.
+ virtual bool responseSent(TcpResponsePtr response) {
+ TcpStreamResponsePtr resp = boost::dynamic_pointer_cast<TcpStreamResponse>(response);
+ if (!resp) {
+ isc_throw(isc::Unexpected, "resp not a TcpStreamResponse");
+ }
+
+ audit_trail_->addEntry(connection_id_, AuditEntry::OUTBOUND, resp->getResponseString());
+ return (true);
+ }
+
+ /// @brief Set the response handler
+ ///
+ /// Sets the response handler invoked by requestReceived.
+ ///
+ /// @param response_handler Handler function to invoke
+ void setResponseHandler(ResponseHandler response_handler) {
+ response_handler_ = response_handler;
+ };
+
+private:
+ /// @brief Id of this connection.
+ size_t connection_id_;
+
+ /// @brief Provides request/response history.
+ AuditTrailPtr audit_trail_;
+
+ /// @brief Reponse handler to pass into each connection.
+ ResponseHandler response_handler_;
+};
+
+/// @brief Defines a shared pointer to a TcpTestConnection.
+typedef boost::shared_ptr<TcpTestConnection> TcpTestConnectionPtr;
+
+/// @brief Implementation of the TCPListener used in tests.
+///
+/// Implements simple stream in/out listener.
+class TcpTestListener : public TcpListener {
+public:
+ /// @brief Constructor
+ TcpTestListener(IOService& io_service,
+ const IOAddress& server_address,
+ const unsigned short server_port,
+ const TlsContextPtr& tls_context,
+ const IdleTimeout& idle_timeout,
+ const TcpConnectionFilterCallback& filter_callback = 0,
+ const size_t read_max = 32 * 1024)
+ : TcpListener(io_service, server_address, server_port,
+ tls_context, idle_timeout, filter_callback),
+ read_max_(read_max), next_connection_id_(0),
+ audit_trail_(new AuditTrail()) {
+ }
+
+protected:
+ /// @brief Creates an instance of the @c TcpConnection.
+ ///
+ /// @param callback Callback invoked when new connection is accepted.
+ /// @param connection_filter Callback invoked during connection acceptance
+ /// that can allow or deny connections based on the remote endpoint.
+ /// @return Pointer to the created connection.
+ virtual TcpConnectionPtr createConnection(
+ const TcpConnectionAcceptorCallback& acceptor_callback,
+ const TcpConnectionFilterCallback& connection_filter) {
+ return(createTestConnection(acceptor_callback, connection_filter, response_handler_));
+ }
+
+ /// @brief Creates an instance of the @c TcpTestConnection.
+ ///
+ /// @param acceptor_callback Callback invoked when new connection is accepted.
+ /// @param connection_filter Callback invoked during connection acceptance
+ /// that can allow or deny connections based on the remote endpoint.
+ /// @param callback invoked by requestReceived() to build a response
+ ///
+ /// @return Pointer to the created connection.
+ virtual TcpTestConnectionPtr createTestConnection(
+ const TcpConnectionAcceptorCallback& acceptor_callback,
+ const TcpConnectionFilterCallback& connection_filter,
+ TcpTestConnection::ResponseHandler response_handler) {
+ TcpTestConnectionPtr conn(new TcpTestConnection(io_service_,
+ acceptor_,
+ tls_context_,
+ connections_,
+ acceptor_callback,
+ connection_filter,
+ idle_timeout_,
+ ++next_connection_id_,
+ audit_trail_,
+ response_handler));
+ conn->setReadMax(read_max_);
+ return (conn);
+ }
+
+ /// @brief Maximum size of a single socket read
+ size_t read_max_;
+
+ /// @brief Id to use for the next connection.
+ size_t next_connection_id_;
+
+ /// @brief Callback connection invokes in requestReceived to construct a response.
+ TcpTestConnection::ResponseHandler response_handler_;
+
+public:
+
+ /// @brief Set the response handler
+ ///
+ /// Sets the response handler invoked by requestReceived.
+ ///
+ /// @param response_handler Handler function to invoke
+ void setResponseHandler(TcpTestConnection::ResponseHandler response_handler) {
+ response_handler_ = response_handler;
+ };
+
+
+ /// @brief Tracks the input/output history of all connections.
+ AuditTrailPtr audit_trail_;
+};
+
+/// @brief Defines a pointer to a TcpTestListener.
+typedef boost::shared_ptr<TcpTestListener> TcpTestListenerPtr;
+
+#endif // TCP_TEST_LISTENER_H
diff --git a/src/lib/tcp/tests/tls_listener_unittests.cc b/src/lib/tcp/tests/tls_listener_unittests.cc
new file mode 100644
index 0000000..f628680
--- /dev/null
+++ b/src/lib/tcp/tests/tls_listener_unittests.cc
@@ -0,0 +1,498 @@
+// Copyright (C) 2023 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 <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/testutils/test_tls.h>
+#include <asiolink/io_service.h>
+#include <tcp_test_listener.h>
+#include <tcp_test_client.h>
+
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace boost::asio::ip;
+using namespace isc::asiolink;
+using namespace isc::asiolink::test;
+using namespace isc::tcp;
+
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief IP address to which service is bound.
+const std::string SERVER_ADDRESS = "127.0.0.1";
+
+/// @brief IPv6 address to whch service is bound.
+const std::string IPV6_SERVER_ADDRESS = "::1";
+
+/// @brief Port number to which service is bound.
+const unsigned short SERVER_PORT = 18123;
+
+/// @brief Request Timeout used in most of the tests (ms).
+const long REQUEST_TIMEOUT = 10000;
+
+/// @brief Connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_REQUEST_TIMEOUT = 200;
+
+/// @brief Connection idle timeout used in most of the tests (ms).
+const long IDLE_TIMEOUT = 10000;
+
+/// @brief Connection idle timeout used in tests where idle connections
+/// are tested (ms).
+const long SHORT_IDLE_TIMEOUT = 200;
+
+/// @brief Test timeout (ms).
+const long TEST_TIMEOUT = 10000;
+
+/// @brief Test fixture class for @ref TcpListener that uses TLS.
+class TlsListenerTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Starts test timer which detects timeouts.
+ TlsListenerTest()
+ : io_service_(), test_timer_(io_service_),
+ run_io_service_timer_(io_service_),
+ clients_(), clients_done_(0) {
+ test_timer_.setup(std::bind(&TlsListenerTest::timeoutHandler, this, true),
+ TEST_TIMEOUT,
+ IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Removes active clients.
+ virtual ~TlsListenerTest() {
+ for (auto client : clients_) {
+ client->close();
+ }
+ }
+
+ /// @brief Fetch the server TLS context.
+ TlsContextPtr serverContext() {
+ TlsContextPtr tls_context;
+ configServer(tls_context);
+ return(tls_context);
+ }
+
+ /// @brief Fetch a client TLS context that works with the server context.
+ TlsContextPtr clientContext() {
+ TlsContextPtr tls_context;
+ configClient(tls_context);
+ return(tls_context);
+ }
+
+ /// @brief Create a new client.
+ ///
+ /// This method creates TcpTestClient instance and retains it in
+ /// the clients_ list.
+ /// @param tls_context TLS context to assign to the client.
+ TcpTestClientPtr createClient(TlsContextPtr tls_context) {
+ TcpTestClientPtr client(new TcpTestClient(io_service_,
+ std::bind(&TlsListenerTest::clientDone, this),
+ tls_context));
+ clients_.push_back(client);
+ return (client);
+ }
+
+ /// @brief Connect to the endpoint and send a request.
+ ///
+ /// This method creates TcpTestClient instance and retains it in
+ /// the clients_ list.
+ ///
+ /// @param request String containing the request to be sent.
+ /// @param tls_context TLS context to assign to the client.
+ void startRequest(const std::string& request, TlsContextPtr tls_context) {
+ ASSERT_TRUE(tls_context);
+ TcpTestClientPtr client = createClient(tls_context);
+ client->startRequest(request);
+ }
+
+ /// @brief Connect to the endpoint and send a list of requests.
+ ///
+ /// This method creates a TcpTestClient instance and initiates a
+ /// series of requests.
+ ///
+ /// @param request String containing the request to be sent.
+ /// @param tls_context TLS context to assign to the client.
+ void startRequests(const std::list<std::string>& requests,
+ TlsContextPtr tls_context) {
+ ASSERT_TRUE(tls_context);
+ TcpTestClientPtr client = createClient(tls_context);
+ client->startRequests(requests);
+ }
+
+ /// @brief Callback function invoke upon test timeout.
+ ///
+ /// It stops the IO service and reports test timeout.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void timeoutHandler(const bool fail_on_timeout) {
+ if (fail_on_timeout) {
+ ADD_FAILURE() << "Timeout occurred while running the test!";
+ }
+ io_service_.stop();
+ }
+
+ /// @brief Callback function each client invokes when done.
+ ///
+ /// It stops the IO service when all clients are done.
+ ///
+ /// @param fail_on_timeout Specifies if test failure should be reported.
+ void clientDone() {
+ ++clients_done_;
+ if (clients_done_ >= clients_.size()) {
+ // They're all done or dead. Stop the service.
+ io_service_.stop();
+ }
+ }
+
+ /// @brief Runs IO service with optional timeout.
+ ///
+ /// @param timeout Optional value specifying for how long the io service
+ /// should be ran.
+ void runIOService(long timeout = 0) {
+ io_service_.get_io_service().reset();
+
+ if (timeout > 0) {
+ run_io_service_timer_.setup(std::bind(&TlsListenerTest::timeoutHandler,
+ this, false),
+ timeout,
+ IntervalTimer::ONE_SHOT);
+ }
+ io_service_.run();
+ io_service_.get_io_service().reset();
+ io_service_.poll();
+ }
+
+ /// @brief Filter that denies every other connection.
+ ///
+ /// @param remote_endpoint_address ip address of the remote end of
+ /// a connection.
+ bool connectionFilter(const boost::asio::ip::tcp::endpoint& remote_endpoint) {
+ static size_t count = 0;
+ // If the address doesn't match, something hinky is going on, so
+ // we'll reject them all. If it does match, then cool, it works
+ // as expected.
+ if ((count++ % 2) ||
+ (remote_endpoint.address().to_string() != SERVER_ADDRESS)) {
+ // Reject every other connection;
+ return (false);
+ }
+
+ return (true);
+ }
+
+ /// @brief IO service used in the tests.
+ IOService io_service_;
+
+ /// @brief Asynchronous timer service to detect timeouts.
+ IntervalTimer test_timer_;
+
+ /// @brief Asynchronous timer for running IO service for a specified amount
+ /// of time.
+ IntervalTimer run_io_service_timer_;
+
+ /// @brief List of client connections.
+ std::list<TcpTestClientPtr> clients_;
+
+ /// @brief Counts the number of clients that have reported as done.
+ size_t clients_done_;
+};
+
+// This test verifies that a TLS connection can be established with a client
+// with valid TLS credentials.
+TEST_F(TlsListenerTest, listen) {
+ const std::string request = "I am done";
+
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ serverContext(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request, clientContext()));
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(1, clients_.size());
+ TcpTestClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+
+ // Verify the audit trail for the connection.
+ // Sanity check to make sure we don't have more entries than we expect.
+ ASSERT_EQ(listener.audit_trail_->entries_.size(), 2);
+
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { 1, AuditEntry::INBOUND, "I am done" },
+ { 1, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ // Verify the audit trail.
+ ASSERT_EQ(expected_entries, listener.audit_trail_->getConnectionTrail(1));
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that a TLS connection is denied to a client
+// with invalid TLS credentials.
+TEST_F(TlsListenerTest, badClient) {
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ serverContext(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+
+ TlsContextPtr bad_client_ctx;
+ configSelf(bad_client_ctx);
+ ASSERT_NO_THROW(startRequest("", bad_client_ctx));
+
+ ASSERT_NO_THROW(runIOService());
+
+ ASSERT_EQ(1, clients_.size());
+ TcpTestClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_FALSE(client->receiveDone());
+
+ // Either we failed during handshake or we EOF'd as expected. OpenSSL fails
+ // after handshake, Botan before it.
+ EXPECT_TRUE(client->expectedEof() || client->handshakeFailed());
+}
+
+// This test verifies that a TLS connection can receive a complete
+// message that spans multiple socket reads.
+TEST_F(TlsListenerTest, splitReads) {
+ const std::string request = "I am done";
+
+ // Read at most one byte at a time.
+ size_t read_max = 1;
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ serverContext(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT),
+ 0,
+ read_max);
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ ASSERT_NO_THROW(startRequest(request, clientContext()));
+ ASSERT_NO_THROW(runIOService());
+
+ // Fetch the client.
+ ASSERT_EQ(1, clients_.size());
+ TcpTestClientPtr client = *clients_.begin();
+ ASSERT_TRUE(client);
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that a TLS connection can be established and used to
+// transmit a streamed request and receive a streamed response.
+TEST_F(TlsListenerTest, idleTimeoutTest) {
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ serverContext(),
+ TcpListener::IdleTimeout(SHORT_IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ // Start a client with an empty request. Empty requests tell the client
+ // to read without sending anything and expect the read to fail when
+ // the listener idle times out the socket.
+ ASSERT_NO_THROW(startRequest("", clientContext()));
+
+ // Run until idle timer expires.
+ ASSERT_NO_THROW(runIOService());
+
+ ASSERT_EQ(1, clients_.size());
+ TcpTestClientPtr client = *clients_.begin();
+ EXPECT_FALSE(client->receiveDone());
+ EXPECT_TRUE(client->expectedEof());
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// This test verifies that TLS connections with multiple clients.
+TEST_F(TlsListenerTest, multipleClientsListen) {
+ const std::string request = "I am done";
+
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ serverContext(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ size_t num_clients = 5;
+ for (auto i = 0; i < num_clients; ++i) {
+ ASSERT_NO_THROW(startRequest(request, clientContext()));
+ }
+
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(num_clients, clients_.size());
+
+ size_t connection_id = 1;
+ for (auto client : clients_) {
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { connection_id, AuditEntry::INBOUND, "I am done" },
+ { connection_id, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ // Fetch the entries for this connection.
+ auto entries = listener.audit_trail_->getConnectionTrail(connection_id);
+ ASSERT_EQ(expected_entries, entries);
+ ++connection_id;
+ }
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// Verify that the listener handles multiple requests for multiple
+// clients.
+TEST_F(TlsListenerTest, multipleRequetsPerClients) {
+ std::list<std::string>requests{ "one", "two", "three", "I am done"};
+
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ serverContext(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ size_t num_clients = 5;
+ for (auto i = 0; i < num_clients; ++i) {
+ ASSERT_NO_THROW(startRequests(requests, clientContext()));
+ }
+
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(num_clients, clients_.size());
+
+ std::list<std::string>expected_responses{ "echo one", "echo two",
+ "echo three", "good bye"};
+ size_t connection_id = 1;
+ for (auto client : clients_) {
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+ EXPECT_EQ(expected_responses, client->getResponses());
+
+ // Verify the connection's audit trail.
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { connection_id, AuditEntry::INBOUND, "one" },
+ { connection_id, AuditEntry::OUTBOUND, "echo one" },
+ { connection_id, AuditEntry::INBOUND, "two" },
+ { connection_id, AuditEntry::OUTBOUND, "echo two" },
+ { connection_id, AuditEntry::INBOUND, "three" },
+ { connection_id, AuditEntry::OUTBOUND, "echo three" },
+ { connection_id, AuditEntry::INBOUND, "I am done" },
+ { connection_id, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ // Fetch the entries for this connection.
+ auto entries = listener.audit_trail_->getConnectionTrail(connection_id);
+ ASSERT_EQ(expected_entries, entries);
+ ++connection_id;
+ }
+
+ listener.stop();
+ io_service_.poll();
+}
+
+// Verify that connection filtering can eliminate specific connections.
+TEST_F(TlsListenerTest, filterClientsTest) {
+ TcpTestListener listener(io_service_,
+ IOAddress(SERVER_ADDRESS),
+ SERVER_PORT,
+ serverContext(),
+ TcpListener::IdleTimeout(IDLE_TIMEOUT),
+ std::bind(&TlsListenerTest::connectionFilter, this, ph::_1));
+
+ ASSERT_NO_THROW(listener.start());
+ ASSERT_EQ(SERVER_ADDRESS, listener.getLocalAddress().toText());
+ ASSERT_EQ(SERVER_PORT, listener.getLocalPort());
+ size_t num_clients = 5;
+ for (auto i = 0; i < num_clients; ++i) {
+ // Every other client sends nothing (i.e. waits for EOF) as
+ // we expect the filter to reject them.
+ if (i % 2 == 0) {
+ ASSERT_NO_THROW(startRequest("I am done", clientContext()));
+ } else {
+ ASSERT_NO_THROW(startRequest("", clientContext()));
+ }
+ }
+
+ ASSERT_NO_THROW(runIOService());
+ ASSERT_EQ(num_clients, clients_.size());
+
+ size_t i = 0;
+ for (auto client : clients_) {
+ if (i % 2 == 0) {
+ // These clients should have been accepted and received responses.
+ EXPECT_TRUE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+ EXPECT_FALSE(client->handshakeFailed());
+
+ // Now verify the AuditTrail.
+ // Create the list of expected entries.
+ std::list<AuditEntry> expected_entries {
+ { i+1, AuditEntry::INBOUND, "I am done" },
+ { i+1, AuditEntry::OUTBOUND, "good bye" }
+ };
+
+ auto entries = listener.audit_trail_->getConnectionTrail(i+1);
+ ASSERT_EQ(expected_entries, entries);
+
+ } else {
+ // Connection filtering closes the connection before the client
+ // initiates the handshake, causing the subsequent handshake attempt
+ // to fail.
+ EXPECT_FALSE(client->receiveDone());
+ EXPECT_FALSE(client->expectedEof());
+ EXPECT_TRUE(client->handshakeFailed());
+
+ // Verify connection recorded no audit entries.
+ auto entries = listener.audit_trail_->getConnectionTrail(i+1);
+ ASSERT_EQ(entries.size(), 0);
+ }
+
+ ++i;
+ }
+
+ listener.stop();
+ io_service_.poll();
+}
+
+}
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
new file mode 100644
index 0000000..03d78fb
--- /dev/null
+++ b/src/lib/testutils/Makefile.am
@@ -0,0 +1,30 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS=$(KEA_CXXFLAGS)
+
+noinst_SCRIPTS = dhcp_test_lib.sh xml_reporting_test_lib.sh
+
+if HAVE_GTEST
+noinst_LTLIBRARIES = libkea-testutils.la
+
+libkea_testutils_la_SOURCES = io_utils.cc io_utils.h
+libkea_testutils_la_SOURCES += sandbox.h
+libkea_testutils_la_SOURCES += log_utils.cc log_utils.h
+libkea_testutils_la_SOURCES += test_to_element.cc test_to_element.h
+libkea_testutils_la_SOURCES += threaded_test.cc threaded_test.h
+libkea_testutils_la_SOURCES += unix_control_client.cc unix_control_client.h
+libkea_testutils_la_SOURCES += user_context_utils.cc user_context_utils.h
+libkea_testutils_la_SOURCES += gtest_utils.h
+libkea_testutils_la_SOURCES += multi_threading_utils.h
+libkea_testutils_la_SOURCES += lib_load_test_fixture.h
+libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_testutils_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+endif
+
+# Include common libraries being used by shell-based tests.
+SHLIBS = dhcp_test_lib.sh.in xml_reporting_test_lib.sh.in
+
+EXTRA_DIST = $(SHLIBS)
+
+CLEANFILES = dhcp_test_lib.sh xml_reporting_test_lib.sh
diff --git a/src/lib/testutils/Makefile.in b/src/lib/testutils/Makefile.in
new file mode 100644
index 0000000..d7d2ce6
--- /dev/null
+++ b/src/lib/testutils/Makefile.in
@@ -0,0 +1,824 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = dhcp_test_lib.sh xml_reporting_test_lib.sh
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libkea_testutils_la_DEPENDENCIES = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la
+am__libkea_testutils_la_SOURCES_DIST = io_utils.cc io_utils.h \
+ sandbox.h log_utils.cc log_utils.h test_to_element.cc \
+ test_to_element.h threaded_test.cc threaded_test.h \
+ unix_control_client.cc unix_control_client.h \
+ user_context_utils.cc user_context_utils.h gtest_utils.h \
+ multi_threading_utils.h lib_load_test_fixture.h
+@HAVE_GTEST_TRUE@am_libkea_testutils_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-io_utils.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-log_utils.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-test_to_element.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-threaded_test.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-unix_control_client.lo \
+@HAVE_GTEST_TRUE@ libkea_testutils_la-user_context_utils.lo
+libkea_testutils_la_OBJECTS = $(am_libkea_testutils_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 =
+@HAVE_GTEST_TRUE@am_libkea_testutils_la_rpath =
+SCRIPTS = $(noinst_SCRIPTS)
+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_testutils_la-io_utils.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-log_utils.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo \
+ ./$(DEPDIR)/libkea_testutils_la-user_context_utils.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_testutils_la_SOURCES)
+DIST_SOURCES = $(am__libkea_testutils_la_SOURCES_DIST)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/dhcp_test_lib.sh.in \
+ $(srcdir)/xml_reporting_test_lib.sh.in $(top_srcdir)/depcomp \
+ README
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+noinst_SCRIPTS = dhcp_test_lib.sh xml_reporting_test_lib.sh
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libkea-testutils.la
+@HAVE_GTEST_TRUE@libkea_testutils_la_SOURCES = io_utils.cc io_utils.h \
+@HAVE_GTEST_TRUE@ sandbox.h log_utils.cc log_utils.h \
+@HAVE_GTEST_TRUE@ test_to_element.cc test_to_element.h \
+@HAVE_GTEST_TRUE@ threaded_test.cc threaded_test.h \
+@HAVE_GTEST_TRUE@ unix_control_client.cc unix_control_client.h \
+@HAVE_GTEST_TRUE@ user_context_utils.cc user_context_utils.h \
+@HAVE_GTEST_TRUE@ gtest_utils.h multi_threading_utils.h \
+@HAVE_GTEST_TRUE@ lib_load_test_fixture.h
+@HAVE_GTEST_TRUE@libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libkea_testutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la
+
+# Include common libraries being used by shell-based tests.
+SHLIBS = dhcp_test_lib.sh.in xml_reporting_test_lib.sh.in
+EXTRA_DIST = $(SHLIBS)
+CLEANFILES = dhcp_test_lib.sh xml_reporting_test_lib.sh
+all: all-am
+
+.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/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/testutils/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):
+dhcp_test_lib.sh: $(top_builddir)/config.status $(srcdir)/dhcp_test_lib.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+xml_reporting_test_lib.sh: $(top_builddir)/config.status $(srcdir)/xml_reporting_test_lib.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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-testutils.la: $(libkea_testutils_la_OBJECTS) $(libkea_testutils_la_DEPENDENCIES) $(EXTRA_libkea_testutils_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(am_libkea_testutils_la_rpath) $(libkea_testutils_la_OBJECTS) $(libkea_testutils_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-io_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-log_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_testutils_la-user_context_utils.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)$(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 $@ $<
+
+libkea_testutils_la-io_utils.lo: io_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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-io_utils.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-io_utils.Tpo -c -o libkea_testutils_la-io_utils.lo `test -f 'io_utils.cc' || echo '$(srcdir)/'`io_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-io_utils.Tpo $(DEPDIR)/libkea_testutils_la-io_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_utils.cc' object='libkea_testutils_la-io_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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-io_utils.lo `test -f 'io_utils.cc' || echo '$(srcdir)/'`io_utils.cc
+
+libkea_testutils_la-log_utils.lo: log_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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-log_utils.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-log_utils.Tpo -c -o libkea_testutils_la-log_utils.lo `test -f 'log_utils.cc' || echo '$(srcdir)/'`log_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-log_utils.Tpo $(DEPDIR)/libkea_testutils_la-log_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='log_utils.cc' object='libkea_testutils_la-log_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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-log_utils.lo `test -f 'log_utils.cc' || echo '$(srcdir)/'`log_utils.cc
+
+libkea_testutils_la-test_to_element.lo: test_to_element.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-test_to_element.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-test_to_element.Tpo -c -o libkea_testutils_la-test_to_element.lo `test -f 'test_to_element.cc' || echo '$(srcdir)/'`test_to_element.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-test_to_element.Tpo $(DEPDIR)/libkea_testutils_la-test_to_element.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='test_to_element.cc' object='libkea_testutils_la-test_to_element.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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-test_to_element.lo `test -f 'test_to_element.cc' || echo '$(srcdir)/'`test_to_element.cc
+
+libkea_testutils_la-threaded_test.lo: threaded_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-threaded_test.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-threaded_test.Tpo -c -o libkea_testutils_la-threaded_test.lo `test -f 'threaded_test.cc' || echo '$(srcdir)/'`threaded_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-threaded_test.Tpo $(DEPDIR)/libkea_testutils_la-threaded_test.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='threaded_test.cc' object='libkea_testutils_la-threaded_test.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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-threaded_test.lo `test -f 'threaded_test.cc' || echo '$(srcdir)/'`threaded_test.cc
+
+libkea_testutils_la-unix_control_client.lo: unix_control_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-unix_control_client.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-unix_control_client.Tpo -c -o libkea_testutils_la-unix_control_client.lo `test -f 'unix_control_client.cc' || echo '$(srcdir)/'`unix_control_client.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-unix_control_client.Tpo $(DEPDIR)/libkea_testutils_la-unix_control_client.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unix_control_client.cc' object='libkea_testutils_la-unix_control_client.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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-unix_control_client.lo `test -f 'unix_control_client.cc' || echo '$(srcdir)/'`unix_control_client.cc
+
+libkea_testutils_la-user_context_utils.lo: user_context_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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_testutils_la-user_context_utils.lo -MD -MP -MF $(DEPDIR)/libkea_testutils_la-user_context_utils.Tpo -c -o libkea_testutils_la-user_context_utils.lo `test -f 'user_context_utils.cc' || echo '$(srcdir)/'`user_context_utils.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_testutils_la-user_context_utils.Tpo $(DEPDIR)/libkea_testutils_la-user_context_utils.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='user_context_utils.cc' object='libkea_testutils_la-user_context_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_testutils_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_testutils_la-user_context_utils.lo `test -f 'user_context_utils.cc' || echo '$(srcdir)/'`user_context_utils.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(SCRIPTS)
+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 clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-io_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-log_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-user_context_utils.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/libkea_testutils_la-io_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-log_utils.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-test_to_element.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-threaded_test.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-unix_control_client.Plo
+ -rm -f ./$(DEPDIR)/libkea_testutils_la-user_context_utils.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ 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 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/testutils/README b/src/lib/testutils/README
new file mode 100644
index 0000000..126f1b8
--- /dev/null
+++ b/src/lib/testutils/README
@@ -0,0 +1,2 @@
+Here is some code used by more than one test. No code is used for Kea
+itself, only for testing.
diff --git a/src/lib/testutils/dhcp_test_lib.sh.in b/src/lib/testutils/dhcp_test_lib.sh.in
new file mode 100644
index 0000000..6260d2d
--- /dev/null
+++ b/src/lib/testutils/dhcp_test_lib.sh.in
@@ -0,0 +1,1176 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2023 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/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# shellcheck disable=SC2153
+# SC2153: Possible misspelling: ... may not be assigned, but ... is.
+
+# shellcheck disable=SC2154
+# SC2154: bin_path is referenced but not assigned.
+
+# shellcheck disable=SC3043
+# SC3043: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Include XML reporting library.
+. "@abs_top_builddir@/src/lib/testutils/xml_reporting_test_lib.sh"
+
+prefix="@prefix@"
+
+# Expected version
+EXPECTED_VERSION="@PACKAGE_VERSION@"
+
+# Kea environment variables for shell tests.
+# KEA_LOGGER_DESTINATION is set per test with set_logger.
+export KEA_LFC_EXECUTABLE="@abs_top_builddir@/src/bin/lfc/kea-lfc"
+export KEA_LOCKFILE_DIR="@abs_top_builddir@/test_lockfile_dir"
+export KEA_PIDFILE_DIR="@abs_top_builddir@/test_pidfile_dir"
+KEA_DHCP4_LOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/load_marker.txt"
+KEA_DHCP4_UNLOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/unload_marker.txt"
+KEA_DHCP4_SRV_CONFIG_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp4/tests/srv_config_marker_file.txt"
+KEA_DHCP6_LOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/load_marker.txt"
+KEA_DHCP6_UNLOAD_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/unload_marker.txt"
+KEA_DHCP6_SRV_CONFIG_MARKER_FILE="@abs_top_builddir@/src/bin/dhcp6/tests/srv_config_marker_file.txt"
+
+# A list of Kea processes, mainly used by the cleanup functions.
+KEA_PROCS="kea-dhcp4 kea-dhcp6 kea-dhcp-ddns kea-ctrl-agent"
+
+### Colors ###
+
+if test -t 1; then
+ green='\033[92m'
+ red='\033[91m'
+ reset='\033[0m'
+fi
+
+### Logging functions ###
+
+# Prints error message.
+test_lib_error() {
+ local s="${1-}" # Error message.
+ local no_new_line="${2-}" # If specified, the message is not terminated
+ # with new line.
+ printf "ERROR/test_lib: %s" "${s}"
+ if [ -z "${no_new_line}" ]; then
+ printf '\n'
+ fi
+}
+
+# Prints info message.
+test_lib_info() {
+ local s="${1-}" # Info message.
+ local no_new_line="${2-}" # If specified, the message is not terminated
+ # with new line.
+ printf "INFO/test_lib: %s" "${s}"
+ if [ -z "${no_new_line}" ]; then
+ printf '\n'
+ fi
+}
+
+### Assertions ###
+
+# Assertion that checks if two numbers are equal.
+# If numbers are not equal, the mismatched values are presented and the
+# detailed error is printed. The detailed error must use the printf
+# formatting like this:
+# "Expected that some value 1 %d is equal to some other value %d".
+assert_eq() {
+ val1=${1} # Reference value
+ val2=${2} # Tested value
+ detailed_err=${3-} # Optional detailed error format string
+ # If nothing found, present an error an exit.
+ if [ "${val1}" -ne "${val2}" ]; then
+ printf 'Assertion failure: %s != %s, expected %s, got %s\n' \
+ "${val1}" "${val2}" "${val1}" "${val2}"
+ # shellcheck disable=SC2059
+ # SC2059: Don't use variables in the printf format string. Use printf '..%s..' "$foo"
+ ERROR=$(printf "${detailed_err}" "${val1}" "${val2}")
+ printf '%s\n%s\n' "${ERROR}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Assertion that checks that two strings are equal.
+# If strings are not equal, the mismatched values are presented and the
+# detailed error is printed. The detailed error must use the printf
+# formatting like this:
+# "Expected that some value 1 %d is equal to some other value %d".
+assert_str_eq() {
+ val1=${1} # Reference value
+ val2=${2} # Tested value
+ detailed_err=${3-} # Optional detailed error format string
+ # If nothing found, present an error an exit.
+ if [ "${val1}" != "${val2}" ]; then
+ printf 'Assertion failure: %s != %s, expected "%s", got "%s"\n' \
+ "${val1}" "${val2}" "${val1}" "${val2}"
+ # shellcheck disable=SC2059
+ # SC2059: SC2059: Don't use variables in the printf format string. Use printf '..%s..' "$foo".
+ ERROR=$(printf "${detailed_err}" "${val1}" "${val2}")
+ printf '%s\n%s\n' "${ERROR}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Assertion that checks that two strings are NOT equal.
+# If strings are equal, the mismatched values are presented and the
+# optional detailed error, if any, is printed.
+assert_str_neq() {
+ reference=${1} # Reference value
+ tested=${2} # Tested value
+ detailed_error=${3-} # Optional detailed error format string
+ if test "${reference}" = "${tested}"; then
+ printf 'Assertion failure: expected different strings, but '
+ printf 'both variables have the value "%s".\n' "${reference}"
+ printf '%s\n%s\n' "${detailed_error}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Assertion that checks if one string contains another string.
+# If assertion fails, both strings are displayed and the detailed
+# error is printed. The detailed error must use the printf formatting
+# like this:
+# "Expected some string to contain this string: %s".
+assert_string_contains() {
+ pattern="${1}" # Substring or awk pattern
+ text="${2}" # Text to be searched for substring
+ detailed_err="${3}" # Detailed error format string
+ # Search for a pattern
+ match=$( printf "%s" "${text}" | awk /"${pattern}"/ )
+ # If nothing found, present an error and exit.
+ if [ -z "${match}" ]; then
+ ERROR=$(printf \
+"Assertion failure:
+\"%s\"
+
+does not contain pattern:
+\"%s\"
+
+${detailed_err}
+" "${text}" "${pattern}" "${pattern}")
+ printf '%s\n%s\n' "${ERROR}" "${OUTPUT}" >&2
+ clean_exit 1
+ fi
+}
+
+# Runs all the given arguments as a single command. Maintains quoting. Places
+# output in ${OUTPUT} and exit code in ${EXIT_CODE}. Does not support pipes and
+# redirections. Support for them could be added through eval and single
+# parameter assignment, but eval is not recommended.
+# shellcheck disable=SC2034
+# SC2034: ... appears unused. Verify use (or export if used externally).
+run_command() {
+ if test -n "${DEBUG+x}"; then
+ printf '%s\n' "${*}" >&2
+ fi
+ set +e
+ OUTPUT=$("${@}")
+ EXIT_CODE=${?}
+ set -e
+}
+
+# Enable traps to print FAILED status when a command fails unexpectedly or when
+# the user sends a SIGINT. Used in `test_start`.
+traps_on() {
+ for t in HUP INT QUIT KILL TERM EXIT; do
+ # shellcheck disable=SC2064
+ # SC2064: Use single quotes, otherwise this expands now rather than when signalled.
+ # reason: we want ${red-} and ${reset-} to expand here, at trap-time
+ # they will be empty or have other values
+ trap "
+ exit_code=\${?}
+ printf '${red-}[ FAILED ]${reset-} %s (exit code: %d)\n' \
+ \"\${TEST_NAME}\" \"\${exit_code}\"
+ " "${t}"
+ done
+}
+
+# Disable traps so that a double status is not printed. Used in `test_finish`
+# after the status has been printed explicitly.
+traps_off() {
+ for t in HUP INT QUIT KILL TERM EXIT; do
+ trap - "${t}"
+ done
+}
+
+# Print UNIX time with millisecond resolution.
+get_current_time() {
+ local time
+ time=$(date +%s%3N)
+
+ # In some systems, particularly BSD-based, `+%3N` millisecond resolution is
+ # not supported. It instead prints the literal '3N', but we check for any
+ # alphabetical character. If we do find one, revert to second resolution and
+ # convert to milliseconds.
+ if printf '%s' "${time}" | grep -E '[A-Za-z]' > /dev/null 2>&1; then
+ time=$(date +%s)
+ time=$((1000 * time))
+ fi
+
+ printf '%s' "${time}"
+}
+
+# Begins a test by printing its name.
+test_start() {
+ TEST_NAME=${1-}
+ if [ -z "${TEST_NAME}" ]; then
+ test_lib_error "test_start requires test name as an argument"
+ clean_exit 1
+ fi
+
+ # Set traps first to fail if something goes wrong.
+ traps_on
+
+ # Announce test start.
+ printf "${green-}[ RUN ]${reset-} %s\n" "${TEST_NAME}"
+
+ # Remove dangling Kea instances and remove log files.
+ cleanup
+
+ # Make sure lockfile and pidfile directories exist. They are used in some
+ # tests.
+ mkdir -p "${KEA_LOCKFILE_DIR}"
+ # There are certain tests that intentionally run without a KEA_PIDFILE_DIR
+ # e.g. keactrl.status_test. Only create the directory if we test requires
+ # one.
+ if test -n "${KEA_PIDFILE_DIR+x}"; then
+ mkdir -p "${KEA_PIDFILE_DIR}"
+ fi
+
+ # Start timer in milliseconds.
+ START_TIME=$(get_current_time)
+}
+
+# Prints test result an cleans up after the test.
+test_finish() {
+ # Exit code to be returned by the exit function
+ local exit_code="${1}"
+
+ # Stop timer and set duration.
+ FINISH_TIME=$(get_current_time)
+ local duration
+ duration=$((FINISH_TIME - START_TIME))
+
+ # Add the test result to the XML.
+ report_test_result_in_xml "${TEST_NAME}" "${exit_code}" "${duration}"
+
+ if [ "${exit_code}" -eq 0 ]; then
+ printf "${green-}[ OK ]${reset-} %s\n" "${TEST_NAME}"
+ else
+ # Dump log file for debugging purposes if specified and exists.
+ # Otherwise the code below would simply call cat.
+ # Use ${var+x} to test if ${var} is defined.
+ if test -n "${LOG_FILE+x}" && test -s "${LOG_FILE}"; then
+ printf 'Log file dump:\n'
+ cat "${LOG_FILE}"
+ fi
+ printf "${red-}[ FAILED ]${reset-} %s\n" "${TEST_NAME}"
+ fi
+
+ # Remove dangling Kea instances and log files.
+ cleanup
+
+ # Reset traps.
+ traps_off
+
+ # Explicitly return ${exit_code}. The effect should be for `make check` to
+ # return with the exit same code or at least another non-zero exit code thus
+ # reporting a failure.
+ return "${exit_code}"
+}
+
+# Stores the configuration specified as a parameter in the configuration
+# file which name has been set in the ${CFG_FILE} variable.
+create_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${CFG_FILE+x}" ]; then
+ test_lib_error "create_config requires CFG_FILE variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_config requires argument holding a configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Kea configuration file: %s.\n' "${CFG_FILE}"
+ printf '%b' "${cfg}" > "${CFG_FILE}"
+}
+
+# Stores the DHCP4 configuration specified as a parameter in the
+# configuration file which name has been set in the ${DHCP4_CFG_FILE}
+# variable.
+create_dhcp4_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${DHCP4_CFG_FILE+x}" ]; then
+ test_lib_error "create_dhcp4_config requires DHCP4_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_dhcp4_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Dhcp4 configuration file: %s.\n' "${DHCP4_CFG_FILE}"
+ printf '%b' "${cfg}" > "${DHCP4_CFG_FILE}"
+}
+
+# Stores the DHCP6 configuration specified as a parameter in the
+# configuration file which name has been set in the ${DHCP6_CFG_FILE}
+# variable.
+create_dhcp6_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${DHCP6_CFG_FILE+x}" ]; then
+ test_lib_error "create_dhcp6_config requires DHCP6_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_dhcp6_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Dhcp6 configuration file: %s.\n' "${DHCP6_CFG_FILE}"
+ printf '%b' "${cfg}" > "${DHCP6_CFG_FILE}"
+}
+
+# Stores the D2 configuration specified as a parameter in the
+# configuration file which name has been set in the ${D2_CFG_FILE}
+# variable.
+create_d2_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${D2_CFG_FILE+x}" ]; then
+ test_lib_error "create_d2_config requires D2_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_d2_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating D2 configuration file: %s.\n' "${D2_CFG_FILE}"
+ printf '%b' "${cfg}" > "${D2_CFG_FILE}"
+}
+
+# Stores the CA configuration specified as a parameter in the
+# configuration file which name has been set in the ${CA_CFG_FILE}
+# variable.
+create_ca_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${CA_CFG_FILE+x}" ]; then
+ test_lib_error "create_ca_config requires CA_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_ca_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Ca configuration file: %s.\n' "${CA_CFG_FILE}"
+ printf '%b' "${cfg}" > "${CA_CFG_FILE}"
+}
+
+# Stores the NC configuration specified as a parameter in the
+# configuration file which name has been set in the ${NC_CFG_FILE}
+# variable.
+create_nc_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${NC_CFG_FILE+x}" ]; then
+ test_lib_error "create_nc_config requires NC_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_nc_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating Nc configuration file: %s.\n' "${NC_CFG_FILE}"
+ printf '%b' "${cfg}" > "${NC_CFG_FILE}"
+}
+
+# Stores the keactrl configuration specified as a parameter in the
+# configuration file which name has been set in the ${KEACTRL_CFG_FILE}
+# variable.
+create_keactrl_config() {
+ local cfg="${1-}" # Configuration string.
+ if [ -z "${KEACTRL_CFG_FILE+x}" ]; then
+ test_lib_error "create_keactrl_config requires KEACTRL_CFG_FILE \
+variable be set"
+ clean_exit 1
+
+ elif [ -z "${cfg}" ]; then
+ test_lib_error "create_keactrl_config requires argument holding a \
+configuration"
+ clean_exit 1
+ fi
+ printf 'Creating keactrl configuration file: %s.\n' "${KEACTRL_CFG_FILE}"
+ printf '%b' "${cfg}" > "${KEACTRL_CFG_FILE}"
+}
+
+# Sets Kea logger to write to the file specified by the global value
+# ${LOG_FILE}.
+set_logger() {
+ if [ -z "${LOG_FILE+x}" ]; then
+ test_lib_error "set_logger requires LOG_FILE variable be set"
+ clean_exit 1
+ fi
+ printf 'Kea log will be stored in %s.\n' "${LOG_FILE}"
+ export KEA_LOGGER_DESTINATION=${LOG_FILE}
+}
+
+# Checks if specified process is running.
+#
+# This function uses PID file to obtain the PID and then calls
+# 'kill -0 <pid>' to check if the process is alive.
+# The PID files are expected to be located in the ${KEA_PIDFILE_DIR},
+# and their names should match the following pattern:
+# <cfg_file_name>.<proc_name>.pid. If the <cfg_file_name> is not
+# specified a 'test_config' is used by default.
+#
+# Return value:
+# _GET_PID: holds a PID if process is running
+# _GET_PIDS_NUM: holds 1 if process is running, 0 otherwise
+get_pid() {
+ local proc_name="${1-}" # Process name
+ local cfg_file_name="${2-}" # Configuration file name without extension.
+
+ # Reset PID results.
+ _GET_PID=0
+ _GET_PIDS_NUM=0
+
+ # PID file name includes process name. The process name is required.
+ if [ -z "${proc_name}" ]; then
+ test_lib_error "get_pid requires process name"
+ clean_exit 1
+ fi
+
+ # There are certain tests that intentionally run without a KEA_PIDFILE_DIR
+ # e.g. keactrl.status_test. We can't get the PID if KEA_PIDFILE_DIR is not
+ # defined. In this case, this function is reporting process not running
+ # (_GET_PID == 0).
+ if test -z "${KEA_PIDFILE_DIR+x}"; then
+ return
+ fi
+
+ # PID file name includes server configuration file name. For most of
+ # the tests it is 'test-config' (excluding .json extension). It is
+ # possible to specify custom name if required.
+ if [ -z "${cfg_file_name}" ]; then
+ cfg_file_name="test_config"
+ fi
+
+ # Get the absolute location of the PID file for the specified process
+ # name.
+ abs_pidfile_path="${KEA_PIDFILE_DIR}/${cfg_file_name}.${proc_name}.pid"
+
+ # If the PID file exists, get the PID and see if the process is alive.
+ pid=$(cat "${abs_pidfile_path}" 2> /dev/null || true)
+ if test -n "${pid}"; then
+ if kill -0 "${pid}" > /dev/null 2>&1; then
+ _GET_PID=${pid}
+ _GET_PIDS_NUM=1
+ fi
+ fi
+}
+
+# Get the name of the process identified by PID.
+get_process_name() {
+ local pid="${1-}"
+ if test -z "${pid}"; then
+ test_lib_error 'expected PID parameter in get_process_name'
+ clean_exit 1
+ fi
+
+ ps "${pid}" | tr -s ' ' | cut -d ' ' -f 6- | head -n 2 | tail -n 1
+}
+
+# Wait for file to be created.
+wait_for_file() {
+ local file="${1-}"
+ if test -z "${file}"; then
+ test_lib_error 'expected file parameter in wait_for_file'
+ clean_exit 1
+ fi
+
+ local timeout='4' # seconds
+ local deadline="$(($(date +%s) + timeout))"
+ while ! test -f "${file}"; do
+ if test "${deadline}" -lt "$(date +%s)"; then
+ # Time is up.
+ printf 'ERROR: file "%s" was not created in time.\n' "${file}" >&2
+ return 1
+ fi
+ printf 'Waiting for file "%s" to be created...\n' "${file}"
+ sleep 1
+ done
+}
+
+# Wait for process identified by PID to die.
+wait_for_process_to_stop() {
+ local pid="${1-}"
+ if test -z "${pid}"; then
+ test_lib_error 'expected PID parameter in wait_for_process_to_stop'
+ clean_exit 1
+ fi
+
+ local timeout='4' # seconds
+ local deadline="$(($(date +%s) + timeout))"
+ while ps "${pid}" >/dev/null; do
+ if test "${deadline}" -lt "$(date +%s)"; then
+ # Time is up.
+ printf 'ERROR: %s is not stopping.\n' "$(get_process_name "${pid}")" >&2
+ return 1
+ fi
+ printf 'Waiting for %s to stop...\n' "$(get_process_name "${pid}")"
+ sleep 1
+ done
+}
+
+# Kills processes specified by name.
+#
+# This function kills all processes having a specified name.
+# It uses 'pgrep' to obtain pids of those processes.
+# This function should be used when identifying process by
+# the value in its PID file is not relevant.
+#
+# Linux limitation for pgrep: The process name used for matching is
+# limited to the 15 characters. If you call this with long process
+# names, add this before pgrep:
+# proc_name=$(printf '%s' "${proc_name}" | cut -c1-15)
+kill_processes_by_name() {
+ local proc_name="${1-}" # Process name
+ if [ -z "${proc_name}" ]; then
+ test_lib_error "kill_processes_by_name requires process name"
+ clean_exit 1
+ fi
+
+ # Obtain PIDs of running processes.
+ local pids
+ pids=$(pgrep "${proc_name}" || true)
+ # For each PID found, send kill signal.
+ for pid in ${pids}; do
+ printf 'Shutting down Kea process %s with PID %d...\n' "${proc_name}" "${pid}"
+ kill -9 "${pid}" || true
+ done
+
+ # Wait for all processes to stop.
+ for pid in ${pids}; do
+ printf 'Waiting for Kea process %s with PID %d to stop...\n' "${proc_name}" "${pid}"
+ wait_for_process_to_stop "${pid}"
+ done
+}
+
+# Returns the number of occurrences of the Kea log message in the log file.
+# Return value:
+# _GET_LOG_MESSAGES: number of log message occurrences.
+get_log_messages() {
+ local msg="${1}" # Message id, e.g. DHCP6_SHUTDOWN
+ if [ -z "${msg}" ]; then
+ test_lib_error "get_log_messages require message identifier"
+ clean_exit 1
+ fi
+ _GET_LOG_MESSAGES=0
+ # If log file is not present, the number of occurrences is 0.
+ # Use ${var+x} to test if ${var} is defined.
+ if test -n "${LOG_FILE+x}" && test -s "${LOG_FILE}"; then
+ # Grep log file for the logger message occurrences and remove
+ # whitespaces, if any.
+ _GET_LOG_MESSAGES=$(grep -Fo "${msg}" "${LOG_FILE}" | wc -w | tr -d " ")
+ fi
+}
+
+# Returns the number of server configurations performed so far. Also
+# returns the number of configuration errors.
+# Return values:
+# _GET_RECONFIGS: number of configurations so far.
+# _GET_RECONFIG_ERRORS: number of configuration errors.
+get_reconfigs() {
+ # Grep log file for CONFIG_COMPLETE occurrences. There should
+ # be one occurrence per (re)configuration.
+ _GET_RECONFIGS=$(grep -Fo CONFIG_COMPLETE "${LOG_FILE}" | wc -w)
+ # Grep log file for CONFIG_LOAD_FAIL to check for configuration
+ # failures.
+ _GET_RECONFIG_ERRORS=$(grep -Fo CONFIG_LOAD_FAIL "${LOG_FILE}" | wc -w)
+ # Remove whitespaces
+ ${_GET_RECONFIGS##*[! ]}
+ ${_GET_RECONFIG_ERRORS##*[! ]}
+}
+
+# Remove the given directories or files if they exist.
+remove_if_exists() {
+ while test ${#} -gt 0; do
+ if test -e "${1}"; then
+ rm -rf "${1}"
+ fi
+ shift
+ done
+}
+
+# Performs cleanup after test.
+# It shuts down running Kea processes and removes temporary files.
+# The location of the log file and the configuration files should be set
+# in the ${LOG_FILE}, ${CFG_FILE} and ${KEACTRL_CFG_FILE} variables
+# respectively, prior to calling this function.
+cleanup() {
+ # If there is no KEA_PROCS set, just return
+ if [ -z "${KEA_PROCS}" ]; then
+ return
+ fi
+
+ # KEA_PROCS holds the name of all Kea processes. Shut down each
+ # of them if running.
+ for proc_name in ${KEA_PROCS}
+ do
+ get_pid "${proc_name}"
+ # Shut down running Kea process.
+ if [ "${_GET_PIDS_NUM}" -ne 0 ]; then
+ printf 'Shutting down Kea process having pid %d.\n' "${_GET_PID}"
+ kill -9 "${_GET_PID}"
+ fi
+ done
+
+ # Kill any running LFC processes. Even though 'kea-lfc' creates PID
+ # file we rather want to use 'pgrep' to find the process PID, because
+ # kea-lfc execution is not controlled from the test and thus there
+ # is possibility that process is already/still running but the PID
+ # file doesn't exist for it. As a result, the process will not
+ # be killed. This is not a problem for other processes because
+ # tests control launching them and monitor when they are shut down.
+ kill_processes_by_name "kea-lfc"
+
+ # Remove temporary files.
+ remove_if_exists \
+ "${CA_CFG_FILE-}" \
+ "${CFG_FILE-}" \
+ "${D2_CFG_FILE-}" \
+ "${DHCP4_CFG_FILE-}" \
+ "${KEA_DHCP4_LOAD_MARKER_FILE-}" \
+ "${KEA_DHCP4_UNLOAD_MARKER_FILE-}" \
+ "${KEA_DHCP4_SRV_CONFIG_MARKER_FILE-}" \
+ "${DHCP6_CFG_FILE-}" \
+ "${KEA_DHCP6_LOAD_MARKER_FILE-}" \
+ "${KEA_DHCP6_UNLOAD_MARKER_FILE-}" \
+ "${KEA_DHCP6_SRV_CONFIG_MARKER_FILE-}" \
+ "${KEACTRL_CFG_FILE-}" \
+ "${KEA_LOCKFILE_DIR-}" \
+ "${KEA_PIDFILE_DIR-}" \
+ "${NC_CFG_FILE-}"
+
+ # Use ${var+x} to test if ${var} is defined.
+ if test -n "${LOG_FILE+x}" && test -n "${LOG_FILE}"; then
+ rm -rf "${LOG_FILE}"
+ rm -rf "${LOG_FILE}.lock"
+ fi
+ # Use asterisk to remove all files starting with the given name,
+ # in case the LFC has been run. LFC creates files with postfixes
+ # appended to the lease file name.
+ if test -n "${LEASE_FILE+x}" && test -n "${LEASE_FILE}"; then
+ rm -rf "${LEASE_FILE}"*
+ fi
+}
+
+# Exists the test in the clean way.
+# It performs the cleanup and prints whether the test has passed or failed.
+# If a test fails, the Kea log is dumped.
+clean_exit() {
+ exit_code=${1-} # Exit code to be returned by the exit function.
+ case ${exit_code} in
+ ''|*[!0-9]*)
+ test_lib_error "argument passed to clean_exit must be a number" ;;
+ esac
+ # Print test result and perform a cleanup
+ test_finish "${exit_code}"
+ exit "${exit_code}"
+}
+
+# Starts Kea process in background using a configuration file specified
+# in the global variable ${CFG_FILE}.
+start_kea() {
+ local bin="${1-}"
+ if [ -z "${bin}" ]; then
+ test_lib_error "binary name must be specified for start_kea"
+ clean_exit 1
+ fi
+ printf "Running command %s.\n" "\"${bin} -c ${CFG_FILE}\""
+ "${bin}" -c "${CFG_FILE}" &
+}
+
+# Waits with timeout for Kea to start.
+# This function repeatedly checks if the Kea log file has been created
+# and is non-empty. If it is, the function assumes that Kea has started.
+# It doesn't check the contents of the log file though.
+# If the log file doesn't exist the function sleeps for a second and
+# checks again. This is repeated until timeout is reached or non-empty
+# log file is found. If timeout is reached, the function reports an
+# error.
+# Return value:
+# _WAIT_FOR_KEA: 0 if Kea hasn't started, 1 otherwise
+wait_for_kea() {
+ local timeout="${1-}" # Desired timeout in seconds.
+ if test -z "${timeout}"; then
+ test_lib_error 'expected timeout parameter in wait_for_kea'
+ clean_exit 1
+ fi
+ case ${timeout} in
+ ''|*[!0-9]*)
+ test_lib_error "argument passed to wait_for_kea must be a number"
+ clean_exit 1 ;;
+ esac
+ local loops=0 # Loops counter
+ _WAIT_FOR_KEA=0
+ test_lib_info "wait_for_kea " "skip-new-line"
+ while [ ! -s "${LOG_FILE}" ] && [ "${loops}" -le "${timeout}" ]; do
+ printf "."
+ sleep 1
+ loops=$(( loops + 1 ))
+ done
+ printf '\n'
+ if [ "${loops}" -le "${timeout}" ]; then
+ _WAIT_FOR_KEA=1
+ fi
+}
+
+# Waits for a specific message to occur in the Kea log file.
+# This function is called when the test expects specific message
+# to show up in the log file as a result of some action that has
+# been taken. Typically, the test expects that the message
+# is logged when the SIGHUP or SIGTERM signal has been sent to the
+# Kea process.
+# This function waits a specified number of seconds for the number
+# of message occurrences to show up. If the expected number of
+# message doesn't occur, the error status is returned.
+# Return value:
+# _WAIT_FOR_MESSAGE: 0 if the message hasn't occurred, 1 otherwise.
+wait_for_message() {
+ local timeout="${1-}" # Expected timeout value in seconds.
+ local message="${2-}" # Expected message id.
+ local occurrences="${3-}" # Number of expected occurrences.
+
+ # Validate timeout
+ case ${timeout} in
+ ''|*[!0-9]*)
+ test_lib_error "argument timeout passed to wait_for_message must \
+be a number"
+ clean_exit 1 ;;
+ esac
+
+ # Validate message
+ if [ -z "${message}" ]; then
+ test_lib_error "message id is a required argument for wait_for_message"
+ clean_exit 1
+ fi
+
+ # Validate occurrences
+ case ${occurrences} in
+ ''|*[!0-9]*)
+ test_lib_error "argument occurrences passed to wait_for_message \
+must be a number"
+ clean_exit 1 ;;
+ esac
+
+ local loops=0 # Number of loops performed so far.
+ _WAIT_FOR_MESSAGE=0
+ test_lib_info "wait_for_message ${message}: " "skip-new-line"
+ # Check if log file exists and if we reached timeout.
+ while [ "${loops}" -le "${timeout}" ]; do
+ printf "."
+ # Check if the message has been logged.
+ get_log_messages "${message}"
+ if [ "${_GET_LOG_MESSAGES}" -ge "${occurrences}" ]; then
+ printf '\n'
+ _WAIT_FOR_MESSAGE=1
+ return
+ fi
+ # Message not recorded. Keep going.
+ sleep 1
+ loops=$(( loops + 1 ))
+ done
+ printf '\n'
+ # Timeout.
+}
+
+# Waits for server to be down.
+# Return value:
+# _WAIT_FOR_SERVER_DOWN: 1 if server is down, 0 if timeout occurred and the
+# server is still running.
+wait_for_server_down() {
+ local timeout="${1-}" # Timeout specified in seconds.
+ local proc_name="${2-}" # Server process name.
+ if test -z "${proc_name}"; then
+ test_lib_error 'expected process name parameter in wait_for_server_down'
+ clean_exit 1
+ fi
+
+ case ${timeout} in
+ ''|*[!0-9]*)
+ test_lib_error "argument passed to wait_for_server_down must be a number"
+ clean_exit 1 ;;
+ esac
+ local loops=0 # Loops counter
+ _WAIT_FOR_SERVER_DOWN=0
+ test_lib_info "wait_for_server_down ${proc_name}: " "skip-new-line"
+ while [ "${loops}" -le "${timeout}" ]; do
+ printf "."
+ get_pid "${proc_name}"
+ if [ "${_GET_PIDS_NUM}" -eq 0 ]; then
+ printf '\n'
+ _WAIT_FOR_SERVER_DOWN=1
+ return
+ fi
+ sleep 1
+ loops=$(( loops + 1 ))
+ done
+ printf '\n'
+}
+
+# Sends specified signal to the Kea process.
+send_signal() {
+ local sig="${1-}" # Signal number.
+ local proc_name="${2-}" # Process name
+
+ # Validate signal
+ case ${sig} in
+ ''|*[!0-9]*)
+ test_lib_error "signal number passed to send_signal \
+must be a number"
+ clean_exit 1 ;;
+ esac
+ # Validate process name
+ if [ -z "${proc_name}" ]; then
+ test_lib_error "send_signal requires process name be passed as argument"
+ clean_exit 1
+ fi
+ # Get Kea pid.
+ get_pid "${proc_name}"
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one Kea process to be started.\
+ Found %d processes started.\n" ${_GET_PIDS_NUM}
+ clean_exit 1
+ fi
+ printf "Sending signal %s to Kea process (pid=%s).\n" "${sig}" "${_GET_PID}"
+ # Actually send a signal.
+ kill "-${sig}" "${_GET_PID}"
+}
+
+# Verifies that a server is up running by its PID file
+# The PID file is constructed from the given config file name and
+# binary name. If it exists and the PID it contains refers to a
+# live process it sets _SERVER_PID_FILE and _SERVER_PID to the
+# corresponding values. Otherwise, it emits an error and exits.
+verify_server_pid() {
+ local bin_name="${1-}" # binary name of the server
+ local cfg_file="${2-}" # config file name
+
+ # We will construct the PID file name based on the server config
+ # and binary name
+ if [ -z "${bin_name}" ]; then
+ test_lib_error "verify_server_pid requires binary name"
+ clean_exit 1
+ fi
+
+ if [ -z "${cfg_file}" ]; then
+ test_lib_error "verify_server_pid requires config file name"
+ clean_exit 1
+ fi
+
+ # Only the file name portion of the config file is used, try and
+ # extract it. NOTE if this "algorithm" changes this code will need
+ # to be updated.
+ fname=$(basename "${cfg_file}")
+ fname=$(echo "${fname}" | cut -f1 -d'.')
+
+ if [ -z "${fname}" ]; then
+ test_lib_error "verify_server_pid could not extract config name"
+ clean_exit 1
+ fi
+
+ # Now we can build the name:
+ pid_file="${KEA_PIDFILE_DIR}/${fname}.${bin_name}.pid"
+
+ if [ ! -e "${pid_file}" ]; then
+ printf "ERROR: PID file:[%s] does not exist\n" "${pid_file}"
+ clean_exit 1
+ fi
+
+ # File exists, does its PID point to a live process?
+ pid=$(cat "${pid_file}" 2> /dev/null || true)
+ if ! kill -0 "${pid}"; then
+ printf "ERROR: PID file:[%s] exists but PID:[%d] does not\n" \
+ "${pid_file}" "${pid}"
+ clean_exit 1
+ fi
+
+ # Make the values accessible to the caller
+ _SERVER_PID="${pid}"
+ _SERVER_PID_FILE="${pid_file}"
+}
+
+# This test verifies that the binary is reporting its version properly.
+version_test() {
+ test_name=${1} # Test name
+ long_version=${2-} # Test long version?
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+
+ # If set to anything other than empty string, reset it to the long version
+ # parameter.
+ if test -n "${long_version}"; then
+ long_version='--version'
+ fi
+
+ # Keep ${long_version} unquoted so that it is not included as an empty
+ # string if not given as argument.
+ for v in -v ${long_version}; do
+ run_command \
+ "${bin_path}/${bin}" "${v}"
+
+ if test "${OUTPUT}" != "${EXPECTED_VERSION}"; then
+ printf 'ERROR: Expected version "%s", got "%s" when calling "%s"\n' \
+ "${EXPECTED_VERSION}" "${OUTPUT}" "${bin} ${v}"
+ test_finish 1
+ fi
+ done
+
+ test_finish 0
+}
+
+# This test verifies that the server is using logger variable
+# KEA_LOCKFILE_DIR properly (it should be used to point out to the directory,
+# where lockfile should be created. Also, "none" value means to not create
+# the lockfile at all).
+logger_vars_test() {
+ test_name=${1} # Test name
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+
+ # Create bogus configuration file. We don't really want the server to start,
+ # just want it to log something and die. Empty config is an easy way to
+ # enforce that behavior.
+ create_config "{ }"
+ printf "Please ignore any config error messages.\n"
+
+ # Remember old KEA_LOCKFILE_DIR
+ KEA_LOCKFILE_DIR_OLD=${KEA_LOCKFILE_DIR}
+
+ # Set lockfile directory to current directory.
+ KEA_LOCKFILE_DIR=.
+
+ # Start Kea.
+ start_kea "${bin_path}/${bin}"
+
+ # Wait for Kea to process the invalid configuration and die.
+ sleep 1
+
+ # Check if it is still running. It should have terminated.
+ get_pid "${bin}"
+ if [ "${_GET_PIDS_NUM}" -ne 0 ]; then
+ printf 'ERROR: expected Kea process to not start. '
+ printf 'Found %d processes running.\n' "${_GET_PIDS_NUM}"
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+ clean_exit 1
+ fi
+
+ if [ ! -f "./logger_lockfile" ]; then
+ printf 'ERROR: Expect %s to create logger_lockfile in the ' "${bin}"
+ printf 'current directory, but no such file exists.\n'
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR__OLD}
+ clean_exit 1
+ fi
+
+ # Remove the lock file
+ rm -f ./logger_lockfile
+
+ # Tell Kea to NOT create logfiles at all
+ KEA_LOCKFILE_DIR="none"
+
+ # Start Kea.
+ start_kea "${bin_path}/${bin}"
+
+ # Wait for Kea to process the invalid configuration and die.
+ sleep 1
+
+ # Check if it is still running. It should have terminated.
+ get_pid "${bin}"
+ if [ "${_GET_PIDS_NUM}" -ne 0 ]; then
+ printf 'ERROR: expected Kea process to not start. '
+ printf 'Found %d processes running.\n' "${_GET_PIDS_NUM}"
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+
+ clean_exit 1
+ fi
+
+ if [ -f "./logger_lockfile" ]; then
+ printf 'ERROR: Expect %s to NOT create logger_lockfile in the ' "${bin}"
+ printf 'current directory, but the file exists.\n'
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+
+ clean_exit 1
+ fi
+
+ # Revert to the old KEA_LOCKFILE_DIR value
+ printf 'Reverting KEA_LOCKFILE_DIR to %s\n' "${KEA_LOCKFILE_DIR_OLD}"
+ KEA_LOCKFILE_DIR=${KEA_LOCKFILE_DIR_OLD}
+
+ test_finish 0
+}
+
+# This test verifies server PID file management
+# 1. It verifies that upon startup, the server creates a PID file
+# 2. It verifies the an attempt to start a second instance fails
+# due to pre-existing PID File/PID detection
+server_pid_file_test() {
+ local server_cfg="${1}"
+ local log_id="${2}"
+
+ # Log the start of the test and print test name.
+ test_start "${bin}.server_pid_file_test"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct server to log to the specific file.
+ set_logger
+ # Start server
+ start_kea "${bin_path}/${bin}"
+ # Wait up to 20s for server to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf 'ERROR: timeout waiting for %s to start.\n' "${bin}"
+ clean_exit 1
+ fi
+
+ # Verify server is still running
+ verify_server_pid "${bin}" "${CFG_FILE}"
+
+ printf 'PID file is [%s], PID is [%d]\n' "${_SERVER_PID_FILE}" "${_SERVER_PID}"
+
+ # Now try to start a second one
+ start_kea "${bin_path}/${bin}"
+
+ wait_for_message 10 "${log_id}" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf 'ERROR: Second %s instance started? ' "${bin}"
+ printf 'PID conflict not reported.\n'
+ clean_exit 1
+ fi
+
+ # Verify server is still running
+ verify_server_pid "${bin}" "${CFG_FILE}"
+
+ # All ok. Shut down the server and exit.
+ test_finish 0
+}
+
+# This test verifies that passwords are redacted in logs.
+# This function takes 2 parameters:
+# test_name
+# config - string with a content of the config (will be written to a file)
+# expected_code - expected exit code returned by kea (0 - success, 1 - failure)
+password_redact_test() {
+ local test_name="${1}"
+ local config="${2}"
+ local expected_code="${3}"
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create correct configuration file.
+ create_config "${config}"
+ # Instruct Control Agent to log to the specific file.
+ set_logger
+ # Check it
+ printf "Running command %s.\n" "\"${bin_path}/${bin} -d -t ${CFG_FILE}\""
+ run_command \
+ "${bin_path}/${bin}" -d -t "${CFG_FILE}"
+ if [ "${EXIT_CODE}" -ne "${expected_code}" ]; then
+ printf 'ERROR: expected exit code %s, got %s\n' "${expected_code}" "${EXIT_CODE}"
+ clean_exit 1
+ fi
+ if grep -q 'sensitive' "${LOG_FILE}"; then
+ printf "ERROR: sensitive is present in logs\n"
+ clean_exit 1
+ fi
+ if ! grep -q 'superadmin' "${LOG_FILE}"; then
+ printf "ERROR: superadmin is not present in logs\n"
+ clean_exit 1
+ fi
+ test_finish 0
+}
+
+# kea-dhcp[46] configuration with a password
+# used for redact tests:
+# - sensitive should be hidden
+# - superadmin should be visible
+kea_dhcp_config() {
+ printf '
+{
+ "Dhcp%s": {
+ "config-control": {
+ "config-databases": [
+ {
+ "password": "sensitive",
+ "type": "mysql",
+ "user": "keatest"
+ }
+ ]
+ },
+ "hooks-libraries": [
+ {
+ "library": "@abs_top_builddir@/src/bin/dhcp%s/tests/.libs/libco1.so",
+ "parameters": {
+ "password": "sensitive",
+ "user": "keatest",
+ "nested-map": {
+ "password": "sensitive",
+ "user": "keatest"
+ }
+ }
+ }
+ ],
+ "hosts-database": {
+ "password": "sensitive",
+ "type": "mysql",
+ "user": "keatest"
+ },
+ "lease-database": {
+ "password": "sensitive",
+ "type": "mysql",
+ "user": "keatest"
+ },
+ "user-context": {
+ "password": "superadmin",
+ "secret": "superadmin",
+ "shared-info": {
+ "password": "superadmin",
+ "secret": "superadmin"
+ }
+ }
+ }
+}
+' "${1}" "${1}"
+}
diff --git a/src/lib/testutils/gtest_utils.h b/src/lib/testutils/gtest_utils.h
new file mode 100644
index 0000000..e7b10b2
--- /dev/null
+++ b/src/lib/testutils/gtest_utils.h
@@ -0,0 +1,92 @@
+// Copyright (C) 2019 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 GTEST_UTILS_H
+#define GTEST_UTILS_H
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Verifies an expected exception type and message
+///
+/// If the statement does not generate the expected exception
+/// containing the expected message it will generate a non-fatal
+/// failure.
+///
+/// @param statement - statement block to execute
+/// @param etype - type of exception expected
+/// @param emsg - exact content expected to be returned by ex.what()
+#define EXPECT_THROW_MSG(statement,etype,emsg) \
+{ \
+ try { \
+ statement; \
+ ADD_FAILURE() << "no exception, expected: " << #etype; \
+ } catch (const etype& ex) { \
+ EXPECT_EQ(std::string(ex.what()), emsg); \
+ } catch (...) { \
+ ADD_FAILURE() << "wrong exception type thrown, expected: " << #etype; \
+ } \
+} \
+
+/// @brief Verifies an expected exception type and message
+///
+/// If the statement does not generate the expected exception
+/// containing the expected message it will generate a fatal
+/// failure.
+///
+/// @param statement - statement block to execute
+/// @param etype - type of exception expected
+/// @param emsg - exact content expected to be returned by ex.what()
+#define ASSERT_THROW_MSG(statement,etype,emsg) \
+{ \
+ try { \
+ statement; \
+ GTEST_FAIL() << "no exception, expected: " << #etype; \
+ } catch (const etype& ex) { \
+ ASSERT_EQ(std::string(ex.what()), emsg); \
+ } catch (...) { \
+ GTEST_FAIL() << "wrong exception type thrown, expected: " << #etype; \
+ } \
+} \
+
+/// @brief Adds a non-fatal failure with exception info, if the given
+/// expression throws. Note the type name emitted may be mangled.
+///
+/// @param statement - statement block to execute
+#define EXPECT_NO_THROW_LOG(statement) \
+{ \
+ try { \
+ statement; \
+ } catch (const std::exception& ex) { \
+ ADD_FAILURE() << #statement << " threw type: " << typeid(ex).name() \
+ << ", what: " << ex.what(); \
+ } catch (...) { \
+ ADD_FAILURE() << #statement << "threw non-std::exception"; \
+ } \
+} \
+
+/// @brief Generates a fatal failure with exception info, if the given
+/// expression throws. Note the type name emitted may be mangled.
+///
+/// @param statement - statement block to execute
+#define ASSERT_NO_THROW_LOG(statement) \
+{ \
+ try { \
+ statement; \
+ } catch (const std::exception& ex) { \
+ GTEST_FAIL() << #statement << " threw type: " << typeid(ex).name() \
+ << ", what: " << ex.what(); \
+ } catch (...) { \
+ GTEST_FAIL() << #statement << " threw non-std::exception"; \
+ } \
+} \
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // GTEST_UTILS_H
diff --git a/src/lib/testutils/io_utils.cc b/src/lib/testutils/io_utils.cc
new file mode 100644
index 0000000..4b08f16
--- /dev/null
+++ b/src/lib/testutils/io_utils.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2015-2017 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 <testutils/io_utils.h>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+namespace isc {
+namespace test {
+
+bool fileExists(const std::string& file_path) {
+ struct stat statbuf;
+ return(stat(file_path.c_str(), &statbuf) == 0);
+}
+
+std::string readFile(const std::string& file_path) {
+ std::ifstream ifs;
+ ifs.open(file_path.c_str(), std::ifstream::in);
+ if (!ifs.good()) {
+ return (std::string());
+ }
+ std::string buf;
+ std::ostringstream output;
+ while (!ifs.eof() && ifs.good()) {
+ ifs >> buf;
+ output << buf;
+ }
+ ifs.close();
+
+ return (output.str());
+}
+
+std::string decommentJSONfile(const std::string& input_file) {
+
+ using namespace std;
+
+ ifstream f(input_file);
+ if (!f.is_open()) {
+ isc_throw(isc::BadValue, "can't open input file for reading: " + input_file);
+ }
+
+ string outfile;
+ size_t last_slash = input_file.find_last_of("/");
+ if (last_slash != string::npos) {
+ outfile = input_file.substr(last_slash + 1);
+ } else {
+ outfile = input_file;
+ }
+ outfile += "-decommented";
+
+ ofstream out(outfile);
+ if (!out.is_open()) {
+ isc_throw(isc::BadValue, "can't open output file for writing: " + input_file);
+ }
+
+ bool in_comment = false;
+ string line;
+ while (std::getline(f, line)) {
+ // First, let's get rid of the # comments
+ size_t hash_pos = line.find("#");
+ if (hash_pos != string::npos) {
+ line = line.substr(0, hash_pos);
+ }
+
+ // Second, let's get rid of the // comments
+ // at the beginning or after a control character.
+ size_t dblslash_pos = line.find("//");
+ if ((dblslash_pos != string::npos) &&
+ ((dblslash_pos == 0) ||
+ ((unsigned) line[dblslash_pos - 1] <= 32))) {
+ line = line.substr(0, dblslash_pos);
+ }
+
+ // Now the tricky part: c comments.
+ size_t begin_pos = line.find("/*");
+ size_t end_pos = line.find("*/");
+ if (in_comment && end_pos == string::npos) {
+ // we continue through multiline comment
+ line = "";
+ } else {
+
+ if (begin_pos != string::npos) {
+ in_comment = true;
+ if (end_pos != string::npos) {
+ // single line comment. Let's get rid of the content in between
+ line = line.replace(begin_pos, end_pos + 2, end_pos + 2 - begin_pos, ' ');
+ in_comment = false;
+ } else {
+ line = line.substr(0, begin_pos);
+ }
+ } else {
+ if (in_comment && end_pos != string::npos) {
+ line = line.replace(0, end_pos +2 , end_pos + 2, ' ');
+ in_comment = false;
+ }
+ }
+ }
+
+ // Finally, write the line to the output file.
+ out << line << endl;
+ }
+ f.close();
+ out.close();
+
+ return (outfile);
+}
+
+}; // end of isc::test namespace
+}; // end of isc namespace
diff --git a/src/lib/testutils/io_utils.h b/src/lib/testutils/io_utils.h
new file mode 100644
index 0000000..58fd015
--- /dev/null
+++ b/src/lib/testutils/io_utils.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2015-2017 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 TEST_IO_UTILS_H
+#define TEST_IO_UTILS_H
+
+#include <string>
+#include <sys/stat.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Checks if specified file exists.
+///
+/// @param file_path Path to a file.
+/// @return true if the file exists, false otherwise.
+bool fileExists(const std::string& file_path);
+
+/// @brief Reads contents of the specified file.
+///
+/// @param file_path Path to a file.
+/// @return File contents.
+std::string readFile(const std::string& file_path);
+
+/// @brief Removes comments from a JSON file
+///
+/// Removes //, # and /* */ comments from the input file and writes its content
+/// to another file. The comments are replaced with spaces, so the original
+/// token locations should remain unaffected. This is rather naive
+/// implementation, but it's probably sufficient for testing. It won't be able
+/// to pick any trickier cases, like # or // appearing in strings, nested C++
+/// comments etc at the exception of // in URLs.
+///
+/// @param input_file file to be stripped of comments
+/// @return filename of a new file that has comments stripped from it
+/// @throw BadValue if the input file cannot be opened
+std::string decommentJSONfile(const std::string& input_file);
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // TEST_IO_UTILS_H
diff --git a/src/lib/testutils/lib_load_test_fixture.h b/src/lib/testutils/lib_load_test_fixture.h
new file mode 100644
index 0000000..182d0ef
--- /dev/null
+++ b/src/lib/testutils/lib_load_test_fixture.h
@@ -0,0 +1,148 @@
+// Copyright (C) 2022 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 ISC_TESTUTILS_LIB_LOAD_TEST_FIXTURE_H
+#define ISC_TESTUTILS_LIB_LOAD_TEST_FIXTURE_H
+
+#include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <process/daemon.h>
+#include <testutils/gtest_utils.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Test fixture for testing loading and unloading of hook libraries.
+class LibLoadTest : public ::testing::Test {
+public:
+ /// @brief Constructor. Unloads any previously loaded libraries.
+ ///
+ /// @param lib_so_name_ full pathname to the library so file under test
+ LibLoadTest(const std::string lib_so_name = "no-lib-specified")
+ : lib_so_name_(lib_so_name) {
+ unloadLibraries();
+ }
+
+ virtual void SetUp() {
+ valid_params_ = validConfigParams();
+ }
+
+ /// @brief Destructor. Unloads any previously loaded libraries.
+ ~LibLoadTest() {
+ unloadLibraries();
+ }
+
+ /// @brief Adds a library along with its parameters to the list of
+ /// libraries to be loaded.
+ ///
+ /// @param library the path to the library to be loaded
+ /// @param parameters the library's parameters in Element format
+ void addLibrary(const std::string& library,
+ isc::data::ConstElementPtr parameters) {
+ libraries_.push_back({library, parameters});
+ }
+
+ void clearLibraries() {
+ libraries_.clear();
+ }
+
+ /// @brief Load all libraries.
+ ///
+ /// @return true if all libraries loaded successfully, false if one or more
+ /// libraries failed to load.
+ bool loadLibraries() {
+ bool result(false);
+ EXPECT_NO_THROW(result = isc::hooks::HooksManager::loadLibraries(libraries_));
+ return result;
+ }
+
+ /// @brief Unloads all libraries.
+ ///
+ /// @return true if all libraries unloaded successfully, false if they
+ /// are still in memory.
+ bool unloadLibraries() {
+ bool result(false);
+ EXPECT_NO_THROW(result = isc::hooks::HooksManager::unloadLibraries());
+ return result;
+ }
+
+ /// @brief Verifies that a valid daemon can load and unload a
+ /// library multiple times.
+ ///
+ /// @param daemon_name name of the daemon that should try to load the library
+ /// @param family Protocol family of the loading daemon, either
+ /// AF_INET or AF_INET6. Defaults to AF_INET.
+ /// @param params ElementPtr to set of parameters that are valid for the library.
+ /// Defaults to an empty pointer.
+ ///
+ /// @note: implemented here to avoid dependency with the dhcpsrv library.
+ void validDaemonTest(const std::string& daemon_name,
+ uint16_t family = AF_INET,
+ const isc::data::ElementPtr& params = isc::data::ElementPtr()) {
+ // Set family and daemon's proc name.
+ isc::dhcp::CfgMgr::instance().setFamily(family);
+ isc::process::Daemon::setProcName(daemon_name);
+
+ clearLibraries();
+
+ // Adding the library to the list of libraries should work.
+ ASSERT_NO_THROW_LOG(addLibrary(lib_so_name_, params));
+
+ // Should be able to load and unload the library more than once.
+ ASSERT_NO_THROW_LOG(loadLibraries());
+ ASSERT_NO_THROW_LOG(unloadLibraries());
+
+ ASSERT_NO_THROW_LOG(loadLibraries());
+ ASSERT_NO_THROW_LOG(unloadLibraries());
+ }
+
+ /// @brief Verifies that an invalid daemon cannot load the library.
+ ///
+ /// @param libname full path to the library's SO. Typically this
+ /// value is defined in the Makefile (e.g. -DLIBDHCP_BOOTP_SO=...)
+ /// @param daemon_name name of the daemon that should try to load the library
+ /// @param family Protocol family of the loading daemon, either
+ /// AF_INET or AF_INET6. Defaults to AF_INET.
+ /// @param params ElementPtr to set of parameters that are valid
+ /// for the library. Defaults to an empty pointer.
+ ///
+ /// @note: implemented here to avoid dependency with the dhcpsrv library.
+ void invalidDaemonTest(const std::string& daemon_name,
+ uint16_t family = AF_INET,
+ const isc::data::ElementPtr& params = isc::data::ElementPtr()) {
+ // Set family and daemon's proc name.
+ isc::dhcp::CfgMgr::instance().setFamily(family);
+ isc::process::Daemon::setProcName(daemon_name);
+
+ clearLibraries();
+
+ // Adding the library to the list of libraries should work.
+ ASSERT_NO_THROW_LOG(addLibrary(lib_so_name_, params));
+
+ // Loading the library should fail.
+ ASSERT_FALSE(loadLibraries()) << "library: " << lib_so_name_
+ << ", should not have loaded for: " << daemon_name;
+ }
+
+ /// @brief Creates a set configuration parameters valid for the library.
+ virtual isc::data::ElementPtr validConfigParams() {
+ return (isc::data::Element::createMap());
+ }
+
+ /// @brief full pathname to the library so file under test;
+ std::string lib_so_name_;
+
+ /// @brief Libraries
+ isc::hooks::HookLibsCollection libraries_;
+
+ /// @brief Contains a set configuration parameters valid for the library.
+ isc::data::ElementPtr valid_params_;
+};
+
+} // namespace test
+} // namespace isc
+
+#endif // ISC_TESTUTILS_LIB_LOAD_TEST_FIXTURE_H
diff --git a/src/lib/testutils/log_utils.cc b/src/lib/testutils/log_utils.cc
new file mode 100644
index 0000000..56b6d33
--- /dev/null
+++ b/src/lib/testutils/log_utils.cc
@@ -0,0 +1,131 @@
+// Copyright (C) 2016-2021 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 <testutils/log_utils.h>
+#include <cstdlib>
+#include <iostream>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+LogContentTest::LogContentTest()
+ :verbose_(false) {
+ // Get rid of any old files
+ remFile();
+
+ // Set up the logger for use in checking the debug statements.
+ // We send the debug statements to a file which we can
+ // check after the evaluations have completed. We also
+ // set the log severity and debug levels so that the log
+ // statements are executed.
+ LoggerSpecification spec(getRootLoggerName(),
+ keaLoggerSeverity(isc::log::DEBUG),
+ keaLoggerDbglevel(isc::log::MAX_DEBUG_LEVEL));
+ OutputOption option;
+ option.destination = OutputOption::DEST_FILE;
+ option.filename = string(LogContentTest::LOG_FILE);
+ spec.addOutputOption(option);
+ LoggerManager manager;
+ manager.process(spec);
+
+ // Overwrite the verbose_ default is the KEA_LOG_CHECK_VERBOSE
+ // environment variable exists.
+ if (getenv(KEA_LOG_CHECK_VERBOSE)) {
+ verbose_ = true;
+ }
+}
+
+LogContentTest:: ~LogContentTest() {
+ remFile();
+}
+
+bool LogContentTest::checkFile() {
+ ifstream file(LOG_FILE);
+ EXPECT_TRUE(file.is_open());
+ string line, exp_line;
+ int i = 0;
+ bool found = true;
+
+ using namespace std;
+
+ while (getline(file, line) && (i != exp_strings_.size())) {
+ exp_line = exp_strings_[i];
+ if (verbose_) {
+ cout << "Read line : " << line << endl;
+ cout << "Looking for: " << exp_line << endl;
+ }
+ i++;
+ if (string::npos == line.find(exp_line)) {
+ if (verbose_) {
+ cout << "Verdict : not found" << endl;
+ }
+ found = false;
+ }
+ }
+
+ file.close();
+
+ if ((i != exp_strings_.size()) || (found == false)) {
+ if (verbose_) {
+ cout << "Final verdict: false" << endl;
+ }
+ return (false);
+ }
+
+ return (true);
+}
+
+size_t LogContentTest::countFile(const string& exp_string) {
+ ifstream file(LOG_FILE);
+ EXPECT_TRUE(file.is_open());
+ string line;
+ size_t cnt = 0;
+
+ using namespace std;
+
+ if (verbose_) {
+ cout << "Looking for:" << exp_string << endl;
+ }
+ while (getline(file, line)) {
+ if (verbose_) {
+ cout << "Read line :" << line << endl;
+ }
+ if (line.find(exp_string) != string::npos) {
+ ++cnt;
+ }
+ }
+
+ file.close();
+
+ if (verbose_) {
+ cout << "Final count: " << cnt << endl;
+ }
+
+ return (cnt);
+}
+
+void LogContentTest::remFile() {
+ static_cast<void>(remove(LOG_FILE));
+}
+
+void LogContentTest::addString(const string& new_string) {
+ exp_strings_.push_back(new_string);
+}
+
+// Set up the name of the LOG_FILE for use in checking
+// the debug statements.
+// Must not be the same file name used by test shell scripts.
+const char* LogContentTest::LOG_FILE = "logtest.log";
+
+// The environment variable to overwrite the verbose_ default value.
+const char* LogContentTest::KEA_LOG_CHECK_VERBOSE = "KEA_LOG_CHECK_VERBOSE";
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/testutils/log_utils.h b/src/lib/testutils/log_utils.h
new file mode 100644
index 0000000..7191e0e
--- /dev/null
+++ b/src/lib/testutils/log_utils.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2016-2022 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 TEST_LOG_UTILS_H
+#define TEST_LOG_UTILS_H
+
+#include <string>
+#include <fstream>
+
+//#include <config.h>
+
+#include <log/logger_manager.h>
+#include <log/logger_name.h>
+#include <log/logger_support.h>
+
+//#include <boost/shared_ptr.hpp>
+//#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::log;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test class for testing things that emit log entries
+///
+/// This class provides a convenient method for testing out
+/// things that emit log entries. The class sets up the logging
+/// to log everything into a file and provides a routine to
+/// check the expected strings vs the actual log entries.
+/// The user needs to call the addString function for each of
+/// the strings they expect in the output in the order they
+/// will be emitted.
+
+class LogContentTest : public ::testing::Test {
+public:
+
+ /// @brief Initializes the logger setup for using
+ /// in checking log statements
+ ///
+ /// @todo add support to adjust the severity and debug level
+ /// to allow for better control over the statements that
+ /// get logged.
+ LogContentTest();
+
+ virtual ~LogContentTest();
+
+ /// @brief check that the requested strings are in the
+ /// test log file in the requested order.
+ ///
+ /// This routine expects that the caller has properly
+ /// set up the vector of expected strings by calling
+ /// addString() with the necessary strings.
+ ///
+ /// @return true if all of the strings match
+ bool checkFile();
+
+ /// @brief check that the requested string is in the
+ /// test log file.
+ ///
+ /// @param exp_string the string to be searched
+ /// @return count of matching lines
+ size_t countFile(const string& exp_string);
+
+ /// @brief remove the test log file
+ void remFile();
+
+ /// @brief Enables or disables verbose mode.
+ ///
+ /// See @ref verbose_ for details.
+ ///
+ /// @param talk_a_lot (true - as the name says, false - shut up)
+ void logCheckVerbose(bool talk_a_lot) {
+ verbose_ = talk_a_lot;
+ }
+
+ /// @brief Add a string to the vector of expected strings
+ ///
+ /// @param new_string the string to add to the end of the vector
+ void addString(const string& new_string);
+
+ vector<string> exp_strings_;
+ static const char* LOG_FILE;
+ static const char* KEA_LOG_CHECK_VERBOSE;
+
+ /// @brief controls whether the checkFile() should print more details.
+ ///
+ /// If set to true, checkFile() will print each expected line, each
+ /// logged line and will print out a failure message if those two do
+ /// not match. Also, a final verdict is printed. Everything is printed
+ /// on stdout.
+ /// The default is false but can be overwritten by setting the
+ /// KEA_LOG_CHECK_VERBOSE environment variable.
+ bool verbose_;
+};
+
+} // end of isc::dhcp::test namespace
+} // end of isc::dhcp namespace
+} // end of isc namespace
+#endif // TEST_LOG_UTILS_H
diff --git a/src/lib/testutils/multi_threading_utils.h b/src/lib/testutils/multi_threading_utils.h
new file mode 100644
index 0000000..8754208
--- /dev/null
+++ b/src/lib/testutils/multi_threading_utils.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2020 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 MULTI_THREADING_UTILS_H
+#define MULTI_THREADING_UTILS_H
+
+#include <util/multi_threading_mgr.h>
+
+namespace isc {
+namespace test {
+
+/// @brief A RAII class which disables the multi threading on exit of scope.
+///
+/// Usually the multi threading is disabled by the fixture destructor or
+/// TearDown but of course this works only when a fixture class is used.
+class MultiThreadingTest {
+public:
+
+ /// @brief Constructor (set multi threading mode).
+ ///
+ /// @param mode The mode to use in the body. Defaults to true / enabled.
+ MultiThreadingTest(bool mode = true) {
+ isc::util::MultiThreadingMgr::instance().setMode(mode);
+ }
+
+ /// @brief Destructor (disable multi threading).
+ ~MultiThreadingTest() {
+ isc::util::MultiThreadingMgr::instance().setMode(false);
+ }
+};
+
+} // end of isc::test namespace
+} // end of isc namespace
+
+#endif // MULTI_THREADING_UTILS_H
diff --git a/src/lib/testutils/sandbox.h b/src/lib/testutils/sandbox.h
new file mode 100644
index 0000000..8dfb572
--- /dev/null
+++ b/src/lib/testutils/sandbox.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2019,2021 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 SANDBOX_H
+#define SANDBOX_H
+
+#include <exceptions/exceptions.h>
+
+#include <iostream>
+#include <string>
+#include <cstdlib>
+#include <cstdio>
+#include <unistd.h>
+#include <ftw.h>
+
+namespace isc {
+namespace test {
+
+/// @brief A Sandbox class that provides access to unit test unique
+/// temporary folder.
+///
+/// The sandbox's temporary folder is created in constructor ie.
+/// in unit test setup phase, and then it is deleted with its content
+/// in destructor ie. in unit test tear down phase.
+class Sandbox {
+private:
+ /// Path to temporary folder
+ std::string path_;
+
+ /// @brief Method for deleting files and folders, used in nftw traversal function.
+ ///
+ /// @param fpath path to the file to be removed.
+ static int rmFile(const char *fpath, const struct stat *, int , struct FTW *) {
+ return(remove(fpath));
+ }
+
+public:
+ /// @brief Sandbox constructor.
+ Sandbox() {
+ char tmpl[] = {P_tmpdir "/kea-XXXXXX"};
+ path_ = mkdtemp(tmpl);
+ }
+
+ /// @brief Destructor, it deletes temporary folder with its content.
+ ~Sandbox() {
+ // Delete content of path_ recursively.
+ if (nftw(path_.c_str(), Sandbox::rmFile, 10, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0) {
+ auto msg = "Some error occurred while deleting unit test sandbox " + path_;
+ std::perror(msg.c_str());
+ exit(1);
+ }
+ }
+
+ /// @brief Join sandbox path with indicated file subpath.
+ ///
+ /// @param file path to file that should be joined to base path of sandbox.
+ std::string join(std::string file) {
+ return (path_ + "/" + file);
+ }
+};
+
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // SANDBOX_H
diff --git a/src/lib/testutils/test_to_element.cc b/src/lib/testutils/test_to_element.cc
new file mode 100644
index 0000000..115757a
--- /dev/null
+++ b/src/lib/testutils/test_to_element.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 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 <testutils/test_to_element.h>
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace test {
+
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+std::string generateDiff(std::string left, std::string right) {
+ std::vector<std::string> left_lines;
+ boost::split(left_lines, left, boost::is_any_of("\n"));
+ std::vector<std::string> right_lines;
+ boost::split(right_lines, right, boost::is_any_of("\n"));
+ using namespace testing::internal;
+ return (edit_distance::CreateUnifiedDiff(left_lines, right_lines));
+}
+#else
+std::string generateDiff(std::string, std::string) {
+ return ("");
+}
+#endif
+
+}; // end of isc::test namespace
+}; // end of isc namespace
diff --git a/src/lib/testutils/test_to_element.h b/src/lib/testutils/test_to_element.h
new file mode 100644
index 0000000..1aa88fc
--- /dev/null
+++ b/src/lib/testutils/test_to_element.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2017 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 TEST_TO_ELEMENT_H
+#define TEST_TO_ELEMENT_H
+
+#include <cc/data.h>
+#include <cc/cfg_to_element.h>
+#include <gtest/gtest.h>
+#include <string>
+#ifdef HAVE_IS_BASE_OF
+#include <type_traits>
+#endif
+
+#ifndef CONFIG_H_WAS_INCLUDED
+#error config.h must be included before test_to_element.h
+#endif
+
+namespace isc {
+namespace test {
+
+/// @brief Return the difference between two strings
+///
+/// Use the gtest >= 1.8.0 tool which builds the difference between
+/// two vectors of lines.
+///
+/// @param left left string
+/// @param right right string
+/// @return the unified diff between left and right
+std::string generateDiff(std::string left, std::string right);
+
+/// @brief Run a test using toElement() method with a string
+///
+/// @tparam Cfg the class implementing the toElement() method
+/// @param expected the expected textual value
+/// @param cfg an instance of the Cfg class
+template<typename Cfg>
+void runToElementTest(const std::string& expected, const Cfg& cfg) {
+ using namespace isc::data;
+#ifdef HAVE_IS_BASE_OF
+ static_assert(std::is_base_of<CfgToElement, Cfg>::value,
+ "CfgToElement is not a base of the template parameter");
+#endif
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(expected)) << expected;
+ ConstElementPtr unparsed;
+ ASSERT_NO_THROW(unparsed = cfg.toElement());
+ if (!isEquivalent(json, unparsed)) {
+ std::string wanted = prettyPrint(json);
+ std::string got = prettyPrint(unparsed);
+ ADD_FAILURE() << "Expected:\n" << wanted << "\n"
+ << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+ << "\n";
+ }
+}
+
+/// @brief Run a test using toElement() method with an Element
+///
+/// @tparam Cfg the class implementing the toElement() method
+/// @param expected the expected element value
+/// @param cfg an instance of the Cfg class
+template<typename Cfg>
+void runToElementTest(isc::data::ConstElementPtr expected, const Cfg& cfg) {
+#ifdef HAVE_IS_BASE_OF
+ static_assert(std::is_base_of<isc::data::CfgToElement, Cfg>::value,
+ "CfgToElement is not a base of the template parameter");
+#endif
+ isc::data::ConstElementPtr unparsed;
+ ASSERT_NO_THROW(unparsed = cfg.toElement());
+ if (!isEquivalent(expected, unparsed)) {
+ std::string wanted = prettyPrint(expected);
+ std::string got = prettyPrint(unparsed);
+ ADD_FAILURE() << "Expected:\n" << wanted << "\n"
+ << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+ << "\n";
+ }
+}
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // TEST_TO_ELEMENT_H
diff --git a/src/lib/testutils/threaded_test.cc b/src/lib/testutils/threaded_test.cc
new file mode 100644
index 0000000..88cb846
--- /dev/null
+++ b/src/lib/testutils/threaded_test.cc
@@ -0,0 +1,73 @@
+// Copyright (C) 2018-2020 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 <testutils/threaded_test.h>
+
+namespace isc {
+namespace test {
+
+ThreadedTest::ThreadedTest()
+ : thread_(), condvar_(), ready_(false), stopping_(false),
+ stopped_(false) {
+}
+
+void
+ThreadedTest::doSignal(bool& flag) {
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ flag = true;
+ }
+ condvar_.notify_one();
+}
+
+void
+ThreadedTest::signalReady() {
+ doSignal(ready_);
+}
+
+void
+ThreadedTest::signalStopping() {
+ doSignal(stopping_);
+}
+
+void
+ThreadedTest::signalStopped() {
+ doSignal(stopped_);
+}
+
+void
+ThreadedTest::doWait(bool& flag) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ while (!flag) {
+ condvar_.wait(lock);
+ }
+}
+
+void
+ThreadedTest::waitReady() {
+ doWait(ready_);
+}
+
+void
+ThreadedTest::waitStopping() {
+ doWait(stopping_);
+}
+
+void
+ThreadedTest::waitStopped() {
+ doWait(stopped_);
+}
+
+bool
+ThreadedTest::isStopping() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (stopping_);
+}
+
+} // end of namespace isc::test
+} // end of namespace isc
diff --git a/src/lib/testutils/threaded_test.h b/src/lib/testutils/threaded_test.h
new file mode 100644
index 0000000..6f430e4
--- /dev/null
+++ b/src/lib/testutils/threaded_test.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2018-2019,2021 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 THREADED_TEST_H
+#define THREADED_TEST_H
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
+namespace isc {
+namespace test {
+
+/// @brief Base class for tests requiring threads.
+///
+/// This class contains 3 flags to signal when the thread has
+/// started, is stopping and when it is stopped. The flags
+/// are accessed in thread safe manner.
+class ThreadedTest : public ::testing::Test {
+protected:
+
+ /// @brief Constructor.
+ ThreadedTest();
+
+ /// @brief Sets selected flag to true and signals condition
+ /// variable.
+ ///
+ /// @param flag Reference to flag which should be set to true.
+ void doSignal(bool& flag);
+
+ /// @brief Signal that thread is ready.
+ void signalReady();
+
+ /// @brief Signal that thread is stopping.
+ void signalStopping();
+
+ /// @brief Signal that thread is stopped.
+ void signalStopped();
+
+ /// @brief Wait for a selected flag to be set.
+ ///
+ /// @param flag Reference to a flag on which the thread is
+ /// waiting.
+ void doWait(bool& flag);
+
+ /// @brief Wait for the thread to be ready.
+ void waitReady();
+
+ /// @brief Wait for the thread to be stopping.
+ void waitStopping();
+
+ /// @brief Wait for the thread to stop.
+ void waitStopped();
+
+ /// @brief Checks if the thread is stopping.
+ ///
+ /// @return true if the thread is stopping, false otherwise.
+ bool isStopping();
+
+ /// @brief Pointer to server thread.
+ boost::shared_ptr<std::thread> thread_;
+
+ /// @brief Mutex used to synchronize threads.
+ std::mutex mutex_;
+
+ /// Conditional variable for thread waits.
+ std::condition_variable condvar_;
+
+ /// Flag indicating that the thread is ready.
+ bool ready_;
+
+ /// Flag indicating that the thread is stopping.
+ bool stopping_;
+
+ /// Flag indicating that the thread is stopped.
+ bool stopped_;
+};
+
+
+} // end of namespace isc::test
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/testutils/unix_control_client.cc b/src/lib/testutils/unix_control_client.cc
new file mode 100644
index 0000000..f0f8dfa
--- /dev/null
+++ b/src/lib/testutils/unix_control_client.cc
@@ -0,0 +1,139 @@
+// Copyright (C) 2015-2019 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 <testutils/unix_control_client.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <string.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+UnixControlClient::UnixControlClient() {
+ socket_fd_ = -1;
+}
+
+UnixControlClient::~UnixControlClient() {
+ disconnectFromServer();
+}
+
+ /// @brief Closes the Control Channel socket
+void UnixControlClient::disconnectFromServer() {
+ if (socket_fd_ >= 0) {
+ static_cast<void>(close(socket_fd_));
+ socket_fd_ = -1;
+ }
+}
+
+bool UnixControlClient::connectToServer(const std::string& socket_path) {
+ // Create UNIX socket
+ socket_fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socket_fd_ < 0) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to open unix stream socket: " << errmsg;
+ return (false);
+ }
+
+ struct sockaddr_un srv_addr;
+ if (socket_path.size() > sizeof(srv_addr.sun_path) - 1) {
+ ADD_FAILURE() << "Socket path specified (" << socket_path
+ << ") is larger than " << (sizeof(srv_addr.sun_path) - 1)
+ << " allowed.";
+ disconnectFromServer();
+ return (false);
+ }
+
+ // Prepare socket address
+ memset(&srv_addr, 0, sizeof(srv_addr));
+ srv_addr.sun_family = AF_UNIX;
+ strncpy(srv_addr.sun_path, socket_path.c_str(),
+ sizeof(srv_addr.sun_path) - 1);
+ socklen_t len = sizeof(srv_addr);
+
+ // Connect to the specified UNIX socket
+ int status = connect(socket_fd_, (struct sockaddr*)&srv_addr, len);
+ if (status == -1) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to connect unix socket: fd=" << socket_fd_
+ << ", path=" << socket_path << " : " << errmsg;
+ disconnectFromServer();
+ return (false);
+ }
+
+ return (true);
+}
+
+bool UnixControlClient::sendCommand(const std::string& command) {
+ // Send command
+ int bytes_sent = send(socket_fd_, command.c_str(), command.length(), 0);
+ if (bytes_sent < command.length()) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to send " << command.length()
+ << " bytes, send() returned " << bytes_sent
+ << " : " << errmsg;
+ return (false);
+ }
+
+ return (true);
+}
+
+bool UnixControlClient::getResponse(std::string& response,
+ const unsigned int timeout_sec) {
+ // Receive response
+ char buf[65536];
+ memset(buf, 0, sizeof(buf));
+ switch (selectCheck(timeout_sec)) {
+ case -1: {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "getResponse - select failed: " << errmsg;
+ return (false);
+ }
+ case 0:
+ return (false);
+
+ default:
+ break;
+ }
+
+ int bytes_rcvd = recv(socket_fd_, buf, sizeof(buf), 0);
+ if (bytes_rcvd < 0) {
+ const char* errmsg = strerror(errno);
+ ADD_FAILURE() << "Failed to receive a response. recv() returned "
+ << bytes_rcvd << " : " << errmsg;
+ return (false);
+ }
+
+ // Convert the response to a string
+ response = std::string(buf, bytes_rcvd);
+ return (true);
+}
+
+int UnixControlClient::selectCheck(const unsigned int timeout_sec) {
+ int maxfd = 0;
+
+ fd_set read_fds;
+ FD_ZERO(&read_fds);
+
+ // Add this socket to listening set
+ FD_SET(socket_fd_, &read_fds);
+ maxfd = socket_fd_;
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = static_cast<time_t>(timeout_sec);
+ select_timeout.tv_usec = 0;
+
+ return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+};
+};
+};
diff --git a/src/lib/testutils/unix_control_client.h b/src/lib/testutils/unix_control_client.h
new file mode 100644
index 0000000..225c949
--- /dev/null
+++ b/src/lib/testutils/unix_control_client.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2015-2017 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 UNIX_CONTROL_CLIENT_H
+#define UNIX_CONTROL_CLIENT_H
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Class that acts as a UnixCommandSocket client
+///
+/// This class is expected to be used unit-tests that attempt to communicate
+/// with the servers that use control channel (see src/lib/config/command_mgr.h)
+/// It can connect to an open UnixCommandSocket and exchange ControlChannel
+/// commands and responses.
+class UnixControlClient {
+public:
+
+ /// @brief Default constructor
+ UnixControlClient();
+
+ /// @brief Destructor
+ ~UnixControlClient();
+
+ /// @brief Closes the Control Channel socket
+ void disconnectFromServer();
+
+ /// @brief Connects to a Unix socket at the given path
+ /// @param socket_path pathname of the socket to open
+ /// @return true if the connect was successful, false otherwise
+ bool connectToServer(const std::string& socket_path);
+
+ /// @brief Sends the given command across the open Control Channel
+ /// @param command the command text to execute in JSON form
+ /// @return true if the send succeeds, false otherwise
+ bool sendCommand(const std::string& command);
+
+ /// @brief Reads the response text from the open Control Channel
+ /// @param response variable into which the received response should be
+ /// placed.
+ /// @param timeout_sec Timeout for receiving response in seconds.
+ /// @return true if data was successfully read from the socket,
+ /// false otherwise
+ bool getResponse(std::string& response, const unsigned int timeout_sec = 0);
+
+ /// @brief Uses select to poll the Control Channel for data waiting
+ ///
+ /// @param timeout_sec Select timeout in seconds
+ /// @return -1 on error, 0 if no data is available, 1 if data is ready
+ int selectCheck(const unsigned int timeout_sec);
+
+ /// @brief Retains the fd of the open socket
+ int socket_fd_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // UNIX_CONTROL_CLIENT_H
diff --git a/src/lib/testutils/user_context_utils.cc b/src/lib/testutils/user_context_utils.cc
new file mode 100644
index 0000000..bad26bc
--- /dev/null
+++ b/src/lib/testutils/user_context_utils.cc
@@ -0,0 +1,137 @@
+// Copyright (C) 2017-2020 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 <testutils/user_context_utils.h>
+
+using namespace isc::data;
+
+namespace {
+
+/// @brief Encapsulate either a modified copy or a unmodified value
+/// @tparam EP ElementPtr or ConstElementPtr (compiler can't infer which one)
+template<typename EP>
+class Value {
+public:
+ /// @brief Factory for modified copy
+ static Value mkCopy(EP value) { return (Value(value, false)); }
+
+ /// @brief Factory for unmodified original
+ static Value mkShare(EP value) { return (Value(value, true)); }
+
+ /// @brief Get the value
+ /// @return the value
+ EP get() const { return (value_); }
+
+ /// @brief Get the shared status
+ /// @return true if original, false if copy
+ bool isShared() const { return (shared_); }
+
+private:
+ /// @brief Constructor
+ /// @param value the modified copy or unmodified value
+ /// @param shared true if original, false if copy
+ Value(EP value, bool shared) : value_(value), shared_(shared) { }
+
+ /// @brief the value
+ EP value_;
+
+ /// @brief the shared status
+ bool shared_;
+};
+
+/// @brief Recursive helper
+///
+/// @tparam EP ElementPtr or ConstElementPtr (compiler will infer which one)
+/// @param element the element to traverse
+/// @return a modified copy where comment entries were moved to user-context
+/// or the unmodified original argument encapsulated into a Value
+template<typename EP>
+Value<EP> moveComments1(EP element) {
+ bool modified = false;
+
+ // On lists recurse on items
+ if (element->getType() == Element::list) {
+ ElementPtr result = ElementPtr(new ListElement());
+ typedef std::vector<ElementPtr> ListType;
+ const ListType& list = element->listValue();
+ for (ListType::const_iterator it = list.cbegin();
+ it != list.cend(); ++it) {
+ Value<ElementPtr> item = moveComments1(*it);
+ result->add(item.get());
+ if (!item.isShared()) {
+ modified = true;
+ }
+ }
+ if (!modified) {
+ return (Value<EP>::mkShare(element));
+ } else {
+ return (Value<EP>::mkCopy(result));
+ }
+ } else if (element->getType() != Element::map) {
+ return (Value<EP>::mkShare(element));
+ }
+
+ // Process maps: recurse on items
+ ElementPtr result = ElementPtr(new MapElement());
+ bool has_comment = false;
+ typedef std::map<std::string, ConstElementPtr> map_type;
+ const map_type& map = element->mapValue();
+ for (map_type::const_iterator it = map.cbegin(); it != map.cend(); ++it) {
+ if (it->first == "comment") {
+ // Note there is a comment entry to move
+ has_comment = true;
+ } else if (it->first == "user-context") {
+ // Do not traverse user-context entries
+ result->set("user-context", it->second);
+ } else {
+ // Not comment or user-context
+ Value<ConstElementPtr> item = moveComments1(it->second);
+ result->set(it->first, item.get());
+ if (!item.isShared()) {
+ modified = true;
+ }
+ }
+ }
+ // Check if the value should be not modified
+ if (!has_comment && !modified) {
+ return (Value<EP>::mkShare(element));
+ }
+
+ if (has_comment) {
+ // Move the comment entry
+ ConstElementPtr comment = element->get("comment");
+ ElementPtr moved = Element::createMap();
+ moved->set("comment", comment);
+ ConstElementPtr previous = element->get("user-context");
+ // If there is already a user context merge it
+ if (previous) {
+ merge(moved, previous);
+ }
+ result->set("user-context", moved);
+ }
+
+ return (Value<EP>::mkCopy(result));
+}
+
+} // anonymous namespace
+
+namespace isc {
+namespace test {
+
+ElementPtr moveComments(ElementPtr element) {
+ Value<ElementPtr> result = moveComments1(element);
+ return (result.get());
+}
+
+ConstElementPtr moveComments(ConstElementPtr element) {
+ Value<ConstElementPtr> result = moveComments1(element);
+ return (result.get());
+}
+
+} // end of isc::test namespace
+} // end of isc namespace
diff --git a/src/lib/testutils/user_context_utils.h b/src/lib/testutils/user_context_utils.h
new file mode 100644
index 0000000..3a04f41
--- /dev/null
+++ b/src/lib/testutils/user_context_utils.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2017-2020 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 USER_CONTEXT_UTILS_H
+#define USER_CONTEXT_UTILS_H
+
+#include <cc/data.h>
+
+namespace isc {
+namespace test {
+
+/// @brief Move comment entries to user-context
+///
+/// Process an element looking for comment entries in maps and
+/// moving them to user-context entries. As the common case is
+/// no comment and this routine tries to maximize sharing the
+/// standard behavior is just to return the argument unchanged.
+///
+/// @param element
+/// @return a processed copy of element or unmodified element
+isc::data::ElementPtr moveComments(isc::data::ElementPtr element);
+
+/// @brief Move comment entries to user-context (const variant)
+///
+/// @param element
+/// @return a processed copy of element or unmodified element
+isc::data::ConstElementPtr moveComments(isc::data::ConstElementPtr element);
+
+/// extractComments was removed.
+
+}; // end of isc::test namespace
+}; // end of isc namespace
+
+#endif // USER_CONTEXT_UTILS_H
diff --git a/src/lib/testutils/xml_reporting_test_lib.sh.in b/src/lib/testutils/xml_reporting_test_lib.sh.in
new file mode 100644
index 0000000..7c6dc0f
--- /dev/null
+++ b/src/lib/testutils/xml_reporting_test_lib.sh.in
@@ -0,0 +1,353 @@
+#!/bin/sh
+
+# Copyright (C) 2020-2022 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/.
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+############################### Public functions ###############################
+
+# Add an entry to the XML test report.
+report_test_result_in_xml() {
+ # If GTEST_OUTPUT is not defined...
+ if ! test -n "${GTEST_OUTPUT+x}"; then
+ # There is nowhere to report.
+ return
+ fi
+
+ # Declarations
+ local test_name="${1}"; shift
+ local exit_code="${1}"; shift
+ local duration="${1}"; shift # milliseconds
+ local now
+ local test_case
+ local test_suite
+ local xml
+ now=$(date '+%FT%H:%M:%S')
+ test_suite=$(printf '%s' "${test_name}" | cut -d '.' -f 1)
+ test_case=$(printf '%s' "${test_name}" | cut -d '.' -f 2-)
+
+ # Strip the 'xml:' at the start of GTEST_OUTPUT if it is there.
+ xml="${GTEST_OUTPUT}"
+ if test "$(printf '%s' "${xml}" | cut -c 1-4)" = 'xml:'; then
+ xml=$(printf '%s' "${xml}" | cut -c 5-)
+ fi
+ xml="${xml}/${test_suite}.sh.xml"
+
+ # Convert to seconds, but keep the millisecond precision.
+ duration=$(_calculate "${duration} / 1000.0")
+
+ # For test suites that have a single test case and no name for the test
+ # case, name the test case after the test suite.
+ if test -z "${test_case}"; then
+ test_case="${test_suite}"
+ fi
+
+ # Determine result based on exit code. Googletest seems to omit the failed
+ # tests, instead we are explicitly adding them with a 'failed' result.
+ local result
+ if test "${exit_code}" -eq 0; then
+ result='success'
+ else
+ result='failed'
+ fi
+
+ _create_xml "${xml}" "${now}"
+
+ _add_test_suite "${test_suite}" "${xml}" "${now}"
+
+ _add_test_case "${test_suite}" "${test_case}" "${result}" "${duration}" \
+ "${xml}" "${now}"
+}
+
+############################## Private functions ###############################
+
+# Add ${string} after ${reference} in ${file}.
+_add_after() {
+ local string="${1}"; shift
+ local reference="${1}"; shift
+ local file="${1}"; shift
+
+ # Escape all slashes.
+ string=$(printf '%s' "${string}" | sed 's#\/#\\\/#g')
+ reference=$(printf '%s' "${reference}" | sed 's#\/#\\\/#g')
+
+ # Escape all spaces. Only trailing spaces need escaped, but that's harder
+ # and this still empirically works.
+ string=$(printf '%s' "${string}" | sed 's#\ #\\\ #g')
+ reference=$(printf '%s' "${reference}" | sed 's#\ #\\\ #g')
+
+ # Linearize. To avoid this change, add one line at a time.
+ string=$(printf '%s' "${string}" | tr '\n' ' ')
+
+ # Add ${string} after ${reference} in ${file}.
+ # The "\\" followed by newline is for BSD support.
+ sed "/${reference}/a\\
+${string}
+" "${file}" > "${file}.tmp"
+ mv "${file}.tmp" "${file}"
+}
+
+# Add ${string} before ${reference} in ${file}.
+_add_before() {
+ local string="${1}"; shift
+ local reference="${1}"; shift
+ local file="${1}"; shift
+
+ # Get the line number of the reference line.
+ local line_number
+ line_number=$(grep -Fn "${reference}" "${file}" | cut -d ':' -f 1)
+
+ # Escape all slashes.
+ string=$(printf '%s' "${string}" | sed 's#\/#\\\/#g')
+ reference=$(printf '%s' "${reference}" | sed 's#\/#\\\/#g')
+
+ # Escape all spaces. Only trailing spaces need escaped, but that's harder
+ # and this still empirically works.
+ string=$(printf '%s' "${string}" | sed 's#\ #\\\ #g')
+ reference=$(printf '%s' "${reference}" | sed 's#\ #\\\ #g')
+
+ # Linearize. To avoid this change, add one line at a time.
+ string=$(printf '%s' "${string}" | tr '\n' ' ')
+
+ # Add ${string} before ${reference} in ${file}.
+ # The "\\" followed by newline is for BSD support.
+ sed "${line_number}i\\
+${string}
+" "${file}" > "${file}.tmp"
+ mv "${file}.tmp" "${file}"
+}
+
+_add_failure_tag() {
+ local test_case_tag="${1}"; shift
+ local xml="${1}"; shift
+
+ local closing_tag=' </testcase>'
+ local failure_tag
+ local failure_text
+ local linearized_failure_text
+ # Remove characters which are suspected to not be allowed in:
+ # * sed
+ # * XML attribute values
+ # * XML CDATA
+ failure_text=$(printf '%s\n%s' "${ERROR-}" "${OUTPUT-}" | \
+ sed 's/"/ /g' | sed 's/\[/ /g' | sed 's/\]/ /g')
+ linearized_failure_text=$(printf '%s' "${failure_text}" | tr '\n' ' ')
+ failure_tag=$(printf ' <failure message="%s" type=""><![CDATA[%s]]></failure>' \
+ "${linearized_failure_text}" "${failure_text}")
+
+ # Add.
+ _add_after "${closing_tag}" "${test_case_tag}" "${xml}"
+ _add_after "${failure_tag}" "${test_case_tag}" "${xml}"
+}
+
+# Add test result if not in file.
+_add_test_case() {
+ local test_suite="${1}"; shift
+ local test_case="${1}"; shift
+ local result="${1}"; shift
+ local duration="${1}"; shift
+ local xml="${1}"; shift
+ local now="${1}"; shift
+
+ # Determine the test case tag.
+ local closing_backslash
+ local closing_tag
+ if test "${result}" = 'success'; then
+ closing_backslash=' /'
+ else
+ closing_backslash=
+ fi
+
+ # Create the test XML tag.
+ local test_case_line
+ test_case_line=$(printf ' <testcase name="%s" status="run" result="completed" time="%s" timestamp="%s" classname="%s"%s>' \
+ "${test_case}" "${duration}" "${now}" "${test_suite}" \
+ "${closing_backslash}")
+
+ # Add this test case to all the other test cases.
+ local all_test_cases
+ all_test_cases=$(_print_lines_between_matching_patterns \
+ " <testsuite name=\"${test_suite}\"" ' </testsuite>' "${xml}")
+ all_test_cases=$(printf '%s\n%s' "${all_test_cases}" "${test_case_line}")
+
+ # Find the test following this one.
+ local following_line
+ following_line=$(printf '%s' "${all_test_cases}" | \
+ grep -A1 -F "${test_case_line}" | \
+ grep -Fv "${test_case_line}" || true)
+ if test -n "${following_line}"; then
+ # If found, add it before.
+ _add_before "${test_case_line}" "${following_line}" "${xml}"
+ else
+ # Find the test before this one.
+ local previous_line
+ previous_line=$(printf '%s' "${all_test_cases}" | \
+ grep -B1 -F "${test_case_line}" | \
+ grep -Fv "${test_case_line}" || true)
+ if test -n "${previous_line}"; then
+ # If found, add it after.
+ _add_after "${test_case_line}" "${previous_line}" "${xml}"
+ else
+ # If neither were found, add it as the first test case following the test
+ # suite line.
+ _add_after "${test_case_line}" " <testsuite name=\"${test_suite}\"" "${xml}"
+ fi
+ fi
+
+ # Add the failure tag if it is the case.
+ if test "${result}" != 'success'; then
+ _add_failure_tag "${test_case_line}" "${xml}"
+ fi
+
+ # Retrieve again to include the failure tag that may have just been added
+ # among other tags or lines.
+ all_test_cases=$(_print_lines_between_matching_patterns \
+ " <testsuite name=\"${test_suite}\"" ' </testsuite>' "${xml}")
+
+ # Update attributes for the parent <testsuite> and the global <testsuites>.
+ _update_test_suite_metrics "${test_suite}" "${all_test_cases}" "${xml}" "${now}"
+}
+
+# Add a set of test suite tags if not already present in the XML.
+_add_test_suite() {
+ local test_suite="${1}"; shift
+ local xml="${1}"; shift
+ local now="${1}"; shift
+ local test_suite_line
+ local all_test_suites
+
+ # If test suite tag is already there, then there is nothing to do.
+ if grep -F "<testsuite name=\"${test_suite}\"" "${xml}" \
+ > /dev/null 2>&1; then
+ return
+ fi
+
+ # Create the test suite XML tag.
+ local test_suite_line
+ test_suite_line=$(printf ' <testsuite name="%s" tests="0" failures="0" disabled="0" errors="0" time="0" timestamp="%s">' \
+ "${test_suite}" "${now}")
+
+ # Add this test suite to all the other test suites and sort them.
+ local all_test_suites
+ all_test_suites=$(printf '%s\n%s' " ${test_suite_line}" \
+ "$(grep -E ' <testsuite name=|</testsuites>' "${xml}")")
+
+ # Find the test suite following this one.
+ local following_line
+ following_line=$(printf '%s' "${all_test_suites}" | \
+ grep -A1 -F "${test_suite_line}" | \
+ grep -Fv "${test_suite_line}" || true)
+
+ # Add the test suite tag to the XML.
+ _add_before "${test_suite_line}" "${following_line}" "${xml}"
+ _add_after ' </testsuite>' "${test_suite_line}" "${xml}"
+}
+
+# Calculate the given mathematical expression and print it in a format that
+# matches googletest's time in the XML attribute time="..." which is seconds
+# rounded to 3 decimals.
+_calculate() {
+ awk "BEGIN{print ${*}}";
+}
+
+# Create XML with header and top-level tags if the file doesn't exist.
+_create_xml() {
+ # If file exists and we have set GTEST_OUTPUT_CREATED previously, then there
+ # is nothing to do.
+ if test -f "${xml}" && test -n "${GTEST_OUTPUT_CREATED+x}"; then
+ return;
+ fi
+
+ local xml="${1}"; shift
+ local now="${1}"; shift
+
+ mkdir -p "$(dirname "${xml}")"
+ printf \
+'<?xml version="1.0" encoding="UTF-8"?>
+<testsuites tests="0" failures="0" disabled="0" errors="0" time="0" timestamp="%s" name="AllTests">
+</testsuites>
+' "${now}" > "${xml}"
+
+ # GTEST_OUTPUT_CREATED is not a googletest variable, but our way of allowing
+ # to overwrite XMLs created in a previous test run. The lifetime of
+ # GTEST_OUTPUT_CREATED is extended to the oldest ancestor file who has
+ # sourced this script i.e. the *_test.sh file. So it gets lost from one
+ # *_test.sh to another. The consensus that need to be kept so that this
+ # works correctly are:
+ # * Needless to say, don't set this variable on your own.
+ # * Always call these scripts directly or through `make check`.
+ # Never source test files e.g. `source memfile_tests.sh` or
+ # `. memfile_tests.sh`.
+ # * The ${xml} passed here must be deterministically and uniquely
+ # attributed to the *_test.sh. At the time of this writing, ${xml} is the
+ # part of the name before the dot. So for example, for memfile, all tests
+ # should start with the same thing e.g. `memfile.*`.
+ export GTEST_OUTPUT_CREATED=true
+}
+
+# Print the lines between two matching regex patterns from a file. Excludes the
+# lines that contain the patterns themselves. Matches only the first occurrence.
+_print_lines_between_matching_patterns() {
+ local start_pattern="${1}"; shift
+ local end_pattern="${1}"; shift
+ local file="${1}"; shift
+
+ # Escape all slashes.
+ start_pattern=$(printf '%s' "${start_pattern}" | sed 's#\/#\\\/#g')
+ end_pattern=$(printf '%s' "${end_pattern}" | sed 's#\/#\\\/#g')
+
+ # Print with sed.
+ sed -n "/${start_pattern}/,/${end_pattern}/p;/${end_pattern}/q" "${file}" \
+ | sed '$d' | tail -n +2
+}
+
+# Update the test suite XML attributes with metrics collected from the child
+# test cases.
+_update_test_suite_metrics() {
+ local test_suite="${1}"; shift
+ local all_test_cases="${1}"; shift
+ local xml="${1}"; shift
+ local now="${1}"; shift
+
+ # Get the metrics on the parent test suite.
+ local duration
+ local durations_summed
+ local failures
+ local tests
+ tests=$(printf '%s' "${all_test_cases}" | \
+ grep -Fc '<testcase' || true)
+ failures=$(printf '%s' "${all_test_cases}" | \
+ grep -Fc '<failure' || true)
+ durations_summed=$(printf '%s' "${all_test_cases}" | \
+ grep -Eo 'time="[0-9.]+"' | cut -d '"' -f 2 | xargs | sed 's/ / + /g')
+ duration=$(_calculate "${durations_summed}")
+
+ # Create the test suite XML tag.
+ local test_suite_line
+ test_suite_line=$(printf ' <testsuite name="%s" tests="%s" failures="%s" disabled="0" errors="0" time="%s" timestamp="%s">' \
+ "${test_suite}" "${tests}" "${failures}" "${duration}" "${now}")
+
+ # Update the test suite with the collected metrics.
+ sed "s# <testsuite name=\"${test_suite}\".*>#${test_suite_line}#g" \
+ "${xml}" > "${xml}.tmp"
+ mv "${xml}.tmp" "${xml}"
+
+ # Create the test suites XML tag.
+ local test_suites_line
+ test_suites_line=$(printf '<testsuites tests="%s" failures="%s" disabled="0" errors="0" time="%s" timestamp="%s" name="AllTests">' \
+ "${tests}" "${failures}" "${duration}" "${now}")
+
+ # Update the test suites with the collected metrics.
+ sed "s#<testsuites .*>#${test_suites_line}#g" \
+ "${xml}" > "${xml}.tmp"
+ mv "${xml}.tmp" "${xml}"
+}
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
new file mode 100644
index 0000000..e2833a9
--- /dev/null
+++ b/src/lib/util/Makefile.am
@@ -0,0 +1,111 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+SUBDIRS = . io unittests tests python
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-util.la
+libkea_util_la_SOURCES =
+libkea_util_la_SOURCES += bigints.h
+libkea_util_la_SOURCES += boost_time_utils.h boost_time_utils.cc
+libkea_util_la_SOURCES += buffer.h io_utilities.h
+libkea_util_la_SOURCES += chrono_time_utils.h chrono_time_utils.cc
+libkea_util_la_SOURCES += csv_file.h csv_file.cc
+libkea_util_la_SOURCES += dhcp_space.h dhcp_space.cc
+libkea_util_la_SOURCES += doubles.h
+libkea_util_la_SOURCES += file_utilities.h file_utilities.cc
+libkea_util_la_SOURCES += filename.h filename.cc
+libkea_util_la_SOURCES += hash.h
+libkea_util_la_SOURCES += labeled_value.h labeled_value.cc
+libkea_util_la_SOURCES += memory_segment.h
+libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
+libkea_util_la_SOURCES += multi_threading_mgr.h multi_threading_mgr.cc
+libkea_util_la_SOURCES += optional.h
+libkea_util_la_SOURCES += pid_file.h pid_file.cc
+libkea_util_la_SOURCES += pointer_util.h
+libkea_util_la_SOURCES += range_utilities.h
+libkea_util_la_SOURCES += readwrite_mutex.h
+libkea_util_la_SOURCES += reconnect_ctl.h reconnect_ctl.cc
+libkea_util_la_SOURCES += staged_value.h
+libkea_util_la_SOURCES += state_model.cc state_model.h
+libkea_util_la_SOURCES += stopwatch.cc stopwatch.h
+libkea_util_la_SOURCES += stopwatch_impl.cc stopwatch_impl.h
+libkea_util_la_SOURCES += strutil.h strutil.cc
+libkea_util_la_SOURCES += thread_pool.h
+libkea_util_la_SOURCES += time_utilities.h time_utilities.cc
+libkea_util_la_SOURCES += triplet.h
+libkea_util_la_SOURCES += unlock_guard.h
+libkea_util_la_SOURCES += versioned_csv_file.h versioned_csv_file.cc
+libkea_util_la_SOURCES += watch_socket.cc watch_socket.h
+libkea_util_la_SOURCES += watched_thread.cc watched_thread.h
+libkea_util_la_SOURCES += encode/base16_from_binary.h
+libkea_util_la_SOURCES += encode/base32hex.h encode/base64.h
+libkea_util_la_SOURCES += encode/base32hex_from_binary.h
+libkea_util_la_SOURCES += encode/base_n.cc encode/hex.h
+libkea_util_la_SOURCES += encode/binary_from_base32hex.h
+libkea_util_la_SOURCES += encode/binary_from_base16.h
+libkea_util_la_SOURCES += encode/utf8.cc encode/utf8.h
+
+libkea_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+
+libkea_util_la_LDFLAGS = -no-undefined -version-info 68:0:0
+
+EXTRA_DIST = util.dox
+
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_util_includedir = $(pkgincludedir)/util
+libkea_util_include_HEADERS = \
+ bigints.h \
+ boost_time_utils.h \
+ buffer.h \
+ csv_file.h \
+ dhcp_space.h \
+ doubles.h \
+ file_utilities.h \
+ filename.h \
+ hash.h \
+ io_utilities.h \
+ labeled_value.h \
+ memory_segment.h \
+ memory_segment_local.h \
+ multi_threading_mgr.h \
+ optional.h \
+ pid_file.h \
+ pointer_util.h \
+ range_utilities.h \
+ readwrite_mutex.h \
+ reconnect_ctl.h \
+ staged_value.h \
+ state_model.h \
+ stopwatch.h \
+ stopwatch_impl.h \
+ strutil.h \
+ thread_pool.h \
+ time_utilities.h \
+ triplet.h \
+ unlock_guard.h \
+ versioned_csv_file.h \
+ watch_socket.h \
+ watched_thread.h
+
+libkea_util_encode_includedir = $(pkgincludedir)/util/encode
+libkea_util_encode_include_HEADERS = \
+ encode/base16_from_binary.h \
+ encode/base32hex.h \
+ encode/base32hex_from_binary.h \
+ encode/base64.h \
+ encode/binary_from_base16.h \
+ encode/binary_from_base32hex.h \
+ encode/hex.h \
+ encode/utf8.h
+
+libkea_util_io_includedir = $(pkgincludedir)/util/io
+libkea_util_io_include_HEADERS = \
+ io/fd.h \
+ io/fd_share.h \
+ io/pktinfo_utilities.h \
+ io/sockaddr_util.h
diff --git a/src/lib/util/Makefile.in b/src/lib/util/Makefile.in
new file mode 100644
index 0000000..f79840f
--- /dev/null
+++ b/src/lib/util/Makefile.in
@@ -0,0 +1,1161 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/util
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 \
+ $(libkea_util_encode_include_HEADERS) \
+ $(libkea_util_include_HEADERS) \
+ $(libkea_util_io_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)$(libkea_util_encode_includedir)" \
+ "$(DESTDIR)$(libkea_util_includedir)" \
+ "$(DESTDIR)$(libkea_util_io_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libkea_util_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+am__dirstamp = $(am__leading_dot)dirstamp
+am_libkea_util_la_OBJECTS = boost_time_utils.lo chrono_time_utils.lo \
+ csv_file.lo dhcp_space.lo file_utilities.lo filename.lo \
+ labeled_value.lo memory_segment_local.lo \
+ multi_threading_mgr.lo pid_file.lo reconnect_ctl.lo \
+ state_model.lo stopwatch.lo stopwatch_impl.lo strutil.lo \
+ time_utilities.lo versioned_csv_file.lo watch_socket.lo \
+ watched_thread.lo encode/base_n.lo encode/utf8.lo
+libkea_util_la_OBJECTS = $(am_libkea_util_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_util_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_util_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)/boost_time_utils.Plo \
+ ./$(DEPDIR)/chrono_time_utils.Plo ./$(DEPDIR)/csv_file.Plo \
+ ./$(DEPDIR)/dhcp_space.Plo ./$(DEPDIR)/file_utilities.Plo \
+ ./$(DEPDIR)/filename.Plo ./$(DEPDIR)/labeled_value.Plo \
+ ./$(DEPDIR)/memory_segment_local.Plo \
+ ./$(DEPDIR)/multi_threading_mgr.Plo ./$(DEPDIR)/pid_file.Plo \
+ ./$(DEPDIR)/reconnect_ctl.Plo ./$(DEPDIR)/state_model.Plo \
+ ./$(DEPDIR)/stopwatch.Plo ./$(DEPDIR)/stopwatch_impl.Plo \
+ ./$(DEPDIR)/strutil.Plo ./$(DEPDIR)/time_utilities.Plo \
+ ./$(DEPDIR)/versioned_csv_file.Plo \
+ ./$(DEPDIR)/watch_socket.Plo ./$(DEPDIR)/watched_thread.Plo \
+ encode/$(DEPDIR)/base_n.Plo encode/$(DEPDIR)/utf8.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_util_la_SOURCES)
+DIST_SOURCES = $(libkea_util_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 = $(libkea_util_encode_include_HEADERS) \
+ $(libkea_util_include_HEADERS) \
+ $(libkea_util_io_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . io unittests tests python
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-util.la
+libkea_util_la_SOURCES = bigints.h boost_time_utils.h \
+ boost_time_utils.cc buffer.h io_utilities.h \
+ chrono_time_utils.h chrono_time_utils.cc csv_file.h \
+ csv_file.cc dhcp_space.h dhcp_space.cc doubles.h \
+ file_utilities.h file_utilities.cc filename.h filename.cc \
+ hash.h labeled_value.h labeled_value.cc memory_segment.h \
+ memory_segment_local.h memory_segment_local.cc \
+ multi_threading_mgr.h multi_threading_mgr.cc optional.h \
+ pid_file.h pid_file.cc pointer_util.h range_utilities.h \
+ readwrite_mutex.h reconnect_ctl.h reconnect_ctl.cc \
+ staged_value.h state_model.cc state_model.h stopwatch.cc \
+ stopwatch.h stopwatch_impl.cc stopwatch_impl.h strutil.h \
+ strutil.cc thread_pool.h time_utilities.h time_utilities.cc \
+ triplet.h unlock_guard.h versioned_csv_file.h \
+ versioned_csv_file.cc watch_socket.cc watch_socket.h \
+ watched_thread.cc watched_thread.h encode/base16_from_binary.h \
+ encode/base32hex.h encode/base64.h \
+ encode/base32hex_from_binary.h encode/base_n.cc encode/hex.h \
+ encode/binary_from_base32hex.h encode/binary_from_base16.h \
+ encode/utf8.cc encode/utf8.h
+libkea_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_util_la_LDFLAGS = -no-undefined -version-info 68:0:0
+EXTRA_DIST = util.dox
+CLEANFILES = *.gcno *.gcda
+
+# Specify the headers for copying into the installation directory tree.
+libkea_util_includedir = $(pkgincludedir)/util
+libkea_util_include_HEADERS = \
+ bigints.h \
+ boost_time_utils.h \
+ buffer.h \
+ csv_file.h \
+ dhcp_space.h \
+ doubles.h \
+ file_utilities.h \
+ filename.h \
+ hash.h \
+ io_utilities.h \
+ labeled_value.h \
+ memory_segment.h \
+ memory_segment_local.h \
+ multi_threading_mgr.h \
+ optional.h \
+ pid_file.h \
+ pointer_util.h \
+ range_utilities.h \
+ readwrite_mutex.h \
+ reconnect_ctl.h \
+ staged_value.h \
+ state_model.h \
+ stopwatch.h \
+ stopwatch_impl.h \
+ strutil.h \
+ thread_pool.h \
+ time_utilities.h \
+ triplet.h \
+ unlock_guard.h \
+ versioned_csv_file.h \
+ watch_socket.h \
+ watched_thread.h
+
+libkea_util_encode_includedir = $(pkgincludedir)/util/encode
+libkea_util_encode_include_HEADERS = \
+ encode/base16_from_binary.h \
+ encode/base32hex.h \
+ encode/base32hex_from_binary.h \
+ encode/base64.h \
+ encode/binary_from_base16.h \
+ encode/binary_from_base32hex.h \
+ encode/hex.h \
+ encode/utf8.h
+
+libkea_util_io_includedir = $(pkgincludedir)/util/io
+libkea_util_io_include_HEADERS = \
+ io/fd.h \
+ io/fd_share.h \
+ io/pktinfo_utilities.h \
+ io/sockaddr_util.h
+
+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/util/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/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}; \
+ }
+encode/$(am__dirstamp):
+ @$(MKDIR_P) encode
+ @: > encode/$(am__dirstamp)
+encode/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) encode/$(DEPDIR)
+ @: > encode/$(DEPDIR)/$(am__dirstamp)
+encode/base_n.lo: encode/$(am__dirstamp) \
+ encode/$(DEPDIR)/$(am__dirstamp)
+encode/utf8.lo: encode/$(am__dirstamp) \
+ encode/$(DEPDIR)/$(am__dirstamp)
+
+libkea-util.la: $(libkea_util_la_OBJECTS) $(libkea_util_la_DEPENDENCIES) $(EXTRA_libkea_util_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_util_la_LINK) -rpath $(libdir) $(libkea_util_la_OBJECTS) $(libkea_util_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f encode/*.$(OBJEXT)
+ -rm -f encode/*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boost_time_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chrono_time_utils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/csv_file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dhcp_space.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file_utilities.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filename.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/labeled_value.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/memory_segment_local.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/multi_threading_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pid_file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reconnect_ctl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/state_model.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stopwatch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stopwatch_impl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strutil.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/time_utilities.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/versioned_csv_file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/watch_socket.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/watched_thread.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@encode/$(DEPDIR)/base_n.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@encode/$(DEPDIR)/utf8.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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf encode/.libs encode/_libs
+install-libkea_util_encode_includeHEADERS: $(libkea_util_encode_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_util_encode_include_HEADERS)'; test -n "$(libkea_util_encode_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_util_encode_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_util_encode_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)$(libkea_util_encode_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_util_encode_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_util_encode_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_util_encode_include_HEADERS)'; test -n "$(libkea_util_encode_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_util_encode_includedir)'; $(am__uninstall_files_from_dir)
+install-libkea_util_includeHEADERS: $(libkea_util_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_util_include_HEADERS)'; test -n "$(libkea_util_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_util_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_util_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)$(libkea_util_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_util_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_util_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_util_include_HEADERS)'; test -n "$(libkea_util_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_util_includedir)'; $(am__uninstall_files_from_dir)
+install-libkea_util_io_includeHEADERS: $(libkea_util_io_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_util_io_include_HEADERS)'; test -n "$(libkea_util_io_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_util_io_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_util_io_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)$(libkea_util_io_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_util_io_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_util_io_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_util_io_include_HEADERS)'; test -n "$(libkea_util_io_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_util_io_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_util_encode_includedir)" "$(DESTDIR)$(libkea_util_includedir)" "$(DESTDIR)$(libkea_util_io_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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)
+ -rm -f encode/$(DEPDIR)/$(am__dirstamp)
+ -rm -f encode/$(am__dirstamp)
+
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/boost_time_utils.Plo
+ -rm -f ./$(DEPDIR)/chrono_time_utils.Plo
+ -rm -f ./$(DEPDIR)/csv_file.Plo
+ -rm -f ./$(DEPDIR)/dhcp_space.Plo
+ -rm -f ./$(DEPDIR)/file_utilities.Plo
+ -rm -f ./$(DEPDIR)/filename.Plo
+ -rm -f ./$(DEPDIR)/labeled_value.Plo
+ -rm -f ./$(DEPDIR)/memory_segment_local.Plo
+ -rm -f ./$(DEPDIR)/multi_threading_mgr.Plo
+ -rm -f ./$(DEPDIR)/pid_file.Plo
+ -rm -f ./$(DEPDIR)/reconnect_ctl.Plo
+ -rm -f ./$(DEPDIR)/state_model.Plo
+ -rm -f ./$(DEPDIR)/stopwatch.Plo
+ -rm -f ./$(DEPDIR)/stopwatch_impl.Plo
+ -rm -f ./$(DEPDIR)/strutil.Plo
+ -rm -f ./$(DEPDIR)/time_utilities.Plo
+ -rm -f ./$(DEPDIR)/versioned_csv_file.Plo
+ -rm -f ./$(DEPDIR)/watch_socket.Plo
+ -rm -f ./$(DEPDIR)/watched_thread.Plo
+ -rm -f encode/$(DEPDIR)/base_n.Plo
+ -rm -f encode/$(DEPDIR)/utf8.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-libkea_util_encode_includeHEADERS \
+ install-libkea_util_includeHEADERS \
+ install-libkea_util_io_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)/boost_time_utils.Plo
+ -rm -f ./$(DEPDIR)/chrono_time_utils.Plo
+ -rm -f ./$(DEPDIR)/csv_file.Plo
+ -rm -f ./$(DEPDIR)/dhcp_space.Plo
+ -rm -f ./$(DEPDIR)/file_utilities.Plo
+ -rm -f ./$(DEPDIR)/filename.Plo
+ -rm -f ./$(DEPDIR)/labeled_value.Plo
+ -rm -f ./$(DEPDIR)/memory_segment_local.Plo
+ -rm -f ./$(DEPDIR)/multi_threading_mgr.Plo
+ -rm -f ./$(DEPDIR)/pid_file.Plo
+ -rm -f ./$(DEPDIR)/reconnect_ctl.Plo
+ -rm -f ./$(DEPDIR)/state_model.Plo
+ -rm -f ./$(DEPDIR)/stopwatch.Plo
+ -rm -f ./$(DEPDIR)/stopwatch_impl.Plo
+ -rm -f ./$(DEPDIR)/strutil.Plo
+ -rm -f ./$(DEPDIR)/time_utilities.Plo
+ -rm -f ./$(DEPDIR)/versioned_csv_file.Plo
+ -rm -f ./$(DEPDIR)/watch_socket.Plo
+ -rm -f ./$(DEPDIR)/watched_thread.Plo
+ -rm -f encode/$(DEPDIR)/base_n.Plo
+ -rm -f encode/$(DEPDIR)/utf8.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-libkea_util_encode_includeHEADERS \
+ uninstall-libkea_util_includeHEADERS \
+ uninstall-libkea_util_io_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_util_encode_includeHEADERS \
+ install-libkea_util_includeHEADERS \
+ install-libkea_util_io_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-libkea_util_encode_includeHEADERS \
+ uninstall-libkea_util_includeHEADERS \
+ uninstall-libkea_util_io_includeHEADERS
+
+.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/util/bigints.h b/src/lib/util/bigints.h
new file mode 100644
index 0000000..be76b1d
--- /dev/null
+++ b/src/lib/util/bigints.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 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 file provides an interface towards bigint implementations.
+// Currently, it uses the ones from boost::multiprecision, but if we ever want
+// to swap it out, or implement our own, we can seamlessly do it in this header.
+
+#ifndef UTIL_BIGINTS_H
+#define UTIL_BIGINTS_H
+
+#include <boost/multiprecision/cpp_int.hpp>
+
+namespace isc {
+namespace util {
+
+using int128_t = boost::multiprecision::int128_t;
+
+using uint128_t = boost::multiprecision::uint128_t;
+
+} // namespace util
+} // namespace isc
+
+#endif // UTIL_BIGINTS_H \ No newline at end of file
diff --git a/src/lib/util/boost_time_utils.cc b/src/lib/util/boost_time_utils.cc
new file mode 100644
index 0000000..3719f72
--- /dev/null
+++ b/src/lib/util/boost_time_utils.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2015-2019 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/boost_time_utils.h>
+#include <sstream>
+#include <iomanip>
+
+std::string
+isc::util::ptimeToText(boost::posix_time::ptime t, size_t fsecs_precision) {
+ boost::gregorian::date d = t.date();
+ std::stringstream s;
+ s << d.year()
+ << "-" << std::setw(2) << std::setfill('0') << d.month().as_number()
+ << "-" << std::setw(2) << std::setfill('0') << d.day()
+ << " " << durationToText(t.time_of_day(), fsecs_precision);
+ return (s.str());
+}
+
+std::string
+isc::util::durationToText(boost::posix_time::time_duration dur, size_t fsecs_precision) {
+ std::stringstream s;
+ s << std::setw(2) << std::setfill('0') << dur.hours()
+ << ":" << std::setw(2) << std::setfill('0') << dur.minutes()
+ << ":" << std::setw(2) << std::setfill('0') << dur.seconds();
+
+ // If the requested precision is less than the maximum native precision
+ // we will divide the fractional seconds value by 10^(max - requested)
+ if (fsecs_precision) {
+ size_t fsecs = dur.fractional_seconds();
+ size_t width = MAX_FSECS_PRECISION;
+ if (fsecs_precision < width) {
+ for (auto i = 0; i < width - fsecs_precision; ++i) {
+ fsecs /= 10;
+ }
+
+ width = fsecs_precision;
+ }
+
+ s << "." << std::setw(width)
+ << std::setfill('0')
+ << fsecs;
+ }
+
+ return (s.str());
+}
diff --git a/src/lib/util/boost_time_utils.h b/src/lib/util/boost_time_utils.h
new file mode 100644
index 0000000..a4cdeff
--- /dev/null
+++ b/src/lib/util/boost_time_utils.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2015-2019 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 KEA_BOOST_TIME_UTILS_H
+#define KEA_BOOST_TIME_UTILS_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief The number of digits of fractional seconds supplied by the
+/// underlying class, boost::posix_time. Typically 6 = microseconds.
+const size_t MAX_FSECS_PRECISION=boost::posix_time::time_duration::num_fractional_digits();
+
+/// @brief Converts ptime structure to text
+///
+/// This is Kea implementation for converting ptime to strings.
+/// It's a functional equivalent of boost::posix_time::to_simple_string. We do
+/// not use it, though, because it would introduce unclear dependency on
+/// boost_time_date library. First, we try to avoid depending on boost libraries
+/// (we tend to use only the headers). Second, this dependency is system
+/// specific, i.e. it is required on Ubuntu and FreeBSD, but doesn't seem to
+/// be needed on OS X. Since the functionality needed is minor, we decided to
+/// reimplement it on our own, rather than introduce extra dependencies.
+/// This explanation also applies to @ref durationToText.
+/// @param t ptime value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Default is given by boost::posix_time::time_duration::num_fractional_digits().
+/// Zero omits the value.
+///
+/// @return a string representing time
+std::string ptimeToText(boost::posix_time::ptime t,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+/// @brief Converts StatsDuration to text
+///
+/// This is Kea equivalent of boost::posix_time::to_simple_string(time_duration).
+/// See @ref ptimeToText for explanation why we chose our own implementation.
+/// @param dur duration value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Default is given by boost::posix_time::time_duration::num_fractional_digits().
+/// Zero omits the value.
+///
+/// @return a string representing time
+std::string durationToText(boost::posix_time::time_duration dur,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+}; // end of isc::util namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/util/buffer.h b/src/lib/util/buffer.h
new file mode 100644
index 0000000..4f91856
--- /dev/null
+++ b/src/lib/util/buffer.h
@@ -0,0 +1,611 @@
+// Copyright (C) 2009-2022 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 BUFFER_H
+#define BUFFER_H 1
+
+#include <stdlib.h>
+#include <cstring>
+#include <vector>
+
+#include <stdint.h>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+///
+/// \brief A standard DNS module exception that is thrown if an out-of-range
+/// buffer operation is being performed.
+///
+class InvalidBufferPosition : public Exception {
+public:
+ InvalidBufferPosition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+///\brief The \c InputBuffer class is a buffer abstraction for manipulating
+/// read-only data.
+///
+/// The main purpose of this class is to provide a safe placeholder for
+/// examining wire-format data received from a network.
+///
+/// Applications normally use this class only in a limited situation: as an
+/// interface between legacy I/O operation (such as receiving data from a BSD
+/// socket) and the rest of the Kea DNS library. One common usage of this
+/// class for an application would therefore be something like this:
+///
+/// \code unsigned char buf[1024];
+/// struct sockaddr addr;
+/// socklen_t addrlen = sizeof(addr);
+/// int cc = recvfrom(s, buf, sizeof(buf), 0, &addr, &addrlen);
+/// InputBuffer buffer(buf, cc);
+/// // pass the buffer to a DNS message object to parse the message \endcode
+///
+/// Other Kea DNS classes will then use methods of this class to get access
+/// to the data, but the application normally doesn't have to care about the
+/// details.
+///
+/// An \c InputBuffer object internally holds a reference to the given data,
+/// rather than make a local copy of the data. Also, it does not have an
+/// ownership of the given data. It is application's responsibility to ensure
+/// the data remains valid throughout the lifetime of the \c InputBuffer
+/// object. Likewise, this object generally assumes the data isn't modified
+/// throughout its lifetime; if the application modifies the data while this
+/// object retains a reference to it, the result is undefined. The application
+/// will also be responsible for releasing the data when it's not needed if it
+/// was dynamically acquired.
+///
+/// This is a deliberate design choice: although it's safer to make a local
+/// copy of the given data on construction, it would cause unacceptable
+/// performance overhead, especially considering that a DNS message can be
+/// as large as a few KB. Alternatively, we could allow the object to allocate
+/// memory internally and expose it to the application to store network data
+/// in it. This is also a bad design, however, in that we would effectively
+/// break the abstraction employed in the class, and do so by publishing
+/// "read-only" stuff as a writable memory region. Since there doesn't seem to
+/// be a perfect solution, we have adopted what we thought a "least bad" one.
+///
+/// Methods for reading data from the buffer generally work like an input
+/// stream: it begins with the head of the data, and once some length of data
+/// is read from the buffer, the next read operation will take place from the
+/// head of the unread data. An object of this class internally holds (a
+/// notion of) where the next read operation should start. We call it the
+/// <em>read position</em> in this document.
+class InputBuffer {
+public:
+ ///
+ /// \name Constructors and Destructor
+ //@{
+ /// \brief Constructor from variable length of data.
+ ///
+ /// It is caller's responsibility to ensure that the data is valid as long
+ /// as the buffer exists.
+ /// \param data A pointer to the data stored in the buffer.
+ /// \param len The length of the data in bytes.
+ InputBuffer(const void* data, size_t len) :
+ position_(0), data_(static_cast<const uint8_t*>(data)), len_(len) {}
+ //@}
+
+ ///
+ /// \name Getter Methods
+ //@{
+ /// \brief Return the length of the data stored in the buffer.
+ size_t getLength() const { return (len_); }
+ /// \brief Return the current read position.
+ size_t getPosition() const { return (position_); }
+ //@}
+
+ ///
+ /// \name Setter Methods
+ ///
+ //@{
+ /// \brief Set the read position of the buffer to the given value.
+ ///
+ /// The new position must be in the valid range of the buffer; otherwise
+ /// an exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ /// \param position The new position (offset from the beginning of the
+ /// buffer).
+ void setPosition(size_t position) {
+ if (position > len_) {
+ throwError("position is too large");
+ }
+ position_ = position;
+ }
+ //@}
+
+ ///
+ /// \name Methods for reading data from the buffer.
+ //@{
+ /// \brief Read an unsigned 8-bit integer from the buffer and return it.
+ ///
+ /// If the remaining length of the buffer is smaller than 8-bit, an
+ /// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ uint8_t readUint8() {
+ if (position_ + sizeof(uint8_t) > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ return (data_[position_++]);
+ }
+ /// \brief Read an unsigned 16-bit integer in network byte order from the
+ /// buffer, convert it to host byte order, and return it.
+ ///
+ /// If the remaining length of the buffer is smaller than 16-bit, an
+ /// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ uint16_t readUint16() {
+ uint16_t data;
+ const uint8_t* cp;
+
+ if (position_ + sizeof(data) > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ cp = &data_[position_];
+ data = ((unsigned int)(cp[0])) << 8;
+ data |= ((unsigned int)(cp[1]));
+ position_ += sizeof(data);
+
+ return (data);
+ }
+ /// \brief Read an unsigned 32-bit integer in network byte order from the
+ /// buffer, convert it to host byte order, and return it.
+ ///
+ /// If the remaining length of the buffer is smaller than 32-bit, an
+ /// exception of class \c isc::dns::InvalidBufferPosition will be thrown.
+ uint32_t readUint32() {
+ uint32_t data;
+ const uint8_t* cp;
+
+ if (position_ + sizeof(data) > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ cp = &data_[position_];
+ data = ((unsigned int)(cp[0])) << 24;
+ data |= ((unsigned int)(cp[1])) << 16;
+ data |= ((unsigned int)(cp[2])) << 8;
+ data |= ((unsigned int)(cp[3]));
+ position_ += sizeof(data);
+
+ return (data);
+ }
+ /// \brief Read data of the specified length from the buffer and copy it to
+ /// the caller supplied buffer.
+ ///
+ /// The data is copied as stored in the buffer; no conversion is performed.
+ /// If the remaining length of the buffer is smaller than the specified
+ /// length, an exception of class \c isc::dns::InvalidBufferPosition will
+ /// be thrown.
+ void readData(void* data, size_t len) {
+ if (position_ + len > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ static_cast<void>(std::memmove(data, &data_[position_], len));
+ position_ += len;
+ }
+ //@}
+
+ /// @brief Read specified number of bytes as a vector.
+ ///
+ /// If specified buffer is too short, it will be expanded
+ /// using vector::resize() method.
+ ///
+ /// @param data Reference to a buffer (data will be stored there).
+ /// @param len Size specified number of bytes to read in a vector.
+ ///
+ void readVector(std::vector<uint8_t>& data, size_t len) {
+ if (position_ + len > len_) {
+ throwError("read beyond end of buffer");
+ }
+
+ data.resize(len);
+ readData(&data[0], len);
+ }
+
+private:
+ /// \brief A common helper to throw an exception on invalid operation.
+ ///
+ /// Experiments showed that throwing from each method makes the buffer
+ /// operation slower, so we consolidate it here, and let the methods
+ /// call this.
+ static void throwError(const char* msg) {
+ isc_throw(InvalidBufferPosition, msg);
+ }
+
+ size_t position_;
+
+ // XXX: The following must be private, but for a short term workaround with
+ // Boost.Python binding, we changed it to protected. We should soon
+ // revisit it.
+protected:
+ const uint8_t* data_;
+ size_t len_;
+};
+
+///
+///\brief The \c OutputBuffer class is a buffer abstraction for manipulating
+/// mutable data.
+///
+/// The main purpose of this class is to provide a safe workplace for
+/// constructing wire-format data to be sent out to a network. Here,
+/// <em>safe</em> means that it automatically allocates necessary memory and
+/// avoid buffer overrun.
+///
+/// Like for the \c InputBuffer class, applications normally use this class only
+/// in a limited situation. One common usage of this class for an application
+/// would be something like this:
+///
+/// \code OutputBuffer buffer(4096); // give a sufficiently large initial size
+/// // pass the buffer to a DNS message object to construct a wire-format
+/// // DNS message.
+/// struct sockaddr to;
+/// sendto(s, buffer.getData(), buffer.getLength(), 0, &to, sizeof(to));
+/// \endcode
+///
+/// where the \c getData() method gives a reference to the internal memory
+/// region stored in the \c buffer object. This is a suboptimal design in that
+/// it exposes an encapsulated "handle" of an object to its user.
+/// Unfortunately, there is no easy way to avoid this without involving
+/// expensive data copy if we want to use this object with a legacy API such as
+/// a BSD socket interface. And, indeed, this is one major purpose for this
+/// object. Applications should use this method only under such a special
+/// circumstance. It should also be noted that the memory region returned by
+/// \c getData() may be invalidated after a subsequent write operation.
+///
+/// An \c OutputBuffer class object automatically extends its memory region when
+/// data is written beyond the end of the current buffer. However, it will
+/// involve performance overhead such as reallocating more memory and copying
+/// data. It is therefore recommended to construct the buffer object with a
+/// sufficiently large initial size.
+/// The \c getCapacity() method provides the current maximum size of data
+/// (including the portion already written) that can be written into the buffer
+/// without causing memory reallocation.
+///
+/// Methods for writing data into the buffer generally work like an output
+/// stream: it begins with the head of the buffer, and once some length of data
+/// is written into the buffer, the next write operation will take place from
+/// the end of the buffer. Other methods to emulate "random access" are also
+/// provided (e.g., \c writeUint16At()). The normal write operations are
+/// normally exception-free as this class automatically extends the buffer
+/// when necessary. However, in extreme cases such as an attempt of writing
+/// multi-GB data, a separate exception (e.g., \c std::bad_alloc) may be thrown
+/// by the system. This also applies to the constructor with a very large
+/// initial size.
+///
+/// Note to developers: it may make more sense to introduce an abstract base
+/// class for the \c OutputBuffer and define the simple implementation as a
+/// concrete derived class. That way we can provide flexibility for future
+/// extension such as more efficient buffer implementation or allowing users
+/// to have their own customized version without modifying the source code.
+/// We in fact considered that option, but at the moment chose the simpler
+/// approach with a single concrete class because it may make the
+/// implementation unnecessarily complicated while we were still not certain
+/// if we really want that flexibility. We may revisit the class design as
+/// we see more applications of the class. The same considerations apply to
+/// the \c InputBuffer and \c MessageRenderer classes.
+class OutputBuffer {
+public:
+ ///
+ /// \name Constructors and Destructor
+ ///
+ //@{
+ /// \brief Constructor from the initial size of the buffer.
+ ///
+ /// \param len The initial length of the buffer in bytes.
+ OutputBuffer(size_t len) :
+ buffer_(NULL),
+ size_(0),
+ allocated_(len)
+ {
+ // We use malloc and free instead of C++ new[] and delete[].
+ // This way we can use realloc, which may in fact do it without a copy.
+ if (allocated_ != 0) {
+ buffer_ = static_cast<uint8_t*>(malloc(allocated_));
+ if (buffer_ == NULL) {
+ throw std::bad_alloc();
+ }
+ }
+ }
+
+ /// \brief Copy constructor
+ ///
+ /// \param other Source object from which to make a copy.
+ ///
+ /// \note It is assumed that the source object is consistent, i.e.
+ /// size_ <= allocated_, and that if allocated_ is greater than zero,
+ /// buffer_ points to valid memory.
+ OutputBuffer(const OutputBuffer& other) :
+ buffer_(NULL),
+ size_(other.size_),
+ allocated_(other.allocated_)
+ {
+ if (allocated_ != 0) {
+ buffer_ = static_cast<uint8_t*>(malloc(allocated_));
+ if (buffer_ == NULL) {
+ throw std::bad_alloc();
+ }
+ static_cast<void>(std::memmove(buffer_, other.buffer_, other.size_));
+ }
+ }
+
+ /// \brief Destructor
+ ~OutputBuffer() {
+ free(buffer_);
+ }
+ //@}
+
+ /// \brief Assignment operator
+ ///
+ /// \param other Object to copy into "this".
+ ///
+ /// \note It is assumed that the source object is consistent, i.e.
+ /// size_ <= allocated_, and that if allocated_ is greater than zero,
+ /// buffer_ points to valid memory.
+ OutputBuffer& operator =(const OutputBuffer& other) {
+ if (this != &other) {
+ // Not self-assignment.
+ if (other.allocated_ != 0) {
+
+ // There is something in the source object, so allocate memory
+ // and copy it. The pointer to the allocated memory is placed
+ // in a temporary variable so that if the allocation fails and
+ // an exception is thrown, the destination object ("this") is
+ // unchanged.
+ uint8_t* newbuff = static_cast<uint8_t*>(malloc(other.allocated_));
+ if (newbuff == NULL) {
+ throw std::bad_alloc();
+ }
+
+ // Memory allocated, update the source object and copy data
+ // across.
+ free(buffer_);
+ buffer_ = newbuff;
+ static_cast<void>(std::memmove(buffer_, other.buffer_, other.size_));
+
+ } else {
+
+ // Nothing allocated in the source object, so zero the buffer
+ // in the destination.
+ free(buffer_);
+ buffer_ = NULL;
+ }
+
+ // Update the other member variables.
+ size_ = other.size_;
+ allocated_ = other.allocated_;
+ }
+ return (*this);
+ }
+
+ ///
+ /// \name Getter Methods
+ ///
+ //@{
+ /// \brief Return the current capacity of the buffer.
+ size_t getCapacity() const { return (allocated_); }
+ /// \brief Return a pointer to the head of the data stored in the buffer.
+ ///
+ /// The caller can assume that the subsequent \c getLength() bytes are
+ /// identical to the stored data of the buffer.
+ ///
+ /// Note: The pointer returned by this method may be invalidated after a
+ /// subsequent write operation.
+ const void* getData() const { return (buffer_); }
+ /// \brief Return the length of data written in the buffer.
+ size_t getLength() const { return (size_); }
+ /// \brief Return the value of the buffer at the specified position.
+ ///
+ /// \c pos must specify the valid position of the buffer; otherwise an
+ /// exception class of \c InvalidBufferPosition will be thrown.
+ ///
+ /// \param pos The position in the buffer to be returned.
+ uint8_t operator[](size_t pos) const {
+ if (pos >= size_) {
+ isc_throw(InvalidBufferPosition,
+ "[]: pos (" << pos << ") >= size (" << size_ << ")");
+ }
+ return (buffer_[pos]);
+ }
+ //@}
+
+ ///
+ /// \name Methods for writing data into the 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) {
+ ensureAllocated(size_ + len);
+ size_ += len;
+ }
+
+ /// \brief Trim the specified length of data from the end of the buffer.
+ ///
+ /// 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) {
+ if (len > size_) {
+ isc_throw(OutOfRange, "trimming too large from output buffer");
+ }
+ size_ -= len;
+ }
+ /// \brief Clear buffer content.
+ ///
+ /// This method can be used to re-initialize and reuse the buffer without
+ /// constructing a new one. Note it must keep current content.
+ void clear() { size_ = 0; }
+
+ /// \brief Wipe buffer content.
+ ///
+ /// This method is the destructive alternative to clear().
+ void wipe() {
+ if (buffer_ != NULL) {
+ static_cast<void>(std::memset(buffer_, 0, allocated_));
+ }
+ size_ = 0;
+ }
+
+ /// \brief Write an unsigned 8-bit integer into the buffer.
+ ///
+ /// \param data The 8-bit integer to be written into the buffer.
+ void writeUint8(uint8_t data) {
+ ensureAllocated(size_ + 1);
+ buffer_[size_ ++] = data;
+ }
+
+ /// \brief Write an unsigned 8-bit integer into the buffer.
+ ///
+ /// The position must be lower than the size of the buffer,
+ /// otherwise an exception of class \c isc::dns::InvalidBufferPosition
+ /// will be thrown.
+ ///
+ /// \param data The 8-bit integer to be written into the buffer.
+ /// \param pos The position in the buffer to write the data.
+ void writeUint8At(uint8_t data, size_t pos) {
+ if (pos + sizeof(data) > size_) {
+ isc_throw(InvalidBufferPosition, "write at invalid position");
+ }
+ buffer_[pos] = data;
+ }
+
+ /// \brief Write an unsigned 16-bit integer in host byte order into the
+ /// buffer in network byte order.
+ ///
+ /// \param data The 16-bit integer to be written into the buffer.
+ void writeUint16(uint16_t data) {
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff00U) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x00ffU);
+ }
+
+ /// \brief Write an unsigned 16-bit integer in host byte order at the
+ /// specified position of the 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::dns::InvalidBufferPosition will
+ /// be thrown.
+ /// Note also that this method never extends the buffer.
+ ///
+ /// \param data The 16-bit integer to be written into the buffer.
+ /// \param pos The beginning position in the buffer to write the data.
+ void writeUint16At(uint16_t data, size_t pos) {
+ if (pos + sizeof(data) > size_) {
+ isc_throw(InvalidBufferPosition, "write at invalid position");
+ }
+
+ buffer_[pos] = static_cast<uint8_t>((data & 0xff00U) >> 8);
+ buffer_[pos + 1] = static_cast<uint8_t>(data & 0x00ffU);
+ }
+
+ /// \brief Write an unsigned 32-bit integer in host byte order
+ /// into the buffer in network byte order.
+ ///
+ /// \param data The 32-bit integer to be written into the buffer.
+ void writeUint32(uint32_t data) {
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff000000) >> 24);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x00ff0000) >> 16);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x0000ff00) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x000000ff);
+ }
+
+ /// \brief Write an unsigned 64-bit integer in host byte order
+ /// into the buffer in network byte order.
+ ///
+ /// \param data The 64-bit integer to be written into the buffer.
+ void writeUint64(uint64_t data) {
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff00000000000000) >> 56);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x00ff000000000000) >> 48);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x0000ff0000000000) >> 40);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x000000ff00000000) >> 32);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x00000000ff000000) >> 24);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x0000000000ff0000) >> 16);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x000000000000ff00) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x00000000000000ff);
+ }
+
+ /// \brief Copy an arbitrary length of data into the buffer.
+ ///
+ /// No conversion on the copied data is performed.
+ ///
+ /// \param data A pointer to the data to be copied into the buffer.
+ /// \param len The length of the data in bytes.
+ void writeData(const void *data, size_t len) {
+ if (len == 0) {
+ return;
+ }
+
+ ensureAllocated(size_ + len);
+ static_cast<void>(std::memmove(buffer_ + size_, data, len));
+ size_ += len;
+ }
+ //@}
+
+private:
+ /// The actual data
+ uint8_t* buffer_;
+ /// How many bytes are used
+ size_t size_;
+ /// How many bytes do we have preallocated (eg. the capacity)
+ size_t allocated_;
+
+ /// \brief Ensure buffer is appropriate size
+ ///
+ /// Checks that the buffer equal to or larger than the size given as
+ /// argument and extends it to at least that size if not.
+ ///
+ /// \param needed_size The number of bytes required in the buffer
+ void ensureAllocated(size_t needed_size) {
+ if (allocated_ < needed_size) {
+ // Guess some bigger size
+ size_t new_size = (allocated_ == 0) ? 1024 : allocated_;
+ while (new_size < needed_size) {
+ new_size *= 2;
+ }
+ // Allocate bigger space. Note that buffer_ may be NULL,
+ // in which case realloc acts as malloc.
+ uint8_t* new_buffer_(static_cast<uint8_t*>(realloc(buffer_,
+ new_size)));
+ if (new_buffer_ == NULL) {
+ // If it fails, the original block is left intact by it
+ throw std::bad_alloc();
+ }
+ buffer_ = new_buffer_;
+ allocated_ = new_size;
+ }
+ }
+};
+
+/// \brief Pointer-like types pointing to \c InputBuffer or \c OutputBuffer
+///
+/// These types are 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<InputBuffer> InputBufferPtr;
+typedef boost::shared_ptr<OutputBuffer> OutputBufferPtr;
+
+} // namespace util
+} // namespace isc
+#endif // BUFFER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/chrono_time_utils.cc b/src/lib/util/chrono_time_utils.cc
new file mode 100644
index 0000000..6c76112
--- /dev/null
+++ b/src/lib/util/chrono_time_utils.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2015-2020 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/chrono_time_utils.h>
+#include <sstream>
+#include <iomanip>
+
+using namespace std::chrono;
+
+namespace isc {
+namespace util {
+
+std::string
+clockToText(std::chrono::system_clock::time_point t, size_t fsecs_precision) {
+ time_t tt = system_clock::to_time_t(t);
+ struct tm tm;
+ localtime_r(&tt, &tm);
+ std::stringstream s;
+ s << (tm.tm_year + 1900)
+ << "-" << std::setw(2) << std::setfill('0') << (tm.tm_mon + 1)
+ << "-" << std::setw(2) << std::setfill('0') << tm.tm_mday
+ << " " << std::setw(2) << std::setfill('0') << tm.tm_hour
+ << ":" << std::setw(2) << std::setfill('0') << tm.tm_min
+ << ":" << std::setw(2) << std::setfill('0') << tm.tm_sec;
+
+ // If the requested precision is less than the maximum native precision
+ // we will divide the fractional seconds value by 10^(max - requested)
+ if (fsecs_precision) {
+ system_clock::duration dur = t - system_clock::from_time_t(tt);
+ microseconds frac = duration_cast<microseconds>(dur);
+ auto fsecs = frac.count();
+ size_t width = MAX_FSECS_PRECISION;
+ if (fsecs_precision < width) {
+ for (auto i = 0; i < width - fsecs_precision; ++i) {
+ fsecs /= 10;
+ }
+
+ width = fsecs_precision;
+ }
+
+ s << "." << std::setw(width)
+ << std::setfill('0')
+ << fsecs;
+ }
+
+ return (s.str());
+}
+
+template<typename Duration> std::string
+durationToText(Duration dur, size_t fsecs_precision) {
+ seconds unfrac = duration_cast<seconds>(dur);
+ auto secs = unfrac.count();
+ std::stringstream s;
+ auto hours = secs / 3600;
+ secs -= hours * 3600;
+ s << std::setw(2) << std::setfill('0') << hours;
+ auto mins = secs / 60;
+ secs -= mins * 60;
+ s << ":" << std::setw(2) << std::setfill('0') << mins
+ << ":" << std::setw(2) << std::setfill('0') << secs;
+
+ // If the requested precision is less than the maximum native precision
+ // we will divide the fractional seconds value by 10^(max - requested)
+ if (fsecs_precision) {
+ microseconds frac = duration_cast<microseconds>(dur);
+ frac -= duration_cast<microseconds>(unfrac);
+ auto fsecs = frac.count();
+ size_t width = MAX_FSECS_PRECISION;
+ if (fsecs_precision < width) {
+ for (auto i = 0; i < width - fsecs_precision; ++i) {
+ fsecs /= 10;
+ }
+
+ width = fsecs_precision;
+ }
+
+ s << "." << std::setw(width)
+ << std::setfill('0')
+ << fsecs;
+ }
+
+ return (s.str());
+}
+
+// Instantiate for standard clocks.
+template std::string
+durationToText<system_clock::duration>(system_clock::duration dur,
+ size_t fsecs_precision);
+
+#if !CHRONO_SAME_DURATION
+template std::string
+durationToText<steady_clock::duration>(steady_clock::duration dur,
+ size_t fsecs_precision);
+#endif
+
+} // end of isc::util namespace
+} // end of isc namespace
diff --git a/src/lib/util/chrono_time_utils.h b/src/lib/util/chrono_time_utils.h
new file mode 100644
index 0000000..3ef0848
--- /dev/null
+++ b/src/lib/util/chrono_time_utils.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2015-2020 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 KEA_CHRONO_TIME_UTILS_H
+#define KEA_CHRONO_TIME_UTILS_H
+
+#include <chrono>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief The number of digits of fractional seconds supplied by the
+/// underlying class, std::chrono::time_point. Typically 6 = microseconds.
+const size_t MAX_FSECS_PRECISION = 6;
+
+/// @brief Converts chrono time point structure to text
+///
+/// This is Kea implementation for converting time point to strings.
+/// @param t time point value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Zero omits the value.
+///
+/// @return a string representing time
+std::string clockToText(std::chrono::system_clock::time_point t,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+/// @brief Converts StatsDuration to text
+///
+/// See @ref clockToText for explanation why we chose our own implementation.
+/// @tparam Duration duration type instance for instance
+/// @c std::chrono::system_clock::duration.
+/// @param dur duration value to convert to text
+/// @param fsecs_precision number of digits of precision for fractional seconds.
+/// Zero omits the value.
+///
+/// @return a string representing time
+template<typename Duration>
+std::string durationToText(Duration dur,
+ size_t fsecs_precision = MAX_FSECS_PRECISION);
+
+} // end of isc::util namespace
+} // end of isc namespace
+
+#endif
diff --git a/src/lib/util/csv_file.cc b/src/lib/util/csv_file.cc
new file mode 100644
index 0000000..f402038
--- /dev/null
+++ b/src/lib/util/csv_file.cc
@@ -0,0 +1,557 @@
+// Copyright (C) 2014-2021 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/csv_file.h>
+
+#include <algorithm>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <iomanip>
+
+namespace isc {
+namespace util {
+
+CSVRow::CSVRow(const size_t cols, const char separator)
+ : separator_(1, separator), values_(cols) {
+}
+
+CSVRow::CSVRow(const std::string& text, const char separator)
+ : separator_(1, separator) {
+ // Parsing is exception safe, so this will not throw.
+ parse(text);
+}
+
+void
+CSVRow::parse(const std::string& line) {
+ size_t sep_pos = 0;
+ size_t prev_pos = 0;
+ size_t len = 0;
+
+ // In case someone is reusing the row.
+ values_.clear();
+
+ // Iterate over line, splitting on separators.
+ while (prev_pos < line.size()) {
+ // Find the next separator.
+ sep_pos = line.find_first_of(separator_, prev_pos);
+ if (sep_pos == std::string::npos) {
+ break;
+ }
+
+ // Extract the value for the previous column.
+ len = sep_pos - prev_pos;
+ values_.push_back(line.substr(prev_pos, len));
+
+ // Move past the separator.
+ prev_pos = sep_pos + 1;
+ };
+
+ // Extract the last column.
+ len = line.size() - prev_pos;
+ values_.push_back(line.substr(prev_pos, len));
+}
+
+std::string
+CSVRow::readAt(const size_t at) const {
+ checkIndex(at);
+ return (values_[at]);
+}
+
+std::string
+CSVRow::readAtEscaped(const size_t at) const {
+ return (unescapeCharacters(readAt(at)));
+}
+
+std::string
+CSVRow::render() const {
+ std::ostringstream s;
+ for (size_t i = 0; i < values_.size(); ++i) {
+ // Do not put separator before the first value.
+ if (i > 0) {
+ s << separator_;
+ }
+ s << values_[i];
+ }
+ return (s.str());
+}
+
+void
+CSVRow::writeAt(const size_t at, const char* value) {
+ checkIndex(at);
+ values_[at] = value;
+}
+
+void
+CSVRow::writeAtEscaped(const size_t at, const std::string& value) {
+ writeAt(at, escapeCharacters(value, separator_));
+}
+
+void
+CSVRow::trim(const size_t count) {
+ checkIndex(count);
+ values_.resize(values_.size() - count);
+}
+
+std::ostream& operator<<(std::ostream& os, const CSVRow& row) {
+ os << row.render();
+ return (os);
+}
+
+void
+CSVRow::checkIndex(const size_t at) const {
+ if (at >= values_.size()) {
+ isc_throw(CSVFileError, "value index '" << at << "' of the CSV row"
+ " is out of bounds; maximal index is '"
+ << (values_.size() - 1) << "'");
+ }
+}
+
+CSVFile::CSVFile(const std::string& filename)
+ : filename_(filename), fs_(), cols_(0), read_msg_() {
+}
+
+CSVFile::~CSVFile() {
+ close();
+}
+
+void
+CSVFile::close() {
+ // It is allowed to close multiple times. If file has been already closed,
+ // this is no-op.
+ if (fs_) {
+ fs_->close();
+ fs_.reset();
+ }
+}
+
+bool
+CSVFile::exists() const {
+ std::ifstream fs(filename_.c_str());
+ const bool file_exists = fs.good();
+ fs.close();
+ return (file_exists);
+}
+
+void
+CSVFile::flush() const {
+ checkStreamStatusAndReset("flush");
+ fs_->flush();
+}
+
+void
+CSVFile::addColumn(const std::string& col_name) {
+ // It is not allowed to add a new column when file is open.
+ if (fs_) {
+ isc_throw(CSVFileError, "attempt to add a column '" << col_name
+ << "' while the file '" << getFilename()
+ << "' is open");
+ }
+ addColumnInternal(col_name);
+}
+
+void
+CSVFile::addColumnInternal(const std::string& col_name) {
+ if (std::find(cols_.begin(), cols_.end(), col_name) != cols_.end()) {
+ isc_throw(CSVFileError, "attempt to add duplicate column '"
+ << col_name << "'");
+ }
+ cols_.push_back(col_name);
+}
+
+void
+CSVFile::append(const CSVRow& row) const {
+ checkStreamStatusAndReset("append");
+
+ if (row.getValuesCount() != getColumnCount()) {
+ isc_throw(CSVFileError, "number of values in the CSV row '"
+ << row.getValuesCount() << "' doesn't match the number of"
+ " columns in the CSV file '" << getColumnCount() << "'");
+ }
+
+ /// @todo Apparently, seekp and seekg are interchangeable. A call to seekp
+ /// results in moving the input pointer too. This is ok for now. It means
+ /// that when the append() is called, the read pointer is moved to the EOF.
+ /// For the current use cases we only read a file and then append a new
+ /// content. If we come up with the scenarios when read and write is
+ /// needed at the same time, we may revisit this: perhaps remember the
+ /// old pointer. Also, for safety, we call both functions so as we are
+ /// sure that both pointers are moved.
+ fs_->seekp(0, std::ios_base::end);
+ fs_->seekg(0, std::ios_base::end);
+ fs_->clear();
+
+ std::string text = row.render();
+ *fs_ << text << std::endl;
+ if (!fs_->good()) {
+ fs_->clear();
+ isc_throw(CSVFileError, "failed to write CSV row '"
+ << text << "' to the file '" << filename_ << "'");
+ }
+}
+
+void
+CSVFile::checkStreamStatusAndReset(const std::string& operation) const {
+ if (!fs_) {
+ isc_throw(CSVFileError, "NULL stream pointer when performing '"
+ << operation << "' on file '" << filename_ << "'");
+
+ } else if (!fs_->is_open()) {
+ fs_->clear();
+ isc_throw(CSVFileError, "closed stream when performing '"
+ << operation << "' on file '" << filename_ << "'");
+
+ } else {
+ fs_->clear();
+ }
+}
+
+std::streampos
+CSVFile::size() const {
+ std::ifstream fs(filename_.c_str());
+ bool ok = fs.good();
+ // If something goes wrong, including that the file doesn't exist,
+ // return 0.
+ if (!ok) {
+ fs.close();
+ return (0);
+ }
+ std::ifstream::pos_type pos;
+ try {
+ // Seek to the end of file and see where we are. This is a size of
+ // the file.
+ fs.seekg(0, std::ifstream::end);
+ pos = fs.tellg();
+ fs.close();
+ } catch (const std::exception&) {
+ return (0);
+ }
+ return (pos);
+}
+
+size_t
+CSVFile::getColumnIndex(const std::string& col_name) const {
+ for (size_t i = 0; i < cols_.size(); ++i) {
+ if (cols_[i] == col_name) {
+ return (i);
+ }
+ }
+ isc_throw(isc::OutOfRange, "column '" << col_name << "' doesn't exist");
+}
+
+std::string
+CSVFile::getColumnName(const size_t col_index) const {
+ if (col_index >= cols_.size()) {
+ isc_throw(isc::OutOfRange, "column index " << col_index << " in the "
+ " CSV file '" << filename_ << "' is out of range; the CSV"
+ " file has only " << cols_.size() << " columns ");
+ }
+ return (cols_[col_index]);
+}
+
+bool
+CSVFile::next(CSVRow& row, const bool skip_validation) {
+ // Set something as row validation error. Although, we haven't started
+ // actual row validation we should get rid of any previously recorded
+ // errors so as the caller doesn't interpret them as the current one.
+ setReadMsg("validation not started");
+
+ try {
+ // Check that stream is "ready" for any IO operations.
+ checkStreamStatusAndReset("get next row");
+
+ } catch (const isc::Exception& ex) {
+ setReadMsg(ex.what());
+ return (false);
+ }
+
+ // Get the next non-blank line from the file.
+ std::string line;
+ while (fs_->good() && line.empty()) {
+ std::getline(*fs_, line);
+ }
+
+ // If we didn't read anything...
+ if (line.empty()) {
+ // If we reached the end of file, return an empty row to signal EOF.
+ if (fs_->eof()) {
+ row = EMPTY_ROW();
+ return (true);
+
+ } else if (!fs_->good()) {
+ // If we hit an IO error, communicate it to the caller but do NOT close
+ // the stream. Caller may try again.
+ setReadMsg("error reading a row from CSV file '"
+ + std::string(filename_) + "'");
+ return (false);
+ }
+ }
+
+ // Parse the line.
+ row.parse(line);
+
+ // And check if it is correct.
+ return (skip_validation ? true : validate(row));
+}
+
+void
+CSVFile::open(const bool seek_to_end) {
+ // If file doesn't exist or is empty, we have to create our own file.
+ if (size() == static_cast<std::streampos>(0)) {
+ recreate();
+
+ } else {
+ // Try to open existing file, holding some data.
+ fs_.reset(new std::fstream(filename_.c_str()));
+
+ // Catch exceptions so as we can close the file if error occurs.
+ try {
+ // The file may fail to open. For example, because of insufficient
+ // permissions. Although the file is not open we should call close
+ // to reset our internal pointer.
+ if (!fs_->is_open()) {
+ isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
+ }
+ // Make sure we are on the beginning of the file, so as we
+ // can parse the header.
+ fs_->seekg(0);
+ if (!fs_->good()) {
+ isc_throw(CSVFileError, "unable to set read pointer in the file '"
+ << filename_ << "'");
+ }
+
+ // Read the header.
+ CSVRow header;
+ if (!next(header, true)) {
+ isc_throw(CSVFileError, "failed to read and parse header of the"
+ " CSV file '" << filename_ << "': "
+ << getReadMsg());
+ }
+
+ // Check the header against the columns specified for the CSV file.
+ if (!validateHeader(header)) {
+ isc_throw(CSVFileError, "invalid header '" << header
+ << "' in CSV file '" << filename_ << "': "
+ << getReadMsg());
+ }
+
+ // Everything is good, so if we haven't added any columns yet,
+ // add them.
+ if (getColumnCount() == 0) {
+ for (size_t i = 0; i < header.getValuesCount(); ++i) {
+ addColumnInternal(header.readAt(i));
+ }
+ }
+
+ // If caller requested that the pointer is set at the end of file,
+ // move both read and write pointer.
+ if (seek_to_end) {
+ fs_->seekp(0, std::ios_base::end);
+ fs_->seekg(0, std::ios_base::end);
+ if (!fs_->good()) {
+ isc_throw(CSVFileError, "unable to move to the end of"
+ " CSV file '" << filename_ << "'");
+ }
+ fs_->clear();
+ }
+
+ } catch (const std::exception&) {
+ close();
+ throw;
+ }
+ }
+}
+
+void
+CSVFile::recreate() {
+ // There is no sense creating a file if we don't specify columns for it.
+ if (getColumnCount() == 0) {
+ close();
+ isc_throw(CSVFileError, "no columns defined for the newly"
+ " created CSV file '" << filename_ << "'");
+ }
+
+ // Close any dangling files.
+ close();
+ fs_.reset(new std::fstream(filename_.c_str(), std::fstream::out));
+ if (!fs_->is_open()) {
+ close();
+ isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
+ }
+ // Opened successfully. Write a header to it.
+ try {
+ CSVRow header(getColumnCount());
+ for (size_t i = 0; i < getColumnCount(); ++i) {
+ header.writeAt(i, getColumnName(i));
+ }
+ *fs_ << header << std::endl;
+
+ } catch (const std::exception& ex) {
+ close();
+ isc_throw(CSVFileError, ex.what());
+ }
+
+}
+
+bool
+CSVFile::validate(const CSVRow& row) {
+ setReadMsg("success");
+ bool ok = (row.getValuesCount() == getColumnCount());
+ if (!ok) {
+ std::ostringstream s;
+ s << "the size of the row '" << row << "' doesn't match the number of"
+ " columns '" << getColumnCount() << "' of the CSV file '"
+ << filename_ << "'";
+ setReadMsg(s.str());
+ }
+ return (ok);
+}
+
+bool
+CSVFile::validateHeader(const CSVRow& header) {
+ if (getColumnCount() == 0) {
+ return (true);
+ }
+
+ if (getColumnCount() != header.getValuesCount()) {
+ return (false);
+ }
+
+ for (size_t i = 0; i < getColumnCount(); ++i) {
+ if (getColumnName(i) != header.readAt(i)) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+const std::string CSVRow::escape_tag("&#x");
+
+std::string
+CSVRow::escapeCharacters(const std::string& orig_str, const std::string& characters) {
+ size_t char_pos = 0;
+ size_t prev_pos = 0;
+
+ // We add the first character of the escape tag to the list of
+ // characters to escape. This ensures input which happens to
+ // be valid escape sequences will be escaped.
+ std::string escape_chars(characters + escape_tag[0]);
+
+ // Check for a first occurrence. If none, just return a
+ // copy of the original.
+ char_pos = orig_str.find_first_of(escape_chars, prev_pos);
+ if (char_pos == std::string::npos) {
+ return(orig_str);
+ }
+
+ std::stringstream ss;
+ while (char_pos < orig_str.size()) {
+ // Copy everything upto the character to escape.
+ ss << orig_str.substr(prev_pos, char_pos - prev_pos);
+
+ // Copy the escape tag followed by the hex digits of the character.
+ ss << escape_tag << std::hex << std::setw(2)
+ << static_cast<uint16_t>(orig_str[char_pos]);
+
+ ++char_pos;
+ prev_pos = char_pos;
+
+ // Find the next character to escape.
+ char_pos = orig_str.find_first_of(escape_chars, prev_pos);
+
+ // If no more, copy the remainder of the string.
+ if (char_pos == std::string::npos) {
+ ss << orig_str.substr(prev_pos, char_pos - prev_pos);
+ break;
+ }
+
+ };
+
+ // Return the escaped string.
+ return(ss.str());
+}
+
+std::string
+CSVRow::unescapeCharacters(const std::string& escaped_str) {
+ size_t esc_pos = 0;
+ size_t start_pos = 0;
+
+ // Look for the escape tag.
+ esc_pos = escaped_str.find(escape_tag, start_pos);
+ if (esc_pos == std::string::npos) {
+ // No escape tags at all, we're done.
+ return(escaped_str);
+ }
+
+ // We have at least one escape tag.
+ std::stringstream ss;
+ while (esc_pos < escaped_str.size()) {
+ // Save everything up to the tag.
+ ss << escaped_str.substr(start_pos, esc_pos - start_pos);
+
+ // Now we need to see if we have valid hex digits
+ // following the tag.
+ unsigned int escaped_char = 0;
+ bool converted = true;
+ size_t dig_pos = esc_pos + escape_tag.size();
+ if (dig_pos <= escaped_str.size() - 2) {
+ for (int i = 0; i < 2; ++i) {
+ uint8_t digit = escaped_str[dig_pos];
+
+ if (digit >= 'a' && digit <= 'f') {
+ digit = digit - 'a' + 10;
+ } else if (digit >= 'A' && digit <= 'F') {
+ digit = digit - 'A' + 10;
+ } else if (digit >= '0' && digit <= '9') {
+ digit -= '0';
+ } else {
+ converted = false;
+ break;
+ }
+
+ if (i == 0) {
+ escaped_char = digit << 4;
+ } else {
+ escaped_char |= digit;
+ }
+
+ ++dig_pos;
+ }
+ }
+
+ // If we converted an escaped character, add it.
+ if (converted) {
+ ss << static_cast<unsigned char>(escaped_char);
+ esc_pos = dig_pos;
+ } else {
+ // Apparently the escape_tag was not followed by two valid hex
+ // digits. We'll assume it just happens to be in the string, so
+ // we'll include it in the output.
+ ss << escape_tag;
+ esc_pos += escape_tag.size();
+ }
+
+ // Set the new start of search.
+ start_pos = esc_pos;
+
+ // Look for the next escape tag.
+ esc_pos = escaped_str.find(escape_tag, start_pos);
+
+ // If we're at the end we're done.
+ if (esc_pos == std::string::npos) {
+ // Make sure we grab the remnant.
+ ss << escaped_str.substr(start_pos, esc_pos - start_pos);
+ break;
+ }
+ };
+
+ return(ss.str());
+}
+
+
+} // end of isc::util namespace
+} // end of isc namespace
diff --git a/src/lib/util/csv_file.h b/src/lib/util/csv_file.h
new file mode 100644
index 0000000..65b62ba
--- /dev/null
+++ b/src/lib/util/csv_file.h
@@ -0,0 +1,575 @@
+// Copyright (C) 2014-2020 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 CSV_FILE_H
+#define CSV_FILE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <fstream>
+#include <ostream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during CSV file processing.
+class CSVFileError : public Exception {
+public:
+ CSVFileError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a single row of the CSV file.
+///
+/// The object of this type can create the string holding a collection of the
+/// comma separated values, representing a row of the CSV file. It allows the
+/// selection of any character as a separator for the values. The default
+/// separator is the comma symbol.
+///
+/// The @c CSVRow object can be constructed in two different ways. The first
+/// option is that the caller creates an object holding empty values
+/// and then adds values one by one. Note that it is possible to either add
+/// a string or a number. The number is converted to the appropriate text
+/// representation. When all the values are added, the text representation of
+/// the row can be obtained by calling @c CSVRow::render function or output
+/// stream operator.
+///
+/// The @c CSVRow object can be also constructed by parsing a row of a CSV
+/// file. In this case, the separator has to be known in advance and passed to
+/// the class constructor. The constructor will call the @c CSVRow::parse
+/// function internally to tokenize the CSV row and create the collection of
+/// values. The class accessors can be then used to retrieve individual values.
+///
+/// This class is meant to be used by the @c CSVFile class to manipulate
+/// individual rows of the CSV file.
+class CSVRow {
+public:
+
+ /// @brief Constructor, creates the raw to be used for output.
+ ///
+ /// Creates CSV row with empty values. The values should be
+ /// later set using the @c CSVRow::writeAt functions. When the
+ /// @c CSVRow::render is called, the text representation of the
+ /// row will be created using a separator character specified
+ /// as an argument of this constructor.
+ ///
+ /// This constructor is exception-free.
+ ///
+ /// @param cols Number of values in the row.
+ /// @param separator Character used as a separator between values in the
+ /// text representation of the row.
+ CSVRow(const size_t cols = 0, const char separator = ',');
+
+ /// @brief Constructor, parses a single row of the CSV file.
+ ///
+ /// This constructor should be used to parse a single row of the CSV
+ /// file. The separator being used for the particular row needs to
+ /// be known in advance and specified as an argument of the constructor
+ /// if other than the default separator is used in the row being parsed.
+ /// An example string to be parsed by this function looks as follows:
+ /// "foo,bar,foo-bar".
+ ///
+ /// This constructor is exception-free.
+ ///
+ /// @param text Text representation of the CSV row.
+ /// @param separator Character being used as a separator in a parsed file.
+ CSVRow(const std::string& text, const char separator = ',');
+
+ /// @brief Returns number of values in a CSV row.
+ size_t getValuesCount() const {
+ return (values_.size());
+ }
+
+ /// @brief Parse the CSV file row.
+ ///
+ /// This function parses a string containing CSV values and assigns them
+ /// to the @c values_ private container. These values can be retrieved
+ /// from the container by calling @c CSVRow::readAt function.
+ ///
+ /// This function is exception-free.
+ ///
+ /// @param line String holding a row of comma separated values.
+ void parse(const std::string& line);
+
+ /// @brief Retrieves a value from the internal container.
+ ///
+ /// @param at Index of the value in the container. The values are indexed
+ /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+ ///
+ /// @return Value at specified index in the text form.
+ ///
+ /// @throw CSVFileError if the index is out of range. The number of elements
+ /// being held by the container can be obtained using
+ /// @c CSVRow::getValuesCount.
+ std::string readAt(const size_t at) const;
+
+ /// @brief Retrieves a value from the internal container, free of escaped
+ /// characters.
+ ///
+ /// Returns a copy of the internal container value at the given index
+ /// which has had all escaped characters replaced with their unescaped
+ /// values. Escaped characters embedded using the following format:
+ ///
+ /// This function fetches the value at the given index and passes it
+ /// into CSVRow::unescapeCharacters which replaces any escaped special
+ /// characters with their unescaped form.
+ ///
+ /// @param at Index of the value in the container. The values are indexed
+ /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+ ///
+ /// @return Value at specified index in the text form.
+ ///
+ /// @throw CSVFileError if the index is out of range. The number of elements
+ /// being held by the container can be obtained using
+ /// @c CSVRow::getValuesCount.
+ std::string readAtEscaped(const size_t at) const;
+
+ /// @brief Trims a given number of elements from the end of a row
+ ///
+ /// @param count number of elements to trim
+ ///
+ /// @throw CSVFileError if the number to trim is larger than
+ /// then the number of elements
+ void trim(const size_t count);
+
+ /// @brief Retrieves a value from the internal container.
+ ///
+ /// This method is reads a value from the internal container and converts
+ /// this value to the type specified as a template parameter. Internally
+ /// it uses @c boost::lexical_cast.
+ ///
+ /// @param at Index of the value in the container. The values are indexed
+ /// from 0, where 0 corresponds to the left-most value in the CSV file row.
+ /// @tparam T type of the value to convert to.
+ ///
+ /// @return Converted value.
+ ///
+ /// @throw CSVFileError if the index is out of range or if the
+ /// @c boost::bad_lexical_cast is thrown by the @c boost::lexical_cast.
+ template<typename T>
+ T readAndConvertAt(const size_t at) const {
+ T cast_value;
+ try {
+ cast_value = boost::lexical_cast<T>(readAt(at).c_str());
+
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(CSVFileError, ex.what());
+ }
+ return (cast_value);
+ }
+
+ /// @brief Creates a text representation of the CSV file row.
+ ///
+ /// This function iterates over all values currently held in the internal
+ /// @c values_ container and appends them to a string. The values are
+ /// separated using the separator character specified in the constructor.
+ ///
+ /// This function is exception free.
+ ///
+ /// @return Text representation of the CSV file row.
+ std::string render() const;
+
+ /// @brief Replaces the value at specified index.
+ ///
+ /// This function is used to set values to be rendered using
+ /// @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written given as string.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ void writeAt(const size_t at, const char* value);
+
+ /// @brief Replaces the value at specified index.
+ ///
+ /// This function is used to set values to be rendered using
+ /// @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written given as string.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ void writeAt(const size_t at, const std::string& value) {
+ writeAt(at, value.c_str());
+ }
+
+ /// @brief Replaces the value at the specified index with a value that has
+ /// had special characters escaped
+ ///
+ /// This function first calls @c CSVRow::escapeCharacters to replace
+ /// special characters with their escaped form. It then sets the value
+ /// to be rendered using @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written given as string.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ void writeAtEscaped(const size_t at, const std::string& value);
+
+ /// @brief Appends the value as a new column.
+ ///
+ /// @param value Value to be written.
+ /// @tparam T Type of the value being written.
+ template<typename T>
+ void append(const T value) {
+ try {
+ values_.push_back(boost::lexical_cast<std::string>(value));
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(CSVFileError, "unable to stringify the value to be "
+ "appended to the CSV file row.");
+ }
+ }
+
+ /// @brief Replaces the value at specified index.
+ ///
+ /// This function is used to set values to be rendered using
+ /// @c CSVRow::render function.
+ ///
+ /// @param at Index of the value to be replaced.
+ /// @param value Value to be written - typically a number.
+ /// @tparam T Type of the value being written.
+ ///
+ /// @throw CSVFileError if index is out of range.
+ template<typename T>
+ void writeAt(const size_t at, const T value) {
+ checkIndex(at);
+ try {
+ values_[at] = boost::lexical_cast<std::string>(value);
+ } catch (const boost::bad_lexical_cast& ex) {
+ isc_throw(CSVFileError, "unable to stringify the value to be"
+ " written in the CSV file row at position '"
+ << at << "'");
+ }
+ }
+
+ /// @brief Equality operator.
+ ///
+ /// Two CSV rows are equal when their string representation is equal. This
+ /// includes the order of fields, separator etc.
+ ///
+ /// @param other Object to compare to.
+ bool operator==(const CSVRow& other) const {
+ return (render() == other.render());
+ }
+
+ /// @brief Unequality operator.
+ ///
+ /// Two CSV rows are unequal when their string representation is unequal.
+ /// This includes the order of fields, separator etc.
+ ///
+ /// @param other Object to compare to.
+ bool operator!=(const CSVRow& other) const {
+ return (render() != other.render());
+ }
+
+ /// @brief Returns a copy of a string with special characters escaped
+ ///
+ /// @param orig_str string which may contain characters that require
+ /// escaping.
+ /// @param characters list of characters which require escaping.
+ ///
+ /// The escaped characters will use the following format:
+ ///
+ /// @verbatim
+ /// &#x{xx}
+ /// @endverbatim
+ ///
+ /// where {xx} is the two digit hexadecimal ASCII value of the character
+ /// escaped. A comma, for example is:
+ ///
+ /// &\#x2c
+ ///
+ /// @return A copy of the original string with special characters escaped.
+ static std::string escapeCharacters(const std::string& orig_str,
+ const std::string& characters);
+
+ /// @brief Returns a copy of a string with special characters unescaped
+ ///
+ /// This function reverses the escaping of characters done by @c
+ /// CSVRow::escapeCharacters.
+ ///
+ /// @param escaped_str string which may contain escaped characters.
+ ///
+ /// @return A string free of escaped characters
+ static std::string unescapeCharacters(const std::string& escaped_str);
+
+private:
+
+ /// @brief Check if the specified index of the value is in range.
+ ///
+ /// This function is used internally by other functions.
+ ///
+ /// @param at Value index.
+ /// @throw CSVFileError if specified index is not in range.
+ void checkIndex(const size_t at) const;
+
+ /// @brief Separator character specified in the constructor.
+ ///
+ /// @note Separator is held as a string object (one character long),
+ /// because the boost::is_any_of algorithm requires a string, not a
+ /// char value. If we held the separator as a char, we would need to
+ /// convert it to string on every call to @c CSVRow::parse.
+ std::string separator_;
+
+ /// @brief Internal container holding values that belong to the row.
+ std::vector<std::string> values_;
+
+ /// @brief Prefix used to escape special characters.
+ static const std::string escape_tag;
+};
+
+/// @brief Overrides standard output stream operator for @c CSVRow object.
+///
+/// The resulting string of characters is the same as the one returned by
+/// @c CSVRow::render function.
+///
+/// @param os Output stream.
+/// @param row Object representing a CSV file row.
+std::ostream& operator<<(std::ostream& os, const CSVRow& row);
+
+/// @brief Provides input/output access to CSV files.
+///
+/// This class provides basic methods to access (parse) and create CSV files.
+/// The file is identified by its name qualified with the absolute path.
+/// The name of the file is passed to the constructor. Constructor doesn't
+/// open/create a file, but simply records a file name specified by a caller.
+///
+/// There are two functions that can be used to open a file:
+/// - @c open - opens an existing file; if the file doesn't exist it creates it,
+/// - @c recreate - removes existing file and creates a new one.
+///
+/// When the file is opened its header file is parsed and column names are
+/// identified. At this point it is already possible to get the list of the
+/// column names using appropriate accessors. The data rows are not parsed
+/// at this time. The row parsing is triggered by calling @c next function.
+/// The result of parsing a row is stored in the @c CSVRow object passed as
+/// a parameter.
+///
+/// When the new file is created (when @c recreate is called), the CSV header is
+/// immediately written into it. The header consists of the column names
+/// specified with the @c addColumn function. The subsequent rows are written
+/// into this file by calling @c append.
+class CSVFile {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param filename CSV file name.
+ CSVFile(const std::string& filename);
+
+ /// @brief Destructor
+ virtual ~CSVFile();
+
+ /// @brief Adds new column name.
+ ///
+ /// This column adds a new column but doesn't write it to the file yet.
+ /// The name of the column will be placed in the CSV header when new file
+ /// is created by calling @c recreate or @c open function.
+ ///
+ /// @param col_name Name of the column.
+ ///
+ /// @throw CSVFileError if a column with the specified name exists.
+ void addColumn(const std::string& col_name);
+
+ /// @brief Writes the CSV row into the file.
+ ///
+ /// @param row Object representing a CSV file row.
+ ///
+ /// @throw CSVFileError When error occurred during IO operation or if the
+ /// size of the row doesn't match the number of columns.
+ void append(const CSVRow& row) const;
+
+ /// @brief Closes the CSV file.
+ void close();
+
+ /// @brief Checks if the CSV file exists and can be opened for reading.
+ ///
+ /// This method doesn't check if the existing file has a correct file
+ /// format.
+ ///
+ /// @return true if file exists, false otherwise.
+ bool exists() const;
+
+ /// @brief Flushes a file.
+ void flush() const;
+
+ /// @brief Returns the number of columns in the file.
+ size_t getColumnCount() const {
+ return (cols_.size());
+ }
+
+ /// @brief Returns the path to the CSV file.
+ std::string getFilename() const {
+ return (filename_);
+ }
+
+ /// @brief Returns the description of the last error returned by the
+ /// @c CSVFile::next function.
+ ///
+ /// @return Description of the last error during row validation.
+ std::string getReadMsg() const {
+ return (read_msg_);
+ }
+
+ /// @brief Returns the index of the column having specified name.
+ ///
+ /// This function is exception safe.
+ ///
+ /// @param col_name Name of the column.
+ /// @return Index of the column.
+ /// @throw OutOfRange if column with such name doesn't exist.
+ size_t getColumnIndex(const std::string& col_name) const;
+
+ /// @brief Returns the name of the column.
+ ///
+ /// @param col_index Index of the column.
+ ///
+ /// @return Name of the column.
+ /// @throw CSVFileError if the specified index is out of range.
+ std::string getColumnName(const size_t col_index) const;
+
+ /// @brief Reads next row from CSV file.
+ ///
+ /// This function will return the @c CSVRow object representing a
+ /// parsed row if parsing is successful. If the end of file has been
+ /// reached, the empty row is returned (a row containing no values).
+ ///
+ /// @param [out] row Object receiving the parsed CSV file.
+ /// @param skip_validation Do not perform validation.
+ ///
+ /// @return true if row has been read and validated; false if validation
+ /// failed.
+ bool next(CSVRow& row, const bool skip_validation = false);
+
+ /// @brief Opens existing file or creates a new one.
+ ///
+ /// This function will try to open existing file if this file has size
+ /// greater than 0. If the file doesn't exist or has size of 0, the
+ /// file is recreated. If the existing file has been opened, the header
+ /// is parsed and column names are initialized in the @c CSVFile object.
+ /// By default, the data pointer in the file is set to the beginning of
+ /// the first row. In order to retrieve the row contents the @c next
+ /// function should be called. If a @c seek_to_end parameter is set to
+ /// true, the file will be opened and the internal pointer will be set
+ /// to the end of file.
+ ///
+ /// @param seek_to_end A boolean value which indicates if the input and
+ /// output file pointer should be set at the end of file.
+ ///
+ /// @throw CSVFileError when IO operation fails.
+
+ virtual void open(const bool seek_to_end = false);
+
+ /// @brief Creates a new CSV file.
+ ///
+ /// The file creation will fail if there are no columns specified.
+ /// Otherwise, this function will write the header to the file.
+ /// In order to write rows to opened file, the @c append function
+ /// should be called.
+ virtual void recreate();
+
+ /// @brief Sets error message after row validation.
+ ///
+ /// The @c CSVFile::validate function is responsible for setting the
+ /// error message after validation of the row read from the CSV file.
+ /// It will use this function to set this message. Note, that the
+ /// @c validate function can set a message after successful validation
+ /// too. Such message could say "success", or something similar.
+ ///
+ /// @param read_msg Error message to be set.
+ void setReadMsg(const std::string& read_msg) {
+ read_msg_ = read_msg;
+ }
+
+ /// @brief Represents empty row.
+ static CSVRow EMPTY_ROW() {
+ static CSVRow row(0);
+ return (row);
+ }
+
+protected:
+
+ /// @brief Adds a column regardless if the file is open or not.
+ ///
+ /// This function adds as new column to the collection. It is meant to be
+ /// called internally by the methods of the base class and derived classes.
+ /// It must not be used in the public scope. The @c CSVFile::addColumn
+ /// must be used in the public scope instead, because it prevents addition
+ /// of the new column when the file is open.
+ ///
+ /// @param col_name Name of the column.
+ ///
+ /// @throw CSVFileError if a column with the specified name exists.
+ void addColumnInternal(const std::string& col_name);
+
+ /// @brief Validate the row read from a file.
+ ///
+ /// This function implements a basic validation for the row read from the
+ /// CSV file. It is virtual so as it may be customized in derived classes.
+ ///
+ /// This default implementation checks that the number of values in the
+ /// row corresponds to the number of columns specified for this file.
+ ///
+ /// If row validation fails, the error message is noted and can be retrieved
+ /// using @c CSVFile::getReadMsg. The function which overrides this
+ /// base implementation is responsible for setting the error message using
+ /// @c CSVFile::setReadMsg.
+ ///
+ /// @param row A row to be validated.
+ ///
+ /// @return true if the column is valid; false otherwise.
+ virtual bool validate(const CSVRow& row);
+
+protected:
+
+ /// @brief This function validates the header of the CSV file.
+ ///
+ /// If there are any columns added to the @c CSVFile object, it will
+ /// compare that they exactly match (including order) the header read
+ /// from the file.
+ ///
+ /// This function is called internally by @ref CSVFile::open. Derived classes
+ /// may add extra validation steps.
+ ///
+ /// @param header A row holding a header.
+ /// @return true if header matches the columns; false otherwise.
+ virtual bool validateHeader(const CSVRow& header);
+
+private:
+ /// @brief Sanity check if stream is open.
+ ///
+ /// Checks if the file stream is open so as IO operations can be performed
+ /// on it. This is internally called by the public class members to prevent
+ /// them from performing IO operations on invalid stream and using NULL
+ /// pointer to a stream. The @c clear() method is called on the stream
+ /// after the status has been checked.
+ ///
+ /// @throw CSVFileError if stream is closed or pointer to it is NULL.
+ void checkStreamStatusAndReset(const std::string& operation) const;
+
+ /// @brief Returns size of the CSV file.
+ std::streampos size() const;
+
+ /// @brief CSV file name.
+ std::string filename_;
+
+ /// @brief Holds a pointer to the file stream.
+ boost::shared_ptr<std::fstream> fs_;
+
+ /// @brief Holds CSV file columns.
+ std::vector<std::string> cols_;
+
+ /// @brief Holds last error during row reading or validation.
+ std::string read_msg_;
+};
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // CSV_FILE_H
diff --git a/src/lib/util/dhcp_space.cc b/src/lib/util/dhcp_space.cc
new file mode 100644
index 0000000..46bb3b1
--- /dev/null
+++ b/src/lib/util/dhcp_space.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2022 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/dhcp_space.h>
+
+namespace isc {
+namespace util {
+
+template <>
+char const* cStringDhcpSpace<DHCPv4>() {
+ return "4";
+}
+
+template <>
+char const* cStringDhcpSpace<DHCPv6>() {
+ return "6";
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/dhcp_space.h b/src/lib/util/dhcp_space.h
new file mode 100644
index 0000000..e34c7cb
--- /dev/null
+++ b/src/lib/util/dhcp_space.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2022 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 ISC_UTIL_DHCP_SPACE_H
+#define ISC_UTIL_DHCP_SPACE_H 1
+
+#include <string>
+
+#include <boost/algorithm/string/replace.hpp>
+
+namespace isc {
+namespace util {
+
+enum DhcpSpace {
+ DHCPv4,
+ DHCPv6,
+};
+
+/// @brief Provides the C string representation of the DHCP space.
+///
+/// @tparam D DHCP space
+///
+/// @return "4" or "6"
+template <DhcpSpace D>
+char const* cStringDhcpSpace();
+
+/// @brief Replaces all occurrences of {} with 4 or 6 based on the templated DHCP space.
+///
+/// @tparam D DHCP space
+///
+/// @return the formatted string
+template <DhcpSpace D>
+std::string formatDhcpSpace(char const* const format_string) {
+ std::string result(format_string);
+ boost::replace_all(result, "{}", cStringDhcpSpace<D>());
+ return result;
+}
+
+} // namespace util
+} // namespace isc
+
+#endif // ISC_UTIL_DHCP_SPACE_H
diff --git a/src/lib/util/doubles.h b/src/lib/util/doubles.h
new file mode 100644
index 0000000..780b8fc
--- /dev/null
+++ b/src/lib/util/doubles.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 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 DOUBLES_H
+#define DOUBLES_H
+
+#include <complex>
+
+namespace isc {
+namespace util {
+
+/// @brief Tests two doubles for equivalence within a given tolerance.
+///
+/// @param a comparison operand one
+/// @param b comparison operand two
+/// @param tolerance the amount by which the two values may differ and
+/// still be considered "equal".
+/// @return True if the two values differ by less than the tolerance.
+inline bool areDoublesEquivalent(double a, double b, double tolerance=0.000001) {
+ return(std::abs(a - b) < tolerance);
+}
+
+} // namespace util
+} // namespace isc
+
+#endif // DOUBLES_H
diff --git a/src/lib/util/encode/base16_from_binary.h b/src/lib/util/encode/base16_from_binary.h
new file mode 100644
index 0000000..3eb697d
--- /dev/null
+++ b/src/lib/util/encode/base16_from_binary.h
@@ -0,0 +1,103 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
+#define BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// base16_from_binary.h (derived from boost base64_from_binary.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <exceptions/isc_assert.h>
+
+#include <cstddef> // size_t
+#include <boost/config.hpp> // for BOOST_DEDUCED_TYPENAME
+#if defined(BOOST_NO_STDC_NAMESPACE)
+namespace std{
+ using ::size_t;
+} // namespace std
+#endif
+
+// See base32hex_from_binary.h for why we need base64_from...hpp here.
+#include <boost/archive/iterators/base64_from_binary.hpp>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert binary integers to base16 characters
+
+namespace detail {
+
+template<class CharType>
+struct from_4_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const char * lookup_table =
+ "0123456789"
+ "ABCDEF";
+ isc_throw_assert(t < 16);
+ return (lookup_table[static_cast<size_t>(t)]);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_4_bit<CharType>,
+// transform_width<Base, 4, sizeof(Base::value_type) * 8, CharType>
+// > base16_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+//template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class base16_from_binary :
+ public transform_iterator<
+ detail::from_4_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ BOOST_DEDUCED_TYPENAME detail::from_4_bit<CharType>,
+ Base
+ > super_t;
+
+public:
+ // make composable by using templated constructor
+ template<class T>
+ base16_from_binary(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::from_4_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ base16_from_binary(const base16_from_binary & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::from_4_bit<CharType>()
+ )
+ {}
+// base16_from_binary(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
diff --git a/src/lib/util/encode/base32hex.h b/src/lib/util/encode/base32hex.h
new file mode 100644
index 0000000..0a85b36
--- /dev/null
+++ b/src/lib/util/encode/base32hex.h
@@ -0,0 +1,56 @@
+// 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/.
+
+#ifndef BASE32HEX_H
+#define BASE32HEX_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace util {
+namespace encode {
+
+/// \brief Encode binary data in the base32hex format.
+///
+/// The underlying implementation is shared with \c encodeBase64, and all
+/// description except the format (base32hex) equally applies.
+///
+/// Note: the encoding format is base32hex, not base32.
+///
+/// \param binary A vector object storing the data to be encoded.
+/// \return A newly created string that stores base32hex encoded value for
+/// binary.
+std::string encodeBase32Hex(const std::vector<uint8_t>& binary);
+
+/// \brief Decode a text encoded in the base32hex format into the
+/// original %data.
+///
+/// The underlying implementation is shared with \c decodeBase64, and all
+/// description except the format (base32hex) equally applies.
+///
+/// Note: the encoding format is base32hex, not base32.
+///
+/// \param input A text encoded in the base32hex format.
+/// \param result A vector in which the decoded %data is to be stored.
+void decodeBase32Hex(const std::string& input, std::vector<uint8_t>& result);
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // BASE32HEX_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/base32hex_from_binary.h b/src/lib/util/encode/base32hex_from_binary.h
new file mode 100644
index 0000000..84f2b69
--- /dev/null
+++ b/src/lib/util/encode/base32hex_from_binary.h
@@ -0,0 +1,105 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
+#define BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// base32hex_from_binary.h (derived from boost base64_from_binary.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <exceptions/isc_assert.h>
+
+#include <cstddef> // size_t
+#include <boost/config.hpp> // for BOOST_DEDUCED_TYPENAME
+#if defined(BOOST_NO_STDC_NAMESPACE)
+namespace std{
+ using ::size_t;
+} // namespace std
+#endif
+
+// We use the same boost header files used in "base64_from_". Since the
+// precise path to these headers may vary depending on the boost version we
+// simply include the base64 header here.
+#include <boost/archive/iterators/base64_from_binary.hpp>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert binary integers to base32hex characters
+
+namespace detail {
+
+template<class CharType>
+struct from_5_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const char * lookup_table =
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUV";
+ isc_throw_assert(t < 32);
+ return (lookup_table[static_cast<size_t>(t)]);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_5_bit<CharType>,
+// transform_width<Base, 5, sizeof(Base::value_type) * 8, CharType>
+// > base32hex_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+//template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class base32hex_from_binary :
+ public transform_iterator<
+ detail::from_5_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ BOOST_DEDUCED_TYPENAME detail::from_5_bit<CharType>,
+ Base
+ > super_t;
+
+public:
+ // make composable by using templated constructor
+ template<class T>
+ base32hex_from_binary(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::from_5_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ base32hex_from_binary(const base32hex_from_binary & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::from_5_bit<CharType>()
+ )
+ {}
+// base32hex_from_binary(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
diff --git a/src/lib/util/encode/base64.h b/src/lib/util/encode/base64.h
new file mode 100644
index 0000000..84280ec
--- /dev/null
+++ b/src/lib/util/encode/base64.h
@@ -0,0 +1,71 @@
+// 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/.
+
+#ifndef BASE64_H
+#define BASE64_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace util {
+namespace encode {
+
+/// \brief Encode binary data in the base64 format.
+///
+/// This function returns a new \c std::string object that stores a text
+/// encoded in the base64 format for the given \c binary %data.
+/// The resulting string will be a valid, canonical form of base64
+/// representation as specified in RFC4648.
+///
+/// If memory allocation for the returned string fails, a corresponding
+/// standard exception will be thrown. This function never throws exceptions
+/// otherwise.
+///
+/// \param binary A vector object storing the data to be encoded.
+/// \return A newly created string that stores base64 encoded value for binary.
+std::string encodeBase64(const std::vector<uint8_t>& binary);
+
+/// \brief Decode a text encoded in the base64 format into the original %data.
+///
+/// The \c input argument must be a valid string represented in the base64
+/// format as specified in RFC4648. Space characters (spaces, tabs, newlines)
+/// can be included in \c input and will be ignored. Without spaces, the
+/// length of string must be a multiple of 4 bytes with necessary paddings.
+/// Also it must be encoded using the canonical encoding (see RFC4648).
+/// If any of these conditions is not met, an exception of class
+/// \c isc::BadValue will be thrown.
+///
+/// If \c result doesn't have sufficient capacity to store all decoded %data
+/// and memory allocation fails, a corresponding standard exception will be
+/// thrown. If the caller knows the necessary length (which can in theory
+/// be calculated from the input string), this situation can be avoided by
+/// reserving sufficient space for \c result beforehand.
+///
+/// Any existing %data in \c result will be removed. This is the case in some
+/// of the cases where an exception is thrown; that is, this function only
+/// provides the basic exception guarantee.
+///
+/// \param input A text encoded in the base64 format.
+/// \param result A vector in which the decoded %data is to be stored.
+void decodeBase64(const std::string& input, std::vector<uint8_t>& result);
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // BASE64_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/base_n.cc b/src/lib/util/encode/base_n.cc
new file mode 100644
index 0000000..e0c37e5
--- /dev/null
+++ b/src/lib/util/encode/base_n.cc
@@ -0,0 +1,494 @@
+// Copyright (C) 2010-2022 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/encode/base32hex_from_binary.h>
+#include <util/encode/binary_from_base32hex.h>
+#include <util/encode/base16_from_binary.h>
+#include <util/encode/binary_from_base16.h>
+#include <util/encode/base32hex.h>
+#include <util/encode/base64.h>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/binary_from_base64.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+#ifdef HAVE_BOOST_INTEGER_COMMON_FACTOR_HPP
+#include <boost/integer/common_factor.hpp>
+#else
+#include <boost/math/common_factor.hpp>
+#endif
+
+#include <stdint.h>
+#include <stdexcept>
+#include <iterator>
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace boost::archive::iterators;
+
+namespace isc {
+namespace util {
+namespace encode {
+
+// Some versions of clang cannot handle exceptions in unnamed namespaces
+// so this exception is defined in an 'internal' namespace
+namespace clang_unnamed_namespace_workaround {
+// An internally caught exception to unify a few possible cases of the same
+// error.
+class IncompleteBaseInput : public std::exception {
+};
+} // end namespace internal
+
+// In the following anonymous namespace, we provide a generic framework
+// to encode/decode baseN format. We use the following tools:
+// - boost base64_from_binary/binary_from_base64: provide mapping table for
+// base64.
+// These classes take another iterator (Base) as a template argument, and
+// their dereference operator (operator*()) first retrieves an input value
+// from Base via Base::operator* and converts the value using their mapping
+// table. The converted value is returned as their own operator*.
+// - base{32hex,16}_from_binary/binary_from_base{32hex,16}: provide mapping
+// table for base32hex and base16. A straightforward variation of their
+// base64 counterparts.
+// - EncodeNormalizer/DecodeNormalizer: supplemental filter handling baseN
+// padding characters (=)
+// - boost transform_width: an iterator framework for handling data stream
+// per bit-group. It takes another iterator (Base) and output/input bit
+// numbers (BitsOut/BitsIn) template arguments. A transform_width object
+// internally maintains a bit stream, which can be retrieved per BitsOut
+// bits via its dereference operator (operator*()). It builds the stream
+// by internally iterating over the Base object via Base::operator++ and
+// Base::operator*, using the least BitsIn bits of the result of
+// Base::operator*. In our usage BitsIn for encoding and BitsOut for
+// decoding are always 8 (# of bits for one byte).
+//
+// Its dereference operator
+// retrieves BitsIn bits from the result of "*Base" (if necessary it
+// internally calls ++Base)
+//
+// A conceptual description of how the encoding and decoding work is as
+// follows:
+// Encoding:
+// input binary data => Normalizer (append sufficient number of 0 bits)
+// => transform_width (extract bit groups from the original
+// stream)
+// => baseXX_from_binary (convert each bit group to an
+// encoded byte using the mapping)
+// Decoding:
+// input baseXX text => Normalizer (convert '='s to the encoded characters
+// corresponding to 0, e.g. 'A's in base64)
+// => binary_from_baseXX (convert each encoded byte into
+// the original group bit)
+// => transform_width (build original byte stream by
+// concatenating the decoded bit
+// stream)
+//
+// Below, we define a set of templated classes to handle different parameters
+// for different encoding algorithms.
+namespace {
+// Common constants used for all baseN encoding.
+const char BASE_PADDING_CHAR = '=';
+const uint8_t BINARY_ZERO_CODE = 0;
+
+// EncodeNormalizer is an input iterator intended to be used as a filter
+// between the binary stream and baseXX_from_binary translator (via
+// transform_width). An EncodeNormalizer object is configured with two
+// iterators (base and base_end), specifying the head and end of the input
+// stream. It internally iterators over the original stream, and return
+// each byte of the stream intact via its dereference operator until it
+// reaches the end of the stream. After that the EncodeNormalizer object
+// will return 0 no matter how many times it is subsequently incremented.
+// This is necessary because the input binary stream may not contain
+// sufficient bits for a full encoded text while baseXX_from_binary expects
+// a sufficient length of input.
+// Note: this class is intended to be used within this implementation file,
+// and assumes "base < base_end" on construction without validating the
+// arguments. The behavior is undefined if this assumption doesn't hold.
+class EncodeNormalizer {
+public:
+ // Aliases used to enable iterator behavior on this class
+ using iterator_category = input_iterator_tag;
+ using value_type = uint8_t;
+ using difference_type = ptrdiff_t;
+ using pointer = uint8_t*;
+ using reference = uint8_t&;
+
+ EncodeNormalizer(const vector<uint8_t>::const_iterator& base,
+ const vector<uint8_t>::const_iterator& base_end) :
+ base_(base), base_end_(base_end), in_pad_(false)
+ {}
+ EncodeNormalizer& operator++() { // prefix version
+ increment();
+ return (*this);
+ }
+ EncodeNormalizer operator++(int) { // postfix version
+ const EncodeNormalizer copy = *this;
+ increment();
+ return (copy);
+ }
+ const uint8_t& operator*() const {
+ if (in_pad_) {
+ return (BINARY_ZERO_CODE);
+ } else {
+ return (*base_);
+ }
+ }
+ bool operator==(const EncodeNormalizer& other) const {
+ return (base_ == other.base_);
+ }
+private:
+ void increment() {
+ if (!in_pad_) {
+ ++base_;
+ }
+ if (base_ == base_end_) {
+ in_pad_ = true;
+ }
+ }
+ vector<uint8_t>::const_iterator base_;
+ const vector<uint8_t>::const_iterator base_end_;
+ bool in_pad_;
+};
+
+// DecodeNormalizer is an input iterator intended to be used as a filter
+// between the encoded baseX stream and binary_from_baseXX.
+// A DecodeNormalizer object is configured with three string iterators
+// (base, base_beginpad, and base_end), specifying the head of the string,
+// the beginning position of baseX padding (when there's padding), and
+// end of the string, respectively. It internally iterators over the original
+// stream, and return each character of the encoded string via its dereference
+// operator until it reaches base_beginpad. After that the DecodeNormalizer
+// will return the encoding character corresponding to the all-0 value
+// (which is specified on construction via base_zero_code. see also
+// BaseZeroCode below). This translation is necessary because
+// binary_from_baseXX doesn't accept the padding character (i.e. '=').
+// Note: this class is intended to be used within this implementation file,
+// and for simplicity assumes "base < base_beginpad <= base_end" on
+// construction without validating the arguments. The behavior is undefined
+// if this assumption doesn't hold.
+class DecodeNormalizer {
+public:
+ // Aliases used to enable iterator behavior on this class
+ using iterator_category = input_iterator_tag;
+ using value_type = char;
+ using difference_type = ptrdiff_t;
+ using pointer = char*;
+ using reference = char&;
+
+ DecodeNormalizer(const char base_zero_code,
+ const string::const_iterator& base,
+ const string::const_iterator& base_beginpad,
+ const string::const_iterator& base_end,
+ size_t* char_count) :
+ base_zero_code_(base_zero_code),
+ base_(base), base_beginpad_(base_beginpad), base_end_(base_end),
+ in_pad_(false), char_count_(char_count)
+ {
+ // Skip beginning spaces, if any. We need do it here because
+ // otherwise the first call to operator*() would be confused.
+ skipSpaces();
+ }
+ DecodeNormalizer& operator++() {
+ if (base_ < base_end_) {
+ ++*char_count_;
+ }
+ ++base_;
+ skipSpaces();
+ if (base_ == base_beginpad_) {
+ in_pad_ = true;
+ }
+ return (*this);
+ }
+ void skipSpaces() {
+ // If (char is signed and) *base_ < 0, on Windows platform with Visual
+ // Studio compiler it may trigger _ASSERTE((unsigned)(c + 1) <= 256);
+ // so make sure that the parameter of isspace() is larger than 0.
+ // We don't simply cast it to unsigned char to avoid confusing the
+ // isspace() implementation with a possible extension for values
+ // larger than 127. Also note the check is not ">= 0"; for systems
+ // where char is unsigned that would always be true and would possibly
+ // trigger a compiler warning that could stop the build.
+ while (base_ != base_end_ && *base_ > 0 && isspace(*base_)) {
+ ++base_;
+ }
+ }
+ const char& operator*() const {
+ if (base_ == base_end_) {
+ // binary_from_baseX can call this operator when it needs more bits
+ // even if the internal iterator (base_) has reached its end
+ // (if that happens it means the input is an incomplete baseX
+ // string and should be rejected). So this is the only point
+ // we can catch and reject this type of invalid input.
+ //
+ // More recent versions of Boost fixed the behavior and the
+ // out-of-range call to this operator doesn't happen. It's good,
+ // but in that case we need to catch incomplete baseX input in
+ // a different way. It's done via char_count_ and after the
+ // completion of decoding.
+
+ // throw this now and convert it
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
+ }
+ if (*base_ == BASE_PADDING_CHAR) {
+ // Padding can only happen at the end of the input string. We can
+ // detect any violation of this by checking in_pad_, which is
+ // true iff we are on or after the first valid sequence of padding
+ // characters.
+ if (in_pad_) {
+ return (base_zero_code_);
+ } else {
+ isc_throw(BadValue, "Intermediate padding found");
+ }
+ } else {
+ return (*base_);
+ }
+ }
+ bool operator==(const DecodeNormalizer& other) const {
+ return (base_ == other.base_);
+ }
+private:
+ const char base_zero_code_;
+ string::const_iterator base_;
+ const string::const_iterator base_beginpad_;
+ const string::const_iterator base_end_;
+ bool in_pad_;
+ // Store number of non-space decoded characters (incl. pad) here. Define
+ // it as a pointer so we can carry it over to any copied objects.
+ size_t* char_count_;
+};
+
+// BitsPerChunk: number of bits to be converted using the baseN mapping table.
+// e.g. 6 for base64.
+// BaseZeroCode: the byte character that represents a value of 0 in
+// the corresponding encoding. e.g. 'A' for base64.
+// Encoder: baseX_from_binary<transform_width<EncodeNormalizer,
+// BitsPerChunk, 8> >
+// Decoder: transform_width<binary_from_baseX<DecodeNormalizer>,
+// 8, BitsPerChunk>
+template <int BitsPerChunk, char BaseZeroCode,
+ typename Encoder, typename Decoder>
+struct BaseNTransformer {
+ static string encode(const vector<uint8_t>& binary);
+ static void decode(const char* algorithm,
+ const string& base64, vector<uint8_t>& result);
+
+ // BITS_PER_GROUP is the number of bits for the smallest possible (non
+ // empty) bit string that can be converted to a valid baseN encoded text
+ // without padding. It's the least common multiple of 8 and BitsPerChunk,
+ // e.g. 24 for base64.
+ static const int BITS_PER_GROUP =
+#ifdef HAVE_BOOST_INTEGER_COMMON_FACTOR_HPP
+ boost::integer::static_lcm<BitsPerChunk, 8>::value;
+#else
+ boost::math::static_lcm<BitsPerChunk, 8>::value;
+#endif
+
+ // MAX_PADDING_CHARS is the maximum number of padding characters
+ // that can appear in a valid baseN encoded text.
+ // It's group_len - chars_for_byte, where group_len is the number of
+ // encoded characters to represent BITS_PER_GROUP bits, and
+ // chars_for_byte is the number of encoded character that is needed to
+ // represent a single byte, which is ceil(8 / BitsPerChunk).
+ // For example, for base64 we need two encoded characters to represent a
+ // byte, and each group consists of 4 encoded characters, so
+ // MAX_PADDING_CHARS is 4 - 2 = 2.
+ static const int MAX_PADDING_CHARS =
+ BITS_PER_GROUP / BitsPerChunk -
+ (8 / BitsPerChunk + ((8 % BitsPerChunk) == 0 ? 0 : 1));
+};
+
+template <int BitsPerChunk, char BaseZeroCode,
+ typename Encoder, typename Decoder>
+string
+BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::encode(
+ const vector<uint8_t>& binary)
+{
+ // calculate the resulting length.
+ size_t bits = binary.size() * 8;
+ if (bits % BITS_PER_GROUP > 0) {
+ bits += (BITS_PER_GROUP - (bits % BITS_PER_GROUP));
+ }
+ const size_t len = bits / BitsPerChunk;
+
+ string result;
+ result.reserve(len);
+ result.assign(Encoder(EncodeNormalizer(binary.begin(), binary.end())),
+ Encoder(EncodeNormalizer(binary.end(), binary.end())));
+ isc_throw_assert(len >= result.length());
+ result.append(len - result.length(), BASE_PADDING_CHAR);
+ return (result);
+}
+
+template <int BitsPerChunk, char BaseZeroCode,
+ typename Encoder, typename Decoder>
+void
+BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::decode(
+ const char* const algorithm,
+ const string& input,
+ vector<uint8_t>& result)
+{
+ // enumerate the number of trailing padding characters (=), ignoring
+ // white spaces. since baseN_from_binary doesn't accept padding,
+ // we handle it explicitly.
+ size_t padchars = 0;
+ string::const_reverse_iterator srit = input.rbegin();
+ string::const_reverse_iterator srit_end = input.rend();
+ while (srit != srit_end) {
+ char ch = *srit;
+ if (ch == BASE_PADDING_CHAR) {
+ if (++padchars > MAX_PADDING_CHARS) {
+ isc_throw(BadValue, "Too many " << algorithm
+ << " padding characters: " << input);
+ }
+ } else if (!(ch > 0 && isspace(ch))) {
+ // see the note for DecodeNormalizer::skipSpaces() above for ch > 0
+ break;
+ }
+ ++srit;
+ }
+ // then calculate the number of padding bits corresponding to the padding
+ // characters. In general, the padding bits consist of all-zero
+ // trailing bits of the last encoded character followed by zero bits
+ // represented by the padding characters:
+ // 1st pad 2nd pad 3rd pad...
+ // +++===== ======= ===... (+: from encoded chars, =: from pad chars)
+ // 0000...0 0......0 000...
+ // 0 7 8 15 16.... (bits)
+ // The number of bits for the '==...' part is padchars * BitsPerChunk.
+ // So the total number of padding bits is the smallest multiple of 8
+ // that is >= padchars * BitsPerChunk.
+ // (Below, note the common idiom of the bitwise AND with ~7. It clears the
+ // lowest three bits, so has the effect of rounding the result down to the
+ // nearest multiple of 8)
+ const size_t padbits = (padchars * BitsPerChunk + 7) & ~7;
+
+ // In some encoding algorithm, it could happen that a padding byte would
+ // contain a full set of encoded bits, which is not allowed by definition
+ // of padding. For example, if BitsPerChunk is 5, the following
+ // representation could happen:
+ // ++00000= (+: from encoded chars, 0: encoded char for '0', =: pad chars)
+ // 0 7 (bits)
+ // This must actually be encoded as follows:
+ // ++======
+ // 0 7 (bits)
+ // The following check rejects this type of invalid encoding.
+ if (padbits > BitsPerChunk * (padchars + 1)) {
+ isc_throw(BadValue, "Invalid " << algorithm << " padding: " << input);
+ }
+
+ // convert the number of bits in bytes for convenience.
+ const size_t padbytes = padbits / 8;
+
+ try {
+ size_t char_count = 0;
+ result.assign(Decoder(DecodeNormalizer(BaseZeroCode, input.begin(),
+ srit.base(), input.end(),
+ &char_count)),
+ Decoder(DecodeNormalizer(BaseZeroCode, input.end(),
+ input.end(), input.end(),
+ NULL)));
+
+ // Number of bits of the conversion result including padding must be
+ // a multiple of 8; otherwise the decoder reaches the end of input
+ // with some incomplete bits of data, which is invalid.
+ if (((char_count * BitsPerChunk) % 8) != 0) {
+ // catch this immediately below
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
+ }
+ } catch (const clang_unnamed_namespace_workaround::IncompleteBaseInput&) {
+ // we unify error handling for incomplete input here.
+ isc_throw(BadValue, "Incomplete input for " << algorithm
+ << ": " << input);
+ } catch (const dataflow_exception& ex) {
+ // convert any boost exceptions into our local one.
+ isc_throw(BadValue, ex.what());
+ }
+
+ // Confirm the original BaseX text is the canonical encoding of the
+ // data, that is, that the first byte of padding is indeed 0.
+ // (DecodeNormalizer and binary_from_baseXX ensure that the rest of the
+ // padding is all zero).
+ isc_throw_assert(result.size() >= padbytes);
+ if (padbytes > 0 && *(result.end() - padbytes) != 0) {
+ isc_throw(BadValue, "Non 0 bits included in " << algorithm
+ << " padding: " << input);
+ }
+
+ // strip the padded zero-bit fields
+ result.resize(result.size() - padbytes);
+}
+
+//
+// Instantiation for BASE-64
+//
+typedef
+base64_from_binary<transform_width<EncodeNormalizer, 6, 8> > base64_encoder;
+typedef
+transform_width<binary_from_base64<DecodeNormalizer>, 8, 6> base64_decoder;
+typedef BaseNTransformer<6, 'A', base64_encoder, base64_decoder>
+Base64Transformer;
+
+//
+// Instantiation for BASE-32HEX
+//
+typedef
+base32hex_from_binary<transform_width<EncodeNormalizer, 5, 8> >
+base32hex_encoder;
+typedef
+transform_width<binary_from_base32hex<DecodeNormalizer>, 8, 5>
+base32hex_decoder;
+typedef BaseNTransformer<5, '0', base32hex_encoder, base32hex_decoder>
+Base32HexTransformer;
+
+//
+// Instantiation for BASE-16 (HEX)
+//
+typedef
+base16_from_binary<transform_width<EncodeNormalizer, 4, 8> > base16_encoder;
+typedef
+transform_width<binary_from_base16<DecodeNormalizer>, 8, 4> base16_decoder;
+typedef BaseNTransformer<4, '0', base16_encoder, base16_decoder>
+Base16Transformer;
+}
+
+string
+encodeBase64(const vector<uint8_t>& binary) {
+ return (Base64Transformer::encode(binary));
+}
+
+void
+decodeBase64(const string& input, vector<uint8_t>& result) {
+ Base64Transformer::decode("base64", input, result);
+}
+
+string
+encodeBase32Hex(const vector<uint8_t>& binary) {
+ return (Base32HexTransformer::encode(binary));
+}
+
+void
+decodeBase32Hex(const string& input, vector<uint8_t>& result) {
+ Base32HexTransformer::decode("base32hex", input, result);
+}
+
+string
+encodeHex(const vector<uint8_t>& binary) {
+ return (Base16Transformer::encode(binary));
+}
+
+void
+decodeHex(const string& input, vector<uint8_t>& result) {
+ Base16Transformer::decode("base16", input, result);
+}
+
+} // namespace encode
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/encode/binary_from_base16.h b/src/lib/util/encode/binary_from_base16.h
new file mode 100644
index 0000000..f913dd0
--- /dev/null
+++ b/src/lib/util/encode/binary_from_base16.h
@@ -0,0 +1,112 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE16_HPP
+#define BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE16_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// binary_from_base16.h (derived from boost binary_from_base64.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <cassert>
+
+// See binary_from_base32hex.h for why we need _from_base64.hpp here.
+#include <boost/archive/iterators/binary_from_base64.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert base16 characters to binary data
+
+namespace detail {
+
+template<class CharType>
+struct to_4_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const signed char lookup_table[] = {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 00-0f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 10-1f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 20-2f
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 30-3f
+ -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 40-4f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 50-5f
+ -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 60-6f
+ };
+ BOOST_STATIC_ASSERT(0x70 == sizeof(lookup_table));
+ signed char value = -1;
+ if((unsigned)t < sizeof(lookup_table))
+ value = lookup_table[(unsigned)t];
+ if(-1 == value) {
+ isc_throw(isc::BadValue,
+ "attempt to decode a value not in base16 char set");
+ }
+ return (value);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_4_bit<CharType>,
+// transform_width<Base, 4, sizeof(Base::value_type) * 8, CharType>
+// > base16_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class binary_from_base16 : public
+ transform_iterator<
+ detail::to_4_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ detail::to_4_bit<CharType>,
+ Base
+ > super_t;
+public:
+ // make composable by using templated constructor
+ template<class T>
+ binary_from_base16(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::to_4_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ binary_from_base16(const binary_from_base16 & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::to_4_bit<CharType>()
+ )
+ {}
+// binary_from_base16(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE16_HPP
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/binary_from_base32hex.h b/src/lib/util/encode/binary_from_base32hex.h
new file mode 100644
index 0000000..2911789
--- /dev/null
+++ b/src/lib/util/encode/binary_from_base32hex.h
@@ -0,0 +1,115 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE32HEX_HPP
+#define BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE32HEX_HPP
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// binary_from_base32hex.h (derived from boost binary_from_base64.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+// See http://www.boost.org for updates, documentation, and revision history.
+
+#include <cassert>
+
+// We use the same boost header files used in "_from_base64". Since the
+// precise path to these headers may vary depending on the boost version we
+// simply include the base64 header here.
+#include <boost/archive/iterators/binary_from_base64.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace boost {
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert base32hex characters to binary data
+
+namespace detail {
+
+template<class CharType>
+struct to_5_bit {
+ typedef CharType result_type;
+ CharType operator()(CharType t) const{
+ const signed char lookup_table[] = {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 00-0f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 10-1f
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 20-2f
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, // 30-3f
+ -1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 40-4f
+ 25,26,27,28,29,30,31,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 50-5f
+ -1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 60-6f
+ 25,26,27,28,29,30,31,-1,-1,-1,-1,-1,-1,-1,-1,-1 // 70-7f
+ };
+ BOOST_STATIC_ASSERT(0x80 == sizeof(lookup_table));
+ signed char value = -1;
+ if((unsigned)t < sizeof(lookup_table))
+ value = lookup_table[(unsigned)t];
+ if(-1 == value) {
+ isc_throw(isc::BadValue,
+ "attempt to decode a value not in base32hex char set");
+ }
+ return (value);
+ }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+// typedef transform_iterator<
+// from_5_bit<CharType>,
+// transform_width<Base, 5, sizeof(Base::value_type) * 8, CharType>
+// > base32hex_from_binary;
+// but C++ won't accept this. Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor. This makes it incompatible with the dataflow
+// ideal. This is also addressed here.
+
+template<
+ class Base,
+ class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class binary_from_base32hex : public
+ transform_iterator<
+ detail::to_5_bit<CharType>,
+ Base
+ >
+{
+ friend class boost::iterator_core_access;
+ typedef transform_iterator<
+ detail::to_5_bit<CharType>,
+ Base
+ > super_t;
+public:
+ // make composable by using templated constructor
+ template<class T>
+ binary_from_base32hex(T start) :
+ super_t(
+ Base(static_cast<T>(start)),
+ detail::to_5_bit<CharType>()
+ )
+ {}
+ // intel 7.1 doesn't like default copy constructor
+ binary_from_base32hex(const binary_from_base32hex & rhs) :
+ super_t(
+ Base(rhs.base_reference()),
+ detail::to_5_bit<CharType>()
+ )
+ {}
+// binary_from_base32hex(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BINARY_FROM_BASE32HEX_HPP
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/hex.h b/src/lib/util/encode/hex.h
new file mode 100644
index 0000000..de3ac21
--- /dev/null
+++ b/src/lib/util/encode/hex.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2009-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/.
+
+#ifndef HEX_H
+#define HEX_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace util {
+namespace encode {
+/// \brief Encode binary data in the base16 ('hex') format.
+///
+/// The underlying implementation is shared with \c encodeBase64, and most of
+/// the description except the format (base16) equally applies.
+/// Another notable exception is that the base16 encoding doesn't require
+/// padding, so padding related considerations and the notion of canonical
+/// encoding don't apply.
+///
+/// \param binary A vector object storing the data to be encoded.
+/// \return A newly created string that stores base16 encoded value for
+/// binary.
+std::string encodeHex(const std::vector<uint8_t>& binary);
+
+/// \brief Decode a text encoded in the base16 ('hex') format into the
+/// original %data.
+///
+/// The underlying implementation is shared with \c decodeBase64, and most
+/// of the description except the format (base16) equally applies.
+/// Another notable exception is that the base16 encoding doesn't require
+/// padding, so padding related considerations and the notion of canonical
+/// encoding don't apply.
+///
+/// \param input A text encoded in the base16 format.
+/// \param result A vector in which the decoded %data is to be stored.
+void decodeHex(const std::string& input, std::vector<uint8_t>& result);
+
+/// \brief Encode in hexadecimal inline
+///
+/// \param value the value to encode
+/// \return 0x followed by the value encoded in hexa
+inline std::string toHex(std::string value) {
+ std::vector<uint8_t> bin(value.begin(), value.end());
+ return ("0x" + encodeHex(bin));
+}
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // HEX_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/encode/utf8.cc b/src/lib/util/encode/utf8.cc
new file mode 100644
index 0000000..ac9e0d0
--- /dev/null
+++ b/src/lib/util/encode/utf8.cc
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 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/encode/utf8.h>
+
+namespace isc {
+namespace util {
+namespace encode {
+
+std::vector<uint8_t> encodeUtf8(const std::string& value) {
+ std::vector<uint8_t> result;
+ if (value.empty()) {
+ return (result);
+ }
+ const uint8_t* start = reinterpret_cast<const uint8_t*>(value.c_str());
+ std::vector<uint8_t> binary(start, start + value.size());
+ for (uint8_t ch : binary) {
+ if (ch < 0x80) {
+ result.push_back(ch);
+ } else {
+ result.push_back(0xc0 | (ch >> 6));
+ result.push_back(0x80 | (ch & 0x3f));
+ }
+ }
+ return (result);
+}
+
+} // namespace encode
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/encode/utf8.h b/src/lib/util/encode/utf8.h
new file mode 100644
index 0000000..9eda471
--- /dev/null
+++ b/src/lib/util/encode/utf8.h
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 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 UTF8_H
+#define UTF8_H 1
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace util {
+namespace encode {
+/// @brief Encode value string into UTF-8.
+///
+/// @param value A string in latin1 i.e. no encoding.
+/// @return A vector object storing the data encoded in UTF-8.
+std::vector<uint8_t> encodeUtf8(const std::string& value);
+
+} // namespace encode
+} // namespace util
+} // namespace isc
+
+#endif // UTF8_H
diff --git a/src/lib/util/file_utilities.cc b/src/lib/util/file_utilities.cc
new file mode 100644
index 0000000..dfe5e89
--- /dev/null
+++ b/src/lib/util/file_utilities.cc
@@ -0,0 +1,69 @@
+// Copyright (C) 2021-2022 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/filename.h>
+#include <cerrno>
+#include <cstring>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace file {
+
+string
+getContent(const string& file_name) {
+ // Open the file.
+ int fd = ::open(file_name.c_str(), O_RDONLY);
+ if (fd < 0) {
+ isc_throw(BadValue, "can't open file '" << file_name << "': "
+ << std::strerror(errno));
+ }
+ try {
+ struct stat stats;
+ if (fstat(fd, &stats) < 0) {
+ isc_throw(BadValue, "can't stat file '" << file_name << "': "
+ << std::strerror(errno));
+ }
+ if ((stats.st_mode & S_IFMT) != S_IFREG) {
+ isc_throw(BadValue, "'" << file_name
+ << "' must be a regular file");
+ }
+ string content(stats.st_size, ' ');
+ ssize_t got = ::read(fd, &content[0], stats.st_size);
+ if (got < 0) {
+ isc_throw(BadValue, "can't read file '" << file_name << "': "
+ << std::strerror(errno));
+ }
+ if (got != stats.st_size) {
+ isc_throw(BadValue, "can't read whole file '" << file_name
+ << "' (got " << got << " of " << stats.st_size << ")");
+ }
+ static_cast<void>(close(fd));
+ return (content);
+ } catch (const std::exception&) {
+ static_cast<void>(close(fd));
+ throw;
+ }
+}
+
+bool
+isDir(const string& name) {
+ struct stat stats;
+ if (::stat(name.c_str(), &stats) < 0) {
+ return (false);
+ }
+ return ((stats.st_mode & S_IFMT) == S_IFDIR);
+}
+
+} // namespace file
+} // namespace log
+} // namespace isc
diff --git a/src/lib/util/file_utilities.h b/src/lib/util/file_utilities.h
new file mode 100644
index 0000000..8c34822
--- /dev/null
+++ b/src/lib/util/file_utilities.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2021-2022 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 FILE_UTILITIES_H
+#define FILE_UTILITIES_H
+
+#include <string>
+
+namespace isc {
+namespace util {
+namespace file {
+
+/// @brief Get the content of a regular file.
+///
+/// @param file_name The file name.
+/// @return The content of the file_name file.
+/// @throw BadValue when the file can't be opened or is not a regular one.
+std::string getContent(const std::string& file_name);
+
+/// @brief Is a directory predicate.
+///
+/// @param name The file or directory name.
+/// @return True if the name points to a directory, false otherwise including
+/// if the pointed location does not exist.
+bool isDir(const std::string& name);
+
+} // namespace file
+} // namespace util
+} // namespace isc
+
+#endif // FILE_UTILITIES_H
diff --git a/src/lib/util/filename.cc b/src/lib/util/filename.cc
new file mode 100644
index 0000000..8cd9cd3
--- /dev/null
+++ b/src/lib/util/filename.cc
@@ -0,0 +1,148 @@
+// Copyright (C) 2011-2021 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 <algorithm>
+#include <string>
+
+#include <ctype.h>
+
+#include <util/filename.h>
+
+using namespace std;
+
+
+namespace isc {
+namespace util {
+
+// Split string into components. Any backslashes are assumed to have
+// been replaced by forward slashes.
+
+void
+Filename::split(const string& full_name, string& directory, string& name,
+ string& extension) const {
+ directory = name = extension = "";
+ if (!full_name.empty()) {
+
+ bool dir_present = false;
+ // Find the directory.
+ size_t last_slash = full_name.find_last_of('/');
+ if (last_slash != string::npos) {
+
+ // Found the last slash, so extract directory component and
+ // set where the scan for the last_dot should terminate.
+ directory = full_name.substr(0, last_slash + 1);
+ if (last_slash == full_name.size()) {
+
+ // The entire string was a directory, so exit not and don't
+ // do any more searching.
+ return;
+ }
+
+ // Found a directory so note the fact.
+ dir_present = true;
+ }
+
+ // Now search backwards for the last ".".
+ size_t last_dot = full_name.find_last_of('.');
+ if ((last_dot == string::npos) ||
+ (dir_present && (last_dot < last_slash))) {
+
+ // Last "." either not found or it occurs to the left of the last
+ // slash if a directory was present (so it is part of a directory
+ // name). In this case, the remainder of the string after the slash
+ // is the name part.
+ name = full_name.substr(last_slash + 1);
+ return;
+ }
+
+ // Did find a valid dot, so it and everything to the right is the
+ // extension...
+ extension = full_name.substr(last_dot);
+
+ // ... and the name of the file is everything in between.
+ if ((last_dot - last_slash) > 1) {
+ name = full_name.substr(last_slash + 1, last_dot - last_slash - 1);
+ }
+ }
+
+}
+
+// Expand the stored filename with the default.
+
+string
+Filename::expandWithDefault(const string& defname) const {
+
+ string def_directory("");
+ string def_name("");
+ string def_extension("");
+
+ // Normalize the input string.
+ string copy_defname = isc::util::str::trim(defname);
+#ifdef WIN32
+ isc::util::str::normalizeSlash(copy_defname);
+#endif
+
+ // Split into the components
+ split(copy_defname, def_directory, def_name, def_extension);
+
+ // Now construct the result.
+ string retstring =
+ (directory_.empty() ? def_directory : directory_) +
+ (name_.empty() ? def_name : name_) +
+ (extension_.empty() ? def_extension : extension_);
+ return (retstring);
+}
+
+// Use the stored name as default for a given name
+
+string
+Filename::useAsDefault(const string& name) const {
+
+ string name_directory("");
+ string name_name("");
+ string name_extension("");
+
+ // Normalize the input string.
+ string copy_name = isc::util::str::trim(name);
+#ifdef WIN32
+ isc::util::str::normalizeSlash(copy_name);
+#endif
+
+ // Split into the components
+ split(copy_name, name_directory, name_name, name_extension);
+
+ // Now construct the result.
+ string retstring =
+ (name_directory.empty() ? directory_ : name_directory) +
+ (name_name.empty() ? name_ : name_name) +
+ (name_extension.empty() ? extension_ : name_extension);
+ return (retstring);
+}
+
+void
+Filename::setDirectory(const std::string& new_directory) {
+ std::string directory(new_directory);
+
+ if (directory.length() > 0) {
+ // append '/' if necessary
+ size_t sep = directory.rfind('/');
+ if (sep == std::string::npos || sep < directory.size() - 1) {
+ directory += "/";
+ }
+ }
+ // and regenerate the full name
+ std::string full_name = directory + name_ + extension_;
+
+ directory_.swap(directory);
+ full_name_.swap(full_name);
+}
+
+
+} // namespace log
+} // namespace isc
diff --git a/src/lib/util/filename.h b/src/lib/util/filename.h
new file mode 100644
index 0000000..ae5ccd2
--- /dev/null
+++ b/src/lib/util/filename.h
@@ -0,0 +1,164 @@
+// Copyright (C) 2011-2021 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 FILENAME_H
+#define FILENAME_H
+
+#include <string>
+
+#include <util/strutil.h>
+
+namespace isc {
+namespace util {
+
+/// \brief Class to Manipulate Filenames
+///
+/// This is a utility class to manipulate filenames. It repeats some of the
+/// features found in the Boost filename class, but is self-contained so avoids
+/// the need to link in the Boost library.
+///
+/// A Unix-style filename comprises three parts:
+///
+/// Directory - everything up to and including the last "/". If there is no
+/// "/" in the string, there is no directory component. Note that the
+/// requirement of a trailing slash eliminates the ambiguity of whether a
+/// component is a directory or not, e.g. in /alpha/beta", "beta" could be the
+/// name of a directory or is could be a file. The interpretation here is that
+/// "beta" is the name of a file (although that file could be a directory).
+///
+/// Note: Under Windows, the drive letter is considered to be part of the
+/// directory specification. Unless this class becomes more widely-used on
+/// Windows, there is no point in adding redundant code.
+///
+/// Name - everything from the character after the last "/" up to but not
+/// including the last ".".
+///
+/// Extension - everything from the right-most "." (after the right-most "/") to
+/// the end of the string. If there is no "." after the last "/", there is
+/// no file extension.
+///
+/// (Note that on Windows, this function will replace all "\" characters
+/// with "/" characters on input strings.)
+///
+/// This class provides functions for extracting the components and for
+/// substituting components.
+
+
+class Filename {
+public:
+
+ /// \brief Constructor
+ Filename(const std::string& name) :
+ full_name_(""), directory_(""), name_(""), extension_("") {
+ setName(name);
+ }
+
+ /// \brief Sets Stored Filename
+ ///
+ /// \param name New name to replaced currently stored name
+ void setName(const std::string& name) {
+ full_name_ = isc::util::str::trim(name);
+#ifdef WIN32
+ isc::util::str::normalizeSlash(full_name_);
+#endif
+ split(full_name_, directory_, name_, extension_);
+ }
+
+ /// \return Stored Filename
+ std::string fullName() const {
+ return (full_name_);
+ }
+
+ /// \return Directory of Given File Name
+ std::string directory() const {
+ return (directory_);
+ }
+
+ /// \brief Set directory for the file
+ ///
+ /// \param new_directory The directory to set. If this is an empty
+ /// string, the directory this filename object currently
+ /// has will be removed.
+ void setDirectory(const std::string& new_directory);
+
+ /// \return Name of Given File Name
+ std::string name() const {
+ return (name_);
+ }
+
+ /// \return Extension of Given File Name
+ std::string extension() const {
+ return (extension_);
+ }
+
+ /// \return Name + extension of Given File Name
+ std::string nameAndExtension() const {
+ return (name_ + extension_);
+ }
+
+ /// \brief Expand Name with Default
+ ///
+ /// A default file specified is supplied and used to fill in any missing
+ /// fields. For example, if the name stored is "/a/b" and the supplied
+ /// name is "c.d", the result is "/a/b.d": the only field missing from the
+ /// stored name is the extension, which is supplied by the default.
+ /// Another example would be to store "a.b" and to supply a default of
+ /// "/c/d/" - the result is "/c/d/a.b". (Note that if the supplied default
+ /// was "/c/d", the result would be "/c/a.b", even if "/c/d" were actually
+ /// a directory.)
+ ///
+ /// \param defname Default name
+ ///
+ /// \return Name expanded with defname.
+ std::string expandWithDefault(const std::string& defname) const;
+
+ /// \brief Use as Default and Substitute into String
+ ///
+ /// Does essentially the inverse of expand(); that filled in the stored
+ /// name with a default and returned the result. This treats the stored
+ /// name as the default and uses it to fill in a given name. In essence,
+ /// the code:
+ /// \code
+ /// Filename f("/a/b");
+ /// result = f.expandWithdefault("c.d");
+ /// \endcode
+ /// gives as a result "/a/b.d". This is the same as:
+ /// \code
+ /// Filename f("c.d");
+ /// result = f.useAsDefault("/a/b");
+ /// \endcode
+ ///
+ /// \param name Name to expand
+ ///
+ /// \return Name expanded with stored name
+ std::string useAsDefault(const std::string& name) const;
+
+private:
+ /// \brief Split Name into Components
+ ///
+ /// Splits the file name into the directory, name and extension parts.
+ /// The name is assumed to have had back slashes replaced by forward
+ /// slashes (if appropriate).
+ ///
+ /// \param full_name Name to split
+ /// \param directory Returned directory part
+ /// \param name Returned name part
+ /// \param extension Returned extension part
+ void split(const std::string& full_name, std::string& directory,
+ std::string& name, std::string& extension) const;
+
+ // Members
+
+ std::string full_name_; ///< Given name
+ std::string directory_; ///< Directory part
+ std::string name_; ///< Name part
+ std::string extension_; ///< Extension part
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // FILENAME_H
diff --git a/src/lib/util/hash.h b/src/lib/util/hash.h
new file mode 100644
index 0000000..45d6ba5
--- /dev/null
+++ b/src/lib/util/hash.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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 HASH_H
+#define HASH_H
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Hash implementation based on Fowler-Noll-Vo hash function
+///
+struct Hash64 {
+ /// @brief Compute the hash
+ ///
+ /// FNV-1a hash function
+ ///
+ /// @param data data to hash
+ /// @param length length of data
+ /// @return the hash value
+ static uint64_t hash(const uint8_t* data, size_t length) {
+ uint64_t hash = FNV_offset_basis;
+ for (size_t i = 0; i < length; ++i) {
+ hash = hash ^ data[i];
+ hash = hash * FNV_prime;
+ }
+ return (hash);
+ }
+
+ /// @brief Compute the hash
+ ///
+ /// FNV-1a hash function
+ ///
+ /// @param str not empty string to hash
+ /// @return the hash value
+ static uint64_t hash(const std::string& str) {
+ return (hash(reinterpret_cast<const uint8_t*>(str.c_str()),
+ str.size()));
+ }
+
+ /// @brief Offset basis
+ static const uint64_t FNV_offset_basis = 14695981039346656037ull;
+
+ /// @brief Prime
+ static const uint64_t FNV_prime = 1099511628211ull;
+};
+
+} // end of namespace isc::util
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/util/io/Makefile.am b/src/lib/util/io/Makefile.am
new file mode 100644
index 0000000..b5659b9
--- /dev/null
+++ b/src/lib/util/io/Makefile.am
@@ -0,0 +1,14 @@
+SUBDIRS = .
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+lib_LTLIBRARIES = libkea-util-io.la
+libkea_util_io_la_SOURCES = fd.h fd.cc fd_share.h fd_share.cc
+libkea_util_io_la_SOURCES += socketsession.h socketsession.cc sockaddr_util.h
+libkea_util_io_la_SOURCES += pktinfo_utilities.h
+libkea_util_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_util_io_la_LDFLAGS = -no-undefined -version-info 0:1:0
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/io/Makefile.in b/src/lib/util/io/Makefile.in
new file mode 100644
index 0000000..fb2d9e9
--- /dev/null
+++ b/src/lib/util/io/Makefile.in
@@ -0,0 +1,914 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/util/io
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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__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)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libkea_util_io_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+am_libkea_util_io_la_OBJECTS = fd.lo fd_share.lo socketsession.lo
+libkea_util_io_la_OBJECTS = $(am_libkea_util_io_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_util_io_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_util_io_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)/fd.Plo ./$(DEPDIR)/fd_share.Plo \
+ ./$(DEPDIR)/socketsession.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_util_io_la_SOURCES)
+DIST_SOURCES = $(libkea_util_io_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
+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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES)
+lib_LTLIBRARIES = libkea-util-io.la
+libkea_util_io_la_SOURCES = fd.h fd.cc fd_share.h fd_share.cc \
+ socketsession.h socketsession.cc sockaddr_util.h \
+ pktinfo_utilities.h
+libkea_util_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_util_io_la_LDFLAGS = -no-undefined -version-info 0:1:0
+CLEANFILES = *.gcno *.gcda
+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/util/io/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/io/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-util-io.la: $(libkea_util_io_la_OBJECTS) $(libkea_util_io_la_DEPENDENCIES) $(EXTRA_libkea_util_io_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_util_io_la_LINK) -rpath $(libdir) $(libkea_util_io_la_OBJECTS) $(libkea_util_io_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fd_share.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/socketsession.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)$(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 $@ $<
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/fd.Plo
+ -rm -f ./$(DEPDIR)/fd_share.Plo
+ -rm -f ./$(DEPDIR)/socketsession.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-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)/fd.Plo
+ -rm -f ./$(DEPDIR)/fd_share.Plo
+ -rm -f ./$(DEPDIR)/socketsession.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
+
+.MAKE: $(am__recursive_targets) install-am 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-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
+
+.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/util/io/fd.cc b/src/lib/util/io/fd.cc
new file mode 100644
index 0000000..9d89485
--- /dev/null
+++ b/src/lib/util/io/fd.cc
@@ -0,0 +1,78 @@
+// 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 <util/io/fd.h>
+
+#include <unistd.h>
+#include <cerrno>
+
+namespace isc {
+namespace util {
+namespace io {
+
+bool
+write_data(const int fd, const void *buffer_v, const size_t length) {
+ const unsigned char* buffer(static_cast<const unsigned char*>(buffer_v));
+ size_t remaining = length; // Amount remaining to be written
+
+ // Just keep writing until all is written
+ while (remaining > 0) {
+ const int written = write(fd, buffer, remaining);
+ if (written == -1) {
+ if (errno == EINTR) { // Just keep going
+ continue;
+ } else {
+ return (false);
+ }
+
+ } else if (written > 0) {
+ // Wrote "written" bytes from the buffer
+ remaining -= written;
+ buffer += written;
+
+ } else {
+ // Wrote zero bytes from the buffer. We should not get here as any
+ // error that causes zero bytes to be written should have returned
+ // -1. However, write(2) can return 0, and in this case we
+ // interpret it as an error.
+ return (false);
+ }
+ }
+ return (true);
+}
+
+ssize_t
+read_data(const int fd, void *buffer_v, const size_t length) {
+ unsigned char* buffer(static_cast<unsigned char*>(buffer_v));
+ size_t remaining = length; // Amount remaining to be read
+
+ while (remaining > 0) {
+ const int amount = read(fd, buffer, remaining);
+ if (amount == -1) {
+ if (errno == EINTR) { // Continue on interrupted call
+ continue;
+ } else {
+ return (-1);
+ }
+ } else if (amount > 0) {
+ // Read "amount" bytes into the buffer
+ remaining -= amount;
+ buffer += amount;
+ } else {
+ // EOF - end the read
+ break;
+ }
+ }
+
+ // Return total number of bytes read
+ return (static_cast<ssize_t>(length - remaining));
+}
+
+}
+}
+}
diff --git a/src/lib/util/io/fd.h b/src/lib/util/io/fd.h
new file mode 100644
index 0000000..b647425
--- /dev/null
+++ b/src/lib/util/io/fd.h
@@ -0,0 +1,53 @@
+// 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/.
+
+#ifndef UTIL_IO_FD_H
+#define UTIL_IO_FD_H 1
+
+#include <unistd.h>
+
+/**
+ * @file fd.h
+ * @short Wrappers around common unix fd manipulation functions.
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+/*
+ * \short write() that writes everything.
+ * Wrapper around write(). The difference is, it never writes less data
+ * and looks successful (eg. it blocks until all data are written).
+ * Retries on signals.
+ *
+ * \return True if successful, false otherwise. The errno variable is left
+ * intact.
+ * \param fd Where to write.
+ * \param data The buffer to write.
+ * \param length How much data is there to write.
+ */
+bool
+write_data(const int fd, const void *data, const size_t length);
+
+/*
+ * \short read() that reads everything.
+ * Wrapper around read(). It does not do short reads, if it returns less,
+ * it means there was EOF. It retries on signals.
+ *
+ * \return Number of bytes read or -1 on error.
+ * \param fd Where to read data from.
+ * \param data Where to put the data.
+ * \param length How many of them.
+ */
+ssize_t
+read_data(const int fd, void *buffer, const size_t length);
+
+}
+}
+}
+
+#endif // UTIL_IO_FD_H
diff --git a/src/lib/util/io/fd_share.cc b/src/lib/util/io/fd_share.cc
new file mode 100644
index 0000000..6edffd3
--- /dev/null
+++ b/src/lib/util/io/fd_share.cc
@@ -0,0 +1,166 @@
+// Copyright (C) 2010-2019 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 <cstring>
+#include <cstdlib>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <stdlib.h> // for malloc and free
+#include <unistd.h>
+#include <util/io/fd_share.h>
+
+namespace isc {
+namespace util {
+namespace io {
+
+namespace {
+// Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE.
+// In order to ensure as much portability as possible, we provide wrapper
+// functions of these macros.
+// Note that cmsg_space() could run slow on OSes that do not have
+// CMSG_SPACE.
+inline socklen_t
+cmsg_len(const socklen_t len) {
+#ifdef CMSG_LEN
+ return (CMSG_LEN(len));
+#else
+ // Cast NULL so that any pointer arithmetic performed by CMSG_DATA
+ // is correct.
+ const uintptr_t hdrlen = (uintptr_t)CMSG_DATA(((struct cmsghdr*)NULL));
+ return (hdrlen + len);
+#endif
+}
+
+inline socklen_t
+cmsg_space(const socklen_t len) {
+#ifdef CMSG_SPACE
+ return (CMSG_SPACE(len));
+#else
+ struct msghdr msg;
+ struct cmsghdr* cmsgp;
+ // XXX: The buffer length is an ad hoc value, but should be enough
+ // in a practical sense.
+ char dummybuf[sizeof(struct cmsghdr) + 1024];
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_control = dummybuf;
+ msg.msg_controllen = sizeof(dummybuf);
+
+ cmsgp = (struct cmsghdr*)dummybuf;
+ cmsgp->cmsg_len = cmsg_len(len);
+
+ cmsgp = CMSG_NXTHDR(&msg, cmsgp);
+ if (cmsgp != NULL) {
+ return ((char*)cmsgp - (char*)msg.msg_control);
+ } else {
+ return (0);
+ }
+#endif // CMSG_SPACE
+}
+}
+
+int
+recv_fd(const int sock) {
+ struct msghdr msghdr;
+ struct iovec iov_dummy;
+ unsigned char dummy_data;
+
+ iov_dummy.iov_base = &dummy_data;
+ iov_dummy.iov_len = sizeof(dummy_data);
+ msghdr.msg_name = NULL;
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = &iov_dummy;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_flags = 0;
+ msghdr.msg_controllen = cmsg_space(sizeof(int));
+ msghdr.msg_control = malloc(msghdr.msg_controllen);
+ if (msghdr.msg_control == NULL) {
+ return (FD_SYSTEM_ERROR);
+ }
+
+ const int cc = recvmsg(sock, &msghdr, 0);
+ if (cc <= 0) {
+ free(msghdr.msg_control);
+ if (cc == 0) {
+ errno = ECONNRESET;
+ }
+ return (FD_SYSTEM_ERROR);
+ }
+ const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
+ int fd = FD_OTHER_ERROR;
+ if (cmsg != NULL && cmsg->cmsg_len == cmsg_len(sizeof(int)) &&
+ cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+// Some systems (e.g. recent NetBSD) converted all CMSG access macros
+// to static_cast when used in C++ code. As cmsg is declared const
+// this makes the CMSG_DATA macro to not compile. But fortunately
+// these systems provide a const alternative named CCMSG_DATA.
+#ifdef CCMSG_DATA
+ std::memcpy(&fd, CCMSG_DATA(cmsg), sizeof(int));
+#else
+ std::memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
+#endif
+ }
+ free(msghdr.msg_control);
+ int new_fd = -1;
+ int close_error = -1;
+ if (fd >= 0) {
+ // It is strange, but the call can return the same file descriptor as
+ // one returned previously, even if that one is not closed yet. So,
+ // we just re-number every one we get, so they are unique.
+ new_fd = dup(fd);
+ close_error = close(fd);
+ }
+ if (close_error == -1 || new_fd == -1) {
+ // We need to return an error, because something failed. But in case
+ // it was the previous close, we at least try to close the duped FD.
+ if (new_fd != -1) {
+ close(new_fd); // If this fails, nothing but returning error can't
+ // be done and we are doing that anyway.
+ }
+ return (FD_SYSTEM_ERROR);
+ }
+ return (new_fd);
+}
+
+int
+send_fd(const int sock, const int fd) {
+ struct msghdr msghdr;
+ struct iovec iov_dummy;
+ unsigned char dummy_data = 0;
+
+ iov_dummy.iov_base = &dummy_data;
+ iov_dummy.iov_len = sizeof(dummy_data);
+ msghdr.msg_name = NULL;
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = &iov_dummy;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_flags = 0;
+ msghdr.msg_controllen = cmsg_space(sizeof(int));
+ msghdr.msg_control = malloc(msghdr.msg_controllen);
+ if (msghdr.msg_control == NULL) {
+ return (FD_OTHER_ERROR);
+ }
+ std::memset(msghdr.msg_control, 0, msghdr.msg_controllen);
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
+ cmsg->cmsg_len = cmsg_len(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ std::memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+ const int ret = sendmsg(sock, &msghdr, 0);
+ free(msghdr.msg_control);
+ return (ret >= 0 ? 0 : FD_SYSTEM_ERROR);
+}
+
+} // End for namespace io
+} // End for namespace util
+} // End for namespace isc
diff --git a/src/lib/util/io/fd_share.h b/src/lib/util/io/fd_share.h
new file mode 100644
index 0000000..36d0d5f
--- /dev/null
+++ b/src/lib/util/io/fd_share.h
@@ -0,0 +1,61 @@
+// 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/.
+
+#ifndef FD_SHARE_H_
+#define FD_SHARE_H_
+
+/**
+ * \file fd_share.h
+ * \short Support to transfer file descriptors between processes.
+ * \todo This interface is very C-ish. Should we have some kind of exceptions?
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+const int FD_SYSTEM_ERROR = -2;
+const int FD_OTHER_ERROR = -1;
+
+/**
+ * \short Receives a file descriptor.
+ * This receives a file descriptor sent over an unix domain socket. This
+ * is the counterpart of send_fd().
+ *
+ * \return FD_SYSTEM_ERROR when there's an error at the operating system
+ * level (such as a system call failure). The global 'errno' variable
+ * indicates the specific error. FD_OTHER_ERROR when there's a different
+ * error.
+ *
+ * \param sock The unix domain socket to read from. Tested and it does
+ * not work with a pipe.
+ */
+int recv_fd(const int sock);
+
+/**
+ * \short Sends a file descriptor.
+ * This sends a file descriptor over an unix domain socket. This is the
+ * counterpart of recv_fd().
+ *
+ * \return FD_SYSTEM_ERROR when there's an error at the operating system
+ * level (such as a system call failure). The global 'errno' variable
+ * indicates the specific error.
+ * \param sock The unix domain socket to send to. Tested and it does not
+ * work with a pipe.
+ * \param fd The file descriptor to send. It should work with any valid
+ * file descriptor.
+ */
+int send_fd(const int sock, const int fd);
+
+} // End for namespace io
+} // End for namespace util
+} // End for namespace isc
+
+#endif
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io/pktinfo_utilities.h b/src/lib/util/io/pktinfo_utilities.h
new file mode 100644
index 0000000..2625008
--- /dev/null
+++ b/src/lib/util/io/pktinfo_utilities.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2012-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/.
+
+#ifndef PKTINFO_UTIL_H
+#define PKTINFO_UTIL_H 1
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+// These definitions in this file are for the convenience of internal
+// implementation and test code, and are not intended to be used publicly.
+// The namespace "internal" indicates the intent.
+
+namespace isc {
+namespace util {
+namespace io {
+namespace internal {
+
+// Lower level C-APIs require conversion between char* pointers
+// (like structures returned by CMSG_DATA macro) and in6_pktinfo,
+// which is not friendly with C++. The following templates
+// are a shortcut of common workaround conversion in such cases.
+inline struct in6_pktinfo*
+convertPktInfo6(char* pktinfo) {
+ return (static_cast<struct in6_pktinfo*>(static_cast<void*>(pktinfo)));
+}
+
+inline struct in6_pktinfo*
+convertPktInfo6(unsigned char* pktinfo) {
+ return (static_cast<struct in6_pktinfo*>(static_cast<void*>(pktinfo)));
+}
+
+/// @todo: Do we need const versions as well?
+
+}
+}
+}
+}
+
+#endif // PKTINFO_UTIL_H
diff --git a/src/lib/util/io/sockaddr_util.h b/src/lib/util/io/sockaddr_util.h
new file mode 100644
index 0000000..1cb31f2
--- /dev/null
+++ b/src/lib/util/io/sockaddr_util.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2011-2020 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 SOCKADDR_UTIL_H
+#define SOCKADDR_UTIL_H 1
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <exceptions/isc_assert.h>
+
+// These definitions in this file are for the convenience of internal
+// implementation and test code, and are not intended to be used publicly.
+// The namespace "internal" indicates the intent.
+
+namespace isc {
+namespace util {
+namespace io {
+namespace internal {
+
+inline socklen_t
+getSALength(const struct sockaddr& sa) {
+ if (sa.sa_family == AF_INET) {
+ return (sizeof(struct sockaddr_in));
+ } else {
+ isc_throw_assert(sa.sa_family == AF_INET6);
+ return (sizeof(struct sockaddr_in6));
+ }
+}
+
+// Lower level C-APIs require conversion between various variants of
+// sockaddr's, which is not friendly with C++. The following templates
+// are a shortcut of common workaround conversion in such cases.
+
+template <typename SAType>
+const struct sockaddr*
+convertSockAddr(const SAType* sa) {
+ const void* p = sa;
+ return (static_cast<const struct sockaddr*>(p));
+}
+
+template <typename SAType>
+const SAType*
+convertSockAddr(const struct sockaddr* sa) {
+ const void* p = sa;
+ return (static_cast<const SAType*>(p));
+}
+
+template <typename SAType>
+struct sockaddr*
+convertSockAddr(SAType* sa) {
+ void* p = sa;
+ return (static_cast<struct sockaddr*>(p));
+}
+
+template <typename SAType>
+SAType*
+convertSockAddr(struct sockaddr* sa) {
+ void* p = sa;
+ return (static_cast<SAType*>(p));
+}
+
+}
+}
+}
+}
+
+#endif // SOCKADDR_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io/socketsession.cc b/src/lib/util/io/socketsession.cc
new file mode 100644
index 0000000..88f2b1e
--- /dev/null
+++ b/src/lib/util/io/socketsession.cc
@@ -0,0 +1,438 @@
+// Copyright (C) 2011-2021 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 <unistd.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+
+#include <cerrno>
+#include <csignal>
+#include <cstddef>
+#include <cstring>
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+#include <exceptions/isc_assert.h>
+
+#include <util/buffer.h>
+
+#include <util/io/fd_share.h>
+#include <util/io/socketsession.h>
+#include <util/io/sockaddr_util.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace io {
+
+using namespace internal;
+
+// The expected max size of the session header: 2-byte header length,
+// 6 32-bit fields, and 2 sockaddr structure. (see the SocketSessionUtility
+// overview description in the header file). sizeof sockaddr_storage
+// should be the possible max of any sockaddr structure
+const size_t DEFAULT_HEADER_BUFLEN = sizeof(uint16_t) + sizeof(uint32_t) * 6 +
+ sizeof(struct sockaddr_storage) * 2;
+
+// The allowable maximum size of data passed with the socket FD. For now
+// we use a fixed value of 65535, the largest possible size of valid DNS
+// messages. We may enlarge it or make it configurable as we see the need
+// for more flexibility.
+const int MAX_DATASIZE = 65535;
+
+// The initial buffer size for receiving socket session data in the receiver.
+// This value is the maximum message size of DNS messages carried over UDP
+// (without EDNS). In our expected usage (at the moment) this should be
+// sufficiently large (the expected data is AXFR/IXFR query or an UPDATE
+// requests. The former should be generally quite small. While the latter
+// could be large, it would often be small enough for a single UDP message).
+// If it turns out that there are many exceptions, we may want to extend
+// the class so that this value can be customized. Note that the buffer
+// will be automatically extended for longer data and this is only about
+// efficiency.
+const size_t INITIAL_BUFSIZE = 512;
+
+// The (default) socket buffer size for the forwarder and receiver. This is
+// chosen to be sufficiently large to store two full-size DNS messages. We
+// may want to customize this value in future.
+const int SOCKSESSION_BUFSIZE = (DEFAULT_HEADER_BUFLEN + MAX_DATASIZE) * 2;
+
+struct SocketSessionForwarder::ForwarderImpl {
+ ForwarderImpl() : sock_un_len_(0), fd_(-1), buf_(DEFAULT_HEADER_BUFLEN) {
+ memset(&sock_un_, 0, sizeof(sock_un_));
+ }
+
+ struct sockaddr_un sock_un_;
+ socklen_t sock_un_len_;
+ int fd_;
+ OutputBuffer buf_;
+};
+
+SocketSessionForwarder::SocketSessionForwarder(const std::string& unix_file) :
+ impl_(NULL)
+{
+ // We need to filter SIGPIPE for subsequent push(). See the class
+ // description.
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
+ isc_throw(Unexpected, "Failed to filter SIGPIPE: " << strerror(errno));
+ }
+
+ ForwarderImpl impl;
+ if (sizeof(impl.sock_un_.sun_path) - 1 < unix_file.length()) {
+ isc_throw(SocketSessionError,
+ "File name for a UNIX domain socket is too long: " <<
+ unix_file);
+ }
+ impl.sock_un_.sun_family = AF_UNIX;
+ // the copy should be safe due to the above check, but we'd be rather
+ // paranoid about making it 100% sure even if the check has a bug (with
+ // triggering the assertion in the worse case)
+ memset(&impl.sock_un_.sun_path, 0, sizeof(impl.sock_un_.sun_path));
+ strncpy(impl.sock_un_.sun_path, unix_file.c_str(),
+ sizeof(impl.sock_un_.sun_path) - 1);
+ isc_throw_assert(impl.sock_un_.sun_path[sizeof(impl.sock_un_.sun_path) - 1] == '\0');
+ impl.sock_un_len_ = offsetof(struct sockaddr_un, sun_path) +
+ unix_file.length();
+#ifdef HAVE_SA_LEN
+ impl.sock_un_.sun_len = impl.sock_un_len_;
+#endif
+ impl.fd_ = -1;
+
+ impl_ = new ForwarderImpl;
+ *impl_ = impl;
+}
+
+SocketSessionForwarder::~SocketSessionForwarder() {
+ if (impl_->fd_ != -1) {
+ close();
+ }
+ delete impl_;
+}
+
+void
+SocketSessionForwarder::connectToReceiver() {
+ if (impl_->fd_ != -1) {
+ isc_throw(BadValue, "Duplicate connect to UNIX domain "
+ "endpoint " << impl_->sock_un_.sun_path);
+ }
+
+ impl_->fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (impl_->fd_ == -1) {
+ isc_throw(SocketSessionError, "Failed to create a UNIX domain socket: "
+ << strerror(errno));
+ }
+ // Make the socket non blocking
+ int fcntl_flags = fcntl(impl_->fd_, F_GETFL, 0);
+ if (fcntl_flags != -1) {
+ fcntl_flags |= O_NONBLOCK;
+ fcntl_flags = fcntl(impl_->fd_, F_SETFL, fcntl_flags);
+ }
+ if (fcntl_flags == -1) {
+ close(); // note: this is the internal method, not ::close()
+ isc_throw(SocketSessionError,
+ "Failed to make UNIX domain socket non blocking: " <<
+ strerror(errno));
+ }
+ // Ensure the socket send buffer is large enough. If we can't get the
+ // current size, simply set the sufficient size.
+ int sndbuf_size;
+ socklen_t sndbuf_size_len = sizeof(sndbuf_size);
+ if (getsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &sndbuf_size,
+ &sndbuf_size_len) == -1 ||
+ sndbuf_size < SOCKSESSION_BUFSIZE) {
+ if (setsockopt(impl_->fd_, SOL_SOCKET, SO_SNDBUF, &SOCKSESSION_BUFSIZE,
+ sizeof(SOCKSESSION_BUFSIZE)) == -1) {
+ close();
+ isc_throw(SocketSessionError,
+ "Failed to set send buffer size to " <<
+ SOCKSESSION_BUFSIZE);
+ }
+ }
+ if (connect(impl_->fd_, convertSockAddr(&impl_->sock_un_),
+ impl_->sock_un_len_) == -1) {
+ close();
+ isc_throw(SocketSessionError, "Failed to connect to UNIX domain "
+ "endpoint " << impl_->sock_un_.sun_path << ": " <<
+ strerror(errno));
+ }
+}
+
+void
+SocketSessionForwarder::close() {
+ if (impl_->fd_ == -1) {
+ isc_throw(BadValue, "Attempt of close before connect");
+ }
+ ::close(impl_->fd_);
+ impl_->fd_ = -1;
+}
+
+void
+SocketSessionForwarder::push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len)
+{
+ if (impl_->fd_ == -1) {
+ isc_throw(BadValue, "Attempt of push before connect");
+ }
+ if ((local_end.sa_family != AF_INET && local_end.sa_family != AF_INET6) ||
+ (remote_end.sa_family != AF_INET && remote_end.sa_family != AF_INET6))
+ {
+ isc_throw(BadValue, "Invalid address family: must be "
+ "AF_INET or AF_INET6; " <<
+ static_cast<int>(local_end.sa_family) << ", " <<
+ static_cast<int>(remote_end.sa_family) << " given");
+ }
+ if (family != local_end.sa_family || family != remote_end.sa_family) {
+ isc_throw(BadValue, "Inconsistent address family: must be "
+ << static_cast<int>(family) << "; "
+ << static_cast<int>(local_end.sa_family) << ", "
+ << static_cast<int>(remote_end.sa_family) << " given");
+ }
+ if (data_len == 0 || data == NULL) {
+ isc_throw(BadValue, "Data for a socket session must not be empty");
+ }
+ if (data_len > MAX_DATASIZE) {
+ isc_throw(BadValue, "Invalid socket session data size: " <<
+ data_len << ", must not exceed " << MAX_DATASIZE);
+ }
+
+ if (send_fd(impl_->fd_, sock) != 0) {
+ isc_throw(SocketSessionError, "FD passing failed: " <<
+ strerror(errno));
+ }
+
+ impl_->buf_.clear();
+ // Leave the space for the header length
+ impl_->buf_.skip(sizeof(uint16_t));
+ // Socket properties: family, type, protocol
+ impl_->buf_.writeUint32(static_cast<uint32_t>(family));
+ impl_->buf_.writeUint32(static_cast<uint32_t>(type));
+ impl_->buf_.writeUint32(static_cast<uint32_t>(protocol));
+ // Local endpoint
+ impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(local_end)));
+ impl_->buf_.writeData(&local_end, getSALength(local_end));
+ // Remote endpoint
+ impl_->buf_.writeUint32(static_cast<uint32_t>(getSALength(remote_end)));
+ impl_->buf_.writeData(&remote_end, getSALength(remote_end));
+ // Data length. Must be fit uint32 due to the range check above.
+ const uint32_t data_len32 = static_cast<uint32_t>(data_len);
+ isc_throw_assert(data_len == data_len32); // shouldn't cause overflow.
+ impl_->buf_.writeUint32(data_len32);
+ // Write the resulting header length at the beginning of the buffer
+ impl_->buf_.writeUint16At(impl_->buf_.getLength() - sizeof(uint16_t), 0);
+
+ const struct iovec iov[2] = {
+ { const_cast<void*>(impl_->buf_.getData()), impl_->buf_.getLength() },
+ { const_cast<void*>(data), data_len }
+ };
+ const int cc = writev(impl_->fd_, iov, 2);
+ if (cc != impl_->buf_.getLength() + data_len) {
+ if (cc < 0) {
+ isc_throw(SocketSessionError,
+ "Write failed in forwarding a socket session: " <<
+ strerror(errno));
+ }
+ isc_throw(SocketSessionError,
+ "Incomplete write in forwarding a socket session: " << cc <<
+ "/" << (impl_->buf_.getLength() + data_len));
+ }
+}
+
+SocketSession::SocketSession(int sock, int family, int type, int protocol,
+ const sockaddr* local_end,
+ const sockaddr* remote_end,
+ const void* data, size_t data_len) :
+ sock_(sock), family_(family), type_(type), protocol_(protocol),
+ local_end_(local_end), remote_end_(remote_end),
+ data_(data), data_len_(data_len)
+{
+ if (local_end == NULL || remote_end == NULL) {
+ isc_throw(BadValue, "sockaddr must be non NULL for SocketSession");
+ }
+ if (data_len == 0) {
+ isc_throw(BadValue, "data_len must be non 0 for SocketSession");
+ }
+ if (data == NULL) {
+ isc_throw(BadValue, "data must be non NULL for SocketSession");
+ }
+}
+
+struct SocketSessionReceiver::ReceiverImpl {
+ ReceiverImpl(int fd) : fd_(fd),
+ sa_local_(convertSockAddr(&ss_local_)),
+ sa_remote_(convertSockAddr(&ss_remote_)),
+ header_buf_(DEFAULT_HEADER_BUFLEN),
+ data_buf_(INITIAL_BUFSIZE)
+ {
+ memset(&ss_local_, 0, sizeof(ss_local_));
+ memset(&ss_remote_, 0, sizeof(ss_remote_));
+
+ if (setsockopt(fd_, SOL_SOCKET, SO_RCVBUF, &SOCKSESSION_BUFSIZE,
+ sizeof(SOCKSESSION_BUFSIZE)) == -1) {
+ isc_throw(SocketSessionError,
+ "Failed to set receive buffer size to " <<
+ SOCKSESSION_BUFSIZE);
+ }
+ }
+
+ const int fd_;
+ struct sockaddr_storage ss_local_; // placeholder for local endpoint
+ struct sockaddr* const sa_local_;
+ struct sockaddr_storage ss_remote_; // placeholder for remote endpoint
+ struct sockaddr* const sa_remote_;
+
+ // placeholder for session header and data
+ vector<uint8_t> header_buf_;
+ vector<uint8_t> data_buf_;
+};
+
+SocketSessionReceiver::SocketSessionReceiver(int fd) :
+ impl_(new ReceiverImpl(fd))
+{
+}
+
+SocketSessionReceiver::~SocketSessionReceiver() {
+ delete impl_;
+}
+
+namespace {
+// A shortcut to throw common exception on failure of recv(2)
+void
+readFail(int actual_len, int expected_len) {
+ if (expected_len < 0) {
+ isc_throw(SocketSessionError, "Failed to receive data from "
+ "SocketSessionForwarder: " << strerror(errno));
+ }
+ isc_throw(SocketSessionError, "Incomplete data from "
+ "SocketSessionForwarder: " << actual_len << "/" <<
+ expected_len);
+}
+
+// A helper container for a (socket) file descriptor used in
+// SocketSessionReceiver::pop that ensures the socket is closed unless it
+// can be safely passed to the caller via release().
+struct ScopedSocket : boost::noncopyable {
+ ScopedSocket(int fd) : fd_(fd) {}
+ ~ScopedSocket() {
+ if (fd_ >= 0) {
+ close(fd_);
+ }
+ }
+ int release() {
+ const int fd = fd_;
+ fd_ = -1;
+ return (fd);
+ }
+ int fd_;
+};
+}
+
+SocketSession
+SocketSessionReceiver::pop() {
+ ScopedSocket passed_sock(recv_fd(impl_->fd_));
+ if (passed_sock.fd_ == FD_SYSTEM_ERROR) {
+ isc_throw(SocketSessionError, "Receiving a forwarded FD failed: " <<
+ strerror(errno));
+ } else if (passed_sock.fd_ < 0) {
+ isc_throw(SocketSessionError, "No FD forwarded");
+ }
+
+ uint16_t header_len;
+ const int cc_hlen = recv(impl_->fd_, &header_len, sizeof(header_len),
+ MSG_WAITALL);
+ if (cc_hlen < sizeof(header_len)) {
+ readFail(cc_hlen, sizeof(header_len));
+ }
+ header_len = InputBuffer(&header_len, sizeof(header_len)).readUint16();
+ if (header_len > DEFAULT_HEADER_BUFLEN) {
+ isc_throw(SocketSessionError, "Too large header length: " <<
+ header_len);
+ }
+ impl_->header_buf_.clear();
+ impl_->header_buf_.resize(header_len);
+ const int cc_hdr = recv(impl_->fd_, &impl_->header_buf_[0], header_len,
+ MSG_WAITALL);
+ if (cc_hdr < header_len) {
+ readFail(cc_hdr, header_len);
+ }
+
+ InputBuffer ibuffer(&impl_->header_buf_[0], header_len);
+ try {
+ const int family = static_cast<int>(ibuffer.readUint32());
+ if (family != AF_INET && family != AF_INET6) {
+ isc_throw(SocketSessionError,
+ "Unsupported address family is passed: " << family);
+ }
+ const int type = static_cast<int>(ibuffer.readUint32());
+ const int protocol = static_cast<int>(ibuffer.readUint32());
+ const socklen_t local_end_len = ibuffer.readUint32();
+ const socklen_t endpoint_minlen = (family == AF_INET) ?
+ sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
+ if (local_end_len < endpoint_minlen ||
+ local_end_len > sizeof(impl_->ss_local_)) {
+ isc_throw(SocketSessionError, "Invalid local SA length: " <<
+ local_end_len);
+ }
+ ibuffer.readData(&impl_->ss_local_, local_end_len);
+ const socklen_t remote_end_len = ibuffer.readUint32();
+ if (remote_end_len < endpoint_minlen ||
+ remote_end_len > sizeof(impl_->ss_remote_)) {
+ isc_throw(SocketSessionError, "Invalid remote SA length: " <<
+ remote_end_len);
+ }
+ ibuffer.readData(&impl_->ss_remote_, remote_end_len);
+ if (family != impl_->sa_local_->sa_family ||
+ family != impl_->sa_remote_->sa_family) {
+ isc_throw(SocketSessionError, "SA family inconsistent: " <<
+ static_cast<int>(impl_->sa_local_->sa_family) << ", " <<
+ static_cast<int>(impl_->sa_remote_->sa_family) <<
+ " given, must be " << family);
+ }
+ const size_t data_len = ibuffer.readUint32();
+ if (data_len == 0 || data_len > MAX_DATASIZE) {
+ isc_throw(SocketSessionError,
+ "Invalid socket session data size: " << data_len <<
+ ", must be > 0 and <= " << MAX_DATASIZE);
+ }
+
+ impl_->data_buf_.clear();
+ impl_->data_buf_.resize(data_len);
+ const int cc_data = recv(impl_->fd_, &impl_->data_buf_[0], data_len,
+ MSG_WAITALL);
+ if (cc_data < data_len) {
+ readFail(cc_data, data_len);
+ }
+
+ return (SocketSession(passed_sock.release(), family, type, protocol,
+ impl_->sa_local_, impl_->sa_remote_,
+ &impl_->data_buf_[0], data_len));
+ } catch (const InvalidBufferPosition& ex) {
+ // We catch the case where the given header is too short and convert
+ // the exception to SocketSessionError.
+ isc_throw(SocketSessionError, "bogus socket session header: " <<
+ ex.what());
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/util/io/socketsession.h b/src/lib/util/io/socketsession.h
new file mode 100644
index 0000000..52e33de
--- /dev/null
+++ b/src/lib/util/io/socketsession.h
@@ -0,0 +1,495 @@
+// 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/.
+
+#ifndef SOCKETSESSION_H
+#define SOCKETSESSION_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+#include <sys/socket.h>
+
+namespace isc {
+namespace util {
+namespace io {
+
+/// \page SocketSessionUtility Socket session utility
+///
+/// \note This class is currently unused. Once we get to the implementation
+/// of the remote parts of the management API, we will evaluate whether
+/// this code is useful or not and either start using it or remove it.
+///
+/// This utility defines a set of classes that support forwarding a
+/// "socket session" from one process to another. A socket session is a
+/// conceptual tuple of the following elements:
+/// - A network socket
+/// - The local and remote endpoints of a (IP) communication taking place on
+/// the socket. In practice an endpoint is a pair of an IP address and
+/// TCP or UDP port number.
+/// - Some amount of data sent from the remote endpoint and received on the
+/// socket. We call it (socket) session data in this documentation.
+///
+/// Note that this is a conceptual definition. Depending on the underlying
+/// implementation and/or the network protocol, some of the elements could be
+/// part of others; for example, if it's an established TCP connection,
+/// the local and remote endpoints would be able to be retrieved from the
+/// socket using the standard \c getsockname() and \c getpeername() system
+/// calls. But in this definition we separate these to be more generic.
+/// Also, as a matter of fact our intended usage includes non-connected UDP
+/// communications, in which case at least the remote endpoint should be
+/// provided separately from the socket.
+///
+/// In the actual implementation we represent a socket as a tuple of
+/// socket's file descriptor, address family (e.g. \c AF_INET6),
+/// socket type (e.g. \c SOCK_STREAM), and protocol (e.g. \c IPPROTO_TCP).
+/// The latter three are included in the representation of a socket in order
+/// to provide complete information of how the socket would be created
+/// by the \c socket(2) system call. More specifically in practice, these
+/// parameters could be used to construct a Python socket object from the
+/// file descriptor.
+///
+/// We use the standard \c sockaddr structure to represent endpoints.
+///
+/// Socket session data is an opaque memory region of an arbitrary length
+/// (possibly with some reasonable upper limit).
+///
+/// To forward a socket session between processes, we use connected UNIX
+/// domain sockets established between the processes. The file descriptor
+/// will be forwarded through the sockets as an ancillary data item of
+/// type \c SCM_RIGHTS. Other elements of the session will be transferred
+/// as normal data over the connection.
+///
+/// We provide three classes to help applications forward socket sessions:
+/// \c SocketSessionForwarder is the sender of the UNIX domain connection,
+/// while \c SocketSessionReceiver is the receiver (this interface assumes
+/// one direction of forwarding); \c SocketSession represents a single
+/// socket session.
+///
+/// \c SocketSessionForwarder and \c SocketSessionReceiver objects use a
+/// straightforward protocol to pass elements of socket sessions.
+/// Once the connection is established, the forwarder object first forwards
+/// the file descriptor with 1-byte dummy data. It then forwards a
+/// "(socket) session header", which contains all other elements of the session
+/// except the file descriptor (already forwarded) and session data.
+/// The wire format of the header is as follows:
+/// - The length of the header (16-bit unsigned integer)
+/// - Address family
+/// - Socket type
+/// - Protocol
+/// - Size of the local endpoint in bytes
+/// - Local endpoint (a copy of the memory image of the corresponding
+/// \c sockaddr)
+/// - Size of the remote endpoint in bytes
+/// - Remote endpoint (same as local endpoint)
+/// - Size of session data in bytes
+///
+/// The type of the fields is 32-bit unsigned integer unless explicitly
+/// noted, and all fields are formatted in the network byte order.
+///
+/// The socket session data immediately follows the session header.
+///
+/// Note that the fields do not necessarily be in the network byte order
+/// because they are expected to be exchanged on the same machine. Likewise,
+/// integer elements such as address family do not necessarily be represented
+/// as an fixed-size value (i.e., 32-bit). But fixed size fields are used
+/// in order to ensure maximum portability in such a (rare) case where the
+/// forwarder and the receiver are built with different compilers that have
+/// different definitions of \c int. Also, since \c sockaddr fields are
+/// generally formatted in the network byte order, other fields are defined
+/// so to be consistent.
+///
+/// One basic assumption in the API of this utility is socket sessions should
+/// be forwarded without blocking, thus eliminating the need for incremental
+/// read/write or blocking other important services such as responding to
+/// requests from the application's clients. This assumption should be held
+/// as long as both the forwarder and receiver have sufficient resources
+/// to handle the forwarding process since the communication is local.
+/// But a forward attempt could still block if the receiver is busy (or even
+/// hang up) and cannot keep up with the volume of incoming sessions.
+///
+/// So, in this implementation, the forwarder uses non blocking writes to
+/// forward sessions. If a write attempt could block, it immediately gives
+/// up the operation with an exception. The corresponding application is
+/// expected to catch it, close the connection, and perform any necessary
+/// recovery for that application (that would normally be re-establish the
+/// connection with a new receiver, possibly after confirming the receiving
+/// side is still alive). On the other hand, the receiver implementation
+/// assumes it's possible that it only receive incomplete elements of a
+/// session (such as in the case where the forwarder writes part of the
+/// entire session and gives up the connection). The receiver implementation
+/// throws an exception when it encounters an incomplete session. Like the
+/// case of the forwarder application, the receiver application is expected
+/// to catch it, close the connection, and perform any necessary recovery
+/// steps.
+///
+/// Note that the receiver implementation uses blocking read. So it's
+/// application's responsibility to ensure that there's at least some data
+/// in the connection when the receiver object is requested to receive a
+/// session (unless this operation can be blocking, e.g., by the use of
+/// a separate thread). Also, if the forwarder implementation or application
+/// is malicious or extremely buggy and intentionally sends partial session
+/// and keeps the connection, the receiver could block in receiving a session.
+/// In general, we assume the forwarder doesn't do intentional blocking
+/// as it's a local node and is generally a module of the same (Kea)
+/// system. The minimum requirement for the forwarder implementation (and
+/// application) is to make sure the connection is closed once it detects
+/// an error on it. Even a naive implementation that simply dies due to
+/// the exception will meet this requirement.
+
+/// An exception indicating general errors that takes place in the
+/// socket session related class objects.
+///
+/// In general the errors are unusual but possible failures such as unexpected
+/// connection reset, and suggest the application to close the connection and
+/// (if necessary) reestablish it.
+class SocketSessionError: public Exception {
+public:
+ SocketSessionError(const char *file, size_t line, const char *what):
+ isc::Exception(file, line, what) {}
+};
+
+/// The "base" class of \c SocketSessionForwarder
+///
+/// This class defines abstract interfaces of the \c SocketSessionForwarder
+/// class. Although \c SocketSessionForwarder is not intended to be used in
+/// a polymorphic way, it's not easy to use in tests because it will require
+/// various low level network operations. So it would be useful if we
+/// provide a framework for defining a fake or mock version of it.
+/// An application that needs to use \c SocketSessionForwarder would actually
+/// refer to this base class, and tests for the application would define
+/// and use a fake version of the forwarder class.
+///
+/// Normal applications are not expected to define and use their own derived
+/// version of this base class, while it's not prohibited at the API level.
+///
+/// See description of \c SocketSessionForwarder for the expected interface.
+class BaseSocketSessionForwarder {
+protected:
+ BaseSocketSessionForwarder() {}
+
+public:
+ virtual ~BaseSocketSessionForwarder() {}
+ virtual void connectToReceiver() = 0;
+ virtual void close() = 0;
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len) = 0;
+};
+
+/// The forwarder of socket sessions
+///
+/// An object of this class maintains a UNIX domain socket (normally expected
+/// to be connected to a \c SocketSessionReceiver object) and forwards
+/// socket sessions to the receiver.
+///
+/// See the description of \ref SocketSessionUtility for other details of how
+/// the session forwarding works.
+class SocketSessionForwarder : boost::noncopyable,
+ public BaseSocketSessionForwarder
+{
+public:
+ /// The constructor.
+ ///
+ /// It's constructed with path information of the intended receiver,
+ /// but does not immediately establish a connection to the receiver;
+ /// \c connectToReceiver() must be called to establish it. These are
+ /// separated so that an object of class can be initialized (possibly
+ /// as an attribute of a higher level application class object) without
+ /// knowing the receiver is ready for accepting new forwarders. The
+ /// separate connect interface allows the object to be reused when it
+ /// detects connection failure and tries to re-establish it after closing
+ /// the failed one.
+ ///
+ /// On construction, it also installs a signal filter for SIGPIPE to
+ /// ignore it. Since this class uses a stream-type connected UNIX domain
+ /// socket, if the receiver (abruptly) closes the connection a subsequent
+ /// write operation on the socket would trigger a SIGPIPE signal, which
+ /// kills the caller process by default. This behavior would be
+ /// undesirable in many cases, so this implementation always disables
+ /// the signal.
+ ///
+ /// This approach has some drawbacks, however; first, since signal handling
+ /// is process (or thread) wide, ignoring it may not what the application
+ /// wants. On the other hand, if the application changes how the signal is
+ /// handled after instantiating this class, the new behavior affects the
+ /// class operation. Secondly, even if ignoring the signal is the desired
+ /// operation, it's a waste to set the filter every time this class object
+ /// is constructed. It's sufficient to do it once. We still adopt this
+ /// behavior based on the observation that in most cases applications would
+ /// like to ignore SIGPIPE (or simply doesn't care about it) and that this
+ /// class is not instantiated so often (so the wasteful setting overhead
+ /// should be marginal). On the other hand, doing it every time is
+ /// beneficial if the application is threaded and different threads
+ /// create different forwarder objects (and if signals work per thread).
+ ///
+ /// \exception SocketSessionError \c unix_file is invalid as a path name
+ /// of a UNIX domain socket.
+ /// \exception Unexpected Error in setting a filter for SIGPIPE (see above)
+ /// \exception std::bad_alloc resource allocation failure
+ ///
+ /// \param unix_file Path name of the receiver.
+ explicit SocketSessionForwarder(const std::string& unix_file);
+
+ /// The destructor.
+ ///
+ /// If a connection has been established, it's automatically closed in
+ /// the destructor.
+ virtual ~SocketSessionForwarder();
+
+ /// Establish a connection to the receiver.
+ ///
+ /// This method establishes a connection to the receiver at the path
+ /// given on construction. It makes the underlying UNIX domain socket
+ /// non blocking, so this method (or subsequent \c push() calls) does not
+ /// block.
+ ///
+ /// \exception BadValue The method is called while an already
+ /// established connection is still active.
+ /// \exception SocketSessionError A system error in socket operation.
+ virtual void connectToReceiver();
+
+ /// Close the connection to the receiver.
+ ///
+ /// The connection must have been established by \c connectToReceiver().
+ /// As long as it's met this method is exception free.
+ ///
+ /// \exception BadValue The connection hasn't been established.
+ virtual void close();
+
+ /// Forward a socket session to the receiver.
+ ///
+ /// This method takes a set of parameters that represent a single socket
+ /// session, renders them in the "wire" format according to the internal
+ /// protocol (see \ref SocketSessionUtility) and forwards them to
+ /// the receiver through the UNIX domain connection.
+ ///
+ /// The connection must have been established by \c connectToReceiver().
+ ///
+ /// For simplicity and for the convenience of detecting application
+ /// errors, this method imposes some restrictions on the parameters:
+ /// - Socket family must be either \c AF_INET or \c AF_INET6
+ /// - The address family (\c sa_family) member of the local and remote
+ /// end points must be equal to the \c family parameter
+ /// - Socket session data must not be empty (\c data_len must not be 0
+ /// and \c data must not be NULL)
+ /// - Data length must not exceed 65535
+ /// These are not architectural limitation, and might be loosened in
+ /// future versions as we see the need for flexibility.
+ ///
+ /// Since the underlying UNIX domain socket is non blocking
+ /// (see the description for the constructor), a call to this method
+ /// should either return immediately or result in exception (in case of
+ /// "would block").
+ ///
+ /// \exception BadValue The method is called before establishing a
+ /// connection or given parameters are invalid.
+ /// \exception SocketSessionError A system error in socket operation,
+ /// including the case where the write operation would block.
+ ///
+ /// \param sock The socket file descriptor
+ /// \param family The address family (such as AF_INET6) of the socket
+ /// \param type The socket type (such as SOCK_DGRAM) of the socket
+ /// \param protocol The transport protocol (such as IPPROTO_UDP) of the
+ /// socket
+ /// \param local_end The local end point of the session in the form of
+ /// \c sockaddr.
+ /// \param remote_end The remote end point of the session in the form of
+ /// \c sockaddr.
+ /// \param data A pointer to the beginning of the memory region for the
+ /// session data
+ /// \param data_len The size of the session data in bytes.
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len);
+
+private:
+ struct ForwarderImpl;
+ ForwarderImpl* impl_;
+};
+
+/// Socket session object.
+///
+/// The \c SocketSession class provides a convenient encapsulation
+/// for the notion of a socket session. It's instantiated with straightforward
+/// parameters corresponding to a socket session, and provides read only
+/// accessors to the parameters to ensure data integrity.
+///
+/// In the initial design and implementation it's only used as a return type
+/// of \c SocketSessionReceiver::pop(), but it could also be used by
+/// the \c SocketSessionForwarder class or for other purposes.
+///
+/// It is assumed that the original owner of a \c SocketSession object
+/// (e.g. a class or a function that constructs it) is responsible for validity
+/// of the data passed to the object. See the description of
+/// \c SocketSessionReceiver::pop() for the specific case of that usage.
+class SocketSession {
+public:
+ /// The constructor.
+ ///
+ /// This is a trivial constructor, taking a straightforward representation
+ /// of session parameters and storing them internally to ensure integrity.
+ ///
+ /// As long as the given parameters are valid it never throws an exception.
+ ///
+ /// \exception BadValue Given parameters don't meet the requirement
+ /// (see the parameter descriptions).
+ ///
+ /// \param sock The socket file descriptor
+ /// \param family The address family (such as AF_INET6) of the socket
+ /// \param type The socket type (such as SOCK_DGRAM) of the socket
+ /// \param protocol The transport protocol (such as IPPROTO_UDP) of the
+ /// socket.
+ /// \param local_end The local end point of the session in the form of
+ /// \c sockaddr. Must not be NULL.
+ /// \param remote_end The remote end point of the session in the form of
+ /// \c sockaddr. Must not be NULL.
+ /// \param data A pointer to the beginning of the memory region for the
+ /// session data. Must not be NULL, and the subsequent \c data_len bytes
+ /// must be valid.
+ /// \param data_len The size of the session data in bytes. Must not be 0.
+ SocketSession(int sock, int family, int type, int protocol,
+ const sockaddr* local_end, const sockaddr* remote_end,
+ const void* data, size_t data_len);
+
+ /// Return the socket file descriptor.
+ int getSocket() const { return (sock_); }
+
+ /// Return the address family (such as AF_INET6) of the socket.
+ int getFamily() const { return (family_); }
+
+ /// Return the socket type (such as SOCK_DGRAM) of the socket.
+ int getType() const { return (type_); }
+
+ /// Return the transport protocol (such as IPPROTO_UDP) of the socket.
+ int getProtocol() const { return (protocol_); }
+
+ /// Return the local end point of the session in the form of \c sockaddr.
+ const sockaddr& getLocalEndpoint() const { return (*local_end_); }
+
+ /// Return the remote end point of the session in the form of \c sockaddr.
+ const sockaddr& getRemoteEndpoint() const { return (*remote_end_); }
+
+ /// Return a pointer to the beginning of the memory region for the session
+ /// data.
+ ///
+ /// In the current implementation it should never be NULL, and the region
+ /// of the size returned by \c getDataLength() is expected to be valid.
+ const void* getData() const { return (data_); }
+
+ /// Return the size of the session data in bytes.
+ ///
+ /// In the current implementation it should be always larger than 0.
+ size_t getDataLength() const { return (data_len_); }
+
+private:
+ const int sock_;
+ const int family_;
+ const int type_;
+ const int protocol_;
+ const sockaddr* local_end_;
+ const sockaddr* remote_end_;
+ const void* const data_;
+ const size_t data_len_;
+};
+
+/// The receiver of socket sessions
+///
+/// An object of this class holds a UNIX domain socket for an
+/// <em>established connection</em>, receives socket sessions from
+/// the remote forwarder, and provides the session to the application
+/// in the form of a \c SocketSession object.
+///
+/// Note that this class is instantiated with an already connected socket;
+/// it's not a listening socket that is accepting connection requests from
+/// forwarders. It's application's responsibility to create the listening
+/// socket, listen on it and accept connections. Once the connection is
+/// established, the application would construct a \c SocketSessionReceiver
+/// object with the socket for the newly established connection.
+/// This behavior is based on the design decision that the application should
+/// decide when it performs (possibly) blocking operations (see \ref
+/// SocketSessionUtility for more details).
+///
+/// See the description of \ref SocketSessionUtility for other details of how
+/// the session forwarding works.
+class SocketSessionReceiver : boost::noncopyable {
+public:
+ /// The constructor.
+ ///
+ /// \exception SocketSessionError Any error on an operation that is
+ /// performed on the given socket as part of initialization.
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \param fd A UNIX domain socket for an established connection with
+ /// a forwarder.
+ explicit SocketSessionReceiver(int fd);
+
+ /// The destructor.
+ ///
+ /// The destructor does \c not close the socket given on construction.
+ /// It's up to the application what to do with it (note that the
+ /// application would have to maintain the socket itself for detecting
+ /// the existence of a new socket session asynchronously).
+ ~SocketSessionReceiver();
+
+ /// Receive a socket session from the forwarder.
+ ///
+ /// This method receives wire-format data (see \ref SocketSessionUtility)
+ /// for a socket session on the UNIX domain socket, performs some
+ /// validation on the data, and returns the session information in the
+ /// form of a \c SocketSession object.
+ ///
+ /// The returned SocketSession object is valid only until the next time
+ /// this method is called or until the \c SocketSessionReceiver object is
+ /// destroyed.
+ ///
+ /// The caller is responsible for closing the received socket (whose
+ /// file descriptor is accessible via \c SocketSession::getSocket()).
+ /// If the caller copies the returned \c SocketSession object, it's also
+ /// responsible for making sure the descriptor is closed at most once.
+ /// On the other hand, the caller is not responsible for freeing the
+ /// socket session data (accessible via \c SocketSession::getData());
+ /// the \c SocketSessionReceiver object will clean it up automatically.
+ ///
+ /// It ensures the following:
+ /// - The address family is either \c AF_INET or \c AF_INET6
+ /// - The address family (\c sa_family) member of the local and remote
+ /// end points must be equal to the \c family parameter
+ /// - The socket session data is not empty and does not exceed 65535
+ /// bytes.
+ /// If the validation fails or an unexpected system error happens
+ /// (including a connection close in the meddle of reception), it throws
+ /// an SocketSessionError exception. When this happens, it's very
+ /// unlikely that a subsequent call to this method succeeds, so in reality
+ /// the application is expected to destruct it and close the socket in
+ /// such a case.
+ ///
+ /// \exception SocketSessionError Invalid data is received or a system
+ /// error on socket operation happens.
+ /// \exception std::bad_alloc Resource allocation failure
+ ///
+ /// \return A \c SocketSession object corresponding to the extracted
+ /// socket session.
+ SocketSession pop();
+
+private:
+ struct ReceiverImpl;
+ ReceiverImpl* impl_;
+};
+
+}
+}
+}
+
+#endif // SOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io_utilities.h b/src/lib/util/io_utilities.h
new file mode 100644
index 0000000..bb32819
--- /dev/null
+++ b/src/lib/util/io_utilities.h
@@ -0,0 +1,185 @@
+// Copyright (C) 2011-2023 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 IO_UTILITIES_H
+#define IO_UTILITIES_H
+
+#include <exceptions/exceptions.h>
+#include <cstddef>
+
+namespace isc {
+namespace util {
+
+/// \brief Read Unsigned 16-Bit Integer from Buffer
+///
+/// This is essentially a copy of the isc::util::InputBuffer::readUint16. It
+/// should really be moved into a separate library.
+///
+/// \param buffer Data buffer at least two bytes long of which the first two
+/// bytes are assumed to represent a 16-bit integer in network-byte
+/// order.
+/// \param length Length of the data buffer.
+///
+/// \return Value of 16-bit integer
+inline uint16_t
+readUint16(const void* buffer, size_t length) {
+ if (length < sizeof(uint16_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint16_t");
+ }
+
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint16_t result = (static_cast<uint16_t>(byte_buffer[0])) << 8;
+ result |= static_cast<uint16_t>(byte_buffer[1]);
+
+ return (result);
+}
+
+/// \brief Write Unsigned 16-Bit Integer to Buffer
+///
+/// This is essentially a copy of isc::util::OutputBuffer::writeUint16. It
+/// should really be moved into a separate library.
+///
+/// \param value 16-bit value to convert
+/// \param buffer Data buffer at least two bytes long into which the 16-bit
+/// value is written in network-byte order.
+/// \param length Length of the data buffer.
+///
+/// \return pointer to the next byte after stored value
+inline uint8_t*
+writeUint16(uint16_t value, void* buffer, size_t length) {
+ if (length < sizeof(uint16_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint16_t");
+ }
+
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff00U) >> 8);
+ byte_buffer[1] = static_cast<uint8_t>(value & 0x00ffU);
+
+ return (byte_buffer + sizeof(uint16_t));
+}
+
+/// \brief Read Unsigned 32-Bit Integer from Buffer
+///
+/// \param buffer Data buffer at least four bytes long of which the first four
+/// bytes are assumed to represent a 32-bit integer in network-byte
+/// order.
+/// \param length Length of the data buffer.
+///
+/// \return Value of 32-bit unsigned integer
+inline uint32_t
+readUint32(const void* buffer, size_t length) {
+ if (length < sizeof(uint32_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint32_t");
+ }
+
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint32_t result = (static_cast<uint32_t>(byte_buffer[0])) << 24;
+ result |= (static_cast<uint32_t>(byte_buffer[1])) << 16;
+ result |= (static_cast<uint32_t>(byte_buffer[2])) << 8;
+ result |= (static_cast<uint32_t>(byte_buffer[3]));
+
+ return (result);
+}
+
+/// \brief Write Unsigned 32-Bit Integer to Buffer
+///
+/// \param value 32-bit value to convert
+/// \param buffer Data buffer at least four bytes long into which the 32-bit
+/// value is written in network-byte order.
+/// \param length Length of the data buffer.
+///
+/// \return pointer to the next byte after stored value
+inline uint8_t*
+writeUint32(uint32_t value, void* buffer, size_t length) {
+ if (length < sizeof(uint32_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint32_t");
+ }
+
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff000000U) >> 24);
+ byte_buffer[1] = static_cast<uint8_t>((value & 0x00ff0000U) >> 16);
+ byte_buffer[2] = static_cast<uint8_t>((value & 0x0000ff00U) >> 8);
+ byte_buffer[3] = static_cast<uint8_t>((value & 0x000000ffU));
+
+ return (byte_buffer + sizeof(uint32_t));
+}
+
+/// \brief Read Unsigned 64-Bit Integer from Buffer
+///
+/// \param buffer Data buffer at least four bytes long of which the first four
+/// bytes are assumed to represent a 64-bit integer in network-byte
+/// order.
+/// \param length Length of the data buffer.
+///
+/// \return Value of 64-bit unsigned integer
+inline uint64_t
+readUint64(const void* buffer, size_t length) {
+ if (length < sizeof(uint64_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to read a uint64_t");
+ }
+
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint64_t result = (static_cast<uint64_t>(byte_buffer[0])) << 56;
+ result |= (static_cast<uint64_t>(byte_buffer[1])) << 48;
+ result |= (static_cast<uint64_t>(byte_buffer[2])) << 40;
+ result |= (static_cast<uint64_t>(byte_buffer[3])) << 32;
+ result |= (static_cast<uint64_t>(byte_buffer[4])) << 24;
+ result |= (static_cast<uint64_t>(byte_buffer[5])) << 16;
+ result |= (static_cast<uint64_t>(byte_buffer[6])) << 8;
+ result |= (static_cast<uint64_t>(byte_buffer[7]));
+
+ return (result);
+}
+
+/// \brief Write Unsigned 64-Bit Integer to Buffer
+///
+/// \param value 64-bit value to convert
+/// \param buffer Data buffer at least four bytes long into which the 64-bit
+/// value is written in network-byte order.
+/// \param length Length of the data buffer.
+///
+/// \return pointer to the next byte after stored value
+inline uint8_t*
+writeUint64(uint64_t value, void* buffer, size_t length) {
+ if (length < sizeof(uint64_t)) {
+ isc_throw(isc::OutOfRange,
+ "Length (" << length << ") of buffer is insufficient " <<
+ "to write a uint64_t");
+ }
+
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff00000000000000UL) >> 56);
+ byte_buffer[1] = static_cast<uint8_t>((value & 0x00ff000000000000UL) >> 48);
+ byte_buffer[2] = static_cast<uint8_t>((value & 0x0000ff0000000000UL) >> 40);
+ byte_buffer[3] = static_cast<uint8_t>((value & 0x000000ff00000000UL) >> 32);
+ byte_buffer[4] = static_cast<uint8_t>((value & 0x00000000ff000000UL) >> 24);
+ byte_buffer[5] = static_cast<uint8_t>((value & 0x0000000000ff0000UL) >> 16);
+ byte_buffer[6] = static_cast<uint8_t>((value & 0x000000000000ff00UL) >> 8);
+ byte_buffer[7] = static_cast<uint8_t>((value & 0x00000000000000ffUL));
+
+ return (byte_buffer + sizeof(uint64_t));
+}
+
+} // namespace util
+} // namespace isc
+
+#endif // IO_UTILITIES_H
diff --git a/src/lib/util/labeled_value.cc b/src/lib/util/labeled_value.cc
new file mode 100644
index 0000000..9fa184a
--- /dev/null
+++ b/src/lib/util/labeled_value.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2013-2020 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/labeled_value.h>
+
+namespace isc {
+namespace util {
+
+/**************************** LabeledValue ****************************/
+
+LabeledValue::LabeledValue(const int value, const std::string& label)
+ : value_(value), label_(label) {
+ if (label.empty()) {
+ isc_throw(LabeledValueError, "labels cannot be empty");
+ }
+}
+
+LabeledValue::~LabeledValue(){
+}
+
+int
+LabeledValue::getValue() const {
+ return (value_);
+}
+
+std::string
+LabeledValue::getLabel() const {
+ return (label_);
+}
+
+bool
+LabeledValue::operator==(const LabeledValue& other) const {
+ return (this->value_ == other.value_);
+}
+
+bool
+LabeledValue::operator!=(const LabeledValue& other) const {
+ return (this->value_ != other.value_);
+}
+
+bool
+LabeledValue::operator<(const LabeledValue& other) const {
+ return (this->value_ < other.value_);
+}
+
+std::ostream& operator<<(std::ostream& os, const LabeledValue& vlp) {
+ os << vlp.getLabel();
+ return (os);
+}
+
+/**************************** LabeledValueSet ****************************/
+
+const char* LabeledValueSet::UNDEFINED_LABEL = "UNDEFINED";
+
+LabeledValueSet::LabeledValueSet(){
+}
+
+LabeledValueSet::~LabeledValueSet() {
+}
+
+void
+LabeledValueSet::add(LabeledValuePtr entry) {
+ if (!entry) {
+ isc_throw(LabeledValueError, "cannot add an null entry to set");
+ }
+
+ const int value = entry->getValue();
+ if (isDefined(value)) {
+ isc_throw(LabeledValueError,
+ "value: " << value << " is already defined as: "
+ << getLabel(value));
+ }
+
+ map_[value] = entry;
+}
+
+void
+LabeledValueSet::add(const int value, const std::string& label) {
+ add(LabeledValuePtr(new LabeledValue(value,label)));
+}
+
+const LabeledValuePtr&
+LabeledValueSet::get(int value) {
+ static LabeledValuePtr undefined;
+ LabeledValueMap::iterator it = map_.find(value);
+ if (it != map_.end()) {
+ return ((*it).second);
+ }
+
+ // Return an empty pointer when not found.
+ return (undefined);
+}
+
+bool
+LabeledValueSet::isDefined(const int value) const {
+ LabeledValueMap::const_iterator it = map_.find(value);
+ return (it != map_.end());
+}
+
+std::string
+LabeledValueSet::getLabel(const int value) const {
+ LabeledValueMap::const_iterator it = map_.find(value);
+ if (it != map_.end()) {
+ const LabeledValuePtr& ptr = (*it).second;
+ return (ptr->getLabel());
+ }
+
+ return (std::string(UNDEFINED_LABEL));
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/labeled_value.h b/src/lib/util/labeled_value.h
new file mode 100644
index 0000000..e85b537
--- /dev/null
+++ b/src/lib/util/labeled_value.h
@@ -0,0 +1,176 @@
+// Copyright (C) 2013-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/.
+
+#ifndef LABELED_VALUE_H
+#define LABELED_VALUE_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+#include <ostream>
+#include <string>
+#include <map>
+
+/// @file labeled_value.h This file defines classes: LabeledValue and
+/// LabeledValueSet.
+
+namespace isc {
+namespace util {
+
+/// @brief Thrown if an error is encountered handling a LabeledValue.
+class LabeledValueError : public isc::Exception {
+public:
+ LabeledValueError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Implements the concept of a constant value with a text label.
+///
+/// This class implements an association between a constant integer value
+/// and a text label. It provides a single constructor, accessors for both
+/// the value and label, and boolean operators which treat the value as
+/// the "key" for comparisons. This allows them to be assembled into
+/// dictionaries of unique values. Note, that the labels are not required to
+/// be unique but in practice it makes little sense to for them to be
+/// otherwise.
+class LabeledValue {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param value the numeric constant value to be labeled.
+ /// @param label the text label to associate to this value.
+ ///
+ /// @throw LabeledValueError if label is empty.
+ LabeledValue(const int value, const std::string& label);
+
+ /// @brief Destructor.
+ ///
+ /// Destructor is virtual to permit derivations.
+ virtual ~LabeledValue();
+
+ /// @brief Gets the integer value of this instance.
+ ///
+ /// @return integer value of this instance.
+ int getValue() const;
+
+ /// @brief Gets the text label of this instance.
+ ///
+ /// @return The text label as string
+ std::string getLabel() const;
+
+ /// @brief Equality operator
+ ///
+ /// @return True if a.value_ is equal to b.value_.
+ bool operator==(const LabeledValue& other) const;
+
+ /// @brief Inequality operator
+ ///
+ /// @return True if a.value_ is not equal to b.value_.
+ bool operator!=(const LabeledValue& other) const;
+
+ /// @brief Less-than operator
+ ///
+ /// @return True if a.value_ is less than b.value_.
+ bool operator<(const LabeledValue& other) const;
+
+private:
+ /// @brief The numeric value to label.
+ int value_;
+
+ /// @brief The text label for the value.
+ std::string label_;
+};
+
+/// @brief Dumps the label to ostream.
+std::ostream& operator<<(std::ostream& os, const LabeledValue& vlp);
+
+/// @brief Defines a shared pointer to a LabeledValue instance.
+typedef boost::shared_ptr<LabeledValue> LabeledValuePtr;
+
+/// @brief Defines a map of pointers to LabeledValues keyed by value.
+typedef std::map<unsigned int, LabeledValuePtr> LabeledValueMap;
+
+
+/// @brief Implements a set of unique LabeledValues.
+///
+/// This class is intended to function as a dictionary of numeric values
+/// and the labels associated with them. It is essentially a thin wrapper
+/// around a std::map of LabeledValues, keyed by their values. This is handy
+/// for defining a set of "valid" constants while conveniently associating a
+/// text label with each value.
+///
+/// This class offers two variants of an add method for adding entries to the
+/// set, and accessors for finding an entry or an entry's label by value.
+/// Note that the add methods may throw but all accessors are exception safe.
+/// It is up to the caller to determine when and if an undefined value is
+/// exception-worthy.
+///
+/// More interestingly, a derivation of this class can be used to "define"
+/// valid instances of derivations of LabeledValue.
+class LabeledValueSet {
+public:
+ /// @brief Defines a text label returned by when value is not found.
+ static const char* UNDEFINED_LABEL;
+
+ /// @brief Constructor
+ ///
+ /// Constructs an empty set.
+ LabeledValueSet();
+
+ /// @brief Destructor
+ ///
+ /// Destructor is virtual to permit derivations.
+ virtual ~LabeledValueSet();
+
+ /// @brief Adds the given entry to the set
+ ///
+ /// @param entry is the entry to add.
+ ///
+ /// @throw LabeledValuePtr if the entry is null or the set already
+ /// contains an entry with the same value.
+ void add(LabeledValuePtr entry);
+
+ /// @brief Adds an entry to the set for the given value and label
+ ///
+ /// @param value the numeric constant value to be labeled.
+ /// @param label the text label to associate to this value.
+ ///
+ /// @throw LabeledValuePtr if the label is empty, or if the set
+ /// already contains an entry with the same value.
+ void add(const int value, const std::string& label);
+
+ /// @brief Fetches a pointer to the entry associated with value
+ ///
+ /// @param value is the value of the entry desired.
+ ///
+ /// @return A pointer to the entry if the entry was found otherwise the
+ /// pointer is empty.
+ const LabeledValuePtr& get(int value);
+
+ /// @brief Tests if the set contains an entry for the given value.
+ ///
+ /// @param value is the value of the entry to test.
+ ///
+ /// @return True if an entry for value exists in the set, false if not.
+ bool isDefined(const int value) const;
+
+ /// @brief Fetches the label for the given value
+ ///
+ /// @param value is the value for which the label is desired.
+ ///
+ /// @return the label of the value if defined, otherwise it returns
+ /// UNDEFINED_LABEL.
+ std::string getLabel(const int value) const;
+
+private:
+ /// @brief The map of labeled values.
+ LabeledValueMap map_;
+};
+
+} // namespace isc::util
+} // namespace isc
+#endif
diff --git a/src/lib/util/memory_segment.h b/src/lib/util/memory_segment.h
new file mode 100644
index 0000000..c9ae97f
--- /dev/null
+++ b/src/lib/util/memory_segment.h
@@ -0,0 +1,333 @@
+// Copyright (C) 2012-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/.
+
+#ifndef MEMORY_SEGMENT_H
+#define MEMORY_SEGMENT_H
+
+#include <exceptions/exceptions.h>
+
+#include <utility>
+
+#include <stdlib.h>
+
+namespace isc {
+namespace util {
+
+/// \brief Exception that can be thrown when constructing a MemorySegment
+/// object.
+class MemorySegmentOpenError : public Exception {
+public:
+ MemorySegmentOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief Exception that is thrown, when allocating space in a MemorySegment
+/// results in growing the underlying segment.
+///
+/// See MemorySegment::allocate() for details.
+class MemorySegmentGrown : public Exception {
+public:
+ MemorySegmentGrown(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief General error that can be thrown by a MemorySegment
+/// implementation.
+class MemorySegmentError : public Exception {
+public:
+ MemorySegmentError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief Memory Segment Class
+///
+/// This class specifies an interface for allocating memory segments.
+/// It's intended to provide a unified interface, whether the underlying
+/// memory is local to a specific process or is sharable by multiple
+/// processes.
+///
+/// This is an abstract class and a real implementation such as
+/// MemorySegmentLocal should be used in code.
+class MemorySegment {
+public:
+ /// \brief Destructor
+ virtual ~MemorySegment() {}
+
+ /// \brief Allocate/acquire a fragment of memory.
+ ///
+ /// The source of the memory is dependent on the implementation used.
+ ///
+ /// Depending on the implementation details, it may have to grow the
+ /// internal memory segment (again, in an implementation dependent way)
+ /// to allocate the required size of memory. In that case the
+ /// implementation must grow the internal segment sufficiently so the
+ /// next call to allocate() for the same size will succeed, and throw
+ /// a \c MemorySegmentGrown exception (not really allocating the memory
+ /// yet).
+ ///
+ /// An application that uses this memory segment abstraction to allocate
+ /// memory should expect this exception, and should normally catch it
+ /// at an appropriate layer (which may be immediately after a call to
+ /// \c allocate() or a bit higher layer). It should interpret the
+ /// exception as any raw address that belongs to the segment may have
+ /// been remapped and must be re-fetched via an already established
+ /// named address using the \c getNamedAddress() method.
+ ///
+ /// The intended use case of \c allocate() with the \c MemorySegmentGrown
+ /// exception is to build a complex object that would internally require
+ /// multiple calls to \c allocate():
+ ///
+ /// \code
+ /// ComplicatedStuff* stuff = NULL;
+ /// while (!stuff) { // this must eventually succeed or result in bad_alloc
+ /// try {
+ /// // create() is a factory method that takes a memory segment
+ /// // and calls allocate() on it multiple times. create()
+ /// // provides an exception guarantee that any intermediately
+ /// // allocated memory will be properly deallocate()-ed on
+ /// // exception.
+ /// stuff = ComplicatedStuff::create(mem_segment);
+ /// } catch (const MemorySegmentGrown&) { /* just try again */ }
+ /// }
+ /// \endcode
+ ///
+ /// This way, \c create() can be written as if each call to \c allocate()
+ /// always succeeds.
+ ///
+ /// Alternatively, or in addition to this, we could introduce a "no throw"
+ /// version of this method with a way to tell the caller the reason of
+ /// any failure (whether it's really out of memory or just due to growing
+ /// the segment). That would be more convenient if the caller wants to
+ /// deal with the failures on a per-call basis rather than as a set
+ /// of calls like in the above example. At the moment, we don't expect
+ /// to have such use-cases, so we only provide the exception
+ /// version.
+ ///
+ /// \throw std::bad_alloc The implementation cannot allocate the
+ /// requested storage.
+ /// \throw MemorySegmentGrown The memory segment doesn't have sufficient
+ /// space for the requested size and has grown internally.
+ /// \throw MemorySegmentError An attempt was made to allocate
+ /// storage on a read-only memory segment.
+ ///
+ /// \param size The size of the memory requested in bytes.
+ /// \return Returns pointer to the memory allocated.
+ virtual void* allocate(size_t size) = 0;
+
+ /// \brief Free/release a segment of memory.
+ ///
+ /// This method may throw <code>isc::OutOfRange</code> if \c size is
+ /// not equal to the originally allocated size. \c size could be
+ /// used by some implementations such as a slice allocator, where
+ /// freeing memory also requires the size to be specified. We also
+ /// use this argument in some implementations to test if all allocated
+ /// memory was deallocated properly.
+ ///
+ /// Specific implementation may also throw \c MemorySegmentError if it
+ /// encounters violation of implementation specific restrictions.
+ ///
+ /// In general, however, this method must succeed and exception free
+ /// as long as the caller passes valid parameters (\c ptr specifies
+ /// memory previously allocated and \c size is correct).
+ ///
+ /// \throw OutOfRange The passed size doesn't match the allocated memory
+ /// size (when identifiable for the implementation).
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param ptr Pointer to the block of memory to free/release. This
+ /// should be equal to a value returned by <code>allocate()</code>.
+ /// \param size The size of the memory to be freed in bytes. This
+ /// should be equal to the number of bytes originally allocated.
+ virtual void deallocate(void* ptr, size_t size) = 0;
+
+ /// \brief Check if all allocated memory was deallocated.
+ ///
+ /// \return Returns <code>true</code> if all allocated memory (including
+ /// names associated by memory addresses by \c setNamedAddress()) was
+ /// deallocated, <code>false</code> otherwise.
+ virtual bool allMemoryDeallocated() const = 0;
+
+ /// \brief Associate specified address in the segment with a given name.
+ ///
+ /// This method establishes an association between the given name and
+ /// the address in an implementation specific way. The stored address
+ /// is retrieved by the name later by calling \c getNamedAddress().
+ /// If the underlying memory segment is sharable by multiple processes,
+ /// the implementation must ensure the portability of the association;
+ /// if a process gives an address in the shared segment a name, another
+ /// process that shares the same segment should be able to retrieve the
+ /// corresponding address by that name (in such cases the real address
+ /// may be different between these two processes).
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// \c addr must be 0 (NULL) or an address that belongs to this segment.
+ /// The latter case means it must be the return value of a previous call
+ /// to \c allocate(). The actual implementation is encouraged to detect
+ /// violation of this restriction and signal it with an exception, but
+ /// it's not an API requirement. It's generally the caller's
+ /// responsibility to meet the restriction. Note that NULL is allowed
+ /// as \c addr even if it wouldn't be considered to "belong to" the
+ /// segment in its normal sense; it can be used to indicate that memory
+ /// has not been allocated for the specified name. A subsequent call
+ /// to \c getNamedAddress() will return NamedAddressResult(true, NULL)
+ /// for that name.
+ ///
+ /// \note Naming an address is intentionally separated from allocation
+ /// so that, for example, one module of a program can name a memory
+ /// region allocated by another module of the program.
+ ///
+ /// There can be an existing association for the name; in that case the
+ /// association will be overridden with the newly given address.
+ ///
+ /// While normally unexpected, it's possible that available space in the
+ /// segment is not sufficient to allocate a space (if not already exist)
+ /// for the specified name in the segment. In that case, if possible, the
+ /// implementation should try to grow the internal segment and retry
+ /// establishing the association. The implementation should throw
+ /// std::bad_alloc if even reasonable attempts of retry still fail.
+ ///
+ /// This method should normally return false, but if the internal segment
+ /// had to grow to store the given name, it must return true. The
+ /// application should interpret it just like the case of
+ /// \c MemorySegmentGrown exception thrown from the \c allocate() method.
+ ///
+ /// \note The behavior in case the internal segment grows is different
+ /// from that of \c allocate(). This is intentional. In intended use
+ /// cases (for the moment) this method will be called independently,
+ /// rather than as part of a set of allocations. It's also expected
+ /// that other raw memory addresses (which would have been invalidated
+ /// due to the change to the segment) won't be referenced directly
+ /// immediately after this call. So, the caller should normally be able
+ /// to call this method as mostly never-fail one (except in case of real
+ /// memory exhaustion) and ignore the return value.
+ ///
+ /// \throw std::bad_alloc Allocation of a segment space for the given name
+ /// failed.
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param name A C string to be associated with \c addr. Must not be NULL.
+ /// \param addr A memory address returned by a prior call to \c allocate.
+ /// \return true if the internal segment has grown to allocate space for
+ /// the name; false otherwise (see above).
+ bool setNamedAddress(const char* name, void* addr) {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (setNamedAddressImpl(name, addr));
+ }
+
+ /// \brief Type definition for result returned by getNamedAddress()
+ typedef std::pair<bool, void*> NamedAddressResult;
+
+ /// \brief Return the address in the segment that has the given name.
+ ///
+ /// This method returns the memory address in the segment corresponding
+ /// to the specified \c name. The name and address must have been
+ /// associated by a prior call to \c setNameAddress(). If no address
+ /// associated with the given name is found, it returns NULL.
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// This method should generally be considered exception free, but there
+ /// can be a small chance it throws, depending on the internal
+ /// implementation (e.g., if it converts the name to std::string), so the
+ /// API doesn't guarantee that property. In general, if this method
+ /// throws it should be considered a fatal condition.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ ///
+ /// \param name A C string of which the segment memory address is to be
+ /// returned. Must not be NULL.
+ /// \return An std::pair containing a bool (set to true if the name
+ /// was found, or false otherwise) and the address associated with
+ /// the name (which is undefined if the name was not found).
+ NamedAddressResult getNamedAddress(const char* name) const {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (getNamedAddressImpl(name));
+ }
+
+ /// \brief Delete a name previously associated with a segment address.
+ ///
+ /// This method deletes the association of the given \c name to
+ /// a corresponding segment address previously established by
+ /// \c setNamedAddress(). If there is no association for the given name
+ /// this method returns false; otherwise it returns true.
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// See \c getNamedAddress() about exception consideration.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param name A C string of which the segment memory address is to be
+ /// deleted. Must not be NULL.
+ bool clearNamedAddress(const char* name) {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (clearNamedAddressImpl(name));
+ }
+
+private:
+ /// \brief Validate the passed name.
+ ///
+ /// This method validates the passed name (for name/address pairs)
+ /// and throws \c InvalidParameter if the name fails
+ /// validation. Otherwise, it does nothing.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ static void validateName(const char* name) {
+ if (!name) {
+ isc_throw(InvalidParameter, "NULL is invalid for a name.");
+ } else if (*name == '\0') {
+ isc_throw(InvalidParameter, "Empty names are invalid.");
+ } else if (*name == '_') {
+ isc_throw(InvalidParameter,
+ "Names beginning with '_' are reserved for "
+ "internal use only.");
+ }
+ }
+
+protected:
+ /// \brief Implementation of setNamedAddress beyond common validation.
+ virtual bool setNamedAddressImpl(const char* name, void* addr) = 0;
+
+ /// \brief Implementation of getNamedAddress beyond common validation.
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const = 0;
+
+ /// \brief Implementation of clearNamedAddress beyond common validation.
+ virtual bool clearNamedAddressImpl(const char* name) = 0;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // MEMORY_SEGMENT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/memory_segment_local.cc b/src/lib/util/memory_segment_local.cc
new file mode 100644
index 0000000..2331e4b
--- /dev/null
+++ b/src/lib/util/memory_segment_local.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2012-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 <util/memory_segment_local.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+
+void*
+MemorySegmentLocal::allocate(size_t size) {
+ void* ptr = malloc(size);
+ if (ptr == NULL) {
+ throw std::bad_alloc();
+ }
+
+ allocated_size_ += size;
+ return (ptr);
+}
+
+void
+MemorySegmentLocal::deallocate(void* ptr, size_t size) {
+ if (ptr == NULL) {
+ // Return early if NULL is passed to be deallocated (without
+ // modifying allocated_size, or comparing against it).
+ return;
+ }
+
+ if (size > allocated_size_) {
+ isc_throw(OutOfRange, "Invalid size to deallocate: " << size
+ << "; currently allocated size: " << allocated_size_);
+ }
+
+ allocated_size_ -= size;
+ free(ptr);
+}
+
+bool
+MemorySegmentLocal::allMemoryDeallocated() const {
+ return (allocated_size_ == 0 && named_addrs_.empty());
+}
+
+MemorySegment::NamedAddressResult
+MemorySegmentLocal::getNamedAddressImpl(const char* name) const {
+ std::map<std::string, void*>::const_iterator found =
+ named_addrs_.find(name);
+ if (found != named_addrs_.end()) {
+ return (NamedAddressResult(true, found->second));
+ }
+ return (NamedAddressResult(false, static_cast<void*>(0)));
+}
+
+bool
+MemorySegmentLocal::setNamedAddressImpl(const char* name, void* addr) {
+ named_addrs_[name] = addr;
+ return (false);
+}
+
+bool
+MemorySegmentLocal::clearNamedAddressImpl(const char* name) {
+ const size_t n_erased = named_addrs_.erase(name);
+ return (n_erased != 0);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/memory_segment_local.h b/src/lib/util/memory_segment_local.h
new file mode 100644
index 0000000..2c0ee53
--- /dev/null
+++ b/src/lib/util/memory_segment_local.h
@@ -0,0 +1,100 @@
+// Copyright (C) 2012-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/.
+
+#ifndef MEMORY_SEGMENT_LOCAL_H
+#define MEMORY_SEGMENT_LOCAL_H
+
+#include <util/memory_segment.h>
+
+#include <string>
+#include <map>
+
+namespace isc {
+namespace util {
+
+/// \brief malloc/free based Memory Segment class
+///
+/// This class specifies a concrete implementation for a malloc/free
+/// based MemorySegment. Please see the MemorySegment class
+/// documentation for usage.
+class MemorySegmentLocal : public MemorySegment {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a local memory segment object
+ MemorySegmentLocal() : allocated_size_(0) {
+ }
+
+ /// \brief Destructor
+ virtual ~MemorySegmentLocal() {}
+
+ /// \brief Allocate/acquire a segment of memory. The source of the
+ /// memory is libc's malloc().
+ ///
+ /// Throws <code>std::bad_alloc</code> if the implementation cannot
+ /// allocate the requested storage.
+ ///
+ /// \param size The size of the memory requested in bytes.
+ /// \return Returns pointer to the memory allocated.
+ virtual void* allocate(size_t size);
+
+ /// \brief Free/release a segment of memory.
+ ///
+ /// This method may throw <code>isc::OutOfRange</code> if \c size is
+ /// not equal to the originally allocated size.
+ ///
+ /// \param ptr Pointer to the block of memory to free/release. This
+ /// should be equal to a value returned by <code>allocate()</code>.
+ /// \param size The size of the memory to be freed in bytes. This
+ /// should be equal to the number of bytes originally allocated.
+ virtual void deallocate(void* ptr, size_t size);
+
+ /// \brief Check if all allocated memory was deallocated.
+ ///
+ /// \return Returns <code>true</code> if all allocated memory was
+ /// deallocated, <code>false</code> otherwise.
+ virtual bool allMemoryDeallocated() const;
+
+ /// \brief Local segment version of getNamedAddress.
+ ///
+ /// There's a small chance this method could throw std::bad_alloc.
+ /// It should be considered a fatal error.
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
+
+ /// \brief Local segment version of setNamedAddress.
+ ///
+ /// This version does not validate the given address to see whether it
+ /// belongs to this segment.
+ ///
+ /// This implementation of this method always returns \c false (but the
+ /// application should expect a return value of \c true unless it knows
+ /// the memory segment class is \c MemorySegmentLocal and needs to
+ /// exploit the fact).
+ virtual bool setNamedAddressImpl(const char* name, void* addr);
+
+ /// \brief Local segment version of clearNamedAddress.
+ ///
+ /// There's a small chance this method could throw std::bad_alloc.
+ /// It should be considered a fatal error.
+ virtual bool clearNamedAddressImpl(const char* name);
+
+private:
+ // allocated_size_ can underflow, wrap around to max size_t (which
+ // is unsigned). But because we only do a check against 0 and not a
+ // relation comparison, this is okay.
+ size_t allocated_size_;
+
+ std::map<std::string, void*> named_addrs_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // MEMORY_SEGMENT_LOCAL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/multi_threading_mgr.cc b/src/lib/util/multi_threading_mgr.cc
new file mode 100644
index 0000000..311b0ab
--- /dev/null
+++ b/src/lib/util/multi_threading_mgr.cc
@@ -0,0 +1,291 @@
+// Copyright (C) 2019-2022 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/multi_threading_mgr.h>
+
+namespace isc {
+namespace util {
+
+MultiThreadingMgr::MultiThreadingMgr()
+ : enabled_(false), critical_section_count_(0), thread_pool_size_(0) {
+}
+
+MultiThreadingMgr::~MultiThreadingMgr() {
+}
+
+MultiThreadingMgr&
+MultiThreadingMgr::instance() {
+ static MultiThreadingMgr manager;
+ return (manager);
+}
+
+bool
+MultiThreadingMgr::getMode() const {
+ return (enabled_);
+}
+
+void
+MultiThreadingMgr::setMode(bool enabled) {
+ enabled_ = enabled;
+}
+
+void
+MultiThreadingMgr::enterCriticalSection() {
+ checkCallbacksPermissions();
+ bool inside = isInCriticalSection();
+ // Increment the counter to allow CS to be created in the registered
+ // callbacks (in which case the new CS would not call callbacks again).
+ // The counter must be updated regardless of the MT mode because the MT mode
+ // can change between the constructor call and the destructor call.
+ ++critical_section_count_;
+ if (getMode() && !inside) {
+ if (getThreadPoolSize()) {
+ thread_pool_.stop();
+ }
+ // Now it is safe to call callbacks which can also create other CSs.
+ callEntryCallbacks();
+ }
+}
+
+void
+MultiThreadingMgr::exitCriticalSection() {
+ // The number of CS destructors should match the number of CS constructors.
+ // The case when counter is 0 is only possible if calling this function
+ // explicitly, which is a programming error.
+ if (!isInCriticalSection()) {
+ isc_throw(InvalidOperation, "invalid value for critical section count");
+ }
+ // Decrement the counter to allow the check for last CS destructor which
+ // would result in restarting the thread pool.
+ // The counter must be updated regardless of the MT mode because the MT mode
+ // can change between the constructor call and the destructor call.
+ --critical_section_count_;
+ if (getMode() && !isInCriticalSection()) {
+ if (getThreadPoolSize()) {
+ thread_pool_.start(getThreadPoolSize());
+ }
+ // Now it is safe to call callbacks which can also create other CSs.
+ callExitCallbacks();
+ }
+}
+
+bool
+MultiThreadingMgr::isInCriticalSection() {
+ return (critical_section_count_ != 0);
+}
+
+ThreadPool<std::function<void()>>&
+MultiThreadingMgr::getThreadPool() {
+ return thread_pool_;
+}
+
+uint32_t
+MultiThreadingMgr::getThreadPoolSize() const {
+ return (thread_pool_size_);
+}
+
+void
+MultiThreadingMgr::setThreadPoolSize(uint32_t size) {
+ thread_pool_size_ = size;
+}
+
+uint32_t
+MultiThreadingMgr::getPacketQueueSize() {
+ return (thread_pool_.getMaxQueueSize());
+}
+
+void
+MultiThreadingMgr::setPacketQueueSize(uint32_t size) {
+ thread_pool_.setMaxQueueSize(size);
+}
+
+uint32_t
+MultiThreadingMgr::detectThreadCount() {
+ return (std::thread::hardware_concurrency());
+}
+
+void
+MultiThreadingMgr::apply(bool enabled, uint32_t thread_count, uint32_t queue_size) {
+ // check the enabled flag
+ if (enabled) {
+ // check for auto scaling (enabled flag true but thread_count 0)
+ if (!thread_count) {
+ // might also return 0
+ thread_count = MultiThreadingMgr::detectThreadCount();
+ }
+ } else {
+ thread_count = 0;
+ queue_size = 0;
+ }
+ // check enabled flag and explicit number of threads or system supports
+ // hardware concurrency
+ if (thread_count) {
+ if (thread_pool_.size()) {
+ thread_pool_.stop();
+ }
+ setThreadPoolSize(thread_count);
+ setPacketQueueSize(queue_size);
+ setMode(true);
+ if (!isInCriticalSection()) {
+ thread_pool_.start(thread_count);
+ }
+ } else {
+ removeAllCriticalSectionCallbacks();
+ thread_pool_.reset();
+ setMode(false);
+ setThreadPoolSize(thread_count);
+ setPacketQueueSize(queue_size);
+ }
+}
+
+void
+MultiThreadingMgr::checkCallbacksPermissions() {
+ if (getMode()) {
+ for (const auto& cb : cs_callbacks_.getCallbackSets()) {
+ try {
+ (cb.check_cb_)();
+ } catch (const isc::MultiThreadingInvalidOperation& ex) {
+ // If any registered callback throws, the exception needs to be
+ // propagated to the caller of the
+ // @ref MultiThreadingCriticalSection constructor.
+ // Because this function is called by the
+ // @ref MultiThreadingCriticalSection constructor, throwing here
+ // is safe.
+ throw;
+ } catch (...) {
+ // We can't log it and throwing could be chaos.
+ // We'll swallow it and tell people their callbacks
+ // must be exception-proof
+ }
+ }
+ }
+}
+
+void
+MultiThreadingMgr::callEntryCallbacks() {
+ if (getMode()) {
+ const auto& callbacks = cs_callbacks_.getCallbackSets();
+ for (auto cb_it = callbacks.begin(); cb_it != callbacks.end(); cb_it++) {
+ try {
+ (cb_it->entry_cb_)();
+ } catch (...) {
+ // We can't log it and throwing could be chaos.
+ // We'll swallow it and tell people their callbacks
+ // must be exception-proof
+ }
+ }
+ }
+}
+
+void
+MultiThreadingMgr::callExitCallbacks() {
+ if (getMode()) {
+ const auto& callbacks = cs_callbacks_.getCallbackSets();
+ for (auto cb_it = callbacks.rbegin(); cb_it != callbacks.rend(); cb_it++) {
+ try {
+ (cb_it->exit_cb_)();
+ } catch (...) {
+ // We can't log it and throwing could be chaos.
+ // We'll swallow it and tell people their callbacks
+ // must be exception-proof
+ // Because this function is called by the
+ // @ref MultiThreadingCriticalSection destructor, throwing here
+ // is not safe and will cause the process to crash.
+ }
+ }
+ }
+}
+
+void
+MultiThreadingMgr::addCriticalSectionCallbacks(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb) {
+ cs_callbacks_.addCallbackSet(name, check_cb, entry_cb, exit_cb);
+}
+
+void
+MultiThreadingMgr::removeCriticalSectionCallbacks(const std::string& name) {
+ cs_callbacks_.removeCallbackSet(name);
+}
+
+void
+MultiThreadingMgr::removeAllCriticalSectionCallbacks() {
+ cs_callbacks_.removeAll();
+}
+
+MultiThreadingCriticalSection::MultiThreadingCriticalSection() {
+ MultiThreadingMgr::instance().enterCriticalSection();
+}
+
+MultiThreadingCriticalSection::~MultiThreadingCriticalSection() {
+ MultiThreadingMgr::instance().exitCriticalSection();
+}
+
+MultiThreadingLock::MultiThreadingLock(std::mutex& mutex) {
+ if (MultiThreadingMgr::instance().getMode()) {
+ lock_ = std::unique_lock<std::mutex>(mutex);
+ }
+}
+
+void
+CSCallbackSetList::addCallbackSet(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb) {
+ if (name.empty()) {
+ isc_throw(BadValue, "CSCallbackSetList - name cannot be empty");
+ }
+
+ if (!check_cb) {
+ isc_throw(BadValue, "CSCallbackSetList - check callback for " << name
+ << " cannot be empty");
+ }
+
+ if (!entry_cb) {
+ isc_throw(BadValue, "CSCallbackSetList - entry callback for " << name
+ << " cannot be empty");
+ }
+
+ if (!exit_cb) {
+ isc_throw(BadValue, "CSCallbackSetList - exit callback for " << name
+ << " cannot be empty");
+ }
+
+ for (auto const& callback : cb_sets_) {
+ if (callback.name_ == name) {
+ isc_throw(BadValue, "CSCallbackSetList - callbacks for " << name
+ << " already exist");
+ }
+ }
+
+ cb_sets_.push_back(CSCallbackSet(name, check_cb, entry_cb, exit_cb));
+}
+
+void
+CSCallbackSetList::removeCallbackSet(const std::string& name) {
+ for (auto it = cb_sets_.begin(); it != cb_sets_.end(); ++it) {
+ if ((*it).name_ == name) {
+ cb_sets_.erase(it);
+ break;
+ }
+ }
+}
+
+void
+CSCallbackSetList::removeAll() {
+ cb_sets_.clear();
+}
+
+const std::list<CSCallbackSet>&
+CSCallbackSetList::getCallbackSets() {
+ return (cb_sets_);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/multi_threading_mgr.h b/src/lib/util/multi_threading_mgr.h
new file mode 100644
index 0000000..e86c488
--- /dev/null
+++ b/src/lib/util/multi_threading_mgr.h
@@ -0,0 +1,374 @@
+// Copyright (C) 2019-2022 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 MULTI_THREADING_MGR_H
+#define MULTI_THREADING_MGR_H
+
+#include <util/thread_pool.h>
+#include <functional>
+#include <list>
+
+#include <boost/noncopyable.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+
+
+/// @brief Embodies a named set of CriticalSection callbacks.
+///
+/// This class associates with a name, a set of callbacks, one to be invoked
+/// before CriticalSection entry and exit callbacks to check current thread
+/// permissions to perform such actions, one to be invoked upon CriticalSection
+/// entry and one to be invoked upon CriticalSection exit.
+/// The name allows the set to be uniquely identified such that they can be
+/// added and removed as needed.
+/// The check current thread permissions callback needs to throw
+/// @ref MultiThreadingInvalidOperation if the thread is not allowed to perform
+/// CriticalSection entry and exit. Any other exception thrown by the check
+/// permission callbacks will be silently ignored.
+/// The CriticalSection entry and exit callbacks exceptions will be silently
+/// ignored.
+struct CSCallbackSet {
+ /// @brief Defines a callback as a simple void() functor.
+ typedef std::function<void()> Callback;
+
+ /// @brief Constructor
+ ///
+ /// @param name Name by which the callbacks can be found.
+ /// @param check_cb Callback to check current thread permissions to call
+ /// the CriticalSection entry and exit callbacks.
+ /// @param entry_cb Callback to invoke upon CriticalSection entry.
+ /// @param exit_cb Callback to invoke upon CriticalSection exit.
+ CSCallbackSet(const std::string& name, const Callback& check_cb,
+ const Callback& entry_cb, const Callback& exit_cb)
+ : name_(name), check_cb_(check_cb), entry_cb_(entry_cb),
+ exit_cb_(exit_cb) {}
+
+ /// @brief Name by which the callback can be found.
+ std::string name_;
+
+ /// @brief Check permissions callback associated with name.
+ Callback check_cb_;
+
+ /// @brief Entry point callback associated with name.
+ Callback entry_cb_;
+
+ /// @brief Exit point callback associated with name.
+ Callback exit_cb_;
+};
+
+/// @brief Maintains list of unique CSCallbackSets.
+///
+/// The list emphasizes iteration order and speed over
+/// retrieval by name. When iterating over the list of
+/// callback sets, they are returned in the order they were
+/// added, not by name.
+class CSCallbackSetList {
+public:
+ /// @brief Constructor.
+ CSCallbackSetList() {}
+
+ /// @brief Adds a callback set to the list.
+ ///
+ /// @param name Name of the callback to add.
+ /// @param check_cb The check permissions callback to add.
+ /// @param entry_cb The CriticalSection entry callback to add.
+ /// @param exit_cb The CriticalSection exit callback to add.
+ ///
+ /// @throw BadValue if the name is already in the list,
+ /// the name is blank, or either callback is empty.
+ void addCallbackSet(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb);
+
+ /// @brief Removes a callback set from the list.
+ ///
+ /// @param name Name of the callback to remove.
+ /// If no such callback exists, it simply returns.
+ void removeCallbackSet(const std::string& name);
+
+ /// @brief Removes all callbacks from the list.
+ void removeAll();
+
+ /// @brief Fetches the list of callback sets.
+ const std::list<CSCallbackSet>& getCallbackSets();
+
+private:
+ /// @brief The list of callback sets.
+ std::list<CSCallbackSet> cb_sets_;
+};
+
+/// @brief Multi Threading Manager.
+///
+/// This singleton class holds the multi-threading mode.
+///
+/// See the @ref MultiThreadingLock class for a standard way of protecting code
+/// with a mutex. Or if you want to make it look like you're writing more code:
+/// @code
+/// if (MultiThreadingMgr::instance().getMode()) {
+/// multi-threaded code
+/// } else {
+/// single-threaded code
+/// }
+/// @endcode
+///
+/// For instance for a class protected by its mutex:
+/// @code
+/// namespace locked {
+/// void foo() { ... }
+/// } // end of locked namespace
+///
+/// void foo() {
+/// if (MultiThreadingMgr::instance().getMode()) {
+/// lock_guard<mutex> lock(mutex_);
+/// locked::foo();
+/// } else {
+/// locked::foo();
+/// }
+/// }
+/// @endcode
+class MultiThreadingMgr : public boost::noncopyable {
+public:
+ /// @brief Returns a single instance of Multi Threading Manager.
+ ///
+ /// MultiThreadingMgr is a singleton and this method is the only way
+ /// of accessing it.
+ ///
+ /// @return the single instance.
+ static MultiThreadingMgr& instance();
+
+ /// @brief Get the multi-threading mode.
+ ///
+ /// @return The current multi-threading mode: true if multi-threading is
+ /// enabled, false otherwise.
+ bool getMode() const;
+
+ /// @brief Set the multi-threading mode.
+ ///
+ /// @param enabled The new mode.
+ void setMode(bool enabled);
+
+ /// @brief Enter critical section.
+ ///
+ /// When entering @ref MultiThreadingCriticalSection, increment internal
+ /// counter so that any configuration change that might start the packet
+ /// thread pool is delayed until exiting the respective section.
+ /// If the internal counter is 0, then stop the thread pool.
+ ///
+ /// Invokes all CriticalSection entry callbacks. Has no effect in
+ /// single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all entry callbacks
+ /// without logging to avoid breaking the CS chain.
+ void enterCriticalSection();
+
+ /// @brief Exit critical section.
+ ///
+ /// When exiting @ref MultiThreadingCriticalSection, decrement internal
+ /// counter so that the dhcp thread pool can be started according to the
+ /// new configuration.
+ /// If the internal counter is 0, then start the thread pool.
+ ///
+ /// Invokes all CriticalSection exit callbacks. Has no effect in
+ /// single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all exit callbacks
+ /// without logging to avoid breaking the CS chain.
+ void exitCriticalSection();
+
+ /// @brief Is in critical section flag.
+ ///
+ /// @return The critical section flag.
+ bool isInCriticalSection();
+
+ /// @brief Get the dhcp thread pool.
+ ///
+ /// @return The dhcp thread pool.
+ ThreadPool<std::function<void()>>& getThreadPool();
+
+ /// @brief Get the configured dhcp thread pool size.
+ ///
+ /// @return The dhcp thread pool size.
+ uint32_t getThreadPoolSize() const;
+
+ /// @brief Set the configured dhcp thread pool size.
+ ///
+ /// @param size The dhcp thread pool size.
+ void setThreadPoolSize(uint32_t size);
+
+ /// @brief Get the configured dhcp packet queue size.
+ ///
+ /// @return The dhcp packet queue size.
+ uint32_t getPacketQueueSize();
+
+ /// @brief Set the configured dhcp packet queue size.
+ ///
+ /// @param size The dhcp packet queue size.
+ void setPacketQueueSize(uint32_t size);
+
+ /// @brief The system current detected hardware concurrency thread count.
+ ///
+ /// This function will return 0 if the value can not be determined.
+ ///
+ /// @return The thread count.
+ static uint32_t detectThreadCount();
+
+ /// @brief Apply the multi-threading related settings.
+ ///
+ /// This function should usually be called after constructing a
+ /// @ref MultiThreadingCriticalSection so that all thread pool parameters
+ /// can be safely applied.
+ ///
+ /// @param enabled The enabled flag: true if multi-threading is enabled,
+ /// false otherwise.
+ /// @param thread_count The desired number of threads: non 0 if explicitly
+ /// configured, 0 if auto scaling is desired
+ /// @param queue_size The desired thread queue size: non 0 if explicitly
+ /// configured, 0 for unlimited size
+ void apply(bool enabled, uint32_t thread_count, uint32_t queue_size);
+
+ /// @brief Adds a set of callbacks to the list of CriticalSection callbacks.
+ ///
+ /// @note Callbacks must be exception-safe, handling any errors themselves.
+ ///
+ /// @param name Name of the set of callbacks. This value is used by the
+ /// callback owner to add and remove them. Duplicates are not allowed.
+ /// @param check_cb Callback to check current thread permissions to call
+ /// the CriticalSection entry and exit callbacks.
+ /// @param entry_cb Callback to invoke upon CriticalSection entry. Cannot be
+ /// empty.
+ /// @param exit_cb Callback to invoke upon CriticalSection exit. Cannot be
+ /// empty.
+ void addCriticalSectionCallbacks(const std::string& name,
+ const CSCallbackSet::Callback& check_cb,
+ const CSCallbackSet::Callback& entry_cb,
+ const CSCallbackSet::Callback& exit_cb);
+
+ /// @brief Removes the set of callbacks associated with a given name
+ /// from the list of CriticalSection callbacks.
+ ///
+ /// If the name is not found in the list, it simply returns, otherwise
+ /// both callbacks registered under the name are removed.
+ ///
+ /// @param name Name of the set of callbacks to remove.
+ void removeCriticalSectionCallbacks(const std::string& name);
+
+ /// @brief Removes all callbacks in the list of CriticalSection callbacks.
+ void removeAllCriticalSectionCallbacks();
+
+protected:
+
+ /// @brief Constructor.
+ MultiThreadingMgr();
+
+ /// @brief Destructor.
+ virtual ~MultiThreadingMgr();
+
+private:
+
+ /// @brief Class method tests if current thread is allowed to enter the
+ /// @ref MultiThreadingCriticalSection and to invoke the entry and exit
+ /// callbacks.
+ ///
+ /// Has no effect in single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all check permission
+ /// callbacks without logging to avoid breaking the CS chain, except for the
+ /// @ref MultiThreadingInvalidOperation which needs to be propagated to the
+ /// scope of the @ref MultiThreadingCriticalSection constructor.
+ /// @throw MultiThreadingInvalidOperation if current thread has no
+ /// permission to enter CriticalSection.
+ void checkCallbacksPermissions();
+
+ /// @brief Class method which invokes CriticalSection entry callbacks.
+ ///
+ /// Has no effect in single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all entry callbacks
+ /// without logging to avoid breaking the CS chain.
+ void callEntryCallbacks();
+
+ /// @brief Class method which invokes CriticalSection exit callbacks.
+ ///
+ /// Has no effect in single-threaded mode.
+ ///
+ /// @note This function swallows exceptions thrown by all exit callbacks
+ /// without logging to avoid breaking the CS chain.
+ void callExitCallbacks();
+
+ /// @brief The current multi-threading mode.
+ ///
+ /// The multi-threading flag: true if multi-threading is enabled, false
+ /// otherwise.
+ bool enabled_;
+
+ /// @brief The critical section count.
+ ///
+ /// In case the configuration is applied within a
+ /// @ref MultiThreadingCriticalSection, the thread pool should not be
+ /// started until leaving the respective section.
+ /// This handles multiple interleaved sections.
+ uint32_t critical_section_count_;
+
+ /// @brief The configured size of the dhcp thread pool.
+ uint32_t thread_pool_size_;
+
+ /// @brief Packet processing thread pool.
+ ThreadPool<std::function<void()>> thread_pool_;
+
+ /// @brief List of CriticalSection entry and exit callback sets.
+ CSCallbackSetList cs_callbacks_;
+};
+
+/// @brief RAII lock object to protect the code in the same scope with a mutex
+struct MultiThreadingLock {
+ /// @brief Constructor locks the mutex if multi-threading is enabled.
+ ///
+ /// The lock is automatically unlocked in the default destructor.
+ ///
+ /// @param mutex the mutex to be locked
+ MultiThreadingLock(std::mutex& mutex);
+
+private:
+ /// @brief object keeping the mutex locked for its entire lifetime
+ std::unique_lock<std::mutex> lock_;
+};
+
+/// @note: everything here MUST be used ONLY from the main thread.
+/// When called from a thread of the pool it can deadlock.
+
+/// @brief RAII class creating a critical section.
+///
+/// @note: the multi-threading mode MUST NOT be changed in the RAII
+/// @c MultiThreadingCriticalSection body.
+/// @note: starting and stopping the dhcp thread pool should be handled
+/// in the main thread, if done on one of the processing threads will cause a
+/// deadlock.
+/// This is mainly useful in hook commands which handle configuration
+/// changes.
+class MultiThreadingCriticalSection : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Entering the critical section. The dhcp thread pool instance will be
+ /// stopped so that all configuration changes can be safely applied.
+ MultiThreadingCriticalSection();
+
+ /// @brief Destructor.
+ ///
+ /// Leaving the critical section. The dhcp thread pool instance will be
+ /// started according to the new configuration.
+ virtual ~MultiThreadingCriticalSection();
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // MULTI_THREADING_MGR_H
diff --git a/src/lib/util/optional.h b/src/lib/util/optional.h
new file mode 100644
index 0000000..16d5dc0
--- /dev/null
+++ b/src/lib/util/optional.h
@@ -0,0 +1,201 @@
+// Copyright (C) 2014-2022 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 OPTIONAL_H
+#define OPTIONAL_H
+
+#include <exceptions/exceptions.h>
+#include <ostream>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief A template representing an optional value.
+///
+/// This template class encapsulates an optional value. The default implementation
+/// encapsulates numeric values, but additional specializations are defined
+/// as necessary to support other types od data.
+///
+/// This class includes a boolean flag which indicates if the encapsulated
+/// value is specified or unspecified. For example, a configuration parser
+/// for the DHCP server may use this class to represent a value of the
+/// configuration parameter which may appear in the configuration file, but
+/// is not mandatory. The value of the @c Optional may be initialized to
+/// "unspecified" initially. When the configuration parser finds that the
+/// particular parameter exists in the configuration file, the default value
+/// can be overridden and the value may be marked as "specified". If the
+/// parameter is not found, the value remains "unspecified" and the appropriate
+/// actions may be taken, e.g. the default value may be used.
+///
+/// @tparam Type of the encapsulated value.
+template<typename T>
+class Optional {
+public:
+
+ /// @brief Type of the encapsulated value.
+ typedef T ValueType;
+
+ /// @brief Assigns a new value value and marks it "specified".
+ ///
+ /// @tparam A Type of the value to be assigned. Typically this is @c T, but
+ /// may also be a type that can be cast to @c T.
+ /// @param other new actual value.
+ template<typename A>
+ Optional<T>& operator=(A other) {
+ default_ = other;
+ unspecified_ = false;
+ return (*this);
+ }
+
+ /// @brief Type cast operator.
+ ///
+ /// This operator converts the optional value to the actual value being
+ /// encapsulated.
+ ///
+ /// @return Encapsulated value.
+ operator T() const {
+ return (default_);
+ }
+
+ /// @brief Equality operator.
+ ///
+ /// @param other value to be compared.
+ bool operator==(const T& other) const {
+ return (default_ == other);
+ }
+
+ /// @brief Inequality operator.
+ ///
+ /// @param other value to be compared.
+ bool operator!=(const T& other) const {
+ return (default_ != other);
+ }
+
+ /// @brief Default constructor.
+ ///
+ /// Sets the encapsulated value to 0 and marks it as "unspecified".
+ ///
+ /// The caller must ensure that the constructor of the class @c T
+ /// creates a valid object when invoked with 0 as an argument.
+ /// For example, a @c std::string(0) compiles but will crash at
+ /// runtime as 0 is not a valid pointer for the
+ /// @c std::string(const char*) constructor. Therefore, the
+ /// specialization of the @c Optional template for @c std::string
+ /// is provided below. It uses @c std::string default constructor.
+ ///
+ /// For any other type used with this template which doesn't come
+ /// with an appropriate constructor, the caller must create a
+ /// template specialization similar to the one provided for
+ /// @c std::string below.
+ Optional()
+ : default_(T(0)), unspecified_(true) {
+ }
+
+ /// @brief Constructor
+ ///
+ /// Sets an explicit value and marks it as "specified".
+ ///
+ /// @tparam A Type of the value to be assigned. Typically this is @c T, but
+ /// may also be a type that can be cast to @c T.
+ /// @param value value to be assigned.
+ /// @param unspecified initial state. Default is "unspecified".
+ template<typename A>
+ Optional(A value, const bool unspecified = false)
+ : default_(value), unspecified_(unspecified) {
+ }
+
+ /// @brief Retrieves the encapsulated value.
+ ///
+ /// @return the encapsulated value
+ T get() const {
+ return (default_);
+ }
+
+ /// @brief Retrieves the encapsulated value if specified, or the given value
+ /// otherwise.
+ ///
+ /// @param or_value the value it defaults to, if unspecified
+ ///
+ /// @return the encapsulated value or the default value
+ T valueOr(T const& or_value) const {
+ if (unspecified_) {
+ return or_value;
+ }
+ return default_;
+ }
+
+ /// @brief Modifies the flag that indicates whether the value is specified
+ /// or unspecified.
+ ///
+ /// @param unspecified new value of the flag. If it is @c true, the
+ /// value is marked as unspecified, otherwise it is marked as specified.
+ void unspecified(bool unspecified) {
+ unspecified_ = unspecified;
+ }
+
+ /// @brief Checks if the value has been specified or unspecified.
+ ///
+ /// @return true if the value hasn't been specified, false otherwise.
+ bool unspecified() const {
+ return (unspecified_);
+ }
+
+ /// @brief Checks if the encapsulated value is empty.
+ ///
+ /// This method can be overloaded in the template specializations that
+ /// are dedicated to strings, vectors etc.
+ ///
+ /// @throw isc::InvalidOperation.
+ bool empty() const {
+ isc_throw(isc::InvalidOperation, "call to empty() not supported");
+ }
+
+protected:
+ T default_; ///< Encapsulated value.
+ bool unspecified_; ///< Flag which indicates if the value is specified.
+};
+
+/// @brief Specialization of the default @c Optional constructor for
+/// strings.
+///
+/// It calls default string object constructor.
+template<>
+inline Optional<std::string>::Optional()
+ : default_(), unspecified_(true) {
+}
+
+/// @brief Specialization of the @c Optional::empty method for strings.
+///
+/// @return true if the value is empty, false otherwise.
+template<>
+inline bool Optional<std::string>::empty() const {
+ return (default_.empty());
+}
+
+/// @brief Inserts an optional value to a stream.
+///
+/// This function overloads the global operator<< to behave as in
+/// @c ostream::operator<< but applied to @c Optional objects.
+///
+/// @param os A @c std::ostream object to which the value is inserted.
+/// @param optional_value An @c Optional object to be inserted into
+/// a stream.
+/// @tparam Type of the value encapsulated by the @c Optional object.
+///
+/// @return A reference to the stream after insertion.
+template<typename T>
+std::ostream&
+operator<<(std::ostream& os, const Optional<T>& optional_value) {
+ os << optional_value.get();
+ return (os);
+}
+
+
+} // end of namespace isc::util
+} // end of namespace isc
+
+#endif // OPTIONAL_VALUE_H
diff --git a/src/lib/util/pid_file.cc b/src/lib/util/pid_file.cc
new file mode 100644
index 0000000..ef519b3
--- /dev/null
+++ b/src/lib/util/pid_file.cc
@@ -0,0 +1,93 @@
+// Copyright (C) 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 <util/pid_file.h>
+#include <cstdio>
+#include <signal.h>
+#include <unistd.h>
+#include <cerrno>
+
+namespace isc {
+namespace util {
+
+PIDFile::PIDFile(const std::string& filename)
+ : filename_(filename) {
+}
+
+PIDFile::~PIDFile() {
+}
+
+int
+PIDFile::check() const {
+ std::ifstream fs(filename_.c_str());
+ int pid;
+ bool good;
+
+ // If we weren't able to open the file treat
+ // it as if the process wasn't running
+ if (!fs.is_open()) {
+ return (false);
+ }
+
+ // Try to get the pid, get the status and get rid of the file
+ fs >> pid;
+ good = fs.good();
+ fs.close();
+
+ // If we weren't able to read a pid send back an exception
+ if (!good) {
+ isc_throw(PIDCantReadPID, "Unable to read PID from file '"
+ << filename_ << "'");
+ }
+
+ // If the process is still running return its pid.
+ if (kill(pid, 0) == 0) {
+ return (pid);
+ }
+
+ // No process
+ return (0);
+}
+
+void
+PIDFile::write() const {
+ write(getpid());
+}
+
+void
+PIDFile::write(int pid) const {
+ std::ofstream fs(filename_.c_str(), std::ofstream::trunc);
+
+ if (!fs.is_open()) {
+ isc_throw(PIDFileError, "Unable to open PID file '"
+ << filename_ << "' for write");
+ }
+
+ // File is open, write the pid.
+ fs << pid << std::endl;
+
+ bool good = fs.good();
+ fs.close();
+
+ if (!good) {
+ isc_throw(PIDFileError, "Unable to write to PID file '"
+ << filename_ << "'");
+ }
+}
+
+void
+PIDFile::deleteFile() const {
+ if ((remove(filename_.c_str()) != 0) &&
+ (errno != ENOENT)) {
+ isc_throw(PIDFileError, "Unable to delete PID file '"
+ << filename_ << "'");
+ }
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/pid_file.h b/src/lib/util/pid_file.h
new file mode 100644
index 0000000..a30640d
--- /dev/null
+++ b/src/lib/util/pid_file.h
@@ -0,0 +1,97 @@
+// Copyright (C) 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/.
+
+#ifndef PID_FILE_H
+#define PID_FILE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <fstream>
+#include <ostream>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during PID file processing.
+class PIDFileError : public Exception {
+public:
+ PIDFileError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when an error occurs trying to read a PID
+/// from an opened file.
+class PIDCantReadPID : public Exception {
+public:
+ PIDCantReadPID(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Class to help with processing PID files
+///
+/// This is a utility class to manipulate PID file. It provides
+/// functions for writing and deleting a file containing a PID as
+/// well as for extracting a PID from a file and checking if the
+/// process is still running.
+class PIDFile {
+public:
+ /// @brief Constructor
+ ///
+ /// @param filename PID filename.
+ PIDFile(const std::string& filename);
+
+ /// @brief Destructor
+ ~PIDFile();
+
+ /// @brief Read the PID in from the file and check it.
+ ///
+ /// Read the PID in from the file then use it to see if
+ /// a process with that PID exists. If the file doesn't
+ /// exist treat it as the process not being there.
+ /// If the file exists but can't be read or it doesn't have
+ /// the proper format treat it as the process existing.
+ ///
+ /// @return returns the PID if it is in, 0 otherwise.
+ ///
+ /// @throw throws PIDCantReadPID if it was able to open the file
+ /// but was unable to read the PID from it.
+ int check() const;
+
+ /// @brief Write the PID to the file.
+ ///
+ /// @param pid the pid to write to the file.
+ ///
+ /// @throw throws PIDFileError if it can't open or write to the PID file.
+ void write(int) const;
+
+ /// @brief Get PID of the current process and write it to the file.
+ ///
+ /// @throw throws PIDFileError if it can't open or write to the PID file.
+ void write() const;
+
+ /// @brief Delete the PID file.
+ ///
+ /// @throw throws PIDFileError if it can't delete the PID file
+ void deleteFile() const;
+
+ /// @brief Returns the path to the PID file.
+ std::string getFilename() const {
+ return (filename_);
+ }
+
+private:
+ /// @brief PID filename
+ std::string filename_;
+};
+
+/// @brief Defines a shared pointer to a PIDFile
+typedef boost::shared_ptr<PIDFile> PIDFilePtr;
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // PID_FILE_H
diff --git a/src/lib/util/pointer_util.h b/src/lib/util/pointer_util.h
new file mode 100644
index 0000000..a775584
--- /dev/null
+++ b/src/lib/util/pointer_util.h
@@ -0,0 +1,49 @@
+// Copyright (C) 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/.
+
+#ifndef POINTER_UTIL_H
+#define POINTER_UTIL_H
+
+namespace isc {
+namespace util {
+
+/// @brief This function checks if two pointers are non-null and values
+/// are equal.
+///
+/// This function performs the typical comparison of values encapsulated by
+/// the smart pointers, with checking if the pointers are non-null.
+///
+/// @param ptr1 First pointer.
+/// @param ptr2 Second pointer.
+///
+/// @tparam T Pointer type, e.g. boost::shared_ptr, or boost::scoped_ptr.
+///
+/// @return true only if both pointers are non-null and the values which they
+/// point to are equal.
+template<typename T>
+inline bool equalValues(const T& ptr1, const T& ptr2) {
+ return (ptr1 && ptr2 && (*ptr1 == *ptr2));
+}
+
+/// @brief This function checks if two pointers are both null or both
+/// are non-null and they point to equal values.
+///
+/// @param ptr1 First pointer.
+/// @param ptr2 Second pointer.
+///
+/// @tparam T Pointer type, e.g. boost::shared_ptr, or boost::scoped_ptr.
+///
+/// @return true if both pointers are null or if they are both non-null
+/// and the values pointed to are equal.
+template<typename T>
+inline bool nullOrEqualValues(const T& ptr1, const T& ptr2) {
+ return ((!ptr1 && !ptr2) || equalValues(ptr1, ptr2));
+}
+
+} // end of namespace isc::util
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/util/python/Makefile.am b/src/lib/util/python/Makefile.am
new file mode 100644
index 0000000..460bf1a
--- /dev/null
+++ b/src/lib/util/python/Makefile.am
@@ -0,0 +1,3 @@
+noinst_SCRIPTS = const2hdr.py gen_wiredata.py
+EXTRA_DIST = const2hdr.py
+DISTCLEANFILES = gen_wiredata.py
diff --git a/src/lib/util/python/Makefile.in b/src/lib/util/python/Makefile.in
new file mode 100644
index 0000000..47a89e6
--- /dev/null
+++ b/src/lib/util/python/Makefile.in
@@ -0,0 +1,555 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/util/python
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = gen_wiredata.py
+CONFIG_CLEAN_VPATH_FILES =
+SCRIPTS = $(noinst_SCRIPTS)
+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 $(srcdir)/gen_wiredata.py.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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+noinst_SCRIPTS = const2hdr.py gen_wiredata.py
+EXTRA_DIST = const2hdr.py
+DISTCLEANFILES = gen_wiredata.py
+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/util/python/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/python/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):
+gen_wiredata.py: $(top_builddir)/config.status $(srcdir)/gen_wiredata.py.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+
+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 $(SCRIPTS)
+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:
+
+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)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+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
+
+
+# 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/util/python/const2hdr.py b/src/lib/util/python/const2hdr.py
new file mode 100644
index 0000000..e89a735
--- /dev/null
+++ b/src/lib/util/python/const2hdr.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2013-2017 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/.
+
+'''
+The script takes a C++ file with constant definitions and creates a
+header file for the constants. It, however, does not understand C++
+syntax, it only does some heuristics to guess what looks like
+a constant and strips the values.
+
+The purpose is just to save some work with keeping both the source and
+header. The source syntax must be limited already, because it's used to
+generate the python module (by the
+lib/python/isc/util/pythonize_constants.py script).
+'''
+
+import sys
+import re
+
+if len(sys.argv) != 3:
+ sys.stderr.write("Usage: python3 ./const2hdr.py input.cc output.h\n")
+ sys.exit(1)
+
+[filename_in, filename_out] = sys.argv[1:3]
+
+preproc = re.compile('^#')
+constant = re.compile('^([a-zA-Z].*?[a-zA-Z_0-9]+)\\s*=.*;')
+
+with open(filename_in) as file_in, open(filename_out, "w") as file_out:
+ file_out.write("// This file is generated from " + filename_in + "\n" +
+ "// by the const2hdr.py script.\n" +
+ "// Do not edit, all changes will be lost.\n\n")
+ for line in file_in:
+ if preproc.match(line):
+ # There's only one preprocessor line in the .cc file. We abuse
+ # that to position the top part of the header.
+ file_out.write("#ifndef BIND10_COMMON_DEFS_H\n" +
+ "#define BIND10_COMMON_DEFS_H\n" +
+ "\n" +
+ "// \\file " + filename_out + "\n" +
+'''// \\brief Common shared constants\n
+// This file contains common definitions of constants used across the sources.
+// It includes, but is not limited to the definitions of messages sent from
+// one process to another. Since the names should be self-explanatory and
+// the variables here are used mostly to synchronize the same values across
+// multiple programs, separate documentation for each variable is not provided.
+''')
+ continue
+ # Extract the constant. Remove the values and add "extern"
+ line = constant.sub('extern \\1;', line)
+
+ file_out.write(line)
+
+ file_out.write("#endif\n")
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
new file mode 100644
index 0000000..f1b51f3
--- /dev/null
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -0,0 +1,1448 @@
+#!@PYTHON@
+
+# Copyright (C) 2010-2021 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/.
+
+"""
+Generator of various types of DNS data in the hex format.
+
+This script reads a human readable specification file (called "spec
+file" hereafter) that defines some type of DNS data (an RDATA, an RR,
+or a complete message) and dumps the defined data to a separate file
+as a "wire format" sequence parsable by the
+UnitTestUtil::readWireData() function (currently defined as part of
+libdns++ tests). Many DNS related tests involve wire format test
+data, so it will be convenient if we can define the data in a more
+intuitive way than writing the entire hex sequence by hand.
+
+Here is a simple example. Consider the following spec file:
+
+ [custom]
+ sections: a
+ [a]
+ as_rr: True
+
+When the script reads this file, it detects the file specifies a single
+component (called "section" here) that consists of a single A RDATA,
+which must be dumped as an RR (not only the part of RDATA). It then
+dumps the following content:
+
+ # A RR (QNAME=example.com Class=IN(1) TTL=86400 RDLEN=4)
+ 076578616d706c6503636f6d00 0001 0001 00015180 0004
+ # Address=192.0.2.1
+ c0000201
+
+As can be seen, the script automatically completes all variable
+parameters of RRs: owner name, class, TTL, RDATA length and data. For
+testing purposes many of these will be the same common one (like
+"example.com" or 192.0.2.1), so it would be convenient if we only have
+to specify non default parameters. To change the RDATA (i.e., the
+IPv4 address), we should add the following line at the end of the spec
+file:
+
+ address: 192.0.2.2
+
+Then the last two lines of the output file will be as follows:
+
+ # Address=192.0.2.2
+ c0000202
+
+In some cases we would rather specify malformed data for tests. This
+script has the ability to specify broken parameters for many types of
+data. For example, we can generate data that would look like an A RR
+but the RDLEN is 3 by adding the following line to the spec file:
+
+ rdlen: 3
+
+Then the first two lines of the output file will be as follows:
+
+ # A RR (QNAME=example.com Class=IN(1) TTL=86400 RDLEN=3)
+ 076578616d706c6503636f6d00 0001 0001 00015180 0003
+
+** USAGE **
+
+ gen_wiredata.py [-o output_file] spec_file
+
+If the -o option is missing, and if the spec_file has a suffix (such as
+in the form of "data.spec"), the output file name will be the prefix
+part of it (as in "data"); if -o is missing and the spec_file does not
+have a suffix, the script will fail.
+
+** SPEC FILE SYNTAX **
+
+A spec file accepted in this script should be in the form of a
+configuration file that is parsable by the Python's standard
+configparser module. In short, it consists of sections; each section
+is identified in the form of [section_name] followed by "name: value"
+entries. Lines beginning with # or ; will be treated as comments.
+Refer to the configparser module documentation for further details of
+the general syntax.
+
+This script has two major modes: the custom mode and the DNS query
+mode. The former generates an arbitrary combination of DNS message
+header, question section, RDATAs or RRs. It is mainly intended to
+generate a test data for a single type of RDATA or RR, or for
+complicated complete DNS messages. The DNS query mode is actually a
+special case of the custom mode, which is a shortcut to generate a
+simple DNS query message (with or without EDNS).
+
+* Custom mode syntax *
+
+By default this script assumes the DNS query mode. To specify the
+custom mode, there must be a special "custom" section in the spec
+file, which should contain 'sections' entry. This value of this
+entryis colon-separated string fields, each of which is either
+"header", "question", "edns", "name", or a string specifying an RR
+type. For RR types the string is lower-cased string mnemonic that
+identifies the type: 'a' for type A, 'ns' for type NS, and so on
+(note: in the current implementation it's case sensitive, and must be
+lower cased).
+
+Each of these fields is interpreted as a section name of the spec
+(configuration), and in that section parameters specific to the
+semantics of the field can be configured.
+
+A "header" section specifies the content of a DNS message header.
+See the documentation of the DNSHeader class of this module for
+configurable parameters.
+
+A "question" section specifies the content of a single question that
+is normally to be placed in the Question section of a DNS message.
+See the documentation of the DNSQuestion class of this module for
+configurable parameters.
+
+An "edns" section specifies the content of an EDNS OPT RR. See the
+documentation of the EDNS class of this module for configurable
+parameters.
+
+A "name" section specifies a domain name with or without compression.
+This is specifically intended to be used for testing name related
+functionalities and would rarely be used with other sections. See the
+documentation of the Name class of this module for configurable
+parameters.
+
+In a specific section for an RR or RDATA, possible entries depend on
+the type. But there are some common configurable entries. See the
+description of the RR class. The most important one would be "as_rr".
+It controls whether the entry should be treated as an RR (with name,
+type, class and TTL) or only as an RDATA. By default as_rr is
+"False", so if an entry is to be interpreted as an RR, an as_rr entry
+must be explicitly specified with a value of "True".
+
+Another common entry is "rdlen". It specifies the RDLEN field value
+of the RR (note: this is included when the entry is interpreted as
+RDATA, too). By default this value is automatically determined by the
+RR type and (it has a variable length) from other fields of RDATA, but
+as shown in the above example, it can be explicitly set, possibly to a
+bogus value for testing against invalid data.
+
+For type specific entries (and their defaults when provided), see the
+documentation of the corresponding Python class defined in this
+module. In general, there should be a class named the same mnemonic
+of the corresponding RR type for each supported type, and they are a
+subclass of the RR class. For example, the "NS" class is defined for
+RR type NS.
+
+Look again at the A RR example shown at the beginning of this
+description. There's a "custom" section, which consists of a
+"sections" entry whose value is a single "a", which means the data to
+be generated is an A RR or RDATA. There's a corresponding "a"
+section, which only specifies that it should be interpreted as an RR
+(all field values of the RR are derived from the default).
+
+If you want to generate a data sequence for two ore more RRs or
+RDATAs, you can specify them in the form of colon-separated fields for
+the "sections" entry. For example, to generate a sequence of A and NS
+RRs in that order, the "custom" section would be something like this:
+
+ [custom]
+ sections: a:ns
+
+and there must be an "ns" section in addition to "a".
+
+If a sequence of two or more RRs/RDATAs of the same RR type should be
+generated, these should be uniquely indexed with the "/" separator.
+For example, to generate two A RRs, the "custom" section would be as
+follows:
+
+ [custom]
+ sections: a/1:a/2
+
+and there must be "a/1" and "a/2" sections.
+
+Another practical example that would be used for many tests is to
+generate data for a complete DNS response message. The spec file of
+such an example configuration would look like as follows:
+
+ [custom]
+ sections: header:question:a
+ [header]
+ qr: 1
+ ancount: 1
+ [question]
+ [a]
+ as_rr: True
+
+With this configuration, this script will generate test data for a DNS
+response to a query for example.com/IN/A containing one corresponding
+A RR in the answer section.
+
+* DNS query mode syntax *
+
+If the spec file does not contain a "custom" section (that has a
+"sections" entry), this script assumes the DNS query mode. This mode
+is actually a special case of custom mode; it implicitly assumes the
+"sections" entry whose value is "header:question:edns".
+
+In this mode it is expected that the spec file also contains at least
+a "header" and "question" sections, and optionally an "edns" section.
+But the script does not warn or fail even if the expected sections are
+missing.
+
+* Entry value types *
+
+As described above, a section of the spec file accepts entries
+specific to the semantics of the section. They generally correspond
+to DNS message or RR fields.
+
+Many of them are expected to be integral values, for which either decimal or
+hexadecimal representation is accepted, for example:
+
+ rr_ttl: 3600
+ tag: 0x1234
+
+Some others are expected to be string. A string value does not have
+to be quoted:
+
+ address: 192.0.2.2
+
+but can also be quoted with single quotes:
+
+ address: '192.0.2.2'
+
+Note 1: a string that can be interpreted as an integer must be quoted.
+For example, if you want to set a "string" entry to "3600", it should
+be:
+
+ string: '3600'
+
+instead of
+
+ string: 3600
+
+Note 2: a string enclosed with double quotes is not accepted:
+
+ # This doesn't work:
+ address: "192.0.2.2"
+
+In general, string values are converted to hexadecimal sequences
+according to the semantics of the entry. For instance, a textual IPv4
+address in the above example will be converted to a hexadecimal
+sequence corresponding to a 4-byte integer. So, in many cases, the
+acceptable syntax for a particular string entry value should be
+obvious from the context. There are still some exceptional cases
+especially for complicated RR field values, for which the
+corresponding class documentation should be referenced.
+
+One special string syntax that would be worth noting is domain names,
+which would naturally be used in many kinds of entries. The simplest
+form of acceptable syntax is a textual representation of domain names
+such as "example.com" (note: names are always assumed to be
+"absolute", so the trailing dot can be omitted). But a domain name in
+the wire format can also contain a compression pointer. This script
+provides a simple support for name compression with a special notation
+of "ptr=nn" where nn is the numeric pointer value (decimal). For example,
+if the NSDNAME field of an NS RDATA is specified as follows:
+
+ nsname: ns.ptr=12
+
+this script will generate the following output:
+
+ # NS name=ns.ptr=12
+ 026e73c00c
+
+** EXTEND THE SCRIPT **
+
+This script is expected to be extended as we add more support for
+various types of RR. It is encouraged to add support for a new type
+of RR to this script as we see the need for testing that type. Here
+is a simple instruction of how to do that.
+
+Assume you are adding support for "FOO" RR. Also assume that the FOO
+RDATA contains a single field named "value".
+
+What you are expected to do is as follows:
+
+- Define a new class named "FOO" inherited from the RR class. Also
+ define a class variable named "value" for the FOO RDATA field (the
+ variable name can be different from the field name, but it's
+ convenient if it can be easily identifiable.) with an appropriate
+ default value (if possible):
+
+ class FOO(RR):
+ value = 10
+
+ The name of the variable will be (automatically) used as the
+ corresponding entry name in the spec file. So, a spec file that
+ sets this field to 20 would look like this:
+
+ [foo]
+ value: 20
+
+- Define the "dump()" method for class FOO. It must call
+ self.dump_header() (which is derived from class RR) at the
+ beginning. It then prints the RDATA field values in an appropriate
+ way. Assuming the value is a 16-bit integer field, a complete
+ dump() method would look like this:
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 2
+ self.dump_header(f, self.rdlen)
+ f.write('# Value=%d\\n' % (self.value))
+ f.write('%04x\\n' % (self.value))
+
+ The first f.write() call is not mandatory, but is encouraged to
+ be provided so that the generated files will be more human readable.
+ Depending on the complexity of the RDATA fields, the dump()
+ implementation would be more complicated. In particular, if the
+ RDATA length is variable and the RDLEN field value is not specified
+ in the spec file, the dump() method is normally expected to
+ calculate the correct length and pass it to dump_header(). See the
+ implementation of various derived classes of class RR for actual
+ examples.
+"""
+
+import configparser, re, time, socket, sys, base64
+from datetime import datetime
+from optparse import OptionParser
+
+re_hex = re.compile(r'^0x[0-9a-fA-F]+')
+re_decimal = re.compile(r'^\d+$')
+re_string = re.compile(r"\'(.*)\'$")
+
+dnssec_timefmt = '%Y%m%d%H%M%S'
+
+dict_qr = { 'query' : 0, 'response' : 1 }
+dict_opcode = { 'query' : 0, 'iquery' : 1, 'status' : 2, 'notify' : 4,
+ 'update' : 5 }
+rdict_opcode = dict([(dict_opcode[k], k.upper()) for k in dict_opcode.keys()])
+dict_rcode = { 'noerror' : 0, 'formerr' : 1, 'servfail' : 2, 'nxdomain' : 3,
+ 'notimp' : 4, 'refused' : 5, 'yxdomain' : 6, 'yxrrset' : 7,
+ 'nxrrset' : 8, 'notauth' : 9, 'notzone' : 10 }
+rdict_rcode = dict([(dict_rcode[k], k.upper()) for k in dict_rcode.keys()])
+dict_rrtype = { 'none' : 0, '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_tr' : 23, 'sig' : 24, 'key' : 25,
+ 'px' : 26, 'gpos' : 27, 'aaaa' : 28, 'loc' : 29, 'nxt' : 30,
+ 'srv' : 33, 'naptr' : 35, 'kx' : 36, 'cert' : 37, 'a6' : 38,
+ 'dname' : 39, 'opt' : 41, 'apl' : 42, 'ds' : 43, 'sshfp' : 44,
+ 'ipseckey' : 45, 'rrsig' : 46, 'nsec' : 47, 'dnskey' : 48,
+ 'dhcid' : 49, 'nsec3' : 50, 'nsec3param' : 51, 'tlsa' : 52, 'hip' : 55,
+ 'spf' : 99, 'unspec' : 103, 'tkey' : 249, 'tsig' : 250,
+ 'dlv' : 32769, 'ixfr' : 251, 'axfr' : 252, 'mailb' : 253,
+ 'maila' : 254, 'any' : 255, 'caa' : 257 }
+rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
+dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
+rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
+ dict_rrclass.keys()])
+dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4,
+ 'rsasha1' : 5 }
+dict_nsec3_algorithm = { 'reserved' : 0, 'sha1' : 1 }
+rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in \
+ dict_algorithm.keys()])
+rdict_nsec3_algorithm = dict([(dict_nsec3_algorithm[k], k.upper()) for k in \
+ dict_nsec3_algorithm.keys()])
+
+header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
+ 'rcode' : dict_rcode }
+question_xtables = { 'rrtype' : dict_rrtype, 'rrclass' : dict_rrclass }
+
+def parse_value(value, xtable = {}):
+ if re.search(re_hex, value):
+ return int(value, 16)
+ if re.search(re_decimal, value):
+ return int(value)
+ m = re.match(re_string, value)
+ if m:
+ return m.group(1)
+ lovalue = value.lower()
+ if lovalue in xtable:
+ return xtable[lovalue]
+ return value
+
+def code_totext(code, dict):
+ if code in dict.keys():
+ return dict[code] + '(' + str(code) + ')'
+ return str(code)
+
+def encode_name(name, absolute=True):
+ # make sure the name is dot-terminated. duplicate dots will be ignored
+ # below.
+ name += '.'
+ labels = name.split('.')
+ wire = ''
+ for l in labels:
+ if len(l) > 4 and l[0:4] == 'ptr=':
+ # special meta-syntax for compression pointer
+ wire += '%04x' % (0xc000 | int(l[4:]))
+ break
+ if absolute or len(l) > 0:
+ wire += '%02x' % len(l)
+ wire += ''.join(['%02x' % ord(ch) for ch in l])
+ if len(l) == 0:
+ break
+ return wire
+
+def encode_string(name, len=None):
+ if type(name) is int and len is not None:
+ return '%0.*x' % (len * 2, name)
+ return ''.join(['%02x' % ord(ch) for ch in name])
+
+def encode_bytes(name, len=None):
+ if type(name) is int and len is not None:
+ return '%0.*x' % (len * 2, name)
+ return ''.join(['%02x' % ch for ch in name])
+
+def count_namelabels(name):
+ if name == '.': # special case
+ return 0
+ m = re.match('^(.*)\.$', name)
+ if m:
+ name = m.group(1)
+ return len(name.split('.'))
+
+def get_config(config, section, configobj, xtables = {}):
+ try:
+ for field in config.options(section):
+ value = config.get(section, field)
+ if field in xtables.keys():
+ xtable = xtables[field]
+ else:
+ xtable = {}
+ configobj.__dict__[field] = parse_value(value, xtable)
+ except configparser.NoSectionError:
+ return False
+ return True
+
+def print_header(f, input_file):
+ f.write('''###
+### This data file was auto-generated from ''' + input_file + '''
+###
+''')
+
+class Name:
+ '''Implements rendering a single domain name in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - name (string): A textual representation of the name, such as
+ 'example.com'.
+ - pointer (int): If specified, compression pointer will be
+ prepended to the generated data with the offset being the value
+ of this parameter.
+ '''
+
+ name = 'example.com'
+ pointer = None # no compression by default
+ def dump(self, f):
+ name = self.name
+ if self.pointer is not None:
+ if len(name) > 0 and name[-1] != '.':
+ name += '.'
+ name += 'ptr=%d' % self.pointer
+ name_wire = encode_name(name)
+ f.write('\n# DNS Name: %s' % self.name)
+ if self.pointer is not None:
+ f.write(' + compression pointer: %d' % self.pointer)
+ f.write('\n')
+ f.write('%s' % name_wire)
+ f.write('\n')
+
+class DNSHeader:
+ '''Implements rendering a DNS Header section in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - id (16-bit int):
+ - qr, aa, tc, rd, ra, ad, cd (0 or 1): Standard header bits as
+ defined in RFC1035 and RFC4035. If set to 1, the corresponding
+ bit will be set; if set to 0, it will be cleared.
+ - mbz (0-3): The reserved field of the 3rd and 4th octets of the
+ header.
+ - rcode (4-bit int or string): The RCODE field. If specified as a
+ string, it must be the commonly used textual mnemonic of the RCODEs
+ (NOERROR, FORMERR, etc, case insensitive).
+ - opcode (4-bit int or string): The OPCODE field. If specified as
+ a string, it must be the commonly used textual mnemonic of the
+ OPCODEs (QUERY, NOTIFY, etc, case insensitive).
+ - qdcount, ancount, nscount, arcount (16-bit int): The QD/AN/NS/AR
+ COUNT fields, respectively.
+ '''
+
+ id = 0x1035
+ (qr, aa, tc, rd, ra, ad, cd) = 0, 0, 0, 0, 0, 0, 0
+ mbz = 0
+ rcode = 0 # noerror
+ opcode = 0 # query
+ (qdcount, ancount, nscount, arcount) = 1, 0, 0, 0
+
+ def dump(self, f):
+ f.write('\n# Header Section\n')
+ f.write('# ID=' + str(self.id))
+ f.write(' QR=' + ('Response' if self.qr else 'Query'))
+ f.write(' Opcode=' + code_totext(self.opcode, rdict_opcode))
+ f.write(' Rcode=' + code_totext(self.rcode, rdict_rcode))
+ f.write('%s' % (' AA' if self.aa else ''))
+ f.write('%s' % (' TC' if self.tc else ''))
+ f.write('%s' % (' RD' if self.rd else ''))
+ f.write('%s' % (' AD' if self.ad else ''))
+ f.write('%s' % (' CD' if self.cd else ''))
+ f.write('\n')
+ f.write('%04x ' % self.id)
+ flag_and_code = 0
+ flag_and_code |= (self.qr << 15 | self.opcode << 14 | self.aa << 10 |
+ self.tc << 9 | self.rd << 8 | self.ra << 7 |
+ self.mbz << 6 | self.ad << 5 | self.cd << 4 |
+ self.rcode)
+ f.write('%04x\n' % flag_and_code)
+ f.write('# QDCNT=%d, ANCNT=%d, NSCNT=%d, ARCNT=%d\n' %
+ (self.qdcount, self.ancount, self.nscount, self.arcount))
+ f.write('%04x %04x %04x %04x\n' % (self.qdcount, self.ancount,
+ self.nscount, self.arcount))
+
+class DNSQuestion:
+ '''Implements rendering a DNS question in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - name (string): The QNAME. The string must be interpreted as a
+ valid domain name.
+ - rrtype (int or string): The question type. If specified
+ as an integer, it must be the 16-bit RR type value of the
+ covered type. If specified as a string, it must be the textual
+ mnemonic of the type.
+ - rrclass (int or string): The question class. If specified as an
+ integer, it must be the 16-bit RR class value of the covered
+ type. If specified as a string, it must be the textual mnemonic
+ of the class.
+ '''
+ name = 'example.com.'
+ rrtype = parse_value('A', dict_rrtype)
+ rrclass = parse_value('IN', dict_rrclass)
+
+ def dump(self, f):
+ f.write('\n# Question Section\n')
+ f.write('# QNAME=%s QTYPE=%s QCLASS=%s\n' %
+ (self.name,
+ code_totext(self.rrtype, rdict_rrtype),
+ code_totext(self.rrclass, rdict_rrclass)))
+ f.write(encode_name(self.name))
+ f.write(' %04x %04x\n' % (self.rrtype, self.rrclass))
+
+class EDNS:
+ '''Implements rendering EDNS OPT RR in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - name (string): The owner name of the OPT RR. The string must be
+ interpreted as a valid domain name.
+ - udpsize (16-bit int): The UDP payload size (set as the RR class)
+ - extrcode (8-bit int): The upper 8 bits of the extended RCODE.
+ - version (8-bit int): The EDNS version.
+ - do (int): The DNSSEC DO bit. The bit will be set if this value
+ is 1; otherwise the bit will be unset.
+ - mbz (15-bit int): The rest of the flags field.
+ - rdlen (16-bit int): The RDLEN field. Note: right now specifying
+ a non 0 value (except for making bogus data) doesn't make sense
+ because there is no way to configure RDATA.
+ '''
+ name = '.'
+ udpsize = 4096
+ extrcode = 0
+ version = 0
+ do = 0
+ mbz = 0
+ rdlen = 0
+ def dump(self, f):
+ f.write('\n# EDNS OPT RR\n')
+ f.write('# NAME=%s TYPE=%s UDPSize=%d ExtRcode=%s Version=%s DO=%d\n' %
+ (self.name, code_totext(dict_rrtype['opt'], rdict_rrtype),
+ self.udpsize, self.extrcode, self.version,
+ 1 if self.do else 0))
+
+ code_vers = (self.extrcode << 8) | (self.version & 0x00ff)
+ extflags = (self.do << 15) | (self.mbz & ~0x8000)
+ f.write('%s %04x %04x %04x %04x\n' %
+ (encode_name(self.name), dict_rrtype['opt'], self.udpsize,
+ code_vers, extflags))
+ f.write('# RDLEN=%d\n' % self.rdlen)
+ f.write('%04x\n' % self.rdlen)
+
+class RR:
+ '''This is a base class for various types of RR test data.
+ For each RR type (A, AAAA, NS, etc), we define a derived class of RR
+ to dump type specific RDATA parameters. This class defines parameters
+ common to all types of RDATA, namely the owner name, RR class and TTL.
+ The dump() method of derived classes are expected to call dump_header(),
+ whose default implementation is provided in this class. This method
+ decides whether to dump the test data as an RR (with name, type, class)
+ or only as RDATA (with its length), and dumps the corresponding data
+ via the specified file object.
+
+ By convention we assume derived classes are named after the common
+ standard mnemonic of the corresponding RR types. For example, the
+ derived class for the RR type SOA should be named "SOA".
+
+ Configurable parameters are as follows:
+ - as_rr (bool): Whether or not the data is to be dumped as an RR.
+ False by default.
+ - rr_name (string): The owner name of the RR. The string must be
+ interpreted as a valid domain name (compression pointer can be
+ contained). Default is 'example.com.'
+ - rr_class (string): The RR class of the data. Only meaningful
+ when the data is dumped as an RR. Default is 'IN'.
+ - rr_ttl (int): The TTL value of the RR. Only meaningful when
+ the data is dumped as an RR. Default is 86400 (1 day).
+ - rdlen (int): 16-bit RDATA length. It can be None (i.e. omitted
+ in the spec file), in which case the actual length of the
+ generated RDATA is automatically determined and used; if
+ negative, the RDLEN field will be omitted from the output data.
+ (Note that omitting RDLEN with as_rr being True is mostly
+ meaningless, although the script doesn't complain about it).
+ Default is None.
+ '''
+
+ def __init__(self):
+ self.as_rr = False
+ # only when as_rr is True, same for class/TTL:
+ self.rr_name = 'example.com'
+ self.rr_class = 'IN'
+ self.rr_ttl = 86400
+ self.rdlen = None
+
+ def dump_header(self, f, rdlen):
+ type_txt = self.__class__.__name__
+ type_code = parse_value(type_txt, dict_rrtype)
+ rdlen_spec = ''
+ rdlen_data = ''
+ if rdlen >= 0:
+ rdlen_spec = ', RDLEN=%d' % rdlen
+ rdlen_data = '%04x' % rdlen
+ if self.as_rr:
+ rrclass = parse_value(self.rr_class, dict_rrclass)
+ f.write('\n# %s RR (QNAME=%s Class=%s TTL=%d%s)\n' %
+ (type_txt, self.rr_name,
+ code_totext(rrclass, rdict_rrclass), self.rr_ttl,
+ rdlen_spec))
+ f.write('%s %04x %04x %08x %s\n' %
+ (encode_name(self.rr_name), type_code, rrclass,
+ self.rr_ttl, rdlen_data))
+ else:
+ f.write('\n# %s RDATA%s\n' % (type_txt, rdlen_spec))
+ f.write('%s\n' % rdlen_data)
+
+class A(RR):
+ '''Implements rendering A RDATA (of class IN) in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - address (string): The address field. This must be a valid textual
+ IPv4 address.
+ '''
+ RDLEN_DEFAULT = 4 # fixed by default
+ address = '192.0.2.1'
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = self.RDLEN_DEFAULT
+ self.dump_header(f, self.rdlen)
+ f.write('# Address=%s\n' % (self.address))
+ bin_address = socket.inet_aton(self.address)
+ f.write('%02x%02x%02x%02x\n' % (bin_address[0], bin_address[1],
+ bin_address[2], bin_address[3]))
+
+class AAAA(RR):
+ '''Implements rendering AAAA RDATA (of class IN) in the test data
+ format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - address (string): The address field. This must be a valid textual
+ IPv6 address.
+ '''
+ RDLEN_DEFAULT = 16 # fixed by default
+ address = '2001:db8::1'
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = self.RDLEN_DEFAULT
+ self.dump_header(f, self.rdlen)
+ f.write('# Address=%s\n' % (self.address))
+ bin_address = socket.inet_pton(socket.AF_INET6, self.address)
+ [f.write('%02x' % x) for x in bin_address]
+ f.write('\n')
+
+class NS(RR):
+ '''Implements rendering NS RDATA in the test data format.
+
+ Configurable parameter is as follows (see the description of the
+ same name of attribute for the default value):
+ - nsname (string): The NSDNAME field. The string must be
+ interpreted as a valid domain name.
+ '''
+
+ nsname = 'ns.example.com'
+
+ def dump(self, f):
+ nsname_wire = encode_name(self.nsname)
+ if self.rdlen is None:
+ self.rdlen = len(nsname_wire) / 2
+ self.dump_header(f, self.rdlen)
+ f.write('# NS name=%s\n' % (self.nsname))
+ f.write('%s\n' % nsname_wire)
+
+class SOA(RR):
+ '''Implements rendering SOA RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - mname/rname (string): The MNAME/RNAME fields, respectively. The
+ string must be interpreted as a valid domain name.
+ - serial (32-bit int): The SERIAL field
+ - refresh (32-bit int): The REFRESH field
+ - retry (32-bit int): The RETRY field
+ - expire (32-bit int): The EXPIRE field
+ - minimum (32-bit int): The MINIMUM field
+ '''
+
+ mname = 'ns.example.com'
+ rname = 'root.example.com'
+ serial = 2010012601
+ refresh = 3600
+ retry = 300
+ expire = 3600000
+ minimum = 1200
+ def dump(self, f):
+ mname_wire = encode_name(self.mname)
+ rname_wire = encode_name(self.rname)
+ if self.rdlen is None:
+ self.rdlen = int(20 + len(mname_wire) / 2 + len(str(rname_wire)) / 2)
+ self.dump_header(f, self.rdlen)
+ f.write('# NNAME=%s RNAME=%s\n' % (self.mname, self.rname))
+ f.write('%s %s\n' % (mname_wire, rname_wire))
+ f.write('# SERIAL(%d) REFRESH(%d) RETRY(%d) EXPIRE(%d) MINIMUM(%d)\n' %
+ (self.serial, self.refresh, self.retry, self.expire,
+ self.minimum))
+ f.write('%08x %08x %08x %08x %08x\n' % (self.serial, self.refresh,
+ self.retry, self.expire,
+ self.minimum))
+
+class TXT(RR):
+ '''Implements rendering TXT RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - nstring (int): number of character-strings
+ - stringlenN (int) (int, N = 0, ..., nstring-1): the length of the
+ N-th character-string.
+ - stringN (string, N = 0, ..., nstring-1): the N-th
+ character-string.
+ - stringlen (int): the default string. If nstring >= 1 and the
+ corresponding stringlenN isn't specified in the spec file, this
+ value will be used. If this parameter isn't specified either,
+ the length of the string will be used. Note that it means
+ this parameter (or any stringlenN) doesn't have to be specified
+ unless you want to intentionally build a broken character string.
+ - string (string): the default string. If nstring >= 1 and the
+ corresponding stringN isn't specified in the spec file, this
+ string will be used.
+ '''
+
+ nstring = 1
+ stringlen = None
+ string = 'Test-String'
+
+ def dump(self, f):
+ stringlen_list = []
+ string_list = []
+ wirestring_list = []
+ for i in range(0, self.nstring):
+ key_string = 'string' + str(i)
+ if key_string in self.__dict__:
+ string_list.append(self.__dict__[key_string])
+ else:
+ string_list.append(self.string)
+ wirestring_list.append(encode_string(string_list[-1]))
+ key_stringlen = 'stringlen' + str(i)
+ if key_stringlen in self.__dict__:
+ stringlen_list.append(self.__dict__[key_stringlen])
+ else:
+ stringlen_list.append(self.stringlen)
+ if stringlen_list[-1] is None:
+ stringlen_list[-1] = int(len(wirestring_list[-1]) / 2)
+ if self.rdlen is None:
+ self.rdlen = int(len(''.join(wirestring_list)) / 2) + self.nstring
+ self.dump_header(f, self.rdlen)
+ for i in range(0, self.nstring):
+ f.write('# String Len=%d, String=\"%s\"\n' %
+ (stringlen_list[i], string_list[i]))
+ f.write('%02x%s%s\n' % (stringlen_list[i],
+ ' ' if len(wirestring_list[i]) > 0 else '',
+ wirestring_list[i]))
+
+class RP(RR):
+ '''Implements rendering RP RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - mailbox (string): The mailbox field.
+ - text (string): The text field.
+ These strings must be interpreted as a valid domain name.
+ '''
+ mailbox = 'root.example.com'
+ text = 'rp-text.example.com'
+ def dump(self, f):
+ mailbox_wire = encode_name(self.mailbox)
+ text_wire = encode_name(self.text)
+ if self.rdlen is None:
+ self.rdlen = (len(mailbox_wire) + len(text_wire)) / 2
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
+ f.write('%s %s\n' % (mailbox_wire, text_wire))
+
+class SSHFP(RR):
+ '''Implements rendering SSHFP RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - algorithm (int): The algorithm number.
+ - fingerprint_type (int): The fingerprint type.
+ - fingerprint (string): The fingerprint.
+ '''
+ algorithm = 2
+ fingerprint_type = 1
+ fingerprint = '123456789abcdef67890123456789abcdef67890'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 2 + (len(self.fingerprint) / 2)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# ALGORITHM=%d FINGERPRINT_TYPE=%d FINGERPRINT=%s\n' % (self.algorithm,
+ self.fingerprint_type,
+ self.fingerprint))
+ f.write('%02x %02x %s\n' % (self.algorithm, self.fingerprint_type, self.fingerprint))
+
+class MINFO(RR):
+ '''Implements rendering MINFO RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - rmailbox (string): The rmailbox field.
+ - emailbox (string): The emailbox field.
+ These strings must be interpreted as a valid domain name.
+ '''
+ rmailbox = 'rmailbox.example.com'
+ emailbox = 'emailbox.example.com'
+ def dump(self, f):
+ rmailbox_wire = encode_name(self.rmailbox)
+ emailbox_wire = encode_name(self.emailbox)
+ if self.rdlen is None:
+ self.rdlen = (len(rmailbox_wire) + len(emailbox_wire)) / 2
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# RMAILBOX=%s EMAILBOX=%s\n' % (self.rmailbox, self.emailbox))
+ f.write('%s %s\n' % (rmailbox_wire, emailbox_wire))
+
+class AFSDB(RR):
+ '''Implements rendering AFSDB RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - subtype (16 bit int): The subtype field.
+ - server (string): The server field.
+ The string must be interpreted as a valid domain name.
+ '''
+ subtype = 1
+ server = 'afsdb.example.com'
+ def dump(self, f):
+ server_wire = encode_name(self.server)
+ if self.rdlen is None:
+ self.rdlen = 2 + len(server_wire) / 2
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
+ f.write('%04x %s\n' % (self.subtype, server_wire))
+
+class CAA(RR):
+ '''Implements rendering CAA RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - flags (int): The flags field.
+ - tag (string): The tag field.
+ - value (string): The value field.
+ '''
+ flags = 0
+ tag = 'issue'
+ value = 'ca.example.net'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 1 + 1 + len(self.tag) + len(self.value)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# FLAGS=%d TAG=%s VALUE=%s\n' % \
+ (self.flags, self.tag, self.value))
+ f.write('%02x %02x ' % \
+ (self.flags, len(self.tag)))
+ f.write(encode_string(self.tag))
+ f.write(encode_string(self.value))
+ f.write('\n')
+
+class DNSKEY(RR):
+ '''Implements rendering DNSKEY RDATA in the test data format.
+
+ Configurable parameters are as follows (see code below for the
+ default values):
+ - flags (16-bit int): The flags field.
+ - protocol (8-bit int): The protocol field.
+ - algorithm (8-bit int): The algorithm field.
+ - digest (string): The key digest field.
+ '''
+ flags = 257
+ protocol = 3
+ algorithm = 5
+ digest = 'AAECAwQFBgcICQoLDA0ODw=='
+
+ def dump(self, f):
+ decoded_digest = base64.b64decode(bytes(self.digest, 'ascii'))
+ if self.rdlen is None:
+ self.rdlen = 4 + len(decoded_digest)
+ else:
+ self.rdlen = int(self.rdlen)
+
+ self.dump_header(f, self.rdlen)
+
+ f.write('# FLAGS=%d\n' % (self.flags))
+ f.write('%04x\n' % (self.flags))
+
+ f.write('# PROTOCOL=%d\n' % (self.protocol))
+ f.write('%02x\n' % (self.protocol))
+
+ f.write('# ALGORITHM=%d\n' % (self.algorithm))
+ f.write('%02x\n' % (self.algorithm))
+
+ f.write('# DIGEST=%s\n' % (self.digest))
+ f.write('%s\n' % (encode_bytes(decoded_digest)))
+
+class NSECBASE(RR):
+ '''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
+ these RRs. The NSEC and NSEC3 classes will be inherited from this
+ class.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - nbitmap (int): The number of type bitmaps.
+ The following three define the bitmaps. If suffixed with "N"
+ (0 <= N < nbitmaps), it means the definition for the N-th bitmap.
+ If there is no suffix (e.g., just "block", it means the default
+ for any unspecified values)
+ - block[N] (8-bit int): The Window Block.
+ - maplen[N] (8-bit int): The Bitmap Length. The default "maplen"
+ can also be unspecified (with being set to None), in which case
+ the corresponding length will be calculated from the bitmap.
+ - bitmap[N] (string): The Bitmap. This must be the hexadecimal
+ representation of the bitmap field. For example, for a bitmap
+ where the 7th and 15th bits (and only these bits) are set, it
+ must be '0101'. Note also that the value must be quoted with
+ single quotations because it could also be interpreted as an
+ integer.
+ '''
+ nbitmap = 1 # number of bitmaps
+ block = 0
+ maplen = None # default bitmap length, auto-calculate
+ bitmap = '040000000003' # an arbitrarily chosen bitmap sample
+ def dump(self, f):
+ # first, construct the bitmap data
+ block_list = []
+ maplen_list = []
+ bitmap_list = []
+ for i in range(0, self.nbitmap):
+ key_bitmap = 'bitmap' + str(i)
+ if key_bitmap in self.__dict__:
+ bitmap_list.append(self.__dict__[key_bitmap])
+ else:
+ bitmap_list.append(self.bitmap)
+ key_maplen = 'maplen' + str(i)
+ if key_maplen in self.__dict__:
+ maplen_list.append(self.__dict__[key_maplen])
+ else:
+ maplen_list.append(self.maplen)
+ if maplen_list[-1] is None: # calculate it if not specified
+ maplen_list[-1] = int(len(bitmap_list[-1]) / 2)
+ key_block = 'block' + str(i)
+ if key_block in self.__dict__:
+ block_list.append(self.__dict__[key_block])
+ else:
+ block_list.append(self.block)
+
+ # dump RR-type specific part (NSEC or NSEC3)
+ self.dump_fixedpart(f, 2 * self.nbitmap + \
+ int(len(''.join(bitmap_list)) / 2))
+
+ # dump the bitmap
+ for i in range(0, self.nbitmap):
+ f.write('# Bitmap: Block=%d, Length=%d\n' %
+ (block_list[i], maplen_list[i]))
+ f.write('%02x %02x %s\n' %
+ (block_list[i], maplen_list[i], bitmap_list[i]))
+
+class NSEC(NSECBASE):
+ '''Implements rendering NSEC RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - Type bitmap related parameters: see class NSECBASE
+ - nextname (string): The Next Domain Name field. The string must be
+ interpreted as a valid domain name.
+ '''
+
+ nextname = 'next.example.com'
+ def dump_fixedpart(self, f, bitmap_totallen):
+ name_wire = encode_name(self.nextname)
+ if self.rdlen is None:
+ # if rdlen needs to be calculated, it must be based on the bitmap
+ # length, because the configured maplen can be fake.
+ self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
+ self.dump_header(f, self.rdlen)
+ f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
+ int(len(name_wire) / 2)))
+ f.write('%s\n' % name_wire)
+
+class NSEC3PARAM(RR):
+ '''Implements rendering NSEC3PARAM RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - hashalg (8-bit int): The Hash Algorithm field. Note that
+ currently the only defined algorithm is SHA-1, for which a value
+ of 1 will be used, and it's the default. So this implementation
+ does not support any string representation right now.
+ - optout (bool): The Opt-Out flag of the Flags field.
+ - mbz (7-bit int): The rest of the Flags field. This value will
+ be left shifted for 1 bit and then OR-ed with optout to
+ construct the complete Flags field.
+ - iterations (16-bit int): The Iterations field.
+ - saltlen (int): The Salt Length field.
+ - salt (string): The Salt field. It is converted to a sequence of
+ ascii codes and its hexadecimal representation will be used.
+ '''
+
+ hashalg = 1 # SHA-1
+ optout = False # opt-out flag
+ mbz = 0 # other flag fields (none defined yet)
+ iterations = 1
+ saltlen = 5
+ salt = 's' * saltlen
+
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 4 + 1 + len(self.salt)
+ self.dump_header(f, self.rdlen)
+ self._dump_params(f)
+
+ def _dump_params(self, f):
+ '''This method is intended to be shared with NSEC3 class.
+
+ '''
+
+ optout_val = 1 if self.optout else 0
+ f.write('# Hash Alg=%s, Opt-Out=%d, Other Flags=%0x, Iterations=%d\n' %
+ (code_totext(self.hashalg, rdict_nsec3_algorithm),
+ optout_val, self.mbz, self.iterations))
+ f.write('%02x %02x %04x\n' %
+ (self.hashalg, (self.mbz << 1) | optout_val, self.iterations))
+ f.write("# Salt Len=%d, Salt='%s'\n" % (self.saltlen, self.salt))
+ f.write('%02x%s%s\n' % (self.saltlen,
+ ' ' if len(self.salt) > 0 else '',
+ encode_string(self.salt)))
+
+class NSEC3(NSECBASE, NSEC3PARAM):
+ '''Implements rendering NSEC3 RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - Type bitmap related parameters: see class NSECBASE
+ - Hash parameter related parameters: see class NSEC3PARAM
+ - hashlen (int): The Hash Length field.
+ - hash (string): The Next Hashed Owner Name field. This parameter
+ is interpreted as "salt".
+ '''
+
+ hashlen = 20
+ hash = 'h' * hashlen
+ def dump_fixedpart(self, f, bitmap_totallen):
+ if self.rdlen is None:
+ # if rdlen needs to be calculated, it must be based on the bitmap
+ # length, because the configured maplen can be fake.
+ self.rdlen = 4 + 1 + len(self.salt) + 1 + len(self.hash) \
+ + bitmap_totallen
+ self.dump_header(f, self.rdlen)
+ self._dump_params(f)
+ f.write("# Hash Len=%d, Hash='%s'\n" % (self.hashlen, self.hash))
+ f.write('%02x%s%s\n' % (self.hashlen,
+ ' ' if len(self.hash) > 0 else '',
+ encode_string(self.hash)))
+
+class RRSIG(RR):
+ '''Implements rendering RRSIG RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - covered (int or string): The Type Covered field. If specified
+ as an integer, it must be the 16-bit RR type value of the
+ covered type. If specified as a string, it must be the textual
+ mnemonic of the type.
+ - algorithm (int or string): The Algorithm field. If specified
+ as an integer, it must be the 8-bit algorithm number as defined
+ in RFC4034. If specified as a string, it must be one of the keys
+ of dict_algorithm (case insensitive).
+ - labels (int): The Labels field. If omitted (the corresponding
+ variable being set to None), the number of labels of "signer"
+ (excluding the trailing null label as specified in RFC4034) will
+ be used.
+ - originalttl (32-bit int): The Original TTL field.
+ - expiration (32-bit int): The Expiration TTL field.
+ - inception (32-bit int): The Inception TTL field.
+ - tag (16-bit int): The Key Tag field.
+ - signer (string): The Signer's Name field. The string must be
+ interpreted as a valid domain name.
+ - signature (int): The Signature field. Right now only a simple
+ integer form is supported. A prefix of "0" will be prepended if
+ the resulting hexadecimal representation consists of an odd
+ number of characters.
+ '''
+
+ covered = 'A'
+ algorithm = 'RSASHA1'
+ labels = None # auto-calculate (#labels of signer)
+ originalttl = 3600
+ expiration = int(time.mktime(datetime.strptime('20100131120000',
+ dnssec_timefmt).timetuple()))
+ inception = int(time.mktime(datetime.strptime('20100101120000',
+ dnssec_timefmt).timetuple()))
+ tag = 0x1035
+ signer = 'example.com'
+ signature = 0x123456789abcdef123456789abcdef
+
+ def dump(self, f):
+ name_wire = encode_name(self.signer)
+ sig_wire = '%x' % self.signature
+ if len(sig_wire) % 2 != 0:
+ sig_wire = '0' + sig_wire
+ if self.rdlen is None:
+ self.rdlen = int(18 + len(name_wire) / 2 + len(str(sig_wire)) / 2)
+ self.dump_header(f, self.rdlen)
+
+ if type(self.covered) is str:
+ self.covered = dict_rrtype[self.covered.lower()]
+ if type(self.algorithm) is str:
+ self.algorithm = dict_algorithm[self.algorithm.lower()]
+ if self.labels is None:
+ self.labels = count_namelabels(self.signer)
+ f.write('# Covered=%s Algorithm=%s Labels=%d OrigTTL=%d\n' %
+ (code_totext(self.covered, rdict_rrtype),
+ code_totext(self.algorithm, rdict_algorithm), self.labels,
+ self.originalttl))
+ f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm,
+ self.labels, self.originalttl))
+ f.write('# Expiration=%s, Inception=%s\n' %
+ (str(self.expiration), str(self.inception)))
+ f.write('%08x %08x\n' % (self.expiration, self.inception))
+ f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
+ f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
+
+class TKEY(RR):
+ '''Implements rendering TKEY RDATA in the test data format.
+
+ As a meta RR type TKEY uses some non common parameters. This
+ class overrides some of the default attributes of the RR class
+ accordingly:
+ - rr_class is set to 'ANY'
+ - rr_ttl is set to 0
+ Like other derived classes these can be overridden via the spec
+ file.
+
+ Other configurable parameters are as follows (see the description
+ of the same name of attribute for the default value):
+ - algorithm (string): The Algorithm Name field. The value is
+ generally interpreted as a domain name string, and will
+ typically be gss-tsig.
+ - inception (32-bit int): The Inception TTL field.
+ - expire (32-bit int): The Expire TTL field.
+ - mode (16-bit int): The Mode field.
+ - error (16-bit int): The Error field.
+ - key_len (int): The Key Len field.
+ - key (int or string): The Key field. If specified as an integer,
+ the integer value is used as the Key, possibly with prepended
+ 0's so that the total length will be key len. If specified as a
+ string, it is converted to a sequence of ascii codes and its
+ hexadecimal representation will be used. So, for example, if
+ "key" is set to 'abc', it will be converted to '616263'. Note
+ that in this case the length of "key" may not be equal to
+ key_len. If unspecified, the key_len number of '78' (ascii
+ code of 'x') will be used.
+ - other_len (int): The Other Len field.
+ - other_data (int or string): The Other Data field. This is
+ interpreted just like "key" except that other_len is used
+ instead of key_len. If unspecified this will be empty.
+ '''
+
+ algorithm = 'gss-tsig'
+ inception = int(time.mktime(datetime.strptime('20210501120000',
+ dnssec_timefmt).timetuple()))
+ expire = int(time.mktime(datetime.strptime('20210501130000',
+ dnssec_timefmt).timetuple()))
+ mode = 3 # GSS-API
+ error = 0
+ key_len = 32
+ key = None # use 'x' * key_len
+ other_len = 0
+ other_data = None
+
+ # TKEY has some special defaults
+ def __init__(self):
+ super().__init__()
+ self.rr_class = 'ANY'
+ self.rr_ttl = 0
+
+ def dump(self, f):
+ name_wire = encode_name(self.algorithm)
+ key_len = self.key_len
+ key = self.key
+ if key is None:
+ key = encode_string('x' * key_len)
+ else:
+ key = encode_string(self.key, key_len)
+ other_len = self.other_len
+ if other_len is None:
+ other_len = 0
+ other_data = self.other_data
+ if other_data is None:
+ other_data = ''
+ else:
+ other_data = encode_string(self.other_data, other_len)
+ if self.rdlen is None:
+ self.rdlen = int(len(name_wire) / 2 + 16 + len(key) / 2 + \
+ len(other_data) / 2)
+ self.dump_header(f, self.rdlen)
+ f.write('# Algorithm=%s\n' % self.algorithm)
+ f.write('%s\n' % name_wire)
+ f.write('# Inception=%d Expire=%d Mode=%d Error=%d\n' %
+ (self.inception, self.expire, self.mode, self.error))
+ f.write('%08x %08x %04x %04x\n' %
+ (self.inception, self.expire, self.mode, self.error))
+ f.write('# Key Len=%d Key=(see hex)\n' % key_len)
+ f.write('%04x%s\n' % (key_len, ' ' + key if len(key) > 0 else ''))
+ f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
+ f.write('%04x%s\n' % (other_len,
+ ' ' + other_data if len(other_data) > 0 else ''))
+
+class TLSA(RR):
+ '''Implements rendering TLSA RDATA in the test data format.
+
+ Configurable parameters are as follows (see the description of the
+ same name of attribute for the default value):
+ - certificate_usage (int): The certificate usage field value.
+ - selector (int): The selector field value.
+ - matching_type (int): The matching type field value.
+ - certificate_association_data (string): The certificate association data.
+ '''
+ certificate_usage = 0
+ selector = 0
+ matching_type = 1
+ certificate_association_data = 'd2abde240d7cd3ee6b4b28c54df034b97983a1d16e8a410e4561cb106618e971'
+ def dump(self, f):
+ if self.rdlen is None:
+ self.rdlen = 2 + (len(self.certificate_association_data) / 2)
+ else:
+ self.rdlen = int(self.rdlen)
+ self.dump_header(f, self.rdlen)
+ f.write('# CERTIFICATE_USAGE=%d SELECTOR=%d MATCHING_TYPE=%d CERTIFICATE_ASSOCIATION_DATA=%s\n' %\
+ (self.certificate_usage, self.selector, self.matching_type,\
+ self.certificate_association_data))
+ f.write('%02x %02x %02x %s\n' % (self.certificate_usage, self.selector, self.matching_type,\
+ self.certificate_association_data))
+
+class TSIG(RR):
+ '''Implements rendering TSIG RDATA in the test data format.
+
+ As a meta RR type TSIG uses some non common parameters. This
+ class overrides some of the default attributes of the RR class
+ accordingly:
+ - rr_class is set to 'ANY'
+ - rr_ttl is set to 0
+ Like other derived classes these can be overridden via the spec
+ file.
+
+ Other configurable parameters are as follows (see the description
+ of the same name of attribute for the default value):
+ - algorithm (string): The Algorithm Name field. The value is
+ generally interpreted as a domain name string, and will
+ typically be one of the standard algorithm names defined in
+ RFC4635. For convenience, however, a shortcut value "hmac-md5"
+ is allowed instead of the standard "hmac-md5.sig-alg.reg.int".
+ - time_signed (48-bit int): The Time Signed field.
+ - fudge (16-bit int): The Fudge field.
+ - mac_size (int): The MAC Size field. If omitted, the common value
+ determined by the algorithm will be used.
+ - mac (int or string): The MAC field. If specified as an integer,
+ the integer value is used as the MAC, possibly with prepended
+ 0's so that the total length will be mac_size. If specified as a
+ string, it is converted to a sequence of ascii codes and its
+ hexadecimal representation will be used. So, for example, if
+ "mac" is set to 'abc', it will be converted to '616263'. Note
+ that in this case the length of "mac" may not be equal to
+ mac_size. If unspecified, the mac_size number of '78' (ascii
+ code of 'x') will be used.
+ - original_id (16-bit int): The Original ID field.
+ - error (16-bit int): The Error field.
+ - other_len (int): The Other Len field.
+ - other_data (int or string): The Other Data field. This is
+ interpreted just like "mac" except that other_len is used
+ instead of mac_size. If unspecified this will be empty unless
+ the "error" is set to 18 (which means the "BADTIME" error), in
+ which case a hexadecimal representation of "time_signed + fudge
+ + 1" will be used.
+ '''
+
+ algorithm = 'hmac-sha256'
+ time_signed = 1286978795 # arbitrarily chosen default
+ fudge = 300
+ mac_size = None # use a common value for the algorithm
+ mac = None # use 'x' * mac_size
+ original_id = 2845 # arbitrarily chosen default
+ error = 0
+ other_len = None # 6 if error is BADTIME; otherwise 0
+ other_data = None # use time_signed + fudge + 1 for BADTIME
+ dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
+
+ # TSIG has some special defaults
+ def __init__(self):
+ super().__init__()
+ self.rr_class = 'ANY'
+ self.rr_ttl = 0
+
+ def dump(self, f):
+ if str(self.algorithm) == 'hmac-md5':
+ name_wire = encode_name('hmac-md5.sig-alg.reg.int')
+ else:
+ name_wire = encode_name(self.algorithm)
+ mac_size = self.mac_size
+ if mac_size is None:
+ if self.algorithm in self.dict_macsize.keys():
+ mac_size = self.dict_macsize[self.algorithm]
+ else:
+ raise RuntimeError('TSIG Mac Size cannot be determined')
+ mac = encode_string('x' * mac_size) if self.mac is None else \
+ encode_string(self.mac, mac_size)
+ other_len = self.other_len
+ if other_len is None:
+ # 18 = BADTIME
+ other_len = 6 if self.error == 18 else 0
+ other_data = self.other_data
+ if other_data is None:
+ other_data = '%012x' % (self.time_signed + self.fudge + 1) \
+ if self.error == 18 else ''
+ else:
+ other_data = encode_string(self.other_data, other_len)
+ if self.rdlen is None:
+ self.rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
+ len(other_data) / 2)
+ self.dump_header(f, self.rdlen)
+ f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
+ (self.algorithm, self.time_signed, self.fudge))
+ f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
+ f.write('# MAC Size=%d MAC=(see hex)\n' % mac_size)
+ f.write('%04x%s\n' % (mac_size, ' ' + mac if len(mac) > 0 else ''))
+ f.write('# Original-ID=%d Error=%d\n' % (self.original_id, self.error))
+ f.write('%04x %04x\n' % (self.original_id, self.error))
+ f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
+ f.write('%04x%s\n' % (other_len,
+ ' ' + other_data if len(other_data) > 0 else ''))
+
+# Build section-class mapping
+config_param = { 'name' : (Name, {}),
+ 'header' : (DNSHeader, header_xtables),
+ 'question' : (DNSQuestion, question_xtables),
+ 'edns' : (EDNS, {}) }
+for rrtype in dict_rrtype.keys():
+ # For any supported RR types add the tuple of (RR_CLASS, {}).
+ # We expect KeyError as not all the types are supported, and simply
+ # ignore them.
+ try:
+ cur_mod = sys.modules[__name__]
+ config_param[rrtype] = (cur_mod.__dict__[rrtype.upper()], {})
+ except KeyError:
+ pass
+
+def get_config_param(section):
+ s = section
+ m = re.match('^([^:]+)/\d+$', section)
+ if m:
+ s = m.group(1)
+ return config_param[s]
+
+usage = '''usage: %prog [options] input_file'''
+
+if __name__ == "__main__":
+ parser = OptionParser(usage=usage)
+ parser.add_option('-o', '--output', action='store', dest='output',
+ default=None, metavar='FILE',
+ help='output file name [default: prefix of input_file]')
+ (options, args) = parser.parse_args()
+
+ if len(args) == 0:
+ parser.error('input file is missing')
+ configfile = args[0]
+
+ outputfile = options.output
+ if not outputfile:
+ m = re.match('(.*)\.[^.]+$', configfile)
+ if m:
+ outputfile = m.group(1)
+ else:
+ raise ValueError('output file is not specified and input file is not in the form of "output_file.suffix"')
+
+ # DeprecationWarning: use ConfigParser directly
+ config = configparser.SafeConfigParser()
+ config.read(configfile)
+
+ output = open(outputfile, 'w')
+
+ print_header(output, configfile)
+
+ # First try the 'custom' mode; if it fails assume the query mode.
+ try:
+ sections = config.get('custom', 'sections').split(':')
+ except configparser.NoSectionError:
+ sections = ['header', 'question', 'edns']
+
+ for s in sections:
+ section_param = get_config_param(s)
+ (obj, xtables) = (section_param[0](), section_param[1])
+ if get_config(config, s, obj, xtables):
+ obj.dump(output)
+
+ output.close()
diff --git a/src/lib/util/range_utilities.h b/src/lib/util/range_utilities.h
new file mode 100644
index 0000000..a14b677
--- /dev/null
+++ b/src/lib/util/range_utilities.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2012-2019,2021 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 RANGE_UTIL_H
+#define RANGE_UTIL_H 1
+
+#include <stdlib.h>
+#include <algorithm>
+#include <functional>
+
+// This header contains useful methods for conduction operations on
+// a range of container elements. Currently the collection is limited,
+// but it is expected to grow.
+
+namespace isc {
+namespace util {
+
+/// @brief Checks if specified range in a container contains only zeros
+///
+/// @param begin beginning of the range
+/// @param end end of the range
+///
+/// @return true if there are only zeroes, false otherwise
+template <typename Iterator>
+bool
+isRangeZero(Iterator begin, Iterator end) {
+ return (std::find_if(begin, end, [] (int x) { return (0 != x); })
+ == end);
+}
+
+/// @brief Fill in specified range with a random data.
+///
+/// Make sure that random number generator is initialized
+/// properly. Otherwise you will get the same pseudo-random sequence
+/// after every start of your process. Calling srand() is enough. This
+/// method uses default rand(), which is usually a LCG pseudo-random
+/// number generator, so it is not suitable for security
+/// purposes. Please use cryptolink RNG if you are doing anything
+/// related with security.
+///
+/// PRNG initialization is left out of this function on purpose. It may
+/// be initialized to specific value on purpose, e.g. to repeat exactly
+/// the same sequence in a test.
+///
+/// @param begin
+/// @param end
+template <typename Iterator>
+void
+fillRandom(Iterator begin, Iterator end) {
+ for (Iterator x = begin; x != end; ++x) {
+ *x = random();
+ }
+}
+
+} // end of isc::util namespace
+} // end of isc namespace
+
+#endif // RANGE_UTIL_H
diff --git a/src/lib/util/readwrite_mutex.h b/src/lib/util/readwrite_mutex.h
new file mode 100644
index 0000000..14d54e6
--- /dev/null
+++ b/src/lib/util/readwrite_mutex.h
@@ -0,0 +1,187 @@
+// Copyright (C) 2020-2022 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 READWRITE_MUTEX_H
+#define READWRITE_MUTEX_H
+
+/// @file readwrite_mutex.h
+///
+/// Standard implementation of read-write mutexes with writer preference
+/// using C++11 mutex and condition variable.
+/// As we need only the RAII wrappers implement only used methods.
+
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <climits>
+#include <condition_variable>
+#include <mutex>
+
+namespace isc {
+namespace util {
+
+/// @brief Read-Write Mutex.
+///
+/// The code is based on Howard Hinnant's reference implementation
+/// for C++17 shared_mutex.
+class ReadWriteMutex : public boost::noncopyable {
+public:
+
+ /// Constants.
+
+ /// @brief The write entered flag (higher bit so 2^31).
+ static const unsigned WRITE_ENTERED =
+ 1U << (sizeof(unsigned) * CHAR_BIT - 1);
+
+ /// @brief The maximum number of readers (flag complement so 2^31 - 1).
+ static const unsigned MAX_READERS = ~WRITE_ENTERED;
+
+ /// @brief Constructor.
+ ReadWriteMutex() : state_(0) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// @note: do not check that state is 0 as there is nothing very
+ /// useful to do in this case...
+ virtual ~ReadWriteMutex() {
+ std::lock_guard<std::mutex> lk(mutex_);
+ }
+
+ /// @brief Lock write.
+ void writeLock() {
+ std::unique_lock<std::mutex> lk(mutex_);
+ // Wait until the write entered flag can be set.
+ gate1_.wait(lk, [&]() { return (!writeEntered()); });
+ state_ |= WRITE_ENTERED;
+ // Wait until there are no more readers.
+ gate2_.wait(lk, [&]() { return (readers() == 0); });
+ }
+
+ /// @brief Unlock write.
+ ///
+ /// @note: do not check that WRITE_ENTERED was set.
+ void writeUnlock() {
+ std::lock_guard<std::mutex> lk(mutex_);
+ state_ = 0;
+ // Wake-up waiting threads when exiting the guard.
+ gate1_.notify_all();
+ }
+
+ /// @brief Lock read.
+ void readLock() {
+ std::unique_lock<std::mutex> lk(mutex_);
+ // Wait if there is a writer or if readers overflow.
+ gate1_.wait(lk, [&]() { return (state_ < MAX_READERS); });
+ ++state_;
+ }
+
+ /// @brief Unlock read.
+ ///
+ /// @note: do not check that there is a least one reader.
+ void readUnlock() {
+ std::lock_guard<std::mutex> lk(mutex_);
+ unsigned prev = state_--;
+ if (writeEntered()) {
+ if (readers() == 0) {
+ // Last reader: wake up a waiting writer.
+ gate2_.notify_one();
+ }
+ } else {
+ if (prev == MAX_READERS) {
+ // Reader overflow: wake up one waiting reader.
+ gate1_.notify_one();
+ }
+ }
+ }
+
+private:
+
+ /// Helpers.
+
+ /// @brief Check if the write entered flag is set.
+ bool writeEntered() const {
+ return (state_ & WRITE_ENTERED);
+ }
+
+ /// @brief Return the number of readers.
+ unsigned readers() const {
+ return (state_ & MAX_READERS);
+ }
+
+ /// Members.
+
+ /// @brief Mutex.
+ ///
+ /// Used to protect the state and in condition variables.
+ std::mutex mutex_;
+
+ /// @brief First condition variable.
+ ///
+ /// Used to block while the write entered flag is set or readers overflow.
+ std::condition_variable gate1_;
+
+ /// @brief Second condition variable.
+ ///
+ /// Used to block writers until the reader count decrements to zero.
+ std::condition_variable gate2_;
+
+ /// @brief State.
+ ///
+ /// Used to handle the write entered flag and the reader count.
+ unsigned state_;
+};
+
+/// @brief Read mutex RAII handler.
+///
+/// The constructor acquires the lock, the destructor releases it.
+class ReadLockGuard : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param rw_mutex The read mutex.
+ ReadLockGuard(ReadWriteMutex& rw_mutex) : rw_mutex_(rw_mutex) {
+ rw_mutex_.readLock();
+ }
+
+ /// @brief Destructor.
+ virtual ~ReadLockGuard() {
+ rw_mutex_.readUnlock();
+ }
+
+private:
+ /// @brief The read-write mutex.
+ ReadWriteMutex& rw_mutex_;
+
+};
+
+/// @brief Write mutex RAII handler.
+///
+/// The constructor acquires the lock, the destructor releases it.
+class WriteLockGuard : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param rw_mutex The write mutex.
+ WriteLockGuard(ReadWriteMutex& rw_mutex) : rw_mutex_(rw_mutex) {
+ rw_mutex_.writeLock();
+ }
+
+ /// @brief Destructor.
+ virtual ~WriteLockGuard() {
+ rw_mutex_.writeUnlock();
+ }
+
+private:
+ /// @brief The read-write mutex.
+ ReadWriteMutex& rw_mutex_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // READWRITE_MUTEX_H
diff --git a/src/lib/util/reconnect_ctl.cc b/src/lib/util/reconnect_ctl.cc
new file mode 100644
index 0000000..265ff2f
--- /dev/null
+++ b/src/lib/util/reconnect_ctl.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 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/reconnect_ctl.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+
+std::string
+ReconnectCtl::onFailActionToText(OnFailAction action) {
+ switch (action) {
+ case OnFailAction::STOP_RETRY_EXIT:
+ return ("stop-retry-exit");
+ case OnFailAction::SERVE_RETRY_EXIT:
+ return ("serve-retry-exit");
+ case OnFailAction::SERVE_RETRY_CONTINUE:
+ return ("serve-retry-continue");
+ }
+ return ("invalid-action-type");
+}
+
+OnFailAction
+ReconnectCtl::onFailActionFromText(const std::string& text) {
+ if (text == "stop-retry-exit") {
+ return (OnFailAction::STOP_RETRY_EXIT);
+ } else if (text == "serve-retry-exit") {
+ return (OnFailAction::SERVE_RETRY_EXIT);
+ } else if (text == "serve-retry-continue") {
+ return (OnFailAction::SERVE_RETRY_CONTINUE);
+ } else {
+ isc_throw(BadValue, "Invalid action on connection loss: " << text);
+ }
+}
+
+}
+}
diff --git a/src/lib/util/reconnect_ctl.h b/src/lib/util/reconnect_ctl.h
new file mode 100644
index 0000000..b9f4a32
--- /dev/null
+++ b/src/lib/util/reconnect_ctl.h
@@ -0,0 +1,143 @@
+// Copyright (C) 2022 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 RECONNECT_CTL_H
+#define RECONNECT_CTL_H
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief Type of action to take on connection loss.
+enum class OnFailAction {
+ STOP_RETRY_EXIT,
+ SERVE_RETRY_EXIT,
+ SERVE_RETRY_CONTINUE
+};
+
+/// @brief Warehouses reconnect control values
+///
+/// When any connection loses connectivity to its backend, it
+/// creates an instance of this class based on its configuration parameters and
+/// passes the instance into connection's lost callback. This allows
+/// the layer(s) above the connection to know how to proceed.
+///
+class ReconnectCtl {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param backend_type type of the caller backend.
+ /// @param timer_name timer associated to this object.
+ /// @param max_retries maximum number of reconnect attempts to make.
+ /// @param retry_interval amount of time to between reconnect attempts.
+ /// @param action which should be taken on connection loss.
+ ReconnectCtl(const std::string& backend_type, const std::string& timer_name,
+ unsigned int max_retries, unsigned int retry_interval,
+ OnFailAction action) :
+ backend_type_(backend_type), timer_name_(timer_name),
+ max_retries_(max_retries), retries_left_(max_retries),
+ retry_interval_(retry_interval), action_(action) {}
+
+ /// @brief Returns the type of the caller backend.
+ std::string backendType() const {
+ return (backend_type_);
+ }
+
+ /// @brief Returns the associated timer name.
+ ///
+ /// @return the associated timer.
+ std::string timerName() const {
+ return (timer_name_);
+ }
+
+ /// @brief Decrements the number of retries remaining
+ ///
+ /// Each call decrements the number of retries by one until zero is reached.
+ /// @return true the number of retries remaining is greater than zero.
+ bool checkRetries() {
+ return (retries_left_ ? --retries_left_ : false);
+ }
+
+ /// @brief Returns the maximum number of retries allowed.
+ unsigned int maxRetries() const {
+ return (max_retries_);
+ }
+
+ /// @brief Returns the number for retries remaining.
+ unsigned int retriesLeft() const {
+ return (retries_left_);
+ }
+
+ /// @brief Returns an index of current retry.
+ unsigned int retryIndex() const {
+ return (max_retries_ - retries_left_);
+ }
+
+ /// @brief Returns the amount of time to wait between reconnect attempts.
+ unsigned int retryInterval() const {
+ return (retry_interval_);
+ }
+
+ /// @brief Resets the retries count.
+ void resetRetries() {
+ retries_left_ = max_retries_;
+ }
+
+ /// @brief Return true if the connection loss should affect the service,
+ /// false otherwise
+ bool alterServiceState() const {
+ return (action_ == OnFailAction::STOP_RETRY_EXIT);
+ }
+
+ /// @brief Return true if the connection recovery mechanism should shut down
+ /// the server on failure, false otherwise.
+ bool exitOnFailure() const {
+ return ((action_ == OnFailAction::STOP_RETRY_EXIT) ||
+ (action_ == OnFailAction::SERVE_RETRY_EXIT));
+ }
+
+ /// @brief Convert action to string.
+ ///
+ /// @param action The action type to be converted to text.
+ /// @return The text representation of the action type.
+ static std::string onFailActionToText(OnFailAction action);
+
+ /// @brief Convert string to action.
+ ///
+ /// @param text The text to be converted to action type.
+ /// @return The action type corresponding to the text representation.
+ static OnFailAction onFailActionFromText(const std::string& text);
+
+private:
+
+ /// @brief Caller backend type.
+ const std::string backend_type_;
+
+ /// @brief Timer associated to this object.
+ std::string timer_name_;
+
+ /// @brief Maximum number of retry attempts to make.
+ unsigned int max_retries_;
+
+ /// @brief Number of attempts remaining.
+ unsigned int retries_left_;
+
+ /// @brief The amount of time to wait between reconnect attempts.
+ unsigned int retry_interval_;
+
+ /// @brief Action to take on connection loss.
+ OnFailAction action_;
+};
+
+/// @brief Pointer to an instance of ReconnectCtl
+typedef boost::shared_ptr<ReconnectCtl> ReconnectCtlPtr;
+
+}
+}
+
+#endif // RECONNECT_CTL_H
diff --git a/src/lib/util/staged_value.h b/src/lib/util/staged_value.h
new file mode 100644
index 0000000..3f85f0e
--- /dev/null
+++ b/src/lib/util/staged_value.h
@@ -0,0 +1,118 @@
+// Copyright (C) 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/.
+
+#ifndef STAGED_VALUE_H
+#define STAGED_VALUE_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief This class implements set/commit mechanism for a single object.
+///
+/// In some cases it is desired to set value for an object while keeping
+/// ability to revert to an original value under certain conditions.
+/// This is often desired for objects holding some part of application's
+/// configuration. Configuration is usually a multi-step process and
+/// may fail on almost any stage. If this happens, the last good
+/// configuration should be used. This implies that some of the state
+/// of some of the objects needs to be reverted.
+///
+/// This class implements a mechanism for setting and committing a value.
+/// Until the new value has been committed it is possible to revert to
+/// an original value.
+///
+/// @tparam ValueType Type of the value represented by this class.
+template<typename ValueType>
+class StagedValue : public boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the default value.
+ StagedValue()
+ : staging_(new ValueType()), current_(new ValueType()),
+ modified_(false) {
+ }
+
+ /// @brief Retrieves current value.
+ ///
+ /// If the value hasn't been modified since last commit, reset or
+ /// revert operation, a committed value is returned. If the value
+ /// has been modified, the modified value is returned.
+ const ValueType& getValue() const {
+ return (modified_ ? *staging_ : *current_);
+ }
+
+ /// @brief Sets new value.
+ ///
+ /// @param new_value New value to be assigned.
+ void setValue(const ValueType& new_value) {
+ *staging_ = new_value;
+ modified_ = true;
+ }
+
+ /// @brief Commits a value.
+ void commit() {
+ // Only apply changes if any modifications made.
+ if (modified_) {
+ current_ = staging_;
+ }
+ revert();
+ }
+
+ /// @brief Resets value to defaults.
+ void reset() {
+ revert();
+ current_.reset(new ValueType());
+ }
+
+ /// @brief Reverts any modifications since last commit.
+ void revert() {
+ staging_.reset(new ValueType());
+ modified_ = false;
+ }
+
+ /// @brief Assignment operator.
+ ///
+ /// @param value New value to be assigned.
+ /// @return Reference to this.
+ StagedValue& operator=(const ValueType& value) {
+ setValue(value);
+ return (*this);
+ }
+
+ /// @brief Conversion operator to value type.
+ ///
+ /// @return Reference to value represented by this object.
+ operator const ValueType&() const {
+ return (getValue());
+ }
+
+private:
+
+ /// @brief Pointer to staging value.
+ ///
+ /// This value holds any modifications made.
+ boost::shared_ptr<ValueType> staging_;
+
+ /// @brief Pointer to committed value.
+ ///
+ /// This value holds last committed changes.
+ boost::shared_ptr<ValueType> current_;
+
+ /// @brief Boolean flag which indicates if any modifications have been
+ /// applied since last commit.
+ bool modified_;
+
+};
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // STAGED_VALUE_H
diff --git a/src/lib/util/state_model.cc b/src/lib/util/state_model.cc
new file mode 100644
index 0000000..6c9a13d
--- /dev/null
+++ b/src/lib/util/state_model.cc
@@ -0,0 +1,465 @@
+// Copyright (C) 2013-2020 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/state_model.h>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/********************************** State *******************************/
+
+State::State(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing)
+ : LabeledValue(value, label), handler_(handler), pausing_(state_pausing),
+ was_paused_(false) {
+}
+
+State::~State() {
+}
+
+void
+State::run() {
+ (handler_)();
+}
+
+bool
+State::shouldPause() {
+ if ((pausing_ == STATE_PAUSE_ALWAYS) ||
+ ((pausing_ == STATE_PAUSE_ONCE) && (!was_paused_))) {
+ was_paused_ = true;
+ return (true);
+ }
+ return (false);
+}
+
+/********************************** StateSet *******************************/
+
+StateSet::StateSet() {
+}
+
+StateSet::~StateSet() {
+}
+
+void
+StateSet::add(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing) {
+ try {
+ LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler,
+ state_pausing)));
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "StateSet: cannot add state :" << ex.what());
+ }
+
+}
+
+const StatePtr
+StateSet::getState(int value) {
+ if (!isDefined(value)) {
+ isc_throw(StateModelError," StateSet: state is undefined");
+ }
+
+ // Since we have to use dynamic casting, to get a state pointer
+ // we can't return a reference.
+ StatePtr state = boost::dynamic_pointer_cast<State>(get(value));
+ return (state);
+}
+
+/********************************** StateModel *******************************/
+
+
+// Common state model states
+const int StateModel::NEW_ST;
+const int StateModel::END_ST;
+
+const int StateModel::SM_DERIVED_STATE_MIN;
+
+// Common state model events
+const int StateModel::NOP_EVT;
+const int StateModel::START_EVT;
+const int StateModel::END_EVT;
+const int StateModel::FAIL_EVT;
+
+const int StateModel::SM_DERIVED_EVENT_MIN;
+
+StateModel::StateModel() : events_(), states_(), dictionaries_initted_(false),
+ curr_state_(NEW_ST), prev_state_(NEW_ST),
+ last_event_(NOP_EVT), next_event_(NOP_EVT),
+ on_entry_flag_(false), on_exit_flag_(false),
+ paused_(false), mutex_(new std::mutex) {
+}
+
+StateModel::~StateModel(){
+}
+
+void
+StateModel::startModel(const int start_state) {
+ // Initialize dictionaries of events and states.
+ initDictionaries();
+
+ // Set the current state to starting state and enter the run loop.
+ setState(start_state);
+
+ // Start running the model.
+ runModel(START_EVT);
+}
+
+void
+StateModel::runModel(unsigned int run_event) {
+ /// If the dictionaries aren't built bail out.
+ if (!dictionaries_initted_) {
+ abortModel("runModel invoked before model has been initialized");
+ }
+
+ try {
+ // Seed the loop with the given event as the next to process.
+ postNextEvent(run_event);
+ do {
+ // Invoke the current state's handler. It should consume the
+ // next event, then determine what happens next by setting
+ // current state and/or the next event.
+ getState(curr_state_)->run();
+
+ // Keep going until a handler sets next event to a NOP_EVT.
+ } while (!isModelDone() && getNextEvent() != NOP_EVT);
+ } catch (const std::exception& ex) {
+ // The model has suffered an unexpected exception. This constitutes
+ // a model violation and indicates a programmatic shortcoming.
+ // In theory, the model should account for all error scenarios and
+ // deal with them accordingly. Transition to END_ST with FAILED_EVT
+ // via abortModel.
+ abortModel(ex.what());
+ }
+}
+
+void
+StateModel::nopStateHandler() {
+}
+
+void
+StateModel::initDictionaries() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ if (dictionaries_initted_) {
+ isc_throw(StateModelError, "Dictionaries already initialized");
+ }
+ // First let's build and verify the dictionary of events.
+ try {
+ defineEvents();
+ verifyEvents();
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "Event set is invalid: " << ex.what());
+ }
+
+ // Next let's build and verify the dictionary of states.
+ try {
+ defineStates();
+ verifyStates();
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "State set is invalid: " << ex.what());
+ }
+
+ // Record that we are good to go.
+ dictionaries_initted_ = true;
+}
+
+void
+StateModel::defineEvent(unsigned int event_value, const std::string& label) {
+ if (!isModelNewInternal()) {
+ // Don't allow for self-modifying models.
+ isc_throw(StateModelError, "Events may only be added to a new model."
+ << event_value << " - " << label);
+ }
+
+ // Attempt to add the event to the set.
+ try {
+ events_.add(event_value, label);
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "Error adding event: " << ex.what());
+ }
+}
+
+const EventPtr&
+StateModel::getEvent(unsigned int event_value) {
+ if (!events_.isDefined(event_value)) {
+ isc_throw(StateModelError,
+ "Event value is not defined:" << event_value);
+ }
+
+ return (events_.get(event_value));
+}
+
+void
+StateModel::defineState(unsigned int state_value, const std::string& label,
+ StateHandler handler, const StatePausing& state_pausing) {
+ if (!isModelNewInternal()) {
+ // Don't allow for self-modifying maps.
+ isc_throw(StateModelError, "States may only be added to a new model."
+ << state_value << " - " << label);
+ }
+
+ // Attempt to add the state to the set.
+ try {
+ states_.add(state_value, label, handler, state_pausing);
+ } catch (const std::exception& ex) {
+ isc_throw(StateModelError, "Error adding state: " << ex.what());
+ }
+}
+
+const StatePtr
+StateModel::getState(unsigned int state_value) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return getStateInternal(state_value);
+}
+
+const StatePtr
+StateModel::getStateInternal(unsigned int state_value) {
+ if (!states_.isDefined(state_value)) {
+ isc_throw(StateModelError,
+ "State value is not defined:" << state_value);
+ }
+
+ return (states_.getState(state_value));
+}
+
+void
+StateModel::defineEvents() {
+ defineEvent(NOP_EVT, "NOP_EVT");
+ defineEvent(START_EVT, "START_EVT");
+ defineEvent(END_EVT, "END_EVT");
+ defineEvent(FAIL_EVT, "FAIL_EVT");
+}
+
+void
+StateModel::verifyEvents() {
+ getEvent(NOP_EVT);
+ getEvent(START_EVT);
+ getEvent(END_EVT);
+ getEvent(FAIL_EVT);
+}
+
+void
+StateModel::defineStates() {
+ defineState(NEW_ST, "NEW_ST",
+ std::bind(&StateModel::nopStateHandler, this));
+ defineState(END_ST, "END_ST",
+ std::bind(&StateModel::nopStateHandler, this));
+}
+
+void
+StateModel::verifyStates() {
+ getStateInternal(NEW_ST);
+ getStateInternal(END_ST);
+}
+
+void
+StateModel::onModelFailure(const std::string&) {
+ // Empty implementation to make deriving classes simpler.
+}
+
+void
+StateModel::transition(unsigned int state, unsigned int event) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ setStateInternal(state);
+ postNextEventInternal(event);
+}
+
+void
+StateModel::endModel() {
+ transition(END_ST, END_EVT);
+}
+
+void
+StateModel::unpauseModel() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ paused_ = false;
+}
+
+void
+StateModel::abortModel(const std::string& explanation) {
+ transition(END_ST, FAIL_EVT);
+
+ std::ostringstream stream ;
+ stream << explanation << " : " << getContextStr();
+ onModelFailure(stream.str());
+}
+
+void
+StateModel::setState(unsigned int state) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ setStateInternal(state);
+}
+
+void
+StateModel::setStateInternal(unsigned int state) {
+ if (state != END_ST && !states_.isDefined(state)) {
+ isc_throw(StateModelError,
+ "Attempt to set state to an undefined value: " << state );
+ }
+
+ prev_state_ = curr_state_;
+ curr_state_ = state;
+
+ // Set the "do" flags if we are transitioning.
+ on_entry_flag_ = ((state != END_ST) && (prev_state_ != curr_state_));
+
+ // At this time they are calculated the same way.
+ on_exit_flag_ = on_entry_flag_;
+
+ // If we're entering the new state we need to see if we should
+ // pause the state model in this state.
+ if (on_entry_flag_ && !paused_ && getStateInternal(state)->shouldPause()) {
+ paused_ = true;
+ }
+}
+
+void
+StateModel::postNextEvent(unsigned int event_value) {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ postNextEventInternal(event_value);
+}
+
+void
+StateModel::postNextEventInternal(unsigned int event_value) {
+ // Check for FAIL_EVT as special case of model error before events are
+ // defined.
+ if (event_value != FAIL_EVT && !events_.isDefined(event_value)) {
+ isc_throw(StateModelError,
+ "Attempt to post an undefined event, value: " << event_value);
+ }
+
+ last_event_ = next_event_;
+ next_event_ = event_value;
+}
+
+bool
+StateModel::doOnEntry() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ bool ret = on_entry_flag_;
+ on_entry_flag_ = false;
+ return (ret);
+}
+
+bool
+StateModel::doOnExit() {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ bool ret = on_exit_flag_;
+ on_exit_flag_ = false;
+ return (ret);
+}
+
+unsigned int
+StateModel::getCurrState() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (curr_state_);
+}
+
+unsigned int
+StateModel::getPrevState() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (prev_state_);
+}
+
+unsigned int
+StateModel::getLastEvent() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (last_event_);
+}
+
+unsigned int
+StateModel::getNextEvent() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (next_event_);
+}
+
+bool
+StateModel::isModelNew() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return isModelNewInternal();
+}
+
+bool
+StateModel::isModelNewInternal() const {
+ return (curr_state_ == NEW_ST);
+}
+
+bool
+StateModel::isModelRunning() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST));
+}
+
+bool
+StateModel::isModelWaiting() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST) &&
+ (next_event_ == NOP_EVT));
+}
+
+bool
+StateModel::isModelDone() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (curr_state_ == END_ST);
+}
+
+bool
+StateModel::didModelFail() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return ((curr_state_ == END_ST) && (next_event_ == FAIL_EVT));
+}
+
+bool
+StateModel::isModelPaused() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return (paused_);
+}
+
+std::string
+StateModel::getStateLabel(const int state) const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return getStateLabelInternal(state);
+}
+
+std::string
+StateModel::getStateLabelInternal(const int state) const {
+ return (states_.getLabel(state));
+}
+
+std::string
+StateModel::getEventLabel(const int event) const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ return getEventLabelInternal(event);
+}
+
+std::string
+StateModel::getEventLabelInternal(const int event) const {
+ return (events_.getLabel(event));
+}
+
+std::string
+StateModel::getContextStr() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ std::ostringstream stream;
+ stream << "current state: [ "
+ << curr_state_ << " " << getStateLabelInternal(curr_state_)
+ << " ] next event: [ "
+ << next_event_ << " " << getEventLabelInternal(next_event_) << " ]";
+ return (stream.str());
+}
+
+std::string
+StateModel::getPrevContextStr() const {
+ std::lock_guard<std::mutex> lock(*mutex_);
+ std::ostringstream stream;
+ stream << "previous state: [ "
+ << prev_state_ << " " << getStateLabelInternal(prev_state_)
+ << " ] last event: [ "
+ << next_event_ << " " << getEventLabelInternal(last_event_) << " ]";
+ return (stream.str());
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/state_model.h b/src/lib/util/state_model.h
new file mode 100644
index 0000000..6ef5238
--- /dev/null
+++ b/src/lib/util/state_model.h
@@ -0,0 +1,850 @@
+// Copyright (C) 2013-2021 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 STATE_MODEL_H
+#define STATE_MODEL_H
+
+/// @file state_model.h This file defines the class StateModel.
+
+#include <exceptions/exceptions.h>
+#include <util/labeled_value.h>
+#include <boost/shared_ptr.hpp>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Thrown if the state machine encounters a general error.
+class StateModelError : public isc::Exception {
+public:
+ StateModelError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Define an Event.
+typedef LabeledValue Event;
+
+/// @brief Define Event pointer.
+typedef LabeledValuePtr EventPtr;
+
+/// @brief Defines a pointer to an instance method for handling a state.
+typedef std::function<void()> StateHandler;
+
+/// @brief State machine pausing modes.
+///
+/// Supported modes are:
+/// - always pause in the given state,
+/// - never pause in the given state,
+/// - pause upon first transition to the given state.
+enum StatePausing {
+ STATE_PAUSE_ALWAYS,
+ STATE_PAUSE_NEVER,
+ STATE_PAUSE_ONCE
+};
+
+/// @brief Defines a State within the State Model.
+///
+/// This class provides the means to define a state within a set or dictionary
+/// of states, and assign the state an handler method to execute the state's
+/// actions. It derives from LabeledValue which allows a set of states to be
+/// keyed by integer constants.
+///
+/// Because a state model can be paused in selected states, this class also
+/// provides the means for specifying a pausing mode and for checking whether
+/// the state model should be paused when entering this state.
+class State : public LabeledValue {
+public:
+ /// @brief Constructor
+ ///
+ /// @param value is the numeric value of the state
+ /// @param label is the text label to assign to the state
+ /// @param handler is the bound instance method which handles the state's
+ /// action.
+ /// @param state_pausing pausing mode selected for the given state. The
+ /// default value is @c STATE_PAUSE_NEVER.
+ ///
+ /// A typical invocation might look this:
+ ///
+ /// @code
+ /// State(SOME_INT_VAL, "SOME_INT_VAL",
+ /// std::bind(&StateModelDerivation::someHandler, this));
+ /// @endcode
+ ///
+ /// @throw StateModelError if label is null or blank.
+ State(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing = STATE_PAUSE_NEVER);
+
+ /// @brief Destructor
+ virtual ~State();
+
+ /// @brief Invokes the State's handler.
+ void run();
+
+ /// @brief Indicates if the state model should pause upon entering
+ /// this state.
+ ///
+ /// It modifies the @c was_paused_ flag if the state model should
+ /// pause. That way, it keeps track of visits in this particular state,
+ /// making it possible to pause only upon the first transition to the
+ /// state when @c STATE_PAUSE_ONCE mode is used.
+ bool shouldPause();
+
+private:
+ /// @brief Bound instance method pointer to the state's handler method.
+ StateHandler handler_;
+
+ /// @brief Specifies selected pausing mode for a state.
+ StatePausing pausing_;
+
+ /// @brief Indicates if the state machine was already paused in this
+ /// state.
+ bool was_paused_;
+};
+
+/// @brief Defines a shared pointer to a State.
+typedef boost::shared_ptr<State> StatePtr;
+
+/// @brief Implements a unique set or dictionary of states.
+///
+/// This class provides the means to construct and access a unique set of
+/// states. This provide the ability to validate state values, look up their
+/// text labels, and their handlers.
+class StateSet : public LabeledValueSet {
+public:
+ /// @brief Constructor
+ StateSet();
+
+ /// @brief Destructor
+ virtual ~StateSet();
+
+ /// @brief Adds a state definition to the set of states.
+ ///
+ /// @param value is the numeric value of the state
+ /// @param label is the text label to assign to the state
+ /// @param handler is the bound instance method which handles the state's
+ /// @param state_pausing state pausing mode for the given state.
+ ///
+ /// @throw StateModelError if the value is already defined in the set, or
+ /// if the label is null or blank.
+ void add(const int value, const std::string& label, StateHandler handler,
+ const StatePausing& state_pausing);
+
+ /// @brief Fetches a state for the given value.
+ ///
+ /// @param value the numeric value of the state desired
+ ///
+ /// @return A constant pointer the State found.
+ /// Note, this relies on dynamic cast and cannot return a pointer reference.
+ ///
+ /// @throw StateModelError if the value is undefined.
+ const StatePtr getState(int value);
+};
+
+/// @brief Implements a finite state machine.
+///
+/// StateModel is an abstract class that provides the structure and mechanics
+/// of a basic finite state machine.
+///
+/// The state model implementation used is a very basic approach. The model
+/// uses numeric constants to identify events and states, and maintains
+/// dictionaries of defined events and states. Event and state definitions
+/// include a text label for logging purposes. Additionally, each state
+/// definition includes a state handler. State handlers are methods which
+/// implement the actions that need to occur when the model is "in" a given
+/// state. The implementation provides methods to add entries to and verify
+/// the contents of both dictionaries.
+///
+/// During model execution, the following context is tracked:
+///
+/// * current state - The current state of the model
+/// * previous state - The state the model was in prior to the current state
+/// * next event - The next event to be consumed
+/// * last event - The event most recently consumed
+///
+/// When invoked, a state handler determines what it should do based upon the
+/// next event including what the next state and event should be. In other
+/// words the state transition knowledge is distributed among the state
+/// handlers rather than encapsulated in some form of state transition table.
+///
+/// Events "posted" from within the state handlers are "internally" triggered
+/// events. Events "posted" from outside the state model, such as through
+/// the invocation of a callback are "externally" triggered.
+///
+/// StateModel defines two states:
+///
+/// * NEW_ST - State that a model is in following instantiation. It remains in
+/// this state until model execution has begun.
+/// * END_ST - State that a model is in once it has reached its conclusion.
+///
+/// and the following events:
+///
+/// * START_EVT - Event used to start model execution.
+/// * NOP_EVT - Event used to signify that the model stop and wait for an
+/// external event, such as the completion of an asynchronous IO operation.
+/// * END_EVT - Event used to trigger a normal conclusion of the model. This
+/// means only that the model was traversed from start to finish, without any
+/// model violations (i.e. invalid state, event, or transition) or uncaught
+/// exceptions.
+/// * FAIL_EVT - Event to trigger an abnormal conclusion of the model. This
+/// event is posted internally when model execution fails due to a model
+/// violation or uncaught exception. It signifies that the model has reached
+/// an inoperable condition.
+///
+/// Derivations add their own states and events appropriate for their state
+/// model. Note that NEW_ST and END_ST do not support handlers. No work can
+/// be done (events consumed) prior to starting the model nor can work be done
+/// once the model has ended.
+///
+/// Model execution consists of iteratively invoking the state handler
+/// indicated by the current state which should consume the next event. As the
+/// handlers post events and/or change the state, the model is traversed. The
+/// loop stops whenever the model cannot continue without an externally
+/// triggered event or when it has reached its final state. In the case of
+/// the former, the loop may be re-entered upon arrival of the external event.
+///
+/// This loop is implemented in the runModel method. This method accepts an
+/// event as argument which it "posts" as the next event. It then retrieves the
+/// handler for the current state from the handler map and invokes it. runModel
+/// repeats this process until either a NOP_EVT posts or the state changes
+/// to END_ST. In other words each invocation of runModel causes the model to
+/// be traversed from the current state until it must wait or ends.
+///
+/// Re-entering the "loop" upon the occurrence of an external event is done by
+/// invoking runModel with the appropriate event. As before, runModel will
+/// loop until either the NOP_EVT occurs or until the model reaches its end.
+///
+/// A StateModel (derivation) is in the NEW_ST when constructed and remains
+/// there until it has been "started". Starting the model is done by invoking
+/// the startModel method which accepts a state as a parameter. This parameter
+/// specifies the "start" state and it becomes the current state.
+
+/// The first task undertaken by startModel is to initialize and verify the
+/// the event and state dictionaries. The following virtual methods are
+/// provided for this:
+///
+/// * defineEvents - define events
+/// * verifyEvents - verifies that the expected events are defined
+/// * defineStates - defines states
+/// * verifyStates - verifies that the expected states are defined
+///
+/// The concept behind the verify methods is to provide an initial sanity
+/// check of the dictionaries. This should help avoid using undefined event
+/// or state values accidentally.
+///
+/// These methods are intended to be implemented by each "layer" in a StateModel
+/// derivation hierarchy. This allows each layer to define additional events
+/// and states.
+///
+/// Once the dictionaries have been properly initialized, the startModel method
+/// invokes runModel with an event of START_EVT. From this point forward and
+/// until the model reaches the END_ST or fails, it is considered to be
+/// "running". If the model encounters a NOP_EVT then it is "running" and
+/// "waiting". If the model reaches END_ST with an END_EVT it is considered
+/// "done". If the model fails (END_ST with a FAILED_EVT) it is considered
+/// "done" and "failed". There are several boolean status methods which may
+/// be used to check these conditions.
+/// Once the model has been started, defining new events or new states is
+/// illegal. It is possible to call startModel only once.
+///
+/// To progress from one state to the another, state handlers invoke use
+/// the method, transition. This method accepts a state and an event as
+/// parameters. These values become the current state and the next event
+/// respectively. This has the effect of entering the given state having posted
+/// the given event. The postEvent method may be used to post a new event
+/// to the current state.
+///
+/// Bringing the model to a normal end is done by invoking the endModel method
+/// which transitions the model to END_ST with END_EVT. Bringing the model to
+/// an abnormal end is done via the abortModel method, which transitions the
+/// model to END_ST with FAILED_EVT.
+///
+/// The model can be paused in the selected states. The states in which the
+/// state model should pause (always or only once) are determined within the
+/// @c StateModel::defineStates method. The state handlers can check whether
+/// the state machine is paused or not by calling @c StateModel::isModelPaused
+/// and act accordingy. Typically, the state handler would simply post the
+/// @c NOP_EVT when it finds that the state model is paused. The model
+/// remains paused until @c StateModel::unpauseModel is called.
+class StateModel {
+public:
+
+ //@{ States common to all models.
+ /// @brief State that a state model is in immediately after construction.
+ static const int NEW_ST = 0;
+
+ /// @brief Final state, all the state model has reached its conclusion.
+ static const int END_ST = 1;
+
+ /// @brief Value at which custom states in a derived class should begin.
+ static const int SM_DERIVED_STATE_MIN = 11;
+ //@}
+
+ //@{ Events common to all state models.
+ /// @brief Signifies that no event has occurred.
+ /// This is event used to interrupt the event loop to allow waiting for
+ /// an IO event or when there is no more work to be done.
+ static const int NOP_EVT = 0;
+
+ /// @brief Event issued to start the model execution.
+ static const int START_EVT = 1;
+
+ /// @brief Event issued to end the model execution.
+ static const int END_EVT = 2;
+
+ /// @brief Event issued to abort the model execution.
+ static const int FAIL_EVT = 3;
+
+ /// @brief Value at which custom events in a derived class should begin.
+ static const int SM_DERIVED_EVENT_MIN = 11;
+ //@}
+
+ /// @brief Constructor
+ StateModel();
+
+ /// @brief Destructor
+ virtual ~StateModel();
+
+ /// @brief Begins execution of the model.
+ ///
+ /// This method invokes initDictionaries method to initialize the event
+ /// and state dictionaries and then starts the model execution setting
+ /// the current state to the given start state, and the event to START_EVT.
+ /// This method can be called only once to start the state model.
+ ///
+ /// @param start_state is the state in which to begin execution.
+ ///
+ /// @throw StateModelError or others indirectly, as this method calls
+ /// dictionary define and verify methods.
+ void startModel(const int start_state);
+
+ /// @brief Processes events through the state model
+ ///
+ /// This method implements the state model "execution loop". It uses
+ /// the given event as the next event to process and begins invoking
+ /// the state handler for the current state. As described above, the
+ /// invoked state handler consumes the next event and then determines the
+ /// next event and the current state as required to implement the business
+ /// logic. The method continues to loop until the next event posted is
+ /// NOP_EVT or the model ends.
+ ///
+ /// Any exception thrown during the loop is caught, logged, and the
+ /// model is aborted with a FAIL_EVT. The derivation's state
+ /// model is expected to account for any possible errors so any that
+ /// escape are treated as unrecoverable.
+ ///
+ /// @note This method is made virtual for the unit tests which require
+ /// customizations allowing for more control over the state model
+ /// execution.
+ ///
+ /// @param event is the next event to process
+ ///
+ /// This method is guaranteed not to throw.
+ virtual void runModel(unsigned int event);
+
+ /// @brief Conducts a normal transition to the end of the model.
+ ///
+ /// This method posts an END_EVT and sets the current state to END_ST.
+ /// It should be called by any state handler in the model from which
+ /// an exit leads to the model end. In other words, if the transition
+ /// out of a particular state is to the end of the model, that state's
+ /// handler should call endModel.
+ void endModel();
+
+ /// @brief Unpauses state model.
+ void unpauseModel();
+
+ /// @brief An empty state handler.
+ ///
+ /// This method is primarily used to permit special states, NEW_ST and
+ /// END_ST to be included in the state dictionary. Currently it is an
+ /// empty method.
+ void nopStateHandler();
+
+protected:
+
+ /// @brief Initializes the event and state dictionaries.
+ ///
+ /// This method invokes the define and verify methods for both events and
+ /// states to initialize their respective dictionaries.
+ /// This method can be called only once to initialize the state model.
+ ///
+ /// @throw StateModelError or others indirectly, as this method calls
+ /// dictionary define and verify methods.
+ void initDictionaries();
+
+ /// @brief Populates the set of events.
+ ///
+ /// This method is used to construct the set of valid events. Each class
+ /// within a StateModel derivation hierarchy uses this method to add any
+ /// events it defines to the set. Each derivation's implementation must
+ /// also call its superclass's implementation. This allows each class
+ /// within the hierarchy to make contributions to the set of defined
+ /// events. Implementations use the method, defineEvent(), to add event
+ /// definitions. An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::defineEvents() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::defineEvents();
+ ///
+ /// // Add the events defined by the derivation.
+ /// defineEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
+ /// defineEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void defineEvents();
+
+ /// @brief Adds an event value and associated label to the set of events.
+ ///
+ /// This method is called in a thread safe context from @ref defineEvents.
+ ///
+ /// @param value is the numeric value of the event
+ /// @param label is the text label of the event used in log messages and
+ /// exceptions.
+ ///
+ /// @throw StateModelError if the model has already been started, if
+ /// the value is already defined, or if the label is empty.
+ void defineEvent(unsigned int value, const std::string& label);
+
+ /// @brief Fetches the event referred to by value.
+ ///
+ /// This method is called in a thread safe context from @ref verifyEvents.
+ ///
+ /// @param value is the numeric value of the event desired.
+ ///
+ /// @return returns a constant pointer reference to the event if found
+ ///
+ /// @throw StateModelError if the event is not defined.
+ const EventPtr& getEvent(unsigned int value);
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// This method is invoked immediately after the defineEvents method and
+ /// is used to verify that all the required events are defined. If the
+ /// event set is determined to be invalid this method should throw a
+ /// StateModelError. As with the defineEvents method, each class within
+ /// a StateModel derivation hierarchy must supply an implementation
+ /// which calls its superclass's implementation as well as verifying any
+ /// events added by the derivation. Validating an event is accomplished
+ /// by simply attempting to fetch an event by its value from the event set.
+ /// An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::verifyEvents() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::verifyEvents();
+ ///
+ /// // Verify the events defined by the derivation.
+ /// getEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
+ /// getEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void verifyEvents();
+
+ /// @brief Populates the set of states.
+ ///
+ /// This method is used to construct the set of valid states. Each class
+ /// within a StateModel derivation hierarchy uses this method to add any
+ /// states it defines to the set. Each derivation's implementation must
+ /// also call its superclass's implementation. This allows each class
+ /// within the hierarchy to make contributions to the set of defined
+ /// states. Implementations use the method, defineState(), to add state
+ /// definitions. An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::defineStates() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::defineStates();
+ ///
+ /// // Add the states defined by the derivation.
+ /// defineState(SOME_ST, "SOME_ST",
+ /// std::bind(&StateModelDerivation::someHandler, this));
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void defineStates();
+
+ /// @brief Adds an state value and associated label to the set of states.
+ ///
+ /// This method is called in a thread safe context from @ref defineStates.
+ ///
+ /// @param value is the numeric value of the state
+ /// @param label is the text label of the state used in log messages and
+ /// exceptions.
+ /// @param handler is the bound instance method which implements the state's
+ /// actions.
+ /// @param state_pausing pausing mode selected for the given state. The
+ /// default value is @c STATE_PAUSE_NEVER.
+ ///
+ /// @throw StateModelError if the model has already been started, if
+ /// the value is already defined, or if the label is empty.
+ void defineState(unsigned int value, const std::string& label,
+ StateHandler handler,
+ const StatePausing& state_pausing = STATE_PAUSE_NEVER);
+
+ /// @brief Fetches the state referred to by value.
+ ///
+ /// @param value is the numeric value of the state desired.
+ ///
+ /// @return returns a constant pointer to the state if found
+ ///
+ /// @throw StateModelError if the state is not defined.
+ const StatePtr getState(unsigned int value);
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// This method is invoked immediately after the defineStates method and
+ /// is used to verify that all the required states are defined. If the
+ /// state set is determined to be invalid this method should throw a
+ /// StateModelError. As with the defineStates method, each class within
+ /// a StateModel derivation hierarchy must supply an implementation
+ /// which calls its superclass's implementation as well as verifying any
+ /// states added by the derivation. Validating an state is accomplished
+ /// by simply attempting to fetch the state by its value from the state set.
+ /// An example of the derivation's implementation follows:
+ ///
+ /// @code
+ /// void StateModelDerivation::verifyStates() {
+ /// // Call the superclass implementation.
+ /// StateModelDerivation::verifyStates();
+ ///
+ /// // Verify the states defined by the derivation.
+ /// getState(SOME_CUSTOM_EVT_2);
+ /// :
+ /// }
+ /// @endcode
+ ///
+ /// This method is called in a thread safe context from
+ /// @ref initDictionaries.
+ virtual void verifyStates();
+
+ /// @brief Handler for fatal model execution errors.
+ ///
+ /// This method is called when an unexpected error renders during
+ /// model execution, such as a state handler throwing an exception.
+ /// It provides derivations an opportunity to act accordingly by setting
+ /// the appropriate status or taking other remedial action. This allows
+ /// the model execution loop to remain exception safe. This default
+ /// implementation does nothing.
+ ///
+ /// @param explanation text detailing the error and state machine context
+ virtual void onModelFailure(const std::string& explanation);
+
+ /// @brief Sets up the model to transition into given state with a given
+ /// event.
+ ///
+ /// This updates the model's notion of the current state and the next
+ /// event to process. State handlers use this method to move from one state
+ /// to the next.
+ ///
+ /// @param state the new value to assign to the current state.
+ /// @param event the new value to assign to the next event.
+ ///
+ /// @throw StateModelError if the state is invalid.
+ void transition(unsigned int state, unsigned int event);
+
+ /// @brief Aborts model execution.
+ ///
+ /// This method posts a FAILED_EVT and sets the current state to END_ST.
+ /// It is called internally when a model violation occurs. Violations are
+ /// any sort of inconsistency such as attempting to reference an invalid
+ /// state, or if the next event is not valid for the current state, or a
+ /// state handler throws an uncaught exception.
+ ///
+ /// @param explanation is text detailing the reason for aborting.
+ void abortModel(const std::string& explanation);
+
+ /// @brief Sets the current state to the given state value.
+ ///
+ /// This updates the model's notion of the current state and is the
+ /// state whose handler will be executed on the next iteration of the run
+ /// loop. This is intended primarily for internal use and testing. It is
+ /// unlikely that transitioning to a new state without a new event is of
+ /// much use.
+ ///
+ /// @param state the new value to assign to the current state.
+ ///
+ /// @throw StateModelError if the state is invalid.
+ void setState(unsigned int state);
+
+ /// @brief Sets the next event to the given event value.
+ ///
+ /// This updates the model's notion of the next event and is the
+ /// event that will be passed into the current state's handler on the next
+ /// iteration of the run loop.
+ ///
+ /// @param event the numeric event value to post as the next event.
+ ///
+ /// @throw StateModelError if the event is undefined
+ void postNextEvent(unsigned int event);
+
+ /// @brief Checks if on entry flag is true.
+ ///
+ /// This method acts as a one-shot test of whether or not the model is
+ /// transitioning into a new state. It returns true if the on-entry flag
+ /// is true upon entering this method and will set the flag false prior
+ /// to exit. It may be used within state handlers to perform steps that
+ /// should only occur upon entry into the state.
+ ///
+ /// @return true if the on entry flag is true, false otherwise.
+ bool doOnEntry();
+
+ /// @brief Checks if on exit flag is true.
+ ///
+ /// This method acts as a one-shot test of whether or not the model is
+ /// transitioning out of the current state. It returns true if the
+ /// on-exit flag is true upon entering this method and will set the flag
+ /// false prior to exiting. It may be used within state handlers to perform
+ /// steps that should only occur upon exiting out of the current state.
+ ///
+ /// @return true if the on entry flag is true, false otherwise.
+ bool doOnExit();
+
+public:
+
+ /// @brief Fetches the model's current state.
+ ///
+ /// This returns the model's notion of the current state. It is the
+ /// state whose handler will be executed on the next iteration of the run
+ /// loop.
+ ///
+ /// @return An unsigned int representing the current state.
+ unsigned int getCurrState() const;
+
+ /// @brief Fetches the model's previous state.
+ ///
+ /// @return An unsigned int representing the previous state.
+ unsigned int getPrevState() const;
+
+ /// @brief Fetches the model's last event.
+ ///
+ /// @return An unsigned int representing the last event.
+ unsigned int getLastEvent() const;
+
+ /// @brief Fetches the model's next event.
+ ///
+ /// This returns the model's notion of the next event. It is the
+ /// event that will be passed into the current state's handler on the next
+ /// iteration of the run loop.
+ ///
+ /// @return An unsigned int representing the next event.
+ unsigned int getNextEvent() const;
+
+ /// @brief Returns whether or not the model is new.
+ ///
+ /// @return Boolean true if the model has not been started.
+ bool isModelNew() const;
+
+ /// @brief Returns whether or not the model is running.
+ ///
+ /// @return Boolean true if the model has been started but has not yet
+ /// ended.
+ bool isModelRunning() const;
+
+ /// @brief Returns whether or not the model is waiting.
+ ///
+ /// @return Boolean true if the model is running but is waiting for an
+ /// external event for resumption.
+ bool isModelWaiting() const;
+
+ /// @brief Returns whether or not the model has finished execution.
+ ///
+ /// @return Boolean true if the model has reached the END_ST.
+ bool isModelDone() const;
+
+ /// @brief Returns whether or not the model is paused.
+ ///
+ /// @return Boolean true if the model is paused, false otherwise.
+ bool isModelPaused() const;
+
+ /// @brief Returns whether or not the model failed.
+ ///
+ /// @return Boolean true if the model has reached the END_ST and the last
+ /// event indicates a model violation, FAILED_EVT.
+ bool didModelFail() const;
+
+ /// @brief Fetches the label associated with an event value.
+ ///
+ /// @param event is the numeric event value for which the label is desired.
+ ///
+ /// @return Returns a string containing the event label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getEventLabel(const int event) const;
+
+ /// @brief Fetches the label associated with an state value.
+ ///
+ /// @param state is the numeric state value for which the label is desired.
+ ///
+ /// @return Returns a const char* containing the state label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getStateLabel(const int state) const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// current state and next event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// current state: [ {state} {label} ] next event: [ {event} {label} ]
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getContextStr() const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// previous state and last event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// previous state: [ {state} {label} ] last event: [ {event} {label} ]
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getPrevContextStr() const;
+
+protected:
+
+ /// @brief Fetches the state referred to by value.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param value is the numeric value of the state desired.
+ ///
+ /// @return returns a constant pointer to the state if found
+ ///
+ /// @throw StateModelError if the state is not defined.
+ const StatePtr getStateInternal(unsigned int value);
+
+private:
+
+ /// @brief Sets the current state to the given state value.
+ ///
+ /// This updates the model's notion of the current state and is the
+ /// state whose handler will be executed on the next iteration of the run
+ /// loop. This is intended primarily for internal use and testing. It is
+ /// unlikely that transitioning to a new state without a new event is of
+ /// much use.
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param state the new value to assign to the current state.
+ ///
+ /// @throw StateModelError if the state is invalid.
+ void setStateInternal(unsigned int state);
+
+ /// @brief Sets the next event to the given event value.
+ ///
+ /// This updates the model's notion of the next event and is the
+ /// event that will be passed into the current state's handler on the next
+ /// iteration of the run loop.
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param event the numeric event value to post as the next event.
+ ///
+ /// @throw StateModelError if the event is undefined
+ void postNextEventInternal(unsigned int event);
+
+ /// @brief Returns whether or not the model is new.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @return Boolean true if the model has not been started.
+ bool isModelNewInternal() const;
+
+ /// @brief Fetches the label associated with an event value.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param event is the numeric event value for which the label is desired.
+ ///
+ /// @return Returns a string containing the event label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getEventLabelInternal(const int event) const;
+
+ /// @brief Fetches the label associated with an state value.
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @param state is the numeric state value for which the label is desired.
+ ///
+ /// @return Returns a const char* containing the state label or
+ /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+ std::string getStateLabelInternal(const int state) const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// current state and next event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// current state: [ {state} {label} ] next event: [ {event} {label} ]
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getContextStrInternal() const;
+
+ /// @brief Convenience method which returns a string rendition of the
+ /// previous state and last event.
+ ///
+ /// The string will be of the form:
+ ///
+ /// previous state: [ {state} {label} ] last event: [ {event} {label} ]
+ ///
+ /// This method should be called in a thread safe context.
+ ///
+ /// @return Returns a std::string of the format described above.
+ std::string getPrevContextStrInternal() const;
+
+ /// @brief The dictionary of valid events.
+ LabeledValueSet events_;
+
+ /// @brief The dictionary of valid states.
+ StateSet states_;
+
+ /// @brief Indicates if the event and state dictionaries have been initted.
+ bool dictionaries_initted_;
+
+ /// @brief The current state within the model's state model.
+ unsigned int curr_state_;
+
+ /// @brief The previous state within the model's state model.
+ unsigned int prev_state_;
+
+ /// @brief The event last processed by the model.
+ unsigned int last_event_;
+
+ /// @brief The event the model should process next.
+ unsigned int next_event_;
+
+ /// @brief Indicates if state entry logic should be executed.
+ bool on_entry_flag_;
+
+ /// @brief Indicates if state exit logic should be executed.
+ bool on_exit_flag_;
+
+ /// @brief Indicates if the state model is paused.
+ bool paused_;
+
+ /// @brief Protects against concurrent transitions.
+ boost::shared_ptr<std::mutex> mutex_;
+};
+
+/// @brief Defines a pointer to a StateModel.
+typedef boost::shared_ptr<StateModel> StateModelPtr;
+
+} // namespace isc::util
+} // namespace isc
+#endif
diff --git a/src/lib/util/stopwatch.cc b/src/lib/util/stopwatch.cc
new file mode 100644
index 0000000..f75c6cd
--- /dev/null
+++ b/src/lib/util/stopwatch.cc
@@ -0,0 +1,85 @@
+// Copyright (C) 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 <util/stopwatch.h>
+#include <util/stopwatch_impl.h>
+
+namespace isc {
+namespace util {
+
+using namespace boost::posix_time;
+
+Stopwatch::Stopwatch(const bool autostart)
+ : impl_(new StopwatchImpl()) {
+ // If the autostart has been specified, invoke start.
+ if (autostart) {
+ start();
+ }
+}
+
+Stopwatch::~Stopwatch() {
+ delete impl_;
+}
+
+void
+Stopwatch::start() {
+ impl_->start();
+}
+
+void
+Stopwatch::stop() {
+ impl_->stop();
+}
+
+void
+Stopwatch::reset() {
+ impl_->reset();
+}
+
+boost::posix_time::time_duration
+Stopwatch::getLastDuration() const {
+ return (impl_->getLastDuration());
+}
+
+boost::posix_time::time_duration
+Stopwatch::getTotalDuration() const {
+ return (impl_->getTotalDuration());
+}
+
+long
+Stopwatch::getLastMilliseconds() const {
+ return (getLastDuration().total_milliseconds());
+}
+
+long
+Stopwatch::getTotalMilliseconds() const {
+ return (getTotalDuration().total_milliseconds());
+}
+
+long
+Stopwatch::getLastMicroseconds() const {
+ return (getLastDuration().total_microseconds());
+}
+
+long
+Stopwatch::getTotalMicroseconds() const {
+ return (getTotalDuration().total_microseconds());
+}
+
+std::string
+Stopwatch::logFormatLastDuration() const {
+ return (StopwatchImpl::logFormat(getLastDuration()));
+}
+
+std::string
+Stopwatch::logFormatTotalDuration() const {
+ return (StopwatchImpl::logFormat(getTotalDuration()));
+}
+
+} // end of isc::util
+} // end of isc
diff --git a/src/lib/util/stopwatch.h b/src/lib/util/stopwatch.h
new file mode 100644
index 0000000..c3d0b23
--- /dev/null
+++ b/src/lib/util/stopwatch.h
@@ -0,0 +1,129 @@
+// Copyright (C) 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/.
+
+#ifndef STOPWATCH_H
+#define STOPWATCH_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief Forward declaration to the @c Stopwatch implementation.
+class StopwatchImpl;
+
+/// @brief Utility class to measure code execution times.
+///
+/// The API of this class is based on the use cases of a stopwatch. It is
+/// used to measure time spent executing portions of the code. The typical
+/// use case for the @c Stopwatch is to measure the time spent invoking
+/// callouts in hooks library. This provides means for diagnosing the
+/// server's performance degradations when hooks libraries are in use.
+///
+/// This class exposes functions like @c start, @c stop and @c reset which
+/// behave in the same way as a stopwatch used to measure time for sport
+/// activities.
+///
+/// It is possible to measure the cumulative execution time by invoking
+/// @c start and @c stop consecutively. The total measured time will be
+/// a sum of durations between the invocations of respective starts and
+/// stops.
+class Stopwatch : boost::noncopyable {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param autostart Indicates if the stopwatch should be initialized to
+ /// the "started" state. In this state the stopwatch is measuring the time
+ /// since it has been started (object has been constructed in this case.
+ /// If the parameter is set to false (default value), the
+ /// @c Stopwatch::start must be called to start time measurement.
+ Stopwatch(const bool autostart = true);
+
+ /// @brief Destructor.
+ ///
+ /// Destroys the implementation instance.
+ ~Stopwatch();
+
+ /// @brief Starts the stopwatch.
+ ///
+ /// Sets the stopwatch to the "started" state. In this state the stopwatch
+ /// is measuring the duration since @c Stopwatch::start has been invoked.
+ ///
+ //// This method is no-op if the stopwatch is already in the "started"
+ /// state.
+ void start();
+
+ /// @brief Stops the stopwatch.
+ ///
+ /// Sets the stopwatch to the "stopped" state. The stopwatch stops the time
+ /// measurement and records the duration between the last stopwatch start
+ /// and the stop invocation. It also updates the total measured duration,
+ /// i.e. the sum of durations between all start/stop invocations. Both
+ /// values can be retrieved using @c Stopwatch::getLastDuration and
+ /// @c Stopwatch::getTotalDuration respectively, or their variants.
+ ///
+ /// This method is no-op if the stopwatch is already in the "stopped" state.
+ void stop();
+
+ /// @brief Resets the stopwatch.
+ ///
+ /// It resets the stopwatch to the initial state. In this state, the last
+ /// measured duration and the total duration is set to 0. The stopwatch
+ /// is set to the "stopped" state.
+ void reset();
+
+ /// @brief Retrieves last measured duration.
+ ///
+ /// If the stopwatch is in the "stopped" state this method retrieves the
+ /// duration between the last start and stop. If the stopwatch is in the
+ /// "started" state, the retrieved duration is the duration between the
+ /// last start of the stopwatch and the current time.
+ boost::posix_time::time_duration getLastDuration() const;
+
+ /// @brief Retrieves total measured duration.
+ ///
+ /// If the stopwatch is in the "stopped" state this method retrieves the
+ /// total duration between all starts and stops invoked during the
+ /// lifetime of the object or since the last reset. If the stopwatch is
+ /// in the "started" state, the returned is the sum of all durations
+ /// between respective starts and stops, and the duration since the
+ /// stopwatch has been last started and the current time.
+ boost::posix_time::time_duration getTotalDuration() const;
+
+ /// @brief Retrieves the last measured duration in milliseconds.
+ long getLastMilliseconds() const;
+
+ /// @brief Retrieves the total measured duration in milliseconds.
+ long getTotalMilliseconds() const;
+
+ /// @brief Retrieves the last measured duration in microseconds.
+ long getLastMicroseconds() const;
+
+ /// @brief Retrieves the total measured duration in microseconds.
+ long getTotalMicroseconds() const;
+
+ /// @brief Returns the last measured duration in the format directly
+ /// usable in log messages.
+ std::string logFormatLastDuration() const;
+
+ /// @brief Returns the total measured duration in the format directly
+ /// usable in the log messages.
+ std::string logFormatTotalDuration() const;
+
+private:
+
+ /// @brief Pointer to the @c StopwatchImpl.
+ StopwatchImpl* impl_;
+
+};
+
+}
+}
+
+#endif // STOPWATCH_H
+
diff --git a/src/lib/util/stopwatch_impl.cc b/src/lib/util/stopwatch_impl.cc
new file mode 100644
index 0000000..12c154a
--- /dev/null
+++ b/src/lib/util/stopwatch_impl.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2015-2023 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/stopwatch_impl.h>
+#include <iomanip>
+#include <sstream>
+
+namespace isc {
+namespace util {
+
+using namespace boost::posix_time;
+
+StopwatchImpl::StopwatchImpl()
+ : started_(false),
+ last_start_(getCurrentTime()),
+ last_stop_(last_start_),
+ cumulative_time_(microseconds(0)) {
+}
+
+StopwatchImpl::~StopwatchImpl() {
+}
+
+void
+StopwatchImpl::start() {
+ // If stopwatch is "stopped", start it.
+ if (!started_) {
+ last_start_ = getCurrentTime();
+ started_ = true;
+ }
+}
+
+void
+StopwatchImpl::stop() {
+ // Is stopwatch is "started", stop it.
+ if (started_) {
+ last_stop_ = getCurrentTime();
+ // Update the total time with the last measured duration.
+ cumulative_time_ += last_stop_ - last_start_;
+ started_ = false;
+ }
+}
+
+void
+StopwatchImpl::reset() {
+ // Set last start and stop values to the current time. This is the
+ // same as in the constructor. As a result the last duration will
+ // be 0.
+ last_start_ = getCurrentTime();
+ last_stop_ = last_start_;
+ // Set the total duration to 0.
+ cumulative_time_ = microseconds(0);
+ started_ = false;
+}
+
+time_duration
+StopwatchImpl::getLastDuration() const {
+ // If the stopwatch is started, the time measured is between the
+ // start time and the current time. Otherwise, it is between the
+ // start time and last stop.
+ ptime end_time = started_ ? getCurrentTime() : last_stop_;
+ return (end_time - last_start_);
+}
+
+time_duration
+StopwatchImpl::getTotalDuration() const {
+ // Get the total time recorded so far.
+ time_duration total_duration = cumulative_time_;
+ if (started_) {
+ // If the stopwatch is started, add the duration between the
+ // start time and current time.
+ total_duration += (getCurrentTime() - last_start_);
+ }
+ return (total_duration);
+}
+
+std::string
+StopwatchImpl::logFormat(const boost::posix_time::time_duration& duration) {
+ std::ostringstream s;
+ if (duration.total_seconds() > 0) {
+ s << duration.total_seconds() << "."
+ << std::setfill('0') << std::setw(2) << (duration.total_milliseconds()/10 % 100)
+ << " s";
+ } else {
+ s << duration.total_milliseconds() << ".";
+ s << std::setfill('0') << std::setw(3) << (duration.total_microseconds() % 1000)
+ << " ms";
+ }
+ return (s.str());
+}
+
+ptime
+StopwatchImpl::getCurrentTime() const {
+ return (microsec_clock::universal_time());
+}
+
+
+} // end of isc::util
+} // end of isc
diff --git a/src/lib/util/stopwatch_impl.h b/src/lib/util/stopwatch_impl.h
new file mode 100644
index 0000000..0aaae62
--- /dev/null
+++ b/src/lib/util/stopwatch_impl.h
@@ -0,0 +1,122 @@
+// Copyright (C) 2015-2023 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 STOPWATCH_IMPL_H
+#define STOPWATCH_IMPL_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief @c Stopwatch class implementation.
+///
+/// The @c Stopwatch class uses the plimpl idiom to make it easier to unit
+/// test behavior of the @c Stopwatch class without a need to rely on the system
+/// clock. The @c StopwatchImpl API allows for overriding the @c getCurrentTime
+/// method to return the arbitrary time value as current time to various
+/// methods. By setting the current time to arbitrary values the test can expect
+/// arbitrary values being returned by the class methods.
+///
+/// Also, by using the pimpl idiom the @c Stopwatch class hides its implementation
+/// details and leaves the header with only the pointer to the @c StopwatchImpl
+/// class.
+class StopwatchImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Initializes the internally used timestamps. It also sets the state of
+ /// the stopwatch to "stopped".
+ StopwatchImpl();
+
+ /// @brief Virtual destructor.
+ ///
+ /// This destructor is virtual because the @c StopwatchImpl::getCurrentTime
+ /// is virtual.
+ virtual ~StopwatchImpl();
+
+ /// @brief Starts the stopwatch.
+ ///
+ /// Sets the stopwatch to the "started" state. It also records the time when
+ /// the stopwatch is started. This method is no-op if the stopwatch is
+ /// already in the "started" state.
+ ///
+ /// Also see the @c Stopwatch::start for details.
+ void start();
+
+ /// @brief Stop the stopwatch.
+ ///
+ /// Sets the stopwatch to the "stopped" state. The stop time is recorded and
+ /// the cumulative time is updated to include the duration between the most
+ /// recent start and stop. This method is no-op if the stopwatch is already
+ /// in the "stopped" state.
+ ///
+ /// Also see the @c Stopwatch::stop for details.
+ void stop();
+
+ /// @brief Reset the stopwatch.
+ ///
+ /// Also see the @c Stopwatch::reset for details.
+ void reset();
+
+ /// @brief Retrieves the measured duration.
+ ///
+ /// Also see the @c Stopwatch::getLastDuration for details.
+ boost::posix_time::time_duration getLastDuration() const;
+
+ /// @brief Retrieves the total measured duration.
+ ///
+ /// Also see the @c Stopwatch::getTotalDuration for details.
+ boost::posix_time::time_duration getTotalDuration() const;
+
+ /// @brief Returns the duration in the textual format which can be
+ /// directly used in log messages.
+ ///
+ /// @param duration Duration to be converted to the textual format.
+ ///
+ /// @return Converted value which can be used to log duration.
+ static std::string
+ logFormat(const boost::posix_time::time_duration& duration);
+
+protected:
+
+ /// @brief Returns the current time.
+ ///
+ /// This method is used internally by the @c StopwatchImpl class and
+ /// its derivations. This class simply returns the value of
+ /// @c boost::posix_time::microsec_clock::univeral_time(), which is
+ /// a current timestamp. The derivations may replace it with the
+ /// custom implementations. The typical use case is for the unit tests
+ /// to customize the behavior of this function to return well known
+ /// (deterministic) values. As a result, it is possible to influence
+ /// the "measured" values returned by accessors of this class, which
+ /// can be compared against some exact values.
+ virtual boost::posix_time::ptime getCurrentTime() const;
+
+private:
+
+ /// @brief Holds the state of the stopwatch.
+ bool started_;
+
+ /// @brief Holds the timestamp when the stopwatch has been last started.
+ boost::posix_time::ptime last_start_;
+
+ /// @brief Holds the timestamp when the stopwatch has been last stopped.
+ boost::posix_time::ptime last_stop_;
+
+ /// @brief Holds the total measured time since the stopwatch has been
+ /// first started after creation or reset.
+ boost::posix_time::time_duration cumulative_time_;
+
+};
+
+}
+}
+
+#endif // STOPWATCH_H
+
diff --git a/src/lib/util/strutil.cc b/src/lib/util/strutil.cc
new file mode 100644
index 0000000..55f5f97
--- /dev/null
+++ b/src/lib/util/strutil.cc
@@ -0,0 +1,467 @@
+// Copyright (C) 2011-2022 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/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+#include <numeric>
+#include <iostream>
+#include <sstream>
+
+// Early versions of C++11 regex were buggy, use it if we
+// can otherwise, we fall back to regcomp/regexec. For more info see:
+// https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions
+#ifdef USE_REGEX
+#include <regex>
+#else
+#include <sys/types.h>
+#include <regex.h>
+#endif
+
+#include <string.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace str {
+
+// Normalize slashes
+
+void
+normalizeSlash(std::string& name) {
+ if (!name.empty()) {
+ size_t pos = 0;
+ while ((pos = name.find('\\', pos)) != std::string::npos) {
+ name[pos] = '/';
+ }
+ }
+}
+
+// Trim String
+
+string
+trim(const string& instring) {
+ string retstring = "";
+ if (!instring.empty()) {
+ static const char* blanks = " \t\n";
+
+ // Search for first non-blank character in the string
+ size_t first = instring.find_first_not_of(blanks);
+ if (first != string::npos) {
+
+ // String not all blanks, so look for last character
+ size_t last = instring.find_last_not_of(blanks);
+
+ // Extract the trimmed substring
+ retstring = instring.substr(first, (last - first + 1));
+ }
+ }
+
+ return (retstring);
+}
+
+// Tokenize string. As noted in the header, this is locally written to avoid
+// another dependency on a Boost library.
+
+vector<string>
+tokens(const std::string& text, const std::string& delim, bool escape) {
+ vector<string> result;
+ string token;
+ bool in_token = false;
+ bool escaped = false;
+ for (auto c = text.cbegin(); c != text.cend(); ++c) {
+ if (delim.find(*c) != string::npos) {
+ // Current character is a delimiter
+ if (!in_token) {
+ // Two or more delimiters, eat them
+ } else if (escaped) {
+ // Escaped delimiter in a token: reset escaped and keep it
+ escaped = false;
+ token.push_back(*c);
+ } else {
+ // End of the current token: save it if not empty
+ if (!token.empty()) {
+ result.push_back(token);
+ }
+ // Reset state
+ in_token = false;
+ token.clear();
+ }
+ } else if (escape && (*c == '\\')) {
+ // Current character is the escape character
+ if (!in_token) {
+ // The escape character is the first character of a new token
+ in_token = true;
+ }
+ if (escaped) {
+ // Escaped escape: reset escaped and keep one character
+ escaped = false;
+ token.push_back(*c);
+ } else {
+ // Remember to keep the next character
+ escaped = true;
+ }
+ } else {
+ // Not a delimiter nor an escape
+ if (!in_token) {
+ // First character of a new token
+ in_token = true;
+ }
+ if (escaped) {
+ // Escaped common character: as escape was false
+ escaped = false;
+ token.push_back('\\');
+ token.push_back(*c);
+ } else {
+ // The common case: keep it
+ token.push_back(*c);
+ }
+ }
+ }
+ // End of input: close and save the current token if not empty
+ if (escaped) {
+ // Pending escape
+ token.push_back('\\');
+ }
+ if (!token.empty()) {
+ result.push_back(token);
+ }
+
+ return (result);
+}
+
+// Local function to pass to accumulate() for summing up string lengths.
+
+namespace {
+
+size_t
+lengthSum(string::size_type curlen, const string& cur_string) {
+ return (curlen + cur_string.size());
+}
+
+}
+
+// Provide printf-style formatting.
+
+std::string
+format(const std::string& format, const std::vector<std::string>& args) {
+
+ static const string flag = "%s";
+
+ // Initialize return string. To speed things up, we'll reserve an
+ // appropriate amount of space - current string size, plus length of all
+ // the argument strings, less two characters for each argument (the %s in
+ // the format string is being replaced).
+ string result;
+ size_t length = accumulate(args.begin(), args.end(), format.size(),
+ lengthSum) - (args.size() * flag.size());
+ result.reserve(length);
+
+ // Iterate through replacing all tokens
+ result = format;
+ size_t tokenpos = 0; // Position of last token replaced
+ std::vector<std::string>::size_type i = 0; // Index into argument array
+
+ while ((i < args.size()) && (tokenpos != string::npos)) {
+ tokenpos = result.find(flag, tokenpos);
+ if (tokenpos != string::npos) {
+ result.replace(tokenpos, flag.size(), args[i++]);
+ }
+ }
+
+ return (result);
+}
+
+std::string
+getToken(std::istringstream& iss) {
+ string token;
+ iss >> token;
+ if (iss.bad() || iss.fail()) {
+ isc_throw(StringTokenError, "could not read token from string");
+ }
+ return (token);
+}
+
+std::vector<uint8_t>
+quotedStringToBinary(const std::string& quoted_string) {
+ std::vector<uint8_t> binary;
+ // Remove whitespace before and after the quotes.
+ std::string trimmed_string = trim(quoted_string);
+
+ // We require two quote characters, so the length of the string must be
+ // equal to 2 at minimum, and it must start and end with quotes.
+ if ((trimmed_string.length() > 1) && ((trimmed_string[0] == '\'') &&
+ (trimmed_string[trimmed_string.length()-1] == '\''))) {
+ // Remove quotes and trim the text inside the quotes.
+ trimmed_string = trim(trimmed_string.substr(1, trimmed_string.length() - 2));
+ // Copy string contents into the vector.
+ binary.assign(trimmed_string.begin(), trimmed_string.end());
+ }
+ // Return resulting vector or empty vector.
+ return (binary);
+}
+
+void
+decodeColonSeparatedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary) {
+ decodeSeparatedHexString(hex_string, ":", binary);
+}
+
+void
+decodeSeparatedHexString(const std::string& hex_string, const std::string& sep,
+ std::vector<uint8_t>& binary) {
+ std::vector<std::string> split_text;
+ boost::split(split_text, hex_string, boost::is_any_of(sep),
+ boost::algorithm::token_compress_off);
+
+ std::vector<uint8_t> binary_vec;
+ for (size_t i = 0; i < split_text.size(); ++i) {
+
+ // If there are multiple tokens and the current one is empty, it
+ // means that two consecutive colons were specified. This is not
+ // allowed.
+ if ((split_text.size() > 1) && split_text[i].empty()) {
+ isc_throw(isc::BadValue, "two consecutive separators ('" << sep << "') specified in"
+ " a decoded string '" << hex_string << "'");
+
+ // Between a colon we expect at most two characters.
+ } else if (split_text[i].size() > 2) {
+ isc_throw(isc::BadValue, "invalid format of the decoded string"
+ << " '" << hex_string << "'");
+
+ } else if (!split_text[i].empty()) {
+ std::stringstream s;
+ s << "0x";
+
+ for (unsigned int j = 0; j < split_text[i].length(); ++j) {
+ // Check if we're dealing with hexadecimal digit.
+ if (!isxdigit(split_text[i][j])) {
+ isc_throw(isc::BadValue, "'" << split_text[i][j]
+ << "' is not a valid hexadecimal digit in"
+ << " decoded string '" << hex_string << "'");
+ }
+ s << split_text[i][j];
+ }
+
+ // The stream should now have one or two hexadecimal digits.
+ // Let's convert it to a number and store in a temporary
+ // vector.
+ unsigned int binary_value;
+ s >> std::hex >> binary_value;
+
+ binary_vec.push_back(static_cast<uint8_t>(binary_value));
+ }
+
+ }
+
+ // All ok, replace the data in the output vector with a result.
+ binary.swap(binary_vec);
+}
+
+
+void
+decodeFormattedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary) {
+ // If there is at least one colon we assume that the string
+ // comprises octets separated by colons (e.g. MAC address notation).
+ if (hex_string.find(':') != std::string::npos) {
+ decodeSeparatedHexString(hex_string, ":", binary);
+ } else if (hex_string.find(' ') != std::string::npos) {
+ decodeSeparatedHexString(hex_string, " ", binary);
+ } else {
+ std::ostringstream s;
+
+ // If we have odd number of digits we'll have to prepend '0'.
+ if (hex_string.length() % 2 != 0) {
+ s << "0";
+ }
+
+ // It is ok to use '0x' prefix in a string.
+ if ((hex_string.length() > 2) && (hex_string.substr(0, 2) == "0x")) {
+ // Exclude '0x' from the decoded string.
+ s << hex_string.substr(2);
+
+ } else {
+ // No '0x', so decode the whole string.
+ s << hex_string;
+ }
+
+ try {
+ // Decode the hex string.
+ encode::decodeHex(s.str(), binary);
+
+ } catch (...) {
+ isc_throw(isc::BadValue, "'" << hex_string << "' is not a valid"
+ " string of hexadecimal digits");
+ }
+ }
+}
+
+class StringSanitizerImpl {
+public:
+ /// @brief Constructor.
+ StringSanitizerImpl(const std::string& char_set, const std::string& char_replacement)
+ : char_set_(char_set), char_replacement_(char_replacement) {
+ if (char_set.size() > StringSanitizer::MAX_DATA_SIZE) {
+ isc_throw(isc::BadValue, "char set size: '" << char_set.size()
+ << "' exceeds max size: '"
+ << StringSanitizer::MAX_DATA_SIZE << "'");
+ }
+
+ if (char_replacement.size() > StringSanitizer::MAX_DATA_SIZE) {
+ isc_throw(isc::BadValue, "char replacement size: '"
+ << char_replacement.size() << "' exceeds max size: '"
+ << StringSanitizer::MAX_DATA_SIZE << "'");
+ }
+#ifdef USE_REGEX
+ try {
+ scrub_exp_ = std::regex(char_set, std::regex::extended);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "invalid regex: '"
+ << char_set_ << "', " << ex.what());
+ }
+#else
+ int ec = regcomp(&scrub_exp_, char_set_.c_str(), REG_EXTENDED);
+ if (ec) {
+ char errbuf[512] = "";
+ static_cast<void>(regerror(ec, &scrub_exp_, errbuf, sizeof(errbuf)));
+ regfree(&scrub_exp_);
+ isc_throw(isc::BadValue, "invalid regex: '" << char_set_ << "', " << errbuf);
+ }
+#endif
+ }
+
+ /// @brief Destructor.
+ ~StringSanitizerImpl() {
+#ifndef USE_REGEX
+ regfree(&scrub_exp_);
+#endif
+ }
+
+ std::string scrub(const std::string& original) {
+#ifdef USE_REGEX
+ std::stringstream result;
+ try {
+ std::regex_replace(std::ostream_iterator<char>(result),
+ original.begin(), original.end(),
+ scrub_exp_, char_replacement_);
+ } catch (const std::exception& ex) {
+ isc_throw(isc::BadValue, "replacing '" << char_set_ << "' with '"
+ << char_replacement_ << "' in '" << original << "' failed: ,"
+ << ex.what());
+ }
+
+ return (result.str());
+#else
+ // In order to handle embedded nuls, we have to process in nul-terminated
+ // chunks. We iterate over the original data, doing pattern replacement
+ // on each chunk.
+ const char* orig_data = original.data();
+ const char* dead_end = orig_data + original.size();
+ const char* start_from = orig_data;
+ stringstream result;
+
+ while (start_from < dead_end) {
+ // Iterate over original string, match by match.
+ regmatch_t matches[2]; // n matches + 1
+ const char* end_at = start_from + strlen(start_from);
+
+ while (start_from < end_at) {
+ // Look for the next match
+ if (regexec(&scrub_exp_, start_from, 1, matches, 0) == REG_NOMATCH) {
+ // No matches, so add in the remainder
+ result << start_from;
+ start_from = end_at + 1;
+ break;
+ }
+
+ // Shouldn't happen, but one never knows eh?
+ if (matches[0].rm_so == -1) {
+ isc_throw(isc::Unexpected, "matched but so is -1?");
+ }
+
+ // Add everything from starting point up to the current match
+ const char* match_at = start_from + matches[0].rm_so;
+ while (start_from < match_at) {
+ result << *start_from;
+ ++start_from;
+ }
+
+ // Add in the replacement
+ result << char_replacement_;
+
+ // Move past the match.
+ ++start_from;
+ }
+
+ // if we have an embedded nul, replace it and continue
+ if (start_from < dead_end) {
+ // Add in the replacement
+ result << char_replacement_;
+ start_from = end_at + 1;
+ }
+ }
+
+ return (result.str());
+#endif
+ }
+
+private:
+ /// @brief The char set data for regex.
+ std::string char_set_;
+
+ /// @brief The char replacement data for regex.
+ std::string char_replacement_;
+
+#ifdef USE_REGEX
+ regex scrub_exp_;
+#else
+ regex_t scrub_exp_;
+#endif
+};
+
+// @note The regex engine is implemented using recursion and can cause
+// stack overflow if the input data is too large. An arbitrary size of
+// 4096 should be enough for all cases.
+const uint32_t StringSanitizer::MAX_DATA_SIZE = 4096;
+
+StringSanitizer::StringSanitizer(const std::string& char_set,
+ const std::string& char_replacement)
+ : impl_(new StringSanitizerImpl(char_set, char_replacement)) {
+}
+
+StringSanitizer::~StringSanitizer() {
+}
+
+std::string
+StringSanitizer::scrub(const std::string& original) {
+ return (impl_->scrub(original));
+}
+
+std::string dumpAsHex(const uint8_t* data, size_t length) {
+ std::stringstream output;
+ for (unsigned int i = 0; i < length; i++) {
+ if (i) {
+ output << ":";
+ }
+
+ output << std::setfill('0') << std::setw(2) << std::hex
+ << static_cast<unsigned short>(data[i]);
+ }
+
+ return (output.str());
+}
+
+} // namespace str
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/strutil.h b/src/lib/util/strutil.h
new file mode 100644
index 0000000..e5d2496
--- /dev/null
+++ b/src/lib/util/strutil.h
@@ -0,0 +1,403 @@
+// Copyright (C) 2011-2022 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 STRUTIL_H
+#define STRUTIL_H
+
+#include <algorithm>
+#include <cctype>
+#include <stdint.h>
+#include <string>
+#include <iomanip>
+#include <sstream>
+#include <vector>
+#include <exceptions/exceptions.h>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+namespace str {
+
+/// @brief A Set of C++ Utilities for Manipulating Strings
+
+///
+/// @brief A standard string util exception that is thrown if getToken or
+/// numToToken are called with bad input data
+///
+class StringTokenError : public Exception {
+public:
+ StringTokenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Normalize Backslash
+///
+/// Only relevant to Windows, this replaces all "\" in a string with "/"
+/// and returns the result. On other systems it is a no-op. Note
+/// that Windows does recognize file names with the "\" replaced by "/"
+/// (at least in system calls, if not the command line).
+///
+/// @param name Name to be substituted
+void normalizeSlash(std::string& name);
+
+/// @brief Trim Leading and Trailing Spaces
+///
+/// Returns a copy of the input string but with any leading or trailing spaces
+/// or tabs removed.
+///
+/// @param instring Input string to modify
+///
+/// @return String with leading and trailing spaces removed
+std::string trim(const std::string& instring);
+
+/// @brief Finds the "trimmed" end of a buffer
+///
+/// Works backward from the end of the buffer, looking for the first
+/// character not equal to the trim value, and returns an iterator
+/// pointing that that position.
+///
+/// @param begin - Forward iterator pointing to the beginning of the
+/// buffer to trim
+/// @param end - Forward iterator pointing to the untrimmed end of
+/// the buffer to trim
+/// @param trim_val - byte value to trim off
+///
+/// @return Iterator pointing the first character from the end of the
+/// buffer not equal to the trim value
+template<typename Iterator>
+Iterator
+seekTrimmed(Iterator begin, Iterator end, uint8_t trim_val) {
+ for ( ; end != begin && *(end - 1) == trim_val; --end);
+ return(end);
+}
+
+/// @brief Split String into Tokens
+///
+/// Splits a string into tokens (the tokens being delimited by one or more of
+/// the delimiter characters) and returns the tokens in a vector array. Note
+/// that adjacent delimiters are considered to be a single delimiter.
+///
+/// Special cases are:
+/// -# The empty string is considered to be zero tokens.
+/// -# A string comprising nothing but delimiters is considered to be zero
+/// tokens.
+///
+/// The reasoning behind this is that the string can be thought of as having
+/// invisible leading and trailing delimiter characters. Therefore both cases
+/// reduce to a set of contiguous delimiters, which are considered a single
+/// delimiter (so getting rid of the string).
+/// Optional escape allows to escape delimiter characters (and *only* them
+/// and the escape character itself) using backslash.
+///
+/// We could use Boost for this, but this (simple) function eliminates one
+/// dependency in the code.
+///
+/// @param text String to be split. Passed by value as the internal copy is
+/// altered during the processing.
+/// @param delim Delimiter characters
+/// @param escape Use backslash to escape delimiter characters
+///
+/// @return Vector of tokens.
+std::vector<std::string> tokens(const std::string& text,
+ const std::string& delim = std::string(" \t\n"),
+ bool escape = false);
+
+/// @brief Uppercase Character
+///
+/// Used in uppercase() to pass as an argument to std::transform(). The
+/// function std::toupper() can't be used as it takes an "int" as its argument;
+/// this confuses the template expansion mechanism because dereferencing a
+/// string::iterator returns a char.
+///
+/// @param chr Character to be upper-cased.
+///
+/// @return Uppercase version of the argument
+inline char toUpper(char chr) {
+ return (static_cast<char>(std::toupper(static_cast<int>(chr))));
+}
+
+/// @brief Uppercase String
+///
+/// A convenience function to uppercase a string.
+///
+/// @param text String to be upper-cased.
+inline void uppercase(std::string& text) {
+ std::transform(text.begin(), text.end(), text.begin(),
+ isc::util::str::toUpper);
+}
+
+/// @brief Lowercase Character
+///
+/// Used in lowercase() to pass as an argument to std::transform(). The
+/// function std::tolower() can't be used as it takes an "int" as its argument;
+/// this confuses the template expansion mechanism because dereferencing a
+/// string::iterator returns a char.
+///
+/// @param chr Character to be lower-cased.
+///
+/// @return Lowercase version of the argument
+inline char toLower(char chr) {
+ return (static_cast<char>(std::tolower(static_cast<int>(chr))));
+}
+
+/// @brief Lowercase String
+///
+/// A convenience function to lowercase a string
+///
+/// @param text String to be lower-cased.
+inline void lowercase(std::string& text) {
+ std::transform(text.begin(), text.end(), text.begin(),
+ isc::util::str::toLower);
+}
+
+/// @brief Apply Formatting
+///
+/// Given a printf-style format string containing only "%s" place holders
+/// (others are ignored) and a vector of strings, this produces a single string
+/// with the placeholders replaced.
+///
+/// @param format Format string
+/// @param args Vector of argument strings
+///
+/// @return Resultant string
+std::string format(const std::string& format,
+ const std::vector<std::string>& args);
+
+
+/// @brief Returns one token from the given stringstream
+///
+/// Using the >> operator, with basic error checking
+///
+/// @throw StringTokenError if the token cannot be read from the stream
+///
+/// @param iss stringstream to read one token from
+///
+/// @return the first token read from the stringstream
+std::string getToken(std::istringstream& iss);
+
+/// @brief Converts a string token to an *unsigned* integer.
+///
+/// The value is converted using a lexical cast, with error and bounds
+/// checking.
+///
+/// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
+/// wide to store resulting integers.
+///
+/// BitSize is the maximum number of bits that the resulting integer can take.
+/// This function first checks whether the given token can be converted to
+/// an integer of NumType type. It then confirms the conversion result is
+/// within the valid range, i.e., [0, 2^BitSize - 1]. The second check is
+/// necessary because lexical_cast<T> where T is an unsigned integer type
+/// doesn't correctly reject negative numbers when compiled with SunStudio.
+///
+/// @throw StringTokenError if the value is out of range, or if it
+/// could not be converted
+///
+/// @param num_token the string token to convert
+///
+/// @return the converted value, of type NumType
+template <typename NumType, int BitSize>
+NumType
+tokenToNum(const std::string& num_token) {
+ NumType num;
+ try {
+ num = boost::lexical_cast<NumType>(num_token);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(StringTokenError, "Invalid SRV numeric parameter: " <<
+ num_token);
+ }
+ if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
+ isc_throw(StringTokenError, "Numeric SRV parameter out of range: " <<
+ num);
+ }
+ return (num);
+}
+
+/// @brief Converts a string in quotes into vector.
+///
+/// A converted string is first trimmed. If a trimmed string is in
+/// quotes, the quotes are removed and the resulting string is copied
+/// into a vector. If the string is not in quotes, an empty vector is
+/// returned.
+///
+/// The resulting string is copied to a vector and returned.
+///
+/// This function is intended to be used by the server configuration
+/// parsers to convert string values surrounded with quotes into
+/// binary form.
+///
+/// @param quoted_string String to be converted.
+///
+/// @return Vector containing converted string or empty string if
+/// input string didn't contain expected quote characters.
+std::vector<uint8_t>
+quotedStringToBinary(const std::string& quoted_string);
+
+/// @brief Converts a string of separated hexadecimal digits
+/// into a vector.
+///
+/// Octets may contain 1 or 2 digits. For example, using a colon
+/// for a separator all of the following are valid:
+///
+/// - yy:yy:yy:yy:yy
+/// - y:y:y:y:y
+/// - y:yy:yy:y:y
+///
+/// If the decoded string doesn't match any of the supported formats,
+/// an exception is thrown.
+///
+/// @param hex_string Input string.
+/// @param sep character to use as a separator.
+/// @param binary Vector receiving converted string into binary.
+///
+/// @throw isc::BadValue if the format of the input string is invalid.
+void
+decodeSeparatedHexString(const std::string& hex_string,
+ const std::string& sep,
+ std::vector<uint8_t>& binary);
+
+/// @brief Converts a string of hexadecimal digits with colons into
+/// a vector.
+///
+/// Convenience method which calls @c decodeSeparatedHexString() passing
+/// in a colon for the separator.
+
+/// @param hex_string Input string.
+/// @param binary Vector receiving converted string into binary.
+///
+/// @throw isc::BadValue if the format of the input string is invalid.
+void
+decodeColonSeparatedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary);
+
+/// @brief Converts a formatted string of hexadecimal digits into
+/// a vector.
+///
+/// This function supports the following formats:
+///
+/// - yy:yy:yy:yy or yy yy yy yy - octets delimited by colons or
+/// spaces, see @c decodeSeparatedHexString
+///
+/// - yyyyyyyyyy
+/// - 0xyyyyyyyyyy
+///
+/// If there is an odd number of hexadecimal digits in the input
+/// string, the '0' is prepended to the string before decoding.
+///
+/// @param hex_string Input string.
+/// @param binary Vector receiving converted string into binary.
+///
+/// @throw isc::BadValue if the format of the input string is invalid.
+void
+decodeFormattedHexString(const std::string& hex_string,
+ std::vector<uint8_t>& binary);
+
+/// @brief Forward declaration to the @c StringSanitizer implementation.
+class StringSanitizerImpl;
+
+/// @brief Type representing the pointer to the @c StringSanitizerImpl.
+typedef boost::shared_ptr<StringSanitizerImpl> StringSanitizerImplPtr;
+
+/// @brief Implements a regular expression based string scrubber
+///
+/// The implementation uses C++11 regex IF the environment supports it
+/// (tested in configure.ac). If not it falls back to C lib regcomp/regexec.
+/// Older compilers, such as pre Gnu g++ 4.9.0, provided only experimental
+/// implementations of regex which are recognized as buggy.
+class StringSanitizer {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Compiles the given character set into a regular expression, and
+ /// retains the given character replacement. Thereafter, the instance
+ /// may be used to scrub an arbitrary number of strings.
+ ///
+ /// @param char_set string containing a regular expression (POSIX
+ /// extended syntax) that describes the characters to replace. If you
+ /// wanted to sanitize hostnames for example, you could specify the
+ /// inversion of valid characters "[^A-Za-z0-9_-]".
+ /// @param char_replacement string of one or more characters to use as the
+ /// replacement for invalid characters.
+ ///
+ /// @throw BadValue if given an invalid regular expression
+ StringSanitizer(const std::string& char_set,
+ const std::string& char_replacement);
+
+ /// @brief Destructor.
+ ///
+ /// Destroys the implementation instance.
+ ~StringSanitizer();
+
+ /// Returns a scrubbed copy of a given string
+ ///
+ /// Replaces all occurrences of characters described by the regular
+ /// expression with the character replacement.
+ ///
+ /// @param original the string to scrub
+ ///
+ /// @throw Unexpected if an error occurs during scrubbing
+ std::string scrub(const std::string& original);
+
+ /// @brief The maximum size for regex parameters.
+ ///
+ /// @note The regex engine is implemented using recursion and can cause
+ /// stack overflow if the input data is too large. An arbitrary size of
+ /// 4096 should be enough for all cases.
+ static const uint32_t MAX_DATA_SIZE;
+
+private:
+ /// @brief Pointer to the @c StringSanitizerImpl.
+ StringSanitizerImplPtr impl_;
+};
+
+/// @brief Type representing the pointer to the @c StringSanitizer.
+typedef boost::shared_ptr<StringSanitizer> StringSanitizerPtr;
+
+/// @brief Check if a string is printable
+///
+/// @param content String to check for printable characters
+///
+/// @return True if empty or contains only printable characters, False otherwise
+inline bool
+isPrintable(const std::string& content) {
+ for (const auto& ch : content) {
+ if (isprint(static_cast<int>(ch)) == 0) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+/// @brief Check if a byte vector is printable
+///
+/// @param content Vector to check for printable characters
+///
+/// @return True if empty or contains only printable characters, False otherwise
+inline bool
+isPrintable(const std::vector<uint8_t>& content) {
+ for (const auto& ch : content) {
+ if (isprint(static_cast<int>(ch)) == 0) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+
+/// @brief Dumps a buffer of bytes as a string of hexadecimal digits
+///
+/// @param data pointer to the data to dump
+/// @param length number of bytes to dump. Caller should ensure the length
+/// does not exceed the buffer.
+std::string dumpAsHex(const uint8_t* data, size_t length);
+
+} // namespace str
+} // namespace util
+} // namespace isc
+
+#endif // STRUTIL_H
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
new file mode 100644
index 0000000..5f8b1e8
--- /dev/null
+++ b/src/lib/util/tests/Makefile.am
@@ -0,0 +1,74 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\"
+# XXX: we'll pollute the top builddir for creating a temporary test file
+# used to bind a UNIX domain socket so we can minimize the risk of exceeding
+# the limit of file name path size.
+AM_CPPFLAGS += -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+# CSV files are created by unit tests for CSVFile class.
+CLEANFILES += *.csv
+
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += bigint_unittest.cc
+run_unittests_SOURCES += base32hex_unittest.cc
+run_unittests_SOURCES += base64_unittest.cc
+run_unittests_SOURCES += boost_time_utils_unittest.cc
+run_unittests_SOURCES += buffer_unittest.cc
+run_unittests_SOURCES += chrono_time_utils_unittest.cc
+run_unittests_SOURCES += csv_file_unittest.cc
+run_unittests_SOURCES += dhcp_space_unittest.cc
+run_unittests_SOURCES += doubles_unittest.cc
+run_unittests_SOURCES += fd_share_tests.cc
+run_unittests_SOURCES += fd_tests.cc
+run_unittests_SOURCES += file_utilities_unittest.cc
+run_unittests_SOURCES += filename_unittest.cc
+run_unittests_SOURCES += hash_unittest.cc
+run_unittests_SOURCES += hex_unittest.cc
+run_unittests_SOURCES += io_utilities_unittest.cc
+run_unittests_SOURCES += labeled_value_unittest.cc
+run_unittests_SOURCES += memory_segment_local_unittest.cc
+run_unittests_SOURCES += memory_segment_common_unittest.h
+run_unittests_SOURCES += memory_segment_common_unittest.cc
+run_unittests_SOURCES += multi_threading_mgr_unittest.cc
+run_unittests_SOURCES += optional_unittest.cc
+run_unittests_SOURCES += pid_file_unittest.cc
+run_unittests_SOURCES += staged_value_unittest.cc
+run_unittests_SOURCES += state_model_unittest.cc
+run_unittests_SOURCES += strutil_unittest.cc
+run_unittests_SOURCES += thread_pool_unittest.cc
+run_unittests_SOURCES += time_utilities_unittest.cc
+run_unittests_SOURCES += triplet_unittest.cc
+run_unittests_SOURCES += range_utilities_unittest.cc
+run_unittests_SOURCES += readwrite_mutex_unittest.cc
+run_unittests_SOURCES += stopwatch_unittest.cc
+run_unittests_SOURCES += unlock_guard_unittests.cc
+run_unittests_SOURCES += utf8_unittest.cc
+run_unittests_SOURCES += versioned_csv_file_unittest.cc
+run_unittests_SOURCES += watch_socket_unittests.cc
+run_unittests_SOURCES += watched_thread_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.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 += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/util/tests/Makefile.in b/src/lib/util/tests/Makefile.in
new file mode 100644
index 0000000..e2f6051
--- /dev/null
+++ b/src/lib/util/tests/Makefile.in
@@ -0,0 +1,1730 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/util/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = run_unittests.cc bigint_unittest.cc \
+ base32hex_unittest.cc base64_unittest.cc \
+ boost_time_utils_unittest.cc buffer_unittest.cc \
+ chrono_time_utils_unittest.cc csv_file_unittest.cc \
+ dhcp_space_unittest.cc doubles_unittest.cc fd_share_tests.cc \
+ fd_tests.cc file_utilities_unittest.cc filename_unittest.cc \
+ hash_unittest.cc hex_unittest.cc io_utilities_unittest.cc \
+ labeled_value_unittest.cc memory_segment_local_unittest.cc \
+ memory_segment_common_unittest.h \
+ memory_segment_common_unittest.cc \
+ multi_threading_mgr_unittest.cc optional_unittest.cc \
+ pid_file_unittest.cc staged_value_unittest.cc \
+ state_model_unittest.cc strutil_unittest.cc \
+ thread_pool_unittest.cc time_utilities_unittest.cc \
+ triplet_unittest.cc range_utilities_unittest.cc \
+ readwrite_mutex_unittest.cc stopwatch_unittest.cc \
+ unlock_guard_unittests.cc utf8_unittest.cc \
+ versioned_csv_file_unittest.cc watch_socket_unittests.cc \
+ watched_thread_unittest.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-bigint_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-base32hex_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-base64_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-boost_time_utils_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-buffer_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-chrono_time_utils_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-csv_file_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-dhcp_space_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-doubles_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-fd_share_tests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-fd_tests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-file_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-filename_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hash_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-hex_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-io_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-labeled_value_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-memory_segment_local_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-memory_segment_common_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-multi_threading_mgr_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-optional_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-pid_file_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-staged_value_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-state_model_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-strutil_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-thread_pool_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-time_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-triplet_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-range_utilities_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-readwrite_mutex_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-stopwatch_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-unlock_guard_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-utf8_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-versioned_csv_file_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-watch_socket_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-watched_thread_unittest.$(OBJEXT)
+run_unittests_OBJECTS = $(am_run_unittests_OBJECTS)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/io/libkea-util-io.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_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-base32hex_unittest.Po \
+ ./$(DEPDIR)/run_unittests-base64_unittest.Po \
+ ./$(DEPDIR)/run_unittests-bigint_unittest.Po \
+ ./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po \
+ ./$(DEPDIR)/run_unittests-buffer_unittest.Po \
+ ./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po \
+ ./$(DEPDIR)/run_unittests-csv_file_unittest.Po \
+ ./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po \
+ ./$(DEPDIR)/run_unittests-doubles_unittest.Po \
+ ./$(DEPDIR)/run_unittests-fd_share_tests.Po \
+ ./$(DEPDIR)/run_unittests-fd_tests.Po \
+ ./$(DEPDIR)/run_unittests-file_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-filename_unittest.Po \
+ ./$(DEPDIR)/run_unittests-hash_unittest.Po \
+ ./$(DEPDIR)/run_unittests-hex_unittest.Po \
+ ./$(DEPDIR)/run_unittests-io_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-labeled_value_unittest.Po \
+ ./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po \
+ ./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po \
+ ./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po \
+ ./$(DEPDIR)/run_unittests-optional_unittest.Po \
+ ./$(DEPDIR)/run_unittests-pid_file_unittest.Po \
+ ./$(DEPDIR)/run_unittests-range_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-staged_value_unittest.Po \
+ ./$(DEPDIR)/run_unittests-state_model_unittest.Po \
+ ./$(DEPDIR)/run_unittests-stopwatch_unittest.Po \
+ ./$(DEPDIR)/run_unittests-strutil_unittest.Po \
+ ./$(DEPDIR)/run_unittests-thread_pool_unittest.Po \
+ ./$(DEPDIR)/run_unittests-time_utilities_unittest.Po \
+ ./$(DEPDIR)/run_unittests-triplet_unittest.Po \
+ ./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po \
+ ./$(DEPDIR)/run_unittests-utf8_unittest.Po \
+ ./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po \
+ ./$(DEPDIR)/run_unittests-watch_socket_unittests.Po \
+ ./$(DEPDIR)/run_unittests-watched_thread_unittest.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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+# XXX: we'll pollute the top builddir for creating a temporary test file
+# used to bind a UNIX domain socket so we can minimize the risk of exceeding
+# the limit of file name path size.
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\" \
+ -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\"
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+# CSV files are created by unit tests for CSVFile class.
+CLEANFILES = *.gcno *.gcda *.csv
+TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+@HAVE_GTEST_TRUE@run_unittests_SOURCES = run_unittests.cc \
+@HAVE_GTEST_TRUE@ bigint_unittest.cc base32hex_unittest.cc \
+@HAVE_GTEST_TRUE@ base64_unittest.cc \
+@HAVE_GTEST_TRUE@ boost_time_utils_unittest.cc \
+@HAVE_GTEST_TRUE@ buffer_unittest.cc \
+@HAVE_GTEST_TRUE@ chrono_time_utils_unittest.cc \
+@HAVE_GTEST_TRUE@ csv_file_unittest.cc dhcp_space_unittest.cc \
+@HAVE_GTEST_TRUE@ doubles_unittest.cc fd_share_tests.cc \
+@HAVE_GTEST_TRUE@ fd_tests.cc file_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ filename_unittest.cc hash_unittest.cc \
+@HAVE_GTEST_TRUE@ hex_unittest.cc io_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ labeled_value_unittest.cc \
+@HAVE_GTEST_TRUE@ memory_segment_local_unittest.cc \
+@HAVE_GTEST_TRUE@ memory_segment_common_unittest.h \
+@HAVE_GTEST_TRUE@ memory_segment_common_unittest.cc \
+@HAVE_GTEST_TRUE@ multi_threading_mgr_unittest.cc \
+@HAVE_GTEST_TRUE@ optional_unittest.cc pid_file_unittest.cc \
+@HAVE_GTEST_TRUE@ staged_value_unittest.cc \
+@HAVE_GTEST_TRUE@ state_model_unittest.cc strutil_unittest.cc \
+@HAVE_GTEST_TRUE@ thread_pool_unittest.cc \
+@HAVE_GTEST_TRUE@ time_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ triplet_unittest.cc \
+@HAVE_GTEST_TRUE@ range_utilities_unittest.cc \
+@HAVE_GTEST_TRUE@ readwrite_mutex_unittest.cc \
+@HAVE_GTEST_TRUE@ stopwatch_unittest.cc \
+@HAVE_GTEST_TRUE@ unlock_guard_unittests.cc utf8_unittest.cc \
+@HAVE_GTEST_TRUE@ versioned_csv_file_unittest.cc \
+@HAVE_GTEST_TRUE@ watch_socket_unittests.cc \
+@HAVE_GTEST_TRUE@ watched_thread_unittest.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/io/libkea-util-io.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@ $(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/util/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/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-base32hex_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-base64_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-bigint_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-buffer_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-csv_file_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-doubles_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-fd_share_tests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-fd_tests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-file_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-filename_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hash_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-hex_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-io_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-labeled_value_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-optional_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-pid_file_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-range_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-readwrite_mutex_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-staged_value_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-state_model_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-stopwatch_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-strutil_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-thread_pool_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-time_utilities_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-triplet_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-utf8_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-watch_socket_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-watched_thread_unittest.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-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`
+
+run_unittests-bigint_unittest.o: bigint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-bigint_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-bigint_unittest.Tpo -c -o run_unittests-bigint_unittest.o `test -f 'bigint_unittest.cc' || echo '$(srcdir)/'`bigint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-bigint_unittest.Tpo $(DEPDIR)/run_unittests-bigint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bigint_unittest.cc' object='run_unittests-bigint_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-bigint_unittest.o `test -f 'bigint_unittest.cc' || echo '$(srcdir)/'`bigint_unittest.cc
+
+run_unittests-bigint_unittest.obj: bigint_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-bigint_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-bigint_unittest.Tpo -c -o run_unittests-bigint_unittest.obj `if test -f 'bigint_unittest.cc'; then $(CYGPATH_W) 'bigint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/bigint_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-bigint_unittest.Tpo $(DEPDIR)/run_unittests-bigint_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='bigint_unittest.cc' object='run_unittests-bigint_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-bigint_unittest.obj `if test -f 'bigint_unittest.cc'; then $(CYGPATH_W) 'bigint_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/bigint_unittest.cc'; fi`
+
+run_unittests-base32hex_unittest.o: base32hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base32hex_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-base32hex_unittest.Tpo -c -o run_unittests-base32hex_unittest.o `test -f 'base32hex_unittest.cc' || echo '$(srcdir)/'`base32hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base32hex_unittest.Tpo $(DEPDIR)/run_unittests-base32hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base32hex_unittest.cc' object='run_unittests-base32hex_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-base32hex_unittest.o `test -f 'base32hex_unittest.cc' || echo '$(srcdir)/'`base32hex_unittest.cc
+
+run_unittests-base32hex_unittest.obj: base32hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base32hex_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-base32hex_unittest.Tpo -c -o run_unittests-base32hex_unittest.obj `if test -f 'base32hex_unittest.cc'; then $(CYGPATH_W) 'base32hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base32hex_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base32hex_unittest.Tpo $(DEPDIR)/run_unittests-base32hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base32hex_unittest.cc' object='run_unittests-base32hex_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-base32hex_unittest.obj `if test -f 'base32hex_unittest.cc'; then $(CYGPATH_W) 'base32hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base32hex_unittest.cc'; fi`
+
+run_unittests-base64_unittest.o: base64_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base64_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-base64_unittest.Tpo -c -o run_unittests-base64_unittest.o `test -f 'base64_unittest.cc' || echo '$(srcdir)/'`base64_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base64_unittest.Tpo $(DEPDIR)/run_unittests-base64_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base64_unittest.cc' object='run_unittests-base64_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-base64_unittest.o `test -f 'base64_unittest.cc' || echo '$(srcdir)/'`base64_unittest.cc
+
+run_unittests-base64_unittest.obj: base64_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-base64_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-base64_unittest.Tpo -c -o run_unittests-base64_unittest.obj `if test -f 'base64_unittest.cc'; then $(CYGPATH_W) 'base64_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base64_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-base64_unittest.Tpo $(DEPDIR)/run_unittests-base64_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='base64_unittest.cc' object='run_unittests-base64_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-base64_unittest.obj `if test -f 'base64_unittest.cc'; then $(CYGPATH_W) 'base64_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/base64_unittest.cc'; fi`
+
+run_unittests-boost_time_utils_unittest.o: boost_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-boost_time_utils_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo -c -o run_unittests-boost_time_utils_unittest.o `test -f 'boost_time_utils_unittest.cc' || echo '$(srcdir)/'`boost_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boost_time_utils_unittest.cc' object='run_unittests-boost_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-boost_time_utils_unittest.o `test -f 'boost_time_utils_unittest.cc' || echo '$(srcdir)/'`boost_time_utils_unittest.cc
+
+run_unittests-boost_time_utils_unittest.obj: boost_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-boost_time_utils_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo -c -o run_unittests-boost_time_utils_unittest.obj `if test -f 'boost_time_utils_unittest.cc'; then $(CYGPATH_W) 'boost_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boost_time_utils_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-boost_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boost_time_utils_unittest.cc' object='run_unittests-boost_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-boost_time_utils_unittest.obj `if test -f 'boost_time_utils_unittest.cc'; then $(CYGPATH_W) 'boost_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boost_time_utils_unittest.cc'; fi`
+
+run_unittests-buffer_unittest.o: buffer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-buffer_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-buffer_unittest.Tpo -c -o run_unittests-buffer_unittest.o `test -f 'buffer_unittest.cc' || echo '$(srcdir)/'`buffer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-buffer_unittest.Tpo $(DEPDIR)/run_unittests-buffer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_unittest.cc' object='run_unittests-buffer_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-buffer_unittest.o `test -f 'buffer_unittest.cc' || echo '$(srcdir)/'`buffer_unittest.cc
+
+run_unittests-buffer_unittest.obj: buffer_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-buffer_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-buffer_unittest.Tpo -c -o run_unittests-buffer_unittest.obj `if test -f 'buffer_unittest.cc'; then $(CYGPATH_W) 'buffer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-buffer_unittest.Tpo $(DEPDIR)/run_unittests-buffer_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='buffer_unittest.cc' object='run_unittests-buffer_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-buffer_unittest.obj `if test -f 'buffer_unittest.cc'; then $(CYGPATH_W) 'buffer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/buffer_unittest.cc'; fi`
+
+run_unittests-chrono_time_utils_unittest.o: chrono_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-chrono_time_utils_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo -c -o run_unittests-chrono_time_utils_unittest.o `test -f 'chrono_time_utils_unittest.cc' || echo '$(srcdir)/'`chrono_time_utils_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='chrono_time_utils_unittest.cc' object='run_unittests-chrono_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-chrono_time_utils_unittest.o `test -f 'chrono_time_utils_unittest.cc' || echo '$(srcdir)/'`chrono_time_utils_unittest.cc
+
+run_unittests-chrono_time_utils_unittest.obj: chrono_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-chrono_time_utils_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo -c -o run_unittests-chrono_time_utils_unittest.obj `if test -f 'chrono_time_utils_unittest.cc'; then $(CYGPATH_W) 'chrono_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/chrono_time_utils_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Tpo $(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='chrono_time_utils_unittest.cc' object='run_unittests-chrono_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-chrono_time_utils_unittest.obj `if test -f 'chrono_time_utils_unittest.cc'; then $(CYGPATH_W) 'chrono_time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/chrono_time_utils_unittest.cc'; fi`
+
+run_unittests-csv_file_unittest.o: csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-csv_file_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-csv_file_unittest.Tpo -c -o run_unittests-csv_file_unittest.o `test -f 'csv_file_unittest.cc' || echo '$(srcdir)/'`csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-csv_file_unittest.Tpo $(DEPDIR)/run_unittests-csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_file_unittest.cc' object='run_unittests-csv_file_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-csv_file_unittest.o `test -f 'csv_file_unittest.cc' || echo '$(srcdir)/'`csv_file_unittest.cc
+
+run_unittests-csv_file_unittest.obj: csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-csv_file_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-csv_file_unittest.Tpo -c -o run_unittests-csv_file_unittest.obj `if test -f 'csv_file_unittest.cc'; then $(CYGPATH_W) 'csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_file_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-csv_file_unittest.Tpo $(DEPDIR)/run_unittests-csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='csv_file_unittest.cc' object='run_unittests-csv_file_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-csv_file_unittest.obj `if test -f 'csv_file_unittest.cc'; then $(CYGPATH_W) 'csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/csv_file_unittest.cc'; fi`
+
+run_unittests-dhcp_space_unittest.o: dhcp_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dhcp_space_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo -c -o run_unittests-dhcp_space_unittest.o `test -f 'dhcp_space_unittest.cc' || echo '$(srcdir)/'`dhcp_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo $(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_space_unittest.cc' object='run_unittests-dhcp_space_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-dhcp_space_unittest.o `test -f 'dhcp_space_unittest.cc' || echo '$(srcdir)/'`dhcp_space_unittest.cc
+
+run_unittests-dhcp_space_unittest.obj: dhcp_space_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dhcp_space_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo -c -o run_unittests-dhcp_space_unittest.obj `if test -f 'dhcp_space_unittest.cc'; then $(CYGPATH_W) 'dhcp_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_space_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dhcp_space_unittest.Tpo $(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dhcp_space_unittest.cc' object='run_unittests-dhcp_space_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-dhcp_space_unittest.obj `if test -f 'dhcp_space_unittest.cc'; then $(CYGPATH_W) 'dhcp_space_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dhcp_space_unittest.cc'; fi`
+
+run_unittests-doubles_unittest.o: doubles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-doubles_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-doubles_unittest.Tpo -c -o run_unittests-doubles_unittest.o `test -f 'doubles_unittest.cc' || echo '$(srcdir)/'`doubles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-doubles_unittest.Tpo $(DEPDIR)/run_unittests-doubles_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='doubles_unittest.cc' object='run_unittests-doubles_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-doubles_unittest.o `test -f 'doubles_unittest.cc' || echo '$(srcdir)/'`doubles_unittest.cc
+
+run_unittests-doubles_unittest.obj: doubles_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-doubles_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-doubles_unittest.Tpo -c -o run_unittests-doubles_unittest.obj `if test -f 'doubles_unittest.cc'; then $(CYGPATH_W) 'doubles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/doubles_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-doubles_unittest.Tpo $(DEPDIR)/run_unittests-doubles_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='doubles_unittest.cc' object='run_unittests-doubles_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-doubles_unittest.obj `if test -f 'doubles_unittest.cc'; then $(CYGPATH_W) 'doubles_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/doubles_unittest.cc'; fi`
+
+run_unittests-fd_share_tests.o: fd_share_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_share_tests.o -MD -MP -MF $(DEPDIR)/run_unittests-fd_share_tests.Tpo -c -o run_unittests-fd_share_tests.o `test -f 'fd_share_tests.cc' || echo '$(srcdir)/'`fd_share_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_share_tests.Tpo $(DEPDIR)/run_unittests-fd_share_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_share_tests.cc' object='run_unittests-fd_share_tests.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-fd_share_tests.o `test -f 'fd_share_tests.cc' || echo '$(srcdir)/'`fd_share_tests.cc
+
+run_unittests-fd_share_tests.obj: fd_share_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_share_tests.obj -MD -MP -MF $(DEPDIR)/run_unittests-fd_share_tests.Tpo -c -o run_unittests-fd_share_tests.obj `if test -f 'fd_share_tests.cc'; then $(CYGPATH_W) 'fd_share_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_share_tests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_share_tests.Tpo $(DEPDIR)/run_unittests-fd_share_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_share_tests.cc' object='run_unittests-fd_share_tests.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-fd_share_tests.obj `if test -f 'fd_share_tests.cc'; then $(CYGPATH_W) 'fd_share_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_share_tests.cc'; fi`
+
+run_unittests-fd_tests.o: fd_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_tests.o -MD -MP -MF $(DEPDIR)/run_unittests-fd_tests.Tpo -c -o run_unittests-fd_tests.o `test -f 'fd_tests.cc' || echo '$(srcdir)/'`fd_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_tests.Tpo $(DEPDIR)/run_unittests-fd_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_tests.cc' object='run_unittests-fd_tests.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-fd_tests.o `test -f 'fd_tests.cc' || echo '$(srcdir)/'`fd_tests.cc
+
+run_unittests-fd_tests.obj: fd_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-fd_tests.obj -MD -MP -MF $(DEPDIR)/run_unittests-fd_tests.Tpo -c -o run_unittests-fd_tests.obj `if test -f 'fd_tests.cc'; then $(CYGPATH_W) 'fd_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_tests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-fd_tests.Tpo $(DEPDIR)/run_unittests-fd_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fd_tests.cc' object='run_unittests-fd_tests.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-fd_tests.obj `if test -f 'fd_tests.cc'; then $(CYGPATH_W) 'fd_tests.cc'; else $(CYGPATH_W) '$(srcdir)/fd_tests.cc'; fi`
+
+run_unittests-file_utilities_unittest.o: file_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-file_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo -c -o run_unittests-file_utilities_unittest.o `test -f 'file_utilities_unittest.cc' || echo '$(srcdir)/'`file_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo $(DEPDIR)/run_unittests-file_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file_utilities_unittest.cc' object='run_unittests-file_utilities_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-file_utilities_unittest.o `test -f 'file_utilities_unittest.cc' || echo '$(srcdir)/'`file_utilities_unittest.cc
+
+run_unittests-file_utilities_unittest.obj: file_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-file_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo -c -o run_unittests-file_utilities_unittest.obj `if test -f 'file_utilities_unittest.cc'; then $(CYGPATH_W) 'file_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/file_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-file_utilities_unittest.Tpo $(DEPDIR)/run_unittests-file_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='file_utilities_unittest.cc' object='run_unittests-file_utilities_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-file_utilities_unittest.obj `if test -f 'file_utilities_unittest.cc'; then $(CYGPATH_W) 'file_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/file_utilities_unittest.cc'; fi`
+
+run_unittests-filename_unittest.o: filename_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-filename_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-filename_unittest.Tpo -c -o run_unittests-filename_unittest.o `test -f 'filename_unittest.cc' || echo '$(srcdir)/'`filename_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-filename_unittest.Tpo $(DEPDIR)/run_unittests-filename_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='filename_unittest.cc' object='run_unittests-filename_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-filename_unittest.o `test -f 'filename_unittest.cc' || echo '$(srcdir)/'`filename_unittest.cc
+
+run_unittests-filename_unittest.obj: filename_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-filename_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-filename_unittest.Tpo -c -o run_unittests-filename_unittest.obj `if test -f 'filename_unittest.cc'; then $(CYGPATH_W) 'filename_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/filename_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-filename_unittest.Tpo $(DEPDIR)/run_unittests-filename_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='filename_unittest.cc' object='run_unittests-filename_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-filename_unittest.obj `if test -f 'filename_unittest.cc'; then $(CYGPATH_W) 'filename_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/filename_unittest.cc'; fi`
+
+run_unittests-hash_unittest.o: hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-hash_unittest.Tpo -c -o run_unittests-hash_unittest.o `test -f 'hash_unittest.cc' || echo '$(srcdir)/'`hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_unittest.Tpo $(DEPDIR)/run_unittests-hash_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_unittest.cc' object='run_unittests-hash_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-hash_unittest.o `test -f 'hash_unittest.cc' || echo '$(srcdir)/'`hash_unittest.cc
+
+run_unittests-hash_unittest.obj: hash_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hash_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-hash_unittest.Tpo -c -o run_unittests-hash_unittest.obj `if test -f 'hash_unittest.cc'; then $(CYGPATH_W) 'hash_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hash_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hash_unittest.Tpo $(DEPDIR)/run_unittests-hash_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hash_unittest.cc' object='run_unittests-hash_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-hash_unittest.obj `if test -f 'hash_unittest.cc'; then $(CYGPATH_W) 'hash_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hash_unittest.cc'; fi`
+
+run_unittests-hex_unittest.o: hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hex_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-hex_unittest.Tpo -c -o run_unittests-hex_unittest.o `test -f 'hex_unittest.cc' || echo '$(srcdir)/'`hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hex_unittest.Tpo $(DEPDIR)/run_unittests-hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hex_unittest.cc' object='run_unittests-hex_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-hex_unittest.o `test -f 'hex_unittest.cc' || echo '$(srcdir)/'`hex_unittest.cc
+
+run_unittests-hex_unittest.obj: hex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-hex_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-hex_unittest.Tpo -c -o run_unittests-hex_unittest.obj `if test -f 'hex_unittest.cc'; then $(CYGPATH_W) 'hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hex_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-hex_unittest.Tpo $(DEPDIR)/run_unittests-hex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='hex_unittest.cc' object='run_unittests-hex_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-hex_unittest.obj `if test -f 'hex_unittest.cc'; then $(CYGPATH_W) 'hex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/hex_unittest.cc'; fi`
+
+run_unittests-io_utilities_unittest.o: io_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo -c -o run_unittests-io_utilities_unittest.o `test -f 'io_utilities_unittest.cc' || echo '$(srcdir)/'`io_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo $(DEPDIR)/run_unittests-io_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_utilities_unittest.cc' object='run_unittests-io_utilities_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-io_utilities_unittest.o `test -f 'io_utilities_unittest.cc' || echo '$(srcdir)/'`io_utilities_unittest.cc
+
+run_unittests-io_utilities_unittest.obj: io_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-io_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo -c -o run_unittests-io_utilities_unittest.obj `if test -f 'io_utilities_unittest.cc'; then $(CYGPATH_W) 'io_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-io_utilities_unittest.Tpo $(DEPDIR)/run_unittests-io_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='io_utilities_unittest.cc' object='run_unittests-io_utilities_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-io_utilities_unittest.obj `if test -f 'io_utilities_unittest.cc'; then $(CYGPATH_W) 'io_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/io_utilities_unittest.cc'; fi`
+
+run_unittests-labeled_value_unittest.o: labeled_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labeled_value_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo -c -o run_unittests-labeled_value_unittest.o `test -f 'labeled_value_unittest.cc' || echo '$(srcdir)/'`labeled_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo $(DEPDIR)/run_unittests-labeled_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labeled_value_unittest.cc' object='run_unittests-labeled_value_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-labeled_value_unittest.o `test -f 'labeled_value_unittest.cc' || echo '$(srcdir)/'`labeled_value_unittest.cc
+
+run_unittests-labeled_value_unittest.obj: labeled_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labeled_value_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo -c -o run_unittests-labeled_value_unittest.obj `if test -f 'labeled_value_unittest.cc'; then $(CYGPATH_W) 'labeled_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labeled_value_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labeled_value_unittest.Tpo $(DEPDIR)/run_unittests-labeled_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labeled_value_unittest.cc' object='run_unittests-labeled_value_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-labeled_value_unittest.obj `if test -f 'labeled_value_unittest.cc'; then $(CYGPATH_W) 'labeled_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labeled_value_unittest.cc'; fi`
+
+run_unittests-memory_segment_local_unittest.o: memory_segment_local_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_local_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo -c -o run_unittests-memory_segment_local_unittest.o `test -f 'memory_segment_local_unittest.cc' || echo '$(srcdir)/'`memory_segment_local_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_local_unittest.cc' object='run_unittests-memory_segment_local_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-memory_segment_local_unittest.o `test -f 'memory_segment_local_unittest.cc' || echo '$(srcdir)/'`memory_segment_local_unittest.cc
+
+run_unittests-memory_segment_local_unittest.obj: memory_segment_local_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_local_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo -c -o run_unittests-memory_segment_local_unittest.obj `if test -f 'memory_segment_local_unittest.cc'; then $(CYGPATH_W) 'memory_segment_local_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_local_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_local_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_local_unittest.cc' object='run_unittests-memory_segment_local_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-memory_segment_local_unittest.obj `if test -f 'memory_segment_local_unittest.cc'; then $(CYGPATH_W) 'memory_segment_local_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_local_unittest.cc'; fi`
+
+run_unittests-memory_segment_common_unittest.o: memory_segment_common_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_common_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo -c -o run_unittests-memory_segment_common_unittest.o `test -f 'memory_segment_common_unittest.cc' || echo '$(srcdir)/'`memory_segment_common_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_common_unittest.cc' object='run_unittests-memory_segment_common_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-memory_segment_common_unittest.o `test -f 'memory_segment_common_unittest.cc' || echo '$(srcdir)/'`memory_segment_common_unittest.cc
+
+run_unittests-memory_segment_common_unittest.obj: memory_segment_common_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-memory_segment_common_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo -c -o run_unittests-memory_segment_common_unittest.obj `if test -f 'memory_segment_common_unittest.cc'; then $(CYGPATH_W) 'memory_segment_common_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_common_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-memory_segment_common_unittest.Tpo $(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='memory_segment_common_unittest.cc' object='run_unittests-memory_segment_common_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-memory_segment_common_unittest.obj `if test -f 'memory_segment_common_unittest.cc'; then $(CYGPATH_W) 'memory_segment_common_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/memory_segment_common_unittest.cc'; fi`
+
+run_unittests-multi_threading_mgr_unittest.o: multi_threading_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-multi_threading_mgr_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo -c -o run_unittests-multi_threading_mgr_unittest.o `test -f 'multi_threading_mgr_unittest.cc' || echo '$(srcdir)/'`multi_threading_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='multi_threading_mgr_unittest.cc' object='run_unittests-multi_threading_mgr_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-multi_threading_mgr_unittest.o `test -f 'multi_threading_mgr_unittest.cc' || echo '$(srcdir)/'`multi_threading_mgr_unittest.cc
+
+run_unittests-multi_threading_mgr_unittest.obj: multi_threading_mgr_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-multi_threading_mgr_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo -c -o run_unittests-multi_threading_mgr_unittest.obj `if test -f 'multi_threading_mgr_unittest.cc'; then $(CYGPATH_W) 'multi_threading_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/multi_threading_mgr_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Tpo $(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='multi_threading_mgr_unittest.cc' object='run_unittests-multi_threading_mgr_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-multi_threading_mgr_unittest.obj `if test -f 'multi_threading_mgr_unittest.cc'; then $(CYGPATH_W) 'multi_threading_mgr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/multi_threading_mgr_unittest.cc'; fi`
+
+run_unittests-optional_unittest.o: optional_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-optional_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-optional_unittest.Tpo -c -o run_unittests-optional_unittest.o `test -f 'optional_unittest.cc' || echo '$(srcdir)/'`optional_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-optional_unittest.Tpo $(DEPDIR)/run_unittests-optional_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='optional_unittest.cc' object='run_unittests-optional_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-optional_unittest.o `test -f 'optional_unittest.cc' || echo '$(srcdir)/'`optional_unittest.cc
+
+run_unittests-optional_unittest.obj: optional_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-optional_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-optional_unittest.Tpo -c -o run_unittests-optional_unittest.obj `if test -f 'optional_unittest.cc'; then $(CYGPATH_W) 'optional_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/optional_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-optional_unittest.Tpo $(DEPDIR)/run_unittests-optional_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='optional_unittest.cc' object='run_unittests-optional_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-optional_unittest.obj `if test -f 'optional_unittest.cc'; then $(CYGPATH_W) 'optional_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/optional_unittest.cc'; fi`
+
+run_unittests-pid_file_unittest.o: pid_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-pid_file_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-pid_file_unittest.Tpo -c -o run_unittests-pid_file_unittest.o `test -f 'pid_file_unittest.cc' || echo '$(srcdir)/'`pid_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-pid_file_unittest.Tpo $(DEPDIR)/run_unittests-pid_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pid_file_unittest.cc' object='run_unittests-pid_file_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-pid_file_unittest.o `test -f 'pid_file_unittest.cc' || echo '$(srcdir)/'`pid_file_unittest.cc
+
+run_unittests-pid_file_unittest.obj: pid_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-pid_file_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-pid_file_unittest.Tpo -c -o run_unittests-pid_file_unittest.obj `if test -f 'pid_file_unittest.cc'; then $(CYGPATH_W) 'pid_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pid_file_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-pid_file_unittest.Tpo $(DEPDIR)/run_unittests-pid_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='pid_file_unittest.cc' object='run_unittests-pid_file_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-pid_file_unittest.obj `if test -f 'pid_file_unittest.cc'; then $(CYGPATH_W) 'pid_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/pid_file_unittest.cc'; fi`
+
+run_unittests-staged_value_unittest.o: staged_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-staged_value_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-staged_value_unittest.Tpo -c -o run_unittests-staged_value_unittest.o `test -f 'staged_value_unittest.cc' || echo '$(srcdir)/'`staged_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-staged_value_unittest.Tpo $(DEPDIR)/run_unittests-staged_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='staged_value_unittest.cc' object='run_unittests-staged_value_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-staged_value_unittest.o `test -f 'staged_value_unittest.cc' || echo '$(srcdir)/'`staged_value_unittest.cc
+
+run_unittests-staged_value_unittest.obj: staged_value_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-staged_value_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-staged_value_unittest.Tpo -c -o run_unittests-staged_value_unittest.obj `if test -f 'staged_value_unittest.cc'; then $(CYGPATH_W) 'staged_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/staged_value_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-staged_value_unittest.Tpo $(DEPDIR)/run_unittests-staged_value_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='staged_value_unittest.cc' object='run_unittests-staged_value_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-staged_value_unittest.obj `if test -f 'staged_value_unittest.cc'; then $(CYGPATH_W) 'staged_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/staged_value_unittest.cc'; fi`
+
+run_unittests-state_model_unittest.o: state_model_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-state_model_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-state_model_unittest.Tpo -c -o run_unittests-state_model_unittest.o `test -f 'state_model_unittest.cc' || echo '$(srcdir)/'`state_model_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-state_model_unittest.Tpo $(DEPDIR)/run_unittests-state_model_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='state_model_unittest.cc' object='run_unittests-state_model_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-state_model_unittest.o `test -f 'state_model_unittest.cc' || echo '$(srcdir)/'`state_model_unittest.cc
+
+run_unittests-state_model_unittest.obj: state_model_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-state_model_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-state_model_unittest.Tpo -c -o run_unittests-state_model_unittest.obj `if test -f 'state_model_unittest.cc'; then $(CYGPATH_W) 'state_model_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/state_model_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-state_model_unittest.Tpo $(DEPDIR)/run_unittests-state_model_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='state_model_unittest.cc' object='run_unittests-state_model_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-state_model_unittest.obj `if test -f 'state_model_unittest.cc'; then $(CYGPATH_W) 'state_model_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/state_model_unittest.cc'; fi`
+
+run_unittests-strutil_unittest.o: strutil_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-strutil_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-strutil_unittest.Tpo -c -o run_unittests-strutil_unittest.o `test -f 'strutil_unittest.cc' || echo '$(srcdir)/'`strutil_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-strutil_unittest.Tpo $(DEPDIR)/run_unittests-strutil_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='strutil_unittest.cc' object='run_unittests-strutil_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-strutil_unittest.o `test -f 'strutil_unittest.cc' || echo '$(srcdir)/'`strutil_unittest.cc
+
+run_unittests-strutil_unittest.obj: strutil_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-strutil_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-strutil_unittest.Tpo -c -o run_unittests-strutil_unittest.obj `if test -f 'strutil_unittest.cc'; then $(CYGPATH_W) 'strutil_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/strutil_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-strutil_unittest.Tpo $(DEPDIR)/run_unittests-strutil_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='strutil_unittest.cc' object='run_unittests-strutil_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-strutil_unittest.obj `if test -f 'strutil_unittest.cc'; then $(CYGPATH_W) 'strutil_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/strutil_unittest.cc'; fi`
+
+run_unittests-thread_pool_unittest.o: thread_pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-thread_pool_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo -c -o run_unittests-thread_pool_unittest.o `test -f 'thread_pool_unittest.cc' || echo '$(srcdir)/'`thread_pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo $(DEPDIR)/run_unittests-thread_pool_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='thread_pool_unittest.cc' object='run_unittests-thread_pool_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-thread_pool_unittest.o `test -f 'thread_pool_unittest.cc' || echo '$(srcdir)/'`thread_pool_unittest.cc
+
+run_unittests-thread_pool_unittest.obj: thread_pool_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-thread_pool_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo -c -o run_unittests-thread_pool_unittest.obj `if test -f 'thread_pool_unittest.cc'; then $(CYGPATH_W) 'thread_pool_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/thread_pool_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-thread_pool_unittest.Tpo $(DEPDIR)/run_unittests-thread_pool_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='thread_pool_unittest.cc' object='run_unittests-thread_pool_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-thread_pool_unittest.obj `if test -f 'thread_pool_unittest.cc'; then $(CYGPATH_W) 'thread_pool_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/thread_pool_unittest.cc'; fi`
+
+run_unittests-time_utilities_unittest.o: time_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo -c -o run_unittests-time_utilities_unittest.o `test -f 'time_utilities_unittest.cc' || echo '$(srcdir)/'`time_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo $(DEPDIR)/run_unittests-time_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utilities_unittest.cc' object='run_unittests-time_utilities_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_utilities_unittest.o `test -f 'time_utilities_unittest.cc' || echo '$(srcdir)/'`time_utilities_unittest.cc
+
+run_unittests-time_utilities_unittest.obj: time_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo -c -o run_unittests-time_utilities_unittest.obj `if test -f 'time_utilities_unittest.cc'; then $(CYGPATH_W) 'time_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utilities_unittest.Tpo $(DEPDIR)/run_unittests-time_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utilities_unittest.cc' object='run_unittests-time_utilities_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_utilities_unittest.obj `if test -f 'time_utilities_unittest.cc'; then $(CYGPATH_W) 'time_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utilities_unittest.cc'; fi`
+
+run_unittests-triplet_unittest.o: triplet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-triplet_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-triplet_unittest.Tpo -c -o run_unittests-triplet_unittest.o `test -f 'triplet_unittest.cc' || echo '$(srcdir)/'`triplet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-triplet_unittest.Tpo $(DEPDIR)/run_unittests-triplet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='triplet_unittest.cc' object='run_unittests-triplet_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-triplet_unittest.o `test -f 'triplet_unittest.cc' || echo '$(srcdir)/'`triplet_unittest.cc
+
+run_unittests-triplet_unittest.obj: triplet_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-triplet_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-triplet_unittest.Tpo -c -o run_unittests-triplet_unittest.obj `if test -f 'triplet_unittest.cc'; then $(CYGPATH_W) 'triplet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/triplet_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-triplet_unittest.Tpo $(DEPDIR)/run_unittests-triplet_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='triplet_unittest.cc' object='run_unittests-triplet_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-triplet_unittest.obj `if test -f 'triplet_unittest.cc'; then $(CYGPATH_W) 'triplet_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/triplet_unittest.cc'; fi`
+
+run_unittests-range_utilities_unittest.o: range_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-range_utilities_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo -c -o run_unittests-range_utilities_unittest.o `test -f 'range_utilities_unittest.cc' || echo '$(srcdir)/'`range_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo $(DEPDIR)/run_unittests-range_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='range_utilities_unittest.cc' object='run_unittests-range_utilities_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-range_utilities_unittest.o `test -f 'range_utilities_unittest.cc' || echo '$(srcdir)/'`range_utilities_unittest.cc
+
+run_unittests-range_utilities_unittest.obj: range_utilities_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-range_utilities_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo -c -o run_unittests-range_utilities_unittest.obj `if test -f 'range_utilities_unittest.cc'; then $(CYGPATH_W) 'range_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/range_utilities_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-range_utilities_unittest.Tpo $(DEPDIR)/run_unittests-range_utilities_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='range_utilities_unittest.cc' object='run_unittests-range_utilities_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-range_utilities_unittest.obj `if test -f 'range_utilities_unittest.cc'; then $(CYGPATH_W) 'range_utilities_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/range_utilities_unittest.cc'; fi`
+
+run_unittests-readwrite_mutex_unittest.o: readwrite_mutex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-readwrite_mutex_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo -c -o run_unittests-readwrite_mutex_unittest.o `test -f 'readwrite_mutex_unittest.cc' || echo '$(srcdir)/'`readwrite_mutex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='readwrite_mutex_unittest.cc' object='run_unittests-readwrite_mutex_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-readwrite_mutex_unittest.o `test -f 'readwrite_mutex_unittest.cc' || echo '$(srcdir)/'`readwrite_mutex_unittest.cc
+
+run_unittests-readwrite_mutex_unittest.obj: readwrite_mutex_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-readwrite_mutex_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo -c -o run_unittests-readwrite_mutex_unittest.obj `if test -f 'readwrite_mutex_unittest.cc'; then $(CYGPATH_W) 'readwrite_mutex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/readwrite_mutex_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Tpo $(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='readwrite_mutex_unittest.cc' object='run_unittests-readwrite_mutex_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-readwrite_mutex_unittest.obj `if test -f 'readwrite_mutex_unittest.cc'; then $(CYGPATH_W) 'readwrite_mutex_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/readwrite_mutex_unittest.cc'; fi`
+
+run_unittests-stopwatch_unittest.o: stopwatch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stopwatch_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo -c -o run_unittests-stopwatch_unittest.o `test -f 'stopwatch_unittest.cc' || echo '$(srcdir)/'`stopwatch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo $(DEPDIR)/run_unittests-stopwatch_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stopwatch_unittest.cc' object='run_unittests-stopwatch_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-stopwatch_unittest.o `test -f 'stopwatch_unittest.cc' || echo '$(srcdir)/'`stopwatch_unittest.cc
+
+run_unittests-stopwatch_unittest.obj: stopwatch_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stopwatch_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo -c -o run_unittests-stopwatch_unittest.obj `if test -f 'stopwatch_unittest.cc'; then $(CYGPATH_W) 'stopwatch_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stopwatch_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stopwatch_unittest.Tpo $(DEPDIR)/run_unittests-stopwatch_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stopwatch_unittest.cc' object='run_unittests-stopwatch_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-stopwatch_unittest.obj `if test -f 'stopwatch_unittest.cc'; then $(CYGPATH_W) 'stopwatch_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stopwatch_unittest.cc'; fi`
+
+run_unittests-unlock_guard_unittests.o: unlock_guard_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unlock_guard_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo -c -o run_unittests-unlock_guard_unittests.o `test -f 'unlock_guard_unittests.cc' || echo '$(srcdir)/'`unlock_guard_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo $(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unlock_guard_unittests.cc' object='run_unittests-unlock_guard_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-unlock_guard_unittests.o `test -f 'unlock_guard_unittests.cc' || echo '$(srcdir)/'`unlock_guard_unittests.cc
+
+run_unittests-unlock_guard_unittests.obj: unlock_guard_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unlock_guard_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo -c -o run_unittests-unlock_guard_unittests.obj `if test -f 'unlock_guard_unittests.cc'; then $(CYGPATH_W) 'unlock_guard_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/unlock_guard_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unlock_guard_unittests.Tpo $(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unlock_guard_unittests.cc' object='run_unittests-unlock_guard_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-unlock_guard_unittests.obj `if test -f 'unlock_guard_unittests.cc'; then $(CYGPATH_W) 'unlock_guard_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/unlock_guard_unittests.cc'; fi`
+
+run_unittests-utf8_unittest.o: utf8_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-utf8_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-utf8_unittest.Tpo -c -o run_unittests-utf8_unittest.o `test -f 'utf8_unittest.cc' || echo '$(srcdir)/'`utf8_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-utf8_unittest.Tpo $(DEPDIR)/run_unittests-utf8_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='utf8_unittest.cc' object='run_unittests-utf8_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-utf8_unittest.o `test -f 'utf8_unittest.cc' || echo '$(srcdir)/'`utf8_unittest.cc
+
+run_unittests-utf8_unittest.obj: utf8_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-utf8_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-utf8_unittest.Tpo -c -o run_unittests-utf8_unittest.obj `if test -f 'utf8_unittest.cc'; then $(CYGPATH_W) 'utf8_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/utf8_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-utf8_unittest.Tpo $(DEPDIR)/run_unittests-utf8_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='utf8_unittest.cc' object='run_unittests-utf8_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-utf8_unittest.obj `if test -f 'utf8_unittest.cc'; then $(CYGPATH_W) 'utf8_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/utf8_unittest.cc'; fi`
+
+run_unittests-versioned_csv_file_unittest.o: versioned_csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-versioned_csv_file_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo -c -o run_unittests-versioned_csv_file_unittest.o `test -f 'versioned_csv_file_unittest.cc' || echo '$(srcdir)/'`versioned_csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='versioned_csv_file_unittest.cc' object='run_unittests-versioned_csv_file_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-versioned_csv_file_unittest.o `test -f 'versioned_csv_file_unittest.cc' || echo '$(srcdir)/'`versioned_csv_file_unittest.cc
+
+run_unittests-versioned_csv_file_unittest.obj: versioned_csv_file_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-versioned_csv_file_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo -c -o run_unittests-versioned_csv_file_unittest.obj `if test -f 'versioned_csv_file_unittest.cc'; then $(CYGPATH_W) 'versioned_csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/versioned_csv_file_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Tpo $(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='versioned_csv_file_unittest.cc' object='run_unittests-versioned_csv_file_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-versioned_csv_file_unittest.obj `if test -f 'versioned_csv_file_unittest.cc'; then $(CYGPATH_W) 'versioned_csv_file_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/versioned_csv_file_unittest.cc'; fi`
+
+run_unittests-watch_socket_unittests.o: watch_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watch_socket_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo -c -o run_unittests-watch_socket_unittests.o `test -f 'watch_socket_unittests.cc' || echo '$(srcdir)/'`watch_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo $(DEPDIR)/run_unittests-watch_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watch_socket_unittests.cc' object='run_unittests-watch_socket_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-watch_socket_unittests.o `test -f 'watch_socket_unittests.cc' || echo '$(srcdir)/'`watch_socket_unittests.cc
+
+run_unittests-watch_socket_unittests.obj: watch_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watch_socket_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo -c -o run_unittests-watch_socket_unittests.obj `if test -f 'watch_socket_unittests.cc'; then $(CYGPATH_W) 'watch_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/watch_socket_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watch_socket_unittests.Tpo $(DEPDIR)/run_unittests-watch_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watch_socket_unittests.cc' object='run_unittests-watch_socket_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-watch_socket_unittests.obj `if test -f 'watch_socket_unittests.cc'; then $(CYGPATH_W) 'watch_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/watch_socket_unittests.cc'; fi`
+
+run_unittests-watched_thread_unittest.o: watched_thread_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watched_thread_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo -c -o run_unittests-watched_thread_unittest.o `test -f 'watched_thread_unittest.cc' || echo '$(srcdir)/'`watched_thread_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo $(DEPDIR)/run_unittests-watched_thread_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watched_thread_unittest.cc' object='run_unittests-watched_thread_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-watched_thread_unittest.o `test -f 'watched_thread_unittest.cc' || echo '$(srcdir)/'`watched_thread_unittest.cc
+
+run_unittests-watched_thread_unittest.obj: watched_thread_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-watched_thread_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo -c -o run_unittests-watched_thread_unittest.obj `if test -f 'watched_thread_unittest.cc'; then $(CYGPATH_W) 'watched_thread_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/watched_thread_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-watched_thread_unittest.Tpo $(DEPDIR)/run_unittests-watched_thread_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='watched_thread_unittest.cc' object='run_unittests-watched_thread_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-watched_thread_unittest.obj `if test -f 'watched_thread_unittest.cc'; then $(CYGPATH_W) 'watched_thread_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/watched_thread_unittest.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-base32hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-base64_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-bigint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-buffer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-doubles_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_share_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-file_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-filename_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-labeled_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-optional_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-pid_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-range_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-staged_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-state_model_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stopwatch_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-strutil_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-thread_pool_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-time_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-triplet_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-utf8_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watch_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watched_thread_unittest.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-base32hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-base64_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-bigint_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-boost_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-buffer_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-chrono_time_utils_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-dhcp_space_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-doubles_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_share_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-fd_tests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-file_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-filename_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hash_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-hex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-io_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-labeled_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_common_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-memory_segment_local_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-multi_threading_mgr_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-optional_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-pid_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-range_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-readwrite_mutex_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-staged_value_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-state_model_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-stopwatch_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-strutil_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-thread_pool_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-time_utilities_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-triplet_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-unlock_guard_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-utf8_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-versioned_csv_file_unittest.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watch_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-watched_thread_unittest.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/util/tests/base32hex_unittest.cc b/src/lib/util/tests/base32hex_unittest.cc
new file mode 100644
index 0000000..bf28f62
--- /dev/null
+++ b/src/lib/util/tests/base32hex_unittest.cc
@@ -0,0 +1,158 @@
+// 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 <stdint.h>
+
+#include <cctype>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <util/encode/base32hex.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::encode;
+
+namespace {
+
+typedef pair<string, string> StringPair;
+
+class Base32HexTest : public ::testing::Test {
+protected:
+ Base32HexTest() : encoding_chars("0123456789ABCDEFGHIJKLMNOPQRSTUV=") {
+ // test vectors from RFC4648
+ test_sequence.push_back(StringPair("", ""));
+ test_sequence.push_back(StringPair("f", "CO======"));
+ test_sequence.push_back(StringPair("fo", "CPNG===="));
+ test_sequence.push_back(StringPair("foo", "CPNMU==="));
+ test_sequence.push_back(StringPair("foob", "CPNMUOG="));
+ test_sequence.push_back(StringPair("fooba", "CPNMUOJ1"));
+ test_sequence.push_back(StringPair("foobar", "CPNMUOJ1E8======"));
+
+ // the same data, encoded using lower case chars (testable only
+ // for the decode side)
+ test_sequence_lower.push_back(StringPair("f", "co======"));
+ test_sequence_lower.push_back(StringPair("fo", "cpng===="));
+ test_sequence_lower.push_back(StringPair("foo", "cpnmu==="));
+ test_sequence_lower.push_back(StringPair("foob", "cpnmuog="));
+ test_sequence_lower.push_back(StringPair("fooba", "cpnmuoj1"));
+ test_sequence_lower.push_back(StringPair("foobar",
+ "cpnmuoj1e8======"));
+ }
+ vector<StringPair> test_sequence;
+ vector<StringPair> test_sequence_lower;
+ vector<uint8_t> decoded_data;
+ const string encoding_chars;
+};
+
+void
+decodeCheck(const string& input_string, vector<uint8_t>& output,
+ const string& expected)
+{
+ decodeBase32Hex(input_string, output);
+ EXPECT_EQ(expected, string(output.begin(), output.end()));
+}
+
+TEST_F(Base32HexTest, decode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decodeCheck((*it).second, decoded_data, (*it).first);
+ }
+
+ // whitespace should be allowed
+ decodeCheck("CP NM\tUOG=", decoded_data, "foob");
+ decodeCheck("CPNMU===\n", decoded_data, "foo");
+ decodeCheck(" CP NM\tUOG=", decoded_data, "foob");
+ decodeCheck(" ", decoded_data, "");
+
+ // Incomplete input
+ EXPECT_THROW(decodeBase32Hex("CPNMUOJ", decoded_data), BadValue);
+
+ // invalid number of padding characters
+ EXPECT_THROW(decodeBase32Hex("CPNMU0==", decoded_data), BadValue);
+ EXPECT_THROW(decodeBase32Hex("CO0=====", decoded_data), BadValue);
+ EXPECT_THROW(decodeBase32Hex("CO=======", decoded_data), // too many ='s
+ BadValue);
+
+ // intermediate padding isn't allowed
+ EXPECT_THROW(decodeBase32Hex("CPNMUOG=CPNMUOG=", decoded_data), BadValue);
+
+ // Non canonical form isn't allowed.
+ // P => 25(11001), so the padding byte would be 01000000
+ EXPECT_THROW(decodeBase32Hex("0P======", decoded_data), BadValue);
+}
+
+TEST_F(Base32HexTest, decodeLower) {
+ for (vector<StringPair>::const_iterator it = test_sequence_lower.begin();
+ it != test_sequence_lower.end();
+ ++it) {
+ decodeCheck((*it).second, decoded_data, (*it).first);
+ }
+}
+
+TEST_F(Base32HexTest, encode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decoded_data.assign((*it).first.begin(), (*it).first.end());
+ EXPECT_EQ((*it).second, encodeBase32Hex(decoded_data));
+ }
+}
+
+// For Base32Hex we use handmade mappings, so it's prudent to test the
+// entire mapping table explicitly.
+TEST_F(Base32HexTest, decodeMap) {
+ string input(8, '0'); // input placeholder
+
+ // We're going to populate an input string with only the last character
+ // not equal to the zero character ('0') for each valid base32hex encoding
+ // character. Decoding that input should result in a data stream with
+ // the last byte equal to the numeric value represented by the that
+ // character. For example, we'll generate and confirm the following:
+ // "00000000" => should be 0 (as a 40bit integer)
+ // "00000001" => should be 1 (as a 40bit integer)
+ // ...
+ // "0000000V" => should be 31 (as a 40bit integer)
+ // We also check the use of an invalid character for the last character
+ // surely fails. '=' should be accepted as a valid padding in this
+ // context; space characters shouldn't be allowed in this context.
+
+ for (int i = 0; i < 256; ++i) {
+ input[7] = i;
+
+ const char ch = toupper(i);
+ const size_t pos = encoding_chars.find(ch);
+ if (pos == string::npos) {
+ EXPECT_THROW(decodeBase32Hex(input, decoded_data), BadValue);
+ } else {
+ decodeBase32Hex(input, decoded_data);
+ if (ch == '=') {
+ EXPECT_EQ(4, decoded_data.size());
+ } else {
+ EXPECT_EQ(5, decoded_data.size());
+ EXPECT_EQ(pos, decoded_data[4]);
+ }
+ }
+ }
+}
+
+TEST_F(Base32HexTest, encodeMap) {
+ for (uint8_t i = 0; i < 32; ++i) {
+ decoded_data.assign(4, 0);
+ decoded_data.push_back(i);
+ EXPECT_EQ(encoding_chars[i], encodeBase32Hex(decoded_data)[7]);
+ }
+}
+
+}
diff --git a/src/lib/util/tests/base64_unittest.cc b/src/lib/util/tests/base64_unittest.cc
new file mode 100644
index 0000000..516925e
--- /dev/null
+++ b/src/lib/util/tests/base64_unittest.cc
@@ -0,0 +1,93 @@
+// 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 <utility>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <util/encode/base64.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::encode;
+
+namespace {
+
+typedef pair<string, string> StringPair;
+
+class Base64Test : public ::testing::Test {
+protected:
+ Base64Test()
+ {
+ // test vectors from RFC4648
+ test_sequence.push_back(StringPair("", ""));
+ test_sequence.push_back(StringPair("f", "Zg=="));
+ test_sequence.push_back(StringPair("fo", "Zm8="));
+ test_sequence.push_back(StringPair("foo", "Zm9v"));
+ test_sequence.push_back(StringPair("foob", "Zm9vYg=="));
+ test_sequence.push_back(StringPair("fooba", "Zm9vYmE="));
+ test_sequence.push_back(StringPair("foobar", "Zm9vYmFy"));
+ }
+ vector<StringPair> test_sequence;
+ vector<uint8_t> decoded_data;
+};
+
+void
+decodeCheck(const string& input_string, vector<uint8_t>& output,
+ const string& expected)
+{
+ decodeBase64(input_string, output);
+ EXPECT_EQ(expected, string(output.begin(), output.end()));
+}
+
+TEST_F(Base64Test, decode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decodeCheck((*it).second, decoded_data, (*it).first);
+ }
+
+ // whitespace should be allowed
+ decodeCheck("Zm 9v\tYmF\ny", decoded_data, "foobar");
+ decodeCheck("Zm9vYg==", decoded_data, "foob");
+ decodeCheck("Zm9vYmE=\n", decoded_data, "fooba");
+ decodeCheck(" Zm9vYmE=\n", decoded_data, "fooba");
+ decodeCheck(" ", decoded_data, "");
+ decodeCheck("\n\t", decoded_data, "");
+
+ // incomplete input
+ EXPECT_THROW(decodeBase64("Zm9vYmF", decoded_data), BadValue);
+
+ // only up to 2 padding characters are allowed
+ EXPECT_THROW(decodeBase64("A===", decoded_data), BadValue);
+ EXPECT_THROW(decodeBase64("A= ==", decoded_data), BadValue);
+
+ // intermediate padding isn't allowed
+ EXPECT_THROW(decodeBase64("YmE=YmE=", decoded_data), BadValue);
+
+ // Non canonical form isn't allowed.
+ // Z => 25(011001), m => 38(100110), 9 => 60(111101), so the padding
+ // byte would be 0100 0000.
+ EXPECT_THROW(decodeBase64("Zm9=", decoded_data), BadValue);
+ // Same for the 1st padding byte. This would make it 01100000.
+ EXPECT_THROW(decodeBase64("Zm==", decoded_data), BadValue);
+}
+
+TEST_F(Base64Test, encode) {
+ for (vector<StringPair>::const_iterator it = test_sequence.begin();
+ it != test_sequence.end();
+ ++it) {
+ decoded_data.assign((*it).first.begin(), (*it).first.end());
+ EXPECT_EQ((*it).second, encodeBase64(decoded_data));
+ }
+}
+}
diff --git a/src/lib/util/tests/bigint_unittest.cc b/src/lib/util/tests/bigint_unittest.cc
new file mode 100644
index 0000000..b811c07
--- /dev/null
+++ b/src/lib/util/tests/bigint_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright (C) 2023 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 <testutils/gtest_utils.h>
+#include <util/bigints.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+
+namespace {
+
+// C++ doesn't allow very big integer literals, so that's why some tests on big
+// numbers might appear like they're unnecessarily circumventing a more obvious
+// test choice.
+
+// Checks that int128_t behaves like a signed integer should.
+TEST(BigintTest, int128) {
+ // Check addition with small numbers.
+ EXPECT_EQ(24, int128_t(16) + int128_t(8));
+
+ // Check subtraction with small numbers.
+ EXPECT_EQ(48, int128_t(64) - int128_t(16));
+
+ // Check multiplication with small numbers.
+ EXPECT_EQ(8, int128_t(2) * int128_t(4));
+
+ // Check division with small numbers.
+ EXPECT_EQ(16, int128_t(64) / int128_t(4));
+
+ // Check rounded division with small numbers.
+ EXPECT_EQ(16, int128_t(65) / int128_t(4));
+
+ // Check that dividing by zero throws.
+ EXPECT_THROW(int128_t(1) / 0, std::overflow_error);
+
+ // Check that underflowing results in a negative number for int128_t.
+ EXPECT_EQ(-1, int128_t(0) - 1);
+
+ // Check that UINT64_MAX < INT128_MAX.
+ EXPECT_LT(uint64_t(0) - 1, int128_t(uint128_t(0) - 1));
+
+ // Check that int128_t is default-initialized to zero. Not a strict
+ // requirement, but something that the current implementation ensures.
+ int128_t i128;
+ EXPECT_EQ(0, i128);
+
+ // Check that overflowing on big numbers has the correct result.
+ i128 = int128_t(0) - 1;
+ EXPECT_EQ(i128 - 1, i128 + i128);
+}
+
+// Checks that uint128_t behaves like an unsigned integer should.
+TEST(BigintTest, uint128) {
+ // Check addition with small numbers.
+ EXPECT_EQ(24, uint128_t(16) + uint128_t(8));
+
+ // Check subtraction with small numbers.
+ EXPECT_EQ(48, uint128_t(64) - uint128_t(16));
+
+ // Check multiplication with small numbers.
+ EXPECT_EQ(8, uint128_t(2) * uint128_t(4));
+
+ // Check division with small numbers.
+ EXPECT_EQ(16, uint128_t(64) / uint128_t(4));
+
+ // Check rounded division with small numbers.
+ EXPECT_EQ(16, uint128_t(65) / uint128_t(4));
+
+ // Check that dividing by zero throws.
+ EXPECT_THROW(uint128_t(1) / 0, std::overflow_error);
+
+ // Check that underflowing results in a positive number for uint128_t.
+ EXPECT_LT(0, uint128_t(0) - 1);
+
+ // Check that UINT64_MAX < UINT128_MAX.
+ EXPECT_LT(uint64_t(0) - 1, uint128_t(0) - 1);
+
+ // Check that uint128_t is default-initialized to zero. Not a strict
+ // requirement, but something that the current implementation ensures.
+ uint128_t u128;
+ EXPECT_EQ(0, u128);
+
+ // Check that overflowing on big numbers has the correct result.
+ u128 = uint128_t(0) - 1;
+ EXPECT_EQ(u128 - 1, u128 + u128);
+}
+
+} // namespace
diff --git a/src/lib/util/tests/boost_time_utils_unittest.cc b/src/lib/util/tests/boost_time_utils_unittest.cc
new file mode 100644
index 0000000..e7e8c81
--- /dev/null
+++ b/src/lib/util/tests/boost_time_utils_unittest.cc
@@ -0,0 +1,99 @@
+// Copyright (C) 2015-2019 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/boost_time_utils.h>
+
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace boost::posix_time;
+using namespace boost::gregorian;
+
+/// Check the ptimeToText() function returns a numeric month.
+/// Note durationToText() is called by ptimeToText() so is tested too.
+
+// The Posix time epoch is 1970
+TEST(BoostTimeUtilsTest, epoch) {
+ time_t tepoch = 0;
+ ptime pepoch = from_time_t(tepoch);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("1970-01-01 00:00:00");
+ std::string sepoch;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point.
+ expected.push_back('.');
+ }
+
+ if (precision >= 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sepoch = ptimeToText(pepoch, precision);
+ EXPECT_EQ(expected, sepoch) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sepoch = ptimeToText(pepoch);
+ EXPECT_EQ(expected, sepoch);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sepoch = ptimeToText(pepoch, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sepoch);
+
+}
+
+// The 2015 Bastille day
+TEST(BoostTimeUtilsTest, bastilleDay) {
+ time_duration tdbast =
+ hours(12) + minutes(13) + seconds(14) + milliseconds(500);
+ ptime pbast(date(2015, Jul, 14), tdbast);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("2015-07-14 12:13:14");
+ std::string sbast;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point
+ // and the digit 5 (i.e. 500 ms = .5 secs).
+ expected.push_back('.');
+ expected.push_back('5');
+ } else if (precision > 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sbast = ptimeToText(pbast, precision);
+ EXPECT_EQ(expected, sbast) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sbast = ptimeToText(pbast);
+ EXPECT_EQ(expected, sbast);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sbast = ptimeToText(pbast, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sbast);
+}
diff --git a/src/lib/util/tests/buffer_unittest.cc b/src/lib/util/tests/buffer_unittest.cc
new file mode 100644
index 0000000..1f63167
--- /dev/null
+++ b/src/lib/util/tests/buffer_unittest.cc
@@ -0,0 +1,341 @@
+// Copyright (C) 2009-2020 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 <exceptions/exceptions.h>
+
+#ifdef EXPECT_DEATH
+#include <util/unittests/resource.h>
+#include <util/unittests/check_valgrind.h>
+#endif /* EXPECT_DEATH */
+
+#include <util/buffer.h>
+
+using namespace isc;
+
+namespace {
+
+using isc::util::InputBuffer;
+using isc::util::OutputBuffer;
+
+class BufferTest : public ::testing::Test {
+protected:
+ BufferTest() : ibuffer(testdata, sizeof(testdata)), obuffer(0),
+ expected_size(0)
+ {
+ data16 = (2 << 8) | 3;
+ data32 = (4 << 24) | (5 << 16) | (6 << 8) | 7;
+ memset(vdata, 0, sizeof(testdata));
+ }
+
+ InputBuffer ibuffer;
+ OutputBuffer obuffer;
+ static const uint8_t testdata[5];
+ uint8_t vdata[sizeof(testdata)];
+ size_t expected_size;
+ uint16_t data16;
+ uint32_t data32;
+};
+
+const uint8_t BufferTest::testdata[5] = {1, 2, 3, 4, 5};
+
+TEST_F(BufferTest, inputBufferRead) {
+ EXPECT_EQ(5, ibuffer.getLength());
+ EXPECT_EQ(1, ibuffer.readUint8());
+ EXPECT_EQ(1, ibuffer.getPosition());
+ data16 = ibuffer.readUint16();
+ EXPECT_EQ((2 << 8) | 3, data16);
+ EXPECT_EQ(3, ibuffer.getPosition());
+ ibuffer.setPosition(1);
+ EXPECT_EQ(1, ibuffer.getPosition());
+ data32 = ibuffer.readUint32();
+ EXPECT_EQ((2 << 24) | (3 << 16) | (4 << 8) | 5, data32);
+ ibuffer.setPosition(0);
+ memset(vdata, 0, sizeof(vdata));
+ ibuffer.readData(vdata, sizeof(vdata));
+ EXPECT_EQ(0, memcmp(vdata, testdata, sizeof(testdata)));
+}
+
+TEST_F(BufferTest, inputBufferException) {
+ EXPECT_THROW(ibuffer.setPosition(6), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata));
+ EXPECT_THROW(ibuffer.readUint8(), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata) - 1);
+ EXPECT_THROW(ibuffer.readUint16(), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata) - 3);
+ EXPECT_THROW(ibuffer.readUint32(), isc::util::InvalidBufferPosition);
+
+ ibuffer.setPosition(sizeof(testdata) - 4);
+ EXPECT_THROW(ibuffer.readData(vdata, sizeof(vdata)),
+ isc::util::InvalidBufferPosition);
+}
+
+TEST_F(BufferTest, outputBufferExtend) {
+ EXPECT_EQ(0, obuffer.getCapacity());
+ EXPECT_EQ(0, obuffer.getLength());
+ obuffer.writeUint8(10);
+ EXPECT_LT(0, obuffer.getCapacity());
+ EXPECT_EQ(1, obuffer.getLength());
+}
+
+TEST_F(BufferTest, outputBufferWrite) {
+ const uint8_t* cp;
+
+ obuffer.writeUint8(1);
+ expected_size += sizeof(uint8_t);
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(1, *cp);
+
+ obuffer.writeUint16(data16);
+ expected_size += sizeof(data16);
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ EXPECT_EQ(2, *(cp + 1));
+ EXPECT_EQ(3, *(cp + 2));
+
+ obuffer.writeUint32(data32);
+ expected_size += sizeof(data32);
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ EXPECT_EQ(4, *(cp + 3));
+ EXPECT_EQ(5, *(cp + 4));
+ EXPECT_EQ(6, *(cp + 5));
+ EXPECT_EQ(7, *(cp + 6));
+
+ obuffer.writeData(testdata, sizeof(testdata));
+ expected_size += sizeof(testdata);
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(0, memcmp(cp + 7, testdata, sizeof(testdata)));
+}
+
+TEST_F(BufferTest, outputBufferWriteat) {
+ obuffer.writeUint32(data32);
+ expected_size += sizeof(data32);
+
+ // overwrite 2nd byte
+ obuffer.writeUint8At(4, 1);
+ EXPECT_EQ(expected_size, obuffer.getLength()); // length shouldn't change
+ const uint8_t* cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(4, *(cp + 1));
+
+ // overwrite 2nd and 3rd bytes
+ obuffer.writeUint16At(data16, 1);
+ EXPECT_EQ(expected_size, obuffer.getLength()); // length shouldn't change
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(2, *(cp + 1));
+ EXPECT_EQ(3, *(cp + 2));
+
+ // overwrite 3rd and 4th bytes
+ obuffer.writeUint16At(data16, 2);
+ EXPECT_EQ(expected_size, obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_EQ(2, *(cp + 2));
+ EXPECT_EQ(3, *(cp + 3));
+
+ EXPECT_THROW(obuffer.writeUint8At(data16, 5),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint8At(data16, 4),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint16At(data16, 3),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint16At(data16, 4),
+ isc::util::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint16At(data16, 5),
+ isc::util::InvalidBufferPosition);
+}
+
+TEST_F(BufferTest, outputBufferSkip) {
+ obuffer.skip(4);
+ EXPECT_EQ(4, obuffer.getLength());
+
+ obuffer.skip(2);
+ EXPECT_EQ(6, obuffer.getLength());
+}
+
+TEST_F(BufferTest, outputBufferTrim) {
+ obuffer.writeData(testdata, sizeof(testdata));
+ EXPECT_EQ(5, obuffer.getLength());
+
+ obuffer.trim(1);
+ EXPECT_EQ(4, obuffer.getLength());
+
+ obuffer.trim(2);
+ EXPECT_EQ(2, obuffer.getLength());
+
+ EXPECT_THROW(obuffer.trim(3), OutOfRange);
+}
+
+TEST_F(BufferTest, outputBufferReadAt) {
+ obuffer.writeData(testdata, sizeof(testdata));
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], obuffer[i]);
+ }
+ EXPECT_THROW(obuffer[sizeof(testdata)], isc::util::InvalidBufferPosition);
+}
+
+TEST_F(BufferTest, outputBufferClear) {
+ const uint8_t* cp;
+
+ obuffer.writeData(testdata, sizeof(testdata));
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ obuffer.clear();
+ EXPECT_EQ(0, obuffer.getLength());
+ EXPECT_EQ(*cp, 1);
+}
+
+TEST_F(BufferTest, outputBufferWipe) {
+ const uint8_t* cp;
+
+ obuffer.writeData(testdata, sizeof(testdata));
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ obuffer.wipe();
+ EXPECT_EQ(0, obuffer.getLength());
+ EXPECT_EQ(*cp, 0);
+}
+
+TEST_F(BufferTest, emptyOutputBufferWipe) {
+ ASSERT_NO_THROW(obuffer.wipe());
+ EXPECT_EQ(0, obuffer.getLength());
+}
+
+TEST_F(BufferTest, outputBufferCopy) {
+ obuffer.writeData(testdata, sizeof(testdata));
+
+ EXPECT_NO_THROW({
+ OutputBuffer copy(obuffer);
+ ASSERT_EQ(sizeof(testdata), copy.getLength());
+ ASSERT_NE(obuffer.getData(), copy.getData());
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], copy[i]);
+ if (i + 1 < sizeof(testdata)) {
+ obuffer.writeUint16At(0, i);
+ }
+ EXPECT_EQ(testdata[i], copy[i]);
+ }
+ obuffer.clear();
+ ASSERT_EQ(sizeof(testdata), copy.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputEmptyBufferCopy) {
+ EXPECT_NO_THROW({
+ OutputBuffer copy(obuffer);
+ ASSERT_EQ(0, copy.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputBufferAssign) {
+ OutputBuffer another(0);
+ another.clear();
+ obuffer.writeData(testdata, sizeof(testdata));
+
+ EXPECT_NO_THROW({
+ another = obuffer;
+ ASSERT_EQ(sizeof(testdata), another.getLength());
+ ASSERT_NE(obuffer.getData(), another.getData());
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], another[i]);
+ if (i + 1 < sizeof(testdata)) {
+ obuffer.writeUint16At(0, i);
+ }
+ EXPECT_EQ(testdata[i], another[i]);
+ }
+ obuffer.clear();
+ ASSERT_EQ(sizeof(testdata), another.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputEmptyBufferAssign) {
+ OutputBuffer copy(0);
+ ASSERT_NO_THROW({
+ copy = obuffer;
+ });
+ ASSERT_EQ(0, copy.getLength());
+ EXPECT_EQ(NULL, copy.getData());
+}
+
+// Check assign to self doesn't break stuff
+TEST_F(BufferTest, outputBufferAssignSelf) {
+ EXPECT_NO_THROW(obuffer = obuffer);
+}
+
+TEST_F(BufferTest, outputBufferZeroSize) {
+ // Some OSes might return NULL on malloc for 0 size, so check it works
+ EXPECT_NO_THROW({
+ OutputBuffer first(0);
+ OutputBuffer copy(first);
+ OutputBuffer second(0);
+ second = first;
+ });
+}
+
+TEST_F(BufferTest, inputBufferReadVectorAll) {
+ std::vector<uint8_t> vec;
+
+ // check that vector can read the whole buffer
+ ibuffer.readVector(vec, 5);
+
+ ASSERT_EQ(5, vec.size());
+ EXPECT_EQ(0, memcmp(&vec[0], testdata, 5));
+
+ // ibuffer is 5 bytes long. Can't read past it.
+ EXPECT_THROW(
+ ibuffer.readVector(vec, 1),
+ isc::util::InvalidBufferPosition
+ );
+}
+
+TEST_F(BufferTest, inputBufferReadVectorChunks) {
+ std::vector<uint8_t> vec;
+
+ // check that vector can read the whole buffer
+ ibuffer.readVector(vec, 3);
+ EXPECT_EQ(3, vec.size());
+
+ EXPECT_EQ(0, memcmp(&vec[0], testdata, 3));
+
+ EXPECT_NO_THROW(
+ ibuffer.readVector(vec, 2)
+ );
+
+ EXPECT_EQ(0, memcmp(&vec[0], testdata+3, 2));
+}
+
+// Tests whether uint64 can be written properly.
+TEST_F(BufferTest, writeUint64) {
+
+ uint64_t val1 = 0x0102030405060708ul;
+ uint64_t val2 = 0xfffffffffffffffful;
+
+ uint8_t exp_val1[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ uint8_t exp_val2[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+ const uint8_t* cp;
+
+ obuffer.writeUint64(val1);
+ ASSERT_EQ(sizeof(uint64_t), obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_TRUE(cp);
+ EXPECT_FALSE(memcmp(exp_val1, obuffer.getData(), sizeof(uint64_t)));
+
+ EXPECT_NO_THROW(obuffer.clear());
+
+ obuffer.writeUint64(val2);
+ ASSERT_EQ(sizeof(uint64_t), obuffer.getLength());
+ cp = static_cast<const uint8_t*>(obuffer.getData());
+ EXPECT_TRUE(cp);
+ EXPECT_FALSE(memcmp(exp_val2, obuffer.getData(), sizeof(uint64_t)));
+}
+
+}
diff --git a/src/lib/util/tests/chrono_time_utils_unittest.cc b/src/lib/util/tests/chrono_time_utils_unittest.cc
new file mode 100644
index 0000000..e9d2917
--- /dev/null
+++ b/src/lib/util/tests/chrono_time_utils_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright (C) 2015-2020 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/chrono_time_utils.h>
+
+#include <string.h>
+#include <time.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace std::chrono;
+using namespace isc::util;
+
+/// Check the clockToText() function returns a numeric month.
+TEST(ChronoTimeUtilsTest, epoch) {
+ // The system clock is a wall clock using the local time zone so
+ // the epoch is zero only at some places or of course if the
+ // system is in UTC...
+ struct tm epoch;
+ memset(&epoch, 0, sizeof(epoch));
+ epoch.tm_year = 70;
+ epoch.tm_mday = 1;
+ epoch.tm_isdst = -1;
+ time_t tepoch = mktime(&epoch);
+ system_clock::time_point pepoch = system_clock::from_time_t(tepoch);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("1970-01-01 00:00:00");
+ std::string sepoch;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point.
+ expected.push_back('.');
+ }
+
+ if (precision >= 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sepoch = clockToText(pepoch, precision);
+ EXPECT_EQ(expected, sepoch) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sepoch = clockToText(pepoch);
+ EXPECT_EQ(expected, sepoch);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sepoch = clockToText(pepoch, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sepoch);
+
+}
+
+/// Check the durationToText() works as expected.
+/// Note durationToText() is not called by clockToText().
+TEST(ChronoTimeUtilsTest, duration) {
+ system_clock::duration p123 = hours(1) + minutes(2) + seconds(3);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("01:02:03");
+ std::string s123;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point.
+ expected.push_back('.');
+ }
+
+ if (precision >= 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ s123 = durationToText(p123, precision);
+ EXPECT_EQ(expected, s123) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ s123 = durationToText(p123);
+ EXPECT_EQ(expected, s123);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ s123 = durationToText(p123, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, s123);
+}
+
+// The 2015 Bastille day
+TEST(ChronoTimeUtilsTest, bastilleDay) {
+ struct tm tm;
+ tm.tm_year = 2015 - 1900;
+ tm.tm_mon = 7 - 1;
+ tm.tm_mday = 14;
+ tm.tm_hour = 12;
+ tm.tm_min = 13;
+ tm.tm_sec = 14;
+ tm.tm_isdst = -1;
+ time_t tbast = mktime(&tm);
+ system_clock::time_point tpbast = system_clock::from_time_t(tbast);
+ tpbast += milliseconds(500);
+
+ // We're going to loop through precision values starting with 0 through
+ // the max supported precision. Each pass should after the first, should
+ // add an additional level of precision: secs, secs/10, secs/100,
+ // secs/1000 and so on. The initial string has no fraction seconds.
+ std::string expected("2015-07-14 12:13:14");
+ std::string sbast;
+ for (int precision = 0; precision <= MAX_FSECS_PRECISION; ++precision) {
+ if (precision == 1) {
+ // Adding fractional seconds so we need append a decimal point
+ // and the digit 5 (i.e. 500 ms = .5 secs).
+ expected.push_back('.');
+ expected.push_back('5');
+ } else if (precision > 1) {
+ // Adding an additional level of precision, append a zero.
+ expected.push_back('0');
+ }
+
+ // Now let's see if we get the correct precision in the text.
+ sbast = clockToText(tpbast, precision);
+ EXPECT_EQ(expected, sbast) << " test precision:" << precision;
+ }
+
+ // Expected string should have same precision as default, so
+ // test the default.
+ sbast = clockToText(tpbast);
+ EXPECT_EQ(expected, sbast);
+
+ // Now test a requested precision beyond default. We should
+ // get the default precision.
+ sbast = clockToText(tpbast, MAX_FSECS_PRECISION + 1);
+ EXPECT_EQ(expected, sbast);
+}
+
+// Try steady clock duration.
+TEST(ChronoTimeUtilsTest, steadyClock) {
+ steady_clock::duration p12345 = hours(1) + minutes(2) + seconds(3) +
+ milliseconds(4) + microseconds(5);
+ std::string expected("01:02:03.004005");
+ std::string s12345 = durationToText(p12345, 6);
+ EXPECT_EQ(expected, s12345);
+}
diff --git a/src/lib/util/tests/csv_file_unittest.cc b/src/lib/util/tests/csv_file_unittest.cc
new file mode 100644
index 0000000..f55ec55
--- /dev/null
+++ b/src/lib/util/tests/csv_file_unittest.cc
@@ -0,0 +1,700 @@
+// Copyright (C) 2014-2021 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/csv_file.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+namespace {
+
+using namespace isc::util;
+
+// This test exercises escaping and unescaping of characters.
+TEST(CSVRowTest, escapeUnescape) {
+ std::string orig(",FO^O\\,B?,AR,");
+
+ // We'll escape commas, question marks, and carets.
+ std::string escaped = CSVRow::escapeCharacters(orig, ",?^");
+ EXPECT_EQ ("&#x2cFO&#x5eO\\&#x2cB&#x3f&#x2cAR&#x2c", escaped);
+
+ // Now make sure we can unescape it correctly.
+ std::string unescaped = CSVRow::unescapeCharacters(escaped);
+ EXPECT_EQ (orig, unescaped);
+
+ // Make sure that an incident occurrence of just the escape tag
+ // is left intact.
+ orig = ("no&#xescape");
+ escaped = CSVRow::escapeCharacters(orig, ",");
+ unescaped = CSVRow::unescapeCharacters(orig);
+ EXPECT_EQ (orig, unescaped);
+
+ // Make sure that an incidental occurrence of a valid
+ // escape tag sequence left intact.
+ orig = ("no&#x2cescape");
+ escaped = CSVRow::escapeCharacters(orig, ",");
+ unescaped = CSVRow::unescapeCharacters(escaped);
+ EXPECT_EQ (orig, unescaped);
+}
+
+// This test checks that the single data row is parsed.
+TEST(CSVRow, parse) {
+ CSVRow row0("foo,bar,foo-bar");
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("foo", row0.readAt(0));
+ EXPECT_EQ("bar", row0.readAt(1));
+ EXPECT_EQ("foo-bar", row0.readAt(2));
+
+ row0.parse("bar,,foo-bar");
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("bar", row0.readAt(0));
+ EXPECT_TRUE(row0.readAt(1).empty());
+ EXPECT_EQ("foo-bar", row0.readAt(2));
+
+ row0.parse("bar,foo&#x2c-bar");
+ ASSERT_EQ(2, row0.getValuesCount());
+ EXPECT_EQ("bar", row0.readAt(0));
+ // Read the second column as-is and escaped
+ EXPECT_EQ("foo&#x2c-bar", row0.readAt(1));
+ EXPECT_EQ("foo,-bar", row0.readAtEscaped(1));
+
+ CSVRow row1("foo-bar|foo|bar|", '|');
+ ASSERT_EQ(4, row1.getValuesCount());
+ EXPECT_EQ("foo-bar", row1.readAt(0));
+ EXPECT_EQ("foo", row1.readAt(1));
+ EXPECT_EQ("bar", row1.readAt(2));
+ EXPECT_TRUE(row1.readAt(3).empty());
+
+ row1.parse("");
+ ASSERT_EQ(1, row1.getValuesCount());
+ EXPECT_TRUE(row1.readAt(0).empty());
+}
+
+// Verifies that empty columns are handled correctly.
+TEST(CSVRow, emptyColumns) {
+ // Should get four columns, all blank except column the second one.
+ CSVRow row(",one,,");
+ ASSERT_EQ(4, row.getValuesCount());
+ EXPECT_EQ("", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("", row.readAt(2));
+ EXPECT_EQ("", row.readAt(3));
+}
+
+// Verifies that empty columns are handled correctly.
+TEST(CSVRow, oneColumn) {
+ // Should get one column
+ CSVRow row("zero");
+ ASSERT_EQ(1, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+}
+
+// This test checks that the text representation of the CSV row
+// is created correctly.
+TEST(CSVRow, render) {
+ CSVRow row0(3);
+ row0.writeAt(0, "foo");
+ row0.writeAt(1, "foo-bar");
+ row0.writeAt(2, "bar");
+
+ std::string text;
+ ASSERT_NO_THROW(text = row0.render());
+ EXPECT_EQ(text, "foo,foo-bar,bar");
+
+ CSVRow row1(4, ';');
+ row1.writeAt(0, "foo");
+ row1.writeAt(2, "bar");
+ row1.writeAt(3, 10);
+
+ ASSERT_NO_THROW(text = row1.render());
+ EXPECT_EQ(text, "foo;;bar;10");
+
+ CSVRow row2(0);
+ ASSERT_NO_THROW(text = row2.render());
+ EXPECT_TRUE(text.empty());
+}
+
+// This test checks that the data values can be set for the CSV row.
+TEST(CSVRow, writeAt) {
+ CSVRow row(4);
+ row.writeAt(0, 10);
+ row.writeAt(1, "foo");
+ row.writeAt(2, "bar");
+ row.writeAtEscaped(3, "bar,one,two");
+
+ EXPECT_EQ("10", row.readAt(0));
+ EXPECT_EQ("foo", row.readAt(1));
+ EXPECT_EQ("bar", row.readAt(2));
+ // Read third column as-is and unescaped
+ EXPECT_EQ("bar&#x2cone&#x2ctwo", row.readAt(3));
+ EXPECT_EQ("bar,one,two", row.readAtEscaped(3));
+
+ EXPECT_THROW(row.writeAt(4, 20), CSVFileError);
+ EXPECT_THROW(row.writeAt(4, "foo"), CSVFileError);
+}
+
+// Checks whether writeAt() and append() can be mixed together.
+TEST(CSVRow, append) {
+ CSVRow row(3);
+
+ EXPECT_EQ(3, row.getValuesCount());
+
+ row.writeAt(0, "alpha");
+ ASSERT_NO_THROW(row.append("delta"));
+ EXPECT_EQ(4, row.getValuesCount());
+ row.writeAt(1, "beta");
+ row.writeAt(2, "gamma");
+ ASSERT_NO_THROW(row.append("epsilon"));
+ EXPECT_EQ(5, row.getValuesCount());
+
+ std::string text;
+ ASSERT_NO_THROW(text = row.render());
+ EXPECT_EQ("alpha,beta,gamma,delta,epsilon", text);
+}
+
+// This test checks that a row can be trimmed of
+// a given number of elements
+TEST(CSVRow, trim) {
+ CSVRow row("zero,one,two,three,four");
+ ASSERT_EQ(5, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("two", row.readAt(2));
+ EXPECT_EQ("three", row.readAt(3));
+ EXPECT_EQ("four", row.readAt(4));
+
+ ASSERT_THROW(row.trim(10), CSVFileError);
+
+ // Verify that we can erase just one
+ ASSERT_NO_THROW(row.trim(1));
+ ASSERT_EQ(4, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+ EXPECT_EQ("two", row.readAt(2));
+ EXPECT_EQ("three", row.readAt(3));
+
+ // Verify we can trim more than one
+ ASSERT_NO_THROW(row.trim(2));
+ ASSERT_EQ(2, row.getValuesCount());
+ EXPECT_EQ("zero", row.readAt(0));
+ EXPECT_EQ("one", row.readAt(1));
+}
+
+/// @brief Test fixture class for testing operations on CSV file.
+///
+/// It implements basic operations on files, such as reading writing
+/// file removal and checking presence of the file. This is used by
+/// unit tests to verify correctness of the file created by the
+/// CSVFile class.
+class CSVFileTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets the path to the CSV file used throughout the tests.
+ /// The name of the file is test.csv and it is located in the
+ /// current build folder.
+ ///
+ /// It also deletes any dangling files after previous tests.
+ CSVFileTest();
+
+ /// @brief Destructor.
+ ///
+ /// Deletes the test CSV file if any.
+ virtual ~CSVFileTest();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Check if test file exists on disk.
+ bool exists() const;
+
+ /// @brief Reads whole CSV file.
+ ///
+ /// @return Contents of the file.
+ std::string readFile() const;
+
+ /// @brief Removes existing file (if any).
+ int removeFile() const;
+
+ /// @brief Creates file with contents.
+ ///
+ /// @param contents Contents of the file.
+ void writeFile(const std::string& contents) const;
+
+ /// @brief Absolute path to the file used in the tests.
+ std::string testfile_;
+
+};
+
+CSVFileTest::CSVFileTest()
+ : testfile_(absolutePath("test.csv")) {
+ static_cast<void>(removeFile());
+}
+
+CSVFileTest::~CSVFileTest() {
+ static_cast<void>(removeFile());
+}
+
+std::string
+CSVFileTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+ return (s.str());
+}
+
+bool
+CSVFileTest::exists() const {
+ std::ifstream fs(testfile_.c_str());
+ bool ok = fs.good();
+ fs.close();
+ return (ok);
+}
+
+std::string
+CSVFileTest::readFile() const {
+ std::ifstream fs(testfile_.c_str());
+ if (!fs.is_open()) {
+ return ("");
+ }
+ std::string contents((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+ return (contents);
+}
+
+int
+CSVFileTest::removeFile() const {
+ return (remove(testfile_.c_str()));
+}
+
+void
+CSVFileTest::writeFile(const std::string& contents) const {
+ std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+ if (fs.is_open()) {
+ fs << contents;
+ fs.close();
+ }
+}
+
+// This test checks that the function which is used to add columns of the
+// CSV file works as expected.
+TEST_F(CSVFileTest, addColumn) {
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ // Add two columns.
+ ASSERT_NO_THROW(csv->addColumn("animal"));
+ ASSERT_NO_THROW(csv->addColumn("color"));
+ // Make sure we can't add duplicates.
+ EXPECT_THROW(csv->addColumn("animal"), CSVFileError);
+ EXPECT_THROW(csv->addColumn("color"), CSVFileError);
+ // But we should still be able to add unique columns.
+ EXPECT_NO_THROW(csv->addColumn("age"));
+ EXPECT_NO_THROW(csv->addColumn("comments"));
+ // Assert that the file is opened, because the rest of the test relies
+ // on this.
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ // Make sure we can't add columns (even unique) when the file is open.
+ ASSERT_THROW(csv->addColumn("zoo"), CSVFileError);
+ // Close the file.
+ ASSERT_NO_THROW(csv->close());
+ // And check that now it is possible to add the column.
+ EXPECT_NO_THROW(csv->addColumn("zoo"));
+}
+
+// This test checks that the appropriate file name is initialized.
+TEST_F(CSVFileTest, getFilename) {
+ CSVFile csv(testfile_);
+ EXPECT_EQ(testfile_, csv.getFilename());
+}
+
+// This test checks that the file can be opened, its whole content is
+// parsed correctly and data may be appended. It also checks that empty
+// row is returned when EOF is reached.
+TEST_F(CSVFileTest, openReadAllWrite) {
+ // Create a new CSV file that contains a header and two data rows.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ // Open this file and check that the header is parsed.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+ ASSERT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ("animal", csv->getColumnName(0));
+ EXPECT_EQ("age", csv->getColumnName(1));
+ EXPECT_EQ("color", csv->getColumnName(2));
+
+ // Read first row.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ ASSERT_EQ(3, row.getValuesCount());
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("10", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+
+ // Read second row.
+ ASSERT_TRUE(csv->next(row));
+ ASSERT_EQ(3, row.getValuesCount());
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("15", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+
+ // There is no 3rd row, so the empty one should be returned.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // It should be fine to read again, but again empty row should be returned.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Now, let's try to append something to this file.
+ CSVRow row_write(3);
+ row_write.writeAt(0, "dog");
+ row_write.writeAt(1, 2);
+ row_write.writeAt(2, "blue");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ // Check the file contents are correct.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n",
+ readFile());
+
+ // Any attempt to read from the file or write to it should now fail.
+ EXPECT_FALSE(csv->next(row));
+ EXPECT_THROW(csv->append(row_write), CSVFileError);
+
+ CSVRow row_write2(3);
+ row_write2.writeAt(0, "bird");
+ row_write2.writeAt(1, 3);
+ row_write2.writeAt(2, "purple");
+
+ // Reopen the file, seek to the end of file so as we can append
+ // some more data.
+ ASSERT_NO_THROW(csv->open(true));
+ // The file pointer should be at the end of file, so an attempt
+ // to read should result in an empty row.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+ // We should be able to append new data.
+ ASSERT_NO_THROW(csv->append(row_write2));
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+ // Check that new data has been appended.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n"
+ "bird,3,purple\n",
+ readFile());
+}
+
+// This test checks that contents may be appended to a file which hasn't
+// been fully parsed/read.
+TEST_F(CSVFileTest, openReadPartialWrite) {
+ // Create a CSV file with two rows in it.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ // Open this file.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+
+ // Read the first row.
+ CSVRow row0(0);
+ ASSERT_NO_THROW(csv->next(row0));
+ ASSERT_EQ(3, row0.getValuesCount());
+ EXPECT_EQ("cat", row0.readAt(0));
+ EXPECT_EQ("10", row0.readAt(1));
+ EXPECT_EQ("white", row0.readAt(2));
+
+ // There is still second row to be read. But, it should be possible to
+ // skip reading it and append new row to the end of file.
+ CSVRow row_write(3);
+ row_write.writeAt(0, "dog");
+ row_write.writeAt(1, 2);
+ row_write.writeAt(2, "blue");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // At this point, the file pointer is at the end of file, so reading
+ // should return empty row.
+ CSVRow row1(0);
+ ASSERT_NO_THROW(csv->next(row1));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row1);
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ // Check that there are two initial lines and one new there.
+ EXPECT_EQ("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n"
+ "dog,2,blue\n",
+ readFile());
+
+}
+
+// This test checks that the new CSV file is created and header
+// is written to it. It also checks that data rows can be
+// appended to it.
+TEST_F(CSVFileTest, recreate) {
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("color");
+ csv->addColumn("age");
+ csv->addColumn("comments");
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ CSVRow row0(4);
+ row0.writeAt(0, "dog");
+ row0.writeAt(1, "grey");
+ row0.writeAt(2, 3);
+ row0.writeAt(3, "nice one");
+ ASSERT_NO_THROW(csv->append(row0));
+
+ CSVRow row1(4);
+ row1.writeAt(0, "cat");
+ row1.writeAt(1, "black");
+ row1.writeAt(2, 2);
+ ASSERT_NO_THROW(csv->append(row1));
+
+ ASSERT_NO_THROW(csv->flush());
+ csv->close();
+
+ EXPECT_EQ("animal,color,age,comments\n"
+ "dog,grey,3,nice one\n"
+ "cat,black,2,\n",
+ readFile());
+}
+
+// This test checks that the error is reported when the size of the row being
+// read doesn't match the number of columns of the CSV file.
+TEST_F(CSVFileTest, validate) {
+ // Create CSV file with 2 invalid rows in it: one too long, one too short.
+ // Apart from that, there are two valid columns that should be read
+ // successfully.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow,black\n"
+ "dog,3,green\n"
+ "elephant,11\n");
+
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ ASSERT_NO_THROW(csv->open());
+ // First row is correct.
+ CSVRow row0;
+ ASSERT_TRUE(csv->next(row0));
+ EXPECT_EQ("cat", row0.readAt(0));
+ EXPECT_EQ("10", row0.readAt(1));
+ EXPECT_EQ("white", row0.readAt(2));
+ EXPECT_EQ("success", csv->getReadMsg());
+ // This row is too long.
+ CSVRow row1;
+ EXPECT_FALSE(csv->next(row1));
+ EXPECT_NE("success", csv->getReadMsg());
+ // This row is correct.
+ CSVRow row2;
+ ASSERT_TRUE(csv->next(row2));
+ EXPECT_EQ("dog", row2.readAt(0));
+ EXPECT_EQ("3", row2.readAt(1));
+ EXPECT_EQ("green", row2.readAt(2));
+ EXPECT_EQ("success", csv->getReadMsg());
+ // This row is too short.
+ CSVRow row3;
+ EXPECT_FALSE(csv->next(row3));
+ EXPECT_NE("success", csv->getReadMsg());
+}
+
+// Test test checks that exception is thrown when the header of the CSV file
+// parsed, doesn't match the columns specified.
+TEST_F(CSVFileTest, validateHeader) {
+ // Create CSV file with 3 columns.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow,black\n");
+
+ // Invalid order of columns.
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ csv->addColumn("color");
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ EXPECT_THROW(csv->open(), CSVFileError);
+
+ // Too many columns.
+ csv.reset(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ csv->addColumn("color");
+ csv->addColumn("notes");
+ EXPECT_THROW(csv->open(), CSVFileError);
+
+ // Too few columns.
+ csv.reset(new CSVFile(testfile_));
+ csv->addColumn("animal");
+ csv->addColumn("age");
+ EXPECT_THROW(csv->open(), CSVFileError);
+}
+
+// This test checks that the exists method of the CSVFile class properly
+// checks that the file exists.
+TEST_F(CSVFileTest, exists) {
+ // Create a new CSV file that contains a header and two data rows.
+ writeFile("animal,age,color\n"
+ "cat,10,white\n"
+ "lion,15,yellow\n");
+
+ boost::scoped_ptr<CSVFile> csv(new CSVFile(testfile_));
+ // The CSVFile class should return true even if the file hasn't been
+ // opened.
+ EXPECT_TRUE(csv->exists());
+ // Now open the file and make sure it still returns true.
+ ASSERT_NO_THROW(csv->open());
+ EXPECT_TRUE(csv->exists());
+
+ // Close the file and remove it.
+ csv->close();
+ EXPECT_EQ(0, removeFile());
+
+ // The file should not exist.
+ EXPECT_FALSE(csv->exists());
+}
+
+// Check that a single header without a trailing blank line can be parsed.
+TEST_F(CSVFileTest, parseHeaderWithoutTrailingBlankLine) {
+ // Create a new CSV file that only contains a header without a new line.
+ writeFile("animal,age,color");
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Attempt to read the next row which doesn't exist.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+}
+
+// Check that content without a trailing blank line can be parsed.
+TEST_F(CSVFileTest, parseContentWithoutTrailingBlankLine) {
+ // Now create a new CSV file that contains header plus data, but the last
+ // line is missing a new line.
+ writeFile("animal,age,color\n"
+ "cat,4,white\n"
+ "lion,8,yellow");
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Check the first data row.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("4", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Check the second data row.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("8", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Attempt to read the next row which doesn't exist.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+}
+
+// Check that blank lines are skipped when reading from a file.
+TEST_F(CSVFileTest, parseContentWithBlankLines) {
+ for (char const* const& content : {
+ // Single intermediary blank line
+ "animal,age,color\n"
+ "cat,4,white\n"
+ "\n"
+ "lion,8,yellow\n",
+
+ // Blank lines all over
+ "\n"
+ "\n"
+ "animal,age,color\n"
+ "\n"
+ "\n"
+ "cat,4,white\n"
+ "\n"
+ "\n"
+ "lion,8,yellow\n"
+ "\n"
+ "\n",
+ }) {
+ // Create a new CSV file.
+ writeFile(content);
+
+ // Open this file and check that the header is parsed.
+ CSVFile csv(testfile_);
+ ASSERT_NO_THROW(csv.open());
+ ASSERT_EQ(3, csv.getColumnCount());
+ EXPECT_EQ("animal", csv.getColumnName(0));
+ EXPECT_EQ("age", csv.getColumnName(1));
+ EXPECT_EQ("color", csv.getColumnName(2));
+
+ // Check the first data row.
+ CSVRow row;
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("4", row.readAt(1));
+ EXPECT_EQ("white", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Check the second non-blank data row.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("8", row.readAt(1));
+ EXPECT_EQ("yellow", row.readAt(2));
+ EXPECT_EQ("success", csv.getReadMsg());
+
+ // Attempt to read the next row which doesn't exist.
+ ASSERT_TRUE(csv.next(row));
+ EXPECT_EQ(CSVFile::EMPTY_ROW(), row);
+
+ // Close the file.
+ csv.close();
+ }
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/dhcp_space_unittest.cc b/src/lib/util/tests/dhcp_space_unittest.cc
new file mode 100644
index 0000000..e5269c4
--- /dev/null
+++ b/src/lib/util/tests/dhcp_space_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (C) 2022 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/dhcp_space.h>
+
+#include <cstring>
+
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc::util;
+
+TEST(DhcpSpace, cString) {
+ EXPECT_EQ(std::strcmp(cStringDhcpSpace<DHCPv4>(), "4"), 0);
+ EXPECT_EQ(std::strcmp(cStringDhcpSpace<DHCPv6>(), "6"), 0);
+}
+
+TEST(DhcpSpace, format) {
+ EXPECT_EQ(formatDhcpSpace<DHCPv4>("dhcp{}"), "dhcp4");
+ EXPECT_EQ(formatDhcpSpace<DHCPv6>("dhcp{}"), "dhcp6");
+
+ EXPECT_EQ(formatDhcpSpace<DHCPv4>("Dhcp{}.subnet{}"), "Dhcp4.subnet4");
+ EXPECT_EQ(formatDhcpSpace<DHCPv6>("Dhcp{}.subnet{}"), "Dhcp6.subnet6");
+}
+
+} // namespace
diff --git a/src/lib/util/tests/doubles_unittest.cc b/src/lib/util/tests/doubles_unittest.cc
new file mode 100644
index 0000000..1c17f90
--- /dev/null
+++ b/src/lib/util/tests/doubles_unittest.cc
@@ -0,0 +1,32 @@
+// Copyright (C) 2019,2021 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/doubles.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+// Exercises isc::util::areDoublesEquivalent().
+TEST(Doubles, areDoublesEquivalent) {
+ std::vector<uint8_t> data;
+
+ // Default tolerance is 0.000001
+ EXPECT_TRUE(areDoublesEquivalent( 1.0000000, 1.0000005));
+ EXPECT_FALSE(areDoublesEquivalent(1.0000000, 1.000005));
+
+ // Check custom tolerance.
+ EXPECT_TRUE(areDoublesEquivalent( 1.000, 1.005, 0.01));
+ EXPECT_FALSE(areDoublesEquivalent(1.000, 1.005, 0.001));
+}
+
+}
diff --git a/src/lib/util/tests/fd_share_tests.cc b/src/lib/util/tests/fd_share_tests.cc
new file mode 100644
index 0000000..f870ec2
--- /dev/null
+++ b/src/lib/util/tests/fd_share_tests.cc
@@ -0,0 +1,72 @@
+// 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 <util/io/fd.h>
+#include <util/io/fd_share.h>
+
+#include <util/unittests/check_valgrind.h>
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <cstdio>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// We test that we can transfer a pipe over other pipe
+TEST(FDShare, transfer) {
+
+ if (!isc::util::unittests::runningOnValgrind()) {
+ // Get a pipe and fork
+ int pipes[2];
+ ASSERT_NE(-1, socketpair(AF_UNIX, SOCK_STREAM, 0, pipes));
+ const pid_t sender(fork());
+ ASSERT_NE(-1, sender);
+ if (sender) { // We are in parent
+ // Close the other side of pipe, we want only writable one
+ EXPECT_NE(-1, close(pipes[0]));
+ // Get a process to check data
+ int fd(0);
+ const pid_t checker(check_output(&fd, "data", 4));
+ ASSERT_NE(-1, checker);
+ // Now, send the file descriptor, close it and close the pipe
+ EXPECT_NE(-1, send_fd(pipes[1], fd));
+ EXPECT_NE(-1, close(pipes[1]));
+ EXPECT_NE(-1, close(fd));
+ // Check both subprocesses ended well
+ EXPECT_TRUE(process_ok(sender));
+ EXPECT_TRUE(process_ok(checker));
+ } else { // We are in child. We do not use ASSERT here
+ // Close the write end, we only read
+ if (close(pipes[1])) {
+ exit(1);
+ }
+ // Get the file descriptor
+ const int fd(recv_fd(pipes[0]));
+ if (fd == -1) {
+ exit(1);
+ }
+ // This pipe is not needed
+ if (close(pipes[0])) {
+ exit(1);
+ }
+ // Send "data" through the received fd, close it and be done
+ if (!write_data(fd, "data", 4) || close(fd) == -1) {
+ exit(1);
+ }
+ exit(0);
+ }
+ }
+}
+
+}
diff --git a/src/lib/util/tests/fd_tests.cc b/src/lib/util/tests/fd_tests.cc
new file mode 100644
index 0000000..eee597c
--- /dev/null
+++ b/src/lib/util/tests/fd_tests.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2011-2021 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/check_valgrind.h>
+
+#include <util/io/fd.h>
+
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// Make sure the test is large enough and does not fit into one
+// read or write request
+const size_t TEST_DATA_SIZE = 8 * 1024 * 1024;
+
+class FDTest : public ::testing::Test {
+public:
+ unsigned char *data, *buffer;
+
+ /// @brief Constructor
+ FDTest() :
+ // We do not care what is inside, we just need it to be the same
+ data(new unsigned char[TEST_DATA_SIZE]),
+ buffer(NULL) {
+ memset(data, 0, TEST_DATA_SIZE);
+ }
+
+ /// @brief Destructor
+ ~FDTest() {
+ delete[] data;
+ delete[] buffer;
+ }
+};
+
+// Test we read what was sent
+TEST_F(FDTest, read) {
+ if (!isc::util::unittests::runningOnValgrind()) {
+ int read_pipe(0);
+ buffer = new unsigned char[TEST_DATA_SIZE];
+ pid_t feeder(provide_input(&read_pipe, data, TEST_DATA_SIZE));
+ ASSERT_GE(feeder, 0);
+ ssize_t received(read_data(read_pipe, buffer, TEST_DATA_SIZE));
+ EXPECT_TRUE(process_ok(feeder));
+ EXPECT_EQ(TEST_DATA_SIZE, received);
+ EXPECT_EQ(0, memcmp(data, buffer, received));
+ }
+}
+
+// Test we write the correct thing
+TEST_F(FDTest, write) {
+ if (!isc::util::unittests::runningOnValgrind()) {
+ int write_pipe(0);
+ pid_t checker(check_output(&write_pipe, data, TEST_DATA_SIZE));
+ ASSERT_GE(checker, 0);
+ EXPECT_TRUE(write_data(write_pipe, data, TEST_DATA_SIZE));
+ EXPECT_EQ(0, close(write_pipe));
+ EXPECT_TRUE(process_ok(checker));
+ }
+}
+
+}
diff --git a/src/lib/util/tests/file_utilities_unittest.cc b/src/lib/util/tests/file_utilities_unittest.cc
new file mode 100644
index 0000000..4ee9093
--- /dev/null
+++ b/src/lib/util/tests/file_utilities_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright (C) 2015-2022 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/file_utilities.h>
+#include <gtest/gtest.h>
+#include <fstream>
+
+using namespace isc;
+using namespace isc::util::file;
+using namespace std;
+
+namespace {
+
+/// @brief Test fixture class for testing operations on files.
+class FileUtilTest : public ::testing::Test {
+public:
+
+ /// @brief Destructor.
+ ///
+ /// Deletes the test file if any.
+ virtual ~FileUtilTest();
+};
+
+FileUtilTest::~FileUtilTest() {
+ string test_file_name = string(TEST_DATA_BUILDDIR) + "/fu.test";
+ static_cast<void>(remove(test_file_name.c_str()));
+}
+
+/// @brief Check an error is returned by getContent on not existent file.
+TEST_F(FileUtilTest, notExists) {
+ string file_name("/this/does/not/exists");
+ try {
+ string c = getContent(file_name);
+ FAIL() << "this test must throw before this line";
+ } catch (const BadValue& ex) {
+ string expected = "can't open file '" + file_name;
+ expected += "': No such file or directory";
+ EXPECT_EQ(string(ex.what()), expected);
+ } catch (const std::exception& ex) {
+ FAIL() << "unexpected exception: " << ex.what();
+ }
+}
+
+/// @note No easy can't stat.
+
+/// @brief Check an error is returned by getContent on not regular file.
+TEST_F(FileUtilTest, notRegular) {
+ string file_name("/");
+ try {
+ string c = getContent(file_name);
+ FAIL() << "this test must throw before this line";
+ } catch (const BadValue& ex) {
+ string expected = "'" + file_name + "' must be a regular file";
+ EXPECT_EQ(string(ex.what()), expected);
+ } catch (const std::exception& ex) {
+ FAIL() << "unexpected exception: " << ex.what();
+ }
+}
+
+/// @brief Check getContent works.
+TEST_F(FileUtilTest, basic) {
+ string file_name = string(TEST_DATA_BUILDDIR) + "/fu.test";
+ ofstream fs(file_name.c_str(), ofstream::out | ofstream::trunc);
+ ASSERT_TRUE(fs.is_open());
+ fs << "abdc";
+ fs.close();
+ string content;
+ EXPECT_NO_THROW(content = getContent(file_name));
+ EXPECT_EQ("abdc", content);
+}
+
+/// @brief Check isDir works.
+TEST_F(FileUtilTest, isDir) {
+ EXPECT_TRUE(isDir("/dev"));
+ EXPECT_FALSE(isDir("/dev/null"));
+ EXPECT_FALSE(isDir("/this/does/not/exists"));
+ EXPECT_FALSE(isDir("/etc/hosts"));
+}
+
+}
diff --git a/src/lib/util/tests/filename_unittest.cc b/src/lib/util/tests/filename_unittest.cc
new file mode 100644
index 0000000..39ff1f9
--- /dev/null
+++ b/src/lib/util/tests/filename_unittest.cc
@@ -0,0 +1,225 @@
+// 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 <string>
+
+#include <gtest/gtest.h>
+
+#include <util/filename.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace std;
+
+class FilenameTest : public ::testing::Test {
+protected:
+ FilenameTest()
+ {
+ }
+};
+
+
+// Check that the name can be changed
+
+TEST_F(FilenameTest, SetName) {
+ Filename fname("/a/b/c.d");
+ EXPECT_EQ("/a/b/c.d", fname.fullName());
+
+ fname.setName("test.txt");
+ EXPECT_EQ("test.txt", fname.fullName());
+}
+
+
+// Check that the components are split correctly. This is a check of the
+// private member split() method.
+
+TEST_F(FilenameTest, Components) {
+
+ // Complete name
+ Filename fname("/alpha/beta/gamma.delta");
+ EXPECT_EQ("/alpha/beta/", fname.directory());
+ EXPECT_EQ("gamma", fname.name());
+ EXPECT_EQ(".delta", fname.extension());
+ EXPECT_EQ("gamma.delta", fname.nameAndExtension());
+
+ // Directory only
+ fname.setName("/gamma/delta/");
+ EXPECT_EQ("/gamma/delta/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ // Filename only
+ fname.setName("epsilon");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("epsilon", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("epsilon", fname.nameAndExtension());
+
+ // Extension only
+ fname.setName(".zeta");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".zeta", fname.extension());
+ EXPECT_EQ(".zeta", fname.nameAndExtension());
+
+ // Missing directory
+ fname.setName("eta.theta");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("eta", fname.name());
+ EXPECT_EQ(".theta", fname.extension());
+ EXPECT_EQ("eta.theta", fname.nameAndExtension());
+
+ // Missing filename
+ fname.setName("/iota/.kappa");
+ EXPECT_EQ("/iota/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".kappa", fname.extension());
+ EXPECT_EQ(".kappa", fname.nameAndExtension());
+
+ // Missing extension
+ fname.setName("lambda/mu/nu");
+ EXPECT_EQ("lambda/mu/", fname.directory());
+ EXPECT_EQ("nu", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("nu", fname.nameAndExtension());
+
+ // Check that the decomposition can occur in the presence of leading and
+ // trailing spaces
+ fname.setName(" lambda/mu/nu\t ");
+ EXPECT_EQ("lambda/mu/", fname.directory());
+ EXPECT_EQ("nu", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("nu", fname.nameAndExtension());
+
+ // Empty string
+ fname.setName("");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ // ... and just spaces
+ fname.setName(" ");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ // Check corner cases - where separators are present, but strings are
+ // absent.
+ fname.setName("/");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ("", fname.extension());
+ EXPECT_EQ("", fname.nameAndExtension());
+
+ fname.setName(".");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(".", fname.nameAndExtension());
+
+ fname.setName("/.");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(".", fname.nameAndExtension());
+
+ // Note that the space is a valid filename here; only leading and trailing
+ // spaces should be trimmed.
+ fname.setName("/ .");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ(" ", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(" .", fname.nameAndExtension());
+
+ fname.setName(" / . ");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ(" ", fname.name());
+ EXPECT_EQ(".", fname.extension());
+ EXPECT_EQ(" .", fname.nameAndExtension());
+}
+
+// Check that the expansion with a default works.
+
+TEST_F(FilenameTest, ExpandWithDefault) {
+ Filename fname("a.b");
+
+ // These tests also check that the trimming of the default component is
+ // done properly.
+ EXPECT_EQ("/c/d/a.b", fname.expandWithDefault(" /c/d/ "));
+ EXPECT_EQ("/c/d/a.b", fname.expandWithDefault("/c/d/e.f"));
+ EXPECT_EQ("a.b", fname.expandWithDefault("e.f"));
+
+ fname.setName("/a/b/c");
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault(".d"));
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("x.d"));
+ EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("/s/t/u.d"));
+ EXPECT_EQ("/a/b/c", fname.expandWithDefault("/s/t/u"));
+
+ fname.setName(".h");
+ EXPECT_EQ("/a/b/c.h", fname.expandWithDefault("/a/b/c.msg"));
+}
+
+// Check that we can use this as a default in expanding a filename
+
+TEST_F(FilenameTest, UseAsDefault) {
+
+ Filename fname("a.b");
+
+ // These tests also check that the trimming of the default component is
+ // done properly.
+ EXPECT_EQ("/c/d/a.b", fname.useAsDefault(" /c/d/ "));
+ EXPECT_EQ("/c/d/e.f", fname.useAsDefault("/c/d/e.f"));
+ EXPECT_EQ("e.f", fname.useAsDefault("e.f"));
+
+ fname.setName("/a/b/c");
+ EXPECT_EQ("/a/b/c.d", fname.useAsDefault(".d"));
+ EXPECT_EQ("/a/b/x.d", fname.useAsDefault("x.d"));
+ EXPECT_EQ("/s/t/u.d", fname.useAsDefault("/s/t/u.d"));
+ EXPECT_EQ("/s/t/u", fname.useAsDefault("/s/t/u"));
+ EXPECT_EQ("/a/b/c", fname.useAsDefault(""));
+}
+
+TEST_F(FilenameTest, setDirectory) {
+ Filename fname("a.b");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("a.b", fname.fullName());
+ EXPECT_EQ("a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/just/some/dir/");
+ EXPECT_EQ("/just/some/dir/", fname.directory());
+ EXPECT_EQ("/just/some/dir/a.b", fname.fullName());
+ EXPECT_EQ("/just/some/dir/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/just/some/dir");
+ EXPECT_EQ("/just/some/dir/", fname.directory());
+ EXPECT_EQ("/just/some/dir/a.b", fname.fullName());
+ EXPECT_EQ("/just/some/dir/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/");
+ EXPECT_EQ("/", fname.directory());
+ EXPECT_EQ("/a.b", fname.fullName());
+ EXPECT_EQ("/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("");
+ EXPECT_EQ("", fname.directory());
+ EXPECT_EQ("a.b", fname.fullName());
+ EXPECT_EQ("a.b", fname.expandWithDefault(""));
+
+ fname = Filename("/first/a.b");
+ EXPECT_EQ("/first/", fname.directory());
+ EXPECT_EQ("/first/a.b", fname.fullName());
+ EXPECT_EQ("/first/a.b", fname.expandWithDefault(""));
+
+ fname.setDirectory("/just/some/dir");
+ EXPECT_EQ("/just/some/dir/", fname.directory());
+ EXPECT_EQ("/just/some/dir/a.b", fname.fullName());
+ EXPECT_EQ("/just/some/dir/a.b", fname.expandWithDefault(""));
+}
diff --git a/src/lib/util/tests/hash_unittest.cc b/src/lib/util/tests/hash_unittest.cc
new file mode 100644
index 0000000..f789e51
--- /dev/null
+++ b/src/lib/util/tests/hash_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2018 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/hash.h>
+
+#include <gtest/gtest.h>
+
+#include <cstring>
+#include <vector>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+TEST(HashTest, empty) {
+ EXPECT_EQ(14695981039346656037ull, Hash64::hash(0, 0));
+}
+
+TEST(HashTest, foobar) {
+ EXPECT_EQ(9625390261332436968ull, Hash64::hash(string("foobar")));
+}
+
+TEST(HashTest, chongo) {
+ EXPECT_EQ(5080352029159061781ull,
+ Hash64::hash(string("chongo was here!\n")));
+}
+
+}
diff --git a/src/lib/util/tests/hex_unittest.cc b/src/lib/util/tests/hex_unittest.cc
new file mode 100644
index 0000000..1e611b5
--- /dev/null
+++ b/src/lib/util/tests/hex_unittest.cc
@@ -0,0 +1,114 @@
+// 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 <stdint.h>
+
+#include <vector>
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <util/encode/hex.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::encode;
+
+namespace {
+const string hex_txt("DEADBEEFDECADE");
+const string hex_txt_space("DEAD BEEF DECADE");
+const string hex_txt_lower("deadbeefdecade");
+
+class HexTest : public ::testing::Test {
+protected:
+ HexTest() : encoding_chars("0123456789ABCDEF") {}
+ vector<uint8_t> decoded_data;
+ const string encoding_chars;
+};
+
+TEST_F(HexTest, encodeHex) {
+ std::vector<uint8_t> data;
+
+ data.push_back(0xde);
+ data.push_back(0xad);
+ data.push_back(0xbe);
+ data.push_back(0xef);
+ data.push_back(0xde);
+ data.push_back(0xca);
+ data.push_back(0xde);
+ EXPECT_EQ(hex_txt, encodeHex(data));
+}
+
+void
+compareData(const std::vector<uint8_t>& data) {
+ EXPECT_EQ(0xde, data[0]);
+ EXPECT_EQ(0xad, data[1]);
+ EXPECT_EQ(0xbe, data[2]);
+ EXPECT_EQ(0xef, data[3]);
+ EXPECT_EQ(0xde, data[4]);
+ EXPECT_EQ(0xca, data[5]);
+ EXPECT_EQ(0xde, data[6]);
+}
+
+TEST_F(HexTest, decodeHex) {
+ std::vector<uint8_t> result;
+
+ decodeHex(hex_txt, result);
+ compareData(result);
+
+ // lower case hex digits should be accepted
+ result.clear();
+ decodeHex(hex_txt_lower, result);
+ compareData(result);
+
+ // white space should be ignored
+ result.clear();
+ decodeHex(hex_txt_space, result);
+ compareData(result);
+
+ // Bogus input: should fail
+ result.clear();
+ EXPECT_THROW(decodeHex("1x", result), BadValue);
+
+ // Bogus input: encoded string must have an even number of characters.
+ result.clear();
+ EXPECT_THROW(decodeHex("dea", result), BadValue);
+}
+
+// For Hex encode/decode we use handmade mappings, so it's prudent to test the
+// entire mapping table explicitly.
+TEST_F(HexTest, decodeMap) {
+ string input("00"); // input placeholder
+
+ // See Base32HexTest.decodeMap for details of the following tests.
+ for (int i = 0; i < 256; ++i) {
+ input[1] = i;
+
+ const char ch = toupper(i);
+ const size_t pos = encoding_chars.find(ch);
+ if (pos == string::npos) {
+ EXPECT_THROW(decodeHex(input, decoded_data), BadValue);
+ } else {
+ decodeHex(input, decoded_data);
+ EXPECT_EQ(1, decoded_data.size());
+ EXPECT_EQ(pos, decoded_data[0]);
+ }
+ }
+}
+
+TEST_F(HexTest, encodeMap) {
+ for (uint8_t i = 0; i < 16; ++i) {
+ decoded_data.clear();
+ decoded_data.push_back(i);
+ EXPECT_EQ(encoding_chars[i], encodeHex(decoded_data)[1]);
+ }
+}
+
+}
diff --git a/src/lib/util/tests/io_utilities_unittest.cc b/src/lib/util/tests/io_utilities_unittest.cc
new file mode 100644
index 0000000..acaaf13
--- /dev/null
+++ b/src/lib/util/tests/io_utilities_unittest.cc
@@ -0,0 +1,160 @@
+// Copyright (C) 2011-2022 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/.
+
+/// \brief Test of asiolink utilities
+///
+/// Tests the functionality of the asiolink utilities code by comparing them
+/// with the equivalent methods in isc::dns::[Input/Output]Buffer.
+
+#include <config.h>
+
+#include <cstddef>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+
+using namespace isc::util;
+
+TEST(asioutil, readUint16) {
+
+ // Reference buffer
+ uint8_t data[2] = {0, 0};
+ InputBuffer buffer(data, sizeof(data));
+
+ // Avoid possible compiler warnings by only setting uint8_t variables to
+ // uint8_t values.
+ uint8_t i8 = 0;
+ uint8_t j8 = 0;
+ for (int i = 0; i < (2 << 8); ++i, ++i8) {
+ for (int j = 0; j < (2 << 8); ++j, ++j8) {
+ data[0] = i8;
+ data[1] = j8;
+ buffer.setPosition(0);
+ EXPECT_EQ(buffer.readUint16(), readUint16(data, sizeof(data)));
+ }
+ }
+}
+
+TEST(asioutil, readUint16OutOfRange) {
+ uint8_t data = 0;
+ EXPECT_THROW(readUint16(&data, sizeof(data)), isc::OutOfRange);
+}
+
+TEST(asioutil, writeUint16) {
+
+ // Reference buffer
+ OutputBuffer buffer(2);
+ uint8_t test[2];
+
+ // Avoid possible compiler warnings by only setting uint16_t variables to
+ // uint16_t values.
+ uint16_t i16 = 0;
+ for (uint32_t i = 0; i < (2 << 16); ++i, ++i16) {
+
+ // Write the reference data
+ buffer.clear();
+ buffer.writeUint16(i16);
+
+ // ... and the test data
+ writeUint16(i16, test, sizeof(test));
+
+ // ... and compare
+ const uint8_t* ref = static_cast<const uint8_t*>(buffer.getData());
+ EXPECT_EQ(ref[0], test[0]);
+ EXPECT_EQ(ref[1], test[1]);
+ }
+}
+
+TEST(asioutil, writeUint16OutOfRange) {
+ uint16_t i16 = 42;
+ uint8_t data;
+ EXPECT_THROW(writeUint16(i16, &data, sizeof(data)), isc::OutOfRange);
+}
+
+// test data shared amount readUint32 and writeUint32 tests
+const static uint32_t test32[] = {
+ 0,
+ 1,
+ 2000,
+ 0x80000000,
+ 0xffffffff
+};
+
+TEST(asioutil, readUint32) {
+ uint8_t data[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+ // make sure that we can read data, regardless of
+ // the memory alignment. That' why we need to repeat
+ // it 4 times.
+ for (int offset=0; offset < 4; offset++) {
+ for (int i=0; i < sizeof(test32)/sizeof(uint32_t); i++) {
+ uint32_t tmp = htonl(test32[i]);
+ memcpy(&data[offset], &tmp, sizeof(uint32_t));
+
+ EXPECT_EQ(test32[i], readUint32(&data[offset], sizeof(uint32_t)));
+ }
+ }
+}
+
+TEST(asioutil, readUint32OutOfRange) {
+ uint8_t data[3] = {0, 0, 0};
+ EXPECT_THROW(readUint32(data, sizeof(data)), isc::OutOfRange);
+}
+
+TEST(asioutil, writeUint32) {
+ uint8_t data[8];
+
+ // make sure that we can write data, regardless of
+ // the memory alignment. That's why we need to repeat
+ // it 4 times.
+ for (int offset=0; offset < 4; offset++) {
+ for (int i=0; i < sizeof(test32)/sizeof(uint32_t); i++) {
+ uint8_t* ptr = writeUint32(test32[i], &data[offset],
+ sizeof(uint32_t));
+
+ EXPECT_EQ(&data[offset]+sizeof(uint32_t), ptr);
+
+ uint32_t tmp = htonl(test32[i]);
+
+ EXPECT_EQ(0, memcmp(&tmp, &data[offset], sizeof(uint32_t)));
+ }
+ }
+}
+
+TEST(asioutil, writeUint32OutOfRange) {
+ uint32_t i32 = 28;
+ uint8_t data[3];
+ EXPECT_THROW(writeUint32(i32, data, sizeof(data)), isc::OutOfRange);
+}
+
+// Tests whether uint64 can be read from a buffer properly.
+TEST(asioutil, readUint64) {
+
+ uint8_t buf[8];
+ for (int offset = 0; offset < sizeof(buf); offset++) {
+ buf[offset] = offset+1;
+ }
+
+ // Let's do some simple sanity checks first.
+ EXPECT_THROW(readUint64(NULL, 0), isc::OutOfRange);
+ EXPECT_THROW(readUint64(buf, 7), isc::OutOfRange);
+
+ // Now check if a real value could be read.
+ const uint64_t exp_val = 0x0102030405060708ul;
+ uint64_t val;
+
+ EXPECT_NO_THROW(val = readUint64(buf, 8));
+ EXPECT_EQ(val, exp_val);
+
+ // Now check if there are no buffer overflows.
+ memset(buf, 0xff, 8);
+
+ EXPECT_NO_THROW(val = readUint64(buf, 8));
+ EXPECT_EQ(0xfffffffffffffffful, val);
+}
diff --git a/src/lib/util/tests/labeled_value_unittest.cc b/src/lib/util/tests/labeled_value_unittest.cc
new file mode 100644
index 0000000..c994156
--- /dev/null
+++ b/src/lib/util/tests/labeled_value_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright (C) 2013-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 <util/labeled_value.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Verifies basic construction and accessors for LabeledValue.
+TEST(LabeledValue, construction) {
+ /// Verify that an empty label is not allowed.
+ ASSERT_THROW(LabeledValue(1, ""), LabeledValueError);
+
+ /// Verify that a valid constructor works.
+ LabeledValuePtr lvp;
+ ASSERT_NO_THROW(lvp.reset(new LabeledValue(1, "NotBlank")));
+ ASSERT_TRUE(lvp);
+
+ // Verify that the value can be accessed.
+ EXPECT_EQ(1, lvp->getValue());
+
+ // Verify that the label can be accessed.
+ EXPECT_EQ("NotBlank", lvp->getLabel());
+}
+
+/// @brief Verifies the logical operators defined for LabeledValue.
+TEST(LabeledValue, operators) {
+ LabeledValuePtr lvp1;
+ LabeledValuePtr lvp1Also;
+ LabeledValuePtr lvp2;
+
+ // Create three instances, two of which have the same numeric value.
+ ASSERT_NO_THROW(lvp1.reset(new LabeledValue(1, "One")));
+ ASSERT_NO_THROW(lvp1Also.reset(new LabeledValue(1, "OneAlso")));
+ ASSERT_NO_THROW(lvp2.reset(new LabeledValue(2, "Two")));
+
+ // Verify each of the operators.
+ EXPECT_TRUE(*lvp1 == *lvp1Also);
+ EXPECT_TRUE(*lvp1 != *lvp2);
+ EXPECT_TRUE(*lvp1 < *lvp2);
+ EXPECT_FALSE(*lvp2 < *lvp1);
+}
+
+/// @brief Verifies the default constructor for LabeledValueSet.
+TEST(LabeledValueSet, construction) {
+ ASSERT_NO_THROW (LabeledValueSet());
+}
+
+/// @brief Verifies the basic operations of a LabeledValueSet.
+/// Essentially we verify that we can define a set of valid entries and
+/// look them up without issue.
+TEST(LabeledValueSet, basicOperation) {
+ const char* labels[] = {"Zero", "One", "Two", "Three" };
+ LabeledValueSet lvset;
+ LabeledValuePtr lvp;
+
+ // Verify the we cannot add an empty pointer to the set.
+ EXPECT_THROW(lvset.add(lvp), LabeledValueError);
+
+ // Verify that we can add an entry to the set via pointer.
+ ASSERT_NO_THROW(lvp.reset(new LabeledValue(0, labels[0])));
+ EXPECT_NO_THROW(lvset.add(lvp));
+
+ // Verify that we cannot add a duplicate entry.
+ EXPECT_THROW(lvset.add(lvp), LabeledValueError);
+
+ // Add the remaining entries using add(int,char*) variant.
+ for (int i = 1; i < 3; i++) {
+ EXPECT_NO_THROW(lvset.add(i, labels[i]));
+ }
+
+ // Verify that we can't add a duplicate entry this way either.
+ EXPECT_THROW ((lvset.add(0, labels[0])), LabeledValueError);
+
+ // Verify that we can look up all of the defined entries properly.
+ for (int i = 1; i < 3; i++) {
+ EXPECT_TRUE(lvset.isDefined(i));
+ EXPECT_NO_THROW(lvp = lvset.get(i));
+ EXPECT_EQ(lvp->getValue(), i);
+ EXPECT_EQ(lvp->getLabel(), labels[i]);
+ EXPECT_EQ(lvset.getLabel(i), labels[i]);
+ }
+
+ // Verify behavior for a value that is not defined.
+ EXPECT_FALSE(lvset.isDefined(4));
+ EXPECT_NO_THROW(lvp = lvset.get(4));
+ EXPECT_FALSE(lvp);
+ EXPECT_EQ(lvset.getLabel(4), LabeledValueSet::UNDEFINED_LABEL);
+}
+
+}
diff --git a/src/lib/util/tests/memory_segment_common_unittest.cc b/src/lib/util/tests/memory_segment_common_unittest.cc
new file mode 100644
index 0000000..f95021d
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_common_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright (C) 2013-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 <util/memory_segment.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <cstring>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+void
+checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok) {
+ // NULL name is not allowed.
+ EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
+
+ // If the name does not exist, false should be returned.
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
+
+ // Now set it
+ void* ptr32 = segment.allocate(sizeof(uint32_t));
+ const uint32_t test_val = 42;
+ *static_cast<uint32_t*>(ptr32) = test_val;
+ EXPECT_FALSE(segment.setNamedAddress("test address", ptr32));
+
+ // NULL name isn't allowed.
+ EXPECT_THROW(segment.setNamedAddress(NULL, ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress(NULL), InvalidParameter);
+
+ // Empty names are not allowed.
+ EXPECT_THROW(segment.setNamedAddress("", ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress(""), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress(""), InvalidParameter);
+
+ // Names beginning with _ are not allowed.
+ EXPECT_THROW(segment.setNamedAddress("_foo", ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress("_foo"), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress("_foo"), InvalidParameter);
+
+ // we can now get it; the stored value should be intact.
+ MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val, *static_cast<const uint32_t*>(result.second));
+
+ // Override it.
+ void* ptr16 = segment.allocate(sizeof(uint16_t));
+ const uint16_t test_val16 = 4200;
+ *static_cast<uint16_t*>(ptr16) = test_val16;
+ EXPECT_FALSE(segment.setNamedAddress("test address", ptr16));
+ result = segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
+
+ // Clear it. Then we won't be able to find it any more.
+ EXPECT_TRUE(segment.clearNamedAddress("test address"));
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
+
+ // duplicate attempt of clear will result in false as it doesn't exist.
+ EXPECT_FALSE(segment.clearNamedAddress("test address"));
+
+ // Setting NULL is okay.
+ EXPECT_FALSE(segment.setNamedAddress("null address", NULL));
+ result = segment.getNamedAddress("null address");
+ EXPECT_TRUE(result.first);
+ EXPECT_FALSE(result.second);
+
+ // If the underlying implementation performs explicit check against
+ // out-of-segment address, confirm the behavior.
+ if (!out_of_segment_ok) {
+ uint8_t ch = 'A';
+ EXPECT_THROW(segment.setNamedAddress("local address", &ch),
+ MemorySegmentError);
+ }
+
+ // clean them up all
+ segment.deallocate(ptr32, sizeof(uint32_t));
+ EXPECT_FALSE(segment.allMemoryDeallocated()); // not fully deallocated
+ segment.deallocate(ptr16, sizeof(uint16_t)); // not yet
+ EXPECT_FALSE(segment.allMemoryDeallocated());
+ EXPECT_TRUE(segment.clearNamedAddress("null address"));
+ // null name isn't allowed:
+ EXPECT_THROW(segment.clearNamedAddress(NULL), InvalidParameter);
+ EXPECT_TRUE(segment.allMemoryDeallocated()); // now everything is gone
+}
+
+}
+}
+}
diff --git a/src/lib/util/tests/memory_segment_common_unittest.h b/src/lib/util/tests/memory_segment_common_unittest.h
new file mode 100644
index 0000000..435ff12
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_common_unittest.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2013-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 <util/memory_segment.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+/// \brief Implementation dependent checks on memory segment named addresses.
+///
+/// This function contains a set of test cases for given memory segment
+/// regarding "named address" methods. The test cases basically only depend
+/// on the base class interfaces, but if the underlying implementation does
+/// not check if the given address to setNamedAddress() belongs to the segment,
+/// out_of_segment_ok should be set to true.
+void checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok);
+
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/tests/memory_segment_local_unittest.cc b/src/lib/util/tests/memory_segment_local_unittest.cc
new file mode 100644
index 0000000..d1aa52d
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_local_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2012-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 <util/tests/memory_segment_common_unittest.h>
+
+#include <util/memory_segment_local.h>
+#include <exceptions/exceptions.h>
+#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+#include <memory>
+#include <limits.h>
+
+using namespace std;
+using namespace isc::util;
+
+namespace {
+
+TEST(MemorySegmentLocal, TestLocal) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // By default, nothing is allocated.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ void* ptr = segment->allocate(1024);
+
+ // Now, we have an allocation:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ void* ptr2 = segment->allocate(42);
+
+ // Still:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ // These should not fail, because the buffers have been allocated.
+ EXPECT_NO_FATAL_FAILURE(memset(ptr, 0, 1024));
+ EXPECT_NO_FATAL_FAILURE(memset(ptr, 0, 42));
+
+ segment->deallocate(ptr, 1024);
+
+ // Still:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ segment->deallocate(ptr2, 42);
+
+ // Now, we have an deallocated everything:
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+/// @todo: disabled, see ticket #3510
+TEST(MemorySegmentLocal, DISABLED_TestTooMuchMemory) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // Although it should be perfectly fine to use the ULONG_MAX
+ // instead of LONG_MAX as the size_t value should be unsigned,
+ // Valgrind appears to be using the signed value and hence the
+ // maximum positive value is LONG_MAX for Valgrind. But, this
+ // should be sufficient to test the "too much memory" conditions.
+ EXPECT_THROW(segment->allocate(LONG_MAX), bad_alloc);
+}
+
+TEST(MemorySegmentLocal, TestBadDeallocate) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // By default, nothing is allocated.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ void* ptr = segment->allocate(1024);
+
+ // Now, we have an allocation:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ // This should not throw
+ EXPECT_NO_THROW(segment->deallocate(ptr, 1024));
+
+ // Now, we have an deallocated everything:
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ ptr = segment->allocate(1024);
+
+ // Now, we have another allocation:
+ EXPECT_FALSE(segment->allMemoryDeallocated());
+
+ // This should throw as the size passed to deallocate() is larger
+ // than what was allocated.
+ EXPECT_THROW(segment->deallocate(ptr, 2048), isc::OutOfRange);
+
+ // This should not throw
+ EXPECT_NO_THROW(segment->deallocate(ptr, 1024));
+
+ // Now, we have an deallocated everything:
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+TEST(MemorySegmentLocal, TestNullDeallocate) {
+ boost::scoped_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+ // By default, nothing is allocated.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+
+ // NULL deallocation is a no-op.
+ EXPECT_NO_THROW(segment->deallocate(NULL, 1024));
+
+ // This should still return true.
+ EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+TEST(MemorySegmentLocal, namedAddress) {
+ MemorySegmentLocal segment;
+ isc::util::test::checkSegmentNamedAddress(segment, true);
+}
+
+} // anonymous namespace
diff --git a/src/lib/util/tests/multi_threading_mgr_unittest.cc b/src/lib/util/tests/multi_threading_mgr_unittest.cc
new file mode 100644
index 0000000..4d3ae33
--- /dev/null
+++ b/src/lib/util/tests/multi_threading_mgr_unittest.cc
@@ -0,0 +1,636 @@
+// Copyright (C) 2019-2023 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/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+using namespace isc;
+
+/// @brief Fixture used to reset multi-threading before and after each test.
+struct MultiThreadingMgrTest : ::testing::Test {
+ MultiThreadingMgrTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+ ~MultiThreadingMgrTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+};
+
+/// @brief Verifies that the default mode is false (MT disabled).
+TEST_F(MultiThreadingMgrTest, defaultMode) {
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+}
+
+/// @brief Verifies that the mode setter works.
+TEST_F(MultiThreadingMgrTest, setMode) {
+ // enable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(true));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(false));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+}
+
+/// @brief Verifies that accessing the thread pool works.
+TEST_F(MultiThreadingMgrTest, threadPool) {
+ // get the thread pool
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().getThreadPool());
+}
+
+/// @brief Verifies that the thread pool size setter works.
+TEST_F(MultiThreadingMgrTest, threadPoolSize) {
+ // default thread count is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // set thread count to 16
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setThreadPoolSize(16));
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // set thread count to 0
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setThreadPoolSize(0));
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+}
+
+/// @brief Verifies that the packet queue size setter works.
+TEST_F(MultiThreadingMgrTest, packetQueueSize) {
+ // default queue size is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 0);
+ // set queue size to 16
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setPacketQueueSize(16));
+ // queue size should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 16);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 16);
+ // set queue size to 0
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setPacketQueueSize(0));
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 0);
+}
+
+/// @brief Verifies that detecting thread count works.
+TEST_F(MultiThreadingMgrTest, detectThreadCount) {
+ // detecting thread count should work
+ EXPECT_NE(MultiThreadingMgr::detectThreadCount(), 0);
+}
+
+/// @brief Verifies that apply settings works.
+TEST_F(MultiThreadingMgrTest, applyConfig) {
+ // get the thread pool
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // default thread count is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // enable MT with 16 threads and queue size 256
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 16, 256));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // thread pool should be started
+ EXPECT_EQ(thread_pool.size(), 16);
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 16, 256));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // enable MT with auto scaling
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 0, 0));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be detected automatically
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), MultiThreadingMgr::detectThreadCount());
+ // thread pool should be started
+ EXPECT_EQ(thread_pool.size(), MultiThreadingMgr::detectThreadCount());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 0, 0));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief Verifies that the critical section flag works.
+TEST_F(MultiThreadingMgrTest, criticalSectionFlag) {
+ // get the thread pool
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // exit critical section
+ EXPECT_THROW(MultiThreadingMgr::instance().exitCriticalSection(), InvalidOperation);
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // enter critical section
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().enterCriticalSection());
+ // critical section should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().isInCriticalSection());
+ // enable MT with 16 threads and queue size 256
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 16, 256));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // exit critical section
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().exitCriticalSection());
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // exit critical section
+ EXPECT_THROW(MultiThreadingMgr::instance().exitCriticalSection(), InvalidOperation);
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 0, 0));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief Verifies that the critical section works.
+TEST_F(MultiThreadingMgrTest, criticalSection) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // apply multi-threading configuration with 16 threads and queue size 256
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 16);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection inner_cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 16);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // apply multi-threading configuration with 64 threads and queue size 4
+ MultiThreadingMgr::instance().apply(true, 64, 4);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 64);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ // apply multi-threading configuration with 0 threads
+ MultiThreadingMgr::instance().apply(false, 64, 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection inner_cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // apply multi-threading configuration with 64 threads
+ MultiThreadingMgr::instance().apply(true, 64, 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 64);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // apply multi-threading configuration with 0 threads
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+}
+
+/// @brief Checks that the lock works only when multi-threading is enabled and
+/// only during its lifetime.
+TEST(MultiThreadingLockTest, scope) {
+ // Check that the mutex is unlocked by default at first.
+ std::mutex mutex;
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(false));
+
+ // Check that the lock does not locks the mutex if multi-threading is disabled.
+ {
+ MultiThreadingLock lock(mutex);
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+ }
+
+ // Check that the mutex is still unlocked when the lock goes out of scope.
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(true));
+
+ // Check that the lock actively locks the mutex if multi-threading is enabled.
+ {
+ MultiThreadingLock lock(mutex);
+ ASSERT_FALSE(mutex.try_lock());
+ }
+
+ // Check that the mutex is unlocked when the lock goes out of scope.
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+}
+
+/// @brief Test fixture for exercised CriticalSection callbacks.
+class CriticalSectionCallbackTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ CriticalSectionCallbackTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+
+ /// @brief Destructor.
+ ~CriticalSectionCallbackTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+
+ /// @brief A callback that adds the value 1 to invocations lists.
+ void one() {
+ invocations_.push_back(1);
+ }
+
+ /// @brief A callback that adds the value 2 to invocations lists.
+ void two() {
+ invocations_.push_back(2);
+ }
+
+ /// @brief A callback that adds the value 3 to invocations lists.
+ void three() {
+ invocations_.push_back(3);
+ }
+
+ /// @brief A callback that adds the value 4 to invocations lists.
+ void four() {
+ invocations_.push_back(4);
+ }
+
+ /// @brief A callback that throws @ref isc::Exception which is ignored.
+ void ignoredException() {
+ isc_throw(isc::Exception, "ignored");
+ }
+
+ /// @brief A callback that throws @ref isc::MultiThreadingInvalidOperation
+ /// which is propagated to the scope of the
+ /// @ref MultiThreadingCriticalSection constructor.
+ void observedException() {
+ isc_throw(isc::MultiThreadingInvalidOperation, "observed");
+ }
+
+ /// @brief Indicates whether or not the DHCP thread pool is running.
+ ///
+ /// @return True if the pool is running, false otherwise.
+ bool isThreadPoolRunning() {
+ return (MultiThreadingMgr::instance().getThreadPool().size());
+ }
+
+ /// @brief Checks callback invocations over a series of nested
+ /// CriticalSections.
+ ///
+ /// @param entries A vector of the invocation values that should
+ /// be present after entry into the outermost CriticalSection. The
+ /// expected values should be in the order the callbacks were added
+ /// to the MultiThreadingMgr's list of callbacks.
+ /// @param exits A vector of the invocation values that should
+ /// be present after exiting the outermost CriticalSection. The
+ /// expected values should be in the order the callbacks were added
+ /// to the MultiThreadingMgr's list of callbacks.
+ /// @param should_throw The flag indicating if the CriticalSection should
+ /// throw, simulating a dead-lock scenario when a processing thread tries
+ /// to stop the thread pool.
+ void runCriticalSections(std::vector<int> entries, std::vector<int>exits,
+ bool should_throw = false) {
+ // Pool must be running.
+ ASSERT_TRUE(isThreadPoolRunning());
+
+ // Clear the invocations list.
+ invocations_.clear();
+
+ // Use scope to create nested CriticalSections.
+ if (!should_throw) {
+ // Enter a critical section.
+ MultiThreadingCriticalSection cs;
+
+ // Thread pool should be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ if (entries.size()) {
+ // We expect entry invocations.
+ ASSERT_EQ(invocations_.size(), entries.size());
+ ASSERT_EQ(invocations_, entries);
+ } else {
+ // We do not expect entry invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // Clear the invocations list.
+ invocations_.clear();
+
+ {
+ // Enter another CriticalSection.
+ MultiThreadingCriticalSection inner_cs;
+
+ // Thread pool should still be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ // We should not have had any callback invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // After exiting inner section, the thread pool should
+ // still be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ // We should not have had more callback invocations.
+ ASSERT_FALSE(invocations_.size());
+ } else {
+ ASSERT_THROW(MultiThreadingCriticalSection cs, MultiThreadingInvalidOperation);
+
+ if (entries.size()) {
+ // We expect entry invocations.
+ ASSERT_EQ(invocations_.size(), entries.size());
+ ASSERT_EQ(invocations_, entries);
+ } else {
+ // We do not expect entry invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // Clear the invocations list.
+ invocations_.clear();
+ }
+
+ // After exiting the outer section, the thread pool should
+ // match the thread count.
+ ASSERT_TRUE(isThreadPoolRunning());
+
+ if (exits.size()) {
+ // We expect exit invocations.
+ ASSERT_EQ(invocations_, exits);
+ } else {
+ // We do not expect exit invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+ }
+
+ /// @brief A list of values set by callback invocations.
+ std::vector<int> invocations_;
+};
+
+/// @brief Verifies critical section callback maintenance:
+/// catch invalid pairs, add pairs, remove pairs.
+TEST_F(CriticalSectionCallbackTest, addAndRemove) {
+ auto& mgr = MultiThreadingMgr::instance();
+
+ // Cannot add with a blank name.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("", [](){}, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - name cannot be empty");
+
+ // Cannot add with an empty check callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", nullptr, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - check callback for bad cannot be empty");
+
+ // Cannot add with an empty exit callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, nullptr, [](){}),
+ BadValue, "CSCallbackSetList - entry callback for bad cannot be empty");
+
+ // Cannot add with an empty exit callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, [](){}, nullptr),
+ BadValue, "CSCallbackSetList - exit callback for bad cannot be empty");
+
+ // Should be able to add foo.
+ ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}));
+
+ // Should not be able to add foo twice.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - callbacks for foo already exist");
+
+ // Should be able to add bar.
+ ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("bar", [](){}, [](){}, [](){}));
+
+ // Should be able to remove foo.
+ ASSERT_NO_THROW_LOG(mgr.removeCriticalSectionCallbacks("foo"));
+
+ // Should be able to remove foo twice without issue.
+ ASSERT_NO_THROW_LOG(mgr.removeCriticalSectionCallbacks("foo"));
+
+ // Should be able to remove all without issue.
+ ASSERT_NO_THROW_LOG(mgr.removeAllCriticalSectionCallbacks());
+}
+
+/// @brief Verifies that the critical section callbacks work.
+TEST_F(CriticalSectionCallbackTest, invocations) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+
+ // Add two sets of CriticalSection call backs.
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("oneAndTwo",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::one, this),
+ std::bind(&CriticalSectionCallbackTest::two, this));
+
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("threeAndFour",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::three, this),
+ std::bind(&CriticalSectionCallbackTest::four, this));
+
+ // Apply multi-threading configuration with 16 threads and queue size 256.
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+
+ // Make three passes over nested CriticalSections to ensure
+ // callbacks execute at the appropriate times and we can do
+ // so repeatedly.
+ for (int i = 0; i < 3; ++i) {
+ runCriticalSections({1 ,3}, {4, 2});
+ }
+
+ // Now remove the first set of callbacks.
+ MultiThreadingMgr::instance().removeCriticalSectionCallbacks("oneAndTwo");
+
+ // Retest CriticalSections.
+ runCriticalSections({3}, {4});
+
+ // Now remove the remaining callbacks.
+ MultiThreadingMgr::instance().removeAllCriticalSectionCallbacks();
+
+ // Retest CriticalSections.
+ runCriticalSections({}, {});
+}
+
+/// @brief Verifies that the critical section callbacks work.
+TEST_F(CriticalSectionCallbackTest, invocationsWithExceptions) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+
+ // Apply multi-threading configuration with 16 threads and queue size 256.
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+
+ // Add two sets of CriticalSection call backs.
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("observed",
+ std::bind(&CriticalSectionCallbackTest::observedException, this),
+ std::bind(&CriticalSectionCallbackTest::one, this),
+ std::bind(&CriticalSectionCallbackTest::two, this));
+
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("ignored",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::three, this),
+ std::bind(&CriticalSectionCallbackTest::four, this));
+
+ // Make three passes over nested CriticalSections to ensure
+ // callbacks execute at the appropriate times and we can do
+ // so repeatedly.
+ for (int i = 0; i < 3; ++i) {
+ runCriticalSections({}, {}, true);
+ }
+
+ // Now remove the first set of callbacks.
+ MultiThreadingMgr::instance().removeCriticalSectionCallbacks("observed");
+
+ // Retest CriticalSections.
+ runCriticalSections({3}, {4});
+
+ // Now remove the remaining callbacks.
+ MultiThreadingMgr::instance().removeAllCriticalSectionCallbacks();
+
+ // Retest CriticalSections.
+ runCriticalSections({}, {});
+}
diff --git a/src/lib/util/tests/optional_unittest.cc b/src/lib/util/tests/optional_unittest.cc
new file mode 100644
index 0000000..71830ab
--- /dev/null
+++ b/src/lib/util/tests/optional_unittest.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2015-2022 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/optional.h>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc::util;
+
+// This test checks that the constructors work correctly.
+TEST(OptionalTest, constructor) {
+ // Explicitly set a value via constructor. The value becomes
+ // specified.
+ Optional<int> value1(10);
+ EXPECT_EQ(10, value1.get());
+ EXPECT_FALSE(value1.unspecified());
+
+ // Do not set a value in a constructor. The value should be
+ // unspecified.
+ Optional<int> value2;
+ EXPECT_EQ(0, value2.get());
+ EXPECT_TRUE(value2.unspecified());
+
+ // Use the non-default value for second parameter.
+ Optional<bool> value3(true, true);
+ EXPECT_TRUE(value3.get());
+ EXPECT_TRUE(value3.unspecified());
+}
+
+// This test checks if the constructors for a string value
+// work correctly.
+TEST(OptionalTest, constructorString) {
+ Optional<std::string> value1("foo");
+ EXPECT_EQ("foo", value1.get());
+ EXPECT_FALSE(value1.unspecified());
+
+ Optional<std::string> value2;
+ EXPECT_TRUE(value2.get().empty());
+ EXPECT_TRUE(value2.unspecified());
+}
+
+// This test checks if the assignment operator assigning an actual
+// value to the optional value works as expected.
+TEST(OptionalTest, assignValue) {
+ Optional<int> value(10, true);
+ EXPECT_EQ(10, value.get());
+ EXPECT_TRUE(value.unspecified());
+
+ // Assign a new value.
+ value = 111;
+ EXPECT_EQ(111, value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ // Assign another value.
+ value = 1000;
+ EXPECT_EQ(1000, value.get());
+ EXPECT_FALSE(value.unspecified());
+}
+
+// This test checks if the assignment operator assigning an actual
+// string value to the optional value works as expected.
+TEST(OptionalTest, assignStringValue) {
+ Optional<std::string> value("foo");
+ EXPECT_EQ("foo", value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ value = "bar";
+ EXPECT_EQ("bar", value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ value = "foobar";
+ EXPECT_EQ("foobar", value.get());
+ EXPECT_FALSE(value.unspecified());
+}
+
+// This test checks that it is possible to modify the flag that indicates
+// if the value is specified or unspecified.
+TEST(OptionalTest, modifyUnspecified) {
+ Optional<int> value;
+ EXPECT_TRUE(value.unspecified());
+
+ value.unspecified(false);
+ EXPECT_FALSE(value.unspecified());
+
+ value.unspecified(true);
+ EXPECT_TRUE(value.unspecified());
+}
+
+// This test checks if the type case operator returns correct value.
+TEST(OptionalTest, typeCastOperator) {
+ Optional<int> value(-10);
+ EXPECT_EQ(-10, value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ int actual = value;
+ EXPECT_EQ(-10, actual);
+}
+
+// This test checks if the type case operator returns correct string
+// value.
+TEST(OptionalTest, stringCastOperator) {
+ Optional<std::string> value("xyz");
+ EXPECT_EQ("xyz", value.get());
+ EXPECT_FALSE(value.unspecified());
+
+ std::string actual = value;
+ EXPECT_EQ("xyz", actual);
+}
+
+// This test checks that the equality operators work as expected.
+TEST(OptionalTest, equality) {
+ int exp_value = 1234;
+ Optional<int> value(1234);
+ EXPECT_TRUE(value == exp_value);
+ EXPECT_FALSE(value != exp_value);
+}
+
+// This test checks that the equality operators for strings work as
+// expected.
+TEST(OptionalTest, stringEquality) {
+ const char* exp_value = "foo";
+ Optional<std::string> value("foo");
+ EXPECT_TRUE(value == exp_value);
+ EXPECT_FALSE(value != exp_value);
+}
+
+// This test checks that an exception is thrown when calling an empty()
+// method on non-string optional value.
+TEST(OptionalTest, empty) {
+ Optional<int> value(10);
+ EXPECT_THROW(value.empty(), isc::InvalidOperation);
+}
+
+// This test checks that no exception is thrown when calling an empty()
+// method on string optional value and that it returns an expected
+// boolean value.
+TEST(OptionalTest, stringEmpty) {
+ Optional<std::string> value("foo");
+ bool is_empty = true;
+ ASSERT_NO_THROW(is_empty = value.empty());
+ EXPECT_FALSE(is_empty);
+
+ value = "";
+ ASSERT_NO_THROW(is_empty = value.empty());
+ EXPECT_TRUE(is_empty);
+}
+
+// Checks that the valueOr function works correctly.
+TEST(OptionalTest, valueOr) {
+ Optional<std::string> optional("foo");
+ EXPECT_EQ(optional.valueOr("bar"), "foo");
+
+ Optional<std::string> unspecified_optional;
+ EXPECT_EQ(unspecified_optional.valueOr("bar"), "bar");
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/pid_file_unittest.cc b/src/lib/util/tests/pid_file_unittest.cc
new file mode 100644
index 0000000..5f00d72
--- /dev/null
+++ b/src/lib/util/tests/pid_file_unittest.cc
@@ -0,0 +1,206 @@
+// Copyright (C) 2015-2021 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/pid_file.h>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <signal.h>
+#include <stdint.h>
+
+namespace {
+using namespace isc::util;
+
+// Filenames used for testing.
+const char* TESTNAME = "pid_file.test";
+
+class PIDFileTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ PIDFileTest() = default;
+
+ /// @brief Destructor
+ virtual ~PIDFileTest() = default;
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Generate a random number for use as a PID
+ ///
+ /// @param start - the start of the range we want the PID in
+ /// @param range - the size of the range for our PID
+ ///
+ /// @return returns a random value between start and start + range
+ int randomizePID(const uint32_t start, const uint32_t range) {
+ int pid;
+
+ for (pid = (random() % range) + start;
+ kill(pid, 0) == 0;
+ ++pid)
+ ;
+
+ return (pid);
+ }
+
+protected:
+ /// @brief Removes any old test files before the test
+ virtual void SetUp() {
+ removeTestFile();
+ }
+
+ /// @brief Removes any remaining test files after the test
+ virtual void TearDown() {
+ removeTestFile();
+ }
+
+private:
+ /// @brief Removes any remaining test files
+ void removeTestFile() const {
+ static_cast<void>(remove(absolutePath(TESTNAME).c_str()));
+ }
+
+};
+
+std::string
+PIDFileTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+
+ return (s.str());
+}
+
+/// @brief Test file writing and deletion. Start by removing
+/// any leftover file. Then write a known PID to the file and
+/// attempt to read the file and verify the PID. Next write
+/// a second and verify a second PID to verify that an existing
+/// file is properly overwritten.
+
+TEST_F(PIDFileTest, writeAndDelete) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+ std::ifstream fs;
+ int pid(0);
+
+ // Write a known process id
+ pid_file.write(10);
+
+ // Read the file and compare the pid
+ fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+ fs >> pid;
+ EXPECT_TRUE(fs.good());
+ EXPECT_EQ(pid, 10);
+ fs.close();
+
+ // Write a second known process id
+ pid_file.write(20);
+
+ // And compare the second pid
+ fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+ fs >> pid;
+ EXPECT_TRUE(fs.good());
+ EXPECT_EQ(pid, 20);
+ fs.close();
+
+ // Delete the file
+ pid_file.deleteFile();
+
+ // And verify that it's gone
+ fs.open(absolutePath(TESTNAME).c_str(), std::ifstream::in);
+ EXPECT_FALSE(fs.good());
+ fs.close();
+}
+
+/// @brief Test checking a PID. Write the PID of the current
+/// process to the PID file then verify that check indicates
+/// the process is running.
+TEST_F(PIDFileTest, pidInUse) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+
+ // Write the current PID
+ pid_file.write();
+
+ // Check if we think the process is running
+ EXPECT_EQ(getpid(), pid_file.check());
+}
+
+/// @brief Test checking a PID. Write a PID that isn't in use
+/// to the PID file and verify that check indicates the process
+/// isn't running. The PID may get used between when we select it
+/// and write the file and when we check it. To minimize false
+/// errors if the first call to check fails we try again with a
+/// different range of values and only if both attempts fail do
+/// we declare the test to have failed.
+TEST_F(PIDFileTest, pidNotInUse) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+ int pid;
+
+ // get a pid between 10000 and 20000
+ pid = randomizePID(10000, 10000);
+
+ // write it
+ pid_file.write(pid);
+
+ // Check to see if we think the process is running
+ if (pid_file.check() == 0) {
+ return;
+ }
+
+ // get a pid between 40000 and 50000
+ pid = randomizePID(10000, 40000);
+
+ // write it
+ pid_file.write(pid);
+
+ // Check to see if we think the process is running
+ EXPECT_EQ(0, pid_file.check());
+}
+
+/// @brief Test checking a PID. Write garbage to the PID file
+/// and verify that check throws an error. In this situation
+/// the caller should probably log an error and may decide to
+/// continue or not depending on the requirements.
+TEST_F(PIDFileTest, pidGarbage) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+ std::ofstream fs;
+
+ // Open the file and write garbage to it
+ fs.open(absolutePath(TESTNAME).c_str(), std::ofstream::out);
+ fs << "text" << std::endl;
+ fs.close();
+
+ // Run the check, we expect to get an exception
+ EXPECT_THROW(pid_file.check(), PIDCantReadPID);
+}
+
+/// @brief Test failing to write a file.
+TEST_F(PIDFileTest, pidWriteFail) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+
+ // Create the test file and change it's permission bits
+ // so we can't write to it.
+ pid_file.write(10);
+ chmod(absolutePath(TESTNAME).c_str(), S_IRUSR);
+
+ // Now try a write to the file, expecting an exception
+ EXPECT_THROW(pid_file.write(10), PIDFileError);
+
+ // Don't forget to restore the write right for the next test
+ chmod(absolutePath(TESTNAME).c_str(), S_IRUSR | S_IWUSR);
+}
+
+/// @brief Test deleting a file that doesn't exist
+TEST_F(PIDFileTest, noDeleteFile) {
+ PIDFile pid_file(absolutePath(TESTNAME));
+
+ // Delete a file we haven't created
+ pid_file.deleteFile();
+}
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/range_utilities_unittest.cc b/src/lib/util/tests/range_utilities_unittest.cc
new file mode 100644
index 0000000..ce94a38
--- /dev/null
+++ b/src/lib/util/tests/range_utilities_unittest.cc
@@ -0,0 +1,50 @@
+// 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 <stdint.h>
+#include <stdlib.h>
+
+#include <gtest/gtest.h>
+#include <vector>
+
+#include <util/range_utilities.h>
+
+using namespace std;
+using namespace isc::util;
+
+TEST(RangeUtilitiesTest, isZero) {
+
+ vector<uint8_t> vec(32,0);
+
+ EXPECT_TRUE(isRangeZero(vec.begin(), vec.end()));
+
+ EXPECT_TRUE(isRangeZero(vec.begin(), vec.begin()+1));
+
+ vec[5] = 1;
+ EXPECT_TRUE(isRangeZero(vec.begin(), vec.begin()+5));
+ EXPECT_FALSE(isRangeZero(vec.begin(), vec.begin()+6));
+}
+
+TEST(RangeUtilitiesTest, randomFill) {
+
+ srandom(time(NULL));
+
+ vector<uint8_t> vec1(16,0);
+ vector<uint8_t> vec2(16,0);
+
+ // Testing if returned value is actually random is extraordinary difficult.
+ // Let's just generate bunch of vectors and see if we get the same
+ // value. If we manage to do that in 100 tries, pseudo-random generator
+ // really sucks.
+ fillRandom(vec1.begin(), vec1.end());
+ for (int i=0; i<100; i++) {
+ fillRandom(vec2.begin(), vec2.end());
+ if (vec1 == vec2)
+ FAIL();
+ }
+
+}
diff --git a/src/lib/util/tests/readwrite_mutex_unittest.cc b/src/lib/util/tests/readwrite_mutex_unittest.cc
new file mode 100644
index 0000000..6b4af5f
--- /dev/null
+++ b/src/lib/util/tests/readwrite_mutex_unittest.cc
@@ -0,0 +1,470 @@
+// Copyright (C) 2020 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/readwrite_mutex.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
+
+#include <chrono>
+#include <iostream>
+#include <thread>
+#include <unistd.h>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief Test Fixture for testing read-write mutexes.
+///
+/// Each not basic test follows the same schema:
+/// @code
+/// main thread work thread
+/// <- started
+/// work ->
+/// enter guard
+/// <- done
+/// terminate ->
+/// return
+/// join
+/// @endcode
+class ReadWriteMutexTest : public ::testing::Test {
+public:
+
+ /// @brief Read-write mutex.
+ ReadWriteMutex rw_mutex_;
+
+ /// @brief Synchronization objects for work threads.
+ struct Sync {
+ bool started = false;
+ mutex started_mtx;
+ condition_variable started_cv;
+ bool work = false;
+ mutex work_mtx;
+ condition_variable work_cv;
+ bool done = false;
+ mutex done_mtx;
+ condition_variable done_cv;
+ bool terminate = false;
+ mutex terminate_mtx;
+ condition_variable terminate_cv;
+ } syncr_, syncw_;
+
+ /// @brief Body of the reader.
+ ///
+ /// @param rw_mutex The read-write mutex.
+ /// @param syncr The reader synchronization object.
+ void reader(ReadWriteMutex& rw_mutex, Sync& syncr) {
+ // Take mutex to wait for main thread signals.
+ unique_lock<mutex> terminate_lock(syncr.terminate_mtx);
+
+ // Signal the thread started.
+ {
+ lock_guard<mutex> lock(syncr.started_mtx);
+ syncr.started = true;
+ }
+
+ // Wait for work.
+ {
+ unique_lock<mutex> work_lock(syncr.work_mtx);
+ // When this thread starts waiting, the main thread is resumed.
+ syncr.started_cv.notify_one();
+ syncr.work_cv.wait(work_lock, [&](){ return syncr.work; });
+ }
+
+ {
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex);
+
+ // Signal the thread holds the guard.
+ {
+ lock_guard<mutex> done_lock(syncr.done_mtx);
+ syncr.done = true;
+ }
+ syncr.done_cv.notify_one();
+ }
+
+ // Wait to terminate.
+ syncr.terminate_cv.wait(terminate_lock, [&](){ return syncr.terminate; });
+ }
+
+ /// @brief Body of the writer.
+ ///
+ /// @param rw_mutex The read-write mutex.
+ /// @param syncw The writer synchronization object.
+ void writer(ReadWriteMutex& rw_mutex, Sync& syncw) {
+ // Take mutex to wait for main thread signals.
+ unique_lock<mutex> terminate_lock(syncw.terminate_mtx);
+
+ // Signal the thread started.
+ {
+ lock_guard<mutex> lock(syncw.started_mtx);
+ syncw.started = true;
+ }
+
+ // Wait for work.
+ {
+ unique_lock<mutex> work_lock(syncw.work_mtx);
+ // When this thread starts waiting, the main thread is resumed.
+ syncw.started_cv.notify_one();
+ syncw.work_cv.wait(work_lock, [&](){ return syncw.work; });
+ }
+
+ {
+ // Enter a write lock guard.
+ WriteLockGuard rwlock(rw_mutex);
+
+ // Signal the thread holds the guard.
+ {
+ lock_guard<mutex> done_lock(syncw.done_mtx);
+ syncw.done = true;
+ }
+ syncw.done_cv.notify_one();
+ }
+
+ // Wait to terminate.
+ syncw.terminate_cv.wait(terminate_lock, [&](){ return syncw.terminate; });
+ }
+};
+
+// Verify basic read lock guard.
+TEST_F(ReadWriteMutexTest, basicRead) {
+ ReadLockGuard lock(rw_mutex_);
+}
+
+// Verify basic write lock guard.
+TEST_F(ReadWriteMutexTest, basicWrite) {
+ WriteLockGuard lock(rw_mutex_);
+}
+
+// Verify read lock guard using a thread.
+TEST_F(ReadWriteMutexTest, read) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncr_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
+
+ // Wait work thread to start.
+ syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; });
+
+ unique_lock<mutex> done_lock(syncr_.done_mtx);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncr_.work_mtx);
+ syncr_.work = true;
+ }
+ syncr_.work_cv.notify_one();
+
+ // Wait thread to hold the read lock.
+ syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
+ syncr_.terminate = true;
+ }
+ syncr_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify write lock guard using a thread.
+TEST_F(ReadWriteMutexTest, write) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncw_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
+
+ unique_lock<mutex> done_lock(syncw_.done_mtx);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Wait thread to hold the write lock.
+ syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify read lock guard can be acquired by multiple threads.
+TEST_F(ReadWriteMutexTest, readRead) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncr_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
+
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex_);
+
+ // Wait work thread to start.
+ syncr_.started_cv.wait(started_lock, [this](){ return syncr_.started; });
+
+ unique_lock<mutex> done_lock(syncr_.done_mtx);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncr_.work_mtx);
+ syncr_.work = true;
+ }
+ syncr_.work_cv.notify_one();
+
+ // Wait thread to hold the read lock.
+ syncr_.done_cv.wait(done_lock, [this](){ return syncr_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
+ syncr_.terminate = true;
+ }
+ syncr_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify write lock guard is exclusive of a reader.
+TEST_F(ReadWriteMutexTest, readWrite) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncw_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
+
+ unique_lock<mutex> done_lock(syncw_.done_mtx);
+
+ {
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex_);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Verify the work thread is waiting for the write lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; });
+
+ EXPECT_FALSE(syncw_.done);
+ EXPECT_FALSE(ret);
+
+ // Exiting the read lock guard.
+ }
+
+ // Wait thread to hold the write lock.
+ syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify write lock guard is exclusive of a writer.
+TEST_F(ReadWriteMutexTest, writeWrite) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> thread;
+ {
+ unique_lock<mutex> started_lock(syncw_.started_mtx);
+
+ // Create a work thread.
+ thread = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(started_lock, [this](){ return syncw_.started; });
+
+ unique_lock<mutex> done_lock(syncw_.done_mtx);
+
+ {
+ // Enter a write lock guard.
+ WriteLockGuard rwlock(rw_mutex_);
+
+ // Signal the thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Verify the work thread is waiting for the write lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncw_.done_cv.wait_for(done_lock, chrono::seconds(1), [this](){ return syncw_.done; });
+
+ EXPECT_FALSE(syncw_.done);
+ EXPECT_FALSE(ret);
+
+ // Exiting the write lock guard.
+ }
+
+ // Wait thread to hold the write lock.
+ syncw_.done_cv.wait(done_lock, [this](){ return syncw_.done; });
+ }
+
+ // Signal the thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the thread.
+ thread->join();
+}
+
+// Verify that a writer has the preference.
+TEST_F(ReadWriteMutexTest, readWriteRead) {
+ // Take mutex to wait for work thread signals.
+ boost::shared_ptr<std::thread> threadw;
+ {
+ unique_lock<mutex> startedw_lock(syncw_.started_mtx);
+
+ // First thread is a writer.
+ threadw = boost::make_shared<std::thread>([this](){ writer(rw_mutex_, syncw_); });
+
+ // Wait work thread to start.
+ syncw_.started_cv.wait(startedw_lock, [this](){ return syncw_.started; });
+ }
+
+ boost::shared_ptr<std::thread> threadr;
+ {
+ unique_lock<mutex> startedr_lock(syncr_.started_mtx);
+
+ // Second thread is a reader.
+ threadr = boost::make_shared<std::thread>([this](){ reader(rw_mutex_, syncr_); });
+
+ // Wait work thread to start.
+ syncr_.started_cv.wait(startedr_lock, [this](){ return syncr_.started; });
+ }
+
+ {
+ unique_lock<mutex> donew_lock(syncw_.done_mtx);
+ {
+ // Enter a read lock guard.
+ ReadLockGuard rwlock(rw_mutex_);
+
+ // Signal the writer thread to work.
+ {
+ lock_guard<mutex> work_lock(syncw_.work_mtx);
+ syncw_.work = true;
+ }
+ syncw_.work_cv.notify_one();
+
+ // Verify the writer thread is waiting for the write lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncw_.done_cv.wait_for(donew_lock, chrono::seconds(1), [this](){ return syncw_.done; });
+
+ EXPECT_FALSE(syncw_.done);
+ EXPECT_FALSE(ret);
+
+ {
+ unique_lock<mutex> doner_lock(syncr_.done_mtx);
+
+ // Signal the reader thread to work.
+ {
+ lock_guard<mutex> work_lock(syncr_.work_mtx);
+ syncr_.work = true;
+ }
+ syncr_.work_cv.notify_one();
+
+ // Verify the reader thread is waiting for the read lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; });
+
+ EXPECT_FALSE(syncr_.done);
+ EXPECT_FALSE(ret);
+ }
+ // Exiting the read lock guard.
+ }
+
+ {
+ unique_lock<mutex> doner_lock(syncr_.done_mtx);
+ // Verify the reader thread is still waiting for the read lock.
+ cout << "pausing for one second" << std::endl;
+ bool ret = syncr_.done_cv.wait_for(doner_lock, chrono::seconds(1), [this](){ return syncr_.done; });
+
+ EXPECT_FALSE(syncr_.done);
+ EXPECT_FALSE(ret);
+ }
+
+ // Wait writer thread to hold the write lock.
+ syncw_.done_cv.wait(donew_lock, [this](){ return syncw_.done; });
+ }
+
+ {
+ unique_lock<mutex> doner_lock(syncr_.done_mtx);
+ // Wait reader thread to hold the read lock.
+ syncr_.done_cv.wait(doner_lock, [this](){ return syncr_.done; });
+ }
+
+ // Signal the writer thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncw_.terminate_mtx);
+ syncw_.terminate = true;
+ }
+ syncw_.terminate_cv.notify_one();
+
+ // Join the writer thread.
+ threadw->join();
+
+ // Signal the reader thread to terminate.
+ {
+ lock_guard<mutex> terminate_lock(syncr_.terminate_mtx);
+ syncr_.terminate = true;
+ }
+ syncr_.terminate_cv.notify_one();
+
+ // Join the reader thread.
+ threadr->join();
+}
+
+}
diff --git a/src/lib/util/tests/run_unittests.cc b/src/lib/util/tests/run_unittests.cc
new file mode 100644
index 0000000..ef2a372
--- /dev/null
+++ b/src/lib/util/tests/run_unittests.cc
@@ -0,0 +1,18 @@
+// 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 <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <stdlib.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/util/tests/staged_value_unittest.cc b/src/lib/util/tests/staged_value_unittest.cc
new file mode 100644
index 0000000..bcfc677
--- /dev/null
+++ b/src/lib/util/tests/staged_value_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (C) 2015-2017 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/staged_value.h>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc::util;
+
+// This test verifies that the value can be assigned and committed.
+TEST(StagedValueTest, assignAndCommit) {
+ // Initially the value should be set to a default
+ StagedValue<int> value;
+ ASSERT_EQ(0, value.getValue());
+
+ // Set the new value without committing it and make sure it
+ // can be retrieved.
+ value.setValue(4);
+ ASSERT_EQ(4, value.getValue());
+
+ // Commit the value and make sure it still can be retrieved.
+ value.commit();
+ ASSERT_EQ(4, value.getValue());
+
+ // Set new value and retrieve it.
+ value.setValue(10);
+ ASSERT_EQ(10, value.getValue());
+
+ // Do it again and commit it.
+ value.setValue(20);
+ ASSERT_EQ(20, value.getValue());
+ value.commit();
+ EXPECT_EQ(20, value.getValue());
+}
+
+// This test verifies that the value can be reverted if it hasn't been
+// committed.
+TEST(StagedValueTest, revert) {
+ // Set the value and commit.
+ StagedValue<int> value;
+ value.setValue(123);
+ value.commit();
+
+ // Set new value and do not commit.
+ value.setValue(500);
+ // The new value should be the one returned.
+ ASSERT_EQ(500, value.getValue());
+ // But, reverting gets us back to original value.
+ value.revert();
+ EXPECT_EQ(123, value.getValue());
+ // Reverting again doesn't have any effect.
+ value.revert();
+ EXPECT_EQ(123, value.getValue());
+}
+
+// This test verifies that the value can be restored to an original one.
+TEST(StagedValueTest, reset) {
+ // Set the new value and commit.
+ StagedValue<int> value;
+ value.setValue(123);
+ value.commit();
+
+ // Override the value but do not commit.
+ value.setValue(500);
+
+ // Resetting should take us back to default value.
+ value.reset();
+ EXPECT_EQ(0, value.getValue());
+ value.revert();
+ EXPECT_EQ(0, value.getValue());
+}
+
+// This test verifies that second commit doesn't modify a value.
+TEST(StagedValueTest, commit) {
+ // Set the value and commit.
+ StagedValue<int> value;
+ value.setValue(123);
+ value.commit();
+
+ // Second commit should have no effect.
+ value.commit();
+ EXPECT_EQ(123, value.getValue());
+}
+
+// This test checks that type conversion operator works correctly.
+TEST(StagedValueTest, conversionOperator) {
+ StagedValue<int> value;
+ value.setValue(244);
+ EXPECT_EQ(244, value);
+}
+
+// This test checks that the assignment operator works correctly.
+TEST(StagedValueTest, assignmentOperator) {
+ StagedValue<int> value;
+ value = 111;
+ EXPECT_EQ(111, value);
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/state_model_unittest.cc b/src/lib/util/tests/state_model_unittest.cc
new file mode 100644
index 0000000..eaaba73
--- /dev/null
+++ b/src/lib/util/tests/state_model_unittest.cc
@@ -0,0 +1,916 @@
+// Copyright (C) 2013-2020 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/state_model.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test derivation of StateModel for exercising state model mechanics.
+///
+/// This class facilitates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines. It implements a very
+/// rudimentary state model, sufficient to test the state model mechanics
+/// supplied by the base class.
+class StateModelTest : public StateModel, public testing::Test {
+public:
+
+ ///@brief StateModelTest states
+ ///@brief Fake state used for handler mapping tests.
+ static const int DUMMY_ST = SM_DERIVED_STATE_MIN + 1;
+
+ ///@brief Starting state for the test state model.
+ static const int READY_ST = SM_DERIVED_STATE_MIN + 2;
+
+ ///@brief State which simulates doing asynchronous work.
+ static const int DO_WORK_ST = SM_DERIVED_STATE_MIN + 3;
+
+ ///@brief State which finishes off processing.
+ static const int DONE_ST = SM_DERIVED_STATE_MIN + 4;
+
+ ///@brief State in which model is always paused.
+ static const int PAUSE_ALWAYS_ST = SM_DERIVED_STATE_MIN + 5;
+
+ ///@brief State in which model is paused at most once.
+ static const int PAUSE_ONCE_ST = SM_DERIVED_STATE_MIN + 6;
+
+ // StateModelTest events
+ ///@brief Event used to trigger initiation of asynchronous work.
+ static const int WORK_START_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ ///@brief Event issued when the asynchronous work "completes".
+ static const int WORK_DONE_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ ///@brief Event issued when all the work is done.
+ static const int ALL_DONE_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ ///@brief Event used to trigger an attempt to transition to bad state
+ static const int FORCE_UNDEFINED_ST_EVT = SM_DERIVED_EVENT_MIN + 4;
+
+ ///@brief Event used to trigger an attempt to transition to bad state
+ static const int SIMULATE_ERROR_EVT = SM_DERIVED_EVENT_MIN + 5;
+
+ ///@brief Event used to indicate that state machine is unpaused.
+ static const int UNPAUSED_EVT = SM_DERIVED_EVENT_MIN + 6;
+
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by StateModel.
+ StateModelTest() : dummy_called_(false), work_completed_(false),
+ failure_explanation_("") {
+ }
+ /// @brief Destructor
+ virtual ~StateModelTest() {
+ }
+
+ /// @brief Fetches the value of the dummy called flag.
+ bool getDummyCalled() {
+ return (dummy_called_);
+ }
+
+ /// @brief StateHandler for fake state, DummyState.
+ ///
+ /// It simply sets the dummy called flag to indicate that this method
+ /// was invoked.
+ void dummyHandler() {
+ dummy_called_ = true;
+ }
+
+ /// @brief Returns the failure explanation string.
+ ///
+ /// This value is set only via onModelFailure and it stores whatever
+ /// explanation that method was passed.
+ const std::string& getFailureExplanation() {
+ return (failure_explanation_);
+ }
+
+ /// @brief Returns indication of whether or not the model succeeded.
+ ///
+ /// If true, this indicates that the test model executed correctly through
+ /// to completion. The flag is only by the DONE_ST handler.
+ bool getWorkCompleted() {
+ return (work_completed_);
+ }
+
+ /// @brief State handler for the READY_ST.
+ ///
+ /// Serves as the starting state handler, it consumes the
+ /// START_EVT "transitioning" to the state, DO_WORK_ST and
+ /// sets the next event to WORK_START_EVT.
+ void readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ transition(DO_WORK_ST, WORK_START_EVT);
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "readyHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DO_WORK_ST.
+ ///
+ /// Simulates a state that starts some form of asynchronous work.
+ /// When next event is WORK_START_EVT it sets the status to pending
+ /// and signals the state model must "wait" for an event by setting
+ /// next event to NOP_EVT.
+ ///
+ /// When next event is IO_COMPLETED_EVT, it transitions to the state,
+ /// DONE_ST, and sets the next event to WORK_DONE_EVT.
+ void doWorkHandler() {
+ switch(getNextEvent()) {
+ case WORK_START_EVT:
+ postNextEvent(NOP_EVT);
+ break;
+ case WORK_DONE_EVT:
+ work_completed_ = true;
+ transition(DONE_ST, ALL_DONE_EVT);
+ break;
+ case FORCE_UNDEFINED_ST_EVT:
+ transition(9999, ALL_DONE_EVT);
+ break;
+ case SIMULATE_ERROR_EVT:
+ throw std::logic_error("Simulated Unexpected Error");
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "doWorkHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DONE_ST.
+ ///
+ /// This is the last state in the model. Note that it sets the
+ /// status to completed and next event to NOP_EVT.
+ void doneWorkHandler() {
+ switch(getNextEvent()) {
+ case ALL_DONE_EVT:
+ endModel();
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "doneWorkHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for PAUSE_ALWAYS_ST and PAUSE_ONCE_ST.
+ void pauseHandler() {
+ postNextEvent(NOP_EVT);
+ }
+
+ /// @brief Construct the event dictionary.
+ virtual void defineEvents() {
+ // Invoke the base call implementation first.
+ StateModel::defineEvents();
+
+ // Define our events.
+ defineEvent(WORK_START_EVT, "WORK_START_EVT");
+ defineEvent(WORK_DONE_EVT , "WORK_DONE_EVT");
+ defineEvent(ALL_DONE_EVT, "ALL_DONE_EVT");
+ defineEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT");
+ defineEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT");
+ defineEvent(UNPAUSED_EVT, "UNPAUSED_EVT");
+ }
+
+ /// @brief Verify the event dictionary.
+ virtual void verifyEvents() {
+ // Invoke the base call implementation first.
+ StateModel::verifyEvents();
+
+ // Verify our events.
+ getEvent(WORK_START_EVT);
+ getEvent(WORK_DONE_EVT);
+ getEvent(ALL_DONE_EVT);
+ getEvent(FORCE_UNDEFINED_ST_EVT);
+ getEvent(SIMULATE_ERROR_EVT);
+ getEvent(UNPAUSED_EVT);
+ }
+
+ /// @brief Construct the state dictionary.
+ virtual void defineStates() {
+ // Invoke the base call implementation first.
+ StateModel::defineStates();
+
+ // Define our states.
+ defineState(DUMMY_ST, "DUMMY_ST",
+ std::bind(&StateModelTest::dummyHandler, this));
+
+ defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::readyHandler, this));
+
+ defineState(DO_WORK_ST, "DO_WORK_ST",
+ std::bind(&StateModelTest::doWorkHandler, this));
+
+ defineState(DONE_ST, "DONE_ST",
+ std::bind(&StateModelTest::doneWorkHandler, this));
+
+ defineState(PAUSE_ALWAYS_ST, "PAUSE_ALWAYS_ST",
+ std::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ALWAYS);
+
+ defineState(PAUSE_ONCE_ST, "PAUSE_ONCE_ST",
+ std::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ONCE);
+ }
+
+ /// @brief Verify the state dictionary.
+ virtual void verifyStates() {
+ // Invoke the base call implementation first.
+ StateModel::verifyStates();
+
+ // Verify our states.
+ getStateInternal(DUMMY_ST);
+ getStateInternal(READY_ST);
+ getStateInternal(DO_WORK_ST);
+ getStateInternal(DONE_ST);
+ getStateInternal(PAUSE_ALWAYS_ST);
+ getStateInternal(PAUSE_ONCE_ST);
+ }
+
+ /// @brief Manually construct the event and state dictionaries.
+ /// This allows testing without running startModel.
+ void initDictionaries() {
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+ }
+
+ /// @brief Tests the event dictionary entry for the given event value.
+ bool checkEvent(const int value, const std::string& label) {
+ EventPtr event;
+ try {
+ event = getEvent(value);
+ EXPECT_TRUE(event);
+ EXPECT_EQ(value, event->getValue());
+ EXPECT_EQ(label, event->getLabel());
+ } catch (const std::exception& ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// @brief Tests the state dictionary entry for the given state value.
+ bool checkState(const int value, const std::string& label) {
+ EventPtr state;
+ try {
+ state = getState(value);
+ EXPECT_TRUE(state);
+ EXPECT_EQ(value, state->getValue());
+ EXPECT_EQ(label, state->getLabel());
+ } catch (const std::exception& ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /// @brief Handler called when the model suffers an execution error.
+ virtual void onModelFailure(const std::string& explanation) {
+ failure_explanation_ = explanation;
+ }
+
+ /// @brief Indicator of whether or not the DUMMY_ST handler has been called.
+ bool dummy_called_;
+
+ /// @brief Indicator of whether or not DONE_ST handler has been called.
+ bool work_completed_;
+
+ /// @brief Stores the failure explanation
+ std::string failure_explanation_;
+};
+
+// Declare them so gtest can see them.
+const int StateModelTest::DUMMY_ST;
+const int StateModelTest::READY_ST;
+const int StateModelTest::DO_WORK_ST;
+const int StateModelTest::DONE_ST;
+const int StateModelTest::WORK_START_EVT;
+const int StateModelTest::WORK_DONE_EVT;
+const int StateModelTest::ALL_DONE_EVT;
+const int StateModelTest::PAUSE_ALWAYS_ST;
+const int StateModelTest::PAUSE_ONCE_ST;
+
+/// @brief Checks the fundamentals of defining and retrieving events.
+TEST_F(StateModelTest, eventDefinition) {
+ // After construction, the event dictionary should be empty. Verify that
+ // getEvent will throw when event is not defined.
+ EXPECT_THROW(getEvent(NOP_EVT), StateModelError);
+
+ // Verify that we can add a handler to the map.
+ ASSERT_NO_THROW(defineEvent(NOP_EVT, "NOP_EVT"));
+
+ // Verify that we can find the event by value and its content is correct.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+
+ // Verify that we cannot add a duplicate.
+ ASSERT_THROW(defineEvent(NOP_EVT, "NOP_EVT"), StateModelError);
+
+ // Verify that we can still find the event.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+}
+
+/// @brief Tests event dictionary construction and verification.
+TEST_F(StateModelTest, eventDictionary) {
+ // After construction, the event dictionary should be empty.
+ // Make sure that verifyEvents() throws.
+ EXPECT_THROW(verifyEvents(), StateModelError);
+
+ // Construct the dictionary and verify it.
+ EXPECT_NO_THROW(defineEvents());
+ EXPECT_NO_THROW(verifyEvents());
+
+ // Verify base class events are defined.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+ EXPECT_TRUE(checkEvent(START_EVT, "START_EVT"));
+ EXPECT_TRUE(checkEvent(END_EVT, "END_EVT"));
+ EXPECT_TRUE(checkEvent(FAIL_EVT, "FAIL_EVT"));
+
+ // Verify stub class events are defined.
+ EXPECT_TRUE(checkEvent(WORK_START_EVT, "WORK_START_EVT"));
+ EXPECT_TRUE(checkEvent(WORK_DONE_EVT, "WORK_DONE_EVT"));
+ EXPECT_TRUE(checkEvent(ALL_DONE_EVT, "ALL_DONE_EVT"));
+ EXPECT_TRUE(checkEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT"));
+ EXPECT_TRUE(checkEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT"));
+
+ // Verify that undefined events are handled correctly.
+ EXPECT_THROW(getEvent(9999), StateModelError);
+ EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getEventLabel(9999));
+}
+
+/// @brief General testing of event context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, eventContextAccessors) {
+ // Construct the event definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+
+ // Verify the post-construction values.
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call setEvent which will update both next event and last event.
+ EXPECT_NO_THROW(postNextEvent(START_EVT));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call setEvent again.
+ EXPECT_NO_THROW(postNextEvent(WORK_START_EVT));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(WORK_START_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+
+ // Verify that posting an undefined event throws.
+ EXPECT_THROW(postNextEvent(9999), StateModelError);
+}
+
+/// @brief Tests the fundamental methods used for state handler mapping.
+/// Verifies the ability to search for and add entries in the state handler map.
+TEST_F(StateModelTest, stateDefinition) {
+ // After construction, the state dictionary should be empty. Verify that
+ // getState will throw when, state is not defined.
+ EXPECT_THROW(getState(READY_ST), StateModelError);
+
+ // Verify that we can add a state to the dictionary.
+ ASSERT_NO_THROW(defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::dummyHandler,
+ this)));
+
+ // Verify that we can find the state by its value.
+ StatePtr state;
+ EXPECT_NO_THROW(state = getState(READY_ST));
+ EXPECT_TRUE(state);
+
+ // Verify the state's value and label.
+ EXPECT_EQ(READY_ST, state->getValue());
+ EXPECT_EQ("READY_ST", state->getLabel());
+
+ // Now verify that retrieved state's handler executes the correct method.
+ // Make sure the dummy called flag is false prior to invocation.
+ EXPECT_FALSE(getDummyCalled());
+
+ // Invoke the state's handler.
+ EXPECT_NO_THROW(state->run());
+
+ // Verify the dummy called flag is now true.
+ EXPECT_TRUE(getDummyCalled());
+
+ // Verify that we cannot add a duplicate.
+ EXPECT_THROW(defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::readyHandler, this)),
+ StateModelError);
+
+ // Verify that we can still find the state.
+ EXPECT_NO_THROW(getState(READY_ST));
+}
+
+/// @brief Tests state dictionary initialization and validation.
+/// This tests the basic concept of state dictionary initialization and
+/// verification by manually invoking the methods normally called by startModel.
+TEST_F(StateModelTest, stateDictionary) {
+ // Verify that the map validation throws prior to the dictionary being
+ // initialized.
+ EXPECT_THROW(verifyStates(), StateModelError);
+
+ // Construct the dictionary and verify it.
+ ASSERT_NO_THROW(defineStates());
+ EXPECT_NO_THROW(verifyStates());
+
+ // Verify the base class states.
+ EXPECT_TRUE(checkState(NEW_ST, "NEW_ST"));
+ EXPECT_TRUE(checkState(END_ST, "END_ST"));
+
+ // Verify stub class states.
+ EXPECT_TRUE(checkState(DUMMY_ST, "DUMMY_ST"));
+ EXPECT_TRUE(checkState(READY_ST, "READY_ST"));
+ EXPECT_TRUE(checkState(DO_WORK_ST, "DO_WORK_ST"));
+ EXPECT_TRUE(checkState(DONE_ST, "DONE_ST"));
+
+ // Verify that undefined states are handled correctly.
+ EXPECT_THROW(getState(9999), StateModelError);
+ EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getStateLabel(9999));
+}
+
+/// @brief General testing of state context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, stateContextAccessors) {
+ // setState will throw unless we initialize the handler map.
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // Verify post-construction state values.
+ EXPECT_EQ(NEW_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+
+ // Call setState which will update both state and previous state.
+ EXPECT_NO_THROW(setState(READY_ST));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(READY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+
+ // Call setState again.
+ EXPECT_NO_THROW(setState(DO_WORK_ST));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(READY_ST, getPrevState());
+
+ // Verify that calling setState with an state that has no handler
+ // will throw.
+ EXPECT_THROW(setState(-1), StateModelError);
+
+ // Verify that calling setState with NEW_ST is ok.
+ EXPECT_NO_THROW(setState(NEW_ST));
+
+ // Verify that calling setState with END_ST is ok.
+ EXPECT_NO_THROW(setState(END_ST));
+
+ // Verify that calling setState with an undefined state throws.
+ EXPECT_THROW(setState(9999), StateModelError);
+}
+
+/// @brief Checks that invoking runModel prior to startModel is not allowed.
+TEST_F(StateModelTest, runBeforeStart) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+
+ // Attempt to call runModel before startModel. This should result in an
+ // orderly model failure.
+ ASSERT_NO_THROW(runModel(START_EVT));
+
+ // Check that state and event are correct.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(FAIL_EVT, getNextEvent());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+}
+
+/// @brief Tests that the endModel may be used to transition the model to
+/// a normal conclusion.
+TEST_F(StateModelTest, transitionWithEnd) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Verify that state and event members are as expected.
+ EXPECT_EQ(DUMMY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(endModel());
+
+ // Verify state and event members are correctly set.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(DUMMY_ST, getPrevState());
+ EXPECT_EQ(END_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the abortModel may be used to transition the model to
+/// failed conclusion.
+TEST_F(StateModelTest, transitionWithAbort) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Verify that state and event members are as expected.
+ EXPECT_EQ(DUMMY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(abortModel("test invocation"));
+
+ // Verify state and event members are correctly set.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(DUMMY_ST, getPrevState());
+ EXPECT_EQ(FAIL_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the boolean indicators for on state entry and exit
+/// work properly.
+TEST_F(StateModelTest, doFlags) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // Verify that "do" flags are false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // We are transitioning states, so "do" flags should be true.
+ EXPECT_TRUE(doOnEntry());
+ EXPECT_TRUE(doOnExit());
+
+ // "do" flags are one-shots, so they should now both be false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+ // call transition to re-enter same state, "do" flags should be false.
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // "do" flags should be false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+}
+
+/// @brief Verifies that the model status methods accurately reflect the model
+/// status. It also verifies that the dictionaries can be modified before
+/// the model is running but not after.
+TEST_F(StateModelTest, statusMethods) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // After construction, state model is "new", all others should be false.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // Verify that events and states can be added before the model is started.
+ EXPECT_NO_THROW(defineEvent(9998, "9998"));
+ EXPECT_NO_THROW(defineState(9998, "9998",
+ std::bind(&StateModelTest::readyHandler,
+ this)));
+
+ // "START" the model.
+ // Fake out starting the model by calling transition to move from NEW_ST
+ // to DUMMY_ST with START_EVT. If we used startModel this would blow by
+ // the status of "running" but not "waiting".
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+
+ // Verify that events and states cannot be added after the model is started.
+ EXPECT_THROW(defineEvent(9999, "9999"), StateModelError);
+ EXPECT_THROW(defineState(9999, "9999",
+ std::bind(&StateModelTest::readyHandler, this)),
+ StateModelError);
+
+ // The state and event combos set above, should show the model as
+ // "running", all others should be false.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_TRUE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // call transition to submit NOP_EVT to current state, DUMMY_ST.
+ EXPECT_NO_THROW(transition(DUMMY_ST, NOP_EVT));
+
+ // Verify the status methods are correct: with next event set to NOP_EVT,
+ // model should be "running" and "waiting".
+ EXPECT_FALSE(isModelNew());
+ EXPECT_TRUE(isModelRunning());
+ EXPECT_TRUE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(endModel());
+
+ // With state set to END_ST, model should be done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+}
+
+/// @brief Tests that the model status methods are correct after a model
+/// failure.
+TEST_F(StateModelTest, statusMethodsOnFailure) {
+ // Construct the event and state definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(abortModel("test invocation"));
+
+ // With state set to END_ST, model should be done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+}
+
+/// @brief Checks that the context strings accurately reflect context and
+/// are safe to invoke.
+TEST_F(StateModelTest, contextStrs) {
+ // Verify context methods do not throw prior to dictionary init.
+ ASSERT_NO_THROW(getContextStr());
+ ASSERT_NO_THROW(getPrevContextStr());
+
+ // Construct the event and state definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // transition uses setState and setEvent, testing it tests all three.
+ EXPECT_NO_THROW(transition(READY_ST, START_EVT));
+
+ // Verify the current context string depicts correct state and event.
+ std::string ctx_str;
+ ASSERT_NO_THROW(ctx_str = getContextStr());
+ EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(READY_ST)));
+ EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(START_EVT)));
+
+ // Verify the previous context string depicts correct state and event.
+ ASSERT_NO_THROW(ctx_str = getPrevContextStr());
+ EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(NEW_ST)));
+ EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(NOP_EVT)));
+}
+
+/// @brief Tests that undefined states are handled gracefully.
+/// This test verifies that attempting to transition to an undefined state,
+/// which constitutes a model violation, results in an orderly model failure.
+TEST_F(StateModelTest, undefinedState) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel. This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Resume the model with next event set to cause the DO_WORK_ST handler
+ // to transition to an undefined state. This should cause it to return
+ // without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(FORCE_UNDEFINED_ST_EVT));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that an unexpected exception thrown by a state handler is
+/// handled gracefully. State models are supposed to account for and handle
+/// all errors that they actions (i.e. handlers) may cause. In the event they
+/// do not, this constitutes a model violation. This test verifies such
+/// violations are handled correctly and result in an orderly model failure.
+TEST_F(StateModelTest, unexpectedError) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel with a start state of READY_ST.
+ // This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Resume the model with next event set to cause the DO_WORK_ST handler
+ // to transition to an undefined state. This should cause it to return
+ // without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(SIMULATE_ERROR_EVT));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that undefined events are handled gracefully.
+/// This test verifies that submitting an undefined event to the state machine
+/// results, which constitutes a model violation, results in an orderly model
+/// failure.
+TEST_F(StateModelTest, undefinedEvent) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel with a start state of READY_ST.
+ // This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Attempting to post an undefined event within runModel should cause it
+ // to return without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(9999));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Test the basic mechanics of state model execution.
+/// This test exercises the ability to execute state model from start to
+/// finish, including the handling of a asynchronous IO operation.
+TEST_F(StateModelTest, stateModelTest) {
+ // Verify that status methods are correct: model is new.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // Launch the transaction by calling startModel. The state model
+ // should run up until the simulated async work operation is initiated
+ // in DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify that we are now in state of DO_WORK_ST, the last event was
+ // WORK_START_EVT, the next event is NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(WORK_START_EVT, getLastEvent());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Simulate completion of async work completion by resuming runModel with
+ // an event of WORK_DONE_EVT.
+ ASSERT_NO_THROW(runModel(WORK_DONE_EVT));
+
+ // Verify that the state model has progressed through to completion:
+ // it is in the DONE_ST, the status is ST_COMPLETED, and the next event
+ // is NOP_EVT.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(END_EVT, getNextEvent());
+
+ // Verify that status methods are correct: model done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+
+ // Verify that failure explanation is empty.
+ EXPECT_TRUE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is true.
+ EXPECT_TRUE(getWorkCompleted());
+}
+
+// This test verifies the pausing and un-pausing capabilities of the state
+// model.
+TEST_F(StateModelTest, stateModelPause) {
+ // Verify that status methods are correct: model is new.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(isModelPaused());
+
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // Transition straight to the state in which the model should always
+ // pause.
+ ASSERT_NO_THROW(startModel(PAUSE_ALWAYS_ST));
+
+ // Verify it was successful and that the model is paused.
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Run the model again. It should still be paused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should be paused at most once.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // The model should still be paused until explicitly unpaused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ unpauseModel();
+
+ // Transition back to the first state. The model should pause again.
+ transition(PAUSE_ALWAYS_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should pause only once. This time it should not pause.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_FALSE(isModelPaused());
+}
+
+}
diff --git a/src/lib/util/tests/stopwatch_unittest.cc b/src/lib/util/tests/stopwatch_unittest.cc
new file mode 100644
index 0000000..a506b6b
--- /dev/null
+++ b/src/lib/util/tests/stopwatch_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright (C) 2015-2023 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/stopwatch.h>
+#include <util/stopwatch_impl.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::util;
+using namespace boost::posix_time;
+
+/// @brief @c StopwatchImpl mock object.
+///
+/// This class derives from the @c StopwatchImpl to override the
+/// @c StopwatchImpl::getCurrentTime. This method is internally called by
+/// the @c StopwatchImpl to determine the current time. By providing the
+/// implementation of this method which returns the fixed (well known)
+/// timestamp value we can obtain the deterministic values from the accessors
+/// of this class.
+///
+/// This class also includes some convenience methods to return the time
+/// durations in milliseconds.
+class StopwatchMock : public StopwatchImpl {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param ref_time Reference time, i.e. the arbitrary time value from
+ /// which time is measured. The @c current_time_ value returned by the
+ /// @c StopwatchMock::getCurrentTime is initialized to this value.
+ /// Subsequent calls to the @c StopwatchMock::ffwd move the value of
+ /// the @c current_time_ forward.
+ StopwatchMock(const ptime& ref_time);
+
+ /// @brief Fast forward time.
+ ///
+ /// Moves the value of the @c current_time_ forward by the specified
+ /// number of milliseconds (microseconds). As a result the timestamp
+ /// returned by the @c StopwatchMock::getCurrentTime moves by this value.
+ /// This simulates the time progress.
+ ///
+ /// @param ms Specifies the number of milliseconds to move current time.
+ /// @param us Specifies the number of fractional microseconds to move
+ /// current time.
+ void ffwd(const uint32_t ms, const uint32_t us = 0);
+
+ /// @brief Returns the last duration in milliseconds.
+ uint32_t getLastDurationInMs() const;
+
+ /// @brief Returns the total duration in milliseconds.
+ uint32_t getTotalDurationInMs() const;
+
+protected:
+
+ /// @brief Returns the current time.
+ ///
+ /// This method returns the fixed @c current_time_ timestamp.
+ virtual ptime getCurrentTime() const;
+
+private:
+
+ /// @brief Holds the current time to be returned by the
+ /// @c StopwatchMock::getCurrentTime.
+ ptime current_time_;
+
+};
+
+StopwatchMock::StopwatchMock(const ptime& ref_time)
+ : StopwatchImpl(), current_time_(ref_time) {
+}
+
+void
+StopwatchMock::ffwd(const uint32_t ms, const uint32_t us) {
+ current_time_ += milliseconds(ms);
+ current_time_ += microseconds(us);
+}
+
+uint32_t
+StopwatchMock::getLastDurationInMs() const {
+ return (getLastDuration().total_milliseconds());
+}
+
+uint32_t
+StopwatchMock::getTotalDurationInMs() const {
+ return (getTotalDuration().total_milliseconds());
+}
+
+ptime
+StopwatchMock::getCurrentTime() const {
+ return (current_time_);
+}
+
+/// @brief Test fixture class for testing @c StopwatchImpl.
+class StopwatchTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ StopwatchTest() = default;
+
+ /// @brief Destructor
+ virtual ~StopwatchTest() = default;
+
+protected:
+
+ /// @brief Set up the test.
+ ///
+ /// Initializes the reference time to be used to create the instances
+ /// of the @c StopwatchMock objects.
+ virtual void SetUp();
+
+ /// @brief Holds the reference time to be used to create the instances
+ /// of the @c StopwatchMock objects.
+ ptime ref_time_;
+};
+
+void
+StopwatchTest::SetUp() {
+ ref_time_ = microsec_clock::universal_time();
+}
+
+/// This test checks the behavior of the stopwatch when it is started
+/// and stopped multiple times. It uses the StopwatchMock object to
+/// control the "time flow" by setting the current time to arbitrary
+/// values using the StopwatchMock::ffwd. In addition, this test
+/// checks that the stopwatch can be reset.
+TEST_F(StopwatchTest, multipleMeasurements) {
+ StopwatchMock stopwatch(ref_time_);
+ // The stopwatch shouldn't automatically start. The initial
+ // durations should be set to 0.
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
+
+ stopwatch.start();
+
+ // Even though the stopwatch is started, the time is still set to
+ // the initial value. The durations should not be affected.
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
+
+ // Move the time by 10 ms.
+ stopwatch.ffwd(10);
+
+ // It should be possible to retrieve the durations even when the
+ // stopwatch is running.
+ EXPECT_EQ(10, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
+
+ // Now stop it and make sure that the same values are returned.
+ stopwatch.stop();
+
+ EXPECT_EQ(10, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
+
+ // Start it again, but don't move the time forward yet.
+ stopwatch.start();
+
+ // The new duration should be 0, but the total should be equal to
+ // the previously measured duration.
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(10, stopwatch.getTotalDurationInMs());
+
+ // Move time by 5 ms.
+ stopwatch.ffwd(5);
+
+ // New measured duration should be 5 ms. The total should be 15 ms.
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Stop it again and make sure the values returned are the same.
+ stopwatch.stop();
+
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Move the time forward while the stopwatch is stopped.
+ stopwatch.ffwd(8);
+
+ // The measured values should not be affected.
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Stop should be no-op in this case.
+ stopwatch.stop();
+
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(15, stopwatch.getTotalDurationInMs());
+
+ // Start the stopwatch again.
+ stopwatch.start();
+
+ // Move time by 3 ms.
+ stopwatch.ffwd(3);
+
+ // Since the stopwatch is running, the measured duration should
+ // get updated again.
+ EXPECT_EQ(3, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(18, stopwatch.getTotalDurationInMs());
+
+ // Move the time by 2 ms.
+ stopwatch.ffwd(2);
+
+ // Start should be no-op in this case.
+ stopwatch.start();
+
+ // But the durations should be updated.
+ EXPECT_EQ(5, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(20, stopwatch.getTotalDurationInMs());
+
+ // Make sure we can reset.
+ stopwatch.reset();
+
+ EXPECT_EQ(0, stopwatch.getLastDurationInMs());
+ EXPECT_EQ(0, stopwatch.getTotalDurationInMs());
+}
+
+// This test checks that the stopwatch works when the real clock is in use.
+TEST_F(StopwatchTest, realTime) {
+ // Initially, the measured time should be 0.
+ Stopwatch stopwatch;
+ EXPECT_EQ(0, stopwatch.getLastMilliseconds());
+ EXPECT_EQ(0, stopwatch.getTotalMilliseconds());
+
+ // Start the stopwatch.
+ stopwatch.start();
+
+ // Sleep for 1 ms. The stopwatch should measure this duration.
+ usleep(1000);
+
+ stopwatch.stop();
+
+ // The measured duration should be greater or equal 1 ms.
+ long current_duration = stopwatch.getLastMilliseconds();
+ EXPECT_GE(current_duration, 1);
+ EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds());
+
+ // Sleep for another 2 ms while the stopwatch is in the stopped state.
+ usleep(2000);
+
+ // In the stopped state, we should still have old durations measured.
+ EXPECT_EQ(current_duration, stopwatch.getLastMilliseconds());
+ EXPECT_EQ(current_duration, stopwatch.getTotalMilliseconds());
+
+ // Start it again.
+ stopwatch.start();
+
+ // Sleep for 1 ms.
+ usleep(1000);
+
+ // The durations should get updated as appropriate.
+ EXPECT_GE(stopwatch.getLastMilliseconds(), 1);
+ EXPECT_GE(stopwatch.getTotalMilliseconds(), 2);
+}
+
+// Make sure that we can obtain the durations as microseconds.
+TEST_F(StopwatchTest, getLastMicroseconds) {
+ Stopwatch stopwatch;
+ stopwatch.start();
+
+ usleep(1000);
+
+ stopwatch.stop();
+
+ long current_duration = stopwatch.getLastMicroseconds();
+ EXPECT_GE(current_duration, 1000);
+ EXPECT_EQ(current_duration, stopwatch.getTotalMicroseconds());
+}
+
+// Make sure that we can use the "autostart" option to start the time
+// measurement in the constructor.
+TEST_F(StopwatchTest, autostart) {
+ Stopwatch stopwatch(true);
+ usleep(1000);
+
+ stopwatch.stop();
+
+ EXPECT_GE(stopwatch.getLastMilliseconds(), 1);
+ EXPECT_EQ(stopwatch.getLastMilliseconds(), stopwatch.getTotalMilliseconds());
+}
+
+// Make sure that the conversion to the loggable string works as expected.
+TEST_F(StopwatchTest, logFormat) {
+ time_duration duration = microseconds(223043);
+ EXPECT_EQ("223.043 ms", StopwatchImpl::logFormat(duration));
+
+ duration = microseconds(1234);
+ EXPECT_EQ("1.234 ms", StopwatchImpl::logFormat(duration));
+
+ duration = microseconds(2000);
+ EXPECT_EQ("2.000 ms", StopwatchImpl::logFormat(duration));
+
+ duration = milliseconds(2100);
+ EXPECT_EQ("2.10 s", StopwatchImpl::logFormat(duration));
+
+ duration = milliseconds(3123);
+ EXPECT_EQ("3.12 s", StopwatchImpl::logFormat(duration));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/strutil_unittest.cc b/src/lib/util/tests/strutil_unittest.cc
new file mode 100644
index 0000000..912b40f
--- /dev/null
+++ b/src/lib/util/tests/strutil_unittest.cc
@@ -0,0 +1,642 @@
+// Copyright (C) 2011-2022 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/strutil.h>
+#include <util/encode/hex.h>
+
+#include <gtest/gtest.h>
+
+#include <stdint.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::util;
+using namespace isc::util::str;
+using namespace std;
+
+namespace {
+
+// Check for slash replacement
+
+TEST(StringUtilTest, Slash) {
+
+ string instring = "";
+ isc::util::str::normalizeSlash(instring);
+ EXPECT_EQ("", instring);
+
+ instring = "C:\\A\\B\\C.D";
+ isc::util::str::normalizeSlash(instring);
+ EXPECT_EQ("C:/A/B/C.D", instring);
+
+ instring = "// \\ //";
+ isc::util::str::normalizeSlash(instring);
+ EXPECT_EQ("// / //", instring);
+}
+
+// Check that leading and trailing space trimming works
+
+TEST(StringUtilTest, Trim) {
+
+ // Empty and full string.
+ EXPECT_EQ("", isc::util::str::trim(""));
+ EXPECT_EQ("abcxyz", isc::util::str::trim("abcxyz"));
+
+ // Trim right-most blanks
+ EXPECT_EQ("ABC", isc::util::str::trim("ABC "));
+ EXPECT_EQ("ABC", isc::util::str::trim("ABC\t\t \n\t"));
+
+ // Left-most blank trimming
+ EXPECT_EQ("XYZ", isc::util::str::trim(" XYZ"));
+ EXPECT_EQ("XYZ", isc::util::str::trim("\t\t \tXYZ"));
+
+ // Right and left, with embedded spaces
+ EXPECT_EQ("MN \t OP", isc::util::str::trim("\t\tMN \t OP \t"));
+}
+
+// Check tokenization. Note that ASSERT_EQ is used to check the size of the
+// returned vector; if not as expected, the following references may be invalid
+// so should not be used.
+
+TEST(StringUtilTest, Tokens) {
+ vector<string> result;
+
+ // Default delimiters
+
+ // Degenerate cases
+ result = isc::util::str::tokens(""); // Empty string
+ EXPECT_EQ(0, result.size());
+
+ result = isc::util::str::tokens(" \n "); // String is all delimiters
+ EXPECT_EQ(0, result.size());
+
+ result = isc::util::str::tokens("abc"); // String has no delimiters
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+
+ // String containing leading and/or trailing delimiters, no embedded ones.
+ result = isc::util::str::tokens("\txyz"); // One leading delimiter
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("\t \nxyz"); // Multiple leading delimiters
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("xyz\n"); // One trailing delimiter
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("xyz \t"); // Multiple trailing
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ result = isc::util::str::tokens("\t xyz \n"); // Leading and trailing
+ ASSERT_EQ(1, result.size());
+ EXPECT_EQ(string("xyz"), result[0]);
+
+ // Embedded delimiters
+ result = isc::util::str::tokens("abc\ndef"); // 2 tokens, one separator
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+
+ result = isc::util::str::tokens("abc\t\t\ndef"); // 2 tokens, 3 separators
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+
+ result = isc::util::str::tokens("abc\n \tdef\t\tghi");
+ ASSERT_EQ(3, result.size()); // Multiple tokens, many delims
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+ EXPECT_EQ(string("ghi"), result[2]);
+
+ // Embedded and non-embedded delimiters
+
+ result = isc::util::str::tokens("\t\t \nabc\n \tdef\t\tghi \n\n");
+ ASSERT_EQ(3, result.size()); // Multiple tokens, many delims
+ EXPECT_EQ(string("abc"), result[0]);
+ EXPECT_EQ(string("def"), result[1]);
+ EXPECT_EQ(string("ghi"), result[2]);
+
+ // Non-default delimiter
+ result = isc::util::str::tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
+ ASSERT_EQ(6, result.size());
+ EXPECT_EQ(string("alpha"), result[0]);
+ EXPECT_EQ(string("beta"), result[1]);
+ EXPECT_EQ(string(" "), result[2]);
+ EXPECT_EQ(string("gamma"), result[3]);
+ EXPECT_EQ(string("delta"), result[4]);
+ EXPECT_EQ(string("epsilon"), result[5]);
+
+ // Non-default delimiters (plural)
+ result = isc::util::str::tokens("+*--alpha*beta+ -gamma**delta+epsilon-+**",
+ "*+-");
+ ASSERT_EQ(6, result.size());
+ EXPECT_EQ(string("alpha"), result[0]);
+ EXPECT_EQ(string("beta"), result[1]);
+ EXPECT_EQ(string(" "), result[2]);
+ EXPECT_EQ(string("gamma"), result[3]);
+ EXPECT_EQ(string("delta"), result[4]);
+ EXPECT_EQ(string("epsilon"), result[5]);
+
+ // Escaped delimiter
+ result = isc::util::str::tokens("foo\\,bar", ",", true);
+ EXPECT_EQ(1, result.size());
+ EXPECT_EQ(string("foo,bar"), result[0]);
+
+ // Escaped escape
+ result = isc::util::str::tokens("foo\\\\,bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo\\"), result[0]);
+ EXPECT_EQ(string("bar"), result[1]);
+
+ // Double escapes
+ result = isc::util::str::tokens("foo\\\\\\\\,\\bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo\\\\"), result[0]);
+ EXPECT_EQ(string("\\bar"), result[1]);
+
+ // Escaped standard character
+ result = isc::util::str::tokens("fo\\o,bar", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("fo\\o"), result[0]);
+ EXPECT_EQ(string("bar"), result[1]);
+
+ // Escape at the end
+ result = isc::util::str::tokens("foo,bar\\", ",", true);
+ ASSERT_EQ(2, result.size());
+ EXPECT_EQ(string("foo"), result[0]);
+ EXPECT_EQ(string("bar\\"), result[1]);
+
+ // Escape opening a token
+ result = isc::util::str::tokens("foo,\\,,bar", ",", true);
+ ASSERT_EQ(3, result.size());
+ EXPECT_EQ(string("foo"), result[0]);
+ EXPECT_EQ(string(","), result[1]);
+ EXPECT_EQ(string("bar"), result[2]);
+}
+
+// Changing case
+
+TEST(StringUtilTest, ChangeCase) {
+ string mixed("abcDEFghiJKLmno123[]{=+--+]}");
+ string upper("ABCDEFGHIJKLMNO123[]{=+--+]}");
+ string lower("abcdefghijklmno123[]{=+--+]}");
+
+ string test = mixed;
+ isc::util::str::lowercase(test);
+ EXPECT_EQ(lower, test);
+
+ test = mixed;
+ isc::util::str::uppercase(test);
+ EXPECT_EQ(upper, test);
+}
+
+// Formatting
+
+TEST(StringUtilTest, Formatting) {
+
+ vector<string> args;
+ args.push_back("arg1");
+ args.push_back("arg2");
+ args.push_back("arg3");
+
+ string format1 = "This is a string with no tokens";
+ EXPECT_EQ(format1, isc::util::str::format(format1, args));
+
+ string format2 = ""; // Empty string
+ EXPECT_EQ(format2, isc::util::str::format(format2, args));
+
+ string format3 = " "; // Empty string
+ EXPECT_EQ(format3, isc::util::str::format(format3, args));
+
+ string format4 = "String with %d non-string tokens %lf";
+ EXPECT_EQ(format4, isc::util::str::format(format4, args));
+
+ string format5 = "String with %s correct %s number of tokens %s";
+ string result5 = "String with arg1 correct arg2 number of tokens arg3";
+ EXPECT_EQ(result5, isc::util::str::format(format5, args));
+
+ string format6 = "String with %s too %s few tokens";
+ string result6 = "String with arg1 too arg2 few tokens";
+ EXPECT_EQ(result6, isc::util::str::format(format6, args));
+
+ string format7 = "String with %s too %s many %s tokens %s !";
+ string result7 = "String with arg1 too arg2 many arg3 tokens %s !";
+ EXPECT_EQ(result7, isc::util::str::format(format7, args));
+
+ string format8 = "String with embedded%s%s%stokens";
+ string result8 = "String with embeddedarg1arg2arg3tokens";
+ EXPECT_EQ(result8, isc::util::str::format(format8, args));
+
+ // Handle an empty vector
+ args.clear();
+ string format9 = "%s %s";
+ EXPECT_EQ(format9, isc::util::str::format(format9, args));
+}
+
+TEST(StringUtilTest, getToken) {
+ string s("a b c");
+ istringstream ss(s);
+ EXPECT_EQ("a", isc::util::str::getToken(ss));
+ EXPECT_EQ("b", isc::util::str::getToken(ss));
+ EXPECT_EQ("c", isc::util::str::getToken(ss));
+ EXPECT_THROW(isc::util::str::getToken(ss), isc::util::str::StringTokenError);
+}
+
+int32_t tokenToNumCall_32_16(const string& token) {
+ return isc::util::str::tokenToNum<int32_t, 16>(token);
+}
+
+int16_t tokenToNumCall_16_8(const string& token) {
+ return isc::util::str::tokenToNum<int16_t, 8>(token);
+}
+
+TEST(StringUtilTest, tokenToNum) {
+ uint32_t num32 = tokenToNumCall_32_16("0");
+ EXPECT_EQ(0, num32);
+ num32 = tokenToNumCall_32_16("123");
+ EXPECT_EQ(123, num32);
+ num32 = tokenToNumCall_32_16("65535");
+ EXPECT_EQ(65535, num32);
+
+ EXPECT_THROW(tokenToNumCall_32_16(""),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("a"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("-1"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("65536"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("1234567890"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_32_16("-1234567890"),
+ isc::util::str::StringTokenError);
+
+ uint16_t num16 = tokenToNumCall_16_8("123");
+ EXPECT_EQ(123, num16);
+ num16 = tokenToNumCall_16_8("0");
+ EXPECT_EQ(0, num16);
+ num16 = tokenToNumCall_16_8("255");
+ EXPECT_EQ(255, num16);
+
+ EXPECT_THROW(tokenToNumCall_16_8(""),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("a"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("-1"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("256"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("1234567890"),
+ isc::util::str::StringTokenError);
+ EXPECT_THROW(tokenToNumCall_16_8("-1234567890"),
+ isc::util::str::StringTokenError);
+
+}
+
+/// @brief Convenience function which calls quotedStringToBinary
+/// and converts returned vector back to string.
+///
+/// @param s Input string.
+/// @return String holding a copy of a vector returned by the
+/// quotedStringToBinary.
+std::string testQuoted(const std::string& s) {
+ std::vector<uint8_t> vec = str::quotedStringToBinary(s);
+ std::string s2(vec.begin(), vec.end());
+ return (s2);
+}
+
+TEST(StringUtilTest, quotedStringToBinary) {
+ // No opening or closing quote should result in empty string.
+ EXPECT_TRUE(str::quotedStringToBinary("'").empty());
+ EXPECT_TRUE(str::quotedStringToBinary("").empty());
+ EXPECT_TRUE(str::quotedStringToBinary(" ").empty());
+ EXPECT_TRUE(str::quotedStringToBinary("'circuit id").empty());
+ EXPECT_TRUE(str::quotedStringToBinary("circuit id'").empty());
+
+ // If there is only opening and closing quote, an empty
+ // vector should be returned.
+ EXPECT_TRUE(str::quotedStringToBinary("''").empty());
+
+ // Both opening and ending quote is present.
+ EXPECT_EQ("circuit id", testQuoted("'circuit id'"));
+ EXPECT_EQ("remote id", testQuoted(" ' remote id'"));
+ EXPECT_EQ("duid", testQuoted(" ' duid'"));
+ EXPECT_EQ("duid", testQuoted("'duid ' "));
+ EXPECT_EQ("remote'id", testQuoted(" ' remote'id '"));
+ EXPECT_EQ("remote id'", testQuoted("'remote id''"));
+ EXPECT_EQ("'remote id", testQuoted("''remote id'"));
+
+ // Multiple quotes.
+ EXPECT_EQ("'", testQuoted("'''"));
+ EXPECT_EQ("''", testQuoted("''''"));
+}
+
+/// @brief Test that hex string with colons can be decoded.
+///
+/// @param input Input string to be decoded.
+/// @param reference A string without colons representing the
+/// decoded data.
+void testColonSeparated(const std::string& input,
+ const std::string& reference) {
+ // Create a reference vector.
+ std::vector<uint8_t> reference_vector;
+ ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector));
+
+ // Fill the output vector with some garbage to make sure that
+ // the data is erased when a string is decoded successfully.
+ std::vector<uint8_t> decoded(1, 10);
+ ASSERT_NO_THROW(decodeColonSeparatedHexString(input, decoded));
+
+ // Get the string representation of the decoded data for logging
+ // purposes.
+ std::string encoded;
+ ASSERT_NO_THROW(encoded = encode::encodeHex(decoded));
+
+ // Check if the decoded data matches the reference.
+ EXPECT_TRUE(decoded == reference_vector)
+ << "decoded data don't match the reference, input='"
+ << input << "', reference='" << reference << "'"
+ ", decoded='" << encoded << "'";
+}
+
+TEST(StringUtilTest, decodeColonSeparatedHexString) {
+ // Test valid strings.
+ testColonSeparated("A1:02:C3:d4:e5:F6", "A102C3D4E5F6");
+ testColonSeparated("A:02:3:d:E5:F6", "0A02030DE5F6");
+ testColonSeparated("A:B:C:D", "0A0B0C0D");
+ testColonSeparated("1", "01");
+ testColonSeparated("1e", "1E");
+ testColonSeparated("", "");
+
+ // Test invalid strings.
+ std::vector<uint8_t> decoded;
+ // Whitespaces.
+ EXPECT_THROW(decodeColonSeparatedHexString(" ", decoded),
+ isc::BadValue);
+ // Whitespace before digits.
+ EXPECT_THROW(decodeColonSeparatedHexString(" A1", decoded),
+ isc::BadValue);
+ // Two consecutive colons.
+ EXPECT_THROW(decodeColonSeparatedHexString("A::01", decoded),
+ isc::BadValue);
+ // Three consecutive colons.
+ EXPECT_THROW(decodeColonSeparatedHexString("A:::01", decoded),
+ isc::BadValue);
+ // Whitespace within a string.
+ EXPECT_THROW(decodeColonSeparatedHexString("A :01", decoded),
+ isc::BadValue);
+ // Terminating colon.
+ EXPECT_THROW(decodeColonSeparatedHexString("0A:01:", decoded),
+ isc::BadValue);
+ // Opening colon.
+ EXPECT_THROW(decodeColonSeparatedHexString(":0A:01", decoded),
+ isc::BadValue);
+ // Three digits before the colon.
+ EXPECT_THROW(decodeColonSeparatedHexString("0A1:B1", decoded),
+ isc::BadValue);
+}
+
+void testFormatted(const std::string& input,
+ const std::string& reference) {
+ // Create a reference vector.
+ std::vector<uint8_t> reference_vector;
+ ASSERT_NO_THROW(encode::decodeHex(reference, reference_vector));
+
+ // Fill the output vector with some garbage to make sure that
+ // the data is erased when a string is decoded successfully.
+ std::vector<uint8_t> decoded(1, 10);
+ ASSERT_NO_THROW(decodeFormattedHexString(input, decoded));
+
+ // Get the string representation of the decoded data for logging
+ // purposes.
+ std::string encoded;
+ ASSERT_NO_THROW(encoded = encode::encodeHex(decoded));
+
+ // Check if the decoded data matches the reference.
+ EXPECT_TRUE(decoded == reference_vector)
+ << "decoded data don't match the reference, input='"
+ << input << "', reference='" << reference << "'"
+ ", decoded='" << encoded << "'";
+}
+
+TEST(StringUtilTest, decodeFormattedHexString) {
+ // Colon separated.
+ testFormatted("1:A7:B5:4:23", "01A7B50423");
+ // Space separated.
+ testFormatted("1 A7 B5 4 23", "01A7B50423");
+ // No colons, even number of digits.
+ testFormatted("17a534", "17A534");
+ // Odd number of digits.
+ testFormatted("A3A6f78", "0A3A6F78");
+ // '0x' prefix.
+ testFormatted("0xA3A6f78", "0A3A6F78");
+ // '0x' prefix with a special value of 0.
+ testFormatted("0x0", "00");
+ // Empty string.
+ testFormatted("", "");
+
+ std::vector<uint8_t> decoded;
+ // Dangling colon.
+ EXPECT_THROW(decodeFormattedHexString("0a:", decoded),
+ isc::BadValue);
+ // Dangling space.
+ EXPECT_THROW(decodeFormattedHexString("0a ", decoded),
+ isc::BadValue);
+ // '0x' prefix and spaces.
+ EXPECT_THROW(decodeFormattedHexString("0x01 02", decoded),
+ isc::BadValue);
+ // '0x' prefix and colons.
+ EXPECT_THROW(decodeFormattedHexString("0x01:02", decoded),
+ isc::BadValue);
+ // colon and spaces mixed
+ EXPECT_THROW(decodeFormattedHexString("01:02 03", decoded),
+ isc::BadValue);
+ // Missing colon.
+ EXPECT_THROW(decodeFormattedHexString("01:0203", decoded),
+ isc::BadValue);
+ // Missing space.
+ EXPECT_THROW(decodeFormattedHexString("01 0203", decoded),
+ isc::BadValue);
+ // Invalid prefix.
+ EXPECT_THROW(decodeFormattedHexString("x0102", decoded),
+ isc::BadValue);
+ // Invalid prefix again.
+ EXPECT_THROW(decodeFormattedHexString("1x0102", decoded),
+ isc::BadValue);
+}
+
+/// @brief Function used to test StringSantitizer
+/// @param original - string to sanitize
+/// @param char_set - regular expression string describing invalid
+/// characters
+/// @param char_replacement - character(s) which replace invalid
+/// characters
+/// @param expected - expected sanitized string
+void sanitizeStringTest(
+ const std::string& original,
+ const std::string& char_set,
+ const std::string& char_replacement,
+ const std::string& expected) {
+
+ StringSanitizerPtr ss;
+ std::string sanitized;
+
+ try {
+ ss.reset(new StringSanitizer(char_set, char_replacement));
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Could not construct sanitizer:" << ex.what();
+ return;
+ }
+
+ try {
+ sanitized = ss->scrub(original);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "Could not scrub string:" << ex.what();
+ return;
+ }
+
+ EXPECT_EQ(sanitized, expected);
+}
+
+// Verifies StringSantizer class
+TEST(StringUtilTest, stringSanitizer) {
+ // Bad regular expression should throw.
+ StringSanitizerPtr ss;
+ ASSERT_THROW(ss.reset(new StringSanitizer("[bogus-regex","")), BadValue);
+
+ std::string good_data(StringSanitizer::MAX_DATA_SIZE, '0');
+ std::string bad_data(StringSanitizer::MAX_DATA_SIZE + 1, '0');
+
+ ASSERT_NO_THROW(ss.reset(new StringSanitizer(good_data, good_data)));
+
+ ASSERT_THROW(ss.reset(new StringSanitizer(bad_data, "")), BadValue);
+ ASSERT_THROW(ss.reset(new StringSanitizer("", bad_data)), BadValue);
+
+ // List of invalid chars should work: (b,c,2 are invalid)
+ sanitizeStringTest("abc.123", "[b-c2]", "*",
+ "a**.1*3");
+ // Inverted list of valid chars should work: (b,c,2 are valid)
+ sanitizeStringTest("abc.123", "[^b-c2]", "*",
+ "*bc**2*");
+
+ // A string of all valid chars should return an identical string.
+ sanitizeStringTest("-_A--B__Cabc34567_-", "[^A-Ca-c3-7_-]", "x",
+ "-_A--B__Cabc34567_-");
+
+ // Replacing with a character should work.
+ sanitizeStringTest("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "*",
+ "A*b*c*JoE3*_x*B*Y*e");
+
+ // Removing (i.e.replacing with an "empty" string) should work.
+ sanitizeStringTest("A[b]c\12JoE3-_x!B$Y#e", "[^A-Za-z0-9_]", "",
+ "AbcJoE3_xBYe");
+
+ // More than one non-matching in a row should work.
+ sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "x",
+ "xxAxxBxxCxx");
+
+ // Removing more than one non-matching in a row should work.
+ sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "",
+ "ABC");
+
+ // Replacing with a string should work.
+ sanitizeStringTest("%%A%%B%%C%%", "[^A-Za-z0-9_]", "xyz",
+ "xyzxyzAxyzxyzBxyzxyzCxyzxyz");
+
+ // Dots as valid chars work.
+ sanitizeStringTest("abc.123", "[^A-Za-z0-9_.]", "*",
+ "abc.123");
+
+ std::string withNulls("\000ab\000c.12\0003",10);
+ sanitizeStringTest(withNulls, "[^A-Za-z0-9_.]", "*",
+ "*ab*c.12*3");
+}
+
+// Verifies templated buffer iterator seekTrimmed() function
+TEST(StringUtilTest, seekTrimmed) {
+
+ // Empty buffer should be fine.
+ std::vector<uint8_t> buffer;
+ auto begin = buffer.end();
+ auto end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(0, std::distance(begin, end));
+
+ // Buffer of only trim values, should be fine.
+ buffer = { 1, 1 };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 1));
+ EXPECT_EQ(0, std::distance(begin, end));
+
+ // One trailing null should trim off.
+ buffer = {'o', 'n', 'e', 0 };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(3, std::distance(begin, end));
+
+ // More than one trailing null should trim off.
+ buffer = { 't', 'h', 'r', 'e', 'e', 0, 0, 0 };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(5, std::distance(begin, end));
+
+ // Embedded null should be left in place.
+ buffer = { 'e', 'm', 0, 'b', 'e', 'd' };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(6, std::distance(begin, end));
+
+ // Leading null should be left in place.
+ buffer = { 0, 'l', 'e', 'a', 'd', 'i', 'n', 'g' };
+ begin = buffer.begin();
+ end = buffer.end();
+ ASSERT_NO_THROW(end = seekTrimmed(begin, end, 0));
+ EXPECT_EQ(8, std::distance(begin, end));
+}
+
+// Verifies isPrintable predicate on strings.
+TEST(StringUtilTest, stringIsPrintable) {
+ string content;
+
+ // Empty is printable.
+ EXPECT_TRUE(isPrintable(content));
+
+ // Check Abcd.
+ content = "Abcd";
+ EXPECT_TRUE(isPrintable(content));
+
+ // Add a control character (not printable).
+ content += "\a";
+ EXPECT_FALSE(isPrintable(content));
+}
+
+// Verifies isPrintable predicate on byte vectors.
+TEST(StringUtilTest, vectorIsPrintable) {
+ vector<uint8_t> content;
+
+ // Empty is printable.
+ EXPECT_TRUE(isPrintable(content));
+
+ // Check Abcd.
+ content = { 0x41, 0x62, 0x63, 0x64 };
+ EXPECT_TRUE(isPrintable(content));
+
+ // Add a control character (not printable).
+ content.push_back('\a');
+ EXPECT_FALSE(isPrintable(content));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/thread_pool_unittest.cc b/src/lib/util/tests/thread_pool_unittest.cc
new file mode 100644
index 0000000..9c636c9
--- /dev/null
+++ b/src/lib/util/tests/thread_pool_unittest.cc
@@ -0,0 +1,661 @@
+// Copyright (C) 2018-2021 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 <exceptions/exceptions.h>
+#include <util/thread_pool.h>
+
+#include <signal.h>
+
+using namespace isc;
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief define CallBack type
+typedef function<void()> CallBack;
+
+/// @brief Test Fixture for testing isc::dhcp::ThreadPool
+class ThreadPoolTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ThreadPoolTest() : thread_count_(0), count_(0), wait_(false) {
+ }
+
+ /// @brief task function which registers the thread id and signals main
+ /// thread to stop waiting and then waits for main thread to signal to exit
+ void runAndWait() {
+ // run task
+ run();
+ // wait for main thread signal to exit
+ unique_lock<mutex> lk(wait_mutex_);
+ wait_cv_.wait(lk, [&]() {return (wait() == false);});
+ }
+
+ /// @brief task function which registers the thread id and signals main
+ /// thread to stop waiting
+ void run() {
+ {
+ // make sure this thread has started and it is accounted for
+ lock_guard<mutex> lk(mutex_);
+ auto id = this_thread::get_id();
+ // register this thread as doing work on items
+ ids_.emplace(id);
+ // finish task
+ ++count_;
+ // register this task on the history of this thread
+ history_[id].push_back(count_);
+ }
+ // wake main thread if it is waiting for this thread to process
+ cv_.notify_all();
+ }
+
+ /// @brief task function which tries to stop the thread pool and then calls
+ /// @ref runAndWait
+ void runStopResetAndWait(ThreadPool<CallBack>* thread_pool) {
+ EXPECT_THROW(thread_pool->stop(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(thread_pool->reset(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(thread_pool->wait(), MultiThreadingInvalidOperation);
+ EXPECT_THROW(thread_pool->wait(0), MultiThreadingInvalidOperation);
+ sigset_t nsset;
+ pthread_sigmask(SIG_SETMASK, 0, &nsset);
+ EXPECT_EQ(1, sigismember(&nsset, SIGCHLD));
+ EXPECT_EQ(1, sigismember(&nsset, SIGINT));
+ EXPECT_EQ(1, sigismember(&nsset, SIGHUP));
+ EXPECT_EQ(1, sigismember(&nsset, SIGTERM));
+ EXPECT_NO_THROW(runAndWait());
+ }
+
+ /// @brief reset all counters and internal test state
+ void reset(uint32_t thread_count) {
+ // stop test threads
+ stopThreads();
+ // reset the internal state of the test
+ thread_count_ = thread_count;
+ count_ = 0;
+ wait_ = true;
+ ids_.clear();
+ history_.clear();
+ }
+
+ /// @brief start test threads and block main thread until all tasks have
+ /// been processed
+ ///
+ /// @param thread_count number of threads to be started
+ /// @param items_count number of items to wait for being processed
+ /// @param start create processing threads
+ /// @param signal give main thread control over the threads exit
+ void waitTasks(uint32_t thread_count, uint32_t items_count,
+ bool start = false, bool signal = true) {
+ // make sure we have all threads running when performing all the checks
+ unique_lock<mutex> lck(mutex_);
+ if (start) {
+ // start test threads if explicitly specified
+ startThreads(thread_count, signal);
+ }
+ // wait for the threads to process all the items
+ cv_.wait(lck, [&]() {return (count() == items_count);});
+ }
+
+ /// @brief start test threads
+ ///
+ /// @param thread_count number of threads to be started
+ /// @param signal give main thread control over the threads exit
+ void startThreads(uint32_t thread_count, bool signal = true) {
+ // set default task function to wait for main thread signal
+ auto runFunction = &ThreadPoolTest::runAndWait;
+ if (!signal) {
+ // set the task function to finish immediately
+ runFunction = &ThreadPoolTest::run;
+ }
+ // start test threads
+ for (uint32_t i = 0; i < thread_count; ++i) {
+ threads_.push_back(boost::make_shared<std::thread>(runFunction, this));
+ }
+ }
+
+ /// @brief stop test threads
+ void stopThreads() {
+ // signal threads that are waiting
+ signalThreads();
+ // wait for all test threads to exit
+ for (auto thread : threads_) {
+ thread->join();
+ }
+ // reset all threads
+ threads_.clear();
+ }
+
+ /// @brief function used by main thread to unblock processing threads
+ void signalThreads() {
+ {
+ lock_guard<mutex> lk(wait_mutex_);
+ // clear the wait flag so that threads will no longer wait for the main
+ // thread signal
+ wait_ = false;
+ }
+ // wake all threads if waiting for main thread signal
+ wait_cv_.notify_all();
+ }
+
+ /// @brief number of completed tasks
+ ///
+ /// @return the number of completed tasks
+ uint32_t count() {
+ return (count_);
+ }
+
+ /// @brief flag which indicates if working thread should wait for main
+ /// thread signal
+ ///
+ /// @return the wait flag
+ bool wait() {
+ return (wait_);
+ }
+
+ /// @brief check the total number of tasks that have been processed
+ /// Some of the tasks may have been run on the same thread and none may have
+ /// been processed by other threads
+ void checkRunHistory(uint32_t items_count) {
+ uint32_t count = 0;
+ // iterate over all threads history and count all the processed tasks
+ for (auto element : history_) {
+ count += element.second.size();
+ }
+ ASSERT_EQ(count, items_count);
+ }
+
+ /// @brief check the total number of threads that have processed tasks
+ void checkIds(uint32_t count) {
+ ASSERT_EQ(ids_.size(), count);
+ }
+
+private:
+ /// @brief thread count used by the test
+ uint32_t thread_count_;
+
+ /// @brief mutex used to keep the internal state consistent
+ std::mutex mutex_;
+
+ /// @brief condition variable used to signal main thread that all threads
+ /// have started processing
+ condition_variable cv_;
+
+ /// @brief mutex used to keep the internal state consistent
+ /// related to the control of the main thread over the working threads exit
+ std::mutex wait_mutex_;
+
+ /// @brief condition variable used to signal working threads to exit
+ condition_variable wait_cv_;
+
+ /// @brief number of completed tasks
+ uint32_t count_;
+
+ /// @brief flag which indicates if working thread should wait for main
+ /// thread signal
+ bool wait_;
+
+ /// @brief the set of thread ids which have completed tasks
+ set<std::thread::id> ids_;
+
+ /// @brief the list of completed tasks run by each thread
+ map<std::thread::id, list<uint32_t>> history_;
+
+ /// @brief the list of test threads
+ list<boost::shared_ptr<std::thread>> threads_;
+};
+
+/// @brief test ThreadPool add and count
+TEST_F(ThreadPoolTest, addAndCount) {
+ uint32_t items_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 4;
+
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+
+ // calling reset should clear all threads and should remove all queued items
+ EXPECT_NO_THROW(thread_pool.reset());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief test ThreadPool start and stop
+TEST_F(ThreadPoolTest, startAndStop) {
+ uint32_t items_count;
+ uint32_t thread_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 4;
+ thread_count = 4;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which block thread pool threads until signaled by main
+ // thread to force all threads of the thread pool to run exactly one task
+ call_back = std::bind(&ThreadPoolTest::runAndWait, this);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_THROW(thread_pool.start(0), InvalidParameter);
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // do it once again to check if it works
+ EXPECT_THROW(thread_pool.start(thread_count), InvalidOperation);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // do it once again to check if it works
+ EXPECT_THROW(thread_pool.stop(), InvalidOperation);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_THROW(thread_pool.stop(), InvalidOperation);
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling reset should clear all threads and should remove all queued items
+ EXPECT_NO_THROW(thread_pool.reset());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // do it once again to check if it works
+ EXPECT_NO_THROW(thread_pool.reset());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // add items to running thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // as each thread pool thread is still waiting on main to unblock, each
+ // thread should have been registered in ids list
+ checkIds(items_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // signal thread pool tasks to continue
+ signalThreads();
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 64;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which do not block the thread pool threads so that several
+ // tasks can be run on the same thread and some of the threads never even
+ // having a chance to run
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 16;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which try to stop the thread pool and then block thread pool
+ // threads until signaled by main thread to force all threads of the thread
+ // pool to run exactly one task
+ call_back = std::bind(&ThreadPoolTest::runStopResetAndWait, this, &thread_pool);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // add items to running thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // as each thread pool thread is still waiting on main to unblock, each
+ // thread should have been registered in ids list
+ checkIds(items_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // signal thread pool tasks to continue
+ signalThreads();
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ /// statistics
+ std::cout << "stat10: " << thread_pool.getQueueStat(10) << std::endl;
+ std::cout << "stat100: " << thread_pool.getQueueStat(100) << std::endl;
+ std::cout << "stat1000: " << thread_pool.getQueueStat(1000) << std::endl;
+}
+
+/// @brief test ThreadPool wait
+TEST_F(ThreadPoolTest, wait) {
+ uint32_t items_count;
+ uint32_t thread_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 16;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which block thread pool threads until signaled by main
+ // thread to force all threads of the thread pool to run exactly one task
+ call_back = std::bind(&ThreadPoolTest::runAndWait, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // wait for all items to be processed
+ waitTasks(thread_count, items_count);
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // as each thread pool thread is still waiting on main to unblock, each
+ // thread should have been registered in ids list
+ checkIds(items_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+
+ // check that waiting on tasks does timeout
+ ASSERT_FALSE(thread_pool.wait(1));
+
+ // signal thread pool tasks to continue
+ signalThreads();
+
+ // calling stop should clear all threads and should keep queued items
+ EXPECT_NO_THROW(thread_pool.stop());
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 64;
+ thread_count = 16;
+ // prepare setup
+ reset(thread_count);
+
+ // create tasks which do not block the thread pool threads so that several
+ // tasks can be run on the same thread and some of the threads never even
+ // having a chance to run
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ for (uint32_t i = 0; i < items_count; ++i) {
+ bool ret = true;
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ // calling start should create the threads and should keep the queued items
+ EXPECT_NO_THROW(thread_pool.start(thread_count));
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+
+ // wait for all items to be processed
+ thread_pool.wait();
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should match
+ ASSERT_EQ(thread_pool.size(), thread_count);
+ // all items should have been processed
+ ASSERT_EQ(count(), items_count);
+
+ // check that the number of processed tasks matches the number of items
+ checkRunHistory(items_count);
+}
+
+/// @brief test ThreadPool max queue size
+TEST_F(ThreadPoolTest, maxQueueSize) {
+ uint32_t items_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 20;
+
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ bool ret = true;
+ for (uint32_t i = 0; i < items_count; ++i) {
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+
+ // change the max count
+ ASSERT_EQ(thread_pool.getMaxQueueSize(), 0);
+ size_t max_queue_size = 10;
+ thread_pool.setMaxQueueSize(max_queue_size);
+ EXPECT_EQ(thread_pool.getMaxQueueSize(), max_queue_size);
+
+ // adding an item should squeeze the queue
+ EXPECT_EQ(thread_pool.count(), items_count);
+ EXPECT_NO_THROW(ret = thread_pool.add(boost::make_shared<CallBack>(call_back)));
+ EXPECT_FALSE(ret);
+ EXPECT_EQ(thread_pool.count(), max_queue_size);
+}
+
+/// @brief test ThreadPool add front.
+TEST_F(ThreadPoolTest, addFront) {
+ uint32_t items_count;
+ CallBack call_back;
+ ThreadPool<CallBack> thread_pool;
+ // the item count should be 0
+ ASSERT_EQ(thread_pool.count(), 0);
+ // the thread count should be 0
+ ASSERT_EQ(thread_pool.size(), 0);
+
+ items_count = 20;
+
+ call_back = std::bind(&ThreadPoolTest::run, this);
+
+ // add items to stopped thread pool
+ bool ret = true;
+ for (uint32_t i = 0; i < items_count; ++i) {
+ EXPECT_NO_THROW(ret = thread_pool.addFront(boost::make_shared<CallBack>(call_back)));
+ EXPECT_TRUE(ret);
+ }
+
+ // the item count should match
+ ASSERT_EQ(thread_pool.count(), items_count);
+
+ // change the max count
+ ASSERT_EQ(thread_pool.getMaxQueueSize(), 0);
+ size_t max_queue_size = 10;
+ thread_pool.setMaxQueueSize(max_queue_size);
+ EXPECT_EQ(thread_pool.getMaxQueueSize(), max_queue_size);
+
+ // adding an item at front should change nothing queue
+ EXPECT_EQ(thread_pool.count(), items_count);
+ EXPECT_NO_THROW(ret = thread_pool.addFront(boost::make_shared<CallBack>(call_back)));
+ EXPECT_FALSE(ret);
+ EXPECT_EQ(thread_pool.count(), items_count);
+}
+
+/// @brief test ThreadPool get queue statistics.
+TEST_F(ThreadPoolTest, getQueueStat) {
+ ThreadPool<CallBack> thread_pool;
+ EXPECT_THROW(thread_pool.getQueueStat(0), InvalidParameter);
+ EXPECT_THROW(thread_pool.getQueueStat(1), InvalidParameter);
+ EXPECT_THROW(thread_pool.getQueueStat(-10), InvalidParameter);
+ EXPECT_THROW(thread_pool.getQueueStat(10000), InvalidParameter);
+ EXPECT_NO_THROW(thread_pool.getQueueStat(10));
+ EXPECT_NO_THROW(thread_pool.getQueueStat(100));
+ EXPECT_NO_THROW(thread_pool.getQueueStat(1000));
+}
+
+} // namespace
diff --git a/src/lib/util/tests/time_utilities_unittest.cc b/src/lib/util/tests/time_utilities_unittest.cc
new file mode 100644
index 0000000..1637a7a
--- /dev/null
+++ b/src/lib/util/tests/time_utilities_unittest.cc
@@ -0,0 +1,155 @@
+// 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 <time.h>
+
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::util;
+
+// See time_utilities.cc
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+
+namespace {
+
+class DNSSECTimeTest : public ::testing::Test {
+protected:
+ ~DNSSECTimeTest() {
+ detail::gettimeFunction = NULL;
+ }
+};
+
+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.
+const 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);
+}
+
+}
diff --git a/src/lib/util/tests/triplet_unittest.cc b/src/lib/util/tests/triplet_unittest.cc
new file mode 100644
index 0000000..9736011
--- /dev/null
+++ b/src/lib/util/tests/triplet_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright (C) 2012-2021 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/triplet.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <stdint.h>
+
+using namespace isc::util;
+using namespace isc;
+
+namespace {
+
+// constructor validation
+TEST(TripletTest, constructor) {
+
+ const uint32_t min = 10;
+ const uint32_t value = 20;
+ const uint32_t max = 30;
+
+ Triplet<uint32_t> x(min, value, max);
+
+ EXPECT_EQ(min, x.getMin());
+ EXPECT_EQ(value, x.get());
+ EXPECT_EQ(max, x.getMax());
+ EXPECT_FALSE(x.unspecified());
+
+ // requested values below min should return allowed min value
+ EXPECT_EQ(min, x.get(min - 5));
+
+ EXPECT_EQ(min, x.get(min));
+
+ // requesting a value from within the range (min < x < max) should
+ // return the requested value
+ EXPECT_EQ(17, x.get(17));
+
+ EXPECT_EQ(max, x.get(max));
+
+ EXPECT_EQ(max, x.get(max + 5));
+
+ // this will be boring. It is expected to return 42 no matter what
+ Triplet<uint32_t> y(42);
+
+ EXPECT_EQ(42, y.getMin()); // min, default and max are equal to 42
+ EXPECT_EQ(42, y.get()); // it returns ...
+ EXPECT_EQ(42, y.getMax()); // the exact value...
+ EXPECT_FALSE(x.unspecified());
+
+ // requested values below or above are ignore
+ EXPECT_EQ(42, y.get(5)); // all...
+ EXPECT_EQ(42, y.get(42)); // the...
+ EXPECT_EQ(42, y.get(80)); // time!
+}
+
+TEST(TripletTest, unspecified) {
+ Triplet<uint32_t> x;
+ // When using the constructor without parameters, the triplet
+ // value is unspecified.
+ EXPECT_EQ(0, x.getMin());
+ EXPECT_EQ(0, x.get());
+ EXPECT_EQ(0, x.getMax());
+ EXPECT_TRUE(x.unspecified());
+
+ // For the triplet which has unspecified value we can call accessors
+ // without an exception.
+ uint32_t exp_unspec = 0;
+ EXPECT_EQ(exp_unspec, x);
+
+ x = 72;
+ // Check if the new value has been assigned.
+ EXPECT_EQ(72, x.getMin());
+ EXPECT_EQ(72, x.get());
+ EXPECT_EQ(72, x.getMax());
+ // Triplet is now specified.
+ EXPECT_FALSE(x.unspecified());
+}
+
+// Triplets must be easy to use.
+// Simple to/from int conversions must be done on the fly.
+TEST(TripletTest, operator) {
+
+ uint32_t x = 47;
+
+ Triplet<uint32_t> foo(1,2,3);
+ Triplet<uint32_t> bar(4,5,6);
+
+ foo = bar;
+
+ EXPECT_EQ(4, foo.getMin());
+ EXPECT_EQ(5, foo.get());
+ EXPECT_EQ(6, foo.getMax());
+ EXPECT_FALSE(foo.unspecified());
+
+ // assignment operator: uint32_t => triplet
+ Triplet<uint32_t> y(0);
+ y = x;
+
+ EXPECT_EQ(x, y.get());
+
+ // let's try the other way around: triplet => uint32_t
+ uint32_t z = 0;
+ z = y;
+
+ EXPECT_EQ(x, z);
+}
+
+// check if specified values are sane
+TEST(TripletTest, sanityCheck) {
+
+ // min is larger than default
+ EXPECT_THROW(Triplet<uint32_t>(6,5,5), BadValue);
+
+ // max is smaller than default
+ EXPECT_THROW(Triplet<uint32_t>(5,5,4), BadValue);
+
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/util/tests/unlock_guard_unittests.cc b/src/lib/util/tests/unlock_guard_unittests.cc
new file mode 100644
index 0000000..65d868b
--- /dev/null
+++ b/src/lib/util/tests/unlock_guard_unittests.cc
@@ -0,0 +1,236 @@
+// Copyright (C) 2020-2021 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/unlock_guard.h>
+#include <exceptions/exceptions.h>
+
+#include <mutex>
+#include <thread>
+
+using namespace isc::util;
+using namespace std;
+
+namespace {
+
+/// @brief test mutex class used to check the internal state of a 'fictional'
+/// mutex so that the functionality of the UnlockGuard can be tested
+/// @note the test mutex can be recursive which means that a lock can be called
+/// on the same thread and not resulting in a dead lock
+class TestMutex {
+public:
+ /// @brief Constructor
+ ///
+ /// @param recursive sets the mutex as recursive mutex
+ TestMutex(bool recursive = false) : lock_(0), dead_lock_(false),
+ lock_count_(0), unlock_count_(0), recursive_(recursive) {
+ }
+
+ /// @brief lock the mutex
+ void lock() {
+ lock_guard<mutex> lk(mutex_);
+ if (lock_ >= 1) {
+ // mutex is already locked
+ if (!recursive_) {
+ // lock on a non-recursive mutex resulting in a dead lock
+ dead_lock_ = true;
+ isc_throw(isc::InvalidOperation,
+ "recursive lock on already locked mutex resulting in "
+ "dead lock");
+ } else {
+ // lock on a recursive mutex
+ if (this_thread::get_id() != id_) {
+ // lock on a recursive mutex on a different thread resulting
+ // in a dead lock
+ dead_lock_ = true;
+ isc_throw(isc::InvalidOperation,
+ "recursive lock on a different thread on already "
+ "locked mutex resulting in dead lock");
+ }
+ }
+ }
+ // increment the total number of locks
+ lock_count_++;
+ // increment the lock state
+ lock_++;
+ // save the thread id
+ id_ = this_thread::get_id();
+ }
+
+ /// @brief unlock the mutex
+ void unlock() {
+ lock_guard<mutex> lk(mutex_);
+ if (lock_ <= 0) {
+ // unlock an unlocked mutex
+ isc_throw(isc::InvalidOperation, "unlock on non locked mutex "
+ "resulting in undefined behavior");
+ }
+ if (lock_ == 1) {
+ // only one thread has the lock
+ // self healing mutex resetting the dead lock flag
+ dead_lock_ = false;
+ // reset the thread id
+ id_ = std::thread::id();
+ }
+ // increment the total number of unlocks
+ unlock_count_++;
+ // decrement the lock state
+ lock_--;
+ }
+
+ /// @brief get the mutex lock state
+ ///
+ /// @return the mutex lock state
+ int32_t getLock() {
+ lock_guard<mutex> lk(mutex_);
+ return lock_;
+ }
+
+ /// @brief get the mutex dead lock state
+ ///
+ /// @return the mutex dead lock state
+ bool getDeadLock() {
+ lock_guard<mutex> lk(mutex_);
+ return dead_lock_;
+ }
+
+ /// @brief get the number of locks performed on mutex
+ ///
+ /// @return the mutex number of locks
+ uint32_t getLockCount() {
+ lock_guard<mutex> lk(mutex_);
+ return lock_count_;
+ }
+
+ /// @brief get the number of unlocks performed on mutex
+ ///
+ /// @return the mutex number of unlocks
+ uint32_t getUnlockCount() {
+ lock_guard<mutex> lk(mutex_);
+ return unlock_count_;
+ }
+
+ /// @brief test the internal state of the mutex
+ ///
+ /// @param expected_lock check equality of this value with lock state
+ /// @param expected_lock_count check equality of this value with lock count
+ /// @param expected_unlock_count check equality of this value with unlock count
+ /// @param expected_dead_lock check equality of this value with dead lock state
+ void testMutexState(int32_t expected_lock,
+ uint32_t expected_lock_count,
+ uint32_t expected_unlock_count,
+ bool expected_dead_lock) {
+ ASSERT_EQ(getLock(), expected_lock);
+ ASSERT_EQ(getLockCount(), expected_lock_count);
+ ASSERT_EQ(getUnlockCount(), expected_unlock_count);
+ ASSERT_EQ(getDeadLock(), expected_dead_lock);
+ }
+
+private:
+ /// @brief internal lock state of the mutex
+ int32_t lock_;
+
+ /// @brief state which indicates that the mutex is in dead lock
+ bool dead_lock_;
+
+ /// @brief total number of locks performed on the mutex
+ uint32_t lock_count_;
+
+ /// @brief total number of unlocks performed on the mutex
+ uint32_t unlock_count_;
+
+ /// @brief flag to indicate if the mutex is recursive or not
+ bool recursive_;
+
+ /// @brief mutex used to keep the internal state consistent
+ mutex mutex_;
+
+ /// @brief the id of the thread holding the mutex
+ std::thread::id id_;
+};
+
+/// @brief Test Fixture for testing isc::util::UnlockGuard
+class UnlockGuardTest : public ::testing::Test {
+};
+
+/// @brief test TestMutex functionality with non-recursive mutex, and recursive
+/// mutex
+TEST_F(UnlockGuardTest, testMutex) {
+ shared_ptr<TestMutex> test_mutex;
+ // test non-recursive lock
+ test_mutex = make_shared<TestMutex>();
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<TestMutex> lock(*test_mutex.get());
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+ test_mutex->testMutexState(1, 1, 0, false);
+ {
+ // call lock_guard constructor which locks mutex resulting in an
+ // exception as the mutex is already locked (dead lock)
+ EXPECT_THROW(lock_guard<TestMutex> lock(*test_mutex.get()),
+ isc::InvalidOperation);
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
+ // you should not be able to get here...using a real mutex
+ test_mutex->testMutexState(1, 1, 0, true);
+ }
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock true
+ // you should not be able to get here...using a real mutex
+ test_mutex->testMutexState(1, 1, 0, true);
+ }
+ // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
+ // the implementation is self healing when completely unlocking the mutex
+ test_mutex->testMutexState(0, 1, 1, false);
+ // test recursive lock
+ test_mutex = make_shared<TestMutex>(true);
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<TestMutex> lock(*test_mutex.get());
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+ test_mutex->testMutexState(1, 1, 0, false);
+ {
+ // call lock_guard constructor which locks mutex but does not block
+ // as this is done on the same thread and the mutex is recursive
+ EXPECT_NO_THROW(lock_guard<TestMutex> lock(*test_mutex.get()));
+ // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+ // the destructor was already called in EXPECT_NO_THROW scope
+ test_mutex->testMutexState(1, 2, 1, false);
+ }
+ // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+ test_mutex->testMutexState(1, 2, 1, false);
+ }
+ // expect lock 0 lock_count 2 unlock_count 2 dead_lock false
+ test_mutex->testMutexState(0, 2, 2, false);
+}
+
+/// @brief test UnlockGuard functionality with non-recursive mutex
+TEST_F(UnlockGuardTest, testUnlockGuard) {
+ shared_ptr<TestMutex> test_mutex;
+ // test non-recursive lock
+ test_mutex = make_shared<TestMutex>();
+ test_mutex->testMutexState(0, 0, 0, false);
+ {
+ // call lock_guard constructor which locks mutex
+ lock_guard<TestMutex> lock(*test_mutex.get());
+ // expect lock 1 lock_count 1 unlock_count 0 dead_lock false
+ test_mutex->testMutexState(1, 1, 0, false);
+ {
+ UnlockGuard<TestMutex> unlock_guard(*test_mutex.get());
+ // expect lock 0 lock_count 1 unlock_count 1 dead_lock false
+ test_mutex->testMutexState(0, 1, 1, false);
+ }
+ // expect lock 1 lock_count 2 unlock_count 1 dead_lock false
+ test_mutex->testMutexState(1, 2, 1, false);
+ }
+ // expect lock 0 lock_count 2 unlock_count 2 dead_lock false
+ test_mutex->testMutexState(0, 2, 2, false);
+}
+
+} // namespace
diff --git a/src/lib/util/tests/utf8_unittest.cc b/src/lib/util/tests/utf8_unittest.cc
new file mode 100644
index 0000000..168f38b
--- /dev/null
+++ b/src/lib/util/tests/utf8_unittest.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2020 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/encode/utf8.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace std;
+
+namespace {
+
+// Verify it does nothing for ASCII.
+TEST(Utf8Test, foobar) {
+ string str("foobar");
+ vector<uint8_t> vec8 = encodeUtf8(str);
+ ASSERT_FALSE(vec8.empty());
+ const char* start = reinterpret_cast<const char*>(&vec8[0]);
+ string str8(start, start + vec8.size());
+ EXPECT_EQ(str, str8);
+}
+
+// Verify it encodes not ASCII as expected.
+TEST(Utf8Test, eightc) {
+ string str("-\x8c-");
+ vector<uint8_t> vec8 = encodeUtf8(str);
+ ASSERT_FALSE(vec8.empty());
+ const char* start = reinterpret_cast<const char*>(&vec8[0]);
+ string str8(start, start + vec8.size());
+ string expected("-\xc2\x8c-");
+ EXPECT_EQ(expected, str8);
+}
+
+// Verify it handles correctly control characters.
+TEST(Utf8Test, control) {
+ string str("fo\x00\n\bar");
+ vector<uint8_t> vec8 = encodeUtf8(str);
+ ASSERT_FALSE(vec8.empty());
+ const char* start = reinterpret_cast<const char*>(&vec8[0]);
+ string str8(start, start + vec8.size());
+ EXPECT_EQ(str, str8);
+}
+
+}
diff --git a/src/lib/util/tests/versioned_csv_file_unittest.cc b/src/lib/util/tests/versioned_csv_file_unittest.cc
new file mode 100644
index 0000000..36a1f91
--- /dev/null
+++ b/src/lib/util/tests/versioned_csv_file_unittest.cc
@@ -0,0 +1,501 @@
+// Copyright (C) 2015-2021 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/versioned_csv_file.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+
+namespace {
+
+using namespace isc::util;
+
+/// @brief Test fixture class for testing operations on VersionedCSVFile.
+///
+/// It implements basic operations on files, such as reading writing
+/// file removal and checking presence of the file. This is used by
+/// unit tests to verify correctness of the file created by the
+/// CSVFile class.
+class VersionedCSVFileTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Sets the path to the CSV file used throughout the tests.
+ /// The name of the file is test.csv and it is located in the
+ /// current build folder.
+ ///
+ /// It also deletes any dangling files after previous tests.
+ VersionedCSVFileTest();
+
+ /// @brief Destructor.
+ ///
+ /// Deletes the test CSV file if any.
+ virtual ~VersionedCSVFileTest();
+
+ /// @brief Prepends the absolute path to the file specified
+ /// as an argument.
+ ///
+ /// @param filename Name of the file.
+ /// @return Absolute path to the test file.
+ static std::string absolutePath(const std::string& filename);
+
+ /// @brief Check if test file exists on disk.
+ bool exists() const;
+
+ /// @brief Reads whole CSV file.
+ ///
+ /// @return Contents of the file.
+ std::string readFile() const;
+
+ /// @brief Removes existing file (if any).
+ int removeFile() const;
+
+ /// @brief Creates file with contents.
+ ///
+ /// @param contents Contents of the file.
+ void writeFile(const std::string& contents) const;
+
+ /// @brief Absolute path to the file used in the tests.
+ std::string testfile_;
+
+};
+
+VersionedCSVFileTest::VersionedCSVFileTest()
+ : testfile_(absolutePath("test.csv")) {
+ static_cast<void>(removeFile());
+}
+
+VersionedCSVFileTest::~VersionedCSVFileTest() {
+ static_cast<void>(removeFile());
+}
+
+std::string
+VersionedCSVFileTest::absolutePath(const std::string& filename) {
+ std::ostringstream s;
+ s << TEST_DATA_BUILDDIR << "/" << filename;
+ return (s.str());
+}
+
+bool
+VersionedCSVFileTest::exists() const {
+ std::ifstream fs(testfile_.c_str());
+ bool ok = fs.good();
+ fs.close();
+ return (ok);
+}
+
+std::string
+VersionedCSVFileTest::readFile() const {
+ std::ifstream fs(testfile_.c_str());
+ if (!fs.is_open()) {
+ return ("");
+ }
+ std::string contents((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+ return (contents);
+}
+
+int
+VersionedCSVFileTest::removeFile() const {
+ return (remove(testfile_.c_str()));
+}
+
+void
+VersionedCSVFileTest::writeFile(const std::string& contents) const {
+ std::ofstream fs(testfile_.c_str(), std::ofstream::out);
+ if (fs.is_open()) {
+ fs << contents;
+ fs.close();
+ }
+}
+
+// This test checks that the function which is used to add columns of the
+// CSV file works as expected.
+TEST_F(VersionedCSVFileTest, addColumn) {
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+
+ // Verify that we're not allowed to open it without the schema
+ ASSERT_THROW(csv->open(), VersionedCSVFileError);
+
+ // Add two columns.
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Make sure we can't add duplicates.
+ EXPECT_THROW(csv->addColumn("animal", "1.0", ""), CSVFileError);
+ EXPECT_THROW(csv->addColumn("color", "2.0", "blue"), CSVFileError);
+
+ // But we should still be able to add unique columns.
+ EXPECT_NO_THROW(csv->addColumn("age", "3.0", "21"));
+
+ // Assert that the file is opened, because the rest of the test relies
+ // on this.
+ ASSERT_NO_THROW(csv->recreate());
+ ASSERT_TRUE(exists());
+
+ // We should have 3 defined columns
+ // Input Header should match defined columns on new files
+ // Valid columns should match defined columns on new files
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ(3, csv->getInputHeaderCount());
+ EXPECT_EQ(3, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Schema versions for new files should always match
+ EXPECT_EQ("3.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("3.0", csv->getSchemaVersion());
+
+ // Input Schema State should be current for new files
+ EXPECT_EQ(VersionedCSVFile::CURRENT, csv->getInputSchemaState());
+ EXPECT_FALSE(csv->needsConversion());
+
+ // Make sure we can't add columns (even unique) when the file is open.
+ ASSERT_THROW(csv->addColumn("zoo", "3.0", ""), CSVFileError);
+
+ // Close the file.
+ ASSERT_NO_THROW(csv->close());
+ // And check that now it is possible to add the column.
+ EXPECT_NO_THROW(csv->addColumn("zoo", "3.0", ""));
+}
+
+// Verifies that a current schema version file loads correctly.
+TEST_F(VersionedCSVFileTest, currentSchemaTest) {
+
+ // Create our versioned file, with three columns
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "2.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "grey"));
+ ASSERT_NO_THROW(csv->addColumn("age", "2.0", "0"));
+
+ // Write a file compliant with the current schema version.
+ writeFile("animal,color,age\n"
+ "cat,black,2\n"
+ "lion,yellow,17\n"
+ "dog,brown,5\n");
+
+ // Header should pass validation and allow the open to succeed.
+ ASSERT_NO_THROW(csv->open());
+
+ // For schema current file We should have:
+ // 3 defined columns
+ // 3 columns total found in the header
+ // 3 valid columns found in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ(3, csv->getInputHeaderCount());
+ EXPECT_EQ(3, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Input schema and current schema should both be 2.0
+ EXPECT_EQ("2.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("2.0", csv->getSchemaVersion());
+
+ // Input Schema State should be CURRENT
+ EXPECT_EQ(VersionedCSVFile::CURRENT, csv->getInputSchemaState());
+ EXPECT_FALSE(csv->needsConversion());
+
+ // First row is correct.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("black", row.readAt(1));
+ EXPECT_EQ("2", row.readAt(2));
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("yellow", row.readAt(1));
+ EXPECT_EQ("17", row.readAt(2));
+
+ // Third row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("brown", row.readAt(1));
+ EXPECT_EQ("5", row.readAt(2));
+}
+
+
+// Verifies the basic ability to upgrade valid files.
+// It starts with a version 1.0 file and updates
+// it through two schema evolutions.
+TEST_F(VersionedCSVFileTest, upgradeOlderVersions) {
+
+ // Create version 1.0 schema CSV file
+ writeFile("animal\n"
+ "cat\n"
+ "lion\n"
+ "dog\n");
+
+ // Create our versioned file, with two columns, one for each
+ // schema version
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Header should pass validation and allow the open to succeed.
+ ASSERT_NO_THROW(csv->open());
+
+ // We should have:
+ // 2 defined columns
+ // 1 column found in the header
+ // 1 valid column in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(2, csv->getColumnCount());
+ EXPECT_EQ(1, csv->getInputHeaderCount());
+ EXPECT_EQ(1, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Input schema should be 1.0, while our current schema should be 2.0
+ EXPECT_EQ("1.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("2.0", csv->getSchemaVersion());
+
+ // Input Schema State should be NEEDS_UPGRADE
+ EXPECT_EQ(VersionedCSVFile::NEEDS_UPGRADE, csv->getInputSchemaState());
+ EXPECT_TRUE(csv->needsConversion());
+
+ // First row is correct.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+
+ // Third row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+
+ // Now, let's try to append something to this file.
+ CSVRow row_write(2);
+ row_write.writeAt(0, "bird");
+ row_write.writeAt(1, "yellow");
+ ASSERT_NO_THROW(csv->append(row_write));
+
+ // Close the file
+ ASSERT_NO_THROW(csv->flush());
+ ASSERT_NO_THROW(csv->close());
+
+
+ // Check the file contents are correct.
+ EXPECT_EQ("animal\n"
+ "cat\n"
+ "lion\n"
+ "dog\n"
+ "bird,yellow\n",
+ readFile());
+
+ // Create a third schema by adding a column
+ ASSERT_NO_THROW(csv->addColumn("age", "3.0", "21"));
+ ASSERT_EQ(3, csv->getColumnCount());
+
+ // Header should pass validation and allow the open to succeed
+ ASSERT_NO_THROW(csv->open());
+
+ // We should have:
+ // 3 defined columns
+ // 1 column found in the header
+ // 1 valid column in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(3, csv->getColumnCount());
+ EXPECT_EQ(1, csv->getInputHeaderCount());
+ EXPECT_EQ(1, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Make sure schema versions are accurate
+ EXPECT_EQ("1.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("3.0", csv->getSchemaVersion());
+
+ // Input Schema State should be NEEDS_UPGRADE
+ EXPECT_EQ(VersionedCSVFile::NEEDS_UPGRADE, csv->getInputSchemaState());
+ EXPECT_TRUE(csv->needsConversion());
+
+ // First row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ // Third row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ // Fourth row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("bird", row.readAt(0));
+ EXPECT_EQ("yellow", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+}
+
+TEST_F(VersionedCSVFileTest, minimumValidColumn) {
+ // Create version 1.0 schema CSV file
+ writeFile("animal\n"
+ "cat\n"
+ "lion\n"
+ "dog\n");
+
+ // Create our versioned file, with three columns, one for each
+ // schema version
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+ ASSERT_NO_THROW(csv->addColumn("age", "3.0", "21"));
+
+ // Verify we can't set minimum columns with a non-existent column
+ EXPECT_THROW(csv->setMinimumValidColumns("bogus"), VersionedCSVFileError);
+
+ // Set the minimum number of columns to "color"
+ csv->setMinimumValidColumns("color");
+ EXPECT_EQ(2, csv->getMinimumValidColumns());
+
+ // Header validation should fail, too few columns
+ ASSERT_THROW(csv->open(), CSVFileError);
+
+ // Set the minimum number of columns to 1. File should parse now.
+ csv->setMinimumValidColumns("animal");
+ EXPECT_EQ(1, csv->getMinimumValidColumns());
+ ASSERT_NO_THROW(csv->open());
+
+ // First row is correct.
+ CSVRow row;
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("dog", row.readAt(0));
+ EXPECT_EQ("blue", row.readAt(1));
+ EXPECT_EQ("21", row.readAt(2));
+}
+
+TEST_F(VersionedCSVFileTest, invalidHeaderColumn) {
+
+ // Create our version 2.0 schema file
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Create a file with the correct number of columns but a wrong column name
+ writeFile("animal,colour\n"
+ "cat,red\n"
+ "lion,green\n");
+
+ // Header validation should fail, we have an invalid column
+ ASSERT_THROW(csv->open(), CSVFileError);
+}
+
+TEST_F(VersionedCSVFileTest, downGrading) {
+ // Create our version 2.0 schema file
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ ASSERT_NO_THROW(csv->addColumn("animal", "1.0", ""));
+ ASSERT_NO_THROW(csv->addColumn("color", "2.0", "blue"));
+
+ // Create schema 2.0 file PLUS an extra column
+ writeFile("animal,color,age\n"
+ "cat,red,5\n"
+ "lion,green,8\n");
+
+ // Header should validate and file should open.
+ ASSERT_NO_THROW(csv->open());
+
+ // We should have:
+ // 2 defined columns
+ // 3 columns found in the header
+ // 2 valid columns in the header
+ // Minimum valid columns wasn't set. (Remember it's optional)
+ EXPECT_EQ(2, csv->getColumnCount());
+ EXPECT_EQ(3, csv->getInputHeaderCount());
+ EXPECT_EQ(2, csv->getValidColumnCount());
+ EXPECT_EQ(0, csv->getMinimumValidColumns());
+
+ // Input schema and current schema should both be 2.0
+ EXPECT_EQ("2.0", csv->getInputSchemaVersion());
+ EXPECT_EQ("2.0", csv->getSchemaVersion());
+
+ // Input Schema State should be NEEDS_DOWNGRADE
+ EXPECT_EQ(VersionedCSVFile::NEEDS_DOWNGRADE, csv->getInputSchemaState());
+ EXPECT_TRUE(csv->needsConversion());
+
+ // First row is correct.
+ CSVRow row;
+ EXPECT_TRUE(csv->next(row));
+ EXPECT_EQ("cat", row.readAt(0));
+ EXPECT_EQ("red", row.readAt(1));
+
+ // No data beyond the second column
+ EXPECT_THROW(row.readAt(2), CSVFileError);
+
+ // Second row is correct.
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("green", row.readAt(1));
+
+ // No data beyond the second column
+ EXPECT_THROW(row.readAt(2), CSVFileError);
+}
+
+
+TEST_F(VersionedCSVFileTest, rowChecking) {
+ // Create version 2.0 schema CSV file with a
+ // - valid header
+ // - row 0 has too many values
+ // - row 1 is valid
+ // - row 3 is too few values
+ writeFile("animal,color\n"
+ "cat,red,bogus_row_value\n"
+ "lion,green\n"
+ "too_few\n");
+
+ // Create our versioned file, with two columns, one for each
+ // schema version
+ boost::scoped_ptr<VersionedCSVFile> csv(new VersionedCSVFile(testfile_));
+ csv->addColumn("animal", "1.0", "");
+ csv->addColumn("color", "2.0", "blue");
+
+ // Header validation should pass, so we can open
+ ASSERT_NO_THROW(csv->open());
+
+ CSVRow row;
+ // First row has too many
+ EXPECT_FALSE(csv->next(row));
+
+ // Second row is valid
+ ASSERT_TRUE(csv->next(row));
+ EXPECT_EQ("lion", row.readAt(0));
+ EXPECT_EQ("green", row.readAt(1));
+
+ // Third row has too few
+ EXPECT_FALSE(csv->next(row));
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/watch_socket_unittests.cc b/src/lib/util/tests/watch_socket_unittests.cc
new file mode 100644
index 0000000..b503844
--- /dev/null
+++ b/src/lib/util/tests/watch_socket_unittests.cc
@@ -0,0 +1,263 @@
+// Copyright (C) 2014-2018 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/watch_socket.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <sys/ioctl.h>
+
+#ifdef HAVE_SYS_FILIO_H
+// FIONREAD is here on Solaris
+#include <sys/filio.h>
+#endif
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Returns the result of select() given an fd to check for read status.
+///
+/// @param fd_to_check The file descriptor to test
+///
+/// @return Returns less than one on an error, 0 if the fd is not ready to
+/// read, > 0 if it is ready to read.
+int selectCheck(int fd_to_check) {
+ fd_set read_fds;
+ int maxfd = 0;
+
+ FD_ZERO(&read_fds);
+
+ // Add this socket to listening set
+ FD_SET(fd_to_check, &read_fds);
+ maxfd = fd_to_check;
+
+ struct timeval select_timeout;
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 0;
+
+ return (select(maxfd + 1, &read_fds, NULL, NULL, &select_timeout));
+}
+
+/// @brief Tests the basic functionality of WatchSocket.
+TEST(WatchSocketTest, basics) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ EXPECT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that isReady() is false and that a call to select agrees.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_EQ(0, selectCheck(select_fd));
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+
+ /// Verify that we have exactly one marker waiting to be read.
+ int count = 0;
+ EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+ EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+ /// Verify that we can call markReady again without error.
+ ASSERT_NO_THROW(watch->markReady());
+
+ /// Verify that we STILL have exactly one marker waiting to be read.
+ EXPECT_FALSE(ioctl(select_fd, FIONREAD, &count));
+ EXPECT_EQ(sizeof(WatchSocket::MARKER), count);
+
+ /// Verify that isReady() is true and that a call to select agrees.
+ EXPECT_TRUE(watch->isReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+
+ /// Verify that the socket can be cleared.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ /// Verify that isReady() is false and that a call to select agrees.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_EQ(0, selectCheck(select_fd));
+}
+
+/// @brief Checks behavior when select_fd is closed externally while in the
+/// "cleared" state.
+TEST(WatchSocketTest, closedWhileClear) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ // Verify that socket does not appear ready.
+ ASSERT_EQ(0, watch->isReady());
+
+ // Interfere by closing the fd.
+ ASSERT_EQ(0, close(select_fd));
+
+ // Verify that socket does not appear ready.
+ ASSERT_EQ(0, watch->isReady());
+
+ // Verify that clear does NOT throw.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify that trying to mark it fails.
+ ASSERT_THROW(watch->markReady(), WatchSocketError);
+
+ // Verify that clear does NOT throw.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify that getSelectFd() returns invalid socket.
+ ASSERT_EQ(WatchSocket::SOCKET_NOT_VALID, watch->getSelectFd());
+}
+
+/// @brief Checks behavior when select_fd has closed while in the "ready"
+/// state.
+TEST(WatchSocketTest, closedWhileReady) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+ EXPECT_TRUE(watch->isReady());
+
+ // Interfere by closing the fd.
+ ASSERT_EQ(0, close(select_fd));
+
+ // Verify that isReady() does not throw.
+ ASSERT_NO_THROW(watch->isReady());
+
+ // and return false.
+ EXPECT_FALSE(watch->isReady());
+
+ // Verify that trying to clear it does not throw.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify the select_fd fails as socket is invalid/closed.
+ EXPECT_EQ(-1, selectCheck(select_fd));
+
+ // Verify that subsequent attempts to mark it will fail.
+ ASSERT_THROW(watch->markReady(), WatchSocketError);
+}
+
+/// @brief Checks behavior when select_fd has been marked ready but then
+/// emptied by an external read.
+TEST(WatchSocketTest, emptyReadySelectFd) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+ EXPECT_TRUE(watch->isReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+
+ // Interfere by reading the fd. This should empty the read pipe.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, sizeof(buf))), sizeof(buf));
+ ASSERT_EQ(WatchSocket::MARKER, buf);
+
+ // Really nothing that can be done to protect against this, but let's
+ // make sure we aren't in a weird state.
+ ASSERT_NO_THROW(watch->clearReady());
+
+ // Verify the select_fd does not fail.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_EQ(0, selectCheck(select_fd));
+
+ // Verify that getSelectFd() returns is still good.
+ ASSERT_EQ(select_fd, watch->getSelectFd());
+}
+
+/// @brief Checks behavior when select_fd has been marked ready but then
+/// contents have been "corrupted" by a partial read.
+TEST(WatchSocketTest, badReadOnClear) {
+ WatchSocketPtr watch;
+
+ /// Verify that we can construct a WatchSocket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ /// Verify that post-construction the state the select-fd is valid.
+ int select_fd = watch->getSelectFd();
+ ASSERT_NE(select_fd, WatchSocket::SOCKET_NOT_VALID);
+
+ /// Verify that the socket can be marked ready.
+ ASSERT_NO_THROW(watch->markReady());
+ EXPECT_TRUE(watch->isReady());
+ EXPECT_EQ(1, selectCheck(select_fd));
+
+ // Interfere by reading the fd. This should empty the read pipe.
+ uint32_t buf = 0;
+ ASSERT_EQ((read (select_fd, &buf, 1)), 1);
+ ASSERT_NE(WatchSocket::MARKER, buf);
+
+ // Really nothing that can be done to protect against this, but let's
+ // make sure we aren't in a weird state.
+ /// @todo maybe clear should never throw, log only
+ ASSERT_THROW(watch->clearReady(), WatchSocketError);
+
+ // Verify the select_fd does not evaluate to ready.
+ EXPECT_FALSE(watch->isReady());
+ EXPECT_NE(1, selectCheck(select_fd));
+
+ // Verify that getSelectFd() returns INVALID.
+ ASSERT_EQ(WatchSocket::SOCKET_NOT_VALID, watch->getSelectFd());
+
+ // Verify that subsequent attempt to mark it fails.
+ ASSERT_THROW(watch->markReady(), WatchSocketError);
+}
+
+/// @brief Checks if the socket can be explicitly closed.
+TEST(WatchSocketTest, explicitClose) {
+ WatchSocketPtr watch;
+
+ // Create new instance of the socket.
+ ASSERT_NO_THROW(watch.reset(new WatchSocket()));
+ ASSERT_TRUE(watch);
+
+ // Make sure it has been opened by checking that its descriptor
+ // is valid.
+ EXPECT_NE(watch->getSelectFd(), WatchSocket::SOCKET_NOT_VALID);
+
+ // Close the socket.
+ std::string error_string;
+ ASSERT_TRUE(watch->closeSocket(error_string));
+
+ // Make sure that the descriptor is now invalid which indicates
+ // that the socket has been closed.
+ EXPECT_EQ(WatchSocket::SOCKET_NOT_VALID, watch->getSelectFd());
+ // No errors should be reported.
+ EXPECT_TRUE(error_string.empty());
+ // Not ready too.
+ ASSERT_NO_THROW(watch->isReady());
+ EXPECT_FALSE(watch->isReady());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/util/tests/watched_thread_unittest.cc b/src/lib/util/tests/watched_thread_unittest.cc
new file mode 100644
index 0000000..dd01550
--- /dev/null
+++ b/src/lib/util/tests/watched_thread_unittest.cc
@@ -0,0 +1,218 @@
+// Copyright (C) 2018-2021 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/watched_thread.h>
+
+#include <gtest/gtest.h>
+
+#include <atomic>
+#include <functional>
+#include <signal.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test Fixture for testing @c isc::util::WatchedThread
+class WatchedThreadTest : public ::testing::Test {
+public:
+ /// @brief Maximum number of passes allowed in worker event loop
+ static const int WORKER_MAX_PASSES;
+
+ /// @brief Constructor.
+ WatchedThreadTest() {}
+
+ /// @brief Destructor.
+ ~WatchedThreadTest() {
+ }
+
+ /// @brief Sleeps for a given number of event periods sleep
+ /// Each period is 50 ms.
+ void nap(int periods) {
+ usleep(periods * 50 * 1000);
+ };
+
+ /// @brief Worker function to be used by the WatchedThread's thread
+ ///
+ /// The function runs 10 passes through an "event" loop.
+ /// On each pass:
+ /// - check terminate command
+ /// - instigate the desired event (second pass only)
+ /// - naps for 1 period (50ms)
+ ///
+ /// @param watch_type type of event that should occur
+ void worker(WatchedThread::WatchType watch_type) {
+ sigset_t nsset;
+ pthread_sigmask(SIG_SETMASK, 0, &nsset);
+ EXPECT_EQ(1, sigismember(&nsset, SIGCHLD));
+ EXPECT_EQ(1, sigismember(&nsset, SIGINT));
+ EXPECT_EQ(1, sigismember(&nsset, SIGHUP));
+ EXPECT_EQ(1, sigismember(&nsset, SIGTERM));
+ for (passes_ = 1; passes_ < WORKER_MAX_PASSES; ++passes_) {
+
+ // Stop if we're told to do it.
+ if (wthread_->shouldTerminate()) {
+ return;
+ }
+
+ // On the second pass, set the event.
+ if (passes_ == 2) {
+ switch (watch_type) {
+ case WatchedThread::ERROR:
+ wthread_->setError("we have an error");
+ break;
+ case WatchedThread::READY:
+ wthread_->markReady(watch_type);
+ break;
+ case WatchedThread::TERMINATE:
+ default:
+ // Do nothing, we're waiting to be told to stop.
+ break;
+ }
+ }
+
+ // Take a nap.
+ nap(1);
+ }
+
+ // Indicate why we stopped.
+ wthread_->setError("thread expired");
+ }
+
+ /// @brief Current WatchedThread instance.
+ WatchedThreadPtr wthread_;
+
+ /// @brief Counter used to track the number of passes made
+ /// within the thread worker function.
+ std::atomic<int> passes_;
+};
+
+const int WatchedThreadTest::WORKER_MAX_PASSES = 10;
+
+/// Verifies the basic operation of the WatchedThread class.
+/// It checks that a WatchedThread can be created, can be stopped,
+/// and that in set and clear sockets.
+TEST_F(WatchedThreadTest, watchedThreadClassBasics) {
+
+ /// We'll create a WatchedThread and let it run until it expires. (Note this is more
+ /// of a test of WatchedThreadTest itself and ensures that the assumptions made in
+ /// our other tests as to why threads have finished are sound.
+ wthread_.reset(new WatchedThread());
+ ASSERT_FALSE(wthread_->isRunning());
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // Wait more long enough (we hope) for the thread to expire.
+ nap(WORKER_MAX_PASSES * 4);
+
+ // It should have done the maximum number of passes.
+ EXPECT_EQ(passes_, WORKER_MAX_PASSES);
+
+ // Error should be ready and error text should be "thread expired".
+ ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread expired", wthread_->getLastError());
+
+ // Thread is technically still running, so let's stop it.
+ EXPECT_TRUE(wthread_->isRunning());
+ ASSERT_NO_THROW(wthread_->stop());
+ ASSERT_FALSE(wthread_->isRunning());
+
+ /// Now we'll test stopping a thread.
+ /// Start the WatchedThread, let it run a little and then tell it to stop.
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::TERMINATE));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // No watches should be ready.
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+
+ // Wait a little while.
+ nap(3);
+
+ // Tell it to stop.
+ wthread_->stop();
+ ASSERT_FALSE(wthread_->isRunning());
+
+ // It should have done less than the maximum number of passes.
+ EXPECT_LT(passes_, WORKER_MAX_PASSES);
+
+ // No watches should be ready. Error text should be "thread stopped".
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread stopped", wthread_->getLastError());
+
+
+ // Next we'll test error notification.
+ // Start the WatchedThread with a thread that sets an error on the second pass.
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::ERROR));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // No watches should be ready.
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+
+ // Wait a little while.
+ nap(6);
+
+ // It should now indicate an error.
+ ASSERT_TRUE(wthread_->isReady(WatchedThread::ERROR));
+ EXPECT_EQ("we have an error", wthread_->getLastError());
+
+ // Tell it to stop.
+ wthread_->stop();
+ ASSERT_FALSE(wthread_->isRunning());
+
+ // It should have done less than the maximum number of passes.
+ EXPECT_LT(passes_, WORKER_MAX_PASSES);
+
+ // No watches should be ready. Error text should be "thread stopped".
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread stopped", wthread_->getLastError());
+
+
+ // Finally, we'll test data ready notification.
+ // We'll start the WatchedThread with a thread that indicates data ready on its second pass.
+ wthread_->start(std::bind(&WatchedThreadTest::worker, this, WatchedThread::READY));
+ ASSERT_TRUE(wthread_->isRunning());
+
+ // No watches should be ready.
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+
+ // Wait a little while.
+ nap(6);
+
+ // It should now indicate data ready.
+ ASSERT_TRUE(wthread_->isReady(WatchedThread::READY));
+
+ // Tell it to stop.
+ wthread_->stop();
+ ASSERT_FALSE(wthread_->isRunning());
+
+ // It should have done less than the maximum number of passes.
+ EXPECT_LT(passes_, WORKER_MAX_PASSES);
+
+ // No watches should be ready. Error text should be "thread stopped".
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::ERROR));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::READY));
+ ASSERT_FALSE(wthread_->isReady(WatchedThread::TERMINATE));
+ EXPECT_EQ("thread stopped", wthread_->getLastError());
+}
+
+}
diff --git a/src/lib/util/thread_pool.h b/src/lib/util/thread_pool.h
new file mode 100644
index 0000000..fdfce0f
--- /dev/null
+++ b/src/lib/util/thread_pool.h
@@ -0,0 +1,527 @@
+// Copyright (C) 2018-2021 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 THREAD_POOL_H
+#define THREAD_POOL_H
+
+#include <exceptions/exceptions.h>
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <atomic>
+#include <chrono>
+#include <cmath>
+#include <condition_variable>
+#include <list>
+#include <mutex>
+#include <queue>
+#include <thread>
+
+#include <signal.h>
+
+namespace isc {
+namespace util {
+
+/// @brief Defines a thread pool which uses a thread pool queue for managing
+/// work items. Each work item is a 'functor' object.
+///
+/// @tparam WorkItem a functor
+/// @tparam Container a 'queue like' container
+template <typename WorkItem, typename Container = std::deque<boost::shared_ptr<WorkItem>>>
+struct ThreadPool {
+ /// @brief Rounding value for 10 packet statistic.
+ static const double CEXP10;
+
+ /// @brief Rounding value for 100 packet statistic.
+ static const double CEXP100;
+
+ /// @brief Rounding value for 1000 packet statistic.
+ static const double CEXP1000;
+
+ /// @brief Type of shared pointers to work items.
+ typedef typename boost::shared_ptr<WorkItem> WorkItemPtr;
+
+ /// @brief Constructor
+ ThreadPool() {
+ }
+
+ /// @brief Destructor
+ ~ThreadPool() {
+ reset();
+ }
+
+ /// @brief reset the thread pool stopping threads and clearing the internal
+ /// queue
+ ///
+ /// It can be called several times even when the thread pool is stopped
+ void reset() {
+ stopInternal();
+ queue_.clear();
+ }
+
+ /// @brief start all the threads
+ ///
+ /// @param thread_count specifies the number of threads to be created and
+ /// started
+ ///
+ /// @throw InvalidOperation if thread pool already started
+ /// @throw InvalidParameter if thread count is 0
+ void start(uint32_t thread_count) {
+ if (!thread_count) {
+ isc_throw(InvalidParameter, "thread count is 0");
+ }
+ if (queue_.enabled()) {
+ isc_throw(InvalidOperation, "thread pool already started");
+ }
+ startInternal(thread_count);
+ }
+
+ /// @brief stop all the threads
+ ///
+ /// @throw InvalidOperation if thread pool already stopped
+ void stop() {
+ if (!queue_.enabled()) {
+ isc_throw(InvalidOperation, "thread pool already stopped");
+ }
+ stopInternal();
+ }
+
+ /// @brief add a work item to the thread pool
+ ///
+ /// @param item the 'functor' object to be added to the queue
+ /// @return false if the queue was full and oldest item(s) was dropped,
+ /// true otherwise.
+ bool add(const WorkItemPtr& item) {
+ return (queue_.pushBack(item));
+ }
+
+ /// @brief add a work item to the thread pool at front
+ ///
+ /// @param item the 'functor' object to be added to the queue
+ /// @return false if the queue was full, true otherwise.
+ bool addFront(const WorkItemPtr& item) {
+ return (queue_.pushFront(item));
+ }
+
+ /// @brief count number of work items in the queue
+ ///
+ /// @return the number of work items in the queue
+ size_t count() {
+ return (queue_.count());
+ }
+
+ /// @brief wait for current items to be processed
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed
+ void wait() {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "thread pool wait called by worker thread");
+ }
+ queue_.wait();
+ }
+
+ /// @brief wait for items to be processed or return after timeout
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed or return after timeout
+ ///
+ /// @param seconds the time in seconds to wait for tasks to finish
+ /// @return true if all tasks finished, false on timeout
+ bool wait(uint32_t seconds) {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "thread pool wait with timeout called by worker thread");
+ }
+ return (queue_.wait(seconds));
+ }
+
+ /// @brief set maximum number of work items in the queue
+ ///
+ /// @param max_queue_size the maximum size (0 means unlimited)
+ void setMaxQueueSize(size_t max_queue_size) {
+ queue_.setMaxQueueSize(max_queue_size);
+ }
+
+ /// @brief get maximum number of work items in the queue
+ ///
+ /// @return the maximum size (0 means unlimited)
+ size_t getMaxQueueSize() {
+ return (queue_.getMaxQueueSize());
+ }
+
+ /// @brief size number of thread pool threads
+ ///
+ /// @return the number of threads
+ size_t size() {
+ return (threads_.size());
+ }
+
+ /// @brief get queue length statistic
+ ///
+ /// @param which select the statistic (10, 100 or 1000)
+ /// @return the queue length statistic
+ /// @throw InvalidParameter if which is not 10 and 100 and 1000.
+ double getQueueStat(size_t which) {
+ return (queue_.getQueueStat(which));
+ }
+
+private:
+ /// @brief start all the threads
+ ///
+ /// @param thread_count specifies the number of threads to be created and
+ /// started
+ void startInternal(uint32_t thread_count) {
+ // Protect us against signals
+ sigset_t sset;
+ sigset_t osset;
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGCHLD);
+ sigaddset(&sset, SIGINT);
+ sigaddset(&sset, SIGHUP);
+ sigaddset(&sset, SIGTERM);
+ pthread_sigmask(SIG_BLOCK, &sset, &osset);
+ queue_.enable(thread_count);
+ try {
+ for (uint32_t i = 0; i < thread_count; ++i) {
+ threads_.push_back(boost::make_shared<std::thread>(&ThreadPool::run, this));
+ }
+ } catch (...) {
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+ throw;
+ }
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+ }
+
+ /// @brief stop all the threads
+ void stopInternal() {
+ auto id = std::this_thread::get_id();
+ if (checkThreadId(id)) {
+ isc_throw(MultiThreadingInvalidOperation, "thread pool stop called by worker thread");
+ }
+ queue_.disable();
+ for (auto thread : threads_) {
+ thread->join();
+ }
+ threads_.clear();
+ }
+
+ /// @brief check specified thread id against own threads
+ ///
+ /// @return true if thread is owned, false otherwise
+ bool checkThreadId(std::thread::id id) {
+ for (auto thread : threads_) {
+ if (id == thread->get_id()) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ /// @brief Defines a generic thread pool queue.
+ ///
+ /// The main purpose is to safely manage thread pool tasks.
+ /// The thread pool queue can be 'disabled', which means that no items can be
+ /// removed from the queue, or 'enabled', which guarantees that inserting or
+ /// removing items are thread safe.
+ /// In 'disabled' state, all threads waiting on the queue are unlocked and all
+ /// operations are non blocking.
+ ///
+ /// @tparam Item a 'smart pointer' to a functor
+ /// @tparam QueueContainer a 'queue like' container
+ template <typename Item, typename QueueContainer = std::queue<Item>>
+ struct ThreadPoolQueue {
+ /// @brief Constructor
+ ///
+ /// Creates the thread pool queue in 'disabled' state
+ ThreadPoolQueue()
+ : enabled_(false), max_queue_size_(0), working_(0),
+ stat10(0.), stat100(0.), stat1000(0.) {
+ }
+
+ /// @brief Destructor
+ ///
+ /// Destroys the thread pool queue
+ ~ThreadPoolQueue() {
+ disable();
+ clear();
+ }
+
+ /// @brief set maximum number of work items in the queue
+ ///
+ /// @return the maximum size (0 means unlimited)
+ void setMaxQueueSize(size_t max_queue_size) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ max_queue_size_ = max_queue_size;
+ }
+
+ /// @brief get maximum number of work items in the queue
+ ///
+ /// @return the maximum size (0 means unlimited)
+ size_t getMaxQueueSize() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (max_queue_size_);
+ }
+
+ /// @brief push work item to the queue
+ ///
+ /// Used to add work items to the queue.
+ /// When the queue is full oldest items are removed and false is
+ /// returned.
+ /// This function adds an item to the queue and wakes up at least one
+ /// thread waiting on the queue.
+ ///
+ /// @param item the new item to be added to the queue
+ /// @return false if the queue was full and oldest item(s) dropped,
+ /// true otherwise
+ bool pushBack(const Item& item) {
+ bool ret = true;
+ if (!item) {
+ return (ret);
+ }
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (max_queue_size_ != 0) {
+ while (queue_.size() >= max_queue_size_) {
+ queue_.pop_front();
+ ret = false;
+ }
+ }
+ queue_.push_back(item);
+ }
+ // Notify pop function so that it can effectively remove a work item.
+ cv_.notify_one();
+ return (ret);
+ }
+
+ /// @brief push work item to the queue at front.
+ ///
+ /// Used to add work items to the queue at front.
+ /// When the queue is full the item is not added.
+ ///
+ /// @param item the new item to be added to the queue
+ /// @return false if the queue was full, true otherwise
+ bool pushFront(const Item& item) {
+ if (!item) {
+ return (true);
+ }
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if ((max_queue_size_ != 0) &&
+ (queue_.size() >= max_queue_size_)) {
+ return (false);
+ }
+ queue_.push_front(item);
+ }
+ // Notify pop function so that it can effectively remove a work item.
+ cv_.notify_one();
+ return (true);
+ }
+
+ /// @brief pop work item from the queue or block waiting
+ ///
+ /// Used to retrieve and remove a work item from the queue
+ /// If the queue is 'disabled', this function returns immediately an empty
+ /// element.
+ /// If the queue is 'enabled', this function returns the first element in
+ /// the queue or blocks the calling thread if there are no work items
+ /// available.
+ /// Before a work item is returned statistics are updated.
+ ///
+ /// @return the first work item from the queue or an empty element.
+ Item pop() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ --working_;
+ // Wait for push or disable functions.
+ if (working_ == 0 && queue_.empty()) {
+ wait_cv_.notify_all();
+ }
+ cv_.wait(lock, [&]() {return (!enabled_ || !queue_.empty());});
+ if (!enabled_) {
+ return (Item());
+ }
+ ++working_;
+ size_t length = queue_.size();
+ stat10 = stat10 * CEXP10 + (1 - CEXP10) * length;
+ stat100 = stat100 * CEXP100 + (1 - CEXP100) * length;
+ stat1000 = stat1000 * CEXP1000 + (1 - CEXP1000) * length;
+ Item item = queue_.front();
+ queue_.pop_front();
+ return (item);
+ }
+
+ /// @brief count number of work items in the queue
+ ///
+ /// Returns the number of work items in the queue
+ ///
+ /// @return the number of work items
+ size_t count() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (queue_.size());
+ }
+
+ /// @brief wait for current items to be processed
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed
+ void wait() {
+ std::unique_lock<std::mutex> lock(mutex_);
+ // Wait for any item or for working threads to finish.
+ wait_cv_.wait(lock, [&]() {return (working_ == 0 && queue_.empty());});
+ }
+
+ /// @brief wait for items to be processed or return after timeout
+ ///
+ /// Used to block the calling thread until all items in the queue have
+ /// been processed or return after timeout
+ ///
+ /// @param seconds the time in seconds to wait for tasks to finish
+ /// @return true if all tasks finished, false on timeout
+ bool wait(uint32_t seconds) {
+ std::unique_lock<std::mutex> lock(mutex_);
+ // Wait for any item or for working threads to finish.
+ bool ret = wait_cv_.wait_for(lock, std::chrono::seconds(seconds),
+ [&]() {return (working_ == 0 && queue_.empty());});
+ return (ret);
+ }
+
+ /// @brief get queue length statistic
+ ///
+ /// @param which select the statistic (10, 100 or 1000)
+ /// @return the queue length statistic
+ /// @throw InvalidParameter if which is not 10 and 100 and 1000.
+ double getQueueStat(size_t which) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ switch (which) {
+ case 10:
+ return (stat10);
+ case 100:
+ return (stat100);
+ case 1000:
+ return (stat1000);
+ default:
+ isc_throw(InvalidParameter, "supported statistic for "
+ << "10/100/1000 only, not " << which);
+ }
+ }
+
+ /// @brief clear remove all work items
+ ///
+ /// Removes all queued work items
+ void clear() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ queue_ = QueueContainer();
+ working_ = 0;
+ wait_cv_.notify_all();
+ }
+
+ /// @brief enable the queue
+ ///
+ /// Sets the queue state to 'enabled'
+ ///
+ /// @param number of working threads
+ void enable(uint32_t thread_count) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ enabled_ = true;
+ working_ = thread_count;
+ }
+
+ /// @brief disable the queue
+ ///
+ /// Sets the queue state to 'disabled'
+ void disable() {
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ enabled_ = false;
+ }
+ // Notify pop so that it can exit.
+ cv_.notify_all();
+ }
+
+ /// @brief return the state of the queue
+ ///
+ /// Returns the state of the queue
+ ///
+ /// @return the state
+ bool enabled() {
+ return (enabled_);
+ }
+
+ private:
+ /// @brief underlying queue container
+ QueueContainer queue_;
+
+ /// @brief mutex used for critical sections
+ std::mutex mutex_;
+
+ /// @brief condition variable used to signal waiting threads
+ std::condition_variable cv_;
+
+ /// @brief condition variable used to wait for all items to be processed
+ std::condition_variable wait_cv_;
+
+ /// @brief the sate of the queue
+ /// The 'enabled' state corresponds to true value
+ /// The 'disabled' state corresponds to false value
+ std::atomic<bool> enabled_;
+
+ /// @brief maximum number of work items in the queue
+ /// (0 means unlimited)
+ size_t max_queue_size_;
+
+ /// @brief number of threads currently doing work
+ uint32_t working_;
+
+ /// @brief queue length statistic for 10 packets
+ double stat10;
+
+ /// @brief queue length statistic for 100 packets
+ double stat100;
+
+ /// @brief queue length statistic for 1000 packets
+ double stat1000;
+ };
+
+ /// @brief run function of each thread
+ void run() {
+ while (queue_.enabled()) {
+ WorkItemPtr item = queue_.pop();
+ if (item) {
+ try {
+ (*item)();
+ } catch (...) {
+ // catch all exceptions
+ }
+ }
+ }
+ }
+
+ /// @brief list of worker threads
+ std::vector<boost::shared_ptr<std::thread>> threads_;
+
+ /// @brief underlying work items queue
+ ThreadPoolQueue<WorkItemPtr, Container> queue_;
+};
+
+/// Initialize the 10 packet rounding to exp(-.1)
+template <typename W, typename C>
+const double ThreadPool<W, C>::CEXP10 = std::exp(-.1);
+
+/// Initialize the 100 packet rounding to exp(-.01)
+template <typename W, typename C>
+const double ThreadPool<W, C>::CEXP100 = std::exp(-.01);
+
+/// Initialize the 1000 packet rounding to exp(-.001)
+template <typename W, typename C>
+const double ThreadPool<W, C>::CEXP1000 = std::exp(-.001);
+
+} // namespace util
+} // namespace isc
+
+#endif // THREAD_POOL_H
diff --git a/src/lib/util/time_utilities.cc b/src/lib/util/time_utilities.cc
new file mode 100644
index 0000000..5da1db7
--- /dev/null
+++ b/src/lib/util/time_utilities.cc
@@ -0,0 +1,203 @@
+// 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 <stdint.h>
+
+#include <sys/time.h>
+
+#include <string>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+#include <stdio.h>
+#include <time.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/time_utilities.h>
+
+using namespace std;
+
+namespace {
+int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+inline 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);
+}
+}
+
+namespace isc {
+namespace util {
+
+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());
+}
+
+// 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.
+namespace detail {
+int64_t (*gettimeFunction)() = NULL;
+
+int64_t
+gettimeWrapper() {
+ if (gettimeFunction != NULL) {
+ return (gettimeFunction());
+ }
+
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ return (static_cast<int64_t>(now.tv_sec));
+}
+}
+
+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));
+}
+
+namespace {
+const size_t DATE_LEN = 14; // YYYYMMDDHHmmSS
+
+inline uint64_t ull(const int c) { return (static_cast<uint64_t>(c)); }
+
+inline 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);
+}
+}
+
+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));
+}
+
+}
+}
diff --git a/src/lib/util/time_utilities.h b/src/lib/util/time_utilities.h
new file mode 100644
index 0000000..226a632
--- /dev/null
+++ b/src/lib/util/time_utilities.h
@@ -0,0 +1,165 @@
+// 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/.
+
+#ifndef TIME_UTILITIES_H
+#define TIME_UTILITIES_H 1
+
+#include <string>
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include <exceptions/exceptions.h>
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+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 {
+/// 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.
+int64_t gettimeWrapper();
+}
+
+///
+/// \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.
+//@{
+/// 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.
+///
+/// \exception InvalidTime The given textual representation is invalid.
+///
+/// \param time_txt Textual time in the form of YYYYMMDDHHmmSS
+/// \return Seconds since epoch corresponding to \c time_txt
+uint64_t
+timeFromText64(const std::string& time_txt);
+
+/// 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.
+uint32_t
+timeFromText32(const std::string& time_txt);
+
+/// 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.
+///
+/// \exception InvalidTime The given time specifies on or after year 10,000.
+/// \exception Other A standard exception, if resource allocation for the
+/// returned text fails.
+///
+/// \param value Seconds since epoch to be converted.
+/// \return Textual representation of \c value in the form of YYYYMMDDHHmmSS.
+std::string
+timeToText64(uint64_t value);
+
+/// 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".
+std::string
+timeToText32(const uint32_t value);
+
+//@}
+}
+}
+
+#endif // TIME_UTILITIES_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/triplet.h b/src/lib/util/triplet.h
new file mode 100644
index 0000000..fb2a645
--- /dev/null
+++ b/src/lib/util/triplet.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2012-2021 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 TRIPLET_H
+#define TRIPLET_H
+
+#include <exceptions/exceptions.h>
+#include <util/optional.h>
+
+namespace isc {
+namespace util {
+
+/// @brief This template specifies a parameter value
+///
+/// This template class is used to store configuration parameters, like lifetime
+/// or T1. It defines 3 parameters: min, default, and max value. If the
+/// particular configuration parameter is not mandatory, it is possible to
+/// mark the parameter described by a @c Triplet "unspecified". For example, the
+/// T1 and T2 values in DHCPv4 server are optional and may be not specified
+/// in the configuration. The @c Triplets describing these parameters will be
+/// marked "unspecified". If the server finds that the particular parameter
+/// is unspecified it will not include it (e.g. option 58 or 59) in the message
+/// to a client.
+///
+/// There are 3 constructors:
+/// - without parameters - marks the parameter "unspecified"
+/// - simple (just one value that sets all parameters)
+/// - extended (that sets default value and two thresholds)
+///
+/// It will be used with integer types. It provides necessary operators, so
+/// it can be assigned to a plain integer or integer assigned to a Triplet.
+/// See TripletTest.operator test for details on an easy Triplet usage.
+template <class T>
+class Triplet : public util::Optional<T> {
+public:
+
+ using util::Optional<T>::get;
+
+ /// @brief Base type to Triplet conversion.
+ ///
+ /// Typically: uint32_t to Triplet assignment. It is very convenient
+ /// to be able to simply write Triplet<uint32_t> x = 7;
+ ///
+ /// @param other A number to be assigned as min, max and default value.
+ Triplet<T>& operator=(T other) {
+ min_ = other;
+ util::Optional<T>::default_ = other;
+ max_ = other;
+ // The value is now specified because we just assigned one.
+ util::Optional<T>::unspecified_ = false;
+ return (*this);
+ }
+
+ /// @brief Constructor without parameters.
+ ///
+ /// Marks value in @c Triplet unspecified.
+ Triplet()
+ : util::Optional<T>(), min_(0), max_(0) {
+ }
+
+ /// @brief Sets a fixed value.
+ ///
+ /// This constructor assigns a fixed (i.e. no range, just a single value)
+ /// value.
+ ///
+ /// @param value A number to be assigned as min, max and default value.
+ Triplet(T value)
+ : util::Optional<T>(value), min_(value), max_(value) {
+ }
+
+ /// @brief Sets the default value and thresholds
+ ///
+ /// @throw BadValue if min <= def <= max rule is violated
+ Triplet(T min, T def, T max)
+ : util::Optional<T>(def), min_(min), max_(max) {
+ if ( (min_ > def) || (def > max_) ) {
+ isc_throw(BadValue, "Invalid triplet values.");
+ }
+ }
+
+ /// @brief Returns a minimum allowed value
+ T getMin() const { return (min_);}
+
+ /// @brief Returns value with a hint
+ ///
+ /// DHCP protocol treats any values sent by a client as hints.
+ /// This is a method that implements that. We can assign any value
+ /// from configured range that client asks.
+ ///
+ /// @param hint A value being returned when if it is within the range
+ /// between min and max value of @c Triplet. If the hint value is lower
+ /// than min value, the min value is returned. if the hint is greater
+ /// than max value, the max value is returned.
+ ///
+ /// @return A value adjusted to the hint.
+ T get(T hint) const {
+ if (hint <= min_) {
+ return (min_);
+ }
+
+ if (hint >= max_) {
+ return (max_);
+ }
+
+ return (hint);
+ }
+
+ /// @brief Returns a maximum allowed value
+ T getMax() const { return (max_); }
+
+private:
+
+ /// @brief the minimum value
+ T min_;
+
+ /// @brief the maximum value
+ T max_;
+};
+
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // TRIPLET_H
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
new file mode 100644
index 0000000..2d6523c
--- /dev/null
+++ b/src/lib/util/unittests/Makefile.am
@@ -0,0 +1,32 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+noinst_LTLIBRARIES = libutil_unittests.la
+libutil_unittests_la_SOURCES = fork.h fork.cc
+libutil_unittests_la_SOURCES += newhook.h newhook.cc
+libutil_unittests_la_SOURCES += testdata.h testdata.cc
+if HAVE_GTEST
+libutil_unittests_la_SOURCES += resource.h resource.cc
+libutil_unittests_la_SOURCES += check_valgrind.h check_valgrind.cc
+libutil_unittests_la_SOURCES += run_all.h run_all.cc
+libutil_unittests_la_SOURCES += textdata.h
+libutil_unittests_la_SOURCES += wiredata.h wiredata.cc
+libutil_unittests_la_SOURCES += interprocess_util.h interprocess_util.cc
+endif
+
+# For now, this isn't needed for libutil_unittests
+EXTRA_DIST = mock_socketsession.h
+
+libutil_unittests_la_CPPFLAGS = $(AM_CPPFLAGS)
+if HAVE_GTEST
+libutil_unittests_la_CPPFLAGS += $(GTEST_INCLUDES)
+endif
+
+libutil_unittests_la_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+libutil_unittests_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la
+libutil_unittests_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+if HAVE_GTEST
+libutil_unittests_la_LIBADD += $(GTEST_LDADD)
+endif
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/unittests/Makefile.in b/src/lib/util/unittests/Makefile.in
new file mode 100644
index 0000000..46ecf8f
--- /dev/null
+++ b/src/lib/util/unittests/Makefile.in
@@ -0,0 +1,846 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+@HAVE_GTEST_TRUE@am__append_1 = resource.h resource.cc \
+@HAVE_GTEST_TRUE@ check_valgrind.h check_valgrind.cc run_all.h \
+@HAVE_GTEST_TRUE@ run_all.cc textdata.h wiredata.h wiredata.cc \
+@HAVE_GTEST_TRUE@ interprocess_util.h interprocess_util.cc
+@HAVE_GTEST_TRUE@am__append_2 = $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@am__append_3 = $(GTEST_LDADD)
+subdir = src/lib/util/unittests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
+libutil_unittests_la_DEPENDENCIES = \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_2)
+am__libutil_unittests_la_SOURCES_DIST = fork.h fork.cc newhook.h \
+ newhook.cc testdata.h testdata.cc resource.h resource.cc \
+ check_valgrind.h check_valgrind.cc run_all.h run_all.cc \
+ textdata.h wiredata.h wiredata.cc interprocess_util.h \
+ interprocess_util.cc
+@HAVE_GTEST_TRUE@am__objects_1 = libutil_unittests_la-resource.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-check_valgrind.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-run_all.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-wiredata.lo \
+@HAVE_GTEST_TRUE@ libutil_unittests_la-interprocess_util.lo
+am_libutil_unittests_la_OBJECTS = libutil_unittests_la-fork.lo \
+ libutil_unittests_la-newhook.lo \
+ libutil_unittests_la-testdata.lo $(am__objects_1)
+libutil_unittests_la_OBJECTS = $(am_libutil_unittests_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 =
+libutil_unittests_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libutil_unittests_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)/libutil_unittests_la-check_valgrind.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-fork.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-newhook.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-resource.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-run_all.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-testdata.Plo \
+ ./$(DEPDIR)/libutil_unittests_la-wiredata.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 = $(libutil_unittests_la_SOURCES)
+DIST_SOURCES = $(am__libutil_unittests_la_SOURCES_DIST)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp README
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+noinst_LTLIBRARIES = libutil_unittests.la
+libutil_unittests_la_SOURCES = fork.h fork.cc newhook.h newhook.cc \
+ testdata.h testdata.cc $(am__append_1)
+
+# For now, this isn't needed for libutil_unittests
+EXTRA_DIST = mock_socketsession.h
+libutil_unittests_la_CPPFLAGS = $(AM_CPPFLAGS) $(am__append_2)
+libutil_unittests_la_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+libutil_unittests_la_LIBADD = \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__append_3)
+CLEANFILES = *.gcno *.gcda
+all: all-am
+
+.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/util/unittests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/util/unittests/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libutil_unittests.la: $(libutil_unittests_la_OBJECTS) $(libutil_unittests_la_DEPENDENCIES) $(EXTRA_libutil_unittests_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libutil_unittests_la_LINK) $(libutil_unittests_la_OBJECTS) $(libutil_unittests_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-check_valgrind.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-fork.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-newhook.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-resource.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-run_all.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-testdata.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libutil_unittests_la-wiredata.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)$(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 $@ $<
+
+libutil_unittests_la-fork.lo: fork.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-fork.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-fork.Tpo -c -o libutil_unittests_la-fork.lo `test -f 'fork.cc' || echo '$(srcdir)/'`fork.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-fork.Tpo $(DEPDIR)/libutil_unittests_la-fork.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='fork.cc' object='libutil_unittests_la-fork.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-fork.lo `test -f 'fork.cc' || echo '$(srcdir)/'`fork.cc
+
+libutil_unittests_la-newhook.lo: newhook.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-newhook.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-newhook.Tpo -c -o libutil_unittests_la-newhook.lo `test -f 'newhook.cc' || echo '$(srcdir)/'`newhook.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-newhook.Tpo $(DEPDIR)/libutil_unittests_la-newhook.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='newhook.cc' object='libutil_unittests_la-newhook.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-newhook.lo `test -f 'newhook.cc' || echo '$(srcdir)/'`newhook.cc
+
+libutil_unittests_la-testdata.lo: testdata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-testdata.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-testdata.Tpo -c -o libutil_unittests_la-testdata.lo `test -f 'testdata.cc' || echo '$(srcdir)/'`testdata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-testdata.Tpo $(DEPDIR)/libutil_unittests_la-testdata.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='testdata.cc' object='libutil_unittests_la-testdata.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-testdata.lo `test -f 'testdata.cc' || echo '$(srcdir)/'`testdata.cc
+
+libutil_unittests_la-resource.lo: resource.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-resource.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-resource.Tpo -c -o libutil_unittests_la-resource.lo `test -f 'resource.cc' || echo '$(srcdir)/'`resource.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-resource.Tpo $(DEPDIR)/libutil_unittests_la-resource.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='resource.cc' object='libutil_unittests_la-resource.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-resource.lo `test -f 'resource.cc' || echo '$(srcdir)/'`resource.cc
+
+libutil_unittests_la-check_valgrind.lo: check_valgrind.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-check_valgrind.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-check_valgrind.Tpo -c -o libutil_unittests_la-check_valgrind.lo `test -f 'check_valgrind.cc' || echo '$(srcdir)/'`check_valgrind.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-check_valgrind.Tpo $(DEPDIR)/libutil_unittests_la-check_valgrind.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='check_valgrind.cc' object='libutil_unittests_la-check_valgrind.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-check_valgrind.lo `test -f 'check_valgrind.cc' || echo '$(srcdir)/'`check_valgrind.cc
+
+libutil_unittests_la-run_all.lo: run_all.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-run_all.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-run_all.Tpo -c -o libutil_unittests_la-run_all.lo `test -f 'run_all.cc' || echo '$(srcdir)/'`run_all.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-run_all.Tpo $(DEPDIR)/libutil_unittests_la-run_all.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_all.cc' object='libutil_unittests_la-run_all.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-run_all.lo `test -f 'run_all.cc' || echo '$(srcdir)/'`run_all.cc
+
+libutil_unittests_la-wiredata.lo: wiredata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-wiredata.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-wiredata.Tpo -c -o libutil_unittests_la-wiredata.lo `test -f 'wiredata.cc' || echo '$(srcdir)/'`wiredata.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-wiredata.Tpo $(DEPDIR)/libutil_unittests_la-wiredata.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='wiredata.cc' object='libutil_unittests_la-wiredata.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-wiredata.lo `test -f 'wiredata.cc' || echo '$(srcdir)/'`wiredata.cc
+
+libutil_unittests_la-interprocess_util.lo: interprocess_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libutil_unittests_la-interprocess_util.lo -MD -MP -MF $(DEPDIR)/libutil_unittests_la-interprocess_util.Tpo -c -o libutil_unittests_la-interprocess_util.lo `test -f 'interprocess_util.cc' || echo '$(srcdir)/'`interprocess_util.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libutil_unittests_la-interprocess_util.Tpo $(DEPDIR)/libutil_unittests_la-interprocess_util.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='interprocess_util.cc' object='libutil_unittests_la-interprocess_util.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) $(libutil_unittests_la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libutil_unittests_la-interprocess_util.lo `test -f 'interprocess_util.cc' || echo '$(srcdir)/'`interprocess_util.cc
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+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 clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-check_valgrind.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-fork.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-newhook.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-resource.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-run_all.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-testdata.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-wiredata.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/libutil_unittests_la-check_valgrind.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-fork.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-interprocess_util.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-newhook.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-resource.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-run_all.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-testdata.Plo
+ -rm -f ./$(DEPDIR)/libutil_unittests_la-wiredata.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ 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 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/util/unittests/README b/src/lib/util/unittests/README
new file mode 100644
index 0000000..0ed888f
--- /dev/null
+++ b/src/lib/util/unittests/README
@@ -0,0 +1,5 @@
+This directory contains some code that is useful while writing various
+unittest code. It doesn't contain any code that would actually run in
+bind10.
+
+Because this is a test code, we do not test it explicitly.
diff --git a/src/lib/util/unittests/check_valgrind.cc b/src/lib/util/unittests/check_valgrind.cc
new file mode 100644
index 0000000..8412d43
--- /dev/null
+++ b/src/lib/util/unittests/check_valgrind.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2012-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>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+#if HAVE_VALGRIND_HEADERS
+#include <valgrind/valgrind.h>
+/// \brief Check if the program is run in valgrind
+///
+/// \return true if valgrind headers are available, and valgrind is running,
+/// false if the headers are not available, or if valgrind is not
+/// running
+bool
+runningOnValgrind() {
+ return (RUNNING_ON_VALGRIND != 0);
+}
+#else
+bool
+runningOnValgrind() {
+ return (false);
+}
+#endif // HAVE_VALGRIND_HEADERS
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
diff --git a/src/lib/util/unittests/check_valgrind.h b/src/lib/util/unittests/check_valgrind.h
new file mode 100644
index 0000000..0abe39f
--- /dev/null
+++ b/src/lib/util/unittests/check_valgrind.h
@@ -0,0 +1,45 @@
+// Copyright (C) 2012-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/.
+
+//
+// If we have the valgrind headers available, we can detect whether
+// valgrind is running. This should normally never be done, as you
+// want the to test the actual code in operation with valgrind.
+//
+// However, there is a limited set of operations where we want to
+// skip some tests if run under valgrind, most notably the
+// EXPECT_DEATH tests, as these would report memory leaks by
+// definition.
+//
+// If the valgrind headers are NOT available, the method checkValgrind()
+// always returns false; i.e. it always pretends the program is run
+// natively
+//
+
+#ifndef UTIL_UNITTESTS_CHECK_VALGRIND_H
+#define UTIL_UNITTESTS_CHECK_VALGRIND_H 1
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Check if the program is run in valgrind
+///
+/// This is used to check for valgrind and skip (parts of) tests that fork,
+/// such as death tests, and general forking tests, and some threading tests;
+/// These tend to cause valgrind to report errors, which would hide other
+/// potential valgrind reports.
+///
+/// \return true if valgrind headers are available, and valgrind is running,
+/// false if the headers are not available, or if valgrind is not
+/// running
+bool runningOnValgrind();
+
+} // end namespace unittests
+} // end namespace util
+} // end namespace isc
+
+#endif // UTIL_UNITTESTS_CHECK_VALGRIND_H
diff --git a/src/lib/util/unittests/fork.cc b/src/lib/util/unittests/fork.cc
new file mode 100644
index 0000000..fc8b525
--- /dev/null
+++ b/src/lib/util/unittests/fork.cc
@@ -0,0 +1,141 @@
+// 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 <util/unittests/fork.h>
+
+#include <util/io/fd.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <cerrno>
+#include <stdlib.h>
+#include <stdio.h>
+
+using namespace isc::util::io;
+
+namespace {
+
+// Just a NOP function to ignore a signal but let it interrupt function.
+void no_handler(int) { }
+
+};
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+bool
+process_ok(pid_t process) {
+ // Create a timeout
+ struct sigaction ignored, original;
+ memset(&ignored, 0, sizeof ignored);
+ ignored.sa_handler = no_handler;
+ if (sigaction(SIGALRM, &ignored, &original)) {
+ return false;
+ }
+ // It is long, but if everything is OK, it'll not happen
+ alarm(10);
+ int status;
+ int result(waitpid(process, &status, 0) == -1);
+ // Cancel the alarm and return the original handler
+ alarm(0);
+ if (sigaction(SIGALRM, &original, NULL)) {
+ return false;
+ }
+ // Check what we found out
+ if (result) {
+ if (errno == EINTR)
+ kill(process, SIGTERM);
+ return false;
+ }
+ return WIFEXITED(status) && WEXITSTATUS(status) == 0;
+}
+
+/*
+ * This creates a pipe, forks and feeds the pipe with given data.
+ * Used to provide the input in non-blocking/asynchronous way.
+ */
+pid_t
+provide_input(int *read_pipe, const void *input, const size_t length)
+{
+ int pipes[2];
+ if (pipe(pipes)) {
+ return -1;
+ }
+ *read_pipe = pipes[0];
+
+ pid_t pid(fork());
+ if (pid) { // We are in the parent
+ return pid;
+ } else { // This is in the child, just puts the data there
+ close(pipes[0]);
+ if (!write_data(pipes[1], input, length)) {
+ exit(1);
+ } else {
+ close(pipes[1]);
+ exit(0);
+ }
+ }
+}
+
+
+/*
+ * This creates a pipe, forks and reads the pipe and compares it
+ * with given data. Used to check output of run in an asynchronous way.
+ */
+pid_t
+check_output(int *write_pipe, const void* const output, const size_t length)
+{
+ int pipes[2];
+ if (pipe(pipes)) {
+ return -1;
+ }
+ *write_pipe = pipes[1];
+ pid_t pid(fork());
+ if (pid) { // We are in parent
+ close(pipes[0]);
+ return pid;
+ } else {
+ close(pipes[1]);
+ unsigned char* buffer = new unsigned char[length + 1];
+ // Try to read one byte more to see if the output ends here
+ size_t got_length(read_data(pipes[0], buffer, length + 1));
+ bool ok(true);
+ if (got_length != length) {
+ fprintf(stderr, "Different length (expected %u, got %u)\n",
+ static_cast<unsigned>(length),
+ static_cast<unsigned>(got_length));
+ ok = false;
+ }
+ if(!ok || memcmp(buffer, output, length)) {
+ const unsigned char *output_c(static_cast<const unsigned char *>(
+ output));
+ // If they differ, print what we have
+ for(size_t i(0); i != got_length; ++ i) {
+ fprintf(stderr, "%02hhx", buffer[i]);
+ }
+ fprintf(stderr, "\n");
+ for(size_t i(0); i != length; ++ i) {
+ fprintf(stderr, "%02hhx", output_c[i]);
+ }
+ fprintf(stderr, "\n");
+ delete [] buffer;
+ exit(1);
+ } else {
+ delete [] buffer;
+ exit(0);
+ }
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/fork.h b/src/lib/util/unittests/fork.h
new file mode 100644
index 0000000..68277f6
--- /dev/null
+++ b/src/lib/util/unittests/fork.h
@@ -0,0 +1,44 @@
+// 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/.
+
+#ifndef UTIL_UNITTESTS_FORK_H
+#define UTIL_UNITTESTS_FORK_H 1
+
+#include <unistd.h>
+
+/**
+ * \file fork.h
+ * \brief Help functions to fork the test case process.
+ * Various functions to fork a process and feed some data to pipe, check
+ * its output and such lives here.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/**
+ * @short Checks that a process terminates correctly.
+ * Waits for a process to terminate (with a short timeout, this should be
+ * used whan the process is about to terminate) and checks its exit code.
+ *
+ * @return True if the process terminates with 0, false otherwise.
+ * @param process The ID of process to wait for.
+ */
+bool
+process_ok(pid_t process);
+
+pid_t
+provide_input(int* read_pipe, const void* input, const size_t length);
+
+pid_t
+check_output(int* write_pipe, const void* const output, const size_t length);
+
+} // End of the namespace
+}
+}
+
+#endif // UTIL_UNITTESTS_FORK_H
diff --git a/src/lib/util/unittests/interprocess_util.cc b/src/lib/util/unittests/interprocess_util.cc
new file mode 100644
index 0000000..dd639ff
--- /dev/null
+++ b/src/lib/util/unittests/interprocess_util.cc
@@ -0,0 +1,42 @@
+// Copyright (C) 2013-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 <sys/select.h>
+#include <cstddef>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+unsigned char
+parentReadState(int fd) {
+ unsigned char result = 0xff;
+
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+
+ struct timeval tv = {5, 0};
+
+ const int nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
+ EXPECT_EQ(1, nfds);
+
+ if (nfds == 1) {
+ // Read status
+ const ssize_t bytes_read = read(fd, &result, sizeof(result));
+ EXPECT_EQ(sizeof(result), bytes_read);
+ }
+
+ return (result);
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/interprocess_util.h b/src/lib/util/unittests/interprocess_util.h
new file mode 100644
index 0000000..7012f7b
--- /dev/null
+++ b/src/lib/util/unittests/interprocess_util.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2013-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/.
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// \brief A helper utility for a simple synchronization with another process.
+///
+/// It waits for incoming data on a given file descriptor up to 5 seconds
+/// (arbitrary choice), read one byte data, and return it to the caller.
+/// On any failure it returns 0xff (255), so the sender process should use
+/// a different value to pass.
+unsigned char parentReadState(int fd);
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/mock_socketsession.h b/src/lib/util/unittests/mock_socketsession.h
new file mode 100644
index 0000000..808cddb
--- /dev/null
+++ b/src/lib/util/unittests/mock_socketsession.h
@@ -0,0 +1,151 @@
+// Copyright (C) 2012-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/.
+
+#ifndef UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+#define UTIL_UNITTESTS_MOCKSOCKETSESSION_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <util/io/socketsession.h>
+#include <util/io/sockaddr_util.h>
+
+#include <cassert>
+#include <cstring>
+#include <vector>
+
+#include <sys/socket.h>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Mock socket session forwarder.
+///
+/// It emulates the behavior of SocketSessionForwarder without involving
+/// network communication, and allowing the tester to customize the behavior
+/// and to examine forwarded data afterwards.
+class MockSocketSessionForwarder :
+ public isc::util::io::BaseSocketSessionForwarder
+{
+public:
+ MockSocketSessionForwarder() :
+ is_connected_(false), connect_ok_(true), push_ok_(true),
+ close_ok_(true),
+ // These are not used until set, but we set them anyway here,
+ // partly to silence cppcheck, and partly to be cleaner. Put some
+ // invalid values in.
+ pushed_sock_(-1), pushed_family_(-1), pushed_type_(-1),
+ pushed_protocol_(-1)
+ {}
+
+ virtual void connectToReceiver() {
+ if (!connect_ok_) {
+ isc_throw(isc::util::io::SocketSessionError, "socket session "
+ "forwarding connection disabled for test");
+ }
+ if (is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError, "duplicate connect");
+ }
+ is_connected_ = true;
+ }
+ virtual void close() {
+ if (!is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError, "duplicate close");
+ }
+ is_connected_ = false;
+ }
+
+ // Pushing a socket session. It copies the given session data
+ // so that the test code can check the values later via the getter
+ // methods. Complete deep copy will be created, so the caller doesn't
+ // have to keep the parameters valid after the call to this method.
+ virtual void push(int sock, int family, int type, int protocol,
+ const struct sockaddr& local_end,
+ const struct sockaddr& remote_end,
+ const void* data, size_t data_len)
+ {
+ if (!push_ok_) {
+ isc_throw(isc::util::io::SocketSessionError,
+ "socket session forwarding is disabled for test");
+ }
+ if (!is_connected_) {
+ isc_throw(isc::util::io::SocketSessionError,
+ "socket session is being pushed before connected");
+ }
+
+ // Copy parameters for later checks
+ pushed_sock_ = sock;
+ pushed_family_ = family;
+ pushed_type_ = type;
+ pushed_protocol_ = protocol;
+ assert(io::internal::getSALength(local_end) <=
+ sizeof(pushed_local_end_ss_));
+ std::memcpy(&pushed_local_end_ss_, &local_end,
+ io::internal::getSALength(local_end));
+ assert(io::internal::getSALength(remote_end) <=
+ sizeof(pushed_remote_end_ss_));
+ std::memcpy(&pushed_remote_end_ss_, &remote_end,
+ io::internal::getSALength(remote_end));
+ pushed_data_.resize(data_len);
+ std::memcpy(&pushed_data_[0], data, data_len);
+ }
+
+ // Allow the test code to check if the connection is established.
+ bool isConnected() const { return (is_connected_); }
+
+ // Allow the test code to customize the forwarder behavior wrt whether
+ // a specific operation should succeed or fail.
+ void disableConnect() { connect_ok_ = false; }
+ void enableConnect() { connect_ok_ = true; }
+ void disableClose() { close_ok_ = false; }
+ void disablePush() { push_ok_ = false; }
+ void enablePush() { push_ok_ = true; }
+
+ // Read-only accessors to recorded parameters to the previous successful
+ // call to push(). Return values are undefined if there has been no
+ // successful call to push().
+ // Note that we use convertSockAddr() to convert sockaddr_storage to
+ // sockaddr. It should be safe since we use the storage in its literal
+ // sense; it was originally filled with the binary image of another
+ // sockaddr structure, and we are going to return the image opaquely
+ // as a sockaddr structure without touching the data.
+ int getPushedSock() const { return (pushed_sock_); }
+ int getPushedFamily() const { return (pushed_family_); }
+ int getPushedType() const { return (pushed_type_); }
+ int getPushedProtocol() const { return (pushed_protocol_); }
+ const struct sockaddr& getPushedLocalend() const {
+ return (*io::internal::convertSockAddr(&pushed_local_end_ss_));
+ }
+ const struct sockaddr& getPushedRemoteend() const {
+ return (*io::internal::convertSockAddr(&pushed_remote_end_ss_));
+ }
+ const std::vector<uint8_t>& getPushedData() const {
+ return (pushed_data_);
+ }
+
+private:
+ bool is_connected_;
+ bool connect_ok_;
+ bool push_ok_;
+ bool close_ok_;
+ int pushed_sock_;
+ int pushed_family_;
+ int pushed_type_;
+ int pushed_protocol_;
+ struct sockaddr_storage pushed_local_end_ss_;
+ struct sockaddr_storage pushed_remote_end_ss_;
+ std::vector<uint8_t> pushed_data_;
+};
+
+} // end of unittests
+} // end of util
+} // end of isc
+#endif // UTIL_UNITTESTS_MOCKSOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/newhook.cc b/src/lib/util/unittests/newhook.cc
new file mode 100644
index 0000000..16c601a
--- /dev/null
+++ b/src/lib/util/unittests/newhook.cc
@@ -0,0 +1,45 @@
+// 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 <stdlib.h>
+
+#include <new>
+#include <stdexcept>
+
+#include <util/unittests/newhook.h>
+
+#ifdef ENABLE_CUSTOM_OPERATOR_NEW
+void*
+operator new(size_t size) throw(std::bad_alloc) {
+ if (isc::util::unittests::force_throw_on_new &&
+ size == isc::util::unittests::throw_size_on_new) {
+ throw std::bad_alloc();
+ }
+ void* p = malloc(size);
+ if (p == NULL) {
+ throw std::bad_alloc();
+ }
+ return (p);
+}
+
+void
+operator delete(void* p) throw() {
+ if (p != NULL) {
+ free(p);
+ }
+}
+#endif
+
+namespace isc {
+namespace util {
+namespace unittests {
+bool force_throw_on_new = false;
+size_t throw_size_on_new = 0;
+}
+}
+}
diff --git a/src/lib/util/unittests/newhook.h b/src/lib/util/unittests/newhook.h
new file mode 100644
index 0000000..1fc3eba
--- /dev/null
+++ b/src/lib/util/unittests/newhook.h
@@ -0,0 +1,74 @@
+// 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/.
+
+#ifndef UTIL_UNITTESTS_NEWHOOK_H
+#define UTIL_UNITTESTS_NEWHOOK_H 1
+
+/**
+ * \file newhook.h
+ * \brief Enable the use of special operator new that throws for testing.
+ *
+ * This small utility allows a test case to force the global operator new
+ * to throw for a given size to test a case where memory allocation fails
+ * (which normally doesn't happen). To enable the feature, everything must
+ * be built with defining ENABLE_CUSTOM_OPERATOR_NEW beforehand, and set
+ * \c force_throw_on_new to \c true and \c throw_size_on_new to the size
+ * of data that should trigger the exception, immediately before starting
+ * the specific test that needs the exception.
+ *
+ * Example:
+ * \code #include <util/unittests/newhook.h>
+ * ...
+ * TEST(SomeTest, newException) {
+ * isc::util::unittests::force_throw_on_new = true;
+ * isc::util::unittests::throw_size_on_new = sizeof(Foo);
+ * try {
+ * // this will do 'new Foo()' internally and should throw
+ * createFoo();
+ * isc::util::unittests::force_throw_on_new = false;
+ * ASSERT_FALSE(true) << "Expected throw on new";
+ * } catch (const std::bad_alloc&) {
+ * isc::util::unittests::force_throw_on_new = false;
+ * // do some integrity check, etc, if necessary
+ * }
+ * } \endcode
+ *
+ * Replacing the global operator new (and delete) is a dangerous technique,
+ * and triggering an exception solely based on the allocation size is not
+ * reliable, so this feature is disabled by default two-fold: The
+ * ENABLE_CUSTOM_OPERATOR_NEW build time variable, and run-time
+ * \c force_throw_on_new.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Switch to enable the use of special operator new
+///
+/// This is set to \c false by default.
+extern bool force_throw_on_new;
+
+/// The allocation size that triggers an exception in the special operator new
+///
+/// This is the exact size that causes an exception to be thrown;
+/// for example, if it is set to 100, an attempt of allocating 100 bytes
+/// will result in an exception, but allocation attempt for 101 bytes won't
+/// (unless, of course, memory is really exhausted and allocation really
+/// fails).
+///
+/// The default value is 0. The value of this variable has no meaning
+/// unless the use of the special operator is enabled at build time and
+/// via \c force_throw_on_new.
+extern size_t throw_size_on_new;
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_NEWHOOK_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/resource.cc b/src/lib/util/unittests/resource.cc
new file mode 100644
index 0000000..b4a33b3
--- /dev/null
+++ b/src/lib/util/unittests/resource.cc
@@ -0,0 +1,29 @@
+// Copyright (C) 2012-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 <util/unittests/resource.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+dontCreateCoreDumps() {
+ const rlimit core_limit = {0, 0};
+
+ EXPECT_EQ(setrlimit(RLIMIT_CORE, &core_limit), 0);
+}
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
diff --git a/src/lib/util/unittests/resource.h b/src/lib/util/unittests/resource.h
new file mode 100644
index 0000000..dfa44ee
--- /dev/null
+++ b/src/lib/util/unittests/resource.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2012-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/.
+
+#ifndef UTIL_UNITTESTS_RESOURCE_H
+#define UTIL_UNITTESTS_RESOURCE_H 1
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Don't create core dumps.
+///
+/// This function sets the core size to 0, inhibiting the creation of
+/// core dumps. It is meant to be used in testcases where EXPECT_DEATH
+/// is used, where processes abort (and create cores in the process).
+/// As a new process is forked to run EXPECT_DEATH tests, the rlimits of
+/// the parent process that runs the other tests should be unaffected.
+void dontCreateCoreDumps();
+
+} // end of namespace unittests
+} // end of namespace util
+} // end of namespace isc
+
+#endif // UTIL_UNITTESTS_RESOURCE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/run_all.cc b/src/lib/util/unittests/run_all.cc
new file mode 100644
index 0000000..82542ca
--- /dev/null
+++ b/src/lib/util/unittests/run_all.cc
@@ -0,0 +1,89 @@
+// 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 <stdlib.h>
+
+#include <iostream>
+#include <iomanip>
+
+#include <gtest/gtest.h>
+#include <exceptions/exceptions.h>
+#include <util/unittests/run_all.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+int
+run_all() {
+ int ret = 0;
+
+ // The catching of exceptions generated in tests is controlled by the
+ // KEATEST_CATCH_EXCEPTION environment variable. Setting this to
+ // 1 enables the catching of exceptions; setting it to 0 disables it.
+ // Anything else causes a message to be printed to stderr and the default
+ // taken. (The default is to catch exceptions if compiling with clang
+ // and false if not.)
+#ifdef __clang__
+ bool catch_exception = true;
+#else
+ bool catch_exception = false;
+#endif
+
+ const char* keatest_catch_exception = getenv("KEATEST_CATCH_EXCEPTION");
+ if (keatest_catch_exception != NULL) {
+ if (strcmp(keatest_catch_exception, "1") == 0) {
+ catch_exception = true;
+ } else if (strcmp(keatest_catch_exception, "0") == 0) {
+ catch_exception = false;
+ } else {
+ std::cerr << "***ERROR: KEATEST_CATCH_EXCEPTION is '"
+ << keatest_catch_exception
+ << "': allowed values are '1' or '0'.\n"
+ << " The default value of "
+ << (catch_exception ?
+ "1 (exception catching enabled)":
+ "0 (exception catching disabled)")
+ << " will be used.\n";
+ }
+ }
+
+ // Actually run the code
+ if (catch_exception) {
+ try {
+ ret = RUN_ALL_TESTS();
+ } catch (const isc::Exception& ex) {
+ // Could output more information with typeid(), but there is no
+ // guarantee that all compilers will support it without an explicit
+ // flag on the command line.
+ std::cerr << "*** Exception derived from isc::exception thrown:\n"
+ << " file: " << ex.getFile() << "\n"
+ << " line: " << ex.getLine() << "\n"
+ << " what: " << ex.what() << std::endl;
+ throw;
+ } catch (const std::exception& ex) {
+ std::cerr << "*** Exception derived from std::exception thrown:\n"
+ << " what: " << ex.what() << std::endl;
+ throw;
+ }
+ } else {
+ // This is a separate path for the case where the exception is not
+ // being caught. Although the other code path re-throws the exception
+ // after catching it, there is no guarantee that the state of the
+ // stack is preserved - a compiler might have unwound the stack to
+ // the point at which the exception is caught. This would prove
+ // awkward if trying to debug the program using a debugger.
+ ret = RUN_ALL_TESTS();
+ }
+
+ return (ret);
+}
+
+} // namespace unittests
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/unittests/run_all.h b/src/lib/util/unittests/run_all.h
new file mode 100644
index 0000000..7415e6b
--- /dev/null
+++ b/src/lib/util/unittests/run_all.h
@@ -0,0 +1,44 @@
+// 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/.
+
+
+
+#ifndef RUN_ALL_H
+#define RUN_ALL_H
+
+// Avoid need for user to include this header file.
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Run All Tests
+///
+/// A wrapper for the Google Test RUN_ALL_TESTS() macro, this calls the macro
+/// but wraps the call in a try...catch block if the environment variable
+/// KEATEST_CATCH_EXCEPTION is defined, and calls the macro directly if not.
+///
+/// The catch block catches exceptions of types isc::Exception and
+/// std::exception and prints some information about them to stderr. (In the
+/// case of isc::Exception, this includes the file and line number from which
+/// the exception was raised.) It then re-throws the exception.
+///
+/// See: https://lists.isc.org/pipermail/bind10-dev/2011-January/001867.html
+/// for some context.
+///
+/// \return Return value from RUN_ALL_TESTS().
+
+int run_all();
+
+} // namespace unittests
+} // namespace util
+} // namespace isc
+
+
+
+#endif // RUN_ALL_H
diff --git a/src/lib/util/unittests/testdata.cc b/src/lib/util/unittests/testdata.cc
new file mode 100644
index 0000000..e3ecc91
--- /dev/null
+++ b/src/lib/util/unittests/testdata.cc
@@ -0,0 +1,55 @@
+// 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 <string>
+#include <stdexcept>
+#include <fstream>
+#include <vector>
+
+#include <util/unittests/testdata.h>
+
+using namespace std;
+
+namespace {
+vector<string>&
+getDataPaths() {
+ static vector<string> data_path;
+ return (data_path);
+}
+}
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+addTestDataPath(const string& path) {
+ getDataPaths().push_back(path);
+}
+
+void
+openTestData(const char* const datafile, ifstream& ifs) {
+ vector<string>::const_iterator it = getDataPaths().begin();
+ for (; it != getDataPaths().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.fail()) {
+ return;
+ }
+ }
+
+ throw runtime_error("failed to open data file in data paths: " +
+ string(datafile));
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/testdata.h b/src/lib/util/unittests/testdata.h
new file mode 100644
index 0000000..ee7c1c5
--- /dev/null
+++ b/src/lib/util/unittests/testdata.h
@@ -0,0 +1,46 @@
+// 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/.
+
+#ifndef UTIL_UNITTESTS_TESTDATA_H
+#define UTIL_UNITTESTS_TESTDATA_H 1
+
+/**
+ * \file testdata.h
+ * \brief Manipulating test data files.
+ *
+ * This utility defines functions that help test case handle test data
+ * stored in a file.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Add a path (directory) that \c openTestData() will search for test data
+/// files.
+void addTestDataPath(const std::string& path);
+
+/// Open a file specified by 'datafile' using the data paths registered via
+/// addTestDataPath(). On success, ifs will be ready for reading the data
+/// stored in 'datafile'. If the data file cannot be open with any of the
+/// registered paths, a runtime_error exception will be thrown.
+///
+/// \note Care should be taken if you want to reuse the same single \c ifs
+/// for multiple input data. Some standard C++ library implementations retain
+/// the failure bit if the first stream reaches the end of the first file,
+/// and make the second call to \c ifstream::open() fail. The safest way
+/// is to use a different \c ifstream object for a new call to this function;
+/// alternatively make sure you explicitly clear the error bit by calling
+/// \c ifstream::clear() on \c ifs.
+void openTestData(const char* const datafile, std::ifstream& ifs);
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_TESTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/textdata.h b/src/lib/util/unittests/textdata.h
new file mode 100644
index 0000000..ae132cd
--- /dev/null
+++ b/src/lib/util/unittests/textdata.h
@@ -0,0 +1,95 @@
+// 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 <istream>
+#include <string>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#ifndef UTIL_UNITTESTS_TEXTDATA_H
+#define UTIL_UNITTESTS_TEXTDATA_H 1
+
+/**
+ * \file textdata.h
+ * \brief Utilities for tests with text data.
+ *
+ * This utility provides convenient helper functions for unit tests using
+ * textual data.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Line-by-line text comparison.
+///
+/// This templated function takes two standard input streams, extracts
+/// strings from them, and compares the two sets of strings line by line.
+template <typename EXPECTED_STREAM, typename ACTUAL_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, ACTUAL_STREAM& actual) {
+ std::string actual_line;
+ std::string expected_line;
+ while (std::getline(actual, actual_line), !actual.eof()) {
+ std::getline(expected, expected_line);
+ if (expected.eof()) {
+ FAIL() << "Redundant line in actual output: " << actual_line;
+ break;
+ }
+ if (actual.bad() || actual.fail() ||
+ expected.bad() || expected.fail()) {
+ throw std::runtime_error("Unexpected error in data streams");
+ }
+ EXPECT_EQ(expected_line, actual_line);
+ }
+ while (std::getline(expected, expected_line), !expected.eof()) {
+ ADD_FAILURE() << "Missing line in actual output: " << expected_line;
+ }
+}
+
+/// Similar to the fully templated version, but takes string for the second
+/// (actual) data.
+///
+/// Due to the nature of textual data, it will often be the case that test
+/// data is given as a string object. This shortcut version helps such cases
+/// so that the test code doesn't have to create a string stream with the
+/// string data just for testing.
+template <typename EXPECTED_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, const std::string& actual_text) {
+ std::istringstream iss(actual_text);
+ matchTextData(expected, iss);
+}
+
+/// Same for the previous version, but the first argument is string.
+template <typename ACTUAL_STREAM>
+void
+matchTextData(const std::string& expected_text, ACTUAL_STREAM& actual) {
+ std::istringstream iss(expected_text);
+ matchTextData(iss, actual);
+}
+
+/// Same for the previous two, but takes strings for both expected and
+/// actual data.
+void
+matchTextData(const std::string& expected_text,
+ const std::string& actual_text)
+{
+ std::istringstream expected_is(expected_text);
+ std::istringstream actual_is(actual_text);
+ matchTextData(expected_is, actual_is);
+}
+
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_TEXTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/wiredata.cc b/src/lib/util/unittests/wiredata.cc
new file mode 100644
index 0000000..c14c8f1
--- /dev/null
+++ b/src/lib/util/unittests/wiredata.cc
@@ -0,0 +1,46 @@
+// Copyright (C) 2012-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 <util/unittests/wiredata.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm> // for std::min
+#include <stdint.h>
+
+using namespace std;
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+matchWireData(const void* expected_data, size_t expected_len,
+ const void* actual_data, size_t actual_len)
+{
+ const size_t cmplen = std::min(expected_len, actual_len);
+
+ for (size_t i = 0; i < cmplen; ++i) {
+ const int ebyte = static_cast<const uint8_t*>(expected_data)[i];
+ const int abyte = static_cast<const uint8_t*>(actual_data)[i];
+ // Once we find a mismatch, it's quite likely that there will be many
+ // mismatches after this point. So we stop here by using ASSERT not
+ // to be too noisy.
+ ASSERT_EQ(ebyte, abyte) << "Wire data mismatch at " << i << "th byte\n"
+ << " Actual: " << abyte << "\n"
+ << "Expected: " << ebyte << "\n";
+ }
+ EXPECT_EQ(expected_len, actual_len)
+ << "Wire data mismatch in length:\n"
+ << " Actual: " << actual_len << "\n"
+ << "Expected: " << expected_len << "\n";
+}
+
+} // unittests
+} // util
+} // isc
diff --git a/src/lib/util/unittests/wiredata.h b/src/lib/util/unittests/wiredata.h
new file mode 100644
index 0000000..fee555e
--- /dev/null
+++ b/src/lib/util/unittests/wiredata.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2012-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/.
+
+#ifndef UTIL_UNITTESTS_WIREDATA_H
+#define UTIL_UNITTESTS_WIREDATA_H 1
+
+#include <cstddef>
+
+/// \file wiredata.h
+/// \brief Utilities for tests with wire data.
+///
+/// This utility provides convenient helper functions for unit tests using
+/// wire (binary) data.
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// \brief Compare two sets of binary data in a google test.
+///
+/// This method checks if the expected and actual data have the same length
+/// and all bytes are the same. If not, it reports the point of mismatch in
+/// the google test format.
+void matchWireData(const void* expected_data, std::size_t expected_len,
+ const void* actual_data, std::size_t actual_len);
+}
+}
+}
+
+#endif // UTIL_UNITTESTS_WIREDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unlock_guard.h b/src/lib/util/unlock_guard.h
new file mode 100644
index 0000000..30be514
--- /dev/null
+++ b/src/lib/util/unlock_guard.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2020 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 UNLOCK_GUARD_H
+#define UNLOCK_GUARD_H
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief Unlock Guard.
+///
+/// Acts as a reverse std::lock_guard.
+///
+/// @tparam Mutex a mutex object.
+template<typename Mutex>
+class UnlockGuard : public boost::noncopyable {
+public:
+ /// @brief Constructor.
+ ///
+ /// Unlock mutex object on constructor.
+ ///
+ /// @param lock the mutex used for unlocking and locking.
+ explicit UnlockGuard(Mutex& lock) : lock_(lock) {
+ lock_.unlock();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Lock mutex object on destructor.
+ ~UnlockGuard() {
+ lock_.lock();
+ }
+
+private:
+ /// @brief The mutex object.
+ Mutex& lock_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // UNLOCK_GUARD_H
diff --git a/src/lib/util/util.dox b/src/lib/util/util.dox
new file mode 100644
index 0000000..94769a1
--- /dev/null
+++ b/src/lib/util/util.dox
@@ -0,0 +1,93 @@
+// Copyright (C) 2020-2021 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/.
+
+/**
+ @page libutil libkea-util - Kea Utilities Library
+
+@section utilUtilities Utilities
+
+The utilities library (libkea-util) provides generic and Kea utilities:
+
+ - boost time: boost Posix time and duration to text conversions.
+
+ - buffer (header only): input and output buffers.
+
+ - csv file: comma-separated values (CSV) files.
+
+ - double: test utility for checking double equivalence (vs. strict
+ equality).
+
+ - encode: base16, base32, base64 and hexadecimal conversions.
+
+ - filename: filename manipulation (avoid dependency on boost).
+
+ - hash: Fowler-Noll-Vo 64 bit hash function.
+
+ - io: test utils for file descriptors and sockets.
+
+ - io utilities (header only): reads and writes integers from / to buffers.
+
+ - labeled values: labeled constants and label constant sets.
+
+ - multi threading manager (in the util library to be available in the
+ whole Kea code).
+
+ - optional: optional values.
+
+ - pid file: process id files.
+
+ - pointer util: test utility to compare smart pointers.
+
+ - process spawn.
+
+ - range utilities.
+
+ - read-write mutex (header only).
+
+ - signal set: signal handling (please use @c isc::asiolink::IOSignalSet
+ instead).
+
+ - staged values.
+
+ - state model: event-driven deterministic finite state machine.
+
+ - stop watch: to measure code execution time.
+
+ - string util: various string common tools.
+
+ - thread pool.
+
+ - time utilities: DNSSEC time conversions from and to text.
+
+ - unittests (directory): tools for google test unit tests.
+
+ - unlock guard: RAII helper to unlock a mutex in a limited scope.
+
+ - versioned csv file: csv files with multiple versions of file schema.
+
+ - watched socket (required as select() or poll() do not support condition
+ variables).
+
+ - watched threads: threads using ready, error and terminate watched socket.
+
+@section utilMTConsiderations Multi-Threading Consideration for Utilities
+
+By default utilities are not thread safe, for instance CSV files and
+qid random generators are not thread safe. Exceptions are:
+
+ - multi threading manager is thread safe.
+
+ - read-write mutex is thread safe.
+
+ - state model is thread safe.
+
+ - thread pool is thread safe.
+
+ - unlock guard is thread safe.
+
+ - watched threads are thread safe.
+
+*/
diff --git a/src/lib/util/versioned_csv_file.cc b/src/lib/util/versioned_csv_file.cc
new file mode 100644
index 0000000..8c48f66
--- /dev/null
+++ b/src/lib/util/versioned_csv_file.cc
@@ -0,0 +1,247 @@
+// Copyright (C) 2015-2021 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/versioned_csv_file.h>
+
+namespace isc {
+namespace util {
+
+VersionedCSVFile::VersionedCSVFile(const std::string& filename)
+ : CSVFile(filename), columns_(0), valid_column_count_(0),
+ minimum_valid_columns_(0), input_header_count_(0),
+ input_schema_state_(CURRENT) {
+}
+
+VersionedCSVFile::~VersionedCSVFile() {
+}
+
+void
+VersionedCSVFile::addColumn(const std::string& name,
+ const std::string& version,
+ const std::string& default_value) {
+ CSVFile::addColumn(name);
+ columns_.push_back(VersionedColumnPtr(new VersionedColumn(name, version,
+ default_value)));
+}
+
+void
+VersionedCSVFile::setMinimumValidColumns(const std::string& column_name) {
+ try {
+ int index = getColumnIndex(column_name);
+ minimum_valid_columns_ = index + 1;
+
+ } catch (...) {
+ isc_throw(VersionedCSVFileError,
+ "setMinimumValidColumns: " << column_name << " is not "
+ "defined");
+ }
+}
+
+size_t
+VersionedCSVFile::getMinimumValidColumns() const {
+ return (minimum_valid_columns_);
+}
+
+size_t
+VersionedCSVFile::getValidColumnCount() const {
+ return (valid_column_count_);
+}
+
+size_t
+VersionedCSVFile::getInputHeaderCount() const {
+ return (input_header_count_);
+}
+
+void
+VersionedCSVFile::open(const bool seek_to_end) {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "no schema has been defined, cannot open CSV file :"
+ << getFilename());
+ }
+
+ CSVFile::open(seek_to_end);
+}
+
+void
+VersionedCSVFile::recreate() {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "no schema has been defined, cannot create CSV file :"
+ << getFilename());
+ }
+
+ CSVFile::recreate();
+ // For new files they always match.
+ input_header_count_ = valid_column_count_ = getColumnCount();
+}
+
+VersionedCSVFile::InputSchemaState
+VersionedCSVFile::getInputSchemaState() const {
+ return (input_schema_state_);
+}
+
+bool
+VersionedCSVFile::needsConversion() const {
+ return (input_schema_state_ != CURRENT);
+}
+
+std::string
+VersionedCSVFile::getInputSchemaVersion() const {
+ if (getValidColumnCount() > 0) {
+ return (getVersionedColumn(getValidColumnCount() - 1)->version_);
+ }
+
+ return ("undefined");
+}
+
+std::string
+VersionedCSVFile::getSchemaVersion() const {
+ if (getColumnCount() > 0) {
+ return (getVersionedColumn(getColumnCount() - 1)->version_);
+ }
+
+ return ("undefined");
+}
+
+const VersionedColumnPtr&
+VersionedCSVFile::getVersionedColumn(const size_t index) const {
+ if (index >= getColumnCount()) {
+ isc_throw(isc::OutOfRange, "versioned column index " << index
+ << " out of range; CSV file : " << getFilename()
+ << " only has " << getColumnCount() << " columns ");
+ }
+
+ return (columns_[index]);
+}
+
+bool
+VersionedCSVFile::next(CSVRow& row) {
+ setReadMsg("success");
+ // Use base class to physical read the row, but skip its row
+ // validation
+ CSVFile::next(row, true);
+ if (row == CSVFile::EMPTY_ROW()) {
+ return(true);
+ }
+
+ bool row_valid = true;
+ switch(getInputSchemaState()) {
+ case CURRENT:
+ // All rows must match than the current schema
+ if (row.getValuesCount() != getColumnCount()) {
+ columnCountError(row, "must match current schema");
+ row_valid = false;
+ }
+ break;
+
+ case NEEDS_UPGRADE:
+ // The input header met the minimum column count but
+ // is less than the current schema so:
+ // Rows must not be shorter than the valid column count
+ // and not longer than the current schema
+ if (row.getValuesCount() < getValidColumnCount()) {
+ columnCountError(row, "too few columns to upgrade");
+ row_valid = false;
+ } else if (row.getValuesCount() > getColumnCount()) {
+ columnCountError(row, "too many columns to upgrade");
+ row_valid = false;
+ } else {
+ // Add any missing values
+ for (size_t index = row.getValuesCount();
+ index < getColumnCount(); ++index) {
+ row.append(columns_[index]->default_value_);
+ }
+ }
+ break;
+
+ case NEEDS_DOWNGRADE:
+ // The input header exceeded current schema so:
+ // Rows may be as long as input header but not shorter than
+ // the current schema
+ if (row.getValuesCount() < getColumnCount()) {
+ columnCountError(row, "too few columns to downgrade");
+ } else if (row.getValuesCount() > getInputHeaderCount()) {
+ columnCountError(row, "too many columns to downgrade");
+ } else {
+ // Toss any the extra columns
+ row.trim(row.getValuesCount() - getColumnCount());
+ }
+ break;
+ }
+
+ return (row_valid);
+}
+
+void
+VersionedCSVFile::columnCountError(const CSVRow& row,
+ const std::string& reason) {
+ std::ostringstream s;
+ s << "Invalid number of columns: "
+ << row.getValuesCount() << " in row: '" << row
+ << "', file: '" << getFilename() << "' : " << reason;
+ setReadMsg(s.str());
+}
+
+bool
+VersionedCSVFile::validateHeader(const CSVRow& header) {
+ if (getColumnCount() == 0) {
+ isc_throw(VersionedCSVFileError,
+ "cannot validate header, no schema has been defined");
+ }
+
+ input_header_count_ = header.getValuesCount();
+
+ // Iterate over the number of columns in the header, testing
+ // each against the defined column in the same position.
+ // If there is a mismatch, bail.
+ size_t i = 0;
+ for ( ; i < getInputHeaderCount() && i < getColumnCount(); ++i) {
+ if (getColumnName(i) != header.readAt(i)) {
+ std::ostringstream s;
+ s << " - header contains an invalid column: '"
+ << header.readAt(i) << "'";
+ setReadMsg(s.str());
+ return (false);
+ }
+ }
+
+ // If we found too few valid columns, then we cannot convert this
+ // file. It's too old, too corrupt, or not a Kea file.
+ if (i < getMinimumValidColumns()) {
+ std::ostringstream s;
+ s << " - header has only " << i << " valid column(s), "
+ << "it must have at least " << getMinimumValidColumns();
+ setReadMsg(s.str());
+ return (false);
+ }
+
+ // Remember the number of valid columns we found. When this number
+ // is less than the number of defined columns, then we have an older
+ // version of the lease file. We'll need this value to validate
+ // and upgrade data rows.
+ valid_column_count_ = i;
+
+ if (getValidColumnCount() < getColumnCount()) {
+ input_schema_state_ = NEEDS_UPGRADE;
+ } else if (getInputHeaderCount() > getColumnCount()) {
+ // If there are more values in the header than defined columns
+ // then, we'll drop the extra. This allows someone to attempt to
+ // downgrade if need be.
+ input_schema_state_ = NEEDS_DOWNGRADE;
+ std::ostringstream s;
+ s << " - header has " << getInputHeaderCount() - getColumnCount()
+ << " extra column(s), these will be ignored";
+ setReadMsg(s.str());
+ }
+
+ return (true);
+}
+
+} // end of isc::util namespace
+} // end of isc namespace
diff --git a/src/lib/util/versioned_csv_file.h b/src/lib/util/versioned_csv_file.h
new file mode 100644
index 0000000..cfd18d9
--- /dev/null
+++ b/src/lib/util/versioned_csv_file.h
@@ -0,0 +1,317 @@
+// Copyright (C) 2015,2017 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 VERSIONED_CSV_FILE_H
+#define VERSIONED_CSV_FILE_H
+
+#include <util/csv_file.h>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown when an error occurs during CSV file processing.
+class VersionedCSVFileError : public Exception {
+public:
+ VersionedCSVFileError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Contains the metadata for a single column in a file.
+class VersionedColumn {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name Name of the column.
+ /// @param version Text representation of the schema version in which
+ /// this column first appeared.
+ /// @param default_value The value the column should be assigned if it
+ /// is not present in a data row. It defaults to an empty string, ""
+ VersionedColumn(const std::string& name, const std::string& version,
+ const std::string& default_value = "")
+ : name_(name), version_(version), default_value_(default_value) {
+ };
+
+ /// @brief Destructor
+ virtual ~VersionedColumn(){};
+
+ /// @brief Name of the column.
+ std::string name_;
+
+ /// @brief Text representation of the schema version in which
+ /// this column first appeared.
+ std::string version_;
+
+ /// @brief default_value The value the column should be assigned if it
+ /// is not present in a data row.
+ std::string default_value_;
+};
+
+/// @brief Defines a smart pointer to VersionedColumn
+typedef boost::shared_ptr<VersionedColumn> VersionedColumnPtr;
+
+/// @brief Implements a CSV file that supports multiple versions of
+/// the file's "schema". This allows files with older schemas to be
+/// upgraded to newer schemas as they are being read. The file's schema
+/// is defined through a list of column descriptors, or @ref
+/// isc::util::VersionedColumn(s). Each descriptor contains metadata describing
+/// the column, consisting of the column's name, the version label in which
+/// the column was added to the schema, and a default value to be used if the
+/// column is missing from the file. Note that the column descriptors are
+/// defined in the order they occur in the file, when reading a row from left
+/// to right. This also assumes that when new version of the schema evolves,
+/// all new columns are added at the end of the row. In other words, the
+/// order of the columns reflects not only the order in which they occur
+/// in a row but also the order they were added to the schema. Conceptually,
+/// the entire list of columns defined constitutes the current schema. Earlier
+/// schema versions are therefore subsets of this list. Creating the schema
+/// is done by calling VersionedCSVfile::addColumn() for each column. Note
+/// that the schema must be defined prior to opening the file.
+///
+/// The first row of the file is always the header row and is a comma-separated
+/// list of the names of the column in the file. This row is used when
+/// opening the file via @ref VersionedCSVFile::open(), to identify its schema
+/// version so that it may be be read correctly. This is done by comparing
+/// the column found in the header to the columns defined in the schema. The
+/// columns must match both by name and the order in which they occur.
+///
+/// -# If there are fewer columns in the header than in the schema, the file
+/// is presumed to be an earlier schema version and will be upgraded as it is
+/// read. There is an ability to mark a specific column as being the minimum
+/// column which must be present, see @ref VersionedCSVFile::setMinimumValidColumns().
+/// If the header columns do not match up to this
+/// minimum column, the file is presumed to be too old to upgrade and the
+/// open will fail. A valid, upgradable file will have an input schema
+/// state of VersionedCSVFile::NEEDS_UPGRADE.
+///
+/// -# If there is a mismatch between a found column name and the column name
+/// defined for that position in the row, the file is presumed to be invalid
+/// and the open will fail.
+///
+/// -# If the content of the header matches exactly the columns defined in
+/// the schema, the file is considered to match the schema exactly and the
+/// input schema state will VersionedCSVFile::CURRENT.
+///
+/// -# If there columns in the header beyond all of the columns defined in
+/// the schema (i.e the schema is a subset of the header), then the file
+/// is presumed to be from a newer version of Kea and can be downgraded. The
+/// input schema state fo the file will be set to
+/// VersionedCSVFile::NEEDS_DOWNGRADE.
+///
+/// After successfully opening a file, rows are read one at a time via
+/// @ref VersionedCSVFile::next() and handled according to the input schema
+/// state. Each data row is expected to have at least the same number of
+/// columns as were found in the header. Any row which as fewer values is
+/// discarded as invalid. Similarly, any row which is found to have more
+/// values than were found in the header is discarded as invalid.
+///
+/// When upgrading a row, the values for each missing column is filled in
+/// with the default value specified by that column's descriptor. When
+/// downgrading a row, extraneous values are dropped from the row.
+///
+/// It is important to note that upgrading or downgrading a file does NOT
+/// alter the physical file itself. Rather the conversion occurs after the
+/// raw data has been read but before it is passed to caller.
+///
+/// Also note that there is currently no support for writing out a file in
+/// anything other than the current schema.
+class VersionedCSVFile : public CSVFile {
+public:
+
+ /// @brief Possible input file schema states.
+ /// Used to categorize the input file's schema, relative to the defined
+ /// schema.
+ enum InputSchemaState {
+ CURRENT,
+ NEEDS_UPGRADE,
+ NEEDS_DOWNGRADE
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param filename CSV file name.
+ VersionedCSVFile(const std::string& filename);
+
+ /// @brief Destructor
+ virtual ~VersionedCSVFile();
+
+ /// @brief Adds metadata for a single column to the schema.
+ ///
+ /// This method appends a new column description to the file's schema.
+ /// Note this does not cause anything to be written to the physical file.
+ /// The name of the column will be placed in the CSV header when new file
+ /// is created by calling @c recreate or @c open function.
+ ///
+ /// @param col_name Name of the column.
+ /// @param version Text representation of the schema version in which
+ /// this column first appeared.
+ /// @param default_value value the missing column should be given during
+ /// an upgrade. It defaults to an empty string, ""
+ ///
+ /// @throw CSVFileError if a column with the specified name exists.
+ void addColumn(const std::string& col_name, const std::string& version,
+ const std::string& default_value = "");
+
+ /// @brief Sets the minimum number of valid columns based on a given column
+ ///
+ /// @param column_name Name of the column which positionally represents
+ /// the minimum columns which must be present in a file and to be
+ /// considered valid.
+ void setMinimumValidColumns(const std::string& column_name);
+
+ /// @brief Returns the minimum number of columns which must be present
+ /// for the file to be considered valid.
+ size_t getMinimumValidColumns() const;
+
+ /// @brief Returns the number of columns found in the input header
+ size_t getInputHeaderCount() const;
+
+ /// @brief Returns the number of valid columns found in the header
+ /// For newly created files this will always match the number of defined
+ /// columns (i.e. getColumnCount()). For existing files, this will be
+ /// the number of columns in the header that match the defined columns.
+ /// When this number is less than getColumnCount() it means the input file
+ /// is from an earlier schema. This value is zero until the file has
+ /// been opened.
+ size_t getValidColumnCount() const;
+
+ /// @brief Opens existing file or creates a new one.
+ ///
+ /// This function will try to open existing file if this file has size
+ /// greater than 0. If the file doesn't exist or has size of 0, the
+ /// file is recreated. If the existing file has been opened, the header
+ /// is parsed and and validated against the schema.
+ /// By default, the data pointer in the file is set to the beginning of
+ /// the first data row. In order to retrieve the row contents the @c next
+ /// function should be called. If a @c seek_to_end parameter is set to
+ /// true, the file will be opened and the internal pointer will be set
+ /// to the end of file.
+ ///
+ /// @param seek_to_end A boolean value which indicates if the input and
+ /// output file pointer should be set at the end of file.
+ ///
+ /// @throw VersionedCSVFileError if schema has not been defined,
+ /// CSVFileError when IO operation fails, or header fails to validate.
+ virtual void open(const bool seek_to_end = false);
+
+ /// @brief Creates a new CSV file.
+ ///
+ /// The file creation will fail if there are no columns specified.
+ /// Otherwise, this function will write the header to the file.
+ /// In order to write rows to opened file, the @c append function
+ /// should be called.
+ ///
+ /// @throw VersionedCSVFileError if schema has not been defined
+ /// CSVFileError if an IO operation fails
+ virtual void recreate();
+
+ /// @brief Reads next row from the file file.
+ ///
+ /// This function will return the @c CSVRow object representing a
+ /// parsed row if parsing is successful. If the end of file has been
+ /// reached, the empty row is returned (a row containing no values).
+ ///
+ /// 1. If the row has fewer values than were found in the header it is
+ /// discarded as invalid.
+ ///
+ /// 2. If the row is found to have more values than are defined in the
+ /// schema it is discarded as invalid
+ ///
+ /// When a valid row has fewer than the defined number of columns, the
+ /// values for each missing column is filled in with the default value
+ /// specified by that column's descriptor.
+ ///
+ /// @param [out] row Object receiving the parsed CSV file.
+ ///
+ /// @return true if row has been read and validated; false if validation
+ /// failed.
+ bool next(CSVRow& row);
+
+ /// @brief Returns the schema version of the physical file
+ ///
+ /// @return text version of the schema found or string "undefined" if the
+ /// file has not been opened
+ std::string getInputSchemaVersion() const;
+
+ /// @brief text version of current schema supported by the file's metadata
+ ///
+ /// @return text version info assigned to the last column in the list of
+ /// defined column, or the string "undefined" if no columns have been
+ /// defined.
+ std::string getSchemaVersion() const;
+
+ /// @brief Fetch the column descriptor for a given index
+ ///
+ /// @param index index within the list of columns of the desired column
+ /// @return a pointer to the VersionedColumn at the given index
+ /// @throw OutOfRange exception if the index is invalid
+ const VersionedColumnPtr& getVersionedColumn(const size_t index) const;
+
+ /// @brief Fetches the state of the input file's schema
+ ///
+ /// Reflects that state of the input file's schema relative to the
+ /// defined schema as a enum, InputSchemaState.
+ ///
+ /// @return VersionedCSVFile::CURRENT if the input file schema matches
+ /// the defined schema, NEEDS_UPGRADE if the input file schema is older,
+ /// and NEEDS_DOWNGRADE if it is newer
+ enum InputSchemaState getInputSchemaState() const;
+
+ /// @brief Returns true if the input file schema state is not CURRENT
+ bool needsConversion() const;
+
+protected:
+
+ /// @brief Validates the header of a VersionedCSVFile
+ ///
+ /// This function is called internally when the reading in an existing
+ /// file. It parses the header row of the file, comparing each value
+ /// in succession against the defined list of columns. If the header
+ /// contains too few matching columns (i.e. less than @c
+ /// minimum_valid_columns_) or too many (more than the number of defined
+ /// columns), the file is presumed to be either too old, too new, or too
+ /// corrupt to process. Otherwise it retains the number of valid columns
+ /// found and deems the header valid.
+ ///
+ /// @param header A row holding a header.
+ /// @return true if header matches the columns; false otherwise.
+ virtual bool validateHeader(const CSVRow& header);
+
+ /// @brief Convenience method for adding an error message
+ ///
+ /// Constructs an error message indicating that the number of columns
+ /// in a given row are wrong and why, then adds it readMsg.
+ ///
+ /// @param row The row in error
+ /// @param reason An explanation as to why the row column count is wrong
+ void columnCountError(const CSVRow& row, const std::string& reason);
+
+private:
+ /// @brief Holds the collection of column descriptors
+ std::vector<VersionedColumnPtr> columns_;
+
+ /// @brief Number of valid columns present in input file. If this is less
+ /// than the number of columns defined, this implies the input file is
+ /// from an earlier version of the code.
+ size_t valid_column_count_;
+
+ /// @brief Minimum number of valid columns an input file must contain.
+ /// If an input file does not meet this number it cannot be upgraded.
+ size_t minimum_valid_columns_;
+
+ /// @brief The number of columns found in the input header row
+ /// This value represent the number of columns present, in the header
+ /// valid or otherwise.
+ size_t input_header_count_;
+
+ /// @brief The state of the input schema in relation to the current schema
+ enum InputSchemaState input_schema_state_;
+};
+
+
+} // namespace isc::util
+} // namespace isc
+
+#endif // VERSIONED_CSV_FILE_H
diff --git a/src/lib/util/watch_socket.cc b/src/lib/util/watch_socket.cc
new file mode 100644
index 0000000..6ffb396
--- /dev/null
+++ b/src/lib/util/watch_socket.cc
@@ -0,0 +1,165 @@
+// Copyright (C) 2014-2020 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/.
+
+/// @file watch_socket.cc
+
+#include <config.h>
+
+//#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <util/watch_socket.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sstream>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+namespace isc {
+namespace util {
+
+
+const int WatchSocket::SOCKET_NOT_VALID;
+const uint32_t WatchSocket::MARKER;
+
+WatchSocket::WatchSocket()
+ : source_(SOCKET_NOT_VALID), sink_(SOCKET_NOT_VALID) {
+ // Open the pipe.
+ int fds[2];
+ if (pipe(fds)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot construct pipe: " << errstr);
+ }
+
+ source_ = fds[1];
+ sink_ = fds[0];
+
+ if (fcntl(source_, F_SETFD, FD_CLOEXEC)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot set source to close-on-exec: "
+ << errstr);
+ }
+
+ if (fcntl(sink_, F_SETFD, FD_CLOEXEC)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot set sink to close-on-exec: "
+ << errstr);
+ }
+
+ if (fcntl(sink_, F_SETFL, O_NONBLOCK)) {
+ const char* errstr = strerror(errno);
+ isc_throw(WatchSocketError, "Cannot set sink to non-blocking: "
+ << errstr);
+ }
+}
+
+WatchSocket::~WatchSocket() {
+ closeSocket();
+}
+
+void
+WatchSocket::markReady() {
+ // Make sure it hasn't been orphaned! Otherwise we may get SIGPIPE. We
+ // use fcntl to check as select() on some systems may show it as ready to
+ // read.
+ if (fcntl(sink_, F_GETFL) < 0) {
+ closeSocket();
+ isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+ " select_fd was closed!");
+ }
+
+ if (!isReady()) {
+ int nbytes = write (source_, &MARKER, sizeof(MARKER));
+ if (nbytes != sizeof(MARKER)) {
+ // If there's an error get the error message than close
+ // the pipe. This should ensure any further use of the socket
+ // or testing the fd with select_fd will fail.
+ const char* errstr = strerror(errno);
+ closeSocket();
+ isc_throw(WatchSocketError, "WatchSocket markReady failed:"
+ << " bytes written: " << nbytes << " : " << errstr);
+ }
+ }
+}
+
+bool
+WatchSocket::isReady() {
+ // Report it as not ready rather than error here.
+ if (sink_ == SOCKET_NOT_VALID) {
+ return (false);
+ }
+
+ // Use ioctl FIONREAD vs polling select as it is faster.
+ int len;
+ int result = ioctl(sink_, FIONREAD, &len);
+ // Return true only if read ready, treat error same as not ready.
+ return ((result == 0) && (len > 0));
+}
+
+void
+WatchSocket::clearReady() {
+ if (isReady()) {
+ uint32_t buf = 0;
+ int nbytes = read (sink_, &buf, sizeof(buf));
+ if ((nbytes != sizeof(MARKER) || (buf != MARKER))) {
+ // If there's an error get the error message than close
+ // the pipe. This should ensure any further use of the socket
+ // or testing the fd with select_fd will fail.
+ const char* errstr = strerror(errno);
+ closeSocket();
+ isc_throw(WatchSocketError, "WatchSocket clearReady failed: "
+ "bytes read: " << nbytes << " : "
+ "value read: " << buf << " error :" << errstr);
+ }
+ }
+}
+
+bool
+WatchSocket::closeSocket(std::string& error_string) {
+ std::ostringstream s;
+ // Close the pipe fds. Technically a close can fail (hugely unlikely)
+ // but there's no recovery for it either. If one does fail we log it
+ // and go on. Plus this is called by the destructor and no one likes
+ // destructors that throw.
+ if (source_ != SOCKET_NOT_VALID) {
+ if (close(source_)) {
+ // An error occurred.
+ s << "Could not close source: " << strerror(errno);
+ }
+
+ source_ = SOCKET_NOT_VALID;
+ }
+
+ if (sink_ != SOCKET_NOT_VALID) {
+ if (close(sink_)) {
+ // An error occurred.
+ if (error_string.empty()) {
+ s << "could not close sink: " << strerror(errno);
+ }
+ }
+
+ sink_ = SOCKET_NOT_VALID;
+ }
+
+ error_string = s.str();
+
+ // If any errors have been reported, return false.
+ return (error_string.empty() ? true : false);
+}
+
+void
+WatchSocket::closeSocket() {
+ std::string error_string;
+ closeSocket(error_string);
+}
+
+int
+WatchSocket::getSelectFd() {
+ return (sink_);
+}
+
+} // namespace isc::util
+} // namespace isc
diff --git a/src/lib/util/watch_socket.h b/src/lib/util/watch_socket.h
new file mode 100644
index 0000000..e6d65f6
--- /dev/null
+++ b/src/lib/util/watch_socket.h
@@ -0,0 +1,143 @@
+// Copyright (C) 2014-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/.
+
+#ifndef WATCH_SOCKET_H
+#define WATCH_SOCKET_H
+
+/// @file watch_socket.h Defines the class, WatchSocket.
+
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class WatchSocketError : public isc::Exception {
+public:
+ WatchSocketError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Provides an IO "ready" semaphore for use with select() or poll()
+/// WatchSocket exposes a single open file descriptor, the "select-fd" which
+/// can be marked as being ready to read (i.e. !EWOULDBLOCK) and cleared
+/// (i.e. EWOULDBLOCK). The select-fd can be used with select(), poll(), or
+/// their variants alongside other file descriptors.
+///
+/// Internally, WatchSocket uses a pipe. The select-fd is the "read" end of
+/// pipe. To mark the socket as ready to read, an integer marker is written
+/// to the pipe. To clear the socket, the marker is read from the pipe. Note
+/// that WatchSocket will only write the marker if it is not already marked.
+/// This prevents the socket's pipe from filling endlessly.
+///
+/// @warning Because the read or "sink" side of the pipe is used as the select_fd,
+/// it is possible for that fd to be interfered with, albeit only from within the
+/// process space which owns it. Performing operations that may alter the fd's state
+/// such as close, read, or altering behavior flags with fcntl or ioctl can have
+/// unpredictable results. It is intended strictly use with functions such as select()
+/// poll() or their variants.
+class WatchSocket : public boost::noncopyable {
+public:
+ /// @brief Value used to signify an invalid descriptor.
+ static const int SOCKET_NOT_VALID = -1;
+ /// @brief Value written to the source when marking the socket as ready.
+ /// The value itself is arbitrarily chosen as one that is unlikely to occur
+ /// otherwise and easy to debug.
+ static const uint32_t MARKER = 0xDEADBEEF;
+
+ /// @brief Constructor
+ ///
+ /// Constructs an instance of the WatchSocket in the cleared (EWOULDBLOCK)
+ /// state.
+ WatchSocket();
+
+ /// @brief Destructor
+ ///
+ /// Closes all internal resources, including the select-fd.
+ virtual ~WatchSocket();
+
+ /// @brief Marks the select-fd as ready to read.
+ ///
+ /// Marks the socket as ready to read, if is not already so marked.
+ /// If an error occurs, closeSocket is called. This will force any further
+ /// use of the select_fd to fail rather than show the fd as READY. Such
+ /// an error is almost surely a programmatic error which has corrupted the
+ /// select_fd.
+ ///
+ /// @throw WatchSocketError if an error occurs marking the socket.
+ void markReady();
+
+ /// @brief Returns true the if socket is marked as ready.
+ ///
+ /// This method uses a non-blocking call to select() to test read state of the
+ /// select_fd. Rather than track what the status "should be" it tests the status.
+ /// This should eliminate conditions where the select-fd appear to be perpetually
+ /// ready.
+ /// @return Returns true if select_fd is not SOCKET_NOT_VALID and select() reports it
+ /// as !EWOULDBLOCK, otherwise it returns false.
+ /// This method is guaranteed NOT to throw.
+ bool isReady();
+
+ /// @brief Clears the socket's ready to read marker.
+ ///
+ /// Clears the socket if it is currently marked as ready to read.
+ /// If an error occurs, closeSocket is called. This will force any further
+ /// use of the select_fd to fail rather than show the fd as READY. Such
+ /// an error is almost surely a programmatic error which has corrupted the
+ /// select_fd.
+ ///
+ /// @throw WatchSocketError if an error occurs clearing the socket
+ /// marker.
+ void clearReady();
+
+ /// @brief Returns the file descriptor to use to monitor the socket.
+ ///
+ /// @note Using this file descriptor as anything other than an argument
+ /// to select() or similar methods can have unpredictable results.
+ ///
+ /// @return The file descriptor associated with read end of the socket's
+ /// pipe.
+ int getSelectFd();
+
+ /// @brief Closes the descriptors associated with the socket.
+ ///
+ /// This method is used to close the socket and capture errors that
+ /// may occur during this operation.
+ ///
+ /// @param [out] error_string Holds the error string if closing
+ /// the socket failed. It will hold empty string otherwise.
+ ///
+ /// @return true if the operation was successful, false otherwise.
+ bool closeSocket(std::string& error_string);
+
+private:
+
+ /// @brief Closes the descriptors associated with the socket.
+ ///
+ /// This method is called by the class destructor and it ignores
+ /// any errors that may occur while closing the sockets.
+ void closeSocket();
+
+ /// @brief The end of the pipe to which the marker is written
+ int source_;
+
+ /// @brief The end of the pipe from which the marker is read.
+ /// This is the value returned as the select-fd.
+ int sink_;
+};
+
+/// @brief Defines a smart pointer to an instance of a WatchSocket.
+typedef boost::shared_ptr<WatchSocket> WatchSocketPtr;
+
+} // namespace isc::util
+} // namespace isc
+
+#endif
diff --git a/src/lib/util/watched_thread.cc b/src/lib/util/watched_thread.cc
new file mode 100644
index 0000000..e9c3468
--- /dev/null
+++ b/src/lib/util/watched_thread.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2018-2021 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/watched_thread.h>
+#include <signal.h>
+
+namespace isc {
+namespace util {
+
+void
+WatchedThread::start(const std::function<void()>& thread_main) {
+ clearReady(ERROR);
+ clearReady(READY);
+ clearReady(TERMINATE);
+ setErrorInternal("no error");
+ // Protect us against signals
+ sigset_t sset;
+ sigset_t osset;
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGCHLD);
+ sigaddset(&sset, SIGINT);
+ sigaddset(&sset, SIGHUP);
+ sigaddset(&sset, SIGTERM);
+ pthread_sigmask(SIG_BLOCK, &sset, &osset);
+ try {
+ thread_.reset(new std::thread(thread_main));
+ } catch (...) {
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+ throw;
+ }
+ // Restore signal mask.
+ pthread_sigmask(SIG_SETMASK, &osset, 0);
+}
+
+int
+WatchedThread::getWatchFd(WatchType watch_type) {
+ return(sockets_[watch_type].getSelectFd());
+}
+
+void
+WatchedThread::markReady(WatchType watch_type) {
+ sockets_[watch_type].markReady();
+}
+
+bool
+WatchedThread::isReady(WatchType watch_type) {
+ return (sockets_[watch_type].isReady());
+}
+
+void
+WatchedThread::clearReady(WatchType watch_type) {
+ sockets_[watch_type].clearReady();
+}
+
+bool
+WatchedThread::shouldTerminate() {
+ if (sockets_[TERMINATE].isReady()) {
+ clearReady(TERMINATE);
+ return (true);
+ }
+
+ return (false);
+}
+
+void
+WatchedThread::stop() {
+ if (thread_) {
+ markReady(TERMINATE);
+ thread_->join();
+ thread_.reset();
+ }
+
+ clearReady(ERROR);
+ clearReady(READY);
+ setErrorInternal("thread stopped");
+}
+
+void
+WatchedThread::setErrorInternal(const std::string& error_msg) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ last_error_ = error_msg;
+}
+
+void
+WatchedThread::setError(const std::string& error_msg) {
+ setErrorInternal(error_msg);
+ markReady(ERROR);
+}
+
+std::string
+WatchedThread::getLastError() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return (last_error_);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/watched_thread.h b/src/lib/util/watched_thread.h
new file mode 100644
index 0000000..47b7264
--- /dev/null
+++ b/src/lib/util/watched_thread.h
@@ -0,0 +1,145 @@
+// Copyright (C) 2018-2020 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 WATCHED_THREAD_H
+#define WATCHED_THREAD_H
+
+#include <util/watch_socket.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <functional>
+#include <mutex>
+#include <thread>
+
+namespace isc {
+namespace util {
+
+/// @brief Thread pointer type.
+typedef boost::shared_ptr<std::thread> ThreadPtr;
+
+/// @brief Provides a thread and controls for monitoring its activities
+///
+/// Given a "worker function", this class creates a thread which
+/// runs the function and provides the means to monitor the thread
+/// for "error" and "ready" conditions, and finally to stop the thread.
+/// It uses three WatchSockets: one to indicate an error, one to indicate
+/// data is ready, and a third to monitor as a shut-down command.
+class WatchedThread {
+public:
+ /// @brief Enumerates the list of watch sockets used to mark events
+ /// These are used as arguments to watch socket accessor methods.
+ enum WatchType {
+ ERROR = 0,
+ READY = 1,
+ TERMINATE = 2
+ };
+
+ /// @brief Constructor
+ WatchedThread(){};
+
+ /// @brief Virtual destructor
+ virtual ~WatchedThread(){}
+
+ /// @brief Fetches the fd of a watch socket
+ ///
+ /// @param watch_type indicates which watch socket
+ /// @return the watch socket's file descriptor
+ int getWatchFd(WatchType watch_type);
+
+ /// @brief Sets a watch socket state to ready
+ ///
+ /// @param watch_type indicates which watch socket to mark
+ void markReady(WatchType watch_type);
+
+ /// @brief Indicates if a watch socket state is ready
+ ///
+ /// @param watch_type indicates which watch socket to mark
+ /// @return true if the watch socket is ready, false otherwise
+ bool isReady(WatchType watch_type);
+
+ /// @brief Sets a watch socket state to not ready
+ ///
+ /// @param watch_type indicates which watch socket to clear
+ void clearReady(WatchType watch_type);
+
+ /// @brief Checks if the thread should terminate
+ ///
+ /// Performs a "one-shot" check of the terminate watch socket.
+ /// If it is ready, return true and then clear it, otherwise
+ /// return false.
+ ///
+ /// @return true if the terminate watch socket is ready
+ bool shouldTerminate();
+
+ /// @brief Creates and runs the thread.
+ ///
+ /// Creates the thread, passing into it the given function to run.
+ ///
+ /// @param thread_main function the thread should run
+ void start(const std::function<void()>& thread_main);
+
+ /// @brief Returns true if the thread is running
+ bool isRunning() {
+ return (thread_ != 0);
+ }
+
+ /// @brief Terminates the thread
+ ///
+ /// It marks the terminate watch socket ready, and then waits for the
+ /// thread to stop. At this point, the thread is defunct. This is
+ /// not done in the destructor to avoid race conditions.
+ void stop();
+
+ /// @brief Sets the error state
+ ///
+ /// This records the given error message and sets the error watch
+ /// socket to ready.
+ ///
+ /// @param error_msg to be set as last error
+ void setError(const std::string& error_msg);
+
+ /// @brief Fetches the error message text for the most recent error
+ ///
+ /// @return string containing the error message
+ std::string getLastError();
+
+private:
+
+ /// @brief Sets the error state thread safe
+ ///
+ /// This records the given error message
+ ///
+ /// @param error_msg to be set as last error
+ void setErrorInternal(const std::string& error_msg);
+
+ /// @brief Error message of the last error encountered
+ std::string last_error_;
+
+ /// @brief Mutex to protect internal state
+ std::mutex mutex_;
+
+ /// @brief WatchSockets that are used to communicate with the owning thread
+ /// There are three:
+ /// -# ERROR - Marked as ready by the thread when it experiences an error.
+ /// -# READY - Marked as ready by the thread when it needs attention for a normal event
+ /// (e.g. a thread used to receive data would mark READY when it has data available)
+ /// -# TERMINATE - Marked as ready by WatchedThread owner to instruct the thread to
+ /// terminate. Worker functions must monitor TERMINATE by periodically calling
+ /// @c shouldTerminate
+ WatchSocket sockets_[TERMINATE + 1];
+
+ /// @brief Current thread instance
+ ThreadPtr thread_ ;
+};
+
+/// @brief Defines a pointer to a WatchedThread
+typedef boost::shared_ptr<WatchedThread> WatchedThreadPtr;
+
+} // namespace util
+} // namespace isc
+
+#endif // WATCHED_THREAD_H
diff --git a/src/lib/yang/Makefile.am b/src/lib/yang/Makefile.am
new file mode 100644
index 0000000..123f004
--- /dev/null
+++ b/src/lib/yang/Makefile.am
@@ -0,0 +1,83 @@
+SUBDIRS = . testutils pretests tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(LIBYANG_CPPFLAGS)
+AM_CPPFLAGS += $(LIBYANG_INCLUDEDIR)
+AM_CPPFLAGS += $(LIBYANGCPP_CPPFLAGS)
+AM_CPPFLAGS += $(LIBYANGCPP_INCLUDEDIR)
+AM_CPPFLAGS += $(SYSREPO_CPPFLAGS)
+AM_CPPFLAGS += $(SYSREPO_INCLUDEDIR)
+AM_CPPFLAGS += $(SYSREPOCPP_CPPFLAGS)
+AM_CPPFLAGS += $(SYSREPOCPP_INCLUDEDIR)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+lib_LTLIBRARIES = libkea-yang.la
+libkea_yang_la_SOURCES = adaptor.cc adaptor.h
+libkea_yang_la_SOURCES += adaptor_host.cc adaptor_host.h
+libkea_yang_la_SOURCES += adaptor_pool.cc adaptor_pool.h
+libkea_yang_la_SOURCES += adaptor_option.cc adaptor_option.h
+libkea_yang_la_SOURCES += adaptor_subnet.cc adaptor_subnet.h
+libkea_yang_la_SOURCES += adaptor_config.cc adaptor_config.h
+libkea_yang_la_SOURCES += netconf_error.h
+libkea_yang_la_SOURCES += translator.cc translator.h
+libkea_yang_la_SOURCES += translator_control_socket.cc
+libkea_yang_la_SOURCES += translator_control_socket.h
+libkea_yang_la_SOURCES += translator_database.cc translator_database.h
+libkea_yang_la_SOURCES += translator_logger.cc translator_logger.h
+libkea_yang_la_SOURCES += translator_option_data.cc
+libkea_yang_la_SOURCES += translator_option_data.h
+libkea_yang_la_SOURCES += translator_option_def.cc
+libkea_yang_la_SOURCES += translator_option_def.h
+libkea_yang_la_SOURCES += translator_class.cc translator_class.h
+libkea_yang_la_SOURCES += translator_pool.cc translator_pool.h
+libkea_yang_la_SOURCES += translator_pd_pool.cc translator_pd_pool.h
+libkea_yang_la_SOURCES += translator_host.cc translator_host.h
+libkea_yang_la_SOURCES += translator_subnet.cc translator_subnet.h
+libkea_yang_la_SOURCES += translator_shared_network.cc
+libkea_yang_la_SOURCES += translator_shared_network.h
+libkea_yang_la_SOURCES += translator_config.cc translator_config.h
+libkea_yang_la_SOURCES += yang_models.h yang_revisions.h
+
+libkea_yang_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_yang_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libkea_yang_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libkea_yang_la_LIBADD += $(LIBYANG_LIBS)
+libkea_yang_la_LIBADD += $(LIBYANGCPP_LIBS)
+libkea_yang_la_LIBADD += $(SYSREPO_LIBS)
+libkea_yang_la_LIBADD += $(SYSREPOCPP_LIBS)
+
+libkea_yang_la_LDFLAGS = -no-undefined -version-info 46:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_yang_includedir = $(pkgincludedir)/yang
+libkea_yang_include_HEADERS = \
+ adaptor.h \
+ adaptor_config.h \
+ adaptor_host.h \
+ adaptor_option.h \
+ adaptor_pool.h \
+ adaptor_subnet.h \
+ netconf_error.h \
+ translator.h \
+ translator_class.h \
+ translator_config.h \
+ translator_control_socket.h \
+ translator_database.h \
+ translator_host.h \
+ translator_logger.h \
+ translator_option_data.h \
+ translator_option_def.h \
+ translator_pd_pool.h \
+ translator_pool.h \
+ translator_shared_network.h \
+ translator_subnet.h \
+ yang_models.h \
+ yang_revisions.h
+
+EXTRA_DIST = yang.dox yang_revisions.h.skel
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/yang/Makefile.in b/src/lib/yang/Makefile.in
new file mode 100644
index 0000000..a398af4
--- /dev/null
+++ b/src/lib/yang/Makefile.in
@@ -0,0 +1,1067 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/yang
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 $(libkea_yang_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)$(libkea_yang_includedir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libkea_yang_la_DEPENDENCIES = $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_libkea_yang_la_OBJECTS = adaptor.lo adaptor_host.lo adaptor_pool.lo \
+ adaptor_option.lo adaptor_subnet.lo adaptor_config.lo \
+ translator.lo translator_control_socket.lo \
+ translator_database.lo translator_logger.lo \
+ translator_option_data.lo translator_option_def.lo \
+ translator_class.lo translator_pool.lo translator_pd_pool.lo \
+ translator_host.lo translator_subnet.lo \
+ translator_shared_network.lo translator_config.lo
+libkea_yang_la_OBJECTS = $(am_libkea_yang_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_yang_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_yang_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)/adaptor.Plo \
+ ./$(DEPDIR)/adaptor_config.Plo ./$(DEPDIR)/adaptor_host.Plo \
+ ./$(DEPDIR)/adaptor_option.Plo ./$(DEPDIR)/adaptor_pool.Plo \
+ ./$(DEPDIR)/adaptor_subnet.Plo ./$(DEPDIR)/translator.Plo \
+ ./$(DEPDIR)/translator_class.Plo \
+ ./$(DEPDIR)/translator_config.Plo \
+ ./$(DEPDIR)/translator_control_socket.Plo \
+ ./$(DEPDIR)/translator_database.Plo \
+ ./$(DEPDIR)/translator_host.Plo \
+ ./$(DEPDIR)/translator_logger.Plo \
+ ./$(DEPDIR)/translator_option_data.Plo \
+ ./$(DEPDIR)/translator_option_def.Plo \
+ ./$(DEPDIR)/translator_pd_pool.Plo \
+ ./$(DEPDIR)/translator_pool.Plo \
+ ./$(DEPDIR)/translator_shared_network.Plo \
+ ./$(DEPDIR)/translator_subnet.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_yang_la_SOURCES)
+DIST_SOURCES = $(libkea_yang_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 = $(libkea_yang_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = . testutils pretests tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ $(BOOST_INCLUDES) $(LIBYANG_CPPFLAGS) $(LIBYANG_INCLUDEDIR) \
+ $(LIBYANGCPP_CPPFLAGS) $(LIBYANGCPP_INCLUDEDIR) \
+ $(SYSREPO_CPPFLAGS) $(SYSREPO_INCLUDEDIR) \
+ $(SYSREPOCPP_CPPFLAGS) $(SYSREPOCPP_INCLUDEDIR)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+lib_LTLIBRARIES = libkea-yang.la
+libkea_yang_la_SOURCES = adaptor.cc adaptor.h adaptor_host.cc \
+ adaptor_host.h adaptor_pool.cc adaptor_pool.h \
+ adaptor_option.cc adaptor_option.h adaptor_subnet.cc \
+ adaptor_subnet.h adaptor_config.cc adaptor_config.h \
+ netconf_error.h translator.cc translator.h \
+ translator_control_socket.cc translator_control_socket.h \
+ translator_database.cc translator_database.h \
+ translator_logger.cc translator_logger.h \
+ translator_option_data.cc translator_option_data.h \
+ translator_option_def.cc translator_option_def.h \
+ translator_class.cc translator_class.h translator_pool.cc \
+ translator_pool.h translator_pd_pool.cc translator_pd_pool.h \
+ translator_host.cc translator_host.h translator_subnet.cc \
+ translator_subnet.h translator_shared_network.cc \
+ translator_shared_network.h translator_config.cc \
+ translator_config.h yang_models.h yang_revisions.h
+libkea_yang_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/log/libkea-log.la \
+ $(top_builddir)/src/lib/util/libkea-util.la \
+ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(LIBYANG_LIBS) \
+ $(LIBYANGCPP_LIBS) $(SYSREPO_LIBS) $(SYSREPOCPP_LIBS)
+libkea_yang_la_LDFLAGS = -no-undefined -version-info 46:0:0
+
+# Specify the headers for copying into the installation directory tree.
+libkea_yang_includedir = $(pkgincludedir)/yang
+libkea_yang_include_HEADERS = \
+ adaptor.h \
+ adaptor_config.h \
+ adaptor_host.h \
+ adaptor_option.h \
+ adaptor_pool.h \
+ adaptor_subnet.h \
+ netconf_error.h \
+ translator.h \
+ translator_class.h \
+ translator_config.h \
+ translator_control_socket.h \
+ translator_database.h \
+ translator_host.h \
+ translator_logger.h \
+ translator_option_data.h \
+ translator_option_def.h \
+ translator_pd_pool.h \
+ translator_pool.h \
+ translator_shared_network.h \
+ translator_subnet.h \
+ yang_models.h \
+ yang_revisions.h
+
+EXTRA_DIST = yang.dox yang_revisions.h.skel
+CLEANFILES = *.gcno *.gcda
+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/yang/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/yang/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-yang.la: $(libkea_yang_la_OBJECTS) $(libkea_yang_la_DEPENDENCIES) $(EXTRA_libkea_yang_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libkea_yang_la_LINK) -rpath $(libdir) $(libkea_yang_la_OBJECTS) $(libkea_yang_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptor.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptor_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptor_host.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptor_option.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptor_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptor_subnet.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_class.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_config.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_control_socket.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_database.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_host.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_logger.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_option_data.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_option_def.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_pd_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_shared_network.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/translator_subnet.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)$(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 $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libkea_yang_includeHEADERS: $(libkea_yang_include_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libkea_yang_include_HEADERS)'; test -n "$(libkea_yang_includedir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libkea_yang_includedir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libkea_yang_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)$(libkea_yang_includedir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_yang_includedir)" || exit $$?; \
+ done
+
+uninstall-libkea_yang_includeHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libkea_yang_include_HEADERS)'; test -n "$(libkea_yang_includedir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libkea_yang_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: check-recursive
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_yang_includedir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+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-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/adaptor.Plo
+ -rm -f ./$(DEPDIR)/adaptor_config.Plo
+ -rm -f ./$(DEPDIR)/adaptor_host.Plo
+ -rm -f ./$(DEPDIR)/adaptor_option.Plo
+ -rm -f ./$(DEPDIR)/adaptor_pool.Plo
+ -rm -f ./$(DEPDIR)/adaptor_subnet.Plo
+ -rm -f ./$(DEPDIR)/translator.Plo
+ -rm -f ./$(DEPDIR)/translator_class.Plo
+ -rm -f ./$(DEPDIR)/translator_config.Plo
+ -rm -f ./$(DEPDIR)/translator_control_socket.Plo
+ -rm -f ./$(DEPDIR)/translator_database.Plo
+ -rm -f ./$(DEPDIR)/translator_host.Plo
+ -rm -f ./$(DEPDIR)/translator_logger.Plo
+ -rm -f ./$(DEPDIR)/translator_option_data.Plo
+ -rm -f ./$(DEPDIR)/translator_option_def.Plo
+ -rm -f ./$(DEPDIR)/translator_pd_pool.Plo
+ -rm -f ./$(DEPDIR)/translator_pool.Plo
+ -rm -f ./$(DEPDIR)/translator_shared_network.Plo
+ -rm -f ./$(DEPDIR)/translator_subnet.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-libkea_yang_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)/adaptor.Plo
+ -rm -f ./$(DEPDIR)/adaptor_config.Plo
+ -rm -f ./$(DEPDIR)/adaptor_host.Plo
+ -rm -f ./$(DEPDIR)/adaptor_option.Plo
+ -rm -f ./$(DEPDIR)/adaptor_pool.Plo
+ -rm -f ./$(DEPDIR)/adaptor_subnet.Plo
+ -rm -f ./$(DEPDIR)/translator.Plo
+ -rm -f ./$(DEPDIR)/translator_class.Plo
+ -rm -f ./$(DEPDIR)/translator_config.Plo
+ -rm -f ./$(DEPDIR)/translator_control_socket.Plo
+ -rm -f ./$(DEPDIR)/translator_database.Plo
+ -rm -f ./$(DEPDIR)/translator_host.Plo
+ -rm -f ./$(DEPDIR)/translator_logger.Plo
+ -rm -f ./$(DEPDIR)/translator_option_data.Plo
+ -rm -f ./$(DEPDIR)/translator_option_def.Plo
+ -rm -f ./$(DEPDIR)/translator_pd_pool.Plo
+ -rm -f ./$(DEPDIR)/translator_pool.Plo
+ -rm -f ./$(DEPDIR)/translator_shared_network.Plo
+ -rm -f ./$(DEPDIR)/translator_subnet.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-libkea_yang_includeHEADERS
+
+.MAKE: $(am__recursive_targets) install-am 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-libkea_yang_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-libkea_yang_includeHEADERS
+
+.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/yang/adaptor.cc b/src/lib/yang/adaptor.cc
new file mode 100644
index 0000000..5e2de98
--- /dev/null
+++ b/src/lib/yang/adaptor.cc
@@ -0,0 +1,302 @@
+// Copyright (C) 2018-2022 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 <yang/adaptor.h>
+
+#include <iostream>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace yang {
+
+ConstElementPtr
+Adaptor::getContext(ConstElementPtr parent) {
+ ConstElementPtr context = parent->get("user-context");
+ ConstElementPtr comment = parent->get("comment");
+ if (!comment) {
+ return (context);
+ }
+ ElementPtr result;
+ if (context) {
+ result = copy(context);
+ } else {
+ result = Element::createMap();
+ }
+ result->set("comment", comment);
+ return (result);
+}
+
+void
+Adaptor::fromParent(const string& name, ConstElementPtr parent,
+ ConstElementPtr list) {
+ ConstElementPtr param = parent->get(name);
+ if (!param) {
+ return;
+ }
+ for (ElementPtr const& item : list->listValue()) {
+ // don't override. Skip this entry if it already has the parameter.
+ if (item->contains(name)) {
+ continue;
+ }
+ item->set(name, param);
+ }
+}
+
+void
+Adaptor::toParent(const string& name, ElementPtr parent,
+ ConstElementPtr list) {
+ ConstElementPtr param;
+ bool first = true;
+ for (ElementPtr const& item : list->listValue()) {
+ if (first) {
+ first = false;
+ param = item->get(name);
+ } else if ((!param && item->contains(name)) ||
+ (param && !item->contains(name)) ||
+ (param && item->contains(name) &&
+ !param->equals(*item->get(name)))) {
+ isc_throw(BadValue,
+ "inconsistent value of " << name
+ << " in " << list->str());
+ }
+ }
+ if (!first && param) {
+ for (ElementPtr const& item : list->listValue()) {
+ if (param) {
+ item->remove(name);
+ }
+ }
+ parent->set(name, param);
+ }
+}
+
+namespace {
+
+/// @brief Apply insert.
+///
+/// This function applies an insert action at the given scope:
+/// - in a map it adds the value at the key when the key does not exist
+/// - in a list it appends the value
+/// - in other scope type it does nothing
+/// @note a copy of the value is used to avoid unwanted sharing/side effects.
+///
+/// @param key The key of the modification.
+/// @param value The value of the modification.
+/// @param scope The place to apply the insert.
+void applyInsert(ConstElementPtr key, ConstElementPtr value,
+ ElementPtr scope) {
+ if (scope->getType() == Element::map) {
+ if (!key || !value || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (!name.empty() && !scope->contains(name)) {
+ scope->set(name, copy(value));
+ }
+ } else if (scope->getType() == Element::list) {
+ if (value) {
+ scope->add(copy(value));
+ }
+ }
+}
+
+/// @brief Apply replace.
+///
+/// This function works only on map scopes and acts as insert at the
+/// exception the new value is set even if the key already exists.
+///
+/// @param key The key of the modification.
+/// @param value The value of the modification.
+/// @param scope The place to apply the replace.
+void applyReplace(ConstElementPtr key, ConstElementPtr value,
+ ElementPtr scope) {
+ if ((scope->getType() != Element::map) ||
+ !key || !value || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (!name.empty()) {
+ scope->set(name, copy(value));
+ }
+}
+
+/// @brief Apply delete.
+///
+/// This function deletes the value designed by the key from the given scope:
+/// - in a map the key is the key of the value to remove
+/// - in a list the key is:
+/// * when it is an integer the index of the value to remove
+/// * when it is a map the key / value pair which belongs to the value
+/// to remove
+/// - in other scope type it does nothing
+///
+/// @param key The key item of the path.
+/// @param scope The place to apply the delete.
+void applyDelete(ConstElementPtr key, ElementPtr scope) {
+ if (scope->getType() == Element::map) {
+ if (!key || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (!name.empty()) {
+ scope->remove(name);
+ }
+ } else if (scope->getType() == Element::list) {
+ if (!key) {
+ return;
+ } else if (key->getType() == Element::integer) {
+ int index = key->intValue();
+ if ((index >= 0) && (index < scope->size())) {
+ scope->remove(index);
+ }
+ } else if (key->getType() == Element::map) {
+ ConstElementPtr entry = key->get("key");
+ ConstElementPtr value = key->get("value");
+ if (!entry || !value || (entry->getType() != Element::string)) {
+ return;
+ }
+ string name = entry->stringValue();
+ if (name.empty()) {
+ return;
+ }
+ for (int i = 0; i < scope->size(); ++i) {
+ ElementPtr item = scope->getNonConst(i);
+ if (!item || (item->getType() != Element::map)) {
+ continue;
+ }
+ ConstElementPtr compare = item->get(name);
+ if (compare && value->equals(*compare)) {
+ scope->remove(i);
+ return;
+ }
+ }
+ }
+ }
+}
+
+/// @brief Apply action.
+///
+/// This function applies one action (insert, replace or delete) using
+/// applyInsert, applyReplace or applyDelete.
+///
+/// @param actions The action list.
+/// @param scope The current scope.
+/// @param next The index of the next action.
+void applyAction(ConstElementPtr actions, ElementPtr scope, size_t next) {
+ if (next == actions->size()) {
+ return;
+ }
+ ConstElementPtr action = actions->get(next);
+ ++next;
+ if (!action || (action->getType() != Element::map) ||
+ !action->contains("action")) {
+ applyAction(actions, scope, next);
+ return;
+ }
+ string name = action->get("action")->stringValue();
+ if (name == "insert") {
+ applyInsert(action->get("key"), action->get("value"), scope);
+ } else if (name == "replace") {
+ applyReplace(action->get("key"), action->get("value"), scope);
+ } else if (name == "delete") {
+ applyDelete(action->get("key"), scope);
+ }
+ applyAction(actions, scope, next);
+}
+
+/// @brief Apply recursively actions following the path and going down
+/// in the to-be-modified structure.
+///
+/// The recursive call when the end of the path is not reached is done:
+/// - in a map on the value at the key
+/// - in a list the next path item is:
+/// * if it is a positive integer on the value at the index
+/// * if it is -1 on all elements of the list
+/// * if a map on the element where the key / value pair belongs to
+///
+/// @param path The search list.
+/// @param actions The action list.
+/// @param scope The current scope.
+/// @param next The index of the next item to use in the path.
+void applyDown(ConstElementPtr path, ConstElementPtr actions, ElementPtr scope,
+ size_t next) {
+ if (!scope) {
+ return;
+ }
+ if (next == path->size()) {
+ applyAction(actions, scope, 0);
+ return;
+ }
+ ConstElementPtr step = path->get(next);
+ ++next;
+ if (scope->getType() == Element::map) {
+ if (!step || (step->getType() != Element::string)) {
+ return;
+ }
+ string name = step->stringValue();
+ if (name.empty() || !scope->contains(name)) {
+ return;
+ }
+ ConstElementPtr down(scope->get(name));
+ if (down) {
+ ElementPtr mutable_down(copy(down, 0));
+ applyDown(path, actions, mutable_down, next);
+ scope->set(name, mutable_down);
+ }
+ } else if (scope->getType() == Element::list) {
+ if (!step) {
+ return;
+ }
+ auto downs = scope->listValue();
+ if (step->getType() == Element::map) {
+ ConstElementPtr key = step->get("key");
+ ConstElementPtr value = step->get("value");
+ if (!key || !value || (key->getType() != Element::string)) {
+ return;
+ }
+ string name = key->stringValue();
+ if (name.empty()) {
+ return;
+ }
+ for (ElementPtr down : downs) {
+ if (!down || (down->getType() != Element::map)) {
+ continue;
+ }
+ ConstElementPtr compare = down->get(name);
+ if (compare && value->equals(*compare)) {
+ applyDown(path, actions, down, next);
+ return;
+ }
+ }
+ } else if (step->getType() != Element::integer) {
+ return;
+ }
+ int index = step->intValue();
+ if (index == -1) {
+ for (ElementPtr down : downs) {
+ applyDown(path, actions, down, next);
+ }
+ } else if ((index >= 0) && (index < scope->size())) {
+ applyDown(path, actions, scope->getNonConst(index), next);
+ }
+ }
+}
+
+} //anonymous namespace
+
+/// Apply recursively starting at the beginning of the path.
+void
+Adaptor::modify(ConstElementPtr path, ConstElementPtr actions,
+ ElementPtr config) {
+ applyDown(path, actions, config, 0);
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/adaptor.h b/src/lib/yang/adaptor.h
new file mode 100644
index 0000000..d4909fb
--- /dev/null
+++ b/src/lib/yang/adaptor.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2018-2022 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 ISC_ADAPTOR_H
+#define ISC_ADAPTOR_H 1
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief JSON adaptor between canonical Kea and YANG models.
+///
+/// An adaptor slightly modifies a JSON configuration between canonical Kea
+/// what required or rendered by a YANG model, e.g. moving a parameter
+/// to/from a parent.
+/// The basic adaptor provides a set of tools.
+class Adaptor {
+public:
+ /// @brief Destructor.
+ virtual ~Adaptor() = default;
+
+ /// @brief Get user context.
+ ///
+ /// Get user-context and/or comment and return it with the comment
+ /// if exists moved inside the user-context (without checking if
+ /// there is already a comment as it should never be the case).
+ ///
+ /// This behavior is used to handle comments. For historical purposes
+ /// Kea allows to define comments in some scopes. Once the user-context
+ /// has been introduced, the comment (as a separate text field)
+ /// disappeared and was moved to comment key within user-context.
+ /// Nevertheless, the old syntax is still supported.
+ static isc::data::ConstElementPtr
+ getContext(isc::data::ConstElementPtr parent);
+
+ /// @brief Moves a parameter from parent to a list of children.
+ ///
+ /// Move a parameter from the parent to each item in a list.
+ /// If the parameter exists in a child, it is skipped for this
+ /// particular child, not overridden.
+ ///
+ /// @param name The parameter name.
+ /// @param parent The parent element.
+ /// @param list The children list.
+ static void fromParent(const std::string& name,
+ isc::data::ConstElementPtr parent,
+ isc::data::ConstElementPtr list);
+
+ /// @brief Moves a parameter to a parent.
+ ///
+ /// Move a parameter from children to the parent. All children
+ /// on the list must have the parameter specified and it has to have
+ /// the same value.
+ ///
+ /// @param name The parameter name.
+ /// @param parent The parent element.
+ /// @param list The children list.
+ static void toParent(const std::string& name,
+ isc::data::ElementPtr parent,
+ isc::data::ConstElementPtr list);
+
+ /// @brief Modify a configuration in its JSON element format.
+ ///
+ /// Smart merging tool, e.g. completing an ElementPtr received from YANG.
+ ///
+ /// A modification is a path and actions:
+ /// - path item can be:
+ /// * a string: current scope is a map, go down following the string
+ /// as a key.
+ /// * a number: current scope is a list, go down the number as an index.
+ /// * special value -1: current scope is a list, apply to all items.
+ /// * map { "<key>": <value> }: current scope is a list, go down to
+ /// the item using the key / value pair.
+ /// - an action can be: insert, replace or delete:
+ /// * insert adds a value at a key:
+ /// . in a map the key is the new entry key
+ /// . in a list an integer key is the new element index
+ /// . in a list a map key is the key / value pair the to-be-modified
+ /// element contains
+ /// * replace adds or replaces if it already exists a value at
+ /// an entry key in a map.
+ /// * delete removes a value designed by a key
+ ///
+ /// For instance to add a control-socket entry in a configuration
+ /// from a generic (vs Kea) model:
+ /// @code
+ /// path := [ ]
+ /// actions := [ {
+ /// "action": "insert",
+ /// "key": "Dhcp4",
+ /// "value":
+ /// {
+ /// "control-socket":
+ /// {
+ /// "socket-type": "unix",
+ /// "socket-name": "/tmp/kea4-ctrl-socket"
+ /// }
+ /// }
+ /// }
+ /// @endcode
+ ///
+ /// @param path The search list to follow down to the place to
+ /// apply the action list.
+ /// @param actions The action list
+ /// @param config The configuration (JSON map) to modify.
+ /// @note modify does not throw but returns on unexpected conditions.
+ static void modify(isc::data::ConstElementPtr path,
+ isc::data::ConstElementPtr actions,
+ isc::data::ElementPtr config);
+
+}; // Adaptor
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_ADAPTOR_H
diff --git a/src/lib/yang/adaptor_config.cc b/src/lib/yang/adaptor_config.cc
new file mode 100644
index 0000000..a97bf09
--- /dev/null
+++ b/src/lib/yang/adaptor_config.cc
@@ -0,0 +1,653 @@
+// Copyright (C) 2018-2022 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 <dhcpsrv/subnet_id.h>
+#include <yang/adaptor_config.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+const string DHCP4_SPACE = "dhcp4";
+const string DHCP6_SPACE = "dhcp6";
+}
+
+namespace isc {
+namespace yang {
+
+bool
+AdaptorConfig::subnetsCollectID(ConstElementPtr subnets, SubnetIDSet& set) {
+ bool have_ids = true;
+
+ if (!subnets || subnets->empty()) {
+ // There are no subnets defined, so technically there are no ids.
+ // However, the flag is used to determine whether the code later
+ // needs to call assignIDs. Since there is no need to assign
+ // anything, the code returns true here.
+ return (true);
+ }
+
+ // If there are subnets defined, let's go over them one by one and
+ // collect subnet-ids used in them.
+ for (ElementPtr const& subnet : subnets->listValue()) {
+ if (!collectID(subnet, set)) {
+ have_ids = false;
+ }
+ }
+ return (have_ids);
+}
+
+bool
+AdaptorConfig::sharedNetworksCollectID(ConstElementPtr networks,
+ SubnetIDSet& set,
+ const string& subsel) {
+ if (!networks || networks->empty()) {
+ // There are no shared networks defined, so technically there are no
+ // ids. However, the flag is used to determine whether the code later
+ // needs to call assignIDs. Since there is no need to assign anything,
+ // the code returns true here.
+ return (true);
+ }
+
+ // This determines if EVERY subnet has subnet-id defined.
+ bool have_ids = true;
+ for (size_t i = 0; i < networks->size(); ++i) {
+ ElementPtr network = networks->getNonConst(i);
+ ConstElementPtr subnets = network->get(subsel);
+ if (subnets) {
+ if (!subnets->empty()) {
+ // If there are subnets, collect their subnet-ids. If any
+ // of them doesn't have a subnet-id, return false.
+ if (!subnetsCollectID(subnets, set)) {
+ have_ids = false;
+ }
+ } else {
+ // There's empty subnets list, so just remove it.
+ network->remove(subsel);
+ }
+ }
+ }
+ return (have_ids);
+}
+
+void
+AdaptorConfig::subnetsAssignID(ConstElementPtr subnets, SubnetIDSet& set,
+ SubnetID& next) {
+ if (!subnets || subnets->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < subnets->size(); ++i) {
+ ElementPtr subnet = subnets->getNonConst(i);
+ assignID(subnet, set, next);
+ }
+}
+
+void
+AdaptorConfig::sharedNetworksAssignID(ConstElementPtr networks,
+ SubnetIDSet& set, SubnetID& next,
+ const string& subsel) {
+ if (!networks || networks->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (ElementPtr const& network : networks->listValue()) {
+ ConstElementPtr subnets = network->get(subsel);
+ if (!subnets || subnets->empty()) {
+ continue;
+ }
+
+ for (size_t i = 0; i < subnets->size(); ++i) {
+ ElementPtr subnet = subnets->getNonConst(i);
+ assignID(subnet, set, next);
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizePools(ConstElementPtr pools) {
+ if (!pools || pools->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ // Canonize (clean up name, remove extra spaces, add one space where
+ // needed) every pool on the list.
+ for (size_t i = 0; i < pools->size(); ++i) {
+ ElementPtr pool = pools->getNonConst(i);
+ AdaptorPool::canonizePool(pool);
+ }
+}
+
+void
+AdaptorConfig::sanitizePoolsInSubnets(ConstElementPtr subnets) {
+ if (!subnets || subnets->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (ElementPtr const& subnet : subnets->listValue()) {
+ sanitizePools(subnet->get("pools"));
+ }
+}
+
+void
+AdaptorConfig::sanitizePoolsInSharedNetworks(ConstElementPtr networks,
+ const string& subsel) {
+ if (!networks || networks->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (ElementPtr const& network : networks->listValue()) {
+ sanitizePoolsInSubnets(network->get(subsel));
+ }
+}
+
+void
+AdaptorConfig::sanitizeOptionDefList(ConstElementPtr defs,
+ const string& space,
+ OptionCodes& codes) {
+ if (!defs || defs->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ // Do sanity checks on every option definition and fill any missing
+ // fields with default values.
+ for (size_t i = 0; i < defs->size(); ++i) {
+ ElementPtr def = defs->getNonConst(i);
+ checkCode(def);
+ checkType(def);
+ setSpace(def, space);
+ collect(def, codes);
+ }
+}
+
+void
+AdaptorConfig::sanitizeOptionDataList(ConstElementPtr options,
+ const string& space,
+ const OptionCodes& codes) {
+ if (!options || options->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ // Sanitize option-data. The only missing elements we may possibly
+ // need to fill are option space and option code.
+ for (size_t i = 0; i < options->size(); ++i) {
+ ElementPtr option = options->getNonConst(i);
+ setSpace(option, space);
+ setCode(option, codes);
+ }
+}
+
+void
+AdaptorConfig::sanitizeOptionClasses(ConstElementPtr classes,
+ const string& space,
+ OptionCodes& codes) {
+ if (!classes || classes->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ // For every client class defined...
+ for (size_t i = 0; i < classes->size(); ++i) {
+ ElementPtr cclass = classes->getNonConst(i);
+
+ if (space == DHCP4_SPACE) {
+ ConstElementPtr options = cclass->get("option-def");
+ if (options) {
+ if (!options->empty()) {
+ // If present, sanitize it.
+ sanitizeOptionDefList(options, space, codes);
+ } else {
+ // If empty, remove it.
+ cclass->remove("option-def");
+ }
+ }
+ }
+
+ // also sanitize option data.
+ ConstElementPtr options = cclass->get("option-data");
+ if (options) {
+ if (!options->empty()) {
+ // If present, sanitize it.
+ sanitizeOptionDataList(options, space, codes);
+ } else {
+ // If empty, remove it.
+ cclass->remove("option-data");
+ }
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeOptionPools(ConstElementPtr pools, const string& space,
+ const OptionCodes& codes) {
+ if (!pools || pools->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < pools->size(); ++i) {
+ ElementPtr pool = pools->getNonConst(i);
+ ConstElementPtr options = pool->get("option-data");
+ if (options) {
+ if (!options->empty()) {
+ sanitizeOptionDataList(options, space, codes);
+ } else {
+ pool->remove("option-data");
+ }
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeOptionHosts(ConstElementPtr hosts, const string& space,
+ const OptionCodes& codes) {
+ if (!hosts || hosts->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < hosts->size(); ++i) {
+ ElementPtr host = hosts->getNonConst(i);
+ ConstElementPtr options = host->get("option-data");
+ if (options) {
+ if (!options->empty()) {
+ sanitizeOptionDataList(options, space, codes);
+ } else {
+ host->remove("option-data");
+ }
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeOptionSubnets(ConstElementPtr subnets,
+ const string& space,
+ const OptionCodes& codes) {
+ if (!subnets || subnets->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < subnets->size(); ++i) {
+ ElementPtr subnet = subnets->getNonConst(i);
+
+ // Let's try to sanitize option-data first.
+ ConstElementPtr options = subnet->get("option-data");
+ if (options) {
+ if (!options->empty()) {
+ sanitizeOptionDataList(options, space, codes);
+ } else {
+ subnet->remove("option-data");
+ }
+ }
+
+ // Then try to sanitize pools.
+ ConstElementPtr pools = subnet->get("pools");
+ if (pools) {
+ if (!pools->empty()) {
+ sanitizeOptionPools(pools, space, codes);
+ } else {
+ subnet->remove("pools");
+ }
+ }
+
+ // If this is v6, also sanitize pd-pools.
+ if (space == DHCP6_SPACE) {
+ ConstElementPtr pd_pools = subnet->get("pd-pools");
+ if (pd_pools) {
+ if (!pd_pools->empty()) {
+ sanitizeOptionPools(pd_pools, space, codes);
+ } else {
+ subnet->remove("pd-pools");
+ }
+ }
+ }
+
+ // Finally, sanitize host reservations.
+ ConstElementPtr hosts = subnet->get("reservations");
+ if (hosts) {
+ if (!hosts->empty()) {
+ sanitizeOptionHosts(hosts, space, codes);
+ } else {
+ subnet->remove("reservations");
+ }
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeOptionSharedNetworks(ConstElementPtr networks,
+ const string& space,
+ const OptionCodes& codes) {
+ if (!networks || networks->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ // For every shared network...
+ for (size_t i = 0; i < networks->size(); ++i) {
+ ElementPtr network = networks->getNonConst(i);
+
+ // try to sanitize shared network options first.
+ ConstElementPtr options = network->get("option-data");
+ if (options) {
+ if (!options->empty()) {
+ sanitizeOptionDataList(options, space, codes);
+ } else {
+ network->remove("option-data");
+ }
+ }
+ string subnet = "subnet";
+ if (space == DHCP4_SPACE) {
+ subnet += "4";
+ } else {
+ subnet += "6";
+ }
+
+ // Now try to sanitize subnets.
+ ConstElementPtr subnets = network->get(subnet);
+ if (subnets) {
+ if (!subnets->empty()) {
+ sanitizeOptionSubnets(subnets, space, codes);
+ } else {
+ network->remove(subnet);
+ }
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeRequireClassesPools(ConstElementPtr pools) {
+ if (!pools || pools->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < pools->size(); ++i) {
+ ElementPtr pool = pools->getNonConst(i);
+ ConstElementPtr require = pool->get("require-client-classes");
+ if (require && require->empty()) {
+ pool->remove("require-client-classes");
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeRequireClassesSubnets(ConstElementPtr subnets) {
+ if (!subnets || subnets->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < subnets->size(); ++i) {
+ ElementPtr subnet = subnets->getNonConst(i);
+ sanitizeRequireClassesPools(subnet->get("pools"));
+ sanitizeRequireClassesPools(subnet->get("pd-pools"));
+ ConstElementPtr require = subnet->get("require-client-classes");
+ if (require && require->empty()) {
+ subnet->remove("require-client-classes");
+ }
+ }
+}
+
+void
+AdaptorConfig::requireClassesSharedNetworks(ConstElementPtr networks,
+ const string& subsel) {
+ if (!networks || networks->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < networks->size(); ++i) {
+ ElementPtr network = networks->getNonConst(i);
+ sanitizeRequireClassesSubnets(network->get(subsel));
+ ConstElementPtr require = network->get("require-client-classes");
+ if (require && require->empty()) {
+ network->remove("require-client-classes");
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeHostList(ConstElementPtr hosts) {
+
+ if (!hosts || hosts->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < hosts->size(); ++i) {
+ ElementPtr host = hosts->getNonConst(i);
+ quoteIdentifier(host);
+ }
+}
+
+void
+AdaptorConfig::sanitizeHostSubnets(ConstElementPtr subnets) {
+
+ if (!subnets || subnets->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (ElementPtr const& subnet : subnets->listValue()) {
+ sanitizeHostList(subnet->get("reservations"));
+ }
+}
+
+void
+AdaptorConfig::SanitizeHostsInSharedNetworks(ConstElementPtr networks,
+ const string& space) {
+ if (!networks || networks->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (ElementPtr const& network : networks->listValue()) {
+ if (space == DHCP4_SPACE) {
+ sanitizeHostSubnets(network->get("subnet4"));
+ } else {
+ sanitizeHostSubnets(network->get("subnet6"));
+ }
+ }
+}
+
+void
+AdaptorConfig::sanitizeRelaySubnets(ConstElementPtr subnets) {
+ if (!subnets || subnets->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < subnets->size(); ++i) {
+ ElementPtr subnet = subnets->getNonConst(i);
+ updateRelay(subnet);
+ }
+}
+
+void
+AdaptorConfig::sanitizeRelayInSharedNetworks(ConstElementPtr networks,
+ const string& subsel) {
+ if (!networks || networks->empty()) {
+ // nothing to do here.
+ return;
+ }
+
+ for (size_t i = 0; i < networks->size(); ++i) {
+ ElementPtr network = networks->getNonConst(i);
+ updateRelay(network);
+ sanitizeRelaySubnets(network->get(subsel));
+ }
+}
+
+void
+AdaptorConfig::sanitizeDatabase(ElementPtr dhcp) {
+ ConstElementPtr database = dhcp->get("hosts-database");
+ if (!database) {
+ // nothing to do here.
+ return;
+ }
+
+ dhcp->remove("hosts-database");
+ ElementPtr list = Element::createList();
+ list->add(copy(database, 0));
+ dhcp->set("hosts-databases", list);
+}
+
+void
+AdaptorConfig::sanitizeRelaySuppliedOptions(ElementPtr dhcp) {
+ ConstElementPtr options = dhcp->get("relay-supplied-options");
+ if (!options || !options->empty()) {
+ // nothing to do here.
+ return;
+ }
+ dhcp->remove("relay-supplied-options");
+}
+
+void
+AdaptorConfig::preProcess(ElementPtr dhcp, const string& subsel,
+ const string& space) {
+ if (!dhcp) {
+ isc_throw(BadValue, "preProcess: null DHCP config");
+ }
+ bool have_ids = true;
+ SubnetIDSet set;
+ ConstElementPtr subnets = dhcp->get(subsel);
+ if (subnets) {
+ if (!subnets->empty()) {
+ if (!subnetsCollectID(subnets, set)) {
+ have_ids = false;
+ }
+ } else {
+ dhcp->remove(subsel);
+ }
+ }
+ ConstElementPtr networks = dhcp->get("shared-networks");
+ if (networks) {
+ if (!networks->empty()) {
+ if (!sharedNetworksCollectID(networks, set, subsel)) {
+ have_ids = false;
+ }
+ } else {
+ dhcp->remove("shared-networks");
+ }
+ }
+
+ if (!have_ids) {
+ SubnetID next(1);
+ subnetsAssignID(subnets, set, next);
+ sharedNetworksAssignID(networks, set, next, subsel);
+ }
+
+ OptionCodes codes;
+ initCodes(codes, space);
+ ConstElementPtr defs = dhcp->get("option-def");
+ if (defs) {
+ if (!defs->empty()) {
+ sanitizeOptionDefList(defs, space, codes);
+ } else {
+ dhcp->remove("option-def");
+ }
+ }
+ ConstElementPtr options = dhcp->get("option-data");
+ if (options) {
+ if (!options->empty()) {
+ sanitizeOptionDataList(options, space, codes);
+ } else {
+ dhcp->remove("option-data");
+ }
+ }
+ ConstElementPtr classes = dhcp->get("client-classes");
+ if (classes) {
+ if (!classes->empty()) {
+ sanitizeOptionClasses(classes, space, codes);
+ } else {
+ dhcp->remove("client-classes");
+ }
+ }
+ ConstElementPtr hosts = dhcp->get("reservations");
+ if (hosts) {
+ if (!hosts->empty()) {
+ sanitizeHostList(hosts);
+ sanitizeOptionHosts(hosts, space, codes);
+ } else {
+ dhcp->remove("reservations");
+ }
+ }
+ sanitizeOptionSubnets(subnets, space, codes);
+ sanitizeOptionSharedNetworks(networks, space, codes);
+
+ sanitizePoolsInSubnets(subnets);
+ sanitizePoolsInSharedNetworks(networks, subsel);
+
+ sanitizeHostSubnets(subnets);
+ SanitizeHostsInSharedNetworks(networks, space);
+
+ sanitizeRelaySubnets(subnets);
+ sanitizeRelayInSharedNetworks(networks, subsel);
+
+ sanitizeRequireClassesSubnets(subnets);
+ requireClassesSharedNetworks(networks, subsel);
+
+ sanitizeDatabase(dhcp);
+
+ if (space == DHCP6_SPACE) {
+ sanitizeRelaySuppliedOptions(dhcp);
+ }
+}
+
+void
+AdaptorConfig::preProcess4(ElementPtr config) {
+ if (!config) {
+ isc_throw(BadValue, "preProcess4: null config");
+ }
+ if (config->getType() != Element::map) {
+ isc_throw(BadValue, "preProcess4: not map: " << config->str());
+ }
+ if (config->contains("Logging")) {
+ isc_throw(BadValue, "preProcess4: got Logging object");
+ }
+ ConstElementPtr dhcp = config->get("Dhcp4");
+ if (!dhcp) {
+ return;
+ }
+ ElementPtr mutable_dhcp(copy(dhcp, 0));
+ preProcess(mutable_dhcp, "subnet4", DHCP4_SPACE);
+ config->set("Dhcp4", mutable_dhcp);
+}
+
+void
+AdaptorConfig::preProcess6(ElementPtr config) {
+ if (!config) {
+ isc_throw(BadValue, "preProcess6: null config");
+ }
+ if (config->getType() != Element::map) {
+ isc_throw(BadValue, "preProcess6: not map: " << config->str());
+ }
+ if (config->contains("Logging")) {
+ isc_throw(BadValue, "preProcess6: got Logging object");
+ }
+ ConstElementPtr dhcp = config->get("Dhcp6");
+ if (!dhcp) {
+ return;
+ }
+ ElementPtr mutable_dhcp(copy(dhcp, 0));
+ preProcess(mutable_dhcp, "subnet6", DHCP6_SPACE);
+ config->set("Dhcp6", mutable_dhcp);
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/adaptor_config.h b/src/lib/yang/adaptor_config.h
new file mode 100644
index 0000000..4848250
--- /dev/null
+++ b/src/lib/yang/adaptor_config.h
@@ -0,0 +1,286 @@
+// Copyright (C) 2018-2022 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 ISC_ADAPTOR_CONFIG_H
+#define ISC_ADAPTOR_CONFIG_H 1
+
+#include <dhcpsrv/subnet_id.h>
+#include <yang/adaptor_host.h>
+#include <yang/adaptor_option.h>
+#include <yang/adaptor_pool.h>
+#include <yang/adaptor_subnet.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief JSON adaptor for Kea server configurations.
+///
+/// Currently only from JSON to YANG for DHCPv4 and DHCPv6 available
+/// as preProcess4 and preProcess6 class methods, filling some required
+/// (by YANG) fields (e.g. subnet IDs, or option code and space), or
+/// transforming a hand-written JSON configuration into a canonical form.
+class AdaptorConfig : public AdaptorHost, public AdaptorOption,
+ public AdaptorSubnet {
+public:
+ /// @brief Pre process a DHCPv4 configuration.
+ ///
+ /// Assign subnet IDs, check and set defaults in options, etc.
+ /// Note even though the parameter is a ConstElementPtr and is not modified,
+ /// sub-structures can modify it, so if you need a copy do a deep one.
+ ///
+ /// @param config The configuration.
+ /// @throw MissingKey when a required key is missing.
+ /// @throw BadValue when null or not a map or deprecated Logging present.
+ /// @note Does nothing if "Dhcp4" is not present in the map.
+ static void preProcess4(isc::data::ElementPtr config);
+
+ /// @brief Pre process a DHCPv6 configuration.
+ ///
+ /// Assign subnet IDs, check and set default in options, etc.
+ /// Note even though the parameter is a ConstElementPtr and is not modified,
+ /// sub-structures can modify it, so if you need a copy do a deep one.
+ ///
+ /// @param config The configuration.
+ /// @throw MissingKey when a required key is missing.
+ /// @throw BadValue when null or not a map or deprecated Logging present.
+ /// @note Does nothing if "Dhcp6" is not present in the map.
+ static void preProcess6(isc::data::ElementPtr config);
+
+protected:
+ /// @brief Collects subnet-ids on all subnets.
+ ///
+ /// It will go over all subnets and collect their ids. It will then return
+ /// true if all subnets have ids. If the subnets list is empty, it will also
+ /// return true. False will be returned if there is at least one subnet that
+ /// doesn't have subnet-id.
+ ///
+ /// @param subnets The subnet list.
+ /// @param set The reference to the set of assigned IDs.
+ /// @return True if all subnets have an ID, false otherwise.
+ static bool subnetsCollectID(isc::data::ConstElementPtr subnets,
+ isc::dhcp::SubnetIDSet& set);
+
+ /// @brief Collects subnet-ids in all subnets in all shared network list.
+ ///
+ /// It will go over all subnets in all shared networks specified to collect
+ /// their subnet-ids. It will then return true if all subnets have ids. If
+ /// the subnets list is empty, it will also return true. False will be
+ /// returned if there is at least one subnet that doesn't have subnet-id.
+ ///
+ /// @param networks The shared network list.
+ /// @param set The reference to the set of assigned IDs.
+ /// @param subsel The subnet list name.
+ /// @return True if all subnets have an ID, false otherwise.
+ static bool sharedNetworksCollectID(isc::data::ConstElementPtr networks,
+ isc::dhcp::SubnetIDSet& set,
+ const std::string& subsel);
+
+ /// @brief Assigns subnet-id to every subnet in a subnet list.
+ ///
+ /// Only those subnets that don't have one subnet-id assigned yet,
+ /// will get a new subnet-id value.
+ ///
+ /// @param subnets The subnet list.
+ /// @param set The reference to the set of assigned IDs.
+ /// @param next The next ID.
+ static void subnetsAssignID(isc::data::ConstElementPtr subnets,
+ isc::dhcp::SubnetIDSet& set,
+ isc::dhcp::SubnetID& next);
+
+ /// @brief Assigns subnet-id to every subnet in a shared network list.
+ ///
+ /// Only those subnets that don't have one subnet-id assigned yet,
+ /// will get a new subnet-id value.
+ ///
+ /// @param networks The shared network list.
+ /// @param set The reference to the set of assigned IDs.
+ /// @param next The next ID.
+ /// @param subsel The subnet list name.
+ static void sharedNetworksAssignID(isc::data::ConstElementPtr networks,
+ isc::dhcp::SubnetIDSet& set,
+ isc::dhcp::SubnetID& next,
+ const std::string& subsel);
+
+ /// @brief Sanitizes all pools in a pools list.
+ ///
+ /// Goes over each pool and cleans up its definition (removes extra spaces,
+ /// adds one space before and after - ).
+ ///
+ /// @param pools The pool list.
+ static void sanitizePools(isc::data::ConstElementPtr pools);
+
+ /// @brief Sanitizes all pools in a subnets list.
+ ///
+ /// @param subnets The subnet list.
+ static void sanitizePoolsInSubnets(isc::data::ConstElementPtr subnets);
+
+ /// @brief Sanitizes all pools in all subnets in a shared network list.
+ ///
+ /// @param networks The shared network list.
+ /// @param subsel The subnet list name.
+ static void sanitizePoolsInSharedNetworks(isc::data::ConstElementPtr networks,
+ const std::string& subsel);
+
+ /// @brief Collect option definitions from an option definition list.
+ ///
+ /// Collects options definitions, but also sets missing option space
+ /// with default.
+ ///
+ /// @param defs The option definition list.
+ /// @param space The default space name (missing will be filled with this)
+ /// @param codes Option definitions.
+ static void sanitizeOptionDefList(isc::data::ConstElementPtr defs,
+ const std::string& space,
+ OptionCodes& codes);
+
+ /// @brief Set missing option codes to an option data list.
+ ///
+ /// @param options The option data list.
+ /// @param space The default space name.
+ /// @param codes Option definitions.
+ static void sanitizeOptionDataList(isc::data::ConstElementPtr options,
+ const std::string& space,
+ const OptionCodes& codes);
+
+ /// @brief Collect option definitions from a client class list
+ /// and set missing option codes.
+ ///
+ /// @param classes The client class list.
+ /// @param space The default space name.
+ /// @param codes Option definitions.
+ static void sanitizeOptionClasses(isc::data::ConstElementPtr classes,
+ const std::string& space,
+ OptionCodes& codes);
+
+ /// @brief Set missing option codes to a pool list.
+ ///
+ /// @param pools The pool list.
+ /// @param space The default space name.
+ /// @param codes Option definitions.
+ static void sanitizeOptionPools(isc::data::ConstElementPtr pools,
+ const std::string& space,
+ const OptionCodes& codes);
+
+ /// @brief Set missing option codes to a host reservation list.
+ ///
+ /// @param hosts The host reservation list.
+ /// @param space The default space name.
+ /// @param codes Option definitions.
+ static void sanitizeOptionHosts(isc::data::ConstElementPtr hosts,
+ const std::string& space,
+ const OptionCodes& codes);
+
+ /// @brief Set missing option codes to a subnet list.
+ ///
+ /// @param subnets The subnet list.
+ /// @param space The default space name.
+ /// @param codes Option definitions.
+ static void sanitizeOptionSubnets(isc::data::ConstElementPtr subnets,
+ const std::string& space,
+ const OptionCodes& codes);
+
+ /// @brief Set missing option codes to a shared network list.
+ ///
+ /// @param networks The shared network list.
+ /// @param space The default space name.
+ /// @param codes Option definitions.
+ static void sanitizeOptionSharedNetworks(isc::data::ConstElementPtr networks,
+ const std::string& space,
+ const OptionCodes& codes);
+
+ /// @brief Process require client classes in a pool list.
+ ///
+ /// Remove empty require client class list.
+ ///
+ /// @param pools The pool list.
+ static void sanitizeRequireClassesPools(isc::data::ConstElementPtr pools);
+
+ /// @brief Process require client classes in a subnet list.
+ ///
+ /// Remove empty require client class lists.
+ ///
+ /// @param subnets The subnet list.
+ static void sanitizeRequireClassesSubnets(isc::data::ConstElementPtr subnets);
+
+ /// @brief Process require client classes in a shared network list.
+ ///
+ /// Remove empty require client class lists.
+ ///
+ /// @param networks The shared network list.
+ /// @param subsel The subnet list name.
+ static void requireClassesSharedNetworks(isc::data::ConstElementPtr networks,
+ const std::string& subsel);
+
+ /// @brief Process host reservation list.
+ ///
+ /// Quote when needed flex-id identifiers.
+ ///
+ /// @param hosts The host reservation list.
+ static void sanitizeHostList(isc::data::ConstElementPtr hosts);
+
+ /// @brief Process host reservations in a subnet list.
+ ///
+ /// Quote when needed flex-id identifiers.
+ ///
+ /// @param subnets The subnet list.
+ static void sanitizeHostSubnets(isc::data::ConstElementPtr subnets);
+
+ /// @brief Process host reservations in a shared network list.
+ ///
+ /// Quote when needed flex-id identifiers.
+ ///
+ /// @param networks The shared network list.
+ /// @param space The default space name.
+ static void SanitizeHostsInSharedNetworks(isc::data::ConstElementPtr networks,
+ const std::string& space);
+
+ /// @brief Sanitizes relay information in subnets in a subnet list.
+ ///
+ /// Force the use of ip-addresses when it finds an ip-address entry.
+ ///
+ /// @param subnets The subnet list.
+ static void sanitizeRelaySubnets(isc::data::ConstElementPtr subnets);
+
+ /// @brief Sanitizes relay information in a shared network list.
+ ///
+ /// Force the use of ip-addresses when it finds an ip-address entry.
+ ///
+ /// @param networks The shared network list.
+ /// @param subsel The subnet list name.
+ static void sanitizeRelayInSharedNetworks(isc::data::ConstElementPtr networks,
+ const std::string& subsel);
+
+ /// @brief Update (hosts) database.
+ ///
+ /// Force the use of hosts-databases vs. hosts-database.
+ ///
+ /// @param dhcp The DHCP server.
+ static void sanitizeDatabase(isc::data::ElementPtr dhcp);
+
+ /// @brief Update relay supplied options.
+ ///
+ /// Remove empty relay supplied option list.
+ ///
+ /// @param dhcp The DHCPv6 server.
+ static void sanitizeRelaySuppliedOptions(isc::data::ElementPtr dhcp);
+
+ /// @brief Pre process a configuration.
+ ///
+ /// Assign subnet IDs, check and set default in options, etc.
+ ///
+ /// @param dhcp The server configuration.
+ /// @param subsel The subnet list name.
+ /// @param space The default option space name.
+ /// @throw MissingKey when a required key is missing.
+ static void preProcess(isc::data::ElementPtr dhcp,
+ const std::string& subsel,
+ const std::string& space);
+}; // AdaptorConfig
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_ADAPTOR_CONFIG_H
diff --git a/src/lib/yang/adaptor_host.cc b/src/lib/yang/adaptor_host.cc
new file mode 100644
index 0000000..990a103
--- /dev/null
+++ b/src/lib/yang/adaptor_host.cc
@@ -0,0 +1,64 @@
+// Copyright (C) 2018-2022 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/encode/hex.h>
+#include <util/strutil.h>
+#include <yang/adaptor_host.h>
+
+#include <iomanip>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::util;
+
+namespace isc {
+namespace yang {
+
+const string
+AdaptorHost::STD_CHARACTERS =
+ "0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-.@_";
+
+void
+AdaptorHost::quoteIdentifier(ElementPtr host) {
+ ConstElementPtr flex_id = host->get("flex-id");
+ if (!flex_id) {
+ return;
+ }
+ const string& id = flex_id->stringValue();
+ // Empty is allowed.
+ if (id.empty()) {
+ return;
+ }
+ // No special and no not printable characters?
+ if (id.find_first_not_of(STD_CHARACTERS) == string::npos) {
+ return;
+ }
+ // Quoted identifier?
+ vector<uint8_t> binary = str::quotedStringToBinary(id);
+ if (binary.empty()) {
+ binary.assign(id.begin(), id.end());
+ }
+ // Convert in hexadecimal (from DUID::toText()).
+ stringstream tmp;
+ tmp << hex;
+ bool delim = false;
+ for (vector<uint8_t>::const_iterator it = binary.begin();
+ it != binary.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << setw(2) << setfill('0') << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ host->set("flex-id", Element::create(tmp.str()));
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/adaptor_host.h b/src/lib/yang/adaptor_host.h
new file mode 100644
index 0000000..75dee52
--- /dev/null
+++ b/src/lib/yang/adaptor_host.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2018-2022 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 ISC_ADAPTOR_HOST_H
+#define ISC_ADAPTOR_HOST_H 1
+
+#include <yang/adaptor.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief JSON adaptor for host reservations quoting identifiers.
+///
+/// The identifier type and identifier value are used as keys in YANG
+/// host reservation lists so some constraints were put on their contents.
+/// For instance a quoted flex-id identifier raises an error (keys
+/// are between quotes in setItem commands).
+class AdaptorHost {
+public:
+
+ /// @brief The string of standard (vs special or not printable)
+ /// characters (digit, letters, -, ., @, _).
+ static const std::string STD_CHARACTERS;
+
+ /// @brief Destructor.
+ virtual ~AdaptorHost() = default;
+
+ /// @brief Quote when needed a host identifier.
+ ///
+ /// Check if the flex-id identifier includes a special (including quote)
+ /// or not printable character. When it is the case produce and replace
+ /// by a hexadecimal identifier trying first for a quoted identifier.
+ ///
+ /// @param host The host.
+ static void quoteIdentifier(isc::data::ElementPtr host);
+}; // AdaptorHost
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_ADAPTOR_HOST_H
diff --git a/src/lib/yang/adaptor_option.cc b/src/lib/yang/adaptor_option.cc
new file mode 100644
index 0000000..e0405c1
--- /dev/null
+++ b/src/lib/yang/adaptor_option.cc
@@ -0,0 +1,123 @@
+// Copyright (C) 2018-2022 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 <dhcp/docsis3_option_defs.h>
+#include <dhcp/std_option_defs.h>
+#include <yang/adaptor_option.h>
+#include <yang/netconf_error.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace isc {
+namespace yang {
+
+void
+AdaptorOption::setSpace(ElementPtr option, const string& space) {
+ if (!option->contains("space")) {
+ option->set("space", Element::create(space));
+ }
+}
+
+void
+AdaptorOption::checkType(ConstElementPtr option) {
+ if (!option->contains("type")) {
+ isc_throw(MissingKey, "missing type in option definition "
+ << option->str());
+ }
+}
+
+void
+AdaptorOption::checkCode(ConstElementPtr option) {
+ if (!option->contains("code")) {
+ isc_throw(MissingKey, "missing code in option " << option->str());
+ }
+}
+
+void
+AdaptorOption::collect(ConstElementPtr option, OptionCodes& codes) {
+ ConstElementPtr name = option->get("name");
+ if (name) {
+ ConstElementPtr space = option->get("space");
+ ConstElementPtr code = option->get("code");
+ string index = space->stringValue() + "@" + name->stringValue();
+ uint16_t val = static_cast<uint16_t>(code->intValue());
+ codes.insert(pair<string, uint16_t>(index, val));
+ }
+}
+
+void
+AdaptorOption::setCode(ElementPtr option, const OptionCodes& codes) {
+ ConstElementPtr code = option->get("code");
+ if (!code) {
+ ConstElementPtr name = option->get("name");
+ if (!name) {
+ isc_throw(MissingKey, "missing name and code in option "
+ << option->str());
+ }
+ ConstElementPtr space = option->get("space");
+ string index = space->stringValue() + "@" + name->stringValue();
+ OptionCodes::const_iterator it = codes.find(index);
+ if (it == codes.end()) {
+ isc_throw(MissingKey, "can't get code from option "
+ << option->str());
+ }
+ option->set("code", Element::create(static_cast<int>(it->second)));
+ }
+}
+
+void
+AdaptorOption::initCodes(OptionCodes& codes, const string& space) {
+ if (space == DHCP4_OPTION_SPACE) {
+ initCodesInternal(codes, space, STANDARD_V4_OPTION_DEFINITIONS,
+ STANDARD_V4_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, space, LAST_RESORT_V4_OPTION_DEFINITIONS,
+ LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, "vendor-4491",
+ DOCSIS3_V4_OPTION_DEFINITIONS,
+ DOCSIS3_V4_OPTION_DEFINITIONS_SIZE);
+ } else if (space == DHCP6_OPTION_SPACE) {
+ initCodesInternal(codes, space, STANDARD_V6_OPTION_DEFINITIONS,
+ STANDARD_V6_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, "vendor-4491",
+ DOCSIS3_V6_OPTION_DEFINITIONS,
+ DOCSIS3_V6_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, MAPE_V6_OPTION_SPACE,
+ MAPE_V6_OPTION_DEFINITIONS,
+ MAPE_V6_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, MAPT_V6_OPTION_SPACE,
+ MAPT_V6_OPTION_DEFINITIONS,
+ MAPT_V6_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, LW_V6_OPTION_SPACE,
+ LW_V6_OPTION_DEFINITIONS,
+ LW_V6_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, V4V6_RULE_OPTION_SPACE,
+ V4V6_RULE_OPTION_DEFINITIONS,
+ V4V6_RULE_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, V4V6_BIND_OPTION_SPACE,
+ V4V6_BIND_OPTION_DEFINITIONS,
+ V4V6_BIND_OPTION_DEFINITIONS_SIZE);
+ initCodesInternal(codes, "vendor-2495",
+ ISC_V6_OPTION_DEFINITIONS,
+ ISC_V6_OPTION_DEFINITIONS_SIZE);
+ }
+}
+
+void
+AdaptorOption::initCodesInternal(OptionCodes& codes, const string& space,
+ const OptionDefParams* params,
+ size_t params_size) {
+ for (size_t i = 0; i < params_size; ++i) {
+ string index = space + "@" + params[i].name;
+ codes.insert(pair<string, uint16_t>(index, params[i].code));
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/adaptor_option.h b/src/lib/yang/adaptor_option.h
new file mode 100644
index 0000000..6acbeef
--- /dev/null
+++ b/src/lib/yang/adaptor_option.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2018-2022 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 ISC_ADAPTOR_OPTION_H
+#define ISC_ADAPTOR_OPTION_H 1
+
+#include <dhcp/option_data_types.h>
+#include <yang/adaptor.h>
+
+#include <unordered_map>
+
+namespace isc {
+namespace yang {
+
+/// @brief Map for DHCP option definitions handling code and
+/// an index built from space and name.
+///
+/// The map is used to store space+name to code mappings so for
+/// an option data without a code entry the code entry can be supplied.
+using OptionCodes = std::unordered_map<std::string, uint16_t>;
+
+/// @brief JSON adaptor for option data or definition setting defaults.
+///
+/// Both option data and option definition lists are keyed by the code
+/// and space pair so both must be available in setOptionXXX methods.
+/// For space there is an implicit default so setSpace must be called
+/// to add this default to option entries without space.
+/// Note remaining adaptors assume this was done first.
+///
+/// checkCode and checkType can be used to check if code or type is present
+/// (type is mandatory in option definitions).
+///
+/// A missing code can be found in standard definitions (loaded by initCodes)
+/// or in configuration option definitions (at global and client classes
+/// scopes). setCode uses the space+name to code map to set missing codes
+/// and raises an error when it can't.
+class AdaptorOption {
+public:
+ /// @brief Destructor.
+ virtual ~AdaptorOption() = default;
+
+ /// @brief Set space.
+ ///
+ /// @param option The option.
+ /// @param space The default space name.
+ static void setSpace(isc::data::ElementPtr option,
+ const std::string& space);
+
+ /// @brief Checks if type is specified in option definition.
+ ///
+ /// @param option The option.
+ /// @throw MissingKey if the type is not present.
+ static void checkType(isc::data::ConstElementPtr option);
+
+ /// @brief Check if code is specified in option defintion.
+ ///
+ /// @param option The option.
+ /// @throw MissingKey if the code is not present.
+ static void checkCode(isc::data::ConstElementPtr option);
+
+ /// @brief Collect definition.
+ ///
+ /// This method looks at an option definition and adds the
+ /// space+name to code mapping into the OptionCodes codes store
+ /// aka definitions.
+ ///
+ /// @param option The option definition.
+ /// @param codes The reference to option definitions.
+ static void collect(isc::data::ConstElementPtr option, OptionCodes& codes);
+
+ /// @brief Set code from name and definitions.
+ ///
+ /// @param option The option data.
+ /// @param codes Option definitions.
+ static void setCode(isc::data::ElementPtr option,
+ const OptionCodes& codes);
+
+ /// @brief Initialize code map.
+ ///
+ /// @param codes The reference to option definitions.
+ /// @param space The space name.
+ static void initCodes(OptionCodes& codes, const std::string& space);
+
+protected:
+ /// @brief Initialize code map from option definition parameters.
+ ///
+ /// @param codes The reference to option definitions.
+ /// @param space The space name.
+ /// @param params Array of option definition parameters
+ /// @param params_size The size of the array.
+ static void initCodesInternal(OptionCodes& codes, const std::string& space,
+ const isc::dhcp::OptionDefParams* params,
+ size_t params_size);
+}; // AdaptorOption
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_ADAPTOR_OPTION_H
diff --git a/src/lib/yang/adaptor_pool.cc b/src/lib/yang/adaptor_pool.cc
new file mode 100644
index 0000000..6d9aeae
--- /dev/null
+++ b/src/lib/yang/adaptor_pool.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2018-2022 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 <yang/adaptor_pool.h>
+#include <yang/yang_models.h>
+
+#include <vector>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace yang {
+
+void
+AdaptorPool::canonizePool(ElementPtr pool) {
+ const string& orig = pool->get("pool")->stringValue();
+ vector<char> v;
+ for (char ch : orig) {
+ if ((ch == ' ') || (ch == '\t') || (ch == '\n')) {
+ continue;
+ } else if (ch == '-') {
+ v.push_back(' ');
+ v.push_back(ch);
+ v.push_back(' ');
+ } else {
+ v.push_back(ch);
+ }
+ }
+ string canon;
+ canon.assign(v.begin(), v.end());
+ if (orig != canon) {
+ pool->set("pool", Element::create(canon));
+ }
+}
+
+void
+AdaptorPool::fromSubnet(const string& model, ConstElementPtr subnet,
+ ConstElementPtr pools) {
+ if (model == IETF_DHCPV6_SERVER) {
+ fromSubnetIetf6(subnet, pools);
+ } else if ((model != KEA_DHCP4_SERVER) &&
+ (model != KEA_DHCP6_SERVER)) {
+ isc_throw(NotImplemented,
+ "fromSubnet not implemented for the model: " << model);
+ }
+}
+
+void
+AdaptorPool::fromSubnetIetf6(ConstElementPtr subnet, ConstElementPtr pools) {
+ Adaptor::fromParent("valid-lifetime", subnet, pools);
+ Adaptor::fromParent("preferred-lifetime", subnet, pools);
+ Adaptor::fromParent("renew-timer", subnet, pools);
+ Adaptor::fromParent("rebind-timer", subnet, pools);
+}
+
+void
+AdaptorPool::toSubnet(const string& model, ElementPtr subnet,
+ ConstElementPtr pools) {
+ if (model == IETF_DHCPV6_SERVER) {
+ toSubnetIetf6(subnet, pools);
+ } else if ((model != KEA_DHCP4_SERVER) &&
+ (model != KEA_DHCP6_SERVER)) {
+ isc_throw(NotImplemented,
+ "toSubnet not implemented for the model: " << model);
+ }
+}
+
+void
+AdaptorPool::toSubnetIetf6(ElementPtr subnet, ConstElementPtr pools) {
+ Adaptor::toParent("valid-lifetime", subnet, pools);
+ Adaptor::toParent("preferred-lifetime", subnet, pools);
+ Adaptor::toParent("renew-timer", subnet, pools);
+ Adaptor::toParent("rebind-timer", subnet, pools);
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/adaptor_pool.h b/src/lib/yang/adaptor_pool.h
new file mode 100644
index 0000000..b82458b
--- /dev/null
+++ b/src/lib/yang/adaptor_pool.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2018-2022 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 ISC_ADAPTOR_POOL_H
+#define ISC_ADAPTOR_POOL_H 1
+
+#include <yang/adaptor.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief JSON adaptor for pools between canonical Kea and YANG models.
+///
+/// First adaptor canonizePool checks and fixes if needed the pool entry
+/// to a canonical form which is no space for prefix and one space each
+/// side of the minus character for ranges.
+///
+/// Second adaptor is specific to the IETF DHCPv6 model (and does nothing
+/// with a Kea DHCP model): it moves timer definitions from the subnet scope,
+/// i.e. the scope where they are in Kea, to the pool scope, i.e. the scope
+/// where they are in the IETF model, and back. The from way leaves timers
+/// in the subnet scope as they are ignored by the translator, the to way
+/// removes timers from pools as they are not expected by Kea at this scope.
+class AdaptorPool {
+public:
+ /// @brief Destructor.
+ virtual ~AdaptorPool() = default;
+
+ /// @brief Canonize pool.
+ ///
+ /// Remove spaces and replace "-" by " - " for readability.
+ /// @param pool The pool.
+ static void canonizePool(isc::data::ElementPtr pool);
+
+ /// @brief Moves parameters from subnets to pools.
+ ///
+ /// Move parameters from the subnet to each pool. Currently the only
+ /// supported model is ietf-dhcpv6-server. The parameters moved are
+ /// valid-lifetime, preferred-lifetime, renew-timer, rebind-timer.
+ ///
+ /// @param model Model name.
+ /// @param subnet The subnet element.
+ /// @param pools The children pools.
+ /// @throw NotImplemented on unexpected model.
+ static void fromSubnet(const std::string& model,
+ isc::data::ConstElementPtr subnet,
+ isc::data::ConstElementPtr pools);
+
+ /// @brief Move parameters from pools to the subnet.
+ ///
+ /// Move parameters from pool to its parent subnet. Currently the only
+ /// supported model is ietf-dhcpv6-server. The parameters moved are
+ /// valid-lifetime, preferred-lifetime, renew-timer, rebind-timer.
+ ///
+ /// @note: currently it is the only from YANG to JSON aka
+ /// post-processing adaptor.
+ ///
+ /// @param model Model name.
+ /// @param subnet The subnet element.
+ /// @param pools The children pools.
+ /// @throw NotImplemented on unexpected model.
+ /// @throw BadValue on inconsistent (different timer values) pools.
+ static void toSubnet(const std::string& model,
+ isc::data::ElementPtr subnet,
+ isc::data::ConstElementPtr pools);
+
+protected:
+ /// @brief From subnets for ietf-dhcpv6-server.
+ ///
+ /// Use common and move valid-lifetime and preferred-lifetime.
+ ///
+ /// @param subnet The subnet element.
+ /// @param pools The children pools.
+ static void fromSubnetIetf6(isc::data::ConstElementPtr subnet,
+ isc::data::ConstElementPtr pools);
+
+ /// @brief To subnet for ietf-dhcpv6-server.
+ ///
+ /// Use common and move valid-lifetime and preferred-lifetime.
+ ///
+ /// @param subnet The subnet element.
+ /// @param pools The children pools.
+ static void toSubnetIetf6(isc::data::ElementPtr subnet,
+ isc::data::ConstElementPtr pools);
+}; // AdaptorPool
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_ADAPTOR_POOL_H
diff --git a/src/lib/yang/adaptor_subnet.cc b/src/lib/yang/adaptor_subnet.cc
new file mode 100644
index 0000000..0beba82
--- /dev/null
+++ b/src/lib/yang/adaptor_subnet.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 2018-2022 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 <dhcpsrv/subnet_id.h>
+#include <yang/adaptor_subnet.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace isc {
+namespace yang {
+
+bool
+AdaptorSubnet::collectID(ConstElementPtr subnet, SubnetIDSet& set) {
+ ConstElementPtr id = subnet->get("id");
+ if (id) {
+ set.insert(static_cast<SubnetID>(id->intValue()));
+ return (true);
+ }
+ return (false);
+}
+
+void
+AdaptorSubnet::assignID(ElementPtr subnet, SubnetIDSet& set, SubnetID& next) {
+ ConstElementPtr id = subnet->get("id");
+ if (!id) {
+ // Skip already used.
+ while (set.count(next) > 0) {
+ ++next;
+ }
+ subnet->set("id", Element::create(static_cast<long long>(next)));
+ set.insert(next);
+ ++next;
+ }
+}
+
+void
+AdaptorSubnet::updateRelay(ElementPtr subnet) {
+ ConstElementPtr relay = subnet->get("relay");
+ if (!relay) {
+ return;
+ }
+ ConstElementPtr addresses = relay->get("ip-addresses");
+ if (!addresses) {
+ ConstElementPtr address = relay->get("ip-address");
+ if (!address) {
+ subnet->remove("relay");
+ return;
+ }
+ ElementPtr addr = Element::create(address->stringValue());
+ ElementPtr addrs = Element::createList();
+ addrs->add(addr);
+ ElementPtr updated = Element::createMap();
+ updated->set("ip-addresses", addrs);
+ subnet->set("relay", updated);
+ } else if (addresses->size() == 0) {
+ subnet->remove("relay");
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/adaptor_subnet.h b/src/lib/yang/adaptor_subnet.h
new file mode 100644
index 0000000..f357eb9
--- /dev/null
+++ b/src/lib/yang/adaptor_subnet.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2018-2022 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 ISC_ADAPTOR_SUBNET_H
+#define ISC_ADAPTOR_SUBNET_H 1
+
+#include <dhcpsrv/subnet_id.h>
+#include <yang/adaptor.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief JSON adaptor for subnets adding IDs and canonizes relays.
+///
+/// Adding IDs is done in two passes walking through subnets.
+/// -1- Add in the set used values and return false when there is no ID
+/// so the caller can decide if the second pass is needed.
+/// -2- For a subnet without an ID, assigned the next unused ID.
+///
+/// For relays an old syntax ip-address is translated into a new syntax
+/// ip-addresses. Note as all canonization adaptor it is optional, i.e.,
+/// code should work without it.
+class AdaptorSubnet {
+public:
+ /// @brief Destructor.
+ virtual ~AdaptorSubnet() = default;
+
+ /// @brief Collect a subnet ID.
+ ///
+ /// @param subnet The subnet.
+ /// @param set The reference to the set of assigned IDs.
+ /// @return True if the subnet has an ID, false otherwise.
+ static bool collectID(isc::data::ConstElementPtr subnet,
+ isc::dhcp::SubnetIDSet& set);
+
+ /// @brief Assign subnet ID.
+ ///
+ /// @param subnet The subnet.
+ /// @param set The reference to the set of assigned IDs.
+ /// @param next The next ID.
+ static void assignID(isc::data::ElementPtr subnet,
+ isc::dhcp::SubnetIDSet& set,
+ isc::dhcp::SubnetID& next);
+
+ /// @brief Update relay.
+ ///
+ /// Force the use of ip-addresses when it finds an ip-address entry.
+ /// Can be used for shared networks too.
+ ///
+ /// @param subnet The subnet.
+ static void updateRelay(isc::data::ElementPtr subnet);
+}; // AdaptorSubnet
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_ADAPTOR_SUBNET_H
diff --git a/src/lib/yang/netconf_error.h b/src/lib/yang/netconf_error.h
new file mode 100644
index 0000000..17aed1b
--- /dev/null
+++ b/src/lib/yang/netconf_error.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2018-2022 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 ISC_SYSREPO_ERROR_H
+#define ISC_SYSREPO_ERROR_H 1
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief Missing node error
+struct MissingNode : public Exception {
+ MissingNode(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+}; // MissingNode
+
+/// @brief Missing key error
+struct MissingKey : public MissingNode {
+ MissingKey(const char* file, size_t line, const char* what) :
+ MissingNode(file, line, what)
+ {}
+}; // MissingKey
+
+/// @brief Generic NETCONF error
+struct NetconfError : public Exception {
+ NetconfError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+}; // NetconfError
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_SYSREPO_ERROR_H
diff --git a/src/lib/yang/pretests/Makefile.am b/src/lib/yang/pretests/Makefile.am
new file mode 100644
index 0000000..17c9b20
--- /dev/null
+++ b/src/lib/yang/pretests/Makefile.am
@@ -0,0 +1,25 @@
+CLEANFILES = *.gcno *.gcda
+
+TESTS = sysrepo_setup_tests
+
+sysrepo_setup_tests_SOURCES = sysrepo_setup_tests.cc
+sysrepo_setup_tests_CPPFLAGS =
+sysrepo_setup_tests_CPPFLAGS += -I$(top_builddir)/src/lib
+sysrepo_setup_tests_CPPFLAGS += -I$(top_srcdir)/src/lib
+sysrepo_setup_tests_CPPFLAGS += -DKEATEST_MODULE
+sysrepo_setup_tests_CPPFLAGS += $(LIBYANG_CPPFLAGS)
+sysrepo_setup_tests_CPPFLAGS += $(LIBYANG_INCLUDEDIR)
+sysrepo_setup_tests_CPPFLAGS += $(LIBYANGCPP_CPPFLAGS)
+sysrepo_setup_tests_CPPFLAGS += $(LIBYANGCPP_INCLUDEDIR)
+sysrepo_setup_tests_CPPFLAGS += $(SYSREPO_CPPFLAGS)
+sysrepo_setup_tests_CPPFLAGS += $(SYSREPO_INCLUDEDIR)
+sysrepo_setup_tests_CPPFLAGS += $(SYSREPOCPP_CPPFLAGS)
+sysrepo_setup_tests_CPPFLAGS += $(SYSREPOCPP_INCLUDEDIR)
+sysrepo_setup_tests_LDFLAGS = $(AM_LDFLAGS)
+sysrepo_setup_tests_LDADD =
+sysrepo_setup_tests_LDADD += $(LIBYANG_LIBS)
+sysrepo_setup_tests_LDADD += $(LIBYANGCPP_LIBS)
+sysrepo_setup_tests_LDADD += $(SYSREPO_LIBS)
+sysrepo_setup_tests_LDADD += $(SYSREPOCPP_LIBS)
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/yang/pretests/Makefile.in b/src/lib/yang/pretests/Makefile.in
new file mode 100644
index 0000000..4b6ea52
--- /dev/null
+++ b/src/lib/yang/pretests/Makefile.in
@@ -0,0 +1,853 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 = sysrepo_setup_tests$(EXEEXT)
+noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/lib/yang/pretests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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__EXEEXT_1 = sysrepo_setup_tests$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+am_sysrepo_setup_tests_OBJECTS = \
+ sysrepo_setup_tests-sysrepo_setup_tests.$(OBJEXT)
+sysrepo_setup_tests_OBJECTS = $(am_sysrepo_setup_tests_OBJECTS)
+am__DEPENDENCIES_1 =
+sysrepo_setup_tests_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(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 =
+sysrepo_setup_tests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(sysrepo_setup_tests_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)/sysrepo_setup_tests-sysrepo_setup_tests.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 =
+SOURCES = $(sysrepo_setup_tests_SOURCES)
+DIST_SOURCES = $(sysrepo_setup_tests_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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+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; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = *.gcno *.gcda
+sysrepo_setup_tests_SOURCES = sysrepo_setup_tests.cc
+sysrepo_setup_tests_CPPFLAGS = -I$(top_builddir)/src/lib \
+ -I$(top_srcdir)/src/lib -DKEATEST_MODULE $(LIBYANG_CPPFLAGS) \
+ $(LIBYANG_INCLUDEDIR) $(LIBYANGCPP_CPPFLAGS) \
+ $(LIBYANGCPP_INCLUDEDIR) $(SYSREPO_CPPFLAGS) \
+ $(SYSREPO_INCLUDEDIR) $(SYSREPOCPP_CPPFLAGS) \
+ $(SYSREPOCPP_INCLUDEDIR)
+sysrepo_setup_tests_LDFLAGS = $(AM_LDFLAGS)
+sysrepo_setup_tests_LDADD = $(LIBYANG_LIBS) $(LIBYANGCPP_LIBS) \
+ $(SYSREPO_LIBS) $(SYSREPOCPP_LIBS)
+all: all-am
+
+.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/yang/pretests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/yang/pretests/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
+
+sysrepo_setup_tests$(EXEEXT): $(sysrepo_setup_tests_OBJECTS) $(sysrepo_setup_tests_DEPENDENCIES) $(EXTRA_sysrepo_setup_tests_DEPENDENCIES)
+ @rm -f sysrepo_setup_tests$(EXEEXT)
+ $(AM_V_CXXLD)$(sysrepo_setup_tests_LINK) $(sysrepo_setup_tests_OBJECTS) $(sysrepo_setup_tests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.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 $@ $<
+
+sysrepo_setup_tests-sysrepo_setup_tests.o: sysrepo_setup_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sysrepo_setup_tests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT sysrepo_setup_tests-sysrepo_setup_tests.o -MD -MP -MF $(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Tpo -c -o sysrepo_setup_tests-sysrepo_setup_tests.o `test -f 'sysrepo_setup_tests.cc' || echo '$(srcdir)/'`sysrepo_setup_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Tpo $(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sysrepo_setup_tests.cc' object='sysrepo_setup_tests-sysrepo_setup_tests.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) $(sysrepo_setup_tests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o sysrepo_setup_tests-sysrepo_setup_tests.o `test -f 'sysrepo_setup_tests.cc' || echo '$(srcdir)/'`sysrepo_setup_tests.cc
+
+sysrepo_setup_tests-sysrepo_setup_tests.obj: sysrepo_setup_tests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(sysrepo_setup_tests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT sysrepo_setup_tests-sysrepo_setup_tests.obj -MD -MP -MF $(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Tpo -c -o sysrepo_setup_tests-sysrepo_setup_tests.obj `if test -f 'sysrepo_setup_tests.cc'; then $(CYGPATH_W) 'sysrepo_setup_tests.cc'; else $(CYGPATH_W) '$(srcdir)/sysrepo_setup_tests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Tpo $(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='sysrepo_setup_tests.cc' object='sysrepo_setup_tests-sysrepo_setup_tests.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) $(sysrepo_setup_tests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o sysrepo_setup_tests-sysrepo_setup_tests.obj `if test -f 'sysrepo_setup_tests.cc'; then $(CYGPATH_W) 'sysrepo_setup_tests.cc'; else $(CYGPATH_W) '$(srcdir)/sysrepo_setup_tests.cc'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS)
+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 clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/sysrepo_setup_tests-sysrepo_setup_tests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: 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 \
+ 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/yang/pretests/sysrepo_setup_tests.cc b/src/lib/yang/pretests/sysrepo_setup_tests.cc
new file mode 100644
index 0000000..9af2386
--- /dev/null
+++ b/src/lib/yang/pretests/sysrepo_setup_tests.cc
@@ -0,0 +1,119 @@
+// Copyright (C) 2018-2022 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 <yang/yang_revisions.h>
+
+#include <sysrepo-cpp/Connection.hpp>
+#include <sysrepo-cpp/Session.hpp>
+
+#include <iostream>
+#include <unordered_map>
+#include <sstream>
+#include <vector>
+
+using namespace std;
+using namespace libyang;
+using namespace sysrepo;
+using namespace isc::yang;
+
+using libyang::Context;
+using libyang::Module;
+
+/// @brief Returns nicely formed error message if module is missing
+///
+/// @param name name of the YANG module to complain about
+/// @param revision revision of the YANG module
+/// @return a text explaining what the problem is and how to fix it
+string missingModuleText(const string& name, const string& revision) {
+ stringstream tmp;
+ tmp << "ERROR: YANG module " << name << " is not installed." << endl
+ << "The environment is not suitable for running unit tests." << endl
+ << "Please install the module " << name << ":" << endl
+ << "$ sysrepoctl -i ./src/share/yang/modules/" << name << "@" << revision << ".yang" << endl
+ << "Or reinstall all modules:" << endl
+ << "$ ./src/share/yang/modules/utils/reinstall.sh -u" << endl
+ << endl;
+ return (tmp.str());
+}
+
+/// @brief Returns nicely formed error message if module does not have
+/// the expected revision.
+///
+/// @param name name of the YANG module to complain about
+/// @param expected expected revision of the YANG module
+/// @param got got (bad) revision of the YANG module
+string badRevisionModuleText(const string& name, const string& expected,
+ const string& got) {
+ stringstream tmp;
+ tmp << endl
+ << "ERROR: YANG module " << name << " is not installed with the right "
+ << "revision: got " << got << ", but expected " << expected << "." << endl
+ << "Please remove the module " << name << " and reinstall it: " << endl
+ << "$ sysrepoctl -u " << name << endl
+ << "$ sysrepoctl -i ./src/share/yang/modules/" << name << "@" << expected << ".yang" << endl
+ << "Or reinstall all modules:" << endl
+ << "$ ./src/share/yang/modules/utils/reinstall.sh -u" << endl
+ << endl;
+ return (tmp.str());
+}
+
+/// @brief Checks sysrepo setup:
+/// - connection establishment
+/// - session establishment
+/// - test module
+/// - type modules
+/// - IETF module
+/// - Kea modules.
+/// - daemon required
+int main() {
+ optional<Session> sess;
+ try {
+ sess = Connection{}.sessionStart();
+ sess->switchDatastore(sysrepo::Datastore::Candidate);
+ } catch (Error const& ex) {
+ cerr << "ERROR: Can't establish a sysrepo session: "
+ << ex.what() << endl;
+ return (2);
+ }
+
+ vector<Module> modules;
+ try {
+ Context const& context(sess->getContext());
+ modules = context.modules();
+ } catch (Error const& ex) {
+ cerr << "ERROR: Can't retrieve available modules: " << ex.what() << endl;
+ return (3);
+ }
+
+ std::unordered_map<std::string, std::string> installed_modules;
+ for (Module const& module : modules) {
+ string const name(module.name());
+ if (!module.revision()) {
+ cerr << "ERROR: module " << name << " has no revision." << endl;
+ return (5);
+ }
+ string const revision(*module.revision());
+ installed_modules.emplace(name, revision);
+ }
+
+ for (auto const& kv : YANG_REVISIONS) {
+ std::string const& name(kv.first);
+ std::string const& expected_revision(kv.second);
+ if (!installed_modules.count(name)) {
+ cerr << missingModuleText(name, expected_revision);
+ return (6);
+ }
+ string const& revision(installed_modules.at(name));
+ if (expected_revision != revision) {
+ cerr << badRevisionModuleText(name, expected_revision, revision);
+ return (7);
+ }
+ }
+
+ return 0;
+}
diff --git a/src/lib/yang/tests/Makefile.am b/src/lib/yang/tests/Makefile.am
new file mode 100644
index 0000000..028f3d9
--- /dev/null
+++ b/src/lib/yang/tests/Makefile.am
@@ -0,0 +1,70 @@
+AM_CPPFLAGS =
+AM_CPPFLAGS += -DKEATEST_MODULE
+AM_CPPFLAGS += -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(LIBYANG_CPPFLAGS)
+AM_CPPFLAGS += $(LIBYANG_INCLUDEDIR)
+AM_CPPFLAGS += $(LIBYANGCPP_CPPFLAGS)
+AM_CPPFLAGS += $(LIBYANGCPP_INCLUDEDIR)
+AM_CPPFLAGS += $(SYSREPO_CPPFLAGS)
+AM_CPPFLAGS += $(SYSREPO_INCLUDEDIR)
+AM_CPPFLAGS += $(SYSREPOCPP_CPPFLAGS)
+AM_CPPFLAGS += $(SYSREPOCPP_INCLUDEDIR)
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples\"
+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 = json_configs.h yang_configs.h
+run_unittests_SOURCES += adaptor_unittests.cc
+run_unittests_SOURCES += adaptor_option_unittests.cc
+run_unittests_SOURCES += adaptor_pool_unittests.cc
+run_unittests_SOURCES += adaptor_host_unittests.cc
+run_unittests_SOURCES += adaptor_subnet_unittests.cc
+run_unittests_SOURCES += adaptor_config_unittests.cc
+run_unittests_SOURCES += sysrepo_setup.h
+run_unittests_SOURCES += translator_unittests.cc
+run_unittests_SOURCES += translator_control_socket_unittests.cc
+run_unittests_SOURCES += translator_database_unittests.cc
+run_unittests_SOURCES += translator_logger_unittests.cc
+run_unittests_SOURCES += translator_option_data_unittests.cc
+run_unittests_SOURCES += translator_option_def_unittests.cc
+run_unittests_SOURCES += translator_class_unittests.cc
+run_unittests_SOURCES += translator_pool_unittests.cc
+run_unittests_SOURCES += translator_pd_pool_unittests.cc
+run_unittests_SOURCES += translator_host_unittests.cc
+run_unittests_SOURCES += translator_subnet_unittests.cc
+run_unittests_SOURCES += translator_shared_network_unittests.cc
+run_unittests_SOURCES += translator_utils_unittests.cc
+run_unittests_SOURCES += config_unittests.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = $(top_builddir)/src/lib/yang/testutils/libyangtest.la
+run_unittests_LDADD += $(top_builddir)/src/lib/yang/libkea-yang.la
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.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 += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD)
+run_unittests_LDADD += $(LIBYANG_LIBS)
+run_unittests_LDADD += $(LIBYANGCPP_LIBS)
+run_unittests_LDADD += $(SYSREPO_LIBS)
+run_unittests_LDADD += $(SYSREPOCPP_LIBS)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/yang/tests/Makefile.in b/src/lib/yang/tests/Makefile.in
new file mode 100644
index 0000000..6c5989a
--- /dev/null
+++ b/src/lib/yang/tests/Makefile.in
@@ -0,0 +1,1315 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/yang/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 = json_configs.h yang_configs.h \
+ adaptor_unittests.cc adaptor_option_unittests.cc \
+ adaptor_pool_unittests.cc adaptor_host_unittests.cc \
+ adaptor_subnet_unittests.cc adaptor_config_unittests.cc \
+ sysrepo_setup.h translator_unittests.cc \
+ translator_control_socket_unittests.cc \
+ translator_database_unittests.cc \
+ translator_logger_unittests.cc \
+ translator_option_data_unittests.cc \
+ translator_option_def_unittests.cc \
+ translator_class_unittests.cc translator_pool_unittests.cc \
+ translator_pd_pool_unittests.cc translator_host_unittests.cc \
+ translator_subnet_unittests.cc \
+ translator_shared_network_unittests.cc \
+ translator_utils_unittests.cc config_unittests.cc \
+ run_unittests.cc
+@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ run_unittests-adaptor_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-adaptor_option_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-adaptor_pool_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-adaptor_host_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-adaptor_subnet_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-adaptor_config_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_control_socket_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_database_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_logger_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_option_data_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_option_def_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_class_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_pool_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_pd_pool_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_host_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_subnet_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_shared_network_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-translator_utils_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ run_unittests-config_unittests.$(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 = $(top_builddir)/src/lib/yang/testutils/libyangtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/libkea-yang.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+@HAVE_GTEST_TRUE@ $(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-adaptor_config_unittests.Po \
+ ./$(DEPDIR)/run_unittests-adaptor_host_unittests.Po \
+ ./$(DEPDIR)/run_unittests-adaptor_option_unittests.Po \
+ ./$(DEPDIR)/run_unittests-adaptor_pool_unittests.Po \
+ ./$(DEPDIR)/run_unittests-adaptor_subnet_unittests.Po \
+ ./$(DEPDIR)/run_unittests-adaptor_unittests.Po \
+ ./$(DEPDIR)/run_unittests-config_unittests.Po \
+ ./$(DEPDIR)/run_unittests-run_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_class_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_control_socket_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_database_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_host_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_logger_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_option_data_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_option_def_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_pd_pool_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_pool_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_shared_network_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_subnet_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_unittests.Po \
+ ./$(DEPDIR)/run_unittests-translator_utils_unittests.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)
+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)
+# 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)`
+ETAGS = etags
+CTAGS = ctags
+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; \
+}
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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@
+AM_CPPFLAGS = -DKEATEST_MODULE -I$(top_builddir)/src/lib \
+ -I$(top_srcdir)/src/lib $(BOOST_INCLUDES) $(LIBYANG_CPPFLAGS) \
+ $(LIBYANG_INCLUDEDIR) $(LIBYANGCPP_CPPFLAGS) \
+ $(LIBYANGCPP_INCLUDEDIR) $(SYSREPO_CPPFLAGS) \
+ $(SYSREPO_INCLUDEDIR) $(SYSREPOCPP_CPPFLAGS) \
+ $(SYSREPOCPP_INCLUDEDIR) \
+ -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples\"
+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 = json_configs.h yang_configs.h \
+@HAVE_GTEST_TRUE@ adaptor_unittests.cc \
+@HAVE_GTEST_TRUE@ adaptor_option_unittests.cc \
+@HAVE_GTEST_TRUE@ adaptor_pool_unittests.cc \
+@HAVE_GTEST_TRUE@ adaptor_host_unittests.cc \
+@HAVE_GTEST_TRUE@ adaptor_subnet_unittests.cc \
+@HAVE_GTEST_TRUE@ adaptor_config_unittests.cc sysrepo_setup.h \
+@HAVE_GTEST_TRUE@ translator_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_control_socket_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_database_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_logger_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_option_data_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_option_def_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_class_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_pool_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_pd_pool_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_host_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_subnet_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_shared_network_unittests.cc \
+@HAVE_GTEST_TRUE@ translator_utils_unittests.cc \
+@HAVE_GTEST_TRUE@ config_unittests.cc run_unittests.cc
+@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@run_unittests_LDADD = $(top_builddir)/src/lib/yang/testutils/libyangtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/libkea-yang.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \
+@HAVE_GTEST_TRUE@ $(GTEST_LDADD) $(LIBYANG_LIBS) \
+@HAVE_GTEST_TRUE@ $(LIBYANGCPP_LIBS) $(SYSREPO_LIBS) \
+@HAVE_GTEST_TRUE@ $(SYSREPOCPP_LIBS)
+all: all-am
+
+.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/yang/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/yang/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-adaptor_config_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-adaptor_host_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-adaptor_option_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-adaptor_pool_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-adaptor_subnet_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-adaptor_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-config_unittests.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-translator_class_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_control_socket_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_database_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_host_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_logger_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_option_data_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_option_def_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_pd_pool_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_pool_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_shared_network_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_subnet_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-translator_utils_unittests.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-adaptor_unittests.o: adaptor_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_unittests.Tpo -c -o run_unittests-adaptor_unittests.o `test -f 'adaptor_unittests.cc' || echo '$(srcdir)/'`adaptor_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_unittests.cc' object='run_unittests-adaptor_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-adaptor_unittests.o `test -f 'adaptor_unittests.cc' || echo '$(srcdir)/'`adaptor_unittests.cc
+
+run_unittests-adaptor_unittests.obj: adaptor_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_unittests.Tpo -c -o run_unittests-adaptor_unittests.obj `if test -f 'adaptor_unittests.cc'; then $(CYGPATH_W) 'adaptor_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_unittests.cc' object='run_unittests-adaptor_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-adaptor_unittests.obj `if test -f 'adaptor_unittests.cc'; then $(CYGPATH_W) 'adaptor_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_unittests.cc'; fi`
+
+run_unittests-adaptor_option_unittests.o: adaptor_option_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_option_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_option_unittests.Tpo -c -o run_unittests-adaptor_option_unittests.o `test -f 'adaptor_option_unittests.cc' || echo '$(srcdir)/'`adaptor_option_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_option_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_option_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_option_unittests.cc' object='run_unittests-adaptor_option_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-adaptor_option_unittests.o `test -f 'adaptor_option_unittests.cc' || echo '$(srcdir)/'`adaptor_option_unittests.cc
+
+run_unittests-adaptor_option_unittests.obj: adaptor_option_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_option_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_option_unittests.Tpo -c -o run_unittests-adaptor_option_unittests.obj `if test -f 'adaptor_option_unittests.cc'; then $(CYGPATH_W) 'adaptor_option_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_option_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_option_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_option_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_option_unittests.cc' object='run_unittests-adaptor_option_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-adaptor_option_unittests.obj `if test -f 'adaptor_option_unittests.cc'; then $(CYGPATH_W) 'adaptor_option_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_option_unittests.cc'; fi`
+
+run_unittests-adaptor_pool_unittests.o: adaptor_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_pool_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_pool_unittests.Tpo -c -o run_unittests-adaptor_pool_unittests.o `test -f 'adaptor_pool_unittests.cc' || echo '$(srcdir)/'`adaptor_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_pool_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_pool_unittests.cc' object='run_unittests-adaptor_pool_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-adaptor_pool_unittests.o `test -f 'adaptor_pool_unittests.cc' || echo '$(srcdir)/'`adaptor_pool_unittests.cc
+
+run_unittests-adaptor_pool_unittests.obj: adaptor_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_pool_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_pool_unittests.Tpo -c -o run_unittests-adaptor_pool_unittests.obj `if test -f 'adaptor_pool_unittests.cc'; then $(CYGPATH_W) 'adaptor_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_pool_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_pool_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_pool_unittests.cc' object='run_unittests-adaptor_pool_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-adaptor_pool_unittests.obj `if test -f 'adaptor_pool_unittests.cc'; then $(CYGPATH_W) 'adaptor_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_pool_unittests.cc'; fi`
+
+run_unittests-adaptor_host_unittests.o: adaptor_host_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_host_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_host_unittests.Tpo -c -o run_unittests-adaptor_host_unittests.o `test -f 'adaptor_host_unittests.cc' || echo '$(srcdir)/'`adaptor_host_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_host_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_host_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_host_unittests.cc' object='run_unittests-adaptor_host_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-adaptor_host_unittests.o `test -f 'adaptor_host_unittests.cc' || echo '$(srcdir)/'`adaptor_host_unittests.cc
+
+run_unittests-adaptor_host_unittests.obj: adaptor_host_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_host_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_host_unittests.Tpo -c -o run_unittests-adaptor_host_unittests.obj `if test -f 'adaptor_host_unittests.cc'; then $(CYGPATH_W) 'adaptor_host_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_host_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_host_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_host_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_host_unittests.cc' object='run_unittests-adaptor_host_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-adaptor_host_unittests.obj `if test -f 'adaptor_host_unittests.cc'; then $(CYGPATH_W) 'adaptor_host_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_host_unittests.cc'; fi`
+
+run_unittests-adaptor_subnet_unittests.o: adaptor_subnet_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_subnet_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_subnet_unittests.Tpo -c -o run_unittests-adaptor_subnet_unittests.o `test -f 'adaptor_subnet_unittests.cc' || echo '$(srcdir)/'`adaptor_subnet_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_subnet_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_subnet_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_subnet_unittests.cc' object='run_unittests-adaptor_subnet_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-adaptor_subnet_unittests.o `test -f 'adaptor_subnet_unittests.cc' || echo '$(srcdir)/'`adaptor_subnet_unittests.cc
+
+run_unittests-adaptor_subnet_unittests.obj: adaptor_subnet_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_subnet_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_subnet_unittests.Tpo -c -o run_unittests-adaptor_subnet_unittests.obj `if test -f 'adaptor_subnet_unittests.cc'; then $(CYGPATH_W) 'adaptor_subnet_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_subnet_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_subnet_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_subnet_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_subnet_unittests.cc' object='run_unittests-adaptor_subnet_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-adaptor_subnet_unittests.obj `if test -f 'adaptor_subnet_unittests.cc'; then $(CYGPATH_W) 'adaptor_subnet_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_subnet_unittests.cc'; fi`
+
+run_unittests-adaptor_config_unittests.o: adaptor_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_config_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_config_unittests.Tpo -c -o run_unittests-adaptor_config_unittests.o `test -f 'adaptor_config_unittests.cc' || echo '$(srcdir)/'`adaptor_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_config_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_config_unittests.cc' object='run_unittests-adaptor_config_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-adaptor_config_unittests.o `test -f 'adaptor_config_unittests.cc' || echo '$(srcdir)/'`adaptor_config_unittests.cc
+
+run_unittests-adaptor_config_unittests.obj: adaptor_config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-adaptor_config_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-adaptor_config_unittests.Tpo -c -o run_unittests-adaptor_config_unittests.obj `if test -f 'adaptor_config_unittests.cc'; then $(CYGPATH_W) 'adaptor_config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_config_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-adaptor_config_unittests.Tpo $(DEPDIR)/run_unittests-adaptor_config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='adaptor_config_unittests.cc' object='run_unittests-adaptor_config_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-adaptor_config_unittests.obj `if test -f 'adaptor_config_unittests.cc'; then $(CYGPATH_W) 'adaptor_config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/adaptor_config_unittests.cc'; fi`
+
+run_unittests-translator_unittests.o: translator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_unittests.Tpo -c -o run_unittests-translator_unittests.o `test -f 'translator_unittests.cc' || echo '$(srcdir)/'`translator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_unittests.Tpo $(DEPDIR)/run_unittests-translator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_unittests.cc' object='run_unittests-translator_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-translator_unittests.o `test -f 'translator_unittests.cc' || echo '$(srcdir)/'`translator_unittests.cc
+
+run_unittests-translator_unittests.obj: translator_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_unittests.Tpo -c -o run_unittests-translator_unittests.obj `if test -f 'translator_unittests.cc'; then $(CYGPATH_W) 'translator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_unittests.Tpo $(DEPDIR)/run_unittests-translator_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_unittests.cc' object='run_unittests-translator_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-translator_unittests.obj `if test -f 'translator_unittests.cc'; then $(CYGPATH_W) 'translator_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_unittests.cc'; fi`
+
+run_unittests-translator_control_socket_unittests.o: translator_control_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_control_socket_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_control_socket_unittests.Tpo -c -o run_unittests-translator_control_socket_unittests.o `test -f 'translator_control_socket_unittests.cc' || echo '$(srcdir)/'`translator_control_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_control_socket_unittests.Tpo $(DEPDIR)/run_unittests-translator_control_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_control_socket_unittests.cc' object='run_unittests-translator_control_socket_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-translator_control_socket_unittests.o `test -f 'translator_control_socket_unittests.cc' || echo '$(srcdir)/'`translator_control_socket_unittests.cc
+
+run_unittests-translator_control_socket_unittests.obj: translator_control_socket_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_control_socket_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_control_socket_unittests.Tpo -c -o run_unittests-translator_control_socket_unittests.obj `if test -f 'translator_control_socket_unittests.cc'; then $(CYGPATH_W) 'translator_control_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_control_socket_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_control_socket_unittests.Tpo $(DEPDIR)/run_unittests-translator_control_socket_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_control_socket_unittests.cc' object='run_unittests-translator_control_socket_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-translator_control_socket_unittests.obj `if test -f 'translator_control_socket_unittests.cc'; then $(CYGPATH_W) 'translator_control_socket_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_control_socket_unittests.cc'; fi`
+
+run_unittests-translator_database_unittests.o: translator_database_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_database_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_database_unittests.Tpo -c -o run_unittests-translator_database_unittests.o `test -f 'translator_database_unittests.cc' || echo '$(srcdir)/'`translator_database_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_database_unittests.Tpo $(DEPDIR)/run_unittests-translator_database_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_database_unittests.cc' object='run_unittests-translator_database_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-translator_database_unittests.o `test -f 'translator_database_unittests.cc' || echo '$(srcdir)/'`translator_database_unittests.cc
+
+run_unittests-translator_database_unittests.obj: translator_database_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_database_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_database_unittests.Tpo -c -o run_unittests-translator_database_unittests.obj `if test -f 'translator_database_unittests.cc'; then $(CYGPATH_W) 'translator_database_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_database_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_database_unittests.Tpo $(DEPDIR)/run_unittests-translator_database_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_database_unittests.cc' object='run_unittests-translator_database_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-translator_database_unittests.obj `if test -f 'translator_database_unittests.cc'; then $(CYGPATH_W) 'translator_database_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_database_unittests.cc'; fi`
+
+run_unittests-translator_logger_unittests.o: translator_logger_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_logger_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_logger_unittests.Tpo -c -o run_unittests-translator_logger_unittests.o `test -f 'translator_logger_unittests.cc' || echo '$(srcdir)/'`translator_logger_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_logger_unittests.Tpo $(DEPDIR)/run_unittests-translator_logger_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_logger_unittests.cc' object='run_unittests-translator_logger_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-translator_logger_unittests.o `test -f 'translator_logger_unittests.cc' || echo '$(srcdir)/'`translator_logger_unittests.cc
+
+run_unittests-translator_logger_unittests.obj: translator_logger_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_logger_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_logger_unittests.Tpo -c -o run_unittests-translator_logger_unittests.obj `if test -f 'translator_logger_unittests.cc'; then $(CYGPATH_W) 'translator_logger_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_logger_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_logger_unittests.Tpo $(DEPDIR)/run_unittests-translator_logger_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_logger_unittests.cc' object='run_unittests-translator_logger_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-translator_logger_unittests.obj `if test -f 'translator_logger_unittests.cc'; then $(CYGPATH_W) 'translator_logger_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_logger_unittests.cc'; fi`
+
+run_unittests-translator_option_data_unittests.o: translator_option_data_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_option_data_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_option_data_unittests.Tpo -c -o run_unittests-translator_option_data_unittests.o `test -f 'translator_option_data_unittests.cc' || echo '$(srcdir)/'`translator_option_data_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_option_data_unittests.Tpo $(DEPDIR)/run_unittests-translator_option_data_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_option_data_unittests.cc' object='run_unittests-translator_option_data_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-translator_option_data_unittests.o `test -f 'translator_option_data_unittests.cc' || echo '$(srcdir)/'`translator_option_data_unittests.cc
+
+run_unittests-translator_option_data_unittests.obj: translator_option_data_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_option_data_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_option_data_unittests.Tpo -c -o run_unittests-translator_option_data_unittests.obj `if test -f 'translator_option_data_unittests.cc'; then $(CYGPATH_W) 'translator_option_data_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_option_data_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_option_data_unittests.Tpo $(DEPDIR)/run_unittests-translator_option_data_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_option_data_unittests.cc' object='run_unittests-translator_option_data_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-translator_option_data_unittests.obj `if test -f 'translator_option_data_unittests.cc'; then $(CYGPATH_W) 'translator_option_data_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_option_data_unittests.cc'; fi`
+
+run_unittests-translator_option_def_unittests.o: translator_option_def_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_option_def_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_option_def_unittests.Tpo -c -o run_unittests-translator_option_def_unittests.o `test -f 'translator_option_def_unittests.cc' || echo '$(srcdir)/'`translator_option_def_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_option_def_unittests.Tpo $(DEPDIR)/run_unittests-translator_option_def_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_option_def_unittests.cc' object='run_unittests-translator_option_def_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-translator_option_def_unittests.o `test -f 'translator_option_def_unittests.cc' || echo '$(srcdir)/'`translator_option_def_unittests.cc
+
+run_unittests-translator_option_def_unittests.obj: translator_option_def_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_option_def_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_option_def_unittests.Tpo -c -o run_unittests-translator_option_def_unittests.obj `if test -f 'translator_option_def_unittests.cc'; then $(CYGPATH_W) 'translator_option_def_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_option_def_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_option_def_unittests.Tpo $(DEPDIR)/run_unittests-translator_option_def_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_option_def_unittests.cc' object='run_unittests-translator_option_def_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-translator_option_def_unittests.obj `if test -f 'translator_option_def_unittests.cc'; then $(CYGPATH_W) 'translator_option_def_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_option_def_unittests.cc'; fi`
+
+run_unittests-translator_class_unittests.o: translator_class_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_class_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_class_unittests.Tpo -c -o run_unittests-translator_class_unittests.o `test -f 'translator_class_unittests.cc' || echo '$(srcdir)/'`translator_class_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_class_unittests.Tpo $(DEPDIR)/run_unittests-translator_class_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_class_unittests.cc' object='run_unittests-translator_class_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-translator_class_unittests.o `test -f 'translator_class_unittests.cc' || echo '$(srcdir)/'`translator_class_unittests.cc
+
+run_unittests-translator_class_unittests.obj: translator_class_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_class_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_class_unittests.Tpo -c -o run_unittests-translator_class_unittests.obj `if test -f 'translator_class_unittests.cc'; then $(CYGPATH_W) 'translator_class_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_class_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_class_unittests.Tpo $(DEPDIR)/run_unittests-translator_class_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_class_unittests.cc' object='run_unittests-translator_class_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-translator_class_unittests.obj `if test -f 'translator_class_unittests.cc'; then $(CYGPATH_W) 'translator_class_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_class_unittests.cc'; fi`
+
+run_unittests-translator_pool_unittests.o: translator_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_pool_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_pool_unittests.Tpo -c -o run_unittests-translator_pool_unittests.o `test -f 'translator_pool_unittests.cc' || echo '$(srcdir)/'`translator_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_pool_unittests.Tpo $(DEPDIR)/run_unittests-translator_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_pool_unittests.cc' object='run_unittests-translator_pool_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-translator_pool_unittests.o `test -f 'translator_pool_unittests.cc' || echo '$(srcdir)/'`translator_pool_unittests.cc
+
+run_unittests-translator_pool_unittests.obj: translator_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_pool_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_pool_unittests.Tpo -c -o run_unittests-translator_pool_unittests.obj `if test -f 'translator_pool_unittests.cc'; then $(CYGPATH_W) 'translator_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_pool_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_pool_unittests.Tpo $(DEPDIR)/run_unittests-translator_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_pool_unittests.cc' object='run_unittests-translator_pool_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-translator_pool_unittests.obj `if test -f 'translator_pool_unittests.cc'; then $(CYGPATH_W) 'translator_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_pool_unittests.cc'; fi`
+
+run_unittests-translator_pd_pool_unittests.o: translator_pd_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_pd_pool_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_pd_pool_unittests.Tpo -c -o run_unittests-translator_pd_pool_unittests.o `test -f 'translator_pd_pool_unittests.cc' || echo '$(srcdir)/'`translator_pd_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_pd_pool_unittests.Tpo $(DEPDIR)/run_unittests-translator_pd_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_pd_pool_unittests.cc' object='run_unittests-translator_pd_pool_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-translator_pd_pool_unittests.o `test -f 'translator_pd_pool_unittests.cc' || echo '$(srcdir)/'`translator_pd_pool_unittests.cc
+
+run_unittests-translator_pd_pool_unittests.obj: translator_pd_pool_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_pd_pool_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_pd_pool_unittests.Tpo -c -o run_unittests-translator_pd_pool_unittests.obj `if test -f 'translator_pd_pool_unittests.cc'; then $(CYGPATH_W) 'translator_pd_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_pd_pool_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_pd_pool_unittests.Tpo $(DEPDIR)/run_unittests-translator_pd_pool_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_pd_pool_unittests.cc' object='run_unittests-translator_pd_pool_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-translator_pd_pool_unittests.obj `if test -f 'translator_pd_pool_unittests.cc'; then $(CYGPATH_W) 'translator_pd_pool_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_pd_pool_unittests.cc'; fi`
+
+run_unittests-translator_host_unittests.o: translator_host_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_host_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_host_unittests.Tpo -c -o run_unittests-translator_host_unittests.o `test -f 'translator_host_unittests.cc' || echo '$(srcdir)/'`translator_host_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_host_unittests.Tpo $(DEPDIR)/run_unittests-translator_host_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_host_unittests.cc' object='run_unittests-translator_host_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-translator_host_unittests.o `test -f 'translator_host_unittests.cc' || echo '$(srcdir)/'`translator_host_unittests.cc
+
+run_unittests-translator_host_unittests.obj: translator_host_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_host_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_host_unittests.Tpo -c -o run_unittests-translator_host_unittests.obj `if test -f 'translator_host_unittests.cc'; then $(CYGPATH_W) 'translator_host_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_host_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_host_unittests.Tpo $(DEPDIR)/run_unittests-translator_host_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_host_unittests.cc' object='run_unittests-translator_host_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-translator_host_unittests.obj `if test -f 'translator_host_unittests.cc'; then $(CYGPATH_W) 'translator_host_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_host_unittests.cc'; fi`
+
+run_unittests-translator_subnet_unittests.o: translator_subnet_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_subnet_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_subnet_unittests.Tpo -c -o run_unittests-translator_subnet_unittests.o `test -f 'translator_subnet_unittests.cc' || echo '$(srcdir)/'`translator_subnet_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_subnet_unittests.Tpo $(DEPDIR)/run_unittests-translator_subnet_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_subnet_unittests.cc' object='run_unittests-translator_subnet_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-translator_subnet_unittests.o `test -f 'translator_subnet_unittests.cc' || echo '$(srcdir)/'`translator_subnet_unittests.cc
+
+run_unittests-translator_subnet_unittests.obj: translator_subnet_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_subnet_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_subnet_unittests.Tpo -c -o run_unittests-translator_subnet_unittests.obj `if test -f 'translator_subnet_unittests.cc'; then $(CYGPATH_W) 'translator_subnet_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_subnet_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_subnet_unittests.Tpo $(DEPDIR)/run_unittests-translator_subnet_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_subnet_unittests.cc' object='run_unittests-translator_subnet_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-translator_subnet_unittests.obj `if test -f 'translator_subnet_unittests.cc'; then $(CYGPATH_W) 'translator_subnet_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_subnet_unittests.cc'; fi`
+
+run_unittests-translator_shared_network_unittests.o: translator_shared_network_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_shared_network_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_shared_network_unittests.Tpo -c -o run_unittests-translator_shared_network_unittests.o `test -f 'translator_shared_network_unittests.cc' || echo '$(srcdir)/'`translator_shared_network_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_shared_network_unittests.Tpo $(DEPDIR)/run_unittests-translator_shared_network_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_shared_network_unittests.cc' object='run_unittests-translator_shared_network_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-translator_shared_network_unittests.o `test -f 'translator_shared_network_unittests.cc' || echo '$(srcdir)/'`translator_shared_network_unittests.cc
+
+run_unittests-translator_shared_network_unittests.obj: translator_shared_network_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_shared_network_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_shared_network_unittests.Tpo -c -o run_unittests-translator_shared_network_unittests.obj `if test -f 'translator_shared_network_unittests.cc'; then $(CYGPATH_W) 'translator_shared_network_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_shared_network_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_shared_network_unittests.Tpo $(DEPDIR)/run_unittests-translator_shared_network_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_shared_network_unittests.cc' object='run_unittests-translator_shared_network_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-translator_shared_network_unittests.obj `if test -f 'translator_shared_network_unittests.cc'; then $(CYGPATH_W) 'translator_shared_network_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_shared_network_unittests.cc'; fi`
+
+run_unittests-translator_utils_unittests.o: translator_utils_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_utils_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-translator_utils_unittests.Tpo -c -o run_unittests-translator_utils_unittests.o `test -f 'translator_utils_unittests.cc' || echo '$(srcdir)/'`translator_utils_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_utils_unittests.Tpo $(DEPDIR)/run_unittests-translator_utils_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_utils_unittests.cc' object='run_unittests-translator_utils_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-translator_utils_unittests.o `test -f 'translator_utils_unittests.cc' || echo '$(srcdir)/'`translator_utils_unittests.cc
+
+run_unittests-translator_utils_unittests.obj: translator_utils_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-translator_utils_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-translator_utils_unittests.Tpo -c -o run_unittests-translator_utils_unittests.obj `if test -f 'translator_utils_unittests.cc'; then $(CYGPATH_W) 'translator_utils_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_utils_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-translator_utils_unittests.Tpo $(DEPDIR)/run_unittests-translator_utils_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_utils_unittests.cc' object='run_unittests-translator_utils_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-translator_utils_unittests.obj `if test -f 'translator_utils_unittests.cc'; then $(CYGPATH_W) 'translator_utils_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/translator_utils_unittests.cc'; fi`
+
+run_unittests-config_unittests.o: config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-config_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-config_unittests.Tpo -c -o run_unittests-config_unittests.o `test -f 'config_unittests.cc' || echo '$(srcdir)/'`config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-config_unittests.Tpo $(DEPDIR)/run_unittests-config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_unittests.cc' object='run_unittests-config_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-config_unittests.o `test -f 'config_unittests.cc' || echo '$(srcdir)/'`config_unittests.cc
+
+run_unittests-config_unittests.obj: config_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-config_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-config_unittests.Tpo -c -o run_unittests-config_unittests.obj `if test -f 'config_unittests.cc'; then $(CYGPATH_W) 'config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-config_unittests.Tpo $(DEPDIR)/run_unittests-config_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='config_unittests.cc' object='run_unittests-config_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-config_unittests.obj `if test -f 'config_unittests.cc'; then $(CYGPATH_W) 'config_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/config_unittests.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
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(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-am
+
+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-am
+
+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
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-am
+all-am: Makefile $(PROGRAMS)
+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 clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_config_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_host_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_option_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_subnet_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-config_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_class_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_control_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_database_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_host_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_logger_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_option_data_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_option_def_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_pd_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_shared_network_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_subnet_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_utils_unittests.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+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 ./$(DEPDIR)/run_unittests-adaptor_config_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_host_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_option_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_subnet_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-adaptor_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-config_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_class_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_control_socket_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_database_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_host_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_logger_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_option_data_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_option_def_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_pd_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_pool_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_shared_network_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_subnet_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_unittests.Po
+ -rm -f ./$(DEPDIR)/run_unittests-translator_utils_unittests.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: check-am install-am install-strip
+
+.PHONY: 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 \
+ 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/yang/tests/adaptor_config_unittests.cc b/src/lib/yang/tests/adaptor_config_unittests.cc
new file mode 100644
index 0000000..2109890
--- /dev/null
+++ b/src/lib/yang/tests/adaptor_config_unittests.cc
@@ -0,0 +1,154 @@
+// Copyright (C) 2018-2023 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 <testutils/gtest_utils.h>
+#include <testutils/io_utils.h>
+#include <testutils/user_context_utils.h>
+#include <yang/adaptor_config.h>
+
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::test;
+using namespace isc::yang;
+
+namespace {
+
+/// @brief Fixture class that helps testing AdaptorConfig
+class AdaptorConfigTest : public ::testing::Test {
+public:
+
+/// @brief Load an example JSON config
+///
+/// @param fname name of the file (expected to be a valid JSON config)
+/// @param v6 - false=v4, true=v6
+/// @param result - JSON converted by a AdaptorConfig::preprocess[4/6]
+void testFile(const string& fname, bool v6, ElementPtr& result) {
+ ElementPtr json;
+ ElementPtr reference_json;
+
+ string decommented = decommentJSONfile(fname);
+
+ EXPECT_NO_THROW_LOG(json = Element::fromJSONFile(decommented, true));
+ reference_json = moveComments(json);
+
+ // remove the temporary file
+ EXPECT_NO_THROW_LOG(::remove(decommented.c_str()));
+
+ string before = json->str();
+ if (v6) {
+ ASSERT_NO_THROW_LOG(AdaptorConfig::preProcess6(json));
+ } else {
+ ASSERT_NO_THROW_LOG(AdaptorConfig::preProcess4(json));
+ }
+ string after = json->str();
+
+ EXPECT_FALSE(before.empty());
+ EXPECT_FALSE(after.empty());
+
+ result = json;
+}
+
+}; // AdaptorConfigTest
+
+TEST_F(AdaptorConfigTest, loadExamples4) {
+ vector<string> configs = {
+ "advanced.json",
+ "all-keys-netconf.json",
+ "all-options.json",
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+ "config-backend.json",
+ "dhcpv4-over-dhcpv6.json",
+ "dnr.json",
+ "global-reservations.json",
+ "ha-load-balancing-server1-mt-with-tls.json",
+ "ha-load-balancing-server2-mt.json",
+ "hooks.json",
+ "hooks-radius.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+ "mysql-reservations.json",
+ "pgsql-reservations.json",
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "single-subnet.json",
+ "vendor-specific.json",
+ "vivso.json",
+ "with-ddns.json",
+ };
+
+ ElementPtr x;
+
+ for (int i = 0; i<configs.size(); i++) {
+ x.reset();
+ testFile(string(CFG_EXAMPLES) + "/kea4/" + configs[i], false, x);
+ ASSERT_TRUE(x);
+ }
+}
+
+TEST_F(AdaptorConfigTest, loadExamples6) {
+ vector<string> configs = {
+ "advanced.json",
+ "all-keys-netconf.json",
+ "all-options.json",
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+ "config-backend.json",
+ "dhcpv4-over-dhcpv6.json",
+ "dnr.json",
+ "duid.json",
+ "global-reservations.json",
+ "ha-hot-standby-server1-with-tls.json",
+ "ha-hot-standby-server2.json",
+ "hooks.json",
+ "iPXE.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+ "mysql-reservations.json",
+ "pgsql-reservations.json",
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "simple.json",
+ "softwire46.json",
+ "stateless.json",
+ "tee-times.json",
+ "with-ddns.json",
+ };
+ ElementPtr x;
+
+ for (int i = 0; i<configs.size(); i++) {
+ x.reset();
+ testFile(string(CFG_EXAMPLES) + "/kea6/" + configs[i], true, x);
+ ASSERT_TRUE(x);
+ }
+}
+
+/// In general, the AdatorConfig class doesn't need very thorough
+/// direct tests, as there will be tests that will process whole
+/// configuration that will in turn use AdaptorConfig calls.
+/// Nevertheless, here are some ideas for tests. We hope to have
+/// them implemented one day.
+///
+/// @todo: Check subnet-id assigned in subnets using any config
+/// @todo: Check shared-networks assign id using kea4/shared-network.json
+/// @todo: Check option classes using kea4/classify2.json
+/// @todo: Check option data using kea6/with-ddns.json
+/// @todo: Check option defs using kea6/dhcpv4-over-dhcpv6.json
+
+} // anonymous namespace
diff --git a/src/lib/yang/tests/adaptor_host_unittests.cc b/src/lib/yang/tests/adaptor_host_unittests.cc
new file mode 100644
index 0000000..0d50632
--- /dev/null
+++ b/src/lib/yang/tests/adaptor_host_unittests.cc
@@ -0,0 +1,119 @@
+// Copyright (C) 2018-2022 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 <testutils/gtest_utils.h>
+#include <yang/adaptor_host.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+
+namespace {
+
+// Verifies that quoteIdentifier does not touch an identifier which
+// has a type different from flex-d.
+TEST(AdaptorHostTest, notFlexId) {
+ string config = "{\n"
+ " \"hw-address\": \"1a:1b:1c:1d:1e:1f\",\n"
+ " \"ip-address\": \"192.0.2.201\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorHost::quoteIdentifier(json));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies that quoteIdentifier does not touch a flex-id identifier
+// without quotes.
+TEST(AdaptorHostTest, noQuote) {
+ string config = "{\n"
+ " \"flex-id\": \"s0mEVaLue\",\n"
+ " \"ip-address\": \"192.0.2.206\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorHost::quoteIdentifier(json));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies that quoteIdentifier removes quotes from a flex-id identifier.
+TEST(AdaptorHostTest, quotes) {
+ string config = "{\n"
+ " \"flex-id\": \"'somevalue'\",\n"
+ " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorHost::quoteIdentifier(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr id = json->get("flex-id");
+ ASSERT_TRUE(id);
+ ASSERT_EQ(Element::string, id->getType());
+ EXPECT_EQ("73:6f:6d:65:76:61:6c:75:65", id->stringValue());
+}
+
+// Verifies that quoteIdentifier removes quotes from a flex-id identifier
+// but does not interpret a quote in the middle.
+TEST(AdaptorHostTest, extraQuote) {
+ string config = "{\n"
+ " \"flex-id\": \"'some'value'\",\n"
+ " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorHost::quoteIdentifier(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr id = json->get("flex-id");
+ ASSERT_TRUE(id);
+ ASSERT_EQ(Element::string, id->getType());
+ EXPECT_EQ("73:6f:6d:65:27:76:61:6c:75:65", id->stringValue());
+}
+
+// Verifies that quoteIdentifier works on not standard characters too.
+TEST(AdaptorHostTest, notStandard) {
+ string config = "{\n"
+ " \"flex-id\": \"'some\\\"value'\",\n"
+ " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorHost::quoteIdentifier(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr id = json->get("flex-id");
+ ASSERT_TRUE(id);
+ ASSERT_EQ(Element::string, id->getType());
+ EXPECT_EQ("73:6f:6d:65:22:76:61:6c:75:65", id->stringValue());
+}
+
+// Verifies that quoteIdentifier works on not standard characters too
+// even without quotes.
+TEST(AdaptorHostTest, notQuoted) {
+ string config = "{\n"
+ " \"flex-id\": \"some\\\"value\",\n"
+ " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorHost::quoteIdentifier(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr id = json->get("flex-id");
+ ASSERT_TRUE(id);
+ ASSERT_EQ(Element::string, id->getType());
+ EXPECT_EQ("73:6f:6d:65:22:76:61:6c:75:65", id->stringValue());
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/adaptor_option_unittests.cc b/src/lib/yang/tests/adaptor_option_unittests.cc
new file mode 100644
index 0000000..9fdcb9c
--- /dev/null
+++ b/src/lib/yang/tests/adaptor_option_unittests.cc
@@ -0,0 +1,245 @@
+// Copyright (C) 2018-2022 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 <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_space.h>
+#include <testutils/gtest_utils.h>
+#include <yang/adaptor_option.h>
+#include <yang/netconf_error.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::yang;
+
+namespace {
+
+// Verifies that setSpace adds a space to an option without this entry.
+TEST(AdaptorOptionTest, setSpaceNoSpace) {
+ string config = "{\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorOption::setSpace(json, "foo"));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr space = json->get("space");
+ ASSERT_TRUE(space);
+ ASSERT_EQ(Element::string, space->getType());
+ EXPECT_EQ("foo", space->stringValue());
+}
+
+// Verifies that setSpace does not change to an option with space.
+TEST(AdaptorOptionTest, setSpace) {
+ string config = "{\n"
+ " \"space\": \"dhcp4\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorOption::setSpace(json, "foo"));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies that checkType accepts an option with type.
+TEST(AdaptorOptionTest, checkType) {
+ string config = "{\n"
+ " \"type\": \"string\"\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ EXPECT_NO_THROW_LOG(AdaptorOption::checkType(json));
+}
+
+// Verifies that checkType does not accept an option without type.
+TEST(AdaptorOptionTest, checkTypeNoType) {
+ string config = "{\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ EXPECT_THROW_MSG(AdaptorOption::checkType(json), MissingKey,
+ "missing type in option definition { }");
+}
+
+// Verifies that checkCode accepts an option with code.
+TEST(AdaptorOptionTest, checkCode) {
+ string config = "{\n"
+ " \"code\": 123\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ EXPECT_NO_THROW_LOG(AdaptorOption::checkCode(json));
+}
+
+// Verifies that checkCode does not accept an option without code.
+TEST(AdaptorOptionTest, checkCodeNoCode) {
+ string config = "{\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ EXPECT_THROW_MSG(AdaptorOption::checkCode(json), MissingKey, "missing code in option { }");
+}
+
+// Verifies that collect works as expected.
+TEST(AdaptorOptionTest, collect) {
+ string config = "{\n"
+ " \"code\": 123,\n"
+ " \"name\": \"foo\",\n"
+ " \"space\": \"bar\"\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ OptionCodes codes;
+ ASSERT_NO_THROW_LOG(AdaptorOption::collect(json, codes));
+ EXPECT_EQ(1, codes.size());
+ EXPECT_EQ(123, codes["bar@foo"]);
+ EXPECT_FALSE(codes.contains("foo@bar"));
+}
+
+// Verifies that collect skips an already known option definition.
+TEST(AdaptorOptionTest, collectKnown) {
+ string config = "{\n"
+ " \"code\": 123,\n"
+ " \"name\": \"foo\",\n"
+ " \"space\": \"bar\"\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ OptionCodes codes = { { "bar@foo", 111 } };
+ ASSERT_NO_THROW_LOG(AdaptorOption::collect(json, codes));
+ EXPECT_EQ(1, codes.size());
+ EXPECT_EQ(111, codes["bar@foo"]);
+}
+
+// Verifies that setCode adds a code to an option without this entry
+// when the code is available in the map.
+TEST(AdaptorOptionTest, setCodeNoCode) {
+ string config = "{\n"
+ " \"name\": \"foo\",\n"
+ " \"space\": \"bar\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ OptionCodes codes = { { "bar@foo", 123 } };
+ EXPECT_NO_THROW_LOG(AdaptorOption::setCode(json, codes));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr code = json->get("code");
+ ASSERT_TRUE(code);
+ ASSERT_EQ(Element::integer, code->getType());
+ EXPECT_EQ(123, code->intValue());
+}
+
+// Verifies that setCode does not change to an option with code.
+TEST(AdaptorOptionTest, setCode) {
+ string config = "{\n"
+ " \"code\": \"dhcp4\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ OptionCodes codes;
+ EXPECT_NO_THROW_LOG(AdaptorOption::setCode(json, codes));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies that setCode raises an error on option without name and code.
+TEST(AdaptorOptionTest, setCodeNoName) {
+ string config = "{\n"
+ " \"space\": \"bar\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ OptionCodes codes;
+ EXPECT_THROW_MSG(AdaptorOption::setCode(json, codes), MissingKey,
+ "missing name and code in option { \"space\": \"bar\" }");
+}
+
+// Note the code assumes there is a space, i.e. setSpace was called.
+
+// Verifies that setCode raises an error on option without code but
+// the code is not available in the map.
+TEST(AdaptorOptionTest, setCodeNotInMap) {
+ string config = "{\n"
+ " \"name\": \"foo\",\n"
+ " \"space\": \"bar\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ OptionCodes codes;
+ EXPECT_THROW_MSG(AdaptorOption::setCode(json, codes), MissingKey,
+ "can't get code from option { \"name\": \"foo\", \"space\": \"bar\" }");
+}
+
+/// @brief Test class to make initCodes public.
+class TestAdaptorOption : public AdaptorOption {
+public:
+ using AdaptorOption::initCodesInternal;
+}; // TestAdaptorOption
+
+// Verifies that initCodesInternal works as expected.
+TEST(AdaptorOptionTest, initCodesInternal) {
+ const OptionDefParams DEFS[] = {
+ { "subnet-mask", DHO_SUBNET_MASK, DHCP4_OPTION_SPACE,
+ OPT_IPV4_ADDRESS_TYPE, false, 0, 0, "" },
+ { "time-offset", DHO_TIME_OFFSET, DHCP4_OPTION_SPACE, OPT_INT32_TYPE,
+ false, 0, 0, "" }
+ };
+ const size_t DEFS_SIZE = sizeof(DEFS) / sizeof(DEFS[0]);
+ OptionCodes codes;
+ ASSERT_NO_THROW_LOG(TestAdaptorOption::initCodesInternal(codes,
+ DHCP4_OPTION_SPACE,
+ DEFS,
+ DEFS_SIZE));
+ ASSERT_EQ(2, codes.size());
+ EXPECT_EQ(DHO_SUBNET_MASK, codes["dhcp4@subnet-mask"]);
+ EXPECT_EQ(DHO_TIME_OFFSET, codes["dhcp4@time-offset"]);
+}
+
+// Verifies that initCodes works as expected with DHCPv4.
+TEST(AdaptorOptionTest, initCodes4) {
+ OptionCodes codes;
+ ASSERT_NO_THROW_LOG(AdaptorOption::initCodes(codes, DHCP4_OPTION_SPACE));
+ EXPECT_EQ(DHO_SUBNET_MASK, codes["dhcp4@subnet-mask"]);
+ EXPECT_EQ(DHO_TIME_OFFSET, codes["dhcp4@time-offset"]);
+ EXPECT_FALSE(codes.contains("dhcp6@clientid"));
+
+ // initCodes loads last resort too.
+ EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS,
+ codes["dhcp4@vendor-encapsulated-options"]);
+
+ // and DOCSIS3.
+ EXPECT_EQ(2, codes["vendor-4491@tftp-servers"]);
+}
+
+// Verifies that initCodes works as expected with DHCPv6.
+TEST(AdaptorOptionTest, initCodes6) {
+ OptionCodes codes;
+ ASSERT_NO_THROW_LOG(AdaptorOption::initCodes(codes, DHCP6_OPTION_SPACE));
+ EXPECT_EQ(D6O_CLIENTID, codes["dhcp6@clientid"]);
+ EXPECT_EQ(D6O_SERVERID, codes["dhcp6@serverid"]);
+ EXPECT_FALSE(codes.contains("dhcp4@subnet-mask"));
+
+ // initCodes loads DOCSIS3 too.
+ EXPECT_EQ(32, codes["vendor-4491@tftp-servers"]);
+
+ // Various MAP suboptions.
+ EXPECT_EQ(D6O_S46_BR, codes["s46-cont-mape-options@s46-br"]);
+ EXPECT_EQ(D6O_S46_V4V6BIND, codes["s46-cont-lw-options@s46-v4v6bind"]);
+ EXPECT_EQ(D6O_S46_PORTPARAMS, codes["s46-v4v6bind-options@s46-portparams"]);
+
+ // And ISC space.
+ EXPECT_EQ(ISC_V6_4O6_INTERFACE, codes["vendor-2495@4o6-interface"]);
+}
+
+} //anonymous namespace
diff --git a/src/lib/yang/tests/adaptor_pool_unittests.cc b/src/lib/yang/tests/adaptor_pool_unittests.cc
new file mode 100644
index 0000000..6152834
--- /dev/null
+++ b/src/lib/yang/tests/adaptor_pool_unittests.cc
@@ -0,0 +1,276 @@
+// Copyright (C) 2018-2022 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 <testutils/gtest_utils.h>
+#include <yang/adaptor_pool.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+
+namespace {
+
+// Verifies that canonizePool does not touch a prefix without space.
+TEST(AdaptorPoolTest, canonizePoolPrefixNoSpace) {
+ string config = "{\n"
+ " \"pool\": \"192.0.2.128/28\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorPool::canonizePool(json));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies that canonizePool does not touch a canonical range.
+TEST(AdaptorPoolTest, canonizePoolRange) {
+ string config = "{\n"
+ " \"pool\": \"192.0.2.1 - 192.0.2.200\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorPool::canonizePool(json));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies that canonizePool removes spaces from a prefix.
+TEST(AdaptorPoolTest, canonizePoolPrefixSpaces) {
+ string config = "{\n"
+ " \"pool\": \"192.0.2.128 /\t28\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorPool::canonizePool(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr pool = json->get("pool");
+ ASSERT_TRUE(pool);
+ ASSERT_EQ(Element::string, pool->getType());
+ EXPECT_EQ("192.0.2.128/28", pool->stringValue());
+}
+
+// Verifies that canonizePool adds two spaces from a range.
+TEST(AdaptorPoolTest, canonizePoolRangeNoSpace) {
+ string config = "{\n"
+ " \"pool\": \"192.0.2.1-192.0.2.200\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorPool::canonizePool(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr pool = json->get("pool");
+ ASSERT_TRUE(pool);
+ ASSERT_EQ(Element::string, pool->getType());
+ EXPECT_EQ("192.0.2.1 - 192.0.2.200", pool->stringValue());
+}
+
+// Verifies that canonizePool removes extra spaces from a range.
+TEST(AdaptorPoolTest, canonizePoolRangeExtraSpaces) {
+ string config = "{\n"
+ " \"pool\": \"192.0.2.1 - 192.0.2.200\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ EXPECT_NO_THROW_LOG(AdaptorPool::canonizePool(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr pool = json->get("pool");
+ ASSERT_TRUE(pool);
+ ASSERT_EQ(Element::string, pool->getType());
+ EXPECT_EQ("192.0.2.1 - 192.0.2.200", pool->stringValue());
+}
+
+// Verifies that fromSubnet is specific to ietf-dhcpv6-server model.
+TEST(AdaptorPoolTest, fromSubnetKea) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+ " },{\n"
+ " \"pool\": \"192.0.2.101 - 192.0.2.200\"\n"
+ " } ],\n"
+ " \"valid-lifetime\": 4000,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 2000\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ConstElementPtr pools = json->get("pools");
+
+ // This should be no-op for kea-dhcp4-server and kea-dhcp6-server models
+ EXPECT_NO_THROW_LOG(AdaptorPool::fromSubnet(KEA_DHCP4_SERVER, json, pools));
+ EXPECT_TRUE(copied->equals(*json));
+ // The model is checked first.
+ EXPECT_NO_THROW_LOG(AdaptorPool::fromSubnet(KEA_DHCP6_SERVER, json, pools));
+ EXPECT_TRUE(copied->equals(*json));
+
+ // Check that the model name is actually checked.
+ EXPECT_THROW_MSG(AdaptorPool::fromSubnet("non-existent-module", json, pools), NotImplemented,
+ "fromSubnet not implemented for the model: non-existent-module");
+}
+
+// Verifies that fromSubnet works as expected.
+TEST(AdaptorPoolTest, fromSubnet) {
+ string config = "{\n"
+ " \"subnet\": \"2001:db8:1::/64\",\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8:1::/80\"\n"
+ " },{\n"
+ " \"pool\": \"2001:db8:1:0:1::/80\"\n"
+ " } ],\n"
+ " \"preferred-lifetime\": 3000,\n"
+ " \"valid-lifetime\": 4000,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 2000\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ConstElementPtr pools = json->get("pools");
+ EXPECT_NO_THROW_LOG(AdaptorPool::fromSubnet(IETF_DHCPV6_SERVER, json, pools));
+ EXPECT_FALSE(copied->equals(*json));
+ pools = json->get("pools");
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(2, pools->size());
+ ConstElementPtr pool = pools->get(0);
+ ASSERT_TRUE(pool);
+ string expected = "{"
+ " \"pool\": \"2001:db8:1::/80\","
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"valid-lifetime\": 4000 }";
+ EXPECT_EQ(expected, pool->str());
+ pool = pools->get(1);
+ ASSERT_TRUE(pool);
+ expected = "{"
+ " \"pool\": \"2001:db8:1:0:1::/80\","
+ " \"preferred-lifetime\": 3000,"
+ " \"rebind-timer\": 2000,"
+ " \"renew-timer\": 1000,"
+ " \"valid-lifetime\": 4000 }";
+ EXPECT_EQ(expected, pool->str());
+}
+
+// Verifies that toSubnet is specific to ietf-dhcpv6-server model.
+TEST(AdaptorPoolTest, toSubnetKea) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
+ " },{\n"
+ " \"pool\": \"192.0.2.101 - 192.0.2.200\"\n"
+ " } ],\n"
+ " \"valid-lifetime\": 4000,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 2000\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ConstElementPtr pools = json->get("pools");
+ EXPECT_NO_THROW_LOG(AdaptorPool::toSubnet(KEA_DHCP4_SERVER, json, pools));
+ EXPECT_TRUE(copied->equals(*json));
+ // The model is checked first.
+ EXPECT_NO_THROW_LOG(AdaptorPool::toSubnet(KEA_DHCP6_SERVER, json, pools));
+ EXPECT_TRUE(copied->equals(*json));
+ // Model name is not free: an error is raised if it is not expected.
+ EXPECT_THROW_MSG(AdaptorPool::toSubnet("keatest-module", json, pools), NotImplemented,
+ "toSubnet not implemented for the model: keatest-module");
+}
+
+// Verifies that toSubnet works as expected.
+TEST(AdaptorPoolTest, toSubnet) {
+ string config = "{\n"
+ " \"subnet\": \"2001:db8:1::/64\",\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8:1::/80\",\n"
+ " \"preferred-lifetime\": 3000,\n"
+ " \"valid-lifetime\": 4000,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 2000\n"
+ " },{\n"
+ " \"pool\": \"2001:db8:1:0:1::/80\",\n"
+ " \"preferred-lifetime\": 3000,\n"
+ " \"valid-lifetime\": 4000,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 2000\n"
+ " } ]\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ConstElementPtr pools = json->get("pools");
+ EXPECT_NO_THROW_LOG(AdaptorPool::toSubnet(IETF_DHCPV6_SERVER, json, pools));
+ EXPECT_FALSE(copied->equals(*json));
+ // Check timers.
+ ConstElementPtr timer = json->get("valid-lifetime");
+ ASSERT_TRUE(timer);
+ EXPECT_EQ("4000", timer->str());
+ timer = json->get("preferred-lifetime");
+ ASSERT_TRUE(timer);
+ EXPECT_EQ("3000", timer->str());
+ timer = json->get("renew-timer");
+ ASSERT_TRUE(timer);
+ EXPECT_EQ("1000", timer->str());
+ timer = json->get("rebind-timer");
+ ASSERT_TRUE(timer);
+ EXPECT_EQ("2000", timer->str());
+ // Timers must be removed as they are not allowed here in Kea.
+ pools = json->get("pools");
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(2, pools->size());
+ ConstElementPtr pool = pools->get(0);
+ ASSERT_TRUE(pool);
+ EXPECT_EQ("{ \"pool\": \"2001:db8:1::/80\" }", pool->str());
+ pool = pools->get(1);
+ ASSERT_TRUE(pool);
+ EXPECT_EQ("{ \"pool\": \"2001:db8:1:0:1::/80\" }", pool->str());
+}
+
+// Verifies that toSubnet fails on inconsistent input.
+TEST(AdaptorPoolTest, toSubnetBad) {
+ // Changed last rebind-timer to a different value.
+ string config = "{\n"
+ " \"subnet\": \"2001:db8:1::/64\",\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8:1::/80\",\n"
+ " \"preferred-lifetime\": 3000,\n"
+ " \"valid-lifetime\": 4000,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 2000\n"
+ " },{\n"
+ " \"pool\": \"2001:db8:1:0:1::/80\",\n"
+ " \"preferred-lifetime\": 3000,\n"
+ " \"valid-lifetime\": 4000,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 20\n"
+ " } ]\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr pools = json->get("pools");
+ EXPECT_THROW_MSG(AdaptorPool::toSubnet(IETF_DHCPV6_SERVER, json, pools), BadValue,
+ "inconsistent value of rebind-timer in [ { \"pool\": \"2001:db8:1::/80\", "
+ "\"rebind-timer\": 2000 }, { \"pool\": \"2001:db8:1:0:1::/80\", "
+ "\"rebind-timer\": 20 } ]");
+}
+
+} // anonymous namespace
diff --git a/src/lib/yang/tests/adaptor_subnet_unittests.cc b/src/lib/yang/tests/adaptor_subnet_unittests.cc
new file mode 100644
index 0000000..4b744b4
--- /dev/null
+++ b/src/lib/yang/tests/adaptor_subnet_unittests.cc
@@ -0,0 +1,214 @@
+// Copyright (C) 2018-2022 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 <dhcpsrv/subnet_id.h>
+#include <testutils/gtest_utils.h>
+#include <yang/adaptor_subnet.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::yang;
+
+namespace {
+
+// Verifies how collectID handles a subnet entry without ID.
+TEST(AdaptorSubnetTest, collectNoId) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\"\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ SubnetIDSet set;
+ bool ret = true;
+ ASSERT_NO_THROW_LOG(ret = AdaptorSubnet::collectID(json, set));
+ EXPECT_FALSE(ret);
+ EXPECT_EQ(0, set.size());
+}
+
+// Verifies how collectID handles a subnet entry with an ID.
+TEST(AdaptorSubnetTest, collectId) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"id\": 123\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ SubnetIDSet set;
+ bool ret = false;
+ ASSERT_NO_THROW_LOG(ret = AdaptorSubnet::collectID(json, set));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(1, set.size());
+ EXPECT_EQ(1, set.count(123));
+}
+
+// Verifies how collectID handles a subnet entry with an ID which is
+// already known: the set is not updated.
+TEST(AdaptorSubnetTest, collectKnownId) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"id\": 123\n"
+ "}";
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ SubnetIDSet set = { 123 };
+ bool ret = false;
+ ASSERT_NO_THROW_LOG(ret = AdaptorSubnet::collectID(json, set));
+ EXPECT_TRUE(ret);
+ EXPECT_EQ(1, set.size());
+ EXPECT_EQ(1, set.count(123));
+}
+
+// Verifies how assignID handles a subnet entry without ID: the next ID
+// is assigned, the set is updated and the next ID is incremented.
+TEST(AdaptorSubnetTest, assignNoId) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ SubnetIDSet set;
+ SubnetID next_id = 123;
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::assignID(json, set, next_id));
+ EXPECT_FALSE(copied->equals(*json));
+ EXPECT_EQ(1, set.size());
+ EXPECT_EQ(1, set.count(123));
+ EXPECT_EQ(124, next_id);
+ ConstElementPtr id = json->get("id");
+ ASSERT_TRUE(id);
+ ASSERT_EQ(Element::integer, id->getType());
+ EXPECT_EQ(123, id->intValue());
+}
+
+// Verifies how assignID handles a subnet entry without ID but with the
+// candidate ID already used: the used value is skipped.
+TEST(AdaptorSubnetTest, assignNoIdUsed) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ SubnetIDSet set = { 123 };
+ SubnetID next_id = 123;
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::assignID(json, set, next_id));
+ EXPECT_FALSE(copied->equals(*json));
+ EXPECT_EQ(2, set.size());
+ EXPECT_EQ(1, set.count(123));
+ EXPECT_EQ(1, set.count(124));
+ EXPECT_EQ(125, next_id);
+ ConstElementPtr id = json->get("id");
+ ASSERT_TRUE(id);
+ ASSERT_EQ(Element::integer, id->getType());
+ EXPECT_EQ(124, id->intValue());
+}
+
+// Verifies how assignID handles a subnet entry with ID: no change.
+TEST(AdaptorSubnetTest, assignId) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"id\": 123\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ SubnetIDSet set; // ignored.
+ SubnetID next_id = 123; // ignored.
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::assignID(json, set, next_id));
+ EXPECT_TRUE(copied->equals(*json));
+ EXPECT_EQ(0, set.size());
+ EXPECT_EQ(123, next_id);
+}
+
+// Verifies how updateRelay handles a subnet entry without relay: no change.
+TEST(AdaptorSubnetTest, updateNoRelay) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\"\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::updateRelay(json));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies how updateRelay handles a subnet entry with empty relay:
+// the relay entry is useless and removed.
+TEST(AdaptorSubnetTest, updateEmptyRelay) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"relay\": { }\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::updateRelay(json));
+ EXPECT_FALSE(copied->equals(*json));
+ EXPECT_FALSE(json->get("relay"));
+}
+
+// Verifies how updateRelay handles a subnet entry with relay which
+// has empty addresses: the relay entry is useless and removed.
+TEST(AdaptorSubnetTest, updateRelayEmptyAddresses) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ ]\n"
+ " }\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::updateRelay(json));
+ EXPECT_FALSE(copied->equals(*json));
+ EXPECT_FALSE(json->get("relay"));
+}
+
+// Verifies how updateRelay handles a subnet entry with relay which
+// has addresses: no change.
+TEST(AdaptorSubnetTest, updateRelayAddresses) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"relay\": {\n"
+ " \"ip-addresses\": [ \"192.168.1.1\" ]\n"
+ " }\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::updateRelay(json));
+ EXPECT_TRUE(copied->equals(*json));
+}
+
+// Verifies how updateRelay handles a subnet entry with relay which
+// has only address: the address is moved to a new list.
+TEST(AdaptorSubnetTest, updateRelayAddress) {
+ string config = "{\n"
+ " \"subnet\": \"192.0.2.0/24\",\n"
+ " \"relay\": {\n"
+ " \"ip-address\": \"192.168.1.1\"\n"
+ " }\n"
+ "}";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ ConstElementPtr copied = copy(json);
+ ASSERT_NO_THROW_LOG(AdaptorSubnet::updateRelay(json));
+ EXPECT_FALSE(copied->equals(*json));
+ ConstElementPtr relay = json->get("relay");
+ ASSERT_TRUE(relay);
+ string expected = "{ \"ip-addresses\": [ \"192.168.1.1\" ] }";
+ EXPECT_EQ(expected, relay->str());
+}
+
+// It does not make sense to have both ip-address and ip-addresses...
+
+} // anonymous namespace
diff --git a/src/lib/yang/tests/adaptor_unittests.cc b/src/lib/yang/tests/adaptor_unittests.cc
new file mode 100644
index 0000000..f95d5d3
--- /dev/null
+++ b/src/lib/yang/tests/adaptor_unittests.cc
@@ -0,0 +1,398 @@
+// Copyright (C) 2018-2022 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 <testutils/gtest_utils.h>
+#include <yang/adaptor.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+
+namespace {
+
+// Test that get context works as expected i.e. moves a comment into
+// the user context creating it if not exists.
+TEST(AdaptorTest, getContext) {
+ // Empty.
+ string config = "{\n"
+ "}\n";
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr context;
+ ASSERT_NO_THROW_LOG(context = Adaptor::getContext(json));
+ EXPECT_FALSE(context);
+
+ // No relevant.
+ config = "{\n"
+ " \"foo\": 1\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW_LOG(context = Adaptor::getContext(json));
+ EXPECT_FALSE(context);
+
+ // User context.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"user-context\": { \"bar\": 2 }\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW_LOG(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"bar\": 2 }", context->str());
+
+ // Comment.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"comment\": \"a comment\"\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW_LOG(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"comment\": \"a comment\" }", context->str());
+
+ // User context and comment.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"user-context\": { \"bar\": 2 },\n"
+ " \"comment\": \"a comment\"\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW_LOG(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"bar\": 2, \"comment\": \"a comment\" }", context->str());
+
+ // User context with conflicting comment and comment.
+ config = "{\n"
+ " \"foo\": 1,\n"
+ " \"user-context\": {\n"
+ " \"bar\": 2,\n"
+ " \"comment\": \"conflicting\"\n"
+ " },\n"
+ " \"comment\": \"a comment\"\n"
+ "}\n";
+ json = Element::fromJSON(config);
+ ASSERT_NO_THROW_LOG(context = Adaptor::getContext(json));
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"bar\": 2, \"comment\": \"a comment\" }", context->str());
+}
+
+// Test that fromParent works as expected i.e. moves parameters from the
+// parent to children and not overwrite them.
+TEST(AdaptorTest, fromParent) {
+ string config = "{\n"
+ " \"param1\": 123,\n"
+ " \"param2\": \"foo\",\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param1\": 234\n"
+ " },{\n"
+ " \"another\": \"entry\"\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW_LOG(Adaptor::fromParent("param1", json, json->get("list")));
+ EXPECT_NO_THROW_LOG(Adaptor::fromParent("param2", json, json->get("list")));
+ EXPECT_NO_THROW_LOG(Adaptor::fromParent("param3", json, json->get("list")));
+
+ string expected = "{\n"
+ " \"param1\": 123,\n"
+ " \"param2\": \"foo\",\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param1\": 234,\n"
+ " \"param2\": \"foo\"\n"
+ " },{\n"
+ " \"another\": \"entry\",\n"
+ " \"param1\": 123,\n"
+ " \"param2\": \"foo\"\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+ EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
+}
+
+// Test that toParent works as expected i.e. moves parameters from children
+// to the parent throwing when a value differs between two children.
+TEST(AdaptorTest, toParent) {
+ string config = "{\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param2\": \"foo\",\n"
+ " \"param3\": 234,\n"
+ " \"param4\": true\n"
+ " },{\n"
+ " \"another\": \"entry\",\n"
+ " \"param2\": \"foo\",\n"
+ " \"param3\": 123,\n"
+ " \"param5\": false\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW_LOG(Adaptor::toParent("param1", json, json->get("list")));
+ EXPECT_TRUE(json->equals(*Element::fromJSON(config)));
+
+ string expected = "{\n"
+ " \"param2\": \"foo\",\n"
+ " \"list\": [\n"
+ " {\n"
+ " \"param3\": 234,\n"
+ " \"param4\": true\n"
+ " },{\n"
+ " \"another\": \"entry\",\n"
+ " \"param3\": 123,\n"
+ " \"param5\": false\n"
+ " }\n"
+ " ]\n"
+ "}\n";
+
+ EXPECT_NO_THROW_LOG(Adaptor::toParent("param2",json, json->get("list")));
+ EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
+
+ // param[345] have different values so it should throw.
+ EXPECT_THROW_MSG(Adaptor::toParent("param3", json, json->get("list")), BadValue,
+ "inconsistent value of param3 in [ { \"param3\": 234, \"param4\": true }, { "
+ "\"another\": \"entry\", \"param3\": 123, \"param5\": false } ]");
+ EXPECT_THROW_MSG(Adaptor::toParent("param4", json, json->get("list")), BadValue,
+ "inconsistent value of param4 in [ { \"param3\": 234, \"param4\": true }, { "
+ "\"another\": \"entry\", \"param3\": 123, \"param5\": false } ]");
+ EXPECT_THROW_MSG(Adaptor::toParent("param5", json, json->get("list")), BadValue,
+ "inconsistent value of param5 in [ { \"param3\": 234, \"param4\": true }, { "
+ "\"another\": \"entry\", \"param3\": 123, \"param5\": false } ]");
+
+ // And not modify the value.
+ EXPECT_TRUE(json->equals(*Element::fromJSON(expected)));
+}
+
+// Test for modify (maps & insert).
+TEST(AdaptorTest, modifyMapInsert) {
+ string config = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ "}}}\n";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ string spath = "[ \"foo\", \"bar\" ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW_LOG(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test\",\n"
+ " \"value\": 1234\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW_LOG(actions = Element::fromJSON(sactions));
+ string result = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test\": 1234\n"
+ "}}}\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW_LOG(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (maps & replace).
+TEST(AdaptorTest, modifyMapReplace) {
+ string config = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test1\": 1234,\n"
+ " \"test2\": 1234\n"
+ "}}}\n";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ string spath = "[ \"foo\", \"bar\" ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW_LOG(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test1\",\n"
+ " \"value\": 5678\n"
+ "},{\n"
+ " \"action\": \"replace\",\n"
+ " \"key\": \"test2\",\n"
+ " \"value\": 5678\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW_LOG(actions = Element::fromJSON(sactions));
+ string result = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test1\": 1234,\n"
+ " \"test2\": 5678\n"
+ "}}}\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW_LOG(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (maps & delete).
+TEST(AdaptorTest, modifyMapDelete) {
+ string config = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ " \"test\": 1234\n"
+ "}}}\n";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ string spath = "[ \"foo\", \"bar\" ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW_LOG(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"delete\",\n"
+ " \"key\": \"test\"\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW_LOG(actions = Element::fromJSON(sactions));
+ string result = "{\n"
+ " \"foo\": {\n"
+ " \"bar\": {\n"
+ "}}}\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW_LOG(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (lists & insert).
+TEST(AdaptorTest, modifyListInsert) {
+ string config = "[\n"
+ "[{\n"
+ " \"foo\": \"bar\"\n"
+ "}]]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ string spath = "[ 0, { \"key\": \"foo\", \"value\": \"bar\" }]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW_LOG(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test\",\n"
+ " \"value\": 1234\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW_LOG(actions = Element::fromJSON(sactions));
+ string result = "[\n"
+ "[{\n"
+ " \"foo\": \"bar\",\n"
+ " \"test\": 1234\n"
+ "}]]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW_LOG(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+// Test for modify (list all & insert).
+TEST(AdaptorTest, modifyListAllInsert) {
+ string config = "[\n"
+ "{},\n"
+ "{},\n"
+ "{ \"test\": 1234 },\n"
+ "]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ string spath = "[ -1 ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW_LOG(path = Element::fromJSON(spath));
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"insert\",\n"
+ " \"key\": \"test\",\n"
+ " \"value\": 5678\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW_LOG(actions = Element::fromJSON(sactions));
+ string result = "[\n"
+ "{ \"test\": 5678 },\n"
+ "{ \"test\": 5678 },\n"
+ "{ \"test\": 1234 }\n"
+ "]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW_LOG(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+TEST(AdaptorTest, modifyListDelete) {
+ string config = "[[\n"
+ "{\n"
+ " \"foo\": \"bar\"\n"
+ "},{\n"
+ "},[\n"
+ "0, 1, 2, 3\n"
+ "]]]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ string spath = "[ 0 ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW_LOG(path = Element::fromJSON(spath));
+ // Put the positional first as it applies after previous actions...
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"delete\",\n"
+ " \"key\": 2\n"
+ "},{\n"
+ " \"action\": \"delete\",\n"
+ " \"key\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW_LOG(actions = Element::fromJSON(sactions));
+ string result = "[[{}]]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW_LOG(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+TEST(AdaptorTest, modifyListAllDelete) {
+ string config = "[[\n"
+ "{\n"
+ " \"foo\": \"bar\"\n"
+ "},{\n"
+ "},[\n"
+ "0, 1, 2, 3\n"
+ "]]]\n";
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ // The main change from the previous unit test is key 0 -> -1 so
+ // modify() applies the delete to all elements vs only the first one.
+ string spath = "[ -1 ]";
+ ConstElementPtr path;
+ ASSERT_NO_THROW_LOG(path = Element::fromJSON(spath));
+ // Put the positional first as it applies after previous actions...
+ string sactions = "[\n"
+ "{\n"
+ " \"action\": \"delete\",\n"
+ " \"key\": 2\n"
+ "},{\n"
+ " \"action\": \"delete\",\n"
+ " \"key\": { \"key\": \"foo\", \"value\": \"bar\" }\n"
+ "}]\n";
+ ConstElementPtr actions;
+ ASSERT_NO_THROW_LOG(actions = Element::fromJSON(sactions));
+ string result = "[[{}]]\n";
+ ConstElementPtr expected;
+ ASSERT_NO_THROW_LOG(expected = Element::fromJSON(result));
+ ASSERT_NO_THROW_LOG(Adaptor::modify(path, actions, json));
+ EXPECT_TRUE(expected->equals(*json));
+}
+
+} // anonymous namespace
diff --git a/src/lib/yang/tests/config_unittests.cc b/src/lib/yang/tests/config_unittests.cc
new file mode 100644
index 0000000..46b591d
--- /dev/null
+++ b/src/lib/yang/tests/config_unittests.cc
@@ -0,0 +1,392 @@
+// Copyright (C) 2018-2023 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 <testutils/io_utils.h>
+#include <testutils/user_context_utils.h>
+#include <yang/tests/json_configs.h>
+#include <yang/tests/sysrepo_setup.h>
+#include <yang/tests/yang_configs.h>
+#include <yang/translator_config.h>
+#include <yang/yang_models.h>
+
+#include <iostream>
+#include <vector>
+
+#include <boost/algorithm/string.hpp>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Return the difference between two strings
+///
+/// Use the gtest >= 1.8.0 tool which builds the difference between
+/// two vectors of lines.
+///
+/// @param left left string
+/// @param right right string
+/// @return the unified diff between left and right
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+string generateDiff(string left, string right) {
+ vector<string> left_lines;
+ boost::split(left_lines, left, boost::is_any_of("\n"));
+ vector<string> right_lines;
+ boost::split(right_lines, right, boost::is_any_of("\n"));
+ using namespace testing::internal;
+ return (edit_distance::CreateUnifiedDiff(left_lines, right_lines));
+}
+#else
+string generateDiff(string, string) {
+ return ("");
+}
+#endif
+
+/// @brief Test Fixture class for Yang <-> JSON configs.
+class ConfigTest : public ::testing::Test {
+public:
+ ConfigTest() : session_(Connection{}.sessionStart()) {
+ session_.switchDatastore(Datastore::Candidate);
+ }
+ virtual ~ConfigTest() = default;
+
+ void SetUp() override {
+ translator_.reset(new Translator(session_, model_));
+ cleanModelData();
+ }
+
+ void TearDown() override {
+ cleanModelData();
+ translator_.reset();
+ SysrepoSetup::cleanSharedMemory();
+ }
+
+ void cleanModelData() {
+ string toplevel_node("config");
+ if (model_ == IETF_DHCPV6_SERVER) {
+ toplevel_node = "server";
+ }
+ translator_->deleteItem("/" + model_ + ":" + toplevel_node);
+ }
+
+ /// @brief Reset session.
+ void resetSession() {
+ SetUp();
+ }
+
+ /// @brief Loads YANG configuration from specified tree.
+ ///
+ /// @param tree The Yang tree to load.
+ void load(const YRTree& tree) {
+ YangRepr repr(model_);
+ repr.set(tree, session_);
+ }
+
+ /// @brief Loads JSON configuration from specified Element tree.
+ ///
+ /// @param json The JSON tree to load.
+ void load(ElementPtr json) {
+ TranslatorConfig tc(session_, model_);
+ tc.setConfig(json);
+ }
+
+ /// @brief Load a cofiguration from a string containing JSON.
+ ///
+ /// @param config The JSON tree to load in textual format.
+ void load(const string& config) {
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ load(json);
+ }
+
+ /// @brief Load a configuration from JSON file.
+ ///
+ /// @param filename The name of the JSON file to load,
+ ConstElementPtr loadFile(const string& filename) {
+ string decommented = isc::test::decommentJSONfile(filename);
+ ElementPtr json = Element::fromJSONFile(decommented, true);
+ ::remove(decommented.c_str());
+ load(json);
+ return (json);
+ }
+
+ /// @brief Returns YANG tree configuration.
+ YRTree getYang() {
+ YangRepr repr(model_);
+ return (repr.get(session_));
+ }
+
+ /// @brief Returns configuration in JSON (translated by TranslatorConfig)
+ ConstElementPtr getJSON() {
+ TranslatorConfig tc(session_, model_);
+ return (tc.getConfig());
+ }
+
+ /// @brief Retrieves configuration as text (in pretty JSON format).
+ string getText() {
+ return (isc::data::prettyPrint(getJSON()));
+ }
+
+ /// @brief Verify Yang.
+ ///
+ /// @param expected The expected Yang tree.
+ bool verify(const YRTree& expected) {
+ YangRepr repr(model_);
+ return (repr.verify(expected, session_, cerr));
+ }
+
+ /// @brief Verify JSON.
+ ///
+ /// @param expected The expected JSON tree.
+ bool verify(ConstElementPtr expected) {
+ TranslatorConfig tc(session_, model_);
+ ConstElementPtr content = tc.getConfig();
+ if (isEquivalent(expected, content)) {
+ return (true);
+ }
+ string wanted = prettyPrint(expected);
+ string got = prettyPrint(content);
+ cerr << "Expected:\n" << wanted << "\n"
+ << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+ << "\n";
+ return (false);
+ }
+
+ /// @brief Verify JSON.
+ ///
+ /// @param expected The expected JSON tree in textual format.
+ bool verify(const string& config) {
+ ConstElementPtr expected;
+ expected = Element::fromJSON(config);
+ return (verify(expected));
+ }
+
+ /// @brief The model.
+ string model_;
+
+ /// @brief The sysrepo session.
+ Session session_;
+
+ unique_ptr<Translator> translator_;
+}; // ConfigTest
+
+struct ConfigTestKeaV4 : ConfigTest {
+ ConfigTestKeaV4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // ConfigTestKeaV4
+struct ConfigTestKeaV6 : ConfigTest {
+ ConfigTestKeaV6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // ConfigTestKeaV6
+struct ConfigTestIetfV6 : ConfigTest {
+ ConfigTestIetfV6() {
+ model_ = IETF_DHCPV6_SERVER;
+ }
+}; // ConfigTestIetfV6
+
+// Check empty config with ietf-dhcpv6-server model.
+TEST_F(ConfigTestIetfV6, emptyIetf6) {
+ YRTree tree;
+ ASSERT_NO_THROW_LOG(load(tree));
+ EXPECT_TRUE(verify(tree));
+
+ ElementPtr json = Element::fromJSON(emptyJson6);
+ EXPECT_TRUE(verify(json));
+ ASSERT_NO_THROW_LOG(load(json));
+ EXPECT_TRUE(verify(emptyJson6));
+ EXPECT_TRUE(verify(tree));
+}
+
+// Check empty config with kea-dhcp4-server:config model.
+TEST_F(ConfigTestKeaV4, emptyKeaDhcp4) {
+ YRTree tree;
+ ASSERT_NO_THROW_LOG(load(tree));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp4));
+
+ ElementPtr json = Element::fromJSON(emptyJson4);
+ EXPECT_TRUE(verify(json));
+ ASSERT_NO_THROW_LOG(load(json));
+ EXPECT_TRUE(verify(emptyJson4));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp4));
+}
+
+// Check empty config with kea-dhcp6-server:config model.
+TEST_F(ConfigTestKeaV6, emptyKeaDhcp6) {
+ YRTree tree;
+ ASSERT_NO_THROW_LOG(load(tree));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp6));
+
+ ElementPtr json = Element::fromJSON(emptyJson6);
+ EXPECT_TRUE(verify(json));
+ ASSERT_NO_THROW_LOG(load(json));
+ EXPECT_TRUE(verify(emptyJson6));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp6));
+}
+
+// Check subnet with two pools with ietf-dhcpv6-server model.
+// Validation will fail because the current model has a vendor-info
+// container with a mandatory ent-num leaf and no presence flag,
+// and of course the candidate YANG tree has nothing for this.
+TEST_F(ConfigTestIetfV6, subnetTwoPoolsIetf6) {
+ ASSERT_NO_THROW_LOG(load(subnetTwoPoolsTreeIetf6));
+ EXPECT_TRUE(verify(subnetTwoPoolsJson6));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetTwoPoolsJson6));
+ EXPECT_TRUE(verify(subnetTwoPoolsTreeIetf6));
+}
+
+// Check subnet with a pool and option data lists with
+// kea-dhcp4-server:config model.
+TEST_F(ConfigTestKeaV4, subnetOptionsKeaDhcp4) {
+ ASSERT_NO_THROW_LOG(load(subnetOptionsTreeKeaDhcp4));
+ EXPECT_TRUE(verify(subnetOptionsJson4));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetOptionsJson4));
+ EXPECT_TRUE(verify(subnetOptionsTreeKeaDhcp4));
+}
+
+// Check subnet with a pool and option data lists with
+// kea-dhcp6-server:config model.
+TEST_F(ConfigTestKeaV6, subnetOptionsKeaDhcp6) {
+ ASSERT_NO_THROW_LOG(load(subnetOptionsTreeKeaDhcp6));
+ EXPECT_TRUE(verify(subnetOptionsJson6));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetOptionsJson6));
+ EXPECT_TRUE(verify(subnetOptionsTreeKeaDhcp6));
+}
+
+// Check with timers.
+TEST_F(ConfigTestIetfV6, subnetTimersIetf6) {
+ ASSERT_NO_THROW_LOG(load(subnetTimersIetf6));
+ EXPECT_TRUE(verify(subnetTimersJson6));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetTimersJson6));
+ EXPECT_TRUE(verify(subnetTimersIetf6));
+}
+
+// Check a ietf-dhcpv6-server configuration which validates.
+TEST_F(ConfigTestIetfV6, validateIetf6) {
+ ASSERT_NO_THROW_LOG(load(validTreeIetf6));
+ EXPECT_TRUE(verify(validTreeIetf6));
+}
+
+// Check Kea4 example files.
+TEST_F(ConfigTestKeaV4, examples4) {
+ vector<string> examples = {
+ "advanced.json",
+ "all-keys-netconf.json",
+ "all-options.json",
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+ "config-backend.json",
+ "dhcpv4-over-dhcpv6.json",
+ "dnr.json",
+ "global-reservations.json",
+ "ha-load-balancing-server1-mt-with-tls.json",
+ "ha-load-balancing-server2-mt.json",
+ "hooks.json",
+ "hooks-radius.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+ "mysql-reservations.json",
+ "pgsql-reservations.json",
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "single-subnet.json",
+ "vendor-specific.json",
+ "vivso.json",
+ "with-ddns.json",
+ };
+ for (string file : examples) {
+ resetSession();
+ string path = string(CFG_EXAMPLES) + "/kea4/" + file;
+ SCOPED_TRACE("\n* Tested file: " + path);
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = loadFile(path));
+ json = isc::test::moveComments(json);
+ EXPECT_TRUE(verify(json));
+ }
+}
+
+// Check Kea6 example files.
+TEST_F(ConfigTestKeaV6, examples6) {
+ vector<string> examples = {
+ "advanced.json",
+ "all-keys-netconf.json",
+ "all-options.json",
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+ "config-backend.json",
+ "dhcpv4-over-dhcpv6.json",
+ "dnr.json",
+ "duid.json",
+ "global-reservations.json",
+ "ha-hot-standby-server1-with-tls.json",
+ "ha-hot-standby-server2.json",
+ "hooks.json",
+ "iPXE.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+ "mysql-reservations.json",
+ "pgsql-reservations.json",
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "simple.json",
+ "softwire46.json",
+ "stateless.json",
+ "tee-times.json",
+ "with-ddns.json",
+ };
+ for (string file : examples) {
+ resetSession();
+ string path = string(CFG_EXAMPLES) + "/kea6/" + file;
+ SCOPED_TRACE("\n* Tested file: " + path);
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = loadFile(path));
+ json = isc::test::moveComments(json);
+ EXPECT_TRUE(verify(json));
+ }
+}
+
+// Check the example in the design document.
+TEST_F(ConfigTestIetfV6, designExample) {
+ ASSERT_NO_THROW_LOG(load(designExampleTree));
+ EXPECT_TRUE(verify(designExampleJson));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(designExampleJson));
+ EXPECT_TRUE(verify(designExampleTree));
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/json_configs.h b/src/lib/yang/tests/json_configs.h
new file mode 100644
index 0000000..147cbc7
--- /dev/null
+++ b/src/lib/yang/tests/json_configs.h
@@ -0,0 +1,176 @@
+// Copyright (C) 2018-2023 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 ISC_JSON_CONFIGS_H
+#define ISC_JSON_CONFIGS_H
+
+#include <string>
+
+namespace isc {
+namespace yang {
+namespace test {
+
+/// @brief Empty DHCPv4 config.
+const std::string emptyJson4 =
+ "{\n"
+ " \"Dhcp4\": {\n"
+ " }\n"
+ "}";
+
+/// @brief A DHCPv4 config with one subnet with two pools.
+const std::string subnetTwoPoolsJson4 =
+ "{\n"
+ " \"Dhcp4\": {\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"id\": 111,\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"10.0.1.0/24\"\n"
+ " },\n"
+ " {\n"
+ " \"pool\": \"10.0.2.0/24\"\n"
+ " }\n"
+ " ],\n"
+ " \"subnet\": \"10.0.0.0/8\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
+
+/// @brief A DHCPv6 config with one subnet with two pools and timers.
+const std::string subnetTimersJson6 =
+ "{\n"
+ " \"Dhcp6\": {\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 111,\n"
+ " \"renew-timer\": 1000,\n"
+ " \"rebind-timer\": 2000,\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8::1:0/112\"\n"
+ " },\n"
+ " {\n"
+ " \"pool\": \"2001:db8::2:0/112\"\n"
+ " }\n"
+ " ],\n"
+ " \"subnet\": \"2001:db8::/48\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
+
+/// @brief A DHCPv4 subnet with one pool and option data lists.
+const std::string subnetOptionsJson4 =
+ "{\n"
+ " \"Dhcp4\": {\n"
+ " \"subnet4\": [\n"
+ " {\n"
+ " \"id\": 111,\n"
+ " \"option-data\": [\n"
+ " {\n"
+ " \"code\": 100,\n"
+ " \"space\": \"dns\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"12121212\",\n"
+ " \"always-send\": false,\n"
+ " \"never-send\": false\n"
+ " }\n"
+ " ],\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"10.0.1.0/24\"\n"
+ " }\n"
+ " ],\n"
+ " \"subnet\": \"10.0.0.0/8\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
+
+/// @brief Empty DHCPv6 config.
+const std::string emptyJson6 =
+ "{\n"
+ " \"Dhcp6\": {\n"
+ " }\n"
+ "}";
+
+/// @brief A DHCPv6 config with one subnet with one pool and option data lists.
+const std::string subnetOptionsJson6 =
+ "{\n"
+ " \"Dhcp6\": {\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 111,\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"option-data\": [\n"
+ " {\n"
+ " \"code\": 100,\n"
+ " \"space\": \"dns\",\n"
+ " \"csv-format\": false,\n"
+ " \"data\": \"12121212\",\n"
+ " \"always-send\": false,\n"
+ " \"never-send\": false\n"
+ " }\n"
+ " ],\n"
+ " \"pool\": \"2001:db8::1:0/112\"\n"
+ " }\n"
+ " ],\n"
+ " \"subnet\": \"2001:db8::/48\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
+
+/// @brief A DHCPv6 config with one subnet with two pools.
+const std::string subnetTwoPoolsJson6 =
+ "{\n"
+ " \"Dhcp6\": {\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 111,\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8::1:0/112\"\n"
+ " },\n"
+ " {\n"
+ " \"pool\": \"2001:db8::2:0/112\"\n"
+ " }\n"
+ " ],\n"
+ " \"subnet\": \"2001:db8::/48\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
+
+/// @brief Example from the design document.
+const std::string designExampleJson =
+ "{\n"
+ " \"Dhcp6\": {\n"
+ " \"subnet6\": [\n"
+ " {\n"
+ " \"id\": 1,\n"
+ " \"subnet\": \"2001:db8:20:b00::/56\",\n"
+ " \"user-context\": { \"description\": \"example\" },\n"
+ " \"pd-pools\": [\n"
+ " {\n"
+ " \"prefix\": \"2001:db8:20:b00::\",\n"
+ " \"prefix-len\": 57\n"
+// " \"delegated-len\": 57\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}";
+
+} // namespace test
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_JSON_CONFIGS_H
diff --git a/src/lib/yang/tests/run_unittests.cc b/src/lib/yang/tests/run_unittests.cc
new file mode 100644
index 0000000..24870d1
--- /dev/null
+++ b/src/lib/yang/tests/run_unittests.cc
@@ -0,0 +1,21 @@
+// Copyright (C) 2018-2022 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 <log/logger_support.h>
+#include <util/unittests/run_all.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ isc::log::initLogger();
+
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/yang/tests/sysrepo_setup.h b/src/lib/yang/tests/sysrepo_setup.h
new file mode 100644
index 0000000..4e315dd
--- /dev/null
+++ b/src/lib/yang/tests/sysrepo_setup.h
@@ -0,0 +1,96 @@
+// Copyright (C) 2018-2022 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 SYSREPO_SETUP_H
+#define SYSREPO_SETUP_H
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <testutils/gtest_utils.h>
+#include <yang/translator.h>
+#include <yang/yang_models.h>
+
+#include <sysrepo-cpp/Connection.hpp>
+
+namespace isc {
+namespace yang {
+namespace test {
+
+/// @brief Wrapper for sysrepo setup actions
+struct SysrepoSetup {
+ /// @brief Cleans shared memory.
+ ///
+ /// If a unit test crashes (mostly when migrating to a new version), it
+ /// leaves behind corrupted shared memory. Let's make sure we can run all
+ /// the following unit tests free of any side effect from said crash. This
+ /// is the equivalent of running "make shm_clean" in sysrepo:
+ /// https://github.com/sysrepo/sysrepo/blob/v1.4.140/CMakeLists.txt#L329-L334
+ static void cleanSharedMemory() {
+ call_system("rm -rf /dev/shm/sr_*");
+ call_system("rm -rf /dev/shm/srsub_*");
+ }
+
+private:
+ /// @brief Wrapper over a system() call that also checks the return value.
+ ///
+ /// @brief command the command to be run
+ static void call_system(char const* command) {
+ int return_value(system(command));
+ if (return_value != 0) {
+ std::cerr << "\"" << command << "\" exited with " << return_value
+ << ". Unit tests may be influenced by previous abnormally "
+ "terminated unit tests or sysrepo uses."
+ << std::endl;
+ }
+ }
+}; // SysrepoSetup
+
+/// @brief Test Fixture template for translator tests.
+///
+/// @tparam Name The name of the translator to test.
+/// @tparam Type The type of the translator to test.
+template<char const* Name, typename translator_t>
+class GenericTranslatorTest : public ::testing::Test {
+public:
+ virtual ~GenericTranslatorTest() = default;
+
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ sess_ = sysrepo::Connection{}.sessionStart();
+ sess_->switchDatastore(sysrepo::Datastore::Candidate);
+ translator_.reset(new translator_t(*sess_, model_));
+ cleanModelData();
+ }
+
+ void TearDown() override {
+ cleanModelData();
+ translator_.reset();
+ SysrepoSetup::cleanSharedMemory();
+ }
+
+ void cleanModelData() {
+ std::string toplevel_node("config");
+ if (model_ == IETF_DHCPV6_SERVER) {
+ toplevel_node = "server";
+ }
+ translator_->deleteItem("/" + model_ + ":" + toplevel_node);
+ }
+
+ /// @brief Sysrepo session.
+ std::optional<sysrepo::Session> sess_;
+
+ /// @brief Shared pointer to the transaction object.
+ std::shared_ptr<translator_t> translator_;
+ std::string model_;
+}; // GenericTranslatorTest
+
+} // namespace test
+} // namespace yang
+} // namespace isc
+
+#endif // SYSREPO_SETUP_H
diff --git a/src/lib/yang/tests/translator_class_unittests.cc b/src/lib/yang/tests/translator_class_unittests.cc
new file mode 100644
index 0000000..02405c5
--- /dev/null
+++ b/src/lib/yang/tests/translator_class_unittests.cc
@@ -0,0 +1,123 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_class.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const client_classes[] = "client classes";
+
+/// @brief Test fixture class for @ref TranslatorClasses.
+class TranslatorClassesTestv4 :
+ public GenericTranslatorTest<client_classes, TranslatorClasses> {
+public:
+ /// @brief Constructor
+ TranslatorClassesTestv4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorClassesTestv4
+
+class TranslatorClassesTestv6 :
+ public GenericTranslatorTest<client_classes, TranslatorClasses> {
+public:
+ /// @brief Constructor
+ TranslatorClassesTestv6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorClassesTestv6
+
+// This test verifies that an empty client class list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorClassesTestv4, getEmpty) {
+ // Get the client class list and check if it is empty.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr classes;
+ EXPECT_NO_THROW_LOG(classes = translator_->getClassesFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(classes);
+}
+
+// This test verifies that one client class can be properly translated
+// from YANG to JSON.
+TEST_F(TranslatorClassesTestv6, get) {
+ // Create the client class.
+ const string& xpath = "/kea-dhcp6-server:config";
+ const string& xclass = xpath + "/client-class[name='foo']";
+ const string& xtest = xclass + "/test";
+ string const v_test("not member('ALL')");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xtest, v_test));
+ sess_->applyChanges();
+
+ // Get the client class.
+ ConstElementPtr cclass;
+ EXPECT_NO_THROW_LOG(cclass = translator_->getClassFromAbsoluteXpath(xclass));
+ ASSERT_TRUE(cclass);
+ ElementPtr expected = Element::createMap();
+ expected->set("name", Element::create("foo"));
+ expected->set("test", Element::create("not member('ALL')"));
+ EXPECT_TRUE(expected->equals(*cclass));
+
+ // Get the client class list and check if the client class is in it.
+ ConstElementPtr classes;
+ EXPECT_NO_THROW_LOG(classes = translator_->getClassesFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(classes);
+ ASSERT_EQ(Element::list, classes->getType());
+ ASSERT_EQ(1, classes->size());
+ EXPECT_TRUE(cclass->equals(*classes->get(0)));
+}
+
+// This test verifies that an empty client class list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorClassesTestv4, setEmpty) {
+ // Set empty list.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr classes = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setClasses(xpath, classes));
+
+ // Get it back.
+ classes.reset();
+ EXPECT_NO_THROW_LOG(classes = translator_->getClassesFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(classes);
+}
+
+// This test verifies that one client class can be properly translated
+// from JSON to YANG.
+TEST_F(TranslatorClassesTestv6, set) {
+ // Set one client class.
+ const string& xpath = "/kea-dhcp6-server:config";
+ ElementPtr classes = Element::createList();
+ ElementPtr cclass = Element::createMap();
+ cclass->set("name", Element::create("foo"));
+ cclass->set("test", Element::create("''==''"));
+ cclass->set("only-if-required", Element::create(false));
+ classes->add(cclass);
+ EXPECT_NO_THROW_LOG(translator_->setClasses(xpath, classes));
+
+ // Get it back.
+ ConstElementPtr got;
+ EXPECT_NO_THROW_LOG(got = translator_->getClassesFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(got);
+ ASSERT_EQ(Element::list, got->getType());
+ ASSERT_EQ(1, got->size());
+ EXPECT_TRUE(cclass->equals(*got->get(0)));
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_control_socket_unittests.cc b/src/lib/yang/tests/translator_control_socket_unittests.cc
new file mode 100644
index 0000000..f9174db
--- /dev/null
+++ b/src/lib/yang/tests/translator_control_socket_unittests.cc
@@ -0,0 +1,174 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_control_socket.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const control_socket[] = "control socket";
+
+/// @brief Test fixture class for @ref TranslatorControlSocket.
+class TranslatorControlSocketTestv4 :
+ public GenericTranslatorTest<control_socket, TranslatorControlSocket> {
+public:
+ /// @brief Constructor
+ TranslatorControlSocketTestv4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+
+ virtual ~TranslatorControlSocketTestv4() = default;
+}; // TranslatorControlSocketTestv4
+
+class TranslatorControlSocketTestv6 :
+ public GenericTranslatorTest<control_socket, TranslatorControlSocket> {
+public:
+ /// @brief Constructor
+ TranslatorControlSocketTestv6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+
+ virtual ~TranslatorControlSocketTestv6() = default;
+}; // TranslatorControlSocketTestv6
+
+class TranslatorControlSocketTestCtrlAgent :
+ public GenericTranslatorTest<control_socket, TranslatorControlSocket> {
+public:
+ /// @brief Constructor
+ TranslatorControlSocketTestCtrlAgent() {
+ model_ = KEA_CTRL_AGENT;
+ }
+
+ virtual ~TranslatorControlSocketTestCtrlAgent() = default;
+}; // TranslatorControlSocketTestCtrlAgent
+
+// This test verifies that an empty control socket can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorControlSocketTestv4, getEmpty) {
+ // Get empty.
+ const string& xpath = "/kea-dhcp4-server:config/control-socket";
+ ConstElementPtr sock;
+ EXPECT_NO_THROW_LOG(sock = translator_->getControlSocketFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(sock);
+}
+
+// This test verifies that a not empty control socket can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorControlSocketTestv6, get) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp6-server:config/control-socket";
+ const string& xname = xpath + "/socket-name";
+ const string& xtype = xpath + "/socket-type";
+ const string& xcontext = xpath + "/user-context";
+ string const s_name("/tmp/kea.sock");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xname, s_name));
+ string const s_type("unix");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xtype, s_type));
+ string const s_context("{ \"foo\": 1 }");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xcontext, s_context));
+ sess_->applyChanges();
+
+ // Get it.
+ ConstElementPtr sock;
+ EXPECT_NO_THROW_LOG(sock = translator_->getControlSocketFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(sock);
+ ASSERT_EQ(Element::map, sock->getType());
+ EXPECT_EQ(3, sock->size());
+ ConstElementPtr type = sock->get("socket-type");
+ ASSERT_TRUE(type);
+ ASSERT_EQ(Element::string, type->getType());
+ EXPECT_EQ("unix", type->stringValue());
+ ConstElementPtr name = sock->get("socket-name");
+ ASSERT_TRUE(name);
+ ASSERT_EQ(Element::string, name->getType());
+ EXPECT_EQ("/tmp/kea.sock", name->stringValue());
+ ConstElementPtr context = sock->get("user-context");
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"foo\": 1 }", context->str());
+}
+
+// This test verifies that a not empty control socket can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorControlSocketTestCtrlAgent, set) {
+ // Set a value.
+ const string& xpath =
+ "/kea-ctrl-agent:config/control-sockets/socket[server-type='dhcp4']/control-socket";
+ ElementPtr sock = Element::createMap();
+ sock->set("socket-name", Element::create("/tmp/kea.sock"));
+ sock->set("socket-type", Element::create("unix"));
+ sock->set("comment", Element::create("a comment"));
+ try {
+ translator_->setControlSocket(xpath, sock);
+ } catch (exception const& ex) {
+ cerr << "setControlSocket fail with " << ex.what() << endl;
+ }
+ ASSERT_NO_THROW_LOG(translator_->setControlSocket(xpath, sock));
+
+ // Get it back.
+ ConstElementPtr got;
+ EXPECT_NO_THROW_LOG(got = translator_->getControlSocketFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(got);
+ ASSERT_EQ(Element::map, got->getType());
+ EXPECT_EQ(3, got->size());
+ ConstElementPtr name = got->get("socket-name");
+ ASSERT_TRUE(name);
+ ASSERT_EQ(Element::string, name->getType());
+ EXPECT_EQ("/tmp/kea.sock", name->stringValue());
+ ConstElementPtr type = got->get("socket-type");
+ ASSERT_TRUE(type);
+ ASSERT_EQ(Element::string, type->getType());
+ EXPECT_EQ("unix", type->stringValue());
+ ConstElementPtr context = got->get("user-context");
+ ASSERT_TRUE(context);
+ EXPECT_EQ("{ \"comment\": \"a comment\" }", context->str());
+}
+
+// This test verifies that an empty control socket can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorControlSocketTestv4, setEmpty) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config/control-socket";
+ const string& xname = xpath + "/socket-name";
+ const string& xtype = xpath + "/socket-type";
+ const string& xcontext = xpath + "/user-context";
+ string const s_name("/tmp/kea.sock");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xname, s_name));
+ string const s_type("unix");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xtype, s_type));
+ string const s_context("{ \"foo\": 1 }");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xcontext, s_context));
+ sess_->applyChanges();
+
+ // Get it back.
+ ConstElementPtr sock;
+ EXPECT_NO_THROW_LOG(sock = translator_->getControlSocketFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(sock);
+ EXPECT_EQ(sock->str(),
+ R"({ "socket-name": "/tmp/kea.sock", "socket-type": "unix", "user-context": { "foo": 1 } })");
+
+ // Reset to empty.
+ EXPECT_NO_THROW_LOG(translator_->setControlSocket(xpath, ConstElementPtr()));
+
+ // Get it back.
+ EXPECT_NO_THROW_LOG(sock = translator_->getControlSocketFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(sock);
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_database_unittests.cc b/src/lib/yang/tests/translator_database_unittests.cc
new file mode 100644
index 0000000..4203bcb
--- /dev/null
+++ b/src/lib/yang/tests/translator_database_unittests.cc
@@ -0,0 +1,324 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_database.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const database_access[] = "database access";
+
+/// @brief Test fixture class for @ref TranslatorDatabase.
+class TranslatorDatabaseTestv4 :
+ public GenericTranslatorTest<database_access, TranslatorDatabase> {
+public:
+ TranslatorDatabaseTestv4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+
+ virtual ~TranslatorDatabaseTestv4() = default;
+}; // TranslatorDatabaseTestv4
+
+class TranslatorDatabaseTestv6 :
+ public GenericTranslatorTest<database_access, TranslatorDatabase> {
+public:
+ TranslatorDatabaseTestv6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+
+ virtual ~TranslatorDatabaseTestv6() = default;
+}; // TranslatorDatabaseTestv6
+
+// This test verifies that an empty database can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorDatabaseTestv4, getEmpty) {
+ // Get empty.
+ const string& xpath = "/kea-dhcp4-server:config/lease-database";
+ ConstElementPtr database;
+ EXPECT_NO_THROW_LOG(database = translator_->getDatabaseFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(database);
+}
+
+// This test verifies that a database can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorDatabaseTestv4, get) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config/lease-database";
+ const string& xtype = xpath + "/database-type";
+ const string& xinterval = xpath + "/lfc-interval";
+ string const s_type("memfile");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xtype, s_type));
+ uint32_t li = 3600;
+ string const s_interval(to_string(li));
+ EXPECT_NO_THROW_LOG(sess_->setItem(xinterval, s_interval));
+ sess_->applyChanges();
+
+ // Get empty.
+ ConstElementPtr database;
+ EXPECT_NO_THROW_LOG(database = translator_->getDatabaseFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(database);
+ EXPECT_EQ(2, database->size());
+ ConstElementPtr type = database->get("type");
+ ASSERT_TRUE(type);
+ ASSERT_EQ(Element::string, type->getType());
+ EXPECT_EQ("memfile", type->stringValue());
+ ConstElementPtr interval = database->get("lfc-interval");
+ ASSERT_TRUE(interval);
+ ASSERT_EQ(Element::integer, interval->getType());
+ EXPECT_EQ(li, interval->intValue());
+}
+
+// This test verifies that a database can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorDatabaseTestv4, set) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config/lease-database";
+ ElementPtr database = Element::createMap();
+ database->set("type", Element::create("memfile"));
+ database->set("lfc-interval", Element::create(3600));
+ ASSERT_NO_THROW_LOG(translator_->setDatabase(xpath, database));
+
+ // Get it back.
+ ConstElementPtr got;
+ EXPECT_NO_THROW_LOG(got = translator_->getDatabaseFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(got);
+ ASSERT_EQ(Element::map, got->getType());
+ EXPECT_EQ(2, got->size());
+ ConstElementPtr type = got->get("type");
+ ASSERT_TRUE(type);
+ ASSERT_EQ(Element::string, type->getType());
+ EXPECT_EQ("memfile", type->stringValue());
+ ConstElementPtr interval = database->get("lfc-interval");
+ ASSERT_TRUE(interval);
+ ASSERT_EQ(Element::integer, interval->getType());
+ EXPECT_EQ(3600, interval->intValue());
+}
+
+// This test verifies that an empty database can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorDatabaseTestv4, setEmpty) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config/lease-database";
+ const string& xtype = xpath + "/database-type";
+ const string& xinterval = xpath + "/lfc-interval";
+ string const s_type("memfile");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xtype, s_type));
+ uint32_t li = 3600;
+ string const s_interval(to_string(li));
+ EXPECT_NO_THROW_LOG(sess_->setItem(xinterval, s_interval));
+ sess_->applyChanges();
+
+ // Reset to empty.
+ ASSERT_NO_THROW_LOG(translator_->setDatabase(xpath, ConstElementPtr()));
+
+ // Get it back.
+ ConstElementPtr database;
+ EXPECT_NO_THROW_LOG(database = translator_->getDatabaseFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(database);
+}
+
+/// @brief Translator name.
+extern char const database_accesses[] = "database accesses";
+
+/// @brief Test fixture class for @ref TranslatorDatabases.
+class TranslatorDatabasesTestv4 :
+ public GenericTranslatorTest<database_accesses, TranslatorDatabases> {
+public:
+ /// @brief Constructor
+ TranslatorDatabasesTestv4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorDatabasesTestv4
+
+class TranslatorDatabasesTestv6 :
+ public GenericTranslatorTest<database_accesses, TranslatorDatabases> {
+public:
+ /// @brief Constructor
+ TranslatorDatabasesTestv6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorDatabasesTestv6
+
+// This test verifies that an empty database list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorDatabasesTestv6, getEmpty) {
+ // Get empty.
+ const string& xpath = "/kea-dhcp6-server:config/hosts-database";
+ ConstElementPtr databases;
+ EXPECT_NO_THROW_LOG(databases = translator_->getDatabasesFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(databases);
+}
+
+// This test verifies that a database list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorDatabasesTestv4, get) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config/hosts-database";
+ const string& xdatabase = xpath + "[database-type='mysql']";
+ const string& xname = xdatabase + "/name";
+ const string& xuser = xdatabase + "/user";
+ const string& xpassword = xdatabase + "/password";
+ const string& xhost = xdatabase + "/host";
+ const string& xport = xdatabase + "/port";
+ string const s_name("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xname, s_name));
+ string const s_user("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xuser, s_user));
+ string const s_password("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xpassword, s_password));
+ string const s_host("localhost");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xhost, s_host));
+ uint16_t mport = 3306;
+ string const s_port(to_string(mport));
+ EXPECT_NO_THROW_LOG(sess_->setItem(xport, s_port));
+ sess_->applyChanges();
+
+ // Get empty.
+ ConstElementPtr databases;
+ EXPECT_NO_THROW_LOG(databases = translator_->getDatabasesFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(databases);
+ ASSERT_EQ(1, databases->size());
+ ConstElementPtr database = databases->get(0);
+ ASSERT_TRUE(database);
+ EXPECT_EQ(6, database->size());
+ ConstElementPtr type = database->get("type");
+ ASSERT_TRUE(type);
+ ASSERT_EQ(Element::string, type->getType());
+ EXPECT_EQ("mysql", type->stringValue());
+ ConstElementPtr name = database->get("name");
+ ASSERT_TRUE(name);
+ ASSERT_EQ(Element::string, name->getType());
+ EXPECT_EQ("kea", name->stringValue());
+ ConstElementPtr user = database->get("user");
+ ASSERT_TRUE(user);
+ ASSERT_EQ(Element::string, user->getType());
+ EXPECT_EQ("kea", user->stringValue());
+ ConstElementPtr password = database->get("password");
+ ASSERT_TRUE(password);
+ ASSERT_EQ(Element::string, password->getType());
+ EXPECT_EQ("kea", password->stringValue());
+ ConstElementPtr host = database->get("host");
+ ASSERT_TRUE(host);
+ ASSERT_EQ(Element::string, host->getType());
+ EXPECT_EQ("localhost", host->stringValue());
+ ConstElementPtr port = database->get("port");
+ ASSERT_TRUE(port);
+ ASSERT_EQ(Element::integer, port->getType());
+ EXPECT_EQ(mport, port->intValue());
+}
+
+// This test verifies that a database list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorDatabasesTestv6, set) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp6-server:config/hosts-database";
+ ElementPtr database = Element::createMap();
+ database->set("type", Element::create("memfile"));
+ database->set("lfc-interval", Element::create(3600));
+ ElementPtr databases = Element::createList();
+ databases->add(database);
+ ASSERT_NO_THROW_LOG(translator_->setDatabases(xpath, databases));
+
+ // Get it back.
+ ConstElementPtr gots;
+ EXPECT_NO_THROW_LOG(gots = translator_->getDatabasesFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(gots);
+ ASSERT_EQ(Element::list, gots->getType());
+ ASSERT_EQ(1, gots->size());
+ ConstElementPtr got = gots->get(0);
+ ASSERT_TRUE(got);
+ ASSERT_EQ(Element::map, got->getType());
+ EXPECT_EQ(2, got->size());
+ ConstElementPtr type = got->get("type");
+ ASSERT_TRUE(type);
+ ASSERT_EQ(Element::string, type->getType());
+ EXPECT_EQ("memfile", type->stringValue());
+ ConstElementPtr interval = database->get("lfc-interval");
+ ASSERT_TRUE(interval);
+ ASSERT_EQ(Element::integer, interval->getType());
+ EXPECT_EQ(3600, interval->intValue());
+}
+
+// This test verifies that an emptied database list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorDatabasesTestv4, setEmpty) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config/hosts-database";
+ const string& xdatabase = xpath + "[database-type='mysql']";
+ const string& xname = xdatabase + "/name";
+ const string& xuser = xdatabase + "/user";
+ const string& xpassword = xdatabase + "/password";
+ const string& xhost = xdatabase + "/host";
+ const string& xport = xdatabase + "/port";
+ string const s_name("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xname, s_name));
+ string const s_user("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xuser, s_user));
+ string const s_password("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xpassword, s_password));
+ string const s_host("localhost");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xhost, s_host));
+ uint16_t mport = 3306;
+ string const s_port(to_string(mport));
+ EXPECT_NO_THROW_LOG(sess_->setItem(xport, s_port));
+ sess_->applyChanges();
+
+ // Reset to empty.
+ EXPECT_NO_THROW_LOG(translator_->setDatabase(xdatabase, ConstElementPtr()));
+
+ // Get empty.
+ ConstElementPtr databases;
+ EXPECT_NO_THROW_LOG(databases = translator_->getDatabasesFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(databases);
+}
+
+// This test verifies that an empty database list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorDatabasesTestv4, setEmpties) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config/hosts-database";
+ const string& xdatabase = xpath + "[database-type='mysql']";
+ const string& xname = xdatabase + "/name";
+ const string& xuser = xdatabase + "/user";
+ const string& xpassword = xdatabase + "/password";
+ const string& xhost = xdatabase + "/host";
+ const string& xport = xdatabase + "/port";
+ string const s_name("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xname, s_name));
+ string const s_user("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xuser, s_user));
+ string const s_password("kea");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xpassword, s_password));
+ string const s_host("localhost");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xhost, s_host));
+ uint16_t mport = 3306;
+ string const s_port(to_string(mport));
+ EXPECT_NO_THROW_LOG(sess_->setItem(xport, s_port));
+ sess_->applyChanges();
+
+ // Reset to empty.
+ EXPECT_NO_THROW_LOG(translator_->setDatabases(xdatabase, ConstElementPtr()));
+
+ // Get empty.
+ ConstElementPtr databases;
+ EXPECT_NO_THROW_LOG(databases = translator_->getDatabasesFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(databases);
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_host_unittests.cc b/src/lib/yang/tests/translator_host_unittests.cc
new file mode 100644
index 0000000..d51c235
--- /dev/null
+++ b/src/lib/yang/tests/translator_host_unittests.cc
@@ -0,0 +1,192 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_host.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const host_reservations[] = "host reservations";
+
+/// @brief Test fixture class for @ref TranslatorHosts.
+class TranslatorHostsTestv4 :
+ public GenericTranslatorTest<host_reservations, TranslatorHosts> {
+public:
+ /// @brief Constructor
+ TranslatorHostsTestv4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorHostsTestv4
+
+class TranslatorHostsTestv6 :
+ public GenericTranslatorTest<host_reservations, TranslatorHosts> {
+public:
+ /// @brief Constructor
+ TranslatorHostsTestv6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorHostsTestv6
+
+// This test verifies that an empty host reservation list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorHostsTestv6, getEmpty) {
+ // Get the host reservation list and check if it is empty.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ ConstElementPtr hosts;
+ EXPECT_NO_THROW_LOG(hosts = translator_->getHostsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(hosts);
+}
+
+// This test verifies that one host reservation can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorHostsTestv6, get) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the host reservation for 2001:db8::1.
+ ostringstream shost;
+ shost << xpath + "/host[identifier-type='hw-address']"
+ << "[identifier='00:01:02:03:04:05']";
+ const string& xaddr = shost.str() + "/ip-addresses";
+ string const s_addr("2001:db8::1");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xaddr, s_addr));
+ sess_->applyChanges();
+
+ // Get the host.
+ ConstElementPtr host;
+ EXPECT_NO_THROW_LOG(host = translator_->getHostFromAbsoluteXpath(shost.str()));
+ ASSERT_TRUE(host);
+ ElementPtr expected = Element::createMap();
+ ElementPtr addresses = Element::createList();
+ addresses->add(Element::create("2001:db8::1"));
+ expected->set("hw-address", Element::create("00:01:02:03:04:05"));
+ expected->set("ip-addresses", addresses);
+ EXPECT_TRUE(expected->equals(*host));
+
+ // Get the host reservation list and check if the host reservation
+ // is in it.
+ ConstElementPtr hosts;
+ EXPECT_NO_THROW_LOG(hosts = translator_->getHostsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(hosts);
+ ASSERT_EQ(Element::list, hosts->getType());
+ ASSERT_EQ(1, hosts->size());
+ EXPECT_TRUE(host->equals(*hosts->get(0)));
+}
+
+// This test verifies that an empty host reservation list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorHostsTestv6, setEmpty) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set empty list.
+ ConstElementPtr hosts = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setHosts(xpath, hosts));
+
+ // Get it back.
+ hosts.reset();
+ EXPECT_NO_THROW_LOG(hosts = translator_->getHostsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(hosts);
+}
+
+// This test verifies that one host reservation can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorHostsTestv4, set) {
+ // Create the subnet 10.0.0.0/14 #111.
+ const string& xpath =
+ "/kea-dhcp4-server:config/subnet4[id='111']";
+ string const v_subnet("10.0.0.0/24");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set one host.
+ ElementPtr hosts = Element::createList();
+ ElementPtr host = Element::createMap();
+ host->set("flex-id", Element::create("00:ff"));
+ host->set("ip-address", Element::create("10.0.0.1"));
+ host->set("hostname", Element::create("foo"));
+ hosts->add(host);
+ EXPECT_NO_THROW_LOG(translator_->setHosts(xpath, hosts));
+
+ // Get it back.
+ hosts.reset();
+ EXPECT_NO_THROW_LOG(hosts = translator_->getHostsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(hosts);
+ ASSERT_EQ(Element::list, hosts->getType());
+ ASSERT_EQ(1, hosts->size());
+ EXPECT_TRUE(host->equals(*hosts->get(0)));
+}
+
+// This test verifies that several host reservations can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorHostsTestv6, getMany) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the host reservation for 2001:db8::1.
+ ostringstream shost;
+ shost << xpath + "/host[identifier-type='hw-address']"
+ << "[identifier='00:01:02:03:04:05']";
+ const string& xaddr = shost.str() + "/ip-addresses";
+ string const s_addr("2001:db8::1");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xaddr, s_addr));
+ sess_->applyChanges();
+
+ // Create another reservation for 2001:db8::2
+ ostringstream shost2;
+ shost2 << xpath + "/host[identifier-type='hw-address']"
+ << "[identifier='00:01:0a:0b:0c:0d']";
+ const string xaddr2 = shost2.str() + "/ip-addresses";
+ string const s_addr2("2001:db8::2");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xaddr2, s_addr2));
+ sess_->applyChanges();
+
+ // Get the host.
+ ConstElementPtr hosts;
+ EXPECT_NO_THROW_LOG(hosts = translator_->getHostsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(hosts);
+
+ EXPECT_EQ(hosts->str(),
+ "[ { \"hw-address\": \"00:01:02:03:04:05\", "
+ "\"ip-addresses\": [ \"2001:db8::1\" ] }, "
+ "{ \"hw-address\": \"00:01:0a:0b:0c:0d\", "
+ "\"ip-addresses\": [ \"2001:db8::2\" ] } ]");
+}
+
+} // anonymous namespace
diff --git a/src/lib/yang/tests/translator_logger_unittests.cc b/src/lib/yang/tests/translator_logger_unittests.cc
new file mode 100644
index 0000000..365e4bd
--- /dev/null
+++ b/src/lib/yang/tests/translator_logger_unittests.cc
@@ -0,0 +1,156 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_logger.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const logger_list[] = "logger list";
+
+/// @brief Test fixture class for @ref TranslatorLoggers.
+class TranslatorLoggersTestv4 :
+ public GenericTranslatorTest<logger_list, TranslatorLoggers> {
+public:
+ /// @brief Constructor
+ TranslatorLoggersTestv4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorLoggersTestv4
+
+class TranslatorLoggersTestv6 :
+ public GenericTranslatorTest<logger_list, TranslatorLoggers> {
+public:
+ /// @brief Constructor
+ TranslatorLoggersTestv6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorLoggersTestv6
+
+// This test verifies that an empty logger list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorLoggersTestv4, getEmpty) {
+ // Get empty.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr loggers;
+ EXPECT_NO_THROW_LOG(loggers = translator_->getLoggersFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(loggers);
+}
+
+// This test verifies that one logger can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorLoggersTestv6, get) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp6-server:config";
+ const string& xlogger = xpath + "/logger[name='foo']";
+ const string& xseverity = xlogger + "/severity";
+ const string& xoption = xlogger + "/output-option[output='/bar']";
+ const string& xmaxver = xoption + "/maxver";
+ string const s_severity("WARN");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xseverity, s_severity));
+ uint32_t max_ver = 10;
+ string const s_maxver(to_string(max_ver));
+ EXPECT_NO_THROW_LOG(sess_->setItem(xmaxver, s_maxver));
+ sess_->applyChanges();
+
+ // Get empty.
+ ConstElementPtr loggers;
+ EXPECT_NO_THROW_LOG(loggers = translator_->getLoggersFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(loggers);
+ ASSERT_EQ(1, loggers->size());
+ ConstElementPtr logger = loggers->get(0);
+ ASSERT_TRUE(logger);
+ EXPECT_EQ(3, logger->size());
+ ConstElementPtr name = logger->get("name");
+ ASSERT_TRUE(name);
+ ASSERT_EQ(Element::string, name->getType());
+ EXPECT_EQ("foo", name->stringValue());
+ ConstElementPtr severity = logger->get("severity");
+ ASSERT_TRUE(severity);
+ ASSERT_EQ(Element::string, severity->getType());
+ EXPECT_EQ("WARN", severity->stringValue());
+ ConstElementPtr options = logger->get("output_options");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+ ConstElementPtr option = options->get(0);
+ ASSERT_TRUE(option);
+ EXPECT_EQ(2, option->size());
+ ConstElementPtr output = option->get("output");
+ ASSERT_TRUE(output);
+ ASSERT_EQ(Element::string, output->getType());
+ EXPECT_EQ("/bar", output->stringValue());
+ ConstElementPtr maxver = option->get("maxver");
+ ASSERT_TRUE(maxver);
+ ASSERT_EQ(Element::integer, maxver->getType());
+ EXPECT_EQ(max_ver, maxver->intValue());
+}
+
+// This test verifies that one logger can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorLoggersTestv4, set) {
+ // Set a value.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ElementPtr option = Element::createMap();
+ option->set("output", Element::create("/bar"));
+ option->set("maxver", Element::create(10));
+ ElementPtr options = Element::createList();
+ options->add(option);
+ ElementPtr logger = Element::createMap();
+ logger->set("name", Element::create("foo"));
+ logger->set("severity", Element::create("WARN"));
+ logger->set("output_options", options);
+ ElementPtr loggers = Element::createList();
+ loggers->add(logger);
+ ASSERT_NO_THROW_LOG(translator_->setLoggers(xpath, loggers));
+
+ // Get it back.
+ ConstElementPtr gots;
+ EXPECT_NO_THROW_LOG(gots = translator_->getLoggersFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(gots);
+ ASSERT_EQ(1, gots->size());
+ ConstElementPtr got = gots->get(0);
+ ASSERT_TRUE(got);
+ EXPECT_EQ(3, got->size());
+ ConstElementPtr name = got->get("name");
+ ASSERT_TRUE(name);
+ ASSERT_EQ(Element::string, name->getType());
+ EXPECT_EQ("foo", name->stringValue());
+ ConstElementPtr severity = logger->get("severity");
+ ASSERT_TRUE(severity);
+ ASSERT_EQ(Element::string, severity->getType());
+ EXPECT_EQ("WARN", severity->stringValue());
+ ConstElementPtr got_os = logger->get("output_options");
+ ASSERT_TRUE(got_os);
+ ASSERT_EQ(1, got_os->size());
+ ConstElementPtr got_o = got_os->get(0);
+ ASSERT_TRUE(got_o);
+ EXPECT_EQ(2, got_o->size());
+ ConstElementPtr output = got_o->get("output");
+ ASSERT_TRUE(output);
+ ASSERT_EQ(Element::string, output->getType());
+ EXPECT_EQ("/bar", output->stringValue());
+ ConstElementPtr maxver = got_o->get("maxver");
+ ASSERT_TRUE(maxver);
+ ASSERT_EQ(Element::integer, maxver->getType());
+ EXPECT_EQ(10, maxver->intValue());
+}
+
+/// @todo: Implement a test that will cover multiple loggers.
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_option_data_unittests.cc b/src/lib/yang/tests/translator_option_data_unittests.cc
new file mode 100644
index 0000000..3917e26
--- /dev/null
+++ b/src/lib/yang/tests/translator_option_data_unittests.cc
@@ -0,0 +1,135 @@
+// Copyright (C) 2018-2023 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_option_data.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const option_data_list[] = "option data list";
+
+/// @brief Test fixture class for @ref TranslatorOptionDataList.
+class TranslatorOptionDataListTestv4 :
+ public GenericTranslatorTest<option_data_list, TranslatorOptionDataList> {
+public:
+ /// @brief Constructor
+ TranslatorOptionDataListTestv4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorOptionDataListTestv4
+
+class TranslatorOptionDataListTestv6 :
+ public GenericTranslatorTest<option_data_list, TranslatorOptionDataList> {
+public:
+ /// @brief Constructor
+ TranslatorOptionDataListTestv6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorOptionDataListTestv6
+
+// This test verifies that an empty option data list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorOptionDataListTestv4, getEmpty) {
+ // Get the option data list and check if it is empty.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr options;
+ EXPECT_NO_THROW_LOG(options = translator_->getOptionDataListFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(options);
+}
+
+// This test verifies that one option data can be properly translated
+// from YANG to JSON.
+TEST_F(TranslatorOptionDataListTestv6, get) {
+ // Create the option code 100.
+ const string& xpath = "/kea-dhcp6-server:config";
+ const string& xoption = xpath + "/option-data[code='100'][space='dns']";
+ const string& xformat = xoption + "/csv-format";
+ const string& xdata = xoption + "/data";
+ const string& xalways = xoption + "/always-send";
+ const string& xnever = xoption + "/never-send";
+ string const s_false("false");
+ ASSERT_NO_THROW_LOG(sess_->setItem(xformat, s_false));
+ string const s_data("12121212");
+ ASSERT_NO_THROW_LOG(sess_->setItem(xdata, s_data));
+ ASSERT_NO_THROW_LOG(sess_->setItem(xalways, s_false));
+ ASSERT_NO_THROW_LOG(sess_->setItem(xnever, s_false));
+ sess_->applyChanges();
+
+ // Get the option data.
+ ConstElementPtr option;
+ EXPECT_NO_THROW_LOG(option = translator_->getOptionDataFromAbsoluteXpath(xoption));
+ ASSERT_TRUE(option);
+ EXPECT_EQ("{"
+ " \"always-send\": false,"
+ " \"code\": 100,"
+ " \"csv-format\": false,"
+ " \"data\": \"12121212\","
+ " \"never-send\": false,"
+ " \"space\": \"dns\""
+ " }",
+ option->str());
+
+ // Get the option data list.
+ ConstElementPtr options;
+ EXPECT_NO_THROW_LOG(options = translator_->getOptionDataListFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(options);
+ ASSERT_EQ(Element::list, options->getType());
+ EXPECT_EQ(1, options->size());
+ EXPECT_TRUE(option->equals(*options->get(0)));
+}
+
+// This test verifies that an empty option data list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorOptionDataListTestv4, setEmpty) {
+ // Set empty list.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr options = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setOptionDataList(xpath, options));
+
+ // Get it back.
+ options.reset();
+ EXPECT_NO_THROW_LOG(options = translator_->getOptionDataListFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(options);
+}
+
+// This test verifies that one option data can be properly translated
+// from JSON to YANG.
+TEST_F(TranslatorOptionDataListTestv6, set) {
+ // Set one option data.
+ const string& xpath = "/kea-dhcp6-server:config";
+ ElementPtr options = Element::createList();
+ ElementPtr option = Element::createMap();
+ option->set("code", Element::create(100));
+ option->set("space", Element::create("dns"));
+ option->set("csv-format", Element::create(false));
+ option->set("data", Element::create("12121212"));
+ option->set("always-send", Element::create(false));
+ option->set("never-send", Element::create(false));
+ options->add(option);
+ EXPECT_NO_THROW_LOG(translator_->setOptionDataList(xpath, options));
+
+ // Get it back.
+ ConstElementPtr got;
+ EXPECT_NO_THROW_LOG(got = translator_->getOptionDataListFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(got);
+ ASSERT_EQ(1, got->size());
+ EXPECT_TRUE(option->equals(*got->get(0)));
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_option_def_unittests.cc b/src/lib/yang/tests/translator_option_def_unittests.cc
new file mode 100644
index 0000000..8482b93
--- /dev/null
+++ b/src/lib/yang/tests/translator_option_def_unittests.cc
@@ -0,0 +1,138 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_option_def.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const option_definition_list[] = "option definition list";
+
+/// @brief Test fixture class for @ref TranslatorOptionDefList.
+class TranslatorOptionDefListTestKeaV4 :
+ public GenericTranslatorTest<option_definition_list, TranslatorOptionDefList> {
+public:
+ /// @brief Constructor
+ TranslatorOptionDefListTestKeaV4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorOptionDefListTestKeaV4
+class TranslatorOptionDefListTestKeaV6 :
+ public GenericTranslatorTest<option_definition_list, TranslatorOptionDefList> {
+public:
+ /// @brief Constructor
+ TranslatorOptionDefListTestKeaV6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorOptionDefListTestKeaV6
+class TranslatorOptionDefListTestIetfV6 :
+ public GenericTranslatorTest<option_definition_list, TranslatorOptionDefList> {
+public:
+ /// @brief Constructor
+ TranslatorOptionDefListTestIetfV6() {
+ model_ = IETF_DHCPV6_SERVER;
+ }
+}; // TranslatorOptionDefListTestIetfV6
+
+// This test verifies that an empty option definition list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorOptionDefListTestKeaV4, getEmpty) {
+ // Get the option definition list and check if it is empty.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr options;
+ EXPECT_NO_THROW_LOG(options = translator_->getOptionDefListFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(options);
+}
+
+// This test verifies that one option definition can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorOptionDefListTestKeaV6, get) {
+ // Create the option code 100.
+ const string& xpath = "/kea-dhcp6-server:config";
+ const string& xdef = xpath + "/option-def[code='100'][space='isc']";
+ const string& xname = xdef + "/name";
+ const string& xtype = xdef + "/type";
+ const string& xarray = xdef + "/array";
+ string const s_name("foo");
+ ASSERT_NO_THROW_LOG(sess_->setItem(xname, s_name));
+ string const s_type("string");
+ ASSERT_NO_THROW_LOG(sess_->setItem(xtype, s_type));
+ string const s_array("false");
+ ASSERT_NO_THROW_LOG(sess_->setItem(xarray, s_array));
+ sess_->applyChanges();
+
+ // Get the option def.
+ ConstElementPtr def;
+ EXPECT_NO_THROW_LOG(def = translator_->getOptionDefFromAbsoluteXpath(xdef));
+ ASSERT_TRUE(def);
+ EXPECT_EQ("{ "
+ "\"array\": false, "
+ "\"code\": 100, "
+ "\"name\": \"foo\", "
+ "\"space\": \"isc\", "
+ "\"type\": \"string\" }",
+ def->str());
+
+ // Get the option definition list.
+ ConstElementPtr defs;
+ EXPECT_NO_THROW_LOG(defs = translator_->getOptionDefListFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(defs);
+ ASSERT_EQ(Element::list, defs->getType());
+ EXPECT_EQ(1, defs->size());
+ EXPECT_TRUE(def->equals(*defs->get(0)));
+}
+
+// This test verifies that an empty option definition list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorOptionDefListTestKeaV4, setEmpty) {
+ // Set empty list.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr defs = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setOptionDefList(xpath, defs));
+
+ // Get it back.
+ defs.reset();
+ EXPECT_NO_THROW_LOG(defs = translator_->getOptionDefListFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(defs);
+}
+
+// This test verifies that one option definition can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorOptionDefListTestKeaV6, set) {
+ // Set one option def.
+ const string& xpath = "/kea-dhcp6-server:config";
+ ElementPtr defs = Element::createList();
+ ElementPtr def = Element::createMap();
+ def->set("code", Element::create(100));
+ def->set("name", Element::create("foo"));
+ def->set("space", Element::create("isc"));
+ def->set("type", Element::create("string"));
+ def->set("array", Element::create(false));
+ defs->add(def);
+ EXPECT_NO_THROW_LOG(translator_->setOptionDefList(xpath, defs));
+
+ // Get it back.
+ ConstElementPtr got;
+ EXPECT_NO_THROW_LOG(got = translator_->getOptionDefListFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(got);
+ ASSERT_EQ(1, got->size());
+ EXPECT_TRUE(def->equals(*got->get(0)));
+}
+
+} // anonymous namespace
diff --git a/src/lib/yang/tests/translator_pd_pool_unittests.cc b/src/lib/yang/tests/translator_pd_pool_unittests.cc
new file mode 100644
index 0000000..f8c9388
--- /dev/null
+++ b/src/lib/yang/tests/translator_pd_pool_unittests.cc
@@ -0,0 +1,296 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_pd_pool.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const pd_pool_list[] = "pd pool list";
+
+/// @brief Test fixture class for @ref TranslatorPdPools.
+class TranslatorPdPoolsTestKeaV6 :
+ public GenericTranslatorTest<pd_pool_list, TranslatorPdPools> {
+public:
+ /// @brief Constructor
+ TranslatorPdPoolsTestKeaV6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorPdPoolsTestKeaV6
+class TranslatorPdPoolsTestIetfV6 :
+ public GenericTranslatorTest<pd_pool_list, TranslatorPdPools> {
+public:
+ /// @brief Constructor
+ TranslatorPdPoolsTestIetfV6() {
+ model_ = IETF_DHCPV6_SERVER;
+ }
+}; // TranslatorPdPoolsTestIetfV6
+
+// This test verifies that an empty pd pool list can be properly
+// translated from YANG to JSON using the IETF model.
+TEST_F(TranslatorPdPoolsTestIetfV6, getEmptyIetf) {
+ // Get the pd-pool list and check if it is empty.
+ const string& xpath =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges"
+ "/network-range[network-range-id='111']/pd-pools";
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that an empty pd pool list can be properly
+// translated from YANG to JSON using the Kea ad hoc model.
+TEST_F(TranslatorPdPoolsTestKeaV6, getEmptyKea) {
+ // Get the pd-pool list and check if it is empty.
+ const string& xpath = "/kea-dhcp6-server:config/subnet6[id='111']";
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that one pd pool can be properly
+// translated from YANG to JSON using the IETF model.
+TEST_F(TranslatorPdPoolsTestIetfV6, getIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& subnet =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges"
+ "/network-range[network-range-id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet_subnet = subnet + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet_subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the pd-pool 2001:db8:0:1000::/64 #222.
+ const string& xpath = subnet + "/pd-pools/pd-pool[pool-id='222']";
+ const string& prefix = xpath + "/prefix";
+ string const s_prefix("2001:db8:0:1000::/56");
+ EXPECT_NO_THROW_LOG(sess_->setItem(prefix, s_prefix));
+ const string& length = xpath + "/prefix-length";
+ uint8_t len = 56;
+ string const s_length(to_string(len));
+ EXPECT_NO_THROW_LOG(sess_->setItem(length, s_length));
+ sess_->applyChanges();
+
+ // Get the pool.
+ ConstElementPtr pool;
+ EXPECT_NO_THROW_LOG(pool = translator_->getPdPoolFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pool);
+ ElementPtr expected = Element::createMap();
+ expected->set("prefix", Element::create("2001:db8:0:1000::"));
+ expected->set("prefix-len", Element::create(56));
+ EXPECT_TRUE(expected->equals(*pool));
+
+ // Get the pd-pool list and check if the pd-pool is in it.
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(subnet + "/pd-pools"));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that one pd pool can be properly
+// translated from YANG to JSON using the Kea ad hoc model.
+TEST_F(TranslatorPdPoolsTestKeaV6, getKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the pd-pool 2001:db8:0:1000::/64.
+ const string& prefix = "2001:db8:0:1000::/56";
+ ostringstream spool;
+ spool << xpath + "/pd-pool[prefix='" << prefix << "']";
+ const string& x_delegated = spool.str() + "/delegated-len";
+ uint8_t dl = 64;
+ string const s_delegated(to_string(dl));
+ EXPECT_NO_THROW_LOG(sess_->setItem(x_delegated, s_delegated));
+ sess_->applyChanges();
+
+ // Get the pool.
+ ConstElementPtr pool;
+ EXPECT_NO_THROW_LOG(pool = translator_->getPdPoolFromAbsoluteXpath(spool.str()));
+ ASSERT_TRUE(pool);
+ ElementPtr expected = Element::createMap();
+ expected->set("prefix", Element::create("2001:db8:0:1000::"));
+ expected->set("prefix-len", Element::create(56));
+ expected->set("delegated-len", Element::create(64));
+ EXPECT_TRUE(expected->equals(*pool));
+
+ // Get the pd-pool list and check if the pd-pool is in it.
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that an empty pd pool list can be properly
+// translated from JSON to YANG using the IETF model.
+TEST_F(TranslatorPdPoolsTestIetfV6, setEmptyIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& subnet =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges"
+ "/network-range[network-range-id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet_subnet = subnet + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet_subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set empty list.
+ const string& xpath = subnet + "/pd-pools";
+ ConstElementPtr pools = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setPdPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that an empty pd pool list can be properly
+// translated from JSON to YANG using the Kea ad hoc model.
+TEST_F(TranslatorPdPoolsTestKeaV6, setEmptyKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set empty list.
+ ConstElementPtr pools = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setPdPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that one pd pool can be properly
+// translated from JSON to YANG using the IETF model.
+TEST_F(TranslatorPdPoolsTestIetfV6, setIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& subnet =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges"
+ "/network-range[network-range-id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet_subnet = subnet + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet_subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set one pool.
+ const string& xpath = subnet + "/pd-pools";
+ ElementPtr pools = Element::createList();
+ ElementPtr pool = Element::createMap();
+ pool->set("prefix", Element::create("2001:db8:0:1000::"));
+ pool->set("prefix-len", Element::create(56));
+ pools->add(pool);
+ EXPECT_NO_THROW_LOG(translator_->setPdPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that one pd pool can be properly
+// translated from JSON to YANG using the kea ad hoc model.
+TEST_F(TranslatorPdPoolsTestKeaV6, setKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set one pool.
+ ElementPtr pools = Element::createList();
+ ElementPtr pool = Element::createMap();
+ pool->set("prefix", Element::create("2001:db8:0:1000::"));
+ pool->set("prefix-len", Element::create(56));
+ pool->set("delegated-len", Element::create(64));
+ pools->add(pool);
+ EXPECT_NO_THROW_LOG(translator_->setPdPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that a non-empty list of pd pools can be properly
+// translated from YANG to JSON using the Kea ad hoc model.
+TEST_F(TranslatorPdPoolsTestKeaV6, getListKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the first pd-pool 2001:db8:0:1000::/56.
+ const string& prefix = "2001:db8:0:1000::/56";
+ ostringstream spool;
+ spool << xpath + "/pd-pool[prefix='" << prefix << "']";
+ const string& x_delegated = spool.str() + "/delegated-len";
+ uint8_t dl = 64;
+ string const s_delegated(to_string(dl));
+ EXPECT_NO_THROW_LOG(sess_->setItem(x_delegated, s_delegated));
+ sess_->applyChanges();
+
+ // Create the second pd-pool 2001:db8:0:2000::/56
+ const string& prefix2 = "2001:db8:0:2000::/56";
+ ostringstream spool2;
+ spool2 << xpath + "/pd-pool[prefix='" << prefix2 << "']";
+ const string& x_delegated2 = spool2.str() + "/delegated-len";
+ uint8_t dl2 = 60;
+ string const s_delegated2(to_string(dl2));
+ EXPECT_NO_THROW_LOG(sess_->setItem(x_delegated2, s_delegated2));
+ sess_->applyChanges();
+
+ // Get the pools list.
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPdPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+
+ // Check that both of them are returned properly.
+ EXPECT_EQ(pools->str(),
+ "[ { \"delegated-len\": 64, \"prefix\": \"2001:db8:0:1000::\", "
+ "\"prefix-len\": 56 }, { \"delegated-len\": 60, \"prefix\": "
+ "\"2001:db8:0:2000::\", \"prefix-len\": 56 } ]");
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_pool_unittests.cc b/src/lib/yang/tests/translator_pool_unittests.cc
new file mode 100644
index 0000000..ad0d25f
--- /dev/null
+++ b/src/lib/yang/tests/translator_pool_unittests.cc
@@ -0,0 +1,252 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_pool.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const pool_list[] = "pool list";
+
+/// @brief Test fixture class for @ref TranslatorPools.
+class TranslatorPoolsTestKeaV4 :
+ public GenericTranslatorTest<pool_list, TranslatorPools> {
+public:
+ /// @brief Constructor
+ TranslatorPoolsTestKeaV4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorPoolsTestKeaV4
+class TranslatorPoolsTestKeaV6 :
+ public GenericTranslatorTest<pool_list, TranslatorPools> {
+public:
+ /// @brief Constructor
+ TranslatorPoolsTestKeaV6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorPoolsTestKeaV6
+class TranslatorPoolsTestIetfV6 :
+ public GenericTranslatorTest<pool_list, TranslatorPools> {
+public:
+ /// @brief Constructor
+ TranslatorPoolsTestIetfV6() {
+ model_ = IETF_DHCPV6_SERVER;
+ }
+}; // TranslatorPoolsTestIetfV6
+
+// This test verifies that an empty pool list can be properly
+// translated from YANG to JSON using IETF model.
+TEST_F(TranslatorPoolsTestIetfV6, getEmptyIetf) {
+ // Get the pool list and check if it is empty.
+ const string& xpath = "/ietf-dhcpv6-server:server/server-config/"
+ "network-ranges/network-range[network-range-id='111']/address-pools";
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that an empty pool list can be properly
+// translated from YANG to JSON using Kea ad hoc model.
+TEST_F(TranslatorPoolsTestKeaV6, getEmptyKea) {
+ // Get the pool list and check if it is empty.
+ const string& xpath = "/kea-dhcp6-server:config/subnet6[id='111']";
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that one pool can be properly
+// translated from YANG to JSON using IETF model.
+TEST_F(TranslatorPoolsTestIetfV6, getIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& subnet = "/ietf-dhcpv6-server:server/server-config/"
+ "network-ranges/network-range[network-range-id='111']";
+ string const subnet_value("2001:db8::/48");
+ string const& network_prefix_path = subnet + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(network_prefix_path, subnet_value));
+ sess_->applyChanges();
+
+ // Create the pool 2001:db8::1:0/112 #222.
+ const string& xpath = subnet + "/address-pools";
+ const string& prefix = xpath + "/address-pool[pool-id='222']/pool-prefix";
+ string const value("2001:db8::1:0/112");
+ EXPECT_NO_THROW_LOG(sess_->setItem(prefix, value));
+ sess_->applyChanges();
+
+ // Get the pool.
+ ConstElementPtr pool;
+ EXPECT_NO_THROW_LOG(pool = translator_->getPoolFromAbsoluteXpath(xpath + "/address-pool[pool-id='222']"));
+ ASSERT_TRUE(pool);
+ EXPECT_EQ("{ \"pool\": \"2001:db8::1:0/112\" }", pool->str());
+
+ // Get the pool list and check if the pool is in it.
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that one pool can be properly
+// translated from YANG to JSON using Kea ad hoc model.
+TEST_F(TranslatorPoolsTestKeaV6, getKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the pool 2001:db8::1:0/112.
+ const string& prefix = "2001:db8::1:0/112";
+ string start_addr;
+ string end_addr;
+ ASSERT_NO_THROW_LOG(TranslatorPool::getAddresses(prefix,
+ start_addr, end_addr));
+ EXPECT_EQ("2001:db8::1:0", start_addr);
+ EXPECT_EQ("2001:db8::1:ffff", end_addr);
+ ostringstream spool;
+ spool << xpath + "/pool[start-address='" << start_addr
+ << "'][end-address='" << end_addr << "']";
+ const string& x_prefix = spool.str() + "/prefix";
+ string const s_prefix("2001:db8::1:0/112");
+ EXPECT_NO_THROW_LOG(sess_->setItem(x_prefix, s_prefix));
+ sess_->applyChanges();
+
+ // Get the pool.
+ ConstElementPtr pool;
+ EXPECT_NO_THROW_LOG(pool = translator_->getPoolFromAbsoluteXpath(spool.str()));
+ ASSERT_TRUE(pool);
+ ElementPtr expected = Element::createMap();
+ expected->set("pool", Element::create("2001:db8::1:0/112"));
+ EXPECT_TRUE(expected->equals(*pool));
+
+ // Get the pool list and check if the pool is in it.
+ ConstElementPtr pools;
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that an empty pool list can be properly
+// translated from JSON to YANG using IETF model.
+TEST_F(TranslatorPoolsTestIetfV6, setEmptyIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& subnet = "/ietf-dhcpv6-server:server/server-config/"
+ "network-ranges/network-range[network-range-id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet_subnet = subnet + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet_subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set empty list.
+ const string& xpath = subnet + "/address-pools";
+ ConstElementPtr pools = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that an empty pool list can be properly
+// translated from JSON to YANG using Kea ad hoc model.
+TEST_F(TranslatorPoolsTestKeaV6, setEmptyKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set empty list.
+ ConstElementPtr pools = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(pools);
+}
+
+// This test verifies that one pool can be properly
+// translated from JSON to YANG using IETF model.
+TEST_F(TranslatorPoolsTestIetfV6, setIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& subnet = "/ietf-dhcpv6-server:server/server-config/"
+ "network-ranges/network-range[network-range-id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet_subnet = subnet + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet_subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set one pool.
+ const string& xpath = subnet + "/address-pools";
+ ElementPtr pools = Element::createList();
+ ElementPtr pool = Element::createMap();
+ pool->set("pool", Element::create("2001:db8::1:0/112"));
+ pools->add(pool);
+ EXPECT_NO_THROW_LOG(translator_->setPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+// This test verifies that one pool can be properly
+// translated from JSON to YANG using Kea ad hoc model.
+TEST_F(TranslatorPoolsTestKeaV6, setKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/kea-dhcp6-server:config/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& subnet = xpath + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(subnet, v_subnet));
+ sess_->applyChanges();
+
+ // Set one pool.
+ ElementPtr pools = Element::createList();
+ ElementPtr pool = Element::createMap();
+ pool->set("pool",
+ Element::create("2001:db8::1 - 2001:db8::100"));
+ pools->add(pool);
+ EXPECT_NO_THROW_LOG(translator_->setPools(xpath, pools));
+
+ // Get it back.
+ pools.reset();
+ EXPECT_NO_THROW_LOG(pools = translator_->getPoolsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(pools);
+ ASSERT_EQ(Element::list, pools->getType());
+ ASSERT_EQ(1, pools->size());
+ EXPECT_TRUE(pool->equals(*pools->get(0)));
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_shared_network_unittests.cc b/src/lib/yang/tests/translator_shared_network_unittests.cc
new file mode 100644
index 0000000..e897713
--- /dev/null
+++ b/src/lib/yang/tests/translator_shared_network_unittests.cc
@@ -0,0 +1,210 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_shared_network.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const shared_networks[] = "shared networks";
+
+/// @brief Test fixture class for @ref TranslatorSharedNetworks.
+class TranslatorSharedNetworksTestKeaV4 :
+ public GenericTranslatorTest<shared_networks, TranslatorSharedNetworks> {
+public:
+ /// @brief Constructor
+ TranslatorSharedNetworksTestKeaV4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorSharedNetworksTestKeaV4
+class TranslatorSharedNetworksTestKeaV6 :
+ public GenericTranslatorTest<shared_networks, TranslatorSharedNetworks> {
+public:
+ /// @brief Constructor
+ TranslatorSharedNetworksTestKeaV6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorSharedNetworksTestKeaV6
+
+// This test verifies that an empty shared network list can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorSharedNetworksTestKeaV4, getEmpty) {
+ // Get the shared network list and check if it is empty.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr networks;
+ EXPECT_NO_THROW_LOG(networks = translator_->getSharedNetworksFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(networks);
+}
+
+// This test verifies that one shared network can be properly
+// translated from YANG to JSON.
+TEST_F(TranslatorSharedNetworksTestKeaV6, get) {
+ // Create the subnet 2001:db8::/48 #111 in shared network foo.
+ const string& xpath = "/kea-dhcp6-server:config";
+ const string& xnetwork = xpath + "/shared-network[name='foo']";
+ const string& xsubnet = xnetwork + "/subnet6[id='111']/subnet";
+ string const v_subnet("2001:db8::/48");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet, v_subnet));
+ sess_->applyChanges();
+
+ // Get the shared network.
+ ConstElementPtr network;
+ EXPECT_NO_THROW_LOG(network = translator_->getSharedNetworkFromAbsoluteXpath(xnetwork));
+ ASSERT_TRUE(network);
+ ElementPtr subnet = Element::createMap();
+ subnet->set("id", Element::create(111));
+ subnet->set("subnet", Element::create("2001:db8::/48"));
+ ElementPtr subnets = Element::createList();
+ subnets->add(subnet);
+ ElementPtr expected = Element::createMap();
+ expected->set("name", Element::create("foo"));
+ expected->set("subnet6", subnets);
+ EXPECT_TRUE(expected->equals(*network));
+
+ // Get the shared network list and check if the shared network is in it.
+ ConstElementPtr networks;
+ EXPECT_NO_THROW_LOG(networks = translator_->getSharedNetworksFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(networks);
+ ASSERT_EQ(Element::list, networks->getType());
+ ASSERT_EQ(1, networks->size());
+ EXPECT_TRUE(network->equals(*networks->get(0)));
+}
+
+// This test verifies that an empty shared network list can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorSharedNetworksTestKeaV4, setEmpty) {
+ // Set empty list.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ConstElementPtr networks = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setSharedNetworks(xpath, networks));
+
+ // Get it back.
+ networks.reset();
+ EXPECT_NO_THROW_LOG(networks = translator_->getSharedNetworksFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(networks);
+}
+
+// This test verifies that one shared network can be properly
+// translated from JSON to YANG.
+TEST_F(TranslatorSharedNetworksTestKeaV6, set) {
+ // Set one shared network.
+ const string& xpath = "/kea-dhcp6-server:config";
+ ElementPtr networks = Element::createList();
+ ElementPtr share = Element::createMap();
+ ElementPtr subnets = Element::createList();
+ ElementPtr subnet = Element::createMap();
+ subnet->set("subnet", Element::create("2001:db8::/48"));
+ subnet->set("id", Element::create(123));
+ subnets->add(subnet);
+ share->set("name", Element::create("foo"));
+ share->set("subnet6", subnets);
+ networks->add(share);
+ EXPECT_NO_THROW_LOG(translator_->setSharedNetworks(xpath, networks));
+
+ // Get it back.
+ networks.reset();
+ EXPECT_NO_THROW_LOG(networks = translator_->getSharedNetworksFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(networks);
+ ASSERT_EQ(Element::list, networks->getType());
+ ASSERT_EQ(1, networks->size());
+ EXPECT_TRUE(share->equals(*networks->get(0)));
+}
+
+// This test verifies that several shared networks can be properly
+// translated from YANG to JSON. This test adds the following structure:
+//
+// shared-network "foo":
+// - subnet1: 2001:db8:1::/48 (subnet-id 1)
+// - subnet1: 2001:db8:2::/48 (subnet-id 2)
+// shared-network "bar":
+// - subnet1: 2001:db8:101::/48 (subnet-id 101)
+// - subnet1: 2001:db8:102::/48 (subnet-id 102)
+TEST_F(TranslatorSharedNetworksTestKeaV6, getList) {
+ const string& xpath = "/kea-dhcp6-server:config";
+
+ // Those two networks will be added.
+ const string& xnetwork1 = xpath + "/shared-network[name='foo']";
+ const string& xnetwork2 = xpath + "/shared-network[name='bar']";
+
+ // Non-existent network
+ const string& xnetwork3 = xpath + "/shared-network[name='baz']";
+
+ const string& exp_net1 =
+ "{ \"name\": \"foo\", \"subnet6\": [ { \"id\": 1, "
+ "\"subnet\": \"2001:db8:1::/48\" }, "
+ "{ \"id\": 2, \"subnet\": \"2001:db8:2::/48\" } ] }";
+
+ const string& exp_net2 =
+ "{ \"name\": \"bar\", \"subnet6\": [ { \"id\": 101, "
+ "\"subnet\": \"2001:db8:101::/48\" }, "
+ "{ \"id\": 102, \"subnet\": \"2001:db8:102::/48\" } ] }";
+
+ const string exp_both =
+ "[ " + exp_net1 + ", " + exp_net2 + " ]";
+
+ // Create the subnet1: 2001:db8:1::/48 #1 in shared network foo.
+ const string& xsubnet1 = xnetwork1 + "/subnet6[id='1']/subnet";
+ string const v_subnet1("2001:db8:1::/48");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet1, v_subnet1));
+ sess_->applyChanges();
+
+ // Create the subnet2: 2001:db8:2::/48 #2 in shared network foo.
+ const string& xsubnet2 = xnetwork1 + "/subnet6[id='2']/subnet";
+ string const v_subnet2("2001:db8:2::/48");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet2, v_subnet2));
+ sess_->applyChanges();
+
+ // Create the subnet1: 2001:db8:101::/48 #101 in shared network foo.
+ const string& xsubnet3 = xnetwork2 + "/subnet6[id='101']/subnet";
+ string const v_subnet("2001:db8:101::/48");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet3, v_subnet));
+ sess_->applyChanges();
+
+ // Create the subnet2: 2001:db8:2::/48 #2 in shared network foo.
+ const string& xsubnet4 = xnetwork2 + "/subnet6[id='102']/subnet";
+ string const v_subnet4("2001:db8:102::/48");
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet4, v_subnet4));
+ sess_->applyChanges();
+
+ // Ok, now test the getters. Let's start with the easier ones that
+ // return a single network.
+ ConstElementPtr network;
+
+ // Get the first network.
+ EXPECT_NO_THROW_LOG(network = translator_->getSharedNetworkFromAbsoluteXpath(xnetwork1));
+ ASSERT_TRUE(network);
+ EXPECT_EQ(exp_net1, network->str());
+
+ // Get the second network.
+ EXPECT_NO_THROW_LOG(network = translator_->getSharedNetworkFromAbsoluteXpath(xnetwork2));
+ ASSERT_TRUE(network);
+ EXPECT_EQ(exp_net2, network->str());
+
+ // Check that networks with non-existent name are not returned.
+ EXPECT_NO_THROW_LOG(network = translator_->getSharedNetworkFromAbsoluteXpath(xnetwork3));
+ EXPECT_FALSE(network);
+
+ // Now test returns all networks
+ ConstElementPtr networks;
+ EXPECT_NO_THROW_LOG(networks = translator_->getSharedNetworksFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(networks);
+ EXPECT_EQ(exp_both, networks->str());
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_subnet_unittests.cc b/src/lib/yang/tests/translator_subnet_unittests.cc
new file mode 100644
index 0000000..d9a8104
--- /dev/null
+++ b/src/lib/yang/tests/translator_subnet_unittests.cc
@@ -0,0 +1,379 @@
+// Copyright (C) 2018-2022 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 <yang/tests/sysrepo_setup.h>
+#include <yang/translator_subnet.h>
+#include <yang/yang_models.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Translator name.
+extern char const subnet_list[] = "subnet list";
+
+/// @brief Test fixture class for @ref TranslatorSubnets.
+class TranslatorSubnetsTestKeaV4 :
+ public GenericTranslatorTest<subnet_list, TranslatorSubnets> {
+public:
+ /// @brief Constructor
+ TranslatorSubnetsTestKeaV4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+}; // TranslatorSubnetsTestKeaV4
+
+class TranslatorSubnetsTestKeaV6 :
+ public GenericTranslatorTest<subnet_list, TranslatorSubnets> {
+public:
+ /// @brief Constructor
+ TranslatorSubnetsTestKeaV6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+}; // TranslatorSubnetsTestKeaV6
+
+class TranslatorSubnetsTestIetfV6 :
+ public GenericTranslatorTest<subnet_list, TranslatorSubnets> {
+public:
+ /// @brief Constructor
+ TranslatorSubnetsTestIetfV6() {
+ model_ = IETF_DHCPV6_SERVER;
+ }
+}; // TranslatorSubnetsTestIetfV6
+
+// This test verifies that an empty subnet list can be properly
+// translated from YANG to JSON using IETF model.
+TEST_F(TranslatorSubnetsTestIetfV6, getEmptyIetf) {
+ // Get the subnet list and check if it is empty.
+ const string& xpath =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges";
+ ConstElementPtr subnets;
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(subnets);
+}
+
+// This test verifies that an empty subnet list can be properly
+// translated from YANG to JSON using Kea ad hoc model.
+TEST_F(TranslatorSubnetsTestKeaV6, getEmptyKea) {
+ // Get the subnet list and check if it is empty.
+ const string& xpath = "/kea-dhcp6-server:config";
+ ConstElementPtr subnets;
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(subnets);
+}
+
+// This test verifies that one subnet can be properly
+// translated from YANG to JSON using IETF model.
+TEST_F(TranslatorSubnetsTestIetfV6, getIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges";
+ const string& xsub = xpath + "/network-range[network-range-id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& xsubnet = xsub + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet, v_subnet));
+ sess_->applyChanges();
+
+ // Get the subnet.
+ ConstElementPtr subnet;
+ EXPECT_NO_THROW_LOG(subnet = translator_->getSubnetFromAbsoluteXpath(xsub));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ("{ \"id\": 111, "
+ "\"subnet\": \"2001:db8::/48\" }",
+ subnet->str());
+
+ // Get the subnet list and check if the subnet is in it.
+ ConstElementPtr subnets;
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ ASSERT_TRUE(subnets->get(0));
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+// This test verifies that one subnet can be properly
+// translated from YANG to JSON using Kea ad hoc model.
+TEST_F(TranslatorSubnetsTestKeaV6, getKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath = "/kea-dhcp6-server:config";
+ const string& xsub = xpath + "/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& xsubnet = xsub + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet, v_subnet));
+ sess_->applyChanges();
+
+ // Get the subnet.
+ ConstElementPtr subnet;
+ EXPECT_NO_THROW_LOG(subnet = translator_->getSubnetFromAbsoluteXpath(xsub));
+ ASSERT_TRUE(subnet);
+ ElementPtr expected = Element::createMap();
+ expected->set("id", Element::create(111));
+ expected->set("subnet", Element::create("2001:db8::/48"));
+ EXPECT_TRUE(expected->equals(*subnet));
+
+ // Get the subnet list and check if the subnet is in it.
+ ConstElementPtr subnets;
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+// This test verifies that one subnet with two pools can be properly
+// translated from YANG to JSON using IETF model.
+TEST_F(TranslatorSubnetsTestIetfV6, getPoolsIetf) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges";
+ const string& xsub = xpath + "/network-range[network-range-id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& xsubnet = xsub + "/network-prefix";
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the pool 2001:db8::1:0/112 #1.
+ const string& xpool = xsub + "/address-pools";
+ const string& prefix1 = xpool + "/address-pool[pool-id='1']/pool-prefix";
+ string const s_pool1("2001:db8::1:0/112");
+ EXPECT_NO_THROW_LOG(sess_->setItem(prefix1, s_pool1));
+ sess_->applyChanges();
+
+ // Create the pool 2001:db8::2:0/112 #2.
+ const string& prefix2 = xpool + "/address-pool[pool-id='2']/pool-prefix";
+ string const s_pool2("2001:db8::2:0/112");
+ EXPECT_NO_THROW_LOG(sess_->setItem(prefix2, s_pool2));
+ sess_->applyChanges();
+
+ // Get the subnet.
+ ConstElementPtr subnet;
+ EXPECT_NO_THROW_LOG(subnet = translator_->getSubnetFromAbsoluteXpath(xsub));
+ ASSERT_TRUE(subnet);
+ string expected =
+ "{\n"
+ " \"id\": 111,\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8::1:0/112\"\n"
+ " },\n"
+ " {\n"
+ " \"pool\": \"2001:db8::2:0/112\"\n"
+ " }\n"
+ " ],\n"
+ " \"subnet\": \"2001:db8::/48\"\n"
+ "}";
+ EXPECT_EQ(expected, prettyPrint(subnet));
+
+ // Get the subnet list and check if the subnet is in it.
+ ConstElementPtr subnets;
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+// This test verifies that one subnet with two pools can be properly
+// translated from YANG to JSON using Kea ad hoc model.
+TEST_F(TranslatorSubnetsTestKeaV6, getPoolsKea) {
+ // Create the subnet 2001:db8::/48 #111.
+ const string& xpath = "/kea-dhcp6-server:config";
+ const string& xsub = xpath + "/subnet6[id='111']";
+ string const v_subnet("2001:db8::/48");
+ const string& xsubnet = xsub + "/subnet";
+ EXPECT_NO_THROW_LOG(sess_->setItem(xsubnet, v_subnet));
+ sess_->applyChanges();
+
+ // Create the pool 2001:db8::1:0/112.
+ const string& prefix1 = xsub + "/pool[start-address='2001:db8::1:0']" +
+ "[end-address='2001:db8::1:ffff']/prefix";
+ string const s_pool1("2001:db8::1:0/112");
+ EXPECT_NO_THROW_LOG(sess_->setItem(prefix1, s_pool1));
+ sess_->applyChanges();
+
+ // Create the pool 2001:db8::2:0/112.
+ const string& prefix2 = xsub + "/pool[start-address='2001:db8::2:0']" +
+ "[end-address='2001:db8::2:ffff']";
+ EXPECT_NO_THROW_LOG(sess_->setItem(prefix2, nullopt));
+ sess_->applyChanges();
+
+ // Get the subnet.
+ ConstElementPtr subnet;
+ EXPECT_NO_THROW_LOG(subnet = translator_->getSubnetFromAbsoluteXpath(xsub));
+ ASSERT_TRUE(subnet);
+ string expected =
+ "{\n"
+ " \"id\": 111,\n"
+ " \"pools\": [\n"
+ " {\n"
+ " \"pool\": \"2001:db8::1:0/112\"\n"
+ " },\n"
+ " {\n"
+ " \"pool\": \"2001:db8::2:0 - 2001:db8::2:ffff\"\n"
+ " }\n"
+ " ],\n"
+ " \"subnet\": \"2001:db8::/48\"\n"
+ "}";
+ EXPECT_EQ(expected, prettyPrint(subnet));
+
+ // Get the subnet list and check if the subnet is in it.
+ ConstElementPtr subnets;
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+// This test verifies that an empty subnet list can be properly
+// translated from JSON to YANG using IETF model.
+TEST_F(TranslatorSubnetsTestIetfV6, setEmptyIetf) {
+ // Set empty list.
+ const string& xpath =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges";
+ ConstElementPtr subnets = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setSubnets(xpath, subnets));
+
+ // Get it back.
+ subnets.reset();
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(subnets);
+}
+
+// This test verifies that an empty subnet list can be properly
+// translated from JSON to YANG using Kea ad hoc model.
+TEST_F(TranslatorSubnetsTestKeaV4, setEmptyKea) {
+ // Set empty list.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ElementPtr subnets = Element::createList();
+ EXPECT_NO_THROW_LOG(translator_->setSubnets(xpath, subnets));
+
+ // Get it back.
+ subnets.reset();
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_FALSE(subnets);
+}
+
+// This test verifies that one subnet can be properly
+// translated from JSON to YANG using IETF model.
+TEST_F(TranslatorSubnetsTestIetfV6, setIetf) {
+ // Set one subnet.
+ const string& xpath =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges";
+ ElementPtr subnets = Element::createList();
+ ElementPtr subnet = Element::createMap();
+ subnet->set("subnet", Element::create("2001:db8::/48"));
+ subnet->set("id", Element::create(123));
+ subnets->add(subnet);
+ EXPECT_NO_THROW_LOG(translator_->setSubnets(xpath, subnets));
+
+ // Get it back.
+ subnets.reset();
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+// This test verifies that one subnet can be properly
+// translated from JSON to YANG using Kea ad hoc model.
+TEST_F(TranslatorSubnetsTestKeaV4, setKea) {
+ // Set one subnet.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ElementPtr subnets = Element::createList();
+ ElementPtr subnet = Element::createMap();
+ subnet->set("subnet", Element::create("10.0.1.0/24"));
+ subnet->set("id", Element::create(123));
+ subnets->add(subnet);
+ EXPECT_NO_THROW_LOG(translator_->setSubnets(xpath, subnets));
+
+ // Get it back.
+ subnets.reset();
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+// This test verifies that one subnet with two pools can be properly
+// translated from JSON to YANG using IETF model.
+TEST_F(TranslatorSubnetsTestIetfV6, setTwoIetf) {
+ // Set one subnet.
+ const string& xpath =
+ "/ietf-dhcpv6-server:server/server-config/network-ranges";
+ ElementPtr subnets = Element::createList();
+ ElementPtr subnet = Element::createMap();
+ subnet->set("subnet", Element::create("2001:db8::/48"));
+ subnet->set("id", Element::create(123));
+
+ // Add two pools.
+ ElementPtr pools = Element::createList();
+ ElementPtr pool1 = Element::createMap();
+ pool1->set("pool", Element::create("2001:db8::1:0/112"));
+ pools->add(pool1);
+ ElementPtr pool2 = Element::createMap();
+ pool2->set("pool", Element::create("2001:db8::2:0/112"));
+ pools->add(pool2);
+ subnet->set("pools", pools);
+
+ // Add the subnet.
+ subnets->add(subnet);
+ EXPECT_NO_THROW_LOG(translator_->setSubnets(xpath, subnets));
+
+ // Get it back.
+ subnets.reset();
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+// This test verifies that one subnet with two pools can be properly
+// translated from JSON to YANG using Kea ad hoc model.
+TEST_F(TranslatorSubnetsTestKeaV4, setTwoKea) {
+ // Set one subnet.
+ const string& xpath = "/kea-dhcp4-server:config";
+ ElementPtr subnets = Element::createList();
+ ElementPtr subnet = Element::createMap();
+ subnet->set("subnet", Element::create("10.0.1.0/24"));
+ subnet->set("id", Element::create(123));
+
+ // Add two pools.
+ ElementPtr pools = Element::createList();
+ ElementPtr pool1 = Element::createMap();
+ pool1->set("pool", Element::create("10.0.1.0/28"));
+ pools->add(pool1);
+ ElementPtr pool2 = Element::createMap();
+ pool2->set("pool", Element::create("10.0.1.200 - 10.0.1.222"));
+ pools->add(pool2);
+ subnet->set("pools", pools);
+
+ // Add the subnet.
+ subnets->add(subnet);
+ EXPECT_NO_THROW_LOG(translator_->setSubnets(xpath, subnets));
+
+ // Get it back.
+ subnets.reset();
+ EXPECT_NO_THROW_LOG(subnets = translator_->getSubnetsFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(subnets);
+ ASSERT_EQ(Element::list, subnets->getType());
+ ASSERT_EQ(1, subnets->size());
+ EXPECT_TRUE(subnet->equals(*subnets->get(0)));
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/translator_unittests.cc b/src/lib/yang/tests/translator_unittests.cc
new file mode 100644
index 0000000..8532419
--- /dev/null
+++ b/src/lib/yang/tests/translator_unittests.cc
@@ -0,0 +1,892 @@
+// Copyright (C) 2018-2022 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 <testutils/gtest_utils.h>
+#include <yang/tests/sysrepo_setup.h>
+#include <yang/translator.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace libyang;
+using namespace sysrepo;
+
+struct TranslatorTest : ::testing::Test {
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ cleanUp();
+ }
+
+ void TearDown() override {
+ cleanUp();
+ SysrepoSetup::cleanSharedMemory();
+ }
+
+private:
+ void cleanUp() {
+ Session session(sysrepo::Connection{}.sessionStart());
+ session.switchDatastore(sysrepo::Datastore::Candidate);
+ session.deleteItem("/keatest-module:container");
+ session.deleteItem("/keatest-module:kernel-modules");
+ session.deleteItem("/keatest-module:list");
+ session.deleteItem("/keatest-module:main");
+ session.deleteItem("/keatest-module:presence-container");
+ session.applyChanges();
+ }
+}; // TranslatorTest
+
+namespace {
+
+// Test constructor.
+TEST_F(TranslatorTest, constructor) {
+ // Get a session.
+ Session sess(Connection{}.sessionStart());
+ sess.switchDatastore(sysrepo::Datastore::Candidate);
+ // Get a translator object.
+ unique_ptr<Translator> translator;
+ EXPECT_NO_THROW_LOG(translator.reset(new Translator(sess, "")));
+}
+
+// Test basic YANG to JSON value conversion using sysrepo test models.
+TEST_F(TranslatorTest, getItem) {
+ // Get a translator object to play with.
+ Session sess(Connection{}.sessionStart());
+ sess.switchDatastore(sysrepo::Datastore::Candidate);
+ unique_ptr<Translator> translator;
+ ASSERT_NO_THROW_LOG(translator.reset(new Translator(sess, "")));
+ string value;
+ ConstElementPtr element;
+ string xpath;
+
+ // String.
+ xpath = "/keatest-module:main/string";
+ value = "str";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ("str", element->stringValue());
+ element.reset();
+
+ // Bool.
+ xpath = "/keatest-module:main/boolean";
+ value = "true";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::boolean, element->getType());
+ EXPECT_TRUE(element->boolValue());
+ element.reset();
+
+ // Unsigned 8 bit integer.
+ xpath = "/keatest-module:main/ui8";
+ uint8_t u8(8);
+ value = to_string(u8);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(8, element->intValue());
+ element.reset();
+
+ // Unsigned 16 bit integer.
+ xpath = "/keatest-module:main/ui16";
+ uint16_t u16(16);
+ value = to_string(u16);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(16, element->intValue());
+ element.reset();
+
+ // Unsigned 32 bit integer.
+ xpath = "/keatest-module:main/ui32";
+ uint32_t u32(32);
+ value = to_string(u32);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(32, element->intValue());
+ element.reset();
+
+ // Unsigned 64 bit integer.
+ xpath = "/keatest-module:main/ui64";
+ uint64_t u64(64);
+ value = to_string(u64);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(64, element->intValue());
+ element.reset();
+
+ // Empty.
+ xpath = "/keatest-module:main/empty";
+ value = string();
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ(string(), element->stringValue());
+ element.reset();
+
+ // Signed 8 bit integer.
+ xpath = "/keatest-module:main/i8";
+ int8_t s8(8);
+ value = to_string(s8);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(8, element->intValue());
+ element.reset();
+
+ // Signed 16 bit integer.
+ xpath = "/keatest-module:main/i16";
+ int16_t s16(16);
+ value = to_string(s16);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(16, element->intValue());
+ element.reset();
+
+ // Signed 32 bit integer.
+ xpath = "/keatest-module:main/i32";
+ int32_t s32(32);
+ value = to_string(s32);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(32, element->intValue());
+ element.reset();
+
+ // Signed 64 bit integer.
+ xpath = "/keatest-module:main/i64";
+ int64_t s64(64);
+ value = to_string(s64);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::integer, element->getType());
+ EXPECT_EQ(64, element->intValue());
+ element.reset();
+
+ // Identity reference.
+ xpath = "/keatest-module:main/id_ref";
+ value = "keatest-module:id_1";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ("keatest-module:id_1", element->stringValue());
+ element.reset();
+
+ // Enumeration item.
+ xpath = "/keatest-module:main/enum";
+ value = "maybe";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ("maybe", element->stringValue());
+ element.reset();
+
+ // Bits.
+ xpath = "/keatest-module:main/options";
+ value = "strict recursive logging";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ("strict recursive logging", element->stringValue());
+ element.reset();
+
+ // Binary.
+ xpath = "/keatest-module:main/raw";
+ value = "Zm9vYmFy";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ("foobar", element->stringValue());
+ element.reset();
+
+ // Leaf-list: not yet exist.
+ xpath = "/keatest-module:main/numbers";
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(element);
+ element.reset();
+
+ // No easy way to create it empty.
+
+ // Leaf-list: 1, 2 and 3.
+ u8 = 1;
+ value = to_string(u8);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ u8 = 2;
+ value = to_string(u8);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ u8 = 3;
+ value = to_string(u8);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::list, element->getType());
+ EXPECT_EQ(3, element->size());
+ EXPECT_EQ("[ 1, 2, 3 ]", element->str());
+ element.reset();
+
+ // Instance identifier.
+ xpath = "/keatest-module:main/instance_id";
+ value = "/keatest-module:main/numbers[.='1']";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ(value, element->stringValue());
+ element.reset();
+
+ // Union.
+ xpath = "/keatest-module:main/union";
+ value = "8";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ(value, element->stringValue());
+ element.reset();
+ value = "infinity";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ(value, element->stringValue());
+ element.reset();
+
+ // Leafref.
+ xpath = "/keatest-module:main/leafref-i8";
+ value = "9";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ(value, element->stringValue());
+ element.reset();
+
+ // Leafref.
+ xpath = "/keatest-module:main/leafref-raw";
+ value = "ff012345";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ(value, element->stringValue());
+ element.reset();
+
+ // Leafref.
+ xpath = "/keatest-module:main/leafref-string";
+ value = "string through leafref";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::string, element->getType());
+ EXPECT_EQ(value, element->stringValue());
+ element.reset();
+
+ // Decimal 64.
+ xpath = "/keatest-module:main/dec64";
+ value = to_string(9.85);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ sess.applyChanges();
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_TRUE(element);
+ ASSERT_EQ(Element::real, element->getType());
+ EXPECT_EQ("9.85", element->str());
+ element.reset();
+
+ // Not existing.
+ xpath = "/keatest-module:main/no_such_node";
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(element);
+ element.reset();
+
+ // Check error.
+ xpath = "null";
+ EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(element);
+}
+
+// Check the Translator::deleteItem function.
+TEST_F(TranslatorTest, deleteItem) {
+ ElementPtr got;
+ string xpath;
+
+ // Get a translator object to play with.
+ Translator translator(Connection{}.sessionStart(), "keatest-module");
+
+ // Missing schema node
+ EXPECT_NO_THROW_LOG(translator.deleteItem("/keatest-module:main/no_such_node"));
+
+ // Existing schema node, but no data
+ xpath = "/keatest-module:main/string";
+ EXPECT_NO_THROW_LOG(translator.deleteItem(xpath));
+ EXPECT_NO_THROW_LOG(got = translator.getItemFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(got);
+ got.reset();
+
+ // Existing schema node, existing data
+ translator.setItem(xpath, Element::create("value"), LeafBaseType::String);
+ EXPECT_NO_THROW_LOG(translator.deleteItem(xpath));
+ EXPECT_NO_THROW_LOG(got = translator.getItemFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(got);
+ got.reset();
+
+ // Container schema node, no data
+ EXPECT_NO_THROW_LOG(translator.deleteItem("/keatest-module:main"));
+ EXPECT_NO_THROW_LOG(got = translator.getItemFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(got);
+ got.reset();
+
+ // Container schema node, existing data
+ translator.setItem(xpath, Element::create("value"), LeafBaseType::String);
+ EXPECT_NO_THROW_LOG(translator.deleteItem("/keatest-module:main"));
+ EXPECT_NO_THROW_LOG(got = translator.getItemFromAbsoluteXpath(xpath));
+ EXPECT_FALSE(got);
+ got.reset();
+}
+
+// Test JSON to basic YANG value conversion using the static method.
+TEST_F(TranslatorTest, valueTo) {
+ optional<string> value;
+
+ // Null.
+ ConstElementPtr element;
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::String));
+ EXPECT_EQ(nullopt, value);
+ EXPECT_FALSE(element);
+
+ // Container.
+ element = Element::createMap();
+ EXPECT_THROW_MSG(Translator::translateToYang(element, LeafBaseType::Unknown), NotImplemented,
+ "Translator::value(): map element");
+
+ // List.
+ element = Element::createList();
+ EXPECT_THROW_MSG(Translator::translateToYang(element, LeafBaseType::Unknown), NotImplemented,
+ "Translator::value(): list element");
+
+ // String.
+ string str("foo");
+ element = Element::create(str);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::String));
+ EXPECT_EQ(element->stringValue(), value);
+
+ // Bool.
+ element = Element::create(false);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Bool));
+ EXPECT_EQ(element->str(), value);
+
+ // Unsigned 8 bit integer.
+ element = Element::create(123);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Uint8));
+ EXPECT_EQ(element->str(), value);
+ element.reset();
+
+ // Unsigned 16 bit integer.
+ element = Element::create(12345);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Uint16));
+ EXPECT_EQ(element->str(), value);
+ element.reset();
+
+ // Unsigned 32 bit integer.
+ element = Element::create(123456789);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Uint32));
+ EXPECT_EQ(element->str(), value);
+ element.reset();
+
+ // Unsigned 64 bit integer.
+ element = Element::create(int64_t(1234567890123456));
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Uint64));
+ EXPECT_EQ("1234567890123456", value);
+ element.reset();
+
+ // Signed 8 bit integer.
+ element = Element::create(-123);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Int8));
+ EXPECT_EQ(element->str(), value);
+ element.reset();
+
+ // Signed 16 bit integer.
+ element = Element::create(-12345);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Int16));
+ EXPECT_EQ(element->str(), value);
+ element.reset();
+
+ // Signed 32 bit integer.
+ element = Element::create(-123456789);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Int32));
+ EXPECT_EQ(element->str(), value);
+ element.reset();
+
+ // Signed 64 bit integer.
+ element = Element::create(int64_t(-1234567890123456));
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Int64));
+ EXPECT_EQ("-1234567890123456", value);
+ element.reset();
+
+ // Identity reference.
+ element = Element::create(str);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::IdentityRef));
+ EXPECT_EQ(element->stringValue(), value);
+
+ // Enumeration item.
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Enum));
+ EXPECT_EQ(element->stringValue(), value);
+
+ // Binary.
+ element = Element::create("foobar");
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Binary));
+ EXPECT_EQ("Zm9vYmFy", value);
+
+ // Bits.
+ element = Element::create("foobar");
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Bits));
+ EXPECT_EQ(element->stringValue(), value);
+
+ // Decimal 64.
+ double d64(.1234);
+ element = Element::create(d64);
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Dec64));
+ EXPECT_EQ(element->str(), value);
+
+ // Empty.
+ element = Element::create(string());
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Empty));
+ EXPECT_EQ(element->stringValue(), value);
+
+ // Leafref.
+ element = Element::create("leafref");
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Leafref));
+ EXPECT_EQ(element->stringValue(), value);
+
+ // Union.
+ element = Element::create("union");
+ EXPECT_NO_THROW_LOG(value = Translator::translateToYang(element, LeafBaseType::Union));
+ EXPECT_EQ(element->stringValue(), value);
+}
+
+// Test JSON to basic YANG value conversion using sysrepo test models.
+TEST_F(TranslatorTest, setItem) {
+ // Get a translator object to play with.
+ Session sess(Connection{}.sessionStart());
+ sess.switchDatastore(sysrepo::Datastore::Candidate);
+ unique_ptr<Translator> translator;
+ ASSERT_NO_THROW_LOG(translator.reset(new Translator(sess, "keatest-module")));
+
+ ElementPtr element;
+ string xpath;
+
+ // String.
+ optional<DataNode> data_node;
+ xpath = "/keatest-module:main/string";
+ element = Element::create("str");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::String));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::String, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Bool.
+ xpath = "/keatest-module:main/boolean";
+ element = Element::create(true);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Bool));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Bool, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Unsigned 8 bit integer.
+ xpath = "/keatest-module:main/ui8";
+ element = Element::create(8);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Uint8));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Uint8, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Unsigned 16 bit integer.
+ xpath = "/keatest-module:main/ui16";
+ element = Element::create(16);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Uint16));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Uint16, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Unsigned 32 bit integer.
+ xpath = "/keatest-module:main/ui32";
+ element = Element::create(32);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Uint32));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Uint32, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Unsigned 64 bit integer.
+ xpath = "/keatest-module:main/ui64";
+ element = Element::create(64);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Uint64));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Uint64, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Empty.
+ xpath = "/keatest-module:main/empty";
+ element = Element::create(string());
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Empty));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Empty, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Signed 8 bit integer.
+ xpath = "/keatest-module:main/i8";
+ element = Element::create(8);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Int8));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Int8, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Signed 16 bit integer.
+ xpath = "/keatest-module:main/i16";
+ element = Element::create(16);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Int16));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Int16, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Signed 32 bit integer.
+ xpath = "/keatest-module:main/i32";
+ element = Element::create(32);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Int32));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Int32, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Signed 64 bit integer.
+ xpath = "/keatest-module:main/i64";
+ element = Element::create(64);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Int64));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Int64, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Identity reference.
+ xpath = "/keatest-module:main/id_ref";
+ element = Element::create("keatest-module:id_1");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::IdentityRef));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::IdentityRef, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Enumeration item.
+ xpath = "/keatest-module:main/enum";
+ element = Element::create("maybe");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Enum));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Enum, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Binary.
+ xpath = "/keatest-module:main/raw";
+ element = Element::create("foobar");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Binary));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Binary, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ("Zm9vYmFy", string(data_node->asTerm().valueStr()));
+
+ // Bits.
+ xpath = "/keatest-module:main/options";
+ element = Element::create("strict recursive logging");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Bits));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Bits, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Decimal 64.
+ xpath = "/keatest-module:main/dec64";
+ double d64(9.85);
+ element = Element::create(d64);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Dec64));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Dec64, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->str(), string(data_node->asTerm().valueStr()));
+
+ // Leaf-list.
+ xpath = "/keatest-module:main/numbers";
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, Element::fromJSON("1"), LeafBaseType::Uint8));
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, Element::fromJSON("2"), LeafBaseType::Uint8));
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, Element::fromJSON("3"), LeafBaseType::Uint8));
+ ElementPtr got(translator->getItemFromAbsoluteXpath(xpath));
+ ASSERT_EQ(LeafBaseType::Dec64, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ("[ 1, 2, 3 ]", got->str());
+
+ // Clean the leaf-list.
+ EXPECT_NO_THROW_LOG(translator->deleteItem(xpath));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ EXPECT_FALSE(data_node);
+ data_node.reset();
+
+ // Instance identifier.
+ xpath = "/keatest-module:main/instance_id";
+ element = Element::create("/keatest-module:main/numbers[.='1']");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::InstanceIdentifier));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::InstanceIdentifier, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Union.
+ xpath = "/keatest-module:main/union";
+ element = Element::create("8");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Union));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Union, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+ element = Element::create("infinity");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Union));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Union, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Leafref.
+ xpath = "/keatest-module:main/leafref-i8";
+ element = Element::create("9");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Leafref));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Leafref, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Leafref.
+ xpath = "/keatest-module:main/leafref-raw";
+ element = Element::create("ff012345");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Leafref));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Leafref, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Leafref.
+ xpath = "/keatest-module:main/leafref-string";
+ element = Element::create("string through");
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Leafref));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ ASSERT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ ASSERT_TRUE(data_node);
+ ASSERT_EQ(LeafBaseType::Leafref, data_node->schema().asLeaf().valueType().base());
+ EXPECT_EQ(element->stringValue(), string(data_node->asTerm().valueStr()));
+
+ // Bad xpath.
+ xpath = "/keatest-module:main/no_such_node";
+ element = Element::create("str");
+ EXPECT_THROW_MSG(translator->setItem(xpath, element, LeafBaseType::String), NetconfError,
+ "setting item '\"str\"' at '" + xpath +
+ "': Session::setItem: Couldn't set "
+ "'/keatest-module:main/no_such_node' to 'str': SR_ERR_INVAL_ARG");
+
+ // Bad type.
+ xpath = "/keatest-module:main/string";
+ element = Element::create(true);
+ EXPECT_NO_THROW_LOG(translator->setItem(xpath, element, LeafBaseType::Bool));
+
+ element = translator->getItemFromAbsoluteXpath(xpath);
+ ASSERT_TRUE(element);
+ EXPECT_EQ(element->getType(), Element::string);
+ EXPECT_EQ(element->str(), "\"true\"");
+
+ // Delete (twice).
+ xpath = "/keatest-module:main/string";
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ EXPECT_TRUE(data_node);
+ EXPECT_NO_THROW_LOG(data_node = data_node->findPath(xpath));
+ EXPECT_TRUE(data_node);
+ ASSERT_TRUE(NodeType::Leaf == data_node->schema().nodeType());
+ EXPECT_EQ("true", data_node->asTerm().valueStr());
+ EXPECT_NO_THROW_LOG(translator->deleteItem(xpath));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ EXPECT_FALSE(data_node);
+ EXPECT_NO_THROW_LOG(translator->deleteItem(xpath));
+ EXPECT_NO_THROW_LOG(data_node = sess.getData(xpath));
+ EXPECT_FALSE(data_node);
+}
+
+// Test YANG container retrieval.
+TEST_F(TranslatorTest, container) {
+ ElementPtr element;
+
+ // Get a translator object to play with.
+ Translator translator(Connection{}.sessionStart(), "keatest-module");
+
+ // Container with no data apparently throws.
+ EXPECT_THROW_MSG(element = translator.getItemFromAbsoluteXpath("/keatest-module:container"), NotImplemented,
+ "getting node of type 1 not supported, xpath is '/keatest-module:container'");
+ EXPECT_FALSE(element);
+ element.reset();
+
+ EXPECT_NO_THROW_LOG(
+ translator.setItem("/keatest-module:container",
+ ElementPtr(), LeafBaseType::Unknown));
+
+ // Container with data filled in throws when retrieving the container itself.
+ element = Element::create("Leaf value");
+ EXPECT_NO_THROW_LOG(
+ translator.setItem("/keatest-module:container/list[key1='key1'][key2='key2']/leaf",
+ element, LeafBaseType::String));
+ element.reset();
+ EXPECT_THROW_MSG(
+ element = translator.getItemFromAbsoluteXpath("/keatest-module:container"), NotImplemented,
+ "getting node of type 1 not supported, xpath is '/keatest-module:container'");
+ EXPECT_FALSE(element);
+ element.reset();
+ EXPECT_NO_THROW_LOG(
+ element = translator.getItemFromAbsoluteXpath("/keatest-module:container[key1='key1'][key2='key2']"));
+ EXPECT_FALSE(element);
+}
+
+// Test YANG list retrieval.
+TEST_F(TranslatorTest, list) {
+ ElementPtr element;
+
+ // Get a translator to play with.
+ Translator translator(Connection{}.sessionStart(), "keatest-module");
+
+ // List with no data does not throw and returns a null ElementPtr by default.
+ EXPECT_NO_THROW_LOG(
+ element = translator.getItemFromAbsoluteXpath("/keatest-module:container/list"));
+ EXPECT_FALSE(element);
+ element.reset();
+
+ // List with data filled in throws when retrieving the list itself.
+ element = Element::create("Leaf value");
+ EXPECT_NO_THROW_LOG(
+ translator.setItem("/keatest-module:container/list[key1='key1'][key2='key2']/leaf", element,
+ LeafBaseType::String));
+ element.reset();
+ EXPECT_THROW_MSG(
+ element = translator.getItemFromAbsoluteXpath("/keatest-module:container/list"), NotImplemented,
+ "getting node of type 16 not supported, xpath is '/keatest-module:container/list'");
+ EXPECT_FALSE(element);
+ element.reset();
+ EXPECT_THROW_MSG(element = translator.getItemFromAbsoluteXpath(
+ "/keatest-module:container/list[key1='key1'][key2='key2']"), NotImplemented,
+ "getting node of type 16 not supported, xpath is "
+ "'/keatest-module:container/list[key1='key1'][key2='key2']'");
+ EXPECT_FALSE(element);
+}
+
+// Test that we can check if a schema node exists.
+TEST_F(TranslatorTest, schemaNodeExists) {
+ // Get a translator to play with.
+ Translator translator(Connection{}.sessionStart(), "keatest-module");
+
+ EXPECT_FALSE(translator.schemaNodeExists("/keatest-module:main/no_such_node"));
+ EXPECT_TRUE(translator.schemaNodeExists("/keatest-module:main"));
+ EXPECT_TRUE(translator.schemaNodeExists("/keatest-module:main/string"));
+ EXPECT_TRUE(translator.schemaNodeExists("/keatest-module:container/list"));
+}
+
+} // anonymous namespace
diff --git a/src/lib/yang/tests/translator_utils_unittests.cc b/src/lib/yang/tests/translator_utils_unittests.cc
new file mode 100644
index 0000000..ddd4cb9
--- /dev/null
+++ b/src/lib/yang/tests/translator_utils_unittests.cc
@@ -0,0 +1,288 @@
+// Copyright (C) 2018-2022 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 <testutils/gtest_utils.h>
+#include <yang/testutils/translator_test.h>
+#include <yang/tests/sysrepo_setup.h>
+#include <yang/tests/yang_configs.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace {
+
+// Test LeafBaseType print.
+TEST(YangReprTest, type) {
+ ostringstream os;
+
+ // Verify that string is "string" (vs a number).
+ LeafBaseType t(LeafBaseType::String);
+ os << t;
+ EXPECT_EQ("string", os.str());
+ os.str("");
+
+ // Compiler does not let to create an invalid value...
+}
+
+// Test YangReprItem basic stuff.
+TEST(YangReprTest, item) {
+ // An item.
+ YRItem item1("/foo", "bar", LeafBaseType::String, true);
+ EXPECT_EQ("/foo", item1.xpath_);
+ EXPECT_EQ("bar", item1.value_);
+ EXPECT_TRUE(item1.settable_);
+
+ // EXPECT_EQ doesn't work. Tries to << into a stringstream.
+ EXPECT_TRUE(LeafBaseType::String == item1.type_);
+
+ // Another one.
+ YRItem item2("/foo", "bar", LeafBaseType::String, false);
+ EXPECT_EQ("/foo", item2.xpath_);
+ EXPECT_EQ("bar", item2.value_);
+ EXPECT_FALSE(item2.settable_);
+
+ // EXPECT_EQ doesn't work. Tries to << into a stringstream.
+ EXPECT_TRUE(LeafBaseType::String == item2.type_);
+
+ // Equality.
+ EXPECT_TRUE(item1 == item2);
+ EXPECT_TRUE(item2 == item1);
+ EXPECT_FALSE(item1 != item2);
+ EXPECT_FALSE(item2 != item1);
+ EXPECT_EQ(item1, item2);
+ EXPECT_EQ(item2, item1);
+}
+
+// Test get with test module.
+TEST(YangReprTest, getTest) {
+ SysrepoSetup::cleanSharedMemory();
+
+ // Get a translator object to play with.
+ Session sess(Connection{}.sessionStart());
+ sess.switchDatastore(sysrepo::Datastore::Candidate);
+
+ // Cleanup.
+ EXPECT_NO_THROW_LOG(sess.deleteItem("/keatest-module:container"));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+ EXPECT_NO_THROW_LOG(sess.deleteItem("/keatest-module:main"));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ // Fill the test module.
+ string xpath;
+ string value;
+
+ xpath = "/keatest-module:main/string";
+ value = "str";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/boolean";
+ value = "true";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/ui8";
+ uint8_t u8(8);
+ value = to_string(u8);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/ui16";
+ uint16_t u16(16);
+ value = to_string(u16);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/ui32";
+ uint32_t u32(32);
+ value = to_string(u32);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/ui64";
+ uint64_t u64(64);
+ value = to_string(u64);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/i8";
+ int8_t s8(8);
+ value = to_string(s8);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/i16";
+ int16_t s16(16);
+ value = to_string(s16);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/i32";
+ int32_t s32(32);
+ value = to_string(s32);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/i64";
+ int64_t s64(64);
+ value = to_string(s64);
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/id_ref";
+ value = "keatest-module:id_1";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ xpath = "/keatest-module:main/enum";
+ value = "maybe";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ // Binary.
+ xpath = "/keatest-module:main/raw";
+ value = "Zm9vYmFy";
+ EXPECT_NO_THROW_LOG(sess.setItem(xpath, value));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ // Get it.
+ YangRepr repr(testModel);
+ YRTree tree;
+ EXPECT_NO_THROW_LOG(tree = repr.get(sess));
+
+ // Verify.
+ EXPECT_TRUE(repr.verify(testTree, sess, cerr));
+}
+
+// This test verifies that errors are handled properly.
+TEST(YangReprTrest, getTestErrors) {
+ SysrepoSetup::cleanSharedMemory();
+
+ // Get a translator object to play with.
+ Session sess(Connection{}.sessionStart());
+ sess.switchDatastore(sysrepo::Datastore::Candidate);
+
+ // Cleanup.
+ EXPECT_NO_THROW_LOG(sess.deleteItem("/keatest-module:container"));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+ EXPECT_NO_THROW_LOG(sess.deleteItem("/keatest-module:main"));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ // Get it.
+ YangRepr repr(testModel);
+ YRTree tree;
+ EXPECT_NO_THROW_LOG(repr.set(testTree, sess));
+
+ // Verify.
+ EXPECT_TRUE(repr.verify(testTree, sess, cerr));
+
+ // Change a path. Remove final 'm'.
+ YRTree badpath = testTree;
+ string xpath("/keatest-module:main/enum");
+ YRItem node(badpath.at(xpath));
+ node.xpath_ = "/keatest-module:main/enu";
+ badpath.erase(xpath);
+ badpath.emplace(xpath, node);
+ EXPECT_FALSE(repr.verify(badpath, sess, cerr));
+
+ // Change a value from "str" to "Str".
+ YRTree badvalue = testTree;
+ xpath = "/keatest-module:main/string";
+ badvalue.at(xpath).value_ = "Str";
+ EXPECT_FALSE(repr.verify(badvalue, sess, cerr));
+
+ // Change a type from LeafBaseType::Int32 to LeafBaseType::Uint32.
+ YRTree badtype = testTree;
+ xpath = "/keatest-module:main/i32";
+ badtype.at(xpath).type_ = LeafBaseType::Uint32;
+ EXPECT_FALSE(repr.verify(badtype, sess, cerr));
+
+ // Delete last record.
+ YRTree badextra = testTree;
+ badextra.erase("/keatest-module:kernel-modules");
+ EXPECT_FALSE(repr.verify(badextra, sess, cerr));
+}
+
+// Test set with test module.
+TEST(YangReprTest, setTest) {
+ SysrepoSetup::cleanSharedMemory();
+
+ // Get a translator object to play with.
+ Session sess(Connection{}.sessionStart());
+ sess.switchDatastore(sysrepo::Datastore::Candidate);
+
+ // Cleanup.
+ EXPECT_NO_THROW_LOG(sess.deleteItem("/keatest-module:container"));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+ EXPECT_NO_THROW_LOG(sess.deleteItem("/keatest-module:main"));
+ EXPECT_NO_THROW_LOG(sess.applyChanges());
+
+ // Set the module content.
+ YangRepr repr(testModel);
+ EXPECT_NO_THROW_LOG(repr.set(testTree, sess));
+
+ // Verify it.
+ EXPECT_TRUE(repr.verify(testTree, sess, cerr));
+}
+
+/// @brief Tests specified configuration.
+///
+/// Configuration is set and then verified using YangRepr object.
+///
+/// @param model name of the model to be verified against.
+/// @param tree tree to be verified.
+void sanityCheckConfig(const string& model, const YRTree& tree) {
+ SCOPED_TRACE("\n* Tested model: " + model);
+ SysrepoSetup::cleanSharedMemory();
+
+ // Get a translator object to play with.
+ Session sess(Connection{}.sessionStart());
+ sess.switchDatastore(sysrepo::Datastore::Candidate);
+
+ // Cleanup.
+ Translator translator(sess, model);
+ if (model == "keatest-module") {
+ translator.deleteItem("/keatest-module:container");
+ translator.deleteItem("/keatest-module:kernel-modules");
+ translator.deleteItem("/keatest-module:list");
+ translator.deleteItem("/keatest-module:main");
+ translator.deleteItem("/keatest-module:presence-container");
+ } else {
+ string toplevel_node("config");
+ if (model == IETF_DHCPV6_SERVER) {
+ toplevel_node = "server";
+ }
+ EXPECT_NO_THROW_LOG(translator.deleteItem("/" + model + ":" + toplevel_node));
+ }
+
+ // Get it.
+ YangRepr repr(model);
+
+ EXPECT_NO_THROW_LOG(repr.set(tree, sess));
+ bool result = false;
+ EXPECT_NO_THROW_LOG(result = repr.verify(tree, sess, cerr));
+ EXPECT_TRUE(result);
+}
+
+// This is test environment sanity check. It verifies that all configuration
+// defined in yang_configs.h are sane.
+TEST(YangReprTest, verifyConfigs) {
+ for (auto x : TEST_CONFIGS) {
+ sanityCheckConfig(x.first, x.second);
+ }
+}
+
+} // namespace
diff --git a/src/lib/yang/tests/yang_configs.h b/src/lib/yang/tests/yang_configs.h
new file mode 100644
index 0000000..d3825c9
--- /dev/null
+++ b/src/lib/yang/tests/yang_configs.h
@@ -0,0 +1,655 @@
+// Copyright (C) 2018-2023 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 ISC_YANG_CONFIGS_H
+#define ISC_YANG_CONFIGS_H
+
+#include <yang/testutils/translator_test.h>
+#include <yang/yang_models.h>
+
+#include <vector>
+
+namespace isc {
+namespace yang {
+namespace test {
+
+/// @brief The test module from sysrepo tests.
+const std::string testModel = "keatest-module";
+const YRTree testTree = YangRepr::buildTreeFromVector({
+ { "/keatest-module:container",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/keatest-module:main",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/keatest-module:main/string",
+ "str", libyang::LeafBaseType::String, true },
+ { "/keatest-module:main/boolean",
+ "true", libyang::LeafBaseType::Bool, true },
+ { "/keatest-module:main/ui8",
+ "8", libyang::LeafBaseType::Uint8, true },
+ { "/keatest-module:main/ui16",
+ "16", libyang::LeafBaseType::Uint16, true },
+ { "/keatest-module:main/ui32",
+ "32", libyang::LeafBaseType::Uint32, true },
+ { "/keatest-module:main/ui64",
+ "64", libyang::LeafBaseType::Uint64, true },
+ { "/keatest-module:main/i8",
+ "8", libyang::LeafBaseType::Int8, true },
+ { "/keatest-module:main/i16",
+ "16", libyang::LeafBaseType::Int16, true },
+ { "/keatest-module:main/i32",
+ "32", libyang::LeafBaseType::Int32, true },
+ { "/keatest-module:main/i64",
+ "64", libyang::LeafBaseType::Int64, true },
+ { "/keatest-module:main/id_ref",
+ "keatest-module:id_1", libyang::LeafBaseType::IdentityRef, true },
+ { "/keatest-module:main/enum",
+ "maybe", libyang::LeafBaseType::Enum, true },
+ { "/keatest-module:main/raw",
+ "Zm9vYmFy", libyang::LeafBaseType::Binary, true },
+ { "/keatest-module:kernel-modules",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+/// @brief A subnet with two pools with ietf-dhcpv6-server model.
+const std::string subnetTwoPoolsModelIetf6 = IETF_DHCPV6_SERVER;
+const YRTree subnetTwoPoolsTreeIetf6 = YangRepr::buildTreeFromVector({
+ { "/ietf-dhcpv6-server:server",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/network-range-id",
+ "111", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/network-prefix",
+ "2001:db8::/48", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/pool-id",
+ "0", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/pool-prefix",
+ "2001:db8::1:0/112", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/start-address",
+ "2001:db8::1:0", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/end-address",
+ "2001:db8::1:ffff", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/max-address-count",
+ "disabled", libyang::LeafBaseType::Enum, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/pool-id",
+ "1", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/pool-prefix",
+ "2001:db8::2:0/112", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/start-address",
+ "2001:db8::2:0", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/end-address",
+ "2001:db8::2:ffff", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/max-address-count",
+ "disabled", libyang::LeafBaseType::Enum, true },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid/type-code",
+ "65535", libyang::LeafBaseType::Uint16, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/host-reservations",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/lease-storage",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/vendor-info",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/option-sets",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/relay-opaque-paras",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/rsoo-enabled-options",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+/// @brief A subnet with timers with ietf-dhcpv6-server model.
+const std::string subnetTimersModel = IETF_DHCPV6_SERVER;
+const YRTree subnetTimersIetf6 = YangRepr::buildTreeFromVector({
+ { "/ietf-dhcpv6-server:server",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/network-range-id",
+ "111", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/network-prefix",
+ "2001:db8::/48", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/pool-id",
+ "0", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/pool-prefix",
+ "2001:db8::1:0/112", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/start-address",
+ "2001:db8::1:0", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/end-address",
+ "2001:db8::1:ffff", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/renew-time",
+ "1000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/rebind-time",
+ "2000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='0']/max-address-count",
+ "disabled", libyang::LeafBaseType::Enum, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/pool-id",
+ "1", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/pool-prefix",
+ "2001:db8::2:0/112", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/start-address",
+ "2001:db8::2:0", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/end-address",
+ "2001:db8::2:ffff", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/renew-time",
+ "1000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/rebind-time",
+ "2000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools/"
+ "address-pool[pool-id='1']/max-address-count",
+ "disabled", libyang::LeafBaseType::Enum, true },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid/type-code",
+ "65535", libyang::LeafBaseType::Uint16, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/host-reservations",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/lease-storage",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/vendor-info",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/option-sets",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/relay-opaque-paras",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/rsoo-enabled-options",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+/// @brief A subnet with two pools with ietf-dhcpv6-server model
+/// which validates.
+const std::string validModelIetf6 = IETF_DHCPV6_SERVER;
+const YRTree validTreeIetf6 = YangRepr::buildTreeFromVector({
+ { "/ietf-dhcpv6-server:server",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/vendor-info",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes"
+ "/vendor-info/ent-num",
+ "2495", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/option-sets",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/network-range-id",
+ "111", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/network-description",
+ "Subnet#111", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/network-prefix",
+ "2001:db8::/48", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/pool-id",
+ "0", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/prefix",
+ "2001:db8:1::/48", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/prefix-length",
+ "48", libyang::LeafBaseType::Uint8, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/valid-lifetime",
+ "4000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/preferred-lifetime",
+ "3000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/renew-time",
+ "1000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/rebind-time",
+ "2000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/rapid-commit",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/option-set-id",
+ "0", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='0']/max-pd-space-utilization",
+ "disabled", libyang::LeafBaseType::Enum, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/pool-id",
+ "1", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/prefix",
+ "2001:db8:2::/48", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/prefix-length",
+ "48", libyang::LeafBaseType::Uint8, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/valid-lifetime",
+ "4000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/preferred-lifetime",
+ "3000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/renew-time",
+ "1000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/rebind-time",
+ "2000", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/rapid-commit",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/option-set-id",
+ "0", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools/"
+ "pd-pool[pool-id='1']/max-pd-space-utilization",
+ "disabled", libyang::LeafBaseType::Enum, true },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid/type-code",
+ "65535", libyang::LeafBaseType::Uint16, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/address-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/pd-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='111']/host-reservations",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/lease-storage",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/relay-opaque-paras",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/rsoo-enabled-options",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+/// @brief A subnet with a pool and option data lists with
+/// kea-dhcp4-server:config model.
+const std::string subnetOptionsModelKeaDhcp4 = KEA_DHCP4_SERVER;
+const YRTree subnetOptionsTreeKeaDhcp4 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/id",
+ "111", libyang::LeafBaseType::Uint32, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "option-data[code='100'][space='dns']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "option-data[code='100'][space='dns']/code",
+ "100", libyang::LeafBaseType::Uint8, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "option-data[code='100'][space='dns']/space",
+ "dns", libyang::LeafBaseType::String, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "option-data[code='100'][space='dns']/data",
+ "12121212", libyang::LeafBaseType::String, true },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "option-data[code='100'][space='dns']/csv-format",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "option-data[code='100'][space='dns']/always-send",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "option-data[code='100'][space='dns']/never-send",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "pool[start-address='10.0.1.0'][end-address='10.0.1.255']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "pool[start-address='10.0.1.0'][end-address='10.0.1.255']/start-address",
+ "10.0.1.0", libyang::LeafBaseType::String, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "pool[start-address='10.0.1.0'][end-address='10.0.1.255']/end-address",
+ "10.0.1.255", libyang::LeafBaseType::String, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/"
+ "pool[start-address='10.0.1.0'][end-address='10.0.1.255']/prefix",
+ "10.0.1.0/24", libyang::LeafBaseType::String, true },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/subnet",
+ "10.0.0.0/8", libyang::LeafBaseType::String, true },
+ { "/kea-dhcp4-server:config/expired-leases-processing",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/dhcp-ddns",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/config-control",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/sanity-checks",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/interfaces-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/subnet4[id='111']/relay",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/compatibility",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/multi-threading",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+/// @brief A subnet with a pool and option data lists with
+/// kea-dhcp6-server:config model.
+const std::string subnetOptionsModelKeaDhcp6 = KEA_DHCP6_SERVER;
+const YRTree subnetOptionsTreeKeaDhcp6 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp6-server:config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/id",
+ "111", libyang::LeafBaseType::Uint32, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "start-address",
+ "2001:db8::1:0", libyang::LeafBaseType::String, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "end-address",
+ "2001:db8::1:ffff", libyang::LeafBaseType::String, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "prefix",
+ "2001:db8::1:0/112", libyang::LeafBaseType::String, true },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "option-data[code='100'][space='dns']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "option-data[code='100'][space='dns']/code",
+ "100", libyang::LeafBaseType::Uint16, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "option-data[code='100'][space='dns']/space",
+ "dns", libyang::LeafBaseType::String, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "option-data[code='100'][space='dns']/data",
+ "12121212", libyang::LeafBaseType::String, true },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "option-data[code='100'][space='dns']/csv-format",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "option-data[code='100'][space='dns']/always-send",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/"
+ "pool[start-address='2001:db8::1:0'][end-address='2001:db8::1:ffff']/"
+ "option-data[code='100'][space='dns']/never-send",
+ "false", libyang::LeafBaseType::Bool, true },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/subnet",
+ "2001:db8::/48", libyang::LeafBaseType::String, true },
+ { "/kea-dhcp6-server:config/expired-leases-processing",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/dhcp-ddns",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/config-control",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/sanity-checks",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/interfaces-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/subnet6[id='111']/relay",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/compatibility",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/multi-threading",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+/// @brief Example from the design document.
+const std::string designExampleModel = IETF_DHCPV6_SERVER;
+const YRTree designExampleTree = YangRepr::buildTreeFromVector({
+ { "/ietf-dhcpv6-server:server",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/network-range-id",
+ "1", libyang::LeafBaseType::Uint32, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/network-description",
+ "example", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/pd-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/pd-pools/pd-pool[pool-id='0']",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/pd-pools/pd-pool[pool-id='0']"
+ "/pool-id",
+ "0", libyang::LeafBaseType::Uint32, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/pd-pools/pd-pool[pool-id='0']"
+ "/prefix",
+ "2001:db8:20:b00::/57", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/pd-pools/pd-pool[pool-id='0']"
+ "/prefix-length",
+ "57", libyang::LeafBaseType::Uint8, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/pd-pools/"
+ "pd-pool[pool-id='0']/max-pd-space-utilization",
+ "disabled", libyang::LeafBaseType::Enum, true },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/network-prefix",
+ "2001:db8:20:b00::/56", libyang::LeafBaseType::String, true },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid/type-code",
+ "65535", libyang::LeafBaseType::Uint16, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/host-reservations",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/duid",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/lease-storage",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/serv-attributes/vendor-info",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/option-sets",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/relay-opaque-paras",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/rsoo-enabled-options",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/ietf-dhcpv6-server:server/server-config/network-ranges/"
+ "network-range[network-range-id='1']/address-pools",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+const YRTree emptyTreeKeaDhcp4 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp4-server:config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/expired-leases-processing",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/dhcp-ddns",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/config-control",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/sanity-checks",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/interfaces-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/compatibility",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp4-server:config/multi-threading",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+const YRTree emptyTreeKeaDhcp6 = YangRepr::buildTreeFromVector({
+ { "/kea-dhcp6-server:config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/expired-leases-processing",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/dhcp-ddns",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/config-control",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/sanity-checks",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/interfaces-config",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/compatibility",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+ { "/kea-dhcp6-server:config/multi-threading",
+ std::nullopt, libyang::LeafBaseType::Unknown, false },
+});
+
+/// @brief Set of example configurations.
+const std::vector<std::pair<std::string, YRTree> > TEST_CONFIGS =
+{
+ { testModel, testTree },
+ { subnetTwoPoolsModelIetf6, subnetTwoPoolsTreeIetf6 },
+ { subnetTimersModel, subnetTimersIetf6 },
+ { validModelIetf6, validTreeIetf6 },
+ { subnetOptionsModelKeaDhcp4, subnetOptionsTreeKeaDhcp4 },
+ { subnetOptionsModelKeaDhcp6, subnetOptionsTreeKeaDhcp6 },
+ { designExampleModel, designExampleTree }
+}; // TEST_CONFIGS
+
+} // namespace test
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_YANG_CONFIGS_H
diff --git a/src/lib/yang/testutils/Makefile.am b/src/lib/yang/testutils/Makefile.am
new file mode 100644
index 0000000..408d555
--- /dev/null
+++ b/src/lib/yang/testutils/Makefile.am
@@ -0,0 +1,30 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(LIBYANG_CPPFLAGS)
+AM_CPPFLAGS += $(LIBYANG_INCLUDEDIR)
+AM_CPPFLAGS += $(LIBYANGCPP_CPPFLAGS)
+AM_CPPFLAGS += $(LIBYANGCPP_INCLUDEDIR)
+AM_CPPFLAGS += $(SYSREPO_CPPFLAGS)
+AM_CPPFLAGS += $(SYSREPO_INCLUDEDIR)
+AM_CPPFLAGS += $(SYSREPOCPP_CPPFLAGS)
+AM_CPPFLAGS += $(SYSREPOCPP_INCLUDEDIR)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libyangtest.la
+
+libyangtest_la_SOURCES = translator_test.cc translator_test.h
+
+libyangtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libyangtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libyangtest_la_LDFLAGS = $(AM_LDFLAGS)
+
+libyangtest_la_LIBADD = $(top_builddir)/src/lib/yang/libkea-yang.la
+libyangtest_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+
+endif
diff --git a/src/lib/yang/testutils/Makefile.in b/src/lib/yang/testutils/Makefile.in
new file mode 100644
index 0000000..56435fc
--- /dev/null
+++ b/src/lib/yang/testutils/Makefile.in
@@ -0,0 +1,867 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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/yang/testutils
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.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 =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libyangtest_la_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/libkea-yang.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+am__libyangtest_la_SOURCES_DIST = translator_test.cc translator_test.h
+@HAVE_GTEST_TRUE@am_libyangtest_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libyangtest_la-translator_test.lo
+libyangtest_la_OBJECTS = $(am_libyangtest_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 =
+libyangtest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libyangtest_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libyangtest_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libyangtest_la_rpath =
+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)/libyangtest_la-translator_test.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 = $(libyangtest_la_SOURCES)
+DIST_SOURCES = $(am__libyangtest_la_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)`
+ETAGS = etags
+CTAGS = ctags
+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@
+CPP = @CPP@
+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@
+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@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+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@
+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 = .
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \
+ $(BOOST_INCLUDES) $(LIBYANG_CPPFLAGS) $(LIBYANG_INCLUDEDIR) \
+ $(LIBYANGCPP_CPPFLAGS) $(LIBYANGCPP_INCLUDEDIR) \
+ $(SYSREPO_CPPFLAGS) $(SYSREPO_INCLUDEDIR) \
+ $(SYSREPOCPP_CPPFLAGS) $(SYSREPOCPP_INCLUDEDIR)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+CLEANFILES = *.gcno *.gcda
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libyangtest.la
+@HAVE_GTEST_TRUE@libyangtest_la_SOURCES = translator_test.cc translator_test.h
+@HAVE_GTEST_TRUE@libyangtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libyangtest_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@libyangtest_la_LDFLAGS = $(AM_LDFLAGS)
+@HAVE_GTEST_TRUE@libyangtest_la_LIBADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/yang/libkea-yang.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+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/yang/testutils/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib/yang/testutils/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-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_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}; \
+ }
+
+libyangtest.la: $(libyangtest_la_OBJECTS) $(libyangtest_la_DEPENDENCIES) $(EXTRA_libyangtest_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libyangtest_la_LINK) $(am_libyangtest_la_rpath) $(libyangtest_la_OBJECTS) $(libyangtest_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libyangtest_la-translator_test.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)$(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 $@ $<
+
+libyangtest_la-translator_test.lo: translator_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libyangtest_la_CPPFLAGS) $(CPPFLAGS) $(libyangtest_la_CXXFLAGS) $(CXXFLAGS) -MT libyangtest_la-translator_test.lo -MD -MP -MF $(DEPDIR)/libyangtest_la-translator_test.Tpo -c -o libyangtest_la-translator_test.lo `test -f 'translator_test.cc' || echo '$(srcdir)/'`translator_test.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libyangtest_la-translator_test.Tpo $(DEPDIR)/libyangtest_la-translator_test.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='translator_test.cc' object='libyangtest_la-translator_test.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) $(libyangtest_la_CPPFLAGS) $(CPPFLAGS) $(libyangtest_la_CXXFLAGS) $(CXXFLAGS) -c -o libyangtest_la-translator_test.lo `test -f 'translator_test.cc' || echo '$(srcdir)/'`translator_test.cc
+
+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
+
+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: check-recursive
+all-am: Makefile $(LTLIBRARIES)
+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-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/libyangtest_la-translator_test.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-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)/libyangtest_la-translator_test.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:
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \
+ am--depfiles check check-am clean clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES 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/yang/testutils/translator_test.cc b/src/lib/yang/testutils/translator_test.cc
new file mode 100644
index 0000000..36012db
--- /dev/null
+++ b/src/lib/yang/testutils/translator_test.cc
@@ -0,0 +1,276 @@
+// Copyright (C) 2018-2022 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 <yang/testutils/translator_test.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <variant>
+#include <vector>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+namespace test {
+
+LeafBaseType
+YangRepr::YangReprItem::getUnionType(Value const& value) {
+ static_assert(
+ std::is_same<
+ Value, std::variant<int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, uint32_t,
+ uint64_t, bool, Empty, Binary, string, optional<DataNode>,
+ Decimal64, vector<Bit>, Enum, IdentityRef>>::value,
+ "Value type has changed. The if statement needs to be adjusted to include all alternatives "
+ "of the std::variant.");
+
+ if (holds_alternative<int8_t>(value)) {
+ return LeafBaseType::Int8;
+ } else if (holds_alternative<int16_t>(value)) {
+ return LeafBaseType::Int16;
+ } else if (holds_alternative<int32_t>(value)) {
+ return LeafBaseType::Int32;
+ } else if (holds_alternative<int64_t>(value)) {
+ return LeafBaseType::Int64;
+ } else if (holds_alternative<uint8_t>(value)) {
+ return LeafBaseType::Uint8;
+ } else if (holds_alternative<uint16_t>(value)) {
+ return LeafBaseType::Uint16;
+ } else if (holds_alternative<uint32_t>(value)) {
+ return LeafBaseType::Uint32;
+ } else if (holds_alternative<uint64_t>(value)) {
+ return LeafBaseType::Uint64;
+ } else if (holds_alternative<bool>(value)) {
+ return LeafBaseType::Bool;
+ } else if (holds_alternative<Empty>(value)) {
+ return LeafBaseType::Empty;
+ } else if (holds_alternative<Binary>(value)) {
+ return LeafBaseType::Binary;
+ } else if (holds_alternative<string>(value)) {
+ return LeafBaseType::String;
+ } else if (holds_alternative<optional<DataNode>>(value)) {
+ return LeafBaseType::InstanceIdentifier;
+ } else if (holds_alternative<Decimal64>(value)) {
+ return LeafBaseType::Dec64;
+ } else if (holds_alternative<vector<Bit>>(value)) {
+ return LeafBaseType::Bits;
+ } else if (holds_alternative<Enum>(value)) {
+ return LeafBaseType::Enum;
+ } else if (holds_alternative<IdentityRef>(value)) {
+ return LeafBaseType::IdentityRef;
+ }
+ return LeafBaseType::Unknown;
+}
+
+YangRepr::YangReprItem
+YangRepr::YangReprItem::get(const string& xpath, Session session) {
+ string val_xpath;
+ optional<string> value;
+ LeafBaseType type(LeafBaseType::Unknown);
+ bool settable = true;
+ try {
+ optional<DataNode> data_node(session.getData(xpath));
+ if (!data_node) {
+ isc_throw(BadValue, "YangReprItem failed at '" << xpath << "'");
+ }
+ data_node = data_node->findPath(xpath);
+ SchemaNode const& schema(data_node->schema());
+ NodeType const node_type(schema.nodeType());
+ if (node_type == NodeType::Leaf) {
+ type = schema.asLeaf().valueType().base();
+ } else if (node_type == NodeType::Leaflist) {
+ type = schema.asLeafList().valueType().base();
+ } else {
+ settable = false;
+ }
+ if (type == LeafBaseType::Union) {
+ // Get the underlying type.
+ type = getUnionType(data_node->asTerm().value());
+ }
+ if (type == LeafBaseType::Leafref) {
+ // Get the underlying type.
+ type = data_node->schema().asLeaf().valueType().asLeafRef().resolvedType().base();
+ }
+ value = Translator::translateToYang(Translator::translateFromYang(data_node), type);
+ val_xpath = string(data_node->path());
+ } catch (Error const& ex) {
+ isc_throw(NetconfError, "in YangReprItem: " << ex.what());
+ }
+ return (YangReprItem(val_xpath, value, type, settable));
+}
+
+YangRepr::Tree
+YangRepr::get(Session session) const {
+ Tree result;
+ try {
+ Translator tb(session, model_);
+ string const xpath0("/" + model_ + ":*//.");
+ tb.forAll(xpath0, [&](libyang::DataNode const& node) {
+ string const& xpath(node.path());
+ result.emplace(xpath, YangReprItem::get(xpath, session));
+ });
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "in YangRepr::getTree: " << ex.what());
+ }
+ return (result);
+}
+
+bool
+YangRepr::verify(const Tree& expected, Session session,
+ ostream& errs) const {
+ bool result(true);
+ const Tree& received = get(session);
+ for (auto const& kv : received) {
+ string const& xpath(kv.first);
+ YangReprItem const& received_node(kv.second);
+
+ auto iterator(expected.find(xpath));
+ if (iterator == expected.end()) {
+ errs << "received " << received_node << ", but was not expected"
+ << endl;
+ result = false;
+ continue;
+ }
+
+ YangReprItem const expected_node(iterator->second);
+ if (expected_node != received_node) {
+ errs << "expected " << expected_node << ", but received "
+ << received_node << endl;
+ result = false;
+ }
+ }
+
+ for (auto const& kv : expected) {
+ string const& xpath(kv.first);
+ YangReprItem const& expected_node(kv.second);
+
+ auto iterator(received.find(xpath));
+ if (iterator == received.end()) {
+ errs << "expected " << expected_node << ", but was not received"
+ << endl;
+ result = false;
+ }
+ }
+
+ return result;
+}
+
+void
+YangRepr::set(const Tree& tree, Session session) const {
+ for (auto const& kv : tree) {
+ YangReprItem const& item(kv.second);
+ if (!item.settable_) {
+ continue;
+ }
+ try {
+ Context const& context(session.getContext());
+ SchemaNode const& schema(context.findPath(item.xpath_));
+ NodeType const node_type(schema.nodeType());
+ bool is_key(false);
+ if (node_type == NodeType::Leaf) {
+ is_key = schema.asLeaf().isKey();
+ }
+ if (!is_key) {
+ session.setItem(item.xpath_, item.value_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "in YangRepr::set for " << item
+ << ", error: " << ex.what());
+ }
+ }
+ session.applyChanges();
+}
+
+ostream&
+operator<<(ostream& os, LeafBaseType type) {
+ switch (type) {
+ case LeafBaseType::String:
+ os << "string";
+ break;
+ case LeafBaseType::Bool:
+ os << "bool";
+ break;
+ case LeafBaseType::Uint8:
+ os << "uint8";
+ break;
+ case LeafBaseType::Uint16:
+ os << "uint16";
+ break;
+ case LeafBaseType::Uint32:
+ os << "uint32";
+ break;
+ case LeafBaseType::Int8:
+ os << "int8";
+ break;
+ case LeafBaseType::Int16:
+ os << "int16";
+ break;
+ case LeafBaseType::Int32:
+ os << "int32";
+ break;
+ case LeafBaseType::IdentityRef:
+ os << "identity ref";
+ break;
+ case LeafBaseType::Enum:
+ os << "enum";
+ break;
+ case LeafBaseType::Binary:
+ os << "binary";
+ break;
+ case LeafBaseType::Bits:
+ os << "bits";
+ break;
+ case LeafBaseType::Dec64:
+ os << "decimal64";
+ break;
+ case LeafBaseType::InstanceIdentifier:
+ os << "instance id";
+ break;
+ case LeafBaseType::Int64:
+ os << "int64";
+ break;
+ case LeafBaseType::Uint64:
+ os << "uint64";
+ break;
+ case LeafBaseType::Union:
+ os << "union";
+ break;
+ case LeafBaseType::Leafref:
+ os << "leafref";
+ break;
+ case LeafBaseType::Unknown:
+ os << "unknown";
+ break;
+ default:
+ isc_throw(BadValue, "unsupported type " << int(type));
+ }
+ return (os);
+}
+
+ostream& operator<<(ostream& os, const YangRepr::YangReprItem& item) {
+ os << item.xpath_ << " = (" << item.type_ << ") '" << (item.value_ ? *item.value_ : "nullopt")
+ << "'";
+ return (os);
+}
+
+ostream& operator<<(ostream& os, const YangRepr::Tree& tree) {
+ for (auto const& kv : tree) {
+ YangRepr::YangReprItem const& item(kv.second);
+ os << item << endl;
+ }
+ return (os);
+}
+
+} // namespace test
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/testutils/translator_test.h b/src/lib/yang/testutils/translator_test.h
new file mode 100644
index 0000000..bb11d92
--- /dev/null
+++ b/src/lib/yang/testutils/translator_test.h
@@ -0,0 +1,162 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_TEST_H
+#define ISC_TRANSLATOR_TEST_H 1
+
+#include <yang/translator.h>
+
+#include <vector>
+#include <unordered_map>
+
+namespace isc {
+namespace yang {
+namespace test {
+
+/// @brief Yang/sysrepo datastore textual representation class.
+///
+/// This class allows to store a configuration tree in YANG format.
+/// It is used in tests to conduct operations on whole configurations.
+class YangRepr {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param model The model name.
+ YangRepr(const std::string& model) : model_(model) {
+ }
+
+ /// @brief A struct representing a single entry.
+ struct YangReprItem {
+ /// @brief Constructor with content.
+ ///
+ /// @param xpath The xpath.
+ /// @param value The textual value.
+ /// @param type The type of the value.
+ /// @param settable The settable flag.
+ YangReprItem(std::string xpath, std::optional<std::string> value,
+ libyang::LeafBaseType type, bool settable)
+ : xpath_(xpath), value_(value), type_(type), settable_(settable) {
+ }
+
+ /// @brief Retrieves configuration parameter from sysrepo.
+ ///
+ /// @param xpath The xpath of an element to be retrieved.
+ /// @param session Sysrepo session.
+ /// @return YangReprItem instance representing configuration parameter.
+ static YangReprItem get(const std::string& xpath,
+ sysrepo::Session session);
+
+ /// @brief The xpath.
+ std::string xpath_;
+
+ /// @brief The textual value.
+ std::optional<std::string> value_;
+
+ /// @brief The type of the value.
+ libyang::LeafBaseType type_;
+
+ /// @brief The settable flag.
+ bool settable_;
+
+ /// @brief The equal operator ignoring settable.
+ ///
+ /// @param other the other object to compare with.
+ /// @return true if equal.
+ bool operator==(const YangReprItem& other) const {
+ return ((xpath_ == other.xpath_) &&
+ (value_ == other.value_) &&
+ (type_ == other.type_));
+ }
+
+ /// @brief The unequal operator ignoring settable.
+ ///
+ /// @param other the other object to compare with.
+ /// @return false if equal.
+ bool operator!=(const YangReprItem& other) const {
+ return (!(*this == other));
+ }
+
+ private:
+ /// @brief Gets the underlying type behind a union node.
+ ///
+ /// @param value the value obtained from the union node with ->asTerm().value()
+ ///
+ /// @return the underlying type
+ static libyang::LeafBaseType getUnionType(libyang::Value const& value);
+ }; // YangReprItem
+
+ /// @brief Tree type.
+ ///
+ /// Indexed by xpath so that we can check against an entry received from
+ /// sysrepo which has empirically proven to not come in a certain order
+ /// starting with sysrepo 1.x. Relying on the order would have made a linear
+ /// data structure more fitting.
+ using Tree = std::unordered_map<std::string, YangReprItem>;
+
+ /// @brief Get tree from session.
+ ///
+ /// @param session Sysrepo session.
+ Tree get(sysrepo::Session session) const;
+
+ /// @brief Verifies a tree.
+ ///
+ /// @param expected The expected value.
+ /// @param session Sysrepo session.
+ /// @param errs Error stream.
+ /// @return true if verification succeeds, false with errors displayed.
+ /// on errs if it fails.
+ bool verify(const Tree& expected, sysrepo::Session session,
+ std::ostream& errs) const;
+
+ /// @brief Sets specified tree in a sysrepo.
+ ///
+ /// The actual access parameters are specified within session.
+ ///
+ /// @param tree The tree to install.
+ /// @param session Sysrepo session.
+ void set(const Tree& tree, sysrepo::Session session) const;
+
+ /// @brief Convenience function that indexes a collection of items by xpath.
+ ///
+ /// @param v the input vector
+ ///
+ /// @return the output map
+ static Tree buildTreeFromVector(std::vector<YangReprItem> const& v) {
+ Tree tree;
+ for (YangReprItem const& item : v) {
+ if (tree.contains(item.xpath_)) {
+ isc_throw(BadValue, "YangRepr::buildTreeFromVector(): duplicate " << item.xpath_);
+ }
+ tree.emplace(item.xpath_, item);
+ }
+ return tree;
+ }
+
+private:
+ /// @brief The model name.
+ std::string model_;
+}; // YangRepr
+
+/// @brief Alias for Items.
+using YRItem = YangRepr::YangReprItem;
+
+/// @brief Alias for Trees.
+using YRTree = YangRepr::Tree;
+
+/// @brief Overrides standard output operator for LeafBaseType.
+std::ostream& operator<<(std::ostream& os, libyang::LeafBaseType type);
+
+/// @brief Overrides standard output operator for @c YangReprItem object.
+std::ostream& operator<<(std::ostream& os, const YRItem& item);
+
+/// @brief Overrides standard output operator for @c Tree object.
+std::ostream& operator<<(std::ostream& os, const YRTree& tree);
+
+} // namespace test
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_TEST_H
diff --git a/src/lib/yang/translator.cc b/src/lib/yang/translator.cc
new file mode 100644
index 0000000..9c5ed9c
--- /dev/null
+++ b/src/lib/yang/translator.cc
@@ -0,0 +1,382 @@
+// Copyright (C) 2018-2022 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/encode/base64.h>
+#include <yang/adaptor.h>
+#include <yang/translator.h>
+
+#include <sysrepo-cpp/utils/exception.hpp>
+
+#include <cstring>
+#include <vector>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::util::encode;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+Translator::Translator(Session session, const string& model)
+ : session_(session), model_(model) {
+}
+
+void
+Translator::checkAndGetLeaf(ElementPtr& storage,
+ DataNode const& data_node,
+ string const& name) const {
+ ElementPtr const& x(getItem(data_node, name));
+ if (x) {
+ storage->set(name, x);
+ }
+}
+
+void
+Translator::checkAndGetDivergingLeaf(ElementPtr& storage,
+ DataNode const& data_node,
+ string const& name,
+ string const& yang_name) const {
+ ElementPtr const& x(getItem(data_node, yang_name));
+ if (x) {
+ storage->set(name, x);
+ }
+}
+
+void
+Translator::checkAndGetAndJsonifyLeaf(ElementPtr& storage,
+ DataNode const& data_node,
+ string const& name) const {
+ ElementPtr const& x(getItem(data_node, name));
+ if (x) {
+ storage->set(name, Element::fromJSON(x->stringValue()));
+ }
+}
+
+void
+Translator::checkAndSetLeaf(ConstElementPtr const& from,
+ string const& xpath,
+ string const& name,
+ LeafBaseType const type) {
+ ConstElementPtr const& x(from->get(name));
+ if (x) {
+ setItem(xpath + "/" + name, x, type);
+ }
+}
+
+void
+Translator::checkAndSetDivergingLeaf(ConstElementPtr const& from,
+ string const& xpath,
+ string const& name,
+ string const& yang_name,
+ LeafBaseType const type) {
+ ConstElementPtr const& x(from->get(name));
+ if (x) {
+ setItem(xpath + "/" + yang_name, x, type);
+ }
+}
+
+void
+Translator::checkAndSetLeafList(ConstElementPtr const& from,
+ string const& xpath,
+ string const& name,
+ LeafBaseType const type) {
+ ConstElementPtr const& leaf_list(from->get(name));
+ if (leaf_list && !leaf_list->empty()) {
+ for (ElementPtr const& leaf : leaf_list->listValue()) {
+ setItem(xpath + "/" + name, leaf, type);
+ }
+ }
+}
+
+void
+Translator::checkAndSetUserContext(ConstElementPtr const& from,
+ string const& xpath) {
+ ConstElementPtr const& user_context(Adaptor::getContext(from));
+ if (user_context) {
+ setItem(xpath + "/user-context", Element::create(user_context->str()),
+ LeafBaseType::String);
+ }
+}
+
+void
+Translator::checkAndStringifyAndSetLeaf(ConstElementPtr const& from,
+ string const& xpath,
+ string const& name) {
+ ConstElementPtr const& x(from->get(name));
+ if (x) {
+ ElementPtr const& json(Element::create(x->str()));
+ setItem(xpath + "/" + name, json, LeafBaseType::String);
+ }
+}
+
+void
+Translator::deleteItem(string const& xpath) {
+ try {
+ if (session_.getData(xpath)) {
+ session_.deleteItem(xpath);
+ }
+ } catch (sysrepo::Error const& ex) {
+ isc_throw(NetconfError, "deleting item at '" << xpath << "': " << ex.what());
+ }
+ session_.applyChanges();
+}
+
+DataNode
+Translator::findXPath(string const& xpath) const {
+ optional<DataNode> const& data_node(getData(xpath));
+ if (!data_node) {
+ isc_throw(NetconfError, "no data at xpath " << xpath);
+ }
+ Set<DataNode> at_path(data_node->findXPath(xpath));
+ if (at_path.empty()) {
+ isc_throw(NetconfError, "no data at xpath " << xpath);
+ }
+ return at_path.front();
+}
+
+optional<DataNode>
+Translator::getData(string const& xpath) const {
+ optional<DataNode> data_node;
+ try {
+ data_node = session_.getData(xpath);
+ } catch (sysrepo::Error const& ex) {
+ isc_throw(NetconfError, "getting item at '" << xpath << "': " << ex.what());
+ }
+
+ return data_node;
+}
+
+ElementPtr
+Translator::getItem(DataNode const& data_node,
+ string const& xpath) const {
+ try {
+ Set<DataNode> const& nodes(data_node.findXPath(xpath));
+ if (nodes.empty()) {
+ return ElementPtr();
+ }
+ DataNode const& front(nodes.front());
+ NodeType const node_type(front.schema().nodeType());
+
+ // Leaf
+ if (node_type == NodeType::Leaf) {
+ return translateFromYang(front);
+ } else if (node_type == NodeType::Leaflist) {
+ ElementPtr result(Element::createList());
+ for (DataNode const& i : nodes) {
+ result->add(translateFromYang(i));
+ }
+ return result;
+ } else {
+ isc_throw(NotImplemented, "getting node of type "
+ << int(node_type) << " not supported, xpath is '" << xpath
+ << "'");
+ }
+
+ } catch (sysrepo::Error const& ex) {
+ isc_throw(NetconfError, "getting item at '" << xpath << "': " << ex.what());
+ }
+}
+
+ElementPtr
+Translator::getItemFromAbsoluteXpath(string const& xpath) const {
+ optional<DataNode> const& data_node(getData(xpath));
+ if (!data_node) {
+ return ElementPtr();
+ }
+ return getItem(*data_node, xpath);
+}
+
+void
+Translator::getMandatoryLeaf(ElementPtr& storage,
+ DataNode const& data_node,
+ string const& name) const {
+ ElementPtr const& x(getItem(data_node, name));
+ if (!x) {
+ isc_throw(MissingNode, name);
+ }
+ storage->set(name, x);
+}
+
+void
+Translator::getMandatoryDivergingLeaf(ElementPtr& storage,
+ DataNode const& data_node,
+ string const& name,
+ string const& yang_name) const {
+ ElementPtr const& x(getItem(data_node, yang_name));
+ if (!x) {
+ isc_throw(MissingNode, yang_name);
+ }
+ storage->set(name, x);
+}
+
+bool
+Translator::schemaNodeExists(string const& xpath) const {
+ Context const& context(session_.getContext());
+ try {
+ context.findPath(xpath);
+ } catch (libyang::Error const& ex) {
+ return false;
+ }
+ return true;
+}
+
+void
+Translator::setItem(const string& xpath, ConstElementPtr elem, LeafBaseType type) {
+ optional<string> const value(translateToYang(elem, type));
+ try {
+ session_.setItem(xpath, value);
+ } catch (sysrepo::Error const& ex) {
+ isc_throw(NetconfError, "setting item '" << (elem ? elem->str() : "nullopt")
+ << "' at '" << xpath << "': " << ex.what());
+ }
+ session_.applyChanges();
+}
+
+void
+Translator::setMandatoryLeaf(ConstElementPtr const& from,
+ string const& xpath,
+ string const& name,
+ LeafBaseType const type) {
+ ConstElementPtr const& x(from->get(name));
+ if (!x) {
+ isc_throw(MissingNode, "xpath: " << xpath << ", name: " << name);
+ }
+ setItem(xpath + "/" + name, x, type);
+}
+
+void
+Translator::setMandatoryDivergingLeaf(ConstElementPtr const& from,
+ string const& xpath,
+ string const& name,
+ string const& yang_name,
+ LeafBaseType const type) {
+ ConstElementPtr const& x(from->get(name));
+ if (!x) {
+ isc_throw(MissingNode, "xpath: " << xpath << ", name: " << name);
+ }
+ setItem(xpath + "/" + yang_name, x, type);
+}
+
+ElementPtr
+Translator::translateFromYang(optional<DataNode> data_node) {
+ NodeType const node_type(data_node->schema().nodeType());
+ if (node_type == NodeType::Leaf || node_type == NodeType::Leaflist) {
+ DataNodeTerm const& leaf(data_node->asTerm());
+ LeafBaseType type;
+ if (node_type == NodeType::Leaf) {
+ type = leaf.schema().asLeaf().valueType().base();
+ } else {
+ type = leaf.schema().asLeafList().valueType().base();
+ }
+
+ static Deserializer deserializer(initializeDeserializer());
+ return deserializer.at(type)(string(leaf.valueStr()));
+ }
+ return ElementPtr();
+}
+
+optional<string>
+Translator::translateToYang(ConstElementPtr const& element,
+ libyang::LeafBaseType const type) {
+ string string_representation;
+ if (!element) {
+ // A null ElementPtr is how we signal that this item requires no value.
+ // Useful when setting YANG lists which is the only way to set their
+ // keys in sysrepo since setting the key itself results in an error.
+ return nullopt;
+ } else if (element->getType() == Element::map) {
+ isc_throw(NotImplemented, "Translator::value(): map element");
+ } else if (element->getType() == Element::list) {
+ isc_throw(NotImplemented, "Translator::value(): list element");
+ } else if (element->getType() == Element::string) {
+ // If it's a string, get the variant without quotes.
+ string_representation = element->stringValue();
+ } else {
+ // If it's not a string, also get the variant without quotes, but it's a different method.
+ string_representation = element->str();
+ }
+
+ static Serializer serializer(initializeSerializer());
+ return serializer.at(type)(string_representation);
+}
+
+string
+Translator::decode64(string const& input) {
+ vector<uint8_t> binary;
+ decodeBase64(input, binary);
+ string result;
+ result.resize(binary.size());
+ memcpy(&result[0], &binary[0], result.size());
+ return (result);
+}
+
+string
+Translator::encode64(string const& input) {
+ vector<uint8_t> binary;
+ binary.resize(input.size());
+ memcpy(&binary[0], input.c_str(), binary.size());
+ return (encodeBase64(binary));
+}
+
+Translator::Deserializer
+Translator::initializeDeserializer() {
+ Deserializer result;
+
+ result.emplace(LeafBaseType::Binary, [](string const& value) -> ElementPtr const {
+ return Element::create(decode64(value));
+ });
+
+ for (LeafBaseType const& i :
+ {LeafBaseType::Bool, LeafBaseType::Dec64, LeafBaseType::Int8, LeafBaseType::Int16,
+ LeafBaseType::Int32, LeafBaseType::Int64, LeafBaseType::Uint8, LeafBaseType::Uint16,
+ LeafBaseType::Uint32, LeafBaseType::Uint64}) {
+ result.emplace(i, [](string const& value) -> ElementPtr const {
+ return Element::fromJSON(value);
+ });
+ }
+
+ // The rest of YANG values can be expressed as strings.
+ for (LeafBaseType const& i :
+ {LeafBaseType::Bits, LeafBaseType::Empty, LeafBaseType::Enum, LeafBaseType::IdentityRef,
+ LeafBaseType::InstanceIdentifier, LeafBaseType::Leafref, LeafBaseType::String,
+ LeafBaseType::Union, LeafBaseType::Unknown}) {
+ result.emplace(i, [](string const& value) -> ElementPtr const {
+ return Element::create(value);
+ });
+ }
+
+ return result;
+}
+
+Translator::Serializer
+Translator::initializeSerializer() {
+ Serializer result;
+
+ result.emplace(LeafBaseType::Binary, [](string const& value) -> string const {
+ return encode64(value);
+ });
+
+ // The rest of YANG values can be expressed directly.
+ for (LeafBaseType const& i :
+ {LeafBaseType::Bits, LeafBaseType::Bool, LeafBaseType::Dec64, LeafBaseType::Empty,
+ LeafBaseType::Enum, LeafBaseType::IdentityRef, LeafBaseType::InstanceIdentifier,
+ LeafBaseType::Int8, LeafBaseType::Int16, LeafBaseType::Int32, LeafBaseType::Int64,
+ LeafBaseType::Leafref, LeafBaseType::String, LeafBaseType::Uint8, LeafBaseType::Uint16,
+ LeafBaseType::Uint32, LeafBaseType::Uint64, LeafBaseType::Union, LeafBaseType::Unknown}) {
+ result.emplace(i, [](string const& value) -> string const {
+ return value;
+ });
+ }
+
+ return result;
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator.h b/src/lib/yang/translator.h
new file mode 100644
index 0000000..5bbaa7d
--- /dev/null
+++ b/src/lib/yang/translator.h
@@ -0,0 +1,432 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_H
+#define ISC_TRANSLATOR_H 1
+
+#include <cc/data.h>
+#include <yang/netconf_error.h>
+
+#include <sysrepo-cpp/Connection.hpp>
+#include <sysrepo-cpp/Session.hpp>
+
+#include <unordered_map>
+
+namespace isc {
+namespace yang {
+
+/// @brief Between YANG and JSON translator class for basic values.
+class Translator {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name (used and shared by derived classes).
+ Translator(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~Translator() = default;
+
+ /// @brief Calls {translate} for the element found at {xpath} relative to
+ /// {data_node} and sets the result in {storage} with the {xpath} key.
+ ///
+ /// @tparam T typename of the function to be called
+ ///
+ /// @param storage ElementMap where result will be stored
+ /// @param data_node parent data node of container type
+ /// @param xpath relative xpath to search by
+ /// @param translate function to be called to translate a data node to an element pointer
+ template <typename T>
+ void checkAndGet(isc::data::ElementPtr const& storage,
+ libyang::DataNode const& data_node,
+ std::string const& xpath,
+ T translate) const {
+ libyang::Set<libyang::DataNode> const& nodes(data_node.findXPath(xpath));
+ if (!nodes.empty()) {
+ isc::data::ElementPtr const& element(translate(nodes.front()));
+ if (element && !element->empty()) {
+ storage->set(xpath, element);
+ }
+ }
+ }
+
+ /// @brief Calls {translate} for the element found at {xpath} relative to
+ /// {data_node} and sets the result in {storage} with the {key} key.
+ ///
+ /// It's the counterpart for @ref checkAndGet, but when the YANG xpath
+ /// and the JSON key diverge.
+ ///
+ /// @tparam T typename of the function to be called
+ ///
+ /// @param storage ElementMap where result will be stored
+ /// @param data_node parent data node of container type
+ /// @param key where to set the result in the {storage} map
+ /// @param xpath relative xpath to search by
+ /// @param translate function to be called to translate a data node to an element pointer
+ template <typename T>
+ void checkAndGetDiverging(isc::data::ElementPtr const& storage,
+ libyang::DataNode const& data_node,
+ std::string const& key,
+ std::string const& xpath,
+ T translate) const {
+ libyang::Set<libyang::DataNode> const& nodes(data_node.findXPath(xpath));
+ if (!nodes.empty()) {
+ isc::data::ElementPtr const& element(translate(nodes.front()));
+ if (element && !element->empty()) {
+ storage->set(key, element);
+ }
+ }
+ }
+
+ /// @brief Retrieves a child YANG data node identified by name from the
+ /// given parent YANG container node and stores it in the specified storage.
+ ///
+ /// @param storage ElementMap where result will be stored
+ /// @param data_node parent data node of container type
+ /// @param name the name of the parameter to be set in storage
+ void checkAndGetLeaf(isc::data::ElementPtr& storage,
+ libyang::DataNode const& data_node,
+ std::string const& name) const;
+
+ /// @brief Retrieves a child YANG data node identified by name from the
+ /// given parent YANG container node and stores it in the specified storage.
+ ///
+ /// It's the counterpart for @ref checkAndGetLeaf, but when the YANG xpath
+ /// and the JSON key diverge.
+ ///
+ /// @param storage ElementMap where result will be stored
+ /// @param data_node parent data node of container type
+ /// @param name the name of the parameter to be set in storage
+ /// @param yang_name the name by which to find the parameter in the YANG data node
+ void checkAndGetDivergingLeaf(isc::data::ElementPtr& storage,
+ libyang::DataNode const& data_node,
+ std::string const& name,
+ std::string const& yang_name) const;
+
+ /// @brief Retrieves a child YANG data node identified by name from the given parent YANG
+ /// container node, converts it from string to JSON and stores it in the specified storage.
+ ///
+ /// @param storage ElementMap where result will be stored
+ /// @param data_node parent data node of container type
+ /// @param name the name of the parameter to be set in storage
+ void checkAndGetAndJsonifyLeaf(isc::data::ElementPtr& storage,
+ libyang::DataNode const& data_node,
+ const std::string& name) const;
+
+ /// @brief Get an element from given ElementPtr node and set it in sysrepo
+ /// at given xpath.
+ ///
+ /// @param from the parent configuration node from which to take the value
+ /// @param xpath the xpath to the YANG node without the last node
+ /// @param name the name of the YANG node which should also match the key
+ /// under which data is set in the MapElement {from}
+ /// @param type the YANG node type
+ void checkAndSetLeaf(isc::data::ConstElementPtr const& from,
+ std::string const& xpath,
+ std::string const& name,
+ libyang::LeafBaseType const type);
+
+ /// @brief Get an element from given ElementPtr node and set it in sysrepo
+ /// at given xpath.
+ ///
+ /// It's the counterpart for @ref checkAndSetLeaf, but when the YANG xpath
+ /// and the JSON key diverge.
+ ///
+ /// @param from the parent configuration node from which to take the value
+ /// @param xpath the xpath to the YANG node without the last node
+ /// @param name the key under which data is set in the MapElement {from}
+ /// @param yang_name the name of the YANG node
+ /// @param type the YANG node type
+ void checkAndSetDivergingLeaf(isc::data::ConstElementPtr const& from,
+ std::string const& xpath,
+ std::string const& name,
+ std::string const& yang_name,
+ libyang::LeafBaseType const type);
+
+ /// @brief Get an element from given ElementPtr node and set it in sysrepo
+ /// at given xpath as a leaf-list.
+ ///
+ /// @param from the parent configuration node from which to take the values
+ /// @param xpath the xpath to the YANG node without the last node
+ /// @param name the name of the YANG node which should also match the key
+ /// under which data is set in the MapElement {from}
+ /// @param type the YANG node type of the underlying leaf-list nodes
+ void checkAndSetLeafList(isc::data::ConstElementPtr const& from,
+ std::string const& xpath,
+ std::string const& name,
+ libyang::LeafBaseType const type);
+
+ /// @brief Get an element from given ElementPtr node and set it in sysrepo
+ /// at given xpath.
+ ///
+ /// @param from the parent configuration node from which to take the value
+ /// @param xpath the xpath to the YANG node without the last node
+ void checkAndSetUserContext(isc::data::ConstElementPtr const& from,
+ std::string const& xpath);
+
+ /// @brief Get an element from given ElementPtr node and set it in sysrepo
+ /// at given xpath.
+ ///
+ /// @param from the parent configuration node from which to take the value
+ /// @param xpath the xpath to the YANG node without the last node
+ /// @param name the name of the YANG node which should also match the key
+ /// under which data is set in the MapElement {from}
+ void checkAndStringifyAndSetLeaf(isc::data::ConstElementPtr const& from,
+ std::string const& xpath,
+ std::string const& name);
+
+ /// @brief Delete basic value from YANG.
+ ///
+ /// @param xpath The xpath of the basic value.
+ void deleteItem(const std::string& xpath);
+
+ /// @brief Retrieves a YANG data node by xpath.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ ///
+ /// @param xpath the xpath of the requested node
+ ///
+ /// @return the requested YANG data node
+ ///
+ /// @throw NetconfError if no YANG data node was found
+ libyang::DataNode findXPath(std::string const& xpath) const;
+
+ /// @brief Run a function for a node and all its children.
+ ///
+ /// @tparam functor_t typename of the function to be called
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup
+ /// in the sysrepo datastore by calling Session::getData(). It should be
+ /// used sparingly in production code. It is primarily meant for unit tests.
+ ///
+ /// @param xpath the xpath of the root node belonging to the the tree being traversed
+ /// @param f the function to be called on the node itself and each descendant
+ template <typename functor_t>
+ void forAll(std::string const& xpath, functor_t f) const {
+ std::optional<libyang::DataNode> const& data_node(session_.getData(xpath));
+ if (!data_node) {
+ return;
+ }
+
+ for (libyang::DataNode const& sibling : data_node->siblings()) {
+ for (libyang::DataNode const& n : sibling.childrenDfs()) {
+ f(n);
+ }
+ }
+ }
+
+ /// @brief Get a YANG data node found at the given absolute xpath.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ ///
+ /// @param xpath the xpath at which the YANG data node is to be found
+ ///
+ /// @return a DataNode if found, or nullopt otherwise
+ ///
+ /// @throw NetconfError when the used sysrepo API throws an error
+ std::optional<libyang::DataNode> getData(std::string const& xpath) const;
+
+ /// @brief Translate a basic value from YANG to JSON for
+ /// a given xpath that is relative to the given source node.
+ ///
+ /// @param data_node the source data node
+ /// @param xpath the relative xpath
+ ///
+ /// @return The Element representing the item at xpath or null
+ /// when not found.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getItem(libyang::DataNode const& data_node,
+ std::string const& xpath) const;
+
+ /// @brief Translate a basic value from YANG to JSON for a given absolute xpath.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ ///
+ /// @param xpath The xpath of the basic value.
+ /// @return The Element representing the item at xpath or null
+ /// when not found.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getItemFromAbsoluteXpath(std::string const& xpath) const;
+
+ /// @brief Retrieve a list as ElementPtr from sysrepo from a certain xpath.
+ ///
+ /// @tparam T typename of the translator that holds the function that will
+ /// be called on the xpath of each child from the list
+ ///
+ /// @param data_node the YANG data node to retrieve data from
+ /// @param xpath the xpath to the element to be retrieved from, relative to {data_node}
+ /// @param t address of a translator instance that holds the function that
+ /// will be called on the xpath of each child from the list
+ /// @param f the function that will be called on the xpath of each child
+ /// from the list
+ template <typename T>
+ isc::data::ElementPtr getList(libyang::DataNode const& data_node,
+ std::string const& xpath,
+ T& t,
+ isc::data::ElementPtr (T::*f)(libyang::DataNode const&)) const {
+ try {
+ libyang::Set<libyang::DataNode> const& nodes(data_node.findXPath(xpath));
+ if (nodes.empty()) {
+ return (isc::data::ElementPtr());
+ }
+ isc::data::ElementPtr const result(isc::data::Element::createList());
+ for (libyang::DataNode const& i : nodes) {
+ result->add((t.*f)(i));
+ }
+ return result;
+ } catch (libyang::Error const& ex) {
+ isc_throw(NetconfError, "getting item: " << ex.what());
+ }
+ }
+
+ /// @brief Retrieves a child YANG data node identified by name from the
+ /// given parent YANG container node and stores it in the specified storage.
+ ///
+ /// Unlike @ref checkAndGetLeaf, the leaf is expected to be present.
+ ///
+ /// @param storage ElementMap where result will be stored
+ /// @param data_node parent data node of container type
+ /// @param name the name of the YANG node which should also match the map
+ /// key in the JSON configuration
+ ///
+ /// @throw MissingNode if leaf is not found
+ void getMandatoryLeaf(isc::data::ElementPtr& storage,
+ libyang::DataNode const& data_node,
+ std::string const& name) const;
+
+ /// @brief Retrieves a child YANG data node identified by one name from the
+ /// given parent YANG container node and stores it in the specified storage
+ /// under a different name.
+ ///
+ /// Unlike @ref checkAndGetLeaf, the leaf is expected to be present.
+ ///
+ /// @param storage ElementMap where result will be stored
+ /// @param data_node parent data node of container type
+ /// @param name the map key in the JSON configuration
+ /// @param yang_name the name by which to find the parameter in the YANG data node
+ ///
+
+ /// @throw MissingNode if leaf is not found
+ void getMandatoryDivergingLeaf(isc::data::ElementPtr& storage,
+ libyang::DataNode const& data_node,
+ std::string const& name,
+ std::string const& yang_name) const;
+
+ /// @brief Checks whether a YANG node exists in the schema.
+ ///
+ /// @param xpath the xpath to be checked
+ ///
+ /// @return true if the YANG node exists in the schema, false otherwise
+ bool schemaNodeExists(std::string const& xpath) const;
+
+ /// @brief Translate and set basic value from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the basic value.
+ /// @param elem The JSON element.
+ /// @param type The sysrepo type.
+ void setItem(const std::string& xpath,
+ isc::data::ConstElementPtr elem,
+ libyang::LeafBaseType type);
+
+ /// @brief Get an element from given ElementPtr node and set it in sysrepo
+ /// at given xpath.
+ ///
+ /// @param from the parent configuration node from which to take the value
+ /// @param xpath the xpath to the YANG node without the last node
+ /// @param name the name of the YANG node which should also match the map
+ /// key in the JSON configuration
+ /// @param type the YANG node type
+ void setMandatoryLeaf(isc::data::ConstElementPtr const& from,
+ std::string const& xpath,
+ std::string const& name,
+ libyang::LeafBaseType const type);
+
+ /// @brief Get an element from given ElementPtr node and set it in sysrepo
+ /// at given xpath.
+ ///
+ /// @param from the parent configuration node from which to take the value
+ /// @param xpath the xpath to the YANG node without the last node
+ /// @param name the map key in the JSON configuration
+ /// @param yang_name the name by which to find the parameter in the YANG data node
+ /// @param type the YANG node type
+ void setMandatoryDivergingLeaf(isc::data::ConstElementPtr const& from,
+ std::string const& xpath,
+ std::string const& name,
+ std::string const& yang_name,
+ libyang::LeafBaseType const type);
+
+ /// @brief Translate basic value from the given YANG data node to JSON element.
+ ///
+ /// @param data_node the YANG data node
+ ///
+ /// @return the translated JSON element
+ static isc::data::ElementPtr translateFromYang(std::optional<libyang::DataNode> data_node);
+
+ /// @brief Translate basic value from JSON to YANG.
+ ///
+ /// @param elem The JSON element.
+ /// @param type The sysrepo type.
+ ///
+ /// @return string representation of {elem}, or nullopt if {elem} is null
+ static std::optional<std::string> translateToYang(isc::data::ConstElementPtr const& elem,
+ libyang::LeafBaseType const type);
+
+protected:
+ /// @brief Decode a YANG element of binary type to a string that
+ /// can be stored in an Element::string JSON.
+ ///
+ /// @param input string to be decoded
+ ///
+ /// @return the decoded string
+ static std::string decode64(std::string const& input);
+
+ /// @brief Encode a string such that it can be stored in a YANG element of binary type.
+ ///
+ /// @param input string to be encoded
+ ///
+ /// @return the encoded string
+ static std::string encode64(std::string const& input);
+
+ /// @brief Maps YANG types to functions that transform a YANG type into an ElementPtr.
+ using Deserializer =
+ std::unordered_map<libyang::LeafBaseType,
+ std::function<isc::data::ElementPtr const(std::string const&)>>;
+
+ /// @brief Initializes the deserializer which is used to translate a YANG node to an ElementPtr.
+ ///
+ /// @return a copy of the deserializer
+ static Deserializer initializeDeserializer();
+
+ /// @brief Maps YANG types to functions that transform the string representation of an
+ /// Element into a string that can be passed into Session::setItem().
+ using Serializer =
+ std::unordered_map<libyang::LeafBaseType,
+ std::function<std::string const(std::string const&)>>;
+
+ /// @brief Initializes the serializer which is used to translate the string value of an Element
+ /// to a string that can be passed into Session::setItem().
+ ///
+ /// @return a copy of the serializer
+ static Serializer initializeSerializer();
+
+ /// @brief The sysrepo session.
+ sysrepo::Session session_;
+
+ /// @brief The model.
+ std::string model_;
+}; // Translator
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_H
diff --git a/src/lib/yang/translator_class.cc b/src/lib/yang/translator_class.cc
new file mode 100644
index 0000000..5553ced
--- /dev/null
+++ b/src/lib/yang/translator_class.cc
@@ -0,0 +1,219 @@
+// Copyright (C) 2018-2023 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 <yang/translator_class.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorClass::TranslatorClass(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorOptionDef(session, model),
+ TranslatorOptionDefList(session, model) {
+}
+
+ElementPtr
+TranslatorClass::getClass(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getClassKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting client class:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getClass not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorClass::getClassFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getClass(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorClass::getClassKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryLeaf(result, data_node, "name");
+
+ checkAndGetLeaf(result, data_node, "max-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "min-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "only-if-required");
+ checkAndGetLeaf(result, data_node, "template-test");
+ checkAndGetLeaf(result, data_node, "test");
+ checkAndGetLeaf(result, data_node, "valid-lifetime");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ ConstElementPtr options = getOptionDataList(data_node);
+ if (options) {
+ result->set("option-data", options);
+ }
+
+ if (model_ == KEA_DHCP4_SERVER) {
+ checkAndGetLeaf(result, data_node, "boot-file-name");
+ checkAndGetLeaf(result, data_node, "next-server");
+ checkAndGetLeaf(result, data_node, "offer-lifetime");
+ checkAndGetLeaf(result, data_node, "server-hostname");
+
+ ConstElementPtr defs = getOptionDefList(data_node);
+ if (defs) {
+ result->set("option-def", defs);
+ }
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ checkAndGetLeaf(result, data_node, "max-preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "min-preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "preferred-lifetime");
+ }
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorClass::setClass(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setClassKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setClass not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting client class '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorClass::setClassKea(string const& xpath, ConstElementPtr elem) {
+ // Keys are set by setting the list itself.
+ setItem(xpath, ElementPtr(), LeafBaseType::Unknown);
+
+ checkAndSetLeaf(elem, xpath, "max-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "only-if-required", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "template-test", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "test", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "valid-lifetime", LeafBaseType::Uint32);
+
+ ConstElementPtr options = elem->get("option-data");
+ if (options) {
+ setOptionDataList(xpath, options);
+ }
+
+ if (model_ == KEA_DHCP4_SERVER) {
+ checkAndSetLeaf(elem, xpath, "boot-file-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "next-server", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "offer-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "server-hostname", LeafBaseType::String);
+
+ ConstElementPtr defs = elem->get("option-def");
+ if (defs) {
+ setOptionDefList(xpath, defs);
+ }
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ checkAndSetLeaf(elem, xpath, "max-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "preferred-lifetime", LeafBaseType::Uint32);
+ }
+
+ checkAndSetUserContext(elem, xpath);
+}
+
+TranslatorClasses::TranslatorClasses(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorOptionDef(session, model),
+ TranslatorOptionDefList(session, model),
+ TranslatorClass(session, model) {
+}
+
+ElementPtr
+TranslatorClasses::getClasses(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getClassesKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting client classes:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getClasses not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorClasses::getClassesFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getClasses(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorClasses::getClassesKea(DataNode const& data_node) {
+ return getList<TranslatorClass>(data_node, "client-class", *this,
+ &TranslatorClass::getClass);
+}
+
+void
+TranslatorClasses::setClasses(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setClassesKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setClasses not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting client classes '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorClasses::setClassesKea(string const& xpath, ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr cclass = elem->getNonConst(i);
+ if (!cclass->contains("name")) {
+ isc_throw(BadValue, "client class without name: " << elem->str());
+ }
+ string name = cclass->get("name")->stringValue();
+ ostringstream key;
+ key << xpath << "/client-class[name='" << name << "']";
+ setClass(key.str(), cclass);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_class.h b/src/lib/yang/translator_class.h
new file mode 100644
index 0000000..5b2b7f6
--- /dev/null
+++ b/src/lib/yang/translator_class.h
@@ -0,0 +1,203 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_CLASS_H
+#define ISC_TRANSLATOR_CLASS_H 1
+
+#include <yang/translator_option_data.h>
+#include <yang/translator_option_def.h>
+
+namespace isc {
+namespace yang {
+
+/// Client class translation between YANG and JSON
+///
+/// JSON syntax for all Kea servers with client class is:
+/// @code
+/// {
+/// "name": <name>,
+/// "test": <test expression>,
+/// "only-if-required": <only if required flag>,
+/// "option-data": <option data list>,
+/// (DHCPv4 only)
+/// "option-def": <option definition list>,
+/// "next-server": <next server address>,
+/// "server-hostname": <server hostname>,
+/// "boot-file-name": <boot filename>,
+/// "user-context": { <json map> },
+/// "comment": <comment>
+/// }
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46] is with the name as the list key:
+/// @code
+/// +--rw client-class* [name]
+/// +--rw name string
+/// +--rw test? string
+/// +--rw only-if-required? boolean
+/// +--rw option-def* [code space]
+/// +--rw option-data* [code space]
+/// +--rw next-server? inet:ipv4-address
+/// +--rw server-hostname? string
+/// +--rw boot-file-name? string
+/// +--rw valid-lifetime? uint32
+/// +--rw min-valid-lifetime? uint32
+/// +--rw max-valid-lifetime? uint32
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "name": "foo",
+/// "test": "''==''",
+/// "only-if-required": false
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp6-server:config (container)
+/// /kea-dhcp6-server:config/client-class[name='foo'] (list instance)
+/// /kea-dhcp6-server:config/client-class[name='foo']/name = foo
+/// /kea-dhcp6-server:config/client-class[name='foo']/test = ''==''
+/// /kea-dhcp6-server:config/client-class[name='foo']/only-if-required = false
+/// @endcode
+
+/// @brief A translator class for converting a client class between
+/// YANG and JSON.
+///
+/// Currently supports only kea-dhcp[46]-server. Ietf-dhcpv6-server does
+/// not define client class contents.
+class TranslatorClass : virtual public TranslatorOptionDataList,
+ virtual public TranslatorOptionDefList {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorClass(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorClass() = default;
+
+ /// @brief Translate a client class from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the class
+ ///
+ /// @return the JSON representation of the class
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getClass(libyang::DataNode const& data_node);
+
+ /// @brief Translate a client class from YANG to JSON.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ /// Use getClass(libyang::DataNode) as a scalable alternative.
+ ///
+ /// @param xpath the xpath of the class
+ ///
+ /// @return JSON representation of the class
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getClassFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set client class from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the client class.
+ /// @param elem The JSON element.
+ void setClass(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getClass JSON for kea-dhcp[46].
+ ///
+ /// @param data_node the YANG node representing the client class
+ ///
+ /// @return JSON representation of the class
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getClassKea(libyang::DataNode const& data_node);
+
+ /// @brief setClass for kea-dhcp[46].
+ ///
+ /// @param xpath The xpath of the client class.
+ /// @param elem The JSON element.
+ void setClassKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorClass
+
+/// @brief A translator class for converting a client class list between
+/// YANG and JSON.
+///
+/// Currently supports only kea-dhcp[46]-server. Ietf-dhcpv6-server does
+/// not define client class contents.
+class TranslatorClasses : virtual public TranslatorClass {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorClasses(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorClasses() = default;
+
+ /// @brief Translate client classes from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the class
+ ///
+ /// @return the JSON representation of the list of classes
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getClasses(libyang::DataNode const& data_node);
+
+ /// @brief Translate client classes from YANG to JSON.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ /// Use getClasses(libyang::DataNode) as a scalable alternative.
+ ///
+ /// @param xpath The xpath of classes.
+ ///
+ /// @return JSON representation of classes.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getClassesFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set client classes from JSON to YANG.
+ ///
+ /// @param xpath The xpath of classes.
+ /// @param elem The JSON element.
+ void setClasses(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getClasses JSON for kea-dhcp[46].
+ ///
+ /// @param data_node the YANG node representing the classes
+ ///
+ /// @return JSON representation of classes.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getClassesKea(libyang::DataNode const& data_node);
+
+ /// @brief setClasses for kea-dhcp[46].
+ ///
+ /// @param xpath The xpath of classes.
+ /// @param elem The JSON element.
+ ///
+ /// @throw BadValue on client class without name.
+ void setClassesKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorClasses
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_CLASS_H
diff --git a/src/lib/yang/translator_config.cc b/src/lib/yang/translator_config.cc
new file mode 100644
index 0000000..4c2c6b8
--- /dev/null
+++ b/src/lib/yang/translator_config.cc
@@ -0,0 +1,759 @@
+// Copyright (C) 2018-2023 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 <yang/adaptor_config.h>
+#include <yang/translator_config.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorConfig::TranslatorConfig(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorControlSocket(session, model),
+ TranslatorDatabase(session, model),
+ TranslatorDatabases(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorOptionDef(session, model),
+ TranslatorOptionDefList(session, model),
+ TranslatorClass(session, model),
+ TranslatorClasses(session, model),
+ TranslatorPool(session, model),
+ TranslatorPools(session, model),
+ TranslatorPdPool(session, model),
+ TranslatorPdPools(session, model),
+ TranslatorHost(session, model),
+ TranslatorHosts(session, model),
+ TranslatorSubnet(session, model),
+ TranslatorSubnets(session, model),
+ TranslatorSharedNetwork(session, model),
+ TranslatorSharedNetworks(session, model),
+ TranslatorLogger(session, model),
+ TranslatorLoggers(session, model) {
+}
+
+ElementPtr
+TranslatorConfig::getConfig() {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ return (getConfigIetf6());
+ } else if (model_ == KEA_DHCP4_SERVER) {
+ return (getConfigKea4());
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ return (getConfigKea6());
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError, "getting config: " << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getConfig not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorConfig::getConfigIetf6() {
+ ElementPtr result = Element::createMap();
+ ElementPtr dhcp6 = Element::createMap();
+ result->set("Dhcp6", dhcp6);
+ string const xpath("/ietf-dhcpv6-server:server");
+ optional<DataNode> config;
+ try {
+ config = findXPath(xpath);
+ } catch (NetconfError const&) {
+ return result;
+ }
+
+ checkAndGetDiverging(dhcp6, *config, "subnet6", "server-config/network-ranges",
+ [&](DataNode const& data_node) -> ElementPtr const {
+ return getSubnets(data_node);
+ });
+
+ // Skip everything else.
+ return (result);
+}
+
+ElementPtr
+TranslatorConfig::getConfigKea4() {
+ ElementPtr result = Element::createMap();
+ ElementPtr dhcp = getServerKeaDhcp4();
+ result->set("Dhcp4", dhcp);
+ return (result);
+}
+
+ElementPtr
+TranslatorConfig::getConfigKea6() {
+ ElementPtr result = Element::createMap();
+ ElementPtr dhcp = getServerKeaDhcp6();
+ result->set("Dhcp6", dhcp);
+ return (result);
+}
+
+ElementPtr TranslatorConfig::getHook(DataNode const& data_node) {
+ ElementPtr hook_library(Element::createMap());
+ checkAndGetLeaf(hook_library, data_node, "library");
+ checkAndGetAndJsonifyLeaf(hook_library, data_node, "parameters");
+ return hook_library;
+}
+
+ElementPtr
+TranslatorConfig::getHooksKea(DataNode const& data_node) {
+ return getList(data_node, "hook-library", *this, &TranslatorConfig::getHook);
+}
+
+isc::data::ElementPtr
+TranslatorConfig::getExpiredKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ checkAndGetLeaf(result, data_node, "flush-reclaimed-timer-wait-time");
+ checkAndGetLeaf(result, data_node, "hold-reclaimed-time");
+ checkAndGetLeaf(result, data_node, "max-reclaim-leases");
+ checkAndGetLeaf(result, data_node, "max-reclaim-time");
+ checkAndGetLeaf(result, data_node, "reclaim-timer-wait-time");
+ checkAndGetLeaf(result, data_node, "unwarned-reclaim-cycles");
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+isc::data::ElementPtr
+TranslatorConfig::getDdnsKea(DataNode const& data_node) {
+ ElementPtr result(Element::createMap());
+
+ checkAndGetLeaf(result, data_node, "enable-updates");
+ checkAndGetLeaf(result, data_node, "generated-prefix");
+ checkAndGetLeaf(result, data_node, "hostname-char-replacement");
+ checkAndGetLeaf(result, data_node, "hostname-char-set");
+ checkAndGetLeaf(result, data_node, "max-queue-size");
+ checkAndGetLeaf(result, data_node, "ncr-format");
+ checkAndGetLeaf(result, data_node, "ncr-protocol");
+ checkAndGetLeaf(result, data_node, "qualifying-suffix");
+ checkAndGetLeaf(result, data_node, "override-client-update");
+ checkAndGetLeaf(result, data_node, "override-no-update");
+ checkAndGetLeaf(result, data_node, "replace-client-name");
+ checkAndGetLeaf(result, data_node, "sender-ip");
+ checkAndGetLeaf(result, data_node, "sender-port");
+ checkAndGetLeaf(result, data_node, "server-ip");
+ checkAndGetLeaf(result, data_node, "server-port");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorConfig::getConfigControlKea(DataNode const& data_node) {
+ ElementPtr result(Element::createMap());
+ checkAndGetLeaf(result, data_node, "config-fetch-wait-time");
+ ConstElementPtr databases = getDatabases(data_node, "config-database");
+ if (databases && !databases->empty()) {
+ result->set("config-databases", databases);
+ }
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorConfig::getInterfacesKea(DataNode const& config) {
+ ElementPtr result;
+ optional<DataNode> const& interfaces_config_optional(config.findPath("interfaces-config"));
+ if (interfaces_config_optional) {
+ DataNode const interfaces_config(*interfaces_config_optional);
+ result = Element::createMap();
+
+ checkAndGetLeaf(result, interfaces_config, "dhcp-socket-type");
+ checkAndGetLeaf(result, interfaces_config, "interfaces");
+ checkAndGetLeaf(result, interfaces_config, "outbound-interface");
+ checkAndGetLeaf(result, interfaces_config, "re-detect");
+ checkAndGetLeaf(result, interfaces_config, "service-sockets-max-retries");
+ checkAndGetLeaf(result, interfaces_config, "service-sockets-require-all");
+ checkAndGetLeaf(result, interfaces_config, "service-sockets-retry-wait-time");
+
+ checkAndGetAndJsonifyLeaf(result, interfaces_config, "user-context");
+ }
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorConfig::getServerKeaDhcpCommon(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ checkAndGetLeaf(result, data_node, "allocator");
+ checkAndGetLeaf(result, data_node, "cache-max-age");
+ checkAndGetLeaf(result, data_node, "cache-threshold");
+ checkAndGetLeaf(result, data_node, "calculate-tee-times");
+ checkAndGetLeaf(result, data_node, "dhcp4o6-port");
+ checkAndGetLeaf(result, data_node, "ddns-generated-prefix");
+ checkAndGetLeaf(result, data_node, "ddns-override-client-update");
+ checkAndGetLeaf(result, data_node, "ddns-override-no-update");
+ checkAndGetLeaf(result, data_node, "ddns-qualifying-suffix");
+ checkAndGetLeaf(result, data_node, "ddns-replace-client-name");
+ checkAndGetLeaf(result, data_node, "ddns-send-updates");
+ checkAndGetLeaf(result, data_node, "ddns-ttl-percent");
+ checkAndGetLeaf(result, data_node, "ddns-update-on-renew");
+ checkAndGetLeaf(result, data_node, "ddns-use-conflict-resolution");
+ checkAndGetLeaf(result, data_node, "decline-probation-period");
+ checkAndGetLeaf(result, data_node, "early-global-reservations-lookup");
+ checkAndGetLeaf(result, data_node, "host-reservation-identifiers");
+ checkAndGetLeaf(result, data_node, "hostname-char-replacement");
+ checkAndGetLeaf(result, data_node, "hostname-char-set");
+ checkAndGetLeaf(result, data_node, "ip-reservations-unique");
+ checkAndGetLeaf(result, data_node, "max-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "min-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "parked-packet-limit");
+ checkAndGetLeaf(result, data_node, "rebind-timer");
+ checkAndGetLeaf(result, data_node, "renew-timer");
+ checkAndGetLeaf(result, data_node, "reservation-mode");
+ checkAndGetLeaf(result, data_node, "reservations-global");
+ checkAndGetLeaf(result, data_node, "reservations-in-subnet");
+ checkAndGetLeaf(result, data_node, "reservations-lookup-first");
+ checkAndGetLeaf(result, data_node, "reservations-out-of-pool");
+ checkAndGetLeaf(result, data_node, "server-tag");
+ checkAndGetLeaf(result, data_node, "statistic-default-sample-age");
+ checkAndGetLeaf(result, data_node, "statistic-default-sample-count");
+ checkAndGetLeaf(result, data_node, "store-extended-info");
+ checkAndGetLeaf(result, data_node, "t1-percent");
+ checkAndGetLeaf(result, data_node, "t2-percent");
+ checkAndGetLeaf(result, data_node, "valid-lifetime");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "dhcp-queue-control");
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ ConstElementPtr classes = getClasses(data_node);
+ if (classes && !classes->empty()) {
+ result->set("client-classes", classes);
+ }
+
+ checkAndGet(result, data_node, "compatibility",
+ [&](DataNode const& node) -> ElementPtr const {
+ ElementPtr compatibility(Element::createMap());
+ checkAndGetLeaf(compatibility, node, "ignore-rai-link-selection");
+ checkAndGetLeaf(compatibility, node, "lenient-option-parsing");
+ return compatibility;
+ });
+
+ checkAndGet(result, data_node, "config-control",
+ [&](DataNode const& node) -> ElementPtr const {
+ return getConfigControlKea(node);
+ });
+
+ checkAndGet(result, data_node, "control-socket",
+ [&](DataNode const& node) -> ElementPtr const {
+ return getControlSocket(node);
+ });
+
+ checkAndGet(result, data_node, "dhcp-ddns",
+ [&](DataNode const& node) -> ElementPtr const {
+ return getDdnsKea(node);
+ });
+
+ checkAndGet(result, data_node, "expired-leases-processing",
+ [&](DataNode const& node) -> ElementPtr const {
+ return getExpiredKea(node);
+ });
+
+ ConstElementPtr hooks = getHooksKea(data_node);
+ if (hooks && !hooks->empty()) {
+ result->set("hooks-libraries", hooks);
+ }
+
+ ConstElementPtr const& hosts_databases(getDatabases(data_node, "hosts-database"));
+ if (hosts_databases && !hosts_databases->empty()) {
+ result->set("hosts-databases", hosts_databases);
+ }
+
+ checkAndGet(result, data_node, "lease-database",
+ [&](DataNode const& node) -> ElementPtr const {
+ return getDatabase(node);
+ });
+
+ ConstElementPtr loggers = getLoggers(data_node);
+ if (loggers && !loggers->empty()) {
+ result->set("loggers", loggers);
+ }
+
+ checkAndGet(result, data_node, "multi-threading",
+ [&](DataNode const& node) -> ElementPtr const {
+ ElementPtr multi_threading(Element::createMap());
+ checkAndGetLeaf(multi_threading, node, "enable-multi-threading");
+ checkAndGetLeaf(multi_threading, node, "packet-queue-size");
+ checkAndGetLeaf(multi_threading, node, "thread-pool-size");
+ return multi_threading;
+ });
+
+ ConstElementPtr options = getOptionDataList(data_node);
+ if (options) {
+ result->set("option-data", options);
+ }
+
+ ConstElementPtr defs = getOptionDefList(data_node);
+ if (defs) {
+ result->set("option-def", defs);
+ }
+
+ ConstElementPtr hosts = getHosts(data_node);
+ if (hosts) {
+ result->set("reservations", hosts);
+ }
+
+ ConstElementPtr networks = getSharedNetworks(data_node);
+ if (networks) {
+ result->set("shared-networks", networks);
+ }
+
+ checkAndGet(result, data_node, "sanity-checks",
+ [&](DataNode const& node) -> ElementPtr const {
+ ElementPtr sanity_checks = Element::createMap();
+ checkAndGetLeaf(sanity_checks, node, "extended-info-checks");
+ checkAndGetLeaf(sanity_checks, node, "lease-checks");
+ return sanity_checks;
+ });
+
+ return (result);
+}
+
+ElementPtr
+TranslatorConfig::getServerKeaDhcp4() {
+ string xpath = "/kea-dhcp4-server:config";
+ optional<DataNode> config_optional;
+ try {
+ config_optional = findXPath(xpath);
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+ DataNode const config(*config_optional);
+ ElementPtr result = getServerKeaDhcpCommon(config);
+
+ // Handle DHCPv4 specific global parameters.
+ checkAndGetLeaf(result, config, "authoritative");
+ checkAndGetLeaf(result, config, "boot-file-name");
+ checkAndGetLeaf(result, config, "echo-client-id");
+ checkAndGetLeaf(result, config, "match-client-id");
+ checkAndGetLeaf(result, config, "next-server");
+ checkAndGetLeaf(result, config, "offer-lifetime");
+ checkAndGetLeaf(result, config, "server-hostname");
+
+ checkAndGet(result, config, "compatibility",
+ [&](DataNode const& node) -> ElementPtr const {
+ // If it exists, add to the existing compatibility map created in getServerKeaDhcpCommon.
+ ConstElementPtr const_compatibility(result->get("compatibility"));
+ ElementPtr compatibility;
+ if (const_compatibility) {
+ compatibility = copy(const_compatibility);
+ } else {
+ compatibility = Element::createMap();
+ }
+
+ checkAndGetLeaf(compatibility, node, "exclude-first-last-24");
+ checkAndGetLeaf(compatibility, node, "ignore-dhcp-server-identifier");
+ return compatibility;
+ });
+
+ // Handle interfaces.
+ ElementPtr interfaces_config(getInterfacesKea(config));
+ if (interfaces_config) {
+ result->set("interfaces-config", interfaces_config);
+ }
+
+ // Handle subnets.
+ ConstElementPtr subnets = getSubnets(config);
+ if (subnets) {
+ result->set("subnet4", subnets);
+ }
+
+ return (result);
+}
+
+ElementPtr
+TranslatorConfig::getServerKeaDhcp6() {
+ string xpath = "/kea-dhcp6-server:config";
+ optional<DataNode> config_optional;
+ try {
+ config_optional = findXPath(xpath);
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+ DataNode const config(*config_optional);
+ ElementPtr result = getServerKeaDhcpCommon(config);
+
+ // Handle DHCPv6 specific global parameters.
+ checkAndGetLeaf(result, config, "data-directory");
+ checkAndGetLeaf(result, config, "mac-sources");
+ checkAndGetLeaf(result, config, "max-preferred-lifetime");
+ checkAndGetLeaf(result, config, "min-preferred-lifetime");
+ checkAndGetLeaf(result, config, "pd-allocator");
+ checkAndGetLeaf(result, config, "preferred-lifetime");
+ checkAndGetLeaf(result, config, "relay-supplied-options");
+
+ // Handle interfaces.
+ ElementPtr interfaces_config(getInterfacesKea(config));
+ if (interfaces_config) {
+ result->set("interfaces-config", interfaces_config);
+ }
+
+ // Handle server-id.
+ optional<DataNode> const& server_id_optional(config.findPath("server-id"));
+ if (server_id_optional) {
+ DataNode const server_id(*server_id_optional);
+ ElementPtr server_id_map(Element::createMap());
+ checkAndGetLeaf(server_id_map, server_id, "type");
+ checkAndGetLeaf(server_id_map, server_id, "identifier");
+ checkAndGetLeaf(server_id_map, server_id, "time");
+ checkAndGetLeaf(server_id_map, server_id, "htype");
+ checkAndGetLeaf(server_id_map, server_id, "enterprise-id");
+ checkAndGetLeaf(server_id_map, server_id, "persist");
+ checkAndGetAndJsonifyLeaf(server_id_map, server_id, "user-context");
+ if (!server_id_map->empty()) {
+ result->set("server-id", server_id_map);
+ }
+ }
+
+ // Handle subnets.
+ ConstElementPtr subnets = getSubnets(config);
+ if (subnets) {
+ result->set("subnet6", subnets);
+ }
+
+ return (result);
+}
+
+void
+TranslatorConfig::setConfig(ElementPtr elem) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ if (elem) {
+ AdaptorConfig::preProcess6(elem);
+ setConfigIetf6(elem);
+ } else {
+ delConfigIetf6();
+ }
+ } else if (model_ == KEA_DHCP4_SERVER) {
+ if (elem) {
+ AdaptorConfig::preProcess4(elem);
+ setConfigKea4(elem);
+ } else {
+ delConfigKea();
+ }
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ if (elem) {
+ AdaptorConfig::preProcess6(elem);
+ setConfigKea6(elem);
+ } else {
+ delConfigKea();
+ }
+ } else {
+ isc_throw(NotImplemented,
+ "setConfig not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting config '" << elem->str()
+ << "': " << ex.what());
+ }
+}
+
+void
+TranslatorConfig::delConfigIetf6() {
+ deleteItem("/" + model_ + ":server");
+}
+
+void
+TranslatorConfig::setConfigIetf6(ConstElementPtr elem) {
+ string xpath = "/" + model_ + ":server/server-config";
+ ConstElementPtr dhcp6 = elem->get("Dhcp6");
+ if (!dhcp6) {
+ isc_throw(BadValue, "no Dhcp6 entry in " << elem->str());
+ }
+
+ ConstElementPtr ranges = dhcp6->get("subnet6");
+ if (ranges && !ranges->empty()) {
+ setSubnets(xpath + "/network-ranges", ranges);
+ }
+
+ // Skip everything else.
+}
+
+void
+TranslatorConfig::delConfigKea() {
+ deleteItem("/" + model_ + ":config");
+}
+
+void
+TranslatorConfig::setConfigKea4(ConstElementPtr elem) {
+ ConstElementPtr dhcp = elem->get("Dhcp4");
+ if (dhcp) {
+ setServerKeaDhcp4(dhcp);
+ }
+}
+
+void
+TranslatorConfig::setConfigKea6(ConstElementPtr elem) {
+ ConstElementPtr dhcp = elem->get("Dhcp6");
+ if (dhcp) {
+ setServerKeaDhcp6(dhcp);
+ }
+}
+
+void
+TranslatorConfig::setServerKeaDhcpCommon(string const& xpath,
+ ConstElementPtr elem) {
+ checkAndSetLeaf(elem, xpath, "allocator", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "cache-max-age", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "cache-threshold", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "calculate-tee-times", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-generated-prefix", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-override-client-update", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-override-no-update", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-qualifying-suffix", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-replace-client-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-send-updates", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-ttl-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "ddns-update-on-renew", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-use-conflict-resolution", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "dhcp4o6-port", LeafBaseType::Uint16);
+ checkAndSetLeaf(elem, xpath, "decline-probation-period", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "early-global-reservations-lookup", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "hostname-char-replacement", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "hostname-char-set", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ip-reservations-unique", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "max-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "parked-packet-limit", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "rebind-timer", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "renew-timer", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "reservation-mode", LeafBaseType::Enum);
+ checkAndSetLeaf(elem, xpath, "reservations-global", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "reservations-in-subnet", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "reservations-lookup-first", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "reservations-out-of-pool", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "server-tag", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "statistic-default-sample-age", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "statistic-default-sample-count", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "store-extended-info", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "t1-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "t2-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "valid-lifetime", LeafBaseType::Uint32);
+
+ checkAndSetLeafList(elem, xpath, "host-reservation-identifiers", LeafBaseType::Enum);
+
+ checkAndStringifyAndSetLeaf(elem, xpath, "dhcp-queue-control");
+
+ checkAndSetUserContext(elem, xpath);
+
+ ConstElementPtr classes = elem->get("client-classes");
+ if (classes && !classes->empty()) {
+ setClasses(xpath, classes);
+ }
+
+ ConstElementPtr compatibility(elem->get("compatibility"));
+ if (compatibility) {
+ checkAndSetLeaf(compatibility, xpath + "/compatibility", "ignore-rai-link-selection", LeafBaseType::Bool);
+ checkAndSetLeaf(compatibility, xpath + "/compatibility", "lenient-option-parsing", LeafBaseType::Bool);
+ }
+
+ ConstElementPtr config_ctrl = elem->get("config-control");
+ if (config_ctrl && !config_ctrl->empty()) {
+ checkAndSetLeaf(config_ctrl, xpath + "/config-control", "config-fetch-wait-time", LeafBaseType::Uint32);
+ ConstElementPtr config_databases = config_ctrl->get("config-databases");
+ if (config_databases && !config_databases->empty()) {
+ setDatabases(xpath + "/config-control/config-database", config_databases);
+ }
+ }
+
+ ConstElementPtr socket = elem->get("control-socket");
+ if (socket && !socket->empty()) {
+ setControlSocket(xpath + "/control-socket", socket);
+ }
+
+ ConstElementPtr ddns = elem->get("dhcp-ddns");
+ if (ddns) {
+ string const ddns_xpath(xpath + "/dhcp-ddns");
+ checkAndSetLeaf(ddns, ddns_xpath, "enable-updates", LeafBaseType::Bool);
+ checkAndSetLeaf(ddns, ddns_xpath, "generated-prefix", LeafBaseType::String);
+ checkAndSetLeaf(ddns, ddns_xpath, "hostname-char-replacement", LeafBaseType::String);
+ checkAndSetLeaf(ddns, ddns_xpath, "hostname-char-set", LeafBaseType::String);
+ checkAndSetLeaf(ddns, ddns_xpath, "max-queue-size", LeafBaseType::Uint32);
+ checkAndSetLeaf(ddns, ddns_xpath, "ncr-format", LeafBaseType::Enum);
+ checkAndSetLeaf(ddns, ddns_xpath, "ncr-protocol", LeafBaseType::Enum);
+ checkAndSetLeaf(ddns, ddns_xpath, "override-client-update", LeafBaseType::Bool);
+ checkAndSetLeaf(ddns, ddns_xpath, "override-no-update", LeafBaseType::Bool);
+ checkAndSetLeaf(ddns, ddns_xpath, "qualifying-suffix", LeafBaseType::String);
+ checkAndSetLeaf(ddns, ddns_xpath, "replace-client-name", LeafBaseType::Enum);
+ checkAndSetLeaf(ddns, ddns_xpath, "sender-ip", LeafBaseType::String);
+ checkAndSetLeaf(ddns, ddns_xpath, "sender-port", LeafBaseType::Uint16);
+ checkAndSetLeaf(ddns, ddns_xpath, "server-ip", LeafBaseType::String);
+ checkAndSetLeaf(ddns, ddns_xpath, "server-port", LeafBaseType::Uint16);
+ checkAndSetUserContext(ddns, ddns_xpath);
+ }
+
+ ConstElementPtr expired = elem->get("expired-leases-processing");
+ if (expired) {
+ string const expired_xpath(xpath + "/expired-leases-processing");
+ checkAndSetLeaf(expired, expired_xpath, "flush-reclaimed-timer-wait-time", LeafBaseType::Uint32);
+ checkAndSetLeaf(expired, expired_xpath, "hold-reclaimed-time", LeafBaseType::Uint32);
+ checkAndSetLeaf(expired, expired_xpath, "max-reclaim-leases", LeafBaseType::Uint32);
+ checkAndSetLeaf(expired, expired_xpath, "max-reclaim-time", LeafBaseType::Uint32);
+ checkAndSetLeaf(expired, expired_xpath, "reclaim-timer-wait-time", LeafBaseType::Uint32);
+ checkAndSetLeaf(expired, expired_xpath, "unwarned-reclaim-cycles", LeafBaseType::Uint32);
+ }
+
+ ConstElementPtr hook_libs = elem->get("hooks-libraries");
+ if (hook_libs) {
+ for (ElementPtr const& lib : hook_libs->listValue()) {
+ ConstElementPtr name = lib->get("library");
+ if (!name) {
+ continue;
+ }
+ ostringstream hook_lib;
+ hook_lib << xpath << "/hook-library[library='"
+ << name->stringValue() << "']";
+ string const hook_xpath(hook_lib.str());
+ setItem(hook_xpath, ElementPtr(), LeafBaseType::Unknown);
+ checkAndStringifyAndSetLeaf(lib, hook_xpath, "parameters");
+ }
+ }
+
+ ConstElementPtr hosts_databases = elem->get("hosts-databases");
+ if (hosts_databases && !hosts_databases->empty()) {
+ setDatabases(xpath + "/hosts-database", hosts_databases);
+ }
+
+ ConstElementPtr database = elem->get("lease-database");
+ if (database && !database->empty()) {
+ setDatabase(xpath + "/lease-database", database);
+ }
+
+ ConstElementPtr loggers = elem->get("loggers");
+ if (loggers) {
+ setLoggers(xpath, loggers);
+ }
+
+ ConstElementPtr multi_threading(elem->get("multi-threading"));
+ if (multi_threading) {
+ string const mt_xpath(xpath + "/multi-threading");
+ checkAndSetLeaf(multi_threading, mt_xpath, "enable-multi-threading", LeafBaseType::Bool);
+ checkAndSetLeaf(multi_threading, mt_xpath, "packet-queue-size", LeafBaseType::Uint32);
+ checkAndSetLeaf(multi_threading, mt_xpath, "thread-pool-size", LeafBaseType::Uint32);
+ }
+
+ ConstElementPtr options = elem->get("option-data");
+ if (options && !options->empty()) {
+ setOptionDataList(xpath, options);
+ }
+
+ ConstElementPtr defs = elem->get("option-def");
+ if (defs && !defs->empty()) {
+ setOptionDefList(xpath, defs);
+ }
+
+ ConstElementPtr hosts = elem->get("reservations");
+ if (hosts && !hosts->empty()) {
+ setHosts(xpath, hosts);
+ }
+
+ ConstElementPtr sanity = elem->get("sanity-checks");
+ if (sanity) {
+ checkAndSetLeaf(sanity, xpath + "/sanity-checks", "extended-info-checks", LeafBaseType::Enum);
+ checkAndSetLeaf(sanity, xpath + "/sanity-checks", "lease-checks", LeafBaseType::Enum);
+ }
+
+ ConstElementPtr networks = elem->get("shared-networks");
+ if (networks && !networks->empty()) {
+ setSharedNetworks(xpath, networks);
+ }
+}
+
+void
+TranslatorConfig::setServerKeaDhcp4(ConstElementPtr elem) {
+ string xpath = "/kea-dhcp4-server:config";
+
+ setServerKeaDhcpCommon(xpath, elem);
+
+ checkAndSetLeaf(elem, xpath, "authoritative", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "boot-file-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "echo-client-id", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "match-client-id", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "next-server", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "offer-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "server-hostname", LeafBaseType::String);
+
+ ConstElementPtr compatibility(elem->get("compatibility"));
+ if (compatibility) {
+ checkAndSetLeaf(compatibility, xpath + "/compatibility", "exclude-first-last-24", LeafBaseType::Bool);
+ checkAndSetLeaf(compatibility, xpath + "/compatibility", "ignore-dhcp-server-identifier", LeafBaseType::Bool);
+ }
+
+ ConstElementPtr if_config = elem->get("interfaces-config");
+ if (if_config) {
+ string const if_cfg_xpath(xpath + "/interfaces-config");
+ checkAndSetLeaf(if_config, if_cfg_xpath, "dhcp-socket-type", LeafBaseType::Enum);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "outbound-interface", LeafBaseType::Enum);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "service-sockets-max-retries", LeafBaseType::Uint32);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "service-sockets-require-all", LeafBaseType::Bool);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "service-sockets-retry-wait-time", LeafBaseType::Uint32);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "re-detect", LeafBaseType::Bool);
+ checkAndSetLeafList(if_config, if_cfg_xpath, "interfaces", LeafBaseType::String);
+ checkAndSetUserContext(if_config, if_cfg_xpath);
+ }
+
+ ConstElementPtr subnets = elem->get("subnet4");
+ if (subnets) {
+ setSubnets(xpath, subnets);
+ }
+}
+
+void
+TranslatorConfig::setServerKeaDhcp6(ConstElementPtr elem) {
+ string xpath = "/kea-dhcp6-server:config";
+
+ setServerKeaDhcpCommon(xpath, elem);
+
+ checkAndSetLeaf(elem, xpath, "data-directory", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "max-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "pd-allocator", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "preferred-lifetime", LeafBaseType::Uint32);
+
+ checkAndSetLeafList(elem, xpath, "mac-sources", LeafBaseType::String);
+ checkAndSetLeafList(elem, xpath, "relay-supplied-options", LeafBaseType::String);
+
+ ConstElementPtr if_config = elem->get("interfaces-config");
+ if (if_config) {
+ string const if_cfg_xpath(xpath + "/interfaces-config");
+ checkAndSetLeaf(if_config, if_cfg_xpath, "service-sockets-max-retries", LeafBaseType::Uint32);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "service-sockets-require-all", LeafBaseType::Bool);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "service-sockets-retry-wait-time", LeafBaseType::Uint32);
+ checkAndSetLeaf(if_config, if_cfg_xpath, "re-detect", LeafBaseType::Bool);
+ checkAndSetLeafList(if_config, if_cfg_xpath, "interfaces", LeafBaseType::String);
+ checkAndSetUserContext(if_config, if_cfg_xpath);
+ }
+
+ ConstElementPtr server_id = elem->get("server-id");
+ if (server_id) {
+ string const srv_id_xpath(xpath + "/server-id");
+ checkAndSetLeaf(server_id, srv_id_xpath, "type", LeafBaseType::Enum);
+ checkAndSetLeaf(server_id, srv_id_xpath, "identifier", LeafBaseType::String);
+ checkAndSetLeaf(server_id, srv_id_xpath, "time", LeafBaseType::Uint32);
+ checkAndSetLeaf(server_id, srv_id_xpath, "htype", LeafBaseType::Uint16);
+ checkAndSetLeaf(server_id, srv_id_xpath, "enterprise-id", LeafBaseType::Uint32);
+ checkAndSetLeaf(server_id, srv_id_xpath, "persist", LeafBaseType::Bool);
+ checkAndSetUserContext(server_id, srv_id_xpath);
+ }
+
+ ConstElementPtr subnets = elem->get("subnet6");
+ if (subnets) {
+ setSubnets(xpath, subnets);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_config.h b/src/lib/yang/translator_config.h
new file mode 100644
index 0000000..cbdff25
--- /dev/null
+++ b/src/lib/yang/translator_config.h
@@ -0,0 +1,556 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_CONFIG_H
+#define ISC_TRANSLATOR_CONFIG_H 1
+
+#include <yang/translator.h>
+#include <yang/translator_class.h>
+#include <yang/translator_control_socket.h>
+#include <yang/translator_database.h>
+#include <yang/translator_logger.h>
+#include <yang/translator_shared_network.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief DHCP configuration translation between YANG and JSON
+///
+/// This translator supports kea-dhcp4-server, kea-dhcp6-server and
+/// partially ietf-dhcpv6-server.
+
+/// JSON syntax for kea-dhcp4 is:
+/// @code
+/// "Dhcp4": {
+/// "valid-lifetime": <valid lifetime>,
+/// "min-valid-lifetime": <minimum valid lifetime>,
+/// "max-valid-lifetime": <maximum valid lifetime>,
+/// "renew-timer": <renew timer>,
+/// "rebind-timer": <rebind timer>,
+/// "calculate-tee-times": <calculate T1/T2 times>,
+/// "t1-percent": <T1 percent>,
+/// "t2-percent": <T2 percent>,
+/// "decline-probation-period": <decline probation period>,
+/// "subnet4": [ <list of subnet4> ],
+/// <shared-networks>,
+/// "interfaces-config": {
+/// "interfaces" [ <list of interface names / specs> ],
+/// ...
+/// },
+/// <databases>,
+/// <host-reservation-identifiers>,
+/// <client-classes>,
+/// <option-def>,
+/// <option-data>,
+/// <hooks-libraries>,
+/// <expired-leases-processing>,
+/// <dhcp4o6-port>,
+/// <control-socket>,
+/// <hostname-char-set": <hostname character set>,
+/// <hostname-char-replacement": <hostname character replacement>,
+/// <dhcp-ddns>,
+/// "echo-client-id": <echo client id flag>,
+/// "match-client-id": <match client id flag>,
+/// "next-server": <next server address>,
+/// "server-hostname": <server hostname>,
+/// "boot-file-name": <boot file name>,
+/// "authoritative": <authoritative flag>,
+/// <user-context>,
+/// <comment>,
+/// "sanity-checks": { <sanity checks> },
+/// "reservation-mode": <host reservation mode>,
+/// "reservations": [ <list of host reservations> ],
+/// <config-control>,
+/// "server-tag": <server tag>,
+/// "dhcp-queue-control": { <DHCP queue control> },
+/// "loggers": [ <list of loggers> ]
+/// },
+/// @endcode
+///
+/// YANG syntax for kea-dhcp4-server:config is:
+/// @code
+/// +--rw valid-lifetime? uint32
+/// +--rw min-valid-lifetime? uint32
+/// +--rw max-valid-lifetime? uint32
+/// +--rw renew-timer? uint32
+/// +--rw rebind-timer? uint32
+/// +--rw calculate-tee-times? boolean
+/// +--rw t1-percent? decimal64
+/// +--rw t2-percent? decimal64
+/// +--rw decline-probation-period? uint32
+/// +--rw subnet4* [id]
+/// +--rw shared-network* [name]
+/// +--rw interfaces-config
+/// +--rw lease-database!
+/// +--rw hosts-database* [database-type]
+/// +--rw host-reservation-identifiers* host-identifier-type
+/// +--rw client-class* [name]
+/// +--rw option-def* [code space]
+/// +--rw option-data* [code space]
+/// +--rw hook-library* [library]
+/// +--rw expired-leases-processing
+/// +--rw dhcp4o6-port? uint16
+/// +--rw control-socket!
+/// +--rw hostname-char-set? string
+/// +--rw hostname-char-replacement? string
+/// +--rw dhcp-ddns
+/// +--rw echo-client-id? boolean
+/// +--rw match-client-id? boolean
+/// +--rw next-server? inet:ipv4-address
+/// +--rw server-hostname? string
+/// +--rw boot-file-name? string
+/// +--rw authoritative? boolean
+/// +--rw user-context? user-context
+/// +--rw sanity-checks
+/// +--rw reservation-mode? host-reservation-mode
+/// +--rw host* [identifier-type identifier]
+/// +--rw config-control
+/// +--rw server-tag? string
+/// +--rw dhcp-queue-control? string
+/// +--rw logger* [name]
+/// +--rw cache-max-age? uint32
+/// +--rw cache-threshold? decimal64
+/// +--rw compatibility
+/// +--rw ddns-generated-prefix? string
+/// +--rw ddns-override-client-update? boolean
+/// +--rw ddns-override-no-update? boolean
+/// +--rw ddns-qualifying-suffix? string
+/// +--rw ddns-replace-client-name? string
+/// +--rw ddns-send-updates? boolean
+/// +--rw ddns-update-on-renew? boolean
+/// +--rw ddns-use-conflict-resolution? boolean
+/// +--rw ip-reservations-unique? boolean
+/// +--rw early-global-reservations-lookup? boolean
+/// +--rw reservations-lookup-first? boolean
+/// +--rw multi-threading
+/// +--rw parked-packet-limit? uint32
+/// +--rw reservations-global? boolean
+/// +--rw reservations-in-subnet? boolean
+/// +--rw reservations-out-of-pool? boolean
+/// +--rw statistic-default-sample-age? uint32
+/// +--rw statistic-default-sample-count? uint32
+/// +--rw store-extended-info? boolean
+/// @endcode
+///
+/// Example of kea-dhcp6 simple configuration:
+/// @code
+/// {
+/// "Dhcp4": {
+/// "interfaces-config":
+/// {
+/// "interfaces": [ "eth1" ]
+/// },
+/// "control-socket": {
+/// "socket-type": "unix",
+/// "socket-name": "/tmp/kea4-sock"
+/// },
+/// "subnet4":
+/// [
+/// {
+/// "subnet": "10.0.35.0/24",
+/// "pools":
+/// [
+/// {
+/// "pool": "10.0.35.64/27"
+/// }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+///
+/// @endcode
+///
+/// The same configuration wrote into YANG datastore using @c setConfig()
+/// with the kea-dhcp4-model and exported to XML format:
+/// @code
+/// <config xmlns="urn:ietf:params:xml:ns:yang:kea-dhcp4-server">
+/// <subnet4>
+/// <id>1</id>
+/// <pool>
+/// <start-address>10.0.35.64</start-address>
+/// <end-address>10.0.35.95</end-address>
+/// <prefix>10.0.35.64/27</prefix>
+/// </pool>
+/// <subnet>10.0.35.0/24</subnet>
+/// </subnet4>
+/// <interfaces-config>
+/// <interfaces>eth1</interfaces>
+/// </interfaces-config>
+/// <control-socket>
+/// <socket-name>/tmp/kea4-sock</socket-name>
+/// <socket-type>unix</socket-type>
+/// </control-socket>
+/// </config>
+/// @endcode
+
+/// JSON syntax for kea-dhcp6 is:
+/// @code
+/// "Dhcp6": {
+/// "data-directory": <data directory>,
+/// "preferred-lifetime": <preferred lifetime>,
+/// "min-preferred-lifetime": <minimum preferred lifetime>,
+/// "max-preferred-lifetime": <maximum preferred lifetime>,
+/// "valid-lifetime": <valid lifetime>,
+/// "min-valid-lifetime": <minimum valid lifetime>,
+/// "max-valid-lifetime": <maximum valid lifetime>,
+/// "renew-timer": <renew timer>,
+/// "rebind-timer": <rebind timer>,
+/// "calculate-tee-times": <calculate T1/T2 times>,
+/// "t1-percent": <T1 percent>,
+/// "t2-percent": <T2 percent>,
+/// "decline-probation-period": <decline probation period>,
+/// "subnet6": [ <list of subnet6> ],
+/// <shared-networks>,
+/// "interfaces-config": {
+/// "interfaces" [ <list of interface names / specs> ],
+/// ...
+/// },
+/// <databases>,
+/// <mac-sources>,
+/// <relay-supplied-options>,
+/// <host-reservation-identifiers>,
+/// <client-classes>,
+/// <option-def>,
+/// <option-data>,
+/// <hooks-libraries>,
+/// <expired-leases-processing>,
+/// <server-id>,
+/// <dhcp4o6-port>,
+/// <control-socket>,
+/// <hostname-char-set": <hostname character set>,
+/// <hostname-char-replacement": <hostname character replacement>,
+/// <dhcp-ddns>,
+/// <user-context>,
+/// <comment>
+/// "sanity-checks": { <sanity checks> },
+/// "reservation-mode": <host reservation mode>,
+/// "reservations": [ <list of host reservations> ],
+/// <config-control>,
+/// "server-tag": <server tag>,
+/// "dhcp-queue-control": { <DHCP queue control> },
+/// "loggers": [ <list of loggers> ]
+/// },
+/// @endcode
+///
+/// YANG syntax for kea-dhcp6-server:config is:
+/// @code
+/// +--rw data-directory? string
+/// +--rw preferred-lifetime? uint32
+/// +--rw min-preferred-lifetime? uint32
+/// +--rw max-preferred-lifetime? uint32
+/// +--rw valid-lifetime? uint32
+/// +--rw min-valid-lifetime? uint32
+/// +--rw max-valid-lifetime? uint32
+/// +--rw renew-timer? uint32
+/// +--rw rebind-timer? uint32
+/// +--rw calculate-tee-times? boolean
+/// +--rw t1-percent? decimal64
+/// +--rw t2-percent? decimal64
+/// +--rw decline-probation-period? uint32
+/// +--rw subnet6* [id]
+/// +--rw shared-network* [name]
+/// +--rw interfaces-config
+/// +--rw lease-database!
+/// +--rw hosts-database* [database-type]
+/// +--rw relay-supplied-options* string
+/// +--rw mac-sources* string
+/// +--rw host-reservation-identifiers* host-identifier-type
+/// +--rw client-class* [name]
+/// +--rw option-def* [code space]
+/// +--rw option-data* [code space]
+/// +--rw hook-library* [library]
+/// +--rw expired-leases-processing
+/// +--rw server-id!
+/// +--rw dhcp4o6-port? uint16
+/// +--rw control-socket!
+/// +--rw hostname-char-set? string
+/// +--rw hostname-char-replacement? string
+/// +--rw dhcp-ddns
+/// +--rw user-context? user-context
+/// +--rw sanity-checks
+/// +--rw reservation-mode? host-reservation-mode
+/// +--rw host* [identifier-type identifier]
+/// +--rw config-control
+/// +--rw server-tag? string
+/// +--rw dhcp-queue-control? string
+/// +--rw logger* [name]
+/// +--rw cache-max-age? uint32
+/// +--rw cache-threshold? decimal64
+/// +--rw compatibility
+/// +--rw ddns-generated-prefix? string
+/// +--rw ddns-override-client-update? boolean
+/// +--rw ddns-override-no-update? boolean
+/// +--rw ddns-qualifying-suffix? string
+/// +--rw ddns-replace-client-name? string
+/// +--rw ddns-send-updates? boolean
+/// +--rw ddns-update-on-renew? boolean
+/// +--rw ddns-use-conflict-resolution? boolean
+/// +--rw ip-reservations-unique? boolean
+/// +--rw early-global-reservations-lookup? boolean
+/// +--rw reservations-lookup-first? boolean
+/// +--rw multi-threading
+/// +--rw parked-packet-limit? uint32
+/// +--rw reservations-global? boolean
+/// +--rw reservations-in-subnet? boolean
+/// +--rw reservations-out-of-pool? boolean
+/// +--rw statistic-default-sample-age? uint32
+/// +--rw statistic-default-sample-count? uint32
+/// +--rw store-extended-info? boolean
+/// @endcode
+///
+/// Example of kea-dhcp6 simple configuration:
+/// @code
+/// {
+/// "Dhcp6": {
+/// "interfaces-config":
+/// {
+/// "interfaces": [ "eth1" ]
+/// },
+/// "control-socket": {
+/// "socket-type": "unix",
+/// "socket-name": "/tmp/kea6-sock"
+/// },
+/// "subnet6":
+/// [
+/// {
+/// "subnet": "2001:db8::/64",
+/// "pools":
+/// [
+/// {
+/// "pool": "2001:db8::1:0/112"
+/// }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+/// @endcode
+///
+/// The same configuration wrote into YANG datastore using @c setConfig()
+/// with the kea-dhcp6-model and exported to XML format:
+/// @code
+/// <config xmlns="urn:ietf:params:xml:ns:yang:kea-dhcp6-server">
+/// <subnet6>
+/// <id>1</id>
+/// <pool>
+/// <start-address>2001:db8::1:0</start-address>
+/// <end-address>2001:db8::1:ffff</end-address>
+/// <prefix>2001:db8::1:0/112</prefix>
+/// </pool>
+/// <subnet>2001:db8::/64</subnet>
+/// </subnet6>
+/// <interfaces-config>
+/// <interfaces>eth1</interfaces>
+/// </interfaces-config>
+/// <control-socket>
+/// <socket-name>/tmp/kea6-sock</socket-name>
+/// <socket-type>unix</socket-type>
+/// </control-socket>
+/// </config>
+/// @endcode
+
+/// Inheritance graph between translators is:
+///
+/// +-----------------------------------------+
+/// | |
+/// +------------------------------+ |
+/// | | |
+/// +----------+-------------------+----------+
+/// | | | |
+/// | | +---------+----------+
+/// | | | | |
+/// config +- shared +- subnet +- pool --+- option -+ basic
+/// | network (list) | (list) | data |
+/// | (list) | | (list) |
+/// | | | |
+/// | +- pd ----+ |
+/// | | pool | |
+/// | | (list) | |
+/// | | | |
+/// +--------------------+- host --+ |
+/// | (liat) | |
+/// | | |
+/// | +--------------------+----------+
+/// | | |
+/// +- class -+- option ----------------------+
+/// | (list) | def |
+/// | | (list) |
+/// +---------+ |
+/// | |
+/// + control --------------------------------+
+/// | socket |
+/// | |
+/// +------------+ |
+/// | | |
+/// +- database -+- database -----------------+
+/// | list |
+/// | |
+/// +- logger --------------------------------+
+/// (list)
+///
+/// 'XXX (list)' stands for 'XXX list --- XXX' which is a common motif
+/// (only database shows direct dependencies on both the list and the element)
+
+/// @brief A translator class for converting the config between YANG and JSON.
+///
+/// Currently supports the following models:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+/// - ietf-dhcpv6-server (partial)
+class TranslatorConfig : virtual public TranslatorControlSocket,
+ virtual public TranslatorDatabases,
+ virtual public TranslatorClasses,
+ virtual public TranslatorSharedNetworks,
+ virtual public TranslatorLoggers {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorConfig(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorConfig() = default;
+
+ /// @brief Translate the whole DHCP server configuration from YANG to JSON.
+ ///
+ /// @return JSON representation of the config.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getConfig();
+
+ /// @brief Translate and set the DHCP server configuration from JSON to YANG.
+ ///
+ /// Null elem argument removes the config containers.
+ ///
+ /// @param elem The JSON element.
+ void setConfig(isc::data::ElementPtr elem);
+
+protected:
+ /// @brief getConfig for ietf-dhcpv6-server.
+ ///
+ /// This implementation is very preliminary. It handles network-ranges
+ /// only partially and nothing else.
+ ///
+ /// @return JSON representation of the config.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getConfigIetf6();
+
+ /// @brief delConfig for ietf-dhcpv6-server.
+ void delConfigIetf6();
+
+ /// @brief setConfig for ietf-dhcpv6-server.
+ ///
+ /// This implementation is very preliminary. It handles network-ranges
+ /// only partially and nothing else.
+ ///
+ /// @param elem The JSON element.
+ /// @throw BadValue on config without Dhcp6.
+ void setConfigIetf6(isc::data::ConstElementPtr elem);
+
+ /// @brief getConfig for kea-dhcp4-server.
+ ///
+ /// @return JSON representation of the config.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getConfigKea4();
+
+ /// @brief getConfig for kea-dhcp6-server.
+ ///
+ /// @return JSON representation of the config.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getConfigKea6();
+
+ /// @brief getServer common part for kea-dhcp[46]-server:config.
+ ///
+ /// @param data_node the YANG node representing the server configuration
+ ///
+ /// @return JSON representation of the server.
+ isc::data::ElementPtr getServerKeaDhcpCommon(libyang::DataNode const& data_node);
+
+ /// @brief getServer for kea-dhcp4-server:config.
+ ///
+ /// @return JSON representation of the config.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getServerKeaDhcp4();
+
+ /// @brief getServer for kea-dhcp6-server:config.
+ ///
+ /// @return JSON representation of the config.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getServerKeaDhcp6();
+
+ /// @brief delConfig for kea-dhcp[46]-server.
+ void delConfigKea();
+
+ /// @brief setConfig for kea-dhcp[46]-server.
+ ///
+ /// @param elem The JSON element.
+ void setConfigKea4(isc::data::ConstElementPtr elem);
+
+ /// @brief setConfig for kea-dhcp6-server.
+ ///
+ /// @param elem The JSON element.
+ void setConfigKea6(isc::data::ConstElementPtr elem);
+
+ /// @brief setServer common part for kea-dhcp[46]-server:config.
+ ///
+ /// @param xpath The xpath of the server.
+ /// @param elem The JSON element.
+ void setServerKeaDhcpCommon(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ isc::data::ElementPtr getHook(libyang::DataNode const& data_node);
+
+ /// @brief Retrieves hooks configuration from sysrepo.
+ ///
+ /// @param data_node the YANG node representing the hook libraries
+ ///
+ /// @return ElementList with hooks configuration.
+ isc::data::ElementPtr getHooksKea(libyang::DataNode const& data_node);
+
+ /// @brief Retrieves expired leases processing parameters from sysrepo.
+ ///
+ /// @param data_node the YANG node representing the configuration for expired lease processing
+ ///
+ /// @return ElementList with expired leases configuration.
+ isc::data::ElementPtr getExpiredKea(libyang::DataNode const& data_node);
+
+ /// @brief Retrieves DDNS configuration from sysrepo
+ ///
+ /// @param data_node the YANG node representing dhcp-ddns configuration
+ ///
+ /// @return ElementList with dhcp-ddns configuration.
+ isc::data::ElementPtr getDdnsKea(libyang::DataNode const& data_node);
+
+ /// @brief Retrieves configuration control from sysrepo.
+ ///
+ /// @param data_node the YANG node representing configuration control
+ ///
+ /// @return ElementMap with configuration control.
+ isc::data::ElementPtr getConfigControlKea(libyang::DataNode const& data_node);
+
+ /// @brief Retrieves interfaces configuration from sysrepo.
+ ///
+ /// @param data_node the YANG node representing the interfaces configuration
+ ///
+ /// @return ElementMap with configuration control.
+ isc::data::ElementPtr getInterfacesKea(libyang::DataNode const& data_node);
+
+ /// @brief setServer for kea-dhcp4-server:config.
+ ///
+ /// @param elem The JSON element.
+ void setServerKeaDhcp4(isc::data::ConstElementPtr elem);
+
+ /// @brief setServer for kea-dhcp6-server:config.
+ ///
+ /// @param elem The JSON element.
+ void setServerKeaDhcp6(isc::data::ConstElementPtr elem);
+}; // TranslatorConfig
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_CONFIG_H
diff --git a/src/lib/yang/translator_control_socket.cc b/src/lib/yang/translator_control_socket.cc
new file mode 100644
index 0000000..2d44b2f
--- /dev/null
+++ b/src/lib/yang/translator_control_socket.cc
@@ -0,0 +1,97 @@
+// Copyright (C) 2018-2022 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 <yang/translator_control_socket.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorControlSocket::TranslatorControlSocket(Session session,
+ const string& model)
+ : Translator(session, model) {
+}
+
+ElementPtr
+TranslatorControlSocket::getControlSocket(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER) ||
+ (model_ == KEA_DHCP_DDNS) ||
+ (model_ == KEA_CTRL_AGENT)) {
+ return (getControlSocketKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting control socket: " << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getControlSocket not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorControlSocket::getControlSocketKea(DataNode const& data_node) {
+ ElementPtr result(Element::createMap());
+ getMandatoryLeaf(result, data_node, "socket-name");
+ getMandatoryLeaf(result, data_node, "socket-type");
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorControlSocket::getControlSocketFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getControlSocket(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+void
+TranslatorControlSocket::setControlSocket(string const& xpath,
+ ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER) ||
+ (model_ == KEA_DHCP_DDNS) ||
+ (model_ == KEA_CTRL_AGENT)) {
+ setControlSocketKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setControlSocket not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting control socket '" << elem->str()
+ << "' at '" << xpath << "': " << ex.what());
+ }
+}
+
+void
+TranslatorControlSocket::setControlSocketKea(string const& xpath,
+ ConstElementPtr elem) {
+ if (!elem) {
+ deleteItem(xpath);
+ return;
+ }
+
+ setMandatoryLeaf(elem, xpath, "socket-name", LeafBaseType::String);
+ setMandatoryLeaf(elem, xpath, "socket-type", LeafBaseType::Enum);
+ checkAndSetUserContext(elem, xpath);
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_control_socket.h b/src/lib/yang/translator_control_socket.h
new file mode 100644
index 0000000..f2602c7
--- /dev/null
+++ b/src/lib/yang/translator_control_socket.h
@@ -0,0 +1,134 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_CONTROL_SOCKET_H
+#define ISC_TRANSLATOR_CONTROL_SOCKET_H 1
+
+#include <yang/translator.h>
+
+namespace isc {
+namespace yang {
+
+/// Control socket translation between YANG and JSON
+///
+/// JSON syntax for all Kea servers with command channel is:
+/// @code
+/// "control-socket": {
+/// "socket-type": "<socket type>",
+/// "socket-name": "<socket name>",
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax is:
+/// @code
+/// +--rw control-socket!
+/// +--rw socket-name string
+/// +--rw socket-type enumeration
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// {
+/// "socket-name": "/tmp/kea.sock",
+/// "socket-type": "unix",
+/// "user-context": { "foo": 1 }
+/// }
+/// @endcode
+/// @code
+/// /kea-ctrl-agent:config (container)
+/// /kea-ctrl-agent:config/control-sockets (container)
+/// /kea-ctrl-agent:config/control-sockets/
+/// socket[server-type='dhcp4'] (list instance)
+/// /kea-ctrl-agent:config/control-sockets/socket[server-type='dhcp4']/
+/// server-type = dhcp4
+/// /kea-ctrl-agent:config/control-sockets/socket[server-type='dhcp4']/
+/// control-socket (container)
+/// /kea-ctrl-agent:config/control-sockets/socket[server-type='dhcp4']/
+/// control-socket/socket-name = /tmp/kea.sock
+/// /kea-ctrl-agent:config/control-sockets/socket[server-type='dhcp4']/
+/// control-socket/socket-type = unix
+/// /kea-ctrl-agent:config/control-sockets/socket[server-type='dhcp4']/
+/// control-socket/user-context = { \"foo\": 1 }
+/// @endcode
+
+/// @brief A translator class for converting a control socket between
+/// YANG and JSON.
+///
+/// Supports the following models:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+/// - kea-dhcp-ddns
+/// - kea-ctrl-agent
+class TranslatorControlSocket : virtual public Translator {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorControlSocket(sysrepo::Session session,
+ const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorControlSocket() = default;
+
+ /// @brief Translate a control socket from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the control socket
+ ///
+ /// @return the JSON representation of the control socket
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getControlSocket(libyang::DataNode const& data_node);
+
+ /// @brief Translate a control socket from YANG to JSON.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ /// Use getControlSocket(libyang::DataNode) as a scalable alternative.
+ ///
+ /// @param xpath The xpath of the control socket.
+ ///
+ /// @return JSON representation of the control socket
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getControlSocketFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set control socket from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the control socket.
+ /// @param elem The JSON element.
+ void setControlSocket(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getControlSocket JSON for kea models.
+ ///
+ /// @param data_node the YANG node representing the control socket
+ /// @return JSON representation of the control socket.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getControlSocketKea(libyang::DataNode const& data_node);
+
+ /// @brief setControlSocket for kea models.
+ ///
+ /// Null elem argument removes the container.
+ /// Required parameters passed in elem are: socket-name, socket-type.
+ /// Optional parameters are: user-context.
+ ///
+ /// @param xpath The xpath of the control socket.
+ /// @param elem The JSON element.
+ /// @throw BadValue on control socket without socket type or name.
+ void setControlSocketKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorControlSocket
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_CONTROL_SOCKET_H
diff --git a/src/lib/yang/translator_database.cc b/src/lib/yang/translator_database.cc
new file mode 100644
index 0000000..e92f2ff
--- /dev/null
+++ b/src/lib/yang/translator_database.cc
@@ -0,0 +1,213 @@
+// Copyright (C) 2018-2023 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 <yang/translator_database.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorDatabase::TranslatorDatabase(Session session, const string& model)
+ : Translator(session, model) {
+}
+
+ElementPtr
+TranslatorDatabase::getDatabase(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getDatabaseKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting database access: " << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getDatabase not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorDatabase::getDatabaseFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getDatabase(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorDatabase::getDatabaseKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryDivergingLeaf(result, data_node, "type", "database-type");
+
+ checkAndGetLeaf(result, data_node, "cert-file");
+ checkAndGetLeaf(result, data_node, "cipher-list");
+ checkAndGetLeaf(result, data_node, "connect-timeout");
+ checkAndGetLeaf(result, data_node, "host");
+ checkAndGetLeaf(result, data_node, "key-file");
+ checkAndGetLeaf(result, data_node, "lfc-interval");
+ checkAndGetLeaf(result, data_node, "max-reconnect-tries");
+ checkAndGetLeaf(result, data_node, "max-row-errors");
+ checkAndGetLeaf(result, data_node, "name");
+ checkAndGetLeaf(result, data_node, "on-fail");
+ checkAndGetLeaf(result, data_node, "password");
+ checkAndGetLeaf(result, data_node, "persist");
+ checkAndGetLeaf(result, data_node, "port");
+ checkAndGetLeaf(result, data_node, "read-timeout");
+ checkAndGetLeaf(result, data_node, "readonly");
+ checkAndGetLeaf(result, data_node, "reconnect-wait-time");
+ checkAndGetLeaf(result, data_node, "tcp-user-timeout");
+ checkAndGetLeaf(result, data_node, "trust-anchor");
+ checkAndGetLeaf(result, data_node, "user");
+ checkAndGetLeaf(result, data_node, "write-timeout");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorDatabase::setDatabase(string const& xpath,
+ ConstElementPtr elem,
+ bool skip) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setDatabaseKea(xpath, elem, skip);
+ } else {
+ isc_throw(NotImplemented,
+ "setDatabase not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting database access '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorDatabase::setDatabaseKea(string const& xpath,
+ ConstElementPtr elem,
+ bool skip) {
+ if (!elem) {
+ deleteItem(xpath);
+ return;
+ }
+
+ checkAndSetLeaf(elem, xpath, "connect-timeout", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "cert-file", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "cipher-list", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "host", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "key-file", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "lfc-interval", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "max-reconnect-tries", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "max-row-errors", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "on-fail", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "password", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "persist", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "port", LeafBaseType::Uint16);
+ checkAndSetLeaf(elem, xpath, "readonly", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "read-timeout", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "reconnect-wait-time", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "tcp-user-timeout", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "trust-anchor", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "user", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "write-timeout", LeafBaseType::Uint32);
+
+ checkAndSetUserContext(elem, xpath);
+
+ if (!skip) {
+ setMandatoryDivergingLeaf(elem, xpath, "type", "database-type", LeafBaseType::String);
+ }
+}
+
+TranslatorDatabases::TranslatorDatabases(Session session,
+ const string& model)
+ : Translator(session, model),
+ TranslatorDatabase(session, model) {
+}
+
+ElementPtr
+TranslatorDatabases::getDatabases(DataNode const& data_node,
+ string const& xpath) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getDatabasesKea(data_node, xpath));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting database accesses: " << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getDatabases not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorDatabases::getDatabasesFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getDatabases(findXPath(xpath), xpath);
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorDatabases::getDatabasesKea(DataNode const& data_node, string const& xpath) {
+ return getList<TranslatorDatabase>(data_node, xpath, *this,
+ &TranslatorDatabase::getDatabase);
+}
+
+void
+TranslatorDatabases::setDatabases(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setDatabasesKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setDatabases not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting database accesses '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorDatabases::setDatabasesKea(string const& xpath,
+ ConstElementPtr elem) {
+ if (!elem) {
+ deleteItem(xpath);
+ return;
+ }
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr database = elem->getNonConst(i);
+ if (!database->contains("type")) {
+ isc_throw(BadValue, "database without type: " << database->str());
+ }
+ string type = database->get("type")->stringValue();
+ ostringstream key;
+ key << xpath << "[database-type='" << type << "']";
+ setDatabase(key.str(), database, true);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_database.h b/src/lib/yang/translator_database.h
new file mode 100644
index 0000000..7480fa7
--- /dev/null
+++ b/src/lib/yang/translator_database.h
@@ -0,0 +1,243 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_DATABASE_H
+#define ISC_TRANSLATOR_DATABASE_H 1
+
+#include <yang/translator.h>
+
+namespace isc {
+namespace yang {
+
+/// Database access translation between YANG and JSON
+///
+/// JSON syntax for all Kea servers with database access is:
+/// @code
+/// {
+/// "type": <type>, /// required
+/// "user": <user>,
+/// "password": <password>,
+/// "host": <host>,
+/// "name": <name>,
+/// "persist": <persist flag>,
+/// "port": <port>,
+/// "lfc-interval": <lfc interval>,
+/// "readonly": <readonly flag>,
+/// "trust-anchor": <trust anchor>,
+/// "cert-file": <cert file>,
+/// "key-file": <key file>,
+/// "cipher-list": <cipher list>,
+/// "connect-timeout": <connect timeout>,
+/// "max-reconnect-tries": <maximum reconnect tries>,
+/// "reconnect-wait-time": <reconnect wait time>,
+/// "max-row-errors": <maximum row errors>,
+/// "user-context": { <json map> },
+/// "comment": <comment>
+/// }
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46] is using database-type as the list key:
+/// @code
+/// +--rw database
+/// +--rw database-type string
+/// +--rw user? string
+/// +--rw password? string
+/// +--rw host? string
+/// +--rw name? string
+/// +--rw persist? boolean
+/// +--rw port? uint16
+/// +--rw lfc-interval? uint32
+/// +--rw readonly? boolean
+/// +--rw trust-anchor? string
+/// +--rw cert-file? string
+/// +--rw key-file? string
+/// +--rw cipher-list? string
+/// +--rw connect-timeout? uint32
+/// +--rw max-reconnect-tries? uint32
+/// +--rw reconnect-wait-time? uint32
+/// +--rw max-row-errors? uint32
+/// +--rw on-fail? string
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "type": "mysql",
+/// "name": "kea",
+/// "user": "kea",
+/// "password": "kea",
+/// "host": "localhost",
+/// "port": 3306
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp6-server:config (container)
+/// /kea-dhcp6-server:config/
+/// hosts-database[database-type='mysql'] (list instance)
+/// /kea-dhcp6-server:config/
+/// hosts-database[database-type='mysql']/type = mysql
+/// /kea-dhcp6-server:config/
+/// hosts-database[database-type='mysql']/name = kea
+/// /kea-dhcp6-server:config/
+/// hosts-database[database-type='mysql']/user = kea
+/// /kea-dhcp6-server:config/
+/// hosts-database[database-type='mysql']/password = kea
+/// /kea-dhcp6-server:config/
+/// hosts-database[database-type='mysql']/host = localhost
+/// /kea-dhcp6-server:config/
+/// hosts-database[database-type='mysql']/port = 3306
+/// @endcode
+
+/// @brief A translator class for converting a database access parameters
+/// between YANG and JSON.
+///
+/// Supports the following models:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+class TranslatorDatabase : virtual public Translator {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorDatabase(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorDatabase() = default;
+
+ /// @brief Translate a database access from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the control socket
+ ///
+ /// @return the JSON representation of the database
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getDatabase(libyang::DataNode const& data_node);
+
+ /// @brief Translate a database access from YANG to JSON.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ /// Use getDatabase(libyang::DataNode) as a scalable alternative.
+ ///
+ /// @param xpath The xpath of the database.
+ ///
+ /// @return JSON representation of the database.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getDatabaseFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set database access from JSON to YANG.
+ ///
+ /// Null elem argument removes the database entry.
+ ///
+ /// @param xpath The xpath of the database access.
+ /// @param elem The JSON element.
+ /// @param skip The skip type field flag.
+ void setDatabase(const std::string& xpath,
+ isc::data::ConstElementPtr elem,
+ bool skip = false);
+
+protected:
+ /// @brief getDatabase JSON for kea-dhcp[46]-server models.
+ ///
+ /// @param data_node the YANG node representing the database configuration
+ ///
+ /// @return JSON representation of the database
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getDatabaseKea(libyang::DataNode const& data_node);
+
+ /// @brief setDatabase for kea-dhcp[46]-server models.
+ ///
+ /// @param xpath The xpath of the database access.
+ /// @param elem The JSON element.
+ /// @param skip The skip type field flag.
+ /// @throw BadValue on database without type,
+ void setDatabaseKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem,
+ bool skip);
+}; // TranslatorDatabase
+
+/// @brief A translator class for converting a database access list between
+/// YANG and JSON.
+///
+/// Supports kea-dhcp[46]-server, does not exist in ietf-dhcpv6-server.
+class TranslatorDatabases : virtual public TranslatorDatabase {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorDatabases(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorDatabases() = default;
+
+ /// @brief Translate database accesses from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the databases
+ /// @param xpath the xpath of databases relative to {data_node}
+ ///
+ /// @return the JSON representation of the list of databases
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getDatabases(libyang::DataNode const& data_node,
+ std::string const& xpath);
+
+ /// @brief Translate database accesses from YANG to JSON.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ /// Use getDatabases(libyang::DataNode, std::string) as a scalable alternative.
+ ///
+ /// @param xpath The xpath of databases including the list name.
+ ///
+ /// @return JSON representation of databases.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getDatabasesFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set database accesses from JSON to YANG.
+ ///
+ /// Null elem argument removes the database list.
+ ///
+ /// @param xpath The xpath of databases including the list name.
+ /// @param elem The JSON element.
+ void setDatabases(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getDatabases JSON for kea-dhcp[46]-server models.
+ ///
+ /// @param data_node the YANG node representing the databases
+ /// @param xpath the xpath of databases relative to {data_node}
+ ///
+ /// @return JSON representation of databases.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getDatabasesKea(libyang::DataNode const& data_node,
+ std::string const& xpath);
+
+ /// @brief setDatabases for kea-dhcp[46]-server models.
+ ///
+ /// @param xpath The xpath of databases including the list name.
+ /// @param elem The JSON element.
+ ///
+ /// @throw BadValue on database without type,
+ void setDatabasesKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorDatabases
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_DATABASE_H
diff --git a/src/lib/yang/translator_host.cc b/src/lib/yang/translator_host.cc
new file mode 100644
index 0000000..7aa6fa4
--- /dev/null
+++ b/src/lib/yang/translator_host.cc
@@ -0,0 +1,215 @@
+// Copyright (C) 2018-2022 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 <yang/translator_host.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorHost::TranslatorHost(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model) {
+}
+
+ElementPtr
+TranslatorHost::getHost(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getHostKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting host reservation:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getHost not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorHost::getHostFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getHost(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorHost::getHostKea(DataNode const& data_node) {
+ ConstElementPtr id_type = getItem(data_node, "identifier-type");
+ ConstElementPtr id = getItem(data_node, "identifier");
+ if (!id_type || !id) {
+ isc_throw(MissingNode, "getHostKea requires both identifier and identifier-type");
+ }
+ ElementPtr result = Element::createMap();
+ result->set(id_type->stringValue(), id);
+
+ checkAndGetLeaf(result, data_node, "client-classes");
+ checkAndGetLeaf(result, data_node, "hostname");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ ConstElementPtr options = getOptionDataList(data_node);
+ if (options) {
+ result->set("option-data", options);
+ }
+
+ if (model_ == KEA_DHCP4_SERVER) {
+ checkAndGetLeaf(result, data_node, "boot-file-name");
+ checkAndGetLeaf(result, data_node, "ip-address");
+ checkAndGetLeaf(result, data_node, "next-server");
+ checkAndGetLeaf(result, data_node, "server-hostname");
+ } else {
+ checkAndGetLeaf(result, data_node, "ip-addresses");
+ checkAndGetLeaf(result, data_node, "prefixes");
+ }
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorHost::setHost(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setHostKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setHost not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting host reservation '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorHost::setHostKea(string const& xpath, ConstElementPtr elem) {
+ // Skip keys "identifier" and "identifier-type".
+
+ checkAndSetLeaf(elem, xpath, "hostname", LeafBaseType::String);
+
+ checkAndSetLeafList(elem, xpath, "client-classes", LeafBaseType::String);
+
+ ConstElementPtr options = elem->get("option-data");
+ if (options && !options->empty()) {
+ setOptionDataList(xpath, options);
+ }
+
+ if (model_ == KEA_DHCP4_SERVER) {
+ checkAndSetLeaf(elem, xpath, "boot-file-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ip-address", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "next-server", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "server-hostname", LeafBaseType::String);
+ } else {
+ checkAndSetLeafList(elem, xpath, "ip-addresses", LeafBaseType::String);
+ checkAndSetLeafList(elem, xpath, "prefixes", LeafBaseType::String);
+ }
+
+ // User context is supported in both kea-dhcp4-server and kea-dhcp6-server.
+ checkAndSetUserContext(elem, xpath);
+}
+
+TranslatorHosts::TranslatorHosts(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorHost(session, model) {
+}
+
+ElementPtr
+TranslatorHosts::getHosts(DataNode const& data_node) {
+ return getList<TranslatorHost>(data_node, "host", *this,
+ &TranslatorHost::getHost);
+}
+
+ElementPtr
+TranslatorHosts::getHostsFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getHosts(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+void
+TranslatorHosts::setHosts(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setHostsKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setHosts not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting host reservations '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorHosts::setHostsKea(string const& xpath, ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ string id_type = "unknown";
+ ElementPtr host = elem->getNonConst(i);
+ ConstElementPtr id = host->get("hw-address");
+ if (id) {
+ id_type = "hw-address";
+ goto found;
+ }
+ id = host->get("duid");
+ if (id) {
+ id_type = "duid";
+ goto found;
+ }
+ if (model_ == KEA_DHCP4_SERVER) {
+ id = host->get("circuit-id");
+ if (id) {
+ id_type = "circuit-id";
+ goto found;
+ }
+ id = host->get("client-id");
+ if (id) {
+ id_type = "client-id";
+ goto found;
+ }
+ }
+ id = host->get("flex-id");
+ if (id) {
+ id_type = "flex-id";
+ goto found;
+ }
+
+ found:
+ if (id_type == "unknown") {
+ isc_throw(BadValue, "getHosts: can't find the identifier type in "
+ << host->str());
+ }
+ ostringstream key;
+ key << xpath << "/host[identifier-type='" << id_type
+ << "'][identifier='" << id->stringValue() << "']";
+ setHost(key.str(), host);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_host.h b/src/lib/yang/translator_host.h
new file mode 100644
index 0000000..2236b14
--- /dev/null
+++ b/src/lib/yang/translator_host.h
@@ -0,0 +1,229 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_HOST_H
+#define ISC_TRANSLATOR_HOST_H 1
+
+#include <yang/translator_option_data.h>
+
+namespace isc {
+namespace yang {
+
+/// Translation between YANG and JSON for a single host reservation.
+///
+/// JSON syntax for kea-dhcp4 is:
+/// @code
+/// {
+/// "hw-address": <hardware address>,
+/// "duid": <duid>,
+/// "circuit-id": <circuit id>,
+/// "client-id": <client id>,
+/// "flex-id": <flex id>,
+/// "ip-address": <ipv4 reserved address>,
+/// "hostname": <hostname>,
+/// "next-server": "<next server>",
+/// "server-hostname": "<server hostname>",
+/// "boot-file-name": "<boot file name>",
+/// "client-classes": "<client class names>",
+/// "option-data": [ <list of option data> ],
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// JSON syntax for kea-dhcp6 is:
+/// @code
+/// {
+/// "hw-address": <hardware address>,
+/// "duid": <duid>,
+/// "flex-id": <flex id>,
+/// "ip-addresses": <ipv6 reserved addresses>,
+/// "prefixes": <ipv6 reserved prefixes>,
+/// "hostname": <hostname>,
+/// "client-classes": "<client class names>",
+/// "option-data": [ <list of option data> ],
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46] is with identifier-type and identifier
+/// as the list keys:
+/// @code
+/// +--rw identifier-type host-identifier-type
+/// +--rw identifier string
+/// +--rw hostname? string
+/// +--rw client-classes* string
+/// +--rw option-data* [code space]
+/// +--rw user-context? user-context
+///
+/// DHCPv4 only:
+/// +--rw ip-address? inet:ipv4-address
+/// +--rw next-server? inet:ipv4-address
+/// +--rw server-hostname? string
+/// +--rw boot-file-name? string
+///
+/// DHCPv6 only:
+/// +--rw ip-addresses* inet:ipv6-address
+/// +--rw prefixes* inet:ipv6-prefix
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "flex-id": "00:ff",
+/// "ip-address": "10.0.0.1",
+/// "hostname": "foo"
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp4-server:config (container)
+/// /kea-dhcp4-server:config/subnet4[id='111'] (list instance)
+/// /kea-dhcp4-server:config/subnet4[id='111']/id = 111
+/// /kea-dhcp4-server:config/subnet4[id='111']/subnet = 10.0.0.0/24
+/// /kea-dhcp4-server:config/subnet4[id='111']/
+/// host[identifier-type='flex-id'][identifier='00:ff'] (list instance)
+/// /kea-dhcp4-server:config/subnet4[id='111']/
+/// host[identifier-type='flex-id'][identifier='00:ff']/
+/// identifier-type = flex-id
+/// /kea-dhcp4-server:config/subnet4[id='111']/
+/// host[identifier-type='flex-id'][identifier='00:ff']/
+/// identifier = 00:ff
+/// /kea-dhcp4-server:config/subnet4[id='111']/
+/// host[identifier-type='flex-id'][identifier='00:ff']/
+/// hostname = foo
+/// /kea-dhcp4-server:config/subnet4[id='111']/
+/// host[identifier-type='flex-id'][identifier='00:ff']/
+/// ip-address = 10.0.0.1
+/// @endcode
+
+/// @brief A translator class for converting a host reservation between
+/// YANG and JSON.
+///
+/// Currently supported models are:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+///
+/// ietf-dhcpv6-server is not supported yet.
+class TranslatorHost : virtual public TranslatorOptionDataList {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorHost(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorHost() = default;
+
+ /// @brief Translate a host reservation from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the host reservation
+ ///
+ /// @return the JSON representation of the host reservation
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getHost(libyang::DataNode const& data_node);
+
+ /// @brief Translate a host reservation from YANG to JSON.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ /// Use getHost(libyang::DataNode) as a scalable alternative.
+ ///
+ /// @param xpath The xpath of the host reservation.
+ ///
+ /// @return JSON representation of the host reservation.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getHostFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set host reservation from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the host reservation.
+ /// @param elem The JSON element.
+ void setHost(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getHost for kea-dhcp[46]-server models.
+ ///
+ /// @param data_node the YANG node representing the host reservation
+ ///
+ /// @return JSON representation of the host reservation.
+ isc::data::ElementPtr getHostKea(libyang::DataNode const& data_node);
+
+ /// @brief setHost for kea-dhcp[46]-server models.
+ ///
+ /// @param xpath The xpath of the host reservation.
+ /// @param elem The JSON element.
+ void setHostKea(const std::string& xpath, isc::data::ConstElementPtr elem);
+}; // TranslatorHost
+
+/// @brief A translator class for converting host reservations list between
+/// YANG and JSON.
+///
+/// Currently supports the following models:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+///
+/// The ietf-dhcpv6-server model is not yet supported.
+class TranslatorHosts : virtual public TranslatorHost {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorHosts(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorHosts() = default;
+
+ /// @brief Translate host reservations from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of host reservations
+ ///
+ /// @return the JSON representation of the list of host reservations
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getHosts(libyang::DataNode const& data_node);
+
+ /// @brief Translate host reservations from YANG to JSON.
+ ///
+ /// @note This is a computationally expensive operation that makes a lookup in the sysrepo
+ /// datastore by calling Session::getData(). It should be used sparingly in production code,
+ /// mainly to get an initial data node to work with. It may be used at will in unit tests.
+ /// Use getHosts(libyang::DataNode) as a scalable alternative.
+ ///
+ /// @param xpath The xpath of the host reservation list.
+ ///
+ /// @return JSON representation of the host reservation list.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getHostsFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set (address) host reservations from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the host reservation list.
+ /// @param elem The JSON element.
+ void setHosts(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief setHosts for kea-dhcp[46].
+ ///
+ /// @param xpath The xpath of the host reservation list.
+ /// @param elem The JSON element.
+ /// @throw BadValue on host reservation without known identifier type.
+ void setHostsKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorHosts
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_HOST_H
diff --git a/src/lib/yang/translator_logger.cc b/src/lib/yang/translator_logger.cc
new file mode 100644
index 0000000..bff43db
--- /dev/null
+++ b/src/lib/yang/translator_logger.cc
@@ -0,0 +1,212 @@
+// Copyright (C) 2018-2022 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 <yang/translator_logger.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorLogger::TranslatorLogger(Session session, const string& model)
+ : Translator(session, model) {
+}
+
+ElementPtr
+TranslatorLogger::getLogger(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER) ||
+ (model_ == KEA_DHCP_DDNS) ||
+ (model_ == KEA_CTRL_AGENT)) {
+ return (getLoggerKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting logger: " << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getLogger not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorLogger::getLoggerKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryLeaf(result, data_node, "name");
+
+ checkAndGetLeaf(result, data_node, "debuglevel");
+ checkAndGetLeaf(result, data_node, "severity");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ ConstElementPtr options = getOutputOptions(data_node);
+ if (options) {
+ result->set("output_options", options);
+ }
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorLogger::getOutputOption(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryLeaf(result, data_node, "output");
+
+ checkAndGetLeaf(result, data_node, "flush");
+ checkAndGetLeaf(result, data_node, "maxsize");
+ checkAndGetLeaf(result, data_node, "maxver");
+ checkAndGetLeaf(result, data_node, "pattern");
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorLogger::getOutputOptions(DataNode const& data_node) {
+ return getList(data_node, "output-option", *this,
+ &TranslatorLogger::getOutputOption);
+}
+
+void
+TranslatorLogger::setLogger(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER) ||
+ (model_ == KEA_DHCP_DDNS) ||
+ (model_ == KEA_CTRL_AGENT)) {
+ setLoggerKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setLogger not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting logger '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorLogger::setLoggerKea(string const& xpath, ConstElementPtr elem) {
+ // Skip key "name".
+
+ checkAndSetLeaf(elem, xpath, "debuglevel", LeafBaseType::Uint8);
+ checkAndSetLeaf(elem, xpath, "severity", LeafBaseType::Enum);
+ checkAndSetUserContext(elem, xpath);
+
+ ConstElementPtr options = elem->get("output_options");
+ if (options && !options->empty()) {
+ setOutputOptions(xpath, options);
+ }
+}
+
+void
+TranslatorLogger::setOutputOption(string const& xpath, ConstElementPtr elem) {
+ // Keys are set by setting the list itself.
+ setItem(xpath, ElementPtr(), LeafBaseType::Unknown);
+
+ checkAndSetLeaf(elem, xpath, "flush", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "maxsize", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "maxver", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "pattern", LeafBaseType::String);
+}
+
+void
+TranslatorLogger::setOutputOptions(string const& xpath, ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr option = elem->getNonConst(i);
+ if (!option->contains("output")) {
+ isc_throw(BadValue, "output-option without output: "
+ << option->str());
+ }
+ string output = option->get("output")->stringValue();
+ ostringstream key;
+ key << xpath << "/output-option[output='" << output << "']";
+ setOutputOption(key.str(), option);
+ }
+}
+
+TranslatorLoggers::TranslatorLoggers(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorLogger(session, model) {
+}
+
+ConstElementPtr
+TranslatorLoggers::getLoggers(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER) ||
+ (model_ == KEA_DHCP_DDNS) ||
+ (model_ == KEA_CTRL_AGENT)) {
+ return (getLoggersKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting loggers: " << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getLoggers not implemented for the model: " << model_);
+}
+
+ConstElementPtr
+TranslatorLoggers::getLoggersFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getLoggers(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorLoggers::getLoggersKea(DataNode const& data_node) {
+ return getList<TranslatorLogger>(data_node, "logger", *this,
+ &TranslatorLogger::getLogger);
+}
+
+void
+TranslatorLoggers::setLoggers(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER) ||
+ (model_ == KEA_DHCP_DDNS) ||
+ (model_ == KEA_CTRL_AGENT)) {
+ setLoggersKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setLoggers not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting loggers '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorLoggers::setLoggersKea(string const& xpath, ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr logger = elem->getNonConst(i);
+ if (!logger->contains("name")) {
+ isc_throw(BadValue, "logger without name: " << logger->str());
+ }
+ string name = logger->get("name")->stringValue();
+ ostringstream key;
+ key << xpath << "/logger[name='" << name << "']";
+ setLogger(key.str(), logger);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_logger.h b/src/lib/yang/translator_logger.h
new file mode 100644
index 0000000..6d54dc4
--- /dev/null
+++ b/src/lib/yang/translator_logger.h
@@ -0,0 +1,233 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_LOGGER_H
+#define ISC_TRANSLATOR_LOGGER_H 1
+
+#include <yang/translator.h>
+
+namespace isc {
+namespace yang {
+
+/// Logger translation between YANG and JSON
+///
+/// JSON syntax for all Kea servers with loggers is:
+/// @code
+/// {
+/// "name": <name>,
+/// "output_options": [ <output options> ],
+/// "severity": <severity>,
+/// "debuglevel": <debug level>,
+/// "user-context": { <json map> },
+/// "comment": <comment>
+/// }
+/// @endcode
+///
+/// JSON syntax for all Kea server for output options is:
+/// @code
+/// {
+/// "output": <output, e.g. log file name>,
+/// "maxver": <maximum file version>,
+/// "maxsize": <maximum file size>,
+/// "flush": <flush flag>,
+/// "pattern": <custom layout>
+/// }
+/// @endcode
+///
+/// YANG syntax for loggers is:
+/// @code
+/// +--rw logger* [name]
+/// +--rw name string
+/// +--rw output-option* [output]
+/// | +--rw output string
+/// | +--rw flush? boolean
+/// | +--rw maxsize? uint32
+/// | +--rw maxver? uint32
+/// | +--rw pattern? string
+/// +--rw debuglevel? uint8
+/// +--rw severity? enumeration
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "name": "foo",
+/// "severity": "WARN",
+/// "output_options":
+/// [
+/// {
+/// "output": "/bar",
+/// "maxver": 10
+/// }
+/// ]
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp4-server:config (container)
+/// /kea-dhcp4-server:config/...
+/// /kea-dhcp4-server:config/logger[name='foo'] (list instance)
+/// /kea-dhcp4-server:config/logger[name='foo']/name = foo
+/// /kea-dhcp4-server:config/logger[name='foo']/
+/// option[output='/bar'] (list instance)
+/// /kea-dhcp4-server:config/logger[name='foo']/
+/// option[output='/bar']/option = /bar
+/// /kea-dhcp4-server:config/logger[name='foo']/
+/// option[output='/bar']/maxver = 10
+/// /kea-dhcp4-server:config/logger[name='foo']/severity = WARN
+/// @endcode
+
+/// @brief A translator class for converting a logger between
+/// YANG and JSON.
+///
+/// Currently supports all kea models:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+/// - kea-dhcp-ddns
+/// - kea-ctrl-agent
+class TranslatorLogger : virtual public Translator {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorLogger(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorLogger() = default;
+
+ /// @brief Translate a logger from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the logger configuration
+ ///
+ /// @return JSON representation of the logger.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getLogger(libyang::DataNode const& data_node);
+
+ /// @brief Translate and set logger from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the logger.
+ /// @param elem The JSON element.
+ void setLogger(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief Translate an output option from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the output option
+ ///
+ /// @return JSON representation of the output option.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getOutputOption(libyang::DataNode const& data_node);
+
+ /// @brief Translate output options from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing output options
+ ///
+ /// @return JSON representation of output options.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getOutputOptions(libyang::DataNode const& data_node);
+
+ /// @brief Translate and set an output option from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the output option.
+ /// @param elem The JSON element.
+ void setOutputOption(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief Translate and set output options from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the output options.
+ /// @param elem The JSON element.
+ /// @throw BadValue on an output option without output.
+ void setOutputOptions(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief getLogger JSON for loggers.
+ ///
+ /// @param data_node the YANG node representing the logger configuration
+ ///
+ /// @return JSON representation of the logger.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getLoggerKea(libyang::DataNode const& data_node);
+
+ /// @brief setLogger for loggers.
+ ///
+ /// @param xpath The xpath of the logger.
+ /// @param elem The JSON element.
+ void setLoggerKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorLogger
+
+/// @brief A translator class for converting a logger list between
+/// YANG and JSON.
+///
+/// Currently supports all kea servers and agents. Specific to Kea.
+class TranslatorLoggers : virtual public TranslatorLogger {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorLoggers(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorLoggers() = default;
+
+ /// @brief Translate loggers from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of loggers
+ ///
+ /// @return the JSON representation of the list of loggers
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getLoggers(libyang::DataNode const& data_node);
+
+ /// @brief Translate loggers from YANG to JSON.
+ ///
+ /// @param xpath The xpath of loggers.
+ ///
+ /// @return JSON representation of loggers.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getLoggersFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set loggers from JSON to YANG.
+ ///
+ /// @param xpath The xpath of loggers.
+ ///
+ /// @param elem The JSON element.
+ void setLoggers(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getLoggers JSON for loggers.
+ ///
+ /// @param data_node the YANG node representing loggers configuration
+ ///
+ /// @return JSON representation of loggers.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getLoggersKea(libyang::DataNode const& data_node);
+
+ /// @brief setLoggers for loggers.
+ ///
+ /// @param xpath The xpath of loggers.
+ /// @param elem The JSON element.
+ /// @throw BadValue on a logger without name.
+ void setLoggersKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorLoggers
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_LOGGER_H
diff --git a/src/lib/yang/translator_option_data.cc b/src/lib/yang/translator_option_data.cc
new file mode 100644
index 0000000..5826869
--- /dev/null
+++ b/src/lib/yang/translator_option_data.cc
@@ -0,0 +1,180 @@
+// Copyright (C) 2018-2023 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 <yang/translator_option_data.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorOptionData::TranslatorOptionData(Session session,
+ const string& model)
+ : Translator(session, model) {
+}
+
+ElementPtr
+TranslatorOptionData::getOptionData(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getOptionDataKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting option data:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getOptionData not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorOptionData::getOptionDataFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getOptionData(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorOptionData::getOptionDataKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryLeaf(result, data_node, "code");
+ getMandatoryLeaf(result, data_node, "space");
+
+ checkAndGetLeaf(result, data_node, "always-send");
+ checkAndGetLeaf(result, data_node, "csv-format");
+ checkAndGetLeaf(result, data_node, "data");
+ checkAndGetLeaf(result, data_node, "name");
+ checkAndGetLeaf(result, data_node, "never-send");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorOptionData::setOptionData(string const& xpath,
+ ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setOptionDataKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setOptionData not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting option data '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorOptionData::setOptionDataKea(string const& xpath,
+ ConstElementPtr elem) {
+ // Skip keys "code" and "space".
+
+ checkAndSetLeaf(elem, xpath, "always-send", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "csv-format", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "data", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "never-send", LeafBaseType::Bool);
+
+ checkAndSetUserContext(elem, xpath);
+}
+
+TranslatorOptionDataList::TranslatorOptionDataList(Session session,
+ const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model) {
+}
+
+ConstElementPtr
+TranslatorOptionDataList::getOptionDataList(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getOptionDataListKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting option data list:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getOptionDataList not implemented for the model: " << model_);
+}
+
+ConstElementPtr
+TranslatorOptionDataList::getOptionDataListFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getOptionDataList(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ConstElementPtr
+TranslatorOptionDataList::getOptionDataListKea(DataNode const& data_node) {
+ return getList<TranslatorOptionData>(data_node, "option-data", *this,
+ &TranslatorOptionData::getOptionData);
+}
+
+void
+TranslatorOptionDataList::setOptionDataList(string const& xpath,
+ ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setOptionDataListKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setOptionDataList not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting option data list '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorOptionDataList::setOptionDataListKea(string const& xpath,
+ ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr option = elem->getNonConst(i);
+ if (!option->contains("code")) {
+ isc_throw(BadValue, "option data without code: " << option->str());
+ }
+ unsigned code = static_cast<unsigned>(option->get("code")->intValue());
+ if (!option->contains("space")) {
+ isc_throw(BadValue, "option data without space: " <<option->str());
+ }
+ string space = option->get("space")->stringValue();
+ ostringstream keys;
+ keys << xpath << "/option-data[code='" << code
+ << "'][space='" << space << "']";
+ setOptionData(keys.str(), option);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_option_data.h b/src/lib/yang/translator_option_data.h
new file mode 100644
index 0000000..d8d4f60
--- /dev/null
+++ b/src/lib/yang/translator_option_data.h
@@ -0,0 +1,195 @@
+// Copyright (C) 2018-2023 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 ISC_TRANSLATOR_OPTION_DATA_H
+#define ISC_TRANSLATOR_OPTION_DATA_H 1
+
+#include <yang/translator.h>
+
+namespace isc {
+namespace yang {
+
+/// Option data translation between YANG and JSON
+///
+/// JSON syntax for Kea DHCP with command channel is:
+/// @code
+/// {
+/// "code": <code>,
+/// "name": <name>,
+/// "space": <space>,
+/// "csv-format": <csv format flag>,
+/// "data": <value>,
+/// "always-send": <always send flag>,
+/// "never-send": <never send flag>,
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46] with code and space as keys is:
+/// @code
+/// +--rw option-data* [code space]
+/// +--rw code uint8
+/// +--rw space string
+/// +--rw name? string
+/// +--rw data? string
+/// +--rw csv-format? boolean
+/// +--rw always-send? boolean
+/// +--rw never-send? boolean
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "code": 100,
+/// "space": "dns",
+/// "csv-format": false,
+/// "data": "12121212",
+/// "always-send": false,
+/// "never-send": false
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp6-server:config (container)
+/// /kea-dhcp6-server:config/
+/// option-data[code='100'][space='dns'] (list instance)
+/// /kea-dhcp6-server:config/
+/// option-data[code='100'][space='dns']/code = 100
+/// /kea-dhcp6-server:config/
+/// option-data[code='100'][space='dns']/space = dns
+/// /kea-dhcp6-server:config/
+/// option-data[code='100'][space='dns']/data = 12121212
+/// /kea-dhcp6-server:config/
+/// option-data[code='100'][space='dns']/csv-format = false
+/// /kea-dhcp6-server:config/
+/// option-data[code='100'][space='dns']/always-send = false
+/// /kea-dhcp6-server:config/
+/// option-data[code='100'][space='dns']/never-send = false
+/// @endcode
+
+/// @brief A translator class for converting an option data between
+/// YANG and JSON.
+///
+/// Currently supports on kea-dhcp[46]-server, not yet ietf-dhcpv6-server.
+class TranslatorOptionData : virtual public Translator {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorOptionData(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorOptionData() = default;
+
+ /// @brief Translate an option data from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the option data
+ ///
+ /// @return the JSON representation of the option data
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getOptionData(libyang::DataNode const& data_node);
+
+ /// @brief Translate an option data from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the option data.
+ ///
+ /// @return JSON representation of the option data.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getOptionDataFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set option data from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the option data.
+ /// @param elem The JSON element.
+ void setOptionData(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getOptionData JSON for kea-dhcp[46].
+ ///
+ /// @param data_node the YANG node representing the option data
+ ///
+ /// @return JSON representation of the option data.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getOptionDataKea(libyang::DataNode const& data_node);
+
+ /// @brief setOptionData for kea-dhcp[46].
+ ///
+ /// @param xpath The xpath of the option data.
+ /// @param elem The JSON element.
+ void setOptionDataKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorOptionData
+
+/// @brief A translator class for converting an option data list between
+/// YANG and JSON.
+///
+/// Currently supports on kea-dhcp[46]-server, not yet ietf-dhcpv6-server.
+class TranslatorOptionDataList : virtual public TranslatorOptionData {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorOptionDataList(sysrepo::Session session,
+ const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorOptionDataList() = default;
+
+ /// @brief Translate option data list from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of option data
+ ///
+ /// @return the JSON representation of the list of option data
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getOptionDataList(libyang::DataNode const& data_node);
+
+ /// @brief Translate option data list from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the option data list.
+ ///
+ /// @return the JSON representation of the list of option data
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getOptionDataListFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set option data list from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the option data list.
+ /// @param elem The JSON element.
+ void setOptionDataList(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getOptionDataList for kea-dhcp[46].
+ ///
+ /// @param data_node the YANG node representing the list of option data
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getOptionDataListKea(libyang::DataNode const& data_node);
+
+ /// @brief setOptionDataList for kea-dhcp[46].
+ ///
+ /// @param xpath The xpath of the option data list.
+ /// @param elem The JSON element.
+ /// @throw BadValue on option data without code or space.
+ void setOptionDataListKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorOptionDataList
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_OPTION_DATA_H
diff --git a/src/lib/yang/translator_option_def.cc b/src/lib/yang/translator_option_def.cc
new file mode 100644
index 0000000..70bdd9f
--- /dev/null
+++ b/src/lib/yang/translator_option_def.cc
@@ -0,0 +1,182 @@
+// Copyright (C) 2018-2022 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 <yang/translator_option_def.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorOptionDef::TranslatorOptionDef(Session session,
+ const string& model)
+ : Translator(session, model) {
+}
+
+ElementPtr
+TranslatorOptionDef::getOptionDef(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getOptionDefKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting option definition:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getOptionDef not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorOptionDef::getOptionDefFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getOptionDef(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorOptionDef::getOptionDefKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryLeaf(result, data_node, "code");
+ getMandatoryLeaf(result, data_node, "name");
+ getMandatoryLeaf(result, data_node, "space");
+ getMandatoryLeaf(result, data_node, "type");
+
+ checkAndGetLeaf(result, data_node, "array");
+ checkAndGetLeaf(result, data_node, "encapsulate");
+ checkAndGetLeaf(result, data_node, "record-types");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorOptionDef::setOptionDef(string const& xpath, ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setOptionDefKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setOptionDef not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting option definition '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorOptionDef::setOptionDefKea(string const& xpath,
+ ConstElementPtr elem) {
+ // Skip keys "code" and "space".
+
+ setMandatoryLeaf(elem, xpath, "name", LeafBaseType::String);
+ setMandatoryLeaf(elem, xpath, "type", LeafBaseType::String);
+
+ checkAndSetLeaf(elem, xpath, "array", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "encapsulate", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "record-types", LeafBaseType::String);
+
+ checkAndSetUserContext(elem, xpath);
+}
+
+TranslatorOptionDefList::TranslatorOptionDefList(Session session,
+ const string& model)
+ : Translator(session, model),
+ TranslatorOptionDef(session, model) {
+}
+
+ConstElementPtr
+TranslatorOptionDefList::getOptionDefList(DataNode const& data_node) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getOptionDefListKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting option definition list:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getOptionDefList not implemented for the model: " << model_);
+}
+
+ConstElementPtr
+TranslatorOptionDefList::getOptionDefListFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getOptionDefList(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ConstElementPtr
+TranslatorOptionDefList::getOptionDefListKea(DataNode const& data_node) {
+ return getList<TranslatorOptionDef>(data_node, "option-def", *this,
+ &TranslatorOptionDefList::getOptionDef);
+}
+
+void
+TranslatorOptionDefList::setOptionDefList(string const& xpath,
+ ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setOptionDefListKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setOptionDefList not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting option definition list '"
+ << elem->str() << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorOptionDefList::setOptionDefListKea(string const& xpath,
+ ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr def = elem->getNonConst(i);
+ if (!def->contains("code")) {
+ isc_throw(BadValue,
+ "option definition without code: " << def->str());
+ }
+ unsigned code = static_cast<unsigned>(def->get("code")->intValue());
+ if (!def->contains("space")) {
+ isc_throw(BadValue,
+ "option definition without space: " << def->str());
+ }
+ string space = def->get("space")->stringValue();
+ ostringstream keys;
+ keys << xpath << "/option-def[code='" << code
+ << "'][space='" << space << "']";
+ setOptionDef(keys.str(), def);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_option_def.h b/src/lib/yang/translator_option_def.h
new file mode 100644
index 0000000..b36ba47
--- /dev/null
+++ b/src/lib/yang/translator_option_def.h
@@ -0,0 +1,198 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_OPTION_DEF_H
+#define ISC_TRANSLATOR_OPTION_DEF_H 1
+
+#include <yang/translator.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief Option definition translation between YANG and JSON
+///
+/// JSON syntax for Kea DHCP servers is:
+/// @code
+/// {
+/// "code": <code>,
+/// "name": <name>,
+/// "space": <space>,
+/// "type": <type>,
+/// "array": <array flag>,
+/// "encapsulate": <encapsulated space>,
+/// "record-types": <record types>,
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46] with code and space as keys is:
+/// @code
+/// +--rw option-def* [code space]
+/// +--rw code uint8
+/// +--rw space string
+/// +--rw name string
+/// +--rw type string
+/// +--rw record-types? string
+/// +--rw encapsulate? string
+/// +--rw array? boolean
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "code": 100,
+/// "name": "foo",
+/// "space": "isc",
+/// "type": "string",
+/// "array": false
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp6-server:config (container)
+/// /kea-dhcp6-server:config/
+/// option-def[code='100'][space='isc'] (list instance)
+/// /kea-dhcp6-server:config/
+/// option-def[code='100'][space='isc']/code = 100
+/// /kea-dhcp6-server:config/
+/// option-def[code='100'][space='isc']/space = isc
+/// /kea-dhcp6-server:config/
+/// option-def[code='100'][space='isc']/name = foo
+/// /kea-dhcp6-server:config/
+/// option-def[code='100'][space='isc']/type = string
+/// /kea-dhcp6-server:config/
+/// option-def[code='100'][space='isc']/array = false
+/// @endcode
+
+/// @brief A translator class for converting an option definition between
+/// YANG and JSON.
+///
+/// Currently supports kea-dhcp[46]-server models.
+/// @todo: Support for ietf-dhcpv6-server model.
+class TranslatorOptionDef : virtual public Translator {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorOptionDef(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorOptionDef() = default;
+
+ /// @brief Translate an option definition from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the option definition
+ ///
+ /// @return the JSON representation of the option definition
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getOptionDef(libyang::DataNode const& data_node);
+
+ /// @brief Translate an option definition from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the option definition.
+ ///
+ /// @return JSON representation of the option definition.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getOptionDefFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set option definition from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the option definition..
+ /// @param elem The JSON element.
+ void setOptionDef(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getOptionDef implementation specific to kea-dhcp[46]-server models.
+ ///
+ /// @param data_node the YANG node representing the option definition
+ ///
+ /// @return JSON representation of the option definition.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ /// @throw BadValue on option definition without name or type.
+ isc::data::ElementPtr getOptionDefKea(libyang::DataNode const& data_node);
+
+ /// @brief setOptionDef implementation specific to kea-dhcp[46]-server models.
+ ///
+ /// @param xpath The xpath of the option definition.
+ /// @param elem The JSON element.
+ /// @throw BadValue on option definition without name or type.
+ void setOptionDefKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorOptionDef
+
+// @brief A translator class for converting an option definition list
+// between YANG and JSON.
+//
+/// Currently supports kea-dhcp[46]-server models.
+/// @todo: Support for ietf-dhcpv6-server model.
+class TranslatorOptionDefList : virtual public TranslatorOptionDef {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorOptionDefList(sysrepo::Session session,
+ const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorOptionDefList() = default;
+
+ /// @brief Translate option definition list from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of option definitions
+ ///
+ /// @return the JSON representation of the list of option definitions
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getOptionDefList(libyang::DataNode const& data_node);
+
+ /// @brief Translate option definition list from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the option definition list.
+ ///
+ /// @return JSON representation of the list of option definitions
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getOptionDefListFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set option definition list from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the option definition list.
+ /// @param elem The JSON element.
+ void setOptionDefList(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getOptionDefList implementation specific to kea-dhcp[46]-server
+ /// models.
+ ///
+ /// @param data_node the YANG node representing the list of option definitions
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ConstElementPtr getOptionDefListKea(libyang::DataNode const& data_node);
+
+ /// @brief setOptionDefList implementation specific to kea-dhcp[46]-server
+ /// models.
+ ///
+ /// @param xpath The xpath of the option definition list.
+ /// @param elem The JSON element.
+ /// @throw BadValue on option definition without code or space.
+ void setOptionDefListKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorOptionDefList
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_OPTION_DEF_H
diff --git a/src/lib/yang/translator_pd_pool.cc b/src/lib/yang/translator_pd_pool.cc
new file mode 100644
index 0000000..d0f8880
--- /dev/null
+++ b/src/lib/yang/translator_pd_pool.cc
@@ -0,0 +1,320 @@
+// Copyright (C) 2018-2022 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 <yang/translator_pd_pool.h>
+#include <yang/yang_models.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorPdPool::TranslatorPdPool(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model) {
+}
+
+ElementPtr
+TranslatorPdPool::getPdPool(DataNode const& data_node) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ return (getPdPoolIetf6(data_node));
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ return (getPdPoolKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting pd-pool:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getPdPool not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorPdPool::getPdPoolFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getPdPool(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorPdPool::getPdPoolIetf6(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ ConstElementPtr pref = getItem(data_node, "prefix");
+ if (!pref) {
+ isc_throw(MissingNode, "getPdPoolIetf6: prefix is required");
+ }
+ const string& prefix = pref->stringValue();
+ size_t slash = prefix.find("/");
+ if (slash == string::npos) {
+ isc_throw(MissingNode,
+ "getPdPoolIetf6: no '/' in prefix '" << prefix << "'");
+ }
+ const string& address = prefix.substr(0, slash);
+ if (address.empty()) {
+ isc_throw(MissingNode,
+ "getPdPoolIetf6: malformed prefix '" << prefix << "'");
+ }
+ result->set("prefix", Element::create(address));
+
+ // Silly: the prefix length is specified twice...
+ getMandatoryDivergingLeaf(result, data_node, "prefix-len", "prefix-length");
+
+ checkAndGetLeaf(result, data_node, "preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "client-class");
+ checkAndGetLeaf(result, data_node, "valid-lifetime");
+
+ checkAndGetDivergingLeaf(result, data_node, "rebind-timer", "rebind-time");
+ checkAndGetDivergingLeaf(result, data_node, "renew-timer", "renew-time");
+
+ // no require-client-classes nor user-context.
+ // Skip max-pd-space-utilization.
+ // Skip rapid-commit.
+ // @todo: option-data
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorPdPool::getPdPoolKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+ ConstElementPtr pref = getItem(data_node, "prefix");
+ if (!pref) {
+ isc_throw(MissingNode, "getPdPoolKea: no prefix defined");
+ }
+ const string& prefix = pref->stringValue();
+ size_t slash = prefix.find("/");
+ if (slash == string::npos) {
+ isc_throw(BadValue,
+ "getPdPoolKea: no '/' in prefix '" << prefix << "'");
+ }
+ const string& address = prefix.substr(0, slash);
+ const string& length = prefix.substr(slash + 1, string::npos);
+ if (address.empty() || length.empty()) {
+ isc_throw(BadValue,
+ "getPdPoolKea: malformed prefix '" << prefix << "'");
+ }
+ result->set("prefix", Element::create(address));
+ try {
+ unsigned len = boost::lexical_cast<unsigned>(length);
+ result->set("prefix-len", Element::create(static_cast<int>(len)));
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(BadValue,
+ "getPdPoolKea: bad prefix length in '" << prefix << "'");
+ }
+ ConstElementPtr xpref = getItem(data_node, "excluded-prefix");
+ if (xpref) {
+ const string& xprefix = xpref->stringValue();
+ size_t xslash = xprefix.find("/");
+ if (xslash == string::npos) {
+ isc_throw(BadValue,
+ "getPdPoolKea: no '/' in excluded prefix '"
+ << xprefix << "'");
+ }
+ const string& xaddress = xprefix.substr(0, xslash);
+ const string& xlength = xprefix.substr(xslash + 1, string::npos);
+ if (xaddress.empty() || xlength.empty()) {
+ isc_throw(BadValue,
+ "getPdPoolKea: malformed excluded prefix '"
+ << xprefix << "'");
+ }
+ result->set("excluded-prefix", Element::create(xaddress));
+ try {
+ unsigned xlen = boost::lexical_cast<unsigned>(xlength);
+ result->set("excluded-prefix-len",
+ Element::create(static_cast<int>(xlen)));
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(BadValue,
+ "getPdPoolKea: bad excluded prefix length in '"
+ << xprefix << "'");
+ }
+ }
+
+ checkAndGetLeaf(result, data_node, "client-class");
+ checkAndGetLeaf(result, data_node, "delegated-len");
+ checkAndGetLeaf(result, data_node, "require-client-classes");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ ConstElementPtr options = getOptionDataList(data_node);
+ if (options) {
+ result->set("option-data", options);
+ }
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorPdPool::setPdPool(string const& xpath, ConstElementPtr elem) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ setPdPoolIetf6(xpath, elem);
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ setPdPoolKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setPdPool not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting pd-pool '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorPdPool::setPdPoolIetf6(string const& xpath, ConstElementPtr elem) {
+ ConstElementPtr base = elem->get("prefix");
+ ConstElementPtr length = elem->get("prefix-len");
+ if (!base || !length) {
+ isc_throw(BadValue,
+ "setPdPoolIetf6 requires prefix and prefix length: "
+ << elem->str());
+ }
+ ostringstream prefix;
+ prefix << base->stringValue() << "/" << length->intValue();
+ setItem(xpath + "/prefix", Element::create(prefix.str()), LeafBaseType::String);
+ setItem(xpath + "/prefix-length", length, LeafBaseType::Uint8);
+
+ checkAndSetLeaf(elem, xpath, "client-class", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "valid-lifetime", LeafBaseType::Uint32);
+
+ checkAndSetDivergingLeaf(elem, xpath, "rebind-timer", "rebind-time", LeafBaseType::Uint32);
+ checkAndSetDivergingLeaf(elem, xpath, "renew-timer", "renew-time", LeafBaseType::Uint32);
+
+ // Set max pd space utilization to disabled.
+ setItem(xpath + "/max-pd-space-utilization", Element::create("disabled"),
+ LeafBaseType::Enum);
+
+ // Skip rapid-commit.
+ // @todo: option-data
+}
+
+void
+TranslatorPdPool::setPdPoolKea(string const& xpath, ConstElementPtr elem) {
+ // Keys are set by setting the list itself.
+ setItem(xpath, ElementPtr(), LeafBaseType::Unknown);
+
+ checkAndSetLeaf(elem, xpath, "client-class", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "delegated-len", LeafBaseType::Uint8);
+
+ checkAndSetLeafList(elem, xpath, "require-client-classes", LeafBaseType::String);
+
+ checkAndSetUserContext(elem, xpath);
+
+ ConstElementPtr xprefix = elem->get("excluded-prefix");
+ ConstElementPtr xlen = elem->get("excluded-prefix-len");
+ if (xprefix && xlen) {
+ ostringstream xpref;
+ xpref << xprefix->stringValue() << "/" << xlen->intValue();
+ setItem(xpath + "/excluded-prefix", Element::create(xpref.str()), LeafBaseType::String);
+ }
+ ConstElementPtr options = elem->get("option-data");
+ if (options && !options->empty()) {
+ setOptionDataList(xpath, options);
+ }
+}
+
+TranslatorPdPools::TranslatorPdPools(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorPdPool(session, model) {
+}
+
+ElementPtr
+TranslatorPdPools::getPdPools(DataNode const& data_node) {
+ try {
+ if ((model_ == IETF_DHCPV6_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getPdPoolsCommon(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting pd-pools:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getPdPools not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorPdPools::getPdPoolsFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getPdPools(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorPdPools::getPdPoolsCommon(DataNode const& data_node) {
+ return getList<TranslatorPdPool>(data_node, "pd-pool", *this,
+ &TranslatorPdPool::getPdPool);
+}
+
+void
+TranslatorPdPools::setPdPools(string const& xpath, ConstElementPtr elem) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ setPdPoolsId(xpath, elem);
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ setPdPoolsPrefix(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setPdPools not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting pools '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorPdPools::setPdPoolsId(string const& xpath, ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr pool = elem->getNonConst(i);
+ ostringstream prefix;
+ prefix << xpath << "/pd-pool[pool-id='" << i << "']";
+ setPdPool(prefix.str(), pool);
+ }
+}
+
+void
+TranslatorPdPools::setPdPoolsPrefix(string const& xpath,
+ ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr pool = elem->getNonConst(i);
+ if (!pool->contains("prefix") || !pool->contains("prefix-len")) {
+ isc_throw(BadValue, "pd-pool requires prefix and prefix length: "
+ << pool->str());
+ }
+ ostringstream prefix;
+ prefix << xpath << "/pd-pool[prefix='"
+ << pool->get("prefix")->stringValue() << "/"
+ << pool->get("prefix-len")->intValue() << "']";
+ setPdPool(prefix.str(), pool);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_pd_pool.h b/src/lib/yang/translator_pd_pool.h
new file mode 100644
index 0000000..a7d0ec0
--- /dev/null
+++ b/src/lib/yang/translator_pd_pool.h
@@ -0,0 +1,256 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_PD_POOL_H
+#define ISC_TRANSLATOR_PD_POOL_H 1
+
+#include <yang/translator_option_data.h>
+
+namespace isc {
+namespace yang {
+
+/// Prefix delegation pool translation between YANG and JSON
+///
+/// JSON syntax for kea-dhcp6-server is:
+/// @code
+/// {
+/// "prefix": <prefix base>,
+/// "prefix-len": <prefix length>,
+/// "delegated-len": <delegated length>,
+/// "excluded-prefix": <excluded prefix>,
+/// "excluded-prefix-len": <excluded prefix length>,
+/// "option-data": [ <list of option data> ],
+/// "client-class": "<guard class name>",
+/// "require-client-classes": [ <list of required class names> ],
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax for ietf-dhcpv6-server is with pool-id as the key.
+/// @code
+/// +--rw pool-id uint32
+/// +--rw prefix inet:ipv6-prefix
+/// +--rw prefix-length uint8
+/// +--rw valid-lifetime yang:timeticks
+/// +--rw renew-time yang:timeticks
+/// +--rw rebind-time yang:timeticks
+/// +--rw preferred-lifetime yang:timeticks
+/// +--rw rapid-commit? boolean
+/// +--rw client-class? string
+/// +--rw max-pd-space-utilization? threshold
+/// +--rw option-set-id?
+/// /server/server-config/option-sets/option-set/option-set-id
+/// @endcode
+///
+/// YANG syntax for kea-dhcp6-server is with prefix as the key.
+/// @code
+/// +--rw pd-pool* [prefix]
+/// +--rw prefix inet:ipv6-prefix
+/// +--rw delegated-len? uint8
+/// +--rw option-data* [code space]
+/// +--rw client-class? string
+/// +--rw require-client-classes* string
+/// +--rw excluded-prefix? inet:ipv6-prefix
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "prefix": "2001:db8:0:1000::",
+/// "prefix-len": 56,
+/// "delegated-len": 64
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /ietf-dhcpv6-server:server (container)
+/// /ietf-dhcpv6-server:server/server-config (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges
+/// network-range[network-range-id='111'] (list instance)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges
+/// network-range[network-range-id='111']/network-range-id = 111
+/// /ietf-dhcpv6-server:server/server-config/network-ranges
+/// network-range[network-range-id='111']/network-prefix = 2001:db8::/48
+/// /ietf-dhcpv6-server:server/server-config/network-ranges
+/// network-range[network-range-id='111']/pd-pools (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges
+/// network-range[network-range-id='111']/pd-pools/
+/// pd-pool[pool-id='0'] (list instance)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// pd-pool[pool-id='0']/pool-id = 0
+/// network-range[network-range-id='111']/pd-pools/
+/// pd-pool[pool-id='0']/prefix = 2001:db8:0:1000::/56
+/// /ietf-dhcpv6-server:server/server-config/network-ranges
+/// network-range[network-range-id='111']/pd-pools/
+/// pd-pool[pool-id='0']/prefix-length = 56
+/// /ietf-dhcpv6-server:server/server-config/network-ranges
+/// network-range[network-range-id='111']/pd-pools/
+/// pd-pool[pool-id='0']/max-pd-space-utilization = disabled
+/// @endcode
+/// @code
+/// /kea-dhcp6-server:config (container)
+/// /kea-dhcp6-server:config/subnet6[id='111'] (list instance)
+/// /kea-dhcp6-server:config/subnet6[id='111']/id = 111
+/// /kea-dhcp6-server:config/subnet6[id='111']/subnet = 2001:db8::/48
+/// /kea-dhcp6-server:config/subnet6[id='111']/
+/// pd-pool[prefix='2001:db8:0:1000::/56' (list instance)
+/// /kea-dhcp6-server:config/subnet6[id='111']/
+/// pd-pool[prefix='2001:db8:0:1000::/56'/prefix = 2001:db8:0:1000::/56
+/// /kea-dhcp6-server:config/subnet6[id='111']/
+/// pd-pool[prefix='2001:db8:0:1000::/56'/delegated-len = 64
+/// @endcode
+
+/// @brief A translator class for converting a pd-pool between
+/// YANG and JSON.
+///
+/// Currently supported models:
+/// - kea-dhcp6-server
+/// - ietf-dhcpv6-server (partial support)
+class TranslatorPdPool : virtual public TranslatorOptionDataList {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorPdPool(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorPdPool() = default;
+
+ /// @brief Translate a pd-pool from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the PD pool
+ ///
+ /// @return the JSON representation of the PD pool
+ ///
+ /// @throw BadValue on pd-pool without well formed prefix.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPdPool(libyang::DataNode const& data_node);
+
+ /// @brief Translate a pd-pool from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the pd-pool.
+ ///
+ /// @return JSON representation of the pd-pool.
+ ///
+ /// @throw BadValue on pd-pool without well formed prefix.
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPdPoolFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set pd-pool from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the pd-pool.
+ /// @param elem The JSON element.
+ void setPdPool(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getPdPool for ietf-dhcpv6-server.
+ ///
+ /// @param data_node the YANG node representing the PD pool
+ ///
+ /// @return JSON representation of the pd-pool.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPdPoolIetf6(libyang::DataNode const& data_node);
+
+ /// @brief setPdPool for ietf-dhcpv6-server.
+ ///
+ /// @param xpath The xpath of the pd-pool.
+ /// @param elem The JSON element.
+ /// @throw BadValue on pd-pool without prefix or prefix length.
+ void setPdPoolIetf6(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief getPdPool for kea-dhcp6-server.
+ ///
+ /// @param data_node the YANG node representing the PD pool
+ ///
+ /// @return JSON representation of the pd-pool.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPdPoolKea(libyang::DataNode const& data_node);
+
+ /// @brief setPdPool for kea-dhcp6-server.
+ ///
+ /// @param xpath The xpath of the pd-pool.
+ /// @param elem The JSON element.
+ void setPdPoolKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorPdPool
+
+/// @brief A translator class for converting a pd-pool list between
+/// YANG and JSON.
+///
+/// Currently supports the following models:
+/// - kea-dhcp6-server
+/// - ietf-dhcpv6-server (partial support)
+class TranslatorPdPools : virtual public TranslatorPdPool {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorPdPools(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorPdPools() = default;
+
+ /// @brief Translate pd-pools from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of PD pools
+ ///
+ /// @return the JSON representation of the list of PD pools
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPdPools(libyang::DataNode const& data_node);
+
+ /// @brief Translate pd-pools from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the pd-pool list.
+ ///
+ /// @return the JSON representation of the list of PD pools
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPdPoolsFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set pd-pools from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the pd-pool list.
+ /// @param elem The JSON element.
+ void setPdPools(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getPdPools common part.
+ ///
+ /// @param data_node the YANG node representing the list of PD pools
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPdPoolsCommon(libyang::DataNode const& data_node);
+
+ /// @brief setPdPools using pool-id.
+ ///
+ /// @param xpath The xpath of the pd-pool list.
+ /// @param elem The JSON element.
+ void setPdPoolsId(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief setPdPools using prefix.
+ ///
+ /// @param xpath The xpath of the pd-pool list.
+ /// @param elem The JSON element.
+ /// @throw BadValue on pd-pool without prefix or prefix length.
+ void setPdPoolsPrefix(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorPdPools
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_PD_POOL_H
diff --git a/src/lib/yang/translator_pool.cc b/src/lib/yang/translator_pool.cc
new file mode 100644
index 0000000..f1bb8a3
--- /dev/null
+++ b/src/lib/yang/translator_pool.cc
@@ -0,0 +1,327 @@
+// Copyright (C) 2018-2023 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 <asiolink/addr_utilities.h>
+#include <asiolink/io_address.h>
+#include <yang/translator_pool.h>
+#include <yang/yang_models.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorPool::TranslatorPool(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model) {
+}
+
+ElementPtr
+TranslatorPool::getPool(DataNode const& data_node) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ return (getPoolIetf6(data_node));
+ } else if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getPoolKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting pool:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getPool not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorPool::getPoolFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getPool(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorPool::getPoolIetf6(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryDivergingLeaf(result, data_node, "pool", "pool-prefix");
+
+ checkAndGetLeaf(result, data_node, "client-class");
+ checkAndGetLeaf(result, data_node, "preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "valid-lifetime");
+
+ checkAndGetDivergingLeaf(result, data_node, "rebind-timer", "rebind-time");
+ checkAndGetDivergingLeaf(result, data_node, "renew-timer", "renew-time");
+
+ // Skip max-addr-count.
+ // Skip pool-id which exists but is not used.
+ // Skip rapid-commit.
+ // Skip start-address - end-address as prefix form is mandatory?
+ // @todo: option-data
+ // No require-client-classes.
+ // No user-context.
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorPool::getPoolKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+ ConstElementPtr prefix = getItem(data_node, "prefix");
+ if (prefix) {
+ result->set("pool", prefix);
+ } else {
+ ConstElementPtr start_addr = getItem(data_node, "start-address");
+ ConstElementPtr end_addr = getItem(data_node, "end-address");
+ if (!start_addr || !end_addr) {
+ isc_throw(MissingNode, "getPoolKea requires either prefix or "
+ "both start and end addresses");
+ }
+ ostringstream range;
+ range << start_addr->stringValue() << " - "
+ << end_addr->stringValue();
+ result->set("pool", Element::create(range.str()));
+ }
+ ConstElementPtr options = getOptionDataList(data_node);
+ if (options) {
+ result->set("option-data", options);
+ }
+
+ checkAndGetLeaf(result, data_node, "client-class");
+ checkAndGetLeaf(result, data_node, "require-client-classes");
+
+ checkAndGetLeaf(result, data_node, "pool-id");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorPool::setPool(string const& xpath, ConstElementPtr elem) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ setPoolIetf6(xpath, elem);
+ } else if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setPoolKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setPool not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting pool '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorPool::setPoolIetf6(string const& xpath, ConstElementPtr elem) {
+ ConstElementPtr pool = elem->get("pool");
+ if (!pool) {
+ isc_throw(BadValue, "setPoolIetf6 requires pool: " << elem->str());
+ }
+ string prefix = pool->stringValue();
+ if (prefix.find("/") == string::npos) {
+ isc_throw(BadValue,
+ "setPoolIetf only supports pools in prefix (vs range) "
+ "format and was called with '" << prefix << "'");
+ }
+ setItem(xpath + "/pool-prefix", pool, LeafBaseType::String);
+ string addr = prefix.substr(0, prefix.find_first_of(" /"));
+ uint8_t plen = boost::lexical_cast<unsigned>
+ (prefix.substr(prefix.find_last_of(" /") + 1, string::npos));
+ const IOAddress& base(addr);
+ setItem(xpath + "/start-address",
+ Element::create(firstAddrInPrefix(base, plen).toText()),
+ LeafBaseType::String);
+ setItem(xpath + "/end-address",
+ Element::create(lastAddrInPrefix(base, plen).toText()),
+ LeafBaseType::String);
+
+ checkAndSetLeaf(elem, xpath, "client-class", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "valid-lifetime", LeafBaseType::Uint32);
+
+ checkAndSetDivergingLeaf(elem, xpath, "rebind-timer", "rebind-time", LeafBaseType::Uint32);
+ checkAndSetDivergingLeaf(elem, xpath, "renew-timer", "renew-time", LeafBaseType::Uint32);
+
+ // Set max address count to disabled.
+ setItem(xpath + "/max-address-count",
+ Element::create("disabled"),
+ LeafBaseType::Enum);
+
+ // Skip max-addr-count.
+ // Skip rapid-commit.
+ // @todo: option-data
+}
+
+void
+TranslatorPool::setPoolKea(string const& xpath, ConstElementPtr elem) {
+ // Skip keys "start-address" and "end-address".
+
+ ConstElementPtr pool = elem->get("pool");
+ if (!pool) {
+ isc_throw(BadValue, "setPoolKea requires pool: " << elem->str());
+ }
+
+ // Keys are set by setting the list itself.
+ setItem(xpath, ElementPtr(), LeafBaseType::Unknown);
+
+ checkAndSetLeaf(elem, xpath, "client-class", LeafBaseType::String);
+
+ checkAndSetLeafList(elem, xpath, "require-client-classes", LeafBaseType::String);
+
+ checkAndSetLeaf(elem, xpath, "pool-id", LeafBaseType::Dec64);
+
+ checkAndSetUserContext(elem, xpath);
+
+ string prefix = pool->stringValue();
+ string start_addr;
+ string end_addr;
+ getAddresses(prefix, start_addr, end_addr);
+ if (prefix.find("/") != string::npos) {
+ setItem(xpath + "/prefix", pool, LeafBaseType::String);
+ }
+ ConstElementPtr options = elem->get("option-data");
+ if (options && !options->empty()) {
+ setOptionDataList(xpath, options);
+ }
+}
+
+void
+TranslatorPool::getAddresses(const string& prefix,
+ string& start_address, string& end_address) {
+ size_t slash = prefix.find("/");
+ if (slash != string::npos) {
+ string addr = prefix.substr(0, prefix.find_first_of(" /"));
+ uint8_t plen = boost::lexical_cast<unsigned>
+ (prefix.substr(prefix.find_last_of(" /") + 1, string::npos));
+ start_address = firstAddrInPrefix(IOAddress(addr), plen).toText();
+ end_address = lastAddrInPrefix(IOAddress(addr), plen).toText();
+ return;
+ }
+ size_t dash = prefix.find("-");
+ if (dash == string::npos) {
+ isc_throw(BadValue,
+ "getAddresses called with invalid prefix: " << prefix);
+ }
+ start_address = prefix.substr(0, prefix.find_first_of(" -"));
+ end_address = prefix.substr(prefix.find_last_of(" -") + 1, string::npos);
+}
+
+TranslatorPools::TranslatorPools(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorPool(session, model) {
+}
+
+ElementPtr
+TranslatorPools::getPools(DataNode const& data_node) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ return (getPoolsIetf(data_node));
+ } else if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getPoolsKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting pools:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getPools not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorPools::getPoolsFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getPools(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorPools::getPoolsIetf(DataNode const& data_node) {
+ return getList<TranslatorPool>(data_node, "address-pool", *this,
+ &TranslatorPool::getPool);
+}
+
+ElementPtr
+TranslatorPools::getPoolsKea(DataNode const& data_node) {
+ return getList<TranslatorPool>(data_node, "pool", *this,
+ &TranslatorPool::getPool);
+}
+
+void
+TranslatorPools::setPools(string const& xpath, ConstElementPtr elem) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ setPoolsById(xpath, elem);
+ } else if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setPoolsByAddresses(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setPools not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting pools '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorPools::setPoolsById(string const& xpath, ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr pool = elem->getNonConst(i);
+ ostringstream prefix;
+ prefix << xpath << "/address-pool[pool-id='" << i << "']";
+ setPool(prefix.str(), pool);
+ }
+}
+
+void
+TranslatorPools::setPoolsByAddresses(string const& xpath,
+ ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr pool = elem->getNonConst(i);
+ if (!pool->contains("pool")) {
+ isc_throw(BadValue, "setPoolsByAddresses: missing required pool: "
+ << pool->str());
+ }
+ string pref = pool->get("pool")->stringValue();
+ string start_addr;
+ string end_addr;
+ getAddresses(pref, start_addr, end_addr);
+ ostringstream prefix;
+ prefix << xpath << "/pool[start-address='" << start_addr
+ << "'][end-address='" << end_addr << "']";
+ setPool(prefix.str(), pool);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_pool.h b/src/lib/yang/translator_pool.h
new file mode 100644
index 0000000..90b9f35
--- /dev/null
+++ b/src/lib/yang/translator_pool.h
@@ -0,0 +1,277 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_POOL_H
+#define ISC_TRANSLATOR_POOL_H 1
+
+#include <yang/translator_option_data.h>
+
+namespace isc {
+namespace yang {
+
+/// @brief A translator class for converting a pool between YANG and JSON.
+///
+/// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server.
+///
+/// JSON syntax for both kea-dhcp4 and kea-dhcp6 is:
+/// @code
+/// {
+/// "pool": "<pool prefix or start - end addresses>",
+/// "option-data": [ <list of option data> ],
+/// "client-class": "<guard class name>",
+/// "require-client-classes": [ <list of required class names> ],
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax is for ietf-dhcpv6-server is with pool-id as the key:
+/// @code
+/// +--rw pool-id uint32
+/// +--rw pool-prefix inet:ipv6-prefix
+/// +--rw start-address inet:ipv6-address-no-zone
+/// +--rw end-address inet:ipv6-address-no-zone
+/// +--rw valid-lifetime yang:timeticks
+/// +--rw renew-time yang:timeticks
+/// +--rw rebind-time yang:timeticks
+/// +--rw preferred-lifetime yang:timeticks
+/// +--rw rapid-commit? boolean
+/// +--rw client-class? string
+/// +--rw max-address-count threshold
+/// +--rw option-set-id
+/// /server/server-config/option-sets/option-set/option-set-id
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46] is with start-address and end-address
+/// as the keys:
+/// @code
+/// +--rw pool* [start-address end-address]
+/// +--rw prefix? inet:ipv[46]-prefix
+/// +--rw start-address inet:ipv[46]-address
+/// +--rw end-address inet:ipv[46]-address
+/// +--rw option-data* [code space]
+/// +--rw client-class? string
+/// +--rw require-client-classes* string
+/// +--rw user-context? user-context
+/// @endcode
+///
+/// An example in JSON and YANG formats for the IETF model:
+/// @code
+/// [
+/// {
+/// "pool": "2001:db8::/112"
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /ietf-dhcpv6-server:server (container)
+/// /ietf-dhcpv6-server:server/server-config (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111'] (list instance)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/network-range-id = 111
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/network-prefix = 2001:db8::/48
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/address-pools (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/address-pools/
+/// address-pool[pool-id='0'] (list instance)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/address-pools/
+/// address-pool[pool-id='0']/pool-id = 0
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/address-pools/
+/// address-pool[pool-id='0']/pool-prefix = 2001:db8::1:0/112
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/address-pools/
+/// address-pool[pool-id='0']/start-address = 2001:db8::1:0
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/address-pools/
+/// address-pool[pool-id='0']/end-address = 2001:db8::1:ffff
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='111']/address-pools/
+/// address-pool[pool-id='0']/max-address-count = disabled
+/// @endcode
+///
+/// An example in JSON and YANG formats for the Kea model:
+/// @code
+/// [
+/// {
+/// "pool": "2001:db8::1 - 2001:db8::100"
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp6-server:config (container)
+/// /kea-dhcp6-server:config/subnet6[id='111'] (list instance)
+/// /kea-dhcp6-server:config/subnet6[id='111']/id = 111
+/// /kea-dhcp6-server:config/subnet6[id='111']/subnet = 2001:db8::/48
+/// /kea-dhcp6-server:config/subnet6[id='111']/
+/// pool[start-address='2001:db8::1'][end-address='2001:db8::100']
+/// (list instance)
+/// /kea-dhcp6-server:config/subnet6[id='111']/
+/// pool[start-address='2001:db8::1'][end-address='2001:db8::100']/
+/// start-address = 2001:db8::1
+/// /kea-dhcp6-server:config/subnet6[id='111']/
+/// pool[start-address='2001:db8::1'][end-address='2001:db8::100']/
+/// end-address = 2001:db8::100
+/// @endcode
+class TranslatorPool : virtual public TranslatorOptionDataList {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorPool(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorPool() = default;
+
+ /// @brief Translate a pool from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the pool
+ ///
+ /// @return the JSON representation of the pool
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPool(libyang::DataNode const& data_node);
+
+ /// @brief Translate a pool from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the pool.
+ ///
+ /// @return JSON representation of the pool.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPoolFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set (address) pool from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the pool.
+ /// @param elem The JSON element.
+ void setPool(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+ /// @brief Get start and end addresses from prefix.
+ ///
+ /// @param prefix The prefix string.
+ /// @param start_address The reference to the start_address.
+ /// @param end_address The reference to the end_address.
+ /// @throw BadValue when the prefix is not correctly formed.
+ static void getAddresses(const std::string& prefix,
+ std::string& start_address,
+ std::string& end_address);
+
+protected:
+ /// @brief getPool for ietf-dhcpv6-server.
+ ///
+ /// @param data_node the YANG node representing the pool configuration
+ ///
+ /// @return JSON representation of the pool.
+ ///
+ /// @throw BadValue on pool without prefix.
+ isc::data::ElementPtr getPoolIetf6(libyang::DataNode const& data_node);
+
+ /// @brief setPool for ietf-dhcpv6-server.
+ ///
+ /// @param xpath The xpath of the pool.
+ /// @param elem The JSON element.
+ /// @throw BadValue on pool without prefix and with a range which is
+ /// not a prefix.
+ void setPoolIetf6(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief getPool for kea-dhcp[46]-server.
+ ///
+ /// @param data_node the YANG node representing the pool configuration
+ ///
+ /// @return JSON representation of the pool.
+ ///
+ /// @throw BadValue on a pool without prefix and start or end address.
+ isc::data::ElementPtr getPoolKea(libyang::DataNode const& data_node);
+
+ /// @brief setPool for kea-dhcp[46]-server.
+ ///
+ /// @param xpath The xpath of the pool.
+ /// @param elem The JSON element.
+ /// @throw BadValue on a pool without a well formed prefix.
+ void setPoolKea(const std::string& xpath, isc::data::ConstElementPtr elem);
+}; // TranslatorPool
+
+/// @brief A translator class for converting pools between YANG and JSON.
+///
+/// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server.
+class TranslatorPools : virtual public TranslatorPool {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorPools(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorPools() = default;
+
+ /// @brief Translate pools from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of pools
+ ///
+ /// @return the JSON representation of the list of pools
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPools(libyang::DataNode const& data_node);
+
+ /// @brief Translate pools from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the pool list.
+ ///
+ /// @return the JSON representation of the list of pools
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getPoolsFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set (address) pools from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the pool list.
+ /// @param elem The JSON element.
+ void setPools(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getPools for ietf-dhcpv6-server.
+ ///
+ /// @param data_node the YANG node representing the list of pools
+ ///
+ /// @return the JSON representation of the list of pools
+ isc::data::ElementPtr getPoolsIetf(libyang::DataNode const& data_node);
+
+ /// @brief getPools for kea-dhcp[46]-server.
+ ///
+ /// @param data_node the YANG node representing the list of pools
+ ///
+ /// @return the JSON representation of the list of pools
+ isc::data::ElementPtr getPoolsKea(libyang::DataNode const& data_node);
+
+ /// @brief setPools using pool-id.
+ ///
+ /// @param xpath The xpath of the pool list.
+ /// @param elem The JSON element.
+ void setPoolsById(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief setPools using address pair.
+ ///
+ /// @param xpath The xpath of the pool list.
+ /// @param elem The JSON element.
+ /// @throw BadValue on a pool without a prefix.
+ void setPoolsByAddresses(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorPools
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_POOL_H
diff --git a/src/lib/yang/translator_shared_network.cc b/src/lib/yang/translator_shared_network.cc
new file mode 100644
index 0000000..0004496
--- /dev/null
+++ b/src/lib/yang/translator_shared_network.cc
@@ -0,0 +1,306 @@
+// Copyright (C) 2018-2023 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 <yang/translator_shared_network.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorSharedNetwork::TranslatorSharedNetwork(Session session,
+ const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorPool(session, model),
+ TranslatorPools(session, model),
+ TranslatorPdPool(session, model),
+ TranslatorPdPools(session, model),
+ TranslatorHost(session, model),
+ TranslatorHosts(session, model),
+ TranslatorSubnet(session, model),
+ TranslatorSubnets(session, model) {
+}
+
+ElementPtr
+TranslatorSharedNetwork::getSharedNetwork(DataNode const& data_node) {
+ try {
+ if (model_ == KEA_DHCP4_SERVER) {
+ return (getSharedNetworkKea(data_node, "subnet4"));
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ return (getSharedNetworkKea(data_node, "subnet6"));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting shared network:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getSharedNetwork not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorSharedNetwork::getSharedNetworkFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getSharedNetwork(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorSharedNetwork::getSharedNetworkKea(DataNode const& data_node,
+ string const& subsel) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryLeaf(result, data_node, "name");
+
+ checkAndGetLeaf(result, data_node, "allocator");
+ checkAndGetLeaf(result, data_node, "cache-max-age");
+ checkAndGetLeaf(result, data_node, "cache-threshold");
+ checkAndGetLeaf(result, data_node, "calculate-tee-times");
+ checkAndGetLeaf(result, data_node, "client-class");
+ checkAndGetLeaf(result, data_node, "ddns-generated-prefix");
+ checkAndGetLeaf(result, data_node, "ddns-override-client-update");
+ checkAndGetLeaf(result, data_node, "ddns-override-no-update");
+ checkAndGetLeaf(result, data_node, "ddns-qualifying-suffix");
+ checkAndGetLeaf(result, data_node, "ddns-replace-client-name");
+ checkAndGetLeaf(result, data_node, "ddns-send-updates");
+ checkAndGetLeaf(result, data_node, "ddns-ttl-percent");
+ checkAndGetLeaf(result, data_node, "ddns-update-on-renew");
+ checkAndGetLeaf(result, data_node, "ddns-use-conflict-resolution");
+ checkAndGetLeaf(result, data_node, "hostname-char-replacement");
+ checkAndGetLeaf(result, data_node, "hostname-char-set");
+ checkAndGetLeaf(result, data_node, "interface");
+ checkAndGetLeaf(result, data_node, "max-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "min-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "rebind-timer");
+ checkAndGetLeaf(result, data_node, "renew-timer");
+ checkAndGetLeaf(result, data_node, "require-client-classes");
+ checkAndGetLeaf(result, data_node, "reservation-mode");
+ checkAndGetLeaf(result, data_node, "reservations-global");
+ checkAndGetLeaf(result, data_node, "reservations-in-subnet");
+ checkAndGetLeaf(result, data_node, "reservations-out-of-pool");
+ checkAndGetLeaf(result, data_node, "store-extended-info");
+ checkAndGetLeaf(result, data_node, "t1-percent");
+ checkAndGetLeaf(result, data_node, "t2-percent");
+ checkAndGetLeaf(result, data_node, "valid-lifetime");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ ConstElementPtr options = getOptionDataList(data_node);
+ if (options) {
+ result->set("option-data", options);
+ }
+
+ ConstElementPtr subnets = getSubnets(data_node);
+ if (subnets) {
+ result->set(subsel, subnets);
+ }
+
+ checkAndGet(result, data_node, "relay",
+ [&](DataNode const& node) -> ElementPtr const {
+ ElementPtr relay(Element::createMap());
+ checkAndGetLeaf(relay, node, "ip-addresses");
+ return relay;
+ });
+
+ if (subsel == "subnet6") {
+ checkAndGetLeaf(result, data_node, "interface-id");
+ checkAndGetLeaf(result, data_node, "max-preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "min-preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "pd-allocator");
+ checkAndGetLeaf(result, data_node, "preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "rapid-commit");
+ } else if (subsel == "subnet4") {
+ checkAndGetLeaf(result, data_node, "authoritative");
+ checkAndGetLeaf(result, data_node, "boot-file-name");
+ checkAndGetLeaf(result, data_node, "match-client-id");
+ checkAndGetLeaf(result, data_node, "next-server");
+ checkAndGetLeaf(result, data_node, "offer-lifetime");
+ checkAndGetLeaf(result, data_node, "server-hostname");
+ }
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorSharedNetwork::setSharedNetwork(string const& xpath,
+ ConstElementPtr elem) {
+ try {
+ if (model_ == KEA_DHCP4_SERVER) {
+ setSharedNetworkKea(xpath, elem, "subnet4");
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ setSharedNetworkKea(xpath, elem, "subnet6");
+ } else {
+ isc_throw(NotImplemented,
+ "setSharedNetwork not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting shared network '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorSharedNetwork::setSharedNetworkKea(string const& xpath,
+ ConstElementPtr elem,
+ string const& subsel) {
+ // Skip key "name".
+
+ checkAndSetLeaf(elem, xpath, "allocator", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "cache-max-age", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "cache-threshold", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "calculate-tee-times", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "client-class", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-generated-prefix", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-override-client-update", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-override-no-update", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-qualifying-suffix", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-replace-client-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-send-updates", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-ttl-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "ddns-update-on-renew", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-use-conflict-resolution", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "hostname-char-replacement", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "hostname-char-set", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "interface", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "max-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "rebind-timer", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "renew-timer", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "reservation-mode", LeafBaseType::Enum);
+ checkAndSetLeaf(elem, xpath, "reservations-global", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "reservations-in-subnet", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "reservations-out-of-pool", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "store-extended-info", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "t1-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "t2-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "valid-lifetime", LeafBaseType::Uint32);
+
+ checkAndSetLeafList(elem, xpath, "require-client-classes", LeafBaseType::String);
+
+ checkAndSetUserContext(elem, xpath);
+
+ ConstElementPtr relay = elem->get("relay");
+ if (relay) {
+ ConstElementPtr address = relay->get("ip-address");
+ ConstElementPtr addresses = relay->get("ip-addresses");
+ if (address) {
+ setItem(xpath + "/relay/ip-addresses", address, LeafBaseType::String);
+ } else if (addresses && !addresses->empty()) {
+ for (ElementPtr const& addr : addresses->listValue()) {
+ setItem(xpath + "/relay/ip-addresses", addr, LeafBaseType::String);
+ }
+ }
+ }
+
+ ConstElementPtr options = elem->get("option-data");
+ if (options && !options->empty()) {
+ setOptionDataList(xpath, options);
+ }
+
+ ConstElementPtr subnets = elem->get(subsel);
+ if (subnets && !subnets->empty()) {
+ setSubnets(xpath, subnets);
+ }
+
+ if (subsel == "subnet6") {
+ checkAndSetLeaf(elem, xpath, "interface-id", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "max-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "pd-allocator", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "rapid-commit", LeafBaseType::Bool);
+ } else {
+ checkAndSetLeaf(elem, xpath, "authoritative", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "boot-file-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "match-client-id", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "next-server", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "offer-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "server-hostname", LeafBaseType::String);
+ }
+}
+
+TranslatorSharedNetworks::TranslatorSharedNetworks(Session session,
+ const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorPool(session, model),
+ TranslatorPools(session, model),
+ TranslatorPdPool(session, model),
+ TranslatorPdPools(session, model),
+ TranslatorHost(session, model),
+ TranslatorHosts(session, model),
+ TranslatorSubnet(session, model),
+ TranslatorSubnets(session, model),
+ TranslatorSharedNetwork(session, model) {
+}
+
+ElementPtr
+TranslatorSharedNetworks::getSharedNetworks(DataNode const& data_node) {
+ return getList<TranslatorSharedNetwork>(data_node, "shared-network", *this,
+ &TranslatorSharedNetwork::getSharedNetwork);
+}
+
+ElementPtr
+TranslatorSharedNetworks::getSharedNetworksFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getSharedNetworks(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+void
+TranslatorSharedNetworks::setSharedNetworks(string const& xpath,
+ ConstElementPtr elem) {
+ try {
+ if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setSharedNetworksKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setSharedNetworks not implemented for the model: "
+ << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting shared networks '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorSharedNetworks::setSharedNetworksKea(string const& xpath,
+ ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr network = elem->getNonConst(i);
+ if (!network->contains("name")) {
+ isc_throw(BadValue, "setSharedNetworksKea requires name: "
+ << network->str());
+ }
+ string name = network->get("name")->stringValue();
+ ostringstream key;
+ key<< xpath << "/shared-network[name='" << name << "']";
+ setSharedNetwork(key.str(), network);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_shared_network.h b/src/lib/yang/translator_shared_network.h
new file mode 100644
index 0000000..04f8335
--- /dev/null
+++ b/src/lib/yang/translator_shared_network.h
@@ -0,0 +1,281 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_SHARED_NETWORK_H
+#define ISC_TRANSLATOR_SHARED_NETWORK_H 1
+
+#include <yang/translator.h>
+#include <yang/translator_subnet.h>
+
+namespace isc {
+namespace yang {
+
+/// Shared network translation between YANG and JSON
+///
+/// JSON syntax for kea-dhcp4 is:
+/// @code
+/// {
+/// "name": <name>,
+/// "subnet4": <subnet list>,
+/// "valid-lifetime": <valid lifetime>,
+/// "min-valid-lifetime": <minimum valid lifetime>,
+/// "max-valid-lifetime": <maximum valid lifetime>,
+/// "renew-timer": <renew timer>,
+/// "rebind-timer": <rebind timer>,
+/// "calculate-tee-times": <calculate T1/T2 times>,
+/// "t1-percent": <T1 percent>,
+/// "t2-percent": <T2 percent>,
+/// "option-data": [ <list of option data> ],
+/// "interface": "<interface>",
+/// "client-class": "<guard class name>",
+/// "require-client-classes": [ <list of required class names> ],
+/// "reservation-mode": <host reservation mode>,
+/// "relay": <relay ip address(es)>,
+/// "match-client-id": <match client id flag>,
+/// "next-server": "<next server>",
+/// "server-hostname": "<server hostname>",
+/// "boot-file-name": "<boot file name>",
+/// "authoritative": <authoritative flag>,
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// JSON syntax for kea-dhcp6 is:
+/// @code
+/// {
+/// "name": <name>,
+/// "subnet6": <subnet list>,
+/// "preferred-lifetime": <preferred lifetime>,
+/// "min-preferred-lifetime": <minimum preferred lifetime>,
+/// "max-preferred-lifetime": <maximum preferred lifetime>,
+/// "valid-lifetime": <valid lifetime>,
+/// "min-valid-lifetime": <minimum valid lifetime>,
+/// "max-valid-lifetime": <maximum valid lifetime>,
+/// "renew-timer": <renew timer>,
+/// "rebind-timer": <rebind timer>,
+/// "calculate-tee-times": <calculate T1/T2 times>,
+/// "t1-percent": <T1 percent>,
+/// "t2-percent": <T2 percent>,
+/// "option-data": [ <list of option data> ],
+/// "interface": "<interface>",
+/// "interface-id": "<interface id>",
+/// "rapid-commit": <rapid commit flag>,
+/// "client-class": "<guard class name>",
+/// "require-client-classes": [ <list of required class names> ],
+/// "reservation-mode": <host reservation mode>,
+/// "relay": <relay ip address(es)>,
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46]-server is with name as the list key:
+/// @code
+/// +--rw shared-network* [name]
+/// +--rw name string
+/// +--rw interface? string
+/// +--rw renew-timer? uint32
+/// +--rw rebind-timer? uint32
+/// +--rw option-data* [code space]
+/// +--rw reservation-mode? host-reservation-mode
+/// +--rw client-class? string
+/// +--rw require-client-classes* string
+/// +--rw valid-lifetime? uint32
+/// +--rw min-valid-lifetime? uint32
+/// +--rw max-valid-lifetime? uint32
+/// +--rw calculate-tee-times? boolean
+/// +--rw t1-percent? decimal64
+/// +--rw t2-percent? decimal64
+/// +--rw cache-max-age? uint32
+/// +--rw cache-threshold? decimal64
+/// +--rw ddns-generated-prefix? string
+/// +--rw ddns-override-client-update? boolean
+/// +--rw ddns-override-no-update? boolean
+/// +--rw ddns-qualifying-suffix? string
+/// +--rw ddns-replace-client-name? string
+/// +--rw ddns-send-updates? boolean
+/// +--rw ddns-update-on-renew? boolean
+/// +--rw ddns-use-conflict-resolution? boolean
+/// +--rw store-extended-info? boolean
+/// +--rw hostname-char-replacement? string
+/// +--rw hostname-char-set? string
+/// +--rw reservations-global? boolean
+/// +--rw reservations-in-subnet? boolean
+/// +--rw reservations-out-of-pool? boolean
+/// +--rw user-context? user-context
+///
+/// DHCPv4 only:
+/// +--rw subnet4*
+/// +--rw match-client-id? boolean
+/// +--rw next-server? inet:ipv4-address
+/// +--rw server-hostname? string
+/// +--rw boot-file-name? string
+/// +--rw authoritative? boolean
+/// +--rw relay
+/// | +--rw ip-addresses* inet:ipv4-address
+///
+/// DHCPv6 only:
+/// +--rw subnet6*
+/// +--rw preferred-lifetime? uint32
+/// +--rw min-preferred-lifetime? uint32
+/// +--rw max-preferred-lifetime? uint32
+/// +--rw interface-id? string
+/// +--rw rapid-commit? boolean
+/// +--rw relay
+/// | +--rw ip-addresses* inet:ipv6-address
+/// @endcode
+///
+/// An example in JSON and YANG formats:
+/// @code
+/// [
+/// {
+/// "name": "foo",
+/// "subnet6":
+/// [
+/// {
+/// "subnet": "2001:db8::/48",
+/// "id": 123
+/// }
+/// ]
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp6-server:config (container)
+/// /kea-dhcp6-server:config/shared-network[name='foo'] (list instance)
+/// /kea-dhcp6-server:config/shared-network[name='foo']/name = foo
+/// /kea-dhcp6-server:config/shared-network[name='foo']/
+/// subnet6[id='123'] (list instance)
+/// /kea-dhcp6-server:config/shared-network[name='foo']/
+/// subnet6[id='123']/id = 123
+/// /kea-dhcp6-server:config/shared-network[name='foo']/
+/// subnet6[id='123']/subnet = 2001:db8::/48
+/// @endcode
+
+/// @brief A translator class for converting a shared network between
+/// YANG and JSON.
+///
+/// Currently supports the following models:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+class TranslatorSharedNetwork : virtual public TranslatorSubnets {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorSharedNetwork(sysrepo::Session session,
+ const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorSharedNetwork() = default;
+
+ /// @brief Translate a shared network from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the shared network
+ ///
+ /// @return the JSON representation of the shared network
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getSharedNetwork(libyang::DataNode const& data_node);
+
+ /// @brief Translate a shared network from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the shared network.
+ ///
+ /// @return JSON representation of the shared network.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getSharedNetworkFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set shared network from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the shared network.
+ /// @param elem The JSON element.
+ void setSharedNetwork(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getSharedNetwork for kea-dhcp4-server and
+ /// kea-dhcp6-server models
+ ///
+ /// @param data_node the YANG node representing the shared nework
+ /// @param subsel The subnet list name (either "subnet4" or "subnet6").
+ ///
+ /// @return JSON representation of the shared network.
+ isc::data::ElementPtr getSharedNetworkKea(libyang::DataNode const& data_node,
+ const std::string& subsel);
+
+ /// @brief setSharedNetwork for kea-dhcp4-server and
+ /// kea-dhcp6-server models
+ ///
+ /// @param xpath The xpath of the shared network.
+ /// @param elem The JSON element.
+ /// @param subsel The subnet list name (either "subnet4" or "subnet6").
+ void setSharedNetworkKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem,
+ const std::string& subsel);
+}; // TranslatorSharedNetwork
+
+/// @brief A translator class for converting a shared network list between
+/// YANG and JSON.
+///
+/// Currently supports the following models:
+/// - kea-dhcp4-server
+/// - kea-dhcp6-server
+class TranslatorSharedNetworks : virtual public TranslatorSharedNetwork {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorSharedNetworks(sysrepo::Session session,
+ const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorSharedNetworks() = default;
+
+ /// @brief Translate shared networks from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of shared networks
+ ///
+ /// @return the JSON representation of the list of shared networks
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getSharedNetworks(libyang::DataNode const& data_node);
+
+ /// @brief Translate shared networks from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the shared network list.
+ ///
+ /// @return the JSON representation of the list of shared networks
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getSharedNetworksFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set shared networks from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the shared network list.
+ /// @param elem The JSON element.
+ void setSharedNetworks(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief setSharedNetworks for kea-dhcp4-server and
+ /// kea-dhcp6-server
+ ///
+ /// @param xpath The xpath of the shared network list.
+ /// @param elem The JSON element.
+ /// @throw BadValue on a shared network without name.
+ void setSharedNetworksKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorSharedNetworks
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_SHARED_NETWORK_H
diff --git a/src/lib/yang/translator_subnet.cc b/src/lib/yang/translator_subnet.cc
new file mode 100644
index 0000000..438895a
--- /dev/null
+++ b/src/lib/yang/translator_subnet.cc
@@ -0,0 +1,430 @@
+// Copyright (C) 2018-2023 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 <yang/adaptor_pool.h>
+#include <yang/translator_subnet.h>
+#include <yang/yang_models.h>
+
+#include <sstream>
+
+using namespace std;
+using namespace isc::data;
+using namespace libyang;
+using namespace sysrepo;
+
+namespace isc {
+namespace yang {
+
+TranslatorSubnet::TranslatorSubnet(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorPool(session, model),
+ TranslatorPools(session, model),
+ TranslatorPdPool(session, model),
+ TranslatorPdPools(session, model),
+ TranslatorHost(session, model),
+ TranslatorHosts(session, model) {
+}
+
+ElementPtr
+TranslatorSubnet::getSubnet(DataNode const& data_node) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ return (getSubnetIetf6(data_node));
+ } else if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ return (getSubnetKea(data_node));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting subnet:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getSubnet not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorSubnet::getSubnetFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getSubnet(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorSubnet::getSubnetIetf6(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+ getMandatoryDivergingLeaf(result, data_node, "subnet", "network-prefix");
+ getMandatoryDivergingLeaf(result, data_node, "id", "network-range-id");
+
+ checkAndGetDiverging(result, data_node, "pools", "address-pools",
+ [&](DataNode const& node) -> ElementPtr const {
+ return getPools(node);
+ });
+
+ checkAndGet(result, data_node, "pd-pools",
+ [&](DataNode const& node) -> ElementPtr const {
+ return getPdPools(node);
+ });
+
+ ConstElementPtr description = getItem(data_node, "network-description");
+ if (description) {
+ ElementPtr context = Element::createMap();
+ context->set("description", description);
+ result->set("user-context", context);
+ }
+
+ if (result->get("pools")) {
+ AdaptorPool::toSubnet(model_, result, result->get("pools"));
+ }
+
+ /// @todo: timers
+ /// @todo: option-data
+ /// @todo: reservations
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+ElementPtr
+TranslatorSubnet::getSubnetKea(DataNode const& data_node) {
+ ElementPtr result = Element::createMap();
+
+ getMandatoryLeaf(result, data_node, "id");
+ getMandatoryLeaf(result, data_node, "subnet");
+
+ checkAndGetLeaf(result, data_node, "allocator");
+ checkAndGetLeaf(result, data_node, "cache-max-age");
+ checkAndGetLeaf(result, data_node, "cache-threshold");
+ checkAndGetLeaf(result, data_node, "calculate-tee-times");
+ checkAndGetLeaf(result, data_node, "client-class");
+ checkAndGetLeaf(result, data_node, "ddns-generated-prefix");
+ checkAndGetLeaf(result, data_node, "ddns-override-client-update");
+ checkAndGetLeaf(result, data_node, "ddns-override-no-update");
+ checkAndGetLeaf(result, data_node, "ddns-qualifying-suffix");
+ checkAndGetLeaf(result, data_node, "ddns-replace-client-name");
+ checkAndGetLeaf(result, data_node, "ddns-send-updates");
+ checkAndGetLeaf(result, data_node, "ddns-ttl-percent");
+ checkAndGetLeaf(result, data_node, "ddns-update-on-renew");
+ checkAndGetLeaf(result, data_node, "ddns-use-conflict-resolution");
+ checkAndGetLeaf(result, data_node, "hostname-char-replacement");
+ checkAndGetLeaf(result, data_node, "hostname-char-set");
+ checkAndGetLeaf(result, data_node, "interface");
+ checkAndGetLeaf(result, data_node, "max-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "min-valid-lifetime");
+ checkAndGetLeaf(result, data_node, "rebind-timer");
+ checkAndGetLeaf(result, data_node, "renew-timer");
+ checkAndGetLeaf(result, data_node, "require-client-classes");
+ checkAndGetLeaf(result, data_node, "reservation-mode");
+ checkAndGetLeaf(result, data_node, "reservations-global");
+ checkAndGetLeaf(result, data_node, "reservations-in-subnet");
+ checkAndGetLeaf(result, data_node, "reservations-out-of-pool");
+ checkAndGetLeaf(result, data_node, "store-extended-info");
+ checkAndGetLeaf(result, data_node, "t1-percent");
+ checkAndGetLeaf(result, data_node, "t2-percent");
+ checkAndGetLeaf(result, data_node, "valid-lifetime");
+
+ checkAndGetAndJsonifyLeaf(result, data_node, "user-context");
+
+ ConstElementPtr options = getOptionDataList(data_node);
+ if (options) {
+ result->set("option-data", options);
+ }
+
+ ConstElementPtr pools = getPools(data_node);
+ if (pools) {
+ result->set("pools", pools);
+ }
+
+ checkAndGet(result, data_node, "relay",
+ [&](DataNode const& node) -> ElementPtr const {
+ ElementPtr relay(Element::createMap());
+ checkAndGetLeaf(relay, node, "ip-addresses");
+ return relay;
+ });
+
+ ConstElementPtr hosts = getHosts(data_node);
+ if (hosts) {
+ result->set("reservations", hosts);
+ }
+
+ if (model_ == KEA_DHCP6_SERVER) {
+ checkAndGetLeaf(result, data_node, "interface-id");
+ checkAndGetLeaf(result, data_node, "max-preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "min-preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "pd-allocator");
+ checkAndGetLeaf(result, data_node, "preferred-lifetime");
+ checkAndGetLeaf(result, data_node, "rapid-commit");
+
+ ElementPtr pd_pools = getPdPools(data_node);
+ if (pd_pools) {
+ result->set("pd-pools", pd_pools);
+ }
+ } else if (model_ == KEA_DHCP4_SERVER) {
+ checkAndGetLeaf(result, data_node, "authoritative");
+ checkAndGetLeaf(result, data_node, "boot-file-name");
+ checkAndGetLeaf(result, data_node, "match-client-id");
+ checkAndGetLeaf(result, data_node, "next-server");
+ checkAndGetLeaf(result, data_node, "offer-lifetime");
+ checkAndGetLeaf(result, data_node, "server-hostname");
+
+ checkAndGetDivergingLeaf(result, data_node, "4o6-interface", "subnet-4o6-interface");
+ checkAndGetDivergingLeaf(result, data_node, "4o6-interface-id", "subnet-4o6-interface-id");
+ checkAndGetDivergingLeaf(result, data_node, "4o6-subnet", "subnet-4o6-subnet");
+ }
+
+ return (result->empty() ? ElementPtr() : result);
+}
+
+void
+TranslatorSubnet::setSubnet(string const& xpath, ConstElementPtr elem) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ setSubnetIetf6(xpath, elem);
+ } else if ((model_ == KEA_DHCP4_SERVER) ||
+ (model_ == KEA_DHCP6_SERVER)) {
+ setSubnetKea(xpath, elem);
+ } else {
+ isc_throw(NotImplemented,
+ "setSubnet not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting subnet '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorSubnet::setSubnetIetf6(string const& xpath, ConstElementPtr elem) {
+ /// Skip key "id".
+
+ setMandatoryDivergingLeaf(elem, xpath, "subnet", "network-prefix", LeafBaseType::String);
+
+ AdaptorPool::fromSubnet(model_, elem, elem->get("pools"));
+ ConstElementPtr context = elem->get("user-context");
+ if (context && context->contains("description")) {
+ ConstElementPtr description = context->get("description");
+ if (description->getType() == Element::string) {
+ setItem(xpath + "/network-description", description, LeafBaseType::String);
+ }
+ }
+ ConstElementPtr subnet = elem->get("subnet");
+ if (!subnet) {
+ isc_throw(BadValue, "setSubnetIetf6 requires subnet: " << elem->str());
+ }
+ setItem(xpath + "/network-prefix", subnet, LeafBaseType::String);
+ ConstElementPtr pools = elem->get("pools");
+ if (pools && !pools->empty()) {
+ setPools(xpath + "/address-pools", pools);
+ }
+ pools = elem->get("pd-pools");
+ if (pools && !pools->empty()) {
+ setPdPools(xpath + "/pd-pools", pools);
+ }
+
+ /// @todo: option-data
+ /// @todo: reservations
+}
+
+void
+TranslatorSubnet::setSubnetKea(string const& xpath, ConstElementPtr elem) {
+ /// Skip key "id".
+
+ ConstElementPtr subnet = elem->get("subnet");
+ if (!subnet) {
+ isc_throw(BadValue, "setSubnetKea requires subnet: " << elem->str());
+ }
+ setItem(xpath + "/subnet", subnet, LeafBaseType::String);
+
+ checkAndSetLeaf(elem, xpath, "allocator", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "cache-max-age", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "cache-threshold", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "calculate-tee-times", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "client-class", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-generated-prefix", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-override-client-update", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-override-no-update", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-qualifying-suffix", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-replace-client-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "ddns-send-updates", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-ttl-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "ddns-update-on-renew", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "ddns-use-conflict-resolution", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "hostname-char-replacement", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "hostname-char-set", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "interface", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "max-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-valid-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "rebind-timer", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "renew-timer", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "reservation-mode", LeafBaseType::Enum);
+ checkAndSetLeaf(elem, xpath, "reservations-global", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "reservations-in-subnet", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "reservations-out-of-pool", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "store-extended-info", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "t1-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "t2-percent", LeafBaseType::Dec64);
+ checkAndSetLeaf(elem, xpath, "valid-lifetime", LeafBaseType::Uint32);
+
+ checkAndSetLeafList(elem, xpath, "require-client-classes", LeafBaseType::String);
+
+ ConstElementPtr options = elem->get("option-data");
+ if (options && !options->empty()) {
+ setOptionDataList(xpath, options);
+ }
+ ConstElementPtr pools = elem->get("pools");
+ if (pools && !pools->empty()) {
+ setPools(xpath, pools);
+ }
+ ConstElementPtr relay = elem->get("relay");
+ if (relay) {
+ ConstElementPtr address = relay->get("ip-address");
+ ConstElementPtr addresses = relay->get("ip-addresses");
+ if (address) {
+ setItem(xpath + "/relay/ip-addresses", address, LeafBaseType::String);
+ } else if (addresses && !addresses->empty()) {
+ for (ElementPtr const& addr : addresses->listValue()) {
+ setItem(xpath + "/relay/ip-addresses", addr, LeafBaseType::String);
+ }
+ }
+ }
+ ConstElementPtr hosts = elem->get("reservations");
+ if (hosts && !hosts->empty()) {
+ setHosts(xpath, hosts);
+ }
+
+ if (model_ == KEA_DHCP6_SERVER) {
+ checkAndSetLeaf(elem, xpath, "interface-id", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "max-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "min-preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "pd-allocator", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "preferred-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "rapid-commit", LeafBaseType::Bool);
+
+ pools = elem->get("pd-pools");
+ if (pools && !pools->empty()) {
+ setPdPools(xpath, pools);
+ }
+ } else {
+ checkAndSetLeaf(elem, xpath, "authoritative", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "boot-file-name", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "match-client-id", LeafBaseType::Bool);
+ checkAndSetLeaf(elem, xpath, "next-server", LeafBaseType::String);
+ checkAndSetLeaf(elem, xpath, "offer-lifetime", LeafBaseType::Uint32);
+ checkAndSetLeaf(elem, xpath, "server-hostname", LeafBaseType::String);
+
+ checkAndSetDivergingLeaf(elem, xpath, "4o6-interface", "subnet-4o6-interface", LeafBaseType::String);
+ checkAndSetDivergingLeaf(elem, xpath, "4o6-interface-id", "subnet-4o6-interface-id", LeafBaseType::String);
+ checkAndSetDivergingLeaf(elem, xpath, "4o6-subnet", "subnet-4o6-subnet", LeafBaseType::String);
+ }
+ checkAndSetUserContext(elem, xpath);
+}
+
+TranslatorSubnets::TranslatorSubnets(Session session, const string& model)
+ : Translator(session, model),
+ TranslatorOptionData(session, model),
+ TranslatorOptionDataList(session, model),
+ TranslatorPool(session, model),
+ TranslatorPools(session, model),
+ TranslatorPdPool(session, model),
+ TranslatorPdPools(session, model),
+ TranslatorHost(session, model),
+ TranslatorHosts(session, model),
+ TranslatorSubnet(session, model) {
+}
+
+ElementPtr
+TranslatorSubnets::getSubnets(DataNode const& data_node) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ return (getSubnetsCommon(data_node, "network-range"));
+ } else if (model_ == KEA_DHCP4_SERVER) {
+ return (getSubnetsCommon(data_node, "subnet4"));
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ return (getSubnetsCommon(data_node, "subnet6"));
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "getting subnets:"
+ << ex.what());
+ }
+ isc_throw(NotImplemented,
+ "getSubnets not implemented for the model: " << model_);
+}
+
+ElementPtr
+TranslatorSubnets::getSubnetsFromAbsoluteXpath(string const& xpath) {
+ try {
+ return getSubnets(findXPath(xpath));
+ } catch (NetconfError const&) {
+ return ElementPtr();
+ }
+}
+
+ElementPtr
+TranslatorSubnets::getSubnetsCommon(DataNode const& data_node,
+ string const& subsel) {
+ return getList<TranslatorSubnet>(data_node, subsel, *this,
+ &TranslatorSubnet::getSubnet);
+}
+
+void
+TranslatorSubnets::setSubnets(string const& xpath, ConstElementPtr elem) {
+ try {
+ if (model_ == IETF_DHCPV6_SERVER) {
+ setSubnetsIetf6(xpath, elem);
+ } else if (model_ == KEA_DHCP4_SERVER) {
+ setSubnetsKea(xpath, elem, "subnet4");
+ } else if (model_ == KEA_DHCP6_SERVER) {
+ setSubnetsKea(xpath, elem, "subnet6");
+ } else {
+ isc_throw(NotImplemented,
+ "setSubnets not implemented for the model: " << model_);
+ }
+ } catch (Error const& ex) {
+ isc_throw(NetconfError,
+ "setting subnets '" << elem->str()
+ << "' : " << ex.what());
+ }
+}
+
+void
+TranslatorSubnets::setSubnetsIetf6(string const& xpath, ConstElementPtr elem) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr subnet = elem->getNonConst(i);
+ ostringstream range;
+ range << xpath << "/network-range[network-range-id='";
+ ConstElementPtr id = subnet->get("id");
+ if (!id) {
+ isc_throw(BadValue, "subnet without id: " << elem->str());
+ }
+ range << id->intValue() << "']";
+ setSubnet(range.str(), subnet);
+ }
+}
+
+void
+TranslatorSubnets::setSubnetsKea(string const& xpath, ConstElementPtr elem,
+ string const& subsel) {
+ for (size_t i = 0; i < elem->size(); ++i) {
+ ElementPtr subnet = elem->getNonConst(i);
+ if (!subnet->contains("id")) {
+ isc_throw(BadValue, "subnet without id: " << subnet->str());
+ }
+ ostringstream prefix;
+ prefix << xpath << "/" << subsel << "[id='"
+ << subnet->get("id")->intValue() << "']";
+ setSubnet(prefix.str(), subnet);
+ }
+}
+
+} // namespace yang
+} // namespace isc
diff --git a/src/lib/yang/translator_subnet.h b/src/lib/yang/translator_subnet.h
new file mode 100644
index 0000000..b5e48d7
--- /dev/null
+++ b/src/lib/yang/translator_subnet.h
@@ -0,0 +1,411 @@
+// Copyright (C) 2018-2022 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 ISC_TRANSLATOR_SUBNET_H
+#define ISC_TRANSLATOR_SUBNET_H 1
+
+#include <yang/translator.h>
+#include <yang/translator_pool.h>
+#include <yang/translator_pd_pool.h>
+#include <yang/translator_host.h>
+
+namespace isc {
+namespace yang {
+
+/// Subnet (aka network range) translation between YANG and JSON
+///
+/// JSON syntax for kea-dhcp4 is:
+/// @code
+/// {
+/// "valid-lifetime": <valid lifetime>,
+/// "min-valid-lifetime": <minimum valid lifetime>,
+/// "max-valid-lifetime": <maximum valid lifetime>,
+/// "renew-timer": <renew timer>,
+/// "rebind-timer": <rebind timer>,
+/// "calculate-tee-times": <calculate T1/T2 times>,
+/// "t1-percent": <T1 percent>,
+/// "t2-percent": <T2 percent>,
+/// "option-data": [ <list of option data> ],
+/// "pools": [ <list of pools> ],
+/// "subnet": "<subnet prefix>",
+/// "interface": "<interface>",
+/// "id": <id>,
+/// "client-class": "<guard class name>",
+/// "require-client-classes": [ <list of required class names> ],
+/// "reservations": [ <list of host reservations> ],
+/// "reservation-mode": <host reservation mode>,
+/// "relay": <relay ip address(es)>,
+/// "match-client-id": <match client id flag>,
+/// "next-server": "<next server>",
+/// "server-hostname": "<server hostname>",
+/// "boot-file-name": "<boot file name>",
+/// "4o6-interface": "<dhcpv4-over-dhcpv6 interface>",
+/// "4o6-interface-id": "<dhcpv4-over-dhcpv6 interface id>",
+/// "4o6-subnet": "<dhcpv4-over-dhcpv6 subnet>",
+/// "authoritative": <authoritative flag>,
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// JSON syntax for kea-dhcp6 is:
+/// @code
+/// {
+/// "preferred-lifetime": <preferred lifetime>,
+/// "min-preferred-lifetime": <minimum preferred lifetime>,
+/// "max-preferred-lifetime": <maximum preferred lifetime>,
+/// "valid-lifetime": <valid lifetime>,
+/// "min-valid-lifetime": <minimum valid lifetime>,
+/// "max-valid-lifetime": <maximum valid lifetime>,
+/// "renew-timer": <renew timer>,
+/// "rebind-timer": <rebind timer>,
+/// "calculate-tee-times": <calculate T1/T2 times>,
+/// "t1-percent": <T1 percent>,
+/// "t2-percent": <T2 percent>,
+/// "option-data": [ <list of option data> ],
+/// "pools": [ <list of pools> ],
+/// "pd-pools": [ <list of prefix delegation pools> ],
+/// "subnet": "<subnet prefix>",
+/// "interface": "<interface>",
+/// "interface-id": "<interface id>",
+/// "id": <id>,
+/// "rapid-commit": <rapid commit flag>,
+/// "client-class": "<guard class name>",
+/// "require-client-classes": [ <list of required class names> ],
+/// "reservations": [ <list of host reservations> ],
+/// "reservation-mode": <host reservation mode>,
+/// "relay": <relay ip address(es)>,
+/// "user-context": { <json map> },
+/// "comment": "<comment>"
+/// }
+/// @endcode
+///
+/// YANG syntax for ietf-dhcpv6-server is with network-range-id as the key:
+/// @code
+/// +--rw network-range-id uint32
+/// +--rw network-description string
+/// +--rw network-prefix inet:ipv4-prefix
+/// +--rw option-set-id?
+/// /server/server-config/option-sets/option-set/option-set-id
+/// +--rw address-pool* [pool-id]
+/// +--rw pd-pool* [pool-id]
+/// +--rw host-reservations host-reservation* [cli-id]
+/// @endcode
+///
+/// YANG syntax for kea-dhcp[46]-server is with id as the key:
+/// @code
+/// +--rw subnet[46]* [id]
+/// +--rw valid-lifetime? uint32
+/// +--rw min-valid-lifetime? uint32
+/// +--rw max-valid-lifetime? uint32
+/// +--rw renew-timer? uint32
+/// +--rw rebind-timer? uint32
+/// +--rw calculate-tee-times? boolean
+/// +--rw t1-percent? decimal64
+/// +--rw t2-percent? decimal64
+/// +--rw option-data* [code space]
+/// +--rw pool* [start-address end-address]
+/// +--rw subnet inet:ipv4-prefix
+/// +--rw interface? string
+/// +--rw id uint32
+/// +--rw client-class? string
+/// +--rw require-client-classes* string
+/// +--rw host* [identifier-type identifier]
+/// +--rw reservation-mode? host-reservation-mode
+/// +--rw relay
+/// +--rw cache-max-age? uint32
+/// +--rw cache-threshold? decimal64
+/// +--rw ddns-generated-prefix? string
+/// +--rw ddns-override-client-update? boolean
+/// +--rw ddns-override-no-update? boolean
+/// +--rw ddns-qualifying-suffix? string
+/// +--rw ddns-replace-client-name? string
+/// +--rw ddns-send-updates? boolean
+/// +--rw ddns-update-on-renew? boolean
+/// +--rw ddns-use-conflict-resolution? boolean
+/// +--rw hostname-char-replacement? string
+/// +--rw hostname-char-set? string
+/// +--rw reservations-global? boolean
+/// +--rw reservations-in-subnet? boolean
+/// +--rw reservations-out-of-pool? boolean
+/// +--rw store-extended-info? boolean
+/// +--rw user-context? user-context
+///
+/// DHCPv4 only:
+/// +--rw match-client-id? boolean
+/// +--rw next-server? inet:ipv4-address
+/// +--rw server-hostname? string
+/// +--rw boot-file-name? string
+/// +--rw subnet-4o6-interface? string
+/// +--rw subnet-4o6-interface-id? string
+/// +--rw subnet-4o6-subnet? inet:ipv6-prefix
+/// +--rw authoritative? boolean
+///
+/// DHCPv6 only:
+/// +--rw preferred-lifetime? uint32
+/// +--rw min-preferred-lifetime? uint32
+/// +--rw max-preferred-lifetime? uint32
+/// +--rw pd-pool*
+/// +--rw interface-id? string
+/// +--rw rapid-commit? boolean
+/// @endcode
+///
+/// An example in JSON and YANG formats for the IETF model:
+/// @code
+/// [
+/// {
+/// "subnet": "2001:db8::/48",
+/// "id": 123,
+/// "pools":
+/// [
+/// {
+/// "pool": "2001:db8::1:0/112"
+/// },
+/// {
+/// "pool": "2001:db8::2:0/112"
+/// }
+/// ]
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /ietf-dhcpv6-server:server (container)
+/// /ietf-dhcpv6-server:server/server-config (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123'] (list instance)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/network-range-id = 123
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/network-prefix = 2001:db8::/48
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools (container)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='0'] (list instance)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='0']/pool-id = 0
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='0']/pool-prefix = 2001:db8::1:0/112
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='0']/start-address = 2001:db8::1:0
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='0']/end-address = 2001:db8::1:ffff
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='0']/max-address-count = disabled
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='1'] (list instance)
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='1']/pool-id = 1
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='1']/pool-prefix = 2001:db8::2:0/112
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='1']/start-address = 2001:db8::2:0
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='1']/end-address = 2001:db8::2:ffff
+/// /ietf-dhcpv6-server:server/server-config/network-ranges/
+/// network-range[network-range-id='123']/address-pools/
+/// address-pool[pool-id='1']/max-address-count = disabled
+/// @endcode
+///
+/// An example in JSON and YANG formats for the Kea model:
+/// @code
+/// [
+/// {
+/// "subnet": "10.0.1.0/24",
+/// "id": 123,
+/// "pools":
+/// [
+/// {
+/// "pool": "10.0.1.0/28",
+/// },
+/// {
+/// "pool": "10.0.1.200 - 10.0.1.222"
+/// }
+/// ]
+/// }
+/// ]
+/// @endcode
+/// @code
+/// /kea-dhcp4-server:config (container)
+/// /kea-dhcp4-server:config/subnet4[id='123'] (list instance)
+/// /kea-dhcp4-server:config/subnet4[id='123']/id = 123
+/// /kea-dhcp4-server:config/subnet4[id='123']/
+/// pool[start-address='10.0.1.0'][end-address='10.0.1.15'] (list instance)
+/// /kea-dhcp4-server:config/subnet4[id='123']/
+/// pool[start-address='10.0.1.0'][end-address='10.0.1.15']/
+/// start-address = 10.0.1.0
+/// /kea-dhcp4-server:config/subnet4[id='123']/
+/// pool[start-address='10.0.1.0'][end-address='10.0.1.15']/
+/// end-address = 10.0.1.15
+/// /kea-dhcp4-server:config/subnet4[id='123']/
+/// pool[start-address='10.0.1.0'][end-address='10.0.1.15']/
+/// prefix = 10.0.1.0/28
+/// /kea-dhcp4-server:config/subnet4[id='123']/
+/// pool[start-address='10.0.1.200'][end-address='10.0.1.222']
+/// (list instance)
+/// /kea-dhcp4-server:config/subnet4[id='123']/
+/// pool[start-address='10.0.1.200'][end-address='10.0.1.222']/
+/// start-address = 10.0.1.200
+/// /kea-dhcp4-server:config/subnet4[id='123']/
+/// pool[start-address='10.0.1.200'][end-address='10.0.1.222']/
+/// end-address = 10.0.1.222
+/// /kea-dhcp4-server:config/subnet4[id='123']/subnet = 10.0.1.0/24
+/// @endcode
+
+/// @brief A translator class for converting a subnet between YANG and JSON.
+///
+/// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server.
+class TranslatorSubnet : virtual public TranslatorPools,
+ virtual public TranslatorPdPools, virtual public TranslatorHosts {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorSubnet(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorSubnet() = default;
+
+ /// @brief Get and translate a subnet from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the subnet configuration
+ ///
+ /// @return the JSON representation of the subnet
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ /// @throw BadValue on a subnet without prefix or id.
+ isc::data::ElementPtr getSubnet(libyang::DataNode const& data_node);
+
+ /// @brief Get and translate a subnet from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the subnet.
+ ///
+ /// @return JSON representation of the subnet.
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ /// @throw BadValue on a subnet without prefix or id.
+ isc::data::ElementPtr getSubnetFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set subnet from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the subnet.
+ /// @param elem The JSON element.
+ /// @throw BadValue on a subnet without prefix.
+ void setSubnet(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getSubnet for ietf-dhcpv6-server.
+ ///
+ /// @param data_node the YANG node representing the subnet configuration
+ ///
+ /// @return JSON representation of the subnet.
+ isc::data::ElementPtr getSubnetIetf6(libyang::DataNode const& data_node);
+
+ /// @brief setSubnet for ietf-dhcpv6-server.
+ ///
+ /// @param xpath The xpath of the subnet.
+ /// @param elem The JSON element.
+ void setSubnetIetf6(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief getSubnet for kea-dhcp[46]-server.
+ ///
+ /// @param data_node the YANG node representing the subnet configuration
+ ///
+ /// @return JSON representation of the subnet.
+ isc::data::ElementPtr getSubnetKea(libyang::DataNode const& data_node);
+
+ /// @brief setSubnet for kea-dhcp[46]-server.
+ ///
+ /// @param xpath The xpath of the subnet.
+ /// @param elem The JSON element.
+ void setSubnetKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+}; // TranslatorSubnets
+
+/// @brief A translator class for converting a subnet list between
+/// YANG and JSON.
+///
+/// Currently supports on kea-dhcp[46]-server and partially ietf-dhcpv6-server.
+class TranslatorSubnets : virtual public TranslatorSubnet {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param session Sysrepo session.
+ /// @param model Model name.
+ TranslatorSubnets(sysrepo::Session session, const std::string& model);
+
+ /// @brief Destructor.
+ virtual ~TranslatorSubnets() = default;
+
+ /// @brief Get and translate subnets from YANG to JSON.
+ ///
+ /// @param data_node the YANG node representing the list of subnets
+ ///
+ /// @return the JSON representation of the list of subnets
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getSubnets(libyang::DataNode const& data_node);
+
+ /// @brief Get and translate subnets from YANG to JSON.
+ ///
+ /// @param xpath The xpath of the subnet list.
+ ///
+ /// @return the JSON representation of the list of subnets
+ ///
+ /// @throw NetconfError when sysrepo raises an error.
+ isc::data::ElementPtr getSubnetsFromAbsoluteXpath(std::string const& xpath);
+
+ /// @brief Translate and set subnets from JSON to YANG.
+ ///
+ /// @param xpath The xpath of the subnet list.
+ /// @param elem The JSON element.
+ /// @throw BadValue on a subnet without id.
+ void setSubnets(const std::string& xpath, isc::data::ConstElementPtr elem);
+
+protected:
+ /// @brief getSubnets common part.
+ ///
+ /// @param data_node the YANG node representing the list of subnets
+ /// @param subsel The subnet list name.
+ ///
+ /// @return the JSON representation of the list of subnets
+ isc::data::ElementPtr getSubnetsCommon(libyang::DataNode const& data_node,
+ const std::string& subsel);
+
+ /// @brief setSubnets for ietf-dhcpv6-server.
+ ///
+ /// @param xpath The xpath of the subnet list.
+ /// @param elem The JSON element.
+ void setSubnetsIetf6(const std::string& xpath,
+ isc::data::ConstElementPtr elem);
+
+ /// @brief setSubnets for kea-dhcp[46]-server.
+ ///
+ /// @param xpath The xpath of the subnet list.
+ /// @param elem The JSON element.
+ /// @param subsel The subnet list name.
+ void setSubnetsKea(const std::string& xpath,
+ isc::data::ConstElementPtr elem,
+ const std::string& subsel);
+}; // TranslatorSubnets
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_TRANSLATOR_SUBNET_H
diff --git a/src/lib/yang/yang.dox b/src/lib/yang/yang.dox
new file mode 100644
index 0000000..e0fcab0
--- /dev/null
+++ b/src/lib/yang/yang.dox
@@ -0,0 +1,276 @@
+// Copyright (C) 2018-2022 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/.
+
+/**
+ @page libyang libkea-yang - Kea YANG Utilities Library
+
+The libkea-yang library was developed to handle base YANG operations,
+such as retrieving YANG schema and configuration and translating
+data between YANG and JSON that is understandable by Kea.
+
+@section yangTranslator Translator between YANG and JSON
+
+An essential concept is the idea of translator. It is a primitive that is able
+to convert certain data structure between YANG and JSON. It is envisaged
+that more complex translators will use other translators to handle more
+complex data structures. For details, see @ref isc::yang::Translator.
+It is also envisioned that translators could do the translation automatically by
+relying on capabilities of iterating through data, retrieving data type
+information and value information from nodes through the libyang and sysrepo
+APIs.
+
+Note that although the initial focus is on translation from YANG to JSON (so
+Kea can retrieve its configuration), the opposite translation direction -
+from JSON to YANG - is also very useful, for at least three reasons. First,
+in many cases we can use it in tests to check that conversion back and forth
+doesn't lose anything: yang = toYang(toJson(yang)). Secondly, YANG modules
+cover two major types of data: configuration and run-time state. While
+we're initially focusing on getting the configuration, the run-time state
+is something that Kea is expected to provide. Kea uses JSON internally in many
+places and that data will have to be exported in YANG format. Thirdly, the
+Event::Update (SR_EV_UPDATE prior to sysrepo v2) callback allows mid-flight
+configuration changes before data is
+committed to the sysrepo datastore. If it ever will be used in the future,
+changes applied during this step will most likely come from Kea's current JSON
+configuration. As such, JSON to YANG translation will be necessary. One
+application for this is reverting stuff that is necessary for Kea - Sysrepo
+communication like the unix socket.
+
+All translators take a Session pointer (a structure provided by Sysrepo that
+is responsible for maintaining a connection) in constructors and derive from
+the basic / base class and recursively from translators for embedded parts.
+
+@c isc::yang::Translator provides several public methods:
+
+ - @c isc::yang::Translator::checkAndGet() is able to retrieve a YANG node that
+ can be retrieved through complex logic which is abstracted through a lambda.
+
+ - @c isc::yang::Translator::checkAndGetLeaf() is a convenience wrapper
+ over @c isc::yang::Translator::getItem().
+
+ - @c isc::yang::Translator::checkAndGetAndJsonifyLeaf() retrieves elements
+ that are strings in the YANG schema, but that require passing through
+ @c isc::data::Element::fromJSON() when translating to ElementPtr.
+
+ - @c isc::yang::Translator::checkAndStringifyAndSetLeaf() does the opposite of
+ @c isc::yang::Translator::checkAndGetAndJsonifyLeaf(). It takes an arbitrary
+ Element and sets it as a YANG string in sysrepo.
+
+ - @c isc::yang::Translator::checkAndSetLeaf() is a convenience wrapper
+ over @c isc::yang::Translator::setItem().
+
+ - @c isc::yang::Translator::checkAndSetLeafList() is able to push multiple
+ leaf list nodes to the same xpath to form a leaf list.
+
+ - @c isc::yang::Translator::checkAndSetUserContext() is specifically tailored
+ for setting user context in sysrepo. It's use is frequent enough to have
+ earned its own function.
+
+ - @c isc::yang::Translator::deleteItem() deletes the data node found at
+ given xpath.
+
+ - @c isc::yang::Translator::findXPath() retrieves any data node found at any
+ xpath. It is computationally intensive. Use sparingly.
+
+ - @c isc::yang::Translator::forAll() iterates over the node found at
+ given xpath and all its descendants and calls the given function.
+
+ - @c isc::yang::Translator::getData() retrieves any data node found at any
+ xpath. The difference from @c isc::yang::Translator::findXPath() is that it
+ does not throw if the data node is not found and instead returns nullopt.
+
+ - @c isc::yang::Translator::getItem() retrieves and translates a leaf
+ from YANG to Element.
+
+ - @c isc::yang::Translator::getList() retrieves a list from sysrepo and
+ translates it from YANG to Element.
+
+ - @c isc::yang::Translator::getMandatoryLeaf() fetches a leaf that is expected
+ to be present in the YANG data node, most of the time a YANG key.
+
+ - @c isc::yang::Translator::schemaNodeExists() checks if an xpath is valid
+ from the YANG schema point of view. Not used anywhere, but it's here to
+ substitute logic that had been previously removed.
+
+ - @c isc::yang::Translator::setItem() translates a leaf from Element to
+ YANG and sets it in sysrepo.
+
+ - @c isc::yang::Translator::setMandatoryLeaf() sets a leaf that is expected
+ to be present in the Element node, most of the time a YANG key.
+
+ - @c isc::yang::Translator::translateFromYang(optional<DataNode>, string)
+ translates a YANG leaf to an Element node based on YANG type.
+ All YANG types are explicitly handled.
+
+ - @c isc::yang::Translator::translateToYang(ConstElementPtr, LeafBaseType)
+ translates an Element leaf to a string based on the YANG type.
+ All YANG types are explicitly handled.
+
+Some of these methods have a counterpart that have "Diverging" in their name.
+They are exceptionally used in the case where YANG xpath and Element map key are
+different. This facilitates identifying these diverging nodes.
+
+@section yangTranslatorPool Pool translator
+
+@c isc::yang::TranslatorPool is the standard example of a translator
+for a structured value. Its constructor takes a module name: the code
+implements some variants to accommodate the module with shared code
+moved into a common private routine. When called with an unsupported
+module, generic methods of all structure translators throw
+@c isc::NotImplemented.
+
+Note pools show two shortcomings in IETF modules:
+ - option sets make to track changes nearly impossible: the only easy
+ code is to translate the whole configuration.
+ - prefix and start - end forms of pool ranges are both mandatory.
+ (reported to authors' so should be fixed in the next version).
+
+All structure translators depend on @c isc::yang::Translator and
+some of them depend on other structures, for instance
+@c isc::yang::TranslatorPool depends on
+@c isc::yang::TranslatorOptionDataList which itself, as all list translators,
+depends on the corresponding list item translator
+@c isc::yang::TranslatorOptionData. This multiple inheritance forms
+a graph with the basic and the configuration translators at the two ends.
+Multiple inheritance and its diamond issue are handled by C++ with
+the virtual inheritance: depending classes must be virtually inherited
+and explicitly constructed.
+
+@section yangTranslatorSubnet Subnet translator
+
+The new thing here is the use of adaptors to move timers from subnets
+to pools and back.
+
+@section yangAdaptor Adapting JSON configuration
+
+Adaptors are tools which adapt JSON complete or partial configuration
+before translation to YANG to ease this translation or after translation
+from YANG to follow the Kea syntax, for instance by adding static
+components which are not in the module.
+
+Methods provided by adaptors are class methods (i.e. declared static).
+Specific adaptors can be derived from the isc::yang::Adaptor base class.
+
+There are a few basic adaptors and per structure adaptors. The second
+category of adaptors are divided into:
+ - from JSON to YANG adaptors or pre-processing which adapt a JSON
+ configuration to make it acceptable by a from JSON to YANG (setXXX)
+ translators. For a Kea module this kind of adaptors fill some required
+ but missing fields, or only transform a configuration into a canonical
+ form. Note for a Kea module and a configuration taken from config-get
+ or config-write it likely does nearly nothing but the code must
+ handle any hand written configuration so these adaptors are always
+ applied.
+
+ - from YANG to JSON adaptors or post-processing which adapt translated
+ YANG configuration (by getXXX) to make it acceptable by a Kea server.
+ By definition, they are not defined for Kea modules.
+
+@section unitTestsSysrepo Running unit tests with Sysrepo
+
+To run YANG/NETCONF/Sysrepo tests you need to compile Kea with Sysrepo support:
+
+@verbatim
+./configure --with-libyang --with-sysrepo --with-libyang-cpp --with-sysrepo-cpp
+@endverbatim
+
+For details, see Section "YANG/NETCONF support" in the Kea Administrator
+Reference Manual: https://kea.readthedocs.io/en/latest/arm/integrations.html#yang-netconf.
+
+You also need to install YANG modules, so the unit tests are able to
+retrieve, add, update and generally interact with the sysrepo information.
+There are several Kea modules (*.yang in src/share/yang/modules/), mostly usable
+in production, but one called keatest-module is only used in unit tests. To be
+able to run unit tests as a non-root user, which is the recommended way, make
+sure the sysrepo repository and /dev/shm/sr* are owned by said user. One way to
+prevent sporadic chown-ing is to install sysrepo and the Kea modules as
+non-root.
+
+To install all the modules, run the following script:
+
+@verbatim
+./src/share/yang/modules/utils/reinstall.sh
+@endverbatim
+
+If the YANG modules are already installed, they will be upgraded.
+To avoid any upgrade hastle, the -u flag may be passed to uninstall the modules
+before installing again.
+
+@verbatim
+./src/share/yang/modules/utils/reinstall.sh -u
+@endverbatim
+
+Alternatively to install each module, issue the following command:
+
+@verbatim
+sysrepoctl -i "src/share/yang/modules/${module}.yang"
+@endverbatim
+
+To verify that you have the schemas installed, do this:
+@verbatim
+sysrepoctl -l
+@endverbatim
+
+Make sure that keatest-module and other necessary modules are on the list.
+
+As DHCP modules are still being developed, if the revision has been bumped,
+reinstalling it will update the module automatically. Otherwise, it can be
+useful to uninstall them before reinstalling a more recent version:
+
+@verbatim
+sysrepoctl -u <module-name>
+@endverbatim
+
+Tests use these modules which you can find in src/share/yang/modules in addition
+to keatest-module:
+ - ietf-dhcpv6-server
+ - kea-ctrl-agent
+ - kea-dhcp-ddns
+ - kea-dhcp4-server
+ - kea-dhcp6-server
+
+Those modules depend on the following modules:
+ - ietf-inet-types
+ - ietf-yang-types
+ - ietf-interfaces
+ - kea-types
+ - kea-dhcp-types
+
+The following modules are extracted from the IETF DHCPv6 YANG draft:
+ - ietf-dhcpv6-client
+ - ietf-dhcpv6-options
+ - ietf-dhcpv6-relay
+ - ietf-dhcpv6-types
+
+All are available in the src/share/yang/modules directory using the
+<module-name>[@<revision>].yang syntax for file names.
+src/share/yang/modules/utils provides a few utilities for developers:
+ - check-revisions.sh which verifies if the revision in the file name
+ and in the file content matches
+ - check-hashes.sh which detects updates in the file content without
+ a revision change using the SHA-256 hash of the to YIN translation.
+ Updates hashes automatically if -a is passed to the script.
+ - gen-revisions.sh which produces the module / revision table of
+ the yang_revisions.h header file.
+ - reinstall.sh which installs all the modules.
+
+You can run this tool:
+
+@verbatim
+src/lib/yang/pretests/sysrepo_setup_tests
+@endverbatim
+
+to verify that your environment is ready. If there is anything
+wrong, it will enumerate the problems and will suggest how to solve
+them.
+
+@section yangMTConsiderations Multi-Threading Consideration for YANG Utilities
+
+The YANG utilities are not thread-safe. Note as they are used only in a
+configuration context it is not a problem, and the libyang and sysrepo libraries
+are multi-threaded so their APIs are thread-safe.
+
+*/
diff --git a/src/lib/yang/yang_models.h b/src/lib/yang/yang_models.h
new file mode 100644
index 0000000..8055d5d
--- /dev/null
+++ b/src/lib/yang/yang_models.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2018-2022 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 ISC_YANG_MODELS_H
+#define ISC_YANG_MODELS_H 1
+
+#include <string>
+
+namespace isc {
+namespace yang {
+
+/// This model is being developed at DHC working group in IETF.
+/// For details, see draft-ietf-dhc-dhcpv6-yang Internet Draft.
+/// It is a generic model that is somewhat applicable to Kea.
+static const std::string IETF_DHCPV6_SERVER = "ietf-dhcpv6-server";
+
+/// This model is being developed by ISC and is dedicated to Kea.
+static const std::string KEA_DHCP4_SERVER = "kea-dhcp4-server";
+
+/// This model is being developed by ISC and is dedicated to Kea.
+static const std::string KEA_DHCP6_SERVER = "kea-dhcp6-server";
+
+/// This model is currently in prototype phase. It will be developed
+/// by ISC in the near future.
+static const std::string KEA_DHCP_DDNS = "kea-dhcp-ddns";
+
+/// This model is currently in prototype phase. It will be developed
+/// by ISC in the near future.
+static const std::string KEA_CTRL_AGENT = "kea-ctrl-agent";
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_YANG_MODELS_H
diff --git a/src/lib/yang/yang_revisions.h b/src/lib/yang/yang_revisions.h
new file mode 100644
index 0000000..91fee57
--- /dev/null
+++ b/src/lib/yang/yang_revisions.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2018-2023 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 ISC_YANG_REVISIONS_H
+#define ISC_YANG_REVISIONS_H 1
+
+#include <string>
+#include <unordered_map>
+
+namespace isc {
+namespace yang {
+
+// Table of module name / revision.
+static const std::unordered_map<std::string, std::string> YANG_REVISIONS = {
+#ifdef KEATEST_MODULE
+ { "keatest-module", "2022-11-30" },
+#endif // KEATEST_MODULE
+ { "ietf-dhcpv6-types", "2018-09-04" },
+ { "ietf-dhcpv6-options", "2018-09-04" },
+ { "ietf-dhcpv6-server", "2018-09-04" },
+ { "kea-types", "2019-08-12" },
+ { "kea-dhcp-types", "2023-06-28" },
+ { "kea-dhcp4-server", "2023-06-28" },
+ { "kea-dhcp6-server", "2023-06-28" },
+ { "kea-ctrl-agent", "2019-08-12" },
+ { "kea-dhcp-ddns", "2022-07-27" }
+}; // YANG_REVISIONS
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_YANG_REVISIONS_H
diff --git a/src/lib/yang/yang_revisions.h.skel b/src/lib/yang/yang_revisions.h.skel
new file mode 100644
index 0000000..826f26e
--- /dev/null
+++ b/src/lib/yang/yang_revisions.h.skel
@@ -0,0 +1,27 @@
+// Copyright (C) 2018-2022 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 ISC_YANG_REVISIONS_H
+#define ISC_YANG_REVISIONS_H 1
+
+#include <string>
+#include <unordered_map>
+
+namespace isc {
+namespace yang {
+
+// Table of module name / revision.
+static const std::unordered_map<std::string, std::string> YANG_REVISIONS = {
+// Fill this by:
+// cd .../src/share/yang/modules
+// sh utils/gen-revisions.sh > r
+// insert the r file here
+}; // YANG_REVISIONS
+
+} // namespace yang
+} // namespace isc
+
+#endif // ISC_YANG_REVISIONS_H